From 301756bb43f7a9e7f1116452275d85cae39d8e1e Mon Sep 17 00:00:00 2001 From: John McMahon Date: Wed, 14 Aug 2019 17:13:37 -0700 Subject: [PATCH 1/3] feat(core) : cleanup --- docs/readme.txt | 5 +- docs/samples/Formulas/testFormulas_out.xls | Bin 13824 -> 13824 bytes pom.xml | 97 +- .../java/io/starter/OpenXLS/ChartHandle.java | 1020 ++- .../java/io/starter/OpenXLS/Document.java | 85 +- .../io/starter/OpenXLS/DocumentHandle.java | 422 +- .../java/io/starter/OpenXLS/FormatHandle.java | 2 +- src/main/java/io/starter/OpenXLS/GetInfo.java | 2 +- .../java/io/starter/OpenXLS/WorkBook.java | 231 +- .../io/starter/OpenXLS/WorkBookHandle.java | 421 +- .../java/io/starter/formats/LEO/LEOFile.java | 1395 ++-- .../io/starter/formats/LEO/LEOHeader.java | 2 +- .../io/starter/formats/LEO/StorageTable.java | 20 +- .../io/starter/formats/XLS/Boundsheet.java | 5076 +++++++----- .../formats/XLS/DiscontiguousRefStruct.java | 2 +- .../starter/formats/XLS/ExpressionParser.java | 2128 ++--- .../java/io/starter/formats/XLS/Format.java | 223 +- .../io/starter/formats/XLS/MSODrawing.java | 22 +- .../starter/formats/XLS/MSODrawingGroup.java | 2 +- .../io/starter/formats/XLS/OOXMLAdapter.java | 1587 ++-- .../io/starter/formats/XLS/OOXMLReader.java | 2 +- .../io/starter/formats/XLS/PivotCache.java | 26 +- .../starter/formats/XLS/ReferenceTracker.java | 2 +- .../io/starter/formats/XLS/Unicodestring.java | 2 +- .../java/io/starter/formats/XLS/WorkBook.java | 6810 +++++++++-------- .../starter/formats/XLS/WorkBookFactory.java | 24 +- .../XLS/WorkSheetNotFoundException.java | 53 +- .../io/starter/formats/XLS/charts/Ai.java | 1025 ++- .../starter/formats/XLS/charts/AreaChart.java | 380 +- .../formats/XLS/charts/AttachedLabel.java | 286 +- .../starter/formats/XLS/charts/Axesused.java | 27 +- .../formats/XLS/charts/BubbleChart.java | 4 +- .../io/starter/formats/XLS/charts/Chart.java | 8 +- .../formats/XLS/charts/ChartSeries.java | 2 +- .../io/starter/formats/XLS/charts/Legend.java | 10 +- .../formats/XLS/charts/OOXMLChart.java | 2 +- .../XLS/formulas/DateTimeCalculator.java | 2 +- .../formats/XLS/formulas/PtgArray.java | 988 +-- .../starter/formats/XLS/formulas/PtgErr.java | 202 +- .../formats/XLS/formulas/PtgMemArea.java | 2 +- .../formats/XML/UnicodeInputStream.java | 2 +- src/main/java/io/starter/toolkit/Base64.java | 8 +- .../java/io/starter/toolkit/LogOutputter.java | 42 +- src/main/java/io/starter/toolkit/Logger.java | 541 +- .../io/starter/toolkit/ResourceLoader.java | 5 +- .../java/io/starter/toolkit/StringTool.java | 617 +- .../io/starter/OpenXLS}/FormulaTest.java | 93 +- 47 files changed, 13351 insertions(+), 10556 deletions(-) rename src/test/{docs/samples/Formulas => java/io/starter/OpenXLS}/FormulaTest.java (76%) diff --git a/docs/readme.txt b/docs/readme.txt index 9d5b271..7f62db3 100644 --- a/docs/readme.txt +++ b/docs/readme.txt @@ -5,11 +5,8 @@ Thank you for installing OpenXLS Excel-compatible Java Spreadsheet Library. This document contains notes regarding this release that may not have been available in time to include with the release documentation. -For the most up-to-date information, please visit our resource center: -http://extentech.com/index.jsp - I - Release Notes -OpenXLS 11.0 introduces few tangible improvements over prior versions, however it does reflect new stewardship of the project from Starter Inc. +OpenXLS 11.0 introduces few major tangible improvements over prior versions, however it does reflect new stewardship of the project from Starter Inc. Please visit our GitHub page for the most recent updates. diff --git a/docs/samples/Formulas/testFormulas_out.xls b/docs/samples/Formulas/testFormulas_out.xls index c9e0626d86f8f4242ad23094f529c147968bb272..41b74641a66ffae2ee3c20a51cf2e1afa072571c 100644 GIT binary patch delta 56 zcmZq3X~@~IfkWGiQNxb+;64XtCO!s61_lOx1`pO$^Hj}^4r~mn3`}4V_o>}0!s{9~ J-{w%&1pto_4g3HA delta 56 zcmZq3X~@~IfkQh(s?f*g+!hCBCO!s61_lOxhR>Zd7aGJbaA0FlWncn}^c?l$XYoqi Je49g67XaLa4|V_m diff --git a/pom.xml b/pom.xml index 1624b02..a6437fe 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,11 @@ UTF-8 release + 1.7.25 + 1.1.3.3 + 20180813 + 4.10 + nothing-to-exclude @@ -37,20 +42,16 @@ - - log4j - log4j - - - log4j - log4j - 1.2.16 - + org.json json - 20090211 + 20180813 @@ -75,21 +76,7 @@ 1.2.1 - - - false - ${basedir}/src/main/resources - - - - true - ${basedir}/src/main/resources - - io/starter/OpenXLS/build.properties - - - @@ -118,14 +105,22 @@ - + + maven-surefire-plugin + + + ${exclude.tests} + + alphabetical + + org.apache.maven.plugins maven-compiler-plugin 2.5.1 - 1.5 - 1.5 + 1.8 + 1.8 @@ -139,7 +134,7 @@ - com/extench/profiling/**/* + io/starter/profiling/**/* **/*$* @@ -149,9 +144,39 @@ + + + false + ${basedir}/src/main/resources + + + + + true + ${basedir}/src/main/resources + + io/starter/OpenXLS/build.properties + + + + + + mac-profile + + true + + ${java.home}/../lib/tools.jar + + + + ${java.home}/../lib/tools.jar + + + + release @@ -212,22 +237,30 @@ - + + - repo.starter.io + snapshot.repo.starter.io s3://repo.starter.io/snapshot - repo.starter.io + release.repo.starter.io s3://repo.starter.io/release + - repo.starter.io + snapshot.repo.starter.io + s3://repo.starter.io/snapshot + + + release.repo.starter.io s3://repo.starter.io/release + + diff --git a/src/main/java/io/starter/OpenXLS/ChartHandle.java b/src/main/java/io/starter/OpenXLS/ChartHandle.java index e7d193a..2b67851 100644 --- a/src/main/java/io/starter/OpenXLS/ChartHandle.java +++ b/src/main/java/io/starter/OpenXLS/ChartHandle.java @@ -5,18 +5,25 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -45,6 +52,7 @@ import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.OOXMLAdapter; import io.starter.formats.XLS.WorkSheetNotFoundException; +import io.starter.formats.XLS.charts.Axis; import io.starter.formats.XLS.charts.Chart; import io.starter.formats.XLS.charts.ChartConstants; import io.starter.formats.XLS.charts.ChartType; @@ -74,57 +82,63 @@ */ public class ChartHandle implements ChartConstants { // 20080114 KSC: delegate so visible - public static final int BARCHART = ChartConstants.BARCHART; - public static final int COLCHART = ChartConstants.COLCHART; - public static final int LINECHART = ChartConstants.LINECHART; - public static final int PIECHART = ChartConstants.PIECHART; - public static final int AREACHART = ChartConstants.AREACHART; - public static final int SCATTERCHART = ChartConstants.SCATTERCHART; - public static final int RADARCHART = ChartConstants.RADARCHART; - public static final int SURFACECHART = ChartConstants.SURFACECHART; - public static final int DOUGHNUTCHART = ChartConstants.DOUGHNUTCHART; - public static final int BUBBLECHART = ChartConstants.BUBBLECHART; - public static final int OFPIECHART = ChartConstants.OFPIECHART; - public static final int PYRAMIDCHART = ChartConstants.PYRAMIDCHART; - public static final int CYLINDERCHART = ChartConstants.CYLINDERCHART; - public static final int CONECHART = ChartConstants.CONECHART; - public static final int PYRAMIDBARCHART = ChartConstants.PYRAMIDBARCHART; - public static final int CYLINDERBARCHART = ChartConstants.CYLINDERBARCHART; - public static final int CONEBARCHART = ChartConstants.CONEBARCHART; - public static final int RADARAREACHART = ChartConstants.RADARAREACHART; - public static final int STOCKCHART = ChartConstants.STOCKCHART; + public static final int BARCHART = ChartConstants.BARCHART; + public static final int COLCHART = ChartConstants.COLCHART; + public static final int LINECHART = ChartConstants.LINECHART; + public static final int PIECHART = ChartConstants.PIECHART; + public static final int AREACHART = ChartConstants.AREACHART; + public static final int SCATTERCHART = ChartConstants.SCATTERCHART; + public static final int RADARCHART = ChartConstants.RADARCHART; + public static final int SURFACECHART = ChartConstants.SURFACECHART; + public static final int DOUGHNUTCHART = ChartConstants.DOUGHNUTCHART; + public static final int BUBBLECHART = ChartConstants.BUBBLECHART; + public static final int OFPIECHART = ChartConstants.OFPIECHART; + public static final int PYRAMIDCHART = ChartConstants.PYRAMIDCHART; + public static final int CYLINDERCHART = ChartConstants.CYLINDERCHART; + public static final int CONECHART = ChartConstants.CONECHART; + public static final int PYRAMIDBARCHART = ChartConstants.PYRAMIDBARCHART; + public static final int CYLINDERBARCHART = ChartConstants.CYLINDERBARCHART; + public static final int CONEBARCHART = ChartConstants.CONEBARCHART; + public static final int RADARAREACHART = ChartConstants.RADARAREACHART; + public static final int STOCKCHART = ChartConstants.STOCKCHART; // legacy - public static final int BAR = BARCHART; - public static final int COL = COLCHART; - public static final int LINE = LINECHART; - public static final int PIE = PIECHART; - public static final int AREA = AREACHART; - public static final int SCATTER = SCATTERCHART; - public static final int RADAR = RADARCHART; - public static final int SURFACE = SURFACECHART; - public static final int DOUGHNUT = DOUGHNUTCHART; - public static final int BUBBLE = BUBBLECHART; - public static final int RADARAREA = RADARAREACHART; - public static final int PYRAMID = PYRAMIDCHART; - public static final int CYLINDER = CYLINDERCHART; - public static final int CONE = CONECHART; - public static final int PYRAMIDBAR = PYRAMIDBARCHART; - public static final int CYLINDERBAR = CYLINDERBARCHART; - public static final int CONEBAR = CONEBARCHART; + public static final int BAR = BARCHART; + public static final int COL = COLCHART; + public static final int LINE = LINECHART; + public static final int PIE = PIECHART; + public static final int AREA = AREACHART; + public static final int SCATTER = SCATTERCHART; + public static final int RADAR = RADARCHART; + public static final int SURFACE = SURFACECHART; + public static final int DOUGHNUT = DOUGHNUTCHART; + public static final int BUBBLE = BUBBLECHART; + public static final int RADARAREA = RADARAREACHART; + public static final int PYRAMID = PYRAMIDCHART; + public static final int CYLINDER = CYLINDERCHART; + public static final int CONE = CONECHART; + public static final int PYRAMIDBAR = PYRAMIDBARCHART; + public static final int CYLINDERBAR = CYLINDERBARCHART; + public static final int CONEBAR = CONEBARCHART; // axis types - public static final int XAXIS = ChartConstants.XAXIS; - public static final int YAXIS = ChartConstants.YAXIS; - public static final int ZAXIS = ChartConstants.ZAXIS; - public static final int XVALAXIS = ChartConstants.XVALAXIS; // an X axis type but VAL records + public static final int XAXIS = ChartConstants.XAXIS; + public static final int YAXIS = ChartConstants.YAXIS; + public static final int ZAXIS = ChartConstants.ZAXIS; + public static final int XVALAXIS = ChartConstants.XVALAXIS; // an + // X + // axis + // type + // but + // VAL + // records // coordinates - public static final int X = 0; - public static final int Y = 1; - public static final int WIDTH = 2; - public static final int HEIGHT = 3; + public static final int X = 0; + public static final int Y = 1; + public static final int WIDTH = 2; + public static final int HEIGHT = 3; - protected WorkBookHandle wbh; - private Chart mychart; + protected WorkBookHandle wbh; + private Chart mychart; /** * Constructor which creates a new ChartHandle from an existing Chart Object @@ -171,7 +185,8 @@ public String getDataRangeJSON() { } public int[] getEncompassingDataRange() { - return getEncompassingDataRange(this.mychart.getChartSeries().getDataRangeJSON()); + return getEncompassingDataRange(this.mychart.getChartSeries() + .getDataRangeJSON()); } /** @@ -188,7 +203,8 @@ public static int[] getEncompassingDataRange(JSONObject jsonDataRange) { int[] retVals = ExcelTools.getRangeRowCol(catrange); int nSeries = jsonDataRange.getJSONArray("Series").length(); for (int i = 0; i < nSeries; i++) { - JSONObject series = (JSONObject) jsonDataRange.getJSONArray("Series").get(i); + JSONObject series = (JSONObject) jsonDataRange + .getJSONArray("Series").get(i); String serrange = series.get("v").toString(); if (!serrange.startsWith(sheet)) continue; @@ -231,13 +247,15 @@ public static int[] getEncompassingDataRange(JSONObject jsonDataRange) { } } return retVals; - } catch (Exception e) { - } + } catch (Exception e) {} return null; /* - * while (ptgs.hasNext()) { PtgRef pr= (PtgRef) ptgs.next(); // PtgRef pr= - * (PtgRef)refs[i]; int[] locs = pr.getIntLocation(); for (int x=0;x<2;x++) { - * if((locs[x]retValues[x]))retValues[x]=locs[x]; } i++; } */ } @@ -271,7 +289,8 @@ public String toString() { * wedge of data in the Chart */ public String[] getSeries() { - return mychart.getSeries(-1); // -1 is flag for all rather than for a specific chart + return mychart.getSeries(-1); // -1 is flag for all rather than for a + // specific chart } /** @@ -285,7 +304,8 @@ public String[] getSeries() { * @return String[] each item being a Cell Range representing the Category Data */ public String[] getCategories() { - return getCategories(-1); // -1 is flag for all rather than for a specific chart + return getCategories(-1); // -1 is flag for all rather than for a + // specific chart } /** @@ -389,7 +409,8 @@ public ChartSeriesHandle getChartSeriesHandle(int idx) { * @see ChartSeriesHandle */ public ChartSeriesHandle getChartSeriesHandleByName(String legend) { - Series s = mychart.getSeries(legend, -1); // -1 is flag for all rather than for a specific chart + Series s = mychart.getSeries(legend, -1); // -1 is flag for all rather + // than for a specific chart return new ChartSeriesHandle(s, this.wbh); } @@ -516,7 +537,9 @@ public double getAxisMinScale(int axisType) { * @see getAxisMaxScale() */ public double getAxisMaxScale(int axisType) { - double[] minmax = mychart.getMinMax(this.wbh); // -1 is flag for all rather than for a specific chart + double[] minmax = mychart.getMinMax(this.wbh); // -1 is flag for all + // rather than for a + // specific chart return mychart.getAxes().getMinMax(minmax[0], minmax[1], axisType)[1]; } @@ -662,7 +685,9 @@ public void setAxisAutomaticScale(boolean b) { * @see getAxisMinScale(int axisType) */ public double getAxisMinScale() { - double[] minmax = mychart.getMinMax(this.wbh); // -1 is flag for all rather than for a specific chart + double[] minmax = mychart.getMinMax(this.wbh); // -1 is flag for all + // rather than for a + // specific chart return mychart.getAxes().getMinMax(minmax[0], minmax[1])[0]; } @@ -952,8 +977,10 @@ public void setPlotAreaBgColor(int bg) { */ public boolean changeTextValue(String originalval, String newval) { /* - * KSC: TODO: Refactor *** for(int x=0;x option * where 1-9 indicate an overlay chart) */ public void setChartType(int chartType, int nChart) { - mychart.setChartType(chartType, nChart, EnumSet.noneOf(ChartOptions.class)); // no specific options + mychart.setChartType(chartType, nChart, EnumSet + .noneOf(ChartOptions.class)); // no specific options } /** @@ -1215,8 +1248,10 @@ public int getChartType(int nChart) { * @return boolean whether the Cell Reference was found and modified */ /* - * TODO: NEEDED?? public boolean setLocationLocked(String loc, boolean l){ int x - * = Ptg.PTG_LOCATION_POLICY_UNLOCKED; if(l)x = Ptg.PTG_LOCATION_POLICY_LOCKED; + * TODO: NEEDED?? public boolean setLocationLocked(String + * loc, boolean l){ int x + * = Ptg.PTG_LOCATION_POLICY_UNLOCKED; if(l)x = + * Ptg.PTG_LOCATION_POLICY_LOCKED; * return setLocationPolicy(loc, x); } */ @@ -1303,13 +1338,13 @@ public boolean changeCategoryRange(String originalrange, String newrange) { * @return a ChartSeriesHandle representing the new or altered Series * @throws CellNotFoundException */ - public ChartSeriesHandle setSeries(int index, String legendCell, String categoryRange, String seriesRange, - String bubbleRange) throws CellNotFoundException { + public ChartSeriesHandle setSeries(int index, String legendCell, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { String legendText = ""; try { CellHandle ICell = null; if (legendCell != null && !legendCell.equals("")) { - // 20070707 KSC: allow addition of new cell ranges for legendCell (see + // 20070707 KSC: allow addition of new cell ranges for + // legendCell (see // OpenXLS.handleChartElement) try { ICell = wbh.getCell(legendCell); @@ -1326,7 +1361,9 @@ public ChartSeriesHandle setSeries(int index, String legendCell, String category } return setSeries(index, legendCell, legendText, categoryRange, seriesRange, bubbleRange); } catch (WorkSheetNotFoundException e) { - throw new CellNotFoundException("Error locating cell for adding series range: " + legendCell); + throw new CellNotFoundException( + "Error locating cell for adding series range: " + + legendCell); } } @@ -1358,9 +1395,9 @@ public ChartSeriesHandle setSeries(int index, String legendCell, String category * @return a ChartSeriesHandle representing the new or altered Series * @throws CellNotFoundException */ - public ChartSeriesHandle setSeries(int index, String legendCell, String legendText, String categoryRange, - String seriesRange, String bubbleRange) throws CellNotFoundException { - return setSeries(index, legendCell, legendText, categoryRange, seriesRange, bubbleRange, 0); // for default + public ChartSeriesHandle setSeries(int index, String legendCell, String legendText, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { + return setSeries(index, legendCell, legendText, categoryRange, seriesRange, bubbleRange, 0); // for + // default // chart } @@ -1395,9 +1432,9 @@ public ChartSeriesHandle setSeries(int index, String legendCell, String legendTe * @return a ChartSeriesHandle representing the new or altered Series * @throws CellNotFoundException */ - public ChartSeriesHandle setSeries(int index, String legendCell, String legendText, String categoryRange, - String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { - // if (index < mychart.getAllSeries(nChart).size() && index >= 0) { + public ChartSeriesHandle setSeries(int index, String legendCell, String legendText, String categoryRange, String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { + // if (index < mychart.getAllSeries(nChart).size() && index + // >= 0) { try { Series s = (Series) mychart.getAllSeries(nChart).get(index); ChartSeriesHandle csh = new ChartSeriesHandle(s, this.wbh); @@ -1429,9 +1466,9 @@ public ChartSeriesHandle setSeries(int index, String legendCell, String legendTe * @return ChartSeriesHandle representing the new series * @throws CellNotFoundException */ - private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText, String categoryRange, - String seriesRange, String bubbleRange) throws CellNotFoundException { - return addSeriesRange(legendAddress, legendText, categoryRange, seriesRange, bubbleRange, 0); // for default + private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { + return addSeriesRange(legendAddress, legendText, categoryRange, seriesRange, bubbleRange, 0); // for + // default // chart } @@ -1458,13 +1495,14 @@ private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText * @return ChartSeriesHandle representing the new series * @throws CellNotFoundException */ - private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText, String categoryRange, - String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { + private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText, String categoryRange, String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { Series s = null; if (bubbleRange == null || bubbleRange.equals("")) - s = mychart.addSeries(seriesRange, categoryRange, "", legendAddress, legendText, nChart); + s = mychart + .addSeries(seriesRange, categoryRange, "", legendAddress, legendText, nChart); else - s = mychart.addSeries(seriesRange, categoryRange, bubbleRange, legendAddress, legendText, nChart); + s = mychart + .addSeries(seriesRange, categoryRange, bubbleRange, legendAddress, legendText, nChart); if (nChart > 0) { // must update SeriesList record for overlay charts // TODO: FINISH } @@ -1500,9 +1538,11 @@ private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText * Sheet1!A2:A5); or null if chart is not of type Bubble. * @return ChartSeriesHandle referencing the newly added series */ - public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, String seriesRange, - String bubbleRange) throws CellNotFoundException { - return this.addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0); // target default chart + public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { + return this + .addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0); // target + // default + // chart } /** @@ -1536,8 +1576,7 @@ public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, * where 1-9 indicate an overlay chart) * @return ChartSeriesHandle referencing the newly added series */ - public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, String seriesRange, - String bubbleRange, int nChart) throws CellNotFoundException { + public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { String legendText = ""; String legendAddr = ""; try { @@ -1546,7 +1585,8 @@ public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, try { ICell = wbh.getCell(legendCell); if (legendCell.indexOf("!") == -1) - legendAddr = ICell.getWorkSheetName() + "!" + ICell.getCellAddress(); + legendAddr = ICell.getWorkSheetName() + "!" + + ICell.getCellAddress(); else legendAddr = legendCell; } catch (CellNotFoundException c) { @@ -1555,7 +1595,8 @@ public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, String sheetstr = legendCell.substring(0, shtpos); WorkSheetHandle sht = wbh.getWorkSheet(sheetstr); String celstr = legendCell.substring(shtpos + 1); - ICell = sht.add("", celstr); // TODO: Why is this being added? + ICell = sht.add("", celstr); // TODO: Why is this being + // added? legendAddr = celstr; } } @@ -1566,13 +1607,18 @@ public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, } Series s = null; if (bubbleRange == null) - s = mychart.addSeries(seriesRange, categoryRange, "", legendAddr, legendText, nChart); + s = mychart + .addSeries(seriesRange, categoryRange, "", legendAddr, legendText, nChart); else - s = mychart.addSeries(seriesRange, categoryRange, bubbleRange, legendAddr, legendText, nChart); - setDimensionsRecord(); // update chart DIMENSIONS record upon update of series + s = mychart + .addSeries(seriesRange, categoryRange, bubbleRange, legendAddr, legendText, nChart); + setDimensionsRecord(); // update chart DIMENSIONS record upon update + // of series return new ChartSeriesHandle(s, wbh); } catch (WorkSheetNotFoundException e) { - throw new CellNotFoundException("Error locating cell for adding series range: " + legendCell); + throw new CellNotFoundException( + "Error locating cell for adding series range: " + + legendCell); } } @@ -1595,9 +1641,10 @@ public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, * String seriesRange, String bubbleRange) * @return ChartSeriesHandle referencing the newly added series */ - public ChartSeriesHandle addSeriesRange(CellHandle legendCell, CellRange categoryRange, CellRange seriesRange, - CellRange bubbleRange) { - return this.addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0); // 0=default chart + public ChartSeriesHandle addSeriesRange(CellHandle legendCell, CellRange categoryRange, CellRange seriesRange, CellRange bubbleRange) { + return this + .addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0); // 0=default + // chart } /** @@ -1624,18 +1671,21 @@ public ChartSeriesHandle addSeriesRange(CellHandle legendCell, CellRange categor * String seriesRange, String bubbleRange) * @return ChartSeriesHandle referencing the newly added series */ - public ChartSeriesHandle addSeriesRange(CellHandle legendCell, CellRange categoryRange, CellRange seriesRange, - CellRange bubbleRange, int nChart) { + public ChartSeriesHandle addSeriesRange(CellHandle legendCell, CellRange categoryRange, CellRange seriesRange, CellRange bubbleRange, int nChart) { Series s = null; if (bubbleRange == null) - s = mychart.addSeries(seriesRange.toString(), categoryRange.toString(), "", - legendCell.getWorkSheetName() + "!" + legendCell.getCellAddress(), legendCell.getStringVal(), - nChart); + s = mychart.addSeries(seriesRange.toString(), categoryRange + .toString(), "", legendCell.getWorkSheetName() + "!" + + legendCell.getCellAddress(), legendCell + .getStringVal(), nChart); else - s = mychart.addSeries(seriesRange.toString(), categoryRange.toString(), bubbleRange.toString(), - legendCell.getWorkSheetName() + "!" + legendCell.getCellAddress(), legendCell.getStringVal(), - nChart); - setDimensionsRecord(); // 20080417 KSC: update chart DIMENSIONS record upon update of series + s = mychart.addSeries(seriesRange.toString(), categoryRange + .toString(), bubbleRange + .toString(), legendCell.getWorkSheetName() + "!" + + legendCell.getCellAddress(), legendCell + .getStringVal(), nChart); + setDimensionsRecord(); // 20080417 KSC: update chart DIMENSIONS record + // upon update of series return new ChartSeriesHandle(s, wbh); } @@ -1664,7 +1714,8 @@ public void removeSeries(int index) { */ public void removeSeries(int index, int nChart) { Vector seriesperchart = mychart.getAllSeries(nChart); - Series seriestodelete = (Series) seriesperchart.get(index); // series to delete + Series seriestodelete = (Series) seriesperchart.get(index); // series to + // delete mychart.removeSeries(seriestodelete); setDimensionsRecord(); } @@ -1692,9 +1743,11 @@ public void removeSeries(int index, int nChart) { * categoryRange - The Cell Range representing the categories (X * Axis) for the entire Chart */ - public void addAllSeries(String[] valueRanges, String[] legendCells, String[] bubbleSizeRanges, - String categoryRange) { - addAllSeries(valueRanges, legendCells, bubbleSizeRanges, categoryRange, 0); // do for default chart + public void addAllSeries(String[] valueRanges, String[] legendCells, String[] bubbleSizeRanges, String categoryRange) { + addAllSeries(valueRanges, legendCells, bubbleSizeRanges, categoryRange, 0); // do + // for + // default + // chart } /** @@ -1723,23 +1776,33 @@ public void addAllSeries(String[] valueRanges, String[] legendCells, String[] bu * number and drawing order of the desired chart (default= 0 max=9 * where 1-9 indicate an overlay chart) */ - private void addAllSeries(String[] valueRanges, String[] legendCells, String[] bubbleSizeRanges, - String categoryRange, int nChart) { + private void addAllSeries(String[] valueRanges, String[] legendCells, String[] bubbleSizeRanges, String categoryRange, int nChart) { // first, remove all existing series Vector v = mychart.getAllSeries(); for (int i = 0; i < v.size(); i++) { mychart.removeSeries((Series) v.get(i)); } try { - HashMap chartMetrics = mychart.getMetrics(wbh); // build or retrieve Chart Metrics --> - // dimensions + series data ... - this.mychart.getLegend().resetPos(chartMetrics.get("y"), chartMetrics.get("h"), chartMetrics.get("canvash"), - legendCells.length); - } catch (Exception e) { - } + HashMap chartMetrics = mychart.getMetrics(wbh); // build + // or + // retrieve + // Chart + // Metrics + // --> + // dimensions + // + + // series + // data + // ... + this.mychart.getLegend() + .resetPos(chartMetrics.get("y"), chartMetrics + .get("h"), chartMetrics + .get("canvash"), legendCells.length); + } catch (Exception e) {} setDimensionsRecord(); // now add series - boolean hasBubbles = ((bubbleSizeRanges != null && bubbleSizeRanges.length == valueRanges.length)); + boolean hasBubbles = ((bubbleSizeRanges != null + && bubbleSizeRanges.length == valueRanges.length)); for (int i = valueRanges.length - 1; i >= 0; i--) { try { if (!hasBubbles) // usual case @@ -1798,7 +1861,8 @@ public ChartSeriesHandle appendRowSeriesToChart(int nChart) { String legendRef = theHandle.getSeriesLegendReference(); if (legendRef != null && !legendRef.equals("")) { String sheetnm = legendRef.substring(0, legendRef.indexOf("!")); - legendRef = legendRef.substring(legendRef.indexOf("!") + 1, legendRef.length()); + legendRef = legendRef + .substring(legendRef.indexOf("!") + 1, legendRef.length()); int[] rc = ExcelTools.getRowColFromString(legendRef); rc[0] = rc[0] + 1; legendRef = sheetnm + "!" + ExcelTools.formatLocation(rc); @@ -1810,7 +1874,8 @@ public ChartSeriesHandle appendRowSeriesToChart(int nChart) { String categoryRange = theHandle.getCategoryRange(); String seriesRange = theHandle.getSeriesRange(); String sheetnm = seriesRange.substring(0, seriesRange.indexOf("!")); - seriesRange = seriesRange.substring(seriesRange.indexOf("!") + 1, seriesRange.length()); + seriesRange = seriesRange + .substring(seriesRange.indexOf("!") + 1, seriesRange.length()); int[] rc = ExcelTools.getRangeRowCol(seriesRange); // fiddle it, since exceltools doesn't translate back/forth int[] newRc = new int[4]; @@ -1820,9 +1885,11 @@ public ChartSeriesHandle appendRowSeriesToChart(int nChart) { newRc[3] = rc[2] + 1; seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); try { - return this.addSeriesRange(legendRef, "", categoryRange, seriesRange, "", nChart); + return this + .addSeriesRange(legendRef, "", categoryRange, seriesRange, "", nChart); } catch (CellNotFoundException e) { - Logger.logErr("ChartHandle.appendRowSeriesToChart: Unable to append series to chart: " + e); + Logger.logErr("ChartHandle.appendRowSeriesToChart: Unable to append series to chart: " + + e); } return null; } @@ -1860,13 +1927,16 @@ public void appendRowCategoryToChart(int nChart) { String[] s = ExcelTools.stripSheetNameFromRange(seriesRange); String sheetnm = s[0]; seriesRange = s[1]; - // String sheetnm = seriesRange.substring(0, seriesRange.indexOf("!")); - // seriesRange = seriesRange.substring( seriesRange.indexOf("!")+1, + // String sheetnm = seriesRange.substring(0, + // seriesRange.indexOf("!")); + // seriesRange = seriesRange.substring( + // seriesRange.indexOf("!")+1, // seriesRange.length()); // Strip 2nd sheet ref, if any 20080213 KSC // int n= seriesRange.indexOf('!'); // int m= seriesRange.indexOf(':'); - // seriesRange= seriesRange.substring(0, m+1) + seriesRange.substring(n+1); + // seriesRange= seriesRange.substring(0, m+1) + + // seriesRange.substring(n+1); int[] rc = ExcelTools.getRangeRowCol(seriesRange); int[] newRc = new int[4]; @@ -1882,10 +1952,14 @@ public void appendRowCategoryToChart(int nChart) { sheetnm = s[0]; seriesRange = s[1]; /* - * sheetnm = seriesRange.substring(0, seriesRange.indexOf("!")); seriesRange = - * seriesRange.substring( seriesRange.indexOf("!")+1, seriesRange.length()); // - * Strip 2nd sheet ref, if any 20080213 KSC n= seriesRange.indexOf('!'); m= - * seriesRange.indexOf(':'); seriesRange= seriesRange.substring(0, m+1) + + * sheetnm = seriesRange.substring(0, + * seriesRange.indexOf("!")); seriesRange = + * seriesRange.substring( seriesRange.indexOf("!")+1, + * seriesRange.length()); // + * Strip 2nd sheet ref, if any 20080213 KSC n= + * seriesRange.indexOf('!'); m= + * seriesRange.indexOf(':'); seriesRange= + * seriesRange.substring(0, m+1) + * seriesRange.substring(n+1); */ rc = ExcelTools.getRangeRowCol(seriesRange); @@ -1900,54 +1974,82 @@ public void appendRowCategoryToChart(int nChart) { } /* - * NOT IMPLEMENTED YET adjust chart cell references upon row insertion or - * deletion NOTE: Assumes we're on the correct sheet NOT COMPLETELY IMPLEMENTED + * NOT IMPLEMENTED YET adjust chart cell references upon row + * insertion or + * deletion NOTE: Assumes we're on the correct sheet NOT + * COMPLETELY IMPLEMENTED * YEt * * @param rownum * * @param shiftamt +1= insert row, -1= delete row * - * public void adjustCellRefs(int rownum, int shiftamt) { Vector v = - * mychart.getAllSeries(); boolean bSeriesRows= false; boolean bMod= false; for - * (int i=0;i 0) { // insert row i.e. - * shift ai location down if ((loc.length==2 && loc[0]>=rownum) || - * (loc[0]>=rownum || loc[2]>=rownum)) { adjustAiLocation(ai, - * p[0].getIntLocation(), shiftamt); bMod= true; } } else { if ((loc.length==2 - * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) { // remove it - * this.removeSeries(i); bMod= true; continue; } } } catch(Exception e) { + * public void adjustCellRefs(int rownum, int shiftamt) { + * Vector v = + * mychart.getAllSeries(); boolean bSeriesRows= false; + * boolean bMod= false; for + * (int i=0;i 0) { // + * insert row i.e. + * shift ai location down if ((loc.length==2 && + * loc[0]>=rownum) || + * (loc[0]>=rownum || loc[2]>=rownum)) { + * adjustAiLocation(ai, + * p[0].getIntLocation(), shiftamt); bMod= true; } } else { + * if ((loc.length==2 + * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) + * { // remove it + * this.removeSeries(i); bMod= true; continue; } } } + * catch(Exception e) { * * } } catch (Exception e) { * * } try { // CATEGORY Ai ai= s.getCategoryValueAi(); Ptg[] - * p=ai.getCellRangePtgs(); try { int[] loc= p[0].getIntLocation(); if (shiftamt - * > 0) { // insert row i.e. shift ai location down if ((loc.length==2 && - * loc[0]>=rownum) || (loc[0]>=rownum || loc[2]>=rownum)) { adjustAiLocation(ai, - * p[0].getIntLocation(), shiftamt); bMod= true; } } else { if ((loc.length==2 - * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) { // remove it ???? - * this.removeSeries(i); bMod= true; } } } catch(Exception e) { + * p=ai.getCellRangePtgs(); try { int[] loc= + * p[0].getIntLocation(); if (shiftamt + * > 0) { // insert row i.e. shift ai location down if + * ((loc.length==2 && + * loc[0]>=rownum) || (loc[0]>=rownum || loc[2]>=rownum)) { + * adjustAiLocation(ai, + * p[0].getIntLocation(), shiftamt); bMod= true; } } else { + * if ((loc.length==2 + * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) + * { // remove it ???? + * this.removeSeries(i); bMod= true; } } } catch(Exception + * e) { * * } } catch (Exception e) { * - * } try { // LEGEND Ai ai= s.getLegendAi(); Ptg[] p=ai.getCellRangePtgs(); - * int[] loc= p[0].getIntLocation(); if (shiftamt > 0) { // insert row i.e. - * shift ai location down if ((loc.length==2 && loc[0]>=rownum) || - * (loc[0]>=rownum || loc[2]>=rownum)) { adjustAiLocation(ai, - * p[0].getIntLocation(), shiftamt); bMod= true; } } else { if ((loc.length==2 - * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) { // remove it - * this.removeSeries(i); bMod= true; } } } catch(Exception e) { + * } try { // LEGEND Ai ai= s.getLegendAi(); Ptg[] + * p=ai.getCellRangePtgs(); + * int[] loc= p[0].getIntLocation(); if (shiftamt > 0) { // + * insert row i.e. + * shift ai location down if ((loc.length==2 && + * loc[0]>=rownum) || + * (loc[0]>=rownum || loc[2]>=rownum)) { + * adjustAiLocation(ai, + * p[0].getIntLocation(), shiftamt); bMod= true; } } else { + * if ((loc.length==2 + * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) + * { // remove it + * this.removeSeries(i); bMod= true; } } } catch(Exception + * e) { * * } } if (bMod)// one or more series elements were modified - * setDimensionsRecord(); // update dimensions i.e. Data Range + * setDimensionsRecord(); // update dimensions i.e. Data + * Range * * } */ /* - * doesn't appear to be used right now move the ai location (row) according to + * doesn't appear to be used right now move the ai location + * (row) according to * shift amount * * @param ai @@ -1956,13 +2058,20 @@ public void appendRowCategoryToChart(int nChart) { * * @param shift * - * private void adjustAiLocation(Ai ai, int[] loc, int shift) { String oldloc= - * ExcelTools.formatLocation(loc); if (loc.length>2) // get 2nd part of range - * oldloc += ":" + ExcelTools.formatLocation(new int[]{loc[2], loc[3]}); oldloc= - * this.getSheet().getSheetName() + "!" + oldloc; if (loc.length==2)// single - * cell loc[0]+=shift; else { // range loc[0]+=shift; loc[2]+=shift; } String - * newloc= ExcelTools.formatLocation(loc); if (loc.length>2) // get 2nd part of - * range newloc += ":" + ExcelTools.formatLocation(new int[]{loc[2], loc[3]}); + * private void adjustAiLocation(Ai ai, int[] loc, int + * shift) { String oldloc= + * ExcelTools.formatLocation(loc); if (loc.length>2) // get + * 2nd part of range + * oldloc += ":" + ExcelTools.formatLocation(new + * int[]{loc[2], loc[3]}); oldloc= + * this.getSheet().getSheetName() + "!" + oldloc; if + * (loc.length==2)// single + * cell loc[0]+=shift; else { // range loc[0]+=shift; + * loc[2]+=shift; } String + * newloc= ExcelTools.formatLocation(loc); if (loc.length>2) + * // get 2nd part of + * range newloc += ":" + ExcelTools.formatLocation(new + * int[]{loc[2], loc[3]}); * ai.changeAiLocation(oldloc, newloc); } * */ @@ -2011,11 +2120,13 @@ public void parseXML(WorkSheetHandle sht, XmlPullParser xpp, HashMap maps) String tnm = xpp.getName(); if (tnm.equals("ChartFonts")) { for (int x = 0; x < xpp.getAttributeCount(); x++) { - this.setChartFont(xpp.getAttributeName(x), xpp.getAttributeValue(x)); + this.setChartFont(xpp.getAttributeName(x), xpp + .getAttributeValue(x)); } } else if (tnm.equals("ChartFontRec")) { String fName = ""; - int fontId = 0, fSize = 0, fWeight = 0, fColor = 0, fUnderline = 0; + int fontId = 0, fSize = 0, fWeight = 0, fColor = 0, + fUnderline = 0; boolean bIsBold = false; for (int x = 0; x < xpp.getAttributeCount(); x++) { String attr = xpp.getAttributeName(x); @@ -2025,9 +2136,11 @@ public void parseXML(WorkSheetHandle sht, XmlPullParser xpp, HashMap maps) } else if (attr.equals("id")) { fontId = Integer.parseInt(val); } else if (attr.equals("size")) { - fSize = Font.PointsToFontHeight(Double.parseDouble(val)); + fSize = Font.PointsToFontHeight(Double + .parseDouble(val)); } else if (attr.equals("color")) { - fColor = FormatHandle.HexStringToColorInt(val, FormatHandle.colorFONT); + fColor = FormatHandle + .HexStringToColorInt(val, FormatHandle.colorFONT); if (fColor == 0) fColor = 32767; // necessary? } else if (attr.equals("weight")) { @@ -2039,7 +2152,8 @@ public void parseXML(WorkSheetHandle sht, XmlPullParser xpp, HashMap maps) } } while (this.getWorkBook().getNumFonts() < fontId - 1) { - this.getWorkBook().insertFont(new Font("Arial", FormatConstants.PLAIN, 10)); + this.getWorkBook().insertFont(new Font("Arial", + FormatConstants.PLAIN, 10)); } if (this.getWorkBook().getNumFonts() < fontId) { Font f = new Font(fName, fWeight, fSize); @@ -2047,7 +2161,8 @@ public void parseXML(WorkSheetHandle sht, XmlPullParser xpp, HashMap maps) f.setBold(bIsBold); f.setUnderlineStyle((byte) fUnderline); this.getWorkBook().insertFont(f); - } else { // TODO: this will screw up linked fonts, perhaps, so what to do? + } else { // TODO: this will screw up linked fonts, + // perhaps, so what to do? Font f = this.getWorkBook().getFont(fontId); f.setFontWeight(fWeight); f.setFontName(fName); @@ -2056,48 +2171,72 @@ public void parseXML(WorkSheetHandle sht, XmlPullParser xpp, HashMap maps) f.setBold(bIsBold); f.setUnderlineStyle((byte) fUnderline); } - } else if (tnm.equals("FormatChartArea")) { // TODO: something! + } else if (tnm.equals("FormatChartArea")) { // TODO: + // something! // ChartBorder // ChartProperties } else if (tnm.equals("Series")) { // series --> // Legend Range Category shape typex typey - String legend = "", series = "", category = "", bubble = ""; + String legend = "", series = "", category = "", + bubble = ""; String dataTypeX = "", dataTypeY = ""; String shape = ""; for (int x = 0; x < xpp.getAttributeCount(); x++) { - if (xpp.getAttributeName(x).equalsIgnoreCase("Legend")) + if (xpp.getAttributeName(x) + .equalsIgnoreCase("Legend")) legend = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("Range")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Range")) series = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("Category")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Category")) category = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("Bubbles")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Bubbles")) bubble = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("TypeX")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("TypeX")) dataTypeX = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("TypeY")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("TypeY")) dataTypeY = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("Shape")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Shape")) shape = xpp.getAttributeValue(x); } // 20070709 KSC: can't add until all cells are added // ch.addSeriesRange(legend, category, series); - String[] s = { legend, series, category, bubble, dataTypeX, dataTypeY, shape }; - HashMap map = (HashMap) maps.get("chartseries"); + String[] s = { legend, series, category, bubble, + dataTypeX, dataTypeY, shape }; + HashMap map = (HashMap) maps + .get("chartseries"); map.put(s, this); - } else if (tnm.equals("ChartOptions")) { // handle chart-type-specific options such as legend + } else if (tnm.equals("ChartOptions")) { // handle + // chart-type-specific + // options such + // as legend // options - // 20070716 KSC: handle chart-type-specific options in a very generic way ... + // 20070716 KSC: handle chart-type-specific options in a + // very generic way ... for (int x = 0; x < xpp.getAttributeCount(); x++) { - this.setChartOption(xpp.getAttributeName(x), xpp.getAttributeValue(x)); + this.setChartOption(xpp.getAttributeName(x), xpp + .getAttributeValue(x)); } } else if (tnm.equals("ThreeD")) { // handle three-d options - // handle threeD record options in a very generic way ... - this.make3D(); // default chart - TODO; if mutliple charts, handle - for (int x = 0; x < xpp.getAttributeCount(); x++) { // now add threed rec options - this.setChartOption(xpp.getAttributeName(x), xpp.getAttributeValue(x)); + // handle threeD record options in a very generic way + // ... + this.make3D(); // default chart - TODO; if mutliple + // charts, handle + for (int x = 0; x < xpp.getAttributeCount(); x++) { // now + // add + // threed + // rec + // options + this.setChartOption(xpp.getAttributeName(x), xpp + .getAttributeValue(x)); } - } else if (tnm.endsWith("Axis")) { // handle axis specs (Label + options ...) + } else if (tnm.endsWith("Axis")) { // handle axis specs + // (Label + options ...) // 20070720 KSC: handle Axis record options ... int type = 0; String axis = xpp.getName(); @@ -2107,38 +2246,52 @@ else if (axis.equalsIgnoreCase("YAxis")) type = YAXIS; else if (axis.equalsIgnoreCase("ZAxis")) type = ZAXIS; - if (xpp.getAttributeCount() > 0) { // then has axis options + if (xpp.getAttributeCount() > 0) { // then has axis + // options for (int x = 0; x < xpp.getAttributeCount(); x++) { - this.setAxisOption(type, xpp.getAttributeName(x), xpp.getAttributeValue(x)); + this.setAxisOption(type, xpp + .getAttributeName(x), xpp + .getAttributeValue(x)); } - } else { // no axis options means no axis present; remove + } else { // no axis options means no axis present; + // remove this.removeAxis(type); } } else if (tnm.equals("Series")) { // handle series data // Legend Range Category - String legend = "", series = "", category = "", bubble = ""; + String legend = "", series = "", category = "", + bubble = ""; String dataTypeX = "", dataTypeY = ""; String shape = ""; for (int x = 0; x < xpp.getAttributeCount(); x++) { - if (xpp.getAttributeName(x).equalsIgnoreCase("Legend")) + if (xpp.getAttributeName(x) + .equalsIgnoreCase("Legend")) legend = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("Range")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Range")) series = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("Category")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Category")) category = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("Bubbles")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Bubbles")) bubble = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("TypeX")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("TypeX")) dataTypeX = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("TypeY")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("TypeY")) dataTypeY = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x).equalsIgnoreCase("Shape")) + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Shape")) shape = xpp.getAttributeValue(x); } // 20070709 KSC: can't add until all cells are added // ch.addSeriesRange(legend, category, series); - String[] s = { legend, series, category, bubble, dataTypeX, dataTypeY, shape }; - HashMap map = (HashMap) maps.get("chartseries"); + String[] s = { legend, series, category, bubble, + dataTypeX, dataTypeY, shape }; + HashMap map = (HashMap) maps + .get("chartseries"); map.put(s, this); } } else if (eventType == XmlPullParser.END_TAG) { @@ -2148,7 +2301,8 @@ else if (xpp.getAttributeName(x).equalsIgnoreCase("Shape")) eventType = xpp.next(); } } catch (Exception e) { - Logger.logWarn("ChartHandle.parseXML <" + xpp.getName() + ">: " + e.toString()); + Logger.logWarn("ChartHandle.parseXML <" + xpp.getName() + ">: " + + e.toString()); // TODO: propogate Exception??? } } @@ -2168,8 +2322,9 @@ public String getXML() { sb.append(" Fill=\"" + this.getPlotAreaBgColor() + "\""); // Position short[] coords = mychart.getCoords(); - sb.append(" Left=\"" + coords[0] + "\" Top=\"" + coords[1] + "\" Width=\"" + coords[2] + "\" Height=\"" - + coords[3] + "\""); + sb.append(" Left=\"" + coords[0] + "\" Top=\"" + coords[1] + + "\" Width=\"" + coords[2] + "\" Height=\"" + coords[3] + + "\""); sb.append(">\n"); // Chart Fonts @@ -2179,13 +2334,15 @@ public String getXML() { // Format Chart Area sb.append(t(2) + "\n"); sb.append(t(3) + "\n"); // KSC: TODO: BORDER - sb.append(t(3) + "\n"); // TODO: Properties + sb.append(t(3) + "\n"); // TODO: + // Properties sb.append(t(2) + "\n"); // Source Data sb.append(t(2) + "\n"); ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); for (int i = 0; i < series.length; i++) { - sb.append(t(3) + "\n"); @@ -2242,9 +2400,12 @@ public String getOOXML(int rId) { mychart.getChartSeries().resetSeriesNumber(); // reset series idx // retrieve pertinent chart data // axes id's TODO: HANDLE MULTIPLE AXES per chart ... - String catAxisId = Integer.toString((int) (Math.random() * 1000000)); - String valAxisId = Integer.toString((int) (Math.random() * 1000000)); - String serAxisId = Integer.toString((int) (Math.random() * 1000000)); + String catAxisId = Integer + .toString((int) (Math.random() * 1000000)); + String valAxisId = Integer + .toString((int) (Math.random() * 1000000)); + String serAxisId = Integer + .toString((int) (Math.random() * 1000000)); OOXMLChart thischart; if ((mychart instanceof OOXMLChart)) thischart = (OOXMLChart) mychart; @@ -2262,14 +2423,16 @@ public String getOOXML(int rId) { if (chartEmbeds != null) { int j = 0; for (int i = 0; i < chartEmbeds.size(); i++) { - if (((String[]) chartEmbeds.get(i))[0].equals("userShape")) { + if (((String[]) chartEmbeds.get(i))[0] + .equals("userShape")) { j++; cooxml.append(""); } } } } catch (Exception e) { - Logger.logErr("ChartHandle.getOOXML: error generating OOXML. Chart not created: " + e.toString()); + Logger.logErr("ChartHandle.getOOXML: error generating OOXML. Chart not created: " + + e.toString()); } return cooxml.toString(); } @@ -2286,10 +2449,13 @@ public String getOOXML(int rId) { * @return String OOXML */ public String getChartDrawingOOXML(int id) { - TwoCellAnchor t = new TwoCellAnchor(((OOXMLChart) mychart).getEditMovement()); - t.setAsChart(id, OOXMLAdapter.stripNonAscii(this.getOOXMLName()).toString(), - TwoCellAnchor.convertBoundsFromBIFF8(this.getSheet(), mychart.getBounds())); // adjust BIFF8 bounds to - // OOXML units + TwoCellAnchor t = new TwoCellAnchor( + ((OOXMLChart) mychart).getEditMovement()); + t.setAsChart(id, OOXMLAdapter.stripNonAscii(this.getOOXMLName()) + .toString(), TwoCellAnchor.convertBoundsFromBIFF8(this + .getSheet(), mychart.getBounds())); // adjust BIFF8 + // bounds to + // OOXML units return t.getOOXML(); } @@ -2324,15 +2490,21 @@ public void parseOOXML(java.io.InputStream ii) { */ try { OOXMLChart thischart = (OOXMLChart) mychart; - int drawingOrder = 0; // drawing order of the chart (0=default, 1-9 for multiple charts in 1) + int drawingOrder = 0; // drawing order of the chart (0=default, 1-9 + // for multiple charts in 1) boolean hasPivotTableSource = false; // remove any undesired formatting from default chart: this.setTitle(""); // clear any previously set mychart.getAxes().setPlotAreaBgColor(FormatConstants.COLOR_WHITE); - mychart.getAxes().setPlotAreaBorder(-1, -1); // remove plot area border + mychart.getAxes().setPlotAreaBorder(-1, -1); // remove plot area + // border - java.util.Stack lastTag = new java.util.Stack(); // keep track of element hierarchy + java.util.Stack lastTag = new java.util.Stack(); // keep + // track + // of + // element + // hierarchy XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); @@ -2342,9 +2514,12 @@ public void parseOOXML(java.io.InputStream ii) { int eventType = xpp.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); // main entry= chartSpace, children: lang, chart + String tnm = xpp.getName(); // main entry= chartSpace, + // children: lang, chart lastTag.push(tnm); // keep track of element hierarchy - if (tnm.equals("chart")) { // beginning of DrawingML for a single image or chart; children: title, + if (tnm.equals("chart")) { // beginning of DrawingML for a + // single image or chart; + // children: title, // plotArea, legend, /** * 5 @@ -2372,20 +2547,26 @@ public void parseOOXML(java.io.InputStream ii) { } else if (tnm.equals("lang")) { thischart.lang = xpp.getAttributeValue(0); } else if (tnm.equals("roundedCorners")) { - thischart.roundedCorners = xpp.getAttributeValue(0).equals("1"); + thischart.roundedCorners = xpp.getAttributeValue(0) + .equals("1"); } else if (tnm.equals("pivotSource")) { // has a pivot table hasPivotTableSource = true; } else if (tnm.equals("view3D")) { ThreeD.parseOOXML(xpp, lastTag, thischart); } else if (tnm.equals("layout")) { - thischart.plotAreaLayout = (Layout) Layout.parseOOXML(xpp, lastTag).cloneElement(); + thischart.plotAreaLayout = (Layout) Layout + .parseOOXML(xpp, lastTag).cloneElement(); } else if (tnm.equals("legend")) { thischart.showLegend(true, false); thischart.ooxmlLegend = (io.starter.formats.OOXML.Legend) io.starter.formats.OOXML.Legend - .parseOOXML(xpp, lastTag, this.wbh).cloneElement(); - thischart.ooxmlLegend.fill2003Legend(thischart.getLegend()); - // Parse actual CHART TYPE element (barChart, pieChart, etc.) - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART]) + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement(); + thischart.ooxmlLegend + .fill2003Legend(thischart.getLegend()); + // Parse actual CHART TYPE element (barChart, pieChart, + // etc.) + } else if (tnm + .equals(OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART]) || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.LINECHART]) || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.PIECHART]) || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.AREACHART]) @@ -2402,36 +2583,62 @@ public void parseOOXML(java.io.InputStream ii) { || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.AREACHART]) || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SCATTERCHART]) || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART])) { // specific - // chart type- - ChartType.parseOOXML(xpp, this.wbh, mychart, drawingOrder++); + // chart + // type- + ChartType + .parseOOXML(xpp, this.wbh, mychart, drawingOrder++); lastTag.pop(); } else if (tnm.equals("title")) { - thischart.setOOXMLTitle((Title) Title.parseOOXML(xpp, lastTag, this.wbh).cloneElement(), - this.wbh); + thischart.setOOXMLTitle((Title) Title + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement(), this.wbh); this.setTitle(thischart.getOOXMLTitle().getTitle()); - } else if (tnm.equals("spPr")) { // shape properties -- can be for plot area or chart space + } else if (tnm.equals("spPr")) { // shape properties -- can + // be for plot area or + // chart space String parent = lastTag.get(lastTag.size() - 2); if (parent.equals("plotArea")) { - thischart.setSpPr(0, (SpPr) SpPr.parseOOXML(xpp, lastTag, this.wbh).cloneElement()); + thischart.setSpPr(0, (SpPr) SpPr + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement()); } else if (parent.equals("chartSpace")) { - thischart.setSpPr(1, (SpPr) SpPr.parseOOXML(xpp, lastTag, this.wbh).cloneElement()); + thischart.setSpPr(1, (SpPr) SpPr + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement()); } } else if (tnm.equals("txPr")) { // text formatting - thischart.setTxPr((TxPr) TxPr.parseOOXML(xpp, lastTag, this.wbh).cloneElement()); + thischart.setTxPr((TxPr) TxPr + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement()); } else if (tnm.equals("catAx")) { // child of plotArea - mychart.getAxes().parseOOXML(XAXIS, xpp, tnm, lastTag, this.wbh); + mychart.getAxes() + .parseOOXML(XAXIS, xpp, tnm, lastTag, this.wbh); } else if (tnm.equals("valAx")) { // child of plotArea - if (mychart.getAxes().hasAxis(ChartConstants.XAXIS)) // usual, have a catAx then a valAx - mychart.getAxes().parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); - else if (mychart.getAxes().hasAxis(ChartConstants.YAXIS)) // for bubble charts, has two valAxes - // and no + if (mychart.getAxes().hasAxis(ChartConstants.XAXIS)) // usual, + // have + // a + // catAx + // then + // a + // valAx + mychart.getAxes() + .parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); + else if (mychart.getAxes() + .hasAxis(ChartConstants.YAXIS)) // for bubble + // charts, has + // two valAxes + // and no // catAx - mychart.getAxes().parseOOXML(ChartConstants.XVALAXIS, xpp, tnm, lastTag, this.wbh); + mychart.getAxes() + .parseOOXML(ChartConstants.XVALAXIS, xpp, tnm, lastTag, this.wbh); else // 2nd val axis is Y axis - mychart.getAxes().parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); + mychart.getAxes() + .parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); } else if (tnm.equals("serAx")) { // series axis - 3d charts - mychart.getAxes().parseOOXML(ZAXIS, xpp, tnm, lastTag, this.wbh); - } else if (tnm.equals("dateAx")) { // TODO: not finished: figure out! + mychart.getAxes() + .parseOOXML(ZAXIS, xpp, tnm, lastTag, this.wbh); + } else if (tnm.equals("dateAx")) { // TODO: not finished: + // figure out! // ?? } @@ -2519,13 +2726,13 @@ protected void setDimensionsRecord() { int nPoints = 0; for (int i = 0; i < series.length; i++) { try { - int[] coords = ExcelTools.getRangeCoords(series[i].getSeriesRange()); + int[] coords = ExcelTools + .getRangeCoords(series[i].getSeriesRange()); if (coords[3] > coords[1]) nPoints = Math.max(nPoints, coords[3] - coords[1] + 1); // c1-c0 else nPoints = Math.max(nPoints, coords[2] - coords[0] + 1); // r1-r0 - } catch (Exception e) { - } + } catch (Exception e) {} } mychart.setDimensionsRecord(0, nPoints, 0, nSeries); } @@ -2645,7 +2852,7 @@ public String getThreeDXML() { * where 1-9 indicate an overlay chart) * @return ThreeD rec */ - private ThreeD initThreeD(int nChart) { + ThreeD initThreeD(int nChart) { return mychart.initThreeD(nChart, this.getChartType(nChart)); } @@ -2696,10 +2903,13 @@ public void setAxisPlacement(int Axis, int Placement) { } /* - * returns the desired axis If bCreateIfNecessary, will creates if doesn't exist + * returns the desired axis If bCreateIfNecessary, will + * creates if doesn't exist * - * @return Axis Object / private Axis getAxis(int axisType, boolean - * bCreateIfNecessary) { return mychart.getAxis(axisType, bCreateIfNecessary); } + * @return Axis Object / private Axis getAxis(int axisType, + * boolean + * bCreateIfNecessary) { return mychart.getAxis(axisType, + * bCreateIfNecessary); } */ /** @@ -2772,7 +2982,8 @@ public WorkSheetHandle getWorkSheetHandle() { * bounds - left or x value, top or y value, width, height * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT */ - // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and Coords are very + // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and + // Coords are very // distinct public void setBounds(short[] bounds) { mychart.setCoords(bounds); @@ -2785,7 +2996,8 @@ public void setBounds(short[] bounds) { * @return short[4] bounds - left or x value, top or y value, width, height * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT */ - // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and Coords are very + // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and + // Coords are very // distinct public short[] getBounds() { return mychart.getCoords(); @@ -2853,24 +3065,31 @@ public void setRelativeBounds(short[] bounds) { /* * For internal debugging use only * - * public void writeChartRecs(String fName) { mychart.writeChartRecs(fName); } + * public void writeChartRecs(String fName) { + * mychart.writeChartRecs(fName); } * * /** set DataLabels option for this chart * * @param String type - see below * - * @param boolean bShowLegendKey - true if show legend key, false otherwise - *

possible String type values:
Series
Category
Value - *
Percentage (Only for Pie Charts)
Bubble (Only for Bubble Charts) - *
X Value (Only for Bubble Charts)
Y Value (Only for Bubble Charts) + * @param boolean bShowLegendKey - true if show legend key, + * false otherwise + *

possible String type values:
Series
Category + *
Value + *
Percentage (Only for Pie Charts)
Bubble (Only for + * Bubble Charts) + *
X Value (Only for Bubble Charts)
Y Value (Only + * for Bubble Charts) *
CandP * *

NOTE: not 100% implemented at this time */ public void setDataLabel(String/* [] */ type, boolean bShowLegendKey) { /* - * for now, only 1 option is valid - multiple legend settings e.g. Category and - * Value are not figured out yet for (int i= 0; i < type.length; i++) + * for now, only 1 option is valid - multiple legend + * settings e.g. Category and + * Value are not figured out yet for (int i= 0; i < + * type.length; i++) * mychart.setChartOption("DataLabel", type[i]); */ if (!bShowLegendKey) @@ -2929,7 +3148,9 @@ public void removeLegend() { * @deprecated */ @Deprecated - public void makeStacked(int nChart) { // bar, col, line, area, pyramid col + bar, cone col + bar, cylinder col + bar + public void makeStacked(int nChart) { // bar, col, line, area, pyramid col + + // bar, cone col + bar, cylinder col + // + bar int chartType = this.getChartType(nChart); this.setChartOption("Stacked", "true", nChart); switch (chartType) { @@ -2970,7 +3191,9 @@ public void makeStacked(int nChart) { // bar, col, line, area, pyramid col + bar * @deprecated */ @Deprecated - public void make100PercentStacked(int nChart) { // bar, col, line, pyramid col + bar, cone col + bar, cylinder col + + public void make100PercentStacked(int nChart) { // bar, col, line, pyramid + // col + bar, cone col + + // bar, cylinder col + // bar int chartType = this.getChartType(nChart); this.setChartOption("Stacked", "true", nChart); @@ -3067,7 +3290,8 @@ public void make100PercentStacked3D(int nChart) { // bar, col, area * is100PercentStacked) instead */ @Deprecated - public void make3D() { // bar, col, line, pie, area, bubble, pyramid, cone, cylinder + public void make3D() { // bar, col, line, pie, area, bubble, pyramid, cone, + // cylinder make3D(0); } @@ -3086,7 +3310,8 @@ public void make3D() { // bar, col, line, pie, area, bubble, pyramid, cone, cyli * is100%Stacked) instead */ @Deprecated - public void make3D(int nChart) { // bar, col, line, pie, area, bubble, pyramid, cone, cylinder + public void make3D(int nChart) { // bar, col, line, pie, area, bubble, + // pyramid, cone, cylinder int chartType = this.getChartType(nChart); ThreeD td = null; switch (chartType) { @@ -3114,7 +3339,8 @@ public void make3D(int nChart) { // bar, col, line, pie, area, bubble, pyramid, break; case ChartConstants.PIECHART: case ChartConstants.LINECHART: - this.initThreeD(nChart); // just create a threeD rec w/ no extra options + this.initThreeD(nChart); // just create a threeD rec w/ no extra + // options break; case ChartConstants.BUBBLECHART: this.setChartOption("Percentage", "25", nChart); @@ -3170,7 +3396,8 @@ public void makeExploded() { // pie, donut this.setChartOption("Percentage", "25"); break; // ShowLdrLines="true" Percentage="25"/> - // exploded donut: ShowLdrLines="true" Donut="50" Percentage="25" + // exploded donut: ShowLdrLines="true" Donut="50" + // Percentage="25" // SmoothedLine="true"/> } } @@ -3202,18 +3429,25 @@ public void makeExploded3D(int nChart) { // pie } /* - * NOT IMPLEMENTED YET TODO: IMPLEMENT Make this Chart have smoothed lines + * NOT IMPLEMENTED YET TODO: IMPLEMENT Make this Chart have + * smoothed lines * (Scatter only) * - * public void makeSmoothedLines() { // scatter //Percentage="25" + * public void makeSmoothedLines() { // scatter + * //Percentage="25" * SmoothedLine="true } * - * public void makeWireFrame() { // surface // NO ColorFill, only // - * Percentage="25" SmoothedLine="true" // AnRot="20" Perspective="true" - * ThreeDScaling="true" TwoDWalls="true"/> // all else should be default for - * surface charts } public void makeContour() { // surface -- for wireframe + * public void makeWireFrame() { // surface // NO ColorFill, + * only // + * Percentage="25" SmoothedLine="true" // AnRot="20" + * Perspective="true" + * ThreeDScaling="true" TwoDWalls="true"/> // all else + * should be default for + * surface charts } public void makeContour() { // surface + * -- for wireframe * surface, no ColorFill //ColorFill="true" Percentage="25" - * SmoothedLine="true"/> // AnElev="90" pcDist="0" Perspective="true" + * SmoothedLine="true"/> // AnElev="90" pcDist="0" + * Perspective="true" * ThreeDScaling="true" TwoDWalls="true" } */ @@ -3251,20 +3485,27 @@ public String getJSON() { JSONObject theChart = new JSONObject(); try { JSONObject titles = new JSONObject(); - int type = this.getChartType(); // necessary for parsing AXIS options: horizontal charts "switch" axes ... + int type = this.getChartType(); // necessary for parsing AXIS + // options: horizontal charts + // "switch" axes ... // titles/labels titles.put("title", this.getTitle()); - titles.put("XAxis", (type != ChartConstants.BARCHART) ? (this.getXAxisLabel()) : this.getYAxisLabel()); // bar - // axes - // are - // reversed - // ... - titles.put("YAxis", (type != ChartConstants.BARCHART) ? (this.getYAxisLabel()) : this.getXAxisLabel()); + titles.put("XAxis", (type != ChartConstants.BARCHART) + ? (this.getXAxisLabel()) + : this.getYAxisLabel()); // bar + // axes + // are + // reversed + // ... + titles.put("YAxis", (type != ChartConstants.BARCHART) + ? (this.getYAxisLabel()) + : this.getXAxisLabel()); try { titles.put("ZAxis", this.getZAxisLabel()); } catch (Exception e) { - Logger.logWarn("ChartHandle.getJSON failed getting zaxislable:" + e.toString()); + Logger.logWarn("ChartHandle.getJSON failed getting zaxislable:" + + e.toString()); } theChart.put("titles", titles); @@ -3272,7 +3513,8 @@ public String getJSON() { short[] coords = mychart.getCoords(); theChart.put("width", coords[ChartHandle.WIDTH]); theChart.put("height", coords[ChartHandle.HEIGHT]); - theChart.put("row", mychart.getRow0()); // TODO: may not be necessary, see usage ... + theChart.put("row", mychart.getRow0()); // TODO: may not be + // necessary, see usage ... theChart.put("col", mychart.getCol0()); // Plot Area Background color int plotAreabg = this.getPlotAreaBgColor(); @@ -3281,18 +3523,19 @@ public String getJSON() { theChart.put("fill", FormatConstants.SVGCOLORSTRINGS[plotAreabg]); Double[] jMinMax = new Double[3]; - JSONObject chartObjectJSON = this.mychart.getChartObject().getJSON(this.mychart.getChartSeries(), this.wbh, - jMinMax); + JSONObject chartObjectJSON = this.mychart.getChartObject() + .getJSON(this.mychart.getChartSeries(), this.wbh, jMinMax); double yMax = 1.0, yMin = 0.0; int nSeries = 0; try { // it's possible to not have any series defined ... theChart.put("Series", chartObjectJSON.getJSONArray("Series")); // 20080416 KSC: Save SeriesJSON for later comparisons mychart.setSeriesJSON(chartObjectJSON.getJSONArray("Series")); - theChart.put("SeriesFills", chartObjectJSON.getJSONArray("SeriesFills")); // 20090729 KSC: capture bar - // colors or fills - } catch (Exception e) { - } + theChart.put("SeriesFills", chartObjectJSON + .getJSONArray("SeriesFills")); // 20090729 KSC: capture + // bar + // colors or fills + } catch (Exception e) {} theChart.put("type", chartObjectJSON.getJSONObject("type")); yMin = jMinMax[0].doubleValue(); yMax = jMinMax[1].doubleValue(); @@ -3300,29 +3543,35 @@ public String getJSON() { // Axes + Category Labels + Grid Lines try { - // inputJSONObject(theChart, this.getAxis(YAXIS, false).getJSON(this.wbh, type, + // inputJSONObject(theChart, this.getAxis(YAXIS, + // false).getJSON(this.wbh, type, // yMax, yMin, nSeries)); - theChart.put("y", - mychart.getAxes().getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries).getJSONObject("y")); - theChart.put("back_grid", mychart.getAxes().getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries) + theChart.put("y", mychart.getAxes() + .getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries) + .getJSONObject("y")); + theChart.put("back_grid", mychart.getAxes() + .getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries) .getJSONObject("back_grid")); - } catch (Exception e) { - } + } catch (Exception e) {} try { - // inputJSONObject(theChart, this.getAxis(XAXIS, false).getJSON(this.wbh, type, + // inputJSONObject(theChart, this.getAxis(XAXIS, + // false).getJSON(this.wbh, type, // yMax, yMin, nSeries)); - theChart.put("x", - mychart.getAxes().getJSON(XAXIS, this.wbh, type, yMax, yMin, nSeries).getJSONObject("x")); + theChart.put("x", mychart.getAxes() + .getJSON(XAXIS, this.wbh, type, yMax, yMin, nSeries) + .getJSONObject("x")); theChart.put("back_grid", mychart.getAxes() - .getJSON(ChartConstants.YAXIS, this.wbh, type, yMax, yMin, nSeries).getJSONObject("back_grid")); - } catch (Exception e) { - } + .getJSON(ChartConstants.YAXIS, this.wbh, type, yMax, yMin, nSeries) + .getJSONObject("back_grid")); + } catch (Exception e) {} // TODO: 3d Charts (z axis) /* * /* Chart Fonts sb.append(t(2) + "" + - * this.getChartFontRecsXML()); sb.append("\n" + t(2) + "\n"); - * sb.append(t(2) + "\n"); + * this.getChartFontRecsXML()); sb.append("\n" + t(2) + + * "\n"); + * sb.append(t(2) + "\n"); */ /* * Format Chart Area @@ -3332,13 +3581,19 @@ public String getJSON() { // Chart Legend if (this.hasDataLegend()) { short s = this.mychart.getLegend().getLegendPosition(); - String[] legends = this.mychart.getLegends(-1); // -1 is flag for all rather than for a specific chart + String[] legends = this.mychart.getLegends(-1); // -1 is flag + // for all + // rather than + // for a + // specific + // chart String l = ""; for (int i = 0; i < legends.length; i++) l += legends[i] + ","; if (l.length() > 0) l = l.substring(0, l.length() - 1); - theChart.put("legend", new JSONObject("{position:" + s + ",labels:[" + l + "]}")); + theChart.put("legend", new JSONObject( + "{position:" + s + ",labels:[" + l + "]}")); } } catch (JSONException e) { @@ -3358,7 +3613,8 @@ protected void inputJSONObject(JSONObject source, JSONObject input) { if (source != null) { try { for (int j = 0; j < input.names().length(); j++) { - source.put(input.names().getString(j), input.get(input.names().getString(j))); + source.put(input.names().getString(j), input + .get(input.names().getString(j))); } } catch (JSONException e) { Logger.logErr("Error inputting JSON Object: " + e); @@ -3403,14 +3659,16 @@ public JSONObject getCurrentSeries() { // Series Data // 20080516 KSC: See above JSONObject chartObjectJSON= // ((GenericChartObject)this.mychart.getChartObject()).getJSON( - // this.getAllChartSeriesHandles(), this.getCategories()[0], this.wbh, minMax); - JSONObject chartObjectJSON = this.mychart.getChartObject().getJSON(this.mychart.getChartSeries(), this.wbh, - jMinMax); + // this.getAllChartSeriesHandles(), this.getCategories()[0], + // this.wbh, minMax); + JSONObject chartObjectJSON = this.mychart.getChartObject() + .getJSON(this.mychart.getChartSeries(), this.wbh, jMinMax); try { retJSON.put("Series", chartObjectJSON.getJSONArray("Series")); } catch (Exception e) { - Logger.logWarn("ChartHandle.getCurrentSeries problem:" + e.toString()); + Logger.logWarn("ChartHandle.getCurrentSeries problem:" + + e.toString()); } // Retrieve Axis Scale info @@ -3420,18 +3678,25 @@ public JSONObject getCurrentSeries() { yMax = jMinMax[1].doubleValue(); nSeries = jMinMax[2].intValue(); - int type = this.getChartType(); // necessary for parsing AXIS options: horizontal charts "switch" axes ... + int type = this.getChartType(); // necessary for parsing AXIS + // options: horizontal charts + // "switch" axes ... // Axes + Category Labels + Grid Lines /* - * KSC: TAKE OUT JSON STUFF FOR NOW; WILL REFACTOR LATER try { - * inputJSONObject(retJSON, mychart.getAxes().getMinMaxJSON(YAXIS, this.wbh, - * type, yMax, yMin, nSeries)); } catch (Exception e) { } try { - * inputJSONObject(retJSON, mychart.getAxes().getMinMaxJSON(XAXIS, this.wbh, + * KSC: TAKE OUT JSON STUFF FOR NOW; WILL REFACTOR LATER try + * { + * inputJSONObject(retJSON, + * mychart.getAxes().getMinMaxJSON(YAXIS, this.wbh, + * type, yMax, yMin, nSeries)); } catch (Exception e) { } + * try { + * inputJSONObject(retJSON, + * mychart.getAxes().getMinMaxJSON(XAXIS, this.wbh, * type, yMax, yMin, nSeries)); } catch (Exception e) { } */ // TODO: 3d Charts (z axis) } catch (JSONException e) { - Logger.logErr("ChartHandle.getCurrentSeries: Error getting Series JSON: " + e); + Logger.logErr("ChartHandle.getCurrentSeries: Error getting Series JSON: " + + e); } return retJSON; } @@ -3487,67 +3752,95 @@ public String getSVG() { * @return String SVG */ public String getSVG(double scale) { - HashMap chartMetrics = mychart.getMetrics(wbh); // build or retrieve Chart Metrics --> - // dimensions + series data ... + HashMap chartMetrics = mychart.getMetrics(wbh); // build + // or + // retrieve + // Chart + // Metrics + // --> + // dimensions + // + + // series + // data + // ... StringBuffer svg = new StringBuffer(); // required header - // svg.append("\r\n"); // referneces + // svg.append("\r\n"); // referneces // the DTD - // svg.append("\r\n"); // Define SVG Canvas: - svg.append("\r\n"); svg.append(""); // scale chart -default scale=1 == 100% + + (chartMetrics.get("canvash") * scale) + "px'>"); // scale + // chart + // -default + // scale=1 + // == 100% // JavaScript hooks svg.append(getJavaScript()); - // Data Legend Box -- do before drawing plot area as legends box may change plot + // Data Legend Box -- do before drawing plot area as legends + // box may change plot // area coordinates - String legendSVG = getLegendSVG(chartMetrics); // but have to append it after because should overlap the + String legendSVG = getLegendSVG(chartMetrics); // but have to append it + // after because should + // overlap the // plotarea String bgclr = this.mychart.getPlotAreaBgColor(); // setup gradients svg.append(""); svg.append(""); - svg.append(""); - svg.append(""); + svg.append(""); + svg.append(""); svg.append(""); svg.append(""); // PLOT AREA BG + RECT // rectangle around entire chart canvas - if (!(mychart instanceof OOXMLChart) || !((OOXMLChart) mychart).roundedCorners) - svg.append("\r\n"); else // OOXML rounded corners - svg.append("\r\n"); // actual plot area svg.append("\r\n"); + + " x='" + chartMetrics.get("x") + "' y='" + + chartMetrics.get("y") + "' width='" + chartMetrics.get("w") + + "' height='" + chartMetrics.get("h") + + "' fill-rule='evenodd'/>\r\n"); - // AXES, IF PRESENT - DO BEFORE ACTUAL SERIES DATA SO DRAWS CORRECTLY + ADJUST + // AXES, IF PRESENT - DO BEFORE ACTUAL SERIES DATA SO DRAWS + // CORRECTLY + ADJUST // CHART DIMENSIONS - svg.append(mychart.getAxes().getSVG(XAXIS, chartMetrics, mychart.getChartSeries().getCategories())); - svg.append(mychart.getAxes().getSVG(YAXIS, chartMetrics, mychart.getChartSeries().getCategories())); + svg.append(mychart.getAxes().getSVG(XAXIS, chartMetrics, mychart + .getChartSeries().getCategories())); + svg.append(mychart.getAxes().getSVG(YAXIS, chartMetrics, mychart + .getChartSeries().getCategories())); // TODO: Z Axis // After Axes and gridlines (if present), - // ACTUAL bar/series/area/etc. svg generated from series and scale data - svg.append(this.mychart.getChartObject().getSVG(chartMetrics, mychart.getAxes().getMetrics(), - mychart.getChartSeries())); + // ACTUAL bar/series/area/etc. svg generated from series and + // scale data + svg.append(this.mychart.getChartObject().getSVG(chartMetrics, mychart + .getAxes().getMetrics(), mychart.getChartSeries())); svg.append(legendSVG); // append legend SVG obtained above @@ -3560,11 +3853,14 @@ public String getSVG(double scale) { svg.append(""); /* * //KSC: TESTING: REMOVE WHEN DONE if - * (WorkBookFactory.PID==WorkBookFactory.E360) { // save svg for testing + * (WorkBookFactory.PID==WorkBookFactory.E360) { // save svg + * for testing * purposes try { java.io.File f= new java.io.File( * "c:/eclipse/workspace/testfiles/io.starter.OpenXLS/output/charts/FromSheetster.svg" - * ); java.io.FileOutputStream fos= new java.io.FileOutputStream(f); - * fos.write(svg.toString().getBytes()); fos.flush(); fos.close(); } catch + * ); java.io.FileOutputStream fos= new + * java.io.FileOutputStream(f); + * fos.write(svg.toString().getBytes()); fos.flush(); + * fos.close(); } catch * (Exception e) {} } */ return svg.toString(); @@ -3572,7 +3868,8 @@ public String getSVG(double scale) { private String getLegendSVG(HashMap chartMetrics) { try { - return mychart.getLegend().getSVG(chartMetrics, mychart.getChartObject(), mychart.getChartSeries()); + return mychart.getLegend().getSVG(chartMetrics, mychart + .getChartObject(), mychart.getChartSeries()); } catch (NullPointerException ne) { // no legend?? return null; } @@ -3593,13 +3890,19 @@ protected String getJavaScript() { svg.append("function highLight(evt) {"); svg.append("this.bgc = evt.target.getAttributeNS(null, 'fill');"); - svg.append("evt.target.setAttributeNS(null,'fill','gold');"); // rgb('+ red +','+ green+','+blue+')');"); + svg.append("evt.target.setAttributeNS(null,'fill','gold');"); // rgb('+ + // red + // +','+ + // green+','+blue+')');"); svg.append("evt.target.setAttributeNS(null,'stroke-width','2');"); // svg.append("evt.target.setAttributeNS(null,'stroke-color','white');"); svg.append("}"); svg.append("function restore(evt) {"); - svg.append("evt.target.setAttributeNS(null,'fill',this.bgc);"); // rgb('+ red +','+ green+','+blue+')');"); + svg.append("evt.target.setAttributeNS(null,'fill',this.bgc);"); // rgb('+ + // red + // +','+ + // green+','+blue+')');"); svg.append("evt.target.setAttributeNS(null,'stroke-width','1');"); svg.append("}"); @@ -3629,4 +3932,9 @@ protected String getJavaScript() { public void WriteMainChartRecs(String fName) { mychart.getChartObject().WriteMainChartRecs(fName); } + + public Axis getAxis(int type) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/io/starter/OpenXLS/Document.java b/src/main/java/io/starter/OpenXLS/Document.java index e79f823..04192a0 100644 --- a/src/main/java/io/starter/OpenXLS/Document.java +++ b/src/main/java/io/starter/OpenXLS/Document.java @@ -5,18 +5,25 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -26,37 +33,35 @@ import java.io.IOException; import java.io.OutputStream; -import io.starter.formats.XLS.*; +import io.starter.formats.XLS.XLSConstants; /** An interface representing an OpenXLS document. - * @deprecated This interface provides no functionality beyond the abstract + * This interface provides no functionality beyond the abstract * {@link DocumentHandle} class. Use that type instead. */ -@Deprecated -public interface Document{ - public static final int DEBUG_LOW = XLSConstants.DEBUG_LOW; - public static final int DEBUG_MEDIUM = XLSConstants.DEBUG_MEDIUM; - public static final int DEBUG_HIGH = XLSConstants.DEBUG_HIGH; - +public interface Document { + public static final int DEBUG_LOW = XLSConstants.DEBUG_LOW; + public static final int DEBUG_MEDIUM = XLSConstants.DEBUG_MEDIUM; + public static final int DEBUG_HIGH = XLSConstants.DEBUG_HIGH; + + /** get a non-Excel property + * @return Returns the properties. + */ + public Object getProperty(String name); + + /** add non-Excel property + * @param properties The properties to set. + */ + public void addProperty(String name, Object val); - /** get a non-Excel property - * @return Returns the properties. - */ - public Object getProperty(String name) ; - - /** add non-Excel property - * @param properties The properties to set. - */ - public void addProperty(String name, Object val) ; - /** The Session for the WorkBook instance * * @return public BookSession getSession(); */ - - /** Sets the internal name of this WorkBookHandle. + + /** Sets the internal name of this WorkBookHandle. * * Overrides the default for 'getName()' which returns * the file name source of this WorkBook by default. @@ -64,18 +69,20 @@ public interface Document{ * @param WorkBook Name */ public abstract void setName(String nm); + /** Set the Debugging level. Higher values output more debugging info during execution. @parameter int Debug level. higher=more verbose */ public abstract void setDebugLevel(int l); + /** Returns the name of this WorkBook @return String name of WorkBook */ public abstract String getName(); - + /** Clears all values in a template WorkBook. Use this method to 'reset' the values of your @@ -90,36 +97,32 @@ public interface Document{ */ public abstract void reset(); - + /** Writes the document to the given stream in the requested format. * @param dest the stream to which the document should be written * @param format the constant representing the desired output format * @throws IllegalArgumentException if the given type code is invalid * @throws IOException if an error occurs while writing to the stream */ - public void write (OutputStream dest, int format) - throws IOException; - + public void write(OutputStream dest, int format) throws IOException; + /** Writes the document to the given stream in its native format. * @param dest the stream to which the document should be written * @throws IOException if an error occurs while writing to the stream */ - public void write (OutputStream dest) - throws IOException; - + public void write(OutputStream dest) throws IOException; + /** Writes the document to the given file in the requested format. * @param file the path to which the document should be written * @param format the constant representing the desired output format * @throws IllegalArgumentException if the given type code is invalid * @throws IOException if an error occurs while writing to the file */ - public void write (File file, int format) - throws IOException; - + public void write(File file, int format) throws IOException; + /** Writes the document to the given file in its native format. * @param file the path to which the document should be written * @throws IOException if an error occurs while writing to the stream */ - public void write (File file) - throws IOException; + public void write(File file) throws IOException; } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/DocumentHandle.java b/src/main/java/io/starter/OpenXLS/DocumentHandle.java index 9c84565..2c7de12 100644 --- a/src/main/java/io/starter/OpenXLS/DocumentHandle.java +++ b/src/main/java/io/starter/OpenXLS/DocumentHandle.java @@ -5,18 +5,25 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -46,42 +53,45 @@ /** Functionality common to all document types. */ -public abstract class DocumentHandle -implements Document, Handle, Closeable { +public abstract class DocumentHandle implements Document, Handle, Closeable { + + public static String workingdir = System.getProperty("user.dir") + + "/tmp"; + /** Format constant for the most appropriate format for this document. * If the document was read in from a file, this is usually the format that * was read in. */ - public static final int FORMAT_NATIVE = 0; - + public static final int FORMAT_NATIVE = 0; + /** The level of debugging output requested by the user. * Higher values should produce more output. */ - //TODO: should the debug level be static? - protected int DEBUGLEVEL = 0; + // TODO: should the debug level be static? + protected int DEBUGLEVEL = 0; /** The user-visible display name or title of this document. */ - protected String name = null; - + protected String name = null; + /** The file associated with this document. * This will generally be the file the document was parsed from, if any. */ - protected File file; - + protected File file; + /** Store for workbook properties. */ - private Map props = new HashMap(); + private Map props = new HashMap(); /** Handling for a streaming worksheet based workbook **/ - private boolean streamingSheets = false; - + private boolean streamingSheets = false; + /** * default constructor */ public DocumentHandle() { super(); - + } - + /** * Apr 5, 2011 * @param urlx @@ -92,14 +102,19 @@ public DocumentHandle(InputStream urlx) { Logger.logErr("DocumentHandle InputStream Constructor Not Implemented"); } + public DocumentHandle(File input) { + // TODO Auto-generated constructor stub + } + /** Retrieves a property in the workbook property store. * This is not an Excel-compatible feature. * * @param name the name of the property to retrieve * @return the value of the requested property or null if it doesn't exist */ + @Override public Object getProperty(String name) { - return props.get(name); + return props.get(name); } /** Sets the value of a property in the workbook property store. @@ -108,16 +123,17 @@ public Object getProperty(String name) { * @param name the name of the property which should be updated * @param value the value to which the property should be set */ + @Override public void addProperty(String name, Object val) { - props.put(name,val); + props.put(name, val); } /** Retrieves a Map containing the workbook properties store. * This is not an Excel-compatible feature. * @return an immutable Map containing the current workbook properties */ - public Map getProperties() { - return Collections.unmodifiableMap( props ); + public Map getProperties() { + return Collections.unmodifiableMap(props); } /** Replaces the workbook properties with the values in a given Map. @@ -125,138 +141,144 @@ public Map getProperties() { * * @param properties the values that will replace the existing properties */ - public void setProperties(Map properties) { - props = new HashMap(); - props.putAll( properties ); + public void setProperties(Map properties) { + props = new HashMap(); + props.putAll(properties); } /** Gets the OpenXLS version number. */ public static String getVersion() { - return GetInfo.getVersion(); + return GetInfo.getVersion(); } /** Sets the user-visible descriptive name or title of this document. * Some formats will persist this setting in the document itself. */ + @Override public void setName(String nm) { name = nm; } - - + /** Handling for streaming sheets. Currently this is in development and unsupported * @param streamSheets */ public void setStreamingSheets(boolean streamSheets) { - this.streamingSheets = streamSheets; + this.streamingSheets = streamSheets; } - + /** Sets the file name associated with this document. * @deprecated Use {@link #setFile(File)} instead. */ - public void setFileName (String name) { - file = new File( name ).getAbsoluteFile(); - } - - /** Sets the file associated with this document. + @Deprecated + public void setFileName(String name) { + file = new File(name).getAbsoluteFile(); + } + + /** Sets the file associated with this document. + */ + public void setFile(File file) { + this.file = file; + } + + /** Gets the file associated with this document. + * For documents read in from a file, this defaults to that file. If no + * file is associated with this document, for example if the document was + * parsed from a stream, this may return null. + */ + public File getFile() { + return file; + } + + /** Looks for magic numbers in the given input data and attempts to parse + * it with an appropriate DocumentHandle subclass. Detection + * is performed on a best-effort basis and is not guaranteed to be accurate. + * @throws IOException if an error occurs while reading from the stream + * @throws WorkBookException if parsing fails + */ + public static DocumentHandle getInstance(InputStream input) throws IOException { + BufferedInputStream bufferedStream = new BufferedInputStream(input); + // read in that start of the file for checking magic numbers + byte[] headerBytes; + int count; + // make sure the file is long enough to get magic numbers + bufferedStream.mark(1028); + headerBytes = new byte[512]; + count = bufferedStream.read(headerBytes); + bufferedStream.reset(); + + // if it starts with the LEO magic number check the header + if (LEOFile.checkIsLEO(headerBytes, count)) { + LEOFile leo = new LEOFile(bufferedStream); + + if (leo.hasWorkBook()) + return new WorkBookHandle(leo); + else + throw new WorkBookException( + "input is LEO but no supported format detected", -1); + } + + String headerString; + try { + headerString = new String(headerBytes, 0, count, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // UTF-8 support is required by the JLS + throw new Error("the JVM does not support UTF-8", e); + } + + // if it's a ZIP archive, try parsing as OOXML + if (headerString.startsWith("PK")) { + return new WorkBookHandle(bufferedStream); + } + + if ((headerString.indexOf(",") > -1) + && (headerString.indexOf(",") > -1)) { + // init a blank workbook + WorkBookHandle book = new WorkBookHandle(); + + // map CSV into workbook + try { + WorkSheetHandle sheet = book.getWorkSheet(0); + sheet.readCSV(new BufferedReader( + new InputStreamReader(bufferedStream))); + return book; + } catch (Exception e) { + throw new WorkBookException( + "Error encountered importing CSV: " + e.toString(), + WorkBookException.ILLEGAL_INIT_ERROR); + } + } + + else { + throw new WorkBookException("unknown file format", -1); + } + } + + /** Gets the file name associated with this document. + * For documents read in from a file, this defaults to that file. If no + * file is associated with this document, for example if the document was + * parsed from a stream, this may return null. + * @deprecated Use {@link #getFile()} instead. */ - public void setFile (File file) { - this.file = file; - } - - /** Gets the file associated with this document. - * For documents read in from a file, this defaults to that file. If no - * file is associated with this document, for example if the document was - * parsed from a stream, this may return null. - */ - public File getFile() { - return file; - } - - /** Looks for magic numbers in the given input data and attempts to parse - * it with an appropriate DocumentHandle subclass. Detection - * is performed on a best-effort basis and is not guaranteed to be accurate. - * @throws IOException if an error occurs while reading from the stream - * @throws WorkBookException if parsing fails - */ - public static DocumentHandle getInstance (InputStream input) - throws IOException { - BufferedInputStream bufferedStream = new BufferedInputStream(input); - // read in that start of the file for checking magic numbers - byte[] headerBytes; - int count; - // make sure the file is long enough to get magic numbers - bufferedStream.mark(1028); - headerBytes = new byte[ 512 ]; - count = bufferedStream.read( headerBytes ); - bufferedStream.reset(); - - // if it starts with the LEO magic number check the header - if (LEOFile.checkIsLEO( headerBytes, count )) { - LEOFile leo = new LEOFile( bufferedStream ); - - if (leo.hasWorkBook()) return new WorkBookHandle( leo ); - else throw new WorkBookException( - "input is LEO but no supported format detected", -1 ); - } - - String headerString; - try { - headerString = new String( headerBytes, 0, count, "UTF-8" ); - } catch (UnsupportedEncodingException e) { - // UTF-8 support is required by the JLS - throw new Error( "the JVM does not support UTF-8", e ); - } - - // if it's a ZIP archive, try parsing as OOXML - if (headerString.startsWith( "PK" )) { - return new WorkBookHandle( bufferedStream ); - } - - if((headerString.indexOf(",")>-1) - &&(headerString.indexOf(",")>-1)){ - // init a blank workbook - WorkBookHandle book = new WorkBookHandle(); - - - // map CSV into workbook - try{ - WorkSheetHandle sheet = book.getWorkSheet( 0 ); - sheet.readCSV( new BufferedReader( - new InputStreamReader( bufferedStream ) ) ); - return book; - }catch(Exception e){ - throw new WorkBookException( - "Error encountered importing CSV: " + e.toString(), WorkBookException.ILLEGAL_INIT_ERROR); - } - } - - else { - throw new WorkBookException( "unknown file format", -1 ); - } - } - - /** Gets the file name associated with this document. - * For documents read in from a file, this defaults to that file. If no - * file is associated with this document, for example if the document was - * parsed from a stream, this may return null. - * @deprecated Use {@link #getFile()} instead. - */ - public String getFileName(){ - return file != null ? file.getPath() : "New Document.doc"; - } - + @Deprecated + public String getFileName() { + return file != null ? file.getPath() : "New Document.doc"; + } + /** Sets the debugging output level. * Higher values will produce more output. Output at higher values will * generally only be of use to OpenXLS developers. Increased output incurs * a performance penalty, so it is recommended this be left at zero unless * you are reporting a bug. */ - public void setDebugLevel (int level) { + @Override + public void setDebugLevel(int level) { DEBUGLEVEL = level; } - - public int getDebugLevel() { return DEBUGLEVEL; } + + public int getDebugLevel() { + return DEBUGLEVEL; + } /** Downloads the resource at the given URL to a temporary file. * @param u the URL representing the resource to be downloaded @@ -267,116 +289,124 @@ public void setDebugLevel (int level) { */ @Deprecated protected static File getFileFromURL(URL u) { - try{ - File fx = TempFileManager.createTempFile("upload-"+System.currentTimeMillis(),".tmp"); - + try { + File fx = TempFileManager.createTempFile("upload-" + + System.currentTimeMillis(), ".tmp"); + URLConnection uc = u.openConnection(); String contentType = uc.getContentType(); - int contentLength = uc.getContentLength(); - if (contentType.startsWith("text/") || contentLength == -1) { - throw new IOException("This is not a binary file."); - } - InputStream raw = uc.getInputStream(); - InputStream in = new BufferedInputStream(raw); - byte[] data = new byte[contentLength]; - int bytesRead = 0; - int offset = 0; - while (offset < contentLength) { - bytesRead = in.read(data, offset, data.length - offset); - if (bytesRead == -1) - break; - offset += bytesRead; - } - in.close(); - - if (offset != contentLength) { - throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes"); - } - - // String filename = u.getFile().substring(filename.lastIndexOf('/') + 1); - FileOutputStream out = new FileOutputStream(fx); - out.write(data); - out.flush(); - out.close(); - return fx; - }catch(Exception e){ + int contentLength = uc.getContentLength(); + if (contentType.startsWith("text/") || contentLength == -1) { + throw new IOException("This is not a binary file."); + } + InputStream raw = uc.getInputStream(); + InputStream in = new BufferedInputStream(raw); + byte[] data = new byte[contentLength]; + int bytesRead = 0; + int offset = 0; + while (offset < contentLength) { + bytesRead = in.read(data, offset, data.length - offset); + if (bytesRead == -1) + break; + offset += bytesRead; + } + in.close(); + + if (offset != contentLength) { + throw new IOException("Only read " + offset + + " bytes; Expected " + contentLength + " bytes"); + } + + // String filename = + // u.getFile().substring(filename.lastIndexOf('/') + 1); + FileOutputStream out = new FileOutputStream(fx); + out.write(data); + out.flush(); + out.close(); + return fx; + } catch (Exception e) { Logger.logErr("Could not load WorkBook from URL: " + e.toString()); return null; } } - /** Gets the user-visible descriptive name or title of this document. - */ - public String getName(){ - if(name!=null) - return name; - else - return "Untitled Document"; - } + /** Gets the user-visible descriptive name or title of this document. + */ + @Override + public String getName() { + if (name != null) + return name; + else + return "Untitled Document"; + } /** Resets the document state to what it was when it was loaded. * @throws UnsupportedOperationException if there is not sufficient data * available to perform the reversion */ + @Override public abstract void reset(); /** Gets the constant representing this document's native format. - */ - public abstract int getFormat(); - - /** Gets the file name extension for this document's native format. - */ - public abstract String getFileExtension(); - + */ + public abstract int getFormat(); + + /** Gets the file name extension for this document's native format. + */ + public abstract String getFileExtension(); + /** Writes the document to the given stream in the requested format. * @param dest the stream to which the document should be written * @param format the constant representing the desired output format * @throws IllegalArgumentException if the given type code is invalid * @throws IOException if an error occurs while writing to the stream */ - public abstract void write (OutputStream dest, int format) - throws IOException; - + @Override + public abstract void write(OutputStream dest, int format) throws IOException; + /** Writes the document to the given stream in its native format. * @param dest the stream to which the document should be written * @throws IOException if an error occurs while writing to the stream */ - public void write (OutputStream dest) - throws IOException { - this.write( dest, FORMAT_NATIVE ); + @Override + public void write(OutputStream dest) throws IOException { + this.write(dest, FORMAT_NATIVE); } - + /** Writes the document to the given file in the requested format. * @param file the path to which the document should be written * @param format the constant representing the desired output format * @throws IllegalArgumentException if the given type code is invalid * @throws IOException if an error occurs while writing to the file */ - public void write (File file, int format) - throws IOException { - if (format > WorkBookHandle.FORMAT_XLS && this.file!=null) - OOXMLAdapter.refreshPassThroughFiles((WorkBookHandle)this); - - if (file.exists()) file.delete(); // try this - OutputStream stream = new BufferedOutputStream(new FileOutputStream( file ) ); - this.write( stream, format ); - this.file= file; // necesary for OOXML re-write ... + @Override + public void write(File file, int format) throws IOException { + if (format > WorkBookHandle.FORMAT_XLS && this.file != null) + OOXMLAdapter.refreshPassThroughFiles((WorkBookHandle) this); + + if (file.exists()) + file.delete(); // try this + OutputStream stream = new BufferedOutputStream( + new FileOutputStream(file)); + this.write(stream, format); + this.file = file; // necesary for OOXML re-write ... stream.flush(); stream.close(); } - + /** Writes the document to the given file in its native format. * @param file the path to which the document should be written * @throws IOException if an error occurs while writing to the stream */ - public void write (File file) - throws IOException { - this.write( file, FORMAT_NATIVE ); + @Override + public void write(File file) throws IOException { + this.write(file, FORMAT_NATIVE); } - + /** Returns a string representation of the object. * This is currently equivalent to {@link #getName()}. */ + @Override public String toString() { return getName(); } diff --git a/src/main/java/io/starter/OpenXLS/FormatHandle.java b/src/main/java/io/starter/OpenXLS/FormatHandle.java index 9698284..a796f3a 100644 --- a/src/main/java/io/starter/OpenXLS/FormatHandle.java +++ b/src/main/java/io/starter/OpenXLS/FormatHandle.java @@ -1995,7 +1995,7 @@ else if (foundit && !(c=='0' || c=='#' || c=='?')) // numeric placeholders. if newPat+= pats[i]; // leave alone } - //io.starter.OpenXLS.util.Logger.log("Old Style" + pat + ". New Style: " + newPat + ". Increase?" + (increase?"yes":"no")); // KSC: TESETING: TAKE OUT WHEN DONE + //io.starter.toolkit.Logger.log("Old Style" + pat + ". New Style: " + newPat + ". Increase?" + (increase?"yes":"no")); // KSC: TESETING: TAKE OUT WHEN DONE this.setFormatPattern(newPat); } catch (Exception e) { Logger.logErr("Error setting style"); // KSC: TESETING: TAKE OUT WHEN DONE diff --git a/src/main/java/io/starter/OpenXLS/GetInfo.java b/src/main/java/io/starter/OpenXLS/GetInfo.java index 7a3db6d..cf6ee59 100644 --- a/src/main/java/io/starter/OpenXLS/GetInfo.java +++ b/src/main/java/io/starter/OpenXLS/GetInfo.java @@ -72,6 +72,6 @@ else if (null != commit) public static void main(String[] args) { // Product name and version - io.starter.OpenXLS.util.Logger.log ("This is OpenXLS " + getVersion()); + io.starter.toolkit.Logger.log ("This is OpenXLS " + getVersion()); } } diff --git a/src/main/java/io/starter/OpenXLS/WorkBook.java b/src/main/java/io/starter/OpenXLS/WorkBook.java index 1f771a9..ffbfa7d 100644 --- a/src/main/java/io/starter/OpenXLS/WorkBook.java +++ b/src/main/java/io/starter/OpenXLS/WorkBook.java @@ -5,18 +5,25 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -24,101 +31,100 @@ import java.io.OutputStream; -import io.starter.formats.XLS.*; +import io.starter.formats.XLS.CellNotFoundException; +import io.starter.formats.XLS.ChartNotFoundException; +import io.starter.formats.XLS.FunctionNotSupportedException; +import io.starter.formats.XLS.PivotTableNotFoundException; +import io.starter.formats.XLS.WorkSheetNotFoundException; +import io.starter.formats.XLS.XLSConstants; /** An interface representing an OpenXLS WorkBook. - * @deprecated This interface does not provide any useful abstraction. In - * practice it is equivalent to the {@link WorkBookHandle} class. - * Use that type instead. + * */ -public interface WorkBook -extends Handle, Document{ - - public static int CALCULATE_ALWAYS = XLSConstants.CALCULATE_ALWAYS; - public static int CALCULATE_EXPLICIT= XLSConstants.CALCULATE_EXPLICIT; - public static int CALCULATE_AUTO = XLSConstants.CALCULATE_AUTO; - public static String CALC_MODE_PROP = XLSConstants.CALC_MODE_PROP; - public static String REFTRACK_PROP = XLSConstants.REFTRACK_PROP; - public static String USETEMPFILE_PROP = XLSConstants.USETEMPFILE_PROP; - public static String DEFAULTENCODING= XLSConstants.DEFAULTENCODING; - public static String UNICODEENCODING= XLSConstants.UNICODEENCODING; - public static String VALIDATEWORKBOOK= XLSConstants.VALIDATEWORKBOOK; - - // public static final int FORMULA_CALC_AUTO = 0; - - public static final int STRING_ENCODING_AUTO = XLSConstants.STRING_ENCODING_AUTO; - public static final int STRING_ENCODING_UNICODE = - XLSConstants.STRING_ENCODING_UNICODE; - public static final int STRING_ENCODING_COMPRESSED = - XLSConstants.STRING_ENCODING_COMPRESSED; - public static final int ALLOWDUPES = XLSConstants.ALLOWDUPES; - public static final int SHAREDUPES = XLSConstants.SHAREDUPES; - +public interface WorkBook extends Handle, Document { - /** Explicit calcing of formulas - * - * - * @param mode - */ + public static int CALCULATE_ALWAYS = XLSConstants.CALCULATE_ALWAYS; + public static int CALCULATE_EXPLICIT = XLSConstants.CALCULATE_EXPLICIT; + public static int CALCULATE_AUTO = XLSConstants.CALCULATE_AUTO; + public static String CALC_MODE_PROP = XLSConstants.CALC_MODE_PROP; + public static String REFTRACK_PROP = XLSConstants.REFTRACK_PROP; + public static String USETEMPFILE_PROP = XLSConstants.USETEMPFILE_PROP; + public static String DEFAULTENCODING = XLSConstants.DEFAULTENCODING; + public static String UNICODEENCODING = XLSConstants.UNICODEENCODING; + public static String VALIDATEWORKBOOK = XLSConstants.VALIDATEWORKBOOK; + + // public static final int FORMULA_CALC_AUTO = 0; + + public static final int STRING_ENCODING_AUTO = XLSConstants.STRING_ENCODING_AUTO; + public static final int STRING_ENCODING_UNICODE = XLSConstants.STRING_ENCODING_UNICODE; + public static final int STRING_ENCODING_COMPRESSED = XLSConstants.STRING_ENCODING_COMPRESSED; + public static final int ALLOWDUPES = XLSConstants.ALLOWDUPES; + public static final int SHAREDUPES = XLSConstants.SHAREDUPES; + + /** Explicit calcing of formulas + * + * + * @param mode + */ public void setFormulaCalculationMode(int mode); - - public int getFormulaCalculationMode(); - - /** get a non-Excel property - * @return Returns the properties. - */ - public Object getProperty(String name) ; - - /** add non-Excel property - * @param properties The properties to set. - */ - public void addProperty(String name, Object val) ; - + + public int getFormulaCalculationMode(); + + /** get a non-Excel property + * @return Returns the properties. + */ + @Override + public Object getProperty(String name); + + /** add non-Excel property + * @param properties The properties to set. + */ + @Override + public void addProperty(String name, Object val); + /** The Session for the WorkBook instance * * @return public BookSession getSession(); */ - - public java.awt.Color[] getColorTable() ; - - /** Returns all of the Cells contained in the WorkBook - * - */ - public CellHandle[] getCells(); - + + public java.awt.Color[] getColorTable(); + + /** Returns all of the Cells contained in the WorkBook + * + */ + public CellHandle[] getCells(); + /** Returns the Cell at the specified Location * * @param address * @return */ - public abstract CellHandle getCell(String address) - throws CellNotFoundException, WorkSheetNotFoundException; - - + public abstract CellHandle getCell(String address) throws CellNotFoundException, WorkSheetNotFoundException; + /** * Returns an Array of all the FormatHandles present in the workbook * @return all existing FormatHandles in the workbook */ public abstract FormatHandle[] getFormats(); - + /** Gets the date format used by this book. */ - public DateConverter.DateFormat getDateFormat(); - + public DateConverter.DateFormat getDateFormat(); + /** get a handle to a PivotTable in the WorkBook @param String name of the PivotTable @return PivotTable the PivotTable */ - public abstract PivotTableHandle getPivotTable(String ptname) - throws PivotTableNotFoundException; + public abstract PivotTableHandle getPivotTable(String ptname) throws PivotTableNotFoundException; + /** get an array of handles to all PivotTables in the WorkBook @return PivotTable[] all of the WorkBooks PivotTables */ - public abstract PivotTableHandle[] getPivotTables() - throws PivotTableNotFoundException; + public abstract PivotTableHandle[] getPivotTables() throws PivotTableNotFoundException; + /** set the workbook to protected mode Note: the password cannot be decrypted or changed @@ -129,6 +135,7 @@ public abstract PivotTableHandle[] getPivotTables() */ public abstract void setProtected(boolean b); + /** set Default row height Note: only affects undefined Rows containing Cells @@ -136,6 +143,7 @@ public abstract PivotTableHandle[] getPivotTables() @param int Default Row Height */ public abstract void setDefaultRowHeight(int t); + /** set Default col width Note: only affects undefined Columns containing Cells @@ -144,9 +152,7 @@ public abstract PivotTableHandle[] getPivotTables() */ public abstract void setDefaultColWidth(int t); - - - + /** Sets the internal name of this WorkBookHandle. * * Overrides the default for 'getName()' which returns @@ -154,45 +160,54 @@ public abstract PivotTableHandle[] getPivotTables() * * @param WorkBook Name */ + @Override public abstract void setName(String nm); + /** Set the Debugging level. Higher values output more debugging info during execution. @parameter int Debug level. higher=more verbose */ + @Override public abstract void setDebugLevel(int l); + /** Returns a Named Range Handle @return NameHandle a Named range in the WorkBook */ - public abstract NameHandle getNamedRange(String rangename) - throws CellNotFoundException; + public abstract NameHandle getNamedRange(String rangename) throws CellNotFoundException; + /** Returns a Chart Handle @return ChartHandle a Chart in the WorkBook */ - public abstract ChartHandle getChart(String chartname) - throws ChartNotFoundException; + public abstract ChartHandle getChart(String chartname) throws ChartNotFoundException; + /** Returns all Chart Handles contained in the WorkBook @return ChartHandle[] an array of all Charts in the WorkBook */ public abstract ChartHandle[] getCharts(); + /** Returns all Named Range Handles @return NameHandle[] all of the Named ranges in the WorkBook */ public abstract NameHandle[] getNamedRanges(); + /** Returns the name of this WorkBook @return String name of WorkBook */ + @Override public abstract String getName(); + /** Returns the number of Cells in this WorkBook @return int number of Cells */ public abstract int getNumCells(); + /** Returns a byte Array containing the valid file containing this WorkBook and associated Storages (such as VB files @@ -216,15 +231,16 @@ and associated Storages (such as VB files * are moved. * */ - // public abstract void initCellRanges(boolean createblanks); - + // public abstract void initCellRanges(boolean + // createblanks); + /** Returns an array of handles to all of the WorkSheets in the Workbook. @return WorkSheetHandle[] Array of all WorkSheets in WorkBook */ public abstract WorkSheetHandle[] getWorkSheets(); - + /** returns the handle to a WorkSheet by name. @param index of worksheet (ie: 0) @@ -232,9 +248,8 @@ and associated Storages (such as VB files @exception WorkSheetNotFoundException if the specified WorkSheet is not found in the WorkBook. */ - public abstract WorkSheetHandle getWorkSheet(int i) - throws WorkSheetNotFoundException; - + public abstract WorkSheetHandle getWorkSheet(int i) throws WorkSheetNotFoundException; + /** returns the handle to a WorkSheet by name. @param String name of worksheet (ie: "Sheet1") @@ -242,9 +257,8 @@ public abstract WorkSheetHandle getWorkSheet(int i) @exception WorkSheetNotFoundException if the specified WorkSheet is not found in the WorkBook. */ - public abstract WorkSheetHandle getWorkSheet(String handstr) - throws WorkSheetNotFoundException; - + public abstract WorkSheetHandle getWorkSheet(String handstr) throws WorkSheetNotFoundException; + /** Returns a low-level WorkBook. NOTE: The WorkBook class is NOT a part of the @@ -255,7 +269,6 @@ public abstract WorkSheetHandle getWorkSheet(String handstr) */ public abstract io.starter.formats.XLS.WorkBook getWorkBook(); - /** Clears all values in a template WorkBook. Use this method to 'reset' the values of your @@ -269,6 +282,7 @@ public abstract WorkSheetHandle getWorkSheet(String handstr) and set them back to a default. */ + @Override public abstract void reset(); /** Set Encoding mode of new Strings added to file. @@ -305,6 +319,7 @@ in your String data can be represented with a single byte (Compressed.) @param int String Encoding Mode */ public abstract void setStringEncodingMode(int mode); + /** Set Duplicate String Handling Mode. The Duplicate String Mode determines the behavior of @@ -336,32 +351,29 @@ in your String data can be represented with a single byte (Compressed.) @param int Duplicate String Handling Mode */ public abstract void setDupeStringMode(int mode); + /** Copies an existing Chart to another WorkSheet * * @param chartname * @param sheetname */ - public abstract void copyChartToSheet(String chartname, String sheetname) - throws ChartNotFoundException, WorkSheetNotFoundException; + public abstract void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException; + /** Copies an existing Chart to another WorkSheet * * @param chart * @param sheet */ - public abstract void copyChartToSheet( - ChartHandle chart, - WorkSheetHandle sheet) - throws ChartNotFoundException, WorkSheetNotFoundException; + public abstract void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) throws ChartNotFoundException, WorkSheetNotFoundException; + /** Copy (duplicate) a worksheet in the workbook and add it to the end of the workbook with a new name @param String the Name of the source worksheet; @param String the Name of the new (destination) worksheet; @return the new WorkSheetHandle */ - public abstract WorkSheetHandle copyWorkSheet( - String SourceSheetName, - String NewSheetName) - throws WorkSheetNotFoundException; + public abstract WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName) throws WorkSheetNotFoundException; + /** Iterate through the formulas in this WorkBook and call the * calculate method on each. * @@ -374,10 +386,9 @@ public abstract WorkSheetHandle copyWorkSheet( * * @throws FunctionNotSupportedException */ - public abstract void calculateFormulas() - throws FunctionNotSupportedException; - - /** Removes all of the WorkSheets from this WorkBook. + public abstract void calculateFormulas() throws FunctionNotSupportedException; + + /** Removes all of the WorkSheets from this WorkBook. * * Bytes streamed from this WorkBook will create invalid * Spreadsheet files unless a WorkSheet(s) are added to it. @@ -385,6 +396,7 @@ public abstract void calculateFormulas() * */ public abstract void removeAllWorkSheets(); + /** Returns a WorkBookHandle containing an empty * version of this WorkBook. * @@ -401,6 +413,7 @@ public abstract void calculateFormulas() * @see addSheetFromWorkBook */ public abstract WorkBookHandle getNoSheetWorkBook(); + /** Inserts a worksheet from a Source WorkBook. * * @@ -408,30 +421,30 @@ public abstract void calculateFormulas() * @param sourceBook - the WorkBook containing the sheet to copy * @param sourceSheetName - the name of the sheet to copy * @param destSheetName - the name of the new sheet in this workbook - * @deprecated + * @throws WorkSheetNotFoundException */ - public abstract boolean addSheetFromWorkBook( - WorkBookHandle sourceBook, - String sourceSheetName, - String destSheetName); + public abstract boolean addSheetFromWorkBook(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException; + /** Inserts a new worksheet and places it at the end of the workbook @param WorkSheetHandle the source WorkSheetHandle; @param String the Name of the new (destination) worksheet; */ public abstract WorkSheetHandle addWorkSheet(WorkSheetHandle sht, String NewSheetName); + /** Creates a new worksheet and places it at the end of the workbook @param String the Name of the newly created worksheet @return the new WorkSheetHandle */ public abstract WorkSheetHandle createWorkSheet(String name); - + /** * Returns the name of this Sheet. * * @see java.lang.Object#toString() */ + @Override public abstract String toString(); public StringBuffer writeBytes(OutputStream bbout); diff --git a/src/main/java/io/starter/OpenXLS/WorkBookHandle.java b/src/main/java/io/starter/OpenXLS/WorkBookHandle.java index 4160a51..5d5d292 100644 --- a/src/main/java/io/starter/OpenXLS/WorkBookHandle.java +++ b/src/main/java/io/starter/OpenXLS/WorkBookHandle.java @@ -5,18 +5,25 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -104,53 +111,54 @@ * */ public class WorkBookHandle extends DocumentHandle implements WorkBook, Handle { - private static final long serialVersionUID = -7040065539645064209L; - /** * A Writer to which a record dump should be written on input. This is used by * the dumping code in WorkBookFactory. */ - public static Writer dump_input = null; + public static Writer dump_input = null; - protected io.starter.formats.XLS.WorkBook mybook; - protected LEOFile myLEOFile; - protected WorkBookFactory myfactory = null; + protected io.starter.formats.XLS.WorkBook mybook; + protected LEOFile myLEOFile; + protected WorkBookFactory myfactory = null; - protected ProgressListener plist; + protected ProgressListener plist; /** Format constant for BIFF8 (Excel '97-2007). */ - public static final int FORMAT_XLS = 100; + public static final int FORMAT_XLS = 100; /** Format constant for normal OOXML (Excel 2007). */ - public static final int FORMAT_XLSX = 101; + public static final int FORMAT_XLSX = 101; /** Format constant for macro-enabled OOXML (Excel 2007). */ - public static final int FORMAT_XLSM = 102; + public static final int FORMAT_XLSM = 102; /** Format constant for OOXML template (Excel 2007). */ - public static final int FORMAT_XLTX = 103; + public static final int FORMAT_XLTX = 103; /** Format constant for macro-enabled OOXML template (Excel 2007). */ - public static final int FORMAT_XLTM = 104; + public static final int FORMAT_XLTM = 104; - private static byte[] protobook; - private static byte[] protochart; - private static byte[] protosheet; - public static java.text.SimpleDateFormat simpledateformat = new java.text.SimpleDateFormat(); // static to reuse + private static byte[] protobook; + private static byte[] protochart; + private static byte[] protosheet; + public static java.text.SimpleDateFormat simpledateformat = new java.text.SimpleDateFormat(); // static + // to + // reuse /** * How many recursion levels to allow formulas to be calculated before throwing * a circular reference error */ - public static int RECURSION_LEVELS_ALLOWED = 107; + public static int RECURSION_LEVELS_ALLOWED = 107; /** This is **/ - public static String CONVERTMULBLANKS = "deprecated"; + public static String CONVERTMULBLANKS = "deprecated"; protected static byte[] getPrototypeBook() throws IOException { if (protobook == null) - protobook = ResourceLoader.getBytesFromJar("/io/starter/OpenXLS/templates/prototysspe.ser"); + protobook = ResourceLoader + .getBytesFromJar("/io/starter/OpenXLS/templates/prototysspe.ser"); return protobook; } @@ -172,7 +180,8 @@ protected static byte[] getPrototypeSheet() { protected static byte[] getPrototypeChart() { if (protochart == null) { try { - byte[] bookbytes = ResourceLoader.getBytesFromJar("/io/starter/OpenXLS/templates/prototypechart.ser"); + byte[] bookbytes = ResourceLoader + .getBytesFromJar("/io/starter/OpenXLS/templates/prototypechart.ser"); WorkBookHandle chartBook = new WorkBookHandle(bookbytes); ChartHandle ch = chartBook.getCharts()[0]; protochart = ch.getSerialBytes(); @@ -221,7 +230,8 @@ public int searchAndReplace(String searchfor, String replacewith) { if (!cx[t].isNumber()) { String v = cx[t].getStringVal(); if (v.indexOf(searchfor) > -1) { - cx[t].setVal(StringTool.replaceText(v, searchfor, replacewith)); + cx[t].setVal(StringTool + .replaceText(v, searchfor, replacewith)); foundcount++; } } @@ -248,6 +258,7 @@ public String[] getAllStrings() { * * @return */ + @Override public java.awt.Color[] getColorTable() { return this.getWorkBook().getColorTable(); } @@ -263,6 +274,7 @@ public boolean is1904() { } /** Gets the date format used by this book. */ + @Override public DateConverter.DateFormat getDateFormat() { return mybook.getDateFormat(); } @@ -302,11 +314,12 @@ public String getStats() { * @param address * @return */ + @Override public CellHandle getCell(String address) throws CellNotFoundException, WorkSheetNotFoundException { int shtpos = address.indexOf("!"); if (shtpos < 0) - throw new CellNotFoundException( - address + " not found. You need to specify a location in the format: Sheet1!A1"); + throw new CellNotFoundException(address + + " not found. You need to specify a location in the format: Sheet1!A1"); String sheetstr = address.substring(0, shtpos); WorkSheetHandle sht = this.getWorkSheet(sheetstr); String celstr = address.substring(shtpos + 1); @@ -335,6 +348,7 @@ public CellRange[] getCellRanges() { * name of the PivotTable * @return PivotTable the PivotTable */ + @Override public PivotTableHandle getPivotTable(String ptname) throws PivotTableNotFoundException { Sxview st = mybook.getPivotTableView(ptname); if (st == null) @@ -347,10 +361,12 @@ public PivotTableHandle getPivotTable(String ptname) throws PivotTableNotFoundEx * * @return PivotTable[] all of the WorkBooks PivotTables */ + @Override public PivotTableHandle[] getPivotTables() throws PivotTableNotFoundException { Sxview[] sxv = mybook.getAllPivotTableViews(); if (sxv == null || sxv.length == 0) - throw new PivotTableNotFoundException("There are no PivotTables defined in: " + this.getName()); + throw new PivotTableNotFoundException( + "There are no PivotTables defined in: " + this.getName()); PivotTableHandle[] pth = new PivotTableHandle[sxv.length]; for (int t = 0; t < pth.length; t++) { pth[t] = new PivotTableHandle(sxv[t], this); @@ -378,6 +394,7 @@ public PivotTableHandle[] getPivotTables() throws PivotTableNotFoundException { * @param CalcMode * Calculation mode to use in workbook. */ + @Override public void setFormulaCalculationMode(int CalcMode) { mybook.setCalcMode(CalcMode); } @@ -399,6 +416,7 @@ public void setFormulaCalculationMode(int CalcMode) { * @param CalcMode * Calculation mode to use in workbook. */ + @Override public int getFormulaCalculationMode() { return mybook.getCalcMode(); } @@ -412,6 +430,7 @@ public int getFormulaCalculationMode() { * @param boolean * whether to protect the book */ + @Override public void setProtected(boolean protect) { // TODO: Check that this behavior is correct // This is what the old implementation did @@ -435,11 +454,15 @@ public void setProtected(boolean protect) { * @param int * Default Row Height */ - // should be a double as Excel units are 1/20 of what is stored in + // should be a double as Excel units are 1/20 of what is + // stored in // defaultrowheight - // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 (approx) - // should expect users to use Excel units and target method do the 20* + // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 + // (approx) + // should expect users to use Excel units and target method + // do the 20* // conversion + @Override public void setDefaultRowHeight(int t) { mybook.setDefaultRowHeight(t); } @@ -453,6 +476,7 @@ public void setDefaultRowHeight(int t) { * This setting is roughly the width of the character '0' The default width of a * column is 8. */ + @Override public void setDefaultColWidth(int t) { mybook.setDefaultColWidth(t); } @@ -506,7 +530,8 @@ public ImageHandle getImage(String imagename) throws ImageNotFoundException { ; } } - throw new ImageNotFoundException("Image not found: " + imagename + " in " + this.toString()); + throw new ImageNotFoundException( + "Image not found: " + imagename + " in " + this.toString()); } /** @@ -514,6 +539,7 @@ public ImageHandle getImage(String imagename) throws ImageNotFoundException { * * @return NameHandle a Named range in the WorkBook */ + @Override public NameHandle getNamedRange(String rangename) throws CellNotFoundException { Name nand = mybook.getName(rangename.toUpperCase()); // case-insensitive if (nand == null) @@ -565,9 +591,11 @@ public NameHandle createNamedRange(String name, String rangeDef) { * * @return ChartHandle a Chart in the WorkBook */ - // KSC: NOTE: this methodology needs work as a book may contain charts in + // KSC: NOTE: this methodology needs work as a book may + // contain charts in // different sheets containing the same name // TODO: rethink + @Override public ChartHandle getChart(String chartname) throws ChartNotFoundException { return new ChartHandle(mybook.getChart(chartname), this); } @@ -577,6 +605,7 @@ public ChartHandle getChart(String chartname) throws ChartNotFoundException { * * @return ChartHandle[] an array of all Charts in the WorkBook */ + @Override public ChartHandle[] getCharts() { AbstractList cv = mybook.getChartVect(); ChartHandle[] cht = new ChartHandle[cv.size()]; @@ -609,6 +638,7 @@ public ChartHandle getChartById(int id) throws ChartNotFoundException { * * @return NameHandle[] all of the Named ranges in the WorkBook */ + @Override public NameHandle[] getNamedRanges() { Name[] nand = mybook.getNames(); NameHandle[] nands = new NameHandle[nand.length]; @@ -651,6 +681,7 @@ public String getName() { * * @return CellHandle array of all book cells */ + @Override public CellHandle[] getCells() { BiffRec[] allcz = this.mybook.getCells(); CellHandle[] ret = new CellHandle[allcz.length]; @@ -659,15 +690,19 @@ public CellHandle[] getCells() { for (int t = 0; t < ret.length; t++) { ret[t] = new CellHandle(allcz[t], this); if (allcz[t].getOpcode() == XLSConstants.MULBLANK) { - // handle Mulblanks: ref a range of cells; to get correct cell address, - // traverse thru range and set cellhandle ref to correct column + // handle Mulblanks: ref a range of cells; to get correct + // cell address, + // traverse thru range and set cellhandle ref to correct + // column if (allcz[t] == aMul) { c++; } else { aMul = (Mulblank) allcz[t]; c = (short) aMul.getColFirst(); } - ret[t].setBlankRef(c); // for Mulblank use only -sets correct column reference for multiple blank cells + ret[t].setBlankRef(c); // for Mulblank use only -sets correct + // column reference for multiple blank + // cells // ... } } @@ -679,6 +714,7 @@ public CellHandle[] getCells() { * * @return int number of Cells */ + @Override public int getNumCells() { return mybook.getNumCells(); } @@ -701,6 +737,7 @@ public void setShowSheetTabs(boolean show) { * Use the {@link #write} family of methods instead. If you need a * byte array use {@link ByteArrayOutputStream}. */ + @Override @Deprecated public byte[] getBytes() { try { @@ -763,7 +800,8 @@ public void write(String path, boolean ooxml) { try { this.write(new File(path), format); } catch (Exception e) { - throw new WorkBookException("error writing workbook", WorkBookException.WRITING_ERROR, e); + throw new WorkBookException("error writing workbook", + WorkBookException.WRITING_ERROR, e); } } @@ -796,7 +834,8 @@ public void write(OutputStream dest, boolean ooxml) { } this.write(dest, format); } catch (Exception e) { - throw new WorkBookException("error writing workbook", WorkBookException.WRITING_ERROR, e); + throw new WorkBookException("error writing workbook", + WorkBookException.WRITING_ERROR, e); } } @@ -904,8 +943,10 @@ public void write(OutputStream dest, int format) throws IOException { } catch (IOException e) { throw e; } catch (Exception e) { - // TODO: OOXMLAdapter only throws IOException, change its throws - throw new WorkBookException("error writing workbook", WorkBookException.WRITING_ERROR, e); + // TODO: OOXMLAdapter only throws IOException, change its + // throws + throw new WorkBookException("error writing workbook", + WorkBookException.WRITING_ERROR, e); } break; @@ -1003,6 +1044,7 @@ public void setIsExcel2007(boolean isExcel2007) { * bytes streamed * @deprecated Use {@link #write(OutputStream,int)} instead. */ + @Override @Deprecated public StringBuffer writeBytes(OutputStream dest) { StringBuffer sb = this.mybook.getStreamer().writeOut(dest); @@ -1015,8 +1057,10 @@ public StringBuffer writeBytes(OutputStream dest) { * 3 WorkSheets: "Sheet1","Sheet2",and "Sheet3". */ public WorkBookHandle() { - // Xf.DEFAULTIXFE= 15; // reset to default in cases of having previously read - // Excel2007 template which may have set defaultXF differently + // Xf.DEFAULTIXFE= 15; // reset to default in cases of + // having previously read + // Excel2007 template which may have set defaultXF + // differently this.initDefault(); } @@ -1045,14 +1089,16 @@ protected void initDefault() { try { byte[] b = getPrototypeBook(); if (b == null) { - throw new io.starter.formats.XLS.WorkBookException("Unable to load prototype workbook.", + throw new io.starter.formats.XLS.WorkBookException( + "Unable to load prototype workbook.", WorkBookException.LICENSING_FAILED); } ByteBuffer bbf = ByteBuffer.wrap(b); bbf.order(ByteOrder.LITTLE_ENDIAN); myLEOFile = new LEOFile(bbf); } catch (Exception e) { - throw new InvalidFileException("WorkBook could not be instantiated: " + e.toString()); + throw new InvalidFileException( + "WorkBook could not be instantiated: " + e.toString()); } this.initFromLeoFile(myLEOFile); } @@ -1082,7 +1128,8 @@ private void initFromLeoFile(LEOFile leo) { } catch (Exception e) { if (e instanceof io.starter.formats.XLS.WorkBookException) throw (io.starter.formats.XLS.WorkBookException) e; - throw new io.starter.formats.XLS.WorkBookException("ERROR: instantiating WorkBookHandle failed: " + e, + throw new io.starter.formats.XLS.WorkBookException( + "ERROR: instantiating WorkBookHandle failed: " + e, WorkBookException.UNSPECIFIED_INIT_ERROR, e); } } @@ -1098,15 +1145,18 @@ protected void initFromStream(InputStream input) { JFileWriter.writeToFile(input, target); this.initFromFile(target.getAbsoluteFile()); - if (this.myLEOFile != null)// it would be if XLSX or XLSM ... 20090323 KSC + if (this.myLEOFile != null)// it would be if XLSX or XLSM ... + // 20090323 KSC this.myLEOFile.closefb(); - // this.myLEOFile.close(); // close now flushes buffers + storages ... + // this.myLEOFile.close(); // close now flushes buffers + + // storages ... input.close(); File fdel = new File(target.toString()); if (!fdel.delete()) { if (this.DEBUGLEVEL > DEBUG_LOW) - Logger.logWarn("Could not delete tempfile: " + target.toString()); + Logger.logWarn("Could not delete tempfile: " + + target.toString()); } } catch (IOException ex) { Logger.logErr("Initializing WorkBookHandle failed.", ex); @@ -1142,7 +1192,8 @@ protected void initializeFromByteArray(byte[] barray) { this.initFromFile(ftmp); return; } catch (Exception e) { - Logger.logErr("Could not parse XLSX from bytes." + e.toString()); + Logger.logErr("Could not parse XLSX from bytes." + + e.toString()); return; } } @@ -1159,13 +1210,15 @@ protected void initializeFromByteArray(byte[] barray) { throw (Error) e; if (e instanceof WorkBookException) throw (WorkBookException) e; - String errstr = "Instantiating WorkBookHandle failed: " + e.toString(); - throw new io.starter.formats.XLS.WorkBookException(errstr, WorkBookException.UNSPECIFIED_INIT_ERROR); + String errstr = "Instantiating WorkBookHandle failed: " + + e.toString(); + throw new io.starter.formats.XLS.WorkBookException(errstr, + WorkBookException.UNSPECIFIED_INIT_ERROR); } } else { - Logger.logWarn( - "Initializing WorkBookHandle failed: byte array does not contain a supported Excel WorkBook."); - throw new InvalidFileException("byte array does not contian a supported Excel WorkBook."); + Logger.logWarn("Initializing WorkBookHandle failed: byte array does not contain a supported Excel WorkBook."); + throw new InvalidFileException( + "byte array does not contian a supported Excel WorkBook."); } } @@ -1181,8 +1234,10 @@ protected void initializeFromByteArray(byte[] barray) { */ public WorkBookHandle(URL url) { /* - * OK, both this method and the (inputstream) constructor set a temp file, is - * this not possible to do without hitting the disk? TODO: look into fix + * OK, both this method and the (inputstream) constructor + * set a temp file, is + * this not possible to do without hitting the disk? TODO: + * look into fix */ this(getFileFromURL(url)); } @@ -1211,7 +1266,8 @@ public WorkBookHandle(String filePath, int debug) { this.setDebugLevel(debug); File f = new File(filePath); this.initFromFile(f); - this.file = f; // XXX KSC: Save for potential re-input of pass-through ooxml files + this.file = f; // XXX KSC: Save for potential re-input of pass-through + // ooxml files } @@ -1245,7 +1301,8 @@ private boolean initXLSX(String fname) { if (plist != null) myfactory.register(plist); // register progress notifier try { - // iterate sheets,inputting cell values, named ranges and formula strings + // iterate sheets,inputting cell values, named ranges and + // formula strings OOXMLReader oe = new OOXMLReader(); WorkBookHandle bk = new WorkBookHandle(); bk.removeAllWorkSheets(); @@ -1255,7 +1312,8 @@ private boolean initXLSX(String fname) { this.sheethandles = bk.sheethandles; this.mybook = bk.mybook; } catch (Exception e) { - throw new WorkBookException("WorkBookHandle OOXML Read failed: " + e.toString(), + throw new WorkBookException( + "WorkBookHandle OOXML Read failed: " + e.toString(), WorkBookException.UNSPECIFIED_INIT_ERROR, e); } @@ -1285,12 +1343,14 @@ protected void initFromFile(File fx) { fincheck.close(); } catch (FileNotFoundException e) { - Logger.logErr("WorkBookHandle: Cannot open file " + fname + ": " + e); + Logger.logErr("WorkBookHandle: Cannot open file " + fname + ": " + + e); } catch (Exception e1) { Logger.logErr("Invalid XLSX/OOXML File."); } this.name = fname; // 20081231 KSC: set here - if (finch.toUpperCase().startsWith("PK")) { // it's a zip file... give XLSX parsing a shot + if (finch.toUpperCase().startsWith("PK")) { // it's a zip file... give + // XLSX parsing a shot if (this.file != null) OOXMLAdapter.refreshPassThroughFiles(this); if (initXLSX(fname)) @@ -1309,7 +1369,8 @@ protected void initFromFile(File fx) { sheet.readCSV(new BufferedReader(new FileReader(fx))); return; } catch (Exception e) { - throw new WorkBookException("Error encountered importing CSV: " + e.toString(), + throw new WorkBookException( + "Error encountered importing CSV: " + e.toString(), WorkBookException.ILLEGAL_INIT_ERROR); } } else { @@ -1321,9 +1382,10 @@ protected void initFromFile(File fx) { this.initFromLeoFile(myLEOFile); } else { // total failure to load - Logger.logErr( - "Initializing WorkBookHandle failed: " + fname + " does not contain a supported Excel WorkBook."); - throw new InvalidFileException(fname + " does not contian a supported Excel WorkBook."); + Logger.logErr("Initializing WorkBookHandle failed: " + fname + + " does not contain a supported Excel WorkBook."); + throw new InvalidFileException( + fname + " does not contian a supported Excel WorkBook."); } } @@ -1342,7 +1404,8 @@ public WorkBookHandle(ProgressListener pn) { bbf.order(ByteOrder.LITTLE_ENDIAN); myLEOFile = new LEOFile(bbf); } catch (Exception e) { - throw new InvalidFileException("WorkBook could not be instantiated: " + e.toString()); + throw new InvalidFileException( + "WorkBook could not be instantiated: " + e.toString()); } this.initFromLeoFile(myLEOFile); } @@ -1381,7 +1444,8 @@ protected synchronized void initBytes(BlockByteReader blockByteReader) { myfactory.register(plist); // register progress notifier myfactory.setDebugLevel(this.DEBUGLEVEL); - mybook = (io.starter.formats.XLS.WorkBook) myfactory.getWorkBook(blockByteReader, myLEOFile); + mybook = (io.starter.formats.XLS.WorkBook) myfactory + .getWorkBook(blockByteReader, myLEOFile); if (dump_input != null) { try { @@ -1401,7 +1465,8 @@ protected synchronized void initBytes(BlockByteReader blockByteReader) { void postLoad() { initHlinks(); initMerges(); - mybook.initializeNames(); // must initialize name expressions AFTER loading sheet records + mybook.initializeNames(); // must initialize name expressions AFTER + // loading sheet records mybook.mergeMSODrawingRecords(); mybook.initializeIndirectFormulas(); initPivotCache(); // if any @@ -1442,6 +1507,7 @@ void initPivotCache() { /** * Closes the WorkBook and releases resources. */ + @Override public void close() { try { if (myLEOFile != null) @@ -1449,7 +1515,8 @@ public void close() { myLEOFile = null; } catch (Exception e) { if (DEBUGLEVEL > 3) - Logger.logWarn("Closing Document: " + toString() + " failed: " + e.toString()); + Logger.logWarn("Closing Document: " + toString() + " failed: " + + e.toString()); } if (mybook != null) mybook.close(); // clear out object refs to release memory @@ -1475,12 +1542,14 @@ public void reset() { * * @return WorkSheetHandle[] Array of all WorkSheets in WorkBook */ + @Override public WorkSheetHandle[] getWorkSheets() { try { if (myfactory != null) { int numsheets = mybook.getNumWorkSheets(); if (numsheets == 0) - throw new WorkSheetNotFoundException("WorkBook has No Sheets."); + throw new WorkSheetNotFoundException( + "WorkBook has No Sheets."); WorkSheetHandle[] sheets = new WorkSheetHandle[numsheets]; for (int i = 0; i < numsheets; i++) { Boundsheet bs = mybook.getWorkSheetByNumber(i); @@ -1507,6 +1576,7 @@ public WorkSheetHandle[] getWorkSheets() { * @exception WorkSheetNotFoundException * if the specified WorkSheet is not found in the WorkBook. */ + @Override public WorkSheetHandle getWorkSheet(int sheetnum) throws WorkSheetNotFoundException { Boundsheet st = mybook.getWorkSheetByNumber(sheetnum); if (sheethandles.get(st.getSheetName()) != null) @@ -1529,6 +1599,7 @@ public WorkSheetHandle getWorkSheet(int sheetnum) throws WorkSheetNotFoundExcept * @exception WorkSheetNotFoundException * if the specified WorkSheet is not found in the WorkBook. */ + @Override public WorkSheetHandle getWorkSheet(String handstr) throws WorkSheetNotFoundException { if (sheethandles.get(handstr) != null) { if (mybook.getWorkSheetByName(handstr) != null) @@ -1547,7 +1618,8 @@ public WorkSheetHandle getWorkSheet(String handstr) throws WorkSheetNotFoundExce throw new WorkSheetNotFoundException(handstr); } } - throw new WorkSheetNotFoundException("Cannot find WorkSheet " + handstr); + throw new WorkSheetNotFoundException( + "Cannot find WorkSheet " + handstr); } /** @@ -1568,6 +1640,7 @@ public WorkSheetHandle getActiveSheet() throws WorkSheetNotFoundException { * notice in new versions of OpenXLS. * */ + @Override public io.starter.formats.XLS.WorkBook getWorkBook() { return this.mybook; } @@ -1604,6 +1677,7 @@ public io.starter.formats.XLS.WorkBook getWorkBook() { * @param int * String Encoding Mode */ + @Override public void setStringEncodingMode(int mode) { mybook.setStringEncodingMode(mode); } @@ -1643,6 +1717,7 @@ public void setStringEncodingMode(int mode) { * @param int * Duplicate String Handling Mode */ + @Override public void setDupeStringMode(int mode) { mybook.setDupeStringMode(mode); } @@ -1653,8 +1728,8 @@ public void setDupeStringMode(int mode) { * @param chartname * @param sheetname */ - public void copyChartToSheet(String chartname, String sheetname) - throws ChartNotFoundException, WorkSheetNotFoundException { + @Override + public void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException { mybook.copyChartToSheet(chartname, sheetname); } @@ -1664,8 +1739,8 @@ public void copyChartToSheet(String chartname, String sheetname) * @param chart * @param sheet */ - public void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) - throws ChartNotFoundException, WorkSheetNotFoundException { + @Override + public void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) throws ChartNotFoundException, WorkSheetNotFoundException { mybook.copyChartToSheet(chart.getTitle(), sheet.getSheetName()); } @@ -1679,16 +1754,18 @@ public void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) * the Name of the new (destination) worksheet; * @return the new WorkSheetHandle */ - public WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName) - throws WorkSheetNotFoundException { + @Override + public WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName) throws WorkSheetNotFoundException { try { mybook.copyWorkSheet(SourceSheetName, NewSheetName); } catch (Exception e) { - throw new WorkBookException("Failed to copy WorkSheet: " + SourceSheetName + ": " + e.toString(), + throw new WorkBookException("Failed to copy WorkSheet: " + + SourceSheetName + ": " + e.toString(), WorkBookException.RUNTIME_ERROR); } mybook.getRefTracker().clearPtgLocationCaches(NewSheetName); - // update the merged cells (requires a WBH, that's why it's here) + // update the merged cells (requires a WBH, that's why it's + // here) WorkSheetHandle wsh = this.getWorkSheet(NewSheetName); if (wsh != null) { List mc = wsh.getMysheet().getMergedCellsRecs(); @@ -1699,8 +1776,10 @@ public WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName } // now conditional formats /* - * mc = wsh.getMysheet().getConditionalFormats(); for (int i=0;i 0 && - * sourceBook.getWorkBook().msodg!=null) { - * mybook.setMsodrawinggroup(sourceBook.getWorkBook().msodg); - * mybook.msodg.initNewMsodrawingGroup(); // generate and add required records - * for drawing records } WorkSheetHandle wsh = this.getWorkSheet(destSheetName); - * // 20080303 KSC: added for merged cells (stolen from addSheetFromWorkBook // - * update the merged cells (requires a WBH, that's why it's here) if (wsh!=null) - * { List mc = wsh.getMysheet().getMergedCellsRecs(); for (int - * i=0;i chts = sourceSheet.getSheet().getCharts(); for (int i = 0; i < chts.size(); i++) { Chart cxi = (Chart) chts.get(i); @@ -1972,8 +2002,10 @@ public WorkSheetHandle addWorkSheet(WorkSheetHandle sourceSheet, String NewSheet } byte[] bao = sourceSheet.getSerialBytes(); try { - mybook.addBoundsheet(bao, sourceSheet.getSheetName(), NewSheetName, - StringTool.stripPath(sourceSheet.getWorkBook().getName()), true); + mybook.addBoundsheet(bao, sourceSheet + .getSheetName(), NewSheetName, StringTool + .stripPath(sourceSheet.getWorkBook() + .getName()), true); WorkSheetHandle wsh = this.getWorkSheet(NewSheetName); if (wsh != null) { List mc = wsh.getMysheet().getMergedCellsRecs(); @@ -1986,7 +2018,9 @@ public WorkSheetHandle addWorkSheet(WorkSheetHandle sourceSheet, String NewSheet return wsh; } catch (Exception e) { - throw new WorkBookException("Failed to copy WorkSheet: " + e.toString(), WorkBookException.RUNTIME_ERROR); + throw new WorkBookException( + "Failed to copy WorkSheet: " + e.toString(), + WorkBookException.RUNTIME_ERROR); } } @@ -2017,14 +2051,18 @@ public ChartHandle createChart(String name, WorkSheetHandle wsh) { // this is a sheetless chart - TODO: } /* - * a chart needs a supbook, externsheet, & MSO object in the book stream. I - * think this is due to the fact that the referenced series are usually stored - * in the fashon 'Sheet1!A4:B6' The sheet1 reference requires a supbook, though + * a chart needs a supbook, externsheet, & MSO object in the + * book stream. I + * think this is due to the fact that the referenced series + * are usually stored + * in the fashon 'Sheet1!A4:B6' The sheet1 reference + * requires a supbook, though * the reference is internal. */ try { - ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(getPrototypeChart())); + ObjectInputStream ois = new ObjectInputStream( + new ByteArrayInputStream(getPrototypeChart())); Chart newchart = (Chart) ois.readObject(); newchart.setWorkBook(this.getWorkBook()); if (this.getIsExcel2007()) @@ -2032,7 +2070,8 @@ public ChartHandle createChart(String name, WorkSheetHandle wsh) { mybook.addPreChart(); mybook.addChart(newchart, name, wsh.getSheet()); /* - * add font recs if nec: for the default chart: default chart text fonts are # 5 + * add font recs if nec: for the default chart: default + * chart text fonts are # 5 * & 6 title # 7 axis # 8 */ ChartHandle bs = new ChartHandle(newchart, this); @@ -2043,19 +2082,23 @@ public ChartHandle createChart(String name, WorkSheetHandle wsh) { nfonts++; } Font f = mybook.getFont(8); // axis title font - if (f.toString().equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { - // it's default text font -- change to default axis title font + if (f.toString() + .equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { + // it's default text font -- change to default axis title + // font f = new Font("Arial", Font.BOLD, 240); bs.setAxisFont(f); } f = mybook.getFont(7); // chart title font - if (f.toString().equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { + if (f.toString() + .equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { // it's default text font -- change to default title font f = new Font("Arial", Font.BOLD, 360); bs.setTitleFont(f); } bs.removeSeries(0); // remove the "dummied" series - bs.setAxisTitle(ChartHandle.XAXIS, null); // remove default axis titles, if any + bs.setAxisTitle(ChartHandle.XAXIS, null); // remove default axis + // titles, if any bs.setAxisTitle(ChartHandle.YAXIS, null); // "" return bs; } catch (Exception e) { @@ -2073,7 +2116,8 @@ public void deleteChart(String chartname, WorkSheetHandle wsh) throws ChartNotFo try { mybook.deleteChart(chartname, wsh.getSheet()); } catch (ChartNotFoundException e) { - throw new ChartNotFoundException("Removing Chart: " + chartname + " failed: " + e); + throw new ChartNotFoundException( + "Removing Chart: " + chartname + " failed: " + e); } catch (Exception e) { Logger.logErr("Removing Chart: " + chartname + " failed: " + e); } @@ -2120,18 +2164,22 @@ public WorkSheetHandle createWorkSheet(String name, int sheetpos) { * the name of the newly created worksheet * @return the new WorkSheetHandle */ + @Override public WorkSheetHandle createWorkSheet(String name) { try { this.getWorkSheet(name); - throw new WorkBookException("Attempting to add worksheet with duplicate name. " + name - + " already exists in " + this.toString(), WorkBookException.RUNTIME_ERROR); + throw new WorkBookException( + "Attempting to add worksheet with duplicate name. " + name + + " already exists in " + this.toString(), + WorkBookException.RUNTIME_ERROR); } catch (WorkSheetNotFoundException ex) { ; // good! } Boundsheet bo = null; try { - ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(getPrototypeSheet())); + ObjectInputStream ois = new ObjectInputStream( + new ByteArrayInputStream(getPrototypeSheet())); bo = (Boundsheet) ois.readObject(); mybook.addBoundsheet(bo, null, name, null, false); try { @@ -2155,6 +2203,7 @@ public WorkSheetHandle createWorkSheet(String name) { /** * Returns an array of all FormatHandles in the workbook */ + @Override public FormatHandle[] getFormats() { List l = this.mybook.getXfrecs(); FormatHandle[] formats = new FormatHandle[l.size()]; @@ -2162,8 +2211,10 @@ public FormatHandle[] getFormats() { int i = 0; while (its.hasNext()) { Xf x = (Xf) its.next(); - // passing (this) with the format handle breaks the relationship to the font. - // if you need to pass it in we will have to handle it differently + // passing (this) with the format handle breaks the + // relationship to the font. + // if you need to pass it in we will have to handle it + // differently try { formats[i] = new FormatHandle(); formats[i].setWorkBook(this.getWorkBook()); @@ -2189,9 +2240,11 @@ public FormatHandle[] getFormats() { public FormatHandle[] getConditionalFormats() { // the idea is to create a fake IXFE for use by // sheetster to find formats - // int cfxe = this.getWorkBook().getNumFormats() + 50000; // there would have to + // int cfxe = this.getWorkBook().getNumFormats() + 50000; // + // there would have to // be 50k styles on the sheet to conflict here.... - // int cfxe = this.getWorkBook().getNumXfs() + 50000; // there would have to be + // int cfxe = this.getWorkBook().getNumXfs() + 50000; // + // there would have to be // 50k styles on the sheet to conflict here.... List retl = new Vector(); @@ -2232,4 +2285,14 @@ public FormatHandle[] getConditionalFormats() { public static void setFormulaRecursionLevels(int recursion_allowed) { RECURSION_LEVELS_ALLOWED = recursion_allowed; } + + public String getWorkingDirectory() { + // TODO Auto-generated method stub + return null; + } + + public void initSharedFormulas() { + // TODO Auto-generated method stub + + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/LEOFile.java b/src/main/java/io/starter/formats/LEO/LEOFile.java index 38250c1..47fbe20 100644 --- a/src/main/java/io/starter/formats/LEO/LEOFile.java +++ b/src/main/java/io/starter/formats/LEO/LEOFile.java @@ -5,32 +5,53 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.LEO; -import java.util.*; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; import io.starter.formats.XLS.WorkBookException; -import io.starter.toolkit.*; - -import java.nio.*; - - +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.CompatibleVector; +import io.starter.toolkit.JFileWriter; +import io.starter.toolkit.Logger; +import io.starter.toolkit.ResourceLoader; +import io.starter.toolkit.TempFileManager; /** LEOFile is an archive format compatible with other popular archive formats such as OLE. @@ -39,85 +60,108 @@ */ -public class LEOFile implements Serializable{ +public class LEOFile implements Serializable { - /** + /** * serialVersionUID */ - private static final long serialVersionUID = 2760792940329331096L; - public final static int MAXDIFATLEN = 109; // maximum DIFATLEN = 109; if more sectors are needed goes into extraDIFAT - public final static int IDXBLOCKSIZE= 128; // number of indexes that can be stored in 1 block - public final static boolean DEBUG = false; - public int DEBUGLEVEL =0; - public static int actualOutput= 0; - private List bigBlocks; - private boolean readok = false; - private LEOHeader header =null; - private StorageTable directories; - private FileBuffer fb = null; - String fileName = "New Spreadsheet"; - byte[] encryptionStorageOverage = null; - boolean encryptedXLSX = false; - - // TODO: fix temp file issues and implement shutdown cleanup -- currently no way to delete -jm - - public LEOHeader getHeader() { return header; } + private static final long serialVersionUID = 2760792940329331096L; + public final static int MAXDIFATLEN = 109; // maximum + // DIFATLEN + // = + // 109; + // if + // more + // sectors + // are + // needed + // goes + // into + // extraDIFAT + public final static int IDXBLOCKSIZE = 128; // number + // of + // indexes + // that + // can + // be + // stored + // in + // 1 + // block + public final static boolean DEBUG = false; + public int DEBUGLEVEL = 0; + public static int actualOutput = 0; + private List bigBlocks; + private boolean readok = false; + private LEOHeader header = null; + private StorageTable directories; + private FileBuffer fb = null; + String fileName = "New Spreadsheet"; + byte[] encryptionStorageOverage = null; + boolean encryptedXLSX = false; + + // TODO: fix temp file issues and implement shutdown cleanup + // -- currently no way to delete -jm + + public LEOHeader getHeader() { + return header; + } + /** Close the underlying IO streams for this LEOFile. * * Also deletes the temp file. * * @throws Exception */ - public void close() - throws IOException{ - if(fb!=null) - fb.close(); - // KSC: close out other object refs - fb= null; - //header.getBytes().clear(); - header= null;//new LEOHeader(); - if (directories!=null) { - directories.close(); - directories= null; - } - if (bigBlocks!=null) { - for (int i= 0; i < bigBlocks.size(); i++) { - BlockImpl b= (BlockImpl) bigBlocks.get(i); - b.close(); - } - bigBlocks.clear(); - } - bigBlocks= null; -// FAT= null; + public void close() throws IOException { + if (fb != null) + fb.close(); + // KSC: close out other object refs + fb = null; + // header.getBytes().clear(); + header = null;// new LEOHeader(); + if (directories != null) { + directories.close(); + directories = null; + } + if (bigBlocks != null) { + for (int i = 0; i < bigBlocks.size(); i++) { + BlockImpl b = (BlockImpl) bigBlocks.get(i); + b.close(); + } + bigBlocks.clear(); + } + bigBlocks = null; + // FAT= null; } - + /** * just closes the filebuffer withut clearing out buffers and storage tables */ - public void closefb() throws IOException{ - if(fb!=null) - fb.close(); - fb= null; + public void closefb() throws IOException { + if (fb != null) + fb.close(); + fb = null; + } + + public void shutdown() { + try { + this.close(); + } catch (Exception e) { + if (DEBUGLEVEL > 0) + Logger.logWarn("could not close workbook cleanly." + e); + } } - - public void shutdown() { - try{ - this.close(); - }catch(Exception e) { - if(DEBUGLEVEL > 0)Logger.logWarn("could not close workbook cleanly." + e); - } - } - /** a new LEO file containing LEO archive entries @param String a file path containing a valid LEOfile */ public LEOFile(String fname) { - if(fname.indexOf(".ser")>-1) { - this.initFromPrototype(fname); - return; - } + if (fname.indexOf(".ser") > -1) { + this.initFromPrototype(fname); + return; + } this.fileName = fname; fb = LEOFile.readFile(fname); this.initWrapper(fb.getBuffer()); @@ -131,46 +175,50 @@ public LEOFile(String fname) { * @param fname */ private void initFromPrototype(String fname) { - try{ - byte[] b = ResourceLoader.getBytesFromJar(fname); - if (b == null) {throw new io.starter.formats.XLS.WorkBookException( - "Required Class files not on the CLASSPATH. Check location of .jar file and/or jarloc System property.", - WorkBookException.LICENSING_FAILED); - } - ByteBuffer bbf = ByteBuffer.wrap(b); - bbf.order(ByteOrder.LITTLE_ENDIAN); - this.initWrapper(bbf); - }catch(Exception e) { - throw new InvalidFileException("WorkBook could not be instantiated: "+ e.toString()); - } - } - - /** Instantiate a Leo file from an encrypted document. We currently have a hack - * in place for encrypted documents due to their having truncated bigblocks that our - * interface does not correctly handle. - * - @param a file containing a valid LEOfile (XLS BIFF8) - @param whether to use a temp file - @param if the file is encrypted xlsx format - */ - public LEOFile(File fpath, boolean usetempfile, boolean encryptedXLSX) { - this.encryptedXLSX = encryptedXLSX; - this.fileName = fpath.getAbsolutePath(); - fb = LEOFile.readFile(fpath, usetempfile); - this.initWrapper(fb.getBuffer()); - } - - /** Checks whether the given byte array starts with the LEO magic number. - */ - public static boolean checkIsLEO (byte[] data, int count) { - if (count < LEOHeader.majick.length) return false; - for (int idx = 0; idx < LEOHeader.majick.length; idx++) - if (data[ idx ] != LEOHeader.majick[ idx ]) return false; - return true; - } - - /** a new LEO file containing LEO archive entries - * + try { + byte[] b = ResourceLoader.getBytesFromJar(fname); + if (b == null) { + throw new io.starter.formats.XLS.WorkBookException( + "Required Class files not on the CLASSPATH. Check location of .jar file and/or jarloc System property.", + WorkBookException.LICENSING_FAILED); + } + ByteBuffer bbf = ByteBuffer.wrap(b); + bbf.order(ByteOrder.LITTLE_ENDIAN); + this.initWrapper(bbf); + } catch (Exception e) { + throw new InvalidFileException( + "WorkBook could not be instantiated: " + e.toString()); + } + } + + /** Instantiate a Leo file from an encrypted document. We currently have a hack + * in place for encrypted documents due to their having truncated bigblocks that our + * interface does not correctly handle. + * + @param a file containing a valid LEOfile (XLS BIFF8) + @param whether to use a temp file + @param if the file is encrypted xlsx format + */ + public LEOFile(File fpath, boolean usetempfile, boolean encryptedXLSX) { + this.encryptedXLSX = encryptedXLSX; + this.fileName = fpath.getAbsolutePath(); + fb = LEOFile.readFile(fpath, usetempfile); + this.initWrapper(fb.getBuffer()); + } + + /** Checks whether the given byte array starts with the LEO magic number. + */ + public static boolean checkIsLEO(byte[] data, int count) { + if (count < LEOHeader.majick.length) + return false; + for (int idx = 0; idx < LEOHeader.majick.length; idx++) + if (data[idx] != LEOHeader.majick[idx]) + return false; + return true; + } + + /** a new LEO file containing LEO archive entries + * @param a file containing a valid LEOfile (XLS BIFF8) @param whether to use a temp file */ @@ -180,60 +228,56 @@ public LEOFile(File fpath, boolean usetempfile) { this.initWrapper(fb.getBuffer()); } - /** a new LEO file containing LEO archive entries + /** a new LEO file containing LEO archive entries @param a file containing a valid LEOfile (XLS BIFF8) */ public LEOFile(File fpath, int DEBUGLEVEL) { this.fileName = fpath.getAbsolutePath(); - this.DEBUGLEVEL= DEBUGLEVEL; + this.DEBUGLEVEL = DEBUGLEVEL; fb = LEOFile.readFile(fpath); this.initWrapper(fb.getBuffer()); } - + /** a new LEO file containing LEO archive entries - @param byte[] a byte array containing a valid LEOfile - */ - public LEOFile(ByteBuffer bytebuff) { - this.initWrapper(bytebuff); - } - - /** - * This is just removing some duplicate code from our constructors - * - * We should add some exception handling in here! - */ - public void initWrapper(ByteBuffer bytebuff) { - int[] FAT = this.init(bytebuff); - if (FAT != null) { - directories.initDirectories(bigBlocks, FAT); -// KSC: TESTING: XLS-97 - if (DEBUG || DEBUGLEVEL > 200) - directories.DEBUG(); - FAT= null; - readok = true; - } else - readok = false; - } - - - - public void clearAfterInit() { - this.bigBlocks.clear(); - } - - - /** - * Reads in an encrypted LEO stream. May be able to remove this and use the standard - * constructor, we shall see. - * - * @param encryptedFile - */ - public void readEncryptedFile(File encryptedFile) { - fb = readFile(encryptedFile); - this.initWrapper(fb.getBuffer()); - } + @param byte[] a byte array containing a valid LEOfile + */ + public LEOFile(ByteBuffer bytebuff) { + this.initWrapper(bytebuff); + } + + /** + * This is just removing some duplicate code from our constructors + * + * We should add some exception handling in here! + */ + public void initWrapper(ByteBuffer bytebuff) { + int[] FAT = this.init(bytebuff); + if (FAT != null) { + directories.initDirectories(bigBlocks, FAT); + // KSC: TESTING: XLS-97 + if (DEBUG || DEBUGLEVEL > 200) + directories.DEBUG(); + FAT = null; + readok = true; + } else + readok = false; + } + + public void clearAfterInit() { + this.bigBlocks.clear(); + } + + /** + * Reads in an encrypted LEO stream. May be able to remove this and use the standard + * constructor, we shall see. + * + * @param encryptedFile + */ + public void readEncryptedFile(File encryptedFile) { + fb = readFile(encryptedFile); + this.initWrapper(fb.getBuffer()); + } - public String getFileName() { return fileName; } @@ -242,48 +286,49 @@ public String getFileName() { */ public boolean hasWorkBook() { Storage book; - try { - book = directories.getDirectoryByName("Workbook"); - } catch (StorageNotFoundException e) { - try { - book = directories.getDirectoryByName("Book"); - } catch (StorageNotFoundException e1) { - return false; - } - } - return true; + try { + book = directories.getDirectoryByName("Workbook"); + } catch (StorageNotFoundException e) { + try { + book = directories.getDirectoryByName("Book"); + } catch (StorageNotFoundException e1) { + return false; + } + } + return true; } /** return whether the LEOFile contains a valid doc */ public boolean hasDoc() { - if( readok ){ - try { - Storage doc = this.directories.getDirectoryByName("WordDocument"); - } catch (StorageNotFoundException e) { - return false; - } + if (readok) { + try { + Storage doc = this.directories + .getDirectoryByName("WordDocument"); + } catch (StorageNotFoundException e) { + return false; + } return true; } - return false; + return false; } - - + /** * return whether the LEOFile contains the _SX_DB_CUR Pivot Table Cache storage (required for Pivot Table) * @return */ public boolean hasPivotCache() { - if( readok ){ - try { - Storage doc = this.directories.getDirectoryByName("_SX_DB_CUR"); - } catch (StorageNotFoundException e) { - return false; - } + if (readok) { + try { + Storage doc = this.directories.getDirectoryByName("_SX_DB_CUR"); + } catch (StorageNotFoundException e) { + return false; + } return true; } - return false; + return false; } + /** * */ @@ -291,28 +336,31 @@ public LEOFile() { super(); } - /** * Create a LEOFile from an input stream. Unfortunately because of our byte backer * we need to write to a temporary file in order to do this * * @param stream */ - public LEOFile(InputStream stream) throws IOException{ - File target = TempFileManager.createTempFile("ExtenXLS_temp", ".leo"); - JFileWriter.writeToFile(stream,target); - - this.fileName = target.getAbsolutePath(); - fb = LEOFile.readFile(target); - this.initWrapper(fb.getBuffer()); - target.deleteOnExit(); - target.delete(); - } - - /** calculate number of FAT blocks necessary to describe compound file + public LEOFile(InputStream stream) throws IOException { + File target = TempFileManager.createTempFile("ExtenXLS_temp", ".leo"); + JFileWriter.writeToFile(stream, target); + + this.fileName = target.getAbsolutePath(); + fb = LEOFile.readFile(target); + this.initWrapper(fb.getBuffer()); + target.deleteOnExit(); + target.delete(); + } + + public LEOFile(File fx) { + // TODO Auto-generated constructor stub + } + + /** calculate number of FAT blocks necessary to describe compound file */ - final static int getNumFATSectors(int storageTotal) { - int nFAT = storageTotal * 4 ; + final static int getNumFATSectors(int storageTotal) { + int nFAT = storageTotal * 4; nFAT /= BIGBLOCK.SIZE; float realnum = ((float) (storageTotal * 4) / BIGBLOCK.SIZE); if ((realnum - nFAT) > 0 || (nFAT > MAXDIFATLEN)) { @@ -320,7 +368,6 @@ final static int getNumFATSectors(int storageTotal) { } return nFAT; } - /** * Creates all the necessary headers and associated storages/structures that, @@ -371,337 +418,467 @@ final static int getNumFATSectors(int storageTotal) { */ public synchronized List writeBytes(OutputStream out, int workbook_byte_size) { /***** storages to be written - rebuild from saved storages and recreated workbook storage*/ - AbstractList storages = new Vector(); - // header variables - int numFATSectors= 0; //num FAT indexes - int rootstart= 0; // block position where root sector is located - int numExtraDIFATSectors= 0; // num extra sectors/blocks - int extraDIFATStart= -2; // extra sector start (-2 for none) - int numMiniFATSectors= 0; //num short sectors/miniFAT indexes - int miniFATStart= -2; // start block position of short sector container blocks (-2 for none) - int sbidxpos= -2; // start position of short sector idx chain (-2 for none) - int sbsz= 0; // size of short sector container - int[] DIFAT; // holds the sector id chains for the 1st 109 sector index blocks (DIFAT) - int[] extraDIFAT; // holds the sector id chains for any sector index blocks (DIFAT) > 109 - int numblocks = 1; // header is 1st - - // workbook directory - Storage book = null; - boolean isEncrypted = false; - try { - book = directories.getDirectoryByName("Workbook"); - } catch (StorageNotFoundException e3) { - try { - book = directories.getDirectoryByName("EncryptedPackage"); - isEncrypted = !true; - }catch (StorageNotFoundException e1) { - //this is an error state, ideally we would be throwing a (non-runtime) exception? - throw new InvalidFileException("Input LEO file not valid for output"); - } - } - - // root directory - RootStorage rootStore = null; - try { - rootStore = directories.getRootStorage(); - } catch (StorageNotFoundException e2) { - //this is an error state, ideally we would be throwing a (non-runtime) exception? - throw new InvalidFileException("Input LEO file not valid for output"); - } - - // get existing directories + store all except workbook + root (which are rebuilt) + AbstractList storages = new Vector(); + // header variables + int numFATSectors = 0; // num FAT indexes + int rootstart = 0; // block position where root sector is located + int numExtraDIFATSectors = 0; // num extra sectors/blocks + int extraDIFATStart = -2; // extra sector start (-2 for none) + int numMiniFATSectors = 0; // num short sectors/miniFAT indexes + int miniFATStart = -2; // start block position of short sector container + // blocks (-2 for none) + int sbidxpos = -2; // start position of short sector idx chain (-2 for + // none) + int sbsz = 0; // size of short sector container + int[] DIFAT; // holds the sector id chains for the 1st 109 sector index + // blocks (DIFAT) + int[] extraDIFAT; // holds the sector id chains for any sector index + // blocks (DIFAT) > 109 + int numblocks = 1; // header is 1st + + // workbook directory + Storage book = null; + boolean isEncrypted = false; + try { + book = directories.getDirectoryByName("Workbook"); + } catch (StorageNotFoundException e3) { + try { + book = directories.getDirectoryByName("EncryptedPackage"); + isEncrypted = !true; + } catch (StorageNotFoundException e1) { + // this is an error state, ideally we would be throwing a + // (non-runtime) exception? + throw new InvalidFileException( + "Input LEO file not valid for output"); + } + } + + // root directory + RootStorage rootStore = null; + try { + rootStore = directories.getRootStorage(); + } catch (StorageNotFoundException e2) { + // this is an error state, ideally we would be throwing a + // (non-runtime) exception? + throw new InvalidFileException( + "Input LEO file not valid for output"); + } + + // get existing directories + store all except workbook + + // root (which are rebuilt) Enumeration e = directories.getAllDirectories().elements(); while (e.hasMoreElements()) { Storage thisStore = (Storage) e.nextElement(); - if (thisStore!=book && thisStore!=rootStore) { // && thisStore!=encryptionInfo? + if (thisStore != book && thisStore != rootStore) { // && + // thisStore!=encryptionInfo? storages.add(thisStore); - if (thisStore.getBlockType() == Block.SMALL) { // count number of miniStream blocks - thisStore.miniStreamStorage= true; // TODO: investigate why this setting gets lost! - numMiniFATSectors+=thisStore.getBlockVect().size(); + if (thisStore.getBlockType() == Block.SMALL) { // count number + // of miniStream + // blocks + thisStore.miniStreamStorage = true; // TODO: investigate why + // this setting gets + // lost! + numMiniFATSectors += thisStore.getBlockVect().size(); } } - } + } - // if have miniStream sectors, rebuild miniFAT index - // + convert mini blocks to one or more big blocks (=miniFAT container) + // if have miniStream sectors, rebuild miniFAT index + // + convert mini blocks to one or more big blocks (=miniFAT + // container) if (numMiniFATSectors > 0) { - Storage[] sbs= buildMiniFAT(storages, numMiniFATSectors); // returns two non-directory storages: miniStream Storage + miniFAT index + Storage[] sbs = buildMiniFAT(storages, numMiniFATSectors); // returns + // two + // non-directory + // storages: + // miniStream + // Storage + // + + // miniFAT + // index storages.add(sbs[0]); - numMiniFATSectors= sbs[1].getBlockVect().size(); // trap number of miniStream block indexes - storages.add(sbs[1]); + numMiniFATSectors = sbs[1].getBlockVect().size(); // trap number of + // miniStream + // block indexes + storages.add(sbs[1]); } - + // we need to count up the total block array // count number of blocks: // add blocks necessary for root storage - numblocks += Math.ceil(directories.directoryVector.size()/4); // each 4 directories==1 block (512 byte sector) + numblocks += Math.ceil(directories.directoryVector.size() / 4); // each + // 4 + // directories==1 + // block + // (512 + // byte + // sector) // count Storage Blocks (except root and workbook) for (int t = 0; t < storages.size(); t++) { - Storage nstr = (Storage) storages.get(t); // saved stores containt thier original blocks + padding block signifying end of sector/block - if(!nstr.miniStreamStorage) - numblocks += nstr.getSizeInBlocks(); - } + Storage nstr = (Storage) storages.get(t); // saved stores containt + // thier original blocks + // + padding block + // signifying end of + // sector/block + if (!nstr.miniStreamStorage) + numblocks += nstr.getSizeInBlocks(); + } // take current workbook byte size and calculate # bigblocks - book.miniStreamStorage= false; // if had any initially, now it's big blocks - - int workbook_idx_block_size = LEOFile.getSizeInBlocks(workbook_byte_size,BIGBLOCK.SIZE); - workbook_idx_block_size= Math.max(workbook_idx_block_size, getMinBlocks()-1);// ensure minimum # blocks - numblocks += workbook_idx_block_size +1;// to account for end of block sector - if(isEncrypted) { // Encrypted workbooks are already set to correct size - numblocks--; - workbook_idx_block_size--; + book.miniStreamStorage = false; // if had any initially, now it's big + // blocks + + int workbook_idx_block_size = LEOFile + .getSizeInBlocks(workbook_byte_size, BIGBLOCK.SIZE); + workbook_idx_block_size = Math + .max(workbook_idx_block_size, getMinBlocks() - 1);// ensure + // minimum # + // blocks + numblocks += workbook_idx_block_size + 1;// to account for end of block + // sector + if (isEncrypted) { // Encrypted workbooks are already set to correct + // size + numblocks--; + workbook_idx_block_size--; } - // Given amount of blocks to write, calculate id chain necessary to describe - numFATSectors= LEOFile.getNumFATSectors(numblocks); // get # of secids necessary to describe blocks + // Given amount of blocks to write, calculate id chain + // necessary to describe + numFATSectors = LEOFile.getNumFATSectors(numblocks); // get # of secids + // necessary to + // describe + // blocks // now include the FAT itself - // total number of Sectors(blocks)= number of Sectors + FAT chain block(s) + // total number of Sectors(blocks)= number of Sectors + FAT + // chain block(s) numFATSectors = getNumFATSectors(numFATSectors + numblocks); - int[] FAT = new int[IDXBLOCKSIZE*numFATSectors]; - for (int i= 0; i < FAT.length; i++) FAT[i]= -1; // create byte array of -1's 128 bytes - total number of sector ids - - // allocate FAT array index - // important positions that will be recorded in the header or FAT/miniFAT - DIFAT= new int[Math.min(numFATSectors, MAXDIFATLEN)]; // 1 up to the 1st 109 sector index blocks used to describe this file - extraDIFAT= new int[Math.max(numFATSectors-MAXDIFATLEN, 0)]; // if necessary, sector index blocks in excess of 109 - + int[] FAT = new int[IDXBLOCKSIZE * numFATSectors]; + for (int i = 0; i < FAT.length; i++) + FAT[i] = -1; // create byte array of -1's 128 bytes - total number + // of sector ids + + // allocate FAT array index + // important positions that will be recorded in the header + // or FAT/miniFAT + DIFAT = new int[Math.min(numFATSectors, MAXDIFATLEN)]; // 1 up to the + // 1st 109 + // sector index + // blocks used + // to describe + // this file + extraDIFAT = new int[Math.max(numFATSectors - MAXDIFATLEN, 0)]; // if + // necessary, + // sector + // index + // blocks + // in + // excess + // of + // 109 + /************************ now start to lay out block positions of all directories' associated data ************************/ /************************ note: order of directories in storages is order that they will be written out * this order is indexed in the FAT via the block indexes used for each sector * upon return, storages contains all the directories, their associated data, the FAT ... * in the calling method, the workbook records (==associated data) is written first because * the workbook storage is first in the FAT. - Eventually, want to change this so all other storages and the FAT are written first, then - the calling method only needs to write the workbook records. + Eventually, want to change this so all other storages and the FAT are written first, then + the calling method only needs to write the workbook records. **************************************************************************************************************************/ - // first start block index with workbook blocks (they will be written out in ByteStreamer before all other blocks except the header) - int blockpos= 0; // initial sector position + // first start block index with workbook blocks (they will + // be written out in ByteStreamer before all other blocks + // except the header) + int blockpos = 0; // initial sector position book.setStartBlock(blockpos); - for (int t=0; t < workbook_idx_block_size; t++) { - FAT[blockpos++]= blockpos; + for (int t = 0; t < workbook_idx_block_size; t++) { + FAT[blockpos++] = blockpos; } - FAT[blockpos++]= -2; // end of sector flag - if(!isEncrypted) { - // this is a questionable line. It is setting the storage length to that of the padded bigblock. - // causes errors in encrypted files - book.setActualFileSize((workbook_idx_block_size+1)*BIGBLOCK.SIZE); - if (DEBUG) Logger.logInfo("Workbook actual bytes: " + book.getActualFileSize()); + FAT[blockpos++] = -2; // end of sector flag + if (!isEncrypted) { + // this is a questionable line. It is setting the storage + // length to that of the padded bigblock. + // causes errors in encrypted files + book.setActualFileSize((workbook_idx_block_size + 1) + * BIGBLOCK.SIZE); + if (DEBUG) + Logger.logInfo("Workbook actual bytes: " + + book.getActualFileSize()); } - - // now rest of "static" stores (summary, document summary, comp obj ...) - for (int t=0; t < storages.size(); t++) { + + // now rest of "static" stores (summary, document summary, + // comp obj ...) + for (int t = 0; t < storages.size(); t++) { Storage nstr = (Storage) storages.get(t); - if (!nstr.miniStreamStorage) { // miniStream blocks are handled separately - if (nstr.getName().equals("miniStream")) { // miniStream (short sector container) - miniFATStart= blockpos; // this goes in rootstore startblock to denote start of short sector container - sbsz= nstr.getActualFileSize(); // actual size of short sector (miniFAT) container - stored in root storage - } else if (nstr.getName().equals("miniFAT")) { // miniFAT (short sector) index - sbidxpos= blockpos; + if (!nstr.miniStreamStorage) { // miniStream blocks are handled + // separately + if (nstr.getName().equals("miniStream")) { // miniStream (short + // sector container) + miniFATStart = blockpos; // this goes in rootstore + // startblock to denote start of + // short sector container + sbsz = nstr.getActualFileSize(); // actual size of short + // sector (miniFAT) + // container - stored in + // root storage + } else if (nstr.getName().equals("miniFAT")) { // miniFAT (short + // sector) index + sbidxpos = blockpos; } - nstr.setStartBlock(-2); // default, not linked to any blocks -// 20100325 KSC: since original blocks are kept for non-workbook storage, -// and since size is not under our control, keep original size - otherwise can error -// nstr.setActualFileSize(0); // default, 0 bytes - // record index of block position for each block of the storage + nstr.setStartBlock(-2); // default, not linked to any blocks + // 20100325 KSC: since original blocks are kept for + // non-workbook storage, + // and since size is not under our control, keep original + // size - otherwise can error + // nstr.setActualFileSize(0); // default, 0 bytes + // record index of block position for each block of the + // storage Block[] blks = nstr.getBlocks(); - if (blks!=null) { - nstr.setStartBlock(blockpos); // start of block chain - for (int i= 0; i < blks.length-1; i++) { // -1 to trap last block with special end-of-block flag (-2) - FAT[blockpos++]= blockpos; // start of this store's blocks as indexed in block index + if (blks != null) { + nstr.setStartBlock(blockpos); // start of block chain + for (int i = 0; i < blks.length - 1; i++) { // -1 to trap + // last block + // with special + // end-of-block + // flag (-2) + FAT[blockpos++] = blockpos; // start of this store's + // blocks as indexed in + // block index } - FAT[blockpos++]= -2; // end of sector/block flag -// nstr.setActualFileSize((blks.length)*512); // set file size -- see above why we can't do this at this time + FAT[blockpos++] = -2; // end of sector/block flag + // nstr.setActualFileSize((blks.length)*512); // set file + // size -- see above why we can't do this at this time } } } - + // after other storages, add root store: - // must rebuild after info from above as root storage is built from from all other storages - rootStore.setStartBlock(miniFATStart); // root store start points to miniFAT container, or -2 if none - rootStore.setActualFileSize(sbsz); // 0 or miniFAT container size - // handle rootstorage blocks = data of all other directory storages - rootStore.setBytes(directories.rebuildRootStore()); // now rebuild rootstore from all other storages - rootstart= blockpos; - for (int i= 0; i < rootStore.getBlockVect().size()-1; i++) { - FAT[blockpos++]= blockpos; + // must rebuild after info from above as root storage is + // built from from all other storages + rootStore.setStartBlock(miniFATStart); // root store start points to + // miniFAT container, or -2 if + // none + rootStore.setActualFileSize(sbsz); // 0 or miniFAT container size + // handle rootstorage blocks = data of all other directory + // storages + rootStore.setBytes(directories.rebuildRootStore()); // now rebuild + // rootstore from + // all other + // storages + rootstart = blockpos; + for (int i = 0; i < rootStore.getBlockVect().size() - 1; i++) { + FAT[blockpos++] = blockpos; } - FAT[blockpos++]= -2; // end of sector/block flag - // add rootstore to list of blocks writing out - storages.add(rootStore); + FAT[blockpos++] = -2; // end of sector/block flag + // add rootstore to list of blocks writing out + storages.add(rootStore); // mark position of FAT itself within blocks (==DIFAT) // the 1st MAXDIFATLEN DIFAT indexes goes into header - for (int i= 0; i < DIFAT.length; i++) { - DIFAT[i]= blockpos+1; // each pos is decremented in method below, so increment here - FAT[blockpos++]= -3; // mark loc of FAT with special value -3 + for (int i = 0; i < DIFAT.length; i++) { + DIFAT[i] = blockpos + 1; // each pos is decremented in method below, + // so increment here + FAT[blockpos++] = -3; // mark loc of FAT with special value -3 } // if any FAT sectors > MAX, goes into extra blocks - if (numFATSectors > MAXDIFATLEN) { // handle extra blocks - need extra blocks to store the indexes - for (int n= MAXDIFATLEN; n < numFATSectors; n++) { - extraDIFAT[n-MAXDIFATLEN]= blockpos + 1; - FAT[blockpos++]= -3; + if (numFATSectors > MAXDIFATLEN) { // handle extra blocks - need extra + // blocks to store the indexes + for (int n = MAXDIFATLEN; n < numFATSectors; n++) { + extraDIFAT[n - MAXDIFATLEN] = blockpos + 1; + FAT[blockpos++] = -3; } - for (int i= 0; i < (int)Math.ceil((numFATSectors-MAXDIFATLEN)/(IDXBLOCKSIZE*1.0)); i++) - FAT[blockpos++]= -4; // flag for MSAT or XBB + for (int i = 0; i < (int) Math.ceil((numFATSectors - MAXDIFATLEN) + / (IDXBLOCKSIZE * 1.0)); i++) + FAT[blockpos++] = -4; // flag for MSAT or XBB } - // now that all the blocks referenced by storages such as workbook and document summary, - // plus blocks referenced by the root storage, - // transform the blockindex to blocks & input a phantom or special store so can write out + // now that all the blocks referenced by storages such as + // workbook and document summary, + // plus blocks referenced by the root storage, + // transform the blockindex to blocks & input a phantom or + // special store so can write out // have been indexed, store the index blocks themselves Storage idxstore = new Storage(); idxstore.setName("IDXStorage"); - Block[] FATSectors= getIDXBlocks(FAT); // convert each int to 4-bytes in sector/block format --> FAT Sectors - idxstore.setBlocks(FATSectors); //input the FAT Sectors to a non-directory store for later writing - storages.add(idxstore); // output FAT Sectors after all other blocks + Block[] FATSectors = getIDXBlocks(FAT); // convert each int to 4-bytes + // in sector/block format --> + // FAT Sectors + idxstore.setBlocks(FATSectors); // input the FAT Sectors to a + // non-directory store for later writing + storages.add(idxstore); // output FAT Sectors after all other blocks - - if (numFATSectors > MAXDIFATLEN) { - // now build and add the extra sectors necessary to store the FAT - extraDIFATStart= blockpos-(int)Math.ceil((numFATSectors-MAXDIFATLEN)/(IDXBLOCKSIZE*1.0)); - Storage xbbstore= buildExtraDIFAT(extraDIFAT, extraDIFATStart); // create block(s) from the extraDIFAT - numExtraDIFATSectors= xbbstore.getBlockVect().size(); + if (numFATSectors > MAXDIFATLEN) { + // now build and add the extra sectors necessary to store + // the FAT + extraDIFATStart = blockpos - (int) Math + .ceil((numFATSectors - MAXDIFATLEN) / (IDXBLOCKSIZE * 1.0)); + Storage xbbstore = buildExtraDIFAT(extraDIFAT, extraDIFATStart); // create + // block(s) + // from + // the + // extraDIFAT + numExtraDIFATSectors = xbbstore.getBlockVect().size(); storages.add(xbbstore); } - - // now that blocks have been accounted for and indexed and storages and all their blocks - // (except for workbook blocks) are stored, we can build and output the header block + + // now that blocks have been accounted for and indexed and + // storages and all their blocks + // (except for workbook blocks) are stored, we can build and + // output the header block // build header block: - - header= LEOHeader.getPrototype(DIFAT); // input secIdChain to header loc 76 - // start sector of short sector/miniFAT index chain or -2 if n/a - header.setMiniFATStart(sbidxpos); + + header = LEOHeader.getPrototype(DIFAT); // input secIdChain to header + // loc 76 + // start sector of short sector/miniFAT index chain or -2 if + // n/a + header.setMiniFATStart(sbidxpos); // the block # where the storages begin - header.setRootStorageStart(rootstart); - // number of block indexes needed to describe 1st 109 sectors/blocks (= DIFAT) - header.setNumFATSectors(numFATSectors); - // number of block indexes needed to describe short sectors/miniFAT sectors if any + header.setRootStorageStart(rootstart); + // number of block indexes needed to describe 1st 109 + // sectors/blocks (= DIFAT) + header.setNumFATSectors(numFATSectors); + // number of block indexes needed to describe short + // sectors/miniFAT sectors if any header.setNumMiniFATSectors(numMiniFATSectors); - // number of block indexes necessary to describe extra sectors if any + // number of block indexes necessary to describe extra + // sectors if any header.setNumExtraDIFATSectors(numExtraDIFATSectors); // start of extra sector block index or -2 if n/a header.setExtraDIFATStart(extraDIFATStart); - + // setup the header info... if (!header.init()) - throw new RuntimeException("LEO File Header Not Initialized"); // invalid WorkBook File - - -// return the storages + throw new RuntimeException("LEO File Header Not Initialized"); // invalid + // WorkBook + // File + + // return the storages return storages; } - - - + /** * build Extra Storage Sector index (extra DIFAT) * @param difatidx - indexes of blocks > 13952 (= 109 blocks described by DIFAT * 128 indexes in each block) * @return */ private Storage buildExtraDIFAT(int[] difatidx, int xbbpos) { -// handle Extra blocks beyond the 109*128 blocks described by the regular DIFAT (109 limit) - if (difatidx!= null) { - ArrayList outblocks= new ArrayList(); + // handle Extra blocks beyond the 109*128 blocks described + // by the regular DIFAT (109 limit) + if (difatidx != null) { + ArrayList outblocks = new ArrayList(); byte[] xbytes = new byte[difatidx.length * 4]; - // convert the int difatidx to bytes + // convert the int difatidx to bytes for (int i = 0; i < difatidx.length; i++) { byte[] idx = ByteTools.cLongToLEBytes(difatidx[i] - 1); System.arraycopy(idx, 0, xbytes, i * 4, 4); - } + } int counter = 0; for (int i = 0; i < xbytes.length;) { - ByteBuffer bl = BlockFactory.getPrototypeBlock(Block.BIG).getByteBuffer(); + ByteBuffer bl = BlockFactory.getPrototypeBlock(Block.BIG) + .getByteBuffer(); int len = xbytes.length - i; - + // handle eob - if (len > BIGBLOCK.SIZE-4) - len = BIGBLOCK.SIZE-4; + if (len > BIGBLOCK.SIZE - 4) + len = BIGBLOCK.SIZE - 4; bl.position(0); bl.put(xbytes, i, len); - + // handle eob counter++; - if (len == BIGBLOCK.SIZE-4) - bl.putInt(xbbpos+counter); + if (len == BIGBLOCK.SIZE - 4) + bl.putInt(xbbpos + counter); BIGBLOCK xbbBlock = new BIGBLOCK(); xbbBlock.init(bl, 0, 0); outblocks.add(xbbBlock); - - i += BIGBLOCK.SIZE -4; + + i += BIGBLOCK.SIZE - 4; } // create new storage so can be added to end - Storage xbbstore= new Storage(); + Storage xbbstore = new Storage(); xbbstore.setName("XBBStore"); Block[] xblx = new Block[outblocks.size()]; outblocks.toArray(xblx); xbbstore.setBlocks(xblx); return xbbstore; - } + } return null; } - /** - * Gather up all existing smallblocks from the existing storages - * and create contiguous bigblocks from it; since smallblocks are - * only 128 bytes long, 4 smallblocks fit into 1 bigblock - * while building the miniFAT Sectors,also build the miniFAT index - * which corresponds to it - * - * @param storages existing Storages (except workbook and root, which are handled separately) - * @param numsbs total number of miniFAT blocks - * @return Storage[] - two storage containers: 1 contains smallblocks, 2 contains smallblock index - */ + /** + * Gather up all existing smallblocks from the existing storages + * and create contiguous bigblocks from it; since smallblocks are + * only 128 bytes long, 4 smallblocks fit into 1 bigblock + * while building the miniFAT Sectors,also build the miniFAT index + * which corresponds to it + * + * @param storages existing Storages (except workbook and root, which are handled separately) + * @param numsbs total number of miniFAT blocks + * @return Storage[] - two storage containers: 1 contains smallblocks, 2 contains smallblock index + */ private Storage[] buildMiniFAT(List storages, int numsbs) { - // once more, now gathering the sbidx's (miniFAT index) - int[] sbidx= new int[(int) Math.ceil(numsbs/(IDXBLOCKSIZE*1.0))*IDXBLOCKSIZE]; - byte[] smallblocks= new byte[0]; // sum up all short sectors, will be subsequently converted to big blocks - for (int i= 0; i < sbidx.length; i++) sbidx[i]= -1; // init sbidx index - int z= 0; - for (int i= 0; i < storages.size(); i++) { + // once more, now gathering the sbidx's (miniFAT index) + int[] sbidx = new int[(int) Math.ceil(numsbs / (IDXBLOCKSIZE * 1.0)) + * IDXBLOCKSIZE]; + byte[] smallblocks = new byte[0]; // sum up all short sectors, will be + // subsequently converted to big + // blocks + for (int i = 0; i < sbidx.length; i++) + sbidx[i] = -1; // init sbidx index + int z = 0; + for (int i = 0; i < storages.size(); i++) { Storage thisStore = (Storage) storages.get(i); if (thisStore.getBlockType() == Block.SMALL) { - //since these are static blocks we can copy original data - thisStore.setStartBlock(z); // set start position of miniFATs as may have changed + // since these are static blocks we can copy original data + thisStore.setStartBlock(z); // set start position of miniFATs as + // may have changed // keep length as that hasn't changed - for (int j= 0; j BBDIX + Block[] sbIDX = getIDXBlocks(sbidx); // convert each int to 4-bytes in + // sector/block format --> BBDIX miniFAT.setBlocks(sbIDX); return new Storage[] { miniFATContainer, miniFAT }; } - + /** create the idx from the actual locations of the Blocks in the outblock array */ - final static void initSmallBlockIndex(int[] newidx, Block b) { - while (b.hasNext()&& !(b==(Block)b.next())) { + final static void initSmallBlockIndex(int[] newidx, Block b) { + while (b.hasNext() && !(b == (Block) b.next())) { int origps = b.getBlockIndex(); if (origps < 0) { - Logger.logWarn("WARNING: LEOFile Block Not In MINIFAT vector: "+ String.valueOf(b.getOriginalIdx())); + Logger.logWarn("WARNING: LEOFile Block Not In MINIFAT vector: " + + String.valueOf(b.getOriginalIdx())); } else { int newp = ((Block) b.next()).getBlockIndex(); - if (false)Logger.logInfo("INFO: LEOFile Initializing block index: "+ origps+ " val: "+ newp+ " idxlen: "+ newidx.length); + if (false) + Logger.logInfo("INFO: LEOFile Initializing block index: " + + origps + " val: " + newp + " idxlen: " + + newidx.length); newidx[origps] = newp; } b = (Block) b.next(); @@ -730,32 +907,32 @@ final static int initWBBlockIndex(int[] newidx, int sz) { newidx[(start+sz-1)] = -2; // end of storage return start; } -*/ - - + */ + /** returns an empty new FAT which accounts for the size of its own blocks */ - final static int[] getEmptyDIFAT(int totblocks, int numFATSectors) { + final static int[] getEmptyDIFAT(int totblocks, int numFATSectors) { - // allocate space in idx for FAT recs, StorageTable and for final -2 + // allocate space in idx for FAT recs, StorageTable and for + // final -2 int[] bbdi = new int[numFATSectors + totblocks + 1]; for (int x = 0; x < bbdi.length; x++) - bbdi[x] = (byte) - 1; + bbdi[x] = (byte) -1; return bbdi; } /** Create the array of BB Idx blocks */ - final static Block[] getIDXBlocks(int[] bbdidx) { + final static Block[] getIDXBlocks(int[] bbdidx) { // step through and create index recs for all Blocks byte[] b = new byte[bbdidx.length * 4]; int bv = 0; - + // can we wrap t in an nio buffered and read? - - for (int t = 0; t < b.length/* 20100304 KSC: why? - 4*/;) { + + for (int t = 0; t < b.length/* 20100304 KSC: why? - 4 */;) { byte[] bs = ByteTools.cLongToLEBytes(bbdidx[bv++]); // for(int z=0;z<4;z++) b[t++] = bs[0]; @@ -768,28 +945,29 @@ final static Block[] getIDXBlocks(int[] bbdidx) { /** get a handle to a Storage within this LEO file */ - public Storage getStorageByName(String s) throws StorageNotFoundException{ + public Storage getStorageByName(String s) throws StorageNotFoundException { return directories.getDirectoryByName(s); } - - /** get all Directories in this LEO file - */ - public Storage[] getAllDirectories() { - CompatibleVector v = directories.getAllDirectories(); - Storage[] s = new Storage[v.size()]; - s = (Storage[]) v.toArray(s); - return s; - } - - - /** - * return the Directory Array for this LEOFile - *
holds all directories (storages and streams) in correct order - *
Directories in turn can reference streams of data - * @return - */ - public StorageTable getDirectoryArray() { return directories; } - + + /** get all Directories in this LEO file + */ + public Storage[] getAllDirectories() { + CompatibleVector v = directories.getAllDirectories(); + Storage[] s = new Storage[v.size()]; + s = (Storage[]) v.toArray(s); + return s; + } + + /** + * return the Directory Array for this LEOFile + *
holds all directories (storages and streams) in correct order + *
Directories in turn can reference streams of data + * @return + */ + public StorageTable getDirectoryArray() { + return directories; + } + /** Simply setting the byte size on the book storage * private void checkIfSmallBlockOutput(int blen) throws StorageNotFoundException { @@ -814,36 +992,38 @@ private void checkIfSmallBlockOutput(int blen) throws StorageNotFoundException { } else book.setActualFileSize(StorageTable.BIGSTORAGE_SIZE); // set at the minimum } -*/ + */ /** get the bytes of the DOC file. */ public BlockByteReader getDocBlockBytes() { // get the bytes for the WordDocument substream only Storage doc; - try { - doc = directories.getDirectoryByName("WordDocument"); - } catch (StorageNotFoundException e) { - throw new InvalidFileException("InvalidFileException: Not Word '97 or later version. Unsupported file format."); - } + try { + doc = directories.getDirectoryByName("WordDocument"); + } catch (StorageNotFoundException e) { + throw new InvalidFileException( + "InvalidFileException: Not Word '97 or later version. Unsupported file format."); + } return doc.getBlockReader(); } - + /** get the bytes of the XLS file. */ public BlockByteReader getXLSBlockBytes() { // get the bytes for the Workbook substream only Storage book; - try { - book = directories.getDirectoryByName("Workbook"); - } catch (StorageNotFoundException e) { - try { - book = directories.getDirectoryByName("Book"); - } catch (StorageNotFoundException e1) { - Logger.logInfo("Not Excel '97 (BIFF8) or later version. Unsupported file format."); - throw new InvalidFileException("InvalidFileException: Not Excel '97 (BIFF8) or later version. Unsupported file format."); - } - } + try { + book = directories.getDirectoryByName("Workbook"); + } catch (StorageNotFoundException e) { + try { + book = directories.getDirectoryByName("Book"); + } catch (StorageNotFoundException e1) { + Logger.logInfo("Not Excel '97 (BIFF8) or later version. Unsupported file format."); + throw new InvalidFileException( + "InvalidFileException: Not Excel '97 (BIFF8) or later version. Unsupported file format."); + } + } return book.getBlockReader(); } @@ -851,71 +1031,83 @@ public BlockByteReader getXLSBlockBytes() { */ public synchronized int[] init(ByteBuffer bbuf) { int pos = 0; - CompatibleVector FATSectors = new CompatibleVector(); // one or more sectors which hold the FAT (File Allocation Table or indexes into the sectors) - + CompatibleVector FATSectors = new CompatibleVector(); // one or more + // sectors which + // hold the FAT + // (File + // Allocation + // Table or + // indexes into + // the sectors) + bigBlocks = new ArrayList(); int len = bbuf.limit() / BIGBLOCK.SIZE; // get ALL BIGBLOCKS (512 byte chunks of file) - if (DEBUG) - Logger.logInfo("\nINIT: Total Number of bigblocks: " + len); + if (DEBUG) + Logger.logInfo("\nINIT: Total Number of bigblocks: " + len); for (int i = 0; i < len; i++) { BIGBLOCK bbd = new BIGBLOCK(); - bbd.init(bbuf, i, pos); + bbd.init(bbuf, i, pos); pos += BIGBLOCK.SIZE; this.bigBlocks.add(bbd); } - + // Encrypted workbooks can have random overages. - // not ideal, but store this value in LEO and get from the storage if its named 'EncryptedPackage' + // not ideal, but store this value in LEO and get from the + // storage if its named 'EncryptedPackage' int encryptionStorageOverageLen = (bbuf.limit() % BIGBLOCK.SIZE); - if(encryptionStorageOverageLen>0) { - int filepos = len * BIGBLOCK.SIZE; - if (this.encryptedXLSX) { - bbuf.position(filepos); - encryptionStorageOverage = new byte[encryptionStorageOverageLen]; - bbuf.get(encryptionStorageOverage, 0, encryptionStorageOverage.length); - }else { - BIGBLOCK bbd = new BIGBLOCK(); - bbd.init(bbuf, len, pos); // filepos); - pos += encryptionStorageOverageLen; - this.bigBlocks.add(bbd); - } - } + if (encryptionStorageOverageLen > 0) { + int filepos = len * BIGBLOCK.SIZE; + if (this.encryptedXLSX) { + bbuf.position(filepos); + encryptionStorageOverage = new byte[encryptionStorageOverageLen]; + bbuf.get(encryptionStorageOverage, 0, encryptionStorageOverage.length); + } else { + BIGBLOCK bbd = new BIGBLOCK(); + bbd.init(bbuf, len, pos); // filepos); + pos += encryptionStorageOverageLen; + this.bigBlocks.add(bbd); + } + } /***** Read in the file header */ - // header holds directory start sector and FAT/miniFAT/extraFAT or Sector Chain index info + // header holds directory start sector and + // FAT/miniFAT/extraFAT or Sector Chain index info header = new LEOHeader(); // read the LEO file header rec // is a valid WorkBook File? if (!header.init(bbuf)) throw new InvalidFileException( - this.getFileName() + " is not a valid OLE File."); + this.getFileName() + " is not a valid OLE File."); if (DEBUG) { Logger.logInfo("Header: "); Logger.logInfo("numbFATSectors: " + header.getNumFATSectors()); - Logger.logInfo("numMiniFATSectors: " + header.getNumMiniFATSectors()); - Logger.logInfo("numbExtraDIFATSectors: " + header.getNumExtraDIFATSectors()); + Logger.logInfo("numMiniFATSectors: " + + header.getNumMiniFATSectors()); + Logger.logInfo("numbExtraDIFATSectors: " + + header.getNumExtraDIFATSectors()); Logger.logInfo("rootstart: " + header.getRootStartPos()); Logger.logInfo("miniFATStart: " + header.getMiniFATStart()); } BIGBLOCK headerblock = (BIGBLOCK) bigBlocks.get(0); headerblock.setInitialized(true); - + /***** Read in the FAT sectors - the sectors or blocks used for the FAT (==Sector Chain Index or File Allocation Table) */ - FATSectors= getFATSectors(); - - /***** turn the FAT Sectors into the FAT or Sector Index Chain */ + FATSectors = getFATSectors(); + + /***** turn the FAT Sectors into the FAT or Sector Index Chain */ byte[] blx = LEOFile.getBytes(FATSectors); int[] FAT = null; FAT = LEOFile.readFAT(blx); blx = null; // done - - if (DEBUG) Logger.logInfo("FAT:\n" + Arrays.toString(FAT)); - -// if (DEBUG) -// StorageTable.writeitout(FATSectors, "FAT.dat"); - + + if (DEBUG) + Logger.logInfo("FAT:\n" + Arrays.toString(FAT)); + + // if (DEBUG) + // StorageTable.writeitout(FATSectors, "FAT.dat"); + /***** Read the Directory blocks */ directories = new StorageTable(); directories.init(bbuf, header, bigBlocks, FAT); @@ -930,16 +1122,17 @@ public synchronized int[] init(ByteBuffer bbuf) { * @param fpath * @return */ - public final static FileBuffer readFile(File fpath) { - boolean usetempfile = false; - String tmpfu = (String)System.getProperties().get("io.starter.formats.LEO.usetempfile"); - - if(tmpfu!=null) - usetempfile = tmpfu.equalsIgnoreCase("true"); - - return readFile(fpath, usetempfile); + public final static FileBuffer readFile(File fpath) { + boolean usetempfile = false; + String tmpfu = (String) System.getProperties() + .get("io.starter.formats.LEO.usetempfile"); + + if (tmpfu != null) + usetempfile = tmpfu.equalsIgnoreCase("true"); + + return readFile(fpath, usetempfile); } - + /** * create a FileBuffer from a file, use boolean parameter to determine * whether to use a temp file. @@ -948,12 +1141,12 @@ public final static FileBuffer readFile(File fpath) { * @param whether to use a tempfile * @return */ - public final static FileBuffer readFile(File fpath, boolean usetempfile) { - if(usetempfile) + public final static FileBuffer readFile(File fpath, boolean usetempfile) { + if (usetempfile) return FileBuffer.readFileUsingTemp(fpath); return FileBuffer.readFile(fpath); } - + /** read in a WorkBook ByteBuffer from a file path. * from here on out, we're reading pointers to the bytes on disk @@ -963,14 +1156,14 @@ public final static FileBuffer readFile(File fpath, boolean usetempfile) { *
By default, OpenXLS will lock open WorkBook files, to close the file after parsing and work with a temporary file instead, use the following setting:

- System.getProperties().put("io.starter.formats.LEO.usetempfile", "true"); -

- IMPORTANT NOTE: You will need to clean up temp files occasionally in your user directory (temp filenames will begin - with "ExtenXLS_".) -

+ System.getProperties().put("io.starter.formats.LEO.usetempfile", "true"); +

+ IMPORTANT NOTE: You will need to clean up temp files occasionally in your user directory (temp filenames will begin + with "ExtenXLS_".) +

* **/ - public final static FileBuffer readFile(String fpath) { + public final static FileBuffer readFile(String fpath) { return LEOFile.readFile(new File(fpath)); } @@ -989,8 +1182,8 @@ private final static int[] readFAT(byte[] data) { int[] bbs = new int[data.length / 4]; int pos = 0; for (int i = 0; i < data.length;) { - bbs[pos++] = - ByteTools.readInt(data[i++], data[i++], data[i++], data[i++]); + bbs[pos++] = ByteTools + .readInt(data[i++], data[i++], data[i++], data[i++]); } data = null; return bbs; @@ -1001,13 +1194,13 @@ public final static byte[] getBytes(Block[] outblocks) { getBytes(outblocks, out); return out.toByteArray(); } - + public final static OutputStream getByteStream(Block[] outblocks) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - getBytes(outblocks, out); - return out; - } - + ByteArrayOutputStream out = new ByteArrayOutputStream(); + getBytes(outblocks, out); + return out; + } + /** Call this method for each storage, root storage first... * * @@ -1016,10 +1209,11 @@ public final static OutputStream getByteStream(Block[] outblocks) { */ public final static void getBytes(Block[] outblocks, OutputStream out) { List blxt = new ArrayList(); - for(int tx=0;tx= 0) { String ss= new String (rec.getBytesAt(28, slen)); // AnsiUserType= a display name of the linked object or embedded object. - io.starter.OpenXLS.util.Logger.log("\tOLE Object:" + ss); + io.starter.toolkit.Logger.log("\tOLE Object:" + ss); } // AnsiClipboardFormat (variable) -// io.starter.OpenXLS.util.Logger.log("\t" + Arrays.toString(rec.getData())); +// io.starter.toolkit.Logger.log("\t" + Arrays.toString(rec.getData())); } else if (n.startsWith("000")) { // pivot cache if (s.myblocks!=null) { for (int zz= 0; zz < s.myblocks.size(); zz++) if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) - io.starter.OpenXLS.util.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK)s.myblocks.get(zz)).getBytes())); + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK)s.myblocks.get(zz)).getBytes())); else - io.starter.OpenXLS.util.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK)s.myblocks.get(zz)).getBytes())); + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK)s.myblocks.get(zz)).getBytes())); } BlockByteReader bytes= s.getBlockReader(); int len= bytes.getLength(); @@ -592,16 +592,16 @@ public void DEBUG() { rec.setOffset(z); rec.setLength((short) reclen); rec.init(); - io.starter.OpenXLS.util.Logger.log("\t\t" + rec.toString()); + io.starter.toolkit.Logger.log("\t\t" + rec.toString()); z+=reclen+4; } } else { if (s.myblocks!=null) { for (int zz= 0; zz < s.myblocks.size(); zz++) if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) - io.starter.OpenXLS.util.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK)s.myblocks.get(zz)).getBytes())); + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK)s.myblocks.get(zz)).getBytes())); else - io.starter.OpenXLS.util.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK)s.myblocks.get(zz)).getBytes())); + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK)s.myblocks.get(zz)).getBytes())); } } } diff --git a/src/main/java/io/starter/formats/XLS/Boundsheet.java b/src/main/java/io/starter/formats/XLS/Boundsheet.java index 98a5c7e..34d0b46 100644 --- a/src/main/java/io/starter/formats/XLS/Boundsheet.java +++ b/src/main/java/io/starter/formats/XLS/Boundsheet.java @@ -5,25 +5,54 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS; -import java.io.*; -import java.util.*; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.Stack; +import java.util.TreeMap; +import java.util.Vector; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -31,17 +60,34 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; -import io.starter.OpenXLS.*; -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.*; -import io.starter.formats.XLS.charts.*; -import io.starter.formats.cellformat.CellFormatFactory; +import io.starter.OpenXLS.Cell; +import io.starter.OpenXLS.CellRange; +import io.starter.OpenXLS.ChartHandle; +import io.starter.OpenXLS.ColHandle; +import io.starter.OpenXLS.CommentHandle; +import io.starter.OpenXLS.ExcelTools; +import io.starter.OpenXLS.FormatHandle; +import io.starter.OpenXLS.ImageHandle; +import io.starter.OpenXLS.PrinterSettingsHandle; +import io.starter.OpenXLS.WorkBookHandle; +import io.starter.OpenXLS.WorkSheetHandle; import io.starter.formats.OOXML.OneCellAnchor; import io.starter.formats.OOXML.SheetPr; import io.starter.formats.OOXML.SheetView; import io.starter.formats.OOXML.Text; import io.starter.formats.OOXML.TwoCellAnchor; - +import io.starter.formats.XLS.charts.Chart; +import io.starter.formats.XLS.charts.Fontx; +import io.starter.formats.XLS.charts.GenericChartObject; +import io.starter.formats.XLS.formulas.FormulaParser; +import io.starter.formats.XLS.formulas.Ptg; +import io.starter.formats.XLS.formulas.PtgRef; +import io.starter.formats.cellformat.CellFormatFactory; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.CompatibleVector; +import io.starter.toolkit.FastAddVector; +import io.starter.toolkit.Logger; +import io.starter.toolkit.StringTool; /** Boundsheet: WorkSheet Information 0x85
@@ -92,123 +138,186 @@ public final class Boundsheet extends XLSRecord implements Sheet { /** * */ - private static final long serialVersionUID = 8977216410574107840L; - private Bof mybof = null; - private Eof myeof = null; - private String sheetname = ""; - private String sheetHash = ""; - private Map rows = new LinkedHashMap(); - - private SortedMap cellsByRow = - new TreeMap( - new CellAddressible.RowMajorComparator() ); - - private SortedMap cellsByCol = - new TreeMap( - new CellAddressible.ColumnMajorComparator() ); - - private Map arrFormulaLocs = new HashMap(); // use for trapping array formula refs to original cell reference [OOXML Array Formulas] - protected AbstractList arrayformulas = new ArrayList(); // trap array formulas that span one or more cells - private SortedMap colinfos = - new TreeMap( new ColumnRange.Comparator() ); - private AbstractList SheetRecs = new ArrayList(); - private AbstractList localrecs; + private static final long serialVersionUID = 8977216410574107840L; + private Bof mybof = null; + private Eof myeof = null; + private String sheetname = ""; + private String sheetHash = ""; + private Map rows = new LinkedHashMap(); + + private SortedMap cellsByRow = new TreeMap( + new CellAddressible.RowMajorComparator()); + + private SortedMap cellsByCol = new TreeMap( + new CellAddressible.ColumnMajorComparator()); + + private Map arrFormulaLocs = new HashMap(); // use + // for + // trapping + // array + // formula + // refs + // to + // original + // cell + // reference + // [OOXML + // Array + // Formulas] + protected AbstractList arrayformulas = new ArrayList(); // trap + // array + // formulas + // that + // span + // one + // or + // more + // cells + private SortedMap colinfos = new TreeMap( + new ColumnRange.Comparator()); + private AbstractList SheetRecs = new ArrayList(); + private AbstractList localrecs; /** Records containting various bits of print setup. */ - private List printRecs; - - // These records are for boundsheet transferral to a new book. - private List transferXfs = new ArrayList(); - private List transferFonts = new ArrayList(); - public HashMap imageMap = new HashMap(); - private List charts = new ArrayList(); // chart specific for this sheet - private long lbPlyPos; - private short grbit; - private byte cch; - private byte grbitChr; + private List printRecs; + + // These records are for boundsheet transferral to a new + // book. + private List transferXfs = new ArrayList(); + private List transferFonts = new ArrayList(); + public HashMap imageMap = new HashMap(); + private List charts = new ArrayList(); // chart + // specific + // for + // this + // sheet + private long lbPlyPos; + private short grbit; + private byte cch; + private byte grbitChr; // private int sheetnum; - private Index myidx; - private BiffRec lastCell; - private Row lastRow = null; - private Window2 win2; - private Scl scl; - private Pane pane; - private Dval dval; + private Index myidx; + private BiffRec lastCell; + private Row lastRow = null; + private Window2 win2; + private Scl scl; + private Pane pane; + private Dval dval; + + protected Headerrec hdr; + protected Footerrec ftr; + private WsBool wsbool; + private Guts guts; + + public int lastObjId = 0; // 20100210 + // KSC: + // track + // last-used + // Object + // id + // for + // this + // sheet - protected Headerrec hdr; - protected Footerrec ftr; - private WsBool wsbool; - private Guts guts; + @Override + public Headerrec getHeader() { + return this.hdr; + } + + @Override + public void setHeader(BiffRec h) { + this.hdr = (Headerrec) h; + } + + @Override + public Footerrec getFooter() { + return this.ftr; + } - public int lastObjId= 0; // 20100210 KSC: track last-used Object id for this sheet + @Override + public void setFooter(BiffRec ftr) { + this.ftr = (Footerrec) ftr; + } - public Headerrec getHeader(){return this.hdr;} - public void setHeader(BiffRec h) {this.hdr = (Headerrec) h;} - public Footerrec getFooter(){return this.ftr;} - public void setFooter(BiffRec ftr){this.ftr = (Footerrec)ftr;} + public void setDvalRec(Dval d) { + dval = d; + } - public void setDvalRec(Dval d) {dval = d;} - public Dval getDvalRec() {return dval;} + public Dval getDvalRec() { + return dval; + } // sheet types from grbit field offset 0 - static final byte SHEET_DIALOG = 0x00; - static final byte XL4_MACRO = 0x01; - static final byte CHART = 0x02; - static final byte VBMODULE = 0x06; + static final byte SHEET_DIALOG = 0x00; + static final byte XL4_MACRO = 0x01; + static final byte CHART = 0x02; + static final byte VBMODULE = 0x06; // hidden states from grbit field offset 1 - public static final byte VISIBLE = 0x00; - public static final byte HIDDEN = 0x01; - public static final byte VERY_HIDDEN = 0x02; + public static final byte VISIBLE = 0x00; + public static final byte HIDDEN = 0x01; + public static final byte VERY_HIDDEN = 0x02; - private boolean formulaShiftInclusive = false; - private AbstractList cond_formats = new Vector(); - private AbstractList autoFilters = new Vector(); // 20100111 KSC + private boolean formulaShiftInclusive = false; + private AbstractList cond_formats = new Vector(); + private AbstractList autoFilters = new Vector(); // 20100111 + // KSC // OOXML use: stores external sheet-level OOXML objects - private AbstractList ooxmlObjects = new ArrayList(); - - // OOXML-specific sheet attributes TODO: translate to Excel 2003 version IF POSSIBLE - private boolean thickBottom = false; - private boolean thickTop = false; - private boolean zeroHeight = false; - private boolean customHeight = false; - private double defaultRowHeight = 12.75; // measured in point size - private float defaultColWidth = (float) -1.0; - private DefColWidth defColWidth = null; - private HashMap ooxmlShapes = null; // stores OOXML shapes - private SheetView sheetview = null; - private SheetPr sheetPr = null; - private io.starter.formats.OOXML.AutoFilter ooautofilter = null; - private SheetProtectionManager protector; - - private transient HashMap sheetNameRecs = new HashMap(); // sheet scoped names - List mc = new CompatibleVector(); - private Selection lastselection = null; - - - - - /** Gets this sheet's SheetProtectionManager. + private AbstractList ooxmlObjects = new ArrayList(); + + // OOXML-specific sheet attributes TODO: translate to Excel + // 2003 version IF POSSIBLE + private boolean thickBottom = false; + private boolean thickTop = false; + private boolean zeroHeight = false; + private boolean customHeight = false; + private double defaultRowHeight = 12.75; // measured + // in + // point + // size + private float defaultColWidth = (float) -1.0; + private DefColWidth defColWidth = null; + private HashMap ooxmlShapes = null; // stores + // OOXML + // shapes + private SheetView sheetview = null; + private SheetPr sheetPr = null; + private io.starter.formats.OOXML.AutoFilter ooautofilter = null; + private SheetProtectionManager protector; + + private transient HashMap sheetNameRecs = new HashMap(); // sheet + // scoped + // names + List mc = new CompatibleVector(); + private Selection lastselection = null; + + /** Gets this sheet's SheetProtectionManager. */ public SheetProtectionManager getProtectionManager() { - if (protector == null) protector = new SheetProtectionManager( this ); + if (protector == null) + protector = new SheetProtectionManager(this); return protector; } - /** get the last BiffRec added to this sheet + /** get the last BiffRec added to this sheet */ - public BiffRec getLastCell(){return lastCell;} + @Override + public BiffRec getLastCell() { + return lastCell; + } /* - * TODO: find calls to this method which really need to be calling 'assembleSheetRecs() -jm 8/05 - * */ + * TODO: find calls to this method which really need to be + * calling 'assembleSheetRecs() -jm 8/05 + */ + @Override public List getSheetRecs() { return SheetRecs; } - /** Insert an image into the WorkBook - + /** Insert an image into the WorkBook + * * @param im */ @@ -226,38 +335,73 @@ public void insertImage(ImageHandle im, boolean bAddUnconditionally) { msoDrawing.setSheet(this); msoDrawing.setCoords(im.getCoords()); - im.setMsgdrawing(msoDrawing); // 20070924 KSC: link 2 actual msodrawing that describes this image for setting bounds, etc. + im.setMsgdrawing(msoDrawing); // 20070924 KSC: link 2 actual msodrawing + // that describes this image for setting + // bounds, etc. int insertIndex = -1; Obj obj = (Obj) Obj.getPrototype(); // now add to proper place in stream - if (msodg!=null) { // already have drawing records; just add to records + update msodg + if (msodg != null) { // already have drawing records; just add to + // records + update msodg insertIndex = this.getIndexOf(MSODRAWINGSELECTION); if (insertIndex < 0) insertIndex = this.getIndexOf(WINDOW2); - if (msodg.getMsoHeaderRec(this)==null) // handle case of multiple sheets- each needs it's own mso header ... + if (msodg.getMsoHeaderRec(this) == null) // handle case of multiple + // sheets- each needs + // it's own mso header + // ... msoDrawing.setIsHeader(); - } else { // No images present in workbook, must add appropriate records + } else { // No images present in workbook, must add appropriate + // records // Create new msodg rec - this.wkbook.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup.getPrototype()); + this.wkbook.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup + .getPrototype()); msodg = this.wkbook.getMSODrawingGroup(); - msodg.initNewMSODrawingGroup(); // generate and add required records for drawing records + msodg.initNewMSODrawingGroup(); // generate and add required records + // for drawing records // also add 1st portion for drawing rec msoDrawing.setIsHeader(); // insertion point for new msodrawing rec insertIndex = getIndexOf(DIMENSIONS) + 1; } if (insertIndex > 0) { // should! then have a drawing record to insert - // 20071120 KSC: retrieve idx in order to reuse/link to existing image bytes if duplicating images - int idx= msodg.addImage(im.getImageBytes(), im.getImageType(), bAddUnconditionally); - imageMap.put(im, Integer.valueOf(im.getImageIndex()-1)); // add new image to map and link to actual imageIndex - moved from above - msoDrawing.createRecord(++this.wkbook.lastSPID, im.getImageName(), im.getShapeName(), idx); // generate msoDrawing using correct values moved from above + // 20071120 KSC: retrieve idx in order to reuse/link to + // existing image bytes if duplicating images + int idx = msodg.addImage(im.getImageBytes(), im + .getImageType(), bAddUnconditionally); + imageMap.put(im, Integer.valueOf(im.getImageIndex() - 1)); // add + // new + // image + // to + // map + // and + // link + // to + // actual + // imageIndex + // - + // moved + // from + // above + msoDrawing.createRecord(++this.wkbook.lastSPID, im + .getImageName(), im.getShapeName(), idx); // generate + // msoDrawing + // using correct + // values moved + // from above this.SheetRecs.add(insertIndex++, msoDrawing); this.SheetRecs.add(insertIndex++, obj); - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the msodrawinggroup set of recs - wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing header record and update it (using info from other msodrawing recs) + msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to + // the msodrawinggroup set of + // recs + wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing + // header record and update + // it (using info from other + // msodrawing recs) // 20080908 KSC: moved from above msodg.setSpidMax(this.wkbook.lastSPID + 1); // was ++lastSPID - msodg.updateRecord(); // given all information, generate appropriate bytes + msodg.updateRecord(); // given all information, generate appropriate + // bytes msodg.dirtyflag = true; } else { Logger.logErr("Boundsheet.insertImage: Drawing Group not created."); @@ -271,16 +415,17 @@ public List getImageVect() { ArrayList im = new ArrayList(); Iterator ir = imageMap.keySet().iterator(); while (ir.hasNext()) { - im.add((ImageHandle) ir.next()); + im.add(ir.next()); } return im; } - /** Get a collection of all names in the worksheet + /** Get a collection of all names in the worksheet */ public Name[] getSheetScopedNames() { - if(this.sheetNameRecs==null)this.sheetNameRecs=new HashMap(); + if (this.sheetNameRecs == null) + this.sheetNameRecs = new HashMap(); ArrayList a = new ArrayList(this.sheetNameRecs.values()); Name[] n = new Name[a.size()]; a.toArray(n); @@ -288,14 +433,15 @@ public Name[] getSheetScopedNames() { } /** - * Rationalizes the itab (sheet reference) for name records, - * this has to occur after sheet insert/delete operations to keep the - * references intact. Unfortunately these references do not use the Externsheet, - * so are not ilbl listeners. + * Rationalizes the itab (sheet reference) for name records, + * this has to occur after sheet insert/delete operations to keep the + * references intact. Unfortunately these references do not use the Externsheet, + * so are not ilbl listeners. * */ void updateLocalNameReferences() { - if (sheetNameRecs == null)return; + if (sheetNameRecs == null) + return; Iterator i = this.sheetNameRecs.values().iterator(); while (i.hasNext()) { Name n = (Name) i.next(); @@ -303,14 +449,16 @@ void updateLocalNameReferences() { } } - /** for whatever reason, we return a Handle from an internal class - + /** for whatever reason, we return a Handle from an internal class + * * @return */ public ImageHandle[] getImages() { - /* 20071026 KSC: since there may be multiple copies of the same - * image in the sheet, must build imageHandle array by hand + /* + * 20071026 KSC: since there may be multiple copies of the + * same + * image in the sheet, must build imageHandle array by hand */ if (imageMap == null) return null; @@ -322,34 +470,39 @@ public ImageHandle[] getImages() { } return im; } - /** column formatting records - * - * Note that it checks if exists. This is due to externally copied boundsheets already having - * the record in the array when addrecord occurs. - */ + + /** column formatting records + * + * Note that it checks if exists. This is due to externally copied boundsheets already having + * the record in the array when addrecord occurs. + */ + @Override public void addColinfo(Colinfo c) { if (!this.colinfos.containsValue(c)) { this.colinfos.put(c, c); - } + } } - - /** - * For workbooks that do not contain a dval record, - * insert a default dval rec + /** + * For workbooks that do not contain a dval record, + * insert a default dval rec + * * @return */ public Dval insertDvalRec() { - if (this.getDvalRec()!=null)return this.getDvalRec(); + if (this.getDvalRec() != null) + return this.getDvalRec(); Dval d = (Dval) Dval.getPrototype(); d.setSheet(this); int insertIdx = win2.getRecordIndex() + 1; - // correct position for DV block is before sheet protection records (if any) + // correct position for DV block is before sheet protection + // records (if any) // or before EOF int opc = ((BiffRec) SheetRecs.get(insertIdx)).getOpcode(); while (opc != EOF) { - if (opc==SHEETPROTECTION || opc==RANGEPROTECTION || opc==SHEETLAYOUT) + if (opc == SHEETPROTECTION || opc == RANGEPROTECTION + || opc == SHEETLAYOUT) break; insertIdx++; opc = ((BiffRec) SheetRecs.get(insertIdx)).getOpcode(); @@ -360,16 +513,17 @@ public Dval insertDvalRec() { } /** - * Create a dv (validation record) - * record gets inserted into the byte stream from - * within Dval - + * Create a dv (validation record) + * record gets inserted into the byte stream from + * within Dval + * * @param location * @return */ public Dv createDv(String location) { - if(this.getDvalRec()==null)this.insertDvalRec(); + if (this.getDvalRec() == null) + this.insertDvalRec(); Dv dv = this.getDvalRec().createDvRec(location); int insertIdx = this.SheetRecs.size() - 2; // start at 1 before EOF int opc = ((BiffRec) SheetRecs.get(insertIdx)).getOpcode(); @@ -382,9 +536,9 @@ public Dv createDv(String location) { } /** - * Create a Condfmt (Conditional format) record and - * add it to sheet recs - + * Create a Condfmt (Conditional format) record and + * add it to sheet recs + * * @param location * @return @@ -393,12 +547,11 @@ public Condfmt createCondfmt(String location, WorkBookHandle wbh) { Condfmt cfx = (Condfmt) Condfmt.getPrototype(); int insertIdx = win2.getRecordIndex() + 1; BiffRec rec = (BiffRec) this.SheetRecs.get(insertIdx); - while (rec.getOpcode()!= HLINK && - rec.getOffset()!= DVAL && - rec.getOpcode()!= 0x0862 /* SHEETLAYOUT */ && - rec.getOpcode()!= 0x0867 /* SHEETPROTECTION */ && - rec.getOpcode()!= 0x0868 /* RANGEPROTECTION */ && - rec.getOpcode()!=EOF) + while (rec.getOpcode() != HLINK && rec.getOffset() != DVAL + && rec.getOpcode() != 0x0862 /* SHEETLAYOUT */ + && rec.getOpcode() != 0x0867 /* SHEETPROTECTION */ + && rec.getOpcode() != 0x0868 /* RANGEPROTECTION */ + && rec.getOpcode() != EOF) rec = (BiffRec) this.SheetRecs.get(++insertIdx); this.SheetRecs.add(insertIdx, cfx); @@ -411,11 +564,11 @@ public Condfmt createCondfmt(String location, WorkBookHandle wbh) { } /** - * Create a Cf (Conditional format rule) record and - * add it to sheet recs - + * Create a Cf (Conditional format rule) record and + * add it to sheet recs + * - * @param Conditional format + * @param Conditional format * @param range * @return */ @@ -433,8 +586,8 @@ public Cf createCf(Condfmt cfx) { } /** - * obtain the desired image handle via the MsoDrawing Image Index - * used for mapping images from copied worksheets + * obtain the desired image handle via the MsoDrawing Image Index + * used for mapping images from copied worksheets * @param index * @return */ @@ -494,7 +647,8 @@ public int getIndexOfDimensions() { return foundIndex; } - // Generic getIndexOf - replace specific hardocoded cases ... + // Generic getIndexOf - replace specific hardocoded cases + // ... public int getIndexOf(short opc) { BiffRec rec = null; @@ -526,7 +680,8 @@ public BiffRec getSheetRec(short opc) { return null; } - // 20070916 KSC: access for inserting records into sheetrecs collection + // 20070916 KSC: access for inserting records into sheetrecs + // collection public void insertSheetRecordAt(BiffRec r, int index) { r.setSheet(this); if (index > -1 && index < SheetRecs.size()) @@ -536,21 +691,21 @@ public void insertSheetRecordAt(BiffRec r, int index) { } /** - * Sheet hash is a cross-workbook identifier for OpenXLS. The first time it is called it creates the sheet hash. + * Sheet hash is a cross-workbook identifier for OpenXLS. The first time it is called it creates the sheet hash. * @return */ public String getSheetHash() { if (sheetHash.equals("")) { - sheetHash = this.getSheetName() + this.getSheetNum() + this.getRealRecordIndex(); + sheetHash = this.getSheetName() + this.getSheetNum() + + this.getRealRecordIndex(); } return sheetHash; } - /** - * assembleSheetRecs assembles the array of records, then ouputs - * the ordered list to the bytestreamer, which should be the only - * thing calling this. + * assembleSheetRecs assembles the array of records, then ouputs + * the ordered list to the bytestreamer, which should be the only + * thing calling this. * * */ public List assembleSheetRecs() { @@ -568,90 +723,107 @@ public List assembleSheetRecs() { * formulas are saved as text. */ /* - * From Excel: To preserve the formulas if you reopen the file in Microsoft - * Excel, select the Delimited option in the Text Import Wizard, and select - * tab characters as the delimiters. Note If your workbook contains special - * font characters, such as a copyright symbol (©), and you will be using - * the converted text file on a computer with a different operating system, - * save the workbook in the text file format appropriate for that system. - * For example, if you are using Windows and want to use the text file on a - * Macintosh computer, save the file in the Text (Macintosh) format. If you - * are using a Macintosh computer and want to use the text file on a system - * running Windows or Windows NT, save the file in the Text (Windows) + * From Excel: To preserve the formulas if you reopen the + * file in Microsoft + * Excel, select the Delimited option in the Text Import + * Wizard, and select + * tab characters as the delimiters. Note If your workbook + * contains special + * font characters, such as a copyright symbol (©), and you + * will be using + * the converted text file on a computer with a different + * operating system, + * save the workbook in the text file format appropriate for + * that system. + * For example, if you are using Windows and want to use the + * text file on a + * Macintosh computer, save the file in the Text (Macintosh) + * format. If you + * are using a Macintosh computer and want to use the text + * file on a system + * running Windows or Windows NT, save the file in the Text + * (Windows) * format. */ public void writeAsTabbedText(OutputStream dest) throws IOException { - int lastrow= this.getMaxRow(); - int lastcol= this.getMaxCol(); - boolean isInteger= false; - byte[] tab= {9}; - byte[] crlf= {13,10}; - for (int i= 0; i < lastrow; i++) { - Row r= this.getRowByNumber(i); - if (r!=null) { - for (int j= 0; j < lastcol; j++) { + int lastrow = this.getMaxRow(); + int lastcol = this.getMaxCol(); + boolean isInteger = false; + byte[] tab = { 9 }; + byte[] crlf = { 13, 10 }; + for (int i = 0; i < lastrow; i++) { + Row r = this.getRowByNumber(i); + if (r != null) { + for (int j = 0; j < lastcol; j++) { BiffRec c = null; try { - // Look for the cell and output - c = r.getCell((short)j); - int type= ((XLSRecord) c).getCellType(); + // Look for the cell and output + c = r.getCell((short) j); + int type = ((XLSRecord) c).getCellType(); Object o; - if (type!=Cell.TYPE_FORMULA) { - isInteger= (type==Cell.TYPE_INT); - o= c.getStringVal(); + if (type != Cell.TYPE_FORMULA) { + isInteger = (type == Cell.TYPE_INT); + o = c.getStringVal(); } else { - o= ((Formula) c).calculateFormula(); - if (o instanceof Integer || (o instanceof Double && ((Double)o).intValue()==((Double)o).doubleValue())) - isInteger= true; + o = ((Formula) c).calculateFormula(); + if (o instanceof Integer || (o instanceof Double + && ((Double) o).intValue() == ((Double) o) + .doubleValue())) + isInteger = true; else { - isInteger= false; + isInteger = false; } } try { - String output = CellFormatFactory.fromPatternString( - c.getFormatPattern() ).format( o.toString() ); - if (output.indexOf(",")!=-1) - output= "\"" + output + "\""; + String output = CellFormatFactory + .fromPatternString(c.getFormatPattern()) + .format(o.toString()); + if (output.indexOf(",") != -1) + output = "\"" + output + "\""; dest.write(output.getBytes()); } catch (Exception e) { - Logger.logWarn("Boundsheet.writeAsTabbedText: error writing " + c.getCellAddress() + ":" + e.toString()); + Logger.logWarn("Boundsheet.writeAsTabbedText: error writing " + + c.getCellAddress() + ":" + e.toString()); } } catch (CellNotFoundException e1) { // No cell exists at this location, continue } dest.write(tab); - } - //} catch (RowNotFoundException e) { } - } - dest.write(crlf); - } - dest.flush(); - dest.close(); - } + } + // } catch (RowNotFoundException e) { } + } + dest.write(crlf); + } + dest.flush(); + dest.close(); + } - /** do all of the expensive updating here - only right before streaming record. + /** do all of the expensive updating here + only right before streaming record. */ - public void preStream() { - } + @Override + public void preStream() {} + @Override public Bof getMyBof() { return mybof; } + @Override public Eof getMyEof() { return myeof; } /** - * Return an array of all the dvRecs within - * this boundsheet (Dval parent rec) - + * Return an array of all the dvRecs within + * this boundsheet (Dval parent rec) + * * @return */ public List getDvRecs() { - if (this.getDvalRec()!=null)return this.getDvalRec().getDvs(); + if (this.getDvalRec() != null) + return this.getDvalRec().getDvs(); return null; } @@ -673,17 +845,19 @@ public void addConditionalFormat(Condfmt cf) { cond_formats.add(cf); } + @Override public void setWindow2(Window2 w) { win2 = w; } + @Override public Window2 getWindow2() { return win2; } /** - * set/save the Pane rec for this sheet - * also links the Window2 rec to the pane rec + * set/save the Pane rec for this sheet + * also links the Window2 rec to the pane rec * @param p */ public void setPane(Pane p) { @@ -712,14 +886,17 @@ public void removePane() { pane = null; } + @Override public WorkBook getWorkBook() { return wkbook; } - /** Remove a BiffRec from this WorkSheet. - * - * @deprecated Use {@link #removeCell(int, int)} instead. + /** Remove a BiffRec from this WorkSheet. + * + * @deprecated Use {@link #removeCell(int, int)} instead. */ + @Deprecated + @Override public void removeCell(String celladdr) { BiffRec c = this.getCell(celladdr); if (c != null) { @@ -727,8 +904,8 @@ public void removeCell(String celladdr) { } } - /** Remove a BiffRec from this WorkSheet. - */ + /** Remove a BiffRec from this WorkSheet. + */ public void removeCell(int row, int col) { BiffRec c; try { @@ -738,23 +915,24 @@ public void removeCell(int row, int col) { // cell does not exist, this is fine } } - + /** remove a BiffRec from the worksheet. * * Unfortunately this also has to manage mulrecs */ - public void removeCell (BiffRec cell) { - if (cell.getOpcode() == MULBLANK) { - ((Mulblank)cell).removeCell(cell.getColNumber()); - } - if (cell.getOpcode() == XLSConstants.FORMULA) { + @Override + public void removeCell(BiffRec cell) { + if (cell.getOpcode() == MULBLANK) { + ((Mulblank) cell).removeCell(cell.getColNumber()); + } + if (cell.getOpcode() == XLSConstants.FORMULA) { Formula f = (Formula) cell; this.wkbook.removeFormula(f); } - cellsByRow.remove( cell ); - cellsByCol.remove( cell ); - } - + cellsByRow.remove(cell); + cellsByCol.remove(cell); + } + /** * removes an image from the imagehandle cache (should be in WSH) * @@ -767,12 +945,11 @@ public boolean removeImage(ImageHandle img) { } /** - * remove a record from the vector via it's index - * into the SheetRecs aray, includes firing a change event + * remove a record from the vector via it's index + * into the SheetRecs aray, includes firing a change event * @param idx */ - public void removeRecFromVec(int idx) - { + public void removeRecFromVec(int idx) { try { BiffRec rec = (BiffRec) this.SheetRecs.get(idx); removeRecFromVec(rec); @@ -780,60 +957,64 @@ public void removeRecFromVec(int idx) Logger.logErr("Boundsheet.removeRecFromVec: " + e.toString()); } } - - /** Removes some rows and all associated cells from this sheet. - * References are not handled; for those see {@link ReferenceTracker}. - * - * @param first the zero-based index of the first row to be removed - * @param count the number of rows to be removed - * @param shift whether to shift subsequent rows up to fill the empty space - */ - public void removeRows (int first, int count, boolean shift) { - - for (int rowIdx = first; rowIdx < first + count; rowIdx++) { - //this.removeRowContents(rowIdx); - - - Row row = rows.get( rowIdx ); - if (null == row) continue; - - Iterator iter = row.getCells().iterator(); - while (iter.hasNext()) { - BiffRec cell = iter.next(); - - // This removes the cell from the Row's map without perturbing - // the iterator. When removeCell tries to remove it later the - // map will silently do nothing instead of throwing a CME. - iter.remove(); - - this.removeCell( cell ); - } - - rows.remove( rowIdx ); - this.removeRecFromVec( row ); - } - - // shift all following rows up to fill the gap left by the removed rows - if (shift && !rows.isEmpty()) { - int shiftBy = -1 * count; - int lastrow = lastRow.getRowNumber(); + + /** Removes some rows and all associated cells from this sheet. + * References are not handled; for those see {@link ReferenceTracker}. + * + * @param first the zero-based index of the first row to be removed + * @param count the number of rows to be removed + * @param shift whether to shift subsequent rows up to fill the empty space + */ + public void removeRows(int first, int count, boolean shift) { + + for (int rowIdx = first; rowIdx < first + count; rowIdx++) { + // this.removeRowContents(rowIdx); + + Row row = rows.get(rowIdx); + if (null == row) + continue; + + Iterator iter = row.getCells().iterator(); + while (iter.hasNext()) { + BiffRec cell = iter.next(); + + // This removes the cell from the Row's map without + // perturbing + // the iterator. When removeCell tries to remove it later + // the + // map will silently do nothing instead of throwing a CME. + iter.remove(); + + this.removeCell(cell); + } + + rows.remove(rowIdx); + this.removeRecFromVec(row); + } + + // shift all following rows up to fill the gap left by the + // removed rows + if (shift && !rows.isEmpty()) { + int shiftBy = -1 * count; + int lastrow = lastRow.getRowNumber(); for (int rowIdx = first + 1; rowIdx <= lastrow; rowIdx++) { - Row row = rows.get( rowIdx ); - if (null == row) continue; - - this.shiftRow( row, shiftBy ); + Row row = rows.get(rowIdx); + if (null == row) + continue; + + this.shiftRow(row, shiftBy); } - } - - // update sheet dimensions - this.dimensions.setRowLast( - null != lastRow ? lastRow.getRowNumber() : 0 ); - } - - - /** remove rec from the vector, includes firing - a changeevent. + } + + // update sheet dimensions + this.dimensions + .setRowLast(null != lastRow ? lastRow.getRowNumber() : 0); + } + + /** remove rec from the vector, includes firing + a changeevent. */ + @Override public void removeRecFromVec(BiffRec rec) { boolean removerec = true; // is it an RK, maybe part of a Mulrk?? @@ -843,39 +1024,43 @@ public void removeRecFromVec(BiffRec rec) { } else if (rec.getOpcode() == FORMULA) { Formula f = (Formula) rec; this.wkbook.removeFormula(f); - } - else if (rec.getOpcode() == LABELSST){ + } else if (rec.getOpcode() == LABELSST) { Labelsst lst = (Labelsst) rec; Sst strtable = wkbook.getSharedStringTable(); lst.initUnsharedString(); strtable.removeUnicodestring(lst.getUnsharedString()); - } else if (rec instanceof Mulblank) { // KSC: Added + } else if (rec instanceof Mulblank) { // KSC: Added removerec = ((Mulblank) rec).removeCell(rec.getColNumber()); } if (removerec) { if (streamer.removeRecord(rec)) { - if(DEBUGLEVEL > 5)Logger.logInfo("Boundsheet RemoveRec Removed: " + rec.toString()); + if (DEBUGLEVEL > 5) + Logger.logInfo("Boundsheet RemoveRec Removed: " + + rec.toString()); } else { if (rec instanceof Mul) { if (!((Mul) rec).removed()) - if(DEBUGLEVEL > 1)Logger.logWarn("RemoveRec failed: " + rec.getClass().getName() + " not found in Streamer Vec"); + if (DEBUGLEVEL > 1) + Logger.logWarn("RemoveRec failed: " + + rec.getClass().getName() + + " not found in Streamer Vec"); } else { - if(DEBUGLEVEL > 1)Logger.logWarn("RemoveRec failed: " + rec.getClass().getName() + " not found in Streamer Vec"); + if (DEBUGLEVEL > 1) + Logger.logWarn("RemoveRec failed: " + + rec.getClass().getName() + + " not found in Streamer Vec"); } } } } - - - - /** - Called from removeCell(), removeMulrk() handles the fact that you - are trying to delete a rk that is really just a part of a Mulrk. This - is handled by truncating the mulrk at the cell, then creating individual numbers - after the deleted cell. + Called from removeCell(), removeMulrk() handles the fact that you + are trying to delete a rk that is really just a part of a Mulrk. This + is handled by truncating the mulrk at the cell, then creating individual numbers + after the deleted cell. **/ + @Override public void removeMulrk(Rk thisrk) { Mulrk mymul = (Mulrk) thisrk.getMyMul(); if (mymul != null) { // Part of a mulrk. JOY! @@ -911,7 +1096,6 @@ public void removeMulrk(Rk thisrk) { } - /** * Remove a row, do not shift any other rows * @throws RowNotFoundException @@ -933,140 +1117,158 @@ public void removeRowContents(int rownum) throws RowNotFoundException { } } - /** Removes a set of columns and their associated cells from this sheet. - * Optionally shifts the subsequent columns left to fill the empty space. - * This method only updates the sheet and cell records. It doesn't adjust - * references; that's handled by {@link WorkSheetHandle#removeCols}. - * - * @param first this zero-based index of the first column to be removed - * @param count the number of columns to remove - * @param shift whether to shift subsequent columns left - */ - public void removeCols (int first, int count, boolean shift) { - + + /** Removes a set of columns and their associated cells from this sheet. + * Optionally shifts the subsequent columns left to fill the empty space. + * This method only updates the sheet and cell records. It doesn't adjust + * references; that's handled by {@link WorkSheetHandle#removeCols}. + * + * @param first this zero-based index of the first column to be removed + * @param count the number of columns to remove + * @param shift whether to shift subsequent columns left + */ + public void removeCols(int first, int count, boolean shift) { + if (shift) { - ReferenceTracker.updateReferences(first, count*-1, this, false); //shift or expand/contract ALL affected references including named ranges + ReferenceTracker.updateReferences(first, count * -1, this, false); // shift + // or + // expand/contract + // ALL + // affected + // references + // including + // named + // ranges } - - for (int colIdx = first; colIdx < first + count; colIdx++) { - - // update or remove the ColInfo record as appropriate - Colinfo info = this.getColInfo(colIdx); - if (null != info) { - if (info.getColLast() < first + count) { - if (info.getColFirst() >= first) { - this.removeColInfo( info ); - } else { - info.setColLast( first - 1 ); - } - } else if (info.getColFirst() >= first) { - info.setColFirst( first + count ); - } - } - - // remove the cells in the column + + for (int colIdx = first; colIdx < first + count; colIdx++) { + + // update or remove the ColInfo record as appropriate + Colinfo info = this.getColInfo(colIdx); + if (null != info) { + if (info.getColLast() < first + count) { + if (info.getColFirst() >= first) { + this.removeColInfo(info); + } else { + info.setColLast(first - 1); + } + } else if (info.getColFirst() >= first) { + info.setColFirst(first + count); + } + } + + // remove the cells in the column try { List cells = this.getCellsByCol(colIdx); int cellCount = cells.size(); - for (int idx = cellCount - 1; idx >= 0; idx--) { - BiffRec cell = cells.get( idx ); - if (null == cell) continue; - - this.removeCell( cell ); - } + for (int idx = cellCount - 1; idx >= 0; idx--) { + BiffRec cell = cells.get(idx); + if (null == cell) + continue; + + this.removeCell(cell); + } } catch (CellNotFoundException e) { // This is fine, no cells in this column } - - } - - if (shift){ - int shiftBy = -1 * count; - int maxcol = this.getRealMaxCol(); - for (int colIdx = first + 1; colIdx <= maxcol; colIdx++){ - this.shiftCol( colIdx, shiftBy ); - } - } - - // make sure dimensions record is correctly updated upon output - this.dimensions.setColLast( this.getRealMaxCol() ); - } - - - /** - * Access an arrayList of cells by column - * @param colNum - * @return - */ - public ArrayList getCellsByCol(int colNum) throws CellNotFoundException - { - SortedMap theCells = cellsByCol.subMap( - new CellAddressible.RangeBoundary( 0, colNum, true), - new CellAddressible.RangeBoundary(0, colNum+1, false) - ); - if(theCells.size()==0) - throw new CellNotFoundException( this.sheetname, 0, col ); - + + } + + if (shift) { + int shiftBy = -1 * count; + int maxcol = this.getRealMaxCol(); + for (int colIdx = first + 1; colIdx <= maxcol; colIdx++) { + this.shiftCol(colIdx, shiftBy); + } + } + + // make sure dimensions record is correctly updated upon + // output + this.dimensions.setColLast(this.getRealMaxCol()); + } + + /** + * Access an arrayList of cells by column + * @param colNum + * @return + */ + public ArrayList getCellsByCol(int colNum) throws CellNotFoundException { + SortedMap theCells = cellsByCol + .subMap(new CellAddressible.RangeBoundary(0, colNum, + true), new CellAddressible.RangeBoundary(0, colNum + 1, + false)); + if (theCells.size() == 0) + throw new CellNotFoundException(this.sheetname, 0, col); + Collection cells = theCells.values(); Iterator i = cells.iterator(); - while(i.hasNext()){ + while (i.hasNext()) { BiffRec biffrec = i.next(); - if (biffrec.getOpcode()==MULBLANK) { + if (biffrec.getOpcode() == MULBLANK) { ((Mulblank) biffrec).setCurrentCell((short) colNum); } } return new ArrayList(cells); - } - - - /** - * Access an arrayList of cells by column - * @param colNum - * @return - */ - public ArrayList getCellsByRow(int rowNum) throws CellNotFoundException - { - SortedMap theCells = cellsByRow.subMap( - new CellAddressible.Reference( rowNum, 0 ), - new CellAddressible.Reference( rowNum+1, 0 ) - ); - if(theCells.size()==0) - throw new CellNotFoundException( this.sheetname, 0, col ); - - Collection cells = theCells.values(); - return new ArrayList(cells); - } + } + /** + * Access an arrayList of cells by column + * @param colNum + * @return + */ + public ArrayList getCellsByRow(int rowNum) throws CellNotFoundException { + SortedMap theCells = cellsByRow + .subMap(new CellAddressible.Reference(rowNum, + 0), new CellAddressible.Reference(rowNum + 1, 0)); + if (theCells.size() == 0) + throw new CellNotFoundException(this.sheetname, 0, col); + Collection cells = theCells.values(); + return new ArrayList(cells); + } - /** get a handle to a specific column of cells in this sheet - */ - public Colinfo getColInfo (int col) { - Colinfo info = this.colinfos.get( - new ColumnRange.Reference( col, col ) ); - if (null == info) return null; - - if (info.inrange( col )) return info; - return null; - } + /** get a handle to a specific column of cells in this sheet + */ + @Override + public Colinfo getColInfo(int col) { + Colinfo info = this.colinfos.get(new ColumnRange.Reference(col, col)); + if (null == info) + return null; - - /** remove all Sheet records from Sheet. + if (info.inrange(col)) + return info; + return null; + } + + /** remove all Sheet records from Sheet. */ + @Override public void removeAllRecords() { // this.setSheetRecs(); XLSRecord[] rx = new XLSRecord[SheetRecs.size()]; SheetRecs.toArray(rx); for (int t = 0; t < rx.length; t++) { - int opcode= ((BiffRec)rx[t]).getOpcode(); // Handle continues masking mso's - if (opcode!=MSODRAWING && !(opcode==CONTINUE && ((Continue)rx[t]).maskedMso!=null)) + int opcode = ((BiffRec) rx[t]).getOpcode(); // Handle continues + // masking mso's + if (opcode != MSODRAWING && !(opcode == CONTINUE + && ((Continue) rx[t]).maskedMso != null)) this.removeRecFromVec(rx[t]); else // must update MSODrawingGroup record as well ... if (opcode == MSODRAWING) - this.wkbook.msodg.removeMsodrawingrec((MSODrawing)rx[t], this, false); // don't remove assoc object record - else // a Continue record masking an MSoo - this.wkbook.msodg.removeMsodrawingrec(((Continue)rx[t]).maskedMso, this, false); // don't remove assoc object record + this.wkbook.msodg + .removeMsodrawingrec((MSODrawing) rx[t], this, false); // don't + // remove + // assoc + // object + // record + else // a Continue record masking an MSoo + this.wkbook.msodg + .removeMsodrawingrec(((Continue) rx[t]).maskedMso, this, false); // don't + // remove + // assoc + // object + // record rx[t] = null; } SheetRecs.clear(); @@ -1074,40 +1276,40 @@ public void removeAllRecords() { } - /** Shifts a single column. - * This adjusts any mention of the column number in the associated records. - * References are not handled; for those see {@link ReferenceTracker}. - * - * @param col the column to be shifted - * @param shift the number of columns by which to shift - */ - private void shiftCol (int colNum, int shift) { - Colinfo info = this.getColInfo(colNum); - int oldCol = colNum; - int newCol = oldCol + shift; - - List cells; + /** Shifts a single column. + * This adjusts any mention of the column number in the associated records. + * References are not handled; for those see {@link ReferenceTracker}. + * + * @param col the column to be shifted + * @param shift the number of columns by which to shift + */ + private void shiftCol(int colNum, int shift) { + Colinfo info = this.getColInfo(colNum); + int oldCol = colNum; + int newCol = oldCol + shift; + + List cells; try { cells = this.getCellsByCol(colNum); - for (BiffRec cell : cells) { - cell.setCol( (short) newCol ); - this.updateDimensions(cell.getRowNumber(), cell.getColNumber()); - } + for (BiffRec cell : cells) { + cell.setCol((short) newCol); + this.updateDimensions(cell.getRowNumber(), cell.getColNumber()); + } } catch (CellNotFoundException e) { // No cells exist in this column } - + if (null != info) { - int first = info.getColFirst(); - if (first == oldCol || first > newCol) { - info.setColFirst( newCol ); - } - - int last = info.getColLast(); - if (last == oldCol || last < newCol) - info.setColLast( newCol ); - } - } + int first = info.getColFirst(); + if (first == oldCol || first > newCol) { + info.setColFirst(newCol); + } + + int last = info.getColLast(); + if (last == oldCol || last < newCol) + info.setColLast(newCol); + } + } private void removeColInfo(Colinfo ci) { this.removeRecFromVec(ci); @@ -1115,12 +1317,12 @@ private void removeColInfo(Colinfo ci) { } /* - * - */ - + * + */ - /** set the Bof record for this Boundsheet + /** set the Bof record for this Boundsheet */ + @Override public void setBOF(Bof b) { mybof = b; b.setSheet(this); @@ -1130,17 +1332,21 @@ public void setBOF(Bof b) { * Determine if the boundsheet is a chart only boundsheet * @return */ + @Override public boolean isChartOnlySheet() { - if (mybof!=null)return mybof.isChartBof(); + if (mybof != null) + return mybof.isChartBof(); return false; } + @Override public void setEOF(Eof f) { myeof = f; } - /** return the pos of the Bof for this Sheet + /** return the pos of the Bof for this Sheet */ + @Override public long getLbPlyPos() {// if (mybof != null) return mybof.getLbPlyPos(); @@ -1148,101 +1354,139 @@ public long getLbPlyPos() {// } - /** set the pos of the Bof for this Sheet + /** set the pos of the Bof for this Sheet */ + @Override public void setLbPlyPos(long newpos) { byte[] newposbytes = ByteTools.cLongToLEBytes((int) newpos); System.arraycopy(newposbytes, 0, this.getData(), 0, 4); this.lbPlyPos = newpos; } - /** get the min/max dimensions - for this sheet. + /** get the min/max dimensions + for this sheet. */ private Dimensions dimensions; - /** the beginning of the Dimensions record - is the index of the RowBlocks + /** the beginning of the Dimensions record + is the index of the RowBlocks */ - public Dimensions getDimensions(){return dimensions;} + @Override + public Dimensions getDimensions() { + return dimensions; + } + + @Override public void setDimensions(Dimensions d) { - // only set the first dimensions. Other dimensions records may exist within - // the boundsheet stream from charts & msodrawing objects, but going to run with the - // assumption that the first one is the identifier for valrec start + // only set the first dimensions. Other dimensions records + // may exist within + // the boundsheet stream from charts & msodrawing objects, + // but going to run with the + // assumption that the first one is the identifier for + // valrec start if (dimensions == null) { dimensions = d; - if(myidx!=null)this.myidx.setDimensions(d); + if (myidx != null) + this.myidx.setDimensions(d); } } - /** Shifts a single row. - * This adjusts any mention of the row number in the row records. Formula - * references are not handled; for those see {@link ReferenceTracker}. - * - * @param row the row to be shifted - * @param shift the number of rows by which to shift - */ - private void shiftRow (Row row, int shift) { - Iterator cells = row.getCells().iterator(); - Mulblank skipMulBlank = null; - while (cells.hasNext()) { - BiffRec cell = (BiffRec) cells.next(); - - if (cell == skipMulBlank) continue; - else if (cell.getOpcode() == MULBLANK) - skipMulBlank = (Mulblank) cell; - - this.shiftCellRow( cell, shift ); - } - - int oldRow = row.getRowNumber(); - int newRow = oldRow + shift; - row.setRowNumber( newRow ); - - rows.remove( oldRow ); - rows.put( newRow, row ); - - if(this.dimensions.getRowLast() 10)Logger.logInfo("Boundsheet Updating Dimensions: " + row + ":" + col); + if (DEBUGLEVEL > 10) + Logger.logInfo("Boundsheet Updating Dimensions: " + row + ":" + + col); short col = (short) c; maximumCellCol = Math.max(maximumCellCol, col); maximumCellRow = Math.max(maximumCellRow, row); - if(dimensions != null)dimensions.updateDimensions(row-1, col); - if(this.myidx != null)this.myidx.updateRowDimensions(this.getMinRow(),this.getMaxRow()); // TODO: investigate why no Index is possible + if (dimensions != null) + dimensions.updateDimensions(row - 1, col); + if (this.myidx != null) + this.myidx.updateRowDimensions(this.getMinRow(), this.getMaxRow()); // TODO: + // investigate + // why + // no + // Index + // is + // possible } - /** set the associated sheet index + /** set the associated sheet index */ + @Override public void setSheetIDX(Index idx) { idx.setSheet(this); myidx = idx; } - /** set the associated sheet index + /** set the associated sheet index */ public Index getSheetIDX() { return myidx; } - /** Adjusts a cell to reflect its parent row being shifted. * This adjusts any mention of the row number in the cell record. Formula * references are not handled; for those see {@link ReferenceTracker}. @@ -1250,54 +1494,56 @@ public Index getSheetIDX() { * @param cell the cell record to be shifted * @param shift the number of rows by which to shift the cell */ - private void shiftCellRow (BiffRec cell, int shift) { + private void shiftCellRow(BiffRec cell, int shift) { int newrow = cell.getRowNumber() + shift; - cell.setRowNumber( newrow ); - + cell.setRowNumber(newrow); + // handle per-record special cases switch (cell.getOpcode()) { case XLSConstants.RK: ((Rk) cell).setMulrkRow(newrow); break; - + case XLSConstants.FORMULA: Formula formula = (Formula) cell; - + // must also shift shared formulas if necessary if (formula.isSharedFormula()) { - if (formula.getInternalRecords().size()>0) {// is it the parent? - Object o= formula.getInternalRecords().get(0); - if (o instanceof Shrfmla) { // should! - Shrfmla s= (Shrfmla) o; - s.setFirstRow(s.getFirstRow()+shift); - s.setLastRow(s.getLastRow()+shift); - } + if (formula.getInternalRecords().size() > 0) {// is it the + // parent? + Object o = formula.getInternalRecords().get(0); + if (o instanceof Shrfmla) { // should! + Shrfmla s = (Shrfmla) o; + s.setFirstRow(s.getFirstRow() + shift); + s.setLastRow(s.getLastRow() + shift); + } } } break; } - } - - /** set the numeric sheet number + } + + /** set the numeric sheet number */ + @Override public int getSheetNum() { return this.wkbook.getSheetVect().indexOf(this); } - - /** add a row to the worksheet as well - as to the RowBlock which will handle - the updating of Dbcell index behavior - - - @param BiffRec the cell being added (can't add a row without one...) + /** add a row to the worksheet as well + as to the RowBlock which will handle + the updating of Dbcell index behavior + + + @param BiffRec the cell being added (can't add a row without one...) */ private Row addNewRow(BiffRec cell) { int rn = cell.getRowNumber(); if (this.getRowByNumber(rn) != null) return this.getRowByNumber(rn); // already exists! Row r = new Row(rn, wkbook); - try { //Out-of-spec wb's may not have dimensions record -- will be handled upon validation + try { // Out-of-spec wb's may not have dimensions record -- will + // be handled upon validation if (rn >= this.getMaxRow()) dimensions.setRowLast(rn); } catch (NullPointerException e) {} @@ -1307,79 +1553,92 @@ private Row addNewRow(BiffRec cell) { } - - /** Inserts a row and shifts subsequent rows down by one. - * - * @param rownum the zero-based index of the row to be created - * @return the row that was just inserted - */ + /** Inserts a row and shifts subsequent rows down by one. + * + * @param rownum the zero-based index of the row to be created + * @return the row that was just inserted + */ // TODO: reduce this functionality to simply inserting a row // and shifting the row number of subsequent rows and cells - public Row insertRow(int rownum, int firstcol, int flag, boolean shiftrows){ - Row roe= null; - if (shiftrows && !this.fastCellAdds){ - try{ - // shift all rows after this one down... + public Row insertRow(int rownum, int firstcol, int flag, boolean shiftrows) { + Row roe = null; + if (shiftrows && !this.fastCellAdds) { + try { + // shift all rows after this one down... // moves refs, formats, merges, etc. - if (lastRow!=null) { - int startrow = lastRow.getRowNumber(); - if (startrow==MAXROWS) startrow--; // 20080925 KSC: can't add more than maxrows - - for(int t = startrow;t>=rownum;t--){ // traverse from last row to current - Row rowtoshift = (Row)rows.get(Integer.valueOf(t)); - if(rowtoshift!=null) { - try{ - this.shiftRow(rowtoshift, 1);// pass original row # for formula shifting + flag - }catch(Exception e){ - Logger.logWarn("Boundsheet.insertRow() failed shifting row: " + t+ " - " + e.toString()); + if (lastRow != null) { + int startrow = lastRow.getRowNumber(); + if (startrow == MAXROWS) + startrow--; // 20080925 KSC: can't add more than maxrows + + for (int t = startrow; t >= rownum; t--) { // traverse from + // last row to + // current + Row rowtoshift = rows.get(Integer.valueOf(t)); + if (rowtoshift != null) { + try { + this.shiftRow(rowtoshift, 1);// pass original + // row # for + // formula + // shifting + + // flag + } catch (Exception e) { + Logger.logWarn("Boundsheet.insertRow() failed shifting row: " + + t + " - " + e.toString()); } - } - } - } - - // we add a blank because a row cannot be empty - roe = this.getRowByNumber(rownum); - if(roe==null){ - int[] rc = {rownum,firstcol}; // added firstcol to not add bad cells at a1 - this.addRecord(Blank.getPrototype(),rc); - roe = this.getRowByNumber(rownum); + } + } } - - }catch(Exception a){Logger.logInfo("Boundsheet.insertRow: Shifting row during Insert failed: " + a);} - }else { - roe = this.getRowByNumber(rownum); - if (roe==null) { - Row r = new Row(rownum, wkbook); - // must also update maxrow on sheet - if (rownum >= this.getMaxRow()) - dimensions.setRowLast(rownum); - r.setSheet(this); - this.addRowRec(r); - roe = this.getRowByNumber(rownum); - roe.resetCacheBytes(); - } - } - return roe; - } + // we add a blank because a row cannot be empty + roe = this.getRowByNumber(rownum); + if (roe == null) { + int[] rc = { rownum, firstcol }; // added firstcol to not + // add bad cells at a1 + this.addRecord(Blank.getPrototype(), rc); + roe = this.getRowByNumber(rownum); + } + + } catch (Exception a) { + Logger.logInfo("Boundsheet.insertRow: Shifting row during Insert failed: " + + a); + } + } else { + roe = this.getRowByNumber(rownum); + if (roe == null) { + Row r = new Row(rownum, wkbook); + // must also update maxrow on sheet + if (rownum >= this.getMaxRow()) + dimensions.setRowLast(rownum); + r.setSheet(this); + this.addRowRec(r); + roe = this.getRowByNumber(rownum); + roe.resetCacheBytes(); + } + } + return roe; + } /** * shifts Merged cells. 10-15-04 -jm - */ // used??? + */ // used??? + @Override public void updateMergedCells() { - if(this.mc.size()<1)return; + if (this.mc.size() < 1) + return; Iterator mcs = this.mc.iterator(); - while(mcs.hasNext())((Mergedcells)mcs.next()).update(); + while (mcs.hasNext()) + ((Mergedcells) mcs.next()).update(); } - - /** associate an existing Row with this Boundsheet - if the row already exists... ignore? + /** associate an existing Row with this Boundsheet + if the row already exists... ignore? */ public void addRowRec(Row r) { int rwn = r.getRowNumber(); if (rows.containsKey(Integer.valueOf(rwn))) { - if(DEBUGLEVEL>2)Logger.logWarn("Sheet.addRow() attempting to add existing row"); + if (DEBUGLEVEL > 2) + Logger.logWarn("Sheet.addRow() attempting to add existing row"); } else { rows.put(Integer.valueOf(rwn), r); if (lastRow == null) { @@ -1390,20 +1649,24 @@ public void addRowRec(Row r) { } } - /** get whether this sheet is hidden upon opening (either regular or "very hidden" - + /** get whether this sheet is hidden upon opening (either regular or "very hidden" + */ + @Override public boolean getHidden() { - if(grbit == VISIBLE)return false; + if (grbit == VISIBLE) + return false; return true; } public boolean getVeryHidden() { return (grbit == VERY_HIDDEN); } - /** set whether this sheet is hidden upon opening + /** set whether this sheet is hidden upon opening + */ + @Override public void setHidden(int gr) { grbit = (short) gr; byte[] bt = ByteTools.shortToLEBytes(grbit); @@ -1412,42 +1675,46 @@ public void setHidden(int gr) { boolean selected = false; - /** returns the selected sheet status + /** returns the selected sheet status */ + @Override public boolean selected() { return selected; } - /** set whether this sheet is selected upon opening - + /** set whether this sheet is selected upon opening + */ + @Override public void setSelected(boolean b) { - if(this.win2!=null)this.win2.setSelected(b); + if (this.win2 != null) + this.win2.setSelected(b); if (b) { this.getWorkBook().setSelectedSheet(this); } selected = b; } - /** associate an Array formula with this Boundsheet + /** associate an Array formula with this Boundsheet */ public void addArrayFormula(Array a) { arrayformulas.add(a); } - /** Returns an array formula for the set address - */ + /** Returns an array formula for the set address + */ public Array getArrayFormula(String addr) { Array form = null; for (int i = 0; i < arrayformulas.size(); i++) { form = (Array) arrayformulas.get(i); - if (form.isInRange(addr)) return form; + if (form.isInRange(addr)) + return form; } return null; } - /** map array formula range reference to the parent array formula address - *
for Array Formula Parent Records only + /** map array formula range reference to the parent array formula address + *
for Array Formula Parent Records only * boundsheet * */ @@ -1470,9 +1737,10 @@ public Object getArrayFormulaParent(int[] rc) { Iterator i = arrFormulaLocs.keySet().iterator(); while (i.hasNext()) { String addr = (String) i.next(); - int[] arrayRC = ExcelTools.getRangeRowCol((String) arrFormulaLocs.get(addr)); - if ((rc[1] >= arrayRC[1]) && (rc[1]<= arrayRC[3]) && - (rc[0] >= arrayRC[0]) && (rc[0]<= arrayRC[2])){ + int[] arrayRC = ExcelTools + .getRangeRowCol((String) arrFormulaLocs.get(addr)); + if ((rc[1] >= arrayRC[1]) && (rc[1] <= arrayRC[3]) + && (rc[0] >= arrayRC[0]) && (rc[0] <= arrayRC[2])) { return arrayRC; } } @@ -1498,160 +1766,178 @@ public boolean isArrayFormulaParent(String addr) { public String getArrayRef(String formAddress) { return (String) arrFormulaLocs.get(formAddress); } - + /** * inserts a col and shifts all of the other rows over one * * @param first zero-based int for the column (0='A') - */ - public void insertCols (int first, int count) { - - ReferenceTracker.updateReferences(first+1, count, this, false); //shift or expand/contract ALL affected references including named ranges - + */ + public void insertCols(int first, int count) { + + ReferenceTracker.updateReferences(first + 1, count, this, false); // shift + // or + // expand/contract + // ALL + // affected + // references + // including + // named + // ranges + // shift the existing columns to the right to make room - for (int colIdx = this.getRealMaxCol(); colIdx >= first; colIdx--) { - this.shiftCol( colIdx, count ); + for (int colIdx = this.getRealMaxCol(); colIdx >= first; colIdx--) { + this.shiftCol(colIdx, count); } - - // update the new colinfos to include the formatting and the width of the inserted col + + // update the new colinfos to include the formatting and the + // width of the inserted col Colinfo movedCol = this.getColInfo(first + count); - if(movedCol!=null){ - for (int i=0; i < count; i++){ - Colinfo newcol = this.getColInfo(first +i); - if(newcol==null){ - this.addColinfo(first+i, first+i, movedCol.getColWidth(), movedCol.getIxfe(), movedCol.getGrbit()); - }else{ + if (movedCol != null) { + for (int i = 0; i < count; i++) { + Colinfo newcol = this.getColInfo(first + i); + if (newcol == null) { + this.addColinfo(first + i, first + i, movedCol + .getColWidth(), movedCol + .getIxfe(), movedCol.getGrbit()); + } else { newcol.setGrbit(movedCol.getGrbit()); newcol.setColWidth(movedCol.getColWidth()); newcol.setIxfe(movedCol.getIxfe()); } - } + } } - + // ensure the sheet bounds are accurate - this.dimensions.setColLast( this.getRealMaxCol() ); + this.dimensions.setColLast(this.getRealMaxCol()); } - - /** get the number of defined rows on this sheet + + /** get the number of defined rows on this sheet */ + @Override public int getNumRows() { return rows.size(); } - /** get the number of defined cells on this sheet + /** get the number of defined cells on this sheet */ + @Override public int getNumCells() { int counter = 0; - Set cellset = (Set) rows.keySet(); + Set cellset = rows.keySet(); Object[] rws = cellset.toArray(); - if(rws.length==0)return 0; + if (rws.length == 0) + return 0; for (int i = 0; i < rws.length; i++) { - Row r = (Row) rows.get((Integer) rws[i]); + Row r = rows.get(rws[i]); counter += r.getNumberOfCells(); } return counter; } - /** get the FastAddVector of columns defined on this sheet + /** get the FastAddVector of columns defined on this sheet */ + @Override public List getColNames() { FastAddVector retvec = new FastAddVector(); for (int x = 0; x < this.getRealMaxCol(); x++) { - String c = (String) ExcelTools.getAlphaVal(x); - retvec.add(c); + String c = ExcelTools.getAlphaVal(x); + retvec.add(c); } return retvec; } - /** get the Number of columns defined on this sheet - */ - public int getNumCols(){return getRealMaxCol();} - + /** get the Number of columns defined on this sheet + */ + @Override + public int getNumCols() { + return getRealMaxCol(); + } - /** - * Add a new colinfo - * @param first The beginning column number (0 based) - * @param last The end column number - * @param width Initial width of the column - * @param ixfe formatting - * @param grbit ?? - * @return Colinfo - */ - public Colinfo createColinfo - (int first, int last, int width, int ixfe, int grbit) { - Colinfo ci = Colinfo.getPrototype(first,last,width,ixfe); - ci.setGrbit(grbit); - ci.setWorkBook(getWorkBook()); - ci.setSheet(this); - this.addColinfo(ci); - int recpos = this.getDimensions().getRecordIndex(); - recpos--; - List sr = this.getSheetRecs(); - // get to last Colinfo record - BiffRec rec= (BiffRec) sr.get(recpos); - // TODO: is it ABSOLUTELY true that if no Colinfos there must be a DefColWidth record???? - while (!(rec instanceof Colinfo) && - !(rec instanceof DefColWidth) && - recpos > 0){ // loop until we find either a colinfo or DEFCOLWIDTH - rec= (BiffRec) sr.get(--recpos); - } - // now position this Colinfo in the proper position within the Colinfo set - int cf= ci.getColFirst(); - while (rec instanceof Colinfo && - ((Colinfo) rec).getColFirst() > cf) { - rec= (BiffRec) sr.get(--recpos); - } - recpos++; - this.getStreamer().addRecordAt(ci,recpos); - return ci; - } - - /** - * Create a colinfo using the values from an existing colinfo - * @param first first col in the colinfo - * @param last last col in the colinfo - * @param template template column - * @return - */ - public Colinfo createColinfo (int first, int last, Colinfo template) { - return this.createColinfo( first, last, template.getColWidth(), - template.getIxfe(), template.getGrbit() ); - } - - public Colinfo createColinfo (int first, int last) { - return this.createColinfo( first, last, - Colinfo.DEFAULT_COLWIDTH, 0, 0 ); - } - - /** get a handle to the Row at the specified - row index - - Zero-based Index. - - ie: row 0 contains cell A1 + /** + * Add a new colinfo + * @param first The beginning column number (0 based) + * @param last The end column number + * @param width Initial width of the column + * @param ixfe formatting + * @param grbit ?? + * @return Colinfo + */ + public Colinfo createColinfo(int first, int last, int width, int ixfe, int grbit) { + Colinfo ci = Colinfo.getPrototype(first, last, width, ixfe); + ci.setGrbit(grbit); + ci.setWorkBook(getWorkBook()); + ci.setSheet(this); + this.addColinfo(ci); + int recpos = this.getDimensions().getRecordIndex(); + recpos--; + List sr = this.getSheetRecs(); + // get to last Colinfo record + BiffRec rec = (BiffRec) sr.get(recpos); + // TODO: is it ABSOLUTELY true that if no Colinfos there + // must be a DefColWidth record???? + while (!(rec instanceof Colinfo) && !(rec instanceof DefColWidth) + && recpos > 0) { // loop until we find either a colinfo or + // DEFCOLWIDTH + rec = (BiffRec) sr.get(--recpos); + } + // now position this Colinfo in the proper position within + // the Colinfo set + int cf = ci.getColFirst(); + while (rec instanceof Colinfo && ((Colinfo) rec).getColFirst() > cf) { + rec = (BiffRec) sr.get(--recpos); + } + recpos++; + this.getStreamer().addRecordAt(ci, recpos); + return ci; + } + /** + * Create a colinfo using the values from an existing colinfo + * @param first first col in the colinfo + * @param last last col in the colinfo + * @param template template column + * @return */ - public Row getRowByNumber(int r) { - return (Row) rows.get(Integer.valueOf(r)); + public Colinfo createColinfo(int first, int last, Colinfo template) { + return this.createColinfo(first, last, template.getColWidth(), template + .getIxfe(), template.getGrbit()); } - + public Colinfo createColinfo(int first, int last) { + return this.createColinfo(first, last, Colinfo.DEFAULT_COLWIDTH, 0, 0); + } - /** get the FastAddVector of rows defined on this sheet + /** get a handle to the Row at the specified + row index + + Zero-based Index. + + ie: row 0 contains cell A1 + + */ + @Override + public Row getRowByNumber(int r) { + return rows.get(Integer.valueOf(r)); + } + + /** get the FastAddVector of rows defined on this sheet * */ + @Override public List getRowNums() { Set e = rows.keySet(); Iterator iter = e.iterator(); FastAddVector rownames = new FastAddVector(); - while(iter.hasNext()){rownames.add(rownames.size(),(Integer)iter.next());} + while (iter.hasNext()) { + rownames.add(rownames.size(), iter.next()); + } return rownames; } /** - * return the map of row in this sheet sorted by row # - * (will be unsorted if insertions and deletions) + * return the map of row in this sheet sorted by row # + * (will be unsorted if insertions and deletions) * @return */ public SortedMap getSortedRows() { @@ -1659,39 +1945,41 @@ public SortedMap getSortedRows() { return sm; } - /** return an Array of the Rows + /** return an Array of the Rows */ + @Override public Row[] getRows() { Map rxs = new TreeMap(rows); // treemap does ordering... LHM does not Row[] rarr = new Row[rxs.size()]; return (Row[]) rxs.values().toArray(rarr); } - /** return a Map of the Rows + /** return a Map of the Rows */ public Map getRowMap() { return rows; } - public boolean fastCellAdds = false; // performance setting which skips safety checks - + public boolean fastCellAdds = false; // performance setting which skips + // safety checks + @Override public BiffRec addValue(Object obj, String address) { return addValue(obj, address, false); } - /** Add a Value record to a WorkSheet. - * This method's purpose is to handle default formatting - * of the cell that is being added, and to do any manipulations - * neccessary to handle mulrks, mulblanks, etc. It is also the - * main entry point of adding values to the boundsheet. These values - * are then passed into createValrec() which - - @param obj the value of the new Cell - @param address the address of the new Cell - + /** Add a Value record to a WorkSheet. + * This method's purpose is to handle default formatting + * of the cell that is being added, and to do any manipulations + * neccessary to handle mulrks, mulblanks, etc. It is also the + * main entry point of adding values to the boundsheet. These values + * are then passed into createValrec() which + + @param obj the value of the new Cell + @param address the address of the new Cell + */ - public BiffRec addValue(Object obj, String address, boolean fixNumberAsString){ + public BiffRec addValue(Object obj, String address, boolean fixNumberAsString) { // first see if there's an existing item int[] rc = ExcelTools.getRowColFromString(address); @@ -1700,17 +1988,18 @@ public BiffRec addValue(Object obj, String address, boolean fixNumberAsString){ } - /** adds a value to the sheet + /** adds a value to the sheet * * @param obj * @param rc * @return */ public BiffRec addValue(Object obj, int[] rc, boolean fixNumberAsString) { - return addValue(obj, rc, this.getWorkBook().getDefaultIxfe(), fixNumberAsString); + return addValue(obj, rc, this.getWorkBook() + .getDefaultIxfe(), fixNumberAsString); } - /** adds a cell to the Sheet + /** adds a cell to the Sheet * * @param obj * @param rc @@ -1721,36 +2010,46 @@ public BiffRec addValue(Object obj, int[] rc, int FORMAT_ID) { return addValue(obj, rc, FORMAT_ID, false); } - /** adds a cell to the Sheet + /** adds a cell to the Sheet * * @param obj * @param rc * @param FORMAT_ID - * @param fixNumberAsString - whether to attempt to convert to a number if it is a NSaS situation + * @param fixNumberAsString - whether to attempt to convert to a number if it is a NSaS situation * @return */ - public BiffRec addValue(Object obj, int[] rc, int FORMAT_ID, boolean fixNumberAsString){ - - if (rc[1] > WorkBook.MAXCOLS) - throw new InvalidRecordException("Cell Column number: " + rc[1] + " is greater than maximum allowable Columns: " + WorkBook.MAXCOLS); - - // sanity checks + public BiffRec addValue(Object obj, int[] rc, int FORMAT_ID, boolean fixNumberAsString) { + + if (rc[1] > WorkBook.MAXCOLS) + throw new InvalidRecordException("Cell Column number: " + rc[1] + + " is greater than maximum allowable Columns: " + + WorkBook.MAXCOLS); + + // sanity checks if (rc[0] > WorkBook.MAXROWS) - throw new InvalidRecordException("Cell Row number: " + rc[0] + " is greater than maximum allowable row: " + WorkBook.MAXROWS); + throw new InvalidRecordException("Cell Row number: " + rc[0] + + " is greater than maximum allowable row: " + + WorkBook.MAXROWS); Row r = this.getRowByNumber(rc[0]); /* - * from Doc: The default cell format is always present in an Excel file, - * described by the XF record with the fixed index 15 (0-based). + * from Doc: The default cell format is always present in an + * Excel file, + * described by the XF record with the fixed index 15 + * (0-based). * - * By default, it uses the worksheet/workbook default cell style, + * By default, it uses the worksheet/workbook default cell + * style, * described by the very first XF record (index 0). */ if (FORMAT_ID <= 0) FORMAT_ID = this.getWorkBook().getDefaultIxfe(); if (FORMAT_ID == this.getWorkBook().getDefaultIxfe()) { - if (this.getColInfo(rc[1])!=null) { /* get default colinfo if possible */ + if (this.getColInfo(rc[1]) != null) { /* + * get default colinfo if + * possible + */ Colinfo co = this.getColInfo(rc[1]); if (co != null && co.getIxfe() != 0) FORMAT_ID = co.getIxfe(); @@ -1767,14 +2066,16 @@ public BiffRec addValue(Object obj, int[] rc, int FORMAT_ID, boolean fixNumberAs BiffRec mycell = this.getCell(rc[0], rc[1]); BiffRec rec = mycell; merge_range = rec.getMergeRange(); - // specific cell format overrides any other formats: if (FORMAT_ID == defaultFormatId) - if (rec.getIxfe() != this.getWorkBook().getDefaultIxfe() && rec.getIxfe() != 0) + // specific cell format overrides any other formats: if + // (FORMAT_ID == defaultFormatId) + if (rec.getIxfe() != this.getWorkBook().getDefaultIxfe() + && rec.getIxfe() != 0) FORMAT_ID = rec.getIxfe(); this.removeCell(mycell); } catch (CellNotFoundException cnfe) { // good! - }; - } + } ; + } // Handle detection of Number stored as Strings Object[] fixed = null; @@ -1797,15 +2098,17 @@ public BiffRec addValue(Object obj, int[] rc, int FORMAT_ID, boolean fixNumberAs // check this does not touch affectedcells this.addRecord(rec, rc); - if (fixed != null){ - FormatHandle f = new FormatHandle(this.wkbook, this.getWorkBook().getDefaultIxfe()); + if (fixed != null) { + FormatHandle f = new FormatHandle(this.wkbook, + this.getWorkBook().getDefaultIxfe()); f.setFormatPattern(fixed[1].toString()); rec.setXFRecord(f.getFormatId()); } rec.resetCacheBytes(); - if (r==null) { // if no row initially, check default row height; if not Excel's default, set row height + if (r == null) { // if no row initially, check default row height; if + // not Excel's default, set row height double rh = this.getDefaultRowHeight(); if (rh != 12.75) {// the default r = this.getRowByNumber(rc[0]); @@ -1816,10 +2119,10 @@ public BiffRec addValue(Object obj, int[] rc, int FORMAT_ID, boolean fixNumberAs } /** - * for numbers stored as strings, try to guess the - * format pattern used, and strip the value to a number + * for numbers stored as strings, try to guess the + * format pattern used, and strip the value to a number * - * TODO: increase sophistication of pattern matching to better guess pattern used + * TODO: increase sophistication of pattern matching to better guess pattern used * * @param s * @return Object[Double value, String formatPattern] @@ -1832,58 +2135,58 @@ Object[] fixNumberStoredAsString(Object s) throws NumberFormatException { String p = ""; // the format pattern boolean matched = false; - - for (NumberAsStringFormat fmts: NumberAsStringFormat.values()){ - if(input.indexOf(fmts.identifier)>-1){ - input = StringTool.strip(input, fmts.identifier); - p = fmts.pattern; - matched = true; - Double d = new Double(input); - d = fmts.adjustValue(d); - Object[] ret = new Object[2]; - ret[0] = d; // value - ret[1] = p; // format pattern - return ret; - } - } + for (NumberAsStringFormat fmts : NumberAsStringFormat.values()) { + if (input.indexOf(fmts.identifier) > -1) { + input = StringTool.strip(input, fmts.identifier); + p = fmts.pattern; + matched = true; + Double d = new Double(input); + d = fmts.adjustValue(d); + Object[] ret = new Object[2]; + ret[0] = d; // value + ret[1] = p; // format pattern + return ret; + } + } throw new NumberFormatException(); } - + /** * A simple enum to store matching strings, format patterns, and value * switches where necessary * */ - private enum NumberAsStringFormat{ - PERCENT ("%", "0%"), - EURO ("€","€#,##0;(€#,##0)"), - YEN ("¥","¥#,##0;(¥#,##0)"), - POUND ("£","£#,##0;(£#,##0)"), - DOLLAR ("$","$#,##0;(€#,##0)"), - ALT_POUND ("₤","₤#,##0;(₤#,##0)"); - - private final String identifier; - private final String pattern; - - private NumberAsStringFormat(String id, String format){ + private enum NumberAsStringFormat { + PERCENT("%", "0%"), + EURO("€", "€#,##0;(€#,##0)"), + YEN("¥", "¥#,##0;(¥#,##0)"), + POUND("£", "£#,##0;(£#,##0)"), + DOLLAR("$", "$#,##0;(€#,##0)"), + ALT_POUND("₤", "₤#,##0;(₤#,##0)"); + + private final String identifier; + private final String pattern; + + private NumberAsStringFormat(String id, String format) { this.identifier = id; this.pattern = format; } - - public String identifier(){ + + public String identifier() { return this.identifier; } - - public String pattern(){ + + public String pattern() { return this.pattern; } - + /** adjust the value where necessary **/ - public double adjustValue(double inputVal){ - if(this.identifier=="%")return inputVal*.01; + public double adjustValue(double inputVal) { + if (this.identifier == "%") + return inputVal * .01; return inputVal; - } + } } /** Creates a valrec (Value containing XLSRecord). This method observes @@ -1902,10 +2205,12 @@ public double adjustValue(double inputVal){ @return partially formed XLS Record. */ private XLSRecord createValrec(Object obj, int[] rc, int FORMAT_ID) { - /*try{ - BiffRec cx = this.getCell(rc[0],rc[1]); - this.removeCell(cx); - }catch(CellNotFoundException e){}*/ + /* + * try{ + * BiffRec cx = this.getCell(rc[0],rc[1]); + * this.removeCell(cx); + * }catch(CellNotFoundException e){} + */ XLSRecord rec = null; if (obj == null) { rec = new Blank(); @@ -1917,24 +2222,44 @@ private XLSRecord createValrec(Object obj, int[] rc, int FORMAT_ID) { if (((String) obj).startsWith("=")) { try { // Logger.logInfo("adding formula"); - rec = FormulaParser.getFormulaFromString((String)obj, this, rc); + rec = FormulaParser + .getFormulaFromString((String) obj, this, rc); - // this is a problem because workbook adds the rec to lastbounds + // this is a problem because workbook adds the rec to + // lastbounds // in other words it will show up on the last sheet. Needed? getWorkBook().addRecord(rec, false); - //getWorkBook().addFormula((Formula)rec); // next best thing methinks... + // getWorkBook().addFormula((Formula)rec); // next best + // thing methinks... } catch (Exception e) { - // 20070212 KSC: add sheet name + address Logger.logWarn("adding new Formula at row:" + rc[0] +" col: " + rc[1] + ":"+obj.toString()+" failed, adding value to worksheet as String."); - throw new FunctionNotSupportedException("Adding new Formula at " + this.getSheetName() + "!" + ExcelTools.formatLocation(rc) + " failed: " + e.toString() + "."); - //rec = Labelsst.getPrototype((String) obj, this.getWorkBook().getSharedStringTable()); + // 20070212 KSC: add sheet name + address + // Logger.logWarn("adding new Formula at row:" + rc[0] +" + // col: " + rc[1] + ":"+obj.toString()+" failed, adding + // value to worksheet as String."); + throw new FunctionNotSupportedException( + "Adding new Formula at " + this.getSheetName() + "!" + + ExcelTools.formatLocation(rc) + + " failed: " + e.toString() + "."); + // rec = Labelsst.getPrototype((String) obj, + // this.getWorkBook().getSharedStringTable()); } - } else if (((String) obj).startsWith("{=")){ // interpret array formulas as well 20090526 KSC: changed from "{" to "{=" tracy vo complex string addition + } else if (((String) obj).startsWith("{=")) { // interpret array + // formulas as well + // 20090526 KSC: + // changed from "{" + // to "{=" tracy vo + // complex string + // addition try { - rec = FormulaParser.getFormulaFromString((String)obj, this, rc); + rec = FormulaParser + .getFormulaFromString((String) obj, this, rc); rec.isFormula = true; } catch (Exception e) { - throw new FunctionNotSupportedException("Adding new Formula at " + this.getSheetName() + "!" + ExcelTools.formatLocation(rc) + " failed: " + e.toString() + "."); + throw new FunctionNotSupportedException( + "Adding new Formula at " + this.getSheetName() + "!" + + ExcelTools.formatLocation(rc) + + " failed: " + e.toString() + "."); } } else if (obj.toString().equalsIgnoreCase("")) { rec = new Blank(); @@ -1953,7 +2278,9 @@ private XLSRecord createValrec(Object obj, int[] rc, int FORMAT_ID) { rec = Boolerr.getPrototype(); ((Boolerr) rec).setBooleanVal(((Boolean) obj).booleanValue()); } else { - double d = new Double(String.valueOf(obj)).doubleValue(); // 20080211 KSC: Double.valueOf(String.valueOf(obj)).doubleValue(); + double d = new Double(String.valueOf(obj)).doubleValue(); // 20080211 + // KSC: + // Double.valueOf(String.valueOf(obj)).doubleValue(); rec = new NumberRec(d); } rec.setWorkBook(getWorkBook()); @@ -1964,17 +2291,19 @@ private XLSRecord createValrec(Object obj, int[] rc, int FORMAT_ID) { return rec; } - /** Add an XLSRecord to a WorkSheet. + /** Add an XLSRecord to a WorkSheet. * - * Creates the container cell for a record, sets the default - * information on the valrec (ie row/col/bs), checks to see if - * there is a container row for the cell, if not, then it creates the - * row. Finally, the cell is passed on to addCellToRowCol where it performs - * final initialization and is added to it's row + * Creates the container cell for a record, sets the default + * information on the valrec (ie row/col/bs), checks to see if + * there is a container row for the cell, if not, then it creates the + * row. Finally, the cell is passed on to addCellToRowCol where it performs + * final initialization and is added to it's row * */ + @Override public void addRecord(BiffRec rec, int[] rc) { - // check to see if there is a BiffRec already at the address add the rec to the Cell, + // check to see if there is a BiffRec already at the address + // add the rec to the Cell, // set as value if it's a val type rec rec.setSheet(this);// create a new BiffRec if none exists @@ -1986,7 +2315,7 @@ public void addRecord(BiffRec rec, int[] rc) { if (!this.fastCellAdds) { Row ro = null; - ro = (Row) rows.get(Integer.valueOf(rc[0])); + ro = rows.get(Integer.valueOf(rc[0])); if (ro == null) ro = this.addNewRow(rec); @@ -1997,8 +2326,10 @@ public void addRecord(BiffRec rec, int[] rc) { try { this.addCell((CellRec) rec); } catch (ArrayIndexOutOfBoundsException ax) { - Logger.logErr("Boundsheet.addRecord() failed. Column "+rc[1]+" is greater than Maximum column count"); - throw new InvalidRecordException("Adding cell failed. Column "+rc[1]+" is greater than the maximum column limit."); + Logger.logErr("Boundsheet.addRecord() failed. Column " + rc[1] + + " is greater than Maximum column count"); + throw new InvalidRecordException("Adding cell failed. Column " + + rc[1] + " is greater than the maximum column limit."); } } @@ -2006,27 +2337,32 @@ public void addRecord(BiffRec rec, int[] rc) { * * @param cell */ - public void addCell (CellRec cell) - { - cellsByRow.put( cell, cell ); - cellsByCol.put( cell, cell ); - Row row = rows.get( Integer.valueOf( cell.getRowNumber() ) ); - if (null == row) row = this.addNewRow( cell ); - row.addCell( cell ); - if (cell != null) - cell.setSheet(this); - this.updateDimensions(cell.getRowNumber(), cell.getColNumber()); + @Override + public void addCell(CellRec cell) { + cellsByRow.put(cell, cell); + cellsByCol.put(cell, cell); + Row row = rows.get(Integer.valueOf(cell.getRowNumber())); + if (null == row) + row = this.addNewRow(cell); + row.addCell(cell); + if (cell != null) + cell.setSheet(this); + this.updateDimensions(cell.getRowNumber(), cell.getColNumber()); } - private boolean copypriorformats = true; - public void setCopyPriorCellFormats(boolean f){this.copypriorformats = f;} + + @Override + public void setCopyPriorCellFormats(boolean f) { + this.copypriorformats = f; + } private boolean copyPriorCellFormatForNewCells(BiffRec c) { int row = c.getRowNumber() + 1; // get the prior cell addy String cnm = ExcelTools.getAlphaVal(c.getColNumber()); BiffRec ch = this.getCell(cnm + row); // try it... - if(ch == null)return false; + if (ch == null) + return false; c.setIxfe(ch.getIxfe()); return true; } @@ -2039,18 +2375,16 @@ public int getRealMaxCol() { return this.maximumCellCol; } - - /** * Add a new colinfo - * @param begCol The beginning column number (0 based) - * @param endCol The end column number - * @param width Initial width of the column - * @param ixfe formatting - * @param grbit ?? + * @param begCol The beginning column number (0 based) + * @param endCol The end column number + * @param width Initial width of the column + * @param ixfe formatting + * @param grbit ?? * @return Colinfo */ - public Colinfo addColinfo(int begCol, int endCol, int width, int ixfe, int grbit) { + public Colinfo addColinfo(int begCol, int endCol, int width, int ixfe, int grbit) { Colinfo ci = Colinfo.getPrototype(begCol, endCol, width, ixfe); ci.setGrbit(grbit); ci.setWorkBook(getWorkBook()); @@ -2058,52 +2392,57 @@ public Colinfo addColinfo(int begCol, int endCol, int width, int ixfe, int grbit this.addColinfo(ci); int recpos = this.getDimensions().getRecordIndex(); recpos--; - List sr = this.getSheetRecs(); + List sr = this.getSheetRecs(); // get to last Colinfo record - BiffRec rec = (BiffRec) sr.get(recpos); - // TODO: is it ABSOLUTELY true that if no Colinfos there must be a DefColWidth record???? - while (!(rec instanceof Colinfo) && - !(rec instanceof DefColWidth) && - recpos > 0){ // loop until we find either a colinfo or DEFCOLWIDTH - rec = (BiffRec) sr.get(--recpos); + BiffRec rec = sr.get(recpos); + // TODO: is it ABSOLUTELY true that if no Colinfos there + // must be a DefColWidth record???? + while (!(rec instanceof Colinfo) && !(rec instanceof DefColWidth) + && recpos > 0) { // loop until we find either a colinfo or + // DEFCOLWIDTH + rec = sr.get(--recpos); } - // now position this Colinfo in the proper position within the Colinfo set + // now position this Colinfo in the proper position within + // the Colinfo set int cf = ci.getColFirst(); - while (rec instanceof Colinfo && - ((Colinfo) rec).getColFirst() > cf) { - rec = (BiffRec) sr.get(--recpos); + while (rec instanceof Colinfo && ((Colinfo) rec).getColFirst() > cf) { + rec = sr.get(--recpos); } recpos++; this.getStreamer().addRecordAt(ci, recpos); return ci; } - /** get a colinfo by name - */ - public Colinfo getColinfo(String c){ - return this.getColInfo( ExcelTools.getIntVal( c ) ); - } + /** get a colinfo by name + */ + @Override + public Colinfo getColinfo(String c) { + return this.getColInfo(ExcelTools.getIntVal(c)); + } - /** get the Collection of Colinfos - */ - public Collection getColinfos(){ - return Collections.unmodifiableCollection( colinfos.values() ); - } + /** get the Collection of Colinfos + */ + @Override + public Collection getColinfos() { + return Collections.unmodifiableCollection(colinfos.values()); + } - private int maximumCellCol = -1; - private int maximumCellRow = -1; + private int maximumCellCol = -1; + private int maximumCellRow = -1; /** - Moves a cell location from one address to another + Moves a cell location from one address to another */ public void moveCell(String startaddr, String endaddr) { - BiffRec c = (BiffRec) getCell(startaddr); + BiffRec c = getCell(startaddr); if (c.getOpcode() == WorkBookFactory.RK) { try { Double d = new Double(c.getDblVal()); this.removeCell(c); this.addValue(d, endaddr); - }catch(Exception e){Logger.logInfo("Boundsheet.moveCell() error :" + e);} + } catch (Exception e) { + Logger.logInfo("Boundsheet.moveCell() error :" + e); + } } else { int[] s = ExcelTools.getRowColFromString(endaddr); c.setCol((short) s[1]); @@ -2114,9 +2453,9 @@ public void moveCell(String startaddr, String endaddr) { } /** - Moves a cell location from one address to another, - without any clearing of previous locations. This is used in sorting - and other cell movements where we do not want to delete from starting address + Moves a cell location from one address to another, + without any clearing of previous locations. This is used in sorting + and other cell movements where we do not want to delete from starting address */ public void updateCellReferences(BiffRec c, String endaddr) { if (c.getOpcode() == WorkBookFactory.RK) { @@ -2124,7 +2463,9 @@ public void updateCellReferences(BiffRec c, String endaddr) { Double d = new Double(c.getDblVal()); this.removeCell(c); this.addValue(d, endaddr); - }catch(Exception e){Logger.logInfo("Boundsheet.moveCell() error :" + e);} + } catch (Exception e) { + Logger.logInfo("Boundsheet.moveCell() error :" + e); + } } else { int[] s = ExcelTools.getRowColFromString(endaddr); c.setCol((short) s[1]); @@ -2133,21 +2474,22 @@ public void updateCellReferences(BiffRec c, String endaddr) { } } - - /** Gets a cell on this sheet by its Excel A1-style address. - * @param address the A1-style address of the cell to retrieve - * @return the cell record - * or null if no cell exists at the given address - * @deprecated Use {@link #getCell(int, int)} instead. - */ - public BiffRec getCell (String address) { - int[] rc = ExcelTools.getRowColFromString( address ); - try { - return this.getCell( rc[ 0 ], rc[ 1 ] ); - } catch (CellNotFoundException ex) { - return null; - } - } + /** Gets a cell on this sheet by its Excel A1-style address. + * @param address the A1-style address of the cell to retrieve + * @return the cell record + * or null if no cell exists at the given address + * @deprecated Use {@link #getCell(int, int)} instead. + */ + @Deprecated + @Override + public BiffRec getCell(String address) { + int[] rc = ExcelTools.getRowColFromString(address); + try { + return this.getCell(rc[0], rc[1]); + } catch (CellNotFoundException ex) { + return null; + } + } /** Gets a cell on this sheet by its row and column indexes. * @param row the zero-based index of the cell's parent row @@ -2155,28 +2497,29 @@ public BiffRec getCell (String address) { * @return the cell record at the given address * @throws CellNotFoundException if no cell exists at the given address */ - public BiffRec getCell (int row, int col) - throws CellNotFoundException { + @Override + public BiffRec getCell(int row, int col) throws CellNotFoundException { // get the nearest entry from the cell map - BiffRec theCell = cellsByRow.get( - new CellAddressible.Reference( row, col ) ); + BiffRec theCell = cellsByRow + .get(new CellAddressible.Reference(row, col)); if (null == theCell) - throw new CellNotFoundException( this.sheetname, row, col ); - - if (theCell!=null && theCell.getOpcode()==MULBLANK) { + throw new CellNotFoundException(this.sheetname, row, col); + + if (theCell != null && theCell.getOpcode() == MULBLANK) { ((Mulblank) theCell).setCurrentCell((short) col); } return theCell; } - - /** get an array of all cells for this worksheet + /** get an array of all cells for this worksheet */ - public BiffRec[] getCells() { - Collection cells = cellsByRow.values(); - return cells.toArray( new BiffRec[ cells.size() ] ); - } + @Override + public BiffRec[] getCells() { + Collection cells = cellsByRow.values(); + return cells.toArray(new BiffRec[cells.size()]); + } + @Override public void addMergedCellsRec(Mergedcells r) { mc.add(r); } @@ -2190,7 +2533,9 @@ private ArrayList getBuiltInNames() { ArrayList retlist = new ArrayList(); Name[] ns = this.getWorkBook().getNames(); for (int i = 0; i < ns.length; i++) { - if (ns[i].isBuiltIn() && ((ns[i].getIxals() == this.getSheetNum()+1)||(ns[i].getItab() == this.getSheetNum()+1)) ) { + if (ns[i].isBuiltIn() + && ((ns[i].getIxals() == this.getSheetNum() + 1) + || (ns[i].getItab() == this.getSheetNum() + 1))) { retlist.add(ns[i]); } } @@ -2198,9 +2543,9 @@ private ArrayList getBuiltInNames() { } /** - * Get the print area or titles name rec for this - * boundsheet, return null if not exists - + * Get the print area or titles name rec for this + * boundsheet, return null if not exists + * * @return */ @@ -2216,9 +2561,9 @@ protected Name getPrintAreaNameRec(byte type) { } /** - * Get the print area name rec for this - * boundsheet, return null if not exists - + * Get the print area name rec for this + * boundsheet, return null if not exists + * * @return */ @@ -2240,10 +2585,13 @@ public String getPrintArea() { Stack s = n.getExpression(); for (int x = 0; x < s.size(); x++) { Ptg p = (Ptg) s.get(x); -/* if (p instanceof PtgArea3d) {// can be other than ptgarea ... - ((PtgRef)p).clearLocationCache();// why?? - return p.toString(); - }*/ + /* + * if (p instanceof PtgArea3d) {// can be other than ptgarea + * ... + * ((PtgRef)p).clearLocationCache();// why?? + * return p.toString(); + * } + */ ret += p.toString(); } return ret; @@ -2277,21 +2625,22 @@ public void setPrintArea(String range) { } /** - * Set the print titles for this worksheet= row(s) or col(s) to repeat at the top of each page + * Set the print titles for this worksheet= row(s) or col(s) to repeat at the top of each page */ public void setPrintTitles(String range) { setPrintArea(range, Name.PRINT_TITLES); } /** - * adds the _FILTERDATABASE name necessary for AutoFilter - * if not already presetn + * adds the _FILTERDATABASE name necessary for AutoFilter + * if not already presetn */ private void addFilterDatabase() { List names = this.getBuiltInNames(); Name n = null; for (int i = 0; i < names.size() && n == null; i++) { - if (((Name) names.get(i)).getBuiltInType() == Name._FILTER_DATABASE) { + if (((Name) names.get(i)) + .getBuiltInType() == Name._FILTER_DATABASE) { n = (Name) names.get(i); } } @@ -2299,15 +2648,20 @@ private void addFilterDatabase() { try { n = new Name(this.getWorkBook(), "Built-in: _FILTER_DATABASE"); n.setBuiltIn(Name._FILTER_DATABASE); - int xref = this.getWorkBook().getExternSheet(true).insertLocation(this.getSheetNum(), this.getSheetNum()); + int xref = this.getWorkBook().getExternSheet(true) + .insertLocation(this.getSheetNum(), this.getSheetNum()); n.setExternsheetRef(xref); n.updateSheetReferences(this); n.setSheet(this); n.setIxals((short) (this.getSheetNum()/* +1 */)); n.setItab((short) (this.getSheetNum() + 1)); - String loc= ExcelTools.formatLocation(new int[] { this.getMinRow(), this.getMinCol(), this.getMaxRow()-1, this.getMaxCol()-1}, false, false); + String loc = ExcelTools + .formatLocation(new int[] { this.getMinRow(), + this.getMinCol(), this.getMaxRow() - 1, + this.getMaxCol() - 1 }, false, false); Stack s = new Stack(); - s.push(PtgRef.createPtgRefFromString(this.getSheetName() + "!" + loc, n)); + s.push(PtgRef.createPtgRefFromString(this.getSheetName() + "!" + + loc, n)); n.setExpression(s); } catch (Exception e) { @@ -2323,14 +2677,14 @@ private void removeFilterDatabase() { Name n = null; try { for (int i = 0; i < names.size() && n == null; i++) { - if (((Name) names.get(i)).getBuiltInType() == Name._FILTER_DATABASE) { + if (((Name) names.get(i)) + .getBuiltInType() == Name._FILTER_DATABASE) { n = (Name) names.get(i); this.getWorkBook().removeName(n); break; } } - } catch (Exception e) { - } + } catch (Exception e) {} } /** @@ -2344,10 +2698,10 @@ public void setPrintArea(String printarea, byte type) { for (int x = 0; x < s.size(); x++) { Ptg p = (Ptg) s.get(x); if (p instanceof PtgRef) { - Ptg ptg= PtgRef.createPtgRefFromString(printarea, n); + Ptg ptg = PtgRef.createPtgRefFromString(printarea, n); s.remove(x); s.add(x, ptg); - } + } } return; } @@ -2355,18 +2709,19 @@ public void setPrintArea(String printarea, byte type) { // create the name try { String t; - if (type==Name.PRINT_AREA){ - t= "PRINT_AREA"; - }else{ - t= "PRINT_TITLES"; - } + if (type == Name.PRINT_AREA) { + t = "PRINT_AREA"; + } else { + t = "PRINT_TITLES"; + } Name n = new Name(this.getWorkBook(), "Built-in: " + t); n.setBuiltIn(type); - int xref = this.getWorkBook().getExternSheet(true).insertLocation(this.getSheetNum(), this.getSheetNum()); + int xref = this.getWorkBook().getExternSheet(true) + .insertLocation(this.getSheetNum(), this.getSheetNum()); n.setExternsheetRef(xref); n.updateSheetReferences(this); n.setSheet(this); - n.setIxals((short)(this.getSheetNum())); + n.setIxals((short) (this.getSheetNum())); n.setItab((short) (this.getSheetNum() + 1)); Stack s = new Stack(); Ptg p = PtgRef.createPtgRefFromString(printarea, n); @@ -2385,7 +2740,7 @@ public ArrayList getNotes() { ArrayList notes = new ArrayList(); int idx = this.getIndexOf(NOTE); while (idx > -1) { - notes.add((Note) SheetRecs.get(idx++)); + notes.add(SheetRecs.get(idx++)); if (((BiffRec) SheetRecs.get(idx)).getOpcode() != NOTE) break; } @@ -2393,23 +2748,29 @@ public ArrayList getNotes() { } /*** - */ + */ + @Override public Mergedcells getMergedCellsRec() { if (mc.size() == 0) return null; // 20081031 KSC- don't automatically add new! - return (Mergedcells)this.getMergedCellsRecs().get(this.getMergedCellsRecs().size()-1); + return (Mergedcells) this.getMergedCellsRecs() + .get(this.getMergedCellsRecs().size() - 1); } + @Override public List getMergedCellsRecs() { return mc; - /* 20081031 don't add a merged cell rec automatically - if (mc.size()>0) { - return mc; - } - Mergedcells mec = (Mergedcells)Mergedcells.getPrototype(); - mec.setSheet(this); - this.getStreamer().addRecordAt(mec, this.getSheetRecs().size()-1); - this.addMergedCellsRec(mec); + /* + * 20081031 don't add a merged cell rec automatically + * if (mc.size()>0) { + * return mc; + * } + * Mergedcells mec = + * (Mergedcells)Mergedcells.getPrototype(); + * mec.setSheet(this); + * this.getStreamer().addRecordAt(mec, + * this.getSheetRecs().size()-1); + * this.addMergedCellsRec(mec); */ } @@ -2437,26 +2798,35 @@ public List getMergedCells() { * return truth of "has merged cells" * @return */ - public boolean hasMergedCells() { return mc.size()>0; } - /** get the name of the sheet + public boolean hasMergedCells() { + return mc.size() > 0; + } + + /** get the name of the sheet */ + @Override public String getSheetName() { return sheetname; } - /** get the name of the sheet + /** get the name of the sheet */ - public String toString(){return getSheetName();} - + @Override + public String toString() { + return getSheetName(); + } - /** initialize the SheetImpl with data from - the byte array. + /** initialize the SheetImpl with data from + the byte array. */ + @Override public void init() { super.init(); - int lt = ByteTools.readInt(this.getByteAt(0),this.getByteAt(1),this.getByteAt(2),this.getByteAt(3)); + int lt = ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), this + .getByteAt(2), this.getByteAt(3)); - // this is the index used by the BOF's Sheet to associate the record + // this is the index used by the BOF's Sheet to associate + // the record lbPlyPos = lt; grbit = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); if (DEBUGLEVEL > 9) { @@ -2468,26 +2838,32 @@ public void init() { byte[] namebytes = this.getBytesAt(8, this.getLength() - 12); try { if (grbitChr == 0x1) { - sheetname = new String(namebytes, WorkBookFactory.UNICODEENCODING); + sheetname = new String(namebytes, + WorkBookFactory.UNICODEENCODING); } else { - sheetname = new String(namebytes, WorkBookFactory.DEFAULTENCODING); + sheetname = new String(namebytes, + WorkBookFactory.DEFAULTENCODING); } } catch (UnsupportedEncodingException e) { - Logger.logInfo("Boundsheet.init() Unsupported Encoding error: " + e);} - if(DEBUGLEVEL > 9) Logger.logInfo("Sheet name: " + sheetname); - ooxmlObjects= new ArrayList(); // possible that boundsheet is created by readObject and therefore ooxmlObjects will not be set + Logger.logInfo("Boundsheet.init() Unsupported Encoding error: " + + e); } + if (DEBUGLEVEL > 9) + Logger.logInfo("Sheet name: " + sheetname); + ooxmlObjects = new ArrayList(); // possible that boundsheet is created + // by readObject and therefore + // ooxmlObjects will not be set + } - - - /** change the displayed name of the sheet - - Affects the following byte values: - 10 cch 1 Length of sheet name - 11 grbitChr 1 Compressed/Uncompressed Unicode - 12 rgch var Sheet name - + /** change the displayed name of the sheet + + Affects the following byte values: + 10 cch 1 Length of sheet name + 11 grbitChr 1 Compressed/Uncompressed Unicode + 12 rgch var Sheet name + */ + @Override public void setSheetName(String newname) { cch = (byte) newname.length(); @@ -2506,7 +2882,8 @@ public void setSheetName(String newname) { } } catch (UnsupportedEncodingException e) { namebytes = newname.getBytes(); - Logger.logWarn("UnsupportedEncodingException in setting sheet name: " + e + " falling back to system default."); + Logger.logWarn("UnsupportedEncodingException in setting sheet name: " + + e + " falling back to system default."); } byte[] newdata = new byte[namebytes.length + 8]; if (data == null) @@ -2522,9 +2899,10 @@ public void setSheetName(String newname) { } /** - Returns a serialized copy of this Boundsheet + Returns a serialized copy of this Boundsheet * @throws IOException */ + @Override public byte[] getSheetBytes() throws IOException { this.setLocalRecs(); ObjectOutputStream obs = null; @@ -2538,11 +2916,12 @@ public byte[] getSheetBytes() throws IOException { return b; } - /** prior to serializing the worksheet, - we need to initialize the records which belong to this sheet - instance. - + /** prior to serializing the worksheet, + we need to initialize the records which belong to this sheet + instance. + */ + @Override public void setLocalRecs() { localrecs = new CompatibleVector(); @@ -2561,7 +2940,8 @@ public void setLocalRecs() { Logger.logWarn("Setting Boundsheet records problem: " + e); } } - // add the charts to the boundsheet, as they are stored in the workbook normally. (why?) + // add the charts to the boundsheet, as they are stored in + // the workbook normally. (why?) charts.clear(); Chart[] chts = this.getWorkBook().getCharts(); for (int i = 0; i < chts.length; i++) { @@ -2572,9 +2952,9 @@ public void setLocalRecs() { } /** - * This seems incorrect, should we not be just returning - * charts for the boundsheet in question? - + * This seems incorrect, should we not be just returning + * charts for the boundsheet in question? + * * @return */ @@ -2582,15 +2962,17 @@ public List getCharts() { return charts; } - /** get the type of sheet as a short + /** get the type of sheet as a short */ + @Override public short getSheetType() { return grbit; } - /** get the type of sheet as a string + /** get the type of sheet as a string */ + @Override public String getSheetTypeString() { switch (grbit) { case SHEET_DIALOG: @@ -2624,8 +3006,11 @@ public Chart addChart(byte[] inbytes, short[] coords) { return this.addChart(inbytes, "useDefault", coords); } - /* Inserts a serialized boundsheet into the workbook, and changes the name. + /* + * Inserts a serialized boundsheet into the workbook, and + * changes the name. */ + @Override public Chart addChart(byte[] inbytes, String NewChartName, short[] coords) { Chart destChart = null; // Deserialize bytes @@ -2634,21 +3019,37 @@ public Chart addChart(byte[] inbytes, String NewChartName, short[] coords) { BufferedInputStream bufstr = new BufferedInputStream(bais); ObjectInputStream o = new ObjectInputStream(bufstr); destChart = (Chart) o.readObject(); - } catch (Exception e) { Logger.logInfo("Boundsheet.addChart() failed:" + e);} + } catch (Exception e) { + Logger.logInfo("Boundsheet.addChart() failed:" + e); + } if (destChart != null) { // got chart - if(!NewChartName.equals("useDefault"))destChart.setTitle(NewChartName); // set new name -// why do we need this??? shouldn't it be already set?? destChart.getChartFormat().setParentChart(destChart); // make same as WorkBook.addChart + if (!NewChartName.equals("useDefault")) + destChart.setTitle(NewChartName); // set new name + // why do we need this??? shouldn't it be already set?? + // destChart.getChartFormat().setParentChart(destChart); // + // make same as WorkBook.addChart destChart.setSheet(this); - // BUGTRACKER 2372: chart bounds are dependent upon row+col sizes so use coordinates (which are row/col independent) + // BUGTRACKER 2372: chart bounds are dependent upon row+col + // sizes so use coordinates (which are row/col independent) // does it makes sense to only set h + w and NOT x and y short[] origCoords = destChart.getCoords(); - coords[0]= origCoords[0]; // don't set X and Y (keep to original row and column + coords[0] = origCoords[0]; // don't set X and Y (keep to original + // row and column coords[1] = origCoords[1]; destChart.setCoords(coords); // but set w + h - destChart.setId(this.lastObjId+1); // 20100210 KSC: track last obj id per sheet ... + destChart.setId(this.lastObjId + 1); // 20100210 KSC: track last obj + // id per sheet ... HashMap localFonts = null; // fonts currently in workbook - if (this.getTransferFonts()!=null && this.getTransferFonts().size()>0) { // then must translate old font indexes to new font indexes - localFonts = (HashMap)this.getWorkBook().getFontRecsAsXML(); // fonts in this workbook + if (this.getTransferFonts() != null + && this.getTransferFonts().size() > 0) { // then must + // translate old + // font indexes + // to new font + // indexes + localFonts = (HashMap) this.getWorkBook().getFontRecsAsXML(); // fonts + // in + // this + // workbook } List recs = destChart.getXLSrecs(); for (int i = 0; i < recs.size(); i++) { @@ -2659,17 +3060,24 @@ public Chart addChart(byte[] inbytes, String NewChartName, short[] coords) { wkbook.addChartUpdateMsodg((MSODrawing) rec, this); continue; } - if (!(rec instanceof Bof)) // TODO: error/problem with the BOF record!!! + if (!(rec instanceof Bof)) // TODO: error/problem with the BOF + // record!!! rec.init(); if (rec instanceof Dimensions) destChart.setDimensions((Dimensions) rec); - if (rec instanceof FontBasis) { // 20090506 KSC: fontbasis font indexes link to subsequent text displays [added for BUGTRACKER 2372] + if (rec instanceof FontBasis) { // 20090506 KSC: fontbasis font + // indexes link to subsequent + // text displays [added for + // BUGTRACKER 2372] int fid = ((FontBasis) rec).getFontIndex(); - // see if must translate old font indexes to new font indexes + // see if must translate old font indexes to new font + // indexes fid = translateFontIndex(fid, localFonts); ((FontBasis) rec).setFontIndex(fid); } - if (rec instanceof Fontx) { // 20080911 KSC: must handle out of bounds font references upon chart copies [JPM BugTracker 1434] + if (rec instanceof Fontx) { // 20080911 KSC: must handle out of + // bounds font references upon chart + // copies [JPM BugTracker 1434] int fid = ((Fontx) rec).getIfnt(); if (fid > 0) fid = translateFontIndex(fid, localFonts); @@ -2677,9 +3085,13 @@ public Chart addChart(byte[] inbytes, String NewChartName, short[] coords) { } try { ((GenericChartObject) rec).setParentChart(destChart); - }catch (ClassCastException e) { // Scl, Obj and others are not chart objects + } catch (ClassCastException e) { // Scl, Obj and others are not + // chart objects } - //try{Logger.logInfo("Boundsheet Added new Chart rec:" + rec);}catch(Exception e){Logger.logWarn("Boundsheet.addChart() could not get String for rec: "+ rec.getCellAddress());} + // try{Logger.logInfo("Boundsheet Added new Chart rec:" + + // rec);}catch(Exception + // e){Logger.logWarn("Boundsheet.addChart() could not get + // String for rec: "+ rec.getCellAddress());} } this.wkbook.getChartVect().add(destChart); } @@ -2694,7 +3106,8 @@ public Chart addChart(byte[] inbytes, String NewChartName, short[] coords) { */ int translateFontIndex(int fid, HashMap localFonts) { if (transferFonts != null && fid - 1 < transferFonts.size()) { - // must translate fid to corrent font index for current fonts + // must translate fid to corrent font index for current + // fonts // translate font style and see if already present Font thisFont = (Font) transferFonts.get(fid - 1); String xmlFont = "<" + thisFont.getXML() + "/>"; @@ -2706,15 +3119,16 @@ int translateFontIndex(int fid, HashMap localFonts) { localFonts.put(xmlFont, Integer.valueOf(fid)); } } - if (fid > this.getWorkBook().getNumFonts()) { // if fid is still incorrect, set to 0 + if (fid > this.getWorkBook().getNumFonts()) { // if fid is still + // incorrect, set to 0 fid = 0; } return fid; } /** - * populateForTransfer is a method that takes all of the shared resources (SST, XF, Font, etc) records and - * verifies that they are populated for use in a destination workbook + * populateForTransfer is a method that takes all of the shared resources (SST, XF, Font, etc) records and + * verifies that they are populated for use in a destination workbook * */ public void populateForTransfer() { @@ -2753,18 +3167,22 @@ protected List getTransferFonts() { return transferFonts; } + @Override public Guts getGuts() { return guts; } + @Override public void setGuts(Guts g) { guts = g; } + @Override public void setWsBool(WsBool ws) { wsbool = ws; } + @Override public WsBool getWsBool() { return wsbool; } @@ -2772,12 +3190,13 @@ public WsBool getWsBool() { /** * @return Returns the localrecs. */ + @Override public List getLocalRecs() { return localrecs; } /** - * @param localrecs The localrecs to set. + * @param localrecs The localrecs to set. */ public void setLocalRecs(FastAddVector l) { this.localrecs = l; @@ -2790,13 +3209,15 @@ public void setSheetRecs(AbstractList shtRecs) { /** * @return Returns the grbitChr. */ + @Override public byte getGrbitChr() { return grbitChr; } /** - * @param grbitChr The grbitChr to set. + * @param grbitChr The grbitChr to set. */ + @Override public void setGrbitChr(byte gb) { this.grbitChr = gb; } @@ -2809,23 +3230,23 @@ public Selection getLastselection() { } /** - * @param lastselection The lastselection to set. + * @param lastselection The lastselection to set. */ public void setLastselection(Selection lastselection) { this.lastselection = lastselection; } /** - * Set to true to turn off checking for existing cells, conditional formats and merged ranges in order to - * accelerate adding new cells + * Set to true to turn off checking for existing cells, conditional formats and merged ranges in order to + * accelerate adding new cells * - * @param fastCellAdds The fastCellAdds to set. + * @param fastCellAdds The fastCellAdds to set. */ public void setFastCellAdds(boolean fastCellAdds) { this.fastCellAdds = fastCellAdds; } - /** scl is for zoom + /** scl is for zoom * * @return */ @@ -2850,13 +3271,17 @@ public void setScl(Scl s) { * Set whether to shift formula cells inclusively * i.e. if inserting row 5, shift formula D5:D6 down or D6:D7 OR shift inclusive by D5:D7 */ - public void setShiftRule(boolean bShiftInclusive) { formulaShiftInclusive= bShiftInclusive; } + public void setShiftRule(boolean bShiftInclusive) { + formulaShiftInclusive = bShiftInclusive; + } + /** * return whether to shift (formula cells, named ranges) "inclusive" or not * @return */ - public boolean isShiftInclusive() { return formulaShiftInclusive; } - + public boolean isShiftInclusive() { + return formulaShiftInclusive; + } /** Gets the printer setup handle for this sheet. */ @@ -2874,7 +3299,8 @@ public List getPrintRecs() { /** Adds a print-related record to the list of said. */ public void addPrintRec(BiffRec record) { - if (printRecs == null) printRecs = new ArrayList(); + if (printRecs == null) + printRecs = new ArrayList(); printRecs.add(record); } @@ -2912,22 +3338,27 @@ public Note createNote(String address, String txt, String author) { int[] coords = ExcelTools.getRowColFromString(address); int insertIndex = this.insertMSOObjectsForNote(coords); - // after mso/obj/mso, now add txo/continue/continue and note record + // after mso/obj/mso, now add txo/continue/continue and note + // record Txo t = (Txo) Txo.getPrototype(); t.setSheet(this); this.SheetRecs.add(insertIndex++, t); - this.SheetRecs.add(insertIndex++, t.text); // add the associated Continues that defines the text + this.SheetRecs.add(insertIndex++, t.text); // add the associated + // Continues that defines + // the text t.text.setPredecessor(t); // link this continues to it's predecessor Continue c = Continue.getBasicFormattingRunContinues(); c.setPredecessor(t); // TODO: is this correct???? c.setSheet(this); - this.SheetRecs.add(insertIndex++, c); // and add associated formatting runs + this.SheetRecs.add(insertIndex++, c); // and add associated formatting + // runs try { t.setStringVal(txt); // must do *after* adding continues } catch (IllegalArgumentException e) { Logger.logErr(e.toString()); } - // after (mso/obj/mso/txo/continue/continue) * n, note records are listed in order + // after (mso/obj/mso/txo/continue/continue) * n, note + // records are listed in order insertIndex = this.getIndexOf(WINDOW2); Note n = (Note) Note.getPrototype(author); n.setId(this.lastObjId); // same as Obj record above @@ -2941,18 +3372,18 @@ public Note createNote(String address, String txt, String author) { * Handles the MSO manipulations necessary for creating a note record * * // For each note: - // [msodrawing - // obj - ftNts note - // msodrawing - attached shape - // Txo (text object), continue, continue] x n - // [note 1] x n - // window 2 - // ************************************************************************************ - // NOTE: - // SOME TEMPLATES HAVE [obj= ftNts, Continue, Txo, continue, continue, continue] - // MORE INFO (get this): - // Obj, Continue= 2nd MSO!!!, Txo, Continue, Continue, Continue= 1st MSO!!! - // ************************************************************************************ + // [msodrawing + // obj - ftNts note + // msodrawing - attached shape + // Txo (text object), continue, continue] x n + // [note 1] x n + // window 2 + // ************************************************************************************ + // NOTE: + // SOME TEMPLATES HAVE [obj= ftNts, Continue, Txo, continue, continue, continue] + // MORE INFO (get this): + // Obj, Continue= 2nd MSO!!!, Txo, Continue, Continue, Continue= 1st MSO!!! + // ************************************************************************************ * * @param coords rowcol of the note record * @return insertion index for note @@ -2965,7 +3396,8 @@ private int insertMSOObjectsForNote(int[] coords) { msodg.initNewMSODrawingGroup(); } - // insert either above first NOTE record or before WINDOW2 and certain other XLSRECORDS + // insert either above first NOTE record or before WINDOW2 + // and certain other XLSRECORDS insertIndex = this.getIndexOf(NOTE); if (insertIndex == -1) // no existing notes - find proper insert index insertIndex = this.getIndexOf(WINDOW2); @@ -2981,11 +3413,13 @@ private int insertMSOObjectsForNote(int[] coords) { break; } if (rec.getSOLVERContainerLength() == 0) - break; // solver containers must be last, apparently ... sigh ... + break; // solver containers must be last, apparently ... + // sigh ... // else - // Logger.logInfo("Boundsheet.InsertMSOObjectsForNote. SOLVER CONTAINER ENCOUNTED"); - }else if (opc == OBJ || opc == CONTINUE - || opc == DIMENSIONS || opc == 0x866 || opc == 0x1C2) + // Logger.logInfo("Boundsheet.InsertMSOObjectsForNote. + // SOLVER CONTAINER ENCOUNTED"); + } else if (opc == OBJ || opc == CONTINUE || opc == DIMENSIONS + || opc == 0x866 || opc == 0x1C2) break; insertIndex--; } @@ -3003,22 +3437,32 @@ private int insertMSOObjectsForNote(int[] coords) { msoDrawing.createCommentBox(coords[0], coords[1]); this.SheetRecs.add(insertIndex++, msoDrawing); msoheader.numShapes++; - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the msodrawinggroup set of recs + msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the + // msodrawinggroup set of recs // object record which defines a basic note - Obj obj = (Obj) Obj.getBasicObjRecord(Obj.otNote, ++this.lastObjId); // create a note object + Obj obj = Obj.getBasicObjRecord(Obj.otNote, ++this.lastObjId); // create + // a + // note + // object this.SheetRecs.add(insertIndex++, obj); - // now add attached text-type mso, specifying the shape has attached text + // now add attached text-type mso, specifying the shape has + // attached text msoDrawing = (MSODrawing) MSODrawing.getTextBoxPrototype(); msoDrawing.setSheet(this); this.SheetRecs.add(insertIndex++, msoDrawing); - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the msodrawinggroup set of recs + msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the + // msodrawinggroup set of recs // now update msodg + msoheader rec - wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing header record and update it (using info from other msodrawing recs) + wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing header + // record and update it (using + // info from other msodrawing + // recs) msodg.setSpidMax(this.wkbook.lastSPID + 1); - msodg.updateRecord(); // given all information, generate appropriate bytes for the Mso rec + msodg.updateRecord(); // given all information, generate appropriate + // bytes for the Mso rec msodg.dirtyflag = true; return insertIndex; } @@ -3036,7 +3480,8 @@ public int insertDropDownBox(int colNum) { msodg.initNewMSODrawingGroup(); } - // insert either above first NOTE record or before WINDOW2 and certain other XLSRECORDS + // insert either above first NOTE record or before WINDOW2 + // and certain other XLSRECORDS insertIndex = this.getIndexOf(NOTE); if (insertIndex == -1) // no existing notes - find proper insert index insertIndex = this.getIndexOf(WINDOW2); @@ -3052,16 +3497,21 @@ public int insertDropDownBox(int colNum) { break; } if (rec.getSOLVERContainerLength() == 0) - break; // solver containers must be last, apparently ... sigh ... + break; // solver containers must be last, apparently ... + // sigh ... // else - // Logger.logInfo("Boundsheet.InsertMSOObjectsForNote. SOLVER CONTAINER ENCOUNTED"); + // Logger.logInfo("Boundsheet.InsertMSOObjectsForNote. + // SOLVER CONTAINER ENCOUNTED"); } else if (opc == OBJ) { Obj rec = (Obj) SheetRecs.get(insertIndex - 1); - if (rec.getObjType()==Obj.otDropdownlist) // TODO: verify that drop downs are reused/shared in all cases!!!! + if (rec.getObjType() == Obj.otDropdownlist) // TODO: verify that + // drop downs are + // reused/shared in + // all cases!!!! return rec.getObjId(); // already have one return object id break; - }else if (opc == OBJ || opc == CONTINUE - || opc == DIMENSIONS || opc == 0x866 || opc == 0x1C2) { + } else if (opc == OBJ || opc == CONTINUE || opc == DIMENSIONS + || opc == 0x866 || opc == 0x1C2) { break; } insertIndex--; @@ -3076,10 +3526,19 @@ public int insertDropDownBox(int colNum) { msoheader = msoDrawing; } - msoDrawing.createDropDownListStyle(colNum); // create the records necessary to define the dropdown box symbol at the desired column + msoDrawing.createDropDownListStyle(colNum); // create the records + // necessary to define the + // dropdown box symbol at + // the desired column // object record which defines a basic dropdown list - Obj obj = (Obj) Obj.getBasicObjRecord(Obj.otDropdownlist, ++this.lastObjId); // create a drop-down object record for each + Obj obj = Obj.getBasicObjRecord(Obj.otDropdownlist, ++this.lastObjId); // create + // a + // drop-down + // object + // record + // for + // each int objID = obj.getObjId(); // insert new mso + obj records into sheet @@ -3088,10 +3547,15 @@ public int insertDropDownBox(int colNum) { // now update msodg + msoheader rec msoheader.numShapes++; - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the msodrawinggroup set of recs - wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing header record and update it (using info from other msodrawing recs) + msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the + // msodrawinggroup set of recs + wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing header + // record and update it (using + // info from other msodrawing + // recs) msodg.setSpidMax(this.wkbook.lastSPID + 1); - msodg.updateRecord(); // given all information, generate appropriate bytes + msodg.updateRecord(); // given all information, generate appropriate + // bytes msodg.dirtyflag = true; return objID; @@ -3107,7 +3571,9 @@ public int insertDropDownBox(int colNum) { */ public Note createNote(String address, Unicodestring txt, String author) { Note nh = this.createNote(address, txt.getStringVal(), author); - // TODO: deal with formats - incorporate into Txo/Continues -- for now they are just stored as-is, no modification allowed + // TODO: deal with formats - incorporate into Txo/Continues + // -- for now they are just stored as-is, no modification + // allowed nh.setFormattingRuns(txt.getFormattingRuns()); return nh; } @@ -3119,18 +3585,23 @@ public Note createNote(String address, Unicodestring txt, String author) { public void removeNote(Note n) { int id = n.getId(); int idx = this.getIndexOf(OBJ); - if (idx==-1) return; // should't! + if (idx == -1) + return; // should't! while (idx < this.SheetRecs.size()) { if (((BiffRec) this.SheetRecs.get(idx)).getOpcode() == OBJ) { Obj o = ((Obj) this.SheetRecs.get(idx)); // if it's of type Note + has the same id, this is it if (o.getObjType() == 0x19 && o.getObjId() == id) { // got it! - // apparently sometimes you don't find the mso/obj/mso combo, so check - if (((BiffRec) this.SheetRecs.get(idx - 1)).getOpcode() == MSODRAWING) { + // apparently sometimes you don't find the mso/obj/mso + // combo, so check + if (((BiffRec) this.SheetRecs.get(idx - 1)) + .getOpcode() == MSODRAWING) { idx--; break; - } else if ((((BiffRec)this.SheetRecs.get(idx+1)).getOpcode()==CONTINUE) && - (((Continue)this.SheetRecs.get(idx+1))).maskedMso!=null) { + } else if ((((BiffRec) this.SheetRecs.get(idx + 1)) + .getOpcode() == CONTINUE) + && (((Continue) this.SheetRecs + .get(idx + 1))).maskedMso != null) { // idx++; break; } @@ -3138,7 +3609,8 @@ public void removeNote(Note n) { } idx++; } - // usual format= mso/obj/mso/txo/continue/continue but can also be: + // usual format= mso/obj/mso/txo/continue/continue but can + // also be: // obj/continue (mso)/txo/continue/continue/continue (mso) int objidx = 0; int msoidx = 0; @@ -3153,27 +3625,38 @@ else if (rec.getOpcode() == MSODRAWING) { maskedMso = false; if (mso.getShapeType() == MSODrawingConstants.msosptTextBox) msoidx++; - else if ((((MSODrawing)rec).isShape)) // it's not a text box or the associated text "oddball" mso, so break (Another test: SPID==0??) + else if ((((MSODrawing) rec).isShape)) // it's not a text box or + // the associated text + // "oddball" mso, so + // break (Another test: + // SPID==0??) break; } else if (rec.getOpcode() == CONTINUE && maskedMso) { mso = ((Continue) rec).maskedMso; if (mso.getShapeType() == MSODrawingConstants.msosptTextBox) msoidx++; - else if ((((MSODrawing)rec).isShape)) // it's not a text box or the associated text "oddball" mso, so break (Another test: SPID==0??) + else if ((((MSODrawing) rec).isShape)) // it's not a text box or + // the associated text + // "oddball" mso, so + // break (Another test: + // SPID==0??) break; } else if (rec.getOpcode() == NOTE) break; - if (objidx>1 || msoidx>1) // reached the next set of note-associated recs, so get out + if (objidx > 1 || msoidx > 1) // reached the next set of + // note-associated recs, so get out break; this.SheetRecs.remove(idx); // otherwise, ok to delete - if (mso!=null && mso.isShape) {// if removed an mso, must update msodg + if (mso != null && mso.isShape) {// if removed an mso, must update + // msodg MSODrawingGroup msodg = this.wkbook.getMSODrawingGroup(); msodg.removeMsodrawingrec(mso, this, true); } } // now remove the actual note record idx = this.getIndexOf(NOTE); - while (idx < this.SheetRecs.size() && ((BiffRec)this.SheetRecs.get(idx)).getOpcode()==NOTE) { + while (idx < this.SheetRecs.size() + && ((BiffRec) this.SheetRecs.get(idx)).getOpcode() == NOTE) { if (((Note) this.SheetRecs.get(idx)).equals(n)) { this.SheetRecs.remove(idx); break; // we're done @@ -3192,17 +3675,22 @@ public AutoFilter addAutoFilter(int column) { // then must add a mso/obj pair for each column on the sheet // to define dropdown box next to each column // also must add built-in name _FILTERDATABASE + - // must add a mystery XlSRecord with opcode== 0x9D -- cannot find any information about this opcode + // must add a mystery XlSRecord with opcode== 0x9D -- cannot + // find any information about this opcode if (this.autoFilters == null || this.autoFilters.size() == 0) { // add _FILTERDATABASE Name this.addFilterDatabase(); - /* 20100216 KSC: WHAT ARE THESE RECORD??? They are necessary for new AutoFilter's */ + /* + * 20100216 KSC: WHAT ARE THESE RECORD??? They are necessary + * for new AutoFilter's + */ int zz = getIndexOf(COLINFO); if (zz == -1) zz = getIndexOf(DEFCOLWIDTH) + 1; else { - while (((BiffRec) this.SheetRecs.get(zz)).getOpcode() == COLINFO) + while (((BiffRec) this.SheetRecs.get(zz)) + .getOpcode() == COLINFO) zz++; } // insert after COLINFOs or DefColWidth @@ -3211,20 +3699,24 @@ public AutoFilter addAutoFilter(int column) { rec.setData(new byte[] {}); this.SheetRecs.add(zz++, rec); rec = new XLSRecord(); - rec.setOpcode((short)157); // this has SOMETHING to do with # columns ... + rec.setOpcode((short) 157); // this has SOMETHING to do with # + // columns ... rec.setData(new byte[] { (byte) this.getMaxCol(), 0 }); this.SheetRecs.add(zz, rec); // add required Mso/object records int insertIndex; MSODrawingGroup msodg = this.wkbook.getMSODrawingGroup(); - if (msodg!=null) { // already have drawing records; just add to records + update msodg + if (msodg != null) { // already have drawing records; just add to + // records + update msodg insertIndex = this.getIndexOf(MSODRAWINGSELECTION); if (insertIndex < 0) insertIndex = this.getIndexOf(WINDOW2); - } else { // No images present in workbook, must add appropriate records + } else { // No images present in workbook, must add appropriate + // records msodg = this.wkbook.createMSODrawingGroup(); - msodg.initNewMSODrawingGroup(); // generate and add required records for drawing records + msodg.initNewMSODrawingGroup(); // generate and add required + // records for drawing records // insertion point for new msodrawing rec insertIndex = getIndexOf(DIMENSIONS) + 1; } @@ -3234,7 +3726,8 @@ public AutoFilter addAutoFilter(int column) { // Must add for each column for (int i = 0; i < this.getRealMaxCol(); i++) { try { - if (this.getCellsByCol(i).size()==0) break; + if (this.getCellsByCol(i).size() == 0) + break; } catch (CellNotFoundException e) { break; } @@ -3249,10 +3742,20 @@ public AutoFilter addAutoFilter(int column) { msoheader = msoDrawing; } - msoDrawing.createDropDownListStyle(j); // create the records necessary to define the dropdown box symbol + msoDrawing.createDropDownListStyle(j); // create the records + // necessary to define + // the dropdown box + // symbol // object record which defines a basic dropdown list - Obj obj = (Obj) Obj.getBasicObjRecord(Obj.otDropdownlist, ++this.lastObjId); // create a drop-down object record for each + Obj obj = Obj + .getBasicObjRecord(Obj.otDropdownlist, ++this.lastObjId); // create + // a + // drop-down + // object + // record + // for + // each // insert new mso + obj records into sheet this.SheetRecs.add(insertIndex++, msoDrawing); @@ -3260,10 +3763,17 @@ public AutoFilter addAutoFilter(int column) { // now update msodg + msoheader rec msoheader.numShapes++; - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the msodrawinggroup set of recs - wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing header record and update it (using info from other msodrawing recs) + msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec + // to the msodrawinggroup + // set of recs + wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing + // header record and + // update it (using info + // from other msodrawing + // recs) msodg.setSpidMax(this.wkbook.lastSPID + 1); - msodg.updateRecord(); // given all information, generate appropriate bytes + msodg.updateRecord(); // given all information, generate + // appropriate bytes msodg.dirtyflag = true; } } @@ -3282,7 +3792,8 @@ public AutoFilter addAutoFilter(int column) { * removes all autofilters from this sheet */ public void removeAutoFilter() { - this.removeFilterDatabase(); // remove the _FILTER_DATABASE name necessary for AutoFilters + this.removeFilterDatabase(); // remove the _FILTER_DATABASE name + // necessary for AutoFilters int zz = getIndexOf(AUTOFILTER); // remove all AutoFitler records while (zz != -1) { this.SheetRecs.remove(zz); @@ -3297,22 +3808,27 @@ public void removeAutoFilter() { this.SheetRecs.remove(zz); // and hows about the Mso/Obj records, huh? huh? this.autoFilters.clear(); - // finally, must set all rows to NOT hidden - I believe Excel does this when AutoFilters are turned off + // finally, must set all rows to NOT hidden - I believe + // Excel does this when AutoFilters are turned off for (int i = 0; i < rows.size(); i++) - ((Row) rows.get(Integer.valueOf(i))).setHidden(false); + rows.get(Integer.valueOf(i)).setHidden(false); } /** * adds a sxview - pivot table lead record - and required associated records to the worksheet *
other methods that add data, row, col and page fields will fill in the pivot table fields and formatting info - * @param ref Cell Range which identifies pivot table data range - * @param wbh WorkBookHandle - * @param sId Stream or cachid Id -- links back to SxStream set of records + * @param ref Cell Range which identifies pivot table data range + * @param wbh WorkBookHandle + * @param sId Stream or cachid Id -- links back to SxStream set of records * @return */ public Sxview addPivotTable(String ref, WorkBookHandle wbh, int sId, String tablename) { - this.wkbook.addPivotCache(ref, wbh, sId); // create the directory/storage for a pivot cache, if not already created - // ensure the proper directory/storage and pivot cache record is created + this.wkbook.addPivotCache(ref, wbh, sId); // create the + // directory/storage for a + // pivot cache, if not + // already created + // ensure the proper directory/storage and pivot cache + // record is created int zz = win2.getRecordIndex() - 1; while (zz > 0) { if (((BiffRec) this.SheetRecs.get(zz)).getOpcode() == NOTE) @@ -3329,19 +3845,19 @@ public Sxview addPivotTable(String ref, WorkBookHandle wbh, int sId, String tabl this.SheetRecs.add(zz++, sx); this.SheetRecs.addAll(zz, sx.addInitialRecords(this)); sx.setTableName(tablename); - this.wkbook.addPivotTable(sx); // add to lookup + this.wkbook.addPivotTable(sx); // add to lookup return sx; } /** - * update row filter (hidden status) by evaluating AutoFilter conditions on the sheet - *
Must do after autofilter updates or additions + * update row filter (hidden status) by evaluating AutoFilter conditions on the sheet + *
Must do after autofilter updates or additions */ public void evaluateAutoFilters() { // first must set all rows to NOT hidden for (int i = 0; i < rows.size(); i++) try { - ((Row) rows.get(Integer.valueOf(i))).setHidden(false); + rows.get(Integer.valueOf(i)).setHidden(false); } catch (NullPointerException e) { // blank rows ... } @@ -3353,8 +3869,8 @@ public void evaluateAutoFilters() { } /** - * returns the list of Excel 2007 objects which are external or auxillary to this sheet - * e.g printerSeettings, vmlDrawings + * returns the list of Excel 2007 objects which are external or auxillary to this sheet + * e.g printerSeettings, vmlDrawings * @return */ public List getOOXMLObjects() { @@ -3362,88 +3878,121 @@ public List getOOXMLObjects() { } /** - * adds the object-specific signature of the external or auxillary Excel 2007 object - * e.g. oleObjects, vmlDrawings + * adds the object-specific signature of the external or auxillary Excel 2007 object + * e.g. oleObjects, vmlDrawings * @param o */ public void addOOXMLObject(Object o) { ooxmlObjects.add(o); } - // TODO: Handle below options in Excel 2003 i.e. create appropriate records ************************************************************* + // TODO: Handle below options in Excel 2003 i.e. create + // appropriate records + // ************************************************************* /** * set if row has thick bottom by default (Excel 2007-Specific) */ - public boolean hasThickBottom() { return thickBottom; } + public boolean hasThickBottom() { + return thickBottom; + } + /** * return true if row has thick top by default (Excel 2007-Specific) */ - public boolean hasThickTop() { return thickTop; } + public boolean hasThickTop() { + return thickTop; + } + /** * return true if rows are hidden by default (Excel 2007-Specific) */ - public boolean hasZeroHeight() { return zeroHeight; } + public boolean hasZeroHeight() { + return zeroHeight; + } + /** * return true if defaultrowheight is manually set (Excel 2007-Specific) */ - public boolean hasCustomHeight() { return customHeight; } + public boolean hasCustomHeight() { + return customHeight; + } + /** * return the default row height in points (Excel 2007-Specific) */ - public double getDefaultRowHeight() { return defaultRowHeight; } + public double getDefaultRowHeight() { + return defaultRowHeight; + } + /** - * return the default column width in # characters of the maximum digit width of the normal style's font - * - * This is currently a floating point value, something I question. I don't understand the need for this, - * and possibly it should be an int? - * - */ - public float getDefaultColumnWidth() { - // biff8 setting - if (defColWidth!=null){ - return (float)defColWidth.getDefaultWidth(); - } - return defaultColWidth; - } + * return the default column width in # characters of the maximum digit width of the normal style's font + * + * This is currently a floating point value, something I question. I don't understand the need for this, + * and possibly it should be an int? + * + */ + public float getDefaultColumnWidth() { + // biff8 setting + if (defColWidth != null) { + return defColWidth.getDefaultWidth(); + } + return defaultColWidth; + } + /** * set if row has thick bottom by default (Excel 2007-Specific) */ - public void setThickBottom(boolean b) { thickBottom= b; } + public void setThickBottom(boolean b) { + thickBottom = b; + } + /** * set if row has thick top by default (Excel 2007-Specific) */ - public void setThickTop(boolean b) { thickTop= b; } + public void setThickTop(boolean b) { + thickTop = b; + } + /** * set if rows are hidden by default (Excel 2007-Specific) */ - public void setZeroHeight(boolean b) { zeroHeight= b; } + public void setZeroHeight(boolean b) { + zeroHeight = b; + } + /** * set if defaultrowheight is manually set (Excel 2007-Specific) */ - public void setHasCustomHeight(boolean b) { customHeight= b; } + public void setHasCustomHeight(boolean b) { + customHeight = b; + } + /** * set the default row height in points (Excel 2007-Specific) */ - public void setDefaultRowHeight(double h) { defaultRowHeight= h; } - + public void setDefaultRowHeight(double h) { + defaultRowHeight = h; + } + /** - * set the default column width in # characters of the maximum digit width of the normal style's font - */ - public void setDefaultColumnWidth(float w) { - // ooxml setting - defaultColWidth= w; - // biff8 setting - if (defColWidth!=null){ - defColWidth.setDefaultColWidth((int)w); - } - } + * set the default column width in # characters of the maximum digit width of the normal style's font + */ + public void setDefaultColumnWidth(float w) { + // ooxml setting + defaultColWidth = w; + // biff8 setting + if (defColWidth != null) { + defColWidth.setDefaultColWidth((int) w); + } + } /** * store Excel 2007 shape via Shape Name * @param tca */ public void addOOXMLShape(io.starter.formats.OOXML.TwoCellAnchor tca) { - if (ooxmlShapes==null) ooxmlShapes= new HashMap(); + if (ooxmlShapes == null) + ooxmlShapes = new HashMap(); ooxmlShapes.put(tca.getName(), tca); } @@ -3452,7 +4001,8 @@ public void addOOXMLShape(io.starter.formats.OOXML.TwoCellAnchor tca) { * @param tca */ public void addOOXMLShape(io.starter.formats.OOXML.OneCellAnchor oca) { - if (ooxmlShapes==null) ooxmlShapes= new HashMap(); + if (ooxmlShapes == null) + ooxmlShapes = new HashMap(); ooxmlShapes.put(oca.getName(), oca); } @@ -3461,8 +4011,10 @@ public void addOOXMLShape(io.starter.formats.OOXML.OneCellAnchor oca) { * @param vml */ public void addOOXMLShape(Object vml) { - if (ooxmlShapes==null) ooxmlShapes= new HashMap(); - ooxmlShapes.put("vml", vml); // only 1 vml (=legacy drawing info) per sheet so just refer to it as "vml" + if (ooxmlShapes == null) + ooxmlShapes = new HashMap(); + ooxmlShapes.put("vml", vml); // only 1 vml (=legacy drawing info) per + // sheet so just refer to it as "vml" } /** @@ -3477,7 +4029,9 @@ public HashMap getOOXMLShapes() { * returns the Excel 2007 sheetView element for this sheet (controls topLeftCell, pane attributes ... * @return */ - public SheetView getSheetView() { return sheetview; } + public SheetView getSheetView() { + return sheetview; + } /** * set the Excel 2007 sheetView element for this sheet (controls topLeftCell, pane attributes ... @@ -3491,7 +4045,9 @@ public void setSheetView(SheetView s) { * returns the Excel 2007 sheetPr sheet Properties element for this sheet (controls codename, tabColor ...) * @return */ - public SheetPr getSheetPr() { return sheetPr; } + public SheetPr getSheetPr() { + return sheetPr; + } /** * set the Excel 2007 sheetView element for this sheet (controls topLeftCell, pane attributes ... @@ -3506,7 +4062,9 @@ public void setSheetPr(SheetPr s) { * TODO: Merge with 2003 AutoFilter * @return */ - public io.starter.formats.OOXML.AutoFilter getOOAutoFilter() { return ooautofilter; } + public io.starter.formats.OOXML.AutoFilter getOOAutoFilter() { + return ooautofilter; + } /** * set the Excel 2007 autoFilter element for this sheet (temporarily hides rows based upon filter criteria) @@ -3517,14 +4075,15 @@ public void setOOAutoFilter(io.starter.formats.OOXML.AutoFilter a) { ooautofilter = a; } - /** returns a scoped named range by name string - + /** returns a scoped named range by name string + * * @param t * @return */ public Name getScopedName(String nameRef) { - Object o = this.sheetNameRecs.get(nameRef.toUpperCase()); // case insensitive + Object o = this.sheetNameRecs.get(nameRef.toUpperCase()); // case + // insensitive if (o == null) { return null; } @@ -3534,21 +4093,22 @@ public Name getScopedName(String nameRef) { /** * Add a sheet-scoped name record to the boundsheet * - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this sheet, adding them here will not add them to the workbook; + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this sheet, adding them here will not add them to the workbook; * * @param sheetNameRecs */ public void addLocalName(Name name) { - if (sheetNameRecs == null)sheetNameRecs = new HashMap(); + if (sheetNameRecs == null) + sheetNameRecs = new HashMap(); sheetNameRecs.put(name.getNameA(), name); } /** * Remove a sheet-scoped name record from the boundsheet. * - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this sheet, removing them here will not remove them completely from the workbook. + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this sheet, removing them here will not remove them completely from the workbook. * * In order to do that you will need to call book.removeName * @@ -3564,9 +4124,11 @@ public void removeLocalName(Name name) { * @return */ public Name getName(String name) { - if (sheetNameRecs == null) return null; + if (sheetNameRecs == null) + return null; Object o = sheetNameRecs.get(name.toUpperCase()); // case insensitive - if (o!=null) return (Name)o; + if (o != null) + return (Name) o; return null; } @@ -3576,7 +4138,8 @@ public Name getName(String name) { * @return */ public Name[] getAllNames() { - if(this.sheetNameRecs==null)this.sheetNameRecs=new HashMap(); + if (this.sheetNameRecs == null) + this.sheetNameRecs = new HashMap(); ArrayList retnames = new ArrayList(sheetNameRecs.values()); Name[] names = new Name[retnames.size()]; return (Name[]) retnames.toArray(names); @@ -3592,16 +4155,18 @@ public void addMarginRecord(BiffRec r) { int thisOpCode = r.getOpcode(); // iterate up from SETUP record // desired order: - // WsBool, HeaderRec, FooterRec, HCenter, VCenter, LeftMargin, RightMargin, TopMargin, BottomMargin, Pls + // WsBool, HeaderRec, FooterRec, HCenter, VCenter, + // LeftMargin, RightMargin, TopMargin, BottomMargin, Pls while (i > 0) { - int prevOpCode = ((BiffRec) this.getSheetRecs().get(--i)).getOpcode(); - if (prevOpCode == VCENTER || /* assume AT LEAST a VCENTER or FOOTERREC */ - prevOpCode==FOOTERREC || - prevOpCode==LEFT_MARGIN) { + int prevOpCode = ((BiffRec) this.getSheetRecs().get(--i)) + .getOpcode(); + if (prevOpCode == VCENTER + || /* assume AT LEAST a VCENTER or FOOTERREC */ + prevOpCode == FOOTERREC || prevOpCode == LEFT_MARGIN) { break; } - if ((prevOpCode == BOTTOM_MARGIN || prevOpCode == TOP_MARGIN || prevOpCode == RIGHT_MARGIN) - && thisOpCode == PLS) + if ((prevOpCode == BOTTOM_MARGIN || prevOpCode == TOP_MARGIN + || prevOpCode == RIGHT_MARGIN) && thisOpCode == PLS) break; if ((prevOpCode == TOP_MARGIN || prevOpCode == RIGHT_MARGIN) && thisOpCode == BOTTOM_MARGIN) @@ -3611,972 +4176,1345 @@ public void addMarginRecord(BiffRec r) { } this.SheetRecs.add(++i, r); } + + /** inserts a row and shifts all of the other rows down one - /** inserts a row and shifts all of the other rows down one - - the rownum is zero based. calling insertrow(9,true) will - create a row containing A10, and subsequently shift rows > 9 by 1. - - * @return the row that was just inserted - */ + the rownum is zero based. calling insertrow(9,true) will + create a row containing A10, and subsequently shift rows > 9 by 1. + + * @return the row that was just inserted + */ private Row insertRow(int rownum, boolean shiftrows) { return insertRow(rownum, 0, WorkSheetHandle.ROW_INSERT_MULTI, shiftrows); } - /** - * given sheet.xml input stream, parse OOXML into the current sheet - * @param bk - * @param sheet - * @param ii - * @param sst The sst. - * @param formulas Arraylist stores all formulas/info - must be added after all sheets and cells - * @param hyperlinks - * @param inlineStrs Hashmap stores inline strings and cell addresses; must be added after all sheets and cells - * @throws IOException - * @throws XmlPullParserException - * @throws CellNotFoundException - */ - HashMap shExternalLinkInfo= null; - void parseOOXML(WorkBookHandle bk, WorkSheetHandle sheet, InputStream ii, ArrayList sst, ArrayList formulas, ArrayList hyperlinks, HashMap inlineStrs) throws XmlPullParserException, IOException { - int sfindex= formulas.size(); - -// try { - Row r= null; - String cellAddr = null; - int formatId= 0; - String type= ""; - shExternalLinkInfo= new HashMap(); - - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sheetFormatPr")) { // baseColWidth, customHeight (true if defaultRowHeight has been manually set), - // defaultColWidth, defaultRowHeight - optimiztion so that we don't have to write out values on each - // thickBottom - true if rows have a thick bottom by default - // thickTop - true if rows have a thick top by default - // zeroHeight - true if rows are hidden by default (an optimization) - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String n= xpp.getAttributeName(i); - String v= xpp.getAttributeValue(i); - if (n.equals("thickBottom")) - this.setThickBottom(true); - else if (n.equals("thickTop")) - this.setThickTop(true); - else if (n.equals("zeroHeight")) - this.setZeroHeight(v.equals("1")); - else if (n.equals("customHeight")) - this.setHasCustomHeight(v.equals("1")); - else if (n.equals("defaultColWidth")) - this.setDefaultColumnWidth(new Float(v).floatValue()); - else if (n.equals("defaultRowHeight")) - this.setDefaultRowHeight(new Double(v).doubleValue()); - } - } - else if (tnm.equals("sheetView")) - { // TODO: finish handling options - SheetView s= (SheetView) SheetView.parseOOXML(xpp).cloneElement(); - this.setSheetView(s); - this.getWindow2().setShowGridlines(!(s.getAttrS("showGridlines")).equals("0")); - if (s.getAttr("showRowColHeaders")!=null) - this.getWindow2().setShowSheetHeaders((s.getAttrS("showRowColHeaders")).equals("1")); - if (s.getAttr("showZeros")!=null) - this.getWindow2().setShowZeroValues((s.getAttrS("showZeros")).equals("1")); - if (s.getAttr("showOutlineSymbols")!=null) - this.getWindow2().setShowOutlineSymbols((s.getAttrS("showOutlineSymbols")).equals("1")); - if (s.getAttr("tabSelected")!=null) - this.setSelected((s.getAttrS("tabSelected")).equals("1")); - if (s.getAttr("zoomScale")!=null) - this.getScl().setZoom(new Double(s.getAttrS("zoomScale")).floatValue()/100); - } - else if (tnm.equals("sheetPr")) - { // sheet properties element - SheetPr sp= (SheetPr) SheetPr.parseOOXML(xpp).cloneElement(); - this.setSheetPr(sp); - } - else if (tnm.equals("dimension")) { // ref attribute - /* this may not reflect actual rows/cols in sheet - * just let our normal machinery set the sheet dimensions - String ref= xpp.getAttributeValue(0); - int[] rc= ExcelTools.getRangeCoords(ref); - this.updateDimensions(rc[2]-1, rc[3]); -*/ } - else if (tnm.equals("sheetProtection")) - { // ref attribute - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); - String v= xpp.getAttributeValue(i); - if (nm.equals("password")) - this.getProtectionManager().setPasswordHashed( v ); - else if (nm.equals( "sheet" )) - this.getProtectionManager().setProtected( OOXMLReader.parseBoolean( v ) ); - } - } - else if (tnm.equals("col")) - { // min, max, width - int min= 0, max= 0, style= 0; - double width= 0; - boolean hidden= false; - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); - String v= xpp.getAttributeValue(i); - if (nm.equals("min")) - min= Integer.valueOf(v).intValue(); - else if (nm.equals("max")) - max= Integer.valueOf(v).intValue(); - else if (nm.equals("width")) - width= new Double(v).doubleValue(); - else if (nm.equals("hidden")) - hidden= true; - else if (nm.equals("style")) // customFormat? - style= Integer.valueOf(v).intValue(); - } - if (max > WorkBook.MAXCOLS) max= WorkBook.MAXCOLS-1; - ColHandle col= sheet.addCol(min-1, max-1); - col.setWidth((int)(width*OOXMLReader.colWFactor)); - if (style>0) - col.setFormatId(style); - //col.setColLast(max-1); - if (hidden) col.setHidden(true); - } - else if (tnm.equals("row")) - { - int ht= -1, ixfe= 0; - boolean customHeight= false; - for (int i= 0; i < xpp.getAttributeCount(); i++) { // r, v= row #+1, ht, ... - String nm= xpp.getAttributeName(i); - String v= xpp.getAttributeValue(i); - if (nm.equals("r")) { - int rownum= Integer.valueOf(v).intValue()-1; - r = this.insertRow(rownum, false); // now insertRow with no shift rows does NOT add a blank cell so no need to delete extra cell anymore - r.setIxfe(this.getWorkBook().getDefaultIxfe()); - } else if (nm.equals("ht")) { - ht= (int)(new Double(v).doubleValue()*OOXMLReader.rowHtFactor); - } else if (nm.equals("s")) { // customFormat? - ixfe= Integer.valueOf(v).intValue(); - } else if (nm.equals("customFormat")) { - r.setIxfe(ixfe); - } else if (nm.equals("hidden")) { - r.setHidden(true); - } else if (nm.equals("collapsed")) { // 20090513 KSC: Added collapsed, outlineLevel [BUGTRACKER 2371] - boolean h= r.isHidden(); // setCollapsed unconditionally sets hidden - r.setCollapsed(true); - if (!h) r.setHidden(false); - } else if (nm.equals("outlineLevel")) { - r.setOutlineLevel(Integer.valueOf(v).intValue()); - } else if (nm.equals("customHeight")) { - customHeight= true; - } else if (nm.equals("thickBot")){ - r.setHasAnyThickBottomBorder(true); - } else if (nm.equals("thickTop")){ - r.setHasAnyThickTopBorder(true); - } - if (ht!=-1 && customHeight) // if customHeight is NOT set do not set row height (encountered in Baxter XLSM templates) - r.setRowHeight(ht); - } - // if customheight is NOT specified do not set row height - } - else if (tnm.equals("c")) - {// element c child v= value - if (cellAddr!=null) { - if (r.getExplicitFormatSet() || ((formatId!=this.getWorkBook().getDefaultIxfe() && formatId!=0))) { //default or not specified NOTE: default for OOXML is 0 not 15 - int[] rc = ExcelTools.getRowColFromString(cellAddr); - OOXMLReader.sheetAdd(sheet,null,rc[0],rc[1],formatId); - } - cellAddr= null; - } - formatId= 0; - type= "n"; // reset for those cells that don't specify a type, default = number - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); // r, s=style, t= type - String v= xpp.getAttributeValue(i); - if (nm.equals("r")) { // cell address - cellAddr = v; // save for setting later - } else if (nm.equals("s")){ - formatId= Integer.valueOf(v).intValue(); // save for setting later - }else if (nm.equals("t")) { - type= v; - } - } - // would be great if could peek at next tag to determine - // whether to add a blank cell here rather than catch it at - // end tag below - } - else if (tnm.equals("is")) - { // inline string child of - // control->controlPr - // Fallback - // control - // i.e. 1st choice is a control with control settings - // if not possible, fallback is - } else if (tnm.equals("Fallback")) { - OOXMLReader.getCurrentElement(xpp); // skip as can replicate Choice - } else if (tnm.equals("controlPr")) { - OOXMLReader.getCurrentElement(xpp); // skip for now!! - } else if (tnm.equals("extLst")) { // skip for now!! - OOXMLReader.getCurrentElement(xpp); // skip for now!! - } /*else { - if (true) - Logger.logWarn("unprocessed XLSX sheet element: " + tnm); - }*/ - } else if(eventType == XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("row") && cellAddr!=null) { - - int[] rc = ExcelTools.getRowColFromString(cellAddr); - // if masking an explicit row format or if it's a unique format, set to new blank cell - if (r.getExplicitFormatSet() || ((formatId != this.getWorkBook().getDefaultIxfe() && formatId!=0))) { //default or not specified NOTE: default for OOXML is 0 not 15 (unless converted from XLS ((: -// if (r.myRow.getExplicitFormatSet() || (/*formatId!=15 && */formatId!=0 && uniqueFormat)) { //default or not specified NOTE: default for OOXML==0 NOT 15 - OOXMLReader.sheetAdd(sheet, null,rc[0],rc[1],formatId); -// } else{ -// sheetAdd(sheet,null,rc[0],rc[1],formatId); - } - cellAddr= null; - } else /**/if (endTag.equals("worksheet")) // we're done! - break; - } - eventType = xpp.next(); - } - } - + /** + * given sheet.xml input stream, parse OOXML into the current sheet + * @param bk + * @param sheet + * @param ii + * @param sst The sst. + * @param formulas Arraylist stores all formulas/info - must be added after all sheets and cells + * @param hyperlinks + * @param inlineStrs Hashmap stores inline strings and cell addresses; must be added after all sheets and cells + * @throws IOException + * @throws XmlPullParserException + * @throws CellNotFoundException + */ + HashMap shExternalLinkInfo = null; + + void parseOOXML(WorkBookHandle bk, WorkSheetHandle sheet, InputStream ii, ArrayList sst, ArrayList formulas, ArrayList hyperlinks, HashMap inlineStrs) throws XmlPullParserException, IOException { + int sfindex = formulas.size(); + + // try { + Row r = null; + String cellAddr = null; + int formatId = 0; + String type = ""; + shExternalLinkInfo = new HashMap(); + + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + + xpp.setInput(ii, null); // using XML 1.0 specification + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("sheetFormatPr")) { // baseColWidth, customHeight + // (true if defaultRowHeight + // has been manually set), + // defaultColWidth, + // defaultRowHeight - + // optimiztion so that we + // don't have to write out + // values on each + // thickBottom - true if + // rows have a thick bottom + // by default + // thickTop - true if rows + // have a thick top by + // default + // zeroHeight - true if rows + // are hidden by default (an + // optimization) + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String n = xpp.getAttributeName(i); + String v = xpp.getAttributeValue(i); + if (n.equals("thickBottom")) + this.setThickBottom(true); + else if (n.equals("thickTop")) + this.setThickTop(true); + else if (n.equals("zeroHeight")) + this.setZeroHeight(v.equals("1")); + else if (n.equals("customHeight")) + this.setHasCustomHeight(v.equals("1")); + else if (n.equals("defaultColWidth")) + this.setDefaultColumnWidth(new Float(v) + .floatValue()); + else if (n.equals("defaultRowHeight")) + this.setDefaultRowHeight(new Double(v) + .doubleValue()); + } + } else if (tnm.equals("sheetView")) { // TODO: finish handling + // options + SheetView s = (SheetView) SheetView.parseOOXML(xpp) + .cloneElement(); + this.setSheetView(s); + this.getWindow2() + .setShowGridlines(!(s.getAttrS("showGridlines")) + .equals("0")); + if (s.getAttr("showRowColHeaders") != null) + this.getWindow2() + .setShowSheetHeaders((s + .getAttrS("showRowColHeaders")) + .equals("1")); + if (s.getAttr("showZeros") != null) + this.getWindow2() + .setShowZeroValues((s.getAttrS("showZeros")) + .equals("1")); + if (s.getAttr("showOutlineSymbols") != null) + this.getWindow2() + .setShowOutlineSymbols((s + .getAttrS("showOutlineSymbols")) + .equals("1")); + if (s.getAttr("tabSelected") != null) + this.setSelected((s.getAttrS("tabSelected")) + .equals("1")); + if (s.getAttr("zoomScale") != null) + this.getScl() + .setZoom(new Double(s.getAttrS("zoomScale")) + .floatValue() / 100); + } else if (tnm.equals("sheetPr")) { // sheet properties element + SheetPr sp = (SheetPr) SheetPr.parseOOXML(xpp) + .cloneElement(); + this.setSheetPr(sp); + } else if (tnm.equals("dimension")) { // ref attribute + /* + * this may not reflect actual rows/cols in sheet + * just let our normal machinery set the sheet dimensions + * String ref= xpp.getAttributeValue(0); + * int[] rc= ExcelTools.getRangeCoords(ref); + * this.updateDimensions(rc[2]-1, rc[3]); + */ } else if (tnm.equals("sheetProtection")) { // ref + // attribute + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + String v = xpp.getAttributeValue(i); + if (nm.equals("password")) + this.getProtectionManager().setPasswordHashed(v); + else if (nm.equals("sheet")) + this.getProtectionManager() + .setProtected(OOXMLReader.parseBoolean(v)); + } + } else if (tnm.equals("col")) { // min, max, width + int min = 0, max = 0, style = 0; + double width = 0; + boolean hidden = false; + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + String v = xpp.getAttributeValue(i); + if (nm.equals("min")) + min = Integer.valueOf(v).intValue(); + else if (nm.equals("max")) + max = Integer.valueOf(v).intValue(); + else if (nm.equals("width")) + width = new Double(v).doubleValue(); + else if (nm.equals("hidden")) + hidden = true; + else if (nm.equals("style")) // customFormat? + style = Integer.valueOf(v).intValue(); + } + if (max > WorkBook.MAXCOLS) + max = WorkBook.MAXCOLS - 1; + ColHandle col = sheet.addCol(min - 1, max - 1); + col.setWidth((int) (width * OOXMLReader.colWFactor)); + if (style > 0) + col.setFormatId(style); + // col.setColLast(max-1); + if (hidden) + col.setHidden(true); + } else if (tnm.equals("row")) { + int ht = -1, ixfe = 0; + boolean customHeight = false; + for (int i = 0; i < xpp.getAttributeCount(); i++) { // r, v= + // row + // #+1, + // ht, + // ... + String nm = xpp.getAttributeName(i); + String v = xpp.getAttributeValue(i); + if (nm.equals("r")) { + int rownum = Integer.valueOf(v).intValue() - 1; + r = this.insertRow(rownum, false); // now insertRow + // with no shift + // rows does NOT + // add a blank + // cell so no + // need to + // delete extra + // cell anymore + r.setIxfe(this.getWorkBook().getDefaultIxfe()); + } else if (nm.equals("ht")) { + ht = (int) (new Double(v).doubleValue() + * OOXMLReader.rowHtFactor); + } else if (nm.equals("s")) { // customFormat? + ixfe = Integer.valueOf(v).intValue(); + } else if (nm.equals("customFormat")) { + r.setIxfe(ixfe); + } else if (nm.equals("hidden")) { + r.setHidden(true); + } else if (nm.equals("collapsed")) { // 20090513 KSC: + // Added + // collapsed, + // outlineLevel + // [BUGTRACKER + // 2371] + boolean h = r.isHidden(); // setCollapsed + // unconditionally sets + // hidden + r.setCollapsed(true); + if (!h) + r.setHidden(false); + } else if (nm.equals("outlineLevel")) { + r.setOutlineLevel(Integer.valueOf(v).intValue()); + } else if (nm.equals("customHeight")) { + customHeight = true; + } else if (nm.equals("thickBot")) { + r.setHasAnyThickBottomBorder(true); + } else if (nm.equals("thickTop")) { + r.setHasAnyThickTopBorder(true); + } + if (ht != -1 && customHeight) // if customHeight is NOT + // set do not set row + // height (encountered + // in Baxter XLSM + // templates) + r.setRowHeight(ht); + } + // if customheight is NOT specified do not set row height + } else if (tnm.equals("c")) {// element c child v= value + if (cellAddr != null) { + if (r.getExplicitFormatSet() + || ((formatId != this.getWorkBook() + .getDefaultIxfe() && formatId != 0))) { // default + // or + // not + // specified + // NOTE: + // default + // for + // OOXML + // is + // 0 + // not + // 15 + int[] rc = ExcelTools.getRowColFromString(cellAddr); + OOXMLReader + .sheetAdd(sheet, null, rc[0], rc[1], formatId); + } + cellAddr = null; + } + formatId = 0; + type = "n"; // reset for those cells that don't specify a + // type, default = number + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); // r, s=style, t= + // type + String v = xpp.getAttributeValue(i); + if (nm.equals("r")) { // cell address + cellAddr = v; // save for setting later + } else if (nm.equals("s")) { + formatId = Integer.valueOf(v).intValue(); // save + // for + // setting + // later + } else if (nm.equals("t")) { + type = v; + } + } + // would be great if could peek at next tag to determine + // whether to add a blank cell here rather than catch it at + // end tag below + } else if (tnm.equals("is")) { // inline string child of + // control->controlPr + // Fallback + // control + // i.e. 1st choice is a control with control settings + // if not possible, fallback is + } else if (tnm.equals("Fallback")) { + OOXMLReader.getCurrentElement(xpp); // skip as can replicate + // Choice + } else if (tnm.equals("controlPr")) { + OOXMLReader.getCurrentElement(xpp); // skip for now!! + } else if (tnm.equals("extLst")) { // skip for now!! + OOXMLReader.getCurrentElement(xpp); // skip for now!! + } /* + * else { + * if (true) + * Logger.logWarn("unprocessed XLSX sheet element: " + tnm); + * } + */ + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("row") && cellAddr != null) { + + int[] rc = ExcelTools.getRowColFromString(cellAddr); + // if masking an explicit row format or if it's a unique + // format, set to new blank cell + if (r.getExplicitFormatSet() + || ((formatId != this.getWorkBook().getDefaultIxfe() + && formatId != 0))) { // default or not + // specified NOTE: + // default for OOXML + // is 0 not 15 + // (unless converted + // from XLS ((: + // if (r.myRow.getExplicitFormatSet() || (/*formatId!=15 + // && */formatId!=0 && uniqueFormat)) { //default or not + // specified NOTE: default for OOXML==0 NOT 15 + OOXMLReader + .sheetAdd(sheet, null, rc[0], rc[1], formatId); + // } else{ + // sheetAdd(sheet,null,rc[0],rc[1],formatId); + } + cellAddr = null; + } else /**/if (endTag.equals("worksheet")) // we're done! + break; + } + eventType = xpp.next(); + } + } - /** - * associates external reference info with the r:id of the external reference - * for instance, oleObject elements are associated with a shape Id that links back to a .vml file entry - * @param externalobjs - * @param xpp - */ - protected static void addExternalInfo(Map externalobjs, XmlPullParser xpp) { - //String[] attrs= new String[xpp.getAttributeCount()-1]; - ArrayList attrs= new ArrayList(); - String rId= ""; - //int j= 0; - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String n=xpp.getAttributeName(i); - if (n.equals("id")) - rId= xpp.getAttributeValue(i); - else - //attrs[j++]= n+ "=\"" + xpp.getAttributeValue(i) +"\""; - attrs.add(n+ "=\"" + xpp.getAttributeValue(i) +"\""); - } - String s= Arrays.asList(attrs.toArray()).toString(); // 1.6 only Arrays.toString(attrs.toArray()); - if (s.length()>2) { - s= s.substring(1, s.length()-1); - //1.6 only s= s.replace(",", ""); // only issue is embedded ,'s in quoted strings, lets assume not! - s= StringTool.replaceText(s, ",", ""); // only issue is embedded ,'s in quoted strings, lets assume not! - } - externalobjs.put(rId, s); - } + /** + * associates external reference info with the r:id of the external reference + * for instance, oleObject elements are associated with a shape Id that links back to a .vml file entry + * @param externalobjs + * @param xpp + */ + protected static void addExternalInfo(Map externalobjs, XmlPullParser xpp) { + // String[] attrs= new String[xpp.getAttributeCount()-1]; + ArrayList attrs = new ArrayList(); + String rId = ""; + // int j= 0; + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String n = xpp.getAttributeName(i); + if (n.equals("id")) + rId = xpp.getAttributeValue(i); + else + // attrs[j++]= n+ "=\"" + xpp.getAttributeValue(i) +"\""; + attrs.add(n + "=\"" + xpp.getAttributeValue(i) + "\""); + } + String s = Arrays.asList(attrs.toArray()).toString(); // 1.6 only + // Arrays.toString(attrs.toArray()); + if (s.length() > 2) { + s = s.substring(1, s.length() - 1); + // 1.6 only s= s.replace(",", ""); // only issue is embedded + // ,'s in quoted strings, lets assume not! + s = StringTool.replaceText(s, ",", ""); // only issue is embedded + // ,'s in quoted strings, + // lets assume not! + } + externalobjs.put(rId, s); + } - + /** + * parses OOXML content files given a content list cl from zip file zip + * recurses if content file has it's own content + * ************************************* + * NOTE: certain elements we do not as of yet process; we "pass-through" or store such elements along with any embedded objects associated with them + * for example, activeX objects, vbaProject.bin, etc. + * ************************************* + * @param bk WorkBookHandle + * @param sheet WorkSheetHandle (set if recursing) + * @param zip currently open ZipOutputStream + * @param cl ArrayList of Contents (type, filename, rId) to parse + * @param parentDir Parent Directory for relative paths in content lists + * @param formulas, hyperlinks, inlineStrs -- ArrayLists/Hashmaps stores sheet-specific info for later entry + * @throws CellNotFoundException + * @throws XmlPullParserException + */ + protected void parseSheetElements(WorkBookHandle bk, ZipFile zip, ArrayList cl, String parentDir, String externalDir, ArrayList formulas, ArrayList hyperlinks, HashMap inlineStrs, HashMap pivotTables) throws XmlPullParserException, CellNotFoundException { + String p; + ZipEntry target; - /** - * parses OOXML content files given a content list cl from zip file zip - * recurses if content file has it's own content - * ************************************* - * NOTE: certain elements we do not as of yet process; we "pass-through" or store such elements along with any embedded objects associated with them - * for example, activeX objects, vbaProject.bin, etc. - * ************************************* - * @param bk WorkBookHandle - * @param sheet WorkSheetHandle (set if recursing) - * @param zip currently open ZipOutputStream - * @param cl ArrayList of Contents (type, filename, rId) to parse - * @param parentDir Parent Directory for relative paths in content lists - * @param formulas, hyperlinks, inlineStrs -- ArrayLists/Hashmaps stores sheet-specific info for later entry - * @throws CellNotFoundException - * @throws XmlPullParserException - */ - protected void parseSheetElements(WorkBookHandle bk, ZipFile zip, ArrayList cl, String parentDir, String externalDir, ArrayList formulas, ArrayList hyperlinks, HashMap inlineStrs, HashMap pivotTables) throws XmlPullParserException, CellNotFoundException { - String p; - ZipEntry target; - - try { - for (int i = 0; i < cl.size(); i++) { - String[] c = (String[]) cl.get(i); - String ooxmlElement= c[0]; - - //if(DEBUG) - // Logger.logInfo("OOXMLReader.parse: " + ooxmlElement + ":" + c[1] + ":" + c[2]); - - p= StringTool.getPath(c[1]); - p= OOXMLReader.parsePathForZip(p, parentDir); - if (!ooxmlElement.equals("hyperlink")) // if it's a hyperlink reference, don't strip path info :) - c[1]= StringTool.stripPath(c[1]); - String f= c[1]; - String rId= c[2]; - - if (ooxmlElement.equals("drawing")) { // images, charts - // parse drawing rels to obtain image file names and chart xml files - target= OOXMLReader.getEntry(zip,p + "_rels/" + f.substring(f.lastIndexOf("/")+1)+".rels"); - ArrayList drawingFiles= null; - if (target!=null) // first retrieve enbedded content in .rels (images, charts ...) - drawingFiles= OOXMLReader.parseRels(OOXMLReader.wrapInputStream(OOXMLReader.wrapInputStream(zip.getInputStream(target)))); // obtain a list of image file references for use in later parsing - target= OOXMLReader.getEntry(zip,p + f); // now get drawingml file and process it - parseDrawingXML(bk, drawingFiles, OOXMLReader.wrapInputStream(zip.getInputStream(target)), zip, p, externalDir); - } else if (ooxmlElement.equals("vmldrawing")) { // legacy drawing elements - target = OOXMLReader.getEntry(zip,p + f); - StringBuffer vml= parseLegacyDrawingXML(bk, OOXMLReader.wrapInputStream(zip.getInputStream(target))); - target = OOXMLReader.getEntry(zip,p + "_rels/" // get external objects linked to the vml by parsing it's rels - + f.substring(f.lastIndexOf("/") + 1) + ".rels"); - if (target != null) { - String[] embeds= OOXMLReader.storeEmbeds(zip, target, p, externalDir); // passes thru embedded objects - this.addOOXMLShape(new Object[] { vml, embeds}); - } else - this.addOOXMLShape(vml); - /**/ } else if (ooxmlElement.equals("hyperlink")) { // hyperlinks - c = (String[]) cl.get(i); // don't strip path - for (int j= 0; j < hyperlinks.size(); j++) { - if (rId.equals(((String[]) hyperlinks.get(j))[0])) { - String[] h= (String[]) hyperlinks.get(j); - try { // target= cl[2], ref= h[1], desc= h[2] - bk.getWorkSheet(this.getSheetName()).getCell(h[1]).setURL(rId, h[2], ""); // TODO: hyperlink text mark - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parse: failed setting hyperlink to cell " + h[1] + ":" + e.toString()); - } - break; - } - } - } else if (OOXMLReader.parsePivotTables && ooxmlElement.equals("pivotTable")) { // sheet-parent -/* - * TODO: Do we really need to get rels ???? - // must lookup cacheid from rid of pivotCacheDefinitionX.xml in pivotTableDefinitionX.xml.rels - target= OOXMLReader.getEntry(zip,p + "_rels/" + f.substring(f.lastIndexOf("/")+1)+".rels"); - * ArrayList ptrels= parseRels(wrapInputStream(wrapInputStream(zip.getInputStream(target)))); - if (ptrels.size() > 1) { // what could this be? - Logger.logWarn("OOXMLReader.parse: Unknown Pivot Table Association: " + ptrels.get(1)); - } - String pcd= ((String[])ptrels.get(0))[1]; - pcd= pcd.substring(pcd.lastIndexOf("/")+1); - Object cacheid= null; - for (int z= 0; z < pivotCaches.size(); z++) { - Object[] o= (Object[]) pivotCaches.get(z); - if (pcd.equals(o[0])) { - cacheid= o[1]; - break; - } - } - - target = getEntry(zip,p + f); - PivotTableDefinition.parseOOXML(bk, /*cacheid, * /this, wrapInputStream(zip.getInputStream(target)));*/ - try { // SAVE FOR LATER INPUT -- must do after all sheets are input ... - pivotTables.put(p + f, bk.getWorkSheet(this.getSheetName())); - } catch (WorkSheetNotFoundException we) {} - - } else if (ooxmlElement.equals("comments")) { // parse comments or notes - target = OOXMLReader.getEntry(zip,p + f); - parseCommentsXML(bk, OOXMLReader.wrapInputStream(zip.getInputStream(target))); - - // Below are elements we do not as of yet handle - } else if (ooxmlElement.equals("macro") - || ooxmlElement.equals("activeX") - || ooxmlElement.equals("table") - || ooxmlElement.equals("vdependencies") - || ooxmlElement.equals("oleObject") - || ooxmlElement.equals("image") - || ooxmlElement.equals("printerSettings")) { - - String attrs= ""; - if (shExternalLinkInfo!=null && shExternalLinkInfo.get(rId)!=null) - attrs= shExternalLinkInfo.get(rId); - OOXMLReader.handleSheetPassThroughs(zip, bk, this, p, externalDir, c, attrs); -// OOXMLReader.handlePassThroughs(zip, bk, this, p, c); // pass-through this file and any embedded objects as well - } else { // unknown type - Logger.logWarn("OOXMLAdapter.parse: XLSX Option Not yet Implemented " + ooxmlElement); - } - } - } catch (IOException e) { - Logger.logErr("OOXMLAdapter.parse failed: " + e.toString()); - } - shExternalLinkInfo= null; - } - - /** - * NOTE: commentsX.xml also needs legacy drawing info (vmlDrawingX.vml) - * to define the text box itself including position and size, plus the vml elements - * also define whether the note is hidden - */ - void parseCommentsXML(WorkBookHandle bk, InputStream ii) { - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - - java.util.Stack lastTag= new java.util.Stack(); // keep track of element hierarchy - - ArrayList authors= new ArrayList(); - String addr= ""; - int authId= -1; - Unicodestring comment= null; - //ignore for now: phonetic properties (phoneticPr), phonetic run (rPh) - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("author")) { - authors.add(OOXMLReader.getNextText(xpp)); - } else if (tnm.equals("comment")) { - if (comment!=null && !"".equals(addr)) { - this.createNote(addr, comment, (String)authors.get(authId)); - } - addr= xpp.getAttributeValue("", "ref"); - authId= Integer.valueOf(xpp.getAttributeValue("", "authorId")).intValue(); - comment= null; - } else if (tnm.equals("text")) { - // read in text element - lastTag.push(tnm); - Text t= (Text) Text.parseOOXML(xpp, lastTag, bk); - // don't reset state vars as can there can be more - comment= t.getCommentWithFormatting(); - } - } else if(eventType == XmlPullParser.END_TAG) { - } - eventType = xpp.next(); - } - if (!"".equals(comment.toString()) && !"".equals(addr)) { - this.createNote(addr, comment, (String)authors.get(authId)); - } - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parseCommentsXML: " + e.toString()); - } - return; - } - + try { + for (int i = 0; i < cl.size(); i++) { + String[] c = (String[]) cl.get(i); + String ooxmlElement = c[0]; + + // if(DEBUG) + // Logger.logInfo("OOXMLReader.parse: " + ooxmlElement + ":" + // + c[1] + ":" + c[2]); + + p = StringTool.getPath(c[1]); + p = OOXMLReader.parsePathForZip(p, parentDir); + if (!ooxmlElement.equals("hyperlink")) // if it's a hyperlink + // reference, don't + // strip path info :) + c[1] = StringTool.stripPath(c[1]); + String f = c[1]; + String rId = c[2]; + + if (ooxmlElement.equals("drawing")) { // images, charts + // parse drawing rels to obtain image file names and chart + // xml files + target = OOXMLReader.getEntry(zip, p + "_rels/" + + f.substring(f.lastIndexOf("/") + 1) + ".rels"); + ArrayList drawingFiles = null; + if (target != null) // first retrieve enbedded content in + // .rels (images, charts ...) + drawingFiles = OOXMLReader.parseRels(OOXMLReader + .wrapInputStream(OOXMLReader.wrapInputStream(zip + .getInputStream(target)))); // obtain a + // list of + // image + // file + // references + // for use + // in later + // parsing + target = OOXMLReader.getEntry(zip, p + f); // now get + // drawingml + // file and + // process it + parseDrawingXML(bk, drawingFiles, OOXMLReader + .wrapInputStream(zip + .getInputStream(target)), zip, p, externalDir); + } else if (ooxmlElement.equals("vmldrawing")) { // legacy + // drawing + // elements + target = OOXMLReader.getEntry(zip, p + f); + StringBuffer vml = parseLegacyDrawingXML(bk, OOXMLReader + .wrapInputStream(zip.getInputStream(target))); + target = OOXMLReader.getEntry(zip, p + "_rels/" // get + // external + // objects + // linked to + // the vml + // by + // parsing + // it's rels + + f.substring(f.lastIndexOf("/") + 1) + ".rels"); + if (target != null) { + String[] embeds = OOXMLReader + .storeEmbeds(zip, target, p, externalDir); // passes + // thru + // embedded + // objects + this.addOOXMLShape(new Object[] { vml, embeds }); + } else + this.addOOXMLShape(vml); + /**/ } else if (ooxmlElement.equals("hyperlink")) { // hyperlinks + c = (String[]) cl.get(i); // don't strip path + for (int j = 0; j < hyperlinks.size(); j++) { + if (rId.equals(((String[]) hyperlinks.get(j))[0])) { + String[] h = (String[]) hyperlinks.get(j); + try { // target= cl[2], ref= h[1], desc= h[2] + bk.getWorkSheet(this.getSheetName()) + .getCell(h[1]).setURL(rId, h[2], ""); // TODO: + // hyperlink + // text + // mark + } catch (Exception e) { + Logger.logErr("OOXMLAdapter.parse: failed setting hyperlink to cell " + + h[1] + ":" + e.toString()); + } + break; + } + } + } else if (OOXMLReader.parsePivotTables + && ooxmlElement.equals("pivotTable")) { // sheet-parent + /* + * TODO: Do we really need to get rels ???? + * // must lookup cacheid from rid of + * pivotCacheDefinitionX.xml in + * pivotTableDefinitionX.xml.rels + * target= OOXMLReader.getEntry(zip,p + "_rels/" + + * f.substring(f.lastIndexOf("/")+1)+".rels"); + * ArrayList ptrels= + * parseRels(wrapInputStream(wrapInputStream(zip. + * getInputStream(target)))); + * if (ptrels.size() > 1) { // what could this be? + * Logger. + * logWarn("OOXMLReader.parse: Unknown Pivot Table Association: " + * + ptrels.get(1)); + * } + * String pcd= ((String[])ptrels.get(0))[1]; + * pcd= pcd.substring(pcd.lastIndexOf("/")+1); + * Object cacheid= null; + * for (int z= 0; z < pivotCaches.size(); z++) { + * Object[] o= (Object[]) pivotCaches.get(z); + * if (pcd.equals(o[0])) { + * cacheid= o[1]; + * break; + * } + * } + * + * target = getEntry(zip,p + f); + * PivotTableDefinition.parseOOXML(bk, /*cacheid, * /this, + * wrapInputStream(zip.getInputStream(target))); + */ + try { // SAVE FOR LATER INPUT -- must do after all sheets + // are input ... + pivotTables.put(p + f, bk + .getWorkSheet(this.getSheetName())); + } catch (WorkSheetNotFoundException we) {} + + } else if (ooxmlElement.equals("comments")) { // parse comments + // or notes + target = OOXMLReader.getEntry(zip, p + f); + parseCommentsXML(bk, OOXMLReader + .wrapInputStream(zip.getInputStream(target))); + + // Below are elements we do not as of yet handle + } else if (ooxmlElement.equals("macro") + || ooxmlElement.equals("activeX") + || ooxmlElement.equals("table") + || ooxmlElement.equals("vdependencies") + || ooxmlElement.equals("oleObject") + || ooxmlElement.equals("image") + || ooxmlElement.equals("printerSettings")) { + + String attrs = ""; + if (shExternalLinkInfo != null + && shExternalLinkInfo.get(rId) != null) + attrs = shExternalLinkInfo.get(rId); + OOXMLReader + .handleSheetPassThroughs(zip, bk, this, p, externalDir, c, attrs); + // OOXMLReader.handlePassThroughs(zip, bk, this, p, c); // + // pass-through this file and any embedded objects as well + } else { // unknown type + Logger.logWarn("OOXMLAdapter.parse: XLSX Option Not yet Implemented " + + ooxmlElement); + } + } + } catch (IOException e) { + Logger.logErr("OOXMLAdapter.parse failed: " + e.toString()); + } + shExternalLinkInfo = null; + } - /** - * parse vml - legacy drawing info e.g. mso shapes and lines + note textboxes - *
for now, legacy drawing info is just stored and not parsed into BIFF8 structures - *
i.e. store everything but note textboxes at this time; intention is later on - * to store all mso shapes and objects in BIFF8 records - *
this vml is stored at the sheet level in the boundsheet's OOXMLShapes storage - *
Notes textboxes are being created upon writeLegacyDrawingXML - * @param bk - * @param sheet - * @param ii - * @return StringBuffer rep of saved vml - */ - StringBuffer parseLegacyDrawingXML(WorkBookHandle bk, InputStream ii) { - /** - * more info: - * The Shape element is the basic building block of VML. A shape may exist on its own or within a Group - element. Shape defines many attributes and sub-elements that control the look and behavior of the shape. A - shape must define at least a Path and size (Width, Height). VML 1 also uses properties of the CSS2 style - attribute to specify positioning and sizing - - The ShapeType element defines a definition, or template, for a shape. Such a template is “instantiated” by - creating a Shape element that references the ShapeType. The shape can override any value specified by its - ShapeType, or define attributes and elements the ShapeType does not provide. A ShapeType may not - reference another ShapeType. - The attributes and elements a ShapeType uses are identical to those of the Shape element, with these - exceptions: ShapeType may not use the Type element, Visibility is always hidden. - - Regarding Notes: - The visible box shown for comments attached to cells is persisted using VML. The comment contents are - stored separately as part of SpreadsheetML. - */ - StringBuffer savedVml= new StringBuffer(); - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - // NOTE: since vml controls visibility (hidden or shown), text box size, etc., notes are created upon VML parsing - // and edited here for the actual text and formats ... ms's legacy drawing stuff makes for alot of convoluted processing ((; - FastAddVector nhs= new FastAddVector(); - { - CommentHandle[] anhs= bk.getWorkSheet(this.getSheetName()).getCommentHandles(); - for (int i= 0; i < anhs.length; i++){ - nhs.add(anhs[i]); - } - } - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("shapelayout")) { - // just store - savedVml.append(OOXMLReader.getCurrentElement(xpp)); - } else if (tnm.equals("shapetype")) { - // if spt==202 (shape-type=text box) id="_x0000_t202" then it's note textbox - if (!xpp.getAttributeValue("urn:schemas-microsoft-com:office:office", "spt").equals("202")) {// if it's not a note textbox shapetype, store it - savedVml.append(OOXMLReader.getCurrentElement(xpp)); - } else // ignore element - will be rebuilt upon write - OOXMLReader.getCurrentElement(xpp); - } else if (tnm.equals("shape")) { // this is basic - // several types: can contain images, shapes and notes - if (!xpp.getAttributeValue("", "type").endsWith("_x0000_t202")) {// if it's not a note textbox, save it - // if type="#_x0000_t202" it's a note textbox - savedVml.append(OOXMLReader.getCurrentElement(xpp)); - } else {// add note here, text and formatting will be input upon Comments parse; - int r= -1, c= -1; - boolean visible= false; - short[] bounds= new short[8]; - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - tnm = xpp.getName(); // Anchor - if (tnm.equals("Row")) - r= Integer.valueOf(OOXMLReader.getNextText(xpp)).intValue(); - else if (tnm.equals("Column")) - c= Integer.valueOf(OOXMLReader.getNextText(xpp)).intValue(); - else if (tnm.equals("Visible")) - visible= true; - else if (tnm.equals("Anchor")) { - // get a string rep of the bounds - String sbounds= OOXMLReader.getNextText(xpp); - // prepare for parsing - sbounds= sbounds.replaceAll("[^0-9,]+", ""); - String[] s= sbounds.split(","); - for (int i= 0; i < 8; i++) { - bounds[i]= Short.valueOf(s[i]).shortValue(); - } - } - } else if(eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals("shape")) - break; - } - eventType = xpp.next(); - } - String addr= ExcelTools.formatLocation(new int[] {r, c}); - for (int i= 0; i < nhs.size(); i++) { - CommentHandle nh= (CommentHandle) nhs.get(i); - if (nh.getAddress().endsWith(addr)) { - if (visible) - nh.show(); - nh.setTextBoxBounds(bounds); - nhs.remove(i); - break; - } - } - } - } else if (tnm.equals("xml")) { // ignore :) - } else if (tnm.equals("imagedata")) { - } else { // just store -- - savedVml.append(OOXMLReader.getCurrentElement(xpp)); - } - } else if(eventType == XmlPullParser.END_TAG) { - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parseLegacyDrawingXML: " + e.toString()); - } - return savedVml; - } - - /** - * given drawingML drawing.xml inputstream, parse each twoCellAnchor tag into appropriate image or chart and insert into sheet - * @param bk - * @param sheet - * @param imgFiles list of image or chart files (referenced in drawing.xml via rId) - * @param ii InputStream - * @param zip Current Open ZipOutputStream - */ - void parseDrawingXML(WorkBookHandle bk, ArrayList drawingFiles, InputStream ii, ZipFile zip, String parentDir, String externalDir) { - try { - java.util.Stack lastTag= new java.util.Stack(); // keep track of element hierarchy - - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if(tnm.equals("twoCellAnchor")) { // beginning of DrawingML for a single image or chart - lastTag.push(tnm); - // TODO: handle group shapes which combine images, shapes and/or charts ******************************************************** - TwoCellAnchor t= (TwoCellAnchor) TwoCellAnchor.parseOOXML(xpp, lastTag, bk).cloneElement(); - if (t.hasImage()) { - String s= t.getEmbed(); // rid of embedded object - if (s.indexOf("rId")==0) { // should! - String imgFile= OOXMLReader.parsePathForZip(OOXMLReader.getFilename(drawingFiles, s), parentDir); - ZipEntry img= new ZipEntry(imgFile); - BufferedInputStream is= new BufferedInputStream(zip.getInputStream(img)); - ImageHandle im= new ImageHandle(is, this); - this.insertImage(im); - im.setName(t.getName()); - im.setShapeName(t.getDescr()); - im.setBounds(TwoCellAnchor.convertBoundsToBIFF8(this, t.getBounds())); // must do after insert - im.setSpPr(t.getSppr()); // set image shape properties - im.setEditMovement(t.getEditAs()); // specify how to resize or move - im.update(); // update underlying image record with set data - } - } else if (t.hasChart()) { - String s= t.getChartRId(); - if (s.indexOf("rId")==0) { // should! - String chartfilename= OOXMLReader.getFilename(drawingFiles, s); - String name= t.getName(); - if (name==null || name.equals("null")) { - name="Untitled Chart"; - } - ChartHandle ch= bk.createChart(name, bk.getWorkSheet(this.getSheetName())); - ch.setRelativeBounds(TwoCellAnchor.convertBoundsToBIFF8(this, t.getBounds())); // must do after insert - ch.setEditMovement(t.getEditAs()); // specify how to resize or move - ch.setOOXMLName(name); - chartfilename= OOXMLReader.parsePathForZip(chartfilename, parentDir); - ZipEntry chFile= new ZipEntry(chartfilename); - // must account for default chart settings: set fontx recs to default font for this workbook ... - ch.resetFonts(); // reset all fonts for the chart - ch.removeLegend(); // not all charts have legends! - int ps= chartfilename.lastIndexOf("/") + 1; - ZipEntry rels= OOXMLReader.getEntry(zip,chartfilename.substring(0, ps) + "_rels/" + chartfilename.substring(ps)+".rels"); - if (rels!=null) { // chart file has embeds - usually drawing ml which defines userShapes -//xxx TODO: REFACTOR to get these specifics out - ArrayList chartEmbeds= OOXMLReader.parseRels(OOXMLReader.wrapInputStream(zip.getInputStream(rels))); - for (int i= 0; i < chartEmbeds.size(); i++) { - String[] dr= (String[]) chartEmbeds.get(i); - if (dr[0].equals("userShape")) { // should! - dr[1]= dr[1].substring(dr[1].lastIndexOf("/")+1); - ch.addChartEmbed(new String[] {dr[0], externalDir + dr[1]}); - OOXMLReader.passThrough(zip, parentDir + dr[1], externalDir + dr[1]); // Store Embedded Object on disk for later retrieval - } else if (dr[0].equals("image")) { - String parentp= OOXMLReader.parsePathForZip(dr[1], parentDir); - parentp= parentp.substring(0, parentp.lastIndexOf("/")+1); - dr[1]= dr[1].substring(dr[1].lastIndexOf("/")+1); - ch.addChartEmbed(new String[] {dr[0], externalDir + dr[1]}); - OOXMLReader.passThrough(zip, parentp+dr[1], externalDir +dr[1]); // save the original target file for later re-packaging - } else if (dr[0].equals("themeOverride")) { - String parentp= OOXMLReader.parsePathForZip(dr[1], parentDir); - parentp= parentp.substring(0, parentp.lastIndexOf("/")+1); - dr[1]= dr[1].substring(dr[1].lastIndexOf("/")+1); - ch.addChartEmbed(new String[] {dr[0], externalDir + dr[1]}); - ZipEntry target= OOXMLAdapter.getEntry(zip, parentp + dr[1]); - bk.getWorkBook().getTheme().parseOOXML(bk, OOXMLAdapter.wrapInputStream(zip.getInputStream(target))); - } else { - Logger.logWarn("OOXMLAdapter.parseDrawingML: unknown chart embed " + dr[0]); - } - } - } - // do after parsing rels in case there is override theme colors ... - ch.parseOOXML(OOXMLReader.wrapInputStream(zip.getInputStream(chFile))); - } - } else if (t.hasShape()) { - this.addOOXMLShape(t); // just store shape for later output since prev. versions do not handle shapes - if (t.getEmbed()!=null) { // if this shape has embedded objects such as images - String imgFile= OOXMLReader.parsePathForZip(OOXMLReader.getFilename(drawingFiles, t.getEmbed()), parentDir); // look up embedded rid in content list to get filename - t.setEmbedFilename(imgFile); // save embedded filename for later retrieval - OOXMLReader.passThrough(zip, imgFile, externalDir + imgFile); // Store Embedded Object on disk for later retrieval - } - } else { // TESTING! - Logger.logErr("OOXMLAdapter.parseDrawingXML: Unknown twoCellAnchor type"); - } - } else if (tnm.equals("oneCellAnchor")) { // unclear if this can be root of charts and images as well as shapes - lastTag.push(tnm); - OneCellAnchor oca= (OneCellAnchor) OneCellAnchor.parseOOXML(xpp, lastTag, bk).cloneElement(); - if (oca.hasImage()) { - String s= oca.getEmbed(); // rid of embedded object - if (s.indexOf("rId")==0) { // should! - String imgFile= OOXMLReader.parsePathForZip(OOXMLReader.getFilename(drawingFiles, s), parentDir); - ZipEntry img= new ZipEntry(imgFile); - BufferedInputStream is= new BufferedInputStream - (OOXMLReader.wrapInputStream(zip.getInputStream(img))); - ImageHandle im= new ImageHandle(is, this); - this.insertImage(im); - im.setName(oca.getName()); - im.setShapeName(oca.getDescr()); - im.setBounds(oca.getBounds()); // must do after insert - im.setSpPr(oca.getSppr()); // set image shape properties - im.update(); // update underlying image record with set data - } - } else if (oca.hasChart()) { - String s= oca.getEmbed(); - if (s.indexOf("rId")==0) { // should! - String chart= OOXMLReader.getFilename(drawingFiles, s); - String name= oca.getName(); - if (name==null || name.equals("null")) { - name="Untitled Chart"; - } - ChartHandle ch= bk.createChart(name, bk.getWorkSheet(this.getSheetName())); - ch.setRelativeBounds(oca.getBounds()); - //ch.setChartTitle(name); - chart= OOXMLReader.parsePathForZip(chart, parentDir); - ZipEntry chFile= new ZipEntry(chart); - ch.parseOOXML(OOXMLReader.wrapInputStream(zip.getInputStream(chFile))); - } - } else if (oca.hasShape()) { - this.addOOXMLShape(oca); // just store shape for later output since prev. versions do not handle shapes - } else { // TESTING! - Logger.logErr("OOXMLAdapter.parseDrawingXML: Unknown oneCellAnchor type"); - } - } else if (tnm.equals("userShapes")) { // drawings ONTOP of charts = Reference to Chart Drawing Part - Logger.logErr("OOXMLAdapter.parseDrawingXML: USER SHAPE ENCOUNTERED"); - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parseDrawingXML: failed " + e.toString()); - } - } + /** + * NOTE: commentsX.xml also needs legacy drawing info (vmlDrawingX.vml) + * to define the text box itself including position and size, plus the vml elements + * also define whether the note is hidden + */ + void parseCommentsXML(WorkBookHandle bk, InputStream ii) { + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(ii, null); // using XML 1.0 specification + int eventType = xpp.getEventType(); + + java.util.Stack lastTag = new java.util.Stack(); // keep track of + // element + // hierarchy + + ArrayList authors = new ArrayList(); + String addr = ""; + int authId = -1; + Unicodestring comment = null; + // ignore for now: phonetic properties (phoneticPr), + // phonetic run (rPh) + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("author")) { + authors.add(OOXMLReader.getNextText(xpp)); + } else if (tnm.equals("comment")) { + if (comment != null && !"".equals(addr)) { + this.createNote(addr, comment, (String) authors + .get(authId)); + } + addr = xpp.getAttributeValue("", "ref"); + authId = Integer + .valueOf(xpp.getAttributeValue("", "authorId")) + .intValue(); + comment = null; + } else if (tnm.equals("text")) { + // read in text element + lastTag.push(tnm); + Text t = Text.parseOOXML(xpp, lastTag, bk); + // don't reset state vars as can there can be more + comment = t.getCommentWithFormatting(); + } + } else if (eventType == XmlPullParser.END_TAG) {} + eventType = xpp.next(); + } + if (!"".equals(comment.toString()) && !"".equals(addr)) { + this.createNote(addr, comment, (String) authors.get(authId)); + } + } catch (Exception e) { + Logger.logErr("OOXMLAdapter.parseCommentsXML: " + e.toString()); + } + return; + } - + /** + * parse vml - legacy drawing info e.g. mso shapes and lines + note textboxes + *
for now, legacy drawing info is just stored and not parsed into BIFF8 structures + *
i.e. store everything but note textboxes at this time; intention is later on + * to store all mso shapes and objects in BIFF8 records + *
this vml is stored at the sheet level in the boundsheet's OOXMLShapes storage + *
Notes textboxes are being created upon writeLegacyDrawingXML + * @param bk + * @param sheet + * @param ii + * @return StringBuffer rep of saved vml + */ + StringBuffer parseLegacyDrawingXML(WorkBookHandle bk, InputStream ii) { + /** + * more info: + * The Shape element is the basic building block of VML. A shape may exist on its own or within a Group + element. Shape defines many attributes and sub-elements that control the look and behavior of the shape. A + shape must define at least a Path and size (Width, Height). VML 1 also uses properties of the CSS2 style + attribute to specify positioning and sizing + + The ShapeType element defines a definition, or template, for a shape. Such a template is “instantiated” by + creating a Shape element that references the ShapeType. The shape can override any value specified by its + ShapeType, or define attributes and elements the ShapeType does not provide. A ShapeType may not + reference another ShapeType. + The attributes and elements a ShapeType uses are identical to those of the Shape element, with these + exceptions: ShapeType may not use the Type element, Visibility is always hidden. + + Regarding Notes: + The visible box shown for comments attached to cells is persisted using VML. The comment contents are + stored separately as part of SpreadsheetML. + */ + StringBuffer savedVml = new StringBuffer(); + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(ii, null); // using XML 1.0 specification + int eventType = xpp.getEventType(); + // NOTE: since vml controls visibility (hidden or shown), + // text box size, etc., notes are created upon VML parsing + // and edited here for the actual text and formats ... ms's + // legacy drawing stuff makes for alot of convoluted + // processing ((; + FastAddVector nhs = new FastAddVector(); + { + CommentHandle[] anhs = bk.getWorkSheet(this.getSheetName()) + .getCommentHandles(); + for (int i = 0; i < anhs.length; i++) { + nhs.add(anhs[i]); + } + } + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("shapelayout")) { + // just store + savedVml.append(OOXMLReader.getCurrentElement(xpp)); + } else if (tnm.equals("shapetype")) { + // if spt==202 (shape-type=text box) id="_x0000_t202" + // then it's note textbox + if (!xpp.getAttributeValue("urn:schemas-microsoft-com:office:office", "spt") + .equals("202")) {// if it's not a note textbox + // shapetype, store it + savedVml.append(OOXMLReader.getCurrentElement(xpp)); + } else // ignore element - will be rebuilt upon write + OOXMLReader.getCurrentElement(xpp); + } else if (tnm.equals("shape")) { // this is basic + // several types: can contain images, shapes and notes + if (!xpp.getAttributeValue("", "type") + .endsWith("_x0000_t202")) {// if it's not a note + // textbox, save it + // if type="#_x0000_t202" it's a note textbox + savedVml.append(OOXMLReader.getCurrentElement(xpp)); + } else {// add note here, text and formatting will be + // input upon Comments parse; + int r = -1, c = -1; + boolean visible = false; + short[] bounds = new short[8]; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + tnm = xpp.getName(); // Anchor + if (tnm.equals("Row")) + r = Integer + .valueOf(OOXMLReader + .getNextText(xpp)) + .intValue(); + else if (tnm.equals("Column")) + c = Integer + .valueOf(OOXMLReader + .getNextText(xpp)) + .intValue(); + else if (tnm.equals("Visible")) + visible = true; + else if (tnm.equals("Anchor")) { + // get a string rep of the bounds + String sbounds = OOXMLReader + .getNextText(xpp); + // prepare for parsing + sbounds = sbounds + .replaceAll("[^0-9,]+", ""); + String[] s = sbounds.split(","); + for (int i = 0; i < 8; i++) { + bounds[i] = Short.valueOf(s[i]) + .shortValue(); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals("shape")) + break; + } + eventType = xpp.next(); + } + String addr = ExcelTools + .formatLocation(new int[] { r, c }); + for (int i = 0; i < nhs.size(); i++) { + CommentHandle nh = (CommentHandle) nhs.get(i); + if (nh.getAddress().endsWith(addr)) { + if (visible) + nh.show(); + nh.setTextBoxBounds(bounds); + nhs.remove(i); + break; + } + } + } + } else if (tnm.equals("xml")) { // ignore :) + } else if (tnm.equals("imagedata")) {} else { // just store + // -- + savedVml.append(OOXMLReader.getCurrentElement(xpp)); + } + } else if (eventType == XmlPullParser.END_TAG) {} + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("OOXMLAdapter.parseLegacyDrawingXML: " + + e.toString()); + } + return savedVml; + } - - - /** - * clear out object references in prep for closing workbook - */ + /** + * given drawingML drawing.xml inputstream, parse each twoCellAnchor tag into appropriate image or chart and insert into sheet + * @param bk + * @param sheet + * @param imgFiles list of image or chart files (referenced in drawing.xml via rId) + * @param ii InputStream + * @param zip Current Open ZipOutputStream + */ + void parseDrawingXML(WorkBookHandle bk, ArrayList drawingFiles, InputStream ii, ZipFile zip, String parentDir, String externalDir) { + try { + java.util.Stack lastTag = new java.util.Stack(); // keep track of + // element + // hierarchy + + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + + xpp.setInput(ii, null); // using XML 1.0 specification + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("twoCellAnchor")) { // beginning of DrawingML + // for a single image or + // chart + lastTag.push(tnm); + // TODO: handle group shapes which combine images, + // shapes and/or charts + // ******************************************************** + TwoCellAnchor t = (TwoCellAnchor) TwoCellAnchor + .parseOOXML(xpp, lastTag, bk).cloneElement(); + if (t.hasImage()) { + String s = t.getEmbed(); // rid of embedded object + if (s.indexOf("rId") == 0) { // should! + String imgFile = OOXMLReader + .parsePathForZip(OOXMLReader + .getFilename(drawingFiles, s), parentDir); + ZipEntry img = new ZipEntry(imgFile); + BufferedInputStream is = new BufferedInputStream( + zip.getInputStream(img)); + ImageHandle im = new ImageHandle(is, this); + this.insertImage(im); + im.setName(t.getName()); + im.setShapeName(t.getDescr()); + im.setBounds(TwoCellAnchor + .convertBoundsToBIFF8(this, t + .getBounds())); // must do after + // insert + im.setSpPr(t.getSppr()); // set image shape + // properties + im.setEditMovement(t.getEditAs()); // specify + // how to + // resize or + // move + im.update(); // update underlying image record + // with set data + } + } else if (t.hasChart()) { + String s = t.getChartRId(); + if (s.indexOf("rId") == 0) { // should! + String chartfilename = OOXMLReader + .getFilename(drawingFiles, s); + String name = t.getName(); + if (name == null || name.equals("null")) { + name = "Untitled Chart"; + } + ChartHandle ch = bk.createChart(name, bk + .getWorkSheet(this.getSheetName())); + ch.setRelativeBounds(TwoCellAnchor + .convertBoundsToBIFF8(this, t + .getBounds())); // must do after + // insert + ch.setEditMovement(t.getEditAs()); // specify + // how to + // resize or + // move + ch.setOOXMLName(name); + chartfilename = OOXMLReader + .parsePathForZip(chartfilename, parentDir); + ZipEntry chFile = new ZipEntry(chartfilename); + // must account for default chart settings: set + // fontx recs to default font for this workbook + // ... + ch.resetFonts(); // reset all fonts for the + // chart + ch.removeLegend(); // not all charts have + // legends! + int ps = chartfilename.lastIndexOf("/") + 1; + ZipEntry rels = OOXMLReader + .getEntry(zip, chartfilename + .substring(0, ps) + "_rels/" + + chartfilename.substring(ps) + + ".rels"); + if (rels != null) { // chart file has embeds - + // usually drawing ml which + // defines userShapes + // xxx TODO: REFACTOR to get these specifics + // out + ArrayList chartEmbeds = OOXMLReader + .parseRels(OOXMLReader + .wrapInputStream(zip + .getInputStream(rels))); + for (int i = 0; i < chartEmbeds + .size(); i++) { + String[] dr = (String[]) chartEmbeds + .get(i); + if (dr[0].equals("userShape")) { // should! + dr[1] = dr[1].substring(dr[1] + .lastIndexOf("/") + 1); + ch.addChartEmbed(new String[] { + dr[0], + externalDir + dr[1] }); + OOXMLReader + .passThrough(zip, parentDir + + dr[1], externalDir + + dr[1]); // Store + // Embedded + // Object + // on + // disk + // for + // later + // retrieval + } else if (dr[0].equals("image")) { + String parentp = OOXMLReader + .parsePathForZip(dr[1], parentDir); + parentp = parentp + .substring(0, parentp + .lastIndexOf("/") + + 1); + dr[1] = dr[1].substring(dr[1] + .lastIndexOf("/") + 1); + ch.addChartEmbed(new String[] { + dr[0], + externalDir + dr[1] }); + OOXMLReader.passThrough(zip, parentp + + dr[1], externalDir + + dr[1]); // save + // the + // original + // target + // file + // for + // later + // re-packaging + } else if (dr[0] + .equals("themeOverride")) { + String parentp = OOXMLReader + .parsePathForZip(dr[1], parentDir); + parentp = parentp + .substring(0, parentp + .lastIndexOf("/") + + 1); + dr[1] = dr[1].substring(dr[1] + .lastIndexOf("/") + 1); + ch.addChartEmbed(new String[] { + dr[0], + externalDir + dr[1] }); + ZipEntry target = OOXMLAdapter + .getEntry(zip, parentp + + dr[1]); + bk.getWorkBook().getTheme() + .parseOOXML(bk, OOXMLAdapter + .wrapInputStream(zip + .getInputStream(target))); + } else { + Logger.logWarn("OOXMLAdapter.parseDrawingML: unknown chart embed " + + dr[0]); + } + } + } + // do after parsing rels in case there is + // override theme colors ... + ch.parseOOXML(OOXMLReader.wrapInputStream(zip + .getInputStream(chFile))); + } + } else if (t.hasShape()) { + this.addOOXMLShape(t); // just store shape for later + // output since prev. + // versions do not handle + // shapes + if (t.getEmbed() != null) { // if this shape has + // embedded objects such + // as images + String imgFile = OOXMLReader + .parsePathForZip(OOXMLReader + .getFilename(drawingFiles, t + .getEmbed()), parentDir); // look + // up + // embedded + // rid + // in + // content + // list + // to + // get + // filename + t.setEmbedFilename(imgFile); // save embedded + // filename for + // later + // retrieval + OOXMLReader + .passThrough(zip, imgFile, externalDir + + imgFile); // Store Embedded + // Object on disk + // for later + // retrieval + } + } else { // TESTING! + Logger.logErr("OOXMLAdapter.parseDrawingXML: Unknown twoCellAnchor type"); + } + } else if (tnm.equals("oneCellAnchor")) { // unclear if this + // can be root + // of charts and + // images as + // well as + // shapes + lastTag.push(tnm); + OneCellAnchor oca = (OneCellAnchor) OneCellAnchor + .parseOOXML(xpp, lastTag, bk).cloneElement(); + if (oca.hasImage()) { + String s = oca.getEmbed(); // rid of embedded object + if (s.indexOf("rId") == 0) { // should! + String imgFile = OOXMLReader + .parsePathForZip(OOXMLReader + .getFilename(drawingFiles, s), parentDir); + ZipEntry img = new ZipEntry(imgFile); + BufferedInputStream is = new BufferedInputStream( + OOXMLReader.wrapInputStream(zip + .getInputStream(img))); + ImageHandle im = new ImageHandle(is, this); + this.insertImage(im); + im.setName(oca.getName()); + im.setShapeName(oca.getDescr()); + im.setBounds(oca.getBounds()); // must do after + // insert + im.setSpPr(oca.getSppr()); // set image shape + // properties + im.update(); // update underlying image record + // with set data + } + } else if (oca.hasChart()) { + String s = oca.getEmbed(); + if (s.indexOf("rId") == 0) { // should! + String chart = OOXMLReader + .getFilename(drawingFiles, s); + String name = oca.getName(); + if (name == null || name.equals("null")) { + name = "Untitled Chart"; + } + ChartHandle ch = bk.createChart(name, bk + .getWorkSheet(this.getSheetName())); + ch.setRelativeBounds(oca.getBounds()); + // ch.setChartTitle(name); + chart = OOXMLReader + .parsePathForZip(chart, parentDir); + ZipEntry chFile = new ZipEntry(chart); + ch.parseOOXML(OOXMLReader.wrapInputStream(zip + .getInputStream(chFile))); + } + } else if (oca.hasShape()) { + this.addOOXMLShape(oca); // just store shape for + // later output since + // prev. versions do not + // handle shapes + } else { // TESTING! + Logger.logErr("OOXMLAdapter.parseDrawingXML: Unknown oneCellAnchor type"); + } + } else if (tnm.equals("userShapes")) { // drawings ONTOP of + // charts = + // Reference to + // Chart Drawing + // Part + Logger.logErr("OOXMLAdapter.parseDrawingXML: USER SHAPE ENCOUNTERED"); + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("OOXMLAdapter.parseDrawingXML: failed " + + e.toString()); + } + } + + /** + * clear out object references in prep for closing workbook + */ + @Override public void close() { - wkbook= null; + wkbook = null; for (Colinfo info : colinfos.values()) { - if (null != info) info.close(); + if (null != info) + info.close(); } - colinfos.clear(); - Iterator ii= rows.keySet().iterator(); + colinfos.clear(); + Iterator ii = rows.keySet().iterator(); while (ii.hasNext()) { - Row r= (Row) rows.get(ii.next()); + Row r = rows.get(ii.next()); r.close(); } - rows.clear(); - - cellsByRow = - new TreeMap( - new CellAddressible.RowMajorComparator() ); - - cellsByCol = - new TreeMap( - new CellAddressible.ColumnMajorComparator() ); + rows.clear(); + + cellsByRow = new TreeMap( + new CellAddressible.RowMajorComparator()); + + cellsByCol = new TreeMap( + new CellAddressible.ColumnMajorComparator()); // TODO: clear recs - arrayformulas.clear(); - // TODO: clear recs - transferXfs.clear(); - // TODO: clear recs - transferFonts.clear(); - imageMap.clear(); - charts.clear(); - ooxmlObjects.clear(); - if (ooxmlShapes!=null) ooxmlShapes.clear(); - - ooautofilter= null; - mc.clear(); - sheetview= null; // OOXML sheet view object - sheetPr= null; // OOXML sheetPr object - if (lastselection!=null) { - lastselection.close(); - lastselection= null; - } - if (protector!=null) { - protector.close(); - protector= null; - } - - if (sheetNameRecs!=null) { - ii= sheetNameRecs.keySet().iterator(); - while (ii.hasNext()) { - Name n= (Name) sheetNameRecs.get(ii.next()); - n.close(); - } - sheetNameRecs.clear(); - } - - for (int i= 0; i < cond_formats.size(); i++) { - Condfmt c= (Condfmt) cond_formats .get(i); - c.close(); - } - cond_formats.clear(); - - for (int i= 0; i < autoFilters.size(); i++) { - AutoFilter a= (AutoFilter) autoFilters.get(i); - a.close(); - } - autoFilters.clear(); - - if (lastCell!=null) { - ((XLSRecord)lastCell).close(); - lastCell= null; - } - if (lastRow!=null) { - lastRow.close(); - lastRow= null; - } - - if (win2!=null) { - win2.close(); - win2= null; - } - if (scl!=null) { - scl.close(); - scl= null; - } - if (pane!=null) { - pane.close(); - pane= null; - } - if (dval!=null) { - dval.close(); - dval= null; - } - if (hdr!=null) { - hdr.close(); - hdr= null; - } - if (ftr!=null) { - ftr.close(); - ftr= null; - } - if (wsbool!=null) { - wsbool.close(); - wsbool= null; - } - if (guts!=null) { - guts.close(); - guts= null; - } - if (dimensions!=null) { - dimensions.close(); - dimensions= null; - } - if (mybof!=null) { - mybof.close(); - mybof= null; - } - if (myeof!=null) { - myeof.close(); - myeof= null; - } - if (myidx!=null) { - myidx.close(); - myidx= null; - } - for (int i= 0; i < printRecs.size(); i++) { - XLSRecord r= (XLSRecord)printRecs.get(i); - r.close(); - } - printRecs.clear(); - - // clear out refs by sheet recs - for (int j= 0; j < SheetRecs.size(); j++) { - XLSRecord r= (XLSRecord) SheetRecs.get(j); - r.close(); - } - SheetRecs.clear(); - if (localrecs!=null) - localrecs.clear(); - // col records - + arrayformulas.clear(); + // TODO: clear recs + transferXfs.clear(); + // TODO: clear recs + transferFonts.clear(); + imageMap.clear(); + charts.clear(); + ooxmlObjects.clear(); + if (ooxmlShapes != null) + ooxmlShapes.clear(); + + ooautofilter = null; + mc.clear(); + sheetview = null; // OOXML sheet view object + sheetPr = null; // OOXML sheetPr object + if (lastselection != null) { + lastselection.close(); + lastselection = null; + } + if (protector != null) { + protector.close(); + protector = null; + } + + if (sheetNameRecs != null) { + ii = sheetNameRecs.keySet().iterator(); + while (ii.hasNext()) { + Name n = (Name) sheetNameRecs.get(ii.next()); + n.close(); + } + sheetNameRecs.clear(); + } + + for (int i = 0; i < cond_formats.size(); i++) { + Condfmt c = (Condfmt) cond_formats.get(i); + c.close(); + } + cond_formats.clear(); + + for (int i = 0; i < autoFilters.size(); i++) { + AutoFilter a = (AutoFilter) autoFilters.get(i); + a.close(); + } + autoFilters.clear(); + + if (lastCell != null) { + ((XLSRecord) lastCell).close(); + lastCell = null; + } + if (lastRow != null) { + lastRow.close(); + lastRow = null; + } + + if (win2 != null) { + win2.close(); + win2 = null; + } + if (scl != null) { + scl.close(); + scl = null; + } + if (pane != null) { + pane.close(); + pane = null; + } + if (dval != null) { + dval.close(); + dval = null; + } + if (hdr != null) { + hdr.close(); + hdr = null; + } + if (ftr != null) { + ftr.close(); + ftr = null; + } + if (wsbool != null) { + wsbool.close(); + wsbool = null; + } + if (guts != null) { + guts.close(); + guts = null; + } + if (dimensions != null) { + dimensions.close(); + dimensions = null; + } + if (mybof != null) { + mybof.close(); + mybof = null; + } + if (myeof != null) { + myeof.close(); + myeof = null; + } + if (myidx != null) { + myidx.close(); + myidx = null; + } + for (int i = 0; i < printRecs.size(); i++) { + XLSRecord r = (XLSRecord) printRecs.get(i); + r.close(); + } + printRecs.clear(); + + // clear out refs by sheet recs + for (int j = 0; j < SheetRecs.size(); j++) { + XLSRecord r = (XLSRecord) SheetRecs.get(j); + r.close(); + } + SheetRecs.clear(); + if (localrecs != null) + localrecs.clear(); + // col records + } + public DefColWidth getDefColWidth() { return defColWidth; } + public void setDefColWidth(DefColWidth defColWidth) { this.defColWidth = defColWidth; } diff --git a/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.java b/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.java index 918d503..59cf851 100644 --- a/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.java +++ b/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.java @@ -480,7 +480,7 @@ public boolean containsReference(int[] rc) { break; } } -//io.starter.OpenXLS.util.Logger.log(""); +//io.starter.toolkit.Logger.log(""); return false; } diff --git a/src/main/java/io/starter/formats/XLS/ExpressionParser.java b/src/main/java/io/starter/formats/XLS/ExpressionParser.java index df06ef3..8041eb5 100644 --- a/src/main/java/io/starter/formats/XLS/ExpressionParser.java +++ b/src/main/java/io/starter/formats/XLS/ExpressionParser.java @@ -5,18 +5,25 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -26,88 +33,141 @@ import java.util.Stack; import java.util.Vector; -import io.starter.toolkit.*; -import io.starter.formats.XLS.formulas.*; +import io.starter.formats.XLS.formulas.FunctionConstants; +import io.starter.formats.XLS.formulas.Ptg; +import io.starter.formats.XLS.formulas.PtgAdd; +import io.starter.formats.XLS.formulas.PtgArea; +import io.starter.formats.XLS.formulas.PtgArea3d; +import io.starter.formats.XLS.formulas.PtgAreaErr3d; +import io.starter.formats.XLS.formulas.PtgAreaN; +import io.starter.formats.XLS.formulas.PtgArray; +import io.starter.formats.XLS.formulas.PtgAtr; +import io.starter.formats.XLS.formulas.PtgBool; +import io.starter.formats.XLS.formulas.PtgConcat; +import io.starter.formats.XLS.formulas.PtgDiv; +import io.starter.formats.XLS.formulas.PtgEQ; +import io.starter.formats.XLS.formulas.PtgEndSheet; +import io.starter.formats.XLS.formulas.PtgErr; +import io.starter.formats.XLS.formulas.PtgExp; +import io.starter.formats.XLS.formulas.PtgFunc; +import io.starter.formats.XLS.formulas.PtgFuncVar; +import io.starter.formats.XLS.formulas.PtgGE; +import io.starter.formats.XLS.formulas.PtgGT; +import io.starter.formats.XLS.formulas.PtgInt; +import io.starter.formats.XLS.formulas.PtgIsect; +import io.starter.formats.XLS.formulas.PtgLE; +import io.starter.formats.XLS.formulas.PtgLT; +import io.starter.formats.XLS.formulas.PtgMemArea; +import io.starter.formats.XLS.formulas.PtgMemAreaA; +import io.starter.formats.XLS.formulas.PtgMemAreaN; +import io.starter.formats.XLS.formulas.PtgMemAreaNV; +import io.starter.formats.XLS.formulas.PtgMemErr; +import io.starter.formats.XLS.formulas.PtgMemFunc; +import io.starter.formats.XLS.formulas.PtgMissArg; +import io.starter.formats.XLS.formulas.PtgMlt; +import io.starter.formats.XLS.formulas.PtgMystery; +import io.starter.formats.XLS.formulas.PtgNE; +import io.starter.formats.XLS.formulas.PtgName; +import io.starter.formats.XLS.formulas.PtgNameX; +import io.starter.formats.XLS.formulas.PtgNumber; +import io.starter.formats.XLS.formulas.PtgParen; +import io.starter.formats.XLS.formulas.PtgPercent; +import io.starter.formats.XLS.formulas.PtgPower; +import io.starter.formats.XLS.formulas.PtgRange; +import io.starter.formats.XLS.formulas.PtgRef; +import io.starter.formats.XLS.formulas.PtgRef3d; +import io.starter.formats.XLS.formulas.PtgRefErr; +import io.starter.formats.XLS.formulas.PtgRefErr3d; +import io.starter.formats.XLS.formulas.PtgRefN; +import io.starter.formats.XLS.formulas.PtgStr; +import io.starter.formats.XLS.formulas.PtgSub; +import io.starter.formats.XLS.formulas.PtgUMinus; +import io.starter.formats.XLS.formulas.PtgUPlus; +import io.starter.formats.XLS.formulas.PtgUnion; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.CompatibleVector; +import io.starter.toolkit.Logger; /** */ -public final class ExpressionParser implements java.io.Serializable { - /** +public final class ExpressionParser implements java.io.Serializable { + /** * * */ - private static final long serialVersionUID = 4745215965823234010L; - private static int DEBUGLEVEL = 0; - /* All of the operand values - - Section of binary operator PTG's. These pop the two - top values out of a stack and perform an operation on - them before pushing back in - */ - // really "special" one, read all about it. - public static final short ptgExp = 0x1; - public static final short ptgAdd = 0x3; - public static final short ptgSub = 0x4; - public static final short ptgMlt = 0x5; - public static final short ptgDiv = 0x6; - public static final short ptgPower = 0x7; - public static final short ptgConcat = 0x8; - public static final short ptgLT = 0x09; - public static final short ptgLE = 0x0a; - public static final short ptgEQ = 0x0b; - public static final short ptgGE = 0x0c; - public static final short ptgGT = 0x0d; - public static final short ptgNE = 0x0e; - public static final short ptgIsect = 0x0f; - public static final short ptgUnion = 0x10; - public static final short ptgRange = 0x11; - //End of binary operator PTG's - - //Unary Operator tokens - public static final short ptgUPlus = 0x12; - public static final short ptgUMinus = 0x13; //todo - public static final short ptgPercent = 0x14; //todo - - // Controls - public static final short ptgParen = 0x15; - public static final short ptgAtr = 0x19; - // End of Controls - - // Constant operators - public static final short ptgMissArg = 0x16; - public static final short ptgStr = 0x17; - public static final short ptgEndSheet = 0x1b; - public static final short ptgErr = 0x1c; - public static final short ptgBool = 0x1d; - public static final short ptgInt = 0x1e; - public static final short ptgNum = 0x1f; - // End of Constant Operators - - public static final short ptgArray = 0x20; - public static final short ptgFunc = 0x21; - public static final short ptgFuncVar = 0x22; - public static final short ptgName = 0x23; - public static final short ptgRef = 0x24; - public static final short ptgArea = 0x25; - public static final short ptgMemArea = 0x26; - public static final short ptgMemErr = 0x27; - public static final short ptgMemFunc = 0x29; - public static final short ptgRefErr = 0x2a; - public static final short ptgAreaErr = 0x2b; - public static final short ptgRefN = 0x2c; - public static final short ptgAreaN = 0x2d; - public static final short ptgNameX = 0x39; - public static final short ptgRef3d = 0x3a; - public static final short ptgArea3d = 0x3b; - public static final short ptgRefErr3d = 0x3c; + private static final long serialVersionUID = 4745215965823234010L; + private static int DEBUGLEVEL = 0; + /* + * All of the operand values + * + * Section of binary operator PTG's. These pop the two + * top values out of a stack and perform an operation on + * them before pushing back in + */ + // really "special" one, read all about it. + public static final short ptgExp = 0x1; + public static final short ptgAdd = 0x3; + public static final short ptgSub = 0x4; + public static final short ptgMlt = 0x5; + public static final short ptgDiv = 0x6; + public static final short ptgPower = 0x7; + public static final short ptgConcat = 0x8; + public static final short ptgLT = 0x09; + public static final short ptgLE = 0x0a; + public static final short ptgEQ = 0x0b; + public static final short ptgGE = 0x0c; + public static final short ptgGT = 0x0d; + public static final short ptgNE = 0x0e; + public static final short ptgIsect = 0x0f; + public static final short ptgUnion = 0x10; + public static final short ptgRange = 0x11; + // End of binary operator PTG's + + // Unary Operator tokens + public static final short ptgUPlus = 0x12; + public static final short ptgUMinus = 0x13; // todo + public static final short ptgPercent = 0x14; // todo + + // Controls + public static final short ptgParen = 0x15; + public static final short ptgAtr = 0x19; + // End of Controls + + // Constant operators + public static final short ptgMissArg = 0x16; + public static final short ptgStr = 0x17; + public static final short ptgEndSheet = 0x1b; + public static final short ptgErr = 0x1c; + public static final short ptgBool = 0x1d; + public static final short ptgInt = 0x1e; + public static final short ptgNum = 0x1f; + // End of Constant Operators + + public static final short ptgArray = 0x20; + public static final short ptgFunc = 0x21; + public static final short ptgFuncVar = 0x22; + public static final short ptgName = 0x23; + public static final short ptgRef = 0x24; + public static final short ptgArea = 0x25; + public static final short ptgMemArea = 0x26; + public static final short ptgMemErr = 0x27; + public static final short ptgMemFunc = 0x29; + public static final short ptgRefErr = 0x2a; + public static final short ptgAreaErr = 0x2b; + public static final short ptgRefN = 0x2c; + public static final short ptgAreaN = 0x2d; + public static final short ptgNameX = 0x39; + public static final short ptgRef3d = 0x3a; + public static final short ptgArea3d = 0x3b; + public static final short ptgRefErr3d = 0x3c; // who knows, added to fix broken Named ranges -jm 03/26/04 - public static final short ptgAreaErr3d = 0x3d; - public static final short ptgMemAreaA = 0x66; - public static final short ptgMemAreaNV = 0x4e; - public static final short ptgMemAreaN = 0x2e; - + public static final short ptgAreaErr3d = 0x3d; + public static final short ptgMemAreaA = 0x66; + public static final short ptgMemAreaNV = 0x4e; + public static final short ptgMemAreaN = 0x2e; + /** * Parse the byte array, create component Ptg's and insert * them into a stack. @@ -118,897 +178,1065 @@ public final class ExpressionParser implements java.io.Serializable { * @param rec * @return */ - public static Stack parseExpression(byte[] function, XLSRecord rec){ - return ExpressionParser.parseExpression(function,rec,function.length); - } - - /** - * Parse the byte array, create component Ptg's and insert them into - * a stack. - * - * Feb 8, 2010 - * @param function - * @param rec - * @param expressionLen - * @return - */ - public static Stack parseExpression(byte[] function, XLSRecord rec, int expressionLen){ - Stack stack = new Stack(); - short ptg = 0x0; - int ptgLen = 0; - boolean hasArrays = false; - /* Not really needed - //boolean hasPtgExtraMem= false; - //PtgMemArea pma= null;*/ - CompatibleVector arrayLocs = new CompatibleVector(); - if(expressionLen > function.length)expressionLen = function.length; // deal with out of spec formulas (testJapanese:Open25.xls) -jm - // KSC: shared formula changes for peformance: now PtgRefN's/PtgAreaN's are instantiated and reference-tracked (of a sort) ... - XLSRecord p= rec; // parent - - // iterate the expression and create Ptgs. - for (int i = 0; i 5)Logger.logInfo("ptgExp Located"); - if (i == 0){// MUST BE THE ONLY PTG in the formula expression - PtgExp px = new PtgExp(); - ptgLen = px.getLength(); - byte[] b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - px.setParentRec(p); - px.init(b); - stack.push(px); - break; - } - // ptgStr is one of the only ptg's that varies in length, so there is some special handling - // going on for it. - case ptgStr: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgStr Located"); - int x = i; - x += 1; // move past the opcode to the cch - ptgLen = (int)(function[x] & 0xff); // this is the cch - short theGrbit = function[x+1];// this is the grbit; - if ((theGrbit & 0x1) == 0x1){ - // unicode string - ptgLen = ptgLen*2; - } - ptgLen += 3; // include the PtgId, cch, & grbit; - PtgStr pst = new PtgStr(); - byte[] b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pst.init(b); - pst.setParentRec(p); - stack.push(pst); - break; - /* */ - - case ptgMemAreaA: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgMemAreaA Located" + function[i]); - x = i; - x += 5; // move past the opcode & reserved to the cce - ptgLen = ByteTools.readShort(function[x], function[x+1]); // this is the cce - ptgLen += 7; // include the PtgId, cce, & reserv; - PtgMemAreaA pmema = new PtgMemAreaA(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pmema.init(b); - pmema.setParentRec(p); - stack.push(pmema); - break; + public static Stack parseExpression(byte[] function, XLSRecord rec) { + return ExpressionParser.parseExpression(function, rec, function.length); + } - case ptgMemAreaN: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgMemAreaN Located" + function[i]); - PtgMemAreaN pmemn = new PtgMemAreaN(); - ptgLen = pmemn.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pmemn.init(b); - pmemn.setParentRec(p); - stack.push(pmemn); - break; - - case ptgMemAreaNV: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgMemAreaNV Located" + function[i]); - x = i; - x += 5; // move past the opcode & reserved to the cce - ptgLen = ByteTools.readShort(function[x], function[x+1]); // this is the cce - ptgLen += 7; // include the PtgId, cce, & reserv; - PtgMemAreaNV pmemv = new PtgMemAreaNV(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pmemv.init(b); - pmemv.setParentRec(p); - stack.push(pmemv); - break; + /** + * Parse the byte array, create component Ptg's and insert them into + * a stack. + * + * Feb 8, 2010 + * @param function + * @param rec + * @param expressionLen + * @return + */ + public static Stack parseExpression(byte[] function, XLSRecord rec, int expressionLen) { + Stack stack = new Stack(); + short ptg = 0x0; + int ptgLen = 0; + boolean hasArrays = false; + /* + * Not really needed + * //boolean hasPtgExtraMem= false; + * //PtgMemArea pma= null; + */ + CompatibleVector arrayLocs = new CompatibleVector(); + if (expressionLen > function.length) + expressionLen = function.length; // deal with out of spec formulas + // (testJapanese:Open25.xls) -jm + // KSC: shared formula changes for peformance: now + // PtgRefN's/PtgAreaN's are instantiated and + // reference-tracked (of a sort) ... + XLSRecord p = rec; // parent -// ptgMemArea also varies in length... - case ptgMemArea: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgMemArea Located" + function[i]); - ptgLen= 7; - PtgMemArea pmem = new PtgMemArea(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pmem.init(b); - // now grab the rest of the "extra data" that defines the ptgmemarea - // these are separate ptgs (PtgArea, PtgRef's ... plus PtgUnions) - // that comprise the PtgMemArea coordinates - pmem.setParentRec(p); - i+= ptgLen; // after PtgMemArea record, get subexpression - ptgLen= pmem.getnTokens(); - b= new byte[ptgLen]; - if((ptgLen+i) <= function.length) + // iterate the expression and create Ptgs. + for (int i = 0; i < expressionLen;) { + // check if the 40 bit is set, is it a Array class? + if ((function[i] & 0x40) == 0x40) { + // rec is a value class + // we need to strip the high-order bits and set the 0x20 bit + ptg = (short) ((function[i] | 0x20) & 0x3f); + } else { + // the bit is already set, just strip the high order bits + // rec may be an array class. need to figure rec one out. + ptg = (short) (function[i] & 0x3f); + } + switch (ptg) { + + case ptgExp: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgExp Located"); + if (i == 0) {// MUST BE THE ONLY PTG in the formula expression + PtgExp px = new PtgExp(); + ptgLen = px.getLength(); + byte[] b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pmem.setSubExpression(b); - stack.push(pmem); - //hasPtgExtraMem= true; // has a PtgExtraMem structure after end of parsed expression: The PtgExtraMem structure specifies a range that corresponds to a PtgMemArea as specified in RgbExtra. - //pma= pmem; // save for later + px.setParentRec(p); + px.init(b); + stack.push(px); break; - - case ptgMemFunc: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgMemFunc Located"); - PtgMemFunc pmemf = new PtgMemFunc(); - x = i; - x += 1; // move past the opcode to the cce - ptgLen = ByteTools.readShort(function[x], function[x+1]); // this is the cce - ptgLen += 3; // include the PtgId, cce, & reserv; - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pmemf.setParentRec(p); - pmemf.init(b); - stack.push(pmemf); - break; - - case ptgInt: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgInt Located"); - PtgInt pi = new PtgInt(); - ptgLen = pi.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pi.init(b); - pi.setParentRec(p); - stack.push(pi); - break; - - case ptgErr: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgErr Located"); - PtgErr perr = new PtgErr(); - ptgLen = perr.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - perr.init(b); - perr.setParentRec(p); - stack.push(perr); - break; - - case ptgNum: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgNum Located"); - PtgNumber pnum = new PtgNumber(); - ptgLen = pnum.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pnum.init(b); - pnum.setParentRec(p); - stack.push(pnum); - break; - - case ptgBool: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgBool Located"); - PtgBool pboo = new PtgBool(); - ptgLen = pboo.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pboo.init(b); - pboo.setParentRec(p); - stack.push(pboo); - break; - - case ptgName: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgName Located"); - PtgName pn = new PtgName(); - ptgLen = pn.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pn.setParentRec(p); - pn.init(b); - pn.addListener(); - stack.push(pn); - int chk = (i+ptgLen); - if(chk 1)Logger.logWarn("Undocumented Name Record mystery byte encountered in Formula: "); - i++; - } + } + // ptgStr is one of the only ptg's that varies in length, so + // there is some special handling + // going on for it. + case ptgStr: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgStr Located"); + int x = i; + x += 1; // move past the opcode to the cch + ptgLen = function[x] & 0xff; // this is the cch + short theGrbit = function[x + 1];// this is the grbit; + if ((theGrbit & 0x1) == 0x1) { + // unicode string + ptgLen = ptgLen * 2; + } + ptgLen += 3; // include the PtgId, cch, & grbit; + PtgStr pst = new PtgStr(); + byte[] b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pst.init(b); + pst.setParentRec(p); + stack.push(pst); + break; + /* */ + + case ptgMemAreaA: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemAreaA Located" + function[i]); + x = i; + x += 5; // move past the opcode & reserved to the cce + ptgLen = ByteTools.readShort(function[x], function[x + 1]); // this + // is + // the + // cce + ptgLen += 7; // include the PtgId, cce, & reserv; + PtgMemAreaA pmema = new PtgMemAreaA(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pmema.init(b); + pmema.setParentRec(p); + stack.push(pmema); + break; + + case ptgMemAreaN: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemAreaN Located" + function[i]); + PtgMemAreaN pmemn = new PtgMemAreaN(); + ptgLen = pmemn.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pmemn.init(b); + pmemn.setParentRec(p); + stack.push(pmemn); + break; + + case ptgMemAreaNV: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemAreaNV Located" + function[i]); + x = i; + x += 5; // move past the opcode & reserved to the cce + ptgLen = ByteTools.readShort(function[x], function[x + 1]); // this + // is + // the + // cce + ptgLen += 7; // include the PtgId, cce, & reserv; + PtgMemAreaNV pmemv = new PtgMemAreaNV(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pmemv.init(b); + pmemv.setParentRec(p); + stack.push(pmemv); + break; + + // ptgMemArea also varies in length... + case ptgMemArea: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemArea Located" + function[i]); + ptgLen = 7; + PtgMemArea pmem = new PtgMemArea(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pmem.init(b); + // now grab the rest of the "extra data" that defines the + // ptgmemarea + // these are separate ptgs (PtgArea, PtgRef's ... plus + // PtgUnions) + // that comprise the PtgMemArea coordinates + pmem.setParentRec(p); + i += ptgLen; // after PtgMemArea record, get subexpression + ptgLen = pmem.getnTokens(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pmem.setSubExpression(b); + stack.push(pmem); + // hasPtgExtraMem= true; // has a PtgExtraMem structure + // after end of parsed expression: The PtgExtraMem structure + // specifies a range that corresponds to a PtgMemArea as + // specified in RgbExtra. + // pma= pmem; // save for later + break; + + case ptgMemFunc: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemFunc Located"); + PtgMemFunc pmemf = new PtgMemFunc(); + x = i; + x += 1; // move past the opcode to the cce + ptgLen = ByteTools.readShort(function[x], function[x + 1]); // this + // is + // the + // cce + ptgLen += 3; // include the PtgId, cce, & reserv; + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pmemf.setParentRec(p); + pmemf.init(b); + stack.push(pmemf); + break; + + case ptgInt: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgInt Located"); + PtgInt pi = new PtgInt(); + ptgLen = pi.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pi.init(b); + pi.setParentRec(p); + stack.push(pi); + break; + + case ptgErr: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgErr Located"); + PtgErr perr = new PtgErr(); + ptgLen = perr.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + perr.init(b); + perr.setParentRec(p); + stack.push(perr); + break; + + case ptgNum: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgNum Located"); + PtgNumber pnum = new PtgNumber(); + ptgLen = pnum.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pnum.init(b); + pnum.setParentRec(p); + stack.push(pnum); + break; + + case ptgBool: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgBool Located"); + PtgBool pboo = new PtgBool(); + ptgLen = pboo.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pboo.init(b); + pboo.setParentRec(p); + stack.push(pboo); + break; + + case ptgName: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgName Located"); + PtgName pn = new PtgName(); + ptgLen = pn.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pn.setParentRec(p); + pn.init(b); + pn.addListener(); + stack.push(pn); + int chk = (i + ptgLen); + if (chk < function.length) { + if (function[i + ptgLen] == 0x0) { + if (DEBUGLEVEL > 1) + Logger.logWarn("Undocumented Name Record mystery byte encountered in Formula: "); + i++; } - break; - - case ptgNameX: - if(DEBUGLEVEL > 1)Logger.logInfo("ptgNameX Located"); - if(DEBUGLEVEL > 0)Logger.logWarn("referencing external spreadsheets unsupported."); - PtgNameX pnx = new PtgNameX(); - ptgLen = pnx.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pnx.init(b); - pnx.setParentRec(p); - pnx.addListener(); - stack.push(pnx); - break; - - case ptgRef: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgRef Located "); - PtgRef pt = new PtgRef(); - ptgLen = pt.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pt.setParentRec(p); // parent rec must be set before init - pt.init(b); - pt.addToRefTracker(); - stack.push(pt); - break; - - case ptgArray: - hasArrays = true; - if(DEBUGLEVEL > 5)Logger.logInfo("ptgArray Located "); - PtgArray pa = new PtgArray(); - ptgLen = 8; //7 len + id - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pa.init(b); //setArrVals(b); // 20090820 KSC: b represents base record not array values - Integer ingr = Integer.valueOf(stack.size()); // constant value array for PtgArray appears at end of stack see hasArrays below - arrayLocs.add(ingr); - stack.push(pa); - break; - - case ptgRefN: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgRefN Located "); - PtgRefN ptn = new PtgRefN(false); - ptgLen = ptn.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - ptn.setParentRec(rec); // parent rec must be set before init - ptn.init(b); - if (rec.getOpcode()==XLSConstants.SHRFMLA) - ptn.addToRefTracker(); - stack.push(ptn); - break; - - case ptgArea: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgArea Located "); - PtgArea pg = new PtgArea(); - ptgLen = pg.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pg.setParentRec(p); // parent rec must be set before init - pg.init(b); - pg.addToRefTracker(); - stack.push(pg); - break; - - case ptgArea3d: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgArea3d Located "); - PtgArea3d pg3 = new PtgArea3d(); - ptgLen = pg3.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pg3.init(b, p); // we need this to init the sub-ptgs correctly - pg3.addListener(); - pg3.addToRefTracker(); - stack.push(pg3); - break; - - case ptgAreaN: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgAreaN Located "); - PtgAreaN pgn = new PtgAreaN(); - ptgLen = pgn.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pgn.setParentRec(rec); - pgn.init(b); - if (rec.getOpcode()==XLSConstants.SHRFMLA) { - pgn.addToRefTracker(); - } - stack.push(pgn); - break; - - case ptgAreaErr3d: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgAreaErr3d Located"); - PtgAreaErr3d ptfa = new PtgAreaErr3d(); - ptgLen = ptfa.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - ptfa.setParentRec(p); - ptfa.init(b); - //ptfa.addToRefTracker(); - stack.push(ptfa); - break; + } + break; - case ptgRefErr3d: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgRefErr3d Located"); - PtgRefErr3d ptfr = new PtgRefErr3d(); - ptgLen = ptfr.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - ptfr.setParentRec(p); - ptfr.init(b); - //ptfr.addToRefTracker(); - stack.push(ptfr); - break; - - case ptgMemErr: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgMemErr Located"); - PtgMemErr pm = new PtgMemErr(); - ptgLen = pm.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pm.setParentRec(p); - pm.init(b); - stack.push(pm); - break; - - - case ptgRefErr: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgRefErr Located"); - PtgRefErr pr = new PtgRefErr(); - ptgLen = pr.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pr.setParentRec(p); // parent rec must be set before init - pr.init(b); - stack.push(pr); - break; - - case ptgEndSheet: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgEndSheet Located"); - PtgEndSheet prs = new PtgEndSheet(); - ptgLen = prs.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - prs.init(b); - prs.setParentRec(p); - stack.push(prs); - break; - - case ptgRef3d: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgRef3d Located"); - PtgRef3d pr3 = new PtgRef3d(); - ptgLen = pr3.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pr3.setParentRec(p); - pr3.init(b); - pr3.addListener(); - pr3.addToRefTracker(); - stack.push(pr3); - // if an External Link i.e. defined in another workbook, flag formula as such - if (pr3.isExternalLink() && p.getOpcode()==XLSConstants.FORMULA) - ((Formula) p).setIsExternalRef(true); - break; - /* - * PtgAtr is another one of the ugly size-changing ptg's - */ - case ptgAtr: - PtgAtr pat = new PtgAtr((byte)0); - if(DEBUGLEVEL > 5)Logger.logInfo("PtgAtr Located"); - ptgLen = pat.getLength(); - if ((function[i+1] & 0x4)== 0x4){ - ptgLen = ByteTools.readShort(function[i+2], function[i+3]); - ptgLen++; // one extra for some undocumented reason - ptgLen = ptgLen*2; //seems to be two bytes per... - ptgLen += 4; // add the cch & grbit + case ptgNameX: + if (DEBUGLEVEL > 1) + Logger.logInfo("ptgNameX Located"); + if (DEBUGLEVEL > 0) + Logger.logWarn("referencing external spreadsheets unsupported."); + PtgNameX pnx = new PtgNameX(); + ptgLen = pnx.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pnx.init(b); + pnx.setParentRec(p); + pnx.addListener(); + stack.push(pnx); + break; + + case ptgRef: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRef Located "); + PtgRef pt = new PtgRef(); + ptgLen = pt.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pt.setParentRec(p); // parent rec must be set before init + pt.init(b); + pt.addToRefTracker(); + stack.push(pt); + break; + + case ptgArray: + hasArrays = true; + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgArray Located "); + PtgArray pa = new PtgArray(); + ptgLen = 8; // 7 len + id + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pa.init(b); // setArrVals(b); // 20090820 KSC: b represents base + // record not array values + Integer ingr = Integer.valueOf(stack.size()); // constant value + // array for + // PtgArray + // appears at + // end of stack + // see hasArrays + // below + arrayLocs.add(ingr); + stack.push(pa); + break; + + case ptgRefN: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRefN Located "); + PtgRefN ptn = new PtgRefN(false); + ptgLen = ptn.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + ptn.setParentRec(rec); // parent rec must be set before init + ptn.init(b); + if (rec.getOpcode() == XLSConstants.SHRFMLA) + ptn.addToRefTracker(); + stack.push(ptn); + break; + + case ptgArea: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgArea Located "); + PtgArea pg = new PtgArea(); + ptgLen = pg.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pg.setParentRec(p); // parent rec must be set before init + pg.init(b); + pg.addToRefTracker(); + stack.push(pg); + break; + + case ptgArea3d: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgArea3d Located "); + PtgArea3d pg3 = new PtgArea3d(); + ptgLen = pg3.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pg3.init(b, p); // we need this to init the sub-ptgs correctly + pg3.addListener(); + pg3.addToRefTracker(); + stack.push(pg3); + break; + + case ptgAreaN: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgAreaN Located "); + PtgAreaN pgn = new PtgAreaN(); + ptgLen = pgn.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pgn.setParentRec(rec); + pgn.init(b); + if (rec.getOpcode() == XLSConstants.SHRFMLA) { + pgn.addToRefTracker(); + } + stack.push(pgn); + break; + + case ptgAreaErr3d: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgAreaErr3d Located"); + PtgAreaErr3d ptfa = new PtgAreaErr3d(); + ptgLen = ptfa.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + ptfa.setParentRec(p); + ptfa.init(b); + // ptfa.addToRefTracker(); + stack.push(ptfa); + break; + + case ptgRefErr3d: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRefErr3d Located"); + PtgRefErr3d ptfr = new PtgRefErr3d(); + ptgLen = ptfr.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + ptfr.setParentRec(p); + ptfr.init(b); + // ptfr.addToRefTracker(); + stack.push(ptfr); + break; + + case ptgMemErr: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemErr Located"); + PtgMemErr pm = new PtgMemErr(); + ptgLen = pm.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pm.setParentRec(p); + pm.init(b); + stack.push(pm); + break; + + case ptgRefErr: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRefErr Located"); + PtgRefErr pr = new PtgRefErr(); + ptgLen = pr.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pr.setParentRec(p); // parent rec must be set before init + pr.init(b); + stack.push(pr); + break; + + case ptgEndSheet: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgEndSheet Located"); + PtgEndSheet prs = new PtgEndSheet(); + ptgLen = prs.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + prs.init(b); + prs.setParentRec(p); + stack.push(prs); + break; + + case ptgRef3d: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRef3d Located"); + PtgRef3d pr3 = new PtgRef3d(); + ptgLen = pr3.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pr3.setParentRec(p); + pr3.init(b); + pr3.addListener(); + pr3.addToRefTracker(); + stack.push(pr3); + // if an External Link i.e. defined in another workbook, + // flag formula as such + if (pr3.isExternalLink() + && p.getOpcode() == XLSConstants.FORMULA) + ((Formula) p).setIsExternalRef(true); + break; + /* + * PtgAtr is another one of the ugly size-changing ptg's + */ + case ptgAtr: + PtgAtr pat = new PtgAtr((byte) 0); + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgAtr Located"); + ptgLen = pat.getLength(); + if ((function[i + 1] & 0x4) == 0x4) { + ptgLen = ByteTools + .readShort(function[i + 2], function[i + 3]); + ptgLen++; // one extra for some undocumented reason + ptgLen = ptgLen * 2; // seems to be two bytes per... + ptgLen += 4; // add the cch & grbit + } + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pat.init(b); + pat.init(); + pat.setParentRec(p); + stack.push(pat); + break; + + case ptgFunc: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgFunc Located"); + PtgFunc ptf = new PtgFunc(); + ptgLen = ptf.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + ptf.init(b); + ptf.setParentRec(p); + stack.push(ptf); + break; + + case ptgFuncVar: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgFuncVar Located"); + PtgFuncVar ptfv = new PtgFuncVar(); + ptgLen = ptfv.getLength(); + b = new byte[ptgLen]; + if ((ptgLen) + (i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + + ptfv.init(b); + ptfv.setParentRec(p); + if (ptfv.getFunctionId() == FunctionConstants.XLF_INDIRECT) { + /* + * TESTING NEW WAY: + * // New way does not account for expanded shared formula + * references, unfortunately so keep original for new + * + * Stack indirectStack= new Stack(); + * int z= stack.size()-1; + * int nparams= 1; + * for (; z > 0 && nparams > 0; z--) { + * Ptg p= (Ptg) stack.get(z); + * if (p instanceof PtgAtr) { + * continue; + * } + * if(p.getIsOperator()||p.getIsControl()||p.getIsFunction() + * ){ + * if(p.getIsControl() ){ + * if(p.getOpcode() == 0x15) { // its a parens! + * // the parens is already pop'd so just return and it is + * gone... + * continue; + * } + * } + * int t= 0; + * // make sure we have the correct amount popped back in.. + * if (p.getIsBinaryOperator()) t=2; + * if (p.getIsUnaryOperator()) t=1; + * if (p.getIsStandAloneOperator()) t=0; + * if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || + * p.getOpcode() == 0x62){t=p.getNumParams();}// it's a + * ptgfunkvar! + * if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || + * p.getOpcode() == 0x61){t=p.getNumParams();}// guess that + * ptgfunc is not only one.. + * nparams+=t-1; + * if (nparams==0) + * break; + * } else { + * nparams--; + * if (nparams==0) + * break; + * } + * } + * indirectStack.addAll(stack.subList(z, stack.size())); + * indirectStack.push(ptfv); + * rec.getWorkBook().addIndirectFormulaStack(indirectStack); + * // must save and calculate indirect reference AFTER all + * formulas/cells have been added ... + * // original is below + * / + **/ + /**/ + if (rec.getOpcode() == XLSConstants.FORMULA) { + ((Formula) rec).setContainsIndirectFunction(true); + } else if (rec.getOpcode() == XLSConstants.SHRFMLA) { + ((Shrfmla) rec).setContainsIndirectFunction(true); } - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pat.init(b); - pat.init(); - pat.setParentRec(p); - stack.push(pat); - break; - - case ptgFunc: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgFunc Located"); - PtgFunc ptf = new PtgFunc(); - ptgLen = ptf.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - ptf.init(b); - ptf.setParentRec(p); - stack.push(ptf); - break; - - case ptgFuncVar: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgFuncVar Located"); - PtgFuncVar ptfv = new PtgFuncVar(); - ptgLen = ptfv.getLength(); - b = new byte[ptgLen]; - if((ptgLen)+(i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - - ptfv.init(b); - ptfv.setParentRec(p); - if(ptfv.getFunctionId()==FunctionConstants.XLF_INDIRECT) { -/*TESTING NEW WAY: -// New way does not account for expanded shared formula references, unfortunately so keep original for new - * - Stack indirectStack= new Stack(); - int z= stack.size()-1; - int nparams= 1; - for (; z > 0 && nparams > 0; z--) { - Ptg p= (Ptg) stack.get(z); - if (p instanceof PtgAtr) { - continue; - } - if(p.getIsOperator()||p.getIsControl()||p.getIsFunction()){ - if(p.getIsControl() ){ - if(p.getOpcode() == 0x15) { // its a parens! - // the parens is already pop'd so just return and it is gone... - continue; - } - } - int t= 0; - // make sure we have the correct amount popped back in.. - if (p.getIsBinaryOperator()) t=2; - if (p.getIsUnaryOperator()) t=1; - if (p.getIsStandAloneOperator()) t=0; - if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62){t=p.getNumParams();}// it's a ptgfunkvar! - if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61){t=p.getNumParams();}// guess that ptgfunc is not only one.. - nparams+=t-1; - if (nparams==0) - break; - } else { - nparams--; - if (nparams==0) - break; - } - } - indirectStack.addAll(stack.subList(z, stack.size())); - indirectStack.push(ptfv); - rec.getWorkBook().addIndirectFormulaStack(indirectStack); // must save and calculate indirect reference AFTER all formulas/cells have been added ... -// original is below - /**/ -/**/ - if(rec.getOpcode()==XLSConstants.FORMULA) { - ((Formula)rec).setContainsIndirectFunction(true); - }else if(rec.getOpcode()==XLSConstants.SHRFMLA) { - ((Shrfmla)rec).setContainsIndirectFunction(true); - } - /**/ - } - stack.push(ptfv); - break; - - case ptgAdd: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgAdd Located"); - PtgAdd pad = new PtgAdd(); - ptgLen = pad.getLength(); - b = new byte[ptgLen]; - //if((ptgLen+i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pad.init(b); - pad.setParentRec(p); - stack.push(pad); - break; - - case ptgMissArg: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgMissArg Located"); - PtgMissArg pmar = new PtgMissArg(); - ptgLen = pmar.getLength(); - b = new byte[ptgLen]; - //if((ptgLen+i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pmar.init(b); - pmar.setParentRec(p); - stack.push(pmar); - break; - - case ptgSub: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgSub Located"); - PtgSub psb = new PtgSub(); - ptgLen = psb.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - psb.init(b); - psb.setParentRec(p); - stack.push(psb); - break; - - case ptgMlt: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgMlt Located"); - PtgMlt pml = new PtgMlt(); - ptgLen = pml.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pml.init(b); - pml.setParentRec(p); - stack.push(pml); - break; - - case ptgDiv: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgDiv Located"); - PtgDiv pdiv = new PtgDiv(); - ptgLen = pdiv.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pdiv.init(b); - pdiv.setParentRec(p); - stack.push(pdiv); - break; - - case ptgUPlus: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgUPlus Located"); - PtgUPlus puplus = new PtgUPlus(); - ptgLen = puplus.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - puplus.init(b); - puplus.setParentRec(p); - stack.push(puplus); - break; - - case ptgUMinus: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgUminus Located"); - PtgUMinus puminus = new PtgUMinus(); - ptgLen = puminus.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - puminus.init(b); - puminus.setParentRec(p); - stack.push(puminus); - break; - - case ptgPercent: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgPercent Located"); - PtgPercent pperc = new PtgPercent(); - ptgLen = pperc.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pperc.init(b); - pperc.setParentRec(p); - stack.push(pperc); - break; - - case ptgPower: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgPower Located"); - PtgPower pow = new PtgPower(); - ptgLen = pow.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pow.init(b); - pow.setParentRec(p); - stack.push(pow); - break; - - case ptgConcat: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgConcat Located"); - PtgConcat pcon = new PtgConcat(); - ptgLen = pcon.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pcon.init(b); - pcon.setParentRec(p); - stack.push(pcon); - break; - - case ptgLT: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgLT Located"); - PtgLT plt = new PtgLT(); - ptgLen = plt.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - plt.init(b); - plt.setParentRec(p); - stack.push(plt); - break; - - case ptgLE: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgLE Located"); - PtgLE ple = new PtgLE(); - ptgLen = ple.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - ple.init(b); - ple.setParentRec(p); - stack.push(ple); - break; - - case ptgEQ: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgEQ Located"); - PtgEQ peq = new PtgEQ(); - ptgLen = peq.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - peq.init(b); - peq.setParentRec(p); - stack.push(peq); - break; - - case ptgGE: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgGE Located"); - PtgGE pge = new PtgGE(); - ptgLen = pge.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pge.init(b); - pge.setParentRec(p); - stack.push(pge); - break; - - case ptgGT: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgGT Located"); - PtgGT pgt = new PtgGT(); - ptgLen = pgt.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pgt.init(b); - pgt.setParentRec(p); - stack.push(pgt); - break; - - case ptgNE: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgNE Located"); - PtgNE pne = new PtgNE(); - ptgLen = pne.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - - pne.init(b); - pne.setParentRec(p); - stack.push(pne); - break; - - case ptgIsect: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgIsect Located"); - PtgIsect pist = new PtgIsect(); - ptgLen = pist.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - - pist.init(b); - pist.setParentRec(p); - stack.push(pist); - break; - - case ptgUnion: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgUnion Located"); - PtgUnion pun = new PtgUnion(); - ptgLen = pun.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pun.init(b); - pun.setParentRec(p); - stack.push(pun); - break; - - case ptgRange: - if(DEBUGLEVEL > 5)Logger.logInfo("ptgRange Located"); - PtgRange pran = new PtgRange(); - ptgLen = pran.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - pran.init(b); - pran.setParentRec(p); - stack.push(pran); - break; - - case ptgParen: - if(DEBUGLEVEL > 5)Logger.logInfo("PtgParens Located"); - PtgParen pp = new PtgParen(); - ptgLen = pp.getLength(); - b = new byte[ptgLen]; - if((ptgLen+i) <= function.length) System.arraycopy(function, (i), b, 0, ptgLen); - - pp.init(b); - pp.setParentRec(p); - stack.push(pp); - break; - - default: - PtgMystery pmy = new PtgMystery(); - ptgLen = function.length-i; - b = new byte[ptgLen]; - if(DEBUGLEVEL > 0)Logger.logWarn("Unsupported Formula Function: 0x"+ Integer.toHexString(ptg) + " length: " + ptgLen); - System.arraycopy(function, i, b, 0, ptgLen); - pmy.init(b); - pmy.setParentRec(p); - stack.push(pmy); - break; - } - i += ptgLen; - } - if (hasArrays && rec instanceof Formula){ // Array Recs handle extra data differently - // array data is appended to end of expression - // for each array in the function list, - // get saved ptgArray var (stored in stack var), - // grab data and parse array components - int startPos= expressionLen; - for (int i=0;i 5) + Logger.logInfo("ptgAdd Located"); + PtgAdd pad = new PtgAdd(); + ptgLen = pad.getLength(); + b = new byte[ptgLen]; + // if((ptgLen+i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pad.init(b); + pad.setParentRec(p); + stack.push(pad); + break; + + case ptgMissArg: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMissArg Located"); + PtgMissArg pmar = new PtgMissArg(); + ptgLen = pmar.getLength(); + b = new byte[ptgLen]; + // if((ptgLen+i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pmar.init(b); + pmar.setParentRec(p); + stack.push(pmar); + break; + + case ptgSub: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgSub Located"); + PtgSub psb = new PtgSub(); + ptgLen = psb.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + psb.init(b); + psb.setParentRec(p); + stack.push(psb); + break; + + case ptgMlt: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgMlt Located"); + PtgMlt pml = new PtgMlt(); + ptgLen = pml.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pml.init(b); + pml.setParentRec(p); + stack.push(pml); + break; + + case ptgDiv: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgDiv Located"); + PtgDiv pdiv = new PtgDiv(); + ptgLen = pdiv.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pdiv.init(b); + pdiv.setParentRec(p); + stack.push(pdiv); + break; + + case ptgUPlus: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgUPlus Located"); + PtgUPlus puplus = new PtgUPlus(); + ptgLen = puplus.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + puplus.init(b); + puplus.setParentRec(p); + stack.push(puplus); + break; + + case ptgUMinus: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgUminus Located"); + PtgUMinus puminus = new PtgUMinus(); + ptgLen = puminus.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + puminus.init(b); + puminus.setParentRec(p); + stack.push(puminus); + break; + + case ptgPercent: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgPercent Located"); + PtgPercent pperc = new PtgPercent(); + ptgLen = pperc.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pperc.init(b); + pperc.setParentRec(p); + stack.push(pperc); + break; + + case ptgPower: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgPower Located"); + PtgPower pow = new PtgPower(); + ptgLen = pow.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pow.init(b); + pow.setParentRec(p); + stack.push(pow); + break; + + case ptgConcat: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgConcat Located"); + PtgConcat pcon = new PtgConcat(); + ptgLen = pcon.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pcon.init(b); + pcon.setParentRec(p); + stack.push(pcon); + break; + + case ptgLT: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgLT Located"); + PtgLT plt = new PtgLT(); + ptgLen = plt.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + plt.init(b); + plt.setParentRec(p); + stack.push(plt); + break; + + case ptgLE: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgLE Located"); + PtgLE ple = new PtgLE(); + ptgLen = ple.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + ple.init(b); + ple.setParentRec(p); + stack.push(ple); + break; + + case ptgEQ: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgEQ Located"); + PtgEQ peq = new PtgEQ(); + ptgLen = peq.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + peq.init(b); + peq.setParentRec(p); + stack.push(peq); + break; + + case ptgGE: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgGE Located"); + PtgGE pge = new PtgGE(); + ptgLen = pge.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pge.init(b); + pge.setParentRec(p); + stack.push(pge); + break; + + case ptgGT: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgGT Located"); + PtgGT pgt = new PtgGT(); + ptgLen = pgt.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pgt.init(b); + pgt.setParentRec(p); + stack.push(pgt); + break; + + case ptgNE: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgNE Located"); + PtgNE pne = new PtgNE(); + ptgLen = pne.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + + pne.init(b); + pne.setParentRec(p); + stack.push(pne); + break; + + case ptgIsect: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgIsect Located"); + PtgIsect pist = new PtgIsect(); + ptgLen = pist.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + + pist.init(b); + pist.setParentRec(p); + stack.push(pist); + break; + + case ptgUnion: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgUnion Located"); + PtgUnion pun = new PtgUnion(); + ptgLen = pun.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pun.init(b); + pun.setParentRec(p); + stack.push(pun); + break; + + case ptgRange: + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRange Located"); + PtgRange pran = new PtgRange(); + ptgLen = pran.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + pran.init(b); + pran.setParentRec(p); + stack.push(pran); + break; + + case ptgParen: + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgParens Located"); + PtgParen pp = new PtgParen(); + ptgLen = pp.getLength(); + b = new byte[ptgLen]; + if ((ptgLen + i) <= function.length) + System.arraycopy(function, (i), b, 0, ptgLen); + + pp.init(b); + pp.setParentRec(p); + stack.push(pp); + break; + + default: + PtgMystery pmy = new PtgMystery(); + ptgLen = function.length - i; + b = new byte[ptgLen]; + if (DEBUGLEVEL > 0) + Logger.logWarn("Unsupported Formula Function: 0x" + + Integer.toHexString(ptg) + " length: " + ptgLen); + System.arraycopy(function, i, b, 0, ptgLen); + pmy.init(b); + pmy.setParentRec(p); + stack.push(pmy); + break; + } + i += ptgLen; + } + if (hasArrays && rec instanceof Formula) { // Array Recs handle extra + // data differently + // array data is appended to end of expression + // for each array in the function list, + // get saved ptgArray var (stored in stack var), + // grab data and parse array components + int startPos = expressionLen; + for (int i = 0; i < arrayLocs.size(); i++) { + Integer ingr = (Integer) arrayLocs.elementAt(i); + PtgArray parr = (PtgArray) stack.elementAt(ingr.intValue()); + + // have to assume that remaining data all goes for this + // ptgarray + // since length is variable and can only be ascertained by + // parsing + // if multiple arrays are present, actual array length will + // be returned via setArrVals + byte[] b = new byte[function.length - startPos]; // get "extra" + // array + // data System.arraycopy(function, startPos, b, 0, b.length); - try{ + try { parr.setParentRec(rec); - startPos+=parr.setArrVals(b); - }catch(Exception e){//TODO: this needs to be caught due to "name" records being parsed incorrectly. The problem has to do with the lenght of the name record not including the extra 7 bytes of space. Temporary fix for infoteria - if (DEBUGLEVEL > 0)Logger.logInfo("ExpressionParser.parseExpression: Array: " + e); + startPos += parr.setArrVals(b); + } catch (Exception e) {// TODO: this needs to be caught due to + // "name" records being parsed + // incorrectly. The problem has to do + // with the lenght of the name record + // not including the extra 7 bytes of + // space. Temporary fix for infoteria + if (DEBUGLEVEL > 0) + Logger.logInfo("ExpressionParser.parseExpression: Array: " + + e); + } + } + } /* + * no need to keep PtgExtraMem as can regenerate easily else + * if (hasPtgExtraMem && rec instanceof Formula) { + * //The PtgExtraMem structure specifies a range that + * corresponds to a PtgMemArea as specified in RgbExtra.) + * // count (2 bytes): An unsigned integer that specifies + * the areas within the range. + * // array (variable): An array of Ref8U that specifies the + * range. The number of elements MUST be equal to count. + * pma.setPostExpression(function, expressionLen); + * } + */ + if (DEBUGLEVEL > 5) + Logger.logInfo("finished formula"); + return stack; + + } + + /* + * Returns the ptg that matches the string location sent to + * it. + * rec can either be in the format "C5" or a range, such as + * "C4:D9" + * + */ + public static List getPtgsByLocation(String loc, Stack expression) throws FormulaNotFoundException { + List lv = new Vector(); + for (int i = 0; i < expression.size(); i++) { + Object o = expression.elementAt(i); + if (o == null) + throw new FormulaNotFoundException( + "Couldn't get Ptg at: " + loc); + if (o instanceof Byte) { + // do nothing + } else if (o instanceof Ptg) { + Ptg part = (Ptg) o; + String lo = part.getLocation(); + if (lo == null) + lo = "none"; + String comp = loc; + if (loc.indexOf("!") > -1) { // the sheet is referenced + if (lo.indexOf("!") == -1) { // and the ptg does not have + // sheet referenced + comp = loc.substring(loc.indexOf("!") + 1); + } + } + + if (comp.equalsIgnoreCase(lo)) { + lv.add(part); + } else { + // try fq location + lo = part.toString(); + if (loc.equalsIgnoreCase(lo)) { + lv.add(part); + + } else if (o instanceof PtgRef3d) {// gotta look into the + // first & last + // already checked + } else if (o instanceof PtgArea) {// gotta look into the + // first & last + Ptg first = ((PtgArea) o).getFirstPtg(); + Ptg last = ((PtgArea) o).getLastPtg(); + if (first.getLocation().equalsIgnoreCase(loc)) + lv.add(first); + if (last.getLocation().equalsIgnoreCase(loc)) + lv.add(last); + } + } + } + } + return lv; + } + + /** + * returns the position in the expression stack for the ptg associated with this location + * @param loc String + * @param expression + * @return + * @throws FormulaNotFoundException + */ + public static int getExpressionLocByLocation(String loc, Stack expression) throws FormulaNotFoundException { + + for (int i = 0; i < expression.size(); i++) { + Object o = expression.elementAt(i); + if (o == null) + throw new FormulaNotFoundException( + "Couldn't get Ptg at: " + loc); + if (o instanceof Byte) { + // do nothing + } else if (o instanceof Ptg) { + Ptg part = (Ptg) o; + String lo = part.getLocation(); + if (loc.equalsIgnoreCase(lo)) { + return i; } - } - } /* no need to keep PtgExtraMem as can regenerate easily else - if (hasPtgExtraMem && rec instanceof Formula) { - //The PtgExtraMem structure specifies a range that corresponds to a PtgMemArea as specified in RgbExtra.) - // count (2 bytes): An unsigned integer that specifies the areas within the range. - // array (variable): An array of Ref8U that specifies the range. The number of elements MUST be equal to count. - pma.setPostExpression(function, expressionLen); - }*/ - if(DEBUGLEVEL > 5)Logger.logInfo("finished formula"); - return stack; - - } - - /* Returns the ptg that matches the string location sent to it. - rec can either be in the format "C5" or a range, such as "C4:D9" - - */ - public static List getPtgsByLocation(String loc, Stack expression) - throws FormulaNotFoundException{ - List lv = new Vector(); - for (int i = 0; i< expression.size(); i++){ - Object o = expression.elementAt(i); - if(o == null)throw new FormulaNotFoundException("Couldn't get Ptg at: " + loc); - if (o instanceof Byte){ - // do nothing - }else if (o instanceof Ptg){ - Ptg part = (Ptg) o; - String lo = part.getLocation(); - if(lo==null) - lo = "none"; - String comp = loc; - if(loc.indexOf("!")>-1){ // the sheet is referenced - if(lo.indexOf("!")==-1){ // and the ptg does not have sheet referenced - comp = loc.substring(loc.indexOf("!")+1); - } - } - - - if (comp.equalsIgnoreCase(lo)){ - lv.add(part); - }else{ - // try fq location - lo = part.toString(); - if(loc.equalsIgnoreCase(lo)) { - lv.add(part); - - }else if(o instanceof PtgRef3d){// gotta look into the first & last - // already checked - }else if(o instanceof PtgArea){// gotta look into the first & last - Ptg first = ((PtgArea)o).getFirstPtg(); - Ptg last = ((PtgArea)o).getLastPtg(); - if(first.getLocation().equalsIgnoreCase(loc)) - lv.add(first); - if(last.getLocation().equalsIgnoreCase(loc)) - lv.add(last); - } - } - } - } - return lv; - } - - /** - * returns the position in the expression stack for the ptg associated with this location - * @param loc String - * @param expression - * @return - * @throws FormulaNotFoundException - */ - public static int getExpressionLocByLocation(String loc, Stack expression) - throws FormulaNotFoundException{ - - for (int i = 0; i< expression.size(); i++){ - Object o = expression.elementAt(i); - if(o == null)throw new FormulaNotFoundException("Couldn't get Ptg at: " + loc); - if (o instanceof Byte){ - // do nothing - }else if (o instanceof Ptg){ - Ptg part = (Ptg) o; - String lo = part.getLocation(); - if (loc.equalsIgnoreCase(lo)){ - return i; - } - // try full location - lo = part.toString(); - if(loc.equalsIgnoreCase(lo)) { - return i; - } - if(o instanceof PtgArea){// gotta look into the first & last - Ptg first = ((PtgArea)o).getFirstPtg(); - Ptg last = ((PtgArea)o).getLastPtg(); - if(first.getLocation().equalsIgnoreCase(loc))return i; - if(last.getLocation().equalsIgnoreCase(loc))return i; - } - } - } - return -1; -} - - /** - * returns the position in the expression stack for the desired ptg - * @param ptg Ptg to lookk up - * @param expression - * @return - * @throws FormulaNotFoundException - */ - public static int getExpressionLocByPtg(Ptg ptg, Stack expression) - throws FormulaNotFoundException{ - - for (int i = 0; i< expression.size(); i++){ - Object o = expression.elementAt(i); - if(o == null)throw new FormulaNotFoundException("Couldn't get Ptg at: " + ptg.toString()); - if (o instanceof Byte){ - // do nothing - }else if (o instanceof Ptg){ - if (((Ptg) o).equals(ptg)) - return i; - } - } - return -1; -} - + // try full location + lo = part.toString(); + if (loc.equalsIgnoreCase(lo)) { + return i; + } + if (o instanceof PtgArea) {// gotta look into the first & last + Ptg first = ((PtgArea) o).getFirstPtg(); + Ptg last = ((PtgArea) o).getLastPtg(); + if (first.getLocation().equalsIgnoreCase(loc)) + return i; + if (last.getLocation().equalsIgnoreCase(loc)) + return i; + } + } + } + return -1; + } + + /** + * returns the position in the expression stack for the desired ptg + * @param ptg Ptg to lookk up + * @param expression + * @return + * @throws FormulaNotFoundException + */ + public static int getExpressionLocByPtg(Ptg ptg, Stack expression) throws FormulaNotFoundException { + + for (int i = 0; i < expression.size(); i++) { + Object o = expression.elementAt(i); + if (o == null) + throw new FormulaNotFoundException( + "Couldn't get Ptg at: " + ptg.toString()); + if (o instanceof Byte) { + // do nothing + } else if (o instanceof Ptg) { + if (((Ptg) o).equals(ptg)) + return i; + } + } + return -1; + } + /** - * getCellRangePtgs handles locating which cells are refereced in an expression stack. - * - * Essentially the use is we can check a formula if it refereces a cell that is moving, then we have - * the ability to manipulate these ranges in whatever way makes sense. - * - * @expression = a Stack of ptgs that represent an excel calculation. - * @return an array of ptgs that are location based (ptgRef, PtgArea) - */ - public static Ptg[] getCellRangePtgs(Stack expression) - throws FormulaNotFoundException{ - Vector ret = new Vector(); - for (int i = 0; i< expression.size(); i++){ + * getCellRangePtgs handles locating which cells are refereced in an expression stack. + * + * Essentially the use is we can check a formula if it refereces a cell that is moving, then we have + * the ability to manipulate these ranges in whatever way makes sense. + * + * @expression = a Stack of ptgs that represent an excel calculation. + * @return an array of ptgs that are location based (ptgRef, PtgArea) + */ + public static Ptg[] getCellRangePtgs(Stack expression) throws FormulaNotFoundException { + Vector ret = new Vector(); + for (int i = 0; i < expression.size(); i++) { Object o = expression.elementAt(i); - if(o == null)throw new FormulaNotFoundException("Couldn't get Ptg at: " + i); - if (o instanceof Byte){ + if (o == null) + throw new FormulaNotFoundException("Couldn't get Ptg at: " + i); + if (o instanceof Byte) { // do nothing - }else if (o instanceof Ptg){ + } else if (o instanceof Ptg) { Ptg part = (Ptg) o; // handle shared formula range - if (part instanceof PtgExp){ - String lox= part.getLocation(); + if (part instanceof PtgExp) { + String lox = part.getLocation(); PtgRef ref = new PtgRef(); - ref.setParentRec(part.getParentRec()); // must be done before setLocation + ref.setParentRec(part.getParentRec()); // must be done + // before + // setLocation ref.setLocation(lox); ret.add(ref); - } else if (part instanceof PtgRefErr || - part instanceof PtgAreaErr3d) { + } else if (part instanceof PtgRefErr + || part instanceof PtgAreaErr3d) { ret.add("#REF!"); } else if (part instanceof PtgMemFunc) { - //Ptg[] p= getCellRangePtgs(((PtgMemFunc)part).getSubExpression()); - Ptg[] p= ((PtgMemFunc)part).getComponents(); - for (int z= 0; z < p.length; z++) + // Ptg[] p= + // getCellRangePtgs(((PtgMemFunc)part).getSubExpression()); + Ptg[] p = ((PtgMemFunc) part).getComponents(); + for (int z = 0; z < p.length; z++) ret.add(p[z]); - }else { - String lox= part.getLocation(); - if (lox!=null) + } else { + String lox = part.getLocation(); + if (lox != null) ret.add(part); - } + } } } Ptg[] retp = new Ptg[ret.size()]; diff --git a/src/main/java/io/starter/formats/XLS/Format.java b/src/main/java/io/starter/formats/XLS/Format.java index 0cf415a..01c860a 100644 --- a/src/main/java/io/starter/formats/XLS/Format.java +++ b/src/main/java/io/starter/formats/XLS/Format.java @@ -5,18 +5,25 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -27,122 +34,138 @@ /** Stores a custom number format pattern. */ -public final class Format -extends XLSRecord { - private static final long serialVersionUID = 1199947552103220748L; - - private short ifmt = -1; - private String pattern; - private Unicodestring ustring; - - public Format() { - setOpcode( XLSConstants.FORMAT ); - } - - /** Makes a new Format record for the given pattern. - * @param book the workbook to which the pattern should belong - * @param pattern the number format pattern to ensure exists - */ - public Format( WorkBook book, String pattern ) { - this( book, (short) -1, pattern ); - } - - /** Makes a new Format record with the given ID and pattern. - * This should only be used when parsing non-BIFF8 files. BIFF8 parsing - * will use the normal XLSRecord init sequence. For programmatic creation - * of custom formats use {@link #Format(WorkBook,String)} instead. - * - * @param book the workbook to which the pattern should belong - * @param id the format ID to use or -1 to generate one +public final class Format extends XLSRecord { + private static final long serialVersionUID = 1199947552103220748L; + + private short ifmt = -1; + private String pattern; + private Unicodestring ustring; + + public Format() { + setOpcode(XLSConstants.FORMAT); + } + + /** Makes a new Format record for the given pattern. + * @param book the workbook to which the pattern should belong * @param pattern the number format pattern to ensure exists - */ - public Format( WorkBook book, short id, String pattern ) { - this(); - setWorkBook( book ); - - this.pattern = pattern; - this.ustring = Sst.createUnicodeString( - pattern, null, WorkBook.STRING_ENCODING_AUTO ); - - byte[] idbytes; - if (id > 0) { - this.ifmt = id; - idbytes = ByteTools.shortToLEBytes( id ); - } else { - // WorkBook.insertFormat will call setIfmt - idbytes = new byte[ 2 ]; - } - - setData( ByteTools.append( ustring.read(), idbytes ) ); - - book.insertFormat( this ); - } - + */ + public Format(WorkBook book, String pattern) { + this(book, (short) -1, pattern); + } + + /** Makes a new Format record with the given ID and pattern. + * This should only be used when parsing non-BIFF8 files. BIFF8 parsing + * will use the normal XLSRecord init sequence. For programmatic creation + * of custom formats use {@link #Format(WorkBook,String)} instead. + * + * @param book the workbook to which the pattern should belong + * @param id the format ID to use or -1 to generate one + * @param pattern the number format pattern to ensure exists + */ + public Format(WorkBook book, short id, String pattern) { + this(); + setWorkBook(book); + + this.pattern = pattern; + this.ustring = Sst + .createUnicodeString(pattern, null, WorkBook.STRING_ENCODING_AUTO); + + byte[] idbytes; + if (id > 0) { + this.ifmt = id; + idbytes = ByteTools.shortToLEBytes(id); + } else { + // WorkBook.insertFormat will call setIfmt + idbytes = new byte[2]; + } + + setData(ByteTools.append(ustring.read(), idbytes)); + + book.insertFormat(this); + } + /** Initializes the record from bytes. * This method should only be called as part of the normal XLSRecord * init sequence when parsing from bytes. * @throws IllegalStateException if the record has already been parsed */ + @Override public void init() { - if (pattern != null) throw new IllegalStateException( - "the record has already been parsed" ); - + if (pattern != null) + throw new IllegalStateException( + "the record has already been parsed"); + super.init(); - - ifmt = ByteTools.readShort( this.getByteAt( 0 ), this.getByteAt( 1 ) ); - - ustring = new Unicodestring(); - ustring.init( this.getBytesAt( 2, this.getLength() - 2 ), false); - + + ifmt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + + ustring = new Unicodestring(); + ustring.init(this.getBytesAt(2, this.getLength() - 2), false); + pattern = ustring.toString(); - - /* Strip double quotes and backslashes from the format string. - * The quoting characters are an integral part of the format string, - * so this is almost certainly wrong. However, it's what the previous + + /* + * Strip double quotes and backslashes from the format + * string. + * The quoting characters are an integral part of the format + * string, + * so this is almost certainly wrong. However, it's what the + * previous * implementation did and I'm trying to preserve behavior. - * TODO: revisit stripping of quotes when writing number format parser + * TODO: revisit stripping of quotes when writing number + * format parser + * - Sam + */ + pattern = StringTool.replaceText(pattern, "\"", "", 0); + pattern = StringTool.replaceText(pattern, "\\", "", 0); + + this.getWorkBook().addFormat(this); + } + + @Override + public void setWorkBook(WorkBook book) { + super.setWorkBook(book); + + /* + * Not sure why this is here, but I think it has something + * to do with + * worksheet cloning. It's harmless, so might as well leave + * it alone. * - Sam */ - pattern = StringTool.replaceText( pattern, "\"", "", 0); - pattern = StringTool.replaceText( pattern, "\\", "", 0); - - this.getWorkBook().addFormat(this); + if (ifmt != -1) + book.addFormat(this); } - - public void setWorkBook (WorkBook book) { - super.setWorkBook( book ); - - /* Not sure why this is here, but I think it has something to do with - * worksheet cloning. It's harmless, so might as well leave it alone. - * - Sam - */ - if (ifmt != -1) book.addFormat( this ); - } - - /** Sets the format ID of this Format record. + + /** Sets the format ID of this Format record. */ - public void setIfmt (short id) { + public void setIfmt(short id) { ifmt = id; - + // Update the record bytes - System.arraycopy( - ByteTools.shortToLEBytes( ifmt ), 0, - this.getData(), 0, 2 ); + System.arraycopy(ByteTools.shortToLEBytes(ifmt), 0, this + .getData(), 0, 2); } - + /** Gets the format pattern string represented by this Format. */ - public String getFormat(){ + public String getFormat() { return pattern; } /** Gets the format index of this Format in its workbook. */ - public short getIfmt(){ + public short getIfmt() { return ifmt; } - - public String toString() { - return getFormat(); - } + + @Override + public String toString() { + return getFormat(); + } + + public void init(WorkBook workBook, int fID, String format) { + // TODO Auto-generated method stub + + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/MSODrawing.java b/src/main/java/io/starter/formats/XLS/MSODrawing.java index f39d37a..3154375 100644 --- a/src/main/java/io/starter/formats/XLS/MSODrawing.java +++ b/src/main/java/io/starter/formats/XLS/MSODrawing.java @@ -447,12 +447,12 @@ public void init(){ int d1= (SPGRCONTAINERLENGTH+8-(SPCONTAINERLENGTH+otherSPCONTAINERLENGTH)); if (d0+d1!=0) { if (DGCONTAINERLENGTH!=(DGCONTAINERATOMS+SPGRCONTAINERLENGTH+SOLVERCONTAINERLENGTH+8)) { // this may not be 100% since must account for OTHER record's SOLVERCONTAINER LENGTHS - io.starter.OpenXLS.util.Logger.log("DGCONTAINERLENGTH DIFF: " + (DGCONTAINERLENGTH-(DGCONTAINERATOMS+SPGRCONTAINERLENGTH+SOLVERCONTAINERLENGTH+8))); + io.starter.toolkit.Logger.log("DGCONTAINERLENGTH DIFF: " + (DGCONTAINERLENGTH-(DGCONTAINERATOMS+SPGRCONTAINERLENGTH+SOLVERCONTAINERLENGTH+8))); diff= true; } // ******* sum of SPCONTAINERS *************** if (SPGRCONTAINERLENGTH+8!=SPCONTAINERLENGTH+otherSPCONTAINERLENGTH) { - io.starter.OpenXLS.util.Logger.log("SPGRCONTAINERLENGTH DIFF: " + (SPGRCONTAINERLENGTH+8-(SPCONTAINERLENGTH+otherSPCONTAINERLENGTH))); + io.starter.toolkit.Logger.log("SPGRCONTAINERLENGTH DIFF: " + (SPGRCONTAINERLENGTH+8-(SPCONTAINERLENGTH+otherSPCONTAINERLENGTH))); diff= true; } } @@ -505,8 +505,8 @@ public void updateRecord(int spid, String imageName, String shapeName, int image */ public void updateRecord() { /*// debug: check algorithm: - io.starter.OpenXLS.util.Logger.log(this.toString()); - io.starter.OpenXLS.util.Logger.log(this.debugOutput()); + io.starter.toolkit.Logger.log(this.toString()); + io.starter.toolkit.Logger.log(this.debugOutput()); int origSP= SPCONTAINERLENGTH; int origDG= 0; int origSPGR= 0; @@ -741,8 +741,8 @@ public void updateRecord() { // testing /* - io.starter.OpenXLS.util.Logger.log(this.toString()); - io.starter.OpenXLS.util.Logger.log(this.debugOutput()); + io.starter.toolkit.Logger.log(this.toString()); + io.starter.toolkit.Logger.log(this.debugOutput()); /**/ } @@ -1015,9 +1015,9 @@ public void updateHeader(int otherSPContainers, int otherContainers, int numShap dgcontainerlength+=DGATOMS; /** debugging */ /*if (origSPGRL!=spgrcontainerlength || origDGL!=dgcontainerlength) { - io.starter.OpenXLS.util.Logger.log(this.toString()); - io.starter.OpenXLS.util.Logger.log(this.debugOutput()); - io.starter.OpenXLS.util.Logger.log("ORIGDG=" + origDGL + " ORIGSPL=" + origSPGRL + " DIFF: " + (origDGL-dgcontainerlength) + "-" + (origSPGRL-spgrcontainerlength)); + io.starter.toolkit.Logger.log(this.toString()); + io.starter.toolkit.Logger.log(this.debugOutput()); + io.starter.toolkit.Logger.log("ORIGDG=" + origDGL + " ORIGSPL=" + origSPGRL + " DIFF: " + (origDGL-dgcontainerlength) + "-" + (origSPGRL-spgrcontainerlength)); diff= true; } /**/ @@ -1046,8 +1046,8 @@ public void updateHeader(int otherSPContainers, int otherContainers, int numShap } else if (fbt==MSODrawingConstants.MSOFBTSPGRCONTAINER) { // sum of all spcontainers on the sheet System.arraycopy(ByteTools.cLongToLEBytes(spgrcontainerlength), 0, data, data.length-bis.available()-4, 4); /*if (diff) { // debugging container lengths - io.starter.OpenXLS.util.Logger.log(this.toString()); - io.starter.OpenXLS.util.Logger.log(this.debugOutput()); + io.starter.toolkit.Logger.log(this.toString()); + io.starter.toolkit.Logger.log(this.debugOutput()); }/**/ return; } else { // skip atoms diff --git a/src/main/java/io/starter/formats/XLS/MSODrawingGroup.java b/src/main/java/io/starter/formats/XLS/MSODrawingGroup.java index a85185d..4eb8e63 100644 --- a/src/main/java/io/starter/formats/XLS/MSODrawingGroup.java +++ b/src/main/java/io/starter/formats/XLS/MSODrawingGroup.java @@ -287,7 +287,7 @@ public void parse(){ int fbt = ((0xFF&buf[3])<<8)|(0xFF&buf[2]); int len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); - //io.starter.OpenXLS.util.Logger.log("fbt:"+Integer.toHexString(fbt)+";len:"+len); + //io.starter.toolkit.Logger.log("fbt:"+Integer.toHexString(fbt)+";len:"+len); if(fbt <0xF004) continue; // under 0xF005 are container recs; we just parse the atoms for needed info ... diff --git a/src/main/java/io/starter/formats/XLS/OOXMLAdapter.java b/src/main/java/io/starter/formats/XLS/OOXMLAdapter.java index 7881883..9b0d79b 100644 --- a/src/main/java/io/starter/formats/XLS/OOXMLAdapter.java +++ b/src/main/java/io/starter/formats/XLS/OOXMLAdapter.java @@ -22,77 +22,39 @@ */ package io.starter.formats.XLS; -/** - * OOXMLAdapter generates OOXML (SpreadsheetML packaged in ZIP) for a given workbook adhering to the following specification: - *row - * Concept of Open Package Convention (OPC) is identification of Relationships: - * .rels files specify Relationship type, Relatioship id rId and Target filename - * in specific XML file, these rIds are specified, and the .rels files are used to look up the particular file - * - * OOXML for a workbook consists of multiple files contained in a ZIP - * - * - * Open Package Convention Structure of SpreadsheetML ZIP: - * - * [Content_types].xml specifies the content types and the Parts (i.e. files) used in the ZIP package - * \_rels directory - * workbook.xml.rels relationship file for workbook, lists target files and their relationship type - * \xl directory - * styles.xml contains font, fill and xf specs - * sharedStrings.xml contains info re: ssts - * workbook.xml specifies sheet rIds - * \rels\workbook.xml.rels specifies sheet targets - * \worksheets directory - * sheetXX.xml contains row and cell info ... - * if contains drawing ml-specific data will contain "rid" linking - * \rels - * sheetXX.xml.rels if necessary for linking drawingML rIds, printerSetting rIDs - * (if necessary also contains:) - * \theme directory - * \charts directory - * chart1.xml - * \chartsheets directory - * \drawings directory - * drawingXX.xml chart and image info - * \rels - * drawing1.xml.rels links rid's to Target chart XMLs - * \media directory - * contains image files - * \printerSettings - * contains PrinterSettingsX.bin - * \dialogsheets directory - * \macrosheets directory - * \docProps - */ -import java.util.Date; -import java.util.zip.*; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.CharArrayReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Stack; import java.util.TreeMap; -import java.io.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; -import io.starter.OpenXLS.*; -import io.starter.formats.OOXML.*; -import io.starter.formats.XLS.WorkBook; -import io.starter.formats.XLS.charts.Chart; +import io.starter.OpenXLS.ChartHandle; +import io.starter.OpenXLS.Document; +import io.starter.OpenXLS.WorkBookHandle; +import io.starter.formats.OOXML.OOXMLConstants; import io.starter.formats.XML.UnicodeInputStream; -import io.starter.toolkit.FastAddVector; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; import io.starter.toolkit.TempFileManager; -import io.starter.toolkit.WriterOutputStream; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; // TODO: finish Style options, Chart options, Japanese Chars ..., // TODO: dataTables @@ -133,40 +95,46 @@ */ public class OOXMLAdapter implements OOXMLConstants { protected boolean DEBUG = !true; - + ZipOutputStream zip; - Writer writer; - Map deferredFiles = new HashMap(); - // map of original external filename and new filename on disk (may be different as images, etc. must have consecutive indexes ... -// HashMap externalFiles= new HashMap(); not used anylonger - + Writer writer; + Map deferredFiles = new HashMap(); + // map of original external filename and new filename on disk (may be different + // as images, etc. must have consecutive indexes ... + // HashMap externalFiles= new HashMap(); not used anylonger + // content lists used to create all .rels + [Content_Types].xml - ArrayList mainContentList= new ArrayList(); // [Content_Types].xml - ArrayList wbContentList= new ArrayList(); // workbook.xml.rels - written to [Content_Types].xml - ArrayList drContentList= new ArrayList(); // drawingX.xml.rels - written to [Content_Types].xml - ArrayList shContentList= new ArrayList(); // sheetX.xml.rels - ArrayList sheetsContentList= new ArrayList(); // total contents of each sheet - written to [Content_Types].xml - // also have vmContentList and chContentList for fairly rare occurrences of vmldrawings and charts containing embeds - - // External OOXML Object such as Vba projects, Ole Objects, Printer Settings, etc. + ArrayList mainContentList = new ArrayList(); // [Content_Types].xml + ArrayList wbContentList = new ArrayList(); // workbook.xml.rels - written to [Content_Types].xml + ArrayList drContentList = new ArrayList(); // drawingX.xml.rels - written to [Content_Types].xml + ArrayList shContentList = new ArrayList(); // sheetX.xml.rels + ArrayList sheetsContentList = new ArrayList(); // total contents of each sheet - written to [Content_Types].xml + // also have vmContentList and chContentList for fairly rare occurrences of + // vmldrawings and charts containing embeds + + // External OOXML Object such as Vba projects, Ole Objects, Printer Settings, + // etc. // links external ref "extra info" with the external reference id -// Map shExternalLinkInfo= new HashMap(); - - String externalDir= ""; // store "pass-through" files i.e. files we cannot process into our BIFF8 rec structure (vbaProject.bin, for example) - // ordinal numbers for sheet-level objects (rid is stored in sheetX.xml and file stored in appropriate directory, liked via sheetX.xml.rels - // Each item (images, charts, etc) has a very specific and ordered name format e.g. image1.jpg, printerSettings2.bin ... - int drawingId= 0; - int vmlId= 0; // vmlDrawing.vml - int commentsId= 0; - int activeXId= 0; - int activeXBinaryId= 0; // activeX.bin - int printerSettingsId= 0; - int oleObjectsId= 0; - int chartId= 0; - int imgId= 0; - - static final double rowHtFactor= 20.0; - static final double colWFactor= 256.0; + // Map shExternalLinkInfo= new HashMap(); + + String externalDir = ""; // store "pass-through" files i.e. files we cannot process into our BIFF8 rec + // structure (vbaProject.bin, for example) + // ordinal numbers for sheet-level objects (rid is stored in sheetX.xml and file + // stored in appropriate directory, liked via sheetX.xml.rels + // Each item (images, charts, etc) has a very specific and ordered name format + // e.g. image1.jpg, printerSettings2.bin ... + int drawingId = 0; + int vmlId = 0; // vmlDrawing.vml + int commentsId = 0; + int activeXId = 0; + int activeXBinaryId = 0; // activeX.bin + int printerSettingsId = 0; + int oleObjectsId = 0; + int chartId = 0; + int imgId = 0; + + static final double rowHtFactor = 20.0; + static final double colWFactor = 256.0; // TODO: finish Styles OOXML --> cellStyleXfs, MANY options not handled ... // TODO: finish charts and images (many options not handled) @@ -174,77 +142,84 @@ public class OOXMLAdapter implements OOXMLConstants { // TODO: handle themes, doc properties (create? alter?) // TODO: handle shapes - // *************************************************************************************** + // *************************************************************************************** // contents of External OOXMLObject arraylist - int EX_TYPE= 0; // type of External Object - must be listed in OOXMLConstants - int EX_PATH= 1; // path in ZIP - int EX_FNAME= 2; // file name + int EX_TYPE = 0; // type of External Object - must be listed in OOXMLConstants + int EX_PATH = 1; // path in ZIP + int EX_FNAME = 2; // file name // 3= rid - int EX_EXTRAINFO= 4; // any extra information associated - object specific - int EX_EMBEDINFO= 5; // string of embedd - // *************************************************************************************** - int format= WorkBookHandle.FORMAT_XLSX; // default format is non-macro-enabled workbook - static String inputEncoding= "UTF-8"; // default - /** - * set the XLSX format for this WorkBook - *
either FORMAT_XLSX, FORMAT_XLSM (Macro-enabled), FORMAT_XLTS (template) or FORMAT_XLTM (Macro-enabled template) - *
NOTE: If file extension is .XLSM format FORMAT_XLSM must be set - *
either because there are macros present or because the filename - *
is unconditionally set to .XLSM - * @param format - */ - public void setFormat(int format) { - this.format= format; - } - - public int getFormat() { - return this.format; - } - - /** Parses an xsd:boolean value. - * @param value the string to parse - * @return the boolean value of the given string - * @throws IllegalArgumentException if the given string is not a valid - * boolean value - */ - public static final boolean parseBoolean (String value) { - String trimmed = value.trim(); - if (trimmed.equals( "true" ) || trimmed.equals( "1" )) return true; - if (trimmed.equals( "false" ) || trimmed.equals( "0" )) return false; - throw new IllegalArgumentException( - "'" + value + "' is not a valid boolean value" ); - } - - - /** - * get a standalone ChartML document - * - * @param ch - * @return - */ - public static String getStandaloneChartDrawingOOXML(ChartHandle ch) { - String ret= ""; - try { - // trap package contents for drawing.xml - StringBuffer chartml= new StringBuffer(); - chartml.append(xmlHeader); - chartml.append("\r\n"); - chartml.append(""); chartml.append("\r\n"); - chartml.append(ch.getOOXML(1)); - chartml.append(""); - chartml.append("\r\n"); - return chartml.toString(); - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.getStandaloneChartDrawingOOXML: " + e.toString()); - ret= ""; - } - return ret; - } + int EX_EXTRAINFO = 4; // any extra information associated - object specific + int EX_EMBEDINFO = 5; // string of embedd + // *************************************************************************************** + int format = WorkBookHandle.FORMAT_XLSX; // default format is non-macro-enabled workbook + static String inputEncoding = "UTF-8"; // default + + /** + * set the XLSX format for this WorkBook
+ * either FORMAT_XLSX, FORMAT_XLSM (Macro-enabled), FORMAT_XLTS (template) or + * FORMAT_XLTM (Macro-enabled template)
+ * NOTE: If file extension is .XLSM format FORMAT_XLSM must be set
+ * either because there are macros present or because the filename
+ * is unconditionally set to .XLSM + * + * @param format + */ + public void setFormat(int format) { + this.format = format; + } + + public int getFormat() { + return this.format; + } + /** + * Parses an xsd:boolean value. + * + * @param value + * the string to parse + * @return the boolean value of the given string + * @throws IllegalArgumentException + * if the given string is not a valid boolean value + */ + public static final boolean parseBoolean(String value) { + String trimmed = value.trim(); + if (trimmed.equals("true") || trimmed.equals("1")) + return true; + if (trimmed.equals("false") || trimmed.equals("0")) + return false; + throw new IllegalArgumentException("'" + value + "' is not a valid boolean value"); + } + + /** + * get a standalone ChartML document + * + * @param ch + * @return + */ + public static String getStandaloneChartDrawingOOXML(ChartHandle ch) { + String ret = ""; + try { + // trap package contents for drawing.xml + StringBuffer chartml = new StringBuffer(); + chartml.append(xmlHeader); + chartml.append("\r\n"); + chartml.append(""); + chartml.append("\r\n"); + chartml.append(ch.getOOXML(1)); + chartml.append(""); + chartml.append("\r\n"); + return chartml.toString(); + } catch (Exception e) { + Logger.logErr("OOXMLAdapter.getStandaloneChartDrawingOOXML: " + e.toString()); + ret = ""; + } + return ret; + } /** - * generic utility to take the original external object filename and parse - * it using a new ordinal id e.g. image10.emf may become image3.emf NOTE: + * generic utility to take the original external object filename and parse it + * using a new ordinal id e.g. image10.emf may become image3.emf NOTE: * Increments global ordinal ids * * @param f @@ -267,25 +242,19 @@ protected String getExOOXMLFileName(String f) { } else if (root.equalsIgnoreCase("activeX")) { if (ext.toLowerCase().equals(".xml")) fname = root + (++activeXId) + ext; - else if (ext.toLowerCase().equals(".bin")) + else if (ext.toLowerCase().equals(".bin")) fname = root + (++activeXBinaryId) + ext; } else if (root.equalsIgnoreCase("printerSettings")) { fname = root + (++printerSettingsId) + ext; } else if (root.equalsIgnoreCase("drawing")) { fname = root + (++drawingId) + ext; - // if workbook-level file, no incrementing id, just use original - // filename - } else if (root.equalsIgnoreCase("app") - || root.equalsIgnoreCase("core") - || root.equalsIgnoreCase("theme") - || root.equalsIgnoreCase("themeOverride") - || root.equalsIgnoreCase("app") - || root.equalsIgnoreCase("custom") - || root.equalsIgnoreCase("connections") - || root.equalsIgnoreCase("externalLink") - || root.equalsIgnoreCase("calcChain") - || root.equalsIgnoreCase("styles") - || root.equalsIgnoreCase("sharedStrings") + // if workbook-level file, no incrementing id, just use original + // filename + } else if (root.equalsIgnoreCase("app") || root.equalsIgnoreCase("core") || root.equalsIgnoreCase("theme") + || root.equalsIgnoreCase("themeOverride") || root.equalsIgnoreCase("app") + || root.equalsIgnoreCase("custom") || root.equalsIgnoreCase("connections") + || root.equalsIgnoreCase("externalLink") || root.equalsIgnoreCase("calcChain") + || root.equalsIgnoreCase("styles") || root.equalsIgnoreCase("sharedStrings") || root.equalsIgnoreCase("vbaProject")) { ; // do nothing, use original fname *** these do not have ordinal id's } else @@ -295,621 +264,632 @@ else if (ext.toLowerCase().equals(".bin")) return fname; } + /** + * write a temp file for later inclusion in the zip file + * + * @param sb + * @param fn + */ + protected void addDeferredFile(StringBuffer sb, String fn) { + try { + File fx = addDeferredFile(fn); + FileOutputStream fos = new FileOutputStream(fx); + /* + * new way OOXMLAdapter.writeSBToStreamEfficiently(sb,fos); fos.flush(); + * fos.close(); + */ // have to use writer to set encoding - vital to non-utf8 input files + Writer out = new OutputStreamWriter(fos, inputEncoding); // "UTF-8" ); + OOXMLAdapter.writeSBToStreamEfficiently(sb, out); + out.close(); + /* + * old way Writer out = new OutputStreamWriter( fos, inputEncoding); //"UTF-8" + * ); out.write( sb.toString() ); out.close(); + */ + } catch (Exception e) { + Logger.logErr("OOXMLAdapter addDeferredFile failed.", e); + } + } - /** - * write a temp file for later inclusion in the zip file - * - * @param sb - * @param fn - */ - protected void addDeferredFile(StringBuffer sb, String fn){ - try{ - File fx = addDeferredFile( fn ); - FileOutputStream fos = new FileOutputStream(fx); -/* new way - OOXMLAdapter.writeSBToStreamEfficiently(sb,fos); - fos.flush(); - fos.close(); -*/ // have to use writer to set encoding - vital to non-utf8 input files - Writer out = new OutputStreamWriter( fos, inputEncoding); //"UTF-8" ); - OOXMLAdapter.writeSBToStreamEfficiently(sb, out); - out.close(); -/* old way - Writer out = new OutputStreamWriter( fos, inputEncoding); //"UTF-8" ); - out.write( sb.toString() ); - out.close(); -*/ - }catch(Exception e){ - Logger.logErr("OOXMLAdapter addDeferredFile failed.",e); - } - } - - /** - * A memory efficient way to write a StringBuffer to an OutputStream - * without creating Strings and other Objects. - * - * Mar 9, 2012 - * @param aSB - * @param ous - * @throws IOException - */ -// public static void writeSBToStreamEfficiently(StringBuffer aSB, OutputStream ous) throws IOException{ - public static void writeSBToStreamEfficiently(StringBuffer aSB, Writer ous) throws IOException{ - - final int aLength = aSB.length(); - final int aChunk = 1024; - final char[] aChars = new char[aChunk]; //aPosEnd-aPosStart]; //aChunk]; - - for(int aPosStart = 0; aPosStart < aLength; aPosStart += aChunk) { - final int aPosEnd = Math.min(aPosStart + aChunk, aLength); - aSB.getChars(aPosStart, aPosEnd, aChars, 0); // Create no new buffer - final CharArrayReader aCARead = new CharArrayReader(aChars); // Create no new buffer - - // This may be slow but it will not create any more buffer (for bytes) - int aByte; - int i= 0; - while((aByte = aCARead.read()) != -1 && i++ < (aPosEnd-aPosStart)) - ous.write(aByte); - } - } - - - protected File addDeferredFile (String name) - throws IOException { - File file = TempFileManager.createTempFile("OOXMLOutput_", ".tmp"); - deferredFiles.put( name.toString(), file.getAbsolutePath()); - return file; - } - - /** - * write a temp file for later inclusion in the zip file - * - * @param sb - * @param fn - * @throws IOException - */ - protected void addDeferredFile(byte[] b, String fn) throws IOException{ - File fx = addDeferredFile( fn ); - // write to temp file - FileOutputStream fos = new FileOutputStream(fx); - BufferedOutputStream bos = new BufferedOutputStream(fos); - bos.write(b); - bos.close(); - } - - /** - * returns truth of "Book Contains External OOXML Object named type" * - * @param bk - * @param type e.g. "vba", "custprops" - * @return - */ - private boolean hasObject(WorkBookHandle bk, String type) { - List externalOOXML= bk.getWorkBook().getOOXMLObjects(); - for (int i= 0; i < externalOOXML.size(); i++) { - String[] s= (String[]) externalOOXML.get(i); - if (s!=null && s.length==3) { // id, dir, filename - if (s[0].equalsIgnoreCase(type)) - return true; - } - } - return false; - } - - /** - * return true of workbook bk contains macros - * @param bk - * @return - */ - public static boolean hasMacros(Document bk) { - if(bk instanceof WorkBookHandle){ - List externalOOXML= ((WorkBookHandle)bk).getWorkBook().getOOXMLObjects(); - for (int i= 0; i < externalOOXML.size(); i++) { - String[] s= (String[]) externalOOXML.get(i); - if (s!=null && s.length==3) { // id, dir, filename - if (s[0].equalsIgnoreCase("vba") || s[0].equalsIgnoreCase("macro")) { - return true; - } - } - } - } - return false; - } - - - /** - * utility which, given an an exisitng file f2write, - * creates zipEntry and writes to zip var - * - * NOTE: global zip ZipOutputStream must be open - * - * @param f2write name and path of exisitng file - * @param fname desired name in zip - * @throws IOException - */ - protected void writeFileToZIP(String f2write, String fname) throws IOException { - nextZipEntry( fname ); - - FileInputStream fis= new FileInputStream(f2write); - BufferedInputStream bis = new BufferedInputStream(fis); - - int i= bis.read(); - while (i!=-1) { - zip.write(i); - i= bis.read(); - } - - bis.close(); - } - - - protected void nextZipEntry (String name) - throws IOException { - // Flush the writer to ensure data ends up in the right entry - try { - writer.flush(); - }catch(Exception e) { - Logger.logErr("Flush failing on zip entry, likely due to streaming first sheet " + e); - } - - // Start the new entry in the ZIP file - zip.putNextEntry( new ZipEntry( name ) ); - } - - - /** - * utility to look up Content Type string for type abbreviation - * @param type - * @return - */ - protected String getContentType(String type) { - for (int i= 0; i < contentTypes.length; i++) { - if (contentTypes[i][0].equalsIgnoreCase(type)) - return contentTypes[i][1]; - } - return "UNKNOWN TYPE " + type; - } - - /** - * utility to look up Relationship Type string for type abbreviation - * @param type - * @return - * @see OOXMLConstnats - */ - protected String getRelationshipType(String type) { - for (int i= 0; i < relsContentTypes.length; i++) { - if (relsContentTypes[i][0].equalsIgnoreCase(type)) - return relsContentTypes[i][1]; - } - return "UNKNOWN TYPE " + type; - } - - /** - * utility to retrieve correct relationship type abbreviation string from verbose type string - * @param type - * @return - * @see OOXMLConstants - */ - protected static String getRelationshipTypeAbbrev(String type) { - for (int i= 0; i < relsContentTypes.length; i++) { - if (relsContentTypes[i][1].equalsIgnoreCase(type)) - return relsContentTypes[i][0]; - } - return "UNKNOWN TYPE " + type; - } - - - // this is for testing purposes only, not used so no need to comment out logger msg :) - private void getZipEntries(ZipFile zf) { - // testing!! - try { - java.util.Enumeration e= zf.entries(); - while (e.hasMoreElements()) { - ZipEntry ze= (ZipEntry) e.nextElement(); - Logger.logInfo(ze.getName()); - } - } catch (Exception e) { - Logger.logErr("getZipEntries: " + e.toString()); - } - } - - /** - * Strip non-ascii (i.e. xml non-valid) chars from Strings - * This is utilized for xml attributes, node values can contain quote symbols - * @param s - * @return - */ - public static StringBuffer stripNonAscii(String s) { - StringBuffer out = new StringBuffer(); - if (s == null) return out; - /** FROM MS: - * "Special character" refers to any character outside the standard ASCII character set - * range of 0x00 - 0x7F, such as Latin characters with accents, umlauts, or other diacritics. - * The default encoding scheme for XML documents is UTF-8, which encodes ASCII characters with a - * value of 0x80 or higher differently than other standard encoding schemes. - Most often, you see this problem if you are working with data that uses the - simple "iso-8859-1" encoding scheme. In this case, the quickest solution is usually the first - or example, use the following XML declaration: - */ - // Legal characters are tab, carriage return, line feed, and the legal characters of Unicode and ISO/IEC 10646 - // XML processors MUST accept any character in the range specified for Char - // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */ - -/* fro wiki - * Unicode code points in the following ranges are valid in XML 1.0 documents:[1] - - U+0009, U+000A, U+000D: these are the only C0 controls accepted in XML 1.0; - U+0020–U+D7FF, U+E000–U+FFFD: this excludes some (not all) non-characters in the BMP (all surrogates, U+FFFE and U+FFFF are forbidden); - U+10000–U+10FFFF: this includes all code points in supplementary planes, including non-characters. - -The preceding code points ranges contain the following controls which are only valid in certain contexts in XML 1.0 documents, and whose usage is restricted and highly discouraged: - U+007F–U+0084, U+0086–U+009F: this includes a C0 control characters, and most (not all) C1 controls. - */ - for (int i = 0; i < s.length(); i++) { - char c= s.charAt(i); - int charCode = (int) c; - if(charCode==0x9 || - charCode==0xA || - charCode==0xD || - (charCode>=0x20 && charCode <=0xD7FF) || - (charCode>=0xE000 && charCode <=0xFFFD) || - (charCode>=0x10000 && charCode <=0x10FFFF) - ) { - if (charCode=='&') - out.append("&"); - else if (charCode=='"') - out.append("""); - else if (charCode=='<') - out.append("<"); - else if (charCode=='>') - out.append(">"); - else if (charCode=='\'') - out.append("'"); - else - out.append(c); - } /*else { // Encoding Q??????: - io.starter.OpenXLS.util.Logger.log("Skipping Special Char: " + charCode); // skip it - }*/ - } -/* these translations do not seem to make any difference for Baxter's issue - } else if (charCode==8220) { // smart quotes BAXTER ISSUE - TRY - out.append("“"); - } else if (charCode==8221) { // "" - out.append("”"); - } else if (charCode==8216) { // "" - out.append("‘"); - } else if (charCode==8217) { // "" - out.append("’"); - } else if (charCode==8211){ // en dash - out.append("–"); - } else if (charCode==8212){ // em dash - out.append("—"); - } else if (charCode==8242) { // single prime - out.append("′"); - } else if (charCode==8364) { // Euro Symbol - out.append("€"); - } else // copyright= 169 // 10 \n 176=small circle - out.append(c); // 169=© - } -*/ - return out; - } - - /** - * Strip non-ascii (i.e. xml non-valid) chars from Strings - * Node values can contain quote symbols - * @param s - * @return - */ - public static StringBuffer stripNonAsciiRetainQuote(String s) { - StringBuffer out = new StringBuffer(); - if (s == null) return out; - for (int i = 0; i < s.length(); i++) { - char c= s.charAt(i); - int charCode = (int) c; - if(charCode>=32 && charCode <=126) { - if (charCode=='&') - out.append("&"); - else if (charCode=='<') - out.append("<"); - else if (charCode=='>') - out.append(">"); - else - out.append(c); - } - else - out.append(c); - } - return out; - } - - - - /** - * deal with the "BOM" input streams... - * - * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 - * - * - * Oct 14, 2010 - * @param in - * @return - */ - protected static InputStream wrapInputStream(InputStream in){ - UnicodeInputStream uin = new UnicodeInputStream(in,"UTF-8"); - // String enc = uin.getEncoding(); // check for BOM mark and skip - return uin; - } - - /** - * used as a way to monitor Zip Entry fetching - * - * Oct 14, 2010 - * @param f - * @param name - * @return - */ - protected static ZipEntry getEntry(ZipFile f, String name){ - return f.getEntry(name); - } - - /** - * parses any .rels file into content List array list - * - * @param ii - * @return - */ - protected static ArrayList parseRels(InputStream ii) { - ArrayList contentList= new ArrayList(); - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - - xpp.setInput(ii, null); // using XML 1.0 specification - -// if(DEBUG) Logger.logInfo("parseRels InputStream has available bytes: " + ii.available()); - - int eventType = xpp.getEventType(); - -// if(DEBUG) Logger.logInfo("parseRels XPP Name: " + xpp.getName() ); - -// if(DEBUG) Logger.logInfo("parseRels XPP Event Type: " + eventType ); - - while (eventType != XmlPullParser.END_DOCUMENT) { - try{ - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if(tnm != null && tnm.equals("Relationship")) { - String type = "", target = "", rId = ""; - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); // id, Type, - // Target - String v = xpp.getAttributeValue(i); - if (nm.equalsIgnoreCase("Type")) - type = getRelationshipTypeAbbrev(v); - else if (nm.equalsIgnoreCase("Target")) - target = v; - else if (nm.equalsIgnoreCase("id")) - rId = v; - } - // 20100426 KSC: unfortunately, need to ensure that commentsX.xml is processed before vmlDrawingX.xml - if (target.indexOf("comments")==-1) - contentList.add(new String[] { type, target, rId }); - else // ensure comments are before vmlDrawing so that Notes can be created - contentList.add(0, new String[] { type, target, rId }); - }else{ -// if(DEBUG) Logger.logInfo("parseRels null entry name"); - } - } - }catch(Exception ea){ - Logger.logErr("XML Exception in OOXMLAdapter.parseRels. Input file is out of spec.",ea); - } - eventType = xpp.next(); - } - - } catch(org.xmlpull.v1.XmlPullParserException ex){ - Logger.logErr("XML Exception in OOXMLAdapter.parseRels. Input file is out of spec.",ex); - - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parseRels. " + e.toString()); - } - return contentList; - } - - - /** - * utility to retrieve Text element for tag - * @param xpp - * @return - * @throws IOException - */ - public static String getNextText(XmlPullParser xpp) throws IOException,XmlPullParserException { - int eventType= xpp.next(); - String ret= ""; - while (eventType != XmlPullParser.END_DOCUMENT && - eventType != XmlPullParser.END_TAG && - eventType != XmlPullParser.START_TAG && /* true in all cases?? */ - eventType != XmlPullParser.TEXT) { - eventType = xpp.next(); - } - if (eventType==XmlPullParser.TEXT) - ret= xpp.getText(); - - try { - return new String(ret.getBytes(), inputEncoding); -/* KSC: replaced with above *SHOULD* be correct - if (!isUTFEncoding) - if (xpp.getInputEncoding().equals("UTF-8")) - return new String(ret.getBytes(), "UTF-8"); // ensure encoding - else - return new String(ret.getBytes(), xpp.getInputEncoding()); // ensure encoding*/ - } catch (Exception e) { } // inputEncoding can be null - return ret; - } - - /** - * simple utility that ensures that sheets are last in the workbookcontent list - * also ensure that theme(s) are parsed first, as are used in styles etc. - * in order to create all dependent objects first - * @param wbContentList - */ - protected void reorderWbContentList(ArrayList wbContentList) { - for (int j= 0; j < wbContentList.size(); j++) { - String[] wb= (String[]) wbContentList.get(j); - if (!wb[0].equals("sheet")) { // sheets come last - wbContentList.remove(j); - wbContentList.add(0, wb); - } - } - for (int j= 0; j < wbContentList.size(); j++) { - String[] wb= (String[]) wbContentList.get(j); - if (wb[0].equals("theme")) { // goes before styles - wbContentList.remove(j); - wbContentList.add(0, wb); - break; - } - } - - } - - /** - * return the file matching rId in the ContentList (String[] type, filename, rId) - * @param contentList - * @param rId - * @return - */ - protected static String getFilename(ArrayList contentList, String rId) { - for (int j= 0; j < contentList.size(); j++) { - String[] s= (String[]) contentList.get(j); - if (s[2].equals(rId)) - return s[1]; - } - return null; - } - - /** - * creates a copy of a stack so changes won't affect original - * @param origStack - * @return - */ - protected Stack cloneStack(Stack origStack) { - Stack s= new Stack(); - for (int i= 0; i < origStack.size(); i++) { - s.push(origStack.elementAt(i)); - } - return s; - } - - public static boolean deleteDir(File f) { - if (f.isDirectory()) { - String[] children = f.list(); - for (int i=0; i0) - s+= f.substring(0, f.indexOf('.')) + "/"; - else - s+= f + "/"; - - return s; - } - - - /** - * sorts the sheets for incoming workbook xlsx -- used for eventMode only - * + /** + * A memory efficient way to write a StringBuffer to an OutputStream without + * creating Strings and other Objects. + * + * Mar 9, 2012 + * + * @param aSB + * @param ous + * @throws IOException + */ + // public static void writeSBToStreamEfficiently(StringBuffer aSB, OutputStream + // ous) throws IOException{ + public static void writeSBToStreamEfficiently(StringBuffer aSB, Writer ous) throws IOException { + + final int aLength = aSB.length(); + final int aChunk = 1024; + final char[] aChars = new char[aChunk]; // aPosEnd-aPosStart]; //aChunk]; + + for (int aPosStart = 0; aPosStart < aLength; aPosStart += aChunk) { + final int aPosEnd = Math.min(aPosStart + aChunk, aLength); + aSB.getChars(aPosStart, aPosEnd, aChars, 0); // Create no new buffer + final CharArrayReader aCARead = new CharArrayReader(aChars); // Create no new buffer + + // This may be slow but it will not create any more buffer (for bytes) + int aByte; + int i = 0; + while ((aByte = aCARead.read()) != -1 && i++ < (aPosEnd - aPosStart)) + ous.write(aByte); + } + } + + protected File addDeferredFile(String name) throws IOException { + File file = TempFileManager.createTempFile("OOXMLOutput_", ".tmp"); + deferredFiles.put(name.toString(), file.getAbsolutePath()); + return file; + } + + /** + * write a temp file for later inclusion in the zip file + * + * @param sb + * @param fn + * @throws IOException + */ + protected void addDeferredFile(byte[] b, String fn) throws IOException { + File fx = addDeferredFile(fn); + // write to temp file + FileOutputStream fos = new FileOutputStream(fx); + BufferedOutputStream bos = new BufferedOutputStream(fos); + bos.write(b); + bos.close(); + } + + /** + * returns truth of "Book Contains External OOXML Object named type" * + * + * @param bk + * @param type + * e.g. "vba", "custprops" + * @return + */ + private boolean hasObject(WorkBookHandle bk, String type) { + List externalOOXML = bk.getWorkBook().getOOXMLObjects(); + for (int i = 0; i < externalOOXML.size(); i++) { + String[] s = (String[]) externalOOXML.get(i); + if (s != null && s.length == 3) { // id, dir, filename + if (s[0].equalsIgnoreCase(type)) + return true; + } + } + return false; + } + + /** + * return true of workbook bk contains macros + * + * @param bk + * @return + */ + public static boolean hasMacros(Document bk) { + if (bk instanceof WorkBookHandle) { + List externalOOXML = ((WorkBookHandle) bk).getWorkBook().getOOXMLObjects(); + for (int i = 0; i < externalOOXML.size(); i++) { + String[] s = (String[]) externalOOXML.get(i); + if (s != null && s.length == 3) { // id, dir, filename + if (s[0].equalsIgnoreCase("vba") || s[0].equalsIgnoreCase("macro")) { + return true; + } + } + } + } + return false; + } + + /** + * utility which, given an an exisitng file f2write, creates zipEntry and writes + * to zip var + * + * NOTE: global zip ZipOutputStream must be open + * + * @param f2write + * name and path of exisitng file + * @param fname + * desired name in zip + * @throws IOException + */ + protected void writeFileToZIP(String f2write, String fname) throws IOException { + nextZipEntry(fname); + + FileInputStream fis = new FileInputStream(f2write); + BufferedInputStream bis = new BufferedInputStream(fis); + + int i = bis.read(); + while (i != -1) { + zip.write(i); + i = bis.read(); + } + + bis.close(); + } + + protected void nextZipEntry(String name) throws IOException { + // Flush the writer to ensure data ends up in the right entry + try { + writer.flush(); + } catch (Exception e) { + Logger.logErr("Flush failing on zip entry, likely due to streaming first sheet " + e); + } + + // Start the new entry in the ZIP file + zip.putNextEntry(new ZipEntry(name)); + } + + /** + * utility to look up Content Type string for type abbreviation + * + * @param type + * @return + */ + protected String getContentType(String type) { + for (int i = 0; i < contentTypes.length; i++) { + if (contentTypes[i][0].equalsIgnoreCase(type)) + return contentTypes[i][1]; + } + return "UNKNOWN TYPE " + type; + } + + /** + * utility to look up Relationship Type string for type abbreviation + * + * @param type + * @return + * @see OOXMLConstnats + */ + protected String getRelationshipType(String type) { + for (int i = 0; i < relsContentTypes.length; i++) { + if (relsContentTypes[i][0].equalsIgnoreCase(type)) + return relsContentTypes[i][1]; + } + return "UNKNOWN TYPE " + type; + } + + /** + * utility to retrieve correct relationship type abbreviation string from + * verbose type string + * + * @param type + * @return + * @see OOXMLConstants + */ + protected static String getRelationshipTypeAbbrev(String type) { + for (int i = 0; i < relsContentTypes.length; i++) { + if (relsContentTypes[i][1].equalsIgnoreCase(type)) + return relsContentTypes[i][0]; + } + return "UNKNOWN TYPE " + type; + } + + // this is for testing purposes only, not used so no need to comment out logger + // msg :) + private void getZipEntries(ZipFile zf) { + // testing!! + try { + java.util.Enumeration e = zf.entries(); + while (e.hasMoreElements()) { + ZipEntry ze = (ZipEntry) e.nextElement(); + Logger.logInfo(ze.getName()); + } + } catch (Exception e) { + Logger.logErr("getZipEntries: " + e.toString()); + } + } + + /** + * Strip non-ascii (i.e. xml non-valid) chars from Strings This is utilized for + * xml attributes, node values can contain quote symbols + * + * @param s + * @return + */ + public static StringBuffer stripNonAscii(String s) { + StringBuffer out = new StringBuffer(); + if (s == null) + return out; + /** + * FROM MS: "Special character" refers to any character outside the standard + * ASCII character set range of 0x00 - 0x7F, such as Latin characters with + * accents, umlauts, or other diacritics. The default encoding scheme for XML + * documents is UTF-8, which encodes ASCII characters with a value of 0x80 or + * higher differently than other standard encoding schemes. Most often, you see + * this problem if you are working with data that uses the simple "iso-8859-1" + * encoding scheme. In this case, the quickest solution is usually the first or + * example, use the following XML declaration: + */ + // Legal characters are tab, carriage return, line feed, and the legal + // characters of Unicode and ISO/IEC 10646 + // XML processors MUST accept any character in the range specified for Char + // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | + // [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, + // FFFE, and FFFF. */ + + /* + * fro wiki Unicode code points in the following ranges are valid in XML 1.0 + * documents:[1] + * + * U+0009, U+000A, U+000D: these are the only C0 controls accepted in XML 1.0; + * U+0020–U+D7FF, U+E000–U+FFFD: this excludes some (not all) non-characters in + * the BMP (all surrogates, U+FFFE and U+FFFF are forbidden); U+10000–U+10FFFF: + * this includes all code points in supplementary planes, including + * non-characters. + * + * The preceding code points ranges contain the following controls which are + * only valid in certain contexts in XML 1.0 documents, and whose usage is + * restricted and highly discouraged: U+007F–U+0084, U+0086–U+009F: this + * includes a C0 control characters, and most (not all) C1 controls. + */ + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + int charCode = c; + if (charCode == 0x9 || charCode == 0xA || charCode == 0xD || (charCode >= 0x20 && charCode <= 0xD7FF) + || (charCode >= 0xE000 && charCode <= 0xFFFD) || (charCode >= 0x10000 && charCode <= 0x10FFFF)) { + if (charCode == '&') + out.append("&"); + else if (charCode == '"') + out.append("""); + else if (charCode == '<') + out.append("<"); + else if (charCode == '>') + out.append(">"); + else if (charCode == '\'') + out.append("'"); + else + out.append(c); + } /* + * else { // Encoding Q??????: + * io.starter.toolkit.Logger.log("Skipping Special Char: " + charCode); // + * skip it } + */ + } + /* + * these translations do not seem to make any difference for Baxter's issue } + * else if (charCode==8220) { // smart quotes BAXTER ISSUE - TRY + * out.append("“"); } else if (charCode==8221) { // "" + * out.append("”"); } else if (charCode==8216) { // "" + * out.append("‘"); } else if (charCode==8217) { // "" + * out.append("’"); } else if (charCode==8211){ // en dash + * out.append("–"); } else if (charCode==8212){ // em dash + * out.append("—"); } else if (charCode==8242) { // single prime + * out.append("′"); } else if (charCode==8364) { // Euro Symbol + * out.append("€"); } else // copyright= 169 // 10 \n 176=small circle + * out.append(c); // 169=© } + */ + return out; + } + + /** + * Strip non-ascii (i.e. xml non-valid) chars from Strings Node values can + * contain quote symbols + * + * @param s + * @return + */ + public static StringBuffer stripNonAsciiRetainQuote(String s) { + StringBuffer out = new StringBuffer(); + if (s == null) + return out; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + int charCode = c; + if (charCode >= 32 && charCode <= 126) { + if (charCode == '&') + out.append("&"); + else if (charCode == '<') + out.append("<"); + else if (charCode == '>') + out.append(">"); + else + out.append(c); + } else + out.append(c); + } + return out; + } + + /** + * deal with the "BOM" input streams... + * + * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 + * + * + * Oct 14, 2010 + * + * @param in + * @return + */ + protected static InputStream wrapInputStream(InputStream in) { + UnicodeInputStream uin = new UnicodeInputStream(in, "UTF-8"); + // String enc = uin.getEncoding(); // check for BOM mark and skip + return uin; + } + + /** + * used as a way to monitor Zip Entry fetching + * + * Oct 14, 2010 + * + * @param f + * @param name + * @return + */ + protected static ZipEntry getEntry(ZipFile f, String name) { + return f.getEntry(name); + } + + /** + * parses any .rels file into content List array list + * + * @param ii + * @return + */ + protected static ArrayList parseRels(InputStream ii) { + ArrayList contentList = new ArrayList(); + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + + xpp.setInput(ii, null); // using XML 1.0 specification + + // if(DEBUG) Logger.logInfo("parseRels InputStream has available bytes: " + + // ii.available()); + + int eventType = xpp.getEventType(); + + // if(DEBUG) Logger.logInfo("parseRels XPP Name: " + xpp.getName() ); + + // if(DEBUG) Logger.logInfo("parseRels XPP Event Type: " + eventType ); + + while (eventType != XmlPullParser.END_DOCUMENT) { + try { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm != null && tnm.equals("Relationship")) { + String type = "", target = "", rId = ""; + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); // id, Type, + // Target + String v = xpp.getAttributeValue(i); + if (nm.equalsIgnoreCase("Type")) + type = getRelationshipTypeAbbrev(v); + else if (nm.equalsIgnoreCase("Target")) + target = v; + else if (nm.equalsIgnoreCase("id")) + rId = v; + } + // 20100426 KSC: unfortunately, need to ensure that commentsX.xml is processed + // before vmlDrawingX.xml + if (target.indexOf("comments") == -1) + contentList.add(new String[] { type, target, rId }); + else // ensure comments are before vmlDrawing so that Notes can be created + contentList.add(0, new String[] { type, target, rId }); + } else { + // if(DEBUG) Logger.logInfo("parseRels null entry name"); + } + } + } catch (Exception ea) { + Logger.logErr("XML Exception in OOXMLAdapter.parseRels. Input file is out of spec.", ea); + } + eventType = xpp.next(); + } + + } catch (org.xmlpull.v1.XmlPullParserException ex) { + Logger.logErr("XML Exception in OOXMLAdapter.parseRels. Input file is out of spec.", ex); + + } catch (Exception e) { + Logger.logErr("OOXMLAdapter.parseRels. " + e.toString()); + } + return contentList; + } + + /** + * utility to retrieve Text element for tag + * + * @param xpp + * @return + * @throws IOException + */ + public static String getNextText(XmlPullParser xpp) throws IOException, XmlPullParserException { + int eventType = xpp.next(); + String ret = ""; + while (eventType != XmlPullParser.END_DOCUMENT && eventType != XmlPullParser.END_TAG + && eventType != XmlPullParser.START_TAG && /* true in all cases?? */ + eventType != XmlPullParser.TEXT) { + eventType = xpp.next(); + } + if (eventType == XmlPullParser.TEXT) + ret = xpp.getText(); + + try { + return new String(ret.getBytes(), inputEncoding); + /* + * KSC: replaced with above *SHOULD* be correct if (!isUTFEncoding) if + * (xpp.getInputEncoding().equals("UTF-8")) return new String(ret.getBytes(), + * "UTF-8"); // ensure encoding else return new String(ret.getBytes(), + * xpp.getInputEncoding()); // ensure encoding + */ + } catch (Exception e) { + } // inputEncoding can be null + return ret; + } + + /** + * simple utility that ensures that sheets are last in the workbookcontent list + * also ensure that theme(s) are parsed first, as are used in styles etc. in + * order to create all dependent objects first + * + * @param wbContentList + */ + protected void reorderWbContentList(ArrayList wbContentList) { + for (int j = 0; j < wbContentList.size(); j++) { + String[] wb = (String[]) wbContentList.get(j); + if (!wb[0].equals("sheet")) { // sheets come last + wbContentList.remove(j); + wbContentList.add(0, wb); + } + } + for (int j = 0; j < wbContentList.size(); j++) { + String[] wb = (String[]) wbContentList.get(j); + if (wb[0].equals("theme")) { // goes before styles + wbContentList.remove(j); + wbContentList.add(0, wb); + break; + } + } + + } + + /** + * return the file matching rId in the ContentList (String[] type, filename, + * rId) + * + * @param contentList + * @param rId + * @return + */ + protected static String getFilename(ArrayList contentList, String rId) { + for (int j = 0; j < contentList.size(); j++) { + String[] s = (String[]) contentList.get(j); + if (s[2].equals(rId)) + return s[1]; + } + return null; + } + + /** + * creates a copy of a stack so changes won't affect original + * + * @param origStack + * @return + */ + protected Stack cloneStack(Stack origStack) { + Stack s = new Stack(); + for (int i = 0; i < origStack.size(); i++) { + s.push(origStack.elementAt(i)); + } + return s; + } + + public static boolean deleteDir(File f) { + if (f.isDirectory()) { + String[] children = f.list(); + for (int i = 0; i < children.length; i++) { + boolean success = deleteDir(new File(f, children[i])); + if (!success) { + // return false; + } + } + } + // The directory is now empty so delete it + f.deleteOnExit(); + return f.delete(); + } + + public static String getTempDir(String f) throws IOException { + /* + * File fx = TempFileManager.createTempFile("OOXMLA",".tmp"); File fdir = + * fx.getParentFile(); String s = ""; if(fdir.isDirectory()) s = + * fdir.getAbsolutePath(); else{ + */ + String s = System.getProperty("java.io.tmpdir"); + + if (!(s.endsWith("/") || s.endsWith("\\"))) + s += "/"; + f = StringTool.stripPath(f); + s += "extentech/"; + if (f.indexOf('.') > 0) + s += f.substring(0, f.indexOf('.')) + "/"; + else + s += f + "/"; + + return s; + } + + /** + * sorts the sheets for incoming workbook xlsx -- used for eventMode only + * * Jan 19, 2011 + * * @param cl */ - protected static void sortSheets(ArrayList cl) throws Exception{ + protected static void sortSheets(ArrayList cl) throws Exception { // take the array of storages and find sheets // use natural sort of the Tree TreeMap sorted = new TreeMap(); - + Iterator its = cl.iterator(); - while(its.hasNext()){ - String[] c = (String[])its.next(); + while (its.hasNext()) { + String[] c = (String[]) its.next(); String shtnm = c[1]; // parse out sheet number // xxxxsheet1.xmlxxxx int st = shtnm.indexOf("worksheets/sheet"); - if(st>-1){ - st+=16; + if (st > -1) { + st += 16; shtnm = shtnm.substring(st); - shtnm = shtnm.substring(0,shtnm.toLowerCase().indexOf(".xml")); - try{ + shtnm = shtnm.substring(0, shtnm.toLowerCase().indexOf(".xml")); + try { int ti = Integer.parseInt(shtnm); // we know the sheet number, add to the tree - sorted.put(Integer.valueOf(ti),c); - }catch(Exception e){ + sorted.put(Integer.valueOf(ti), c); + } catch (Exception e) { Logger.logErr("Could not sort sheets", e); return; } } } // now we have the sorted map of "CLs" we can re-order sheets in arraylist - its = cl.iterator(); + its = cl.iterator(); Iterator sort = sorted.values().iterator(); int clpos = -1; - while(sort.hasNext()){ - String[] c = (String[])sort.next(); + while (sort.hasNext()) { + String[] c = (String[]) sort.next(); boolean found = false; // now find in cl - while(its.hasNext() && !found){ + while (its.hasNext() && !found) { clpos++; - String[] cx = (String[])its.next(); - if(cx[0].equals("sheet")){ // replace the sheet entries one by one - cl.set(clpos,c); + String[] cx = (String[]) its.next(); + if (cx[0].equals("sheet")) { // replace the sheet entries one by one + cl.set(clpos, c); found = true; } } } } - + /** - * re-save to temp directroy any "pass-through" - * OOXML files i.e. files/entities not present in 2003-version and thus not processed - * - *
(app.xml, theme.xml ...) + * re-save to temp directroy any "pass-through" OOXML files i.e. files/entities + * not present in 2003-version and thus not processed + * + *
+ * (app.xml, theme.xml ...) + * * @param wbh */ - public static void refreshPassThroughFiles(WorkBookHandle wbh) { + public static void refreshPassThroughFiles(WorkBookHandle wbh) { try { // retrieve source zip - java.util.zip.ZipFile sourceZip= new java.util.zip.ZipFile(wbh.getFile()); - OOXMLReader.refreshExternalFiles(sourceZip, OOXMLAdapter.getTempDir(wbh.getWorkBook().getFactory().getFileName())); + java.util.zip.ZipFile sourceZip = new java.util.zip.ZipFile(wbh.getFile()); + OOXMLReader.refreshExternalFiles(sourceZip, + OOXMLAdapter.getTempDir(wbh.getWorkBook().getFactory().getFileName())); wbh.setFile(null); - } catch (Exception e) { // wbh.getFile() can be an XLS file (as source) so Exception is almost always OK (do not report) - //Logger.logErr("OOXMLAdapter.refreshPassThroughFiles: could not retrieve source ooxml: " + e.toString()); + } catch (Exception e) { // wbh.getFile() can be an XLS file (as source) so Exception is almost always OK + // (do not report) + // Logger.logErr("OOXMLAdapter.refreshPassThroughFiles: could not retrieve + // source ooxml: " + e.toString()); } - } - + } + } /** @@ -917,60 +897,67 @@ public static void refreshPassThroughFiles(WorkBookHandle wbh) { * */ class intArray { - private int[] a= null; - - public intArray(int[] a) { - this.a= new int[a.length]; - for (int i= 0; i < a.length; i++) { - this.a[i]= a[i]; - } - } - public intArray(short[] a){ - this.a= new int[a.length]; - for (int i= 0; i < a.length; i++) { - this.a[i]= a[i]; - } - } - - public boolean isZero() { - if (a==null) return true; - for (int i= 0; i < a.length; i++) - if (a[i]!=0) - return false; - return true; - } - - public int[] get() { return a; } - public boolean equals(Object o) { - int[] testa= ((intArray) o).get(); - if (testa==null || a==null || testa.length!=this.a.length) - return false; - for (int i= 0; i < this.a.length; i++) { - if (this.a[i]!=testa[i]) - return false; - } - return true; - } + private int[] a = null; + + public intArray(int[] a) { + this.a = new int[a.length]; + for (int i = 0; i < a.length; i++) { + this.a[i] = a[i]; + } + } + + public intArray(short[] a) { + this.a = new int[a.length]; + for (int i = 0; i < a.length; i++) { + this.a[i] = a[i]; + } + } + + public boolean isZero() { + if (a == null) + return true; + for (int i = 0; i < a.length; i++) + if (a[i] != 0) + return false; + return true; + } + + public int[] get() { + return a; + } + + @Override + public boolean equals(Object o) { + int[] testa = ((intArray) o).get(); + if (testa == null || a == null || testa.length != this.a.length) + return false; + for (int i = 0; i < this.a.length; i++) { + if (this.a[i] != testa[i]) + return false; + } + return true; + } } class objArray { - private Object[] a= null; - - public objArray(Object[] a) { - this.a= a; - /* - this.a= new Object[a.length]; - for (int i= 0; i < a.length; i++) { - this.a[i]= a[i]; - } - */ - } - - public Object[] get() { return a; } - - public boolean equals(Object o) { - Object[] testa= ((objArray) o).get(); - return java.util.Arrays.equals(this.a, testa); - } - + private Object[] a = null; + + public objArray(Object[] a) { + this.a = a; + /* + * this.a= new Object[a.length]; for (int i= 0; i < a.length; i++) { this.a[i]= + * a[i]; } + */ + } + + public Object[] get() { + return a; + } + + @Override + public boolean equals(Object o) { + Object[] testa = ((objArray) o).get(); + return java.util.Arrays.equals(this.a, testa); + } + } diff --git a/src/main/java/io/starter/formats/XLS/OOXMLReader.java b/src/main/java/io/starter/formats/XLS/OOXMLReader.java index 64983b3..31ff6cd 100644 --- a/src/main/java/io/starter/formats/XLS/OOXMLReader.java +++ b/src/main/java/io/starter/formats/XLS/OOXMLReader.java @@ -665,7 +665,7 @@ else if (eventType == XmlPullParser.END_TAG && xpp.getName().equals("cellXfs")) else if (tnm.equals("rgbColor")) { // save custom indexed colors String clr= "#" + xpp.getAttributeValue(0).substring(2); - //io.starter.OpenXLS.util.Logger.log(clr); + //io.starter.toolkit.Logger.log(clr); // usually the same as COLORTABLE but sometimes different too :) try { bk.getWorkBook().getColorTable()[indexedColor++]= FormatHandle.HexStringToColor(clr); diff --git a/src/main/java/io/starter/formats/XLS/PivotCache.java b/src/main/java/io/starter/formats/XLS/PivotCache.java index a406bde..2ce8320 100644 --- a/src/main/java/io/starter/formats/XLS/PivotCache.java +++ b/src/main/java/io/starter/formats/XLS/PivotCache.java @@ -81,9 +81,9 @@ public void init(StorageTable directories, WorkBookHandle wbh) throws StorageNot /* // KSC: TESTING try { -io.starter.OpenXLS.util.Logger.log(rec.getClass().getName().substring(rec.getClass().getName().lastIndexOf(".")+1) + ": " + Arrays.toString(((PivotCacheRecord)rec).getRecord())); +io.starter.toolkit.Logger.log(rec.getClass().getName().substring(rec.getClass().getName().lastIndexOf(".")+1) + ": " + Arrays.toString(((PivotCacheRecord)rec).getRecord())); } catch (ClassCastException e) { -io.starter.OpenXLS.util.Logger.log(rec.getClass().getName().substring(rec.getClass().getName().lastIndexOf(".")+1) + ": " + Arrays.toString(ByteTools.shortToLEBytes(rec.getOpcode())) + Arrays.toString(ByteTools.shortToLEBytes((short)rec.getData().length)) + Arrays.toString(rec.getData())); +io.starter.toolkit.Logger.log(rec.getClass().getName().substring(rec.getClass().getName().lastIndexOf(".")+1) + ": " + Arrays.toString(ByteTools.shortToLEBytes(rec.getOpcode())) + Arrays.toString(ByteTools.shortToLEBytes((short)rec.getData().length)) + Arrays.toString(rec.getData())); } */ if (wbh!=null) @@ -115,7 +115,7 @@ public void createPivotCache(StorageTable directories, WorkBookHandle wbh, Strin try { // KSC: TESTING if (wbh.getDebugLevel() > 100) - io.starter.OpenXLS.util.Logger.log(String.format("creatpivotCache: ref: %s sid %d", ref, sId)); + io.starter.toolkit.Logger.log(String.format("creatpivotCache: ref: %s sid %d", ref, sId)); /** * the Pivot Cache Storage specifies zero or more streams, each of which specify a PivotCache * The name of each stream (1) MUST be unique within the storage, and the name MUST be a four digit hexadecimal number stored as text. @@ -192,12 +192,12 @@ private void updateCacheRecords(int cacheId) { for (BiffRec br: pivotCacheRecs.get(cacheId+1)) { try { newbytes= ByteTools.append(((PivotCacheRecord)br).getRecord(), newbytes); -//io.starter.OpenXLS.util.Logger.log(br.getClass().getName().substring(br.getClass().getName().lastIndexOf(".")+1) + ": " + Arrays.toString(((PivotCacheRecord)br).getRecord())); +//io.starter.toolkit.Logger.log(br.getClass().getName().substring(br.getClass().getName().lastIndexOf(".")+1) + ": " + Arrays.toString(((PivotCacheRecord)br).getRecord())); } catch (ClassCastException e) { newbytes= ByteTools.append(ByteTools.shortToLEBytes(br.getOpcode()), newbytes); newbytes= ByteTools.append(ByteTools.shortToLEBytes((short)br.getData().length), newbytes); newbytes= ByteTools.append(br.getData(), newbytes); -//io.starter.OpenXLS.util.Logger.log(br.getClass().getName().substring(br.getClass().getName().lastIndexOf(".")+1) + ": " + Arrays.toString(ByteTools.shortToLEBytes(br.getOpcode())) + Arrays.toString(ByteTools.shortToLEBytes((short)br.getData().length)) + Arrays.toString(br.getData())); +//io.starter.toolkit.Logger.log(br.getClass().getName().substring(br.getClass().getName().lastIndexOf(".")+1) + ": " + Arrays.toString(ByteTools.shortToLEBytes(br.getOpcode())) + Arrays.toString(ByteTools.shortToLEBytes((short)br.getData().length)) + Arrays.toString(br.getData())); } } Storage pcache1= caches.get(cacheId+1); @@ -233,10 +233,10 @@ byte[] createPivotCacheRecords(String ref, WorkBookHandle wbh, int sId) { sxdb.setNCacheFields(cols.length); sxdb.setStreamID(sId); newbytes= ByteTools.append(sxdb.getRecord(), newbytes); -//io.starter.OpenXLS.util.Logger.log("SXDB: " + Arrays.toString(sxdb.getRecord())); +//io.starter.toolkit.Logger.log("SXDB: " + Arrays.toString(sxdb.getRecord())); SXDBEx sxdbex= (SXDBEx)SXDBEx.getPrototype(); //TODO: nFormulas newbytes= ByteTools.append(sxdbex.getRecord(), newbytes); -//io.starter.OpenXLS.util.Logger.log("SXDBEX: " + Arrays.toString(sxdbex.getRecord())); +//io.starter.toolkit.Logger.log("SXDBEX: " + Arrays.toString(sxdbex.getRecord())); // TODO: cells after row header cell ***should be*** the same type -- true in ALL cases?????? if (ch.length>cols.length) { // have multiple rows for (int i= 0; i < cols.length; i++) { @@ -259,10 +259,10 @@ byte[] createPivotCacheRecords(String ref, WorkBookHandle wbh, int sId) { sxfdb.setCacheField(ch[i].getStringVal()); // row header values sxfdb.setNCacheItems(0); // only set ACTUAL cache items when put cache field(s)on the pivot table (on row, page, column or data axis) newbytes= ByteTools.append(sxfdb.getRecord(), newbytes); -//io.starter.OpenXLS.util.Logger.log("SXFDB: " + Arrays.toString(sxfdb.getRecord())); +//io.starter.toolkit.Logger.log("SXFDB: " + Arrays.toString(sxfdb.getRecord())); SXFDBType sxfdbtype= (SXFDBType) SXFDBType.getPrototype(); newbytes= ByteTools.append(sxfdbtype.getRecord(), newbytes); -//io.starter.OpenXLS.util.Logger.log("SXDFBTYPE: " + Arrays.toString(sxfdbtype.getRecord())); +//io.starter.toolkit.Logger.log("SXDFBTYPE: " + Arrays.toString(sxfdbtype.getRecord())); continue; } cachefieldindexes[i][z-1]= (byte)i; @@ -275,7 +275,7 @@ byte[] createPivotCacheRecords(String ref, WorkBookHandle wbh, int sId) { SXString sxstring= (SXString) SXString.getPrototype(); sxstring.setCacheItem(c.getStringVal()); newbytes= ByteTools.append(sxstring.getRecord(), newbytes); -//io.starter.OpenXLS.util.Logger.log("SXSTRING: " + Arrays.toString(sxstring.getRecord())); +//io.starter.toolkit.Logger.log("SXSTRING: " + Arrays.toString(sxstring.getRecord())); break; case XLSConstants.TYPE_FP: case XLSConstants.TYPE_INT: @@ -283,13 +283,13 @@ byte[] createPivotCacheRecords(String ref, WorkBookHandle wbh, int sId) { SXNum sxnum= (SXNum) SXNum.getPrototype(); sxnum.setNum(c.getDoubleVal()); newbytes= ByteTools.append(sxnum.getRecord(), newbytes); -//io.starter.OpenXLS.util.Logger.log("SXNUM: " + Arrays.toString(sxnum.getRecord())); +//io.starter.toolkit.Logger.log("SXNUM: " + Arrays.toString(sxnum.getRecord())); break; case XLSConstants.TYPE_BOOLEAN: SXBool sxbool= (SXBool) SXBool.getPrototype(); sxbool.setBool(c.getBooleanVal()); newbytes= ByteTools.append(sxbool.getRecord(), newbytes); -//io.starter.OpenXLS.util.Logger.log("SXBOOL: " + Arrays.toString(sxbool.getRecord())); +//io.starter.toolkit.Logger.log("SXBOOL: " + Arrays.toString(sxbool.getRecord())); //TYPE_FORMULA = 3, SxFmla *(SxName *SXPair) case 6: // SXDtr @@ -303,7 +303,7 @@ byte[] createPivotCacheRecords(String ref, WorkBookHandle wbh, int sId) { byte[] b= new byte[4]; System.arraycopy(ByteTools.shortToLEBytes(XLSConstants.EOF), 0, b, 0, 2); newbytes= ByteTools.append(b, newbytes); -//io.starter.OpenXLS.util.Logger.log("EOF: " + Arrays.toString((b))); +//io.starter.toolkit.Logger.log("EOF: " + Arrays.toString((b))); return newbytes; } } diff --git a/src/main/java/io/starter/formats/XLS/ReferenceTracker.java b/src/main/java/io/starter/formats/XLS/ReferenceTracker.java index 3b16547..82ad32b 100644 --- a/src/main/java/io/starter/formats/XLS/ReferenceTracker.java +++ b/src/main/java/io/starter/formats/XLS/ReferenceTracker.java @@ -960,7 +960,7 @@ public Iterator getParents(BiffRec cell) { } } } -//io.starter.OpenXLS.util.Logger.log(""); +//io.starter.toolkit.Logger.log(""); // return parents.toArray(); //iterator(); return parents.iterator(); } diff --git a/src/main/java/io/starter/formats/XLS/Unicodestring.java b/src/main/java/io/starter/formats/XLS/Unicodestring.java index 1469d50..32a546f 100644 --- a/src/main/java/io/starter/formats/XLS/Unicodestring.java +++ b/src/main/java/io/starter/formats/XLS/Unicodestring.java @@ -271,7 +271,7 @@ void initEasternEncoding(byte[] ustrdata, boolean extrstbrk) throws NegativeArra cchExtRst = ByteTools.readInt(ustrdata[5], ustrdata[6], ustrdata[7], ustrdata[8]); // report error??? if ((ustrdata.length-dlen-offer-cchExtRst)!=formatlen) -io.starter.OpenXLS.util.Logger.log("Unicodestring.initEasternEncoding: Format runs are not correct"); +io.starter.toolkit.Logger.log("Unicodestring.initEasternEncoding: Format runs are not correct"); ExtRst = new byte[cchExtRst]; System.arraycopy(ustrdata, (dlen + offer + formatlen), ExtRst, 0, cchExtRst); // Extendadata //report error??? diff --git a/src/main/java/io/starter/formats/XLS/WorkBook.java b/src/main/java/io/starter/formats/XLS/WorkBook.java index 5fa06e3..fe85034 100644 --- a/src/main/java/io/starter/formats/XLS/WorkBook.java +++ b/src/main/java/io/starter/formats/XLS/WorkBook.java @@ -5,36 +5,71 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS; import java.awt.Color; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.OutputStream; +import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.*; - -import io.starter.OpenXLS.*; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.TreeMap; +import java.util.Vector; + +import io.starter.OpenXLS.Cell; +import io.starter.OpenXLS.DateConverter; +import io.starter.OpenXLS.FormatHandle; +import io.starter.OpenXLS.ImageHandle; import io.starter.OpenXLS.WorkBookException; +import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.OOXML.Theme; -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.*; -import io.starter.formats.XLS.charts.*; - +import io.starter.formats.XLS.charts.Ai; +import io.starter.formats.XLS.charts.Chart; +import io.starter.formats.XLS.charts.Fontx; +import io.starter.formats.XLS.charts.GenericChartObject; +import io.starter.formats.XLS.formulas.IlblListener; +import io.starter.formats.XLS.formulas.Ptg; +import io.starter.formats.XLS.formulas.PtgArea3d; +import io.starter.formats.XLS.formulas.PtgExp; +import io.starter.formats.XLS.formulas.PtgNameX; +import io.starter.formats.XLS.formulas.PtgRef; +import io.starter.toolkit.FastAddVector; +import io.starter.toolkit.Logger; /**

     The WorkBook record represents an XLS workbook substream containing worksheets and associated records.
@@ -50,176 +85,327 @@
  */
 
 public class WorkBook implements Serializable, XLSConstants, Book {
-    
 
-	private static final long serialVersionUID = 2282017774412632087L;
+	private static final long		serialVersionUID			= 2282017774412632087L;
+
+	DateConverter.DateFormat		dateFormat					= DateConverter.DateFormat.LEGACY_1900;
+
+	// public constants
+	public static int				STRING_ENCODING_AUTO		= 0;
+	public static int				STRING_ENCODING_UNICODE		= 1;
+	public static int				STRING_ENCODING_COMPRESSED	= 2;
+	public static int				ALLOWDUPES					= 0;
+	public static int				SHAREDUPES					= 1;
+
+	private int						DEBUGLEVEL					= 0;
+	private int						bofct						= 0, eofct = 0,
+			indexnum = 0;
+	private int						defaultIxfe					= 15;
+	private int						CalcMode					= CALCULATE_AUTO;
+	private int						defaultLanguage				= 0;									// default
+																										// language
+																										// code
+																										// for
+																										// the
+																										// current
+																										// workbook
+	private boolean					copying						= false;
+	private boolean					sharedupes					= false;
+	private AbstractList			xfrecs						= new Vector();
+	private AbstractList			indexes						= new Vector();
+	private AbstractList			names						= new Vector();							// ALL
+																										// of
+																										// the
+																										// names
+																										// (including
+																										// worksheet
+																										// scoped,
+																										// etc)
+	private AbstractList			orphanedPtgNames			= new Vector();
+	private AbstractList			externalnames				= new Vector();
+	AbstractList					formulas					= new FastAddVector();
+	private AbstractList			fonts						= new Vector();
+	public Color[]					colorTable;
+
+	/** The list of Format records indexed by format ID. */
+	private TreeMap					formats						= new TreeMap();
+	private AbstractList			charts						= new Vector();
+	/** OOXML-specific */
+	private AbstractList			ooxmlObjects				= new Vector();							// stores
+																										// OOXML
+																										// objects
+																										// external
+																										// to
+																										// workbook
+																										// e.g.
+																										// oleObjects,
+	private String					ooxmlcodename				= null;									// stores
+																										// OOXML
+																										// codename
+	private ArrayList				dxfs						= null;									// 20090622
+																										// KSC:
+																										// stores
+																										// dxf's
+																										// (incremental
+																										// style
+																										// info)
+																										// per
+																										// workbook
+	private int						firstSheet					= 0;									// specifies
+																										// first
+																										// sheet
+																										// (ooxml)
+	private Theme					theme						= null;
+
+	// Reference Tracking
+	private ReferenceTracker		refTracker					= new ReferenceTracker();
+
+	// various
+	private AbstractList			boundsheets					= new Vector();							// TODO:
+																										// remove
+																										// this
+																										// variable?
+																										// its
+																										// duplicated
+																										// in
+																										// workSheets
+	private AbstractList			hlinklookup					= new Vector(
+			20);
+	private AbstractList			mergecelllookup				= new Vector(
+			20);
 
-	DateConverter.DateFormat dateFormat =
-			DateConverter.DateFormat.LEGACY_1900;
-    
-    // public constants
-    public static int STRING_ENCODING_AUTO = 0;
-    public static int STRING_ENCODING_UNICODE = 1;
-    public static int STRING_ENCODING_COMPRESSED = 2;
-    public static int ALLOWDUPES = 0;
-    public static int SHAREDUPES = 1;
-    
-    private int DEBUGLEVEL = 0;
-    private int bofct = 0, eofct = 0, indexnum = 0;
-    private int defaultIxfe = 15;
-    private int CalcMode = CALCULATE_AUTO;
-    private int defaultLanguage= 0;	// default language code for the current workbook
-    private boolean copying = false;
-    private boolean sharedupes = false;
-    private AbstractList xfrecs =           new Vector();
-    private AbstractList indexes =          new Vector();
-    private AbstractList names =            new Vector(); // ALL of the names (including worksheet scoped, etc)
-    private AbstractList orphanedPtgNames = new Vector();
-    private AbstractList externalnames=     new Vector(); 
-    AbstractList formulas =         		new FastAddVector();
-    private AbstractList fonts =            new Vector();
-    public Color[] colorTable;
-    
-    /** The list of Format records indexed by format ID. */
-    private TreeMap formats = new TreeMap();    
-    private AbstractList charts =           new Vector();
-    /** OOXML-specific */
-    private AbstractList ooxmlObjects=		new Vector();	// stores OOXML objects external to workbook e.g. oleObjects,
-    private String ooxmlcodename= 			null;			// stores OOXML codename
-    private ArrayList dxfs=					null;			// 20090622 KSC: stores dxf's (incremental style info) per workbook				
-    private int firstSheet= 0;				// specifies first sheet (ooxml)
-    private Theme theme=					    null;
-
-    // Reference Tracking
-    private ReferenceTracker refTracker = new ReferenceTracker();
-    
-    // various
-    private AbstractList boundsheets =  new Vector();  //TODO:  remove this variable?  its duplicated in workSheets
-    private AbstractList hlinklookup =      new Vector(20);
-    private AbstractList mergecelllookup =  new Vector(20);
-    
-    /*Indirect formulas that need to be calculated after load for reftracker*/
-    private AbstractList indirectFormulas =  new ArrayList();
-    private AbstractList supBooks=          new Vector(); 
+	/*
+	 * Indirect formulas that need to be calculated after load
+	 * for reftracker
+	 */
+	private AbstractList			indirectFormulas			= new ArrayList();
+	private AbstractList			supBooks					= new Vector();
 
-    
-    /* This is here only to allow client code to compile, should be removed */
-	public static String CONVERTMULBLANKS = "deprecated";
-	
-    // We should consider using an ordered collections class for some of these?
-    // an enumeration of worksheets for instance will not always be in the same
-    // order, causing tests to fail...
-    private Hashtable workSheets    = new Hashtable(100, .950f);
-    private HashMap bookNameRecs     = new HashMap();
-    private Hashtable formulashash  = new Hashtable();
-    
-    /** Maps number format patterns to their IDs. */
-    private Hashtable formatlookup  = new Hashtable(30);
-    
-    private Hashtable ptViews      = new Hashtable(20);// TODO: move to sheet - sheet-level pivot table view recordss    
-    private ArrayList ptstream  = new ArrayList();		// wb-level pivot cache definitions usually only 1
-    private PivotCache ptcache= null;					// if has pivot tables this is the one and only pivot cache
-    
-    public HashMap formatCache = new HashMap();
-    
-    private Index lastidx;
-    private Sst stringTable;
-    private Bof lastBOF;
-    private Externsheet myexternsheet;
-    private Bof firstBOF;
-    private ByteStreamer streamer = createByteStreamer();
-    private TabID tabs;
-    private Window1 win1;
-    private CalcMode calcmoderec;		// determines recalculation mode for workbook - Manual, Auto ...
-    private DefaultRowHeight drh;
-    private Chart currchart;
-    private Ai currai;
-    private Supbook myADDINSUPBOOK= null; // for external names SUPBOOK
-    WorkBookFactory factory;
-    private ContinueHandler contHandler = new ContinueHandler(this);
-    private Usersviewbegin usersview;
-    private Eof lasteof;
-    private BiffRec xl2k = null;
-	public AbstractList msodgMerge = new Vector();	
-	public MSODrawingGroup msodg= null;	
-	public int lastSPID= 1024;	// 20071030 last or next SPID (= shape ID or image ID); incremented upon new images ... appropriate to store at book level (?) 
-    private MSODrawing currdrw = null;
-
-    private BookProtectionManager protector;
-
-    protected void reflectiveClone (WorkBook source) {
-    	for (Field field : WorkBook.class.getDeclaredFields()) {
-    		if (Modifier.isStatic( field.getModifiers() )) continue;
-    		
-    		try {
-				field.set( this, field.get( source ) );
+	/*
+	 * This is here only to allow client code to compile, should
+	 * be removed
+	 */
+	public static String			CONVERTMULBLANKS			= "deprecated";
+
+	// We should consider using an ordered collections class for
+	// some of these?
+	// an enumeration of worksheets for instance will not always
+	// be in the same
+	// order, causing tests to fail...
+	private Hashtable				workSheets					= new Hashtable(
+			100, .950f);
+	private HashMap					bookNameRecs				= new HashMap();
+	private Hashtable				formulashash				= new Hashtable();
+
+	/** Maps number format patterns to their IDs. */
+	private Hashtable				formatlookup				= new Hashtable(
+			30);
+
+	private Hashtable				ptViews						= new Hashtable(
+			20);																						// TODO:
+																										// move
+																										// to
+																										// sheet
+																										// -
+																										// sheet-level
+																										// pivot
+																										// table
+																										// view
+																										// recordss
+	private ArrayList				ptstream					= new ArrayList();						// wb-level
+																										// pivot
+																										// cache
+																										// definitions
+																										// usually
+																										// only
+																										// 1
+	private PivotCache				ptcache						= null;									// if
+																										// has
+																										// pivot
+																										// tables
+																										// this
+																										// is
+																										// the
+																										// one
+																										// and
+																										// only
+																										// pivot
+																										// cache
+
+	public HashMap					formatCache					= new HashMap();
+
+	private Index					lastidx;
+	private Sst						stringTable;
+	private Bof						lastBOF;
+	private Externsheet				myexternsheet;
+	private Bof						firstBOF;
+	private ByteStreamer			streamer					= createByteStreamer();
+	private TabID					tabs;
+	private Window1					win1;
+	private CalcMode				calcmoderec;														// determines
+																										// recalculation
+																										// mode
+																										// for
+																										// workbook
+																										// -
+																										// Manual,
+																										// Auto
+																										// ...
+	private DefaultRowHeight		drh;
+	private Chart					currchart;
+	private Ai						currai;
+	private Supbook					myADDINSUPBOOK				= null;									// for
+																										// external
+																										// names
+																										// SUPBOOK
+	WorkBookFactory					factory;
+	private ContinueHandler			contHandler					= new ContinueHandler(
+			this);
+	private Usersviewbegin			usersview;
+	private Eof						lasteof;
+	private BiffRec					xl2k						= null;
+	public AbstractList				msodgMerge					= new Vector();
+	public MSODrawingGroup			msodg						= null;
+	public int						lastSPID					= 1024;									// 20071030
+																										// last
+																										// or
+																										// next
+																										// SPID
+																										// (=
+																										// shape
+																										// ID
+																										// or
+																										// image
+																										// ID);
+																										// incremented
+																										// upon
+																										// new
+																										// images
+																										// ...
+																										// appropriate
+																										// to
+																										// store
+																										// at
+																										// book
+																										// level
+																										// (?)
+	private MSODrawing				currdrw						= null;
+
+	private BookProtectionManager	protector;
+
+	protected void reflectiveClone(WorkBook source) {
+		for (Field field : WorkBook.class.getDeclaredFields()) {
+			if (Modifier.isStatic(field.getModifiers()))
+				continue;
+
+			try {
+				field.set(this, field.get(source));
 			} catch (IllegalAccessException e) {
-				throw new RuntimeException( e );
+				throw new RuntimeException(e);
 			}
-    	}
-    }
-    
-    protected ByteStreamer createByteStreamer() {
-    	return new ByteStreamer( this );
-    }
-    
+		}
+	}
+
+	protected ByteStreamer createByteStreamer() {
+		return new ByteStreamer(this);
+	}
+
 	/** Gets this sheet's SheetProtectionManager.
-     */
-    public BookProtectionManager getProtectionManager() {
-    	if (protector == null) protector = new BookProtectionManager( this );
-    	return protector;
-    }
-	
+	 */
+	public BookProtectionManager getProtectionManager() {
+		if (protector == null)
+			protector = new BookProtectionManager(this);
+		return protector;
+	}
+
 	/** init the ImageHandles  
 	 * 
 	 * 
 	 */
-	void initImages() 	 {	
-		lastSPID= Math.max(lastSPID, msodg.getSpidMax()); // 20090508 KSC: lastSPID should also account for charts [BUGTRACKER 2372 copyChartToSheet error]
+	void initImages() {
+		lastSPID = Math.max(lastSPID, msodg.getSpidMax()); // 20090508 KSC:
+															// lastSPID should
+															// also account for
+															// charts
+															// [BUGTRACKER 2372
+															// copyChartToSheet
+															// error]
 		// 20071217 KSC: clear out imageMap before inputting!
-		for (int x= 0; x < this.getWorkSheets().length; x++) {
+		for (int x = 0; x < this.getWorkSheets().length; x++) {
 			(this.getWorkSheets()[x]).imageMap.clear();
 		}
-		for(int i=0;i 1)
-				msodgMerge.remove(msodgMerge.size()-1);
-			msodg.parse();			
+				msodgMerge.remove(msodgMerge.size() - 1);
+			msodg.parse();
 			this.initImages();
 		}
 	}
@@ -232,2402 +418,2610 @@ public void mergeMSODrawingRecords(){
 	 * @param bs
 	 */
 	public void updateMsodrawingHeaderRec(Boundsheet bs) {
-		MSODrawing msdHeader= msodg.getMsoHeaderRec(bs);
-		if (msdHeader!=null) {
-			int spContainerLength= 0;	// count of all other spcontainer lengths (sum=header spgroupcontainer)
-			int otherContainerLength= 0;	// count of other containers (solvercontainer,etc) added to dgcontainerlength
-			int numshapes= 2; 	// 20100324 KSC: total guess, really
-			int totalDrawingRecs= msodg.getMsodrawingrecs().size();
-			for (int z= 0; z < totalDrawingRecs; z++) {
-				 MSODrawing rec= (MSODrawing) msodg.getMsodrawingrecs().get(z);
-				 if (rec.getSheet().equals(bs)) {
-					 if (rec!=msdHeader && !rec.isHeader()) { 	// added header check- seems like *can* have multiple header recs in charts!
-						 spContainerLength+= rec.getSPContainerLength();
-						 otherContainerLength+= rec.getSOLVERContainerLength();
-						 if (rec.isShape)  // if it's a shape-type mso, count; there are other mso-types that are not SPCONTAINERS; apparently don't count these ...
-							 numshapes++;
-					 }
-				 } 
+		MSODrawing msdHeader = msodg.getMsoHeaderRec(bs);
+		if (msdHeader != null) {
+			int spContainerLength = 0; // count of all other spcontainer lengths
+										// (sum=header spgroupcontainer)
+			int otherContainerLength = 0; // count of other containers
+											// (solvercontainer,etc) added to
+											// dgcontainerlength
+			int numshapes = 2; // 20100324 KSC: total guess, really
+			int totalDrawingRecs = msodg.getMsodrawingrecs().size();
+			for (int z = 0; z < totalDrawingRecs; z++) {
+				MSODrawing rec = (MSODrawing) msodg.getMsodrawingrecs().get(z);
+				if (rec.getSheet().equals(bs)) {
+					if (rec != msdHeader && !rec.isHeader()) { // added header
+																// check- seems
+																// like *can*
+																// have multiple
+																// header recs
+																// in charts!
+						spContainerLength += rec.getSPContainerLength();
+						otherContainerLength += rec.getSOLVERContainerLength();
+						if (rec.isShape) // if it's a shape-type mso, count;
+											// there are other mso-types that
+											// are not SPCONTAINERS; apparently
+											// don't count these ...
+							numshapes++;
+					}
+				}
 			}
-			msdHeader.updateHeader(spContainerLength, otherContainerLength, numshapes, msdHeader.getlastSPID());
+			msdHeader
+					.updateHeader(spContainerLength, otherContainerLength, numshapes, msdHeader
+							.getlastSPID());
 		}
 	}
-	
-	 /**
-     * Get the MsoDrawingGroup for this workbook
-     * @return msodrawinggroup
-     */
+
+	/**
+	* Get the MsoDrawingGroup for this workbook
+	* @return msodrawinggroup
+	*/
 	public MSODrawingGroup getMSODrawingGroup() {
-        return msodg;
-    }
-	
+		return msodg;
+	}
+
 	/**
 	 * Set the msodrawinggroup for this workbook
 	 * @param msodg
 	 */
-	public void setMSODrawingGroup(MSODrawingGroup msodg) { 
-		this.msodg= msodg;
+	public void setMSODrawingGroup(MSODrawingGroup msodg) {
+		this.msodg = msodg;
 		this.msodg.setWorkBook(this);
-    	this.msodg.setStreamer(this.getStreamer());
+		this.msodg.setStreamer(this.getStreamer());
 	}
-	
+
 	/**
 	 * For workbooks that do not contain an MSODrawing group create a new one, 
 	 * if the drawing group already exists return the existing 
 	 * @return
 	 */
 	public MSODrawingGroup createMSODrawingGroup() {
-	    if(msodg != null)return msodg;
-	    this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup.getPrototype()); 
-	    return msodg;
-	}
-   
- 	
-    /** Return some useful statistics about the WorkBook
-     * 
-     * @return
-     */
-	public String getStats(){
+		if (msodg != null)
+			return msodg;
+		this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup
+				.getPrototype());
+		return msodg;
+	}
+
+	/** Return some useful statistics about the WorkBook
+	 * 
+	 * @return
+	 */
+	public String getStats() {
 		return getStats(false);
 	}
-	
-    /** Return some useful statistics about the WorkBook
-     * 
-     * @return
-     * 
-     */
-    public String getStats(boolean usehtml) {
-    	String rex = "\r\n";
-    	if(usehtml)
-    		rex = "
"; - - String ret = "-------------------------------------------" + rex; - ret += "OpenXLS Version: " + WorkBookHandle.getVersion() + rex; - ret += "Excel Version: " + getXLSVersionString() + rex; - ret += "-------------------------------------------------\r\n"; - ret += "Statistics for: "+ this.toString() + rex; - ret += "Number of Worksheets: " + this.getNumWorkSheets() + rex; - ret += "Number of Cells: " + this.getNumCells() + rex; - ret += "Number of Formulas: " + this.getNumFormulas() + rex; - ret += "Number of Charts: " + this.getChartVect().size() + rex; - ret += "Number of Fonts: " + this.getNumFonts() + rex; - ret += "Number of Formats: " + this.getNumFormats() + rex; - ret += "Number of Xfs: " + this.getNumXfs() + rex; - // ret += "StringTable: " + this.stringTable.toString() + rex; - ret += "-------------------------------------------------\r\n"; - return ret; - } - - public String getXLSVersionString() { - return lastBOF.getXLSVersionString(); - } - - private void addMergedcells(Mergedcells c){ - this.mergecelllookup.add(c); - } - - private void addHlink(Hlink r){ - this.hlinklookup.add(r); - } - - /** take care of any lazy updating before output - * - */ - public void prestream(){ - if (this.getMSODrawingGroup()!=null)this.getMSODrawingGroup().prestream(); - } - - public Chart[] getCharts() { - Chart[] chts = new Chart[this.charts.size()]; - return (Chart[]) charts.toArray(chts); - } - - public Supbook[] getSupBooks() { - Supbook[] sbs = new Supbook[this.supBooks.size()]; - return (Supbook[]) this.supBooks.toArray(sbs); - } - - /** - * returns the list of OOXML objects which are external or auxillary to the main workbook - * e.g. theme, doc properties - * @return - */ - public List getOOXMLObjects() { - return ooxmlObjects; - } - - /** - * adds the object-specific signature of the external or auxillary OOXML object - * Object should be of String[] form, - * key, path, local path + filename [, rid, [extra info], [embedded information]] - * e.g. theme, doc properties - * @param o - */ - public void addOOXMLObject(Object o) { - if (!((String[])o)[0].equals("externalLink")) - ooxmlObjects.add(o); - else - ooxmlObjects.add(0, o); // ensure ExternalLinks are 1st because they are linked via rId in workbook.xml - } - - /** - * return the OOXML theme for this workbook, if any - * @return - */ - public Theme getTheme() { return theme; } - - /** - * sets the OOXML theme for this 2007 verison workbook - * @param t - */ - public void setTheme(Theme t) { theme= t; } - /** - * return the External Supbook record associated with the desired externalWorkbook - * will create if bCreate - * @param externalWorkbook String URL (name) of External Workbook - * @param bCreate if true, will create an external SUPBOOK record for the externalWorkbook - * @return Supbook - */ - public Supbook getExternalSupbook(String externalWorkbook, boolean bCreate) { - Supbook sb= null; - if (externalWorkbook==null) return null; - for (int i= 0; i < this.supBooks.size(); i++) { - if (((Supbook)this.supBooks.get(i)).isExternalRecord()) - if (externalWorkbook.equalsIgnoreCase(((Supbook)this.supBooks.get(i)).getExternalWorkBook())) - sb= (Supbook)this.supBooks.get(i); - } - if (sb==null && bCreate) { // create - sb= (Supbook) Supbook.getExternalPrototype(externalWorkbook); - int loc= ((Supbook) supBooks.get(supBooks.size()-1)).getRecordIndex(); // must have at least one global supbook present - streamer.addRecordAt(sb, loc+1); // external supbooks appear to be before "normal" supbooks [BugTracker 1434] - this.supBooks.add(sb); // 20080714 KSC: add at beginning -- correct in all cases? -// this.addRecord(sb,false); // "" no need - } - return sb; - } - /** - * return the index into the Supbook records for this supbook - * @param sb - * @return int - */ - public int getSupbookIndex(Supbook sb) { - for (int i= 0; i < this.supBooks.size(); i++) { - if (((Supbook)this.supBooks.get(i))==sb) - return i; - } - return -1; - } - - - + /** Return some useful statistics about the WorkBook + * + * @return + * + */ + public String getStats(boolean usehtml) { + String rex = "\r\n"; + if (usehtml) + rex = "
"; + + String ret = "-------------------------------------------" + rex; + ret += "OpenXLS Version: " + WorkBookHandle.getVersion() + rex; + ret += "Excel Version: " + getXLSVersionString() + rex; + ret += "-------------------------------------------------\r\n"; + ret += "Statistics for: " + this.toString() + rex; + ret += "Number of Worksheets: " + this.getNumWorkSheets() + rex; + ret += "Number of Cells: " + this.getNumCells() + rex; + ret += "Number of Formulas: " + this.getNumFormulas() + rex; + ret += "Number of Charts: " + this.getChartVect().size() + rex; + ret += "Number of Fonts: " + this.getNumFonts() + rex; + ret += "Number of Formats: " + this.getNumFormats() + rex; + ret += "Number of Xfs: " + this.getNumXfs() + rex; + // ret += "StringTable: " + this.stringTable.toString() + + // rex; + ret += "-------------------------------------------------\r\n"; + return ret; + } - public AbstractList getChartVect() { - return charts; - } + public String getXLSVersionString() { + return lastBOF.getXLSVersionString(); + } - public Sxview getPivotTableView(String nm) { - return (Sxview) ptViews.get(nm); - } + private void addMergedcells(Mergedcells c) { + this.mergecelllookup.add(c); + } - - protected void addPivotTable(Sxview sx) { - this.ptViews.put(sx.getTableName(), sx); // Pivot Table View ==Top-level record for a Pivot Table - } - /** - * return all pivot table views (==Sxview records) - *
SxView is the top-level record of a Pivot Table - as distinct from the PivotCache (stored data source in a LEOFile Storage) - and PivotTable Stream (SxStream top-level record) - * @return - */ - public Sxview[] getAllPivotTableViews() { - Sxview[] sv = new Sxview[ptViews.size()]; - Enumeration x = ptViews.elements(); - int t = 0; - while (x.hasMoreElements()) { - sv[t++] = (Sxview) x.nextElement(); - } - return sv; - } - - public int getNPivotTableViews() { - return ptViews.size(); - } - - /** return the Externsheet for this book - * - * @param create a new Externsheet if it does not exist - * @return the Externsheet - */ - public Externsheet getExternSheet(boolean create) { - if( (myexternsheet == null)&&create){ - addExternsheet(); - } - return myexternsheet; - } - - /** get the Externsheet - */ - public Externsheet getExternSheet() { - if(myexternsheet == null) - addExternsheet(); - return myexternsheet; - } - - - /** default constructor -- do init - */ - public WorkBook() { - - Object cm = System.getProperties().get(WorkBook.CALC_MODE_PROP); - if(cm!=null){ - try{ - this.CalcMode = Integer.parseInt(cm.toString()); - }catch(Exception e){ - Logger.logWarn("Invalid Calc Mode Setting in System properties:" + cm); - } - } - if(System.getProperties().get("io.starter.OpenXLS.sharedupes")!=null){ - this.sharedupes = System.getProperties().get("io.starter.OpenXLS.sharedupes").equals("true"); - if (this.sharedupes) { - this.setDupeStringMode(WorkBook.SHAREDUPES); - } - } - this.initBuiltinFormats(); - // re-init color table: initial state of color table if Pallete record exists, changes may occur - colorTable= new java.awt.Color[FormatHandle.COLORTABLE.length]; - for (int i= 0; i < FormatHandle.COLORTABLE.length; i++) - colorTable[i]= FormatHandle.COLORTABLE[i]; - } - - /** Gets the format ID for a given number format pattern. - * This lookup is completely case-insensitive. For most patterns this - * correctly reflects the case-insensitivity of the tokens. Custom patterns - * containing string literals could be matched incorrectly. - * - * @param pattern the number format pattern to look up - * @return the format ID of the given pattern or -1 if it's not recognized - */ - public short getFormatId (String pattern) { - Short res = (Short) formatlookup.get( pattern.toUpperCase() ); - if (res != null) return res.shortValue(); - return -1; - } - - /** Initializes the format lookup to contain the built-in formats. */ - private void initBuiltinFormats() { - String[][] formats = FormatConstantsImpl.getBuiltinFormats(); - - for (int i = 0; i < formats.length; i++) - formatlookup.put( - formats[i][0].toUpperCase(), - Short.valueOf( formats[i][1], 16 ) ); - } - - /** - * Init names at Post-load - */ - public void initializeNames() { - for (int i= 0; i < names.size(); i++) { - Name n= (Name)names.get(i); - n.parseExpression(); // evaluate expression at postload, after sheet recs are loaded - } - } - - /** add a Name object to the collection of names - */ - public int addName(Name n) { - if(n.getItab()!=0) { - // its a sheet level name - try { - Boundsheet b = this.getWorkSheetByNumber(n.getItab()-1);// one based pointer - b.addLocalName(n); - n.setSheet(b); - } catch (WorkSheetNotFoundException e) { - } - }else { - String sName= n.getNameA(); // returns upper case name - Object existo = bookNameRecs.get(sName); - if(existo!=null) { // handle duplicate named ranges - String bnam = n.toString(); - if(bnam.indexOf("Built-in:")!=0) { - try { - if (((Name) existo).getLocation()!=null) // use original - as good a guess as any - return -1; // an invalid sheet - }catch (Exception e) { - } - // if original does not have a location set, use this one instead - this.names.remove(this.names.indexOf(existo)); - this.bookNameRecs.remove(sName); - } - } - this.bookNameRecs.put(sName, n); - } - this.names.add(n); - if((myexternsheet!=null)&&(n.getExternsheet()==null)) { - try{ - n.setExternsheet(myexternsheet); // update sheet reference - }catch(WorkSheetNotFoundException e) { - Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " + e.toString()); - } - } - return names.size() - 1; - } - - public void addNameUpdateSheetRefs(Name n, String origWorkBookName) { - if(bookNameRecs.get(n.getNameA())==null) { - Name newName= new Name(this, n.getName()); - try { - newName.setLocation(n.getLocation()); - } catch (Exception e) { - } - if((myexternsheet!=null)&&(newName.getExternsheet()==null)) { - try{ - newName.setExternsheet(myexternsheet); // update sheet reference - }catch(WorkSheetNotFoundException e) { - Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " + e.toString()); - } - } - newName.updateSheetRefs(origWorkBookName); - } - } - /** - * Store an external name - * @param n = String describing the name - * @return int location of the name - */ - public int addExternalName(String n) { - this.externalnames.add(n); - return externalnames.size(); // one-based index - } - /** - * Get a string array of external names - * @return externalNames - */ - public String[] getExternalNames() { - String[] n = new String[externalnames.size()]; - externalnames.toArray(n); - return n; - } - - /** - * Get the external name at the specified index. - * @param t index of the name - * @return name at the index, empty string if it doesn't exist. - * - * Why are we calling getExternalName one based, then removing for ordinal, internal processes should always - * be 0,1,2,3... -NR 1/06 - */ - public String getExternalName(int t) { - if (t > 0) - return (String) this.externalnames.get(t-1); // one-based index - return ""; - } - - /** - * For workbooks that do not contain an externsheet - * this creates the externsheet record with one 0000 record - * and the related Supbook rec - - * - */ - public void addDefaultExternsheet() { - Supbook sbb = (Supbook)Supbook.getPrototype(this.getNumWorkSheets()); - int l = this.stringTable.getRecordIndex(); - streamer.addRecordAt(sbb, l++); - supBooks.add(sbb); - Externsheet ex = (Externsheet)Externsheet.getPrototype(0x0000, 0x0000, this); - streamer.addRecordAt(ex, l); - this.addRecord(ex,false); - } - - /** apparently this method adds an External name rec and returns the ilbl - * - * Correct structure is - * Supbook - * Externname - * Supbook - * Externsheet - - * - * @see PtgNameX - * @param s - * @return - */ - public int getExtenalNameNumber(String s) { - int i= externalnames.indexOf(s); - if (i > -1) // got it - return i+1; - if (this.getExternSheet() == null)this.addDefaultExternsheet(); - - // not found; add a new EXTERNNAME record to list of add-ins - int n= addExternalName(s); - try{ - int loc; - if(myADDINSUPBOOK == null) { - Supbook sb = (Supbook)Supbook.getAddInPrototype(); - loc= this.getExternSheet().getRecordIndex(); - streamer.addRecordAt(sb, loc++); - this.addRecord(sb,false); - myADDINSUPBOOK = sb; - supBooks.add(sb); - int externref = this.getExternSheet().getVirtualReference(); - // Add EXTERNNAME record after ADD-IN SUPBOOK record and after existing EXTERNNAME records - Externname exn = (Externname)Externname.getPrototype(s); - streamer.addRecordAt(exn, loc++); - this.addRecord(exn,false); - } else { - loc= streamer.getRecordIndex(myADDINSUPBOOK); - // Add EXTERNNAME record after ADD-IN SUPBOOK record and after existing EXTERNNAME records - Externname exn = (Externname)Externname.getPrototype(s); - streamer.addRecordAt(exn, loc + externalnames.size()); - this.addRecord(exn,false); - } - - }catch(Exception e){Logger.logWarn("Error adding externname: " + e);} - return n; - } - - - /** Get a collection of all names in the workbook - */ - public Name[] getNames() { - Name[] n = new Name[names.size()]; - names.toArray(n); - return n; - } - - - /** Get a collection of all names in the workbook - */ - public Name[] getWorkbookScopedNames() { - ArrayList a = new ArrayList(this.bookNameRecs.values()); - Name[] n = new Name[a.size()]; - a.toArray(n); - return n; - } - - + private void addHlink(Hlink r) { + this.hlinklookup.add(r); + } - /** returns the List of Formulas in the book + /** take care of any lazy updating before output + * + */ + public void prestream() { + if (this.getMSODrawingGroup() != null) + this.getMSODrawingGroup().prestream(); + } - * - * @return - */ - public List getFormulaList() { - return formulas; - } - - /** returns the array of Formulas in the book - - * - * @return - */ - public Formula[] getFormulas() { - Formula[] n = new Formula[formulas.size()]; - formulas.toArray(n); - return n; - } - - /** remove a formula from the book - * - * @param fmla - */ - public void removeFormula(Formula fmla){ - this.formulashash.remove(fmla.getCellAddressWithSheet()); - formulas.remove(fmla); - fmla.destroy(); - } - - /** - * Returns the recalculation mode for the Workbook: - *
0= Manual - *
1= Automatic - *
2= Automatic except for multiple table operations - * @return int - */ - public int getRecalculationMode() { - return this.calcmoderec.getRecalcuationMode(); - } - - /** - * Sets the recalculation mode for the Workbook: - *
0= Manual - *
1= Automatic - *
2= Automatic except for multiple table operations - */ - public void setRecalcuationMode(int mode) { - this.calcmoderec.setRecalculationMode(mode); - } - /** returns a Named range by number - - * - * @param t - * @return - */ - public Name getName(int t) { - return (Name) this.names.get(t-1); - } - - /** rename the NamedRange in the lookup map - - * - * @param t - * @return - */ - public void setNewName(String oldname, String newname) { - if(oldname.equals(newname))return; - oldname= oldname.toUpperCase(); // case-insensitive - newname= newname.toUpperCase(); // "" - Object old = bookNameRecs.get(oldname); - if(old == null)return; // new name? - bookNameRecs.remove(oldname); - bookNameRecs.put(newname,old); - - } + public Chart[] getCharts() { + Chart[] chts = new Chart[this.charts.size()]; + return (Chart[]) charts.toArray(chts); + } - - /** - * Re-assocates ptgrefs that are pointing to a name that has been deleted then - * is recreated - * @param name - */ - public void associateDereferencedNames(Name name) { - Iterator i = orphanedPtgNames.iterator(); - String theName = name.getName(); - while (i.hasNext()) { - IlblListener x = (IlblListener)i.next(); - if(x.getStoredName().equalsIgnoreCase(theName)) { - x.setIlbl((short)this.getNameNumber(theName)); - x.addListener(); - } - } - } - - /** returns a named range by name string - * - * This method will first attempt to look in the book names, then the sheet names, - * obviously different scoped names can have the same identifying name, so this could return - * one of multiple names if this is the case - * - * @param t - * @return - */ - public Name getName(String nameRef) { - nameRef= nameRef.toUpperCase(); // case-insensitive - Object o = this.bookNameRecs.get(nameRef); - if(o==null) { - Boundsheet[] shts = this.getWorkSheets(); - for (int i=0;i=1){ - namepos = nmx[nmx.length-1].getRecordIndex(); - }else{ - namepos =this.getExternSheet().getRecordIndex(); - } - namepos++; - this.getStreamer().addRecordAt(myName, namepos); - return this.getNameNumber(nameStr); - } - - /** - * Get's the index for this particular front. - * - * NOTE: this doesn't actually get a "matching" font, it has to be the exact font. - * 20070826 KSC: changed to match font characterstics, not just return exact matching font - */ - public int getFontIdx(Font f){ - // 20070819 KSC: Try this to see if better! Matches 6 key attributes (size, name, color, etc.) - for (int i= fonts.size()-1; i >=0; i--) { // start from the back so don't initially match defaults... - if (f.matches((Font) fonts.get(i))) - return ((i > 3)?i+1:i); - } - // return fonts.indexOf(f); - return -1; - } - - /** - * Get's the index for this font, based on matching through - * xml strings. If the font doesn't exist in the book it returns -1; - * @return - * KSC: is this method necessary now with above getFontIdx changes? - */ - public int getMatchingFontIndex(Font f) { - Map fontmap = this.getFontRecsAsXML(); - Object o = fontmap.get("<" + f.getXML() + "/>"); - if (o!=null) { - Integer I = (Integer)o; - return I.intValue(); - }else { - return -1; - } - } - - /** - * InsertFont inserts a Font record into the workbook level stream, - * For some reason, the addFont only puts it into an array that is never accessed - * on output. This may have a reason, so I am not overwriting it currently, but - * let's check it out? - * - * */ - public int insertFont(Font x) { - int insertIdx = this.getFont(this.getNumFonts()).getRecordIndex(); - // perform default add rec actions - this.getStreamer().addRecordAt(x, insertIdx+1); - x.setIdx(-1); // flag to add into font array - this.addRecord(x,false); // also adds to font array so no need for additional addFont below - return fonts.indexOf(x); - } - - /** add a Font object to the collection of Fonts - */ - public int addFont(Font f) { - fonts.add(f); - if (fonts.size() > 4) // fake the evil 4! - return fonts.size(); - return fonts.size()-1; - } - - public int getNumFonts() { - return fonts.size(); - } - - /** - * Get the font at the specified index. Note that the number 4 does not exist, so index correctly based of that. - * - * So, if you call getFont(5), you are really doing getFont(4) from the internal array - * @param t - * @return - */ - public Font getFont(int t) { - if(t>=4){t--;} - if(this.fonts.size()>=t){ - if(t>=fonts.size()) { - Logger.logWarn("font " + t + " not found. Workbook contains only: " + fonts.size() + " defined fonts."); - return (Font) fonts.get(0); - }else { - return (Font) fonts.get(t); - } - } - return (Font)this.fonts.get(0); - } - - /** Inserts a newly created Format record into the workbook. - * This method handles assigning the format ID and adding the record to the - * workbook. If the record is already part of the workbook use - * {@link #addFormat} instead. - */ - public int insertFormat (Format format) { - Format last; - try { - last = (Format) formats.get( formats.lastKey() ); - } catch (NoSuchElementException e) { - /* There are no other Format records in the workbook. - * This shouldn't happen because most (all?) Excel files contain - * Format records for the locale-specific (and thus not implied) - * built-in formats. If it does happen, either we need to re-assess - * the above assumption or this method was called before the Format - * records were parsed. Either way we need to know about it. - */ - throw new AssertionError( "WorkBook.insertFormat called but no " + - "Format records exist. This should not happen. Please " + - "report this error to support@extentech.com." ); - } - - // Add it to the streamer and workbook - getStreamer().addRecordAt( format, last.getRecordIndex() + 1 ); - addRecord( format, false ); - - // Give it a format ID - if (format.getIfmt() == -1) - format.setIfmt( (short) Math.max( last.getIfmt() + 1, 164 ) ); - - // Add it to the format lookups - addFormat( format ); - - return format.getIfmt(); - } - - - public Formula getFormula(String cellAddress) throws FormulaNotFoundException - { - Formula formula = (Formula) formulashash.get(cellAddress); - if ( formula == null ) - throw new FormulaNotFoundException( - "no formula found at " + cellAddress); - - return (formula); - } - - /** Adds an existing format record to the list of known formats. - * This method does not add the record to the workbook! If the format is - * not already in the workbook use {@link #insertFormat} instead. - * @param format the Format record to add - */ - public int addFormat (Format format) { - Short ifmt = Short.valueOf( format.getIfmt() ); - - // Add it to the format record lookup - formats.put( ifmt, format ); - - // Add it to the format string lookup - formatlookup.put( format.getFormat().toUpperCase(), ifmt ); - - return format.getIfmt(); - } - - /** Gets the number of custom number formats registered on this book. - */ - public int getNumFormats() { - return formats.size(); - } - - /** Gets a custom number format by its format ID. - */ - public Format getFormat (int id) { - return (Format) formats.get( Short.valueOf( (short) id ) ); - } - - public TabID getTabID() { - return this.tabs; - } - - /** associate default row/col size recs - */ - void setDefaultRowHeightRec(DefaultRowHeight dr) { - this.drh = dr; - } - - - /** set Default row height in twips (1/20 of a point) - */ - // should be a double as Excel units are 1/20 of what is stored in defaultrowheight - // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 (approx) - // should expect users to use Excel units and target method do the 20* conversion - public void setDefaultRowHeight(int t) { - drh.setDefaultRowHeight(t); - } - - /** set Default col width for all worksheets in the workbook, - * - * Default column width can also be set on individual worksheets - */ - public void setDefaultColWidth(int t) { - Boundsheet[] b = this.getWorkSheets(); - for (int i=0;i 0) - streamer.addRecord(rec); - else - streamer.records.add(rec); - } - - switch (opc) { - case AUTOFILTER: - bs.getAutoFilters().add(rec); - break; - - case CONDFMT : - bs.getConditionalFormats().add(rec); - ((Condfmt)rec).initializeReferences(); - break; - - case CF: - Condfmt cfmt= (Condfmt) bs.getConditionalFormats().get(bs.getConditionalFormats().size()-1); - cfmt.addRule((Cf)rec); - break; - - case MERGEDCELLS : - bs.addMergedCellsRec((Mergedcells)rec); - this.addMergedcells((Mergedcells)rec); - break; - - // give protection records to the relevant ProtectionManager - case PASSWORD: - case PROTECT: - case PROT4REV: - case OBJPROTECT: - case SCENPROTECT: - case FEATHEADR: - ProtectionManager manager; - if (bs != null) manager = bs.getProtectionManager(); - else manager = this.getProtectionManager(); - manager.addRecord( rec ); - break; - - case DVAL: - if(bs!=null)bs.setDvalRec((Dval) rec); - break; - - case DV: - if(bs!=null) { - if (bs.getDvalRec()!=null) { - bs.getDvalRec().addDvRec((Dv) rec); - } - } - break; - - case INDEX : - Index id = (Index) rec; - id.setIndexNum(indexnum++); - indexes.add(indexes.size(), id); - this.setLastINDEX(id); - if (bs == null) { - Logger.logWarn( - "ERROR: WorkBook.addRecord( Index ) error: BAD LBPLYPOS. The wrong LB:" + lbplypos); - try { - bs = this.getWorkSheetByNumber(indexnum - 1); - Logger.logInfo(" The RIGHT LB:" + bs.getLbPlyPos()); - } catch (WorkSheetNotFoundException e) { - Logger.logInfo("problem getting WorkSheetByNumber: " + e); - } - - } - bs.setSheetIDX(id); - break; - - case ROW : - Row rw = (Row) rec; - if (bs != null) - bs.addRowRec(rw); - - break; - - case FORMULA : - this.addFormula((Formula)rec); - lastFormula = (Formula) rec; - break; - - case ARRAY: - Array arr= (Array) rec; - if (bs != null) - bs.addArrayFormula(arr); - arr.setParentRec(lastFormula); // [BugTracker 1869] link array formula to it's parent formula rec - break; - -/* case SHRFMLA : done in shrfmla.init - Shrfmla form = (Shrfmla) rec; - try{ // throws exceptipon during pullparse - form.setHostCell( lastFormula ); - }catch(Exception e){;} - break;*/ - - case DATE1904: - if (((NineteenOhFour) rec).is1904) { - this.dateFormat = DateConverter.DateFormat.LEGACY_1904; - } - break; - - /*case PALETTE : // palette now correctly read into COLORTABLE - this.pal = (Palette) rec; - break;*/ - - case HLINK : - Hlink hl = (Hlink)rec; - this.addHlink(hl); - break; - - case DSF: - Dsf dsf = (Dsf)rec; - if(dsf.fDSF == 1){ - Logger.logErr("DOUBLE STREAM FILE DETECTED!"); - Logger.logErr(" OpenXLS is compatible with Excel 97 and above only."); - throw new WorkBookException("ERROR: DOUBLE STREAM FILE DETECTED! OpenXLS is compatible with Excel 97 + only.", WorkBookException.DOUBLE_STREAM_FILE); - } - break; - - case GUTS: - if (bs != null){ - bs.setGuts((Guts)rec); - } - break; - - case DBCELL : - break; - - case BOF : - if(DEBUGLEVEL > 5)Logger.logInfo("BOF:" + bofct + " - " + rec); - if (eofct == bofct) { - if (bs != null) { - bs.setBOF((Bof) rec); - } - } - bofct++; - break; - - case EXTERNSHEET : - myexternsheet = (Externsheet) rec; - break; - - case DEFCOLWIDTH: - if(bs!=null){ - bs.setDefColWidth((DefColWidth) rec); - } - break; - - case EOF : - this.lasteof = (Eof)rec; - eofct++; - if (eofct == bofct) { - if (bs != null) { - bs.setEOF((Eof) rec); - } - eofct--; - bofct--; - } - break; - - case SELECTION : // only used for Recvec index - bs.setLastselection((Selection)rec); - break; - - case COUNTRY: - // Added to save position of 1st bound sheet, which is 1 record - // before COUNTRY RECORD (= 2 before 1st SUPBOOK record - true in all cases?) - countryRec= (XLSRecord) rec; - // USA=1, Canada=1, Japan=81, China=86, Thailand= 66, Korea= 82, India=91 ... - this.defaultLanguage= ((Country) rec).getDefaultLanguage(); - break; - - case SUPBOOK: // KSC: must store ordinal positions of SupBooks, for adding Externsheets - supBooks.add((Supbook) rec); - if (myADDINSUPBOOK==null) { // see if this is the ADD-IN SUPBOOK rec - Supbook sb= (Supbook) rec; - if (sb.isAddInRecord()) - myADDINSUPBOOK= sb; - } - break; - - case BOUNDSHEET : - Boundsheet sh = (Boundsheet) rec; - - /* Here we need to set the selected variable, - but not mess with selected tabs - when all of the sheets aren't in the book yet. - -jm - */ - int ctab= 1; // default- select 1st sheet if no Windows1 record - if (win1!=null) {// Windows1 record is optional 20101004 TestCorruption.TestNPEOnOpen - ctab = win1.getCurrentTab(); - } - int shts = boundsheets.size(); - if (ctab == shts) - sh.selected = true; - - this.addWorkSheet(new Long(sh.getLbPlyPos()), sh); - break; - - case MULRK : - Mulrk mul = (Mulrk) rec; - Iterator xit = mul.getRecs().iterator(); - while(xit.hasNext()) { - this.addRecord(((Rk)xit.next()),false); - } - break; - - case SST : - this.setSharedStringTable((Sst) rec); - break; - - case EXTSST : - ((Extsst) rec).setSst(this.getSharedStringTable()); - break; - - - case SXSTREAMID: - this.ptstream.add(rec); // Pivot Stream - break; - - case SXVS: - case DCONREF: - case DCONNAME: - case DCONBIN: - try { - SxStreamID sid= (SxStreamID) ptstream.get(ptstream.size()-1); - sid.addSubrecord(rec); - }catch (Exception e) {} - break; - - case SXVIEW : - addPivotTable(((Sxview) rec)); // Pivot Table View ==Top-level record for a Pivot Table - break; - - // all* possible records associated with SxView (=PivotTable View) (*hopefully) - case SXVD: - //case SXVI: // subrecords of SxVD - //case SXVDEX: - case SXIVD: - case SXPI: - case SXDI: - case SXLI: - case SXEX: - case SXVIEWEX9: - case QSISXTAG: - try { - Sxview sx= (Sxview) this.ptViews.values().toArray()[this.ptViews.size()-1]; - sx.addSubrecord(rec); - } catch (Exception e) { ; } - break; - - case TABID : - this.tabs = (TabID) rec; - break; - - case NAME: - addName((Name)rec); - break; - - case CALCMODE: - this.calcmoderec= (CalcMode) rec; - break; - - case WINDOW1 : - this.win1 = (Window1) rec; - break; - - case WINDOW2 : - if (bs != null) - bs.setWindow2((Window2) rec); - break; - - case SCL : // scl is for zoom - if (bs != null) - bs.setScl((Scl) rec); - break; - - case PANE: - if (bs!=null) - bs.setPane((Pane)rec); - break; - case EXCEL2K: - xl2k = rec; - break; - - case PHONETIC : - // TODO: this isn't necessary anymore! look at and remove - if(this.currdrw!=null){ - this.currdrw.setMystery((Phonetic)rec); - } - break; - - - case MSODRAWINGGROUP: - if (msodg==null) - msodg= (MSODrawingGroup) rec; - msodgMerge.add(rec); - - break; - - case MSODRAWING : - rec.setSheet(bs); - if (msodg!=null) - msodg.addMsodrawingrec((MSODrawing)rec); - else ; - // do what???io.starter.OpenXLS.util.Logger.log("PROBLEM with MSODG!"); - break; - - case COLINFO : - bs.addColinfo((Colinfo) rec); - break; - - case USERSVIEWBEGIN: - this.usersview = (Usersviewbegin)rec; - break; - - case WSBOOL: - if (bs!=null){ - bs.setWsBool((WsBool)rec); - } - break; - - // Handle continue records which are actually masked Mso's - case CONTINUE: - if (((Continue)rec).maskedMso!=null) { - ((Continue)rec).maskedMso.setSheet(bs); - if (msodg!=null) - msodg.addMsodrawingrec(((Continue)rec).maskedMso); - } - break; - - case XF: - try{ - this.addXf((Xf) rec); - }catch(Exception e){ - ;// throws exceptions during PullParse - } - break; - - default : - // DO NOTHING - - } - - // finish up - rec.setIndex(getLastINDEX()); - rec.setXFRecord(); - return rec; - } + public Supbook[] getSupBooks() { + Supbook[] sbs = new Supbook[this.supBooks.size()]; + return (Supbook[]) this.supBooks.toArray(sbs); + } - /** - * Dec 15, 2010 - * @param rec + /** + * returns the list of OOXML objects which are external or auxillary to the main workbook + * e.g. theme, doc properties * @return */ - public Boundsheet getSheetFromRec(BiffRec rec, Long lbplypos) { - Boundsheet bs = null; + public List getOOXMLObjects() { + return ooxmlObjects; + } - if(rec.getSheet()!=null){ - bs = rec.getSheet(); - }else if(lbplypos!=null){ - bs = this.getWorkSheet(lbplypos); - }else - bs = lastbound; - return bs; + /** + * adds the object-specific signature of the external or auxillary OOXML object + * Object should be of String[] form, + * key, path, local path + filename [, rid, [extra info], [embedded information]] + * e.g. theme, doc properties + * @param o + */ + public void addOOXMLObject(Object o) { + if (!((String[]) o)[0].equals("externalLink")) + ooxmlObjects.add(o); + else + ooxmlObjects.add(0, o); // ensure ExternalLinks are 1st because they + // are linked via rId in workbook.xml } + /** + * return the OOXML theme for this workbook, if any + * @return + */ + public Theme getTheme() { + return theme; + } - /** get a handle to the ContinueHandler - */ - public ContinueHandler getContinueHandler() { - return this.contHandler; - } - - /** get a handle to the Usersviewbegin for the workbook - */ - public Usersviewbegin getUsersviewbegin(){ - if (usersview == null){ - usersview = new Usersviewbegin(); - streamer.addRecord(usersview); - this.addRecord(usersview,false); - } - return usersview; - } - - /** set the Debug level - */ - public void setDebugLevel(int i) { - this.DEBUGLEVEL = i; - } - - /** get a handle to the Reader for this - WorkBook. - */ - public void setFactory(WorkBookFactory r) { - this.factory = r; - } - - /** Get the number of worksheets in this WorkBook - */ - public int getNumWorkSheets() { - return this.boundsheets.size(); - } - - /** get the number of formulas in this WorkBook - - * - * @return - */ - public int getNumFormulas() { - return formulas.size(); - } - - /** get the number of Cells in this WorkBook - */ - public int getNumCells() { - int cellnum = 0; - Enumeration e = workSheets.elements(); - while (e.hasMoreElements()) { - Boundsheet b = (Boundsheet) e.nextElement(); - cellnum += b.getNumCells(); - } - return cellnum; - } - - /** get all of the Cells in this WorkBook - */ - public BiffRec[] getCells() { - List cellz = new FastAddVector(); - for (int i=0;i 10) - Logger.logInfo( - "Workbook Adding Sheet: " - + sheet.getSheetName() - + ":" - + String.valueOf(lbplypos)); - workSheets.put(lbplypos, sheet); - boundsheets.add(sheet); - } - } - - /** - * - * @param n - * @return - */ - public boolean removeName(Name n) { - if(this.names.contains(n)) { - this.names.remove(n); - if (n.getItab()!=0) { - try { - this.getWorkSheetByNumber(n.getItab()-1).removeLocalName(n); - } catch (WorkSheetNotFoundException e) { - } - }else { - this.bookNameRecs.remove(n.toString().toUpperCase()); // case-insensitive - } - } - ArrayList al = n.getIlblListeners(); - this.orphanedPtgNames.addAll(al); - - this.updateNameIlbls(); - return this.getStreamer().removeRecord(n); - } - - /** - * Add a sheet-scoped name record to the boundsheet - * - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this book, adding them here will not add them to the workbook; - * - * @param bookNameRecs - */ - public void addLocalName(Name name) { - bookNameRecs.put(name.getNameA(), name); - } - - /** - * Remove a sheet-scoped name record from the boundsheet. - * - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this book, removing them here will not remove them completely from the workbook. - * - * In order to do that you will need to call book.removeName - * - * @param bookNameRecs - */ - public void removeLocalName(Name name) { - bookNameRecs.remove(name.getNameA()); - } - - /** - * - * After any changes in the name records - * this method needs to be called in order to - * update ilbl records - - * - */ - public void updateNameIlbls() { - for (int i=0;i 0){ - lastbound = (Boundsheet)(boundsheets.get(boundsheets.size()-1)); - lastBOF = lastbound.getMyBof(); - } - - // decrement the tab ids... - this.tabs.removeRecord(); - this.updateScopedNamedRanges(); - - // update wb chart cache - remove charts referenced by deleted sheet - for (int i= getChartVect().size()-1; i >= 0; i--) { - if (((Chart)this.getChartVect().get(i)).getSheet()==sheet) - this.getChartVect().remove(i); - } - - if(this.getNumWorkSheets()==0)return; // empty book - try{ // set the next sheet selected... - while(sheetNum<=this.getNumWorkSheets()){ - Boundsheet s2 = this.getWorkSheetByNumber(sheetNum++); - s2.setSelected(true); - if(!s2.getHidden()) - break; - - } - }catch(WorkSheetNotFoundException e){ - try{ - Boundsheet s2 = this.getWorkSheetByNumber(0); - s2.setSelected(true); - }catch(Exception ee){ - throw new WorkBookException("Invalid WorkBook. WorkBook must contain at least one Sheet.", WorkBookException.RUNTIME_ERROR); - }; - } - } - - /** - * Updates all the name records in the workbook that are bound to a - * worksheet scope (as opposed to a workbook scope). Name records use - * their own non-externsheet based sheet references, so need to be modified - * whenever a sheet delete (or non-last sheet insert) operation occurs - */ - private void updateScopedNamedRanges() { - for (int i=0;i 0; i--) { - Xf xf= (Xf) xfrecs.get(i); - this.streamer.removeRecord(xf); - xfrecs.remove(i); - - } - } - - /** - * TODO: Does this function as desired? See comment for insertXf() above... - * tracks existing xf recs, used when testing whether xfrec exists or not ... - * -NR 1/06 - * ya should - called now from addRecord every time an xf record is added - * NOTE: this is the only place addXf is called - * - * @param xf - * @return - */ - int addXf(Xf xf) { - xfrecs.add(xf); - xf.tableidx= xfrecs.size()-1; // flag that it's been added to records - this.updateFormatCache(xf); // links tostring of xf to xf rec for updating/reuse purposes - return xf.tableidx; - } - /** - * formatCache: - * links tostring of xf to xf rec for updating/reuse purposes - * - * @see FormatHandle.updateXf - * @see WorkBook.addXf - * @param xf - */ - public void updateFormatCache(Xf xf) { - if (xf.tableidx!=-1) { // if this xf has been already added to the workbook - if (formatCache.containsValue(xf)) { // xf signature has changed/it's been updated - Iterator ii= formatCache.keySet().iterator(); // remove and update below - while (ii.hasNext()) { - String key= (String) ii.next(); - Xf x= (Xf) formatCache.get(key); - if (x.equals(xf)) { - formatCache.remove(key); - break; - } - } + + /** + * return the index into the Supbook records for this supbook + * @param sb + * @return int + */ + public int getSupbookIndex(Supbook sb) { + for (int i = 0; i < this.supBooks.size(); i++) { + if (((Supbook) this.supBooks.get(i)) == sb) + return i; + } + return -1; + } + + public AbstractList getChartVect() { + return charts; + } + + public Sxview getPivotTableView(String nm) { + return (Sxview) ptViews.get(nm); + } + + protected void addPivotTable(Sxview sx) { + this.ptViews.put(sx.getTableName(), sx); // Pivot Table View ==Top-level + // record for a Pivot Table + } + + /** + * return all pivot table views (==Sxview records) + *
SxView is the top-level record of a Pivot Table + as distinct from the PivotCache (stored data source in a LEOFile Storage) + and PivotTable Stream (SxStream top-level record) + * @return + */ + public Sxview[] getAllPivotTableViews() { + Sxview[] sv = new Sxview[ptViews.size()]; + Enumeration x = ptViews.elements(); + int t = 0; + while (x.hasMoreElements()) { + sv[t++] = (Sxview) x.nextElement(); + } + return sv; + } + + public int getNPivotTableViews() { + return ptViews.size(); + } + + /** return the Externsheet for this book + * + * @param create a new Externsheet if it does not exist + * @return the Externsheet + */ + public Externsheet getExternSheet(boolean create) { + if ((myexternsheet == null) && create) { + addExternsheet(); + } + return myexternsheet; + } + + /** get the Externsheet + */ + public Externsheet getExternSheet() { + if (myexternsheet == null) + addExternsheet(); + return myexternsheet; + } + + /** default constructor -- do init + */ + public WorkBook() { + + Object cm = System.getProperties().get(WorkBook.CALC_MODE_PROP); + if (cm != null) { + try { + this.CalcMode = Integer.parseInt(cm.toString()); + } catch (Exception e) { + Logger.logWarn("Invalid Calc Mode Setting in System properties:" + + cm); } - String formatStr = xf.toString(); - - if (!formatCache.containsKey(formatStr)) - formatCache.put(formatStr,xf); - } - } + } + if (System.getProperties() + .get("io.starter.OpenXLS.sharedupes") != null) { + this.sharedupes = System.getProperties() + .get("io.starter.OpenXLS.sharedupes").equals("true"); + if (this.sharedupes) { + this.setDupeStringMode(WorkBook.SHAREDUPES); + } + } + this.initBuiltinFormats(); + // re-init color table: initial state of color table if + // Pallete record exists, changes may occur + colorTable = new java.awt.Color[FormatHandle.COLORTABLE.length]; + for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) + colorTable[i] = FormatHandle.COLORTABLE[i]; + } - - /** - * retrieve the format cache - links string vers. of xf to xf rec - * used for resusing xf's - * @see FormatHandle.updateXf - * @return - */ - public HashMap getFormatCache() { - return formatCache; - } - - /** Get a substream by name.*/ - public ByteStreamer getStreamer() { - return this.streamer; - } - - public void setSubStream(ByteStreamer s) { - this.streamer = s; - } - - /** Get the typename for this object.*/ - String getTypeName() { - return "WorkBook"; - } - - /** Write the contents of the WorkBook bytes to an OutputStream - - * - * @param _out - */ - public int stream(OutputStream _out) { - return streamer.streamOut(_out); - } - + /** Gets the format ID for a given number format pattern. + * This lookup is completely case-insensitive. For most patterns this + * correctly reflects the case-insensitivity of the tokens. Custom patterns + * containing string literals could be matched incorrectly. + * + * @param pattern the number format pattern to look up + * @return the format ID of the given pattern or -1 if it's not recognized + */ + public short getFormatId(String pattern) { + Short res = (Short) formatlookup.get(pattern.toUpperCase()); + if (res != null) + return res.shortValue(); + return -1; + } + + /** Initializes the format lookup to contain the built-in formats. */ + private void initBuiltinFormats() { + String[][] formats = FormatConstantsImpl.getBuiltinFormats(); + + for (int i = 0; i < formats.length; i++) + formatlookup.put(formats[i][0].toUpperCase(), Short + .valueOf(formats[i][1], 16)); + } + + /** + * Init names at Post-load + */ + public void initializeNames() { + for (int i = 0; i < names.size(); i++) { + Name n = (Name) names.get(i); + n.parseExpression(); // evaluate expression at postload, after sheet + // recs are loaded + } + } + + /** add a Name object to the collection of names + */ + public int addName(Name n) { + if (n.getItab() != 0) { + // its a sheet level name + try { + Boundsheet b = this.getWorkSheetByNumber(n.getItab() - 1);// one + // based + // pointer + b.addLocalName(n); + n.setSheet(b); + } catch (WorkSheetNotFoundException e) {} + } else { + String sName = n.getNameA(); // returns upper case name + Object existo = bookNameRecs.get(sName); + if (existo != null) { // handle duplicate named ranges + String bnam = n.toString(); + if (bnam.indexOf("Built-in:") != 0) { + try { + if (((Name) existo).getLocation() != null) // use + // original + // - as good + // a guess + // as any + return -1; // an invalid sheet + } catch (Exception e) {} + // if original does not have a location set, use this one + // instead + this.names.remove(this.names.indexOf(existo)); + this.bookNameRecs.remove(sName); + } + } + this.bookNameRecs.put(sName, n); + } + this.names.add(n); + if ((myexternsheet != null) && (n.getExternsheet() == null)) { + try { + n.setExternsheet(myexternsheet); // update sheet reference + } catch (WorkSheetNotFoundException e) { + Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " + + e.toString()); + } + } + return names.size() - 1; + } + + public void addNameUpdateSheetRefs(Name n, String origWorkBookName) { + if (bookNameRecs.get(n.getNameA()) == null) { + Name newName = new Name(this, n.getName()); + try { + newName.setLocation(n.getLocation()); + } catch (Exception e) {} + if ((myexternsheet != null) && (newName.getExternsheet() == null)) { + try { + newName.setExternsheet(myexternsheet); // update sheet + // reference + } catch (WorkSheetNotFoundException e) { + Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " + + e.toString()); + } + } + newName.updateSheetRefs(origWorkBookName); + } + } + + /** + * Store an external name + * @param n = String describing the name + * @return int location of the name + */ + public int addExternalName(String n) { + this.externalnames.add(n); + return externalnames.size(); // one-based index + } + + /** + * Get a string array of external names + * @return externalNames + */ + public String[] getExternalNames() { + String[] n = new String[externalnames.size()]; + externalnames.toArray(n); + return n; + } + + /** + * Get the external name at the specified index. + * @param t index of the name + * @return name at the index, empty string if it doesn't exist. + * + * Why are we calling getExternalName one based, then removing for ordinal, internal processes should always + * be 0,1,2,3... -NR 1/06 + */ + public String getExternalName(int t) { + if (t > 0) + return (String) this.externalnames.get(t - 1); // one-based index + return ""; + } + + /** + * For workbooks that do not contain an externsheet + * this creates the externsheet record with one 0000 record + * and the related Supbook rec + + * + */ + public void addDefaultExternsheet() { + Supbook sbb = (Supbook) Supbook.getPrototype(this.getNumWorkSheets()); + int l = this.stringTable.getRecordIndex(); + streamer.addRecordAt(sbb, l++); + supBooks.add(sbb); + Externsheet ex = (Externsheet) Externsheet + .getPrototype(0x0000, 0x0000, this); + streamer.addRecordAt(ex, l); + this.addRecord(ex, false); + } + /** apparently this method adds an External name rec and returns the ilbl + * + * Correct structure is + * Supbook + * Externname + * Supbook + * Externsheet + + * + * @see PtgNameX + * @param s + * @return + */ + public int getExtenalNameNumber(String s) { + int i = externalnames.indexOf(s); + if (i > -1) // got it + return i + 1; + if (this.getExternSheet() == null) + this.addDefaultExternsheet(); + + // not found; add a new EXTERNNAME record to list of add-ins + int n = addExternalName(s); + try { + int loc; + if (myADDINSUPBOOK == null) { + Supbook sb = (Supbook) Supbook.getAddInPrototype(); + loc = this.getExternSheet().getRecordIndex(); + streamer.addRecordAt(sb, loc++); + this.addRecord(sb, false); + myADDINSUPBOOK = sb; + supBooks.add(sb); + int externref = this.getExternSheet().getVirtualReference(); + // Add EXTERNNAME record after ADD-IN SUPBOOK record and + // after existing EXTERNNAME records + Externname exn = (Externname) Externname.getPrototype(s); + streamer.addRecordAt(exn, loc++); + this.addRecord(exn, false); + } else { + loc = streamer.getRecordIndex(myADDINSUPBOOK); + // Add EXTERNNAME record after ADD-IN SUPBOOK record and + // after existing EXTERNNAME records + Externname exn = (Externname) Externname.getPrototype(s); + streamer.addRecordAt(exn, loc + externalnames.size()); + this.addRecord(exn, false); + } + + } catch (Exception e) { + Logger.logWarn("Error adding externname: " + e); + } + return n; + } + + /** Get a collection of all names in the workbook + */ + public Name[] getNames() { + Name[] n = new Name[names.size()]; + names.toArray(n); + return n; + } + + /** Get a collection of all names in the workbook + */ + public Name[] getWorkbookScopedNames() { + ArrayList a = new ArrayList(this.bookNameRecs.values()); + Name[] n = new Name[a.size()]; + a.toArray(n); + return n; + } + + /** returns the List of Formulas in the book + + * + * @return + */ + public List getFormulaList() { + return formulas; + } + + /** returns the array of Formulas in the book + + * + * @return + */ + public Formula[] getFormulas() { + Formula[] n = new Formula[formulas.size()]; + formulas.toArray(n); + return n; + } + + /** remove a formula from the book + * + * @param fmla + */ + public void removeFormula(Formula fmla) { + this.formulashash.remove(fmla.getCellAddressWithSheet()); + formulas.remove(fmla); + fmla.destroy(); + } + + /** + * Returns the recalculation mode for the Workbook: + *
0= Manual + *
1= Automatic + *
2= Automatic except for multiple table operations + * @return int + */ + public int getRecalculationMode() { + return this.calcmoderec.getRecalcuationMode(); + } + + /** + * Sets the recalculation mode for the Workbook: + *
0= Manual + *
1= Automatic + *
2= Automatic except for multiple table operations + */ + public void setRecalcuationMode(int mode) { + this.calcmoderec.setRecalculationMode(mode); + } + + /** returns a Named range by number + + * + * @param t + * @return + */ + public Name getName(int t) { + return (Name) this.names.get(t - 1); + } + + /** rename the NamedRange in the lookup map + + * + * @param t + * @return + */ + public void setNewName(String oldname, String newname) { + if (oldname.equals(newname)) + return; + oldname = oldname.toUpperCase(); // case-insensitive + newname = newname.toUpperCase(); // "" + Object old = bookNameRecs.get(oldname); + if (old == null) + return; // new name? + bookNameRecs.remove(oldname); + bookNameRecs.put(newname, old); + + } + + /** + * Re-assocates ptgrefs that are pointing to a name that has been deleted then + * is recreated + * @param name + */ + public void associateDereferencedNames(Name name) { + Iterator i = orphanedPtgNames.iterator(); + String theName = name.getName(); + while (i.hasNext()) { + IlblListener x = (IlblListener) i.next(); + if (x.getStoredName().equalsIgnoreCase(theName)) { + x.setIlbl((short) this.getNameNumber(theName)); + x.addListener(); + } + } + } + + /** returns a named range by name string + * + * This method will first attempt to look in the book names, then the sheet names, + * obviously different scoped names can have the same identifying name, so this could return + * one of multiple names if this is the case + * + * @param t + * @return + */ + public Name getName(String nameRef) { + nameRef = nameRef.toUpperCase(); // case-insensitive + Object o = this.bookNameRecs.get(nameRef); + if (o == null) { + Boundsheet[] shts = this.getWorkSheets(); + for (int i = 0; i < shts.length; i++) { + o = shts[i].getName(nameRef); + if (o != null) + return (Name) o; + } + } + return (Name) o; + } + + /** returns a scoped named range by name string + + * + * @param t + * @return + */ + public Name getScopedName(String nameRef) { + Object o = this.bookNameRecs.get(nameRef.toUpperCase()); // case-insensitive + if (o == null) { + return null; + } + return (Name) o; + } + + /** + * Returns the ilbl of the name record associated with the string passed in. + * If the name does not exist, it get's created without a location reference. + * This is needed to support formula creation with non-existent names referenced. + * + * @param t, the name record to search for + * @return the index of the name + */ + public int getNameNumber(String nameStr) { + for (int i = 0; i < names.size(); i++) { + Name n = (Name) this.names.get(i); + if (n.getName().equalsIgnoreCase(nameStr)) + return i + 1; + } + // no name exists, we need to create one. + Name myName; + myName = new Name(this, nameStr); + // myName = new Name(this, true); + // myName.setName(nameStr); + Name[] nmx = this.getNames(); + int namepos = -1; + if (nmx.length >= 1) { + namepos = nmx[nmx.length - 1].getRecordIndex(); + } else { + namepos = this.getExternSheet().getRecordIndex(); + } + namepos++; + this.getStreamer().addRecordAt(myName, namepos); + return this.getNameNumber(nameStr); + } + + /** + * Get's the index for this particular front. + * + * NOTE: this doesn't actually get a "matching" font, it has to be the exact font. + * 20070826 KSC: changed to match font characterstics, not just return exact matching font + */ + public int getFontIdx(Font f) { + // 20070819 KSC: Try this to see if better! Matches 6 key + // attributes (size, name, color, etc.) + for (int i = fonts.size() - 1; i >= 0; i--) { // start from the back so + // don't initially match + // defaults... + if (f.matches((Font) fonts.get(i))) + return ((i > 3) ? i + 1 : i); + } + // return fonts.indexOf(f); + return -1; + } + + /** + * Get's the index for this font, based on matching through + * xml strings. If the font doesn't exist in the book it returns -1; + * @return + * KSC: is this method necessary now with above getFontIdx changes? + */ + public int getMatchingFontIndex(Font f) { + Map fontmap = this.getFontRecsAsXML(); + Object o = fontmap.get("<" + f.getXML() + "/>"); + if (o != null) { + Integer I = (Integer) o; + return I.intValue(); + } else { + return -1; + } + } + + /** + * InsertFont inserts a Font record into the workbook level stream, + * For some reason, the addFont only puts it into an array that is never accessed + * on output. This may have a reason, so I am not overwriting it currently, but + * let's check it out? + * + * */ + public int insertFont(Font x) { + int insertIdx = this.getFont(this.getNumFonts()).getRecordIndex(); + // perform default add rec actions + this.getStreamer().addRecordAt(x, insertIdx + 1); + x.setIdx(-1); // flag to add into font array + this.addRecord(x, false); // also adds to font array so no need for + // additional addFont below + return fonts.indexOf(x); + } + + /** add a Font object to the collection of Fonts + */ + public int addFont(Font f) { + fonts.add(f); + if (fonts.size() > 4) // fake the evil 4! + return fonts.size(); + return fonts.size() - 1; + } + + public int getNumFonts() { + return fonts.size(); + } + + /** + * Get the font at the specified index. Note that the number 4 does not exist, so index correctly based of that. + * + * So, if you call getFont(5), you are really doing getFont(4) from the internal array + * @param t + * @return + */ + public Font getFont(int t) { + if (t >= 4) { + t--; + } + if (this.fonts.size() >= t) { + if (t >= fonts.size()) { + Logger.logWarn("font " + t + + " not found. Workbook contains only: " + fonts.size() + + " defined fonts."); + return (Font) fonts.get(0); + } else { + return (Font) fonts.get(t); + } + } + return (Font) this.fonts.get(0); + } + + /** Inserts a newly created Format record into the workbook. + * This method handles assigning the format ID and adding the record to the + * workbook. If the record is already part of the workbook use + * {@link #addFormat} instead. + */ + public int insertFormat(Format format) { + Format last; + try { + last = (Format) formats.get(formats.lastKey()); + } catch (NoSuchElementException e) { + /* + * There are no other Format records in the workbook. + * This shouldn't happen because most (all?) Excel files + * contain + * Format records for the locale-specific (and thus not + * implied) + * built-in formats. If it does happen, either we need to + * re-assess + * the above assumption or this method was called before the + * Format + * records were parsed. Either way we need to know about it. + */ + throw new AssertionError("WorkBook.insertFormat called but no " + + "Format records exist. This should not happen. Please " + + "report this error to support@extentech.com."); + } + + // Add it to the streamer and workbook + getStreamer().addRecordAt(format, last.getRecordIndex() + 1); + addRecord(format, false); + + // Give it a format ID + if (format.getIfmt() == -1) + format.setIfmt((short) Math.max(last.getIfmt() + 1, 164)); + + // Add it to the format lookups + addFormat(format); + + return format.getIfmt(); + } + + public Formula getFormula(String cellAddress) throws FormulaNotFoundException { + Formula formula = (Formula) formulashash.get(cellAddress); + if (formula == null) + throw new FormulaNotFoundException( + "no formula found at " + cellAddress); + + return (formula); + } + + /** Adds an existing format record to the list of known formats. + * This method does not add the record to the workbook! If the format is + * not already in the workbook use {@link #insertFormat} instead. + * @param format the Format record to add + */ + public int addFormat(Format format) { + Short ifmt = Short.valueOf(format.getIfmt()); + + // Add it to the format record lookup + formats.put(ifmt, format); + + // Add it to the format string lookup + formatlookup.put(format.getFormat().toUpperCase(), ifmt); + + return format.getIfmt(); + } + + /** Gets the number of custom number formats registered on this book. + */ + public int getNumFormats() { + return formats.size(); + } + + /** Gets a custom number format by its format ID. + */ + public Format getFormat(int id) { + return (Format) formats.get(Short.valueOf((short) id)); + } + + public TabID getTabID() { + return this.tabs; + } + + /** associate default row/col size recs + */ + void setDefaultRowHeightRec(DefaultRowHeight dr) { + this.drh = dr; + } + + /** set Default row height in twips (1/20 of a point) + */ + // should be a double as Excel units are 1/20 of what is + // stored in defaultrowheight + // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 + // (approx) + // should expect users to use Excel units and target method + // do the 20* conversion + public void setDefaultRowHeight(int t) { + drh.setDefaultRowHeight(t); + } + + /** set Default col width for all worksheets in the workbook, + * + * Default column width can also be set on individual worksheets + */ + public void setDefaultColWidth(int t) { + Boundsheet[] b = this.getWorkSheets(); + for (int i = 0; i < b.length; i++) { + b[i].setDefaultColumnWidth(t); + } + } + + /** sets the selected worksheet + */ + public int getSelectedSheetNum() { + return win1.getCurrentTab(); + } + + /** sets the selected worksheet + */ + public void setSelectedSheet(Boundsheet bs) { + Boundsheet[] bsx = this.getWorkSheets(); + for (int t = 0; t < bsx.length; t++) { + if (bsx[t] != bs) + bsx[t].setSelected(false); + } + this.win1.setCurrentTab(bs); + } + + Formula lastFormula = null; + XLSRecord countryRec = null; + boolean inChartSubstream = false; + ArrayList chartTemp = new ArrayList(); + + /** + * for those cases where a formula calculation adds a new string rec + * need to explicitly set lastFormula before calling addRecord + * @param f + */ + protected void setLastFormula(Formula f) { + lastFormula = f; + } + + /** Associate a record with its containers and related records. + + */ + @Override + public BiffRec addRecord(BiffRec rec, boolean addtorec) { + short opc = rec.getOpcode(); + rec.setStreamer(streamer); + rec.setWorkBook(this); + + Boundsheet bs = null; + Long lbplypos = new Long(0l); + + // get the relevant Boundsheet for this rec + if (rec instanceof Bof) { + if (this.getFirstBof() == null) + this.setFirstBof((Bof) rec); + if (bofct == eofct) { // not a chart or other non Sheet Bof + this.setLastBOF((Bof) rec); + } + if (((Bof) rec).isChartBof()) { + inChartSubstream = true; + } + } + + if (this.lastBOF == null) + Logger.logWarn("WorkBook: NULL Last BOF"); + long lb = this.lastBOF.getLbPlyPos(); + if (!this.lastBOF.isValidBIFF8()) + lb += 8; + lbplypos = new Long(lb); // use last + + bs = getSheetFromRec(rec, lbplypos); + if (bs != null) + lbplypos = new Long(bs.getLbPlyPos()); + + if ((bs != null)) { // &&){ + lastbound = bs; + if (addtorec) + rec.setSheet(bs);// we don't include Bof or other Book-recs + // because it lives in the Streamer recvec + if /* + * ((rec.isValueForCell()) + * && + */ (!copying) { + if ((lastFormula != null) && (opc == STRINGREC)) { + lastFormula.addInternalRecord(rec); + } else if ((lastFormula != null) && (opc == ARRAY)) { + lastFormula.addInternalRecord(rec); + } else if (rec.isValueForCell()) { + if (currchart == null) + bs.addCell((CellRec) rec); + + } + } + } + + if (inChartSubstream) { + if (currchart == null) { + if (rec.getOpcode() == CHART) { + charts.add(rec); + if (bs != null) + bs.addChart((Chart) rec); + currchart = (Chart) rec; + currchart.setPreRecords(chartTemp); + chartTemp = new ArrayList(); // clear out + } else { + chartTemp.add(rec); + } + } else { + currchart.addInitialChartRecord(rec); + if (rec.getOpcode() == EOF) { + currchart.initChartRecords(); // finished + currchart = null; + inChartSubstream = false; + } + } + addtorec = false; + } + + // Rows, valrecs, dbcells, and muls are stored in the row, + // not the byte streamer + if (opc == XLSRecord.DBCELL || opc == XLSRecord.ROW + || rec.isValueForCell() || opc == XLSRecord.MULRK + /* || opc==MULBLANK */ + || opc == CHART || opc == XLSRecord.FILEPASS + || opc == XLSRecord.SHRFMLA || opc == XLSRecord.ARRAY + || opc == XLSRecord.STRINGREC) { + addtorec = false; + } + + // add it to the record stream + if (addtorec) { + + if (lbplypos.longValue() > 0) + streamer.addRecord(rec); + else + streamer.records.add(rec); + } + + switch (opc) { + case AUTOFILTER: + bs.getAutoFilters().add(rec); + break; + + case CONDFMT: + bs.getConditionalFormats().add(rec); + ((Condfmt) rec).initializeReferences(); + break; + + case CF: + Condfmt cfmt = (Condfmt) bs.getConditionalFormats() + .get(bs.getConditionalFormats().size() - 1); + cfmt.addRule((Cf) rec); + break; + + case MERGEDCELLS: + bs.addMergedCellsRec((Mergedcells) rec); + this.addMergedcells((Mergedcells) rec); + break; + + // give protection records to the relevant ProtectionManager + case PASSWORD: + case PROTECT: + case PROT4REV: + case OBJPROTECT: + case SCENPROTECT: + case FEATHEADR: + ProtectionManager manager; + if (bs != null) + manager = bs.getProtectionManager(); + else + manager = this.getProtectionManager(); + manager.addRecord(rec); + break; + + case DVAL: + if (bs != null) + bs.setDvalRec((Dval) rec); + break; + + case DV: + if (bs != null) { + if (bs.getDvalRec() != null) { + bs.getDvalRec().addDvRec((Dv) rec); + } + } + break; + + case INDEX: + Index id = (Index) rec; + id.setIndexNum(indexnum++); + indexes.add(indexes.size(), id); + this.setLastINDEX(id); + if (bs == null) { + Logger.logWarn("ERROR: WorkBook.addRecord( Index ) error: BAD LBPLYPOS. The wrong LB:" + + lbplypos); + try { + bs = this.getWorkSheetByNumber(indexnum - 1); + Logger.logInfo(" The RIGHT LB:" + bs.getLbPlyPos()); + } catch (WorkSheetNotFoundException e) { + Logger.logInfo("problem getting WorkSheetByNumber: " + e); + } + + } + bs.setSheetIDX(id); + break; + + case ROW: + Row rw = (Row) rec; + if (bs != null) + bs.addRowRec(rw); + + break; + + case FORMULA: + this.addFormula((Formula) rec); + lastFormula = (Formula) rec; + break; + + case ARRAY: + Array arr = (Array) rec; + if (bs != null) + bs.addArrayFormula(arr); + arr.setParentRec(lastFormula); // [BugTracker 1869] link array + // formula to it's parent formula + // rec + break; + + /* + * case SHRFMLA : done in shrfmla.init + * Shrfmla form = (Shrfmla) rec; + * try{ // throws exceptipon during pullparse + * form.setHostCell( lastFormula ); + * }catch(Exception e){;} + * break; + */ + + case DATE1904: + if (((NineteenOhFour) rec).is1904) { + this.dateFormat = DateConverter.DateFormat.LEGACY_1904; + } + break; + + /* + * case PALETTE : // palette now correctly read into + * COLORTABLE + * this.pal = (Palette) rec; + * break; + */ + + case HLINK: + Hlink hl = (Hlink) rec; + this.addHlink(hl); + break; + + case DSF: + Dsf dsf = (Dsf) rec; + if (dsf.fDSF == 1) { + Logger.logErr("DOUBLE STREAM FILE DETECTED!"); + Logger.logErr(" OpenXLS is compatible with Excel 97 and above only."); + throw new WorkBookException( + "ERROR: DOUBLE STREAM FILE DETECTED! OpenXLS is compatible with Excel 97 + only.", + WorkBookException.DOUBLE_STREAM_FILE); + } + break; + + case GUTS: + if (bs != null) { + bs.setGuts((Guts) rec); + } + break; + + case DBCELL: + break; + + case BOF: + if (DEBUGLEVEL > 5) + Logger.logInfo("BOF:" + bofct + " - " + rec); + if (eofct == bofct) { + if (bs != null) { + bs.setBOF((Bof) rec); + } + } + bofct++; + break; + + case EXTERNSHEET: + myexternsheet = (Externsheet) rec; + break; + + case DEFCOLWIDTH: + if (bs != null) { + bs.setDefColWidth((DefColWidth) rec); + } + break; + + case EOF: + this.lasteof = (Eof) rec; + eofct++; + if (eofct == bofct) { + if (bs != null) { + bs.setEOF((Eof) rec); + } + eofct--; + bofct--; + } + break; + + case SELECTION: // only used for Recvec index + bs.setLastselection((Selection) rec); + break; + + case COUNTRY: + // Added to save position of 1st bound sheet, which is 1 + // record + // before COUNTRY RECORD (= 2 before 1st SUPBOOK record - + // true in all cases?) + countryRec = (XLSRecord) rec; + // USA=1, Canada=1, Japan=81, China=86, Thailand= 66, Korea= + // 82, India=91 ... + this.defaultLanguage = ((Country) rec).getDefaultLanguage(); + break; + + case SUPBOOK: // KSC: must store ordinal positions of SupBooks, for + // adding Externsheets + supBooks.add(rec); + if (myADDINSUPBOOK == null) { // see if this is the ADD-IN SUPBOOK + // rec + Supbook sb = (Supbook) rec; + if (sb.isAddInRecord()) + myADDINSUPBOOK = sb; + } + break; + + case BOUNDSHEET: + Boundsheet sh = (Boundsheet) rec; + + /* + * Here we need to set the selected variable, + * but not mess with selected tabs + * when all of the sheets aren't in the book yet. + * -jm + */ + int ctab = 1; // default- select 1st sheet if no Windows1 record + if (win1 != null) {// Windows1 record is optional 20101004 + // TestCorruption.TestNPEOnOpen + ctab = win1.getCurrentTab(); + } + int shts = boundsheets.size(); + if (ctab == shts) + sh.selected = true; + + this.addWorkSheet(new Long(sh.getLbPlyPos()), sh); + break; + + case MULRK: + Mulrk mul = (Mulrk) rec; + Iterator xit = mul.getRecs().iterator(); + while (xit.hasNext()) { + this.addRecord(((Rk) xit.next()), false); + } + break; + + case SST: + this.setSharedStringTable((Sst) rec); + break; + + case EXTSST: + ((Extsst) rec).setSst(this.getSharedStringTable()); + break; + + case SXSTREAMID: + this.ptstream.add(rec); // Pivot Stream + break; + + case SXVS: + case DCONREF: + case DCONNAME: + case DCONBIN: + try { + SxStreamID sid = (SxStreamID) ptstream.get(ptstream.size() - 1); + sid.addSubrecord(rec); + } catch (Exception e) {} + break; + + case SXVIEW: + addPivotTable(((Sxview) rec)); // Pivot Table View ==Top-level + // record for a Pivot Table + break; + + // all* possible records associated with SxView (=PivotTable + // View) (*hopefully) + case SXVD: + // case SXVI: // subrecords of SxVD + // case SXVDEX: + case SXIVD: + case SXPI: + case SXDI: + case SXLI: + case SXEX: + case SXVIEWEX9: + case QSISXTAG: + try { + Sxview sx = (Sxview) this.ptViews.values() + .toArray()[this.ptViews.size() - 1]; + sx.addSubrecord(rec); + } catch (Exception e) { + ; + } + break; + + case TABID: + this.tabs = (TabID) rec; + break; + + case NAME: + addName((Name) rec); + break; + + case CALCMODE: + this.calcmoderec = (CalcMode) rec; + break; + + case WINDOW1: + this.win1 = (Window1) rec; + break; + + case WINDOW2: + if (bs != null) + bs.setWindow2((Window2) rec); + break; + + case SCL: // scl is for zoom + if (bs != null) + bs.setScl((Scl) rec); + break; + + case PANE: + if (bs != null) + bs.setPane((Pane) rec); + break; + case EXCEL2K: + xl2k = rec; + break; + + case PHONETIC: + // TODO: this isn't necessary anymore! look at and remove + if (this.currdrw != null) { + this.currdrw.setMystery((Phonetic) rec); + } + break; + + case MSODRAWINGGROUP: + if (msodg == null) + msodg = (MSODrawingGroup) rec; + msodgMerge.add(rec); + + break; + + case MSODRAWING: + rec.setSheet(bs); + if (msodg != null) + msodg.addMsodrawingrec((MSODrawing) rec); + else + ; + // do what???io.starter.toolkit.Logger.log("PROBLEM with + // MSODG!"); + break; + + case COLINFO: + bs.addColinfo((Colinfo) rec); + break; + + case USERSVIEWBEGIN: + this.usersview = (Usersviewbegin) rec; + break; + + case WSBOOL: + if (bs != null) { + bs.setWsBool((WsBool) rec); + } + break; + + // Handle continue records which are actually masked Mso's + case CONTINUE: + if (((Continue) rec).maskedMso != null) { + ((Continue) rec).maskedMso.setSheet(bs); + if (msodg != null) + msodg.addMsodrawingrec(((Continue) rec).maskedMso); + } + break; + + case XF: + try { + this.addXf((Xf) rec); + } catch (Exception e) { + ;// throws exceptions during PullParse + } + break; + + default: + // DO NOTHING + + } + + // finish up + rec.setIndex(getLastINDEX()); + rec.setXFRecord(); + return rec; + } + + /** + * Dec 15, 2010 + * @param rec + * @return + */ + @Override + public Boundsheet getSheetFromRec(BiffRec rec, Long lbplypos) { + Boundsheet bs = null; + + if (rec.getSheet() != null) { + bs = rec.getSheet(); + } else if (lbplypos != null) { + bs = this.getWorkSheet(lbplypos); + } else + bs = lastbound; + return bs; + } + + /** get a handle to the ContinueHandler + */ + @Override + public ContinueHandler getContinueHandler() { + return this.contHandler; + } + + /** get a handle to the Usersviewbegin for the workbook + */ + public Usersviewbegin getUsersviewbegin() { + if (usersview == null) { + usersview = new Usersviewbegin(); + streamer.addRecord(usersview); + this.addRecord(usersview, false); + } + return usersview; + } + + /** set the Debug level + */ + @Override + public void setDebugLevel(int i) { + this.DEBUGLEVEL = i; + } + + /** get a handle to the Reader for this + WorkBook. + */ + @Override + public void setFactory(WorkBookFactory r) { + this.factory = r; + } + + /** Get the number of worksheets in this WorkBook + */ + public int getNumWorkSheets() { + return this.boundsheets.size(); + } + + /** get the number of formulas in this WorkBook + + * + * @return + */ + public int getNumFormulas() { + return formulas.size(); + } + + /** get the number of Cells in this WorkBook + */ + public int getNumCells() { + int cellnum = 0; + Enumeration e = workSheets.elements(); + while (e.hasMoreElements()) { + Boundsheet b = (Boundsheet) e.nextElement(); + cellnum += b.getNumCells(); + } + return cellnum; + } + + /** get all of the Cells in this WorkBook + */ + public BiffRec[] getCells() { + List cellz = new FastAddVector(); + for (int i = 0; i < workSheets.size(); i++) { + try { + Boundsheet b = this.getWorkSheetByNumber(i); + BiffRec[] cz = b.getCells(); + for (int x = 0; x < cz.length; x++) { + cellz.add(cz[x]); + } + } catch (Exception e) { + Logger.logErr("Error retrieving worksheet for getCells: " + e); + } + } + BiffRec[] cellzr = new BiffRec[cellz.size()]; + cellz.toArray(cellzr); + return cellzr; + } + + /** get the cell by the following String Pattern + + BiffRec c = getCell("SheetName!C17"); + */ + public BiffRec getCell(String cellname) throws CellNotFoundException, WorkSheetNotFoundException { + int semi = cellname.indexOf("!"); + String sname = cellname.substring(0, semi); + String cname = cellname.substring(semi + 1); + return getCell(sname, cname); + } + + /** get the cell by the following String Pattern + + BiffRec c = getCell("Sheet1", "C17"); + */ + public BiffRec getCell(String sheetname, String cellname) throws CellNotFoundException, WorkSheetNotFoundException { + cellname = cellname.toUpperCase(); + try { + Boundsheet bs = this.getWorkSheetByName(sheetname); + BiffRec ret = bs.getCell(cellname); + if (ret == null) + throw new CellNotFoundException(sheetname + ":" + cellname); + return ret; + } catch (WorkSheetNotFoundException a) { + throw new WorkSheetNotFoundException(sheetname + " not found"); + } catch (NullPointerException e) { + throw new CellNotFoundException(sheetname + ":" + cellname); + } + } + + private Boundsheet lastbound = null; + + /** add a Boundsheet to the WorkBook + */ + private void addWorkSheet(Long lbplypos, Boundsheet sheet) { + if (sheet == null) { + Logger.logWarn("WorkBook.addWorkSheet() attempting to add null sheet."); + return; + } else { + this.lastbound = sheet; + if (DEBUGLEVEL > 10) + Logger.logInfo("Workbook Adding Sheet: " + sheet.getSheetName() + + ":" + String.valueOf(lbplypos)); + workSheets.put(lbplypos, sheet); + boundsheets.add(sheet); + } + } + + /** + * + * @param n + * @return + */ + public boolean removeName(Name n) { + if (this.names.contains(n)) { + this.names.remove(n); + if (n.getItab() != 0) { + try { + this.getWorkSheetByNumber(n.getItab() - 1) + .removeLocalName(n); + } catch (WorkSheetNotFoundException e) {} + } else { + this.bookNameRecs.remove(n.toString().toUpperCase()); // case-insensitive + } + } + ArrayList al = n.getIlblListeners(); + this.orphanedPtgNames.addAll(al); + + this.updateNameIlbls(); + return this.getStreamer().removeRecord(n); + } + + /** + * Add a sheet-scoped name record to the boundsheet + * + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this book, adding them here will not add them to the workbook; + * + * @param bookNameRecs + */ + public void addLocalName(Name name) { + bookNameRecs.put(name.getNameA(), name); + } + + /** + * Remove a sheet-scoped name record from the boundsheet. + * + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this book, removing them here will not remove them completely from the workbook. + * + * In order to do that you will need to call book.removeName + * + * @param bookNameRecs + */ + public void removeLocalName(Name name) { + bookNameRecs.remove(name.getNameA()); + } + + /** + * + * After any changes in the name records + * this method needs to be called in order to + * update ilbl records + + * + */ + public void updateNameIlbls() { + for (int i = 0; i < names.size(); i++) { + Name n = (Name) names.get(i); + n.updateIlblListeners(); + } + } + + /** remove a Boundsheet from the WorkBook + + */ + public void removeWorkSheet(Boundsheet sheet) { + + int sheetNum = sheet.getSheetNum(); + // remove the sheet + // automatically deletes Named ranges scoped to the sheet + Name[] namesOnSheet = sheet.getAllNames(); + for (int i = 0; i < namesOnSheet.length; i++) + this.removeName(namesOnSheet[i]); + + // Remove Externsheet ref before removing sheet + // update any Externsheet references... + try { + Externsheet ext = this.getExternSheet(); + if (ext != null) + ext.removeSheet(sheet.getSheetNum()); + } catch (WorkSheetNotFoundException e) { + Logger.logInfo("could not update Externsheet reference from " + + sheet.toString() + " : " + e.toString()); + } + + sheet.removeAllRecords(); + streamer.removeRecord(sheet); + workSheets.remove(new Long(sheet.getLbPlyPos())); + boundsheets.remove(sheet); + // we need to reset the lastbound for adding new worksheets. + // Currently assume it is + // the last one in the vector. + if (boundsheets.size() > 0) { + lastbound = (Boundsheet) (boundsheets.get(boundsheets.size() - 1)); + lastBOF = lastbound.getMyBof(); + } + + // decrement the tab ids... + this.tabs.removeRecord(); + this.updateScopedNamedRanges(); + + // update wb chart cache - remove charts referenced by + // deleted sheet + for (int i = getChartVect().size() - 1; i >= 0; i--) { + if (((Chart) this.getChartVect().get(i)).getSheet() == sheet) + this.getChartVect().remove(i); + } + + if (this.getNumWorkSheets() == 0) + return; // empty book + try { // set the next sheet selected... + while (sheetNum <= this.getNumWorkSheets()) { + Boundsheet s2 = this.getWorkSheetByNumber(sheetNum++); + s2.setSelected(true); + if (!s2.getHidden()) + break; + + } + } catch (WorkSheetNotFoundException e) { + try { + Boundsheet s2 = this.getWorkSheetByNumber(0); + s2.setSelected(true); + } catch (Exception ee) { + throw new WorkBookException( + "Invalid WorkBook. WorkBook must contain at least one Sheet.", + WorkBookException.RUNTIME_ERROR); + } ; + } + } + + /** + * Updates all the name records in the workbook that are bound to a + * worksheet scope (as opposed to a workbook scope). Name records use + * their own non-externsheet based sheet references, so need to be modified + * whenever a sheet delete (or non-last sheet insert) operation occurs + */ + private void updateScopedNamedRanges() { + for (int i = 0; i < boundsheets.size(); i++) { + ((Boundsheet) boundsheets.get(i)).updateLocalNameReferences(); + } + } + + /** returns the Boundsheet identified by its + offset to the BOF record indicating the + start of the Boundsheet data stream. + + used internally to access the Sheets to + ensure that the lbplypos is correct -- essential + to proper operation of XLS file. + + @param Long lbplypos of Boundsheet + */ + private Boundsheet getWorkSheet(Long lbplypos) { + return (Boundsheet) workSheets.get(lbplypos); + } + + /** returns the Boundsheet with the specific name + + @param String name of Boundsheet + */ + public Boundsheet getWorkSheetByName(String bstr) throws WorkSheetNotFoundException { + try { + if (bstr.startsWith("'") || bstr.startsWith("\"")) + bstr = bstr.substring(1, bstr.trim().length() - 1); + Iterator bs = boundsheets.iterator(); + while (bs.hasNext()) { + Boundsheet bsi = (Boundsheet) bs.next(); + String bsin = bsi.getSheetName(); + // TODO: check if we can have dupe names different case + if (bsin.equalsIgnoreCase(bstr)) + return bsi; + } + } catch (Exception ex) { + Logger.logWarn("WorkBook.getWorkSheetByName failed: " + + ex.toString()); + } + throw new WorkSheetNotFoundException( + "Worksheet " + bstr + " not found in " + this.toString()); + } + + /** returns the Boundsheet with the specific Hashname + + @param String hashname of Boundsheet + */ + public Boundsheet getWorkSheetByHash(String s) throws WorkSheetNotFoundException { + Boundsheet[] bs = this.getWorkSheets(); + for (int i = 0; i < bs.length; i++) { + if (bs[i].getSheetHash().equalsIgnoreCase(s)) + return bs[i]; + } + return null; + } + + /** returns the Boundsheet at the specific index + + @param int index of Boundsheet + */ + public Boundsheet getWorkSheetByNumber(int i) throws WorkSheetNotFoundException { + Boundsheet bs = null; + try { + bs = (Boundsheet) boundsheets.get(i); + } catch (ArrayIndexOutOfBoundsException e) {} ; + if (bs == null) { // External Sheet Ref NOT FOUND + throw new WorkSheetNotFoundException( + String.valueOf(i) + " not found"); + } + return bs; + } + + /** set the last processed Index record + */ + private void setLastINDEX(Index id) { + lastidx = id; + } + + Index getLastINDEX() { + return lastidx; + } + + void setSharedStringTable(Sst s) { + stringTable = s; + } + + public Sst getSharedStringTable() { + return stringTable; + } + + /** returns the Vector of Boundsheets + */ + public AbstractList getSheetVect() { + return this.boundsheets; + } + + /** returns the boundsheets for this book as an array + */ + Boundsheet[] getWorkSheets() { + Boundsheet[] ret = new Boundsheet[boundsheets.size()]; + return (Boundsheet[]) boundsheets.toArray(ret); + } + + /** set the last BOF read in the stream */ + void setLastBOF(Bof b) { + lastBOF = b; + } + /** return the last BOF read in the stream */ + // Bof lastBOF{return lastBOF;} + + /** get a handle to the first BOF to perform offset functions which don't know where the + start of the file is due to the compound file format. + + Referred to in Boundsheet as the 'lbPlyPos', this + is the position of the BOF for the Boundsheet relative + to the *first* BOF in the file (the firstBOF of the WorkBook) + + @see Boundsheet + */ + @Override + public void setFirstBof(Bof b) { + firstBOF = b; + } + + Bof getFirstBof() { + return firstBOF; + } + + @Override + public String toString() { + return this.getFileName(); + } + + @Override + public String getFileName() { + if (this.factory != null) // 2003-vers + return this.factory.getFileName(); + return "New Spreadsheet"; + } + + /** Returns whether the sheet selection tabs should be shown. */ + public boolean showSheetTabs() { + return win1.showSheetTabs(); + } + + /** Sets whether the sheet selection tabs should be shown. */ + public void setShowSheetTabs(boolean show) { + win1.setShowSheetTabs(show); + } + + /** set the first visible tab + */ + public void setFirstVisibleSheet(Boundsheet bs2) { + win1.setFirstTab(bs2.getSheetNum()); + } + + // Associate related records + /** return the XF record at the specified index + */ + public Xf getXf(int i) { + if (xfrecs.size() < (i - 1)) + return null; + return (Xf) xfrecs.get(i); + } + + public int getNumXfs() { + return xfrecs.size(); + } + + /** + * InsertXF inserts an XF record into the workbook level stream, + * For some reason, the addXf only puts it into an array that is never accessed + * on output. This may have a reason, so I am not overwriting it currently, but + * let's check it out? + * + * */ + int insertXf(Xf x) { + int insertIdx = this.getXf(this.getNumXfs() - 1).getRecordIndex(); + // perform default add rsec actions + this.getStreamer().addRecordAt(x, insertIdx + 1); + this.addRecord(x, false); // updates xfrecs + formatcache + x.ixfe = x.tableidx; + return x.tableidx; + } + + /** + * internally used in preparation for reading an 2007 and above workbook + */ + public void removeXfRecs() { + // must keep the 1st xf rec as default + for (int i = xfrecs.size() - 1; i > 0; i--) { + Xf xf = (Xf) xfrecs.get(i); + this.streamer.removeRecord(xf); + xfrecs.remove(i); + + } + } + + /** + * TODO: Does this function as desired? See comment for insertXf() above... + * tracks existing xf recs, used when testing whether xfrec exists or not ... + * -NR 1/06 + * ya should - called now from addRecord every time an xf record is added + * NOTE: this is the only place addXf is called + * + * @param xf + * @return + */ + int addXf(Xf xf) { + xfrecs.add(xf); + xf.tableidx = xfrecs.size() - 1; // flag that it's been added to records + this.updateFormatCache(xf); // links tostring of xf to xf rec for + // updating/reuse purposes + return xf.tableidx; + } + + /** + * formatCache: + * links tostring of xf to xf rec for updating/reuse purposes + * + * @see FormatHandle.updateXf + * @see WorkBook.addXf + * @param xf + */ + public void updateFormatCache(Xf xf) { + if (xf.tableidx != -1) { // if this xf has been already added to the + // workbook + if (formatCache.containsValue(xf)) { // xf signature has + // changed/it's been updated + Iterator ii = formatCache.keySet().iterator(); // remove and + // update below + while (ii.hasNext()) { + String key = (String) ii.next(); + Xf x = (Xf) formatCache.get(key); + if (x.equals(xf)) { + formatCache.remove(key); + break; + } + } + } + String formatStr = xf.toString(); + + if (!formatCache.containsKey(formatStr)) + formatCache.put(formatStr, xf); + } + } + + /** + * retrieve the format cache - links string vers. of xf to xf rec + * used for resusing xf's + * @see FormatHandle.updateXf + * @return + */ + public HashMap getFormatCache() { + return formatCache; + } + + /** Get a substream by name.*/ + @Override + public ByteStreamer getStreamer() { + return this.streamer; + } + + public void setSubStream(ByteStreamer s) { + this.streamer = s; + } + + /** Get the typename for this object.*/ + String getTypeName() { + return "WorkBook"; + } + + /** Write the contents of the WorkBook bytes to an OutputStream + + * + * @param _out + */ + @Override + public int stream(OutputStream _out) { + return streamer.streamOut(_out); + } + + /** get a handle to the factory*/ + @Override + public WorkBookFactory getFactory() { + return this.factory; + } + + /** + * Copies a complete boundsheet within the workbook + * + * If a name exists that refers directly to this sheet then duplicate it, otherwise workbook scoped names are + * not copied + */ + public void copyWorkSheet(String SourceSheetName, String NewSheetName) throws Exception { + Boundsheet origSheet = null; + origSheet = this.getWorkSheetByName(SourceSheetName); + List chts = origSheet.getCharts(); // 20080630 KSC: Added + for (int i = 0; i < chts.size(); i++) { + Chart cxi = (Chart) chts.get(i); + cxi.populateForTransfer(); + } + byte[] inbytes = origSheet.getSheetBytes(); + this.addBoundsheet(inbytes, SourceSheetName, NewSheetName, null, false); + Boundsheet bnd = this.getWorkSheetByName(NewSheetName); + // handle moving the built-in name records. These handle + // such items as print area, header/footer, etc + Name[] ns = this.getNames(); + for (int i = 0; i < ns.length; i++) { // 20100404 KSC: take out +1? + if (ns[i].getItab() == origSheet.getSheetNum() + 1) { + // it's a built in record, move it to the new sheet + int sheetnum = bnd.getSheetNum(); + int xref = this.getExternSheet(true) + .insertLocation(sheetnum, sheetnum); + Name n = (Name) ns[i].clone(); + n.setExternsheetRef(xref); + n.updateSheetReferences(bnd); + n.setSheet(bnd); + n.setItab((short) (bnd.getSheetNum() + 1)); + this.insertName(n); + } + } + } + + /** + * Inserts a newly created Name record into the correct location in the streamer. + + * + * @param n + */ + public void insertName(Name n) { + int namepos = -1; + Name[] nmx = getNames(); + if (nmx.length > 0) { + if (nmx[nmx.length - 1].getSheet() != null) { + namepos = nmx[nmx.length - 1].getRecordIndex(); + } else { + namepos = getExternSheet(true).getRecordIndex() + nmx.length; + } + } else { + namepos = getExternSheet(true).getRecordIndex(); + } + namepos++; + getStreamer().addRecordToBookStreamerAt(n, namepos); + addRecord(n, false); + } + + /** Copies an existing Chart to another WorkSheet + * + * @param chartname + * @param sheetname + */ + public void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException { + Chart ct = this.getChart(chartname); + Boundsheet sht = this.getWorkSheetByName(sheetname); + byte[] bt = ct.getChartBytes(); + sht.addChart(bt, chartname, ct.getCoords()); + } + + /** Inserts a serialized boundsheet chart into the workboook + */ + public Chart addChart(Chart destChart, String NewChartName, Boundsheet boundsht) { + destChart.setWorkBook(this); + destChart.setSheet(boundsht); + List recs = destChart.getXLSrecs(); + for (int x = 0; x < recs.size(); x++) { + XLSRecord rec = (XLSRecord) recs.get(x); + rec.setWorkBook(this); + rec.setSheet(boundsht); + if (rec.getOpcode() == MSODRAWING) { + addChartUpdateMsodg((MSODrawing) rec, boundsht); + continue; + } + if (!(rec instanceof Bof)) // TODO: error/problem with the BOF + // record!!! + rec.init(); + if (rec instanceof Dimensions) + destChart.setDimensions((Dimensions) rec); + try { + ((GenericChartObject) rec).setParentChart(destChart); + } catch (ClassCastException e) { // Scl, Obj and others are not + // chart objects + } + + } + destChart.setTitle(NewChartName); + destChart.setId(boundsht.lastObjId + 1); // track last obj id per sheet + // ... + this.charts.add(destChart); + boundsht.getCharts().add(destChart); // should really have two lists??? + return destChart; + } + + /** + * updates Mso (MSODrawingGroup + Msodrawing) records upon add/copy worksheet and add/copy charts + * NOTE: this code is mainly garnered via trial and error, works + * @param mso Msodrawing record that is being added or copied + * @param sht Boundsheet + */ + public void addChartUpdateMsodg(MSODrawing mso, Boundsheet sht) { + if (msodg == null) { + this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup + .getPrototype()); + msodg.initNewMSODrawingGroup(); // generate and add required records + // for drawing records + } + msodg.addMsodrawingrec(mso); + MSODrawing hdr = msodg.getMsoHeaderRec(sht); + if (hdr != null && hdr != mso) { // already have a header rec + if (sht.getCharts().size() > 0) { + mso.makeNonHeader(); + hdr.setNumShapes(hdr.getNumShapes() + 1); + } + } else if (hdr == null) { + mso.setIsHeader(); + hdr = mso; + } + this.updateMsodrawingHeaderRec(sht); + msodg.dirtyflag = true; // flag to reset SPIDs on write + msodg.setSpidMax(++lastSPID); + msodg.updateRecord(); + } + + /** JM - + * Add the requisite records in the book streamer for the chart. \ + * Supbook, externsheet & msodrawinggroup + * + * I think this is due to the fact that the referenced series are usually stored + * in the fashon 'Sheet1!A4:B6' The sheet1 reference requires a supbook, though the + * reference is internal. + */ + public void addPreChart() { + this.addExternsheet(); + if (msodg == null) { + this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup + .getPrototype()); + msodg.initNewMSODrawingGroup(); // generate and add required records + // for drawing records + } + + } + + /** + * remove an existing chart from the workbook + * NOTE: STILL EXPERIMENTAL TESTS OK IN BASIC CIRCUMSTANCES BUT MUST BE TESTED FURTHER + */ + public void deleteChart(String chartname, Boundsheet sheet) throws ChartNotFoundException { + Chart chart = this.getChart(chartname); + // TODO: Update Dimensions record?? + List recs = chart.getXLSrecs(); + // first rec SHOULD BE MsoDrawing!!! + try { + MSODrawing rec = (MSODrawing) recs.get(0); + msodg.removeMsodrawingrec(rec, sheet, true); // also remove + // associated Obj + // record + } catch (Exception e) { + Logger.logErr("deleteChart: expected Msodrawing record"); + } + /* + * shouldn't be necessary to remove chart recs as they are + * separated upon init of workbook and reassebmbled upon + * write + */ + this.removeChart(chartname); + } + + /** + * Inserts a serialized boundsheet into the workboook. + * + * @param inbytes original sheet bytes + * @param NewSheetName new Sheet Name + * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * + * @throws ClassNotFoundException + * @throws IOException + * @boolean SSTPopulatedBoundsheet - a boundsheet that has all of it's sst data saved off in LabelSST records. + * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. + * Do not use this if the data already exists in the SST, you are just causing bloat! + */ + public void addBoundsheet(byte[] inbytes, String origSheetName, String NewSheetName, String origWorkBookName, boolean SSTPopulatedBoundsheet) throws IOException, ClassNotFoundException { + Boundsheet destSheet = null; + ByteArrayInputStream bais = new ByteArrayInputStream(inbytes); + BufferedInputStream bufstr = new BufferedInputStream(bais); + ObjectInputStream o = new ObjectInputStream(bufstr); + destSheet = (Boundsheet) o.readObject(); + + if (destSheet != null) { + this.addBoundsheet(destSheet, origSheetName, NewSheetName, origWorkBookName, SSTPopulatedBoundsheet); + } + } + + /** change the tab order of a boundsheet + */ + public void changeWorkSheetOrder(Boundsheet bs, int idx) { + // reorder the sheet vector + if (idx >= 0 && idx < boundsheets.size()) { + boundsheets.remove(bs); + boundsheets.add(idx, bs); + for (int x = 0; x < boundsheets.size(); x++) { + Boundsheet bs1 = (Boundsheet) boundsheets.get(x); + boolean udpatewin1 = bs1.selected(); + if (udpatewin1) + bs1.setSelected(true); + } + } + + int insertLoc = Integer.MAX_VALUE; + // remove the existing boundsheet records in the streamer + for (int i = 0; i < boundsheets.size(); i++) { + Boundsheet bound = (Boundsheet) boundsheets.get(i); + int position = bound.getRecordIndex(); + insertLoc = Math.min(insertLoc, position); + streamer.removeRecord((XLSRecord) boundsheets.get(i)); + } + // enter the boundsheet records back in the streamer in + // correct order + for (int i = 0; i < boundsheets.size(); i++) { + Boundsheet bound = (Boundsheet) boundsheets.get(i); + streamer.addRecordAt(bound, insertLoc + i); + } + } + + /** add a deserialized boundsheet to this workbook. + * + * @param bound new (copied) sheet + * @param newSheetName new sheetname + * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * + * @boolean SSTPopulatedBoundsheet - the boundsheet has all of it's sst data saved off in LabelSST records. + * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. + * Do not use this if the data already exists in the SST, you are just causing bloat! + */ + public void addBoundsheet(Boundsheet bound, String origSheetName, String newSheetName, String origWorkBookName, boolean SSTPopulatedBoundsheet) { + bound.streamer = streamer; + boolean old_allowdupes = this.isSharedupes(); + this.setDupeStringMode(ALLOWDUPES); + + bound.mc.clear(); + bound.setWorkBook(this); + + // Check if sheetname already exists! + try { + while (this.getWorkSheetByName(newSheetName) != null) + newSheetName = newSheetName + "Copy"; + } catch (WorkSheetNotFoundException we) { + ; + /* good !!! */ } + bound.setSheetName(newSheetName); + + // get a hold of the lbplypos number that we will need for + // the new boundsheet + int recvecOffset = streamer.getRecVecSize() - 1; + XLSRecord x = null; + if (lastbound != null) { + try { // lastbound must be reset because other operations could + // alter + lastbound = this + .getWorkSheetByNumber(this.getNumWorkSheets() - 1); + x = (XLSRecord) lastbound.getSheetRecs() + .get(lastbound.getSheetRecs().size() - 1); + } catch (Exception e) { + ; + } + } else if (countryRec != null) { + x = (XLSRecord) streamer.getRecordAt(countryRec.getRecordIndex()); + } else + x = this.lasteof; + // last record is a junkrec. We are going to move that down + // and put in the new BOF here + // TODO: recvecOffset position when no sheets?????? + if (x.getOpcode() != EOF) { + recvecOffset -= 1; + } + int newloc = x.offset; + // modify the boundsheet rec for its new location/info/name + int listenerpos = -1, newoffset = -1; + if (lastbound != null) { + listenerpos = lastbound.getRecordIndex(); + newoffset = lastbound.offset + lastbound.getLength() + 4; + // offset + reclen + headerlen + } else if (x != null) { + listenerpos = x.getRecordIndex() - 1; // account for +1 below + newoffset = x.offset + x.getLength() + 4; + } else { + listenerpos = recvecOffset - 1; + newoffset = streamer.getRecordAt(recvecOffset).getLength() + 4; + } + + // put the serialized recs from localrecs into the normal + // SheetRecs + bound.setLocalRecs(new FastAddVector()); // reset localrecs + List newrecs = bound.getSheetRecs(); + Bof newbof = (Bof) newrecs.get(0); + + newloc += newbof.getLength() + 4; + newbof.setOffset(newloc); + bound.setBOF(newbof); + this.addRecord(newbof, false); + + recvecOffset += 1; // move it past that last Eof + newoffset = newloc + newbof.getLength() + 4; + lastbound = bound; + // insert the actual boundsheet record into the recvec + streamer.addRecordAt(bound, listenerpos + 1); + + this.addRecord(bound, false); + + // modify the TabID record to reflect new sheet + tabs.addNewRecord(); + recvecOffset = newbof.getRecordIndex(); + + int tout = 0; + copying = true; + + // Add an externsheet ref for the new sheet + if (this.myexternsheet == null) + this.addExternsheet(); + try { + int sheetref = this.getNumWorkSheets() - 1; + myexternsheet.insertLocation(sheetref, sheetref); + } catch (Exception e) { + Logger.logWarn("Adding new sheetRef failed in addBoundsheet()" + + e.toString()); + } + + // update the chart references + add to wb + List chts = bound.getCharts(); + for (int i = 0; i < chts.size(); i++) { + Chart chart = (Chart) chts.get(i); // obviously algorithm has + // changed and chart is NOT + // removed :) [discovered by + // Shigeo/Infoteria/formatbroken273193.sce] + // // 20080702 KSC: since it's + // removed, don't inc index + chart.updateSheetRefs(bound.getSheetName(), origWorkBookName); + this.charts.add(chart); + } + + bound.lastObjId = 1; // see if resetting obj id helps in file open + // errors; if so, must reset Note obj id's as + // well ... + + /********** This loop handles Boundsheet records contained in the sheet level streamer, that is, not the valrecs *****/ + int numrecs = newrecs.size(); + for (int z = 1; z < numrecs; z++) { + XLSRecord xl = (XLSRecord) newrecs.get(z); + this.addRecord(xl, false); + if (DEBUGLEVEL > 5) + try { + Logger.logInfo("Copying: " + xl.toString() + ":" + + String.valueOf(newoffset) + ":" + xl.getLength()); + } catch (Exception e) { + ; + } + if (xl instanceof Codename) { + Codename secretagent = (Codename) xl; // lol -nr + secretagent.setName(newSheetName); + } else if (xl instanceof Name) { + // Name records specify data ranges -- update to point to + // new sheet + Name n = (Name) xl; + int refnum = myexternsheet.getcXTI(); + n.setExternsheetRef(refnum); + } else if (xl instanceof Cf) { // must check Conditional Format + // formula refs and handle any + // external references + try { + updateFormulaPtgRefs(((Cf) xl) + .getFormula1(), origSheetName, newSheetName, origWorkBookName); + // NOTE: FORMULA2 can be null -- TODO: should check here + updateFormulaPtgRefs(((Cf) xl) + .getFormula2(), origSheetName, newSheetName, origWorkBookName); + } catch (Exception e) {} + } else if (xl.getOpcode() == OBJ) { + ((Obj) xl).setObjId(bound.lastObjId++); + } else if (xl.getOpcode() == MSODRAWING + || (xl.getOpcode() == CONTINUE + && ((Continue) xl).maskedMso != null)) { // 20100510 + // KSC: + // handle + // masked + // mso's + MSODrawing mso; + if (xl.getOpcode() == MSODRAWING) + mso = (MSODrawing) xl; + else + mso = ((Continue) xl).maskedMso; + if (msodg == null) { + this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup + .getPrototype()); + msodg.initNewMSODrawingGroup(); // generate and add required + // records for drawing + // records + msodg.addMsodrawingrec(mso); // only add when msodg is null + // b/c otherwise it's added + // via the addRecord + // statement above + } + if (mso.getImageIndex() > 0) { // add image bytes as well, if + // any + ImageHandle im = bound + .getImageByMsoIndex(mso.getImageIndex()); + int idx = msodg.addImage(im.getImageBytes(), im + .getImageType(), false); + bound.imageMap.put(im, Integer.valueOf(idx)); // 20100518 + // KSC: + // makes + // more + // sense? + // im.getImageIndex())); + // // add + // new image + // to map + // and link + // to actual + // imageIndex + // - moved + // from + // above + if (idx != mso.getImageIndex()) + mso.updateImageIndex(idx); + } + mso.setSPID(this.lastSPID); + msodg.setSpidMax(++this.lastSPID); + // resets drawing id's - necessarily correct? + // msodg.dirtyflag= true; // flag to reset SPIDs on write + } + xl.setOffset(newoffset); + tout += xl.getLength(); + newoffset += xl.getLength(); + } + if (msodg != null) {// Moved from above so don't udpate at every mso + // addition + // necessary? all mso sub-records on the sheet should have + // stayed the same ...this.updateMsodrawingHeaderRec(bound); + msodg.updateRecord(); + } + /*************** END handling of boundsheet streamer records *************************/ + + /*************** HANDLE Formats + PtgRefs in Cell Records ****************************/ + updateTransferedCellReferences(bound, origSheetName, origWorkBookName); + + // associate the records in the sheet + this.setSharedupes(old_allowdupes); + + if (SSTPopulatedBoundsheet) { + // bring over the sst + Sst sst = this.getSharedStringTable(); + BiffRec[] b = bound.getCells(); + for (int i = 0; i < b.length; i++) { + b[i].setWorkBook(this); + if (b[i].getOpcode() == XLSConstants.LABELSST) { + Labelsst s = (Labelsst) b[i]; + s.insertUnsharedString(sst); + } + } + } + + if (this.getNumWorkSheets() > 1) + bound.setSelected(false); + else + bound.setSelected(true); + + if (DEBUGLEVEL > 5) + Logger.logInfo("changesize for new boundsheet: " + + bound.getSheetName() + ": " + tout); + copying = false; + } - /** get a handle to the factory*/ - public WorkBookFactory getFactory() { - return this.factory; - } - - /** - * Copies a complete boundsheet within the workbook - * - * If a name exists that refers directly to this sheet then duplicate it, otherwise workbook scoped names are - * not copied - */ - public void copyWorkSheet(String SourceSheetName, String NewSheetName) - throws Exception { - Boundsheet origSheet = null; - origSheet = this.getWorkSheetByName(SourceSheetName); - List chts = origSheet.getCharts(); // 20080630 KSC: Added - for(int i=0;i0) { - if(nmx[nmx.length-1].getSheet()!=null) { - namepos = nmx[nmx.length-1].getRecordIndex(); - }else { - namepos = getExternSheet(true).getRecordIndex() + nmx.length; - } - } else { - namepos = getExternSheet(true).getRecordIndex(); - } - namepos++; - getStreamer().addRecordToBookStreamerAt(n, namepos); - addRecord(n,false); - } - - - /** Copies an existing Chart to another WorkSheet - * - * @param chartname - * @param sheetname - */ - public void copyChartToSheet(String chartname, String sheetname) - throws ChartNotFoundException, WorkSheetNotFoundException{ - Chart ct = this.getChart(chartname); - Boundsheet sht = this.getWorkSheetByName(sheetname); - byte[] bt = ct.getChartBytes(); - sht.addChart(bt, chartname, ct.getCoords()); - } - - /** Inserts a serialized boundsheet chart into the workboook - */ - public Chart addChart(Chart destChart, String NewChartName, Boundsheet boundsht) { - destChart.setWorkBook(this); - destChart.setSheet(boundsht); - List recs = destChart.getXLSrecs(); - for(int x=0;x0) { - mso.makeNonHeader(); - hdr.setNumShapes(hdr.getNumShapes()+1); - } - } else if (hdr==null) { - mso.setIsHeader(); - hdr= mso; - } - this.updateMsodrawingHeaderRec(sht); - msodg.dirtyflag= true; // flag to reset SPIDs on write - msodg.setSpidMax(++lastSPID); - msodg.updateRecord(); - } - - /** JM - - * Add the requisite records in the book streamer for the chart. \ - * Supbook, externsheet & msodrawinggroup - * - * I think this is due to the fact that the referenced series are usually stored - * in the fashon 'Sheet1!A4:B6' The sheet1 reference requires a supbook, though the - * reference is internal. - */ - public void addPreChart() { - this.addExternsheet(); - if (msodg==null) { - this.setMSODrawingGroup((MSODrawingGroup)MSODrawingGroup.getPrototype()); - msodg.initNewMSODrawingGroup(); // generate and add required records for drawing records - } - - } - - /** - * remove an existing chart from the workbook - * NOTE: STILL EXPERIMENTAL TESTS OK IN BASIC CIRCUMSTANCES BUT MUST BE TESTED FURTHER - */ - public void deleteChart(String chartname, Boundsheet sheet) - throws ChartNotFoundException { - Chart chart= this.getChart(chartname); - // TODO: Update Dimensions record?? - List recs = chart.getXLSrecs(); - // first rec SHOULD BE MsoDrawing!!! - try { - MSODrawing rec= (MSODrawing) recs.get(0); - msodg.removeMsodrawingrec(rec, sheet, true); // also remove associated Obj record - } catch (Exception e) { - Logger.logErr("deleteChart: expected Msodrawing record"); - } - /* shouldn't be necessary to remove chart recs as they are separated upon init of workbook and reassebmbled upon write*/ - this.removeChart(chartname); - } - - /** - * Inserts a serialized boundsheet into the workboook. - * - * @param inbytes original sheet bytes - * @param NewSheetName new Sheet Name - * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * - * @throws ClassNotFoundException - * @throws IOException - * @boolean SSTPopulatedBoundsheet - a boundsheet that has all of it's sst data saved off in LabelSST records. - * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. - * Do not use this if the data already exists in the SST, you are just causing bloat! - */ - public void addBoundsheet(byte[] inbytes, String origSheetName, String NewSheetName, String origWorkBookName, boolean SSTPopulatedBoundsheet) throws IOException, ClassNotFoundException { - Boundsheet destSheet = null; - ByteArrayInputStream bais = new ByteArrayInputStream(inbytes); - BufferedInputStream bufstr = new BufferedInputStream(bais); - ObjectInputStream o = new ObjectInputStream(bufstr); - destSheet = (Boundsheet) o.readObject(); - - if (destSheet != null) { - this.addBoundsheet(destSheet, origSheetName, NewSheetName, origWorkBookName, SSTPopulatedBoundsheet); - } - } - + /** + * traverses all rows and their associated cells in the newly transfered sheet, + * ensuring formula/cell references and format references are correctly transfered + * into the current workbook + * + * @param bound source sheet + */ + private void updateTransferedCellReferences(Boundsheet bound, String origSheetName, String origWorkBookName) { + HashMap localFonts = (HashMap) this.getFontRecsAsXML(); + List boundFonts = bound.getTransferFonts(); // ALL fonts in the source + // workbook + HashMap localXfs = (HashMap) this.getXfrecsAsString(); + List boundXfs = bound.getTransferXfs(); + // Set the workbook on all the cells + Row[] rows = bound.getRows(); + for (int i = 0; i < rows.length; i++) { + rows[i].setWorkBook(this); + if (rows[i].getIxfe() != this.getDefaultIxfe()) + transferFormatRecs(rows[i], localFonts, boundFonts, localXfs, boundXfs); // 20080709 + // KSC: + // handle + // default + // ixfe + // for + // row + Iterator rowcells = rows[i].getCells().iterator(); + Mulblank aMul = null; + short c = 0; + while (rowcells.hasNext()) { + BiffRec b = (BiffRec) rowcells.next(); + if (b.getOpcode() == MULBLANK) { + if (aMul == b) + c++; + else { + aMul = (Mulblank) b; + c = (short) aMul.getColFirst(); + } + aMul.setCurrentCell(c); + } + b.setWorkBook(this); // Moved to before updateFormulaPtgRefs + // [BugTracker 1434] + if (b instanceof Formula) { // Examine Ptg Refs to handle + // external sheet references not + // contained in this workbook + updateFormulaPtgRefs((Formula) b, origSheetName, bound + .getSheetName(), origWorkBookName); + if (((Formula) b).shared != null) + ((Formula) b).shared.setWorkBook(this); - /** change the tab order of a boundsheet - */ - public void changeWorkSheetOrder(Boundsheet bs, int idx) - { - // reorder the sheet vector - if (idx >= 0 && idx < boundsheets.size()) { - boundsheets.remove(bs); - boundsheets.add(idx,bs); - for (int x = 0; x < boundsheets.size(); x++) { - Boundsheet bs1 = (Boundsheet) boundsheets.get(x); - boolean udpatewin1 = bs1.selected(); - if (udpatewin1) - bs1.setSelected(true); - } - } - - int insertLoc = Integer.MAX_VALUE; - //remove the existing boundsheet records in the streamer - for(int i=0;i 5)try{Logger.logInfo("Copying: "+ xl.toString()+ ":"+ String.valueOf(newoffset)+ ":"+ xl.getLength());}catch(Exception e){;} - if (xl instanceof Codename){ - Codename secretagent = (Codename) xl; // lol -nr - secretagent.setName(newSheetName); - }else if (xl instanceof Name) { - // Name records specify data ranges -- update to point to new sheet - Name n = (Name) xl; - int refnum = myexternsheet.getcXTI(); - n.setExternsheetRef(refnum); - } else if (xl instanceof Cf) { // must check Conditional Format formula refs and handle any external references - try { - updateFormulaPtgRefs(((Cf) xl).getFormula1(), origSheetName, newSheetName, origWorkBookName); - // NOTE: FORMULA2 can be null -- TODO: should check here - updateFormulaPtgRefs(((Cf) xl).getFormula2(), origSheetName, newSheetName, origWorkBookName); - } catch (Exception e) {} - } else if (xl.getOpcode()==OBJ) { - ((Obj)xl).setObjId(bound.lastObjId++); - }else if (xl.getOpcode()==MSODRAWING || (xl.getOpcode()==CONTINUE && ((Continue)xl).maskedMso!=null)) { // 20100510 KSC: handle masked mso's - MSODrawing mso; - if (xl.getOpcode()==MSODRAWING) - mso= (MSODrawing) xl; - else - mso= ((Continue) xl).maskedMso; - if(msodg==null) { - this.setMSODrawingGroup((MSODrawingGroup)MSODrawingGroup.getPrototype()); - msodg.initNewMSODrawingGroup(); // generate and add required records for drawing records - msodg.addMsodrawingrec(mso); // only add when msodg is null b/c otherwise it's added via the addRecord statement above - } - if (mso.getImageIndex()>0) { // add image bytes as well, if any - ImageHandle im= bound.getImageByMsoIndex(mso.getImageIndex()); - int idx= msodg.addImage(im.getImageBytes(), im.getImageType(), false); - bound.imageMap.put(im, Integer.valueOf(idx)); // 20100518 KSC: makes more sense? im.getImageIndex())); // add new image to map and link to actual imageIndex - moved from above - if (idx!=mso.getImageIndex()) - mso.updateImageIndex(idx); - } - mso.setSPID(this.lastSPID); - msodg.setSpidMax(++this.lastSPID); - // resets drawing id's - necessarily correct? msodg.dirtyflag= true; // flag to reset SPIDs on write + } + // 20080226 KSC: transfer format, fonts and xf here instead + // of populateWorkbookWithRemoteData() + transferFormatRecs(b, localFonts, boundFonts, localXfs, boundXfs); } - xl.setOffset(newoffset); - tout += xl.getLength(); - newoffset += xl.getLength(); - } - if (msodg!=null) {// Moved from above so don't udpate at every mso addition - // necessary? all mso sub-records on the sheet should have stayed the same ...this.updateMsodrawingHeaderRec(bound); - msodg.updateRecord(); - } - /*************** END handling of boundsheet streamer records *************************/ - - /*************** HANDLE Formats + PtgRefs in Cell Records ****************************/ - updateTransferedCellReferences(bound, origSheetName, origWorkBookName); - - // associate the records in the sheet - this.setSharedupes(old_allowdupes); - - if (SSTPopulatedBoundsheet) { - // bring over the sst - Sst sst = this.getSharedStringTable(); - BiffRec[] b = bound.getCells(); - for (int i=0;i1) - bound.setSelected(false); - else - bound.setSelected(true); - - if (DEBUGLEVEL > 5)Logger.logInfo("changesize for new boundsheet: " + bound.getSheetName()+ ": " + tout); - copying = false; - } - - /** - * traverses all rows and their associated cells in the newly transfered sheet, - * ensuring formula/cell references and format references are correctly transfered - * into the current workbook - * - * @param bound source sheet - */ - private void updateTransferedCellReferences(Boundsheet bound, String origSheetName, String origWorkBookName) { - HashMap localFonts = (HashMap)this.getFontRecsAsXML(); - List boundFonts = (List)bound.getTransferFonts(); // ALL fonts in the source workbook - HashMap localXfs = (HashMap)this.getXfrecsAsString(); - List boundXfs= bound.getTransferXfs(); - // Set the workbook on all the cells - Row[] rows = bound.getRows(); - for (int i=0;i3) { - fid= bound.translateFontIndex(fid, localFonts); - fontx.setIfnt(fid); - } - } - } - } - /** - * examine all Ptg's referenced by this formula, looking for hanging or missing sheet references - * if found, sets sheet reference to the current sheet (TODO: a better way?) - * - * @param f Formula Rec - */ + } + // 20080226 KSC: handle xf's for columns + for (Colinfo co : bound.getColinfos()) { + transferFormatRecs(co, localFonts, boundFonts, localXfs, boundXfs); + } + List c = bound.getCharts(); + for (int i = 0; i < c.size(); i++) { + Chart cht = (Chart) c.get(i); + ArrayList fontrefs = cht.getFontxRecs(); + for (int x = 0; x < fontrefs.size(); x++) { + Fontx fontx = (Fontx) fontrefs.get(x); + int fid = fontx.getIfnt(); + if (fid > 3) { + fid = bound.translateFontIndex(fid, localFonts); + fontx.setIfnt(fid); + } + } + } + } + + /** + * examine all Ptg's referenced by this formula, looking for hanging or missing sheet references + * if found, sets sheet reference to the current sheet (TODO: a better way?) + * + * @param f Formula Rec + */ private void updateFormulaPtgRefs(Formula f, String origSheetName, String newSheetName, String origWorkBookName) { try { - if (f==null) return; // 20100222 KSC + if (f == null) + return; // 20100222 KSC f.populateExpression(); - Ptg[] p= f.getCellRangePtgs(); - for (int k= 0; k < p.length; k++) { + Ptg[] p = f.getCellRangePtgs(); + for (int k = 0; k < p.length; k++) { if (p[k] instanceof PtgRef) { - PtgRef ptg= (PtgRef) p[k]; - try { - if (!(ptg instanceof PtgArea3d) || - ((PtgArea3d) ptg).getFirstSheet().equals(((PtgArea3d) ptg).getLastSheet())){ - String sheetName= ptg.getSheetName(); - if (sheetName.equals(origSheetName)) - ptg.setSheetName(newSheetName); - ptg.addToRefTracker(); -/* changed to use above. don't understand this: - if (!sheetName.equals(origSheetName)) { - this.getWorkSheetByName(ptg.getSheetName()); - ptg.setSheetName(newSheetName); - } else - ptg.setSheetName(newSheetName); -*/ - } else { // uncommon case of two sheet range - PtgArea3d pref= (PtgArea3d) ptg; -// this.getWorkSheetByName(pref.getFirstPtg().getSheetName()); -// don't understand this this.getWorkSheetByName(pref.getLastPtg().getSheetName()); - ptg.setLocation(ptg.toString()); // reset ixti if nec. - } + PtgRef ptg = (PtgRef) p[k]; + try { + if (!(ptg instanceof PtgArea3d) || ((PtgArea3d) ptg) + .getFirstSheet() + .equals(((PtgArea3d) ptg).getLastSheet())) { + String sheetName = ptg.getSheetName(); + if (sheetName.equals(origSheetName)) + ptg.setSheetName(newSheetName); + ptg.addToRefTracker(); + /* + * changed to use above. don't understand this: + * if (!sheetName.equals(origSheetName)) { + * this.getWorkSheetByName(ptg.getSheetName()); + * ptg.setSheetName(newSheetName); + * } else + * ptg.setSheetName(newSheetName); + */ + } else { // uncommon case of two sheet range + PtgArea3d pref = (PtgArea3d) ptg; + // this.getWorkSheetByName(pref.getFirstPtg().getSheetName()); + // don't understand this + // this.getWorkSheetByName(pref.getLastPtg().getSheetName()); + ptg.setLocation(ptg.toString()); // reset ixti if + // nec. + } } catch (WorkSheetNotFoundException we) { - Logger.logWarn("External Reference encountered upon updating formula references: Worksheet Reference Found: " + ptg.getSheetName()); + Logger.logWarn("External Reference encountered upon updating formula references: Worksheet Reference Found: " + + ptg.getSheetName()); ptg.setExternalReference(origWorkBookName); } } else if (p[k] instanceof PtgExp) { - PtgExp ptgexp= (PtgExp) p[k]; + PtgExp ptgexp = (PtgExp) p[k]; try { - Ptg[] pe= ptgexp.getConvertedExpression(); // will fail if ShrFmla hasn't been input yet - for (int j= 0; j < pe.length; j++) { - if (pe[j] instanceof PtgRef) { - PtgRef ptg= (PtgRef) pe[j]; - try { - if (ptg instanceof PtgArea3d) { // PtgRef3d, etc. - this.getWorkSheetByName(ptg.getSheetName()); - ptg.setLocation(ptg.toString()); // reset ixti if nec. - } - // otherwise, we're good - } catch (WorkSheetNotFoundException we) { - Logger.logWarn("External References Not Supported: UpdateFormulaReferences: External Worksheet Reference Found: " + ptg.getSheetName()); - ptg.setExternalReference(origWorkBookName); - } + Ptg[] pe = ptgexp.getConvertedExpression(); // will fail + // if + // ShrFmla + // hasn't + // been + // input yet + for (int j = 0; j < pe.length; j++) { + if (pe[j] instanceof PtgRef) { + PtgRef ptg = (PtgRef) pe[j]; + try { + if (ptg instanceof PtgArea3d) { // PtgRef3d, + // etc. + this.getWorkSheetByName(ptg + .getSheetName()); + ptg.setLocation(ptg.toString()); // reset + // ixti + // if + // nec. + } + // otherwise, we're good + } catch (WorkSheetNotFoundException we) { + Logger.logWarn("External References Not Supported: UpdateFormulaReferences: External Worksheet Reference Found: " + + ptg.getSheetName()); + ptg.setExternalReference(origWorkBookName); + } + } } - } } catch (Exception e) { - //if links to "main" ShrFmla, won't be set yet and will give exception - see Shrfmla WorkBook.addRecord - } + // if links to "main" ShrFmla, won't be set yet and will + // give exception - see Shrfmla WorkBook.addRecord + } } } } catch (Exception e) { - Logger.logErr("WorkBook.updateFormulaRefs: error parsing expression: " + e); + Logger.logErr("WorkBook.updateFormulaRefs: error parsing expression: " + + e); } } - + /** * given a record in an previously external workbook, ensure that xf and font records * are correctly input into the current workbook and that the pointers are correctly updated @@ -2638,14 +3032,15 @@ private void updateFormulaPtgRefs(Formula f, String origSheetName, String newShe * @param localXfs HashMap of string version of all xfs, xf nums in current workbook * @param boundXfs List of string version of all xfs, xf nums in external workbook */ - private void transferFormatRecs(BiffRec b, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { - int oldXfNum= b.getIxfe(); - int localNum = this.transferFormatRecs(oldXfNum, localFonts, boundFonts, localXfs, boundXfs); - if(localNum != -1) - b.setIxfe(localNum); - } - - /** given a record in an previously external workbook, ensure that xf and font records + private void transferFormatRecs(BiffRec b, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { + int oldXfNum = b.getIxfe(); + int localNum = this + .transferFormatRecs(oldXfNum, localFonts, boundFonts, localXfs, boundXfs); + if (localNum != -1) + b.setIxfe(localNum); + } + + /** given a record in an previously external workbook, ensure that xf and font records * are correctly input into the current workbook and that the pointers are correctly updated * * @param b BiffRec @@ -2654,464 +3049,513 @@ private void transferFormatRecs(BiffRec b, HashMap localFonts, * @param localXfs HashMap of string version of all xfs, xf nums in current workbook * @param boundXfs List of string version of all xfs, xf nums in external workbook */ - private int transferFormatRecs(int oldXfNum, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { - int localNum = -1; - if (boundXfs.size()>oldXfNum) {// if haven't populatedForTransfer i.e. haven't opted to transfer formats ... - Xf origxf= (Xf) boundXfs.get(oldXfNum); // clone xf so modifcations don't affect original - if (origxf!=null) { - /** FONT **/ - // must handle font first in order to create xf below - // see if referenced xf + fonts are already in workbook; if not, add - int localfNum; - // check to see if the font needs to be added - int fnum= origxf.getIfnt(); - if (fnum>3) fnum--; - Font thisFont = (Font)boundFonts.get(fnum); - String xmlFont = "<" + thisFont.getXML() + "/>"; - Object fontNum = localFonts.get(xmlFont); - if (fontNum!=null) { // then get the fontnum in this book - localfNum = ((Integer)fontNum).intValue(); - }else { // it's a new font for this workbook, add it in - localfNum= this.insertFont(thisFont)+1; - localFonts.put(xmlFont, Integer.valueOf(localfNum)); - } - - /** XF **/ - Xf localxf= FormatHandle.cloneXf(origxf, origxf.getFont(), this); // clone xf so modifcations don't affect original - // input "local" versions of format and font - - /** FORMAT **/ - Format fmt= origxf.getFormat(); // number format - is null if format is general ... - if (fmt!=null) // add if necessary - localxf.setFormatPattern(fmt.getFormat()); // adds new format pattern if not found - localxf.setFont(localfNum); - - // now check out to see if xf needs to be added - String xmlxf= localxf.toString(); - Object xfNum = localXfs.get(xmlxf); - if (xfNum==null) { // insert it into the book - localNum = this.insertXf(localxf); - localXfs.put(xmlxf,Integer.valueOf(localNum)); - } else // already exists in the destination - localNum = ((Integer)xfNum).intValue(); - - } - } - return localNum; - } - - public void setStringEncodingMode(int mode) { - this.getSharedStringTable().setStringEncodingMode(mode); - } - - public void setDupeStringMode(int mode) { - if (mode == ALLOWDUPES) - this.setSharedupes(false); - else if (mode == SHAREDUPES) - this.setSharedupes(true); - } - - - /** Returns a Chart Handle - @return ChartHandle a Chart in the WorkBook - */ - public Chart getChart(String chartname) - throws ChartNotFoundException{ - AbstractList cv = this.getChartVect(); - Chart cht = null; - // Get by MSODG Drawing Name - for(int x=0;xA Pivot Cache identifies the data used in a Pivot Table - *
NOTE: only SHEET cache sources are supported at this time - * @param ref String reference: either reference or named range - * @param sheetName String sheetname - * @param cacheid if > 0, the desired cacheid (useful only in OOXML parsing) - * @return int cacheid - */ - public int addPivotStream(String ref, String sheetName, int sid) { - //in wb substream, DIRECTLY AFTER STYLE records: - // STYLE/STYLEEX [TableStyle TableStyleElement] [Palette] [ClrtClient] - if (sid <0) - sid= 0; // initial cache id if none already present - List records= this.getStreamer().records; - int z= -1; - for (int i= records.size()-1; i > 0 && z==-1; i--) { - int opcode= ((BiffRec) records.get(i)).getOpcode(); - if (opcode==SXADDL) { // find last cache id and increment -/* while (i > 0 && opcode!=SXSTREAMID) - opcode= ((BiffRec) records.get(i--)).getOpcode(); - if (opcode==SXSTREAMID) { - sid= ((SxStreamID) records.get(i+1)).getStreamID() + 1; - }*/ - z= i+1; - } else if (opcode==4188) // ClrtClient - z= i+1; - else if (opcode==PALETTE) - z= i+1; - else if (opcode==2192) // TableStyleElement - z= i+1; - else if (opcode==2194) // StyleEx - z= i+1; - else if (opcode==STYLE) // Style - z= i+1; - } - - TableStyles tx= (TableStyles) TableStyles.getPrototype(); // see if this is really necessary ... - this.getStreamer().addRecordAt(tx, z++); - SxStreamID sxid= (SxStreamID) SxStreamID.getPrototype(); - this.getStreamer().addRecordAt(sxid, z++); - this.ptstream.add(sxid); // Pivot Cache - - sxid.setStreamID(sid); // cache id - this.getStreamer().records.addAll(z, sxid.addInitialRecords(this, ref, sheetName)); - - return sid; - } - - /** - * adds the Pivot Cache Directory Storage +Stream records necessary to - * define the pivot cache (==pivot table data) for pivot table(s) - *
NOTE: at this time only 1 pivot cache is supported - * - * @param ref Cell Range which identifies pivot table data range - * @param wbh - * @param sId Stream or cachid Id -- links back to SxStream set of records - */ - public void addPivotCache(String ref, WorkBookHandle wbh, int sId) { - if (ptcache==null) { - ptcache= new PivotCache(); - ptcache.createPivotCache(factory.myLEO.getDirectoryArray(), wbh, ref, sId); - } - } - - /** - * returns the start of the stream defining the desired pivot cache - * @param cacheid - * @return - */ - public SxStreamID getPivotStream(int cacheid) { -//int z= 0; - for (int i= 0; i < ptstream.size(); i++) { - int sid= ((SxStreamID) ptstream.get(i)).getStreamID(); - if (sid==cacheid) -// if (z++==cacheid) + private int transferFormatRecs(int oldXfNum, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { + int localNum = -1; + if (boundXfs.size() > oldXfNum) {// if haven't populatedForTransfer i.e. + // haven't opted to transfer formats + // ... + Xf origxf = (Xf) boundXfs.get(oldXfNum); // clone xf so modifcations + // don't affect original + if (origxf != null) { + /** FONT **/ + // must handle font first in order to create xf below + // see if referenced xf + fonts are already in workbook; if + // not, add + int localfNum; + // check to see if the font needs to be added + int fnum = origxf.getIfnt(); + if (fnum > 3) + fnum--; + Font thisFont = (Font) boundFonts.get(fnum); + String xmlFont = "<" + thisFont.getXML() + "/>"; + Object fontNum = localFonts.get(xmlFont); + if (fontNum != null) { // then get the fontnum in this book + localfNum = ((Integer) fontNum).intValue(); + } else { // it's a new font for this workbook, add it in + localfNum = this.insertFont(thisFont) + 1; + localFonts.put(xmlFont, Integer.valueOf(localfNum)); + } + + /** XF **/ + Xf localxf = FormatHandle + .cloneXf(origxf, origxf.getFont(), this); // clone xf so + // modifcations + // don't + // affect + // original + // input "local" versions of format and font + + /** FORMAT **/ + Format fmt = origxf.getFormat(); // number format - is null if + // format is general ... + if (fmt != null) // add if necessary + localxf.setFormatPattern(fmt.getFormat()); // adds new + // format + // pattern if + // not found + localxf.setFont(localfNum); + + // now check out to see if xf needs to be added + String xmlxf = localxf.toString(); + Object xfNum = localXfs.get(xmlxf); + if (xfNum == null) { // insert it into the book + localNum = this.insertXf(localxf); + localXfs.put(xmlxf, Integer.valueOf(localNum)); + } else // already exists in the destination + localNum = ((Integer) xfNum).intValue(); + + } + } + return localNum; + } + + public void setStringEncodingMode(int mode) { + this.getSharedStringTable().setStringEncodingMode(mode); + } + + public void setDupeStringMode(int mode) { + if (mode == ALLOWDUPES) + this.setSharedupes(false); + else if (mode == SHAREDUPES) + this.setSharedupes(true); + } + + /** Returns a Chart Handle + @return ChartHandle a Chart in the WorkBook + */ + public Chart getChart(String chartname) throws ChartNotFoundException { + AbstractList cv = this.getChartVect(); + Chart cht = null; + // Get by MSODG Drawing Name + for (int x = 0; x < cv.size(); x++) { + cht = (Chart) cv.get(x); + MSODrawing titlemso = cht.getMsodrawobj(); + if (titlemso != null) { + String mson = titlemso.getName(); // shapeName; + if (mson.equalsIgnoreCase(chartname)) + return cht; + } + } + boolean untitled = chartname.equals("[Untitled]"); + // Try to get by title + for (int x = 0; x < cv.size(); x++) { + cht = (Chart) cv.get(x); + String cname = cht.getTitle(); + if (cname.equalsIgnoreCase(chartname)) + return cht; + else if (untitled && cname.equals("")) + return cht; + } + throw new ChartNotFoundException(chartname); + } + + /** + * removes the desired chart from the list of charts + * @param chartname + * @throws ChartNotFoundException + */ + public void removeChart(String chartname) throws ChartNotFoundException { + AbstractList cv = this.getChartVect(); + Chart cht = null; + for (int x = 0; x < cv.size(); x++) { + cht = (Chart) cv.get(x); + if (cht.getTitle().equalsIgnoreCase(chartname)) { + cv.remove(x); + return; + } + } + throw new ChartNotFoundException(chartname); + } + + /** + * NOT 100% IMPLEMENTE YET + * creates the initial records for a Pivot Cache + *
A Pivot Cache identifies the data used in a Pivot Table + *
NOTE: only SHEET cache sources are supported at this time + * @param ref String reference: either reference or named range + * @param sheetName String sheetname + * @param cacheid if > 0, the desired cacheid (useful only in OOXML parsing) + * @return int cacheid + */ + public int addPivotStream(String ref, String sheetName, int sid) { + // in wb substream, DIRECTLY AFTER STYLE records: + // STYLE/STYLEEX [TableStyle TableStyleElement] [Palette] + // [ClrtClient] + if (sid < 0) + sid = 0; // initial cache id if none already present + List records = this.getStreamer().records; + int z = -1; + for (int i = records.size() - 1; i > 0 && z == -1; i--) { + int opcode = ((BiffRec) records.get(i)).getOpcode(); + if (opcode == SXADDL) { // find last cache id and increment + /* + * while (i > 0 && opcode!=SXSTREAMID) + * opcode= ((BiffRec) records.get(i--)).getOpcode(); + * if (opcode==SXSTREAMID) { + * sid= ((SxStreamID) records.get(i+1)).getStreamID() + 1; + * } + */ + z = i + 1; + } else if (opcode == 4188) // ClrtClient + z = i + 1; + else if (opcode == PALETTE) + z = i + 1; + else if (opcode == 2192) // TableStyleElement + z = i + 1; + else if (opcode == 2194) // StyleEx + z = i + 1; + else if (opcode == STYLE) // Style + z = i + 1; + } + + TableStyles tx = (TableStyles) TableStyles.getPrototype(); // see if + // this is + // really + // necessary + // ... + this.getStreamer().addRecordAt(tx, z++); + SxStreamID sxid = (SxStreamID) SxStreamID.getPrototype(); + this.getStreamer().addRecordAt(sxid, z++); + this.ptstream.add(sxid); // Pivot Cache - + sxid.setStreamID(sid); // cache id + this.getStreamer().records + .addAll(z, sxid.addInitialRecords(this, ref, sheetName)); + + return sid; + } + + /** + * adds the Pivot Cache Directory Storage +Stream records necessary to + * define the pivot cache (==pivot table data) for pivot table(s) + *
NOTE: at this time only 1 pivot cache is supported + * + * @param ref Cell Range which identifies pivot table data range + * @param wbh + * @param sId Stream or cachid Id -- links back to SxStream set of records + */ + public void addPivotCache(String ref, WorkBookHandle wbh, int sId) { + if (ptcache == null) { + ptcache = new PivotCache(); + ptcache.createPivotCache(factory.myLEO + .getDirectoryArray(), wbh, ref, sId); + } + } + + /** + * returns the start of the stream defining the desired pivot cache + * @param cacheid + * @return + */ + public SxStreamID getPivotStream(int cacheid) { + // int z= 0; + for (int i = 0; i < ptstream.size(); i++) { + int sid = ((SxStreamID) ptstream.get(i)).getStreamID(); + if (sid == cacheid) + // if (z++==cacheid) return (SxStreamID) ptstream.get(i); - } -/* List records= this.getStreamer().records; - for (int i= 0; i < records.size(); i++) { - int opcode= ((BiffRec) records.get(i)).getOpcode(); - if (opcode==SXSTREAMID) { - int sid= ((SxStreamID) records.get(i)).getStreamID() + 1; - if (sid==cacheid) - return (SxStreamID) records.get(i); - } - }*/ - return null; - } - - /** - * @return - */ - public AbstractList getHlinklookup() { - return hlinklookup; - } - - /** - * @return - */ - public AbstractList getMergecelllookup() { - return mergecelllookup; - } - + } + /* + * List records= this.getStreamer().records; + * for (int i= 0; i < records.size(); i++) { + * int opcode= ((BiffRec) records.get(i)).getOpcode(); + * if (opcode==SXSTREAMID) { + * int sid= ((SxStreamID) records.get(i)).getStreamID() + 1; + * if (sid==cacheid) + * return (SxStreamID) records.get(i); + * } + * } + */ + return null; + } - public void addIndirectFormula(Formula f) { - indirectFormulas.add(f); - } - - /** - * Initialize the indirect functions in this workbook by calculating the formulas - * - * */ - public void initializeIndirectFormulas() { - Iterator i = indirectFormulas.iterator(); // contains all INDIRECT funcvars + params - while(i.hasNext()) { - Formula f = (Formula)i.next(); - f.calculateIndirectFunction(); - } - indirectFormulas = new ArrayList(); // clear out - } - - - /** - * Inserts an externsheet into the recvec, provided one does not yet exist. - * also calls add supBook - * - */ - public void addExternsheet(){ - if (myexternsheet == null){ - int numsheets = this.getNumWorkSheets(); - Supbook sb = (Supbook)Supbook.getPrototype(numsheets); - // put it in after the last boundsheet record - try{ - Boundsheet b = this.getWorkSheetByNumber(numsheets-1); - int loc = b.getRecordIndex() +1; - if (streamer.getRecordAt(loc).getOpcode()==COUNTRY) - loc++; - streamer.addRecordAt(sb, loc); // 20080306 KSC: do first 'cause externsheet now references global sb store - this.addRecord(sb,false); - Externsheet ex = (Externsheet)Externsheet.getPrototype(0, 0, this); - streamer.addRecordAt(ex, loc+1);// 20080306 KSC: must inc loc since now inserting after sb - this.addRecord(ex,false); - myexternsheet = ex; - }catch(WorkSheetNotFoundException e){Logger.logWarn("WorkBook.addExternSheet() locating Sheet for adding Externsheet failed: " + e);} - - } - } - - /** - * Sets the OpenXLS calculation mode for the workbook. - * @see WorkBookHandle.setFormulaCalculationMode() - * @param CalcMode - */ - public void setCalcMode(int mode) { - CalcMode = mode; - } - - /** - * Sets the calculation mode for the workbook. - * @see WorkBookHandle.setFormulaCalculationMode() - * @param CalcMode - */ - public int getCalcMode() { - return CalcMode; - } - /** - * @return Returns the xfrecs. - */ - public AbstractList getXfrecs() { - return xfrecs; - } - /** - * Return the font records - */ - public AbstractList getFontRecs() { - return fonts; - } - - /** - * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. - * These are used as a comparitor to determine if additional xf's need to be brought in or - * not and to give the new XF number if the xf exists. - * - * Changed 20080226 KSC: to use XF toString as XML is limited in format, toString is more complete - * @return map (String XfXml, Integer xfLookup) - */ - public Map getXfrecsAsString() { - Map retMap = new HashMap(); - for (int xfNum= 1; xfNum=0;i--) { - Font fnt = (Font)this.fonts.get(i); - String xml = "<" + fnt.getXML() + "/>"; - retMap.put(xml, Integer.valueOf(fnt.getIdx())); - } - return retMap; - } - /** - * @return Returns the lastbound. - */ - public Boundsheet getLastbound() { - return lastbound; - } - /** - * @param lastbound The lastbound to set. - */ - public void setLastbound(Boundsheet lastbound) { - this.lastbound = lastbound; - } - - /** add formula to book and init the ptgs - * - * @param rec - */ - public void addFormula(Formula rec) { - this.formulas.add(rec); - String shn = rec.getSheet().getSheetName()+"!"+rec.getCellAddress(); - this.formulashash.put(shn, rec); - } - - public boolean isSharedupes() { - return sharedupes; - } - - public void setSharedupes(boolean sharedupes) { - this.sharedupes = sharedupes; - } - - - /** Returns whether this book uses the 1904 date format. - * @deprecated Use {@link #getDateFormat()} instead. - */ - public boolean is1904() { - return this.dateFormat == DateConverter.DateFormat.LEGACY_1904; - } - - /** Gets the date format used by this book. */ - public DateConverter.DateFormat getDateFormat() { - return this.dateFormat; - } + * @return + */ + public AbstractList getHlinklookup() { + return hlinklookup; + } + + /** + * @return + */ + public AbstractList getMergecelllookup() { + return mergecelllookup; + } + + public void addIndirectFormula(Formula f) { + indirectFormulas.add(f); + } + + /** + * Initialize the indirect functions in this workbook by calculating the formulas + * + * */ + public void initializeIndirectFormulas() { + Iterator i = indirectFormulas.iterator(); // contains all INDIRECT + // funcvars + params + while (i.hasNext()) { + Formula f = (Formula) i.next(); + f.calculateIndirectFunction(); + } + indirectFormulas = new ArrayList(); // clear out + } + + /** + * Inserts an externsheet into the recvec, provided one does not yet exist. + * also calls add supBook + * + */ + public void addExternsheet() { + if (myexternsheet == null) { + int numsheets = this.getNumWorkSheets(); + Supbook sb = (Supbook) Supbook.getPrototype(numsheets); + // put it in after the last boundsheet record + try { + Boundsheet b = this.getWorkSheetByNumber(numsheets - 1); + int loc = b.getRecordIndex() + 1; + if (streamer.getRecordAt(loc).getOpcode() == COUNTRY) + loc++; + streamer.addRecordAt(sb, loc); // 20080306 KSC: do first 'cause + // externsheet now references + // global sb store + this.addRecord(sb, false); + Externsheet ex = (Externsheet) Externsheet + .getPrototype(0, 0, this); + streamer.addRecordAt(ex, loc + 1);// 20080306 KSC: must inc loc + // since now inserting after + // sb + this.addRecord(ex, false); + myexternsheet = ex; + } catch (WorkSheetNotFoundException e) { + Logger.logWarn("WorkBook.addExternSheet() locating Sheet for adding Externsheet failed: " + + e); + } + + } + } + + /** + * Sets the OpenXLS calculation mode for the workbook. + * @see WorkBookHandle.setFormulaCalculationMode() + * @param CalcMode + */ + public void setCalcMode(int mode) { + CalcMode = mode; + } + + /** + * Sets the calculation mode for the workbook. + * @see WorkBookHandle.setFormulaCalculationMode() + * @param CalcMode + */ + public int getCalcMode() { + return CalcMode; + } + + /** + * @return Returns the xfrecs. + */ + public AbstractList getXfrecs() { + return xfrecs; + } + + /** + * Return the font records + */ + public AbstractList getFontRecs() { + return fonts; + } + + /** + * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. + * These are used as a comparitor to determine if additional xf's need to be brought in or + * not and to give the new XF number if the xf exists. + * + * Changed 20080226 KSC: to use XF toString as XML is limited in format, toString is more complete + * @return map (String XfXml, Integer xfLookup) + */ + public Map getXfrecsAsString() { + Map retMap = new HashMap(); + for (int xfNum = 1; xfNum < this.getNumXfs(); xfNum++) { + Xf x = this.getXf(xfNum); + String xml = x.toString(); + retMap.put(xml, Integer.valueOf(xfNum)); + } + return retMap; + } + + /** + * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. + * These are used as a comparitor to determine if additional xf's need to be brought in or + * not and to give the new XF number if the xf exists. + * @return map (String XfXml, Integer xfLookup) + */ + public Map getFontRecsAsXML() { + Map retMap = new HashMap(); + for (int i = this.fonts.size() - 1; i >= 0; i--) { + Font fnt = (Font) this.fonts.get(i); + String xml = "<" + fnt.getXML() + "/>"; + retMap.put(xml, Integer.valueOf(fnt.getIdx())); + } + return retMap; + } + + /** + * @return Returns the lastbound. + */ + public Boundsheet getLastbound() { + return lastbound; + } + + /** + * @param lastbound The lastbound to set. + */ + public void setLastbound(Boundsheet lastbound) { + this.lastbound = lastbound; + } + + /** add formula to book and init the ptgs + * + * @param rec + */ + public void addFormula(Formula rec) { + this.formulas.add(rec); + String shn = rec.getSheet().getSheetName() + "!" + rec.getCellAddress(); + this.formulashash.put(shn, rec); + } + + public boolean isSharedupes() { + return sharedupes; + } + + public void setSharedupes(boolean sharedupes) { + this.sharedupes = sharedupes; + } + + /** Returns whether this book uses the 1904 date format. + * @deprecated Use {@link #getDateFormat()} instead. + */ + @Deprecated + public boolean is1904() { + return this.dateFormat == DateConverter.DateFormat.LEGACY_1904; + } + + /** Gets the date format used by this book. */ + public DateConverter.DateFormat getDateFormat() { + return this.dateFormat; + } public ReferenceTracker getRefTracker() { return refTracker; } - - - - // OOXML Additions - private boolean isExcel2007= false; + + // OOXML Additions + private boolean isExcel2007 = false; + /** * set truth of "Is Excel 2007" * true increases maximums of base storage, etc. * @param b */ - public void setIsExcel2007(boolean b) { isExcel2007= b; } + public void setIsExcel2007(boolean b) { + isExcel2007 = b; + } + /** * returns truth of "Excel 2007" format */ - public boolean getIsExcel2007() { return isExcel2007; } + public boolean getIsExcel2007() { + return isExcel2007; + } + /** * returns the workbook codename used by vba macros OOXML-specific */ - public String getCodename() { return ooxmlcodename; } - - /** - * returns true if the default language selected in Excel is one of - * the DBCS (Double-Byte Code Set) languages - *
+ public String getCodename() { + return ooxmlcodename; + } + + /** + * returns true if the default language selected in Excel is one of + * the DBCS (Double-Byte Code Set) languages + *
The languages that support DBCS include
Japanese, Chinese (Simplified), Chinese (Traditional), and Korean
   
-   Language        Country code    Countries/regions 
-   -------------------------------------------------------------
-
-   Arabic                966       (Saudi Arabia)
-   Czech                 42        (Czech Republic)
-   Danish                45        (Denmark)
-   Dutch                 31        (The Netherlands)
-   English               1         (The United States of America)
-   Farsi                 98        (Iran)
-   Finnish               358       (Finland)
-   French                33        (France)
-   German                49        (Germany)
-   Greek                 30        (Greece)
-   Hebrew                972       (Israel)
-   Hungarian             36        (Hungary)
-   Indian                91        (India)
-   Italian               39        (Italy)
-   Japanese              81        (Japan)
-   Korean                82        (Korea)
-   Norwegian             47        (Norway)
-   Polish                48        (Poland)
-   Portuguese (Brazil)   55        (Brazil)
-   Portuguese            351       (Portugal)
-   Russian               7         (Russian Federation)
-   Simplified Chinese    86        (People's Republic of China)
-   Spanish               34        (Spain)
-   Swedish               46        (Sweden)
-   Thai                  66        (Thailand)
-   Traditional Chinese   886       (Taiwan)
-   Turkish               90        (Turkey)
-   Urdu                  92        (Pakistan)
-   Vietnamese            84        (Vietnam)   
-		
- * @return boolean - */ - public boolean defaultLanguageIsDBCS() { - return (this.defaultLanguage==81 || - this.defaultLanguage==886 || - this.defaultLanguage==86 || - this.defaultLanguage==82); - // PROBLEM WITH THIS: POSSIBLE TO BE SET AS DBCS DEFAULT LANGUAGE - // BUT HAVE NON-DBCS TEXT or VISA VERSA - - /* - * In a double-byte character set, some characters require two bytes, - * while some require only one byte. - * The language driver can distinguish between these two types of characters by designating - * some characters as "lead bytes." - * A lead byte will be followed by another byte (a "tail byte") to create a - * Double-Byte Character (DBC). - * The set of lead bytes is different for each language. - * - * Lead bytes are always guaranteed to be extended characters; no 7-bit ASCII characters - * can be lead bytes. - * The tail byte may be any byte except a NULL byte. - * The end of a string is always defined as the first NULL byte in the string. - * Lead bytes are legal tail bytes; the only way to tell if a byte is acting as a - * lead byte is from the context. - */ - } + Language Country code Countries/regions + ------------------------------------------------------------- + Arabic 966 (Saudi Arabia) + Czech 42 (Czech Republic) + Danish 45 (Denmark) + Dutch 31 (The Netherlands) + English 1 (The United States of America) + Farsi 98 (Iran) + Finnish 358 (Finland) + French 33 (France) + German 49 (Germany) + Greek 30 (Greece) + Hebrew 972 (Israel) + Hungarian 36 (Hungary) + Indian 91 (India) + Italian 39 (Italy) + Japanese 81 (Japan) + Korean 82 (Korea) + Norwegian 47 (Norway) + Polish 48 (Poland) + Portuguese (Brazil) 55 (Brazil) + Portuguese 351 (Portugal) + Russian 7 (Russian Federation) + Simplified Chinese 86 (People's Republic of China) + Spanish 34 (Spain) + Swedish 46 (Sweden) + Thai 66 (Thailand) + Traditional Chinese 886 (Taiwan) + Turkish 90 (Turkey) + Urdu 92 (Pakistan) + Vietnamese 84 (Vietnam) +
+ * @return boolean + */ + public boolean defaultLanguageIsDBCS() { + return (this.defaultLanguage == 81 || this.defaultLanguage == 886 + || this.defaultLanguage == 86 || this.defaultLanguage == 82); + // PROBLEM WITH THIS: POSSIBLE TO BE SET AS DBCS DEFAULT + // LANGUAGE + // BUT HAVE NON-DBCS TEXT or VISA VERSA + + /* + * In a double-byte character set, some characters require + * two bytes, + * while some require only one byte. + * The language driver can distinguish between these two + * types of characters by designating + * some characters as "lead bytes." + * A lead byte will be followed by another byte (a + * "tail byte") to create a + * Double-Byte Character (DBC). + * The set of lead bytes is different for each language. + * + * Lead bytes are always guaranteed to be extended + * characters; no 7-bit ASCII characters + * can be lead bytes. + * The tail byte may be any byte except a NULL byte. + * The end of a string is always defined as the first NULL + * byte in the string. + * Lead bytes are legal tail bytes; the only way to tell if + * a byte is acting as a + * lead byte is from the context. + */ + } + /** * sets the workbook codename used by vba macros OOXML-specific * @param s */ - public void setCodename(String s) { ooxmlcodename= s; } // TODO: input into Codename record + public void setCodename(String s) { + ooxmlcodename = s; + } // TODO: input into Codename record /** * sets the first sheet (int) in the workbook OOXML-specific @@ -3121,10 +3565,10 @@ public boolean defaultLanguageIsDBCS() { * Mar 15, 2010 * @param f */ - public void setFirstSheet(int f) { - firstSheet= f; + public void setFirstSheet(int f) { + firstSheet = f; } - + /** * returns the first non-hidden * @@ -3134,247 +3578,265 @@ public void setFirstSheet(int f) { * @return */ public int getFirstSheet() { - try{ - if(!getWorkSheetByNumber(firstSheet).getHidden()) + try { + if (!getWorkSheetByNumber(firstSheet).getHidden()) return this.firstSheet; - }catch(Exception x){;} - + } catch (Exception x) { + ; + } + // first sheet is hidden -- fix - for(int t=0;t 0; i--) { - Boundsheet b= (Boundsheet) boundsheets.get(i); - if (b.streamer!=null) { // do separately because may call boundsheet close - b.streamer.close(); - b.streamer= null; - } - b.close(); - if (tabs!=null) - tabs.removeRecord(); - } - Object[] recs= this.getStreamer().getBiffRecords(); - boolean resetVars= (recs[0]!=null); - boundsheets.clear(); - for (int i= 0; i < formulas.size(); i++) { - Formula f= (Formula)formulas.get(i); - f.close(); - } - this.formulas.clear(); - if (this.refTracker!=null) - this.refTracker.clearCaches(); - this.formulashash.clear(); - // TODO: handle - this.indirectFormulas.clear(); - this.charts.clear(); - this.chartTemp.clear(); - if (firstBOF!=null) { - firstBOF.close(); - this.firstBOF= null; - } - if (lasteof!=null) { - lasteof.close(); - lasteof= null; - } - if (resetVars) { // just clearing out sheets instead of closing workbook - // reset lasteof for possible new insertion of sheets (if not removing workbook) ... - int i= recs.length-1; - while (i > 0 && lasteof==null) { - if (recs[i]!=null) { - if (((BiffRec)recs[i]).getOpcode()==EOF) - lasteof= (Eof) recs[i]; - } - i--; - } - } - if (lastBOF!=null) { - lastBOF.close(); - lastBOF= null; - } - if (resetVars) { // just clearing out sheets instead of closing workbook - if (((BiffRec)recs[0]).getOpcode()==BOF) {// should!! - lastBOF= (Bof) recs[0]; - firstBOF= (Bof) recs[0]; - } - } - if (lastbound!=null) { - lastbound.close(); - this.lastbound= null; - } - if (lastFormula!=null) { - lastFormula.close(); - this.lastFormula= null; - } - this.workSheets.clear(); - } - - protected void closeRecords() {} - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - closeSheets(); - - if (this.isExcel2007) { - try { - String externalDir= OOXMLAdapter.getTempDir(this.getFactory().getFileName()); - OOXMLAdapter.deleteDir(new File(externalDir)); - } catch (Exception e) { ; } - } - contHandler.close(); - contHandler= new ContinueHandler(this); - - // clear out array list references - Iterator ii= bookNameRecs.keySet().iterator(); + public ArrayList getDxfs() { + return dxfs; + } + + /** + * Returns all strings that are in the SharedStringTable for this workbook. The SST contains + * all standard string records in cells, but may not include such things as strings that are contained + * within formulas. This is useful for such things as full text indexing of workbooks + * + * @return Strings in the workbook. + */ + public String[] getAllStrings() { + ArrayList al = this.getSharedStringTable().getAllStrings(); + String[] s = new String[al.size()]; + s = (String[]) al.toArray(s); + return s; + } + + /** + * set the pivot cache pointer + * @param pc initialized pivot cache + */ + public void setPivotCache(PivotCache pc) { + ptcache = pc; + } + + /** + * return the pivot cache, if any + * @return + */ + public PivotCache getPivotCache() { + return ptcache; + } + + /** + * clear out ALL sheet object refs + */ + public void closeSheets() { + for (int i = boundsheets.size() - 1; i > 0; i--) { + Boundsheet b = (Boundsheet) boundsheets.get(i); + if (b.streamer != null) { // do separately because may call + // boundsheet close + b.streamer.close(); + b.streamer = null; + } + b.close(); + if (tabs != null) + tabs.removeRecord(); + } + Object[] recs = this.getStreamer().getBiffRecords(); + boolean resetVars = (recs[0] != null); + boundsheets.clear(); + for (int i = 0; i < formulas.size(); i++) { + Formula f = (Formula) formulas.get(i); + f.close(); + } + this.formulas.clear(); + if (this.refTracker != null) + this.refTracker.clearCaches(); + this.formulashash.clear(); + // TODO: handle + this.indirectFormulas.clear(); + this.charts.clear(); + this.chartTemp.clear(); + if (firstBOF != null) { + firstBOF.close(); + this.firstBOF = null; + } + if (lasteof != null) { + lasteof.close(); + lasteof = null; + } + if (resetVars) { // just clearing out sheets instead of closing workbook + // reset lasteof for possible new insertion of sheets (if + // not removing workbook) ... + int i = recs.length - 1; + while (i > 0 && lasteof == null) { + if (recs[i] != null) { + if (((BiffRec) recs[i]).getOpcode() == EOF) + lasteof = (Eof) recs[i]; + } + i--; + } + } + if (lastBOF != null) { + lastBOF.close(); + lastBOF = null; + } + if (resetVars) { // just clearing out sheets instead of closing workbook + if (((BiffRec) recs[0]).getOpcode() == BOF) {// should!! + lastBOF = (Bof) recs[0]; + firstBOF = (Bof) recs[0]; + } + } + if (lastbound != null) { + lastbound.close(); + this.lastbound = null; + } + if (lastFormula != null) { + lastFormula.close(); + this.lastFormula = null; + } + this.workSheets.clear(); + } + + protected void closeRecords() {} + + /** + * clear out object references in prep for closing workbook + */ + public void close() { + closeSheets(); + + if (this.isExcel2007) { + try { + String externalDir = OOXMLAdapter + .getTempDir(this.getFactory().getFileName()); + OOXMLAdapter.deleteDir(new File(externalDir)); + } catch (Exception e) { + ; + } + } + contHandler.close(); + contHandler = new ContinueHandler(this); + + // clear out array list references + Iterator ii = bookNameRecs.keySet().iterator(); while (ii.hasNext()) { - Name n= (Name) bookNameRecs.get(ii.next()); + Name n = (Name) bookNameRecs.get(ii.next()); + n.close(); + } + this.bookNameRecs.clear(); + for (int i = 0; i < xfrecs.size(); i++) { + Xf x = (Xf) xfrecs.get(i); + x.close(); + } + this.xfrecs.clear(); + this.formatCache.clear(); + this.formatlookup.clear(); + this.formats.clear(); + this.fonts.clear(); + if (this.dxfs != null) + this.dxfs.clear(); + for (int i = 0; i < msodgMerge.size(); i++) { + MSODrawingGroup m = (MSODrawingGroup) msodgMerge.get(i); + m.close(); + } + this.msodgMerge.clear(); + if (this.msodg != null) { + msodg.close(); + msodg = null; + } + // TODO: handle + this.mergecelllookup.clear(); + this.hlinklookup.clear(); + this.externalnames.clear(); + for (int i = 0; i < names.size(); i++) { + Name n = (Name) names.get(i); n.close(); } - this.bookNameRecs.clear(); - for (int i= 0; i < xfrecs.size(); i++) { - Xf x= (Xf)xfrecs.get(i); - x.close(); - } - this.xfrecs.clear(); - this.formatCache.clear(); - this.formatlookup.clear(); - this.formats.clear(); - this.fonts.clear(); - if (this.dxfs!=null) - this.dxfs.clear(); - for (int i= 0; i < msodgMerge.size(); i++) { - MSODrawingGroup m= (MSODrawingGroup) msodgMerge.get(i); - m.close(); - } - this.msodgMerge.clear(); - if (this.msodg!=null) { - msodg.close(); - msodg= null; - } - // TODO: handle - this.mergecelllookup.clear(); - this.hlinklookup.clear(); - this.externalnames.clear(); - for (int i= 0; i < names.size(); i++) { - Name n= (Name) names.get(i); - n.close(); - } - this.names.clear(); - for (int i= 0; i < indexes.size(); i++) { - Index ind= (Index) indexes.get(i); - ind.close(); - } + this.names.clear(); + for (int i = 0; i < indexes.size(); i++) { + Index ind = (Index) indexes.get(i); + ind.close(); + } this.indexes.clear(); - if (lastidx!=null) { + if (lastidx != null) { lastidx.close(); - lastidx= null; + lastidx = null; } - if (this.stringTable!=null) { + if (this.stringTable != null) { this.stringTable.close(); - this.stringTable= null; - } - - if (countryRec!=null) { - this.countryRec.close(); - this.countryRec= null; - } - if (win1!=null) { - win1.close(); - win1= null; - } - - if (this.drh!=null) { - this.drh.close(); - drh= null; - } - - // integration point for subclasses - this.closeRecords(); - - this.contHandler= new ContinueHandler(this); - if (this.currai!=null) { - this.currai.close(); - this.currai= null; - } - if (calcmoderec!=null) { - calcmoderec.close(); - calcmoderec= null; - } - this.currchart= null; - this.currdrw= null; - if (this.protector!=null) { - this.protector.close(); - this.protector= null; - } - if (this.myexternsheet!=null) { - this.myexternsheet.close(); - this.myexternsheet= null; - } - if (this.refTracker!=null) { - this.refTracker.close(); - this.refTracker= null; - } - if (tabs!=null) { - tabs.close(); - tabs= null; - } - // TODO: deal - this.factory= null; - streamer= createByteStreamer(); - if (xl2k!=null) - ((XLSRecord)xl2k).close(); - xl2k= null; - - } + this.stringTable = null; + } + + if (countryRec != null) { + this.countryRec.close(); + this.countryRec = null; + } + if (win1 != null) { + win1.close(); + win1 = null; + } + + if (this.drh != null) { + this.drh.close(); + drh = null; + } + + // integration point for subclasses + this.closeRecords(); + + this.contHandler = new ContinueHandler(this); + if (this.currai != null) { + this.currai.close(); + this.currai = null; + } + if (calcmoderec != null) { + calcmoderec.close(); + calcmoderec = null; + } + this.currchart = null; + this.currdrw = null; + if (this.protector != null) { + this.protector.close(); + this.protector = null; + } + if (this.myexternsheet != null) { + this.myexternsheet.close(); + this.myexternsheet = null; + } + if (this.refTracker != null) { + this.refTracker.close(); + this.refTracker = null; + } + if (tabs != null) { + tabs.close(); + tabs = null; + } + // TODO: deal + this.factory = null; + streamer = createByteStreamer(); + if (xl2k != null) + ((XLSRecord) xl2k).close(); + xl2k = null; + + } public Color[] getColorTable() { return colorTable; @@ -3391,5 +3853,15 @@ public int getDefaultIxfe() { public void setDefaultIxfe(int defaultIxfe) { this.defaultIxfe = defaultIxfe; } - + + public int getCalcId() { + // TODO Auto-generated method stub + return 0; + } + + public void setCalcId(int i) { + // TODO Auto-generated method stub + + } + } diff --git a/src/main/java/io/starter/formats/XLS/WorkBookFactory.java b/src/main/java/io/starter/formats/XLS/WorkBookFactory.java index 5828f9b..83192d6 100644 --- a/src/main/java/io/starter/formats/XLS/WorkBookFactory.java +++ b/src/main/java/io/starter/formats/XLS/WorkBookFactory.java @@ -691,7 +691,7 @@ private void validateRecords(LinkedHashMap map, Book book, Boundsheet short op= opcodes[i]; R r = map.get(op); if (!r.isPresent && r.isRequired) { - // io.starter.OpenXLS.util.Logger.log("A required record is not present: " + op); + // io.starter.toolkit.Logger.log("A required record is not present: " + op); // Create a new Record BiffRec rec= createMissingRequiredRecord(op, book, bs); rec.setDebugLevel(this.DEBUGLEVEL); @@ -738,9 +738,9 @@ private void validateRecords(LinkedHashMap map, Book book, Boundsheet */ private void validateRecordOrder(LinkedHashMap map, java.util.List list, Short[] opcodes) { /* debugging: - io.starter.OpenXLS.util.Logger.log("BeFORE order:"); + io.starter.toolkit.Logger.log("BeFORE order:"); for (int zz= 0; zz < list.size(); zz++) { - io.starter.OpenXLS.util.Logger.log(zz + "-" + list.get(zz)); + io.starter.toolkit.Logger.log(zz + "-" + list.get(zz)); }*/ R lastR= map.get(BOF); short lastOp= BOF; @@ -764,12 +764,12 @@ private void validateRecordOrder(LinkedHashMap map, java.util.List lis } int origRecPos= r.recordPos; -//io.starter.OpenXLS.util.Logger.log("Record out of order: r:" + op + "/" + r.recordPos + " lastR:" + lastOp + "/" + lastR.recordPos); +//io.starter.toolkit.Logger.log("Record out of order: r:" + op + "/" + r.recordPos + " lastR:" + lastOp + "/" + lastR.recordPos); // find correct insertion point by looking at ordered map for (int zz= i-1; zz > 0; zz--) { R prevr = map.get(opcodes[zz]); if (prevr.isPresent && r.recordPos < prevr.recordPos) { -//io.starter.OpenXLS.util.Logger.log("\tInsert at " + prevr.recordPos + " before op= " + opcodes[zz]); +//io.starter.toolkit.Logger.log("\tInsert at " + prevr.recordPos + " before op= " + opcodes[zz]); int recsMovedCount= 0; BiffRec recToMove= (BiffRec)list.get(origRecPos); do { @@ -777,7 +777,7 @@ private void validateRecordOrder(LinkedHashMap map, java.util.List lis list.add(prevr.recordPos /*+ recsMovedCount*/, recToMove); if (recsMovedCount==0) r.recordPos= recToMove.getRecordIndex(); -//io.starter.OpenXLS.util.Logger.log("\tMoved To " + recToMove.getRecordIndex()); +//io.starter.toolkit.Logger.log("\tMoved To " + recToMove.getRecordIndex()); recsMovedCount++; recToMove= (BiffRec)list.get(origRecPos); } while (recToMove.getOpcode()==op); @@ -796,9 +796,9 @@ private void validateRecordOrder(LinkedHashMap map, java.util.List lis lastOp= op; } } - /*io.starter.OpenXLS.util.Logger.log("AFTER order:"); + /*io.starter.toolkit.Logger.log("AFTER order:"); for (int zz= 0; zz < list.size(); zz++) { - io.starter.OpenXLS.util.Logger.log(zz + "-" + list.get(zz)); + io.starter.toolkit.Logger.log(zz + "-" + list.get(zz)); }*/ } @@ -995,7 +995,7 @@ private BiffRec createMissingRequiredRecord(short opcode, Book book, Boundsheet case EOF: break; default: - io.starter.OpenXLS.util.Logger.log("Must create required rec: " + opcode); + io.starter.toolkit.Logger.log("Must create required rec: " + opcode); } @@ -1041,7 +1041,7 @@ private void markRecord(LinkedHashMap map, BiffRec rec, short opcode) /* if (opcode != CONTINUE && opcode!=DBCELL && opcode < 4000 /* chart records * / && !rec.isValueForCell()) // ignore CELLTABLE records -// io.starter.OpenXLS.util.Logger.log("COULDN'T FIND Opcode: " + opcode);*/ +// io.starter.toolkit.Logger.log("COULDN'T FIND Opcode: " + opcode);*/ } } @@ -1049,12 +1049,12 @@ private void markRecord(LinkedHashMap map, BiffRec rec, short opcode) /** debug utility */ private void displayRecsInStream(LinkedHashMap map) { Iterator ii= map.keySet().iterator(); - io.starter.OpenXLS.util.Logger.log("Present Records"); + io.starter.toolkit.Logger.log("Present Records"); while (ii.hasNext()) { short op= ii.next(); R r= map.get(op); if (r.isPresent) { - io.starter.OpenXLS.util.Logger.log(op + " at " + r.recordPos); + io.starter.toolkit.Logger.log(op + " at " + r.recordPos); } } } diff --git a/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java b/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java index 29f0de0..ee31647 100644 --- a/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java +++ b/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java @@ -22,38 +22,39 @@ */ package io.starter.formats.XLS; +import io.starter.OpenXLS.Cell; -/** No WorkSheet Found. - +/** + * No WorkSheet Found. + * * @see Cell * @see WorkBook -*/ + */ +public final class WorkSheetNotFoundException extends java.lang.Exception { -public final class WorkSheetNotFoundException extends java.lang.Exception{ - - /** - * serialVersionUID - */ + /** + * serialVersionUID + */ private static final long serialVersionUID = -1722195057857012811L; String message = ""; - - public WorkSheetNotFoundException(String n){ - super(); - message = n; - } - - public String getMessage() - { - // This method is derived from class java.lang.Throwable - // to do: code goes here - return this.toString(); - } - public String toString() - { - // This method is derived from class java.lang.Throwable - // to do: code goes here - return message; - } + public WorkSheetNotFoundException(String n) { + super(); + message = n; + } + + @Override + public String getMessage() { + // This method is derived from class java.lang.Throwable + // to do: code goes here + return this.toString(); + } + + @Override + public String toString() { + // This method is derived from class java.lang.Throwable + // to do: code goes here + return message; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Ai.java b/src/main/java/io/starter/formats/XLS/charts/Ai.java index c559a26..e3b2e34 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Ai.java +++ b/src/main/java/io/starter/formats/XLS/charts/Ai.java @@ -5,18 +5,25 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -26,10 +33,22 @@ import java.util.List; import java.util.Stack; -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.*; -import io.starter.formats.XLS.*; +import io.starter.formats.XLS.ExpressionParser; +import io.starter.formats.XLS.Externsheet; +import io.starter.formats.XLS.Formula; +import io.starter.formats.XLS.FormulaNotFoundException; +import io.starter.formats.XLS.WorkSheetNotFoundException; +import io.starter.formats.XLS.formulas.GenericPtg; +import io.starter.formats.XLS.formulas.Ptg; +import io.starter.formats.XLS.formulas.PtgArea3d; +import io.starter.formats.XLS.formulas.PtgMemFunc; +import io.starter.formats.XLS.formulas.PtgMystery; +import io.starter.formats.XLS.formulas.PtgParen; +import io.starter.formats.XLS.formulas.PtgRef; +import io.starter.formats.XLS.formulas.PtgRef3d; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.Logger; +import io.starter.toolkit.StringTool; /** Ai: Linked Chart Data (1051h)
@@ -58,311 +77,376 @@ * @see Chart */ -public final class Ai extends GenericChartObject implements ChartObject -{ +public final class Ai extends GenericChartObject implements ChartObject { /** * */ - private static final long serialVersionUID = -6647823755603289012L; - private Stack expression; - protected int id =-1, ifmt = -1, cce = -1; - private short grbit = -1, rt = -1; - private boolean fCustomIfmt= false; - //private CompatibleVector xlsrecs = new CompatibleVector(); - private SeriesText st= null; + private static final long serialVersionUID = -6647823755603289012L; + private Stack expression; + protected int id = -1, ifmt = -1, cce = -1; + private short grbit = -1, rt = -1; + private boolean fCustomIfmt = false; + // private CompatibleVector xlsrecs = new + // CompatibleVector(); + private SeriesText st = null; // define the type of Ai record - public static final int TYPE_TEXT = 0; - public static final int TYPE_VALS = 1; - public static final int TYPE_CATEGORIES = 2; - public static final int TYPE_BUBBLES = 3; + public static final int TYPE_TEXT = 0; + public static final int TYPE_VALS = 1; + public static final int TYPE_CATEGORIES = 2; + public static final int TYPE_BUBBLES = 3; - - - /** - * This is for storage of the boundsheet name when the ai record is being moved from - * one workbook to another. In these cases, populate this value, and pull it back out - * to locate the cxti and reset the reerences - * @return the name of the original boundsheet this record is associated with. - */ - private String boundName = ""; - private String origSheetName= ""; - private int boundXti= -1; - /** - * returns the bound sheet name - must be called after populateForTransfer - * @return String - */ // 20080116 KSC: Changed name for clarity - public String getBoundName() { - return boundName; - } - /** - * returns the sheet reference index - must call after populateForTransfer - * @return - */ - public int getBoundXti() { - return boundXti; - } - - /** - * Sets the boundsheet name for the data referenced in this AI. - * + sets the xti of the boundsheet reference (necessary upon sheet copy/move) - * Does not currently support multi-boundsheet references. - * @see updateSheetRef - */ - public void populateForTransfer(String origSheetName) { - if ("".equals( boundName )) { - this.origSheetName= origSheetName; // 20080708 KSC: trap original sheet name for updateSheetRefs comparison - for(int t=0;t0)Logger.logWarn("Error getting Chart String value: " + e); + + public String getText() { + try { + // XLSRecord x = getRecord(0); + // if(x instanceof SeriesText)return ((SeriesText)x + // ).toString(); + if (st != null) + return st.toString(); + } catch (Exception e) { + if (DEBUGLEVEL > 0) + Logger.logWarn("Error getting Chart String value: " + e); return getDefinition(); } - //TODO: figure out why this doesn't find the title -- see "reportS01Template.xls" + // TODO: figure out why this doesn't find the title -- see + // "reportS01Template.xls" return "undefined"; } - - public String toString(){ - switch(id){ - case Ai.TYPE_TEXT: - return getText(); - case Ai.TYPE_VALS: - return getDefinition(); - case Ai.TYPE_CATEGORIES: - return getDefinition(); - case Ai.TYPE_BUBBLES: - return getDefinition(); + + @Override + public String toString() { + switch (id) { + case Ai.TYPE_TEXT: + return getText(); + case Ai.TYPE_VALS: + return getDefinition(); + case Ai.TYPE_CATEGORIES: + return getDefinition(); + case Ai.TYPE_BUBBLES: + return getDefinition(); } return super.toString(); } - -// public void addRecord(BiffRec rec){ -// xlsrecs.add(rec); -// } + + // public void addRecord(BiffRec rec){ + // xlsrecs.add(rec); + // } public void setSeriesText(SeriesText s) { - st= s; + st = s; } -// protected XLSRecord getRecord(int i){ -// return (XLSRecord) xlsrecs.get(i); -// } - + // protected XLSRecord getRecord(int i){ + // return (XLSRecord) xlsrecs.get(i); + // } + /** set the Externsheet reference for any associated PtgArea3d's */ - public void setExternsheetRef(int x) - throws WorkSheetNotFoundException{ + public void setExternsheetRef(int x) throws WorkSheetNotFoundException { byte[] dt = this.getData(); int pos = 8; - for(int t=0;t 3)Logger.logInfo("Setting sheet reference for: " + pt.toString() + " in Ai record."); + for (int t = 0; t < expression.size(); t++) { + Ptg p = expression.get(t); + if (p instanceof PtgArea3d) { + PtgArea3d pt = (PtgArea3d) p; + pt.setIxti((short) x); + pt.addToRefTracker(); + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pt.toString() + " in Ai record."); // register with the Externsheet reference this.getWorkBook().getExternSheet().addPtgListener(pt); - updateRecord(); - } else if (p instanceof PtgRef3d) { // 20091015 KSC: Added - PtgRef3d pr = (PtgRef3d)p; - pr.setIxti((short)x); - pr.addToRefTracker(); - if(DEBUGLEVEL > 3)Logger.logInfo("Setting sheet reference for: " + pr.toString() + " in Ai record."); + updateRecord(); + } else if (p instanceof PtgRef3d) { // 20091015 KSC: Added + PtgRef3d pr = (PtgRef3d) p; + pr.setIxti((short) x); + pr.addToRefTracker(); + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record."); // register with the Externsheet reference this.getWorkBook().getExternSheet().addPtgListener(pr); - updateRecord(); - } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added - Ptg pr= ((PtgMemFunc)p).getFirstloc(); - if (pr instanceof PtgRef3d) { - ((PtgRef3d)pr).setIxti((short)x); - ((PtgRef3d)pr).addToRefTracker(); - this.getWorkBook().getExternSheet().addPtgListener((PtgRef3d)pr); - } else { // should be a PtgArea3d - ((PtgArea3d)pr).setIxti((short)x); - ((PtgArea3d)pr).addToRefTracker(); - this.getWorkBook().getExternSheet().addPtgListener((PtgArea3d)pr); - } - if(DEBUGLEVEL > 3)Logger.logInfo("Setting sheet reference for: " + pr.toString() + " in Ai record."); + updateRecord(); + } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added + Ptg pr = ((PtgMemFunc) p).getFirstloc(); + if (pr instanceof PtgRef3d) { + ((PtgRef3d) pr).setIxti((short) x); + ((PtgRef3d) pr).addToRefTracker(); + this.getWorkBook().getExternSheet() + .addPtgListener((PtgRef3d) pr); + } else { // should be a PtgArea3d + ((PtgArea3d) pr).setIxti((short) x); + ((PtgArea3d) pr).addToRefTracker(); + this.getWorkBook().getExternSheet() + .addPtgListener((PtgArea3d) pr); + } + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record."); // register with the Externsheet reference - updateRecord(); + updateRecord(); } else { Logger.logInfo("Ai.setExternsheetRef: unknown Ptg"); } } } - - /** set the Externsheet reference - for any associated PtgArea3d's that match the old reference. - - invaluble for modifying only one sheets worth of references (ie a move sheet situation) -*/ - public void setExternsheetRef(int oldRef, int newRef) - throws WorkSheetNotFoundException{ - for(int t=0;t 3)Logger.logInfo("Setting sheet reference for: " + pt.toString() + " in Ai record."); - // register with the Externsheet reference - this.getWorkBook().getExternSheet().addPtgListener(pt); - updateRecord(); - } - } else if (p instanceof PtgRef3d) { - PtgRef3d pr = (PtgRef3d)p; - int oRef = pr.getIxti(); - if (oRef==oldRef) { - pr.removeFromRefTracker(); - pr.setSheetName(this.getSheet().getSheetName()); // 20100415 KSC: added - pr.setIxti((short)newRef); + + /** set the Externsheet reference + for any associated PtgArea3d's that match the old reference. + + invaluble for modifying only one sheets worth of references (ie a move sheet situation) + */ + public void setExternsheetRef(int oldRef, int newRef) throws WorkSheetNotFoundException { + for (int t = 0; t < expression.size(); t++) { + Ptg p = expression.get(t); + if (p instanceof PtgArea3d) { + PtgArea3d pt = (PtgArea3d) p; + int oRef = pt.getIxti(); + if (oRef == oldRef) { // got the one to update + pt.removeFromRefTracker(); // 20100506 KSC: added + pt.setSheetName(this.getSheet().getSheetName()); // 20100415 + // KSC: + // added + pt.setIxti((short) newRef); + pt.addToRefTracker(); // 20080709 KSC + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pt.toString() + " in Ai record."); + // register with the Externsheet reference + this.getWorkBook().getExternSheet().addPtgListener(pt); + updateRecord(); + } + } else if (p instanceof PtgRef3d) { + PtgRef3d pr = (PtgRef3d) p; + int oRef = pr.getIxti(); + if (oRef == oldRef) { + pr.removeFromRefTracker(); + pr.setSheetName(this.getSheet().getSheetName()); // 20100415 + // KSC: + // added + pr.setIxti((short) newRef); if (!pr.getIsRefErr()) - pr.addToRefTracker(); - if(DEBUGLEVEL > 3)Logger.logInfo("Setting sheet reference for: " + pr.toString() + " in Ai record."); + pr.addToRefTracker(); + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record."); // register with the Externsheet reference this.getWorkBook().getExternSheet().addPtgListener(pr); - updateRecord(); - } - } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added - Ptg pr= ((PtgMemFunc)p).getFirstloc(); - if (pr instanceof PtgRef3d) { - int oRef = ((PtgRef3d)pr).getIxti(); - if (oRef==oldRef) { - ((PtgRef3d)pr).removeFromRefTracker(); // 20100506 KSC: added - ((PtgArea3d)pr).setSheetName(this.getSheet().getSheetName()); // 20100415 KSC: added - ((PtgRef3d)pr).setIxti((short)newRef); - ((PtgRef3d)pr).addToRefTracker(); - this.getWorkBook().getExternSheet().addPtgListener((PtgRef3d)pr); - } - } else { // should be a PtgArea3d - int oRef = ((PtgRef3d)pr).getIxti(); - if (oRef==oldRef) { - ((PtgRef3d)pr).removeFromRefTracker(); // 20100506 KSC: added - ((PtgArea3d)pr).setSheetName(this.getSheet().getSheetName()); // 20100415 KSC: added - ((PtgArea3d)pr).setIxti((short)newRef); - ((PtgArea3d)pr).addToRefTracker(); - this.getWorkBook().getExternSheet().addPtgListener((PtgArea3d)pr); - } - } - if(DEBUGLEVEL > 3)Logger.logInfo("Setting sheet reference for: " + pr.toString() + " in Ai record."); + updateRecord(); + } + } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added + Ptg pr = ((PtgMemFunc) p).getFirstloc(); + if (pr instanceof PtgRef3d) { + int oRef = ((PtgRef3d) pr).getIxti(); + if (oRef == oldRef) { + ((PtgRef3d) pr).removeFromRefTracker(); // 20100506 KSC: + // added + ((PtgArea3d) pr) + .setSheetName(this.getSheet().getSheetName()); // 20100415 + // KSC: + // added + ((PtgRef3d) pr).setIxti((short) newRef); + ((PtgRef3d) pr).addToRefTracker(); + this.getWorkBook().getExternSheet() + .addPtgListener((PtgRef3d) pr); + } + } else { // should be a PtgArea3d + int oRef = ((PtgRef3d) pr).getIxti(); + if (oRef == oldRef) { + ((PtgRef3d) pr).removeFromRefTracker(); // 20100506 KSC: + // added + ((PtgArea3d) pr) + .setSheetName(this.getSheet().getSheetName()); // 20100415 + // KSC: + // added + ((PtgArea3d) pr).setIxti((short) newRef); + ((PtgArea3d) pr).addToRefTracker(); + this.getWorkBook().getExternSheet() + .addPtgListener((PtgArea3d) pr); + } + } + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record."); // register with the Externsheet reference - updateRecord(); + updateRecord(); } else if (p instanceof PtgMystery) { // TODO: do what??? - } - } + } + } } - + /** * take the original boundSheet + boundSheet xti reference and update it to * the sheet reference in the new workbook (or same workbook but different ixti reference) */ public void updateSheetRef(String newSheetName, String origWorkBookName) { try { - // 20080630/20080708 KSC: Fixes for [BugTracker 1799] + [BugTracker 1434] - if (boundXti>-1) { // has populate for transfer been called - should! - int newSheetNum= -1; - try{ - if (!boundName.equalsIgnoreCase(origSheetName)) { // Ai reference is on a dfferent sheet, see if it exists in new workbook - newSheetNum = this.getWorkBook().getWorkSheetByName(boundName).getSheetNum(); - } else // Ai reference is on same sheet, point now to new sheet - newSheetNum = this.getWorkBook().getWorkSheetByName(newSheetName).getSheetNum(); - }catch(Exception e){ // 20080123 KSC: if links arent there, fix == try to make an External ref - for(int t=0;t -1) { // has populate for transfer been called - + // should! + int newSheetNum = -1; + try { + if (!boundName.equalsIgnoreCase(origSheetName)) { // Ai + // reference + // is on + // a + // dfferent + // sheet, + // see + // if it + // exists + // in + // new + // workbook + newSheetNum = this.getWorkBook() + .getWorkSheetByName(boundName).getSheetNum(); + } else // Ai reference is on same sheet, point now to new + // sheet + newSheetNum = this.getWorkBook() + .getWorkSheetByName(newSheetName).getSheetNum(); + } catch (Exception e) { // 20080123 KSC: if links arent there, + // fix == try to make an External ref + for (int t = 0; t < expression.size(); t++) { + if (expression.get(t) instanceof PtgArea3d) { + PtgArea3d p = (PtgArea3d) expression.get(t); + Logger.logWarn("External References are unsupported: External reference found in Chart: " + + p.getSheetName()); + p.setSheetName(boundName); // set external reference + // to original + // boundsheet name + p.setExternalReference(origWorkBookName); this.setExternsheetRef(p.getIxti()); - } else { - Logger.logInfo("Ai.updateSheetRef:"); - } - } + } else { + Logger.logInfo("Ai.updateSheetRef:"); + } + } + } + if (newSheetNum != -1) { + this.setSheet(this.getWorkBook() + .getWorkSheetByName(newSheetName)); // 20100415 KSC: + // set Ai sheet + // ref to new + // sheet + Externsheet xsht = this.getWorkBook().getExternSheet(true); // create + // if + // necessary + int newXRef = xsht.insertLocation(newSheetNum, newSheetNum); + this.setExternsheetRef(boundXti, newXRef); + boundXti = newXRef; // 20100506 KSC: reset + boundName = newSheetName; // "" } - if (newSheetNum!=-1) { - this.setSheet(this.getWorkBook().getWorkSheetByName(newSheetName)); // 20100415 KSC: set Ai sheet ref to new sheet - Externsheet xsht = this.getWorkBook().getExternSheet(true); // create if necessary - int newXRef = xsht.insertLocation(newSheetNum, newSheetNum); - this.setExternsheetRef(boundXti, newXRef); - boundXti= newXRef; // 20100506 KSC: reset - boundName= newSheetName; // "" - } - } - else {// debugging 20100415 -// Logger.logErr("Ai.updateSheetRef: boundxti is -1 for AI " + this.toString()); + } else {// debugging 20100415 + // Logger.logErr("Ai.updateSheetRef: boundxti is -1 for AI " + // + this.toString()); } } catch (Exception e) { Logger.logErr("Ai.updateSheetRef: " + e.toString()); @@ -371,80 +455,88 @@ public void updateSheetRef(String newSheetName, String origWorkBookName) { /** get the display name */ - String getName(){ + String getName() { return "Chart Ai"; } - + /** get the definition text the definition is stored in Excel parsed format */ - public String getDefinition(){ + public String getDefinition() { StringBuffer sb = new StringBuffer(); Ptg[] ep = new Ptg[expression.size()]; - ep = (Ptg[]) expression.toArray(ep); - for(int t = 0;tif 0, use default number format as specified by Axis or Chart - * @return - */ - public int getIfmt() { - return ifmt; - } - /* Returns the ptg that matches the string location sent to it. - this can either be in the format "C5" or a range, such as "C4:D9" - */ - public List getPtgsByLocation(String loc){ - try{ - return ExpressionParser.getPtgsByLocation(loc,expression); - }catch(FormulaNotFoundException e){ + + /** + * Return the type (ID) of this Ai record + * @return int rt + */ + public int getType() { + return id; + } + + /** + * return the custom number format for this AI (category, series or bubble) + *
if 0, use default number format as specified by Axis or Chart + * @return + */ + public int getIfmt() { + return ifmt; + } + + /* + * Returns the ptg that matches the string location sent to + * it. + * this can either be in the format "C5" or a range, such as + * "C4:D9" + */ + public List getPtgsByLocation(String loc) { + try { + return ExpressionParser.getPtgsByLocation(loc, expression); + } catch (FormulaNotFoundException e) { Logger.logWarn("failed to update Chart Series Location: " + e); } return null; } - + /** locks the Ptg at the specified location */ - - public boolean setLocationPolicy(String loc, int l){ - List dx = this.getPtgsByLocation(loc); - Iterator lx=dx.iterator(); - while(lx.hasNext()){ - Ptg d = (Ptg)lx.next(); - if(d!=null){ + + public boolean setLocationPolicy(String loc, int l) { + List dx = this.getPtgsByLocation(loc); + Iterator lx = dx.iterator(); + while (lx.hasNext()) { + Ptg d = lx.next(); + if (d != null) { d.setLocationPolicy(l); - } + } } return true; } - - + /** * simplified version of changeAiLocation which locates current Ptg and updates expression * NOTE: newLoc is expected to be a valid reference @@ -453,180 +545,269 @@ public boolean setLocationPolicy(String loc, int l){ * @return */ public boolean changeAiLocation(Ptg p, String newLoc) { - String[] aiLocs= StringTool.splitString(newLoc, ","); - for (int i= 0; i0) { - z= ExpressionParser.getExpressionLocByLocation(loc, expression); - ptg= (Ptg) expression.get(z); // 20090917 KSC: since creating new ptgs below, must remove original from reftracker "by hand" + if (expression.size() > 0) { + z = ExpressionParser + .getExpressionLocByLocation(loc, expression); + ptg = expression.get(z); // 20090917 KSC: since creating + // new ptgs below, must remove + // original from reftracker "by + // hand" } - if (ptg!=null) ((PtgRef) ptg).removeFromRefTracker(); - } catch (Exception e) { - } - if (z==-1 && newLoc.equals("")) {// no reference -- happens on legends, category ai's ... - this.getData()[1]= 1; // text reference rather than worksheet reference + if (ptg != null) + ((PtgRef) ptg).removeFromRefTracker(); + } catch (Exception e) {} + if (z == -1 && newLoc.equals("")) {// no reference -- happens on + // legends, category ai's ... + this.getData()[1] = 1; // text reference rather than worksheet + // reference return false; } - ptg= PtgRef.createPtgRefFromString(newLoc, this); - if (z!=-1) // then must change original - expression.set(z, ptg); // update expression with new Ptg + ptg = PtgRef.createPtgRefFromString(newLoc, this); + if (z != -1) // then must change original + expression.set(z, ptg); // update expression with new Ptg else expression.add(ptg); updateRecord(); return true; } - - - /* Update the record byte array with the modified ptg records - */ - public void updateRecord(){ + + /* + * Update the record byte array with the modified ptg + * records + */ + public void updateRecord() { int offy = 8; // the start of the parsed expression byte[] rkdata = this.getData(); byte[] updated = new byte[rkdata.length]; System.arraycopy(rkdata, 0, updated, 0, offy); - for (int i = 0; i< expression.size();i++){ + for (int i = 0; i < expression.size(); i++) { Object o = expression.elementAt(i); Ptg ptg = (Ptg) o; byte[] b = ptg.getRecord(); - // must inc. size if Ptgs have inc.'d ... see changeAiLocation - int len= b.length; - if ((updated.length-offy) < len) { - byte[] newArr= new byte[offy + len]; + // must inc. size if Ptgs have inc.'d ... see + // changeAiLocation + int len = b.length; + if ((updated.length - offy) < len) { + byte[] newArr = new byte[offy + len]; System.arraycopy(updated, 0, newArr, 0, updated.length); // update cce in array as well ... - cce+= (newArr.length-updated.length); - updated= newArr; + cce += (newArr.length - updated.length); + updated = newArr; byte[] ix = ByteTools.shortToLEBytes((short) cce); System.arraycopy(ix, 0, updated, 6, 2); } System.arraycopy(b, 0, updated, offy, len); offy = offy + len; - } + } this.setData(updated); } + @Override public void init() { super.init(); id = this.getByteAt(0); - // index id: (0=title or text, 1=series vals, 2=series cats, 3= bubbles + // index id: (0=title or text, 1=series vals, 2=series cats, + // 3= bubbles rt = this.getByteAt(1); - // reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) + // reference type(0=default,1=text in formula bar, + // 2=worksheet, 4=error) grbit = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - fCustomIfmt= (grbit & 0x1)==0x1; + setfCustomIfmt((grbit & 0x1) == 0x1); // flags - ifmt = (int) ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + ifmt = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); // Index to number format - cce = (int) ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + cce = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); // size of rgce (in bytes) int pos = 8; - // Parsed formula of link + // Parsed formula of link - // get the parsed expression + // get the parsed expression byte[] expressionbytes = this.getBytesAt(pos, cce); expression = ExpressionParser.parseExpression(expressionbytes, this); pos += cce; - if(DEBUGLEVEL > 10)Logger.logInfo(this.getName()+ ":" + this.getDefinition()); + if (DEBUGLEVEL > 10) + Logger.logInfo(this.getName() + ":" + this.getDefinition()); } - - /** - * set reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) - * @param i - */ - public void setRt(int i) { - rt = (short) i; - this.getData()[1]= (byte) rt; - } - /** - * Get a prototype with the specified ai types. - * - * 0 = Legend AI - * 1= Series Value Ai - * 2 = Category Ai - * 3 = Unknown, undocumented, but neccesarry AI - * 4 = Blank Legend AI with no reference. - */ - public static ChartObject getPrototype(byte[] aiType) { - Ai ai = new Ai(); - ai.setOpcode(AI); - ai.setData(aiType); - ai.init(); - return ai; - } - - // 20070801 KSC: since changeAiLocation now allows addition of new expression bytes, alter - // default prototype bytes here to not include any expression bytes .. - protected static byte[] AI_TYPE_LEGEND = new byte[] {0, 2, 0, 0, 0, 0, 0, 0}; //, 7, 0, 58, 0, 0, 0, 0, 0, 0}; - protected static byte[] AI_TYPE_SERIES = new byte[] {1, 2, 0, 0, 0, 0, 0, 0}; //, 11, 0, 59, 0, 0, 1, 0, 1, 0, 1, 0, 3, 0}; - protected static byte[] AI_TYPE_CATEGORY = new byte[] {2, 2, 0, 0, 0, 0, 0, 0}; // 11, 0, 59, 0, 0, 0, 0, 0, 0, 1, 0, 3, 0}; - protected static byte[] AI_TYPE_BUBBLE = new byte[] {3, 1, 0, 0, 0, 0, 0, 0}; - protected static byte[] AI_TYPE_NULL_LEGEND = new byte[] {0, 1, 0, 0, 0, 0, 0, 0}; + + /** + * set reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) + * @param i + */ + public void setRt(int i) { + rt = (short) i; + this.getData()[1] = (byte) rt; + } + + /** + * Get a prototype with the specified ai types. + * + * 0 = Legend AI + * 1= Series Value Ai + * 2 = Category Ai + * 3 = Unknown, undocumented, but neccesarry AI + * 4 = Blank Legend AI with no reference. + */ + public static ChartObject getPrototype(byte[] aiType) { + Ai ai = new Ai(); + ai.setOpcode(AI); + ai.setData(aiType); + ai.init(); + return ai; + } + + // 20070801 KSC: since changeAiLocation now allows addition + // of new expression bytes, alter + // default prototype bytes here to not include any + // expression bytes .. + protected static byte[] AI_TYPE_LEGEND = new byte[] { 0, 2, 0, 0, 0, 0, + 0, 0 }; // , + // 7, + // 0, + // 58, + // 0, + // 0, + // 0, + // 0, + // 0, + // 0}; + protected static byte[] AI_TYPE_SERIES = new byte[] { 1, 2, 0, 0, 0, 0, + 0, 0 }; // , + // 11, + // 0, + // 59, + // 0, + // 0, + // 1, + // 0, + // 1, + // 0, + // 1, + // 0, + // 3, + // 0}; + protected static byte[] AI_TYPE_CATEGORY = new byte[] { 2, 2, 0, 0, 0, 0, + 0, 0 }; // 11, + // 0, + // 59, + // 0, + // 0, + // 0, + // 0, + // 0, + // 0, + // 1, + // 0, + // 3, + // 0}; + protected static byte[] AI_TYPE_BUBBLE = new byte[] { 3, 1, 0, 0, 0, 0, + 0, 0 }; + protected static byte[] AI_TYPE_NULL_LEGEND = new byte[] { 0, 1, 0, 0, 0, 0, + 0, 0 }; + public Stack getExpression() { return expression; } + + @Override public void close() { - if (expression!=null) { - while (!expression.isEmpty()) { - GenericPtg p= (GenericPtg) expression.pop(); - if (p instanceof PtgRef) - ((PtgRef) p).close(); - else - p.close(); - p= null; - } - } - super.close(); + if (expression != null) { + while (!expression.isEmpty()) { + GenericPtg p = (GenericPtg) expression.pop(); + if (p instanceof PtgRef) + ((PtgRef) p).close(); + else + p.close(); + p = null; + } + } + super.close(); } + + @Override protected void finalize() { this.close(); } + + /** + * @return the fCustomIfmt + */ + public boolean isfCustomIfmt() { + return fCustomIfmt; + } + + /** + * @param fCustomIfmt the fCustomIfmt to set + */ + public void setfCustomIfmt(boolean fCustomIfmt) { + this.fCustomIfmt = fCustomIfmt; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/AreaChart.java b/src/main/java/io/starter/formats/XLS/charts/AreaChart.java index 4dd6755..003f2bd 100644 --- a/src/main/java/io/starter/formats/XLS/charts/AreaChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/AreaChart.java @@ -2,21 +2,28 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -24,20 +31,14 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.Stack; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartHandle; import io.starter.OpenXLS.ChartSeriesHandle; import io.starter.OpenXLS.WorkBookHandle; -import io.starter.OpenXLS.ChartHandle.ChartOptions; -import io.starter.formats.OOXML.Marker; -import io.starter.formats.OOXML.OOXMLConstants; import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; @@ -47,190 +48,249 @@ * */ public class AreaChart extends ChartType { - Area area= null; - public AreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + Area area = null; + + public AreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { super(charttype, cf, wb); - area= (Area) charttype; + area = (Area) charttype; } - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) - throws JSONException { - JSONObject chartObjectJSON= new JSONObject(); - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // Series - double yMax= 0.0, yMin= 0.0; - int nSeries= 0; - JSONArray seriesJSON= new JSONArray(); - JSONArray seriesCOLORS= new JSONArray(); - try { - for (int i= 0; i < series.length; i++) { - JSONArray seriesvals= CellRange.getValuesAsJSON(series[i].getSeriesRange(), wbh); - // must trap min and max for axis tick and units - double sum= 0.0; // for area-type charts, ymax is the sum of all points in same series - nSeries= Math.max(nSeries, seriesvals.length()); - for (int j= 0; j < seriesvals.length(); j++) { - try { - sum+= seriesvals.getDouble(j); - yMax= Math.max(yMax, sum); - yMin= Math.min(yMin, seriesvals.getDouble(j)); - } catch (NumberFormatException n) {;} - } - seriesJSON.put(seriesvals); - seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i].getSeriesColor()]); - } - chartObjectJSON.put("Series", seriesJSON); - chartObjectJSON.put("SeriesFills", seriesCOLORS); - } catch (JSONException je) { - // TODO: Log error - } - minMax[0]= new Double(yMin); - minMax[1]= new Double(yMax); - minMax[2]= new Double(nSeries); - return chartObjectJSON; - - } + + public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) throws JSONException { + final JSONObject chartObjectJSON = new JSONObject(); + // Type JSON + chartObjectJSON.put("type", this.getTypeJSON()); + + // Series + double yMax = 0.0, yMin = 0.0; + int nSeries = 0; + final JSONArray seriesJSON = new JSONArray(); + final JSONArray seriesCOLORS = new JSONArray(); + try { + for (int i = 0; i < series.length; i++) { + final JSONArray seriesvals = CellRange + .getValuesAsJSON(series[i].getSeriesRange(), wbh); + // must trap min and max for axis tick and units + double sum = 0.0; // for area-type charts, ymax is the sum of + // all points in same series + nSeries = Math.max(nSeries, seriesvals.length()); + for (int j = 0; j < seriesvals.length(); j++) { + try { + sum += seriesvals.getDouble(j); + yMax = Math.max(yMax, sum); + yMin = Math.min(yMin, seriesvals.getDouble(j)); + } catch (final NumberFormatException n) { + ; + } + } + seriesJSON.put(seriesvals); + seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i] + .getSeriesColor()]); + } + chartObjectJSON.put("Series", seriesJSON); + chartObjectJSON.put("SeriesFills", seriesCOLORS); + } catch (final JSONException je) { + // TODO: Log error + } + minMax[0] = new Double(yMin); + minMax[1] = new Double(yMax); + minMax[2] = new Double(nSeries); + return chartObjectJSON; + + } + /** * return the type JSON for this Chart Object * @return */ + @Override public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON= new JSONObject(); - String dojoType; - if (!this.isStacked()) - dojoType="Areas"; - else - dojoType="StackedAreas"; - typeJSON.put("type", dojoType); - return typeJSON; + final JSONObject typeJSON = new JSONObject(); + String dojoType; + if (!this.isStacked()) { + dojoType = "Areas"; + } else { + dojoType = "StackedAreas"; + } + typeJSON.put("type", dojoType); + return typeJSON; } /** * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints * @return String svg */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double x= chartMetrics.get("x"); - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there - // y value for each point= h/YMAX - if (series.size()==0) { + @Override + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + final double x = chartMetrics.get("x"); + final double y = chartMetrics.get("y"); + final double w = chartMetrics.get("w"); + final double h = chartMetrics.get("h"); + final double max = chartMetrics.get("max"); + chartMetrics.get("min"); + final Object[] categories = s.getCategories(); + final ArrayList series = s.getSeriesValues(); + final String[] seriescolors = s.getSeriesBarColors(); + final String[] legends = s.getLegends(); + // x value for each point= w/(ncategories + 1) 1st one is + // xv*2 then increases from there + // y value for each point= h/YMAX + if (series.size() == 0) { Logger.logErr("Area.getSVG: error in series"); return ""; } - StringBuffer svg= new StringBuffer(); - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - - double xfactor= 0, yfactor= 0; // - if (categories.length>1) - xfactor= w/(categories.length-1); // w/#categories - if (max!=0) - yfactor= h/max; // h/YMAXSCALE - - // for each series - int n= series.size(); - for (int i= n-1; i >= 0; i--) { // "paint" right to left + final StringBuffer svg = new StringBuffer(); + final int[] dls = getDataLabelInts(); // get array of data labels (can + // be specific per series ...) + + double xfactor = 0, yfactor = 0; // + if (categories.length > 1) { + xfactor = w / (categories.length - 1); // w/#categories + } + if (max != 0) { + yfactor = h / max; // h/YMAXSCALE + } + + // for each series + final int n = series.size(); + for (int i = n - 1; i >= 0; i--) { // "paint" right to left svg.append("\r\n"); - String points= ""; - double x1= 0; - double y1= 0; - String labels= null; - double[] curseries= (double[])series.get(i); - for (int j= 0; j < curseries.length; j++) { - x1= (x)+j*xfactor; - double yval= curseries[j]; //areapoints[j][i]; // current point - points+= ((x)+(j)*xfactor) + "," + ((y+h)-(yval*yfactor)); - - if (j==0) y1= ((y+h)-(yval*yfactor)); // end point (==start point) for path statement below - points+=" "; + String points = ""; + double x1 = 0; + double y1 = 0; + String labels = null; + final double[] curseries = (double[]) series.get(i); + for (int j = 0; j < curseries.length; j++) { + x1 = (x) + j * xfactor; + final double yval = curseries[j]; // areapoints[j][i]; // + // current point + points += ((x) + (j) * xfactor) + "," + + ((y + h) - (yval * yfactor)); + + if (j == 0) { + y1 = ((y + h) - (yval * yfactor)); // end point (==start + // point) for path + // statement below + } + points += " "; // DATA LABELS - String l= getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); - if (l!=null) { - // if only category label, center over all series; anything else, position at data point - boolean showCategories= (dls[i] & AttachedLabel.CATEGORYLABEL)==AttachedLabel.CATEGORYLABEL; - boolean showValueLabel= (dls[i] & AttachedLabel.VALUELABEL)==AttachedLabel.VALUELABEL; - boolean showValue= (dls[i] & AttachedLabel.VALUE)==AttachedLabel.VALUE; - if (showCategories && !(showValue || showValueLabel) && j==0) { // only 1 label, centered along category axis within area - double hh= y1; // (areapoints[areapoints.length/2][i]*yfactor); - double yy= (y+h) - hh+10; - if (labels==null) labels= ""; - labels= "" + l + "\r\n"; - } else if (showValue || showValueLabel) { // labels at each data point - if (labels==null) labels= ""; - double yy= (((y+h)-((yval-(curseries[j]*.5))*yfactor))); - labels+= "" + l + "\r\n"; + final String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j] + .toString()); + if (l != null) { + // if only category label, center over all series; anything + // else, position at data point + final boolean showCategories = (dls[i] + & AttachedLabel.CATEGORYLABEL) == AttachedLabel.CATEGORYLABEL; + final boolean showValueLabel = (dls[i] + & AttachedLabel.VALUELABEL) == AttachedLabel.VALUELABEL; + final boolean showValue = (dls[i] + & AttachedLabel.VALUE) == AttachedLabel.VALUE; + if (showCategories && !(showValue || showValueLabel) + && j == 0) { // only 1 label, centered along + // category axis within area + final double hh = y1; // (areapoints[areapoints.length/2][i]*yfactor); + final double yy = (y + h) - hh + 10; + if (labels == null) { + labels = ""; + } + labels = "" + l + + "\r\n"; + } else if (showValue || showValueLabel) { // labels at each + // data point + if (labels == null) { + labels = ""; + } + final double yy = (((y + h) + - ((yval - (curseries[j] * .5)) * yfactor))); + labels += "" + + l + "\r\n"; } - } - } + } + } // pointsends connects up area to beginning - double x0= x; - String pointsend= x1 + "," + (y+h) + - " " + x0 + "," + (y+h) + - " " + x0 + "," + y1; - //String clr= getDarkColor(); - /*try { clr= FormatConstants.SVGCOLORSTRINGS[seriescolors[i]]; } catch(ArrayIndexOutOfBoundsException e) {; }*/ - svg.append("\r\n"); + final double x0 = x; + final String pointsend = x1 + "," + (y + h) + " " + x0 + "," + + (y + h) + " " + x0 + "," + y1; + // String clr= getDarkColor(); + /* + * try { clr= + * FormatConstants.SVGCOLORSTRINGS[seriescolors[i]]; } + * catch(ArrayIndexOutOfBoundsException e) {; } + */ + svg.append("\r\n"); // Now print data labels, if any - if (labels!=null) svg.append(labels); + if (labels != null) { + svg.append(labels); + } svg.append("\r\n"); - } + } return svg.toString(); } - - + /** gets the chart-type specific ooxml representation: - * - * @return + * + * @return */ + @Override public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); + final StringBuffer cooxml = new StringBuffer(); // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); cooxml.append(""); cooxml.append("\r\n"); + } + cooxml.append("\"/>"); + cooxml.append("\r\n"); // vary colors??? - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries() + .getOOXML(this.getChartType(), false, 0)); - //TODO: FINISH + // TODO: FINISH // chart data labels, if any - //cooxml.append(getDataLabelsOOXML(cf)); - if (this.cf.getChartLinesRec()!=null) + // cooxml.append(getDataLabelsOOXML(cf)); + if (this.cf.getChartLinesRec() != null) { cooxml.append(this.cf.getChartLinesRec().getOOXML()); + } - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); return cooxml; } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java b/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java index 444480d..2363c7a 100644 --- a/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java +++ b/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java @@ -5,18 +5,25 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ @@ -25,7 +32,6 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; /** * AttachedLabel: Series Data/Value Labels (0x100c) @@ -50,148 +56,156 @@ public class AttachedLabel extends GenericChartObject implements ChartObject { /** * serialVersionUID */ - private static final long serialVersionUID = 2532517522176536995L; - public static final int VALUE= 0x1; - public static final int VALUEPERCENT= 0x2; - public static final int CATEGORYPERCENT= 0x4; - public static final int CATEGORYLABEL= 0x10; - public static final int BUBBLELABEL= 0x20; - public static final int VALUELABEL= 0x40; - - private short grbit= 0; + private static final long serialVersionUID = 2532517522176536995L; + public static final int VALUE = 0x1; + public static final int VALUEPERCENT = 0x2; + public static final int CATEGORYPERCENT = 0x4; + public static final int CATEGORYLABEL = 0x10; + public static final int BUBBLELABEL = 0x20; + public static final int VALUELABEL = 0x40; + + private short grbit = 0; + + @Override public void init() { super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); } public static XLSRecord getPrototype() { - AttachedLabel al = new AttachedLabel(); - al.setOpcode(ATTACHEDLABEL); - al.setData(al.PROTOTYPE_BYTES); - al.init(); - return al; - } - private byte[] PROTOTYPE_BYTES = new byte[] {0,0}; - - /** - * return a string of all the label options chosen - * @return - */ - public String getType() { - String ret= ""; - if ((grbit & VALUE)==VALUE) // bit 0 - ret= "Value "; - if ((grbit & VALUEPERCENT)==VALUEPERCENT) // bit 1 - ret= ret + "ValuePerecentage "; - if ((grbit & CATEGORYPERCENT)==CATEGORYPERCENT) // bit 2 - ret= ret + "CategoryPercentage "; // Pie only - if ((grbit & CATEGORYLABEL)==CATEGORYLABEL) // bit 4 - ret= ret + "CategoryLabel "; - if ((grbit & BUBBLELABEL)==BUBBLELABEL) // bit 5 - ret= ret + "BubbleLabel "; - if ((grbit & VALUELABEL)==VALUELABEL) // bit 6 - ret= ret + "SeriesLabel "; - return ret.trim(); - } - - /** - * return the data label options as an int - * @see AttachedLabel constants + AttachedLabel al = new AttachedLabel(); + al.setOpcode(ATTACHEDLABEL); + al.setData(al.PROTOTYPE_BYTES); + al.init(); + return al; + } + + private byte[] PROTOTYPE_BYTES = new byte[] { 0, 0 }; + + /** + * return a string of all the label options chosen + * @return + */ + public String getType() { + String ret = ""; + if ((grbit & VALUE) == VALUE) // bit 0 + ret = "Value "; + if ((grbit & VALUEPERCENT) == VALUEPERCENT) // bit 1 + ret = ret + "ValuePerecentage "; + if ((grbit & CATEGORYPERCENT) == CATEGORYPERCENT) // bit 2 + ret = ret + "CategoryPercentage "; // Pie only + if ((grbit & CATEGORYLABEL) == CATEGORYLABEL) // bit 4 + ret = ret + "CategoryLabel "; + if ((grbit & BUBBLELABEL) == BUBBLELABEL) // bit 5 + ret = ret + "BubbleLabel "; + if ((grbit & VALUELABEL) == VALUELABEL) // bit 6 + ret = ret + "SeriesLabel "; + return ret.trim(); + } + + /** + * return the data label options as an int + * @see AttachedLabel constants
SHOWVALUE= 0x1;
SHOWVALUEPERCENT= 0x2;
SHOWCATEGORYPERCENT= 0x4;
SHOWCATEGORYLABEL= 0x10;
SHOWBUBBLELABEL= 0x20;
SHOWSERIESLABEL= 0x40; - * @return a combination of data label options above or 0 if none - */ - public int getTypeInt() { - return grbit; - } - - public void setType(short type) { - grbit= type; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - /** - * Show or Hide AttachedLabel Option + * @return a combination of data label options above or 0 if none + */ + public int getTypeInt() { + return grbit; + } + + public void setType(short type) { + grbit = type; + byte[] b = ByteTools.shortToLEBytes(grbit); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } + + /** + * Show or Hide AttachedLabel Option
ShowValueLabel,
ShowValueAsPercent,
ShowLabelAsPercent,
ShowLabel,
ShowBubbleLabel
ShowSeriesName - * @param val true or 1 to set - */ - public void setType(String type, String val) { - boolean bSet= val.equals("true") || val.equals("1"); - if (type.equals("ShowValueLabel")) - grbit= ByteTools.updateGrBit(grbit, bSet, 0); - if (type.equals("ShowValueAsPercent")) - grbit= ByteTools.updateGrBit(grbit, bSet, 1); - if (type.equals("ShowLabelAsPercent")) - grbit= ByteTools.updateGrBit(grbit, bSet, 2); - if (type.equals("ShowLabel")) - grbit= ByteTools.updateGrBit(grbit, bSet, 4); - if (type.equals("ShowBubbleLabel")) - grbit= ByteTools.updateGrBit(grbit, bSet, 5); - if (type.equals("ShowSeriesName")) - grbit= ByteTools.updateGrBit(grbit, bSet, 6); - byte[] bb = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = bb[0]; - this.getData()[1] = bb[1]; - } - - /** - * return the value of the specified option - * @param type Sting option - * @return string true or false - */ - public String getType(String type) { - boolean b= false; - String ret= ""; - if (type.equals("ShowValueLabel")) - b=((grbit & VALUE)==VALUE); // bit 0 - if (type.equals("ShowValueAsPercent")) - b= ((grbit & VALUEPERCENT)==VALUEPERCENT); - if (type.equals("ShowLabelAsPercent")) - b= (((grbit & CATEGORYPERCENT)==CATEGORYPERCENT)); - if (type.equals("ShowLabel")) - b= (((grbit & CATEGORYLABEL)==CATEGORYLABEL)); - if (type.equals("ShowBubbleLabel")) - b= (((grbit & BUBBLELABEL)==BUBBLELABEL)); - if (type.equals("ShowSeriesName")) - b= (((grbit & VALUELABEL)==VALUELABEL)); - return (b)?("1"):("0"); - } - - /** - * @deprecated - * @param type - */ - public void setType(String type) { - short t= 0; - if (type.equalsIgnoreCase("Value") || type.equalsIgnoreCase("Y Value")) - t= 1; - else if (type.equalsIgnoreCase("ValuePercentage")) - t= 2; - else if (type.equalsIgnoreCase("CategoryPercentage")) - t= 3; - else if (type.equalsIgnoreCase("Category") || type.equalsIgnoreCase("X Value")) - t= 16; - else if (type.equalsIgnoreCase("CandP")) - t= 22; - else if (type.equalsIgnoreCase("Bubble")) - t= 32; - grbit= t; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - public String toString() { return this.getType(); } - + * @param val true or 1 to set + */ + public void setType(String type, String val) { + boolean bSet = val.equals("true") || val.equals("1"); + if (type.equals("ShowValueLabel")) + grbit = ByteTools.updateGrBit(grbit, bSet, 0); + if (type.equals("ShowValueAsPercent")) + grbit = ByteTools.updateGrBit(grbit, bSet, 1); + if (type.equals("ShowLabelAsPercent")) + grbit = ByteTools.updateGrBit(grbit, bSet, 2); + if (type.equals("ShowLabel")) + grbit = ByteTools.updateGrBit(grbit, bSet, 4); + if (type.equals("ShowBubbleLabel")) + grbit = ByteTools.updateGrBit(grbit, bSet, 5); + if (type.equals("ShowSeriesName")) + grbit = ByteTools.updateGrBit(grbit, bSet, 6); + byte[] bb = ByteTools.shortToLEBytes(grbit); + this.getData()[0] = bb[0]; + this.getData()[1] = bb[1]; + } + + /** + * return the value of the specified option + * @param type Sting option + * @return string true or false + */ + public String getType(String type) { + boolean b = false; + + if (type.equals("ShowValueLabel")) + b = ((grbit & VALUE) == VALUE); // bit 0 + if (type.equals("ShowValueAsPercent")) + b = ((grbit & VALUEPERCENT) == VALUEPERCENT); + if (type.equals("ShowLabelAsPercent")) + b = (((grbit & CATEGORYPERCENT) == CATEGORYPERCENT)); + if (type.equals("ShowLabel")) + b = (((grbit & CATEGORYLABEL) == CATEGORYLABEL)); + if (type.equals("ShowBubbleLabel")) + b = (((grbit & BUBBLELABEL) == BUBBLELABEL)); + if (type.equals("ShowSeriesName")) + b = (((grbit & VALUELABEL) == VALUELABEL)); + return (b) ? ("1") : ("0"); + } + + /** + * @deprecated + * @param type + */ + @Deprecated + public void setType(String type) { + short t = 0; + if (type.equalsIgnoreCase("Value") || type.equalsIgnoreCase("Y Value")) + t = 1; + else if (type.equalsIgnoreCase("ValuePercentage")) + t = 2; + else if (type.equalsIgnoreCase("CategoryPercentage")) + t = 3; + else if (type.equalsIgnoreCase("Category") + || type.equalsIgnoreCase("X Value")) + t = 16; + else if (type.equalsIgnoreCase("CandP")) + t = 22; + else if (type.equalsIgnoreCase("Bubble")) + t = 32; + grbit = t; + byte[] b = ByteTools.shortToLEBytes(grbit); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } + + @Override + public String toString() { + return this.getType(); + } + } diff --git a/src/main/java/io/starter/formats/XLS/charts/Axesused.java b/src/main/java/io/starter/formats/XLS/charts/Axesused.java index 13254b6..1c525f5 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Axesused.java +++ b/src/main/java/io/starter/formats/XLS/charts/Axesused.java @@ -5,36 +5,43 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; - /** * Axesused: Number of Axes Sets (0x1046) * * 4 cAxes 2 number of axes sets */ + public class Axesused extends GenericChartObject implements ChartObject { /** * serialVersionUID */ private static final long serialVersionUID = -4809979818183513617L; + @Override public void init() { super.init(); } diff --git a/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java b/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java index 8c08d8a..8bfc937 100644 --- a/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java @@ -139,7 +139,7 @@ public String getSVG(HashMap chartMetrics, HashMap\r\n"); else @@ -151,7 +151,7 @@ public String getSVG(HashMap chartMetrics, HashMap\r\n"); return svg.toString(); } diff --git a/src/main/java/io/starter/formats/XLS/charts/Chart.java b/src/main/java/io/starter/formats/XLS/charts/Chart.java index e4c050f..fd59a10 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Chart.java +++ b/src/main/java/io/starter/formats/XLS/charts/Chart.java @@ -1761,7 +1761,7 @@ public HashMap getMetrics(WorkBookHandle wbh) { // TODO: legend adjustment may have to do with y title and label ofsets ...? { adjust= 2*lcoords[4]; // spacing before and after legend box TODO this isn't correct !! //KSC: TESTING! -//io.starter.OpenXLS.util.Logger.log("Original lcoords: " + Arrays.toString(lcoords)); +//io.starter.toolkit.Logger.log("Original lcoords: " + Arrays.toString(lcoords)); } else { lcoords= new int[6]; lcoords[0]= chartMetrics.get("canvasw").intValue(); @@ -1771,14 +1771,14 @@ public HashMap getMetrics(WorkBookHandle wbh) { lcoords[0]= chartMetrics.get("canvasw").intValue(); } double ldist= lcoords[0]-chartMetrics.get("w"); // save distance between legend box and w (significant if legend is on rhs) -//io.starter.OpenXLS.util.Logger.log("Before Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); +//io.starter.toolkit.Logger.log("Before Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); // now adjust plot area coordinates based on canvas w, h, title and label offsets, and legend box, if any chartMetrics.put("x", (Double)chartMetrics.get("x") + (Double) this.getAxes().axisMetrics.get("YAXISLABELOFFSET") + (Double) this.getAxes().axisMetrics.get("YAXISTITLEOFFSET")); chartMetrics.put("y", (Double) chartMetrics.get("y") + chartMetrics.get("TITLEOFFSET")); // TODO: seems that w is different doesn't need decrementing by x?? check out ... chartMetrics.put("w", (Double)chartMetrics.get("w") - (Double) this.getAxes().axisMetrics.get("YAXISLABELOFFSET")); chartMetrics.put("h", (Double) chartMetrics.get("canvash")- (Double)chartMetrics.get("y") - (Double) this.getAxes().axisMetrics.get("XAXISLABELOFFSET") - (Double) this.getAxes().axisMetrics.get("XAXISTITLEOFFSET") - 10); -//io.starter.OpenXLS.util.Logger.log("After Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); +//io.starter.toolkit.Logger.log("After Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); double cw= chartMetrics.get("canvasw"); // rhs legend has to have some extra adjustments to w and/or canvasw ... @@ -1797,7 +1797,7 @@ public HashMap getMetrics(WorkBookHandle wbh) { if (this.getAxes().hasAxis(XAXIS) && ldist > 0) // pie, donut, don't // ensure distance between legend box and edge of plot area remains the same chartMetrics.put("w", lcoords[0]-(Double)chartMetrics.get("x")-ldist); -//io.starter.OpenXLS.util.Logger.log("Adjusted LCoords: " + Arrays.toString(lcoords)); +//io.starter.toolkit.Logger.log("Adjusted LCoords: " + Arrays.toString(lcoords)); } else { double w= chartMetrics.get("w")+chartMetrics.get("x"); diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java b/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java index 6d54627..9e5d91a 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java @@ -745,7 +745,7 @@ else if (idx==-1 || ranges[idx].equals("")) { // shoudln't!! can't have a textua else rc[2]-=(clist.length-npoints); // KSC: TESTING: REMOVE WHEN DONE -//io.starter.OpenXLS.util.Logger.log("Truncate list: old range: " + ranges[idx] + " new range: " + cells.getSheet().getQualifiedSheetName() + "!" + ExcelTools.formatLocation(rc)); +//io.starter.toolkit.Logger.log("Truncate list: old range: " + ranges[idx] + " new range: " + cells.getSheet().getQualifiedSheetName() + "!" + ExcelTools.formatLocation(rc)); ranges[idx]= cells.getSheet().getQualifiedSheetName() + "!" + ExcelTools.formatLocation(rc); } diff --git a/src/main/java/io/starter/formats/XLS/charts/Legend.java b/src/main/java/io/starter/formats/XLS/charts/Legend.java index 1a35e1e..60b6bb3 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Legend.java +++ b/src/main/java/io/starter/formats/XLS/charts/Legend.java @@ -315,7 +315,7 @@ public void adjustWidth(HashMap chartMetrics, int chartType, Str else legendsWidth += StringTool.getApproximateStringWidth(jf, " "); -// io.starter.OpenXLS.util.Logger.log(this.getParentChart().toString() + String.format(": legend box x: %.1f legend box w: %.0f chart x: %.1f w: %.1f cw: %.1f font size: %.0f L.W: %.1f Auto? %b Vertical? %b", +// io.starter.toolkit.Logger.log(this.getParentChart().toString() + String.format(": legend box x: %.1f legend box w: %.0f chart x: %.1f w: %.1f cw: %.1f font size: %.0f L.W: %.1f Auto? %b Vertical? %b", // x, (float)coords[2], chartMetrics.get("x"), w, cw, (float) jf.getSize(), legendsWidth, fAutoPosition, fVert)); p.setLegendW((int)legendsWidth); if (((x + legendsWidth) > cw) || (position==Legend.RIGHT || position==Legend.CORNER)) { @@ -348,11 +348,11 @@ else if (position==Legend.LEFT) { if (totalWidth > (cw-w)) { // can fit in space between plot area and //KSC: TESTINGs - //io.starter.OpenXLS.util.Logger.log("Original Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); + //io.starter.toolkit.Logger.log("Original Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); float newX= (int)Math.ceil(cw-w); if (originalDist > 0 && (w+x) > newX) chartMetrics.put("w", newX-originalDist); - //io.starter.OpenXLS.util.Logger.log("After Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); + //io.starter.toolkit.Logger.log("After Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); } } } else if (totalWidth > cw) { // legends are displayed horizontally, can't fit in canvas width @@ -422,12 +422,12 @@ public int[] getCoords(int charttype, HashMap chartMetrics, Stri double cw= chartMetrics.get("canvasw"); double w= chartMetrics.get("w"); // KSC: TESTING -//io.starter.OpenXLS.util.Logger.log("Original Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); +//io.starter.toolkit.Logger.log("Original Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); double ldist= retcoords[0]-w; // original distance between legend and edge of plot area retcoords[0]= (int)Math.ceil(cw-retcoords[2]); if (ldist > 0 && (chartMetrics.get("w")+chartMetrics.get("x")) > retcoords[0]) chartMetrics.put("w", retcoords[0]-ldist); -//io.starter.OpenXLS.util.Logger.log("After Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); +//io.starter.toolkit.Logger.log("After Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); } } diff --git a/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java b/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java index 6ac53bc..f94189a 100644 --- a/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java @@ -63,7 +63,7 @@ public class OOXMLChart extends Chart { */ public OOXMLChart(Chart c, WorkBookHandle wbh) { // Walk up the superclass hierarchy - //io.starter.OpenXLS.util.Logger.log("BEFORE: chartArr: " + Arrays.toString(chartArr.toArray())); + //io.starter.toolkit.Logger.log("BEFORE: chartArr: " + Arrays.toString(chartArr.toArray())); for (Class obj = c.getClass(); !obj.equals(Object.class); obj = obj.getSuperclass()) diff --git a/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java index 96de7e9..6790102 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java @@ -399,7 +399,7 @@ protected static Ptg calcNetWorkdays(Ptg[] operands) { */ protected static Ptg calcNow(Ptg[] operands){ GregorianCalendar gc = new GregorianCalendar(TimeZone.getDefault());//java.sql.Date dt = new java.sql.Date(); - // io.starter.OpenXLS.util.Logger.log(dt.toGMTString()); + // io.starter.toolkit.Logger.log(dt.toGMTString()); double retdate = DateConverter.getXLSDateVal(gc); return new PtgNumber(retdate); } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java b/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java index 884241f..257b05f 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java @@ -5,544 +5,638 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; +import java.io.UnsupportedEncodingException; + +import io.starter.formats.XLS.ExpressionParser; import io.starter.toolkit.ByteTools; import io.starter.toolkit.CompatibleVector; import io.starter.toolkit.Logger; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; - - -/* ARRAY CONSTANT followed by 7 reserved bytes. +/* + * ARRAY CONSTANT followed by 7 reserved bytes. * - * The token value for ptgArray consists of the array dimensions and the array values + * The token value for ptgArray consists of the array + * dimensions and the array values * - * ptgArray differs from most other operand tokens in that the token value doesn't follow the token type. + * ptgArray differs from most other operand tokens in that + * the token value doesn't follow the token type. * - * Instead, the token value is appended to the saved parsed expression, immediately following the last token. + * Instead, the token value is appended to the saved parsed + * expression, immediately following the last token. * - * Offset Name Size Contents - ----------------------------------------------------------- - 0 nc 1 number of columns -1 in array constant (0 = 256) - 1 nr 2 number of rows -1 in array constant - 3 rgval var the array vals (k+1)*(nr+1) length + * Offset Name Size Contents + * --------------------------------------------------------- + * -- + * 0 nc 1 number of columns -1 in array constant (0 = 256) + * 1 nr 2 number of rows -1 in array constant + * 3 rgval var the array vals (k+1)*(nr+1) length * * - * The format of the token value is shown in the following table. + * The format of the token value is shown in the following + * table. * - * The number of values in the array constant is equal to the product of the array dimensions, (nc+1)*(nr+1_ + * The number of values in the array constant is equal to + * the product of the array dimensions, (nc+1)*(nr+1_ * - * Each value is either an 8-byte IEEE fp numbr or a string. The two formats for these values are shown in the following tables. + * Each value is either an 8-byte IEEE fp numbr or a string. + * The two formats for these values are shown in the + * following tables. * * * IEEE FP Number - * Offset Name Size Contents - * ----------------------------------------------------------- - * 0 grbit 1 =01h - * 1 num 8 IEEE FP number + * Offset Name Size Contents + * --------------------------------------------------------- + * -- + * 0 grbit 1 =01h + * 1 num 8 IEEE FP number * * String - * Offset Name Size Contents - * ----------------------------------------------------------- - * 0 grbit 1 =02h - * 1 cch 1 Length of the String - * 2 rgch var the string. + * Offset Name Size Contents + * --------------------------------------------------------- + * -- + * 0 grbit 1 =02h + * 1 cch 1 Length of the String + * 2 rgch var the string. * - * If a formula contains more than one array constant, the token values for the array constants are appended to the saved - * parsed expression in order: first the values for the first array constant, + * If a formula contains more than one array constant, the + * token values for the array constants are appended to the + * saved + * parsed expression in order: first the values for the + * first array constant, * then the values for the second array constant, etc. * - * If a formula contains very long array constants, the FORMULA, ARRAY, or NAME record contaniing the parsed expression - * may overflow into CONTINUE records. In such cases, an individual array value is NEVER SPLIT between records, - * but record boundaries are established between adjacent array values. + * If a formula contains very long array constants, the + * FORMULA, ARRAY, or NAME record contaniing the parsed + * expression + * may overflow into CONTINUE records. In such cases, an + * individual array value is NEVER SPLIT between records, + * but record boundaries are established between adjacent + * array values. * - * The reference class ptgArray never appears in an Excel formula, only the ptgArrayV and ptgArrayA classes are used. + * The reference class ptgArray never appears in an Excel + * formula, only the ptgArrayV and ptgArrayA classes are + * used. * * * @see Ptg + * * @see Formula - -*/ + * + */ // 20090119-22 KSC: Many, many changes changes -public class PtgArray extends GenericPtg implements Ptg{ - /** +public class PtgArray extends GenericPtg implements Ptg { + /** * serialVersionUID */ - private static final long serialVersionUID = 4416140231168551393L; - int nc = -1; - int nr = -1; - byte[] rgval; - CompatibleVector arrVals = new CompatibleVector(); - boolean isIntermediary= false; // 20090824 KSC: true if this PtgArray is only part of a calcualtion process; if so, apparently can have more than 256 columns [BugTracker 2683] - + private static final long serialVersionUID = 4416140231168551393L; + int nc = -1; + int nr = -1; + byte[] rgval; + CompatibleVector arrVals = new CompatibleVector(); + boolean isIntermediary = false; // 20090824 + // KSC: + // true + // if + // this + // PtgArray + // is + // only + // part + // of + // a + // calcualtion + // process; + // if + // so, + // apparently + // can + // have + // more + // than + // 256 + // columns + // [BugTracker + // 2683] - - public boolean getIsOperand(){ - return true; - } - - /** return the first 8 bytes of the ptgArray record - * this represents the id byte and 7 reserved bytes - * @return - */ - public byte[] getPreRecord(){ - /* 20090820 KSC: now record is always 8 bytes as rgval is now kept separate [BugTracker 2683] - byte[] retbytes = new byte[8]; - if (record!=null) - System.arraycopy(record, 0, retbytes, 0, 8); - return retbytes; - */ + @Override + public boolean getIsOperand() { + return true; + } + + /** return the first 8 bytes of the ptgArray record + * this represents the id byte and 7 reserved bytes + * @return + */ + public byte[] getPreRecord() { return record; } - + /** * these are the bytes appended to the formula token array, after all other ptg's * @return */ - public byte[] getPostRecord(){ + public byte[] getPostRecord() { return rgval; - /* 20090820 KSC: now record and rgval are kept separated (see populateVals) [BugTracker 2683] - if (record==null) { // why?? - return new byte[0]; // hits on testScenario[28] - } - byte[] retbytes = new byte[record.length -8]; - System.arraycopy(record, 8, retbytes, 0, (record.length-8)); - return retbytes; - */ } - public void init(byte[] b){ - ptgId = b[0]; - record = b; - this.populateVals(); - } - - private void populateVals() { -/* 20090819 KSC: according to documentation, this isn't correct: [BugTracker 2683] - nc= record[1] & 0x00FF; - nr = ByteTools.readShort(record[2], record[3]); - * if (record.length>11) { - rgval = new byte[record.length-11]; - System.arraycopy(record, 11, rgval, 0, rgval.length); - this.parseArrayComponents(); + @Override + public void init(byte[] b) { + ptgId = b[0]; + record = b; + this.populateVals(); + } + + private void populateVals() { + if (record.length > 8) { // means that array data has already been + // appeneded to end of record array; store + // in rgvals + rgval = new byte[record.length - 8]; + System.arraycopy(record, 8, rgval, 0, rgval.length); // save post + // array= + // nc, nr + + // array + // data } -*/ - if (record.length>8) { // means that array data has already been appeneded to end of record array; store in rgvals - rgval = new byte[record.length-8]; - System.arraycopy(record, 8, rgval, 0, rgval.length); // save post array= nc, nr + array data - } - if (rgval!=null) { + if (rgval != null) { // clear out record array:0= id 1-7=reserved - byte[] b= new byte[8]; - b[0]= record[0]; - record= b; + byte[] b = new byte[8]; + b[0] = record[0]; + record = b; this.parseArrayComponents(); - } // otherwise, it's just the initial input of the 1st 8 bytes record - see Formula - } - - /** - * given "extra info" at end of formula expression, parse array values - */ - public void parseArrayComponents() { - int nitems= 0; - arrVals.clear(); // 20090820 KSC: makes sense to! [BugTracker 2683] - if (!isIntermediary) // 20090824 KSC: sometimes an intermediary ptgarry can have more than 256 columns [BugTracker 2683] - nc= rgval[0] & 0xFF; // number of columns - nr = ByteTools.readShort(rgval[1], rgval[2]); // number of rows - try { - // (nc+1)*(nr+1) compoments - for (int i=3;i=255) { // 20090824 KSC: apparently sometimes an intermediary calculations step can include > 256 array elements ... - isIntermediary= true; - nc= cols[0].length-1; - } - databytes[8] = (byte)((cols[0].length-1) & 0xFF); // nc-1 // 20090819 KSC: placed in wrong pt of record: was [1] [BugTracker 2683] - //databytes[8] = (byte)((cols[0].length-1)); // nc-1 // 20090819 KSC: placed in wrong pt of record: was [1] [BugTracker 2683] - System.arraycopy(ByteTools.shortToLEBytes((short)(rows.length-1)), 0, databytes, 9, 2); // nr-1 // 20090819 KSC: placed in wrong pt of record: was 2,3 [BugTracker 2683] - // iterate the array and fill out the data section - for (int j= 0; j < rows.length; j++) { - for (int i= 0; i < cols[0].length; i++) { + databytes[0] = 0x60; // 20h=tArrayR, 40h=tArrayV, 60h=tArrayA + isIntermediary = false; // init value + if (cols[0].length >= 255) { // 20090824 KSC: apparently sometimes an + // intermediary calculations step can + // include > 256 array elements ... + isIntermediary = true; + nc = cols[0].length - 1; + } + databytes[8] = (byte) ((cols[0].length - 1) & 0xFF); // nc-1 // 20090819 + // KSC: placed + // in wrong pt + // of record: + // was [1] + // [BugTracker + // 2683] + // databytes[8] = (byte)((cols[0].length-1)); // nc-1 // + // 20090819 KSC: placed in wrong pt of record: was [1] + // [BugTracker 2683] + System.arraycopy(ByteTools + .shortToLEBytes((short) (rows.length - 1)), 0, databytes, 9, 2); // nr-1 + // // + // 20090819 + // KSC: + // placed + // in + // wrong + // pt + // of + // record: + // was + // 2,3 + // [BugTracker + // 2683] + // iterate the array and fill out the data section + for (int j = 0; j < rows.length; j++) { + for (int i = 0; i < cols[0].length; i++) { byte[] valbytes = this.valuesIntoByteArray(cols[j][i]); databytes = ByteTools.append(valbytes, databytes); } } - // populate primary values for rec - record = databytes; - this.init(databytes); - } - - /** - * Turns a vector of values into a byte array representation for the data section of this record - * @param compVect - * @return - */ - private byte[] valuesIntoByteArray(String constVal){ + // populate primary values for rec + record = databytes; + this.init(databytes); + } + + /** + * Turns a vector of values into a byte array representation for the data section of this record + * @param compVect + * @return + */ + private byte[] valuesIntoByteArray(String constVal) { byte[] databytes = new byte[0]; - byte[] thisElement= new byte[9]; + byte[] thisElement = new byte[9]; - try{ // number? + try { // number? Double d = new Double(constVal); - thisElement[0] = 0x1; // id for number value + thisElement[0] = 0x1; // id for number value byte[] b = ByteTools.toBEByteArray(d.doubleValue()); System.arraycopy(b, 0, thisElement, 1, b.length); databytes = ByteTools.append(thisElement, databytes); - }catch(NumberFormatException ee){ + } catch (NumberFormatException ee) { try { - if (constVal.equalsIgnoreCase("true") || constVal.equalsIgnoreCase("false")) { - Boolean bb= Boolean.valueOf(constVal); - thisElement[0] = 0x4; // id for boolean value - thisElement[1]= (byte) (bb.booleanValue()?1:0); - } else if (constVal==null || constVal.equals("")) { // emtpy or null value - thisElement[0] = 0x0; // id for empty value - } else if (constVal.charAt(0)=='#') { // it's an error value - thisElement[0] = 0x10; // id for error value - int errCode= 0; - if (constVal.equals("#NULL!")) - errCode= 0; - else if (constVal.equals("#DIV/0!")) - errCode= 0x7; - else if (constVal.equals("#VALUE!")) - errCode= 0x0F; - else if (constVal.equals("#REF!")) - errCode= 0x17; - else if (constVal.equals("#NAME!")) - errCode= 0x1D; - else if (constVal.equals("#NUM!")) - errCode= 0x24; - else if (constVal.equals("#N/A!") || constVal.equals("#N/A") || constVal.equals("N/A")) - errCode= 0x2A; - thisElement[1]= (byte) errCode; - } else { // assume string + if (constVal.equalsIgnoreCase("true") + || constVal.equalsIgnoreCase("false")) { + Boolean bb = Boolean.valueOf(constVal); + thisElement[0] = 0x4; // id for boolean value + thisElement[1] = (byte) (bb.booleanValue() ? 1 : 0); + } else if (constVal == null || constVal.equals("")) { // emtpy + // or + // null + // value + thisElement[0] = 0x0; // id for empty value + } else if (constVal.charAt(0) == '#') { // it's an error value + thisElement[0] = 0x10; // id for error value + int errCode = 0; + if (constVal.equals("#NULL!")) + errCode = 0; + else if (constVal.equals("#DIV/0!")) + errCode = 0x7; + else if (constVal.equals("#VALUE!")) + errCode = 0x0F; + else if (constVal.equals("#REF!")) + errCode = 0x17; + else if (constVal.equals("#NAME!")) + errCode = 0x1D; + else if (constVal.equals("#NUM!")) + errCode = 0x24; + else if (constVal.equals("#N/A!") || constVal.equals("#N/A") + || constVal.equals("N/A")) + errCode = 0x2A; + thisElement[1] = (byte) errCode; + } else { // assume string thisElement = new byte[3]; - try{ + try { thisElement = new byte[4]; - thisElement[0] = 0x2; // id for string + thisElement[0] = 0x2; // id for string byte[] b = constVal.getBytes(UNICODEENCODING); - System.arraycopy(ByteTools.shortToLEBytes((short)b.length), 0, thisElement, 1, 2); - thisElement[3]= 1; // compressed= 0, uncompressed= 1 (16-bit chars) - thisElement= ByteTools.append(b, thisElement); - }catch(UnsupportedEncodingException z){ - Logger.logWarn("encoding formula array:" +z);} + System.arraycopy(ByteTools + .shortToLEBytes((short) b.length), 0, thisElement, 1, 2); + thisElement[3] = 1; // compressed= 0, uncompressed= 1 + // (16-bit chars) + thisElement = ByteTools.append(b, thisElement); + } catch (UnsupportedEncodingException z) { + Logger.logWarn("encoding formula array:" + z); + } } - databytes = ByteTools.append(thisElement, databytes); - } catch(Exception ex) { - Logger.logWarn("PtgArray.valuesIntoByteArray: error parsing array element:" +ex); + databytes = ByteTools.append(thisElement, databytes); + } catch (Exception ex) { + Logger.logWarn("PtgArray.valuesIntoByteArray: error parsing array element:" + + ex); } } return databytes; - } - - /** - * Returns the second section of bytes for the PtgArray. - * These are the bytes that are split off the end of the - * formula - - public void getComponentBytes(){ - - } - //public void updateRecord(){ - //}*/ - + } + + /** + * Returns the second section of bytes for the PtgArray. + * These are the bytes that are split off the end of the + * formula + + public void getComponentBytes(){ + + } + //public void updateRecord(){ + //}*/ + /** Override due to mystery extra byte * occasionally found in ptgName recs. * - */ - public int getLength(){ - /* 20090820 KSC: really want record length not rgval length, which now is separate [BugTracker 2683] - *if (rgval!=null) - return rgval.length; - */ - return 8; - } - - /* not used - public int getLength(byte[] b){ - int co = b[1]; - int rw = ByteTools.readShort(b[2], b[3]); - rw++; // appears that rows are not ordinal here... - int numrecs = co*rw; - int len = 4; - int loc = 4; - for (int i=0;i<=numrecs;i++){ - if (b[len] == 0x1){ // its a number - len += 9; - }else{ - len += b[len+1] + 2; - } - } - length = len; - return length; - } - */ + */ + @Override + public int getLength() { + /* + * 20090820 KSC: really want record length not rgval length, + * which now is separate [BugTracker 2683] + * if (rgval!=null) + * return rgval.length; + */ + return 8; + } + + /* + * not used + * public int getLength(byte[] b){ + * int co = b[1]; + * int rw = ByteTools.readShort(b[2], b[3]); + * rw++; // appears that rows are not ordinal here... + * int numrecs = co*rw; + * int len = 4; + * int loc = 4; + * for (int i=0;i<=numrecs;i++){ + * if (b[len] == 0x1){ // its a number + * len += 9; + * }else{ + * len += b[len+1] + 2; + * } + * } + * length = len; + * return length; + * } + */ + + @Override + public String toString() { + return this.getString(); + } - public String toString() { - return this.getString(); - } - - public Ptg[] getComponents(){ - Ptg[] retVals = new Ptg[arrVals.size()]; - for(int i=0;i1 then the array is in the form of: - * a,b,c;d,e,f; .... where the semicolons delineate rows - * @return - */ - public int getNumberOfRows() { - return nr; - } - - /** - * returns the 0-based number of columns in this array - * number of columns is the amount of elements before the semicolon (if present) - * a,b,c;d,e,f; .... - * @return - */ - public int getNumberOfColumns() { - return nc; - } - - - /** sets the array components values for this PtgArray - * returns the actual array components length - * @see ExpressionParser.parseExpression - */ - public int setArrVals(byte[] by){ - rgval = by; - if (rgval!=null) { + /** + * returns the 0-based number of rows in this array + * if nr>1 then the array is in the form of: + * a,b,c;d,e,f; .... where the semicolons delineate rows + * @return + */ + public int getNumberOfRows() { + return nr; + } + + /** + * returns the 0-based number of columns in this array + * number of columns is the amount of elements before the semicolon (if present) + * a,b,c;d,e,f; .... + * @return + */ + public int getNumberOfColumns() { + return nc; + } + + /** sets the array components values for this PtgArray + * returns the actual array components length + * @see ExpressionParser.parseExpression + */ + public int setArrVals(byte[] by) { + rgval = by; + if (rgval != null) { // clear out record array:0= id 1-7=reserved - byte[] b= new byte[8]; - b[0]= record[0]; - record= b; + byte[] b = new byte[8]; + b[0] = record[0]; + record = b; this.parseArrayComponents(); - } + } return rgval.length; - - } - - public byte[] getArrVals(){ - return rgval; - } - - /** - * returns a ptg at the specified location. Assumes that it is a one-dimensional - * array. If you need a multidimensional array please use the other elementAt(int,int)method - * @param loc - * @return - */ - public Ptg elementAt(int loc){ - Ptg[] p = this.getComponents(); - return p[loc]; + + } + + public byte[] getArrVals() { + return rgval; + } + + /** + * returns a ptg at the specified location. Assumes that it is a one-dimensional + * array. If you need a multidimensional array please use the other elementAt(int,int)method + * @param loc + * @return + */ + public Ptg elementAt(int loc) { + Ptg[] p = this.getComponents(); + return p[loc]; } - - - public Ptg elementAt(int col, int row){ + + public Ptg elementAt(int col, int row) { Ptg[] p = this.getComponents(); try { int loc = 0; - for (int i=0;i. * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; /* -PtgErr is exactly what one would think it is, a ptg that describes an Error -value. - -Offset Name Size Contents -------------------------------------------- -0 err 1 An error value - -*/ + * PtgErr is exactly what one would think it is, a ptg that + * describes an Error + * value. + *
+ * Offset Name Size Contents
+ * -------------------------------------------
+ * 0 err 1 An error value
+ * 
+ * + */ -public class PtgErr extends GenericPtg implements Ptg{ +public class PtgErr extends GenericPtg implements Ptg { - /** + /** * serialVersionUID */ - private static final long serialVersionUID = -5201871987022621869L; - public static byte ERROR_NULL = 0x0; - public static byte ERROR_DIV_ZERO = 0x7; - public static byte ERROR_VALUE = 0xF; - public static byte ERROR_REF = 0x17; - public static byte ERROR_NAME = 0x1D; - public static byte ERROR_NUM = 0x24; - public static byte ERROR_NA = 0x2A; - - private boolean isCircularError = false; - - - public boolean isCircularError() { + private static final long serialVersionUID = -5201871987022621869L; + public static byte ERROR_NULL = 0x0; + public static byte ERROR_DIV_ZERO = 0x7; + public static byte ERROR_VALUE = 0xF; + public static byte ERROR_REF = 0x17; + public static byte ERROR_NAME = 0x1D; + public static byte ERROR_NUM = 0x24; + public static byte ERROR_NA = 0x2A; + + private boolean isCircularError = false; + + public boolean isCircularError() { return isCircularError; } @@ -58,59 +66,81 @@ public void setCircularError(boolean isCircularError) { } private String errorValue = null; - public boolean getIsOperand(){ return true;} - - public PtgErr(){ - // default constructor - } - - public PtgErr(byte errorV){ - record = new byte[2]; - record[0] = 0x1C; - record[1] = errorV; - } - - public byte getErrorType(){ - return record[1]; - } - - public Object getValue(){ - return toString(); - } - - public void init(byte[] b){ - ptgId = b[0]; - record = b; - } - - public String toString(){ - if(isCircularError())return "#CIR_ERR!"; - byte b = record[1]; - // duh, should have done a switch - if (b == ERROR_NULL) errorValue = "#ERROR!"; - else if (b == ERROR_DIV_ZERO) errorValue = "#DIV/0!"; - else if (b == ERROR_VALUE) errorValue = "#VALUE!"; - else if (b == ERROR_REF) errorValue = "#REF!"; - else if (b == ERROR_NAME) errorValue = "#NAME?"; - else if (b == ERROR_NUM) errorValue = "#NUM!"; - else if (b == ERROR_NA) errorValue = "#N/A"; - return errorValue; - } - - public static byte convertStringToLookupByte(String errorString) { - if(errorString.equals("#ERROR!"))return ERROR_NULL; - if(errorString.equals("#DIV/0!"))return ERROR_DIV_ZERO; - if(errorString.equals("#REF!"))return ERROR_VALUE; - if(errorString.equals("#ERROR!"))return ERROR_REF; - if(errorString.equals("#NAME?"))return ERROR_NAME; - if(errorString.equals("#NUM!"))return ERROR_NUM; - if(errorString.equals("#N/A"))return ERROR_NA; - return ERROR_NULL; - } - - public int getLength(){ - return 2; - } - - + + @Override + public boolean getIsOperand() { + return true; + } + + public PtgErr() { + // default constructor + } + + public PtgErr(byte errorV) { + record = new byte[2]; + record[0] = 0x1C; + record[1] = errorV; + } + + public byte getErrorType() { + return record[1]; + } + + @Override + public Object getValue() { + return toString(); + } + + @Override + public void init(byte[] b) { + ptgId = b[0]; + record = b; + } + + @Override + public String toString() { + if (isCircularError()) + return "#CIR_ERR!"; + byte b = record[1]; + // duh, should have done a switch + if (b == ERROR_NULL) + errorValue = "#ERROR!"; + else if (b == ERROR_DIV_ZERO) + errorValue = "#DIV/0!"; + else if (b == ERROR_VALUE) + errorValue = "#VALUE!"; + else if (b == ERROR_REF) + errorValue = "#REF!"; + else if (b == ERROR_NAME) + errorValue = "#NAME?"; + else if (b == ERROR_NUM) + errorValue = "#NUM!"; + else if (b == ERROR_NA) + errorValue = "#N/A"; + return errorValue; + } + + public static byte convertStringToLookupByte(String errorString) { + if (errorString.equals("#ERROR!")) + return ERROR_NULL; + if (errorString.equals("#DIV/0!")) + return ERROR_DIV_ZERO; + if (errorString.equals("#REF!")) + return ERROR_VALUE; + if (errorString.equals("#ERROR!")) + return ERROR_REF; + if (errorString.equals("#NAME?")) + return ERROR_NAME; + if (errorString.equals("#NUM!")) + return ERROR_NUM; + if (errorString.equals("#N/A")) + return ERROR_NA; + return ERROR_NULL; + } + + @Override + public int getLength() { + return 2; + } + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java index 06de0fd..fe17adc 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java @@ -171,7 +171,7 @@ public byte[] getPostRecord(){ } /* KSC: TESTING if (!Arrays.equals(recbytes, postExp)) - io.starter.OpenXLS.util.Logger.log("ISSUE!!!");*/ + io.starter.toolkit.Logger.log("ISSUE!!!");*/ return recbytes; } diff --git a/src/main/java/io/starter/formats/XML/UnicodeInputStream.java b/src/main/java/io/starter/formats/XML/UnicodeInputStream.java index 78001b9..bd63740 100644 --- a/src/main/java/io/starter/formats/XML/UnicodeInputStream.java +++ b/src/main/java/io/starter/formats/XML/UnicodeInputStream.java @@ -136,7 +136,7 @@ protected void init() throws IOException { encoding = defaultEnc; unread = n; } - // io.starter.OpenXLS.util.Logger.log("read=" + n + ", unread=" + unread); + // io.starter.toolkit.Logger.log("read=" + n + ", unread=" + unread); if (unread > 0) internalIn.unread(bom, (n - unread), unread); diff --git a/src/main/java/io/starter/toolkit/Base64.java b/src/main/java/io/starter/toolkit/Base64.java index 16a679c..3531ede 100644 --- a/src/main/java/io/starter/toolkit/Base64.java +++ b/src/main/java/io/starter/toolkit/Base64.java @@ -632,10 +632,10 @@ else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) return 3; }catch( Exception e){ - io.starter.OpenXLS.util.Logger.log(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); - io.starter.OpenXLS.util.Logger.log(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); - io.starter.OpenXLS.util.Logger.log(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); - io.starter.OpenXLS.util.Logger.log(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); + io.starter.toolkit.Logger.log(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); + io.starter.toolkit.Logger.log(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); + io.starter.toolkit.Logger.log(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); + io.starter.toolkit.Logger.log(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); return -1; } //e nd catch } diff --git a/src/main/java/io/starter/toolkit/LogOutputter.java b/src/main/java/io/starter/toolkit/LogOutputter.java index 72a1ba1..704f5b9 100644 --- a/src/main/java/io/starter/toolkit/LogOutputter.java +++ b/src/main/java/io/starter/toolkit/LogOutputter.java @@ -5,38 +5,32 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.toolkit; -public interface LogOutputter -{ - /** output the string to the log - */ - void log(String s); - - /** output the string to the log - */ - void log(String s, Exception e); +public interface LogOutputter { + + void log(String string); - /** Output an exception to the log, print stack trace optional - * - * - * @param s - * @param e - */ - public void log(String s, Exception e, boolean prt); } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/Logger.java b/src/main/java/io/starter/toolkit/Logger.java index 93f914b..b3a4dc8 100644 --- a/src/main/java/io/starter/toolkit/Logger.java +++ b/src/main/java/io/starter/toolkit/Logger.java @@ -5,22 +5,29 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ -/** +/** * Logger.java * * @@ -46,9 +53,9 @@ * *
* Logger can be used to output standardized messages to System.out and System.err, as well as - * to pluggable LogOutputter implementations. + * to pluggable Logger implementations. *

- * To install a custom LogOutputter implementation, instantiate a class that implements LogOutputter, then + * To install a custom Logger implementation, instantiate a class that implements Logger, then * set the system property: "io.starter.toolkit.logger" *

* For example: @@ -67,239 +74,263 @@ * * */ -public class Logger -extends PrintStream implements LogOutputter { +public class Logger extends PrintStream { /** @deprecated Just use this. */ - protected PrintStream ous = this; - + @Deprecated + protected PrintStream ous = this; + /** Copy of line.separator system property to save lookups. */ - private static final String endl = System.getProperty( "line.separator" ); - - private LogOutputter targetLogger; - private BufferedWriter targetWriter; - private StringBuffer lineBuffer = new StringBuffer(); - private boolean autoFlush; - - public Logger (LogOutputter target) { + private static final String endl = System + .getProperty("line.separator"); + + private static Logger targetLogger; + private static BufferedWriter targetWriter; + private StringBuffer lineBuffer = new StringBuffer(); + private static boolean autoFlush; + + public Logger(Logger target) { this(); - this.init( target ); + this.init(target); } - - public Logger (LogOutputter target, String charset) - throws UnsupportedEncodingException { - this( charset ); - this.init( target ); + + public Logger(Logger target, String charset) + throws UnsupportedEncodingException { + this(charset); + this.init(target); } - - public Logger (OutputStream target) { - this( target, false ); + + public Logger(OutputStream target) { + this(target, false); } - - public Logger (OutputStream target, boolean autoFlush) { - this( new OutputStreamWriter( target ), autoFlush ); + + public Logger(OutputStream target, boolean autoFlush) { + this(new OutputStreamWriter(target), autoFlush); } - - public Logger (OutputStream target, String charset, boolean autoFlush) - throws UnsupportedEncodingException { - this( new OutputStreamWriter( target, charset ), charset, autoFlush ); + + public Logger(OutputStream target, String charset, boolean autoFlush) + throws UnsupportedEncodingException { + this(new OutputStreamWriter(target, charset), charset, autoFlush); } - - public Logger (Writer target) { - this( target, false ); + + public Logger(Writer target) { + this(target, false); } - - public Logger (Writer target, boolean autoFlush) { + + public Logger(Writer target, boolean autoFlush) { this(); - this.init( target, autoFlush ); + this.init(target, autoFlush); } - - public Logger (Writer target, String charset, boolean autoFlush) - throws UnsupportedEncodingException { - this( charset ); - this.init( target, autoFlush ); + + public Logger(Writer target, String charset, boolean autoFlush) + throws UnsupportedEncodingException { + this(charset); + this.init(target, autoFlush); } - + private Logger() { - super( new IndirectOutputStream(), true ); - ((IndirectOutputStream) out).setSink( - new WriterOutputStream( this, Charset.defaultCharset() ) ); - } - - private Logger (String charset) - throws UnsupportedEncodingException { - super( new IndirectOutputStream(), true, charset ); - ((IndirectOutputStream) out).setSink( - new WriterOutputStream( this, charset ) ); - } - - private void init (LogOutputter target) { + super(new IndirectOutputStream(), true); + ((IndirectOutputStream) out).setSink(new WriterOutputStream(this, + Charset.defaultCharset())); + } + + private Logger(String charset) throws UnsupportedEncodingException { + super(new IndirectOutputStream(), true, charset); + ((IndirectOutputStream) out) + .setSink(new WriterOutputStream(this, charset)); + } + + private void init(Logger target) { targetLogger = target; targetWriter = null; - autoFlush = false; // has no meaning for LogOutputter target + autoFlush = false; // has no meaning for Logger target } - - private void init (Writer target, boolean autoFlush) { + + private void init(Writer target, boolean autoFlush) { targetLogger = null; - targetWriter = new BufferedWriter( target ); - this.autoFlush = autoFlush; + targetWriter = new BufferedWriter(target); + // autoFlush = autoFlush; } - + /** Installs this logger as the default logger and replaces the standard * output and error streams. */ public void install() { - setLogger( this ); - System.setOut( this ); - System.setErr( this ); - } - - /* ---------- LogOutputter methods ---------- */ - - public void log (String message) { - if (null != targetLogger) targetLogger.log( message ); - else synchronized (targetWriter) { - try { - targetWriter.write( getLogDate() ); - targetWriter.write( " " ); - targetWriter.write( message ); - targetWriter.newLine(); - if (autoFlush) targetWriter.flush(); - } catch (IOException ex) { - // we're the logger, so we can't exactly log about it - // the interface doesn't support exceptions so just drop it + setLogger(this); + System.setOut(this); + System.setErr(this); + } + + /* ---------- Logger methods ---------- */ + + public static void log(String message) { + if (null != targetLogger) + Logger.log(message); + else + synchronized (targetWriter) { + try { + targetWriter.write(getLogDate()); + targetWriter.write(" "); + targetWriter.write(message); + targetWriter.newLine(); + if (autoFlush) + targetWriter.flush(); + } catch (IOException ex) { + // we're the logger, so we can't exactly log about it + // the interface doesn't support exceptions so just drop it + } } - } } - - public void log (String message, Exception ex, boolean trace) { - if (null != targetLogger) targetLogger.log( message, ex, trace ); - else this.log( formatThrowable( message, ex, trace ) ); + + public void log(String message, Exception ex, boolean trace) { + if (null != targetLogger) + targetLogger.log(message, ex, trace); + else + log(formatThrowable(message, ex, trace)); } - public void log (String message, Exception ex) { - if (null != targetLogger) targetLogger.log( message, ex ); - else this.log( formatThrowable( message, ex, false ) ); + public void log(String message, Exception ex) { + if (null != targetLogger) + targetLogger.log(message, ex); + else + log(formatThrowable(message, ex, false)); } - + /* ---------- PrintStream methods ---------- */ - + public void logLine() { synchronized (lineBuffer) { // if the line buffer ends with a newline, strip it - int length = lineBuffer.length(); - if (length >= endl.length() - && endl.equals( lineBuffer.substring( - length - endl.length(), length ) )) - lineBuffer.setLength( length - endl.length() ); - + int length = lineBuffer.length(); + if (length >= endl.length() && endl.equals(lineBuffer + .substring(length - endl.length(), length))) + lineBuffer.setLength(length - endl.length()); + // log and reset the line buffer but don't log empty lines if (lineBuffer.length() > 0) { - this.log( lineBuffer.toString() ); - lineBuffer.setLength( 0 ); + log(lineBuffer.toString()); + lineBuffer.setLength(0); } } } - + @Override - public Logger append (char value) { - lineBuffer.append( value ); + public Logger append(char value) { + lineBuffer.append(value); return this; } - + @Override - public Logger append (CharSequence value) { - lineBuffer.append( value ); + public Logger append(CharSequence value) { + lineBuffer.append(value); return this; } - + @Override - public Logger append (CharSequence value, int start, int end) { - lineBuffer.append( value, start, end ); + public Logger append(CharSequence value, int start, int end) { + lineBuffer.append(value, start, end); return this; } - - @Override public void print (boolean b) { - lineBuffer.append( b ); + + @Override + public void print(boolean b) { + lineBuffer.append(b); } - @Override public void print (char c) { - lineBuffer.append( c ); + @Override + public void print(char c) { + lineBuffer.append(c); } - @Override public void print (int i) { - lineBuffer.append( i ); + @Override + public void print(int i) { + lineBuffer.append(i); } - @Override public void print (long l) { - lineBuffer.append( l ); + @Override + public void print(long l) { + lineBuffer.append(l); } - @Override public void print (float f) { - lineBuffer.append( f ); + @Override + public void print(float f) { + lineBuffer.append(f); } - @Override public void print (double d) { - lineBuffer.append( d ); + @Override + public void print(double d) { + lineBuffer.append(d); } - @Override public void print (char[] s) { - lineBuffer.append( s ); + @Override + public void print(char[] s) { + lineBuffer.append(s); } - @Override public void print (String s) { + @Override + public void print(String s) { synchronized (lineBuffer) { - lineBuffer.append( s ); - if (s.endsWith( endl )) + lineBuffer.append(s); + if (s.endsWith(endl)) this.println(); } } - @Override public void print (Object obj) { - lineBuffer.append( obj ); + @Override + public void print(Object obj) { + lineBuffer.append(obj); } - @Override public void println (boolean x) { - lineBuffer.append( x ); + @Override + public void println(boolean x) { + lineBuffer.append(x); this.println(); } - @Override public void println (char x) { - lineBuffer.append( x ); + @Override + public void println(char x) { + lineBuffer.append(x); this.println(); } - @Override public void println (int x) { - lineBuffer.append( x ); + @Override + public void println(int x) { + lineBuffer.append(x); this.println(); } - @Override public void println (long x) { - lineBuffer.append( x ); + @Override + public void println(long x) { + lineBuffer.append(x); this.println(); } - @Override public void println (float x) { - lineBuffer.append( x ); + @Override + public void println(float x) { + lineBuffer.append(x); this.println(); } - @Override public void println (double x) { - lineBuffer.append( x ); + @Override + public void println(double x) { + lineBuffer.append(x); this.println(); } - @Override public void println (char[] x) { - lineBuffer.append( x ); + @Override + public void println(char[] x) { + lineBuffer.append(x); this.println(); } - @Override public void println (String x) { - lineBuffer.append( x ); + @Override + public void println(String x) { + lineBuffer.append(x); this.println(); } - @Override public void println (Object x) { - lineBuffer.append( x ); + @Override + public void println(Object x) { + lineBuffer.append(x); this.println(); } @@ -308,177 +339,183 @@ public void println() { synchronized (lineBuffer) { // flush the input stream into the line buffer super.flush(); - + // log the current line - this.logLine(); - } - } - - /* ---------- static convenience methods for logging ---------- */ - - public static final String INFO_STRING = ""; - public static final String WARN_STRING = "WARNING: "; - public static final String ERROR_STRING = "ERROR: "; - + logLine(); + } + } + + /* + * ---------- static convenience methods for logging + * ---------- + */ + + public static final String INFO_STRING = ""; + public static final String WARN_STRING = "WARNING: "; + public static final String ERROR_STRING = "ERROR: "; + /** Gets the current system logger. */ - public static LogOutputter getLogger() { - LogOutputter logger; - + public static Logger getLogger() { + Logger logger; + try { - logger = (LogOutputter) - System.getProperties().get( "io.starter.toolkit.logger" ); + logger = (Logger) System.getProperties() + .get("io.starter.toolkit.logger"); } catch (Exception ex) { logger = null; } - + if (null == logger) { - if (System.err instanceof Logger) - logger = (Logger) System.err; - else { - logger = new Logger(System.err, true); + if (System.err instanceof Logger) + logger = (Logger) System.err; + else { + logger = new Logger(System.err, true); } - setLogger( logger ); + setLogger(logger); } - + return logger; } - + /** Replaces the system logger. */ - public static void setLogger (LogOutputter logger) { - System.getProperties().put( - "io.starter.toolkit.logger", logger ); + public static void setLogger(Logger logger) { + System.getProperties().put("io.starter.toolkit.logger", logger); } - - public static String formatThrowable ( - String message, Throwable ex, boolean trace) { + + public static String formatThrowable(String message, Throwable ex, boolean trace) { StringWriter writer = new StringWriter(); - writer.write( message ); - + writer.write(message); + if (trace) { - writer.write( endl ); - writer.write( endl ); - - PrintWriter printer = new PrintWriter( writer ); - ex.printStackTrace( printer ); + writer.write(endl); + writer.write(endl); + + PrintWriter printer = new PrintWriter(writer); + ex.printStackTrace(printer); printer.flush(); } else { - writer.write( ": " ); - writer.write( ex.toString() ); + writer.write(": "); + writer.write(ex.toString()); } - + return writer.toString(); } - + /** Logs a fatal error message to the system logger. */ - public static void logErr (String message, Exception ex) { - getLogger().log( ERROR_STRING + message, ex ); + public static void logErr(String message, Exception ex) { + getLogger().log(ERROR_STRING + message, ex); } /** Logs a fatal error message to the system logger. */ - public static void logErr (String message, Throwable ex) { - getLogger().log( formatThrowable( ERROR_STRING + message, ex, false ) ); + public static void logErr(String message, Throwable ex) { + getLogger(); + Logger.log(formatThrowable(ERROR_STRING + message, ex, false)); } - + /** Logs a fatal error message to the system logger. */ - public static void logErr (String message) { - getLogger().log( ERROR_STRING + message ); + public static void logErr(String message) { + getLogger(); + Logger.log(ERROR_STRING + message); } /** Logs a fatal error message to the system logger. */ - public static void logErr (String message, Exception ex, boolean trace) { - getLogger().log( ERROR_STRING + message, ex, trace ); + public static void logErr(String message, Exception ex, boolean trace) { + getLogger().log(ERROR_STRING + message, ex, trace); } - + /** Logs the string conversion of an object to the system logger. */ - public static void log (Object object) { - logInfo( object.toString() ); - } - + public static void log(Object object) { + logInfo(object.toString()); + } + /** Logs a non-fatal warning to the system logger. */ - public static void logWarn (String message) { - getLogger().log( WARN_STRING + message ); - } - + public static void logWarn(String message) { + getLogger(); + Logger.log(WARN_STRING + message); + } + /** Logs the string conversion of an exception to the system logger as a * fatal error message. */ - public static void logErr (Exception ex) { - logErr( ex.toString() ); - } - + public static void logErr(Exception ex) { + logErr(ex.toString()); + } + /** Logs an informational message to the system logger. */ - public static void logInfo (String message) { - getLogger().log( INFO_STRING + message ); - } - + public static void logInfo(String message) { + getLogger(); + Logger.log(INFO_STRING + message); + } + /** Attempts to replace the standard output stream with a * Logger instance that writes to the named file. If the * operation fails a message will be logged to the system logger and the * method will return without throwing an exception. */ - public static void setOut (String filename) { - try{ - java.io.File logfile = new java.io.File(filename); + public static void setOut(String filename) { + try { + java.io.File logfile = new java.io.File(filename); FileOutputStream sysout = new FileOutputStream(logfile); - System.setOut( new Logger( sysout ) ); - }catch(Exception e){ - Logger.logErr("Setting System Output Stream in Logger failed: ",e); + System.setOut(new Logger(sysout)); + } catch (Exception e) { + Logger.logErr("Setting System Output Stream in Logger failed: ", e); } } - + /** Attempts to replace the standard error stream with a * Logger instance that writes to the named file. If the * operation fails a message will be logged to the system logger and the * method will return without throwing an exception. */ - public static void setErr(String filename){ - try{ - java.io.File logfile = new java.io.File(filename); + public static void setErr(String filename) { + try { + java.io.File logfile = new java.io.File(filename); FileOutputStream sysout = new FileOutputStream(logfile); - System.setErr( new Logger( sysout ) ); - }catch(Exception e){ - Logger.logErr("Setting System Error Stream in Logger failed: ",e); + System.setErr(new Logger(sysout)); + } catch (Exception e) { + Logger.logErr("Setting System Error Stream in Logger failed: ", e); } } - + /** The default time stamp format for {@link #getLogDate()}. */ - public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss:SSSS"; - - private static SimpleDateFormat dateFormat = - new SimpleDateFormat( DATE_FORMAT ); - private static String dateSpec = DATE_FORMAT; - + public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss:SSSS"; + + private static SimpleDateFormat dateFormat = new SimpleDateFormat( + DATE_FORMAT); + private static String dateSpec = DATE_FORMAT; + /** Returns the current time in a configurable format. * If the system property io.starter.toolkit.logger.dateformat * exists and is a valid date format pattern it will be used. Otherwise the * {@linkplain #DATE_FORMAT default format pattern} will be used. */ public static String getLogDate() { - String spec = System.getProperty( - "io.starter.toolkit.logger.dateformat" ); + String spec = System + .getProperty("io.starter.toolkit.logger.dateformat"); if (null != spec) { - if ("none".equalsIgnoreCase( spec )) return ""; - - if (!dateSpec.equals( spec )) { + if ("none".equalsIgnoreCase(spec)) + return ""; + + if (!dateSpec.equals(spec)) { try { - dateFormat.applyPattern( spec ); + dateFormat.applyPattern(spec); } catch (IllegalArgumentException e) { - dateFormat.applyPattern( DATE_FORMAT ); + dateFormat.applyPattern(DATE_FORMAT); } - + dateSpec = spec; } } - - return dateFormat.format( new Date() ); + + return dateFormat.format(new Date()); } - + } diff --git a/src/main/java/io/starter/toolkit/ResourceLoader.java b/src/main/java/io/starter/toolkit/ResourceLoader.java index 63f2207..8a36159 100644 --- a/src/main/java/io/starter/toolkit/ResourceLoader.java +++ b/src/main/java/io/starter/toolkit/ResourceLoader.java @@ -244,7 +244,10 @@ public static Object loadClass(String className) { * Loads the named resource from the class path. */ public static byte[] getBytesFromJar(String name) throws IOException { - InputStream source = ResourceLoader.class.getResourceAsStream(name); + + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream source = classLoader.getResourceAsStream("io/starter/OpenXLS/templates/prototype.ser"); + if (source == null) return null; diff --git a/src/main/java/io/starter/toolkit/StringTool.java b/src/main/java/io/starter/toolkit/StringTool.java index bbf46b9..dfb018e 100644 --- a/src/main/java/io/starter/toolkit/StringTool.java +++ b/src/main/java/io/starter/toolkit/StringTool.java @@ -5,24 +5,30 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.toolkit; -import java.awt.font.LineMetrics; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; @@ -31,15 +37,14 @@ import java.nio.CharBuffer; import java.util.StringTokenizer; - - /** * A collection of useful methods for manipulating Strings. * */ public class StringTool implements Serializable { - // static final long serialVersionUID = -5757918511951798619l; + // static final long serialVersionUID = + // -5757918511951798619l; static final long serialVersionUID = -2761264230959133529l; /** @@ -54,7 +59,8 @@ public class StringTool implements Serializable { * @return */ public static String convertXMLChars(String rep) { - return io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(rep).toString(); // 20110815 KSC: this method is more complete + return io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(rep) + .toString(); // 20110815 KSC: this method is more complete } // test stuff @@ -164,10 +170,11 @@ public static String convertJavaStyletoDBConvention(String name) { char[] chars = name.toCharArray(); StringBuffer buf = new StringBuffer(); for (int i = 0; i < chars.length; i++) { - // if there is a single upper-case letter, then it's a case-word + // if there is a single upper-case letter, then it's a + // case-word if (Character.isUpperCase(chars[i])) { if ((i > 0) && (i + 1 < chars.length)) { - if (!Character.isUpperCase(chars[i + 1])) + if (!Character.isUpperCase(chars[i])) buf.append("_"); } buf.append(chars[i]); @@ -308,6 +315,14 @@ public static String getCompressedUnicode(byte[] input) { return new String(output); } + public static String getLowerCaseFirstLetter(String thismember) { + String upcase = thismember.substring(0, 1); + upcase = upcase.toLowerCase(); + thismember = thismember.substring(1); + thismember = upcase + thismember; + return thismember; + } + /** * generate a "setXXXX" string from a field name per Extentech java naming * conventions. @@ -315,10 +330,14 @@ public static String getCompressedUnicode(byte[] input) { * ie: Converts "firstName" to "setFirstName" */ public static String getSetMethodNameFromVar(String thismember) { + return "set" + getUpperCaseFirstLetter(thismember); + } + + public static String getUpperCaseFirstLetter(String thismember) { String upcase = thismember.substring(0, 1); upcase = upcase.toUpperCase(); thismember = thismember.substring(1); - thismember = "set" + upcase + thismember; + thismember = upcase + thismember; return thismember; } @@ -329,11 +348,7 @@ public static String getSetMethodNameFromVar(String thismember) { * ie: Converts "firstName" to "getFirstName" */ public static String getGetMethodNameFromVar(String thismember) { - String upcase = thismember.substring(0, 1); - upcase = upcase.toUpperCase(); - thismember = thismember.substring(1); - thismember = "get" + upcase + thismember; - return thismember; + return "get" + getUpperCaseFirstLetter(thismember); } /** @@ -419,12 +434,12 @@ public static String[] getTokensUsingDelim(String instr, String token) { } // escaped slashes - String oneslash = String.valueOf((char) 0x005C); - String twoslash = oneslash + oneslash; + String oneslash = String.valueOf((char) 0x005C); + String twoslash = oneslash + oneslash; /** - * - */ + * + */ public static String dbencode(String holder) { return replaceText(holder, "'", "''", 0); } @@ -465,8 +480,8 @@ public static String strip(String tostrip, String stripstr) { StringBuffer stripped = new StringBuffer(tostrip.length()); while (tostrip.indexOf(stripstr) > -1) { stripped.append(tostrip.substring(0, tostrip.indexOf(stripstr))); - tostrip = tostrip.substring(tostrip.indexOf(stripstr) - + stripstr.length()); + tostrip = tostrip + .substring(tostrip.indexOf(stripstr) + stripstr.length()); } stripped.append(tostrip); return (stripped.toString()); @@ -517,11 +532,9 @@ public static String strip(String tostrip, char stripchar) { * offset of replacement within original string. * @return Processed text. */ - public static String replaceText(String originalText, String replaceText, - String replacementText, int offset, boolean skipmatch) { + public static String replaceText(String originalText, String replaceText, String replacementText, int offset, boolean skipmatch) { if (!skipmatch) - return replaceText(originalText, replaceText, replacementText, - offset); + return replaceText(originalText, replaceText, replacementText, offset); StringBuffer sb = new StringBuffer(); if (originalText.indexOf(replaceText) < 0) { @@ -570,8 +583,7 @@ public static String replaceText(String originalText, String replaceText, * offset of replacement within original string. * @return Processed text. */ - public static String replaceText(String originalText, String replaceText, - String replacementText, int offset) { + public static String replaceText(String originalText, String replaceText, String replacementText, int offset) { int newlen = originalText.length() - replaceText.length() + replacementText.length(); @@ -638,8 +650,7 @@ public static String rTrim(String originalText) { * Delimeter for end of retrieved section. * @return Text between delims or "" if not found. */ - public static String getTextBetweenNestedDelims(String originalText, - String beginDelim, String endDelim) { + public static String getTextBetweenNestedDelims(String originalText, String beginDelim, String endDelim) { StringBuffer sb = new StringBuffer(originalText.length()); // Check to see that both delimiters exist in the string if ((originalText.indexOf(beginDelim) < 0) @@ -681,8 +692,7 @@ public static String getTextBetweenNestedDelims(String originalText, * Delimeter for end of retrieved section. * @return Text between delims or "" if not found. */ - public static String getTextBetweenDelims(String originalText, - String beginDelim, String endDelim) { + public static String getTextBetweenDelims(String originalText, String beginDelim, String endDelim) { StringBuffer sb = new StringBuffer(originalText.length()); // Check to see that both delimiters exist in the string if ((originalText.indexOf(beginDelim) < 0) @@ -728,8 +738,7 @@ public static String getTextBetweenDelims(String originalText, * @return Processed text. */ - public static String replaceSection(String originalText, - String replaceBegin, String replacementText, String replaceEnd) { + public static String replaceSection(String originalText, String replaceBegin, String replacementText, String replaceEnd) { StringBuffer sb = new StringBuffer(originalText.length()); if ((originalText.indexOf(replaceBegin) < 0) || (originalText.indexOf(replaceEnd) < 0)) { @@ -786,8 +795,7 @@ public static String UseOnlyChars(String theFilter, String theString) { return strOut.toString(); } - public static String replaceChars(String theFilter, String theString, - String replacement) { + public static String replaceChars(String theFilter, String theString, String replacement) { StringBuffer strOut = new StringBuffer(theString.length()); char curChar; for (int i = 0; i < theString.length(); i++) { @@ -805,8 +813,7 @@ public static String replaceChars(String theFilter, String theString, /** * replace a section of text based on pattern match throughout string. */ - public static String replaceText(String theString, String theFilter, - String replacement) { + public static String replaceText(String theString, String theFilter, String replacement) { return replaceText(theString, theFilter, replacement, 0); } @@ -825,8 +832,7 @@ public static boolean AllInRange(int x, int y, String theString) { * Replaces a specified token in a string with a value from the passed * through array this is done in matching order from String to Arrray. */ - public static String replaceTokenFromArray(String replace, String token, - String[] vals) { + public static String replaceTokenFromArray(String replace, String token, String[] vals) { StringBuffer sb = new StringBuffer(); StringTokenizer toke = new StringTokenizer(replace, token, false); int i = 0; @@ -935,9 +941,13 @@ public static String replaceExtension(String filepath, String ext) { public static double getApproximateStringWidth(java.awt.Font f, String s) { java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() .getFontMetrics(f); - /* width/height in pixels = (w/h field) * DPI of the display device / 72 */ - double conversion = java.awt.Toolkit.getDefaultToolkit().getScreenResolution() / 72.0; - return fm.stringWidth(s) * conversion; // pixels * conversion + /* + * width/height in pixels = (w/h field) * DPI of the display + * device / 72 + */ + double conversion = java.awt.Toolkit.getDefaultToolkit() + .getScreenResolution() / 72.0; + return fm.stringWidth(s) * conversion; // pixels * conversion } /** @@ -950,16 +960,21 @@ public static double getApproximateStringWidth(java.awt.Font f, String s) { public static double getApproximateStringWidthLB(java.awt.Font f, String s) { java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() .getFontMetrics(f); - /* width/height in pixels = (w/h field) * DPI of the display device / 72 */ - double conversion = java.awt.Toolkit.getDefaultToolkit().getScreenResolution() / 72.0; - String[] ss= s.split("\n"); - double len= 0; + /* + * width/height in pixels = (w/h field) * DPI of the display + * device / 72 + */ + double conversion = java.awt.Toolkit.getDefaultToolkit() + .getScreenResolution() / 72.0; + String[] ss = s.split("\n"); + double len = 0; for (String st : ss) { - len= Math.max(len, fm.stringWidth(st) * conversion); + len = Math.max(len, fm.stringWidth(st) * conversion); } - // return fm.stringWidth(s) * conversion; + // return fm.stringWidth(s) * conversion; return len; } + /** * return the approximate witdth in width in pixels of the given character * @@ -970,13 +985,17 @@ public static double getApproximateStringWidthLB(java.awt.Font f, String s) { * @return double approximate width in pixels */ public static double getApproximateCharWidth(java.awt.Font f, Character c) { - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f); - /* width/height in pixels = (w/h field) * DPI of the display device / 72 */ - double conversion = java.awt.Toolkit.getDefaultToolkit().getScreenResolution() / 72.0; + java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f); + /* + * width/height in pixels = (w/h field) * DPI of the display + * device / 72 + */ + double conversion = java.awt.Toolkit.getDefaultToolkit() + .getScreenResolution() / 72.0; return fm.charWidth(c) * conversion; } - /** * return the approximate height it takes to display the given string in the * given font in the given width @@ -1012,18 +1031,26 @@ public static double getApproximateHeight(java.awt.Font f, String s, double w) { s = s.substring(0, lastSpace) + "\n" + s.substring(lastSpace + 1); } int nl = s.split("\n").length; - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f); - java.awt.font.LineMetrics lm = f.getLineMetrics(s, fm.getFontRenderContext()); + java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f); + java.awt.font.LineMetrics lm = f + .getLineMetrics(s, fm.getFontRenderContext()); // this calc appears to match Excel's ... - float l= lm.getLeading(); - //float h= lm.getHeight(); - //io.starter.OpenXLS.util.Logger.log("Font: " + f.toString()); - //io.starter.OpenXLS.util.Logger.log("l-i:" + fm.getLeading() + " l:" + l + " h-i:" + fm.getHeight() + " h:" + h + " a-i:" + fm.getAscent() + " a:" + lm.getAscent() + " d-i:" + fm.getDescent() + " d:" + lm.getDescent()); - float h= fm.getHeight(); // KSC: revert for now ... - l/3; // i don't know why but this seems to match Excel's the closest - return Math.ceil(h * (nl));//+1)); // KSC: added + 1 for testing + float l = lm.getLeading(); + // float h= lm.getHeight(); + // io.starter.toolkit.Logger.log("Font: " + + // f.toString()); + // io.starter.toolkit.Logger.log("l-i:" + + // fm.getLeading() + " l:" + l + " h-i:" + fm.getHeight() + + // " h:" + h + " a-i:" + fm.getAscent() + " a:" + + // lm.getAscent() + " d-i:" + fm.getDescent() + " d:" + + // lm.getDescent()); + float h = fm.getHeight(); // KSC: revert for now ... - l/3; // i don't + // know why but this seems to match Excel's + // the closest + return Math.ceil(h * (nl));// +1)); // KSC: added + 1 for testing } - /** * converts an excel-style custom format to String.format custom format i.e. %flags-width-precision-conversion @@ -1057,307 +1084,340 @@ public static double getApproximateHeight(java.awt.Font f, String s, double w) { * @return */ public static String convertPatternFromExcelToStringFormatter(String pattern, boolean isNegative) { - String curPattern= pattern; - String jpattern= ""; // return pattern - int w= 0; - int precision= 0; - String flags= ""; - char conversion= 'f'; // default - boolean inConversion= false; - boolean inPrecision= false; - boolean removeSign= false; // true if value is negative and pattern calls for parens or color change or ... i.e. don't display the negative sign -/* - * TODO: \ uXXX is Locale-specific to display? works manually ... - * TODO; finish fractional formats: ?/? - */ - for (int i= 0; i < curPattern.length(); i++) { - int c= curPattern.charAt(i); + String curPattern = pattern; + String jpattern = ""; // return pattern + int w = 0; + int precision = 0; + String flags = ""; + char conversion = 'f'; // default + boolean inConversion = false; + boolean inPrecision = false; + boolean removeSign = false; // true if value is negative and pattern + // calls for parens or color change or ... + // i.e. don't display the negative sign + /* + * TODO: \ uXXX is Locale-specific to display? works + * manually ... + * TODO; finish fractional formats: ?/? + */ + for (int i = 0; i < curPattern.length(); i++) { + int c = curPattern.charAt(i); switch (c) { case '0': w++; if (!inConversion) { - jpattern+="%"; - inConversion= true; + jpattern += "%"; + inConversion = true; } - if (inPrecision && conversion!='E') precision++; + if (inPrecision && conversion != 'E') + precision++; + break; + case '?': // don't really know what to do with this one! break; - case '?': // don't really know what to do with this one! - break; case '#': if (!inConversion) { - jpattern+="%"; - inConversion= true; + jpattern += "%"; + inConversion = true; } - // TODO: handle such as: ###0.00######### --- what's the format spec for that????? + // TODO: handle such as: ###0.00######### --- what's the + // format spec for that????? // if (inPrecision) precision++; break; case ',': - flags+= ","; + flags += ","; break; case '.': - inPrecision= true; + inPrecision = true; break; case 'E': case 'e': if (!inConversion) { - jpattern+="%"; - inConversion= true; + jpattern += "%"; + inConversion = true; } - conversion= 'E'; - i++; // format is e+, E+, e- or E- + conversion = 'E'; + i++; // format is e+, E+, e- or E- break; - case '[': // either color code or local-specific formatting - int j= ++i; - int k= j; - for (; i < curPattern.length(); i++) { // skip colors for now - c= curPattern.charAt(i); - if (c=='-') // got end of an extended char sequence - skip rest (Locale code ...) - k= i; - if (c==']') - break; + case '[': // either color code or local-specific formatting + int j = ++i; + int k = j; + for (; i < curPattern.length(); i++) { // skip colors for now + c = curPattern.charAt(i); + if (c == '-') // got end of an extended char sequence - skip + // rest (Locale code ...) + k = i; + if (c == ']') + break; } if (inConversion) { - inConversion= false; - inPrecision= false; - jpattern+= flags + (w > 0?w:"") + "." + precision + conversion; + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; } - if (k==j) // then it was a color string - removeSign= true; - else // it was a locale-specific string ... - jpattern+= curPattern.substring(++j, k); + if (k == j) // then it was a color string + removeSign = true; + else // it was a locale-specific string ... + jpattern += curPattern.substring(++j, k); break; - case '"': // start of delimited text + case '"': // start of delimited text if (inConversion) { - inConversion= false; - inPrecision= false; - jpattern+= flags + (w > 0?w:"") + "." + precision + conversion; + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; } for (i++; i < curPattern.length(); i++) { - c= curPattern.charAt(i); - if (c=='"') + c = curPattern.charAt(i); + if (c == '"') break; - jpattern+= (char) c; - - } + jpattern += (char) c; + + } break; // ignore - case '@': // text placeholder - jpattern+="%s"; + case '@': // text placeholder + jpattern += "%s"; + break; + case '*': // repeats the next char to fill -- IGNORE!!! break; - case '*': // repeats the next char to fill -- IGNORE!!! - break; - case '(': // enclose negative #'s in parens + case '(': // enclose negative #'s in parens case ')': - //flags+="("; + // flags+="("; if (isNegative) { if (inConversion) { - inConversion= false; - inPrecision= false; - jpattern+= flags + (w > 0?w:"") + "." + precision + conversion; + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; } - jpattern+= (char) c; - removeSign= true; + jpattern += (char) c; + removeSign = true; } break; - case '_': // skips the width of the next char - usually _) - to leave space for a closing parenthesis in a positive number format when the negative number format includes parentheses. This allows the values to line up at the decimal point. + case '_': // skips the width of the next char - usually _) - to + // leave space for a closing parenthesis in a positive + // number format when the negative number format + // includes parentheses. This allows the values to line + // up at the decimal point. if (inConversion) { - inConversion= false; - inPrecision= false; - jpattern+= flags + (w > 0?w:"") + "." + precision + conversion; + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; } - i++; // skip next char -- true in all cases??? + i++; // skip next char -- true in all cases??? break; case '%': if (inConversion) { - inConversion= false; - inPrecision= false; - jpattern+= flags + (w > 0?w:"") + "." + precision + conversion; + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; } - jpattern+="%%"; + jpattern += "%%"; break; case '\\': if (inConversion) { - inConversion= false; - inPrecision= false; - jpattern+= flags + (w > 0?w:"") + "." + precision + conversion; - } + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; + } int z; - if (i+1 < curPattern.length() && curPattern.charAt(i+1)=='u') - z= i+6; + if (i + 1 < curPattern.length() + && curPattern.charAt(i + 1) == 'u') + z = i + 6; else - z= i+1; - for (; i < z && i < curPattern.length(); i++) - jpattern+= (char) curPattern.charAt(i); + z = i + 1; + for (; i < z && i < curPattern.length(); i++) + jpattern += curPattern.charAt(i); break; - default: // %, $, - space -- keep + default: // %, $, - space -- keep if (inConversion) { - inConversion= false; - inPrecision= false; - jpattern+= flags + (w > 0?w:"") + "." + precision + conversion; - } - jpattern+= (char) c; + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; + } + jpattern += (char) c; break; } } if (inConversion) { - jpattern+= flags + (w > 0?w:"") + "." + precision + conversion; + jpattern += flags + (w > 0 ? w : "") + "." + precision + conversion; } if (isNegative && !removeSign) - jpattern= "-" + jpattern; -//System.out.print("Original Pattern " + pattern + " new " + jpattern); -// patterns[z]= jpattern; - pattern= jpattern; - return pattern; + jpattern = "-" + jpattern; + // System.out.print("Original Pattern " + pattern + " new " + // + jpattern); + // patterns[z]= jpattern; + pattern = jpattern; + return pattern; } + public static String convertDatePatternFromExcelToStringFormatter(String pattern) { - String jpattern= ""; // return pattern - String dString= ""; // d string -- ddd ==> EEE and dddd ==> EEEE - String mString= ""; // m string -- either month (M, MM, MMM, MMMM) or minute - int prev= 0; - for (int i= 0; i < pattern.length(); i++) { - int c= pattern.charAt(i); - if (c!='d' && !dString.equals("")) { - if (dString.length()<=2) - jpattern+= dString; - else if (dString.length()==3) - jpattern+= "EEE"; - else if (dString.length()==4) - jpattern+= "EEEE"; - dString= ""; - } else if(c!='m' && !mString.equals("")) { - if (c==':' || prev=='h') { //it's time - jpattern+= mString; - prev= c; + String jpattern = ""; // return pattern + String dString = ""; // d string -- ddd ==> EEE and dddd ==> EEEE + String mString = ""; // m string -- either month (M, MM, MMM, MMMM) or + // minute + int prev = 0; + for (int i = 0; i < pattern.length(); i++) { + int c = pattern.charAt(i); + if (c != 'd' && !dString.equals("")) { + if (dString.length() <= 2) + jpattern += dString; + else if (dString.length() == 3) + jpattern += "EEE"; + else if (dString.length() == 4) + jpattern += "EEEE"; + dString = ""; + } else if (c != 'm' && !mString.equals("")) { + if (c == ':' || prev == 'h') { // it's time + jpattern += mString; + prev = c; } else - jpattern+= mString.toUpperCase(); - mString= ""; + jpattern += mString.toUpperCase(); + mString = ""; } switch (c) { case 'y': - jpattern+=(char) c; + jpattern += (char) c; break; case 'h': - jpattern+='H'; // h in java is 1-24 excel h= 0-23 - prev= 'h'; + jpattern += 'H'; // h in java is 1-24 excel h= 0-23 + prev = 'h'; break; - case '\\': // found case of erroneous use of backslash, as in: mm\-dd\-yy ignore! - case '[': // no java equivalent of [h] [m] or [ss] == elapsed time + case '\\': // found case of erroneous use of backslash, as in: + // mm\-dd\-yy ignore! + case '[': // no java equivalent of [h] [m] or [ss] == elapsed time case ']': break; case 's': - jpattern+=(char) c; + jpattern += (char) c; break; case 'A': - if (pattern.substring(i, i+5).equals("AM/PM")) { - jpattern+= "a"; - i+=5; - for (int z=jpattern.length()-2; z >= 0; z--) { - if (jpattern.charAt(z)=='H') { - jpattern= jpattern.substring(0, z) + 'h' + jpattern.substring(z+1); + if (pattern.substring(i, i + 5).equals("AM/PM")) { + jpattern += "a"; + i += 5; + for (int z = jpattern.length() - 2; z >= 0; z--) { + if (jpattern.charAt(z) == 'H') { + jpattern = jpattern.substring(0, z) + 'h' + + jpattern.substring(z + 1); } - } - } + } + } break; case 'd': - dString+= (char) c; + dString += (char) c; break; case 'm': - mString+= (char) c; + mString += (char) c; break; default: - if (c!=':' && c!='m') - prev= c; - jpattern+=(char) c; - } + if (c != ':' && c != 'm') + prev = c; + jpattern += (char) c; + } } if (!mString.equals("")) { - if (prev=='h') //it's time - jpattern+= mString; + if (prev == 'h') // it's time + jpattern += mString; else - jpattern+=mString.toUpperCase(); // remaining month string - } else if (!dString.equals("")) { - if (dString.length()<=2) - jpattern+= dString; - else if (dString.length()==3) - jpattern+= "EEE"; - else if (dString.length()==4) - jpattern+= "EEEE"; - dString= ""; + jpattern += mString.toUpperCase(); // remaining month string + } else if (!dString.equals("")) { + if (dString.length() <= 2) + jpattern += dString; + else if (dString.length() == 3) + jpattern += "EEE"; + else if (dString.length() == 4) + jpattern += "EEEE"; + dString = ""; } return jpattern; } + /** * extract info, if any, from bracketed expressions within Excel custom number formats * @param pattern String Excel number format * @return String returned number format without the bracketed expression */ public static String convertPatternExtractBracketedExpression(String pattern) { - String[] s= pattern.split("\\["); - if (s.length > 1) { - pattern= ""; - for (int i= 0; i < s.length; i++) { - int zz= s[i].indexOf("]"); - if (zz!=-1) { - String term= ""; - if (s[i].charAt(0)=='$') - term= s[i].substring(1, zz); // skip first $ - else - term= s[i].substring(0, zz); - if (term.indexOf("-")!=-1) // extract character TODO: locale specifics - pattern+= term.substring(0, term.indexOf("-")); - else - pattern+= term; - } - pattern+= s[i].substring(zz+1); - } + String[] s = pattern.split("\\["); + if (s.length > 1) { + pattern = ""; + for (int i = 0; i < s.length; i++) { + int zz = s[i].indexOf("]"); + if (zz != -1) { + String term = ""; + if (s[i].charAt(0) == '$') + term = s[i].substring(1, zz); // skip first $ + else + term = s[i].substring(0, zz); + if (term.indexOf("-") != -1) // extract character TODO: + // locale specifics + pattern += term.substring(0, term.indexOf("-")); + else + pattern += term; + } + pattern += s[i].substring(zz + 1); + } } - return pattern; + return pattern; } + /** * qualifies a pattern string to make valid for applying the pattern * @param pattern * @return */ public static String qualifyPatternString(String pattern) { - pattern = StringTool.strip(pattern, "*"); - pattern = StringTool.strip(pattern, "_("); // width placeholder - pattern = StringTool.strip(pattern, "_)"); // width placeholder - pattern = StringTool.strip(pattern, "_"); - pattern = pattern.replaceAll("\"", ""); - pattern = StringTool.strip(pattern, "?"); - // there are more bracketed expressions to deal with - // see http://office.microsoft.com/en-us/excel-help/creating-international-number-formats-HA001034635.aspx?redir=0 - //pattern = StringTool.strip(pattern, "[Red]"); // [Black] [h] [hhh] [=1] [=2] - //pattern = StringTool.strip(pattern, "Red]"); - // TODO: implement locale-specific entries: [$-409] [$-404] ... ******************** -// pattern= pattern.replaceAll("\\[.+?\\]", ""); -/* if (s.length > 1) { - io.starter.OpenXLS.util.Logger.log(s[0]); - java.util.regex.Pattern p = java.util.regex.Pattern.compile("\\[(.*?)\\]"); - java.util.regex.Matcher m = p.matcher(pattern); - - while(m.find()) { - io.starter.OpenXLS.util.Logger.log(m.group(1)); - } - }*/ - String[] s= pattern.split("\\["); - if (s.length > 1) { - pattern= ""; - for (int i= 0; i < s.length; i++) { - int zz= s[i].indexOf("]"); - if (zz!=-1) { - String term= s[i].substring(1, zz); // skip first $ - if (term.indexOf("-")!=-1) { // extract character TODO: locale specifics - pattern+= term.substring(0, term.indexOf("-")); - } - } - pattern+= s[i].substring(zz+1); - } + pattern = StringTool.strip(pattern, "*"); + pattern = StringTool.strip(pattern, "_("); // width placeholder + pattern = StringTool.strip(pattern, "_)"); // width placeholder + pattern = StringTool.strip(pattern, "_"); + pattern = pattern.replaceAll("\"", ""); + pattern = StringTool.strip(pattern, "?"); + // there are more bracketed expressions to deal with + // see + // http://office.microsoft.com/en-us/excel-help/creating-international-number-formats-HA001034635.aspx?redir=0 + // pattern = StringTool.strip(pattern, "[Red]"); // [Black] + // [h] [hhh] [=1] [=2] + // pattern = StringTool.strip(pattern, "Red]"); + // TODO: implement locale-specific entries: [$-409] [$-404] + // ... ******************** + // pattern= pattern.replaceAll("\\[.+?\\]", ""); + /* + * if (s.length > 1) { + * io.starter.toolkit.Logger.log(s[0]); + * java.util.regex.Pattern p = + * java.util.regex.Pattern.compile("\\[(.*?)\\]"); + * java.util.regex.Matcher m = p.matcher(pattern); + * + * while(m.find()) { + * io.starter.toolkit.Logger.log(m.group(1)); + * } + * } + */ + String[] s = pattern.split("\\["); + if (s.length > 1) { + pattern = ""; + for (int i = 0; i < s.length; i++) { + int zz = s[i].indexOf("]"); + if (zz != -1) { + String term = s[i].substring(1, zz); // skip first $ + if (term.indexOf("-") != -1) { // extract character TODO: + // locale specifics + pattern += term.substring(0, term.indexOf("-")); + } + } + pattern += s[i].substring(zz + 1); + } } - return pattern; + return pattern; } - + /** Reads from a Reader into a String. * Blocking reads will be issued to the reader and the results will be * concatenated into a string, which will be returned once the reader @@ -1365,17 +1425,16 @@ public static String qualifyPatternString(String pattern) { * @param reader the Reader from which to read * @return a string containing all characters read from the input */ - public static String readString (Reader reader) - throws IOException { + public static String readString(Reader reader) throws IOException { StringBuilder builder = new StringBuilder(); - CharBuffer buffer = CharBuffer.allocate( 512 ); - - while (-1 != reader.read( buffer )) { - buffer.flip(); - builder.append( buffer ); - buffer.clear(); - } - - return builder.toString(); + CharBuffer buffer = CharBuffer.allocate(512); + + while (-1 != reader.read(buffer)) { + buffer.flip(); + builder.append(buffer); + buffer.clear(); + } + + return builder.toString(); } } \ No newline at end of file diff --git a/src/test/docs/samples/Formulas/FormulaTest.java b/src/test/java/io/starter/OpenXLS/FormulaTest.java similarity index 76% rename from src/test/docs/samples/Formulas/FormulaTest.java rename to src/test/java/io/starter/OpenXLS/FormulaTest.java index 65089b0..3295c1d 100644 --- a/src/test/docs/samples/Formulas/FormulaTest.java +++ b/src/test/java/io/starter/OpenXLS/FormulaTest.java @@ -1,3 +1,5 @@ +package io.starter.OpenXLS; + /* * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. @@ -5,22 +7,28 @@ * * This file is part of OpenXLS. * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of * the License, or (at your option) any later version. * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ -package docs.samples.Formulas; import java.io.BufferedOutputStream; import java.io.File; @@ -28,10 +36,6 @@ import org.junit.Test; -import io.starter.OpenXLS.CellHandle; -import io.starter.OpenXLS.FormulaHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.OpenXLS.WorkSheetHandle; import io.starter.formats.XLS.CellNotFoundException; import io.starter.formats.XLS.FormulaNotFoundException; import io.starter.formats.XLS.FunctionNotSupportedException; @@ -74,11 +78,12 @@ public void changeSingleCellLoc() { * Test the manipulation of Formulas within a worksheet. */ class testformula { - WorkBookHandle book = null; - WorkSheetHandle sheet = null; - String sheetname = "Sheet1"; - String wd = System.getProperty("user.dir") + "/docs/samples/Formulas/"; - String finpath = wd + "testFormula.xls"; + WorkBookHandle book = null; + WorkSheetHandle sheet = null; + String sheetname = "Sheet1"; + String wd = System.getProperty("user.dir") + + "/docs/samples/Formulas/"; + String finpath = wd + "testFormula.xls"; /** * thrash multiple changes to formula references and recalc @@ -102,7 +107,8 @@ public void testMultiChange() { Logger.logInfo(calced.toString()); } Logger.logInfo("done setting 100k vals"); - wbx.write(new FileOutputStream(new File(wd + "testFormulas_out.xls")), WorkBookHandle.FORMAT_XLS); + wbx.write(new FileOutputStream(new File( + wd + "testFormulas_out.xls")), WorkBookHandle.FORMAT_XLS); } catch (Exception ex) { Logger.logErr("testFormulas.testMultiChange: " + ex.toString()); @@ -124,18 +130,18 @@ public void testCalculation() { // output the calculated values FormulaHandle form = myformulacell.getFormulaHandle(); - io.starter.OpenXLS.util.Logger.log(form.calculate().toString()); + io.starter.toolkit.Logger.log(form.calculate().toString()); // change the values then recalc mycell1.setVal(99); mycell2.setVal(420); - io.starter.OpenXLS.util.Logger.log(form.calculate().toString()); + io.starter.toolkit.Logger.log(form.calculate().toString()); testWrite("testCalculation_out.xls"); } catch (CellNotFoundException e) { - io.starter.OpenXLS.util.Logger.log("cell not found" + e); + io.starter.toolkit.Logger.log("cell not found" + e); } catch (FormulaNotFoundException e) { - io.starter.OpenXLS.util.Logger.log("No formula to change" + e); + io.starter.toolkit.Logger.log("No formula to change" + e); } catch (Exception e) { Logger.logErr("TestFormulas failed.", e); } @@ -153,9 +159,9 @@ public void changeSingleCellLoc() { form.changeFormulaLocation("A3", "G10"); testWrite("testChangeSingleCellLoc_out.xls"); } catch (CellNotFoundException e) { - io.starter.OpenXLS.util.Logger.log("cell not found" + e); + io.starter.toolkit.Logger.log("cell not found" + e); } catch (FormulaNotFoundException e) { - io.starter.OpenXLS.util.Logger.log("No formula to change" + e); + io.starter.toolkit.Logger.log("No formula to change" + e); } } @@ -171,9 +177,9 @@ public void testHandlerFunctions() { boolean b = myhandle.changeFormulaLocation("A1:B2", "D1:D28"); testWrite("testHandlerFunctions_out.xls"); } catch (CellNotFoundException e) { - io.starter.OpenXLS.util.Logger.log("cell not found" + e); + io.starter.toolkit.Logger.log("cell not found" + e); } catch (FormulaNotFoundException e) { - io.starter.OpenXLS.util.Logger.log("No formula to change" + e); + io.starter.toolkit.Logger.log("No formula to change" + e); } } @@ -190,9 +196,9 @@ public void testCellHandlerFunctions() { boolean b = myhandle.addCellToRange("A1:B2", secondcell); testWrite("testCellHandlerFunctions_out.xls"); } catch (CellNotFoundException e) { - io.starter.OpenXLS.util.Logger.log("cell not found" + e); + io.starter.toolkit.Logger.log("cell not found" + e); } catch (FormulaNotFoundException e) { - io.starter.OpenXLS.util.Logger.log("No formula to change" + e); + io.starter.toolkit.Logger.log("No formula to change" + e); } } @@ -208,7 +214,8 @@ public void testFormula() { sheet.removeRow(2, true); testWrite("testFormula_out.xls"); } catch (Exception e) { - io.starter.OpenXLS.util.Logger.log("Exception in testFORMULA.testFormulaSeries(): " + e); + io.starter.toolkit.Logger + .log("Exception in testFORMULA.testFormulaSeries(): " + e); } } @@ -287,53 +294,53 @@ void testFormulaCalc(String fs, String sh) { f.setFormula("LOG(10,2)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); f.setFormula("ROUND(32.443,1)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); f.setFormula("MOD(45,6)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); f.setFormula("DATE(1998,2,4)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); f.setFormula("SUM(1998,2,4)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); f.setFormula("IF(TRUE,1,0)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); f.setFormula("ISERR(\"test\")"); b = (Boolean) f.calculate(); - io.starter.OpenXLS.util.Logger.log(b.toString()); + io.starter.toolkit.Logger.log(b.toString()); // many operand ptgfuncvar f.setFormula("SUM(12,3,2,4,5,1)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); // test with a sub-calc f.setFormula("IF((1<2),1,0)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); f.setFormula("IF((1<2),MOD(45,6),1)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); f.setFormula("IF((1<2),if((true),8,1),1)"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); f.setFormula("IF((SUM(23,2,3,4)<12),if((true),8,1),DATE(1998,2,4))"); i = (Double) f.calculate(); - io.starter.OpenXLS.util.Logger.log(i.toString()); + io.starter.toolkit.Logger.log(i.toString()); } catch (CellNotFoundException e) { Logger.logErr("TestFormulas failed.", e); @@ -351,7 +358,7 @@ public void openSheet(String finp, String sheetnm) { try { sheet = book.getWorkSheet(sheetnm); } catch (WorkSheetNotFoundException e) { - io.starter.OpenXLS.util.Logger.log("couldn't find worksheet" + e); + io.starter.toolkit.Logger.log("couldn't find worksheet" + e); } } From f07ee9d3273dff14850a74c8de15970025be6796 Mon Sep 17 00:00:00 2001 From: John McMahon Date: Tue, 29 Oct 2019 12:54:44 -0700 Subject: [PATCH 2/3] feat(core) : major cleanup --- .idea/encodings.xml | 6 + .idea/vcs.xml | 6 + .../io/starter/OpenXLS/AutoFilterHandle.java | 426 +- src/main/java/io/starter/OpenXLS/Cell.java | 159 +- .../io/starter/OpenXLS/CellComparator.java | 186 +- .../java/io/starter/OpenXLS/CellHandle.java | 5845 ++++---- .../java/io/starter/OpenXLS/CellRange.java | 3877 +++--- .../java/io/starter/OpenXLS/CellRangeRef.java | 602 +- .../java/io/starter/OpenXLS/ChartHandle.java | 7627 ++++++----- .../io/starter/OpenXLS/ChartSeriesHandle.java | 329 +- .../java/io/starter/OpenXLS/ColHandle.java | 528 +- .../io/starter/OpenXLS/CommentHandle.java | 426 +- .../OpenXLS/ConditionalFormatHandle.java | 248 +- .../OpenXLS/ConditionalFormatRule.java | 1206 +- .../io/starter/OpenXLS/DateConverter.java | 1391 +- .../java/io/starter/OpenXLS/Document.java | 180 +- .../io/starter/OpenXLS/DocumentHandle.java | 781 +- .../DocumentObjectNotFoundException.java | 10 +- .../java/io/starter/OpenXLS/ExcelTools.java | 2245 ++-- .../java/io/starter/OpenXLS/FormatCache.java | 85 +- .../java/io/starter/OpenXLS/FormatHandle.java | 5639 ++++---- .../io/starter/OpenXLS/FormulaHandle.java | 938 +- src/main/java/io/starter/OpenXLS/GetInfo.java | 108 +- src/main/java/io/starter/OpenXLS/Handle.java | 11 +- .../java/io/starter/OpenXLS/ImageHandle.java | 1113 +- .../io/starter/OpenXLS/JSONConstants.java | 76 +- .../java/io/starter/OpenXLS/NameHandle.java | 1096 +- .../io/starter/OpenXLS/PivotTableHandle.java | 1066 +- .../OpenXLS/PrinterSettingsHandle.java | 1345 +- .../java/io/starter/OpenXLS/RowHandle.java | 803 +- .../io/starter/OpenXLS/ValidationHandle.java | 1177 +- .../java/io/starter/OpenXLS/WorkBook.java | 859 +- .../io/starter/OpenXLS/WorkBookException.java | 41 +- .../io/starter/OpenXLS/WorkBookHandle.java | 4376 +++--- .../java/io/starter/OpenXLS/WorkSheet.java | 1043 +- .../io/starter/OpenXLS/WorkSheetHandle.java | 5996 ++++----- src/main/java/io/starter/OpenXLS/package.html | 11 +- .../java/io/starter/OpenXLS/util/Logger.java | 53 - .../starter/OpenXLS/util/SystemConstants.java | 5 - .../java/io/starter/formats/LEO/BIGBLOCK.java | 58 +- .../java/io/starter/formats/LEO/Block.java | 265 +- .../formats/LEO/BlockByteConsumer.java | 189 +- .../starter/formats/LEO/BlockByteReader.java | 1804 +-- .../io/starter/formats/LEO/BlockFactory.java | 244 +- .../io/starter/formats/LEO/BlockImpl.java | 625 +- .../io/starter/formats/LEO/FileBuffer.java | 137 +- .../formats/LEO/InvalidFileException.java | 35 +- .../java/io/starter/formats/LEO/LEOFile.java | 2683 ++-- .../io/starter/formats/LEO/LEOHeader.java | 765 +- .../formats/LEO/LEOIndexingException.java | 23 +- .../io/starter/formats/LEO/RootStorage.java | 64 +- .../io/starter/formats/LEO/SMALLBLOCK.java | 55 +- .../java/io/starter/formats/LEO/Storable.java | 38 +- .../java/io/starter/formats/LEO/Storage.java | 1384 +- .../formats/LEO/StorageNotFoundException.java | 22 +- .../io/starter/formats/LEO/StorageTable.java | 1109 +- .../io/starter/formats/OOXML/Alignment.java | 166 +- .../io/starter/formats/OOXML/AutoFilter.java | 1112 +- .../java/io/starter/formats/OOXML/AvLst.java | 130 +- .../java/io/starter/formats/OOXML/BodyPr.java | 338 +- .../java/io/starter/formats/OOXML/Border.java | 942 +- .../java/io/starter/formats/OOXML/CNvPr.java | 301 +- .../java/io/starter/formats/OOXML/CfRule.java | 434 +- .../java/io/starter/formats/OOXML/Color.java | 1398 +- .../io/starter/formats/OOXML/ColorChoice.java | 1444 +- .../java/io/starter/formats/OOXML/CxnSp.java | 644 +- .../java/io/starter/formats/OOXML/DLbls.java | 383 +- .../java/io/starter/formats/OOXML/DPt.java | 183 +- .../java/io/starter/formats/OOXML/DefRPr.java | 357 +- .../java/io/starter/formats/OOXML/Dxf.java | 669 +- .../formats/OOXML/EffectPropsGroup.java | 345 +- .../java/io/starter/formats/OOXML/Ext.java | 154 +- .../java/io/starter/formats/OOXML/ExtLst.java | 139 +- .../java/io/starter/formats/OOXML/Fill.java | 2234 ++-- .../io/starter/formats/OOXML/FillGroup.java | 2457 ++-- .../java/io/starter/formats/OOXML/Gd.java | 131 +- .../io/starter/formats/OOXML/GeomGroup.java | 1316 +- .../io/starter/formats/OOXML/Graphic.java | 373 +- .../starter/formats/OOXML/GraphicFrame.java | 780 +- .../java/io/starter/formats/OOXML/GrpSp.java | 1472 ++- .../java/io/starter/formats/OOXML/Layout.java | 433 +- .../java/io/starter/formats/OOXML/Legend.java | 404 +- .../java/io/starter/formats/OOXML/Ln.java | 1078 +- .../java/io/starter/formats/OOXML/Marker.java | 154 +- .../java/io/starter/formats/OOXML/NumFmt.java | 194 +- .../starter/formats/OOXML/OOXMLConstants.java | 554 +- .../starter/formats/OOXML/OOXMLElement.java | 19 +- .../io/starter/formats/OOXML/OOXMLHandle.java | 102 +- .../starter/formats/OOXML/ObjectChoice.java | 798 +- .../starter/formats/OOXML/OneCellAnchor.java | 579 +- src/main/java/io/starter/formats/OOXML/P.java | 778 +- .../java/io/starter/formats/OOXML/Pic.java | 866 +- .../formats/OOXML/PivotCacheDefinition.java | 219 +- .../formats/OOXML/PivotTableDefinition.java | 1250 +- .../io/starter/formats/OOXML/SheetPr.java | 607 +- .../io/starter/formats/OOXML/SheetView.java | 488 +- .../java/io/starter/formats/OOXML/Sp.java | 954 +- .../java/io/starter/formats/OOXML/SpPr.java | 593 +- .../java/io/starter/formats/OOXML/Ss_rPr.java | 546 +- .../java/io/starter/formats/OOXML/StrRef.java | 273 +- .../java/io/starter/formats/OOXML/Style.java | 734 +- .../java/io/starter/formats/OOXML/Text.java | 304 +- .../io/starter/formats/OOXML/TextRun.java | 870 +- .../java/io/starter/formats/OOXML/Theme.java | 249 +- .../java/io/starter/formats/OOXML/Title.java | 599 +- .../starter/formats/OOXML/TwoCellAnchor.java | 1145 +- .../java/io/starter/formats/OOXML/TxBody.java | 138 +- .../java/io/starter/formats/OOXML/TxPr.java | 224 +- .../java/io/starter/formats/OOXML/Xfrm.java | 330 +- .../java/io/starter/formats/XLS/Array.java | 427 +- .../io/starter/formats/XLS/AutoFilter.java | 1281 +- .../java/io/starter/formats/XLS/BiffRec.java | 225 +- .../java/io/starter/formats/XLS/Blank.java | 122 +- src/main/java/io/starter/formats/XLS/Bof.java | 103 +- .../java/io/starter/formats/XLS/Book.java | 90 +- .../java/io/starter/formats/XLS/BookBool.java | 50 +- .../formats/XLS/BookProtectionManager.java | 35 +- .../java/io/starter/formats/XLS/Boolerr.java | 177 +- .../io/starter/formats/XLS/BottomMargin.java | 70 +- .../io/starter/formats/XLS/Boundsheet.java | 11015 ++++++++-------- .../io/starter/formats/XLS/ByteStreamer.java | 924 +- .../java/io/starter/formats/XLS/CalcMode.java | 83 +- .../formats/XLS/CellAddressComparator.java | 102 +- .../starter/formats/XLS/CellAddressible.java | 199 +- .../formats/XLS/CellNotFoundException.java | 30 +- .../XLS/CellPositionConflictException.java | 41 +- .../java/io/starter/formats/XLS/CellRec.java | 13 +- .../XLS/CellTypeMismatchException.java | 45 +- src/main/java/io/starter/formats/XLS/Cf.java | 4018 +++--- .../java/io/starter/formats/XLS/Cf12.java | 21 +- .../formats/XLS/ChartNotFoundException.java | 67 +- .../java/io/starter/formats/XLS/Codename.java | 138 +- .../java/io/starter/formats/XLS/Colinfo.java | 553 +- .../java/io/starter/formats/XLS/Color.java | 50 +- .../formats/XLS/ColumnNotFoundException.java | 43 +- .../io/starter/formats/XLS/ColumnRange.java | 171 +- .../java/io/starter/formats/XLS/Condfmt.java | 448 +- .../io/starter/formats/XLS/Condfmt12.java | 23 +- .../java/io/starter/formats/XLS/Continue.java | 404 +- .../starter/formats/XLS/ContinueHandler.java | 1178 +- .../java/io/starter/formats/XLS/Country.java | 48 +- src/main/java/io/starter/formats/XLS/Crn.java | 152 +- .../formats/XLS/CustomFormatHelper.java | 367 +- .../java/io/starter/formats/XLS/DConBin.java | 229 +- .../java/io/starter/formats/XLS/DConName.java | 186 +- .../java/io/starter/formats/XLS/DConRef.java | 342 +- .../java/io/starter/formats/XLS/Dbcell.java | 512 +- .../io/starter/formats/XLS/DefColWidth.java | 67 +- .../starter/formats/XLS/DefaultRowHeight.java | 104 +- .../io/starter/formats/XLS/Dimensions.java | 220 +- .../formats/XLS/DiscontiguousRefStruct.java | 629 +- src/main/java/io/starter/formats/XLS/Dsf.java | 44 +- src/main/java/io/starter/formats/XLS/Dv.java | 1545 ++- .../java/io/starter/formats/XLS/Dval.java | 368 +- src/main/java/io/starter/formats/XLS/Eof.java | 27 +- .../starter/formats/XLS/ExpressionParser.java | 2375 ++-- .../io/starter/formats/XLS/Externname.java | 191 +- .../io/starter/formats/XLS/Externsheet.java | 761 +- .../java/io/starter/formats/XLS/Extsst.java | 327 +- .../io/starter/formats/XLS/FeatHeadr.java | 359 +- .../java/io/starter/formats/XLS/FileLock.java | 16 +- .../java/io/starter/formats/XLS/Font.java | 1698 ++- .../io/starter/formats/XLS/FontBasis.java | 64 +- .../io/starter/formats/XLS/Footerrec.java | 143 +- .../java/io/starter/formats/XLS/Format.java | 285 +- .../starter/formats/XLS/FormatConstants.java | 1956 ++- .../formats/XLS/FormatConstantsImpl.java | 20 +- .../java/io/starter/formats/XLS/Formula.java | 1836 +-- .../formats/XLS/FormulaNotFoundException.java | 31 +- .../XLS/FunctionNotSupportedException.java | 103 +- .../java/io/starter/formats/XLS/Guts.java | 158 +- .../java/io/starter/formats/XLS/HCenter.java | 50 +- .../io/starter/formats/XLS/Headerrec.java | 143 +- .../java/io/starter/formats/XLS/Hlink.java | 915 +- .../formats/XLS/ImageNotFoundException.java | 46 +- .../java/io/starter/formats/XLS/Index.java | 376 +- .../io/starter/formats/XLS/InterfaceHdr.java | 16 +- .../formats/XLS/InvalidRecordException.java | 31 +- .../java/io/starter/formats/XLS/Isstinf.java | 60 +- .../java/io/starter/formats/XLS/Label.java | 135 +- .../java/io/starter/formats/XLS/Labelsst.java | 366 +- .../io/starter/formats/XLS/LeftMargin.java | 71 +- .../io/starter/formats/XLS/MSODrawing.java | 3372 ++--- .../formats/XLS/MSODrawingConstants.java | 715 +- .../starter/formats/XLS/MSODrawingGroup.java | 1356 +- .../formats/XLS/MSODrawingSelection.java | 73 +- .../io/starter/formats/XLS/Mergedcells.java | 395 +- src/main/java/io/starter/formats/XLS/Mul.java | 32 +- .../java/io/starter/formats/XLS/Mulblank.java | 608 +- .../java/io/starter/formats/XLS/Mulled.java | 26 +- .../java/io/starter/formats/XLS/Mulrk.java | 279 +- .../java/io/starter/formats/XLS/Name.java | 1475 ++- .../formats/XLS/NameNotFoundException.java | 44 +- .../starter/formats/XLS/NineteenOhFour.java | 49 +- .../java/io/starter/formats/XLS/Note.java | 607 +- .../io/starter/formats/XLS/NumberRec.java | 218 +- .../io/starter/formats/XLS/OOXMLAdapter.java | 1763 ++- .../io/starter/formats/XLS/OOXMLReader.java | 1617 ++- .../io/starter/formats/XLS/OOXMLWriter.java | 3206 ++--- src/main/java/io/starter/formats/XLS/Obj.java | 1605 +-- .../io/starter/formats/XLS/ObjProtect.java | 86 +- .../java/io/starter/formats/XLS/Obproj.java | 33 +- src/main/java/io/starter/formats/XLS/PLV.java | 41 +- .../java/io/starter/formats/XLS/Palette.java | 63 +- .../java/io/starter/formats/XLS/Pane.java | 214 +- .../java/io/starter/formats/XLS/Password.java | 178 +- .../java/io/starter/formats/XLS/Phonetic.java | 90 +- .../io/starter/formats/XLS/PivotCache.java | 477 +- .../starter/formats/XLS/PivotCacheRecord.java | 21 +- .../XLS/PivotTableNotFoundException.java | 47 +- src/main/java/io/starter/formats/XLS/Pls.java | 74 +- .../io/starter/formats/XLS/PrintGrid.java | 49 +- .../io/starter/formats/XLS/PrintRowCol.java | 49 +- .../java/io/starter/formats/XLS/Prot4rev.java | 70 +- .../java/io/starter/formats/XLS/Protect.java | 80 +- .../formats/XLS/ProtectionManager.java | 156 +- .../java/io/starter/formats/XLS/QsiSXTag.java | 293 +- src/main/java/io/starter/formats/XLS/Ref.java | 125 +- .../starter/formats/XLS/ReferenceTracker.java | 1615 +-- .../io/starter/formats/XLS/RightMargin.java | 70 +- src/main/java/io/starter/formats/XLS/Rk.java | 1222 +- src/main/java/io/starter/formats/XLS/Row.java | 1279 +- .../formats/XLS/RowNotFoundException.java | 43 +- .../java/io/starter/formats/XLS/RrdHead.java | 16 +- .../java/io/starter/formats/XLS/RrdInfo.java | 16 +- .../java/io/starter/formats/XLS/Rstring.java | 28 +- .../java/io/starter/formats/XLS/SXBool.java | 90 +- .../java/io/starter/formats/XLS/SXDBEx.java | 137 +- .../io/starter/formats/XLS/SXFDBType.java | 186 +- .../java/io/starter/formats/XLS/SXNum.java | 89 +- .../java/io/starter/formats/XLS/SXString.java | 170 +- .../io/starter/formats/XLS/ScenProtect.java | 83 +- src/main/java/io/starter/formats/XLS/Scl.java | 152 +- .../io/starter/formats/XLS/Selection.java | 105 +- .../java/io/starter/formats/XLS/Setup.java | 1141 +- .../java/io/starter/formats/XLS/Sheet.java | 715 +- .../formats/XLS/SheetProtectionManager.java | 494 +- .../java/io/starter/formats/XLS/Shrfmla.java | 637 +- src/main/java/io/starter/formats/XLS/Sst.java | 3303 +++-- .../io/starter/formats/XLS/StringRec.java | 135 +- .../java/io/starter/formats/XLS/Style.java | 41 +- .../java/io/starter/formats/XLS/Supbook.java | 584 +- .../java/io/starter/formats/XLS/SxAddl.java | 725 +- .../java/io/starter/formats/XLS/SxDB.java | 391 +- .../java/io/starter/formats/XLS/SxDBB.java | 138 +- .../java/io/starter/formats/XLS/SxDI.java | 639 +- .../java/io/starter/formats/XLS/SxEX.java | 240 +- .../java/io/starter/formats/XLS/SxFDB.java | 533 +- .../java/io/starter/formats/XLS/SxPI.java | 179 +- .../io/starter/formats/XLS/SxStreamID.java | 346 +- .../io/starter/formats/XLS/SxVIEWEX9.java | 125 +- .../java/io/starter/formats/XLS/SxVS.java | 88 +- .../java/io/starter/formats/XLS/SxVdEX.java | 246 +- .../java/io/starter/formats/XLS/Sxformat.java | 60 +- .../java/io/starter/formats/XLS/Sxivd.java | 196 +- .../java/io/starter/formats/XLS/Sxli.java | 525 +- .../java/io/starter/formats/XLS/Sxvd.java | 673 +- .../java/io/starter/formats/XLS/Sxvi.java | 528 +- .../java/io/starter/formats/XLS/Sxview.java | 1766 +-- .../java/io/starter/formats/XLS/TabID.java | 122 +- .../io/starter/formats/XLS/TableStyles.java | 120 +- .../io/starter/formats/XLS/TopMargin.java | 70 +- src/main/java/io/starter/formats/XLS/Txo.java | 568 +- .../formats/XLS/UnencryptedXLSRecord.java | 21 +- .../io/starter/formats/XLS/Unicodestring.java | 1137 +- .../starter/formats/XLS/Usersviewbegin.java | 186 +- .../io/starter/formats/XLS/Usersviewend.java | 67 +- .../java/io/starter/formats/XLS/UsrExcl.java | 18 +- .../java/io/starter/formats/XLS/VCenter.java | 50 +- .../formats/XLS/ValidationException.java | 47 +- .../java/io/starter/formats/XLS/Window1.java | 149 +- .../java/io/starter/formats/XLS/Window2.java | 328 +- .../java/io/starter/formats/XLS/WorkBook.java | 7715 +++++------ .../formats/XLS/WorkBookAssembler.java | 220 +- .../formats/XLS/WorkBookException.java | 57 +- .../starter/formats/XLS/WorkBookFactory.java | 1712 +-- .../XLS/WorkSheetNotFoundException.java | 52 +- .../io/starter/formats/XLS/Writeaccess.java | 73 +- .../java/io/starter/formats/XLS/WsBool.java | 97 +- .../io/starter/formats/XLS/XLSCellRecord.java | 37 +- .../io/starter/formats/XLS/XLSConstants.java | 586 +- .../io/starter/formats/XLS/XLSRecord.java | 1715 +-- .../starter/formats/XLS/XLSRecordFactory.java | 1542 ++- .../io/starter/formats/XLS/XLSSubstream.java | 37 +- src/main/java/io/starter/formats/XLS/Xct.java | 54 +- src/main/java/io/starter/formats/XLS/Xf.java | 2336 ++-- .../io/starter/formats/XLS/charts/Ai.java | 1560 +-- .../io/starter/formats/XLS/charts/AlRuns.java | 24 +- .../io/starter/formats/XLS/charts/Area.java | 138 +- .../formats/XLS/charts/Area3DChart.java | 103 +- .../starter/formats/XLS/charts/AreaChart.java | 468 +- .../formats/XLS/charts/AreaFormat.java | 339 +- .../formats/XLS/charts/AttachedLabel.java | 351 +- .../io/starter/formats/XLS/charts/Axcent.java | 172 +- .../starter/formats/XLS/charts/Axesused.java | 28 +- .../io/starter/formats/XLS/charts/Axis.java | 3740 +++--- .../formats/XLS/charts/AxisLineFormat.java | 77 +- .../formats/XLS/charts/AxisParent.java | 494 +- .../io/starter/formats/XLS/charts/Bar.java | 249 +- .../formats/XLS/charts/Bar3DChart.java | 124 +- .../starter/formats/XLS/charts/BarChart.java | 384 +- .../io/starter/formats/XLS/charts/Begin.java | 43 +- .../io/starter/formats/XLS/charts/Boppop.java | 342 +- .../formats/XLS/charts/BoppopCustom.java | 24 +- .../formats/XLS/charts/BubbleChart.java | 381 +- .../io/starter/formats/XLS/charts/CatLab.java | 169 +- .../formats/XLS/charts/CatserRange.java | 293 +- .../io/starter/formats/XLS/charts/Chart.java | 2706 ++-- .../formats/XLS/charts/Chart3DBarShape.java | 110 +- .../starter/formats/XLS/charts/ChartAxes.java | 1644 +-- .../formats/XLS/charts/ChartConstants.java | 81 +- .../formats/XLS/charts/ChartFormat.java | 1735 +-- .../formats/XLS/charts/ChartFormatLink.java | 38 +- .../formats/XLS/charts/ChartFrtInfo.java | 86 +- .../starter/formats/XLS/charts/ChartLine.java | 288 +- .../formats/XLS/charts/ChartObject.java | 38 +- .../formats/XLS/charts/ChartSeries.java | 1613 +-- .../starter/formats/XLS/charts/ChartType.java | 2696 ++-- .../formats/XLS/charts/Col3DChart.java | 114 +- .../starter/formats/XLS/charts/ColChart.java | 270 +- .../formats/XLS/charts/ConeBarChart.java | 23 +- .../starter/formats/XLS/charts/ConeChart.java | 23 +- .../formats/XLS/charts/CrtLayout12.java | 219 +- .../formats/XLS/charts/CrtLayout12A.java | 184 +- .../formats/XLS/charts/CylinderBarChart.java | 23 +- .../formats/XLS/charts/CylinderChart.java | 23 +- .../io/starter/formats/XLS/charts/Dat.java | 107 +- .../formats/XLS/charts/DataFormat.java | 1130 +- .../formats/XLS/charts/DataLabExt.java | 61 +- .../XLS/charts/DataLabExtContents.java | 150 +- .../formats/XLS/charts/DefaultText.java | 67 +- .../formats/XLS/charts/DoughnutChart.java | 419 +- .../starter/formats/XLS/charts/Dropbar.java | 155 +- .../io/starter/formats/XLS/charts/End.java | 35 +- .../starter/formats/XLS/charts/EndBlock.java | 64 +- .../starter/formats/XLS/charts/EndObject.java | 55 +- .../io/starter/formats/XLS/charts/Fbi.java | 23 +- .../io/starter/formats/XLS/charts/Fontx.java | 78 +- .../io/starter/formats/XLS/charts/Frame.java | 279 +- .../formats/XLS/charts/FrtFontList.java | 101 +- .../formats/XLS/charts/FrtWrapper.java | 143 +- .../starter/formats/XLS/charts/GelFrame.java | 68 +- .../XLS/charts/GenericChartObject.java | 155 +- .../io/starter/formats/XLS/charts/Ifmt.java | 33 +- .../io/starter/formats/XLS/charts/Legend.java | 866 +- .../starter/formats/XLS/charts/Legendxn.java | 24 +- .../io/starter/formats/XLS/charts/Line.java | 137 +- .../formats/XLS/charts/Line3DChart.java | 108 +- .../starter/formats/XLS/charts/LineChart.java | 333 +- .../formats/XLS/charts/LineFormat.java | 323 +- .../formats/XLS/charts/MarkerFormat.java | 266 +- .../formats/XLS/charts/OOXMLChart.java | 671 +- .../formats/XLS/charts/ObjectLink.java | 87 +- .../formats/XLS/charts/OfPieChart.java | 97 +- .../io/starter/formats/XLS/charts/Picf.java | 24 +- .../io/starter/formats/XLS/charts/Pie.java | 236 +- .../formats/XLS/charts/Pie3dChart.java | 351 +- .../starter/formats/XLS/charts/PieChart.java | 421 +- .../starter/formats/XLS/charts/PieFormat.java | 64 +- .../formats/XLS/charts/PivotChartBits.java | 71 +- .../formats/XLS/charts/PivotChartLink.java | 59 +- .../starter/formats/XLS/charts/PlotArea.java | 26 +- .../formats/XLS/charts/PlotGrowth.java | 24 +- .../io/starter/formats/XLS/charts/Pos.java | 378 +- .../formats/XLS/charts/PyramidBarChart.java | 23 +- .../formats/XLS/charts/PyramidChart.java | 22 +- .../io/starter/formats/XLS/charts/Radar.java | 105 +- .../starter/formats/XLS/charts/RadarArea.java | 93 +- .../formats/XLS/charts/RadarAreaChart.java | 74 +- .../formats/XLS/charts/RadarChart.java | 375 +- .../starter/formats/XLS/charts/SbaseRef.java | 24 +- .../starter/formats/XLS/charts/Scatter.java | 149 +- .../formats/XLS/charts/ScatterChart.java | 523 +- .../starter/formats/XLS/charts/SerParent.java | 24 +- .../starter/formats/XLS/charts/SerToCrt.java | 31 +- .../formats/XLS/charts/SerauxErrBar.java | 24 +- .../formats/XLS/charts/SerauxTrend.java | 24 +- .../io/starter/formats/XLS/charts/Serfmt.java | 159 +- .../io/starter/formats/XLS/charts/Series.java | 1529 ++- .../formats/XLS/charts/SeriesList.java | 117 +- .../formats/XLS/charts/SeriesText.java | 163 +- .../starter/formats/XLS/charts/ShtProps.java | 30 +- .../starter/formats/XLS/charts/SiIndex.java | 73 +- .../formats/XLS/charts/StackedAreaChart.java | 212 +- .../formats/XLS/charts/StackedColumn.java | 195 +- .../formats/XLS/charts/StartBlock.java | 189 +- .../formats/XLS/charts/StartObject.java | 59 +- .../formats/XLS/charts/StockChart.java | 86 +- .../starter/formats/XLS/charts/Surface.java | 176 +- .../formats/XLS/charts/Surface3DChart.java | 78 +- .../formats/XLS/charts/SurfaceChart.java | 83 +- .../formats/XLS/charts/SxViewLink.java | 65 +- .../starter/formats/XLS/charts/TextDisp.java | 762 +- .../io/starter/formats/XLS/charts/ThreeD.java | 770 +- .../io/starter/formats/XLS/charts/Tick.java | 272 +- .../io/starter/formats/XLS/charts/Units.java | 24 +- .../formats/XLS/charts/ValueRange.java | 836 +- .../io/starter/formats/XLS/charts/YMult.java | 320 +- .../XLS/formulas/CalculationException.java | 300 +- .../formats/XLS/formulas/Calculator.java | 323 +- .../formulas/CircularReferenceException.java | 22 +- .../XLS/formulas/DDEExternalCalculator.java | 25 +- .../XLS/formulas/DatabaseCalculator.java | 2266 ++-- .../XLS/formulas/DateTimeCalculator.java | 1203 +- .../XLS/formulas/EngineeringCalculator.java | 4541 +++---- .../XLS/formulas/FinancialCalculator.java | 6541 ++++----- .../XLS/formulas/FormulaCalculator.java | 222 +- .../formats/XLS/formulas/FormulaParser.java | 2820 ++-- .../XLS/formulas/FunctionConstants.java | 3241 +++-- .../formats/XLS/formulas/FunctionHandler.java | 2594 ++-- .../formats/XLS/formulas/GenericPtg.java | 1023 +- .../formats/XLS/formulas/IlblListener.java | 47 +- .../XLS/formulas/InformationCalculator.java | 1179 +- .../formats/XLS/formulas/IxtiListener.java | 35 +- .../XLS/formulas/LogicalCalculator.java | 450 +- .../formulas/LookupReferenceCalculator.java | 2436 ++-- .../XLS/formulas/MathFunctionCalculator.java | 2603 ++-- .../io/starter/formats/XLS/formulas/Ptg.java | 571 +- .../starter/formats/XLS/formulas/PtgAdd.java | 170 +- .../starter/formats/XLS/formulas/PtgArea.java | 1182 +- .../formats/XLS/formulas/PtgArea3d.java | 1151 +- .../formats/XLS/formulas/PtgAreaErr3d.java | 121 +- .../formats/XLS/formulas/PtgAreaN.java | 322 +- .../formats/XLS/formulas/PtgArray.java | 1115 +- .../starter/formats/XLS/formulas/PtgAtr.java | 321 +- .../starter/formats/XLS/formulas/PtgBool.java | 101 +- .../formats/XLS/formulas/PtgCalculator.java | 449 +- .../formats/XLS/formulas/PtgConcat.java | 135 +- .../starter/formats/XLS/formulas/PtgDiv.java | 192 +- .../starter/formats/XLS/formulas/PtgEQ.java | 242 +- .../formats/XLS/formulas/PtgEndSheet.java | 58 +- .../starter/formats/XLS/formulas/PtgErr.java | 208 +- .../starter/formats/XLS/formulas/PtgExp.java | 452 +- .../starter/formats/XLS/formulas/PtgFunc.java | 418 +- .../formats/XLS/formulas/PtgFuncVar.java | 342 +- .../starter/formats/XLS/formulas/PtgGE.java | 234 +- .../starter/formats/XLS/formulas/PtgGT.java | 242 +- .../starter/formats/XLS/formulas/PtgInt.java | 104 +- .../formats/XLS/formulas/PtgIsect.java | 356 +- .../starter/formats/XLS/formulas/PtgLE.java | 246 +- .../starter/formats/XLS/formulas/PtgLT.java | 245 +- .../formats/XLS/formulas/PtgMemArea.java | 439 +- .../formats/XLS/formulas/PtgMemAreaA.java | 205 +- .../formats/XLS/formulas/PtgMemAreaN.java | 39 +- .../formats/XLS/formulas/PtgMemAreaNV.java | 41 +- .../formats/XLS/formulas/PtgMemErr.java | 43 +- .../formats/XLS/formulas/PtgMemFunc.java | 938 +- .../formats/XLS/formulas/PtgMissArg.java | 60 +- .../starter/formats/XLS/formulas/PtgMlt.java | 198 +- .../formats/XLS/formulas/PtgMystery.java | 51 +- .../starter/formats/XLS/formulas/PtgNE.java | 210 +- .../starter/formats/XLS/formulas/PtgName.java | 353 +- .../formats/XLS/formulas/PtgNameX.java | 223 +- .../formats/XLS/formulas/PtgNumber.java | 133 +- .../formats/XLS/formulas/PtgParen.java | 86 +- .../formats/XLS/formulas/PtgPercent.java | 97 +- .../formats/XLS/formulas/PtgPower.java | 106 +- .../formats/XLS/formulas/PtgRange.java | 429 +- .../starter/formats/XLS/formulas/PtgRef.java | 1744 +-- .../formats/XLS/formulas/PtgRef3d.java | 697 +- .../formats/XLS/formulas/PtgRefErr.java | 79 +- .../formats/XLS/formulas/PtgRefErr3d.java | 89 +- .../starter/formats/XLS/formulas/PtgRefN.java | 569 +- .../starter/formats/XLS/formulas/PtgStr.java | 202 +- .../starter/formats/XLS/formulas/PtgSub.java | 204 +- .../formats/XLS/formulas/PtgUMinus.java | 125 +- .../formats/XLS/formulas/PtgUPlus.java | 89 +- .../formats/XLS/formulas/PtgUnion.java | 324 +- .../XLS/formulas/StatisticalCalculator.java | 3693 +++--- .../formats/XLS/formulas/TextCalculator.java | 1502 +-- .../formats/XML/UnicodeInputStream.java | 250 +- .../formats/cellformat/CellFormat.java | 13 +- .../formats/cellformat/CellFormatFactory.java | 94 +- .../formats/cellformat/DateCellFormat.java | 64 +- .../formats/cellformat/GeneralCellFormat.java | 91 +- .../formats/cellformat/NumberCellFormat.java | 149 +- .../starter/formats/escher/EscherRecord.java | 182 +- .../io/starter/formats/escher/MsofbtBSE.java | 327 +- .../formats/escher/MsofbtBstoreContainer.java | 34 +- .../formats/escher/MsofbtClientAnchor.java | 108 +- .../formats/escher/MsofbtClientData.java | 30 +- .../io/starter/formats/escher/MsofbtDg.java | 70 +- .../formats/escher/MsofbtDgContainer.java | 34 +- .../io/starter/formats/escher/MsofbtDgg.java | 135 +- .../formats/escher/MsofbtDggContainer.java | 34 +- .../io/starter/formats/escher/MsofbtOPT.java | 971 +- .../io/starter/formats/escher/MsofbtSp.java | 70 +- .../formats/escher/MsofbtSpContainer.java | 32 +- .../io/starter/formats/escher/MsofbtSpgr.java | 83 +- .../formats/escher/MsofbtSpgrContainer.java | 32 +- .../formats/escher/MsofbtSplitMenuColors.java | 124 +- .../naming/InitialContextFactoryImpl.java | 34 +- .../io/starter/naming/InitialContextImpl.java | 384 +- src/main/java/io/starter/naming/NameImpl.java | 351 +- .../io/starter/naming/NameParserImpl.java | 51 +- .../starter/naming/NamingEnumerationImpl.java | 87 +- src/main/java/io/starter/toolkit/Base64.java | 1464 +- .../io/starter/toolkit/BrowserLauncher.java | 172 +- .../java/io/starter/toolkit/ByteTools.java | 787 +- .../starter/toolkit/CompatibleBigDecimal.java | 69 +- .../io/starter/toolkit/CompatibleVector.java | 484 +- .../toolkit/CompatibleVectorHints.java | 33 +- .../toolkit/DefaultTempFileGeneratorImpl.java | 75 +- .../toolkit/DelimitedValuesParser.java | 144 +- .../io/starter/toolkit/ExceptionListener.java | 15 +- .../io/starter/toolkit/ExtenClassLoader.java | 110 +- .../io/starter/toolkit/FastAddVector.java | 284 +- .../io/starter/toolkit/FastGetVector.java | 334 +- .../java/io/starter/toolkit/FileFilter.java | 25 +- .../io/starter/toolkit/GenericRecycleBin.java | 395 +- src/main/java/io/starter/toolkit/InFile.java | 85 +- .../starter/toolkit/IndirectOutputStream.java | 289 +- .../java/io/starter/toolkit/JFileWriter.java | 307 +- .../java/io/starter/toolkit/LogOutputter.java | 10 +- src/main/java/io/starter/toolkit/Logger.java | 914 +- .../java/io/starter/toolkit/MD4Digest.java | 238 +- src/main/java/io/starter/toolkit/Money.java | 2354 ++-- src/main/java/io/starter/toolkit/OutFile.java | 20 +- .../io/starter/toolkit/ProgressListener.java | 19 +- .../io/starter/toolkit/ProgressNotifier.java | 19 +- .../java/io/starter/toolkit/Recyclable.java | 44 +- .../java/io/starter/toolkit/RecycleBin.java | 83 +- .../toolkit/RecycleBinFullException.java | 37 +- .../io/starter/toolkit/ResourceLoader.java | 1034 +- .../io/starter/toolkit/SpecialArrayList.java | 1091 +- .../java/io/starter/toolkit/StringTool.java | 2772 ++-- .../io/starter/toolkit/TempFileGenerator.java | 27 +- .../io/starter/toolkit/TempFileManager.java | 146 +- .../starter/toolkit/WriterOutputStream.java | 395 +- .../starter/toolkit/XLSOptimizedTreeList.java | 1740 ++- .../io/starter/toolkit/XSLConverterTool.java | 595 +- .../io/starter/OpenXLS/build.properties | 3 - .../java/io/starter/OpenXLS/FormulaTest.java | 652 +- 533 files changed, 153968 insertions(+), 150514 deletions(-) create mode 100644 .idea/encodings.xml create mode 100644 .idea/vcs.xml delete mode 100755 src/main/java/io/starter/OpenXLS/util/Logger.java delete mode 100644 src/main/java/io/starter/OpenXLS/util/SystemConstants.java diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..b26911b --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/AutoFilterHandle.java b/src/main/java/io/starter/OpenXLS/AutoFilterHandle.java index 14e11d7..7e61d90 100644 --- a/src/main/java/io/starter/OpenXLS/AutoFilterHandle.java +++ b/src/main/java/io/starter/OpenXLS/AutoFilterHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -29,220 +29,210 @@ * Spreadsheet * * @author John McMahon - * */ public class AutoFilterHandle implements Handle { - private AutoFilter af = null; - - /** - * For internal use only. Creates an AutoFilter Handle based on the AutoFilter - * passed in - * - * @param AutoFilter - * af - */ - protected AutoFilterHandle(AutoFilter af) { - this.af = af; - } - - /** - * returns the string representation of this AutoFilter - * - * @return the string representation of this AutoFilter - */ - @Override - public String toString() { - if (this.af != null) - return af.toString(); - return "No AutoFilter"; - } - - /** - * returns the column this AutoFilter is applied to
- * NOTE: this may not be 100% exact - * - * @return in column number - */ - public int getCol() { - if (this.af != null) - return af.getCol(); - return -1; - } - - /** - * Sets the custom comparison of this AutoFilter via a String operator and an - * Object value
- * Only those rows that meet the equation: (column value) OP value will be shown - *
- * e.g show all rows where column value >= 2.99 - *

- * Object value can be of type - *

- * String
- * Boolean
- * Error
- * a Number type object - *

- * String operator may be one of: "=", ">", ">=", "<>", "<", "<=" - * - * @param Object - * val - value to set - * @param String - * op - operator - * @see setVal2 - */ - public void setVal(Object val, String op) { - if (af != null) - af.setVal(val, op); - } - - /** - * Sets the custom comparison of the second condition of this AutoFilter via a - * String operator and an Object value
- * This method sets the second condition of a two-condition filter - *

- * Only those rows that meet the equation:
- * first condition AND/OR (column value) OP value will be shown
- * e.g show all rows where (column value) <= 1.99 AND (column value) >= 2.99 - *

- * Object value can be of type - *

- * String
- * Boolean
- * Error
- * a Number type object - *

- * String operator may be one of: "=", ">", ">=", "<>", "<", "<=" - * - * @param Object - * val - value to set - * @param String - * op - operator - * @param boolean - * AND - true if two conditions should be AND'ed, false if OR'd - * @see setVal2 - */ - public void setVal2(Object val, String op, boolean AND) { - if (af != null) - af.setVal2(val, op, AND); - } - - /** - * returns the String representation of the comparison value for this - * AutoFilter, if any - * - *
- * This will return the comparison value of the first condition for those - * AutoFilters containing two conditions - * - * @return String comparison value for the second condition or null if none - * exists - * @see getVal2 - */ - public String getVal() { - if (af != null) - return (String) af.getVal(); - return null; - } - - /** - * returns the String representation of the second comparison value for this - * AutoFilter, if any - * - *
- * This will return the comparison value of the second condition for those - * AutoFilters containing two conditions - * - * @return String comparison value for the second condition or null if none - * exists - * @see getVal - */ - public String getVal2() { - if (af != null) - return (String) af.getVal2(); - return null; - } - - /** - * get the operator associated with this AutoFilter
- * NOTE: this will return the operator in the first condition if this AutoFilter - * contains two conditions
- * Use getOp2 to retrieve the second condition operator - * - * @return String operator - */ - public String getOp() { - if (af != null) - return af.getOp(); - return null; - } - - /** - * Sets this AutoFilter to be a Top-n or Bottom-n type of filter
- * Top-n filters only show the Top n values or percent in the column
- * Bottom-n filters only show the bottom n values or percent in the column
- * n can be from 1-500, or 0 to turn off Top 10 filtering - * - * @param int - * n - 0-500 - * @param boolean - * percent - true if show Top-n percent; false to show Top-n items - * @param boolean - * top10 - true if show Top-n (items or percent), false to show - * Bottom-n (items or percent) - */ - public void setTop10(int n, boolean percent, boolean top10) { - if (af != null) - af.setTop10(n, percent, top10); - } - - /** - * returns true if this AutoFilter is set to Top-10
- * Top-n filters only show the Top n values or percent in the column - * - * @return - */ - public boolean isTop10() { - if (af != null) - return af.isTop10(); - return false; - } - - /** - * sets this AutoFilter to filter all blank rows - */ - public void setFilterBlanks() { - if (af != null) - af.setFilterBlanks(); - } - - /** - * sets this AutoFilter to filter all non-blank rows - */ - public void setFilterNonBlanks() { - if (af != null) - af.setFilterNonBlanks(); - } - - /** - * returns true if this AutoFitler is set to filter all blank rows - * - * @return true if filter blanks, false otherwise - */ - public boolean isFilterBlanks() { - if (af != null) - return af.isFilterBlanks(); - return false; - } - - /** - * returns true if this AutoFitler is set to filter all non-blank rows - * - * @return true if filter non-blanks, false otherwise - */ - public boolean isFilterNonBlanks() { - if (af != null) - return af.isFilterNonBlanks(); - return false; - } + private AutoFilter af = null; + + /** + * For internal use only. Creates an AutoFilter Handle based on the AutoFilter + * passed in + * + * @param AutoFilter af + */ + protected AutoFilterHandle(AutoFilter af) { + this.af = af; + } + + /** + * returns the string representation of this AutoFilter + * + * @return the string representation of this AutoFilter + */ + @Override + public String toString() { + if (this.af != null) + return af.toString(); + return "No AutoFilter"; + } + + /** + * returns the column this AutoFilter is applied to
+ * NOTE: this may not be 100% exact + * + * @return in column number + */ + public int getCol() { + if (this.af != null) + return af.getCol(); + return -1; + } + + /** + * Sets the custom comparison of this AutoFilter via a String operator and an + * Object value
+ * Only those rows that meet the equation: (column value) OP value will be shown + *
+ * e.g show all rows where column value >= 2.99 + *

+ * Object value can be of type + *

+ * String
+ * Boolean
+ * Error
+ * a Number type object + *

+ * String operator may be one of: "=", ">", ">=", "<>", "<", "<=" + * + * @param Object val - value to set + * @param String op - operator + * @see setVal2 + */ + public void setVal(Object val, String op) { + if (af != null) + af.setVal(val, op); + } + + /** + * Sets the custom comparison of the second condition of this AutoFilter via a + * String operator and an Object value
+ * This method sets the second condition of a two-condition filter + *

+ * Only those rows that meet the equation:
+ * first condition AND/OR (column value) OP value will be shown
+ * e.g show all rows where (column value) <= 1.99 AND (column value) >= 2.99 + *

+ * Object value can be of type + *

+ * String
+ * Boolean
+ * Error
+ * a Number type object + *

+ * String operator may be one of: "=", ">", ">=", "<>", "<", "<=" + * + * @param Object val - value to set + * @param String op - operator + * @param boolean AND - true if two conditions should be AND'ed, false if OR'd + * @see setVal2 + */ + public void setVal2(Object val, String op, boolean AND) { + if (af != null) + af.setVal2(val, op, AND); + } + + /** + * returns the String representation of the comparison value for this + * AutoFilter, if any + * + *
+ * This will return the comparison value of the first condition for those + * AutoFilters containing two conditions + * + * @return String comparison value for the second condition or null if none + * exists + * @see getVal2 + */ + public String getVal() { + if (af != null) + return (String) af.getVal(); + return null; + } + + /** + * returns the String representation of the second comparison value for this + * AutoFilter, if any + * + *
+ * This will return the comparison value of the second condition for those + * AutoFilters containing two conditions + * + * @return String comparison value for the second condition or null if none + * exists + * @see getVal + */ + public String getVal2() { + if (af != null) + return (String) af.getVal2(); + return null; + } + + /** + * get the operator associated with this AutoFilter
+ * NOTE: this will return the operator in the first condition if this AutoFilter + * contains two conditions
+ * Use getOp2 to retrieve the second condition operator + * + * @return String operator + */ + public String getOp() { + if (af != null) + return af.getOp(); + return null; + } + + /** + * Sets this AutoFilter to be a Top-n or Bottom-n type of filter
+ * Top-n filters only show the Top n values or percent in the column
+ * Bottom-n filters only show the bottom n values or percent in the column
+ * n can be from 1-500, or 0 to turn off Top 10 filtering + * + * @param int n - 0-500 + * @param boolean percent - true if show Top-n percent; false to show Top-n items + * @param boolean top10 - true if show Top-n (items or percent), false to show + * Bottom-n (items or percent) + */ + public void setTop10(int n, boolean percent, boolean top10) { + if (af != null) + af.setTop10(n, percent, top10); + } + + /** + * returns true if this AutoFilter is set to Top-10
+ * Top-n filters only show the Top n values or percent in the column + * + * @return + */ + public boolean isTop10() { + if (af != null) + return af.isTop10(); + return false; + } + + /** + * sets this AutoFilter to filter all blank rows + */ + public void setFilterBlanks() { + if (af != null) + af.setFilterBlanks(); + } + + /** + * sets this AutoFilter to filter all non-blank rows + */ + public void setFilterNonBlanks() { + if (af != null) + af.setFilterNonBlanks(); + } + + /** + * returns true if this AutoFitler is set to filter all blank rows + * + * @return true if filter blanks, false otherwise + */ + public boolean isFilterBlanks() { + if (af != null) + return af.isFilterBlanks(); + return false; + } + + /** + * returns true if this AutoFitler is set to filter all non-blank rows + * + * @return true if filter non-blanks, false otherwise + */ + public boolean isFilterNonBlanks() { + if (af != null) + return af.isFilterNonBlanks(); + return false; + } } diff --git a/src/main/java/io/starter/OpenXLS/Cell.java b/src/main/java/io/starter/OpenXLS/Cell.java index b68e53e..605c04a 100644 --- a/src/main/java/io/starter/OpenXLS/Cell.java +++ b/src/main/java/io/starter/OpenXLS/Cell.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,95 +25,92 @@ /** * A lightweight subset of Cell methods allowing for low memory overhead * streaming implementations - * - * @author John McMahon * + * @author John McMahon */ public interface Cell { - /** - * Cell types - */ - public static final int TYPE_BLANK = -1; - public static final int TYPE_STRING = 0; - public static final int TYPE_FP = 1; - public static final int TYPE_INT = 2; - public static final int TYPE_FORMULA = 3; - public static final int TYPE_BOOLEAN = 4; - public static final int TYPE_DOUBLE = 5; + /** + * Cell types + */ + int TYPE_BLANK = -1; + int TYPE_STRING = 0; + int TYPE_FP = 1; + int TYPE_INT = 2; + int TYPE_FORMULA = 3; + int TYPE_BOOLEAN = 4; + int TYPE_DOUBLE = 5; - public boolean isDate(); + boolean isDate(); - public int getCellType(); + int getCellType(); - /** - * Returns the Formatting record ID (FormatId) for this Cell
- * This can be used with 'setFormatId(int i)' to copy the formatting from one - * Cell to another (e.g. a template cell to a new cell) - * - * @return int the FormatId for this Cell - * - */ - public int getFormatId(); + /** + * Returns the Formatting record ID (FormatId) for this Cell
+ * This can be used with 'setFormatId(int i)' to copy the formatting from one + * Cell to another (e.g. a template cell to a new cell) + * + * @return int the FormatId for this Cell + */ + int getFormatId(); - /** - * Returns the value of this Cell in the native underlying data type. - * - * Formula cells will return the calculated value of the formula in the - * calculated data type. - * - * Use 'getStringVal()' to return a String regardless of underlying value type. - * - * @return Object value for this Cell - */ - public Object getVal(); + /** + * Returns the value of this Cell in the native underlying data type. + *

+ * Formula cells will return the calculated value of the formula in the + * calculated data type. + *

+ * Use 'getStringVal()' to return a String regardless of underlying value type. + * + * @return Object value for this Cell + */ + Object getVal(); - /** - * Returns the value of the Cell as a String with formatting pattern applied.. - *
- * see: http://java.sun.com/docs/books/tutorial/i18n/format/decimalFormat.html - *
- * boolean Cell types will return "true" or "false"
- * Negative numbers that are formatted in excel to show as red values rather - * than using a "-" will return with a minus symbol. - * - * @return String the formatted value of the Cell - */ - public String getFormattedStringVal(); + /** + * Returns the value of the Cell as a String with formatting pattern applied.. + *
+ * see: http://java.sun.com/docs/books/tutorial/i18n/format/decimalFormat.html + *
+ * boolean Cell types will return "true" or "false"
+ * Negative numbers that are formatted in excel to show as red values rather + * than using a "-" will return with a minus symbol. + * + * @return String the formatted value of the Cell + */ + String getFormattedStringVal(); - /** - * Returns the column number of this Cell. - * - * @return int the Column Number of the Cell - */ - public int getColNum(); + /** + * Returns the column number of this Cell. + * + * @return int the Column Number of the Cell + */ + int getColNum(); - /** - * Returns the row number of this Cell. - * - * NOTE: This is the 1-based row number such as you will see in a spreadsheet - * UI. - * - * ie: A1 = row 1 - * - * - * @return int the ONE-based Row Number of the Cell - */ - public int getRowNum(); + /** + * Returns the row number of this Cell. + *

+ * NOTE: This is the 1-based row number such as you will see in a spreadsheet + * UI. + *

+ * ie: A1 = row 1 + * + * @return int the ONE-based Row Number of the Cell + */ + int getRowNum(); - /** - * Returns the Address of this Cell as a String. - * - * @return String the address of this Cell in the WorkSheet - */ - public String getCellAddress(); + /** + * Returns the Address of this Cell as a String. + * + * @return String the address of this Cell in the WorkSheet + */ + String getCellAddress(); - /** - * Returns the name of this Cell's WorkSheet as a String. - * - * @return String the name this Cell's WorkSheet - */ - public String getWorkSheetName(); + /** + * Returns the name of this Cell's WorkSheet as a String. + * + * @return String the name this Cell's WorkSheet + */ + String getWorkSheetName(); } diff --git a/src/main/java/io/starter/OpenXLS/CellComparator.java b/src/main/java/io/starter/OpenXLS/CellComparator.java index 3e57121..72bd8a9 100644 --- a/src/main/java/io/starter/OpenXLS/CellComparator.java +++ b/src/main/java/io/starter/OpenXLS/CellComparator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,112 +22,110 @@ */ package io.starter.OpenXLS; -import java.util.Comparator; - import io.starter.formats.XLS.FormulaNotFoundException; +import java.util.Comparator; + /** * CellComparitor is a implementation of a comparitor for sorting cell values. * In this instance numeric values (whether from a cell or a formula result) are * sorted before any string values. - * + *

* Date values are sorted according to their internal date representation. Note * that currently this means Dates will always sort above strings due to them * storing their value as a long. - * + * * Starter Inc. - * */ public class CellComparator implements Comparator { - /** - * Compare 2 cellHandle classes - * - * This method handles comparisons of cells, note that formula results are used - * rather than formula strings, Numbers are sorted ahead of string values. Dates - * are stored as numbers internally in excel so are sorted against numbers - * - */ - public int compare(Object cellHandle1, Object cellHandle2) { - CellHandle cell1 = (CellHandle) cellHandle1; - CellHandle cell2 = (CellHandle) cellHandle2; + /** + * Compare 2 cellHandle classes + *

+ * This method handles comparisons of cells, note that formula results are used + * rather than formula strings, Numbers are sorted ahead of string values. Dates + * are stored as numbers internally in excel so are sorted against numbers + */ + public int compare(Object cellHandle1, Object cellHandle2) { + CellHandle cell1 = (CellHandle) cellHandle1; + CellHandle cell2 = (CellHandle) cellHandle2; - int cellType1 = cell1.getCellType(); - int cellType2 = cell1.getCellType(); + int cellType1 = cell1.getCellType(); + int cellType2 = cell1.getCellType(); - // numerics - possibly break out more to see if values are floating point or - // not. - // would help for equality, which is likely never reached here. - if (cell1.isNumber() && cell2.isNumber()) { - if (cell1.getDoubleVal() > cell2.getDoubleVal()) - return 1; - if (cell1.getDoubleVal() < cell2.getDoubleVal()) - return -1; - return 0; - } else if (cell1.isNumber()) {// get formula value if exists and is a numeric value - if (cellType2 == CellHandle.TYPE_FORMULA) { - FormulaHandle f = null; - try { - f = cell2.getFormulaHandle(); - } catch (FormulaNotFoundException e) { - } - Double d = f.getDoubleVal(); - if (!(d == Double.NaN)) { - if (cell1.getDoubleVal() > d) - return 1; - if (cell1.getDoubleVal() < d) - return -1; - return 0; - } - } else { - return 1; - } - } else if (cell2.isNumber()) { - if (cellType1 == CellHandle.TYPE_FORMULA) { - FormulaHandle f = null; - try { - f = cell1.getFormulaHandle(); - } catch (FormulaNotFoundException e) { - } - Double d = f.getDoubleVal(); - if (!(d == Double.NaN)) { - if (cell2.getDoubleVal() < d) - return 1; - if (cell2.getDoubleVal() > d) - return -1; - return 0; - } - } else { - return -1; - } - } + // numerics - possibly break out more to see if values are floating point or + // not. + // would help for equality, which is likely never reached here. + if (cell1.isNumber() && cell2.isNumber()) { + if (cell1.getDoubleVal() > cell2.getDoubleVal()) + return 1; + if (cell1.getDoubleVal() < cell2.getDoubleVal()) + return -1; + return 0; + } else if (cell1.isNumber()) {// get formula value if exists and is a numeric value + if (cellType2 == CellHandle.TYPE_FORMULA) { + FormulaHandle f = null; + try { + f = cell2.getFormulaHandle(); + } catch (FormulaNotFoundException e) { + } + Double d = f.getDoubleVal(); + if (!(d == Double.NaN)) { + if (cell1.getDoubleVal() > d) + return 1; + if (cell1.getDoubleVal() < d) + return -1; + return 0; + } + } else { + return 1; + } + } else if (cell2.isNumber()) { + if (cellType1 == CellHandle.TYPE_FORMULA) { + FormulaHandle f = null; + try { + f = cell1.getFormulaHandle(); + } catch (FormulaNotFoundException e) { + } + Double d = f.getDoubleVal(); + if (!(d == Double.NaN)) { + if (cell2.getDoubleVal() < d) + return 1; + if (cell2.getDoubleVal() > d) + return -1; + return 0; + } + } else { + return -1; + } + } - // Two formulas; - if (cellType1 == CellHandle.TYPE_FORMULA && cellType2 == CellHandle.TYPE_FORMULA) { - try { - FormulaHandle f1 = cell1.getFormulaHandle(); - FormulaHandle f2 = cell2.getFormulaHandle(); - double d1 = f1.getDoubleVal(); - double d2 = f2.getDoubleVal(); - if (!(d1 == Double.NaN) && !(d2 == Double.NaN)) { - if (d1 > d2) - return 1; - if (d1 < d2) - return -1; - return 0; - } else if (!(d1 == Double.NaN)) { - return 1; - } else if (!(d2 == Double.NaN)) { - return -1; - } - } catch (FormulaNotFoundException e) { - } - } + // Two formulas; + if (cellType1 == CellHandle.TYPE_FORMULA && cellType2 == CellHandle.TYPE_FORMULA) { + try { + FormulaHandle f1 = cell1.getFormulaHandle(); + FormulaHandle f2 = cell2.getFormulaHandle(); + double d1 = f1.getDoubleVal(); + double d2 = f2.getDoubleVal(); + if (!(d1 == Double.NaN) && !(d2 == Double.NaN)) { + if (d1 > d2) + return 1; + if (d1 < d2) + return -1; + return 0; + } else if (!(d1 == Double.NaN)) { + return 1; + } else if (!(d2 == Double.NaN)) { + return -1; + } + } catch (FormulaNotFoundException e) { + } + } - // Strings, the last choice - String val1 = cell1.getStringVal(); - String val2 = cell2.getStringVal(); - return val1.compareTo(val2); - } + // Strings, the last choice + String val1 = cell1.getStringVal(); + String val2 = cell2.getStringVal(); + return val1.compareTo(val2); + } } diff --git a/src/main/java/io/starter/OpenXLS/CellHandle.java b/src/main/java/io/starter/OpenXLS/CellHandle.java index b89f64e..e41d04b 100644 --- a/src/main/java/io/starter/OpenXLS/CellHandle.java +++ b/src/main/java/io/starter/OpenXLS/CellHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,75 +22,24 @@ */ package io.starter.OpenXLS; -import static io.starter.OpenXLS.JSONConstants.JSON_CELL_FORMATTED_VALUE; -import static io.starter.OpenXLS.JSONConstants.JSON_CELL_FORMULA; -import static io.starter.OpenXLS.JSONConstants.JSON_CELL_VALUE; -import static io.starter.OpenXLS.JSONConstants.JSON_DATA; -import static io.starter.OpenXLS.JSONConstants.JSON_DATETIME; -import static io.starter.OpenXLS.JSONConstants.JSON_DOUBLE; -import static io.starter.OpenXLS.JSONConstants.JSON_FLOAT; -import static io.starter.OpenXLS.JSONConstants.JSON_FORMULA_HIDDEN; -import static io.starter.OpenXLS.JSONConstants.JSON_HIDDEN; -import static io.starter.OpenXLS.JSONConstants.JSON_HREF; -import static io.starter.OpenXLS.JSONConstants.JSON_INTEGER; -import static io.starter.OpenXLS.JSONConstants.JSON_LOCATION; -import static io.starter.OpenXLS.JSONConstants.JSON_LOCKED; -import static io.starter.OpenXLS.JSONConstants.JSON_MERGEACROSS; -import static io.starter.OpenXLS.JSONConstants.JSON_MERGECHILD; -import static io.starter.OpenXLS.JSONConstants.JSON_MERGEDOWN; -import static io.starter.OpenXLS.JSONConstants.JSON_MERGEPARENT; -import static io.starter.OpenXLS.JSONConstants.JSON_RED_FORMAT; -import static io.starter.OpenXLS.JSONConstants.JSON_STRING; -import static io.starter.OpenXLS.JSONConstants.JSON_STYLEID; -import static io.starter.OpenXLS.JSONConstants.JSON_TEXT_ALIGN; -import static io.starter.OpenXLS.JSONConstants.JSON_TYPE; -import static io.starter.OpenXLS.JSONConstants.JSON_VALIDATION_MESSAGE; -import static io.starter.OpenXLS.JSONConstants.JSON_WORD_WRAP; - -import java.awt.Color; -import java.awt.font.TextAttribute; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Stack; - -import org.json.JSONException; -import org.json.JSONObject; - -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.Blank; -import io.starter.formats.XLS.Boolerr; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.CellNotFoundException; -import io.starter.formats.XLS.CellPositionConflictException; -import io.starter.formats.XLS.CellTypeMismatchException; -import io.starter.formats.XLS.Cf; -import io.starter.formats.XLS.ColumnNotFoundException; -import io.starter.formats.XLS.Condfmt; import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.FormatConstantsImpl; -import io.starter.formats.XLS.Formula; -import io.starter.formats.XLS.FormulaNotFoundException; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.formats.XLS.Hlink; -import io.starter.formats.XLS.Labelsst; -import io.starter.formats.XLS.Mulblank; -import io.starter.formats.XLS.Note; -import io.starter.formats.XLS.NumberRec; -import io.starter.formats.XLS.ReferenceTracker; -import io.starter.formats.XLS.Rk; -import io.starter.formats.XLS.Unicodestring; -import io.starter.formats.XLS.XLSConstants; -import io.starter.formats.XLS.XLSRecord; -import io.starter.formats.XLS.Xf; +import io.starter.formats.XLS.*; import io.starter.formats.XLS.charts.Ai; import io.starter.formats.XLS.formulas.Ptg; import io.starter.formats.XLS.formulas.PtgRef; import io.starter.formats.cellformat.CellFormatFactory; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; +import org.json.JSONException; +import org.json.JSONObject; + +import java.awt.Color; +import java.awt.font.TextAttribute; +import java.io.Serializable; +import java.util.List; +import java.util.*; + +import static io.starter.OpenXLS.JSONConstants.*; /** * The CellHandle provides a handle to an XLS Cell and its values.
@@ -114,9 +63,9 @@ * - change the value of a Cell
* - get a handle to any Formula for this Cell
*
- * + * * Starter Inc. - * + * * @see WorkBookHandle * @see WorkSheetHandle * @see FormulaHandle @@ -125,2942 +74,2828 @@ */ public class CellHandle implements Cell, Serializable, Handle, Comparable { - /** - * - * - */ - private static final long serialVersionUID = 4737120893891570607L; - /** - * Cell types - */ - public static final int TYPE_BLANK = Cell.TYPE_BLANK; - public static final int TYPE_STRING = Cell.TYPE_STRING; - public static final int TYPE_FP = Cell.TYPE_FP; - public static final int TYPE_INT = Cell.TYPE_INT; - public static final int TYPE_FORMULA = Cell.TYPE_FORMULA; - public static final int TYPE_BOOLEAN = Cell.TYPE_BOOLEAN; - public static final int TYPE_DOUBLE = Cell.TYPE_DOUBLE; - public static final int NOTATION_STANDARD = 0, NOTATION_SCIENTIFIC = 1, NOTATION_SCIENTIFIC_EXCEL = 2; - - private transient WorkBook wbh = null; - private transient WorkSheetHandle wsh = null; - private ColHandle mycol; - private RowHandle myrow; - private FormatHandle formatter; - // reusing or creating new xfs is handled in FormatHandle/cloneXf and - // updateXf - // boolean useExistingXF = !false; - private boolean DEBUG = false; - private XLSRecord mycell; - - /** - * returns the underlying BIFF8 record for the Cell
- * NOTE: the underlying record is not a part of the public API and may change at - * any time. - * - * @return Returns the underlying biff record. - */ - public XLSRecord getRecord() { - return mycell; - } - - /** - * sets the underlying BIFF8 record for the Cell
- * NOTE: the underlying record is not a part of the public API and may change at - * any time. - * - * @param XLSRecord - * rec - The BIFF record to set. - */ - public void setRecord(XLSRecord rec) { - this.mycell = rec; - } - - /** - * Public Constructor added for use in Bean-context ONLY. - * - * Do NOT create CellHandles manually. - */ - public CellHandle(BiffRec c) { - mycell = (XLSRecord) c; - } - - /** - * if this cellhandle refers to a mulblank, ensure internal mulblank properties - * are set to appropriate cell in the mulblank range - */ - private void setMulblank() { - if (mycell.getOpcode() == XLSConstants.MULBLANK) { - if (mulblankcolnum == -1) { // init - mulblankcolnum = ((Mulblank) mycell).getColNumber(); - ((Mulblank) mycell).setCurrentCell(mulblankcolnum); - ((Mulblank) mycell).getIxfe(); // ensure myxf is set to correct - // xf for the given cell in the - // set of mulblanks - } else if (mulblankcolnum != mycell.getColNumber()) { - ((Mulblank) mycell).setCurrentCell(mulblankcolnum); - ((Mulblank) mycell).getIxfe(); // ensure myxf is set to correct - // xf for the given cell in the - // set of mulblanks - } - this.formatter = null; - } - } - - /** - * Public Constructor added for use in Bean-context ONLY. - * - * Do NOT create CellHandles manually. - */ - public CellHandle(BiffRec c, WorkBook myb) { - mycell = (XLSRecord) c; - setMulblank(); - this.wbh = myb; - } - - /** - * Get a FormatHandle (a Format Object describing the formats for this Cell) - * referenced by this CellHandle. - * - * @return FormatHandle - * @see FormatHandle - */ - void setFormatHandle() { - setMulblank(); - if (formatter != null && formatter.getFormatId() == this.mycell.getIxfe()) { - return; - } - // reusing or creating new xfs is handled in FormatHandle/cloneXf and - // updateXf - if (this.mycell.getXfRec() != null) { - formatter = new FormatHandle(this.wbh, this.mycell.myxf); - } else {// should ever happen now? - // useExistingXF = false; - if (wbh == null && this.mycell.getWorkBook() != null) - formatter = new FormatHandle(this.mycell.getWorkBook(), -1); - else - formatter = new FormatHandle(this.wbh, -1); - } - formatter.addCell(mycell); - } - - /** - * Sets a default "empty" value appropriate for the cell type of this CellHandle - *
- * For example, will set the value to 0.0 for TYPE_DOUBLE, an empty String for - * TYPE_BLANK - **/ - public void setToDefault() { - setVal(this.getDefaultVal()); - } - - /** - * Get the default "empty" data value for this CellHandle - * - * @return Object a default empty value corresponding to the cell type
- * such as 0.0 for TYPE_DOUBLE or an empty String for TYPE_BLANK - **/ - public Object getDefaultVal() { - return mycell.getDefaultVal(); - } - - /** - * Sets the number format pattern for this cell. All Excel built-in number - * formats are supported. Custom formats will not be applied by OpenXLS (e.g. - * the {@link #getFormattedStringVal} method) but they will be written correctly - * to the output file. For more information on number format patterns see - * Microsoft KB264372. - * - * @param pat - * the Excel number format pattern to apply - * @see FormatConstantsImpl#getBuiltinFormats - */ - public void setFormatPattern(String pat) { - setFormatHandle(); - formatter.setFormatPattern(pat); - } - - /** - * Gets the number format pattern for this cell, if set. For more information on - * number format patterns see - * Microsoft KB264372. - * - * @return the Excel number format pattern for this cell or null if - * none is applied - */ - public String getFormatPattern() { - if (this.getFont() == null) - return ""; - return mycell.getFormatPattern(); - } - - /** - * Returns whether this Cell has Date formatting applied.
- * NOTE: This does not guarantee that the value is a valid date. - * - * @return boolean true if this Cell has a Date Format applied - */ - public boolean isDate() { - if (mycell.myxf == null) - return false; - if (mycell.isString) - return false; - if (mycell.isBoolean) - return false; - if (mycell.isBlank) - return false; - return mycell.myxf.isDatePattern(); - } - - /** Returns whether this cell is a formula. */ - public boolean isFormula() { - return mycell.isFormula(); - } - - /** - * Returns whether the formula for the Cell is hidden - * - * @return boolean true if formula is hidden - */ - public boolean isFormulaHidden() { - return this.getFormatHandle().isFormulaHidden(); - } - - /** - * returns whether this Cell is locked for editing - * - * @return boolean true if the cell is locked - */ - public boolean isLocked() { - return this.getFormatHandle().isLocked(); - } - - /** - * Returns whether this is a blank cell. - * - * @return true if this cell is blank - */ - public boolean isBlank() { - return this.mycell.isBlank; - } - - /** - * Returns whether this Cell has a Currency format applied. - * - * @return boolean true if this Cell has a Currency format applied - */ - public boolean isCurrency() { - if (mycell.myxf == null) - return false; - return mycell.myxf.isCurrencyPattern(); - } - - /** - * Returns whether this Cell has a numeric value - * - * @return boolean true if this Cell contains a numeric value - */ - public boolean isNumber() { - return this.mycell.isNumber(); - } - - /** - * change the size (in points) of the Font used by this Cell and all other Cells - * sharing this FormatId.
- * NOTE: To add an entirely new Font use the setFont(String fn, int typ, int sz) - * method instead. - * - * @param int - * sz - Font size in Points. - */ - public void setFontSize(int sz) { - setFormatHandle(); - sz *= 20; // excel size is 1/20 pt. - formatter.setFontHeight(sz); - } - - /** - * change the weight (boldness) of the Font used by this Cell.
- * Some examples:
- * a weight of 200 is normal
- * a weight of 700 is bold
- * - * @param int - * wt - Font weight range between 100-1000 - */ - public void setFontWeight(int wt) { - setFormatHandle(); - formatter.setFontWeight(wt); - } - - /** - * Convenience method for toggling the bold state of the Font used by this Cell. - * - * @param boolean - * bold - true if bold - */ - public void setBold(boolean bold) { - setFormatHandle(); - formatter.setBold(bold); - } - - /** - * get the weight (boldness) of the Font used by this Cell. - * - * @return int Font weight range between 100-1000 - */ - public int getFontWeight() { - if (this.getFont() == null) - return FormatHandle.DEFAULT_FONT_WEIGHT; - return mycell.getFont().getFontWeight(); - } - - /** - * get the size in points of the Font used by this Cell - * - * @return int Font size in Points. - */ - public int getFontSize() { - if (this.getFont() == null) - return FormatHandle.DEFAULT_FONT_SIZE; - return mycell.getFont().getFontHeight() / 20; - } - - /** - * get the Color of the Font used by this Cell.
- * - * @see FormatHandle.COLOR constants - * @return int Excel color constant for Font color - */ - public Color getFontColor() { - if (this.getFont() == null) - return FormatHandle.Black; - int clidx = this.getFont().getColor(); - - // handle white on white text issue - Xf x = mycell.getXfRec(); - int clidb = x.getBackgroundColor(); - if (clidx == 64 && clidb == 64) { // return black - return FormatHandle.Black; - } - // black on black - if (clidx < this.getWorkBook().getColorTable().length) { - Color mycolr = this.getWorkBook().getColorTable()[clidx]; - return mycolr; - } - return Color.black; - } - - /** - * Set the color of the Font used by this Cell. - * - * @param java - * .Awt.Color col - color of the font - */ - public void setFontColor(Color col) { - setFormatHandle(); - formatter.setFontColor(col); - } - - /** - * set the Foreground Color for this Cell.
- *
- * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and - * Background color is the PATTERN color for all patterns not equal to - * PATTERN_SOLID
- * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and - * Background Color is 64 (white). - * - * @param int - * t - Excel color constant - * @see FormatHandle.COLOR constants - */ - public void setForegroundColor(int t) { - setFormatHandle(); - formatter.setForegroundColor(t); - } - - /** - * set the Foreground Color for this Cell
- * NOTE: this is the PATTERN Color - * - * @param int - * Excel color constant - * @see FormatHandle.COLOR constants - */ - // TODO: is this doc correct? - public void setForeColor(int i) { - if (mycell.myxf == null) - this.getNewXf(); - mycell.myxf.setForeColor(i, null); - } - - /** - * set the Background Color for this Cell.
- *
- * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and - * Background color is the PATTERN color for all patterns not equal to - * PATTERN_SOLID
- * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and - * Background Color is 64 (white). - * - * @param int - * t - Excel color constant - * @see FormatHandle.COLOR constants - */ - public void setBackgroundColor(int t) { - setFormatHandle(); - formatter.setBackgroundColor(t); - } - - /** - * get the Color of the Cell Foreground of this Cell and all other cells sharing - * this FormatId.
- *
- * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and - * Background color is the PATTERN color for all patterns not equal to - * PATTERN_SOLID
- * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and - * Background Color is 64 (white). - * - * @return java.awt.Color of the Font - */ - public Color getForegroundColor() { - if (this.mycell.getXfRec() != null) { - int clidx = mycell.getXfRec().getForegroundColor(); - if (clidx < this.getWorkBook().getColorTable().length) { - Color mycolr = this.getWorkBook().getColorTable()[clidx]; - return mycolr; - } - } - return Color.black; - } - - /** - * sets the Color of the Cell Foreground pattern for this Cell. - * - *
- *
- * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and - * Background color is the PATTERN color for all patterns not equal to - * PATTERN_SOLID
- * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and - * Background Color is 64 (white). - * - * @param java - * .awt.Color col - color for the foreground - */ - public void setForegroundColor(Color col) { - setFormatHandle(); - formatter.setForegroundColor(col); - } - - /** - * get the Color of the Cell Background by this Cell and all other cells sharing - * this FormatId.
- *
- * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and - * Background color is the PATTERN color for all patterns not equal to - * PATTERN_SOLID
- * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and - * Background Color is 64 (white). - * - * @return java.awt.Color for the Font - */ - public Color getBackgroundColor() { - if (this.mycell.getXfRec() != null) { - Xf x = mycell.getXfRec(); - int clidx = x.getBackgroundColor(); - if (clidx < this.getWorkBook().getColorTable().length) { - Color mycolr = this.getWorkBook().getColorTable()[clidx]; - return mycolr; - } - } - return Color.white; - } - - /** - * Return the cell background color i.e. the color if a pattern is set, or white - * if none - * - * @return java.awt.Color cell background color - */ - public Color getCellBackgroundColor() { - setFormatHandle(); - int clidx = formatter.getCellBackgroundColor(); - if (clidx < this.wbh.getWorkBook().getColorTable().length) { - Color mycolr = this.getWorkBook().getColorTable()[clidx]; - return mycolr; - } - return Color.white; - } - - /** - * set the Color of the Cell Background pattern for this Cell.
- *
- * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and - * Background color is the PATTERN color for all patterns not equal to - * PATTERN_SOLID
- * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and - * Background Color is 64 (white). - * - * @param java - * .awt.Color col - background color - */ - public void setBackgroundColor(Color col) { - setFormatHandle(); - formatter.setBackgroundColor(col); - } - - /** - * set the Color of the Cell Background for this Cell.
- *
- * see FormatHandle.COLOR constants for valid values
- *
- * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and - * Background color is the PATTERN color for all patterns not equal to - * PATTERN_SOLID
- * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and - * Background Color is 64 (white). - * - * @param int - * t - Excel color constant for Cell Background color - */ - public void setCellBackgroundColor(int t) { - setFormatHandle(); - formatter.setCellBackgroundColor(t); - } - - /** - * set the Color of the Cell Background for this Cell.
- *
- * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and - * Background color is the PATTERN color for all patterns not equal to - * PATTERN_SOLID
- * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and - * Background Color is 64 (white). - * - * @param java - * .awt.Color col - color of the cell background - */ - public void setCellBackgroundColor(Color col) { - setFormatHandle(); - formatter.setCellBackgroundColor(col); - } - - /** - * set the Color of the Cell Background Pattern for this Cell. - * - * @param java - * .awt.Color col - color of the pattern background - */ - public void setPatternBackgroundColor(Color col) { - setFormatHandle(); - formatter.setBackgroundColor(col); - } - - /** - * set the Background Pattern for this Cell.
- * - * @param int - * t - Excel pattern constant - * @see FormatHandle.PATTERN constants - */ - public void setBackgroundPattern(int t) { - setFormatHandle(); - formatter.setPattern(t); - } - - /** - * get the Background Pattern for this cell
- * - * @return int Excel pattern constant - * @see FormatHandle.PATTERN constants - */ - public int getBackgroundPattern() { - return this.mycell.getXfRec().getFillPattern(); - } - - /** - * get the fill pattern for this cell
- * Same as getBackgroundPattern
- * - * @return int Excel fill pattern constant - * @see FormatHandle.PATTERN constants - */ - public int getFillPattern() { - setFormatHandle(); - return formatter.getFillPattern(); - } - - /** - * set the Color of the Border for this Cell. - * - * @param java - * .awt.Color col - border color - */ - public void setBorderColor(Color col) { - setFormatHandle(); - formatter.setBorderColor(col); - } - - /** - * set the Color of the right Border line for this Cell. - * - * @param java - * .awt.Color col - right border color - */ - public void setBorderRightColor(Color col) { - setFormatHandle(); - formatter.setBorderRightColor(col); - } - - /** - * set the Color of the left Border line for this Cell. - * - * @param java - * .awt.Color col - left border color - */ - public void setBorderLeftColor(Color col) { - setFormatHandle(); - formatter.setBorderLeftColor(col); - } - - /** - * set the Color of the top Border line for this Cell. - * - * @param java - * .awt.Color col - top border color - */ - public void setBorderTopColor(Color col) { - setFormatHandle(); - formatter.setBorderTopColor(col); - } - - /** - * set the Color of the bottom Border line for this Cell. - * - * @param java - * .awt.Color col - bottom border color - */ - public void setBorderBottomColor(Color col) { - setFormatHandle(); - formatter.setBorderBottomColor(col); - } - - /** - * get the Font face used by this Cell. - * - * @return String the system name of the font for this Cell - */ - public String getFontFace() { - if (this.getFont() == null) - return FormatHandle.DEFAULT_FONT_FACE; - return mycell.getFont().getFontName(); - } - - /** - * set the Font face used by this Cell. - * - * @param String - * fn - system name of the font - */ - public void setFontFace(String fn) { - setFormatHandle(); - formatter.setFontName(fn); - } - - /** - * set the Border line style for this Cell. - * - * @param short - * s - border constant - * @see FormatHandle.BORDER line style constants - */ - public void setBorderLineStyle(short s) { - setFormatHandle(); - formatter.setBorderLineStyle(s); - } - - /** - * set the Right Border line style for this Cell. - * - * @param short - * s - border constant - * @see FormatHandle.BORDER line style constants - */ - public void setRightBorderLineStyle(short s) { - setFormatHandle(); - formatter.setRightBorderLineStyle(s); - } - - /** - * set the Left Border line style for this Cell. - * - * @see FormatHandle.BORDER line style constants - * @param short - * s - border constant - */ - public void setLeftBorderLineStyle(short s) { - setFormatHandle(); - formatter.setLeftBorderLineStyle(s); - } - - /** - * set the Top Border line style for this Cell. - * - * @param short - * s - border constant - * @see FormatHandle.BORDER line style constants - */ - public void setTopBorderLineStyle(short s) { - setFormatHandle(); - formatter.setTopBorderLineStyle(s); - } - - /** - * set the Bottom Border line style for this Cell. - * - * @param short - * s - border constant - * @see FormatHandle.BORDER line style constants - */ - public void setBottomBorderLineStyle(short s) { - setFormatHandle(); - formatter.setBottomBorderLineStyle(s); - } - - /** - * removes the borders for this cell - * - */ - public void removeBorder() { - setFormatHandle(); - formatter.removeBorders(); - } - - /** - * Get the OpenXLS Font for this Cell, which roughly matches the functionality - * of the java.awt.Font class.
- * Due to awt problems on console systems, converting the OpenXLS font to a GUI - * font is up to you.
- * - * @return OpenXLS font for Cell - */ - public Font getFont() { - return mycell.getFont(); - } - - /** - * convert the OpenXLS font used by this Cell to java.awt.Font - * - * @return java.awt.Font for this cell - */ - public java.awt.Font getAwtFont() { - String fface = "Arial"; - try { - fface = this.getFontFace(); - } catch (Exception e) { - ; - } - int sz = 12; - try { - sz = this.getFontSize(); - } catch (Exception e) { - sz = 12; // back to default - } - sz += 4; // Excel seems to display 1 point larger than Java - // Logger.logInfo("Displaying font:" + fface); - HashMap ftmap = new HashMap(); - // implement underlines - try { - if (this.getIsUnderlined()) { - ftmap.put(java.awt.font.TextAttribute.UNDERLINE, java.awt.font.TextAttribute.UNDERLINE); - } - } catch (Exception e) { - ; - } - // Ahhh, much cooler fonts here! - ftmap.put(java.awt.font.TextAttribute.FAMILY, fface); - float fx = this.getFontWeight(); - ftmap.put(java.awt.font.TextAttribute.SIZE, new Float(sz)); - // TODO: Interpret other weights- LIGHT, DEMI_LIGHT, DEMI_BOLD, etc. - if (fx == FormatHandle.BOLD) - ftmap.put(java.awt.font.TextAttribute.WEIGHT, java.awt.font.TextAttribute.WEIGHT_BOLD); - else - ftmap.put(java.awt.font.TextAttribute.WEIGHT, java.awt.font.TextAttribute.WEIGHT_REGULAR); - return new java.awt.Font(ftmap); - } - - /** - * Set the Font for this Cell via font name, font style and font size
- * This method adds a new Font to the WorkBook.
- * Roughly matches the functionality of the java.awt.Font class. - * - * @param String - * fn - system name of the font e.g. "Arial" - * @param int - * stl - font style (either Font.BOLD or Font.PLAIN) - * @param int - * sz - font size in points - */ - public void setFont(String fn, int stl, int sz) { - setFormatHandle(); - formatter.setFont(fn, stl, sz); - } - - /** - * Get a CommentHandle to the note attached to this cell - */ - public CommentHandle getComment() throws DocumentObjectNotFoundException { - // this needs significant cleanup. We should not have to iterate notes - ArrayList notes = mycell.getSheet().getNotes(); - for (int i = 0; i < notes.size(); i++) { - Note n = (Note) notes.get(i); - if (n.getCellAddressWithSheet().equals(this.getCellAddressWithSheet())) { - return new CommentHandle(n); - } - } - throw new DocumentObjectNotFoundException("Note record not found at " + this.getCellAddressWithSheet()); - } - - /** - * Removes any note/comment records attached to this cell - */ - public void removeComment() { - try { - CommentHandle note = this.getComment(); - note.remove(); - } catch (DocumentObjectNotFoundException e) { - } - } - - /** - * Creates a new annotation (Note or Comment) to the cell - * - * @param comment - * -- text of note - * @param author - * -- name of author - * @return CommentHandle - handle which allows access to the Note object - * @see CommentHandle - */ - public CommentHandle createComment(String comment, String author) { - Note n = mycell.getSheet().createNote(this.getCellAddress(), comment, author); - return new CommentHandle(n); - } - - /** - * returns whether the Font for this Cell is underlined - * - * @return boolean true if Font is underlined - */ - public boolean getIsUnderlined() { - if (this.getFont() == null) - return false; - if (this.getFont().getUnderlineStyle() != 0x0) - return true; - return false; - } - - /** - * Set whether the Font for this Cell is underlined - * - * @param boolean - * b - true if the Font for this Cell should be underlined (single - * underline only) - */ - public void setUnderlined(boolean isUnderlined) { - setFormatHandle(); - if (isUnderlined) - this.getFont().setUnderlineStyle(Font.STYLE_UNDERLINE_SINGLE); - else - this.getFont().setUnderlineStyle(Font.STYLE_UNDERLINE_NONE); - } - - /** - * set the Font Color for this Cell
- *
- * see FormatHandle.COLOR constants for valid values - * - * @param int - * t - Excel color constant - */ - public void setFontColor(int t) { - setFormatHandle(); - formatter.setFontColor(t); - } - - /** - * Returns any other Cells merged with this one, or null if this Cell is not a - * part of a merged range.
- * Adding and/or removing Cells from this CellRange will merge or unmerge the - * Cells. - * - * @return CellRange object containing all Cells in this Cell's merged range. - */ - public CellRange getMergedCellRange() { - return mycell.getMergeRange(); - } - - /** - * Returns if the Cell is the parent (cell containing display value) of a merged - * cell range. - * - * @return boolean true if this cell is a merge parent cell - */ - public boolean isMergeParent() { - try { - CellRange cr = mycell.getMergeRange(); - if (cr == null) - return false; - int[] i = cr.getRangeCoords(); - if (this.getRowNum() + 1 == i[0] && this.getColNum() == i[1]) - return true; - } catch (Exception e) { - return false; - } - return false; - - } - - /** - * Returns the ColHandle for the Cell. - * - * @return ColHandle for the Cell - */ - public ColHandle getCol() { - try { - if (mycol == null) - mycol = wsh.getCol(this.getColNum()); - } catch (ColumnNotFoundException ex) { - // can't happen, the column has to exist because we're in it - throw new RuntimeException(ex); - } - return this.mycol; - } - - /** - * Returns the RowHandle for the Cell. - * - * @return RowHandle representing the Row for the Cell - */ - public RowHandle getRow() { - if (myrow == null) - myrow = new RowHandle(mycell.getRow(), this.wsh); - return this.myrow; - } - - /** - * Returns the value of this Cell in the native underlying data type. - * - * Formula cells will return the calculated value of the formula in the - * calculated data type. - * - * Use 'getStringVal()' to return a String regardless of underlying value type. - * - * @return Object value for this Cell - */ - public Object getVal() { - return FormulaHandle.sanitizeValue(mycell.getInternalVal()); - } - - /**/ - - /** - * returns the java Type string of the Cell
- * One of: - *

  • "String" - *
  • "Float" - *
  • "Integer" - *
  • "Formula" - *
  • "Double" - * - * @return String java data type - */ - public String getCellTypeName() { - String typename = "Object"; - int tp = getCellType(); - switch (tp) { - case XLSConstants.TYPE_BLANK: - typename = "String"; - break; - case XLSConstants.TYPE_STRING: - typename = "String"; - break; - case XLSConstants.TYPE_FP: - typename = "Float"; - break; - case XLSConstants.TYPE_INT: - typename = "Integer"; - break; - case XLSConstants.TYPE_FORMULA: - typename = "Formula"; - break; - case XLSConstants.TYPE_DOUBLE: - typename = "Double"; - break; - } - return typename; - } - - /** - * Returns the type of the Cell as an int
    - *
  • TYPE_STRING = 0, - *
  • TYPE_FP = 1, - *
  • TYPE_INT = 2, - *
  • TYPE_FORMULA = 3, - *
  • TYPE_BOOLEAN = 4, - *
  • TYPE_DOUBLE = 5; - * - * @return int type for this Cell - */ - public int getCellType() { - return mycell.getCellType(); - } - - /** - * return the underlying cell record
    - * for internal API use only - * - * @return XLS cell record - */ - public BiffRec getCell() { - return mycell; - } - - /** - * returns Border Colors of Cell in: top, left, bottom, right order
    - * returns null or a java.awt.Color object for each of 4 sides - * - * @return java.awt.Color array representing the 4 borders of the cell - */ - public Color[] getBorderColors() { - getFormatHandle(); - return this.formatter.getBorderColors(); - } - - /** - * returns the low-level bytes for the underlying BIFF8 record.
    - * For Internal API use only - * - * @return bytes for the underlying record - */ - public byte[] getBytes() { - return mycell.getData(); - } - - /** - * Returns the column number of this Cell. - * - * @return int the Column Number of the Cell - */ - public int getColNum() { - setMulblank(); - return mycell.getColNumber(); - } - - /** - * Returns the row number of this Cell. - * - * NOTE: This is the 1-based row number such as you will see in a spreadsheet - * UI. - * - * ie: A1 = row 1 - * - * @return 1-based int the Row Number of the Cell - */ - public int getRowNum() { - return mycell.getRowNumber(); - } - - /** - * Returns the Address of this Cell as a String. - * - * @return String the address of this Cell in the WorkSheet - */ - public String getCellAddress() { - setMulblank(); - return mycell.getCellAddress(); - - } - - public int[] getIntLocation() { - setMulblank(); - return mycell.getIntLocation(); - } - - /** - * Returns the Address of this Cell as a String. Includes the sheet name in the - * address. - * - * @return String the address of this Cell in the WorkSheet - */ - public String getCellAddressWithSheet() { - setMulblank(); - return mycell.getCellAddressWithSheet(); - - } - - /** - * sets the column number referenced in the set of multiple blanks
    - * for multiple blank cells only
    - * for Internal Use. Not intended for the End User. - */ - short mulblankcolnum = -1; - - public void setBlankRef(int c) { - mulblankcolnum = (short) c; - } - - /** - * Returns the name of this Cell's WorkSheet as a String. - * - * @return String the name this Cell's WorkSheet - */ - public String getWorkSheetName() { - if (wsh == null) { - try { - return mycell.getSheet().getSheetName(); - } catch (Exception e) { - return ""; - } - } - return wsh.getSheetName(); - } - - /** - * Returns the value of the Cell as a String.
    - * boolean Cell types will return "true" or "false" - * - * @return String the value of the Cell - */ - public String getStringVal() { - return mycell.getStringVal(); - } - - /** - * Gets the value of the cell as a String with the number format applied. - * Boolean cell types will return "true" or "false". Custom number formats are - * not currently supported, although they will be written correctly to the - * output file. Patterns that display negative numbers in red are not currently - * supported; the number will be prefixed with a minus sign instead. For more - * information on number format patterns see - * Microsoft KB264372. - * - * @return the value of the cell as a string formatted according to the cell - * type and, if present, the number format pattern - */ - public String getFormattedStringVal() { - FormatHandle myfmt = this.getFormatHandle(); - return CellFormatFactory.fromPatternString(myfmt.getFormatPattern()).format(this); - } - - /** - * Gets the value of the cell as a String with the number format applied. - * Boolean cell types will return "true" or "false". Custom number formats are - * not currently supported, although they will be written correctly to the - * output file. Patterns that display negative numbers in red are not currently - * supported; the number will be prefixed with a minus sign instead. For more - * information on number format patterns see - * Microsoft KB264372. - * - * @param formatForXML - * if true non-compliant characters will be properly qualified - * @return the value of the cell as a string formatted according to the cell - * type and, if present, the number format pattern - */ - public String getFormattedStringVal(boolean formatForXML) { - FormatHandle myfmt = this.getFormatHandle(); - String val = this.getVal().toString(); - if (formatForXML) - val = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(val).toString(); - return CellFormatFactory.fromPatternString(myfmt.getFormatPattern()).format(val); - } - - /** - * Returns the value of the Cell as a String.
    - * For numeric cell values, including cells containing a formula which return a - * numeric value, the notation to be used in representing the value as a String - * must be specified.
    - * The Notation choices are: - *
  • CellHandle.NOTATION_STANDARD e.g. "8002974342" - *
  • CellHandle.NOTATION_SCIENTIFIC e.g. "8.002974342E9" - *
  • CellHandle.NOTATION_SCIENTIFIC_EXCEL e.g. "8.002974342+E9"
    - *
    - * For non-numeric values, the value of the cell as a string is returned
    - * boolean Cell types will return "true" or "false" - * - * @param int - * notation one of the CellHandle.NOTATION constants for numeric cell - * types; ignored for other cell types - * @param int - * notation - notation constant - * @return String the value of the Cell - */ - public String getStringVal(int notation) { - String numval = mycell.getStringVal(); - int i = this.getCellType(); - if (i == TYPE_FP || i == TYPE_INT || i == TYPE_FORMULA || i == TYPE_DOUBLE) { - return ExcelTools.formatNumericNotation(numval, notation); - } - return numval; - } - - /** - * Returns the value of the Cell as a String with the specified encoding.
    - * boolean Cell types will return "true" or "false" - * - * @param String - * encoding - * @return String the value of the Cell - */ - public String getStringVal(String encoding) { - return mycell.getStringVal(encoding); - } - - /** - * Returns the Formatting record ID (FormatId) for this Cell
    - * This can be used with 'setFormatId(int i)' to copy the formatting from one - * Cell to another (e.g. a template cell to a new cell) - * - * @return int the FormatId for this Cell - */ - public int getFormatId() { - setMulblank(); - return mycell.getIxfe(); - } - - /** - * get the conditional formatting record ID (FormatId)
    - * returns the normal FormatId if the condition(s) in the conditional format - * have not been met - * - * @return int the conditional format id - */ - public int getConditionalFormatId() { - ConditionalFormatHandle[] cfhandles = this.getConditionalFormatHandles(); - if (cfhandles == null || cfhandles.length == 0) { - return this.getFormatId(); - } - // TODO: only supporting first cfmat handle again - Condfmt cfmt = cfhandles[0].getCndfmt(); - - Iterator x = cfmt.getRules().iterator(); - while (x.hasNext()) { - Cf cx1 = ((Cf) x.next()); - // create a ptgref for this Cell - Ptg pref = new PtgRef(this.getCellAddress(), this.mycell, false); - - if (cx1.evaluate(pref)) { - // TODO: evaluate and combine multiple rules... - // currently returns on first true format - int ret = cfmt.getCfxe(); - return ret; - } - } - return this.getFormatId(); - } - - /** - * move this cell to another row
    - * throws CellPositionConflictException if there is a cell in that position - * already - * - * @param int - * newrow - new row number - * @exception CellPositionConflictException - */ - public void moveToRow(int newrow) throws CellPositionConflictException { - String newaddr = ExcelTools.getAlphaVal(mycell.getColNumber()); - newaddr += String.valueOf(newrow); - this.moveTo(newaddr); - } - - /** - * move this cell to another row
    - * overwrite any cells in the destination - * - * @param int - * newrow - new row number - */ - public void moveAndOverwriteToRow(int newrow) { - String newaddr = ExcelTools.getAlphaVal(mycell.getColNumber()); - newaddr += String.valueOf(newrow); - this.moveAndOverwriteTo(newaddr); - } - - /** - * move this cell to another column
    - * throws CellPositionConflictException if there is a cell in that position - * already - * - * @param String - * newcol - the new column in alpha format e.g. "A", "B" ... - * @exception CellPositionConflictException - */ - public void moveToCol(String newcol) throws CellPositionConflictException { - String newaddr = newcol; - newaddr += String.valueOf(mycell.getRowNumber() + 1); - this.moveTo(newaddr); - } - - /** - * Copy all formats from a source Cell to this Cell - * - * @param CellHandle - * source - source cell - */ - public void copyFormat(CellHandle source) { - this.getCell().copyFormat(source.getCell()); - } - - /** - * copy this Cell to another location. - * - * @param String - * newaddr - address for copy of this Cell in Excel-style e.g. "A1" - * @return returns the newly copied CellHandle - * @exception CellPositionConflictException - * if there is a cell in the new address already - */ - public CellHandle copyTo(String newaddr) throws CellPositionConflictException { - - // check for existing - Boundsheet bs = this.mycell.getSheet(); - - BiffRec rec = this.mycell; - XLSRecord nucell = (XLSRecord) ((XLSRecord) rec).clone(); - int[] rc = ExcelTools.getRowColFromString(newaddr); - nucell.setRowNumber(rc[0]); - nucell.setCol((short) rc[1]); - nucell.setXFRecord(this.mycell.getIxfe()); - bs.addRecord(nucell, rc); - - CellHandle ret = new CellHandle(nucell, wbh); - if (mycell.hyperlink != null) { - // set the bounds of the mycell.hyperlink - ret.setURL(this.getURL()); - } - ret.setWorkSheetHandle(this.getWorkSheetHandle()); - // this.getWorkSheetHandle().cellhandles.put(this.getCellAddress(), - // this); - return ret; - } - - /** - * Removes this Cell from the WorkSheet - * - * @param boolean - * nullme - true if this CellHandle should be nullified after removal - */ - public void remove(boolean nullme) { - mycell.getSheet().removeCell(mycell); - if (nullme) { - try { - this.finalize(); - } catch (Throwable t) { - ; - } - } - } - - /** - * move this cell to another location. - * - * @param String - * newaddr - the new address for Cell in Excel-style notation e.g. - * "A1" - * @exception CellPositionConflictException - * if there is a cell in the new address already - */ - public void moveTo(String newaddr) throws CellPositionConflictException { - - // check for existing - Boundsheet bs = mycell.getSheet(); - BiffRec oldhand = bs.getCell(newaddr); - if (oldhand != null) - throw new CellPositionConflictException(newaddr); - bs.moveCell(this.getCellAddress(), newaddr); - - if (mycell.hyperlink != null) { - // set the bounds of the mycell.hyperlink - // int[] bnds = - // ExcelTools.getRowColFromString(this.getCellAddress()); - - int[] bnds = ExcelTools.getRowColFromString(this.getCellAddress()); - - Hlink hl = mycell.hyperlink; - hl.setRowFirst(bnds[0]); - hl.setRowLast(bnds[0]); - hl.setColFirst(bnds[1]); - hl.setColLast(bnds[1]); - hl.init(); - } - } - - /** - * move this cell to another location, overwriting any cells that are in the way - * - * @param String - * newaddr - the new address for Cell in Excel-style notation e.g. - * "A1" - */ - public void moveAndOverwriteTo(String newaddr) { - - // check for existing - Boundsheet bs = mycell.getSheet(); - BiffRec oldhand = bs.getCell(newaddr); - bs.moveCell(this.getCellAddress(), newaddr); - - if (mycell.hyperlink != null) { - int[] bnds = ExcelTools.getRowColFromString(this.getCellAddress()); - - Hlink hl = mycell.hyperlink; - hl.setRowFirst(bnds[0]); - hl.setRowLast(bnds[0]); - hl.setColFirst(bnds[1]); - hl.setColLast(bnds[1]); - hl.init(); - } - } - - /** - * Set a conditional format upon this cell. - * - * Note that conditional format handles are bound to a specific worksheet, - * - * @param format - * A ConditionalFormatHandle in the same worksheet - */ - public void addConditionalFormat(ConditionalFormatHandle format) { - format.addCell(this); - } - - /** - * returns an array of FormatHandles for the Cell that have the current - * conditional format applied to them. - * - * This behavior is still in testing, and may change - * - * @return an array of FormatHandles, one for each of the Conditional Formatting - * rules - */ - public FormatHandle[] getConditionallyFormattedHandles() { - // TODO, should these be read-only? - // TODO, this is bad, only handles first cf record for the cell - ConditionalFormatHandle[] cfhandles = this.getConditionalFormatHandles(); - if (cfhandles != null) { - FormatHandle[] fmx = new FormatHandle[cfhandles[0].getCndfmt().getRules().size()]; - for (int t = 0; t < fmx.length; t++) { - fmx[t++] = new FormatHandle(cfhandles[0].getCndfmt(), wbh, t, this); - } - return fmx; - } - return null; - } - - /** - * return all the ConditionalFormatHandles for this Cell, if any - * - * @return - */ - public ConditionalFormatHandle[] getConditionalFormatHandles() { - WorkSheetHandle sh = this.getWorkSheetHandle(); - if (sh == null) { - return null; - } - ConditionalFormatHandle[] cfs = sh.getConditionalFormatHandles(); - ArrayList cfhandles = new ArrayList(); - for (int i = 0; i < cfs.length; i++) { - if (cfs[i].contains(this)) - cfhandles.add(cfs[i]); - } - ConditionalFormatHandle[] c = new ConditionalFormatHandle[cfhandles.size()]; - return (ConditionalFormatHandle[]) cfhandles.toArray(c); - } - - /** - * Gets the FormatHandle (a Format Object describing the formats for this Cell) - * for this Cell. - * - * @return FormatHandle for this Cell - */ - public FormatHandle getFormatHandle() { - if (this.formatter == null) - this.setFormatHandle(); - - return this.formatter; - } - - /** - * locks or unlocks this Cell for editing - * - * @param boolean - * locked - true if Cell should be locked, false otherwise - */ - public void setLocked(boolean locked) { - // create a new xf for this - // this causes formats to be lost this.useExistingXF = false; - getFormatHandle().setLocked(locked); - } - - /** - * Hides or shows the formula for this Cell, if present - * - * @param boolean - * hidden - setting whether to hide or show formulas for this Cell - */ - public void setFormulaHidden(boolean hidden) { - // create a new xf for this - // this causes formats to be lost this.useExistingXF = false; - getFormatHandle().setFormulaHidden(hidden); - } - - /** - * Sets the FormatHandle (a Format Object describing the formats for this Cell) - * for this Cell - * - * @param FormatHandle - * to apply to this Cell - * @see FormatHandle - */ - public void setFormatHandle(FormatHandle f) { - f.addCell(this.mycell); - this.formatter = f; - } - - /** - * Sets the Formatting record ID (FormatId) for this Cell - * - * This can be used with 'getFormatId()' to copy the formatting from one Cell to - * another (ie: a template cell to a new cell) - * - * @param int - * i - the new index to the Format for this Cell - */ - public void setFormatId(int i) { - mycell.setXFRecord(i); - } - - /** - * Resets this cell's format to the default. - */ - public void clearFormats() { - this.setFormatId(this.getWorkBook().getWorkBook().getDefaultIxfe()); - } - - /** - * Resets this cells contents to blank. - */ - public void clearContents() { - this.setVal(null); - } - - /** - * Resets this cell to the default, as if it had just been added. - */ - public void clear() { - this.clearFormats(); - this.clearContents(); - } - - /** - * Returns the Formula Handle (an Object describing a Formula) for this Cell, if - * it contains a formula - * - * @return FormulaHandle the Formula of the Cell - * @see FormulaHandle - * @exception FormulaNotFoundException - */ - public FormulaHandle getFormulaHandle() throws FormulaNotFoundException { - Formula f = mycell.getFormulaRec(); - if (f == null) { - throw new FormulaNotFoundException("No Formula for: " + getCellAddress()); - } - FormulaHandle fh = new FormulaHandle(f, this.wbh); - return fh; - } - - /** - * Returns the Hyperlink URL String for this Cell, if any - * - * @return String URL if this Cell contains a hyperlink - */ - public String getURL() { - if (mycell.hyperlink != null) - return mycell.hyperlink.getURL(); - return null; - } - - /** - * Returns the URL Description String for this Cell, if any - * - * @return String URL Description, if this Cell contains a hyperlink - */ - public String getURLDescription() { - if (mycell.hyperlink != null) - return mycell.hyperlink.getDescription(); - return ""; - } - - /** - * returns true if this Cell contains a hyperlink - * - * @return boolean true if this Cell contains a hyperlink - */ - public boolean hasHyperlink() { - return (mycell.hyperlink != null); - } - - /** - * Creates a new Hyperlink for this Cell from a URL String. Can be any valid - * URL. This URL String must include the protocol.
    - * For Example, "http://www.extentech.com/"
    - * To remove a hyperlink, pass in null for the URL String - * - * @param String - * urlstr - the URL String for this Cell - */ - public void setURL(String urlstr) { - if (urlstr == null) { - mycell.hyperlink = null; - // TODO: remove existing Hlink from stream - // mycell.hyperlink.remove(true); - return; - } - setURL(urlstr, "", ""); - } - - /** - * Creates a new Hyperlink for this Cell from a URL String, a descrpiton and - * textMark text.
    - *
    - * The URL String Can be any valid URL. This URL String must include the - * protocol. For Example, "http://www.extentech.com/"
    - * The textMark text is the porition of the URL that follows #
    - *
    - * NOTE: URL text and textMark text must not be null or "" - * - * @param String - * urlstr - the URL String - * @param String - * desc - the description text - * @param String - * textMark - the text that follows # - */ - public void setURL(String urlstr, String desc, String textMark) { - if (mycell.hyperlink != null) { - mycell.hyperlink.setURL(urlstr, desc, textMark); - } else { - // create new URL - mycell.hyperlink = (Hlink) Hlink.getPrototype(); - mycell.hyperlink.setURL(urlstr, desc, textMark); - - // why would we want to set the val during this operation? - // if (!desc.equals("")) setVal(desc); - - // set the bounds of the mycell.hyperlink - int[] bnds = ExcelTools.getRowColFromString(this.getCellAddress()); - mycell.hyperlink.setRowFirst(bnds[0]); - mycell.hyperlink.setColFirst(bnds[1]); - mycell.hyperlink.setRowLast(bnds[0]); - mycell.hyperlink.setColLast(bnds[1]); - } - } - - /** - * Sets a hyperlink to a location within the current template
    - * The URL String should be prefixed with "file://"
    - * - * @param String - * fileURLStr - the file URL String - */ - // TODO: document this: NOTE: Excel File URL in actuality does not match - // documentation - public void setFileURL(String fileURLStr) { - setFileURL(fileURLStr, "", ""); - } - - /** - * Sets a hyperlink to a location within the current template, and includes - * additional optional information: description + textMark text
    - *
    - * The URL String should be prefixed with "file://"
    - *
    - * The textMark text is the porition of the URL that follows # - * - * @param String - * fileURLstr - the file URL String - * @param String - * desc - the description text - * @param String - * textMark - text that follows # - */ - // TODO: this documentation is contradictory - public void setFileURL(String fileURLstr, String desc, String textMark) { - if (mycell.hyperlink != null) { - mycell.hyperlink.setFileURL(fileURLstr, desc, textMark); - } else { - mycell.hyperlink = (Hlink) Hlink.getPrototype(); - - mycell.hyperlink.setFileURL(fileURLstr, desc, textMark); - if (!desc.equals("")) - setVal(desc); - - // set the bounds of the mycell.hyperlink - int[] bnds = ExcelTools.getRowColFromString(this.getCellAddress()); - mycell.hyperlink.setRowFirst(bnds[0]); - mycell.hyperlink.setColFirst(bnds[1]); - mycell.hyperlink.setRowLast(bnds[0]); - mycell.hyperlink.setColLast(bnds[1]); - } - } - - /** - * Set the val of this Cell to an object
    - *
    - * The object may be one of type:
    - * String, Float, Integer, Double, Long, Boolean, java.sql.Date, or null
    - *
    - * To set a Cell to a formula, obj should be a string begining with "="
    - *
    - * To set a Cell to an array formula, obj should be a string begining with "{=" - *
    - * If you wish to put a line break in a string value, use the newline "\n" - * character. Note this will not function unless you also apply a format handle - * to the cell with WrapText=true - * - * @param Object - * obj - the object to set the value of the Cell to - * @exception CellTypeMismatchException - */ - - public void setVal(Object obj) throws CellTypeMismatchException { - if (this.wbh.getFormulaCalculationMode() != WorkBook.CALCULATE_EXPLICIT) - this.clearAffectedCells(); // blow out cache - - if (obj instanceof java.sql.Date) { - this.setVal((java.sql.Date) obj, null); - return; - } - if (obj instanceof String) { - String formstr = (String) obj; - if (formstr.indexOf("=") == 0 || formstr.startsWith("{=")) { // Formula or array string - try { - this.setFormula(formstr); - return; - } catch (/* 20070212 KSC: FunctionNotSupported */Exception a) { - Logger.logWarn( - "CellHandle.setVal() failed. Setting Formula to " + obj.toString() + " failed: " + a); - } - } - } - try { - setBiffRecValue(obj); - } catch (FunctionNotSupportedException fnse) { - // suppress these -- cell has been changed - } catch (Exception e) { - // NOT a CTMME always - - throw new CellTypeMismatchException(e.toString()); - } - } - - /** - * set the value of this cell to String s
    - * NOTE: this method will not check for formula references or do any data - * conversions
    - * This method is useful when a string may start with = but you do not want to - * convert to a Formula value - * - * If you wish to put a line break in the string use the newline "\n" character. - * Note this will not function unless you also apply a format handle to the cell - * with WrapText=true - * - * @param String - * s - the String value to set the Cell to - * @see setVal(Object obj) - * @throws CellTypeMismatchException - */ - public void setStringVal(String s) { - try { - if ((s == null || s.equals("")) && !(mycell instanceof Blank)) - changeCellType(s); - else if (s != null && !s.equals("")) { - if (!(mycell instanceof Labelsst)) - changeCellType(" "); // avoid potential issues with string - // values beginning with "=" - mycell.setStringVal(s); - } - } catch (Exception e) { - throw new CellTypeMismatchException(e.toString()); - } - } - - /** - * set the value of this cell to Unicodestring us
    - * NOTE: This method will not check for formula references or do any data - * conversions
    - * Useful when strings may start with = but you do not want to convert to a - * formula value - * - * @param Unicodestring - * us - Unicode String - * @throws CellTypeMismatchException - */ - public void setStringVal(Unicodestring us) { - try { - if ((us == null || us.toString().equals("")) && !(mycell instanceof Blank)) - changeCellType(null); // set to blank - else if (us != null && !us.toString().equals("")) { - if (!(mycell instanceof Labelsst)) - changeCellType(" "); // avoid potential issues with string - // values beginning with "=" - ((Labelsst) mycell).setStringVal(us); - } - } catch (Exception e) { - throw new CellTypeMismatchException(e.toString()); - } - } - - /** - * this method will be fired as each record is parsed from an input Spreadsheet - * - * Dec 15, 2010 - */ - public void fireParserEvent() { - - } - - /** - * Returns a String representation of this CellHandle - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - String ret = this.getCellAddress() + ":" + this.getStringVal(); - if (this.getURL() != null) - ret += this.getURL(); - return ret; - } - - /** - * Set the Value of the Cell to a double - * - * @param double - * d- double value to set this Cell to - * @throws CellTypeMismatchException - */ - public void setVal(double d) throws CellTypeMismatchException { - this.setVal(new Double(d)); - } - - /** - * Set value of this Cell to a Float - * - * @param float - * f - float value to set this Cell to - * @throws CellTypeMismatchException - */ - public void setVal(float f) throws CellTypeMismatchException { - this.setVal(new Float(f)); - } - - /** - * Sets the value of this Cell to a java.sql.Date.
    - * You must also specify a formatting pattern for the new date, or null for the - * default date format ("m/d/yy h:mm".)
    - *
    - * valid date format patterns:
    - * "m/d/y"
    - * "d-mmm-yy"
    - * "d-mmm"
    - * "mmm-yy"
    - * "h:mm AM/PM"
    - * "h:mm:ss AM/PM"
    - * "h:mm"
    - * "h:mm:ss"
    - * "m/d/yy h:mm"
    - * "mm:ss"
    - * "[h]:mm:ss"
    - * "mm:ss.0" - * - * @param java - * .sql.Date dt - the value of the new Cell - * @param String - * fmt - date formatting pattern - */ - public void setVal(java.sql.Date dt, String fmt) { - - if (this.wbh.getFormulaCalculationMode() != WorkBook.CALCULATE_EXPLICIT) - this.clearAffectedCells(); // blow out cache - if (fmt == null) - fmt = "m/d/yyyy"; - this.setVal(new Double(DateConverter.getXLSDateVal(dt))); - this.setFormatPattern(fmt); - } - - /** - * Sets the value of this Cell to a boolean value - * - * @param boolean - * b - boolean value to set this Cell to - * @throws CellTypeMismatchException - */ - public void setVal(boolean b) throws CellTypeMismatchException { - setVal(Boolean.valueOf(b)); - } - - /** - * Set the value of this Cell to an int value
    - * NOTE: setting a Boolean Cell type to a zero or a negative number will set the - * Cell to 'false'; setting it to an int value 1 or greater will set it to true. - * - * @param int - * i - int value to set this Cell to - * @throws CellTypeMismatchException - */ - public void setVal(int i) throws CellTypeMismatchException { - if (mycell.getCellType() == XLSConstants.TYPE_BOOLEAN) { - if (i > 0) - setVal(Boolean.valueOf(true)); - else - setVal(Boolean.valueOf(false)); - } else { - setVal(Integer.valueOf(i)); - } - } - - /** - * returns the value of this Cell as a double, if possible, or NaN if Cell value - * cannot be converted to double - * - * @return double value or NaN if the Cell value cannot be converted to a double - */ - public double getDoubleVal() { - return mycell.getDblVal(); - } - - /** - * returns the value of this Cell as a int, if possible, or NaN if Cell value - * cannot be converted to int - * - * @return int value or NaN if the Cell value cannot be converted to an int - */ - public int getIntVal() { - return mycell.getIntVal(); - } - - /** - * returns the value of this Cell as a float, if possible, or NaN if Cell value - * cannot be converted to float - * - * @return float value or NaN if the Cell value cannot be converted to an float - */ - public float getFloatVal() { - return mycell.getFloatVal(); - } - - /** - * returns the value of this Cell as a boolean
    - * If the Cell is not of type Boolean, returns false - * - * @return boolean value of cell - */ - public boolean getBooleanVal() { - return mycell.getBooleanVal(); - } - - /** - * Set a cell to Excel-compatible formula passed in as String.
    - * - * @param String - * formStr - the Formula String - * @throws FunctionNotSupportedException - * if unable to parse string correctly - */ - public void setFormula(String formStr) throws FunctionNotSupportedException { - int ixfe = this.mycell.getIxfe(); - this.remove(true); - this.mycell = wsh.add(formStr, this.getCellAddress()).mycell; - this.mycell.setXFRecord(ixfe); - } - - /** - * Set a cell to formula passed in as String. Sets the cachedValue as well, so - * no calculating is necessary. - * - * Parses the string to convert into a Excel formula.
    - * IMPORTANT NOTE: if cell is ALREADY a formula String this method will NOT - * reset it - * - * @param String - * formulaStr - The excel-compatible formula string to pass in - * @param Object - * value - The calculated value of the formula - * @throws Exception - * if unable to parse string correctly - */ - public void setFormula(String formStr, Object value) throws Exception { - if (!(this.mycell instanceof Formula)) { - int ixfe = this.mycell.getIxfe(); - CellRange cr = this.mycell.getMergeRange(); - int r = this.getRowNum(); - int c = this.getColNum(); - this.remove(true); - this.mycell = wsh.add(formStr, r, c, ixfe).mycell; - this.mycell.setMergeRange(cr); - } - Formula f = (Formula) this.mycell; - f.setCachedValue(value); - } - - /** - * sets the formula for this cellhandle using a stack of Ptgs appropriate for - * formula records. This method also sets the cachedValue of the formula as - * well, so no new calculating is necessary. - * - * @param Stack - * newExp - Stack of Ptgs - * @param Object - * value - calculated value of formula - */ - public void setFormula(Stack newExp, Object value) { - if (!(this.mycell instanceof Formula)) { - int ixfe = this.mycell.getIxfe(); - CellRange mccr = this.mycell.getMergeRange(); - int r = this.getRowNum(); - int c = this.getColNum(); - this.remove(true); - this.mycell = wsh.add("=0", r, c, ixfe).mycell; // add the most - // basic formula so - // can modify below - // ((: - this.mycell.setMergeRange(mccr); - } - try { - Formula f = (Formula) this.mycell; - f.setExpression(newExp); - f.setCachedValue(value); - } catch (Exception e) { - ;// do what?? - } - } - - /** - * Returns the size of the merged cell area, if one exists. - * - * @param row - * this parameter is ignored - * @param column - * this parameter is ignored - * @return a 2 position int array with number of rows and number of cols - * @deprecated since October 2012. This method duplicates the functionality of - * {@link #getMergedCellRange()}, which it calls internally. That - * method should be used instead. - */ - @Deprecated - public int[] getSpan(int row, int column) { - CellRange mergerange = getMergedCellRange(); - if (mergerange != null) { - if (DEBUG) - Logger.logInfo("CellHandle " + this.toString() + " getSpan() for range: " + mergerange.toString()); - int[] ret = { 0, 0 }; - // if(check.toString().equals(this.toString())){ //it's the first in - // the range -- show it! - try { - ret[0] = mergerange.getRows().length; - ret[1] = mergerange.getCols().length; // TODO: test! - } catch (Exception e) { - Logger.logWarn("CellHandle getting CellSpan failed: " + e); - } - // } - return ret; - } - return null; - } - - /** - * returns the WorkBookHandle for this Cell - * - * @return WorkBook the book - */ - public WorkBook getWorkBook() { - return wbh; - } - - /** - * get the index of the WorkSheet containing this Cell in the list of sheets - * - * @return int the WorkSheetHandle index for this Cell - */ - public int getSheetNum() { - return this.mycell.getSheet().getSheetNum(); - } - - /** - * get the WorkSheetHandle for this Cell - * - * @return the WorkSheetHandle for this Cell - */ - public WorkSheetHandle getWorkSheetHandle() { - return wsh; - } - - /** - * Determines if the cellHandle represents a completely blank/null cell, and can - * thus be ignored for many operations. - * - * Criteria for returning true is a cell type of BLANK, that has a default - * format id (0), is not part of a merge range, does not contain a URL, and is - * not a part of a validation - * - * @return true if cell is truly blank - */ - public boolean isDefaultCell() { - if ((this.getCellType() == CellHandle.TYPE_BLANK) - && ((this.getFormatId() == 15 && !this.wbh.getWorkBook().getIsExcel2007()) || this.getFormatId() == 0) - && (this.getMergedCellRange() == null) && (this.getURL() == null) - && (this.getValidationHandle() == null)) { - return true; - } - return false; - } - - /** - * set the WorkSheetHandle for this Cell - * - * @param WorkSheetHandle - * handle - the new worksheet for this Cell - * @see WorkSheetHandle - */ - public void setWorkSheetHandle(WorkSheetHandle handle) { - wsh = handle; - - // This is redundant, already done in WSH.getCell(). - - // if (wsh!=null) //20080616 KSC - // wsh.cellhandles.put(this.getCellAddress(), this); - } - - /** - * Returns an XML representation of the cell and it's component data - * - * @return String of XML - */ - public String getXML() { - return getXML(null); - } - - /** - * Returns an XML representation of the cell and it's component data - * - * @param int[] - * mergedRange - include merged ranges in the XML representation if - * not null - * @return String of XML - */ - public String getXML(int[] mergedRange) { - String vl = "", fvl = "", sv = "", hd = "", csp = "", hlink = ""; - Object val = null; - StringBuffer retval = new StringBuffer(); - String typename = this.getCellTypeName(); - // put the formula string in - if (typename.equals("Formula")) { - try { - FormulaHandle fmh = getFormulaHandle(); - String fms = fmh.getFormulaString(); - // use single quotes around formula value to avoid errors in - // xslt transform - if (fms.indexOf("\"") > 0) { - fvl = " Formula='" + StringTool.convertXMLChars(fms) + "'"; - } else { - fvl = " Formula=\"" + StringTool.convertXMLChars(fms) + "\""; - } - try { - if (this.wbh.getWorkBook().getCalcMode() != WorkBook.CALCULATE_EXPLICIT) { - val = fmh.calculate(); - } else { - try { - // changed from getVal() now that getVal returns a - // null - val = fmh.getStringVal(); - } catch (Exception e) { - Logger.logWarn("CellHandle.getXML formula calc failed: " + e.toString()); - } - } - if (val instanceof Float) - typename = "Float"; - else if (val instanceof Double) - typename = "Double"; - else if (val instanceof Integer) - typename = "Integer"; - else if (val instanceof java.util.Date || val instanceof java.sql.Date - || val instanceof java.sql.Timestamp) - typename = "DateTime"; - else - typename = "String"; - } catch (Exception e) { - typename = "String"; // default - } - } catch (Exception e) { - Logger.logErr("OpenXLS.getXML() failed getting type of Formula for: " + this.toString(), e); - } - } - if (this.isDate()) - typename = "DateTime"; // 20060428 KSC: Moved after Formula parsing - - // TODO: when RowHandle.getCells actually contains ALL cells, keep this - if (this.mycell.getOpcode() != XLSConstants.MULBLANK) { - // Put the style ID in - sv = " StyleID=\"s" + getFormatId() + "\""; - if (mergedRange != null) { // TODO: fix! - csp += " MergeAcross=\"" + ((mergedRange[3] - mergedRange[1]) + 1) + "\""; - csp += " MergeDown=\"" + (mergedRange[2] - mergedRange[0]) + "\""; - } - if (this.getCol().isHidden()) { - hd = " Hidden=\"true\""; - } - // TODO: HRefScreenTip ???? - if (this.getURL() != null) { - hlink = " HRef=\"" + StringTool.convertXMLChars(this.getURL()) + "\""; - } - - // put the date formattingin - // Assemble the string - retval.append(""); - if (typename.equals("DateTime")) { - retval.append(DateConverter.getFormattedDateVal(this)); - } else if (this.getCellType() == CellHandle.TYPE_STRING) { - val = this.getStringVal(); // (String)getVal(); - if (val.equals("")) { // 20070216 KSC: John, had the same edits, - // seems to work well in cursory tests ... - // retval.append(" "); does this screw up formulas expecting - // empty strings? -jm - } else { - retval.append(StringTool.convertXMLChars(val.toString())); - } - } else { - try { - // if(val == null) - val = this.getVal(); - retval.append(StringTool.convertXMLChars(val.toString()) + vl); - } catch (Exception e) { - Logger.logErr("CellHandle.getXML failed for: " + this.getCellAddress() + " in: " - + this.getWorkBook().toString(), e); - retval.append("XML ERROR!"); - } - } - retval.append(""); - retval.append(end_cell_xml); - } else { - int c = ((Mulblank) this.mycell).getColFirst(); - int lastcol = ((Mulblank) this.mycell).getColLast(); - for (; c <= lastcol; c++) { - mulblankcolnum = (short) c; - // Put the style ID in - sv = " StyleID=\"s" + getFormatId() + "\""; - if (this.getCol().isHidden()) { - hd = " Hidden=\"true\""; - } - // TODO: HRefScreenTip ???? - if (this.getURL() != null) { - hlink = " HRef=\"" + StringTool.convertXMLChars(this.getURL()) + "\""; - } - - // put the date formattingin - // Assemble the string - retval.append(""); - retval.append(end_cell_xml); - } - } - return retval.toString(); - } - - /** - * Set the horizontal alignment for this Cell - * - * - * @param int - * align - constant value representing the horizontal alignment. - * @see FormatHandle.ALIGN* constants - */ - public void setHorizontalAlignment(int align) { - setFormatHandle(); - formatter.setHorizontalAlignment(align); - } - - /** - * Returns an int representing the current horizontal alignment in this Cell. - * - * @return int representing horizontal alignment - * @see FormatHandle.ALIGN* constants - */ - public int getHorizontalAlignment() { - if (this.mycell.getXfRec() != null) { - return this.mycell.getXfRec().getHorizontalAlignment(); - } - // 0 is default alignment - return 0; - } - - /** - * Set the Vertical alignment for this Cell - * - * @param int - * align - constant value representing the vertical alignment. - * @see FormatHandle.ALIGN* constants - */ - public void setVerticalAlignment(int align) { - setFormatHandle(); - formatter.setVerticalAlignment(align); - } - - /** - * Returns an int representing the current vertical alignment in this Cell. - * - * @return int representing vertical alignment - * @see FormatHandle.ALIGN* constants - */ - public int getVerticalAlignment() { - if (this.mycell.getXfRec() != null) { - return this.mycell.getXfRec().getVerticalAlignment(); - } - // 1 is default alignment - return 1; - } - - /** - * Sets the cell wrapping behavior for this cell - * - * @param boolean - * wrapit - true if cell text should be wrapped (default is false) - */ - public void setWrapText(boolean wrapit) { - setFormatHandle(); - formatter.setWrapText(wrapit); - if (wrapit) { // when wrap text it automatically wraps if row height has - // NOT been set yet - try { - if (!this.getRow().isAlteredHeight()) // has row height been altered?? - this.getRow().setRowHeightAutoFit(); - } catch (Exception e) { /* ignore */ - } - } - } - - /** - * Get the cell wrapping behavior for this cell. - * - * @return true if cell text is wrapped - */ - public boolean getWrapText() { - if (this.mycell.getXfRec() != null) { - return this.mycell.getXfRec().getWrapText(); - } - // false is default alignment - return false; - } - - /** - * Set the rotation of the cell in degrees.
    - * Values 0-90 represent rotation up, 0-90degrees.
    - * Values 91-180 represent rotation down, 0-90 degrees.
    - * Value 255 is vertical - * - * @param int - * align - an int representing the rotation. - */ - public void setCellRotation(int align) { - setFormatHandle(); - formatter.setCellRotation(align); - } - - /** - * Get the rotation of this Cell in degrees.
    - * Values 0-90 represent rotation up, 0-90degrees.
    - * Values 91-180 represent rotation down, 0-90 degrees.
    - * Value 255 is vertical - * - * @return int representing the degrees of cell rotation - */ - public int getCellRotation() { - if (this.mycell.getXfRec() != null) { - return this.mycell.getXfRec().getRotation(); - } - // false is default alignment - return 0; - } - - public int compareTo(CellHandle that) { - int comp = this.getRowNum() - that.getRowNum(); - if (comp != 0) - return comp; - return this.getColNum() - that.getColNum(); - } - - @Override - public boolean equals(Object that) { - if (!(that instanceof CellHandle)) - return false; - return this.mycell.equals(((CellHandle) that).mycell); - } - - @Override - public int hashCode() { - return this.mycell.hashCode(); - } - - /** - * Set the super/sub script for the Font - * - * @param int - * ss - super/sub script constant (0 = none, 1 = super, 2 = sub) - */ - public void setScript(int ss) { - if (mycell.myxf == null) - this.getNewXf(); - mycell.myxf.getFont().setScript(ss); - } - - /** - * Set the val of the biffrec with an Object - * - * @param Object - * to set the value of the Cell to - */ - private void setBiffRecValue(Object obj) throws CellTypeMismatchException { - if (mycell.getOpcode() == XLSConstants.BLANK || mycell.getOpcode() == XLSConstants.MULBLANK) { - // no reason for this Blank blank = (Blank)mycell; - // String addr = mycell.getCellAddress(); - - // trim the Mulblank - /* - * KSC: mulblanks are NOT expanded now if (blank.getMyMul() != null){ Mulblank - * mblank = (Mulblank)blank.getMyMul(); mblank.trim(blank); } - */ - changeCellType(obj); // 20080206 KSC: Basically replaces all above - // code - } else { - if (obj == null) { - // should never be false ??? if (!(mycell instanceof Blank)) - changeCellType(obj); // will set to blank - } else if (obj instanceof Float || obj instanceof Double || obj instanceof Integer || obj instanceof Long) { - if ((mycell instanceof NumberRec) || (mycell instanceof Rk)) { - if (obj instanceof Float) { - Float f = (Float) obj; - mycell.setFloatVal(f.floatValue()); - } else if (obj instanceof Integer) { - Integer i = (Integer) obj; - mycell.setIntVal(i.intValue()); - } else if (obj instanceof Double) { - Double d = (Double) obj; - mycell.setDoubleVal(d.doubleValue()); - } else if (obj instanceof Long) { - Long d = (Long) obj; - mycell.setDoubleVal(d.longValue()); - } - } else - changeCellType(obj); - } else if (obj instanceof Boolean) { - if (mycell instanceof Boolerr) - ((Boolerr) mycell).setBooleanVal(((Boolean) obj).booleanValue()); - else - changeCellType(obj); - } else if (obj instanceof String) { - if (((String) obj).startsWith("=")) - changeCellType(obj); // easier to just redo a formula... - else if (!obj.toString().equalsIgnoreCase("")) { - if (mycell instanceof Labelsst) - mycell.setStringVal(String.valueOf(obj)); - else - changeCellType(obj); - } else if (!(mycell instanceof Blank)) - changeCellType(obj); - } - } - } - - /** - * if object type doesn't match current mycell record, remove and add - * appropriate record type - * - * @param obj - */ - private void changeCellType(Object obj) { - int[] rc = { mycell.getRowNumber(), mycell.getColNumber() }; - Boundsheet bs = mycell.getSheet(); - int oldXf = mycell.getIxfe(); - bs.removeCell(mycell); - BiffRec addedrec = bs.addValue(obj, rc, true); - mycell = (XLSRecord) addedrec; - mycell.setXFRecord(oldXf); - } - - /** - * retrieves or creates a new xf for this cell - * - * @return - */ - private Xf getNewXf() { - if (mycell.myxf != null) - return mycell.myxf; - // reusing or creating new xfs is handled in FormatHandle/cloneXf and - // updateXf - // this.useExistingXF = true; // flag to re-use this XF - try { - mycell.myxf = new Xf(this.getFont().getIdx()); - // get the recidx of the last Xf - int insertIdx = mycell.getWorkBook().getXf(mycell.getWorkBook().getNumXfs() - 1).getRecordIndex(); - // perform default add rec actions - - mycell.myxf.setSheet(null); - mycell.getWorkBook().getStreamer().addRecordAt(mycell.myxf, insertIdx + 1); - mycell.getWorkBook().addRecord(mycell.myxf, false); - // update the pointer - int xfe = mycell.myxf.getIdx(); - mycell.setIxfe(xfe); - return mycell.myxf; - } catch (Exception e) { - return null; - } - } - - static final String begin_hidden_emptycell_xml = ""; - - static final String begin_cell_xml = ""; - static final String end_cell_xml = "
    "; - - /** - * Returns an xml representation of an empty cell - * - * @param loc - * - the cell address - * @param isVisible - * - if the cell is visible (not hidden) - * @return - */ - protected static String getEmptyCellXML(String loc, boolean isVisible) { - if (!isVisible) { - return begin_hidden_emptycell_xml + loc + end_hidden_emptycell_xml; - } else { - return begin_cell_xml + loc + end_emptycell_xml; - } - } - - /** - * Calculates and returns all formulas that reference this CellHandle.
    - * Please note that these cells may have already been calculated, so in order to - * get their values without re-calculating them Extentech suggests setting the - * book level non-calculation flag, ie - * book.setFormulaCalculationMode(WorkBookHandle.CALCULATE_EXPLICIT); or - * FormulaHandle.getCachedVal() - * - * @return List of of calculated cells (CellHandles) - */ - public List calculateAffectedCells() { - ReferenceTracker rt = this.wbh.getWorkBook().getRefTracker(); - Iterator its = rt.clearAffectedFormulaCells(this).values().iterator(); - - List ret = new ArrayList(); - while (its.hasNext()) { - CellHandle cx = new CellHandle((BiffRec) its.next(), this.wbh); - ret.add(cx); - } - return ret; - } - - /** - * Internal method for clearing affected cells, does the same thing as - * calculateAffectedCells, but does not create a list - */ - protected void clearAffectedCells() { - ReferenceTracker rt = this.wbh.getWorkBook().getRefTracker(); - rt.clearAffectedFormulaCells(this); - } - - /** - * Calculates and returns all formulas on the same sheet that reference this - * CellHandle.
    - * Please note that these cells may have already been calculated, so in order to - * get their values without re-calculating them Extentech suggests setting the - * book level non-calculation flag, ie - * book.setFormulaCalculationMode(WorkBookHandle.CALCULATE_EXPLICIT); or - * FormulaHandle.getCachedVal() - * - * @return List of of calculated cells (CellHandles) - */ - public List calculateAffectedCellsOnSheet() { - Iterator its = this.wbh.getWorkBook().getRefTracker() - .clearAffectedFormulaCellsOnSheet(this, this.getWorkSheetName()).values().iterator(); - List ret = new ArrayList(); - while (its.hasNext()) { - CellHandle cx = new CellHandle((BiffRec) its.next(), this.wbh); - ret.add(cx); - } - return ret; - } - - /** - * flags chart references to the particular cell as dirty/ needing caches - * rebuilt - */ - public void clearChartReferences() { - ArrayList ret = new ArrayList(); - Iterator ii = this.wbh.getWorkBook().getRefTracker().getChartReferences(this.getCell()).iterator(); - while (ii.hasNext()) { - Ai ai = (Ai) ii.next(); - if (ai.getParentChart() != null) - ai.getParentChart().setMetricsDirty(); - } - } - - /** - * Creates a copy of this cell on the given worksheet at the given address. - * - * @param sourcecell - * the cell to copy - * @param newsheet - * the sheet to which the cell should be copied - * @param row - * the row in which the copied cell should be placed - * @param col - * the row in which the copied cell should be placed - * @return CellHandle representing the new Cell - */ - public static final CellHandle copyCellToWorkSheet(CellHandle sourcecell, WorkSheetHandle newsheet, int row, - int col) throws CellPositionConflictException, CellNotFoundException { - return copyCellToWorkSheet(sourcecell, newsheet, row, col, false); - } - - /** - * Creates a copy of this cell on the given worksheet at the given address. - * - * @param sourcecell - * the cell to copy - * @param newsheet - * the sheet to which the cell should be copied - * @param row - * the row in which the copied cell should be placed - * @param col - * the row in which the copied cell should be placed - * @param copyByValue - * whether to copy formulas' values instead of the formulas - * themselves - * @return CellHandle representing the new cell - */ - public static CellHandle copyCellToWorkSheet(CellHandle sourcecell, WorkSheetHandle newsheet, int row, int col, - boolean copyByValue) { - // copy cell values - CellHandle newcell = null; - - int offsets[] = { row - sourcecell.getRowNum(), col - sourcecell.getColNum() }; - - if (sourcecell.isFormula() && !copyByValue) - try { - FormulaHandle fmh = sourcecell.getFormulaHandle(); - newcell = newsheet.add(fmh.getFormulaString(), row, col); - FormulaHandle fm2 = newcell.getFormulaHandle(); - FormulaHandle.moveCellRefs(fm2, offsets); - } catch (FormulaNotFoundException ex) { - newcell = null; - } - - if (newcell == null) - newcell = newsheet.add(sourcecell.getVal(), row, col); - - return copyCellHelper(sourcecell, newcell); - } - - /** - * Create a copy of this Cell in another WorkBook - * - * - * @param sourcecell - * the cell to copy - * @param target - * worksheet to copy this cell into - * @return - */ - public static final CellHandle copyCellToWorkSheet(CellHandle sourcecell, WorkSheetHandle newsheet) { - // copy cell values - CellHandle newcell = null; - try { - FormulaHandle fmh = sourcecell.getFormulaHandle(); - // Logger.logInfo("testFormats Formula encountered: "+ - // fmh.getFormulaString()); - - newcell = newsheet.add(fmh.getFormulaString(), sourcecell.getCellAddress()); - } catch (FormulaNotFoundException ex) { - newcell = newsheet.add(sourcecell.getVal(), sourcecell.getCellAddress()); - } - return copyCellHelper(sourcecell, newcell); - } - - /** - * Get the JSON object for this cell. - * - * @return String representing the JSON for this Cell - */ - public String getJSON() { - return getJSONObject().toString(); - } - - /** Get the JSON object for this cell. */ - public JSONObject getJSONObject() { - CellRange cr = getMergedCellRange(); - int[] mergedCellRange = null; - if (cr != null) { - try { - mergedCellRange = cr.getRangeCoords(); - if (mycell.getOpcode() == XLSRecord.MULBLANK) { - Mulblank m = (Mulblank) mycell; - if (!cr.contains(m.getIntLocation())) { - mergedCellRange = null; - } - } - } catch (CellNotFoundException e) { - ; - } - } - - return getJSONObject(mergedCellRange); - } - - /** - * Get a JSON Object representation of a cell utilizing a merged range - * identifier. - * - * @deprecated The {@code mergedRange} parameter is unnecessary. This method - * will be removed in a future version. Use {@link #getJSONObject()} - * instead. - */ - @Deprecated - public JSONObject getJSONObject(int[] mergedRange) { - JSONObject theCell = new JSONObject(); - try { - theCell.put(JSON_LOCATION, getCellAddress()); - - Object val; - try { - val = getVal(); - if (val == null) - val = ""; - } catch (Exception ex) { - Logger.logWarn("OpenXLS.getJSONObject failed: " + ex.toString()); - val = "#ERR!"; - } - - String typename = getCellTypeName(); - JSONObject dataval = new JSONObject(); - - if (typename.equals("Formula")) { - try { - FormulaHandle fmh = getFormulaHandle(); - String fms = fmh.getFormulaString(); - - theCell.put(JSON_CELL_FORMULA, fms); - - try { - if (Float.parseFloat(val.toString()) == (Float.NaN)) { - typename = JSON_FLOAT; - } else if (val instanceof Float) - typename = JSON_FLOAT; - else if (val instanceof Double) - typename = JSON_DOUBLE; - else if (val instanceof Integer) - typename = JSON_INTEGER; - else if (val instanceof java.util.Date || val instanceof java.sql.Date - || val instanceof java.sql.Timestamp) { - typename = JSON_DATETIME; - } else - typename = JSON_STRING; - } catch (Exception e) { - typename = JSON_STRING; // default - } - } catch (Exception e) { - Logger.logErr("OpenXLS.getJSON() failed getting type of Formula for: " + toString(), e); - } - } - - if (isDate()) - typename = JSON_DATETIME; - - dataval.put(JSON_TYPE, typename); - - // TODO: Handle Conditional Format - // cell should return the style id for its condition - // this is an ID that begins incrementing after the last Xf - // and should be contained in the CSS for the output - - // if the conditional format evaluates to TRUE - // then we use *that* style ID not the default - - // We can have multiple CF styles per cell, one per each rule... we'll need that - // from CSS standpoint so... - - // style - theCell.put(JSON_STYLEID, getConditionalFormatId()); - - // merges - if (mergedRange != null) { - theCell.put(JSON_MERGEACROSS, (mergedRange[3] - mergedRange[1])); - theCell.put(JSON_MERGEDOWN, (mergedRange[2] - mergedRange[0])); - if (isMergeParent()) { - theCell.put(JSON_MERGEPARENT, true); - } else { - theCell.put(JSON_MERGECHILD, true); - } - } - - // handle hidden setting - try { - if (getCol().isHidden()) - theCell.put(JSON_HIDDEN, true); - } catch (Exception e) { - ; - } - - // handle the locked/formula hidden setting - // only active if sheet is protected - try { - if (isFormulaHidden()) - theCell.put(JSON_FORMULA_HIDDEN, true); - - theCell.put(JSON_LOCKED, isLocked()); - } catch (Exception e) { - ; - } - - try { - ValidationHandle vh = getValidationHandle(); - if (vh != null) - theCell.put(JSON_VALIDATION_MESSAGE, vh.getPromptBoxTitle() + ":" + vh.getPromptBoxText()); - } catch (Exception e) { - ; - } - - // hyperlinks - if (!(getURL() == null)) - theCell.put(JSON_HREF, getURL()); - - if (getWrapText()) - theCell.put(JSON_WORD_WRAP, true); - - // store alignment for container issues - int alignment = getFormatHandle().getHorizontalAlignment(); - if (alignment == FormatHandle.ALIGN_RIGHT) { - theCell.put(JSON_TEXT_ALIGN, "right"); - } else if (alignment == FormatHandle.ALIGN_CENTER) { - theCell.put(JSON_TEXT_ALIGN, "center"); - } else if (alignment == FormatHandle.ALIGN_LEFT) { - theCell.put(JSON_TEXT_ALIGN, "left"); - } - - // dates - if (typename.equals(JSON_DATETIME) && !(val == null) && !val.equals("")) { - dataval.put(JSON_CELL_VALUE, getFormattedStringVal()); - // dataval.put(JSON_DATEVALUE, ch.getFloatVal()); - dataval.put("time", DateConverter.getCalendarFromCell(this).getTimeInMillis()); - } else if (getCellType() == CellHandle.TYPE_STRING) { - // FORCES CALC - if (((String) val).indexOf("\n") > -1) { - val = ((String) val).replaceAll("\n", "
    "); - } - if (!val.equals("")) - dataval.put(JSON_CELL_VALUE, val.toString()); - } else { // other - dataval.put(JSON_CELL_VALUE, val.toString()); - try { // formatted pattern - String s = getFormatPattern(); - if (!(s.equals(""))) { - String fmtd = getFormattedStringVal(); // TRIGGERS CALC! - if (!(val.equals(fmtd))) - dataval.put(JSON_CELL_FORMATTED_VALUE, fmtd); - if (s.indexOf("Red") > -1) { - Double d = new Double(val.toString()); - if (d.doubleValue() < 0) { - theCell.put(JSON_RED_FORMAT, "1"); - if (fmtd.indexOf("-") == 0) - fmtd = fmtd.substring(1, fmtd.length()); - dataval.put(JSON_CELL_FORMATTED_VALUE, fmtd); - } - } - } - } catch (Exception x) { - } - ; - } - theCell.put(JSON_DATA, dataval); - } catch (JSONException e) { - Logger.logErr("error getting JSON for the cell: " + e); - } - return theCell; - } - - /** - * Returns the validation handle for the cell. - * - * @return ValidationHandle for this Cell, or null if none - */ - public ValidationHandle getValidationHandle() { - ValidationHandle ret = null; - try { - ret = this.getWorkSheetHandle().getValidationHandle(this.getCellAddress()); - } catch (Exception e) { - ; // somewhat normal? - } - return ret; - } - - /** - * Copies all formatting - xf and non-xf (such as column width, hidden state) - * plus merged cell range from a sourcecell to a new cell (usually in a new - * workbook) - * - * @param sourcecell - * the cell to copy - * @param newcell - * the cell to copy to - * @return - */ - protected static final CellHandle copyCellHelper(CellHandle sourcecell, CellHandle newcell) { - // copy row height & attributes - int rz = sourcecell.getRow().getHeight(); - newcell.getRow().setHeight(rz); - if (sourcecell.getRow().isHidden()) { - newcell.getRow().setHidden(true); - } - // copy col width & attributes - int rzx = sourcecell.getCol().getWidth(); - newcell.getCol().setWidth(rzx); - if (sourcecell.getCol().isHidden()) { - newcell.getCol().setHidden(true); - // Logger.logInfo("column " + rzx + " is hidden"); - } - - try { - // copy merged ranges - CellRange rng = sourcecell.getMergedCellRange(); - if (rng != null) { - rng = new CellRange(rng.getRange(), newcell.getWorkBook()); - rng.addCellToRange(newcell); - rng.mergeCells(false); - } - // Handle formats: - Xf origxf = sourcecell.getWorkBook().getWorkBook().getXf(sourcecell.getFormatId()); - newcell.getFormatHandle().addXf(origxf); - return newcell; - } catch (Exception ex) { - Logger.logErr("CellHandle.copyCellHelper failed.", ex); - } - return newcell; - } + /** + * + */ + private static final long serialVersionUID = 4737120893891570607L; + /** + * Cell types + */ + public static final int TYPE_BLANK = Cell.TYPE_BLANK; + public static final int TYPE_STRING = Cell.TYPE_STRING; + public static final int TYPE_FP = Cell.TYPE_FP; + public static final int TYPE_INT = Cell.TYPE_INT; + public static final int TYPE_FORMULA = Cell.TYPE_FORMULA; + public static final int TYPE_BOOLEAN = Cell.TYPE_BOOLEAN; + public static final int TYPE_DOUBLE = Cell.TYPE_DOUBLE; + public static final int NOTATION_STANDARD = 0, NOTATION_SCIENTIFIC = 1, NOTATION_SCIENTIFIC_EXCEL = 2; + + private transient WorkBook wbh = null; + private transient WorkSheetHandle wsh = null; + private ColHandle mycol; + private RowHandle myrow; + private FormatHandle formatter; + // reusing or creating new xfs is handled in FormatHandle/cloneXf and + // updateXf + // boolean useExistingXF = !false; + private boolean DEBUG = false; + private XLSRecord mycell; + + /** + * returns the underlying BIFF8 record for the Cell
    + * NOTE: the underlying record is not a part of the public API and may change at + * any time. + * + * @return Returns the underlying biff record. + */ + public XLSRecord getRecord() { + return mycell; + } + + /** + * sets the underlying BIFF8 record for the Cell
    + * NOTE: the underlying record is not a part of the public API and may change at + * any time. + * + * @param XLSRecord rec - The BIFF record to set. + */ + public void setRecord(XLSRecord rec) { + this.mycell = rec; + } + + /** + * Public Constructor added for use in Bean-context ONLY. + *

    + * Do NOT create CellHandles manually. + */ + public CellHandle(BiffRec c) { + mycell = (XLSRecord) c; + } + + /** + * if this cellhandle refers to a mulblank, ensure internal mulblank properties + * are set to appropriate cell in the mulblank range + */ + private void setMulblank() { + if (mycell.getOpcode() == XLSConstants.MULBLANK) { + if (mulblankcolnum == -1) { // init + mulblankcolnum = mycell.getColNumber(); + ((Mulblank) mycell).setCurrentCell(mulblankcolnum); + mycell.getIxfe(); // ensure myxf is set to correct + // xf for the given cell in the + // set of mulblanks + } else if (mulblankcolnum != mycell.getColNumber()) { + ((Mulblank) mycell).setCurrentCell(mulblankcolnum); + mycell.getIxfe(); // ensure myxf is set to correct + // xf for the given cell in the + // set of mulblanks + } + this.formatter = null; + } + } + + /** + * Public Constructor added for use in Bean-context ONLY. + *

    + * Do NOT create CellHandles manually. + */ + public CellHandle(BiffRec c, WorkBook myb) { + mycell = (XLSRecord) c; + setMulblank(); + this.wbh = myb; + } + + /** + * Get a FormatHandle (a Format Object describing the formats for this Cell) + * referenced by this CellHandle. + * + * @return FormatHandle + * @see FormatHandle + */ + void setFormatHandle() { + setMulblank(); + if (formatter != null && formatter.getFormatId() == this.mycell.getIxfe()) { + return; + } + // reusing or creating new xfs is handled in FormatHandle/cloneXf and + // updateXf + if (this.mycell.getXfRec() != null) { + formatter = new FormatHandle(this.wbh, this.mycell.myxf); + } else {// should ever happen now? + // useExistingXF = false; + if (wbh == null && this.mycell.getWorkBook() != null) + formatter = new FormatHandle(this.mycell.getWorkBook(), -1); + else + formatter = new FormatHandle(this.wbh, -1); + } + formatter.addCell(mycell); + } + + /** + * Sets a default "empty" value appropriate for the cell type of this CellHandle + *
    + * For example, will set the value to 0.0 for TYPE_DOUBLE, an empty String for + * TYPE_BLANK + **/ + public void setToDefault() { + setVal(this.getDefaultVal()); + } + + /** + * Get the default "empty" data value for this CellHandle + * + * @return Object a default empty value corresponding to the cell type
    + * such as 0.0 for TYPE_DOUBLE or an empty String for TYPE_BLANK + **/ + public Object getDefaultVal() { + return mycell.getDefaultVal(); + } + + /** + * Sets the number format pattern for this cell. All Excel built-in number + * formats are supported. Custom formats will not be applied by OpenXLS (e.g. + * the {@link #getFormattedStringVal} method) but they will be written correctly + * to the output file. For more information on number format patterns see + * Microsoft KB264372. + * + * @param pat the Excel number format pattern to apply + * @see FormatConstantsImpl#getBuiltinFormats + */ + public void setFormatPattern(String pat) { + setFormatHandle(); + formatter.setFormatPattern(pat); + } + + /** + * Gets the number format pattern for this cell, if set. For more information on + * number format patterns see + * Microsoft KB264372. + * + * @return the Excel number format pattern for this cell or null if + * none is applied + */ + public String getFormatPattern() { + if (this.getFont() == null) + return ""; + return mycell.getFormatPattern(); + } + + /** + * Returns whether this Cell has Date formatting applied.
    + * NOTE: This does not guarantee that the value is a valid date. + * + * @return boolean true if this Cell has a Date Format applied + */ + public boolean isDate() { + if (mycell.myxf == null) + return false; + if (mycell.isString) + return false; + if (mycell.isBoolean) + return false; + if (mycell.isBlank) + return false; + return mycell.myxf.isDatePattern(); + } + + /** + * Returns whether this cell is a formula. + */ + public boolean isFormula() { + return mycell.isFormula(); + } + + /** + * Returns whether the formula for the Cell is hidden + * + * @return boolean true if formula is hidden + */ + public boolean isFormulaHidden() { + return this.getFormatHandle().isFormulaHidden(); + } + + /** + * returns whether this Cell is locked for editing + * + * @return boolean true if the cell is locked + */ + public boolean isLocked() { + return this.getFormatHandle().isLocked(); + } + + /** + * Returns whether this is a blank cell. + * + * @return true if this cell is blank + */ + public boolean isBlank() { + return this.mycell.isBlank; + } + + /** + * Returns whether this Cell has a Currency format applied. + * + * @return boolean true if this Cell has a Currency format applied + */ + public boolean isCurrency() { + if (mycell.myxf == null) + return false; + return mycell.myxf.isCurrencyPattern(); + } + + /** + * Returns whether this Cell has a numeric value + * + * @return boolean true if this Cell contains a numeric value + */ + public boolean isNumber() { + return this.mycell.isNumber(); + } + + /** + * change the size (in points) of the Font used by this Cell and all other Cells + * sharing this FormatId.
    + * NOTE: To add an entirely new Font use the setFont(String fn, int typ, int sz) + * method instead. + * + * @param int sz - Font size in Points. + */ + public void setFontSize(int sz) { + setFormatHandle(); + sz *= 20; // excel size is 1/20 pt. + formatter.setFontHeight(sz); + } + + /** + * change the weight (boldness) of the Font used by this Cell.
    + * Some examples:
    + * a weight of 200 is normal
    + * a weight of 700 is bold
    + * + * @param int wt - Font weight range between 100-1000 + */ + public void setFontWeight(int wt) { + setFormatHandle(); + formatter.setFontWeight(wt); + } + + /** + * Convenience method for toggling the bold state of the Font used by this Cell. + * + * @param boolean bold - true if bold + */ + public void setBold(boolean bold) { + setFormatHandle(); + formatter.setBold(bold); + } + + /** + * get the weight (boldness) of the Font used by this Cell. + * + * @return int Font weight range between 100-1000 + */ + public int getFontWeight() { + if (this.getFont() == null) + return FormatHandle.DEFAULT_FONT_WEIGHT; + return mycell.getFont().getFontWeight(); + } + + /** + * get the size in points of the Font used by this Cell + * + * @return int Font size in Points. + */ + public int getFontSize() { + if (this.getFont() == null) + return FormatHandle.DEFAULT_FONT_SIZE; + return mycell.getFont().getFontHeight() / 20; + } + + /** + * get the Color of the Font used by this Cell.
    + * + * @return int Excel color constant for Font color + * @see FormatHandle.COLOR constants + */ + public Color getFontColor() { + if (this.getFont() == null) + return FormatHandle.Black; + int clidx = this.getFont().getColor(); + + // handle white on white text issue + Xf x = mycell.getXfRec(); + int clidb = x.getBackgroundColor(); + if (clidx == 64 && clidb == 64) { // return black + return FormatHandle.Black; + } + // black on black + if (clidx < this.getWorkBook().getColorTable().length) { + Color mycolr = this.getWorkBook().getColorTable()[clidx]; + return mycolr; + } + return Color.black; + } + + /** + * Set the color of the Font used by this Cell. + * + * @param java .Awt.Color col - color of the font + */ + public void setFontColor(Color col) { + setFormatHandle(); + formatter.setFontColor(col); + } + + /** + * set the Foreground Color for this Cell.
    + *
    + * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and + * Background color is the PATTERN color for all patterns not equal to + * PATTERN_SOLID
    + * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and + * Background Color is 64 (white). + * + * @param int t - Excel color constant + * @see FormatHandle.COLOR constants + */ + public void setForegroundColor(int t) { + setFormatHandle(); + formatter.setForegroundColor(t); + } + + /** + * set the Foreground Color for this Cell
    + * NOTE: this is the PATTERN Color + * + * @param int Excel color constant + * @see FormatHandle.COLOR constants + */ + // TODO: is this doc correct? + public void setForeColor(int i) { + if (mycell.myxf == null) + this.getNewXf(); + mycell.myxf.setForeColor(i, null); + } + + /** + * set the Background Color for this Cell.
    + *
    + * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and + * Background color is the PATTERN color for all patterns not equal to + * PATTERN_SOLID
    + * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and + * Background Color is 64 (white). + * + * @param int t - Excel color constant + * @see FormatHandle.COLOR constants + */ + public void setBackgroundColor(int t) { + setFormatHandle(); + formatter.setBackgroundColor(t); + } + + /** + * get the Color of the Cell Foreground of this Cell and all other cells sharing + * this FormatId.
    + *
    + * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and + * Background color is the PATTERN color for all patterns not equal to + * PATTERN_SOLID
    + * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and + * Background Color is 64 (white). + * + * @return java.awt.Color of the Font + */ + public Color getForegroundColor() { + if (this.mycell.getXfRec() != null) { + int clidx = mycell.getXfRec().getForegroundColor(); + if (clidx < this.getWorkBook().getColorTable().length) { + Color mycolr = this.getWorkBook().getColorTable()[clidx]; + return mycolr; + } + } + return Color.black; + } + + /** + * sets the Color of the Cell Foreground pattern for this Cell. + * + *
    + *
    + * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and + * Background color is the PATTERN color for all patterns not equal to + * PATTERN_SOLID
    + * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and + * Background Color is 64 (white). + * + * @param java .awt.Color col - color for the foreground + */ + public void setForegroundColor(Color col) { + setFormatHandle(); + formatter.setForegroundColor(col); + } + + /** + * get the Color of the Cell Background by this Cell and all other cells sharing + * this FormatId.
    + *
    + * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and + * Background color is the PATTERN color for all patterns not equal to + * PATTERN_SOLID
    + * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and + * Background Color is 64 (white). + * + * @return java.awt.Color for the Font + */ + public Color getBackgroundColor() { + if (this.mycell.getXfRec() != null) { + Xf x = mycell.getXfRec(); + int clidx = x.getBackgroundColor(); + if (clidx < this.getWorkBook().getColorTable().length) { + Color mycolr = this.getWorkBook().getColorTable()[clidx]; + return mycolr; + } + } + return Color.white; + } + + /** + * Return the cell background color i.e. the color if a pattern is set, or white + * if none + * + * @return java.awt.Color cell background color + */ + public Color getCellBackgroundColor() { + setFormatHandle(); + int clidx = formatter.getCellBackgroundColor(); + if (clidx < this.wbh.getWorkBook().getColorTable().length) { + Color mycolr = this.getWorkBook().getColorTable()[clidx]; + return mycolr; + } + return Color.white; + } + + /** + * set the Color of the Cell Background pattern for this Cell.
    + *
    + * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and + * Background color is the PATTERN color for all patterns not equal to + * PATTERN_SOLID
    + * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and + * Background Color is 64 (white). + * + * @param java .awt.Color col - background color + */ + public void setBackgroundColor(Color col) { + setFormatHandle(); + formatter.setBackgroundColor(col); + } + + /** + * set the Color of the Cell Background for this Cell.
    + *
    + * see FormatHandle.COLOR constants for valid values
    + *
    + * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and + * Background color is the PATTERN color for all patterns not equal to + * PATTERN_SOLID
    + * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and + * Background Color is 64 (white). + * + * @param int t - Excel color constant for Cell Background color + */ + public void setCellBackgroundColor(int t) { + setFormatHandle(); + formatter.setCellBackgroundColor(t); + } + + /** + * set the Color of the Cell Background for this Cell.
    + *
    + * NOTE: Foreground color is the CELL BACKGROUND color for all patterns and + * Background color is the PATTERN color for all patterns not equal to + * PATTERN_SOLID
    + * For PATTERN_SOLID, the Foreground color is the CELL BACKGROUND color and + * Background Color is 64 (white). + * + * @param java .awt.Color col - color of the cell background + */ + public void setCellBackgroundColor(Color col) { + setFormatHandle(); + formatter.setCellBackgroundColor(col); + } + + /** + * set the Color of the Cell Background Pattern for this Cell. + * + * @param java .awt.Color col - color of the pattern background + */ + public void setPatternBackgroundColor(Color col) { + setFormatHandle(); + formatter.setBackgroundColor(col); + } + + /** + * set the Background Pattern for this Cell.
    + * + * @param int t - Excel pattern constant + * @see FormatHandle.PATTERN constants + */ + public void setBackgroundPattern(int t) { + setFormatHandle(); + formatter.setPattern(t); + } + + /** + * get the Background Pattern for this cell
    + * + * @return int Excel pattern constant + * @see FormatHandle.PATTERN constants + */ + public int getBackgroundPattern() { + return this.mycell.getXfRec().getFillPattern(); + } + + /** + * get the fill pattern for this cell
    + * Same as getBackgroundPattern
    + * + * @return int Excel fill pattern constant + * @see FormatHandle.PATTERN constants + */ + public int getFillPattern() { + setFormatHandle(); + return formatter.getFillPattern(); + } + + /** + * set the Color of the Border for this Cell. + * + * @param java .awt.Color col - border color + */ + public void setBorderColor(Color col) { + setFormatHandle(); + formatter.setBorderColor(col); + } + + /** + * set the Color of the right Border line for this Cell. + * + * @param java .awt.Color col - right border color + */ + public void setBorderRightColor(Color col) { + setFormatHandle(); + formatter.setBorderRightColor(col); + } + + /** + * set the Color of the left Border line for this Cell. + * + * @param java .awt.Color col - left border color + */ + public void setBorderLeftColor(Color col) { + setFormatHandle(); + formatter.setBorderLeftColor(col); + } + + /** + * set the Color of the top Border line for this Cell. + * + * @param java .awt.Color col - top border color + */ + public void setBorderTopColor(Color col) { + setFormatHandle(); + formatter.setBorderTopColor(col); + } + + /** + * set the Color of the bottom Border line for this Cell. + * + * @param java .awt.Color col - bottom border color + */ + public void setBorderBottomColor(Color col) { + setFormatHandle(); + formatter.setBorderBottomColor(col); + } + + /** + * get the Font face used by this Cell. + * + * @return String the system name of the font for this Cell + */ + public String getFontFace() { + if (this.getFont() == null) + return FormatHandle.DEFAULT_FONT_FACE; + return mycell.getFont().getFontName(); + } + + /** + * set the Font face used by this Cell. + * + * @param String fn - system name of the font + */ + public void setFontFace(String fn) { + setFormatHandle(); + formatter.setFontName(fn); + } + + /** + * set the Border line style for this Cell. + * + * @param short s - border constant + * @see FormatHandle.BORDER line style constants + */ + public void setBorderLineStyle(short s) { + setFormatHandle(); + formatter.setBorderLineStyle(s); + } + + /** + * set the Right Border line style for this Cell. + * + * @param short s - border constant + * @see FormatHandle.BORDER line style constants + */ + public void setRightBorderLineStyle(short s) { + setFormatHandle(); + formatter.setRightBorderLineStyle(s); + } + + /** + * set the Left Border line style for this Cell. + * + * @param short s - border constant + * @see FormatHandle.BORDER line style constants + */ + public void setLeftBorderLineStyle(short s) { + setFormatHandle(); + formatter.setLeftBorderLineStyle(s); + } + + /** + * set the Top Border line style for this Cell. + * + * @param short s - border constant + * @see FormatHandle.BORDER line style constants + */ + public void setTopBorderLineStyle(short s) { + setFormatHandle(); + formatter.setTopBorderLineStyle(s); + } + + /** + * set the Bottom Border line style for this Cell. + * + * @param short s - border constant + * @see FormatHandle.BORDER line style constants + */ + public void setBottomBorderLineStyle(short s) { + setFormatHandle(); + formatter.setBottomBorderLineStyle(s); + } + + /** + * removes the borders for this cell + */ + public void removeBorder() { + setFormatHandle(); + formatter.removeBorders(); + } + + /** + * Get the OpenXLS Font for this Cell, which roughly matches the functionality + * of the java.awt.Font class.
    + * Due to awt problems on console systems, converting the OpenXLS font to a GUI + * font is up to you.
    + * + * @return OpenXLS font for Cell + */ + public Font getFont() { + return mycell.getFont(); + } + + /** + * convert the OpenXLS font used by this Cell to java.awt.Font + * + * @return java.awt.Font for this cell + */ + public java.awt.Font getAwtFont() { + String fface = "Arial"; + try { + fface = this.getFontFace(); + } catch (Exception e) { + } + int sz = 12; + try { + sz = this.getFontSize(); + } catch (Exception e) { + sz = 12; // back to default + } + sz += 4; // Excel seems to display 1 point larger than Java + // Logger.logInfo("Displaying font:" + fface); + HashMap ftmap = new HashMap(); + // implement underlines + try { + if (this.getIsUnderlined()) { + ftmap.put(java.awt.font.TextAttribute.UNDERLINE, java.awt.font.TextAttribute.UNDERLINE); + } + } catch (Exception e) { + } + // Ahhh, much cooler fonts here! + ftmap.put(java.awt.font.TextAttribute.FAMILY, fface); + float fx = this.getFontWeight(); + ftmap.put(java.awt.font.TextAttribute.SIZE, new Float(sz)); + // TODO: Interpret other weights- LIGHT, DEMI_LIGHT, DEMI_BOLD, etc. + if (fx == FormatHandle.BOLD) + ftmap.put(java.awt.font.TextAttribute.WEIGHT, java.awt.font.TextAttribute.WEIGHT_BOLD); + else + ftmap.put(java.awt.font.TextAttribute.WEIGHT, java.awt.font.TextAttribute.WEIGHT_REGULAR); + return new java.awt.Font(ftmap); + } + + /** + * Set the Font for this Cell via font name, font style and font size
    + * This method adds a new Font to the WorkBook.
    + * Roughly matches the functionality of the java.awt.Font class. + * + * @param String fn - system name of the font e.g. "Arial" + * @param int stl - font style (either Font.BOLD or Font.PLAIN) + * @param int sz - font size in points + */ + public void setFont(String fn, int stl, int sz) { + setFormatHandle(); + formatter.setFont(fn, stl, sz); + } + + /** + * Get a CommentHandle to the note attached to this cell + */ + public CommentHandle getComment() throws DocumentObjectNotFoundException { + // this needs significant cleanup. We should not have to iterate notes + ArrayList notes = mycell.getSheet().getNotes(); + for (int i = 0; i < notes.size(); i++) { + Note n = (Note) notes.get(i); + if (n.getCellAddressWithSheet().equals(this.getCellAddressWithSheet())) { + return new CommentHandle(n); + } + } + throw new DocumentObjectNotFoundException("Note record not found at " + this.getCellAddressWithSheet()); + } + + /** + * Removes any note/comment records attached to this cell + */ + public void removeComment() { + try { + CommentHandle note = this.getComment(); + note.remove(); + } catch (DocumentObjectNotFoundException e) { + } + } + + /** + * Creates a new annotation (Note or Comment) to the cell + * + * @param comment -- text of note + * @param author -- name of author + * @return CommentHandle - handle which allows access to the Note object + * @see CommentHandle + */ + public CommentHandle createComment(String comment, String author) { + Note n = mycell.getSheet().createNote(this.getCellAddress(), comment, author); + return new CommentHandle(n); + } + + /** + * returns whether the Font for this Cell is underlined + * + * @return boolean true if Font is underlined + */ + public boolean getIsUnderlined() { + if (this.getFont() == null) + return false; + return this.getFont().getUnderlineStyle() != 0x0; + } + + /** + * Set whether the Font for this Cell is underlined + * + * @param boolean b - true if the Font for this Cell should be underlined (single + * underline only) + */ + public void setUnderlined(boolean isUnderlined) { + setFormatHandle(); + if (isUnderlined) + this.getFont().setUnderlineStyle(Font.STYLE_UNDERLINE_SINGLE); + else + this.getFont().setUnderlineStyle(Font.STYLE_UNDERLINE_NONE); + } + + /** + * set the Font Color for this Cell
    + *
    + * see FormatHandle.COLOR constants for valid values + * + * @param int t - Excel color constant + */ + public void setFontColor(int t) { + setFormatHandle(); + formatter.setFontColor(t); + } + + /** + * Returns any other Cells merged with this one, or null if this Cell is not a + * part of a merged range.
    + * Adding and/or removing Cells from this CellRange will merge or unmerge the + * Cells. + * + * @return CellRange object containing all Cells in this Cell's merged range. + */ + public CellRange getMergedCellRange() { + return mycell.getMergeRange(); + } + + /** + * Returns if the Cell is the parent (cell containing display value) of a merged + * cell range. + * + * @return boolean true if this cell is a merge parent cell + */ + public boolean isMergeParent() { + try { + CellRange cr = mycell.getMergeRange(); + if (cr == null) + return false; + int[] i = cr.getRangeCoords(); + if (this.getRowNum() + 1 == i[0] && this.getColNum() == i[1]) + return true; + } catch (Exception e) { + return false; + } + return false; + + } + + /** + * Returns the ColHandle for the Cell. + * + * @return ColHandle for the Cell + */ + public ColHandle getCol() { + try { + if (mycol == null) + mycol = wsh.getCol(this.getColNum()); + } catch (ColumnNotFoundException ex) { + // can't happen, the column has to exist because we're in it + throw new RuntimeException(ex); + } + return this.mycol; + } + + /** + * Returns the RowHandle for the Cell. + * + * @return RowHandle representing the Row for the Cell + */ + public RowHandle getRow() { + if (myrow == null) + myrow = new RowHandle(mycell.getRow(), this.wsh); + return this.myrow; + } + + /** + * Returns the value of this Cell in the native underlying data type. + *

    + * Formula cells will return the calculated value of the formula in the + * calculated data type. + *

    + * Use 'getStringVal()' to return a String regardless of underlying value type. + * + * @return Object value for this Cell + */ + public Object getVal() { + return FormulaHandle.sanitizeValue(mycell.getInternalVal()); + } + + /**/ + + /** + * returns the java Type string of the Cell
    + * One of: + *

  • "String" + *
  • "Float" + *
  • "Integer" + *
  • "Formula" + *
  • "Double" + * + * @return String java data type + */ + public String getCellTypeName() { + String typename = "Object"; + int tp = getCellType(); + switch (tp) { + case XLSConstants.TYPE_BLANK: + typename = "String"; + break; + case XLSConstants.TYPE_STRING: + typename = "String"; + break; + case XLSConstants.TYPE_FP: + typename = "Float"; + break; + case XLSConstants.TYPE_INT: + typename = "Integer"; + break; + case XLSConstants.TYPE_FORMULA: + typename = "Formula"; + break; + case XLSConstants.TYPE_DOUBLE: + typename = "Double"; + break; + } + return typename; + } + + /** + * Returns the type of the Cell as an int
    + *
  • TYPE_STRING = 0, + *
  • TYPE_FP = 1, + *
  • TYPE_INT = 2, + *
  • TYPE_FORMULA = 3, + *
  • TYPE_BOOLEAN = 4, + *
  • TYPE_DOUBLE = 5; + * + * @return int type for this Cell + */ + public int getCellType() { + return mycell.getCellType(); + } + + /** + * return the underlying cell record
    + * for internal API use only + * + * @return XLS cell record + */ + public BiffRec getCell() { + return mycell; + } + + /** + * returns Border Colors of Cell in: top, left, bottom, right order
    + * returns null or a java.awt.Color object for each of 4 sides + * + * @return java.awt.Color array representing the 4 borders of the cell + */ + public Color[] getBorderColors() { + getFormatHandle(); + return this.formatter.getBorderColors(); + } + + /** + * returns the low-level bytes for the underlying BIFF8 record.
    + * For Internal API use only + * + * @return bytes for the underlying record + */ + public byte[] getBytes() { + return mycell.getData(); + } + + /** + * Returns the column number of this Cell. + * + * @return int the Column Number of the Cell + */ + public int getColNum() { + setMulblank(); + return mycell.getColNumber(); + } + + /** + * Returns the row number of this Cell. + *

    + * NOTE: This is the 1-based row number such as you will see in a spreadsheet + * UI. + *

    + * ie: A1 = row 1 + * + * @return 1-based int the Row Number of the Cell + */ + public int getRowNum() { + return mycell.getRowNumber(); + } + + /** + * Returns the Address of this Cell as a String. + * + * @return String the address of this Cell in the WorkSheet + */ + public String getCellAddress() { + setMulblank(); + return mycell.getCellAddress(); + + } + + public int[] getIntLocation() { + setMulblank(); + return mycell.getIntLocation(); + } + + /** + * Returns the Address of this Cell as a String. Includes the sheet name in the + * address. + * + * @return String the address of this Cell in the WorkSheet + */ + public String getCellAddressWithSheet() { + setMulblank(); + return mycell.getCellAddressWithSheet(); + + } + + /** + * sets the column number referenced in the set of multiple blanks
    + * for multiple blank cells only
    + * for Internal Use. Not intended for the End User. + */ + short mulblankcolnum = -1; + + public void setBlankRef(int c) { + mulblankcolnum = (short) c; + } + + /** + * Returns the name of this Cell's WorkSheet as a String. + * + * @return String the name this Cell's WorkSheet + */ + public String getWorkSheetName() { + if (wsh == null) { + try { + return mycell.getSheet().getSheetName(); + } catch (Exception e) { + return ""; + } + } + return wsh.getSheetName(); + } + + /** + * Returns the value of the Cell as a String.
    + * boolean Cell types will return "true" or "false" + * + * @return String the value of the Cell + */ + public String getStringVal() { + return mycell.getStringVal(); + } + + /** + * Gets the value of the cell as a String with the number format applied. + * Boolean cell types will return "true" or "false". Custom number formats are + * not currently supported, although they will be written correctly to the + * output file. Patterns that display negative numbers in red are not currently + * supported; the number will be prefixed with a minus sign instead. For more + * information on number format patterns see + * Microsoft KB264372. + * + * @return the value of the cell as a string formatted according to the cell + * type and, if present, the number format pattern + */ + public String getFormattedStringVal() { + FormatHandle myfmt = this.getFormatHandle(); + return CellFormatFactory.fromPatternString(myfmt.getFormatPattern()).format(this); + } + + /** + * Gets the value of the cell as a String with the number format applied. + * Boolean cell types will return "true" or "false". Custom number formats are + * not currently supported, although they will be written correctly to the + * output file. Patterns that display negative numbers in red are not currently + * supported; the number will be prefixed with a minus sign instead. For more + * information on number format patterns see + * Microsoft KB264372. + * + * @param formatForXML if true non-compliant characters will be properly qualified + * @return the value of the cell as a string formatted according to the cell + * type and, if present, the number format pattern + */ + public String getFormattedStringVal(boolean formatForXML) { + FormatHandle myfmt = this.getFormatHandle(); + String val = this.getVal().toString(); + if (formatForXML) + val = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(val).toString(); + return CellFormatFactory.fromPatternString(myfmt.getFormatPattern()).format(val); + } + + /** + * Returns the value of the Cell as a String.
    + * For numeric cell values, including cells containing a formula which return a + * numeric value, the notation to be used in representing the value as a String + * must be specified.
    + * The Notation choices are: + *

  • CellHandle.NOTATION_STANDARD e.g. "8002974342" + *
  • CellHandle.NOTATION_SCIENTIFIC e.g. "8.002974342E9" + *
  • CellHandle.NOTATION_SCIENTIFIC_EXCEL e.g. "8.002974342+E9"
    + *
    + * For non-numeric values, the value of the cell as a string is returned
    + * boolean Cell types will return "true" or "false" + * + * @param int notation one of the CellHandle.NOTATION constants for numeric cell + * types; ignored for other cell types + * @param int notation - notation constant + * @return String the value of the Cell + */ + public String getStringVal(int notation) { + String numval = mycell.getStringVal(); + int i = this.getCellType(); + if (i == TYPE_FP || i == TYPE_INT || i == TYPE_FORMULA || i == TYPE_DOUBLE) { + return ExcelTools.formatNumericNotation(numval, notation); + } + return numval; + } + + /** + * Returns the value of the Cell as a String with the specified encoding.
    + * boolean Cell types will return "true" or "false" + * + * @param String encoding + * @return String the value of the Cell + */ + public String getStringVal(String encoding) { + return mycell.getStringVal(encoding); + } + + /** + * Returns the Formatting record ID (FormatId) for this Cell
    + * This can be used with 'setFormatId(int i)' to copy the formatting from one + * Cell to another (e.g. a template cell to a new cell) + * + * @return int the FormatId for this Cell + */ + public int getFormatId() { + setMulblank(); + return mycell.getIxfe(); + } + + /** + * get the conditional formatting record ID (FormatId)
    + * returns the normal FormatId if the condition(s) in the conditional format + * have not been met + * + * @return int the conditional format id + */ + public int getConditionalFormatId() { + ConditionalFormatHandle[] cfhandles = this.getConditionalFormatHandles(); + if (cfhandles == null || cfhandles.length == 0) { + return this.getFormatId(); + } + // TODO: only supporting first cfmat handle again + Condfmt cfmt = cfhandles[0].getCndfmt(); + + Iterator x = cfmt.getRules().iterator(); + while (x.hasNext()) { + Cf cx1 = ((Cf) x.next()); + // create a ptgref for this Cell + Ptg pref = new PtgRef(this.getCellAddress(), this.mycell, false); + + if (cx1.evaluate(pref)) { + // TODO: evaluate and combine multiple rules... + // currently returns on first true format + int ret = cfmt.getCfxe(); + return ret; + } + } + return this.getFormatId(); + } + + /** + * move this cell to another row
    + * throws CellPositionConflictException if there is a cell in that position + * already + * + * @param int newrow - new row number + * @throws CellPositionConflictException + */ + public void moveToRow(int newrow) throws CellPositionConflictException { + String newaddr = ExcelTools.getAlphaVal(mycell.getColNumber()); + newaddr += String.valueOf(newrow); + this.moveTo(newaddr); + } + + /** + * move this cell to another row
    + * overwrite any cells in the destination + * + * @param int newrow - new row number + */ + public void moveAndOverwriteToRow(int newrow) { + String newaddr = ExcelTools.getAlphaVal(mycell.getColNumber()); + newaddr += String.valueOf(newrow); + this.moveAndOverwriteTo(newaddr); + } + + /** + * move this cell to another column
    + * throws CellPositionConflictException if there is a cell in that position + * already + * + * @param String newcol - the new column in alpha format e.g. "A", "B" ... + * @throws CellPositionConflictException + */ + public void moveToCol(String newcol) throws CellPositionConflictException { + String newaddr = newcol; + newaddr += String.valueOf(mycell.getRowNumber() + 1); + this.moveTo(newaddr); + } + + /** + * Copy all formats from a source Cell to this Cell + * + * @param CellHandle source - source cell + */ + public void copyFormat(CellHandle source) { + this.getCell().copyFormat(source.getCell()); + } + + /** + * copy this Cell to another location. + * + * @param String newaddr - address for copy of this Cell in Excel-style e.g. "A1" + * @return returns the newly copied CellHandle + * @throws CellPositionConflictException if there is a cell in the new address already + */ + public CellHandle copyTo(String newaddr) throws CellPositionConflictException { + + // check for existing + Boundsheet bs = this.mycell.getSheet(); + + BiffRec rec = this.mycell; + XLSRecord nucell = (XLSRecord) ((XLSRecord) rec).clone(); + int[] rc = ExcelTools.getRowColFromString(newaddr); + nucell.setRowNumber(rc[0]); + nucell.setCol((short) rc[1]); + nucell.setXFRecord(this.mycell.getIxfe()); + bs.addRecord(nucell, rc); + + CellHandle ret = new CellHandle(nucell, wbh); + if (mycell.hyperlink != null) { + // set the bounds of the mycell.hyperlink + ret.setURL(this.getURL()); + } + ret.setWorkSheetHandle(this.getWorkSheetHandle()); + // this.getWorkSheetHandle().cellhandles.put(this.getCellAddress(), + // this); + return ret; + } + + /** + * Removes this Cell from the WorkSheet + * + * @param boolean nullme - true if this CellHandle should be nullified after removal + */ + public void remove(boolean nullme) { + mycell.getSheet().removeCell(mycell); + if (nullme) { + try { + this.finalize(); + } catch (Throwable t) { + } + } + } + + /** + * move this cell to another location. + * + * @param String newaddr - the new address for Cell in Excel-style notation e.g. + * "A1" + * @throws CellPositionConflictException if there is a cell in the new address already + */ + public void moveTo(String newaddr) throws CellPositionConflictException { + + // check for existing + Boundsheet bs = mycell.getSheet(); + BiffRec oldhand = bs.getCell(newaddr); + if (oldhand != null) + throw new CellPositionConflictException(newaddr); + bs.moveCell(this.getCellAddress(), newaddr); + + if (mycell.hyperlink != null) { + // set the bounds of the mycell.hyperlink + // int[] bnds = + // ExcelTools.getRowColFromString(this.getCellAddress()); + + int[] bnds = ExcelTools.getRowColFromString(this.getCellAddress()); + + Hlink hl = mycell.hyperlink; + hl.setRowFirst(bnds[0]); + hl.setRowLast(bnds[0]); + hl.setColFirst(bnds[1]); + hl.setColLast(bnds[1]); + hl.init(); + } + } + + /** + * move this cell to another location, overwriting any cells that are in the way + * + * @param String newaddr - the new address for Cell in Excel-style notation e.g. + * "A1" + */ + public void moveAndOverwriteTo(String newaddr) { + + // check for existing + Boundsheet bs = mycell.getSheet(); + BiffRec oldhand = bs.getCell(newaddr); + bs.moveCell(this.getCellAddress(), newaddr); + + if (mycell.hyperlink != null) { + int[] bnds = ExcelTools.getRowColFromString(this.getCellAddress()); + + Hlink hl = mycell.hyperlink; + hl.setRowFirst(bnds[0]); + hl.setRowLast(bnds[0]); + hl.setColFirst(bnds[1]); + hl.setColLast(bnds[1]); + hl.init(); + } + } + + /** + * Set a conditional format upon this cell. + *

    + * Note that conditional format handles are bound to a specific worksheet, + * + * @param format A ConditionalFormatHandle in the same worksheet + */ + public void addConditionalFormat(ConditionalFormatHandle format) { + format.addCell(this); + } + + /** + * returns an array of FormatHandles for the Cell that have the current + * conditional format applied to them. + *

    + * This behavior is still in testing, and may change + * + * @return an array of FormatHandles, one for each of the Conditional Formatting + * rules + */ + public FormatHandle[] getConditionallyFormattedHandles() { + // TODO, should these be read-only? + // TODO, this is bad, only handles first cf record for the cell + ConditionalFormatHandle[] cfhandles = this.getConditionalFormatHandles(); + if (cfhandles != null) { + FormatHandle[] fmx = new FormatHandle[cfhandles[0].getCndfmt().getRules().size()]; + for (int t = 0; t < fmx.length; t++) { + fmx[t++] = new FormatHandle(cfhandles[0].getCndfmt(), wbh, t, this); + } + return fmx; + } + return null; + } + + /** + * return all the ConditionalFormatHandles for this Cell, if any + * + * @return + */ + public ConditionalFormatHandle[] getConditionalFormatHandles() { + WorkSheetHandle sh = this.getWorkSheetHandle(); + if (sh == null) { + return null; + } + ConditionalFormatHandle[] cfs = sh.getConditionalFormatHandles(); + ArrayList cfhandles = new ArrayList(); + for (int i = 0; i < cfs.length; i++) { + if (cfs[i].contains(this)) + cfhandles.add(cfs[i]); + } + ConditionalFormatHandle[] c = new ConditionalFormatHandle[cfhandles.size()]; + return cfhandles.toArray(c); + } + + /** + * Gets the FormatHandle (a Format Object describing the formats for this Cell) + * for this Cell. + * + * @return FormatHandle for this Cell + */ + public FormatHandle getFormatHandle() { + if (this.formatter == null) + this.setFormatHandle(); + + return this.formatter; + } + + /** + * locks or unlocks this Cell for editing + * + * @param boolean locked - true if Cell should be locked, false otherwise + */ + public void setLocked(boolean locked) { + // create a new xf for this + // this causes formats to be lost this.useExistingXF = false; + getFormatHandle().setLocked(locked); + } + + /** + * Hides or shows the formula for this Cell, if present + * + * @param boolean hidden - setting whether to hide or show formulas for this Cell + */ + public void setFormulaHidden(boolean hidden) { + // create a new xf for this + // this causes formats to be lost this.useExistingXF = false; + getFormatHandle().setFormulaHidden(hidden); + } + + /** + * Sets the FormatHandle (a Format Object describing the formats for this Cell) + * for this Cell + * + * @param FormatHandle to apply to this Cell + * @see FormatHandle + */ + public void setFormatHandle(FormatHandle f) { + f.addCell(this.mycell); + this.formatter = f; + } + + /** + * Sets the Formatting record ID (FormatId) for this Cell + *

    + * This can be used with 'getFormatId()' to copy the formatting from one Cell to + * another (ie: a template cell to a new cell) + * + * @param int i - the new index to the Format for this Cell + */ + public void setFormatId(int i) { + mycell.setXFRecord(i); + } + + /** + * Resets this cell's format to the default. + */ + public void clearFormats() { + this.setFormatId(this.getWorkBook().getWorkBook().getDefaultIxfe()); + } + + /** + * Resets this cells contents to blank. + */ + public void clearContents() { + this.setVal(null); + } + + /** + * Resets this cell to the default, as if it had just been added. + */ + public void clear() { + this.clearFormats(); + this.clearContents(); + } + + /** + * Returns the Formula Handle (an Object describing a Formula) for this Cell, if + * it contains a formula + * + * @return FormulaHandle the Formula of the Cell + * @throws FormulaNotFoundException + * @see FormulaHandle + */ + public FormulaHandle getFormulaHandle() throws FormulaNotFoundException { + Formula f = mycell.getFormulaRec(); + if (f == null) { + throw new FormulaNotFoundException("No Formula for: " + getCellAddress()); + } + FormulaHandle fh = new FormulaHandle(f, this.wbh); + return fh; + } + + /** + * Returns the Hyperlink URL String for this Cell, if any + * + * @return String URL if this Cell contains a hyperlink + */ + public String getURL() { + if (mycell.hyperlink != null) + return mycell.hyperlink.getURL(); + return null; + } + + /** + * Returns the URL Description String for this Cell, if any + * + * @return String URL Description, if this Cell contains a hyperlink + */ + public String getURLDescription() { + if (mycell.hyperlink != null) + return mycell.hyperlink.getDescription(); + return ""; + } + + /** + * returns true if this Cell contains a hyperlink + * + * @return boolean true if this Cell contains a hyperlink + */ + public boolean hasHyperlink() { + return (mycell.hyperlink != null); + } + + /** + * Creates a new Hyperlink for this Cell from a URL String. Can be any valid + * URL. This URL String must include the protocol.
    + * For Example, "http://www.extentech.com/"
    + * To remove a hyperlink, pass in null for the URL String + * + * @param String urlstr - the URL String for this Cell + */ + public void setURL(String urlstr) { + if (urlstr == null) { + mycell.hyperlink = null; + // TODO: remove existing Hlink from stream + // mycell.hyperlink.remove(true); + return; + } + setURL(urlstr, "", ""); + } + + /** + * Creates a new Hyperlink for this Cell from a URL String, a descrpiton and + * textMark text.
    + *
    + * The URL String Can be any valid URL. This URL String must include the + * protocol. For Example, "http://www.extentech.com/"
    + * The textMark text is the porition of the URL that follows #
    + *
    + * NOTE: URL text and textMark text must not be null or "" + * + * @param String urlstr - the URL String + * @param String desc - the description text + * @param String textMark - the text that follows # + */ + public void setURL(String urlstr, String desc, String textMark) { + if (mycell.hyperlink != null) { + mycell.hyperlink.setURL(urlstr, desc, textMark); + } else { + // create new URL + mycell.hyperlink = (Hlink) Hlink.getPrototype(); + mycell.hyperlink.setURL(urlstr, desc, textMark); + + // why would we want to set the val during this operation? + // if (!desc.equals("")) setVal(desc); + + // set the bounds of the mycell.hyperlink + int[] bnds = ExcelTools.getRowColFromString(this.getCellAddress()); + mycell.hyperlink.setRowFirst(bnds[0]); + mycell.hyperlink.setColFirst(bnds[1]); + mycell.hyperlink.setRowLast(bnds[0]); + mycell.hyperlink.setColLast(bnds[1]); + } + } + + /** + * Sets a hyperlink to a location within the current template
    + * The URL String should be prefixed with "file://"
    + * + * @param String fileURLStr - the file URL String + */ + // TODO: document this: NOTE: Excel File URL in actuality does not match + // documentation + public void setFileURL(String fileURLStr) { + setFileURL(fileURLStr, "", ""); + } + + /** + * Sets a hyperlink to a location within the current template, and includes + * additional optional information: description + textMark text
    + *
    + * The URL String should be prefixed with "file://"
    + *
    + * The textMark text is the porition of the URL that follows # + * + * @param String fileURLstr - the file URL String + * @param String desc - the description text + * @param String textMark - text that follows # + */ + // TODO: this documentation is contradictory + public void setFileURL(String fileURLstr, String desc, String textMark) { + if (mycell.hyperlink != null) { + mycell.hyperlink.setFileURL(fileURLstr, desc, textMark); + } else { + mycell.hyperlink = (Hlink) Hlink.getPrototype(); + + mycell.hyperlink.setFileURL(fileURLstr, desc, textMark); + if (!desc.equals("")) + setVal(desc); + + // set the bounds of the mycell.hyperlink + int[] bnds = ExcelTools.getRowColFromString(this.getCellAddress()); + mycell.hyperlink.setRowFirst(bnds[0]); + mycell.hyperlink.setColFirst(bnds[1]); + mycell.hyperlink.setRowLast(bnds[0]); + mycell.hyperlink.setColLast(bnds[1]); + } + } + + /** + * Set the val of this Cell to an object
    + *
    + * The object may be one of type:
    + * String, Float, Integer, Double, Long, Boolean, java.sql.Date, or null
    + *
    + * To set a Cell to a formula, obj should be a string begining with "="
    + *
    + * To set a Cell to an array formula, obj should be a string begining with "{=" + *
    + * If you wish to put a line break in a string value, use the newline "\n" + * character. Note this will not function unless you also apply a format handle + * to the cell with WrapText=true + * + * @param Object obj - the object to set the value of the Cell to + * @throws CellTypeMismatchException + */ + + public void setVal(Object obj) throws CellTypeMismatchException { + if (this.wbh.getFormulaCalculationMode() != WorkBook.CALCULATE_EXPLICIT) + this.clearAffectedCells(); // blow out cache + + if (obj instanceof java.sql.Date) { + this.setVal((java.sql.Date) obj, null); + return; + } + if (obj instanceof String) { + String formstr = (String) obj; + if (formstr.indexOf("=") == 0 || formstr.startsWith("{=")) { // Formula or array string + try { + this.setFormula(formstr); + return; + } catch (/* 20070212 KSC: FunctionNotSupported */Exception a) { + Logger.logWarn( + "CellHandle.setVal() failed. Setting Formula to " + obj.toString() + " failed: " + a); + } + } + } + try { + setBiffRecValue(obj); + } catch (FunctionNotSupportedException fnse) { + // suppress these -- cell has been changed + } catch (Exception e) { + // NOT a CTMME always + + throw new CellTypeMismatchException(e.toString()); + } + } + + /** + * set the value of this cell to String s
    + * NOTE: this method will not check for formula references or do any data + * conversions
    + * This method is useful when a string may start with = but you do not want to + * convert to a Formula value + *

    + * If you wish to put a line break in the string use the newline "\n" character. + * Note this will not function unless you also apply a format handle to the cell + * with WrapText=true + * + * @param String s - the String value to set the Cell to + * @throws CellTypeMismatchException + * @see setVal(Object obj) + */ + public void setStringVal(String s) { + try { + if ((s == null || s.equals("")) && !(mycell instanceof Blank)) + changeCellType(s); + else if (s != null && !s.equals("")) { + if (!(mycell instanceof Labelsst)) + changeCellType(" "); // avoid potential issues with string + // values beginning with "=" + mycell.setStringVal(s); + } + } catch (Exception e) { + throw new CellTypeMismatchException(e.toString()); + } + } + + /** + * set the value of this cell to Unicodestring us
    + * NOTE: This method will not check for formula references or do any data + * conversions
    + * Useful when strings may start with = but you do not want to convert to a + * formula value + * + * @param Unicodestring us - Unicode String + * @throws CellTypeMismatchException + */ + public void setStringVal(Unicodestring us) { + try { + if ((us == null || us.toString().equals("")) && !(mycell instanceof Blank)) + changeCellType(null); // set to blank + else if (us != null && !us.toString().equals("")) { + if (!(mycell instanceof Labelsst)) + changeCellType(" "); // avoid potential issues with string + // values beginning with "=" + ((Labelsst) mycell).setStringVal(us); + } + } catch (Exception e) { + throw new CellTypeMismatchException(e.toString()); + } + } + + /** + * this method will be fired as each record is parsed from an input Spreadsheet + *

    + * Dec 15, 2010 + */ + public void fireParserEvent() { + + } + + /** + * Returns a String representation of this CellHandle + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + String ret = this.getCellAddress() + ":" + this.getStringVal(); + if (this.getURL() != null) + ret += this.getURL(); + return ret; + } + + /** + * Set the Value of the Cell to a double + * + * @param double d- double value to set this Cell to + * @throws CellTypeMismatchException + */ + public void setVal(double d) throws CellTypeMismatchException { + this.setVal(new Double(d)); + } + + /** + * Set value of this Cell to a Float + * + * @param float f - float value to set this Cell to + * @throws CellTypeMismatchException + */ + public void setVal(float f) throws CellTypeMismatchException { + this.setVal(new Float(f)); + } + + /** + * Sets the value of this Cell to a java.sql.Date.
    + * You must also specify a formatting pattern for the new date, or null for the + * default date format ("m/d/yy h:mm".)
    + *
    + * valid date format patterns:
    + * "m/d/y"
    + * "d-mmm-yy"
    + * "d-mmm"
    + * "mmm-yy"
    + * "h:mm AM/PM"
    + * "h:mm:ss AM/PM"
    + * "h:mm"
    + * "h:mm:ss"
    + * "m/d/yy h:mm"
    + * "mm:ss"
    + * "[h]:mm:ss"
    + * "mm:ss.0" + * + * @param java .sql.Date dt - the value of the new Cell + * @param String fmt - date formatting pattern + */ + public void setVal(java.sql.Date dt, String fmt) { + + if (this.wbh.getFormulaCalculationMode() != WorkBook.CALCULATE_EXPLICIT) + this.clearAffectedCells(); // blow out cache + if (fmt == null) + fmt = "m/d/yyyy"; + this.setVal(new Double(DateConverter.getXLSDateVal(dt))); + this.setFormatPattern(fmt); + } + + /** + * Sets the value of this Cell to a boolean value + * + * @param boolean b - boolean value to set this Cell to + * @throws CellTypeMismatchException + */ + public void setVal(boolean b) throws CellTypeMismatchException { + setVal(Boolean.valueOf(b)); + } + + /** + * Set the value of this Cell to an int value
    + * NOTE: setting a Boolean Cell type to a zero or a negative number will set the + * Cell to 'false'; setting it to an int value 1 or greater will set it to true. + * + * @param int i - int value to set this Cell to + * @throws CellTypeMismatchException + */ + public void setVal(int i) throws CellTypeMismatchException { + if (mycell.getCellType() == XLSConstants.TYPE_BOOLEAN) { + if (i > 0) + setVal(Boolean.valueOf(true)); + else + setVal(Boolean.valueOf(false)); + } else { + setVal(Integer.valueOf(i)); + } + } + + /** + * returns the value of this Cell as a double, if possible, or NaN if Cell value + * cannot be converted to double + * + * @return double value or NaN if the Cell value cannot be converted to a double + */ + public double getDoubleVal() { + return mycell.getDblVal(); + } + + /** + * returns the value of this Cell as a int, if possible, or NaN if Cell value + * cannot be converted to int + * + * @return int value or NaN if the Cell value cannot be converted to an int + */ + public int getIntVal() { + return mycell.getIntVal(); + } + + /** + * returns the value of this Cell as a float, if possible, or NaN if Cell value + * cannot be converted to float + * + * @return float value or NaN if the Cell value cannot be converted to an float + */ + public float getFloatVal() { + return mycell.getFloatVal(); + } + + /** + * returns the value of this Cell as a boolean
    + * If the Cell is not of type Boolean, returns false + * + * @return boolean value of cell + */ + public boolean getBooleanVal() { + return mycell.getBooleanVal(); + } + + /** + * Set a cell to Excel-compatible formula passed in as String.
    + * + * @param String formStr - the Formula String + * @throws FunctionNotSupportedException if unable to parse string correctly + */ + public void setFormula(String formStr) throws FunctionNotSupportedException { + int ixfe = this.mycell.getIxfe(); + this.remove(true); + this.mycell = wsh.add(formStr, this.getCellAddress()).mycell; + this.mycell.setXFRecord(ixfe); + } + + /** + * Set a cell to formula passed in as String. Sets the cachedValue as well, so + * no calculating is necessary. + *

    + * Parses the string to convert into a Excel formula.
    + * IMPORTANT NOTE: if cell is ALREADY a formula String this method will NOT + * reset it + * + * @param String formulaStr - The excel-compatible formula string to pass in + * @param Object value - The calculated value of the formula + * @throws Exception if unable to parse string correctly + */ + public void setFormula(String formStr, Object value) throws Exception { + if (!(this.mycell instanceof Formula)) { + int ixfe = this.mycell.getIxfe(); + CellRange cr = this.mycell.getMergeRange(); + int r = this.getRowNum(); + int c = this.getColNum(); + this.remove(true); + this.mycell = wsh.add(formStr, r, c, ixfe).mycell; + this.mycell.setMergeRange(cr); + } + Formula f = (Formula) this.mycell; + f.setCachedValue(value); + } + + /** + * sets the formula for this cellhandle using a stack of Ptgs appropriate for + * formula records. This method also sets the cachedValue of the formula as + * well, so no new calculating is necessary. + * + * @param Stack newExp - Stack of Ptgs + * @param Object value - calculated value of formula + */ + public void setFormula(Stack newExp, Object value) { + if (!(this.mycell instanceof Formula)) { + int ixfe = this.mycell.getIxfe(); + CellRange mccr = this.mycell.getMergeRange(); + int r = this.getRowNum(); + int c = this.getColNum(); + this.remove(true); + this.mycell = wsh.add("=0", r, c, ixfe).mycell; // add the most + // basic formula so + // can modify below + // ((: + this.mycell.setMergeRange(mccr); + } + try { + Formula f = (Formula) this.mycell; + f.setExpression(newExp); + f.setCachedValue(value); + } catch (Exception e) { + // do what?? + } + } + + /** + * Returns the size of the merged cell area, if one exists. + * + * @param row this parameter is ignored + * @param column this parameter is ignored + * @return a 2 position int array with number of rows and number of cols + * @deprecated since October 2012. This method duplicates the functionality of + * {@link #getMergedCellRange()}, which it calls internally. That + * method should be used instead. + */ + @Deprecated + public int[] getSpan(int row, int column) { + CellRange mergerange = getMergedCellRange(); + if (mergerange != null) { + if (DEBUG) + Logger.logInfo("CellHandle " + this.toString() + " getSpan() for range: " + mergerange.toString()); + int[] ret = {0, 0}; + // if(check.toString().equals(this.toString())){ //it's the first in + // the range -- show it! + try { + ret[0] = mergerange.getRows().length; + ret[1] = mergerange.getCols().length; // TODO: test! + } catch (Exception e) { + Logger.logWarn("CellHandle getting CellSpan failed: " + e); + } + // } + return ret; + } + return null; + } + + /** + * returns the WorkBookHandle for this Cell + * + * @return WorkBook the book + */ + public WorkBook getWorkBook() { + return wbh; + } + + /** + * get the index of the WorkSheet containing this Cell in the list of sheets + * + * @return int the WorkSheetHandle index for this Cell + */ + public int getSheetNum() { + return this.mycell.getSheet().getSheetNum(); + } + + /** + * get the WorkSheetHandle for this Cell + * + * @return the WorkSheetHandle for this Cell + */ + public WorkSheetHandle getWorkSheetHandle() { + return wsh; + } + + /** + * Determines if the cellHandle represents a completely blank/null cell, and can + * thus be ignored for many operations. + *

    + * Criteria for returning true is a cell type of BLANK, that has a default + * format id (0), is not part of a merge range, does not contain a URL, and is + * not a part of a validation + * + * @return true if cell is truly blank + */ + public boolean isDefaultCell() { + return (this.getCellType() == CellHandle.TYPE_BLANK) + && ((this.getFormatId() == 15 && !this.wbh.getWorkBook().getIsExcel2007()) || this.getFormatId() == 0) + && (this.getMergedCellRange() == null) && (this.getURL() == null) + && (this.getValidationHandle() == null); + } + + /** + * set the WorkSheetHandle for this Cell + * + * @param WorkSheetHandle handle - the new worksheet for this Cell + * @see WorkSheetHandle + */ + public void setWorkSheetHandle(WorkSheetHandle handle) { + wsh = handle; + + // This is redundant, already done in WSH.getCell(). + + // if (wsh!=null) //20080616 KSC + // wsh.cellhandles.put(this.getCellAddress(), this); + } + + /** + * Returns an XML representation of the cell and it's component data + * + * @return String of XML + */ + public String getXML() { + return getXML(null); + } + + /** + * Returns an XML representation of the cell and it's component data + * + * @param int[] mergedRange - include merged ranges in the XML representation if + * not null + * @return String of XML + */ + public String getXML(int[] mergedRange) { + String vl = "", fvl = "", sv = "", hd = "", csp = "", hlink = ""; + Object val = null; + StringBuffer retval = new StringBuffer(); + String typename = this.getCellTypeName(); + // put the formula string in + if (typename.equals("Formula")) { + try { + FormulaHandle fmh = getFormulaHandle(); + String fms = fmh.getFormulaString(); + // use single quotes around formula value to avoid errors in + // xslt transform + if (fms.indexOf("\"") > 0) { + fvl = " Formula='" + StringTool.convertXMLChars(fms) + "'"; + } else { + fvl = " Formula=\"" + StringTool.convertXMLChars(fms) + "\""; + } + try { + if (this.wbh.getWorkBook().getCalcMode() != WorkBook.CALCULATE_EXPLICIT) { + val = fmh.calculate(); + } else { + try { + // changed from getVal() now that getVal returns a + // null + val = fmh.getStringVal(); + } catch (Exception e) { + Logger.logWarn("CellHandle.getXML formula calc failed: " + e.toString()); + } + } + if (val instanceof Float) + typename = "Float"; + else if (val instanceof Double) + typename = "Double"; + else if (val instanceof Integer) + typename = "Integer"; + else if (val instanceof java.util.Date || val instanceof java.sql.Date + || val instanceof java.sql.Timestamp) + typename = "DateTime"; + else + typename = "String"; + } catch (Exception e) { + typename = "String"; // default + } + } catch (Exception e) { + Logger.logErr("OpenXLS.getXML() failed getting type of Formula for: " + this.toString(), e); + } + } + if (this.isDate()) + typename = "DateTime"; // 20060428 KSC: Moved after Formula parsing + + // TODO: when RowHandle.getCells actually contains ALL cells, keep this + if (this.mycell.getOpcode() != XLSConstants.MULBLANK) { + // Put the style ID in + sv = " StyleID=\"s" + getFormatId() + "\""; + if (mergedRange != null) { // TODO: fix! + csp += " MergeAcross=\"" + ((mergedRange[3] - mergedRange[1]) + 1) + "\""; + csp += " MergeDown=\"" + (mergedRange[2] - mergedRange[0]) + "\""; + } + if (this.getCol().isHidden()) { + hd = " Hidden=\"true\""; + } + // TODO: HRefScreenTip ???? + if (this.getURL() != null) { + hlink = " HRef=\"" + StringTool.convertXMLChars(this.getURL()) + "\""; + } + + // put the date formattingin + // Assemble the string + retval.append(""); + if (typename.equals("DateTime")) { + retval.append(DateConverter.getFormattedDateVal(this)); + } else if (this.getCellType() == CellHandle.TYPE_STRING) { + val = this.getStringVal(); // (String)getVal(); + if (val.equals("")) { // 20070216 KSC: John, had the same edits, + // seems to work well in cursory tests ... + // retval.append(" "); does this screw up formulas expecting + // empty strings? -jm + } else { + retval.append(StringTool.convertXMLChars(val.toString())); + } + } else { + try { + // if(val == null) + val = this.getVal(); + retval.append(StringTool.convertXMLChars(val.toString()) + vl); + } catch (Exception e) { + Logger.logErr("CellHandle.getXML failed for: " + this.getCellAddress() + " in: " + + this.getWorkBook().toString(), e); + retval.append("XML ERROR!"); + } + } + retval.append(""); + retval.append(end_cell_xml); + } else { + int c = ((Mulblank) this.mycell).getColFirst(); + int lastcol = ((Mulblank) this.mycell).getColLast(); + for (; c <= lastcol; c++) { + mulblankcolnum = (short) c; + // Put the style ID in + sv = " StyleID=\"s" + getFormatId() + "\""; + if (this.getCol().isHidden()) { + hd = " Hidden=\"true\""; + } + // TODO: HRefScreenTip ???? + if (this.getURL() != null) { + hlink = " HRef=\"" + StringTool.convertXMLChars(this.getURL()) + "\""; + } + + // put the date formattingin + // Assemble the string + retval.append(""); + retval.append(end_cell_xml); + } + } + return retval.toString(); + } + + /** + * Set the horizontal alignment for this Cell + * + * @param int align - constant value representing the horizontal alignment. + * @see FormatHandle.ALIGN* constants + */ + public void setHorizontalAlignment(int align) { + setFormatHandle(); + formatter.setHorizontalAlignment(align); + } + + /** + * Returns an int representing the current horizontal alignment in this Cell. + * + * @return int representing horizontal alignment + * @see FormatHandle.ALIGN* constants + */ + public int getHorizontalAlignment() { + if (this.mycell.getXfRec() != null) { + return this.mycell.getXfRec().getHorizontalAlignment(); + } + // 0 is default alignment + return 0; + } + + /** + * Set the Vertical alignment for this Cell + * + * @param int align - constant value representing the vertical alignment. + * @see FormatHandle.ALIGN* constants + */ + public void setVerticalAlignment(int align) { + setFormatHandle(); + formatter.setVerticalAlignment(align); + } + + /** + * Returns an int representing the current vertical alignment in this Cell. + * + * @return int representing vertical alignment + * @see FormatHandle.ALIGN* constants + */ + public int getVerticalAlignment() { + if (this.mycell.getXfRec() != null) { + return this.mycell.getXfRec().getVerticalAlignment(); + } + // 1 is default alignment + return 1; + } + + /** + * Sets the cell wrapping behavior for this cell + * + * @param boolean wrapit - true if cell text should be wrapped (default is false) + */ + public void setWrapText(boolean wrapit) { + setFormatHandle(); + formatter.setWrapText(wrapit); + if (wrapit) { // when wrap text it automatically wraps if row height has + // NOT been set yet + try { + if (!this.getRow().isAlteredHeight()) // has row height been altered?? + this.getRow().setRowHeightAutoFit(); + } catch (Exception e) { /* ignore */ + } + } + } + + /** + * Get the cell wrapping behavior for this cell. + * + * @return true if cell text is wrapped + */ + public boolean getWrapText() { + if (this.mycell.getXfRec() != null) { + return this.mycell.getXfRec().getWrapText(); + } + // false is default alignment + return false; + } + + /** + * Set the rotation of the cell in degrees.
    + * Values 0-90 represent rotation up, 0-90degrees.
    + * Values 91-180 represent rotation down, 0-90 degrees.
    + * Value 255 is vertical + * + * @param int align - an int representing the rotation. + */ + public void setCellRotation(int align) { + setFormatHandle(); + formatter.setCellRotation(align); + } + + /** + * Get the rotation of this Cell in degrees.
    + * Values 0-90 represent rotation up, 0-90degrees.
    + * Values 91-180 represent rotation down, 0-90 degrees.
    + * Value 255 is vertical + * + * @return int representing the degrees of cell rotation + */ + public int getCellRotation() { + if (this.mycell.getXfRec() != null) { + return this.mycell.getXfRec().getRotation(); + } + // false is default alignment + return 0; + } + + public int compareTo(CellHandle that) { + int comp = this.getRowNum() - that.getRowNum(); + if (comp != 0) + return comp; + return this.getColNum() - that.getColNum(); + } + + @Override + public boolean equals(Object that) { + if (!(that instanceof CellHandle)) + return false; + return this.mycell.equals(((CellHandle) that).mycell); + } + + @Override + public int hashCode() { + return this.mycell.hashCode(); + } + + /** + * Set the super/sub script for the Font + * + * @param int ss - super/sub script constant (0 = none, 1 = super, 2 = sub) + */ + public void setScript(int ss) { + if (mycell.myxf == null) + this.getNewXf(); + mycell.myxf.getFont().setScript(ss); + } + + /** + * Set the val of the biffrec with an Object + * + * @param Object to set the value of the Cell to + */ + private void setBiffRecValue(Object obj) throws CellTypeMismatchException { + if (mycell.getOpcode() == XLSConstants.BLANK || mycell.getOpcode() == XLSConstants.MULBLANK) { + // no reason for this Blank blank = (Blank)mycell; + // String addr = mycell.getCellAddress(); + + // trim the Mulblank + /* + * KSC: mulblanks are NOT expanded now if (blank.getMyMul() != null){ Mulblank + * mblank = (Mulblank)blank.getMyMul(); mblank.trim(blank); } + */ + changeCellType(obj); // 20080206 KSC: Basically replaces all above + // code + } else { + if (obj == null) { + // should never be false ??? if (!(mycell instanceof Blank)) + changeCellType(obj); // will set to blank + } else if (obj instanceof Float || obj instanceof Double || obj instanceof Integer || obj instanceof Long) { + if ((mycell instanceof NumberRec) || (mycell instanceof Rk)) { + if (obj instanceof Float) { + Float f = (Float) obj; + mycell.setFloatVal(f.floatValue()); + } else if (obj instanceof Integer) { + Integer i = (Integer) obj; + mycell.setIntVal(i.intValue()); + } else if (obj instanceof Double) { + Double d = (Double) obj; + mycell.setDoubleVal(d.doubleValue()); + } else if (obj instanceof Long) { + Long d = (Long) obj; + mycell.setDoubleVal(d.longValue()); + } + } else + changeCellType(obj); + } else if (obj instanceof Boolean) { + if (mycell instanceof Boolerr) + mycell.setBooleanVal(((Boolean) obj).booleanValue()); + else + changeCellType(obj); + } else if (obj instanceof String) { + if (((String) obj).startsWith("=")) + changeCellType(obj); // easier to just redo a formula... + else if (!obj.toString().equalsIgnoreCase("")) { + if (mycell instanceof Labelsst) + mycell.setStringVal(String.valueOf(obj)); + else + changeCellType(obj); + } else if (!(mycell instanceof Blank)) + changeCellType(obj); + } + } + } + + /** + * if object type doesn't match current mycell record, remove and add + * appropriate record type + * + * @param obj + */ + private void changeCellType(Object obj) { + int[] rc = {mycell.getRowNumber(), mycell.getColNumber()}; + Boundsheet bs = mycell.getSheet(); + int oldXf = mycell.getIxfe(); + bs.removeCell(mycell); + BiffRec addedrec = bs.addValue(obj, rc, true); + mycell = (XLSRecord) addedrec; + mycell.setXFRecord(oldXf); + } + + /** + * retrieves or creates a new xf for this cell + * + * @return + */ + private Xf getNewXf() { + if (mycell.myxf != null) + return mycell.myxf; + // reusing or creating new xfs is handled in FormatHandle/cloneXf and + // updateXf + // this.useExistingXF = true; // flag to re-use this XF + try { + mycell.myxf = new Xf(this.getFont().getIdx()); + // get the recidx of the last Xf + int insertIdx = mycell.getWorkBook().getXf(mycell.getWorkBook().getNumXfs() - 1).getRecordIndex(); + // perform default add rec actions + + mycell.myxf.setSheet(null); + mycell.getWorkBook().getStreamer().addRecordAt(mycell.myxf, insertIdx + 1); + mycell.getWorkBook().addRecord(mycell.myxf, false); + // update the pointer + int xfe = mycell.myxf.getIdx(); + mycell.setIxfe(xfe); + return mycell.myxf; + } catch (Exception e) { + return null; + } + } + + static final String begin_hidden_emptycell_xml = ""; + + static final String begin_cell_xml = ""; + static final String end_cell_xml = "
    "; + + /** + * Returns an xml representation of an empty cell + * + * @param loc - the cell address + * @param isVisible - if the cell is visible (not hidden) + * @return + */ + protected static String getEmptyCellXML(String loc, boolean isVisible) { + if (!isVisible) { + return begin_hidden_emptycell_xml + loc + end_hidden_emptycell_xml; + } else { + return begin_cell_xml + loc + end_emptycell_xml; + } + } + + /** + * Calculates and returns all formulas that reference this CellHandle.
    + * Please note that these cells may have already been calculated, so in order to + * get their values without re-calculating them Extentech suggests setting the + * book level non-calculation flag, ie + * book.setFormulaCalculationMode(WorkBookHandle.CALCULATE_EXPLICIT); or + * FormulaHandle.getCachedVal() + * + * @return List of of calculated cells (CellHandles) + */ + public List calculateAffectedCells() { + ReferenceTracker rt = this.wbh.getWorkBook().getRefTracker(); + Iterator its = rt.clearAffectedFormulaCells(this).values().iterator(); + + List ret = new ArrayList(); + while (its.hasNext()) { + CellHandle cx = new CellHandle((BiffRec) its.next(), this.wbh); + ret.add(cx); + } + return ret; + } + + /** + * Internal method for clearing affected cells, does the same thing as + * calculateAffectedCells, but does not create a list + */ + protected void clearAffectedCells() { + ReferenceTracker rt = this.wbh.getWorkBook().getRefTracker(); + rt.clearAffectedFormulaCells(this); + } + + /** + * Calculates and returns all formulas on the same sheet that reference this + * CellHandle.
    + * Please note that these cells may have already been calculated, so in order to + * get their values without re-calculating them Extentech suggests setting the + * book level non-calculation flag, ie + * book.setFormulaCalculationMode(WorkBookHandle.CALCULATE_EXPLICIT); or + * FormulaHandle.getCachedVal() + * + * @return List of of calculated cells (CellHandles) + */ + public List calculateAffectedCellsOnSheet() { + Iterator its = this.wbh.getWorkBook().getRefTracker() + .clearAffectedFormulaCellsOnSheet(this, this.getWorkSheetName()).values().iterator(); + List ret = new ArrayList(); + while (its.hasNext()) { + CellHandle cx = new CellHandle((BiffRec) its.next(), this.wbh); + ret.add(cx); + } + return ret; + } + + /** + * flags chart references to the particular cell as dirty/ needing caches + * rebuilt + */ + public void clearChartReferences() { + ArrayList ret = new ArrayList(); + Iterator ii = this.wbh.getWorkBook().getRefTracker().getChartReferences(this.getCell()).iterator(); + while (ii.hasNext()) { + Ai ai = (Ai) ii.next(); + if (ai.getParentChart() != null) + ai.getParentChart().setMetricsDirty(); + } + } + + /** + * Creates a copy of this cell on the given worksheet at the given address. + * + * @param sourcecell the cell to copy + * @param newsheet the sheet to which the cell should be copied + * @param row the row in which the copied cell should be placed + * @param col the row in which the copied cell should be placed + * @return CellHandle representing the new Cell + */ + public static final CellHandle copyCellToWorkSheet(CellHandle sourcecell, WorkSheetHandle newsheet, int row, + int col) throws CellPositionConflictException, CellNotFoundException { + return copyCellToWorkSheet(sourcecell, newsheet, row, col, false); + } + + /** + * Creates a copy of this cell on the given worksheet at the given address. + * + * @param sourcecell the cell to copy + * @param newsheet the sheet to which the cell should be copied + * @param row the row in which the copied cell should be placed + * @param col the row in which the copied cell should be placed + * @param copyByValue whether to copy formulas' values instead of the formulas + * themselves + * @return CellHandle representing the new cell + */ + public static CellHandle copyCellToWorkSheet(CellHandle sourcecell, WorkSheetHandle newsheet, int row, int col, + boolean copyByValue) { + // copy cell values + CellHandle newcell = null; + + int[] offsets = {row - sourcecell.getRowNum(), col - sourcecell.getColNum()}; + + if (sourcecell.isFormula() && !copyByValue) + try { + FormulaHandle fmh = sourcecell.getFormulaHandle(); + newcell = newsheet.add(fmh.getFormulaString(), row, col); + FormulaHandle fm2 = newcell.getFormulaHandle(); + FormulaHandle.moveCellRefs(fm2, offsets); + } catch (FormulaNotFoundException ex) { + newcell = null; + } + + if (newcell == null) + newcell = newsheet.add(sourcecell.getVal(), row, col); + + return copyCellHelper(sourcecell, newcell); + } + + /** + * Create a copy of this Cell in another WorkBook + * + * @param sourcecell the cell to copy + * @param target worksheet to copy this cell into + * @return + */ + public static final CellHandle copyCellToWorkSheet(CellHandle sourcecell, WorkSheetHandle newsheet) { + // copy cell values + CellHandle newcell = null; + try { + FormulaHandle fmh = sourcecell.getFormulaHandle(); + // Logger.logInfo("testFormats Formula encountered: "+ + // fmh.getFormulaString()); + + newcell = newsheet.add(fmh.getFormulaString(), sourcecell.getCellAddress()); + } catch (FormulaNotFoundException ex) { + newcell = newsheet.add(sourcecell.getVal(), sourcecell.getCellAddress()); + } + return copyCellHelper(sourcecell, newcell); + } + + /** + * Get the JSON object for this cell. + * + * @return String representing the JSON for this Cell + */ + public String getJSON() { + return getJSONObject().toString(); + } + + /** + * Get the JSON object for this cell. + */ + public JSONObject getJSONObject() { + CellRange cr = getMergedCellRange(); + int[] mergedCellRange = null; + if (cr != null) { + try { + mergedCellRange = cr.getRangeCoords(); + if (mycell.getOpcode() == XLSRecord.MULBLANK) { + Mulblank m = (Mulblank) mycell; + if (!cr.contains(m.getIntLocation())) { + mergedCellRange = null; + } + } + } catch (CellNotFoundException e) { + } + } + + return getJSONObject(mergedCellRange); + } + + /** + * Get a JSON Object representation of a cell utilizing a merged range + * identifier. + * + * @deprecated The {@code mergedRange} parameter is unnecessary. This method + * will be removed in a future version. Use {@link #getJSONObject()} + * instead. + */ + @Deprecated + public JSONObject getJSONObject(int[] mergedRange) { + JSONObject theCell = new JSONObject(); + try { + theCell.put(JSON_LOCATION, getCellAddress()); + + Object val; + try { + val = getVal(); + if (val == null) + val = ""; + } catch (Exception ex) { + Logger.logWarn("OpenXLS.getJSONObject failed: " + ex.toString()); + val = "#ERR!"; + } + + String typename = getCellTypeName(); + JSONObject dataval = new JSONObject(); + + if (typename.equals("Formula")) { + try { + FormulaHandle fmh = getFormulaHandle(); + String fms = fmh.getFormulaString(); + + theCell.put(JSON_CELL_FORMULA, fms); + + try { + if (Float.parseFloat(val.toString()) == (Float.NaN)) { + typename = JSON_FLOAT; + } else if (val instanceof Float) + typename = JSON_FLOAT; + else if (val instanceof Double) + typename = JSON_DOUBLE; + else if (val instanceof Integer) + typename = JSON_INTEGER; + else if (val instanceof java.util.Date || val instanceof java.sql.Date + || val instanceof java.sql.Timestamp) { + typename = JSON_DATETIME; + } else + typename = JSON_STRING; + } catch (Exception e) { + typename = JSON_STRING; // default + } + } catch (Exception e) { + Logger.logErr("OpenXLS.getJSON() failed getting type of Formula for: " + toString(), e); + } + } + + if (isDate()) + typename = JSON_DATETIME; + + dataval.put(JSON_TYPE, typename); + + // TODO: Handle Conditional Format + // cell should return the style id for its condition + // this is an ID that begins incrementing after the last Xf + // and should be contained in the CSS for the output + + // if the conditional format evaluates to TRUE + // then we use *that* style ID not the default + + // We can have multiple CF styles per cell, one per each rule... we'll need that + // from CSS standpoint so... + + // style + theCell.put(JSON_STYLEID, getConditionalFormatId()); + + // merges + if (mergedRange != null) { + theCell.put(JSON_MERGEACROSS, (mergedRange[3] - mergedRange[1])); + theCell.put(JSON_MERGEDOWN, (mergedRange[2] - mergedRange[0])); + if (isMergeParent()) { + theCell.put(JSON_MERGEPARENT, true); + } else { + theCell.put(JSON_MERGECHILD, true); + } + } + + // handle hidden setting + try { + if (getCol().isHidden()) + theCell.put(JSON_HIDDEN, true); + } catch (Exception e) { + } + + // handle the locked/formula hidden setting + // only active if sheet is protected + try { + if (isFormulaHidden()) + theCell.put(JSON_FORMULA_HIDDEN, true); + + theCell.put(JSON_LOCKED, isLocked()); + } catch (Exception e) { + } + + try { + ValidationHandle vh = getValidationHandle(); + if (vh != null) + theCell.put(JSON_VALIDATION_MESSAGE, vh.getPromptBoxTitle() + ":" + vh.getPromptBoxText()); + } catch (Exception e) { + } + + // hyperlinks + if (!(getURL() == null)) + theCell.put(JSON_HREF, getURL()); + + if (getWrapText()) + theCell.put(JSON_WORD_WRAP, true); + + // store alignment for container issues + int alignment = getFormatHandle().getHorizontalAlignment(); + if (alignment == FormatHandle.ALIGN_RIGHT) { + theCell.put(JSON_TEXT_ALIGN, "right"); + } else if (alignment == FormatHandle.ALIGN_CENTER) { + theCell.put(JSON_TEXT_ALIGN, "center"); + } else if (alignment == FormatHandle.ALIGN_LEFT) { + theCell.put(JSON_TEXT_ALIGN, "left"); + } + + // dates + if (typename.equals(JSON_DATETIME) && !(val == null) && !val.equals("")) { + dataval.put(JSON_CELL_VALUE, getFormattedStringVal()); + // dataval.put(JSON_DATEVALUE, ch.getFloatVal()); + dataval.put("time", DateConverter.getCalendarFromCell(this).getTimeInMillis()); + } else if (getCellType() == CellHandle.TYPE_STRING) { + // FORCES CALC + if (((String) val).indexOf("\n") > -1) { + val = ((String) val).replaceAll("\n", "
    "); + } + if (!val.equals("")) + dataval.put(JSON_CELL_VALUE, val.toString()); + } else { // other + dataval.put(JSON_CELL_VALUE, val.toString()); + try { // formatted pattern + String s = getFormatPattern(); + if (!(s.equals(""))) { + String fmtd = getFormattedStringVal(); // TRIGGERS CALC! + if (!(val.equals(fmtd))) + dataval.put(JSON_CELL_FORMATTED_VALUE, fmtd); + if (s.indexOf("Red") > -1) { + Double d = new Double(val.toString()); + if (d.doubleValue() < 0) { + theCell.put(JSON_RED_FORMAT, "1"); + if (fmtd.indexOf("-") == 0) + fmtd = fmtd.substring(1); + dataval.put(JSON_CELL_FORMATTED_VALUE, fmtd); + } + } + } + } catch (Exception x) { + } + } + theCell.put(JSON_DATA, dataval); + } catch (JSONException e) { + Logger.logErr("error getting JSON for the cell: " + e); + } + return theCell; + } + + /** + * Returns the validation handle for the cell. + * + * @return ValidationHandle for this Cell, or null if none + */ + public ValidationHandle getValidationHandle() { + ValidationHandle ret = null; + try { + ret = this.getWorkSheetHandle().getValidationHandle(this.getCellAddress()); + } catch (Exception e) { + // somewhat normal? + } + return ret; + } + + /** + * Copies all formatting - xf and non-xf (such as column width, hidden state) + * plus merged cell range from a sourcecell to a new cell (usually in a new + * workbook) + * + * @param sourcecell the cell to copy + * @param newcell the cell to copy to + * @return + */ + protected static final CellHandle copyCellHelper(CellHandle sourcecell, CellHandle newcell) { + // copy row height & attributes + int rz = sourcecell.getRow().getHeight(); + newcell.getRow().setHeight(rz); + if (sourcecell.getRow().isHidden()) { + newcell.getRow().setHidden(true); + } + // copy col width & attributes + int rzx = sourcecell.getCol().getWidth(); + newcell.getCol().setWidth(rzx); + if (sourcecell.getCol().isHidden()) { + newcell.getCol().setHidden(true); + // Logger.logInfo("column " + rzx + " is hidden"); + } + + try { + // copy merged ranges + CellRange rng = sourcecell.getMergedCellRange(); + if (rng != null) { + rng = new CellRange(rng.getRange(), newcell.getWorkBook()); + rng.addCellToRange(newcell); + rng.mergeCells(false); + } + // Handle formats: + Xf origxf = sourcecell.getWorkBook().getWorkBook().getXf(sourcecell.getFormatId()); + newcell.getFormatHandle().addXf(origxf); + return newcell; + } catch (Exception ex) { + Logger.logErr("CellHandle.copyCellHelper failed.", ex); + } + return newcell; + } } diff --git a/src/main/java/io/starter/OpenXLS/CellRange.java b/src/main/java/io/starter/OpenXLS/CellRange.java index 0c70226..6a6ae0f 100644 --- a/src/main/java/io/starter/OpenXLS/CellRange.java +++ b/src/main/java/io/starter/OpenXLS/CellRange.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,51 +22,25 @@ */ package io.starter.OpenXLS; -import static io.starter.OpenXLS.JSONConstants.JSON_CELL; -import static io.starter.OpenXLS.JSONConstants.JSON_CELLS; -import static io.starter.OpenXLS.JSONConstants.JSON_CELL_VALUE; -import static io.starter.OpenXLS.JSONConstants.JSON_LOCATION; -import static io.starter.OpenXLS.JSONConstants.JSON_RANGE; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.Blank; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.CellNotFoundException; -import io.starter.formats.XLS.CellRec; -import io.starter.formats.XLS.ColumnNotFoundException; -import io.starter.formats.XLS.Condfmt; -import io.starter.formats.XLS.FormulaNotFoundException; -import io.starter.formats.XLS.Mergedcells; -import io.starter.formats.XLS.Name; -import io.starter.formats.XLS.RowNotFoundException; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.XLSConstants; -import io.starter.formats.XLS.XLSRecord; -import io.starter.formats.XLS.Xf; +import io.starter.formats.XLS.*; import io.starter.formats.XLS.formulas.FormulaParser; import io.starter.formats.XLS.formulas.GenericPtg; import io.starter.formats.XLS.formulas.Ptg; import io.starter.formats.XLS.formulas.PtgRef; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.Serializable; +import java.util.*; + +import static io.starter.OpenXLS.JSONConstants.*; /** * Cell Range is a handle to a range of Workbook Cells - * + * *
    * Contains useful methods for working with Collections of Cells.
    *
    @@ -80,1959 +54,1886 @@ * }
    *
    * - * + * * Starter Inc. - * + * * @see DataBoundCellRange * @see XLSRecord */ public class CellRange implements Serializable { - Condfmt cfx = null; - - /** - * returns the conditional format object for this range, if any - * - * @return Condfmt object - */ - protected Condfmt getConditionalFormat() { - return cfx; - } - - /** - * - * - * - */ - private static final long serialVersionUID = -3609881364824289079L; - private boolean ismerged = false; - private BiffRec parent = null; // if cell range is child of a named range, must ensure update correctly - public static final boolean REMOVE_MERGED_CELLS = true; - public static final boolean RETAIN_MERGED_CELLS = false; - // private Ptg myptg = null; - public boolean DEBUG = false; - private boolean isDirty = false; // true if addCellsToRange without init - int firstcellrow = -1; - int firstcellcol = -1; - int lastcellrow = -1; - int lastcellcol = -1; - private boolean createBlanks = false; - private boolean initializeCells = true; - public transient CellHandle[] cells; - protected transient String range, sheetname; - protected transient io.starter.OpenXLS.WorkBook mybook; - private transient WorkSheetHandle sheet = null; - private int[] myrowints; - private int[] mycolints; - private transient RowHandle[] myrows; - private transient ColHandle[] mycols; - - // for OOXML External References - protected int externalLink1 = 0; - protected int externalLink2 = 0; - - FormatHandle fmtr = null; - boolean wholeCol = false, wholeRow = false; - - /** Protected constructor for creating result ranges. */ - protected CellRange(WorkSheetHandle sheet, int row, int col, int width, int height) { - this.sheet = sheet; - sheetname = sheet.getSheetName(); - mybook = sheet.getWorkBook(); - - firstcellrow = row; - firstcellcol = col; - lastcellrow = row + height - 1; - lastcellcol = col + width - 1; - - range = sheetname + "!" - + ExcelTools.formatRange(new int[] { firstcellcol, firstcellrow, lastcellcol, lastcellrow }); - - cells = new CellHandle[width * height]; - } - - /** - * Initializes a CellRange from a CellRangeRef. The - * source CellRangeRef instance must be qualified with a single - * resolved worksheet. - * - * @param source - * the CellRangeRef from which to initialize - * @param init - * whether to populate the cell array - * @param create - * whether to fill gaps in the range with blank cells - * @throws IllegalArgumentException - * if the source range does not have a resolved sheet or has more - * than one sheet - */ - public CellRange(CellRangeRef source, boolean init, boolean create) { - initializeCells = init; - createBlanks = create; - - sheet = source.getFirstSheet(); - if (sheet == null || source.isMultiSheet()) - throw new IllegalArgumentException("the source range must have a single resolved sheet"); - - mybook = this.sheet.getWorkBook(); - sheetname = this.sheet.getSheetName(); - - // This is inefficient, but fixing it would require rewriting init. - this.range = source.toString(); - - try { - this.init(); - } catch (CellNotFoundException e) { - // this should be impossible - throw new RuntimeException(e); - } - } - - /** - * Initializes a CellRange from a CellRangeRef. The - * source CellRangeRef instance must be qualified with a single - * resolved worksheet. - * - * @param source - * the CellRangeRef from which to initialize - * @throws IllegalArgumentException - * if the source range does not have a resolved sheet or has more - * than one sheet - */ - public CellRange(CellRangeRef source) { - this(source, false, true); - } - - public void clearFormats() { - for (int idx = 0; idx < cells.length; idx++) - if (cells[idx] != null) - cells[idx].clearFormats(); - } - - /** - * @deprecated use clear() - */ - @Deprecated - public void clearContents() { - for (int idx = 0; idx < cells.length; idx++) - if (cells[idx] != null) - cells[idx].clearContents(); - } - - /** - * clears the contents and formats of the cells referenced by this range but - * does not remove the cells from the workbook. - * - */ - public void clear() { - for (int idx = 0; idx < cells.length; idx++) - if (cells[idx] != null) - cells[idx].clear(); - } - - /** - * removes the cells referenced by this range from the sheet. - * - * NOTE: method does not shift rows or cols. - * - */ - public void removeCells() { - for (int idx = 0; idx < cells.length; idx++) - if (cells[idx] != null) - cells[idx].remove(true); - } - - /** - * Un-Merge the Cells contained in this CellRange - * - * @throws Exception - */ - public void unMergeCells() throws Exception { - BiffRec[] mycells = this.getCellRecs(); - for (int t = 0; t < mycells.length; t++) { - mycells[t].setMergeRange(null); // unset the range of merged cells - mycells[t].getXfRec().setMerged(false); - } - Mergedcells mc = this.getSheet().getSheet().getMergedCellsRec(); - if (mc != null) - mc.removeCellRange(this); - this.ismerged = false; - } - - /** - * Set the format ID of all cells in this CellRange
    - * FormatID can be obtained through any CellHandle with the getFormatID() call - * - * @param int - * fmtID - the format ID to set the cells within the range to - */ - public void setFormatID(int fmtID) throws Exception { - BiffRec[] mycells = this.getCellRecs(); - for (int t = 0; t < mycells.length; t++) { - mycells[t].setXFRecord(fmtID); - } - } - - /** - * Set a hyperlink on all cells in this CellRange - * - * @param String - * url - the URL String to set - */ - public void setURL(String url) throws Exception { - BiffRec[] mycells = this.getCellRecs(); - for (int t = 0; t < mycells.length; t++) { - new CellHandle(mycells[t], this.mybook).setURL(url); - } - } - - /** - * Merge the Cells contained in this CellRange - * - * @param boolean - * remove - true to delete the Cells following the first in the range - */ - public void mergeCells(boolean remove) { - this.createBlanks(); - if (remove) - this.mergeCellsClearFollowingCells(); - else - this.mergeCellsKeepFollowingCells(); - } - - /** - * Merge the Cells contained in this CellRange, clearing or removing the Cells - * following the first in the range - * - */ - private void mergeCellsClearFollowingCells() { - BiffRec[] mycells = this.getCellRecs(); - - // mycells[0].setMergeRange(this); // set the range of merged cells - Xf r = mycells[0].getXfRec(); - if (r == null) { - fmtr = new FormatHandle(this.getWorkBook()); - fmtr.addCell(mycells[0]); - r = mycells[0].getXfRec(); - } - r.setMerged(true); - for (int t = 0; t < mycells.length; t++) { - if (mycells[t] != null) - mycells[t].setSheet(this.getSheet().getMysheet()); - mycells[t].setMergeRange(this); // set the range of merged cells - if (t > 0) { - if (!(mycells[t] instanceof Blank)) { - String cellname = mycells[t].getCellAddress(); - Boundsheet sheet = mycells[t].getSheet(); - mycells[t].remove(true); // blow it out! - sheet.addValue(null, cellname); - } - } - } - Mergedcells mc = this.getSheet().getSheet().getMergedCellsRec(); - if (mc == null) - mc = this.getSheet().getSheet().addMergedCellRec(); - mc.addCellRange(this); - this.ismerged = true; - } - - /** - * Merge the Cells contained in this CellRange * - */ - private void mergeCellsKeepFollowingCells() { - BiffRec[] mycells = this.getCellRecs(); - for (int t = 0; t < mycells.length; t++) { - mycells[t].setMergeRange(this); // set the range of merged cells - Xf r = mycells[t].getXfRec(); - if (r == null) { - fmtr = new FormatHandle(this.getWorkBook()); - fmtr.addCellRange(this); - r = mycells[t].getXfRec(); - } - r.setMerged(true); - } - - Mergedcells mc = this.getSheet().getSheet().getMergedCellsRec(); - if (mc == null) - mc = this.getSheet().getSheet().addMergedCellRec(); - mc.addCellRange(this); - this.ismerged = true; - } - - /** - * Gets the number of columns in the range. - */ - public int getWidth() { - return lastcellcol - firstcellcol + 1; - } - - /** - * Gets the number of rows in the range. - */ - public int getHeight() { - return lastcellrow - firstcellrow + 1; - } - - /** - * Returns an array of the row numbers referenced by this CellRange - * - * @return int[] array of row ints - */ - public int[] getRowInts() { - if (myrowints != null) - return myrowints; - int numrows = (lastcellrow + 1) - firstcellrow; - myrowints = new int[numrows]; - for (int t = 0; t < numrows; t++) { - myrowints[t] = firstcellrow + t; - } - return myrowints; - } - - /** - * returns an array of column numbers referenced by this CellRange - * - * @return int[] array of col ints - */ - public int[] getColInts() { - if (mycolints != null) - return mycolints; - int numcols = (lastcellcol + 1) - firstcellcol; - mycolints = new int[numcols]; - for (int t = 0; t < numcols; t++) { - mycolints[t] = firstcellcol + t; - } - return mycolints; - } - - /** - * Returns an array of Rows (RowHandles) referenced by this CellRange - * - * @return RowHandle[] array of row handles - */ - public RowHandle[] getRows() throws RowNotFoundException { - if (myrows != null) - return myrows; - int numrows = (lastcellrow + 1) - firstcellrow; - myrows = new RowHandle[numrows]; - for (int t = 0; t < numrows; t++) { - RowHandle rx = null; - try { - rx = sheet.getRow((firstcellrow - 1) + t); - } catch (Exception x) { - ; // typically empty rows - } - myrows[t] = rx; - } - return myrows; - } - - /** - * Get the number of rows that this CellRange encompasses - * - * @return - */ - public int getNumberOfRows() { - int numRows = (lastcellrow + 1) - firstcellrow; - return numRows; - } - - /** - * Returns an array of Columns (ColHandles) referenced by this CellRange - * - * @return ColHandle[] array of columns handles - */ - public ColHandle[] getCols() throws ColumnNotFoundException { - if (mycols != null) - return mycols; - int numcols = (lastcellcol + 1) - firstcellcol; - mycols = new ColHandle[numcols]; - for (int t = 0; t < numcols; t++) { - mycols[t] = sheet.getCol(firstcellcol + t); - } - return mycols; - } - - /** - * Get the number of columns that this CellRange encompasses - * - * @return - */ - public int getNumberOfCols() { - int numCols = (lastcellcol + 1) - firstcellcol; - return numCols; - } - - /** - * returns edge status of the desired CellHandle within this CellRange ie: top, - * left, bottom, right
    - * returns 0 or 1 for 4 sides
    - * 1,1,1,1 is a single cell in a range 1,1,0,0 is on the top left edge of the - * range - * - * @param CellHandle - * ch - - * @param int - * sz - - * @return int[] array representing the edge positions - */ - // TODO: documentation: Don't quite understand this! - public int[] getEdgePositions(CellHandle ch, int sz) { - int[] coords = { 0, 0, 0, 0 }; - // get the corners, check for 'edges' - String adr = ch.getCellAddress(); - int[] rc = ExcelTools.getRowColFromString(adr); - // increment to one-based - rc[0]++; - if (rc[0] == firstcellrow) - coords[0] = sz; - if (rc[0] == lastcellrow) - coords[2] = sz; - if (rc[1] == firstcellcol) - coords[1] = sz; - if (rc[1] == lastcellcol) - coords[3] = sz; - return coords; - } - - /** - * returns whether this CellRange intersects with another CellRange - * - * @param CellRange - * cr - CellRange to test - * @return boolean true if CellRange cr intersects with this CellRange - */ - public boolean intersects(CellRange cr) { - // get the corners, check for 'contains' - try { - int[] rc = cr.getRangeCoords(); - if ((rc[0] >= firstcellrow) && (rc[2] <= lastcellrow) && (rc[1] >= firstcellcol) - && (rc[3] <= lastcellcol)) { - return true; - } - } catch (CellNotFoundException e) { - Logger.logWarn("CellRange unable to determine intersection of range: " + cr.toString()); - } - return false; - } - - /** - * returns whether this CellRange contains a particular Cell - * - * @param CellHandle - * ch - the Cell to check - * @return true if CellHandle ch is contained within this CellRange - */ - public boolean contains(Cell cxx) { - String chsheet = cxx.getWorkSheetName(); - String mysheet = ""; - if (this.getSheet() != null) - mysheet = this.getSheet().getSheetName(); - if (!chsheet.equalsIgnoreCase(mysheet)) - return false; - String adr = cxx.getCellAddress(); - int[] rc = ExcelTools.getRowColFromString(adr); - return contains(rc); - } - - /** - * returns whether this CellRange contains the specified Row/Col coordinates - * - * @param int[] - * rc - row/col coordinates to test - * @return true if the coordinates are contained with this CellRange - */ - public boolean contains(int[] rc) { - boolean ret = true; - if (rc[0] + 1 < firstcellrow) - ret = false; - if (rc[0] + 1 > lastcellrow) - ret = false; - if (rc[1] < firstcellcol) - ret = false; - if (rc[1] > lastcellcol) - ret = false; - return ret; - } - - /** - * returns the String representation of this CellRange - */ - @Override - public String toString() { - return range; - } - - /** - * Constructor to create a new CellRange from a WorkSheetHandle and a set of - * range coordinates:
    - * coords[0] = first row
    - * coords[1] = first col
    - * coords[2] = last row
    - * coords[3] = last col - * - * @param WorkSheetHandle - * sht - handle to the WorkSheet containing the Range's Cells - * @param int[] - * coords - the cell coordinates - * @param boolean - * cb - true if should create blank cells - * @throws Exception - **/ - public CellRange(WorkSheetHandle sht, int[] coords, boolean cb) throws Exception { - this.createBlanks = cb; - this.sheet = sht; - this.mybook = sht.wbh; - sheetname = sht.getSheetName(); - sheetname = GenericPtg.qualifySheetname(sheetname); - String addr = sheetname + "!"; - String c1 = ExcelTools.getAlphaVal(coords[1]) + String.valueOf(coords[0] + 1); - String c2 = ExcelTools.getAlphaVal(coords[3]) + String.valueOf(coords[2] + 1); - addr += c1 + ":" + c2; - this.range = addr; - this.init(); - } - - /** - * Set this CellRange to be the current Print Area - * - */ - public void setAsPrintArea() { - if (this.sheet == null) { - Logger.logErr("CellRange.setAsPrintArea() failed: " + this.toString() - + " does not have a valid Sheet reference."); - return; - } - sheet.setPrintArea(this); - } - - /** - * Constructor to create a new CellRange from a WorkSheetHandle and a set of - * range coordinates:
    - * coords[0] = first row
    - * coords[1] = first col
    - * coords[2] = last row
    - * coords[3] = last col - * - * @param WorkSheetHandle - * sht - handle to the WorkSheet containing the Range's Cells - * @param int[] - * coords - the cell coordinates - */ - public CellRange(WorkSheetHandle sht, int[] coords) throws Exception { - this(sht, coords, false); - } - - /** - * Constructor to Create a new CellRange from a String range
    - * The String range must be in the format Sheet!CR:CR
    - * For Example, "Sheet1!C9:I19"
    - * NOTE: You MUST Set the WorkBookHandle explicitly on this CellRange or it will - * generate NullPointerException when trying to access the Cells. - * - * - * @param String - * r - range String - * @see CellRange.setWorkBook - */ - public CellRange(String r) { - this.range = r; - } - - /** - * Increase the bounds of the CellRange by including the CellHandle.
    - * These are the limitations and side-effects of this method:
    - * - the Cell must be contiguous with the existing Range, ie: you can add a Cell - * which either increments the row or the column of the existing range by one. - *
    - * - the Cell must be on the same sheet as the existing range.
    - * - as a Cell Range is a 2 dimensional rectangle, expanding a multiple column - * range by adding a Cell to the end will include the logical Cells on the row - * in the range.
    - * Some Examples:
    - *
    - * // simple one dimensional range expansion:
    - * existing Range = A1:A20
    - * addCellToRange(A21) new Range = A1:A21
    - *
    - * existing Range = A1:B20
    - * addCellToRange(A21)
    - * new Range = A1:B21 // note B20 is included automatically
    - *
    - * existing Range = A1:A20
    - * addCellToRange(B1)
    - * new Range = A1:B20 //note entire B column of cells are included automatically - * - * @param CellHandle - * ch - the Cell to add to the CellRange - */ - public boolean addCellToRange(CellHandle ch) { - // check worksheet - String sheetname = ch.getWorkSheetName(); - if (sheetname == null) { - Logger.logWarn("Cell " + ch.toString() + " NOT added to Range: " + this.toString()); - return false; - } - if (!sheetname.equalsIgnoreCase(this.getSheet().getSheetName())) { - Logger.logWarn("Cell " + ch.toString() + " NOT added to Range: " + this.toString()); - return false; - } - int[] rc = { ch.getRowNum(), ch.getColNum() }; - // increment to one-based - rc[0]++; - - // check that it's at the beginning - if (firstcellrow == -1) - firstcellrow = rc[0]; - if (firstcellcol == -1) - firstcellcol = rc[1]; - if (lastcellrow == -1) - lastcellrow = rc[0]; - if (lastcellcol == -1) - lastcellcol = rc[1]; - if (rc[0] < firstcellrow) - firstcellrow--; - if (rc[1] < firstcellcol) - firstcellcol--; - // check that it's at the end - if (rc[0] > lastcellrow) - lastcellrow++; - if (rc[1] > lastcellcol) - lastcellcol++; - - // myptg is never set so myptg access never happens... taking out - // boolean addPtgInfo = false; - // if (myptg != null && myptg instanceof PtgRef) - // addPtgInfo = true; - // format the new range String - String newrange = this.getSheet().getSheetName() + "!"; - String newcellrange = ""; - // if (addPtgInfo && !((PtgRef) myptg).isColRel()) - // newcellrange += "$"; - newcellrange += ExcelTools.getAlphaVal(firstcellcol); - // if (addPtgInfo && !((PtgRef) myptg).isRowRel()) - // newcellrange += "$"; - newcellrange += String.valueOf(firstcellrow); - newcellrange += ":"; - // if (addPtgInfo && !((PtgRef) myptg).isColRel()) - // newcellrange += "$"; - newcellrange += ExcelTools.getAlphaVal(lastcellcol); - // if (addPtgInfo && !((PtgRef) myptg).isColRel()) - // newcellrange += "$"; - newcellrange += String.valueOf(lastcellrow); - this.range = newrange + newcellrange; - isDirty = true; - - /* - * if (addPtgInfo) { ReferenceTracker.updateAddressPerPolicy(myptg, - * newcellrange); return true; } - */ - - if (this.parent != null && this.parent.getOpcode() == XLSConstants.NAME) { - ((Name) parent).setLocation(this.range); // ensure named range expression is updated, as well as any formula - // references are cleared of cache - } - - return false; - } - - /** - * get the Cells in this cell range - * - * @return CellHandle[] all the Cells in this range - */ - public CellHandle[] getCells() { - if (isDirty) - try { - init(); - } catch (CellNotFoundException e) { - ; - } - return cells; - } - - /** - * Return a list of the cells in this cell range - * - * @return List of CellHandles - */ - public List getCellList() { - return Arrays.asList(cells); - } - - /** - * get the underlying Cell Records in this range
    - * NOTE: Cell Records are not a part of the public API and are not intended for - * use in client applications. - * - * @return BiffRec[] array of Cell Records - * - */ - public BiffRec[] getCellRecs() { - CellHandle[] ch = this.getCells(); - BiffRec[] ret = new BiffRec[ch.length]; - for (int t = 0; t < ret.length; t++) { - if (ch[t] != null) - ret[t] = ch[t].getCell(); - } - return ret; - } - - /* - * reset the underlying cell records in this range
    NOTE: Cell Records are - * not a part of the public API and are not intended for use in client - * applications. - * - * @return BiffRec[] array of Cell Records - * - * NOT USED AT THIS TIME public BiffRec[] resetCellRecs() { this.isDirty= true; - * return getCellRecs(); } - */ - - /** - * If the cells contain an incrementing value that can be transferred into an - * int, then return that value, else throw a NPE. I'm sure there is a better - * exception to be thrown, but not sure what that is, and it doesn't make sense - * to return a value like -1 in these cases. - */ - public int getIncrementAmount() throws Exception { - CellHandle[] ch = this.getCells(); - if (ch.length == 1) { - throw new Exception("Cannot have increment with non-range cell"); - } - boolean initialized = false; - int incAmount = 0; - for (int i = 1; i < ch.length; i++) { - int value1 = ch[i - 1].getIntVal(); - int value2 = ch[i].getIntVal(); - if (!initialized) { - incAmount = (value2 - value1); - initialized = true; - } else { - if (value2 - value1 != incAmount) { - throw new Exception("Inconsistent values across increment"); - } - } - } - if (!initialized) { - throw new Exception("Error determining increment"); - } - return incAmount; - } - - /** - * Constructor which creates a new CellRange from a String range
    - * The String range must be in the format Sheet!CR:CR
    - * For Example, "Sheet1!C9:I19" - * - * @param String - * range - the range string - * @param WorkBook - * bk - * @param boolean - * createblanks - true if blank cells should be created if necessary - * @param boolean - * initcells - true if cells should (be initialized) - */ - public CellRange(String range, io.starter.OpenXLS.WorkBook bk, boolean createblanks, boolean initcells) { - createBlanks = createblanks; - initializeCells = initcells; - this.range = range; - if (bk == null) - return; - this.setWorkBook(bk); - try { - this.init(); - } catch (CellNotFoundException e) { - ; - } - } - - /** - * Constructor which creates a new CellRange from a String range
    - * The String range must be in the format Sheet!CR:CR
    - * For Example, "Sheet1!C9:I19" - * - * @param String - * range - the range string - * @param WorkBook - * bk - * @param boolean - * createblanks - true if blank cells should be created (if - * necessary) - */ - public CellRange(String range, io.starter.OpenXLS.WorkBook bk, boolean c) { - createBlanks = c; - this.range = range; - if (bk == null) - return; - this.setWorkBook(bk); - try { - if (!"".equals(this.range)) - this.init(); - } catch (CellNotFoundException e) { - ; - } catch (NumberFormatException ne) { - ; // happens upon !REF range - } - } - - /** - * sets the parent of this Cell range
    - * Used Internally. Not intended for the End User. - * - * @param b - */ - public void setParent(BiffRec b) { - parent = b; - } - - /** - * Re-sort all cells in this cell range according to the column. - * - * A custom comparator can be passed in, or the default one can be used with - * sort(String, boolean). - * - * Comparators will be passed 2 CellHandle objects for comparison. - * - * Collections.reverse will be called on the results if ascending is set to - * false; - * - * @param rowNumber - * the 0 based (row 5 = 4) number of the row to be sorted upon - * @param comparator - * @throws RowNotFoundException - * @throws ColumnNotFoundException - */ - public void sort(int rownumber, Comparator comparator, boolean ascending) throws RowNotFoundException { - this.createBlanks(); - ArrayList sortRow = this.getCellsByRow(rownumber); - Collections.sort(sortRow, comparator); - if (!ascending) - Collections.reverse(sortRow); - // now we have sorted the array list, come up with a map to resort the rows. - int[] coords = null; - try { - coords = this.getRangeCoords(); - // fix stupid wrong offsets; - coords[0] = coords[0]--; - coords[2] = coords[2]--; - } catch (CellNotFoundException e1) { - } - ArrayList outputCols = new ArrayList(); - for (int i = 0; i < sortRow.size(); i++) { - CellHandle cell = sortRow.get(i); - ArrayList cells = null; - try { - cells = this.getCellsByCol(ExcelTools.getAlphaVal(cell.getColNum())); - } catch (ColumnNotFoundException e) { - // if there are no cells in this column ignore it - } - outputCols.add(cells); - } - this.removeCells(); - for (int i = coords[1]; i <= coords[3]; i++) { - ArrayList cells = outputCols.get(i - coords[1]); - for (int x = 0; x < cells.size(); x++) { - CellHandle cell = (CellHandle) cells.get(x); - Boundsheet bs = this.getSheet().getBoundsheet(); - cell.getCell().setCol((short) i); - bs.addCell((CellRec) cell.getCell()); - } - } - - } - - /** - * Changes the cellRange to a createBlanks cellrange and re-initializes the - * range, creating the missing blanks. - */ - private void createBlanks() { - if (!this.createBlanks) { - this.createBlanks = true; - this.initializeCells = true; - try { - this.init(); - } catch (CellNotFoundException e) { - } - } - } - - /** - * Resort all cells in the range according to the rownumber passed in. - * - * @param rownumber - * the 0 based row number - * @param ascending - * @throws RowNotFoundException - * @throws ColumnNotFoundException - */ - public void sort(int rownumber, boolean ascending) throws RowNotFoundException { - Comparator cp = new CellComparator(); - this.sort(rownumber, cp, ascending); - } - - /** - * Re-sort all cells in this cell range according to the column. - * - * A custom comparator can be passed in, or the default one can be used with - * sort(String, boolean). - * - * Comparators will be passed 2 CellHandle objects for comparison. - * - * * Collections.reverse will be called on the results if ascending is set to - * false; - * - * @param columnName - * @param comparator - * @throws RowNotFoundException - * @throws ColumnNotFoundException - */ - public void sort(String columnName, Comparator comparator, boolean ascending) throws ColumnNotFoundException { - if (!this.createBlanks) { - // we cannot have empty cells in this operation - this.createBlanks = true; - try { - this.init(); - } catch (CellNotFoundException e) { - } - } - ArrayList sortCol = this.getCellsByCol(columnName); - Collections.sort(sortCol, comparator); - if (!ascending) - Collections.reverse(sortCol); - // now we have sorted the array list, come up with a map to resort the rows. - int[] coords = null; - try { - coords = this.getRangeCoords(); - // fix stupid wrong offsets; - coords[0] = coords[0]--; - coords[2] = coords[2]--; - } catch (CellNotFoundException e1) { - e1.printStackTrace(); - } - ArrayList> outputRows = new ArrayList>(); - for (int i = 0; i < sortCol.size(); i++) { - CellHandle cell = (CellHandle) sortCol.get(i); - ArrayList cells = null; - try { - cells = this.getCellsByRow(cell.getRowNum()); - } catch (RowNotFoundException e) { - // ignore if no cells available - } - outputRows.add(cells); - } - this.removeCells(); - for (int i = coords[0]; i <= coords[2]; i++) { - ArrayList cells = outputRows.get(i - coords[0]); - for (int x = 0; x < cells.size(); x++) { - CellHandle cell = (CellHandle) cells.get(x); - Boundsheet bs = this.getSheet().getBoundsheet(); - cell.getCell().setRowNumber(i - 1); - bs.addCell((CellRec) cell.getCell()); - } - } - } - - /** - * Resort all cells in the range according to the column passed in. - * - * - * @param columnName - * @param ascending - * @throws ColumnNotFoundException - * @throws RowNotFoundException - */ - @SuppressWarnings("unchecked") - public void sort(String columnName, boolean ascending) throws ColumnNotFoundException { - Comparator cp = new CellComparator(); - this.sort(columnName, cp, ascending); - } - - public static String xmlResponsePre = ""; - public static String xmlResponsePost = ""; - - /** - * Return the XML representation of this CellRange object - * - * @return String of XML - */ - public String getXML() { - StringBuffer sb = new StringBuffer(""); - // StringBuffer sb = new StringBuffer(xmlResponsePre); - CellHandle[] cx = this.getCells(); - sb.append("\r\n"); - // append cellxml - for (int t = 0; t < cx.length; t++) { - sb.append(cx[t].getXML()); - sb.append("\r\n"); - } - sb.append(xmlResponsePost); - return sb.toString(); - } - - /** - * gets the array of Cells in this Name - * - * NOTE: this method variation also returns the Sheetname for the name record if - * not null. - * - * Thus this method is limited to use with 2D ranges. - * - * - * @return Cell[] all Cells defined in this Name - * @param fragment - * whether to enclose result in NameHandle tag - */ - public String getCellRangeXML(boolean fragment) { - StringBuffer sbx = new StringBuffer(); - if (!fragment) - sbx.append(""); - sbx.append(""); - sbx.append(getXML()); - sbx.append(""); - return sbx.toString(); - } - - /** - * gets the array of Cells in this Name - * - * Thus this method is limited to use with 2D ranges. - * - * @return Cell[] all Cells defined in this Name - * @param fragment - * whether to enclose result in NameHandle tag - */ - public String getCellRangeXML() { - StringBuffer sbx = new StringBuffer(); - sbx.append(""); - sbx.append(getXML()); - return sbx.toString(); - } - - /** - * Constructor which creates a new CellRange using an array of cells as it's - * constructor.
    - * NOTE that if the array of cells you are adding is not a rectangle of data (ie - * [A1][B1][C1]) that you will have null cells in your cell range and operations - * on it may cause errors.
    - * If you wish to populate a cell range that is not contiguous, consider the - * constructor CellRange(CellHandle[] newcells, boolean createblanks), which - * will populate null cells with blank records, allowing normal operations such - * as formatting, merging, etc. - * - * @param CellHandle[] - * newcells - the array of cells from which to create the new - * CellRange - * @throws CellNotFoundException - */ - public CellRange(CellHandle[] newcells) throws CellNotFoundException { - this.setWorkBook(newcells[0].getWorkBook()); - this.sheet = newcells[0].getWorkSheetHandle(); - for (int x = 0; x < newcells.length; x++) { - this.addCellToRange(newcells[x]); - } - this.init(); - } - - /** - * create a new CellRange using an array of cells as it's constructor.
    - * If you wish to populate a cell range that is not contiguous, set createblanks - * to true, which will populate null cells with blank records, allowing normal - * operations such as formatting, merging, etc. - * - * @param CellHandle[] - * newcells - the array of cells from which to create the new - * CellRange - * @param boolean - * createblanks - true if should create blank cells if necesary - */ - public CellRange(CellHandle[] newcells, boolean createblanks) throws CellNotFoundException { - this.createBlanks = createblanks; - this.setWorkBook(newcells[0].getWorkBook()); - this.sheet = newcells[0].getWorkSheetHandle(); - for (int x = 0; x < newcells.length; x++) { - this.addCellToRange(newcells[x]); - } - this.init(); - } - - /** - * Constructor which creates a new CellRange from a String range
    - * The String range must be in the format Sheet!CR:CR
    - * For Example, "Sheet1!C9:I19" - * - * @param String - * range - the range string - * @param WorkBook - * bk - * @throws CellNotFoundException - */ - public CellRange(String range, io.starter.OpenXLS.WorkBook bk) throws CellNotFoundException { - this(range, bk, true); - } - - /** - * attach the workbook for this CellRange - * - * @param WorkBook - * bk - */ - public void setWorkBook(io.starter.OpenXLS.WorkBook bk) { - this.mybook = bk; - } - - /** - * Gets the coordinates of this cell range, - * - * @return int[5]: [0] first row (zero based, ie row 1=0), [1] first column, [2] - * last row (zero based, ie row 1=0), [3] last column, [4] number of - * cells in range - */ - public int[] getCoords() throws CellNotFoundException { - int numrows = 0; - int numcols = 0; - int numcells = 0; - int[] coords = new int[5]; - String temprange = range; - String[] s = ExcelTools.stripSheetNameFromRange(temprange); - temprange = s[1]; - // qualify sheet and reset range - necessary if sheetname with spaces is used in - // formula parsing - sheetname = GenericPtg.qualifySheetname(s[0]); - if (sheetname != null && !sheetname.equals("")) { - if (s[2] == null) - this.range = sheetname + "!" + temprange; - else { - s[2] = GenericPtg.qualifySheetname(s[2]); - this.range = sheetname + ":" + s[2] + "!" + temprange; - } - } - - /* - * check for R1C1 - */ - if ((temprange.indexOf("R") == 0) && (temprange.indexOf("C") > 1) - && (Character.isDigit(temprange.charAt(temprange.indexOf("C") - 1)))) { - - int[] b = ExcelTools.getRangeRowCol(temprange); - - numrows = (b[2] - b[0]); - if (numrows <= 0) - numrows = 1; - - numcols = (b[3] - b[2]); - if (numcols <= 0) - numcols = 1; - - numcells = numrows * numcols; - - int[] retr = new int[5]; - retr[0] = b[0]; - retr[1] = b[1]; - retr[2] = b[2]; - retr[3] = b[3]; - retr[4] = numcells; - return retr; - } - String startcell = "", endcell = ""; - int lastcolon = temprange.lastIndexOf(":"); - endcell = temprange.substring(lastcolon + 1); - if (lastcolon == -1) // no range - startcell = endcell; - else - startcell = temprange.substring(0, lastcolon); - - startcell = StringTool.strip(startcell, "$"); - endcell = StringTool.strip(endcell, "$"); - - // get the first cell's coordinates - int charct = startcell.length(); - while (charct > 0) { - if (!Character.isDigit(startcell.charAt(--charct))) { - charct++; - break; - } - } - String firstcellrowstr = startcell.substring(charct); - firstcellrow = Integer.parseInt(firstcellrowstr); - String firstcellcolstr = startcell.substring(0, charct); - firstcellcol = ExcelTools.getIntVal(firstcellcolstr); - // get the last cell's coordinates - charct = endcell.length(); - while (charct > 0) { - if (!Character.isDigit(endcell.charAt(--charct))) { - charct++; - break; - } - } - String lastcellrowstr = endcell.substring(charct); - lastcellrow = Integer.parseInt(lastcellrowstr); - String lastcellcolstr = endcell.substring(0, charct); - lastcellcol = ExcelTools.getIntVal(lastcellcolstr); - numrows = (lastcellrow - firstcellrow) + 1; - numcols = (lastcellcol - firstcellcol) + 1; - - numcells = numrows * numcols; - if (numcells < 0) - numcells *= -1; // handle swapped cells ie: "B1:A1" - - coords[0] = firstcellrow - 1; - coords[1] = firstcellcol; - coords[2] = lastcellrow - 1; - coords[3] = lastcellcol; - coords[4] = numcells; - if (firstcellrow < 0 && lastcellrow < 0 || firstcellcol < 0 || lastcellcol < 0) { - // not an error if it is a whole column or whole row range - if (firstcellcol == -1 && lastcellcol == -1) { - // what should numcells be for wholerow? - wholeRow = true; - } else if (firstcellrow == -1 && lastcellrow == -1) { - // what should numcells be for wholecol? - wholeCol = true; - } else - Logger.logErr("CellRange.getRangeCoords: Error in Range " + range); - } - - // trap OOXML external reference link, if any - if (s[3] != null) - externalLink1 = Integer.valueOf(s[3].substring(1, s[3].length() - 1)).intValue(); - if (s[4] != null) - externalLink2 = Integer.valueOf(s[4].substring(1, s[4].length() - 1)).intValue(); - - return coords; - - } - - /** - * Gets the coordinates of this cell range. - * - * @return int[5]: [0] first row, [1] first column, [2] last row, [3] last - * column, [4] number of cells in range - * - * @deprecated {@link #getCoords()} instead, which returns zero based values for - * rows. - */ - @Deprecated - public int[] getRangeCoords() throws CellNotFoundException { - int[] ordinalValues = this.getCoords(); - ordinalValues[0] += 1; - ordinalValues[2] += 1; - return ordinalValues; - } - - /** - * Returns the WorkSheet referenced in this CellRange. - * - * @return WorkSheetHandle sheet referenced in this CellRange. - */ - public WorkSheetHandle getSheet() { - return sheet; - } - - /** - * initializes this CellRange - * - * @throws CellNotFoundException - */ - public void init() throws CellNotFoundException { - if (!FormulaParser.isComplexRange(range)) { - int[] coords = this.getRangeCoords(); - int rowctr = coords[0]; - int firstcellcol = coords[1]; - int lastcellcol = coords[3]; - int numcells = coords[4]; - - int cellctr = firstcellcol - 1; - try { - if (sheetname != null) { - if (sheetname.equals("")) // 20080214 KSC - is this a good idea? - // default to work sheet 0 - sheetname = this.mybook.getWorkSheet(0).getSheetName(); - } - if (sheetname == null) { - if (this.sheet != null) - sheetname = this.sheet.getSheetName(); - else - throw new IllegalArgumentException("sheet name not specified: " + range); - } - - String s = sheetname; - if (s != null) { - // handle enclosing apostrophes which are added to PtgRefs - if (s.charAt(0) == '\'') { - s = s.substring(1, s.length()); - if (s.charAt(s.length() - 1) == '\'') { - s = s.substring(0, s.length() - 1); - } - } - } - sheet = mybook.getWorkSheet(s); - // if wholerow or wholecol, don't gather cells - if (this.wholeCol || this.wholeRow) - return; - cells = new CellHandle[numcells]; - boolean resetFastAdds = false; - if (sheet.getFastCellAdds() && this.createBlanks) { - resetFastAdds = true; - sheet.setFastCellAdds(false); - } - for (int i = 0; i < numcells; i++) { - if (cellctr == lastcellcol) {// if its the end of the row, - // increment row. - cellctr = firstcellcol - 1; - rowctr++; - } - ++cellctr; - try { - // use caching 20080917 KSC: PROBLEM HERE [Claritas - // BugTracker 1862] - if (this.initializeCells) - cells[i] = sheet.getCell(rowctr - 1, cellctr, sheet.getUseCache()); // 20080917 KSC: use - // cache - // setting instead of - // defaulting to true); - } catch (CellNotFoundException e) { - if (this.createBlanks) { - cells[i] = sheet.add(null, rowctr - 1, cellctr); - } - - } - } - if (resetFastAdds) { - sheet.setFastCellAdds(true); - } - } catch (WorkSheetNotFoundException e) { - throw new IllegalArgumentException(e.toString()); - } - } else { // gather cells for a complex range ... - io.starter.formats.XLS.formulas.PtgMemFunc pm = new io.starter.formats.XLS.formulas.PtgMemFunc(); - XLSRecord b = new XLSRecord(); - b.setWorkBook(this.mybook.getWorkBook()); - pm.setParentRec(b); - try { - pm.setLocation(range); - Ptg[] p = pm.getComponents(); - java.util.ArrayList cellsfromcomplexrange = new java.util.ArrayList(); - for (int i = 0; i < p.length; i++) { - try { - cellsfromcomplexrange.add(mybook.getCell(((PtgRef) p[i]).getLocation())); - } catch (CellNotFoundException e) { - if (this.createBlanks) { - cells[i] = sheet.add(null, p[i].getLocation()); - } - } - } - cells = new CellHandle[cellsfromcomplexrange.size()]; - cells = cellsfromcomplexrange.toArray(cells); - } catch (Exception e) { - throw new IllegalArgumentException(e.toString()); - } - - } - isDirty = false; - } - - /** - * Initializes this CellRange's cell list if necessary. This method - * is useful if this CellRange was created with - * initCells set to false and it is later necessary to - * retrieve the cell list. - * - * @param createBlanks - * whether missing cells should be created as blanks. If this is - * false they will appear in the cell list as - * nulls. - */ - public void initCells(boolean createBlanks) { - // If we don't need to do anything, return - if (initializeCells == true && (createBlanks ? this.createBlanks : true)) - return; - - this.initializeCells = true; - this.createBlanks = createBlanks; - - try { - this.init(); - } catch (CellNotFoundException e) { - // This will never actually happen but we have to catch it anyway - throw new Error(); - } - } - - /** - * @return the workbook object attached to this CellRange - */ - public WorkBook getWorkBook() { - return mybook; - } - - /** - * gets whether this CellRange will add blank records to the WorkBook for any - * missing Cells contained within the range. - * - * @return true if should create blank records for missing Cells - */ - public boolean getCreateBlanks() { - return createBlanks; - } - - /** - * set whether this CellRange will add blank records to the WorkBook for any - * missing Cells contained within the range. - * - * @param boolean - * b - true if should create blank records for missing Cells - */ - public void setCreateBlanks(boolean b) { - createBlanks = b; - } - - /** - * Return the String representation of this range - * - * @return the String range - */ - public String getRange() { - return range; - } - - /** - * Return the String cell address of this range in R1C1 format - * - * @return String range in R1C1 format - */ - public String getR1C1Range() { - String rc1x = "R"; - try { - int[] rc1 = this.getRangeCoords(); - rc1x += rc1[0] + 1; // rangecoords are already 1-based - rc1x += "C" + rc1[1]; - rc1x += ":R" + (rc1[2] + 1); - rc1x += "C" + rc1[3]; - - } catch (CellNotFoundException e) { - Logger.logErr("CellRange.getR1C1Range failed", e); - } - return rc1x; - } - - /** - * Sets the range of cells for this CellRange to a string range - * - * @param String - * rng - Range string - */ - public void setRange(String rng) { - range = rng; - try { - this.init(); - } catch (CellNotFoundException e) { - ; // don't have to report anything - } - } - - /** - * sets a border around the range of cells - * - * @param int - * width - line width - * @param int - * linestyle - line style - * @param java.awt.Color - * colr - color of border line - */ - public void setBorder(int width, int linestyle, java.awt.Color colr) { - CellHandle[] ch = getCells(); - for (int t = 0; t < ch.length; t++) { - int[] coords = getEdgePositions(ch[t], width); - // create Excel border -- top, left, bottom, right - if (coords[0] > 0) { - ch[t].setTopBorderLineStyle((short) linestyle); - ch[t].setBorderTopColor(colr); - } - if (coords[1] > 0) { - ch[t].setLeftBorderLineStyle((short) linestyle); - ch[t].setBorderLeftColor(colr); - } - if (coords[2] > 0) { - ch[t].setBottomBorderLineStyle((short) linestyle); - ch[t].setBorderBottomColor(colr); - } - if (coords[3] > 0) { - ch[t].setRightBorderLineStyle((short) linestyle); - ch[t].setBorderRightColor(colr); - } - } - } - - /** - * update the CellRange when the underlying Cells change their location - * - * @return boolean true if the CellRange could be updated, false if there are no - * cells represented by this range - * - * - */ - public boolean update() { - if (cells == null) - return false; // this is an invalid range -- see Mergedcells prob. - // jm - - // arbitrarily set the initial vals... - if (cells[0] != null) { // 20100106 KSC: if didn't create blanks it's possible that cells are null - firstcellrow = cells[0].getRowNum() + 1; - firstcellcol = cells[0].getColNum(); - lastcellrow = cells[0].getRowNum() + 1; - lastcellcol = cells[0].getColNum(); - for (int t = 0; t < cells.length; t++) { - CellHandle cx = cells[t]; - // 20090901 KSC: apparently can be null - if (cx != null) - this.addCellToRange(cx); - } - this.myrowints = null; - this.mycolints = null; - return true; - } else if (this.range != null) {// 20100106 KSC: handle ranges containing null cell[0] (i.e. ranges referencing - // cells not present and createBlanks==false) - if (this.DEBUG) - Logger.logWarn("CellRange.update: trying to access blank cells in range " + this.range); - try { - this.getRangeCoords(); - return true; - } catch (CellNotFoundException e) { // shouldn't - return false; - } - } - return false; // return false if it doesn't have it's cells defined - } - - /** - * returns the merged state of the CellRange - * - * @return true if this CellRange is merged - */ - public boolean isMerged() { - return ismerged; - } - - /** - * Sets the sheet reference for this CellRange. - * - * @param WorkSheetHandle - * aSheet - */ - public void setSheet(WorkSheetHandle aSheet) { - this.sheet = aSheet; - this.sheetname = aSheet.getSheetName(); - } - - /** - * Whether to copy the cell contents. - * - */ - public static final int COPY_CONTENTS = 0x01; - - /** - * Whether formulas should be copied. If this bit is not set the formula result - * will be copied instead. - */ - public static final int COPY_FORMULAS = 0x02; - - public static final int COPY_FORMATS = 0x0100; - - /** - * Copies this range to another location. At present only contents and complete - * formats may be copied. - * - * @param row - * the topmost row of the target area - * @param col - * the leftmost column of the target area - * @param what - * a set of flags determining what will be copied - * @return the destination range - */ - public CellRange copy(WorkSheetHandle sheet, int row, int col, int what) { - CellRange result = new CellRange(sheet, row, col, this.getWidth(), this.getHeight()); - - int first_col = col; - - // note these are not currently used, see setting below - boolean copy_contents = (what & COPY_CONTENTS) != 0; - boolean copy_formulas = (what & COPY_FORMULAS) != 0; - boolean copy_formats = (what & COPY_FORMATS) != 0; - - // set to true until this thing is fully implemented - copy_formats = true; - copy_formulas = true; - - int cur_row = cells[0].getRowNum(); - for (int idx = 0; idx < cells.length; idx++) { - CellHandle source = cells[idx]; - - if (source.getRowNum() != cur_row) { - cur_row = source.getRowNum(); - row++; - col = first_col; - } - - CellHandle target = null; - try { - target = sheet.getCell(row, col); - } catch (CellNotFoundException e) { - } - - int formatID; - if (copy_formats) - formatID = source.getFormatId(); - else if (target != null) - formatID = target.getFormatId(); - else - formatID = sheet.getWorkBook().getWorkBook().getDefaultIxfe(); - - if (copy_contents) { - Object value; - - if (copy_formulas && source.isFormula()) { - try { - value = source.getFormulaHandle().getFormulaString(); - } catch (FormulaNotFoundException e) { - // This shouldn't happen; we known the formula exists. - // If it does happen it indicates a bug in OpenXLS, - // thus we throw an Error. - throw new Error("formula cell has no Formula record", e); - } - } else { - value = source.getVal(); - } - - target = sheet.add(value, row, col, formatID); - - if (target.isFormula()) - try { - FormulaHandle.moveCellRefs(target.getFormulaHandle(), - new int[] { row - source.getRowNum(), col - source.getColNum() }); - } catch (FormulaNotFoundException e) { - } - } - - else if (target == null) { - target = sheet.add(null, row, col, formatID); - } - - if (copy_formats) { - target.setFormatId(formatID); - } - - result.cells[idx] = target; - col++; - } - - return result; - } - - /** - * Fills this range from the given cell. - * - * @param source - * the cell whose attributes should be copied or null to - * copy from the first cell in the range - * @param what - * a set of flags determining what will be copied - * @param increment - * the amount by which to increment numeric values or - * NaN for no increment - */ - public void fill(CellHandle source, int what, double increment) { - if (null == source) - source = cells[0]; - - boolean copy_contents = (what & COPY_CONTENTS) != 0; - boolean copy_formulas = (what & COPY_FORMULAS) != 0; - boolean copy_formats = (what & COPY_FORMATS) != 0; - - int sourceRow = source.getRowNum(); - int sourceCol = source.getColNum(); - - Object value = null; - if (copy_contents) { - if (copy_formulas && source.isFormula()) { - try { - value = source.getFormulaHandle().getFormulaString(); - } catch (FormulaNotFoundException e) { - throw new Error("formula cell has no Formula record", e); - } - } else { - value = source.getVal(); - } - } - - // if increment is set, ensure the value can be incremented - if (!Double.isNaN(increment) && !(copy_contents && value instanceof Number)) - throw new IllegalArgumentException("cannot increment unless filling with a numeric value"); - - for (int idx = 0; idx < cells.length; idx++) { - CellHandle target = cells[idx]; - - // don't overwrite the source cell - if (source.equals(target)) - continue; - - if (!Double.isNaN(increment)) - value = ((Number) value).doubleValue() + increment; - - int formatID = (copy_formats ? source : target).getFormatId(); - - if (copy_contents) { - cells[idx] = target = sheet.add(value, target.getRowNum(), target.getColNum(), formatID); - - if (target.isFormula()) - try { - FormulaHandle.moveCellRefs(target.getFormulaHandle(), - new int[] { target.getRowNum() - sourceRow, target.getColNum() - sourceCol }); - } catch (FormulaNotFoundException e) { - } - } - - // when adding Date values passing the format ID to sheet.add - // doesn't set the format so we always set it here - if (copy_formats) { - target.setFormatId(formatID); - } - - } - } - - public Collection calculateAffectedCellsOnSheet() { - Set affected = new HashSet(); - for (CellHandle cell : cells) { - if (cell != null) { - affected.add(cell); - affected.addAll(cell.calculateAffectedCellsOnSheet()); - } - } - return affected; - } - - /** - * return a JSON array of cell values for the given range
    - * static version - * - * @param String - * range - a string representation of the desired range of cells - * @param WorkBook - * wbh - the source WorkBook for the cell range - * @return JSONArray - a JSON representation of the desired cell range - */ - public static JSONArray getValuesAsJSON(String range, WorkBook wbh) { - JSONArray rangeArray = new JSONArray(); - try { - CellRange cr = new CellRange(range, wbh, true); - for (int j = 0; j < cr.getCells().length; j++) - rangeArray.put(cr.getCells()[j].getVal()); - } catch (Exception e) { - Logger.logErr("Error obtaining CellRange " + range + " JSON: " + e); - } - return rangeArray; - } - - /** - * Return a json object representing this cell range, entries contain only - * address and values for more compact space - * - * @param range - * @param wbh - * @return - */ - public JSONObject getBasicJSON() { - try { - JSONObject crObj = new JSONObject(); - crObj.put(JSON_RANGE, this.getRange()); - JSONArray rangeArray = new JSONArray(); - // should this possibly be full - CellHandle[] cells = this.getCells(); - for (int j = 0; j < cells.length; j++) { - JSONObject result = new JSONObject(); - String addy = cells[j].getCellAddress(); - String val = cells[j].getVal().toString(); - result.put(JSON_LOCATION, addy); - result.put(JSON_CELL_VALUE, val); - rangeArray.put(result); - } - crObj.put(JSON_CELLS, rangeArray); - return crObj; - } catch (Exception e) { - Logger.logErr("Error obtaining CellRange " + range + " JSON: " + e); - } - return new JSONObject(); - } - - /** - * Return a json object representing this cell range with full cell information - * embedded. - */ - public JSONObject getJSON() { - JSONObject theRange = new JSONObject(); - JSONArray cells = new JSONArray(); - try { - theRange.put(JSON_RANGE, getRange()); - CellHandle[] chandles = getCells(); - for (int i = 0; i < chandles.length; i++) { - CellHandle thisCell = chandles[i]; - JSONObject result = new JSONObject(); - - result.put(JSON_CELL, thisCell.getJSONObject()); - cells.put(result); - } - theRange.put(JSON_CELLS, cells); - } catch (JSONException e) { - Logger.logErr("Error getting cellRange JSON: " + e); - } - return theRange; - } - - /** - * Get the cells from a particular rownumber, constrained by the boundaries of - * the cellRange - * - * @param rownumber - */ - public ArrayList getCellsByRow(int rownumber) throws RowNotFoundException { - ArrayList al = new ArrayList(); - RowHandle r = this.getSheet().getRow(rownumber); - CellHandle[] cells = r.getCells(); - int[] coords = null; - try { - coords = this.getRangeCoords(); - } catch (CellNotFoundException e) { - throw new RowNotFoundException("Error getting internal coordinates for CellRange" + e); - } - for (int i = 0; i < cells.length; i++) { - if (cells[i].getColNum() >= coords[1] && cells[i].getColNum() <= coords[3]) { - al.add(cells[i]); - } - } - return al; - } - - /** - * Get the cells from a particular column, constrained by the boundaries of the - * cellRange - * - * @param rownumber - * @throws ColumnNotFoundException - */ - public ArrayList getCellsByCol(String col) throws ColumnNotFoundException { - ArrayList al = new ArrayList(); - ColHandle r = this.getSheet().getCol(col); - CellHandle[] cells = r.getCells(); - int[] coords = null; - try { - coords = this.getRangeCoords(); - coords[0] = coords[0] - 1; - coords[2] = coords[2] - 1; - } catch (CellNotFoundException e) { - throw new ColumnNotFoundException("Error getting internal coordinates for CellRange" + e); - } - for (int i = 0; i < cells.length; i++) { - if (cells[i].getRowNum() >= coords[0] && cells[i].getRowNum() <= coords[2]) { - al.add(cells[i]); - } - } - return al; - } - - /** - * returns the cells for a given range
    - * static version - * - * @param String - * range - a string representation of the desired range of cells - * @param WorkBook - * wbh - the source WorkBook for the cell range - * @return CellHandle[] array of cells represented by the desired cell range - */ - public static CellHandle[] getCells(String range, WorkBookHandle wbh) { - CellRange cr = new CellRange(range, wbh, true); - return cr.getCells(); - } - - /** - * removes the border from all of the cells in this range - * - */ - public void removeBorder() { - CellHandle[] ch = getCells(); - for (int t = 0; t < ch.length; t++) { - ch[t].removeBorder(); - } - } - - /** - * Sets a bottom border on all cells in the cellrange - * - * Linestyle should be set through the FormatHandle constants - */ - public void setInnerBorderBottom(int linestyle, java.awt.Color colr) { - CellHandle[] ch = getCells(); - for (int t = 0; t < ch.length; t++) { - ch[t].setBottomBorderLineStyle((short) linestyle); - ch[t].setBorderBottomColor(colr); - } - } - - /** - * Sets a right border on all cells in the cellrange - * - * Linestyle should be set through the FormatHandle constants - */ - public void setInnerBorderRight(int linestyle, java.awt.Color colr) { - CellHandle[] ch = getCells(); - for (int t = 0; t < ch.length; t++) { - ch[t].setRightBorderLineStyle((short) linestyle); - ch[t].setBorderRightColor(colr); - } - } - - /** - * Sets a left border on all cells in the cellrange - * - * Linestyle should be set through the FormatHandle constants - */ - public void setInnerBorderLeft(int linestyle, java.awt.Color colr) { - CellHandle[] ch = getCells(); - for (int t = 0; t < ch.length; t++) { - ch[t].setLeftBorderLineStyle((short) linestyle); - ch[t].setBorderLeftColor(colr); - } - } - - /** - * Sets a top border on all cells in the cellrange - * - * Linestyle should be set through the FormatHandle constants - */ - public void setInnerBorderTop(int linestyle, java.awt.Color colr) { - CellHandle[] ch = getCells(); - for (int t = 0; t < ch.length; t++) { - ch[t].setTopBorderLineStyle((short) linestyle); - ch[t].setBorderTopColor(colr); - } - } - - /** - * Sets a surround border on all cells in the cellrange - * - * Linestyle should be set through the FormatHandle constants - */ - public void setInnerBorderSurround(int linestyle, java.awt.Color colr) { - CellHandle[] ch = getCells(); - for (int t = 0; t < ch.length; t++) { - ch[t].setBorderColor(colr); - ch[t].setBorderLineStyle((short) linestyle); - } - } + Condfmt cfx = null; + + /** + * returns the conditional format object for this range, if any + * + * @return Condfmt object + */ + protected Condfmt getConditionalFormat() { + return cfx; + } + + /** + * + */ + private static final long serialVersionUID = -3609881364824289079L; + private boolean ismerged = false; + private BiffRec parent = null; // if cell range is child of a named range, must ensure update correctly + public static final boolean REMOVE_MERGED_CELLS = true; + public static final boolean RETAIN_MERGED_CELLS = false; + // private Ptg myptg = null; + public boolean DEBUG = false; + private boolean isDirty = false; // true if addCellsToRange without init + int firstcellrow = -1; + int firstcellcol = -1; + int lastcellrow = -1; + int lastcellcol = -1; + private boolean createBlanks = false; + private boolean initializeCells = true; + public transient CellHandle[] cells; + protected transient String range, sheetname; + protected transient io.starter.OpenXLS.WorkBook mybook; + private transient WorkSheetHandle sheet = null; + private int[] myrowints; + private int[] mycolints; + private transient RowHandle[] myrows; + private transient ColHandle[] mycols; + + // for OOXML External References + protected int externalLink1 = 0; + protected int externalLink2 = 0; + + FormatHandle fmtr = null; + boolean wholeCol = false, wholeRow = false; + + /** + * Protected constructor for creating result ranges. + */ + protected CellRange(WorkSheetHandle sheet, int row, int col, int width, int height) { + this.sheet = sheet; + sheetname = sheet.getSheetName(); + mybook = sheet.getWorkBook(); + + firstcellrow = row; + firstcellcol = col; + lastcellrow = row + height - 1; + lastcellcol = col + width - 1; + + range = sheetname + "!" + + ExcelTools.formatRange(new int[]{firstcellcol, firstcellrow, lastcellcol, lastcellrow}); + + cells = new CellHandle[width * height]; + } + + /** + * Initializes a CellRange from a CellRangeRef. The + * source CellRangeRef instance must be qualified with a single + * resolved worksheet. + * + * @param source the CellRangeRef from which to initialize + * @param init whether to populate the cell array + * @param create whether to fill gaps in the range with blank cells + * @throws IllegalArgumentException if the source range does not have a resolved sheet or has more + * than one sheet + */ + public CellRange(CellRangeRef source, boolean init, boolean create) { + initializeCells = init; + createBlanks = create; + + sheet = source.getFirstSheet(); + if (sheet == null || source.isMultiSheet()) + throw new IllegalArgumentException("the source range must have a single resolved sheet"); + + mybook = this.sheet.getWorkBook(); + sheetname = this.sheet.getSheetName(); + + // This is inefficient, but fixing it would require rewriting init. + this.range = source.toString(); + + try { + this.init(); + } catch (CellNotFoundException e) { + // this should be impossible + throw new RuntimeException(e); + } + } + + /** + * Initializes a CellRange from a CellRangeRef. The + * source CellRangeRef instance must be qualified with a single + * resolved worksheet. + * + * @param source the CellRangeRef from which to initialize + * @throws IllegalArgumentException if the source range does not have a resolved sheet or has more + * than one sheet + */ + public CellRange(CellRangeRef source) { + this(source, false, true); + } + + public void clearFormats() { + for (int idx = 0; idx < cells.length; idx++) + if (cells[idx] != null) + cells[idx].clearFormats(); + } + + /** + * @deprecated use clear() + */ + @Deprecated + public void clearContents() { + for (int idx = 0; idx < cells.length; idx++) + if (cells[idx] != null) + cells[idx].clearContents(); + } + + /** + * clears the contents and formats of the cells referenced by this range but + * does not remove the cells from the workbook. + */ + public void clear() { + for (int idx = 0; idx < cells.length; idx++) + if (cells[idx] != null) + cells[idx].clear(); + } + + /** + * removes the cells referenced by this range from the sheet. + *

    + * NOTE: method does not shift rows or cols. + */ + public void removeCells() { + for (int idx = 0; idx < cells.length; idx++) + if (cells[idx] != null) + cells[idx].remove(true); + } + + /** + * Un-Merge the Cells contained in this CellRange + * + * @throws Exception + */ + public void unMergeCells() throws Exception { + BiffRec[] mycells = this.getCellRecs(); + for (int t = 0; t < mycells.length; t++) { + mycells[t].setMergeRange(null); // unset the range of merged cells + mycells[t].getXfRec().setMerged(false); + } + Mergedcells mc = this.getSheet().getSheet().getMergedCellsRec(); + if (mc != null) + mc.removeCellRange(this); + this.ismerged = false; + } + + /** + * Set the format ID of all cells in this CellRange
    + * FormatID can be obtained through any CellHandle with the getFormatID() call + * + * @param int fmtID - the format ID to set the cells within the range to + */ + public void setFormatID(int fmtID) throws Exception { + BiffRec[] mycells = this.getCellRecs(); + for (int t = 0; t < mycells.length; t++) { + mycells[t].setXFRecord(fmtID); + } + } + + /** + * Set a hyperlink on all cells in this CellRange + * + * @param String url - the URL String to set + */ + public void setURL(String url) throws Exception { + BiffRec[] mycells = this.getCellRecs(); + for (int t = 0; t < mycells.length; t++) { + new CellHandle(mycells[t], this.mybook).setURL(url); + } + } + + /** + * Merge the Cells contained in this CellRange + * + * @param boolean remove - true to delete the Cells following the first in the range + */ + public void mergeCells(boolean remove) { + this.createBlanks(); + if (remove) + this.mergeCellsClearFollowingCells(); + else + this.mergeCellsKeepFollowingCells(); + } + + /** + * Merge the Cells contained in this CellRange, clearing or removing the Cells + * following the first in the range + */ + private void mergeCellsClearFollowingCells() { + BiffRec[] mycells = this.getCellRecs(); + + // mycells[0].setMergeRange(this); // set the range of merged cells + Xf r = mycells[0].getXfRec(); + if (r == null) { + fmtr = new FormatHandle(this.getWorkBook()); + fmtr.addCell(mycells[0]); + r = mycells[0].getXfRec(); + } + r.setMerged(true); + for (int t = 0; t < mycells.length; t++) { + if (mycells[t] != null) + mycells[t].setSheet(this.getSheet().getMysheet()); + mycells[t].setMergeRange(this); // set the range of merged cells + if (t > 0) { + if (!(mycells[t] instanceof Blank)) { + String cellname = mycells[t].getCellAddress(); + Boundsheet sheet = mycells[t].getSheet(); + mycells[t].remove(true); // blow it out! + sheet.addValue(null, cellname); + } + } + } + Mergedcells mc = this.getSheet().getSheet().getMergedCellsRec(); + if (mc == null) + mc = this.getSheet().getSheet().addMergedCellRec(); + mc.addCellRange(this); + this.ismerged = true; + } + + /** + * Merge the Cells contained in this CellRange * + */ + private void mergeCellsKeepFollowingCells() { + BiffRec[] mycells = this.getCellRecs(); + for (int t = 0; t < mycells.length; t++) { + mycells[t].setMergeRange(this); // set the range of merged cells + Xf r = mycells[t].getXfRec(); + if (r == null) { + fmtr = new FormatHandle(this.getWorkBook()); + fmtr.addCellRange(this); + r = mycells[t].getXfRec(); + } + r.setMerged(true); + } + + Mergedcells mc = this.getSheet().getSheet().getMergedCellsRec(); + if (mc == null) + mc = this.getSheet().getSheet().addMergedCellRec(); + mc.addCellRange(this); + this.ismerged = true; + } + + /** + * Gets the number of columns in the range. + */ + public int getWidth() { + return lastcellcol - firstcellcol + 1; + } + + /** + * Gets the number of rows in the range. + */ + public int getHeight() { + return lastcellrow - firstcellrow + 1; + } + + /** + * Returns an array of the row numbers referenced by this CellRange + * + * @return int[] array of row ints + */ + public int[] getRowInts() { + if (myrowints != null) + return myrowints; + int numrows = (lastcellrow + 1) - firstcellrow; + myrowints = new int[numrows]; + for (int t = 0; t < numrows; t++) { + myrowints[t] = firstcellrow + t; + } + return myrowints; + } + + /** + * returns an array of column numbers referenced by this CellRange + * + * @return int[] array of col ints + */ + public int[] getColInts() { + if (mycolints != null) + return mycolints; + int numcols = (lastcellcol + 1) - firstcellcol; + mycolints = new int[numcols]; + for (int t = 0; t < numcols; t++) { + mycolints[t] = firstcellcol + t; + } + return mycolints; + } + + /** + * Returns an array of Rows (RowHandles) referenced by this CellRange + * + * @return RowHandle[] array of row handles + */ + public RowHandle[] getRows() throws RowNotFoundException { + if (myrows != null) + return myrows; + int numrows = (lastcellrow + 1) - firstcellrow; + myrows = new RowHandle[numrows]; + for (int t = 0; t < numrows; t++) { + RowHandle rx = null; + try { + rx = sheet.getRow((firstcellrow - 1) + t); + } catch (Exception x) { + // typically empty rows + } + myrows[t] = rx; + } + return myrows; + } + + /** + * Get the number of rows that this CellRange encompasses + * + * @return + */ + public int getNumberOfRows() { + int numRows = (lastcellrow + 1) - firstcellrow; + return numRows; + } + + /** + * Returns an array of Columns (ColHandles) referenced by this CellRange + * + * @return ColHandle[] array of columns handles + */ + public ColHandle[] getCols() throws ColumnNotFoundException { + if (mycols != null) + return mycols; + int numcols = (lastcellcol + 1) - firstcellcol; + mycols = new ColHandle[numcols]; + for (int t = 0; t < numcols; t++) { + mycols[t] = sheet.getCol(firstcellcol + t); + } + return mycols; + } + + /** + * Get the number of columns that this CellRange encompasses + * + * @return + */ + public int getNumberOfCols() { + int numCols = (lastcellcol + 1) - firstcellcol; + return numCols; + } + + /** + * returns edge status of the desired CellHandle within this CellRange ie: top, + * left, bottom, right
    + * returns 0 or 1 for 4 sides
    + * 1,1,1,1 is a single cell in a range 1,1,0,0 is on the top left edge of the + * range + * + * @param CellHandle ch - + * @param int sz - + * @return int[] array representing the edge positions + */ + // TODO: documentation: Don't quite understand this! + public int[] getEdgePositions(CellHandle ch, int sz) { + int[] coords = {0, 0, 0, 0}; + // get the corners, check for 'edges' + String adr = ch.getCellAddress(); + int[] rc = ExcelTools.getRowColFromString(adr); + // increment to one-based + rc[0]++; + if (rc[0] == firstcellrow) + coords[0] = sz; + if (rc[0] == lastcellrow) + coords[2] = sz; + if (rc[1] == firstcellcol) + coords[1] = sz; + if (rc[1] == lastcellcol) + coords[3] = sz; + return coords; + } + + /** + * returns whether this CellRange intersects with another CellRange + * + * @param CellRange cr - CellRange to test + * @return boolean true if CellRange cr intersects with this CellRange + */ + public boolean intersects(CellRange cr) { + // get the corners, check for 'contains' + try { + int[] rc = cr.getRangeCoords(); + if ((rc[0] >= firstcellrow) && (rc[2] <= lastcellrow) && (rc[1] >= firstcellcol) + && (rc[3] <= lastcellcol)) { + return true; + } + } catch (CellNotFoundException e) { + Logger.logWarn("CellRange unable to determine intersection of range: " + cr.toString()); + } + return false; + } + + /** + * returns whether this CellRange contains a particular Cell + * + * @param CellHandle ch - the Cell to check + * @return true if CellHandle ch is contained within this CellRange + */ + public boolean contains(Cell cxx) { + String chsheet = cxx.getWorkSheetName(); + String mysheet = ""; + if (this.getSheet() != null) + mysheet = this.getSheet().getSheetName(); + if (!chsheet.equalsIgnoreCase(mysheet)) + return false; + String adr = cxx.getCellAddress(); + int[] rc = ExcelTools.getRowColFromString(adr); + return contains(rc); + } + + /** + * returns whether this CellRange contains the specified Row/Col coordinates + * + * @param int[] rc - row/col coordinates to test + * @return true if the coordinates are contained with this CellRange + */ + public boolean contains(int[] rc) { + boolean ret = true; + if (rc[0] + 1 < firstcellrow) + ret = false; + if (rc[0] + 1 > lastcellrow) + ret = false; + if (rc[1] < firstcellcol) + ret = false; + if (rc[1] > lastcellcol) + ret = false; + return ret; + } + + /** + * returns the String representation of this CellRange + */ + @Override + public String toString() { + return range; + } + + /** + * Constructor to create a new CellRange from a WorkSheetHandle and a set of + * range coordinates:
    + * coords[0] = first row
    + * coords[1] = first col
    + * coords[2] = last row
    + * coords[3] = last col + * + * @param WorkSheetHandle sht - handle to the WorkSheet containing the Range's Cells + * @param int[] coords - the cell coordinates + * @param boolean cb - true if should create blank cells + * @throws Exception + **/ + public CellRange(WorkSheetHandle sht, int[] coords, boolean cb) throws Exception { + this.createBlanks = cb; + this.sheet = sht; + this.mybook = sht.wbh; + sheetname = sht.getSheetName(); + sheetname = GenericPtg.qualifySheetname(sheetname); + String addr = sheetname + "!"; + String c1 = ExcelTools.getAlphaVal(coords[1]) + (coords[0] + 1); + String c2 = ExcelTools.getAlphaVal(coords[3]) + (coords[2] + 1); + addr += c1 + ":" + c2; + this.range = addr; + this.init(); + } + + /** + * Set this CellRange to be the current Print Area + */ + public void setAsPrintArea() { + if (this.sheet == null) { + Logger.logErr("CellRange.setAsPrintArea() failed: " + this.toString() + + " does not have a valid Sheet reference."); + return; + } + sheet.setPrintArea(this); + } + + /** + * Constructor to create a new CellRange from a WorkSheetHandle and a set of + * range coordinates:
    + * coords[0] = first row
    + * coords[1] = first col
    + * coords[2] = last row
    + * coords[3] = last col + * + * @param WorkSheetHandle sht - handle to the WorkSheet containing the Range's Cells + * @param int[] coords - the cell coordinates + */ + public CellRange(WorkSheetHandle sht, int[] coords) throws Exception { + this(sht, coords, false); + } + + /** + * Constructor to Create a new CellRange from a String range
    + * The String range must be in the format Sheet!CR:CR
    + * For Example, "Sheet1!C9:I19"
    + * NOTE: You MUST Set the WorkBookHandle explicitly on this CellRange or it will + * generate NullPointerException when trying to access the Cells. + * + * @param String r - range String + * @see CellRange.setWorkBook + */ + public CellRange(String r) { + this.range = r; + } + + /** + * Increase the bounds of the CellRange by including the CellHandle.
    + * These are the limitations and side-effects of this method:
    + * - the Cell must be contiguous with the existing Range, ie: you can add a Cell + * which either increments the row or the column of the existing range by one. + *
    + * - the Cell must be on the same sheet as the existing range.
    + * - as a Cell Range is a 2 dimensional rectangle, expanding a multiple column + * range by adding a Cell to the end will include the logical Cells on the row + * in the range.
    + * Some Examples:
    + *
    + * // simple one dimensional range expansion:
    + * existing Range = A1:A20
    + * addCellToRange(A21) new Range = A1:A21
    + *
    + * existing Range = A1:B20
    + * addCellToRange(A21)
    + * new Range = A1:B21 // note B20 is included automatically
    + *
    + * existing Range = A1:A20
    + * addCellToRange(B1)
    + * new Range = A1:B20 //note entire B column of cells are included automatically + * + * @param CellHandle ch - the Cell to add to the CellRange + */ + public boolean addCellToRange(CellHandle ch) { + // check worksheet + String sheetname = ch.getWorkSheetName(); + if (sheetname == null) { + Logger.logWarn("Cell " + ch.toString() + " NOT added to Range: " + this.toString()); + return false; + } + if (!sheetname.equalsIgnoreCase(this.getSheet().getSheetName())) { + Logger.logWarn("Cell " + ch.toString() + " NOT added to Range: " + this.toString()); + return false; + } + int[] rc = {ch.getRowNum(), ch.getColNum()}; + // increment to one-based + rc[0]++; + + // check that it's at the beginning + if (firstcellrow == -1) + firstcellrow = rc[0]; + if (firstcellcol == -1) + firstcellcol = rc[1]; + if (lastcellrow == -1) + lastcellrow = rc[0]; + if (lastcellcol == -1) + lastcellcol = rc[1]; + if (rc[0] < firstcellrow) + firstcellrow--; + if (rc[1] < firstcellcol) + firstcellcol--; + // check that it's at the end + if (rc[0] > lastcellrow) + lastcellrow++; + if (rc[1] > lastcellcol) + lastcellcol++; + + // myptg is never set so myptg access never happens... taking out + // boolean addPtgInfo = false; + // if (myptg != null && myptg instanceof PtgRef) + // addPtgInfo = true; + // format the new range String + String newrange = this.getSheet().getSheetName() + "!"; + String newcellrange = ""; + // if (addPtgInfo && !((PtgRef) myptg).isColRel()) + // newcellrange += "$"; + newcellrange += ExcelTools.getAlphaVal(firstcellcol); + // if (addPtgInfo && !((PtgRef) myptg).isRowRel()) + // newcellrange += "$"; + newcellrange += String.valueOf(firstcellrow); + newcellrange += ":"; + // if (addPtgInfo && !((PtgRef) myptg).isColRel()) + // newcellrange += "$"; + newcellrange += ExcelTools.getAlphaVal(lastcellcol); + // if (addPtgInfo && !((PtgRef) myptg).isColRel()) + // newcellrange += "$"; + newcellrange += String.valueOf(lastcellrow); + this.range = newrange + newcellrange; + isDirty = true; + + /* + * if (addPtgInfo) { ReferenceTracker.updateAddressPerPolicy(myptg, + * newcellrange); return true; } + */ + + if (this.parent != null && this.parent.getOpcode() == XLSConstants.NAME) { + ((Name) parent).setLocation(this.range); // ensure named range expression is updated, as well as any formula + // references are cleared of cache + } + + return false; + } + + /** + * get the Cells in this cell range + * + * @return CellHandle[] all the Cells in this range + */ + public CellHandle[] getCells() { + if (isDirty) + try { + init(); + } catch (CellNotFoundException e) { + } + return cells; + } + + /** + * Return a list of the cells in this cell range + * + * @return List of CellHandles + */ + public List getCellList() { + return Arrays.asList(cells); + } + + /** + * get the underlying Cell Records in this range
    + * NOTE: Cell Records are not a part of the public API and are not intended for + * use in client applications. + * + * @return BiffRec[] array of Cell Records + */ + public BiffRec[] getCellRecs() { + CellHandle[] ch = this.getCells(); + BiffRec[] ret = new BiffRec[ch.length]; + for (int t = 0; t < ret.length; t++) { + if (ch[t] != null) + ret[t] = ch[t].getCell(); + } + return ret; + } + + /* + * reset the underlying cell records in this range
    NOTE: Cell Records are + * not a part of the public API and are not intended for use in client + * applications. + * + * @return BiffRec[] array of Cell Records + * + * NOT USED AT THIS TIME public BiffRec[] resetCellRecs() { this.isDirty= true; + * return getCellRecs(); } + */ + + /** + * If the cells contain an incrementing value that can be transferred into an + * int, then return that value, else throw a NPE. I'm sure there is a better + * exception to be thrown, but not sure what that is, and it doesn't make sense + * to return a value like -1 in these cases. + */ + public int getIncrementAmount() throws Exception { + CellHandle[] ch = this.getCells(); + if (ch.length == 1) { + throw new Exception("Cannot have increment with non-range cell"); + } + boolean initialized = false; + int incAmount = 0; + for (int i = 1; i < ch.length; i++) { + int value1 = ch[i - 1].getIntVal(); + int value2 = ch[i].getIntVal(); + if (!initialized) { + incAmount = (value2 - value1); + initialized = true; + } else { + if (value2 - value1 != incAmount) { + throw new Exception("Inconsistent values across increment"); + } + } + } + if (!initialized) { + throw new Exception("Error determining increment"); + } + return incAmount; + } + + /** + * Constructor which creates a new CellRange from a String range
    + * The String range must be in the format Sheet!CR:CR
    + * For Example, "Sheet1!C9:I19" + * + * @param String range - the range string + * @param WorkBook bk + * @param boolean createblanks - true if blank cells should be created if necessary + * @param boolean initcells - true if cells should (be initialized) + */ + public CellRange(String range, io.starter.OpenXLS.WorkBook bk, boolean createblanks, boolean initcells) { + createBlanks = createblanks; + initializeCells = initcells; + this.range = range; + if (bk == null) + return; + this.setWorkBook(bk); + try { + this.init(); + } catch (CellNotFoundException e) { + } + } + + /** + * Constructor which creates a new CellRange from a String range
    + * The String range must be in the format Sheet!CR:CR
    + * For Example, "Sheet1!C9:I19" + * + * @param String range - the range string + * @param WorkBook bk + * @param boolean createblanks - true if blank cells should be created (if + * necessary) + */ + public CellRange(String range, io.starter.OpenXLS.WorkBook bk, boolean c) { + createBlanks = c; + this.range = range; + if (bk == null) + return; + this.setWorkBook(bk); + try { + if (!"".equals(this.range)) + this.init(); + } catch (CellNotFoundException e) { + } catch (NumberFormatException ne) { + // happens upon !REF range + } + } + + /** + * sets the parent of this Cell range
    + * Used Internally. Not intended for the End User. + * + * @param b + */ + public void setParent(BiffRec b) { + parent = b; + } + + /** + * Re-sort all cells in this cell range according to the column. + *

    + * A custom comparator can be passed in, or the default one can be used with + * sort(String, boolean). + *

    + * Comparators will be passed 2 CellHandle objects for comparison. + *

    + * Collections.reverse will be called on the results if ascending is set to + * false; + * + * @param rowNumber the 0 based (row 5 = 4) number of the row to be sorted upon + * @param comparator + * @throws RowNotFoundException + * @throws ColumnNotFoundException + */ + public void sort(int rownumber, Comparator comparator, boolean ascending) throws RowNotFoundException { + this.createBlanks(); + ArrayList sortRow = this.getCellsByRow(rownumber); + Collections.sort(sortRow, comparator); + if (!ascending) + Collections.reverse(sortRow); + // now we have sorted the array list, come up with a map to resort the rows. + int[] coords = null; + try { + coords = this.getRangeCoords(); + // fix stupid wrong offsets; + coords[0] = coords[0]--; + coords[2] = coords[2]--; + } catch (CellNotFoundException e1) { + } + ArrayList outputCols = new ArrayList(); + for (int i = 0; i < sortRow.size(); i++) { + CellHandle cell = sortRow.get(i); + ArrayList cells = null; + try { + cells = this.getCellsByCol(ExcelTools.getAlphaVal(cell.getColNum())); + } catch (ColumnNotFoundException e) { + // if there are no cells in this column ignore it + } + outputCols.add(cells); + } + this.removeCells(); + for (int i = coords[1]; i <= coords[3]; i++) { + ArrayList cells = outputCols.get(i - coords[1]); + for (int x = 0; x < cells.size(); x++) { + CellHandle cell = (CellHandle) cells.get(x); + Boundsheet bs = this.getSheet().getBoundsheet(); + cell.getCell().setCol((short) i); + bs.addCell((CellRec) cell.getCell()); + } + } + + } + + /** + * Changes the cellRange to a createBlanks cellrange and re-initializes the + * range, creating the missing blanks. + */ + private void createBlanks() { + if (!this.createBlanks) { + this.createBlanks = true; + this.initializeCells = true; + try { + this.init(); + } catch (CellNotFoundException e) { + } + } + } + + /** + * Resort all cells in the range according to the rownumber passed in. + * + * @param rownumber the 0 based row number + * @param ascending + * @throws RowNotFoundException + * @throws ColumnNotFoundException + */ + public void sort(int rownumber, boolean ascending) throws RowNotFoundException { + Comparator cp = new CellComparator(); + this.sort(rownumber, cp, ascending); + } + + /** + * Re-sort all cells in this cell range according to the column. + *

    + * A custom comparator can be passed in, or the default one can be used with + * sort(String, boolean). + *

    + * Comparators will be passed 2 CellHandle objects for comparison. + *

    + * * Collections.reverse will be called on the results if ascending is set to + * false; + * + * @param columnName + * @param comparator + * @throws RowNotFoundException + * @throws ColumnNotFoundException + */ + public void sort(String columnName, Comparator comparator, boolean ascending) throws ColumnNotFoundException { + if (!this.createBlanks) { + // we cannot have empty cells in this operation + this.createBlanks = true; + try { + this.init(); + } catch (CellNotFoundException e) { + } + } + ArrayList sortCol = this.getCellsByCol(columnName); + Collections.sort(sortCol, comparator); + if (!ascending) + Collections.reverse(sortCol); + // now we have sorted the array list, come up with a map to resort the rows. + int[] coords = null; + try { + coords = this.getRangeCoords(); + // fix stupid wrong offsets; + coords[0] = coords[0]--; + coords[2] = coords[2]--; + } catch (CellNotFoundException e1) { + e1.printStackTrace(); + } + ArrayList> outputRows = new ArrayList>(); + for (int i = 0; i < sortCol.size(); i++) { + CellHandle cell = (CellHandle) sortCol.get(i); + ArrayList cells = null; + try { + cells = this.getCellsByRow(cell.getRowNum()); + } catch (RowNotFoundException e) { + // ignore if no cells available + } + outputRows.add(cells); + } + this.removeCells(); + for (int i = coords[0]; i <= coords[2]; i++) { + ArrayList cells = outputRows.get(i - coords[0]); + for (int x = 0; x < cells.size(); x++) { + CellHandle cell = (CellHandle) cells.get(x); + Boundsheet bs = this.getSheet().getBoundsheet(); + cell.getCell().setRowNumber(i - 1); + bs.addCell((CellRec) cell.getCell()); + } + } + } + + /** + * Resort all cells in the range according to the column passed in. + * + * @param columnName + * @param ascending + * @throws ColumnNotFoundException + * @throws RowNotFoundException + */ + @SuppressWarnings("unchecked") + public void sort(String columnName, boolean ascending) throws ColumnNotFoundException { + Comparator cp = new CellComparator(); + this.sort(columnName, cp, ascending); + } + + public static String xmlResponsePre = ""; + public static String xmlResponsePost = ""; + + /** + * Return the XML representation of this CellRange object + * + * @return String of XML + */ + public String getXML() { + StringBuffer sb = new StringBuffer(""); + // StringBuffer sb = new StringBuffer(xmlResponsePre); + CellHandle[] cx = this.getCells(); + sb.append("\r\n"); + // append cellxml + for (int t = 0; t < cx.length; t++) { + sb.append(cx[t].getXML()); + sb.append("\r\n"); + } + sb.append(xmlResponsePost); + return sb.toString(); + } + + /** + * gets the array of Cells in this Name + *

    + * NOTE: this method variation also returns the Sheetname for the name record if + * not null. + *

    + * Thus this method is limited to use with 2D ranges. + * + * @param fragment whether to enclose result in NameHandle tag + * @return Cell[] all Cells defined in this Name + */ + public String getCellRangeXML(boolean fragment) { + StringBuffer sbx = new StringBuffer(); + if (!fragment) + sbx.append(""); + sbx.append(""); + sbx.append(getXML()); + sbx.append(""); + return sbx.toString(); + } + + /** + * gets the array of Cells in this Name + *

    + * Thus this method is limited to use with 2D ranges. + * + * @param fragment whether to enclose result in NameHandle tag + * @return Cell[] all Cells defined in this Name + */ + public String getCellRangeXML() { + StringBuffer sbx = new StringBuffer(); + sbx.append(""); + sbx.append(getXML()); + return sbx.toString(); + } + + /** + * Constructor which creates a new CellRange using an array of cells as it's + * constructor.
    + * NOTE that if the array of cells you are adding is not a rectangle of data (ie + * [A1][B1][C1]) that you will have null cells in your cell range and operations + * on it may cause errors.
    + * If you wish to populate a cell range that is not contiguous, consider the + * constructor CellRange(CellHandle[] newcells, boolean createblanks), which + * will populate null cells with blank records, allowing normal operations such + * as formatting, merging, etc. + * + * @param CellHandle[] newcells - the array of cells from which to create the new + * CellRange + * @throws CellNotFoundException + */ + public CellRange(CellHandle[] newcells) throws CellNotFoundException { + this.setWorkBook(newcells[0].getWorkBook()); + this.sheet = newcells[0].getWorkSheetHandle(); + for (int x = 0; x < newcells.length; x++) { + this.addCellToRange(newcells[x]); + } + this.init(); + } + + /** + * create a new CellRange using an array of cells as it's constructor.
    + * If you wish to populate a cell range that is not contiguous, set createblanks + * to true, which will populate null cells with blank records, allowing normal + * operations such as formatting, merging, etc. + * + * @param CellHandle[] newcells - the array of cells from which to create the new + * CellRange + * @param boolean createblanks - true if should create blank cells if necesary + */ + public CellRange(CellHandle[] newcells, boolean createblanks) throws CellNotFoundException { + this.createBlanks = createblanks; + this.setWorkBook(newcells[0].getWorkBook()); + this.sheet = newcells[0].getWorkSheetHandle(); + for (int x = 0; x < newcells.length; x++) { + this.addCellToRange(newcells[x]); + } + this.init(); + } + + /** + * Constructor which creates a new CellRange from a String range
    + * The String range must be in the format Sheet!CR:CR
    + * For Example, "Sheet1!C9:I19" + * + * @param String range - the range string + * @param WorkBook bk + * @throws CellNotFoundException + */ + public CellRange(String range, io.starter.OpenXLS.WorkBook bk) throws CellNotFoundException { + this(range, bk, true); + } + + /** + * attach the workbook for this CellRange + * + * @param WorkBook bk + */ + public void setWorkBook(io.starter.OpenXLS.WorkBook bk) { + this.mybook = bk; + } + + /** + * Gets the coordinates of this cell range, + * + * @return int[5]: [0] first row (zero based, ie row 1=0), [1] first column, [2] + * last row (zero based, ie row 1=0), [3] last column, [4] number of + * cells in range + */ + public int[] getCoords() throws CellNotFoundException { + int numrows = 0; + int numcols = 0; + int numcells = 0; + int[] coords = new int[5]; + String temprange = range; + String[] s = ExcelTools.stripSheetNameFromRange(temprange); + temprange = s[1]; + // qualify sheet and reset range - necessary if sheetname with spaces is used in + // formula parsing + sheetname = GenericPtg.qualifySheetname(s[0]); + if (sheetname != null && !sheetname.equals("")) { + if (s[2] == null) + this.range = sheetname + "!" + temprange; + else { + s[2] = GenericPtg.qualifySheetname(s[2]); + this.range = sheetname + ":" + s[2] + "!" + temprange; + } + } + + /* + * check for R1C1 + */ + if ((temprange.indexOf("R") == 0) && (temprange.indexOf("C") > 1) + && (Character.isDigit(temprange.charAt(temprange.indexOf("C") - 1)))) { + + int[] b = ExcelTools.getRangeRowCol(temprange); + + numrows = (b[2] - b[0]); + if (numrows <= 0) + numrows = 1; + + numcols = (b[3] - b[2]); + if (numcols <= 0) + numcols = 1; + + numcells = numrows * numcols; + + int[] retr = new int[5]; + retr[0] = b[0]; + retr[1] = b[1]; + retr[2] = b[2]; + retr[3] = b[3]; + retr[4] = numcells; + return retr; + } + String startcell = "", endcell = ""; + int lastcolon = temprange.lastIndexOf(":"); + endcell = temprange.substring(lastcolon + 1); + if (lastcolon == -1) // no range + startcell = endcell; + else + startcell = temprange.substring(0, lastcolon); + + startcell = StringTool.strip(startcell, "$"); + endcell = StringTool.strip(endcell, "$"); + + // get the first cell's coordinates + int charct = startcell.length(); + while (charct > 0) { + if (!Character.isDigit(startcell.charAt(--charct))) { + charct++; + break; + } + } + String firstcellrowstr = startcell.substring(charct); + firstcellrow = Integer.parseInt(firstcellrowstr); + String firstcellcolstr = startcell.substring(0, charct); + firstcellcol = ExcelTools.getIntVal(firstcellcolstr); + // get the last cell's coordinates + charct = endcell.length(); + while (charct > 0) { + if (!Character.isDigit(endcell.charAt(--charct))) { + charct++; + break; + } + } + String lastcellrowstr = endcell.substring(charct); + lastcellrow = Integer.parseInt(lastcellrowstr); + String lastcellcolstr = endcell.substring(0, charct); + lastcellcol = ExcelTools.getIntVal(lastcellcolstr); + numrows = (lastcellrow - firstcellrow) + 1; + numcols = (lastcellcol - firstcellcol) + 1; + + numcells = numrows * numcols; + if (numcells < 0) + numcells *= -1; // handle swapped cells ie: "B1:A1" + + coords[0] = firstcellrow - 1; + coords[1] = firstcellcol; + coords[2] = lastcellrow - 1; + coords[3] = lastcellcol; + coords[4] = numcells; + if (firstcellrow < 0 && lastcellrow < 0 || firstcellcol < 0 || lastcellcol < 0) { + // not an error if it is a whole column or whole row range + if (firstcellcol == -1 && lastcellcol == -1) { + // what should numcells be for wholerow? + wholeRow = true; + } else if (firstcellrow == -1 && lastcellrow == -1) { + // what should numcells be for wholecol? + wholeCol = true; + } else + Logger.logErr("CellRange.getRangeCoords: Error in Range " + range); + } + + // trap OOXML external reference link, if any + if (s[3] != null) + externalLink1 = Integer.valueOf(s[3].substring(1, s[3].length() - 1)).intValue(); + if (s[4] != null) + externalLink2 = Integer.valueOf(s[4].substring(1, s[4].length() - 1)).intValue(); + + return coords; + + } + + /** + * Gets the coordinates of this cell range. + * + * @return int[5]: [0] first row, [1] first column, [2] last row, [3] last + * column, [4] number of cells in range + * @deprecated {@link #getCoords()} instead, which returns zero based values for + * rows. + */ + @Deprecated + public int[] getRangeCoords() throws CellNotFoundException { + int[] ordinalValues = this.getCoords(); + ordinalValues[0] += 1; + ordinalValues[2] += 1; + return ordinalValues; + } + + /** + * Returns the WorkSheet referenced in this CellRange. + * + * @return WorkSheetHandle sheet referenced in this CellRange. + */ + public WorkSheetHandle getSheet() { + return sheet; + } + + /** + * initializes this CellRange + * + * @throws CellNotFoundException + */ + public void init() throws CellNotFoundException { + if (!FormulaParser.isComplexRange(range)) { + int[] coords = this.getRangeCoords(); + int rowctr = coords[0]; + int firstcellcol = coords[1]; + int lastcellcol = coords[3]; + int numcells = coords[4]; + + int cellctr = firstcellcol - 1; + try { + if (sheetname != null) { + if (sheetname.equals("")) // 20080214 KSC - is this a good idea? + // default to work sheet 0 + sheetname = this.mybook.getWorkSheet(0).getSheetName(); + } + if (sheetname == null) { + if (this.sheet != null) + sheetname = this.sheet.getSheetName(); + else + throw new IllegalArgumentException("sheet name not specified: " + range); + } + + String s = sheetname; + if (s != null) { + // handle enclosing apostrophes which are added to PtgRefs + if (s.charAt(0) == '\'') { + s = s.substring(1); + if (s.charAt(s.length() - 1) == '\'') { + s = s.substring(0, s.length() - 1); + } + } + } + sheet = mybook.getWorkSheet(s); + // if wholerow or wholecol, don't gather cells + if (this.wholeCol || this.wholeRow) + return; + cells = new CellHandle[numcells]; + boolean resetFastAdds = false; + if (sheet.getFastCellAdds() && this.createBlanks) { + resetFastAdds = true; + sheet.setFastCellAdds(false); + } + for (int i = 0; i < numcells; i++) { + if (cellctr == lastcellcol) {// if its the end of the row, + // increment row. + cellctr = firstcellcol - 1; + rowctr++; + } + ++cellctr; + try { + // use caching 20080917 KSC: PROBLEM HERE [Claritas + // BugTracker 1862] + if (this.initializeCells) + cells[i] = sheet.getCell(rowctr - 1, cellctr, sheet.getUseCache()); // 20080917 KSC: use + // cache + // setting instead of + // defaulting to true); + } catch (CellNotFoundException e) { + if (this.createBlanks) { + cells[i] = sheet.add(null, rowctr - 1, cellctr); + } + + } + } + if (resetFastAdds) { + sheet.setFastCellAdds(true); + } + } catch (WorkSheetNotFoundException e) { + throw new IllegalArgumentException(e.toString()); + } + } else { // gather cells for a complex range ... + io.starter.formats.XLS.formulas.PtgMemFunc pm = new io.starter.formats.XLS.formulas.PtgMemFunc(); + XLSRecord b = new XLSRecord(); + b.setWorkBook(this.mybook.getWorkBook()); + pm.setParentRec(b); + try { + pm.setLocation(range); + Ptg[] p = pm.getComponents(); + java.util.ArrayList cellsfromcomplexrange = new java.util.ArrayList(); + for (int i = 0; i < p.length; i++) { + try { + cellsfromcomplexrange.add(mybook.getCell(((PtgRef) p[i]).getLocation())); + } catch (CellNotFoundException e) { + if (this.createBlanks) { + cells[i] = sheet.add(null, p[i].getLocation()); + } + } + } + cells = new CellHandle[cellsfromcomplexrange.size()]; + cells = cellsfromcomplexrange.toArray(cells); + } catch (Exception e) { + throw new IllegalArgumentException(e.toString()); + } + + } + isDirty = false; + } + + /** + * Initializes this CellRange's cell list if necessary. This method + * is useful if this CellRange was created with + * initCells set to false and it is later necessary to + * retrieve the cell list. + * + * @param createBlanks whether missing cells should be created as blanks. If this is + * false they will appear in the cell list as + * nulls. + */ + public void initCells(boolean createBlanks) { + // If we don't need to do anything, return + if (initializeCells == true && (!createBlanks || this.createBlanks)) + return; + + this.initializeCells = true; + this.createBlanks = createBlanks; + + try { + this.init(); + } catch (CellNotFoundException e) { + // This will never actually happen but we have to catch it anyway + throw new Error(); + } + } + + /** + * @return the workbook object attached to this CellRange + */ + public WorkBook getWorkBook() { + return mybook; + } + + /** + * gets whether this CellRange will add blank records to the WorkBook for any + * missing Cells contained within the range. + * + * @return true if should create blank records for missing Cells + */ + public boolean getCreateBlanks() { + return createBlanks; + } + + /** + * set whether this CellRange will add blank records to the WorkBook for any + * missing Cells contained within the range. + * + * @param boolean b - true if should create blank records for missing Cells + */ + public void setCreateBlanks(boolean b) { + createBlanks = b; + } + + /** + * Return the String representation of this range + * + * @return the String range + */ + public String getRange() { + return range; + } + + /** + * Return the String cell address of this range in R1C1 format + * + * @return String range in R1C1 format + */ + public String getR1C1Range() { + String rc1x = "R"; + try { + int[] rc1 = this.getRangeCoords(); + rc1x += rc1[0] + 1; // rangecoords are already 1-based + rc1x += "C" + rc1[1]; + rc1x += ":R" + (rc1[2] + 1); + rc1x += "C" + rc1[3]; + + } catch (CellNotFoundException e) { + Logger.logErr("CellRange.getR1C1Range failed", e); + } + return rc1x; + } + + /** + * Sets the range of cells for this CellRange to a string range + * + * @param String rng - Range string + */ + public void setRange(String rng) { + range = rng; + try { + this.init(); + } catch (CellNotFoundException e) { + // don't have to report anything + } + } + + /** + * sets a border around the range of cells + * + * @param int width - line width + * @param int linestyle - line style + * @param java.awt.Color colr - color of border line + */ + public void setBorder(int width, int linestyle, java.awt.Color colr) { + CellHandle[] ch = getCells(); + for (int t = 0; t < ch.length; t++) { + int[] coords = getEdgePositions(ch[t], width); + // create Excel border -- top, left, bottom, right + if (coords[0] > 0) { + ch[t].setTopBorderLineStyle((short) linestyle); + ch[t].setBorderTopColor(colr); + } + if (coords[1] > 0) { + ch[t].setLeftBorderLineStyle((short) linestyle); + ch[t].setBorderLeftColor(colr); + } + if (coords[2] > 0) { + ch[t].setBottomBorderLineStyle((short) linestyle); + ch[t].setBorderBottomColor(colr); + } + if (coords[3] > 0) { + ch[t].setRightBorderLineStyle((short) linestyle); + ch[t].setBorderRightColor(colr); + } + } + } + + /** + * update the CellRange when the underlying Cells change their location + * + * @return boolean true if the CellRange could be updated, false if there are no + * cells represented by this range + */ + public boolean update() { + if (cells == null) + return false; // this is an invalid range -- see Mergedcells prob. + // jm + + // arbitrarily set the initial vals... + if (cells[0] != null) { // 20100106 KSC: if didn't create blanks it's possible that cells are null + firstcellrow = cells[0].getRowNum() + 1; + firstcellcol = cells[0].getColNum(); + lastcellrow = cells[0].getRowNum() + 1; + lastcellcol = cells[0].getColNum(); + for (int t = 0; t < cells.length; t++) { + CellHandle cx = cells[t]; + // 20090901 KSC: apparently can be null + if (cx != null) + this.addCellToRange(cx); + } + this.myrowints = null; + this.mycolints = null; + return true; + } else if (this.range != null) {// 20100106 KSC: handle ranges containing null cell[0] (i.e. ranges referencing + // cells not present and createBlanks==false) + if (this.DEBUG) + Logger.logWarn("CellRange.update: trying to access blank cells in range " + this.range); + try { + this.getRangeCoords(); + return true; + } catch (CellNotFoundException e) { // shouldn't + return false; + } + } + return false; // return false if it doesn't have it's cells defined + } + + /** + * returns the merged state of the CellRange + * + * @return true if this CellRange is merged + */ + public boolean isMerged() { + return ismerged; + } + + /** + * Sets the sheet reference for this CellRange. + * + * @param WorkSheetHandle aSheet + */ + public void setSheet(WorkSheetHandle aSheet) { + this.sheet = aSheet; + this.sheetname = aSheet.getSheetName(); + } + + /** + * Whether to copy the cell contents. + */ + public static final int COPY_CONTENTS = 0x01; + + /** + * Whether formulas should be copied. If this bit is not set the formula result + * will be copied instead. + */ + public static final int COPY_FORMULAS = 0x02; + + public static final int COPY_FORMATS = 0x0100; + + /** + * Copies this range to another location. At present only contents and complete + * formats may be copied. + * + * @param row the topmost row of the target area + * @param col the leftmost column of the target area + * @param what a set of flags determining what will be copied + * @return the destination range + */ + public CellRange copy(WorkSheetHandle sheet, int row, int col, int what) { + CellRange result = new CellRange(sheet, row, col, this.getWidth(), this.getHeight()); + + int first_col = col; + + // note these are not currently used, see setting below + boolean copy_contents = (what & COPY_CONTENTS) != 0; + boolean copy_formulas = (what & COPY_FORMULAS) != 0; + boolean copy_formats = (what & COPY_FORMATS) != 0; + + // set to true until this thing is fully implemented + copy_formats = true; + copy_formulas = true; + + int cur_row = cells[0].getRowNum(); + for (int idx = 0; idx < cells.length; idx++) { + CellHandle source = cells[idx]; + + if (source.getRowNum() != cur_row) { + cur_row = source.getRowNum(); + row++; + col = first_col; + } + + CellHandle target = null; + try { + target = sheet.getCell(row, col); + } catch (CellNotFoundException e) { + } + + int formatID; + if (copy_formats) + formatID = source.getFormatId(); + else if (target != null) + formatID = target.getFormatId(); + else + formatID = sheet.getWorkBook().getWorkBook().getDefaultIxfe(); + + if (copy_contents) { + Object value; + + if (copy_formulas && source.isFormula()) { + try { + value = source.getFormulaHandle().getFormulaString(); + } catch (FormulaNotFoundException e) { + // This shouldn't happen; we known the formula exists. + // If it does happen it indicates a bug in OpenXLS, + // thus we throw an Error. + throw new Error("formula cell has no Formula record", e); + } + } else { + value = source.getVal(); + } + + target = sheet.add(value, row, col, formatID); + + if (target.isFormula()) + try { + FormulaHandle.moveCellRefs(target.getFormulaHandle(), + new int[]{row - source.getRowNum(), col - source.getColNum()}); + } catch (FormulaNotFoundException e) { + } + } else if (target == null) { + target = sheet.add(null, row, col, formatID); + } + + if (copy_formats) { + target.setFormatId(formatID); + } + + result.cells[idx] = target; + col++; + } + + return result; + } + + /** + * Fills this range from the given cell. + * + * @param source the cell whose attributes should be copied or null to + * copy from the first cell in the range + * @param what a set of flags determining what will be copied + * @param increment the amount by which to increment numeric values or + * NaN for no increment + */ + public void fill(CellHandle source, int what, double increment) { + if (null == source) + source = cells[0]; + + boolean copy_contents = (what & COPY_CONTENTS) != 0; + boolean copy_formulas = (what & COPY_FORMULAS) != 0; + boolean copy_formats = (what & COPY_FORMATS) != 0; + + int sourceRow = source.getRowNum(); + int sourceCol = source.getColNum(); + + Object value = null; + if (copy_contents) { + if (copy_formulas && source.isFormula()) { + try { + value = source.getFormulaHandle().getFormulaString(); + } catch (FormulaNotFoundException e) { + throw new Error("formula cell has no Formula record", e); + } + } else { + value = source.getVal(); + } + } + + // if increment is set, ensure the value can be incremented + if (!Double.isNaN(increment) && !(copy_contents && value instanceof Number)) + throw new IllegalArgumentException("cannot increment unless filling with a numeric value"); + + for (int idx = 0; idx < cells.length; idx++) { + CellHandle target = cells[idx]; + + // don't overwrite the source cell + if (source.equals(target)) + continue; + + if (!Double.isNaN(increment)) + value = ((Number) value).doubleValue() + increment; + + int formatID = (copy_formats ? source : target).getFormatId(); + + if (copy_contents) { + cells[idx] = target = sheet.add(value, target.getRowNum(), target.getColNum(), formatID); + + if (target.isFormula()) + try { + FormulaHandle.moveCellRefs(target.getFormulaHandle(), + new int[]{target.getRowNum() - sourceRow, target.getColNum() - sourceCol}); + } catch (FormulaNotFoundException e) { + } + } + + // when adding Date values passing the format ID to sheet.add + // doesn't set the format so we always set it here + if (copy_formats) { + target.setFormatId(formatID); + } + + } + } + + public Collection calculateAffectedCellsOnSheet() { + Set affected = new HashSet(); + for (CellHandle cell : cells) { + if (cell != null) { + affected.add(cell); + affected.addAll(cell.calculateAffectedCellsOnSheet()); + } + } + return affected; + } + + /** + * return a JSON array of cell values for the given range
    + * static version + * + * @param String range - a string representation of the desired range of cells + * @param WorkBook wbh - the source WorkBook for the cell range + * @return JSONArray - a JSON representation of the desired cell range + */ + public static JSONArray getValuesAsJSON(String range, WorkBook wbh) { + JSONArray rangeArray = new JSONArray(); + try { + CellRange cr = new CellRange(range, wbh, true); + for (int j = 0; j < cr.getCells().length; j++) + rangeArray.put(cr.getCells()[j].getVal()); + } catch (Exception e) { + Logger.logErr("Error obtaining CellRange " + range + " JSON: " + e); + } + return rangeArray; + } + + /** + * Return a json object representing this cell range, entries contain only + * address and values for more compact space + * + * @param range + * @param wbh + * @return + */ + public JSONObject getBasicJSON() { + try { + JSONObject crObj = new JSONObject(); + crObj.put(JSON_RANGE, this.getRange()); + JSONArray rangeArray = new JSONArray(); + // should this possibly be full + CellHandle[] cells = this.getCells(); + for (int j = 0; j < cells.length; j++) { + JSONObject result = new JSONObject(); + String addy = cells[j].getCellAddress(); + String val = cells[j].getVal().toString(); + result.put(JSON_LOCATION, addy); + result.put(JSON_CELL_VALUE, val); + rangeArray.put(result); + } + crObj.put(JSON_CELLS, rangeArray); + return crObj; + } catch (Exception e) { + Logger.logErr("Error obtaining CellRange " + range + " JSON: " + e); + } + return new JSONObject(); + } + + /** + * Return a json object representing this cell range with full cell information + * embedded. + */ + public JSONObject getJSON() { + JSONObject theRange = new JSONObject(); + JSONArray cells = new JSONArray(); + try { + theRange.put(JSON_RANGE, getRange()); + CellHandle[] chandles = getCells(); + for (int i = 0; i < chandles.length; i++) { + CellHandle thisCell = chandles[i]; + JSONObject result = new JSONObject(); + + result.put(JSON_CELL, thisCell.getJSONObject()); + cells.put(result); + } + theRange.put(JSON_CELLS, cells); + } catch (JSONException e) { + Logger.logErr("Error getting cellRange JSON: " + e); + } + return theRange; + } + + /** + * Get the cells from a particular rownumber, constrained by the boundaries of + * the cellRange + * + * @param rownumber + */ + public ArrayList getCellsByRow(int rownumber) throws RowNotFoundException { + ArrayList al = new ArrayList(); + RowHandle r = this.getSheet().getRow(rownumber); + CellHandle[] cells = r.getCells(); + int[] coords = null; + try { + coords = this.getRangeCoords(); + } catch (CellNotFoundException e) { + throw new RowNotFoundException("Error getting internal coordinates for CellRange" + e); + } + for (int i = 0; i < cells.length; i++) { + if (cells[i].getColNum() >= coords[1] && cells[i].getColNum() <= coords[3]) { + al.add(cells[i]); + } + } + return al; + } + + /** + * Get the cells from a particular column, constrained by the boundaries of the + * cellRange + * + * @param rownumber + * @throws ColumnNotFoundException + */ + public ArrayList getCellsByCol(String col) throws ColumnNotFoundException { + ArrayList al = new ArrayList(); + ColHandle r = this.getSheet().getCol(col); + CellHandle[] cells = r.getCells(); + int[] coords = null; + try { + coords = this.getRangeCoords(); + coords[0] = coords[0] - 1; + coords[2] = coords[2] - 1; + } catch (CellNotFoundException e) { + throw new ColumnNotFoundException("Error getting internal coordinates for CellRange" + e); + } + for (int i = 0; i < cells.length; i++) { + if (cells[i].getRowNum() >= coords[0] && cells[i].getRowNum() <= coords[2]) { + al.add(cells[i]); + } + } + return al; + } + + /** + * returns the cells for a given range
    + * static version + * + * @param String range - a string representation of the desired range of cells + * @param WorkBook wbh - the source WorkBook for the cell range + * @return CellHandle[] array of cells represented by the desired cell range + */ + public static CellHandle[] getCells(String range, WorkBookHandle wbh) { + CellRange cr = new CellRange(range, wbh, true); + return cr.getCells(); + } + + /** + * removes the border from all of the cells in this range + */ + public void removeBorder() { + CellHandle[] ch = getCells(); + for (int t = 0; t < ch.length; t++) { + ch[t].removeBorder(); + } + } + + /** + * Sets a bottom border on all cells in the cellrange + *

    + * Linestyle should be set through the FormatHandle constants + */ + public void setInnerBorderBottom(int linestyle, java.awt.Color colr) { + CellHandle[] ch = getCells(); + for (int t = 0; t < ch.length; t++) { + ch[t].setBottomBorderLineStyle((short) linestyle); + ch[t].setBorderBottomColor(colr); + } + } + + /** + * Sets a right border on all cells in the cellrange + *

    + * Linestyle should be set through the FormatHandle constants + */ + public void setInnerBorderRight(int linestyle, java.awt.Color colr) { + CellHandle[] ch = getCells(); + for (int t = 0; t < ch.length; t++) { + ch[t].setRightBorderLineStyle((short) linestyle); + ch[t].setBorderRightColor(colr); + } + } + + /** + * Sets a left border on all cells in the cellrange + *

    + * Linestyle should be set through the FormatHandle constants + */ + public void setInnerBorderLeft(int linestyle, java.awt.Color colr) { + CellHandle[] ch = getCells(); + for (int t = 0; t < ch.length; t++) { + ch[t].setLeftBorderLineStyle((short) linestyle); + ch[t].setBorderLeftColor(colr); + } + } + + /** + * Sets a top border on all cells in the cellrange + *

    + * Linestyle should be set through the FormatHandle constants + */ + public void setInnerBorderTop(int linestyle, java.awt.Color colr) { + CellHandle[] ch = getCells(); + for (int t = 0; t < ch.length; t++) { + ch[t].setTopBorderLineStyle((short) linestyle); + ch[t].setBorderTopColor(colr); + } + } + + /** + * Sets a surround border on all cells in the cellrange + *

    + * Linestyle should be set through the FormatHandle constants + */ + public void setInnerBorderSurround(int linestyle, java.awt.Color colr) { + CellHandle[] ch = getCells(); + for (int t = 0; t < ch.length; t++) { + ch[t].setBorderColor(colr); + ch[t].setBorderLineStyle((short) linestyle); + } + } } diff --git a/src/main/java/io/starter/OpenXLS/CellRangeRef.java b/src/main/java/io/starter/OpenXLS/CellRangeRef.java index 62ba1f7..a84a259 100644 --- a/src/main/java/io/starter/OpenXLS/CellRangeRef.java +++ b/src/main/java/io/starter/OpenXLS/CellRangeRef.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,309 +27,309 @@ /** * Represents a reference to a 3D range of cells. - * + *

    * This class is not currently part of the public API. - * + *

    * No input validation whatsoever is performed. Any combination of values may be * set, whether it makes any sense or not. - * + * * Starter Inc. - * + * * @see DataBoundCellRange * @see XLSRecord */ public class CellRangeRef implements Cloneable { - private int first_col, first_row, last_col, last_row; - private String first_sheet_name, last_sheet_name; - private WorkSheetHandle first_sheet, last_sheet; - - /** Private nullary constructor for use by static pseudo-constructors. */ - private CellRangeRef() { - } - - public CellRangeRef(int first_row, int first_col, int last_row, int last_col) { - this.first_row = first_row; - this.first_col = first_col; - this.last_row = last_row; - this.last_col = last_col; - } - - /** - * return the number of cells in this rangeref - * - * @return number of cells in ref - */ - public int numCells() { - int ret = -1; - int numrows = this.last_row - this.first_row; - numrows++; - int numcols = this.last_col - this.first_col; - numcols++; - ret = numrows * numcols; - return ret; - } - - public CellRangeRef(int first_row, int first_col, int last_row, int last_col, String first_sheet, - String last_sheet) { - this(first_row, first_col, last_row, last_col); - this.first_sheet_name = first_sheet; - this.last_sheet_name = last_sheet; - } - - public CellRangeRef(int first_row, int first_col, int last_row, int last_col, WorkSheetHandle first_sheet, - WorkSheetHandle last_sheet) { - this(first_row, first_col, last_row, last_col); - this.first_sheet = first_sheet; - this.last_sheet = last_sheet; - } - - /** - * Parses a range in A1 notation and returns the equivalent CellRangeRef. - */ - public static CellRangeRef fromA1(String reference) { - CellRangeRef ret = new CellRangeRef(); - String range; - - { - String[] parts = ExcelTools.stripSheetNameFromRange(reference); - ret.first_sheet_name = parts[0]; - range = parts[1]; - ret.last_sheet_name = parts[2]; - } - - if (range == null) - throw new IllegalArgumentException("missing range component"); - - { - int[] parts = ExcelTools.getRangeRowCol(range); - ret.first_row = parts[0]; - ret.first_col = parts[1]; - ret.last_row = parts[2]; - ret.last_col = parts[3]; - } - - return ret; - } - - /** - * Convenience method combining {@link #fromA1(String)} and - * {@link #resolve(WorkBookHandle)}. - */ - public static CellRangeRef fromA1(String reference, WorkBookHandle book) throws WorkSheetNotFoundException { - CellRangeRef ret = fromA1(reference); - ret.resolve(book); - return ret; - } - - /** - * Resolves sheet names into sheet handles against the given book. - * - * @param book - * the book against which the sheet names should be resolved - * @throws WorkSheetNotFoundException - * if either of the sheets does not exist in the given book - */ - public void resolve(WorkBookHandle book) throws WorkSheetNotFoundException { - if (first_sheet_name != null) - first_sheet = book.getWorkSheet(first_sheet_name); - if (last_sheet_name != null) - last_sheet = book.getWorkSheet(last_sheet_name); - } - - /** - * Returns the lowest-indexed row in this range. - * - * @return the row index or null if this is a column range - */ - public int getFirstRow() { - return first_row; - } - - /** - * Returns the lowest-indexed column in this range. - * - * @return the column index or null if this is a row range - */ - public int getFirstColumn() { - return first_col; - } - - /** - * Returns the highest-indexed row in this range. - * - * @return the row index or null if this is a column range - */ - public int getLastRow() { - return last_row; - } - - /** - * Returns the highest-indexed column in this range. - * - * @return the column index or null if this is a row range - */ - public int getLastColumn() { - return last_col; - } - - /** - * Returns the name of the first sheet in this range. - * - * @return the name of the sheet or null if this range is not qualified with a - * sheet - */ - public String getFirstSheetName() { - if (first_sheet != null) - return first_sheet.getSheetName(); - else - return first_sheet_name; - } - - /** - * Returns the first sheet in this range. - * - * @return the WorkSheetHandle or null if this range is not qualified with a - * sheet or the sheet names have not been resolved - */ - public WorkSheetHandle getFirstSheet() { - return first_sheet; - } - - /** - * Returns the name of the last sheet in this range. - * - * @return the name of the sheet or null if this range is not qualified with a - * sheet - */ - public String getLastSheetName() { - if (last_sheet != null) - return last_sheet.getSheetName(); - else - return last_sheet_name; - } - - /** - * Returns the last sheet in this range. - * - * @return the WorkSheetHandle or null if this range is not qualified with a - * sheet or the sheet names have not been resolved - */ - public WorkSheetHandle getLastSheet() { - return last_sheet; - } - - /** - * Determines whether this range is qualified with a sheet. - */ - public boolean hasSheet() { - return first_sheet != null || first_sheet_name != null; - } - - /** - * Determines whether this range spans multiple sheets. - */ - public boolean isMultiSheet() { - return (first_sheet != null && last_sheet != null && first_sheet != last_sheet) - || (first_sheet_name != null && last_sheet_name != null && first_sheet_name != last_sheet_name); - } - - /** - * Sets the first row in this range. - * - * @param value - * the row index to set - */ - public void setFirstRow(int value) { - first_row = value; - } - - /** - * Sets the first column in this range. - * - * @param value - * the column index to set - */ - public void setFirstColumn(int value) { - first_col = value; - } - - /** - * Sets the first sheet in this range. - */ - public void setFirstSheet(WorkSheetHandle sheet) { - first_sheet_name = null; - first_sheet = sheet; - } - - /** - * Sets the last row in this range. - * - * @param value - * the row index to set - */ - public void setLastRow(int value) { - last_row = value; - } - - /** - * Sets the last column in this range. - * - * @param value - * the column index to set - */ - public void setLastColumn(int value) { - last_col = value; - } - - /** - * Sets the last sheet in this range. - */ - public void setLastSheet(WorkSheetHandle sheet) { - last_sheet_name = null; - last_sheet = sheet; - } - - /** - * Returns whether this range entirely contains the given range. This ignores - * the sheets, if any, and compares only the cell ranges. - */ - public boolean contains(CellRangeRef range) { - return this.first_row <= range.first_row && this.last_row >= range.last_row && this.first_col <= range.first_col - && this.last_col >= range.last_col; - } - - /** - * Compares this range to the specified object. The result is true - * if and only if the argument is not null and is a - * CellRangeRef object that represents the same range as this - * object. - */ - @Override - public boolean equals(Object other) { - // if it's null or not a CellRangeRef it can't be equal - if (other == null || !(other instanceof CellRangeRef)) - return false; - - return this.toString().equals(other.toString()); - } - - /** Creates and returns a copy of this range. */ - @Override - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // This can't happen (we're Cloneable) but we have to catch it - throw new Error("Object.clone() threw CNSE but we're Cloneable"); - } - } - - /** Gets this range in A1 notation. */ - @Override - public String toString() { - String sheet1 = getFirstSheetName(); - String sheet2 = getLastSheetName(); - return (sheet1 != null ? sheet1 + (sheet2 != null && sheet2 != sheet1 ? ":" + sheet2 : "") + "!" : "") - + ExcelTools.formatRange(new int[] { first_col, first_row, last_col, last_row }); - } + private int first_col, first_row, last_col, last_row; + private String first_sheet_name, last_sheet_name; + private WorkSheetHandle first_sheet, last_sheet; + + /** + * Private nullary constructor for use by static pseudo-constructors. + */ + private CellRangeRef() { + } + + public CellRangeRef(int first_row, int first_col, int last_row, int last_col) { + this.first_row = first_row; + this.first_col = first_col; + this.last_row = last_row; + this.last_col = last_col; + } + + /** + * return the number of cells in this rangeref + * + * @return number of cells in ref + */ + public int numCells() { + int ret = -1; + int numrows = this.last_row - this.first_row; + numrows++; + int numcols = this.last_col - this.first_col; + numcols++; + ret = numrows * numcols; + return ret; + } + + public CellRangeRef(int first_row, int first_col, int last_row, int last_col, String first_sheet, + String last_sheet) { + this(first_row, first_col, last_row, last_col); + this.first_sheet_name = first_sheet; + this.last_sheet_name = last_sheet; + } + + public CellRangeRef(int first_row, int first_col, int last_row, int last_col, WorkSheetHandle first_sheet, + WorkSheetHandle last_sheet) { + this(first_row, first_col, last_row, last_col); + this.first_sheet = first_sheet; + this.last_sheet = last_sheet; + } + + /** + * Parses a range in A1 notation and returns the equivalent CellRangeRef. + */ + public static CellRangeRef fromA1(String reference) { + CellRangeRef ret = new CellRangeRef(); + String range; + + { + String[] parts = ExcelTools.stripSheetNameFromRange(reference); + ret.first_sheet_name = parts[0]; + range = parts[1]; + ret.last_sheet_name = parts[2]; + } + + if (range == null) + throw new IllegalArgumentException("missing range component"); + + { + int[] parts = ExcelTools.getRangeRowCol(range); + ret.first_row = parts[0]; + ret.first_col = parts[1]; + ret.last_row = parts[2]; + ret.last_col = parts[3]; + } + + return ret; + } + + /** + * Convenience method combining {@link #fromA1(String)} and + * {@link #resolve(WorkBookHandle)}. + */ + public static CellRangeRef fromA1(String reference, WorkBookHandle book) throws WorkSheetNotFoundException { + CellRangeRef ret = fromA1(reference); + ret.resolve(book); + return ret; + } + + /** + * Resolves sheet names into sheet handles against the given book. + * + * @param book the book against which the sheet names should be resolved + * @throws WorkSheetNotFoundException if either of the sheets does not exist in the given book + */ + public void resolve(WorkBookHandle book) throws WorkSheetNotFoundException { + if (first_sheet_name != null) + first_sheet = book.getWorkSheet(first_sheet_name); + if (last_sheet_name != null) + last_sheet = book.getWorkSheet(last_sheet_name); + } + + /** + * Returns the lowest-indexed row in this range. + * + * @return the row index or null if this is a column range + */ + public int getFirstRow() { + return first_row; + } + + /** + * Returns the lowest-indexed column in this range. + * + * @return the column index or null if this is a row range + */ + public int getFirstColumn() { + return first_col; + } + + /** + * Returns the highest-indexed row in this range. + * + * @return the row index or null if this is a column range + */ + public int getLastRow() { + return last_row; + } + + /** + * Returns the highest-indexed column in this range. + * + * @return the column index or null if this is a row range + */ + public int getLastColumn() { + return last_col; + } + + /** + * Returns the name of the first sheet in this range. + * + * @return the name of the sheet or null if this range is not qualified with a + * sheet + */ + public String getFirstSheetName() { + if (first_sheet != null) + return first_sheet.getSheetName(); + else + return first_sheet_name; + } + + /** + * Returns the first sheet in this range. + * + * @return the WorkSheetHandle or null if this range is not qualified with a + * sheet or the sheet names have not been resolved + */ + public WorkSheetHandle getFirstSheet() { + return first_sheet; + } + + /** + * Returns the name of the last sheet in this range. + * + * @return the name of the sheet or null if this range is not qualified with a + * sheet + */ + public String getLastSheetName() { + if (last_sheet != null) + return last_sheet.getSheetName(); + else + return last_sheet_name; + } + + /** + * Returns the last sheet in this range. + * + * @return the WorkSheetHandle or null if this range is not qualified with a + * sheet or the sheet names have not been resolved + */ + public WorkSheetHandle getLastSheet() { + return last_sheet; + } + + /** + * Determines whether this range is qualified with a sheet. + */ + public boolean hasSheet() { + return first_sheet != null || first_sheet_name != null; + } + + /** + * Determines whether this range spans multiple sheets. + */ + public boolean isMultiSheet() { + return (first_sheet != null && last_sheet != null && first_sheet != last_sheet) + || (first_sheet_name != null && last_sheet_name != null && first_sheet_name != last_sheet_name); + } + + /** + * Sets the first row in this range. + * + * @param value the row index to set + */ + public void setFirstRow(int value) { + first_row = value; + } + + /** + * Sets the first column in this range. + * + * @param value the column index to set + */ + public void setFirstColumn(int value) { + first_col = value; + } + + /** + * Sets the first sheet in this range. + */ + public void setFirstSheet(WorkSheetHandle sheet) { + first_sheet_name = null; + first_sheet = sheet; + } + + /** + * Sets the last row in this range. + * + * @param value the row index to set + */ + public void setLastRow(int value) { + last_row = value; + } + + /** + * Sets the last column in this range. + * + * @param value the column index to set + */ + public void setLastColumn(int value) { + last_col = value; + } + + /** + * Sets the last sheet in this range. + */ + public void setLastSheet(WorkSheetHandle sheet) { + last_sheet_name = null; + last_sheet = sheet; + } + + /** + * Returns whether this range entirely contains the given range. This ignores + * the sheets, if any, and compares only the cell ranges. + */ + public boolean contains(CellRangeRef range) { + return this.first_row <= range.first_row && this.last_row >= range.last_row && this.first_col <= range.first_col + && this.last_col >= range.last_col; + } + + /** + * Compares this range to the specified object. The result is true + * if and only if the argument is not null and is a + * CellRangeRef object that represents the same range as this + * object. + */ + @Override + public boolean equals(Object other) { + // if it's null or not a CellRangeRef it can't be equal + if (other == null || !(other instanceof CellRangeRef)) + return false; + + return this.toString().equals(other.toString()); + } + + /** + * Creates and returns a copy of this range. + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // This can't happen (we're Cloneable) but we have to catch it + throw new Error("Object.clone() threw CNSE but we're Cloneable"); + } + } + + /** + * Gets this range in A1 notation. + */ + @Override + public String toString() { + String sheet1 = getFirstSheetName(); + String sheet2 = getLastSheetName(); + return (sheet1 != null ? sheet1 + (sheet2 != null && sheet2 != sheet1 ? ":" + sheet2 : "") + "!" : "") + + ExcelTools.formatRange(new int[]{first_col, first_row, last_col, last_row}); + } } diff --git a/src/main/java/io/starter/OpenXLS/ChartHandle.java b/src/main/java/io/starter/OpenXLS/ChartHandle.java index 2b67851..51c3179 100644 --- a/src/main/java/io/starter/OpenXLS/ChartHandle.java +++ b/src/main/java/io/starter/OpenXLS/ChartHandle.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,37 +29,20 @@ */ package io.starter.OpenXLS; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Vector; - +import io.starter.formats.OOXML.*; +import io.starter.formats.XLS.*; +import io.starter.formats.XLS.charts.*; +import io.starter.toolkit.Logger; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserFactory; -import io.starter.formats.OOXML.Layout; -import io.starter.formats.OOXML.OOXMLConstants; -import io.starter.formats.OOXML.SpPr; -import io.starter.formats.OOXML.Title; -import io.starter.formats.OOXML.TwoCellAnchor; -import io.starter.formats.OOXML.TxPr; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.CellNotFoundException; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.charts.Axis; -import io.starter.formats.XLS.charts.Chart; -import io.starter.formats.XLS.charts.ChartConstants; -import io.starter.formats.XLS.charts.ChartType; -import io.starter.formats.XLS.charts.OOXMLChart; -import io.starter.formats.XLS.charts.Series; -import io.starter.formats.XLS.charts.ThreeD; -import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Vector; /** * Chart Handle allows for manipulation of Charts within a WorkBook.
    @@ -74,3867 +57,3735 @@ * To Obtain an array of existing chart handles, use
    * WorkBookHandle.getCharts()
    * or WorkBookHandle.getCharts(chart title) - * + * * Starter Inc. - * + * * @see WorkBookHandle - * */ public class ChartHandle implements ChartConstants { - // 20080114 KSC: delegate so visible - public static final int BARCHART = ChartConstants.BARCHART; - public static final int COLCHART = ChartConstants.COLCHART; - public static final int LINECHART = ChartConstants.LINECHART; - public static final int PIECHART = ChartConstants.PIECHART; - public static final int AREACHART = ChartConstants.AREACHART; - public static final int SCATTERCHART = ChartConstants.SCATTERCHART; - public static final int RADARCHART = ChartConstants.RADARCHART; - public static final int SURFACECHART = ChartConstants.SURFACECHART; - public static final int DOUGHNUTCHART = ChartConstants.DOUGHNUTCHART; - public static final int BUBBLECHART = ChartConstants.BUBBLECHART; - public static final int OFPIECHART = ChartConstants.OFPIECHART; - public static final int PYRAMIDCHART = ChartConstants.PYRAMIDCHART; - public static final int CYLINDERCHART = ChartConstants.CYLINDERCHART; - public static final int CONECHART = ChartConstants.CONECHART; - public static final int PYRAMIDBARCHART = ChartConstants.PYRAMIDBARCHART; - public static final int CYLINDERBARCHART = ChartConstants.CYLINDERBARCHART; - public static final int CONEBARCHART = ChartConstants.CONEBARCHART; - public static final int RADARAREACHART = ChartConstants.RADARAREACHART; - public static final int STOCKCHART = ChartConstants.STOCKCHART; - - // legacy - public static final int BAR = BARCHART; - public static final int COL = COLCHART; - public static final int LINE = LINECHART; - public static final int PIE = PIECHART; - public static final int AREA = AREACHART; - public static final int SCATTER = SCATTERCHART; - public static final int RADAR = RADARCHART; - public static final int SURFACE = SURFACECHART; - public static final int DOUGHNUT = DOUGHNUTCHART; - public static final int BUBBLE = BUBBLECHART; - public static final int RADARAREA = RADARAREACHART; - public static final int PYRAMID = PYRAMIDCHART; - public static final int CYLINDER = CYLINDERCHART; - public static final int CONE = CONECHART; - public static final int PYRAMIDBAR = PYRAMIDBARCHART; - public static final int CYLINDERBAR = CYLINDERBARCHART; - public static final int CONEBAR = CONEBARCHART; - // axis types - public static final int XAXIS = ChartConstants.XAXIS; - public static final int YAXIS = ChartConstants.YAXIS; - public static final int ZAXIS = ChartConstants.ZAXIS; - public static final int XVALAXIS = ChartConstants.XVALAXIS; // an - // X - // axis - // type - // but - // VAL - // records - // coordinates - public static final int X = 0; - public static final int Y = 1; - public static final int WIDTH = 2; - public static final int HEIGHT = 3; - - protected WorkBookHandle wbh; - private Chart mychart; - - /** - * Constructor which creates a new ChartHandle from an existing Chart Object - * - * @param Chart - * c - the source Chart object - * @param WorkBookHandle - * wb - the parent WorkBookHandle - */ - public ChartHandle(Chart c, WorkBookHandle wb) { - // super(); - mychart = c; - wbh = wb; - if (mychart.getWorkBook() == null) // TODO: WHY IS THIS NULL???? - mychart.setWorkBook(wb.getWorkBook()); - } - - /** - * Returns the title of the Chart - * - * @return String title of the Chart - */ - public String getTitle() { - return mychart.getTitle(); - } - - /** - * Sets the title of the Chart - * - * @param String - * title - Chart title - */ - public void setTitle(String title) { - this.mychart.setTitle(title); - } - - /** - * returns the data range used by the chart - * - * @return - */ - public String getDataRangeJSON() { - return this.mychart.getChartSeries().getDataRangeJSON().toString(); - } - - public int[] getEncompassingDataRange() { - return getEncompassingDataRange(this.mychart.getChartSeries() - .getDataRangeJSON()); - } - - /** - * returns the encompassing range for this chart, or null if the chart data is - * too complex to represent - * - * @param jsonDataRange - * @return - */ - public static int[] getEncompassingDataRange(JSONObject jsonDataRange) { - try { - String catrange = jsonDataRange.get("c").toString(); - String sheet = catrange.substring(0, catrange.indexOf('!')); - int[] retVals = ExcelTools.getRangeRowCol(catrange); - int nSeries = jsonDataRange.getJSONArray("Series").length(); - for (int i = 0; i < nSeries; i++) { - JSONObject series = (JSONObject) jsonDataRange - .getJSONArray("Series").get(i); - String serrange = series.get("v").toString(); - if (!serrange.startsWith(sheet)) - continue; - int[] locs = ExcelTools.getRangeRowCol(serrange); - try { - if (locs[0] < retVals[0]) - retVals[0] = locs[0]; - if (locs[1] < retVals[1]) - retVals[1] = locs[1]; - if (locs[2] > retVals[2]) - retVals[2] = locs[2]; - if (locs[3] > retVals[3]) - retVals[3] = locs[3]; - - String legendrange = series.get("l").toString(); - locs = ExcelTools.getRowColFromString(legendrange); - if (locs[0] < retVals[0]) - retVals[0] = locs[0]; - if (locs[1] < retVals[1]) - retVals[1] = locs[1]; - if (locs[0] > retVals[2]) - retVals[2] = locs[0]; - if (locs[1] > retVals[3]) - retVals[3] = locs[1]; - - if (series.has("b")) { - String bubblerange = series.get("b").toString(); - locs = ExcelTools.getRangeRowCol(serrange); - if (locs[0] < retVals[0]) - retVals[0] = locs[0]; - if (locs[1] < retVals[1]) - retVals[1] = locs[1]; - if (locs[2] > retVals[2]) - retVals[2] = locs[2]; - if (locs[3] > retVals[3]) - retVals[3] = locs[3]; - } - } catch (Exception e) { - // just continue - } - } - return retVals; - } catch (Exception e) {} - return null; - /* - * while (ptgs.hasNext()) { PtgRef pr= (PtgRef) ptgs.next(); - * // PtgRef pr= - * (PtgRef)refs[i]; int[] locs = pr.getIntLocation(); for - * (int x=0;x<2;x++) { - * if((locs[x]retValues[x]))retValues[x]=locs[x]; } i++; } - */ - } - - /** - * returns the ordinal id associated with the underlying Chart Object - * - * @return int chart id - * @see WorkBookHandle.getChartById - */ - public int getId() { - return this.mychart.getId(); - } - - /** - * returns the string representation of this ChartHandle - */ - @Override - public String toString() { - return mychart.getTitle(); - } - - /***************************************************************************************************************************************/ - /** - * Returns an ordered array of strings representing all the series ranges in the - * Chart.
    - * Each series can only represent one bar, line or wedge of data. - * - * @see ChartHandle.getCategories - * @return String[] each item being a Cell Range representing one bar, line or - * wedge of data in the Chart - */ - public String[] getSeries() { - return mychart.getSeries(-1); // -1 is flag for all rather than for a - // specific chart - } - - /** - * Returns an ordered array of strings representing all the category ranges in - * the chart.
    - * This vector corresponds to the getSeries() method so will often contain - * duplicates, as while the series data changes frequently, category data is the - * same throughout the chart. - * - * @see ChartHandle.getSeries - * @return String[] each item being a Cell Range representing the Category Data - */ - public String[] getCategories() { - return getCategories(-1); // -1 is flag for all rather than for a - // specific chart - } - - /** - * Returns an ordered array of strings representing all the category ranges in - * the chart.
    - * This vector corresponds to the getSeries() method so will often contain - * duplicates, as while the series data changes frequently, category data is the - * same throughout the chart. - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @see ChartHandle.getSeries - * @return String[] each item being a Cell Range representing the Category Data - */ - private String[] getCategories(int nChart) { - return mychart.getCategories(nChart); - } - - /** - * Returns an array of ChartSeriesHandle Objects, one for each bar, line or - * wedge of data. - * - * @return ChartSeriesHandle[] Array of ChartSeriesHandle Objects representing - * Chart Series Data (Series and Categories) - * @see ChartSeriesHandle - */ - public ChartSeriesHandle[] getAllChartSeriesHandles() { - return getAllChartSeriesHandles(-1); // get ALL - } - - /** - * Returns an array of ChartSeriesHandle Objects for the desired chart, one for - * each bar, line or wedge of data.
    - * A chart number of 0 means the default chart, 1-9 indicate series for overlay - * charts
    - * NOTE: using this method returns the series for the desired chart ONLY
    - * You MUST use the corresponding removeSeries(index, nChart) when removing - * series to properly match the series index. Otherwise a mismatch will occur. - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return ChartSeriesHandle[] Array of ChartSeriesHandle Objects representing - * Chart Series Data (Series and Categories) - * @see ChartSeriesHandle - */ - public ChartSeriesHandle[] getAllChartSeriesHandles(int nChart) { - Vector v = mychart.getAllSeries(nChart); - ChartSeriesHandle[] csh = new ChartSeriesHandle[v.size()]; - for (int i = 0; i < v.size(); i++) { - Series s = (Series) v.get(i); - csh[i] = new ChartSeriesHandle(s, this.wbh); - } - return csh; - } - - /** - * Get the ChartSeriesHandle representing Chart Series Data (Series and - * Categories) for the specified Series range - * - * @param String - * seriesRange - For example, "Sheet1!A12:A21" - * @return ChartSeriesHandle - * @see ChartSeriesHandle - */ - public ChartSeriesHandle getChartSeriesHandle(String seriesRange) { - ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); - for (int i = 0; i < series.length; i++) { - String sr = series[i].getSeriesRange(); - if (seriesRange.equalsIgnoreCase(sr)) { - return series[i]; - } - } - return null; - } - - /** - * Get the ChartSeriesHandle representing Chart Series Data (Series and - * Categories) for the specified Series index - * - * @param int - * idx - the index (0 based) of the series - * @see ChartSeriesHandle - * @return ChartSeriesHandle - */ - public ChartSeriesHandle getChartSeriesHandle(int idx) { - ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); - if (series.length >= idx) - return series[idx]; - return null; - } - - /** - * Get the ChartSeriesHandle representing Chart Series Data (Series and - * Categories) for the Series specified by label (legend) - * - * @param String - * legend - label for the desired series - * @return ChartSeriesHandle - * @see ChartSeriesHandle - */ - public ChartSeriesHandle getChartSeriesHandleByName(String legend) { - Series s = mychart.getSeries(legend, -1); // -1 is flag for all rather - // than for a specific chart - return new ChartSeriesHandle(s, this.wbh); - } - - /** - * sets or removes the axis title - * - * @param axisType - * one of: XAXIS, YAXIS, ZAXIS - * @param ttl - * String new title or null to remove - */ - public void setAxisTitle(int axisType, String ttl) { - mychart.getAxes().setTitle(axisType, ttl); - } - - /** - * returns the Y axis Title - * - * @return String title - */ - public String getYAxisLabel() { - return mychart.getAxes().getTitle(YAXIS); - } - - /** - * Sets the Y axis Title - * - * @param String - * yTitle - new Y Axis title - */ - public void setYAxisLabel(String yTitle) { - mychart.getAxes().setTitle(YAXIS, yTitle); - } - - /** - * returns the X axis Title - * - * @return String title - */ - public String getXAxisLabel() { - return mychart.getAxes().getTitle(XAXIS); - } - - /** - * Sets the XAxisTitle - * - * @param String - * xTitle - new X Axis title - */ - public void setXAxisLabel(String xTitle) { - mychart.getAxes().setTitle(XAXIS, xTitle); - } - - /** - * returns the Z axis Title, if any - * - * @return String Title - */ - public String getZAxisLabel() { - return mychart.getAxes().getTitle(ZAXIS); - } - - /** - * set the Z AxisTitle - * - * @param String - * zTitle - new Z Axis Title - */ - public void setZAxisLabel(String zTitle) { - mychart.getAxes().setTitle(ZAXIS, zTitle); - } - - /** - * Sets the automatic scale option on or off for the desired Value axis - *

    - * The Value axis contains numbers rather than labels, and is normally the Y - * axis, but Scatter and Bubble charts may have a value axis on the X Axis as - * well - *

    - * Automatic Scaling automatically sets the scale maximum, minimum and tick - * units upon data changes, and is the default setting for charts - * - * @param int - * axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, - * ChartHandle.ZAXIS - * @param boolean - * b - true if set Automatic scaling on, false otherwise - * @see setAxisAutomaticScale(boolean b) - */ - public void setAxisAutomaticScale(int axisType, boolean b) { - mychart.getAxes().setAxisAutomaticScale(axisType, b); - mychart.setDirtyFlag(true); - } - - /** - * Returns the minimum scale value of the the desired Value axis - *

    - * The Value axis contains numbers rather than labels, and is normally the Y - * axis, but Scatter and Bubble charts may have a value axis on the X Axis as - * well - * - * @param int - * axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, - * ChartHandle.ZAXIS - * @return int Miminum Scale value for the desired axis - * @see getAxisMinScale() - */ - public double getAxisMinScale(int axisType) { - double[] minmax = mychart.getMinMax(this.wbh); - return mychart.getAxes().getMinMax(minmax[0], minmax[1], axisType)[0]; - } - - /** - * Returns the maximum scale value of the desired Value axis - *

    - * The Value axis contains numbers rather than labels, and is normally the Y - * axis, but Scatter and Bubble charts may have a value axis on the X Axis as - * well - * - * @param int - * axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, - * ChartHandle.ZAXIS - * @return int - Maximum Scale value for the desired axis - * @see getAxisMaxScale() - */ - public double getAxisMaxScale(int axisType) { - double[] minmax = mychart.getMinMax(this.wbh); // -1 is flag for all - // rather than for a - // specific chart - return mychart.getAxes().getMinMax(minmax[0], minmax[1], axisType)[1]; - } - - /** - * Returns the major tick unit of the desired Value axis - *

    - * The Value axis contains numbers rather than labels, and is normally the Y - * axis, but Scatter and Bubble charts may have a value axis on the X Axis as - * well - * - * @param int - * axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, - * ChartHandle.ZAXIS - * @return int major tick unit - * @see getAxisMajorUnit() - */ - public int getAxisMajorUnit(int axisType) { - return mychart.getAxes().getAxisMajorUnit(axisType); - } - - /** - * Returns the minor tick unit of the desired Value axis - *

    - * The Value axis contains numbers rather than labels, and is normally the Y - * axis, but Scatter and Bubble charts may have a value axis on the X Axis as - * well - * - * @param int - * axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, - * ChartHandle.ZAXIS - * @return int - minor tick unit of the desired axis - * @see getAxisMinorUnit() - */ - public int getAxisMinorUnit(int axisType) { - return mychart.getAxes().getAxisMinorUnit(axisType); - } - - /** - * Sets the maximum scale value of the desired Value axis - *

    - * The Value axis contains numbers rather than labels, and is normally the Y - * axis, but Scatter and Bubble charts may have a value axis on the X Axis as - * well - *

    - * Note: The default scale setting for charts is known as Automatic Scaling
    - * When data changes, the chart automatically adjusts the scale as necessary - *
    - * Setting the scale manually (either Minimum or Maximum Value) removes - * Automatic Scaling - * - * @param int - * axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, - * ChartHandle.ZAXIS - * @param int - * MaxValue - desired maximum value of the desired axis - * @see setAxisMax(int MaxValue) - */ - public void setAxisMax(int axisType, int MaxValue) { - mychart.getAxes().setAxisMax(axisType, MaxValue); - mychart.setDirtyFlag(true); - } - - /** - * Sets the minimum scale value of the desired Value axis - *

    - * The Value axis contains numbers rather than labels, and is normally the Y - * axis, but Scatter and Bubble charts may have a value axis on the X Axis as - * well - *

    - * Note: The default setting for charts is known as Automatic Scaling
    - * When data values change, the chart automatically adjusts the scale as - * necessary
    - * Setting the scale manually (either Minimum or Maximum Value) removes - * Automatic Scaling - * - * @param int - * axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, - * ChartHandle.ZAXIS - * @param int - * MinValue - the desired Minimum scale value - * @see setAxisMin(int MinValue) - */ - public void setAxisMin(int axisType, int MinValue) { - mychart.getAxes().setAxisMin(axisType, MinValue); - mychart.setDirtyFlag(true); - } - - /** - * Returns true if the desired Value axis is set to automatic scale - *

    - * The Value axis contains numbers rather than labels, and is normally the Y - * axis, but Scatter and Bubble charts may have a value axis on the X Axis as - * well - *

    - * Note: The default setting for charts is known as Automatic Scaling
    - * When data changes, the chart automatically adjusts the scale as necessary - *
    - * Setting the scale manually (either Minimum or Maximum Value) removes - * Automatic Scaling - * - * @param int - * axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, - * ChartHandle.ZAXIS - * @return boolean true if Automatic Scaling is turned on - * @see getAxisAutomaticScale() - */ - public boolean getAxisAutomaticScale(int axisType) { - return mychart.getAxes().getAxisAutomaticScale(axisType); - } - - /** - * Returns true if the Y Axis (Value axis) is set to automatic scale - *

    - * The default setting for charts is known as Automatic Scaling
    - * When data changes, the chart automatically adjusts the scale (minimum, - * maximum values plus major and minor tick units) as necessary - * - * @return boolean true if Automatic Scaling is turned on - * @see getAxisAutomaticScale(int axisType) - */ - public boolean getAxisAutomaticScale() { - return mychart.getAxisAutomaticScale(); - } - - /** - * Sets the automatic scale option on or off for the Y Axis (Value axis) - *

    - * Automatic Scaling will automatically set the scale maximum, minimum and tick - * units upon data changes, and is the default chart setting - * - * @param b - * @see setAxisAutomaticScale(int axisType boolean b) - */ - public void setAxisAutomaticScale(boolean b) { - mychart.getAxes().setAxisAutomaticScale(b); - mychart.setDirtyFlag(true); - } - - /** - * Returns the minimum value of the Y Axis (Value Axis) scale - * - * @return int Miminum Scale value for Y axis - * @see getAxisMinScale(int axisType) - */ - public double getAxisMinScale() { - double[] minmax = mychart.getMinMax(this.wbh); // -1 is flag for all - // rather than for a - // specific chart - return mychart.getAxes().getMinMax(minmax[0], minmax[1])[0]; - } - - /** - * Returns the maximum value of the Y Axis (Value Axis) scale - * - * @return int Maximum Scale value for Y axis - * @see getAxisMaxScale(int axisType) - */ - public double getAxisMaxScale() { - double[] minmax = mychart.getMinMax(this.wbh); - return mychart.getAxes().getMinMax(minmax[0], minmax[1])[1]; - } - - /** - * Returns the major tick unit of the Y Axis (Value Axis) - * - * @return int major tick unit - * @see getAxisMajorUnit(int axisType) - */ - public int getAxisMajorUnit() { - double[] minmax = mychart.getMinMax(this.wbh); - return (int) mychart.getAxes().getMinMax(minmax[0], minmax[1])[2]; - } - - /** - * Returns the minor tick unit of the Y Axis (Value Axis) - * - * @return int minor tick unit - * @see getAxisMinorUnit(int axisType) - */ - public int getAxisMinorUnit() { - double[] minmax = mychart.getMinMax(this.wbh); - return (int) mychart.getAxes().getMinMax(minmax[0], minmax[1])[1]; - } - - /** - * Sets the maximum value of the Y Axis (Value Axis) Scale - *

    - * Note: The default scale setting for charts is known as Automatic Scaling
    - * When data changes, the chart automatically adjusts the scale as necessary - *
    - * Setting the scale manually (either Minimum or Maximum Value) removes - * Automatic Scaling - * - * @param int - * MaxValue - the desired maximum scale value - * @see ChartHandle.setAxisMax(int axisType, int MaxValue) - */ - public void setAxisMax(int MaxValue) { - mychart.getAxes().setAxisMax(MaxValue); - mychart.setDirtyFlag(true); - } - - /** - * Sets the minimum value of the Y Axis (Value Axis) Scale - *

    - * Note: The default setting for charts is known as Automatic Scaling
    - * When data changes, the chart automatically adjusts the scale as necessary - *
    - * Setting the scale manually (either Minimum or Maximum Value) removes - * Automatic Scaling - * - * @param int - * MinValue - the desired minimum scale value - * @see ChartHandle.setAxisMin(int axisType, int MinValue) - */ - public void setAxisMin(int MinValue) { - mychart.getAxes().setAxisMin(MinValue); - mychart.setDirtyFlag(true); - } - - /** - * sets an option for this axis - * - * @param axisType - * one of: XAXIS, YAXIS, ZAXIS - * @param op - * option name; one of: CatCross, LabelCross, Marks, CrossBetween, - * CrossMax, MajorGridLines, AddArea, AreaFg, AreaBg or Linked Text - * Display options: Label, ShowKey, ShowValue, ShowLabelPct, ShowPct, - * ShowCatLabel, ShowBubbleSizes, TextRotation, Font - * @param val - * option value - */ - public void setAxisOption(int axisType, String op, String val) { - mychart.getAxes().setChartOption(axisType, op, val); - - } - - /** - * set the font for the chart title - * - * @param String - * name - font name - * @param int - * height - font height in 1/20 point units - * @param boolean - * bold - true if bold - * @param boolean - * italic - true if italic - * @param boolean - * underline - true if underlined - */ - public void setTitleFont(String name, int height, boolean bold, boolean italic, boolean underline) { - Font f = new Font(name, 200, height); - f.setBold(bold); - f.setItalic(italic); - f.setUnderlined(underline); - int idx = wbh.getWorkBook().getFontIdx(f); - if (idx == -1) { // font doesn't exist yet, add to streamer - f.setIdx(-1); // flag to insert - idx = wbh.getWorkBook().insertFont(f) + 1; - } - mychart.setTitleFont(idx); - } - - /** - * set the font for the Chart Title - * - * @param io.starter.Formats.XLS.Font - * f - desired font for the Chart Title - * @see Font - */ - public void setTitleFont(Font f) { - int idx = wbh.getWorkBook().getFontIdx(f); - if (idx == -1) { // font doesn't exist yet, add to streamer - f.setIdx(-1); // flag to insert - idx = wbh.getWorkBook().insertFont(f) + 1; - } - mychart.setTitleFont(idx); - } - - /** - * returns the Font associated with the Chart Title - * - * @return io.starter.Formats.XLS.Font - * @see Font - */ - public Font getTitleFont() { - return mychart.getTitleFont(); - } - - /** - * set the font for all axes on the chart - * - * @param String - * name - font name - * @param int - * height - font height in 1/20 point units - * @param boolean - * bold - true if bold - * @param boolean - * italic - true if italic - * @param boolean - * underline - true if underlined - */ - public void setAxisFont(String name, int height, boolean bold, boolean italic, boolean underline) { - Font f = new Font(name, 200, height); - f.setBold(bold); - f.setItalic(italic); - f.setUnderlined(underline); - int idx = wbh.getWorkBook().getFontIdx(f); - if (idx == -1) { // font doesn't exist yet, add to streamer - f.setIdx(-1); // flag to insert - idx = wbh.getWorkBook().insertFont(f) + 1; - } - mychart.getAxes().setTitleFont(XAXIS, idx); - mychart.getAxes().setTitleFont(YAXIS, idx); - mychart.getAxes().setTitleFont(ZAXIS, idx); - mychart.setDirtyFlag(true); - } - - /** - * set the font for all axes on the Chart - * - * @param io.starter.Formats.XLS.Font - * f - desired font for the Chart axes - * @see Font - */ - public void setAxisFont(Font f) { - int idx = wbh.getWorkBook().getFontIdx(f); - if (idx == -1) { // font doesn't exist yet, add to streamer - f.setIdx(-1); // flag to insert - idx = wbh.getWorkBook().insertFont(f) + 1; - } - mychart.getAxes().setTitleFont(XAXIS, idx); - mychart.getAxes().setTitleFont(YAXIS, idx); - mychart.getAxes().setTitleFont(ZAXIS, idx); - mychart.setDirtyFlag(true); - } - - /** - * return the Font associated with the Chart Axes - * - * @return io.starter.Formats.XLS.Font for Chart Axes or null if no axes - * @see Font - */ - public Font getAxisFont() { - Font f = null; - f = mychart.getAxes().getTitleFont(XAXIS); - if (f != null) - return f; - f = mychart.getAxes().getTitleFont(YAXIS); - if (f != null) - return f; - f = mychart.getAxes().getTitleFont(ZAXIS); - if (f != null) - return f; - return null; - } - - /** - * resets all fonts in the chart to the default font of the workbook - */ - public void resetFonts() { - mychart.resetFonts(); - } - - /** - * returns the underlhying Sheet Object this Chart is attached to
    - * For Internal Use - * - * @return Boundsheet - */ - public Boundsheet getSheet() { - return mychart.getSheet(); - } - - /** - * return the background color of this chart's Plot Area as an int - * - * @return int background color constant - * @see FormatHandle.COLOR_* constants - */ - public int getPlotAreaBgColor() { - String bg = mychart.getPlotAreaBgColor(); - return FormatHandle.HexStringToColorInt(bg, (short) 0); - } - - public String getPlotAreaBgColorStr() { - return mychart.getPlotAreaBgColor(); - } - - /** - * sets the Plot Area background color - * - * @param int - * bg - color constant - * @see FormatHandle.COLOR_* constants - */ - public void setPlotAreaBgColor(int bg) { - mychart.setPlotAreaBgColor(bg); - } - - /** - * Change the value of a Chart object.
    - * NOTE: THIS HAS NOT BEEN 100% IMPLEMENTED YET
    - * You can use this method to change: - * - *
    - * - the Title of the Chart
    - * - the Text Labels of Categories and Values (X and Y) - * - *
    - * eg: - * - *
    - * To change the value of the Chart title
    - * chart.changeObjectValue("Template Chart Title", "Widget Sales By Quarter"); - * - *
    - * To change the text label of the categories
    - * chart.changeObjectValue("Category X", "Fiscal Year"); - * - *
    - * To change the text label of the values
    - * chart.changeObjectValue("Value Y", "Sales in US$"); - * - * @param String - * originalval - One of: "Template Chart Title", "Category X" or - * "Value Y" - * @param Sring - * newval - the new setting - * @return whether the change was successful - * - */ - public boolean changeTextValue(String originalval, String newval) { - /* - * KSC: TODO: Refactor *** for(int - * x=0;x - * To see possible Chart Types, view the public static int's in ChartHandle. - *
    - * Possible Chart Types:
    - * BARCHART
    - * COLCHART
    - * LINECHART
    - * PIECHART
    - * AREACHART
    - * SCATTERCHART
    - * RADARCHART
    - * SURFACECHART
    - * DOUGHNUTCHART
    - * BUBBLECHART
    - * RADARAREACHART
    - * PYRAMIDCHART
    - * CYLINDERCHART
    - * CONECHART
    - * PYRAMIDBARCHART
    - * CYLINDERBARCHART
    - * CONEBAR - * - * @param int - * chartType - representing the chart type - */ - public void setChartType(int chartType) { - mychart.setChartType(chartType, 0, EnumSet.noneOf(ChartOptions.class)); // no - // specific - // options - } - - /** - * Chart Options. CLUSTERED, bar, col charts only STACKED, PERCENTSTACKED, 100% - * stacked THREED, 3d Effect EXPLODED, Pie, Donut HASLINES, Scatter, Line charts - * ... WIREFRAME, Surface DROPLINES, DOWNBARS, line, stock UPDOWNBARS, line, - * stock SERLINES, bar, ofpie
    - * Use these chart options when creating new charts
    - * A chart may have multiple chart options e.g. 3D Exploded pie chart - * - * @see ChartHandle.createNewChart - */ - // need: hasMarkers **** - public enum ChartOptions { - CLUSTERED, - /** bar, col charts only */ - STACKED, - PERCENTSTACKED, - /** 100% stacked */ - THREED, - /** 3d Effect */ - EXPLODED, - /** Pie, Donut */ - HASLINES, - /** Scatter, Line */ - SMOOTHLINES, - /** Scatter, Line, Radar */ - WIREFRAME, - /** Surface */ - DROPLINES, - /** line, area, stock charts */ - UPDOWNBARS, - /** line, stock */ - SERLINES, - /** bar, ofpie */ - HILOWLINES, - /** line, stock charts */ - FILLED /** radar */ - } - - /** - * Static method to create a new chart on WorkSheet sheet of type chartType with - * chart Options options
    - * After creating, you can set the chart title via ChartHandle.setTitle
    - * and Position via ChartHandle.setRelativeBounds (row/col-based) or - * ChartHandle.setCoords (pixel-based)
    - * as well as several other customizations possible - * - * @param book - * WorkBookHandle - * @param sheet - * WorkSheetHandle - * @param chartType - * one of:
    - * BARCHART
    - * COLCHART
    - * LINECHART
    - * PIECHART
    - * AREACHART
    - * SCATTERCHART
    - * RADARCHART
    - * SURFACECHART
    - * DOUGHNUTCHART
    - * BUBBLECHART
    - * RADARAREACHART
    - * PYRAMIDCHART
    - * CYLINDERCHART
    - * CONECHART
    - * PYRAMIDBARCHART
    - * CYLINDERBARCHART
    - * CONEBARCHART - * @param options - * EnumSet - * @see ChartHandle.ChartOptions - * @see setChartType - * @return - */ - public static ChartHandle createNewChart(WorkSheetHandle sheet, int chartType, EnumSet options) { - // Create Initial Basic Chart - ChartHandle cht = sheet.getWorkBook().createChart("", sheet); - // Change Chart Type with Desired Options: - cht.setChartType(chartType, 0, options); - return cht; - } - - /** - * Sets the Chart type to the specified type (no 3d, no stacked ...)
    - * To see possible Chart Types, view the public static int's in ChartHandle. - *
    - * Possible Chart Types:
    - * BARCHART
    - * COLCHART
    - * LINECHART
    - * PIECHART
    - * AREACHART
    - * SCATTERCHART
    - * RADARCHART
    - * SURFACECHART
    - * DOUGHNUTCHART
    - * BUBBLECHART
    - * RADARAREACHART
    - * PYRAMIDCHART
    - * CYLINDERCHART
    - * CONECHART
    - * PYRAMIDBARCHART
    - * CYLINDERBARCHART
    - * CONEBARCHART - * - * @param int - * chartType - representing the chart type - * @param nChart - * - 0 (default) or 1-9 for complex overlay charts - * @param EnumSet - * 0 or more chart options (Such as Stacked, Exploded ...) - * @see ChartHandle.ChartOptions - */ - public void setChartType(int chartType, int nChart, EnumSet options) { - mychart.setChartType(chartType, nChart, options); - } - - /** - * Sets the basic chart type (no 3d, stacked...) for multiple or overlay Charts. - *
    - * You can specify the drawing order of the Chart, where 0 is the default chart, - * and 1-9 are overlay charts.
    - * The default chart (chart 0) is always present; however, using this method, - * you can create a new overlay chart (up to 9 maximum).
    - * NOTE: The chart number must be unique and in order
    - * If the desired chart number is not present in the chart, a new overlay chart - * will be created.
    - * To set explicit chart options, @see setChartType(chartType, nChart, is3d, - * isStacked, is100PercentStacked)
    - *
    - * To see possible Chart Types, view the public static int's in ChartHandle. - *
    - * Possible Chart Types:
    - * BARCHART
    - * COLCHART
    - * LINECHART
    - * PIECHART
    - * AREACHART
    - * SCATTERCHART
    - * RADARCHART
    - * SURFACECHART
    - * DOUGHNUTCHART
    - * BUBBLECHART
    - * RADARAREACHART
    - * PYRAMIDCHART
    - * CYLINDERCHART
    - * CONECHART
    - * PYRAMIDBARCHART
    - * CYLINDERBARCHART
    - * CONEBARCHART - * - * @param int - * chartType - representing the chart type - * @param chartType - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - */ - public void setChartType(int chartType, int nChart) { - mychart.setChartType(chartType, nChart, EnumSet - .noneOf(ChartOptions.class)); // no specific options - } - - /** - * Return an int corresponding to this ChartHandle's Chart Type for the default - * chart
    - * To see possible Chart Types, view the public static int's in ChartHandle. - * - * @see ChartHandle static Chart Type Constants - * @see ChartHandle.setChartType - * @return int chart type - * - */ - public int getChartType() { - return mychart.getChartType(); - } - - /** - * Return an int corresponding to this ChartHandle's Chart Type for the - * specified chart
    - * To see possible Chart Types, view the public static int's in ChartHandle. - * - * @see ChartHandle static Chart Type Constants - * @see ChartHandle.setChartType - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return int chart type - */ - public int getChartType(int nChart) { - return mychart.getChartType(nChart); - } - - /** - * Sets the location lock on the Cell Reference at the specified location - * - * Used to prevent updating of the Cell Reference when Cells are moved. - * - * @param location - * of the Cell Reference to be locked/unlocked - * @param lock - * status setting - * @return boolean whether the Cell Reference was found and modified - */ - /* - * TODO: NEEDED?? public boolean setLocationLocked(String - * loc, boolean l){ int x - * = Ptg.PTG_LOCATION_POLICY_UNLOCKED; if(l)x = - * Ptg.PTG_LOCATION_POLICY_LOCKED; - * return setLocationPolicy(loc, x); } - */ - - /** - * Change the Cell Range referenced by one of the Series (a bar, line or wedge - * of data) in the Chart.
    - *
    - * For Example, if the data values for one of the Series in the Chart are - * obtained from the range Sheet1!A1:A10 and we want to add 5 more values to - * that Series, use: - * - *
    - * boolean changedOK = - * charthandle.changeSeriesRange("Sheet1!A1:A10","Sheet1!A1:A15");
    - *
    - * Please keep in mind this is only the data range; it does not include labels - * that may have been automatically created when you chose the chart range.
    - *
    - * To illustrate this, if A1 = "label" A2 = "data" A3 = "data", and we want to - * add 2 more data points, we would use: changeSeriesRange("Sheet1!A2:A3", - * "Sheet1!A2:A5");
    - *
    - * Series are always expressed as one single line of data. If your chart - * encompasses a range of rows and columns you will need to modify each of the - * series in the chart handle. To determine the series that already exist in - * your chart, utilize the String[] getSeries() method. - * - * @param String - * originalrange - the original Series (bar, line or wedge of data) - * to alter - * @param String - * newrange -the new data range - * @return whether the change was successful - */ - public boolean changeSeriesRange(String originalrange, String newrange) { - return mychart.changeSeriesRange(originalrange, newrange); - } - - /** - * Change the Cell Range representing the Categories in the Chart.
    - * Categories usually appear on the X Axis and are textual, not numeric
    - * For example: the Category values in the Chart are obtained from the range - * Sheet1!A1:A10 and we want to add 5 more categories to the chart:
    - * boolean changedOK = - * chart.changeCategoryRange("Sheet1!A1:A10","Sheet1!A1:A15");
    - * Note that Category Range is the same for each Series (bar, line or wedge of - * data)
    - * i.e. there is only one Category Range for the Chart, but there may be many - * Series Ranges - * - * @param String - * originalrange - Original Category Range - * @param String - * newrange - New Category Range - * @return true if the change was successful - */ - public boolean changeCategoryRange(String originalrange, String newrange) { - return mychart.changeCategoryRange(originalrange, newrange); - } - - /** - * Changes or adds a Series to the chart via Series Index. Each bar, line or - * wedge in a chart represents a Series.
    - * If the Series index is greater than the number of series already present in - * the chart, the series will be added to the end.
    - * Otherwise the Series at the index position will be altered.
    - * This method allows altering of every aspect of the Series: Data (Series) - * Range, Legend Cell Address, Category Range and/or Bubble Range. - * - * @param int - * index - the series index. If greater than the number of series - * already present in the chart, the series will be added to the end - * @param String - * legendCell - String representation of Legend Cell Address - * @param String - * categoryRange - String representation of Category Range (should be - * same for all series) - * @param String - * seriesRange - String representation of the Series Data Range for - * this series - * @param String - * bubbleRange - String representation of Bubble Range (representing - * bubble sizes), if bubble chart. null if not - * @return a ChartSeriesHandle representing the new or altered Series - * @throws CellNotFoundException - */ - public ChartSeriesHandle setSeries(int index, String legendCell, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { - String legendText = ""; - try { - CellHandle ICell = null; - if (legendCell != null && !legendCell.equals("")) { - // 20070707 KSC: allow addition of new cell ranges for - // legendCell (see - // OpenXLS.handleChartElement) - try { - ICell = wbh.getCell(legendCell); - } catch (CellNotFoundException c) { - int shtpos = legendCell.indexOf("!"); - if (shtpos > 0) { - String sheetstr = legendCell.substring(0, shtpos); - WorkSheetHandle sht = wbh.getWorkSheet(sheetstr); - String celstr = legendCell.substring(shtpos + 1); - ICell = sht.add("", celstr); - } - } - legendText = ICell.getStringVal(); - } - return setSeries(index, legendCell, legendText, categoryRange, seriesRange, bubbleRange); - } catch (WorkSheetNotFoundException e) { - throw new CellNotFoundException( - "Error locating cell for adding series range: " - + legendCell); - } - } - - /** - * Changes or adds a Series to the chart via Series Index. Each bar, line or - * wedge in a chart represents a Series.
    - * If the Series index is greater than the number of series already present in - * the chart, the series will be added to the end.
    - * Otherwise the Series at the index position will be altered.
    - * This method allows altering of every aspect of the Series: Data (Series) - * Range, Legend Text, Legend Cell Address, Category Range and/or Bubble Range. - * - * @param int - * index - the series index. If greater than the number of series - * already present in the chart, the series will be added to the end - * @param String - * legendCell - String representation of Legend Cell Address - * @param String - * legendText - String Legend text - * @param String - * categoryRange - String representation of Category Range (should be - * same for all series) - * @param String - * seriesRange - String representation of the Series Data Range for - * this series - * @param String - * bubbleRange - String representation of Bubble Range (representing - * bubble sizes), if bubble chart. null if not - * @return a ChartSeriesHandle representing the new or altered Series - * @throws CellNotFoundException - */ - public ChartSeriesHandle setSeries(int index, String legendCell, String legendText, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { - return setSeries(index, legendCell, legendText, categoryRange, seriesRange, bubbleRange, 0); // for - // default - // chart - } - - /** - * Changes or adds a Series to the desired Chart (either default or overlay) via - * Series Index. Each bar, line or wedge in a chart represents a Series.
    - * If the Series index is greater than the number of series already present in - * the chart, the series will be added to the end.
    - * Otherwise the Series at the index position will be altered.
    - * This method allows altering of every aspect of the Series: Data (Series) - * Range, Legend Text, Legend Cell Address, Category Range and/or Bubble Range. - * - * @param int - * index - the series index. If greater than the number of series - * already present in the chart, the series will be added to the end - * @param String - * legendCell - String representation of Legend Cell Address - * @param String - * legendText - String Legend text - * @param String - * categoryRange - String representation of Category Range (should be - * same for all series) - * @param String - * seriesRange - String representation of the Series Data Range for - * this series - * @param String - * bubbleRange - String representation of Bubble Range (representing - * bubble sizes), if bubble chart. null if not - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return a ChartSeriesHandle representing the new or altered Series - * @throws CellNotFoundException - */ - public ChartSeriesHandle setSeries(int index, String legendCell, String legendText, String categoryRange, String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { - // if (index < mychart.getAllSeries(nChart).size() && index - // >= 0) { - try { - Series s = (Series) mychart.getAllSeries(nChart).get(index); - ChartSeriesHandle csh = new ChartSeriesHandle(s, this.wbh); - csh.setSeries(legendCell, categoryRange, seriesRange, bubbleRange); - setDimensionsRecord(); - return csh; - } catch (ArrayIndexOutOfBoundsException ae) { // not found - add - return addSeriesRange(legendCell, legendText, categoryRange, seriesRange, bubbleRange, nChart); - } - } - - /** - * Adds a new Series to the chart. Each bar, line or wedge in a chart represents - * a Series. - * - * @param String - * legendAddress - The cell address defining the legend for the - * series - * @param Srring - * legendText - Text of the legend - * @param String - * categoryRange - Cell Range defining the category (normally will be - * the same range for all series) - * @param String - * seriesRange - Cell range defining the data points of the series - * @param String - * bubbleRange - Cell range defining the bubble sizes for this series - * (bubble charts only) - * @return ChartSeriesHandle representing the new series - * @throws CellNotFoundException - */ - private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { - return addSeriesRange(legendAddress, legendText, categoryRange, seriesRange, bubbleRange, 0); // for - // default - // chart - } - - /** - * Adds a new Series to the chart. Each bar, line or wedge in a chart represents - * a Series. - * - * @param String - * legendAddress - The cell address defining the legend for the - * series - * @param Srring - * legendText - Text of the legend - * @param String - * categoryRange - Cell Range defining the category (normally will be - * the same range for all series) - * @param String - * seriesRange - Cell range defining the data points of the series - * @param String - * bubbleRange - Cell range defining the bubble sizes for this series - * (bubble charts only) - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return ChartSeriesHandle representing the new series - * @throws CellNotFoundException - */ - private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText, String categoryRange, String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { - Series s = null; - if (bubbleRange == null || bubbleRange.equals("")) - s = mychart - .addSeries(seriesRange, categoryRange, "", legendAddress, legendText, nChart); - else - s = mychart - .addSeries(seriesRange, categoryRange, bubbleRange, legendAddress, legendText, nChart); - if (nChart > 0) { // must update SeriesList record for overlay charts - // TODO: FINISH - } - setDimensionsRecord(); - return new ChartSeriesHandle(s, wbh); - } - - /** - * Adds a new Series to the chart. Each bar, line or wedge in a chart represents - * a Series.
    - * An Example of adding multiple series to a chart: - *

    - * ChartHandle.addSeriesRange("Sheet1!A3", "Sheet1!B1:E1", "Sheet1:B3:E3", - * null);
    - * ChartHandle.addSeriesRange("Sheet1!A4", "Sheet1!B1:E1", "Sheet1:B4:E4", - * null);
    - * ChartHandle.addSeriesRange("Sheet1!A5", "Sheet1!B1:E1", "Sheet1:B5:E5", - * null);
    - * etc... - *

    - * Note that the category does not change, it is usually constant through - * series.
    - * Also note that the example above is for a non-bubble-type chart. - * - * @param String - * legendCell - Cell reference for the legend cell (e.g. Sheet1!A1) - * @param String - * categoryRange - Category Cell range (e.g. Sheet1!B1:B1); - * @param String - * seriesRange - Series Data range (e.g. Sheet1!B3:E3); - * @param String - * bubbleRange - Cell Range representing Bubble sizes (e.g. - * Sheet1!A2:A5); or null if chart is not of type Bubble. - * @return ChartSeriesHandle referencing the newly added series - */ - public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { - return this - .addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0); // target - // default - // chart - } - - /** - * Adds a new Series to the chart. Each bar, line or wedge in a chart represents - * a Series.
    - * An Example of adding multiple series to a chart: - *

    - * ChartHandle.addSeriesRange("Sheet1!A3", "Sheet1!B1:E1", "Sheet1:B3:E3", - * null);
    - * ChartHandle.addSeriesRange("Sheet1!A4", "Sheet1!B1:E1", "Sheet1:B4:E4", - * null);
    - * ChartHandle.addSeriesRange("Sheet1!A5", "Sheet1!B1:E1", "Sheet1:B5:E5", - * null);
    - * etc... - *

    - * Note that the category does not change, it is usually constant through - * series.
    - * Also note that the example above is for a non-bubble-type chart. - * - * @param String - * legendCell - Cell reference for the legend cell (e.g. Sheet1!A1) - * @param String - * categoryRange - Category Cell range (e.g. Sheet1!B1:B1); - * @param String - * seriesRange - Series Data range (e.g. Sheet1!B3:E3); - * @param String - * bubbleRange - Cell Range representing Bubble sizes (e.g. - * Sheet1!A2:A5); or null if chart is not of type Bubble. - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return ChartSeriesHandle referencing the newly added series - */ - public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { - String legendText = ""; - String legendAddr = ""; - try { - CellHandle ICell = null; - if (legendCell != null && !legendCell.equals("")) { - try { - ICell = wbh.getCell(legendCell); - if (legendCell.indexOf("!") == -1) - legendAddr = ICell.getWorkSheetName() + "!" - + ICell.getCellAddress(); - else - legendAddr = legendCell; - } catch (CellNotFoundException c) { - int shtpos = legendCell.indexOf("!"); - if (shtpos > 0) { - String sheetstr = legendCell.substring(0, shtpos); - WorkSheetHandle sht = wbh.getWorkSheet(sheetstr); - String celstr = legendCell.substring(shtpos + 1); - ICell = sht.add("", celstr); // TODO: Why is this being - // added? - legendAddr = celstr; - } - } - if (ICell != null) - legendText = ICell.getStringVal(); - else - legendText = legendCell; - } - Series s = null; - if (bubbleRange == null) - s = mychart - .addSeries(seriesRange, categoryRange, "", legendAddr, legendText, nChart); - else - s = mychart - .addSeries(seriesRange, categoryRange, bubbleRange, legendAddr, legendText, nChart); - setDimensionsRecord(); // update chart DIMENSIONS record upon update - // of series - return new ChartSeriesHandle(s, wbh); - } catch (WorkSheetNotFoundException e) { - throw new CellNotFoundException( - "Error locating cell for adding series range: " - + legendCell); - } - } - - /** - * Adds a new Series to the chart via CellHandles and CellRange Objects. Each - * bar, line or wedge in a chart represents a Series. - * - * @param CellHandle - * legendCell - references the legend cell for this series - * @param CellRange - * categoryRange - The CellRange referencing the category (should be - * the same for all Series) - * @param CelLRange - * seriesRange - The CellRange referencing the data points for one - * bar, line or wedge in the chart - * @param CellRange - * bubbleRange -The CellRange referencing bubble sizes for this - * series, or null if chart is not of type BUBBLE - * @see ChartHandle.addSeriesRange(String legendCell, String categoryRange, - * String seriesRange, String bubbleRange) - * @return ChartSeriesHandle referencing the newly added series - */ - public ChartSeriesHandle addSeriesRange(CellHandle legendCell, CellRange categoryRange, CellRange seriesRange, CellRange bubbleRange) { - return this - .addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0); // 0=default - // chart - } - - /** - * Adds a new Series to the chart via CellHandles and CellRange Objects. Each - * bar, line or wedge in a chart represents a Series.
    - * This method can update the default chart (nChart==0) or overlay charts - * (nChart 1-9) - * - * @param CellHandle - * legendCell - references the legend cell for this series - * @param CellRange - * categoryRange - The CellRange referencing the category (should be - * the same for all Series) - * @param CelLRange - * seriesRange - The CellRange referencing the data points for one - * bar, line or wedge in the chart - * @param CellRange - * bubbleRange -The CellRange referencing bubble sizes for this - * series, or null if chart is not of type BUBBLE - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @see ChartHandle.addSeriesRange(String legendCell, String categoryRange, - * String seriesRange, String bubbleRange) - * @return ChartSeriesHandle referencing the newly added series - */ - public ChartSeriesHandle addSeriesRange(CellHandle legendCell, CellRange categoryRange, CellRange seriesRange, CellRange bubbleRange, int nChart) { - Series s = null; - if (bubbleRange == null) - s = mychart.addSeries(seriesRange.toString(), categoryRange - .toString(), "", legendCell.getWorkSheetName() + "!" - + legendCell.getCellAddress(), legendCell - .getStringVal(), nChart); - else - s = mychart.addSeries(seriesRange.toString(), categoryRange - .toString(), bubbleRange - .toString(), legendCell.getWorkSheetName() + "!" - + legendCell.getCellAddress(), legendCell - .getStringVal(), nChart); - setDimensionsRecord(); // 20080417 KSC: update chart DIMENSIONS record - // upon update of series - return new ChartSeriesHandle(s, wbh); - } - - /** - * remove the Series (bar, line or wedge) at the desired index - * - * @param int - * index - series index (valid values: 0 to - * getAllChartSeriesHandles().length-1) - * @see getAllChartSeriesHandles - */ - public void removeSeries(int index) { - removeSeries(index, -1); // -1 flag for all series - } - - /** - * remove the Series (bar, line or wedge) at the desired index - * - * @param int - * index - series index (valid values: 0 to - * getAllChartSeriesHandles().length-1) - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @see getAllChartSeriesHandles - */ - public void removeSeries(int index, int nChart) { - Vector seriesperchart = mychart.getAllSeries(nChart); - Series seriestodelete = (Series) seriesperchart.get(index); // series to - // delete - mychart.removeSeries(seriestodelete); - setDimensionsRecord(); - } - - /** - * updates (replaces) every Chart Series (bar, line or wedge on the Chart) with - * the data from the array of values, legends, bubble sizes (optional) and - * category range.
    - * NOTE: all arrays must be the same size (the exception is the bubleSizeRanges - * array, which may be null) - *

    - * NOTE: String arrays come in reverse order from plugins, so this method adds - * series LIFO i.e. reversed - * - * @param String[] - * valueRanges - Array of Cell Ranges representing the Values or Data - * points for each series (bar, line or wedge) on the Chart - * @param String[] - * legendCells - Array of Cell Addresses representing the legends for - * each Series - * @param String[] - * bubbleSizeRanges - Array of Cell ranges representing the bubble - * sizes for the Chart, or null if chart is not of type BUBBLE - * @param String - * categoryRange - The Cell Range representing the categories (X - * Axis) for the entire Chart - */ - public void addAllSeries(String[] valueRanges, String[] legendCells, String[] bubbleSizeRanges, String categoryRange) { - addAllSeries(valueRanges, legendCells, bubbleSizeRanges, categoryRange, 0); // do - // for - // default - // chart - } - - /** - * updates (replaces) every Chart Series (bar, line or wedge on the Chart) with - * the data from the array of values, legends, bubble sizes (optional) and - * category range For the desired chart (0=default 1-9=overlay charts)
    - * NOTE: all arrays must be the same size (the exception is the bubleSizeRanges - * array, which may be null) - *

    - * NOTE: String arrays come in reverse order from plugins, so this method adds - * series LIFO i.e. reversed - * - * @param String[] - * valueRanges - Array of Cell Ranges representing the Values or Data - * points for each series (bar, line or wedge) on the Chart - * @param String[] - * legendCells - Array of Cell Addresses representing the legends for - * each Series - * @param String[] - * bubbleSizeRanges - Array of Cell ranges representing the bubble - * sizes for the Chart, or null if chart is not of type BUBBLE - * @param String - * categoryRange - The Cell Range representing the categories (X - * Axis) for the entire Chart - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - */ - private void addAllSeries(String[] valueRanges, String[] legendCells, String[] bubbleSizeRanges, String categoryRange, int nChart) { - // first, remove all existing series - Vector v = mychart.getAllSeries(); - for (int i = 0; i < v.size(); i++) { - mychart.removeSeries((Series) v.get(i)); - } - try { - HashMap chartMetrics = mychart.getMetrics(wbh); // build - // or - // retrieve - // Chart - // Metrics - // --> - // dimensions - // + - // series - // data - // ... - this.mychart.getLegend() - .resetPos(chartMetrics.get("y"), chartMetrics - .get("h"), chartMetrics - .get("canvash"), legendCells.length); - } catch (Exception e) {} - setDimensionsRecord(); - // now add series - boolean hasBubbles = ((bubbleSizeRanges != null - && bubbleSizeRanges.length == valueRanges.length)); - for (int i = valueRanges.length - 1; i >= 0; i--) { - try { - if (!hasBubbles) // usual case - this.addSeriesRange(legendCells[i], categoryRange, valueRanges[i], null, nChart); - else - this.addSeriesRange(legendCells[i], categoryRange, valueRanges[i], bubbleSizeRanges[i], nChart); - } catch (Exception e) { - Logger.logErr("Error adding series: " + e.toString()); - } - } - } - - /** - * Appends a series one row below the last series in the chart. - *

    - * This can be utilized when programmatically adding rows of data that should be - * reflected in the chart.
    - * Legend cell will be incremented by one row if a reference. Category range - * will stay the same. - * - *
    - * In order for this method to work properly the chart must have row-based - * series. If your chart utilizes column-based series, then you need to append a - * category. - * - * @return ChartSeriesHandle representing newly added series - * @see ChartHandle.appendRowCategoryToChart - */ - public ChartSeriesHandle appendRowSeriesToChart() { - return appendRowSeriesToChart(0); // do for default chart (0) - } - - /** - * Appends a series one row below the last series in the chart for the desired - * chart - *

    - * This can be utilized when programmatically adding rows of data that should be - * reflected in the chart.
    - * Legend cell will be incremented by one row if a reference. Category range - * will stay the same. - * - *
    - * In order for this method to work properly the chart must have row-based - * series. If your chart utilizes column-based series, then you need to append a - * category. - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return ChartSeriesHandle representing newly added series - * @see ChartHandle.appendRowCategoryToChart - */ - public ChartSeriesHandle appendRowSeriesToChart(int nChart) { - ChartSeriesHandle[] handles = this.getAllChartSeriesHandles(nChart); - ChartSeriesHandle theHandle = handles[handles.length - 1]; - String legendRef = theHandle.getSeriesLegendReference(); - if (legendRef != null && !legendRef.equals("")) { - String sheetnm = legendRef.substring(0, legendRef.indexOf("!")); - legendRef = legendRef - .substring(legendRef.indexOf("!") + 1, legendRef.length()); - int[] rc = ExcelTools.getRowColFromString(legendRef); - rc[0] = rc[0] + 1; - legendRef = sheetnm + "!" + ExcelTools.formatLocation(rc); - } else if (legendRef == null) { - legendRef = theHandle.getSeriesLegend(); - } else { - legendRef = ""; - } - String categoryRange = theHandle.getCategoryRange(); - String seriesRange = theHandle.getSeriesRange(); - String sheetnm = seriesRange.substring(0, seriesRange.indexOf("!")); - seriesRange = seriesRange - .substring(seriesRange.indexOf("!") + 1, seriesRange.length()); - int[] rc = ExcelTools.getRangeRowCol(seriesRange); - // fiddle it, since exceltools doesn't translate back/forth - int[] newRc = new int[4]; - newRc[0] = rc[1]; - newRc[1] = rc[0] + 1; - newRc[2] = rc[3]; - newRc[3] = rc[2] + 1; - seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); - try { - return this - .addSeriesRange(legendRef, "", categoryRange, seriesRange, "", nChart); - } catch (CellNotFoundException e) { - Logger.logErr("ChartHandle.appendRowSeriesToChart: Unable to append series to chart: " - + e); - } - return null; - } - - /** - * Append a row of categories to the bottom of the chart.
    - * Expands all Series to include the new bottom row.
    - * To be utilized when expanding a chart to encompass more data that has a - * col-based series. - * - * @see ChartHandle.appendRowSeriesToChart - */ - public void appendRowCategoryToChart() { - appendRowCategoryToChart(0); // default chart - } - - /** - * Append a row of categories to the bottom of the chart.
    - * Expands all Series to include the new bottom row.
    - * To be utilized when expanding a chart to encompass more data that has a - * col-based series. - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @see ChartHandle.appendRowSeriesToChart - */ - public void appendRowCategoryToChart(int nChart) { - ChartSeriesHandle[] handles = this.getAllChartSeriesHandles(nChart); - - for (int i = 0; i < handles.length; i++) { - ChartSeriesHandle theHandle = handles[i]; - // update the series - String seriesRange = theHandle.getSeriesRange(); - String[] s = ExcelTools.stripSheetNameFromRange(seriesRange); - String sheetnm = s[0]; - seriesRange = s[1]; - // String sheetnm = seriesRange.substring(0, - // seriesRange.indexOf("!")); - // seriesRange = seriesRange.substring( - // seriesRange.indexOf("!")+1, - // seriesRange.length()); - // Strip 2nd sheet ref, if any 20080213 KSC - // int n= seriesRange.indexOf('!'); - // int m= seriesRange.indexOf(':'); - // seriesRange= seriesRange.substring(0, m+1) + - // seriesRange.substring(n+1); - - int[] rc = ExcelTools.getRangeRowCol(seriesRange); - int[] newRc = new int[4]; - newRc[0] = rc[1]; - newRc[1] = rc[0]; - newRc[2] = rc[3]; - newRc[3] = rc[2] + 1; - seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); - theHandle.setSeriesRange(seriesRange); - // update the category - seriesRange = theHandle.getCategoryRange(); - s = ExcelTools.stripSheetNameFromRange(seriesRange); - sheetnm = s[0]; - seriesRange = s[1]; - /* - * sheetnm = seriesRange.substring(0, - * seriesRange.indexOf("!")); seriesRange = - * seriesRange.substring( seriesRange.indexOf("!")+1, - * seriesRange.length()); // - * Strip 2nd sheet ref, if any 20080213 KSC n= - * seriesRange.indexOf('!'); m= - * seriesRange.indexOf(':'); seriesRange= - * seriesRange.substring(0, m+1) + - * seriesRange.substring(n+1); - */ - rc = ExcelTools.getRangeRowCol(seriesRange); - newRc = new int[4]; - newRc[0] = rc[1]; - newRc[1] = rc[0]; - newRc[2] = rc[3]; - newRc[3] = rc[2] + 1; - seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); - theHandle.setCategoryRange(seriesRange); - } - } - - /* - * NOT IMPLEMENTED YET adjust chart cell references upon row - * insertion or - * deletion NOTE: Assumes we're on the correct sheet NOT - * COMPLETELY IMPLEMENTED - * YEt - * - * @param rownum - * - * @param shiftamt +1= insert row, -1= delete row - * - * public void adjustCellRefs(int rownum, int shiftamt) { - * Vector v = - * mychart.getAllSeries(); boolean bSeriesRows= false; - * boolean bMod= false; for - * (int i=0;i 0) { // - * insert row i.e. - * shift ai location down if ((loc.length==2 && - * loc[0]>=rownum) || - * (loc[0]>=rownum || loc[2]>=rownum)) { - * adjustAiLocation(ai, - * p[0].getIntLocation(), shiftamt); bMod= true; } } else { - * if ((loc.length==2 - * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) - * { // remove it - * this.removeSeries(i); bMod= true; continue; } } } - * catch(Exception e) { - * - * } } catch (Exception e) { - * - * } try { // CATEGORY Ai ai= s.getCategoryValueAi(); Ptg[] - * p=ai.getCellRangePtgs(); try { int[] loc= - * p[0].getIntLocation(); if (shiftamt - * > 0) { // insert row i.e. shift ai location down if - * ((loc.length==2 && - * loc[0]>=rownum) || (loc[0]>=rownum || loc[2]>=rownum)) { - * adjustAiLocation(ai, - * p[0].getIntLocation(), shiftamt); bMod= true; } } else { - * if ((loc.length==2 - * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) - * { // remove it ???? - * this.removeSeries(i); bMod= true; } } } catch(Exception - * e) { - * - * } } catch (Exception e) { - * - * } try { // LEGEND Ai ai= s.getLegendAi(); Ptg[] - * p=ai.getCellRangePtgs(); - * int[] loc= p[0].getIntLocation(); if (shiftamt > 0) { // - * insert row i.e. - * shift ai location down if ((loc.length==2 && - * loc[0]>=rownum) || - * (loc[0]>=rownum || loc[2]>=rownum)) { - * adjustAiLocation(ai, - * p[0].getIntLocation(), shiftamt); bMod= true; } } else { - * if ((loc.length==2 - * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) - * { // remove it - * this.removeSeries(i); bMod= true; } } } catch(Exception - * e) { - * - * } } if (bMod)// one or more series elements were modified - * setDimensionsRecord(); // update dimensions i.e. Data - * Range - * - * } - */ - - /* - * doesn't appear to be used right now move the ai location - * (row) according to - * shift amount - * - * @param ai - * - * @param loc - * - * @param shift - * - * private void adjustAiLocation(Ai ai, int[] loc, int - * shift) { String oldloc= - * ExcelTools.formatLocation(loc); if (loc.length>2) // get - * 2nd part of range - * oldloc += ":" + ExcelTools.formatLocation(new - * int[]{loc[2], loc[3]}); oldloc= - * this.getSheet().getSheetName() + "!" + oldloc; if - * (loc.length==2)// single - * cell loc[0]+=shift; else { // range loc[0]+=shift; - * loc[2]+=shift; } String - * newloc= ExcelTools.formatLocation(loc); if (loc.length>2) - * // get 2nd part of - * range newloc += ":" + ExcelTools.formatLocation(new - * int[]{loc[2], loc[3]}); - * ai.changeAiLocation(oldloc, newloc); } - * - */ - /** - * Get the Chart's bytes - * - * This is an internal method that is not useful to the end user. - * - */ - public byte[] getChartBytes() { - return mychart.getChartBytes(); - } - - public byte[] getSerialBytes() { - return mychart.getSerialBytes(); - } - - /** - * get the chart-type-specific options in XML form - * - * @return String XML - */ - private String getChartOptionsXML() { - return mychart.getChartOptionsXML(0); // 0 for default chart - } - - private String t(int n) { - String tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t"; - return (tabs.substring(0, n)); - } - - /** - * given a XmlPullParser positioned at the chart element, parse all chart - * elements to create desired chart - * - * @param sht - * WorkSheetHandle - * @param xpp - * XmlPullParser - */ - public void parseXML(WorkSheetHandle sht, XmlPullParser xpp, HashMap maps) { - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ChartFonts")) { - for (int x = 0; x < xpp.getAttributeCount(); x++) { - this.setChartFont(xpp.getAttributeName(x), xpp - .getAttributeValue(x)); - } - } else if (tnm.equals("ChartFontRec")) { - String fName = ""; - int fontId = 0, fSize = 0, fWeight = 0, fColor = 0, - fUnderline = 0; - boolean bIsBold = false; - for (int x = 0; x < xpp.getAttributeCount(); x++) { - String attr = xpp.getAttributeName(x); - String val = xpp.getAttributeValue(x); - if (attr.equals("name")) { - fName = val; - } else if (attr.equals("id")) { - fontId = Integer.parseInt(val); - } else if (attr.equals("size")) { - fSize = Font.PointsToFontHeight(Double - .parseDouble(val)); - } else if (attr.equals("color")) { - fColor = FormatHandle - .HexStringToColorInt(val, FormatHandle.colorFONT); - if (fColor == 0) - fColor = 32767; // necessary? - } else if (attr.equals("weight")) { - fWeight = Integer.parseInt(val); - } else if (attr.equals("bold")) { - bIsBold = true; - } else if (attr.equals("underline")) { - fUnderline = Integer.parseInt(val); - } - } - while (this.getWorkBook().getNumFonts() < fontId - 1) { - this.getWorkBook().insertFont(new Font("Arial", - FormatConstants.PLAIN, 10)); - } - if (this.getWorkBook().getNumFonts() < fontId) { - Font f = new Font(fName, fWeight, fSize); - f.setColor(fColor); - f.setBold(bIsBold); - f.setUnderlineStyle((byte) fUnderline); - this.getWorkBook().insertFont(f); - } else { // TODO: this will screw up linked fonts, - // perhaps, so what to do? - Font f = this.getWorkBook().getFont(fontId); - f.setFontWeight(fWeight); - f.setFontName(fName); - f.setFontHeight(fSize); - f.setColor(fColor); - f.setBold(bIsBold); - f.setUnderlineStyle((byte) fUnderline); - } - } else if (tnm.equals("FormatChartArea")) { // TODO: - // something! - // ChartBorder - // ChartProperties - } else if (tnm.equals("Series")) { // series --> - // Legend Range Category shape typex typey - String legend = "", series = "", category = "", - bubble = ""; - String dataTypeX = "", dataTypeY = ""; - String shape = ""; - for (int x = 0; x < xpp.getAttributeCount(); x++) { - if (xpp.getAttributeName(x) - .equalsIgnoreCase("Legend")) - legend = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Range")) - series = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Category")) - category = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Bubbles")) - bubble = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("TypeX")) - dataTypeX = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("TypeY")) - dataTypeY = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Shape")) - shape = xpp.getAttributeValue(x); - } - // 20070709 KSC: can't add until all cells are added - // ch.addSeriesRange(legend, category, series); - String[] s = { legend, series, category, bubble, - dataTypeX, dataTypeY, shape }; - HashMap map = (HashMap) maps - .get("chartseries"); - map.put(s, this); - } else if (tnm.equals("ChartOptions")) { // handle - // chart-type-specific - // options such - // as legend - // options - // 20070716 KSC: handle chart-type-specific options in a - // very generic way ... - for (int x = 0; x < xpp.getAttributeCount(); x++) { - this.setChartOption(xpp.getAttributeName(x), xpp - .getAttributeValue(x)); - } - } else if (tnm.equals("ThreeD")) { // handle three-d options - // handle threeD record options in a very generic way - // ... - this.make3D(); // default chart - TODO; if mutliple - // charts, handle - for (int x = 0; x < xpp.getAttributeCount(); x++) { // now - // add - // threed - // rec - // options - this.setChartOption(xpp.getAttributeName(x), xpp - .getAttributeValue(x)); - } - } else if (tnm.endsWith("Axis")) { // handle axis specs - // (Label + options ...) - // 20070720 KSC: handle Axis record options ... - int type = 0; - String axis = xpp.getName(); - if (axis.equalsIgnoreCase("XAxis")) - type = XAXIS; - else if (axis.equalsIgnoreCase("YAxis")) - type = YAXIS; - else if (axis.equalsIgnoreCase("ZAxis")) - type = ZAXIS; - if (xpp.getAttributeCount() > 0) { // then has axis - // options - for (int x = 0; x < xpp.getAttributeCount(); x++) { - this.setAxisOption(type, xpp - .getAttributeName(x), xpp - .getAttributeValue(x)); - } - } else { // no axis options means no axis present; - // remove - this.removeAxis(type); - } - } else if (tnm.equals("Series")) { // handle series data - // Legend Range Category - String legend = "", series = "", category = "", - bubble = ""; - String dataTypeX = "", dataTypeY = ""; - String shape = ""; - for (int x = 0; x < xpp.getAttributeCount(); x++) { - if (xpp.getAttributeName(x) - .equalsIgnoreCase("Legend")) - legend = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Range")) - series = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Category")) - category = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Bubbles")) - bubble = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("TypeX")) - dataTypeX = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("TypeY")) - dataTypeY = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Shape")) - shape = xpp.getAttributeValue(x); - } - // 20070709 KSC: can't add until all cells are added - // ch.addSeriesRange(legend, category, series); - String[] s = { legend, series, category, bubble, - dataTypeX, dataTypeY, shape }; - HashMap map = (HashMap) maps - .get("chartseries"); - map.put(s, this); - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals("Chart")) - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logWarn("ChartHandle.parseXML <" + xpp.getName() + ">: " - + e.toString()); - // TODO: propogate Exception??? - } - } - - /** - * returns an XML representation of this chart - * - * @return String XML - */ - public String getXML() { - StringBuffer sb = new StringBuffer(t(1) + "\n"); - - // Chart Fonts - sb.append(t(2) + "" + this.getChartFontRecsXML()); - sb.append("\n" + t(2) + "\n"); - sb.append(t(2) + "\n"); - // Format Chart Area - sb.append(t(2) + "\n"); - sb.append(t(3) + "\n"); // KSC: TODO: BORDER - sb.append(t(3) + "\n"); // TODO: - // Properties - sb.append(t(2) + "\n"); - // Source Data - sb.append(t(2) + "\n"); - ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); - for (int i = 0; i < series.length; i++) { - sb.append(t(3) + "\n"); - } - sb.append(t(2) + "\n"); - // Chart Options - sb.append(t(2) + "\n"); - // Axis Options - sb.append(t(2) + "\n"); - sb.append(t(3) + "\n"); - sb.append(t(3) + "\n"); - sb.append(t(3) + "\n"); - sb.append(t(2) + "\n"); - // ThreeD rec opts - if (this.isThreeD()) { - sb.append(t(2) + "\n"); - } - - sb.append(t(1) + "\n"); - return sb.toString(); - } - - /******************************************************************** - * OOXML Generation Methods - **********************************************************************/ - /** - * Generates OOXML (chartML) for this chart object. - * - *
    - * NOTE: necessary root chartSpace element + namespaces are not set here - * - * @param int - * rId -reference ID for this chart - * @return String representing the OOXML describing this Chart - */ - public String getOOXML(int rId) { - // TODO: finish 3d options- floor, sideWall, backWall - // TODO: finish axis options - // TODO: printSettings - - // generate OOXML (chartML) - StringBuffer cooxml = new StringBuffer(); - try { - mychart.getChartSeries().resetSeriesNumber(); // reset series idx - // retrieve pertinent chart data - // axes id's TODO: HANDLE MULTIPLE AXES per chart ... - String catAxisId = Integer - .toString((int) (Math.random() * 1000000)); - String valAxisId = Integer - .toString((int) (Math.random() * 1000000)); - String serAxisId = Integer - .toString((int) (Math.random() * 1000000)); - OOXMLChart thischart; - if ((mychart instanceof OOXMLChart)) - thischart = (OOXMLChart) mychart; - else { // XLS->XLSX - thischart = new OOXMLChart(mychart, wbh); - mychart = thischart; - thischart.getChartSeries().setParentChart(thischart); - } - thischart.wbh = this.wbh; - - cooxml.append(thischart.getOOXML(catAxisId, valAxisId, serAxisId)); - - // TODO: - ArrayList chartEmbeds = thischart.getChartEmbeds(); - if (chartEmbeds != null) { - int j = 0; - for (int i = 0; i < chartEmbeds.size(); i++) { - if (((String[]) chartEmbeds.get(i))[0] - .equals("userShape")) { - j++; - cooxml.append(""); - } - } - } - } catch (Exception e) { - Logger.logErr("ChartHandle.getOOXML: error generating OOXML. Chart not created: " - + e.toString()); - } - return cooxml.toString(); - } - - /** - * generates the OOXML specific for DrawingML, specifying offsets and - * identifying the chart object.
    - * this Drawing ML (OOXML) is distinct from Chart ML (OOXML) which actually - * defines the chart object including series, categories and axes
    - * This is an internal method that is not useful to the end user. - * - * @param int - * id - the reference id for this chart - * @return String OOXML - */ - public String getChartDrawingOOXML(int id) { - TwoCellAnchor t = new TwoCellAnchor( - ((OOXMLChart) mychart).getEditMovement()); - t.setAsChart(id, OOXMLAdapter.stripNonAscii(this.getOOXMLName()) - .toString(), TwoCellAnchor.convertBoundsFromBIFF8(this - .getSheet(), mychart.getBounds())); // adjust BIFF8 - // bounds to - // OOXML units - return t.getOOXML(); - } - - /******************************************************************************** - * Parsing OOXML Methods - **********************************************************************/ - /** - * defines this chart object based on a Chart ML (OOXML) input Stream (root - * element=c:chartSpace)
    - * This is an internal method that is not useful to the end user. - * - * @param inputStream - * ii - representing chart OOXML - */ - public void parseOOXML(java.io.InputStream ii) { - // overlay in title, after layout - // varyColors val= "0" -- after grouping and before ser - - // series colors by theme: - /* - * accent1 - 6 - */ - /** - * chartSpace: chart (Chart) §5.7.2.27 clrMapOvr (Color Map Override) §5.7.2.30 - * date1904 (1904 Date System) §5.7.2.38 externalData (External Data - * Relationship) §5.7.2.63 extLst (Chart Extensibility) §5.7.2.64 lang (Editing - * Language) §5.7.2.87 pivotSource (Pivot Source) §5.7.2.145 printSettings - * (Print Settings) §5.7.2.149 protection (Protection) §5.7.2.150 roundedCorners - * (Rounded Corners) §5.7.2.160 spPr (Shape Properties) §5.7.2.198 style (Style) - * §5.7.2.203 txPr (Text Properties) §5.7.2.217 userShapes (Reference to Chart - * Drawing Part) §5.7.2.222 - */ - try { - OOXMLChart thischart = (OOXMLChart) mychart; - int drawingOrder = 0; // drawing order of the chart (0=default, 1-9 - // for multiple charts in 1) - boolean hasPivotTableSource = false; - - // remove any undesired formatting from default chart: - this.setTitle(""); // clear any previously set - mychart.getAxes().setPlotAreaBgColor(FormatConstants.COLOR_WHITE); - mychart.getAxes().setPlotAreaBorder(-1, -1); // remove plot area - // border - - java.util.Stack lastTag = new java.util.Stack(); // keep - // track - // of - // element - // hierarchy - - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); // main entry= chartSpace, - // children: lang, chart - lastTag.push(tnm); // keep track of element hierarchy - if (tnm.equals("chart")) { // beginning of DrawingML for a - // single image or chart; - // children: title, - // plotArea, legend, - /** - * 5 - * 6 - * 7 - * 8 - * 9 - * 10 - * 11 - * 12 - * - * plotArea contains layout, , serAx, valAx, catAx, dateAx, spPr, - * dTable 13 - * 14 - * - * 15 - * 16 - * 17 - * - * 18 - */ - } else if (tnm.equals("lang")) { - thischart.lang = xpp.getAttributeValue(0); - } else if (tnm.equals("roundedCorners")) { - thischart.roundedCorners = xpp.getAttributeValue(0) - .equals("1"); - } else if (tnm.equals("pivotSource")) { // has a pivot table - hasPivotTableSource = true; - } else if (tnm.equals("view3D")) { - ThreeD.parseOOXML(xpp, lastTag, thischart); - } else if (tnm.equals("layout")) { - thischart.plotAreaLayout = (Layout) Layout - .parseOOXML(xpp, lastTag).cloneElement(); - } else if (tnm.equals("legend")) { - thischart.showLegend(true, false); - thischart.ooxmlLegend = (io.starter.formats.OOXML.Legend) io.starter.formats.OOXML.Legend - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement(); - thischart.ooxmlLegend - .fill2003Legend(thischart.getLegend()); - // Parse actual CHART TYPE element (barChart, pieChart, - // etc.) - } else if (tnm - .equals(OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.LINECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.PIECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.AREACHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SCATTERCHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.RADARCHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SURFACECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.DOUGHNUTCHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BUBBLECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.OFPIECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.STOCKCHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.BARCHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.LINECHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.PIECHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.AREACHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SCATTERCHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART])) { // specific - // chart - // type- - ChartType - .parseOOXML(xpp, this.wbh, mychart, drawingOrder++); - lastTag.pop(); - } else if (tnm.equals("title")) { - thischart.setOOXMLTitle((Title) Title - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement(), this.wbh); - this.setTitle(thischart.getOOXMLTitle().getTitle()); - } else if (tnm.equals("spPr")) { // shape properties -- can - // be for plot area or - // chart space - String parent = lastTag.get(lastTag.size() - 2); - if (parent.equals("plotArea")) { - thischart.setSpPr(0, (SpPr) SpPr - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement()); - } else if (parent.equals("chartSpace")) { - thischart.setSpPr(1, (SpPr) SpPr - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement()); - } - } else if (tnm.equals("txPr")) { // text formatting - thischart.setTxPr((TxPr) TxPr - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement()); - } else if (tnm.equals("catAx")) { // child of plotArea - mychart.getAxes() - .parseOOXML(XAXIS, xpp, tnm, lastTag, this.wbh); - } else if (tnm.equals("valAx")) { // child of plotArea - if (mychart.getAxes().hasAxis(ChartConstants.XAXIS)) // usual, - // have - // a - // catAx - // then - // a - // valAx - mychart.getAxes() - .parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); - else if (mychart.getAxes() - .hasAxis(ChartConstants.YAXIS)) // for bubble - // charts, has - // two valAxes - // and no - // catAx - mychart.getAxes() - .parseOOXML(ChartConstants.XVALAXIS, xpp, tnm, lastTag, this.wbh); - else // 2nd val axis is Y axis - mychart.getAxes() - .parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); - } else if (tnm.equals("serAx")) { // series axis - 3d charts - mychart.getAxes() - .parseOOXML(ZAXIS, xpp, tnm, lastTag, this.wbh); - } else if (tnm.equals("dateAx")) { // TODO: not finished: - // figure out! - // ?? - } - - } else if (eventType == XmlPullParser.END_TAG) { - lastTag.pop(); - String endTag = xpp.getName(); - if (endTag.equals("chartSpace")) { - setDimensionsRecord(); - break; // done processing - } - } - if (xpp.getEventType() != XmlPullParser.END_DOCUMENT) - eventType = xpp.next(); - else - eventType = XmlPullParser.END_DOCUMENT; - } - } catch (Exception e) { - Logger.logErr("ChartHandle.parseChartOOXML: " + e.toString()); - } - } - - /** - * Specifies how to resize or move this Chart upon edit
    - * This is an internal method that is not useful to the end user.
    - * Excel 7/OOXML specific - * - * @param editMovement - * String OOXML-specific edit movement setting - */ - public void setEditMovement(String editMovement) { - ((OOXMLChart) mychart).setEditMovement(editMovement); - } - - /** - * return the Excel 7/OOXML-specific name for this chart - * - * @return String OOXML name - */ - private String getOOXMLName() { - return ((OOXMLChart) mychart).getOOXMLName(); - } - - /** - * set the Excel 7/OOXML-specific name for this chart - * - * @param String - * name - */ - public void setOOXMLName(String name) { - ((OOXMLChart) mychart).setOOXMLName(name); - } - - /** - * returns the drawingml file name which defines the userShape (if any)
    - * a userShape is a drawing or shape ontop of a chart associated with this chart - * - * @return - */ - public ArrayList getChartEmbeds() { - return ((OOXMLChart) mychart).getChartEmbeds(); - } - - /** - * sets external information linked to or "embedded" in this OOXML chart; can be - * a chart user shape, an image ...
    - * NOTE: a userShape is a drawingml file name which defines the userShape (if - * any)
    - * a userShape is a drawing or shape ontop of a chart - * - * @param String[] - * embedType, filename e.g. {"userShape", "userShape file name"} - */ - public void addChartEmbed(String[] ce) { - ((OOXMLChart) mychart).addChartEmbed(ce); - } - - /** - * set the chart DIMENSIONS record based on the series ranges in the chart - * APPEARS THAT for charts, the DIMENSIONS record merely notes the range of - * values: 0, #points in series, 0, #series - */ - protected void setDimensionsRecord() { - ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); - int nSeries = series.length; - int nPoints = 0; - for (int i = 0; i < series.length; i++) { - try { - int[] coords = ExcelTools - .getRangeCoords(series[i].getSeriesRange()); - if (coords[3] > coords[1]) - nPoints = Math.max(nPoints, coords[3] - coords[1] + 1); // c1-c0 - else - nPoints = Math.max(nPoints, coords[2] - coords[0] + 1); // r1-r0 - } catch (Exception e) {} - } - mychart.setDimensionsRecord(0, nPoints, 0, nSeries); - } - - /** - * Method for setting Chart-Type-specific options in a generic fashion e.g. - * charthandle.setChartOption("Stacked", "true"); - *

    - * Note: since most Chart Type Options are interdependent, there are several - * makeXX methods that set the desired group of options e.g. makeStacked(); use - * setChartOption with care - *

    - * Note that not all Chart Types will have every option available - *

    - * Possible Options: - *

    - * "Stacked" - true or false - set Chart Series to be Stacked
    - * "Cluster" - true or false - set Clustered for Column and Bar Chart Types
    - * "PercentageDisplay" - true or false - Each Category is broken down as a - * percentge
    - * "Percentage" - Distance of pie slice from center of pie as % for Pie Charts - * (0 for all others)
    - * "donutSize" - Donut size for Donut Charts Only
    - * "Overlap" - Space between bars (default= 0%)
    - * "Gap" - Space between categories (%) (default=50%)
    - * "SmoothedLine" - true or false - the Line series has a smoothed line
    - * "AnRot" - Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for others - * (3D option)
    - * "AnElev" - Elevation Angle (-90 to 90 degrees) (15 is default) (3D option) - *
    - * "ThreeDScaling" - true or false - 3d effect
    - * "TwoDWalls" - true if 2D walls (3D option)
    - * "PcDist" - Distance from eye to chart (0 to 100) (30 is default) (3D option) - *
    - * "ThreeDBubbles" - true or false - Draw bubbles with a 3d effect
    - * "ShowLdrLines" - true or false - Show Pie and Donut charts Leader Lines
    - * "MarkerFormat" - "0" thru "9" for various marker options @see - * ChartHandle.setMarkerFormat
    - * "ShowLabel" - true or false - show Series/Data Label
    - * "ShowCatLabel" - true or false - show Category Label
    - * "ShowLabelPct" - true or false - show percentage labels for Pie charts
    - * "ShowBubbleSizes" - true or false - show bubble sizes for Bubble charts - *

    - * NOTE: all values must be in String form - * - * @see ChartHandle.getXML - */ - public void setChartOption(String op, String val) { - mychart.setChartOption(op, val); - } - - /** - * Method for setting Chart-Type-specific options in a generic fashion e.g. - * charthandle.setChartOption("Stacked", "true"); - * - * @param op - * @param val - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - */ - private void setChartOption(String op, String val, int nChart) { - mychart.setChartOption(op, val, nChart); - } - - /** - * @return true if Chart has 3D effects, false otherwise - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - */ - public boolean isThreeD(int nChart) { - return mychart.isThreeD(nChart); - } - - /** - * @return true if Chart has 3D effects, false otherwise - */ - public boolean isThreeD() { - return mychart.isThreeD(0); // default chart - } - - /** - * @return boolean true if Chart contains Stacked Series, false otherwise - */ - public boolean isStacked() { - return mychart.isStacked(0); // default chart - } - - /** - * @return boolean true if Chart is of type 100% Stacked, false otherwise - */ - public boolean is100PercentStacked() { - return mychart.is100PercentStacked(0); // default chart - } - - /** - * @return boolean true if Chart contains Clustered Bars or Columns, false - * otherwise - */ - public boolean isClustered() { - return mychart.isClustered(0); // default chart - } - - /** - * @return String ThreeD options in XML form - */ - public String getThreeDXML() { - return mychart.getThreeDXML(0); // 0 for default chart - } - - /** - * Make chart 3D if not already - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return ThreeD rec - */ - ThreeD initThreeD(int nChart) { - return mychart.initThreeD(nChart, this.getChartType(nChart)); - } - - /** - * @return String XML representation of the desired Axis - * @param int - * Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) - */ - private String getAxisOptionsXML(int Axis) { - return mychart.getAxes().getAxisOptionsXML(Axis); - } - - /** - * Returns the Axis Label Placement or position as an int - *

    - * One of:
    - * Axis.INVISIBLE - axis is hidden
    - * Axis.LOW - low end of plot area
    - * Axis.HIGH - high end of plot area
    - * Axis.NEXTTO- next to axis (default) - * - * @param int - * Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) - * @return int - one of the Axis Label placement constants above - */ - public int getAxisPlacement(int Axis) { - return mychart.getAxes().getAxisPlacement(Axis); - } - - /** - * Sets the Axis labels position or placement to the desired value (these match - * Excel placement options) - *

    - * Possible options:
    - * Axis.INVISIBLE - hides the axis
    - * Axis.LOW - low end of plot area
    - * Axis.HIGH - high end of plot area
    - * Axis.NEXTTO- next to axis (default) - * - * @param int - * Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) - * @param Placement - * - int one of the Axis placement constants listed above - */ - public void setAxisPlacement(int Axis, int Placement) { - mychart.getAxes().setAxisPlacement(Axis, Placement); - mychart.setDirtyFlag(true); - } - - /* - * returns the desired axis If bCreateIfNecessary, will - * creates if doesn't exist - * - * @return Axis Object / private Axis getAxis(int axisType, - * boolean - * bCreateIfNecessary) { return mychart.getAxis(axisType, - * bCreateIfNecessary); } - */ - - /** - * removes the desired Axis from the Chart - * - * @param int - * axisType - one of the Axis constants (XAXIS, YAXIS or ZAXIS) - */ - public void removeAxis(int axisType) { - mychart.getAxes().removeAxis(axisType); - mychart.setDirtyFlag(true); - } - - /** - * returns Chart-specific Font Records in XML form - * - * @return String Chart Font information in XML format - */ - public String getChartFontRecsXML() { - return mychart.getChartFontRecsXML(); - } - - /** - * Return non-axis Chart font ids in XML form - * - * @return String Font information in XML format - */ - public String getChartFontsXML() { - return mychart.getChartFontsXML(); - } - - /** - * Set non-axis chart font id for title, default, etc
    - * For Internal Use Only - * - * @param String - * type - font type - * @param String - * val - font id - */ - public void setChartFont(String type, String val) { - mychart.setChartFont(type, val); - } - - /** - * @return the WorkBook Object attached to this Chart - */ - public io.starter.formats.XLS.WorkBook getWorkBook() { - return mychart.getWorkBook(); - } - - public WorkBookHandle getWorkBookHandle() { - return this.wbh; - } - - public WorkSheetHandle getWorkSheetHandle() { - try { - return this.wbh.getWorkSheet(mychart.getSheet().getSheetNum()); - } catch (WorkSheetNotFoundException e) { - // this should be impossible - throw new RuntimeException(e); - } - } - - /** - * sets the coordinates or bounds (position, width and height) of this chart in - * pixels - * - * @param short[4] - * bounds - left or x value, top or y value, width, height - * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT - */ - // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and - // Coords are very - // distinct - public void setBounds(short[] bounds) { - mychart.setCoords(bounds); - } - - /** - * returns the coordinates or bounds (position, width and height) of this chart - * in pixels - * - * @return short[4] bounds - left or x value, top or y value, width, height - * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT - */ - // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and - // Coords are very - // distinct - public short[] getBounds() { - return mychart.getCoords(); - } - - /** - * sets the coordinates (position, width and height) for this chart in Excel - * size units - * - * @return short[4] pixel coords - left or x value, top or y value, width, - * height - * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT - */ - public void setCoords(short[] coords) { - mychart.setCoords(coords); - } - - /** - * returns the coordinates (position, width and height) of this chart in Excel - * size units - * - * @return short[4] pixel coords - left or x value, top or y value, width, - * height - * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT - */ - public short[] getCoords() { - mychart.getMetrics(this.wbh); - return mychart.getCoords(); - } - - /** - * get the bounds of the chart using coordinates relative to row/cols and their - * offsets - * - * @return short[8] bounds - COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, - * ROW1, ROWOFFSET1 - */ - public short[] getRelativeBounds() { - return mychart.getBounds(); - } - - /** - * returns the offset within the column in pixels - * - * @return - */ - public short getColOffset() { - return mychart.getColOffset(); - } - - /** - * sets the bounds of the chart using coordinates relative to row/cols and their - * offsets - * - * @param short[8] - * bounds - COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, - * ROWOFFSET1 - * - */ - public void setRelativeBounds(short[] bounds) { - mychart.setBounds(bounds); - } - - // 20070802 KSC: Debug uility to write out chartrecs - /* - * For internal debugging use only - * - * public void writeChartRecs(String fName) { - * mychart.writeChartRecs(fName); } - * - * /** set DataLabels option for this chart - * - * @param String type - see below - * - * @param boolean bShowLegendKey - true if show legend key, - * false otherwise - *

    possible String type values:
    Series
    Category - *
    Value - *
    Percentage (Only for Pie Charts)
    Bubble (Only for - * Bubble Charts) - *
    X Value (Only for Bubble Charts)
    Y Value (Only - * for Bubble Charts) - *
    CandP - * - *

    NOTE: not 100% implemented at this time - */ - public void setDataLabel(String/* [] */ type, boolean bShowLegendKey) { - /* - * for now, only 1 option is valid - multiple legend - * settings e.g. Category and - * Value are not figured out yet for (int i= 0; i < - * type.length; i++) - * mychart.setChartOption("DataLabel", type[i]); - */ - if (!bShowLegendKey) - mychart.setChartOption("DataLabel", type); - else - mychart.setChartOption("DataLabelWithLegendKey", type); - } - - /** - * shows or removes the Data Table for this chart - * - * @param boolean - * bShow - true if show data table - */ - public void showDataTable(boolean bShow) { - mychart.showDataTable(bShow); - } - - /** - * shows or hides the Chart legend key - * - * @param booean - * bShow - true if show legend, false to hide - * @param boolean - * vertical - true if show vertically, false for horizontal - */ - public void showLegend(boolean bShow, boolean vertical) { - mychart.showLegend(bShow, vertical); - } - - /** - * returns true if Chart has a Data Legend Key showing - * - * @return true if Chart has a Data Legend Key showing - */ - public boolean hasDataLegend() { - return mychart.hasDataLegend(); - } - - public void removeLegend() { - mychart.removeLegend(); - } - - // 20070905 KSC: Group chart options for ease of setting - // almost all charts have these specific ChartTypes: - /** - * makes this Chart Stacked
    - * sets the group of options necessary to create a stacked chart
    - * For Chart Types:
    - * BAR, COL, LINE, AREA, PYRAMID, PYRAMIDBAR, CYLINDER, CYLINDERBAR, CONE, - * CONEBAR - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void makeStacked(int nChart) { // bar, col, line, area, pyramid col + - // bar, cone col + bar, cylinder col - // + bar - int chartType = this.getChartType(nChart); - this.setChartOption("Stacked", "true", nChart); - switch (chartType) { - case ChartConstants.BARCHART: - case ChartConstants.COLCHART: - this.setChartOption("Overlap", "-100", nChart); - break; - case ChartConstants.CYLINDERCHART: - case ChartConstants.CYLINDERBARCHART: - case ChartConstants.CONECHART: - case ChartConstants.CONEBARCHART: - case ChartConstants.PYRAMIDCHART: - case ChartConstants.PYRAMIDBARCHART: - this.setChartOption("Overlap", "-100", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("Cluster", "false"); - break; - case ChartConstants.LINECHART: - this.setChartOption("Percentage", "0", nChart); - break; - case ChartConstants.AREACHART: - this.setChartOption("Overlap", "-100", nChart); - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - break; - } - } - - /** - * makes this Chart 100% Stacked
    - * sets the group of options necessary to create a 100% stacked chart
    - * For Chart Types:
    - * BAR, COL, LINE, PYRAMID, PYRAMIDBAR, CYLINDER, CYLINDERBAR, CONE, CONEBAR - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void make100PercentStacked(int nChart) { // bar, col, line, pyramid - // col + bar, cone col + - // bar, cylinder col + - // bar - int chartType = this.getChartType(nChart); - this.setChartOption("Stacked", "true", nChart); - this.setChartOption("PercentageDisplay", "true", nChart); - switch (chartType) { - case ChartConstants.COLCHART: // + pyramid - case ChartConstants.BARCHART: - this.setChartOption("Overlap", "-100", nChart); - break; - case ChartConstants.CYLINDERCHART: - case ChartConstants.CYLINDERBARCHART: - case ChartConstants.CONECHART: - case ChartConstants.CONEBARCHART: - case ChartConstants.PYRAMIDCHART: - case ChartConstants.PYRAMIDBARCHART: - this.setChartOption("Overlap", "-100", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("Cluster", "false"); - break; - case ChartConstants.LINECHART: - break; - } - } - - /** - * makes this Chart Stacked with a 3D Effect
    - * sets the group of options necessary to create a Stacked 3D chart
    - * For Chart Types:
    - * BAR, COL, AREA - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - */ - public void makeStacked3D(int nChart) { // bar, col, area - int chartType = this.getChartType(nChart); - this.setChartOption("Stacked", "true", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - switch (chartType) { - case ChartConstants.COLCHART: - case ChartConstants.BARCHART: - this.setChartOption("Overlap", "-100", nChart); - break; - case ChartConstants.AREACHART: - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - break; - } - } - - /** - * makes this Chart 100% Stacked with a 3D Effect
    - * sets the group of options necessary to create a 100% Stacked 3D chart
    - * For Chart Types:
    - * BAR, COL, AREA - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void make100PercentStacked3D(int nChart) { // bar, col, area - int chartType = this.getChartType(nChart); - this.setChartOption("Stacked", "true", nChart); - this.setChartOption("PercentageDisplay", "true", nChart); - switch (chartType) { - case ChartConstants.COLCHART: // + pyramid - case ChartConstants.BARCHART: - this.setChartOption("Overlap", "-100", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - break; - case ChartConstants.AREACHART: - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - break; - } - } - - /** - * makes the default Chart hava a 3D effect
    - * sets the group of options necessary to create a 3D chart
    - * For Chart Types:
    - * BARCHART, COLCHART, LINECHART, PIECHART, AREACHART, BUBBLECHART, - * PYRAMIDCHART, CYLINDERCHART, CONECHART - * - * @deprecated use setChartType(chartType, 0, is3d, isStacked, - * is100PercentStacked) instead - */ - @Deprecated - public void make3D() { // bar, col, line, pie, area, bubble, pyramid, cone, - // cylinder - make3D(0); - } - - /** - * makes the desired Chart hava a 3D effect
    - * where nChart 0= default, 1-9=multiple charts in one
    - * sets the group of options necessary to create a 3D chart
    - * For Chart Types:
    - * BARCHART, COLCHART, LINECHART, PIECHART, AREACHART, BUBBLECHART, - * PYRAMIDCHART, CYLINDERCHART, CONECHART - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated use setChartType(chartType, nChart, is3d, isStacked, - * is100%Stacked) instead - */ - @Deprecated - public void make3D(int nChart) { // bar, col, line, pie, area, bubble, - // pyramid, cone, cylinder - int chartType = this.getChartType(nChart); - ThreeD td = null; - switch (chartType) { - case ChartConstants.COLCHART: - case ChartConstants.BARCHART: - td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - break; - case ChartConstants.CYLINDERCHART: - case ChartConstants.CONECHART: - case ChartConstants.PYRAMIDCHART: - td = this.initThreeD(nChart); - td.setChartOption("Cluster", "false"); - break; - case ChartConstants.AREACHART: - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - td.setChartOption("Perspective", "true"); - break; - case ChartConstants.PIECHART: - case ChartConstants.LINECHART: - this.initThreeD(nChart); // just create a threeD rec w/ no extra - // options - break; - case ChartConstants.BUBBLECHART: - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - this.setChartOption("ThreeDBubbles", "true", nChart); - td = this.initThreeD(nChart); // 20081228 KSC - break; - } - } - - // more specialized option sets - /** - * makes this Chart Clusted with a 3D effect
    - * sets the group of options necessary to create a Clusted 3D chart
    - * For Chart Types:
    - * BAR, COL - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void makeClustered3D(int nChart) { // only for Column and Bar (?) - int chartType = this.getChartType(nChart); - switch (chartType) { - case ChartConstants.BARCHART: - case ChartConstants.COLCHART: - ThreeD td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("Cluster", "true"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - break; - } - } - - /** - * makes this chart's wedges exploded i.e. separated
    - * For Chart Types:
    - * PIECHART, DOUGHNUTCHART - * - * @deprecated - */ - @Deprecated - public void makeExploded() { // pie, donut - int chartType = this.getChartType(); - switch (chartType) { - case ChartConstants.DOUGHNUTCHART: - this.setChartOption("SmoothedLine", "true"); - case ChartConstants.PIECHART: - this.setChartOption("ShowLdrLines", "true"); - this.setChartOption("Percentage", "25"); - break; - // ShowLdrLines="true" Percentage="25"/> - // exploded donut: ShowLdrLines="true" Donut="50" - // Percentage="25" - // SmoothedLine="true"/> - } - } - - /** - * makes this chart's wedges exploded 3D i.e. separated with a 3D effect
    - * For Chart Types:
    - * PIECHART, DOUGHNUTCHART - * - * @param nChart - * number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void makeExploded3D(int nChart) { // pie - // ShowLdrLines="true" Percentage="25" - // AnRot="236" - int chartType = this.getChartType(nChart); - switch (chartType) { - case ChartConstants.DOUGHNUTCHART: - case ChartConstants.PIECHART: - this.setChartOption("ShowLdrLines", "true", nChart); - this.setChartOption("Percentage", "25", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("AnRot", "236"); - break; - } - } - - /* - * NOT IMPLEMENTED YET TODO: IMPLEMENT Make this Chart have - * smoothed lines - * (Scatter only) - * - * public void makeSmoothedLines() { // scatter - * //Percentage="25" - * SmoothedLine="true } - * - * public void makeWireFrame() { // surface // NO ColorFill, - * only // - * Percentage="25" SmoothedLine="true" // AnRot="20" - * Perspective="true" - * ThreeDScaling="true" TwoDWalls="true"/> // all else - * should be default for - * surface charts } public void makeContour() { // surface - * -- for wireframe - * surface, no ColorFill //ColorFill="true" Percentage="25" - * SmoothedLine="true"/> // AnElev="90" pcDist="0" - * Perspective="true" - * ThreeDScaling="true" TwoDWalls="true" } - */ - - /** - * set the marker format style for this chart
    - * one of:
    - * 0 = no marker
    - * 1 = square
    - * 2 = diamond
    - * 3 = triangle
    - * 4 = X
    - * 5 = star
    - * 6 = Dow-Jones
    - * 7 = standard deviation
    - * 8 = circle
    - * 9 = plus sign
    - * For Chart Types:
    - * LINE, SCATTER - * - * @param int - * imf - marker format constant from list above - */ - public void setMarkerFormat(int imf) { // line, scatter ... - this.setChartOption("MarkerFormat", String.valueOf(imf)); - } - - /** - * returs the JSON representation of this chart, based upon - * Dojo-charting-specifics - * - * @return String JSON representation of the chart - * - */ - public String getJSON() { - JSONObject theChart = new JSONObject(); - try { - JSONObject titles = new JSONObject(); - int type = this.getChartType(); // necessary for parsing AXIS - // options: horizontal charts - // "switch" axes ... - - // titles/labels - titles.put("title", this.getTitle()); - titles.put("XAxis", (type != ChartConstants.BARCHART) - ? (this.getXAxisLabel()) - : this.getYAxisLabel()); // bar - // axes - // are - // reversed - // ... - titles.put("YAxis", (type != ChartConstants.BARCHART) - ? (this.getYAxisLabel()) - : this.getXAxisLabel()); - try { - titles.put("ZAxis", this.getZAxisLabel()); - } catch (Exception e) { - Logger.logWarn("ChartHandle.getJSON failed getting zaxislable:" - + e.toString()); - } - theChart.put("titles", titles); - - // Chart dimensions (width, height) - short[] coords = mychart.getCoords(); - theChart.put("width", coords[ChartHandle.WIDTH]); - theChart.put("height", coords[ChartHandle.HEIGHT]); - theChart.put("row", mychart.getRow0()); // TODO: may not be - // necessary, see usage ... - theChart.put("col", mychart.getCol0()); - // Plot Area Background color - int plotAreabg = this.getPlotAreaBgColor(); - if (plotAreabg == 0x4D || plotAreabg == 0x4E) - plotAreabg = FormatConstants.COLOR_WHITE; - theChart.put("fill", FormatConstants.SVGCOLORSTRINGS[plotAreabg]); - - Double[] jMinMax = new Double[3]; - JSONObject chartObjectJSON = this.mychart.getChartObject() - .getJSON(this.mychart.getChartSeries(), this.wbh, jMinMax); - double yMax = 1.0, yMin = 0.0; - int nSeries = 0; - try { // it's possible to not have any series defined ... - theChart.put("Series", chartObjectJSON.getJSONArray("Series")); - // 20080416 KSC: Save SeriesJSON for later comparisons - mychart.setSeriesJSON(chartObjectJSON.getJSONArray("Series")); - theChart.put("SeriesFills", chartObjectJSON - .getJSONArray("SeriesFills")); // 20090729 KSC: capture - // bar - // colors or fills - } catch (Exception e) {} - theChart.put("type", chartObjectJSON.getJSONObject("type")); - yMin = jMinMax[0].doubleValue(); - yMax = jMinMax[1].doubleValue(); - nSeries = jMinMax[2].intValue(); - - // Axes + Category Labels + Grid Lines - try { - // inputJSONObject(theChart, this.getAxis(YAXIS, - // false).getJSON(this.wbh, type, - // yMax, yMin, nSeries)); - theChart.put("y", mychart.getAxes() - .getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries) - .getJSONObject("y")); - theChart.put("back_grid", mychart.getAxes() - .getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries) - .getJSONObject("back_grid")); - } catch (Exception e) {} - try { - // inputJSONObject(theChart, this.getAxis(XAXIS, - // false).getJSON(this.wbh, type, - // yMax, yMin, nSeries)); - theChart.put("x", mychart.getAxes() - .getJSON(XAXIS, this.wbh, type, yMax, yMin, nSeries) - .getJSONObject("x")); - theChart.put("back_grid", mychart.getAxes() - .getJSON(ChartConstants.YAXIS, this.wbh, type, yMax, yMin, nSeries) - .getJSONObject("back_grid")); - } catch (Exception e) {} - // TODO: 3d Charts (z axis) - - /* - * /* Chart Fonts sb.append(t(2) + "" + - * this.getChartFontRecsXML()); sb.append("\n" + t(2) + - * "\n"); - * sb.append(t(2) + "\n"); - */ - /* - * Format Chart Area - */ - /* TODO: read in legend settings */ - - // Chart Legend - if (this.hasDataLegend()) { - short s = this.mychart.getLegend().getLegendPosition(); - String[] legends = this.mychart.getLegends(-1); // -1 is flag - // for all - // rather than - // for a - // specific - // chart - String l = ""; - for (int i = 0; i < legends.length; i++) - l += legends[i] + ","; - if (l.length() > 0) - l = l.substring(0, l.length() - 1); - theChart.put("legend", new JSONObject( - "{position:" + s + ",labels:[" + l + "]}")); - } - - } catch (JSONException e) { - Logger.logErr("Error getting Chart JSON: " + e); - } - return theChart.toString(); - } - - /** - * utility to add a JSON object
    - * This is an internal method that is not useful to the end user. - * - * @param source - * @param input - */ - protected void inputJSONObject(JSONObject source, JSONObject input) { - if (source != null) { - try { - for (int j = 0; j < input.names().length(); j++) { - source.put(input.names().getString(j), input - .get(input.names().getString(j))); - } - } catch (JSONException e) { - Logger.logErr("Error inputting JSON Object: " + e); - } - } - } - - /** - * retrieves the saved Series JSON for comparisons
    - * This is an internal method that is not useful to the end user. - * - * @return JSONArray - */ - public JSONArray getSeriesJSON() { - return mychart.getSeriesJSON(); - } - - /** - * sets the saved Series JSON
    - * This is an internal method that is not useful to the end user. - * - * @param JSONArray - * s - - * @throws JSONException - */ - public void setSeriesJSON(JSONArray s) throws JSONException { - mychart.setSeriesJSON(s); - } - - /** - * retrieves current series and axis scale info in JSONObject form used upon - * chart updating
    - * This is an internal method that is not useful to the end user. - * - * @return JSONObject series and axis info - */ - public JSONObject getCurrentSeries() { - JSONObject retJSON = new JSONObject(); - // Retrieve series data + yMin yMax, nSeries - Double[] jMinMax = new Double[3]; - try { - // Series Data - // 20080516 KSC: See above JSONObject chartObjectJSON= - // ((GenericChartObject)this.mychart.getChartObject()).getJSON( - // this.getAllChartSeriesHandles(), this.getCategories()[0], - // this.wbh, minMax); - JSONObject chartObjectJSON = this.mychart.getChartObject() - .getJSON(this.mychart.getChartSeries(), this.wbh, jMinMax); - - try { - retJSON.put("Series", chartObjectJSON.getJSONArray("Series")); - } catch (Exception e) { - Logger.logWarn("ChartHandle.getCurrentSeries problem:" - + e.toString()); - } - - // Retrieve Axis Scale info - double yMax = 0.0, yMin = 0.0; - int nSeries = 0; - yMin = jMinMax[0].doubleValue(); - yMax = jMinMax[1].doubleValue(); - nSeries = jMinMax[2].intValue(); - - int type = this.getChartType(); // necessary for parsing AXIS - // options: horizontal charts - // "switch" axes ... - // Axes + Category Labels + Grid Lines - /* - * KSC: TAKE OUT JSON STUFF FOR NOW; WILL REFACTOR LATER try - * { - * inputJSONObject(retJSON, - * mychart.getAxes().getMinMaxJSON(YAXIS, this.wbh, - * type, yMax, yMin, nSeries)); } catch (Exception e) { } - * try { - * inputJSONObject(retJSON, - * mychart.getAxes().getMinMaxJSON(XAXIS, this.wbh, - * type, yMax, yMin, nSeries)); } catch (Exception e) { } - */ - // TODO: 3d Charts (z axis) - } catch (JSONException e) { - Logger.logErr("ChartHandle.getCurrentSeries: Error getting Series JSON: " - + e); - } - return retJSON; - } - - /** - * returns a JSON representation of all Series Data (Legend, Categogies, Series - * Values) for the chart
    - * This is an internal method that is not useful to the end user. - * - * @return String JSON representation - */ - public String getAllSeriesDataJSON() { - JSONArray s = new JSONArray(); - ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); - try { - for (int i = 0; i < series.length; i++) { - JSONObject ser = new JSONObject(); - ser.put("l", series[i].getSeriesLegendReference()); - ser.put("v", series[i].getSeriesRange()); - ser.put("b", series[i].getBubbleSizes()); - if (i == 0) - ser.put("c", series[i].getCategoryRange()); // 1 per chart - s.put(ser); - } - } catch (JSONException e) { - Logger.logErr("ChartHandle.getAllSeriesDataJSON: " + e); - } - return s.toString(); - } - - /** - * Take current Chart object and return the SVG code necessary to define it. - */ - /** - * TODO: Less Common Charts: STOCK RADAR SURFACE COLUMN- 3D, CONE, CYLINDER, - * PYRAMID BAR- 3D, CONE, CYLINDER, PYRAMID 3D PIE 3D LINE 3D AREA - * - * LINE CHART APPEARS THAT STARTS AND ENDS A BIT TOO EARLY ***************** Z - * Axis - * - * CHART OPTIONS: STACKED CLUSTERED - */ - public String getSVG() { - return getSVG(1); - } - - /** - * /** Take current Chart object and return the SVG code necessary to define it, - * scaled to the desired percentage e.g. 0.75= 75% - * - * @param scale - * double scale factor - * @return String SVG - */ - public String getSVG(double scale) { - HashMap chartMetrics = mychart.getMetrics(wbh); // build - // or - // retrieve - // Chart - // Metrics - // --> - // dimensions - // + - // series - // data - // ... - - StringBuffer svg = new StringBuffer(); - // required header - // svg.append("\r\n"); // referneces - // the DTD - // svg.append("\r\n"); - - // Define SVG Canvas: - svg.append("\r\n"); - svg.append(""); // scale - // chart - // -default - // scale=1 - // == 100% - // JavaScript hooks - svg.append(getJavaScript()); - - // Data Legend Box -- do before drawing plot area as legends - // box may change plot - // area coordinates - String legendSVG = getLegendSVG(chartMetrics); // but have to append it - // after because should - // overlap the - // plotarea - - String bgclr = this.mychart.getPlotAreaBgColor(); - // setup gradients - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - - // PLOT AREA BG + RECT - // rectangle around entire chart canvas - if (!(mychart instanceof OOXMLChart) - || !((OOXMLChart) mychart).roundedCorners) - svg.append("\r\n"); - else // OOXML rounded corners - svg.append("\r\n"); - - // actual plot area - svg.append("\r\n"); - - // AXES, IF PRESENT - DO BEFORE ACTUAL SERIES DATA SO DRAWS - // CORRECTLY + ADJUST - // CHART DIMENSIONS - svg.append(mychart.getAxes().getSVG(XAXIS, chartMetrics, mychart - .getChartSeries().getCategories())); - svg.append(mychart.getAxes().getSVG(YAXIS, chartMetrics, mychart - .getChartSeries().getCategories())); - // TODO: Z Axis - - // After Axes and gridlines (if present), - // ACTUAL bar/series/area/etc. svg generated from series and - // scale data - svg.append(this.mychart.getChartObject().getSVG(chartMetrics, mychart - .getAxes().getMetrics(), mychart.getChartSeries())); - - svg.append(legendSVG); // append legend SVG obtained above - - // CHART TITLE - - if (mychart.getTitleTd() != null) - svg.append(mychart.getTitleTd().getSVG(chartMetrics)); - - svg.append(""); - svg.append(""); - /* - * //KSC: TESTING: REMOVE WHEN DONE if - * (WorkBookFactory.PID==WorkBookFactory.E360) { // save svg - * for testing - * purposes try { java.io.File f= new java.io.File( - * "c:/eclipse/workspace/testfiles/io.starter.OpenXLS/output/charts/FromSheetster.svg" - * ); java.io.FileOutputStream fos= new - * java.io.FileOutputStream(f); - * fos.write(svg.toString().getBytes()); fos.flush(); - * fos.close(); } catch - * (Exception e) {} } - */ - return svg.toString(); - } - - private String getLegendSVG(HashMap chartMetrics) { - try { - return mychart.getLegend().getSVG(chartMetrics, mychart - .getChartObject(), mychart.getChartSeries()); - } catch (NullPointerException ne) { // no legend?? - return null; - } - } - - /** - * returns the svg for javascript for highlight and restore - * - * @return - */ - protected String getJavaScript() { - StringBuffer svg = new StringBuffer(); - svg.append(""); - return svg.toString(); - } - - /** - * debugging utility remove when done - */ - public void WriteMainChartRecs(String fName) { - mychart.getChartObject().WriteMainChartRecs(fName); - } - - public Axis getAxis(int type) { - // TODO Auto-generated method stub - return null; - } + // 20080114 KSC: delegate so visible + public static final int BARCHART = ChartConstants.BARCHART; + public static final int COLCHART = ChartConstants.COLCHART; + public static final int LINECHART = ChartConstants.LINECHART; + public static final int PIECHART = ChartConstants.PIECHART; + public static final int AREACHART = ChartConstants.AREACHART; + public static final int SCATTERCHART = ChartConstants.SCATTERCHART; + public static final int RADARCHART = ChartConstants.RADARCHART; + public static final int SURFACECHART = ChartConstants.SURFACECHART; + public static final int DOUGHNUTCHART = ChartConstants.DOUGHNUTCHART; + public static final int BUBBLECHART = ChartConstants.BUBBLECHART; + public static final int OFPIECHART = ChartConstants.OFPIECHART; + public static final int PYRAMIDCHART = ChartConstants.PYRAMIDCHART; + public static final int CYLINDERCHART = ChartConstants.CYLINDERCHART; + public static final int CONECHART = ChartConstants.CONECHART; + public static final int PYRAMIDBARCHART = ChartConstants.PYRAMIDBARCHART; + public static final int CYLINDERBARCHART = ChartConstants.CYLINDERBARCHART; + public static final int CONEBARCHART = ChartConstants.CONEBARCHART; + public static final int RADARAREACHART = ChartConstants.RADARAREACHART; + public static final int STOCKCHART = ChartConstants.STOCKCHART; + + // legacy + public static final int BAR = BARCHART; + public static final int COL = COLCHART; + public static final int LINE = LINECHART; + public static final int PIE = PIECHART; + public static final int AREA = AREACHART; + public static final int SCATTER = SCATTERCHART; + public static final int RADAR = RADARCHART; + public static final int SURFACE = SURFACECHART; + public static final int DOUGHNUT = DOUGHNUTCHART; + public static final int BUBBLE = BUBBLECHART; + public static final int RADARAREA = RADARAREACHART; + public static final int PYRAMID = PYRAMIDCHART; + public static final int CYLINDER = CYLINDERCHART; + public static final int CONE = CONECHART; + public static final int PYRAMIDBAR = PYRAMIDBARCHART; + public static final int CYLINDERBAR = CYLINDERBARCHART; + public static final int CONEBAR = CONEBARCHART; + // axis types + public static final int XAXIS = ChartConstants.XAXIS; + public static final int YAXIS = ChartConstants.YAXIS; + public static final int ZAXIS = ChartConstants.ZAXIS; + public static final int XVALAXIS = ChartConstants.XVALAXIS; // an + // X + // axis + // type + // but + // VAL + // records + // coordinates + public static final int X = 0; + public static final int Y = 1; + public static final int WIDTH = 2; + public static final int HEIGHT = 3; + + protected WorkBookHandle wbh; + private Chart mychart; + + /** + * Constructor which creates a new ChartHandle from an existing Chart Object + * + * @param Chart c - the source Chart object + * @param WorkBookHandle wb - the parent WorkBookHandle + */ + public ChartHandle(Chart c, WorkBookHandle wb) { + // super(); + mychart = c; + wbh = wb; + if (mychart.getWorkBook() == null) // TODO: WHY IS THIS NULL???? + mychart.setWorkBook(wb.getWorkBook()); + } + + /** + * Returns the title of the Chart + * + * @return String title of the Chart + */ + public String getTitle() { + return mychart.getTitle(); + } + + /** + * Sets the title of the Chart + * + * @param String title - Chart title + */ + public void setTitle(String title) { + this.mychart.setTitle(title); + } + + /** + * returns the data range used by the chart + * + * @return + */ + public String getDataRangeJSON() { + return this.mychart.getChartSeries().getDataRangeJSON().toString(); + } + + public int[] getEncompassingDataRange() { + return getEncompassingDataRange(this.mychart.getChartSeries() + .getDataRangeJSON()); + } + + /** + * returns the encompassing range for this chart, or null if the chart data is + * too complex to represent + * + * @param jsonDataRange + * @return + */ + public static int[] getEncompassingDataRange(JSONObject jsonDataRange) { + try { + String catrange = jsonDataRange.get("c").toString(); + String sheet = catrange.substring(0, catrange.indexOf('!')); + int[] retVals = ExcelTools.getRangeRowCol(catrange); + int nSeries = jsonDataRange.getJSONArray("Series").length(); + for (int i = 0; i < nSeries; i++) { + JSONObject series = (JSONObject) jsonDataRange + .getJSONArray("Series").get(i); + String serrange = series.get("v").toString(); + if (!serrange.startsWith(sheet)) + continue; + int[] locs = ExcelTools.getRangeRowCol(serrange); + try { + if (locs[0] < retVals[0]) + retVals[0] = locs[0]; + if (locs[1] < retVals[1]) + retVals[1] = locs[1]; + if (locs[2] > retVals[2]) + retVals[2] = locs[2]; + if (locs[3] > retVals[3]) + retVals[3] = locs[3]; + + String legendrange = series.get("l").toString(); + locs = ExcelTools.getRowColFromString(legendrange); + if (locs[0] < retVals[0]) + retVals[0] = locs[0]; + if (locs[1] < retVals[1]) + retVals[1] = locs[1]; + if (locs[0] > retVals[2]) + retVals[2] = locs[0]; + if (locs[1] > retVals[3]) + retVals[3] = locs[1]; + + if (series.has("b")) { + String bubblerange = series.get("b").toString(); + locs = ExcelTools.getRangeRowCol(serrange); + if (locs[0] < retVals[0]) + retVals[0] = locs[0]; + if (locs[1] < retVals[1]) + retVals[1] = locs[1]; + if (locs[2] > retVals[2]) + retVals[2] = locs[2]; + if (locs[3] > retVals[3]) + retVals[3] = locs[3]; + } + } catch (Exception e) { + // just continue + } + } + return retVals; + } catch (Exception e) { + } + return null; + /* + * while (ptgs.hasNext()) { PtgRef pr= (PtgRef) ptgs.next(); + * // PtgRef pr= + * (PtgRef)refs[i]; int[] locs = pr.getIntLocation(); for + * (int x=0;x<2;x++) { + * if((locs[x]retValues[x]))retValues[x]=locs[x]; } i++; } + */ + } + + /** + * returns the ordinal id associated with the underlying Chart Object + * + * @return int chart id + * @see WorkBookHandle.getChartById + */ + public int getId() { + return this.mychart.getId(); + } + + /** + * returns the string representation of this ChartHandle + */ + @Override + public String toString() { + return mychart.getTitle(); + } + + /***************************************************************************************************************************************/ + /** + * Returns an ordered array of strings representing all the series ranges in the + * Chart.
    + * Each series can only represent one bar, line or wedge of data. + * + * @return String[] each item being a Cell Range representing one bar, line or + * wedge of data in the Chart + * @see ChartHandle.getCategories + */ + public String[] getSeries() { + return mychart.getSeries(-1); // -1 is flag for all rather than for a + // specific chart + } + + /** + * Returns an ordered array of strings representing all the category ranges in + * the chart.
    + * This vector corresponds to the getSeries() method so will often contain + * duplicates, as while the series data changes frequently, category data is the + * same throughout the chart. + * + * @return String[] each item being a Cell Range representing the Category Data + * @see ChartHandle.getSeries + */ + public String[] getCategories() { + return getCategories(-1); // -1 is flag for all rather than for a + // specific chart + } + + /** + * Returns an ordered array of strings representing all the category ranges in + * the chart.
    + * This vector corresponds to the getSeries() method so will often contain + * duplicates, as while the series data changes frequently, category data is the + * same throughout the chart. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return String[] each item being a Cell Range representing the Category Data + * @see ChartHandle.getSeries + */ + private String[] getCategories(int nChart) { + return mychart.getCategories(nChart); + } + + /** + * Returns an array of ChartSeriesHandle Objects, one for each bar, line or + * wedge of data. + * + * @return ChartSeriesHandle[] Array of ChartSeriesHandle Objects representing + * Chart Series Data (Series and Categories) + * @see ChartSeriesHandle + */ + public ChartSeriesHandle[] getAllChartSeriesHandles() { + return getAllChartSeriesHandles(-1); // get ALL + } + + /** + * Returns an array of ChartSeriesHandle Objects for the desired chart, one for + * each bar, line or wedge of data.
    + * A chart number of 0 means the default chart, 1-9 indicate series for overlay + * charts
    + * NOTE: using this method returns the series for the desired chart ONLY
    + * You MUST use the corresponding removeSeries(index, nChart) when removing + * series to properly match the series index. Otherwise a mismatch will occur. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle[] Array of ChartSeriesHandle Objects representing + * Chart Series Data (Series and Categories) + * @see ChartSeriesHandle + */ + public ChartSeriesHandle[] getAllChartSeriesHandles(int nChart) { + Vector v = mychart.getAllSeries(nChart); + ChartSeriesHandle[] csh = new ChartSeriesHandle[v.size()]; + for (int i = 0; i < v.size(); i++) { + Series s = (Series) v.get(i); + csh[i] = new ChartSeriesHandle(s, this.wbh); + } + return csh; + } + + /** + * Get the ChartSeriesHandle representing Chart Series Data (Series and + * Categories) for the specified Series range + * + * @param String seriesRange - For example, "Sheet1!A12:A21" + * @return ChartSeriesHandle + * @see ChartSeriesHandle + */ + public ChartSeriesHandle getChartSeriesHandle(String seriesRange) { + ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); + for (int i = 0; i < series.length; i++) { + String sr = series[i].getSeriesRange(); + if (seriesRange.equalsIgnoreCase(sr)) { + return series[i]; + } + } + return null; + } + + /** + * Get the ChartSeriesHandle representing Chart Series Data (Series and + * Categories) for the specified Series index + * + * @param int idx - the index (0 based) of the series + * @return ChartSeriesHandle + * @see ChartSeriesHandle + */ + public ChartSeriesHandle getChartSeriesHandle(int idx) { + ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); + if (series.length >= idx) + return series[idx]; + return null; + } + + /** + * Get the ChartSeriesHandle representing Chart Series Data (Series and + * Categories) for the Series specified by label (legend) + * + * @param String legend - label for the desired series + * @return ChartSeriesHandle + * @see ChartSeriesHandle + */ + public ChartSeriesHandle getChartSeriesHandleByName(String legend) { + Series s = mychart.getSeries(legend, -1); // -1 is flag for all rather + // than for a specific chart + return new ChartSeriesHandle(s, this.wbh); + } + + /** + * sets or removes the axis title + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param ttl String new title or null to remove + */ + public void setAxisTitle(int axisType, String ttl) { + mychart.getAxes().setTitle(axisType, ttl); + } + + /** + * returns the Y axis Title + * + * @return String title + */ + public String getYAxisLabel() { + return mychart.getAxes().getTitle(YAXIS); + } + + /** + * Sets the Y axis Title + * + * @param String yTitle - new Y Axis title + */ + public void setYAxisLabel(String yTitle) { + mychart.getAxes().setTitle(YAXIS, yTitle); + } + + /** + * returns the X axis Title + * + * @return String title + */ + public String getXAxisLabel() { + return mychart.getAxes().getTitle(XAXIS); + } + + /** + * Sets the XAxisTitle + * + * @param String xTitle - new X Axis title + */ + public void setXAxisLabel(String xTitle) { + mychart.getAxes().setTitle(XAXIS, xTitle); + } + + /** + * returns the Z axis Title, if any + * + * @return String Title + */ + public String getZAxisLabel() { + return mychart.getAxes().getTitle(ZAXIS); + } + + /** + * set the Z AxisTitle + * + * @param String zTitle - new Z Axis Title + */ + public void setZAxisLabel(String zTitle) { + mychart.getAxes().setTitle(ZAXIS, zTitle); + } + + /** + * Sets the automatic scale option on or off for the desired Value axis + *

    + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + *

    + * Automatic Scaling automatically sets the scale maximum, minimum and tick + * units upon data changes, and is the default setting for charts + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @param boolean b - true if set Automatic scaling on, false otherwise + * @see setAxisAutomaticScale(boolean b) + */ + public void setAxisAutomaticScale(int axisType, boolean b) { + mychart.getAxes().setAxisAutomaticScale(axisType, b); + mychart.setDirtyFlag(true); + } + + /** + * Returns the minimum scale value of the the desired Value axis + *

    + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return int Miminum Scale value for the desired axis + * @see getAxisMinScale() + */ + public double getAxisMinScale(int axisType) { + double[] minmax = mychart.getMinMax(this.wbh); + return mychart.getAxes().getMinMax(minmax[0], minmax[1], axisType)[0]; + } + + /** + * Returns the maximum scale value of the desired Value axis + *

    + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return int - Maximum Scale value for the desired axis + * @see getAxisMaxScale() + */ + public double getAxisMaxScale(int axisType) { + double[] minmax = mychart.getMinMax(this.wbh); // -1 is flag for all + // rather than for a + // specific chart + return mychart.getAxes().getMinMax(minmax[0], minmax[1], axisType)[1]; + } + + /** + * Returns the major tick unit of the desired Value axis + *

    + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return int major tick unit + * @see getAxisMajorUnit() + */ + public int getAxisMajorUnit(int axisType) { + return mychart.getAxes().getAxisMajorUnit(axisType); + } + + /** + * Returns the minor tick unit of the desired Value axis + *

    + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return int - minor tick unit of the desired axis + * @see getAxisMinorUnit() + */ + public int getAxisMinorUnit(int axisType) { + return mychart.getAxes().getAxisMinorUnit(axisType); + } + + /** + * Sets the maximum scale value of the desired Value axis + *

    + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + *

    + * Note: The default scale setting for charts is known as Automatic Scaling
    + * When data changes, the chart automatically adjusts the scale as necessary + *
    + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @param int MaxValue - desired maximum value of the desired axis + * @see setAxisMax(int MaxValue) + */ + public void setAxisMax(int axisType, int MaxValue) { + mychart.getAxes().setAxisMax(axisType, MaxValue); + mychart.setDirtyFlag(true); + } + + /** + * Sets the minimum scale value of the desired Value axis + *

    + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + *

    + * Note: The default setting for charts is known as Automatic Scaling
    + * When data values change, the chart automatically adjusts the scale as + * necessary
    + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @param int MinValue - the desired Minimum scale value + * @see setAxisMin(int MinValue) + */ + public void setAxisMin(int axisType, int MinValue) { + mychart.getAxes().setAxisMin(axisType, MinValue); + mychart.setDirtyFlag(true); + } + + /** + * Returns true if the desired Value axis is set to automatic scale + *

    + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + *

    + * Note: The default setting for charts is known as Automatic Scaling
    + * When data changes, the chart automatically adjusts the scale as necessary + *
    + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return boolean true if Automatic Scaling is turned on + * @see getAxisAutomaticScale() + */ + public boolean getAxisAutomaticScale(int axisType) { + return mychart.getAxes().getAxisAutomaticScale(axisType); + } + + /** + * Returns true if the Y Axis (Value axis) is set to automatic scale + *

    + * The default setting for charts is known as Automatic Scaling
    + * When data changes, the chart automatically adjusts the scale (minimum, + * maximum values plus major and minor tick units) as necessary + * + * @return boolean true if Automatic Scaling is turned on + * @see getAxisAutomaticScale(int axisType) + */ + public boolean getAxisAutomaticScale() { + return mychart.getAxisAutomaticScale(); + } + + /** + * Sets the automatic scale option on or off for the Y Axis (Value axis) + *

    + * Automatic Scaling will automatically set the scale maximum, minimum and tick + * units upon data changes, and is the default chart setting + * + * @param b + * @see setAxisAutomaticScale(int axisType boolean b) + */ + public void setAxisAutomaticScale(boolean b) { + mychart.getAxes().setAxisAutomaticScale(b); + mychart.setDirtyFlag(true); + } + + /** + * Returns the minimum value of the Y Axis (Value Axis) scale + * + * @return int Miminum Scale value for Y axis + * @see getAxisMinScale(int axisType) + */ + public double getAxisMinScale() { + double[] minmax = mychart.getMinMax(this.wbh); // -1 is flag for all + // rather than for a + // specific chart + return mychart.getAxes().getMinMax(minmax[0], minmax[1])[0]; + } + + /** + * Returns the maximum value of the Y Axis (Value Axis) scale + * + * @return int Maximum Scale value for Y axis + * @see getAxisMaxScale(int axisType) + */ + public double getAxisMaxScale() { + double[] minmax = mychart.getMinMax(this.wbh); + return mychart.getAxes().getMinMax(minmax[0], minmax[1])[1]; + } + + /** + * Returns the major tick unit of the Y Axis (Value Axis) + * + * @return int major tick unit + * @see getAxisMajorUnit(int axisType) + */ + public int getAxisMajorUnit() { + double[] minmax = mychart.getMinMax(this.wbh); + return (int) mychart.getAxes().getMinMax(minmax[0], minmax[1])[2]; + } + + /** + * Returns the minor tick unit of the Y Axis (Value Axis) + * + * @return int minor tick unit + * @see getAxisMinorUnit(int axisType) + */ + public int getAxisMinorUnit() { + double[] minmax = mychart.getMinMax(this.wbh); + return (int) mychart.getAxes().getMinMax(minmax[0], minmax[1])[1]; + } + + /** + * Sets the maximum value of the Y Axis (Value Axis) Scale + *

    + * Note: The default scale setting for charts is known as Automatic Scaling
    + * When data changes, the chart automatically adjusts the scale as necessary + *
    + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int MaxValue - the desired maximum scale value + * @see ChartHandle.setAxisMax(int axisType, int MaxValue) + */ + public void setAxisMax(int MaxValue) { + mychart.getAxes().setAxisMax(MaxValue); + mychart.setDirtyFlag(true); + } + + /** + * Sets the minimum value of the Y Axis (Value Axis) Scale + *

    + * Note: The default setting for charts is known as Automatic Scaling
    + * When data changes, the chart automatically adjusts the scale as necessary + *
    + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int MinValue - the desired minimum scale value + * @see ChartHandle.setAxisMin(int axisType, int MinValue) + */ + public void setAxisMin(int MinValue) { + mychart.getAxes().setAxisMin(MinValue); + mychart.setDirtyFlag(true); + } + + /** + * sets an option for this axis + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param op option name; one of: CatCross, LabelCross, Marks, CrossBetween, + * CrossMax, MajorGridLines, AddArea, AreaFg, AreaBg or Linked Text + * Display options: Label, ShowKey, ShowValue, ShowLabelPct, ShowPct, + * ShowCatLabel, ShowBubbleSizes, TextRotation, Font + * @param val option value + */ + public void setAxisOption(int axisType, String op, String val) { + mychart.getAxes().setChartOption(axisType, op, val); + + } + + /** + * set the font for the chart title + * + * @param String name - font name + * @param int height - font height in 1/20 point units + * @param boolean bold - true if bold + * @param boolean italic - true if italic + * @param boolean underline - true if underlined + */ + public void setTitleFont(String name, int height, boolean bold, boolean italic, boolean underline) { + Font f = new Font(name, 200, height); + f.setBold(bold); + f.setItalic(italic); + f.setUnderlined(underline); + int idx = wbh.getWorkBook().getFontIdx(f); + if (idx == -1) { // font doesn't exist yet, add to streamer + f.setIdx(-1); // flag to insert + idx = wbh.getWorkBook().insertFont(f) + 1; + } + mychart.setTitleFont(idx); + } + + /** + * set the font for the Chart Title + * + * @param io.starter.Formats.XLS.Font f - desired font for the Chart Title + * @see Font + */ + public void setTitleFont(Font f) { + int idx = wbh.getWorkBook().getFontIdx(f); + if (idx == -1) { // font doesn't exist yet, add to streamer + f.setIdx(-1); // flag to insert + idx = wbh.getWorkBook().insertFont(f) + 1; + } + mychart.setTitleFont(idx); + } + + /** + * returns the Font associated with the Chart Title + * + * @return io.starter.Formats.XLS.Font + * @see Font + */ + public Font getTitleFont() { + return mychart.getTitleFont(); + } + + /** + * set the font for all axes on the chart + * + * @param String name - font name + * @param int height - font height in 1/20 point units + * @param boolean bold - true if bold + * @param boolean italic - true if italic + * @param boolean underline - true if underlined + */ + public void setAxisFont(String name, int height, boolean bold, boolean italic, boolean underline) { + Font f = new Font(name, 200, height); + f.setBold(bold); + f.setItalic(italic); + f.setUnderlined(underline); + int idx = wbh.getWorkBook().getFontIdx(f); + if (idx == -1) { // font doesn't exist yet, add to streamer + f.setIdx(-1); // flag to insert + idx = wbh.getWorkBook().insertFont(f) + 1; + } + mychart.getAxes().setTitleFont(XAXIS, idx); + mychart.getAxes().setTitleFont(YAXIS, idx); + mychart.getAxes().setTitleFont(ZAXIS, idx); + mychart.setDirtyFlag(true); + } + + /** + * set the font for all axes on the Chart + * + * @param io.starter.Formats.XLS.Font f - desired font for the Chart axes + * @see Font + */ + public void setAxisFont(Font f) { + int idx = wbh.getWorkBook().getFontIdx(f); + if (idx == -1) { // font doesn't exist yet, add to streamer + f.setIdx(-1); // flag to insert + idx = wbh.getWorkBook().insertFont(f) + 1; + } + mychart.getAxes().setTitleFont(XAXIS, idx); + mychart.getAxes().setTitleFont(YAXIS, idx); + mychart.getAxes().setTitleFont(ZAXIS, idx); + mychart.setDirtyFlag(true); + } + + /** + * return the Font associated with the Chart Axes + * + * @return io.starter.Formats.XLS.Font for Chart Axes or null if no axes + * @see Font + */ + public Font getAxisFont() { + Font f = null; + f = mychart.getAxes().getTitleFont(XAXIS); + if (f != null) + return f; + f = mychart.getAxes().getTitleFont(YAXIS); + if (f != null) + return f; + f = mychart.getAxes().getTitleFont(ZAXIS); + return f; + } + + /** + * resets all fonts in the chart to the default font of the workbook + */ + public void resetFonts() { + mychart.resetFonts(); + } + + /** + * returns the underlhying Sheet Object this Chart is attached to
    + * For Internal Use + * + * @return Boundsheet + */ + public Boundsheet getSheet() { + return mychart.getSheet(); + } + + /** + * return the background color of this chart's Plot Area as an int + * + * @return int background color constant + * @see FormatHandle.COLOR_* constants + */ + public int getPlotAreaBgColor() { + String bg = mychart.getPlotAreaBgColor(); + return FormatHandle.HexStringToColorInt(bg, (short) 0); + } + + public String getPlotAreaBgColorStr() { + return mychart.getPlotAreaBgColor(); + } + + /** + * sets the Plot Area background color + * + * @param int bg - color constant + * @see FormatHandle.COLOR_* constants + */ + public void setPlotAreaBgColor(int bg) { + mychart.setPlotAreaBgColor(bg); + } + + /** + * Change the value of a Chart object.
    + * NOTE: THIS HAS NOT BEEN 100% IMPLEMENTED YET
    + * You can use this method to change: + * + *
    + * - the Title of the Chart
    + * - the Text Labels of Categories and Values (X and Y) + * + *
    + * eg: + * + *
    + * To change the value of the Chart title
    + * chart.changeObjectValue("Template Chart Title", "Widget Sales By Quarter"); + * + *
    + * To change the text label of the categories
    + * chart.changeObjectValue("Category X", "Fiscal Year"); + * + *
    + * To change the text label of the values
    + * chart.changeObjectValue("Value Y", "Sales in US$"); + * + * @param String originalval - One of: "Template Chart Title", "Category X" or + * "Value Y" + * @param Sring newval - the new setting + * @return whether the change was successful + */ + public boolean changeTextValue(String originalval, String newval) { + /* + * KSC: TODO: Refactor *** for(int + * x=0;x + * Used to prevent updating of the Cell Reference when Cells are moved. + * + * @param location of the Cell Reference to be locked/unlocked + * @param lock status setting + * @return boolean whether the Cell Reference was found and modified + */ + private boolean setLocationPolicy(String loc, int l) { + Logger.logErr("ChartHandle.setLocationPolicy is broken"); + + /* + * TODO: Refactor for(int x=0;x + * To see possible Chart Types, view the public static int's in ChartHandle. + *
    + * Possible Chart Types:
    + * BARCHART
    + * COLCHART
    + * LINECHART
    + * PIECHART
    + * AREACHART
    + * SCATTERCHART
    + * RADARCHART
    + * SURFACECHART
    + * DOUGHNUTCHART
    + * BUBBLECHART
    + * RADARAREACHART
    + * PYRAMIDCHART
    + * CYLINDERCHART
    + * CONECHART
    + * PYRAMIDBARCHART
    + * CYLINDERBARCHART
    + * CONEBAR + * + * @param int chartType - representing the chart type + */ + public void setChartType(int chartType) { + mychart.setChartType(chartType, 0, EnumSet.noneOf(ChartOptions.class)); // no + // specific + // options + } + + /** + * Chart Options. CLUSTERED, bar, col charts only STACKED, PERCENTSTACKED, 100% + * stacked THREED, 3d Effect EXPLODED, Pie, Donut HASLINES, Scatter, Line charts + * ... WIREFRAME, Surface DROPLINES, DOWNBARS, line, stock UPDOWNBARS, line, + * stock SERLINES, bar, ofpie
    + * Use these chart options when creating new charts
    + * A chart may have multiple chart options e.g. 3D Exploded pie chart + * + * @see ChartHandle.createNewChart + */ + // need: hasMarkers **** + public enum ChartOptions { + CLUSTERED, + /** + * bar, col charts only + */ + STACKED, + PERCENTSTACKED, + /** + * 100% stacked + */ + THREED, + /** + * 3d Effect + */ + EXPLODED, + /** + * Pie, Donut + */ + HASLINES, + /** + * Scatter, Line + */ + SMOOTHLINES, + /** + * Scatter, Line, Radar + */ + WIREFRAME, + /** + * Surface + */ + DROPLINES, + /** + * line, area, stock charts + */ + UPDOWNBARS, + /** + * line, stock + */ + SERLINES, + /** + * bar, ofpie + */ + HILOWLINES, + /** + * line, stock charts + */ + FILLED /** radar */ + } + + /** + * Static method to create a new chart on WorkSheet sheet of type chartType with + * chart Options options
    + * After creating, you can set the chart title via ChartHandle.setTitle
    + * and Position via ChartHandle.setRelativeBounds (row/col-based) or + * ChartHandle.setCoords (pixel-based)
    + * as well as several other customizations possible + * + * @param book WorkBookHandle + * @param sheet WorkSheetHandle + * @param chartType one of:
    + * BARCHART
    + * COLCHART
    + * LINECHART
    + * PIECHART
    + * AREACHART
    + * SCATTERCHART
    + * RADARCHART
    + * SURFACECHART
    + * DOUGHNUTCHART
    + * BUBBLECHART
    + * RADARAREACHART
    + * PYRAMIDCHART
    + * CYLINDERCHART
    + * CONECHART
    + * PYRAMIDBARCHART
    + * CYLINDERBARCHART
    + * CONEBARCHART + * @param options EnumSet + * @return + * @see ChartHandle.ChartOptions + * @see setChartType + */ + public static ChartHandle createNewChart(WorkSheetHandle sheet, int chartType, EnumSet options) { + // Create Initial Basic Chart + ChartHandle cht = sheet.getWorkBook().createChart("", sheet); + // Change Chart Type with Desired Options: + cht.setChartType(chartType, 0, options); + return cht; + } + + /** + * Sets the Chart type to the specified type (no 3d, no stacked ...)
    + * To see possible Chart Types, view the public static int's in ChartHandle. + *
    + * Possible Chart Types:
    + * BARCHART
    + * COLCHART
    + * LINECHART
    + * PIECHART
    + * AREACHART
    + * SCATTERCHART
    + * RADARCHART
    + * SURFACECHART
    + * DOUGHNUTCHART
    + * BUBBLECHART
    + * RADARAREACHART
    + * PYRAMIDCHART
    + * CYLINDERCHART
    + * CONECHART
    + * PYRAMIDBARCHART
    + * CYLINDERBARCHART
    + * CONEBARCHART + * + * @param int chartType - representing the chart type + * @param nChart - 0 (default) or 1-9 for complex overlay charts + * @param EnumSet 0 or more chart options (Such as Stacked, Exploded ...) + * @see ChartHandle.ChartOptions + */ + public void setChartType(int chartType, int nChart, EnumSet options) { + mychart.setChartType(chartType, nChart, options); + } + + /** + * Sets the basic chart type (no 3d, stacked...) for multiple or overlay Charts. + *
    + * You can specify the drawing order of the Chart, where 0 is the default chart, + * and 1-9 are overlay charts.
    + * The default chart (chart 0) is always present; however, using this method, + * you can create a new overlay chart (up to 9 maximum).
    + * NOTE: The chart number must be unique and in order
    + * If the desired chart number is not present in the chart, a new overlay chart + * will be created.
    + * To set explicit chart options, @see setChartType(chartType, nChart, is3d, + * isStacked, is100PercentStacked)
    + *
    + * To see possible Chart Types, view the public static int's in ChartHandle. + *
    + * Possible Chart Types:
    + * BARCHART
    + * COLCHART
    + * LINECHART
    + * PIECHART
    + * AREACHART
    + * SCATTERCHART
    + * RADARCHART
    + * SURFACECHART
    + * DOUGHNUTCHART
    + * BUBBLECHART
    + * RADARAREACHART
    + * PYRAMIDCHART
    + * CYLINDERCHART
    + * CONECHART
    + * PYRAMIDBARCHART
    + * CYLINDERBARCHART
    + * CONEBARCHART + * + * @param int chartType - representing the chart type + * @param chartType + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + public void setChartType(int chartType, int nChart) { + mychart.setChartType(chartType, nChart, EnumSet + .noneOf(ChartOptions.class)); // no specific options + } + + /** + * Return an int corresponding to this ChartHandle's Chart Type for the default + * chart
    + * To see possible Chart Types, view the public static int's in ChartHandle. + * + * @return int chart type + * @see ChartHandle static Chart Type Constants + * @see ChartHandle.setChartType + */ + public int getChartType() { + return mychart.getChartType(); + } + + /** + * Return an int corresponding to this ChartHandle's Chart Type for the + * specified chart
    + * To see possible Chart Types, view the public static int's in ChartHandle. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return int chart type + * @see ChartHandle static Chart Type Constants + * @see ChartHandle.setChartType + */ + public int getChartType(int nChart) { + return mychart.getChartType(nChart); + } + + /** + * Sets the location lock on the Cell Reference at the specified location + * + * Used to prevent updating of the Cell Reference when Cells are moved. + * + * @param location + * of the Cell Reference to be locked/unlocked + * @param lock + * status setting + * @return boolean whether the Cell Reference was found and modified + */ + /* + * TODO: NEEDED?? public boolean setLocationLocked(String + * loc, boolean l){ int x + * = Ptg.PTG_LOCATION_POLICY_UNLOCKED; if(l)x = + * Ptg.PTG_LOCATION_POLICY_LOCKED; + * return setLocationPolicy(loc, x); } + */ + + /** + * Change the Cell Range referenced by one of the Series (a bar, line or wedge + * of data) in the Chart.
    + *
    + * For Example, if the data values for one of the Series in the Chart are + * obtained from the range Sheet1!A1:A10 and we want to add 5 more values to + * that Series, use: + * + *
    + * boolean changedOK = + * charthandle.changeSeriesRange("Sheet1!A1:A10","Sheet1!A1:A15");
    + *
    + * Please keep in mind this is only the data range; it does not include labels + * that may have been automatically created when you chose the chart range.
    + *
    + * To illustrate this, if A1 = "label" A2 = "data" A3 = "data", and we want to + * add 2 more data points, we would use: changeSeriesRange("Sheet1!A2:A3", + * "Sheet1!A2:A5");
    + *
    + * Series are always expressed as one single line of data. If your chart + * encompasses a range of rows and columns you will need to modify each of the + * series in the chart handle. To determine the series that already exist in + * your chart, utilize the String[] getSeries() method. + * + * @param String originalrange - the original Series (bar, line or wedge of data) + * to alter + * @param String newrange -the new data range + * @return whether the change was successful + */ + public boolean changeSeriesRange(String originalrange, String newrange) { + return mychart.changeSeriesRange(originalrange, newrange); + } + + /** + * Change the Cell Range representing the Categories in the Chart.
    + * Categories usually appear on the X Axis and are textual, not numeric
    + * For example: the Category values in the Chart are obtained from the range + * Sheet1!A1:A10 and we want to add 5 more categories to the chart:
    + * boolean changedOK = + * chart.changeCategoryRange("Sheet1!A1:A10","Sheet1!A1:A15");
    + * Note that Category Range is the same for each Series (bar, line or wedge of + * data)
    + * i.e. there is only one Category Range for the Chart, but there may be many + * Series Ranges + * + * @param String originalrange - Original Category Range + * @param String newrange - New Category Range + * @return true if the change was successful + */ + public boolean changeCategoryRange(String originalrange, String newrange) { + return mychart.changeCategoryRange(originalrange, newrange); + } + + /** + * Changes or adds a Series to the chart via Series Index. Each bar, line or + * wedge in a chart represents a Series.
    + * If the Series index is greater than the number of series already present in + * the chart, the series will be added to the end.
    + * Otherwise the Series at the index position will be altered.
    + * This method allows altering of every aspect of the Series: Data (Series) + * Range, Legend Cell Address, Category Range and/or Bubble Range. + * + * @param int index - the series index. If greater than the number of series + * already present in the chart, the series will be added to the end + * @param String legendCell - String representation of Legend Cell Address + * @param String categoryRange - String representation of Category Range (should be + * same for all series) + * @param String seriesRange - String representation of the Series Data Range for + * this series + * @param String bubbleRange - String representation of Bubble Range (representing + * bubble sizes), if bubble chart. null if not + * @return a ChartSeriesHandle representing the new or altered Series + * @throws CellNotFoundException + */ + public ChartSeriesHandle setSeries(int index, String legendCell, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { + String legendText = ""; + try { + CellHandle ICell = null; + if (legendCell != null && !legendCell.equals("")) { + // 20070707 KSC: allow addition of new cell ranges for + // legendCell (see + // OpenXLS.handleChartElement) + try { + ICell = wbh.getCell(legendCell); + } catch (CellNotFoundException c) { + int shtpos = legendCell.indexOf("!"); + if (shtpos > 0) { + String sheetstr = legendCell.substring(0, shtpos); + WorkSheetHandle sht = wbh.getWorkSheet(sheetstr); + String celstr = legendCell.substring(shtpos + 1); + ICell = sht.add("", celstr); + } + } + legendText = ICell.getStringVal(); + } + return setSeries(index, legendCell, legendText, categoryRange, seriesRange, bubbleRange); + } catch (WorkSheetNotFoundException e) { + throw new CellNotFoundException( + "Error locating cell for adding series range: " + + legendCell); + } + } + + /** + * Changes or adds a Series to the chart via Series Index. Each bar, line or + * wedge in a chart represents a Series.
    + * If the Series index is greater than the number of series already present in + * the chart, the series will be added to the end.
    + * Otherwise the Series at the index position will be altered.
    + * This method allows altering of every aspect of the Series: Data (Series) + * Range, Legend Text, Legend Cell Address, Category Range and/or Bubble Range. + * + * @param int index - the series index. If greater than the number of series + * already present in the chart, the series will be added to the end + * @param String legendCell - String representation of Legend Cell Address + * @param String legendText - String Legend text + * @param String categoryRange - String representation of Category Range (should be + * same for all series) + * @param String seriesRange - String representation of the Series Data Range for + * this series + * @param String bubbleRange - String representation of Bubble Range (representing + * bubble sizes), if bubble chart. null if not + * @return a ChartSeriesHandle representing the new or altered Series + * @throws CellNotFoundException + */ + public ChartSeriesHandle setSeries(int index, String legendCell, String legendText, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { + return setSeries(index, legendCell, legendText, categoryRange, seriesRange, bubbleRange, 0); // for + // default + // chart + } + + /** + * Changes or adds a Series to the desired Chart (either default or overlay) via + * Series Index. Each bar, line or wedge in a chart represents a Series.
    + * If the Series index is greater than the number of series already present in + * the chart, the series will be added to the end.
    + * Otherwise the Series at the index position will be altered.
    + * This method allows altering of every aspect of the Series: Data (Series) + * Range, Legend Text, Legend Cell Address, Category Range and/or Bubble Range. + * + * @param int index - the series index. If greater than the number of series + * already present in the chart, the series will be added to the end + * @param String legendCell - String representation of Legend Cell Address + * @param String legendText - String Legend text + * @param String categoryRange - String representation of Category Range (should be + * same for all series) + * @param String seriesRange - String representation of the Series Data Range for + * this series + * @param String bubbleRange - String representation of Bubble Range (representing + * bubble sizes), if bubble chart. null if not + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return a ChartSeriesHandle representing the new or altered Series + * @throws CellNotFoundException + */ + public ChartSeriesHandle setSeries(int index, String legendCell, String legendText, String categoryRange, String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { + // if (index < mychart.getAllSeries(nChart).size() && index + // >= 0) { + try { + Series s = (Series) mychart.getAllSeries(nChart).get(index); + ChartSeriesHandle csh = new ChartSeriesHandle(s, this.wbh); + csh.setSeries(legendCell, categoryRange, seriesRange, bubbleRange); + setDimensionsRecord(); + return csh; + } catch (ArrayIndexOutOfBoundsException ae) { // not found - add + return addSeriesRange(legendCell, legendText, categoryRange, seriesRange, bubbleRange, nChart); + } + } + + /** + * Adds a new Series to the chart. Each bar, line or wedge in a chart represents + * a Series. + * + * @param String legendAddress - The cell address defining the legend for the + * series + * @param Srring legendText - Text of the legend + * @param String categoryRange - Cell Range defining the category (normally will be + * the same range for all series) + * @param String seriesRange - Cell range defining the data points of the series + * @param String bubbleRange - Cell range defining the bubble sizes for this series + * (bubble charts only) + * @return ChartSeriesHandle representing the new series + * @throws CellNotFoundException + */ + private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { + return addSeriesRange(legendAddress, legendText, categoryRange, seriesRange, bubbleRange, 0); // for + // default + // chart + } + + /** + * Adds a new Series to the chart. Each bar, line or wedge in a chart represents + * a Series. + * + * @param String legendAddress - The cell address defining the legend for the + * series + * @param Srring legendText - Text of the legend + * @param String categoryRange - Cell Range defining the category (normally will be + * the same range for all series) + * @param String seriesRange - Cell range defining the data points of the series + * @param String bubbleRange - Cell range defining the bubble sizes for this series + * (bubble charts only) + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle representing the new series + * @throws CellNotFoundException + */ + private ChartSeriesHandle addSeriesRange(String legendAddress, String legendText, String categoryRange, String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { + Series s = null; + if (bubbleRange == null || bubbleRange.equals("")) + s = mychart + .addSeries(seriesRange, categoryRange, "", legendAddress, legendText, nChart); + else + s = mychart + .addSeries(seriesRange, categoryRange, bubbleRange, legendAddress, legendText, nChart); + if (nChart > 0) { // must update SeriesList record for overlay charts + // TODO: FINISH + } + setDimensionsRecord(); + return new ChartSeriesHandle(s, wbh); + } + + /** + * Adds a new Series to the chart. Each bar, line or wedge in a chart represents + * a Series.
    + * An Example of adding multiple series to a chart: + *

    + * ChartHandle.addSeriesRange("Sheet1!A3", "Sheet1!B1:E1", "Sheet1:B3:E3", + * null);
    + * ChartHandle.addSeriesRange("Sheet1!A4", "Sheet1!B1:E1", "Sheet1:B4:E4", + * null);
    + * ChartHandle.addSeriesRange("Sheet1!A5", "Sheet1!B1:E1", "Sheet1:B5:E5", + * null);
    + * etc... + *

    + * Note that the category does not change, it is usually constant through + * series.
    + * Also note that the example above is for a non-bubble-type chart. + * + * @param String legendCell - Cell reference for the legend cell (e.g. Sheet1!A1) + * @param String categoryRange - Category Cell range (e.g. Sheet1!B1:B1); + * @param String seriesRange - Series Data range (e.g. Sheet1!B3:E3); + * @param String bubbleRange - Cell Range representing Bubble sizes (e.g. + * Sheet1!A2:A5); or null if chart is not of type Bubble. + * @return ChartSeriesHandle referencing the newly added series + */ + public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, String seriesRange, String bubbleRange) throws CellNotFoundException { + return this + .addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0); // target + // default + // chart + } + + /** + * Adds a new Series to the chart. Each bar, line or wedge in a chart represents + * a Series.
    + * An Example of adding multiple series to a chart: + *

    + * ChartHandle.addSeriesRange("Sheet1!A3", "Sheet1!B1:E1", "Sheet1:B3:E3", + * null);
    + * ChartHandle.addSeriesRange("Sheet1!A4", "Sheet1!B1:E1", "Sheet1:B4:E4", + * null);
    + * ChartHandle.addSeriesRange("Sheet1!A5", "Sheet1!B1:E1", "Sheet1:B5:E5", + * null);
    + * etc... + *

    + * Note that the category does not change, it is usually constant through + * series.
    + * Also note that the example above is for a non-bubble-type chart. + * + * @param String legendCell - Cell reference for the legend cell (e.g. Sheet1!A1) + * @param String categoryRange - Category Cell range (e.g. Sheet1!B1:B1); + * @param String seriesRange - Series Data range (e.g. Sheet1!B3:E3); + * @param String bubbleRange - Cell Range representing Bubble sizes (e.g. + * Sheet1!A2:A5); or null if chart is not of type Bubble. + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle referencing the newly added series + */ + public ChartSeriesHandle addSeriesRange(String legendCell, String categoryRange, String seriesRange, String bubbleRange, int nChart) throws CellNotFoundException { + String legendText = ""; + String legendAddr = ""; + try { + CellHandle ICell = null; + if (legendCell != null && !legendCell.equals("")) { + try { + ICell = wbh.getCell(legendCell); + if (legendCell.indexOf("!") == -1) + legendAddr = ICell.getWorkSheetName() + "!" + + ICell.getCellAddress(); + else + legendAddr = legendCell; + } catch (CellNotFoundException c) { + int shtpos = legendCell.indexOf("!"); + if (shtpos > 0) { + String sheetstr = legendCell.substring(0, shtpos); + WorkSheetHandle sht = wbh.getWorkSheet(sheetstr); + String celstr = legendCell.substring(shtpos + 1); + ICell = sht.add("", celstr); // TODO: Why is this being + // added? + legendAddr = celstr; + } + } + if (ICell != null) + legendText = ICell.getStringVal(); + else + legendText = legendCell; + } + Series s = null; + if (bubbleRange == null) + s = mychart + .addSeries(seriesRange, categoryRange, "", legendAddr, legendText, nChart); + else + s = mychart + .addSeries(seriesRange, categoryRange, bubbleRange, legendAddr, legendText, nChart); + setDimensionsRecord(); // update chart DIMENSIONS record upon update + // of series + return new ChartSeriesHandle(s, wbh); + } catch (WorkSheetNotFoundException e) { + throw new CellNotFoundException( + "Error locating cell for adding series range: " + + legendCell); + } + } + + /** + * Adds a new Series to the chart via CellHandles and CellRange Objects. Each + * bar, line or wedge in a chart represents a Series. + * + * @param CellHandle legendCell - references the legend cell for this series + * @param CellRange categoryRange - The CellRange referencing the category (should be + * the same for all Series) + * @param CelLRange seriesRange - The CellRange referencing the data points for one + * bar, line or wedge in the chart + * @param CellRange bubbleRange -The CellRange referencing bubble sizes for this + * series, or null if chart is not of type BUBBLE + * @return ChartSeriesHandle referencing the newly added series + * @see ChartHandle.addSeriesRange(String legendCell, String categoryRange, + * String seriesRange, String bubbleRange) + */ + public ChartSeriesHandle addSeriesRange(CellHandle legendCell, CellRange categoryRange, CellRange seriesRange, CellRange bubbleRange) { + return this + .addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0); // 0=default + // chart + } + + /** + * Adds a new Series to the chart via CellHandles and CellRange Objects. Each + * bar, line or wedge in a chart represents a Series.
    + * This method can update the default chart (nChart==0) or overlay charts + * (nChart 1-9) + * + * @param CellHandle legendCell - references the legend cell for this series + * @param CellRange categoryRange - The CellRange referencing the category (should be + * the same for all Series) + * @param CelLRange seriesRange - The CellRange referencing the data points for one + * bar, line or wedge in the chart + * @param CellRange bubbleRange -The CellRange referencing bubble sizes for this + * series, or null if chart is not of type BUBBLE + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle referencing the newly added series + * @see ChartHandle.addSeriesRange(String legendCell, String categoryRange, + * String seriesRange, String bubbleRange) + */ + public ChartSeriesHandle addSeriesRange(CellHandle legendCell, CellRange categoryRange, CellRange seriesRange, CellRange bubbleRange, int nChart) { + Series s = null; + if (bubbleRange == null) + s = mychart.addSeries(seriesRange.toString(), categoryRange + .toString(), "", legendCell.getWorkSheetName() + "!" + + legendCell.getCellAddress(), legendCell + .getStringVal(), nChart); + else + s = mychart.addSeries(seriesRange.toString(), categoryRange + .toString(), bubbleRange + .toString(), legendCell.getWorkSheetName() + "!" + + legendCell.getCellAddress(), legendCell + .getStringVal(), nChart); + setDimensionsRecord(); // 20080417 KSC: update chart DIMENSIONS record + // upon update of series + return new ChartSeriesHandle(s, wbh); + } + + /** + * remove the Series (bar, line or wedge) at the desired index + * + * @param int index - series index (valid values: 0 to + * getAllChartSeriesHandles().length-1) + * @see getAllChartSeriesHandles + */ + public void removeSeries(int index) { + removeSeries(index, -1); // -1 flag for all series + } + + /** + * remove the Series (bar, line or wedge) at the desired index + * + * @param int index - series index (valid values: 0 to + * getAllChartSeriesHandles().length-1) + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @see getAllChartSeriesHandles + */ + public void removeSeries(int index, int nChart) { + Vector seriesperchart = mychart.getAllSeries(nChart); + Series seriestodelete = (Series) seriesperchart.get(index); // series to + // delete + mychart.removeSeries(seriestodelete); + setDimensionsRecord(); + } + + /** + * updates (replaces) every Chart Series (bar, line or wedge on the Chart) with + * the data from the array of values, legends, bubble sizes (optional) and + * category range.
    + * NOTE: all arrays must be the same size (the exception is the bubleSizeRanges + * array, which may be null) + *

    + * NOTE: String arrays come in reverse order from plugins, so this method adds + * series LIFO i.e. reversed + * + * @param String[] valueRanges - Array of Cell Ranges representing the Values or Data + * points for each series (bar, line or wedge) on the Chart + * @param String[] legendCells - Array of Cell Addresses representing the legends for + * each Series + * @param String[] bubbleSizeRanges - Array of Cell ranges representing the bubble + * sizes for the Chart, or null if chart is not of type BUBBLE + * @param String categoryRange - The Cell Range representing the categories (X + * Axis) for the entire Chart + */ + public void addAllSeries(String[] valueRanges, String[] legendCells, String[] bubbleSizeRanges, String categoryRange) { + addAllSeries(valueRanges, legendCells, bubbleSizeRanges, categoryRange, 0); // do + // for + // default + // chart + } + + /** + * updates (replaces) every Chart Series (bar, line or wedge on the Chart) with + * the data from the array of values, legends, bubble sizes (optional) and + * category range For the desired chart (0=default 1-9=overlay charts)
    + * NOTE: all arrays must be the same size (the exception is the bubleSizeRanges + * array, which may be null) + *

    + * NOTE: String arrays come in reverse order from plugins, so this method adds + * series LIFO i.e. reversed + * + * @param String[] valueRanges - Array of Cell Ranges representing the Values or Data + * points for each series (bar, line or wedge) on the Chart + * @param String[] legendCells - Array of Cell Addresses representing the legends for + * each Series + * @param String[] bubbleSizeRanges - Array of Cell ranges representing the bubble + * sizes for the Chart, or null if chart is not of type BUBBLE + * @param String categoryRange - The Cell Range representing the categories (X + * Axis) for the entire Chart + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + private void addAllSeries(String[] valueRanges, String[] legendCells, String[] bubbleSizeRanges, String categoryRange, int nChart) { + // first, remove all existing series + Vector v = mychart.getAllSeries(); + for (int i = 0; i < v.size(); i++) { + mychart.removeSeries((Series) v.get(i)); + } + try { + HashMap chartMetrics = mychart.getMetrics(wbh); // build + // or + // retrieve + // Chart + // Metrics + // --> + // dimensions + // + + // series + // data + // ... + this.mychart.getLegend() + .resetPos(chartMetrics.get("y"), chartMetrics + .get("h"), chartMetrics + .get("canvash"), legendCells.length); + } catch (Exception e) { + } + setDimensionsRecord(); + // now add series + boolean hasBubbles = ((bubbleSizeRanges != null + && bubbleSizeRanges.length == valueRanges.length)); + for (int i = valueRanges.length - 1; i >= 0; i--) { + try { + if (!hasBubbles) // usual case + this.addSeriesRange(legendCells[i], categoryRange, valueRanges[i], null, nChart); + else + this.addSeriesRange(legendCells[i], categoryRange, valueRanges[i], bubbleSizeRanges[i], nChart); + } catch (Exception e) { + Logger.logErr("Error adding series: " + e.toString()); + } + } + } + + /** + * Appends a series one row below the last series in the chart. + *

    + * This can be utilized when programmatically adding rows of data that should be + * reflected in the chart.
    + * Legend cell will be incremented by one row if a reference. Category range + * will stay the same. + * + *
    + * In order for this method to work properly the chart must have row-based + * series. If your chart utilizes column-based series, then you need to append a + * category. + * + * @return ChartSeriesHandle representing newly added series + * @see ChartHandle.appendRowCategoryToChart + */ + public ChartSeriesHandle appendRowSeriesToChart() { + return appendRowSeriesToChart(0); // do for default chart (0) + } + + /** + * Appends a series one row below the last series in the chart for the desired + * chart + *

    + * This can be utilized when programmatically adding rows of data that should be + * reflected in the chart.
    + * Legend cell will be incremented by one row if a reference. Category range + * will stay the same. + * + *
    + * In order for this method to work properly the chart must have row-based + * series. If your chart utilizes column-based series, then you need to append a + * category. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle representing newly added series + * @see ChartHandle.appendRowCategoryToChart + */ + public ChartSeriesHandle appendRowSeriesToChart(int nChart) { + ChartSeriesHandle[] handles = this.getAllChartSeriesHandles(nChart); + ChartSeriesHandle theHandle = handles[handles.length - 1]; + String legendRef = theHandle.getSeriesLegendReference(); + if (legendRef != null && !legendRef.equals("")) { + String sheetnm = legendRef.substring(0, legendRef.indexOf("!")); + legendRef = legendRef + .substring(legendRef.indexOf("!") + 1); + int[] rc = ExcelTools.getRowColFromString(legendRef); + rc[0] = rc[0] + 1; + legendRef = sheetnm + "!" + ExcelTools.formatLocation(rc); + } else if (legendRef == null) { + legendRef = theHandle.getSeriesLegend(); + } else { + legendRef = ""; + } + String categoryRange = theHandle.getCategoryRange(); + String seriesRange = theHandle.getSeriesRange(); + String sheetnm = seriesRange.substring(0, seriesRange.indexOf("!")); + seriesRange = seriesRange + .substring(seriesRange.indexOf("!") + 1); + int[] rc = ExcelTools.getRangeRowCol(seriesRange); + // fiddle it, since exceltools doesn't translate back/forth + int[] newRc = new int[4]; + newRc[0] = rc[1]; + newRc[1] = rc[0] + 1; + newRc[2] = rc[3]; + newRc[3] = rc[2] + 1; + seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); + try { + return this + .addSeriesRange(legendRef, "", categoryRange, seriesRange, "", nChart); + } catch (CellNotFoundException e) { + Logger.logErr("ChartHandle.appendRowSeriesToChart: Unable to append series to chart: " + + e); + } + return null; + } + + /** + * Append a row of categories to the bottom of the chart.
    + * Expands all Series to include the new bottom row.
    + * To be utilized when expanding a chart to encompass more data that has a + * col-based series. + * + * @see ChartHandle.appendRowSeriesToChart + */ + public void appendRowCategoryToChart() { + appendRowCategoryToChart(0); // default chart + } + + /** + * Append a row of categories to the bottom of the chart.
    + * Expands all Series to include the new bottom row.
    + * To be utilized when expanding a chart to encompass more data that has a + * col-based series. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @see ChartHandle.appendRowSeriesToChart + */ + public void appendRowCategoryToChart(int nChart) { + ChartSeriesHandle[] handles = this.getAllChartSeriesHandles(nChart); + + for (int i = 0; i < handles.length; i++) { + ChartSeriesHandle theHandle = handles[i]; + // update the series + String seriesRange = theHandle.getSeriesRange(); + String[] s = ExcelTools.stripSheetNameFromRange(seriesRange); + String sheetnm = s[0]; + seriesRange = s[1]; + // String sheetnm = seriesRange.substring(0, + // seriesRange.indexOf("!")); + // seriesRange = seriesRange.substring( + // seriesRange.indexOf("!")+1, + // seriesRange.length()); + // Strip 2nd sheet ref, if any 20080213 KSC + // int n= seriesRange.indexOf('!'); + // int m= seriesRange.indexOf(':'); + // seriesRange= seriesRange.substring(0, m+1) + + // seriesRange.substring(n+1); + + int[] rc = ExcelTools.getRangeRowCol(seriesRange); + int[] newRc = new int[4]; + newRc[0] = rc[1]; + newRc[1] = rc[0]; + newRc[2] = rc[3]; + newRc[3] = rc[2] + 1; + seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); + theHandle.setSeriesRange(seriesRange); + // update the category + seriesRange = theHandle.getCategoryRange(); + s = ExcelTools.stripSheetNameFromRange(seriesRange); + sheetnm = s[0]; + seriesRange = s[1]; + /* + * sheetnm = seriesRange.substring(0, + * seriesRange.indexOf("!")); seriesRange = + * seriesRange.substring( seriesRange.indexOf("!")+1, + * seriesRange.length()); // + * Strip 2nd sheet ref, if any 20080213 KSC n= + * seriesRange.indexOf('!'); m= + * seriesRange.indexOf(':'); seriesRange= + * seriesRange.substring(0, m+1) + + * seriesRange.substring(n+1); + */ + rc = ExcelTools.getRangeRowCol(seriesRange); + newRc = new int[4]; + newRc[0] = rc[1]; + newRc[1] = rc[0]; + newRc[2] = rc[3]; + newRc[3] = rc[2] + 1; + seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); + theHandle.setCategoryRange(seriesRange); + } + } + + /* + * NOT IMPLEMENTED YET adjust chart cell references upon row + * insertion or + * deletion NOTE: Assumes we're on the correct sheet NOT + * COMPLETELY IMPLEMENTED + * YEt + * + * @param rownum + * + * @param shiftamt +1= insert row, -1= delete row + * + * public void adjustCellRefs(int rownum, int shiftamt) { + * Vector v = + * mychart.getAllSeries(); boolean bSeriesRows= false; + * boolean bMod= false; for + * (int i=0;i 0) { // + * insert row i.e. + * shift ai location down if ((loc.length==2 && + * loc[0]>=rownum) || + * (loc[0]>=rownum || loc[2]>=rownum)) { + * adjustAiLocation(ai, + * p[0].getIntLocation(), shiftamt); bMod= true; } } else { + * if ((loc.length==2 + * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) + * { // remove it + * this.removeSeries(i); bMod= true; continue; } } } + * catch(Exception e) { + * + * } } catch (Exception e) { + * + * } try { // CATEGORY Ai ai= s.getCategoryValueAi(); Ptg[] + * p=ai.getCellRangePtgs(); try { int[] loc= + * p[0].getIntLocation(); if (shiftamt + * > 0) { // insert row i.e. shift ai location down if + * ((loc.length==2 && + * loc[0]>=rownum) || (loc[0]>=rownum || loc[2]>=rownum)) { + * adjustAiLocation(ai, + * p[0].getIntLocation(), shiftamt); bMod= true; } } else { + * if ((loc.length==2 + * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) + * { // remove it ???? + * this.removeSeries(i); bMod= true; } } } catch(Exception + * e) { + * + * } } catch (Exception e) { + * + * } try { // LEGEND Ai ai= s.getLegendAi(); Ptg[] + * p=ai.getCellRangePtgs(); + * int[] loc= p[0].getIntLocation(); if (shiftamt > 0) { // + * insert row i.e. + * shift ai location down if ((loc.length==2 && + * loc[0]>=rownum) || + * (loc[0]>=rownum || loc[2]>=rownum)) { + * adjustAiLocation(ai, + * p[0].getIntLocation(), shiftamt); bMod= true; } } else { + * if ((loc.length==2 + * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) + * { // remove it + * this.removeSeries(i); bMod= true; } } } catch(Exception + * e) { + * + * } } if (bMod)// one or more series elements were modified + * setDimensionsRecord(); // update dimensions i.e. Data + * Range + * + * } + */ + + /* + * doesn't appear to be used right now move the ai location + * (row) according to + * shift amount + * + * @param ai + * + * @param loc + * + * @param shift + * + * private void adjustAiLocation(Ai ai, int[] loc, int + * shift) { String oldloc= + * ExcelTools.formatLocation(loc); if (loc.length>2) // get + * 2nd part of range + * oldloc += ":" + ExcelTools.formatLocation(new + * int[]{loc[2], loc[3]}); oldloc= + * this.getSheet().getSheetName() + "!" + oldloc; if + * (loc.length==2)// single + * cell loc[0]+=shift; else { // range loc[0]+=shift; + * loc[2]+=shift; } String + * newloc= ExcelTools.formatLocation(loc); if (loc.length>2) + * // get 2nd part of + * range newloc += ":" + ExcelTools.formatLocation(new + * int[]{loc[2], loc[3]}); + * ai.changeAiLocation(oldloc, newloc); } + * + */ + + /** + * Get the Chart's bytes + *

    + * This is an internal method that is not useful to the end user. + */ + public byte[] getChartBytes() { + return mychart.getChartBytes(); + } + + public byte[] getSerialBytes() { + return mychart.getSerialBytes(); + } + + /** + * get the chart-type-specific options in XML form + * + * @return String XML + */ + private String getChartOptionsXML() { + return mychart.getChartOptionsXML(0); // 0 for default chart + } + + private String t(int n) { + String tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t"; + return (tabs.substring(0, n)); + } + + /** + * given a XmlPullParser positioned at the chart element, parse all chart + * elements to create desired chart + * + * @param sht WorkSheetHandle + * @param xpp XmlPullParser + */ + public void parseXML(WorkSheetHandle sht, XmlPullParser xpp, HashMap maps) { + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("ChartFonts")) { + for (int x = 0; x < xpp.getAttributeCount(); x++) { + this.setChartFont(xpp.getAttributeName(x), xpp + .getAttributeValue(x)); + } + } else if (tnm.equals("ChartFontRec")) { + String fName = ""; + int fontId = 0, fSize = 0, fWeight = 0, fColor = 0, + fUnderline = 0; + boolean bIsBold = false; + for (int x = 0; x < xpp.getAttributeCount(); x++) { + String attr = xpp.getAttributeName(x); + String val = xpp.getAttributeValue(x); + if (attr.equals("name")) { + fName = val; + } else if (attr.equals("id")) { + fontId = Integer.parseInt(val); + } else if (attr.equals("size")) { + fSize = Font.PointsToFontHeight(Double + .parseDouble(val)); + } else if (attr.equals("color")) { + fColor = FormatHandle + .HexStringToColorInt(val, FormatHandle.colorFONT); + if (fColor == 0) + fColor = 32767; // necessary? + } else if (attr.equals("weight")) { + fWeight = Integer.parseInt(val); + } else if (attr.equals("bold")) { + bIsBold = true; + } else if (attr.equals("underline")) { + fUnderline = Integer.parseInt(val); + } + } + while (this.getWorkBook().getNumFonts() < fontId - 1) { + this.getWorkBook().insertFont(new Font("Arial", + FormatConstants.PLAIN, 10)); + } + if (this.getWorkBook().getNumFonts() < fontId) { + Font f = new Font(fName, fWeight, fSize); + f.setColor(fColor); + f.setBold(bIsBold); + f.setUnderlineStyle((byte) fUnderline); + this.getWorkBook().insertFont(f); + } else { // TODO: this will screw up linked fonts, + // perhaps, so what to do? + Font f = this.getWorkBook().getFont(fontId); + f.setFontWeight(fWeight); + f.setFontName(fName); + f.setFontHeight(fSize); + f.setColor(fColor); + f.setBold(bIsBold); + f.setUnderlineStyle((byte) fUnderline); + } + } else if (tnm.equals("FormatChartArea")) { // TODO: + // something! + // ChartBorder + // ChartProperties + } else if (tnm.equals("Series")) { // series --> + // Legend Range Category shape typex typey + String legend = "", series = "", category = "", + bubble = ""; + String dataTypeX = "", dataTypeY = ""; + String shape = ""; + for (int x = 0; x < xpp.getAttributeCount(); x++) { + if (xpp.getAttributeName(x) + .equalsIgnoreCase("Legend")) + legend = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Range")) + series = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Category")) + category = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Bubbles")) + bubble = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("TypeX")) + dataTypeX = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("TypeY")) + dataTypeY = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Shape")) + shape = xpp.getAttributeValue(x); + } + // 20070709 KSC: can't add until all cells are added + // ch.addSeriesRange(legend, category, series); + String[] s = {legend, series, category, bubble, + dataTypeX, dataTypeY, shape}; + HashMap map = (HashMap) maps + .get("chartseries"); + map.put(s, this); + } else if (tnm.equals("ChartOptions")) { // handle + // chart-type-specific + // options such + // as legend + // options + // 20070716 KSC: handle chart-type-specific options in a + // very generic way ... + for (int x = 0; x < xpp.getAttributeCount(); x++) { + this.setChartOption(xpp.getAttributeName(x), xpp + .getAttributeValue(x)); + } + } else if (tnm.equals("ThreeD")) { // handle three-d options + // handle threeD record options in a very generic way + // ... + this.make3D(); // default chart - TODO; if mutliple + // charts, handle + for (int x = 0; x < xpp.getAttributeCount(); x++) { // now + // add + // threed + // rec + // options + this.setChartOption(xpp.getAttributeName(x), xpp + .getAttributeValue(x)); + } + } else if (tnm.endsWith("Axis")) { // handle axis specs + // (Label + options ...) + // 20070720 KSC: handle Axis record options ... + int type = 0; + String axis = xpp.getName(); + if (axis.equalsIgnoreCase("XAxis")) + type = XAXIS; + else if (axis.equalsIgnoreCase("YAxis")) + type = YAXIS; + else if (axis.equalsIgnoreCase("ZAxis")) + type = ZAXIS; + if (xpp.getAttributeCount() > 0) { // then has axis + // options + for (int x = 0; x < xpp.getAttributeCount(); x++) { + this.setAxisOption(type, xpp + .getAttributeName(x), xpp + .getAttributeValue(x)); + } + } else { // no axis options means no axis present; + // remove + this.removeAxis(type); + } + } else if (tnm.equals("Series")) { // handle series data + // Legend Range Category + String legend = "", series = "", category = "", + bubble = ""; + String dataTypeX = "", dataTypeY = ""; + String shape = ""; + for (int x = 0; x < xpp.getAttributeCount(); x++) { + if (xpp.getAttributeName(x) + .equalsIgnoreCase("Legend")) + legend = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Range")) + series = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Category")) + category = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Bubbles")) + bubble = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("TypeX")) + dataTypeX = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("TypeY")) + dataTypeY = xpp.getAttributeValue(x); + else if (xpp.getAttributeName(x) + .equalsIgnoreCase("Shape")) + shape = xpp.getAttributeValue(x); + } + // 20070709 KSC: can't add until all cells are added + // ch.addSeriesRange(legend, category, series); + String[] s = {legend, series, category, bubble, + dataTypeX, dataTypeY, shape}; + HashMap map = (HashMap) maps + .get("chartseries"); + map.put(s, this); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals("Chart")) + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logWarn("ChartHandle.parseXML <" + xpp.getName() + ">: " + + e.toString()); + // TODO: propogate Exception??? + } + } + + /** + * returns an XML representation of this chart + * + * @return String XML + */ + public String getXML() { + StringBuffer sb = new StringBuffer(t(1) + "\n"); + + // Chart Fonts + sb.append(t(2) + "" + this.getChartFontRecsXML()); + sb.append("\n" + t(2) + "\n"); + sb.append(t(2) + "\n"); + // Format Chart Area + sb.append(t(2) + "\n"); + sb.append(t(3) + "\n"); // KSC: TODO: BORDER + sb.append(t(3) + "\n"); // TODO: + // Properties + sb.append(t(2) + "\n"); + // Source Data + sb.append(t(2) + "\n"); + ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); + for (int i = 0; i < series.length; i++) { + sb.append(t(3) + "\n"); + } + sb.append(t(2) + "\n"); + // Chart Options + sb.append(t(2) + "\n"); + // Axis Options + sb.append(t(2) + "\n"); + sb.append(t(3) + "\n"); + sb.append(t(3) + "\n"); + sb.append(t(3) + "\n"); + sb.append(t(2) + "\n"); + // ThreeD rec opts + if (this.isThreeD()) { + sb.append(t(2) + "\n"); + } + + sb.append(t(1) + "\n"); + return sb.toString(); + } + + /******************************************************************** + * OOXML Generation Methods + **********************************************************************/ + /** + * Generates OOXML (chartML) for this chart object. + * + *
    + * NOTE: necessary root chartSpace element + namespaces are not set here + * + * @param int rId -reference ID for this chart + * @return String representing the OOXML describing this Chart + */ + public String getOOXML(int rId) { + // TODO: finish 3d options- floor, sideWall, backWall + // TODO: finish axis options + // TODO: printSettings + + // generate OOXML (chartML) + StringBuffer cooxml = new StringBuffer(); + try { + mychart.getChartSeries().resetSeriesNumber(); // reset series idx + // retrieve pertinent chart data + // axes id's TODO: HANDLE MULTIPLE AXES per chart ... + String catAxisId = Integer + .toString((int) (Math.random() * 1000000)); + String valAxisId = Integer + .toString((int) (Math.random() * 1000000)); + String serAxisId = Integer + .toString((int) (Math.random() * 1000000)); + OOXMLChart thischart; + if ((mychart instanceof OOXMLChart)) + thischart = (OOXMLChart) mychart; + else { // XLS->XLSX + thischart = new OOXMLChart(mychart, wbh); + mychart = thischart; + thischart.getChartSeries().setParentChart(thischart); + } + thischart.wbh = this.wbh; + + cooxml.append(thischart.getOOXML(catAxisId, valAxisId, serAxisId)); + + // TODO: + ArrayList chartEmbeds = thischart.getChartEmbeds(); + if (chartEmbeds != null) { + int j = 0; + for (int i = 0; i < chartEmbeds.size(); i++) { + if (((String[]) chartEmbeds.get(i))[0] + .equals("userShape")) { + j++; + cooxml.append(""); + } + } + } + } catch (Exception e) { + Logger.logErr("ChartHandle.getOOXML: error generating OOXML. Chart not created: " + + e.toString()); + } + return cooxml.toString(); + } + + /** + * generates the OOXML specific for DrawingML, specifying offsets and + * identifying the chart object.
    + * this Drawing ML (OOXML) is distinct from Chart ML (OOXML) which actually + * defines the chart object including series, categories and axes
    + * This is an internal method that is not useful to the end user. + * + * @param int id - the reference id for this chart + * @return String OOXML + */ + public String getChartDrawingOOXML(int id) { + TwoCellAnchor t = new TwoCellAnchor( + ((OOXMLChart) mychart).getEditMovement()); + t.setAsChart(id, OOXMLAdapter.stripNonAscii(this.getOOXMLName()) + .toString(), TwoCellAnchor.convertBoundsFromBIFF8(this + .getSheet(), mychart.getBounds())); // adjust BIFF8 + // bounds to + // OOXML units + return t.getOOXML(); + } + + /******************************************************************************** + * Parsing OOXML Methods + **********************************************************************/ + /** + * defines this chart object based on a Chart ML (OOXML) input Stream (root + * element=c:chartSpace)
    + * This is an internal method that is not useful to the end user. + * + * @param inputStream ii - representing chart OOXML + */ + public void parseOOXML(java.io.InputStream ii) { + // overlay in title, after layout + // varyColors val= "0" -- after grouping and before ser + + // series colors by theme: + /* + * accent1 - 6 + */ + /** + * chartSpace: chart (Chart) §5.7.2.27 clrMapOvr (Color Map Override) §5.7.2.30 + * date1904 (1904 Date System) §5.7.2.38 externalData (External Data + * Relationship) §5.7.2.63 extLst (Chart Extensibility) §5.7.2.64 lang (Editing + * Language) §5.7.2.87 pivotSource (Pivot Source) §5.7.2.145 printSettings + * (Print Settings) §5.7.2.149 protection (Protection) §5.7.2.150 roundedCorners + * (Rounded Corners) §5.7.2.160 spPr (Shape Properties) §5.7.2.198 style (Style) + * §5.7.2.203 txPr (Text Properties) §5.7.2.217 userShapes (Reference to Chart + * Drawing Part) §5.7.2.222 + */ + try { + OOXMLChart thischart = (OOXMLChart) mychart; + int drawingOrder = 0; // drawing order of the chart (0=default, 1-9 + // for multiple charts in 1) + boolean hasPivotTableSource = false; + + // remove any undesired formatting from default chart: + this.setTitle(""); // clear any previously set + mychart.getAxes().setPlotAreaBgColor(FormatConstants.COLOR_WHITE); + mychart.getAxes().setPlotAreaBorder(-1, -1); // remove plot area + // border + + java.util.Stack lastTag = new java.util.Stack(); // keep + // track + // of + // element + // hierarchy + + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + + xpp.setInput(ii, null); // using XML 1.0 specification + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); // main entry= chartSpace, + // children: lang, chart + lastTag.push(tnm); // keep track of element hierarchy + if (tnm.equals("chart")) { // beginning of DrawingML for a + // single image or chart; + // children: title, + // plotArea, legend, + /** + * 5 + * 6 + * 7 + * 8 + * 9 + * 10 + * 11 + * 12 + * + * plotArea contains layout, , serAx, valAx, catAx, dateAx, spPr, + * dTable 13 + * 14 + * + * 15 + * 16 + * 17 + * + * 18 + */ + } else if (tnm.equals("lang")) { + thischart.lang = xpp.getAttributeValue(0); + } else if (tnm.equals("roundedCorners")) { + thischart.roundedCorners = xpp.getAttributeValue(0) + .equals("1"); + } else if (tnm.equals("pivotSource")) { // has a pivot table + hasPivotTableSource = true; + } else if (tnm.equals("view3D")) { + ThreeD.parseOOXML(xpp, lastTag, thischart); + } else if (tnm.equals("layout")) { + thischart.plotAreaLayout = (Layout) Layout + .parseOOXML(xpp, lastTag).cloneElement(); + } else if (tnm.equals("legend")) { + thischart.showLegend(true, false); + thischart.ooxmlLegend = (io.starter.formats.OOXML.Legend) io.starter.formats.OOXML.Legend + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement(); + thischart.ooxmlLegend + .fill2003Legend(thischart.getLegend()); + // Parse actual CHART TYPE element (barChart, pieChart, + // etc.) + } else if (tnm + .equals(OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.LINECHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.PIECHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.AREACHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SCATTERCHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.RADARCHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SURFACECHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.DOUGHNUTCHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BUBBLECHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.OFPIECHART]) + || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.STOCKCHART]) + || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.BARCHART]) + || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.LINECHART]) + || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.PIECHART]) + || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.AREACHART]) + || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SCATTERCHART]) + || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART])) { // specific + // chart + // type- + ChartType + .parseOOXML(xpp, this.wbh, mychart, drawingOrder++); + lastTag.pop(); + } else if (tnm.equals("title")) { + thischart.setOOXMLTitle((Title) Title + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement(), this.wbh); + this.setTitle(thischart.getOOXMLTitle().getTitle()); + } else if (tnm.equals("spPr")) { // shape properties -- can + // be for plot area or + // chart space + String parent = lastTag.get(lastTag.size() - 2); + if (parent.equals("plotArea")) { + thischart.setSpPr(0, (SpPr) SpPr + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement()); + } else if (parent.equals("chartSpace")) { + thischart.setSpPr(1, (SpPr) SpPr + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement()); + } + } else if (tnm.equals("txPr")) { // text formatting + thischart.setTxPr((TxPr) TxPr + .parseOOXML(xpp, lastTag, this.wbh) + .cloneElement()); + } else if (tnm.equals("catAx")) { // child of plotArea + mychart.getAxes() + .parseOOXML(XAXIS, xpp, tnm, lastTag, this.wbh); + } else if (tnm.equals("valAx")) { // child of plotArea + if (mychart.getAxes().hasAxis(ChartConstants.XAXIS)) // usual, + // have + // a + // catAx + // then + // a + // valAx + mychart.getAxes() + .parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); + else if (mychart.getAxes() + .hasAxis(ChartConstants.YAXIS)) // for bubble + // charts, has + // two valAxes + // and no + // catAx + mychart.getAxes() + .parseOOXML(ChartConstants.XVALAXIS, xpp, tnm, lastTag, this.wbh); + else // 2nd val axis is Y axis + mychart.getAxes() + .parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); + } else if (tnm.equals("serAx")) { // series axis - 3d charts + mychart.getAxes() + .parseOOXML(ZAXIS, xpp, tnm, lastTag, this.wbh); + } else if (tnm.equals("dateAx")) { // TODO: not finished: + // figure out! + // ?? + } + + } else if (eventType == XmlPullParser.END_TAG) { + lastTag.pop(); + String endTag = xpp.getName(); + if (endTag.equals("chartSpace")) { + setDimensionsRecord(); + break; // done processing + } + } + if (xpp.getEventType() != XmlPullParser.END_DOCUMENT) + eventType = xpp.next(); + else + eventType = XmlPullParser.END_DOCUMENT; + } + } catch (Exception e) { + Logger.logErr("ChartHandle.parseChartOOXML: " + e.toString()); + } + } + + /** + * Specifies how to resize or move this Chart upon edit
    + * This is an internal method that is not useful to the end user.
    + * Excel 7/OOXML specific + * + * @param editMovement String OOXML-specific edit movement setting + */ + public void setEditMovement(String editMovement) { + ((OOXMLChart) mychart).setEditMovement(editMovement); + } + + /** + * return the Excel 7/OOXML-specific name for this chart + * + * @return String OOXML name + */ + private String getOOXMLName() { + return ((OOXMLChart) mychart).getOOXMLName(); + } + + /** + * set the Excel 7/OOXML-specific name for this chart + * + * @param String name + */ + public void setOOXMLName(String name) { + ((OOXMLChart) mychart).setOOXMLName(name); + } + + /** + * returns the drawingml file name which defines the userShape (if any)
    + * a userShape is a drawing or shape ontop of a chart associated with this chart + * + * @return + */ + public ArrayList getChartEmbeds() { + return ((OOXMLChart) mychart).getChartEmbeds(); + } + + /** + * sets external information linked to or "embedded" in this OOXML chart; can be + * a chart user shape, an image ...
    + * NOTE: a userShape is a drawingml file name which defines the userShape (if + * any)
    + * a userShape is a drawing or shape ontop of a chart + * + * @param String[] embedType, filename e.g. {"userShape", "userShape file name"} + */ + public void addChartEmbed(String[] ce) { + ((OOXMLChart) mychart).addChartEmbed(ce); + } + + /** + * set the chart DIMENSIONS record based on the series ranges in the chart + * APPEARS THAT for charts, the DIMENSIONS record merely notes the range of + * values: 0, #points in series, 0, #series + */ + protected void setDimensionsRecord() { + ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); + int nSeries = series.length; + int nPoints = 0; + for (int i = 0; i < series.length; i++) { + try { + int[] coords = ExcelTools + .getRangeCoords(series[i].getSeriesRange()); + if (coords[3] > coords[1]) + nPoints = Math.max(nPoints, coords[3] - coords[1] + 1); // c1-c0 + else + nPoints = Math.max(nPoints, coords[2] - coords[0] + 1); // r1-r0 + } catch (Exception e) { + } + } + mychart.setDimensionsRecord(0, nPoints, 0, nSeries); + } + + /** + * Method for setting Chart-Type-specific options in a generic fashion e.g. + * charthandle.setChartOption("Stacked", "true"); + *

    + * Note: since most Chart Type Options are interdependent, there are several + * makeXX methods that set the desired group of options e.g. makeStacked(); use + * setChartOption with care + *

    + * Note that not all Chart Types will have every option available + *

    + * Possible Options: + *

    + * "Stacked" - true or false - set Chart Series to be Stacked
    + * "Cluster" - true or false - set Clustered for Column and Bar Chart Types
    + * "PercentageDisplay" - true or false - Each Category is broken down as a + * percentge
    + * "Percentage" - Distance of pie slice from center of pie as % for Pie Charts + * (0 for all others)
    + * "donutSize" - Donut size for Donut Charts Only
    + * "Overlap" - Space between bars (default= 0%)
    + * "Gap" - Space between categories (%) (default=50%)
    + * "SmoothedLine" - true or false - the Line series has a smoothed line
    + * "AnRot" - Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for others + * (3D option)
    + * "AnElev" - Elevation Angle (-90 to 90 degrees) (15 is default) (3D option) + *
    + * "ThreeDScaling" - true or false - 3d effect
    + * "TwoDWalls" - true if 2D walls (3D option)
    + * "PcDist" - Distance from eye to chart (0 to 100) (30 is default) (3D option) + *
    + * "ThreeDBubbles" - true or false - Draw bubbles with a 3d effect
    + * "ShowLdrLines" - true or false - Show Pie and Donut charts Leader Lines
    + * "MarkerFormat" - "0" thru "9" for various marker options @see + * ChartHandle.setMarkerFormat
    + * "ShowLabel" - true or false - show Series/Data Label
    + * "ShowCatLabel" - true or false - show Category Label
    + * "ShowLabelPct" - true or false - show percentage labels for Pie charts
    + * "ShowBubbleSizes" - true or false - show bubble sizes for Bubble charts + *

    + * NOTE: all values must be in String form + * + * @see ChartHandle.getXML + */ + public void setChartOption(String op, String val) { + mychart.setChartOption(op, val); + } + + /** + * Method for setting Chart-Type-specific options in a generic fashion e.g. + * charthandle.setChartOption("Stacked", "true"); + * + * @param op + * @param val + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + private void setChartOption(String op, String val, int nChart) { + mychart.setChartOption(op, val, nChart); + } + + /** + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return true if Chart has 3D effects, false otherwise + */ + public boolean isThreeD(int nChart) { + return mychart.isThreeD(nChart); + } + + /** + * @return true if Chart has 3D effects, false otherwise + */ + public boolean isThreeD() { + return mychart.isThreeD(0); // default chart + } + + /** + * @return boolean true if Chart contains Stacked Series, false otherwise + */ + public boolean isStacked() { + return mychart.isStacked(0); // default chart + } + + /** + * @return boolean true if Chart is of type 100% Stacked, false otherwise + */ + public boolean is100PercentStacked() { + return mychart.is100PercentStacked(0); // default chart + } + + /** + * @return boolean true if Chart contains Clustered Bars or Columns, false + * otherwise + */ + public boolean isClustered() { + return mychart.isClustered(0); // default chart + } + + /** + * @return String ThreeD options in XML form + */ + public String getThreeDXML() { + return mychart.getThreeDXML(0); // 0 for default chart + } + + /** + * Make chart 3D if not already + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ThreeD rec + */ + ThreeD initThreeD(int nChart) { + return mychart.initThreeD(nChart, this.getChartType(nChart)); + } + + /** + * @param int Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) + * @return String XML representation of the desired Axis + */ + private String getAxisOptionsXML(int Axis) { + return mychart.getAxes().getAxisOptionsXML(Axis); + } + + /** + * Returns the Axis Label Placement or position as an int + *

    + * One of:
    + * Axis.INVISIBLE - axis is hidden
    + * Axis.LOW - low end of plot area
    + * Axis.HIGH - high end of plot area
    + * Axis.NEXTTO- next to axis (default) + * + * @param int Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) + * @return int - one of the Axis Label placement constants above + */ + public int getAxisPlacement(int Axis) { + return mychart.getAxes().getAxisPlacement(Axis); + } + + /** + * Sets the Axis labels position or placement to the desired value (these match + * Excel placement options) + *

    + * Possible options:
    + * Axis.INVISIBLE - hides the axis
    + * Axis.LOW - low end of plot area
    + * Axis.HIGH - high end of plot area
    + * Axis.NEXTTO- next to axis (default) + * + * @param int Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) + * @param Placement - int one of the Axis placement constants listed above + */ + public void setAxisPlacement(int Axis, int Placement) { + mychart.getAxes().setAxisPlacement(Axis, Placement); + mychart.setDirtyFlag(true); + } + + /* + * returns the desired axis If bCreateIfNecessary, will + * creates if doesn't exist + * + * @return Axis Object / private Axis getAxis(int axisType, + * boolean + * bCreateIfNecessary) { return mychart.getAxis(axisType, + * bCreateIfNecessary); } + */ + + /** + * removes the desired Axis from the Chart + * + * @param int axisType - one of the Axis constants (XAXIS, YAXIS or ZAXIS) + */ + public void removeAxis(int axisType) { + mychart.getAxes().removeAxis(axisType); + mychart.setDirtyFlag(true); + } + + /** + * returns Chart-specific Font Records in XML form + * + * @return String Chart Font information in XML format + */ + public String getChartFontRecsXML() { + return mychart.getChartFontRecsXML(); + } + + /** + * Return non-axis Chart font ids in XML form + * + * @return String Font information in XML format + */ + public String getChartFontsXML() { + return mychart.getChartFontsXML(); + } + + /** + * Set non-axis chart font id for title, default, etc
    + * For Internal Use Only + * + * @param String type - font type + * @param String val - font id + */ + public void setChartFont(String type, String val) { + mychart.setChartFont(type, val); + } + + /** + * @return the WorkBook Object attached to this Chart + */ + public io.starter.formats.XLS.WorkBook getWorkBook() { + return mychart.getWorkBook(); + } + + public WorkBookHandle getWorkBookHandle() { + return this.wbh; + } + + public WorkSheetHandle getWorkSheetHandle() { + try { + return this.wbh.getWorkSheet(mychart.getSheet().getSheetNum()); + } catch (WorkSheetNotFoundException e) { + // this should be impossible + throw new RuntimeException(e); + } + } + + /** + * sets the coordinates or bounds (position, width and height) of this chart in + * pixels + * + * @param short[4] bounds - left or x value, top or y value, width, height + * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT + */ + // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and + // Coords are very + // distinct + public void setBounds(short[] bounds) { + mychart.setCoords(bounds); + } + + /** + * returns the coordinates or bounds (position, width and height) of this chart + * in pixels + * + * @return short[4] bounds - left or x value, top or y value, width, height + * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT + */ + // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and + // Coords are very + // distinct + public short[] getBounds() { + return mychart.getCoords(); + } + + /** + * sets the coordinates (position, width and height) for this chart in Excel + * size units + * + * @return short[4] pixel coords - left or x value, top or y value, width, + * height + * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT + */ + public void setCoords(short[] coords) { + mychart.setCoords(coords); + } + + /** + * returns the coordinates (position, width and height) of this chart in Excel + * size units + * + * @return short[4] pixel coords - left or x value, top or y value, width, + * height + * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT + */ + public short[] getCoords() { + mychart.getMetrics(this.wbh); + return mychart.getCoords(); + } + + /** + * get the bounds of the chart using coordinates relative to row/cols and their + * offsets + * + * @return short[8] bounds - COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, + * ROW1, ROWOFFSET1 + */ + public short[] getRelativeBounds() { + return mychart.getBounds(); + } + + /** + * returns the offset within the column in pixels + * + * @return + */ + public short getColOffset() { + return mychart.getColOffset(); + } + + /** + * sets the bounds of the chart using coordinates relative to row/cols and their + * offsets + * + * @param short[8] bounds - COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, + * ROWOFFSET1 + */ + public void setRelativeBounds(short[] bounds) { + mychart.setBounds(bounds); + } + + // 20070802 KSC: Debug uility to write out chartrecs + /* + * For internal debugging use only + * + * public void writeChartRecs(String fName) { + * mychart.writeChartRecs(fName); } + * + * /** set DataLabels option for this chart + * + * @param String type - see below + * + * @param boolean bShowLegendKey - true if show legend key, + * false otherwise + *

    possible String type values:
    Series
    Category + *
    Value + *
    Percentage (Only for Pie Charts)
    Bubble (Only for + * Bubble Charts) + *
    X Value (Only for Bubble Charts)
    Y Value (Only + * for Bubble Charts) + *
    CandP + * + *

    NOTE: not 100% implemented at this time + */ + public void setDataLabel(String/* [] */ type, boolean bShowLegendKey) { + /* + * for now, only 1 option is valid - multiple legend + * settings e.g. Category and + * Value are not figured out yet for (int i= 0; i < + * type.length; i++) + * mychart.setChartOption("DataLabel", type[i]); + */ + if (!bShowLegendKey) + mychart.setChartOption("DataLabel", type); + else + mychart.setChartOption("DataLabelWithLegendKey", type); + } + + /** + * shows or removes the Data Table for this chart + * + * @param boolean bShow - true if show data table + */ + public void showDataTable(boolean bShow) { + mychart.showDataTable(bShow); + } + + /** + * shows or hides the Chart legend key + * + * @param booean bShow - true if show legend, false to hide + * @param boolean vertical - true if show vertically, false for horizontal + */ + public void showLegend(boolean bShow, boolean vertical) { + mychart.showLegend(bShow, vertical); + } + + /** + * returns true if Chart has a Data Legend Key showing + * + * @return true if Chart has a Data Legend Key showing + */ + public boolean hasDataLegend() { + return mychart.hasDataLegend(); + } + + public void removeLegend() { + mychart.removeLegend(); + } + + // 20070905 KSC: Group chart options for ease of setting + // almost all charts have these specific ChartTypes: + + /** + * makes this Chart Stacked
    + * sets the group of options necessary to create a stacked chart
    + * For Chart Types:
    + * BAR, COL, LINE, AREA, PYRAMID, PYRAMIDBAR, CYLINDER, CYLINDERBAR, CONE, + * CONEBAR + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @deprecated + */ + @Deprecated + public void makeStacked(int nChart) { // bar, col, line, area, pyramid col + + // bar, cone col + bar, cylinder col + // + bar + int chartType = this.getChartType(nChart); + this.setChartOption("Stacked", "true", nChart); + switch (chartType) { + case ChartConstants.BARCHART: + case ChartConstants.COLCHART: + this.setChartOption("Overlap", "-100", nChart); + break; + case ChartConstants.CYLINDERCHART: + case ChartConstants.CYLINDERBARCHART: + case ChartConstants.CONECHART: + case ChartConstants.CONEBARCHART: + case ChartConstants.PYRAMIDCHART: + case ChartConstants.PYRAMIDBARCHART: + this.setChartOption("Overlap", "-100", nChart); + ThreeD td = this.initThreeD(nChart); + td.setChartOption("Cluster", "false"); + break; + case ChartConstants.LINECHART: + this.setChartOption("Percentage", "0", nChart); + break; + case ChartConstants.AREACHART: + this.setChartOption("Overlap", "-100", nChart); + this.setChartOption("Percentage", "25", nChart); + this.setChartOption("SmoothedLine", "true", nChart); + break; + } + } + + /** + * makes this Chart 100% Stacked
    + * sets the group of options necessary to create a 100% stacked chart
    + * For Chart Types:
    + * BAR, COL, LINE, PYRAMID, PYRAMIDBAR, CYLINDER, CYLINDERBAR, CONE, CONEBAR + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @deprecated + */ + @Deprecated + public void make100PercentStacked(int nChart) { // bar, col, line, pyramid + // col + bar, cone col + + // bar, cylinder col + + // bar + int chartType = this.getChartType(nChart); + this.setChartOption("Stacked", "true", nChart); + this.setChartOption("PercentageDisplay", "true", nChart); + switch (chartType) { + case ChartConstants.COLCHART: // + pyramid + case ChartConstants.BARCHART: + this.setChartOption("Overlap", "-100", nChart); + break; + case ChartConstants.CYLINDERCHART: + case ChartConstants.CYLINDERBARCHART: + case ChartConstants.CONECHART: + case ChartConstants.CONEBARCHART: + case ChartConstants.PYRAMIDCHART: + case ChartConstants.PYRAMIDBARCHART: + this.setChartOption("Overlap", "-100", nChart); + ThreeD td = this.initThreeD(nChart); + td.setChartOption("Cluster", "false"); + break; + case ChartConstants.LINECHART: + break; + } + } + + /** + * makes this Chart Stacked with a 3D Effect
    + * sets the group of options necessary to create a Stacked 3D chart
    + * For Chart Types:
    + * BAR, COL, AREA + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + public void makeStacked3D(int nChart) { // bar, col, area + int chartType = this.getChartType(nChart); + this.setChartOption("Stacked", "true", nChart); + ThreeD td = this.initThreeD(nChart); + td.setChartOption("AnRot", "20"); + td.setChartOption("ThreeDScaling", "true"); + td.setChartOption("TwoDWalls", "true"); + switch (chartType) { + case ChartConstants.COLCHART: + case ChartConstants.BARCHART: + this.setChartOption("Overlap", "-100", nChart); + break; + case ChartConstants.AREACHART: + this.setChartOption("Percentage", "25", nChart); + this.setChartOption("SmoothedLine", "true", nChart); + break; + } + } + + /** + * makes this Chart 100% Stacked with a 3D Effect
    + * sets the group of options necessary to create a 100% Stacked 3D chart
    + * For Chart Types:
    + * BAR, COL, AREA + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @deprecated + */ + @Deprecated + public void make100PercentStacked3D(int nChart) { // bar, col, area + int chartType = this.getChartType(nChart); + this.setChartOption("Stacked", "true", nChart); + this.setChartOption("PercentageDisplay", "true", nChart); + switch (chartType) { + case ChartConstants.COLCHART: // + pyramid + case ChartConstants.BARCHART: + this.setChartOption("Overlap", "-100", nChart); + ThreeD td = this.initThreeD(nChart); + td.setChartOption("AnRot", "20"); + td.setChartOption("ThreeDScaling", "true"); + td.setChartOption("TwoDWalls", "true"); + break; + case ChartConstants.AREACHART: + this.setChartOption("Percentage", "25", nChart); + this.setChartOption("SmoothedLine", "true", nChart); + break; + } + } + + /** + * makes the default Chart hava a 3D effect
    + * sets the group of options necessary to create a 3D chart
    + * For Chart Types:
    + * BARCHART, COLCHART, LINECHART, PIECHART, AREACHART, BUBBLECHART, + * PYRAMIDCHART, CYLINDERCHART, CONECHART + * + * @deprecated use setChartType(chartType, 0, is3d, isStacked, + * is100PercentStacked) instead + */ + @Deprecated + public void make3D() { // bar, col, line, pie, area, bubble, pyramid, cone, + // cylinder + make3D(0); + } + + /** + * makes the desired Chart hava a 3D effect
    + * where nChart 0= default, 1-9=multiple charts in one
    + * sets the group of options necessary to create a 3D chart
    + * For Chart Types:
    + * BARCHART, COLCHART, LINECHART, PIECHART, AREACHART, BUBBLECHART, + * PYRAMIDCHART, CYLINDERCHART, CONECHART + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @deprecated use setChartType(chartType, nChart, is3d, isStacked, + * is100%Stacked) instead + */ + @Deprecated + public void make3D(int nChart) { // bar, col, line, pie, area, bubble, + // pyramid, cone, cylinder + int chartType = this.getChartType(nChart); + ThreeD td = null; + switch (chartType) { + case ChartConstants.COLCHART: + case ChartConstants.BARCHART: + td = this.initThreeD(nChart); + td.setChartOption("AnRot", "20"); + td.setChartOption("ThreeDScaling", "true"); + td.setChartOption("TwoDWalls", "true"); + break; + case ChartConstants.CYLINDERCHART: + case ChartConstants.CONECHART: + case ChartConstants.PYRAMIDCHART: + td = this.initThreeD(nChart); + td.setChartOption("Cluster", "false"); + break; + case ChartConstants.AREACHART: + this.setChartOption("Percentage", "25", nChart); + this.setChartOption("SmoothedLine", "true", nChart); + td = this.initThreeD(nChart); + td.setChartOption("AnRot", "20"); + td.setChartOption("ThreeDScaling", "true"); + td.setChartOption("TwoDWalls", "true"); + td.setChartOption("Perspective", "true"); + break; + case ChartConstants.PIECHART: + case ChartConstants.LINECHART: + this.initThreeD(nChart); // just create a threeD rec w/ no extra + // options + break; + case ChartConstants.BUBBLECHART: + this.setChartOption("Percentage", "25", nChart); + this.setChartOption("SmoothedLine", "true", nChart); + this.setChartOption("ThreeDBubbles", "true", nChart); + td = this.initThreeD(nChart); // 20081228 KSC + break; + } + } + + // more specialized option sets + + /** + * makes this Chart Clusted with a 3D effect
    + * sets the group of options necessary to create a Clusted 3D chart
    + * For Chart Types:
    + * BAR, COL + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @deprecated + */ + @Deprecated + public void makeClustered3D(int nChart) { // only for Column and Bar (?) + int chartType = this.getChartType(nChart); + switch (chartType) { + case ChartConstants.BARCHART: + case ChartConstants.COLCHART: + ThreeD td = this.initThreeD(nChart); + td.setChartOption("AnRot", "20"); + td.setChartOption("Cluster", "true"); + td.setChartOption("ThreeDScaling", "true"); + td.setChartOption("TwoDWalls", "true"); + break; + } + } + + /** + * makes this chart's wedges exploded i.e. separated
    + * For Chart Types:
    + * PIECHART, DOUGHNUTCHART + * + * @deprecated + */ + @Deprecated + public void makeExploded() { // pie, donut + int chartType = this.getChartType(); + switch (chartType) { + case ChartConstants.DOUGHNUTCHART: + this.setChartOption("SmoothedLine", "true"); + case ChartConstants.PIECHART: + this.setChartOption("ShowLdrLines", "true"); + this.setChartOption("Percentage", "25"); + break; + // ShowLdrLines="true" Percentage="25"/> + // exploded donut: ShowLdrLines="true" Donut="50" + // Percentage="25" + // SmoothedLine="true"/> + } + } + + /** + * makes this chart's wedges exploded 3D i.e. separated with a 3D effect
    + * For Chart Types:
    + * PIECHART, DOUGHNUTCHART + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @deprecated + */ + @Deprecated + public void makeExploded3D(int nChart) { // pie + // ShowLdrLines="true" Percentage="25" + // AnRot="236" + int chartType = this.getChartType(nChart); + switch (chartType) { + case ChartConstants.DOUGHNUTCHART: + case ChartConstants.PIECHART: + this.setChartOption("ShowLdrLines", "true", nChart); + this.setChartOption("Percentage", "25", nChart); + ThreeD td = this.initThreeD(nChart); + td.setChartOption("AnRot", "236"); + break; + } + } + + /* + * NOT IMPLEMENTED YET TODO: IMPLEMENT Make this Chart have + * smoothed lines + * (Scatter only) + * + * public void makeSmoothedLines() { // scatter + * //Percentage="25" + * SmoothedLine="true } + * + * public void makeWireFrame() { // surface // NO ColorFill, + * only // + * Percentage="25" SmoothedLine="true" // AnRot="20" + * Perspective="true" + * ThreeDScaling="true" TwoDWalls="true"/> // all else + * should be default for + * surface charts } public void makeContour() { // surface + * -- for wireframe + * surface, no ColorFill //ColorFill="true" Percentage="25" + * SmoothedLine="true"/> // AnElev="90" pcDist="0" + * Perspective="true" + * ThreeDScaling="true" TwoDWalls="true" } + */ + + /** + * set the marker format style for this chart
    + * one of:
    + * 0 = no marker
    + * 1 = square
    + * 2 = diamond
    + * 3 = triangle
    + * 4 = X
    + * 5 = star
    + * 6 = Dow-Jones
    + * 7 = standard deviation
    + * 8 = circle
    + * 9 = plus sign
    + * For Chart Types:
    + * LINE, SCATTER + * + * @param int imf - marker format constant from list above + */ + public void setMarkerFormat(int imf) { // line, scatter ... + this.setChartOption("MarkerFormat", String.valueOf(imf)); + } + + /** + * returs the JSON representation of this chart, based upon + * Dojo-charting-specifics + * + * @return String JSON representation of the chart + */ + public String getJSON() { + JSONObject theChart = new JSONObject(); + try { + JSONObject titles = new JSONObject(); + int type = this.getChartType(); // necessary for parsing AXIS + // options: horizontal charts + // "switch" axes ... + + // titles/labels + titles.put("title", this.getTitle()); + titles.put("XAxis", (type != ChartConstants.BARCHART) + ? (this.getXAxisLabel()) + : this.getYAxisLabel()); // bar + // axes + // are + // reversed + // ... + titles.put("YAxis", (type != ChartConstants.BARCHART) + ? (this.getYAxisLabel()) + : this.getXAxisLabel()); + try { + titles.put("ZAxis", this.getZAxisLabel()); + } catch (Exception e) { + Logger.logWarn("ChartHandle.getJSON failed getting zaxislable:" + + e.toString()); + } + theChart.put("titles", titles); + + // Chart dimensions (width, height) + short[] coords = mychart.getCoords(); + theChart.put("width", coords[ChartHandle.WIDTH]); + theChart.put("height", coords[ChartHandle.HEIGHT]); + theChart.put("row", mychart.getRow0()); // TODO: may not be + // necessary, see usage ... + theChart.put("col", mychart.getCol0()); + // Plot Area Background color + int plotAreabg = this.getPlotAreaBgColor(); + if (plotAreabg == 0x4D || plotAreabg == 0x4E) + plotAreabg = FormatConstants.COLOR_WHITE; + theChart.put("fill", FormatConstants.SVGCOLORSTRINGS[plotAreabg]); + + Double[] jMinMax = new Double[3]; + JSONObject chartObjectJSON = this.mychart.getChartObject() + .getJSON(this.mychart.getChartSeries(), this.wbh, jMinMax); + double yMax = 1.0, yMin = 0.0; + int nSeries = 0; + try { // it's possible to not have any series defined ... + theChart.put("Series", chartObjectJSON.getJSONArray("Series")); + // 20080416 KSC: Save SeriesJSON for later comparisons + mychart.setSeriesJSON(chartObjectJSON.getJSONArray("Series")); + theChart.put("SeriesFills", chartObjectJSON + .getJSONArray("SeriesFills")); // 20090729 KSC: capture + // bar + // colors or fills + } catch (Exception e) { + } + theChart.put("type", chartObjectJSON.getJSONObject("type")); + yMin = jMinMax[0].doubleValue(); + yMax = jMinMax[1].doubleValue(); + nSeries = jMinMax[2].intValue(); + + // Axes + Category Labels + Grid Lines + try { + // inputJSONObject(theChart, this.getAxis(YAXIS, + // false).getJSON(this.wbh, type, + // yMax, yMin, nSeries)); + theChart.put("y", mychart.getAxes() + .getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries) + .getJSONObject("y")); + theChart.put("back_grid", mychart.getAxes() + .getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries) + .getJSONObject("back_grid")); + } catch (Exception e) { + } + try { + // inputJSONObject(theChart, this.getAxis(XAXIS, + // false).getJSON(this.wbh, type, + // yMax, yMin, nSeries)); + theChart.put("x", mychart.getAxes() + .getJSON(XAXIS, this.wbh, type, yMax, yMin, nSeries) + .getJSONObject("x")); + theChart.put("back_grid", mychart.getAxes() + .getJSON(ChartConstants.YAXIS, this.wbh, type, yMax, yMin, nSeries) + .getJSONObject("back_grid")); + } catch (Exception e) { + } + // TODO: 3d Charts (z axis) + + /* + * /* Chart Fonts sb.append(t(2) + "" + + * this.getChartFontRecsXML()); sb.append("\n" + t(2) + + * "\n"); + * sb.append(t(2) + "\n"); + */ + /* + * Format Chart Area + */ + /* TODO: read in legend settings */ + + // Chart Legend + if (this.hasDataLegend()) { + short s = this.mychart.getLegend().getLegendPosition(); + String[] legends = this.mychart.getLegends(-1); // -1 is flag + // for all + // rather than + // for a + // specific + // chart + String l = ""; + for (int i = 0; i < legends.length; i++) + l += legends[i] + ","; + if (l.length() > 0) + l = l.substring(0, l.length() - 1); + theChart.put("legend", new JSONObject( + "{position:" + s + ",labels:[" + l + "]}")); + } + + } catch (JSONException e) { + Logger.logErr("Error getting Chart JSON: " + e); + } + return theChart.toString(); + } + + /** + * utility to add a JSON object
    + * This is an internal method that is not useful to the end user. + * + * @param source + * @param input + */ + protected void inputJSONObject(JSONObject source, JSONObject input) { + if (source != null) { + try { + for (int j = 0; j < input.names().length(); j++) { + source.put(input.names().getString(j), input + .get(input.names().getString(j))); + } + } catch (JSONException e) { + Logger.logErr("Error inputting JSON Object: " + e); + } + } + } + + /** + * retrieves the saved Series JSON for comparisons
    + * This is an internal method that is not useful to the end user. + * + * @return JSONArray + */ + public JSONArray getSeriesJSON() { + return mychart.getSeriesJSON(); + } + + /** + * sets the saved Series JSON
    + * This is an internal method that is not useful to the end user. + * + * @param JSONArray s - + * @throws JSONException + */ + public void setSeriesJSON(JSONArray s) throws JSONException { + mychart.setSeriesJSON(s); + } + + /** + * retrieves current series and axis scale info in JSONObject form used upon + * chart updating
    + * This is an internal method that is not useful to the end user. + * + * @return JSONObject series and axis info + */ + public JSONObject getCurrentSeries() { + JSONObject retJSON = new JSONObject(); + // Retrieve series data + yMin yMax, nSeries + Double[] jMinMax = new Double[3]; + try { + // Series Data + // 20080516 KSC: See above JSONObject chartObjectJSON= + // ((GenericChartObject)this.mychart.getChartObject()).getJSON( + // this.getAllChartSeriesHandles(), this.getCategories()[0], + // this.wbh, minMax); + JSONObject chartObjectJSON = this.mychart.getChartObject() + .getJSON(this.mychart.getChartSeries(), this.wbh, jMinMax); + + try { + retJSON.put("Series", chartObjectJSON.getJSONArray("Series")); + } catch (Exception e) { + Logger.logWarn("ChartHandle.getCurrentSeries problem:" + + e.toString()); + } + + // Retrieve Axis Scale info + double yMax = 0.0, yMin = 0.0; + int nSeries = 0; + yMin = jMinMax[0].doubleValue(); + yMax = jMinMax[1].doubleValue(); + nSeries = jMinMax[2].intValue(); + + int type = this.getChartType(); // necessary for parsing AXIS + // options: horizontal charts + // "switch" axes ... + // Axes + Category Labels + Grid Lines + /* + * KSC: TAKE OUT JSON STUFF FOR NOW; WILL REFACTOR LATER try + * { + * inputJSONObject(retJSON, + * mychart.getAxes().getMinMaxJSON(YAXIS, this.wbh, + * type, yMax, yMin, nSeries)); } catch (Exception e) { } + * try { + * inputJSONObject(retJSON, + * mychart.getAxes().getMinMaxJSON(XAXIS, this.wbh, + * type, yMax, yMin, nSeries)); } catch (Exception e) { } + */ + // TODO: 3d Charts (z axis) + } catch (JSONException e) { + Logger.logErr("ChartHandle.getCurrentSeries: Error getting Series JSON: " + + e); + } + return retJSON; + } + + /** + * returns a JSON representation of all Series Data (Legend, Categogies, Series + * Values) for the chart
    + * This is an internal method that is not useful to the end user. + * + * @return String JSON representation + */ + public String getAllSeriesDataJSON() { + JSONArray s = new JSONArray(); + ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); + try { + for (int i = 0; i < series.length; i++) { + JSONObject ser = new JSONObject(); + ser.put("l", series[i].getSeriesLegendReference()); + ser.put("v", series[i].getSeriesRange()); + ser.put("b", series[i].getBubbleSizes()); + if (i == 0) + ser.put("c", series[i].getCategoryRange()); // 1 per chart + s.put(ser); + } + } catch (JSONException e) { + Logger.logErr("ChartHandle.getAllSeriesDataJSON: " + e); + } + return s.toString(); + } + + /** + * Take current Chart object and return the SVG code necessary to define it. + */ + /** + * TODO: Less Common Charts: STOCK RADAR SURFACE COLUMN- 3D, CONE, CYLINDER, + * PYRAMID BAR- 3D, CONE, CYLINDER, PYRAMID 3D PIE 3D LINE 3D AREA + *

    + * LINE CHART APPEARS THAT STARTS AND ENDS A BIT TOO EARLY ***************** Z + * Axis + *

    + * CHART OPTIONS: STACKED CLUSTERED + */ + public String getSVG() { + return getSVG(1); + } + + /** + * /** Take current Chart object and return the SVG code necessary to define it, + * scaled to the desired percentage e.g. 0.75= 75% + * + * @param scale double scale factor + * @return String SVG + */ + public String getSVG(double scale) { + HashMap chartMetrics = mychart.getMetrics(wbh); // build + // or + // retrieve + // Chart + // Metrics + // --> + // dimensions + // + + // series + // data + // ... + + StringBuffer svg = new StringBuffer(); + // required header + // svg.append("\r\n"); // referneces + // the DTD + // svg.append("\r\n"); + + // Define SVG Canvas: + svg.append("\r\n"); + svg.append(""); // scale + // chart + // -default + // scale=1 + // == 100% + // JavaScript hooks + svg.append(getJavaScript()); + + // Data Legend Box -- do before drawing plot area as legends + // box may change plot + // area coordinates + String legendSVG = getLegendSVG(chartMetrics); // but have to append it + // after because should + // overlap the + // plotarea + + String bgclr = this.mychart.getPlotAreaBgColor(); + // setup gradients + svg.append(""); + svg.append(""); + svg.append(""); + svg.append(""); + svg.append(""); + svg.append(""); + + // PLOT AREA BG + RECT + // rectangle around entire chart canvas + if (!(mychart instanceof OOXMLChart) + || !((OOXMLChart) mychart).roundedCorners) + svg.append("\r\n"); + else // OOXML rounded corners + svg.append("\r\n"); + + // actual plot area + svg.append("\r\n"); + + // AXES, IF PRESENT - DO BEFORE ACTUAL SERIES DATA SO DRAWS + // CORRECTLY + ADJUST + // CHART DIMENSIONS + svg.append(mychart.getAxes().getSVG(XAXIS, chartMetrics, mychart + .getChartSeries().getCategories())); + svg.append(mychart.getAxes().getSVG(YAXIS, chartMetrics, mychart + .getChartSeries().getCategories())); + // TODO: Z Axis + + // After Axes and gridlines (if present), + // ACTUAL bar/series/area/etc. svg generated from series and + // scale data + svg.append(this.mychart.getChartObject().getSVG(chartMetrics, mychart + .getAxes().getMetrics(), mychart.getChartSeries())); + + svg.append(legendSVG); // append legend SVG obtained above + + // CHART TITLE + + if (mychart.getTitleTd() != null) + svg.append(mychart.getTitleTd().getSVG(chartMetrics)); + + svg.append(""); + svg.append(""); + /* + * //KSC: TESTING: REMOVE WHEN DONE if + * (WorkBookFactory.PID==WorkBookFactory.E360) { // save svg + * for testing + * purposes try { java.io.File f= new java.io.File( + * "c:/eclipse/workspace/testfiles/io.starter.OpenXLS/output/charts/FromSheetster.svg" + * ); java.io.FileOutputStream fos= new + * java.io.FileOutputStream(f); + * fos.write(svg.toString().getBytes()); fos.flush(); + * fos.close(); } catch + * (Exception e) {} } + */ + return svg.toString(); + } + + private String getLegendSVG(HashMap chartMetrics) { + try { + return mychart.getLegend().getSVG(chartMetrics, mychart + .getChartObject(), mychart.getChartSeries()); + } catch (NullPointerException ne) { // no legend?? + return null; + } + } + + /** + * returns the svg for javascript for highlight and restore + * + * @return + */ + protected String getJavaScript() { + StringBuffer svg = new StringBuffer(); + svg.append(""); + return svg.toString(); + } + + /** + * debugging utility remove when done + */ + public void WriteMainChartRecs(String fName) { + mychart.getChartObject().WriteMainChartRecs(fName); + } + + public Axis getAxis(int type) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/io/starter/OpenXLS/ChartSeriesHandle.java b/src/main/java/io/starter/OpenXLS/ChartSeriesHandle.java index d381e57..10e9d02 100644 --- a/src/main/java/io/starter/OpenXLS/ChartSeriesHandle.java +++ b/src/main/java/io/starter/OpenXLS/ChartSeriesHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,341 +23,368 @@ package io.starter.OpenXLS; //IMPORT OOXML-specific items -import io.starter.formats.OOXML.DLbls; -import io.starter.formats.OOXML.DPt; -import io.starter.formats.OOXML.Marker; -import io.starter.formats.OOXML.SpPr; - -import io.starter.formats.XLS.charts.*; - - -/** Chart Series Handle allows for manipulation of Chart Series within a Chart Handle. -
    - Charts are typically made up of two data elements, series data and category data. - When Microsoft Excel creates a chart, it assigns either the worksheet rows as series data (and the - columns as category data), or the worksheet columns as the series data (and the rows as categories). -
    - Excel tries to minimize the number of series by default, unless both rows and columns are equal, in which case - the default is to have the rows as series. -
    - Typically there will be one series object per row (or column if that is the series) of data. In most cases, the categories - will be the same for every series. -
    - As there are series for every row of data, when performing actions such as adding a column to a chart that has it's series - arranged by row, it will be necessary to modify every series within the chart. ChartSeriesHandle is available to - make this process easier. - - @see ChartHandle -*/ +import io.starter.formats.XLS.charts.Ai; +import io.starter.formats.XLS.charts.Series; + + +/** + * Chart Series Handle allows for manipulation of Chart Series within a Chart Handle. + *
    + * Charts are typically made up of two data elements, series data and category data. + * When Microsoft Excel creates a chart, it assigns either the worksheet rows as series data (and the + * columns as category data), or the worksheet columns as the series data (and the rows as categories). + *
    + * Excel tries to minimize the number of series by default, unless both rows and columns are equal, in which case + * the default is to have the rows as series. + *
    + * Typically there will be one series object per row (or column if that is the series) of data. In most cases, the categories + * will be the same for every series. + *
    + * As there are series for every row of data, when performing actions such as adding a column to a chart that has it's series + * arranged by row, it will be necessary to modify every series within the chart. ChartSeriesHandle is available to + * make this process easier. + * + * @see ChartHandle + */ public class ChartSeriesHandle { private Series myseries; private WorkBookHandle wbh; - + /** * Constructor, used internally. For public use get series from a ChartHandle - * @param Series series + * + * @param Series series * @param WorkBook wbk - WorkBook the Chart is attached to */ public ChartSeriesHandle(Series series, WorkBookHandle wbk) { myseries = series; wbh = wbk; } - + /** * returns the Cell Range String representing the Data in this Series Object. + * * @return String Cell Range representing Series Data e.g. Sheet1!A1:A12 */ public String getSeriesRange() { return myseries.getSeriesValueAi().toString(); } - + /** * returns the Cell Range String representing the Categories (usually the X Axis) for the Chart - *
    Note that Category typically stays constant for all Series in the Chart + *
    Note that Category typically stays constant for all Series in the Chart + * * @return String Category Cell Range e.g. Sheet1!A1:C1 */ public String getCategoryRange() { return myseries.getCategoryValueAi().toString(); } - + /** - * returns the Cell Range String representing Bubble Size for this Series (Bubble Charts only) + * returns the Cell Range String representing Bubble Size for this Series (Bubble Charts only) + * * @return String Bubble Sizes Cell Range e.g. Sheet1!A1:C1 */ public String getBubbleSizes() { - return myseries.getBubbleValueAi().toString(); + return myseries.getBubbleValueAi().toString(); } + /** * returns true if this chart has Bubble Sizes + * * @return */ - public boolean hasBubbleSizes(){ - return myseries.hasBubbleSizes(); + public boolean hasBubbleSizes() { + return myseries.hasBubbleSizes(); } + /** * sets the data for this Series to be obtained from a new Cell Range. *
    - * Note that if you set the size of this series to a different amount of data than other Series + * Note that if you set the size of this series to a different amount of data than other Series * in the chart you could have unexpected results. - * - *@param String seriesRange - a Cell Range representing Series Data e.g. Sheet1!A1:A12 * + * @param String seriesRange - a Cell Range representing Series Data e.g. Sheet1!A1:A12 */ public void setSeriesRange(String seriesRange) { - myseries.getParentChart().setMetricsDirty(); + myseries.getParentChart().setMetricsDirty(); Ai ai = myseries.getSeriesValueAi(); - ai.setParentChart(myseries.getParentChart()); // 20080215 KSC: Added + ai.setParentChart(myseries.getParentChart()); // 20080215 KSC: Added ai.setSheet(myseries.getParentChart().getSheet());// "" - if (ai.getWorkBook()==null)ai.setWorkBook(wbh.getWorkBook()); + if (ai.getWorkBook() == null) ai.setWorkBook(wbh.getWorkBook()); ai.changeAiLocation(ai.toString(), seriesRange); - try { // 20070711 KSC: update value (series) count for series - int coords[]= io.starter.OpenXLS.ExcelTools.getRangeCoords(seriesRange); - myseries.setValueCount(coords[4]); - myseries.getParentChart().setMetricsDirty(); - } catch(Exception e) { - - } + try { // 20070711 KSC: update value (series) count for series + int[] coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(seriesRange); + myseries.setValueCount(coords[4]); + myseries.getParentChart().setMetricsDirty(); + } catch (Exception e) { + + } } - + /** - * sets the Category from a new Cell Range. + * sets the Category from a new Cell Range. *

    - * IMPORTANT! In most cases, the category should be set to the same Cell Range for all Series in the chart. - *
    This method is available for complex charts, but in most cases the ideal way to handle this call is through + * IMPORTANT! In most cases, the category should be set to the same Cell Range for all Series in the chart. + *
    This method is available for complex charts, but in most cases the ideal way to handle this call is through *
    chartHandle.changeCategoryRange. + * * @param String categoryRange - new Category Cell Range */ public void setCategoryRange(String categoryRange) { - myseries.getParentChart().setMetricsDirty(); + myseries.getParentChart().setMetricsDirty(); Ai ai = myseries.getCategoryValueAi(); - if (ai.getWorkBook()==null)ai.setWorkBook(wbh.getWorkBook()); + if (ai.getWorkBook() == null) ai.setWorkBook(wbh.getWorkBook()); ai.changeAiLocation(ai.toString(), categoryRange); - try { // 20070711 KSC: update Category Count for this series - int coords[]= io.starter.OpenXLS.ExcelTools.getRangeCoords(categoryRange); - myseries.setCategoryCount(coords[4]); - myseries.getParentChart().setMetricsDirty(); - } catch(Exception e) { - - } + try { // 20070711 KSC: update Category Count for this series + int[] coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(categoryRange); + myseries.setCategoryCount(coords[4]); + myseries.getParentChart().setMetricsDirty(); + } catch (Exception e) { + + } } - + /** * set the Cell Range for the Bubbles in this Seeries (Bubble Chart Only) + * * @param String bubbleSizes - Cell Range for Bubble Sizes */ public void setBubbleRange(String bubbleSizes) { - if (bubbleSizes!=null && !bubbleSizes.equals("")) { - myseries.getParentChart().setMetricsDirty(); - Ai ai = myseries.getBubbleValueAi(); - if (ai.getWorkBook()==null)ai.setWorkBook(wbh.getWorkBook()); - ai.changeAiLocation(ai.toString(), bubbleSizes); - ai.setRt(2); - try { // also update Bubble Count for this series - int coords[]= io.starter.OpenXLS.ExcelTools.getRangeCoords(bubbleSizes); - myseries.setBubbleCount(coords[4]); - myseries.getParentChart().setMetricsDirty(); - } catch(Exception e) { - - } - } + if (bubbleSizes != null && !bubbleSizes.equals("")) { + myseries.getParentChart().setMetricsDirty(); + Ai ai = myseries.getBubbleValueAi(); + if (ai.getWorkBook() == null) ai.setWorkBook(wbh.getWorkBook()); + ai.changeAiLocation(ai.toString(), bubbleSizes); + ai.setRt(2); + try { // also update Bubble Count for this series + int[] coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(bubbleSizes); + myseries.setBubbleCount(coords[4]); + myseries.getParentChart().setMetricsDirty(); + } catch (Exception e) { + + } + } } - + /** - * Set the Legend text for this Series - *
    note: series legend will then not be linked to a particular cell. if you want to change the legend - * reference, use setSeriesLegendRef - * @param String legend - Legend Text + * Set the Legend text for this Series + *
    note: series legend will then not be linked to a particular cell. if you want to change the legend + * reference, use setSeriesLegendRef + * + * @param String legend - Legend Text * @see setSeriesLegendRef - */ + */ public void setSeriesLegend(String legend) { myseries.setLegend(legend, this.wbh); - myseries.getParentChart().setMetricsDirty(); + myseries.getParentChart().setMetricsDirty(); } - + /** * returns the Legend text for this Series + * * @return String Legend text for this Series * @see getSeriesLegendReference - */ + */ public String getSeriesLegend() { return myseries.getLegendText(); } - - /** - * returns the Legend Cell Reference, if any - * @return String Cell Address representing the Legend for this Series - */ - public String getSeriesLegendReference() { - Ai ai = myseries.getLegendAi(); - if (ai!=null)return ai.getDefinition(); - return null; - } - + /** - * sets Cell Address for the Series Legend + * returns the Legend Cell Reference, if any + * + * @return String Cell Address representing the Legend for this Series + */ + public String getSeriesLegendReference() { + Ai ai = myseries.getLegendAi(); + if (ai != null) return ai.getDefinition(); + return null; + } + + /** + * sets Cell Address for the Series Legend + * * @param String legendCell - Cell Address for Legend */ public void setSeriesLegendRef(String legendCell) { - myseries.setLegendRef(legendCell); - myseries.getParentChart().setMetricsDirty(); + myseries.setLegendRef(legendCell); + myseries.getParentChart().setMetricsDirty(); } - + /** - * gets the Chart Category Data Type + * gets the Chart Category Data Type + * * @return int Data Type of the Category */ public int getCategoryDataType() { - return myseries.getCategoryDataType(); + return myseries.getCategoryDataType(); } - + /** - * gets the Series Data Type + * gets the Series Data Type + * * @return int Data Type of this Series */ public int getSeriesDataType() { - return myseries.getValueDataType(); + return myseries.getValueDataType(); } + /** - * sets the Chart Category Data Type + * sets the Chart Category Data Type + * * @param int i - Category Data Type */ public void setCategoryDataType(int i) { - myseries.setCategoryDataType(i); - myseries.getParentChart().setMetricsDirty(); + myseries.setCategoryDataType(i); + myseries.getParentChart().setMetricsDirty(); } + /** - * sets the Series Data Type + * sets the Series Data Type + * * @param int i - Series Data Type */ public void setSeriesDataType(int i) { - myseries.setValueDataType(i); + myseries.setValueDataType(i); } - + /** * returns a constant that represents the bar shape for this Series *
    This is an internal method that is not useful to the end user. + * * @return int DataPoint shape for this Series (3d Bar, Pyramid ...) */ public int getShape() { - return myseries.getShape(); + return myseries.getShape(); } + /** * sets the constant that represents the bar shape for this Series *
    This is an internal method that is not useful to the end user. + * * @param int shape - DataPoint shape for this Series (3d Bar, Pyramid ...) */ public void setShape(int shape) { - myseries.setShape(shape); - myseries.getParentChart().setMetricsDirty(); + myseries.setShape(shape); + myseries.getParentChart().setMetricsDirty(); } - + /** * defines this Series with Cell References for the Legend, Data Points, Category and Bubble Sizes, * if applicable. + * * @param String legendRef - Cell Address representing the Legend for this Series * @param String series - Cell Range representing the Data Points for this Series - * @param String cat - Cell Range representing the Category for this Series - * (NOTE: The Category Cell Range is typically the same for every Series in the Chart) + * @param String cat - Cell Range representing the Category for this Series + * (NOTE: The Category Cell Range is typically the same for every Series in the Chart) * @param String bubble - Cell Range representing the Bubble Sizes for this Series (Bubble Chart only) or null if none */ public void setSeries(String legendRef, String series, String cat, String bubble) { - this.setSeriesLegendRef(legendRef); - this.setSeriesRange(series); - this.setCategoryRange(cat); - this.setBubbleRange(bubble); - myseries.getParentChart().setMetricsDirty(); + this.setSeriesLegendRef(legendRef); + this.setSeriesRange(series); + this.setCategoryRange(cat); + this.setBubbleRange(bubble); + myseries.getParentChart().setMetricsDirty(); } - + /** * sets the color for this series + * * @param int seriesNumber - series index * @param int clr - color constant - * @deprecated use setSeriesColor(int clr) instead * @see setSeriesColor(int clr) + * @deprecated use setSeriesColor(int clr) instead */ public void setSeriesColor(int seriesNumber, int clr) { - myseries.setColor(clr); - myseries.getParentChart().setMetricsDirty(); + myseries.setColor(clr); + myseries.getParentChart().setMetricsDirty(); } - + /** * sets the color for this Series (bar or line) *
    NOTE: for Pie Charts, use setPieChartSliceColor + * * @param int clr - color constant * @see setPieChartSliceColor * @see FormatHandle.COLOR_* constants */ public void setSeriesColor(int clr) { - myseries.setColor(clr); - myseries.getParentChart().setMetricsDirty(); + myseries.setColor(clr); + myseries.getParentChart().setMetricsDirty(); } - + /** - * sets the color for this Series (bar or line) + * sets the color for this Series (bar or line) *
    NOTE: for Pie Charts, use setPieChartSliceColor + * * @param String color hex string * @see setPieChartSliceColor * @see FormatHandle.COLOR_* constants */ public void setSeriesColor(String clr) { - myseries.setColor(clr); - myseries.getParentChart().setMetricsDirty(); + myseries.setColor(clr); + myseries.getParentChart().setMetricsDirty(); } - + /** * sets the color for a particular Pie Chart slice or wedge + * * @param int clr - color constant * @param int slice - 0-based slice or point number * @see FormatHandle.COLOR_* constants */ public void setPieChartSliceColor(int clr, int slice) { - myseries.setPieSliceColor(clr, slice); - myseries.getParentChart().setMetricsDirty(); + myseries.setPieSliceColor(clr, slice); + myseries.getParentChart().setMetricsDirty(); } - + /** - * returns the Series (bar or line) color + * returns the Series (bar or line) color *
    NOTE: for Pie charts, use getPieChartSliceColor + * * @return int color constant representing the Series color * @see FormatHandle.COLOR_* constants - * @deprecated * @see getSeriesColorStr + * @deprecated */ public int getSeriesColor() { - return FormatHandle.HexStringToColorInt(myseries.getSeriesColor(), FormatHandle.colorBACKGROUND) ; + return FormatHandle.HexStringToColorInt(myseries.getSeriesColor(), FormatHandle.colorBACKGROUND); } - + /** - * returns the Series (bar or line) color hex string + * returns the Series (bar or line) color hex string *
    NOTE: for Pie charts, use getPieChartSliceColor + * * @return int color constant representing the Series color * @see FormatHandle.COLOR_* constants * @see getPieChartSliceColor */ public String getSeriesColorStr() { - return myseries.getSeriesColor(); + return myseries.getSeriesColor(); } - + /** * returns the desired Pie chart slice color + * * @param int slice - 0-based slice or wedge index * @return int color constant repressenting the color for the desired pie slice * @see FormatHandle.COLOR_* constants * @deprecated See getPieChartSliceColorStr */ public int getPieChartSliceColor(int slice) { - return FormatHandle.HexStringToColorInt(myseries.getPieSliceColor(slice), FormatHandle.colorBACKGROUND) ; + return FormatHandle.HexStringToColorInt(myseries.getPieSliceColor(slice), FormatHandle.colorBACKGROUND); } /** * returns the desired Pie chart slice color hex string + * * @param int slice - 0-based slice or wedge index * @return String color hex string representing the color for the desired pie slice */ public String getPieChartSliceColorStr(int slice) { - return myseries.getPieSliceColor(slice); + return myseries.getPieSliceColor(slice); } /* diff --git a/src/main/java/io/starter/OpenXLS/ColHandle.java b/src/main/java/io/starter/OpenXLS/ColHandle.java index 571de60..78c1398 100644 --- a/src/main/java/io/starter/OpenXLS/ColHandle.java +++ b/src/main/java/io/starter/OpenXLS/ColHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,350 +25,380 @@ import io.starter.formats.XLS.*; import io.starter.toolkit.StringTool; -import java.util.*; +import java.util.ArrayList; +import java.util.List; -/** The ColHandle provides access to an Worksheet Column and its Cells. -
    - Use the ColHandle to work with individual Columns in an XLS file. -
    - With a ColHandle you can: -

    - get a handle to the Cells in a column
    - set the default formatting for a column
    -
    - - Note: for a discussion of Column widths see: - http://support.microsoft.com/?kbid=214123 - - @see WorkBookHandle - @see WorkSheetHandle - @see FormulaHandle -*/ +/** + * The ColHandle provides access to an Worksheet Column and its Cells. + *
    + * Use the ColHandle to work with individual Columns in an XLS file. + *
    + * With a ColHandle you can: + *
    + * get a handle to the Cells in a column
    + * set the default formatting for a column
    + *
    + *

    + * Note: for a discussion of Column widths see: + * http://support.microsoft.com/?kbid=214123 + * + * @see WorkBookHandle + * @see WorkSheetHandle + * @see FormulaHandle + */ public class ColHandle { - // TODO: read 1st font in file to set DEFAULT_ZERO_CHAR_WIDTH ... eventually ... - public static final double DEFAULT_ZERO_CHAR_WIDTH= 7.0; // width of '0' char in default font + conversion 1.3 - public static final int COL_UNITS_TO_PIXELS =(int) (256/DEFAULT_ZERO_CHAR_WIDTH); // = 36.57 + // TODO: read 1st font in file to set DEFAULT_ZERO_CHAR_WIDTH ... eventually ... + public static final double DEFAULT_ZERO_CHAR_WIDTH = 7.0; // width of '0' char in default font + conversion 1.3 + public static final int COL_UNITS_TO_PIXELS = (int) (256 / DEFAULT_ZERO_CHAR_WIDTH); // = 36.57 public static final int DEFAULT_COLWIDTH = Colinfo.DEFAULT_COLWIDTH; - + private Colinfo myCol; private FormatHandle formatter; private WorkBook wbh; private WorkSheetHandle mySheet; - + /** * creates a new ColHandle from a Colinfo Object and reference to a worksheet (WorkSheetHandle Object) + * * @param c * @param sheet */ - protected ColHandle(Colinfo c, WorkSheetHandle sheet){ + protected ColHandle(Colinfo c, WorkSheetHandle sheet) { myCol = c; wbh = sheet.getWorkBook(); mySheet = sheet; - } - + } + private int lastsz = 0; // the last checked col width - /** resizes this column to fit the width of all displayed, non-wrapped text. + /** + * resizes this column to fit the width of all displayed, non-wrapped text. *
    NOTE: as the Excel autofit implementation is undocumented, this is an approximation - * */ public void autoFit() { - // KSC: make more betta :) - double w= 0; - CellHandle[] cxt = this.getCells(); - for(int t=0;tlastsz) this.setWidth(csz*factor); - */ - } - if (w==0) - return; // keep original width ... that's what Excel does for blank columns ... - // convert pixels to excel column units basically OpenXLS.COLUNITSTOPIXELS in double form - this.setWidth((int)Math.floor((w/DEFAULT_ZERO_CHAR_WIDTH)*256.0)); + */ + } + if (w == 0) + return; // keep original width ... that's what Excel does for blank columns ... + // convert pixels to excel column units basically OpenXLS.COLUNITSTOPIXELS in double form + this.setWidth((int) Math.floor((w / DEFAULT_ZERO_CHAR_WIDTH) * 256.0)); } - - - /** sets the width of this Column in Characters or Excel units. + + + /** + * sets the width of this Column in Characters or Excel units. *
    - The default Excel column width is set to 8.43 Characters, - based on the default font and font size, -
    - NOTE: The last Cell in the column having its width - set will be the resulting width of the column - @param int i - desired Column width in Characters (Excel units) - */ - public void setWidthInChars(int newWidth){ - /* if an image falls upon this column, + * The default Excel column width is set to 8.43 Characters, + * based on the default font and font size, + *
    + * NOTE: The last Cell in the column having its width + * set will be the resulting width of the column + * + * @param int i - desired Column width in Characters (Excel units) + */ + public void setWidthInChars(int newWidth) { + /* if an image falls upon this column, * adjust image width so that it does not change */ - ArrayList iAdjust= new ArrayList(); - ImageHandle[] images= myCol.getSheet().getImages(); - if(images!=null) { - // for each image that falls over this column, trap index + original width -- to be reset after setting col width - for (int z= 0; z < images.length; z++) { - ImageHandle ih= images[z]; - int c0= ih.getCol(); - int c1= ih.getCol1(); - int col= myCol.getColFirst(); // should only be one, right? - if (col>=c0 && col<=c1) { - int w= ih.getWidth(); - iAdjust.add(new int[]{z,w}); - } - } - } - - myCol.setColWidthInChars(newWidth); - for (int z= 0; z < iAdjust.size(); z++) { - ImageHandle ih= images[((int[]) iAdjust.get(z))[0]]; - ih.setWidth(((int[]) iAdjust.get(z))[1]); - } + ArrayList iAdjust = new ArrayList(); + ImageHandle[] images = myCol.getSheet().getImages(); + if (images != null) { + // for each image that falls over this column, trap index + original width -- to be reset after setting col width + for (int z = 0; z < images.length; z++) { + ImageHandle ih = images[z]; + int c0 = ih.getCol(); + int c1 = ih.getCol1(); + int col = myCol.getColFirst(); // should only be one, right? + if (col >= c0 && col <= c1) { + int w = ih.getWidth(); + iAdjust.add(new int[]{z, w}); + } + } + } + + myCol.setColWidthInChars(newWidth); + for (int z = 0; z < iAdjust.size(); z++) { + ImageHandle ih = images[iAdjust.get(z)[0]]; + ih.setWidth(iAdjust.get(z)[1]); + } } - - /** sets the width of this Column in internal units, described as follows: + + /** + * sets the width of this Column in internal units, described as follows: *
    - * default width of the columns in 1/256 of the width of the zero character, - * using default font. -
    The Default Excel Column, whose width in Characters or Excel Units, is 8.43, has a width in these units of 2300. -

    NOTE: - The last Cell in the column having its width - set will be the resulting width of the column - @param int i - desired Column width in internal units - */ - public void setWidth(int newWidth){ - /* if an image falls upon this column, + * default width of the columns in 1/256 of the width of the zero character, + * using default font. + *
    The Default Excel Column, whose width in Characters or Excel Units, is 8.43, has a width in these units of 2300. + *

    NOTE: + * The last Cell in the column having its width + * set will be the resulting width of the column + * + * @param int i - desired Column width in internal units + */ + public void setWidth(int newWidth) { + /* if an image falls upon this column, * adjust image width so that it does not change */ - ArrayList iAdjust= new ArrayList(); - ImageHandle[] images= myCol.getSheet().getImages(); - if(images!=null) { - // for each image that falls over this column, trap index + original width -- to be reset after setting col width - for (int z= 0; z < images.length; z++) { - ImageHandle ih= images[z]; - int c0= ih.getCol(); - int c1= ih.getCol1(); - int col= myCol.getColFirst(); // should only be one, right? - if (col>=c0 && col<=c1) { - int w= ih.getWidth(); - iAdjust.add(new int[]{z,w}); - } - } - } - lastsz = newWidth; - myCol.setColWidth(newWidth); - // now adjust any of the images that we noted above - for (int z= 0; z < iAdjust.size(); z++) { - ImageHandle ih= images[((int[]) iAdjust.get(z))[0]]; - ih.setWidth(((int[]) iAdjust.get(z))[1]); - } - } - - /** returns the width of this Column in internal units - defined as follows: + ArrayList iAdjust = new ArrayList(); + ImageHandle[] images = myCol.getSheet().getImages(); + if (images != null) { + // for each image that falls over this column, trap index + original width -- to be reset after setting col width + for (int z = 0; z < images.length; z++) { + ImageHandle ih = images[z]; + int c0 = ih.getCol(); + int c1 = ih.getCol1(); + int col = myCol.getColFirst(); // should only be one, right? + if (col >= c0 && col <= c1) { + int w = ih.getWidth(); + iAdjust.add(new int[]{z, w}); + } + } + } + lastsz = newWidth; + myCol.setColWidth(newWidth); + // now adjust any of the images that we noted above + for (int z = 0; z < iAdjust.size(); z++) { + ImageHandle ih = images[iAdjust.get(z)[0]]; + ih.setWidth(iAdjust.get(z)[1]); + } + } + + /** + * returns the width of this Column in internal units + * defined as follows: *
    - * default width of the columns in 1/256 of the width of the zero character, - * using default font. -
    The Default Excel Column, whose width in Excel Units or Characters is 8.43, has a width in these units of 2300. - @return int Column width in internal units - */ - public int getWidth(){ + * default width of the columns in 1/256 of the width of the zero character, + * using default font. + *
    The Default Excel Column, whose width in Excel Units or Characters is 8.43, has a width in these units of 2300. + * + * @return int Column width in internal units + */ + public int getWidth() { return myCol.getColWidth(); } /** * returns the width of this Column in Characters or regular Excel units - *
    NOTE: this value is a calculated value that should be close but still is an approximation of Excel units + *
    NOTE: this value is a calculated value that should be close but still is an approximation of Excel units + * * @return int Column width in Excel units */ public int getWidthInChars() { - return myCol.getColWidthInChars(); + return myCol.getColWidthInChars(); } + /** * static utility method to return the Column width of an existing column - in the units as follows: + * in the units as follows: *
    - * default width of the columns in 1/256 of the width of the zero character, - * using default font. - *
    For Arial 10 point, the default width of the zero character = 7 -
    The Default Excel Column, whose width in Characters or Excel Units is 8.43, has a width in these units of 2300. + * default width of the columns in 1/256 of the width of the zero character, + * using default font. + *
    For Arial 10 point, the default width of the zero character = 7 + *
    The Default Excel Column, whose width in Characters or Excel Units is 8.43, has a width in these units of 2300. + * * @param Boundsheet sheet - source Worksheet - * @param int col - 0-based Column number + * @param int col - 0-based Column number * @return int - Column width in internal units */ public static int getWidth(Boundsheet sheet, int col) { - int w= Colinfo.DEFAULT_COLWIDTH; - try { - Colinfo c = sheet.getColInfo(col); - if(c!=null) - w= (int) c.getColWidth(); - } catch (Exception e) { // exception if no col defined - } - return w; - } - - - /** sets the format id (an index to a Format record) for this Column + int w = Colinfo.DEFAULT_COLWIDTH; + try { + Colinfo c = sheet.getColInfo(col); + if (c != null) + w = c.getColWidth(); + } catch (Exception e) { // exception if no col defined + } + return w; + } + + + /** + * sets the format id (an index to a Format record) for this Column *
    This sets the default formatting for the Column * such that any cell that does not specifically set it's own formatting - * will display this Column formatting - @param int i - ID representing the Format to set this Column - @see FormatHandle - */ - public void setFormatId(int i){ + * will display this Column formatting + * + * @param int i - ID representing the Format to set this Column + * @see FormatHandle + */ + public void setFormatId(int i) { myCol.setIxfe(i); } - + /** * returns the format ID (the index to the format record) for this Column *
    The Column format is the default formatting for each cell contained * within the column - * @return int formatId - the index of the format record for this Column - @see FormatHandle + * + * @return int formatId - the index of the format record for this Column + * @see FormatHandle */ public int getFormatId() { - return myCol.getIxfe(); + return myCol.getIxfe(); } - - /** returns the FormatHandle (a Format Object describing visual properties) for this Column + + /** + * returns the FormatHandle (a Format Object describing visual properties) for this Column *
    NOTE: The Column format record describes the default formatting for each cell contained * within the column - @return FormatHandle - a Format object to apply to this Col -*/ - public FormatHandle getFormatHandle(){ - if(this.formatter==null)this.setFormatHandle(); + * + * @return FormatHandle - a Format object to apply to this Col + */ + public FormatHandle getFormatHandle() { + if (this.formatter == null) this.setFormatHandle(); return this.formatter; } - + /** * sets the FormatHandle (a Format Object describing visual properties) for this Column *
    NOTE: The Column format record describes the default formatting for each cell contained * within the column */ private void setFormatHandle() { - if(formatter!=null)return; - formatter = new FormatHandle(wbh, this.getFormatId()); - formatter.setColHandle(this); + if (formatter != null) return; + formatter = new FormatHandle(wbh, this.getFormatId()); + formatter.setColHandle(this); } - - /** returns the first Column referenced by this column handle + + /** + * returns the first Column referenced by this column handle *
    NOTE: A Column handle may in some circumstances refer to a range of columns - * + * * @return int first column number referenced by this Column handle */ public int getColFirst() { - return myCol.getColFirst(); + return myCol.getColFirst(); } - /** returns the last Column referenced by this column handle + /** + * returns the last Column referenced by this column handle *
    NOTE: A Column handle may in some circumstances refer to a range of columns - * + * * @return int last column number referenced by this Column handle */ public int getColLast() { - return myCol.getColLast(); + return myCol.getColLast(); } - - - /** returns the array of Cells in this Column + + + /** + * returns the array of Cells in this Column + * * @return CellHandle array - */ - public CellHandle[] getCells(){ - List mycells; - try { - mycells = this.mySheet.getBoundsheet().getCellsByCol(this.getColFirst()); - } catch (CellNotFoundException e) { - return new CellHandle[0]; - } - CellHandle[] ch = new CellHandle[mycells.size()]; - for(int t = 0;t mycells; + try { + mycells = this.mySheet.getBoundsheet().getCellsByCol(this.getColFirst()); + } catch (CellNotFoundException e) { + return new CellHandle[0]; + } + CellHandle[] ch = new CellHandle[mycells.size()]; + for (int t = 0; t < ch.length; t++) { + ch[t] = new CellHandle((BiffRec) mycells.get(t), null); + ch[t].setWorkSheetHandle(null); + } return ch; } /** - * determines if this Column passes through i.e. contains a + * determines if this Column passes through i.e. contains a * horizontal merge range - * + * * @return true if this Column is part of any merge (horizontally merged cells) */ public boolean containsMergeRange() { RowHandle[] r = mySheet.getRows(); - for (int i=0;i

    +/**
    + * 
      * CommentHandle allows for manipulation of the Note or Comment feature of Excel
    - * 
    + *
      * In order to create CommentHandles programatically use the methods in WorkSheetHandle or CellHandle
    - * 
    - * 
    * + *
    */ public class CommentHandle implements Handle { - - private Note note; - - /** - * Creates a new CommentHandle object - *
    For internal use only - * @param n - */ - protected CommentHandle(Note n) { - this.note= n; - } - - /** - * Returns the text of the Note (Comment) - * @return String value or text of Note - */ - public String getCommentText() { - if (note!=null) - return note.getText(); - return null; - } - - /** - * Sets the text of the Note (Comment). - *

    The text may contain embedded formatting information as follows: - *
    < font specifics>text segment< font specifics for next segment>text segment... - *
    where font specifics can be one or more of (all are optional): - *

      b - bold - *
      i - italic - *
      s - strikethru - *
      u - underlined - *
      f="" - font name surrounded by quotes e.g. "Tahoma" - *
      sz="" - font size in points surrounded by quotes e.g. "10" - *
      Each option must be delimited by ;'s - *
      For Example: - *
        "<f=\"Tahoma\";b;sz=\"16\">Note: <f=\"Cambria\";sz=\"12\">This is an important point"
      - * To reset to the default font, input an empty format: <> e.g.: - *
        "<b;i;sz=\"8\">Note:<>This is an important comment"
      - * @param text - String text of Note - */ - public void setCommentText(String text) { - if (note!=null) { - try { - note.setText(text); - } catch (IllegalArgumentException e) { - Logger.logErr(e.toString()); - } - } - } - - /** returns the author of this Note (Comment) if set - * @return String author - */ - public String getAuthor() { - if (note!=null) - return note.getAuthor(); - return null; - } - /** - * sets the author of this Note (Comment) - * @param author - */ - public void setAuthor(String author) { - if (note!=null) note.setAuthor(author); - } - - /** - * Removes or deletes this Note (Comment) from the worksheet - */ - public void remove() { - note.getSheet().removeNote(note); - note= null; - } - - /** - * Sets this Note (Comment) to always show, even when the attached cell loses focus - */ - public void show() { - if (note!=null) note.setHidden(false); - } - /** - * Sets this Note (Comment) to be hidden until the attached cell has focus - * - * This is the default state of note records - */ - public void hide() { - if (note!=null) note.setHidden(true); - } - - /** - * Returns true if this Note (Comment) is hidden until focus - * @return - */ - public boolean getIsHidden() { - if (note!=null) - return note.getHidden(); - return false; - } - - /** - * Sets this Note (Comment) to be attached to a cell at [row, col] - * @param row int row number (0-based) - * @param col int column number (0-based) - */ - public void setRowCol(int row, int col) { - if (note!=null) - note.setRowCol(row, col); - } - - /** + private Note note; + + /** + * Creates a new CommentHandle object + *
      For internal use only + * + * @param n + */ + protected CommentHandle(Note n) { + this.note = n; + } + + /** + * Returns the text of the Note (Comment) + * + * @return String value or text of Note + */ + public String getCommentText() { + if (note != null) + return note.getText(); + return null; + } + + /** + * Sets the text of the Note (Comment). + *

      The text may contain embedded formatting information as follows: + *
      < font specifics>text segment< font specifics for next segment>text segment... + *
      where font specifics can be one or more of (all are optional): + *

        b - bold + *
        i - italic + *
        s - strikethru + *
        u - underlined + *
        f="" - font name surrounded by quotes e.g. "Tahoma" + *
        sz="" - font size in points surrounded by quotes e.g. "10" + *
        Each option must be delimited by ;'s + *
        For Example: + *
          "<f=\"Tahoma\";b;sz=\"16\">Note: <f=\"Cambria\";sz=\"12\">This is an important point"
        + * To reset to the default font, input an empty format: <> e.g.: + *
          "<b;i;sz=\"8\">Note:<>This is an important comment"
        + * + * @param text - String text of Note + */ + public void setCommentText(String text) { + if (note != null) { + try { + note.setText(text); + } catch (IllegalArgumentException e) { + Logger.logErr(e.toString()); + } + } + } + + /** + * returns the author of this Note (Comment) if set + * + * @return String author + */ + public String getAuthor() { + if (note != null) + return note.getAuthor(); + return null; + } + + /** + * sets the author of this Note (Comment) + * + * @param author + */ + public void setAuthor(String author) { + if (note != null) note.setAuthor(author); + } + + /** + * Removes or deletes this Note (Comment) from the worksheet + */ + public void remove() { + note.getSheet().removeNote(note); + note = null; + } + + /** + * Sets this Note (Comment) to always show, even when the attached cell loses focus + */ + public void show() { + if (note != null) note.setHidden(false); + } + + /** + * Sets this Note (Comment) to be hidden until the attached cell has focus + *

        + * This is the default state of note records + */ + public void hide() { + if (note != null) note.setHidden(true); + } + + /** + * Returns true if this Note (Comment) is hidden until focus + * + * @return + */ + public boolean getIsHidden() { + if (note != null) + return note.getHidden(); + return false; + } + + /** + * Sets this Note (Comment) to be attached to a cell at [row, col] + * + * @param row int row number (0-based) + * @param col int column number (0-based) + */ + public void setRowCol(int row, int col) { + if (note != null) + note.setRowCol(row, col); + } + + /** * Returns the address this Note (Comment) is attached to - * @return String Cell Address - */ - public String getAddress() { - if (note!=null) - return note.getCellAddressWithSheet(); - return null; - } - - /** - * return the String representation of this CommentHandle - */ - public String toString() { - if (note!=null) - return note.toString(); - return "Not initialized"; - } + * + * @return String Cell Address + */ + public String getAddress() { + if (note != null) + return note.getCellAddressWithSheet(); + return null; + } - /** - * return the Row number (0-based) this Note is attached to - * @return 0-based row number - */ + /** + * return the String representation of this CommentHandle + */ + public String toString() { + if (note != null) + return note.toString(); + return "Not initialized"; + } + + /** + * return the Row number (0-based) this Note is attached to + * + * @return 0-based row number + */ public int getRowNum() { - if (note!=null) - return note.getRowNumber(); - return -1; + if (note != null) + return note.getRowNumber(); + return -1; } - + /** * return the Column this note is attached to + * * @return Column number as an integer e.g. A=0, B=1 ... */ public int getColNum() { - if (note!=null) - return note.getColNumber(); - return -1; + if (note != null) + return note.getColNumber(); + return -1; } - - /** - * Sets the width and height of the bounding text box of the note - *
        Units are in pixels - *
        NOTE: the height algorithm w.r.t. varying row heights is not 100% - * @param width short desired text box width in pixels - * @param height short desired text box height in pixels - * - */ - public void setTextBoxSize(int width, int height) { - if (note!=null) { - note.setTextBoxWidth((short) width); - note.setTextBoxHeight((short) height); - } - } - + + /** + * Sets the width and height of the bounding text box of the note + *
        Units are in pixels + *
        NOTE: the height algorithm w.r.t. varying row heights is not 100% + * + * @param width short desired text box width in pixels + * @param height short desired text box height in pixels + */ + public void setTextBoxSize(int width, int height) { + if (note != null) { + note.setTextBoxWidth((short) width); + note.setTextBoxHeight((short) height); + } + } + /** * returns the bounds (size and position) of the Text Box for this Note - *
        bounds are relative and based upon rows, columns and offsets within - *
        bounds are as follows: -
        bounds[0]= column # of top left position (0-based) of the shape -
        bounds[1]= x offset within the top-left column (0-1023) -
        bounds[2]= row # for top left corner -
        bounds[3]= y offset within the top-left corner (0-1023) -
        bounds[4]= column # of the bottom right corner of the shape -
        bounds[5]= x offset within the cell for the bottom-right corner (0-1023) -
        bounds[6]= row # for bottom-right corner of the shape -
        bounds[7]= y offset within the cell for the bottom-right corner (0-1023) + *
        bounds are relative and based upon rows, columns and offsets within + *
        bounds are as follows: + *
        bounds[0]= column # of top left position (0-based) of the shape + *
        bounds[1]= x offset within the top-left column (0-1023) + *
        bounds[2]= row # for top left corner + *
        bounds[3]= y offset within the top-left corner (0-1023) + *
        bounds[4]= column # of the bottom right corner of the shape + *
        bounds[5]= x offset within the cell for the bottom-right corner (0-1023) + *
        bounds[6]= row # for bottom-right corner of the shape + *
        bounds[7]= y offset within the cell for the bottom-right corner (0-1023) + * * @return */ public short[] getTextBoxBounds() { - if (note!=null) - return note.getTextBoxBounds(); - return null; + if (note != null) + return note.getTextBoxBounds(); + return null; } - - /** + + /** * Sets the bounds (size and position) of the Text Box for this Note - *
        bounds are relative and based upon rows, columns and offsets within - *
        bounds are as follows: -
        bounds[0]= column # of top left position (0-based) of the shape -
        bounds[1]= x offset within the top-left column (0-1023) -
        bounds[2]= row # for top left corner -
        bounds[3]= y offset within the top-left corner (0-1023) -
        bounds[4]= column # of the bottom right corner of the shape -
        bounds[5]= x offset within the cell for the bottom-right corner (0-1023) -
        bounds[6]= row # for bottom-right corner of the shape -
        bounds[7]= y offset within the cell for the bottom-right corner (0-1023) - */ - public void setTextBoxBounds(short[] bounds) { - if (note!=null) - note.setTextBoxBounds(bounds); - } - - - + *
        bounds are relative and based upon rows, columns and offsets within + *
        bounds are as follows: + *
        bounds[0]= column # of top left position (0-based) of the shape + *
        bounds[1]= x offset within the top-left column (0-1023) + *
        bounds[2]= row # for top left corner + *
        bounds[3]= y offset within the top-left corner (0-1023) + *
        bounds[4]= column # of the bottom right corner of the shape + *
        bounds[5]= x offset within the cell for the bottom-right corner (0-1023) + *
        bounds[6]= row # for bottom-right corner of the shape + *
        bounds[7]= y offset within the cell for the bottom-right corner (0-1023) + */ + public void setTextBoxBounds(short[] bounds) { + if (note != null) + note.setTextBoxBounds(bounds); + } + + /** * Returns the internal note record for this CommentHandle. - * + *

        * Be aware that this note record should not be modified directly, and that this * method is only for internal application use. - * + * * @return Name record */ public Note getInternalNoteRec() { return note; } - /** - * Returns the OOXML representation of this Note object - * @param authId 0-based author index for the author linked to this Note - * @return String OOMXL representation - */ - public String getOOXML(int authId) { - StringBuffer ooxml= new StringBuffer(); + + /** + * Returns the OOXML representation of this Note object + * + * @param authId 0-based author index for the author linked to this Note + * @return String OOMXL representation + */ + public String getOOXML(int authId) { + StringBuffer ooxml = new StringBuffer(); // TODO: Handle FORMATS - ooxml.append(""); + ooxml.append(""); ooxml.append(note.getOOXML()); ooxml.append(""); return ooxml.toString(); - } - + } + } diff --git a/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.java b/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.java index 3de56ba..c6f8888 100644 --- a/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.java +++ b/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.java @@ -2,182 +2,181 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.OpenXLS; -import java.util.ArrayList; import io.starter.formats.XLS.Cf; import io.starter.formats.XLS.Condfmt; import io.starter.toolkit.Logger; -/**

        +
        +import java.util.ArrayList;
        +
        +/**
        + * 
          * ConditionalFormatHandle allows for manipulation of the ConditionalFormat cells in Excel
        - * 
        - * Using the ConditionalFormatHandle, the affected range of ConditionalFormats can be modified, 
        + *
        + * Using the ConditionalFormatHandle, the affected range of ConditionalFormats can be modified,
          * along with the formatting applied to the cells when the condition is true.
        - * 
        + *
          * Each ConditionalFormatHandle represents a range of cells and can have a number
          * of formatting rules and formats (ConditionalFormatRule) applied.
        - * 
        + *
          * The ConditionalFormatHandle affected range can either be a contiguous range, or a series of cells and ranges.
        - * 
        + *
          * Each ConditionalFormatRule contains one rule and corresponding format data.
        - * 
        - * 
        + *
        + *
          * Many of these calls are very self-explanatory and can be found in the api.
          * 
        - * */ public class ConditionalFormatHandle implements Handle { - + private Condfmt cndfmt = null; private WorkSheetHandle worksheet; - - - /** - * evaluates the criteria for this Conditional Format - *
        if the criteria involves a comparison i.e. equals, less than, etc., it uses the - * value from the passed in referenced cell to compare with - * + + + /** + * evaluates the criteria for this Conditional Format + *
        if the criteria involves a comparison i.e. equals, less than, etc., it uses the + * value from the passed in referenced cell to compare with + *

        * If there are multiple rules in the ConditionalFormat then the first rule that passes will be returned. - * + *

        * If no valid rules pass then a null result is given - * - * @return the ConditionalFormatRule that passes. + * * @param CellHandle refcell - the cell to obtain a value from in order for evaluation to occur - * @see io.starter.formats.XLS.Cf#evaluate(io.starter.formats.XLS.formulas.Ptg) - */ - public ConditionalFormatRule evaluate(CellHandle refcell) { - ConditionalFormatRule[] rules = this.getRules(); - for (int i=0;i cfs = this.cndfmt.getRules(); - ConditionalFormatRule[] rules = new ConditionalFormatRule[cfs.size()]; - for(int i=0;i cfs = this.cndfmt.getRules(); + ConditionalFormatRule[] rules = new ConditionalFormatRule[cfs.size()]; + for (int i = 0; i < cfs.size(); i++) { + ConditionalFormatRule cfr = new ConditionalFormatRule((Cf) cfs.get(i)); + rules[i] = cfr; + } + return rules; + } + - /** * For internal use only. Creates a ConditionalFormat Handle based of the Condfmt passed in. - * - * @param workBookHandle + * + * @param workBookHandle * @param Condfmt */ protected ConditionalFormatHandle(Condfmt c, WorkSheetHandle workSheetHandle) { - this.cndfmt = c; - worksheet = workSheetHandle; + this.cndfmt = c; + worksheet = workSheetHandle; } - - /** get the WorkSheetHandle for this ConditionalFormat - * - * ConditionalFormats are bound to a specific worksheet and cannot be + + /** + * get the WorkSheetHandle for this ConditionalFormat + *

        + * ConditionalFormats are bound to a specific worksheet and cannot be * applied to multiple worksheets - + * * @return the WorkSheetHandle for this ConditionalFormat */ public WorkSheetHandle getWorkSheetHandle() { return worksheet; } - - - - + + /** * Return the range of data this ConditionalFormatHandle refers to as a string * This location is the largest bounding rectangle that all cells utilized in this conditional * format can be contained in. - * + * * @return Encompassing range in the format "A2:B12" */ public String getEncompassingRange() { int[] rowcols = cndfmt.getEncompassingRange(); return ExcelTools.formatRangeRowCol(rowcols); } - - + + /** * Return a string representing all ranges that this conditional format handle can affect + * * @return range in the format "A2:B3"; - * */ public String[] getAllAffectedRanges() { return cndfmt.getAllRanges(); } - + /** * Determine if the conditional format contains/affects the cell handle passed in - * + * * @param cellHandle * @return */ public boolean contains(CellHandle cellHandle) { return this.cndfmt.contains(cellHandle.getIntLocation()); } - + /** - * Set the range this ConditionalFormatHandle refers to. - * Pass in a range string, sans worksheet. - * - * This range will overwrite all other ranges this ConditionalFormatHandle refers to. - * - * In order to handle multiple ranges, use the addRange(String range) method - * + * Set the range this ConditionalFormatHandle refers to. + * Pass in a range string, sans worksheet. + *

        + * This range will overwrite all other ranges this ConditionalFormatHandle refers to. + *

        + * In order to handle multiple ranges, use the addRange(String range) method + * * @param range = standard excel range without worksheet information ("A1" or "A1:A10") */ public void setRange(String range) { this.cndfmt.resetRange(range); } - + /** * Determines if the ConditionalFormatHandle contains the cell address passed in - * + * * @param cellAddress a cell address in the format "A1" * @return if the ce */ public boolean contains(String celladdy) { - return this.cndfmt.contains(ExcelTools.getRowColFromString(celladdy)); + return this.cndfmt.contains(ExcelTools.getRowColFromString(celladdy)); } - - + /** * Return an xml representation of the ConditionalFormatHandle - - * + * * @return */ // TODO: more than one cf rule??? @@ -185,84 +184,87 @@ public String getXML() { ConditionalFormatRule[] rules = this.getRules(); StringBuffer xml = new StringBuffer(); xml.append("" ); - if(rules[0].getFirstCondition()!=null) { - xml.append("" ); - xml.append(rules[0].getFirstCondition()); - xml.append("" ); - } - if(rules[0].getSecondCondition()!=null) { - xml.append("" ); - xml.append(rules[0].getSecondCondition()); - xml.append("" ); - } + xml.append(" type=\"" + ConditionalFormatRule.VALUE_TYPE[rules[0].getConditionalFormatType()] + "\""); + xml.append(" operator=\"" + ConditionalFormatRule.OPERATORS[rules[0].getTypeOperator()] + "\""); + try { + xml.append(" sqref=\"" + this.getEncompassingRange() + "\""); + } catch (Exception e) { + Logger.logErr("Problem getting range for ConditionalFormatHandle.getXML().", e); + } + xml.append(">"); + if (rules[0].getFirstCondition() != null) { + xml.append(""); + xml.append(rules[0].getFirstCondition()); + xml.append(""); + } + if (rules[0].getSecondCondition() != null) { + xml.append(""); + xml.append(rules[0].getSecondCondition()); + xml.append(""); + } xml.append(""); return xml.toString(); } + /** * return a string representation of this Conditional Format - * + *

        * This method is still incomplete as it only returns data for one rule, and only refers to one range */ // TODO: more than one cf rule??? - public String toString(){ + public String toString() { ConditionalFormatRule[] rules = this.getRules(); - String ret= this.getEncompassingRange() + ": " + rules[0].getType() + " " + rules[0].getOperator(); // range, type + operator - if (rules[0].getFormula1()!=null) // formulas - ret+= " " + rules[0].getFormula1().getFormulaString().substring(1); - if (rules[0].getFormula2()!=null) - ret+= " and " + rules[0].getFormula2().getFormulaString().substring(1); - // todo: add formats to this - return ret; + String ret = this.getEncompassingRange() + ": " + rules[0].getType() + " " + rules[0].getOperator(); // range, type + operator + if (rules[0].getFormula1() != null) // formulas + ret += " " + rules[0].getFormula1().getFormulaString().substring(1); + if (rules[0].getFormula2() != null) + ret += " and " + rules[0].getFormula2().getFormulaString().substring(1); + // todo: add formats to this + return ret; } - /** + /** * @return Returns the cndfmt. */ protected Condfmt getCndfmt() { return cndfmt; } - /** + /** * @param cndfmt The cndfmt to set. */ protected void setCndfmt(Condfmt cndfmt) { this.cndfmt = cndfmt; } - + /** * Add a cell to this conditional format record + * * @param cellHandle */ public void addCell(CellHandle cellHandle) { - if (this.contains(cellHandle))return; + if (this.contains(cellHandle)) return; cndfmt.addLocation(cellHandle.getCellAddress()); } - + /** * returns the formatting for each rule of this Contditional Format Handle + * * @return FormatHandle[] */ public FormatHandle[] getFormats() { - FormatHandle[] fmx = new FormatHandle[this.getCndfmt().getRules().size()]; - if (cndfmt.getFormatHandle()==null) { - //cfm.initCells(this); // added! - int cfxe= cndfmt.getCfxe(); - FormatHandle fz = new FormatHandle(cndfmt, worksheet.wbh, cfxe, null); - } - for (int t = 0; t < fmx.length; t++) { - cndfmt.getFormatHandle().updateFromCF((Cf)this.getCndfmt().getRules().get(t), worksheet.wbh); - fmx[t] = cndfmt.getFormatHandle(); - } - return fmx; + FormatHandle[] fmx = new FormatHandle[this.getCndfmt().getRules().size()]; + if (cndfmt.getFormatHandle() == null) { + //cfm.initCells(this); // added! + int cfxe = cndfmt.getCfxe(); + FormatHandle fz = new FormatHandle(cndfmt, worksheet.wbh, cfxe, null); + } + for (int t = 0; t < fmx.length; t++) { + cndfmt.getFormatHandle().updateFromCF((Cf) this.getCndfmt().getRules().get(t), worksheet.wbh); + fmx[t] = cndfmt.getFormatHandle(); + } + return fmx; } } diff --git a/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.java b/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.java index 3cffba3..4752dd1 100644 --- a/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.java +++ b/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,623 +22,613 @@ */ package io.starter.OpenXLS; -import java.awt.Color; - import io.starter.formats.XLS.Cf; import io.starter.formats.XLS.Font; import io.starter.formats.XLS.Formula; import io.starter.formats.XLS.formulas.Ptg; import io.starter.formats.XLS.formulas.PtgRef; +import java.awt.*; + /** * ConditionalFormatRule defines a single rule for manipulation of the * ConditionalFormat cells in Excel - * + *

        * Each ConditionalFormatRule contains one rule and corresponding format data. - * - * + * * @see ConditionalFormatHandle * @see Handle - * - * */ public class ConditionalFormatRule implements Handle { - Cf currentCf = null; - // static shorts for setting ConditionalFormat type - public static final byte VALUE_ANY = 0x0; - public static final byte VALUE_INTEGER = 0x1; - public static final byte VALUE_DECIMAL = 0x2; - public static final byte VALUE_USER_DEFINED_LIST = 0x3; - public static final byte VALUE_DATE = 0x4; - public static final byte VALUE_TIME = 0x5; - public static final byte VALUE_TEXT_LENGTH = 0x6; - public static final byte VALUE_FORMULA = 0x7; - - // static shorts for setting action on error - public static byte ERROR_STOP = 0x0; - public static byte ERROR_WARN = 0x1; - public static byte ERROR_INFO = 0x2; - - // static shorts for setting conditions on ConditionalFormat - public static final byte CONDITION_BETWEEN = 0x0; - public static final byte CONDITION_NOT_BETWEEN = 0x1; - public static final byte CONDITION_EQUAL = 0x2; - public static final byte CONDITION_NOT_EQUAL = 0x3; - public static final byte CONDITION_GREATER_THAN = 0x4; - public static final byte CONDITION_LESS_THAN = 0x5; - public static final byte CONDITION_GREATER_OR_EQUAL = 0x6; - public static final byte CONDITION_LESS_OR_EQUAL = 0x7; - - public static String[] OPERATORS = { "nocomparison", "between", "notBetween", "equal", "notEqual", "greaterThan", - "lessThan", "greaterOrEqual", "lessOrEqual", "beginsWith", "endsWith", "containsText", "notContains" }; - - /** - * Get the byte representing the condition type string passed in. Options are' - * "between", "notBetween", "equal", "notEqual", "greaterThan", "lessThan", - * "greaterOrEqual", "lessOrEqual" - * - * @return - */ - public static byte getConditionNumber(String conditionType) { - for (int i = 0; i < OPERATORS.length; i++) { - if (conditionType.equalsIgnoreCase(OPERATORS[i])) - return (byte) i; - } - return -1; - } - - public static String[] VALUE_TYPE = { "any", "integer", "decimal", "userDefinedList", "date", "time", "textLength", - "formula" }; - - /** - * Get the byte representing the value type string passed in. Options are' - * "any", "integer", "decimal", "userDefinedList", "date", "time", "textLength", - * "formula" - * - * - * - * - * @return - */ - public static byte getValueNumber(String valueType) { - for (int i = 0; i < VALUE_TYPE.length; i++) { - if (valueType.equalsIgnoreCase(VALUE_TYPE[i])) - return (byte) i; - } - return -1; - } - - /** - * Create a conditional format rule from a Cf record. - * - * @param theCf - */ - protected ConditionalFormatRule(Cf theCf) { - currentCf = theCf; - } - - /** - * evaluates the criteria for this Conditional Format Rule
        - * if the criteria involves a comparison i.e. equals, less than, etc., it uses - * the value from the passed in referenced cell to compare with - * - * @return boolean true if evaluation of criteria passes - * @param Ptg - * refcell - the Ptg location to obtain cell value from - * @see io.starter.formats.XLS.Cf#evaluate(io.starter.formats.XLS.formulas.Ptg) - */ - public boolean evaluate(CellHandle refcell) { - Ptg/* Ref */ pr = PtgRef.createPtgRefFromString(refcell.getCellAddress(), null); - return currentCf.evaluate(pr); - } - - /** - * Get the type operator of this ConditionalFormat as a byte. - * - * These bytes map to the CONDITION_* static values in ConditionalFormatHandle - * - * @return - */ - public byte getTypeOperator() { - return (byte) currentCf.getOperator(); - } - - /** - * set the type operator of this ConditionalFormat as a byte. - * - * These bytes map to the CONDITION_* static values in ConditionalFormatHandle - * - * - */ - public void setTypeOperator(byte typOperator) { - currentCf.setOperator(typOperator); - } - - /** - * Get the second condition of the ConditionalFormat as a string representation - * - * @return - */ - public String getSecondCondition() { - if (currentCf != null && currentCf.getFormula2() != null) - return currentCf.getFormula2().getFormulaString(); - return null; - } - - /** - * Set the second condition of the ConditionalFormat utilizing a string. This - * value must conform to the Value Type of this ConditionalFormat or unexpected - * results may occur. For example, entering a string representation of a date - * here will not work if your ConditionalFormat is an integer... - * - * String passed in should be a vaild XLS formula. Does not need to include the - * "=" - * - * Types of conditions Integer values Decimal values User defined list Date Time - * Text length Formula - * - * Be sure that your ConditionalFormat type (getConditionalFormatType()) matches - * the type of data. - * - * @return - */ - public void setSecondCondition(Object secondCond) { - String setval = secondCond.toString(); - if (secondCond instanceof java.util.Date) { - double d = DateConverter.getXLSDateVal((java.util.Date) secondCond); - setval = d + ""; - } - currentCf.setCondition2(setval); - } - - /** - * retrieves the border colors for the current Conditional Format - * - * @return java.awt.Color array of Color objects for each border side (Top, - * Left, Bottom, Right) - * @see io.starter.formats.XLS.Cf#getBorderColors() - */ - public Color[] getBorderColors() { - return currentCf.getBorderColors(); - } - - /** - * returns the bottom border line color for the current Conditional Format - * - * @return int bottom border line color constant - * @see io.starter.formats.XLS.Cf#getBorderLineColorBottom() - * @see FormatHandle.COLOR_* constants - */ - public int getBorderLineColorBottom() { - return currentCf.getBorderLineColorBottom(); - } - - /** - * returns the left border line color for the current Conditional Format - * - * @return int left border line color constant - * @see io.starter.formats.XLS.Cf#getBorderLineColorLeft() - * @see FormatHandle.COLOR_* constants - */ - public int getBorderLineColorLeft() { - return currentCf.getBorderLineColorLeft(); - } - - /** - * returns the right border line color for the current Conditional Format - * - * @return int right border line color constant - * @see io.starter.formats.XLS.Cf#getBorderLineColorRight() - * @see FormatHandle.COLOR_* constants - */ - public int getBorderLineColorRight() { - return currentCf.getBorderLineColorRight(); - } - - /** - * returns the top border line color for the current Conditional Format - * - * @return int top border line color constant - * @see io.starter.formats.XLS.Cf#getBorderLineColorTop() - * @see FormatHandle.COLOR_* constants - */ - public int getBorderLineColorTop() { - return currentCf.getBorderLineColorTop(); - } - - /** - * returns the bottom border line style for the current Conditional Format - * - * @return int bottom border line style constant - * @see io.starter.formats.XLS.Cf#getBorderLineStylesBottom() - * @see FormatHandle.BORDER* line style constants - */ - public int getBorderLineStylesBottom() { - return currentCf.getBorderLineStylesBottom(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderLineStylesLeft() - */ - public int getBorderLineStylesLeft() { - return currentCf.getBorderLineStylesLeft(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderLineStylesRight() - */ - public int getBorderLineStylesRight() { - return currentCf.getBorderLineStylesRight(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderLineStylesTop() - */ - public int getBorderLineStylesTop() { - return currentCf.getBorderLineStylesTop(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderSizes() - */ - public int[] getBorderSizes() { - return currentCf.getBorderSizes(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderStyles() - */ - public int[] getBorderStyles() { - return currentCf.getBorderStyles(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFont() - */ - public Font getFont() { - return currentCf.getFont(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontColorIndex() - */ - public int getFontColorIndex() { - return currentCf.getFontColorIndex(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontEscapement() - */ - public int getFontEscapement() { - return currentCf.getFontEscapement(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontHeight() - */ - public int getFontHeight() { - return currentCf.getFontHeight(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontOptsCancellation() - */ - public int getFontOptsCancellation() { - return currentCf.getFontOptsCancellation(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontOptsPosture() - */ - public int getFontOptsPosture() { - return currentCf.getFontOptsPosture(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontUnderlineStyle() - */ - public int getFontUnderlineStyle() { - return currentCf.getFontUnderlineStyle(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontWeight() - */ - public int getFontWeight() { - return currentCf.getFontWeight(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getForegroundColor() - */ - public int getForegroundColor() { - return currentCf.getForegroundColor(); - } - - /** - * @return - * @see io.starter.formats.XLS.XLSRecord#getFormatPattern() - */ - public String getFormatPattern() { - return currentCf.getFormatPattern(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFormula1() - */ - public Formula getFormula1() { - return currentCf.getFormula1(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFormula2() - */ - public Formula getFormula2() { - return currentCf.getFormula2(); - } - - /** - * returns the operator for this Conditional Format Rule
        - * e.g. "bewteen", "greater than" ... - * - * @return - */ - public String getOperator() { - int op = currentCf.getOperator(); - if (op >= 0 && op < OPERATORS.length) - return OPERATORS[op]; - return "unknown operator: " + op; - } - - /** - * returns the type of this Conditional Format
        - * e.g. "Cell value is" or "Formula value is" - * - * @return String Conditional Format Type - */ - public String getType() { - return currentCf.getTypeString(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getPatternFillColor() - */ - public int getPatternFillColor() { - return currentCf.getPatternFillColor(); - } - - /** - * returns the pattern color, if any, as an HTML color String. Includes custom - * OOXML colors. - * - * @return String HTML Color String - */ - public String getPatternFgColor() { - return currentCf.getPatternFgColor(); - } - - /** - * returns the pattern color, if any, as an HTML color String. Includes custom - * OOXML colors. - * - * @return String HTML Color String - */ - public String getPatternBgColor() { - return currentCf.getPatternBgColor(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getPatternFillColorBack() - */ - public int getPatternFillColorBack() { - return currentCf.getPatternFillColorBack(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getPatternFillStyle() - */ - public int getPatternFillStyle() { - return currentCf.getPatternFillStyle(); - } - - /** - * @param borderLineColorBottom - * @see io.starter.formats.XLS.Cf#setBorderLineColorBottom(int) - */ - public void setBorderLineColorBottom(int borderLineColorBottom) { - currentCf.setBorderLineColorBottom(borderLineColorBottom); - } - - /** - * @param borderLineColorLeft - * @see io.starter.formats.XLS.Cf#setBorderLineColorLeft(int) - */ - public void setBorderLineColorLeft(int borderLineColorLeft) { - currentCf.setBorderLineColorLeft(borderLineColorLeft); - } - - /** - * @param borderLineColorTop - * @see io.starter.formats.XLS.Cf#setBorderLineColorTop(int) - */ - public void setBorderLineColorTop(int borderLineColorTop) { - currentCf.setBorderLineColorTop(borderLineColorTop); - } - - /** - * @param borderLineStylesBottom - * @see io.starter.formats.XLS.Cf#setBorderLineStylesBottom(int) - */ - public void setBorderLineStylesBottom(int borderLineStylesBottom) { - currentCf.setBorderLineStylesBottom(borderLineStylesBottom); - } - - /** - * @param borderLineStylesLeft - * @see io.starter.formats.XLS.Cf#setBorderLineStylesLeft(int) - */ - public void setBorderLineStylesLeft(int borderLineStylesLeft) { - currentCf.setBorderLineStylesLeft(borderLineStylesLeft); - } - - /** - * @param borderLineStylesRight - * @see io.starter.formats.XLS.Cf#setBorderLineStylesRight(int) - */ - public void setBorderLineStylesRight(int borderLineStylesRight) { - currentCf.setBorderLineStylesRight(borderLineStylesRight); - } - - /** - * @param borderLineStylesTop - * @see io.starter.formats.XLS.Cf#setBorderLineStylesTop(int) - */ - public void setBorderLineStylesTop(int borderLineStylesTop) { - currentCf.setBorderLineStylesTop(borderLineStylesTop); - } - - /** - * @param fontColorIndex - * @see io.starter.formats.XLS.Cf#setFontColorIndex(int) - */ - public void setFontColorIndex(int fontColorIndex) { - currentCf.setFontColorIndex(fontColorIndex); - } - - /** - * @param fontEscapementFlag - * @see io.starter.formats.XLS.Cf#setFontEscapement(int) - */ - public void setFontEscapement(int fontEscapementFlag) { - currentCf.setFontEscapement(fontEscapementFlag); - } - - /** - * @param fontHeight - * @see io.starter.formats.XLS.Cf#setFontHeight(int) - */ - public void setFontHeight(int fontHeight) { - currentCf.setFontHeight(fontHeight); - } - - /** - * @param fontOptsCancellation - * @see io.starter.formats.XLS.Cf#setFontOptsCancellation(int) - */ - public void setFontOptsCancellation(int fontOptsCancellation) { - currentCf.setFontStriken((fontOptsCancellation == Cf.FONT_OPTIONS_CANCELLATION_ON)); - } - - /** - * @param fontOptsPosture - * @see io.starter.formats.XLS.Cf#setFontOptsPosture(int) - */ - public void setFontOptsPosture(int fontOptsPosture) { - currentCf.setFontOptsPosture(fontOptsPosture); - } - - /** - * @param fontUnderlineStyle - * @see io.starter.formats.XLS.Cf#setFontUnderlineStyle(int) - */ - public void setFontUnderlineStyle(int fontUnderlineStyle) { - currentCf.setFontUnderlineStyle(fontUnderlineStyle); - } - - /** - * @param fontWeight - * @see io.starter.formats.XLS.Cf#setFontWeight(int) - */ - public void setFontWeight(int fontWeight) { - currentCf.setFontWeight(fontWeight); - } - - /** - * @param patternFillColor - * @see io.starter.formats.XLS.Cf#setPatternFillColor(int) - */ - public void setPatternFillColor(int patternFillColor) { - currentCf.setPatternFillColor(patternFillColor, null); - } - - /** - * @param patternFillColorBack - * @see io.starter.formats.XLS.Cf#setPatternFillColorBack(int) - */ - public void setPatternFillColorBack(int patternFillColorBack) { - currentCf.setPatternFillColorBack(patternFillColorBack); - } - - /** - * @param patternFillStyle - * @see io.starter.formats.XLS.Cf#setPatternFillStyle(int) - */ - public void setPatternFillStyle(int patternFillStyle) { - currentCf.setPatternFillStyle(patternFillStyle); - } - - /** - * Get the first condition of the ConditionalFormat as a string representation - * - * @return - */ - public String getFirstCondition() { - return currentCf.getFormula1().getFormulaString(); - } - - /** - * Set the first condition of the ConditionalFormat - * - * This value must conform to the Value Type of this ConditionalFormat or - * unexpected results may occur. For example, entering a string representation - * of a date here will not work if your ConditionalFormat is an integer... - * - * A java.util.Date object can also be passed in. This value will be translated - * into an integer as excel stores dates. If you need to manipulate/retrieve - * this value later utilize the DateConverter tool to transform the value - * - * String passed in should be a vaild XLS formula. Does not need to include the - * "=" - * - * Types of conditions Integer values Decimal values User defined list Date Time - * Text length Formula - * - * Be sure that your ConditionalFormat type (getConditionalFormatType()) matches - * the type of data. - * - * @param firstCond - * = the first condition for the ConditionalFormat - */ - public void setFirstCondition(Object firstCond) { - String setval = firstCond.toString(); - if (firstCond instanceof java.util.Date) { - double d = DateConverter.getXLSDateVal((java.util.Date) firstCond); - setval = d + ""; - } - currentCf.setCondition1(setval); - } - - public int getConditionalFormatType() { - return 0; - } + Cf currentCf = null; + // static shorts for setting ConditionalFormat type + public static final byte VALUE_ANY = 0x0; + public static final byte VALUE_INTEGER = 0x1; + public static final byte VALUE_DECIMAL = 0x2; + public static final byte VALUE_USER_DEFINED_LIST = 0x3; + public static final byte VALUE_DATE = 0x4; + public static final byte VALUE_TIME = 0x5; + public static final byte VALUE_TEXT_LENGTH = 0x6; + public static final byte VALUE_FORMULA = 0x7; + + // static shorts for setting action on error + public static byte ERROR_STOP = 0x0; + public static byte ERROR_WARN = 0x1; + public static byte ERROR_INFO = 0x2; + + // static shorts for setting conditions on ConditionalFormat + public static final byte CONDITION_BETWEEN = 0x0; + public static final byte CONDITION_NOT_BETWEEN = 0x1; + public static final byte CONDITION_EQUAL = 0x2; + public static final byte CONDITION_NOT_EQUAL = 0x3; + public static final byte CONDITION_GREATER_THAN = 0x4; + public static final byte CONDITION_LESS_THAN = 0x5; + public static final byte CONDITION_GREATER_OR_EQUAL = 0x6; + public static final byte CONDITION_LESS_OR_EQUAL = 0x7; + + public static String[] OPERATORS = {"nocomparison", "between", "notBetween", "equal", "notEqual", "greaterThan", + "lessThan", "greaterOrEqual", "lessOrEqual", "beginsWith", "endsWith", "containsText", "notContains"}; + + /** + * Get the byte representing the condition type string passed in. Options are' + * "between", "notBetween", "equal", "notEqual", "greaterThan", "lessThan", + * "greaterOrEqual", "lessOrEqual" + * + * @return + */ + public static byte getConditionNumber(String conditionType) { + for (int i = 0; i < OPERATORS.length; i++) { + if (conditionType.equalsIgnoreCase(OPERATORS[i])) + return (byte) i; + } + return -1; + } + + public static String[] VALUE_TYPE = {"any", "integer", "decimal", "userDefinedList", "date", "time", "textLength", + "formula"}; + + /** + * Get the byte representing the value type string passed in. Options are' + * "any", "integer", "decimal", "userDefinedList", "date", "time", "textLength", + * "formula" + * + * @return + */ + public static byte getValueNumber(String valueType) { + for (int i = 0; i < VALUE_TYPE.length; i++) { + if (valueType.equalsIgnoreCase(VALUE_TYPE[i])) + return (byte) i; + } + return -1; + } + + /** + * Create a conditional format rule from a Cf record. + * + * @param theCf + */ + protected ConditionalFormatRule(Cf theCf) { + currentCf = theCf; + } + + /** + * evaluates the criteria for this Conditional Format Rule
        + * if the criteria involves a comparison i.e. equals, less than, etc., it uses + * the value from the passed in referenced cell to compare with + * + * @param Ptg refcell - the Ptg location to obtain cell value from + * @return boolean true if evaluation of criteria passes + * @see io.starter.formats.XLS.Cf#evaluate(io.starter.formats.XLS.formulas.Ptg) + */ + public boolean evaluate(CellHandle refcell) { + Ptg/* Ref */ pr = PtgRef.createPtgRefFromString(refcell.getCellAddress(), null); + return currentCf.evaluate(pr); + } + + /** + * Get the type operator of this ConditionalFormat as a byte. + *

        + * These bytes map to the CONDITION_* static values in ConditionalFormatHandle + * + * @return + */ + public byte getTypeOperator() { + return (byte) currentCf.getOperator(); + } + + /** + * set the type operator of this ConditionalFormat as a byte. + *

        + * These bytes map to the CONDITION_* static values in ConditionalFormatHandle + */ + public void setTypeOperator(byte typOperator) { + currentCf.setOperator(typOperator); + } + + /** + * Get the second condition of the ConditionalFormat as a string representation + * + * @return + */ + public String getSecondCondition() { + if (currentCf != null && currentCf.getFormula2() != null) + return currentCf.getFormula2().getFormulaString(); + return null; + } + + /** + * Set the second condition of the ConditionalFormat utilizing a string. This + * value must conform to the Value Type of this ConditionalFormat or unexpected + * results may occur. For example, entering a string representation of a date + * here will not work if your ConditionalFormat is an integer... + *

        + * String passed in should be a vaild XLS formula. Does not need to include the + * "=" + *

        + * Types of conditions Integer values Decimal values User defined list Date Time + * Text length Formula + *

        + * Be sure that your ConditionalFormat type (getConditionalFormatType()) matches + * the type of data. + * + * @return + */ + public void setSecondCondition(Object secondCond) { + String setval = secondCond.toString(); + if (secondCond instanceof java.util.Date) { + double d = DateConverter.getXLSDateVal((java.util.Date) secondCond); + setval = d + ""; + } + currentCf.setCondition2(setval); + } + + /** + * retrieves the border colors for the current Conditional Format + * + * @return java.awt.Color array of Color objects for each border side (Top, + * Left, Bottom, Right) + * @see io.starter.formats.XLS.Cf#getBorderColors() + */ + public Color[] getBorderColors() { + return currentCf.getBorderColors(); + } + + /** + * returns the bottom border line color for the current Conditional Format + * + * @return int bottom border line color constant + * @see io.starter.formats.XLS.Cf#getBorderLineColorBottom() + * @see FormatHandle.COLOR_* constants + */ + public int getBorderLineColorBottom() { + return currentCf.getBorderLineColorBottom(); + } + + /** + * returns the left border line color for the current Conditional Format + * + * @return int left border line color constant + * @see io.starter.formats.XLS.Cf#getBorderLineColorLeft() + * @see FormatHandle.COLOR_* constants + */ + public int getBorderLineColorLeft() { + return currentCf.getBorderLineColorLeft(); + } + + /** + * returns the right border line color for the current Conditional Format + * + * @return int right border line color constant + * @see io.starter.formats.XLS.Cf#getBorderLineColorRight() + * @see FormatHandle.COLOR_* constants + */ + public int getBorderLineColorRight() { + return currentCf.getBorderLineColorRight(); + } + + /** + * returns the top border line color for the current Conditional Format + * + * @return int top border line color constant + * @see io.starter.formats.XLS.Cf#getBorderLineColorTop() + * @see FormatHandle.COLOR_* constants + */ + public int getBorderLineColorTop() { + return currentCf.getBorderLineColorTop(); + } + + /** + * returns the bottom border line style for the current Conditional Format + * + * @return int bottom border line style constant + * @see io.starter.formats.XLS.Cf#getBorderLineStylesBottom() + * @see FormatHandle.BORDER* line style constants + */ + public int getBorderLineStylesBottom() { + return currentCf.getBorderLineStylesBottom(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getBorderLineStylesLeft() + */ + public int getBorderLineStylesLeft() { + return currentCf.getBorderLineStylesLeft(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getBorderLineStylesRight() + */ + public int getBorderLineStylesRight() { + return currentCf.getBorderLineStylesRight(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getBorderLineStylesTop() + */ + public int getBorderLineStylesTop() { + return currentCf.getBorderLineStylesTop(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getBorderSizes() + */ + public int[] getBorderSizes() { + return currentCf.getBorderSizes(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getBorderStyles() + */ + public int[] getBorderStyles() { + return currentCf.getBorderStyles(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFont() + */ + public Font getFont() { + return currentCf.getFont(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFontColorIndex() + */ + public int getFontColorIndex() { + return currentCf.getFontColorIndex(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFontEscapement() + */ + public int getFontEscapement() { + return currentCf.getFontEscapement(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFontHeight() + */ + public int getFontHeight() { + return currentCf.getFontHeight(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFontOptsCancellation() + */ + public int getFontOptsCancellation() { + return currentCf.getFontOptsCancellation(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFontOptsPosture() + */ + public int getFontOptsPosture() { + return currentCf.getFontOptsPosture(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFontUnderlineStyle() + */ + public int getFontUnderlineStyle() { + return currentCf.getFontUnderlineStyle(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFontWeight() + */ + public int getFontWeight() { + return currentCf.getFontWeight(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getForegroundColor() + */ + public int getForegroundColor() { + return currentCf.getForegroundColor(); + } + + /** + * @return + * @see io.starter.formats.XLS.XLSRecord#getFormatPattern() + */ + public String getFormatPattern() { + return currentCf.getFormatPattern(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFormula1() + */ + public Formula getFormula1() { + return currentCf.getFormula1(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getFormula2() + */ + public Formula getFormula2() { + return currentCf.getFormula2(); + } + + /** + * returns the operator for this Conditional Format Rule
        + * e.g. "bewteen", "greater than" ... + * + * @return + */ + public String getOperator() { + int op = currentCf.getOperator(); + if (op >= 0 && op < OPERATORS.length) + return OPERATORS[op]; + return "unknown operator: " + op; + } + + /** + * returns the type of this Conditional Format
        + * e.g. "Cell value is" or "Formula value is" + * + * @return String Conditional Format Type + */ + public String getType() { + return currentCf.getTypeString(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getPatternFillColor() + */ + public int getPatternFillColor() { + return currentCf.getPatternFillColor(); + } + + /** + * returns the pattern color, if any, as an HTML color String. Includes custom + * OOXML colors. + * + * @return String HTML Color String + */ + public String getPatternFgColor() { + return currentCf.getPatternFgColor(); + } + + /** + * returns the pattern color, if any, as an HTML color String. Includes custom + * OOXML colors. + * + * @return String HTML Color String + */ + public String getPatternBgColor() { + return currentCf.getPatternBgColor(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getPatternFillColorBack() + */ + public int getPatternFillColorBack() { + return currentCf.getPatternFillColorBack(); + } + + /** + * @return + * @see io.starter.formats.XLS.Cf#getPatternFillStyle() + */ + public int getPatternFillStyle() { + return currentCf.getPatternFillStyle(); + } + + /** + * @param borderLineColorBottom + * @see io.starter.formats.XLS.Cf#setBorderLineColorBottom(int) + */ + public void setBorderLineColorBottom(int borderLineColorBottom) { + currentCf.setBorderLineColorBottom(borderLineColorBottom); + } + + /** + * @param borderLineColorLeft + * @see io.starter.formats.XLS.Cf#setBorderLineColorLeft(int) + */ + public void setBorderLineColorLeft(int borderLineColorLeft) { + currentCf.setBorderLineColorLeft(borderLineColorLeft); + } + + /** + * @param borderLineColorTop + * @see io.starter.formats.XLS.Cf#setBorderLineColorTop(int) + */ + public void setBorderLineColorTop(int borderLineColorTop) { + currentCf.setBorderLineColorTop(borderLineColorTop); + } + + /** + * @param borderLineStylesBottom + * @see io.starter.formats.XLS.Cf#setBorderLineStylesBottom(int) + */ + public void setBorderLineStylesBottom(int borderLineStylesBottom) { + currentCf.setBorderLineStylesBottom(borderLineStylesBottom); + } + + /** + * @param borderLineStylesLeft + * @see io.starter.formats.XLS.Cf#setBorderLineStylesLeft(int) + */ + public void setBorderLineStylesLeft(int borderLineStylesLeft) { + currentCf.setBorderLineStylesLeft(borderLineStylesLeft); + } + + /** + * @param borderLineStylesRight + * @see io.starter.formats.XLS.Cf#setBorderLineStylesRight(int) + */ + public void setBorderLineStylesRight(int borderLineStylesRight) { + currentCf.setBorderLineStylesRight(borderLineStylesRight); + } + + /** + * @param borderLineStylesTop + * @see io.starter.formats.XLS.Cf#setBorderLineStylesTop(int) + */ + public void setBorderLineStylesTop(int borderLineStylesTop) { + currentCf.setBorderLineStylesTop(borderLineStylesTop); + } + + /** + * @param fontColorIndex + * @see io.starter.formats.XLS.Cf#setFontColorIndex(int) + */ + public void setFontColorIndex(int fontColorIndex) { + currentCf.setFontColorIndex(fontColorIndex); + } + + /** + * @param fontEscapementFlag + * @see io.starter.formats.XLS.Cf#setFontEscapement(int) + */ + public void setFontEscapement(int fontEscapementFlag) { + currentCf.setFontEscapement(fontEscapementFlag); + } + + /** + * @param fontHeight + * @see io.starter.formats.XLS.Cf#setFontHeight(int) + */ + public void setFontHeight(int fontHeight) { + currentCf.setFontHeight(fontHeight); + } + + /** + * @param fontOptsCancellation + * @see io.starter.formats.XLS.Cf#setFontOptsCancellation(int) + */ + public void setFontOptsCancellation(int fontOptsCancellation) { + currentCf.setFontStriken((fontOptsCancellation == Cf.FONT_OPTIONS_CANCELLATION_ON)); + } + + /** + * @param fontOptsPosture + * @see io.starter.formats.XLS.Cf#setFontOptsPosture(int) + */ + public void setFontOptsPosture(int fontOptsPosture) { + currentCf.setFontOptsPosture(fontOptsPosture); + } + + /** + * @param fontUnderlineStyle + * @see io.starter.formats.XLS.Cf#setFontUnderlineStyle(int) + */ + public void setFontUnderlineStyle(int fontUnderlineStyle) { + currentCf.setFontUnderlineStyle(fontUnderlineStyle); + } + + /** + * @param fontWeight + * @see io.starter.formats.XLS.Cf#setFontWeight(int) + */ + public void setFontWeight(int fontWeight) { + currentCf.setFontWeight(fontWeight); + } + + /** + * @param patternFillColor + * @see io.starter.formats.XLS.Cf#setPatternFillColor(int) + */ + public void setPatternFillColor(int patternFillColor) { + currentCf.setPatternFillColor(patternFillColor, null); + } + + /** + * @param patternFillColorBack + * @see io.starter.formats.XLS.Cf#setPatternFillColorBack(int) + */ + public void setPatternFillColorBack(int patternFillColorBack) { + currentCf.setPatternFillColorBack(patternFillColorBack); + } + + /** + * @param patternFillStyle + * @see io.starter.formats.XLS.Cf#setPatternFillStyle(int) + */ + public void setPatternFillStyle(int patternFillStyle) { + currentCf.setPatternFillStyle(patternFillStyle); + } + + /** + * Get the first condition of the ConditionalFormat as a string representation + * + * @return + */ + public String getFirstCondition() { + return currentCf.getFormula1().getFormulaString(); + } + + /** + * Set the first condition of the ConditionalFormat + *

        + * This value must conform to the Value Type of this ConditionalFormat or + * unexpected results may occur. For example, entering a string representation + * of a date here will not work if your ConditionalFormat is an integer... + *

        + * A java.util.Date object can also be passed in. This value will be translated + * into an integer as excel stores dates. If you need to manipulate/retrieve + * this value later utilize the DateConverter tool to transform the value + *

        + * String passed in should be a vaild XLS formula. Does not need to include the + * "=" + *

        + * Types of conditions Integer values Decimal values User defined list Date Time + * Text length Formula + *

        + * Be sure that your ConditionalFormat type (getConditionalFormatType()) matches + * the type of data. + * + * @param firstCond = the first condition for the ConditionalFormat + */ + public void setFirstCondition(Object firstCond) { + String setval = firstCond.toString(); + if (firstCond instanceof java.util.Date) { + double d = DateConverter.getXLSDateVal((java.util.Date) firstCond); + setval = d + ""; + } + currentCf.setCondition1(setval); + } + + public int getConditionalFormatType() { + return 0; + } } diff --git a/src/main/java/io/starter/OpenXLS/DateConverter.java b/src/main/java/io/starter/OpenXLS/DateConverter.java index d9da18c..7c06154 100644 --- a/src/main/java/io/starter/OpenXLS/DateConverter.java +++ b/src/main/java/io/starter/OpenXLS/DateConverter.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,10 +23,14 @@ package io.starter.OpenXLS; //import java.text.SimpleDateFormat; -import java.util.*; import io.starter.toolkit.Logger; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.StringTokenizer; + /** * Provides methods for conversion to and from Excel serial date values. *

        @@ -49,699 +53,706 @@ * decimal places. This provides accuracy to the unit milliseconds, the maximum * precision of Java's date classes. Accuracy outside the range supported by * Excel is not guaranteed and will degrade as the values get farther from zero. - * */ public class DateConverter { - /** The number of milliseconds in a day. */ - private static final int MILLIS_DAY = 86400000; - - /** The extra day caused by the 1900 leap year bug. */ - private static final int EXTRA_DAY = 60; - - /** Calendar used for date calculation. */ - private static Calendar calendar = Calendar.getInstance(); - - /** Whether to validate input dates for Excel compatibility. */ - private static boolean validate = false; - - /** The set of supported serial date encoding schemes. */ - public static enum DateFormat { - /** 1900 epoch with negative value support as used in OOXML. - *

        - * Lower limit: -9999/01/01 00:00:00, value -4 346 018
        - * Epoch date: 1899/12/30 00:00:00, value 0
        - * Upper limit: 9999/12/31 23:59:59, value 2 958 465.999 988 4
        - */ - OOXML_1900( 25569, -4346018, 2958465.9999884 ), - - /** 1900 epoch without negative value support as used in BIFF8. - *

        - * Epoch date: 1899/12/31 00:00:00, value 0
        - * Lower limit: 1900/01/01 00:00:00, value 1
        - * Upper limit: 9999/12/31 23:59:59, value 2 958 465.999 988 4
        - *

        - * In this system 1900 is (incorrectly) considered a leap year. Serial - * dates after 1900/02/28 are one higher than they otherwise should be - * and the value 60 is unmapped. - */ - LEGACY_1900( 25568, 1, 2958465.9999884 ), - - /** 1904 epoch without negative value support as used by Excel for Mac. - *

        - * Epoch date: 1904/01/01 00:00:00, value 0
        - * Lower limit: 1904/01/01 00:00:00, value 0
        - * Upper limit: 9999/12/31 23:59:59, value 2 957 003.999 988 4
        - */ - LEGACY_1904( 24107, 0, 2957003.9999884 ); - - private final int epoch_delta; - private final double limit_lower, limit_upper; - - private DateFormat (int delta, double min, double max) { - epoch_delta = delta; - limit_lower = min; - limit_upper = max; - } - - protected int getEpochDelta() { - return epoch_delta; - } - - public double getLowerLimit() { - return limit_lower; - } - - public double getUpperLimit() { - return limit_upper; - } - } - - /** - * Gets a clone of the calendar used for date calculation. - */ - public static Calendar getCalendar() { - return (Calendar) calendar.clone(); - } - - /** - * Sets the calendar used to perform date calculation. This allows you to - * set the locale and time zone to something other than the system default. - * - * @param cal - * the calendar that should be used for date calculations - */ - public static void setCalendar(Calendar cal) { - calendar = cal; - } - - /** - * Returns whether input validation is on. - */ - public static boolean getValidate() { - return validate; - } - - /** - * Sets whether to perform input validation. - */ - public static void setValidate(boolean validate) { - DateConverter.validate = validate; - } - - /** - * returns whether this method will work with your input string - */ - public static boolean isParseableDateString(String str) { - // fix problem with timestamps tagged on end - if (str.indexOf(" ") > 0) { - str = str.substring(0, str.indexOf(" ")); - } - try { - java.sql.Date d1 = java.sql.Date.valueOf(str); - getXLSDateVal( d1 ); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } - - /** - * attempt to interpret a date string into a date - * returns null if cannot be converted to date - */ - public static Date getDate(String str) { - try { - java.sql.Timestamp d1 = java.sql.Timestamp.valueOf(str); - Calendar cal = (Calendar) calendar.clone(); - cal.setTime(d1); - return cal.getTime(); - } catch (Exception e) { - ; - } - return null; - - } - /** Converts the value of the given Calendar to an Excel serial date. - * The date will be returned in the - * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. - * - * @param cal the Calendar to convert - * @return the Excel serial date representing the given calendar's value in - * the calendar's time zone - * @deprecated Use {@link #getXLSDateVal( Calendar, DateFormat )} instead. - */ - public static double getXLSDateVal(Calendar cal) { - return getXLSDateVal( cal, DateFormat.LEGACY_1900 ); - } - - /** Converts the value of the given Calendar to an Excel serial date. - * @param cal the Calendar to convert - * @param format the serial date format to use - * @return the Excel serial date representing the given calendar's value in - * the calendar's time zone - */ - public static double getXLSDateVal (Calendar cal, DateFormat format) { - // Get the UTC milliseconds since the epoch - long millis = cal.getTimeInMillis(); - - // Add the GMT offset and daylight savings offset for the time zone - millis += cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET); - - // Convert from milliseconds to days - double days = (double) millis / MILLIS_DAY; - - // Switch from UNIX epoch to the Excel epoch - days += format.getEpochDelta(); - - // If the date is after February 28, 1900 add one day. - // This compensates for Excel incorrectly treating 1900 as a leap year - if (format == DateFormat.LEGACY_1900 && days >= EXTRA_DAY) - days += 1; - - // Perform validation - if (validate && (days < format.getLowerLimit() - || days > format.getUpperLimit() ) ) - throw new IllegalArgumentException( - "the given date is not supported by Excel"); - - // Return the Excel serial date value - return days; - } - - /** Converts the given Date to an Excel serial date in the default time zone. - * - * @param date the date to be converted - * @param format the serial date format to use - * @return the Excel serial date value corresponding to the given date - */ - public static double getXLSDateVal (Date date, DateFormat format) { - Calendar cal = (Calendar) calendar.clone(); - cal.setTime(date); - return getXLSDateVal(cal, format); - } - - /** Converts the given Date to an Excel serial date in the default time zone. - * The date will be returned in the - * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. - * - * @param date the date to be converted - * @return the Excel serial date value corresponding to the given date - * @deprecated Use {@link #getXLSDateVal( Date, DateFormat )} instead. - */ - public static double getXLSDateVal (Date date) { - return getXLSDateVal( date, DateFormat.LEGACY_1900 ); - } - - /** Parses the the given Excel serial date and returns a Calendar. - * The date will be interpreted in the - * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. - * - * @param date the Excel serial date to be interpreted - * @return a Calendar representing the given Excel date interpreted in the - * default time zone - * @deprecated Use {@link #getCalendarFromNumber( double, DateFormat )} instead. - */ - public static Calendar getCalendarFromNumber (double date) { - return getCalendarFromNumber( date, DateFormat.LEGACY_1900 ); - } - - /** Parses the the given Excel serial date and returns a Calendar. - * - * @param date the Excel serial date to be interpreted - * @param format the date format with which to interpret the serial date - * @return a Calendar representing the given Excel date interpreted in the - * default time zone - */ - public static Calendar getCalendarFromNumber (double date, DateFormat format) { - Calendar cal = getCalendar(); - double days = date; - - // For the legacy 1900 epoch, if the date is after 1900/02/28 subtract - // one day. This compensates for 1900 being considered a leap year. - // Not matching the non-existent February 29 causes it to become - // March 1. This behavior matches that of Calendar for the same input. - if (format == DateFormat.LEGACY_1900 && days > EXTRA_DAY) - days -= 1; - - // Switch from the Excel epoch to the UNIX epoch - days -= format.getEpochDelta(); - - // Convert from days to milliseconds - long millis = Math.round( days * MILLIS_DAY ); - - // Set the calendar's approximate time so zone offsets are correct - // The offsets can still be wrong for certain border cases. - cal.setTimeInMillis( millis ); - - // Adjust for time zone and daylight saving time offsets - long offset = 0; - for (int count = 0; offset != (offset = cal.get( Calendar.ZONE_OFFSET ) - + cal.get( Calendar.DST_OFFSET )) - && count < 3; count++) - cal.setTimeInMillis( millis - offset ); - - return cal; - } - - /** Parses the the given Excel serial date and returns a Date. - * The date will be interpreted in the - * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. - * - * @param date the Excel serial date to be interpreted - * @param format the date format with which to interpret the serial date - * @return a Calendar representing the given Excel date interpreted in the - * default time zone - * @deprecated Use {@link #getCalendarFromNumber( double, DateFormat )} instead. - */ - public static Date getDateFromNumber(double date) { - return getCalendarFromNumber(date).getTime(); - } - - /** - * Gets the Date for the given Excel serial date. - * - * @param number - * a Number representing the serial date to be interpreted - * @return a Date representing the given Excel date interpreted in the - * default time zone - * @throws ClassCastException - * if the passed object is not a Number - * @deprecated Use {@link #getDateFromNumber(double)} instead. - */ - public static Date getDateFromNumber(Object number) { - if (number instanceof Number) - return getDateFromNumber(((Number) number).doubleValue()); - - else - throw new ClassCastException("passed object was not a number"); - } - - /** - * Gets the Date for the given Excel serial date. - * - * @param number - * a Number representing the serial date to be interpreted - * @return a Date representing the given Excel date interpreted in the - * default time zone - * @throws ClassCastException - * if the passed object is not a Number - * @deprecated Identical to {@link #getDateFromNumber(Object)}. - */ - public static Date getNonLocalizedDateFromNumber(Object number) { - return getDateFromNumber(number); - } - - /** - * Gets the Calendar for the given Excel serial date. - * - * @param date - * the Excel serial date or datestring to be interpreted - * @return a Calendar representing the given Excel date interpreted in the - * default time zone - * @throws ClassCastException - * if the passed object is not a Number - * @deprecated Use {@link #getCalendarFromNumber(double)} instead. - */ - public static Calendar getCalendarFromNumber(Object number) { - if (number==null) - throw new ClassCastException("object cannot be converted to a date"); - if (!(number instanceof Number)) - number= new Double(number.toString()); - - if (number instanceof Number) - return getCalendarFromNumber(((Number) number).doubleValue()); - - else - throw new ClassCastException("passed object was not a number"); - } - - /** - * Gets the Date for the given Excel serial date. - * - * @param number - * a Number representing the serial date to be interpreted - * @return a Date representing the given Excel date interpreted in the - * default time zone - * @throws ClassCastException - * if the passed object is not a Number - * @deprecated Identical to {@link #getDateFromNumber(Object)}. - */ - public static Calendar getNonLocalizedCalendarFromNumber(Object number) { - return getCalendarFromNumber(number); - } - - /** - * Gets the Date for the given cell. - * - * @param cell - * a CellHandle whose value should be interpreted - * @return a Date representing the given Excel date interpreted in the - * default time zone - */ - public static Date getDateFromCell(CellHandle cell) { - return getCalendarFromCell( cell ).getTime(); - } - - /** - * returns a Java Calendar from a CellHandle containing an Excel-formatted - * Date - * - * The Excel date format does not map 100% accurately to Java dates, due to - * the limitation of the precision of the Excel floating-point value record. - * Due to this, OpenXLS dates may be too precise, this method will round - * the java.util.Date returned to the precision entered. Rounding is handled - * by the ROUND_HALF_UP method. - * - * Pass in a static Calendar precision, options are Calendar.HOUR - * Calendar.MINUTE Calendar.SECOND Calendar.MILLISECOND - * - * @return Calendar - A GregorianCalendar value of the Cell - */ - public static Calendar getCalendarFromCellWithPrecision(Cell cell, - int roundingCalendarField) { - Calendar tmp = getCalendarFromCell(cell); - GregorianCalendar newCalendar = new GregorianCalendar(); - newCalendar.set(Calendar.YEAR, tmp.get(Calendar.YEAR)); - newCalendar.set(Calendar.MONTH, tmp.get(Calendar.MONTH)); - newCalendar.set(Calendar.DAY_OF_MONTH, tmp.get(Calendar.DAY_OF_MONTH)); - newCalendar.set(Calendar.HOUR_OF_DAY, tmp.get(Calendar.HOUR_OF_DAY)); - if (roundingCalendarField == Calendar.HOUR) { - if (tmp.get(Calendar.MINUTE) > 29) { - newCalendar.set(Calendar.HOUR_OF_DAY, - tmp.get(Calendar.HOUR_OF_DAY) + 1); - } - newCalendar.set(Calendar.MINUTE, 0); - newCalendar.set(Calendar.SECOND, 0); - newCalendar.set(Calendar.MILLISECOND, 0); - return newCalendar; - } - newCalendar.set(Calendar.MINUTE, tmp.get(Calendar.MINUTE)); - if (roundingCalendarField == Calendar.MINUTE) { - if (tmp.get(Calendar.SECOND) > 29) { - newCalendar.set(Calendar.MINUTE, tmp.get(Calendar.MINUTE) + 1); - } - newCalendar.set(Calendar.SECOND, 0); - newCalendar.set(Calendar.MILLISECOND, 0); - return newCalendar; - } - newCalendar.set(Calendar.SECOND, tmp.get(Calendar.SECOND)); - if (roundingCalendarField == Calendar.SECOND) { - if (tmp.get(Calendar.MILLISECOND) > 499) { - newCalendar.set(Calendar.SECOND, tmp.get(Calendar.SECOND) + 1); - } - newCalendar.set(Calendar.MILLISECOND, 0); - return newCalendar; - } - // round the milliseconds - newCalendar.set(Calendar.MILLISECOND, tmp.get(Calendar.MILLISECOND)); - return newCalendar; - - } - - /** - * Gets the Calendar for the given cell. - * - * @param cell - * a CellHandle whose value should be interpreted - * @return a Calendar representing the given Excel date interpreted in the - * default time zone - */ - @SuppressWarnings("deprecation") - public static Calendar getCalendarFromCell(Cell cell) { - double value; - DateFormat format = DateFormat.LEGACY_1900; - - if (cell instanceof CellHandle) { - CellHandle realCell = (CellHandle) cell; - value = realCell.getDoubleVal(); - - WorkBook book = realCell.getWorkBook(); - if (null != book) { - format = book.getDateFormat(); - } - } else { - value = Double.parseDouble( cell.getVal().toString() ); - } - - return getCalendarFromNumber( value, format ); - } - - /** - * Inspects a string to determine if it is a date. Looks for a pattern such - * as mm/dd/yy, m/d/yy, mm/dd/yyyy etc. - * - * @param possibleDate - * the string to check for date formats - * @return whether the given string matches a known date format - */ - public static boolean isDatePattern(String possibleDate) { - if (possibleDate.indexOf("/") == -1) - return false; - StringTokenizer st = new StringTokenizer(possibleDate, "/"); - if (st.countTokens() == 3) - return true; - return false; - } - - /** - * Converts a string representation of a date into a valid calendar object - * date must be in format mm/dd/yy (or yyyy) - * - * - * @param dateStr - * @return null if not a valid date - */ - public static Calendar convertStringToCalendar(String dateStr) { - if (!isDatePattern(dateStr)) - return null; - int m, d, y; - StringTokenizer st = new StringTokenizer(dateStr, "/"); - try { - m = Integer.valueOf(st.nextToken()).intValue() - 1; - d = Integer.valueOf(st.nextToken()).intValue(); - String s = st.nextToken(); - int h, mn, sc; - h = mn = sc = 0; - if (s.indexOf(" ") > -1) { - int i = s.indexOf(" "); - String time = s.substring(i + 1); - s = s.substring(0, i); // rest of date - String[] timetokens = time.split(":"); - if (timetokens.length > 0) - h = Integer.valueOf(timetokens[0]).intValue(); - if (timetokens.length > 1) - mn = Integer.valueOf(timetokens[1]).intValue(); - if (timetokens.length > 2) - sc = Integer.valueOf(timetokens[2]).intValue(); - } - y = Integer.valueOf(s).intValue(); - if (y < 100) - y += 2000; - GregorianCalendar cdr = new GregorianCalendar(y, m, d, h, mn, sc); - return cdr; - } catch (Exception e) { - return null; - } - } - - /** - * Returns the value of the cell as a date formatted as a String date - * representation. The format is determined by inspecting the excel format. - * - * Currently supported formats in this method are: mm/dd/yy dd-mmm-yy dd-mmm - * mmm-yy mm/dd/yy hh:mm - * - * If the cell's date forrmat pattern falls outside of this range, the - * default output will be in the following format mm/dd/yyyy - * - * @deprecated The date format handling in this method is wildly incorrect. - * It is retained only to provide compatibility with legacy - * OpenXLS XML files. - * @return String, the value of a cell formatted as a date - */ - public static String getFormattedDateVal(CellHandle cell) { + /** + * The number of milliseconds in a day. + */ + private static final int MILLIS_DAY = 86400000; + + /** + * The extra day caused by the 1900 leap year bug. + */ + private static final int EXTRA_DAY = 60; + + /** + * Calendar used for date calculation. + */ + private static Calendar calendar = Calendar.getInstance(); + + /** + * Whether to validate input dates for Excel compatibility. + */ + private static boolean validate = false; + + /** + * The set of supported serial date encoding schemes. + */ + public enum DateFormat { + /** + * 1900 epoch with negative value support as used in OOXML. + *

        + * Lower limit: -9999/01/01 00:00:00, value -4 346 018
        + * Epoch date: 1899/12/30 00:00:00, value 0
        + * Upper limit: 9999/12/31 23:59:59, value 2 958 465.999 988 4
        + */ + OOXML_1900(25569, -4346018, 2958465.9999884), + + /** + * 1900 epoch without negative value support as used in BIFF8. + *

        + * Epoch date: 1899/12/31 00:00:00, value 0
        + * Lower limit: 1900/01/01 00:00:00, value 1
        + * Upper limit: 9999/12/31 23:59:59, value 2 958 465.999 988 4
        + *

        + * In this system 1900 is (incorrectly) considered a leap year. Serial + * dates after 1900/02/28 are one higher than they otherwise should be + * and the value 60 is unmapped. + */ + LEGACY_1900(25568, 1, 2958465.9999884), + + /** + * 1904 epoch without negative value support as used by Excel for Mac. + *

        + * Epoch date: 1904/01/01 00:00:00, value 0
        + * Lower limit: 1904/01/01 00:00:00, value 0
        + * Upper limit: 9999/12/31 23:59:59, value 2 957 003.999 988 4
        + */ + LEGACY_1904(24107, 0, 2957003.9999884); + + private final int epoch_delta; + private final double limit_lower, limit_upper; + + DateFormat(int delta, double min, double max) { + epoch_delta = delta; + limit_lower = min; + limit_upper = max; + } + + protected int getEpochDelta() { + return epoch_delta; + } + + public double getLowerLimit() { + return limit_lower; + } + + public double getUpperLimit() { + return limit_upper; + } + } + + /** + * Gets a clone of the calendar used for date calculation. + */ + public static Calendar getCalendar() { + return (Calendar) calendar.clone(); + } + + /** + * Sets the calendar used to perform date calculation. This allows you to + * set the locale and time zone to something other than the system default. + * + * @param cal the calendar that should be used for date calculations + */ + public static void setCalendar(Calendar cal) { + calendar = cal; + } + + /** + * Returns whether input validation is on. + */ + public static boolean getValidate() { + return validate; + } + + /** + * Sets whether to perform input validation. + */ + public static void setValidate(boolean validate) { + DateConverter.validate = validate; + } + + /** + * returns whether this method will work with your input string + */ + public static boolean isParseableDateString(String str) { + // fix problem with timestamps tagged on end + if (str.indexOf(" ") > 0) { + str = str.substring(0, str.indexOf(" ")); + } + try { + java.sql.Date d1 = java.sql.Date.valueOf(str); + getXLSDateVal(d1); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * attempt to interpret a date string into a date + * returns null if cannot be converted to date + */ + public static Date getDate(String str) { + try { + java.sql.Timestamp d1 = java.sql.Timestamp.valueOf(str); + Calendar cal = (Calendar) calendar.clone(); + cal.setTime(d1); + return cal.getTime(); + } catch (Exception e) { + } + return null; + + } + + /** + * Converts the value of the given Calendar to an Excel serial date. + * The date will be returned in the + * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. + * + * @param cal the Calendar to convert + * @return the Excel serial date representing the given calendar's value in + * the calendar's time zone + * @deprecated Use {@link #getXLSDateVal(Calendar, DateFormat)} instead. + */ + public static double getXLSDateVal(Calendar cal) { + return getXLSDateVal(cal, DateFormat.LEGACY_1900); + } + + /** + * Converts the value of the given Calendar to an Excel serial date. + * + * @param cal the Calendar to convert + * @param format the serial date format to use + * @return the Excel serial date representing the given calendar's value in + * the calendar's time zone + */ + public static double getXLSDateVal(Calendar cal, DateFormat format) { + // Get the UTC milliseconds since the epoch + long millis = cal.getTimeInMillis(); + + // Add the GMT offset and daylight savings offset for the time zone + millis += cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET); + + // Convert from milliseconds to days + double days = (double) millis / MILLIS_DAY; + + // Switch from UNIX epoch to the Excel epoch + days += format.getEpochDelta(); + + // If the date is after February 28, 1900 add one day. + // This compensates for Excel incorrectly treating 1900 as a leap year + if (format == DateFormat.LEGACY_1900 && days >= EXTRA_DAY) + days += 1; + + // Perform validation + if (validate && (days < format.getLowerLimit() + || days > format.getUpperLimit())) + throw new IllegalArgumentException( + "the given date is not supported by Excel"); + + // Return the Excel serial date value + return days; + } + + /** + * Converts the given Date to an Excel serial date in the default time zone. + * + * @param date the date to be converted + * @param format the serial date format to use + * @return the Excel serial date value corresponding to the given date + */ + public static double getXLSDateVal(Date date, DateFormat format) { + Calendar cal = (Calendar) calendar.clone(); + cal.setTime(date); + return getXLSDateVal(cal, format); + } + + /** + * Converts the given Date to an Excel serial date in the default time zone. + * The date will be returned in the + * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. + * + * @param date the date to be converted + * @return the Excel serial date value corresponding to the given date + * @deprecated Use {@link #getXLSDateVal(Date, DateFormat)} instead. + */ + public static double getXLSDateVal(Date date) { + return getXLSDateVal(date, DateFormat.LEGACY_1900); + } + + /** + * Parses the the given Excel serial date and returns a Calendar. + * The date will be interpreted in the + * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. + * + * @param date the Excel serial date to be interpreted + * @return a Calendar representing the given Excel date interpreted in the + * default time zone + * @deprecated Use {@link #getCalendarFromNumber(double, DateFormat)} instead. + */ + public static Calendar getCalendarFromNumber(double date) { + return getCalendarFromNumber(date, DateFormat.LEGACY_1900); + } + + /** + * Parses the the given Excel serial date and returns a Calendar. + * + * @param date the Excel serial date to be interpreted + * @param format the date format with which to interpret the serial date + * @return a Calendar representing the given Excel date interpreted in the + * default time zone + */ + public static Calendar getCalendarFromNumber(double date, DateFormat format) { + Calendar cal = getCalendar(); + double days = date; + + // For the legacy 1900 epoch, if the date is after 1900/02/28 subtract + // one day. This compensates for 1900 being considered a leap year. + // Not matching the non-existent February 29 causes it to become + // March 1. This behavior matches that of Calendar for the same input. + if (format == DateFormat.LEGACY_1900 && days > EXTRA_DAY) + days -= 1; + + // Switch from the Excel epoch to the UNIX epoch + days -= format.getEpochDelta(); + + // Convert from days to milliseconds + long millis = Math.round(days * MILLIS_DAY); + + // Set the calendar's approximate time so zone offsets are correct + // The offsets can still be wrong for certain border cases. + cal.setTimeInMillis(millis); + + // Adjust for time zone and daylight saving time offsets + long offset = 0; + for (int count = 0; offset != (offset = cal.get(Calendar.ZONE_OFFSET) + + cal.get(Calendar.DST_OFFSET)) + && count < 3; count++) + cal.setTimeInMillis(millis - offset); + + return cal; + } + + /** + * Parses the the given Excel serial date and returns a Date. + * The date will be interpreted in the + * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. + * + * @param date the Excel serial date to be interpreted + * @param format the date format with which to interpret the serial date + * @return a Calendar representing the given Excel date interpreted in the + * default time zone + * @deprecated Use {@link #getCalendarFromNumber(double, DateFormat)} instead. + */ + public static Date getDateFromNumber(double date) { + return getCalendarFromNumber(date).getTime(); + } + + /** + * Gets the Date for the given Excel serial date. + * + * @param number a Number representing the serial date to be interpreted + * @return a Date representing the given Excel date interpreted in the + * default time zone + * @throws ClassCastException if the passed object is not a Number + * @deprecated Use {@link #getDateFromNumber(double)} instead. + */ + public static Date getDateFromNumber(Object number) { + if (number instanceof Number) + return getDateFromNumber(((Number) number).doubleValue()); + + else + throw new ClassCastException("passed object was not a number"); + } + + /** + * Gets the Date for the given Excel serial date. + * + * @param number a Number representing the serial date to be interpreted + * @return a Date representing the given Excel date interpreted in the + * default time zone + * @throws ClassCastException if the passed object is not a Number + * @deprecated Identical to {@link #getDateFromNumber(Object)}. + */ + public static Date getNonLocalizedDateFromNumber(Object number) { + return getDateFromNumber(number); + } + + /** + * Gets the Calendar for the given Excel serial date. + * + * @param date the Excel serial date or datestring to be interpreted + * @return a Calendar representing the given Excel date interpreted in the + * default time zone + * @throws ClassCastException if the passed object is not a Number + * @deprecated Use {@link #getCalendarFromNumber(double)} instead. + */ + public static Calendar getCalendarFromNumber(Object number) { + if (number == null) + throw new ClassCastException("object cannot be converted to a date"); + if (!(number instanceof Number)) + number = new Double(number.toString()); + + if (number instanceof Number) + return getCalendarFromNumber(((Number) number).doubleValue()); + + else + throw new ClassCastException("passed object was not a number"); + } + + /** + * Gets the Date for the given Excel serial date. + * + * @param number a Number representing the serial date to be interpreted + * @return a Date representing the given Excel date interpreted in the + * default time zone + * @throws ClassCastException if the passed object is not a Number + * @deprecated Identical to {@link #getDateFromNumber(Object)}. + */ + public static Calendar getNonLocalizedCalendarFromNumber(Object number) { + return getCalendarFromNumber(number); + } + + /** + * Gets the Date for the given cell. + * + * @param cell a CellHandle whose value should be interpreted + * @return a Date representing the given Excel date interpreted in the + * default time zone + */ + public static Date getDateFromCell(CellHandle cell) { + return getCalendarFromCell(cell).getTime(); + } + + /** + * returns a Java Calendar from a CellHandle containing an Excel-formatted + * Date + *

        + * The Excel date format does not map 100% accurately to Java dates, due to + * the limitation of the precision of the Excel floating-point value record. + * Due to this, OpenXLS dates may be too precise, this method will round + * the java.util.Date returned to the precision entered. Rounding is handled + * by the ROUND_HALF_UP method. + *

        + * Pass in a static Calendar precision, options are Calendar.HOUR + * Calendar.MINUTE Calendar.SECOND Calendar.MILLISECOND + * + * @return Calendar - A GregorianCalendar value of the Cell + */ + public static Calendar getCalendarFromCellWithPrecision(Cell cell, + int roundingCalendarField) { + Calendar tmp = getCalendarFromCell(cell); + GregorianCalendar newCalendar = new GregorianCalendar(); + newCalendar.set(Calendar.YEAR, tmp.get(Calendar.YEAR)); + newCalendar.set(Calendar.MONTH, tmp.get(Calendar.MONTH)); + newCalendar.set(Calendar.DAY_OF_MONTH, tmp.get(Calendar.DAY_OF_MONTH)); + newCalendar.set(Calendar.HOUR_OF_DAY, tmp.get(Calendar.HOUR_OF_DAY)); + if (roundingCalendarField == Calendar.HOUR) { + if (tmp.get(Calendar.MINUTE) > 29) { + newCalendar.set(Calendar.HOUR_OF_DAY, + tmp.get(Calendar.HOUR_OF_DAY) + 1); + } + newCalendar.set(Calendar.MINUTE, 0); + newCalendar.set(Calendar.SECOND, 0); + newCalendar.set(Calendar.MILLISECOND, 0); + return newCalendar; + } + newCalendar.set(Calendar.MINUTE, tmp.get(Calendar.MINUTE)); + if (roundingCalendarField == Calendar.MINUTE) { + if (tmp.get(Calendar.SECOND) > 29) { + newCalendar.set(Calendar.MINUTE, tmp.get(Calendar.MINUTE) + 1); + } + newCalendar.set(Calendar.SECOND, 0); + newCalendar.set(Calendar.MILLISECOND, 0); + return newCalendar; + } + newCalendar.set(Calendar.SECOND, tmp.get(Calendar.SECOND)); + if (roundingCalendarField == Calendar.SECOND) { + if (tmp.get(Calendar.MILLISECOND) > 499) { + newCalendar.set(Calendar.SECOND, tmp.get(Calendar.SECOND) + 1); + } + newCalendar.set(Calendar.MILLISECOND, 0); + return newCalendar; + } + // round the milliseconds + newCalendar.set(Calendar.MILLISECOND, tmp.get(Calendar.MILLISECOND)); + return newCalendar; + + } + + /** + * Gets the Calendar for the given cell. + * + * @param cell a CellHandle whose value should be interpreted + * @return a Calendar representing the given Excel date interpreted in the + * default time zone + */ + @SuppressWarnings("deprecation") + public static Calendar getCalendarFromCell(Cell cell) { + double value; + DateFormat format = DateFormat.LEGACY_1900; + + if (cell instanceof CellHandle) { + CellHandle realCell = (CellHandle) cell; + value = realCell.getDoubleVal(); + + WorkBook book = realCell.getWorkBook(); + if (null != book) { + format = book.getDateFormat(); + } + } else { + value = Double.parseDouble(cell.getVal().toString()); + } + + return getCalendarFromNumber(value, format); + } + + /** + * Inspects a string to determine if it is a date. Looks for a pattern such + * as mm/dd/yy, m/d/yy, mm/dd/yyyy etc. + * + * @param possibleDate the string to check for date formats + * @return whether the given string matches a known date format + */ + public static boolean isDatePattern(String possibleDate) { + if (possibleDate.indexOf("/") == -1) + return false; + StringTokenizer st = new StringTokenizer(possibleDate, "/"); + return st.countTokens() == 3; + } + + /** + * Converts a string representation of a date into a valid calendar object + * date must be in format mm/dd/yy (or yyyy) + * + * @param dateStr + * @return null if not a valid date + */ + public static Calendar convertStringToCalendar(String dateStr) { + if (!isDatePattern(dateStr)) + return null; + int m, d, y; + StringTokenizer st = new StringTokenizer(dateStr, "/"); + try { + m = Integer.valueOf(st.nextToken()).intValue() - 1; + d = Integer.valueOf(st.nextToken()).intValue(); + String s = st.nextToken(); + int h, mn, sc; + h = mn = sc = 0; + if (s.indexOf(" ") > -1) { + int i = s.indexOf(" "); + String time = s.substring(i + 1); + s = s.substring(0, i); // rest of date + String[] timetokens = time.split(":"); + if (timetokens.length > 0) + h = Integer.valueOf(timetokens[0]).intValue(); + if (timetokens.length > 1) + mn = Integer.valueOf(timetokens[1]).intValue(); + if (timetokens.length > 2) + sc = Integer.valueOf(timetokens[2]).intValue(); + } + y = Integer.valueOf(s).intValue(); + if (y < 100) + y += 2000; + GregorianCalendar cdr = new GregorianCalendar(y, m, d, h, mn, sc); + return cdr; + } catch (Exception e) { + return null; + } + } + + /** + * Returns the value of the cell as a date formatted as a String date + * representation. The format is determined by inspecting the excel format. + *

        + * Currently supported formats in this method are: mm/dd/yy dd-mmm-yy dd-mmm + * mmm-yy mm/dd/yy hh:mm + *

        + * If the cell's date forrmat pattern falls outside of this range, the + * default output will be in the following format mm/dd/yyyy + * + * @return String, the value of a cell formatted as a date + * @deprecated The date format handling in this method is wildly incorrect. + * It is retained only to provide compatibility with legacy + * OpenXLS XML files. + */ + public static String getFormattedDateVal(CellHandle cell) { // assemble the formatting for the javascript. - if (!cell.isDate())return "Not a Date"; + if (!cell.isDate()) return "Not a Date"; if (Double.isNaN(cell.getDoubleVal())) - return ""; // 20060623 KSC + return ""; // 20060623 KSC Date cal = DateConverter.getDateFromCell(cell); FormatHandle f = cell.getFormatHandle(); int pat = f.getFormatPatternId(); // SimpleDateFormat sdf = new SimpleDateFormat(); KSC: reuse - - switch(pat) { - - case 0xe: - WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy"); - return WorkBookHandle.simpledateformat.format(cal); - - case 0xf: - WorkBookHandle.simpledateformat.applyPattern("dd-MMM-yy"); - return WorkBookHandle.simpledateformat.format(cal); - - case 0x10: - WorkBookHandle.simpledateformat.applyPattern("dd-MMM"); - return WorkBookHandle.simpledateformat.format(cal); - - case 0x11: - WorkBookHandle.simpledateformat.applyPattern("MMM-yy"); - return WorkBookHandle.simpledateformat.format(cal); - - case 0x16: - WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy HH:mm"); - return WorkBookHandle.simpledateformat.format(cal); - - default: - WorkBookHandle.simpledateformat.applyPattern("MM/dd/yyyy"); - return WorkBookHandle.simpledateformat.format(cal); - - } + + switch (pat) { + + case 0xe: + WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy"); + return WorkBookHandle.simpledateformat.format(cal); + + case 0xf: + WorkBookHandle.simpledateformat.applyPattern("dd-MMM-yy"); + return WorkBookHandle.simpledateformat.format(cal); + + case 0x10: + WorkBookHandle.simpledateformat.applyPattern("dd-MMM"); + return WorkBookHandle.simpledateformat.format(cal); + + case 0x11: + WorkBookHandle.simpledateformat.applyPattern("MMM-yy"); + return WorkBookHandle.simpledateformat.format(cal); + + case 0x16: + WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy HH:mm"); + return WorkBookHandle.simpledateformat.format(cal); + + default: + WorkBookHandle.simpledateformat.applyPattern("MM/dd/yyyy"); + return WorkBookHandle.simpledateformat.format(cal); + + } + } + + /** + * @deprecated The date format handling in this method is wildly incorrect. + * It is retained only to provide compatibility with legacy + * OpenXLS XML files. + */ + public static Date parseDate(String s, int pat) { + if (s.equals("")) + return null; + //SimpleDateFormat sdf = new SimpleDateFormat(); + try { + switch (pat) { + case 0xe: + WorkBookHandle.simpledateformat.applyPattern("dd/MM/yy"); + return WorkBookHandle.simpledateformat.parse(s); + + case 0xf: + WorkBookHandle.simpledateformat.applyPattern("dd-MMM-yy"); + return WorkBookHandle.simpledateformat.parse(s); + + case 0x10: + WorkBookHandle.simpledateformat.applyPattern("dd-MMM"); + return WorkBookHandle.simpledateformat.parse(s); + + case 0x11: + WorkBookHandle.simpledateformat.applyPattern("MMM-yy"); + return WorkBookHandle.simpledateformat.parse(s); + + case 0x16: + WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy HH:mm"); + return WorkBookHandle.simpledateformat.parse(s); + + default: + WorkBookHandle.simpledateformat.applyPattern("dd/MM/yyyy"); + return WorkBookHandle.simpledateformat.parse(s); + + } + } catch (Exception e) { + Logger.logWarn("Failed to parse date " + s + " format pattern: " + + pat); + return Calendar.getInstance().getTime(); + } + } + + /** + * DATEVALUE + * Returns the serial number of the date represented by date_text. Use DATEVALUE to convert a date represented by text to a serial number. + *

        + * Syntax + * DATEVALUE(date_text) + *

        + * Date_text is text that represents a date in a Microsoft Excel date format. For example, "1/30/2008" or "30-Jan-2008" are text strings + * within quotation marks that represent dates. Using the default date system in Excel for Windows, + * date_text must represent a date from January 1, 1900, to December 31, 9999. Using the default date system in Excel for the Macintosh, + * date_text must represent a date from January 1, 1904, to December 31, 9999. DATEVALUE returns the #VALUE! error value if date_text is out of this range. + *

        + * If the year portion of date_text is omitted, DATEVALUE uses the current year from your computer's built-in clock. Time information in date_text is ignored. + *

        + * Remarks + *

        + * Excel stores dates as sequential serial numbers so they can be used in calculations. By default, January 1, 1900 is serial number 1, and January 1, 2008 is serial number 39448 because it is 39,448 days after January 1, 1900. Excel for the Macintosh uses a different date system as its default. + * Most functions automatically convert date values to serial numbers. + * + * @param String dateString + * @return + */ + + public static Double calcDateValue(String dateString) { + String[] formats = {"MM/dd/yyyy HH:mm:ss", + "MM/dd/yy HH:mm:ss", + "MM/dd/yy", + "MM/dd/yyyy", + "MM/d/yyyy HH:mm:ss", + "MM/d/yy HH:mm:ss", + "yy-M-d hh:mm:ss a", + "yy-M-d HH:mm:ss", + "yy-M-d hh:mm a", + "yy-M-d HH:mm", + "dd-M-yyyy hh:mm:ss a", // 10 + "dd-M-yyyy HH:mm:ss", + "dd-M-yyyy hh:mm a", + "dd-M-yyyy HH:mm", + "dd-MMM-yy hh:mm:ss a", + "dd-MMM-yy HH:mm:ss", + "dd-MMM-yy hh:mm a", + "dd-MMM-yy HH:mm", + "yyMMdd", + "MM/d/yy", + "MM/d/yyyy", // 20 + "yyyy/MM/dd", + "d-MMM-yy", + "d-M-yy", + "d-M-yyyy", + "dd-MMM-yy", + "dd-MMM-yyyy", + "dd-M-yyyy", + "dd-MM-yy", + "dd-MM-yyyy", + "d-MMM-yyyy", // 30 + "d-M-yyyy", + "d-MMM-yyyy", // 32 really is d-MMM but inorder to match, must append year + "d/MMM/yyyy", // 33 really is d/MMM but in order to match, must append year + "M-yy", + "MMM-yy", + "M/d/yyyy", + "M d, yyyy", + "yyyy-MM-dd", + "yy-M-d", + "yyyy-MM-dd", + "MMyy", + "yyMM", + "yyyyMMddHHmm", + "yyyyMMddHHmmss", + "yyMMddHHmmss", + "MMDDHHmm", + "MMMM dd, yyyy", + "E, MMM d, yyyy", + "E MMM dd, yyyy", + "EE, MMM dd, yyyy", + "E, MMMM d, yyyy", + "E, MMMM dd, yyyy", + "EE, MMMM dd, yyyy", + "hh:mm:ss a", + "HH:mm:ss", + "hh:mm a", + "HH:mm", + }; + for (int i = 0; i < formats.length; i++) { + try { + String ds = dateString; + if (i == 32 || i == 33) { // then must add year to date + GregorianCalendar calendar = new GregorianCalendar(); + int curyear = calendar.get(Calendar.YEAR); + if (i == 32) ds = dateString + "-" + curyear; + else ds = dateString + "/" + curyear; + } + //SimpleDateFormat format= new SimpleDateFormat(formats[i], Locale.ENGLISH); // 20090701 KSC: apparently need Locale -- why now though?? + //format.setLenient(false); + WorkBookHandle.simpledateformat.applyLocalizedPattern(formats[i]); + WorkBookHandle.simpledateformat.setLenient(false); + //Date d= format.parse(ds); + Date d = WorkBookHandle.simpledateformat.parse(ds); + return DateConverter.getXLSDateVal(d); + } catch (Exception e) { + } + } + return null; } - /** - * @deprecated The date format handling in this method is wildly incorrect. - * It is retained only to provide compatibility with legacy - * OpenXLS XML files. - */ - public static Date parseDate(String s, int pat) { - if (s.equals("")) - return null; - //SimpleDateFormat sdf = new SimpleDateFormat(); - try { - switch (pat) { - case 0xe: - WorkBookHandle.simpledateformat.applyPattern("dd/MM/yy"); - return WorkBookHandle.simpledateformat.parse(s); - - case 0xf: - WorkBookHandle.simpledateformat.applyPattern("dd-MMM-yy"); - return WorkBookHandle.simpledateformat.parse(s); - - case 0x10: - WorkBookHandle.simpledateformat.applyPattern("dd-MMM"); - return WorkBookHandle.simpledateformat.parse(s); - - case 0x11: - WorkBookHandle.simpledateformat.applyPattern("MMM-yy"); - return WorkBookHandle.simpledateformat.parse(s); - - case 0x16: - WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy HH:mm"); - return WorkBookHandle.simpledateformat.parse(s); - - default: - WorkBookHandle.simpledateformat.applyPattern("dd/MM/yyyy"); - return WorkBookHandle.simpledateformat.parse(s); - - } - } catch (Exception e) { - Logger.logWarn("Failed to parse date " + s + " format pattern: " - + pat); - return Calendar.getInstance().getTime(); - } - } - /** - * DATEVALUE - Returns the serial number of the date represented by date_text. Use DATEVALUE to convert a date represented by text to a serial number. - - Syntax - DATEVALUE(date_text) - - Date_text is text that represents a date in a Microsoft Excel date format. For example, "1/30/2008" or "30-Jan-2008" are text strings - within quotation marks that represent dates. Using the default date system in Excel for Windows, - date_text must represent a date from January 1, 1900, to December 31, 9999. Using the default date system in Excel for the Macintosh, - date_text must represent a date from January 1, 1904, to December 31, 9999. DATEVALUE returns the #VALUE! error value if date_text is out of this range. - - If the year portion of date_text is omitted, DATEVALUE uses the current year from your computer's built-in clock. Time information in date_text is ignored. - - Remarks - - Excel stores dates as sequential serial numbers so they can be used in calculations. By default, January 1, 1900 is serial number 1, and January 1, 2008 is serial number 39448 because it is 39,448 days after January 1, 1900. Excel for the Macintosh uses a different date system as its default. - Most functions automatically convert date values to serial numbers. - - * @param String dateString - * @return - */ - - public static Double calcDateValue(String dateString) { - String[] formats= { "MM/dd/yyyy HH:mm:ss", - "MM/dd/yy HH:mm:ss", - "MM/dd/yy", - "MM/dd/yyyy", - "MM/d/yyyy HH:mm:ss", - "MM/d/yy HH:mm:ss", - "yy-M-d hh:mm:ss a", - "yy-M-d HH:mm:ss", - "yy-M-d hh:mm a", - "yy-M-d HH:mm", - "dd-M-yyyy hh:mm:ss a", // 10 - "dd-M-yyyy HH:mm:ss", - "dd-M-yyyy hh:mm a", - "dd-M-yyyy HH:mm", - "dd-MMM-yy hh:mm:ss a", - "dd-MMM-yy HH:mm:ss", - "dd-MMM-yy hh:mm a", - "dd-MMM-yy HH:mm", - "yyMMdd", - "MM/d/yy", - "MM/d/yyyy", // 20 - "yyyy/MM/dd", - "d-MMM-yy", - "d-M-yy", - "d-M-yyyy", - "dd-MMM-yy", - "dd-MMM-yyyy", - "dd-M-yyyy", - "dd-MM-yy", - "dd-MM-yyyy", - "d-MMM-yyyy", // 30 - "d-M-yyyy", - "d-MMM-yyyy", // 32 really is d-MMM but inorder to match, must append year - "d/MMM/yyyy", // 33 really is d/MMM but in order to match, must append year - "M-yy" , - "MMM-yy", - "M/d/yyyy", - "M d, yyyy", - "yyyy-MM-dd", - "yy-M-d", - "yyyy-MM-dd", - "MMyy", - "yyMM", - "yyyyMMddHHmm", - "yyyyMMddHHmmss", - "yyMMddHHmmss", - "MMDDHHmm", - "MMMM dd, yyyy", - "E, MMM d, yyyy", - "E MMM dd, yyyy", - "EE, MMM dd, yyyy", - "E, MMMM d, yyyy", - "E, MMMM dd, yyyy", - "EE, MMMM dd, yyyy", - "hh:mm:ss a", - "HH:mm:ss", - "hh:mm a", - "HH:mm", - }; - for (int i= 0; i < formats.length; i++) { - try { - String ds= dateString; - if (i==32 || i==33) { // then must add year to date - GregorianCalendar calendar = new GregorianCalendar(); - int curyear = calendar.get(Calendar.YEAR); - if (i==32) ds= dateString + "-" + curyear; - else ds= dateString + "/" + curyear; - } - //SimpleDateFormat format= new SimpleDateFormat(formats[i], Locale.ENGLISH); // 20090701 KSC: apparently need Locale -- why now though?? - //format.setLenient(false); - WorkBookHandle.simpledateformat.applyLocalizedPattern(formats[i]); - WorkBookHandle.simpledateformat.setLenient(false); - //Date d= format.parse(ds); - Date d= WorkBookHandle.simpledateformat.parse(ds); - return DateConverter.getXLSDateVal(d); - } catch (Exception e) { } - } - return null; - } - } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/Document.java b/src/main/java/io/starter/OpenXLS/Document.java index 04192a0..ae5a459 100644 --- a/src/main/java/io/starter/OpenXLS/Document.java +++ b/src/main/java/io/starter/OpenXLS/Document.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,100 +29,114 @@ */ package io.starter.OpenXLS; +import io.starter.formats.XLS.XLSConstants; + import java.io.File; import java.io.IOException; import java.io.OutputStream; -import io.starter.formats.XLS.XLSConstants; - -/** An interface representing an OpenXLS document. - * This interface provides no functionality beyond the abstract - * {@link DocumentHandle} class. Use that type instead. +/** + * An interface representing an OpenXLS document. + * This interface provides no functionality beyond the abstract + * {@link DocumentHandle} class. Use that type instead. */ public interface Document { - public static final int DEBUG_LOW = XLSConstants.DEBUG_LOW; - public static final int DEBUG_MEDIUM = XLSConstants.DEBUG_MEDIUM; - public static final int DEBUG_HIGH = XLSConstants.DEBUG_HIGH; + int DEBUG_LOW = XLSConstants.DEBUG_LOW; + int DEBUG_MEDIUM = XLSConstants.DEBUG_MEDIUM; + int DEBUG_HIGH = XLSConstants.DEBUG_HIGH; - /** get a non-Excel property - * @return Returns the properties. - */ - public Object getProperty(String name); + /** + * get a non-Excel property + * + * @return Returns the properties. + */ + Object getProperty(String name); - /** add non-Excel property - * @param properties The properties to set. - */ - public void addProperty(String name, Object val); + /** + * add non-Excel property + * + * @param properties The properties to set. + */ + void addProperty(String name, Object val); - /** The Session for the WorkBook instance - * - * @return - - public BookSession getSession(); - */ + /** The Session for the WorkBook instance + * + * @return public BookSession getSession(); + */ - /** Sets the internal name of this WorkBookHandle. - * - * Overrides the default for 'getName()' which returns - * the file name source of this WorkBook by default. - * - * @param WorkBook Name - */ - public abstract void setName(String nm); + /** + * Sets the internal name of this WorkBookHandle. + *

        + * Overrides the default for 'getName()' which returns + * the file name source of this WorkBook by default. + * + * @param WorkBook Name + */ + void setName(String nm); - /** Set the Debugging level. Higher values output more - debugging info during execution. - - @parameter int Debug level. higher=more verbose - */ - public abstract void setDebugLevel(int l); + /** + * Set the Debugging level. Higher values output more + * debugging info during execution. + * + * @parameter int Debug level. higher=more verbose + */ + void setDebugLevel(int l); - /** Returns the name of this WorkBook - - @return String name of WorkBook - */ - public abstract String getName(); + /** + * Returns the name of this WorkBook + * + * @return String name of WorkBook + */ + String getName(); - /** Clears all values in a template WorkBook. - - Use this method to 'reset' the values of your - WorkBook in memory to defaults. - - For example, if you load a Servlet with a - single WorkBookHandle instance, then modify - values and stream to a Client system, yo - should call 'clearAll()' when the request - is completed to remove the modified values - and set them back to a default. - - */ - public abstract void reset(); + /** + * Clears all values in a template WorkBook. + *

        + * Use this method to 'reset' the values of your + * WorkBook in memory to defaults. + *

        + * For example, if you load a Servlet with a + * single WorkBookHandle instance, then modify + * values and stream to a Client system, yo + * should call 'clearAll()' when the request + * is completed to remove the modified values + * and set them back to a default. + */ + void reset(); - /** Writes the document to the given stream in the requested format. - * @param dest the stream to which the document should be written - * @param format the constant representing the desired output format - * @throws IllegalArgumentException if the given type code is invalid - * @throws IOException if an error occurs while writing to the stream - */ - public void write(OutputStream dest, int format) throws IOException; + /** + * Writes the document to the given stream in the requested format. + * + * @param dest the stream to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the stream + */ + void write(OutputStream dest, int format) throws IOException; - /** Writes the document to the given stream in its native format. - * @param dest the stream to which the document should be written - * @throws IOException if an error occurs while writing to the stream - */ - public void write(OutputStream dest) throws IOException; + /** + * Writes the document to the given stream in its native format. + * + * @param dest the stream to which the document should be written + * @throws IOException if an error occurs while writing to the stream + */ + void write(OutputStream dest) throws IOException; - /** Writes the document to the given file in the requested format. - * @param file the path to which the document should be written - * @param format the constant representing the desired output format - * @throws IllegalArgumentException if the given type code is invalid - * @throws IOException if an error occurs while writing to the file - */ - public void write(File file, int format) throws IOException; + /** + * Writes the document to the given file in the requested format. + * + * @param file the path to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the file + */ + void write(File file, int format) throws IOException; - /** Writes the document to the given file in its native format. - * @param file the path to which the document should be written - * @throws IOException if an error occurs while writing to the stream - */ - public void write(File file) throws IOException; + /** + * Writes the document to the given file in its native format. + * + * @param file the path to which the document should be written + * @throws IOException if an error occurs while writing to the stream + */ + void write(File file) throws IOException; } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/DocumentHandle.java b/src/main/java/io/starter/OpenXLS/DocumentHandle.java index 2c7de12..0eca8fe 100644 --- a/src/main/java/io/starter/OpenXLS/DocumentHandle.java +++ b/src/main/java/io/starter/OpenXLS/DocumentHandle.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,385 +29,414 @@ */ package io.starter.OpenXLS; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.Closeable; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; +import io.starter.formats.LEO.LEOFile; +import io.starter.formats.XLS.OOXMLAdapter; +import io.starter.toolkit.Logger; +import io.starter.toolkit.TempFileManager; + +import java.io.*; import java.net.URL; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import io.starter.formats.LEO.LEOFile; -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.toolkit.Logger; -import io.starter.toolkit.TempFileManager; - -/** Functionality common to all document types. +/** + * Functionality common to all document types. */ public abstract class DocumentHandle implements Document, Handle, Closeable { - public static String workingdir = System.getProperty("user.dir") - + "/tmp"; - - /** Format constant for the most appropriate format for this document. - * If the document was read in from a file, this is usually the format that - * was read in. - */ - public static final int FORMAT_NATIVE = 0; - - /** The level of debugging output requested by the user. - * Higher values should produce more output. - */ - // TODO: should the debug level be static? - protected int DEBUGLEVEL = 0; - - /** The user-visible display name or title of this document. */ - protected String name = null; - - /** The file associated with this document. - * This will generally be the file the document was parsed from, if any. - */ - protected File file; - - /** Store for workbook properties. */ - private Map props = new HashMap(); - - /** Handling for a streaming worksheet based workbook **/ - private boolean streamingSheets = false; - - /** - * default constructor - */ - public DocumentHandle() { - super(); - - } - - /** - * Apr 5, 2011 - * @param urlx - */ - public DocumentHandle(InputStream urlx) { - super(); - // TODO Auto-generated constructor stub - Logger.logErr("DocumentHandle InputStream Constructor Not Implemented"); - } - - public DocumentHandle(File input) { - // TODO Auto-generated constructor stub - } - - /** Retrieves a property in the workbook property store. - * This is not an Excel-compatible feature. - * - * @param name the name of the property to retrieve - * @return the value of the requested property or null if it doesn't exist - */ - @Override - public Object getProperty(String name) { - return props.get(name); - } - - /** Sets the value of a property in the workbook property store. - * This is not an Excel-compatible feature. - * - * @param name the name of the property which should be updated - * @param value the value to which the property should be set - */ - @Override - public void addProperty(String name, Object val) { - props.put(name, val); - } - - /** Retrieves a Map containing the workbook properties store. - * This is not an Excel-compatible feature. - * @return an immutable Map containing the current workbook properties - */ - public Map getProperties() { - return Collections.unmodifiableMap(props); - } - - /** Replaces the workbook properties with the values in a given Map. - * This is not an Excel-compatible feature. - * - * @param properties the values that will replace the existing properties - */ - public void setProperties(Map properties) { - props = new HashMap(); - props.putAll(properties); - } - - /** Gets the OpenXLS version number. - */ - public static String getVersion() { - return GetInfo.getVersion(); - } - - /** Sets the user-visible descriptive name or title of this document. - * Some formats will persist this setting in the document itself. - */ - @Override - public void setName(String nm) { - name = nm; - } - - /** Handling for streaming sheets. Currently this is in development and unsupported - * @param streamSheets - */ - public void setStreamingSheets(boolean streamSheets) { - this.streamingSheets = streamSheets; - } - - /** Sets the file name associated with this document. - * @deprecated Use {@link #setFile(File)} instead. - */ - @Deprecated - public void setFileName(String name) { - file = new File(name).getAbsoluteFile(); - } - - /** Sets the file associated with this document. - */ - public void setFile(File file) { - this.file = file; - } - - /** Gets the file associated with this document. - * For documents read in from a file, this defaults to that file. If no - * file is associated with this document, for example if the document was - * parsed from a stream, this may return null. - */ - public File getFile() { - return file; - } - - /** Looks for magic numbers in the given input data and attempts to parse - * it with an appropriate DocumentHandle subclass. Detection - * is performed on a best-effort basis and is not guaranteed to be accurate. - * @throws IOException if an error occurs while reading from the stream - * @throws WorkBookException if parsing fails - */ - public static DocumentHandle getInstance(InputStream input) throws IOException { - BufferedInputStream bufferedStream = new BufferedInputStream(input); - // read in that start of the file for checking magic numbers - byte[] headerBytes; - int count; - // make sure the file is long enough to get magic numbers - bufferedStream.mark(1028); - headerBytes = new byte[512]; - count = bufferedStream.read(headerBytes); - bufferedStream.reset(); - - // if it starts with the LEO magic number check the header - if (LEOFile.checkIsLEO(headerBytes, count)) { - LEOFile leo = new LEOFile(bufferedStream); - - if (leo.hasWorkBook()) - return new WorkBookHandle(leo); - else - throw new WorkBookException( - "input is LEO but no supported format detected", -1); - } - - String headerString; - try { - headerString = new String(headerBytes, 0, count, "UTF-8"); - } catch (UnsupportedEncodingException e) { - // UTF-8 support is required by the JLS - throw new Error("the JVM does not support UTF-8", e); - } - - // if it's a ZIP archive, try parsing as OOXML - if (headerString.startsWith("PK")) { - return new WorkBookHandle(bufferedStream); - } - - if ((headerString.indexOf(",") > -1) - && (headerString.indexOf(",") > -1)) { - // init a blank workbook - WorkBookHandle book = new WorkBookHandle(); - - // map CSV into workbook - try { - WorkSheetHandle sheet = book.getWorkSheet(0); - sheet.readCSV(new BufferedReader( - new InputStreamReader(bufferedStream))); - return book; - } catch (Exception e) { - throw new WorkBookException( - "Error encountered importing CSV: " + e.toString(), - WorkBookException.ILLEGAL_INIT_ERROR); - } - } - - else { - throw new WorkBookException("unknown file format", -1); - } - } - - /** Gets the file name associated with this document. - * For documents read in from a file, this defaults to that file. If no - * file is associated with this document, for example if the document was - * parsed from a stream, this may return null. - * @deprecated Use {@link #getFile()} instead. - */ - @Deprecated - public String getFileName() { - return file != null ? file.getPath() : "New Document.doc"; - } - - /** Sets the debugging output level. - * Higher values will produce more output. Output at higher values will - * generally only be of use to OpenXLS developers. Increased output incurs - * a performance penalty, so it is recommended this be left at zero unless - * you are reporting a bug. - */ - @Override - public void setDebugLevel(int level) { - DEBUGLEVEL = level; - } - - public int getDebugLevel() { - return DEBUGLEVEL; - } - - /** Downloads the resource at the given URL to a temporary file. - * @param u the URL representing the resource to be downloaded - * @return the path to a temporary file containing the downloaded resource - * or null if an error occurred - * @deprecated The download should be handled outside OpenXLS. - * There is no specific replacement for this method. - */ - @Deprecated - protected static File getFileFromURL(URL u) { - try { - File fx = TempFileManager.createTempFile("upload-" - + System.currentTimeMillis(), ".tmp"); - - URLConnection uc = u.openConnection(); - String contentType = uc.getContentType(); - int contentLength = uc.getContentLength(); - if (contentType.startsWith("text/") || contentLength == -1) { - throw new IOException("This is not a binary file."); - } - InputStream raw = uc.getInputStream(); - InputStream in = new BufferedInputStream(raw); - byte[] data = new byte[contentLength]; - int bytesRead = 0; - int offset = 0; - while (offset < contentLength) { - bytesRead = in.read(data, offset, data.length - offset); - if (bytesRead == -1) - break; - offset += bytesRead; - } - in.close(); - - if (offset != contentLength) { - throw new IOException("Only read " + offset - + " bytes; Expected " + contentLength + " bytes"); - } - - // String filename = - // u.getFile().substring(filename.lastIndexOf('/') + 1); - FileOutputStream out = new FileOutputStream(fx); - out.write(data); - out.flush(); - out.close(); - return fx; - } catch (Exception e) { - Logger.logErr("Could not load WorkBook from URL: " + e.toString()); - return null; - } - } - - /** Gets the user-visible descriptive name or title of this document. - */ - @Override - public String getName() { - if (name != null) - return name; - else - return "Untitled Document"; - } - - /** Resets the document state to what it was when it was loaded. - * @throws UnsupportedOperationException if there is not sufficient data - * available to perform the reversion - */ - @Override - public abstract void reset(); - - /** Gets the constant representing this document's native format. - */ - public abstract int getFormat(); - - /** Gets the file name extension for this document's native format. - */ - public abstract String getFileExtension(); - - /** Writes the document to the given stream in the requested format. - * @param dest the stream to which the document should be written - * @param format the constant representing the desired output format - * @throws IllegalArgumentException if the given type code is invalid - * @throws IOException if an error occurs while writing to the stream - */ - @Override - public abstract void write(OutputStream dest, int format) throws IOException; - - /** Writes the document to the given stream in its native format. - * @param dest the stream to which the document should be written - * @throws IOException if an error occurs while writing to the stream - */ - @Override - public void write(OutputStream dest) throws IOException { - this.write(dest, FORMAT_NATIVE); - } - - /** Writes the document to the given file in the requested format. - * @param file the path to which the document should be written - * @param format the constant representing the desired output format - * @throws IllegalArgumentException if the given type code is invalid - * @throws IOException if an error occurs while writing to the file - */ - @Override - public void write(File file, int format) throws IOException { - if (format > WorkBookHandle.FORMAT_XLS && this.file != null) - OOXMLAdapter.refreshPassThroughFiles((WorkBookHandle) this); - - if (file.exists()) - file.delete(); // try this - OutputStream stream = new BufferedOutputStream( - new FileOutputStream(file)); - this.write(stream, format); - this.file = file; // necesary for OOXML re-write ... - stream.flush(); - stream.close(); - } - - /** Writes the document to the given file in its native format. - * @param file the path to which the document should be written - * @throws IOException if an error occurs while writing to the stream - */ - @Override - public void write(File file) throws IOException { - this.write(file, FORMAT_NATIVE); - } - - /** Returns a string representation of the object. - * This is currently equivalent to {@link #getName()}. - */ - @Override - public String toString() { - return getName(); - } + public static String workingdir = System.getProperty("user.dir") + + "/tmp"; + + /** + * Format constant for the most appropriate format for this document. + * If the document was read in from a file, this is usually the format that + * was read in. + */ + public static final int FORMAT_NATIVE = 0; + + /** + * The level of debugging output requested by the user. + * Higher values should produce more output. + */ + // TODO: should the debug level be static? + protected int DEBUGLEVEL = 0; + + /** + * The user-visible display name or title of this document. + */ + protected String name = null; + + /** + * The file associated with this document. + * This will generally be the file the document was parsed from, if any. + */ + protected File file; + + /** + * Store for workbook properties. + */ + private Map props = new HashMap(); + + /** + * Handling for a streaming worksheet based workbook + **/ + private boolean streamingSheets = false; + + /** + * default constructor + */ + public DocumentHandle() { + super(); + + } + + /** + * Apr 5, 2011 + * + * @param urlx + */ + public DocumentHandle(InputStream urlx) { + super(); + // TODO Auto-generated constructor stub + Logger.logErr("DocumentHandle InputStream Constructor Not Implemented"); + } + + public DocumentHandle(File input) { + // TODO Auto-generated constructor stub + } + + /** + * Retrieves a property in the workbook property store. + * This is not an Excel-compatible feature. + * + * @param name the name of the property to retrieve + * @return the value of the requested property or null if it doesn't exist + */ + @Override + public Object getProperty(String name) { + return props.get(name); + } + + /** + * Sets the value of a property in the workbook property store. + * This is not an Excel-compatible feature. + * + * @param name the name of the property which should be updated + * @param value the value to which the property should be set + */ + @Override + public void addProperty(String name, Object val) { + props.put(name, val); + } + + /** + * Retrieves a Map containing the workbook properties store. + * This is not an Excel-compatible feature. + * + * @return an immutable Map containing the current workbook properties + */ + public Map getProperties() { + return Collections.unmodifiableMap(props); + } + + /** + * Replaces the workbook properties with the values in a given Map. + * This is not an Excel-compatible feature. + * + * @param properties the values that will replace the existing properties + */ + public void setProperties(Map properties) { + props = new HashMap(); + props.putAll(properties); + } + + /** + * Gets the OpenXLS version number. + */ + public static String getVersion() { + return GetInfo.getVersion(); + } + + /** + * Sets the user-visible descriptive name or title of this document. + * Some formats will persist this setting in the document itself. + */ + @Override + public void setName(String nm) { + name = nm; + } + + /** + * Handling for streaming sheets. Currently this is in development and unsupported + * + * @param streamSheets + */ + public void setStreamingSheets(boolean streamSheets) { + this.streamingSheets = streamSheets; + } + + /** + * Sets the file name associated with this document. + * + * @deprecated Use {@link #setFile(File)} instead. + */ + @Deprecated + public void setFileName(String name) { + file = new File(name).getAbsoluteFile(); + } + + /** + * Sets the file associated with this document. + */ + public void setFile(File file) { + this.file = file; + } + + /** + * Gets the file associated with this document. + * For documents read in from a file, this defaults to that file. If no + * file is associated with this document, for example if the document was + * parsed from a stream, this may return null. + */ + public File getFile() { + return file; + } + + /** + * Looks for magic numbers in the given input data and attempts to parse + * it with an appropriate DocumentHandle subclass. Detection + * is performed on a best-effort basis and is not guaranteed to be accurate. + * + * @throws IOException if an error occurs while reading from the stream + * @throws WorkBookException if parsing fails + */ + public static DocumentHandle getInstance(InputStream input) throws IOException { + BufferedInputStream bufferedStream = new BufferedInputStream(input); + // read in that start of the file for checking magic numbers + byte[] headerBytes; + int count; + // make sure the file is long enough to get magic numbers + bufferedStream.mark(1028); + headerBytes = new byte[512]; + count = bufferedStream.read(headerBytes); + bufferedStream.reset(); + + // if it starts with the LEO magic number check the header + if (LEOFile.checkIsLEO(headerBytes, count)) { + LEOFile leo = new LEOFile(bufferedStream); + + if (leo.hasWorkBook()) + return new WorkBookHandle(leo); + else + throw new WorkBookException( + "input is LEO but no supported format detected", -1); + } + + String headerString; + headerString = new String(headerBytes, 0, count, StandardCharsets.UTF_8); + + // if it's a ZIP archive, try parsing as OOXML + if (headerString.startsWith("PK")) { + return new WorkBookHandle(bufferedStream); + } + + if ((headerString.indexOf(",") > -1) + && (headerString.indexOf(",") > -1)) { + // init a blank workbook + WorkBookHandle book = new WorkBookHandle(); + + // map CSV into workbook + try { + WorkSheetHandle sheet = book.getWorkSheet(0); + sheet.readCSV(new BufferedReader( + new InputStreamReader(bufferedStream))); + return book; + } catch (Exception e) { + throw new WorkBookException( + "Error encountered importing CSV: " + e.toString(), + WorkBookException.ILLEGAL_INIT_ERROR); + } + } else { + throw new WorkBookException("unknown file format", -1); + } + } + + /** + * Gets the file name associated with this document. + * For documents read in from a file, this defaults to that file. If no + * file is associated with this document, for example if the document was + * parsed from a stream, this may return null. + * + * @deprecated Use {@link #getFile()} instead. + */ + @Deprecated + public String getFileName() { + return file != null ? file.getPath() : "New Document.doc"; + } + + /** + * Sets the debugging output level. + * Higher values will produce more output. Output at higher values will + * generally only be of use to OpenXLS developers. Increased output incurs + * a performance penalty, so it is recommended this be left at zero unless + * you are reporting a bug. + */ + @Override + public void setDebugLevel(int level) { + DEBUGLEVEL = level; + } + + public int getDebugLevel() { + return DEBUGLEVEL; + } + + /** + * Downloads the resource at the given URL to a temporary file. + * + * @param u the URL representing the resource to be downloaded + * @return the path to a temporary file containing the downloaded resource + * or null if an error occurred + * @deprecated The download should be handled outside OpenXLS. + * There is no specific replacement for this method. + */ + @Deprecated + protected static File getFileFromURL(URL u) { + try { + File fx = TempFileManager.createTempFile("upload-" + + System.currentTimeMillis(), ".tmp"); + + URLConnection uc = u.openConnection(); + String contentType = uc.getContentType(); + int contentLength = uc.getContentLength(); + if (contentType.startsWith("text/") || contentLength == -1) { + throw new IOException("This is not a binary file."); + } + InputStream raw = uc.getInputStream(); + InputStream in = new BufferedInputStream(raw); + byte[] data = new byte[contentLength]; + int bytesRead = 0; + int offset = 0; + while (offset < contentLength) { + bytesRead = in.read(data, offset, data.length - offset); + if (bytesRead == -1) + break; + offset += bytesRead; + } + in.close(); + + if (offset != contentLength) { + throw new IOException("Only read " + offset + + " bytes; Expected " + contentLength + " bytes"); + } + + // String filename = + // u.getFile().substring(filename.lastIndexOf('/') + 1); + FileOutputStream out = new FileOutputStream(fx); + out.write(data); + out.flush(); + out.close(); + return fx; + } catch (Exception e) { + Logger.logErr("Could not load WorkBook from URL: " + e.toString()); + return null; + } + } + + /** + * Gets the user-visible descriptive name or title of this document. + */ + @Override + public String getName() { + if (name != null) + return name; + else + return "Untitled Document"; + } + + /** + * Resets the document state to what it was when it was loaded. + * + * @throws UnsupportedOperationException if there is not sufficient data + * available to perform the reversion + */ + @Override + public abstract void reset(); + + /** + * Gets the constant representing this document's native format. + */ + public abstract int getFormat(); + + /** + * Gets the file name extension for this document's native format. + */ + public abstract String getFileExtension(); + + /** + * Writes the document to the given stream in the requested format. + * + * @param dest the stream to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the stream + */ + @Override + public abstract void write(OutputStream dest, int format) throws IOException; + + /** + * Writes the document to the given stream in its native format. + * + * @param dest the stream to which the document should be written + * @throws IOException if an error occurs while writing to the stream + */ + @Override + public void write(OutputStream dest) throws IOException { + this.write(dest, FORMAT_NATIVE); + } + + /** + * Writes the document to the given file in the requested format. + * + * @param file the path to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the file + */ + @Override + public void write(File file, int format) throws IOException { + if (format > WorkBookHandle.FORMAT_XLS && this.file != null) + OOXMLAdapter.refreshPassThroughFiles((WorkBookHandle) this); + + if (file.exists()) + file.delete(); // try this + OutputStream stream = new BufferedOutputStream( + new FileOutputStream(file)); + this.write(stream, format); + this.file = file; // necesary for OOXML re-write ... + stream.flush(); + stream.close(); + } + + /** + * Writes the document to the given file in its native format. + * + * @param file the path to which the document should be written + * @throws IOException if an error occurs while writing to the stream + */ + @Override + public void write(File file) throws IOException { + this.write(file, FORMAT_NATIVE); + } + + /** + * Returns a string representation of the object. + * This is currently equivalent to {@link #getName()}. + */ + @Override + public String toString() { + return getName(); + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.java b/src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.java index 06b2dad..148c7da 100644 --- a/src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.java +++ b/src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -28,7 +28,7 @@ public class DocumentObjectNotFoundException extends Exception { private static final long serialVersionUID = 6605511680058750453L; public DocumentObjectNotFoundException(String string) { - super(string); + super(string); } } diff --git a/src/main/java/io/starter/OpenXLS/ExcelTools.java b/src/main/java/io/starter/OpenXLS/ExcelTools.java index 4938574..c742c8c 100644 --- a/src/main/java/io/starter/OpenXLS/ExcelTools.java +++ b/src/main/java/io/starter/OpenXLS/ExcelTools.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,1186 +22,1145 @@ */ package io.starter.OpenXLS; -import java.text.DecimalFormat; -//import java.text.SimpleDateFormat; +import io.starter.toolkit.CompatibleBigDecimal; +import io.starter.toolkit.Logger; +import io.starter.toolkit.ResourceLoader; +import io.starter.toolkit.StringTool; + import java.util.ArrayList; -import java.util.Calendar; import java.util.Date; import java.util.IllegalFormatConversionException; import java.util.StringTokenizer; -import io.starter.formats.XLS.Formula; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.Logger; -import io.starter.toolkit.ResourceLoader; -import io.starter.toolkit.CompatibleBigDecimal; -import io.starter.toolkit.StringTool; +//import java.text.SimpleDateFormat; /** * OpenXLS helper methods.
        * Contains helpful methods to ease use of the OpenXLS toolkit.
        - * - * "http://starter.io">Starter Inc. + *

        + * "http://starter.io">Starter Inc. + * * @see ByteTools */ public class ExcelTools implements java.io.Serializable { - private static final long serialVersionUID = 7622857355626065370L; - - /** - * Formats a double in the standard OpenXLS (General) format. Up to - * 99999999999 is expressed in standard notation. Above that is formatted in - * scientific notation - * - * In addition, Excel precision of 9 digits is maintained - * - * - * returns a number formatted in Excel's General format, (assuming a wide - * enough column width - see below) example: - * formatNumericNotation(1234567890123) returns "1.23457E+12" - * - * Information on NOTATION_STANDARD_EXCEL (i.e. Excel's General Format): // - * Excel will show as many decimal places that the text item has room for, - * it won't use a thousands separator, and if the // number can't fit, Excel - * uses a scientific number format. // RULES: // 1- Assuming the column is - * wide enough numbers will only be displayed in the scientific format when - * they contain more than 10 digits. // 2- If you enter a number into a cell - * and thre is not enough room to display all the digits // then the number - * will either be displayed in scientific format or will not be displayed at - * all, meaning that ##### will appear. // The exact precision of the - * scientific format will depend on the width of the actual cell. - * - * @param fpnum - * @return String formatted number - */ - public static String getNumberAsString(double fpnum) { - // Ensure precision and number of digits ala Excel - // double issues - use BigDecimal - java.math.BigDecimal bd = new java.math.BigDecimal(fpnum); - int scale = bd.scale(); - if ((Math.abs(fpnum) > 0.000000001) && scale > 9) { - bd= bd.setScale(9, java.math.RoundingMode.HALF_UP); - } else if (scale > 9) - bd= new java.math.BigDecimal(fpnum, new java.math.MathContext(5,java.math.RoundingMode.HALF_UP)); - bd= bd.stripTrailingZeros(); - String s = bd.toPlainString(); - int len = s.length(); - // If larger than 11 characters, truncate string - if (len > 11 && fpnum > 0 || len > 12) { // must deal with exponents and such as well - if (scale==0) { - s= new java.math.BigDecimal(bd.toString(), new java.math.MathContext(6,java.math.RoundingMode.HALF_UP)).toString(); - } else if (bd.toString().indexOf("E")==-1) { - s= new java.math.BigDecimal(bd.toString(), new java.math.MathContext(10,java.math.RoundingMode.HALF_UP)).toString(); - while (s.length()>0 && s.charAt(s.length()-1)=='0') - s= s.substring(0, s.length()-1); - if (s.endsWith(".")) - s= s.substring(0, s.length()-1); - } else { // 5 + E+XX + sign - s= new java.math.BigDecimal(bd.toString(), new java.math.MathContext(5,java.math.RoundingMode.HALF_UP)).toString(); - } - } - return s; - } + private static final long serialVersionUID = 7622857355626065370L; + + /** + * Formats a double in the standard OpenXLS (General) format. Up to + * 99999999999 is expressed in standard notation. Above that is formatted in + * scientific notation + *

        + * In addition, Excel precision of 9 digits is maintained + *

        + *

        + * returns a number formatted in Excel's General format, (assuming a wide + * enough column width - see below) example: + * formatNumericNotation(1234567890123) returns "1.23457E+12" + *

        + * Information on NOTATION_STANDARD_EXCEL (i.e. Excel's General Format): // + * Excel will show as many decimal places that the text item has room for, + * it won't use a thousands separator, and if the // number can't fit, Excel + * uses a scientific number format. // RULES: // 1- Assuming the column is + * wide enough numbers will only be displayed in the scientific format when + * they contain more than 10 digits. // 2- If you enter a number into a cell + * and thre is not enough room to display all the digits // then the number + * will either be displayed in scientific format or will not be displayed at + * all, meaning that ##### will appear. // The exact precision of the + * scientific format will depend on the width of the actual cell. + * + * @param fpnum + * @return String formatted number + */ + public static String getNumberAsString(double fpnum) { + // Ensure precision and number of digits ala Excel + // double issues - use BigDecimal + java.math.BigDecimal bd = new java.math.BigDecimal(fpnum); + int scale = bd.scale(); + if ((Math.abs(fpnum) > 0.000000001) && scale > 9) { + bd = bd.setScale(9, java.math.RoundingMode.HALF_UP); + } else if (scale > 9) + bd = new java.math.BigDecimal(fpnum, new java.math.MathContext(5, java.math.RoundingMode.HALF_UP)); + bd = bd.stripTrailingZeros(); + String s = bd.toPlainString(); + int len = s.length(); + // If larger than 11 characters, truncate string + if (len > 11 && fpnum > 0 || len > 12) { // must deal with exponents and such as well + if (scale == 0) { + s = new java.math.BigDecimal(bd.toString(), new java.math.MathContext(6, java.math.RoundingMode.HALF_UP)).toString(); + } else if (bd.toString().indexOf("E") == -1) { + s = new java.math.BigDecimal(bd.toString(), new java.math.MathContext(10, java.math.RoundingMode.HALF_UP)).toString(); + while (s.length() > 0 && s.charAt(s.length() - 1) == '0') + s = s.substring(0, s.length() - 1); + if (s.endsWith(".")) + s = s.substring(0, s.length() - 1); + } else { // 5 + E+XX + sign + s = new java.math.BigDecimal(bd.toString(), new java.math.MathContext(5, java.math.RoundingMode.HALF_UP)).toString(); + } + } + return s; + } /** - * static version of getFormattedStringVal; given an object value, a + * static version of getFormattedStringVal; given an object value, a * valid Excel format pattern, return the formatted string value. - * @param Object o - * @param String pattern if General or "" returns string value - * @param boolean isInteger if General pattern, attempt to use integer value (rather than double) + * + * @param Object o + * @param String pattern if General or "" returns string value + * @param boolean isInteger if General pattern, attempt to use integer value (rather than double) */ - public static String getFormattedStringVal(Object o, String pattern/*, boolean isInteger*/){ - if (o==null) o= ""; - - boolean isInteger= false; - if (o instanceof Integer || (o instanceof Double && ((Double)o).intValue()==((Double)o).doubleValue())) - isInteger= true; - else { - isInteger= false; - } - - if(pattern==null || pattern.equals("")||pattern.equalsIgnoreCase("GENERAL")) { - if (isInteger) - return String.valueOf(Double.valueOf(o.toString()).intValue()); - else { // general double numbers have default precision ... - try { - double d= new Double(o.toString()); - return ExcelTools.getNumberAsString(Double.valueOf(o.toString())); // handles default precision - }catch(NumberFormatException e) { + public static String getFormattedStringVal(Object o, String pattern/*, boolean isInteger*/) { + if (o == null) o = ""; + + boolean isInteger = false; + isInteger = o instanceof Integer || (o instanceof Double && ((Double) o).intValue() == ((Double) o).doubleValue()); + + if (pattern == null || pattern.equals("") || pattern.equalsIgnoreCase("GENERAL")) { + if (isInteger) + return String.valueOf(Double.valueOf(o.toString()).intValue()); + else { // general double numbers have default precision ... + try { + double d = new Double(o.toString()); + return ExcelTools.getNumberAsString(Double.valueOf(o.toString())); // handles default precision + } catch (NumberFormatException e) { } - return o.toString(); - } - } else if (pattern.equals("000-00-0000")) { // special case for SSN format ... sigh ... - try { - new Double(o.toString()); // if it can't be converted to a number, return original string (tis what excel does) - String s= o.toString(); - while (s.length() < 9) // tis what excel does ... - s= '0' + s; - return s.substring(0, 3) + "-" + s.substring(3, 5) + "-" + s.substring(5); - } catch (Exception e) { - return o.toString(); - } - } - + return o.toString(); + } + } else if (pattern.equals("000-00-0000")) { // special case for SSN format ... sigh ... + try { + new Double(o.toString()); // if it can't be converted to a number, return original string (tis what excel does) + String s = o.toString(); + while (s.length() < 9) // tis what excel does ... + s = '0' + s; + return s.substring(0, 3) + "-" + s.substring(3, 5) + "-" + s.substring(5); + } catch (Exception e) { + return o.toString(); + } + } + /** try to determine if the format is numeric (+currency) or date */ - boolean isNumeric= false, isDate= false, isString= false; - + boolean isNumeric = false, isDate = false, isString = false; + /** excel formats can have up to 4 parts: ;;; */ - String[] pats= pattern.split(";"); // assign the correct pattern according to double or string value - - String tester= StringTool.convertPatternExtractBracketedExpression(pats[0]); - if (tester.matches(".*(((y{1,4}|m{1,5}|d{1,4}|h{1,2}|s{1,2}).*)+).*")) { - isDate= true; - pats[0]= tester; // ignore locale and other info for dates ... + String[] pats = pattern.split(";"); // assign the correct pattern according to double or string value + + String tester = StringTool.convertPatternExtractBracketedExpression(pats[0]); + if (tester.matches(".*(((y{1,4}|m{1,5}|d{1,4}|h{1,2}|s{1,2}).*)+).*")) { + isDate = true; + pats[0] = tester; // ignore locale and other info for dates ... } - if (!isDate) - { - int idx= pats.length-1; // default with string - try{ - double d = new Double(o.toString()); - isNumeric= true; - if (d > 0) // 1st expression is for + numbers - idx= 0; - else if (pats.length > 1 && d < 0) // 2nd is for - numbers - idx= 1; - else if (pats.length > 2 && d==0) // 3rd for 0 - idx= 2; - pattern= StringTool.convertPatternFromExcelToStringFormatter(pats[idx], d < 0); // get correct format for String.format formatter - } catch (NumberFormatException e) { // 4th for text (non-numeric) - if (pats.length > 3) - idx= 3; - isString= true; - pattern= StringTool.convertPatternFromExcelToStringFormatter(pats[idx], false); // get correct format for String.format formatter - } + if (!isDate) { + int idx = pats.length - 1; // default with string + try { + double d = new Double(o.toString()); + isNumeric = true; + if (d > 0) // 1st expression is for + numbers + idx = 0; + else if (pats.length > 1 && d < 0) // 2nd is for - numbers + idx = 1; + else if (pats.length > 2 && d == 0) // 3rd for 0 + idx = 2; + pattern = StringTool.convertPatternFromExcelToStringFormatter(pats[idx], d < 0); // get correct format for String.format formatter + } catch (NumberFormatException e) { // 4th for text (non-numeric) + if (pats.length > 3) + idx = 3; + isString = true; + pattern = StringTool.convertPatternFromExcelToStringFormatter(pats[idx], false); // get correct format for String.format formatter + } } else { - pattern= pats[0]; - pattern= StringTool.convertDatePatternFromExcelToStringFormatter(pattern); // get correct format for SimpleDateFormat + pattern = pats[0]; + pattern = StringTool.convertDatePatternFromExcelToStringFormatter(pattern); // get correct format for SimpleDateFormat } - - - if (isString) { // use string portion of format, if any + + + if (isString) { // use string portion of format, if any try { - return String.format(pattern, o); + return String.format(pattern, o); } catch (IllegalFormatConversionException e) { - return o.toString(); + return o.toString(); } } if (isNumeric) { - try{ - double d = new Double(o.toString()); + try { + double d = new Double(o.toString()); if (!Double.isNaN(d)) { - d= Math.abs(d); // negative number intricacies have been handled in convertPattern method - // ugly, but has to be done ... - if (pattern.indexOf("%%")!=-1) // convert to percent - d*=100; - // special case of "@" -- integers converted to doubles format incorrectly ... - if (pattern.equals("%s")) { - return o.toString(); - } - return String.format(pattern, d); + d = Math.abs(d); // negative number intricacies have been handled in convertPattern method + // ugly, but has to be done ... + if (pattern.indexOf("%%") != -1) // convert to percent + d *= 100; + // special case of "@" -- integers converted to doubles format incorrectly ... + if (pattern.equals("%s")) { + return o.toString(); + } + return String.format(pattern, d); } - } catch (Exception e) { - } - return o.toString(); + } catch (Exception e) { + } + return o.toString(); } if (isDate) { - try{ - WorkBookHandle.simpledateformat.applyPattern(pattern); - }catch(Exception ex) { - return o.toString(); + try { + WorkBookHandle.simpledateformat.applyPattern(pattern); + } catch (Exception ex) { + return o.toString(); } - try { - return WorkBookHandle.simpledateformat.format(DateConverter.getCalendarFromNumber(o).getTime()); + try { + return WorkBookHandle.simpledateformat.format(DateConverter.getCalendarFromNumber(o).getTime()); /*// KSC: TESTING Date d= DateConverter.getCalendarFromNumber(o).getTime(); return WorkBookHandle.simpledateformat.format(d);*/ - } catch (NumberFormatException e) { - try { - return WorkBookHandle.simpledateformat.format(new Date(o.toString()).getTime()); - } catch (IllegalArgumentException i) { - if (o instanceof Number) - Logger.logWarn("Unable to format date in " + pattern); - } - } catch (IllegalArgumentException e) { - if (o instanceof Number) - Logger.logWarn("Unable to format date in " + pattern); - } + } catch (NumberFormatException e) { + try { + return WorkBookHandle.simpledateformat.format(new Date(o.toString()).getTime()); + } catch (IllegalArgumentException i) { + if (o instanceof Number) + Logger.logWarn("Unable to format date in " + pattern); + } + } catch (IllegalArgumentException e) { + if (o instanceof Number) + Logger.logWarn("Unable to format date in " + pattern); + } } // otherwise return o.toString(); } - - - - - - /** - * A FAIL FAST implementation for finding whether a cell string address - * falls within a set of row/col range coordinates. - * - * Sep 21, 2010 - * - * @param rng - * the range you want to test - * @param rowFirst - * in the target range - * @param rowLast - * in the target range - * @param colFirst - * in the target range - * @param colLast - * in the target range - * @return - */ - public static boolean isInRange(String rng, int rowFirst, int rowLast, - int colFirst, int colLast) { - int[] sh = io.starter.OpenXLS.ExcelTools.getRowColFromString(rng); - - // the guantlet - if (sh[1] < colFirst) - return false; - if (sh[1] > colLast) - return false; - if (sh[0] < rowFirst) - return false; - if (sh[0] > rowLast) - return false; - - return true; // passes! - - } - - /** - * returns true if range intersects with range2 - * - * @param rng - * @param rc - * @return - */ - public static boolean intersects(String rng, int[] rc) { - int[] rc2 = ExcelTools.getRangeCoords(rng); - if ((rc[0] >= rc2[0]) && (rc[2] <= rc2[2]) && (rc[1] >= rc2[1]) - && (rc[3] <= rc2[3])) - return true; - return false; - } - - /** - * returns true if address is before the range coordinates defined by rc - * - * @param rc - * row col of address - * @param rng - * int[] coordinates as: row0, col0, row1, col1 - * @return true if address is before the range coordinates - */ - public static boolean isBeforeRange(int[] rc, int[] rng) { - if (rc[0] < rng[0] || (rc[0] == rng[0] && rc[1] < rng[1])) - return true; - return false; - } - - /** - * returns true if address is before the range coordinates defined by rc - * - * @param rc - * row col of address - * @param rng - * int[] coordinates as: row0, col0, row1, col1 - * @return true if address is before the range coordinates - */ - public static boolean isAfterRange(int[] rc, int[] rng) { - if (rc[0] > rng[2] || (rc[0] == rng[2] && rc[1] > rng[3])) - return true; - return false; - } - - /** - * Takes an input Object and attempts to convert to numeric Objects of the - * highest precision possible. - * - * This method is useful for avoiding the Excel warnings - * "Number Stored As Text" when storing string data that contains numbers. - * - * NOTE: this method is useful for ensuring that Formula references contain - * true numeric values as not all String numbers are properly interpreted in - * Formula engines, and can silently fail. - * - * For this reason, always use numeric, non-string values to calculated - * cells. - * - * - * @param input - * @return - */ - public static Object getObject(Object in) { - // do not record -- only called from other methods - if (!(in instanceof String)) { - return in; - } - - String input = String.valueOf(in); - Object ret = input; // default is the original string - - try { - ret = new Double(input); - return ret; - } catch (NumberFormatException ex) { - try { - ret = new Float(input); - return ret; - } catch (NumberFormatException ex2) { - try { - ret = Integer.valueOf(input); - return ret; - } catch (NumberFormatException ex3) { - // ret Is set outside of loop incase no match is ever found. - } - } - } - - // list of formatting chars to check for - String[][] fmtlist = { { "$", "," }, { ",", "," }, { "%", "," } }; - - // strip the formatting - for (int t = 0; t < fmtlist.length; t++) { - if (input.indexOf(fmtlist[t][0]) > -1) { // contains! - String converted = StringTool.replaceText(input, fmtlist[t][0], - ""); // strip first token (ie: '$') - converted = StringTool - .replaceText(converted, fmtlist[t][1], ""); // strip - // second - // token - // (ie: ',') - try { - ret = new Double(converted); - return ret; - } catch (NumberFormatException ex) { - try { - ret = new Float(converted); - return ret; - } catch (NumberFormatException ex2) { - try { - ret = Integer.valueOf(converted); - return ret; - } catch (NumberFormatException ex3) { - // ret Is set outside of loop incase no match is - // ever found. - } - } - } - } - - } - - return ret; - } - - /** - * convert twips to pixels - * - * - * In addition to a calculated size unit derived from the average size of - * the default characters 0-9, Excel uses the 'twips' measurement which is - * defined as: - * - * 1 twip = 1/20 point or 20 twips = 1 point 1 twip = 1/567 centimeter or - * 567 twips = 1 centimeter 1 twip = 1/1440 inch or 1440 twips = 1 inch - * - * - * 1 pixel = 0.75 points 1 pixel * 1.3333 = 1 point 1 twip * 20 = 1 point - * - * @param pixels - * @return twips - */ - public static final float getPixels(float twips) { - float points = twips / 20; - float pixels = points * 1.3333333f; // good enuff precision - return pixels; - } - - /** - * convert pixels to twips - * - * - * In addition to a calculated size unit derived from the average size of - * the default characters 0-9, Excel uses the 'twips' measurement which is - * defined as: - * - * 1 pixel = 0.75 points 1 pixel * 1.3333 = 1 point 1 twip * 20 = 1 point - * - * @param pixels - * @return twips - */ - public static final float getTwips(float pixels) { - float points = pixels * .75f; - float twips = points * 20; - return twips; - } - - /** - * get recordy byte def as a String - * - * public static String getRecordByteDef(XLSRecord rec){ byte[] b = - * rec.read(); StringBuffer sb = new StringBuffer("byte[] rbytes = {"); - * for(int t = 0;t start time, last time, - * start mem, last mem - * - * @param info - * @param perfobj - */ - public static void benchmark(String info, Object perfobj) { - Runtime rt = Runtime.getRuntime(); - long[] p = null; - long lasttime = 0l, lastmem = 0l; - if (System.getProperties().get(perfobj.toString()) != null) { - p = (long[]) System.getProperties().get(perfobj.toString()); - lasttime = p[1]; - lastmem = p[3]; - p[1] = System.currentTimeMillis(); - p[3] = rt.freeMemory(); - double elapsedsec = p[1] - lasttime; - double usedmem = lastmem - p[3]; // - lastmem; - if (usedmem < 0) - usedmem *= -1; - Logger.logInfo(getLogDate() + " " + info); - Logger.logInfo(" time: " + elapsedsec + " millis"); - Logger.logInfo(" mem: " + usedmem + " bytes."); - } else { - p = new long[4]; - p[0] = System.currentTimeMillis(); - p[1] = System.currentTimeMillis(); - p[2] = rt.freeMemory(); - p[3] = rt.freeMemory(); - lasttime = p[1]; - lastmem = p[3]; - System.getProperties().put(perfobj.toString(), p); - } - - } - - /** - * get the bytes from a Vector of objects public byte[] - * getBytesFrom(CompatibleVector objs){ - * - * for(int t = 0;t 701) { // has 3rd digit - int z = (i / 676) - 1; // -1 to account for 0-based - if ((i % 676) < 26) { // then "leftover" is actually 2nd digit - z--; - leftover = 676; - } - ret = String.valueOf(ExcelTools.alpharr[z]); - i = i % 676; - i += leftover; - } - if (i > 25) { // has 2nd digit - int z = (i / 26) - 1; // -1 to account for 0-based - ret = ret + ExcelTools.alpharr[z]; - i = i % 26; - } - - // bbennett: this raises AIOOB if i = -1. In this situation we don't - // care about alpha because it will just be in the message of the - // exception that flags cell as non existent. - ret += i < 0 ? Integer.toString(i) : String.valueOf(ExcelTools.alpharr[(i)]); - - return ret; - } - - /** - * get the int value of the Excel-style Column alpha representation. - * - * @param String - * column name - * @return int the 0-based column number - */ - public static int getIntVal(String c) { - c = c.toUpperCase(); - if (c.length() > 3) // max col value= XFD in Excel 2007 - return -1; - int i = c.length() - 1; - int ret = 0; - while (i >= 0) { // process least to most-sigificant dig - int z = 0; - char cc = c.charAt(i); - while (z < alpharr.length && cc != alpharr[z++]) - ; - z *= Math.pow(26, c.length() - i - 1); // 1-based col for computing - ret += z; - i--; - } - // make 0-based - ret--; - return ret; - } - - /** - * Parses an Excel cell address into row and column integers. - * - * @param address - * the address to parse, either A1 or R1C1 - * @return int[2]: [0] row index, [1] column index - * @throws IllegalArgumentException - * if the argument is not a valid address - */ - public static int[] getRowColFromString(String address) { - - if (address.indexOf("$") > -1) { - address = StringTool.strip(address, "$"); - } - if (address.indexOf("!") > -1) { - address = address.substring(address.indexOf("!") + 1); - } - if (address.indexOf(":") > -1) - return getRangeRowCol(address); - - char[] adrchars = address.toCharArray(); - int row = 0, col = 0; - int charpos = -1, numpos = -1; - boolean r1c1 = false; - for (int i = 0; i < adrchars.length; i++) { - if (Character.isDigit(adrchars[i])) { - if (numpos == -1) // its a number - numpos = i; - } else if (charpos == -1) { - charpos = i; - if (numpos >= 0) { // we have already set number and we now have - // a nondigit - R1C1 style - r1c1 = true; - // break, it's all over! - break; - } - } - } - - if (r1c1) { // it's a single cell ref - try { - // there's an R and a C, not adjacent - if (address.toUpperCase().indexOf("R") == 0) { // startwith R - String rx = address.substring(1, address.toUpperCase() - .indexOf("C")); - String cx = address.substring(address.toUpperCase() - .indexOf("C") + 1); - row = Integer.parseInt(rx); - col = Integer.parseInt(cx); - } - } catch (NumberFormatException e) { - throw new IllegalArgumentException("illegal R1C1 address '" - + address + "'"); - } - } else { - row = 0; // -1 below - col = -1; - if (charpos == 0 && numpos > 0) { - String colval = address.substring(0, numpos); - col = getIntVal(colval); - if (col < 0) - throw new IllegalArgumentException("illegal column value '" - + colval + "' in address '" + address + "'"); - } - if (numpos >= 0) { - row = Integer.parseInt(address.substring(numpos)); - if (row < 1) - throw new IllegalArgumentException( - "row may not be negative in address '" + address - + "'"); - } else { // it's a wholecol ref - col = getIntVal(address); - if (col < 0) - throw new IllegalArgumentException("illegal column value '" - + address + "' in address '" + address + "'"); - } - } - - int[] ret = { row - 1, col }; - return ret; - } - - /** - * Parses an Excel cell range and returns the addresses as an int array. The - * range may not be qualified with sheet names. Strip them with - * {@link #stripSheetNameFromRange} before calling this method. If the - * argument is a single cell address it will be returned for both bounds. - * - * @param range - * the range to parse - * @return int[4]: [0] first row, [1] first column, [2] second row, [3] - * second column - * @throws IllegalArgumentException - * if the addresses are invalid - */ - public static int[] getRangeRowCol(String range) { - int colon = range.indexOf(":"); - - String firstloc; - String lastloc; - - if (colon > -1) { - firstloc = range.substring(0, colon); - lastloc = range.substring(colon + 1); - } else { - firstloc = range; - lastloc = range; - } - - int[] result = new int[4]; - int[] temp; - - temp = getRowColFromString(firstloc); - System.arraycopy(temp, 0, result, 0, 2); - - temp = getRowColFromString(lastloc); - System.arraycopy(temp, 0, result, 2, 2); - - return result; - } - - /** - * Takes an int array representing a row and column and formats it as a cell - * address. - * - * The index is zero-based. - * - * [0][0] is "A1" [1][1] is "B2" [2][2] is "C3" - * - * @param int[] the numeric range to convert - * @return String the string representation of the range - */ - public static String formatLocation(int[] rowCol) { - StringBuffer sb = new StringBuffer(getAlphaVal(rowCol[1])); - sb.append(String.valueOf(rowCol[0] + 1)); - - // handle ranges - if (rowCol.length > 3) { - // 20090807: KSC: only a range if 1st is != 2nd cell :) - if (rowCol[0] == rowCol[2] && rowCol[1] == rowCol[3]) // it's a single address - return sb.toString(); - sb.append(":"); - sb.append(getAlphaVal(rowCol[3])); - sb.append(String.valueOf(rowCol[2] + 1)); - } - - return sb.toString(); - } - - /** - * Takes an int array representing a row and column and formats it as a cell - * address, taking into account relative or absolute refs - * - * The index is zero-based. - * - * [0][0] is "A1", $A1, A$1 or $A$1 depending upon bRelRow or bRelCol [1][1] - * is "B2", $B1, B$1 or B$1 depending upon bRelRow or bRelCol [2][2] is - * "C3", $C1, C$1 or $C$1 depending upon bRelRow or bRelCol - * - * @param int[] the numeric range to convert - * @param bRelRow - * if true, no "$"s are added, relative row reference - * @param bRelCol - * if true, no "$"s are added, relative col reference - * @return String the string representation of the range - */ - public static String formatLocation(int[] s, boolean bRelRow, - boolean bRelCol) { - StringBuffer sb = new StringBuffer((bRelCol ? "" : "$")); - if (s[1] > -1) // account for WholeRow/WholeCol references - sb.append(getAlphaVal(s[1])); - - if (s[0] > -1) // account for WholeRow/WholeCol references - sb.append((bRelRow ? "" : "$") + String.valueOf(s[0] + 1)); - // 20090906 KSC: handle ranges - if (s.length > 3) { - if (s[0] == s[2] && s[1] == s[3]) // it's a single address - return sb.toString(); - sb.append(":"); - sb.append((bRelCol ? "" : "$")); - sb.append(getAlphaVal(s[3])); - sb.append((bRelRow ? "" : "$") + String.valueOf(s[2] + 1)); - } - - return sb.toString(); - } - - /** - * Takes an array of four shorts and formats it as a cell range. - * - * IE [0][3][1][4] would be "A2:B3" - * - * @param int[] the numeric range to convert - * @return String the string representation of the range - */ - public static String formatRange(int[] s) { - if (s.length != 4) - return "incorrect array size in ExcelTools.formatLocation"; - int[] temp = new int[2]; - temp[0] = s[1]; - temp[1] = s[0]; - String firstcell = formatLocation(temp); - temp[0] = s[3]; - temp[1] = s[2]; - String lastcell = formatLocation(temp); - return firstcell + ":" + lastcell; - } - - /** - * format a range as a string, range in format of [r][c][r1][c1] - * - * @param s - * @return String representation of the integers as a range, ie A1:B4 - */ - public static String formatRangeRowCol(int[] s) { - if (s.length != 4) - return "incorrect array size in ExcelTools.formatLocation"; - int[] temp = new int[2]; - temp[0] = s[0]; - temp[1] = s[1]; - String firstcell = formatLocation(temp); - temp[0] = s[2]; - temp[1] = s[3]; - String lastcell = formatLocation(temp); - return firstcell + ":" + lastcell; - } - - /** - * format a range as a string, range in format of [r][c][r1][c1] including - * relative address state - * - * - * @param s - * @param bRelAddresses - * contains relative row and col state for each rcr1c1 - * @return String representation of the integers as a range, ie A1:B4 - */ - public static String formatRangeRowCol(int[] s, boolean[] bRelAddresses) { - if (s.length != 4) - return "incorrect array size in ExcelTools.formatLocation"; - int[] temp = new int[2]; - temp[0] = s[0]; - temp[1] = s[1]; - String firstcell = formatLocation(temp, bRelAddresses[0], - bRelAddresses[1]); - temp[0] = s[2]; - temp[1] = s[3]; - String lastcell = formatLocation(temp, bRelAddresses[2], - bRelAddresses[3]); - // unfortunately, no formatLocation can do this. This is - // formattingRANGERowCol - // if (firstcell.equals(lastcell)) return firstcell; // 20090309 KSC: - return firstcell + ":" + lastcell; - } - - /** - * Transforms a string to an array of ints for evaluation purposes. For - * example, acdc == [0][2][3][2] - */ - public static int[] transformStringToIntVals(String trans) { - int[] intarr = new int[trans.length()]; - for (int i = 0; i < trans.length(); i++) { - char c = trans.charAt(i); - for (int x = 0; x < alpharr.length; x++) { - if (String.valueOf(c).equalsIgnoreCase( - String.valueOf(alpharr[x]))) { - intarr[i] = x; - } - } - } - return intarr; - } - - /** - * Formats a string representation of a numeric value as a string in the - * specified notation: - * - * @param int
        - * NOTATION_STANDARD = 0,
        - * NOTATION_SCIENTIFIC = 1,
        - * NOTATION_SCIENTIFIC_EXCEL = 2,
        - * EXTENXLS_NOTATION = 3 - * - * - * example: formatNumericNotation(1.23456E5, 0) returns a "123456" - * example: formatNumericNotation(123456, 1) returns "1.23456E5" - * example: formatNumericNotation(123456, 2) returns "1.23456E+5" - * example: formatNumericNotation(123456, 3) returns "1.23456E+5" - */ - public static String formatNumericNotation(String num, int notationType) { - // if (notationType > 2)return null; - boolean negative = false; - if (num.substring(0, 1).equals("-")) { - negative = true; - num = num.substring(1, num.length()); - } - String preString, postString, fullString = ""; - switch (notationType) { - case 0: // NOTATION_STANDARD - int i = num.indexOf("E"); - if (i == -1) { // just return - if (num.substring(num.length() - 2, num.length()).equals(".0")) { - num = num.substring(0, num.length() - 2); - } - if (negative) { - return "-" + num; - } - return num; - } - preString = num.substring(0, i); - CompatibleBigDecimal outNumD = new CompatibleBigDecimal(preString); - String exp = ""; - if (num.indexOf("+") == -1) { - exp = num.substring(i + 1, num.length()); - } else { - exp = num.substring(i + 2, num.length()); - } - int expNum = Integer.valueOf(exp).intValue(); - outNumD = new CompatibleBigDecimal(outNumD.movePointRight(expNum)); - // outNumD = outNumD.multiply(new CompatibleBigDecimal(Math.pow(10, - // expNum))); - // Logger.logInfo(String.valueOf(outNumD)); - // outNum = Math.r - - // check if we should be returning a whole number or a decimal - int moveLen = num.indexOf("E") - num.indexOf(".") - 1; - if (expNum >= moveLen) { - if (negative) { - return "-" - + String.valueOf(Math.round(outNumD.doubleValue())); - } else { - return String.valueOf(Math.round(outNumD.doubleValue())); - } - } - Object[] args = new Object[0]; - // args[0] = outNumD; - Object res = ResourceLoader.executeIfSupported(outNumD, args, - "toPlainString"); - if (res != null) { - fullString = res.toString(); - } else { - fullString = outNumD.toCompatibleString(); - } - break; - case 1: // NOTATION_SCIENTIFIC - if (num.indexOf("E") != -1 && num.indexOf("+") == -1) { - fullString = num; - } else if (num.indexOf("+") != -1) { - preString = num.substring(0, num.indexOf("+")); - postString = num.substring(num.indexOf("+") + 1, num.length()); - return preString + postString; - } else if (num.indexOf(".") != -1) { - int pos = num.indexOf("."); - preString = num.substring(0, 1) + "." - + num.substring(1, num.indexOf(".")); - CompatibleBigDecimal d = new CompatibleBigDecimal(num); - if (d.doubleValue() < 1 && d.doubleValue() != 0) { - // it is a very small value, ie 1.0E-10 - int counter = 0; - while (d.doubleValue() < 1) { - d = new CompatibleBigDecimal(d.movePointRight(1)); - counter++; - } - String retStr = d.toCompatibleString() + "E-" + counter; - return retStr; - } - postString = num.substring(num.indexOf(".") + 1, num.length()); - fullString = preString + postString; - fullString = fullString + "E" + (pos - 1); - } else { - preString = num.substring(0, 1) + "."; - if (num.length() > 1) { - preString += num.substring(1, num.length()); - } else { - preString += "0"; - } - fullString = preString + "E" + (num.length() - 1); - } - break; - case 2: // NOTATION_SCIENTIFIC_EXCEL - if (num.indexOf("E") != -1 && num.indexOf("+") != -1) { - fullString = num; - } else if (num.indexOf("E") != -1) { - preString = num.substring(0, num.indexOf("E") + 1); - postString = "+" - + num.substring(num.indexOf("E") + 1, num.length()); - fullString = preString + postString; - } else if (num.indexOf(".") != -1) { - int pos = num.indexOf("."); - CompatibleBigDecimal d = new CompatibleBigDecimal(num); - if (d.doubleValue() < 1 && d.doubleValue() != 0) { - // it is a very small value, ie 1.0E-10 - int counter = 0; - while (d.doubleValue() < 1) { - d = new CompatibleBigDecimal(d.movePointRight(1)); - counter++; - } - String retStr = d.toCompatibleString() + "E-" + counter; - return retStr; - } - preString = num.substring(0, 1) + "." - + num.substring(1, num.indexOf(".")); - postString = num.substring(num.indexOf(".") + 1, num.length()); - fullString = preString + postString; - fullString = fullString + "E+" + (pos - 1); - } else { - preString = num.substring(0, 1) + "."; - if (num.length() > 1) { - preString += num.substring(1, num.length()); - } else { - preString += "0"; - } - fullString = preString + "E+" + (num.length() - 1); - } - break; - default: - return num; - } - if (negative) - fullString = "-" + fullString; - return fullString; - } - - /** - * Return an array of cell handles specified from the string passed in. - * - * Note that a CellHandle cannot exist for an empty cell, so the cells - * retrieved in this manner will be blank cells, not empty cells. - * - * @param cellstr - * - a comma delimited String representing cells and cell ranges, - * example "A1,A5,A6,B1:B5" would return cells A1, A5, A6, B1, - * B2, B3, B4, B5 - * @param sheet - * the worksheet containing the cells. - * @return CellHandle[] - */ - public static CellHandle[] getCellHandlesFromSheet(String strRange, - WorkSheetHandle sheet) { - CellHandle[] retCells; - StringTokenizer cellTokenizer = new StringTokenizer(strRange, ","); - ArrayList cells = new ArrayList(); - do { - String element = (String) cellTokenizer.nextElement(); - if (element.indexOf(":") != -1) { - CellRange aRange = new CellRange(sheet.getSheetName() + "!" - + strRange, sheet.wbh, true); - cells.addAll(aRange.getCellList()); - } else { - CellHandle aCell = null; - try { - aCell = sheet.getCell(element); - } catch (Exception ce) { - aCell = sheet.add(null, element); - } - if (aCell != null) - cells.add(aCell); - } - } while (cellTokenizer.hasMoreElements()); - retCells = new CellHandle[cells.size()]; - retCells = (CellHandle[]) cells.toArray(retCells); - return retCells; - } - - /** - * Strip sheet name(s) from range string can be Sheet1!AB:Sheet!BC or - * Sheet!AB:BC or AB:BC or Sheet1:Sheet2!A1:A2 - * - * @param address - * or range String - * @return 1st sheetname - * - * Ok, this is a strange method. It returns a string array of the - * following format 0 - sheetname1 1 - cell address or range (what - * if there are 2?) 2 - sheetname2 3 - external link 1 ?? some ooxml - * record 4 - external link 2 - */ - public static String[] stripSheetNameFromRange(String address) { - String sheetname = null, sheetname2 = null; - int m = address.indexOf('!'); - if (m > -1) { - if (address.substring(0, m).indexOf(":") == -1) - sheetname = address.substring(0, m); - else { - int z = address.indexOf(":"); - sheetname = address.substring(0, z); - sheetname2 = address.substring(z + 1, m); - } - } - address = address.substring(m + 1); - int n = address.indexOf('!'); // see if 2nd sheet name exists - if (n > -1 && !address.equals("#REF!")) { - m = address.indexOf(':'); - sheetname2 = address.substring(m + 1, n); - m = address.indexOf(':'); - address = address.substring(0, m + 1) + address.substring(n + 1); - } - // 20090323 KSC: handle external references (OOXML-Specific format of - // [#]SheetName!Ref where # denotes ExternalLink workbook - String exLink1 = null, exLink2 = null; - if (sheetname != null && sheetname.indexOf('[') >= 0) { // External - // OOXML - // reference - exLink1 = sheetname.substring(sheetname.indexOf('[')); - exLink1 = exLink1.substring(0, exLink1.indexOf(']') + 1); - sheetname = StringTool.replaceText(sheetname, exLink1, ""); - if (sheetname.equals("")) - sheetname = null; // possible to have address in form of = - // [#]!Name or range - } - if (sheetname2 != null && sheetname2.indexOf('[') >= 0) { // External - // OOXML - // reference - exLink2 = sheetname2.substring(sheetname2.indexOf('[')); - exLink2 = exLink2.substring(0, exLink2.indexOf(']') + 1); - sheetname2 = StringTool.replaceText(sheetname2, exLink2, ""); - if (sheetname2.equals("")) - sheetname2 = null; // possible to have address in form of = - // [#]!Name or range - } - // return new String[]{sheetname, address, sheetname2}; - return new String[] { sheetname, address, sheetname2, exLink1, exLink2 }; // 20090323 - // KSC: - // add - // any - // external - // link - // info - } - - /** - * return the first and last coords of a range in int form + the number of - * cells in the range range is in the format of Sheet - * - */ - public static int[] getRangeCoords(String range) { - int numrows = 0; - int numcols = 0; - int numcells = 0; - int[] coords = new int[5]; - String temprange = range; - // figure out the sheet bounds using the range string - temprange = ExcelTools.stripSheetNameFromRange(temprange)[1]; - String startcell = "", endcell = ""; - int lastcolon = temprange.lastIndexOf(":"); - endcell = temprange.substring(lastcolon + 1); - if (lastcolon == -1) // no range - startcell = endcell; - else - startcell = temprange.substring(0, lastcolon); - startcell = StringTool.strip(startcell, "$"); - endcell = StringTool.strip(endcell, "$"); - - // get the first cell's coordinates - int charct = startcell.length(); - while (charct > 0) { - if (!Character.isDigit(startcell.charAt(--charct))) { - charct++; - break; - } - } - String firstcellrowstr = startcell.substring(charct); - int firstcellrow = -1; - try { - firstcellrow = Integer.parseInt(firstcellrowstr); - } catch (NumberFormatException e) { // could be a whole-col-style ref - } - String firstcellcolstr = startcell.substring(0, charct).trim(); - int firstcellcol = ExcelTools.getIntVal(firstcellcolstr); - // get the last cell's coordinates - charct = endcell.length(); - while (charct > 0) { - if (!Character.isDigit(endcell.charAt(--charct))) { - charct++; - break; - } - } - String lastcellrowstr = endcell.substring(charct); - int lastcellrow = -1; - try { - lastcellrow = Integer.parseInt(lastcellrowstr); - } catch (NumberFormatException e) { // could be a whole-col-style ref - } - String lastcellcolstr = endcell.substring(0, charct); - int lastcellcol = ExcelTools.getIntVal(lastcellcolstr); - numrows = (lastcellrow - firstcellrow) + 1; - numcols = (lastcellcol - firstcellcol) + 1; - /* - * if(numrows == 0)numrows =1; if(numcols == 0)numcols =1; - */ - numcells = numrows * numcols; - if (numcells < 0) - numcells *= -1; // handle swapped cells ie: "B1:A1" - - coords[0] = firstcellrow; - coords[1] = firstcellcol; - coords[2] = lastcellrow; - coords[3] = lastcellcol; - coords[4] = numcells; - // Trap errors in range - // if (firstcellrow < 0 || lastcellrow < 0 || firstcellcol < 0 || - // lastcellcol < 0) - // Logger.logErr("ExcelTools.getRangeCoords: Error in Range " + range); - return coords; - } + + + /** + * A FAIL FAST implementation for finding whether a cell string address + * falls within a set of row/col range coordinates. + *

        + * Sep 21, 2010 + * + * @param rng the range you want to test + * @param rowFirst in the target range + * @param rowLast in the target range + * @param colFirst in the target range + * @param colLast in the target range + * @return + */ + public static boolean isInRange(String rng, int rowFirst, int rowLast, + int colFirst, int colLast) { + int[] sh = io.starter.OpenXLS.ExcelTools.getRowColFromString(rng); + + // the guantlet + if (sh[1] < colFirst) + return false; + if (sh[1] > colLast) + return false; + if (sh[0] < rowFirst) + return false; + return sh[0] <= rowLast;// passes! + + } + + /** + * returns true if range intersects with range2 + * + * @param rng + * @param rc + * @return + */ + public static boolean intersects(String rng, int[] rc) { + int[] rc2 = ExcelTools.getRangeCoords(rng); + return (rc[0] >= rc2[0]) && (rc[2] <= rc2[2]) && (rc[1] >= rc2[1]) + && (rc[3] <= rc2[3]); + } + + /** + * returns true if address is before the range coordinates defined by rc + * + * @param rc row col of address + * @param rng int[] coordinates as: row0, col0, row1, col1 + * @return true if address is before the range coordinates + */ + public static boolean isBeforeRange(int[] rc, int[] rng) { + return rc[0] < rng[0] || (rc[0] == rng[0] && rc[1] < rng[1]); + } + + /** + * returns true if address is before the range coordinates defined by rc + * + * @param rc row col of address + * @param rng int[] coordinates as: row0, col0, row1, col1 + * @return true if address is before the range coordinates + */ + public static boolean isAfterRange(int[] rc, int[] rng) { + return rc[0] > rng[2] || (rc[0] == rng[2] && rc[1] > rng[3]); + } + + /** + * Takes an input Object and attempts to convert to numeric Objects of the + * highest precision possible. + *

        + * This method is useful for avoiding the Excel warnings + * "Number Stored As Text" when storing string data that contains numbers. + *

        + * NOTE: this method is useful for ensuring that Formula references contain + * true numeric values as not all String numbers are properly interpreted in + * Formula engines, and can silently fail. + *

        + * For this reason, always use numeric, non-string values to calculated + * cells. + * + * @param input + * @return + */ + public static Object getObject(Object in) { + // do not record -- only called from other methods + if (!(in instanceof String)) { + return in; + } + + String input = String.valueOf(in); + Object ret = input; // default is the original string + + try { + ret = new Double(input); + return ret; + } catch (NumberFormatException ex) { + try { + ret = new Float(input); + return ret; + } catch (NumberFormatException ex2) { + try { + ret = Integer.valueOf(input); + return ret; + } catch (NumberFormatException ex3) { + // ret Is set outside of loop incase no match is ever found. + } + } + } + + // list of formatting chars to check for + String[][] fmtlist = {{"$", ","}, {",", ","}, {"%", ","}}; + + // strip the formatting + for (int t = 0; t < fmtlist.length; t++) { + if (input.indexOf(fmtlist[t][0]) > -1) { // contains! + String converted = StringTool.replaceText(input, fmtlist[t][0], + ""); // strip first token (ie: '$') + converted = StringTool + .replaceText(converted, fmtlist[t][1], ""); // strip + // second + // token + // (ie: ',') + try { + ret = new Double(converted); + return ret; + } catch (NumberFormatException ex) { + try { + ret = new Float(converted); + return ret; + } catch (NumberFormatException ex2) { + try { + ret = Integer.valueOf(converted); + return ret; + } catch (NumberFormatException ex3) { + // ret Is set outside of loop incase no match is + // ever found. + } + } + } + } + + } + + return ret; + } + + /** + * convert twips to pixels + *

        + *

        + * In addition to a calculated size unit derived from the average size of + * the default characters 0-9, Excel uses the 'twips' measurement which is + * defined as: + *

        + * 1 twip = 1/20 point or 20 twips = 1 point 1 twip = 1/567 centimeter or + * 567 twips = 1 centimeter 1 twip = 1/1440 inch or 1440 twips = 1 inch + *

        + *

        + * 1 pixel = 0.75 points 1 pixel * 1.3333 = 1 point 1 twip * 20 = 1 point + * + * @param pixels + * @return twips + */ + public static final float getPixels(float twips) { + float points = twips / 20; + float pixels = points * 1.3333333f; // good enuff precision + return pixels; + } + + /** + * convert pixels to twips + *

        + *

        + * In addition to a calculated size unit derived from the average size of + * the default characters 0-9, Excel uses the 'twips' measurement which is + * defined as: + *

        + * 1 pixel = 0.75 points 1 pixel * 1.3333 = 1 point 1 twip * 20 = 1 point + * + * @param pixels + * @return twips + */ + public static final float getTwips(float pixels) { + float points = pixels * .75f; + float twips = points * 20; + return twips; + } + + /** + * get recordy byte def as a String + *

        + * public static String getRecordByteDef(XLSRecord rec){ byte[] b = + * rec.read(); StringBuffer sb = new StringBuffer("byte[] rbytes = {"); + * for(int t = 0;t + * Byte thisb = new Byte(b[t]); + *

        + * sb.append(thisb.toString() + ", "); } sb.append("};"); return + * sb.toString(); } + */ + + public static String getLogDate() { + return String.valueOf(new Date(System.currentTimeMillis())); + } + + /** + * tracks minimal info container for counters -> start time, last time, + * start mem, last mem + * + * @param info + * @param perfobj + */ + public static void benchmark(String info, Object perfobj) { + Runtime rt = Runtime.getRuntime(); + long[] p = null; + long lasttime = 0l, lastmem = 0l; + if (System.getProperties().get(perfobj.toString()) != null) { + p = (long[]) System.getProperties().get(perfobj.toString()); + lasttime = p[1]; + lastmem = p[3]; + p[1] = System.currentTimeMillis(); + p[3] = rt.freeMemory(); + double elapsedsec = p[1] - lasttime; + double usedmem = lastmem - p[3]; // - lastmem; + if (usedmem < 0) + usedmem *= -1; + Logger.logInfo(getLogDate() + " " + info); + Logger.logInfo(" time: " + elapsedsec + " millis"); + Logger.logInfo(" mem: " + usedmem + " bytes."); + } else { + p = new long[4]; + p[0] = System.currentTimeMillis(); + p[1] = System.currentTimeMillis(); + p[2] = rt.freeMemory(); + p[3] = rt.freeMemory(); + lasttime = p[1]; + lastmem = p[3]; + System.getProperties().put(perfobj.toString(), p); + } + + } + + /** + * get the bytes from a Vector of objects public byte[] + * getBytesFrom(CompatibleVector objs){ + *

        + * for(int t = 0;t + * } + *

        + *

        + * } + */ + + static char[] alpharr = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z'}; + /* + * NOT USED ANYMORE -- see getAlphaVal -- OK to remove?? + */ + public static final String[] ALPHASDELETE = {"A", "B", "C", "D", "E", "F", + "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", + "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB", "AC", "AD", "AE", + "AF", "AG", "AH", "AI", "AJ", "AK", "AL", "AM", "AN", "AO", "AP", + "AQ", "AR", "AS", "AT", "AU", "AV", "AW", "AX", "AY", "AZ", "BA", + "BB", "BC", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BK", "BL", + "BM", "BN", "BO", "BP", "BQ", "BR", "BS", "BT", "BU", "BV", "BW", + "BX", "BY", "BZ", "CA", "CB", "CC", "CD", "CE", "CF", "CG", "CH", + "CI", "CJ", "CK", "CL", "CM", "CN", "CO", "CP", "CQ", "CR", "CS", + "CT", "CU", "CV", "CW", "CX", "CY", "CZ", "DA", "DB", "DC", "DD", + "DE", "DF", "DG", "DH", "DI", "DJ", "DK", "DL", "DM", "DN", "DO", + "DP", "DQ", "DR", "DS", "DT", "DU", "DV", "DW", "DX", "DY", "DZ", + "EA", "EB", "EC", "ED", "EE", "EF", "EG", "EH", "EI", "EJ", "EK", + "EL", "EM", "EN", "EO", "EP", "EQ", "ER", "ES", "ET", "EU", "EV", + "EW", "EX", "EY", "EZ", "FA", "FB", "FC", "FD", "FE", "FF", "FG", + "FH", "FI", "FJ", "FK", "FL", "FM", "FN", "FO", "FP", "FQ", "FR", + "FS", "FT", "FU", "FV", "FW", "FX", "FY", "FZ", "GA", "GB", "GC", + "GD", "GE", "GF", "GG", "GH", "GI", "GJ", "GK", "GL", "GM", "GN", + "GO", "GP", "GQ", "GR", "GS", "GT", "GU", "GV", "GW", "GX", "GY", + "GZ", "HA", "HB", "HC", "HD", "HE", "HF", "HG", "HH", "HI", "HJ", + "HK", "HL", "HM", "HN", "HO", "HP", "HQ", "HR", "HS", "HT", "HU", + "HV", "HW", "HX", "HY", "HZ", "IA", "IB", "IC", "ID", "IE", "IF", + "IG", "IH", "II", "IJ", "IK", "IL", "IM", "IN", "IO", "IP", "IQ", + "IR", "IS", "IT", "IU", "IV", "IW", "IX", "IY", "IZ",}; + + /** + * get the Excel-style Column alphabetical representation of an integer + * (0-based). + *

        + * for example: 0 = A 26= AA 701= ZZ 702= AAA 16383= XFD (max) + */ + public static String getAlphaVal(int i) { + String ret = ""; + int leftover = 0; + if (i > 701) { // has 3rd digit + int z = (i / 676) - 1; // -1 to account for 0-based + if ((i % 676) < 26) { // then "leftover" is actually 2nd digit + z--; + leftover = 676; + } + ret = String.valueOf(ExcelTools.alpharr[z]); + i = i % 676; + i += leftover; + } + if (i > 25) { // has 2nd digit + int z = (i / 26) - 1; // -1 to account for 0-based + ret = ret + ExcelTools.alpharr[z]; + i = i % 26; + } + + // bbennett: this raises AIOOB if i = -1. In this situation we don't + // care about alpha because it will just be in the message of the + // exception that flags cell as non existent. + ret += i < 0 ? Integer.toString(i) : String.valueOf(ExcelTools.alpharr[(i)]); + + return ret; + } + + /** + * get the int value of the Excel-style Column alpha representation. + * + * @param String column name + * @return int the 0-based column number + */ + public static int getIntVal(String c) { + c = c.toUpperCase(); + if (c.length() > 3) // max col value= XFD in Excel 2007 + return -1; + int i = c.length() - 1; + int ret = 0; + while (i >= 0) { // process least to most-sigificant dig + int z = 0; + char cc = c.charAt(i); + while (z < alpharr.length && cc != alpharr[z++]) + ; + z *= Math.pow(26, c.length() - i - 1); // 1-based col for computing + ret += z; + i--; + } + // make 0-based + ret--; + return ret; + } + + /** + * Parses an Excel cell address into row and column integers. + * + * @param address the address to parse, either A1 or R1C1 + * @return int[2]: [0] row index, [1] column index + * @throws IllegalArgumentException if the argument is not a valid address + */ + public static int[] getRowColFromString(String address) { + + if (address.indexOf("$") > -1) { + address = StringTool.strip(address, "$"); + } + if (address.indexOf("!") > -1) { + address = address.substring(address.indexOf("!") + 1); + } + if (address.indexOf(":") > -1) + return getRangeRowCol(address); + + char[] adrchars = address.toCharArray(); + int row = 0, col = 0; + int charpos = -1, numpos = -1; + boolean r1c1 = false; + for (int i = 0; i < adrchars.length; i++) { + if (Character.isDigit(adrchars[i])) { + if (numpos == -1) // its a number + numpos = i; + } else if (charpos == -1) { + charpos = i; + if (numpos >= 0) { // we have already set number and we now have + // a nondigit - R1C1 style + r1c1 = true; + // break, it's all over! + break; + } + } + } + + if (r1c1) { // it's a single cell ref + try { + // there's an R and a C, not adjacent + if (address.toUpperCase().indexOf("R") == 0) { // startwith R + String rx = address.substring(1, address.toUpperCase() + .indexOf("C")); + String cx = address.substring(address.toUpperCase() + .indexOf("C") + 1); + row = Integer.parseInt(rx); + col = Integer.parseInt(cx); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("illegal R1C1 address '" + + address + "'"); + } + } else { + row = 0; // -1 below + col = -1; + if (charpos == 0 && numpos > 0) { + String colval = address.substring(0, numpos); + col = getIntVal(colval); + if (col < 0) + throw new IllegalArgumentException("illegal column value '" + + colval + "' in address '" + address + "'"); + } + if (numpos >= 0) { + row = Integer.parseInt(address.substring(numpos)); + if (row < 1) + throw new IllegalArgumentException( + "row may not be negative in address '" + address + + "'"); + } else { // it's a wholecol ref + col = getIntVal(address); + if (col < 0) + throw new IllegalArgumentException("illegal column value '" + + address + "' in address '" + address + "'"); + } + } + + int[] ret = {row - 1, col}; + return ret; + } + + /** + * Parses an Excel cell range and returns the addresses as an int array. The + * range may not be qualified with sheet names. Strip them with + * {@link #stripSheetNameFromRange} before calling this method. If the + * argument is a single cell address it will be returned for both bounds. + * + * @param range the range to parse + * @return int[4]: [0] first row, [1] first column, [2] second row, [3] + * second column + * @throws IllegalArgumentException if the addresses are invalid + */ + public static int[] getRangeRowCol(String range) { + int colon = range.indexOf(":"); + + String firstloc; + String lastloc; + + if (colon > -1) { + firstloc = range.substring(0, colon); + lastloc = range.substring(colon + 1); + } else { + firstloc = range; + lastloc = range; + } + + int[] result = new int[4]; + int[] temp; + + temp = getRowColFromString(firstloc); + System.arraycopy(temp, 0, result, 0, 2); + + temp = getRowColFromString(lastloc); + System.arraycopy(temp, 0, result, 2, 2); + + return result; + } + + /** + * Takes an int array representing a row and column and formats it as a cell + * address. + *

        + * The index is zero-based. + *

        + * [0][0] is "A1" [1][1] is "B2" [2][2] is "C3" + * + * @param int[] the numeric range to convert + * @return String the string representation of the range + */ + public static String formatLocation(int[] rowCol) { + StringBuffer sb = new StringBuffer(getAlphaVal(rowCol[1])); + sb.append((rowCol[0] + 1)); + + // handle ranges + if (rowCol.length > 3) { + // 20090807: KSC: only a range if 1st is != 2nd cell :) + if (rowCol[0] == rowCol[2] && rowCol[1] == rowCol[3]) // it's a single address + return sb.toString(); + sb.append(":"); + sb.append(getAlphaVal(rowCol[3])); + sb.append((rowCol[2] + 1)); + } + + return sb.toString(); + } + + /** + * Takes an int array representing a row and column and formats it as a cell + * address, taking into account relative or absolute refs + *

        + * The index is zero-based. + *

        + * [0][0] is "A1", $A1, A$1 or $A$1 depending upon bRelRow or bRelCol [1][1] + * is "B2", $B1, B$1 or B$1 depending upon bRelRow or bRelCol [2][2] is + * "C3", $C1, C$1 or $C$1 depending upon bRelRow or bRelCol + * + * @param int[] the numeric range to convert + * @param bRelRow if true, no "$"s are added, relative row reference + * @param bRelCol if true, no "$"s are added, relative col reference + * @return String the string representation of the range + */ + public static String formatLocation(int[] s, boolean bRelRow, + boolean bRelCol) { + StringBuffer sb = new StringBuffer((bRelCol ? "" : "$")); + if (s[1] > -1) // account for WholeRow/WholeCol references + sb.append(getAlphaVal(s[1])); + + if (s[0] > -1) // account for WholeRow/WholeCol references + sb.append((bRelRow ? "" : "$") + (s[0] + 1)); + // 20090906 KSC: handle ranges + if (s.length > 3) { + if (s[0] == s[2] && s[1] == s[3]) // it's a single address + return sb.toString(); + sb.append(":"); + sb.append((bRelCol ? "" : "$")); + sb.append(getAlphaVal(s[3])); + sb.append((bRelRow ? "" : "$") + (s[2] + 1)); + } + + return sb.toString(); + } + + /** + * Takes an array of four shorts and formats it as a cell range. + *

        + * IE [0][3][1][4] would be "A2:B3" + * + * @param int[] the numeric range to convert + * @return String the string representation of the range + */ + public static String formatRange(int[] s) { + if (s.length != 4) + return "incorrect array size in ExcelTools.formatLocation"; + int[] temp = new int[2]; + temp[0] = s[1]; + temp[1] = s[0]; + String firstcell = formatLocation(temp); + temp[0] = s[3]; + temp[1] = s[2]; + String lastcell = formatLocation(temp); + return firstcell + ":" + lastcell; + } + + /** + * format a range as a string, range in format of [r][c][r1][c1] + * + * @param s + * @return String representation of the integers as a range, ie A1:B4 + */ + public static String formatRangeRowCol(int[] s) { + if (s.length != 4) + return "incorrect array size in ExcelTools.formatLocation"; + int[] temp = new int[2]; + temp[0] = s[0]; + temp[1] = s[1]; + String firstcell = formatLocation(temp); + temp[0] = s[2]; + temp[1] = s[3]; + String lastcell = formatLocation(temp); + return firstcell + ":" + lastcell; + } + + /** + * format a range as a string, range in format of [r][c][r1][c1] including + * relative address state + * + * @param s + * @param bRelAddresses contains relative row and col state for each rcr1c1 + * @return String representation of the integers as a range, ie A1:B4 + */ + public static String formatRangeRowCol(int[] s, boolean[] bRelAddresses) { + if (s.length != 4) + return "incorrect array size in ExcelTools.formatLocation"; + int[] temp = new int[2]; + temp[0] = s[0]; + temp[1] = s[1]; + String firstcell = formatLocation(temp, bRelAddresses[0], + bRelAddresses[1]); + temp[0] = s[2]; + temp[1] = s[3]; + String lastcell = formatLocation(temp, bRelAddresses[2], + bRelAddresses[3]); + // unfortunately, no formatLocation can do this. This is + // formattingRANGERowCol + // if (firstcell.equals(lastcell)) return firstcell; // 20090309 KSC: + return firstcell + ":" + lastcell; + } + + /** + * Transforms a string to an array of ints for evaluation purposes. For + * example, acdc == [0][2][3][2] + */ + public static int[] transformStringToIntVals(String trans) { + int[] intarr = new int[trans.length()]; + for (int i = 0; i < trans.length(); i++) { + char c = trans.charAt(i); + for (int x = 0; x < alpharr.length; x++) { + if (String.valueOf(c).equalsIgnoreCase( + String.valueOf(alpharr[x]))) { + intarr[i] = x; + } + } + } + return intarr; + } + + /** + * Formats a string representation of a numeric value as a string in the + * specified notation: + * + * @param int
        + * NOTATION_STANDARD = 0,
        + * NOTATION_SCIENTIFIC = 1,
        + * NOTATION_SCIENTIFIC_EXCEL = 2,
        + * EXTENXLS_NOTATION = 3 + *

        + *

        + * example: formatNumericNotation(1.23456E5, 0) returns a "123456" + * example: formatNumericNotation(123456, 1) returns "1.23456E5" + * example: formatNumericNotation(123456, 2) returns "1.23456E+5" + * example: formatNumericNotation(123456, 3) returns "1.23456E+5" + */ + public static String formatNumericNotation(String num, int notationType) { + // if (notationType > 2)return null; + boolean negative = false; + if (num.substring(0, 1).equals("-")) { + negative = true; + num = num.substring(1); + } + String preString, postString, fullString = ""; + switch (notationType) { + case 0: // NOTATION_STANDARD + int i = num.indexOf("E"); + if (i == -1) { // just return + if (num.substring(num.length() - 2).equals(".0")) { + num = num.substring(0, num.length() - 2); + } + if (negative) { + return "-" + num; + } + return num; + } + preString = num.substring(0, i); + CompatibleBigDecimal outNumD = new CompatibleBigDecimal(preString); + String exp = ""; + if (num.indexOf("+") == -1) { + exp = num.substring(i + 1); + } else { + exp = num.substring(i + 2); + } + int expNum = Integer.valueOf(exp).intValue(); + outNumD = new CompatibleBigDecimal(outNumD.movePointRight(expNum)); + // outNumD = outNumD.multiply(new CompatibleBigDecimal(Math.pow(10, + // expNum))); + // Logger.logInfo(String.valueOf(outNumD)); + // outNum = Math.r + + // check if we should be returning a whole number or a decimal + int moveLen = num.indexOf("E") - num.indexOf(".") - 1; + if (expNum >= moveLen) { + if (negative) { + return "-" + + Math.round(outNumD.doubleValue()); + } else { + return String.valueOf(Math.round(outNumD.doubleValue())); + } + } + Object[] args = new Object[0]; + // args[0] = outNumD; + Object res = ResourceLoader.executeIfSupported(outNumD, args, + "toPlainString"); + if (res != null) { + fullString = res.toString(); + } else { + fullString = outNumD.toCompatibleString(); + } + break; + case 1: // NOTATION_SCIENTIFIC + if (num.indexOf("E") != -1 && num.indexOf("+") == -1) { + fullString = num; + } else if (num.indexOf("+") != -1) { + preString = num.substring(0, num.indexOf("+")); + postString = num.substring(num.indexOf("+") + 1); + return preString + postString; + } else if (num.indexOf(".") != -1) { + int pos = num.indexOf("."); + preString = num.substring(0, 1) + "." + + num.substring(1, num.indexOf(".")); + CompatibleBigDecimal d = new CompatibleBigDecimal(num); + if (d.doubleValue() < 1 && d.doubleValue() != 0) { + // it is a very small value, ie 1.0E-10 + int counter = 0; + while (d.doubleValue() < 1) { + d = new CompatibleBigDecimal(d.movePointRight(1)); + counter++; + } + String retStr = d.toCompatibleString() + "E-" + counter; + return retStr; + } + postString = num.substring(num.indexOf(".") + 1); + fullString = preString + postString; + fullString = fullString + "E" + (pos - 1); + } else { + preString = num.substring(0, 1) + "."; + if (num.length() > 1) { + preString += num.substring(1); + } else { + preString += "0"; + } + fullString = preString + "E" + (num.length() - 1); + } + break; + case 2: // NOTATION_SCIENTIFIC_EXCEL + if (num.indexOf("E") != -1 && num.indexOf("+") != -1) { + fullString = num; + } else if (num.indexOf("E") != -1) { + preString = num.substring(0, num.indexOf("E") + 1); + postString = "+" + + num.substring(num.indexOf("E") + 1); + fullString = preString + postString; + } else if (num.indexOf(".") != -1) { + int pos = num.indexOf("."); + CompatibleBigDecimal d = new CompatibleBigDecimal(num); + if (d.doubleValue() < 1 && d.doubleValue() != 0) { + // it is a very small value, ie 1.0E-10 + int counter = 0; + while (d.doubleValue() < 1) { + d = new CompatibleBigDecimal(d.movePointRight(1)); + counter++; + } + String retStr = d.toCompatibleString() + "E-" + counter; + return retStr; + } + preString = num.substring(0, 1) + "." + + num.substring(1, num.indexOf(".")); + postString = num.substring(num.indexOf(".") + 1); + fullString = preString + postString; + fullString = fullString + "E+" + (pos - 1); + } else { + preString = num.substring(0, 1) + "."; + if (num.length() > 1) { + preString += num.substring(1); + } else { + preString += "0"; + } + fullString = preString + "E+" + (num.length() - 1); + } + break; + default: + return num; + } + if (negative) + fullString = "-" + fullString; + return fullString; + } + + /** + * Return an array of cell handles specified from the string passed in. + *

        + * Note that a CellHandle cannot exist for an empty cell, so the cells + * retrieved in this manner will be blank cells, not empty cells. + * + * @param cellstr - a comma delimited String representing cells and cell ranges, + * example "A1,A5,A6,B1:B5" would return cells A1, A5, A6, B1, + * B2, B3, B4, B5 + * @param sheet the worksheet containing the cells. + * @return CellHandle[] + */ + public static CellHandle[] getCellHandlesFromSheet(String strRange, + WorkSheetHandle sheet) { + CellHandle[] retCells; + StringTokenizer cellTokenizer = new StringTokenizer(strRange, ","); + ArrayList cells = new ArrayList(); + do { + String element = (String) cellTokenizer.nextElement(); + if (element.indexOf(":") != -1) { + CellRange aRange = new CellRange(sheet.getSheetName() + "!" + + strRange, sheet.wbh, true); + cells.addAll(aRange.getCellList()); + } else { + CellHandle aCell = null; + try { + aCell = sheet.getCell(element); + } catch (Exception ce) { + aCell = sheet.add(null, element); + } + if (aCell != null) + cells.add(aCell); + } + } while (cellTokenizer.hasMoreElements()); + retCells = new CellHandle[cells.size()]; + retCells = cells.toArray(retCells); + return retCells; + } + + /** + * Strip sheet name(s) from range string can be Sheet1!AB:Sheet!BC or + * Sheet!AB:BC or AB:BC or Sheet1:Sheet2!A1:A2 + * + * @param address or range String + * @return 1st sheetname + *

        + * Ok, this is a strange method. It returns a string array of the + * following format 0 - sheetname1 1 - cell address or range (what + * if there are 2?) 2 - sheetname2 3 - external link 1 ?? some ooxml + * record 4 - external link 2 + */ + public static String[] stripSheetNameFromRange(String address) { + String sheetname = null, sheetname2 = null; + int m = address.indexOf('!'); + if (m > -1) { + if (address.substring(0, m).indexOf(":") == -1) + sheetname = address.substring(0, m); + else { + int z = address.indexOf(":"); + sheetname = address.substring(0, z); + sheetname2 = address.substring(z + 1, m); + } + } + address = address.substring(m + 1); + int n = address.indexOf('!'); // see if 2nd sheet name exists + if (n > -1 && !address.equals("#REF!")) { + m = address.indexOf(':'); + sheetname2 = address.substring(m + 1, n); + m = address.indexOf(':'); + address = address.substring(0, m + 1) + address.substring(n + 1); + } + // 20090323 KSC: handle external references (OOXML-Specific format of + // [#]SheetName!Ref where # denotes ExternalLink workbook + String exLink1 = null, exLink2 = null; + if (sheetname != null && sheetname.indexOf('[') >= 0) { // External + // OOXML + // reference + exLink1 = sheetname.substring(sheetname.indexOf('[')); + exLink1 = exLink1.substring(0, exLink1.indexOf(']') + 1); + sheetname = StringTool.replaceText(sheetname, exLink1, ""); + if (sheetname.equals("")) + sheetname = null; // possible to have address in form of = + // [#]!Name or range + } + if (sheetname2 != null && sheetname2.indexOf('[') >= 0) { // External + // OOXML + // reference + exLink2 = sheetname2.substring(sheetname2.indexOf('[')); + exLink2 = exLink2.substring(0, exLink2.indexOf(']') + 1); + sheetname2 = StringTool.replaceText(sheetname2, exLink2, ""); + if (sheetname2.equals("")) + sheetname2 = null; // possible to have address in form of = + // [#]!Name or range + } + // return new String[]{sheetname, address, sheetname2}; + return new String[]{sheetname, address, sheetname2, exLink1, exLink2}; // 20090323 + // KSC: + // add + // any + // external + // link + // info + } + + /** + * return the first and last coords of a range in int form + the number of + * cells in the range range is in the format of Sheet + */ + public static int[] getRangeCoords(String range) { + int numrows = 0; + int numcols = 0; + int numcells = 0; + int[] coords = new int[5]; + String temprange = range; + // figure out the sheet bounds using the range string + temprange = ExcelTools.stripSheetNameFromRange(temprange)[1]; + String startcell = "", endcell = ""; + int lastcolon = temprange.lastIndexOf(":"); + endcell = temprange.substring(lastcolon + 1); + if (lastcolon == -1) // no range + startcell = endcell; + else + startcell = temprange.substring(0, lastcolon); + startcell = StringTool.strip(startcell, "$"); + endcell = StringTool.strip(endcell, "$"); + + // get the first cell's coordinates + int charct = startcell.length(); + while (charct > 0) { + if (!Character.isDigit(startcell.charAt(--charct))) { + charct++; + break; + } + } + String firstcellrowstr = startcell.substring(charct); + int firstcellrow = -1; + try { + firstcellrow = Integer.parseInt(firstcellrowstr); + } catch (NumberFormatException e) { // could be a whole-col-style ref + } + String firstcellcolstr = startcell.substring(0, charct).trim(); + int firstcellcol = ExcelTools.getIntVal(firstcellcolstr); + // get the last cell's coordinates + charct = endcell.length(); + while (charct > 0) { + if (!Character.isDigit(endcell.charAt(--charct))) { + charct++; + break; + } + } + String lastcellrowstr = endcell.substring(charct); + int lastcellrow = -1; + try { + lastcellrow = Integer.parseInt(lastcellrowstr); + } catch (NumberFormatException e) { // could be a whole-col-style ref + } + String lastcellcolstr = endcell.substring(0, charct); + int lastcellcol = ExcelTools.getIntVal(lastcellcolstr); + numrows = (lastcellrow - firstcellrow) + 1; + numcols = (lastcellcol - firstcellcol) + 1; + /* + * if(numrows == 0)numrows =1; if(numcols == 0)numcols =1; + */ + numcells = numrows * numcols; + if (numcells < 0) + numcells *= -1; // handle swapped cells ie: "B1:A1" + + coords[0] = firstcellrow; + coords[1] = firstcellcol; + coords[2] = lastcellrow; + coords[3] = lastcellcol; + coords[4] = numcells; + // Trap errors in range + // if (firstcellrow < 0 || lastcellrow < 0 || firstcellcol < 0 || + // lastcellcol < 0) + // Logger.logErr("ExcelTools.getRangeCoords: Error in Range " + range); + return coords; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/FormatCache.java b/src/main/java/io/starter/OpenXLS/FormatCache.java index c85d711..09943e3 100644 --- a/src/main/java/io/starter/OpenXLS/FormatCache.java +++ b/src/main/java/io/starter/OpenXLS/FormatCache.java @@ -2,74 +2,73 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ -/** +/** * FormatCache.java - * - * - * */ package io.starter.OpenXLS; -import java.util.*; import io.starter.toolkit.Logger; +import java.util.Iterator; +import java.util.Map; + /** Handles the caching of the Formats - - This class is no longer in use nor needed - * + + This class is no longer in use nor needed + * *@deprecated */ // is this a valid class anymore? public class FormatCache { - - Map mpx = new java.util.HashMap(); - - /** Consolidate all identical formats to avoid too many formats errors - * - * @deprecated - */ - public void pack() { - Iterator itx = mpx.keySet().iterator(); - while(itx.hasNext()) { - Object oby = mpx.get(itx.next()); - FormatHandle thisfmt = (FormatHandle)oby; - thisfmt = this.get(thisfmt); - } - } - /** - * @deprecated - * @return - */ - public FormatHandle get(FormatHandle fmx) { - String fmt = fmx.toString(); - if(!mpx.containsKey(fmt)){ - Logger.logErr("missing in cache: FH " + fmt); - } - FormatHandle ret = (FormatHandle)mpx.get(fmt); - return ret; - } - - /** + Map mpx = new java.util.HashMap(); + + /** Consolidate all identical formats to avoid too many formats errors + * + * @deprecated + */ + public void pack() { + Iterator itx = mpx.keySet().iterator(); + while (itx.hasNext()) { + Object oby = mpx.get(itx.next()); + FormatHandle thisfmt = (FormatHandle) oby; + thisfmt = this.get(thisfmt); + } + } + + /** + * @deprecated + * @return + */ + public FormatHandle get(FormatHandle fmx) { + String fmt = fmx.toString(); + if (!mpx.containsKey(fmt)) { + Logger.logErr("missing in cache: FH " + fmt); + } + FormatHandle ret = (FormatHandle) mpx.get(fmt); + return ret; + } + + /** * @deprecated * @return */ @@ -77,14 +76,14 @@ public Object get(String f) { Object myo = mpx.get(f); return myo; } - + /** * @deprecated * @return */ public int getInt(String f) { int findex = -1; - if(mpx.containsKey(f)) findex = ((Integer)mpx.get(f)).intValue(); + if (mpx.containsKey(f)) findex = ((Integer) mpx.get(f)).intValue(); return findex; } diff --git a/src/main/java/io/starter/OpenXLS/FormatHandle.java b/src/main/java/io/starter/OpenXLS/FormatHandle.java index a796f3a..bf3df68 100644 --- a/src/main/java/io/starter/OpenXLS/FormatHandle.java +++ b/src/main/java/io/starter/OpenXLS/FormatHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,14 +23,18 @@ package io.starter.OpenXLS; import io.starter.formats.OOXML.Fill; +import io.starter.formats.XLS.Font; import io.starter.formats.XLS.*; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.*; -import java.util.*; -import java.awt.Color; +import io.starter.toolkit.CompatibleVector; +import io.starter.toolkit.Logger; +import io.starter.toolkit.StringTool; import org.json.JSONException; import org.json.JSONObject; +import java.awt.Color; +import java.util.List; +import java.util.*; + /** * Provides methods for querying and changing cell formatting information. Cell * formating includes fonts, borders, text alignment, background colors, cell @@ -47,2857 +51,2786 @@ * FormatHandle sets the default formats for the row or column. The default * format for a row or column is the format which appears when no other formats * for the cell are specified, such as for newly created cells. - * */ public class FormatHandle implements Handle, FormatConstants { - private CompatibleVector mycells = new CompatibleVector(); // all the Cells sharing this format e.g. CellRange - private Xf myxf; - private int xfe; -// see Xf.usedCount instead private boolean canModify = false; - private ColHandle mycol = null; - private RowHandle myrow = null; - private boolean writeImmediate = false; - boolean underlined = false; - - private io.starter.formats.XLS.WorkBook wkbook; - private io.starter.OpenXLS.WorkBook wbh; - - public static final Map numericFormatMap; - public static final Map currencyFormatMap; - public static final Map dateFormatMap; - - static { - Map formats = new HashMap(); - for (String[] formatArr : FormatConstants.NUMERIC_FORMATS) { - if (formatArr.length == 3) { - formats.put(formatArr[0].toLowerCase(), formatArr[2]); - } - } - numericFormatMap = Collections.unmodifiableMap(formats); - - formats = new HashMap(); - for (String[] formatArr : FormatConstants.CURRENCY_FORMATS) { - if (formatArr.length == 3) { - formats.put(formatArr[0].toLowerCase(), formatArr[2]); - } - } - currencyFormatMap = Collections.unmodifiableMap(formats); - - formats = new HashMap(); - for (String[] formatArr : FormatConstants.DATE_FORMATS) { - if (formatArr.length == 3) { - formats.put(formatArr[0].toLowerCase(), formatArr[2]); - } else { - // only 2 elements in date format string array: pattern and hex id - formats.put(formatArr[0].toLowerCase(), formatArr[0]); - } - } - dateFormatMap = Collections.unmodifiableMap(formats); - } - - /** - * Nullary constructor for use in bean context. This is not part of the - * public API and should not be called. - */ - public FormatHandle() { - } - - /** - * Constructs a FormatHandle for the given WorkBook and format record. - * This is not part of the public API and should not be called. - */ - public FormatHandle(io.starter.OpenXLS.WorkBook book, Xf xfr) { - myxf = xfr; - xfe = myxf.getIdx(); - wbh = book; - if (book != null) { - wkbook = book.getWorkBook(); - } else { - if (xfr.getWorkBook() != null) { - wkbook = xfr.getWorkBook(); - } else { - Logger.logErr("FormatHandle constructed with null WorkBook."); - } - } - } - - /** - * Constructs a FormatHandle for the given WorkBook's default format. - */ - public FormatHandle(io.starter.OpenXLS.WorkBook book) { - this(book, book.getWorkBook().getDefaultIxfe()); - } - - /** Constructs a FormatHandle for the given format ID and WorkBook. - * This is useful for creating a FormatHandle with the same parameters as - * a cell that does not refer to the cell. For example: - *

        -	 * CellHandle cell = <get cell here>;
        -	 * FormatHandle format = new FormatHandle(
        -	 *         cell.getWorkBook(), cell.getFormatId() );
        -	 * 
        - * - * @param book the WorkBook from which the format should be retrieved - * @param xfnum the ID of the format - */ - public FormatHandle(io.starter.formats.XLS.WorkBook book, int xfnum) { - wkbook = book; - if (xfnum > -1 && xfnum < wkbook.getNumXfs()) { - myxf = wkbook.getXf(xfnum); - xfe = myxf.getIdx(); - } - if (myxf == null) { // add new xf if necessary - myxf = duplicateXf(null); - } - myxf.getFont(); // will set to default (0th) font if not already set - } - - /** Constructs a FormatHandle for the given format ID and WorkBook. - * This is useful for creating a FormatHandle with the same parameters as - * a cell that does not refer to the cell. For example: - *
        -	 * CellHandle cell = <get cell here>;
        -	 * FormatHandle format = new FormatHandle(
        -	 *         cell.getWorkBook(), cell.getFormatId() );
        -	 * 
        - * - * @param book - * the WorkBook from which the format should be retrieved - * @param xfnum - * the ID of the format public FormatHandle(WorkBook book, int - * xfnum ,int x) { this(book.getWorkBook(),xfnum); wbh = book; } - */ - - /** - * Constructs a FormatHandle for the given format index and WorkBook. - * This is not part of the public API and should not be called. - */ - /* - * This constructor is just used from XML parsing, due to some dedupe - * errors. possibly errors in .xlsx too? - */ - protected FormatHandle(io.starter.OpenXLS.WorkBook book, int xfnum, - boolean dedupe) { - this(book, xfnum); - writeImmediate = dedupe; - } - - /** - * Constructs a dummy FormatHandle for the given conditional format. This - * is not part of the public API and should not be called. - *

        - * This unique flavor of FormatHandle is only used to display the formatting - * values of a Conditional format record. - * - * Creates a dummy Xf to store values, otherwise has no effect on the - * WorkBook record stream which are manipulated through CellHandle. - * - * @param book - * containing the conditional formats - * @param the - * index to the conditional format in the book collection - */ - protected FormatHandle(Condfmt cx, io.starter.OpenXLS.WorkBook book, - int xfnum, CellHandle c) { - cx.setFormatHandle(this); - xfe = xfnum; - wbh = book; - wkbook = book.getWorkBook(); - if (c == null) { - // ok, this is a horrible hack, as its only correct if the top left - // cell of the range has the same background format - // as the cell a user is hitting. Lame, but i've been handed this at - // the last moment and am patching things. weak effort guys. - try { - int[] rc = cx.getEncompassingRange(); - c = new CellHandle(cx.getSheet().getCell(rc[0], rc[1]), - (WorkBookHandle) book); - } catch (Exception e) { - } - ; - } - myxf = duplicateXf(book.getWorkBook().getXf(c.getFormatId())); - - // set the format from the cf - List lx = cx.getRules(); - Iterator itx = lx.iterator(); - while (itx.hasNext()) { - Cf format = (Cf) itx.next(); - this.updateFromCF(format, book); - } - } - - /** - * updates this format handle via a Cf rule - * - * @param cf - * Cf rule - * @param book - * workbook - */ - protected void updateFromCF(Cf cf, io.starter.OpenXLS.WorkBook book) { - // border colors - Color[] clr = cf.getBorderColors(); - if (clr != null) - setBorderColors(clr); - - // line style - int[] xs = cf.getBorderStyles(); - if (xs != null) - this.setBorderLineStyle(xs); - - /* - * // cf.getBorderSizes() int[] b = cf.getBorderSizes(); if(b!=null) - * setBorderLineStyle(b); - */ - // cf.getFont() - Font f = cf.getFont(); - if (f != null) - setFont(f); - else - this.setFontHeight(180); // why???????? - - if (cf.getFontItalic()) - this.setItalic(true); - - if (cf.getFontStriken()) - this.setStricken(true); - - int fsup = cf.getFontEscapement(); - // super/sub (0 = none, 1 = super, 2 = sub) - if (fsup > -1) - this.setScript(fsup); - - // handle underlines - int us = cf.getFontUnderlineStyle(); - - if (us > -1) { - this.setUnderlineStyle(us); - this.setUnderlined(true); - } - - // number cf - if (cf.getFormatPattern() != null) - setFormatPattern(cf.getFormatPattern()); - - if (cf.getFill() != null) { - this.setFill(cf.getFill()); - } else { - int fill = cf.getPatternFillStyle(); // Now -1 is a valid entry: - if (fill > -1) { + private CompatibleVector mycells = new CompatibleVector(); // all the Cells sharing this format e.g. CellRange + private Xf myxf; + private int xfe; + // see Xf.usedCount instead private boolean canModify = false; + private ColHandle mycol = null; + private RowHandle myrow = null; + private boolean writeImmediate = false; + boolean underlined = false; + + private io.starter.formats.XLS.WorkBook wkbook; + private io.starter.OpenXLS.WorkBook wbh; + + public static final Map numericFormatMap; + public static final Map currencyFormatMap; + public static final Map dateFormatMap; + + static { + Map formats = new HashMap(); + for (String[] formatArr : FormatConstants.NUMERIC_FORMATS) { + if (formatArr.length == 3) { + formats.put(formatArr[0].toLowerCase(), formatArr[2]); + } + } + numericFormatMap = Collections.unmodifiableMap(formats); + + formats = new HashMap(); + for (String[] formatArr : FormatConstants.CURRENCY_FORMATS) { + if (formatArr.length == 3) { + formats.put(formatArr[0].toLowerCase(), formatArr[2]); + } + } + currencyFormatMap = Collections.unmodifiableMap(formats); + + formats = new HashMap(); + for (String[] formatArr : FormatConstants.DATE_FORMATS) { + if (formatArr.length == 3) { + formats.put(formatArr[0].toLowerCase(), formatArr[2]); + } else { + // only 2 elements in date format string array: pattern and hex id + formats.put(formatArr[0].toLowerCase(), formatArr[0]); + } + } + dateFormatMap = Collections.unmodifiableMap(formats); + } + + /** + * Nullary constructor for use in bean context. This is not part of the + * public API and should not be called. + */ + public FormatHandle() { + } + + /** + * Constructs a FormatHandle for the given WorkBook and format record. + * This is not part of the public API and should not be called. + */ + public FormatHandle(io.starter.OpenXLS.WorkBook book, Xf xfr) { + myxf = xfr; + xfe = myxf.getIdx(); + wbh = book; + if (book != null) { + wkbook = book.getWorkBook(); + } else { + if (xfr.getWorkBook() != null) { + wkbook = xfr.getWorkBook(); + } else { + Logger.logErr("FormatHandle constructed with null WorkBook."); + } + } + } + + /** + * Constructs a FormatHandle for the given WorkBook's default format. + */ + public FormatHandle(io.starter.OpenXLS.WorkBook book) { + this(book, book.getWorkBook().getDefaultIxfe()); + } + + /** + * Constructs a FormatHandle for the given format ID and WorkBook. + * This is useful for creating a FormatHandle with the same parameters as + * a cell that does not refer to the cell. For example: + *

        +     * CellHandle cell = <get cell here>;
        +     * FormatHandle format = new FormatHandle(
        +     *         cell.getWorkBook(), cell.getFormatId() );
        +     * 
        + * + * @param book the WorkBook from which the format should be retrieved + * @param xfnum the ID of the format + */ + public FormatHandle(io.starter.formats.XLS.WorkBook book, int xfnum) { + wkbook = book; + if (xfnum > -1 && xfnum < wkbook.getNumXfs()) { + myxf = wkbook.getXf(xfnum); + xfe = myxf.getIdx(); + } + if (myxf == null) { // add new xf if necessary + myxf = duplicateXf(null); + } + myxf.getFont(); // will set to default (0th) font if not already set + } + + /** Constructs a FormatHandle for the given format ID and WorkBook. + * This is useful for creating a FormatHandle with the same parameters as + * a cell that does not refer to the cell. For example: + *
        +     * CellHandle cell = <get cell here>;
        +     * FormatHandle format = new FormatHandle(
        +     *         cell.getWorkBook(), cell.getFormatId() );
        +     * 
        + * + * @param book + * the WorkBook from which the format should be retrieved + * @param xfnum + * the ID of the format public FormatHandle(WorkBook book, int + * xfnum ,int x) { this(book.getWorkBook(),xfnum); wbh = book; } + */ + + /** + * Constructs a FormatHandle for the given format index and WorkBook. + * This is not part of the public API and should not be called. + */ + /* + * This constructor is just used from XML parsing, due to some dedupe + * errors. possibly errors in .xlsx too? + */ + protected FormatHandle(io.starter.OpenXLS.WorkBook book, int xfnum, + boolean dedupe) { + this(book, xfnum); + writeImmediate = dedupe; + } + + /** + * Constructs a dummy FormatHandle for the given conditional format. This + * is not part of the public API and should not be called. + *

        + * This unique flavor of FormatHandle is only used to display the formatting + * values of a Conditional format record. + *

        + * Creates a dummy Xf to store values, otherwise has no effect on the + * WorkBook record stream which are manipulated through CellHandle. + * + * @param book containing the conditional formats + * @param the index to the conditional format in the book collection + */ + protected FormatHandle(Condfmt cx, io.starter.OpenXLS.WorkBook book, + int xfnum, CellHandle c) { + cx.setFormatHandle(this); + xfe = xfnum; + wbh = book; + wkbook = book.getWorkBook(); + if (c == null) { + // ok, this is a horrible hack, as its only correct if the top left + // cell of the range has the same background format + // as the cell a user is hitting. Lame, but i've been handed this at + // the last moment and am patching things. weak effort guys. + try { + int[] rc = cx.getEncompassingRange(); + c = new CellHandle(cx.getSheet().getCell(rc[0], rc[1]), + book); + } catch (Exception e) { + } + } + myxf = duplicateXf(book.getWorkBook().getXf(c.getFormatId())); + + // set the format from the cf + List lx = cx.getRules(); + Iterator itx = lx.iterator(); + while (itx.hasNext()) { + Cf format = (Cf) itx.next(); + this.updateFromCF(format, book); + } + } + + /** + * updates this format handle via a Cf rule + * + * @param cf Cf rule + * @param book workbook + */ + protected void updateFromCF(Cf cf, io.starter.OpenXLS.WorkBook book) { + // border colors + Color[] clr = cf.getBorderColors(); + if (clr != null) + setBorderColors(clr); + + // line style + int[] xs = cf.getBorderStyles(); + if (xs != null) + this.setBorderLineStyle(xs); + + /* + * // cf.getBorderSizes() int[] b = cf.getBorderSizes(); if(b!=null) + * setBorderLineStyle(b); + */ + // cf.getFont() + Font f = cf.getFont(); + if (f != null) + setFont(f); + else + this.setFontHeight(180); // why???????? + + if (cf.getFontItalic()) + this.setItalic(true); + + if (cf.getFontStriken()) + this.setStricken(true); + + int fsup = cf.getFontEscapement(); + // super/sub (0 = none, 1 = super, 2 = sub) + if (fsup > -1) + this.setScript(fsup); + + // handle underlines + int us = cf.getFontUnderlineStyle(); + + if (us > -1) { + this.setUnderlineStyle(us); + this.setUnderlined(true); + } + + // number cf + if (cf.getFormatPattern() != null) + setFormatPattern(cf.getFormatPattern()); + + if (cf.getFill() != null) { + this.setFill(cf.getFill()); + } else { + int fill = cf.getPatternFillStyle(); // Now -1 is a valid entry: + if (fill > -1) { /* If the fill style is solid: When solid is specified, the foreground color (fgColor) is the only color rendered, even when a background color (bgColor) is also specified. */ - int bg = cf.getPatternFillColorBack(); - int fg = cf.getPatternFillColor(); - this.setFill(fill, fg, bg); - } else { - int fg = cf.getForegroundColor(); - if (fg > -1) - setForegroundColor(fg); - } - } - } - - /** - * Creates a FormatHandle for the given cell. This is not part of the - * public API and should not be called. Customers should use - * {@link CellHandle#getFormatHandle()} instead. - */ - public FormatHandle(CellHandle c) { - wkbook = c.getCell().getWorkBook(); - if (c.getCell().getXfRec() != null) { - myxf = c.getCell().getXfRec(); - xfe = myxf.getIdx(); // update the pointer - 20071010 KSC - } else { // ?? create new - // 20090512 KSC: Shigeo NPE error formaterror646694, create new - // rather than outputting warning - // Logger.logWarn("No XF for cell " + c.toString()); - myxf = wkbook.getXf(c.getCell().getIxfe()); - xfe = myxf.getIdx(); - } - // 20101201 KSC: only add to cache when adding xf's addToCache(); - } - - /** - * overrides the equals method to perform equality based on format - * properties ------------------------------------------------------------ - * - * @param Object - * another - the FormatHandle to compare with this FormatHandle - * @return true if this FormatHandle equals another - */ - public boolean equals(Object another) { - return another.toString().equals(toString()); - } - - /** - * Locks the cell attached to this FormatHandle for editing (makes - * read-only) lock cell and make read-only - * - * - * @param boolean locked - true if cells should be locked for this - * FormatHandle - */ - public void setLocked(boolean locked) { - Xf xf = cloneXf(myxf); - xf.setLocked(locked); - updateXf(xf); - } - - /** - * sets the cell attached to this FormatHandle to hide or show formula - * strings; - * - * @param boolean b- true if formulas should be hidden for this FormatHandle - */ - public void setFormulaHidden(boolean b) { - Xf xf = cloneXf(myxf); - xf.setFormulaHidden(b); - updateXf(xf); - } - - /** - * returns whether this FormatHandle is set to hide formula strings - * - * @return true if the formula strings are hidden, false otherwise - */ - public boolean isFormulaHidden() { - return myxf.isFormulaHidden(); - } - - /** - * returns whether this Format Handle specifies that cells are locked for - * changing - * - * @return true if cells are locked - */ - public boolean isLocked() { - return myxf.isLocked(); - } - - /** - * provides a mapping between Excel formats and Java formats
        - * see: http://java.sun.com/docs/books/tutorial/i18n/format - * /decimalFormat.html - *

        - * Note there are slight Excel-specific differences in the format strings - * returned. Several numeric and currency formats in excel have different - * formatting for postive and negative numbers. In these cases, the java - * format string is split by semicolons and may contain text [Red] which is - * to specify the negative number should be displayed in red. Remove this - * from the string before passing into the Format class; - * - *

        -	 *         G  Era designator  Text  AD  
        -	 *         y  Year  Year  1996; 96  
        -	 *         M  Month in year  Month  July; Jul; 07  
        -	 *         w  Week in year  Number  27  
        -	 *         W  Week in month  Number  2  
        -	 *         D  Day in year  Number  189  
        -	 *         d  Day in month  Number  10  
        -	 *         F  Day of week in month  Number  2  
        -	 *         E  Day in week  Text  Tuesday; Tue  
        -	 *         a  Am/pm marker  Text  PM  
        -	 *         H  Hour in day (0-23)  Number  0  
        -	 *         k  Hour in day (1-24)  Number  24  
        -	 *         K  Hour in am/pm (0-11)  Number  0  
        -	 *         h  Hour in am/pm (1-12)  Number  12  
        -	 *         m  Minute in hour  Number  30  
        -	 *         s  Second in minute  Number  55  
        -	 *         S  Millisecond  Number  978  
        -	 *         z  Time zone  General time zone  Pacific Standard Time; PST; GMT-08:00  
        -	 *         Z  Time zone  RFC 822 time zone  -0800
        -	 * 
        - * - * @return String the formatting pattern for the cell - */ - public String getJavaFormatString() { - String pat = getFormatPattern(); - if (pat==null)return null; - - // toLowerCase is a simplistic way to implement the case insensitivity - // of the pattern tokens. It could cause issues with string literals. - Object patty = convertFormatString(pat.toLowerCase()); - if (patty != null) - patty = StringTool.qualifyPatternString(patty.toString()); - if (myxf.isDatePattern()) { - if (patty != null) - return (String) patty; - else - return "M/d/yy h:mm"; - } - if (patty != null) - return (String) patty; - /* - * If we reached here, we don't have a mapping for this particular - * format. Send a warning to the system then make sure the pattern we - * are sending back is valid. Many excel patterns have 4 patterns - * separated by semicolons. We only can pass 2 into the formatter - * (positive and negative). This usually works out to be the first two - * patterns in the string. - */ - pat = StringTool.qualifyPatternString(pat.toString()); - int firstParens = pat.indexOf(";"); - if (firstParens != -1) { - int secondParens = pat.indexOf(";", firstParens + 1); - if (secondParens != -1) { - pat = pat.substring(0, secondParens); - } else { // yet another hackaround -jm - pat = pat.substring(firstParens + 2, pat.length() - 1); - } - } - return pat; - } - - /** - * converts an Excel-style format string to a Java Format string. - * - * @param String - * pattern - Excel Format String - * @return String that can be used with the Java Format classes. - * @see getJavaFormatString - */ - public static String convertFormatString(String pattern) { - String ret = (String) numericFormatMap.get(pattern); - if (ret != null) - return ret; - ret = (String) currencyFormatMap.get(pattern); - if (ret != null) - return ret; - ret = (String) dateFormatMap.get(pattern); - if (ret != null) - return ret; - return null; - } - - /* - * FIXME: Border Issues (marker) - * - * The methods to set individual border line styles do not follow a - * consistent naming convention with the rest of the border methods. - * - * There are no methods for getting or setting the inside borders. I don't - * know whether this is a problem or a design choice. It depends on whether - * the inside borders are stored in the format record or as separate cell - * formats. - * - * There are no methods to get the diagonal borders. There is a method to - * set the diagonal border line style, but it affects both diagonals. - */ - - /** - * sets the border color for all borders (top, left, bottom and right) from - * a Color array - *

        - * NOTE: this setting will affect every cell which refers to this - * FormatHandle - * - * @param java - * .awt.Color array - 4-element array of desired border colors - * [T, L, B, R] - */ - public void setBorderColors(Color[] bordercolors) { - Xf xf = cloneXf(myxf); - if (bordercolors[0] != null) - xf.setTopBorderColor(getColorInt(bordercolors[0])); - if (bordercolors[1] != null) - xf.setLeftBorderColor(getColorInt(bordercolors[1])); - if (bordercolors[2] != null) - xf.setBottomBorderColor(getColorInt(bordercolors[2])); - if (bordercolors[3] != null) - xf.setRightBorderColor(getColorInt(bordercolors[3])); - updateXf(xf); - } - - /** - * remove borders for this format - * - */ - public void removeBorders() { - Xf xf = cloneXf(myxf); - xf.removeBorders(); - updateXf(xf); - } - - /** - * sets the border color for all borders (top, left, bottom and right) from - * an int array containing color constants - * - *

        - * NOTE: this setting will affect every cell which refers to this - * FormatHandle - * - * @param int[] bordercolors - 4-element array of desired border color - * constants [T, L, B, R] - * @see FormatHandle.COLOR_* constants - */ - public void setBorderColors(int[] bordercolors) { - Xf xf = cloneXf(myxf); - xf.setTopBorderColor(bordercolors[0]); - xf.setLeftBorderColor(bordercolors[1]); - xf.setBottomBorderColor(bordercolors[2]); - xf.setRightBorderColor(bordercolors[3]); - updateXf(xf); - - } - - /** - * set the border color for all borders (top, left, bottom, and right) to - * one color via color constant - * - *

        - * NOTE: this setting will affect every cell which refers to this - * FormatHandle - * - * @param int x - color constant which represents the color to set all - * border sides - * @see FormatHandle.COLOR_* constants - */ - public void setBorderColor(int x) { - Xf xf = cloneXf(myxf); - xf.setRightBorderColor((short) x); - xf.setLeftBorderColor((short) x); - xf.setTopBorderColor((short) x); - xf.setBottomBorderColor((short) x); - updateXf(xf); - - } - - /** - * set the border color for all borders (top, left, bottom, and right) to - * one java.awt.Color - *

        - * NOTE: this setting will affect every cell which refers to this - * FormatHandle - * - * @param Color - * col - color to set all border sides - */ - public void setBorderColor(Color col) { - Xf xf = cloneXf(myxf); - short x = (short) getColorInt(col); - xf.setRightBorderColor(x); - xf.setLeftBorderColor(x); - xf.setTopBorderColor(x); - xf.setBottomBorderColor(x); - updateXf(xf); - - } - - /** - * Set the top border color - * - * @param color - * constant - */ - public void setBorderRightColor(int x) { - Xf xf = cloneXf(myxf); - xf.setRightBorderColor((short) x); - updateXf(xf); - } - - /** - * Set the top border color - * - * @param color - * constant - */ - public void setBorderRightColor(Color x) { - Xf xf = cloneXf(myxf); - xf.setRightBorderColor((short) getColorInt(x)); - updateXf(xf); - } - - /** - * Get the Right border color - * - * @return color constant - */ - public Color getBorderRightColor() { - if (myxf.getRightBorderLineStyle() == 0) - return null; - int x = myxf.getRightBorderColor(); - if (x < this.getWorkBook().colorTable.length) - return this.getWorkBook().getColorTable()[x]; - else - return this.getWorkBook().getColorTable()[0]; // black i'm afraid - } - - /** - * Set the top border color - * - * @param color - * constant - */ - public void setBorderLeftColor(int x) { - Xf xf = cloneXf(myxf); - xf.setLeftBorderColor((short) x); - updateXf(xf); - } - - /** - * Set the top border color - * - * @param color - * constant - */ - public void setBorderLeftColor(Color x) { - Xf xf = cloneXf(myxf); - xf.setLeftBorderColor((short) getColorInt(x)); - updateXf(xf); - } - - /** - * returns Border Colors of Cell ie: top, left, bottom, right - * - * returns null or 1 color for each of 4 sides - * - * 1,1,1,1 represents a border all around the cell 1,1,0,0 represents on the - * top left edge of the cell - * - * @return int array representing Cell borders - */ - public Color[] getBorderColors() { - Color[] colors = new Color[4]; - colors[0] = getBorderTopColor(); - colors[1] = getBorderLeftColor(); - colors[2] = getBorderBottomColor(); - colors[3] = getBorderRightColor(); - return colors; - } - - /** - * Get the Left border color - * - * @return color constant - */ - public Color getBorderLeftColor() { - if (myxf.getLeftBorderLineStyle() == 0) - return null; - return this.getWorkBook().getColorTable()[myxf.getLeftBorderColor()]; - } - - /** - * Set the top border color - * - * @param color - * constant - */ - public void setBorderTopColor(int x) { - Xf xf = cloneXf(myxf); - xf.setTopBorderColor(x); - updateXf(xf); - } - - /** - * Get the Top border color - * - * @return color constant - */ - public Color getBorderTopColor() { - if (myxf.getTopBorderLineStyle() == 0) - return null; - int xt = myxf.getTopBorderColor(); - if (xt > this.getWorkBook().getColorTable().length) // guards - xt = 0; - return this.getWorkBook().getColorTable()[xt]; - } - - /** - * Set the top border color - * - * @param color - * constant - */ - public void setBorderTopColor(Color x) { - Xf xf = cloneXf(myxf); - xf.setTopBorderColor(getColorInt(x)); - updateXf(xf); - } - - /** - * Set the top border color - * - * @param color - * constant - */ - public void setBorderBottomColor(int x) { - Xf xf = cloneXf(myxf); - xf.setBottomBorderColor(x); - updateXf(xf); - } - - /** - * Set the top border color - * - * @param color - * constant - */ - public void setBorderBottomColor(Color x) { - Xf xf = cloneXf(myxf); - xf.setBottomBorderColor(getColorInt(x)); - updateXf(xf); - } - - /** - * Returns true if the value should be red due to a combination of a format - * pattern and a negative number - * - * @return - */ - public boolean isRedWhenNegative() { - String pattern = myxf.getFormatPattern(); - if (pattern.indexOf("Red") > -1) - return true; - return false; - } - - /** - * Get the Right border color - * - * @return color constant - */ - public Color getBorderBottomColor() { - if (myxf.getBottomBorderLineStyle() == 0) - return null; - int x = myxf.getBottomBorderColor(); - if (x < this.getWorkBook().getColorTable().length) - return this.getWorkBook().getColorTable()[x]; - else - return this.getWorkBook().getColorTable()[0]; // black i'm afraid - - } - - /** - * Set the border line style - * - * @param line - * style constant - */ - public void setTopBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setTopBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * Get the border line style - * - * @return line style constant - */ - public int getTopBorderLineStyle() { - return myxf.getTopBorderLineStyle(); - } - - /** - * Set the border line style - * - * @param line - * style constant - */ - public void setBottomBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setBottomBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * Get the border line style - * - * @return line style constant - */ - public int getBottomBorderLineStyle() { - return myxf.getBottomBorderLineStyle(); - } - - /** - * Set the border line style - * - * @param line - * style constant - */ - public void setLeftBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setLeftBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * Get the border line style - * - * @return line style constant - */ - public int getLeftBorderLineStyle() { - return myxf.getLeftBorderLineStyle(); - } - - /** - * Set the border line style - * - * @param line - * style constant - */ - public void setRightBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setRightBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * Get the border line style - * - * @return line style constant - */ - public int getRightBorderLineStyle() { - return myxf.getRightBorderLineStyle(); - } - - /** - * Sets the border line style using static BORDER_ shorts within - * FormatHandle - * - * @param line - * style constant - */ - public void setBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * set border line styles via array of ints representing border styles - * order= left, right, top, bottom, [diagonal] - * - * - * - * @param b - * int[] - */ - public void setBorderLineStyle(int[] b) { - myxf.setAllBorderLineStyles(b); - } - - /** - * Set the border line style - * - * @param line - * style constant - */ - public void setBorderLineStyle(short x) { - Xf xf = cloneXf(myxf); - xf.setBorderLineStyle(x); - updateXf(xf); - } - - /** - * return the 5 border lines styles (l, r, t, b, diag) - * - * @return - */ - public int[] getAllBorderLineStyles() { - int[] ret = new int[5]; - ret[0] = myxf.getLeftBorderLineStyle(); - ret[1] = myxf.getRightBorderLineStyle(); - ret[2] = myxf.getTopBorderLineStyle(); - ret[3] = myxf.getBottomBorderLineStyle(); - ret[4] = myxf.getDiagBorderLineStyle(); - return ret; - } - - /** - * return the 5 border line colors (l, r, t, b, diag) - * - * @return - */ - public int[] getAllBorderColors() { - int[] ret = new int[5]; - ret[0] = myxf.getLeftBorderColor(); - ret[1] = myxf.getRightBorderColor(); - ret[2] = myxf.getTopBorderColor(); - ret[3] = myxf.getBottomBorderColor(); - ret[4] = myxf.getDiagBorderColor(); - return ret; - } - - /** - * Set the border line style - * - * @param line - * style constant - */ - public void setBorderDiagonal(int x) { - Xf xf = cloneXf(myxf); - xf.setBorderDiag(x); - updateXf(xf); - } - - /** - * Set a column handle on this format handle, so all changes applied to this - * format will be applied to the entire column - * - * @param c - */ - public void setColHandle(ColHandle c) { - mycol = c; - } - - /** - * Set a row handle on this format handle, so all changes applied to this - * format will be applied to the entire row - * - * @param c - */ - public void setRowHandle(RowHandle c) { - myrow = c; - } - - /** - * Create a copy of this FormatHandle with its own Xf - * - * @return the copied FormatHandle - */ - public Object clone() { - FormatHandle ret = null; - if (wbh == null) { // who knew??? - wkbook = myxf.getWorkBook(); // Changed to myxf since myfont is no - // longer - ret = new FormatHandle(); - ret.myxf = myxf; - ret.xfe = myxf.getIdx(); - ret.wkbook = wkbook; - } else { - ret = new FormatHandle(wbh, myxf); // no need to duplicate it - just - // use all formatting of - // original xf - } - - return ret; - } - - public String toString() { - return myxf.toString(); - } - - public FormatHandle(io.starter.OpenXLS.WorkBook book, String fontname, - int fontstyle, int fontsize) { - this(book); - setFont(fontname, fontstyle, fontsize); - } - - /** - * Jan 27, 2011 - * - * @param workBook - * @param i - */ - public FormatHandle(io.starter.OpenXLS.WorkBook workBook, int i) { - this(workBook.getWorkBook(), i); - wbh = workBook; - } - - /** - * /** Set the weight of the font in 1/20 point units 100-1000 range. 400 is - * normal, 700 is bold. - * - * @param wt - */ - public void setFontWeight(int wt) { - Font f = cloneFont(myxf.getFont()); - f.setFontWeight(wt); - updateFont(f); - } - - /** - * Get the font weight the weight of the font is in 1/20 point units - * - * @return - */ - public int getFontWeight() { - return myxf.getFont().getFontWeight(); - } - - /** - * Set the Font for this Format. - * - * As adding a new Font and format increases the file size, try using this - * once for each distinct font used in the file, then use 'setFormatId' to - * share this font with other Cells. - * - * Roughly matches the functionality of the java.awt.Font class. Currently - * the style parameter is only useful for bold/normal weights. Italics, - * underlines, etc must be modified elsewhere. - * - * Note that in order to maintain java.awt.Font compatibility for - * bold/normal styles, defaults for weight/style have been mapped to 0 = - * normal (excel 200 weight) and 1 = bold (excel 700 weight) - * - * @param String - * font name - * @param int font style - * @param int font size - */ - public void setFont(Font f) { - setXFToFont(f); - } - - /** - * Set new font to XF, handling duplication and caching ... - * - * @param Font - * f - */ - private void setXFToFont(Font f) { - int fti = addFontIfNecessary(f); - if (myxf == null) { // shouldn't!! - myxf = duplicateXf(null); - myxf.setFont(fti); - } else if (myxf.getIfnt() != fti) { // if not using font already, - // duplicate xf and set to new font - Xf xf = cloneXf(myxf); - xf.setFont(fti); - updateXf(xf); - } - - } - - /** - * Sets this format handle to a font - * @param fn font name e.g. 'Arial' - * @param stl font style either Font.PLAIN or Font.BOLD - * @param sz font size or height in 1/20 point units - * - */ - public void setFont(String fn, int stl, double sz) { - sz *= 20; - if (stl == 0) - stl = 200; - if (stl == 1) - stl = 700; - Font f = new Font(fn, stl, (int) sz); - setXFToFont(f); - } - - /** - * add font to record streamer if cant find exact font already in there - * @param f - * @return - */ - private int addFontIfNecessary(Font f) { - if (wkbook == null) { - Logger.logErr("AddFontIfNecessary: workbook is null"); - return -1; - } - int fti = wkbook.getFontIdx(f); - // don't use the built-ins. - if (fti == 3) - fti = 0; // use initial default font instead of last ... - if (fti == -1) { // font doesn't exist yet, add to streamer - f.setIdx(-1); // flag to insert - fti = wkbook.insertFont(f) + 1; - } else - f.setIdx(fti); - return fti; - } - - /** - * adds a font to the global font store only if exact font is not already - * present - * - * @param f - * Font - * @param bk - * WorkBookHandle - */ - public static int addFont(Font f, WorkBookHandle bk) { - if (bk == null) { - Logger.logErr("addFont: workbook is null"); - } - int fti = bk.getWorkBook().getFontIdx(f); - // if (fti > 3) {// don't use the built-ins. - if (fti == 3) - fti = 0; // use initial default font instead of last ... 20070827 - // KSC - if (fti == -1) { // font doesn't exist yet, add to streamer - f.setIdx(-1); // flag to insert - fti = bk.getWorkBook().insertFont(f) + 1; - } else - f.setIdx(fti); - return fti; - } - - /** - * Adds a font internally to the workbook - * - * @param f - * - * private void addFont(Font f) { - * - * } - */ - - /** - * Apply this Format to a Range of Cells - * - * @param CellRange - * to apply the format to - */ - public void addCellRange(CellRange cr) { - CellHandle[] crcells = cr.getCells(); - for (int t = 0; t < crcells.length; t++) { - addCell(crcells[t]); - } - } - - /** - * Apply this Format to a Range of Cells - * - * @param CellHandle - * array to apply the format to - */ - public void addCellArray(CellHandle[] crcells) { - for (int t = 0; t < crcells.length; t++) { - addCell(crcells[t]); - } - } - - /** - * add a Cell to this FormatHandle thus applying the Format to the Cell - * - * @param CellHandle - * to apply the format to - */ - public void addCell(CellHandle c) { - c.setFormatHandle(this); - } - - /** - * Add a List of Cells to this FormatHandle - * - * - * @param cx - */ - public void addCells(List cx) { - Iterator itx = cx.iterator(); - while (itx.hasNext()) { - addCell((BiffRec) itx.next()); - mycells.add((BiffRec) itx.next()); - } - } - - void addCell(BiffRec c) { - if (myxf != null) { - c.setXFRecord(myxf.getIdx()); - } else { - Logger.logWarn("FormatHandle.addCell() - You MUST call setFont() to initialize the FormatHandle's font before adding Cells."); - } - mycells.add(c); - } - - /** - * Applies the format to a cell without establishing a relationship. The - * format represented by this FormatHandle will be applied to - * the cell but it will not be updated with any future changes. If you want - * that behavior use {@link #addCell(CellHandle) addCell} instead. - */ - public void stamp(CellHandle cell) { - cell.setFormatId(xfe); - } - - /** Applies the format to a cell range without establishing a relationship. - * The format represented by this FormatHandle will be applied - * to the cells but they will not be updated with any future changes. If you - * want that behavior use {@link #addCell(CellHandle) addCell} instead. - */ - public void stamp(CellRange range) { - try { - range.setFormatID(xfe); - } catch (Exception e) { - // This can't actually happen - } - } - - /** - * set the Background Pattern for this Format - * - * @param int Excel color constant - */ - public void setBackgroundPattern(int t) { - Xf xf = cloneXf(myxf); - // 20080103 KSC: handle solid (=filled) backgrounds, in which Excel - // switches fg and bg colors (!!!) - if (t != FormatConstants.PATTERN_FILLED) - xf.setPattern(t); - else { - int bg = xf.getBackgroundColor(); - xf.setBackgroundSolid(); - xf.setForeColor(bg, null); - } - updateXf(xf); - } - - /** - * returns whether this Format is formatted as a Date - * - * @return boolean true if this Format is formatted as a Date - */ - public boolean isDate() { - if (myxf == null) - return false; - return myxf.isDatePattern(); - } - - /** - * returns whether this Format is formatted as a Currency - * - * @return boolean true if this Format is formatted as a currency - */ - public boolean isCurrency() { - if (myxf == null) - return false; - return myxf.isCurrencyPattern(); - } - - /** - * set the underline style for this font - * - * @param int u underline style one of the Font.STYLE_UNDERLINE constants - * - */ - public void setUnderlineStyle(int u) { - Font f = cloneFont(myxf.getFont()); - f.setUnderlineStyle((byte) u); - updateFont(f); - } - - /** - * super/sub (0 = none, 1 = super, 2 = sub) - * - * @param int script type for Format Font - */ - public void setScript(int ss) { - if (ss > 2) - ss = 2; // deal with invalid numbers - if (ss < 0) - ss = 0; // deal with invalid numbers - myxf.getFont().setScript(ss); - } - - /** - * set the Font Color for this Format via indexed color constant - * - * @param int Excel color constant - */ - public void setFontColor(int t) { - Font f = cloneFont(myxf.getFont()); - f.setColor(t); - updateFont(f); - } - - /** - * set the Font Color for this Format - * - * @param AWT - * Color color constant - */ - public void setFontColor(Color colr) { - Font f = cloneFont(myxf.getFont()); - f.setColor(colr); - updateFont(f); - } - - /** - * sets the Font color for this Format via web Hex String - * @param clr - */ - public void setFontColor(String clr) { - Font f = cloneFont(myxf.getFont()); - f.setColor(clr); - updateFont(f); - } - - /** - * Get the Font foreground (text) color as a java.awt.Color - * - * - * @return - */ - public Color getFontColor() { - return myxf.getFont().getColorAsColor(); - } - - - public String getFontColorAsHex() { - return myxf.getFont().getColorAsHex(); - } - - - /** - * set the Foreground Color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @param int Excel color constant - */ - public void setForegroundColor(int t) { - Xf xf = cloneXf(myxf); - xf.setForeColor(t, null); - updateXf(xf); - } - - /** - * set the foreground Color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @param AWT - * Color constant - */ - public void setForegroundColor(Color colr) { - Xf xf = cloneXf(myxf); - int clrz = getColorInt(colr); - xf.setForeColor(clrz, colr); - updateXf(xf); - } - - /** - * set the background color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @param t Excel color constant - */ - public void setBackgroundColor(int t) { - Xf xf = cloneXf(myxf); - if (xf.getFillPattern() == Xf.PATTERN_SOLID) - xf.setForeColor(t, null); - else - xf.setBackColor(t, null); - - updateXf(xf); - } - - /** - * set the Cell Background Color for this Format - * - * NOTE: Foreground color = the CELL BACKGROUND color color for all patterns - * and Background color= the PATTERN color for all patterns != Solid For - * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background - * Color=64 (white). - * - * @param awt - * Color color constant - */ - public void setCellBackgroundColor(Color colr) { - int clrz = getColorInt(colr); - setCellBackgroundColor(clrz); - - } - - /** - * makes the cell a solid pattern background if no pattern was already - * present NOTE: Foreground color = the CELL BACKGROUND color color for all - * patterns and Background color= the PATTERN color for all patterns != - * Solid For PATTERN_SOLID, Foreground color=CELL BACKGROUND color and - * Background Color=64 (white). - * - * @param int Excel color constant - */ - public void setCellBackgroundColor(int t) { - Xf xf = cloneXf(myxf); - - if (xf.getFillPattern() == 0) - xf.setBackgroundSolid(); - - if (xf.getFillPattern() == Xf.PATTERN_SOLID) - xf.setForeColor(t, null); - else - xf.setBackColor(t, null); - updateXf(xf); - } - - /** - * sets this fill pattern from an existing OOXML (2007v) fill element - * - * @param f - */ - protected void setFill(Fill f) { - Xf xf = cloneXf(myxf); - xf.setFill(f); - updateXf(xf); - } - - /** - * sets the fill for this format handle if fill==Xf.PATTERN_SOLID then fg is - * the PATTERN color i.e the CELL BG COLOR - * - * @param fillpattern - * @param fg - * @param bg - */ - public void setFill(int fillpattern, int fg, int bg) { - Xf xf = cloneXf(myxf); - - xf.setPattern(fillpattern); - - /** - * If the fill style is solid: When solid is specified, the foreground - * color (fgColor) is the only color rendered, even when a background - * color (bgColor) is also specified. - */ - if (xf.getFillPattern() == Xf.PATTERN_SOLID) { // is reversed - xf.setForeColor(bg, null); - xf.setBackColor(64, null); - } else { - /** - * or cell fills with patterns specified, then the cell fill color - * is specified by the bgColor element - */ - xf.setForeColor(fg, null); - xf.setBackColor(bg, null); - } - updateXf(xf); - } - - /** - * Get the Pattern Background Color for this Format Pattern - * - * @return the Excel color constant - */ - public int getBackgroundColor() { - return myxf.getBackgroundColor(); - } - - /** - * get the Pattern Background Color for this Format Pattern as a hex string - * - * @return Hex Color String - * - */ - public String getBackgroundColorAsHex() { - return myxf.getBackgroundColorHEX(); - } - - /** - * get the Pattern Background Color for this Format Pattern as an awt.Color - * - * @return background Color - * - */ - public java.awt.Color getBackgroundColorAsColor() { - return HexStringToColor(this.getBackgroundColorAsHex()); - } - - - /** - * returns the foreground color setting regardless of format pattern (which - * can switch fg and bg) - * - * @return - */ - public int getTrueForegroundColor() { - return myxf.getForegroundColor(); // 20080814 KSC: getForegroundColor() does the swapping so use base method - } - - /** - * get the Pattern Background Color for this Formatted Cell - * - * This method handles display of conditional formats for the cell - * - * checks for conditional format, then applies it if conditions are true. - * - * @return the Excel color constant - */ - public int getCellBackgroundColor() { - int fp = getFillPattern(); - - if (fp == Xf.PATTERN_SOLID) - return myxf.getForegroundColor(); // this.getForegroundColor() does - // the swapping so use base - // method - else - return myxf.getBackgroundColor(); - } - - /** - * get the Pattern Background Color for this Format as a Hex Color String - * - * @return Hex Color String - */ - public String getCellBackgroundColorAsHex() { - int fp = getFillPattern(); - - if (fp == Xf.PATTERN_SOLID) - return myxf.getForegroundColorHEX(); // this.getForegroundColor() does - else - return myxf.getBackgroundColorHEX(); - } - - /** - * get the Pattern Background Color for this Format as an awt.Color - * - * @return cell background color - */ - public java.awt.Color getCellBackgroundColorAsColor() { - return HexStringToColor(this.getCellBackgroundColorAsHex()); - } - - /** - * get the Background Color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @return the Excel color constant - */ - public int getForegroundColor() { - // if it's SOLID pattern, fg/bg are swapped - if (getFillPattern() == Xf.PATTERN_SOLID) - return myxf.getBackgroundColor(); - - return myxf.getForegroundColor(); - } - - /** - * get the Background Color for this Format as a Hex Color String NOTE: - * Foreground color = the CELL BACKGROUND color color for all patterns and - * Background color= the PATTERN color for all patterns != Solid For - * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background - * Color=64 (white). - * - * @return Hex Color String - */ - public String getForegroundColorAsHex() { - if (getFillPattern() == Xf.PATTERN_SOLID) // if it's SOLID pattern, - return myxf.getBackgroundColorHEX(); - - return myxf.getForegroundColorHEX(); - } - - /** - * get the Background Color for this Format as a Color NOTE: - * Foreground color = the CELL BACKGROUND color color for all patterns and - * Background color= the PATTERN color for all patterns != Solid For - * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background - * Color=64 (white). - * - * @return Hex Color String - */ - public Color getForegroundColorAsColor() { - return HexStringToColor(this.getForegroundColorAsHex()); - } - - /** - * set the Background Color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @param awt - * Color color constant - */ - public void setBackgroundColor(Color colr) { - Xf xf = cloneXf(myxf); - int clrz = getColorInt(colr); - xf.setBackColor(clrz, colr); - updateXf(xf); - } - - /** - * Set the format handle to use standard bold text - * - * @param boolean isBold - */ - public void setBold(boolean isBold) { - Font f = cloneFont(myxf.getFont()); - f.setBold(isBold); - updateFont(f); - } - - /** - * Get if this format is bold or not - * - * @return boolean whether the cell font format is bold - */ - public boolean getIsBold() { - return myxf.getFont().getIsBold(); - } - - /** - * Return an int representing the underline style - * - * These map to the STYLE_UNDERLINE static integers * - * - * @return int underline style - */ - public int getUnderlineStyle() { - return myxf.getFont().getUnderlineStyle(); - } - - /** - * Get the font height in points - * - * @return font height - */ - public double getFontHeightInPoints() { - return getFont().getFontHeightInPoints(); - } - - /** - * Returns the Font's height in 1/20th point increment - * - * @return font height - */ - public int getFontHeight() { - return getFont().getFontHeight(); - } - - /** - * Set the Font's height in 1/20th point increment - * - * @param new font height - */ - public void setFontHeight(int fontHeight) { - Font f = cloneFont(myxf.getFont()); - f.setFontHeight(fontHeight); - updateFont(f); - } - - /** - * Returns the Font's name - * - * @return font name - */ - public String getFontName() { - return getFont().getFontName(); - } - - /** - * Set the Font's name - * - * To be valid, this font name must be available on the client system. - * - * @param font - * name - */ - public void setFontName(String fontName) { - Font f = cloneFont(myxf.getFont()); - f.setFontName(fontName); - updateFont(f); - } - - /** - * Determine if the format handle refers to a font stricken out - * - * @return boolean representing if the FormatHandle is striking out a cell. - */ - public boolean getStricken() { - if (myxf.getFont() == null) - return false; - return myxf.getFont().getStricken(); - } - - /** - * Set if the format handle is stricken out - * - * @param isStricken - * boolean representing if the formatted cell should be stricken - * out. - */ - public void setStricken(boolean isStricken) { - Font f = cloneFont(myxf.getFont()); - f.setStricken(isStricken); - updateFont(f); - } - - /** - * Get if the font is italic - * - * @return boolean representing if the formatted cell is italic. - */ - public boolean getItalic() { - if (myxf.getFont() == null) - return false; - return myxf.getFont().getItalic(); - } - - /** - * Set if the font is italic - * - * @param isItalic - * boolean representing if the formatted cell should be italic. - */ - public void setItalic(boolean isItalic) { - Font f = cloneFont(myxf.getFont()); - f.setItalic(isItalic); - updateFont(f); - } - - /** - * Get if the font is underlined - * - * @return boolean representing if the formatted cell is underlined. - */ - public boolean getUnderlined() { - if (myxf.getFont() == null) - return underlined; - return myxf.getFont().getUnderlined(); - } - - /** - * Set underline attribute on the font - * - * - * @param isUnderlined - * boolean representing if the formatted cell should be - * underlined - */ - public void setUnderlined(boolean isUnderlined) { - Font f = cloneFont(myxf.getFont()); - f.setUnderlined(isUnderlined); - updateFont(f); - - } - - /** - * Get if the font is bold - * - * @return boolean representing if the formatted cell is bold - */ - public boolean getBold() { - return myxf.getFont().getBold(); - } - - /** - * returns the existing font record for this Format - * - * Font is an internal record and should not be accessed by end users - * - * @return the XLS Font record associated with this Format - */ - public io.starter.formats.XLS.Font getFont() { - // should this be a protected method? - if (myxf != null) // shouldn't! - return myxf.getFont(); - return null; - } - - /** - * Sets the number format pattern for this format. All Excel built-in number - * formats are supported. Custom formats will not be applied by OpenXLS - * (e.g. {@link CellHandle#getFormattedStringVal}) but they will be written - * correctly to the output file. For more information on number format - * patterns see Microsoft - * KB264372. - * - * @param pat - * the Excel number format pattern to apply - * @see FormatConstantsImpl#getBuiltinFormats - */ - public void setFormatPattern(String pat) { - Xf xf = cloneXf(myxf); - xf.setFormatPattern(pat); - updateXf(xf); - } - - public void setPattern(int pat) { - Xf xf = cloneXf(myxf); - xf.setPattern(pat); - updateXf(xf); - } - - /** - * The format ID allows setting of the format for a Cell without adding it - * to the Format's Cell Collection. - * - * Use to decrease memory requirements when dealing with large collections - * of cells. - * - * Usage: - * - * mycell.setFormatId(myformat.getFormatId()); - * - * @return the format ID for this Format - */ - public int getFormatId() { - return xfe; - } - - /** - * Gets the number format pattern for this format, if set. For more - * information on number format patterns see Microsoft KB264372. - * - * @return the Excel number format pattern for this cell or - * null if none is applied - */ - public String getFormatPattern() { - return myxf.getFormatPattern(); - } - - /** - * get the fill pattern for this format - * - */ - public int getFillPattern() { - return myxf.getFillPattern(); - } - - /** - * returns the index of the Color within the Colortable - * - * - * @param the - * index of the color in the colortable - * @return the color - */ - public static Color getColor(int col) { - if (col > -1 && col < FormatHandle.COLORTABLE.length) - return FormatHandle.COLORTABLE[col]; - return FormatHandle.COLORTABLE[0]; - } - - /** - * returns the index of the Color within the Colortable - * - * @param col - * the color - * @return the index of the color in the colortable - */ - public static int getColorInt(Color col) { - for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) { - if (col.equals(FormatHandle.COLORTABLE[i])) { - return i; - } - } - int R = col.getRed(); - int G = col.getGreen(); - int B = col.getBlue(); - int colorMatch = -1; - double colorDiff = Integer.MAX_VALUE; - for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) { - double curDif= - Math.pow(R-FormatHandle.COLORTABLE[i].getRed(), 2) - + Math.pow(G - FormatHandle.COLORTABLE[i].getGreen(), 2) - + Math.pow(B - FormatHandle.COLORTABLE[i].getBlue(), 2); - if (curDif < colorDiff) { - colorDiff = curDif; - colorMatch = i; - } - } - return colorMatch; - } - - /** - * Sets the internal format to the FormatHandle. For internal use only, not - * supported. - */ - public void setXf(Xf xrec) { - myxf = xrec; - } - - /** - * Set the horizontal alignment for this FormatHandle - * - * @param align - * - an int representing the alignment. Please review the - * FormatHandle.ALIGN*** static int's - */ - public void setHorizontalAlignment(int align) { - Xf xf = cloneXf(myxf); - xf.setHorizontalAlignment(align); - updateXf(xf); - } - - /** - * Returns an int representing the current horizontal alignment in this - * FormatHandle. These values are mapped to the FormatHandle.ALIGN*** static - * int's - */ - public int getHorizontalAlignment() { - return myxf.getHorizontalAlignment(); - } - - /** - * set indent (1= 3 spaces) - * @param indent - */ - public void setIndent(int indent) { - Xf xf = cloneXf(myxf); - xf.setIndent(indent); - updateXf(xf); - } - - /** - * increase or decrease the precision of this numeric or curerncy format pattern - *
        If the format pattern is "General", converts to a basic number pattern - *
        If the precision is already 0 and !increase, this method does nothing - * @param increase true if increase the precsion (number of decimals to display) - */ - public void adjustPrecision(boolean increase) { - // TODO: if decimal is contained within quotes ... - String pat = getFormatPattern(); - if (pat.equals("General") && increase) { - pat= "0.0"; // the most basic numeric pattern - this.setFormatPattern(pat); - return; - } - - try { - // split pattern and deal with positive, negative, zero and text separately - // for each, find decimal place and increment/decrement; if not found, find last digit placeholder - String[] pats= pat.split(";"); - String newPat= ""; - for (int i= 0; i < pats.length; i++) { - if (i > 0) newPat+=';'; - int z= pats[i].indexOf('.'); // position of decimal - boolean foundit= false; - if (z!=-1) { // found decimal place - z++; - for (; z < pats[i].length(); z++) { - char c= pats[i].charAt(z); - if ((c=='0' || c=='#' || c=='?')) // numeric placeholders - foundit= true; - else if (foundit && !(c=='0' || c=='#' || c=='?')) // numeric placeholders. if hit last one, either inc or dec - break; - } - if (increase) - newPat+= new StringBuffer(pats[i]).insert(z, "0").toString(); //pats[i].substring(0, z) + "0" + pats[i].substring(z+1); - else { - if (pats[i].charAt(z-2)!='.') { - newPat+= new StringBuffer(pats[i]).deleteCharAt(z-1).toString(); // .pats[i].substring(0, z-1) + pats[i].substring(z+1); - } else { - newPat+= new StringBuffer(pats[i]).delete(z-2, z).toString(); - } - } - - } else if (increase) { // no decimal yet. If decrease, ignore. if increase, add - z= pats[i].length()-1; - for (; z >= 0; z--) { - char c= pats[i].charAt(z); - if ((c=='0' || c=='#' || c=='?')) { // found last numeric placeholder - foundit= true; - break; - } - } - if (foundit) // if had ANY numeric placeholders - newPat+= new StringBuffer(pats[i]).insert(z+1, ".0").toString(); //pats[i].substring(0, z) + ".0" + pats[i].substring(z+1); - else - newPat+= pats[i]; // keep original - } else // if decrease and no decimal found, leave alone - newPat+= pats[i]; // leave alone - } - - //io.starter.toolkit.Logger.log("Old Style" + pat + ". New Style: " + newPat + ". Increase?" + (increase?"yes":"no")); // KSC: TESETING: TAKE OUT WHEN DONE - this.setFormatPattern(newPat); - } catch (Exception e) { - Logger.logErr("Error setting style"); // KSC: TESETING: TAKE OUT WHEN DONE - } - } - - /** - * return indent (1 = 3 spaces) - * - * @return - */ - public int getIndent() { - return myxf.getIndent(); - } - - /** - * sets the Right to Left Text Direction or reading order of this style - * @param rtl possible values: - *
        0=Context Dependent - *
        1=Left-to-Right - *
        2=Right-to-Let - * - * @param rtl - * possible values:
        - * 0=Context Dependent
        - * 1=Left-to-Right
        - * 2=Right-to-Let - * - */ - public void setRightToLeftReadingOrder(int rtl) { - Xf xf = cloneXf(myxf); - xf.setRightToLeftReadingOrder(rtl); - updateXf(xf); - } - - /** - * returns true if this style is set to Right-to-Left text direction - * (reading order) - * - * @return - */ - public int getRightToLetReadingOrder() { - return myxf.getRightToLeftReadingOrder(); - } - - /** - * Set the Vertical alignment for this FormatHandle - * - * @param align - * - an int representing the alignment. Please review the - * FormatHandle.ALIGN*** static int's - */ - public void setVerticalAlignment(int align) { - Xf xf = cloneXf(myxf); - xf.setVerticalAlignment(align); - updateXf(xf); - } - - /** - * Returns an int representing the current Vertical alignment in this - * FormatHandle. These values are mapped to the FormatHandle.ALIGN*** static - * int's - */ - public int getVerticalAlignment() { - return myxf.getVerticalAlignment(); - } - - /** - * DEPRECATED and non functional. Not neccesary as this occurs automatically - * - * Consolidates this Format with other identical formats in workbook - * - * - * There is a limit to the number of distinct formats in an Excel workbook. - * - * This method allows you to share Formats between identically formatted - * cells. - * - * @deprecated - * @return - */ - public FormatHandle pack() { - return this; // wkbook.cache.get(this); - } - - /** - * Set the workbook for this FormatHandle - * - * - * @param bk - */ - public void setWorkBook(io.starter.formats.XLS.WorkBook bk) { - wkbook = bk; - } - - /** - * Set the cell wrapping behavior for this FormatHandle. Default is false - */ - public void setWrapText(boolean wrapit) { - Xf xf = cloneXf(myxf); - xf.setWrapText(wrapit); - updateXf(xf); - } - - /** - * Get the cell wrapping behavior for this FormatHandle. Default is false - */ - public boolean getWrapText() { - return myxf.getWrapText(); - } - - - - - /** - * Set the rotation of the cell in degrees. Values 0-90 represent rotation - * up, 0-90degrees. Values 91-180 represent rotation down, 0-90 degrees. - * Value 255 is vertical - * - * @param align - * - an int representing the rotation. - */ - public void setCellRotation(int align) { - Xf xf = cloneXf(myxf); - xf.setRotation(align); - updateXf(xf); - } - - /** - * Get the rotation of the cell. Value 0 means no rotation (horizontal - * text). Values 1-90 mean rotation up (couter-clockwise) by 1-90 degrees. - * Values 91-180 mean rotation down (clockwise) by 1-90 degrees. Value 255 - * means vertical text. - */ - public int getCellRotation() { - return myxf.getRotation(); - } - - /** - * Get a JSON representation of the format - * - * - * @param cr - * @return - */ - public String getJSON(int XFNum) { - return getJSONObject(XFNum).toString(); - } - - /** - * Get a JSON representation of the format - * - * font height is represented as HTML pt size - * - * @param cr - * @return - */ - public JSONObject getJSONObject(int XFNum) { - Font myf = getFont(); - JSONObject theStyle = new JSONObject(); - try { - theStyle.put("style", XFNum); - // handle the font - JSONObject theFont = new JSONObject(); - theFont.put("name", myf.getFontName()); - - // round out the font size... - long sz = Math.round(myf.getFontHeight() / 22.0); - - theFont.put("size", sz); // adjust smaller - theFont.put("color", colorToHexString(myf.getColorAsColor())); - theFont.put("weight", myf.getFontWeight()); - if (getIsBold()) { - theFont.put("bold", "1"); - } - if (this.getUnderlined()) { - theFont.put("underline", "1"); - } else { - theFont.put("underline", "0"); - } - - if (getItalic()) { - theFont.put("italic", "1"); - } - - theStyle.put("font", theFont); - - // - JSONObject border = new JSONObject(); - if (getRightBorderLineStyle() != 0) { - JSONObject rBorder = new JSONObject(); - rBorder.put("style", - BORDER_STYLES_JSON[getRightBorderLineStyle()]); - rBorder.put("color", colorToHexString(getBorderRightColor())); - border.put("right", rBorder); - } - if (getBottomBorderLineStyle() != 0) { - JSONObject bBorder = new JSONObject(); - bBorder.put("style", - BORDER_STYLES_JSON[getBottomBorderLineStyle()]); - bBorder.put("color", colorToHexString(getBorderBottomColor())); - border.put("bottom", bBorder); - } - if (getLeftBorderLineStyle() != 0) { - JSONObject lBorder = new JSONObject(); - lBorder.put("style", - BORDER_STYLES_JSON[getLeftBorderLineStyle()]); - lBorder.put("color", colorToHexString(getBorderLeftColor())); - border.put("left", lBorder); - } - if (getTopBorderLineStyle() != 0) { - JSONObject tBorder = new JSONObject(); - tBorder.put("style", - BORDER_STYLES_JSON[getTopBorderLineStyle()]); - tBorder.put("color", colorToHexString(getBorderTopColor())); - border.put("top", tBorder); - } - theStyle.put("borders", border); - - // - JSONObject alignment = new JSONObject(); - alignment.put("horizontal", - HORIZONTAL_ALIGNMENTS[getHorizontalAlignment()]); - alignment.put("vertical", - VERTICAL_ALIGNMENTS[getVerticalAlignment()]); - if (getWrapText()) { - alignment.put("wrap", "1"); - } - theStyle.put("alignment", alignment); - - if (getIndent() != 0) { - theStyle.put("indent", getIndent()); - } - // colors + background patterns - if (getFillPattern() >= 0) { // KSC: added >= 0 as some conditional formats have patternFillStyle==0 even though they have a pattern block and - JSONObject interior = new JSONObject(); - // weird black/white case - if (myxf.getForegroundColor() == 65) { - interior.put("color", "#FFFFFF"); - } else { - // KSC: use color string if it exists; create if doesn't - interior.put("color", myxf.getForegroundColorHEX()); - } - interior.put("pattern", myxf.getFillPattern()); - interior.put("fg", myxf.getForegroundColor()); // Excel-2003 Color Table index - interior.put("patterncolor", myxf.getBackgroundColorHEX()); - interior.put("bg", myxf.getBackgroundColor()); // Excel-2003 Color Table index - theStyle.put("interior", interior); - } - - if (myxf.getIfmt() != 0) { // only input user defined formats ... - JSONObject nFormat = new JSONObject(); - String fmtpat = getFormatPattern(); - try { - if (!fmtpat.equals("General")) { - nFormat.put("format", fmtpat); // convertXMLChars? - nFormat.put("formatid", myxf.getIfmt()); - if (isDate()) - nFormat.put("isdate", "1"); - if (isCurrency()) - nFormat.put("iscurrency", "1"); - if (isRedWhenNegative()) - nFormat.put("isrednegative", "1"); - } - - } catch (Exception e) { // it's possible that getFormatPattern - // returns null - } - theStyle.put("numberformat", nFormat); - } - // - JSONObject protection = new JSONObject(); - try { - if (this.myxf.isLocked()) - protection.put("Protected", true); - if (this.myxf.isFormulaHidden()) - protection.put("HideFormula", true); - } catch (Exception e) { - } - theStyle.put("protection", protection); - - } catch (JSONException e) { - Logger.logErr("Error getting cellRange JSON: " + e); - } - return theStyle; - } - - /** - * Returns an XML fragment representing the FormatHandle - */ - public String getXML(int XFNum) { - return getXML(XFNum, false); - } - - /** - * Returns an XML fragment representing the FormatHandle - * - * @param convertToUnicodeFont - * if true, font family will be changed to ArialUnicodeMS - * (standard unicode) for non-ascii fonts - */ - public String getXML(int XFNum, boolean convertToUnicodeFont) { - Font myf = getFont(); - // "); - return sb.toString(); - } - - /** - * Gets the Excel format ID for this format's number format pattern. - * - * @return the Excel format identifier number for the number format pattern - */ - public int getFormatPatternId() { - return myxf.getIfmt(); - } - - /** - * Sets the number format pattern based on the format ID number. This method - * is recommended for advanced users only. In most cases you should use - * {@link #setFormatPattern(String)} instead. - * - * @param fmt - * the format ID number for the desired number format pattern - */ - public void setFormatPatternId(int fmt) { - myxf.setFormat((short) fmt); - } - - /** - * Convert a java.awt.Color to a hex string. - * - * @return String representation of a Color - */ - public static String colorToHexString(Color c) { - int r = c.getRed(); - int g = c.getGreen(); - int b = c.getBlue(); - String rh = Integer.toHexString(r); - if (rh.length() < 2) { - rh = "0" + rh; - } - String gh = Integer.toHexString(g); - if (gh.length() < 2) { - gh = "0" + gh; - } - String bh = Integer.toHexString(b); - if (bh.length() < 2) { - bh = "0" + bh; - } - return ("#" + rh + gh + bh).toUpperCase(); - } - - - public static short colorFONT = 0; - public static short colorBACKGROUND = 1; - public static short colorFOREGROUND = 2; - public static short colorBORDER = 3; - - /** - * convert hex string RGB to Excel colortable int format if an exact match - * is not find, does color-matching to try and obtain closest match - * - * @param s - * @param colorType - * @return - */ - public static int HexStringToColorInt(String s, short colorType) { - if (s.length() > 7) - s = "#" + s.substring(s.length() - 6); - if (s.indexOf("#") == -1) - s = "#" + s; - if (s.length() == 7) { - String rs = s.substring(1, 3); - int r = Integer.parseInt(rs, 16); - String gs = s.substring(3, 5); - int g = Integer.parseInt(gs, 16); - String bs = s.substring(5, 7); - int b = Integer.parseInt(bs, 16); - // Handle exceptions for black, white and color indexes 9 (see - // FormatConstants for more info) - - if (r == 255 && r == g && r == b) { - if (colorType == colorFONT) - return 9; - } - - Color c = new Color(r, g, b); - return getColorInt(c); - } - return 0; - } - - /** - * convert hex string RGB to Excel colortable int format if no exact match - * is found returns -1 - * - * @param s - * HTML color string (#XXXXXX) format or (#FFXXXXXX - OOXML-style - * format) - * @param colorType - * @return index into color table or -1 if not found - */ - public static int HexStringToColorIntExact(String s, short colorType) { - if (s.length() > 7) - s = "#" + s.substring(s.length() - 6); - if (s.indexOf("#") == -1) - s = "#" + s; - if (s.length() == 7) { - String rs = s.substring(1, 3); - int r = Integer.parseInt(rs, 16); - String gs = s.substring(3, 5); - int g = Integer.parseInt(gs, 16); - String bs = s.substring(5, 7); - int b = Integer.parseInt(bs, 16); - - // Handle exceptions for black, white and color indexes 9 (see - // FormatConstants for more info) - if (r == 255 && r == g && r == b) { - if (colorType == colorFONT) - return 9; - } - - Color c = new Color(r, g, b); - for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) { - if (c.equals(FormatHandle.COLORTABLE[i])) { - return i; - } - } - } - return -1; // match NOT FOUND - } - - /** - * convert hex string RGB to a Color - * - * - * @param s web-style hex color string, including #, or OOXML-style color string, FFXXXXXX - * @return Color - * @see Color - */ - public static Color HexStringToColor(String s) { - if (s.length() > 7){ // transform OOXML-style color strings to Web-style - s = "#" + s.substring(s.length() - 6); - } - Color c = null; - if (s.length() == 7) { - String rs = s.substring(1, 3); - int r = Integer.parseInt(rs, 16); - String gs = s.substring(3, 5); - int g = Integer.parseInt(gs, 16); - String bs = s.substring(5, 7); - int b = Integer.parseInt(bs, 16); - c = new Color(r, g, b); - } else - c = new Color(0, 0, 0); // default to black? - return c; - } - - /** - * interpret color table special entries - * - * @param clr - * color index in range of 0x41-0x4F (charts) 0x40 ... - * @return index into color table - */ - public static short interpretSpecialColorIndex(int clr) { - switch (clr) { - case 0x0041: // Default background color. This is the window background - // color in the sheet display and is the default - // background color for a cell. - case 0x004E: // Default chart background color. This is the window - // background color in the chart display. - case 0x0050: // WHAT IS THIS ONE???????? - return FormatConstants.COLOR_WHITE; - case 0x0040: // Default foreground color - case 0x004F: // Chart neutral color which is black, an RGB value of - // (0,0,0). - case 0x004D: // Default chart foreground color. This is the window text - // color in the chart display. - case 0x0051: // ToolTip text color. This is the automatic font color for - // comments. - case 0x7FFF: // Font automatic color. This is the window text color - return FormatConstants.COLOR_BLACK; - default: // 67(=0x43) ??? - return FormatConstants.COLOR_WHITE; - } - - /* - * switch (icvFore) { case 0x40: // default fg color return - * FormatConstants.COLOR_WHITE; case 0x41: // default bg color return - * FormatConstants.COLOR_WHITE; case 0x4D: // default CHART fg color -- - * INDEX SPECIFIC! return -1; // flag to map via series (bar) color - * defaults case 0x4E: // default CHART fg color return icvFore; case - * 0x4F: // chart neutral color == black return - * FormatConstants.COLOR_BLACK; } - * - * switch (icvBack) { case 0x40: // default fg color return - * FormatConstants.COLOR_WHITE; case 0x41: // default bg color return - * FormatConstants.COLOR_WHITE; case 0x4D: // default CHART fg color -- - * INDEX SPECIFIC! return -1; // flag to map via series (bar) color - * defaults case 0x4E: // default CHART bg color //return - * FormatConstants.COLOR_WHITE; // is this correct? return icvBack; case - * 0x4F: // chart neutral color == black return - * FormatConstants.COLOR_BLACK; } - */ - } - - public static int BorderStringToInt(String s) { - for (int i = 0; i < FormatConstants.BORDER_NAMES.length; i++) { - if (FormatConstants.BORDER_NAMES[i].equals(s)) - return i; - } - return 0; - } - - // 20060412 KSC: added for access - public io.starter.formats.XLS.WorkBook getWorkBook() { - return wkbook; - } - - /** - * return truth of "this Xf rec is a style xf" - * - * @return - */ - public boolean isStyleXf() { - return myxf.isStyleXf(); - } - - /** - * creates a new font based on an existing one, adds to workbook recs - */ - private Font createNewFont(Font f) { - f.setIdx(-1); // flag to insert anew (see Font.setWorkBook) - wkbook.insertFont(f); - f.setWorkBook(wkbook); - return f; - } - - /** - * create a new font based on existing font - does not add to workbook recs - * - * @param src - * font - * @return cloned font - */ - private Font cloneFont(Font src) { - Font f = new Font(); - f.setOpcode(io.starter.formats.XLS.XLSConstants.FONT); - f.setData(src.getBytes()); // use default font as basis of new font - f.setIdx(-2); // avoid adding to fonts array when call setW.b. below - f.setWorkBook(this.getWorkBook()); - f.init(); - return f; - } - - /** - * update the font for this FormatHandle, including updating the xf if - * necessary - * - * @param f - */ - private void updateFont(Font f) { - int idx = wkbook.getFontIdx(f); - if (idx == -1) { // can't find it so add new - f = createNewFont(f); - } else - f = wkbook.getFont(idx); - if (f.getIdx()!=myxf.getIfnt()) { // then updated the font, must create new xf to link to - Xf xf = cloneXf(myxf); - xf.setFont(f.getIdx()); - updateXf(xf); - } - } - - /** - * Create or Duplicate Xf rec so can alter (pattern, font, colrs ...) - * - * @param xf - * xf to base off of, or null (will create new) - * @return new Xf - */ - private Xf duplicateXf(Xf xf) { - int fidx = 0; - if (xf != null) - fidx = xf.getFont().getIdx(); - xf = Xf.updateXf(xf, fidx, wkbook); // clones/creates new based upon - // original - xfe = xf.getIdx(); // update the pointer + int bg = cf.getPatternFillColorBack(); + int fg = cf.getPatternFillColor(); + this.setFill(fill, fg, bg); + } else { + int fg = cf.getForegroundColor(); + if (fg > -1) + setForegroundColor(fg); + } + } + } + + /** + * Creates a FormatHandle for the given cell. This is not part of the + * public API and should not be called. Customers should use + * {@link CellHandle#getFormatHandle()} instead. + */ + public FormatHandle(CellHandle c) { + wkbook = c.getCell().getWorkBook(); + if (c.getCell().getXfRec() != null) { + myxf = c.getCell().getXfRec(); + xfe = myxf.getIdx(); // update the pointer - 20071010 KSC + } else { // ?? create new + // 20090512 KSC: Shigeo NPE error formaterror646694, create new + // rather than outputting warning + // Logger.logWarn("No XF for cell " + c.toString()); + myxf = wkbook.getXf(c.getCell().getIxfe()); + xfe = myxf.getIdx(); + } + // 20101201 KSC: only add to cache when adding xf's addToCache(); + } + + /** + * overrides the equals method to perform equality based on format + * properties ------------------------------------------------------------ + * + * @param Object another - the FormatHandle to compare with this FormatHandle + * @return true if this FormatHandle equals another + */ + public boolean equals(Object another) { + return another.toString().equals(toString()); + } + + /** + * Locks the cell attached to this FormatHandle for editing (makes + * read-only) lock cell and make read-only + * + * @param boolean locked - true if cells should be locked for this + * FormatHandle + */ + public void setLocked(boolean locked) { + Xf xf = cloneXf(myxf); + xf.setLocked(locked); + updateXf(xf); + } + + /** + * sets the cell attached to this FormatHandle to hide or show formula + * strings; + * + * @param boolean b- true if formulas should be hidden for this FormatHandle + */ + public void setFormulaHidden(boolean b) { + Xf xf = cloneXf(myxf); + xf.setFormulaHidden(b); + updateXf(xf); + } + + /** + * returns whether this FormatHandle is set to hide formula strings + * + * @return true if the formula strings are hidden, false otherwise + */ + public boolean isFormulaHidden() { + return myxf.isFormulaHidden(); + } + + /** + * returns whether this Format Handle specifies that cells are locked for + * changing + * + * @return true if cells are locked + */ + public boolean isLocked() { + return myxf.isLocked(); + } + + /** + * provides a mapping between Excel formats and Java formats
        + * see: http://java.sun.com/docs/books/tutorial/i18n/format + * /decimalFormat.html + *

        + * Note there are slight Excel-specific differences in the format strings + * returned. Several numeric and currency formats in excel have different + * formatting for postive and negative numbers. In these cases, the java + * format string is split by semicolons and may contain text [Red] which is + * to specify the negative number should be displayed in red. Remove this + * from the string before passing into the Format class; + * + *

        +     *         G  Era designator  Text  AD
        +     *         y  Year  Year  1996; 96
        +     *         M  Month in year  Month  July; Jul; 07
        +     *         w  Week in year  Number  27
        +     *         W  Week in month  Number  2
        +     *         D  Day in year  Number  189
        +     *         d  Day in month  Number  10
        +     *         F  Day of week in month  Number  2
        +     *         E  Day in week  Text  Tuesday; Tue
        +     *         a  Am/pm marker  Text  PM
        +     *         H  Hour in day (0-23)  Number  0
        +     *         k  Hour in day (1-24)  Number  24
        +     *         K  Hour in am/pm (0-11)  Number  0
        +     *         h  Hour in am/pm (1-12)  Number  12
        +     *         m  Minute in hour  Number  30
        +     *         s  Second in minute  Number  55
        +     *         S  Millisecond  Number  978
        +     *         z  Time zone  General time zone  Pacific Standard Time; PST; GMT-08:00
        +     *         Z  Time zone  RFC 822 time zone  -0800
        +     * 
        + * + * @return String the formatting pattern for the cell + */ + public String getJavaFormatString() { + String pat = getFormatPattern(); + if (pat == null) return null; + + // toLowerCase is a simplistic way to implement the case insensitivity + // of the pattern tokens. It could cause issues with string literals. + Object patty = convertFormatString(pat.toLowerCase()); + if (patty != null) + patty = StringTool.qualifyPatternString(patty.toString()); + if (myxf.isDatePattern()) { + if (patty != null) + return (String) patty; + else + return "M/d/yy h:mm"; + } + if (patty != null) + return (String) patty; + /* + * If we reached here, we don't have a mapping for this particular + * format. Send a warning to the system then make sure the pattern we + * are sending back is valid. Many excel patterns have 4 patterns + * separated by semicolons. We only can pass 2 into the formatter + * (positive and negative). This usually works out to be the first two + * patterns in the string. + */ + pat = StringTool.qualifyPatternString(pat); + int firstParens = pat.indexOf(";"); + if (firstParens != -1) { + int secondParens = pat.indexOf(";", firstParens + 1); + if (secondParens != -1) { + pat = pat.substring(0, secondParens); + } else { // yet another hackaround -jm + pat = pat.substring(firstParens + 2, pat.length() - 1); + } + } + return pat; + } + + /** + * converts an Excel-style format string to a Java Format string. + * + * @param String pattern - Excel Format String + * @return String that can be used with the Java Format classes. + * @see getJavaFormatString + */ + public static String convertFormatString(String pattern) { + String ret = numericFormatMap.get(pattern); + if (ret != null) + return ret; + ret = currencyFormatMap.get(pattern); + if (ret != null) + return ret; + ret = dateFormatMap.get(pattern); + return ret; + } + + /* + * FIXME: Border Issues (marker) + * + * The methods to set individual border line styles do not follow a + * consistent naming convention with the rest of the border methods. + * + * There are no methods for getting or setting the inside borders. I don't + * know whether this is a problem or a design choice. It depends on whether + * the inside borders are stored in the format record or as separate cell + * formats. + * + * There are no methods to get the diagonal borders. There is a method to + * set the diagonal border line style, but it affects both diagonals. + */ + + /** + * sets the border color for all borders (top, left, bottom and right) from + * a Color array + *

        + * NOTE: this setting will affect every cell which refers to this + * FormatHandle + * + * @param java .awt.Color array - 4-element array of desired border colors + * [T, L, B, R] + */ + public void setBorderColors(Color[] bordercolors) { + Xf xf = cloneXf(myxf); + if (bordercolors[0] != null) + xf.setTopBorderColor(getColorInt(bordercolors[0])); + if (bordercolors[1] != null) + xf.setLeftBorderColor(getColorInt(bordercolors[1])); + if (bordercolors[2] != null) + xf.setBottomBorderColor(getColorInt(bordercolors[2])); + if (bordercolors[3] != null) + xf.setRightBorderColor(getColorInt(bordercolors[3])); + updateXf(xf); + } + + /** + * remove borders for this format + */ + public void removeBorders() { + Xf xf = cloneXf(myxf); + xf.removeBorders(); + updateXf(xf); + } + + /** + * sets the border color for all borders (top, left, bottom and right) from + * an int array containing color constants + * + *

        + * NOTE: this setting will affect every cell which refers to this + * FormatHandle + * + * @param int[] bordercolors - 4-element array of desired border color + * constants [T, L, B, R] + * @see FormatHandle.COLOR_* constants + */ + public void setBorderColors(int[] bordercolors) { + Xf xf = cloneXf(myxf); + xf.setTopBorderColor(bordercolors[0]); + xf.setLeftBorderColor(bordercolors[1]); + xf.setBottomBorderColor(bordercolors[2]); + xf.setRightBorderColor(bordercolors[3]); + updateXf(xf); + + } + + /** + * set the border color for all borders (top, left, bottom, and right) to + * one color via color constant + * + *

        + * NOTE: this setting will affect every cell which refers to this + * FormatHandle + * + * @param int x - color constant which represents the color to set all + * border sides + * @see FormatHandle.COLOR_* constants + */ + public void setBorderColor(int x) { + Xf xf = cloneXf(myxf); + xf.setRightBorderColor((short) x); + xf.setLeftBorderColor((short) x); + xf.setTopBorderColor((short) x); + xf.setBottomBorderColor((short) x); + updateXf(xf); + + } + + /** + * set the border color for all borders (top, left, bottom, and right) to + * one java.awt.Color + *

        + * NOTE: this setting will affect every cell which refers to this + * FormatHandle + * + * @param Color col - color to set all border sides + */ + public void setBorderColor(Color col) { + Xf xf = cloneXf(myxf); + short x = (short) getColorInt(col); + xf.setRightBorderColor(x); + xf.setLeftBorderColor(x); + xf.setTopBorderColor(x); + xf.setBottomBorderColor(x); + updateXf(xf); + + } + + /** + * Set the top border color + * + * @param color constant + */ + public void setBorderRightColor(int x) { + Xf xf = cloneXf(myxf); + xf.setRightBorderColor((short) x); + updateXf(xf); + } + + /** + * Set the top border color + * + * @param color constant + */ + public void setBorderRightColor(Color x) { + Xf xf = cloneXf(myxf); + xf.setRightBorderColor((short) getColorInt(x)); + updateXf(xf); + } + + /** + * Get the Right border color + * + * @return color constant + */ + public Color getBorderRightColor() { + if (myxf.getRightBorderLineStyle() == 0) + return null; + int x = myxf.getRightBorderColor(); + if (x < this.getWorkBook().colorTable.length) + return this.getWorkBook().getColorTable()[x]; + else + return this.getWorkBook().getColorTable()[0]; // black i'm afraid + } + + /** + * Set the top border color + * + * @param color constant + */ + public void setBorderLeftColor(int x) { + Xf xf = cloneXf(myxf); + xf.setLeftBorderColor((short) x); + updateXf(xf); + } + + /** + * Set the top border color + * + * @param color constant + */ + public void setBorderLeftColor(Color x) { + Xf xf = cloneXf(myxf); + xf.setLeftBorderColor((short) getColorInt(x)); + updateXf(xf); + } + + /** + * returns Border Colors of Cell ie: top, left, bottom, right + *

        + * returns null or 1 color for each of 4 sides + *

        + * 1,1,1,1 represents a border all around the cell 1,1,0,0 represents on the + * top left edge of the cell + * + * @return int array representing Cell borders + */ + public Color[] getBorderColors() { + Color[] colors = new Color[4]; + colors[0] = getBorderTopColor(); + colors[1] = getBorderLeftColor(); + colors[2] = getBorderBottomColor(); + colors[3] = getBorderRightColor(); + return colors; + } + + /** + * Get the Left border color + * + * @return color constant + */ + public Color getBorderLeftColor() { + if (myxf.getLeftBorderLineStyle() == 0) + return null; + return this.getWorkBook().getColorTable()[myxf.getLeftBorderColor()]; + } + + /** + * Set the top border color + * + * @param color constant + */ + public void setBorderTopColor(int x) { + Xf xf = cloneXf(myxf); + xf.setTopBorderColor(x); + updateXf(xf); + } + + /** + * Get the Top border color + * + * @return color constant + */ + public Color getBorderTopColor() { + if (myxf.getTopBorderLineStyle() == 0) + return null; + int xt = myxf.getTopBorderColor(); + if (xt > this.getWorkBook().getColorTable().length) // guards + xt = 0; + return this.getWorkBook().getColorTable()[xt]; + } + + /** + * Set the top border color + * + * @param color constant + */ + public void setBorderTopColor(Color x) { + Xf xf = cloneXf(myxf); + xf.setTopBorderColor(getColorInt(x)); + updateXf(xf); + } + + /** + * Set the top border color + * + * @param color constant + */ + public void setBorderBottomColor(int x) { + Xf xf = cloneXf(myxf); + xf.setBottomBorderColor(x); + updateXf(xf); + } + + /** + * Set the top border color + * + * @param color constant + */ + public void setBorderBottomColor(Color x) { + Xf xf = cloneXf(myxf); + xf.setBottomBorderColor(getColorInt(x)); + updateXf(xf); + } + + /** + * Returns true if the value should be red due to a combination of a format + * pattern and a negative number + * + * @return + */ + public boolean isRedWhenNegative() { + String pattern = myxf.getFormatPattern(); + return pattern.indexOf("Red") > -1; + } + + /** + * Get the Right border color + * + * @return color constant + */ + public Color getBorderBottomColor() { + if (myxf.getBottomBorderLineStyle() == 0) + return null; + int x = myxf.getBottomBorderColor(); + if (x < this.getWorkBook().getColorTable().length) + return this.getWorkBook().getColorTable()[x]; + else + return this.getWorkBook().getColorTable()[0]; // black i'm afraid + + } + + /** + * Set the border line style + * + * @param line style constant + */ + public void setTopBorderLineStyle(int x) { + Xf xf = cloneXf(myxf); + xf.setTopBorderLineStyle((short) x); + updateXf(xf); + } + + /** + * Get the border line style + * + * @return line style constant + */ + public int getTopBorderLineStyle() { + return myxf.getTopBorderLineStyle(); + } + + /** + * Set the border line style + * + * @param line style constant + */ + public void setBottomBorderLineStyle(int x) { + Xf xf = cloneXf(myxf); + xf.setBottomBorderLineStyle((short) x); + updateXf(xf); + } + + /** + * Get the border line style + * + * @return line style constant + */ + public int getBottomBorderLineStyle() { + return myxf.getBottomBorderLineStyle(); + } + + /** + * Set the border line style + * + * @param line style constant + */ + public void setLeftBorderLineStyle(int x) { + Xf xf = cloneXf(myxf); + xf.setLeftBorderLineStyle((short) x); + updateXf(xf); + } + + /** + * Get the border line style + * + * @return line style constant + */ + public int getLeftBorderLineStyle() { + return myxf.getLeftBorderLineStyle(); + } + + /** + * Set the border line style + * + * @param line style constant + */ + public void setRightBorderLineStyle(int x) { + Xf xf = cloneXf(myxf); + xf.setRightBorderLineStyle((short) x); + updateXf(xf); + } + + /** + * Get the border line style + * + * @return line style constant + */ + public int getRightBorderLineStyle() { + return myxf.getRightBorderLineStyle(); + } + + /** + * Sets the border line style using static BORDER_ shorts within + * FormatHandle + * + * @param line style constant + */ + public void setBorderLineStyle(int x) { + Xf xf = cloneXf(myxf); + xf.setBorderLineStyle((short) x); + updateXf(xf); + } + + /** + * set border line styles via array of ints representing border styles + * order= left, right, top, bottom, [diagonal] + * + * @param b int[] + */ + public void setBorderLineStyle(int[] b) { + myxf.setAllBorderLineStyles(b); + } + + /** + * Set the border line style + * + * @param line style constant + */ + public void setBorderLineStyle(short x) { + Xf xf = cloneXf(myxf); + xf.setBorderLineStyle(x); + updateXf(xf); + } + + /** + * return the 5 border lines styles (l, r, t, b, diag) + * + * @return + */ + public int[] getAllBorderLineStyles() { + int[] ret = new int[5]; + ret[0] = myxf.getLeftBorderLineStyle(); + ret[1] = myxf.getRightBorderLineStyle(); + ret[2] = myxf.getTopBorderLineStyle(); + ret[3] = myxf.getBottomBorderLineStyle(); + ret[4] = myxf.getDiagBorderLineStyle(); + return ret; + } + + /** + * return the 5 border line colors (l, r, t, b, diag) + * + * @return + */ + public int[] getAllBorderColors() { + int[] ret = new int[5]; + ret[0] = myxf.getLeftBorderColor(); + ret[1] = myxf.getRightBorderColor(); + ret[2] = myxf.getTopBorderColor(); + ret[3] = myxf.getBottomBorderColor(); + ret[4] = myxf.getDiagBorderColor(); + return ret; + } + + /** + * Set the border line style + * + * @param line style constant + */ + public void setBorderDiagonal(int x) { + Xf xf = cloneXf(myxf); + xf.setBorderDiag(x); + updateXf(xf); + } + + /** + * Set a column handle on this format handle, so all changes applied to this + * format will be applied to the entire column + * + * @param c + */ + public void setColHandle(ColHandle c) { + mycol = c; + } + + /** + * Set a row handle on this format handle, so all changes applied to this + * format will be applied to the entire row + * + * @param c + */ + public void setRowHandle(RowHandle c) { + myrow = c; + } + + /** + * Create a copy of this FormatHandle with its own Xf + * + * @return the copied FormatHandle + */ + public Object clone() { + FormatHandle ret = null; + if (wbh == null) { // who knew??? + wkbook = myxf.getWorkBook(); // Changed to myxf since myfont is no + // longer + ret = new FormatHandle(); + ret.myxf = myxf; + ret.xfe = myxf.getIdx(); + ret.wkbook = wkbook; + } else { + ret = new FormatHandle(wbh, myxf); // no need to duplicate it - just + // use all formatting of + // original xf + } + + return ret; + } + + public String toString() { + return myxf.toString(); + } + + public FormatHandle(io.starter.OpenXLS.WorkBook book, String fontname, + int fontstyle, int fontsize) { + this(book); + setFont(fontname, fontstyle, fontsize); + } + + /** + * Jan 27, 2011 + * + * @param workBook + * @param i + */ + public FormatHandle(io.starter.OpenXLS.WorkBook workBook, int i) { + this(workBook.getWorkBook(), i); + wbh = workBook; + } + + /** + * /** Set the weight of the font in 1/20 point units 100-1000 range. 400 is + * normal, 700 is bold. + * + * @param wt + */ + public void setFontWeight(int wt) { + Font f = cloneFont(myxf.getFont()); + f.setFontWeight(wt); + updateFont(f); + } + + /** + * Get the font weight the weight of the font is in 1/20 point units + * + * @return + */ + public int getFontWeight() { + return myxf.getFont().getFontWeight(); + } + + /** + * Set the Font for this Format. + *

        + * As adding a new Font and format increases the file size, try using this + * once for each distinct font used in the file, then use 'setFormatId' to + * share this font with other Cells. + *

        + * Roughly matches the functionality of the java.awt.Font class. Currently + * the style parameter is only useful for bold/normal weights. Italics, + * underlines, etc must be modified elsewhere. + *

        + * Note that in order to maintain java.awt.Font compatibility for + * bold/normal styles, defaults for weight/style have been mapped to 0 = + * normal (excel 200 weight) and 1 = bold (excel 700 weight) + * + * @param String font name + * @param int font style + * @param int font size + */ + public void setFont(Font f) { + setXFToFont(f); + } + + /** + * Set new font to XF, handling duplication and caching ... + * + * @param Font f + */ + private void setXFToFont(Font f) { + int fti = addFontIfNecessary(f); + if (myxf == null) { // shouldn't!! + myxf = duplicateXf(null); + myxf.setFont(fti); + } else if (myxf.getIfnt() != fti) { // if not using font already, + // duplicate xf and set to new font + Xf xf = cloneXf(myxf); + xf.setFont(fti); + updateXf(xf); + } + + } + + /** + * Sets this format handle to a font + * + * @param fn font name e.g. 'Arial' + * @param stl font style either Font.PLAIN or Font.BOLD + * @param sz font size or height in 1/20 point units + */ + public void setFont(String fn, int stl, double sz) { + sz *= 20; + if (stl == 0) + stl = 200; + if (stl == 1) + stl = 700; + Font f = new Font(fn, stl, (int) sz); + setXFToFont(f); + } + + /** + * add font to record streamer if cant find exact font already in there + * + * @param f + * @return + */ + private int addFontIfNecessary(Font f) { + if (wkbook == null) { + Logger.logErr("AddFontIfNecessary: workbook is null"); + return -1; + } + int fti = wkbook.getFontIdx(f); + // don't use the built-ins. + if (fti == 3) + fti = 0; // use initial default font instead of last ... + if (fti == -1) { // font doesn't exist yet, add to streamer + f.setIdx(-1); // flag to insert + fti = wkbook.insertFont(f) + 1; + } else + f.setIdx(fti); + return fti; + } + + /** + * adds a font to the global font store only if exact font is not already + * present + * + * @param f Font + * @param bk WorkBookHandle + */ + public static int addFont(Font f, WorkBookHandle bk) { + if (bk == null) { + Logger.logErr("addFont: workbook is null"); + } + int fti = bk.getWorkBook().getFontIdx(f); + // if (fti > 3) {// don't use the built-ins. + if (fti == 3) + fti = 0; // use initial default font instead of last ... 20070827 + // KSC + if (fti == -1) { // font doesn't exist yet, add to streamer + f.setIdx(-1); // flag to insert + fti = bk.getWorkBook().insertFont(f) + 1; + } else + f.setIdx(fti); + return fti; + } + + /** + * Adds a font internally to the workbook + * + * @param f + * + * private void addFont(Font f) { + * + * } + */ + + /** + * Apply this Format to a Range of Cells + * + * @param CellRange to apply the format to + */ + public void addCellRange(CellRange cr) { + CellHandle[] crcells = cr.getCells(); + for (int t = 0; t < crcells.length; t++) { + addCell(crcells[t]); + } + } + + /** + * Apply this Format to a Range of Cells + * + * @param CellHandle array to apply the format to + */ + public void addCellArray(CellHandle[] crcells) { + for (int t = 0; t < crcells.length; t++) { + addCell(crcells[t]); + } + } + + /** + * add a Cell to this FormatHandle thus applying the Format to the Cell + * + * @param CellHandle to apply the format to + */ + public void addCell(CellHandle c) { + c.setFormatHandle(this); + } + + /** + * Add a List of Cells to this FormatHandle + * + * @param cx + */ + public void addCells(List cx) { + Iterator itx = cx.iterator(); + while (itx.hasNext()) { + addCell((BiffRec) itx.next()); + mycells.add(itx.next()); + } + } + + void addCell(BiffRec c) { + if (myxf != null) { + c.setXFRecord(myxf.getIdx()); + } else { + Logger.logWarn("FormatHandle.addCell() - You MUST call setFont() to initialize the FormatHandle's font before adding Cells."); + } + mycells.add(c); + } + + /** + * Applies the format to a cell without establishing a relationship. The + * format represented by this FormatHandle will be applied to + * the cell but it will not be updated with any future changes. If you want + * that behavior use {@link #addCell(CellHandle) addCell} instead. + */ + public void stamp(CellHandle cell) { + cell.setFormatId(xfe); + } + + /** + * Applies the format to a cell range without establishing a relationship. + * The format represented by this FormatHandle will be applied + * to the cells but they will not be updated with any future changes. If you + * want that behavior use {@link #addCell(CellHandle) addCell} instead. + */ + public void stamp(CellRange range) { + try { + range.setFormatID(xfe); + } catch (Exception e) { + // This can't actually happen + } + } + + /** + * set the Background Pattern for this Format + * + * @param int Excel color constant + */ + public void setBackgroundPattern(int t) { + Xf xf = cloneXf(myxf); + // 20080103 KSC: handle solid (=filled) backgrounds, in which Excel + // switches fg and bg colors (!!!) + if (t != FormatConstants.PATTERN_FILLED) + xf.setPattern(t); + else { + int bg = xf.getBackgroundColor(); + xf.setBackgroundSolid(); + xf.setForeColor(bg, null); + } + updateXf(xf); + } + + /** + * returns whether this Format is formatted as a Date + * + * @return boolean true if this Format is formatted as a Date + */ + public boolean isDate() { + if (myxf == null) + return false; + return myxf.isDatePattern(); + } + + /** + * returns whether this Format is formatted as a Currency + * + * @return boolean true if this Format is formatted as a currency + */ + public boolean isCurrency() { + if (myxf == null) + return false; + return myxf.isCurrencyPattern(); + } + + /** + * set the underline style for this font + * + * @param int u underline style one of the Font.STYLE_UNDERLINE constants + */ + public void setUnderlineStyle(int u) { + Font f = cloneFont(myxf.getFont()); + f.setUnderlineStyle((byte) u); + updateFont(f); + } + + /** + * super/sub (0 = none, 1 = super, 2 = sub) + * + * @param int script type for Format Font + */ + public void setScript(int ss) { + if (ss > 2) + ss = 2; // deal with invalid numbers + if (ss < 0) + ss = 0; // deal with invalid numbers + myxf.getFont().setScript(ss); + } + + /** + * set the Font Color for this Format via indexed color constant + * + * @param int Excel color constant + */ + public void setFontColor(int t) { + Font f = cloneFont(myxf.getFont()); + f.setColor(t); + updateFont(f); + } + + /** + * set the Font Color for this Format + * + * @param AWT Color color constant + */ + public void setFontColor(Color colr) { + Font f = cloneFont(myxf.getFont()); + f.setColor(colr); + updateFont(f); + } + + /** + * sets the Font color for this Format via web Hex String + * + * @param clr + */ + public void setFontColor(String clr) { + Font f = cloneFont(myxf.getFont()); + f.setColor(clr); + updateFont(f); + } + + /** + * Get the Font foreground (text) color as a java.awt.Color + * + * @return + */ + public Color getFontColor() { + return myxf.getFont().getColorAsColor(); + } + + + public String getFontColorAsHex() { + return myxf.getFont().getColorAsHex(); + } + + + /** + * set the Foreground Color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @param int Excel color constant + */ + public void setForegroundColor(int t) { + Xf xf = cloneXf(myxf); + xf.setForeColor(t, null); + updateXf(xf); + } + + /** + * set the foreground Color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @param AWT Color constant + */ + public void setForegroundColor(Color colr) { + Xf xf = cloneXf(myxf); + int clrz = getColorInt(colr); + xf.setForeColor(clrz, colr); + updateXf(xf); + } + + /** + * set the background color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @param t Excel color constant + */ + public void setBackgroundColor(int t) { + Xf xf = cloneXf(myxf); + if (xf.getFillPattern() == Xf.PATTERN_SOLID) + xf.setForeColor(t, null); + else + xf.setBackColor(t, null); + + updateXf(xf); + } + + /** + * set the Cell Background Color for this Format + *

        + * NOTE: Foreground color = the CELL BACKGROUND color color for all patterns + * and Background color= the PATTERN color for all patterns != Solid For + * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background + * Color=64 (white). + * + * @param awt Color color constant + */ + public void setCellBackgroundColor(Color colr) { + int clrz = getColorInt(colr); + setCellBackgroundColor(clrz); + + } + + /** + * makes the cell a solid pattern background if no pattern was already + * present NOTE: Foreground color = the CELL BACKGROUND color color for all + * patterns and Background color= the PATTERN color for all patterns != + * Solid For PATTERN_SOLID, Foreground color=CELL BACKGROUND color and + * Background Color=64 (white). + * + * @param int Excel color constant + */ + public void setCellBackgroundColor(int t) { + Xf xf = cloneXf(myxf); + + if (xf.getFillPattern() == 0) + xf.setBackgroundSolid(); + + if (xf.getFillPattern() == Xf.PATTERN_SOLID) + xf.setForeColor(t, null); + else + xf.setBackColor(t, null); + updateXf(xf); + } + + /** + * sets this fill pattern from an existing OOXML (2007v) fill element + * + * @param f + */ + protected void setFill(Fill f) { + Xf xf = cloneXf(myxf); + xf.setFill(f); + updateXf(xf); + } + + /** + * sets the fill for this format handle if fill==Xf.PATTERN_SOLID then fg is + * the PATTERN color i.e the CELL BG COLOR + * + * @param fillpattern + * @param fg + * @param bg + */ + public void setFill(int fillpattern, int fg, int bg) { + Xf xf = cloneXf(myxf); + + xf.setPattern(fillpattern); + + /** + * If the fill style is solid: When solid is specified, the foreground + * color (fgColor) is the only color rendered, even when a background + * color (bgColor) is also specified. + */ + if (xf.getFillPattern() == Xf.PATTERN_SOLID) { // is reversed + xf.setForeColor(bg, null); + xf.setBackColor(64, null); + } else { + /** + * or cell fills with patterns specified, then the cell fill color + * is specified by the bgColor element + */ + xf.setForeColor(fg, null); + xf.setBackColor(bg, null); + } + updateXf(xf); + } + + /** + * Get the Pattern Background Color for this Format Pattern + * + * @return the Excel color constant + */ + public int getBackgroundColor() { + return myxf.getBackgroundColor(); + } + + /** + * get the Pattern Background Color for this Format Pattern as a hex string + * + * @return Hex Color String + */ + public String getBackgroundColorAsHex() { + return myxf.getBackgroundColorHEX(); + } + + /** + * get the Pattern Background Color for this Format Pattern as an awt.Color + * + * @return background Color + */ + public java.awt.Color getBackgroundColorAsColor() { + return HexStringToColor(this.getBackgroundColorAsHex()); + } + + + /** + * returns the foreground color setting regardless of format pattern (which + * can switch fg and bg) + * + * @return + */ + public int getTrueForegroundColor() { + return myxf.getForegroundColor(); // 20080814 KSC: getForegroundColor() does the swapping so use base method + } + + /** + * get the Pattern Background Color for this Formatted Cell + *

        + * This method handles display of conditional formats for the cell + *

        + * checks for conditional format, then applies it if conditions are true. + * + * @return the Excel color constant + */ + public int getCellBackgroundColor() { + int fp = getFillPattern(); + + if (fp == Xf.PATTERN_SOLID) + return myxf.getForegroundColor(); // this.getForegroundColor() does + // the swapping so use base + // method + else + return myxf.getBackgroundColor(); + } + + /** + * get the Pattern Background Color for this Format as a Hex Color String + * + * @return Hex Color String + */ + public String getCellBackgroundColorAsHex() { + int fp = getFillPattern(); + + if (fp == Xf.PATTERN_SOLID) + return myxf.getForegroundColorHEX(); // this.getForegroundColor() does + else + return myxf.getBackgroundColorHEX(); + } + + /** + * get the Pattern Background Color for this Format as an awt.Color + * + * @return cell background color + */ + public java.awt.Color getCellBackgroundColorAsColor() { + return HexStringToColor(this.getCellBackgroundColorAsHex()); + } + + /** + * get the Background Color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @return the Excel color constant + */ + public int getForegroundColor() { + // if it's SOLID pattern, fg/bg are swapped + if (getFillPattern() == Xf.PATTERN_SOLID) + return myxf.getBackgroundColor(); + + return myxf.getForegroundColor(); + } + + /** + * get the Background Color for this Format as a Hex Color String NOTE: + * Foreground color = the CELL BACKGROUND color color for all patterns and + * Background color= the PATTERN color for all patterns != Solid For + * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background + * Color=64 (white). + * + * @return Hex Color String + */ + public String getForegroundColorAsHex() { + if (getFillPattern() == Xf.PATTERN_SOLID) // if it's SOLID pattern, + return myxf.getBackgroundColorHEX(); + + return myxf.getForegroundColorHEX(); + } + + /** + * get the Background Color for this Format as a Color NOTE: + * Foreground color = the CELL BACKGROUND color color for all patterns and + * Background color= the PATTERN color for all patterns != Solid For + * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background + * Color=64 (white). + * + * @return Hex Color String + */ + public Color getForegroundColorAsColor() { + return HexStringToColor(this.getForegroundColorAsHex()); + } + + /** + * set the Background Color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @param awt Color color constant + */ + public void setBackgroundColor(Color colr) { + Xf xf = cloneXf(myxf); + int clrz = getColorInt(colr); + xf.setBackColor(clrz, colr); + updateXf(xf); + } + + /** + * Set the format handle to use standard bold text + * + * @param boolean isBold + */ + public void setBold(boolean isBold) { + Font f = cloneFont(myxf.getFont()); + f.setBold(isBold); + updateFont(f); + } + + /** + * Get if this format is bold or not + * + * @return boolean whether the cell font format is bold + */ + public boolean getIsBold() { + return myxf.getFont().getIsBold(); + } + + /** + * Return an int representing the underline style + *

        + * These map to the STYLE_UNDERLINE static integers * + * + * @return int underline style + */ + public int getUnderlineStyle() { + return myxf.getFont().getUnderlineStyle(); + } + + /** + * Get the font height in points + * + * @return font height + */ + public double getFontHeightInPoints() { + return getFont().getFontHeightInPoints(); + } + + /** + * Returns the Font's height in 1/20th point increment + * + * @return font height + */ + public int getFontHeight() { + return getFont().getFontHeight(); + } + + /** + * Set the Font's height in 1/20th point increment + * + * @param new font height + */ + public void setFontHeight(int fontHeight) { + Font f = cloneFont(myxf.getFont()); + f.setFontHeight(fontHeight); + updateFont(f); + } + + /** + * Returns the Font's name + * + * @return font name + */ + public String getFontName() { + return getFont().getFontName(); + } + + /** + * Set the Font's name + *

        + * To be valid, this font name must be available on the client system. + * + * @param font name + */ + public void setFontName(String fontName) { + Font f = cloneFont(myxf.getFont()); + f.setFontName(fontName); + updateFont(f); + } + + /** + * Determine if the format handle refers to a font stricken out + * + * @return boolean representing if the FormatHandle is striking out a cell. + */ + public boolean getStricken() { + if (myxf.getFont() == null) + return false; + return myxf.getFont().getStricken(); + } + + /** + * Set if the format handle is stricken out + * + * @param isStricken boolean representing if the formatted cell should be stricken + * out. + */ + public void setStricken(boolean isStricken) { + Font f = cloneFont(myxf.getFont()); + f.setStricken(isStricken); + updateFont(f); + } + + /** + * Get if the font is italic + * + * @return boolean representing if the formatted cell is italic. + */ + public boolean getItalic() { + if (myxf.getFont() == null) + return false; + return myxf.getFont().getItalic(); + } + + /** + * Set if the font is italic + * + * @param isItalic boolean representing if the formatted cell should be italic. + */ + public void setItalic(boolean isItalic) { + Font f = cloneFont(myxf.getFont()); + f.setItalic(isItalic); + updateFont(f); + } + + /** + * Get if the font is underlined + * + * @return boolean representing if the formatted cell is underlined. + */ + public boolean getUnderlined() { + if (myxf.getFont() == null) + return underlined; + return myxf.getFont().getUnderlined(); + } + + /** + * Set underline attribute on the font + * + * @param isUnderlined boolean representing if the formatted cell should be + * underlined + */ + public void setUnderlined(boolean isUnderlined) { + Font f = cloneFont(myxf.getFont()); + f.setUnderlined(isUnderlined); + updateFont(f); + + } + + /** + * Get if the font is bold + * + * @return boolean representing if the formatted cell is bold + */ + public boolean getBold() { + return myxf.getFont().getBold(); + } + + /** + * returns the existing font record for this Format + *

        + * Font is an internal record and should not be accessed by end users + * + * @return the XLS Font record associated with this Format + */ + public io.starter.formats.XLS.Font getFont() { + // should this be a protected method? + if (myxf != null) // shouldn't! + return myxf.getFont(); + return null; + } + + /** + * Sets the number format pattern for this format. All Excel built-in number + * formats are supported. Custom formats will not be applied by OpenXLS + * (e.g. {@link CellHandle#getFormattedStringVal}) but they will be written + * correctly to the output file. For more information on number format + * patterns see Microsoft + * KB264372. + * + * @param pat the Excel number format pattern to apply + * @see FormatConstantsImpl#getBuiltinFormats + */ + public void setFormatPattern(String pat) { + Xf xf = cloneXf(myxf); + xf.setFormatPattern(pat); + updateXf(xf); + } + + public void setPattern(int pat) { + Xf xf = cloneXf(myxf); + xf.setPattern(pat); + updateXf(xf); + } + + /** + * The format ID allows setting of the format for a Cell without adding it + * to the Format's Cell Collection. + *

        + * Use to decrease memory requirements when dealing with large collections + * of cells. + *

        + * Usage: + *

        + * mycell.setFormatId(myformat.getFormatId()); + * + * @return the format ID for this Format + */ + public int getFormatId() { + return xfe; + } + + /** + * Gets the number format pattern for this format, if set. For more + * information on number format patterns see Microsoft KB264372. + * + * @return the Excel number format pattern for this cell or + * null if none is applied + */ + public String getFormatPattern() { + return myxf.getFormatPattern(); + } + + /** + * get the fill pattern for this format + */ + public int getFillPattern() { + return myxf.getFillPattern(); + } + + /** + * returns the index of the Color within the Colortable + * + * @param the index of the color in the colortable + * @return the color + */ + public static Color getColor(int col) { + if (col > -1 && col < FormatHandle.COLORTABLE.length) + return FormatHandle.COLORTABLE[col]; + return FormatHandle.COLORTABLE[0]; + } + + /** + * returns the index of the Color within the Colortable + * + * @param col the color + * @return the index of the color in the colortable + */ + public static int getColorInt(Color col) { + for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) { + if (col.equals(FormatHandle.COLORTABLE[i])) { + return i; + } + } + int R = col.getRed(); + int G = col.getGreen(); + int B = col.getBlue(); + int colorMatch = -1; + double colorDiff = Integer.MAX_VALUE; + for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) { + double curDif = + Math.pow(R - FormatHandle.COLORTABLE[i].getRed(), 2) + + Math.pow(G - FormatHandle.COLORTABLE[i].getGreen(), 2) + + Math.pow(B - FormatHandle.COLORTABLE[i].getBlue(), 2); + if (curDif < colorDiff) { + colorDiff = curDif; + colorMatch = i; + } + } + return colorMatch; + } + + /** + * Sets the internal format to the FormatHandle. For internal use only, not + * supported. + */ + public void setXf(Xf xrec) { + myxf = xrec; + } + + /** + * Set the horizontal alignment for this FormatHandle + * + * @param align - an int representing the alignment. Please review the + * FormatHandle.ALIGN*** static int's + */ + public void setHorizontalAlignment(int align) { + Xf xf = cloneXf(myxf); + xf.setHorizontalAlignment(align); + updateXf(xf); + } + + /** + * Returns an int representing the current horizontal alignment in this + * FormatHandle. These values are mapped to the FormatHandle.ALIGN*** static + * int's + */ + public int getHorizontalAlignment() { + return myxf.getHorizontalAlignment(); + } + + /** + * set indent (1= 3 spaces) + * + * @param indent + */ + public void setIndent(int indent) { + Xf xf = cloneXf(myxf); + xf.setIndent(indent); + updateXf(xf); + } + + /** + * increase or decrease the precision of this numeric or curerncy format pattern + *
        If the format pattern is "General", converts to a basic number pattern + *
        If the precision is already 0 and !increase, this method does nothing + * + * @param increase true if increase the precsion (number of decimals to display) + */ + public void adjustPrecision(boolean increase) { + // TODO: if decimal is contained within quotes ... + String pat = getFormatPattern(); + if (pat.equals("General") && increase) { + pat = "0.0"; // the most basic numeric pattern + this.setFormatPattern(pat); + return; + } + + try { + // split pattern and deal with positive, negative, zero and text separately + // for each, find decimal place and increment/decrement; if not found, find last digit placeholder + String[] pats = pat.split(";"); + String newPat = ""; + for (int i = 0; i < pats.length; i++) { + if (i > 0) newPat += ';'; + int z = pats[i].indexOf('.'); // position of decimal + boolean foundit = false; + if (z != -1) { // found decimal place + z++; + for (; z < pats[i].length(); z++) { + char c = pats[i].charAt(z); + if ((c == '0' || c == '#' || c == '?')) // numeric placeholders + foundit = true; + else if (foundit && !(c == '0' || c == '#' || c == '?')) // numeric placeholders. if hit last one, either inc or dec + break; + } + if (increase) + newPat += new StringBuffer(pats[i]).insert(z, "0").toString(); //pats[i].substring(0, z) + "0" + pats[i].substring(z+1); + else { + if (pats[i].charAt(z - 2) != '.') { + newPat += new StringBuffer(pats[i]).deleteCharAt(z - 1).toString(); // .pats[i].substring(0, z-1) + pats[i].substring(z+1); + } else { + newPat += new StringBuffer(pats[i]).delete(z - 2, z).toString(); + } + } + + } else if (increase) { // no decimal yet. If decrease, ignore. if increase, add + z = pats[i].length() - 1; + for (; z >= 0; z--) { + char c = pats[i].charAt(z); + if ((c == '0' || c == '#' || c == '?')) { // found last numeric placeholder + foundit = true; + break; + } + } + if (foundit) // if had ANY numeric placeholders + newPat += new StringBuffer(pats[i]).insert(z + 1, ".0").toString(); //pats[i].substring(0, z) + ".0" + pats[i].substring(z+1); + else + newPat += pats[i]; // keep original + } else // if decrease and no decimal found, leave alone + newPat += pats[i]; // leave alone + } + + //io.starter.toolkit.Logger.log("Old Style" + pat + ". New Style: " + newPat + ". Increase?" + (increase?"yes":"no")); // KSC: TESETING: TAKE OUT WHEN DONE + this.setFormatPattern(newPat); + } catch (Exception e) { + Logger.logErr("Error setting style"); // KSC: TESETING: TAKE OUT WHEN DONE + } + } + + /** + * return indent (1 = 3 spaces) + * + * @return + */ + public int getIndent() { + return myxf.getIndent(); + } + + /** + * sets the Right to Left Text Direction or reading order of this style + * + * @param rtl possible values: + *
        0=Context Dependent + *
        1=Left-to-Right + *
        2=Right-to-Let + * @param rtl possible values:
        + * 0=Context Dependent
        + * 1=Left-to-Right
        + * 2=Right-to-Let + */ + public void setRightToLeftReadingOrder(int rtl) { + Xf xf = cloneXf(myxf); + xf.setRightToLeftReadingOrder(rtl); + updateXf(xf); + } + + /** + * returns true if this style is set to Right-to-Left text direction + * (reading order) + * + * @return + */ + public int getRightToLetReadingOrder() { + return myxf.getRightToLeftReadingOrder(); + } + + /** + * Set the Vertical alignment for this FormatHandle + * + * @param align - an int representing the alignment. Please review the + * FormatHandle.ALIGN*** static int's + */ + public void setVerticalAlignment(int align) { + Xf xf = cloneXf(myxf); + xf.setVerticalAlignment(align); + updateXf(xf); + } + + /** + * Returns an int representing the current Vertical alignment in this + * FormatHandle. These values are mapped to the FormatHandle.ALIGN*** static + * int's + */ + public int getVerticalAlignment() { + return myxf.getVerticalAlignment(); + } + + /** + * DEPRECATED and non functional. Not neccesary as this occurs automatically + *

        + * Consolidates this Format with other identical formats in workbook + *

        + *

        + * There is a limit to the number of distinct formats in an Excel workbook. + *

        + * This method allows you to share Formats between identically formatted + * cells. + * + * @return + * @deprecated + */ + public FormatHandle pack() { + return this; // wkbook.cache.get(this); + } + + /** + * Set the workbook for this FormatHandle + * + * @param bk + */ + public void setWorkBook(io.starter.formats.XLS.WorkBook bk) { + wkbook = bk; + } + + /** + * Set the cell wrapping behavior for this FormatHandle. Default is false + */ + public void setWrapText(boolean wrapit) { + Xf xf = cloneXf(myxf); + xf.setWrapText(wrapit); + updateXf(xf); + } + + /** + * Get the cell wrapping behavior for this FormatHandle. Default is false + */ + public boolean getWrapText() { + return myxf.getWrapText(); + } + + + /** + * Set the rotation of the cell in degrees. Values 0-90 represent rotation + * up, 0-90degrees. Values 91-180 represent rotation down, 0-90 degrees. + * Value 255 is vertical + * + * @param align - an int representing the rotation. + */ + public void setCellRotation(int align) { + Xf xf = cloneXf(myxf); + xf.setRotation(align); + updateXf(xf); + } + + /** + * Get the rotation of the cell. Value 0 means no rotation (horizontal + * text). Values 1-90 mean rotation up (couter-clockwise) by 1-90 degrees. + * Values 91-180 mean rotation down (clockwise) by 1-90 degrees. Value 255 + * means vertical text. + */ + public int getCellRotation() { + return myxf.getRotation(); + } + + /** + * Get a JSON representation of the format + * + * @param cr + * @return + */ + public String getJSON(int XFNum) { + return getJSONObject(XFNum).toString(); + } + + /** + * Get a JSON representation of the format + *

        + * font height is represented as HTML pt size + * + * @param cr + * @return + */ + public JSONObject getJSONObject(int XFNum) { + Font myf = getFont(); + JSONObject theStyle = new JSONObject(); + try { + theStyle.put("style", XFNum); + // handle the font + JSONObject theFont = new JSONObject(); + theFont.put("name", myf.getFontName()); + + // round out the font size... + long sz = Math.round(myf.getFontHeight() / 22.0); + + theFont.put("size", sz); // adjust smaller + theFont.put("color", colorToHexString(myf.getColorAsColor())); + theFont.put("weight", myf.getFontWeight()); + if (getIsBold()) { + theFont.put("bold", "1"); + } + if (this.getUnderlined()) { + theFont.put("underline", "1"); + } else { + theFont.put("underline", "0"); + } + + if (getItalic()) { + theFont.put("italic", "1"); + } + + theStyle.put("font", theFont); + + // + JSONObject border = new JSONObject(); + if (getRightBorderLineStyle() != 0) { + JSONObject rBorder = new JSONObject(); + rBorder.put("style", + BORDER_STYLES_JSON[getRightBorderLineStyle()]); + rBorder.put("color", colorToHexString(getBorderRightColor())); + border.put("right", rBorder); + } + if (getBottomBorderLineStyle() != 0) { + JSONObject bBorder = new JSONObject(); + bBorder.put("style", + BORDER_STYLES_JSON[getBottomBorderLineStyle()]); + bBorder.put("color", colorToHexString(getBorderBottomColor())); + border.put("bottom", bBorder); + } + if (getLeftBorderLineStyle() != 0) { + JSONObject lBorder = new JSONObject(); + lBorder.put("style", + BORDER_STYLES_JSON[getLeftBorderLineStyle()]); + lBorder.put("color", colorToHexString(getBorderLeftColor())); + border.put("left", lBorder); + } + if (getTopBorderLineStyle() != 0) { + JSONObject tBorder = new JSONObject(); + tBorder.put("style", + BORDER_STYLES_JSON[getTopBorderLineStyle()]); + tBorder.put("color", colorToHexString(getBorderTopColor())); + border.put("top", tBorder); + } + theStyle.put("borders", border); + + // + JSONObject alignment = new JSONObject(); + alignment.put("horizontal", + HORIZONTAL_ALIGNMENTS[getHorizontalAlignment()]); + alignment.put("vertical", + VERTICAL_ALIGNMENTS[getVerticalAlignment()]); + if (getWrapText()) { + alignment.put("wrap", "1"); + } + theStyle.put("alignment", alignment); + + if (getIndent() != 0) { + theStyle.put("indent", getIndent()); + } + // colors + background patterns + if (getFillPattern() >= 0) { // KSC: added >= 0 as some conditional formats have patternFillStyle==0 even though they have a pattern block and + JSONObject interior = new JSONObject(); + // weird black/white case + if (myxf.getForegroundColor() == 65) { + interior.put("color", "#FFFFFF"); + } else { + // KSC: use color string if it exists; create if doesn't + interior.put("color", myxf.getForegroundColorHEX()); + } + interior.put("pattern", myxf.getFillPattern()); + interior.put("fg", myxf.getForegroundColor()); // Excel-2003 Color Table index + interior.put("patterncolor", myxf.getBackgroundColorHEX()); + interior.put("bg", myxf.getBackgroundColor()); // Excel-2003 Color Table index + theStyle.put("interior", interior); + } + + if (myxf.getIfmt() != 0) { // only input user defined formats ... + JSONObject nFormat = new JSONObject(); + String fmtpat = getFormatPattern(); + try { + if (!fmtpat.equals("General")) { + nFormat.put("format", fmtpat); // convertXMLChars? + nFormat.put("formatid", myxf.getIfmt()); + if (isDate()) + nFormat.put("isdate", "1"); + if (isCurrency()) + nFormat.put("iscurrency", "1"); + if (isRedWhenNegative()) + nFormat.put("isrednegative", "1"); + } + + } catch (Exception e) { // it's possible that getFormatPattern + // returns null + } + theStyle.put("numberformat", nFormat); + } + // + JSONObject protection = new JSONObject(); + try { + if (this.myxf.isLocked()) + protection.put("Protected", true); + if (this.myxf.isFormulaHidden()) + protection.put("HideFormula", true); + } catch (Exception e) { + } + theStyle.put("protection", protection); + + } catch (JSONException e) { + Logger.logErr("Error getting cellRange JSON: " + e); + } + return theStyle; + } + + /** + * Returns an XML fragment representing the FormatHandle + */ + public String getXML(int XFNum) { + return getXML(XFNum, false); + } + + /** + * Returns an XML fragment representing the FormatHandle + * + * @param convertToUnicodeFont if true, font family will be changed to ArialUnicodeMS + * (standard unicode) for non-ascii fonts + */ + public String getXML(int XFNum, boolean convertToUnicodeFont) { + Font myf = getFont(); + // "); + return sb.toString(); + } + + /** + * Gets the Excel format ID for this format's number format pattern. + * + * @return the Excel format identifier number for the number format pattern + */ + public int getFormatPatternId() { + return myxf.getIfmt(); + } + + /** + * Sets the number format pattern based on the format ID number. This method + * is recommended for advanced users only. In most cases you should use + * {@link #setFormatPattern(String)} instead. + * + * @param fmt the format ID number for the desired number format pattern + */ + public void setFormatPatternId(int fmt) { + myxf.setFormat((short) fmt); + } + + /** + * Convert a java.awt.Color to a hex string. + * + * @return String representation of a Color + */ + public static String colorToHexString(Color c) { + int r = c.getRed(); + int g = c.getGreen(); + int b = c.getBlue(); + String rh = Integer.toHexString(r); + if (rh.length() < 2) { + rh = "0" + rh; + } + String gh = Integer.toHexString(g); + if (gh.length() < 2) { + gh = "0" + gh; + } + String bh = Integer.toHexString(b); + if (bh.length() < 2) { + bh = "0" + bh; + } + return ("#" + rh + gh + bh).toUpperCase(); + } + + + public static short colorFONT = 0; + public static short colorBACKGROUND = 1; + public static short colorFOREGROUND = 2; + public static short colorBORDER = 3; + + /** + * convert hex string RGB to Excel colortable int format if an exact match + * is not find, does color-matching to try and obtain closest match + * + * @param s + * @param colorType + * @return + */ + public static int HexStringToColorInt(String s, short colorType) { + if (s.length() > 7) + s = "#" + s.substring(s.length() - 6); + if (s.indexOf("#") == -1) + s = "#" + s; + if (s.length() == 7) { + String rs = s.substring(1, 3); + int r = Integer.parseInt(rs, 16); + String gs = s.substring(3, 5); + int g = Integer.parseInt(gs, 16); + String bs = s.substring(5, 7); + int b = Integer.parseInt(bs, 16); + // Handle exceptions for black, white and color indexes 9 (see + // FormatConstants for more info) + + if (r == 255 && r == g && r == b) { + if (colorType == colorFONT) + return 9; + } + + Color c = new Color(r, g, b); + return getColorInt(c); + } + return 0; + } + + /** + * convert hex string RGB to Excel colortable int format if no exact match + * is found returns -1 + * + * @param s HTML color string (#XXXXXX) format or (#FFXXXXXX - OOXML-style + * format) + * @param colorType + * @return index into color table or -1 if not found + */ + public static int HexStringToColorIntExact(String s, short colorType) { + if (s.length() > 7) + s = "#" + s.substring(s.length() - 6); + if (s.indexOf("#") == -1) + s = "#" + s; + if (s.length() == 7) { + String rs = s.substring(1, 3); + int r = Integer.parseInt(rs, 16); + String gs = s.substring(3, 5); + int g = Integer.parseInt(gs, 16); + String bs = s.substring(5, 7); + int b = Integer.parseInt(bs, 16); + + // Handle exceptions for black, white and color indexes 9 (see + // FormatConstants for more info) + if (r == 255 && r == g && r == b) { + if (colorType == colorFONT) + return 9; + } + + Color c = new Color(r, g, b); + for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) { + if (c.equals(FormatHandle.COLORTABLE[i])) { + return i; + } + } + } + return -1; // match NOT FOUND + } + + /** + * convert hex string RGB to a Color + * + * @param s web-style hex color string, including #, or OOXML-style color string, FFXXXXXX + * @return Color + * @see Color + */ + public static Color HexStringToColor(String s) { + if (s.length() > 7) { // transform OOXML-style color strings to Web-style + s = "#" + s.substring(s.length() - 6); + } + Color c = null; + if (s.length() == 7) { + String rs = s.substring(1, 3); + int r = Integer.parseInt(rs, 16); + String gs = s.substring(3, 5); + int g = Integer.parseInt(gs, 16); + String bs = s.substring(5, 7); + int b = Integer.parseInt(bs, 16); + c = new Color(r, g, b); + } else + c = new Color(0, 0, 0); // default to black? + return c; + } + + /** + * interpret color table special entries + * + * @param clr color index in range of 0x41-0x4F (charts) 0x40 ... + * @return index into color table + */ + public static short interpretSpecialColorIndex(int clr) { + switch (clr) { + case 0x0041: // Default background color. This is the window background + // color in the sheet display and is the default + // background color for a cell. + case 0x004E: // Default chart background color. This is the window + // background color in the chart display. + case 0x0050: // WHAT IS THIS ONE???????? + return FormatConstants.COLOR_WHITE; + case 0x0040: // Default foreground color + case 0x004F: // Chart neutral color which is black, an RGB value of + // (0,0,0). + case 0x004D: // Default chart foreground color. This is the window text + // color in the chart display. + case 0x0051: // ToolTip text color. This is the automatic font color for + // comments. + case 0x7FFF: // Font automatic color. This is the window text color + return FormatConstants.COLOR_BLACK; + default: // 67(=0x43) ??? + return FormatConstants.COLOR_WHITE; + } + + /* + * switch (icvFore) { case 0x40: // default fg color return + * FormatConstants.COLOR_WHITE; case 0x41: // default bg color return + * FormatConstants.COLOR_WHITE; case 0x4D: // default CHART fg color -- + * INDEX SPECIFIC! return -1; // flag to map via series (bar) color + * defaults case 0x4E: // default CHART fg color return icvFore; case + * 0x4F: // chart neutral color == black return + * FormatConstants.COLOR_BLACK; } + * + * switch (icvBack) { case 0x40: // default fg color return + * FormatConstants.COLOR_WHITE; case 0x41: // default bg color return + * FormatConstants.COLOR_WHITE; case 0x4D: // default CHART fg color -- + * INDEX SPECIFIC! return -1; // flag to map via series (bar) color + * defaults case 0x4E: // default CHART bg color //return + * FormatConstants.COLOR_WHITE; // is this correct? return icvBack; case + * 0x4F: // chart neutral color == black return + * FormatConstants.COLOR_BLACK; } + */ + } + + public static int BorderStringToInt(String s) { + for (int i = 0; i < FormatConstants.BORDER_NAMES.length; i++) { + if (FormatConstants.BORDER_NAMES[i].equals(s)) + return i; + } + return 0; + } + + // 20060412 KSC: added for access + public io.starter.formats.XLS.WorkBook getWorkBook() { + return wkbook; + } + + /** + * return truth of "this Xf rec is a style xf" + * + * @return + */ + public boolean isStyleXf() { + return myxf.isStyleXf(); + } + + /** + * creates a new font based on an existing one, adds to workbook recs + */ + private Font createNewFont(Font f) { + f.setIdx(-1); // flag to insert anew (see Font.setWorkBook) + wkbook.insertFont(f); + f.setWorkBook(wkbook); + return f; + } + + /** + * create a new font based on existing font - does not add to workbook recs + * + * @param src font + * @return cloned font + */ + private Font cloneFont(Font src) { + Font f = new Font(); + f.setOpcode(io.starter.formats.XLS.XLSConstants.FONT); + f.setData(src.getBytes()); // use default font as basis of new font + f.setIdx(-2); // avoid adding to fonts array when call setW.b. below + f.setWorkBook(this.getWorkBook()); + f.init(); + return f; + } + + /** + * update the font for this FormatHandle, including updating the xf if + * necessary + * + * @param f + */ + private void updateFont(Font f) { + int idx = wkbook.getFontIdx(f); + if (idx == -1) { // can't find it so add new + f = createNewFont(f); + } else + f = wkbook.getFont(idx); + if (f.getIdx() != myxf.getIfnt()) { // then updated the font, must create new xf to link to + Xf xf = cloneXf(myxf); + xf.setFont(f.getIdx()); + updateXf(xf); + } + } + + /** + * Create or Duplicate Xf rec so can alter (pattern, font, colrs ...) + * + * @param xf xf to base off of, or null (will create new) + * @return new Xf + */ + private Xf duplicateXf(Xf xf) { + int fidx = 0; + if (xf != null) + fidx = xf.getFont().getIdx(); + xf = Xf.updateXf(xf, fidx, wkbook); // clones/creates new based upon + // original + xfe = xf.getIdx(); // update the pointer // not used anymore canModify = true; // if duplicated, it's new and unlinked (thus far) - return xf; - } - - /** - * adds all formatting represented by the sourceXf to this workbook, if not - * already present
        - * This is used internally for transferring formats from one workbook to - * another - * - * @param xf - * - sourceXf - * @return ixfe of added Xf - */ - public int addXf(Xf sourceXf) { - // must handle font first in order to create xf below - // check to see if the font needs to be added in current workbook - int fidx = addFontIfNecessary(sourceXf.getFont()); - - /** XF **/ - Xf localXf = FormatHandle.cloneXf(sourceXf, wkbook.getFont(fidx), - wkbook); // clone xf so modifcations don't affect original - - /** NUMBER FORMAT **/ - String fmt = sourceXf.getFormatPattern(); // number format pattern - if (fmt != null) - localXf.setFormatPattern(fmt); // adds new format pattern if not // found - - // now check out to see if this particular xf pattern exists; if not, add - updateXf(localXf); - return xfe; - } - - /** - * if existing format matches, reuse. otherwise, create new Xf record and - * add to cache - * - * @param xf - */ - private void updateXf(Xf xf) { - if (!myxf.toString().equals(xf.toString())) { - if (myxf.getUseCount() <= 1 && xfe > 15) { // used only by one cell, OK to modify - if (writeImmediate || wkbook.getFormatCache().get(xf.toString()) == null) { - // myxf hasn't been used yet; modify bytes and re-init *** - byte[] xfbytes = xf.getBytes(); - myxf.setData(xfbytes); - if (xf.fill != null) - myxf.fill = (io.starter.formats.OOXML.Fill) xf.fill.cloneElement(); - myxf.init(); - myxf.setFont(myxf.getIfnt()); // set font as well .. - wkbook.updateFormatCache(myxf); // ensure new xf signature - // is stored - } else { - if (myxf.getUseCount()>0) - myxf.decUseCoount(); // flag original xf that 1 less record is referencing it - myxf = (Xf) wkbook.getFormatCache().get(xf.toString()); - xfe = myxf.getIdx(); // update the pointer - if (xfe == -1) // hasn't been added to wb yet - should this ever happen??? - myxf = duplicateXf(xf); // create a duplicate and leave original - else - myxf.incUseCount(); - } - } else { // cannot modify original - either find matching or create new - if (myxf.getUseCount()>0) - myxf.decUseCoount(); // flag original xf that 1 less record is referencing it - if (wkbook.getFormatCache().get(xf.toString()) == null) { // doesn't exist yet - myxf = duplicateXf(xf); // create a duplicate and leave original - } else { - myxf = (Xf) (wkbook.getFormatCache().get(xf.toString())); - xfe = myxf.getIdx(); // update the pointer - if (xfe == -1) // hasn't been added to the record store yet // - should ever happen??? - myxf = duplicateXf(xf); // create a duplicate and leave original - else - myxf.incUseCount(); - } - } - - for (int i = 0; i < mycells.size(); i++) { - ((BiffRec) mycells.get(i)).setXFRecord(xfe); // make sure all linked cells are updated as well - } - if (mycol != null) - mycol.setFormatId(xfe); - if (myrow != null) - myrow.setFormatId(xfe); - } - } - - /** - * create a duplicate xf rec based on existing ... - * - * @param xf - * @return cloned xf - */ - private Xf cloneXf(Xf xf) { - Xf clone = new Xf(xf.getFont().getIdx(), wkbook); - byte[] data = xf.getBytesAt(0, xf.getLength() - 4); - clone.setData(data); - if (xf.fill != null) - clone.fill = (io.starter.formats.OOXML.Fill) xf.fill.cloneElement(); - clone.init(); - return clone; - } - - /** - * static version of cloneXf - * - * @param xf - * @param wkbook - * @return - */ - public static Xf cloneXf(Xf xf, io.starter.formats.XLS.WorkBook wkbook) { - Xf clone = new Xf(xf.getFont().getIdx(), wkbook); - byte[] data = xf.getBytesAt(0, xf.getLength() - 4); - clone.setData(data); - clone.init(); - return clone; - } - - /** - * static version of cloneXf - * - * @param xf - * @param wkbook - * @return - */ - public static Xf cloneXf(Xf xf, Font f, - io.starter.formats.XLS.WorkBook wkbook) { - Xf clone = new Xf(f, wkbook); - byte[] data = xf.getBytesAt(0, xf.getLength() - 4); - clone.setData(data); - clone.setFont(f.getIdx()); // font idx is overwritten by xf data; must reset - clone.init(); - return clone; - } - - /** - * set the pointer to the XFE or Conditional format - * - * - * @param xfe - */ - public void setFormatId(int x) { - this.xfe = x; - } - - /** - * clear out object references - */ - public void close() { - mycells.clear(); - mycells = new CompatibleVector(); // all the Cells sharing this format - myxf = null; - mycol = null; - myrow = null; - wkbook = null; - wbh = null; - } - - protected void finalize() throws Throwable { - try { - close(); // close open files - } finally { - super.finalize(); - } - } - - /** - * For internal usage only, return the internal XF record that - * represents this FormatHandle - * @return the myxf - */ - protected Xf getXf() { - return myxf; - } + return xf; + } + + /** + * adds all formatting represented by the sourceXf to this workbook, if not + * already present
        + * This is used internally for transferring formats from one workbook to + * another + * + * @param xf - sourceXf + * @return ixfe of added Xf + */ + public int addXf(Xf sourceXf) { + // must handle font first in order to create xf below + // check to see if the font needs to be added in current workbook + int fidx = addFontIfNecessary(sourceXf.getFont()); + + /** XF **/ + Xf localXf = FormatHandle.cloneXf(sourceXf, wkbook.getFont(fidx), + wkbook); // clone xf so modifcations don't affect original + + /** NUMBER FORMAT **/ + String fmt = sourceXf.getFormatPattern(); // number format pattern + if (fmt != null) + localXf.setFormatPattern(fmt); // adds new format pattern if not // found + + // now check out to see if this particular xf pattern exists; if not, add + updateXf(localXf); + return xfe; + } + + /** + * if existing format matches, reuse. otherwise, create new Xf record and + * add to cache + * + * @param xf + */ + private void updateXf(Xf xf) { + if (!myxf.toString().equals(xf.toString())) { + if (myxf.getUseCount() <= 1 && xfe > 15) { // used only by one cell, OK to modify + if (writeImmediate || wkbook.getFormatCache().get(xf.toString()) == null) { + // myxf hasn't been used yet; modify bytes and re-init *** + byte[] xfbytes = xf.getBytes(); + myxf.setData(xfbytes); + if (xf.fill != null) + myxf.fill = (io.starter.formats.OOXML.Fill) xf.fill.cloneElement(); + myxf.init(); + myxf.setFont(myxf.getIfnt()); // set font as well .. + wkbook.updateFormatCache(myxf); // ensure new xf signature + // is stored + } else { + if (myxf.getUseCount() > 0) + myxf.decUseCoount(); // flag original xf that 1 less record is referencing it + myxf = (Xf) wkbook.getFormatCache().get(xf.toString()); + xfe = myxf.getIdx(); // update the pointer + if (xfe == -1) // hasn't been added to wb yet - should this ever happen??? + myxf = duplicateXf(xf); // create a duplicate and leave original + else + myxf.incUseCount(); + } + } else { // cannot modify original - either find matching or create new + if (myxf.getUseCount() > 0) + myxf.decUseCoount(); // flag original xf that 1 less record is referencing it + if (wkbook.getFormatCache().get(xf.toString()) == null) { // doesn't exist yet + myxf = duplicateXf(xf); // create a duplicate and leave original + } else { + myxf = (Xf) (wkbook.getFormatCache().get(xf.toString())); + xfe = myxf.getIdx(); // update the pointer + if (xfe == -1) // hasn't been added to the record store yet // - should ever happen??? + myxf = duplicateXf(xf); // create a duplicate and leave original + else + myxf.incUseCount(); + } + } + + for (int i = 0; i < mycells.size(); i++) { + ((BiffRec) mycells.get(i)).setXFRecord(xfe); // make sure all linked cells are updated as well + } + if (mycol != null) + mycol.setFormatId(xfe); + if (myrow != null) + myrow.setFormatId(xfe); + } + } + + /** + * create a duplicate xf rec based on existing ... + * + * @param xf + * @return cloned xf + */ + private Xf cloneXf(Xf xf) { + Xf clone = new Xf(xf.getFont().getIdx(), wkbook); + byte[] data = xf.getBytesAt(0, xf.getLength() - 4); + clone.setData(data); + if (xf.fill != null) + clone.fill = (io.starter.formats.OOXML.Fill) xf.fill.cloneElement(); + clone.init(); + return clone; + } + + /** + * static version of cloneXf + * + * @param xf + * @param wkbook + * @return + */ + public static Xf cloneXf(Xf xf, io.starter.formats.XLS.WorkBook wkbook) { + Xf clone = new Xf(xf.getFont().getIdx(), wkbook); + byte[] data = xf.getBytesAt(0, xf.getLength() - 4); + clone.setData(data); + clone.init(); + return clone; + } + + /** + * static version of cloneXf + * + * @param xf + * @param wkbook + * @return + */ + public static Xf cloneXf(Xf xf, Font f, + io.starter.formats.XLS.WorkBook wkbook) { + Xf clone = new Xf(f, wkbook); + byte[] data = xf.getBytesAt(0, xf.getLength() - 4); + clone.setData(data); + clone.setFont(f.getIdx()); // font idx is overwritten by xf data; must reset + clone.init(); + return clone; + } + + /** + * set the pointer to the XFE or Conditional format + * + * @param xfe + */ + public void setFormatId(int x) { + this.xfe = x; + } + + /** + * clear out object references + */ + public void close() { + mycells.clear(); + mycells = new CompatibleVector(); // all the Cells sharing this format + myxf = null; + mycol = null; + myrow = null; + wkbook = null; + wbh = null; + } + + protected void finalize() throws Throwable { + try { + close(); // close open files + } finally { + super.finalize(); + } + } + + /** + * For internal usage only, return the internal XF record that + * represents this FormatHandle + * + * @return the myxf + */ + protected Xf getXf() { + return myxf; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/FormulaHandle.java b/src/main/java/io/starter/OpenXLS/FormulaHandle.java index 4a662f9..9d33486 100644 --- a/src/main/java/io/starter/OpenXLS/FormulaHandle.java +++ b/src/main/java/io/starter/OpenXLS/FormulaHandle.java @@ -2,531 +2,547 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.OpenXLS; -import java.util.Iterator; -import java.util.List; import io.starter.formats.XLS.*; -import io.starter.formats.XLS.formulas.CalculationException; -import io.starter.formats.XLS.formulas.FunctionConstants; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.formats.XLS.formulas.PtgArea; -import io.starter.formats.XLS.formulas.PtgName; -import io.starter.formats.XLS.formulas.PtgRef; -import io.starter.formats.XLS.formulas.FormulaParser; +import io.starter.formats.XLS.formulas.*; import io.starter.toolkit.Logger; +import java.util.Iterator; +import java.util.List; -/** Formula Handle allows for manipulation of Formulas within a WorkBook. - - @see WorkBookHandle - @see WorkSheetHandle - @see CellHandle -*/ -public class FormulaHandle -{ - public static String[][] getSupportedFunctions() { - return FunctionConstants.recArr; - } - +/** + * Formula Handle allows for manipulation of Formulas within a WorkBook. + * + * @see WorkBookHandle + * @see WorkSheetHandle + * @see CellHandle + */ +public class FormulaHandle { + public static String[][] getSupportedFunctions() { + return FunctionConstants.recArr; + } + private WorkBook bk; - /** Sets the location lock on the Cell Reference at the - * specified location - * - * Used to prevent updating of the Cell Reference when - * Cells are moved. - * - * @param location of the Cell Reference to be locked/unlocked - * @param lock status setting - * @return boolean whether the Cell Reference was found and modified - */ - public boolean setLocationLocked(String loc, boolean l){ - int x = Ptg.PTG_LOCATION_POLICY_UNLOCKED; - if(l)x = Ptg.PTG_LOCATION_POLICY_LOCKED; - return form.setLocationPolicy(loc, x); - } - + /** + * Sets the location lock on the Cell Reference at the + * specified location + *

        + * Used to prevent updating of the Cell Reference when + * Cells are moved. + * + * @param location of the Cell Reference to be locked/unlocked + * @param lock status setting + * @return boolean whether the Cell Reference was found and modified + */ + public boolean setLocationLocked(String loc, boolean l) { + int x = Ptg.PTG_LOCATION_POLICY_UNLOCKED; + if (l) x = Ptg.PTG_LOCATION_POLICY_LOCKED; + return form.setLocationPolicy(loc, x); + } + private Formula form; - - /** Sets the location lock on the Cell Reference at the - * specified location - * - * Used to prevent updating of the Cell Reference when - * Cells are moved. - * - * @param location of the Cell Reference to be locked/unlocked - * @param lock status setting - * @return boolean whether the Cell Reference was found and modified - */ - public boolean setLocationPolicy(String loc, int l){ - return form.setLocationPolicy(loc,l); - } - - /** Create a new FormulaHandle from an Excel Formula - - @param Formula - the formula to create a handle for. - */ - protected FormulaHandle(Formula f, WorkBook book){ + + /** + * Sets the location lock on the Cell Reference at the + * specified location + *

        + * Used to prevent updating of the Cell Reference when + * Cells are moved. + * + * @param location of the Cell Reference to be locked/unlocked + * @param lock status setting + * @return boolean whether the Cell Reference was found and modified + */ + public boolean setLocationPolicy(String loc, int l) { + return form.setLocationPolicy(loc, l); + } + + /** + * Create a new FormulaHandle from an Excel Formula + * + * @param Formula - the formula to create a handle for. + */ + protected FormulaHandle(Formula f, WorkBook book) { this.bk = book; form = f; } - - + + /** * Returns the cell Address of the formula */ - public String getCellAddress(){ - return form.getCellAddress(); + public String getCellAddress() { + return form.getCellAddress(); } - - /** Returns the Human-Readable Formula String - - @return String the Formula in Human-readable format - */ - public String getFormulaString(){ - return form.getFormulaString(); + + /** + * Returns the Human-Readable Formula String + * + * @return String the Formula in Human-readable format + */ + public String getFormulaString() { + return form.getFormulaString(); } - - /** If the Formula evaluates to a String, return - the value as a String. - - @return String - value of the Formula if stored as a String. - */ + + /** + * If the Formula evaluates to a String, return + * the value as a String. + * + * @return String - value of the Formula if stored as a String. + */ public String getStringVal() - throws FunctionNotSupportedException{ - //this.form.init(); - return form.getStringVal(); + throws FunctionNotSupportedException { + //this.form.init(); + return form.getStringVal(); + } + + /** + * Converts a cell value to a form suitable for the public API. + * Currently this converts cached errors ({@link CalculationException}s) + * to the corresponding error string. + */ + static Object sanitizeValue(Object val) { + if (val instanceof CalculationException) + return ((CalculationException) val).getName(); + return val; + } + + /** + * Return the value of the Formula + * + * @return Object - value of the Formula + */ + public Object getVal() + throws FunctionNotSupportedException { + return sanitizeValue(form.calculateFormula()); } - /** Converts a cell value to a form suitable for the public API. - * Currently this converts cached errors ({@link CalculationException}s) - * to the corresponding error string. - */ - static Object sanitizeValue (Object val) { - if (val instanceof CalculationException) - return ((CalculationException)val).getName(); - return val; - } - - /** Return the value of the Formula - - @return Object - value of the Formula - */ - public Object getVal() - throws FunctionNotSupportedException{ - return sanitizeValue( form.calculateFormula() ); - } - - /** Return the cached value of the Formula. - * - * This method returns the value as cached by OpenXLS or Excel of the formula. Please note - * that cases could exist where a cached value does not exist. In this case getCachedVal will not try and calculate - * the formula, it will return null. - - @return Object - cached value of the Formula as a String or a Double dependent on data type. - - public Object getCachedVal() { - return form.getCachedVal(); - } */ - - - /** - * Calculate the value of the formula and return it as an object - * + /** Return the cached value of the Formula. + * + * This method returns the value as cached by OpenXLS or Excel of the formula. Please note + * that cases could exist where a cached value does not exist. In this case getCachedVal will not try and calculate + * the formula, it will return null. + + @return Object - cached value of the Formula as a String or a Double dependent on data type. + + public Object getCachedVal() { + return form.getCachedVal(); + } */ + + + /** + * Calculate the value of the formula and return it as an object + *

        * Calling calculate will ignore the WorkBook formula calculation flags * and forces calculation of the entire formula stack - */ + */ public Object calculate() - throws FunctionNotSupportedException{ - form.clearCachedValue(); - - return sanitizeValue( form.calculate() ); + throws FunctionNotSupportedException { + form.clearCachedValue(); + + return sanitizeValue(form.calculate()); } - - - /** - * Sets the formula to a string passed in excel formula format. - * @param formulaString - String formatted as an excel formula, like Sum(A3+4) - */ - - public void setFormula(String formulaString) - throws FunctionNotSupportedException{ - form = FormulaParser.setFormula(form, formulaString, new int[] { form.getRowNumber(), form.getColNumber()}); - } - - /** If the Formula evaluates to a String, there - will be a Stringrec attached to the Formula - which contains the latest value. - - @return boolean whether this Formula evaluates to a String - */ - public boolean evaluatesToString(){ - return (form.calculateFormula() instanceof String); + + + /** + * Sets the formula to a string passed in excel formula format. + * + * @param formulaString - String formatted as an excel formula, like Sum(A3+4) + */ + + public void setFormula(String formulaString) + throws FunctionNotSupportedException { + form = FormulaParser.setFormula(form, formulaString, new int[]{form.getRowNumber(), form.getColNumber()}); + } + + /** + * If the Formula evaluates to a String, there + * will be a Stringrec attached to the Formula + * which contains the latest value. + * + * @return boolean whether this Formula evaluates to a String + */ + public boolean evaluatesToString() { + return (form.calculateFormula() instanceof String); } - - /** If the Formula evaluates to a float, return - the value as an float. - - If the workbook level flag CALCULATE_EXPLICIT is set - then the cached value of the formula (if available) will be returned, - otherwise the latest calculated value will be returned - - @return float - value of the Formula if available as a float. If the - value cannot be returned as a float NaN will be returned. - */ + + /** + * If the Formula evaluates to a float, return + * the value as an float. + *

        + * If the workbook level flag CALCULATE_EXPLICIT is set + * then the cached value of the formula (if available) will be returned, + * otherwise the latest calculated value will be returned + * + * @return float - value of the Formula if available as a float. If the + * value cannot be returned as a float NaN will be returned. + */ public float getFloatVal() - throws FunctionNotSupportedException{ - return form.getFloatVal(); - } - - /** If the Formula evaluates to a double, return - the value as an double. - - If the workbook level flag CALCULATE_EXPLICIT is set - then the cached value of the formula (if available) will be returned, - otherwise the latest calculated value will be returned - - @return double - value of the Formula if available as a double. If the - value cannot be returned as a double NaN will be returned. - */ + throws FunctionNotSupportedException { + return form.getFloatVal(); + } + + /** + * If the Formula evaluates to a double, return + * the value as an double. + *

        + * If the workbook level flag CALCULATE_EXPLICIT is set + * then the cached value of the formula (if available) will be returned, + * otherwise the latest calculated value will be returned + * + * @return double - value of the Formula if available as a double. If the + * value cannot be returned as a double NaN will be returned. + */ public double getDoubleVal() - throws FunctionNotSupportedException{ - return form.getDblVal(); - } - - /** If the Formula evaluates to an int, return - the value as an int. - - If the workbook level flag CALCULATE_EXPLICIT is set - then the cached value of the formula (if available) will be returned, - otherwise the latest calculated value will be returned - - @return int - value of the Formula if available as a int. If the value returned can not be - represented by an int or is a float/double with a non-zero mantissa a runtime NumberFormatException - will be thrown - */ + throws FunctionNotSupportedException { + return form.getDblVal(); + } + + /** + * If the Formula evaluates to an int, return + * the value as an int. + *

        + * If the workbook level flag CALCULATE_EXPLICIT is set + * then the cached value of the formula (if available) will be returned, + * otherwise the latest calculated value will be returned + * + * @return int - value of the Formula if available as a int. If the value returned can not be + * represented by an int or is a float/double with a non-zero mantissa a runtime NumberFormatException + * will be thrown + */ public int getIntVal() - throws FunctionNotSupportedException{ - return form.getIntVal(); + throws FunctionNotSupportedException { + return form.getIntVal(); } - - /** get CellRange strings referenced by this formula - * + + /** + * get CellRange strings referenced by this formula + * * @return * @throws FormulaNotFoundException */ - public String[] getRanges() - throws FormulaNotFoundException{ - Ptg[] locptgs = form.getCellRangePtgs(); - String[] ret = new String[locptgs.length]; - for(int x=0; x dx = form.getPtgsByLocation(formulaLoc); - Iterator lx=dx.iterator(); - while(lx.hasNext()){ - try{ - Ptg thisptg = (Ptg)lx.next(); - ReferenceTracker.updateAddressPerPolicy(thisptg, newaddr); - form.setCachedValue(null); // flag to recalculate - return true; - }catch(Exception e){ - Logger.logInfo("updating Formula reference failed:" + e.toString()); - return false; - } - } - return true; + Iterator lx = dx.iterator(); + while (lx.hasNext()) { + try { + Ptg thisptg = (Ptg) lx.next(); + ReferenceTracker.updateAddressPerPolicy(thisptg, newaddr); + form.setCachedValue(null); // flag to recalculate + return true; + } catch (Exception e) { + Logger.logInfo("updating Formula reference failed:" + e.toString()); + return false; + } + } + return true; } - - /** Changes a range in a formula to expand until it includes the - cell address from CellHandle. - - Example: - - CellHandle cell = new Cellhandle("D4") Formula = SUM(A1:B2) - addCellToRange("A1:B2",cell); would change the formula to look like"SUM(A1:D4)" - - Returns false if formula does not contain the formulaLoc range. - - @param String - the Cell Range as a String to add the Cell to - @param CellHandle - the CellHandle to add to the range - */ + + /** + * Changes a range in a formula to expand until it includes the + * cell address from CellHandle. + *

        + * Example: + *

        + * CellHandle cell = new Cellhandle("D4") Formula = SUM(A1:B2) + * addCellToRange("A1:B2",cell); would change the formula to look like"SUM(A1:D4)" + *

        + * Returns false if formula does not contain the formulaLoc range. + * + * @param String - the Cell Range as a String to add the Cell to + * @param CellHandle - the CellHandle to add to the range + */ public boolean addCellToRange(String formulaLoc, CellHandle handle) - throws FormulaNotFoundException{ - List dx = form.getPtgsByLocation(formulaLoc); - Iterator lx=dx.iterator(); - boolean b=false; - while(lx.hasNext()){ - Ptg ptg = (Ptg)lx.next(); - if (ptg == null)return false; - int[] formulaaddr = ExcelTools.getRangeRowCol(formulaLoc); - String handleaddr = handle.getCellAddress(); - int[] celladdr = ExcelTools.getRowColFromString(handleaddr); - - // check existing range and set new range vals if the new Cell is outside - if(celladdr[0] > formulaaddr[2])formulaaddr[2] = celladdr[0]; - if(celladdr[0] < formulaaddr[0])formulaaddr[0] = celladdr[0]; - if(celladdr[1] > formulaaddr[3])formulaaddr[3] = celladdr[1]; - if(celladdr[1] < formulaaddr[1])formulaaddr[1] = celladdr[1]; - String newaddr = ExcelTools.formatRange(formulaaddr); - b = this.changeFormulaLocation(formulaLoc, newaddr); - - } - return b; + throws FormulaNotFoundException { + List dx = form.getPtgsByLocation(formulaLoc); + Iterator lx = dx.iterator(); + boolean b = false; + while (lx.hasNext()) { + Ptg ptg = (Ptg) lx.next(); + if (ptg == null) return false; + int[] formulaaddr = ExcelTools.getRangeRowCol(formulaLoc); + String handleaddr = handle.getCellAddress(); + int[] celladdr = ExcelTools.getRowColFromString(handleaddr); + + // check existing range and set new range vals if the new Cell is outside + if (celladdr[0] > formulaaddr[2]) formulaaddr[2] = celladdr[0]; + if (celladdr[0] < formulaaddr[0]) formulaaddr[0] = celladdr[0]; + if (celladdr[1] > formulaaddr[3]) formulaaddr[3] = celladdr[1]; + if (celladdr[1] < formulaaddr[1]) formulaaddr[1] = celladdr[1]; + String newaddr = ExcelTools.formatRange(formulaaddr); + b = this.changeFormulaLocation(formulaLoc, newaddr); + + } + return b; } - - /** Copy the formula references with offsets - * - * @param int[row,col] offsets to move the references - * @return - * + + /** + * Copy the formula references with offsets + * + * @param int[row,col] offsets to move the references + * @return */ public static void moveCellRefs(FormulaHandle fmh, int[] offsets) throws FormulaNotFoundException { // get the current offsets from the FMH references - String [] celladdys = fmh.getRanges(); - + String[] celladdys = fmh.getRanges(); + // iterate - for(int x=0;x-1) { // separate out addresses within a range - secondAddress= range.substring(rangeIdx+1); - range= range.substring(0, rangeIdx); + String secondAddress = null; + if (rangeIdx > -1) { // separate out addresses within a range + secondAddress = range.substring(rangeIdx + 1); + range = range.substring(0, rangeIdx); } - int[] orig = ExcelTools.getRowColFromString(range); - boolean relCol= !(range.startsWith("$")); // 20100603 KSC: handle relative refs - boolean relRow= !(range.length()>0 && range.substring(1).indexOf('$')>-1); - if (relRow) // only move if relative ref - orig[0] += offsets[0]; //row - if (relCol)// only move if relative ref - orig[1]+= offsets[1]; //col - String newAddress= ExcelTools.formatLocation(orig, relRow, relCol); - if(orig[0]<0||orig[1]<0)newAddress="#REF!"; - if (secondAddress!=null) { - orig = ExcelTools.getRowColFromString(secondAddress); - relCol= !(secondAddress.startsWith("$")); // handle relative refs - relRow= !(secondAddress.length()>0 && secondAddress.substring(1).indexOf('$')>-1); - if (orig[0]>=0) { - if (relRow) // only move if relative ref - orig[0] += offsets[0]; //row - } - if (orig[1]>=0) { //if not wholerow/wholecol ref - if (relCol)// only move if relative ref - orig[1]+= offsets[1]; //col - } + int[] orig = ExcelTools.getRowColFromString(range); + boolean relCol = !(range.startsWith("$")); // 20100603 KSC: handle relative refs + boolean relRow = !(range.length() > 0 && range.substring(1).indexOf('$') > -1); + if (relRow) // only move if relative ref + orig[0] += offsets[0]; //row + if (relCol)// only move if relative ref + orig[1] += offsets[1]; //col + String newAddress = ExcelTools.formatLocation(orig, relRow, relCol); + if (orig[0] < 0 || orig[1] < 0) newAddress = "#REF!"; + if (secondAddress != null) { + orig = ExcelTools.getRowColFromString(secondAddress); + relCol = !(secondAddress.startsWith("$")); // handle relative refs + relRow = !(secondAddress.length() > 0 && secondAddress.substring(1).indexOf('$') > -1); + if (orig[0] >= 0) { + if (relRow) // only move if relative ref + orig[0] += offsets[0]; //row + } + if (orig[1] >= 0) { //if not wholerow/wholecol ref + if (relCol)// only move if relative ref + orig[1] += offsets[1]; //col + } String newAddress1 = ExcelTools.formatLocation(orig, relRow, relCol); - newAddress= newAddress + ":" + newAddress1; + newAddress = newAddress + ":" + newAddress1; } - if (sh!=null) // TODO: handle refs with multiple sheets - newAddress= sh + "!" + newAddress; - if(! fmh.changeFormulaLocation(celladdys[x], newAddress)){ - Logger.logErr("Could not change Formula Reference: " + celladdys[x] +" to: " + newAddress); + if (sh != null) // TODO: handle refs with multiple sheets + newAddress = sh + "!" + newAddress; + if (!fmh.changeFormulaLocation(celladdys[x], newAddress)) { + Logger.logErr("Could not change Formula Reference: " + celladdys[x] + " to: " + newAddress); } } return; } - - - public String toString() - { - return this.form.getCellAddress() + ":" + this.form.getFormulaString(); - } - - /** - * return truth of "this formula is shared" - * @return boolean - */ - public boolean isSharedFormula() { return this.form.isSharedFormula(); } - - public boolean isArrayFormula() { return form.isArrayFormula(); } - // 20090120 KSC: should be detected auto public void setIsArrayFormula(boolean b) { form.setIsArrayFormula(b); } - - /** - * returns the low-level formula rec for this Formulahandle - * - * - * @return - */ - public Formula getFormulaRec() { - return form; - } - - /** - * Utility method to determine if the calculation works out to an error value. - * - * The excel values that will cause this to be true are - * #VALUE!, #N/A, #REF!, #DIV/0!, #NUM!, #NAME?, #NULL! - - * - * @return - */ - public boolean isErrorValue() { - return (form.calculateFormula() instanceof CalculationException); + + + public String toString() { + return this.form.getCellAddress() + ":" + this.form.getFormulaString(); + } + + /** + * return truth of "this formula is shared" + * + * @return boolean + */ + public boolean isSharedFormula() { + return this.form.isSharedFormula(); + } + + public boolean isArrayFormula() { + return form.isArrayFormula(); + } + // 20090120 KSC: should be detected auto public void setIsArrayFormula(boolean b) { form.setIsArrayFormula(b); } + + /** + * returns the low-level formula rec for this Formulahandle + * + * @return + */ + public Formula getFormulaRec() { + return form; + } + + /** + * Utility method to determine if the calculation works out to an error value. + *

        + * The excel values that will cause this to be true are + * #VALUE!, #N/A, #REF!, #DIV/0!, #NUM!, #NAME?, #NULL! + * + * @return + */ + public boolean isErrorValue() { + return (form.calculateFormula() instanceof CalculationException); + } + + /** + * return the "Calculate Always" setting for this formula + * used for formulas that always need calculating such as TODAY + * + * @return + */ + public boolean getCalcAlways() { + return form.getCalcAlways(); + } + + /** + * set the "Calculate Always setting for this formula + * used for formulas that always need calculating such as TODAY + * + * @param fAlwaysCalc + */ + public void setCalcAlways(boolean fAlwaysCalc) { + form.setCalcAlways(fAlwaysCalc); + } + + /** + * generate the OOXML necessary to describe this formula + * OOXML element + * + * @return + */ + // TODO: Deal with External References ... dataTables + // common possible attributes: + // aca= always calculate array, bx=name to assign formula to, ca=calculate cell, r1=data table cell1, + // t=formula type shared, array, dataTable or normal=default + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + // must have type of formula result + Object val; + try { + val = this.getVal(); + if (val == null) {// means cache was cleared (in all cases???) MUST recalc + if (this.form.getWorkBook().getCalcMode() != WorkBookHandle.CALCULATE_EXPLICIT) { + this.calculate(); + val = this.getVal(); + } else { + val = new CalculationException(CalculationException.VALUE); + } + } else if (val instanceof String && ((String) val).startsWith("#")) + val = new CalculationException(CalculationException.getErrorCode((String) val)); + } catch (Exception e) { + val = new CalculationException(CalculationException.VALUE); + } + if (val == null) { + Logger.logErr("FormulaHandle.getOOXML: unexpected null encountered when calculating formula: " + this.getCellAddress()); } - - /** - * return the "Calculate Always" setting for this formula - * used for formulas that always need calculating such as TODAY - * @return - */ - public boolean getCalcAlways() { - return form.getCalcAlways(); - } - - /** - * set the "Calculate Always setting for this formula - * used for formulas that always need calculating such as TODAY - * @param fAlwaysCalc - */ - public void setCalcAlways(boolean fAlwaysCalc) { - form.setCalcAlways(fAlwaysCalc); - } - - /** - * generate the OOXML necessary to describe this formula - * OOXML element - * @return - */ - // TODO: Deal with External References ... dataTables - // common possible attributes: - // aca= always calculate array, bx=name to assign formula to, ca=calculate cell, r1=data table cell1, - // t=formula type shared, array, dataTable or normal=default - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - // must have type of formula result - Object val; - try { - val= this.getVal(); - if (val==null) {// means cache was cleared (in all cases???) MUST recalc - if(this.form.getWorkBook().getCalcMode() != WorkBookHandle.CALCULATE_EXPLICIT) { - this.calculate(); - val= this.getVal(); - } else { - val = new CalculationException(CalculationException.VALUE ); - } - } else if (val instanceof String && ((String) val).startsWith("#")) - val= new CalculationException(CalculationException.getErrorCode((String)val)); - } catch (Exception e) { - val = new CalculationException(CalculationException.VALUE ); - } - if (val==null) { - Logger.logErr("FormulaHandle.getOOXML: unexpected null encountered when calculating formula: " + this.getCellAddress()); - } - // Handle attributes for special cached values - if (val instanceof String) { - ooxml.append(" t=\"str\""); - val= OOXMLAdapter.stripNonAscii((String) val); // TODO: how can we strip non-ascii? What about Japanese cell text? ans: has to be XML-compliant is all ... - } else if (val instanceof Boolean) { - ooxml.append(" t=\"b\""); - if (((Boolean)val).booleanValue()) val= "1"; - else val= "0"; - } else if (val instanceof Double) { - ooxml.append(" t=\"n\""); - } else if (val instanceof CalculationException) { - ooxml.append(" t=\"e\""); - } - - String fs= "="; - try { - fs= this.getFormulaString(); - } catch (Exception e) { - Logger.logErr("FormulaHandle.getOOXML: error obtaining formula string: " + e.toString()); - } - fs= OOXMLAdapter.stripNonAscii(fs).toString(); // handle non-standard xml chars -- ummm what about Japanese? -- it's all ok - if (!this.isArrayFormula()) { - ooxml.append(">"); // only output value info - } - } - if (this.isSharedFormula()) { - // TODO: FINISH 00XML SHARED FORMULAS - // TODO: need si= shared formula index; when referencing (after shared formula is defined) don't need to include "fs" just the si= **** - try { - // 20091022 KSC: Shared Formulas do not work 2003->2007 - //ooxml.append(" t=\"shared\" ref=\"" + this.getFormulaRec().getSharedFormula().getCellRange() + "\""); - } catch (Exception e) { } - } - if (this.getCalcAlways()) ooxml.append(" ca=\"1\""); - if (fs!=null) // can happen if not a parent array formula - ooxml.append(">" + fs + ""); - ooxml.append("" + val + ""); - return ooxml.toString(); - } + // Handle attributes for special cached values + if (val instanceof String) { + ooxml.append(" t=\"str\""); + val = OOXMLAdapter.stripNonAscii((String) val); // TODO: how can we strip non-ascii? What about Japanese cell text? ans: has to be XML-compliant is all ... + } else if (val instanceof Boolean) { + ooxml.append(" t=\"b\""); + if (((Boolean) val).booleanValue()) val = "1"; + else val = "0"; + } else if (val instanceof Double) { + ooxml.append(" t=\"n\""); + } else if (val instanceof CalculationException) { + ooxml.append(" t=\"e\""); + } + + String fs = "="; + try { + fs = this.getFormulaString(); + } catch (Exception e) { + Logger.logErr("FormulaHandle.getOOXML: error obtaining formula string: " + e.toString()); + } + fs = OOXMLAdapter.stripNonAscii(fs).toString(); // handle non-standard xml chars -- ummm what about Japanese? -- it's all ok + if (!this.isArrayFormula()) { + ooxml.append(">"); // only output value info + } + } + if (this.isSharedFormula()) { + // TODO: FINISH 00XML SHARED FORMULAS + // TODO: need si= shared formula index; when referencing (after shared formula is defined) don't need to include "fs" just the si= **** + try { + // 20091022 KSC: Shared Formulas do not work 2003->2007 + //ooxml.append(" t=\"shared\" ref=\"" + this.getFormulaRec().getSharedFormula().getCellRange() + "\""); + } catch (Exception e) { + } + } + if (this.getCalcAlways()) ooxml.append(" ca=\"1\""); + if (fs != null) // can happen if not a parent array formula + ooxml.append(">" + fs + ""); + ooxml.append("" + val + ""); + return ooxml.toString(); + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/GetInfo.java b/src/main/java/io/starter/OpenXLS/GetInfo.java index cf6ee59..cffa8ed 100644 --- a/src/main/java/io/starter/OpenXLS/GetInfo.java +++ b/src/main/java/io/starter/OpenXLS/GetInfo.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,54 +24,58 @@ import java.io.IOException; import java.util.Properties; -/** Prints various bits of information about this build. + +/** + * Prints various bits of information about this build. */ public class GetInfo { - -private static Properties buildProps; - - private static final void loadBuildProps() { - if (null == buildProps) { - buildProps = new Properties(); - try { - buildProps.load( GetInfo.class.getResourceAsStream( - "build.properties" ) ); - } catch (IOException caught) { - // don't care, it'll just be empty - } - } - } - - private static String version; - - /** Return the version number of this installation of OpenXLS. */ - public static final String getVersion() { - if (null == version) { - loadBuildProps(); - version = buildProps.getProperty( "version", "unknown" ); - - if (version.endsWith( "-SNAPSHOT" )) { - version = version.substring( 0, version.length() - 9 ); - - String timestamp = buildProps.getProperty( "timestamp" ); - String commit = buildProps.getProperty( "commit" ); - if ("UNKNOWN".equals( commit )) commit = null; - - if (null != timestamp) - version += "-" + timestamp; - else if (null != commit) - version += "-unknown"; - - if (null != commit) - version += "-" + commit.substring( 0, 10 ); - } - } - - return version; - } - - public static void main(String[] args) { - // Product name and version - io.starter.toolkit.Logger.log ("This is OpenXLS " + getVersion()); - } + + private static Properties buildProps; + + private static final void loadBuildProps() { + if (null == buildProps) { + buildProps = new Properties(); + try { + buildProps.load(GetInfo.class.getResourceAsStream( + "build.properties")); + } catch (IOException caught) { + // don't care, it'll just be empty + } + } + } + + private static String version; + + /** + * Return the version number of this installation of OpenXLS. + */ + public static final String getVersion() { + if (null == version) { + loadBuildProps(); + version = buildProps.getProperty("version", "unknown"); + + if (version.endsWith("-SNAPSHOT")) { + version = version.substring(0, version.length() - 9); + + String timestamp = buildProps.getProperty("timestamp"); + String commit = buildProps.getProperty("commit"); + if ("UNKNOWN".equals(commit)) commit = null; + + if (null != timestamp) + version += "-" + timestamp; + else if (null != commit) + version += "-unknown"; + + if (null != commit) + version += "-" + commit.substring(0, 10); + } + } + + return version; + } + + public static void main(String[] args) { + // Product name and version + io.starter.toolkit.Logger.log("This is OpenXLS " + getVersion()); + } } diff --git a/src/main/java/io/starter/OpenXLS/Handle.java b/src/main/java/io/starter/OpenXLS/Handle.java index 7910bb1..2582bf3 100644 --- a/src/main/java/io/starter/OpenXLS/Handle.java +++ b/src/main/java/io/starter/OpenXLS/Handle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,7 +22,8 @@ */ package io.starter.OpenXLS; -/** A type that represents a component of a document. +/** + * A type that represents a component of a document. * This interface serves to distinguish such types from helper and other * non-content classes. */ diff --git a/src/main/java/io/starter/OpenXLS/ImageHandle.java b/src/main/java/io/starter/OpenXLS/ImageHandle.java index 710878e..eaa4311 100644 --- a/src/main/java/io/starter/OpenXLS/ImageHandle.java +++ b/src/main/java/io/starter/OpenXLS/ImageHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,303 +22,307 @@ */ package io.starter.OpenXLS; -import java.io.*; - -import io.starter.formats.XLS.*; -import io.starter.toolkit.*; - -import java.util.*; -import java.awt.image.*; -import javax.imageio.*; -import javax.imageio.stream.*; - +import io.starter.formats.OOXML.SpPr; +import io.starter.formats.OOXML.TwoCellAnchor; +import io.starter.formats.XLS.Boundsheet; +import io.starter.formats.XLS.MSODrawing; +import io.starter.formats.XLS.MSODrawingConstants; +import io.starter.toolkit.Logger; import org.json.JSONException; import org.json.JSONObject; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.Iterator; + //OOXML-specific structures -import io.starter.formats.OOXML.*; - - -/** The ImageHandle provides access to an Image embedded in a spreadsheet.
        -
        - Use the ImageHandle to work with images in spreadsheet.
        -

        - With an ImageHandle you can: -

        -

        - insert images into your spreadsheet - set the position of the image - set the width and height of the image - write spreadsheet image files to any outputstream - -
        -
        -
        - - @see io.starter.OpenXLS.WorkBookHandle - @see io.starter.OpenXLS.WorkSheetHandle -*/ -public class ImageHandle implements Serializable{ - /** - * returns width divided by height for the aspect ratio - * - * - * - * @return the aspect ratio for the image - */ - public double getAspectRatio(){ + + +/** + * The ImageHandle provides access to an Image embedded in a spreadsheet.
        + *
        + * Use the ImageHandle to work with images in spreadsheet.
        + *

        + * With an ImageHandle you can: + *

        + *
        + * insert images into your spreadsheet + * set the position of the image + * set the width and height of the image + * write spreadsheet image files to any outputstream + * + *
        + *
        + *
        + * + * @see io.starter.OpenXLS.WorkBookHandle + * @see io.starter.OpenXLS.WorkSheetHandle + */ +public class ImageHandle implements Serializable { + /** + * returns width divided by height for the aspect ratio + * + * @return the aspect ratio for the image + */ + public double getAspectRatio() { double height, width; - double aspectRatio = 0; + double aspectRatio = 0; height = getHeight() / 122.27; //(convert to in) width = getWidth() / 57.06; //(convert to in) aspectRatio = width / height; return aspectRatio; } - - /** - * - * - */ - private static final long serialVersionUID = 3177017738178634238L; + + /** + * + */ + private static final long serialVersionUID = 3177017738178634238L; private byte[] imageBytes; private Boundsheet mysheet; - // coordinates - public static final int X= 0; - public static final int Y= 1; - public static final int WIDTH= 2; - public static final int HEIGHT= 3; - - private String imageName = " ", shapeName= ""; - private short height , width; - private short x, y; + // coordinates + public static final int X = 0; + public static final int Y = 1; + public static final int WIDTH = 2; + public static final int HEIGHT = 3; + + private String imageName = " ", shapeName = ""; + private short height, width; + private short x, y; private int image_type = -1; private int DEBUGLEVEL = 0; // eventually will set! - + // OOXML-specific private SpPr imagesp; - private String editMovement= null; + private String editMovement = null; + + private MSODrawing thisMsodrawing; //20070924 KSC: link to actual msodrawing rec that describes this image + + public void setMsgdrawing(MSODrawing rec) { + thisMsodrawing = rec; + } + + public MSODrawing getMsodrawing() { + return thisMsodrawing; + } - private MSODrawing thisMsodrawing; //20070924 KSC: link to actual msodrawing rec that describes this image - public void setMsgdrawing(MSODrawing rec) { thisMsodrawing= rec; } - public MSODrawing getMsodrawing() { return thisMsodrawing; } - - - /** Returns the image type - * + /** + * Returns the image type + * * @return */ public String getType() { - switch(image_type) { - case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_GIF: - return "gif"; - case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_JPG: - return "jpeg"; - case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_PNG: - return "png"; - case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_EMF: - return "emf"; - default: - return "undefined"; - } - } - - /** Returns the image mime type - - * + switch (image_type) { + case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_GIF: + return "gif"; + case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_JPG: + return "jpeg"; + case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_PNG: + return "png"; + case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_EMF: + return "emf"; + default: + return "undefined"; + } + } + + /** + * Returns the image mime type + * * @return */ public String getMimeType() { - return "image/" + getImageType(); + return "image/" + getImageType(); } - - /** Constructor which takes image file bytes and inserts into - * specific sheet - * + /** + * Constructor which takes image file bytes and inserts into + * specific sheet + * * @param imageBytes * @param sheet */ - public ImageHandle(InputStream imagebytestream, WorkSheetHandle _sheet){ - this(imagebytestream,_sheet.getMysheet()); + public ImageHandle(InputStream imagebytestream, WorkSheetHandle _sheet) { + this(imagebytestream, _sheet.getMysheet()); } /** - * Constructor which takes image file bytes and associates it with the - * specified boundsheet - - * + * Constructor which takes image file bytes and associates it with the + * specified boundsheet + * * @param imagebytestream * @param bs */ public ImageHandle(InputStream imagebytestream, Boundsheet bs) { - mysheet = bs; - try{ - imageBytes = new byte[imagebytestream.available()]; - imagebytestream.read(imageBytes); - }catch(Exception ex) { - System.err.print("Failed to create new ImageHandle in sheet" + bs.getSheetName() + " from InputStream:" + ex.toString()); - } - initialize(); - } - /** Constructor which takes image file bytes and inserts into - * specific sheet - - * + mysheet = bs; + try { + imageBytes = new byte[imagebytestream.available()]; + imagebytestream.read(imageBytes); + } catch (Exception ex) { + System.err.print("Failed to create new ImageHandle in sheet" + bs.getSheetName() + " from InputStream:" + ex.toString()); + } + initialize(); + } + + /** + * Constructor which takes image file bytes and inserts into + * specific sheet + * * @param imageBytes * @param sheet */ - public ImageHandle(byte[] _imageBytes, Boundsheet sheet){ - mysheet = sheet; - imageBytes = _imageBytes; - - initialize(); + public ImageHandle(byte[] _imageBytes, Boundsheet sheet) { + mysheet = sheet; + imageBytes = _imageBytes; + + initialize(); } - - /** Override equals so that Sheet cannot contain dupes. - * + /** + * Override equals so that Sheet cannot contain dupes. + * * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals(Object another) { - if(another.toString().equals(this.toString())) - return true; - return false; + return another.toString().equals(this.toString()); } - + private void initialize() { - // after instantiating the image you should have - // access to the width and height bounds + // after instantiating the image you should have + // access to the width and height bounds String imageFormat = ""; - try{ - ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes); - - imageFormat = getImageFormat(bis); - if (imageFormat==null) { - // 20080128 KSC: Occurs when image format= EMF ????? We cannot interpret, will most likely crash file - if(DEBUGLEVEL>WorkBook.DEBUG_LOW) - Logger.logErr("ImageHandle.initialize: Unrecognized Image Format"); - return; - } - if(!(imageFormat.equalsIgnoreCase("jpeg") ||imageFormat.equalsIgnoreCase("png"))){ - bis.reset(); - imageBytes = convertData(bis); - image_type = MSODrawingConstants.IMAGE_TYPE_PNG; - } - else{ - if(imageFormat.equalsIgnoreCase("jpeg"))image_type = MSODrawingConstants.IMAGE_TYPE_JPG; - else this.image_type = MSODrawingConstants.IMAGE_TYPE_PNG; - - } - - try{ - BufferedImage bi = ImageIO.read(new ByteArrayInputStream(imageBytes)); - width = (short) bi.getWidth(); - height = (short) bi.getHeight(); - bi = null; - }catch(IOException ex) { - Logger.logWarn("Java ImageIO could not decode image bytes for " + imageName +":" + ex.toString()); - if(false) { // 20081028 KSC: don't overwrite original image bytes - String imgname = "failed_image_read_"+this.imageName+"."+imageFormat; - FileOutputStream outimg = new FileOutputStream(imgname); - outimg.write(imageBytes); - outimg.flush(); - outimg.close(); - - } - } - // 20070924 KSC: bounds[2] = width; - // "" bounds[3] = height; - this.imageName = "UnnamedImage"; - }catch(Exception e){ - Logger.logWarn("Problem creating ImageHandle:" + e.toString() + " Please see BugTrack article: http://extentech.com/uimodules/docs/docs_detail.jsp?meme_id=1431&showall=true"); - if(false) { // debug image parse probs - String imgname = "failed_image_read_"+this.imageName+"."+imageFormat; - try{ - FileOutputStream outimg = new FileOutputStream(imgname); - outimg.write(imageBytes); - outimg.flush(); - outimg.close(); - }catch(Exception ex) { - ; - } - } - } + try { + ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes); + + imageFormat = getImageFormat(bis); + if (imageFormat == null) { + // 20080128 KSC: Occurs when image format= EMF ????? We cannot interpret, will most likely crash file + if (DEBUGLEVEL > WorkBook.DEBUG_LOW) + Logger.logErr("ImageHandle.initialize: Unrecognized Image Format"); + return; + } + if (!(imageFormat.equalsIgnoreCase("jpeg") || imageFormat.equalsIgnoreCase("png"))) { + bis.reset(); + imageBytes = convertData(bis); + image_type = MSODrawingConstants.IMAGE_TYPE_PNG; + } else { + if (imageFormat.equalsIgnoreCase("jpeg")) image_type = MSODrawingConstants.IMAGE_TYPE_JPG; + else this.image_type = MSODrawingConstants.IMAGE_TYPE_PNG; + + } + + try { + BufferedImage bi = ImageIO.read(new ByteArrayInputStream(imageBytes)); + width = (short) bi.getWidth(); + height = (short) bi.getHeight(); + bi = null; + } catch (IOException ex) { + Logger.logWarn("Java ImageIO could not decode image bytes for " + imageName + ":" + ex.toString()); + if (false) { // 20081028 KSC: don't overwrite original image bytes + String imgname = "failed_image_read_" + this.imageName + "." + imageFormat; + FileOutputStream outimg = new FileOutputStream(imgname); + outimg.write(imageBytes); + outimg.flush(); + outimg.close(); + + } + } + // 20070924 KSC: bounds[2] = width; + // "" bounds[3] = height; + this.imageName = "UnnamedImage"; + } catch (Exception e) { + Logger.logWarn("Problem creating ImageHandle:" + e.toString() + " Please see BugTrack article: http://extentech.com/uimodules/docs/docs_detail.jsp?meme_id=1431&showall=true"); + if (false) { // debug image parse probs + String imgname = "failed_image_read_" + this.imageName + "." + imageFormat; + try { + FileOutputStream outimg = new FileOutputStream(imgname); + outimg.write(imageBytes); + outimg.flush(); + outimg.close(); + } catch (Exception ex) { + } + } + } } + /** * update the underlying image record - * */ - public void update() throws Exception{ - if (this.thisMsodrawing==null) - throw new Exception("ImageHandle.Update: Image Not initialzed"); - this.thisMsodrawing.updateRecord(); //this.thisMsodrawing.getSPID()); - this.mysheet.getWorkBook().updateMsodrawingHeaderRec(this.mysheet); + public void update() throws Exception { + if (this.thisMsodrawing == null) + throw new Exception("ImageHandle.Update: Image Not initialzed"); + this.thisMsodrawing.updateRecord(); //this.thisMsodrawing.getSPID()); + this.mysheet.getWorkBook().updateMsodrawingHeaderRec(this.mysheet); } - + /** * returns true if this drawing is active i.e. not deleted *
        Note this is experimental + * * @return */ public boolean isActive() { - if (this.thisMsodrawing!=null) - return this.thisMsodrawing.isActive(); - return true; // default + if (this.thisMsodrawing != null) + return this.thisMsodrawing.isActive(); + return true; // default } - + /** * converts image data into byte array used by - * + *

        * Jan 22, 2010 + * * @param imagebytestream * @return */ - public byte[] convertData(InputStream imagebytestream){ - try{ - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - - BufferedImage bi = ImageIO.read(imagebytestream); - - ImageIO.write(bi,"png",bos); - - return bos.toByteArray(); - } - catch(Exception e){ - Logger.logErr("ImageHandle.convertData: "+e.toString()); - return null; - } - - } - + public byte[] convertData(InputStream imagebytestream) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + BufferedImage bi = ImageIO.read(imagebytestream); + + ImageIO.write(bi, "png", bos); + + return bos.toByteArray(); + } catch (Exception e) { + Logger.logErr("ImageHandle.convertData: " + e.toString()); + return null; + } + + } + /** * returns the format name of the image data - * + *

        * Jan 22, 2010 + * * @param imagebytestream * @return */ - public String getImageFormat(InputStream imagebytestream){ - - try { + public String getImageFormat(InputStream imagebytestream) { + + try { // Create an image input stream on the image ImageInputStream iis = ImageIO.createImageInputStream(imagebytestream); - + // Find all image readers that recognize the image format Iterator iter = ImageIO.getImageReaders(iis); if (!iter.hasNext()) { - + return null; } - + // Use the first reader - ImageReader reader = (ImageReader)iter.next(); - + ImageReader reader = (ImageReader) iter.next(); + // Close stream iis.close(); - + // Return the format name return reader.getFormatName(); } catch (IOException e) { @@ -327,352 +331,400 @@ public String getImageFormat(InputStream imagebytestream){ return null; } - - + + /* - * 20070924 KSC: image bounds are set via column/row # + offsets within the respective cell - * added many position methods that access msodrawing for actual positions - */ + * 20070924 KSC: image bounds are set via column/row # + offsets within the respective cell + * added many position methods that access msodrawing for actual positions + */ /* position methods */ + /** * return the image bounds * images bounds are as follows: - bounds[0]= column # of top left position (0-based) of the shape - bounds[1]= x offset within the top-left column (0-1023) - bounds[2]= row # for top left corner - bounds[3]= y offset within the top-left corner (0-1023) - bounds[4]= column # of the bottom right corner of the shape - bounds[5]= x offset within the cell for the bottom-right corner (0-1023) - bounds[6]= row # for bottom-right corner of the shape - bounds[7]= y offset within the cell for the bottom-right corner (0-1023) + * bounds[0]= column # of top left position (0-based) of the shape + * bounds[1]= x offset within the top-left column (0-1023) + * bounds[2]= row # for top left corner + * bounds[3]= y offset within the top-left corner (0-1023) + * bounds[4]= column # of the bottom right corner of the shape + * bounds[5]= x offset within the cell for the bottom-right corner (0-1023) + * bounds[6]= row # for bottom-right corner of the shape + * bounds[7]= y offset within the cell for the bottom-right corner (0-1023) */ public short[] getBounds() { - return thisMsodrawing.getBounds(); + return thisMsodrawing.getBounds(); } - + // short is not big enough to handle MAXROWS... correct? -jm + /** - * sets the image bounds + * sets the image bounds * images bounds are as follows: - bounds[0]= column # of top left position (0-based) of the shape - bounds[1]= x offset within the top-left column (0-1023) - bounds[2]= row # for top left corner - bounds[3]= y offset within the top-left corner (0-1023) - bounds[4]= column # of the bottom right corner of the shape - bounds[5]= x offset within the cell for the bottom-right corner (0-1023) - bounds[6]= row # for bottom-right corner of the shape - bounds[7]= y offset within the cell for the bottom-right corner (0-1023) + * bounds[0]= column # of top left position (0-based) of the shape + * bounds[1]= x offset within the top-left column (0-1023) + * bounds[2]= row # for top left corner + * bounds[3]= y offset within the top-left corner (0-1023) + * bounds[4]= column # of the bottom right corner of the shape + * bounds[5]= x offset within the cell for the bottom-right corner (0-1023) + * bounds[6]= row # for bottom-right corner of the shape + * bounds[7]= y offset within the cell for the bottom-right corner (0-1023) */ public void setBounds(short[] bounds) { - thisMsodrawing.setBounds(bounds); + thisMsodrawing.setBounds(bounds); } - + /** * return the image bounds in x, y, width, height format in pixels + * * @return */ public short[] getCoords() { - if (thisMsodrawing!=null) - return thisMsodrawing.getCoords(); - else - return new short[] { x, y, width, height }; + if (thisMsodrawing != null) + return thisMsodrawing.getCoords(); + else + return new short[]{x, y, width, height}; } - + /** * set the image x, w, width and height in pixels + * * @param x * @param y * @param w * @param h */ public void setCoords(int x, int y, int w, int h) { - if (thisMsodrawing!=null) - thisMsodrawing.setCoords(new short[] {(short) x, (short) y, (short) w, (short) h}); - else {// save for later - this.x= (short) x; - this.y= (short) y; - this.width= (short) w; - this.height= (short) h; - } + if (thisMsodrawing != null) + thisMsodrawing.setCoords(new short[]{(short) x, (short) y, (short) w, (short) h}); + else {// save for later + this.x = (short) x; + this.y = (short) y; + this.width = (short) w; + this.height = (short) h; + } } /** * set the image upper x coordinate in pixels + * * @param x */ - public void setX(int x) { - if (thisMsodrawing!=null) - thisMsodrawing.setX((short) Math.round(x/6.4)); // 20090506 KSC: convert pixels to excel units - else // save for later - this.x= (short) x; + public void setX(int x) { + if (thisMsodrawing != null) + thisMsodrawing.setX((short) Math.round(x / 6.4)); // 20090506 KSC: convert pixels to excel units + else // save for later + this.x = (short) x; } /** * set the image upper y coordinate in pixels + * * @param y */ - public void setY(int y) { - if (thisMsodrawing!=null) - thisMsodrawing.setY((short) Math.round(y*0.60)); //convert pixels to points - else // save for later - this.y= (short) y; + public void setY(int y) { + if (thisMsodrawing != null) + thisMsodrawing.setY((short) Math.round(y * 0.60)); //convert pixels to points + else // save for later + this.y = (short) y; } + /** * return the topmost row of the image + * * @return */ - public int getRow(){ return thisMsodrawing.getRow0(); } + public int getRow() { + return thisMsodrawing.getRow0(); + } + /** - * return the lower row of the image + * return the lower row of the image + * * @return */ - public int getRow1() { return thisMsodrawing.getRow1(); } + public int getRow1() { + return thisMsodrawing.getRow1(); + } + /** - * set the topmost row of the image + * set the topmost row of the image + * * @param row */ - public void setRow(int row) { thisMsodrawing.setRow(row); } + public void setRow(int row) { + thisMsodrawing.setRow(row); + } + /** - * set the lower row of the image + * set the lower row of the image + * * @param row */ - public void setRow1(int row) { thisMsodrawing.setRow1(row); } - + public void setRow1(int row) { + thisMsodrawing.setRow1(row); + } + /** - * return the leftmost column of the image + * return the leftmost column of the image */ - public int getCol() { return thisMsodrawing.getCol(); } - - /** + public int getCol() { + return thisMsodrawing.getCol(); + } + + /** * return the rightmost column of the image */ - public int getCol1() { return thisMsodrawing.getCol1(); } - - public int getOriginalWidth() { return thisMsodrawing.getOriginalWidth(); } + public int getCol1() { + return thisMsodrawing.getCol1(); + } + + public int getOriginalWidth() { + return thisMsodrawing.getOriginalWidth(); + } + /** * return the width of the image in pixels + * * @return */ - public short getWidth() { return (short) Math.round(thisMsodrawing.getWidth()*6.4); // 20090506 KSC: Convert excel units to pixels + public short getWidth() { + return (short) Math.round(thisMsodrawing.getWidth() * 6.4); // 20090506 KSC: Convert excel units to pixels } + /** * set the width of the image in pixels + * * @param w */ - public void setWidth(int w) { - thisMsodrawing.setWidth((short) Math.round(w/6.4)); // 20090506 KSC: convert pixels to excel units + public void setWidth(int w) { + thisMsodrawing.setWidth((short) Math.round(w / 6.4)); // 20090506 KSC: convert pixels to excel units } + /** * return the height of the image in pixels + * * @return */ - public short getHeight() { return (short) Math.round(thisMsodrawing.getHeight()/0.60); // 20090506 KSC: Convert points to pixels + public short getHeight() { + return (short) Math.round(thisMsodrawing.getHeight() / 0.60); // 20090506 KSC: Convert points to pixels } + /** * set the height of the image in pixels + * * @param h */ - public void setHeight(int h) { - thisMsodrawing.setHeight((short) Math.round(h*0.60)); // 20090506 KSC: convert pixels to points + public void setHeight(int h) { + thisMsodrawing.setHeight((short) Math.round(h * 0.60)); // 20090506 KSC: convert pixels to points } + /** * return the upper x coordinate of the image in pixels + * * @return */ - public short getX() { return (short) Math.round(thisMsodrawing.getX()*6.4); // 20090506 KSC: convert excel units to pixels + public short getX() { + return (short) Math.round(thisMsodrawing.getX() * 6.4); // 20090506 KSC: convert excel units to pixels } + /** * return the upper y coordinate of the image in pixels + * * @return */ - public short getY() { return (short) Math.round(thisMsodrawing.getY()*0.60); // 20090506 KSC: convert points to pixels - } - /** get the position of the top left corner of the image in the sheet - */ - - public short[] getRowAndOffset(){ - return thisMsodrawing.getRowAndOffset(); - } - - /** get the position of the top left corner of the image in the sheet - */ - public short[] getColAndOffset(){ - return thisMsodrawing.getColAndOffset(); - } - - /** Internal method that converts the image bounds appropriate for saving + public short getY() { + return (short) Math.round(thisMsodrawing.getY() * 0.60); // 20090506 KSC: convert points to pixels + } + + /** + * get the position of the top left corner of the image in the sheet + */ + + public short[] getRowAndOffset() { + return thisMsodrawing.getRowAndOffset(); + } + + /** + * get the position of the top left corner of the image in the sheet + */ + public short[] getColAndOffset() { + return thisMsodrawing.getColAndOffset(); + } + + /** + * Internal method that converts the image bounds appropriate for saving * by Excel in the MsofbtOPT record. - */ - public int getImageIndex(){ - return thisMsodrawing.getImageIndex(); + */ + public int getImageIndex() { + return thisMsodrawing.getImageIndex(); } - - /** write the image bytes to an outputstream such as a file - * + /** + * write the image bytes to an outputstream such as a file + * * @param out */ //Modified by Bikash - public void write(OutputStream out) throws IOException{ - // TODO: write the image bytes out - out.write(this.imageBytes); + public void write(OutputStream out) throws IOException { + // TODO: write the image bytes out + out.write(this.imageBytes); } - - /** Need to figure out a unique identifier for these -- if there - * is one in the BIFF8 information, that would be best - * + /** + * Need to figure out a unique identifier for these -- if there + * is one in the BIFF8 information, that would be best + * * @see java.lang.Object#toString() */ - public String toString(){ - return getName(); // 20071026 KSC: imageName; - } - - - /** removes this Image from the WorkBook. - * - * @return whether the removal was a success - */ - public boolean remove() { - this.mysheet.removeImage(this); - // blow out the image rec - this.thisMsodrawing.remove(true); - return true; - } - - /** - * - * @return Returns the image type. - */ - public int getImageType() { - return image_type; - } - - /** - * - * @param image_type The image type to set. - */ - public void setImageType(int type) { - image_type = type; - } - - /** returns the WorkSheet this image is contained in - * - * - * @return Returns the sheet. - */ - public Boundsheet getSheet() { - return mysheet; - } - - /** - * - * @return Returns the name (either explicitly set name or image name). - */ - public String getName() { // 20071025 KSC: use Explicitly set name = shape name, if present. Otherwise, use imageName - if (!shapeName.equals("")) - return shapeName; - return this.imageName; - } - - /** - * return the imageName (= original file name, I believe) - * @return - */ - public String getImageName() { - return this.imageName; - } - /** - * returns the explicitly set shape name (set by entering text in the named range field in Excel) - * @return the shape name - */ - public String getShapeName() { - return shapeName; - } - /** - * - * @param name The name to set. - */ - public void setName(String name) { - this.imageName = name; - // NOTE: updating mso code causes corruption in certain Infoteria files- must look at - if (this.thisMsodrawing!=null && !this.thisMsodrawing.getName().equals(name)) { - this.thisMsodrawing.setImageName(name); // update is done in setImageName - this.thisMsodrawing.getWorkBook().updateMsodrawingHeaderRec(this.getSheet()); // must update header if change mso's - Claritas image insert regression bug (testImages.testInsertImageCorruption) - } - } - - /** - * allow setting of image name as seen in the Named Range Box - * @param name - */ - public void setShapeName(String name){ - if (name!=null) - shapeName= name; - else - shapeName= ""; - // only set name/update record if names have changed - if (this.thisMsodrawing!=null && !shapeName.equals(this.thisMsodrawing.getShapeName())) { - this.thisMsodrawing.setShapeName(shapeName); // update is done in setShapeName - this.thisMsodrawing.getWorkBook().updateMsodrawingHeaderRec(this.getSheet()); // 20100202 KSC: must update header if change mso's - Claritas image insert regression bug (testImages.testInsertImageCorruption) - } - } - - /** - * - * @return Returns the imageBytes. - */ - public byte[] getImageBytes() { - return imageBytes; - } - - /** sets the underlying image bytes to the bytes from a new image - * - * essentially swapping the image bytes for another, while retaining - * all other image properties (borders, etc.) - * - * - * - * @param imageBytes The imageBytes to set. - */ - public void setImageBytes(byte[] imageBytes) { - this.imageBytes = imageBytes; - mysheet.getWorkBook().getMSODrawingGroup().setImageBytes(this.imageBytes, mysheet, this.thisMsodrawing, this.getName()); - } - + public String toString() { + return getName(); // 20071026 KSC: imageName; + } + + + /** + * removes this Image from the WorkBook. + * + * @return whether the removal was a success + */ + public boolean remove() { + this.mysheet.removeImage(this); + // blow out the image rec + this.thisMsodrawing.remove(true); + return true; + } + /** - * Get a JSON representation of the format + * @return Returns the image type. + */ + public int getImageType() { + return image_type; + } + + /** + * @param image_type The image type to set. + */ + public void setImageType(int type) { + image_type = type; + } + + /** + * returns the WorkSheet this image is contained in + * + * @return Returns the sheet. + */ + public Boundsheet getSheet() { + return mysheet; + } + + /** + * @return Returns the name (either explicitly set name or image name). + */ + public String getName() { // 20071025 KSC: use Explicitly set name = shape name, if present. Otherwise, use imageName + if (!shapeName.equals("")) + return shapeName; + return this.imageName; + } + + /** + * return the imageName (= original file name, I believe) + * + * @return + */ + public String getImageName() { + return this.imageName; + } + + /** + * returns the explicitly set shape name (set by entering text in the named range field in Excel) + * + * @return the shape name + */ + public String getShapeName() { + return shapeName; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.imageName = name; + // NOTE: updating mso code causes corruption in certain Infoteria files- must look at + if (this.thisMsodrawing != null && !this.thisMsodrawing.getName().equals(name)) { + this.thisMsodrawing.setImageName(name); // update is done in setImageName + this.thisMsodrawing.getWorkBook().updateMsodrawingHeaderRec(this.getSheet()); // must update header if change mso's - Claritas image insert regression bug (testImages.testInsertImageCorruption) + } + } - * + /** + * allow setting of image name as seen in the Named Range Box + * + * @param name + */ + public void setShapeName(String name) { + if (name != null) + shapeName = name; + else + shapeName = ""; + // only set name/update record if names have changed + if (this.thisMsodrawing != null && !shapeName.equals(this.thisMsodrawing.getShapeName())) { + this.thisMsodrawing.setShapeName(shapeName); // update is done in setShapeName + this.thisMsodrawing.getWorkBook().updateMsodrawingHeaderRec(this.getSheet()); // 20100202 KSC: must update header if change mso's - Claritas image insert regression bug (testImages.testInsertImageCorruption) + } + } + + /** + * @return Returns the imageBytes. + */ + public byte[] getImageBytes() { + return imageBytes; + } + + /** + * sets the underlying image bytes to the bytes from a new image + *

        + * essentially swapping the image bytes for another, while retaining + * all other image properties (borders, etc.) + * + * @param imageBytes The imageBytes to set. + */ + public void setImageBytes(byte[] imageBytes) { + this.imageBytes = imageBytes; + mysheet.getWorkBook().getMSODrawingGroup().setImageBytes(this.imageBytes, mysheet, this.thisMsodrawing, this.getName()); + } + + /** + * Get a JSON representation of the format + * * @param cr * @return */ public JSONObject getJSON() { - JSONObject ch= new JSONObject(); - try { - ch.put("name", this.getImageName()); - short[] coords= this.getCoords(); - - // short[] coords = { x, y, width, height }; - - ch.put("x", coords[ImageHandle.X]); - ch.put("y", coords[ImageHandle.Y]); - - ch.put("width", coords[ImageHandle.WIDTH]); - // ch.put("width", width); // for some reason COORDS wrong for width - - ch.put("height", coords[ImageHandle.HEIGHT]); - ch.put("type", this.getType()); - - }catch(JSONException e) { + JSONObject ch = new JSONObject(); + try { + ch.put("name", this.getImageName()); + short[] coords = this.getCoords(); + + // short[] coords = { x, y, width, height }; + + ch.put("x", coords[ImageHandle.X]); + ch.put("y", coords[ImageHandle.Y]); + + ch.put("width", coords[ImageHandle.WIDTH]); + // ch.put("width", width); // for some reason COORDS wrong for width + + ch.put("height", coords[ImageHandle.HEIGHT]); + ch.put("type", this.getType()); + + } catch (JSONException e) { Logger.logErr("Error getting imageHandle JSON: " + e); - } + } return ch; } - + /** * set ImageHandle position based on another - * @param im source ImageHandle + * + * @param im source ImageHandle * @return */ public void position(ImageHandle im) { @@ -683,77 +735,88 @@ public void position(ImageHandle im) { coords[0]= origcoords[0]; coords[1]= origcoords[1]; this.setCoords(coords[0], coords[1], coords[2], coords[3]); - */ - /* other way, set with all original coordinates */ + */ + /* other way, set with all original coordinates */ this.setBounds(im.getBounds()); } /** * return the XML representation of this image - * @return String - */ + * + * @return String + */ public String getXML(int rId) { - StringBuffer sb= new StringBuffer(); - short[] bounds= this.getBounds(); - final int EMU= 1270; // 1 pt= 1270 EMUs -- for consistency with OOXML - - sb.append(""); sb.append("\r\n"); - // top left coords - sb.append(""); - sb.append("" + bounds[0] + ""); - sb.append("" + bounds[1]*EMU + ""); - sb.append("" + bounds[2] + ""); - sb.append("" + bounds[3]*EMU + ""); - sb.append(""); sb.append("\r\n"); - // bottom right coords - sb.append(""); - sb.append("" + bounds[4] + ""); - sb.append("" + bounds[5]*EMU + ""); - sb.append("" + bounds[6] + ""); - sb.append("" + bounds[7]*EMU + ""); - sb.append(""); sb.append("\r\n"); - // Picture details - req. child elements= nvPicPr (non-visual picture properties), blipFill (links to image), spPr (shape properties) - sb.append(""); sb.append("\r\n"); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); sb.append("\r\n"); - // Picture relationship Id and relationship to the package - sb.append(""); - sb.append(""); - // //If the picture is cropped, these details are stored in the element - sb.append(""); - sb.append(""); sb.append("\r\n"); - // shape properties - sb.append(""/* bwMode=\"auto\">"*/); - sb.append(""); - int x=this.getX()*EMU, y=this.getY()*EMU, cx= this.getWidth()*EMU, cy= this.getHeight()*EMU; - sb.append(""); // offsets= location - sb.append(""); // extents= size of bounding box enclosing pic in EMUs - sb.append(""); - sb.append(""); // preset geometry, appears necessary for images ... - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); sb.append("\r\n"); - sb.append(""); sb.append("\r\n"); - sb.append(""); - return sb.toString(); + StringBuffer sb = new StringBuffer(); + short[] bounds = this.getBounds(); + final int EMU = 1270; // 1 pt= 1270 EMUs -- for consistency with OOXML + + sb.append(""); + sb.append("\r\n"); + // top left coords + sb.append(""); + sb.append("" + bounds[0] + ""); + sb.append("" + bounds[1] * EMU + ""); + sb.append("" + bounds[2] + ""); + sb.append("" + bounds[3] * EMU + ""); + sb.append(""); + sb.append("\r\n"); + // bottom right coords + sb.append(""); + sb.append("" + bounds[4] + ""); + sb.append("" + bounds[5] * EMU + ""); + sb.append("" + bounds[6] + ""); + sb.append("" + bounds[7] * EMU + ""); + sb.append(""); + sb.append("\r\n"); + // Picture details - req. child elements= nvPicPr (non-visual picture properties), blipFill (links to image), spPr (shape properties) + sb.append(""); + sb.append("\r\n"); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("\r\n"); + // Picture relationship Id and relationship to the package + sb.append(""); + sb.append(""); + // //If the picture is cropped, these details are stored in the element + sb.append(""); + sb.append(""); + sb.append("\r\n"); + // shape properties + sb.append(""/* bwMode=\"auto\">"*/); + sb.append(""); + int x = this.getX() * EMU, y = this.getY() * EMU, cx = this.getWidth() * EMU, cy = this.getHeight() * EMU; + sb.append(""); // offsets= location + sb.append(""); // extents= size of bounding box enclosing pic in EMUs + sb.append(""); + sb.append(""); // preset geometry, appears necessary for images ... + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("\r\n"); + sb.append(""); + sb.append("\r\n"); + sb.append(""); + return sb.toString(); } + /** * return the (00)XML (or DrawingML) representation of this image - * @param rId relationship id for image file - * @return String - */ + * + * @param rId relationship id for image file + * @return String + */ public String getOOXML(int rId) { - TwoCellAnchor t= new TwoCellAnchor(this.editMovement); + TwoCellAnchor t = new TwoCellAnchor(this.editMovement); t.setAsImage(rId, this.getImageName(), this.getShapeName(), this.getMsodrawing().getSPID(), this.getSpPr()); - t.setBounds(TwoCellAnchor.convertBoundsFromBIFF8(this.getSheet(), this.getBounds())); // adjust BIFF8 bounds to OOXML units + t.setBounds(TwoCellAnchor.convertBoundsFromBIFF8(this.getSheet(), this.getBounds())); // adjust BIFF8 bounds to OOXML units return t.getOOXML(); // missing in // @@ -820,29 +883,33 @@ public String getOOXML(int rId) { sb.append(""); sb.append("\r\n"); sb.append(""); return sb.toString(); -*/ +*/ } + /** * return the OOXML shape property for this image + * * @return - */ + */ public SpPr getSpPr() { - return imagesp; + return imagesp; } - + /** * define the OOXML shape property for this image from an existing spPr element */ public void setSpPr(SpPr sp) { - imagesp= sp; + imagesp = sp; // imagesp.setNS("xdr"); } + /** - * specify how to resize or move upon edit OOXML specific + * specify how to resize or move upon edit OOXML specific + * * @param editMovement */ public void setEditMovement(String editMovement) { - this.editMovement= editMovement; + this.editMovement = editMovement; } diff --git a/src/main/java/io/starter/OpenXLS/JSONConstants.java b/src/main/java/io/starter/OpenXLS/JSONConstants.java index e9cf994..4826078 100644 --- a/src/main/java/io/starter/OpenXLS/JSONConstants.java +++ b/src/main/java/io/starter/OpenXLS/JSONConstants.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,38 +22,40 @@ */ package io.starter.OpenXLS; -/** Constants used in the generation of JSON output. */ +/** + * Constants used in the generation of JSON output. + */ interface JSONConstants { - public static final String JSON_CELL_VALUE = "v"; - public static final String JSON_CELL_FORMATTED_VALUE = "fv"; - public static final String JSON_CELL_FORMULA = "fm"; - public static final String JSON_CELL = "Cell"; - public static final String JSON_CELLS = "cs"; - public static final String JSON_DATETIME = "DateTime"; - public static final String JSON_DATEVALUE = "DateValue"; - public static final String JSON_DOUBLE = "Double"; - public static final String JSON_RANGE = "Range"; - public static final String JSON_DATA = "d"; - public static final String JSON_FLOAT = "Float"; - public static final String JSON_INTEGER = "Integer"; - public static final String JSON_LOCATION = "loc"; - public static final String JSON_ROW = "Row"; - public static final String JSON_ROW_BORDER_TOP = "BdrT"; - public static final String JSON_ROW_BORDER_BOTTOM = "BdrB"; - public static final String JSON_HEIGHT = "h"; - public static final String JSON_STRING = "String"; - public static final String JSON_STYLEID = "sid"; - public static final String JSON_TYPE = "t"; - public static final String JSON_FORMULA_HIDDEN = "fhd"; - public static final String JSON_LOCKED = "lck"; - public static final String JSON_HIDDEN = "Hidden"; - public static final String JSON_VALIDATION_MESSAGE = "vm"; - public static final String JSON_MERGEACROSS = "MergeAcross"; - public static final String JSON_MERGEDOWN = "MergeDown"; - public static final String JSON_MERGEPARENT = "MergeParent"; - public static final String JSON_MERGECHILD = "MergeChild"; - public static final String JSON_HREF = "HRef"; - public static final String JSON_WORD_WRAP = "wrap"; - public static final String JSON_RED_FORMAT = "negRed"; - public static final String JSON_TEXT_ALIGN = "txtAlign"; + String JSON_CELL_VALUE = "v"; + String JSON_CELL_FORMATTED_VALUE = "fv"; + String JSON_CELL_FORMULA = "fm"; + String JSON_CELL = "Cell"; + String JSON_CELLS = "cs"; + String JSON_DATETIME = "DateTime"; + String JSON_DATEVALUE = "DateValue"; + String JSON_DOUBLE = "Double"; + String JSON_RANGE = "Range"; + String JSON_DATA = "d"; + String JSON_FLOAT = "Float"; + String JSON_INTEGER = "Integer"; + String JSON_LOCATION = "loc"; + String JSON_ROW = "Row"; + String JSON_ROW_BORDER_TOP = "BdrT"; + String JSON_ROW_BORDER_BOTTOM = "BdrB"; + String JSON_HEIGHT = "h"; + String JSON_STRING = "String"; + String JSON_STYLEID = "sid"; + String JSON_TYPE = "t"; + String JSON_FORMULA_HIDDEN = "fhd"; + String JSON_LOCKED = "lck"; + String JSON_HIDDEN = "Hidden"; + String JSON_VALIDATION_MESSAGE = "vm"; + String JSON_MERGEACROSS = "MergeAcross"; + String JSON_MERGEDOWN = "MergeDown"; + String JSON_MERGEPARENT = "MergeParent"; + String JSON_MERGECHILD = "MergeChild"; + String JSON_HREF = "HRef"; + String JSON_WORD_WRAP = "wrap"; + String JSON_RED_FORMAT = "negRed"; + String JSON_TEXT_ALIGN = "txtAlign"; } diff --git a/src/main/java/io/starter/OpenXLS/NameHandle.java b/src/main/java/io/starter/OpenXLS/NameHandle.java index 3819d6f..63d3ed8 100644 --- a/src/main/java/io/starter/OpenXLS/NameHandle.java +++ b/src/main/java/io/starter/OpenXLS/NameHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,34 +22,35 @@ */ package io.starter.OpenXLS; -import static io.starter.OpenXLS.JSONConstants.*; - -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.formulas.Ptg; import io.starter.formats.XLS.*; +import io.starter.formats.XLS.formulas.Ptg; import io.starter.toolkit.CompatibleVector; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; -import org.json.*; - -/** The NameHandle provides access to a Named Range and its Cells.
        -
        - Use the NameHandle to work with individual Named Ranges in an XLS file.
        -

        - With a NameHandle you can: -

        -

        - get a handle to the Cells in a Name
        - set the default formatting for a Name -
        -
        -
        - - @see WorkBookHandle - @see WorkSheetHandle - @see FormulaHandle -*/ +import org.json.JSONArray; +import org.json.JSONObject; + +import static io.starter.OpenXLS.JSONConstants.JSON_CELL; +import static io.starter.OpenXLS.JSONConstants.JSON_CELLS; + +/** + * The NameHandle provides access to a Named Range and its Cells.
        + *
        + * Use the NameHandle to work with individual Named Ranges in an XLS file.
        + *

        + * With a NameHandle you can: + *

        + *
        + * get a handle to the Cells in a Name
        + * set the default formatting for a Name + *
        + *
        + *
        + * + * @see WorkBookHandle + * @see WorkSheetHandle + * @see FormulaHandle + */ public class NameHandle { private Name myName; @@ -57,677 +58,690 @@ public class NameHandle { private int DEBUGLEVEL = -1; private boolean createblanks = false; private CellRange initialRange = null; - - /** Creates a new Named Range from a CellRange - * - * @param Name of the Range, the CellRange referenced by the Name - */ - public NameHandle(String namestr, CellRange cr){ - mybook = cr.getWorkBook(); - initialRange = cr; // cache - myName = new Name(mybook.getWorkBook(),namestr); - this.setName(namestr); - this.setLocation(cr.toString()); - } - + /** - * Create a NameHandle from an internal Name record + * Creates a new Named Range from a CellRange + * + * @param Name of the Range, the CellRange referenced by the Name + */ + public NameHandle(String namestr, CellRange cr) { + mybook = cr.getWorkBook(); + initialRange = cr; // cache + myName = new Name(mybook.getWorkBook(), namestr); + this.setName(namestr); + this.setLocation(cr.toString()); + } - * + /** + * Create a NameHandle from an internal Name record + * * @param c * @param myb */ - protected NameHandle(Name c, WorkBookHandle myb){ + protected NameHandle(Name c, WorkBookHandle myb) { myName = c; mybook = myb; } - + /** * Returns a handle to the object (either workbook or sheet) that is scoped * to the name record - * + *

        * Default scope is a WorkBookHandle, else the WorkSheetHandle is returned. + * * @return the scope of the name */ public Handle getScope() throws WorkSheetNotFoundException { - int itab = myName.getItab(); - if (itab == 0)return mybook; - WorkSheetHandle sheet = mybook.getWorkSheet(itab-1); - return sheet; + int itab = myName.getItab(); + if (itab == 0) return mybook; + WorkSheetHandle sheet = mybook.getWorkSheet(itab - 1); + return sheet; } - + /** * Set the scope of this name to that of the handle passed in. - * + *

        * This can either be a WorkbookHandle or a WorksheetHandle - * - *note: this will only be functional for the workbook that the name is contained in, - *you cannot change the scope to a different Document with this method. + *

        + * note: this will only be functional for the workbook that the name is contained in, + * you cannot change the scope to a different Document with this method. * * @param scope Workbookhandle or WorksheetHandle */ public void setScope(Handle scope) { int newitab = 0; - if(scope instanceof WorkSheetHandle) { - newitab = (((WorkSheetHandle)scope).getSheetNum()+1); + if (scope instanceof WorkSheetHandle) { + newitab = (((WorkSheetHandle) scope).getSheetNum() + 1); } try { myName.setNewScope(newitab); } catch (WorkSheetNotFoundException e) { - // this really shouldnt happen unless you are passing a scope in from a different workbook + // this really shouldnt happen unless you are passing a scope in from a different workbook Logger.logErr("ERROR: setting new scope on name: " + e); } } + /** * Create a new named range in the workbook * note that this does not set the actual range, just the workbook and name, * follow up with setLocation - - * + * * @param namestr * @param myb * @deprecated */ - public NameHandle(String namestr, WorkBookHandle myb){ + public NameHandle(String namestr, WorkBookHandle myb) { mybook = myb; - myName = new Name(mybook.getWorkBook(),namestr); - this.setName(namestr); + myName = new Name(mybook.getWorkBook(), namestr); + this.setName(namestr); } - + /** * Create a new named range in the workbook - - * - * @param name name that should be used to reference this named range + * + * @param name name that should be used to reference this named range * @param location rangeDef Range of the cells for this named range, in excel syntax including sheet name, ie "Sheet1!A1:D1" - * @param book WorkBookHandle to insert this named range into + * @param book WorkBookHandle to insert this named range into */ - public NameHandle(String name, String location, WorkBookHandle book){ + public NameHandle(String name, String location, WorkBookHandle book) { mybook = book; - myName = new Name(mybook.getWorkBook(),name); - this.setName(name); + myName = new Name(mybook.getWorkBook(), name); + this.setName(name); this.setLocation(location); mybook.getWorkBook().associateDereferencedNames(myName); } - - - /** - * Return an XML representation of this name record - */ - public String getXML() { - StringBuffer retXML = new StringBuffer(); - String nmv = getExpressionString(); - if(nmv.indexOf("=!")>-1) { // add the sheetname - Boundsheet bs = myName.getSheet(); - if(bs!=null) { - nmv = bs.getSheetName() + "!" + nmv; - }else{ // TODO: why no sheet defined for name??? - nmv = StringTool.replaceChars("!", nmv, ""); - } - } - // it's possible that name expression can have quotes and other non-compliant characters - nmv = io.starter.toolkit.StringTool.convertXMLChars(nmv); - - String nmx = getName(); - nmx = io.starter.toolkit.StringTool.convertXMLChars(nmx); - - if(nmx.length()==1) { // deal with snafu character names - if(Character.isLetterOrDigit(nmx.charAt(0))) { - Logger.logInfo("NameHandle getting XML for name: " + nmx); - }else { - nmx = "#NAME"; - } - } - - if (nmv.startsWith("=")) - nmv = nmv.substring(1); - retXML.append(" "); - return retXML.toString(); - } - - - /** - * - * @return String XML rep of all the cells referenced by this range - */ - public String getExpandedXML() { - String nmv = getExpressionString(); - if(nmv.indexOf("=!")>-1) { // add the sheetname - Boundsheet bs = myName.getSheet(); - if(bs!=null) { - nmv = bs.getSheetName() + "!" + nmv; - }else{ // TODO: why no sheet defined for name??? - nmv = StringTool.replaceChars("!", nmv, ""); - } - } - String nmx = getName(); - nmx = io.starter.toolkit.StringTool.convertXMLChars(nmx); - - if(nmx.length()==1) { // deal with snafu character names - if(Character.isLetterOrDigit(nmx.charAt(0))) { - Logger.logInfo("NameHandle getting XML for name: " + nmx); - }else { - nmx = "#NAME"; - } - } - - if (nmv.startsWith("=")) - nmv = nmv.substring(1); - StringBuffer retXML = new StringBuffer(); - retXML.append("\t\n"); - try{ - CellHandle[] cells= getCells(); - - for (int i= 0; i < cells.length; i++) { - retXML.append("\t\t" + cells[i].getXML() + "\n"); - } - }catch(CellNotFoundException ex) { - Logger.logErr("NameHandle.getExpandedXML failed: ",ex); - } - retXML.append("\n"); - - return retXML.toString(); - } - /** sets the default format id for the Name's Cells - - @param int Format Id for all Cells in Name - */ - public void setFormatId(int i){ + + + /** + * Return an XML representation of this name record + */ + public String getXML() { + StringBuffer retXML = new StringBuffer(); + String nmv = getExpressionString(); + if (nmv.indexOf("=!") > -1) { // add the sheetname + Boundsheet bs = myName.getSheet(); + if (bs != null) { + nmv = bs.getSheetName() + "!" + nmv; + } else { // TODO: why no sheet defined for name??? + nmv = StringTool.replaceChars("!", nmv, ""); + } + } + // it's possible that name expression can have quotes and other non-compliant characters + nmv = io.starter.toolkit.StringTool.convertXMLChars(nmv); + + String nmx = getName(); + nmx = io.starter.toolkit.StringTool.convertXMLChars(nmx); + + if (nmx.length() == 1) { // deal with snafu character names + if (Character.isLetterOrDigit(nmx.charAt(0))) { + Logger.logInfo("NameHandle getting XML for name: " + nmx); + } else { + nmx = "#NAME"; + } + } + + if (nmv.startsWith("=")) + nmv = nmv.substring(1); + retXML.append(" "); + return retXML.toString(); + } + + + /** + * @return String XML rep of all the cells referenced by this range + */ + public String getExpandedXML() { + String nmv = getExpressionString(); + if (nmv.indexOf("=!") > -1) { // add the sheetname + Boundsheet bs = myName.getSheet(); + if (bs != null) { + nmv = bs.getSheetName() + "!" + nmv; + } else { // TODO: why no sheet defined for name??? + nmv = StringTool.replaceChars("!", nmv, ""); + } + } + String nmx = getName(); + nmx = io.starter.toolkit.StringTool.convertXMLChars(nmx); + + if (nmx.length() == 1) { // deal with snafu character names + if (Character.isLetterOrDigit(nmx.charAt(0))) { + Logger.logInfo("NameHandle getting XML for name: " + nmx); + } else { + nmx = "#NAME"; + } + } + + if (nmv.startsWith("=")) + nmv = nmv.substring(1); + StringBuffer retXML = new StringBuffer(); + retXML.append("\t\n"); + try { + CellHandle[] cells = getCells(); + + for (int i = 0; i < cells.length; i++) { + retXML.append("\t\t" + cells[i].getXML() + "\n"); + } + } catch (CellNotFoundException ex) { + Logger.logErr("NameHandle.getExpandedXML failed: ", ex); + } + retXML.append("\n"); + + return retXML.toString(); + } + + /** + * sets the default format id for the Name's Cells + * + * @param int Format Id for all Cells in Name + */ + public void setFormatId(int i) { // myName.setXFRecord(i); // TODO: why doesn't this work with myName? try { - CellHandle[] cs = this.getCells(); - for(int t = 0;t1) { - throw new WorkBookException("NamedRange.updateRow Object array failed: too many CellRanges.",WorkBookException.RUNTIME_ERROR); - }else if(rngs.length ==0){ - throw new WorkBookException("NamedRange.updateRow Object array failed: zero CellRanges",WorkBookException.RUNTIME_ERROR); + if (rngs.length > 1) { + throw new WorkBookException("NamedRange.updateRow Object array failed: too many CellRanges.", WorkBookException.RUNTIME_ERROR); + } else if (rngs.length == 0) { + throw new WorkBookException("NamedRange.updateRow Object array failed: zero CellRanges", WorkBookException.RUNTIME_ERROR); } - + // WorkSheetHandle shtx = rngs[0].getSheet(); int x = rngs[0].firstcellrow; - - + + RowHandle[] rx = rngs[0].getRows(); boolean found = false; // iterate, find, update - for(int t=0;t1) { - throw new WorkBookException("NamedRange.updateRow Object array failed: too many CellRanges.",WorkBookException.RUNTIME_ERROR); - }else if(rngs.length ==0){ - throw new WorkBookException("NamedRange.updateRow Object array failed: zero CellRanges",WorkBookException.RUNTIME_ERROR); + if (rngs.length > 1) { + throw new WorkBookException("NamedRange.updateRow Object array failed: too many CellRanges.", WorkBookException.RUNTIME_ERROR); + } else if (rngs.length == 0) { + throw new WorkBookException("NamedRange.updateRow Object array failed: zero CellRanges", WorkBookException.RUNTIME_ERROR); } - + // WorkSheetHandle shtx = rngs[0].getSheet(); int x = rngs[0].firstcellrow; - - + + RowHandle[] rx = rngs[0].getRows(); boolean found = false; // iterate, find, update - for(int t=0;tidxcol){ - if(cx[idxcol].getStringVal().equalsIgnoreCase(objarr[idxcol].toString())){ - found = true; - for(int z=0;z idxcol) { + if (cx[idxcol].getStringVal().equalsIgnoreCase(objarr[idxcol].toString())) { + found = true; + for (int z = 0; z < cx.length; z++) { + cx[z].setVal(objarr[z]); + } + } + } } - - if(!found) - this.addRow(objarr); + + if (!found) + this.addRow(objarr); } - - + + /** - * add a row of cells to this named range - * + * add a row of cells to this named range + * * @param an array of Objects to insert at last rown */ - public void addRow(Object[] objarr) throws Exception{ + public void addRow(Object[] objarr) throws Exception { CellRange[] rngs = getCellRanges(); - if(rngs.length>1) { - throw new WorkBookException("NamedRange.add Object array failed: too many CellRanges.",WorkBookException.RUNTIME_ERROR); - }else if(rngs.length ==0){ - throw new WorkBookException("NamedRange.add Object array failed: zero CellRanges",WorkBookException.RUNTIME_ERROR); + if (rngs.length > 1) { + throw new WorkBookException("NamedRange.add Object array failed: too many CellRanges.", WorkBookException.RUNTIME_ERROR); + } else if (rngs.length == 0) { + throw new WorkBookException("NamedRange.add Object array failed: zero CellRanges", WorkBookException.RUNTIME_ERROR); } - + // WorkSheetHandle shtx = rngs[0].getSheet(); - + int x = rngs[0].firstcellrow; CellHandle[] cxx = shtx.insertRow(x, objarr, true); - + //for(int t=0;t1) { - throw new WorkBookException("NamedRange.addCell failed -- more than one cell range defined in this NameHandle, cannot determine where to add cell.",WorkBookException.RUNTIME_ERROR); + if (rngs.length > 1) { + throw new WorkBookException("NamedRange.addCell failed -- more than one cell range defined in this NameHandle, cannot determine where to add cell.", WorkBookException.RUNTIME_ERROR); } rngs[0].addCellToRange(cx); - setLocation(rngs[0].getRange()); - } - - /** set the referenced cells for the named range - * - * this reference should be in the standard excel syntax including sheetname, - * for instance "Sheet1!A1:Z255" - * - * - */ - public void setLocation(String strloc){ - - // if there is no sheetname, and no sheet, then this is an unusable name and cannot be added - if((strloc.indexOf("!")==-1)&&(strloc.indexOf("=")==-1)){ - if(this.get2DSheetName()==null){ - this.remove(); - throw new IllegalArgumentException("Named Range References must include a Sheet name."); - }else - strloc = this.get2DSheetName() + "!" + strloc; - } - try{myName.setLocation(strloc); - }catch(FunctionNotSupportedException e) { + setLocation(rngs[0].getRange()); + } + + /** + * set the referenced cells for the named range + *

        + * this reference should be in the standard excel syntax including sheetname, + * for instance "Sheet1!A1:Z255" + */ + public void setLocation(String strloc) { + + // if there is no sheetname, and no sheet, then this is an unusable name and cannot be added + if ((strloc.indexOf("!") == -1) && (strloc.indexOf("=") == -1)) { + if (this.get2DSheetName() == null) { + this.remove(); + throw new IllegalArgumentException("Named Range References must include a Sheet name."); + } else + strloc = this.get2DSheetName() + "!" + strloc; + } + try { + myName.setLocation(strloc); + } catch (FunctionNotSupportedException e) { Logger.logWarn("NameHandle.setLocation :" + strloc + " failed: " + e.toString()); } } - - /** get the referenced named cell range as string in standard excel syntax including sheetname, - * + + /** + * get the referenced named cell range as string in standard excel syntax including sheetname, + *

        * for instance "Sheet1!A1:Z255" - * - */ - public String getLocation(){ - try{ + */ + public String getLocation() { + try { String loc = myName.getLocation(); return loc; - }catch(Exception e) { + } catch (Exception e) { Logger.logErr("Error getting named range location" + e); return null; } } - - - - - - /** set the name String for the range definition - - @param String definition name - */ - public void setName(String newname){ - myName.setName(newname); - } - - /** returns the name String for the range definition - - @return String definition name - */ - public String getName(){return myName.getName();} - - public String toString(){return getName();} - - /** returns the name's formula String for the range definition - - @return String the expression string - */ - public String getExpressionString(){ - try{ - return myName.getExpressionString(); - }catch(Exception e){ - if(DEBUGLEVEL > -1)Logger.logWarn("Could not parse expression string for name: " + this.getName()); - } - return "#ERR"; - } - - /** removes this Named Range from the WorkBook. - * - * @return whether the removal was a success - */ - public boolean remove() { - boolean success = false; - try{ - success = this.myName.getWorkBook().removeName(myName); - }catch(Exception e) { - return false; - } - return success; - } - - /** set whether the CellRanges referenced by the NameHandle - * will add blank records to the WorkBook for any missing Cells - * contained within the range. - * - * @param b set whether to create blank records for missing Cells - */ - public void setCreateBlanks(boolean b) { - createblanks= b; - } - - /** gets the array of Cells in this Name - * - * NOTE: this method variation also returns the Sheetname for the name record if not null. - * - * Thus this method is limited to use with 2D ranges. - * - * - * @return Cell[] all Cells defined in this Name - @param fragment whether to enclose result in NameHandle tag - */ - public String getCellRangeXML(boolean fragment){ - StringBuffer sbx = new StringBuffer(); - if(!fragment) - sbx.append(""); - sbx.append(""); - sbx.append(getCellRangeXML()); - sbx.append(""); - return sbx.toString(); - } - - /** gets the array of Cells in this Name - - @return Cell[] all Cells defined in this Name - */ - public String getCellRangeXML(){ - StringBuffer sbx = new StringBuffer(); - try { - CellHandle[] celx = this.getCells(); - RowHandle rowhold = null; - for(int x=0;x"); - sbx.append(celx[x].getXML()); // ignores merged ranges here - }else if(rowhold.getRowNumber() == rx.getRowNumber()){ - sbx.append(celx[x].getXML()); // ignores merged ranges here - }else{ - sbx.append(""); - rowhold = rx; - sbx.append(""); - sbx.append(celx[x].getXML()); // ignores merged ranges here - } - } - sbx.append(""); - }catch(CellNotFoundException ex) { - Logger.logErr("NameHandle.getCellRangeXML failed: ",ex); - } - // sbx.append(""); - return sbx.toString(); - } - - /** gets the array of Cells in this Name - - @return Cell[] all Cells defined in this Name - */ + + + /** + * set the name String for the range definition + * + * @param String definition name + */ + public void setName(String newname) { + myName.setName(newname); + } + + /** + * returns the name String for the range definition + * + * @return String definition name + */ + public String getName() { + return myName.getName(); + } + + public String toString() { + return getName(); + } + + /** + * returns the name's formula String for the range definition + * + * @return String the expression string + */ + public String getExpressionString() { + try { + return myName.getExpressionString(); + } catch (Exception e) { + if (DEBUGLEVEL > -1) Logger.logWarn("Could not parse expression string for name: " + this.getName()); + } + return "#ERR"; + } + + /** + * removes this Named Range from the WorkBook. + * + * @return whether the removal was a success + */ + public boolean remove() { + boolean success = false; + try { + success = this.myName.getWorkBook().removeName(myName); + } catch (Exception e) { + return false; + } + return success; + } + + /** + * set whether the CellRanges referenced by the NameHandle + * will add blank records to the WorkBook for any missing Cells + * contained within the range. + * + * @param b set whether to create blank records for missing Cells + */ + public void setCreateBlanks(boolean b) { + createblanks = b; + } + + /** + * gets the array of Cells in this Name + *

        + * NOTE: this method variation also returns the Sheetname for the name record if not null. + *

        + * Thus this method is limited to use with 2D ranges. + * + * @param fragment whether to enclose result in NameHandle tag + * @return Cell[] all Cells defined in this Name + */ + public String getCellRangeXML(boolean fragment) { + StringBuffer sbx = new StringBuffer(); + if (!fragment) + sbx.append(""); + sbx.append(""); + sbx.append(getCellRangeXML()); + sbx.append(""); + return sbx.toString(); + } + + /** + * gets the array of Cells in this Name + * + * @return Cell[] all Cells defined in this Name + */ + public String getCellRangeXML() { + StringBuffer sbx = new StringBuffer(); + try { + CellHandle[] celx = this.getCells(); + RowHandle rowhold = null; + for (int x = 0; x < celx.length; x++) { + RowHandle rx = celx[x].getRow(); + if (x == 0) { + rowhold = rx; + sbx.append(""); + sbx.append(celx[x].getXML()); // ignores merged ranges here + } else if (rowhold.getRowNumber() == rx.getRowNumber()) { + sbx.append(celx[x].getXML()); // ignores merged ranges here + } else { + sbx.append(""); + rowhold = rx; + sbx.append(""); + sbx.append(celx[x].getXML()); // ignores merged ranges here + } + } + sbx.append(""); + } catch (CellNotFoundException ex) { + Logger.logErr("NameHandle.getCellRangeXML failed: ", ex); + } + // sbx.append(""); + return sbx.toString(); + } + + /** + * gets the array of Cells in this Name + * + * @return Cell[] all Cells defined in this Name + */ public CellHandle[] getCells() - throws CellNotFoundException{ - if(false) + throws CellNotFoundException { + if (false) return null; // first get the ptgArea3d from the parsed expression - try{ + try { CompatibleVector cellhandles = new CompatibleVector(); CellRange[] rngz = this.getCellRanges(); - if (rngz!=null) { - for(int t=0;t=0;b--)cellhandles.add(cells[b]); - for(int b =0; b< cells.length;b++) - if(cells[b]!=null) - cellhandles.add(cells[b]); - }catch(Exception ex){ - Logger.logWarn("Could not get cells for range: " + rngz[t]); - } - } + if (rngz != null) { + for (int t = 0; t < rngz.length; t++) { + try { + CellHandle[] cells = rngz[t].getCells(); + // for(int b = (cells.length-1);b>=0;b--)cellhandles.add(cells[b]); + for (int b = 0; b < cells.length; b++) + if (cells[b] != null) + cellhandles.add(cells[b]); + } catch (Exception ex) { + Logger.logWarn("Could not get cells for range: " + rngz[t]); + } + } } else { - return null; + return null; } CellHandle[] ret = new CellHandle[cellhandles.size()]; return (CellHandle[]) cellhandles.toArray(ret); - }catch(Exception e){ - if(e instanceof CellNotFoundException) - throw (CellNotFoundException)e; - throw new CellNotFoundException(e.toString()); + } catch (Exception e) { + if (e instanceof CellNotFoundException) + throw (CellNotFoundException) e; + throw new CellNotFoundException(e.toString()); } } - /** Get an Array of CellRanges, one per referenced WorkSheet. - * - * If this method throws CellNotFoundExceptions, then you are - * addressing a sparsely populated CellRange. - * - * Use 'setCreateBlanks(true)' to populate these Cells and avoid - * this error. - * - * @return - * @throws Exception - */ - public CellRange[] getCellRanges() - throws Exception{ - if(initialRange!=null){ - CellRange[] ret = {initialRange}; - return ret; - } - // 20100217 KSC: try a better way (that can handle 3D refs and complex cell ranges) - String loc= myName.getLocation(); // may contain one or more ranges, separated by ","'s if complex - WorkSheetHandle[] sheets = this.getReferencedSheets(); - // handle commas within quoted sheet names (TestExtenXLSEngine.TestQuotedSheetsWithCommansInNRs) - // NOTE that sheetnames must be properly qualified for the split to work below - String[] nranges= loc.split(",(?=([^'|\"]*'[^'|\"]*'|\")*[^'|\"]*$)"); - // below cannot handle commas embedded within quotes + /** + * Get an Array of CellRanges, one per referenced WorkSheet. + *

        + * If this method throws CellNotFoundExceptions, then you are + * addressing a sparsely populated CellRange. + *

        + * Use 'setCreateBlanks(true)' to populate these Cells and avoid + * this error. + * + * @return + * @throws Exception + */ + public CellRange[] getCellRanges() + throws Exception { + if (initialRange != null) { + CellRange[] ret = {initialRange}; + return ret; + } + // 20100217 KSC: try a better way (that can handle 3D refs and complex cell ranges) + String loc = myName.getLocation(); // may contain one or more ranges, separated by ","'s if complex + WorkSheetHandle[] sheets = this.getReferencedSheets(); + // handle commas within quoted sheet names (TestExtenXLSEngine.TestQuotedSheetsWithCommansInNRs) + // NOTE that sheetnames must be properly qualified for the split to work below + String[] nranges = loc.split(",(?=([^'|\"]*'[^'|\"]*'|\")*[^'|\"]*$)"); + // below cannot handle commas embedded within quotes // String[] nranges= StringTool.splitString(loc, ","); // TODO: can be another delimeter? - CellRange[] ranges = new CellRange[nranges.length]; - for (int i= 0; i < nranges.length; i++) { - String r= nranges[i]; - if (r.indexOf("!")==-1) { // no sheetname - r= sheets[i] + "!" + r; - } - ranges[i] = new CellRange(r,mybook,createblanks); - ranges[i].setParent(this.myName); - } - - return ranges; - } - - /** Get WorkSheetHandles for all of the Boundsheets referenced in - * this NameHandle. - * - * @return an array of WorkSheetHandles referenced in this Name - */ + CellRange[] ranges = new CellRange[nranges.length]; + for (int i = 0; i < nranges.length; i++) { + String r = nranges[i]; + if (r.indexOf("!") == -1) { // no sheetname + r = sheets[i] + "!" + r; + } + ranges[i] = new CellRange(r, mybook, createblanks); + ranges[i].setParent(this.myName); + } + + return ranges; + } + + /** + * Get WorkSheetHandles for all of the Boundsheets referenced in + * this NameHandle. + * + * @return an array of WorkSheetHandles referenced in this Name + */ public WorkSheetHandle[] getReferencedSheets() - throws WorkSheetNotFoundException{ - //first get the ptgArea3d from the parsed expression - Boundsheet[] bs = myName.getBoundSheets(); - if(bs == null) - throw new WorkSheetNotFoundException("Worksheet for Named Range: "+this.toString()+":"+ this.myName.getExpressionString()); - if(bs[0]==null) - throw new WorkSheetNotFoundException("Worksheet for Named Range: "+this.toString()+":" + this.myName.getExpressionString()); - WorkSheetHandle[] ret = new WorkSheetHandle[bs.length]; - for(int x=0;x + * Used to prevent updating of the Cell Reference when + * Cells are moved. + * + * @param location of the Cell Reference to be locked/unlocked + * @param lock status setting + * @return boolean whether the Cell Reference was found and modified + */ + public boolean setLocationLocked(String loc, boolean l) { + int x = Ptg.PTG_LOCATION_POLICY_UNLOCKED; + if (l) x = Ptg.PTG_LOCATION_POLICY_LOCKED; + return myName.setLocationPolicy(loc, x); + } + /** * Return a JSON object representing this name Handle. * name:'nameOfRange' * cellrange:'Sheet1!A1:B1' - - * + * * @return */ public String getJSON() { return getJSON(false); } - - + + /** * Return a JSON object representing this name Handle. * name:'nameOfRange' * cellrange:'Sheet1!A1:B1' * cells:celldata - * - * @return + * * @param whether to return cell data + * @return */ public String getJSON(boolean celldata) { JSONObject theNameHandle = new JSONObject(); try { theNameHandle.put("name", this.getName()); theNameHandle.put("cellrange", myName.getLocation()); - - if(celldata){ - StringBuffer ret = new StringBuffer(); - CellHandle[] cx1 = getCells(); - int p = cx1[0].getRowNum(); - for(int x=0;x + * NOTE: Does not work for 3D ranges + * + * @return the Sheet name if this is a 2d range + */ + public String get2DSheetName() { + try { + return this.myName.getBoundSheets()[0].getSheetName(); + } catch (Exception e) { + try { + return this.myName.getSheet().getSheetName(); + } catch (Exception ex) { + return null; + } + } + } + } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/PivotTableHandle.java b/src/main/java/io/starter/OpenXLS/PivotTableHandle.java index ee6bb15..37f4bf1 100644 --- a/src/main/java/io/starter/OpenXLS/PivotTableHandle.java +++ b/src/main/java/io/starter/OpenXLS/PivotTableHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,552 +23,552 @@ package io.starter.OpenXLS; -import org.json.JSONObject; - -import io.starter.formats.XLS.*; +import io.starter.formats.XLS.ColumnNotFoundException; +import io.starter.formats.XLS.SxStreamID; +import io.starter.formats.XLS.Sxview; import io.starter.toolkit.Logger; +import org.json.JSONObject; -/** PivotTable Handle allows for manipulation of PivotTables within a WorkBook. - - @see WorkBookHandle - @see WorkSheetHandle - @see CellHandle +/** + * PivotTable Handle allows for manipulation of PivotTables within a WorkBook. + * + * @see WorkBookHandle + * @see WorkSheetHandle + * @see CellHandle */ /** - * + * */ /** * */ + /** * */ public class PivotTableHandle { - public static int AUTO_FORMAT_Report1 = 1; - public static int AUTO_FORMAT_Report2 = 2; - public static int AUTO_FORMAT_Report3 = 3; - public static int AUTO_FORMAT_Report4 = 4; - public static int AUTO_FORMAT_Report5 = 5; - public static int AUTO_FORMAT_Report6 = 6; - public static int AUTO_FORMAT_Report7 = 7; - public static int AUTO_FORMAT_Report8 = 8; - public static int AUTO_FORMAT_Report9 = 9; - public static int AUTO_FORMAT_Report10 = 10; - public static int AUTO_FORMAT_Table1 = 11; - public static int AUTO_FORMAT_Table2 = 12; - public static int AUTO_FORMAT_Table3 = 13; - public static int AUTO_FORMAT_Table4 = 14; - public static int AUTO_FORMAT_Table5 = 15; - public static int AUTO_FORMAT_Table6 = 16; - public static int AUTO_FORMAT_Table7 = 17; - public static int AUTO_FORMAT_Table8 = 18; - public static int AUTO_FORMAT_Table9 = 19; - public static int AUTO_FORMAT_Table10 = 20; - public static int AUTO_FORMAT_Classic = 30; - private Sxview pt; - private WorkBookHandle book; - private WorkSheetHandle worksheet; - - /** - * Create a new PivotTableHandle from an Excel PivotTable - * - * @param PivotTable - * - the PivotTable to create a handle for. - */ - public PivotTableHandle(Sxview f, WorkBookHandle bk) { - pt = f; - book = bk; - //SxStreamID sxid= bk.getWorkBook().getPivotStrean(pt.getICache()); - //cellRange= sxid.getCellRange(); - } - - public WorkSheetHandle getWorkSheetHandle() { - return worksheet; - } - - /** - * @return Returns the cellRange. - */ - public CellRange getDataSourceRange() { - SxStreamID sxid= book.getWorkBook().getPivotStream(pt.getICache()); - return sxid.getCellRange(); - } - - /** - * Sets the Pivot Table Range to represent the Data to analyse - *
        NOTE: any existing data will be replaced - * @param cellRange The cellRange to set. - */ - public void setSourceDataRange(CellRange cellRange) { - SxStreamID sxid= book.getWorkBook().getPivotStream(pt.getICache()); - sxid.setCellRange(cellRange); - try { - pt.setNPivotFields((short) cellRange.getCols().length); - } catch (ColumnNotFoundException e) { - - } - - } - - /** - * Sets the Pivot Table Range to represent the Data to analyse - *
        NOTE: any existing data will be replaced - *
        If the cell range does not contain sheet information, the sheet that the pivot table is located will be used - * @param cellRange - */ - public void setSourceDataRange(String range) { - int[] rc= ExcelTools.getRangeCoords(range); - if (range.indexOf("!")==-1) - range= this.getWorkSheetHandle() + "!" + range; - SxStreamID sxid= book.getWorkBook().getPivotStream(pt.getICache()); - sxid.setCellRange(range); - - pt.setNPivotFields((short) ((rc[3]-rc[1])+1)); - } - - /** - * Sets the Pivot Table data source from a named range - * @param namedrange Named Range - */ - public void setSource(String namedrange) { + public static int AUTO_FORMAT_Report1 = 1; + public static int AUTO_FORMAT_Report2 = 2; + public static int AUTO_FORMAT_Report3 = 3; + public static int AUTO_FORMAT_Report4 = 4; + public static int AUTO_FORMAT_Report5 = 5; + public static int AUTO_FORMAT_Report6 = 6; + public static int AUTO_FORMAT_Report7 = 7; + public static int AUTO_FORMAT_Report8 = 8; + public static int AUTO_FORMAT_Report9 = 9; + public static int AUTO_FORMAT_Report10 = 10; + public static int AUTO_FORMAT_Table1 = 11; + public static int AUTO_FORMAT_Table2 = 12; + public static int AUTO_FORMAT_Table3 = 13; + public static int AUTO_FORMAT_Table4 = 14; + public static int AUTO_FORMAT_Table5 = 15; + public static int AUTO_FORMAT_Table6 = 16; + public static int AUTO_FORMAT_Table7 = 17; + public static int AUTO_FORMAT_Table8 = 18; + public static int AUTO_FORMAT_Table9 = 19; + public static int AUTO_FORMAT_Table10 = 20; + public static int AUTO_FORMAT_Classic = 30; + private Sxview pt; + private WorkBookHandle book; + private WorkSheetHandle worksheet; + + /** + * Create a new PivotTableHandle from an Excel PivotTable + * + * @param PivotTable + * - the PivotTable to create a handle for. + */ + public PivotTableHandle(Sxview f, WorkBookHandle bk) { + pt = f; + book = bk; + //SxStreamID sxid= bk.getWorkBook().getPivotStrean(pt.getICache()); + //cellRange= sxid.getCellRange(); + } + + public WorkSheetHandle getWorkSheetHandle() { + return worksheet; + } + + /** + * @return Returns the cellRange. + */ + public CellRange getDataSourceRange() { + SxStreamID sxid = book.getWorkBook().getPivotStream(pt.getICache()); + return sxid.getCellRange(); + } + + /** + * Sets the Pivot Table Range to represent the Data to analyse + *
        NOTE: any existing data will be replaced + * @param cellRange The cellRange to set. + */ + public void setSourceDataRange(CellRange cellRange) { + SxStreamID sxid = book.getWorkBook().getPivotStream(pt.getICache()); + sxid.setCellRange(cellRange); + try { + pt.setNPivotFields((short) cellRange.getCols().length); + } catch (ColumnNotFoundException e) { + + } + + } + + /** + * Sets the Pivot Table Range to represent the Data to analyse + *
        NOTE: any existing data will be replaced + *
        If the cell range does not contain sheet information, the sheet that the pivot table is located will be used + * @param cellRange + */ + public void setSourceDataRange(String range) { + int[] rc = ExcelTools.getRangeCoords(range); + if (range.indexOf("!") == -1) + range = this.getWorkSheetHandle() + "!" + range; + SxStreamID sxid = book.getWorkBook().getPivotStream(pt.getICache()); + sxid.setCellRange(range); + + pt.setNPivotFields((short) ((rc[3] - rc[1]) + 1)); + } + + /** + * Sets the Pivot Table data source from a named range + * @param namedrange Named Range + */ + public void setSource(String namedrange) { // TODO: finish; update DCONNAME - } - - /** - * get the Name of the PivotTable - * - * @return String - value of the PivotTable if stored as a String. - */ - public String getTableName() { - return pt.getTableName(); - } - - /** - * set the Name of the PivotTable - * - * @param String - * - value of the PivotTable if stored as a String. - */ - public void setTableName(String tx) { - pt.setTableName(tx); - } - - /** - * returns the name of the data field - * @return - */ - public String getDataName() { - return pt.getDataName(); - } - - /** - * sets the name of the data field for this pivot table - * @param name - */ - public void setDataName(String name) { - pt.setDataName(name); - } - /** - * returns whether a given row is contained in this PivotTable - * - * @param int the row number - * @return boolean whether the row is in the table - */ - public boolean containsRow(int x) { - if ((x <= pt.getRwLast()) && (x >= pt.getRwFirst())) - return true; - return false; - } - - /** - * return a more friendly - * - * @see java.lang.Object#toString() - */ - public String toString() { - return this.getTableName(); - } - - /** - * returns whether a given col is contained in this PivotTable - * - * @param int the column number - * @return boolean whether the col is in the table - */ - public boolean containsCol(int x) { - if ((x <= pt.getColLast()) && (x >= pt.getColFirst())) - return true; - return false; - } - - /** - * Takes a string as a current PivotTable location, and changes that pointer - * in the PivotTable to the new string that is sent. This can take single - * cells "A5" and cell ranges, "A3:d4" Returns true if the cell range - * specified in PivotTableLoc exists & can be changed else false. This also - * cannot change a cell pointer to a cell range or vice versa. - * - * @param String - * - range of Cells within PivotTable to modify - * @param String - * - new range of Cells within PivotTable - * - * - * public boolean changePivotTableLocation(String PivotTableLoc, - * String newLoc) throws PivotTableNotFoundException{ - * - * Logger.logInfo("Changing: " + PivotTableLoc + " to: " + - * newLoc); - * - * return false; - * - * } - */ - - /** - * set whether to display row grand totals - * - * @param boolean whether to display row grand totals - */ - public void setRowsHaveGrandTotals(boolean b) { - pt.setFRwGrand(b); - } - - /** - * get whether table displays row grand totals - * - * @return boolean whether table displays row grand total - */ - public boolean getRowsHaveGrandTotals() { - return pt.getFRwGrand(); - } - - /** - * set whether to display a column grand total - * - * @param boolean whether to display column grand total - */ - public void setColsHaveGrandTotals(boolean b) { - pt.setColGrand(b); - } - - /** - * get whether table displays a column grand total - * - * @return boolean whether table displays column grand total - */ - public boolean getColsHaveGrandTotals() { - return pt.getFColGrand(); - } - - /** - * set the auto format for the Table - * - *

        -	 *         The valid formats are:
        -	 *         
        -	 *             PivotTableHandle.AUTO_FORMAT_Report1
        -	 *             PivotTableHandle.AUTO_FORMAT_Report2
        -	 *             PivotTableHandle.AUTO_FORMAT_Report3
        -	 *             PivotTableHandle.AUTO_FORMAT_Report4
        -	 *             PivotTableHandle.AUTO_FORMAT_Report5
        -	 *             PivotTableHandle.AUTO_FORMAT_Report6
        -	 *             PivotTableHandle.AUTO_FORMAT_Report7
        -	 *             PivotTableHandle.AUTO_FORMAT_Report8
        -	 *             PivotTableHandle.AUTO_FORMAT_Report9
        -	 *             PivotTableHandle.AUTO_FORMAT_Report10
        -	 *             PivotTableHandle.AUTO_FORMAT_Table1
        -	 *             PivotTableHandle.AUTO_FORMAT_Table2
        -	 *             PivotTableHandle.AUTO_FORMAT_Table3
        -	 *             PivotTableHandle.AUTO_FORMAT_Table4
        -	 *             PivotTableHandle.AUTO_FORMAT_Table5
        -	 *             PivotTableHandle.AUTO_FORMAT_Table6
        -	 *             PivotTableHandle.AUTO_FORMAT_Table7
        -	 *             PivotTableHandle.AUTO_FORMAT_Table8
        -	 *             PivotTableHandle.AUTO_FORMAT_Table9
        -	 *             PivotTableHandle.AUTO_FORMAT_Table10
        -	 * 
        - * - * @param int the auto format Id for the table - */ - public void setAutoFormatId(int b) { - pt.setItblAutoFmt((short) b); - } - - /** - * get the auto format for the Table - * - * @return int the auto format Id for the table - */ - public int getAutoFormatId() { - return pt.getItblAutoFmt(); - } - - /** - * set whether to auto format the Table - * - * @param boolean whether to auto format the table - */ - public void setUsesAutoFormat(boolean b) { - pt.setFAutoFormat(b); - } - - /** - * get whether table has auto format applied - * - * @param boolean whether table has auto format applied - */ - public boolean getUsesAutoFormat() { - return pt.getFAutoFormat(); - } - - /** - * set Width/Height Autoformat is applied - * - * @param boolean whether to apply the Width/Height Autoformat - */ - public void setAutoWidthHeight(boolean b) { - pt.setFWH(b); - } - - /** - * get whether Width/Height Autoformat is applied - * - * @return boolean whether the Width/Height Autoformat is applied - */ - public boolean getAutoWidthHeight() { - return pt.getFWH(); - } - - /** - * set whether Font Autoformat is applied - * - * @param boolean whether to apply the Font Autoformat - */ - public void setAutoFont(boolean b) { - pt.setFFont(b); - } - - /** - * get whether Font Autoformat is applied - * - * @return boolean whether the Font Autoformat is applied - */ - public boolean getAutoFont() { - return pt.getFFont(); - } - - public void removeArtifacts() { - int[] coords = { pt.getRwFirst() - 2, pt.getColFirst(), pt.getRwLast(), - pt.getColLast() }; - try { - CellRange newr = new CellRange(worksheet, coords, true); - CellHandle[] ch = newr.getCells(); - for (int r = 0; r < ch.length; r++) { - if (ch[r] != null) - ch[r].remove(true); - } - } catch (Exception e) { - Logger.logWarn("could not remove artifacts in PivotTableHandle: " - + e); - } - } - - /** - * set whether Alignment Autoformat is applied - * - * @param boolean whether to apply the Alignment Autoformat - */ - public void setAutoAlign(boolean b) { - pt.setFAlign(b); - } - - /** - * get whether Alignment Autoformat is applied - * - * @return boolean whether the Alignment Autoformat is applied - */ - public boolean getAutoAlign() { - return pt.getFAlign(); - } - - /** - * set whether Border Autoformat is applied - * - * @param boolean whether to apply the Border Autoformat - */ - public void setAutoBorder(boolean b) { - pt.setFBorder(b); - } - - /** - * get whether Border Autoformat is applied - * - * @return boolean whether the Border Autoformat is applied - */ - public boolean getAutoBorder() { - return pt.getFBorder(); - } - - /** - * set whether Pattern Autoformat is applied - * - * @param boolean whether to apply the Pattern Autoformat - */ - public void setAutoPattern(boolean b) { - pt.setFPattern(b); - } - - /** - * get whether Pattern Autoformat is applied - * - * @return boolean whether the Pattern Autoformat is applied - */ - public boolean getAutoPattern() { - return pt.getFPattern(); - } - - /** - * set whether Number Autoformat is applied - * - * @param boolean whether to apply the Number Autoformat - */ - public void setAutoNumber(boolean b) { - pt.setFNumber(b); - } - - /** - * get whether Number Autoformat is applied - * - * @return boolean whether the Number Autoformat is applied - */ - public boolean getAutoNumber() { - return pt.getFNumber(); - } - - /** - * set the first row in the PivotTable - */ - public void setRowFirst(int s) { - // s--; // these are zero-based rows - pt.setRwFirst((short) s); - } - - /** - * get the first row in the PivotTable - */ - public int getRowFirst() { - return (int) pt.getRwFirst() + 1; - } - - /** - * set the last row in the PivotTable - */ - public void setRowLast(int s) { - s--; - pt.setRwLast((short) s); - } - - /** - * get the last row in the PivotTable - */ - public int getRowLast() { - return (int) pt.getRwLast() + 1; - } - - /** - * set the first Column in the PivotTable - */ - public void setColFirst(int s) { - pt.setColFirst((short) s); - } - - /** - * get the first Column in the PivotTable - */ - public int getColFirst() { - return (int) pt.getColFirst(); - } - - /** - * set the last Column in the PivotTable - */ - public void setColLast(int s) { - pt.setColLast((short) s); - } - - /** - * get the last Column in the PivotTable - */ - public int getColLast() { - return (int) pt.getColLast(); - } - - /** - * set the first header row - */ - public void setRowFirstHead(int s) { - s--; - pt.setRwFirstHead((short) s); - } - - /** - * get the first header row - */ - public int getRowFirstHead() { - return (int) pt.getRwFirstHead() + 1; - } - - /** - * set the first Row containing data - */ - public void setRowFirstData(int s) { - s--; // zero-based rows - pt.setRwFirstData((short) s); - } - - /** - * get the first Row containing data - */ - public int getRowFirstData() { - return (int) pt.getRwFirstData() + 1; - } - - /** - * set the first Column containing data - */ - public void setColFirstData(int s) { - pt.setColFirstData((short) s); - } - - /** - * get the first Column containing data - */ - public int getColFirstData() { - return (int) pt.getColFirstData(); - } - - /** - * returns the JSON representation of this PivotTable - * - * @return JSON Pivot Table representation - */ - public String getJSON() { - // copy all methods to this sucker - JSONObject thePivot = new JSONObject(); - - try { - thePivot.put("title", this.getTableName()); + } + + /** + * get the Name of the PivotTable + * + * @return String - value of the PivotTable if stored as a String. + */ + public String getTableName() { + return pt.getTableName(); + } + + /** + * set the Name of the PivotTable + * + * @param String + * - value of the PivotTable if stored as a String. + */ + public void setTableName(String tx) { + pt.setTableName(tx); + } + + /** + * returns the name of the data field + * @return + */ + public String getDataName() { + return pt.getDataName(); + } + + /** + * sets the name of the data field for this pivot table + * @param name + */ + public void setDataName(String name) { + pt.setDataName(name); + } + + /** + * returns whether a given row is contained in this PivotTable + * + * @param int the row number + * @return boolean whether the row is in the table + */ + public boolean containsRow(int x) { + return (x <= pt.getRwLast()) && (x >= pt.getRwFirst()); + } + + /** + * return a more friendly + * + * @see java.lang.Object#toString() + */ + public String toString() { + return this.getTableName(); + } + + /** + * returns whether a given col is contained in this PivotTable + * + * @param int the column number + * @return boolean whether the col is in the table + */ + public boolean containsCol(int x) { + return (x <= pt.getColLast()) && (x >= pt.getColFirst()); + } + + /** + * Takes a string as a current PivotTable location, and changes that pointer + * in the PivotTable to the new string that is sent. This can take single + * cells "A5" and cell ranges, "A3:d4" Returns true if the cell range + * specified in PivotTableLoc exists & can be changed else false. This also + * cannot change a cell pointer to a cell range or vice versa. + * + * @param String + * - range of Cells within PivotTable to modify + * @param String + * - new range of Cells within PivotTable + * + * + * public boolean changePivotTableLocation(String PivotTableLoc, + * String newLoc) throws PivotTableNotFoundException{ + * + * Logger.logInfo("Changing: " + PivotTableLoc + " to: " + + * newLoc); + * + * return false; + * + * } + */ + + /** + * set whether to display row grand totals + * + * @param boolean whether to display row grand totals + */ + public void setRowsHaveGrandTotals(boolean b) { + pt.setFRwGrand(b); + } + + /** + * get whether table displays row grand totals + * + * @return boolean whether table displays row grand total + */ + public boolean getRowsHaveGrandTotals() { + return pt.getFRwGrand(); + } + + /** + * set whether to display a column grand total + * + * @param boolean whether to display column grand total + */ + public void setColsHaveGrandTotals(boolean b) { + pt.setColGrand(b); + } + + /** + * get whether table displays a column grand total + * + * @return boolean whether table displays column grand total + */ + public boolean getColsHaveGrandTotals() { + return pt.getFColGrand(); + } + + /** + * set the auto format for the Table + * + *
        +     *         The valid formats are:
        +     *
        +     *             PivotTableHandle.AUTO_FORMAT_Report1
        +     *             PivotTableHandle.AUTO_FORMAT_Report2
        +     *             PivotTableHandle.AUTO_FORMAT_Report3
        +     *             PivotTableHandle.AUTO_FORMAT_Report4
        +     *             PivotTableHandle.AUTO_FORMAT_Report5
        +     *             PivotTableHandle.AUTO_FORMAT_Report6
        +     *             PivotTableHandle.AUTO_FORMAT_Report7
        +     *             PivotTableHandle.AUTO_FORMAT_Report8
        +     *             PivotTableHandle.AUTO_FORMAT_Report9
        +     *             PivotTableHandle.AUTO_FORMAT_Report10
        +     *             PivotTableHandle.AUTO_FORMAT_Table1
        +     *             PivotTableHandle.AUTO_FORMAT_Table2
        +     *             PivotTableHandle.AUTO_FORMAT_Table3
        +     *             PivotTableHandle.AUTO_FORMAT_Table4
        +     *             PivotTableHandle.AUTO_FORMAT_Table5
        +     *             PivotTableHandle.AUTO_FORMAT_Table6
        +     *             PivotTableHandle.AUTO_FORMAT_Table7
        +     *             PivotTableHandle.AUTO_FORMAT_Table8
        +     *             PivotTableHandle.AUTO_FORMAT_Table9
        +     *             PivotTableHandle.AUTO_FORMAT_Table10
        +     * 
        + * + * @param int the auto format Id for the table + */ + public void setAutoFormatId(int b) { + pt.setItblAutoFmt((short) b); + } + + /** + * get the auto format for the Table + * + * @return int the auto format Id for the table + */ + public int getAutoFormatId() { + return pt.getItblAutoFmt(); + } + + /** + * set whether to auto format the Table + * + * @param boolean whether to auto format the table + */ + public void setUsesAutoFormat(boolean b) { + pt.setFAutoFormat(b); + } + + /** + * get whether table has auto format applied + * + * @param boolean whether table has auto format applied + */ + public boolean getUsesAutoFormat() { + return pt.getFAutoFormat(); + } + + /** + * set Width/Height Autoformat is applied + * + * @param boolean whether to apply the Width/Height Autoformat + */ + public void setAutoWidthHeight(boolean b) { + pt.setFWH(b); + } + + /** + * get whether Width/Height Autoformat is applied + * + * @return boolean whether the Width/Height Autoformat is applied + */ + public boolean getAutoWidthHeight() { + return pt.getFWH(); + } + + /** + * set whether Font Autoformat is applied + * + * @param boolean whether to apply the Font Autoformat + */ + public void setAutoFont(boolean b) { + pt.setFFont(b); + } + + /** + * get whether Font Autoformat is applied + * + * @return boolean whether the Font Autoformat is applied + */ + public boolean getAutoFont() { + return pt.getFFont(); + } + + public void removeArtifacts() { + int[] coords = {pt.getRwFirst() - 2, pt.getColFirst(), pt.getRwLast(), + pt.getColLast()}; + try { + CellRange newr = new CellRange(worksheet, coords, true); + CellHandle[] ch = newr.getCells(); + for (int r = 0; r < ch.length; r++) { + if (ch[r] != null) + ch[r].remove(true); + } + } catch (Exception e) { + Logger.logWarn("could not remove artifacts in PivotTableHandle: " + + e); + } + } + + /** + * set whether Alignment Autoformat is applied + * + * @param boolean whether to apply the Alignment Autoformat + */ + public void setAutoAlign(boolean b) { + pt.setFAlign(b); + } + + /** + * get whether Alignment Autoformat is applied + * + * @return boolean whether the Alignment Autoformat is applied + */ + public boolean getAutoAlign() { + return pt.getFAlign(); + } + + /** + * set whether Border Autoformat is applied + * + * @param boolean whether to apply the Border Autoformat + */ + public void setAutoBorder(boolean b) { + pt.setFBorder(b); + } + + /** + * get whether Border Autoformat is applied + * + * @return boolean whether the Border Autoformat is applied + */ + public boolean getAutoBorder() { + return pt.getFBorder(); + } + + /** + * set whether Pattern Autoformat is applied + * + * @param boolean whether to apply the Pattern Autoformat + */ + public void setAutoPattern(boolean b) { + pt.setFPattern(b); + } + + /** + * get whether Pattern Autoformat is applied + * + * @return boolean whether the Pattern Autoformat is applied + */ + public boolean getAutoPattern() { + return pt.getFPattern(); + } + + /** + * set whether Number Autoformat is applied + * + * @param boolean whether to apply the Number Autoformat + */ + public void setAutoNumber(boolean b) { + pt.setFNumber(b); + } + + /** + * get whether Number Autoformat is applied + * + * @return boolean whether the Number Autoformat is applied + */ + public boolean getAutoNumber() { + return pt.getFNumber(); + } + + /** + * set the first row in the PivotTable + */ + public void setRowFirst(int s) { + // s--; // these are zero-based rows + pt.setRwFirst((short) s); + } + + /** + * get the first row in the PivotTable + */ + public int getRowFirst() { + return (int) pt.getRwFirst() + 1; + } + + /** + * set the last row in the PivotTable + */ + public void setRowLast(int s) { + s--; + pt.setRwLast((short) s); + } + + /** + * get the last row in the PivotTable + */ + public int getRowLast() { + return (int) pt.getRwLast() + 1; + } + + /** + * set the first Column in the PivotTable + */ + public void setColFirst(int s) { + pt.setColFirst((short) s); + } + + /** + * get the first Column in the PivotTable + */ + public int getColFirst() { + return (int) pt.getColFirst(); + } + + /** + * set the last Column in the PivotTable + */ + public void setColLast(int s) { + pt.setColLast((short) s); + } + + /** + * get the last Column in the PivotTable + */ + public int getColLast() { + return (int) pt.getColLast(); + } + + /** + * set the first header row + */ + public void setRowFirstHead(int s) { + s--; + pt.setRwFirstHead((short) s); + } + + /** + * get the first header row + */ + public int getRowFirstHead() { + return (int) pt.getRwFirstHead() + 1; + } + + /** + * set the first Row containing data + */ + public void setRowFirstData(int s) { + s--; // zero-based rows + pt.setRwFirstData((short) s); + } + + /** + * get the first Row containing data + */ + public int getRowFirstData() { + return (int) pt.getRwFirstData() + 1; + } + + /** + * set the first Column containing data + */ + public void setColFirstData(int s) { + pt.setColFirstData((short) s); + } + + /** + * get the first Column containing data + */ + public int getColFirstData() { + return (int) pt.getColFirstData(); + } + + /** + * returns the JSON representation of this PivotTable + * + * @return JSON Pivot Table representation + */ + public String getJSON() { + // copy all methods to this sucker + JSONObject thePivot = new JSONObject(); + + try { + thePivot.put("title", this.getTableName()); // thePivot.put("cellrange", this.getCellRange().getRange()); - } catch (Exception e) { - throw new WorkBookException("PivotTableHandle.getJSON failed:" + e, - WorkBookException.RUNTIME_ERROR); - } - return thePivot.toString(); - } + } catch (Exception e) { + throw new WorkBookException("PivotTableHandle.getJSON failed:" + e, + WorkBookException.RUNTIME_ERROR); + } + return thePivot.toString(); + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.java b/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.java index 704a5d2..074d7b8 100644 --- a/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.java +++ b/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.java @@ -2,69 +2,55 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.OpenXLS; -import java.util.Iterator; -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.BottomMargin; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.HCenter; -import io.starter.formats.XLS.LeftMargin; -import io.starter.formats.XLS.Name; -import io.starter.formats.XLS.PrintGrid; -import io.starter.formats.XLS.PrintRowCol; -import io.starter.formats.XLS.RightMargin; -import io.starter.formats.XLS.Setup; -import io.starter.formats.XLS.TopMargin; -import io.starter.formats.XLS.VCenter; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.WsBool; +import io.starter.formats.XLS.*; + +import java.util.Iterator; /** * The PrinterSettingsHandle gives you control over the printer settings for a Sheet such as whether to print in landscape or portrait mode. -

        - * The PrinterSettingsHandle provides fine-grained control over printing settings - * in Excel. - *

        - * NOTE: you can only view the effects of these methods in an open Excel file when - * you use the "Print Setup" command. - *

        - * OpenXLS does not currently support directly sending - * spreadsheet data to a printer. - * + *

        + * The PrinterSettingsHandle provides fine-grained control over printing settings + * in Excel. + *

        + * NOTE: you can only view the effects of these methods in an open Excel file when + * you use the "Print Setup" command. + *

        + * OpenXLS does not currently support directly sending + * spreadsheet data to a printer. + *

        *

        * Example Usage: *

        -	...
        -	PrinterSettingsHandle printersetup = sheet.getPrinterSettings();
        -	// Paper Size
        -	printersetup.setPaperSize(PrinterSettingsHandle.PAPER_SIZE_LEDGER_17x11);
        -	// Scaling
        -	printersetup.setScale(125);
        -	//	resolution
        -	printersetup.setResolution(300);
        -	...
        - 
        - * - * + * ... + * PrinterSettingsHandle printersetup = sheet.getPrinterSettings(); + * // Paper Size + * printersetup.setPaperSize(PrinterSettingsHandle.PAPER_SIZE_LEDGER_17x11); + * // Scaling + * printersetup.setScale(125); + * // resolution + * printersetup.setResolution(300); + * ... + *
        */ /* order of records in Page Settings Block: * HORIZONTALPAGEBREAKS @@ -84,626 +70,659 @@ */ public class PrinterSettingsHandle implements Handle { - -// paper size ints - public static final int PAPER_SIZE_UNDEFINED = 0; - public static final int PAPER_SIZE_LETTER_8_5x11 = 1; // Letter 81/2" x 11" - public static final int PAPER_SIZE_LETTER_SMALL = 2; // Letter small 81/2" x 11" - public static final int PAPER_SIZE_TABLOID_11x17 = 3; // Tabloid 11" x 17" - public static final int PAPER_SIZE_LEDGER_17x11 = 4; // Ledger 17" x 11" - public static final int PAPER_SIZE_LEGAL_8_5x14 = 5; // Legal 81/2" x 14" - public static final int PAPER_SIZE_STATEMENT_5_5x8_5 = 6; // Statement 51/2" x 81/2" - public static final int PAPER_SIZE_LETTER_EXTRA_9_5Ax12 = 50 ; // Letter Extra 91/2" x 12 - public static final int PAPER_SIZE_LEGAL_EXTRA_9_5Ax15 = 51 ; // Legal Extra 91/2" x 15" - public static final int PAPER_SIZE_TABLOID_EXTRA_1111_16Ax18 = 52 ; // Tabloid Extra 1111/16" x 18" - public static final int PAPER_SIZE_A4_EXTRA_235MM_X_322MM = 53 ; // A4 Extra 235mm x 322mm - public static final int PAPER_SIZE_LETTER_TRANSVERSE_8_5Ax11 = 54 ; // Letter Transverse 81/2" x 11" - public static final int PAPER_SIZE_EXECUTIVE_7_QUARTER_X_10_5 = 7 ; // Executive 71/4" x 101/2" - public static final int PAPER_SIZE_TRANSVERSE_210MM_X_297MM = 55 ; // A4 Transverse 210mm x 297mm - public static final int PAPER_SIZE_A3_297MM_X_420MM = 8 ; // 8 A3 297mm x 420mm - public static final int PAPER_SIZE_LETTER_EXTRA_TRANSV_9_5_X_12 = 56 ; // 56 Letter Extra Transv. 91/2" x 12" - public static final int PAPER_SIZE_A4_210MM_X_297MM = 9 ; // A4 210mm x 297mm - public static final int PAPER_SIZE_SUPER_A_A4_227MM_X_356MM = 57 ; // Super A/A4 227mm x 356mm - public static final int PAPER_SIZE_A4_SMALL_210MM_X_297MM = 10 ; // A4 small 210mm x 297mm - public static final int PAPER_SIZE_SUPER_B_A3_305MM_X_487MM = 58 ; // Super B/A3 305mm x 487mm - public static final int PAPER_SIZE_A5_148MM_X_210MM = 11 ; // A5 148mm x 210mm - public static final int PAPER_SIZE_LETTER_PLUS = 59 ; // Letter Plus - public static final int PAPER_SIZE_2_X_1211_16 = 81 ; // 2" x 1211/16" - public static final int PAPER_SIZE_B4_JIS_257MM_X_364MM = 12 ; // B4 (JIS) 257mm x 364mm - public static final int PAPER_SIZE_A4_PLUS_210MM_X_330MM = 60 ; // A4 Plus 210mm x 330mm - public static final int PAPER_SIZE_B5_JIS_182MM_X_257MM = 13 ; // B5 (JIS) 182mm x 257mm - public static final int PAPER_SIZE_A5_TRANSVERSE_148MM_X_210MM = 61 ; // A5 Transverse 148mm x 210mm - public static final int PAPER_SIZE_FOLIO_8_5_X_13 = 14 ; // Folio 81/2" x 13" - public static final int PAPER_SIZE_B5_JIS_TRANSVERSE_182MM_X_257MM = 62 ; // B5 (JIS) Transverse 182mm x 257mm - public static final int PAPER_SIZE_QUATRO_215MM_X_275MM = 15 ; // Quarto 215mm x 275mm - public static final int PAPER_SIZE_A3_EXTRA_322MM_X_445MM = 63 ; // A3 Extra 322mm x 445mm - public static final int PAPER_SIZE_10Ax14_10_X_14 = 16 ; // 10x14 10" x 14" - public static final int PAPER_SIZE_A5_EXTRA_174MM_X_235 = 64 ; // A5 Extra 174mm x 235mm - public static final int PAPER_SIZE_11Ax17_11_X_17 = 17 ; // 11x17 11" x 17" - public static final int PAPER_SIZE_B5_ISO_EXTRA_201MM_X_276MM = 65 ; // B5 (ISO) Extra 201mm x 276mm - public static final int PAPER_SIZE_NOTE_8_5_X_11 = 18 ; // Note 81/2" x 11" - public static final int PAPER_SIZE_A2_420MM_X_594MM = 66 ; // A2 420mm x 594mm - public static final int PAPER_SIZE_ENVELOPE_9_3_78_X_8_78 = 19 ; // Envelope #9 37/8" x 87/8" - public static final int PAPER_SIZE_A3_TRANSVERSE_297MM_X_420MM = 67 ; // A3 Transverse 297mm x 420mm - public static final int PAPER_SIZE_ENVELOPE_10_4_18_X_9_5 = 20 ; // Envelope #10 41/8" x 91/2" - public static final int PAPER_SIZE_EXTRA_TRANSVERSE_322MM_X_445MM = 68 ; // A3 Extra Transverse 322mm x 445mm - public static final int PAPER_SIZE_ENVELOPE_11_4_5_X_10_38 = 21 ; // Envelope #11 41/2" x 103/8" - public static final int PAPER_SIZE_DBL_JAP_POSTCARD_200MM_X_148MM = 69 ; // Dbl. Japanese Postcard 200mm x 148mm - public static final int PAPER_SIZE_ENVELOPE_12_4_34_X_11 = 22 ; // Envelope #12 43/4" x 11" - public static final int PAPER_SIZE_A6_105MM_X_148MM = 70 ; // A6 105mm x 148mm - public static final int PAPER_SIZE_ENVELOPE_14_5_X_11_5 = 23 ; // Envelope #14 5" x 111/2" - public static final int PAPER_SIZE_C_17_X_22_72 = 24 ; // C 17" x 22" 72 - public static final int PAPER_SIZE_D_22_X_34_73 = 25 ; // D 22" x 34" 73 - public static final int PAPER_SIZE_E_34_X_44_74 = 26 ; // E 34" x 44" 74 - public static final int PAPER_SIZE_DL_ENVELOPE_110MM_X_110MM_X_220MM = 27 ; // Envelope DL 110mm x 220mm - public static final int PAPER_SIZE_LETTER_ROTATED_11_X_8_5 = 75 ; // Letter Rotated 11" x 81/2" - public static final int PAPER_SIZE_ENVELOPE_C5_162MM_X_229M = 28 ; // Envelope C5 162mm x 229mm - public static final int PAPER_SIZE_A3_ROTATED_420MM_X_297MM = 76 ; // A3 Rotated 420mm x 297mm - public static final int PAPER_SIZE_ENVELOPE_C3_324MM_X_458MM = 29 ; // Envelope C3 324mm x 458mm - public static final int PAPER_SIZE_A4_ROTATED_297MM_X_210MM = 77 ; // A4 Rotated 297mm x 210mm - public static final int PAPER_SIZE_ENVELOPE_C4_229MM_X_324MM = 30 ; // Envelope C4 229mm x 324mm - public static final int PAPER_SIZE_A5_ROTATED_210MM_X_148MM = 78 ; // A5 Rotated 210mm x 148mm - public static final int PAPER_SIZE_ENVELOPE_C6_115MM_X_162MM = 31 ; // Envelope C6 114mm x 162mm - public static final int PAPER_SIZE_ENVELOPE_C6_C5_114MM_X_229MM = 32 ; // Envelope C6/C5 114mm x 229mm - public static final int PAPER_SIZE_B4_ISO_250MM_X_353MM = 33 ; // B4 (ISO) 250mm x 353mm - public static final int PAPER_SIZE_B5_ISO_176MM_X_250MM = 34 ; // B5 (ISO) 176mm x 250mm - public static final int PAPER_SIZE_DBL_JAP_POSTCARD_ROT_148MM_X_200MM = 82 ; // Dbl. Jap. Postcard Rot. 148mm x 200mm - public static final int PAPER_SIZE_B6_ISO_125MM_X_176MM = 35 ; // B6 (ISO) 125mm x 176mm - public static final int PAPER_SIZE_ENVELOPE_ITALY_10MM_X_230MM = 36 ; // Envelope Italy 110mm x 230mm 84 - public static final int PAPER_SIZE_ENVELOPE_MONARCH_3_7_8_X_7_5 = 37 ; // Envelope Monarch 37/8" x 71/2" 85 - public static final int PAPER_SIZE_6_3_4_ENVELOPE_3_5_8_X_6_5 = 38 ; // 63/4 Envelope 35/8" x 61/2" 86 - public static final int PAPER_SIZE_US_STANDARD_FANFOLD_147_8_X_11 = 39 ; // US Standard Fanfold 147/8" x 11" 87 - public static final int PAPER_SIZE_GERMAN_STD_FANFOLD_8_5_X_12 = 40 ; // German Std. Fanfold 81/2" x 12" - public static final int PAPER_SIZE_GERMAN_LEGAL_FANFOLD_8_5_X_13 = 41 ; // German Legal Fanfold 81/2" x 13" - // public static final int PAPER_SIZE_B4_ISO_250MM_X_353MM = 42 ; // B4 (ISO) 250mm x 353mm - public static final int PAPER_SIZE_JAP_POSTCARD_100M_X_148MM = 43 ; // Japanese Postcard 100mm x 148mm - public static final int PAPER_SIZE_9_X_11 = 44 ; // 9x11 9" x 11" - public static final int PAPER_SIZE_10_X_11 = 45 ; // 10x11 10" x 11" - public static final int PAPER_SIZE_15_X_11 = 46 ; // 15x11 15" x 11" - public static final int PAPER_SIZE_ENVELOPE_INVITE_220MM_X_220MM = 47 ; // Envelope Invite 220mm x 220mm - public static final int PAPER_SIZE_B4_JIS_ROTATED_364MM_X_257MM = 79 ; // B4 (JIS) Rotated 364mm x 257mm - public static final int PAPER_SIZE_B5_JIS_ROTATED_257MMX_X_182MM = 80 ; // B5 (JIS) Rotated 257mm x 182mm - public static final int PAPER_SIZE_JAP_POSTCARD_ROT_148MM_X_100MM = 81 ; // Japanese Postcard Rot. 148mm x 100mm - public static final int PAPER_SIZE_A6_ROTATED_148MM_X_105MM = 83 ; // A6 Rotated 148mm x 105mm - public static final int PAPER_SIZE_B6_JIS_128MM_X_182MM = 88 ; // B6 (JIS) 128mm x 182mm - public static final int PAPER_SIZE_B6_JIS_ROT_182MM_X_128MM = 89 ; // B6 (JIS) Rotated 182mm x 128mm - public static final int PAPER_SIZE_12_X_11 = 90 ; // 12x11 12" x 11" - - /** - * default constructor - */ - public PrinterSettingsHandle (Boundsheet sheet) { - this.sheet = sheet; - - Iterator iter = sheet.getPrintRecs().iterator(); - while (iter.hasNext()) { - BiffRec record = (BiffRec) iter.next(); - - if (record instanceof Setup) - printerSettings = (Setup) record; - else if (record instanceof HCenter) - hCenter = (HCenter) record; - else if (record instanceof VCenter) - vCenter = (VCenter) record; - else if (record instanceof LeftMargin) - leftMargin = (LeftMargin) record; // missing in default set of records - else if (record instanceof RightMargin) - rightMargin = (RightMargin) record; // missing in default set of records - else if (record instanceof TopMargin) - topMargin = (TopMargin) record; // missing in default set of records - else if (record instanceof BottomMargin) - bottomMargin = (BottomMargin) record; // missing in default set of records - else if (record instanceof PrintGrid) - grid = (PrintGrid) record; - else if (record instanceof PrintRowCol) - headers = (PrintRowCol) record; - else if (record instanceof WsBool) - wsBool = (WsBool) record; - } - // Actually, the below comment is incorrect: do NOT do this unconditionally - //printerSettings.setNoPrintData(false); // there IS printer setup data - } - - Boundsheet sheet; - - private Setup printerSettings; - private HCenter hCenter; - private VCenter vCenter; - private LeftMargin leftMargin; - private RightMargin rightMargin; - private TopMargin topMargin; - private BottomMargin bottomMargin; - private PrintGrid grid; - private PrintRowCol headers; - private WsBool wsBool; - - // the following are unimplemented printer setting recs: - // HORIZONTALPAGEBREAKS; - // VERTICALPAGEBREAKS; - - - /** - * get the number of copies to print - * - * @return the number of copies - */ - public short getCopies() { - return printerSettings.getCopies(); - } - - - /** - * get the footer margin size in inches - * - * @return the footer margin in inches - */ - public double getFooterMargin() { - return printerSettings.getFooterMargin(); - } - - /** - * get the header margin size in inches - * - * @return the header margin in inches - */ - public double getHeaderMargin() { - return printerSettings.getHeaderMargin(); - } - - /** Gets the left print margin. */ - public double getLeftMargin() { - if (leftMargin==null) { - leftMargin= new LeftMargin(); - this.sheet.addMarginRecord(leftMargin); - } - return leftMargin.getMargin(); - } - - /** Gets the right print margin. */ - public double getRightMargin() { - if (rightMargin==null) { - rightMargin= new RightMargin(); - this.sheet.addMarginRecord(rightMargin); - } - return rightMargin.getMargin(); - } - - /** Gets the top print margin. */ - public double getTopMargin() { - if (topMargin==null) { - topMargin= new TopMargin(); - this.sheet.addMarginRecord(topMargin); - } - return topMargin.getMargin(); - } - - /** Gets the bottom print margin. */ - public double getBottomMargin() { - if (bottomMargin==null) { - bottomMargin= new BottomMargin(); - this.sheet.addMarginRecord(bottomMargin); - } - return bottomMargin.getMargin(); - } - - /** - * get the landscape orientation - * - * @return whether the print orientation is set to landscape - * - */ - public boolean getLandscape() { - return printerSettings.getLandscape(); - } - - /** - * get the left-to-right print orientation - * - * @return whether the print orientation is set to left-to-right - */ - public boolean getLeftToRight() { - return printerSettings.getLeftToRight(); - } - - /** - * get whether printing is in black and white - * - * @return black and white - */ - public boolean getNoColor() { - return printerSettings.getNoColor(); - } - - /** - * get whether to ignore orientation - * - * @return ignore orientation - */ - public boolean getNoOrient() { - return printerSettings.getNoOrient(); - } - - /** - * get whether printer data is missing - * - * @return whether the printer data is missing - */ - public boolean getNoPrintData() { - return printerSettings.getNoPrintData(); - } - - /** - * get the page to start printing from - * - * @return the page to start printing from - * - */ - public short getPageStart() { - return printerSettings.getPageStart(); - } - - /** - * Returns the paper size setting for the printer setup based on the - * following table: - * - * - * - * @return paper size - */ - public short getPaperSize() { - return printerSettings.getPaperSize(); - } - - /** - * @return - * @return whether to print Notes. - */ - public boolean getPrintNotes() { - return printerSettings.getPrintNotes(); - } - - - /** - * get the print resolution - * - * @return the printer resolution in DPI - */ - public short getResolution() { - return printerSettings.getResolution(); - } - - /** - * get the scale of the printer output in whole percentages - * - * ie: 25 = 25% - * - * @return the scale of printer output - */ - public short getScale() { - return printerSettings.getScale(); - } - - /** - * use custom start page for auto numbering - * - * @return Returns whether to use a custom start page - */ - public boolean getUsePage() { - return printerSettings.getUsePage(); - } - - /** - * get the vertical print resolution - * - * @return the vertical printer resolution in DPI - */ - public short getVerticalResolution() { - return printerSettings.getVerticalResolution(); - } - - /** Whether the sheet should be centered horizontally. */ - public boolean isHCenter() { - return hCenter.isHCenter(); - } - - /** Whether the sheet should be centered vertically. */ - public boolean isVCenter() { - return vCenter.isVCenter(); - } - - /** Whether the grid lines will be printed. */ - public boolean isPrintGridLines() { - return grid.isPrintGrid(); - } - - /** Whether the row and column headers will be printed. */ - public boolean isPrintRowColHeaders() { - return headers.isPrintHeaders(); - } - - /** Gets whether the sheet will be printed fit to some number of pages. */ - public boolean isFitToPage() { - return wsBool.isFitToPage(); - } - - /** - * set for draft quality output - * - * @param whether to use draft quality - */ - public void setDraft(boolean b) { - printerSettings.setDraft(b); - } - - /** - * Set the output to print onto this number of pages high - * - * ie: setFitHeight(10) will stretch the print out to fit 10 - * pages high - * - * @param number of pages to fit to height - */ - public void setFitHeight(int numpages) { - printerSettings.setFitHeight((short)numpages); - } - - /** - * Set the output to print onto this number of pages wide - * - * ie: setFitWidth(10) will stretch the print out to fit 10 - * pages wide - * - * @param number of pages to fit to width - */ - public void setFitWidth(int numpages) { - printerSettings.setFitWidth((short)numpages); - } - - /** Sets whether the sheet will be printed fit to some number of pages. */ - public void setFitToPage (boolean value) { - wsBool.setFitToPage( value ); - } - - /** - * sets the footer margin in inches - * - * @param footer margin - */ - public void setFooterMargin(double f) { - printerSettings.setFooterMargin(f); - } - - /** Sets whether the page should be centered horizontally. */ - public void setHCenter (boolean center) { - hCenter.setHCenter( center ); - } - - /** - * sets the Header margin in inches - * - * @param header margin - */ - public void setHeaderMargin(double h) { - printerSettings.setHeaderMargin(h); - } - - /** Sets the sheet's left print margin. */ - public void setLeftMargin (double value) { - if (leftMargin==null) { - leftMargin= new LeftMargin(); - this.sheet.addMarginRecord(leftMargin); - } - leftMargin.setMargin( value ); - } - - /** Sets the sheet's right print margin. */ - public void setRightMargin (double value) { - if (rightMargin==null) { - rightMargin= new RightMargin(); - this.sheet.addMarginRecord(rightMargin); - } - rightMargin.setMargin( value ); - } - - /** Sets the sheet's top print margin. */ - public void setTopMargin (double value) { - if (topMargin==null) { - topMargin= new TopMargin(); - this.sheet.addMarginRecord(topMargin); - } - topMargin.setMargin( value ); - } - - /** Sets the sheet's bottom print margin. */ - public void setBottomMargin (double value) { - if (bottomMargin==null) { - bottomMargin= new BottomMargin(); - this.sheet.addMarginRecord(bottomMargin); - } - bottomMargin.setMargin( value ); - } - - /** - * set the print orientation to landscape or portrait - * - * @param landscape - */ - public void setLandscape(boolean b) { - printerSettings.setNoOrient(false); // use the orientation setting - printerSettings.setLandscape(b); - } - - /** - * set the print orientation to left-to-right printing - * - * @param leftToRight - */ - public void setLeftToRight(boolean b) { - printerSettings.setLeftToRight(b); - } - - /** - * sets the output to black and white - * - * @param noColor - */ - public void setNoColor(boolean b) { - printerSettings.setNoColor(b); - } - - /** - * set the default page to start printing from - * - * @param p - */ - public void setPageStart(short p) { - printerSettings.setPageStart(p); - } - - /** - * sets the whether to print cell notes - * - * @param printNotes whether to print Notes. - */ - public void setPrintNotes(boolean b) { - printerSettings.setPrintNotes(b); - } - - /** - * Set the output printer resolution - * - * @param resolution The resolution to set in DPI. - */ - public void setResolution(int r) { - printerSettings.setResolution((short)r); - } - - /** - * scale the printer output in whole percentages - * - * ie: 25 = 25% - * - * @param scale The scale to set. - */ - public void setScale(int scale) { - printerSettings.setScale((short)scale); - } - - /** - * @param usePage whether to use custom Page to start printing from. - */ - public void setUsePage(boolean usePage) { - printerSettings.setUsePage(usePage); - } - - /** - * @param verticalResolution The vertical Resolution in DPI - */ - public void setVerticalResolution(short verticalResolution) { - printerSettings.setVerticalResolution(verticalResolution); - } - - /** - * @param copies The number of copies to print - */ - public void setCopies(int copies) { - printerSettings.setCopies((short)copies); - } - - /** - * sets the paper size based on the paper size table - * - * @param the paper size index - */ - public void setPaperSize(int p) { - printerSettings.setPaperSize((short)p); - } - - /** - * get the draft quality setting - * - * @return draft quality - */ - public boolean getDraft() { - return printerSettings.getDraft(); - } - - /** - * get the number of pages to fit the printout to height - * - * @return fit to height - */ - public short getFitHeight() { - return printerSettings.getFitHeight(); - } - - /** - * get the number of pages to fit the printout to width - * - * @return fit to width - */ - public short getFitWidth() { - return printerSettings.getFitWidth(); - } - - /** Sets whether the sheet should be centered vertically. */ - public void setVCenter (boolean center) { - vCenter.setVCenter( center ); - } - - /** Sets whether to print the grid lines. */ - public void setPrintGrid (boolean print) { - grid.setPrintGrid( print ); - } - - /** Sets whether to print the row and column headers. */ - public void setPrintRowColHeaders (boolean print) { - headers.setPrintHeaders( print ); - } - - /** Gets the range specifying the titles printed on each page. - */ - public String getTitles() { - Name range = sheet.getName( "Built-in: PRINT_TITLES" ); - if (range == null) return null; - return range.getExpressionString(); - } - - /** Sets the range specifying the titles printed on each page. - * The reference for the row(s) to repeat e.g. $1:$1 for row 1 - * For Columns, type the reference to the column or columns that - * you want to set as a title e.g. $A:$B for columns A and B - */ - // note: MUST be in $ROW:$ROW or $COL:$COL format, for both - // can be $R:$R, $C:$C for both - public void setTitles (String range) { - Name name = sheet.getName( "Built-in: PRINT_TITLES" ); - if (name == null) try { - name = new Name( sheet.getWorkBook(), "Print_Titles" ); - name.setBuiltIn( (byte)0x07 ); //do before setNewScope as it blows out itab - name.setNewScope( sheet.getSheetNum() + 1 ); - } catch (WorkSheetNotFoundException e) { - // This shouldn't be possible. - throw new Error ("sheet not found re-scoping name"); - } - // pre-process range to ensure in proper format, ensure all absolute ($) refs + - // handle wholerow-wholecol refs + complex ranges - if (range==null) return; // TODO: Do what?? remove?? - String[] ranges= range.split(","); - range= ""; - for (int i= 0; i < ranges.length; i++) { - if (i > 0) // concatenate terms into one ptgmemfunc-style expression - range+=","; - String r= ""; - int[] rc= ExcelTools.getRangeCoords(ranges[i]); - if (rc[0]==rc[2]) // varies by column - r= "$" + ExcelTools.getAlphaVal(rc[1]) + ":$" + ExcelTools.getAlphaVal(rc[3]); - if (rc[1]==rc[3]) {// varies by row - r= "$" + rc[0] + ":$" + rc[2]; - } - range+= sheet.getSheetName() + "!" + r; - } - name.setLocation( range ); -} - + + // paper size ints + public static final int PAPER_SIZE_UNDEFINED = 0; + public static final int PAPER_SIZE_LETTER_8_5x11 = 1; // Letter 81/2" x 11" + public static final int PAPER_SIZE_LETTER_SMALL = 2; // Letter small 81/2" x 11" + public static final int PAPER_SIZE_TABLOID_11x17 = 3; // Tabloid 11" x 17" + public static final int PAPER_SIZE_LEDGER_17x11 = 4; // Ledger 17" x 11" + public static final int PAPER_SIZE_LEGAL_8_5x14 = 5; // Legal 81/2" x 14" + public static final int PAPER_SIZE_STATEMENT_5_5x8_5 = 6; // Statement 51/2" x 81/2" + public static final int PAPER_SIZE_LETTER_EXTRA_9_5Ax12 = 50; // Letter Extra 91/2" x 12 + public static final int PAPER_SIZE_LEGAL_EXTRA_9_5Ax15 = 51; // Legal Extra 91/2" x 15" + public static final int PAPER_SIZE_TABLOID_EXTRA_1111_16Ax18 = 52; // Tabloid Extra 1111/16" x 18" + public static final int PAPER_SIZE_A4_EXTRA_235MM_X_322MM = 53; // A4 Extra 235mm x 322mm + public static final int PAPER_SIZE_LETTER_TRANSVERSE_8_5Ax11 = 54; // Letter Transverse 81/2" x 11" + public static final int PAPER_SIZE_EXECUTIVE_7_QUARTER_X_10_5 = 7; // Executive 71/4" x 101/2" + public static final int PAPER_SIZE_TRANSVERSE_210MM_X_297MM = 55; // A4 Transverse 210mm x 297mm + public static final int PAPER_SIZE_A3_297MM_X_420MM = 8; // 8 A3 297mm x 420mm + public static final int PAPER_SIZE_LETTER_EXTRA_TRANSV_9_5_X_12 = 56; // 56 Letter Extra Transv. 91/2" x 12" + public static final int PAPER_SIZE_A4_210MM_X_297MM = 9; // A4 210mm x 297mm + public static final int PAPER_SIZE_SUPER_A_A4_227MM_X_356MM = 57; // Super A/A4 227mm x 356mm + public static final int PAPER_SIZE_A4_SMALL_210MM_X_297MM = 10; // A4 small 210mm x 297mm + public static final int PAPER_SIZE_SUPER_B_A3_305MM_X_487MM = 58; // Super B/A3 305mm x 487mm + public static final int PAPER_SIZE_A5_148MM_X_210MM = 11; // A5 148mm x 210mm + public static final int PAPER_SIZE_LETTER_PLUS = 59; // Letter Plus + public static final int PAPER_SIZE_2_X_1211_16 = 81; // 2" x 1211/16" + public static final int PAPER_SIZE_B4_JIS_257MM_X_364MM = 12; // B4 (JIS) 257mm x 364mm + public static final int PAPER_SIZE_A4_PLUS_210MM_X_330MM = 60; // A4 Plus 210mm x 330mm + public static final int PAPER_SIZE_B5_JIS_182MM_X_257MM = 13; // B5 (JIS) 182mm x 257mm + public static final int PAPER_SIZE_A5_TRANSVERSE_148MM_X_210MM = 61; // A5 Transverse 148mm x 210mm + public static final int PAPER_SIZE_FOLIO_8_5_X_13 = 14; // Folio 81/2" x 13" + public static final int PAPER_SIZE_B5_JIS_TRANSVERSE_182MM_X_257MM = 62; // B5 (JIS) Transverse 182mm x 257mm + public static final int PAPER_SIZE_QUATRO_215MM_X_275MM = 15; // Quarto 215mm x 275mm + public static final int PAPER_SIZE_A3_EXTRA_322MM_X_445MM = 63; // A3 Extra 322mm x 445mm + public static final int PAPER_SIZE_10Ax14_10_X_14 = 16; // 10x14 10" x 14" + public static final int PAPER_SIZE_A5_EXTRA_174MM_X_235 = 64; // A5 Extra 174mm x 235mm + public static final int PAPER_SIZE_11Ax17_11_X_17 = 17; // 11x17 11" x 17" + public static final int PAPER_SIZE_B5_ISO_EXTRA_201MM_X_276MM = 65; // B5 (ISO) Extra 201mm x 276mm + public static final int PAPER_SIZE_NOTE_8_5_X_11 = 18; // Note 81/2" x 11" + public static final int PAPER_SIZE_A2_420MM_X_594MM = 66; // A2 420mm x 594mm + public static final int PAPER_SIZE_ENVELOPE_9_3_78_X_8_78 = 19; // Envelope #9 37/8" x 87/8" + public static final int PAPER_SIZE_A3_TRANSVERSE_297MM_X_420MM = 67; // A3 Transverse 297mm x 420mm + public static final int PAPER_SIZE_ENVELOPE_10_4_18_X_9_5 = 20; // Envelope #10 41/8" x 91/2" + public static final int PAPER_SIZE_EXTRA_TRANSVERSE_322MM_X_445MM = 68; // A3 Extra Transverse 322mm x 445mm + public static final int PAPER_SIZE_ENVELOPE_11_4_5_X_10_38 = 21; // Envelope #11 41/2" x 103/8" + public static final int PAPER_SIZE_DBL_JAP_POSTCARD_200MM_X_148MM = 69; // Dbl. Japanese Postcard 200mm x 148mm + public static final int PAPER_SIZE_ENVELOPE_12_4_34_X_11 = 22; // Envelope #12 43/4" x 11" + public static final int PAPER_SIZE_A6_105MM_X_148MM = 70; // A6 105mm x 148mm + public static final int PAPER_SIZE_ENVELOPE_14_5_X_11_5 = 23; // Envelope #14 5" x 111/2" + public static final int PAPER_SIZE_C_17_X_22_72 = 24; // C 17" x 22" 72 + public static final int PAPER_SIZE_D_22_X_34_73 = 25; // D 22" x 34" 73 + public static final int PAPER_SIZE_E_34_X_44_74 = 26; // E 34" x 44" 74 + public static final int PAPER_SIZE_DL_ENVELOPE_110MM_X_110MM_X_220MM = 27; // Envelope DL 110mm x 220mm + public static final int PAPER_SIZE_LETTER_ROTATED_11_X_8_5 = 75; // Letter Rotated 11" x 81/2" + public static final int PAPER_SIZE_ENVELOPE_C5_162MM_X_229M = 28; // Envelope C5 162mm x 229mm + public static final int PAPER_SIZE_A3_ROTATED_420MM_X_297MM = 76; // A3 Rotated 420mm x 297mm + public static final int PAPER_SIZE_ENVELOPE_C3_324MM_X_458MM = 29; // Envelope C3 324mm x 458mm + public static final int PAPER_SIZE_A4_ROTATED_297MM_X_210MM = 77; // A4 Rotated 297mm x 210mm + public static final int PAPER_SIZE_ENVELOPE_C4_229MM_X_324MM = 30; // Envelope C4 229mm x 324mm + public static final int PAPER_SIZE_A5_ROTATED_210MM_X_148MM = 78; // A5 Rotated 210mm x 148mm + public static final int PAPER_SIZE_ENVELOPE_C6_115MM_X_162MM = 31; // Envelope C6 114mm x 162mm + public static final int PAPER_SIZE_ENVELOPE_C6_C5_114MM_X_229MM = 32; // Envelope C6/C5 114mm x 229mm + public static final int PAPER_SIZE_B4_ISO_250MM_X_353MM = 33; // B4 (ISO) 250mm x 353mm + public static final int PAPER_SIZE_B5_ISO_176MM_X_250MM = 34; // B5 (ISO) 176mm x 250mm + public static final int PAPER_SIZE_DBL_JAP_POSTCARD_ROT_148MM_X_200MM = 82; // Dbl. Jap. Postcard Rot. 148mm x 200mm + public static final int PAPER_SIZE_B6_ISO_125MM_X_176MM = 35; // B6 (ISO) 125mm x 176mm + public static final int PAPER_SIZE_ENVELOPE_ITALY_10MM_X_230MM = 36; // Envelope Italy 110mm x 230mm 84 + public static final int PAPER_SIZE_ENVELOPE_MONARCH_3_7_8_X_7_5 = 37; // Envelope Monarch 37/8" x 71/2" 85 + public static final int PAPER_SIZE_6_3_4_ENVELOPE_3_5_8_X_6_5 = 38; // 63/4 Envelope 35/8" x 61/2" 86 + public static final int PAPER_SIZE_US_STANDARD_FANFOLD_147_8_X_11 = 39; // US Standard Fanfold 147/8" x 11" 87 + public static final int PAPER_SIZE_GERMAN_STD_FANFOLD_8_5_X_12 = 40; // German Std. Fanfold 81/2" x 12" + public static final int PAPER_SIZE_GERMAN_LEGAL_FANFOLD_8_5_X_13 = 41; // German Legal Fanfold 81/2" x 13" + // public static final int PAPER_SIZE_B4_ISO_250MM_X_353MM = 42 ; // B4 (ISO) 250mm x 353mm + public static final int PAPER_SIZE_JAP_POSTCARD_100M_X_148MM = 43; // Japanese Postcard 100mm x 148mm + public static final int PAPER_SIZE_9_X_11 = 44; // 9x11 9" x 11" + public static final int PAPER_SIZE_10_X_11 = 45; // 10x11 10" x 11" + public static final int PAPER_SIZE_15_X_11 = 46; // 15x11 15" x 11" + public static final int PAPER_SIZE_ENVELOPE_INVITE_220MM_X_220MM = 47; // Envelope Invite 220mm x 220mm + public static final int PAPER_SIZE_B4_JIS_ROTATED_364MM_X_257MM = 79; // B4 (JIS) Rotated 364mm x 257mm + public static final int PAPER_SIZE_B5_JIS_ROTATED_257MMX_X_182MM = 80; // B5 (JIS) Rotated 257mm x 182mm + public static final int PAPER_SIZE_JAP_POSTCARD_ROT_148MM_X_100MM = 81; // Japanese Postcard Rot. 148mm x 100mm + public static final int PAPER_SIZE_A6_ROTATED_148MM_X_105MM = 83; // A6 Rotated 148mm x 105mm + public static final int PAPER_SIZE_B6_JIS_128MM_X_182MM = 88; // B6 (JIS) 128mm x 182mm + public static final int PAPER_SIZE_B6_JIS_ROT_182MM_X_128MM = 89; // B6 (JIS) Rotated 182mm x 128mm + public static final int PAPER_SIZE_12_X_11 = 90; // 12x11 12" x 11" + + /** + * default constructor + */ + public PrinterSettingsHandle(Boundsheet sheet) { + this.sheet = sheet; + + Iterator iter = sheet.getPrintRecs().iterator(); + while (iter.hasNext()) { + BiffRec record = (BiffRec) iter.next(); + + if (record instanceof Setup) + printerSettings = (Setup) record; + else if (record instanceof HCenter) + hCenter = (HCenter) record; + else if (record instanceof VCenter) + vCenter = (VCenter) record; + else if (record instanceof LeftMargin) + leftMargin = (LeftMargin) record; // missing in default set of records + else if (record instanceof RightMargin) + rightMargin = (RightMargin) record; // missing in default set of records + else if (record instanceof TopMargin) + topMargin = (TopMargin) record; // missing in default set of records + else if (record instanceof BottomMargin) + bottomMargin = (BottomMargin) record; // missing in default set of records + else if (record instanceof PrintGrid) + grid = (PrintGrid) record; + else if (record instanceof PrintRowCol) + headers = (PrintRowCol) record; + else if (record instanceof WsBool) + wsBool = (WsBool) record; + } + // Actually, the below comment is incorrect: do NOT do this unconditionally + //printerSettings.setNoPrintData(false); // there IS printer setup data + } + + Boundsheet sheet; + + private Setup printerSettings; + private HCenter hCenter; + private VCenter vCenter; + private LeftMargin leftMargin; + private RightMargin rightMargin; + private TopMargin topMargin; + private BottomMargin bottomMargin; + private PrintGrid grid; + private PrintRowCol headers; + private WsBool wsBool; + + // the following are unimplemented printer setting recs: + // HORIZONTALPAGEBREAKS; + // VERTICALPAGEBREAKS; + + + /** + * get the number of copies to print + * + * @return the number of copies + */ + public short getCopies() { + return printerSettings.getCopies(); + } + + + /** + * get the footer margin size in inches + * + * @return the footer margin in inches + */ + public double getFooterMargin() { + return printerSettings.getFooterMargin(); + } + + /** + * get the header margin size in inches + * + * @return the header margin in inches + */ + public double getHeaderMargin() { + return printerSettings.getHeaderMargin(); + } + + /** + * Gets the left print margin. + */ + public double getLeftMargin() { + if (leftMargin == null) { + leftMargin = new LeftMargin(); + this.sheet.addMarginRecord(leftMargin); + } + return leftMargin.getMargin(); + } + + /** + * Gets the right print margin. + */ + public double getRightMargin() { + if (rightMargin == null) { + rightMargin = new RightMargin(); + this.sheet.addMarginRecord(rightMargin); + } + return rightMargin.getMargin(); + } + + /** + * Gets the top print margin. + */ + public double getTopMargin() { + if (topMargin == null) { + topMargin = new TopMargin(); + this.sheet.addMarginRecord(topMargin); + } + return topMargin.getMargin(); + } + + /** + * Gets the bottom print margin. + */ + public double getBottomMargin() { + if (bottomMargin == null) { + bottomMargin = new BottomMargin(); + this.sheet.addMarginRecord(bottomMargin); + } + return bottomMargin.getMargin(); + } + + /** + * get the landscape orientation + * + * @return whether the print orientation is set to landscape + */ + public boolean getLandscape() { + return printerSettings.getLandscape(); + } + + /** + * get the left-to-right print orientation + * + * @return whether the print orientation is set to left-to-right + */ + public boolean getLeftToRight() { + return printerSettings.getLeftToRight(); + } + + /** + * get whether printing is in black and white + * + * @return black and white + */ + public boolean getNoColor() { + return printerSettings.getNoColor(); + } + + /** + * get whether to ignore orientation + * + * @return ignore orientation + */ + public boolean getNoOrient() { + return printerSettings.getNoOrient(); + } + + /** + * get whether printer data is missing + * + * @return whether the printer data is missing + */ + public boolean getNoPrintData() { + return printerSettings.getNoPrintData(); + } + + /** + * get the page to start printing from + * + * @return the page to start printing from + */ + public short getPageStart() { + return printerSettings.getPageStart(); + } + + /** + * Returns the paper size setting for the printer setup based on the + * following table: + * + * @return paper size + */ + public short getPaperSize() { + return printerSettings.getPaperSize(); + } + + /** + * @return whether to print Notes. + */ + public boolean getPrintNotes() { + return printerSettings.getPrintNotes(); + } + + + /** + * get the print resolution + * + * @return the printer resolution in DPI + */ + public short getResolution() { + return printerSettings.getResolution(); + } + + /** + * get the scale of the printer output in whole percentages + *

        + * ie: 25 = 25% + * + * @return the scale of printer output + */ + public short getScale() { + return printerSettings.getScale(); + } + + /** + * use custom start page for auto numbering + * + * @return Returns whether to use a custom start page + */ + public boolean getUsePage() { + return printerSettings.getUsePage(); + } + + /** + * get the vertical print resolution + * + * @return the vertical printer resolution in DPI + */ + public short getVerticalResolution() { + return printerSettings.getVerticalResolution(); + } + + /** + * Whether the sheet should be centered horizontally. + */ + public boolean isHCenter() { + return hCenter.isHCenter(); + } + + /** + * Whether the sheet should be centered vertically. + */ + public boolean isVCenter() { + return vCenter.isVCenter(); + } + + /** + * Whether the grid lines will be printed. + */ + public boolean isPrintGridLines() { + return grid.isPrintGrid(); + } + + /** + * Whether the row and column headers will be printed. + */ + public boolean isPrintRowColHeaders() { + return headers.isPrintHeaders(); + } + + /** + * Gets whether the sheet will be printed fit to some number of pages. + */ + public boolean isFitToPage() { + return wsBool.isFitToPage(); + } + + /** + * set for draft quality output + * + * @param whether to use draft quality + */ + public void setDraft(boolean b) { + printerSettings.setDraft(b); + } + + /** + * Set the output to print onto this number of pages high + *

        + * ie: setFitHeight(10) will stretch the print out to fit 10 + * pages high + * + * @param number of pages to fit to height + */ + public void setFitHeight(int numpages) { + printerSettings.setFitHeight((short) numpages); + } + + /** + * Set the output to print onto this number of pages wide + *

        + * ie: setFitWidth(10) will stretch the print out to fit 10 + * pages wide + * + * @param number of pages to fit to width + */ + public void setFitWidth(int numpages) { + printerSettings.setFitWidth((short) numpages); + } + + /** + * Sets whether the sheet will be printed fit to some number of pages. + */ + public void setFitToPage(boolean value) { + wsBool.setFitToPage(value); + } + + /** + * sets the footer margin in inches + * + * @param footer margin + */ + public void setFooterMargin(double f) { + printerSettings.setFooterMargin(f); + } + + /** + * Sets whether the page should be centered horizontally. + */ + public void setHCenter(boolean center) { + hCenter.setHCenter(center); + } + + /** + * sets the Header margin in inches + * + * @param header margin + */ + public void setHeaderMargin(double h) { + printerSettings.setHeaderMargin(h); + } + + /** + * Sets the sheet's left print margin. + */ + public void setLeftMargin(double value) { + if (leftMargin == null) { + leftMargin = new LeftMargin(); + this.sheet.addMarginRecord(leftMargin); + } + leftMargin.setMargin(value); + } + + /** + * Sets the sheet's right print margin. + */ + public void setRightMargin(double value) { + if (rightMargin == null) { + rightMargin = new RightMargin(); + this.sheet.addMarginRecord(rightMargin); + } + rightMargin.setMargin(value); + } + + /** + * Sets the sheet's top print margin. + */ + public void setTopMargin(double value) { + if (topMargin == null) { + topMargin = new TopMargin(); + this.sheet.addMarginRecord(topMargin); + } + topMargin.setMargin(value); + } + + /** + * Sets the sheet's bottom print margin. + */ + public void setBottomMargin(double value) { + if (bottomMargin == null) { + bottomMargin = new BottomMargin(); + this.sheet.addMarginRecord(bottomMargin); + } + bottomMargin.setMargin(value); + } + + /** + * set the print orientation to landscape or portrait + * + * @param landscape + */ + public void setLandscape(boolean b) { + printerSettings.setNoOrient(false); // use the orientation setting + printerSettings.setLandscape(b); + } + + /** + * set the print orientation to left-to-right printing + * + * @param leftToRight + */ + public void setLeftToRight(boolean b) { + printerSettings.setLeftToRight(b); + } + + /** + * sets the output to black and white + * + * @param noColor + */ + public void setNoColor(boolean b) { + printerSettings.setNoColor(b); + } + + /** + * set the default page to start printing from + * + * @param p + */ + public void setPageStart(short p) { + printerSettings.setPageStart(p); + } + + /** + * sets the whether to print cell notes + * + * @param printNotes whether to print Notes. + */ + public void setPrintNotes(boolean b) { + printerSettings.setPrintNotes(b); + } + + /** + * Set the output printer resolution + * + * @param resolution The resolution to set in DPI. + */ + public void setResolution(int r) { + printerSettings.setResolution((short) r); + } + + /** + * scale the printer output in whole percentages + *

        + * ie: 25 = 25% + * + * @param scale The scale to set. + */ + public void setScale(int scale) { + printerSettings.setScale((short) scale); + } + + /** + * @param usePage whether to use custom Page to start printing from. + */ + public void setUsePage(boolean usePage) { + printerSettings.setUsePage(usePage); + } + + /** + * @param verticalResolution The vertical Resolution in DPI + */ + public void setVerticalResolution(short verticalResolution) { + printerSettings.setVerticalResolution(verticalResolution); + } + + /** + * @param copies The number of copies to print + */ + public void setCopies(int copies) { + printerSettings.setCopies((short) copies); + } + + /** + * sets the paper size based on the paper size table + * + * @param the paper size index + */ + public void setPaperSize(int p) { + printerSettings.setPaperSize((short) p); + } + + /** + * get the draft quality setting + * + * @return draft quality + */ + public boolean getDraft() { + return printerSettings.getDraft(); + } + + /** + * get the number of pages to fit the printout to height + * + * @return fit to height + */ + public short getFitHeight() { + return printerSettings.getFitHeight(); + } + + /** + * get the number of pages to fit the printout to width + * + * @return fit to width + */ + public short getFitWidth() { + return printerSettings.getFitWidth(); + } + + /** + * Sets whether the sheet should be centered vertically. + */ + public void setVCenter(boolean center) { + vCenter.setVCenter(center); + } + + /** + * Sets whether to print the grid lines. + */ + public void setPrintGrid(boolean print) { + grid.setPrintGrid(print); + } + + /** + * Sets whether to print the row and column headers. + */ + public void setPrintRowColHeaders(boolean print) { + headers.setPrintHeaders(print); + } + + /** + * Gets the range specifying the titles printed on each page. + */ + public String getTitles() { + Name range = sheet.getName("Built-in: PRINT_TITLES"); + if (range == null) return null; + return range.getExpressionString(); + } + + /** + * Sets the range specifying the titles printed on each page. + * The reference for the row(s) to repeat e.g. $1:$1 for row 1 + * For Columns, type the reference to the column or columns that + * you want to set as a title e.g. $A:$B for columns A and B + */ + // note: MUST be in $ROW:$ROW or $COL:$COL format, for both + // can be $R:$R, $C:$C for both + public void setTitles(String range) { + Name name = sheet.getName("Built-in: PRINT_TITLES"); + if (name == null) try { + name = new Name(sheet.getWorkBook(), "Print_Titles"); + name.setBuiltIn((byte) 0x07); //do before setNewScope as it blows out itab + name.setNewScope(sheet.getSheetNum() + 1); + } catch (WorkSheetNotFoundException e) { + // This shouldn't be possible. + throw new Error("sheet not found re-scoping name"); + } + // pre-process range to ensure in proper format, ensure all absolute ($) refs + + // handle wholerow-wholecol refs + complex ranges + if (range == null) return; // TODO: Do what?? remove?? + String[] ranges = range.split(","); + range = ""; + for (int i = 0; i < ranges.length; i++) { + if (i > 0) // concatenate terms into one ptgmemfunc-style expression + range += ","; + String r = ""; + int[] rc = ExcelTools.getRangeCoords(ranges[i]); + if (rc[0] == rc[2]) // varies by column + r = "$" + ExcelTools.getAlphaVal(rc[1]) + ":$" + ExcelTools.getAlphaVal(rc[3]); + if (rc[1] == rc[3]) {// varies by row + r = "$" + rc[0] + ":$" + rc[2]; + } + range += sheet.getSheetName() + "!" + r; + } + name.setLocation(range); + } + } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/RowHandle.java b/src/main/java/io/starter/OpenXLS/RowHandle.java index 655aa00..474c89b 100644 --- a/src/main/java/io/starter/OpenXLS/RowHandle.java +++ b/src/main/java/io/starter/OpenXLS/RowHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,506 +22,535 @@ */ package io.starter.OpenXLS; -import static io.starter.OpenXLS.JSONConstants.*; - +import io.starter.formats.XLS.Font; import io.starter.formats.XLS.*; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; - -import java.awt.Toolkit; -import java.util.*; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; -/** The RowHandle provides access to a Worksheet Row and its Cells. -
        - Use the RowHandle to work with individual Rows in an XLS file. -
        - With a RowHandle you can: -

        - get a handle to the Cells in a row
        - set the default formatting for a Row
        -
        - - @see WorkBookHandle - @see WorkSheetHandle - @see FormulaHandle -*/ +import static io.starter.OpenXLS.JSONConstants.*; + + +/** + * The RowHandle provides access to a Worksheet Row and its Cells. + *
        + * Use the RowHandle to work with individual Rows in an XLS file. + *
        + * With a RowHandle you can: + *
        + * get a handle to the Cells in a row
        + * set the default formatting for a Row
        + *
        + * + * @see WorkBookHandle + * @see WorkSheetHandle + * @see FormulaHandle + */ public class RowHandle { - // FYI: do not change lightly -- these match Excel 2007 almost exactly + // FYI: do not change lightly -- these match Excel 2007 almost exactly public static int ROW_HEIGHT_DIVISOR = 17; - - public Row myRow; + + public Row myRow; private FormatHandle formatter; private WorkBook wbh; private WorkSheetHandle wsh; - - protected RowHandle(Row c, WorkSheetHandle ws){ + + protected RowHandle(Row c, WorkSheetHandle ws) { myRow = c; wbh = ws.getWorkBook(); wsh = ws; - } - - /** - * Return the row height of an existing row. - * - * These values are returned in twips, 1/20th of a character. - * - @return int Height of Row in twips - */ - public int getHeight(){ + } + + /** + * Return the row height of an existing row. + *

        + * These values are returned in twips, 1/20th of a character. + * + * @return int Height of Row in twips + */ + public int getHeight() { return myRow.getRowHeight(); } - + /** * returns the row height in Excel units, which depends upon the default font *
        in Arial 10 pt, standard row height is 12.75 points + * * @return int row height in Excel units */ public int getHeightInChars() { - return myRow.getRowHeight()/20; + return myRow.getRowHeight() / 20; } - + /** - * Return the row height of an existing row. - * - * These values are returned in twips, 1/20th of a character. - * + * Return the row height of an existing row. + *

        + * These values are returned in twips, 1/20th of a character. + * * @param sheet * @param row * @return */ public static int getHeight(Boundsheet sheet, int row) { - int h= 255; - try { - Row r = sheet.getRowByNumber(row); - if(r!=null) - h= r.getRowHeight(); - } catch (Exception e) { // exception if no row defined - h= 255; // default - } - return h; - } - - /** sets the row height in Excel units. - @param double i - row height value in Excel units + int h = 255; + try { + Row r = sheet.getRowByNumber(row); + if (r != null) + h = r.getRowHeight(); + } catch (Exception e) { // exception if no row defined + h = 255; // default + } + return h; + } + + /** + * sets the row height in Excel units. + * + * @param double i - row height value in Excel units */ public void setHeightInChars(int newHeight) { - this.setHeight(newHeight*20); // 20090506 KSC: apparently it's in twips ?? 1/20 of a point + this.setHeight(newHeight * 20); // 20090506 KSC: apparently it's in twips ?? 1/20 of a point } - + /** * sets the row height to auto fit *
        When the row height is set manually, autofit is automatically turned off */ public void setRowHeightAutoFit() { - // this.myRow.setUnsynched(false); // firstly, set so excel - Collection ct = myRow.getCells(); - Iterator it = ct.iterator(); - double h= 0; - int dpi = Toolkit.getDefaultToolkit().getScreenResolution(); // 96 is "small", 120 dpi is "lg" - // 1 point= 1/72 of an inch - // 1 twip= 1 twip= 1/20 of a point - // this should be a pretty good pixels/twips conversion factor. - double factorTwip = (double)dpi / 72 / 20; // .06 is "normal" - // factorZero is width of 0 char in default font. If assume Arial 10 pt, it is 6 + 1= 7 - double factorZero= 7; //java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f).charWidth('0') + 1; - - while(it.hasNext()) { - XLSRecord cellrec= (XLSRecord)it.next(); - try { - double newH= 255; // default row height - try { - Font ef= cellrec.getXfRec().getFont(); - int style= java.awt.Font.PLAIN; - if (ef.getBold()) - style |= java.awt.Font.BOLD; - if (ef.getItalic()) - style |= java.awt.Font.ITALIC; - java.awt.Font f= new java.awt.Font(ef.getFontName(), style, (int)ef.getFontHeightInPoints()); - String s= cellrec.getStringVal(); - if (!cellrec.getXfRec().getWrapText()) // normal case, no wrap - newH= StringTool.getApproximateHeight(f, s, Double.MAX_VALUE); - else { // wrap to column width - // convert column width to pixels - // factorZero is usually 7 // double factorZero= java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f).charWidth('0') + 1; - double cw= ColHandle.getWidth(this.wsh.getBoundsheet(), cellrec.getColNumber())/256.0; - newH= StringTool.getApproximateHeight(f, s, cw*factorZero); - } + // this.myRow.setUnsynched(false); // firstly, set so excel + Collection ct = myRow.getCells(); + Iterator it = ct.iterator(); + double h = 0; + int dpi = Toolkit.getDefaultToolkit().getScreenResolution(); // 96 is "small", 120 dpi is "lg" + // 1 point= 1/72 of an inch + // 1 twip= 1 twip= 1/20 of a point + // this should be a pretty good pixels/twips conversion factor. + double factorTwip = (double) dpi / 72 / 20; // .06 is "normal" + // factorZero is width of 0 char in default font. If assume Arial 10 pt, it is 6 + 1= 7 + double factorZero = 7; //java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f).charWidth('0') + 1; + + while (it.hasNext()) { + XLSRecord cellrec = (XLSRecord) it.next(); + try { + double newH = 255; // default row height + try { + Font ef = cellrec.getXfRec().getFont(); + int style = java.awt.Font.PLAIN; + if (ef.getBold()) + style |= java.awt.Font.BOLD; + if (ef.getItalic()) + style |= java.awt.Font.ITALIC; + java.awt.Font f = new java.awt.Font(ef.getFontName(), style, (int) ef.getFontHeightInPoints()); + String s = cellrec.getStringVal(); + if (!cellrec.getXfRec().getWrapText()) // normal case, no wrap + newH = StringTool.getApproximateHeight(f, s, Double.MAX_VALUE); + else { // wrap to column width + // convert column width to pixels + // factorZero is usually 7 // double factorZero= java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f).charWidth('0') + 1; + double cw = ColHandle.getWidth(this.wsh.getBoundsheet(), cellrec.getColNumber()) / 256.0; + newH = StringTool.getApproximateHeight(f, s, cw * factorZero); + } // this doesn't work correctly newH/=factorTwip; // pixels * twips/pixels == twips) - newH*=20; // this is better ... - } catch (Exception e) { - Logger.logErr("RowHandle.setRowHeightAutoFit: " + e.toString()); - } + newH *= 20; // this is better ... + } catch (Exception e) { + Logger.logErr("RowHandle.setRowHeightAutoFit: " + e.toString()); + } - h= Math.max(h, newH); - } catch (Exception e) { } - } - if (h > 0) - this.myRow.setRowHeight((int) Math.ceil(h)); + h = Math.max(h, newH); + } catch (Exception e) { + } + } + if (h > 0) + this.myRow.setRowHeight((int) Math.ceil(h)); } + /** - * Sets the row height in twips (1/20th of a point) + * Sets the row height in twips (1/20th of a point) + * * @param newHeight */ - public void setHeight(int newHeight){ - /* 20080604 KSC: if an image falls upon this column, + public void setHeight(int newHeight) { + /* 20080604 KSC: if an image falls upon this column, * adjust image width so that it does not change */ - ArrayList iAdjust= new ArrayList(); - ImageHandle[] images= myRow.getSheet().getImages(); - if(images!=null) { - // for each image that falls over this row, trap index + original width -- to be reset after setting row height - for (int z= 0; z < images.length; z++) { - ImageHandle ih= images[z]; - int r0= ih.getRow(); - int r1= ih.getRow1(); - int row= myRow.getRowNumber(); - if (row>=r0 && row<=r1) { - int h= ih.getHeight(); - iAdjust.add(new int[]{z,h}); - } - } - } - myRow.setRowHeight(newHeight); - for (int z= 0; z < iAdjust.size(); z++) { - ImageHandle ih= images[((int[]) iAdjust.get(z))[0]]; - ih.setHeight(((int[]) iAdjust.get(z))[1]); - } - - } - + ArrayList iAdjust = new ArrayList(); + ImageHandle[] images = myRow.getSheet().getImages(); + if (images != null) { + // for each image that falls over this row, trap index + original width -- to be reset after setting row height + for (int z = 0; z < images.length; z++) { + ImageHandle ih = images[z]; + int r0 = ih.getRow(); + int r1 = ih.getRow1(); + int row = myRow.getRowNumber(); + if (row >= r0 && row <= r1) { + int h = ih.getHeight(); + iAdjust.add(new int[]{z, h}); + } + } + } + myRow.setRowHeight(newHeight); + for (int z = 0; z < iAdjust.size(); z++) { + ImageHandle ih = images[iAdjust.get(z)[0]]; + ih.setHeight(iAdjust.get(z)[1]); + } + + } + /** * Determines if the row passes through * a vertical merge range - * + * * @return */ public boolean containsVerticalMergeRange() { CellHandle[] c = this.getCells(); - for (int i=0;i1)return true; - }catch (Exception e) {}; + if (cr.getRows().length > 1) return true; + } catch (Exception e) { + } } } return false; } - /** sets the default format id for the Row's Cells - - @param int Format Id for all Cells in Row - */ - public void setFormatId(int i){ + + /** + * sets the default format id for the Row's Cells + * + * @param int Format Id for all Cells in Row + */ + public void setFormatId(int i) { myRow.setIxfe(i); } - - /** Gets the FormatHandle for this Row. - - @return FormatHandle - a Format object to apply to this Row -*/ - public FormatHandle getFormatHandle(){ - if(this.formatter==null)this.setFormatHandle(); + + /** + * Gets the FormatHandle for this Row. + * + * @return FormatHandle - a Format object to apply to this Row + */ + public FormatHandle getFormatHandle() { + if (this.formatter == null) this.setFormatHandle(); return this.formatter; } - - + + /** * Set up the format handle for this row - * */ private void setFormatHandle() { - if(formatter!=null)return; - formatter = new FormatHandle(wbh, this.getFormatId()); - formatter.setRowHandle(this); + if (formatter != null) return; + formatter = new FormatHandle(wbh, this.getFormatId()); + formatter.setRowHandle(this); } - - /** gets the current default row format id. May be overwritten by contained cells - * + + /** + * gets the current default row format id. May be overwritten by contained cells + * * @return format id of row */ - public int getFormatId(){ - if(myRow.getExplicitFormatSet())return myRow.getIxfe(); + public int getFormatId() { + if (myRow.getExplicitFormatSet()) return myRow.getIxfe(); return this.getWorkBook().getWorkBook().getDefaultIxfe(); } - /** Returns the array of Cells in this Row - - @return Cell[] all Cells in this Row - @param cache cellhandles flag - */ - public CellHandle[] getCells(boolean cached){ - Collection ct = myRow.getCells(); - Iterator it = ct.iterator(); - CellHandle[] ch = new CellHandle[ct.size()]; - int t = 0; - Mulblank aMul= null; - short c= -1; - while(it.hasNext()) { - BiffRec rc = (BiffRec)it.next(); - try{ // use cache of Cellhandles! - if (rc.getOpcode()!=XLSConstants.MULBLANK) { - ch[t] = this.wsh.getCell(rc.getRowNumber(), rc.getColNumber(), cached); - } else { - // handle Mulblanks: ref a range of cells; to get correct cell address, - // traverse thru range and set cellhandle ref to correct column - if (rc==aMul) { - c++; - } else { - aMul= (Mulblank)rc; - c= (short)aMul.getColFirst(); - } - ch[t]= this.wsh.getCell(rc.getRowNumber(), c, cached); - } - }catch(CellNotFoundException cnfe){ - rc.setXFRecord(); - ch[t] = new CellHandle(rc,null); - ch[t].setWorkSheetHandle(null); //TODO: implement if causing grief -jm - if (rc.getOpcode()==XLSConstants.MULBLANK) { - // handle Mulblanks: ref a range of cells; to get correct cell address, - // traverse thru range and set cellhandle ref to correct column - if (rc==aMul) { - c++; - } else { - aMul= (Mulblank)rc; - c= (short)aMul.getColFirst(); - } - ch[t].setBlankRef(c); // for Mulblank use only -sets correct column reference for multiple blank cells ... - } - - } - t++; - } - return ch; - } - - /** Returns the array of Cells in this Row - - @return Cell[] all Cells in this Row - */ - public CellHandle[] getCells(){ - return getCells(false); // don't use cache - } - - + /** + * Returns the array of Cells in this Row + * + * @param cache cellhandles flag + * @return Cell[] all Cells in this Row + */ + public CellHandle[] getCells(boolean cached) { + Collection ct = myRow.getCells(); + Iterator it = ct.iterator(); + CellHandle[] ch = new CellHandle[ct.size()]; + int t = 0; + Mulblank aMul = null; + short c = -1; + while (it.hasNext()) { + BiffRec rc = (BiffRec) it.next(); + try { // use cache of Cellhandles! + if (rc.getOpcode() != XLSConstants.MULBLANK) { + ch[t] = this.wsh.getCell(rc.getRowNumber(), rc.getColNumber(), cached); + } else { + // handle Mulblanks: ref a range of cells; to get correct cell address, + // traverse thru range and set cellhandle ref to correct column + if (rc == aMul) { + c++; + } else { + aMul = (Mulblank) rc; + c = (short) aMul.getColFirst(); + } + ch[t] = this.wsh.getCell(rc.getRowNumber(), c, cached); + } + } catch (CellNotFoundException cnfe) { + rc.setXFRecord(); + ch[t] = new CellHandle(rc, null); + ch[t].setWorkSheetHandle(null); //TODO: implement if causing grief -jm + if (rc.getOpcode() == XLSConstants.MULBLANK) { + // handle Mulblanks: ref a range of cells; to get correct cell address, + // traverse thru range and set cellhandle ref to correct column + if (rc == aMul) { + c++; + } else { + aMul = (Mulblank) rc; + c = (short) aMul.getColFirst(); + } + ch[t].setBlankRef(c); // for Mulblank use only -sets correct column reference for multiple blank cells ... + } + + } + t++; + } + return ch; + } + + /** + * Returns the array of Cells in this Row + * + * @return Cell[] all Cells in this Row + */ + public CellHandle[] getCells() { + return getCells(false); // don't use cache + } + + /** * Get the JSON object for this row. - * + * * @return */ public String getJSON() { - return getJSON( 255 ).toString(); + return getJSON(255).toString(); } - - public JSONObject getJSON (int maxcols) { - JSONObject theRange = new JSONObject(); + + public JSONObject getJSON(int maxcols) { + JSONObject theRange = new JSONObject(); JSONArray cells = new JSONArray(); try { - theRange.put(JSON_ROW, getRowNumber()); - - theRange.put( JSON_ROW_BORDER_TOP, getHasAnyThickTopBorder() ); - theRange.put( JSON_ROW_BORDER_BOTTOM, getHasAnyBottomBorder() ); - if (getFormatId()!=getWorkBook().getWorkBook().getDefaultIxfe())theRange.put("xf", getFormatId()); - theRange.put(JSON_HEIGHT, (getHeight()/ROW_HEIGHT_DIVISOR)+5); // the default is TOO SMALL! - CellHandle[] chandles = getCells(false); - for (int i=0;i=maxcols) { - i=chandles.length; - }else if (thisCell.getCell().getOpcode() == XLSRecord.MULBLANK){ - Mulblank mb = (Mulblank) thisCell.getCell(); - ArrayList columns = mb.getColReferences(); - for (int x=0; x= maxcols) { + i = chandles.length; + } else if (thisCell.getCell().getOpcode() == XLSRecord.MULBLANK) { + Mulblank mb = (Mulblank) thisCell.getCell(); + ArrayList columns = mb.getColReferences(); + for (int x = 0; x < columns.size(); x++) { + thisCell.setBlankRef(columns.get(x)); + thisCell.getCell().setCol(columns.get(x).shortValue()); + JSONObject result = new JSONObject(); + thisCell.getCellAddress(); + Object v = ""; + try { + v = thisCell.getJSONObject(); + } catch (Exception exz) { + Logger.logErr("Error getting Row cell value " + thisCell.getCellAddress() + " JSON: " + exz); + v = "ERROR FETCHING VALUE for:" + thisCell.getCellAddress(); + } + if (v != null) { + result.put(JSON_CELL, v); + cells.put(result); + } + } + } else { JSONObject result = new JSONObject(); - Object v = "ERROR FETCHING VALUE for:"+thisCell.getCellAddress(); - try{ + Object v = "ERROR FETCHING VALUE for:" + thisCell.getCellAddress(); + try { v = thisCell.getJSONObject(); - }catch(Exception exz){ - Logger.logErr("Error getting Row cell value "+thisCell.getCellAddress()+" JSON: " + exz); + } catch (Exception exz) { + Logger.logErr("Error getting Row cell value " + thisCell.getCellAddress() + " JSON: " + exz); } - if(v!=null){ - result.put(JSON_CELL, v); - cells.put(result); + if (v != null) { + result.put(JSON_CELL, v); + cells.put(result); } } } } theRange.put(JSON_CELLS, cells); - }catch(JSONException e) { + } catch (JSONException e) { Logger.logErr("Error getting Row JSON: " + e); - } + } return theRange; } - - /** Returns the String representation of this Row - * + + /** + * Returns the String representation of this Row */ - public String toString(){ - return myRow.toString(); + public String toString() { + return myRow.toString(); } - + /** * Returns the row number of this RowHandle */ public int getRowNumber() { return myRow.getRowNumber(); } - - /** Set whether the row is collapsed. - * Will hide the current row, and all contiguous rows - * with the same outline level. - * - * @param b - */ - public void setCollapsed(boolean b){ - myRow.setCollapsed(b); - } - - /** Set whether the row is hidden - * - * @param b - */ - public void setHidden(boolean b){ - myRow.setHidden(b); - } - - /** Set the Outline level (depth) of the row - * - * @param x - */ - public void setOutlineLevel(int x){ - myRow.setOutlineLevel(x); - } - - /** Returns the Outline level (depth) of the row - * @return - */ - public int getOutlineLevel() { - return myRow.getOutlineLevel(); - } - - /** Returns whether the row is collapsed - * @return - */ - public boolean isCollapsed() { - return myRow.isCollapsed(); - } - - /** Returns whether the row is hidden - * @return - */ - public boolean isHidden() { - return myRow.isHidden(); - } - - - /** - * true if row height has been altered from default - * i.e. set manually - * @return - */ - public boolean isAlteredHeight() { - return myRow.isAlteredHeight(); - } - - public void setBackgroundColor(java.awt.Color colr){ - setFormatHandle(); - formatter.setCellBackgroundColor(colr); - } - - /** - * returns true if there is a Thick Top border set on the row - */ - public boolean getHasThickTopBorder(){ - return myRow.getHasThickTopBorder(); - } - - /** - * returns true if there is a Thick Bottom border set on the row - */ + + /** + * Set whether the row is collapsed. + * Will hide the current row, and all contiguous rows + * with the same outline level. + * + * @param b + */ + public void setCollapsed(boolean b) { + myRow.setCollapsed(b); + } + + /** + * Set whether the row is hidden + * + * @param b + */ + public void setHidden(boolean b) { + myRow.setHidden(b); + } + + /** + * Set the Outline level (depth) of the row + * + * @param x + */ + public void setOutlineLevel(int x) { + myRow.setOutlineLevel(x); + } + + /** + * Returns the Outline level (depth) of the row + * + * @return + */ + public int getOutlineLevel() { + return myRow.getOutlineLevel(); + } + + /** + * Returns whether the row is collapsed + * + * @return + */ + public boolean isCollapsed() { + return myRow.isCollapsed(); + } + + /** + * Returns whether the row is hidden + * + * @return + */ + public boolean isHidden() { + return myRow.isHidden(); + } + + + /** + * true if row height has been altered from default + * i.e. set manually + * + * @return + */ + public boolean isAlteredHeight() { + return myRow.isAlteredHeight(); + } + + public void setBackgroundColor(java.awt.Color colr) { + setFormatHandle(); + formatter.setCellBackgroundColor(colr); + } + + /** + * returns true if there is a Thick Top border set on the row + */ + public boolean getHasThickTopBorder() { + return myRow.getHasThickTopBorder(); + } + + /** + * returns true if there is a Thick Bottom border set on the row + */ public boolean getHasThickBottomBorder() { - return myRow.getHasThickBottomBorder(); + return myRow.getHasThickBottomBorder(); } - + /** * returns true if there is a thick top or thick or medium bottom border on previoous row - * - * Not useful for public API - */ - public boolean getHasAnyThickTopBorder(){ - return myRow.getHasAnyThickTopBorder(); + *

        + * Not useful for public API + */ + public boolean getHasAnyThickTopBorder() { + return myRow.getHasAnyThickTopBorder(); } - + /** * Additional space below the row. This flag is set, if the - lower border of at least one cell in this row or if the upper - border of at least one cell in the row below is formatted with - a medium or thick line style. Thin line styles are not taken - into account. - - Usage of this method is primarily for UI applications, and is not - needed for standard OpenXLS functionality + * lower border of at least one cell in this row or if the upper + * border of at least one cell in the row below is formatted with + * a medium or thick line style. Thin line styles are not taken + * into account. + *

        + * Usage of this method is primarily for UI applications, and is not + * needed for standard OpenXLS functionality */ - public boolean getHasAnyBottomBorder(){ - return myRow.getHasAnyBottomBorder(); + public boolean getHasAnyBottomBorder() { + return myRow.getHasAnyBottomBorder(); } - + /** * sets this row to have a thick top border - */ - public void setHasThickTopBorder(boolean hasBorder){ - myRow.setHasThickTopBorder(hasBorder); + */ + public void setHasThickTopBorder(boolean hasBorder) { + myRow.setHasThickTopBorder(hasBorder); } - + /** * sets this row to have a thick bottom border */ - public void setHasThickBottomBorder(boolean hasBorder){ - myRow.setHasThickBottomBorder(hasBorder); - } - - /** - * return the min/max columns defined for this row - * @return - */ - public int[] getColDimensions() { return myRow.getColDimensions(); } - - public WorkBook getWorkBook() { - return this.wbh; - } - - public WorkSheetHandle getWorkSheetHandle() { - return this.wsh; - } + public void setHasThickBottomBorder(boolean hasBorder) { + myRow.setHasThickBottomBorder(hasBorder); + } + + /** + * return the min/max columns defined for this row + * + * @return + */ + public int[] getColDimensions() { + return myRow.getColDimensions(); + } + + public WorkBook getWorkBook() { + return this.wbh; + } + + public WorkSheetHandle getWorkSheetHandle() { + return this.wsh; + } } diff --git a/src/main/java/io/starter/OpenXLS/ValidationHandle.java b/src/main/java/io/starter/OpenXLS/ValidationHandle.java index 6951218..a164cce 100644 --- a/src/main/java/io/starter/OpenXLS/ValidationHandle.java +++ b/src/main/java/io/starter/OpenXLS/ValidationHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -68,618 +68,569 @@ * * * - * "http://starter.io">Starter Inc. + * "http://starter.io">Starter Inc. */ public class ValidationHandle implements Handle { - private Dv myDv; - - // static shorts for setting validation type - public static final byte VALUE_ANY = 0x0; - public static final byte VALUE_INTEGER = 0x1; - public static final byte VALUE_DECIMAL = 0x2; - public static final byte VALUE_USER_DEFINED_LIST = 0x3; - public static final byte VALUE_DATE = 0x4; - public static final byte VALUE_TIME = 0x5; - public static final byte VALUE_TEXT_LENGTH = 0x6; - public static final byte VALUE_FORMULA = 0x7; - - // static shorts for setting action on error - public static byte ERROR_STOP = 0x0; - public static byte ERROR_WARN = 0x1; - public static byte ERROR_INFO = 0x2; - - // static shorts for setting conditions on validation - public static final byte CONDITION_BETWEEN = 0x0; - public static final byte CONDITION_NOT_BETWEEN = 0x1; - public static final byte CONDITION_EQUAL = 0x2; - public static final byte CONDITION_NOT_EQUAL = 0x3; - public static final byte CONDITION_GREATER_THAN = 0x4; - public static final byte CONDITION_LESS_THAN = 0x5; - public static final byte CONDITION_GREATER_OR_EQUAL = 0x6; - public static final byte CONDITION_LESS_OR_EQUAL = 0x7; - - // static shorts for setting IME modes - public static short IME_MODE_NO_CONTROL = 0x0; // No control for IME. - // (default) - public static short IME_MODE_ON = 0x1; // IME is on. - public static short IME_MODE_OFF = 0x2; // IME is off. - public static short IME_MODE_DISABLE = 0x3;// IME is disabled. - public static short IME_MODE_HIRAGANA = 0x4;// IME is in hiragana input - // mode. - public static short IME_MODE_KATAKANA = 0x5;// IME is in full-width katakana - // input mode. - public static short IME_MODE_KATALANA_HALF = 0x6;// IME is in half-width - // katakana input mode. - public static short IME_MODE_FULL_WIDTH_ALPHA = 0x7;// IME is in full-width - // alphanumeric input - // mode - public static short IME_MODE_HALF_WIDTH_ALPHA = 0x8;// IME is in half-width - // alphanumeric input - // mode. - public static short IME_MODE_FULL_WIDTH_HANKUL = 0x9;// IME is in full-width - // Hankul input mode - public static short IME_MODE_HALF_WIDTH_HANKUL = 0x10;// IME is in - // half-width Hankul - // input mode. - - public static String[] CONDITIONS = { "between", "notBetween", "equal", - "notEqual", "greaterThan", "lessThan", "greaterOrEqual", - "lessOrEqual" }; - - /** - * Get the byte representing the condition type string passed in. Options - * are' "between", "notBetween", "equal", "notEqual", "greaterThan", - * "lessThan", "greaterOrEqual", "lessOrEqual" - * - * - * - * - * @return - */ - public static byte getConditionNumber(String conditionType) { - for (int i = 0; i < CONDITIONS.length; i++) { - if (conditionType.equalsIgnoreCase(CONDITIONS[i])) - return (byte) i; - } - return -1; - } - - public static String[] VALUE_TYPE = { "any", "integer", "decimal", - "userDefinedList", "date", "time", "textLength", "formula" }; - - /** - * Get the byte representing the value type string passed in. Options are' - * "any", "integer", "decimal", "userDefinedList", "date", "time", - * "textLength", "formula" - * - * - * - * - * @return - */ - public static byte getValueNumber(String valueType) { - for (int i = 0; i < VALUE_TYPE.length; i++) { - if (valueType.equalsIgnoreCase(VALUE_TYPE[i])) - return (byte) i; - } - return -1; - } - - /** - * Create a new Validation for the input cell range. - * - * Validations are specific to worksheets. - * - * - * @param cellRange - * = cell or range of cells, example "A1", "A1:A10" - * - * protected ValidationHandle(String cellRange, WorkSheetHandle - * wsh) { - * - * } - */ - /** - * For internal use only. Creates a Validation Handle based of the Dv passed - * in. - * - * - * @param dv - */ - public ValidationHandle(Dv dv) { - myDv = dv; - } - - /** - * Return the range of data this ValidationHandle refers to as a string Will - * not contain worksheet identifier, as ValidationHandles are specific to a - * worksheet. If the Validation effects multiple ranges they are separated - * by a space - * - * - * @return ptgRef.toString() - */ - public String getRange() { - String[] s = myDv.getRanges(); - String out = ""; - for (int i = 0; i < s.length; i++) { - if (i > 0) - out += " "; - out += s[i]; - } - return out; - } - - /** - * Determine if the value passed in is valid for this validation - * - * - * @param value - * @return - */ - public boolean isValid(Object value) throws ValidationException { - return myDv.isValid(value); - } - - /** - * Determine if the value passed in is valid for this validation - * - * - * @param value - * @return - */ - public boolean isValid(Object value, boolean throwException) - throws RuntimeException { - if (throwException) { - try { - return myDv.isValid(value); - } catch (Exception e) { - try { - throw e; - } catch (Exception ex) { - Logger.logErr("Error getting isValid " + ex.toString()); - } - } - ; - } - try { - return myDv.isValid(value); - } catch (Exception e) { - return false; - } - } - - /** - * Set the range this ValidationHandle refers to. Pass in a range string, - * sans worksheet. - * - * This range will overwrite all other ranges this ValidationHandle refers - * to. - * - * In order to handle multiple ranges, use the addRange(String range) method - * - * - * @param range - * = standard excel range without worksheet information ("A1" or - * "A1:A10") - */ - public void setRange(String range) { - myDv.setRange(range); - } - - /** - * Adds an additional range to the existing ranges in this validationhandle - * - * - * @param range - */ - public void addRange(String range) { - myDv.addRange(range); - } - - /** - * Get the text from the error box. - * - * - * @return - */ - public String getErrorBoxText() { - return myDv.getErrorBoxText(); - } - - /** - * Set the text for the error box - * - * - * @param textError - */ - public void setErrorBoxText(String textError) { - myDv.setErrorBoxText(textError); - } - - /** - * Return the text in the prompt box - * - * - * @return - */ - public String getPromptBoxText() { - return myDv.getPromptBoxText(); - } - - /** - * Set the text for the prompt box - * - * - * @param text - */ - public void setPromptBoxText(String text) { - myDv.setPromptBoxText(text); - } - - /** - * Set the title for the error box - * - * - * @param textError - */ - public void setErrorBoxTitle(String textError) { - myDv.setErrorBoxTitle(textError); - } - - /** - * Get the title from the error box - * - * - * @return - */ - public String getErrorBoxTitle() { - return myDv.getErrorBoxTitle(); - } - - /** - * Return the title in the prompt box - * - * - * @return - */ - public String getPromptBoxTitle() { - return myDv.getPromptBoxTitle(); - } - - /** - * Set the title for the prompt box - * - * - * @param text - */ - public void setPromptBoxTitle(String text) { - myDv.setPromptBoxTitle(text); - } - - /** - * Return a byte representing the error style for this ValidationHandle - * - * These map to the static final ints ERROR_* in ValidationHandle - * - * - * @return - */ - public byte getErrorStyle() { - return myDv.getErrorStyle(); - } - - /** - * Set the error style for this ValidationHandle record - * - * These map to the static final ints ERROR_* from ValidationHandle - * - * - * @return - */ - public void setErrorStyle(byte errstyle) { - myDv.setErrorStyle(errstyle); - } - - /** - * Get the IME mode for this validation - * - * - * @return - */ - public short getIMEMode() { - return myDv.getIMEMode(); - } - - /** - * set the IME mode for this validation - * - * - * @return - */ - public void setIMEMode(short mode) { - myDv.setIMEMode(mode); - } - - /** - * Allow blank cells in the validation area? - * - * - * @return - */ - public boolean isAllowBlank() { - return myDv.isAllowBlank(); - } - - /** - * Allow blank cells in the validation area? - * - * - * @return - */ - public void setAllowBlank(boolean allowBlank) { - myDv.setAllowBlank(allowBlank); - } - - /** - * Get the first condition of the validation as a string representation - * - * - * @return - */ - public String getFirstCondition() { - return myDv.getFirstCond(); - } - - /** - * Set the first condition of the validation - * - * This value must conform to the Value Type of this validation or - * unexpected results may occur. For example, entering a string - * representation of a date here will not work if your validation is an - * integer... - * - * A java.util.Date object can also be passed in. This value will be - * translated into an integer as excel stores dates. If you need to - * manipulate/retrieve this value later utilize the DateConverter tool to - * transform the value - * - *String passed in should be a vaild XLS formula. Does not need to include - * the "=" - * - * Types of conditions Integer values Decimal values User defined list Date - * Time Text length Formula - * - * Be sure that your validation type (getValidationType()) matches the type - * of data. - * - * - * @param firstCond - * = the first condition for the validation - */ - public void setFirstCondition(Object firstCond) { - String setval = firstCond.toString(); - if (firstCond instanceof java.util.Date) { - double d = DateConverter.getXLSDateVal((java.util.Date) firstCond); - setval = d + ""; - } - myDv.setFirstCond(setval); - } - - /** - * Get the second condition of the validation as a string representation - * - * - * @return - */ - public String getSecondCondition() { - return myDv.getSecondCond(); - } - - /** - * Set the first condition of the validation utilizing a string. This value - * must conform to the Value Type of this validation or unexpected results - * may occur. For example, entering a string representation of a date here - * will not work if your validation is an integer... - * - *String passed in should be a vaild XLS formula. Does not need to include - * the "=" - * - * Types of conditions Integer values Decimal values User defined list Date - * Time Text length Formula - * - * Be sure that your validation type (getValidationType()) matches the type - * of data. - * - * - * @return - */ - public void setSecondCondition(Object secondCond) { - String setval = secondCond.toString(); - if (secondCond instanceof java.util.Date) { - double d = DateConverter.getXLSDateVal((java.util.Date) secondCond); - setval = d + ""; - } - myDv.setSecondCond(setval); - } - - /** - * Show error box if invalid values entered? - * - * - * @return - */ - public boolean isShowErrorMsg() { - return myDv.isShowErrorMsg(); - } - - /** - * Set show error box if invalid values entered? - * - * - * @return - */ - public void setShowErrorMsg(boolean showErrMsg) { - myDv.setShowErrMsg(showErrMsg); - } - - /** - * Show prompt box if cell selected? - * - * - * @return - */ - public boolean getShowInputMsg() { - return myDv.getShowInputMsg(); - } - - /** - * Set show prompt box if cell selected? - * - * @param showInputMsg - */ - public void setShowInputMsg(boolean showInputMsg) { - myDv.setShowInputMsg(showInputMsg); - } - - /** - * In list type validity the string list is explicitly given in the formula - * - * - * @return boolean - */ - public boolean isStrLookup() { - return myDv.isStrLookup(); - } - - /** - * In list type validity the string list is explicitly given in the formula - * - * - * @param strLookup - */ - public void setStrLookup(boolean strLookup) { - myDv.setStrLookup(strLookup); - } - - /** - * Suppress the drop down arrow in list type validity - * - * - * @return boolean - */ - public boolean isSuppressCombo() { - return myDv.isSuppressCombo(); - } - - /** - * Suppress the drop down arrow in list type validity - * - * - */ - public void setSuppressCombo(boolean suppressCombo) { - myDv.setSuppressCombo(suppressCombo); - } - - /** - *Get the type operator of this validation as a byte. - * - *These bytes map to the CONDITION_* static values in ValidationHandle - * - * - * @return - */ - public byte getTypeOperator() { - return myDv.getTypeOperator(); - } - - /** - *set the type operator of this validation as a byte. - * - *These bytes map to the CONDITION_* static values in ValidationHandle - * - * - */ - public void setTypeOperator(byte typOperator) { - myDv.setTypeOperator(typOperator); - } - - /** - *Get the validation type of this ValidationHandle as a byte - * - *These bytes map to the VALUE_* static values in ValidationHandle - * - * - * @return - */ - public byte getValidationType() { - return myDv.getValType(); - } - - /** - *Set the validation type of this ValidationHandle as a byte - * - *These bytes map to the VALUE_* static values in ValidationHandle - * - * - */ - public void setValidationType(byte valtype) { - myDv.setValType(valtype); - } - - /** - * Determines if the ValidationHandle contains the cell address passed in - * - * - * @param range - * @return - */ - public boolean isInRange(String celladdy) { - return myDv.isInRange(celladdy); - } - - /** - * Return an xml representation of the ValidationHandle - * - * - * @return - */ - public String getXML() { - StringBuffer xml = new StringBuffer(); - xml.append(""); - if (this.getFirstCondition() != null) { - xml.append(""); - xml.append(StringTool.convertXMLChars(this.getFirstCondition())); - xml.append(""); - } - if (this.getSecondCondition() != null) { - xml.append(""); - xml.append(StringTool.convertXMLChars(this.getSecondCondition())); - xml.append(""); - } - xml.append(""); - return xml.toString(); - } + private Dv myDv; + + // static shorts for setting validation type + public static final byte VALUE_ANY = 0x0; + public static final byte VALUE_INTEGER = 0x1; + public static final byte VALUE_DECIMAL = 0x2; + public static final byte VALUE_USER_DEFINED_LIST = 0x3; + public static final byte VALUE_DATE = 0x4; + public static final byte VALUE_TIME = 0x5; + public static final byte VALUE_TEXT_LENGTH = 0x6; + public static final byte VALUE_FORMULA = 0x7; + + // static shorts for setting action on error + public static byte ERROR_STOP = 0x0; + public static byte ERROR_WARN = 0x1; + public static byte ERROR_INFO = 0x2; + + // static shorts for setting conditions on validation + public static final byte CONDITION_BETWEEN = 0x0; + public static final byte CONDITION_NOT_BETWEEN = 0x1; + public static final byte CONDITION_EQUAL = 0x2; + public static final byte CONDITION_NOT_EQUAL = 0x3; + public static final byte CONDITION_GREATER_THAN = 0x4; + public static final byte CONDITION_LESS_THAN = 0x5; + public static final byte CONDITION_GREATER_OR_EQUAL = 0x6; + public static final byte CONDITION_LESS_OR_EQUAL = 0x7; + + // static shorts for setting IME modes + public static short IME_MODE_NO_CONTROL = 0x0; // No control for IME. + // (default) + public static short IME_MODE_ON = 0x1; // IME is on. + public static short IME_MODE_OFF = 0x2; // IME is off. + public static short IME_MODE_DISABLE = 0x3;// IME is disabled. + public static short IME_MODE_HIRAGANA = 0x4;// IME is in hiragana input + // mode. + public static short IME_MODE_KATAKANA = 0x5;// IME is in full-width katakana + // input mode. + public static short IME_MODE_KATALANA_HALF = 0x6;// IME is in half-width + // katakana input mode. + public static short IME_MODE_FULL_WIDTH_ALPHA = 0x7;// IME is in full-width + // alphanumeric input + // mode + public static short IME_MODE_HALF_WIDTH_ALPHA = 0x8;// IME is in half-width + // alphanumeric input + // mode. + public static short IME_MODE_FULL_WIDTH_HANKUL = 0x9;// IME is in full-width + // Hankul input mode + public static short IME_MODE_HALF_WIDTH_HANKUL = 0x10;// IME is in + // half-width Hankul + // input mode. + + public static String[] CONDITIONS = {"between", "notBetween", "equal", + "notEqual", "greaterThan", "lessThan", "greaterOrEqual", + "lessOrEqual"}; + + /** + * Get the byte representing the condition type string passed in. Options + * are' "between", "notBetween", "equal", "notEqual", "greaterThan", + * "lessThan", "greaterOrEqual", "lessOrEqual" + * + * @return + */ + public static byte getConditionNumber(String conditionType) { + for (int i = 0; i < CONDITIONS.length; i++) { + if (conditionType.equalsIgnoreCase(CONDITIONS[i])) + return (byte) i; + } + return -1; + } + + public static String[] VALUE_TYPE = {"any", "integer", "decimal", + "userDefinedList", "date", "time", "textLength", "formula"}; + + /** + * Get the byte representing the value type string passed in. Options are' + * "any", "integer", "decimal", "userDefinedList", "date", "time", + * "textLength", "formula" + * + * @return + */ + public static byte getValueNumber(String valueType) { + for (int i = 0; i < VALUE_TYPE.length; i++) { + if (valueType.equalsIgnoreCase(VALUE_TYPE[i])) + return (byte) i; + } + return -1; + } + + /** + * Create a new Validation for the input cell range. + * + * Validations are specific to worksheets. + * + * + * @param cellRange + * = cell or range of cells, example "A1", "A1:A10" + * + * protected ValidationHandle(String cellRange, WorkSheetHandle + * wsh) { + * + * } + */ + /** + * For internal use only. Creates a Validation Handle based of the Dv passed + * in. + * + * @param dv + */ + public ValidationHandle(Dv dv) { + myDv = dv; + } + + /** + * Return the range of data this ValidationHandle refers to as a string Will + * not contain worksheet identifier, as ValidationHandles are specific to a + * worksheet. If the Validation effects multiple ranges they are separated + * by a space + * + * @return ptgRef.toString() + */ + public String getRange() { + String[] s = myDv.getRanges(); + String out = ""; + for (int i = 0; i < s.length; i++) { + if (i > 0) + out += " "; + out += s[i]; + } + return out; + } + + /** + * Determine if the value passed in is valid for this validation + * + * @param value + * @return + */ + public boolean isValid(Object value) throws ValidationException { + return myDv.isValid(value); + } + + /** + * Determine if the value passed in is valid for this validation + * + * @param value + * @return + */ + public boolean isValid(Object value, boolean throwException) + throws RuntimeException { + if (throwException) { + try { + return myDv.isValid(value); + } catch (Exception e) { + try { + throw e; + } catch (Exception ex) { + Logger.logErr("Error getting isValid " + ex.toString()); + } + } + } + try { + return myDv.isValid(value); + } catch (Exception e) { + return false; + } + } + + /** + * Set the range this ValidationHandle refers to. Pass in a range string, + * sans worksheet. + *

        + * This range will overwrite all other ranges this ValidationHandle refers + * to. + *

        + * In order to handle multiple ranges, use the addRange(String range) method + * + * @param range = standard excel range without worksheet information ("A1" or + * "A1:A10") + */ + public void setRange(String range) { + myDv.setRange(range); + } + + /** + * Adds an additional range to the existing ranges in this validationhandle + * + * @param range + */ + public void addRange(String range) { + myDv.addRange(range); + } + + /** + * Get the text from the error box. + * + * @return + */ + public String getErrorBoxText() { + return myDv.getErrorBoxText(); + } + + /** + * Set the text for the error box + * + * @param textError + */ + public void setErrorBoxText(String textError) { + myDv.setErrorBoxText(textError); + } + + /** + * Return the text in the prompt box + * + * @return + */ + public String getPromptBoxText() { + return myDv.getPromptBoxText(); + } + + /** + * Set the text for the prompt box + * + * @param text + */ + public void setPromptBoxText(String text) { + myDv.setPromptBoxText(text); + } + + /** + * Set the title for the error box + * + * @param textError + */ + public void setErrorBoxTitle(String textError) { + myDv.setErrorBoxTitle(textError); + } + + /** + * Get the title from the error box + * + * @return + */ + public String getErrorBoxTitle() { + return myDv.getErrorBoxTitle(); + } + + /** + * Return the title in the prompt box + * + * @return + */ + public String getPromptBoxTitle() { + return myDv.getPromptBoxTitle(); + } + + /** + * Set the title for the prompt box + * + * @param text + */ + public void setPromptBoxTitle(String text) { + myDv.setPromptBoxTitle(text); + } + + /** + * Return a byte representing the error style for this ValidationHandle + *

        + * These map to the static final ints ERROR_* in ValidationHandle + * + * @return + */ + public byte getErrorStyle() { + return myDv.getErrorStyle(); + } + + /** + * Set the error style for this ValidationHandle record + *

        + * These map to the static final ints ERROR_* from ValidationHandle + * + * @return + */ + public void setErrorStyle(byte errstyle) { + myDv.setErrorStyle(errstyle); + } + + /** + * Get the IME mode for this validation + * + * @return + */ + public short getIMEMode() { + return myDv.getIMEMode(); + } + + /** + * set the IME mode for this validation + * + * @return + */ + public void setIMEMode(short mode) { + myDv.setIMEMode(mode); + } + + /** + * Allow blank cells in the validation area? + * + * @return + */ + public boolean isAllowBlank() { + return myDv.isAllowBlank(); + } + + /** + * Allow blank cells in the validation area? + * + * @return + */ + public void setAllowBlank(boolean allowBlank) { + myDv.setAllowBlank(allowBlank); + } + + /** + * Get the first condition of the validation as a string representation + * + * @return + */ + public String getFirstCondition() { + return myDv.getFirstCond(); + } + + /** + * Set the first condition of the validation + *

        + * This value must conform to the Value Type of this validation or + * unexpected results may occur. For example, entering a string + * representation of a date here will not work if your validation is an + * integer... + *

        + * A java.util.Date object can also be passed in. This value will be + * translated into an integer as excel stores dates. If you need to + * manipulate/retrieve this value later utilize the DateConverter tool to + * transform the value + *

        + * String passed in should be a vaild XLS formula. Does not need to include + * the "=" + *

        + * Types of conditions Integer values Decimal values User defined list Date + * Time Text length Formula + *

        + * Be sure that your validation type (getValidationType()) matches the type + * of data. + * + * @param firstCond = the first condition for the validation + */ + public void setFirstCondition(Object firstCond) { + String setval = firstCond.toString(); + if (firstCond instanceof java.util.Date) { + double d = DateConverter.getXLSDateVal((java.util.Date) firstCond); + setval = d + ""; + } + myDv.setFirstCond(setval); + } + + /** + * Get the second condition of the validation as a string representation + * + * @return + */ + public String getSecondCondition() { + return myDv.getSecondCond(); + } + + /** + * Set the first condition of the validation utilizing a string. This value + * must conform to the Value Type of this validation or unexpected results + * may occur. For example, entering a string representation of a date here + * will not work if your validation is an integer... + *

        + * String passed in should be a vaild XLS formula. Does not need to include + * the "=" + *

        + * Types of conditions Integer values Decimal values User defined list Date + * Time Text length Formula + *

        + * Be sure that your validation type (getValidationType()) matches the type + * of data. + * + * @return + */ + public void setSecondCondition(Object secondCond) { + String setval = secondCond.toString(); + if (secondCond instanceof java.util.Date) { + double d = DateConverter.getXLSDateVal((java.util.Date) secondCond); + setval = d + ""; + } + myDv.setSecondCond(setval); + } + + /** + * Show error box if invalid values entered? + * + * @return + */ + public boolean isShowErrorMsg() { + return myDv.isShowErrorMsg(); + } + + /** + * Set show error box if invalid values entered? + * + * @return + */ + public void setShowErrorMsg(boolean showErrMsg) { + myDv.setShowErrMsg(showErrMsg); + } + + /** + * Show prompt box if cell selected? + * + * @return + */ + public boolean getShowInputMsg() { + return myDv.getShowInputMsg(); + } + + /** + * Set show prompt box if cell selected? + * + * @param showInputMsg + */ + public void setShowInputMsg(boolean showInputMsg) { + myDv.setShowInputMsg(showInputMsg); + } + + /** + * In list type validity the string list is explicitly given in the formula + * + * @return boolean + */ + public boolean isStrLookup() { + return myDv.isStrLookup(); + } + + /** + * In list type validity the string list is explicitly given in the formula + * + * @param strLookup + */ + public void setStrLookup(boolean strLookup) { + myDv.setStrLookup(strLookup); + } + + /** + * Suppress the drop down arrow in list type validity + * + * @return boolean + */ + public boolean isSuppressCombo() { + return myDv.isSuppressCombo(); + } + + /** + * Suppress the drop down arrow in list type validity + */ + public void setSuppressCombo(boolean suppressCombo) { + myDv.setSuppressCombo(suppressCombo); + } + + /** + * Get the type operator of this validation as a byte. + *

        + * These bytes map to the CONDITION_* static values in ValidationHandle + * + * @return + */ + public byte getTypeOperator() { + return myDv.getTypeOperator(); + } + + /** + * set the type operator of this validation as a byte. + *

        + * These bytes map to the CONDITION_* static values in ValidationHandle + */ + public void setTypeOperator(byte typOperator) { + myDv.setTypeOperator(typOperator); + } + + /** + * Get the validation type of this ValidationHandle as a byte + *

        + * These bytes map to the VALUE_* static values in ValidationHandle + * + * @return + */ + public byte getValidationType() { + return myDv.getValType(); + } + + /** + * Set the validation type of this ValidationHandle as a byte + *

        + * These bytes map to the VALUE_* static values in ValidationHandle + */ + public void setValidationType(byte valtype) { + myDv.setValType(valtype); + } + + /** + * Determines if the ValidationHandle contains the cell address passed in + * + * @param range + * @return + */ + public boolean isInRange(String celladdy) { + return myDv.isInRange(celladdy); + } + + /** + * Return an xml representation of the ValidationHandle + * + * @return + */ + public String getXML() { + StringBuffer xml = new StringBuffer(); + xml.append(""); + if (this.getFirstCondition() != null) { + xml.append(""); + xml.append(StringTool.convertXMLChars(this.getFirstCondition())); + xml.append(""); + } + if (this.getSecondCondition() != null) { + xml.append(""); + xml.append(StringTool.convertXMLChars(this.getSecondCondition())); + xml.append(""); + } + xml.append(""); + return xml.toString(); + } } diff --git a/src/main/java/io/starter/OpenXLS/WorkBook.java b/src/main/java/io/starter/OpenXLS/WorkBook.java index ffbfa7d..53d6d29 100644 --- a/src/main/java/io/starter/OpenXLS/WorkBook.java +++ b/src/main/java/io/starter/OpenXLS/WorkBook.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,423 +29,446 @@ */ package io.starter.OpenXLS; -import java.io.OutputStream; +import io.starter.formats.XLS.*; -import io.starter.formats.XLS.CellNotFoundException; -import io.starter.formats.XLS.ChartNotFoundException; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.formats.XLS.PivotTableNotFoundException; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.XLSConstants; +import java.io.OutputStream; -/** An interface representing an OpenXLS WorkBook. - * +/** + * An interface representing an OpenXLS WorkBook. */ public interface WorkBook extends Handle, Document { - public static int CALCULATE_ALWAYS = XLSConstants.CALCULATE_ALWAYS; - public static int CALCULATE_EXPLICIT = XLSConstants.CALCULATE_EXPLICIT; - public static int CALCULATE_AUTO = XLSConstants.CALCULATE_AUTO; - public static String CALC_MODE_PROP = XLSConstants.CALC_MODE_PROP; - public static String REFTRACK_PROP = XLSConstants.REFTRACK_PROP; - public static String USETEMPFILE_PROP = XLSConstants.USETEMPFILE_PROP; - public static String DEFAULTENCODING = XLSConstants.DEFAULTENCODING; - public static String UNICODEENCODING = XLSConstants.UNICODEENCODING; - public static String VALIDATEWORKBOOK = XLSConstants.VALIDATEWORKBOOK; - - // public static final int FORMULA_CALC_AUTO = 0; - - public static final int STRING_ENCODING_AUTO = XLSConstants.STRING_ENCODING_AUTO; - public static final int STRING_ENCODING_UNICODE = XLSConstants.STRING_ENCODING_UNICODE; - public static final int STRING_ENCODING_COMPRESSED = XLSConstants.STRING_ENCODING_COMPRESSED; - public static final int ALLOWDUPES = XLSConstants.ALLOWDUPES; - public static final int SHAREDUPES = XLSConstants.SHAREDUPES; - - /** Explicit calcing of formulas - * - * - * @param mode - */ - public void setFormulaCalculationMode(int mode); - - public int getFormulaCalculationMode(); - - /** get a non-Excel property - * @return Returns the properties. - */ - @Override - public Object getProperty(String name); - - /** add non-Excel property - * @param properties The properties to set. - */ - @Override - public void addProperty(String name, Object val); - - /** The Session for the WorkBook instance - * - * @return - - public BookSession getSession(); - */ - - public java.awt.Color[] getColorTable(); - - /** Returns all of the Cells contained in the WorkBook - * - */ - public CellHandle[] getCells(); - - /** Returns the Cell at the specified Location - * - * @param address - * @return - */ - public abstract CellHandle getCell(String address) throws CellNotFoundException, WorkSheetNotFoundException; - - /** - * Returns an Array of all the FormatHandles present in the workbook - * @return all existing FormatHandles in the workbook - */ - public abstract FormatHandle[] getFormats(); - - /** Gets the date format used by this book. */ - public DateConverter.DateFormat getDateFormat(); - - /** get a handle to a PivotTable in the WorkBook - - @param String name of the PivotTable - @return PivotTable the PivotTable - */ - public abstract PivotTableHandle getPivotTable(String ptname) throws PivotTableNotFoundException; - - /** get an array of handles to all PivotTables in the WorkBook - - @return PivotTable[] all of the WorkBooks PivotTables - */ - public abstract PivotTableHandle[] getPivotTables() throws PivotTableNotFoundException; - - /** set the workbook to protected mode - - Note: the password cannot be decrypted or changed - in Excel -- protection can only be set/removed using - OpenXLS - - @param int Default Column width - - */ - public abstract void setProtected(boolean b); - - /** set Default row height - - Note: only affects undefined Rows containing Cells - - @param int Default Row Height - */ - public abstract void setDefaultRowHeight(int t); - - /** set Default col width - - Note: only affects undefined Columns containing Cells - - @param int Default Column width - - */ - public abstract void setDefaultColWidth(int t); - - /** Sets the internal name of this WorkBookHandle. - * - * Overrides the default for 'getName()' which returns - * the file name source of this WorkBook by default. - * - * @param WorkBook Name - */ - @Override - public abstract void setName(String nm); - - /** Set the Debugging level. Higher values output more - debugging info during execution. - - @parameter int Debug level. higher=more verbose - */ - @Override - public abstract void setDebugLevel(int l); - - /** Returns a Named Range Handle - - @return NameHandle a Named range in the WorkBook - */ - public abstract NameHandle getNamedRange(String rangename) throws CellNotFoundException; - - /** Returns a Chart Handle - - @return ChartHandle a Chart in the WorkBook - */ - public abstract ChartHandle getChart(String chartname) throws ChartNotFoundException; - - /** Returns all Chart Handles contained in the WorkBook - - @return ChartHandle[] an array of all Charts in the WorkBook - */ - public abstract ChartHandle[] getCharts(); - - /** Returns all Named Range Handles - - @return NameHandle[] all of the Named ranges in the WorkBook - */ - public abstract NameHandle[] getNamedRanges(); - - /** Returns the name of this WorkBook - - @return String name of WorkBook - */ - @Override - public abstract String getName(); - - /** Returns the number of Cells in this WorkBook - - @return int number of Cells - */ - public abstract int getNumCells(); - - /** Returns a byte Array containing the - valid file containing this WorkBook - and associated Storages (such as VB files - and PivotTables.) - - This is the actual file data and that can be - read from and written to FileOutputStreams and - ServletOutputStreams. - - @return byte[] the XLS File's bytes - */ - public abstract byte[] getBytes(); - - /** Initialize a Vector of the CellRanges existing in this WorkBook - * specifically the Ranges referenced in Formulas, Charts, and - * Named Ranges. - * - * This is necessary to allow for automatic updating of references - * when adding/removing/moving Cells within these ranges, as well - * as shifting references to Cells in Formulas when Formula records - * are moved. - * - */ - // public abstract void initCellRanges(boolean - // createblanks); - - /** Returns an array of handles to all - of the WorkSheets in the Workbook. - - @return WorkSheetHandle[] Array of all WorkSheets in WorkBook - */ - public abstract WorkSheetHandle[] getWorkSheets(); - - /** returns the handle to a WorkSheet by name. - - @param index of worksheet (ie: 0) - @return WorkSheetHandle the WorkSheet - @exception WorkSheetNotFoundException if the specified WorkSheet is - not found in the WorkBook. - */ - public abstract WorkSheetHandle getWorkSheet(int i) throws WorkSheetNotFoundException; - - /** returns the handle to a WorkSheet by name. - - @param String name of worksheet (ie: "Sheet1") - @return WorkSheetHandle the WorkSheet - @exception WorkSheetNotFoundException if the specified WorkSheet is - not found in the WorkBook. - */ - public abstract WorkSheetHandle getWorkSheet(String handstr) throws WorkSheetNotFoundException; - - /** Returns a low-level WorkBook. - - NOTE: The WorkBook class is NOT a part of the - published API. Any of the methods and/or - variables on a WorkBook object are subject - to change without notice in new versions of OpenXLS. - - */ - public abstract io.starter.formats.XLS.WorkBook getWorkBook(); - - /** Clears all values in a template WorkBook. - - Use this method to 'reset' the values of your - WorkBook in memory to defaults. - - For example, if you load a Servlet with a - single WorkBookHandle instance, then modify - values and stream to a Client system, yo - should call 'clearAll()' when the request - is completed to remove the modified values - and set them back to a default. - - */ - @Override - public abstract void reset(); - - /** Set Encoding mode of new Strings added to file. - - OpenXLS has 3 modes for handling the internal encoding of - String data that is added to the file. - - OpenXLS can save space in the file if it knows that all characters - in your String data can be represented with a single byte (Compressed.) - - If your String contains characters which need 2 bytes to represent (such - as Eastern-language characters) then it needs to be stored in an uncompressed - Unicode format. - - OpenXLS can either automatically detect the mode for each String, or you - can set it explicitly. The auto mode is the most flexible but requires processing - overhead. - - Default mode is WorkBookHandle.STRING_ENCODING_AUTO. - - Valid Modes Are: - - WorkBookHandle.STRING_ENCODING_AUTO Use if you are adding mixed Unicode and non-unicode - Strings and can accept the performance hit - -slowest String adds - -optimal file size for mixed Strings - WorkBookHandle.STRING_ENCODING_UNICODE Use if all of your new Strings are Unicode - faster than AUTO - -faster than AUTO - -largest file size - WorkBookHandle.STRING_ENCODING_COMPRESSED Use if all of your new Strings are non-Unicode and can have high-bytes compressed - -faster than AUTO - -smallest file size - - @param int String Encoding Mode - */ - public abstract void setStringEncodingMode(int mode); - - /** Set Duplicate String Handling Mode. - - The Duplicate String Mode determines the behavior of - the String table when inserting new Strings. - - The String table shares a single entry for multiple - Cells containing the same string. When multiple Cells - have the same value, they share the same underlying string. - - Changing the value of any one of the Cells will change - the value for any Cells sharing that reference. - - For this reason, you need to determine - the handling of new strings added to the sheet that - are duplicates of strings already in the table. - - If you will be changing the values of these - new Cells, you will need to set the Duplicate - String Mode to ALLOWDUPES. If the string table - encounters a duplicate entry being added, it - will insert a duplicate that can then be subsequently - changed without affecting the other duplicate Cells. - - Valid Modes Are: - - WorkBookHandle.ALLOWDUPES - faster, smaller file sizes, dupe Cells share changes - WorkBookHandle.SHAREDUPES - slower inserts, changing Cells has no effect on dupe Cells - - @param int Duplicate String Handling Mode - */ - public abstract void setDupeStringMode(int mode); - - /** Copies an existing Chart to another WorkSheet - * - * @param chartname - * @param sheetname - */ - public abstract void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException; - - /** Copies an existing Chart to another WorkSheet - * - * @param chart - * @param sheet - */ - public abstract void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) throws ChartNotFoundException, WorkSheetNotFoundException; - - /** Copy (duplicate) a worksheet in the workbook and add it to the end of the workbook with a new name - - @param String the Name of the source worksheet; - @param String the Name of the new (destination) worksheet; - @return the new WorkSheetHandle - */ - public abstract WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName) throws WorkSheetNotFoundException; - - /** Iterate through the formulas in this WorkBook and call the - * calculate method on each. - * - * May be more expensive than calling update on individual FormulaHandles - * depending on extent of data changes to your WorkBook, or calling update - * on only the 'top-level' formula in a calculation. When a formula references - * a Cell containing another formula, it will recursively calculate until it - * reaches non-formula Cells. Thus, calling this method may calculate formula - * Cells in a heirarchy more than once. - * - * @throws FunctionNotSupportedException - */ - public abstract void calculateFormulas() throws FunctionNotSupportedException; - - /** Removes all of the WorkSheets from this WorkBook. - * - * Bytes streamed from this WorkBook will create invalid - * Spreadsheet files unless a WorkSheet(s) are added to it. - * - * - */ - public abstract void removeAllWorkSheets(); - - /** Returns a WorkBookHandle containing an empty - * version of this WorkBook. - * - * Use in conjunction with addSheetFromWorkBook() to create - * new output WorkBooks containing various sheets from a master - * template. - * - * ie: - * WorkBookHandle emptytemplate = this.getNoSheetWorkBook(); - * emptytemplate.addSheetFromWorkBook(this, "Sheet1", "TargetSheet"); - * - * - * @return WorkBookHandle - the empty WorkBookHandle duplicate - * @see addSheetFromWorkBook - */ - public abstract WorkBookHandle getNoSheetWorkBook(); - - /** Inserts a worksheet from a Source WorkBook. - * - * - * - * @param sourceBook - the WorkBook containing the sheet to copy - * @param sourceSheetName - the name of the sheet to copy - * @param destSheetName - the name of the new sheet in this workbook - * @throws WorkSheetNotFoundException - */ - public abstract boolean addSheetFromWorkBook(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException; - - /** Inserts a new worksheet and places it at the end of the workbook - - @param WorkSheetHandle the source WorkSheetHandle; - @param String the Name of the new (destination) worksheet; - */ - public abstract WorkSheetHandle addWorkSheet(WorkSheetHandle sht, String NewSheetName); - - /** Creates a new worksheet and places it at the end of the workbook - - @param String the Name of the newly created worksheet - @return the new WorkSheetHandle - */ - public abstract WorkSheetHandle createWorkSheet(String name); - - /** - * Returns the name of this Sheet. - * - * @see java.lang.Object#toString() - */ - @Override - public abstract String toString(); - - public StringBuffer writeBytes(OutputStream bbout); + int CALCULATE_ALWAYS = XLSConstants.CALCULATE_ALWAYS; + int CALCULATE_EXPLICIT = XLSConstants.CALCULATE_EXPLICIT; + int CALCULATE_AUTO = XLSConstants.CALCULATE_AUTO; + String CALC_MODE_PROP = XLSConstants.CALC_MODE_PROP; + String REFTRACK_PROP = XLSConstants.REFTRACK_PROP; + String USETEMPFILE_PROP = XLSConstants.USETEMPFILE_PROP; + String DEFAULTENCODING = XLSConstants.DEFAULTENCODING; + String UNICODEENCODING = XLSConstants.UNICODEENCODING; + String VALIDATEWORKBOOK = XLSConstants.VALIDATEWORKBOOK; + + // public static final int FORMULA_CALC_AUTO = 0; + + int STRING_ENCODING_AUTO = XLSConstants.STRING_ENCODING_AUTO; + int STRING_ENCODING_UNICODE = XLSConstants.STRING_ENCODING_UNICODE; + int STRING_ENCODING_COMPRESSED = XLSConstants.STRING_ENCODING_COMPRESSED; + int ALLOWDUPES = XLSConstants.ALLOWDUPES; + int SHAREDUPES = XLSConstants.SHAREDUPES; + + /** + * Explicit calcing of formulas + * + * @param mode + */ + void setFormulaCalculationMode(int mode); + + int getFormulaCalculationMode(); + + /** + * get a non-Excel property + * + * @return Returns the properties. + */ + @Override + Object getProperty(String name); + + /** + * add non-Excel property + * + * @param properties The properties to set. + */ + @Override + void addProperty(String name, Object val); + + /** + * The Session for the WorkBook instance + * + * @return public BookSession getSession(); + */ + + java.awt.Color[] getColorTable(); + + /** + * Returns all of the Cells contained in the WorkBook + */ + CellHandle[] getCells(); + + /** + * Returns the Cell at the specified Location + * + * @param address + * @return + */ + CellHandle getCell(String address) throws CellNotFoundException, WorkSheetNotFoundException; + + /** + * Returns an Array of all the FormatHandles present in the workbook + * + * @return all existing FormatHandles in the workbook + */ + FormatHandle[] getFormats(); + + /** + * Gets the date format used by this book. + */ + DateConverter.DateFormat getDateFormat(); + + /** + * get a handle to a PivotTable in the WorkBook + * + * @param String name of the PivotTable + * @return PivotTable the PivotTable + */ + PivotTableHandle getPivotTable(String ptname) throws PivotTableNotFoundException; + + /** + * get an array of handles to all PivotTables in the WorkBook + * + * @return PivotTable[] all of the WorkBooks PivotTables + */ + PivotTableHandle[] getPivotTables() throws PivotTableNotFoundException; + + /** + * set the workbook to protected mode + *

        + * Note: the password cannot be decrypted or changed + * in Excel -- protection can only be set/removed using + * OpenXLS + * + * @param int Default Column width + */ + void setProtected(boolean b); + + /** + * set Default row height + *

        + * Note: only affects undefined Rows containing Cells + * + * @param int Default Row Height + */ + void setDefaultRowHeight(int t); + + /** + * set Default col width + *

        + * Note: only affects undefined Columns containing Cells + * + * @param int Default Column width + */ + void setDefaultColWidth(int t); + + /** + * Sets the internal name of this WorkBookHandle. + *

        + * Overrides the default for 'getName()' which returns + * the file name source of this WorkBook by default. + * + * @param WorkBook Name + */ + @Override + void setName(String nm); + + /** + * Set the Debugging level. Higher values output more + * debugging info during execution. + * + * @parameter int Debug level. higher=more verbose + */ + @Override + void setDebugLevel(int l); + + /** + * Returns a Named Range Handle + * + * @return NameHandle a Named range in the WorkBook + */ + NameHandle getNamedRange(String rangename) throws CellNotFoundException; + + /** + * Returns a Chart Handle + * + * @return ChartHandle a Chart in the WorkBook + */ + ChartHandle getChart(String chartname) throws ChartNotFoundException; + + /** + * Returns all Chart Handles contained in the WorkBook + * + * @return ChartHandle[] an array of all Charts in the WorkBook + */ + ChartHandle[] getCharts(); + + /** + * Returns all Named Range Handles + * + * @return NameHandle[] all of the Named ranges in the WorkBook + */ + NameHandle[] getNamedRanges(); + + /** + * Returns the name of this WorkBook + * + * @return String name of WorkBook + */ + @Override + String getName(); + + /** + * Returns the number of Cells in this WorkBook + * + * @return int number of Cells + */ + int getNumCells(); + + /** + * Returns a byte Array containing the + * valid file containing this WorkBook + * and associated Storages (such as VB files + * and PivotTables.) + *

        + * This is the actual file data and that can be + * read from and written to FileOutputStreams and + * ServletOutputStreams. + * + * @return byte[] the XLS File's bytes + */ + byte[] getBytes(); + + /** Initialize a Vector of the CellRanges existing in this WorkBook + * specifically the Ranges referenced in Formulas, Charts, and + * Named Ranges. + * + * This is necessary to allow for automatic updating of references + * when adding/removing/moving Cells within these ranges, as well + * as shifting references to Cells in Formulas when Formula records + * are moved. + * + */ + // public abstract void initCellRanges(boolean + // createblanks); + + /** + * Returns an array of handles to all + * of the WorkSheets in the Workbook. + * + * @return WorkSheetHandle[] Array of all WorkSheets in WorkBook + */ + WorkSheetHandle[] getWorkSheets(); + + /** + * returns the handle to a WorkSheet by name. + * + * @param index of worksheet (ie: 0) + * @return WorkSheetHandle the WorkSheet + * @throws WorkSheetNotFoundException if the specified WorkSheet is + * not found in the WorkBook. + */ + WorkSheetHandle getWorkSheet(int i) throws WorkSheetNotFoundException; + + /** + * returns the handle to a WorkSheet by name. + * + * @param String name of worksheet (ie: "Sheet1") + * @return WorkSheetHandle the WorkSheet + * @throws WorkSheetNotFoundException if the specified WorkSheet is + * not found in the WorkBook. + */ + WorkSheetHandle getWorkSheet(String handstr) throws WorkSheetNotFoundException; + + /** + * Returns a low-level WorkBook. + *

        + * NOTE: The WorkBook class is NOT a part of the + * published API. Any of the methods and/or + * variables on a WorkBook object are subject + * to change without notice in new versions of OpenXLS. + */ + io.starter.formats.XLS.WorkBook getWorkBook(); + + /** + * Clears all values in a template WorkBook. + *

        + * Use this method to 'reset' the values of your + * WorkBook in memory to defaults. + *

        + * For example, if you load a Servlet with a + * single WorkBookHandle instance, then modify + * values and stream to a Client system, yo + * should call 'clearAll()' when the request + * is completed to remove the modified values + * and set them back to a default. + */ + @Override + void reset(); + + /** + * Set Encoding mode of new Strings added to file. + *

        + * OpenXLS has 3 modes for handling the internal encoding of + * String data that is added to the file. + *

        + * OpenXLS can save space in the file if it knows that all characters + * in your String data can be represented with a single byte (Compressed.) + *

        + * If your String contains characters which need 2 bytes to represent (such + * as Eastern-language characters) then it needs to be stored in an uncompressed + * Unicode format. + *

        + * OpenXLS can either automatically detect the mode for each String, or you + * can set it explicitly. The auto mode is the most flexible but requires processing + * overhead. + *

        + * Default mode is WorkBookHandle.STRING_ENCODING_AUTO. + *

        + * Valid Modes Are: + *

        + * WorkBookHandle.STRING_ENCODING_AUTO Use if you are adding mixed Unicode and non-unicode + * Strings and can accept the performance hit + * -slowest String adds + * -optimal file size for mixed Strings + * WorkBookHandle.STRING_ENCODING_UNICODE Use if all of your new Strings are Unicode - faster than AUTO + * -faster than AUTO + * -largest file size + * WorkBookHandle.STRING_ENCODING_COMPRESSED Use if all of your new Strings are non-Unicode and can have high-bytes compressed + * -faster than AUTO + * -smallest file size + * + * @param int String Encoding Mode + */ + void setStringEncodingMode(int mode); + + /** + * Set Duplicate String Handling Mode. + *

        + * The Duplicate String Mode determines the behavior of + * the String table when inserting new Strings. + *

        + * The String table shares a single entry for multiple + * Cells containing the same string. When multiple Cells + * have the same value, they share the same underlying string. + *

        + * Changing the value of any one of the Cells will change + * the value for any Cells sharing that reference. + *

        + * For this reason, you need to determine + * the handling of new strings added to the sheet that + * are duplicates of strings already in the table. + *

        + * If you will be changing the values of these + * new Cells, you will need to set the Duplicate + * String Mode to ALLOWDUPES. If the string table + * encounters a duplicate entry being added, it + * will insert a duplicate that can then be subsequently + * changed without affecting the other duplicate Cells. + *

        + * Valid Modes Are: + *

        + * WorkBookHandle.ALLOWDUPES - faster, smaller file sizes, dupe Cells share changes + * WorkBookHandle.SHAREDUPES - slower inserts, changing Cells has no effect on dupe Cells + * + * @param int Duplicate String Handling Mode + */ + void setDupeStringMode(int mode); + + /** + * Copies an existing Chart to another WorkSheet + * + * @param chartname + * @param sheetname + */ + void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException; + + /** + * Copies an existing Chart to another WorkSheet + * + * @param chart + * @param sheet + */ + void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) throws ChartNotFoundException, WorkSheetNotFoundException; + + /** + * Copy (duplicate) a worksheet in the workbook and add it to the end of the workbook with a new name + * + * @param String the Name of the source worksheet; + * @param String the Name of the new (destination) worksheet; + * @return the new WorkSheetHandle + */ + WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName) throws WorkSheetNotFoundException; + + /** + * Iterate through the formulas in this WorkBook and call the + * calculate method on each. + *

        + * May be more expensive than calling update on individual FormulaHandles + * depending on extent of data changes to your WorkBook, or calling update + * on only the 'top-level' formula in a calculation. When a formula references + * a Cell containing another formula, it will recursively calculate until it + * reaches non-formula Cells. Thus, calling this method may calculate formula + * Cells in a heirarchy more than once. + * + * @throws FunctionNotSupportedException + */ + void calculateFormulas() throws FunctionNotSupportedException; + + /** + * Removes all of the WorkSheets from this WorkBook. + *

        + * Bytes streamed from this WorkBook will create invalid + * Spreadsheet files unless a WorkSheet(s) are added to it. + */ + void removeAllWorkSheets(); + + /** + * Returns a WorkBookHandle containing an empty + * version of this WorkBook. + *

        + * Use in conjunction with addSheetFromWorkBook() to create + * new output WorkBooks containing various sheets from a master + * template. + *

        + * ie: + * WorkBookHandle emptytemplate = this.getNoSheetWorkBook(); + * emptytemplate.addSheetFromWorkBook(this, "Sheet1", "TargetSheet"); + * + * @return WorkBookHandle - the empty WorkBookHandle duplicate + * @see addSheetFromWorkBook + */ + WorkBookHandle getNoSheetWorkBook(); + + /** + * Inserts a worksheet from a Source WorkBook. + * + * @param sourceBook - the WorkBook containing the sheet to copy + * @param sourceSheetName - the name of the sheet to copy + * @param destSheetName - the name of the new sheet in this workbook + * @throws WorkSheetNotFoundException + */ + boolean addSheetFromWorkBook(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException; + + /** + * Inserts a new worksheet and places it at the end of the workbook + * + * @param WorkSheetHandle the source WorkSheetHandle; + * @param String the Name of the new (destination) worksheet; + */ + WorkSheetHandle addWorkSheet(WorkSheetHandle sht, String NewSheetName); + + /** + * Creates a new worksheet and places it at the end of the workbook + * + * @param String the Name of the newly created worksheet + * @return the new WorkSheetHandle + */ + WorkSheetHandle createWorkSheet(String name); + + /** + * Returns the name of this Sheet. + * + * @see java.lang.Object#toString() + */ + @Override + String toString(); + + StringBuffer writeBytes(OutputStream bbout); } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/WorkBookException.java b/src/main/java/io/starter/OpenXLS/WorkBookException.java index 29df80a..ca56690 100644 --- a/src/main/java/io/starter/OpenXLS/WorkBookException.java +++ b/src/main/java/io/starter/OpenXLS/WorkBookException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,39 +25,38 @@ /** * WorkBookInstantiationException is thrown when a workbook cannot be parsed for a particular reason. - * + *

        * Error codes can be retrieved with getErrorCode, which map to the static error ints */ -public class WorkBookException extends io.starter.formats.XLS.WorkBookException{ +public class WorkBookException extends io.starter.formats.XLS.WorkBookException { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5313787084750169461L; - public final static int DOUBLE_STREAM_FILE = 0; - public final static int NOT_BIFF8_FILE = 1; - public final static int LICENSING_FAILED = 2; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -5313787084750169461L; + public final static int DOUBLE_STREAM_FILE = 0; + public final static int NOT_BIFF8_FILE = 1; + public final static int LICENSING_FAILED = 2; public final static int UNSPECIFIED_INIT_ERROR = 3; public final static int RUNTIME_ERROR = 4; public final static int SMALLBLOCK_FILE = 5; - public final static int WRITING_ERROR = 6; - public final static int DECRYPTION_ERROR = 7; - public final static int DECRYPTION_INCORRECT_PASSWORD = 8; - public static final int ENCRYPTION_ERROR = 9; + public final static int WRITING_ERROR = 6; + public final static int DECRYPTION_ERROR = 7; + public final static int DECRYPTION_INCORRECT_PASSWORD = 8; + public static final int ENCRYPTION_ERROR = 9; public final static int DECRYPTION_INCORRECT_FORMAT = 10; public final static int ILLEGAL_INIT_ERROR = 11; public final static int READ_ONLY_EXCEPTION = 12; public final static int SHEETPROTECT_INCORRECT_PASSWORD = 13; - + public WorkBookException(String n, int x) { super(n, x); } public WorkBookException(String string, int x, - Exception e) { - super(string, x,e); + Exception e) { + super(string, x, e); } - } diff --git a/src/main/java/io/starter/OpenXLS/WorkBookHandle.java b/src/main/java/io/starter/OpenXLS/WorkBookHandle.java index 5d5d292..b215547 100644 --- a/src/main/java/io/starter/OpenXLS/WorkBookHandle.java +++ b/src/main/java/io/starter/OpenXLS/WorkBookHandle.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,64 +29,19 @@ */ package io.starter.OpenXLS; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.OutputStream; -import java.io.Writer; -import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Vector; - import io.starter.formats.LEO.BlockByteReader; import io.starter.formats.LEO.InvalidFileException; import io.starter.formats.LEO.LEOFile; -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.BookProtectionManager; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.CellNotFoundException; -import io.starter.formats.XLS.ChartNotFoundException; -import io.starter.formats.XLS.Condfmt; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.Formula; -import io.starter.formats.XLS.FormulaNotFoundException; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.formats.XLS.Hlink; -import io.starter.formats.XLS.ImageNotFoundException; -import io.starter.formats.XLS.Mergedcells; -import io.starter.formats.XLS.Mulblank; -import io.starter.formats.XLS.Name; -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.formats.XLS.OOXMLReader; -import io.starter.formats.XLS.OOXMLWriter; -import io.starter.formats.XLS.PivotCache; -import io.starter.formats.XLS.PivotTableNotFoundException; -import io.starter.formats.XLS.Sxview; -import io.starter.formats.XLS.WorkBookFactory; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.XLSConstants; -import io.starter.formats.XLS.Xf; +import io.starter.formats.XLS.*; import io.starter.formats.XLS.charts.Chart; import io.starter.formats.XLS.charts.OOXMLChart; -import io.starter.toolkit.JFileWriter; -import io.starter.toolkit.Logger; -import io.starter.toolkit.ProgressListener; -import io.starter.toolkit.ResourceLoader; -import io.starter.toolkit.StringTool; -import io.starter.toolkit.TempFileManager; +import io.starter.toolkit.*; + +import java.io.*; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.*; /** * The WorkBookHandle provides a handle to the XLS file and includes convenience @@ -97,7 +52,7 @@ * WorkSheetHandle sheet = book.getWorkSheet("Sheet1");
        * CellHandle cell = sheet.getCell("B22");
        *
        - * + * *

        * By default, OpenXLS will lock open WorkBook files. To close the file after * parsing and work with a temporary file instead, use the following setting: @@ -108,2191 +63,2126 @@ * If you enable this mode you will need to periodically clean up the generated * temporary files in your working directory. All OpenXLS temporary file names * begin with "ExtenXLS_". - * */ public class WorkBookHandle extends DocumentHandle implements WorkBook, Handle { - /** - * A Writer to which a record dump should be written on input. This is used by - * the dumping code in WorkBookFactory. - */ - public static Writer dump_input = null; - - protected io.starter.formats.XLS.WorkBook mybook; - protected LEOFile myLEOFile; - protected WorkBookFactory myfactory = null; - - protected ProgressListener plist; - - /** Format constant for BIFF8 (Excel '97-2007). */ - public static final int FORMAT_XLS = 100; - - /** Format constant for normal OOXML (Excel 2007). */ - public static final int FORMAT_XLSX = 101; - - /** Format constant for macro-enabled OOXML (Excel 2007). */ - public static final int FORMAT_XLSM = 102; - - /** Format constant for OOXML template (Excel 2007). */ - public static final int FORMAT_XLTX = 103; - - /** Format constant for macro-enabled OOXML template (Excel 2007). */ - public static final int FORMAT_XLTM = 104; - - private static byte[] protobook; - private static byte[] protochart; - private static byte[] protosheet; - public static java.text.SimpleDateFormat simpledateformat = new java.text.SimpleDateFormat(); // static - // to - // reuse - - /** - * How many recursion levels to allow formulas to be calculated before throwing - * a circular reference error - */ - public static int RECURSION_LEVELS_ALLOWED = 107; - - /** This is **/ - public static String CONVERTMULBLANKS = "deprecated"; - - protected static byte[] getPrototypeBook() throws IOException { - if (protobook == null) - - protobook = ResourceLoader - .getBytesFromJar("/io/starter/OpenXLS/templates/prototysspe.ser"); - - return protobook; - } - - protected static byte[] getPrototypeSheet() { - if (protosheet == null) - try { - WorkBookHandle bookhandle = new WorkBookHandle(); - io.starter.formats.XLS.WorkBook book = bookhandle.getWorkBook(); - Boundsheet sheet = book.getWorkSheetByNumber(0); - protosheet = sheet.getSheetBytes(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - return protosheet; - } - - protected static byte[] getPrototypeChart() { - if (protochart == null) { - try { - byte[] bookbytes = ResourceLoader - .getBytesFromJar("/io/starter/OpenXLS/templates/prototypechart.ser"); - WorkBookHandle chartBook = new WorkBookHandle(bookbytes); - ChartHandle ch = chartBook.getCharts()[0]; - protochart = ch.getSerialBytes(); - return protochart; - } catch (IOException e) { - Logger.logErr("Unable to get default chart bytes"); - } - } - return protochart; - } - - /** - * Gets the internal Factory object. - *

        - * WARNING: This method is not part of the public API. Its use - * is not supported and behavior is subject to change. - *

        - */ - public WorkBookFactory getFactory() { - return myfactory; - } - - /** - * Gets the internal LEOFile object. - *

        - * WARNING: This method is not part of the public API. Its use - * is not supported and behavior is subject to change. - *

        - */ - public LEOFile getLEOFile() { - return myLEOFile; - } - - /** - * Searches all Cells in the workbook for the string occurrence and replaces - * with the replacement text. - * - * @return the number of replacements that were made - */ - public int searchAndReplace(String searchfor, String replacewith) { - CellHandle[] cx = getCells(); - int foundcount = 0; - for (int t = 0; t < cx.length; t++) { - if (!(cx[t].getCell() instanceof Formula)) { - // find the string - if (!cx[t].isNumber()) { - String v = cx[t].getStringVal(); - if (v.indexOf(searchfor) > -1) { - cx[t].setVal(StringTool - .replaceText(v, searchfor, replacewith)); - foundcount++; - } - } - } - } - return foundcount; - } - - /** - * Returns all strings that are in the SharedStringTable for this workbook. The - * SST contains all standard string records in cells, but may not include such - * things as strings that are contained within formulas. This is useful for such - * things as full text indexing of workbooks - * - * @return Strings in the workbook. - */ - public String[] getAllStrings() { - return this.mybook.getAllStrings(); - } - - /** - * Get either the default color table, or the color table from the custom - * palatte(if exists) from the WorkBook - * - * @return - */ - @Override - public java.awt.Color[] getColorTable() { - return this.getWorkBook().getColorTable(); - } - - /** - * Returns whether this book uses the 1904 date format. - * - * @deprecated Use {@link #getDateFormat()} instead. - */ - @Deprecated - public boolean is1904() { - return mybook.is1904(); - } - - /** Gets the date format used by this book. */ - @Override - public DateConverter.DateFormat getDateFormat() { - return mybook.getDateFormat(); - } - - /** - * Returns the lowest version of Excel compatible with the input file. - * - * @return an Excel version string - */ - public String getXLSVersionString() { - return mybook.getXLSVersionString(); - } - - /** - * Return useful statistics about this workbook. - * - * @return a string contatining various statistics. - * @param use - * html line breaks - */ - public String getStats(boolean usehtml) { - return mybook.getStats(usehtml); - } - - /** - * Return useful statistics about this workbook. - * - * @return a string contatining various statistics. - */ - public String getStats() { - return mybook.getStats(); - } - - /** - * Returns the Cell at the specified Location - * - * @param address - * @return - */ - @Override - public CellHandle getCell(String address) throws CellNotFoundException, WorkSheetNotFoundException { - int shtpos = address.indexOf("!"); - if (shtpos < 0) - throw new CellNotFoundException(address - + " not found. You need to specify a location in the format: Sheet1!A1"); - String sheetstr = address.substring(0, shtpos); - WorkSheetHandle sht = this.getWorkSheet(sheetstr); - String celstr = address.substring(shtpos + 1); - return sht.getCell(celstr); - } - - /** - * Returns an Array of the CellRanges existing in this WorkBook specifically the - * Ranges referenced in Formulas, Charts, and Named Ranges. - * - * This is necessary to allow for automatic updating of references when - * adding/removing/moving Cells within these ranges, as well as shifting - * references to Cells in Formulas when Formula records are moved. - * - * @return all existing Cell Range references used in Formulas, Charts, and - * Names - */ - public CellRange[] getCellRanges() { - return this.mybook.getRefTracker().getCellRanges(); - } - - /** - * get a handle to a PivotTable in the WorkBook - * - * @param String - * name of the PivotTable - * @return PivotTable the PivotTable - */ - @Override - public PivotTableHandle getPivotTable(String ptname) throws PivotTableNotFoundException { - Sxview st = mybook.getPivotTableView(ptname); - if (st == null) - throw new PivotTableNotFoundException(ptname); - return new PivotTableHandle(st, this); - } - - /** - * get an array of handles to all PivotTables in the WorkBook - * - * @return PivotTable[] all of the WorkBooks PivotTables - */ - @Override - public PivotTableHandle[] getPivotTables() throws PivotTableNotFoundException { - Sxview[] sxv = mybook.getAllPivotTableViews(); - if (sxv == null || sxv.length == 0) - throw new PivotTableNotFoundException( - "There are no PivotTables defined in: " + this.getName()); - PivotTableHandle[] pth = new PivotTableHandle[sxv.length]; - for (int t = 0; t < pth.length; t++) { - pth[t] = new PivotTableHandle(sxv[t], this); - } - return pth; - } - - /** - * Set the calculation mode for the workbook. - * - * CALCULATE_AUTO is the default for new workbooks. Calling Cell.getVal() will - * calculate formulas if they exist within the cell. - * - * CALCULATE_EXPLICIT will return present, cached value of the cell. Formula - * calculation will ONLY occur when explicitly called through the Formula - * Handle.calculate() method. - * - * CALCULATE_ALWAYS will ignore the cache and force a recalc every time a cell - * value is requested. - * - * - * WorkBookHandle.CALCULATE_AUTO WorkBookHandle.CALCULATE_ALWAYS - * WorkBookHandle.CALCULATE_EXPLICIT - * - * @param CalcMode - * Calculation mode to use in workbook. - */ - @Override - public void setFormulaCalculationMode(int CalcMode) { - mybook.setCalcMode(CalcMode); - } - - /** - * Get the calculation mode for the workbook. - * - * CALCULATE_ALWAYS is the default for new workbooks. Calling Cell.getVal() will - * calculate formulas if they exist within the cell. - * - * CALCULATE_EXPLICIT will return present value of the cell. Formula calculation - * will only occur when explicitly called through the Formula Handle - * - * WorkBookHandle.CALCULATE_ALWAYS -- recalc every time the cell value is - * requested (no cacheing) WorkBookHandle.CALCULATE_EXPLICIT -- recalc only when - * FormulaHandle.calculate() called WorkBookHandle.CALCULATE_AUTO -- only recac - * when changes - * - * @param CalcMode - * Calculation mode to use in workbook. - */ - @Override - public int getFormulaCalculationMode() { - return mybook.getCalcMode(); - } - - /** - * set the workbook to protected mode - * - * Note: the password cannot be decrypted or changed in Excel -- protection can - * only be set/removed using OpenXLS - * - * @param boolean - * whether to protect the book - */ - @Override - public void setProtected(boolean protect) { - // TODO: Check that this behavior is correct - // This is what the old implementation did - - BookProtectionManager protector = mybook.getProtectionManager(); - - // Excel default... no kidding! - if (protect) - protector.setPassword("VelvetSweatshop"); - else - protector.setPassword(null); - - protector.setProtected(protect); - } - - /** - * set Default row height in twips (=1/20 of a point) - * - * Note: only affects undefined Rows containing Cells - * - * @param int - * Default Row Height - */ - // should be a double as Excel units are 1/20 of what is - // stored in - // defaultrowheight - // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 - // (approx) - // should expect users to use Excel units and target method - // do the 20* - // conversion - @Override - public void setDefaultRowHeight(int t) { - mybook.setDefaultRowHeight(t); - } - - /** - * Set the default column width across all worksheets
        - * This setting is a worksheet level setting, so will be applied to all existing - * worksheets. individual worksheets can also be set using - * WorkSheetHandle.setDefaultColWidth - * - * This setting is roughly the width of the character '0' The default width of a - * column is 8. - */ - @Override - public void setDefaultColWidth(int t) { - mybook.setDefaultColWidth(t); - } - - /** - * Returns a Formula Handle - * - * @return FormulaHandle a formula handle in the WorkBook - */ - public FormulaHandle getFormulaHandle(String celladdress) throws FormulaNotFoundException { - Formula formula = mybook.getFormula(celladdress); - return new FormulaHandle(formula, this); - } - - /** - * Returns all ImageHandles in the workbook - * - * - * @return - */ - public ImageHandle[] getImages() { - List ret = new Vector(); - for (int t = 0; t < this.getNumWorkSheets(); t++) { - try { - ImageHandle[] r = this.getWorkSheet(t).getImages(); - for (int x = 0; x < r.length; x++) - ret.add(r[x]); - } catch (Exception ex) { - ; - } - } - ImageHandle[] retx = new ImageHandle[ret.size()]; - ret.toArray(retx); - return retx; - } - - /** - * Returns an ImageHandle for manipulating images in the WorkBook - * - * @param imagename - * @return - */ - public ImageHandle getImage(String imagename) throws ImageNotFoundException { - for (int t = 0; t < this.getNumWorkSheets(); t++) { - try { - ImageHandle[] r = this.getWorkSheet(t).getImages(); - for (int x = 0; x < r.length; x++) - if (r[x].getName().equals(imagename)) - return r[x]; - } catch (Exception ex) { - ; - } - } - throw new ImageNotFoundException( - "Image not found: " + imagename + " in " + this.toString()); - } - - /** - * Returns a Named Range Handle - * - * @return NameHandle a Named range in the WorkBook - */ - @Override - public NameHandle getNamedRange(String rangename) throws CellNotFoundException { - Name nand = mybook.getName(rangename.toUpperCase()); // case-insensitive - if (nand == null) - throw new CellNotFoundException(rangename); - return new NameHandle(nand, this); - } - - /** - * Returns a Named Range Handle if it exists in the specified scope. - * - * This can be used to distinguish between multiple named ranges with the same - * name but differing scopes - * - * @return NameHandle a Named range in the WorkBook that exists in the scope - */ - public NameHandle getNamedRangeInScope(String rangename) throws CellNotFoundException { - Name nand = mybook.getScopedName(rangename); - if (nand == null) - throw new CellNotFoundException(rangename); - return new NameHandle(nand, this); - } - - /** - * Create a named range in the workbook - * - * Note that the named range designation can conform to excel specs, that is, - * boolean values, references, or string variables can be set. Remember to - * utilize the sheet name when setting referential names. - * - * - * NameHandle nh = createNamedRange("cellRange", "Sheet1!A1:B3"); NameHandle nh - * = createNamedRange("trueRange", "=true"); - * - * - * @param name - * The name that should be used to reference this named range - * @param rangeDef - * Range of the cells for this named range, in excel syntax including - * sheet name, ie "Sheet1!A1:D1" - * @return NameHandle for modifying the named range - */ - public NameHandle createNamedRange(String name, String rangeDef) { - NameHandle nh = new NameHandle(name, rangeDef, this); - return nh; - } - - /** - * Returns a Chart Handle - * - * @return ChartHandle a Chart in the WorkBook - */ - // KSC: NOTE: this methodology needs work as a book may - // contain charts in - // different sheets containing the same name - // TODO: rethink - @Override - public ChartHandle getChart(String chartname) throws ChartNotFoundException { - return new ChartHandle(mybook.getChart(chartname), this); - } - - /** - * Returns all Chart Handles contained in the WorkBook - * - * @return ChartHandle[] an array of all Charts in the WorkBook - */ - @Override - public ChartHandle[] getCharts() { - AbstractList cv = mybook.getChartVect(); - ChartHandle[] cht = new ChartHandle[cv.size()]; - for (int x = 0; x < cv.size(); x++) { - cht[x] = new ChartHandle((Chart) cv.get(x), this); - } - return cht; - } - - /** - * retrieve a ChartHandle via id - * - * @param id - * @return - * @throws ChartNotFoundException - */ - public ChartHandle getChartById(int id) throws ChartNotFoundException { - AbstractList cv = mybook.getChartVect(); - Chart cht = null; - for (int x = 0; x < cv.size(); x++) { - cht = (Chart) cv.get(x); - if (cht.getId() == id) - return new ChartHandle(cht, this); - } - throw new ChartNotFoundException("Id " + id); - } - - /** - * Returns all Named Range Handles - * - * @return NameHandle[] all of the Named ranges in the WorkBook - */ - @Override - public NameHandle[] getNamedRanges() { - Name[] nand = mybook.getNames(); - NameHandle[] nands = new NameHandle[nand.length]; - for (int x = 0; x < nand.length; x++) { - nands[x] = new NameHandle(nand[x], this); - } - return nands; - } - - /** - * Returns all Named Range Handles scoped to WorkBook. - * - * Note this will not include worksheet scoped named ranges - * - * @return NameHandle[] all of the Named ranges that are scoped to WorkBook - */ - public NameHandle[] getNamedRangesInScope() { - Name[] nand = mybook.getWorkbookScopedNames(); - NameHandle[] nands = new NameHandle[nand.length]; - for (int x = 0; x < nand.length; x++) { - nands[x] = new NameHandle(nand[x], this); - } - return nands; - } - - /** - * Returns the name of this WorkBook - * - * @return String name of WorkBook - */ - @Override - public String getName() { - if (name != null) - return name; - return "New Spreadsheet"; - } - - /** - * Returns an array containing all cells in the WorkBook - * - * @return CellHandle array of all book cells - */ - @Override - public CellHandle[] getCells() { - BiffRec[] allcz = this.mybook.getCells(); - CellHandle[] ret = new CellHandle[allcz.length]; - Mulblank aMul = null; - short c = -1; - for (int t = 0; t < ret.length; t++) { - ret[t] = new CellHandle(allcz[t], this); - if (allcz[t].getOpcode() == XLSConstants.MULBLANK) { - // handle Mulblanks: ref a range of cells; to get correct - // cell address, - // traverse thru range and set cellhandle ref to correct - // column - if (allcz[t] == aMul) { - c++; - } else { - aMul = (Mulblank) allcz[t]; - c = (short) aMul.getColFirst(); - } - ret[t].setBlankRef(c); // for Mulblank use only -sets correct - // column reference for multiple blank - // cells - // ... - } - } - return ret; - } - - /** - * Returns the number of Cells in this WorkBook - * - * @return int number of Cells - */ - @Override - public int getNumCells() { - return mybook.getNumCells(); - } - - /** Returns whether the sheet selection tabs should be shown. */ - public boolean showSheetTabs() { - return mybook.showSheetTabs(); - } - - /** Sets whether the sheet selection tabs should be shown. */ - public void setShowSheetTabs(boolean show) { - mybook.setShowSheetTabs(show); - } - - /** - * Gets the spreadsheet as a byte array in BIFF8 (Excel '97-2003) format. - * - * @deprecated Writing the spreadsheet to a byte array uses a great deal of - * memory and generally provides no benefit over streaming output. - * Use the {@link #write} family of methods instead. If you need a - * byte array use {@link ByteArrayOutputStream}. - */ - @Override - @Deprecated - public byte[] getBytes() { - try { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - writeBytes(bout); - - return bout.toByteArray(); - } catch (Exception e1) { - Logger.logErr("Getting Spreadsheet bytes failed.", e1); - return null; - } - } - - /** - * Writes the document to the given path. If the filename ends with ".xlsx" or - * ".xlsm", the workbook will be written as OOXML (XLSX). Otherwise it will be - * written as BIFF8 (XLS). For OOXML, if the file has a VBA project the file - * extension must be ".xlsm". It will be changed if necessary. - * - * @param path - * the path to which the document should be written - * @deprecated The filename-based format choosing is counter-intuitive and - * failure-prone. Use {@link #write(OutputStream,int)} instead. - */ - @Deprecated - public void write(String path) { - String ext = path.toLowerCase(); - write(path, ext.endsWith(".xlsx") || ext.endsWith(".xlsm")); - } - - /** - * Writes the document to the given file in either XLS or XLSX. For OOXML, if - * the file has a VBA project the file extension must be ".xlsm". It will be - * changed if necessary. - * - * @param path - * the path to which the document should be written - * @param ooxml - * If true, write as OOXML (XLSX). Otherwise, write as - * BIFF8 (XLS). - * @deprecated The boolean format parameter is not flexible enough to represent - * all supported formats. Use {@link #write(File,int)} instead. - */ - @Deprecated - public void write(String path, boolean ooxml) { - int format; - if (ooxml) { - if (this.getIsExcel2007()) - format = this.getFormat(path); - else - format = FORMAT_XLSX; - - if (!OOXMLAdapter.hasMacros(this)) - path = StringTool.replaceExtension(path, ".xlsx"); - else // it's a macro-enabled workbook - path = StringTool.replaceExtension(path, ".xlsm"); - } else - format = FORMAT_XLS; - - try { - this.write(new File(path), format); - } catch (Exception e) { - throw new WorkBookException("error writing workbook", - WorkBookException.WRITING_ERROR, e); - } - } - - /** - * Writes the document to the given stream in either XLS or XLSX format. - * - * @param dest - * the stream to which the document should be written - * @param ooxml - * If true, write as OOXML (XLSX). Otherwise, write as - * BIFF8 (XLS). - * @deprecated The boolean format parameter is not flexible enough to represent - * all supported formats. Use {@link #write(OutputStream,int)} - * instead. - */ - @Deprecated - public void write(OutputStream dest, boolean ooxml) { - int format; - if (ooxml) { - if (this.getIsExcel2007()) - format = this.getFormat(); - else - format = FORMAT_XLSX; - } else - format = FORMAT_XLS; - - try { - if (format > WorkBookHandle.FORMAT_XLS && this.file != null) { - OOXMLAdapter.refreshPassThroughFiles(this); - } - this.write(dest, format); - } catch (Exception e) { - throw new WorkBookException("error writing workbook", - WorkBookException.WRITING_ERROR, e); - } - } - - /** - * Gets the constant representing this document's native format. - */ - @Override - public int getFormat() { - String name = this.getFileName().toLowerCase(); - - if (this.getIsExcel2007()) { - if (OOXMLAdapter.hasMacros(this)) - return name.endsWith(".xltm") ? FORMAT_XLTM : FORMAT_XLSM; - else - return name.endsWith(".xltx") ? FORMAT_XLTX : FORMAT_XLSX; - } - - else - return FORMAT_XLS; - } - - /** - * Gets the constant representing this document's desired format - */ - public int getFormat(String path) { - if (path == null) - return getFormat(); - if (this.getIsExcel2007()) { - if (OOXMLAdapter.hasMacros(this)) - return path.endsWith(".xltm") ? FORMAT_XLTM : FORMAT_XLSM; - else - return path.endsWith(".xltx") ? FORMAT_XLTX : FORMAT_XLSX; - } - - else - return FORMAT_XLS; - } - - @Override - public String getFileExtension() { - switch (this.getFormat()) { - case FORMAT_XLSX: - return ".xlsx"; - case FORMAT_XLSM: - return ".xlsm"; - case FORMAT_XLTX: - return ".xltx"; - case FORMAT_XLTM: - return ".xltm"; - case FORMAT_XLS: - return ".xls"; - default: - return ""; - } - } - - /** - * Writes the document to the given stream in the requested format. - *

        - * format choices: - *

        - * WorkBookHandle.FORMAT_XLS for 2003 and previous versions
        - * WorkBookHandle.FORMAT_XLSX for non-macro-enabled 2007 version
        - * WorkBookHandle.FORMAT_XLSM for macro-enabled 2007 version
        - * WorkBookHandle.FORMAT_XLTM for macro-enabled 2007 templates.
        - * WorkBookHandle.FORMAT_XLTX for 2007 templates, - *

        - * IMPORTANT NOTE: if the resulting filename contains the .XLSM extension - *
        - * the WorkBook MUST be written in FORMAT_XLSM; otherwise open errors - * will occur - *

        - * NOTE: If the format is FORMAT_XLSX and the filename contains macros - *
        - * the file will be written as Macro-Enabled i.e. in FORMAT_XLSM. In these - * cases,
        - * the filename must contain the .XLSM extension - * - * @param dest - * the stream to which the document should be written - * @param format - * the constant representing the desired output format - * @throws IllegalArgumentException - * if the given type code is invalid - * @throws IOException - * if an error occurs while writing to the stream - */ - @Override - public void write(OutputStream dest, int format) throws IOException { - if (format == FORMAT_NATIVE) - format = this.getFormat(); - - switch (format) { - case FORMAT_XLSX: - case FORMAT_XLSM: - case FORMAT_XLTX: - case FORMAT_XLTM: - try { - if (this.file != null) - OOXMLAdapter.refreshPassThroughFiles(this); - - OOXMLWriter adapter = new OOXMLWriter(); - adapter.setFormat(format); - adapter.getOOXML(this, dest); - } catch (IOException e) { - throw e; - } catch (Exception e) { - // TODO: OOXMLAdapter only throws IOException, change its - // throws - throw new WorkBookException("error writing workbook", - WorkBookException.WRITING_ERROR, e); - } - break; - - case FORMAT_XLS: - try { - this.mybook.getStreamer().writeOut(dest); - } catch (io.starter.formats.XLS.WorkBookException e) { - Throwable cause = e.getCause(); - if (cause instanceof IOException) - throw (IOException) cause; - throw e; - } - break; - - default: - throw new IllegalArgumentException("unknown output format"); - } - } - - /** - * Writes the document to the given stream in the requested OOXML format. - * - * @param dest - * the stream to which the document should be written - * @param format - * the constant representing the desired output format - * @throws IllegalArgumentException - * if the given type code is invalid - * @throws IOException - * if an error occurs while writing to the stream - * @deprecated This method is like {@link #write(OutputStream,int)} except it - * only supports OOXML formats. Use that instead. - */ - @Deprecated - public void writeXLSXBytes(OutputStream dest, int format) throws Exception { - this.write(dest, format); - } - - /** - * Writes the document to the given stream in the default OOXML format. - * - * @param dest - * the stream to which the document should be written - * @throws IOException - * if an error occurs while writing to the stream - * @deprecated Use {@link #write(OutputStream,int}) instead. - */ - @Deprecated - public void writeXLSXBytes(OutputStream dest) throws Exception { - this.write(dest, true); - } - - /** - * Returns whether the underlying spreadsheet is in Excel 2007 format by - * default. - * - * Even if this method returns true, it is still possible to write out the file - * as a BIFF8 (Excel 97-2003) file, but unsupported features will be dropped, - * and some files could experience corruption. - * - * - * @return whether the underlying spreadsheet is Excel 2007 format - */ - public boolean getIsExcel2007() { - return this.mybook.getIsExcel2007(); - } - - /** - * Sets whether this Workbook is in Excel 2007 format. Excel 2007 format - * contains larger maximum column and row contraints, for example.
        - * Even if the workbook is set to Excel 2007 format, it is still possible to - * write out the file as a BIFF8 (Excel 97-2003) file, but unsupported features - * will be dropped, and some files could experience corruption. - * - * @param isExcel2007 - */ - public void setIsExcel2007(boolean isExcel2007) { - this.mybook.setIsExcel2007(isExcel2007); - } - - /** - * Writes the document to the given stream in BIFF8 (XLS) format. - * - *

        - * To output a debugging StringBuffer, you must first set the - * autolockdown setting:
        - * - * props.put("io.starter.OpenXLS.autocreatelockdown","true"); - * - *

        - * - * @param dest - * the stream to which the document should be written - * @return for debugging: a StringBuffer containing an output of the record - * bytes streamed - * @deprecated Use {@link #write(OutputStream,int)} instead. - */ - @Override - @Deprecated - public StringBuffer writeBytes(OutputStream dest) { - StringBuffer sb = this.mybook.getStreamer().writeOut(dest); - return sb; - } - - /** - * Default constructor creates a new, empty Spreadsheet with - * - * 3 WorkSheets: "Sheet1","Sheet2",and "Sheet3". - */ - public WorkBookHandle() { - // Xf.DEFAULTIXFE= 15; // reset to default in cases of - // having previously read - // Excel2007 template which may have set defaultXF - // differently - this.initDefault(); - } - - /** - * Constructor creates a new, empty Spreadsheet with 3 worksheets: "Sheet1", - * "Sheet2" and "Sheet3"
        - * This version allows flagging the workbook as Excel 2007 format.
        - * Excel 2007 format contains larger maximum column and row contraints, for - * example.
        - * Even if the workbook is set to Excel 2007 format, it is still possible to - * write out the file as a BIFF8 (Excel 97-2003) file, but unsupported features - * will be dropped, and some files could experience corruption. - * - * @param boolean - * Excel2007 - true if set to Excel 2007 version - */ - public WorkBookHandle(boolean Excel2007) { - this.initDefault(); - this.setIsExcel2007(Excel2007); - } - - /** - * another handle to the useful ability to load a book from the prorotype bytes - */ - protected void initDefault() { - try { - byte[] b = getPrototypeBook(); - if (b == null) { - throw new io.starter.formats.XLS.WorkBookException( - "Unable to load prototype workbook.", - WorkBookException.LICENSING_FAILED); - } - ByteBuffer bbf = ByteBuffer.wrap(b); - bbf.order(ByteOrder.LITTLE_ENDIAN); - myLEOFile = new LEOFile(bbf); - } catch (Exception e) { - throw new InvalidFileException( - "WorkBook could not be instantiated: " + e.toString()); - } - this.initFromLeoFile(myLEOFile); - } - - /** - * constructor which takes an InputStream containing the bytes of a valid XLS - * file. - * - * @param InputStream - * contains the valid BIFF8 bytes for reading - */ - public WorkBookHandle(InputStream inx) { - this.initFromStream(inx); - } - - /** - * Initialization of this workbook handle from a leoFile; - * - */ - private void initFromLeoFile(LEOFile leo) { - this.myLEOFile = leo; - try { - BlockByteReader bar = myLEOFile.getXLSBlockBytes(); - this.initBytes(bar); - this.setIsExcel2007(false); - myLEOFile.clearAfterInit(); - } catch (Exception e) { - if (e instanceof io.starter.formats.XLS.WorkBookException) - throw (io.starter.formats.XLS.WorkBookException) e; - throw new io.starter.formats.XLS.WorkBookException( - "ERROR: instantiating WorkBookHandle failed: " + e, - WorkBookException.UNSPECIFIED_INIT_ERROR, e); - } - } - - /** - * Initialize this workbook from a stream, unfortunately our byte backer - * requires a file, so create a tempfile and init from that - * - */ - protected void initFromStream(InputStream input) { - try { - File target = TempFileManager.createTempFile("WBP", ".tmp"); - - JFileWriter.writeToFile(input, target); - this.initFromFile(target.getAbsoluteFile()); - if (this.myLEOFile != null)// it would be if XLSX or XLSM ... - // 20090323 KSC - this.myLEOFile.closefb(); - // this.myLEOFile.close(); // close now flushes buffers + - // storages ... - input.close(); - - File fdel = new File(target.toString()); - if (!fdel.delete()) { - if (this.DEBUGLEVEL > DEBUG_LOW) - Logger.logWarn("Could not delete tempfile: " - + target.toString()); - } - } catch (IOException ex) { - Logger.logErr("Initializing WorkBookHandle failed.", ex); - } - } - - /** - * Create a new WorkBookHandle from the byte array passed in. Byte array passed - * in must contain a valid xls or xlsx workbook file - * - * @param byte[] - * byte array containing the valid XLS or XLSX file for reading - */ - public WorkBookHandle(byte[] barray) { - initializeFromByteArray(barray); - } - - /** - * Protected method that handles WorkBookHandle(byte[]) constructor - * - * @param barray - */ - protected void initializeFromByteArray(byte[] barray) { - // check first bytes to see if this is a zipfile (OOXML) - if ((char) barray[0] == 'P' && (char) barray[1] == 'K') { - try { - // added "." fixes Baxter Open Bug [BugTracker 2909] - File ftmp = TempFileManager.createTempFile("WBP", ".tmp"); - FileOutputStream fous = new FileOutputStream(ftmp); - fous.write(barray); - fous.flush(); - fous.close(); - this.initFromFile(ftmp); - return; - } catch (Exception e) { - Logger.logErr("Could not parse XLSX from bytes." - + e.toString()); - return; - } - } - - ByteBuffer bbf = ByteBuffer.wrap(barray); - bbf.order(ByteOrder.LITTLE_ENDIAN); - myLEOFile = new LEOFile(bbf); - if (myLEOFile.hasWorkBook()) { - try { - BlockByteReader bar = myLEOFile.getXLSBlockBytes(); - this.initBytes(bar); - } catch (Throwable e) { - if (e instanceof OutOfMemoryError) - throw (Error) e; - if (e instanceof WorkBookException) - throw (WorkBookException) e; - String errstr = "Instantiating WorkBookHandle failed: " - + e.toString(); - throw new io.starter.formats.XLS.WorkBookException(errstr, - WorkBookException.UNSPECIFIED_INIT_ERROR); - } - } else { - Logger.logWarn("Initializing WorkBookHandle failed: byte array does not contain a supported Excel WorkBook."); - throw new InvalidFileException( - "byte array does not contian a supported Excel WorkBook."); - } - } - - /** - * Fetches a workbook from a URL - * - * If you need to authenticate your URL connection first then use the - * InputStream constructor - * - * @param urlx - * @return - * @throws Exception - */ - public WorkBookHandle(URL url) { - /* - * OK, both this method and the (inputstream) constructor - * set a temp file, is - * this not possible to do without hitting the disk? TODO: - * look into fix - */ - this(getFileFromURL(url)); - } - - /** - * Constructor which takes the XLS file name( - * - * @param String - * filePath the name of the XLS file to read - */ - public WorkBookHandle(String filePath) { - this(filePath, 0); - } - - /** - * constructor which takes the XLS file name and has an optional debug setting - * to assist with output. Setting this value will cause verbose logging and is - * discouraged unless required for support. - * - * @param String - * filePath the name of the XLS file to read - * @param Debug - * level - */ - public WorkBookHandle(String filePath, int debug) { - this.setDebugLevel(debug); - File f = new File(filePath); - this.initFromFile(f); - this.file = f; // XXX KSC: Save for potential re-input of pass-through - // ooxml files - - } - - /** - * constructor which takes the XLS file - * - * @param File - * the XLS file to read - * @param Debug - * level - */ - public WorkBookHandle(File fx) { - this.initFromFile(fx); - } - - protected void initWorkBookFactory() { - myfactory = new WorkBookFactory(); - } - - /** - * initialize from an XLSX/OOXML workbook. - */ - private boolean initXLSX(String fname) { - // do before parseNBind so can set myfactory & fname - // set state vars for this workbookhandle - this.initWorkBookFactory(); - - myfactory.setDebugLevel(this.DEBUGLEVEL); - myfactory.setFileName(this.name); - - if (plist != null) - myfactory.register(plist); // register progress notifier - try { - // iterate sheets,inputting cell values, named ranges and - // formula strings - OOXMLReader oe = new OOXMLReader(); - WorkBookHandle bk = new WorkBookHandle(); - bk.removeAllWorkSheets(); - myfactory.setDebugLevel(this.DEBUGLEVEL); - bk.DEBUGLEVEL = this.DEBUGLEVEL; - oe.parseNBind(bk, fname); - this.sheethandles = bk.sheethandles; - this.mybook = bk.mybook; - } catch (Exception e) { - throw new WorkBookException( - "WorkBookHandle OOXML Read failed: " + e.toString(), - WorkBookException.UNSPECIFIED_INIT_ERROR, e); - } - - mybook.setIsExcel2007(true); - return true; - } - - /** - * do all initialization with a filename - * - * - * @param fname - */ - protected void initFromFile(File fx) { - String fname = fx.getPath(); - String finch = ""; - - // handle csv import - FileReader fincheck; - try { - fincheck = new FileReader(fx); - if (fx.length() > 100) { - char[] cbuf = new char[100]; - fincheck.read(cbuf); - finch = new String(cbuf); - } - fincheck.close(); - - } catch (FileNotFoundException e) { - Logger.logErr("WorkBookHandle: Cannot open file " + fname + ": " - + e); - } catch (Exception e1) { - Logger.logErr("Invalid XLSX/OOXML File."); - } - this.name = fname; // 20081231 KSC: set here - if (finch.toUpperCase().startsWith("PK")) { // it's a zip file... give - // XLSX parsing a shot - if (this.file != null) - OOXMLAdapter.refreshPassThroughFiles(this); - if (initXLSX(fname)) - return; - } - try { - myLEOFile = new LEOFile(fx, this.DEBUGLEVEL); - } catch (InvalidFileException ifx) { - if ((finch.indexOf(",") > -1) && (finch.indexOf(",") > -1)) { - // init a blank workbook - this.initDefault(); - - // map CSV into workbook - try { - WorkSheetHandle sheet = getWorkSheet(0); - sheet.readCSV(new BufferedReader(new FileReader(fx))); - return; - } catch (Exception e) { - throw new WorkBookException( - "Error encountered importing CSV: " + e.toString(), - WorkBookException.ILLEGAL_INIT_ERROR); - } - } else { - throw ifx; - } - - } - if (myLEOFile.hasWorkBook()) { - this.initFromLeoFile(myLEOFile); - } else { - // total failure to load - Logger.logErr("Initializing WorkBookHandle failed: " + fname - + " does not contain a supported Excel WorkBook."); - throw new InvalidFileException( - fname + " does not contian a supported Excel WorkBook."); - } - } - - /** - * Constructor which takes a ProgressListener which monitors the progress of - * creating a new Excel file. - * - * @param ProgressListener - * object which is monitoring progress of WorkBook read - */ - public WorkBookHandle(ProgressListener pn) { - this.plist = pn; - try { - byte[] b = getPrototypeBook(); - ByteBuffer bbf = ByteBuffer.wrap(b); - bbf.order(ByteOrder.LITTLE_ENDIAN); - myLEOFile = new LEOFile(bbf); - } catch (Exception e) { - throw new InvalidFileException( - "WorkBook could not be instantiated: " + e.toString()); - } - this.initFromLeoFile(myLEOFile); - } - - /** - * Constructor which takes the XLS file name - * - * and a ProgressListener which monitors the progress of reading the Excel file. - * - * @param String - * fname the name of the XLS file to read - * @param ProgressListener - * object which is monitoring progress of WorkBook read - */ - public WorkBookHandle(String fname, ProgressListener pn) { - this.plist = pn; - this.initFromFile(new File(fname)); - } - - /** - * For internal creation of a workbook handle from - * - * @param leo - */ - protected WorkBookHandle(LEOFile leo) { - this.initFromLeoFile(leo); - } - - /** - * init the new WorkBookHandle - */ - protected synchronized void initBytes(BlockByteReader blockByteReader) { - this.initWorkBookFactory(); - - if (plist != null) - myfactory.register(plist); // register progress notifier - myfactory.setDebugLevel(this.DEBUGLEVEL); - - mybook = (io.starter.formats.XLS.WorkBook) myfactory - .getWorkBook(blockByteReader, myLEOFile); - - if (dump_input != null) { - try { - dump_input.flush(); - dump_input.close(); - dump_input = null; - } catch (Exception e) { - ; - } - } - this.postLoad(); - } - - /** - * Handles tasks that need to occur after workbook has been loaded - */ - void postLoad() { - initHlinks(); - initMerges(); - mybook.initializeNames(); // must initialize name expressions AFTER - // loading sheet records - mybook.mergeMSODrawingRecords(); - mybook.initializeIndirectFormulas(); - initPivotCache(); // if any - } - - void initMerges() { - AbstractList mergelookup = mybook.getMergecelllookup(); - for (int t = 0; t < mergelookup.size(); t++) { - Mergedcells mc = (Mergedcells) mergelookup.get(t); - mc.initCells(this); - } - } - - void initHlinks() { - AbstractList hlinklookup = mybook.getHlinklookup(); - for (int t = 0; t < hlinklookup.size(); t++) { - Hlink hl = (Hlink) hlinklookup.get(t); - hl.initCells(this); - } - } - - /** - * reads in the pivot cache storage and parses the pivot cache records
        - * pivot cache(s) are used by pivot tables as data source storage - */ - void initPivotCache() { - if (myLEOFile.hasPivotCache()) { - PivotCache pc = new PivotCache(); // grab any pivot caches - try { - pc.init(myLEOFile.getDirectoryArray(), this); - mybook.setPivotCache(pc); - } catch (Exception e) { - - } - } - } - - /** - * Closes the WorkBook and releases resources. - */ - @Override - public void close() { - try { - if (myLEOFile != null) - myLEOFile.shutdown(); - myLEOFile = null; - } catch (Exception e) { - if (DEBUGLEVEL > 3) - Logger.logWarn("Closing Document: " + toString() + " failed: " - + e.toString()); - } - if (mybook != null) - mybook.close(); // clear out object refs to release memory - mybook = null; - myfactory = null; - name = null; - sheethandles = null; - // Runtime.getRuntime().gc(); - } - - @Override - protected void finalize() throws Throwable { - close(); - } - - @Override - public void reset() { - initFromFile(new File(myLEOFile.getFileName())); - } - - /** - * Returns an array of handles to all of the WorkSheets in the Workbook. - * - * @return WorkSheetHandle[] Array of all WorkSheets in WorkBook - */ - @Override - public WorkSheetHandle[] getWorkSheets() { - try { - if (myfactory != null) { - int numsheets = mybook.getNumWorkSheets(); - if (numsheets == 0) - throw new WorkSheetNotFoundException( - "WorkBook has No Sheets."); - WorkSheetHandle[] sheets = new WorkSheetHandle[numsheets]; - for (int i = 0; i < numsheets; i++) { - Boundsheet bs = mybook.getWorkSheetByNumber(i); - bs.setWorkBook(this.mybook); - sheets[i] = new WorkSheetHandle(bs, this); - } - return sheets; - } - return null; - } catch (WorkSheetNotFoundException a) { - Logger.logWarn("getWorkSheets() failed: " + a); - return null; - } - } - - /** - * returns the handle to a WorkSheet by number. - * - * Sheet 0 is the first Sheet. - * - * @param index - * of worksheet (ie: 0) - * @return WorkSheetHandle the WorkSheet - * @exception WorkSheetNotFoundException - * if the specified WorkSheet is not found in the WorkBook. - */ - @Override - public WorkSheetHandle getWorkSheet(int sheetnum) throws WorkSheetNotFoundException { - Boundsheet st = mybook.getWorkSheetByNumber(sheetnum); - if (sheethandles.get(st.getSheetName()) != null) - return sheethandles.get(st.getSheetName()); - else { - WorkSheetHandle shth = new WorkSheetHandle(st, this); - sheethandles.put(st.getSheetName(), shth); - return shth; - } - } - - Hashtable sheethandles = new Hashtable(); - - /** - * returns the handle to a WorkSheet by name. - * - * @param String - * name of worksheet (ie: "Sheet1") - * @return WorkSheetHandle the WorkSheet - * @exception WorkSheetNotFoundException - * if the specified WorkSheet is not found in the WorkBook. - */ - @Override - public WorkSheetHandle getWorkSheet(String handstr) throws WorkSheetNotFoundException { - if (sheethandles.get(handstr) != null) { - if (mybook.getWorkSheetByName(handstr) != null) - return sheethandles.get(handstr); - else - throw new WorkSheetNotFoundException(handstr + " not found"); - } - if (myfactory != null) { - Boundsheet bs = mybook.getWorkSheetByName(handstr); - if (bs != null) { - bs.setWorkBook(this.mybook); - WorkSheetHandle ret = new WorkSheetHandle(bs, this); - sheethandles.put(handstr, ret); - return ret; - } else { - throw new WorkSheetNotFoundException(handstr); - } - } - throw new WorkSheetNotFoundException( - "Cannot find WorkSheet " + handstr); - } - - /** - * returns the active or selected worksheet tab - * - * @return WorkSheetHandle - * @throws WorkSheetNotFoundException - */ - public WorkSheetHandle getActiveSheet() throws WorkSheetNotFoundException { - return this.getWorkSheet(this.getWorkBook().getSelectedSheetNum()); - } - - /** - * Returns a low-level WorkBook. - * - * NOTE: The WorkBook class is NOT a part of the published API. Any of the - * methods and/or variables on a WorkBook object are subject to change without - * notice in new versions of OpenXLS. - * - */ - @Override - public io.starter.formats.XLS.WorkBook getWorkBook() { - return this.mybook; - } - - /** - * Set Encoding mode of new Strings added to file. - * - * OpenXLS has 3 modes for handling the internal encoding of String data that is - * added to the file. - * - * OpenXLS can save space in the file if it knows that all characters in your - * String data can be represented with a single byte (Compressed.) - * - * If your String contains characters which need 2 bytes to represent (such as - * Eastern-language characters) then it needs to be stored in an uncompressed - * Unicode format. - * - * OpenXLS can either automatically detect the mode for each String, or you can - * set it explicitly. The auto mode is the most flexible but requires processing - * overhead. - * - * Default mode is WorkBookHandle.STRING_ENCODING_AUTO. - * - * Valid Modes Are: - * - * WorkBookHandle.STRING_ENCODING_AUTO Use if you are adding mixed Unicode and - * non-unicode Strings and can accept the performance hit -slowest String adds - * -optimal file size for mixed Strings WorkBookHandle.STRING_ENCODING_UNICODE - * Use if all of your new Strings are Unicode - faster than AUTO -faster than - * AUTO -largest file size WorkBookHandle.STRING_ENCODING_COMPRESSED Use if all - * of your new Strings are non-Unicode and can have high-bytes compressed - * -faster than AUTO -smallest file size - * - * @param int - * String Encoding Mode - */ - @Override - public void setStringEncodingMode(int mode) { - mybook.setStringEncodingMode(mode); - } - - /** - * Set Duplicate String Handling Mode. - * - *
        -	     The Duplicate String Mode determines the behavior of
        -	     the String table when inserting new Strings.
        -	     
        -	     The String table shares a single entry for multiple
        -	     Cells containing the same string.  When multiple Cells
        -	     have the same value, they share the same underlying string.
        -	     
        -	     Changing the value of any one of the Cells will change
        -	     the value for any Cells sharing that reference.
        -	     
        -	     For this reason, you need to determine
        -	     the handling of new strings added to the sheet that
        -	     are duplicates of strings already in the table.
        -	     
        -	     If you will be changing the values of these
        -	     new Cells, you will need to set the Duplicate
        -	     String Mode to ALLOWDUPES.  If the string table
        -	     encounters a duplicate entry being added, it
        -	     will insert a duplicate that can then be subsequently
        -	     changed without affecting the other duplicate Cells.
        -	     
        -	     Valid Modes Are:
        -	     
        -	         WorkBookHandle.ALLOWDUPES - faster String inserts, larger file sizes,  changing Cells has no effect on dupe Cells                   
        -	
        -	         WorkBookHandle.SHAREDUPES - slower inserts, dupe smaller file sizes, Cells share changes
        -	 * 
        - * - * @param int - * Duplicate String Handling Mode - */ - @Override - public void setDupeStringMode(int mode) { - mybook.setDupeStringMode(mode); - } - - /** - * Copies an existing Chart to another WorkSheet - * - * @param chartname - * @param sheetname - */ - @Override - public void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException { - mybook.copyChartToSheet(chartname, sheetname); - } - - /** - * Copies an existing Chart to another WorkSheet - * - * @param chart - * @param sheet - */ - @Override - public void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) throws ChartNotFoundException, WorkSheetNotFoundException { - mybook.copyChartToSheet(chart.getTitle(), sheet.getSheetName()); - } - - /** - * Copy (duplicate) a worksheet in the workbook and add it to the end of the - * workbook with a new name - * - * @param String - * the Name of the source worksheet; - * @param String - * the Name of the new (destination) worksheet; - * @return the new WorkSheetHandle - */ - @Override - public WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName) throws WorkSheetNotFoundException { - try { - mybook.copyWorkSheet(SourceSheetName, NewSheetName); - } catch (Exception e) { - throw new WorkBookException("Failed to copy WorkSheet: " - + SourceSheetName + ": " + e.toString(), - WorkBookException.RUNTIME_ERROR); - } - mybook.getRefTracker().clearPtgLocationCaches(NewSheetName); - // update the merged cells (requires a WBH, that's why it's - // here) - WorkSheetHandle wsh = this.getWorkSheet(NewSheetName); - if (wsh != null) { - List mc = wsh.getMysheet().getMergedCellsRecs(); - for (int i = 0; i < mc.size(); i++) { - Mergedcells mrg = (Mergedcells) mc.get(i); - if (mrg != null) - mrg.initCells(this); - } - // now conditional formats - /* - * mc = wsh.getMysheet().getConditionalFormats(); for (int - * i=0;i - * You generally need not call this method. Dirty formulas will automatically be - * recalculated when their values are queried. This method is only useful for - * forcing calculation to occur at a certain time. In the case of functions such - * as NOW() whose value is volatile the formula will still be recalculated every - * time it is queried. - * - * @throws FunctionNotSupportedException - * if an unsupported function is used by any formula in the workbook - * @see #markFormulasDirty() - */ - public void recalc() { - int calcmode = mybook.getCalcMode(); - mybook.setCalcMode(CALCULATE_AUTO); // ensure referenced functions are - // calcualted as necesary! - Formula[] formulas = mybook.getFormulas(); - for (int idx = 0; idx < formulas.length; idx++) { - try { - formulas[idx].clearCachedValue(); - formulas[idx].calculate(); - } catch (FunctionNotSupportedException fe) { - Logger.logErr("WorkBookHandle.recalc: Error calculating Formula " - + fe.toString()); - } - } - // KSC: Clear out lookup caches! - this.getWorkBook().getRefTracker().clearLookupCaches(); - mybook.setCalcMode(calcmode); // reset - } - - /** - * Removes all of the WorkSheets from this WorkBook. - * - * Bytes streamed from this WorkBook will create invalid Spreadsheet files - * unless a WorkSheet(s) are added to it. - * - * NOTE: A WorkBook with no sheets is *invalid* and will not open in Excel. You - * must add sheets to this WorkBook for it to be valid. - * - */ - @Override - public void removeAllWorkSheets() { - - try { - Object ob = this.mybook.getTabID().getTabIDs().get(0); - this.mybook.getTabID().getTabIDs().removeAllElements(); - this.mybook.getTabID().getTabIDs().add(ob); - this.mybook.getTabID().updateRecord(); - } catch (Exception ex) { - ; - } - WorkSheetHandle[] ws = this.getWorkSheets(); - try { - for (int x = 0; x < ws.length; x++) { - try { - ws[x].remove(); - } catch (WorkBookException e) { - ; - } // ignore the invalid WorkBook problem - } - } catch (Exception e) { - ; // in case sheets already gone... - } - this.sheethandles.clear(); - this.mybook.closeSheets(); // replaced below with this - - /* - * WHY ARE WE DOING THIS??? // init new book // save records - * then reset to avoid - * ByteStreamer.stream records expansion Object[] recs= - * this.getWorkBook().getStreamer().getBiffRecords(); - * - * // keep Excel 2007 status boolean isExcel2007= - * this.getIsExcel2007(); - * WorkBookHandle ret = new WorkBookHandle(this.getBytes()); - * ret.setIsExcel2007(isExcel2007); - * - * this.getWorkBook().getStreamer().setBiffRecords(Arrays. - * asList(recs)); - * this.mybook = ret.getWorkBook(); / - **/ - - } - - /** - * Returns a WorkBookHandle containing an empty version of this WorkBook. - * - * Use in conjunction with addSheetFromWorkBook() to create new output WorkBooks - * containing various sheets from a master template. - * - * ie: WorkBookHandle emptytemplate = this.getNoSheetWorkBook(); - * emptytemplate.addSheetFromWorkBook(this, "Sheet1", "TargetSheet"); - * - * - * @return WorkBookHandle - the empty WorkBookHandle duplicate - * @see addSheetFromWorkBook - */ - @Override - public WorkBookHandle getNoSheetWorkBook() { - // to avoid ByteStreamer.stream records expansion - Object[] recs = this.getWorkBook().getStreamer().getBiffRecords(); - byte[] gb = this.getBytes(); - WorkBookHandle ret = new WorkBookHandle(gb); - this.getWorkBook().getStreamer().setBiffRecords(Arrays.asList(recs)); - ret.removeAllWorkSheets(); - return ret; - } - - /** - * Inserts a worksheet from a Source WorkBook. - * - * - * - * @param sourceBook - * - the WorkBook containing the sheet to copy - * @param sourceSheetName - * - the name of the sheet to copy - * @param destSheetName - * - the name of the new sheet in this workbook - * @throws WorkSheetNotFoundException - * @deprecated - use addWorkSheet(WorkSheetHandle sht, String NewSheetName){ - */ - @Override - @Deprecated - public boolean addSheetFromWorkBook(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException { - return this.addWorkSheet(sourceBook - .getWorkSheet(sourceSheetName), destSheetName) != null; - } - - /** - * Inserts a worksheet from a Source WorkBook. Brings all string data and - * formatting information from the source workbook. - * - * Be aware this is programmatically creating a large amount of new formatting - * information in the destination workbook. A higher performance option will - * usually be using getNoSheetWorkbook and addSheetFromWorkBook. - * - * @param sourceBook - * - the WorkBook containing the sheet to copy - * @param sourceSheetName - * - the name of the sheet to copy - * @param destSheetName - * - the name of the new sheet in this workbook - * @throws WorkSheetNotFoundException - * @deprecated - use addWorkSheet(WorkSheetHandle sht, String NewSheetName){ - */ - @Deprecated - public boolean addSheetFromWorkBookWithFormatting(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException { - return this.addWorkSheet(sourceBook - .getWorkSheet(sourceSheetName), destSheetName) != null; - } - - /** - * Inserts a WorkSheetHandle from a separate WorkBookhandle into the current - * WorkBookHandle. - * - * copies charts, images, formats from source workbook - * - * Worksheet will be the same name as in the source workbook. To add a custom - * named worksheet use the addWorkSheet(WorkSheetHandle, String sheetname) - * method - * - * @param WorkSheetHandle - * the source WorkSheetHandle; - */ - public WorkSheetHandle addWorkSheet(WorkSheetHandle sourceSheet) { - return this.addWorkSheet(sourceSheet, sourceSheet.getSheetName()); - } - - /** - * Inserts a WorkSheetHandle from a separate WorkBookhandle into the current - * WorkBookHandle. - * - * copies charts, images, formats from source workbook - * - * @param WorkSheetHandle - * the source WorkSheetHandle; - * @param String - * the Name of the new (destination) worksheet; - */ - @Override - public WorkSheetHandle addWorkSheet(WorkSheetHandle sourceSheet, String NewSheetName) { - sourceSheet.getSheet().populateForTransfer(); // copy all formatting + - // images for this sheet - List chts = sourceSheet.getSheet().getCharts(); - for (int i = 0; i < chts.size(); i++) { - Chart cxi = (Chart) chts.get(i); - cxi.populateForTransfer(); - } - byte[] bao = sourceSheet.getSerialBytes(); - try { - mybook.addBoundsheet(bao, sourceSheet - .getSheetName(), NewSheetName, StringTool - .stripPath(sourceSheet.getWorkBook() - .getName()), true); - WorkSheetHandle wsh = this.getWorkSheet(NewSheetName); - if (wsh != null) { - List mc = wsh.getMysheet().getMergedCellsRecs(); - for (int i = 0; i < mc.size(); i++) { - Mergedcells mrg = (Mergedcells) mc.get(i); - if (mrg != null) - mrg.initCells(this); - } - } - - return wsh; - } catch (Exception e) { - throw new WorkBookException( - "Failed to copy WorkSheet: " + e.toString(), - WorkBookException.RUNTIME_ERROR); - } - } - - /** - * Utility method to copy a format handle from a separate WorkBookHandle to this - * WorkBookHandle - * - * @param externalFormat - * - FormatHandle from an external WorkBookHandle - * @return - */ - public FormatHandle transferExternalFormatHandle(FormatHandle externalFormat) { - Xf xf = externalFormat.getXf(); - FormatHandle newHandle = new FormatHandle(this); - newHandle.addXf(xf); - return newHandle; - } - - /** - * Creates a new Chart and places it at the end of the workbook - * - * @param String - * the Name of the newly created Chart - * @return the new ChartHandle - */ - public ChartHandle createChart(String name, WorkSheetHandle wsh) { - if (wsh == null) { - // this is a sheetless chart - TODO: - } - /* - * a chart needs a supbook, externsheet, & MSO object in the - * book stream. I - * think this is due to the fact that the referenced series - * are usually stored - * in the fashon 'Sheet1!A4:B6' The sheet1 reference - * requires a supbook, though - * the reference is internal. - */ - - try { - ObjectInputStream ois = new ObjectInputStream( - new ByteArrayInputStream(getPrototypeChart())); - Chart newchart = (Chart) ois.readObject(); - newchart.setWorkBook(this.getWorkBook()); - if (this.getIsExcel2007()) - newchart = new OOXMLChart(newchart, this); - mybook.addPreChart(); - mybook.addChart(newchart, name, wsh.getSheet()); - /* - * add font recs if nec: for the default chart: default - * chart text fonts are # 5 - * & 6 title # 7 axis # 8 - */ - ChartHandle bs = new ChartHandle(newchart, this); - int nfonts = mybook.getNumFonts(); - while (nfonts < 8) { // ensure - Font f = new Font("Arial", Font.PLAIN, 200); - mybook.insertFont(f); - nfonts++; - } - Font f = mybook.getFont(8); // axis title font - if (f.toString() - .equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { - // it's default text font -- change to default axis title - // font - f = new Font("Arial", Font.BOLD, 240); - bs.setAxisFont(f); - } - f = mybook.getFont(7); // chart title font - if (f.toString() - .equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { - // it's default text font -- change to default title font - f = new Font("Arial", Font.BOLD, 360); - bs.setTitleFont(f); - } - bs.removeSeries(0); // remove the "dummied" series - bs.setAxisTitle(ChartHandle.XAXIS, null); // remove default axis - // titles, if any - bs.setAxisTitle(ChartHandle.YAXIS, null); // "" - return bs; - } catch (Exception e) { - Logger.logErr("Creating New Chart: " + name + " failed: " + e); - return null; - } - } - - /** - * delete an existing chart of the workbook - * - * @param chartname - */ - public void deleteChart(String chartname, WorkSheetHandle wsh) throws ChartNotFoundException { - try { - mybook.deleteChart(chartname, wsh.getSheet()); - } catch (ChartNotFoundException e) { - throw new ChartNotFoundException( - "Removing Chart: " + chartname + " failed: " + e); - } catch (Exception e) { - Logger.logErr("Removing Chart: " + chartname + " failed: " + e); - } - } - - /** - * Returns the number of Sheets in this WorkBook - * - * @return int number of Sheets - */ - public int getNumWorkSheets() { - return mybook.getNumWorkSheets(); - } - - /** - * Creates a new worksheet and places it at the specified position. The new - * sheet will be inserted before the sheet currently at the given index. If the - * given index is higher than the last index currently in use, the sheet will be - * added to the end of the workbook and will receive an index one higher than - * that of the current final sheet. If the given index is negative it will be - * interpreted as 0. - * - * @param name - * the name of the newly created worksheet - * @param sheetpos - * the index at which the sheet should be inserted - * @return the new WorkSheetHandle - */ - public WorkSheetHandle createWorkSheet(String name, int sheetpos) { - if (sheetpos > this.getNumWorkSheets()) - sheetpos = this.getNumWorkSheets(); - if (sheetpos < 0) - sheetpos = 0; - - WorkSheetHandle s = this.createWorkSheet(name); - s.setTabIndex(sheetpos); - return s; - } - - /** - * Creates a new worksheet and places it at the end of the workbook. - * - * @param name - * the name of the newly created worksheet - * @return the new WorkSheetHandle - */ - @Override - public WorkSheetHandle createWorkSheet(String name) { - try { - this.getWorkSheet(name); - throw new WorkBookException( - "Attempting to add worksheet with duplicate name. " + name - + " already exists in " + this.toString(), - WorkBookException.RUNTIME_ERROR); - } catch (WorkSheetNotFoundException ex) { - ; // good! - } - - Boundsheet bo = null; - try { - ObjectInputStream ois = new ObjectInputStream( - new ByteArrayInputStream(getPrototypeSheet())); - bo = (Boundsheet) ois.readObject(); - mybook.addBoundsheet(bo, null, name, null, false); - try { - WorkSheetHandle bs = this.getWorkSheet(name); - if (this.mybook.getNumWorkSheets() == 1) { - bs.setSelected(true); // it's the only sheet so select! - } else { - bs.setSelected(false); - } - return bs; - } catch (WorkSheetNotFoundException e) { - Logger.logWarn("Creating New Sheet: " + name + " failed: " + e); - return null; - } - } catch (Exception e) { - Logger.logWarn("Error loading prototype sheet: " + e); - return null; - } - } - - /** - * Returns an array of all FormatHandles in the workbook - */ - @Override - public FormatHandle[] getFormats() { - List l = this.mybook.getXfrecs(); - FormatHandle[] formats = new FormatHandle[l.size()]; - Iterator its = l.iterator(); - int i = 0; - while (its.hasNext()) { - Xf x = (Xf) its.next(); - // passing (this) with the format handle breaks the - // relationship to the font. - // if you need to pass it in we will have to handle it - // differently - try { - formats[i] = new FormatHandle(); - formats[i].setWorkBook(this.getWorkBook()); - formats[i].setXf(x); - } catch (Exception ex) { - ; - } - i++; - } - return formats; - } - - /** - * Returns an array of all Conditional Formats in the workbook - * - * these are formats referenced and used by the conditionally formatted ranges - * in the workbook. - * - * - * - * @return - */ - public FormatHandle[] getConditionalFormats() { - // the idea is to create a fake IXFE for use by - // sheetster to find formats - // int cfxe = this.getWorkBook().getNumFormats() + 50000; // - // there would have to - // be 50k styles on the sheet to conflict here.... - // int cfxe = this.getWorkBook().getNumXfs() + 50000; // - // there would have to be - // 50k styles on the sheet to conflict here.... - - List retl = new Vector(); - AbstractList v = this.mybook.getSheetVect(); - - Iterator its = v.iterator(); - - while (its.hasNext()) { - Boundsheet shtx = (Boundsheet) its.next(); - List fmtlist = shtx.getConditionalFormats(); - Iterator ixa = fmtlist.iterator(); - while (ixa.hasNext()) { - Condfmt cfm = (Condfmt) ixa.next(); - // cfm.initCells(this); // added! - int cfxe = cfm.getCfxe(); - FormatHandle fz = new FormatHandle(cfm, this, cfxe, null); - - fz.setFormatId(cfxe); - retl.add(fz); - // cfm.setCfxe(cfxe); - // cxfe++; - } - } - FormatHandle[] formats = new FormatHandle[retl.size()]; - for (int t = 0; t < formats.length; t++) - formats[t] = retl.get(t); - return formats; - } - - /** - * Set the recursion levels allowed for formulas calculated in this workbook - * before a circular reference is thrown. - * - * Default setting is 250 levels of recursion - * - * @param recursion_allowed - */ - public static void setFormulaRecursionLevels(int recursion_allowed) { - RECURSION_LEVELS_ALLOWED = recursion_allowed; - } - - public String getWorkingDirectory() { - // TODO Auto-generated method stub - return null; - } - - public void initSharedFormulas() { - // TODO Auto-generated method stub - - } + /** + * A Writer to which a record dump should be written on input. This is used by + * the dumping code in WorkBookFactory. + */ + public static Writer dump_input = null; + + protected io.starter.formats.XLS.WorkBook mybook; + protected LEOFile myLEOFile; + protected WorkBookFactory myfactory = null; + + protected ProgressListener plist; + + /** + * Format constant for BIFF8 (Excel '97-2007). + */ + public static final int FORMAT_XLS = 100; + + /** + * Format constant for normal OOXML (Excel 2007). + */ + public static final int FORMAT_XLSX = 101; + + /** + * Format constant for macro-enabled OOXML (Excel 2007). + */ + public static final int FORMAT_XLSM = 102; + + /** + * Format constant for OOXML template (Excel 2007). + */ + public static final int FORMAT_XLTX = 103; + + /** + * Format constant for macro-enabled OOXML template (Excel 2007). + */ + public static final int FORMAT_XLTM = 104; + + private static byte[] protobook; + private static byte[] protochart; + private static byte[] protosheet; + public static java.text.SimpleDateFormat simpledateformat = new java.text.SimpleDateFormat(); // static + // to + // reuse + + /** + * How many recursion levels to allow formulas to be calculated before throwing + * a circular reference error + */ + public static int RECURSION_LEVELS_ALLOWED = 107; + + /** + * This is + **/ + public static String CONVERTMULBLANKS = "deprecated"; + + protected static byte[] getPrototypeBook() throws IOException { + if (protobook == null) + + protobook = ResourceLoader + .getBytesFromJar("/io/starter/OpenXLS/templates/prototysspe.ser"); + + return protobook; + } + + protected static byte[] getPrototypeSheet() { + if (protosheet == null) + try { + WorkBookHandle bookhandle = new WorkBookHandle(); + io.starter.formats.XLS.WorkBook book = bookhandle.getWorkBook(); + Boundsheet sheet = book.getWorkSheetByNumber(0); + protosheet = sheet.getSheetBytes(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return protosheet; + } + + protected static byte[] getPrototypeChart() { + if (protochart == null) { + try { + byte[] bookbytes = ResourceLoader + .getBytesFromJar("/io/starter/OpenXLS/templates/prototypechart.ser"); + WorkBookHandle chartBook = new WorkBookHandle(bookbytes); + ChartHandle ch = chartBook.getCharts()[0]; + protochart = ch.getSerialBytes(); + return protochart; + } catch (IOException e) { + Logger.logErr("Unable to get default chart bytes"); + } + } + return protochart; + } + + /** + * Gets the internal Factory object. + *

        + * WARNING: This method is not part of the public API. Its use + * is not supported and behavior is subject to change. + *

        + */ + public WorkBookFactory getFactory() { + return myfactory; + } + + /** + * Gets the internal LEOFile object. + *

        + * WARNING: This method is not part of the public API. Its use + * is not supported and behavior is subject to change. + *

        + */ + public LEOFile getLEOFile() { + return myLEOFile; + } + + /** + * Searches all Cells in the workbook for the string occurrence and replaces + * with the replacement text. + * + * @return the number of replacements that were made + */ + public int searchAndReplace(String searchfor, String replacewith) { + CellHandle[] cx = getCells(); + int foundcount = 0; + for (int t = 0; t < cx.length; t++) { + if (!(cx[t].getCell() instanceof Formula)) { + // find the string + if (!cx[t].isNumber()) { + String v = cx[t].getStringVal(); + if (v.indexOf(searchfor) > -1) { + cx[t].setVal(StringTool + .replaceText(v, searchfor, replacewith)); + foundcount++; + } + } + } + } + return foundcount; + } + + /** + * Returns all strings that are in the SharedStringTable for this workbook. The + * SST contains all standard string records in cells, but may not include such + * things as strings that are contained within formulas. This is useful for such + * things as full text indexing of workbooks + * + * @return Strings in the workbook. + */ + public String[] getAllStrings() { + return this.mybook.getAllStrings(); + } + + /** + * Get either the default color table, or the color table from the custom + * palatte(if exists) from the WorkBook + * + * @return + */ + @Override + public java.awt.Color[] getColorTable() { + return this.getWorkBook().getColorTable(); + } + + /** + * Returns whether this book uses the 1904 date format. + * + * @deprecated Use {@link #getDateFormat()} instead. + */ + @Deprecated + public boolean is1904() { + return mybook.is1904(); + } + + /** + * Gets the date format used by this book. + */ + @Override + public DateConverter.DateFormat getDateFormat() { + return mybook.getDateFormat(); + } + + /** + * Returns the lowest version of Excel compatible with the input file. + * + * @return an Excel version string + */ + public String getXLSVersionString() { + return mybook.getXLSVersionString(); + } + + /** + * Return useful statistics about this workbook. + * + * @param use html line breaks + * @return a string contatining various statistics. + */ + public String getStats(boolean usehtml) { + return mybook.getStats(usehtml); + } + + /** + * Return useful statistics about this workbook. + * + * @return a string contatining various statistics. + */ + public String getStats() { + return mybook.getStats(); + } + + /** + * Returns the Cell at the specified Location + * + * @param address + * @return + */ + @Override + public CellHandle getCell(String address) throws CellNotFoundException, WorkSheetNotFoundException { + int shtpos = address.indexOf("!"); + if (shtpos < 0) + throw new CellNotFoundException(address + + " not found. You need to specify a location in the format: Sheet1!A1"); + String sheetstr = address.substring(0, shtpos); + WorkSheetHandle sht = this.getWorkSheet(sheetstr); + String celstr = address.substring(shtpos + 1); + return sht.getCell(celstr); + } + + /** + * Returns an Array of the CellRanges existing in this WorkBook specifically the + * Ranges referenced in Formulas, Charts, and Named Ranges. + *

        + * This is necessary to allow for automatic updating of references when + * adding/removing/moving Cells within these ranges, as well as shifting + * references to Cells in Formulas when Formula records are moved. + * + * @return all existing Cell Range references used in Formulas, Charts, and + * Names + */ + public CellRange[] getCellRanges() { + return this.mybook.getRefTracker().getCellRanges(); + } + + /** + * get a handle to a PivotTable in the WorkBook + * + * @param String name of the PivotTable + * @return PivotTable the PivotTable + */ + @Override + public PivotTableHandle getPivotTable(String ptname) throws PivotTableNotFoundException { + Sxview st = mybook.getPivotTableView(ptname); + if (st == null) + throw new PivotTableNotFoundException(ptname); + return new PivotTableHandle(st, this); + } + + /** + * get an array of handles to all PivotTables in the WorkBook + * + * @return PivotTable[] all of the WorkBooks PivotTables + */ + @Override + public PivotTableHandle[] getPivotTables() throws PivotTableNotFoundException { + Sxview[] sxv = mybook.getAllPivotTableViews(); + if (sxv == null || sxv.length == 0) + throw new PivotTableNotFoundException( + "There are no PivotTables defined in: " + this.getName()); + PivotTableHandle[] pth = new PivotTableHandle[sxv.length]; + for (int t = 0; t < pth.length; t++) { + pth[t] = new PivotTableHandle(sxv[t], this); + } + return pth; + } + + /** + * Set the calculation mode for the workbook. + *

        + * CALCULATE_AUTO is the default for new workbooks. Calling Cell.getVal() will + * calculate formulas if they exist within the cell. + *

        + * CALCULATE_EXPLICIT will return present, cached value of the cell. Formula + * calculation will ONLY occur when explicitly called through the Formula + * Handle.calculate() method. + *

        + * CALCULATE_ALWAYS will ignore the cache and force a recalc every time a cell + * value is requested. + *

        + *

        + * WorkBookHandle.CALCULATE_AUTO WorkBookHandle.CALCULATE_ALWAYS + * WorkBookHandle.CALCULATE_EXPLICIT + * + * @param CalcMode Calculation mode to use in workbook. + */ + @Override + public void setFormulaCalculationMode(int CalcMode) { + mybook.setCalcMode(CalcMode); + } + + /** + * Get the calculation mode for the workbook. + *

        + * CALCULATE_ALWAYS is the default for new workbooks. Calling Cell.getVal() will + * calculate formulas if they exist within the cell. + *

        + * CALCULATE_EXPLICIT will return present value of the cell. Formula calculation + * will only occur when explicitly called through the Formula Handle + *

        + * WorkBookHandle.CALCULATE_ALWAYS -- recalc every time the cell value is + * requested (no cacheing) WorkBookHandle.CALCULATE_EXPLICIT -- recalc only when + * FormulaHandle.calculate() called WorkBookHandle.CALCULATE_AUTO -- only recac + * when changes + * + * @param CalcMode Calculation mode to use in workbook. + */ + @Override + public int getFormulaCalculationMode() { + return mybook.getCalcMode(); + } + + /** + * set the workbook to protected mode + *

        + * Note: the password cannot be decrypted or changed in Excel -- protection can + * only be set/removed using OpenXLS + * + * @param boolean whether to protect the book + */ + @Override + public void setProtected(boolean protect) { + // TODO: Check that this behavior is correct + // This is what the old implementation did + + BookProtectionManager protector = mybook.getProtectionManager(); + + // Excel default... no kidding! + if (protect) + protector.setPassword("VelvetSweatshop"); + else + protector.setPassword(null); + + protector.setProtected(protect); + } + + /** + * set Default row height in twips (=1/20 of a point) + *

        + * Note: only affects undefined Rows containing Cells + * + * @param int Default Row Height + */ + // should be a double as Excel units are 1/20 of what is + // stored in + // defaultrowheight + // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 + // (approx) + // should expect users to use Excel units and target method + // do the 20* + // conversion + @Override + public void setDefaultRowHeight(int t) { + mybook.setDefaultRowHeight(t); + } + + /** + * Set the default column width across all worksheets
        + * This setting is a worksheet level setting, so will be applied to all existing + * worksheets. individual worksheets can also be set using + * WorkSheetHandle.setDefaultColWidth + *

        + * This setting is roughly the width of the character '0' The default width of a + * column is 8. + */ + @Override + public void setDefaultColWidth(int t) { + mybook.setDefaultColWidth(t); + } + + /** + * Returns a Formula Handle + * + * @return FormulaHandle a formula handle in the WorkBook + */ + public FormulaHandle getFormulaHandle(String celladdress) throws FormulaNotFoundException { + Formula formula = mybook.getFormula(celladdress); + return new FormulaHandle(formula, this); + } + + /** + * Returns all ImageHandles in the workbook + * + * @return + */ + public ImageHandle[] getImages() { + List ret = new Vector(); + for (int t = 0; t < this.getNumWorkSheets(); t++) { + try { + ImageHandle[] r = this.getWorkSheet(t).getImages(); + for (int x = 0; x < r.length; x++) + ret.add(r[x]); + } catch (Exception ex) { + } + } + ImageHandle[] retx = new ImageHandle[ret.size()]; + ret.toArray(retx); + return retx; + } + + /** + * Returns an ImageHandle for manipulating images in the WorkBook + * + * @param imagename + * @return + */ + public ImageHandle getImage(String imagename) throws ImageNotFoundException { + for (int t = 0; t < this.getNumWorkSheets(); t++) { + try { + ImageHandle[] r = this.getWorkSheet(t).getImages(); + for (int x = 0; x < r.length; x++) + if (r[x].getName().equals(imagename)) + return r[x]; + } catch (Exception ex) { + } + } + throw new ImageNotFoundException( + "Image not found: " + imagename + " in " + this.toString()); + } + + /** + * Returns a Named Range Handle + * + * @return NameHandle a Named range in the WorkBook + */ + @Override + public NameHandle getNamedRange(String rangename) throws CellNotFoundException { + Name nand = mybook.getName(rangename.toUpperCase()); // case-insensitive + if (nand == null) + throw new CellNotFoundException(rangename); + return new NameHandle(nand, this); + } + + /** + * Returns a Named Range Handle if it exists in the specified scope. + *

        + * This can be used to distinguish between multiple named ranges with the same + * name but differing scopes + * + * @return NameHandle a Named range in the WorkBook that exists in the scope + */ + public NameHandle getNamedRangeInScope(String rangename) throws CellNotFoundException { + Name nand = mybook.getScopedName(rangename); + if (nand == null) + throw new CellNotFoundException(rangename); + return new NameHandle(nand, this); + } + + /** + * Create a named range in the workbook + *

        + * Note that the named range designation can conform to excel specs, that is, + * boolean values, references, or string variables can be set. Remember to + * utilize the sheet name when setting referential names. + *

        + *

        + * NameHandle nh = createNamedRange("cellRange", "Sheet1!A1:B3"); NameHandle nh + * = createNamedRange("trueRange", "=true"); + * + * @param name The name that should be used to reference this named range + * @param rangeDef Range of the cells for this named range, in excel syntax including + * sheet name, ie "Sheet1!A1:D1" + * @return NameHandle for modifying the named range + */ + public NameHandle createNamedRange(String name, String rangeDef) { + NameHandle nh = new NameHandle(name, rangeDef, this); + return nh; + } + + /** + * Returns a Chart Handle + * + * @return ChartHandle a Chart in the WorkBook + */ + // KSC: NOTE: this methodology needs work as a book may + // contain charts in + // different sheets containing the same name + // TODO: rethink + @Override + public ChartHandle getChart(String chartname) throws ChartNotFoundException { + return new ChartHandle(mybook.getChart(chartname), this); + } + + /** + * Returns all Chart Handles contained in the WorkBook + * + * @return ChartHandle[] an array of all Charts in the WorkBook + */ + @Override + public ChartHandle[] getCharts() { + AbstractList cv = mybook.getChartVect(); + ChartHandle[] cht = new ChartHandle[cv.size()]; + for (int x = 0; x < cv.size(); x++) { + cht[x] = new ChartHandle((Chart) cv.get(x), this); + } + return cht; + } + + /** + * retrieve a ChartHandle via id + * + * @param id + * @return + * @throws ChartNotFoundException + */ + public ChartHandle getChartById(int id) throws ChartNotFoundException { + AbstractList cv = mybook.getChartVect(); + Chart cht = null; + for (int x = 0; x < cv.size(); x++) { + cht = (Chart) cv.get(x); + if (cht.getId() == id) + return new ChartHandle(cht, this); + } + throw new ChartNotFoundException("Id " + id); + } + + /** + * Returns all Named Range Handles + * + * @return NameHandle[] all of the Named ranges in the WorkBook + */ + @Override + public NameHandle[] getNamedRanges() { + Name[] nand = mybook.getNames(); + NameHandle[] nands = new NameHandle[nand.length]; + for (int x = 0; x < nand.length; x++) { + nands[x] = new NameHandle(nand[x], this); + } + return nands; + } + + /** + * Returns all Named Range Handles scoped to WorkBook. + *

        + * Note this will not include worksheet scoped named ranges + * + * @return NameHandle[] all of the Named ranges that are scoped to WorkBook + */ + public NameHandle[] getNamedRangesInScope() { + Name[] nand = mybook.getWorkbookScopedNames(); + NameHandle[] nands = new NameHandle[nand.length]; + for (int x = 0; x < nand.length; x++) { + nands[x] = new NameHandle(nand[x], this); + } + return nands; + } + + /** + * Returns the name of this WorkBook + * + * @return String name of WorkBook + */ + @Override + public String getName() { + if (name != null) + return name; + return "New Spreadsheet"; + } + + /** + * Returns an array containing all cells in the WorkBook + * + * @return CellHandle array of all book cells + */ + @Override + public CellHandle[] getCells() { + BiffRec[] allcz = this.mybook.getCells(); + CellHandle[] ret = new CellHandle[allcz.length]; + Mulblank aMul = null; + short c = -1; + for (int t = 0; t < ret.length; t++) { + ret[t] = new CellHandle(allcz[t], this); + if (allcz[t].getOpcode() == XLSConstants.MULBLANK) { + // handle Mulblanks: ref a range of cells; to get correct + // cell address, + // traverse thru range and set cellhandle ref to correct + // column + if (allcz[t] == aMul) { + c++; + } else { + aMul = (Mulblank) allcz[t]; + c = (short) aMul.getColFirst(); + } + ret[t].setBlankRef(c); // for Mulblank use only -sets correct + // column reference for multiple blank + // cells + // ... + } + } + return ret; + } + + /** + * Returns the number of Cells in this WorkBook + * + * @return int number of Cells + */ + @Override + public int getNumCells() { + return mybook.getNumCells(); + } + + /** + * Returns whether the sheet selection tabs should be shown. + */ + public boolean showSheetTabs() { + return mybook.showSheetTabs(); + } + + /** + * Sets whether the sheet selection tabs should be shown. + */ + public void setShowSheetTabs(boolean show) { + mybook.setShowSheetTabs(show); + } + + /** + * Gets the spreadsheet as a byte array in BIFF8 (Excel '97-2003) format. + * + * @deprecated Writing the spreadsheet to a byte array uses a great deal of + * memory and generally provides no benefit over streaming output. + * Use the {@link #write} family of methods instead. If you need a + * byte array use {@link ByteArrayOutputStream}. + */ + @Override + @Deprecated + public byte[] getBytes() { + try { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + writeBytes(bout); + + return bout.toByteArray(); + } catch (Exception e1) { + Logger.logErr("Getting Spreadsheet bytes failed.", e1); + return null; + } + } + + /** + * Writes the document to the given path. If the filename ends with ".xlsx" or + * ".xlsm", the workbook will be written as OOXML (XLSX). Otherwise it will be + * written as BIFF8 (XLS). For OOXML, if the file has a VBA project the file + * extension must be ".xlsm". It will be changed if necessary. + * + * @param path the path to which the document should be written + * @deprecated The filename-based format choosing is counter-intuitive and + * failure-prone. Use {@link #write(OutputStream, int)} instead. + */ + @Deprecated + public void write(String path) { + String ext = path.toLowerCase(); + write(path, ext.endsWith(".xlsx") || ext.endsWith(".xlsm")); + } + + /** + * Writes the document to the given file in either XLS or XLSX. For OOXML, if + * the file has a VBA project the file extension must be ".xlsm". It will be + * changed if necessary. + * + * @param path the path to which the document should be written + * @param ooxml If true, write as OOXML (XLSX). Otherwise, write as + * BIFF8 (XLS). + * @deprecated The boolean format parameter is not flexible enough to represent + * all supported formats. Use {@link #write(File, int)} instead. + */ + @Deprecated + public void write(String path, boolean ooxml) { + int format; + if (ooxml) { + if (this.getIsExcel2007()) + format = this.getFormat(path); + else + format = FORMAT_XLSX; + + if (!OOXMLAdapter.hasMacros(this)) + path = StringTool.replaceExtension(path, ".xlsx"); + else // it's a macro-enabled workbook + path = StringTool.replaceExtension(path, ".xlsm"); + } else + format = FORMAT_XLS; + + try { + this.write(new File(path), format); + } catch (Exception e) { + throw new WorkBookException("error writing workbook", + WorkBookException.WRITING_ERROR, e); + } + } + + /** + * Writes the document to the given stream in either XLS or XLSX format. + * + * @param dest the stream to which the document should be written + * @param ooxml If true, write as OOXML (XLSX). Otherwise, write as + * BIFF8 (XLS). + * @deprecated The boolean format parameter is not flexible enough to represent + * all supported formats. Use {@link #write(OutputStream, int)} + * instead. + */ + @Deprecated + public void write(OutputStream dest, boolean ooxml) { + int format; + if (ooxml) { + if (this.getIsExcel2007()) + format = this.getFormat(); + else + format = FORMAT_XLSX; + } else + format = FORMAT_XLS; + + try { + if (format > WorkBookHandle.FORMAT_XLS && this.file != null) { + OOXMLAdapter.refreshPassThroughFiles(this); + } + this.write(dest, format); + } catch (Exception e) { + throw new WorkBookException("error writing workbook", + WorkBookException.WRITING_ERROR, e); + } + } + + /** + * Gets the constant representing this document's native format. + */ + @Override + public int getFormat() { + String name = this.getFileName().toLowerCase(); + + if (this.getIsExcel2007()) { + if (OOXMLAdapter.hasMacros(this)) + return name.endsWith(".xltm") ? FORMAT_XLTM : FORMAT_XLSM; + else + return name.endsWith(".xltx") ? FORMAT_XLTX : FORMAT_XLSX; + } else + return FORMAT_XLS; + } + + /** + * Gets the constant representing this document's desired format + */ + public int getFormat(String path) { + if (path == null) + return getFormat(); + if (this.getIsExcel2007()) { + if (OOXMLAdapter.hasMacros(this)) + return path.endsWith(".xltm") ? FORMAT_XLTM : FORMAT_XLSM; + else + return path.endsWith(".xltx") ? FORMAT_XLTX : FORMAT_XLSX; + } else + return FORMAT_XLS; + } + + @Override + public String getFileExtension() { + switch (this.getFormat()) { + case FORMAT_XLSX: + return ".xlsx"; + case FORMAT_XLSM: + return ".xlsm"; + case FORMAT_XLTX: + return ".xltx"; + case FORMAT_XLTM: + return ".xltm"; + case FORMAT_XLS: + return ".xls"; + default: + return ""; + } + } + + /** + * Writes the document to the given stream in the requested format. + *

        + * format choices: + *

        + * WorkBookHandle.FORMAT_XLS for 2003 and previous versions
        + * WorkBookHandle.FORMAT_XLSX for non-macro-enabled 2007 version
        + * WorkBookHandle.FORMAT_XLSM for macro-enabled 2007 version
        + * WorkBookHandle.FORMAT_XLTM for macro-enabled 2007 templates.
        + * WorkBookHandle.FORMAT_XLTX for 2007 templates, + *

        + * IMPORTANT NOTE: if the resulting filename contains the .XLSM extension + *
        + * the WorkBook MUST be written in FORMAT_XLSM; otherwise open errors + * will occur + *

        + * NOTE: If the format is FORMAT_XLSX and the filename contains macros + *
        + * the file will be written as Macro-Enabled i.e. in FORMAT_XLSM. In these + * cases,
        + * the filename must contain the .XLSM extension + * + * @param dest the stream to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the stream + */ + @Override + public void write(OutputStream dest, int format) throws IOException { + if (format == FORMAT_NATIVE) + format = this.getFormat(); + + switch (format) { + case FORMAT_XLSX: + case FORMAT_XLSM: + case FORMAT_XLTX: + case FORMAT_XLTM: + try { + if (this.file != null) + OOXMLAdapter.refreshPassThroughFiles(this); + + OOXMLWriter adapter = new OOXMLWriter(); + adapter.setFormat(format); + adapter.getOOXML(this, dest); + } catch (IOException e) { + throw e; + } catch (Exception e) { + // TODO: OOXMLAdapter only throws IOException, change its + // throws + throw new WorkBookException("error writing workbook", + WorkBookException.WRITING_ERROR, e); + } + break; + + case FORMAT_XLS: + try { + this.mybook.getStreamer().writeOut(dest); + } catch (io.starter.formats.XLS.WorkBookException e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException) + throw (IOException) cause; + throw e; + } + break; + + default: + throw new IllegalArgumentException("unknown output format"); + } + } + + /** + * Writes the document to the given stream in the requested OOXML format. + * + * @param dest the stream to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the stream + * @deprecated This method is like {@link #write(OutputStream, int)} except it + * only supports OOXML formats. Use that instead. + */ + @Deprecated + public void writeXLSXBytes(OutputStream dest, int format) throws Exception { + this.write(dest, format); + } + + /** + * Writes the document to the given stream in the default OOXML format. + * + * @param dest the stream to which the document should be written + * @throws IOException if an error occurs while writing to the stream + * @deprecated Use {@link #write(OutputStream, int}) instead. + */ + @Deprecated + public void writeXLSXBytes(OutputStream dest) throws Exception { + this.write(dest, true); + } + + /** + * Returns whether the underlying spreadsheet is in Excel 2007 format by + * default. + *

        + * Even if this method returns true, it is still possible to write out the file + * as a BIFF8 (Excel 97-2003) file, but unsupported features will be dropped, + * and some files could experience corruption. + * + * @return whether the underlying spreadsheet is Excel 2007 format + */ + public boolean getIsExcel2007() { + return this.mybook.getIsExcel2007(); + } + + /** + * Sets whether this Workbook is in Excel 2007 format. Excel 2007 format + * contains larger maximum column and row contraints, for example.
        + * Even if the workbook is set to Excel 2007 format, it is still possible to + * write out the file as a BIFF8 (Excel 97-2003) file, but unsupported features + * will be dropped, and some files could experience corruption. + * + * @param isExcel2007 + */ + public void setIsExcel2007(boolean isExcel2007) { + this.mybook.setIsExcel2007(isExcel2007); + } + + /** + * Writes the document to the given stream in BIFF8 (XLS) format. + * + *

        + * To output a debugging StringBuffer, you must first set the + * autolockdown setting:
        + * + * props.put("io.starter.OpenXLS.autocreatelockdown","true"); + * + *

        + * + * @param dest the stream to which the document should be written + * @return for debugging: a StringBuffer containing an output of the record + * bytes streamed + * @deprecated Use {@link #write(OutputStream, int)} instead. + */ + @Override + @Deprecated + public StringBuffer writeBytes(OutputStream dest) { + StringBuffer sb = this.mybook.getStreamer().writeOut(dest); + return sb; + } + + /** + * Default constructor creates a new, empty Spreadsheet with + *

        + * 3 WorkSheets: "Sheet1","Sheet2",and "Sheet3". + */ + public WorkBookHandle() { + // Xf.DEFAULTIXFE= 15; // reset to default in cases of + // having previously read + // Excel2007 template which may have set defaultXF + // differently + this.initDefault(); + } + + /** + * Constructor creates a new, empty Spreadsheet with 3 worksheets: "Sheet1", + * "Sheet2" and "Sheet3"
        + * This version allows flagging the workbook as Excel 2007 format.
        + * Excel 2007 format contains larger maximum column and row contraints, for + * example.
        + * Even if the workbook is set to Excel 2007 format, it is still possible to + * write out the file as a BIFF8 (Excel 97-2003) file, but unsupported features + * will be dropped, and some files could experience corruption. + * + * @param boolean Excel2007 - true if set to Excel 2007 version + */ + public WorkBookHandle(boolean Excel2007) { + this.initDefault(); + this.setIsExcel2007(Excel2007); + } + + /** + * another handle to the useful ability to load a book from the prorotype bytes + */ + protected void initDefault() { + try { + byte[] b = getPrototypeBook(); + if (b == null) { + throw new io.starter.formats.XLS.WorkBookException( + "Unable to load prototype workbook.", + WorkBookException.LICENSING_FAILED); + } + ByteBuffer bbf = ByteBuffer.wrap(b); + bbf.order(ByteOrder.LITTLE_ENDIAN); + myLEOFile = new LEOFile(bbf); + } catch (Exception e) { + throw new InvalidFileException( + "WorkBook could not be instantiated: " + e.toString()); + } + this.initFromLeoFile(myLEOFile); + } + + /** + * constructor which takes an InputStream containing the bytes of a valid XLS + * file. + * + * @param InputStream contains the valid BIFF8 bytes for reading + */ + public WorkBookHandle(InputStream inx) { + this.initFromStream(inx); + } + + /** + * Initialization of this workbook handle from a leoFile; + */ + private void initFromLeoFile(LEOFile leo) { + this.myLEOFile = leo; + try { + BlockByteReader bar = myLEOFile.getXLSBlockBytes(); + this.initBytes(bar); + this.setIsExcel2007(false); + myLEOFile.clearAfterInit(); + } catch (Exception e) { + if (e instanceof io.starter.formats.XLS.WorkBookException) + throw (io.starter.formats.XLS.WorkBookException) e; + throw new io.starter.formats.XLS.WorkBookException( + "ERROR: instantiating WorkBookHandle failed: " + e, + WorkBookException.UNSPECIFIED_INIT_ERROR, e); + } + } + + /** + * Initialize this workbook from a stream, unfortunately our byte backer + * requires a file, so create a tempfile and init from that + */ + protected void initFromStream(InputStream input) { + try { + File target = TempFileManager.createTempFile("WBP", ".tmp"); + + JFileWriter.writeToFile(input, target); + this.initFromFile(target.getAbsoluteFile()); + if (this.myLEOFile != null)// it would be if XLSX or XLSM ... + // 20090323 KSC + this.myLEOFile.closefb(); + // this.myLEOFile.close(); // close now flushes buffers + + // storages ... + input.close(); + + File fdel = new File(target.toString()); + if (!fdel.delete()) { + if (this.DEBUGLEVEL > DEBUG_LOW) + Logger.logWarn("Could not delete tempfile: " + + target.toString()); + } + } catch (IOException ex) { + Logger.logErr("Initializing WorkBookHandle failed.", ex); + } + } + + /** + * Create a new WorkBookHandle from the byte array passed in. Byte array passed + * in must contain a valid xls or xlsx workbook file + * + * @param byte[] byte array containing the valid XLS or XLSX file for reading + */ + public WorkBookHandle(byte[] barray) { + initializeFromByteArray(barray); + } + + /** + * Protected method that handles WorkBookHandle(byte[]) constructor + * + * @param barray + */ + protected void initializeFromByteArray(byte[] barray) { + // check first bytes to see if this is a zipfile (OOXML) + if ((char) barray[0] == 'P' && (char) barray[1] == 'K') { + try { + // added "." fixes Baxter Open Bug [BugTracker 2909] + File ftmp = TempFileManager.createTempFile("WBP", ".tmp"); + FileOutputStream fous = new FileOutputStream(ftmp); + fous.write(barray); + fous.flush(); + fous.close(); + this.initFromFile(ftmp); + return; + } catch (Exception e) { + Logger.logErr("Could not parse XLSX from bytes." + + e.toString()); + return; + } + } + + ByteBuffer bbf = ByteBuffer.wrap(barray); + bbf.order(ByteOrder.LITTLE_ENDIAN); + myLEOFile = new LEOFile(bbf); + if (myLEOFile.hasWorkBook()) { + try { + BlockByteReader bar = myLEOFile.getXLSBlockBytes(); + this.initBytes(bar); + } catch (Throwable e) { + if (e instanceof OutOfMemoryError) + throw (Error) e; + if (e instanceof WorkBookException) + throw (WorkBookException) e; + String errstr = "Instantiating WorkBookHandle failed: " + + e.toString(); + throw new io.starter.formats.XLS.WorkBookException(errstr, + WorkBookException.UNSPECIFIED_INIT_ERROR); + } + } else { + Logger.logWarn("Initializing WorkBookHandle failed: byte array does not contain a supported Excel WorkBook."); + throw new InvalidFileException( + "byte array does not contian a supported Excel WorkBook."); + } + } + + /** + * Fetches a workbook from a URL + *

        + * If you need to authenticate your URL connection first then use the + * InputStream constructor + * + * @param urlx + * @return + * @throws Exception + */ + public WorkBookHandle(URL url) { + /* + * OK, both this method and the (inputstream) constructor + * set a temp file, is + * this not possible to do without hitting the disk? TODO: + * look into fix + */ + this(getFileFromURL(url)); + } + + /** + * Constructor which takes the XLS file name( + * + * @param String filePath the name of the XLS file to read + */ + public WorkBookHandle(String filePath) { + this(filePath, 0); + } + + /** + * constructor which takes the XLS file name and has an optional debug setting + * to assist with output. Setting this value will cause verbose logging and is + * discouraged unless required for support. + * + * @param String filePath the name of the XLS file to read + * @param Debug level + */ + public WorkBookHandle(String filePath, int debug) { + this.setDebugLevel(debug); + File f = new File(filePath); + this.initFromFile(f); + this.file = f; // XXX KSC: Save for potential re-input of pass-through + // ooxml files + + } + + /** + * constructor which takes the XLS file + * + * @param File the XLS file to read + * @param Debug level + */ + public WorkBookHandle(File fx) { + this.initFromFile(fx); + } + + protected void initWorkBookFactory() { + myfactory = new WorkBookFactory(); + } + + /** + * initialize from an XLSX/OOXML workbook. + */ + private boolean initXLSX(String fname) { + // do before parseNBind so can set myfactory & fname + // set state vars for this workbookhandle + this.initWorkBookFactory(); + + myfactory.setDebugLevel(this.DEBUGLEVEL); + myfactory.setFileName(this.name); + + if (plist != null) + myfactory.register(plist); // register progress notifier + try { + // iterate sheets,inputting cell values, named ranges and + // formula strings + OOXMLReader oe = new OOXMLReader(); + WorkBookHandle bk = new WorkBookHandle(); + bk.removeAllWorkSheets(); + myfactory.setDebugLevel(this.DEBUGLEVEL); + bk.DEBUGLEVEL = this.DEBUGLEVEL; + oe.parseNBind(bk, fname); + this.sheethandles = bk.sheethandles; + this.mybook = bk.mybook; + } catch (Exception e) { + throw new WorkBookException( + "WorkBookHandle OOXML Read failed: " + e.toString(), + WorkBookException.UNSPECIFIED_INIT_ERROR, e); + } + + mybook.setIsExcel2007(true); + return true; + } + + /** + * do all initialization with a filename + * + * @param fname + */ + protected void initFromFile(File fx) { + String fname = fx.getPath(); + String finch = ""; + + // handle csv import + FileReader fincheck; + try { + fincheck = new FileReader(fx); + if (fx.length() > 100) { + char[] cbuf = new char[100]; + fincheck.read(cbuf); + finch = new String(cbuf); + } + fincheck.close(); + + } catch (FileNotFoundException e) { + Logger.logErr("WorkBookHandle: Cannot open file " + fname + ": " + + e); + } catch (Exception e1) { + Logger.logErr("Invalid XLSX/OOXML File."); + } + this.name = fname; // 20081231 KSC: set here + if (finch.toUpperCase().startsWith("PK")) { // it's a zip file... give + // XLSX parsing a shot + if (this.file != null) + OOXMLAdapter.refreshPassThroughFiles(this); + if (initXLSX(fname)) + return; + } + try { + myLEOFile = new LEOFile(fx, this.DEBUGLEVEL); + } catch (InvalidFileException ifx) { + if ((finch.indexOf(",") > -1) && (finch.indexOf(",") > -1)) { + // init a blank workbook + this.initDefault(); + + // map CSV into workbook + try { + WorkSheetHandle sheet = getWorkSheet(0); + sheet.readCSV(new BufferedReader(new FileReader(fx))); + return; + } catch (Exception e) { + throw new WorkBookException( + "Error encountered importing CSV: " + e.toString(), + WorkBookException.ILLEGAL_INIT_ERROR); + } + } else { + throw ifx; + } + + } + if (myLEOFile.hasWorkBook()) { + this.initFromLeoFile(myLEOFile); + } else { + // total failure to load + Logger.logErr("Initializing WorkBookHandle failed: " + fname + + " does not contain a supported Excel WorkBook."); + throw new InvalidFileException( + fname + " does not contian a supported Excel WorkBook."); + } + } + + /** + * Constructor which takes a ProgressListener which monitors the progress of + * creating a new Excel file. + * + * @param ProgressListener object which is monitoring progress of WorkBook read + */ + public WorkBookHandle(ProgressListener pn) { + this.plist = pn; + try { + byte[] b = getPrototypeBook(); + ByteBuffer bbf = ByteBuffer.wrap(b); + bbf.order(ByteOrder.LITTLE_ENDIAN); + myLEOFile = new LEOFile(bbf); + } catch (Exception e) { + throw new InvalidFileException( + "WorkBook could not be instantiated: " + e.toString()); + } + this.initFromLeoFile(myLEOFile); + } + + /** + * Constructor which takes the XLS file name + *

        + * and a ProgressListener which monitors the progress of reading the Excel file. + * + * @param String fname the name of the XLS file to read + * @param ProgressListener object which is monitoring progress of WorkBook read + */ + public WorkBookHandle(String fname, ProgressListener pn) { + this.plist = pn; + this.initFromFile(new File(fname)); + } + + /** + * For internal creation of a workbook handle from + * + * @param leo + */ + protected WorkBookHandle(LEOFile leo) { + this.initFromLeoFile(leo); + } + + /** + * init the new WorkBookHandle + */ + protected synchronized void initBytes(BlockByteReader blockByteReader) { + this.initWorkBookFactory(); + + if (plist != null) + myfactory.register(plist); // register progress notifier + myfactory.setDebugLevel(this.DEBUGLEVEL); + + mybook = (io.starter.formats.XLS.WorkBook) myfactory + .getWorkBook(blockByteReader, myLEOFile); + + if (dump_input != null) { + try { + dump_input.flush(); + dump_input.close(); + dump_input = null; + } catch (Exception e) { + } + } + this.postLoad(); + } + + /** + * Handles tasks that need to occur after workbook has been loaded + */ + void postLoad() { + initHlinks(); + initMerges(); + mybook.initializeNames(); // must initialize name expressions AFTER + // loading sheet records + mybook.mergeMSODrawingRecords(); + mybook.initializeIndirectFormulas(); + initPivotCache(); // if any + } + + void initMerges() { + AbstractList mergelookup = mybook.getMergecelllookup(); + for (int t = 0; t < mergelookup.size(); t++) { + Mergedcells mc = (Mergedcells) mergelookup.get(t); + mc.initCells(this); + } + } + + void initHlinks() { + AbstractList hlinklookup = mybook.getHlinklookup(); + for (int t = 0; t < hlinklookup.size(); t++) { + Hlink hl = (Hlink) hlinklookup.get(t); + hl.initCells(this); + } + } + + /** + * reads in the pivot cache storage and parses the pivot cache records
        + * pivot cache(s) are used by pivot tables as data source storage + */ + void initPivotCache() { + if (myLEOFile.hasPivotCache()) { + PivotCache pc = new PivotCache(); // grab any pivot caches + try { + pc.init(myLEOFile.getDirectoryArray(), this); + mybook.setPivotCache(pc); + } catch (Exception e) { + + } + } + } + + /** + * Closes the WorkBook and releases resources. + */ + @Override + public void close() { + try { + if (myLEOFile != null) + myLEOFile.shutdown(); + myLEOFile = null; + } catch (Exception e) { + if (DEBUGLEVEL > 3) + Logger.logWarn("Closing Document: " + toString() + " failed: " + + e.toString()); + } + if (mybook != null) + mybook.close(); // clear out object refs to release memory + mybook = null; + myfactory = null; + name = null; + sheethandles = null; + // Runtime.getRuntime().gc(); + } + + @Override + protected void finalize() throws Throwable { + close(); + } + + @Override + public void reset() { + initFromFile(new File(myLEOFile.getFileName())); + } + + /** + * Returns an array of handles to all of the WorkSheets in the Workbook. + * + * @return WorkSheetHandle[] Array of all WorkSheets in WorkBook + */ + @Override + public WorkSheetHandle[] getWorkSheets() { + try { + if (myfactory != null) { + int numsheets = mybook.getNumWorkSheets(); + if (numsheets == 0) + throw new WorkSheetNotFoundException( + "WorkBook has No Sheets."); + WorkSheetHandle[] sheets = new WorkSheetHandle[numsheets]; + for (int i = 0; i < numsheets; i++) { + Boundsheet bs = mybook.getWorkSheetByNumber(i); + bs.setWorkBook(this.mybook); + sheets[i] = new WorkSheetHandle(bs, this); + } + return sheets; + } + return null; + } catch (WorkSheetNotFoundException a) { + Logger.logWarn("getWorkSheets() failed: " + a); + return null; + } + } + + /** + * returns the handle to a WorkSheet by number. + *

        + * Sheet 0 is the first Sheet. + * + * @param index of worksheet (ie: 0) + * @return WorkSheetHandle the WorkSheet + * @throws WorkSheetNotFoundException if the specified WorkSheet is not found in the WorkBook. + */ + @Override + public WorkSheetHandle getWorkSheet(int sheetnum) throws WorkSheetNotFoundException { + Boundsheet st = mybook.getWorkSheetByNumber(sheetnum); + if (sheethandles.get(st.getSheetName()) != null) + return sheethandles.get(st.getSheetName()); + else { + WorkSheetHandle shth = new WorkSheetHandle(st, this); + sheethandles.put(st.getSheetName(), shth); + return shth; + } + } + + Hashtable sheethandles = new Hashtable(); + + /** + * returns the handle to a WorkSheet by name. + * + * @param String name of worksheet (ie: "Sheet1") + * @return WorkSheetHandle the WorkSheet + * @throws WorkSheetNotFoundException if the specified WorkSheet is not found in the WorkBook. + */ + @Override + public WorkSheetHandle getWorkSheet(String handstr) throws WorkSheetNotFoundException { + if (sheethandles.get(handstr) != null) { + if (mybook.getWorkSheetByName(handstr) != null) + return sheethandles.get(handstr); + else + throw new WorkSheetNotFoundException(handstr + " not found"); + } + if (myfactory != null) { + Boundsheet bs = mybook.getWorkSheetByName(handstr); + if (bs != null) { + bs.setWorkBook(this.mybook); + WorkSheetHandle ret = new WorkSheetHandle(bs, this); + sheethandles.put(handstr, ret); + return ret; + } else { + throw new WorkSheetNotFoundException(handstr); + } + } + throw new WorkSheetNotFoundException( + "Cannot find WorkSheet " + handstr); + } + + /** + * returns the active or selected worksheet tab + * + * @return WorkSheetHandle + * @throws WorkSheetNotFoundException + */ + public WorkSheetHandle getActiveSheet() throws WorkSheetNotFoundException { + return this.getWorkSheet(this.getWorkBook().getSelectedSheetNum()); + } + + /** + * Returns a low-level WorkBook. + *

        + * NOTE: The WorkBook class is NOT a part of the published API. Any of the + * methods and/or variables on a WorkBook object are subject to change without + * notice in new versions of OpenXLS. + */ + @Override + public io.starter.formats.XLS.WorkBook getWorkBook() { + return this.mybook; + } + + /** + * Set Encoding mode of new Strings added to file. + *

        + * OpenXLS has 3 modes for handling the internal encoding of String data that is + * added to the file. + *

        + * OpenXLS can save space in the file if it knows that all characters in your + * String data can be represented with a single byte (Compressed.) + *

        + * If your String contains characters which need 2 bytes to represent (such as + * Eastern-language characters) then it needs to be stored in an uncompressed + * Unicode format. + *

        + * OpenXLS can either automatically detect the mode for each String, or you can + * set it explicitly. The auto mode is the most flexible but requires processing + * overhead. + *

        + * Default mode is WorkBookHandle.STRING_ENCODING_AUTO. + *

        + * Valid Modes Are: + *

        + * WorkBookHandle.STRING_ENCODING_AUTO Use if you are adding mixed Unicode and + * non-unicode Strings and can accept the performance hit -slowest String adds + * -optimal file size for mixed Strings WorkBookHandle.STRING_ENCODING_UNICODE + * Use if all of your new Strings are Unicode - faster than AUTO -faster than + * AUTO -largest file size WorkBookHandle.STRING_ENCODING_COMPRESSED Use if all + * of your new Strings are non-Unicode and can have high-bytes compressed + * -faster than AUTO -smallest file size + * + * @param int String Encoding Mode + */ + @Override + public void setStringEncodingMode(int mode) { + mybook.setStringEncodingMode(mode); + } + + /** + * Set Duplicate String Handling Mode. + * + *

        +     * The Duplicate String Mode determines the behavior of
        +     * the String table when inserting new Strings.
        +     *
        +     * The String table shares a single entry for multiple
        +     * Cells containing the same string.  When multiple Cells
        +     * have the same value, they share the same underlying string.
        +     *
        +     * Changing the value of any one of the Cells will change
        +     * the value for any Cells sharing that reference.
        +     *
        +     * For this reason, you need to determine
        +     * the handling of new strings added to the sheet that
        +     * are duplicates of strings already in the table.
        +     *
        +     * If you will be changing the values of these
        +     * new Cells, you will need to set the Duplicate
        +     * String Mode to ALLOWDUPES.  If the string table
        +     * encounters a duplicate entry being added, it
        +     * will insert a duplicate that can then be subsequently
        +     * changed without affecting the other duplicate Cells.
        +     *
        +     * Valid Modes Are:
        +     *
        +     * WorkBookHandle.ALLOWDUPES - faster String inserts, larger file sizes,  changing Cells has no effect on dupe Cells
        +     *
        +     * WorkBookHandle.SHAREDUPES - slower inserts, dupe smaller file sizes, Cells share changes
        +     * 
        + * + * @param int Duplicate String Handling Mode + */ + @Override + public void setDupeStringMode(int mode) { + mybook.setDupeStringMode(mode); + } + + /** + * Copies an existing Chart to another WorkSheet + * + * @param chartname + * @param sheetname + */ + @Override + public void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException { + mybook.copyChartToSheet(chartname, sheetname); + } + + /** + * Copies an existing Chart to another WorkSheet + * + * @param chart + * @param sheet + */ + @Override + public void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) throws ChartNotFoundException, WorkSheetNotFoundException { + mybook.copyChartToSheet(chart.getTitle(), sheet.getSheetName()); + } + + /** + * Copy (duplicate) a worksheet in the workbook and add it to the end of the + * workbook with a new name + * + * @param String the Name of the source worksheet; + * @param String the Name of the new (destination) worksheet; + * @return the new WorkSheetHandle + */ + @Override + public WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName) throws WorkSheetNotFoundException { + try { + mybook.copyWorkSheet(SourceSheetName, NewSheetName); + } catch (Exception e) { + throw new WorkBookException("Failed to copy WorkSheet: " + + SourceSheetName + ": " + e.toString(), + WorkBookException.RUNTIME_ERROR); + } + mybook.getRefTracker().clearPtgLocationCaches(NewSheetName); + // update the merged cells (requires a WBH, that's why it's + // here) + WorkSheetHandle wsh = this.getWorkSheet(NewSheetName); + if (wsh != null) { + List mc = wsh.getMysheet().getMergedCellsRecs(); + for (int i = 0; i < mc.size(); i++) { + Mergedcells mrg = (Mergedcells) mc.get(i); + if (mrg != null) + mrg.initCells(this); + } + // now conditional formats + /* + * mc = wsh.getMysheet().getConditionalFormats(); for (int + * i=0;i + * You generally need not call this method. Dirty formulas will automatically be + * recalculated when their values are queried. This method is only useful for + * forcing calculation to occur at a certain time. In the case of functions such + * as NOW() whose value is volatile the formula will still be recalculated every + * time it is queried. + * + * @throws FunctionNotSupportedException if an unsupported function is used by any formula in the workbook + * @see #markFormulasDirty() + */ + public void recalc() { + int calcmode = mybook.getCalcMode(); + mybook.setCalcMode(CALCULATE_AUTO); // ensure referenced functions are + // calcualted as necesary! + Formula[] formulas = mybook.getFormulas(); + for (int idx = 0; idx < formulas.length; idx++) { + try { + formulas[idx].clearCachedValue(); + formulas[idx].calculate(); + } catch (FunctionNotSupportedException fe) { + Logger.logErr("WorkBookHandle.recalc: Error calculating Formula " + + fe.toString()); + } + } + // KSC: Clear out lookup caches! + this.getWorkBook().getRefTracker().clearLookupCaches(); + mybook.setCalcMode(calcmode); // reset + } + + /** + * Removes all of the WorkSheets from this WorkBook. + *

        + * Bytes streamed from this WorkBook will create invalid Spreadsheet files + * unless a WorkSheet(s) are added to it. + *

        + * NOTE: A WorkBook with no sheets is *invalid* and will not open in Excel. You + * must add sheets to this WorkBook for it to be valid. + */ + @Override + public void removeAllWorkSheets() { + + try { + Object ob = this.mybook.getTabID().getTabIDs().get(0); + this.mybook.getTabID().getTabIDs().removeAllElements(); + this.mybook.getTabID().getTabIDs().add(ob); + this.mybook.getTabID().updateRecord(); + } catch (Exception ex) { + } + WorkSheetHandle[] ws = this.getWorkSheets(); + try { + for (int x = 0; x < ws.length; x++) { + try { + ws[x].remove(); + } catch (WorkBookException e) { + } // ignore the invalid WorkBook problem + } + } catch (Exception e) { + // in case sheets already gone... + } + this.sheethandles.clear(); + this.mybook.closeSheets(); // replaced below with this + + /* + * WHY ARE WE DOING THIS??? // init new book // save records + * then reset to avoid + * ByteStreamer.stream records expansion Object[] recs= + * this.getWorkBook().getStreamer().getBiffRecords(); + * + * // keep Excel 2007 status boolean isExcel2007= + * this.getIsExcel2007(); + * WorkBookHandle ret = new WorkBookHandle(this.getBytes()); + * ret.setIsExcel2007(isExcel2007); + * + * this.getWorkBook().getStreamer().setBiffRecords(Arrays. + * asList(recs)); + * this.mybook = ret.getWorkBook(); / + **/ + + } + + /** + * Returns a WorkBookHandle containing an empty version of this WorkBook. + *

        + * Use in conjunction with addSheetFromWorkBook() to create new output WorkBooks + * containing various sheets from a master template. + *

        + * ie: WorkBookHandle emptytemplate = this.getNoSheetWorkBook(); + * emptytemplate.addSheetFromWorkBook(this, "Sheet1", "TargetSheet"); + * + * @return WorkBookHandle - the empty WorkBookHandle duplicate + * @see addSheetFromWorkBook + */ + @Override + public WorkBookHandle getNoSheetWorkBook() { + // to avoid ByteStreamer.stream records expansion + Object[] recs = this.getWorkBook().getStreamer().getBiffRecords(); + byte[] gb = this.getBytes(); + WorkBookHandle ret = new WorkBookHandle(gb); + this.getWorkBook().getStreamer().setBiffRecords(Arrays.asList(recs)); + ret.removeAllWorkSheets(); + return ret; + } + + /** + * Inserts a worksheet from a Source WorkBook. + * + * @param sourceBook - the WorkBook containing the sheet to copy + * @param sourceSheetName - the name of the sheet to copy + * @param destSheetName - the name of the new sheet in this workbook + * @throws WorkSheetNotFoundException + * @deprecated - use addWorkSheet(WorkSheetHandle sht, String NewSheetName){ + */ + @Override + @Deprecated + public boolean addSheetFromWorkBook(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException { + return this.addWorkSheet(sourceBook + .getWorkSheet(sourceSheetName), destSheetName) != null; + } + + /** + * Inserts a worksheet from a Source WorkBook. Brings all string data and + * formatting information from the source workbook. + *

        + * Be aware this is programmatically creating a large amount of new formatting + * information in the destination workbook. A higher performance option will + * usually be using getNoSheetWorkbook and addSheetFromWorkBook. + * + * @param sourceBook - the WorkBook containing the sheet to copy + * @param sourceSheetName - the name of the sheet to copy + * @param destSheetName - the name of the new sheet in this workbook + * @throws WorkSheetNotFoundException + * @deprecated - use addWorkSheet(WorkSheetHandle sht, String NewSheetName){ + */ + @Deprecated + public boolean addSheetFromWorkBookWithFormatting(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException { + return this.addWorkSheet(sourceBook + .getWorkSheet(sourceSheetName), destSheetName) != null; + } + + /** + * Inserts a WorkSheetHandle from a separate WorkBookhandle into the current + * WorkBookHandle. + *

        + * copies charts, images, formats from source workbook + *

        + * Worksheet will be the same name as in the source workbook. To add a custom + * named worksheet use the addWorkSheet(WorkSheetHandle, String sheetname) + * method + * + * @param WorkSheetHandle the source WorkSheetHandle; + */ + public WorkSheetHandle addWorkSheet(WorkSheetHandle sourceSheet) { + return this.addWorkSheet(sourceSheet, sourceSheet.getSheetName()); + } + + /** + * Inserts a WorkSheetHandle from a separate WorkBookhandle into the current + * WorkBookHandle. + *

        + * copies charts, images, formats from source workbook + * + * @param WorkSheetHandle the source WorkSheetHandle; + * @param String the Name of the new (destination) worksheet; + */ + @Override + public WorkSheetHandle addWorkSheet(WorkSheetHandle sourceSheet, String NewSheetName) { + sourceSheet.getSheet().populateForTransfer(); // copy all formatting + + // images for this sheet + List chts = sourceSheet.getSheet().getCharts(); + for (int i = 0; i < chts.size(); i++) { + Chart cxi = (Chart) chts.get(i); + cxi.populateForTransfer(); + } + byte[] bao = sourceSheet.getSerialBytes(); + try { + mybook.addBoundsheet(bao, sourceSheet + .getSheetName(), NewSheetName, StringTool + .stripPath(sourceSheet.getWorkBook() + .getName()), true); + WorkSheetHandle wsh = this.getWorkSheet(NewSheetName); + if (wsh != null) { + List mc = wsh.getMysheet().getMergedCellsRecs(); + for (int i = 0; i < mc.size(); i++) { + Mergedcells mrg = (Mergedcells) mc.get(i); + if (mrg != null) + mrg.initCells(this); + } + } + + return wsh; + } catch (Exception e) { + throw new WorkBookException( + "Failed to copy WorkSheet: " + e.toString(), + WorkBookException.RUNTIME_ERROR); + } + } + + /** + * Utility method to copy a format handle from a separate WorkBookHandle to this + * WorkBookHandle + * + * @param externalFormat - FormatHandle from an external WorkBookHandle + * @return + */ + public FormatHandle transferExternalFormatHandle(FormatHandle externalFormat) { + Xf xf = externalFormat.getXf(); + FormatHandle newHandle = new FormatHandle(this); + newHandle.addXf(xf); + return newHandle; + } + + /** + * Creates a new Chart and places it at the end of the workbook + * + * @param String the Name of the newly created Chart + * @return the new ChartHandle + */ + public ChartHandle createChart(String name, WorkSheetHandle wsh) { + if (wsh == null) { + // this is a sheetless chart - TODO: + } + /* + * a chart needs a supbook, externsheet, & MSO object in the + * book stream. I + * think this is due to the fact that the referenced series + * are usually stored + * in the fashon 'Sheet1!A4:B6' The sheet1 reference + * requires a supbook, though + * the reference is internal. + */ + + try { + ObjectInputStream ois = new ObjectInputStream( + new ByteArrayInputStream(getPrototypeChart())); + Chart newchart = (Chart) ois.readObject(); + newchart.setWorkBook(this.getWorkBook()); + if (this.getIsExcel2007()) + newchart = new OOXMLChart(newchart, this); + mybook.addPreChart(); + mybook.addChart(newchart, name, wsh.getSheet()); + /* + * add font recs if nec: for the default chart: default + * chart text fonts are # 5 + * & 6 title # 7 axis # 8 + */ + ChartHandle bs = new ChartHandle(newchart, this); + int nfonts = mybook.getNumFonts(); + while (nfonts < 8) { // ensure + Font f = new Font("Arial", Font.PLAIN, 200); + mybook.insertFont(f); + nfonts++; + } + Font f = mybook.getFont(8); // axis title font + if (f.toString() + .equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { + // it's default text font -- change to default axis title + // font + f = new Font("Arial", Font.BOLD, 240); + bs.setAxisFont(f); + } + f = mybook.getFont(7); // chart title font + if (f.toString() + .equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { + // it's default text font -- change to default title font + f = new Font("Arial", Font.BOLD, 360); + bs.setTitleFont(f); + } + bs.removeSeries(0); // remove the "dummied" series + bs.setAxisTitle(ChartHandle.XAXIS, null); // remove default axis + // titles, if any + bs.setAxisTitle(ChartHandle.YAXIS, null); // "" + return bs; + } catch (Exception e) { + Logger.logErr("Creating New Chart: " + name + " failed: " + e); + return null; + } + } + + /** + * delete an existing chart of the workbook + * + * @param chartname + */ + public void deleteChart(String chartname, WorkSheetHandle wsh) throws ChartNotFoundException { + try { + mybook.deleteChart(chartname, wsh.getSheet()); + } catch (ChartNotFoundException e) { + throw new ChartNotFoundException( + "Removing Chart: " + chartname + " failed: " + e); + } catch (Exception e) { + Logger.logErr("Removing Chart: " + chartname + " failed: " + e); + } + } + + /** + * Returns the number of Sheets in this WorkBook + * + * @return int number of Sheets + */ + public int getNumWorkSheets() { + return mybook.getNumWorkSheets(); + } + + /** + * Creates a new worksheet and places it at the specified position. The new + * sheet will be inserted before the sheet currently at the given index. If the + * given index is higher than the last index currently in use, the sheet will be + * added to the end of the workbook and will receive an index one higher than + * that of the current final sheet. If the given index is negative it will be + * interpreted as 0. + * + * @param name the name of the newly created worksheet + * @param sheetpos the index at which the sheet should be inserted + * @return the new WorkSheetHandle + */ + public WorkSheetHandle createWorkSheet(String name, int sheetpos) { + if (sheetpos > this.getNumWorkSheets()) + sheetpos = this.getNumWorkSheets(); + if (sheetpos < 0) + sheetpos = 0; + + WorkSheetHandle s = this.createWorkSheet(name); + s.setTabIndex(sheetpos); + return s; + } + + /** + * Creates a new worksheet and places it at the end of the workbook. + * + * @param name the name of the newly created worksheet + * @return the new WorkSheetHandle + */ + @Override + public WorkSheetHandle createWorkSheet(String name) { + try { + this.getWorkSheet(name); + throw new WorkBookException( + "Attempting to add worksheet with duplicate name. " + name + + " already exists in " + this.toString(), + WorkBookException.RUNTIME_ERROR); + } catch (WorkSheetNotFoundException ex) { + // good! + } + + Boundsheet bo = null; + try { + ObjectInputStream ois = new ObjectInputStream( + new ByteArrayInputStream(getPrototypeSheet())); + bo = (Boundsheet) ois.readObject(); + mybook.addBoundsheet(bo, null, name, null, false); + try { + WorkSheetHandle bs = this.getWorkSheet(name); + if (this.mybook.getNumWorkSheets() == 1) { + bs.setSelected(true); // it's the only sheet so select! + } else { + bs.setSelected(false); + } + return bs; + } catch (WorkSheetNotFoundException e) { + Logger.logWarn("Creating New Sheet: " + name + " failed: " + e); + return null; + } + } catch (Exception e) { + Logger.logWarn("Error loading prototype sheet: " + e); + return null; + } + } + + /** + * Returns an array of all FormatHandles in the workbook + */ + @Override + public FormatHandle[] getFormats() { + List l = this.mybook.getXfrecs(); + FormatHandle[] formats = new FormatHandle[l.size()]; + Iterator its = l.iterator(); + int i = 0; + while (its.hasNext()) { + Xf x = (Xf) its.next(); + // passing (this) with the format handle breaks the + // relationship to the font. + // if you need to pass it in we will have to handle it + // differently + try { + formats[i] = new FormatHandle(); + formats[i].setWorkBook(this.getWorkBook()); + formats[i].setXf(x); + } catch (Exception ex) { + } + i++; + } + return formats; + } + + /** + * Returns an array of all Conditional Formats in the workbook + *

        + * these are formats referenced and used by the conditionally formatted ranges + * in the workbook. + * + * @return + */ + public FormatHandle[] getConditionalFormats() { + // the idea is to create a fake IXFE for use by + // sheetster to find formats + // int cfxe = this.getWorkBook().getNumFormats() + 50000; // + // there would have to + // be 50k styles on the sheet to conflict here.... + // int cfxe = this.getWorkBook().getNumXfs() + 50000; // + // there would have to be + // 50k styles on the sheet to conflict here.... + + List retl = new Vector(); + AbstractList v = this.mybook.getSheetVect(); + + Iterator its = v.iterator(); + + while (its.hasNext()) { + Boundsheet shtx = (Boundsheet) its.next(); + List fmtlist = shtx.getConditionalFormats(); + Iterator ixa = fmtlist.iterator(); + while (ixa.hasNext()) { + Condfmt cfm = (Condfmt) ixa.next(); + // cfm.initCells(this); // added! + int cfxe = cfm.getCfxe(); + FormatHandle fz = new FormatHandle(cfm, this, cfxe, null); + + fz.setFormatId(cfxe); + retl.add(fz); + // cfm.setCfxe(cfxe); + // cxfe++; + } + } + FormatHandle[] formats = new FormatHandle[retl.size()]; + for (int t = 0; t < formats.length; t++) + formats[t] = retl.get(t); + return formats; + } + + /** + * Set the recursion levels allowed for formulas calculated in this workbook + * before a circular reference is thrown. + *

        + * Default setting is 250 levels of recursion + * + * @param recursion_allowed + */ + public static void setFormulaRecursionLevels(int recursion_allowed) { + RECURSION_LEVELS_ALLOWED = recursion_allowed; + } + + public String getWorkingDirectory() { + // TODO Auto-generated method stub + return null; + } + + public void initSharedFormulas() { + // TODO Auto-generated method stub + + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/WorkSheet.java b/src/main/java/io/starter/OpenXLS/WorkSheet.java index 8f697ad..9abb6f2 100644 --- a/src/main/java/io/starter/OpenXLS/WorkSheet.java +++ b/src/main/java/io/starter/OpenXLS/WorkSheet.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,472 +22,577 @@ */ package io.starter.OpenXLS; -import java.util.*; import io.starter.formats.XLS.*; -/** An interface representing an OpenXLS WorkSheet - * - @see WorkSheetHandle - * +import java.util.List; + +/** + * An interface representing an OpenXLS WorkSheet + * + * @see WorkSheetHandle */ -public interface WorkSheet extends Handle{ - - public WorkBook getWorkBook(); - public Mergedcells getMergedCellsRec() ; - - public abstract void addChart(byte[] serialchart, String name); - /** Get the first row on the Worksheet - * - * @return the Minimum Row Number on the Worksheet - */ - public abstract int getFirstRow(); - /** Get the first column on the Worksheet - * - * @return the Minimum Column Number on the Worksheet - */ - public abstract int getFirstCol(); - /** Get the last row on the Worksheet - * - * @return the Maximum Row Number on the Worksheet - */ - public abstract int getLastRow(); - /** Get the last column on the Worksheet - * - * @return the Maximum Column Number on the Worksheet - */ - public abstract int getLastCol(); - /* set protection on sheet - - @param boolean whether to protect the sheet - */ - public abstract void setProtected(boolean b); - /** set whether this sheet is VERY hidden opening the file. - - VERY hidden means users will not be able to unhide - the sheet without using VB code. - - @param boolean b hidden state - */ - public abstract void setVeryHidden(boolean b); - /** get whether this sheet is selected upon opening the file. - - @return boolean b selected state - */ - public abstract boolean getSelected(); - /** get whether this sheet is hidden from the user opening the file. - - @return boolean b hidden state - */ - public abstract boolean getHidden(); - /** set whether this sheet is hidden from the user opening the file. - - if the sheet is selected, the API will set the first - visible sheet to selected as you cannot have your selected sheet - be hidden. - - to override this behavior, set your desired sheet to selected - after calling this method. - - @param boolean b hidden state - */ - public abstract void setHidden(boolean b); - /** set this WorkSheet as the first visible tab on the left - */ - public abstract void setFirstVisibleTab(); - /** get the tab display order of this Worksheet - - this is a zero based index with zero representing - the left-most WorkSheet tab. - - @return int idx the index of the sheet tab - */ - public abstract int getTabIndex(); - /** set the tab display order of this Worksheet - - this is a zero based index with zero representing - the left-most WorkSheet tab. - - @param int idx the new index of the sheet tab - */ - public abstract void setTabIndex(int idx); - /** set whether this sheet is selected upon - opening the file. - - @param boolean b selected value - */ - public abstract void setSelected(boolean b); - /** returns the Column at the index position - - @return ColHandle the Column - */ - public abstract ColHandle getCol(int clnum) throws ColumnNotFoundException; - /** returns the Column at the named position - - @return ColHandle the Column - */ - public abstract ColHandle getCol(String name) - throws ColumnNotFoundException; - /** returns all of the Columns in this WorkSheet - - @return ColHandle[] Columns - */ - public abstract ColHandle[] getColumns(); - /** returns a Vector of Column names - - @return CompatibleVector column names - */ - public abstract List getColNames(); - /** returns a Vector of Row numbers - - @return CompatibleVector row numbers - */ - public abstract List getRowNums(); - /** get an a RowHandle for - this WorkSheet by number - - @return RowHandle a Row on this WorkSheet - @param int row number to return - */ - public abstract RowHandle getRow(int t) throws RowNotFoundException; - /** get an array of all RowHandles for - this WorkSheet - - @return RowHandle[] all Rows on this WorkSheet - */ - public abstract RowHandle[] getRows(); - /** Returns the number of rows in this WorkSheet - - @return int Number of Rows on this WorkSheet - */ - public abstract int getNumRows(); - /** Returns the number of Columns in this WorkSheet - - @return int Number of Cols on this WorkSheet - */ - public abstract int getNumCols(); - /** Remove a Cell from this WorkSheet. - - @param CellHandle to remove - */ - public abstract void removeCell(CellHandle celldel); - /** Remove a Cell from this WorkSheet. - - @param String celladdr - the Address of the Cell to remove - */ - public abstract void removeCell(String celladdr); - /** Remove a Row and all associated Cells from - this WorkSheet. - - @param int rownum - the number of the row to remove - */ - public abstract void removeRow(int rownum) throws RowNotFoundException; - /** Remove a Row and all associated Cells from - this WorkSheet. Optionally shift all rows below - target row up one. - - @param int rownum - the number of the row to remove - @param boolean shiftrows - true will shift all lower rows up one. - */ - public abstract void removeRow(int rownum, boolean shiftrows) - throws RowNotFoundException; - /** Remove a Column and all associated Cells from - this WorkSheet. - - @param String colstr - the name of the column to remove - */ - public abstract void removeCol(String colstr) - throws ColumnNotFoundException; - /** Remove a Column and all associated Cells from - this WorkSheet. - - Optionally shift all cols to the right - of this column to the left by one. - - @param String colstr - the name of the column to remove - @param boolean shiftcols - true will shift following cols - */ - public abstract void removeCol(String colstr, boolean shiftcols) - throws ColumnNotFoundException; - - /** Returns the low-level OpenXLS Sheet. - * - * @return io.starter.formats.XLS.Sheet - */ - public Sheet getSheet(); - - /** Returns the name of the Sheet. - - @return String Sheet Name - */ - public abstract String getSheetName(); - /** Returns the Serialized bytes for this WorkSheet. - - The output of this method can be used to insert - a copy of this WorkSheet into another WorkBook - using the WorkBook.addWorkSheet(byte[] serialsheet, String NewSheetName) - method. - - @return byte[] the WorkSheet's Serialized bytes - @see WorkBook.addWorkSheet(byte[] serialsheet, String NewSheetName) - */ - public abstract byte[] getSerialBytes(); - /** Set the Object value of the Cell at the given address. - - @param String Cell Address (ie: "D14") - @param Object new Cell Object value - @exception CellNotFoundException is thrown if there is - no existing Cell at the specified address. - */ - public abstract void setVal(String address, Object val) - throws CellNotFoundException, CellTypeMismatchException; - /** - Set the double value of the Cell at the given address - - @param String Cell Address (ie: "D14") - @param double new Cell double value - @param address - @param d - @exception io.starter.formats.XLS.CellNotFoundException is - thrown if there is no existing Cell at the specified address. - */ - public abstract void setVal(String address, double d) - throws CellNotFoundException, CellTypeMismatchException; - /** Set the String value of the Cell at the given address - - @param String Cell Address (ie: "D14") - @param String new Cell String value - @exception CellNotFoundException is thrown if there is no existing Cell at the specified address. - */ - public abstract void setVal(String address, String s) - throws CellNotFoundException, CellTypeMismatchException; - /** Set the name of the Worksheet. This method will change - the name on the Worksheet's tab as displayed in the - WorkBook, as well as all programmatic and internal - references to the name. - - This change takes effect immediately, so all attempts - to reference the Worksheet by its previous name will - fail. - - @param String the new name for the Worksheet - */ - public abstract void setSheetName(String name); - /** - Set the int value of the Cell at the given address - - @param String Cell Address (ie: "D14") - @param int new Cell int value - @exception io.starter.formats.XLS.CellNotFoundException - is thrown if there is no existing Cell at the specified address. - */ - public abstract void setVal(String address, int i) - throws CellNotFoundException, CellTypeMismatchException; - /** Get the Object value of a Cell. - - Numeric Cell values will return as type Long, Integer, or Double. - String Cell values will return as type String. - - @return the value of the Cell as an Object. - @exception CellNotFoundException is thrown if there is - no existing Cell at the specified address. - */ - public abstract Object getVal(String address) throws CellNotFoundException; - /** Insert a blank row into the worksheet. - Shift all rows below the cell down one. - - Adding new cells to non-existent rows will - automatically create new rows in the file, - This method is only necessary to "move" existing cells - by inserting empty rows. - - @param rownum the rownumber to insert - */ - public abstract void insertRow(int rownum); - /** Insert a blank row into the worksheet. - Shift all rows below the cell down one. - - Adding new cells to non-existent rows will - automatically create new rows in the file, - - After calling this method, setVal() can be used on the - newly created cells to update with new values. - - @param rownum the rownumber to insert - @param whether to shift down existing Cells - */ - public abstract void insertRow(int rownum, boolean shiftrows); - public static final int ROW_KEEP = 0; - - public static final int ROW_INSERT = 1; - - /** Insert a blank row into the worksheet. - Shift all rows below the cell down one. - - Adding new cells to non-existent rows will - automatically create new rows in the file, - - After calling this method, setVal() can be used on the - newly created cells to update with new values. - - @param rownum the rownumber to insert - @param whether to shift down existing Cells - */ - public abstract void insertRow( - int rownum, - int copyrow, - int flag, - boolean shiftrows); - /** Insert a blank column into the worksheet. - Shift all columns to the right of the cell over one. - - Adding new cells to non-existent columns will - automatically create new Columns in the file, - This method is only necessary to "move" existing cells - by inserting empty columns. - - @param colstr the Column string to insert - */ - public abstract void insertCol(String colnum); - /** When adding a new Cell to the sheet, OpenXLS can - * automatically copy the formatting from the Cell directly - * above the inserted Cell. - * - * ie: if set to true, newly added Cell D19 would take its formatting - * from Cell D18. - * - * Default is false - * - * - * - * boolean copy the formats from the prior Cell - */ - public abstract void setCopyFormatsFromPriorWhenAdding(boolean f); - /** Add a Cell with the specified value to a WorkSheet. - - This method determines the Cell type based on - type-compatibility of the value. - - In other words, if the Object cannot be converted - safely to a Numeric Object type, then it is treated - as a String and a new String value is added to the - WorkSheet at the Cell address specified. - - @param obj the value of the new Cell - @param int row the row of the new Cell - @param int col the column of the new Cell - - */ - public abstract CellHandle add(Object obj, int row, int col); - /** Add a Cell with the specified value to a WorkSheet. - - This method determines the Cell type based on - type-compatibility of the value. - - In other words, if the Object cannot be converted - safely to a Numeric Object type, then it is treated - as a String and a new String value is added to the - WorkSheet at the Cell address specified. - - @param obj the value of the new Cell - @param address the address of the new Cell - - */ - public abstract CellHandle add(Object obj, String address); - /** Add a java.sql.Date Cell to a WorkSheet. - - You must specify a formatting pattern for the - new date, or null for the default ("m/d/yy h:mm".) - - valid date format patterns - "m/d/y" - "d-mmm-yy" - "d-mmm" - "mmm-yy" - "h:mm AM/PM" - "h:mm:ss AM/PM" - "h:mm" - "h:mm:ss" - "m/d/yy h:mm" - "mm:ss" - "[h]:mm:ss" - "mm:ss.0" - - @param dt the value of the new java.sql.Date Cell - @param address the address of the new java.sql.Date Cell - @param formatting pattern the address of the new java.sql.Date Cell - */ - public abstract CellHandle add( - java.util.Date dt, - String address, - String fmt); - /** Remove this WorkSheet from the WorkBook - - */ - public abstract void remove(); - /** Returns all CellHandles defined on this WorkSheet. - - @return Cell[] - the array of Cells in the Sheet - */ - public abstract CellHandle[] getCells(); - /** Returns a FormulaHandle for working with - the ranges of a formula on a WorkSheet. - - @param addr the address of the Cell - @exception FormulaNotFoundException is thrown if there is no existing formula at the specified address. - */ - public abstract FormulaHandle getFormula(String addr) - throws FormulaNotFoundException, CellNotFoundException; - /** Returns a CellHandle for working with - the value of a Cell on a WorkSheet. - - @param addr the address of the Cell - @exception CellNotFoundException is thrown if there is no existing Cell at the specified address. - */ - public abstract CellHandle getCell(String addr) - throws CellNotFoundException; - /** Returns a CellHandle for working with - the value of a Cell on a WorkSheet. - - @param int Row the integer row of the Cell - @param int Col the integer col of the Cell - @exception CellNotFoundException is thrown if there is no existing Cell at the specified address. - */ - public abstract CellHandle getCell(int row, int col) - throws CellNotFoundException; - /** Move a cell on this WorkSheet. - @param CellHandle c - the cell to be moved - @param String celladdr - the destination address of the cell - */ - public abstract void moveCell(CellHandle c, String addr) - throws CellPositionConflictException; - /** Get the text for the Footer printed at the bottom - of the Worksheet - - @return String footer text - */ - public abstract String getFooterText(); - /** Get the text for the Header printed at the top - of the Worksheet - - @return String header text - */ - public abstract String getHeaderText(); - /** Set the text for the Header printed at the top - of the Worksheet - - @param String header text - */ - public abstract void setHeaderText(String t); - /** Set the text for the Footer printed at the bottom - of the Worksheet - - @param String footer text - */ - public abstract void setFooterText(String t); - /** - * Returns the name of this Sheet. - * - * @see java.lang.Object#toString() - */ - public abstract String toString(); +public interface WorkSheet extends Handle { + + WorkBook getWorkBook(); + + Mergedcells getMergedCellsRec(); + + void addChart(byte[] serialchart, String name); + + /** + * Get the first row on the Worksheet + * + * @return the Minimum Row Number on the Worksheet + */ + int getFirstRow(); + + /** + * Get the first column on the Worksheet + * + * @return the Minimum Column Number on the Worksheet + */ + int getFirstCol(); + + /** + * Get the last row on the Worksheet + * + * @return the Maximum Row Number on the Worksheet + */ + int getLastRow(); + + /** + * Get the last column on the Worksheet + * + * @return the Maximum Column Number on the Worksheet + */ + int getLastCol(); + + /* set protection on sheet + + @param boolean whether to protect the sheet + */ + void setProtected(boolean b); + + /** + * set whether this sheet is VERY hidden opening the file. + *

        + * VERY hidden means users will not be able to unhide + * the sheet without using VB code. + * + * @param boolean b hidden state + */ + void setVeryHidden(boolean b); + + /** + * get whether this sheet is selected upon opening the file. + * + * @return boolean b selected state + */ + boolean getSelected(); + + /** + * get whether this sheet is hidden from the user opening the file. + * + * @return boolean b hidden state + */ + boolean getHidden(); + + /** + * set whether this sheet is hidden from the user opening the file. + *

        + * if the sheet is selected, the API will set the first + * visible sheet to selected as you cannot have your selected sheet + * be hidden. + *

        + * to override this behavior, set your desired sheet to selected + * after calling this method. + * + * @param boolean b hidden state + */ + void setHidden(boolean b); + + /** + * set this WorkSheet as the first visible tab on the left + */ + void setFirstVisibleTab(); + + /** + * get the tab display order of this Worksheet + *

        + * this is a zero based index with zero representing + * the left-most WorkSheet tab. + * + * @return int idx the index of the sheet tab + */ + int getTabIndex(); + + /** + * set the tab display order of this Worksheet + *

        + * this is a zero based index with zero representing + * the left-most WorkSheet tab. + * + * @param int idx the new index of the sheet tab + */ + void setTabIndex(int idx); + + /** + * set whether this sheet is selected upon + * opening the file. + * + * @param boolean b selected value + */ + void setSelected(boolean b); + + /** + * returns the Column at the index position + * + * @return ColHandle the Column + */ + ColHandle getCol(int clnum) throws ColumnNotFoundException; + + /** + * returns the Column at the named position + * + * @return ColHandle the Column + */ + ColHandle getCol(String name) + throws ColumnNotFoundException; + + /** + * returns all of the Columns in this WorkSheet + * + * @return ColHandle[] Columns + */ + ColHandle[] getColumns(); + + /** + * returns a Vector of Column names + * + * @return CompatibleVector column names + */ + List getColNames(); + + /** + * returns a Vector of Row numbers + * + * @return CompatibleVector row numbers + */ + List getRowNums(); + + /** + * get an a RowHandle for + * this WorkSheet by number + * + * @param int row number to return + * @return RowHandle a Row on this WorkSheet + */ + RowHandle getRow(int t) throws RowNotFoundException; + + /** + * get an array of all RowHandles for + * this WorkSheet + * + * @return RowHandle[] all Rows on this WorkSheet + */ + RowHandle[] getRows(); + + /** + * Returns the number of rows in this WorkSheet + * + * @return int Number of Rows on this WorkSheet + */ + int getNumRows(); + + /** + * Returns the number of Columns in this WorkSheet + * + * @return int Number of Cols on this WorkSheet + */ + int getNumCols(); + + /** + * Remove a Cell from this WorkSheet. + * + * @param CellHandle to remove + */ + void removeCell(CellHandle celldel); + + /** + * Remove a Cell from this WorkSheet. + * + * @param String celladdr - the Address of the Cell to remove + */ + void removeCell(String celladdr); + + /** + * Remove a Row and all associated Cells from + * this WorkSheet. + * + * @param int rownum - the number of the row to remove + */ + void removeRow(int rownum) throws RowNotFoundException; + + /** + * Remove a Row and all associated Cells from + * this WorkSheet. Optionally shift all rows below + * target row up one. + * + * @param int rownum - the number of the row to remove + * @param boolean shiftrows - true will shift all lower rows up one. + */ + void removeRow(int rownum, boolean shiftrows) + throws RowNotFoundException; + + /** + * Remove a Column and all associated Cells from + * this WorkSheet. + * + * @param String colstr - the name of the column to remove + */ + void removeCol(String colstr) + throws ColumnNotFoundException; + + /** + * Remove a Column and all associated Cells from + * this WorkSheet. + *

        + * Optionally shift all cols to the right + * of this column to the left by one. + * + * @param String colstr - the name of the column to remove + * @param boolean shiftcols - true will shift following cols + */ + void removeCol(String colstr, boolean shiftcols) + throws ColumnNotFoundException; + + /** + * Returns the low-level OpenXLS Sheet. + * + * @return io.starter.formats.XLS.Sheet + */ + Sheet getSheet(); + + /** + * Returns the name of the Sheet. + * + * @return String Sheet Name + */ + String getSheetName(); + + /** + * Returns the Serialized bytes for this WorkSheet. + *

        + * The output of this method can be used to insert + * a copy of this WorkSheet into another WorkBook + * using the WorkBook.addWorkSheet(byte[] serialsheet, String NewSheetName) + * method. + * + * @return byte[] the WorkSheet's Serialized bytes + * @see WorkBook.addWorkSheet(byte[] serialsheet, String NewSheetName) + */ + byte[] getSerialBytes(); + + /** + * Set the Object value of the Cell at the given address. + * + * @param String Cell Address (ie: "D14") + * @param Object new Cell Object value + * @throws CellNotFoundException is thrown if there is + * no existing Cell at the specified address. + */ + void setVal(String address, Object val) + throws CellNotFoundException, CellTypeMismatchException; + + /** + * Set the double value of the Cell at the given address + * + * @param String Cell Address (ie: "D14") + * @param double new Cell double value + * @param address + * @param d + * @throws io.starter.formats.XLS.CellNotFoundException is + * thrown if there is no existing Cell at the specified address. + */ + void setVal(String address, double d) + throws CellNotFoundException, CellTypeMismatchException; + + /** + * Set the String value of the Cell at the given address + * + * @param String Cell Address (ie: "D14") + * @param String new Cell String value + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified address. + */ + void setVal(String address, String s) + throws CellNotFoundException, CellTypeMismatchException; + + /** + * Set the name of the Worksheet. This method will change + * the name on the Worksheet's tab as displayed in the + * WorkBook, as well as all programmatic and internal + * references to the name. + *

        + * This change takes effect immediately, so all attempts + * to reference the Worksheet by its previous name will + * fail. + * + * @param String the new name for the Worksheet + */ + void setSheetName(String name); + + /** + * Set the int value of the Cell at the given address + * + * @param String Cell Address (ie: "D14") + * @param int new Cell int value + * @throws io.starter.formats.XLS.CellNotFoundException is thrown if there is no existing Cell at the specified address. + */ + void setVal(String address, int i) + throws CellNotFoundException, CellTypeMismatchException; + + /** + * Get the Object value of a Cell. + *

        + * Numeric Cell values will return as type Long, Integer, or Double. + * String Cell values will return as type String. + * + * @return the value of the Cell as an Object. + * @throws CellNotFoundException is thrown if there is + * no existing Cell at the specified address. + */ + Object getVal(String address) throws CellNotFoundException; + + /** + * Insert a blank row into the worksheet. + * Shift all rows below the cell down one. + *

        + * Adding new cells to non-existent rows will + * automatically create new rows in the file, + * This method is only necessary to "move" existing cells + * by inserting empty rows. + * + * @param rownum the rownumber to insert + */ + void insertRow(int rownum); + + /** + * Insert a blank row into the worksheet. + * Shift all rows below the cell down one. + *

        + * Adding new cells to non-existent rows will + * automatically create new rows in the file, + *

        + * After calling this method, setVal() can be used on the + * newly created cells to update with new values. + * + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + */ + void insertRow(int rownum, boolean shiftrows); + + int ROW_KEEP = 0; + + int ROW_INSERT = 1; + + /** + * Insert a blank row into the worksheet. + * Shift all rows below the cell down one. + *

        + * Adding new cells to non-existent rows will + * automatically create new rows in the file, + *

        + * After calling this method, setVal() can be used on the + * newly created cells to update with new values. + * + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + */ + void insertRow( + int rownum, + int copyrow, + int flag, + boolean shiftrows); + + /** + * Insert a blank column into the worksheet. + * Shift all columns to the right of the cell over one. + *

        + * Adding new cells to non-existent columns will + * automatically create new Columns in the file, + * This method is only necessary to "move" existing cells + * by inserting empty columns. + * + * @param colstr the Column string to insert + */ + void insertCol(String colnum); + + /** + * When adding a new Cell to the sheet, OpenXLS can + * automatically copy the formatting from the Cell directly + * above the inserted Cell. + *

        + * ie: if set to true, newly added Cell D19 would take its formatting + * from Cell D18. + *

        + * Default is false + *

        + *

        + *

        + * boolean copy the formats from the prior Cell + */ + void setCopyFormatsFromPriorWhenAdding(boolean f); + + /** + * Add a Cell with the specified value to a WorkSheet. + *

        + * This method determines the Cell type based on + * type-compatibility of the value. + *

        + * In other words, if the Object cannot be converted + * safely to a Numeric Object type, then it is treated + * as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + */ + CellHandle add(Object obj, int row, int col); + + /** + * Add a Cell with the specified value to a WorkSheet. + *

        + * This method determines the Cell type based on + * type-compatibility of the value. + *

        + * In other words, if the Object cannot be converted + * safely to a Numeric Object type, then it is treated + * as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param address the address of the new Cell + */ + CellHandle add(Object obj, String address); + + /** + * Add a java.sql.Date Cell to a WorkSheet. + *

        + * You must specify a formatting pattern for the + * new date, or null for the default ("m/d/yy h:mm".) + *

        + * valid date format patterns + * "m/d/y" + * "d-mmm-yy" + * "d-mmm" + * "mmm-yy" + * "h:mm AM/PM" + * "h:mm:ss AM/PM" + * "h:mm" + * "h:mm:ss" + * "m/d/yy h:mm" + * "mm:ss" + * "[h]:mm:ss" + * "mm:ss.0" + * + * @param dt the value of the new java.sql.Date Cell + * @param address the address of the new java.sql.Date Cell + * @param formatting pattern the address of the new java.sql.Date Cell + */ + CellHandle add( + java.util.Date dt, + String address, + String fmt); + + /** + * Remove this WorkSheet from the WorkBook + */ + void remove(); + + /** + * Returns all CellHandles defined on this WorkSheet. + * + * @return Cell[] - the array of Cells in the Sheet + */ + CellHandle[] getCells(); + + /** + * Returns a FormulaHandle for working with + * the ranges of a formula on a WorkSheet. + * + * @param addr the address of the Cell + * @throws FormulaNotFoundException is thrown if there is no existing formula at the specified address. + */ + FormulaHandle getFormula(String addr) + throws FormulaNotFoundException, CellNotFoundException; + + /** + * Returns a CellHandle for working with + * the value of a Cell on a WorkSheet. + * + * @param addr the address of the Cell + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified address. + */ + CellHandle getCell(String addr) + throws CellNotFoundException; + + /** + * Returns a CellHandle for working with + * the value of a Cell on a WorkSheet. + * + * @param int Row the integer row of the Cell + * @param int Col the integer col of the Cell + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified address. + */ + CellHandle getCell(int row, int col) + throws CellNotFoundException; + + /** + * Move a cell on this WorkSheet. + * + * @param CellHandle c - the cell to be moved + * @param String celladdr - the destination address of the cell + */ + void moveCell(CellHandle c, String addr) + throws CellPositionConflictException; + + /** + * Get the text for the Footer printed at the bottom + * of the Worksheet + * + * @return String footer text + */ + String getFooterText(); + + /** + * Get the text for the Header printed at the top + * of the Worksheet + * + * @return String header text + */ + String getHeaderText(); + + /** + * Set the text for the Header printed at the top + * of the Worksheet + * + * @param String header text + */ + void setHeaderText(String t); + + /** + * Set the text for the Footer printed at the bottom + * of the Worksheet + * + * @param String footer text + */ + void setFooterText(String t); + + /** + * Returns the name of this Sheet. + * + * @see java.lang.Object#toString() + */ + String toString(); } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/WorkSheetHandle.java b/src/main/java/io/starter/OpenXLS/WorkSheetHandle.java index 1393d12..56ccad1 100644 --- a/src/main/java/io/starter/OpenXLS/WorkSheetHandle.java +++ b/src/main/java/io/starter/OpenXLS/WorkSheetHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,52 +22,8 @@ */ package io.starter.OpenXLS; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import io.starter.formats.XLS.AutoFilter; -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.CellNotFoundException; -import io.starter.formats.XLS.CellPositionConflictException; -import io.starter.formats.XLS.CellTypeMismatchException; -import io.starter.formats.XLS.Cf; -import io.starter.formats.XLS.Colinfo; -import io.starter.formats.XLS.ColumnNotFoundException; -import io.starter.formats.XLS.Condfmt; -import io.starter.formats.XLS.Dv; -import io.starter.formats.XLS.FeatHeadr; -import io.starter.formats.XLS.FormulaNotFoundException; -import io.starter.formats.XLS.ImageNotFoundException; -import io.starter.formats.XLS.Mulblank; -import io.starter.formats.XLS.Name; -import io.starter.formats.XLS.Note; -import io.starter.formats.XLS.Password; -import io.starter.formats.XLS.ReferenceTracker; -import io.starter.formats.XLS.Row; -import io.starter.formats.XLS.RowNotFoundException; -import io.starter.formats.XLS.SheetProtectionManager; -import io.starter.formats.XLS.Sxview; -import io.starter.formats.XLS.Unicodestring; -import io.starter.formats.XLS.ValidationException; import io.starter.formats.XLS.WorkBook; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.XLSConstants; -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.*; import io.starter.formats.XLS.charts.Chart; import io.starter.formats.XLS.formulas.GenericPtg; import io.starter.formats.XLS.formulas.Ptg; @@ -76,6 +32,10 @@ import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; +import java.io.*; +import java.sql.Timestamp; +import java.util.*; + /** * The WorkSheetHandle provides a handle to a Worksheet within an XLS file
        * and includes convenience methods for working with the Cell values within the @@ -86,3100 +46,2888 @@ *

        WorkBookHandle book = new WorkBookHandle("testxls.xls");
        * WorkSheetHandle sheet1 = book.getWorkSheet("Sheet1");
        * CellHandle cell = sheet1.getCell("B22");
        - * + * *
        * to add a cell:
        *
        * CellHandle cell = sheet1.add("Hello World","C22");
        - * + * *
        * to add a numeric cell:
        *
        * CellHandle cell = sheet1.add(Integer.valueOf(120),"C23");
        - * + * *
        * to add a formula cell:
        *
        * CellHandle cell = sheet1.add("=PI()","C24");
        - * + * *

        *
        - * + * * @see WorkSheet * @see WorkBookHandle * @see CellHandle */ public class WorkSheetHandle implements Handle { - private Boundsheet mysheet; - private WorkBook mybook; - WorkBookHandle wbh; - private int DEBUGLEVEL = 0; - private Hashtable dateFormats = new Hashtable(); - private boolean cache = true; // 20080917 KSC: set var for caching, default to true [BugTracker 1862] - // public Map cellhandles = new HashMap(); - - public void addChart(byte[] serialchart, String name, short[] coords) { - mysheet.addChart(serialchart, name, coords); - } - - /** - * Get the first row on the Worksheet - * - * @return the Minimum Row Number on the Worksheet - */ - public int getFirstRow() { - return mysheet.getMinRow(); - } - - /** - * Get the first column on the Worksheet - * - * @return the Minimum Column Number on the Worksheet - */ - public int getFirstCol() { - return mysheet.getMinCol(); - } - - /** - * Get the last row on the Worksheet - * - * @return the Maximum Row Number on the Worksheet - */ - public int getLastRow() { - return mysheet.getMaxRow(); - } - - /** - * Get the last column on the Worksheet - * - * @return the Maximum Column Number on the Worksheet - */ - public int getLastCol() { - return mysheet.getMaxCol(); - } - - /** - * Sets whether the worksheet is protected. If protect is - * true, the worksheet will be protected and the password will be - * set to password. If it's false, the worksheet will - * be unprotected and the password will be removed. - * - * @param protect - * whether the worksheet should be protected - * @param password - * the password to set if protect is true. ignored when - * protect is false. - * @throws WorkBookException - * never. This used to be thrown when unprotecting if the password - * was incorrect. - */ - public void setProtected(boolean protect, String password) throws WorkBookException { - SheetProtectionManager protector = mysheet.getProtectionManager(); - - // we need to check if this password can be used to unprotect... - // otherwise it is totally insecure... - String oldpass = protector.getPassword(); - - Password pss = new Password(); - pss.setPassword(password); - String passcheck = pss.getPasswordHashString(); - - if (oldpass != null) { - if (!oldpass.equals(passcheck) && oldpass != "0000") { - throw new WorkBookException("Incorrect Password Attempt to Unprotect Worksheet.", - WorkBookException.SHEETPROTECT_INCORRECT_PASSWORD); - } - } - protector.setProtected(protect); - protector.setPassword(protect ? password : null); - } - - /** - * Sets whether the worksheet is protected. - * - * @param protect - * whether worksheet protection should be enabled - */ - public void setProtected(boolean protect) { - mysheet.getProtectionManager().setProtected(protect); - } - - /** - * Sets the password used to unlock the sheet when it is protected. - * - * @param password - * the clear text of the password to be applied or null to remove the - * existing password - */ - public void setProtectionPassword(String password) { - mysheet.getProtectionManager().setPassword(password); - } - - /** - * Sets the password used to unlock the sheet when it is protected. This method - * is useful in combination with {@link #getHashedProtectionPassword} to copy - * the password from one worksheet to another. - * - * @param hash - * the hash of the protection password to be applied or null to - * remove the existing password - */ - public void setProtectionPasswordHashed(String hash) { - mysheet.getProtectionManager().setPasswordHashed(hash); - } - - /** - * Gets the hash of the sheet protection password. This method returns the - * hashed password as stored in the file. It has been passed through a one-way - * hash function. It is therefore not possible to recover the actual password. - * You can, however, use {@link #setProtectionPasswordHashed} to apply the same - * password to another worksheet. - * - * @return the password hash or "0000" if the sheet doesn't have a password - */ - public String getHashedProtectionPassword() { - return mysheet.getProtectionManager().getPassword(); - } - - /** - * Returns whether the sheet is protected. Note that this is separate from - * whether the sheet has a protection password. It can be protected without a - * password or have a password but not be protected. - * - * @return whether protection is enabled for the sheet - */ - public boolean getProtected() { - return mysheet.getProtectionManager().getProtected(); - } - - /** - * Checks whether the given password matches the protection password. - * - * @param guess - * the password to be checked against the stored hash - * @return whether the given password matches the stored hash - */ - public boolean checkProtectionPassword(String guess) { - return mysheet.getProtectionManager().checkPassword(guess); - } - - /** - * Sets the worksheet enhanced protection option - * - * @see WorkBookHandle.iprot options - * @param int - * protectionOption - */ - public void setEnhancedProtection(int protectionOption, boolean set) { - mysheet.getProtectionManager().setProtected(protectionOption, set); - } - - /** - * returns true if the indicated Enhanced Protection Setting is turned on - * - * @see WorkBookHandle.iprot options - * @param protectionOption - * @return boolean true if the indicated Enhanced Protection Setting is turned - * on - */ - public boolean getEnhancedProtection(int protectionOption) { - return mysheet.getProtectionManager().getProtected(protectionOption); - } - - /** - * set whether this sheet is VERY hidden opening the file. - * - * VERY hidden means users will not be able to unhide the sheet without using VB - * code. - * - * @param boolean - * b hidden state - */ - public void setVeryHidden(boolean b) { - int h = 0; - if (b) - h = Boundsheet.VERY_HIDDEN; - mysheet.setHidden(h); - int t = mysheet.getSheetNum(); - try { // set the next sheet selected... - Boundsheet s2 = mybook.getWorkSheetByNumber(t + 1); - s2.setSelected(true); - } catch (WorkSheetNotFoundException e) { - ; - } - } - - /** - * get whether this sheet is selected upon opening the file. - * - * @return boolean b selected state - */ - public boolean getSelected() { - return mysheet.selected(); - } - - /** - * get whether this sheet is hidden from the user opening the file. - * - * @return boolean b hidden state - */ - public boolean getHidden() { - return mysheet.getHidden(); - } - - /** - * return the 'veryhidden' state of the sheet - * - * @return - */ - public boolean getVeryHidden() { - return mysheet.getVeryHidden(); - } - - /** - * set whether this sheet is hidden from the user opening the file. - * - * if the sheet is selected, the API will set the first visible sheet to - * selected as you cannot have your selected sheet be hidden. - * - * to override this behavior, set your desired sheet to selected after calling - * this method. - * - * @param boolean - * b hidden state - */ - public void setHidden(boolean b) { - int h = 0; - if (b) - h = Boundsheet.HIDDEN; - mysheet.setHidden(h); - if (mysheet.getSheetNum() == 0) { - try { - Boundsheet s2 = mybook.getWorkSheetByNumber(mysheet.getSheetNum() + 1); - mybook.setFirstVisibleSheet(s2); - } catch (WorkSheetNotFoundException e) { - ; - } - } - if (mysheet.selected()) { - try { // set the next sheet selected... - int x = 1; - Boundsheet s2 = mybook.getWorkSheetByNumber(mysheet.getSheetNum() + x); - while (s2.getHidden()) - s2 = mybook.getWorkSheetByNumber(mysheet.getSheetNum() + x++); - s2.setSelected(true); - } catch (WorkSheetNotFoundException e) { - ; - } - } - } - - /** - * set this WorkSheet as the first visible tab on the left - */ - public void setFirstVisibleTab() { - mysheet.getWorkBook().setFirstVisibleSheet(mysheet); - } - - /** - * get the tab display order of this Worksheet - * - * this is a zero based index with zero representing the left-most WorkSheet - * tab. - * - * @return int idx the index of the sheet tab - */ - public int getTabIndex() { - return mysheet.getSheetNum(); - } - - /** - * set the tab display order of this Worksheet - * - * this is a zero based index with zero representing the left-most WorkSheet - * tab. - * - * @param int - * idx the new index of the sheet tab - */ - public void setTabIndex(int idx) { - mysheet.getWorkBook().changeWorkSheetOrder(mysheet, idx); - } - - /** - * set whether this sheet is selected upon opening the file. - * - * @param boolean - * b selected value - */ - public void setSelected(boolean b) { - mysheet.setSelected(b); - } - - /** - * returns the ColHandle for the column at index position the column index is - * zero based ie: column A = 0 - * - * @return ColHandle the Column - */ - public ColHandle getCol(int clnum) throws ColumnNotFoundException { - Colinfo ci = mysheet.getColInfo(clnum); - ColHandle mycol; - if (ci == null || !ci.isSingleCol()) { - try { - if (ci == null) { - ci = mysheet.createColinfo(clnum, clnum); - } else { - ci = mysheet.createColinfo(clnum, clnum, ci); - } - mycol = new ColHandle(ci, this); - - } catch (Exception e) { - throw new ColumnNotFoundException("Unable to getCol for col number " + clnum + " " + e.toString()); - } - } else { - mycol = new ColHandle(ci, this); // usual case - } - return mycol; - } - - /** - * adds the column (col1st, colLast) and returns the new ColHandle - * - * @param c1st - * @param clast - * @return - * @deprecated use addCol(int) - */ - @Deprecated - public ColHandle addCol(int c1st, int clast) { - Colinfo ci = mysheet.createColinfo(c1st, clast); - ColHandle mycol = new ColHandle(ci, this); - return mycol; - } - - /** - * adds the column (col1st, colLast) and returns the new ColHandle - * - * @param colNum, - * zero based number of the column - * @return ColHandle - */ - public ColHandle addCol(int colNum) { - Colinfo ci = mysheet.createColinfo(colNum, colNum); - ColHandle mycol = new ColHandle(ci, this); - return mycol; - } - - /** - * returns the Column at the named position - * - * @return ColHandle the Column - */ - public ColHandle getCol(String name) throws ColumnNotFoundException { - return this.getCol(ExcelTools.getIntVal(name)); - } - - /** - * returns all of the Columns in this WorkSheet - * - * @return ColHandle[] Columns - */ - public ColHandle[] getColumns() { - List columns = new CompatibleVector(); - - for (Colinfo c : mysheet.getColinfos()) { - try { - int start = c.getColFirst(); - int end = c.getColLast(); - for (int i = start; i <= end; i++) { - try { - columns.add(this.getCol(i)); - } catch (ColumnNotFoundException e) { - } - ; - } - } catch (Exception ex) { - ; - } - } - - return (ColHandle[]) columns.toArray(new ColHandle[columns.size()]); - } - - /** - * returns a List of Column names - * - * @return List column names - */ - public List getColNames() { - return mysheet.getColNames(); - } - - /** - * returns a List of Row numbers - * - * @return List of row numbers - */ - public List getRowNums() { - return mysheet.getRowNums(); - } - - /** - * returns the RowHandle for the row at index position - * - * the row index is zero based ie: Excel row 1 = 0 - * - * @return RowHandle a Row on this WorkSheet - * @param int - * row number to return - */ - public RowHandle getRow(int t) throws RowNotFoundException { - Row x = mysheet.getRowByNumber(t); - if (x == null) - throw new RowNotFoundException("Row " + t + " not found in :" + this.getSheetName()); - return new RowHandle(x, this); - } - - /** - * get an array of all RowHandles for this WorkSheet - * - * @return RowHandle[] all Rows on this WorkSheet - */ - public RowHandle[] getRows() { - Row[] rs = mysheet.getRows(); - RowHandle[] ret = new RowHandle[rs.length]; - for (int t = 0; t < rs.length; t++) { - ret[t] = new RowHandle(rs[t], this); - } - return ret; - } - - /** - * get an array of BIFFREC Rows - * - * @return RowHandle[] all Rows on this WorkSheet - */ - public Map getRowMap() { - return mysheet.getRowMap(); - } - - /** - * Returns whether a Cell exists in the WorkSheet. - * - * @param String - * the address of the Cell to check for - * @return boolean whether the Cell exists - */ - boolean hasCell(String addr) { - try { - this.getCell(addr); - return true; - } catch (CellNotFoundException e) { - return false; - } - } - - /** - * Returns the number of rows in this WorkSheet - * - * @return int Number of Rows on this WorkSheet - */ - public int getNumRows() { - return this.mysheet.getNumRows(); - } - - /** - * Returns the number of Columns in this WorkSheet - * - * @return int Number of Cols on this WorkSheet - */ - public int getNumCols() { - return this.mysheet.getNumCols(); - } - - /** - * Remove a Cell from this WorkSheet. - * - * @param CellHandle - * to remove - */ - public void removeCell(CellHandle celldel) { - mysheet.removeCell(celldel.getCell()); - } - - /** - * removes an Image from the Spreadsheet - * - * - * Jan 22, 2010 - * - * @param img - */ - public void removeImage(ImageHandle img) { - mysheet.removeImage(img); - img.remove(); - } - - /** - * Remove a Cell from this WorkSheet. - * - * @param String - * celladdr - the Address of the Cell to remove - */ - public void removeCell(String celladdr) { - mysheet.removeCell(celladdr.toUpperCase()); - } - - /** - * Remove a Row and all associated Cells from this WorkSheet. - * - * @param int - * rownum - the number of the row to remove not used public void - * removeRow(int rownum) throws RowNotFoundException{ - * mysheet.removeRow(rownum); } - */ - - /** - * Remove a Row and all associated Cells from this WorkSheet. Optionally shift - * all rows below target row up one. - * - * @param int - * rownum - the number of the row to remove - * @param boolean - * shiftrows - true will shift all lower rows up one. - */ - public void removeRow(int rownum, boolean shiftrows) throws RowNotFoundException { - if (shiftrows) - removeRow(rownum); - else - removeRow(rownum, WorkSheetHandle.ROW_DELETE_NO_REFERENCE_UPDATE); - } - - /** - * Remove a Row and all associated Cells from this WorkSheet. - * - * @param int - * rownum - the number of the row to remove uses default row deletion - * rules regarding updating references - */ - public void removeRow(int rownum) throws RowNotFoundException { - removeRow(rownum, WorkSheetHandle.ROW_DELETE); - } - - /** - * Remove all cells and formatting from a row within this WorkSheet. Has no - * other affect upon the workbook - * - * @param int - * rownum - the number of the row contents to remove - */ - public void removeRowContents(int rownum) throws RowNotFoundException { - mysheet.removeRowContents(rownum); - } - - /** - * Remove a Row and all associated Cells from this WorkSheet. - * - * @param int - * rownum - the number of the row to remove - * @param int - * flag - controls whether row deletions updates references as well - * ... - */ - public void removeRow(int rownum, int flag) throws RowNotFoundException { - - /* TODO: deal with merges! */ - mysheet.removeRows(rownum, 1, true); - - // Delete chart series IF SERIES ARE ROW-BASED -- do before updateReferences - List charts = this.mysheet.getCharts(); - for (int i = 0; i < charts.size(); i++) { - String sht = GenericPtg.qualifySheetname(this.getSheetName()); - Chart c = (Chart) charts.get(i); - HashMap seriesmap = c.getSeriesPtgs(); - Iterator ii = seriesmap.keySet().iterator(); - while (ii.hasNext()) { - io.starter.formats.XLS.charts.Series s = (io.starter.formats.XLS.charts.Series) ii.next(); - Ptg[] ptgs = (Ptg[]) seriesmap.get(s); - PtgRef pr; - String cursheet; - int[] rc; - for (int j = 0; j < ptgs.length; j++) { - try { - pr = (PtgRef) ptgs[j]; - cursheet = pr.getSheetName(); - rc = pr.getIntLocation(); - if (rc[1] != rc[3] && sht.equalsIgnoreCase(cursheet)) { // series are in rows, if existing - // series fall within deleted row - if ((rc[0]) == rownum - 1) { - c.removeSeries(j); - break; // got it - } - } else - break; // isn't row-based so split - } catch (Exception e) { - continue; // shouldn't happen! - } - } - } - // also shift chart up if necessary [BugTracker 2858] - int row = c.getRow0(); - // only move images whose top is >= rnum - int rnum = rownum + 1; - if (row > rnum) { - int h = c.getHeight(); - // move down 1 row - c.setRow(row - 1); - c.setHeight(h); - } - } - - if (flag != WorkSheetHandle.ROW_DELETE_NO_REFERENCE_UPDATE) - ReferenceTracker.updateReferences(rownum, -1, this.mysheet, true); - - // Adjust image row so that height remains constant - int rnum = rownum + 1; - ImageHandle[] images = this.getImages(); - for (int i = 0; i < images.length; i++) { - ImageHandle ih = images[i]; - int row = ih.getRow(); - // only move images whose top is >= rnum - if (row > rnum) { - short h = ih.getHeight(); - // move down 1 row - ih.setRow(row - 1); - ih.setHeight(h); - } - } - } - - /** - * Removes columns and all their associated cells from the sheet. This method - * does not shift the subsequent columns left, for that use {@link #removeCols}. - * - * @param first - * the zero-based index of the first column to be removed - * @param count - * the number of columns to remove. - */ - public void clearCols(int first, int count) { - this.removeCols(first, count, false); - } - - /** - * Removes columns from the sheet and shifts the following columns left. - * - * @param first - * the zero-based index of the first column to be removed - * @param count - * the number of columns to remove. - */ - public void removeCols(int first, int count) { - this.removeCols(first, count, true); - } - - private void removeCols(int first, int count, boolean shift) { - if (first < 0) - throw new IllegalArgumentException("column index must be zero or greater"); - if (count < 1) - throw new IllegalArgumentException("count must be at least one"); - mysheet.removeCols(first, count, shift); - } - - /** - * Removes a column and all associated cells from this sheet. This does not - * shift subsequent columns. - * - * @param colstr - * the name of the column to remove - * @deprecated Use {@link #clearCols} instead. - */ - @Deprecated - public void removeCol(String colstr) throws ColumnNotFoundException { - this.removeCols(ExcelTools.getIntVal(colstr), 1, false); - } - - /** - * Remove a column and all associated cells from this sheet. Optionally shift - * all subsequent columns left to fill the gap. - * - * @param colstr - * the name of the column to remove - * @param shiftcols - * whether to shift subsequent columns - * @deprecated Use {@link #removeCols} or {@link #clearCols} instead. - */ - @Deprecated - public void removeCol(String colstr, boolean shiftcols) throws ColumnNotFoundException { - this.removeCols(ExcelTools.getIntVal(colstr), 1, shiftcols); - } - - /** - * Returns the index of the Sheet. - * - * @return String Sheet Name - */ - public int getSheetNum() { - return mysheet.getSheetNum(); - } - - /** - * Returns all Named Range Handles scoped to this Worksheet. - * - * Note this will not include workbook scoped named ranges - * - * @return NameHandle[] all of the Named ranges that are scoped to the present - * worksheet - */ - public NameHandle[] getNamedRangesInScope() { - Name[] nand = mysheet.getSheetScopedNames(); - NameHandle[] nands = new NameHandle[nand.length]; - for (int x = 0; x < nand.length; x++) { - nands[x] = new NameHandle(nand[x], this.wbh); - } - return nands; - } - - /** - * Returns a Named Range Handle if it exists in the specified scope. - * - * This can be used to distinguish between multiple named ranges with the same - * name but differing scopes - * - * @return NameHandle a Named range in the Worksheet that exists in the scope - */ - public NameHandle getNamedRangeInScope(String rangename) throws CellNotFoundException { - Name nand = mysheet.getScopedName(rangename); - if (nand == null) - throw new CellNotFoundException(rangename); - return new NameHandle(nand, this.wbh); - } - - /** - * Returns the name of the Sheet. - * - * @return String Sheet Name - */ - public String getSheetName() { - return mysheet.getSheetName(); - } - - /** - * return the sheetname properly qualified or quoted used when the sheetname - * contains spaces, commas or parentheses - * - * @return - */ - public String getQualifiedSheetName() { - return GenericPtg.qualifySheetname(mysheet.getSheetName()); - } - - /** - * Returns the underlying low-level Boundsheet object. - * - * @return Boundsheet sheet - */ - protected Boundsheet getSheet() { - return mysheet; - } - - /** - * Returns the Serialized bytes for this WorkSheet. - * - * The output of this method can be used to insert a copy of this WorkSheet into - * another WorkBook using the WorkBookHandle.addWorkSheet(byte[] serialsheet, - * String NewSheetName) method. - * - * @return byte[] the WorkSheet's Serialized bytes - * @see WorkBookHandle.addWorkSheet(byte[] serialsheet, String NewSheetName) - */ - public byte[] getSerialBytes() { - mysheet.setLocalRecs(); - ObjectOutputStream obs = null; - byte[] b = null; - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - BufferedOutputStream bufo = new BufferedOutputStream(baos); - obs = new ObjectOutputStream(bufo); - obs.writeObject(mysheet); - bufo.flush(); - b = baos.toByteArray(); - } catch (Throwable e) { - Logger.logWarn("Serializing Sheet: " + this.toString() + " failed: " + e); - } - return b; - // return mysheet.getSheetBytes(); - } - - /** - * write this sheet as tabbed text output:
        - * All rows and all characters in each cell are saved. Columns of data are - * separated by tab characters, and each row of data ends in a carriage return. - * If a cell contains a comma, the cell contents are enclosed in double - * quotation marks. All formatting, graphics, objects, and other worksheet - * contents are lost. The euro symbol will be converted to a question mark. If - * cells display formulas instead of formula values, the formulas are saved as - * text. - */ - public void writeAsTabbedText(OutputStream dest) throws IOException { - this.mysheet.writeAsTabbedText(dest); - } - - /** - * Constructor which takes a WorkBook and sheetname as parameters. - * - * @param sht - * The name of the WorkSheet - * @param mybk - * The WorkBook - */ - protected WorkSheetHandle(Boundsheet sht, WorkBookHandle b) { - this.wbh = b; - this.mysheet = sht; - this.mybook = sht.getWorkBook(); - // 20080624 KSC: add flag for shift formula rules upon row insertion/deletion - String shiftRule = (String) System.getProperties().get("io.starter.OpenXLS.WorkSheetHandle.shiftInclusive"); - if (shiftRule != null && shiftRule.equalsIgnoreCase("true")) { - mysheet.setShiftRule(shiftRule.equalsIgnoreCase("true")); - } - // 20080917 KSC: set cache setting via system property [BugTracker 1862] - if (System.getProperty("io.starter.OpenXLS.cacheCellHandles") != null) - cache = Boolean.valueOf((System.getProperty("io.starter.OpenXLS.cacheCellHandles"))).booleanValue(); - } - - /** - * @return setting on whether to use cache or not - */ - public boolean getUseCache() { - return cache; - } - - /** - * Set the Object value of the Cell at the given address. - * - * @param String - * Cell Address (ie: "D14") - * @param Object - * new Cell Object value - * @exception CellNotFoundException - * is thrown if there is no existing Cell at the specified - * address. - */ - public void setVal(String address, Object val) throws CellNotFoundException, CellTypeMismatchException { - CellHandle c = this.getCell(address); - c.setVal(val); - } - - /** - * Set the double value of the Cell at the given address - * - * @param String - * Cell Address (ie: "D14") - * @param double - * new Cell double value - * @param address - * @param d - * @exception io.starter.formats.XLS.CellNotFoundException - * is thrown if there is no existing Cell at the specified - * address. - */ - public void setVal(String address, double d) throws CellNotFoundException, CellTypeMismatchException { - CellHandle c = this.getCell(address); - c.setVal(d); - } - - /** - * Set the String value of the Cell at the given address - * - * @param String - * Cell Address (ie: "D14") - * @param String - * new Cell String value - * @exception CellNotFoundException - * is thrown if there is no existing Cell at the specified - * address. - */ - public void setVal(String address, String s) throws CellNotFoundException, CellTypeMismatchException { - CellHandle c = this.getCell(address); - c.setVal(s); - } - - /** - * Set the name of the Worksheet. This method will change the name on the - * Worksheet's tab as displayed in the WorkBook, as well as all programmatic and - * internal references to the name. - * - * This change takes effect immediately, so all attempts to reference the - * Worksheet by its previous name will fail. - * - * @param String - * the new name for the Worksheet - */ - public void setSheetName(String name) { - wbh.sheethandles.remove(this.getSheetName()); // keep sheethandles (name->wsh) updated - mysheet.setSheetName(name); - wbh.sheethandles.put(name, this); - } - - /** - * Set the int value of the Cell at the given address - * - * @param String - * Cell Address (ie: "D14") - * @param int - * new Cell int value - * @exception io.starter.formats.XLS.CellNotFoundException - * is thrown if there is no existing Cell at the specified - * address. - */ - public void setVal(String address, int i) throws CellNotFoundException, CellTypeMismatchException { - CellHandle c = this.getCell(address); - c.setVal(i); - } - - /** - * Get the Object value of a Cell. - * - * Numeric Cell values will return as type Long, Integer, or Double. String Cell - * values will return as type String. - * - * @return the value of the Cell as an Object. - * @exception CellNotFoundException - * is thrown if there is no existing Cell at the specified - * address. - */ - public Object getVal(String address) throws CellNotFoundException { - CellHandle c = this.getCell(address); - return c.getVal(); - } - - /** - * Insert a row of Objects into the worksheet. Automatically shifts all rows - * below the cell down one. - * - * Method takes an array of Objects to insert into the rows. - * - * Object array must match columns in number starting with column A. - * - * For emptly cells, put a null Object reference in your array. - * - * example: Object[] newCellHandles = { null, // col A "Hello", // col B - * Integer.valueOf(120), // col C "=sum(A1+B1)", // col D null, // col E null, - * // col F "World" // col G }; - * - * CellHandle ret = sheet.insertRow(newCellHandles, 1, true); if(ret !=null) - * Logger.log("It worked"); - * - * @param an - * array of Objects to insert into the new row - * @param rownum - * the rownumber to insert - * @param whether - * to shift down existing Cells - */ - public CellHandle[] insertRow(int row1, Object[] data) { - return insertRow(row1, data, true); - } - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - * - * Adding new cells to non-existent rows will automatically create new rows in - * the file, This method is only necessary to "move" existing cells by inserting - * empty rows. - * - * @param rownum - * the rownumber to insert - */ - public boolean insertRow(int rownum) { - return insertRow(rownum, (Row) null, ROW_INSERT, true); - } - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - * - * Adding new cells to non-existent rows will automatically create new rows in - * the file, This method is only necessary to "move" existing cells by inserting - * empty rows. - * - * Same as insertRow(rownum) except with addition of flag - * - * @param rownum - * the rownumber to insert - * @param flag - * row insertion rule - */ - public void insertRow(int rownum, int flag) { - insertRow(rownum, (Row) null, flag, true); - } - - private CompatibleVector addedrows = new CompatibleVector(); - private boolean range_init = true; - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - * - * This method differs from insertRow in that it can be used to repeatedly - * insert rows at the same row index. - * - * Adding new cells to non-existent rows will automatically create new rows in - * the file, - * - * After calling this method, setVal() can be used on the newly created cells to - * update with new values. - * - * @param rownum - * the rownumber to insert - * @param whether - * to shift down existing Cells - * @return whether the insert was successful - */ - public boolean insertRowAt(int rownum, boolean shiftrows) { - addedrows.remove(Integer.valueOf(rownum)); - return insertRow(rownum, mysheet.getRowByNumber(rownum), rownum, shiftrows); - } - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - * - * Adding new cells to non-existent rows will automatically create new rows in - * the file, - * - * After calling this method, setVal() can be used on the newly created cells to - * update with new values. - * - * @param rownum - * the rownumber to insert (NOTE: rownum is 0-based) - * @param whether - * to shift down existing Cells - * @return whether the insert was successful - */ - public boolean insertRow(int rownum, boolean shiftrows) { - Row myr = null; - try { - myr = mysheet.getRowByNumber(rownum); - } catch (Exception e) { - ; - } - if (myr != null || shiftrows) { - return insertRow(rownum, myr, ROW_INSERT_MULTI, shiftrows); - } else { - // essentially a high performance row insert for the bottom of the workbook, - // used frequently in streaming workbook insertion - Row newRow = mysheet.insertRow(rownum, 0, ROW_INSERT_MULTI, shiftrows); - return true; - } - - } - - // insert handling flags - /** - * Insert row multiple times allowed, also copies formulas to inserted row - */ - public static final int ROW_INSERT_MULTI = 0; - /** - * Excel standard row insertion behavior - */ - public static final int ROW_INSERT = 3; - /** - * Insert row one time, multiple calls ignored - */ - public static final int ROW_INSERT_ONCE = 1; - /** - * Insert row but do not update any cell references affected by insert - */ - public static final int ROW_INSERT_NO_REFERENCE_UPDATE = 2; - - // 20080619 KSC: Add flag constants for Delete Row - public static final int ROW_DELETE = 1; - public static final int ROW_DELETE_NO_REFERENCE_UPDATE = 2; - - /** - * enhanced protection settings: Edit Object - */ - public final static short ALLOWOBJECTS = FeatHeadr.ALLOWOBJECTS; - /** - * enhanced protection settings: Edit scenario - */ - public static final short ALLOWSCENARIOS = FeatHeadr.ALLOWSCENARIOS; - /** - * enhanced protection settings: Format cells - */ - public static final short ALLOWFORMATCELLS = FeatHeadr.ALLOWFORMATCELLS; - /** - * enhanced protection settings: Format columns - */ - public static final short ALLOWFORMATCOLUMNS = FeatHeadr.ALLOWFORMATCOLUMNS; - /** - * enhanced protection settings: Format rows - */ - public static final short ALLOWFORMATROWS = FeatHeadr.ALLOWFORMATROWS; - /** - * enhanced protection settings: Insert columns - */ - public static final short ALLOWINSERTCOLUMNS = FeatHeadr.ALLOWINSERTCOLUMNS; - /** - * enhanced protection settings: Insert rows - */ - public static final short ALLOWINSERTROWS = FeatHeadr.ALLOWINSERTROWS; - /** - * enhanced protection settings: Insert hyperlinks - */ - public static final short ALLOWINSERTHYPERLINKS = FeatHeadr.ALLOWINSERTHYPERLINKS; - /** - * enhanced protection settings: Delete columns - */ - public static final short ALLOWDELETECOLUMNS = FeatHeadr.ALLOWDELETECOLUMNS; - /** - * enhanced protection settings: Delete rows - */ - public static final short ALLOWDELETEROWS = FeatHeadr.ALLOWDELETEROWS; - /** - * enhanced protection settings: Select locked cells - */ - public static final short ALLOWSELLOCKEDCELLS = FeatHeadr.ALLOWSELLOCKEDCELLS; - /** - * enhanced protection settings: Sort - */ - public static final short ALLOWSORT = FeatHeadr.ALLOWSORT; - /** - * enhanced protection settings: Use Autofilter - */ - public static final short ALLOWAUTOFILTER = FeatHeadr.ALLOWAUTOFILTER; - /** - * enhanced protection settings: Use PivotTable reports - */ - public static final short ALLOWPIVOTTABLES = FeatHeadr.ALLOWPIVOTTABLES; - /** - * enhanced protection settings: Select unlocked cells - */ - public static final short ALLOWSELUNLOCKEDCELLS = FeatHeadr.ALLOWSELUNLOCKEDCELLS; - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - * - * Adding new cells to non-existent rows will automatically create new rows in - * the file, - * - * After calling this method, setVal() can be used on the newly created cells to - * update with new values. - * - * @param rownum - * the rownumber to insert - * @param whether - * to shift down existing Cells - */ - public boolean insertRow(int rownum, RowHandle copyRow, int flag, boolean shiftrows) { - return this.insertRow(rownum, copyRow.myRow, flag, shiftrows); - } - - /** - * Insert a row of Objects into the worksheet. Shift all rows below the cell - * down one. - * - * Method takes an array of Objects to insert into the rows. - * - * Object array must match columns in number starting with column A. - * - * For emptly cells, put a null Object reference in your array. - * - * example: Object[] newCellHandles = { null, // col A "Hello", // col B - * Integer.valueOf(120), // col C "=sum(A1+B1)", // col D null, // col E null, - * // col F "World" // col G }; - * - * boolean okay = sheet.insertRow(newCellHandles, 1, true); - * if(okay)Logger.log("It worked"); - * - * @param an - * array of Objects to insert into the new row - * @param rownum - * the rownumber to insert - * @param whether - * to shift down existing Cells - * - */ - public CellHandle[] insertRow(int rownum, Object[] data, boolean shiftrows) { - CellHandle[] retc = new CellHandle[data.length]; - try { - insertRow(rownum, shiftrows); - for (int t = 0; t < data.length; t++) { - if (data[t] != null) - retc[t] = add(data[t], rownum, t); - } - } catch (Exception ex) { - throw new WorkBookException(ex.toString(), WorkBookException.RUNTIME_ERROR); - } - return retc; - } - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - * - * Adding new cells to non-existent rows will automatically create new rows in - * the file, - * - * After calling this method, setVal() can be used on the newly created cells to - * update with new values. - * - * @param rownum - * the rownumber to insert (0-based) - * @param copyrow - * the row to copy formats and formulas from - * @param flag - * determines handling tracking of inserted rows and only allow - * insertion once - * @param whether - * to shift down existing Cells - */ - private boolean insertRow(int rownum, Row copyRow, int flag, boolean shiftrows) { - int offset = 1; - return shiftRow(rownum, copyRow, flag, shiftrows, offset); - } - - /** - * replacement method for delete row that handles references better - * - * - * @param rownum - * @param flag - * @param shiftrows - * @return - */ - boolean deleteRow(int rownum, int flag, boolean shiftrows) { - int offset = -1; - return shiftRow(rownum, null, flag, shiftrows, offset); - } - - /** - * insert/delete agnostic row copy/insert/delete and formula shifter - * - * TODO: Better comments - * - * - * @param rownum - * @param copyRow - * @param flag - * @param shiftrows - * @param offset - * @return - */ - private boolean shiftRow(int rownum, Row copyRow, int flag, boolean shiftrows, int offset) { - - // If the copyrow is null, such as an insert row on an empty row, create that - // row, otherwise - // we end up using different logic for row insertion, which makes no sense. - if (copyRow == null) { - // insert a blank - this.add(null, "A" + rownum + 1); - copyRow = mysheet.getRowByNumber(rownum); - - } - // handle tracking of inserted rows -- if flag is false rows can be inserted - // multiple times at the same index - if (flag == WorkSheetHandle.ROW_INSERT_ONCE) { // not inserted - if (this.addedrows.contains(Integer.valueOf(rownum))) - return false; // can't add an existing row! - } - - // sheetster ui means insert - // 'on top of' row, shift down - if (offset == 0) - offset = 1; - - // shiftrefs BEFORE inserting new row - if (shiftrows && flag != WorkSheetHandle.ROW_INSERT_NO_REFERENCE_UPDATE) { - int refUpdateStart = rownum; - // OpenXLS default behavior is to update one row too high. If we are using - // ROW_INSERT, update per excel standard, - // see TestInsertRows.testUpdateFormulaSettings() for testing - if (flag == WorkSheetHandle.ROW_INSERT) - refUpdateStart++; - ReferenceTracker.updateReferences(refUpdateStart, offset, this.mysheet, true); // shift or expand/contract - // ALL affected references - // including named ranges - } - - int firstcol = copyRow.getColDimensions()[0]; - Row newRow = mysheet.insertRow(rownum, firstcol, flag, shiftrows); // shifts rows down and inserts a new row, - // also shifts shared formula refs (see note - // below) - - // *************************************************************************************************************************************************************/ - // Named Range, Formula and AI references: - // ALL references to the inserted row# (rownum) and rows beyond are shifted in - // ReferenceTracker.updateReferences. - // This method uses the ReferenceTracker collection for the specific sheet in - // question to iterate through the stored references, - // shifting them as the shifting rules allow. - // In addition, all formulas in the copyrow will be duplicated in the newly - // inserted row; - // These formula references are shifted in ReferenceTracker.adjustFormulaRefs - // (see below) - // The only references that are NOT shifted in the schema described above are - // SharedFormula references (specifically, PtgRefN & PtgAreaA), - // which are NOT contained within the ReferenceTracker collection. - // These references are shifted "by hand" in - // ReferenceTracker.moveSharedFormulas, called upon - // insertRow->->Boundsheet.shiftCellRow - // *************************************************************************************************************************************************************/ - - if (shiftrows) - addedrows.add(Integer.valueOf(rownum)); - - // TODO: Why so much logic in here, move this to Boundsheet? - - if ((shiftrows) && (copyRow != null)) { - // Handle shifting reference rules for the newrow only and it's copyrow (NOTE: - // copyrow may have been shifted via insertRow above although it's references - // have not yet been shifted) - int refMovementDiff = (copyRow.getRowNumber() - rownum); // number of rows to shift - int refMovementRow = rownum; // start row for shifting operation - if (refMovementDiff < 0) - refMovementRow += refMovementDiff; // since copyrow < rownum, shifting should be done BEFORE insert row - newRow.setRowHeight(copyRow.getRowHeight()); - - // Now iterate through all cells in the original row and copy formats and - // formulas - Object[] copyRowCells = copyRow.getCellArray(); - Map newmerges = new Hashtable(); - CellRange newmerge = null; - CellHandle newCellHandle = null; - Mulblank aMul = null; // KSC: Mulblank handling - short c = -1; // "" - String sheetname = GenericPtg.qualifySheetname(this.toString()); - for (int i = 0; i < copyRowCells.length; i++) { - BiffRec copyRowCell = (BiffRec) copyRowCells[i]; - if (copyRowCell.getOpcode() == XLSConstants.MULBLANK) { - if (copyRowCell == aMul) - c++; // ref next blank in range - nec. for ixfe (FormatId) see below - else { - aMul = (Mulblank) copyRowCell; - c = (short) aMul.getColFirst(); - } - aMul.setCurrentCell(c); - } - CellHandle copyCellHandle = new CellHandle(copyRowCell, this.wbh); - copyCellHandle.setWorkSheetHandle(this); - // this.cellhandles.put(copyCellHandle.getCellAddress(), copyCellHandle); - int colnum = copyCellHandle.getColNum(); - - // insert an empty copy of the cell OR, if it's a formula cell, copy formula and - // adjust it's cell references appropriate for new cell position - if (copyRowCell.getOpcode() == XLSRecord.FORMULA - && flag != WorkSheetHandle.ROW_INSERT_NO_REFERENCE_UPDATE - && flag != WorkSheetHandle.ROW_INSERT) { - try { - // copy copyrow's formula and then shift it's references relative to copycell's - // original row and references - newCellHandle = add(copyCellHandle.getFormulaHandle().getFormulaString(), rownum, colnum); - // streaming parser uses fast cell adds which returns null, populate here - if (newCellHandle == null) { - try { - newCellHandle = this.getCell(rownum, colnum); - } catch (CellNotFoundException e) { - // should be impossible } - } - } - // because the original formula has been shifted by one, unshift this sucker... - ReferenceTracker.adjustFormulaRefs(newCellHandle, refMovementRow, refMovementDiff * -1, true); - newCellHandle.getFormulaHandle().getFormulaRec().clearCachedValue(); - } catch (Exception e) { - if (DEBUGLEVEL > 0) - Logger.logWarn("WorkSheetHandle.shiftRow() could not adjust formula references in formula: " - + copyCellHandle + " while inserting new row." + e.toString()); - } - } else { - newCellHandle = add(null, rownum, colnum); - // streaming parser uses fast cell adds which returns null, populate here - if (newCellHandle == null) { - try { - newCellHandle = this.getCell(rownum, colnum); - } catch (CellNotFoundException e) { - // should be impossible } - } - } - } - newCellHandle.setFormatId(copyCellHandle.getFormatId()); - - // handle merged cells -- assemble the newmerges collection for below - CellRange oby = copyCellHandle.getMergedCellRange(); - if (oby != null) { // we have a merge - int[] fr = { rownum, oby.firstcellcol }; - int[] lr = { rownum, oby.lastcellcol }; - String newrng = sheetname + "!" + ExcelTools.formatLocation(fr) + ":" - + ExcelTools.formatLocation(lr); - newrng = GenericPtg.qualifySheetname(newrng); - if (DEBUGLEVEL > 10) - Logger.logInfo("WorksheetHandle.insertRow() created new Merge Range: " + newrng); - // check if we've already created... - if (newmerges.get(newrng) == null) { - newmerge = new CellRange(newrng, this.wbh, true); - if (DEBUGLEVEL > 10) - Logger.logInfo("WorksheetHandle.insertRow() created new Merge Range: " + newrng); - newmerges.put(newmerge.toString(), newmerge); - } - } - - } - // now update the new merge ranges... - Collection xl = newmerges.values(); - if (xl != null) { - Iterator itx = xl.iterator(); - while (itx.hasNext()) { - itx.next().mergeCells(true); - } - } - } - // Handle Image Movement - ImageHandle[] images = mysheet.getImages(); - if (images != null) { - for (int i = 0; i < images.length; i++) { - ImageHandle ih = images[i]; - int row = ih.getRow(); - // only move images whose top is >= copyRow - if (row >= rownum) { - // move down 1 row - short h = ih.getHeight(); - ih.setRow(row + 1); - ih.setHeight(h); - } - } - } - // Insert chart series IF SERIES ARE ROW-BASED - List charts = this.mysheet.getCharts(); - for (int i = 0; i < charts.size(); i++) { - Chart c = (Chart) charts.get(i); - ReferenceTracker.insertChartSeries(c, GenericPtg.qualifySheetname(this.getSheetName()), rownum); - // also shift charts down [BugTracker 2858] - int row = c.getRow0(); - // only move charts whose top is >= copyRow - if (row >= rownum) { - // move down 1 row - int h = c.getHeight(); - c.setRow(row + offset); - c.setHeight(h); - } - } - return true; - } - - /** - * returns an array of FormatHandles for the ConditionalFormats applied to this - * cell - * - * @return an array of FormatHandles, one for each of the Conditional Formatting - * rules - */ - public ConditionalFormatHandle[] getConditionalFormatHandles() { - ConditionalFormatHandle[] cfx = new ConditionalFormatHandle[this.mysheet.getConditionalFormats().size()]; - for (int i = 0; i < cfx.length; i++) { - Condfmt cfmt = (Condfmt) this.mysheet.getConditionalFormats().get(i); - cfx[i] = new ConditionalFormatHandle(cfmt, this); - } - return cfx; - } - - /** - * Returns the WorkBookHandle for this Sheet - * - * - * @return - */ - public WorkBookHandle getWorkBook() { - return this.wbh; - } - - /** - * Get a handle to all of the images in this worksheet - * - * - * @return - */ - public ImageHandle getImage(String name) throws ImageNotFoundException { - int idz = mysheet.getImageVect().indexOf(name); - if (idz > 0) - return (ImageHandle) mysheet.getImageVect().get(idz); - throw new ImageNotFoundException("Could not find " + name + " in " + this.getSheetName()); - } - - /** - * Get a handle to all of the images in this worksheet - * - * - * @return - */ - public ImageHandle[] getImages() { - return this.mysheet.getImages(); - } - - /** - * returns the actual amount of images contained in the sheet and is determined - * by imageMap - * - * @return - */ - public int getNumImages() { - return this.mysheet.imageMap.size(); - } - - /** - * write out all of the images in the Sheet to a directory - * - * - * @param imageoutput - * directory - */ - public void extractImagesToDirectory(String outdir) { - ImageHandle[] extracted = getImages(); - - // extract and output images - for (int tx = 0; tx < extracted.length; tx++) { - String n = extracted[tx].getName(); - if (n.equals("")) - n = "image" + extracted[tx].getMsodrawing().getImageIndex(); - String imgname = n + "." + extracted[tx].getType(); - if (DEBUGLEVEL > 0) - Logger.logInfo("Successfully extracted: " + outdir + imgname); - try { - FileOutputStream outimg = new FileOutputStream(outdir + imgname); - extracted[tx].write(outimg); - outimg.flush(); - outimg.close(); - } catch (Exception ex) { - Logger.logErr("Could not extract images from: " + this); - } - } - } - - /** - * retrieves all charts for this sheet and writes them (in SVG form) to outpdir - *
        - * Filename is in form of: _Chart<#>.svg - * - * @param outdir - * String output folder - */ - public void extractChartToDirectory(String outdir) { - ArrayList charts = (ArrayList) this.mysheet.getCharts(); - String sheetname = this.getSheetName(); - for (int i = 0; i < charts.size(); i++) { - ChartHandle ch = new ChartHandle((Chart) charts.get(i), this.getWorkBook()); - String fname = sheetname + "_Chart" + ch.getId() + ".svg"; - try { - FileOutputStream chartout = new FileOutputStream(outdir + fname); - chartout.write(ch.getSVG(1.0).getBytes()); // scaled as necessary in XSL - } catch (Exception ex) { - Logger.logErr("extractChartToDirectory: Could not extract charts from: " + this + ":" + ex.toString()); - } - - } - } - - /** - * insert an image into this worksheet - * - * - * @param im - * -- the ImageHandle to insert - * @see ImageHandle - */ - public void insertImage(ImageHandle im) { - this.mysheet.insertImage(im); - } - - /** - * Inserts empty columns and shifts the following columns to the right. This - * method is used to shift existing columns right to make room for a new column. - * To create a column in the file so its size or style can be set just add a - * blank cell to its first row with {@link #add}. - * - * @param first - * the zero-based index of the first column to insert - * @param count - * the number of columns to insert - */ - public void insertCols(int first, int count) { - if (first < 0) - throw new IllegalArgumentException("column index must be zero or greater"); - if (count < 1) - throw new IllegalArgumentException("count must be at least one"); - mysheet.insertCols(first, count); - } - - /** - * Inserts an empty column and shifts the following columns to the right. - * - * @param colnum - * the zero-based index of the column to be inserted - * @deprecated Use {@link #insertCols} instead. - */ - @Deprecated - public void insertCol(int colnum) { - this.insertCols(colnum, 1); - } - - /** - * Inserts an empty column and shifts the following columns to the right. - * - * @param colnum - * the address of the column to be inserted - * @deprecated Use {@link #insertCols} instead. - */ - @Deprecated - public void insertCol(String colnum) { - this.insertCols(ExcelTools.getIntVal(colnum), 1); - } - - /** - * When adding a new Cell to the sheet, OpenXLS can automatically copy the - * formatting from the Cell directly above the inserted Cell. - * - * ie: if set to true, newly added Cell D19 would take its formatting from Cell - * D18. - * - * Default is false - * - * - * - * boolean copy the formats from the prior Cell - */ - public void setCopyFormatsFromPriorWhenAdding(boolean f) { - mysheet.setCopyPriorCellFormats(f); - } - - /** - * Add a Cell with the specified value to a WorkSheet. - * - * This method determines the Cell type based on type-compatibility of the - * value. - * - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * @param obj - * the value of the new Cell - * @param int - * row the row of the new Cell - * @param int - * col the column of the new Cell - * - */ - public CellHandle add(Object obj, int row, int col) { - return add(obj, row, col, this.getWorkBook().getWorkBook().getDefaultIxfe()); - } - - /** - * Add a Cell with the specified value to a WorkSheet. - * - * This method determines the Cell type based on type-compatibility of the - * value. - * - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param obj - * the value of the new Cell - * @param int - * row the row of the new Cell - * @param int - * col the column of the new Cell - * - */ - public CellHandle[] addValidated(Object obj, int row, int col) throws ValidationException { - return addValidated(obj, row, col, this.getWorkBook().getWorkBook().getDefaultIxfe()); - } - - /** - * Add a Cell with the specified value to a WorkSheet. - * - * This method determines the Cell type based on type-compatibility of the - * value. - * - * Further, this method allows passing in a format id - * - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param obj - * the value of the new Cell - * @param int - * row the row of the new Cell - * @param int - * col the column of the new Cell - * @param int - * the format id to apply to this cell - * - */ - public CellHandle[] addValidated(Object obj, int row, int col, int formatId) throws ValidationException { - int[] rc = { row, col }; - ValidationHandle vh = this.getValidationHandle(ExcelTools.formatLocation(rc)); - if (vh != null) { - vh.isValid(obj); - } - return this.addValidated(obj, ExcelTools.formatLocation(rc)); - } - - /** - * Add a Cell with the specified value to a WorkSheet. - * - * This method determines the Cell type based on type-compatibility of the - * value. - * - * Further, this method allows passing in a format id - * - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * @param obj - * the value of the new Cell - * @param int - * row the row of the new Cell - * @param int - * col the column of the new Cell - * @param int - * the format id to apply to this cell - * - */ - public CellHandle add(Object obj, int row, int col, int formatId) { - int[] rc = { row, col }; - - if (obj instanceof java.util.Date) { - String address = ExcelTools.formatLocation(rc); - this.add((java.util.Date) obj, address, null); - } else { - BiffRec reca = mysheet.addValue(obj, rc, formatId); - - if (DEBUGLEVEL > 1) - if (reca != null) - Logger.logInfo("WorkSheetHandle.add() " + reca.toString() + " Successfully Added."); - else - return null; - } - - if (!mysheet.fastCellAdds) { - try { - CellHandle c = this.getCell(row, col); - if (this.wbh.getFormulaCalculationMode() != wbh.CALCULATE_EXPLICIT) - c.clearAffectedCells(); // blow out cache - return c; - } catch (CellNotFoundException e) { - Logger.logInfo("Adding Cell to row failed row:" + row + " col: " + col + " failed."); - return null; - } - } else { - return null; - } - } - - /** - * Fast-adds a Cell with the specified value to a WorkSheet. - * - * This method determines the Cell type based on type-compatibility of the - * value. - * - * Further, this method allows passing in a format id - * - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * @param obj - * the value of the new Cell - * @param int - * row the row of the new Cell - * @param int - * col the column of the new Cell - * @param int - * the format id to apply to this cell - * - */ - public void fastAdd(Object obj, int row, int col, int formatId) { - int[] rc = { row, col }; - - // use default format - if (formatId == -1) - formatId = 0; - - if (obj instanceof java.util.Date) { - String address = ExcelTools.formatLocation(rc); - this.add((java.util.Date) obj, address, null); - } else { - BiffRec reca = mysheet.addValue(obj, rc, formatId); - if (this.wbh.getFormulaCalculationMode() != wbh.CALCULATE_EXPLICIT) { - ReferenceTracker rt = this.wbh.getWorkBook().getRefTracker(); - rt.clearAffectedFormulaCells(reca); - } - if (DEBUGLEVEL > 1) - if (reca != null) - Logger.logInfo("WorkSheetHandle.add() " + reca.toString() + " Successfully Added."); - } - } - - /** - * Toggle fast cell add mode. - * - * Set to true to turn off checking for existing cells, conditional formats and - * merged ranges in order to accelerate adding new cells - * - * @param fastadds - * whether to disable checking for existing cells and - */ - public void setFastCellAdds(boolean fastadds) { - this.mysheet.setFastCellAdds(fastadds); - } - - /** - * Get the current fast add cell mode for this worksheet - */ - public boolean getFastCellAdds() { - return this.mysheet.fastCellAdds; - } - - /** - * Add a Cell with the specified value to a WorkSheet, optionally attempting to - * convert numeric values to appropriate number cells with appropriate number - * formatting applied. - * - * This would allow a value entered such as "$1.00" to be converted to a numeric - * 1.00d value with a $0.00 format pattern applied. - * - * Note that there is overhead to this method, as the value added needs to be - * parsed, for higher performance do not use the autodetectNumberAndPattern = - * true setting, and instead pass numeric values and format patterns. - * - * If the value is non numeric, then it is simply added. - * - * @param obj - * the value of the new Cell - * @param address - * the address of the new Cell - * @param autoDetectValue - * whether to attempt to store as a number with a format pattern. - * - * - */ - public CellHandle add(Object obj, String address, boolean autoDetectValue) { - if (obj == null) - mysheet.addValue(obj, address); // to add a blank cell - else if (obj instanceof Date) - this.add((Date) obj, address, null); - else - mysheet.addValue(obj, address, autoDetectValue);// - - // fast-adds optimzation -- do not return a CellHandle - if (this.mysheet.fastCellAdds) - return null; - - try { - return this.getCell(address); - } catch (CellNotFoundException e) { - Logger.logInfo("Adding Cell: " + address + " failed"); - return null; - } - } - - /** - * Add a Cell with the specified value to a WorkSheet. - * - * This method determines the Cell type based on type-compatibility of the - * value. - * - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * @param obj - * the value of the new Cell - * @param address - * the address of the new Cell - * - */ - public CellHandle add(Object obj, String address) { - if (obj == null) - mysheet.addValue(obj, address); // to add a blank cell - else if (obj instanceof Date) - this.add((Date) obj, address, null); - else - mysheet.addValue(obj, address);// - - // fast-adds optimzation -- do not return a CellHandle - if (!mysheet.fastCellAdds) { - try { - CellHandle c = this.getCell(address); - if (this.wbh.getFormulaCalculationMode() != wbh.CALCULATE_EXPLICIT) - c.clearAffectedCells(); // blow out cache - return c; - } catch (CellNotFoundException e) { - Logger.logInfo("Adding Cell: " + address + " failed"); - return null; - } - } else { - return null; - } - } - - /** - * Add a Cell with the specified value to a WorkSheet. - * - * This method determines the Cell type based on type-compatibility of the - * value. - * - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param obj - * the value of the new Cell - * @param address - * the address of the new Cell - * - */ - public CellHandle[] addValidated(Object obj, String address) throws ValidationException { - ValidationHandle vh = this.getValidationHandle(address); - if (vh != null) { - vh.isValid(obj); - } - CellHandle ch = this.add(obj, address); - List cxrs = ch.calculateAffectedCellsOnSheet(); - - // return the cellhandles - CellHandle[] cxrx = new CellHandle[cxrs.size() + 1]; - cxrx[0] = ch; - for (int t = 1; t < cxrx.length; t++) { - cxrx[t] = (CellHandle) cxrs.get(t - 1); - cxrx[t].setWorkSheetHandle(this); - // this.cellhandles.put(cxrx[t].getCellAddress(), cxrx[t]); - } - - return cxrx; - } - - /** - * Add a java.sql.Timestamp Cell to a WorkSheet. - * - * Will create a default format of: - * - * "m/d/yyyy h:mm:ss" - * - * if none is specified. - * - * @param dt - * the value of the new java.sql.Timestamp Cell - * @param address - * the address of the new java.sql.Date Cell - * @param formatting - * pattern the address of the new java.sql.Date Cell - */ - public CellHandle add(Timestamp dt, String address, String fmt) { - if (fmt == null) - fmt = "m/d/yyyy h:mm:ss"; - Date dx = new Date(dt.getTime()); - return this.add(dx, address, fmt); - } - - /** - * Add a java.sql.Timestamp Cell to a WorkSheet. - * - * Will create a default format of: - * - * "m/d/yyyy h:mm:ss" - * - * if none is specified. - * - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param dt - * the value of the new java.sql.Timestamp Cell - * @param address - * the address of the new java.sql.Date Cell - * @param formatting - * pattern the address of the new java.sql.Date Cell - */ - public CellHandle[] addValidated(Timestamp dt, String address, String fmt) throws ValidationException { - if (fmt == null) - fmt = "m/d/yyyy h:mm:ss"; - Date dx = new Date(dt.getTime()); - return this.addValidated(dx, address, fmt); - } - - /** - * Add a java.sql.Date Cell to a WorkSheet. - * - * You must specify a formatting pattern for the new date, or null for the - * default ("m/d/yy h:mm".) - * - * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" - * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" - * - * @param dt - * the value of the new java.sql.Date Cell - * @param row - * to add the date - * @param col - * to add the date - * @param formatting - * pattern the address of the new java.sql.Date Cell - */ - public CellHandle add(java.util.Date dt, String address, String fmt) { - int[] rc = ExcelTools.getRowColFromString(address); - return add(dt, rc[0], rc[1], fmt); - } - - /** - * Add a java.sql.Date Cell to a WorkSheet. - * - * You must specify a formatting pattern for the new date, or null for the - * default ("m/d/yy h:mm".) - * - * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" - * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" - * - * @param dt - * the value of the new java.sql.Date Cell - * @param row - * to add the date - * @param col - * to add the date - * @param formatting - * pattern the address of the new java.sql.Date Cell - */ - public CellHandle[] addValidated(java.util.Date dt, String address, String fmt) throws ValidationException { - int[] rc = ExcelTools.getRowColFromString(address); - return addValidated(dt, rc[0], rc[1], fmt); - } - - /** - * Add a java.sql.Date Cell to a WorkSheet. - * - * You must specify a formatting pattern for the new date, or null for the - * default ("m/d/yy h:mm".) - * - * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" - * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" - * - * @param dt - * the value of the new java.sql.Date Cell - * @param address - * the address of the new java.sql.Date Cell - * @param formatting - * pattern the address of the new java.sql.Date Cell - */ - public CellHandle add(java.util.Date dt, int row, int col, String fmt) { - double x = DateConverter.getXLSDateVal(dt, this.mybook.getDateFormat()); - CellHandle thisCell = this.add(new Double(x), row, col); - - // first handle fast adds - if (thisCell == null && this.mysheet.fastCellAdds) { - try { - thisCell = getCell(row, col); - } catch (CellNotFoundException exp) { - Logger.logWarn("adding date to WorkSheet failed: " + this.getSheetName() + ":" + row + ":" + col); - return null; - } - } - - // 20060419 KSC: Use format from cell, if any - if (fmt == null) { - fmt = thisCell.getFormatPattern(); - if (fmt == null || fmt.equals("General")) - fmt = "m/d/yy h:mm"; - } - if (dateFormats.get(fmt) == null) { - FormatHandle fh = thisCell.getFormatHandle(); - fh.setFormatPattern(fmt); - dateFormats.put(fmt, Integer.valueOf(thisCell.getFormatId())); - } else { - Integer in = dateFormats.get(fmt); - thisCell.setFormatId(in.intValue()); - } - // Logger.logInfo("Date added: " + thisCell.getFormattedStringVal()); - return thisCell; - } - - /** - * Add a java.sql.Date Cell to a WorkSheet. - * - * You must specify a formatting pattern for the new date, or null for the - * default ("m/d/yy h:mm".) - * - * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" - * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" - * - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param dt - * the value of the new java.sql.Date Cell - * @param address - * the address of the new java.sql.Date Cell - * @param formatting - * pattern the address of the new java.sql.Date Cell - */ - public CellHandle[] addValidated(java.util.Date dt, int row, int col, String fmt) throws ValidationException { - int[] rc = { row, col }; - ValidationHandle vh = this.getValidationHandle(ExcelTools.formatLocation(rc)); - if (vh != null) { - vh.isValid(dt); - } - CellHandle ch = this.add(dt, row, col, fmt); - List cxrs = ch.calculateAffectedCellsOnSheet(); - - // FIXME: Use List.toArray instead of for loop - // return the cellhandles - CellHandle[] cxrx = new CellHandle[cxrs.size() + 1]; - cxrx[0] = ch; - for (int t = 1; t < cxrx.length; t++) { - cxrx[t] = (CellHandle) cxrs.get(t - 1); - } - - return cxrx; - - } - - /** - * Remove this WorkSheet from the WorkBook - * - * NOTE: will throw a WorkBookException if the last sheet is removed. This - * results in an invalid output file. - */ - public void remove() { - mybook.removeWorkSheet(this.mysheet); - wbh.sheethandles.remove(this.getSheetName()); - } - - /** - * Create a CellRange object from an OpenXLS string range passed in such as - * "A1:F6" - * - * @param rangeName - * "A1:F6" - * @return - * @throws CellNotFoundException - * if the range cannot be created - */ - public CellRange getCellRange(String rangeName) throws CellNotFoundException { - CellRange cr = new CellRange(this.getSheetName() + "!" + rangeName, this.getWorkBook()); - return cr; - } - - /** - * Returns all CellHandles defined on this WorkSheet. - * - * @return CellHandle[] - the array of Cells in the Sheet - */ - public CellHandle[] getCells() { - BiffRec[] cells = mysheet.getCells(); - CellHandle[] retval = new CellHandle[cells.length]; - Mulblank aMul = null; - short c = -1; - for (int i = 0; i < retval.length; i++) { - try { - if (cells[i].getOpcode() != XLSConstants.MULBLANK) { - retval[i] = getCell(cells[i].getRowNumber(), cells[i].getColNumber()); - } else { // Handle MULBLANKS proper column number - if (cells[i] == aMul) { - c++; - } else { - aMul = (Mulblank) cells[i]; - c = (short) aMul.getColFirst(); - } - retval[i] = getCell(cells[i].getRowNumber(), c); - } - } catch (CellNotFoundException cnfe) { - // try harder - retval[i] = new CellHandle(cells[i], this.wbh); - retval[i].setWorkSheetHandle(this); - if (cells[i].getOpcode() == XLSConstants.MULBLANK) { - // handle Mulblanks: ref a range of cells; to get correct cell address, - // traverse thru range and set cellhandle ref to correct column - if (cells[i] == aMul) { - c++; - } else { - aMul = (Mulblank) cells[i]; - c = (short) aMul.getColFirst(); - } - retval[i].setBlankRef(c); // for Mulblank use only -sets correct column reference for multiple blank - // cells ... - } - } - } - return retval; - } - - /** - * Returns a FormulaHandle for working with the ranges of a formula on a - * WorkSheet. - * - * @param addr - * the address of the Cell - * @exception FormulaNotFoundException - * is thrown if there is no existing formula at the specified - * address. - */ - public FormulaHandle getFormula(String addr) throws FormulaNotFoundException, CellNotFoundException { - CellHandle c = this.getCell(addr); - return c.getFormulaHandle(); - } - - /** - * Returns a CellHandle for working with the value of a Cell on a WorkSheet. - * - * @param addr - * the address of the Cell - * @exception CellNotFoundException - * is thrown if there is no existing Cell at the specified - * address. - */ - public CellHandle getCell(String addr) throws CellNotFoundException { - CellHandle ret = null; // - - BiffRec c = mysheet.getCell(addr.toUpperCase()); - if (c == null) { - String sn = ""; - try { - sn = this.getSheetName(); - sn += "!"; - } catch (Exception e) { - ; - } - if (addr == null) - addr = "undefined cell address"; - throw new CellNotFoundException(sn + addr); - } - ret = new CellHandle(c, this.wbh); - ret.setWorkSheetHandle(this); - return ret; - } - - /** - * Returns a CellHandle for working with the value of a Cell on a WorkSheet. - * - * returns a new CellHandle with each call - * - * use caching method getCell(int row, int col, boolean cache) to control - * caching of CellHandles. - * - * @param int - * Row the integer row of the Cell - * @param int - * Col the integer col of the Cell - * @exception CellNotFoundException - * is thrown if there is no existing Cell at the specified - * address. - */ - public CellHandle getCell(int row, int col) throws CellNotFoundException { - return getCell(row, col, false); - } - - /** - * Returns a CellHandle for working with the value of a Cell on a WorkSheet. - * - * - * @param int - * Row the integer row of the Cell - * @param int - * Col the integer col of the Cell - * @param boolean - * whether to cache or return a new CellHandle each call - * @exception CellNotFoundException - * is thrown if there is no existing Cell at the specified - * address. - */ - public CellHandle getCell(int row, int col, boolean cache) throws CellNotFoundException { - CellHandle ret = null; - if (cache) { - int[] rc = { row, col }; - String address = ExcelTools.formatLocation(rc); - // ret = (CellHandle)cellhandles.get(address); - if (ret != null) // caching! - return ret; - else { - ret = new CellHandle(this.mysheet.getCell(row, col), this.wbh); - ret.setWorkSheetHandle(this); - // cellhandles.put(address,ret); - return ret; - } - } - ret = new CellHandle(this.mysheet.getCell(row, col), this.wbh); - ret.setWorkSheetHandle(this); - return ret; - } - - /** - * Move a cell on this WorkSheet. - * - * @param CellHandle - * c - the cell to be moved - * @param String - * celladdr - the destination address of the cell - */ - public void moveCell(CellHandle c, String addr) throws CellPositionConflictException { - this.mysheet.moveCell(c.getCellAddress(), addr); - // c.moveTo(addr); < redundant call to above. 070104 -jm - } - - /** - * Get the text for the Footer printed at the bottom of the Worksheet - * - * @return String footer text - */ - public String getFooterText() { - return mysheet.getFooter().getFooterText(); - } - - /** - * Get the text for the Header printed at the top of the Worksheet - * - * @return String header text - */ - public String getHeaderText() { - return mysheet.getHeader().getHeaderText(); - } - - /** - * Get the print area set for this WorkSheetHandle. - * - * If no print area is set return null; - * - */ - public String getPrintArea() { - return mysheet.getPrintArea(); - } - - /** - * Get the Print Titles set for this WorkSheetHandle. - * - * If no Print Titles are set, this returns null; - */ - public String getPrintTitles() { - return mysheet.getPrintTitles(); - } - - /** - * Get the printer settings handle for this WorkSheetHandle. - * - * - */ - public PrinterSettingsHandle getPrinterSettings() { - return mysheet.getPrinterSetupHandle(); - } - - /** - * Sets the print area for the worksheet - * - * - * sets the printarea as a CellRange - * - * @param printarea - */ - public void setPrintArea(CellRange printarea) { - mysheet.setPrintArea(printarea.getRange()); - } - - /** - * Set the text for the Header printed at the top of the Worksheet - * - * @param String - * header text - */ - public void setHeaderText(String t) { - mysheet.getHeader().setHeaderText(t); - } - - /** - * Set the text for the Footer printed at the bottom of the Worksheet - * - * @param String - * footer text - */ - public void setFooterText(String t) { - mysheet.getFooter().setFooterText(t); - } - - /** - * Set the default column width of the worksheet - * - *
        - * - * This setting is roughly the width of the character '0' The default width of a - * column is 8. - */ - public void setDefaultColWidth(int t) { - mysheet.setDefaultColumnWidth(t); - } - - public int getDefaultColWidth() { - return (int) mysheet.getDefaultColumnWidth(); - } - - /** - * Returns the name of this Sheet. - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return mysheet.toString(); - } - - /** - * FOR internal Use Only! - * - * @return Returns the low-level sheet record. - */ - public Boundsheet getMysheet() { - return mysheet; - } - - /** - * Calculates all formulas that reference the cell address passed in. - * - * Please note that these cells have already been calculated, so in order to get - * their values without re-calculating them Extentech suggests setting the book - * level non-calculation flag, ie - * book.setFormulaCalculationMode(WorkBookHandle.CALCULATE_EXPLICIT) or - * FormulaHandle.getCachedVal() - * - * @return List of of calculated cells - */ - public List calculateAffectedCells(String CellAddress) { - CellHandle c = null; - try { - c = this.getCell(CellAddress); - } catch (CellNotFoundException e) { - return null; - } - return c.calculateAffectedCells(); - } - - /** - * Set whether to show calculated formula results in the output sheet. - * - * @return boolean whether to show calculated formula results - */ - public boolean getShowFormulaResults() { - return this.mysheet.getWindow2().getShowFormulaResults(); - } - - public void setShowFormulaResults(boolean b) { - this.mysheet.getWindow2().setShowFormulaResults(b); - } - - /** - * Get whether to show gridlines in the output sheet. - * - * @param boolean - * whether to show gridlines - */ - public boolean getShowGridlines() { - return this.mysheet.getWindow2().getShowGridlines(); - } - - /** - * Set whether to show gridlines in the output sheet. - * - * @return boolean whether to show gridlines - */ - public void setShowGridlines(boolean b) { - this.mysheet.getWindow2().setShowGridlines(b); - } - - /** - * Get whether to show sheet headers in the output sheet. - * - * @return boolean whether to show sheet headers - */ - public boolean getShowSheetHeaders() { - return this.mysheet.getWindow2().getShowSheetHeaders(); - } - - /** - * Set whether to show sheet headers in the output sheet. - * - * @param boolean - * whether to show sheet headers - */ - public void setShowSheetHeaders(boolean b) { - this.mysheet.getWindow2().setShowSheetHeaders(b); - } - - /** - * Get whether to show zero values in the output sheet. - * - * @return boolean whether to show zero values - */ - public boolean getShowZeroValues() { - return this.mysheet.getWindow2().getShowZeroValues(); - } - - /** - * Set whether to show zero values in the output sheet. - * - * @return boolean whether to show zero values - */ - public void setShowZeroValues(boolean b) { - this.mysheet.getWindow2().setShowZeroValues(b); - } - - /** - * Get whether to show outline symbols in the output sheet. - * - * @return boolean whether to outline symbols - */ - public boolean getShowOutlineSymbols() { - return this.mysheet.getWindow2().getShowOutlineSymbols(); - } - - /** - * Set whether to show outline symbols in the output sheet. - * - * @param boolean - * whether to show outline symbols - */ - public void setShowOutlineSymbols(boolean b) { - this.mysheet.getWindow2().setShowOutlineSymbols(b); - } - - /** - * Get whether to show normal view or page break preview view in the output - * sheet. - * - * @return boolean whether to show normal view or page break preview view - */ - public boolean getShowInNormalView() { - return this.mysheet.getWindow2().getShowInNormalView(); - } - - /** - * Set whether to show normal view or page break preview view in the output - * sheet. - * - * @param boolean - * whether to show normal view or page break preview view - */ - public void setShowInNormalView(boolean b) { - this.mysheet.getWindow2().setShowInNormalView(!b); // opposite of expected behavior - } - - /** - * Get whether there are freeze panes in the output sheet. - * - * @return boolean whether there are freeze panes - */ - public boolean hasFrozenPanes() { - return this.mysheet.getWindow2().getFreezePanes(); - } - - /** - * Set whether there are freeze panes in the output sheet. - * - * @param boolean - * whether there are freeze panes - */ - public void setHasFrozenPanes(boolean b) { - this.mysheet.getWindow2().setFreezePanes(b); - if (!b && this.mysheet.getPane() != null) { - this.mysheet.removePane(); // remove pane rec if unfreezing ... can also convert to plain splits, but a bit - // more complicated ... - } - } - - /** - * sets the zoom for the sheet - * - * @param the - * zoom as a float percentage (.25 = 25%) - */ - public void setZoom(float zm) { - this.mysheet.getScl().setZoom(zm); - } - - /** - * if this sheet has freeze panes, return the address of the top left cell - * otherwise, return null - * - * @return - */ - public String getTopLeftCell() { - if (this.mysheet.getPane() != null) { - return this.mysheet.getPane().getTopLeftCell(); - } - return null; - } - - /** - * gets the zoom for the sheet - * - * @return the zoom as a float percentage (.25 = 25%) - */ - public float getZoom() { - return this.mysheet.getScl().getZoom(); - } - - /** - * freezes the rows starting at the specified row and creating a scrollable - * sheet below this row - * - * @param row - * the row to start the freeze - */ - public void freezeRow(int row) { - if (this.mysheet.getPane() == null) - this.mysheet.setPane(null); // will add new - this.mysheet.getPane().setFrozenRow(row); - } - - /** - * freezes the cols starting at the specified column and creating a scrollable - * sheet to the right - * - * @param col - * the col to start the freeze - */ - public void freezeCol(int col) { - if (this.mysheet.getPane() == null) - this.mysheet.setPane(null); // will add new - this.mysheet.getPane().setFrozenColumn(col); - } - - /** - * splits the worksheet at column col for nCols - * - * Note: unfreezes panes if frozen - * - * - * @param col - * col start col to split - * @param splitpos - * position of the horizontal split - */ - public void splitCol(int col, int splitpos) { - if (this.mysheet.getPane() == null) - this.mysheet.setPane(null); // will add new - this.mysheet.getPane().setSplitColumn(col, splitpos); - } - - /** - * splits the worksheet at row for nRows - * - * Note: unfreezes panes if frozen - * - * @param row - * start row to split - * @param splitpos - * position of the vertical split - */ - public void splitRow(int row, int splitpos) { - if (this.mysheet.getPane() == null) - this.mysheet.setPane(null); // will add new - this.mysheet.getPane().setSplitRow(row, splitpos); - } - - /** - * Gets the row number (0 based) that the sheet split is located on. If the - * sheet is not split returns -1 - * - * - * @return - */ - public int getSplitRowLocation() { - if (this.mysheet.getPane() == null) - return -1; - return this.mysheet.getPane().getVisibleRow(); - } - - /** - * gets the column number (0-based)that the sheet split is locaated on; if the - * sheet is not split, returns -1 - * - * @return 0-based index of split column, if any - */ - public int getSplitColLocation() { - if (this.mysheet.getPane() == null) - return -1; - return this.mysheet.getPane().getVisibleCol(); - } - - /** - * Gets the twips split location returns -1 - * - * - * @return - */ - public int getSplitLocation() { - if (this.mysheet.getPane() == null) - return -1; - return this.mysheet.getPane().getRowSplitLoc(); - } - - /** - * Get whether to use manual grid color in the output sheet. - * - * - * - * @return boolean whether to use manual grid color - */ - public boolean getManualGridLineColor() { - return this.mysheet.getWindow2().getManualGridLineColor(); - } - - /** - * Set whether to use manual grid color in the output sheet. - * - * @param boolean - * whether to use manual grid color - */ - public void setManualGridLineColor(boolean b) { - this.mysheet.getWindow2().setManualGridLineColor(b); - } - - public CompatibleVector getAddedrows() { - return addedrows; - } - - /** - * Create a Conditional Format handle for a cell/range - * - * - * @param cellAddress - * without sheetname. Can also be a range, such as A1:B5 - * @param qualifier - * = maps to CONDITION_* bytes in ConditionalFormatHandle - * @param value1 - * = the error message - * @param value2 - * = the error title - * @param format - * = the initial format string to use with the condition - * @param firstCondition - * = formula string - * @param secondCondition - * = 2nd formula string (optional) - * @return - */ - public ConditionalFormatHandle createConditionalFormatHandle(String cellAddress, String operator, String value1, - String value2, String format, String firstCondition, String secondCondition) { - - if (cellAddress != null && cellAddress.indexOf("!") == -1) - cellAddress = this.getSheetName() + "!" + cellAddress; - Condfmt cfm = this.mysheet.createCondfmt(cellAddress, this.wbh); - - Cf cfr = this.mysheet.createCf(cfm); - cfr.setOperator(operator); - - // only place this is done.. - cfr.setCondition1(value1); - cfr.setCondition2(value2); - - Cf.setStylePropsFromString(format, cfr); - - ConditionalFormatHandle cfh = new ConditionalFormatHandle(cfm, this); - // done above in createCondfmt this.mysheet.addConditionalFormat(cfm); - return cfh; - } - - /** - * Creates a new annotation (Note or Comment) to the worksheeet, attached to a - * specific cell - * - * @param address - * -- address to attach - * @param txt - * -- text of note - * @param author - * -- name of author - * @return NoteHandle - handle which allows access to the Note object - * @see CommentHandle - */ - public CommentHandle createNote(String address, String txt, String author) { - Note n = this.getMysheet().createNote(address, txt, author); - return new CommentHandle(n); - } - - /** - * Creates a new annotation (Note or Comment) to the worksheeet, attached to a - * specific cell
        - * The note or comment is a Unicode string, thus it can contain formatting - * information - * - * @param address - * -- address to attach - * @param txt - * -- Unicode string of note with Formatting - * @param author - * -- name of author - * @return NoteHandle - handle which allows access to the Note object - * @see CommentHandle - */ - public CommentHandle createNote(String address, Unicodestring txt, String author) { - Note n = this.getMysheet().createNote(address, txt, author); - return new CommentHandle(n); - } - - /** - * returns an array of all CommentHandles that exist in the sheet - * - * @return - */ - public CommentHandle[] getCommentHandles() { - ArrayList notes = getMysheet().getNotes(); - CommentHandle[] nHandles = new CommentHandle[notes.size()]; - for (int i = 0; i < nHandles.length; i++) { - nHandles[i] = new CommentHandle((Note) notes.get(i)); - } - return nHandles; - } - - /** - * creates a new, blank PivotTable and adds it to the worksheet. - * - * @param name - * pivot table name - * @param range - * source range for the pivot table. If no sheet is specified, the - * current sheet will be used. - * @param sId - * Stream or cachid Id -- links back to SxStream set of records - * @return PivotTableHandle - */ - public PivotTableHandle createPivotTable(String name, String range, int sId) { - Sxview sx = getMysheet().addPivotTable(range, this.wbh, sId, name); - PivotTableHandle pth = new PivotTableHandle(sx, this.getWorkBook()); - pth.setSourceDataRange(range); - return pth; - } - - /** - * Get a validation handle for the cell address passed in. If the validation is - * for a range, the handle returned will modify the entire range, not just the - * cell address passed in. - * - * Returns null if a validation does not exist at the specified location - * - * - * @param cell - * address String - */ - public ValidationHandle getValidationHandle(String cellAddress) { - if (this.mysheet.getDvalRec() != null) { - Dv d = this.mysheet.getDvalRec().getDv(cellAddress); - if (d == null) - return null; - return new ValidationHandle(d); - } - return null; - } - - /** - * Create a validation handle for a cell/range - * - * - * @param cellAddress - * without sheetname. Can also be a range, such as A1:B5 - * @param valueType - * = maps to VALUE_* bytes in ValidationHandle - * @param condition - * = maps to CONDITION_* bytes in ValidationHandle - * @param errorBoxText - * = the error message - * @param errorBoxTitle - * = the error title - * @param promptBoxText - * = the prompt (hover) message - * @param promptBoxTitle - * = the prompt (hover) title - * @param firstCondition - * = formula string, seeValidationHandle.setFirstCondition - * @param secondCondition - * = seeValidationHandle.setSecondCondition, this can be left null - * for validations that do not require a second argument. - * @return - */ - public ValidationHandle createValidationHandle(String cellAddress, byte valueType, byte condition, - String errorBoxText, String errorBoxTitle, String promptBoxText, String promptBoxTitle, - String firstCondition, String secondCondition) { - Dv d = this.mysheet.createDv(cellAddress); - d.setValType(valueType); - /* - * // KSC: APPARENTLY NOT NEEDED if - * (valueType==ValidationHandle.VALUE_USER_DEFINED_LIST) { // ensure Mso - * Drop-downs are defined int objId = - * this.mysheet.insertDropDownBox(d.getColNumber()); //TODO: verify that drop - * down lists are SHARED **** - * this.mysheet.getDvalRec().setObjectIdentifier(objId); } - */ - d.setTypeOperator(condition); - d.setErrorBoxText(errorBoxText); - d.setErrorBoxTitle(errorBoxTitle); - d.setPromptBoxText(promptBoxText); - d.setPromptBoxTitle(promptBoxTitle); - if (firstCondition != null) - d.setFirstCond(firstCondition); - if (secondCondition != null) - d.setSecondCond(secondCondition); - ValidationHandle vh = new ValidationHandle(d); - return vh; - } - - /** - * Return all validation handles that refer to this worksheet - * - * - * @return array of all validationhandles valid for this worksheet - */ - public ValidationHandle[] getAllValidationHandles() { - if (mysheet.getDvRecs() == null) - return new ValidationHandle[0]; - ValidationHandle[] vh = new ValidationHandle[mysheet.getDvRecs().size()]; - List dvrecs = mysheet.getDvRecs(); - for (int i = 0; i < vh.length; i++) { - vh[i] = new ValidationHandle((Dv) dvrecs.get(i)); - } - return vh; - } - - /** - * return true if sheet contains data validations - * - * @return boolean - */ - public boolean hasDataValidations() { - return (mysheet.getDvRecs() != null); - } - - /** - * Returns a list of all AutoFilterHandles on this sheet
        - * An AutoFilterHandle allows access and manipulation of AutoFilters on the - * sheet - * - * @return array of AutoFilterHandles if any exist on sheet, null otherwise - */ - public AutoFilterHandle[] getAutoFilterHandles() { - if (mysheet.getAutoFilters() == null) - return null; - AutoFilterHandle[] af = new AutoFilterHandle[mysheet.getAutoFilters().size()]; - List afs = mysheet.getAutoFilters(); - for (int i = 0; i < afs.size(); i++) { - af[i] = new AutoFilterHandle((AutoFilter) afs.get(i)); - } - return af; - } - - /** - * Adds a new AutoFilter for the specified column (0-based) in this sheet
        - * returns a handle to the new AutoFilter - * - * @param int - * column - column number to add an AutoFilter to - * @return AutoFilterHandle - */ - public AutoFilterHandle addAutoFilter(int column) { - AutoFilter af = mysheet.addAutoFilter(column); - return new AutoFilterHandle(af); - } - - /** - * Removes all AutoFilters from this sheet
        - * As a consequence, all previously hidden rows are shown or unhidden - */ - public void removeAutoFilters() { - mysheet.removeAutoFilter(); - } - - /** - * Updates the Row filter (hidden status) for each row on the sheet by - * evaluating all AutoFilter conditions - *

        - * NOTE: This method must be called after Autofilter updates or additions - * in order to see the results of the AutoFilter(s) - *

        - * NOTE: this evaluation is NOT done automatically due to performance - * considerations, and is designed to be called after all additions and updating - * is completed (as evaluation may be time-consuming) - */ - public void evaluateAutoFilters() { - mysheet.evaluateAutoFilters(); - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - if (mysheet != null) - mysheet.close(); - addedrows.clear(); - addedrows = new CompatibleVector(); - mysheet = null; - mybook = null; - wbh = null; - dateFormats.clear(); - dateFormats = null; - } - - protected Boundsheet getBoundsheet() { - return this.mysheet; - } - - /** - * Imports the given CSV data into this worksheet. All rows in the input will be - * inserted sequentially before any rows which already exist in this worksheet. - * - * To change the value delimiter set the system property - * "{@code io.starter.OpenXLS.csvdelimiter}". - */ - public void readCSV(BufferedReader input) throws IOException { - int rws = 0; - String field_delimiter = System.getProperty("io.starter.OpenXLS.csvdelimiter", ","); - - String thisLine = ""; - while ((thisLine = input.readLine()) != null) { // while loop begins here - String[] vals = StringTool.getTokensUsingDelim(thisLine, field_delimiter); - Object[] data = new Object[vals.length]; - for (int t = 0; t < vals.length; t++) { - vals[t] = StringTool.strip(vals[t], '"'); - try { - int i = Integer.parseInt(vals[t]); - data[t] = Integer.valueOf(i); - - double d = Double.parseDouble(vals[t]); - data[t] = new Double(d); - - } catch (NumberFormatException ax) { - // it's a string! - data[t] = vals[t]; - } - } - - insertRow(rws++, data, true); - } - } + private Boundsheet mysheet; + private WorkBook mybook; + WorkBookHandle wbh; + private int DEBUGLEVEL = 0; + private Hashtable dateFormats = new Hashtable(); + private boolean cache = true; // 20080917 KSC: set var for caching, default to true [BugTracker 1862] + // public Map cellhandles = new HashMap(); + + public void addChart(byte[] serialchart, String name, short[] coords) { + mysheet.addChart(serialchart, name, coords); + } + + /** + * Get the first row on the Worksheet + * + * @return the Minimum Row Number on the Worksheet + */ + public int getFirstRow() { + return mysheet.getMinRow(); + } + + /** + * Get the first column on the Worksheet + * + * @return the Minimum Column Number on the Worksheet + */ + public int getFirstCol() { + return mysheet.getMinCol(); + } + + /** + * Get the last row on the Worksheet + * + * @return the Maximum Row Number on the Worksheet + */ + public int getLastRow() { + return mysheet.getMaxRow(); + } + + /** + * Get the last column on the Worksheet + * + * @return the Maximum Column Number on the Worksheet + */ + public int getLastCol() { + return mysheet.getMaxCol(); + } + + /** + * Sets whether the worksheet is protected. If protect is + * true, the worksheet will be protected and the password will be + * set to password. If it's false, the worksheet will + * be unprotected and the password will be removed. + * + * @param protect whether the worksheet should be protected + * @param password the password to set if protect is true. ignored when + * protect is false. + * @throws WorkBookException never. This used to be thrown when unprotecting if the password + * was incorrect. + */ + public void setProtected(boolean protect, String password) throws WorkBookException { + SheetProtectionManager protector = mysheet.getProtectionManager(); + + // we need to check if this password can be used to unprotect... + // otherwise it is totally insecure... + String oldpass = protector.getPassword(); + + Password pss = new Password(); + pss.setPassword(password); + String passcheck = pss.getPasswordHashString(); + + if (oldpass != null) { + if (!oldpass.equals(passcheck) && oldpass != "0000") { + throw new WorkBookException("Incorrect Password Attempt to Unprotect Worksheet.", + WorkBookException.SHEETPROTECT_INCORRECT_PASSWORD); + } + } + protector.setProtected(protect); + protector.setPassword(protect ? password : null); + } + + /** + * Sets whether the worksheet is protected. + * + * @param protect whether worksheet protection should be enabled + */ + public void setProtected(boolean protect) { + mysheet.getProtectionManager().setProtected(protect); + } + + /** + * Sets the password used to unlock the sheet when it is protected. + * + * @param password the clear text of the password to be applied or null to remove the + * existing password + */ + public void setProtectionPassword(String password) { + mysheet.getProtectionManager().setPassword(password); + } + + /** + * Sets the password used to unlock the sheet when it is protected. This method + * is useful in combination with {@link #getHashedProtectionPassword} to copy + * the password from one worksheet to another. + * + * @param hash the hash of the protection password to be applied or null to + * remove the existing password + */ + public void setProtectionPasswordHashed(String hash) { + mysheet.getProtectionManager().setPasswordHashed(hash); + } + + /** + * Gets the hash of the sheet protection password. This method returns the + * hashed password as stored in the file. It has been passed through a one-way + * hash function. It is therefore not possible to recover the actual password. + * You can, however, use {@link #setProtectionPasswordHashed} to apply the same + * password to another worksheet. + * + * @return the password hash or "0000" if the sheet doesn't have a password + */ + public String getHashedProtectionPassword() { + return mysheet.getProtectionManager().getPassword(); + } + + /** + * Returns whether the sheet is protected. Note that this is separate from + * whether the sheet has a protection password. It can be protected without a + * password or have a password but not be protected. + * + * @return whether protection is enabled for the sheet + */ + public boolean getProtected() { + return mysheet.getProtectionManager().getProtected(); + } + + /** + * Checks whether the given password matches the protection password. + * + * @param guess the password to be checked against the stored hash + * @return whether the given password matches the stored hash + */ + public boolean checkProtectionPassword(String guess) { + return mysheet.getProtectionManager().checkPassword(guess); + } + + /** + * Sets the worksheet enhanced protection option + * + * @param int protectionOption + * @see WorkBookHandle.iprot options + */ + public void setEnhancedProtection(int protectionOption, boolean set) { + mysheet.getProtectionManager().setProtected(protectionOption, set); + } + + /** + * returns true if the indicated Enhanced Protection Setting is turned on + * + * @param protectionOption + * @return boolean true if the indicated Enhanced Protection Setting is turned + * on + * @see WorkBookHandle.iprot options + */ + public boolean getEnhancedProtection(int protectionOption) { + return mysheet.getProtectionManager().getProtected(protectionOption); + } + + /** + * set whether this sheet is VERY hidden opening the file. + *

        + * VERY hidden means users will not be able to unhide the sheet without using VB + * code. + * + * @param boolean b hidden state + */ + public void setVeryHidden(boolean b) { + int h = 0; + if (b) + h = Boundsheet.VERY_HIDDEN; + mysheet.setHidden(h); + int t = mysheet.getSheetNum(); + try { // set the next sheet selected... + Boundsheet s2 = mybook.getWorkSheetByNumber(t + 1); + s2.setSelected(true); + } catch (WorkSheetNotFoundException e) { + } + } + + /** + * get whether this sheet is selected upon opening the file. + * + * @return boolean b selected state + */ + public boolean getSelected() { + return mysheet.selected(); + } + + /** + * get whether this sheet is hidden from the user opening the file. + * + * @return boolean b hidden state + */ + public boolean getHidden() { + return mysheet.getHidden(); + } + + /** + * return the 'veryhidden' state of the sheet + * + * @return + */ + public boolean getVeryHidden() { + return mysheet.getVeryHidden(); + } + + /** + * set whether this sheet is hidden from the user opening the file. + *

        + * if the sheet is selected, the API will set the first visible sheet to + * selected as you cannot have your selected sheet be hidden. + *

        + * to override this behavior, set your desired sheet to selected after calling + * this method. + * + * @param boolean b hidden state + */ + public void setHidden(boolean b) { + int h = 0; + if (b) + h = Boundsheet.HIDDEN; + mysheet.setHidden(h); + if (mysheet.getSheetNum() == 0) { + try { + Boundsheet s2 = mybook.getWorkSheetByNumber(mysheet.getSheetNum() + 1); + mybook.setFirstVisibleSheet(s2); + } catch (WorkSheetNotFoundException e) { + } + } + if (mysheet.selected()) { + try { // set the next sheet selected... + int x = 1; + Boundsheet s2 = mybook.getWorkSheetByNumber(mysheet.getSheetNum() + x); + while (s2.getHidden()) + s2 = mybook.getWorkSheetByNumber(mysheet.getSheetNum() + x++); + s2.setSelected(true); + } catch (WorkSheetNotFoundException e) { + } + } + } + + /** + * set this WorkSheet as the first visible tab on the left + */ + public void setFirstVisibleTab() { + mysheet.getWorkBook().setFirstVisibleSheet(mysheet); + } + + /** + * get the tab display order of this Worksheet + *

        + * this is a zero based index with zero representing the left-most WorkSheet + * tab. + * + * @return int idx the index of the sheet tab + */ + public int getTabIndex() { + return mysheet.getSheetNum(); + } + + /** + * set the tab display order of this Worksheet + *

        + * this is a zero based index with zero representing the left-most WorkSheet + * tab. + * + * @param int idx the new index of the sheet tab + */ + public void setTabIndex(int idx) { + mysheet.getWorkBook().changeWorkSheetOrder(mysheet, idx); + } + + /** + * set whether this sheet is selected upon opening the file. + * + * @param boolean b selected value + */ + public void setSelected(boolean b) { + mysheet.setSelected(b); + } + + /** + * returns the ColHandle for the column at index position the column index is + * zero based ie: column A = 0 + * + * @return ColHandle the Column + */ + public ColHandle getCol(int clnum) throws ColumnNotFoundException { + Colinfo ci = mysheet.getColInfo(clnum); + ColHandle mycol; + if (ci == null || !ci.isSingleCol()) { + try { + if (ci == null) { + ci = mysheet.createColinfo(clnum, clnum); + } else { + ci = mysheet.createColinfo(clnum, clnum, ci); + } + mycol = new ColHandle(ci, this); + + } catch (Exception e) { + throw new ColumnNotFoundException("Unable to getCol for col number " + clnum + " " + e.toString()); + } + } else { + mycol = new ColHandle(ci, this); // usual case + } + return mycol; + } + + /** + * adds the column (col1st, colLast) and returns the new ColHandle + * + * @param c1st + * @param clast + * @return + * @deprecated use addCol(int) + */ + @Deprecated + public ColHandle addCol(int c1st, int clast) { + Colinfo ci = mysheet.createColinfo(c1st, clast); + ColHandle mycol = new ColHandle(ci, this); + return mycol; + } + + /** + * adds the column (col1st, colLast) and returns the new ColHandle + * + * @param colNum, zero based number of the column + * @return ColHandle + */ + public ColHandle addCol(int colNum) { + Colinfo ci = mysheet.createColinfo(colNum, colNum); + ColHandle mycol = new ColHandle(ci, this); + return mycol; + } + + /** + * returns the Column at the named position + * + * @return ColHandle the Column + */ + public ColHandle getCol(String name) throws ColumnNotFoundException { + return this.getCol(ExcelTools.getIntVal(name)); + } + + /** + * returns all of the Columns in this WorkSheet + * + * @return ColHandle[] Columns + */ + public ColHandle[] getColumns() { + List columns = new CompatibleVector(); + + for (Colinfo c : mysheet.getColinfos()) { + try { + int start = c.getColFirst(); + int end = c.getColLast(); + for (int i = start; i <= end; i++) { + try { + columns.add(this.getCol(i)); + } catch (ColumnNotFoundException e) { + } + } + } catch (Exception ex) { + } + } + + return (ColHandle[]) columns.toArray(new ColHandle[columns.size()]); + } + + /** + * returns a List of Column names + * + * @return List column names + */ + public List getColNames() { + return mysheet.getColNames(); + } + + /** + * returns a List of Row numbers + * + * @return List of row numbers + */ + public List getRowNums() { + return mysheet.getRowNums(); + } + + /** + * returns the RowHandle for the row at index position + *

        + * the row index is zero based ie: Excel row 1 = 0 + * + * @param int row number to return + * @return RowHandle a Row on this WorkSheet + */ + public RowHandle getRow(int t) throws RowNotFoundException { + Row x = mysheet.getRowByNumber(t); + if (x == null) + throw new RowNotFoundException("Row " + t + " not found in :" + this.getSheetName()); + return new RowHandle(x, this); + } + + /** + * get an array of all RowHandles for this WorkSheet + * + * @return RowHandle[] all Rows on this WorkSheet + */ + public RowHandle[] getRows() { + Row[] rs = mysheet.getRows(); + RowHandle[] ret = new RowHandle[rs.length]; + for (int t = 0; t < rs.length; t++) { + ret[t] = new RowHandle(rs[t], this); + } + return ret; + } + + /** + * get an array of BIFFREC Rows + * + * @return RowHandle[] all Rows on this WorkSheet + */ + public Map getRowMap() { + return mysheet.getRowMap(); + } + + /** + * Returns whether a Cell exists in the WorkSheet. + * + * @param String the address of the Cell to check for + * @return boolean whether the Cell exists + */ + boolean hasCell(String addr) { + try { + this.getCell(addr); + return true; + } catch (CellNotFoundException e) { + return false; + } + } + + /** + * Returns the number of rows in this WorkSheet + * + * @return int Number of Rows on this WorkSheet + */ + public int getNumRows() { + return this.mysheet.getNumRows(); + } + + /** + * Returns the number of Columns in this WorkSheet + * + * @return int Number of Cols on this WorkSheet + */ + public int getNumCols() { + return this.mysheet.getNumCols(); + } + + /** + * Remove a Cell from this WorkSheet. + * + * @param CellHandle to remove + */ + public void removeCell(CellHandle celldel) { + mysheet.removeCell(celldel.getCell()); + } + + /** + * removes an Image from the Spreadsheet + *

        + *

        + * Jan 22, 2010 + * + * @param img + */ + public void removeImage(ImageHandle img) { + mysheet.removeImage(img); + img.remove(); + } + + /** + * Remove a Cell from this WorkSheet. + * + * @param String celladdr - the Address of the Cell to remove + */ + public void removeCell(String celladdr) { + mysheet.removeCell(celladdr.toUpperCase()); + } + + /** + * Remove a Row and all associated Cells from this WorkSheet. + * + * @param int + * rownum - the number of the row to remove not used public void + * removeRow(int rownum) throws RowNotFoundException{ + * mysheet.removeRow(rownum); } + */ + + /** + * Remove a Row and all associated Cells from this WorkSheet. Optionally shift + * all rows below target row up one. + * + * @param int rownum - the number of the row to remove + * @param boolean shiftrows - true will shift all lower rows up one. + */ + public void removeRow(int rownum, boolean shiftrows) throws RowNotFoundException { + if (shiftrows) + removeRow(rownum); + else + removeRow(rownum, WorkSheetHandle.ROW_DELETE_NO_REFERENCE_UPDATE); + } + + /** + * Remove a Row and all associated Cells from this WorkSheet. + * + * @param int rownum - the number of the row to remove uses default row deletion + * rules regarding updating references + */ + public void removeRow(int rownum) throws RowNotFoundException { + removeRow(rownum, WorkSheetHandle.ROW_DELETE); + } + + /** + * Remove all cells and formatting from a row within this WorkSheet. Has no + * other affect upon the workbook + * + * @param int rownum - the number of the row contents to remove + */ + public void removeRowContents(int rownum) throws RowNotFoundException { + mysheet.removeRowContents(rownum); + } + + /** + * Remove a Row and all associated Cells from this WorkSheet. + * + * @param int rownum - the number of the row to remove + * @param int flag - controls whether row deletions updates references as well + * ... + */ + public void removeRow(int rownum, int flag) throws RowNotFoundException { + + /* TODO: deal with merges! */ + mysheet.removeRows(rownum, 1, true); + + // Delete chart series IF SERIES ARE ROW-BASED -- do before updateReferences + List charts = this.mysheet.getCharts(); + for (int i = 0; i < charts.size(); i++) { + String sht = GenericPtg.qualifySheetname(this.getSheetName()); + Chart c = (Chart) charts.get(i); + HashMap seriesmap = c.getSeriesPtgs(); + Iterator ii = seriesmap.keySet().iterator(); + while (ii.hasNext()) { + io.starter.formats.XLS.charts.Series s = (io.starter.formats.XLS.charts.Series) ii.next(); + Ptg[] ptgs = (Ptg[]) seriesmap.get(s); + PtgRef pr; + String cursheet; + int[] rc; + for (int j = 0; j < ptgs.length; j++) { + try { + pr = (PtgRef) ptgs[j]; + cursheet = pr.getSheetName(); + rc = pr.getIntLocation(); + if (rc[1] != rc[3] && sht.equalsIgnoreCase(cursheet)) { // series are in rows, if existing + // series fall within deleted row + if ((rc[0]) == rownum - 1) { + c.removeSeries(j); + break; // got it + } + } else + break; // isn't row-based so split + } catch (Exception e) { + continue; // shouldn't happen! + } + } + } + // also shift chart up if necessary [BugTracker 2858] + int row = c.getRow0(); + // only move images whose top is >= rnum + int rnum = rownum + 1; + if (row > rnum) { + int h = c.getHeight(); + // move down 1 row + c.setRow(row - 1); + c.setHeight(h); + } + } + + if (flag != WorkSheetHandle.ROW_DELETE_NO_REFERENCE_UPDATE) + ReferenceTracker.updateReferences(rownum, -1, this.mysheet, true); + + // Adjust image row so that height remains constant + int rnum = rownum + 1; + ImageHandle[] images = this.getImages(); + for (int i = 0; i < images.length; i++) { + ImageHandle ih = images[i]; + int row = ih.getRow(); + // only move images whose top is >= rnum + if (row > rnum) { + short h = ih.getHeight(); + // move down 1 row + ih.setRow(row - 1); + ih.setHeight(h); + } + } + } + + /** + * Removes columns and all their associated cells from the sheet. This method + * does not shift the subsequent columns left, for that use {@link #removeCols}. + * + * @param first the zero-based index of the first column to be removed + * @param count the number of columns to remove. + */ + public void clearCols(int first, int count) { + this.removeCols(first, count, false); + } + + /** + * Removes columns from the sheet and shifts the following columns left. + * + * @param first the zero-based index of the first column to be removed + * @param count the number of columns to remove. + */ + public void removeCols(int first, int count) { + this.removeCols(first, count, true); + } + + private void removeCols(int first, int count, boolean shift) { + if (first < 0) + throw new IllegalArgumentException("column index must be zero or greater"); + if (count < 1) + throw new IllegalArgumentException("count must be at least one"); + mysheet.removeCols(first, count, shift); + } + + /** + * Removes a column and all associated cells from this sheet. This does not + * shift subsequent columns. + * + * @param colstr the name of the column to remove + * @deprecated Use {@link #clearCols} instead. + */ + @Deprecated + public void removeCol(String colstr) throws ColumnNotFoundException { + this.removeCols(ExcelTools.getIntVal(colstr), 1, false); + } + + /** + * Remove a column and all associated cells from this sheet. Optionally shift + * all subsequent columns left to fill the gap. + * + * @param colstr the name of the column to remove + * @param shiftcols whether to shift subsequent columns + * @deprecated Use {@link #removeCols} or {@link #clearCols} instead. + */ + @Deprecated + public void removeCol(String colstr, boolean shiftcols) throws ColumnNotFoundException { + this.removeCols(ExcelTools.getIntVal(colstr), 1, shiftcols); + } + + /** + * Returns the index of the Sheet. + * + * @return String Sheet Name + */ + public int getSheetNum() { + return mysheet.getSheetNum(); + } + + /** + * Returns all Named Range Handles scoped to this Worksheet. + *

        + * Note this will not include workbook scoped named ranges + * + * @return NameHandle[] all of the Named ranges that are scoped to the present + * worksheet + */ + public NameHandle[] getNamedRangesInScope() { + Name[] nand = mysheet.getSheetScopedNames(); + NameHandle[] nands = new NameHandle[nand.length]; + for (int x = 0; x < nand.length; x++) { + nands[x] = new NameHandle(nand[x], this.wbh); + } + return nands; + } + + /** + * Returns a Named Range Handle if it exists in the specified scope. + *

        + * This can be used to distinguish between multiple named ranges with the same + * name but differing scopes + * + * @return NameHandle a Named range in the Worksheet that exists in the scope + */ + public NameHandle getNamedRangeInScope(String rangename) throws CellNotFoundException { + Name nand = mysheet.getScopedName(rangename); + if (nand == null) + throw new CellNotFoundException(rangename); + return new NameHandle(nand, this.wbh); + } + + /** + * Returns the name of the Sheet. + * + * @return String Sheet Name + */ + public String getSheetName() { + return mysheet.getSheetName(); + } + + /** + * return the sheetname properly qualified or quoted used when the sheetname + * contains spaces, commas or parentheses + * + * @return + */ + public String getQualifiedSheetName() { + return GenericPtg.qualifySheetname(mysheet.getSheetName()); + } + + /** + * Returns the underlying low-level Boundsheet object. + * + * @return Boundsheet sheet + */ + protected Boundsheet getSheet() { + return mysheet; + } + + /** + * Returns the Serialized bytes for this WorkSheet. + *

        + * The output of this method can be used to insert a copy of this WorkSheet into + * another WorkBook using the WorkBookHandle.addWorkSheet(byte[] serialsheet, + * String NewSheetName) method. + * + * @return byte[] the WorkSheet's Serialized bytes + * @see WorkBookHandle.addWorkSheet(byte[] serialsheet, String NewSheetName) + */ + public byte[] getSerialBytes() { + mysheet.setLocalRecs(); + ObjectOutputStream obs = null; + byte[] b = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BufferedOutputStream bufo = new BufferedOutputStream(baos); + obs = new ObjectOutputStream(bufo); + obs.writeObject(mysheet); + bufo.flush(); + b = baos.toByteArray(); + } catch (Throwable e) { + Logger.logWarn("Serializing Sheet: " + this.toString() + " failed: " + e); + } + return b; + // return mysheet.getSheetBytes(); + } + + /** + * write this sheet as tabbed text output:
        + * All rows and all characters in each cell are saved. Columns of data are + * separated by tab characters, and each row of data ends in a carriage return. + * If a cell contains a comma, the cell contents are enclosed in double + * quotation marks. All formatting, graphics, objects, and other worksheet + * contents are lost. The euro symbol will be converted to a question mark. If + * cells display formulas instead of formula values, the formulas are saved as + * text. + */ + public void writeAsTabbedText(OutputStream dest) throws IOException { + this.mysheet.writeAsTabbedText(dest); + } + + /** + * Constructor which takes a WorkBook and sheetname as parameters. + * + * @param sht The name of the WorkSheet + * @param mybk The WorkBook + */ + protected WorkSheetHandle(Boundsheet sht, WorkBookHandle b) { + this.wbh = b; + this.mysheet = sht; + this.mybook = sht.getWorkBook(); + // 20080624 KSC: add flag for shift formula rules upon row insertion/deletion + String shiftRule = (String) System.getProperties().get("io.starter.OpenXLS.WorkSheetHandle.shiftInclusive"); + if (shiftRule != null && shiftRule.equalsIgnoreCase("true")) { + mysheet.setShiftRule(shiftRule.equalsIgnoreCase("true")); + } + // 20080917 KSC: set cache setting via system property [BugTracker 1862] + if (System.getProperty("io.starter.OpenXLS.cacheCellHandles") != null) + cache = Boolean.valueOf((System.getProperty("io.starter.OpenXLS.cacheCellHandles"))).booleanValue(); + } + + /** + * @return setting on whether to use cache or not + */ + public boolean getUseCache() { + return cache; + } + + /** + * Set the Object value of the Cell at the given address. + * + * @param String Cell Address (ie: "D14") + * @param Object new Cell Object value + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + public void setVal(String address, Object val) throws CellNotFoundException, CellTypeMismatchException { + CellHandle c = this.getCell(address); + c.setVal(val); + } + + /** + * Set the double value of the Cell at the given address + * + * @param String Cell Address (ie: "D14") + * @param double new Cell double value + * @param address + * @param d + * @throws io.starter.formats.XLS.CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + public void setVal(String address, double d) throws CellNotFoundException, CellTypeMismatchException { + CellHandle c = this.getCell(address); + c.setVal(d); + } + + /** + * Set the String value of the Cell at the given address + * + * @param String Cell Address (ie: "D14") + * @param String new Cell String value + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + public void setVal(String address, String s) throws CellNotFoundException, CellTypeMismatchException { + CellHandle c = this.getCell(address); + c.setVal(s); + } + + /** + * Set the name of the Worksheet. This method will change the name on the + * Worksheet's tab as displayed in the WorkBook, as well as all programmatic and + * internal references to the name. + *

        + * This change takes effect immediately, so all attempts to reference the + * Worksheet by its previous name will fail. + * + * @param String the new name for the Worksheet + */ + public void setSheetName(String name) { + wbh.sheethandles.remove(this.getSheetName()); // keep sheethandles (name->wsh) updated + mysheet.setSheetName(name); + wbh.sheethandles.put(name, this); + } + + /** + * Set the int value of the Cell at the given address + * + * @param String Cell Address (ie: "D14") + * @param int new Cell int value + * @throws io.starter.formats.XLS.CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + public void setVal(String address, int i) throws CellNotFoundException, CellTypeMismatchException { + CellHandle c = this.getCell(address); + c.setVal(i); + } + + /** + * Get the Object value of a Cell. + *

        + * Numeric Cell values will return as type Long, Integer, or Double. String Cell + * values will return as type String. + * + * @return the value of the Cell as an Object. + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + public Object getVal(String address) throws CellNotFoundException { + CellHandle c = this.getCell(address); + return c.getVal(); + } + + /** + * Insert a row of Objects into the worksheet. Automatically shifts all rows + * below the cell down one. + *

        + * Method takes an array of Objects to insert into the rows. + *

        + * Object array must match columns in number starting with column A. + *

        + * For emptly cells, put a null Object reference in your array. + *

        + * example: Object[] newCellHandles = { null, // col A "Hello", // col B + * Integer.valueOf(120), // col C "=sum(A1+B1)", // col D null, // col E null, + * // col F "World" // col G }; + *

        + * CellHandle ret = sheet.insertRow(newCellHandles, 1, true); if(ret !=null) + * Logger.log("It worked"); + * + * @param an array of Objects to insert into the new row + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + */ + public CellHandle[] insertRow(int row1, Object[] data) { + return insertRow(row1, data, true); + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + *

        + * Adding new cells to non-existent rows will automatically create new rows in + * the file, This method is only necessary to "move" existing cells by inserting + * empty rows. + * + * @param rownum the rownumber to insert + */ + public boolean insertRow(int rownum) { + return insertRow(rownum, (Row) null, ROW_INSERT, true); + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + *

        + * Adding new cells to non-existent rows will automatically create new rows in + * the file, This method is only necessary to "move" existing cells by inserting + * empty rows. + *

        + * Same as insertRow(rownum) except with addition of flag + * + * @param rownum the rownumber to insert + * @param flag row insertion rule + */ + public void insertRow(int rownum, int flag) { + insertRow(rownum, (Row) null, flag, true); + } + + private CompatibleVector addedrows = new CompatibleVector(); + private boolean range_init = true; + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + *

        + * This method differs from insertRow in that it can be used to repeatedly + * insert rows at the same row index. + *

        + * Adding new cells to non-existent rows will automatically create new rows in + * the file, + *

        + * After calling this method, setVal() can be used on the newly created cells to + * update with new values. + * + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + * @return whether the insert was successful + */ + public boolean insertRowAt(int rownum, boolean shiftrows) { + addedrows.remove(Integer.valueOf(rownum)); + return insertRow(rownum, mysheet.getRowByNumber(rownum), rownum, shiftrows); + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + *

        + * Adding new cells to non-existent rows will automatically create new rows in + * the file, + *

        + * After calling this method, setVal() can be used on the newly created cells to + * update with new values. + * + * @param rownum the rownumber to insert (NOTE: rownum is 0-based) + * @param whether to shift down existing Cells + * @return whether the insert was successful + */ + public boolean insertRow(int rownum, boolean shiftrows) { + Row myr = null; + try { + myr = mysheet.getRowByNumber(rownum); + } catch (Exception e) { + } + if (myr != null || shiftrows) { + return insertRow(rownum, myr, ROW_INSERT_MULTI, shiftrows); + } else { + // essentially a high performance row insert for the bottom of the workbook, + // used frequently in streaming workbook insertion + Row newRow = mysheet.insertRow(rownum, 0, ROW_INSERT_MULTI, shiftrows); + return true; + } + + } + + // insert handling flags + /** + * Insert row multiple times allowed, also copies formulas to inserted row + */ + public static final int ROW_INSERT_MULTI = 0; + /** + * Excel standard row insertion behavior + */ + public static final int ROW_INSERT = 3; + /** + * Insert row one time, multiple calls ignored + */ + public static final int ROW_INSERT_ONCE = 1; + /** + * Insert row but do not update any cell references affected by insert + */ + public static final int ROW_INSERT_NO_REFERENCE_UPDATE = 2; + + // 20080619 KSC: Add flag constants for Delete Row + public static final int ROW_DELETE = 1; + public static final int ROW_DELETE_NO_REFERENCE_UPDATE = 2; + + /** + * enhanced protection settings: Edit Object + */ + public final static short ALLOWOBJECTS = FeatHeadr.ALLOWOBJECTS; + /** + * enhanced protection settings: Edit scenario + */ + public static final short ALLOWSCENARIOS = FeatHeadr.ALLOWSCENARIOS; + /** + * enhanced protection settings: Format cells + */ + public static final short ALLOWFORMATCELLS = FeatHeadr.ALLOWFORMATCELLS; + /** + * enhanced protection settings: Format columns + */ + public static final short ALLOWFORMATCOLUMNS = FeatHeadr.ALLOWFORMATCOLUMNS; + /** + * enhanced protection settings: Format rows + */ + public static final short ALLOWFORMATROWS = FeatHeadr.ALLOWFORMATROWS; + /** + * enhanced protection settings: Insert columns + */ + public static final short ALLOWINSERTCOLUMNS = FeatHeadr.ALLOWINSERTCOLUMNS; + /** + * enhanced protection settings: Insert rows + */ + public static final short ALLOWINSERTROWS = FeatHeadr.ALLOWINSERTROWS; + /** + * enhanced protection settings: Insert hyperlinks + */ + public static final short ALLOWINSERTHYPERLINKS = FeatHeadr.ALLOWINSERTHYPERLINKS; + /** + * enhanced protection settings: Delete columns + */ + public static final short ALLOWDELETECOLUMNS = FeatHeadr.ALLOWDELETECOLUMNS; + /** + * enhanced protection settings: Delete rows + */ + public static final short ALLOWDELETEROWS = FeatHeadr.ALLOWDELETEROWS; + /** + * enhanced protection settings: Select locked cells + */ + public static final short ALLOWSELLOCKEDCELLS = FeatHeadr.ALLOWSELLOCKEDCELLS; + /** + * enhanced protection settings: Sort + */ + public static final short ALLOWSORT = FeatHeadr.ALLOWSORT; + /** + * enhanced protection settings: Use Autofilter + */ + public static final short ALLOWAUTOFILTER = FeatHeadr.ALLOWAUTOFILTER; + /** + * enhanced protection settings: Use PivotTable reports + */ + public static final short ALLOWPIVOTTABLES = FeatHeadr.ALLOWPIVOTTABLES; + /** + * enhanced protection settings: Select unlocked cells + */ + public static final short ALLOWSELUNLOCKEDCELLS = FeatHeadr.ALLOWSELUNLOCKEDCELLS; + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + *

        + * Adding new cells to non-existent rows will automatically create new rows in + * the file, + *

        + * After calling this method, setVal() can be used on the newly created cells to + * update with new values. + * + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + */ + public boolean insertRow(int rownum, RowHandle copyRow, int flag, boolean shiftrows) { + return this.insertRow(rownum, copyRow.myRow, flag, shiftrows); + } + + /** + * Insert a row of Objects into the worksheet. Shift all rows below the cell + * down one. + *

        + * Method takes an array of Objects to insert into the rows. + *

        + * Object array must match columns in number starting with column A. + *

        + * For emptly cells, put a null Object reference in your array. + *

        + * example: Object[] newCellHandles = { null, // col A "Hello", // col B + * Integer.valueOf(120), // col C "=sum(A1+B1)", // col D null, // col E null, + * // col F "World" // col G }; + *

        + * boolean okay = sheet.insertRow(newCellHandles, 1, true); + * if(okay)Logger.log("It worked"); + * + * @param an array of Objects to insert into the new row + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + */ + public CellHandle[] insertRow(int rownum, Object[] data, boolean shiftrows) { + CellHandle[] retc = new CellHandle[data.length]; + try { + insertRow(rownum, shiftrows); + for (int t = 0; t < data.length; t++) { + if (data[t] != null) + retc[t] = add(data[t], rownum, t); + } + } catch (Exception ex) { + throw new WorkBookException(ex.toString(), WorkBookException.RUNTIME_ERROR); + } + return retc; + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + *

        + * Adding new cells to non-existent rows will automatically create new rows in + * the file, + *

        + * After calling this method, setVal() can be used on the newly created cells to + * update with new values. + * + * @param rownum the rownumber to insert (0-based) + * @param copyrow the row to copy formats and formulas from + * @param flag determines handling tracking of inserted rows and only allow + * insertion once + * @param whether to shift down existing Cells + */ + private boolean insertRow(int rownum, Row copyRow, int flag, boolean shiftrows) { + int offset = 1; + return shiftRow(rownum, copyRow, flag, shiftrows, offset); + } + + /** + * replacement method for delete row that handles references better + * + * @param rownum + * @param flag + * @param shiftrows + * @return + */ + boolean deleteRow(int rownum, int flag, boolean shiftrows) { + int offset = -1; + return shiftRow(rownum, null, flag, shiftrows, offset); + } + + /** + * insert/delete agnostic row copy/insert/delete and formula shifter + *

        + * TODO: Better comments + * + * @param rownum + * @param copyRow + * @param flag + * @param shiftrows + * @param offset + * @return + */ + private boolean shiftRow(int rownum, Row copyRow, int flag, boolean shiftrows, int offset) { + + // If the copyrow is null, such as an insert row on an empty row, create that + // row, otherwise + // we end up using different logic for row insertion, which makes no sense. + if (copyRow == null) { + // insert a blank + this.add(null, "A" + rownum + 1); + copyRow = mysheet.getRowByNumber(rownum); + + } + // handle tracking of inserted rows -- if flag is false rows can be inserted + // multiple times at the same index + if (flag == WorkSheetHandle.ROW_INSERT_ONCE) { // not inserted + if (this.addedrows.contains(Integer.valueOf(rownum))) + return false; // can't add an existing row! + } + + // sheetster ui means insert + // 'on top of' row, shift down + if (offset == 0) + offset = 1; + + // shiftrefs BEFORE inserting new row + if (shiftrows && flag != WorkSheetHandle.ROW_INSERT_NO_REFERENCE_UPDATE) { + int refUpdateStart = rownum; + // OpenXLS default behavior is to update one row too high. If we are using + // ROW_INSERT, update per excel standard, + // see TestInsertRows.testUpdateFormulaSettings() for testing + if (flag == WorkSheetHandle.ROW_INSERT) + refUpdateStart++; + ReferenceTracker.updateReferences(refUpdateStart, offset, this.mysheet, true); // shift or expand/contract + // ALL affected references + // including named ranges + } + + int firstcol = copyRow.getColDimensions()[0]; + Row newRow = mysheet.insertRow(rownum, firstcol, flag, shiftrows); // shifts rows down and inserts a new row, + // also shifts shared formula refs (see note + // below) + + // *************************************************************************************************************************************************************/ + // Named Range, Formula and AI references: + // ALL references to the inserted row# (rownum) and rows beyond are shifted in + // ReferenceTracker.updateReferences. + // This method uses the ReferenceTracker collection for the specific sheet in + // question to iterate through the stored references, + // shifting them as the shifting rules allow. + // In addition, all formulas in the copyrow will be duplicated in the newly + // inserted row; + // These formula references are shifted in ReferenceTracker.adjustFormulaRefs + // (see below) + // The only references that are NOT shifted in the schema described above are + // SharedFormula references (specifically, PtgRefN & PtgAreaA), + // which are NOT contained within the ReferenceTracker collection. + // These references are shifted "by hand" in + // ReferenceTracker.moveSharedFormulas, called upon + // insertRow->->Boundsheet.shiftCellRow + // *************************************************************************************************************************************************************/ + + if (shiftrows) + addedrows.add(Integer.valueOf(rownum)); + + // TODO: Why so much logic in here, move this to Boundsheet? + + if ((shiftrows) && (copyRow != null)) { + // Handle shifting reference rules for the newrow only and it's copyrow (NOTE: + // copyrow may have been shifted via insertRow above although it's references + // have not yet been shifted) + int refMovementDiff = (copyRow.getRowNumber() - rownum); // number of rows to shift + int refMovementRow = rownum; // start row for shifting operation + if (refMovementDiff < 0) + refMovementRow += refMovementDiff; // since copyrow < rownum, shifting should be done BEFORE insert row + newRow.setRowHeight(copyRow.getRowHeight()); + + // Now iterate through all cells in the original row and copy formats and + // formulas + Object[] copyRowCells = copyRow.getCellArray(); + Map newmerges = new Hashtable(); + CellRange newmerge = null; + CellHandle newCellHandle = null; + Mulblank aMul = null; // KSC: Mulblank handling + short c = -1; // "" + String sheetname = GenericPtg.qualifySheetname(this.toString()); + for (int i = 0; i < copyRowCells.length; i++) { + BiffRec copyRowCell = (BiffRec) copyRowCells[i]; + if (copyRowCell.getOpcode() == XLSConstants.MULBLANK) { + if (copyRowCell == aMul) + c++; // ref next blank in range - nec. for ixfe (FormatId) see below + else { + aMul = (Mulblank) copyRowCell; + c = (short) aMul.getColFirst(); + } + aMul.setCurrentCell(c); + } + CellHandle copyCellHandle = new CellHandle(copyRowCell, this.wbh); + copyCellHandle.setWorkSheetHandle(this); + // this.cellhandles.put(copyCellHandle.getCellAddress(), copyCellHandle); + int colnum = copyCellHandle.getColNum(); + + // insert an empty copy of the cell OR, if it's a formula cell, copy formula and + // adjust it's cell references appropriate for new cell position + if (copyRowCell.getOpcode() == XLSRecord.FORMULA + && flag != WorkSheetHandle.ROW_INSERT_NO_REFERENCE_UPDATE + && flag != WorkSheetHandle.ROW_INSERT) { + try { + // copy copyrow's formula and then shift it's references relative to copycell's + // original row and references + newCellHandle = add(copyCellHandle.getFormulaHandle().getFormulaString(), rownum, colnum); + // streaming parser uses fast cell adds which returns null, populate here + if (newCellHandle == null) { + try { + newCellHandle = this.getCell(rownum, colnum); + } catch (CellNotFoundException e) { + // should be impossible } + } + } + // because the original formula has been shifted by one, unshift this sucker... + ReferenceTracker.adjustFormulaRefs(newCellHandle, refMovementRow, refMovementDiff * -1, true); + newCellHandle.getFormulaHandle().getFormulaRec().clearCachedValue(); + } catch (Exception e) { + if (DEBUGLEVEL > 0) + Logger.logWarn("WorkSheetHandle.shiftRow() could not adjust formula references in formula: " + + copyCellHandle + " while inserting new row." + e.toString()); + } + } else { + newCellHandle = add(null, rownum, colnum); + // streaming parser uses fast cell adds which returns null, populate here + if (newCellHandle == null) { + try { + newCellHandle = this.getCell(rownum, colnum); + } catch (CellNotFoundException e) { + // should be impossible } + } + } + } + newCellHandle.setFormatId(copyCellHandle.getFormatId()); + + // handle merged cells -- assemble the newmerges collection for below + CellRange oby = copyCellHandle.getMergedCellRange(); + if (oby != null) { // we have a merge + int[] fr = {rownum, oby.firstcellcol}; + int[] lr = {rownum, oby.lastcellcol}; + String newrng = sheetname + "!" + ExcelTools.formatLocation(fr) + ":" + + ExcelTools.formatLocation(lr); + newrng = GenericPtg.qualifySheetname(newrng); + if (DEBUGLEVEL > 10) + Logger.logInfo("WorksheetHandle.insertRow() created new Merge Range: " + newrng); + // check if we've already created... + if (newmerges.get(newrng) == null) { + newmerge = new CellRange(newrng, this.wbh, true); + if (DEBUGLEVEL > 10) + Logger.logInfo("WorksheetHandle.insertRow() created new Merge Range: " + newrng); + newmerges.put(newmerge.toString(), newmerge); + } + } + + } + // now update the new merge ranges... + Collection xl = newmerges.values(); + if (xl != null) { + Iterator itx = xl.iterator(); + while (itx.hasNext()) { + itx.next().mergeCells(true); + } + } + } + // Handle Image Movement + ImageHandle[] images = mysheet.getImages(); + if (images != null) { + for (int i = 0; i < images.length; i++) { + ImageHandle ih = images[i]; + int row = ih.getRow(); + // only move images whose top is >= copyRow + if (row >= rownum) { + // move down 1 row + short h = ih.getHeight(); + ih.setRow(row + 1); + ih.setHeight(h); + } + } + } + // Insert chart series IF SERIES ARE ROW-BASED + List charts = this.mysheet.getCharts(); + for (int i = 0; i < charts.size(); i++) { + Chart c = (Chart) charts.get(i); + ReferenceTracker.insertChartSeries(c, GenericPtg.qualifySheetname(this.getSheetName()), rownum); + // also shift charts down [BugTracker 2858] + int row = c.getRow0(); + // only move charts whose top is >= copyRow + if (row >= rownum) { + // move down 1 row + int h = c.getHeight(); + c.setRow(row + offset); + c.setHeight(h); + } + } + return true; + } + + /** + * returns an array of FormatHandles for the ConditionalFormats applied to this + * cell + * + * @return an array of FormatHandles, one for each of the Conditional Formatting + * rules + */ + public ConditionalFormatHandle[] getConditionalFormatHandles() { + ConditionalFormatHandle[] cfx = new ConditionalFormatHandle[this.mysheet.getConditionalFormats().size()]; + for (int i = 0; i < cfx.length; i++) { + Condfmt cfmt = (Condfmt) this.mysheet.getConditionalFormats().get(i); + cfx[i] = new ConditionalFormatHandle(cfmt, this); + } + return cfx; + } + + /** + * Returns the WorkBookHandle for this Sheet + * + * @return + */ + public WorkBookHandle getWorkBook() { + return this.wbh; + } + + /** + * Get a handle to all of the images in this worksheet + * + * @return + */ + public ImageHandle getImage(String name) throws ImageNotFoundException { + int idz = mysheet.getImageVect().indexOf(name); + if (idz > 0) + return (ImageHandle) mysheet.getImageVect().get(idz); + throw new ImageNotFoundException("Could not find " + name + " in " + this.getSheetName()); + } + + /** + * Get a handle to all of the images in this worksheet + * + * @return + */ + public ImageHandle[] getImages() { + return this.mysheet.getImages(); + } + + /** + * returns the actual amount of images contained in the sheet and is determined + * by imageMap + * + * @return + */ + public int getNumImages() { + return this.mysheet.imageMap.size(); + } + + /** + * write out all of the images in the Sheet to a directory + * + * @param imageoutput directory + */ + public void extractImagesToDirectory(String outdir) { + ImageHandle[] extracted = getImages(); + + // extract and output images + for (int tx = 0; tx < extracted.length; tx++) { + String n = extracted[tx].getName(); + if (n.equals("")) + n = "image" + extracted[tx].getMsodrawing().getImageIndex(); + String imgname = n + "." + extracted[tx].getType(); + if (DEBUGLEVEL > 0) + Logger.logInfo("Successfully extracted: " + outdir + imgname); + try { + FileOutputStream outimg = new FileOutputStream(outdir + imgname); + extracted[tx].write(outimg); + outimg.flush(); + outimg.close(); + } catch (Exception ex) { + Logger.logErr("Could not extract images from: " + this); + } + } + } + + /** + * retrieves all charts for this sheet and writes them (in SVG form) to outpdir + *
        + * Filename is in form of: _Chart<#>.svg + * + * @param outdir String output folder + */ + public void extractChartToDirectory(String outdir) { + ArrayList charts = (ArrayList) this.mysheet.getCharts(); + String sheetname = this.getSheetName(); + for (int i = 0; i < charts.size(); i++) { + ChartHandle ch = new ChartHandle((Chart) charts.get(i), this.getWorkBook()); + String fname = sheetname + "_Chart" + ch.getId() + ".svg"; + try { + FileOutputStream chartout = new FileOutputStream(outdir + fname); + chartout.write(ch.getSVG(1.0).getBytes()); // scaled as necessary in XSL + } catch (Exception ex) { + Logger.logErr("extractChartToDirectory: Could not extract charts from: " + this + ":" + ex.toString()); + } + + } + } + + /** + * insert an image into this worksheet + * + * @param im -- the ImageHandle to insert + * @see ImageHandle + */ + public void insertImage(ImageHandle im) { + this.mysheet.insertImage(im); + } + + /** + * Inserts empty columns and shifts the following columns to the right. This + * method is used to shift existing columns right to make room for a new column. + * To create a column in the file so its size or style can be set just add a + * blank cell to its first row with {@link #add}. + * + * @param first the zero-based index of the first column to insert + * @param count the number of columns to insert + */ + public void insertCols(int first, int count) { + if (first < 0) + throw new IllegalArgumentException("column index must be zero or greater"); + if (count < 1) + throw new IllegalArgumentException("count must be at least one"); + mysheet.insertCols(first, count); + } + + /** + * Inserts an empty column and shifts the following columns to the right. + * + * @param colnum the zero-based index of the column to be inserted + * @deprecated Use {@link #insertCols} instead. + */ + @Deprecated + public void insertCol(int colnum) { + this.insertCols(colnum, 1); + } + + /** + * Inserts an empty column and shifts the following columns to the right. + * + * @param colnum the address of the column to be inserted + * @deprecated Use {@link #insertCols} instead. + */ + @Deprecated + public void insertCol(String colnum) { + this.insertCols(ExcelTools.getIntVal(colnum), 1); + } + + /** + * When adding a new Cell to the sheet, OpenXLS can automatically copy the + * formatting from the Cell directly above the inserted Cell. + *

        + * ie: if set to true, newly added Cell D19 would take its formatting from Cell + * D18. + *

        + * Default is false + *

        + *

        + *

        + * boolean copy the formats from the prior Cell + */ + public void setCopyFormatsFromPriorWhenAdding(boolean f) { + mysheet.setCopyPriorCellFormats(f); + } + + /** + * Add a Cell with the specified value to a WorkSheet. + *

        + * This method determines the Cell type based on type-compatibility of the + * value. + *

        + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + */ + public CellHandle add(Object obj, int row, int col) { + return add(obj, row, col, this.getWorkBook().getWorkBook().getDefaultIxfe()); + } + + /** + * Add a Cell with the specified value to a WorkSheet. + *

        + * This method determines the Cell type based on type-compatibility of the + * value. + *

        + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + *

        + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + */ + public CellHandle[] addValidated(Object obj, int row, int col) throws ValidationException { + return addValidated(obj, row, col, this.getWorkBook().getWorkBook().getDefaultIxfe()); + } + + /** + * Add a Cell with the specified value to a WorkSheet. + *

        + * This method determines the Cell type based on type-compatibility of the + * value. + *

        + * Further, this method allows passing in a format id + *

        + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + *

        + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + * @param int the format id to apply to this cell + */ + public CellHandle[] addValidated(Object obj, int row, int col, int formatId) throws ValidationException { + int[] rc = {row, col}; + ValidationHandle vh = this.getValidationHandle(ExcelTools.formatLocation(rc)); + if (vh != null) { + vh.isValid(obj); + } + return this.addValidated(obj, ExcelTools.formatLocation(rc)); + } + + /** + * Add a Cell with the specified value to a WorkSheet. + *

        + * This method determines the Cell type based on type-compatibility of the + * value. + *

        + * Further, this method allows passing in a format id + *

        + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + * @param int the format id to apply to this cell + */ + public CellHandle add(Object obj, int row, int col, int formatId) { + int[] rc = {row, col}; + + if (obj instanceof java.util.Date) { + String address = ExcelTools.formatLocation(rc); + this.add((java.util.Date) obj, address, null); + } else { + BiffRec reca = mysheet.addValue(obj, rc, formatId); + + if (DEBUGLEVEL > 1) + if (reca != null) + Logger.logInfo("WorkSheetHandle.add() " + reca.toString() + " Successfully Added."); + else + return null; + } + + if (!mysheet.fastCellAdds) { + try { + CellHandle c = this.getCell(row, col); + if (this.wbh.getFormulaCalculationMode() != wbh.CALCULATE_EXPLICIT) + c.clearAffectedCells(); // blow out cache + return c; + } catch (CellNotFoundException e) { + Logger.logInfo("Adding Cell to row failed row:" + row + " col: " + col + " failed."); + return null; + } + } else { + return null; + } + } + + /** + * Fast-adds a Cell with the specified value to a WorkSheet. + *

        + * This method determines the Cell type based on type-compatibility of the + * value. + *

        + * Further, this method allows passing in a format id + *

        + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + * @param int the format id to apply to this cell + */ + public void fastAdd(Object obj, int row, int col, int formatId) { + int[] rc = {row, col}; + + // use default format + if (formatId == -1) + formatId = 0; + + if (obj instanceof java.util.Date) { + String address = ExcelTools.formatLocation(rc); + this.add((java.util.Date) obj, address, null); + } else { + BiffRec reca = mysheet.addValue(obj, rc, formatId); + if (this.wbh.getFormulaCalculationMode() != wbh.CALCULATE_EXPLICIT) { + ReferenceTracker rt = this.wbh.getWorkBook().getRefTracker(); + rt.clearAffectedFormulaCells(reca); + } + if (DEBUGLEVEL > 1) + if (reca != null) + Logger.logInfo("WorkSheetHandle.add() " + reca.toString() + " Successfully Added."); + } + } + + /** + * Toggle fast cell add mode. + *

        + * Set to true to turn off checking for existing cells, conditional formats and + * merged ranges in order to accelerate adding new cells + * + * @param fastadds whether to disable checking for existing cells and + */ + public void setFastCellAdds(boolean fastadds) { + this.mysheet.setFastCellAdds(fastadds); + } + + /** + * Get the current fast add cell mode for this worksheet + */ + public boolean getFastCellAdds() { + return this.mysheet.fastCellAdds; + } + + /** + * Add a Cell with the specified value to a WorkSheet, optionally attempting to + * convert numeric values to appropriate number cells with appropriate number + * formatting applied. + *

        + * This would allow a value entered such as "$1.00" to be converted to a numeric + * 1.00d value with a $0.00 format pattern applied. + *

        + * Note that there is overhead to this method, as the value added needs to be + * parsed, for higher performance do not use the autodetectNumberAndPattern = + * true setting, and instead pass numeric values and format patterns. + *

        + * If the value is non numeric, then it is simply added. + * + * @param obj the value of the new Cell + * @param address the address of the new Cell + * @param autoDetectValue whether to attempt to store as a number with a format pattern. + */ + public CellHandle add(Object obj, String address, boolean autoDetectValue) { + if (obj == null) + mysheet.addValue(obj, address); // to add a blank cell + else if (obj instanceof Date) + this.add((Date) obj, address, null); + else + mysheet.addValue(obj, address, autoDetectValue);// + + // fast-adds optimzation -- do not return a CellHandle + if (this.mysheet.fastCellAdds) + return null; + + try { + return this.getCell(address); + } catch (CellNotFoundException e) { + Logger.logInfo("Adding Cell: " + address + " failed"); + return null; + } + } + + /** + * Add a Cell with the specified value to a WorkSheet. + *

        + * This method determines the Cell type based on type-compatibility of the + * value. + *

        + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param address the address of the new Cell + */ + public CellHandle add(Object obj, String address) { + if (obj == null) + mysheet.addValue(obj, address); // to add a blank cell + else if (obj instanceof Date) + this.add((Date) obj, address, null); + else + mysheet.addValue(obj, address);// + + // fast-adds optimzation -- do not return a CellHandle + if (!mysheet.fastCellAdds) { + try { + CellHandle c = this.getCell(address); + if (this.wbh.getFormulaCalculationMode() != wbh.CALCULATE_EXPLICIT) + c.clearAffectedCells(); // blow out cache + return c; + } catch (CellNotFoundException e) { + Logger.logInfo("Adding Cell: " + address + " failed"); + return null; + } + } else { + return null; + } + } + + /** + * Add a Cell with the specified value to a WorkSheet. + *

        + * This method determines the Cell type based on type-compatibility of the + * value. + *

        + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + *

        + *

        + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param obj the value of the new Cell + * @param address the address of the new Cell + */ + public CellHandle[] addValidated(Object obj, String address) throws ValidationException { + ValidationHandle vh = this.getValidationHandle(address); + if (vh != null) { + vh.isValid(obj); + } + CellHandle ch = this.add(obj, address); + List cxrs = ch.calculateAffectedCellsOnSheet(); + + // return the cellhandles + CellHandle[] cxrx = new CellHandle[cxrs.size() + 1]; + cxrx[0] = ch; + for (int t = 1; t < cxrx.length; t++) { + cxrx[t] = (CellHandle) cxrs.get(t - 1); + cxrx[t].setWorkSheetHandle(this); + // this.cellhandles.put(cxrx[t].getCellAddress(), cxrx[t]); + } + + return cxrx; + } + + /** + * Add a java.sql.Timestamp Cell to a WorkSheet. + *

        + * Will create a default format of: + *

        + * "m/d/yyyy h:mm:ss" + *

        + * if none is specified. + * + * @param dt the value of the new java.sql.Timestamp Cell + * @param address the address of the new java.sql.Date Cell + * @param formatting pattern the address of the new java.sql.Date Cell + */ + public CellHandle add(Timestamp dt, String address, String fmt) { + if (fmt == null) + fmt = "m/d/yyyy h:mm:ss"; + Date dx = new Date(dt.getTime()); + return this.add(dx, address, fmt); + } + + /** + * Add a java.sql.Timestamp Cell to a WorkSheet. + *

        + * Will create a default format of: + *

        + * "m/d/yyyy h:mm:ss" + *

        + * if none is specified. + *

        + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param dt the value of the new java.sql.Timestamp Cell + * @param address the address of the new java.sql.Date Cell + * @param formatting pattern the address of the new java.sql.Date Cell + */ + public CellHandle[] addValidated(Timestamp dt, String address, String fmt) throws ValidationException { + if (fmt == null) + fmt = "m/d/yyyy h:mm:ss"; + Date dx = new Date(dt.getTime()); + return this.addValidated(dx, address, fmt); + } + + /** + * Add a java.sql.Date Cell to a WorkSheet. + *

        + * You must specify a formatting pattern for the new date, or null for the + * default ("m/d/yy h:mm".) + *

        + * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" + * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" + * + * @param dt the value of the new java.sql.Date Cell + * @param row to add the date + * @param col to add the date + * @param formatting pattern the address of the new java.sql.Date Cell + */ + public CellHandle add(java.util.Date dt, String address, String fmt) { + int[] rc = ExcelTools.getRowColFromString(address); + return add(dt, rc[0], rc[1], fmt); + } + + /** + * Add a java.sql.Date Cell to a WorkSheet. + *

        + * You must specify a formatting pattern for the new date, or null for the + * default ("m/d/yy h:mm".) + *

        + * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" + * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" + * + * @param dt the value of the new java.sql.Date Cell + * @param row to add the date + * @param col to add the date + * @param formatting pattern the address of the new java.sql.Date Cell + */ + public CellHandle[] addValidated(java.util.Date dt, String address, String fmt) throws ValidationException { + int[] rc = ExcelTools.getRowColFromString(address); + return addValidated(dt, rc[0], rc[1], fmt); + } + + /** + * Add a java.sql.Date Cell to a WorkSheet. + *

        + * You must specify a formatting pattern for the new date, or null for the + * default ("m/d/yy h:mm".) + *

        + * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" + * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" + * + * @param dt the value of the new java.sql.Date Cell + * @param address the address of the new java.sql.Date Cell + * @param formatting pattern the address of the new java.sql.Date Cell + */ + public CellHandle add(java.util.Date dt, int row, int col, String fmt) { + double x = DateConverter.getXLSDateVal(dt, this.mybook.getDateFormat()); + CellHandle thisCell = this.add(new Double(x), row, col); + + // first handle fast adds + if (thisCell == null && this.mysheet.fastCellAdds) { + try { + thisCell = getCell(row, col); + } catch (CellNotFoundException exp) { + Logger.logWarn("adding date to WorkSheet failed: " + this.getSheetName() + ":" + row + ":" + col); + return null; + } + } + + // 20060419 KSC: Use format from cell, if any + if (fmt == null) { + fmt = thisCell.getFormatPattern(); + if (fmt == null || fmt.equals("General")) + fmt = "m/d/yy h:mm"; + } + if (dateFormats.get(fmt) == null) { + FormatHandle fh = thisCell.getFormatHandle(); + fh.setFormatPattern(fmt); + dateFormats.put(fmt, Integer.valueOf(thisCell.getFormatId())); + } else { + Integer in = dateFormats.get(fmt); + thisCell.setFormatId(in.intValue()); + } + // Logger.logInfo("Date added: " + thisCell.getFormattedStringVal()); + return thisCell; + } + + /** + * Add a java.sql.Date Cell to a WorkSheet. + *

        + * You must specify a formatting pattern for the new date, or null for the + * default ("m/d/yy h:mm".) + *

        + * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" + * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" + *

        + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param dt the value of the new java.sql.Date Cell + * @param address the address of the new java.sql.Date Cell + * @param formatting pattern the address of the new java.sql.Date Cell + */ + public CellHandle[] addValidated(java.util.Date dt, int row, int col, String fmt) throws ValidationException { + int[] rc = {row, col}; + ValidationHandle vh = this.getValidationHandle(ExcelTools.formatLocation(rc)); + if (vh != null) { + vh.isValid(dt); + } + CellHandle ch = this.add(dt, row, col, fmt); + List cxrs = ch.calculateAffectedCellsOnSheet(); + + // FIXME: Use List.toArray instead of for loop + // return the cellhandles + CellHandle[] cxrx = new CellHandle[cxrs.size() + 1]; + cxrx[0] = ch; + for (int t = 1; t < cxrx.length; t++) { + cxrx[t] = (CellHandle) cxrs.get(t - 1); + } + + return cxrx; + + } + + /** + * Remove this WorkSheet from the WorkBook + *

        + * NOTE: will throw a WorkBookException if the last sheet is removed. This + * results in an invalid output file. + */ + public void remove() { + mybook.removeWorkSheet(this.mysheet); + wbh.sheethandles.remove(this.getSheetName()); + } + + /** + * Create a CellRange object from an OpenXLS string range passed in such as + * "A1:F6" + * + * @param rangeName "A1:F6" + * @return + * @throws CellNotFoundException if the range cannot be created + */ + public CellRange getCellRange(String rangeName) throws CellNotFoundException { + CellRange cr = new CellRange(this.getSheetName() + "!" + rangeName, this.getWorkBook()); + return cr; + } + + /** + * Returns all CellHandles defined on this WorkSheet. + * + * @return CellHandle[] - the array of Cells in the Sheet + */ + public CellHandle[] getCells() { + BiffRec[] cells = mysheet.getCells(); + CellHandle[] retval = new CellHandle[cells.length]; + Mulblank aMul = null; + short c = -1; + for (int i = 0; i < retval.length; i++) { + try { + if (cells[i].getOpcode() != XLSConstants.MULBLANK) { + retval[i] = getCell(cells[i].getRowNumber(), cells[i].getColNumber()); + } else { // Handle MULBLANKS proper column number + if (cells[i] == aMul) { + c++; + } else { + aMul = (Mulblank) cells[i]; + c = (short) aMul.getColFirst(); + } + retval[i] = getCell(cells[i].getRowNumber(), c); + } + } catch (CellNotFoundException cnfe) { + // try harder + retval[i] = new CellHandle(cells[i], this.wbh); + retval[i].setWorkSheetHandle(this); + if (cells[i].getOpcode() == XLSConstants.MULBLANK) { + // handle Mulblanks: ref a range of cells; to get correct cell address, + // traverse thru range and set cellhandle ref to correct column + if (cells[i] == aMul) { + c++; + } else { + aMul = (Mulblank) cells[i]; + c = (short) aMul.getColFirst(); + } + retval[i].setBlankRef(c); // for Mulblank use only -sets correct column reference for multiple blank + // cells ... + } + } + } + return retval; + } + + /** + * Returns a FormulaHandle for working with the ranges of a formula on a + * WorkSheet. + * + * @param addr the address of the Cell + * @throws FormulaNotFoundException is thrown if there is no existing formula at the specified + * address. + */ + public FormulaHandle getFormula(String addr) throws FormulaNotFoundException, CellNotFoundException { + CellHandle c = this.getCell(addr); + return c.getFormulaHandle(); + } + + /** + * Returns a CellHandle for working with the value of a Cell on a WorkSheet. + * + * @param addr the address of the Cell + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + public CellHandle getCell(String addr) throws CellNotFoundException { + CellHandle ret = null; // + + BiffRec c = mysheet.getCell(addr.toUpperCase()); + if (c == null) { + String sn = ""; + try { + sn = this.getSheetName(); + sn += "!"; + } catch (Exception e) { + } + if (addr == null) + addr = "undefined cell address"; + throw new CellNotFoundException(sn + addr); + } + ret = new CellHandle(c, this.wbh); + ret.setWorkSheetHandle(this); + return ret; + } + + /** + * Returns a CellHandle for working with the value of a Cell on a WorkSheet. + *

        + * returns a new CellHandle with each call + *

        + * use caching method getCell(int row, int col, boolean cache) to control + * caching of CellHandles. + * + * @param int Row the integer row of the Cell + * @param int Col the integer col of the Cell + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + public CellHandle getCell(int row, int col) throws CellNotFoundException { + return getCell(row, col, false); + } + + /** + * Returns a CellHandle for working with the value of a Cell on a WorkSheet. + * + * @param int Row the integer row of the Cell + * @param int Col the integer col of the Cell + * @param boolean whether to cache or return a new CellHandle each call + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + public CellHandle getCell(int row, int col, boolean cache) throws CellNotFoundException { + CellHandle ret = null; + if (cache) { + int[] rc = {row, col}; + String address = ExcelTools.formatLocation(rc); + // ret = (CellHandle)cellhandles.get(address); + if (ret != null) // caching! + return ret; + else { + ret = new CellHandle(this.mysheet.getCell(row, col), this.wbh); + ret.setWorkSheetHandle(this); + // cellhandles.put(address,ret); + return ret; + } + } + ret = new CellHandle(this.mysheet.getCell(row, col), this.wbh); + ret.setWorkSheetHandle(this); + return ret; + } + + /** + * Move a cell on this WorkSheet. + * + * @param CellHandle c - the cell to be moved + * @param String celladdr - the destination address of the cell + */ + public void moveCell(CellHandle c, String addr) throws CellPositionConflictException { + this.mysheet.moveCell(c.getCellAddress(), addr); + // c.moveTo(addr); < redundant call to above. 070104 -jm + } + + /** + * Get the text for the Footer printed at the bottom of the Worksheet + * + * @return String footer text + */ + public String getFooterText() { + return mysheet.getFooter().getFooterText(); + } + + /** + * Get the text for the Header printed at the top of the Worksheet + * + * @return String header text + */ + public String getHeaderText() { + return mysheet.getHeader().getHeaderText(); + } + + /** + * Get the print area set for this WorkSheetHandle. + *

        + * If no print area is set return null; + */ + public String getPrintArea() { + return mysheet.getPrintArea(); + } + + /** + * Get the Print Titles set for this WorkSheetHandle. + *

        + * If no Print Titles are set, this returns null; + */ + public String getPrintTitles() { + return mysheet.getPrintTitles(); + } + + /** + * Get the printer settings handle for this WorkSheetHandle. + */ + public PrinterSettingsHandle getPrinterSettings() { + return mysheet.getPrinterSetupHandle(); + } + + /** + * Sets the print area for the worksheet + *

        + *

        + * sets the printarea as a CellRange + * + * @param printarea + */ + public void setPrintArea(CellRange printarea) { + mysheet.setPrintArea(printarea.getRange()); + } + + /** + * Set the text for the Header printed at the top of the Worksheet + * + * @param String header text + */ + public void setHeaderText(String t) { + mysheet.getHeader().setHeaderText(t); + } + + /** + * Set the text for the Footer printed at the bottom of the Worksheet + * + * @param String footer text + */ + public void setFooterText(String t) { + mysheet.getFooter().setFooterText(t); + } + + /** + * Set the default column width of the worksheet + *

        + *
        + *

        + * This setting is roughly the width of the character '0' The default width of a + * column is 8. + */ + public void setDefaultColWidth(int t) { + mysheet.setDefaultColumnWidth(t); + } + + public int getDefaultColWidth() { + return (int) mysheet.getDefaultColumnWidth(); + } + + /** + * Returns the name of this Sheet. + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return mysheet.toString(); + } + + /** + * FOR internal Use Only! + * + * @return Returns the low-level sheet record. + */ + public Boundsheet getMysheet() { + return mysheet; + } + + /** + * Calculates all formulas that reference the cell address passed in. + *

        + * Please note that these cells have already been calculated, so in order to get + * their values without re-calculating them Extentech suggests setting the book + * level non-calculation flag, ie + * book.setFormulaCalculationMode(WorkBookHandle.CALCULATE_EXPLICIT) or + * FormulaHandle.getCachedVal() + * + * @return List of of calculated cells + */ + public List calculateAffectedCells(String CellAddress) { + CellHandle c = null; + try { + c = this.getCell(CellAddress); + } catch (CellNotFoundException e) { + return null; + } + return c.calculateAffectedCells(); + } + + /** + * Set whether to show calculated formula results in the output sheet. + * + * @return boolean whether to show calculated formula results + */ + public boolean getShowFormulaResults() { + return this.mysheet.getWindow2().getShowFormulaResults(); + } + + public void setShowFormulaResults(boolean b) { + this.mysheet.getWindow2().setShowFormulaResults(b); + } + + /** + * Get whether to show gridlines in the output sheet. + * + * @param boolean whether to show gridlines + */ + public boolean getShowGridlines() { + return this.mysheet.getWindow2().getShowGridlines(); + } + + /** + * Set whether to show gridlines in the output sheet. + * + * @return boolean whether to show gridlines + */ + public void setShowGridlines(boolean b) { + this.mysheet.getWindow2().setShowGridlines(b); + } + + /** + * Get whether to show sheet headers in the output sheet. + * + * @return boolean whether to show sheet headers + */ + public boolean getShowSheetHeaders() { + return this.mysheet.getWindow2().getShowSheetHeaders(); + } + + /** + * Set whether to show sheet headers in the output sheet. + * + * @param boolean whether to show sheet headers + */ + public void setShowSheetHeaders(boolean b) { + this.mysheet.getWindow2().setShowSheetHeaders(b); + } + + /** + * Get whether to show zero values in the output sheet. + * + * @return boolean whether to show zero values + */ + public boolean getShowZeroValues() { + return this.mysheet.getWindow2().getShowZeroValues(); + } + + /** + * Set whether to show zero values in the output sheet. + * + * @return boolean whether to show zero values + */ + public void setShowZeroValues(boolean b) { + this.mysheet.getWindow2().setShowZeroValues(b); + } + + /** + * Get whether to show outline symbols in the output sheet. + * + * @return boolean whether to outline symbols + */ + public boolean getShowOutlineSymbols() { + return this.mysheet.getWindow2().getShowOutlineSymbols(); + } + + /** + * Set whether to show outline symbols in the output sheet. + * + * @param boolean whether to show outline symbols + */ + public void setShowOutlineSymbols(boolean b) { + this.mysheet.getWindow2().setShowOutlineSymbols(b); + } + + /** + * Get whether to show normal view or page break preview view in the output + * sheet. + * + * @return boolean whether to show normal view or page break preview view + */ + public boolean getShowInNormalView() { + return this.mysheet.getWindow2().getShowInNormalView(); + } + + /** + * Set whether to show normal view or page break preview view in the output + * sheet. + * + * @param boolean whether to show normal view or page break preview view + */ + public void setShowInNormalView(boolean b) { + this.mysheet.getWindow2().setShowInNormalView(!b); // opposite of expected behavior + } + + /** + * Get whether there are freeze panes in the output sheet. + * + * @return boolean whether there are freeze panes + */ + public boolean hasFrozenPanes() { + return this.mysheet.getWindow2().getFreezePanes(); + } + + /** + * Set whether there are freeze panes in the output sheet. + * + * @param boolean whether there are freeze panes + */ + public void setHasFrozenPanes(boolean b) { + this.mysheet.getWindow2().setFreezePanes(b); + if (!b && this.mysheet.getPane() != null) { + this.mysheet.removePane(); // remove pane rec if unfreezing ... can also convert to plain splits, but a bit + // more complicated ... + } + } + + /** + * sets the zoom for the sheet + * + * @param the zoom as a float percentage (.25 = 25%) + */ + public void setZoom(float zm) { + this.mysheet.getScl().setZoom(zm); + } + + /** + * if this sheet has freeze panes, return the address of the top left cell + * otherwise, return null + * + * @return + */ + public String getTopLeftCell() { + if (this.mysheet.getPane() != null) { + return this.mysheet.getPane().getTopLeftCell(); + } + return null; + } + + /** + * gets the zoom for the sheet + * + * @return the zoom as a float percentage (.25 = 25%) + */ + public float getZoom() { + return this.mysheet.getScl().getZoom(); + } + + /** + * freezes the rows starting at the specified row and creating a scrollable + * sheet below this row + * + * @param row the row to start the freeze + */ + public void freezeRow(int row) { + if (this.mysheet.getPane() == null) + this.mysheet.setPane(null); // will add new + this.mysheet.getPane().setFrozenRow(row); + } + + /** + * freezes the cols starting at the specified column and creating a scrollable + * sheet to the right + * + * @param col the col to start the freeze + */ + public void freezeCol(int col) { + if (this.mysheet.getPane() == null) + this.mysheet.setPane(null); // will add new + this.mysheet.getPane().setFrozenColumn(col); + } + + /** + * splits the worksheet at column col for nCols + *

        + * Note: unfreezes panes if frozen + * + * @param col col start col to split + * @param splitpos position of the horizontal split + */ + public void splitCol(int col, int splitpos) { + if (this.mysheet.getPane() == null) + this.mysheet.setPane(null); // will add new + this.mysheet.getPane().setSplitColumn(col, splitpos); + } + + /** + * splits the worksheet at row for nRows + *

        + * Note: unfreezes panes if frozen + * + * @param row start row to split + * @param splitpos position of the vertical split + */ + public void splitRow(int row, int splitpos) { + if (this.mysheet.getPane() == null) + this.mysheet.setPane(null); // will add new + this.mysheet.getPane().setSplitRow(row, splitpos); + } + + /** + * Gets the row number (0 based) that the sheet split is located on. If the + * sheet is not split returns -1 + * + * @return + */ + public int getSplitRowLocation() { + if (this.mysheet.getPane() == null) + return -1; + return this.mysheet.getPane().getVisibleRow(); + } + + /** + * gets the column number (0-based)that the sheet split is locaated on; if the + * sheet is not split, returns -1 + * + * @return 0-based index of split column, if any + */ + public int getSplitColLocation() { + if (this.mysheet.getPane() == null) + return -1; + return this.mysheet.getPane().getVisibleCol(); + } + + /** + * Gets the twips split location returns -1 + * + * @return + */ + public int getSplitLocation() { + if (this.mysheet.getPane() == null) + return -1; + return this.mysheet.getPane().getRowSplitLoc(); + } + + /** + * Get whether to use manual grid color in the output sheet. + * + * @return boolean whether to use manual grid color + */ + public boolean getManualGridLineColor() { + return this.mysheet.getWindow2().getManualGridLineColor(); + } + + /** + * Set whether to use manual grid color in the output sheet. + * + * @param boolean whether to use manual grid color + */ + public void setManualGridLineColor(boolean b) { + this.mysheet.getWindow2().setManualGridLineColor(b); + } + + public CompatibleVector getAddedrows() { + return addedrows; + } + + /** + * Create a Conditional Format handle for a cell/range + * + * @param cellAddress without sheetname. Can also be a range, such as A1:B5 + * @param qualifier = maps to CONDITION_* bytes in ConditionalFormatHandle + * @param value1 = the error message + * @param value2 = the error title + * @param format = the initial format string to use with the condition + * @param firstCondition = formula string + * @param secondCondition = 2nd formula string (optional) + * @return + */ + public ConditionalFormatHandle createConditionalFormatHandle(String cellAddress, String operator, String value1, + String value2, String format, String firstCondition, String secondCondition) { + + if (cellAddress != null && cellAddress.indexOf("!") == -1) + cellAddress = this.getSheetName() + "!" + cellAddress; + Condfmt cfm = this.mysheet.createCondfmt(cellAddress, this.wbh); + + Cf cfr = this.mysheet.createCf(cfm); + cfr.setOperator(operator); + + // only place this is done.. + cfr.setCondition1(value1); + cfr.setCondition2(value2); + + Cf.setStylePropsFromString(format, cfr); + + ConditionalFormatHandle cfh = new ConditionalFormatHandle(cfm, this); + // done above in createCondfmt this.mysheet.addConditionalFormat(cfm); + return cfh; + } + + /** + * Creates a new annotation (Note or Comment) to the worksheeet, attached to a + * specific cell + * + * @param address -- address to attach + * @param txt -- text of note + * @param author -- name of author + * @return NoteHandle - handle which allows access to the Note object + * @see CommentHandle + */ + public CommentHandle createNote(String address, String txt, String author) { + Note n = this.getMysheet().createNote(address, txt, author); + return new CommentHandle(n); + } + + /** + * Creates a new annotation (Note or Comment) to the worksheeet, attached to a + * specific cell
        + * The note or comment is a Unicode string, thus it can contain formatting + * information + * + * @param address -- address to attach + * @param txt -- Unicode string of note with Formatting + * @param author -- name of author + * @return NoteHandle - handle which allows access to the Note object + * @see CommentHandle + */ + public CommentHandle createNote(String address, Unicodestring txt, String author) { + Note n = this.getMysheet().createNote(address, txt, author); + return new CommentHandle(n); + } + + /** + * returns an array of all CommentHandles that exist in the sheet + * + * @return + */ + public CommentHandle[] getCommentHandles() { + ArrayList notes = getMysheet().getNotes(); + CommentHandle[] nHandles = new CommentHandle[notes.size()]; + for (int i = 0; i < nHandles.length; i++) { + nHandles[i] = new CommentHandle((Note) notes.get(i)); + } + return nHandles; + } + + /** + * creates a new, blank PivotTable and adds it to the worksheet. + * + * @param name pivot table name + * @param range source range for the pivot table. If no sheet is specified, the + * current sheet will be used. + * @param sId Stream or cachid Id -- links back to SxStream set of records + * @return PivotTableHandle + */ + public PivotTableHandle createPivotTable(String name, String range, int sId) { + Sxview sx = getMysheet().addPivotTable(range, this.wbh, sId, name); + PivotTableHandle pth = new PivotTableHandle(sx, this.getWorkBook()); + pth.setSourceDataRange(range); + return pth; + } + + /** + * Get a validation handle for the cell address passed in. If the validation is + * for a range, the handle returned will modify the entire range, not just the + * cell address passed in. + *

        + * Returns null if a validation does not exist at the specified location + * + * @param cell address String + */ + public ValidationHandle getValidationHandle(String cellAddress) { + if (this.mysheet.getDvalRec() != null) { + Dv d = this.mysheet.getDvalRec().getDv(cellAddress); + if (d == null) + return null; + return new ValidationHandle(d); + } + return null; + } + + /** + * Create a validation handle for a cell/range + * + * @param cellAddress without sheetname. Can also be a range, such as A1:B5 + * @param valueType = maps to VALUE_* bytes in ValidationHandle + * @param condition = maps to CONDITION_* bytes in ValidationHandle + * @param errorBoxText = the error message + * @param errorBoxTitle = the error title + * @param promptBoxText = the prompt (hover) message + * @param promptBoxTitle = the prompt (hover) title + * @param firstCondition = formula string, seeValidationHandle.setFirstCondition + * @param secondCondition = seeValidationHandle.setSecondCondition, this can be left null + * for validations that do not require a second argument. + * @return + */ + public ValidationHandle createValidationHandle(String cellAddress, byte valueType, byte condition, + String errorBoxText, String errorBoxTitle, String promptBoxText, String promptBoxTitle, + String firstCondition, String secondCondition) { + Dv d = this.mysheet.createDv(cellAddress); + d.setValType(valueType); + /* + * // KSC: APPARENTLY NOT NEEDED if + * (valueType==ValidationHandle.VALUE_USER_DEFINED_LIST) { // ensure Mso + * Drop-downs are defined int objId = + * this.mysheet.insertDropDownBox(d.getColNumber()); //TODO: verify that drop + * down lists are SHARED **** + * this.mysheet.getDvalRec().setObjectIdentifier(objId); } + */ + d.setTypeOperator(condition); + d.setErrorBoxText(errorBoxText); + d.setErrorBoxTitle(errorBoxTitle); + d.setPromptBoxText(promptBoxText); + d.setPromptBoxTitle(promptBoxTitle); + if (firstCondition != null) + d.setFirstCond(firstCondition); + if (secondCondition != null) + d.setSecondCond(secondCondition); + ValidationHandle vh = new ValidationHandle(d); + return vh; + } + + /** + * Return all validation handles that refer to this worksheet + * + * @return array of all validationhandles valid for this worksheet + */ + public ValidationHandle[] getAllValidationHandles() { + if (mysheet.getDvRecs() == null) + return new ValidationHandle[0]; + ValidationHandle[] vh = new ValidationHandle[mysheet.getDvRecs().size()]; + List dvrecs = mysheet.getDvRecs(); + for (int i = 0; i < vh.length; i++) { + vh[i] = new ValidationHandle((Dv) dvrecs.get(i)); + } + return vh; + } + + /** + * return true if sheet contains data validations + * + * @return boolean + */ + public boolean hasDataValidations() { + return (mysheet.getDvRecs() != null); + } + + /** + * Returns a list of all AutoFilterHandles on this sheet
        + * An AutoFilterHandle allows access and manipulation of AutoFilters on the + * sheet + * + * @return array of AutoFilterHandles if any exist on sheet, null otherwise + */ + public AutoFilterHandle[] getAutoFilterHandles() { + if (mysheet.getAutoFilters() == null) + return null; + AutoFilterHandle[] af = new AutoFilterHandle[mysheet.getAutoFilters().size()]; + List afs = mysheet.getAutoFilters(); + for (int i = 0; i < afs.size(); i++) { + af[i] = new AutoFilterHandle((AutoFilter) afs.get(i)); + } + return af; + } + + /** + * Adds a new AutoFilter for the specified column (0-based) in this sheet
        + * returns a handle to the new AutoFilter + * + * @param int column - column number to add an AutoFilter to + * @return AutoFilterHandle + */ + public AutoFilterHandle addAutoFilter(int column) { + AutoFilter af = mysheet.addAutoFilter(column); + return new AutoFilterHandle(af); + } + + /** + * Removes all AutoFilters from this sheet
        + * As a consequence, all previously hidden rows are shown or unhidden + */ + public void removeAutoFilters() { + mysheet.removeAutoFilter(); + } + + /** + * Updates the Row filter (hidden status) for each row on the sheet by + * evaluating all AutoFilter conditions + *

        + * NOTE: This method must be called after Autofilter updates or additions + * in order to see the results of the AutoFilter(s) + *

        + * NOTE: this evaluation is NOT done automatically due to performance + * considerations, and is designed to be called after all additions and updating + * is completed (as evaluation may be time-consuming) + */ + public void evaluateAutoFilters() { + mysheet.evaluateAutoFilters(); + } + + /** + * clear out object references in prep for closing workbook + */ + public void close() { + if (mysheet != null) + mysheet.close(); + addedrows.clear(); + addedrows = new CompatibleVector(); + mysheet = null; + mybook = null; + wbh = null; + dateFormats.clear(); + dateFormats = null; + } + + protected Boundsheet getBoundsheet() { + return this.mysheet; + } + + /** + * Imports the given CSV data into this worksheet. All rows in the input will be + * inserted sequentially before any rows which already exist in this worksheet. + *

        + * To change the value delimiter set the system property + * "{@code io.starter.OpenXLS.csvdelimiter}". + */ + public void readCSV(BufferedReader input) throws IOException { + int rws = 0; + String field_delimiter = System.getProperty("io.starter.OpenXLS.csvdelimiter", ","); + + String thisLine = ""; + while ((thisLine = input.readLine()) != null) { // while loop begins here + String[] vals = StringTool.getTokensUsingDelim(thisLine, field_delimiter); + Object[] data = new Object[vals.length]; + for (int t = 0; t < vals.length; t++) { + vals[t] = StringTool.strip(vals[t], '"'); + try { + int i = Integer.parseInt(vals[t]); + data[t] = Integer.valueOf(i); + + double d = Double.parseDouble(vals[t]); + data[t] = new Double(d); + + } catch (NumberFormatException ax) { + // it's a string! + data[t] = vals[t]; + } + } + + insertRow(rws++, data, true); + } + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/package.html b/src/main/java/io/starter/OpenXLS/package.html index df72f36..0b92908 100644 --- a/src/main/java/io/starter/OpenXLS/package.html +++ b/src/main/java/io/starter/OpenXLS/package.html @@ -1,3 +1,8 @@ - -The OpenXLS package contains the primary classes for working with spreadsheets. See: OpenXLS Docs - \ No newline at end of file + + + +The OpenXLS package contains the primary classes for working with spreadsheets. See: OpenXLS Docs + + \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/util/Logger.java b/src/main/java/io/starter/OpenXLS/util/Logger.java deleted file mode 100755 index 7692a49..0000000 --- a/src/main/java/io/starter/OpenXLS/util/Logger.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * - */ -package io.starter.OpenXLS.util; - -import org.apache.log4j.PropertyConfigurator; - -/** - * the usual logging stuff - * - * @author John McMahon Copyright 2013 Starter Inc., all rights reserved. - * - */ -public class Logger implements SystemConstants { - - static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Logger.class); - - // PropertiesConfigurator is used to configure logger from properties file - static { - // URL lc = - // Logger.class.getClassLoader().getResource("resources/log4j.properties"); - String lc = ROOT_FOLDER + "/src/resources/log4j.properties"; - PropertyConfigurator.configure(lc); - } - - /** - * start with the basics - * - * @param message - */ - public static void log(String message) { - logger.info(message); - } - - public static void debug(String string) { - logger.debug(string); - } - - public static void error(String string) { - logger.error(string); - } - - public static void warn(String string) { - logger.warn(string); - - } - - public static void error(Exception e) { - error(e.getMessage()); - e.printStackTrace(); - } - -} diff --git a/src/main/java/io/starter/OpenXLS/util/SystemConstants.java b/src/main/java/io/starter/OpenXLS/util/SystemConstants.java deleted file mode 100644 index cd83a66..0000000 --- a/src/main/java/io/starter/OpenXLS/util/SystemConstants.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.starter.OpenXLS.util; - -public interface SystemConstants { - public static final String ROOT_FOLDER = null; -} diff --git a/src/main/java/io/starter/formats/LEO/BIGBLOCK.java b/src/main/java/io/starter/formats/LEO/BIGBLOCK.java index a63cd0e..47d1470 100644 --- a/src/main/java/io/starter/formats/LEO/BIGBLOCK.java +++ b/src/main/java/io/starter/formats/LEO/BIGBLOCK.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,34 +23,34 @@ package io.starter.formats.LEO; +/** + * LEO File BIGBLOCK Information Record. + *

        + * These blocks of data contain information related to the + * LEO file format data blocks. + *

        + * depending on the size and complexity of the file, + * these records may all be contained in the 'header' + * block of the LEO Stream File. + *

        + * In files over that size, one or more BIGBLOCK records + * are inserted directly in the midst of substream records. + */ +public class BIGBLOCK extends BlockImpl { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 839197117033095054L; + public final static int SIZE = 512; -/** LEO File BIGBLOCK Information Record. - - These blocks of data contain information related to the - LEO file format data blocks. - - depending on the size and complexity of the file, - these records may all be contained in the 'header' - block of the LEO Stream File. - - In files over that size, one or more BIGBLOCK records - are inserted directly in the midst of substream records. - -*/ -public class BIGBLOCK extends BlockImpl{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 839197117033095054L; - public final static int SIZE = 512; - - - /** returns the int representing the block type - */ - public int getBlockType(){return BIG;} + /** + * returns the int representing the block type + */ + public int getBlockType() { + return BIG; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/Block.java b/src/main/java/io/starter/formats/LEO/Block.java index 9addd3c..90dbc12 100644 --- a/src/main/java/io/starter/formats/LEO/Block.java +++ b/src/main/java/io/starter/formats/LEO/Block.java @@ -2,145 +2,168 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.LEO; -import java.util.*; -import java.nio.*; -import java.io.*; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.List; -/** the basic unit of data in a LEO file. Can either be BIG or SMALL. - - -*/ +/** + * the basic unit of data in a LEO file. Can either be BIG or SMALL. + */ public interface Block extends java.util.Iterator { - public final static int SMALL = 0; - public final static int BIG = 1; - /** - * @return - */ - public boolean isXBAT(); - - /** - * @param b - */ - public void setIsExtraSector(boolean b); - - /** link to the vector of blocks for the storage - */ - public void setBlockVector(List v); - - /** get the size of the Block data in bytes - * - * @return block data size - */ - public int getBlockSize(); - - /** get the index of this Block in the storage - Vector - */ - public int getBlockIndex(); - - /** link the next Block in the chain - */ - void setNextBlock(Block b); - - /** set the storage for this Block - */ - void setStorage(Storage s); - - /** set the storage for this Block - */ - Storage getStorage(); - - /** returns whether this block has been - added to the output stream - */ - boolean getStreamed(); - - /** sets whether this block has been - added to the output stream - */ - void setStreamed(boolean b); - - /** returns the int representing the block type - */ - int getBlockType(); - - /** returns true if this is a Block Depot block - that needs to be ignored when reading byte storages - */ - boolean getIsSpecialBlock(); - - /** returns true if this is a Block Depot block - that needs to be ignored when reading byte storages - */ - boolean getIsDepotBlock(); - - /** set to true if this is a Block Depot block - */ - void setIsDepotBlock(boolean b); - - /** init the Block Data - */ - void init(ByteBuffer d, int origidx, int origp); - - /** set whether this Block has been read yet... - */ - void setInitialized(boolean b); - - /** returns whether this Block has been read yet... - */ - boolean getInitialized(); - - /** set the data bytes on this Block - */ - void setBytes(ByteBuffer b); - - /** get the data bytes on this Block - */ - ByteBuffer getByteBuffer(); - - /** return the byte Array for this BLOCK - */ - public byte[] getBytes(int start, int end); - - /** get the data bytes on this Block - */ - byte[] getBytes(); - - /** write the data bytes on this Block to out - */ - void writeBytes(OutputStream out); - - /** return the original BB position in the file - */ - int getOriginalPos(); - - /** return the original BB position in the file - */ - int getOriginalIdx(); - - /** set the original BB position in the file - */ - void setOriginalIdx(int x); + int SMALL = 0; + int BIG = 1; + + /** + * @return + */ + boolean isXBAT(); + + /** + * @param b + */ + void setIsExtraSector(boolean b); + + /** + * link to the vector of blocks for the storage + */ + void setBlockVector(List v); + + /** + * get the size of the Block data in bytes + * + * @return block data size + */ + int getBlockSize(); + + /** + * get the index of this Block in the storage + * Vector + */ + int getBlockIndex(); + + /** + * link the next Block in the chain + */ + void setNextBlock(Block b); + + /** + * set the storage for this Block + */ + void setStorage(Storage s); + + /** + * set the storage for this Block + */ + Storage getStorage(); + + /** + * returns whether this block has been + * added to the output stream + */ + boolean getStreamed(); + + /** + * sets whether this block has been + * added to the output stream + */ + void setStreamed(boolean b); + + /** + * returns the int representing the block type + */ + int getBlockType(); + + /** + * returns true if this is a Block Depot block + * that needs to be ignored when reading byte storages + */ + boolean getIsSpecialBlock(); + + /** + * returns true if this is a Block Depot block + * that needs to be ignored when reading byte storages + */ + boolean getIsDepotBlock(); + + /** + * set to true if this is a Block Depot block + */ + void setIsDepotBlock(boolean b); + + /** + * init the Block Data + */ + void init(ByteBuffer d, int origidx, int origp); + + /** + * set whether this Block has been read yet... + */ + void setInitialized(boolean b); + + /** + * returns whether this Block has been read yet... + */ + boolean getInitialized(); + + /** + * set the data bytes on this Block + */ + void setBytes(ByteBuffer b); + + /** + * get the data bytes on this Block + */ + ByteBuffer getByteBuffer(); + + /** + * return the byte Array for this BLOCK + */ + byte[] getBytes(int start, int end); + + /** + * get the data bytes on this Block + */ + byte[] getBytes(); + + /** + * write the data bytes on this Block to out + */ + void writeBytes(OutputStream out); + + /** + * return the original BB position in the file + */ + int getOriginalPos(); + + /** + * return the original BB position in the file + */ + int getOriginalIdx(); + + /** + * set the original BB position in the file + */ + void setOriginalIdx(int x); } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/BlockByteConsumer.java b/src/main/java/io/starter/formats/LEO/BlockByteConsumer.java index ae9bf54..61d3913 100644 --- a/src/main/java/io/starter/formats/LEO/BlockByteConsumer.java +++ b/src/main/java/io/starter/formats/LEO/BlockByteConsumer.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,105 +22,110 @@ */ package io.starter.formats.LEO; -/** The BlockByteConsumer interface describes an Object +/** + * The BlockByteConsumer interface describes an Object * which reads its data from a scattered collection of Blocks. - * - * By tracking the blocks containing the record's data, - * - * - * - * - * + *

        + * By tracking the blocks containing the record's data, */ public interface BlockByteConsumer { - - /** Set the relative position within the data - * underlying the block vector represented by - * the BlockByteReader. - * - * In other words, this is the relative position - * used by the BlockByteReader to offset the Consumer's - * read position within the collection of Data Blocks. - * - * This may be an offset relative to the data in a file, - * or within a Storage contained in a file. - * - * The Workbook Storage for example will contain a non-contiguous - * collection of Blocks containing data from any number of - * positions in a file. - * - * This collection forms a contiguous span of bytes comprising - * an XLS Workbook. The XLSRecords within this span of bytes will - * set their relative position within this 'virtual' array. Thus - * the XLSRecord positions are relative to the order of bytes contained - * in the Block collection. The BOF record then is at offset 0 within the - * data of the first Block, even though the underlying data of this - * first Block may be anywhere on disk. - * - * @param pos - */ - void setOffset(int pos); - /** Get the relative position within the data - * underlying the block vector represented by - * the BlockByteReader. - * - * @return relative position - */ - int getOffset(); - - /** Get the blocks containing this Consumer's data - * - * @return - */ + /** + * Set the relative position within the data + * underlying the block vector represented by + * the BlockByteReader. + *

        + * In other words, this is the relative position + * used by the BlockByteReader to offset the Consumer's + * read position within the collection of Data Blocks. + *

        + * This may be an offset relative to the data in a file, + * or within a Storage contained in a file. + *

        + * The Workbook Storage for example will contain a non-contiguous + * collection of Blocks containing data from any number of + * positions in a file. + *

        + * This collection forms a contiguous span of bytes comprising + * an XLS Workbook. The XLSRecords within this span of bytes will + * set their relative position within this 'virtual' array. Thus + * the XLSRecord positions are relative to the order of bytes contained + * in the Block collection. The BOF record then is at offset 0 within the + * data of the first Block, even though the underlying data of this + * first Block may be anywhere on disk. + * + * @param pos + */ + void setOffset(int pos); + + /** + * Get the relative position within the data + * underlying the block vector represented by + * the BlockByteReader. + * + * @return relative position + */ + int getOffset(); + + /** Get the blocks containing this Consumer's data + * + * @return + */ // KSC: NOT USED Block[] getBlocks(); - - /** Set the blocks containing this Consumer's data - * - * @param myblocks - */ + + /** Set the blocks containing this Consumer's data + * + * @param myblocks + */ // KSC: NOT USED void setBlocks(Block[] myblocks); - /** Sets the index of the first block - * - * @return - */ - void setFirstBlock(int i); + /** + * Sets the index of the first block + * + * @return + */ + void setFirstBlock(int i); + + /** + * Sets the index of the last block + * + * @return + */ + void setLastBlock(int i); - /** Sets the index of the last block - * - * @return - */ - void setLastBlock(int i); + /** + * Returns the index of the first block + * + * @return + */ + int getFirstBlock(); - /** Returns the index of the first block - * - * @return - */ - int getFirstBlock(); + /** + * Returns the index of the last block + * + * @return + */ + int getLastBlock(); - /** Returns the index of the last block - * - * @return - */ - int getLastBlock(); + /** + * Returns the length of the record. + * + * @return + */ + int getLength(); - /** Returns the length of the record. - * - * @return - */ - int getLength(); + /** + * Set the BlockByteReader for this Consumer + * + * @param db + */ + void setByteReader(BlockByteReader db); - /** Set the BlockByteReader for this Consumer - * - * @param db - */ - void setByteReader(BlockByteReader db); - - /** Get the BlockByteReader for this Consumer - * - * @param db - */ - BlockByteReader getByteReader(); + /** + * Get the BlockByteReader for this Consumer + * + * @param db + */ + BlockByteReader getByteReader(); } diff --git a/src/main/java/io/starter/formats/LEO/BlockByteReader.java b/src/main/java/io/starter/formats/LEO/BlockByteReader.java index 6e17c4e..db413ab 100644 --- a/src/main/java/io/starter/formats/LEO/BlockByteReader.java +++ b/src/main/java/io/starter/formats/LEO/BlockByteReader.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,325 +22,333 @@ */ package io.starter.formats.LEO; -import io.starter.toolkit.*; -import java.nio.*; -import java.util.*; -import java.io.*; import io.starter.formats.XLS.WorkBookException; -/** Provide a translation layer between the block vector and a byte array. - * - * +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.Logger; + +import java.io.ByteArrayOutputStream; +import java.io.Serializable; +import java.nio.*; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; + +/** + * Provide a translation layer between the block vector and a byte array. + *

        + *

        * A record (Storage, XLSRecord) can retrieve bytes from Scattered blocks * transparently. - * + *

        * The BlockByteReader Allocates a ByteBuffer containing only byte references * contained within Blocs assigned to the implementation class. - * + *

        * A Class using this reader will either subclass the reader and manage its * blocks, or interact with a shared reader. - * - * In the case of a shared reader, - * - * - * - * + *

        + * In the case of a shared reader, */ -public class BlockByteReader implements Serializable{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4845306509411520019L; - private boolean applyRelativePosition = true; - - protected BlockByteReader() { - // empty constructor... - } - - private List blockmap = new ArrayList(); - private boolean ro = false; - private int length = -1; - transient ByteBuffer backingByteBuffer = ByteBuffer.allocate(0); - - public BlockByteReader(List blokz, int len) { - this.blockmap = blokz; - this.length = len; - } - - public boolean isReadOnly() { - return ro; - } - - /** Allows for getting of header bytes without setting blocks on a rec - * - * @param startpos - * @return - */ - public byte[] getHeaderBytes(int startpos) { - try { - int SIZE= BIGBLOCK.SIZE; // normal case - if (this.length < StorageTable.BIGSTORAGE_SIZE ) { - SIZE= SMALLBLOCK.SIZE; - } - int block = startpos / SIZE; - int check = startpos % SIZE; - // handle EOF that falls right on boundary - if(((check + 4) > SIZE) && (blockmap.size()-1==block)){ - // Last EOF falls within 4 bytes of 512 boundary... junkrec - byte[] junk = {0x0,0x0,0x0,0x0}; - return junk; - }else if ((check + 4) > SIZE) { // SPANNER! - Block bx = (Block) this.blockmap.get(block); - int l1 = ((SIZE * (block + 1)) - startpos); - int s2 = startpos % SIZE; - byte[] b1 = bx.getBytes(s2, s2 + l1); - bx = (Block) this.blockmap.get(block + 1); - l1 = 4 - l1; - byte[] b2 = bx.getBytes(0, l1); - return ByteTools.append(b2, b1); - } - - Block bx = (Block) this.blockmap.get(block); - startpos -= (block * SIZE); - return bx.getBytes(startpos, startpos + 4); - }catch(RuntimeException e) { - throw new WorkBookException("Smallblock based workbooks are unsupported in OpenXLS: see http://extentech.com/uimodules/docs/docs_detail.jsp?showall=true&meme_id=195", WorkBookException.SMALLBLOCK_FILE); - } - } - - - /* Return the byte from the blocks at the proper locations... - * - * @see java.nio.ByteBuffer#get() - */ - public byte get(BlockByteConsumer rec, int startpos) { - byte ret = this.get(rec, startpos, 1)[0]; - return ret; - } - - /* Return the bytes from the blocks at the proper locations... - * - * as opposed to when we are traversing the entire collection of bytes as in WorkBookFactory.parse() - * - * @ see java.nio.ByteBuffer # get() - */ - public byte[] get(BlockByteConsumer rec, int startpos, int len) { - rec.setByteReader(this); - // we only want to add the offset when - // we are fetching data from 'within' a record, ie: rkdata.get(i) - int recoffy = rec.getOffset(); - if (this.getApplyRelativePosition()) { - - startpos+=4; // add the offset - } - startpos += recoffy; - // reality checks - if(false) // ((startpos + len) > getLength()) - Logger.logWarn( - "WARNING: BlockByteReader.get(rec," - + startpos - + "," - + rec.getLength() - + ") error. Attempt to read past end of Block buffer."); - - // return the bytes from the rec - return getRecBytes(rec, startpos, len); - } - - /* Handles the spanning of Record Bytes over Block boundaries - * then returns requested bytes. - * - */ - private byte[] getRecBytes( - BlockByteConsumer rec, - int startpos, - int len) { - if ((startpos < 0) || (startpos > (startpos+len))) - throw new RuntimeException( - "ERROR: BBR.getRecBytes(" - + rec.getClass().getName() - + "," - + startpos - + "," - + (startpos+len) - + ") failed - OUT OF BOUNDS."); - // get the block byte boundaries - int[] pos = this.getReadPositions(startpos,len); - int numblocks = pos.length/3; - int blkdef = 0; - // backingByteBuffer = blokx.getByteBuffer(); - // Temporarily use BAOS... - ByteArrayOutputStream out = new ByteArrayOutputStream(); - for(int t=0;t - * ie: start 10 len 8 - * blk0 = 10-18 - * 0,10,18 - * - * ie: start 514 len 1 - * blk1 = 2-3 - * 1,2,3 - * - * ie: start 480 len 1124 - * - * blk0 = 480-512 - * blk1 = 512-1024 - * blk2 = 1024-1536 - * blk3 = 1536-1604 - * - * [0, 480, 512,1, 0, 512, 2, 0, 512, 3, 0, 68] - * - * 8139, 693 - * - * @param startpos - * @return - */ - public static int[] getReadPositions(int startpos, int len, boolean BIGBLOCKSTORAGE) { - // Logger.logInfo("BBR.getReadPositions()"+startpos+":"+endpos); - - // 20100323 KSC: handle small blocks - int SIZE= BIGBLOCK.SIZE; // normal case - if (!BIGBLOCKSTORAGE) - SIZE= SMALLBLOCK.SIZE; - - - int firstblock = startpos/SIZE; - int lastblock = (startpos+len)/SIZE; - int numblocks = lastblock-firstblock; - numblocks++; - int origlen = len; - - int ct = startpos / SIZE; - - - int pos1 = startpos; - int[] ret = new int[numblocks*3]; - int t = 0; - // for each block, create 2 byte positions - while(len>0) { - if(t>=ret.length){ - Logger.logWarn("BlockByteReader.getReadPositions() wrong guess on NumBlocks."); - numblocks++; - int[] retz = new int[numblocks*3]; - System.arraycopy(ret,0,retz,0,ret.length); - ret = retz; - } - ret[t++]= firstblock++; - int check = pos1 % SIZE; //leftover - check+=len; - if (check > SIZE) { // SPANNER! - pos1 = startpos - ((SIZE)*ct); - if(pos1<0)pos1 = 0; - // int s1 = pos1- ((SIZE)*(ct)); - int s2 = pos1 % SIZE; - if(s2<0){ - s2=0; - pos1 = 0; - } - ret[t++] = s2; - ret[t++] = SIZE; - } else { - pos1 = startpos - ((SIZE)*ct); - int strt = startpos - ((SIZE)*ct); - if(strt<0){ - ret[t++ ] = 0; - ret[t++ ] = len; - }else{ - ret[t++ ] = strt; - ret[t++ ] = (startpos+origlen) - ((SIZE)*ct); - } - - } - ct++; - int ctdn=ret[t-1]-pos1; - len-=(ctdn); - pos1=0;//startpos; - } - return ret; - } - - /** Gets the list of blocks needed to read the given sequence. - */ - public int[] getReadPositions(int startpos, int len) { - return getReadPositions( startpos, len, - (this.length >= StorageTable.BIGSTORAGE_SIZE) ); - } - - /** Gets the mapping from stream offsets to file offsets over the given - * range. - * - * This returns an array of integers arranged in pairs. The first value of - * each pair is an offset from start and the second is the - * corresponding offset in the source file. - */ - public int[] getFileOffsets (int start, int size) { - int[] smap = this.getReadPositions( start, size ); - int[] fmap = new int[ smap.length / 3 * 2 ]; - - int offset = 0; - int fidx = 0; - Block block = null; - Block prev; - for (int sidx = 0; sidx < smap.length; sidx += 3) { - prev = block; - block = (Block) this.blockmap.get( smap[sidx] ); - - if (prev == null || block.getOriginalPos() + smap[sidx+1] != - prev.getOriginalPos() + smap[sidx-1] ) { - fmap[fidx++] = offset; - fmap[fidx++] = block.getOriginalPos() + smap[sidx+1]; - } - - offset += smap[sidx+2] - smap[sidx+1]; - } - - int[] ret = new int[ fidx ]; - System.arraycopy( fmap, 0, ret, 0, fidx ); - return ret; - } - - public String getFileOffsetString (int start, int size) { - String ret = ""; - int[] map = this.getFileOffsets( start, size ); - - for (int idx = 0; idx < map.length; idx += 2) { - ret += (idx != 0 ? " " : "") - + Integer.toHexString( map[idx+0] ).toUpperCase() + ":" - + Integer.toHexString( map[idx+1] ).toUpperCase(); - } - - return ret; - } - - /**Assign blocks to the recs - * - * @param rec - * @param startpos - * @return - */ +public class BlockByteReader implements Serializable { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4845306509411520019L; + private boolean applyRelativePosition = true; + + protected BlockByteReader() { + // empty constructor... + } + + private List blockmap = new ArrayList(); + private boolean ro = false; + private int length = -1; + transient ByteBuffer backingByteBuffer = ByteBuffer.allocate(0); + + public BlockByteReader(List blokz, int len) { + this.blockmap = blokz; + this.length = len; + } + + public boolean isReadOnly() { + return ro; + } + + /** + * Allows for getting of header bytes without setting blocks on a rec + * + * @param startpos + * @return + */ + public byte[] getHeaderBytes(int startpos) { + try { + int SIZE = BIGBLOCK.SIZE; // normal case + if (this.length < StorageTable.BIGSTORAGE_SIZE) { + SIZE = SMALLBLOCK.SIZE; + } + int block = startpos / SIZE; + int check = startpos % SIZE; + // handle EOF that falls right on boundary + if (((check + 4) > SIZE) && (blockmap.size() - 1 == block)) { + // Last EOF falls within 4 bytes of 512 boundary... junkrec + byte[] junk = {0x0, 0x0, 0x0, 0x0}; + return junk; + } else if ((check + 4) > SIZE) { // SPANNER! + Block bx = (Block) this.blockmap.get(block); + int l1 = ((SIZE * (block + 1)) - startpos); + int s2 = startpos % SIZE; + byte[] b1 = bx.getBytes(s2, s2 + l1); + bx = (Block) this.blockmap.get(block + 1); + l1 = 4 - l1; + byte[] b2 = bx.getBytes(0, l1); + return ByteTools.append(b2, b1); + } + + Block bx = (Block) this.blockmap.get(block); + startpos -= (block * SIZE); + return bx.getBytes(startpos, startpos + 4); + } catch (RuntimeException e) { + throw new WorkBookException("Smallblock based workbooks are unsupported in OpenXLS: see http://extentech.com/uimodules/docs/docs_detail.jsp?showall=true&meme_id=195", WorkBookException.SMALLBLOCK_FILE); + } + } + + + /* Return the byte from the blocks at the proper locations... + * + * @see java.nio.ByteBuffer#get() + */ + public byte get(BlockByteConsumer rec, int startpos) { + byte ret = this.get(rec, startpos, 1)[0]; + return ret; + } + + /* Return the bytes from the blocks at the proper locations... + * + * as opposed to when we are traversing the entire collection of bytes as in WorkBookFactory.parse() + * + * @ see java.nio.ByteBuffer # get() + */ + public byte[] get(BlockByteConsumer rec, int startpos, int len) { + rec.setByteReader(this); + // we only want to add the offset when + // we are fetching data from 'within' a record, ie: rkdata.get(i) + int recoffy = rec.getOffset(); + if (this.getApplyRelativePosition()) { + + startpos += 4; // add the offset + } + startpos += recoffy; + // reality checks + if (false) // ((startpos + len) > getLength()) + Logger.logWarn( + "WARNING: BlockByteReader.get(rec," + + startpos + + "," + + rec.getLength() + + ") error. Attempt to read past end of Block buffer."); + + // return the bytes from the rec + return getRecBytes(rec, startpos, len); + } + + /* Handles the spanning of Record Bytes over Block boundaries + * then returns requested bytes. + * + */ + private byte[] getRecBytes( + BlockByteConsumer rec, + int startpos, + int len) { + if ((startpos < 0) || (startpos > (startpos + len))) + throw new RuntimeException( + "ERROR: BBR.getRecBytes(" + + rec.getClass().getName() + + "," + + startpos + + "," + + (startpos + len) + + ") failed - OUT OF BOUNDS."); + // get the block byte boundaries + int[] pos = this.getReadPositions(startpos, len); + int numblocks = pos.length / 3; + int blkdef = 0; + // backingByteBuffer = blokx.getByteBuffer(); + // Temporarily use BAOS... + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (int t = 0; t < numblocks; t++) { + try { // inlining byte read + Block b1 = (Block) this.blockmap.get(pos[blkdef++]); + out.write(b1.getBytes(pos[blkdef++], pos[blkdef++])); + if (false) + Logger.logInfo( + "INFO: BBR.getRecBytes() " + + rec.getClass().getName() + + " ACCESSING DATA for block:" + + b1.getBlockIndex() + + ":" + + pos[0] + + "-" + + pos[1]); + } catch (Exception a) { + Logger.logWarn( + "ERROR: BBR.getRecBytes streaming " + rec.toString() + " bytes for block failed: " + a); + } + } + return out.toByteArray(); + } + + + /** + * returns the lengths of the two byte + *

        +     * ie: start 10 len 8
        +     * blk0 = 10-18
        +     * 0,10,18
        +     *
        +     * ie: start 514 len 1
        +     * blk1 = 2-3
        +     * 1,2,3
        +     *
        +     * ie: start 480 len 1124
        +     *
        +     *  blk0 = 480-512
        +     *  blk1 = 512-1024
        +     *  blk2 = 1024-1536
        +     *  blk3 = 1536-1604
        +     *
        +     * [0, 480, 512,1, 0, 512, 2, 0, 512, 3, 0, 68]
        +     *
        +     * 8139, 693
        +     * 
        + * + * @param startpos + * @return + */ + public static int[] getReadPositions(int startpos, int len, boolean BIGBLOCKSTORAGE) { + // Logger.logInfo("BBR.getReadPositions()"+startpos+":"+endpos); + + // 20100323 KSC: handle small blocks + int SIZE = BIGBLOCK.SIZE; // normal case + if (!BIGBLOCKSTORAGE) + SIZE = SMALLBLOCK.SIZE; + + + int firstblock = startpos / SIZE; + int lastblock = (startpos + len) / SIZE; + int numblocks = lastblock - firstblock; + numblocks++; + int origlen = len; + + int ct = startpos / SIZE; + + + int pos1 = startpos; + int[] ret = new int[numblocks * 3]; + int t = 0; + // for each block, create 2 byte positions + while (len > 0) { + if (t >= ret.length) { + Logger.logWarn("BlockByteReader.getReadPositions() wrong guess on NumBlocks."); + numblocks++; + int[] retz = new int[numblocks * 3]; + System.arraycopy(ret, 0, retz, 0, ret.length); + ret = retz; + } + ret[t++] = firstblock++; + int check = pos1 % SIZE; //leftover + check += len; + if (check > SIZE) { // SPANNER! + pos1 = startpos - ((SIZE) * ct); + if (pos1 < 0) pos1 = 0; + // int s1 = pos1- ((SIZE)*(ct)); + int s2 = pos1 % SIZE; + if (s2 < 0) { + s2 = 0; + pos1 = 0; + } + ret[t++] = s2; + ret[t++] = SIZE; + } else { + pos1 = startpos - ((SIZE) * ct); + int strt = startpos - ((SIZE) * ct); + if (strt < 0) { + ret[t++] = 0; + ret[t++] = len; + } else { + ret[t++] = strt; + ret[t++] = (startpos + origlen) - ((SIZE) * ct); + } + + } + ct++; + int ctdn = ret[t - 1] - pos1; + len -= (ctdn); + pos1 = 0;//startpos; + } + return ret; + } + + /** + * Gets the list of blocks needed to read the given sequence. + */ + public int[] getReadPositions(int startpos, int len) { + return getReadPositions(startpos, len, + (this.length >= StorageTable.BIGSTORAGE_SIZE)); + } + + /** + * Gets the mapping from stream offsets to file offsets over the given + * range. + *

        + * This returns an array of integers arranged in pairs. The first value of + * each pair is an offset from start and the second is the + * corresponding offset in the source file. + */ + public int[] getFileOffsets(int start, int size) { + int[] smap = this.getReadPositions(start, size); + int[] fmap = new int[smap.length / 3 * 2]; + + int offset = 0; + int fidx = 0; + Block block = null; + Block prev; + for (int sidx = 0; sidx < smap.length; sidx += 3) { + prev = block; + block = (Block) this.blockmap.get(smap[sidx]); + + if (prev == null || block.getOriginalPos() + smap[sidx + 1] != + prev.getOriginalPos() + smap[sidx - 1]) { + fmap[fidx++] = offset; + fmap[fidx++] = block.getOriginalPos() + smap[sidx + 1]; + } + + offset += smap[sidx + 2] - smap[sidx + 1]; + } + + int[] ret = new int[fidx]; + System.arraycopy(fmap, 0, ret, 0, fidx); + return ret; + } + + public String getFileOffsetString(int start, int size) { + String ret = ""; + int[] map = this.getFileOffsets(start, size); + + for (int idx = 0; idx < map.length; idx += 2) { + ret += (idx != 0 ? " " : "") + + Integer.toHexString(map[idx + 0]).toUpperCase() + ":" + + Integer.toHexString(map[idx + 1]).toUpperCase(); + } + + return ret; + } + + /**Assign blocks to the recs + * + * @param rec + * @param startpos + * @return + */ /* Not used public int setBlocks(BlockByteConsumer rec, int startpos) { if (rec.getBlocks() != null) @@ -390,588 +398,590 @@ public int setBlocks(BlockByteConsumer rec, int startpos) { return firstblock; } */ - /** For whatever reason, get the blockmap - * - * @return the map of blocks - */ - public List getBlockmap() { - return blockmap; - } - /** Set the map of Blocks contained in this reader - * - * @param list the map of blocks - */ - public void setBlockmap(AbstractList list) { - blockmap = list; - } - - /** - * @return - */ - public int getLength() { - return length; - } - - /** - * @param i - */ - public void setLength(int i) { - length = i; - } - - /** - * @param arg0 - * @return - */ - public static ByteBuffer allocate(int arg0) { - return ByteBuffer.allocate(arg0); - } - - /** - * @param arg0 - * @return - */ - public static ByteBuffer allocateDirect(int arg0) { - return ByteBuffer.allocateDirect(arg0); - } - - /** - * @param arg0 - * @return - */ - public final static ByteBuffer wrap(byte[] arg0) { - return ByteBuffer.wrap(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @param arg2 - * @return - */ - public final static ByteBuffer wrap(byte[] arg0, int arg1, int arg2) { - return ByteBuffer.wrap(arg0, arg1, arg2); - } - - /** - * @return - */ - public byte[] array() { - return backingByteBuffer.array(); - } - - /** - * @return - */ - public int arrayOffset() { - return backingByteBuffer.arrayOffset(); - } - - /** - * @return - */ - public CharBuffer asCharBuffer() { - return backingByteBuffer.asCharBuffer(); - } - - /** - * @return - */ - public DoubleBuffer asDoubleBuffer() { - return backingByteBuffer.asDoubleBuffer(); - } - - /** - * @return - */ - public FloatBuffer asFloatBuffer() { - return backingByteBuffer.asFloatBuffer(); - } - - /** - * @return - */ - public IntBuffer asIntBuffer() { - return backingByteBuffer.asIntBuffer(); - } - - /** - * @return - */ - public LongBuffer asLongBuffer() { - return backingByteBuffer.asLongBuffer(); - } - - /** - * @return - */ - public ByteBuffer asReadOnlyBuffer() { - return backingByteBuffer.asReadOnlyBuffer(); - } - - /** - * @return - */ - public ShortBuffer asShortBuffer() { - return backingByteBuffer.asShortBuffer(); - } - - /** - * @return - */ - public int capacity() { - return backingByteBuffer.capacity(); - } - - /** - * @return - */ - public Buffer clear() { - return backingByteBuffer.clear(); - } - - /** - * @return - */ - public ByteBuffer compact() { - return backingByteBuffer.compact(); - } - - /** - * @param arg0 - * @return - - public int compareTo(Object arg0) { - return backingByteBuffer.compareTo(arg0); - }*/ - - /** - * @return - */ - public ByteBuffer duplicate() { - return backingByteBuffer.duplicate(); - } - - /* (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - public boolean equals(Object arg0) { - return backingByteBuffer.equals(arg0); - } - - /** - * @return - */ - public Buffer flip() { - return backingByteBuffer.flip(); - } - - /** - * @return - */ - public byte get() { - return backingByteBuffer.get(); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer get(byte[] arg0) { - return backingByteBuffer.get(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @param arg2 - * @return - */ - public ByteBuffer get(byte[] arg0, int arg1, int arg2) { - return backingByteBuffer.get(arg0, arg1, arg2); - } - - /** - * @return - */ - public char getChar() { - return backingByteBuffer.getChar(); - } - - /** - * @param arg0 - * @return - */ - public char getChar(int arg0) { - return backingByteBuffer.getChar(arg0); - } - - /** - * @return - */ - public double getDouble() { - return backingByteBuffer.getDouble(); - } - - /** - * @param arg0 - * @return - */ - public double getDouble(int arg0) { - return backingByteBuffer.getDouble(arg0); - } - - /** - * @return - */ - public float getFloat() { - return backingByteBuffer.getFloat(); - } - - /** - * @param arg0 - * @return - */ - public float getFloat(int arg0) { - return backingByteBuffer.getFloat(arg0); - } - - /** - * @return - */ - public int getInt() { - return backingByteBuffer.getInt(); - } - - /** - * @param arg0 - * @return - */ - public int getInt(int arg0) { - return backingByteBuffer.getInt(arg0); - } - - /** - * @return - */ - public long getLong() { - return backingByteBuffer.getLong(); - } - - /** - * @param arg0 - * @return - */ - public long getLong(int arg0) { - return backingByteBuffer.getLong(arg0); - } - - /** - * @return - */ - public short getShort() { - return backingByteBuffer.getShort(); - } - - /** - * @param arg0 - * @return - */ - public short getShort(int arg0) { - return backingByteBuffer.getShort(arg0); - } - - /** - * @return - */ - public boolean hasArray() { - return backingByteBuffer.hasArray(); - } - - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - public int hashCode() { - return backingByteBuffer.hashCode(); - } - - /** - * @return - */ - public boolean hasRemaining() { - return backingByteBuffer.hasRemaining(); - } - - /** - * @return - */ - public boolean isDirect() { - return backingByteBuffer.isDirect(); - } - - /** - * @return - */ - public int limit() { - return backingByteBuffer.limit(); - } - - /** - * @param arg0 - * @return - */ - public Buffer limit(int arg0) { - return backingByteBuffer.limit(arg0); - } - - /** - * @return - */ - public Buffer mark() { - return backingByteBuffer.mark(); - } - - /** - * @return - */ - public ByteOrder order() { - return backingByteBuffer.order(); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer order(ByteOrder arg0) { - return backingByteBuffer.order(arg0); - } - - /** - * @return - */ - public int position() { - return backingByteBuffer.position(); - } - - /** - * @param arg0 - * @return - */ - public Buffer position(int arg0) { - return backingByteBuffer.position(arg0); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer put(byte arg0) { - return backingByteBuffer.put(arg0); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer put(byte[] arg0) { - return backingByteBuffer.put(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @param arg2 - * @return - */ - public ByteBuffer put(byte[] arg0, int arg1, int arg2) { - return backingByteBuffer.put(arg0, arg1, arg2); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer put(int arg0, byte arg1) { - return backingByteBuffer.put(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer put(ByteBuffer arg0) { - return backingByteBuffer.put(arg0); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putChar(char arg0) { - return backingByteBuffer.putChar(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putChar(int arg0, char arg1) { - return backingByteBuffer.putChar(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putDouble(double arg0) { - return backingByteBuffer.putDouble(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putDouble(int arg0, double arg1) { - return backingByteBuffer.putDouble(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putFloat(float arg0) { - return backingByteBuffer.putFloat(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putFloat(int arg0, float arg1) { - return backingByteBuffer.putFloat(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putInt(int arg0) { - return backingByteBuffer.putInt(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putInt(int arg0, int arg1) { - return backingByteBuffer.putInt(arg0, arg1); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putLong(int arg0, long arg1) { - return backingByteBuffer.putLong(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putLong(long arg0) { - return backingByteBuffer.putLong(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putShort(int arg0, short arg1) { - return backingByteBuffer.putShort(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putShort(short arg0) { - return backingByteBuffer.putShort(arg0); - } - - /** - * @return - */ - public int remaining() { - return backingByteBuffer.remaining(); - } - - /** - * @return - */ - public Buffer reset() { - return backingByteBuffer.reset(); - } - - /** - * @return - */ - public Buffer rewind() { - return backingByteBuffer.rewind(); - } - - /** - * @return - */ - public ByteBuffer slice() { - return backingByteBuffer.slice(); - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - public String toString() { - return backingByteBuffer.toString(); - } - - /** - * @return - */ - public ByteBuffer getBackingByteBuffer() { - return backingByteBuffer; - } - - /** - * @param buffer - */ - public void setBackingByteBuffer(ByteBuffer buffer) { - backingByteBuffer = buffer; - } - - /** - * @return - */ - public boolean getApplyRelativePosition() { - return applyRelativePosition; - } - - /** Only add the offset when we are fetching - data from 'within' a record, ie: rkdata.get(i) - as opposed to when we are traversing the entire collection - of bytes as in WorkBookFactory.parse()... - - * * @param b - */ - public void setApplyRelativePosition(boolean b) { - applyRelativePosition = b; - } + /** + * For whatever reason, get the blockmap + * + * @return the map of blocks + */ + public List getBlockmap() { + return blockmap; + } + + /** + * Set the map of Blocks contained in this reader + * + * @param list the map of blocks + */ + public void setBlockmap(AbstractList list) { + blockmap = list; + } + + /** + * @return + */ + public int getLength() { + return length; + } + + /** + * @param i + */ + public void setLength(int i) { + length = i; + } + + /** + * @param arg0 + * @return + */ + public static ByteBuffer allocate(int arg0) { + return ByteBuffer.allocate(arg0); + } + + /** + * @param arg0 + * @return + */ + public static ByteBuffer allocateDirect(int arg0) { + return ByteBuffer.allocateDirect(arg0); + } + + /** + * @param arg0 + * @return + */ + public final static ByteBuffer wrap(byte[] arg0) { + return ByteBuffer.wrap(arg0); + } + + /** + * @param arg0 + * @param arg1 + * @param arg2 + * @return + */ + public final static ByteBuffer wrap(byte[] arg0, int arg1, int arg2) { + return ByteBuffer.wrap(arg0, arg1, arg2); + } + + /** + * @return + */ + public byte[] array() { + return backingByteBuffer.array(); + } + + /** + * @return + */ + public int arrayOffset() { + return backingByteBuffer.arrayOffset(); + } + + /** + * @return + */ + public CharBuffer asCharBuffer() { + return backingByteBuffer.asCharBuffer(); + } + + /** + * @return + */ + public DoubleBuffer asDoubleBuffer() { + return backingByteBuffer.asDoubleBuffer(); + } + + /** + * @return + */ + public FloatBuffer asFloatBuffer() { + return backingByteBuffer.asFloatBuffer(); + } + + /** + * @return + */ + public IntBuffer asIntBuffer() { + return backingByteBuffer.asIntBuffer(); + } + + /** + * @return + */ + public LongBuffer asLongBuffer() { + return backingByteBuffer.asLongBuffer(); + } + + /** + * @return + */ + public ByteBuffer asReadOnlyBuffer() { + return backingByteBuffer.asReadOnlyBuffer(); + } + + /** + * @return + */ + public ShortBuffer asShortBuffer() { + return backingByteBuffer.asShortBuffer(); + } + + /** + * @return + */ + public int capacity() { + return backingByteBuffer.capacity(); + } + + /** + * @return + */ + public Buffer clear() { + return backingByteBuffer.clear(); + } + + /** + * @return + */ + public ByteBuffer compact() { + return backingByteBuffer.compact(); + } + + /** + * @param arg0 + * @return public int compareTo(Object arg0) { + return backingByteBuffer.compareTo(arg0); + }*/ + + /** + * @return + */ + public ByteBuffer duplicate() { + return backingByteBuffer.duplicate(); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object arg0) { + return backingByteBuffer.equals(arg0); + } + + /** + * @return + */ + public Buffer flip() { + return backingByteBuffer.flip(); + } + + /** + * @return + */ + public byte get() { + return backingByteBuffer.get(); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer get(byte[] arg0) { + return backingByteBuffer.get(arg0); + } + + /** + * @param arg0 + * @param arg1 + * @param arg2 + * @return + */ + public ByteBuffer get(byte[] arg0, int arg1, int arg2) { + return backingByteBuffer.get(arg0, arg1, arg2); + } + + /** + * @return + */ + public char getChar() { + return backingByteBuffer.getChar(); + } + + /** + * @param arg0 + * @return + */ + public char getChar(int arg0) { + return backingByteBuffer.getChar(arg0); + } + + /** + * @return + */ + public double getDouble() { + return backingByteBuffer.getDouble(); + } + + /** + * @param arg0 + * @return + */ + public double getDouble(int arg0) { + return backingByteBuffer.getDouble(arg0); + } + + /** + * @return + */ + public float getFloat() { + return backingByteBuffer.getFloat(); + } + + /** + * @param arg0 + * @return + */ + public float getFloat(int arg0) { + return backingByteBuffer.getFloat(arg0); + } + + /** + * @return + */ + public int getInt() { + return backingByteBuffer.getInt(); + } + + /** + * @param arg0 + * @return + */ + public int getInt(int arg0) { + return backingByteBuffer.getInt(arg0); + } + + /** + * @return + */ + public long getLong() { + return backingByteBuffer.getLong(); + } + + /** + * @param arg0 + * @return + */ + public long getLong(int arg0) { + return backingByteBuffer.getLong(arg0); + } + + /** + * @return + */ + public short getShort() { + return backingByteBuffer.getShort(); + } + + /** + * @param arg0 + * @return + */ + public short getShort(int arg0) { + return backingByteBuffer.getShort(arg0); + } + + /** + * @return + */ + public boolean hasArray() { + return backingByteBuffer.hasArray(); + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return backingByteBuffer.hashCode(); + } + + /** + * @return + */ + public boolean hasRemaining() { + return backingByteBuffer.hasRemaining(); + } + + /** + * @return + */ + public boolean isDirect() { + return backingByteBuffer.isDirect(); + } + + /** + * @return + */ + public int limit() { + return backingByteBuffer.limit(); + } + + /** + * @param arg0 + * @return + */ + public Buffer limit(int arg0) { + return backingByteBuffer.limit(arg0); + } + + /** + * @return + */ + public Buffer mark() { + return backingByteBuffer.mark(); + } + + /** + * @return + */ + public ByteOrder order() { + return backingByteBuffer.order(); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer order(ByteOrder arg0) { + return backingByteBuffer.order(arg0); + } + + /** + * @return + */ + public int position() { + return backingByteBuffer.position(); + } + + /** + * @param arg0 + * @return + */ + public Buffer position(int arg0) { + return backingByteBuffer.position(arg0); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer put(byte arg0) { + return backingByteBuffer.put(arg0); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer put(byte[] arg0) { + return backingByteBuffer.put(arg0); + } + + /** + * @param arg0 + * @param arg1 + * @param arg2 + * @return + */ + public ByteBuffer put(byte[] arg0, int arg1, int arg2) { + return backingByteBuffer.put(arg0, arg1, arg2); + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + public ByteBuffer put(int arg0, byte arg1) { + return backingByteBuffer.put(arg0, arg1); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer put(ByteBuffer arg0) { + return backingByteBuffer.put(arg0); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer putChar(char arg0) { + return backingByteBuffer.putChar(arg0); + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + public ByteBuffer putChar(int arg0, char arg1) { + return backingByteBuffer.putChar(arg0, arg1); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer putDouble(double arg0) { + return backingByteBuffer.putDouble(arg0); + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + public ByteBuffer putDouble(int arg0, double arg1) { + return backingByteBuffer.putDouble(arg0, arg1); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer putFloat(float arg0) { + return backingByteBuffer.putFloat(arg0); + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + public ByteBuffer putFloat(int arg0, float arg1) { + return backingByteBuffer.putFloat(arg0, arg1); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer putInt(int arg0) { + return backingByteBuffer.putInt(arg0); + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + public ByteBuffer putInt(int arg0, int arg1) { + return backingByteBuffer.putInt(arg0, arg1); + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + public ByteBuffer putLong(int arg0, long arg1) { + return backingByteBuffer.putLong(arg0, arg1); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer putLong(long arg0) { + return backingByteBuffer.putLong(arg0); + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + public ByteBuffer putShort(int arg0, short arg1) { + return backingByteBuffer.putShort(arg0, arg1); + } + + /** + * @param arg0 + * @return + */ + public ByteBuffer putShort(short arg0) { + return backingByteBuffer.putShort(arg0); + } + + /** + * @return + */ + public int remaining() { + return backingByteBuffer.remaining(); + } + + /** + * @return + */ + public Buffer reset() { + return backingByteBuffer.reset(); + } + + /** + * @return + */ + public Buffer rewind() { + return backingByteBuffer.rewind(); + } + + /** + * @return + */ + public ByteBuffer slice() { + return backingByteBuffer.slice(); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + return backingByteBuffer.toString(); + } + + /** + * @return + */ + public ByteBuffer getBackingByteBuffer() { + return backingByteBuffer; + } + + /** + * @param buffer + */ + public void setBackingByteBuffer(ByteBuffer buffer) { + backingByteBuffer = buffer; + } + + /** + * @return + */ + public boolean getApplyRelativePosition() { + return applyRelativePosition; + } + + /** + * Only add the offset when we are fetching + * data from 'within' a record, ie: rkdata.get(i) + * as opposed to when we are traversing the entire collection + * of bytes as in WorkBookFactory.parse()... + *

        + * * @param b + */ + public void setApplyRelativePosition(boolean b) { + applyRelativePosition = b; + } } diff --git a/src/main/java/io/starter/formats/LEO/BlockFactory.java b/src/main/java/io/starter/formats/LEO/BlockFactory.java index 15cae0a..1d6122d 100644 --- a/src/main/java/io/starter/formats/LEO/BlockFactory.java +++ b/src/main/java/io/starter/formats/LEO/BlockFactory.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,136 +22,140 @@ */ package io.starter.formats.LEO; -import io.starter.toolkit.*; -import java.io.*; -import java.nio.*; -import java.util.*; +import io.starter.toolkit.ByteTools; +import java.io.ByteArrayInputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; -/** Dutifully produces LEO file Blocks. Complains not. - -*/ +/** + * Dutifully produces LEO file Blocks. Complains not. + */ public class BlockFactory { - /** get a new, empty Block - */ - public final static Block getPrototypeBlock(int type) { - Block retblock = null; - ByteBuffer dta = null; - switch (type) { - case Block.BIG : - dta = ByteBuffer.allocate(BIGBLOCK.SIZE); - retblock = new BIGBLOCK(); - break; - case Block.SMALL : - dta = ByteBuffer.allocate(SMALLBLOCK.SIZE); - retblock = new SMALLBLOCK(); - } - dta.order(ByteOrder.LITTLE_ENDIAN); - //for (int x = 0; x < dta.limit(); x++) - // dta.put((byte) - 1); - retblock.setBytes(dta); - return retblock; - } + /** + * get a new, empty Block + */ + public final static Block getPrototypeBlock(int type) { + Block retblock = null; + ByteBuffer dta = null; + switch (type) { + case Block.BIG: + dta = ByteBuffer.allocate(BIGBLOCK.SIZE); + retblock = new BIGBLOCK(); + break; + case Block.SMALL: + dta = ByteBuffer.allocate(SMALLBLOCK.SIZE); + retblock = new SMALLBLOCK(); + } + dta.order(ByteOrder.LITTLE_ENDIAN); + //for (int x = 0; x < dta.limit(); x++) + // dta.put((byte) - 1); + retblock.setBytes(dta); + return retblock; + } - /** transform the byte array into Block records. - */ - public final static Block[] getBlocksFromOutputStream(OutputStream bbuf, int blen, int type) { - int SIZE = 0; - switch (type) { - case Block.BIG : - SIZE = 512; - break; - case Block.SMALL : - SIZE = 64; - } - int sz = LEOFile.getSizeInBlocks(blen,SIZE); - - if (bbuf == null) - return null; - // int len = (blen-3) / SIZE; - int len = (blen) / SIZE; - int pos = 0, sizeDiff = 0,size = 0; + /** + * transform the byte array into Block records. + */ + public final static Block[] getBlocksFromOutputStream(OutputStream bbuf, int blen, int type) { + int SIZE = 0; + switch (type) { + case Block.BIG: + SIZE = 512; + break; + case Block.SMALL: + SIZE = 64; + } + int sz = LEOFile.getSizeInBlocks(blen, SIZE); - if ((len * SIZE) < blen) { - len++; - sizeDiff = (len * SIZE) - blen; - } - Block[] blockarr = new Block[len]; - ByteArrayInputStream ins = null; + if (bbuf == null) + return null; + // int len = (blen-3) / SIZE; + int len = (blen) / SIZE; + int pos = 0, sizeDiff = 0, size = 0; - // KSC: made a bit simpler upon padding situations ... - // get ALL blockVect (512 byte chunks of file) - for (int i = 0; i < len; i++) { - Block bbd = getPrototypeBlock(type); - byte[] bb = bbd.getBytes(); - int filepos = i * SIZE; - if (type==Block.SMALL) // smallblocks don't need file offset as they are allocated differently than bigblocks - filepos= 0; - - size = SIZE; - // make simpler: - if (blen-pos < size) - size=(blen-pos); - ins.read(bb,pos,size); - bbd.init(ByteBuffer.wrap(bb), i, filepos); - pos += SIZE; - blockarr[i] = bbd; - } - return blockarr; - } - - /** transform the byte array into Block records. - */ - public final static Block[] getBlocksFromByteArray(byte[] bbuf, int type) { - int SIZE = 0; - switch (type) { - case Block.BIG : - SIZE = 512; - break; - case Block.SMALL : - SIZE = 64; - } - if (bbuf == null) - return null; + if ((len * SIZE) < blen) { + len++; + sizeDiff = (len * SIZE) - blen; + } + Block[] blockarr = new Block[len]; + ByteArrayInputStream ins = null; + + // KSC: made a bit simpler upon padding situations ... + // get ALL blockVect (512 byte chunks of file) + for (int i = 0; i < len; i++) { + Block bbd = getPrototypeBlock(type); + byte[] bb = bbd.getBytes(); + int filepos = i * SIZE; + if (type == Block.SMALL) // smallblocks don't need file offset as they are allocated differently than bigblocks + filepos = 0; + + size = SIZE; + // make simpler: + if (blen - pos < size) + size = (blen - pos); + ins.read(bb, pos, size); + bbd.init(ByteBuffer.wrap(bb), i, filepos); + pos += SIZE; + blockarr[i] = bbd; + } + return blockarr; + } + + /** + * transform the byte array into Block records. + */ + public final static Block[] getBlocksFromByteArray(byte[] bbuf, int type) { + int SIZE = 0; + switch (type) { + case Block.BIG: + SIZE = 512; + break; + case Block.SMALL: + SIZE = 64; + } + if (bbuf == null) + return null; // int sz = LEOFile.getSizeInBlocks(bbuf.length,SIZE); - - int len = (bbuf.length) / SIZE; - int pos = 0, sizeDiff = 0,size = 0; - if ((len * SIZE) < bbuf.length) { - len++; // PAD - this most usually hits when called from buildSSAT - sizeDiff = (len * SIZE) - bbuf.length; - byte[] bb= new byte[sizeDiff]; - bbuf= ByteTools.append(bb, bbuf); - } - Block[] blockarr = new Block[len]; - - int start= 1; // for BigBlocks, skip 1st block, for small - if (type==Block.SMALL) - start= 0; - // get ALL blockVect (512 byte chunks of file) - for (int i = 0; i < len; i++) { - Block bbd = getPrototypeBlock(type); - byte[] bb = bbd.getBytes(); + int len = (bbuf.length) / SIZE; + int pos = 0, sizeDiff = 0, size = 0; + + if ((len * SIZE) < bbuf.length) { + len++; // PAD - this most usually hits when called from buildSSAT + sizeDiff = (len * SIZE) - bbuf.length; + byte[] bb = new byte[sizeDiff]; + bbuf = ByteTools.append(bb, bbuf); + } + Block[] blockarr = new Block[len]; + + int start = 1; // for BigBlocks, skip 1st block, for small + if (type == Block.SMALL) + start = 0; + // get ALL blockVect (512 byte chunks of file) + for (int i = 0; i < len; i++) { + Block bbd = getPrototypeBlock(type); + byte[] bb = bbd.getBytes(); /* int filepos = (i + start) * SIZE; if (type==Block.SMALL) // smallblocks don't need file offset as they are allocated differently than bigblocks filepos= 0; -*/ - size = SIZE; - // make it simpler: - if (bbuf.length-pos < size) - size= (bbuf.length-pos); // account for leftovers (Block padding) +*/ + size = SIZE; + // make it simpler: + if (bbuf.length - pos < size) + size = (bbuf.length - pos); // account for leftovers (Block padding) /*if (i + start == len) { size -= sizeDiff; // account for leftovers (Block padding) }*/ - // ExcelTools.benchmark("BlockFactory initting new Block@: " + pos + " sz: " +size + " len:" + bbuf.length); - System.arraycopy(bbuf, pos, bb, 0, size); - bbd.init(ByteBuffer.wrap(bb), i, 0); // THIS IS A BYTEARRAY ALL OFFSETS ARE 0 filepos); - pos += size; - blockarr[i] = bbd; - } - return blockarr; - } + // ExcelTools.benchmark("BlockFactory initting new Block@: " + pos + " sz: " +size + " len:" + bbuf.length); + System.arraycopy(bbuf, pos, bb, 0, size); + bbd.init(ByteBuffer.wrap(bb), i, 0); // THIS IS A BYTEARRAY ALL OFFSETS ARE 0 filepos); + pos += size; + blockarr[i] = bbd; + } + return blockarr; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/BlockImpl.java b/src/main/java/io/starter/formats/LEO/BlockImpl.java index 194eae4..52724fe 100644 --- a/src/main/java/io/starter/formats/LEO/BlockImpl.java +++ b/src/main/java/io/starter/formats/LEO/BlockImpl.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,310 +22,335 @@ */ package io.starter.formats.LEO; -import io.starter.toolkit.*; -import java.util.*; -import java.nio.*; -import java.io.*; +import io.starter.toolkit.CompatibleVectorHints; +import io.starter.toolkit.Logger; +import java.io.OutputStream; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.List; -/** LEO File Block Information Record - - These blocks of data contain information related to the - LEO file format data blocks. - - -*/ + +/** + * LEO File Block Information Record + *

        + * These blocks of data contain information related to the + * LEO file format data blocks. + */ public abstract class BlockImpl - implements io.starter.formats.LEO.Block, CompatibleVectorHints, Serializable{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4833713921208834278L; - /*allows the block to be populated with a byte array + implements io.starter.formats.LEO.Block, CompatibleVectorHints, Serializable { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4833713921208834278L; + /*allows the block to be populated with a byte array rather than just a bytebuffer, easing debugging **/ boolean DEBUG = false; - - /** methods from CompatibleVectorHints - */ - transient int recordIdx = -1, lastidx = -1; - transient ByteBuffer data = null; // new byte[SIZE]; - private List blockvec = null; - - // implement iterator for use in - // chaining blocks - public Block nextblock = null; - private boolean isXBAT = false; - - int originalidx, originalpos; - boolean isBBDepotBlock = false; - boolean isSBDepotBlock = false; - boolean isSpecialBlock = false; - private boolean initialized = false; - private boolean streamed = false; - public Storage mystore = null; - - - public void close() { - if (blockvec!=null) { - blockvec.clear(); - blockvec= null; - } - if (nextblock!=null && nextblock!=this) { - nextblock= null; - } - mystore= null; - if (data!=null) { - data.clear(); - data= null; - } - } - - /** return the ByteBuffer for this BLOCK - */ - public ByteBuffer getByteBuffer() { - return data; - } - - /** Write the entire bytes directly to out - * - * - * @see io.starter.formats.LEO.Block#writeBytes(java.io.OutputStream) - */ - public void writeBytes(OutputStream out) { - try{ - out.write(getBytes()); - }catch(Exception exp) { - Logger.logErr("BlockImpl.writeBytes failed.",exp); - } - } - - - //byte[] delbytes = null; - /** return the byte Array for this BLOCK - */ - public byte[] getBytes(int start, int end) { - //if (delbytes == null) delbytes = getBytes(); - int SIZE = end - start; - if ((end) > this.getBlockSize()) - throw new RuntimeException("WARNING: BlockImpl.getBytes(): read position > block size:"+SIZE+start); - - byte[] ret = new byte[SIZE]; - int capcheck = data.capacity(); - // TODO: track why this is occurring - if (capcheck <= SIZE) - originalpos = 0; - // CAN HAPPEN ON OUT-OF-SPEC FILES whom have last block size < 512 - if (capcheck < SIZE+originalpos) - SIZE= capcheck-originalpos; - try { - start+=originalpos; - data.position(start); - data.get(ret, 0, SIZE); - } catch (Exception e) { - Logger.logWarn("BlockImpl.getBytes() start: "+ start + " size: "+SIZE+": " + e); - } - return ret; - } - - public int getBlockSize() { - if (this.getBlockType() == BIG) - return BIGBLOCK.SIZE; - else if (this.getBlockType() == SMALL) - return SMALLBLOCK.SIZE; - else - return 0; - } - - /** return the byte Array for this BLOCK - */ - public byte[] getBytes() { - int SIZE = 0; - if (this.getBlockType() == BIG) - SIZE = BIGBLOCK.SIZE; - else if (this.getBlockType() == SMALL) - SIZE = SMALLBLOCK.SIZE; - byte[] ret = new byte[SIZE]; - - int capcheck = data.capacity(); - if (capcheck<=originalpos) // why is this hitting???????? - originalpos= 0; - if (capcheck < SIZE+originalpos) - SIZE= capcheck-originalpos; // CAN HAPPEN ON OUT-OF-SPEC FILES whom have last block size < 512 - try { - data.position(originalpos); - data.get(ret, 0, SIZE); - } catch (Exception e) { - Logger.logWarn("BlockImpl.getBytes(0,"+SIZE+"): " + e); - } - return ret; - } - - /** set the data bytes on this Block - */ - public void setBytes(ByteBuffer b) { - data = b; - //if(DEBUG) { - // Logger.logInfo("Debugging turned on in BlockImpl.setBytes"); - //delbytes = getBytes(); - //} - } - - /** provide a hint to the CompatibleVector - about this objects likely position. - */ - public int getRecordIndexHint() { - return recordIdx; - } - - /** set index information about this - objects likely position. - */ - public void setRecordIndexHint(int i) { - recordIdx = i; - } - - /** link to the vector of blocks for the storage - */ - public void setBlockVector(List v) { - blockvec = v; - } - - /** get the index of this Block in the storage - Vector - */ - public int getBlockIndex() { - if (blockvec == null) - return this.recordIdx; - return blockvec.indexOf(this); - } - - /** set the original BB position in the file - */ - public void setOriginalIdx(int x) { - originalidx = x; - } - public void setNextBlock(Block b) { - nextblock = b; + + /** + * methods from CompatibleVectorHints + */ + transient int recordIdx = -1, lastidx = -1; + transient ByteBuffer data = null; // new byte[SIZE]; + private List blockvec = null; + + // implement iterator for use in + // chaining blocks + public Block nextblock = null; + private boolean isXBAT = false; + + int originalidx, originalpos; + boolean isBBDepotBlock = false; + boolean isSBDepotBlock = false; + boolean isSpecialBlock = false; + private boolean initialized = false; + private boolean streamed = false; + public Storage mystore = null; + + + public void close() { + if (blockvec != null) { + blockvec.clear(); + blockvec = null; + } + if (nextblock != null && nextblock != this) { + nextblock = null; + } + mystore = null; + if (data != null) { + data.clear(); + data = null; + } + } + + /** + * return the ByteBuffer for this BLOCK + */ + public ByteBuffer getByteBuffer() { + return data; + } + + /** + * Write the entire bytes directly to out + * + * @see io.starter.formats.LEO.Block#writeBytes(java.io.OutputStream) + */ + public void writeBytes(OutputStream out) { + try { + out.write(getBytes()); + } catch (Exception exp) { + Logger.logErr("BlockImpl.writeBytes failed.", exp); + } + } + + + //byte[] delbytes = null; + + /** + * return the byte Array for this BLOCK + */ + public byte[] getBytes(int start, int end) { + //if (delbytes == null) delbytes = getBytes(); + int SIZE = end - start; + if ((end) > this.getBlockSize()) + throw new RuntimeException("WARNING: BlockImpl.getBytes(): read position > block size:" + SIZE + start); + + byte[] ret = new byte[SIZE]; + int capcheck = data.capacity(); + // TODO: track why this is occurring + if (capcheck <= SIZE) + originalpos = 0; + // CAN HAPPEN ON OUT-OF-SPEC FILES whom have last block size < 512 + if (capcheck < SIZE + originalpos) + SIZE = capcheck - originalpos; + try { + start += originalpos; + data.position(start); + data.get(ret, 0, SIZE); + } catch (Exception e) { + Logger.logWarn("BlockImpl.getBytes() start: " + start + " size: " + SIZE + ": " + e); + } + return ret; + } + + public int getBlockSize() { + if (this.getBlockType() == BIG) + return BIGBLOCK.SIZE; + else if (this.getBlockType() == SMALL) + return SMALLBLOCK.SIZE; + else + return 0; + } + + /** + * return the byte Array for this BLOCK + */ + public byte[] getBytes() { + int SIZE = 0; + if (this.getBlockType() == BIG) + SIZE = BIGBLOCK.SIZE; + else if (this.getBlockType() == SMALL) + SIZE = SMALLBLOCK.SIZE; + byte[] ret = new byte[SIZE]; + + int capcheck = data.capacity(); + if (capcheck <= originalpos) // why is this hitting???????? + originalpos = 0; + if (capcheck < SIZE + originalpos) + SIZE = capcheck - originalpos; // CAN HAPPEN ON OUT-OF-SPEC FILES whom have last block size < 512 + try { + data.position(originalpos); + data.get(ret, 0, SIZE); + } catch (Exception e) { + Logger.logWarn("BlockImpl.getBytes(0," + SIZE + "): " + e); + } + return ret; + } + + /** + * set the data bytes on this Block + */ + public void setBytes(ByteBuffer b) { + data = b; + //if(DEBUG) { + // Logger.logInfo("Debugging turned on in BlockImpl.setBytes"); + //delbytes = getBytes(); + //} + } + + /** + * provide a hint to the CompatibleVector + * about this objects likely position. + */ + public int getRecordIndexHint() { + return recordIdx; + } + + /** + * set index information about this + * objects likely position. + */ + public void setRecordIndexHint(int i) { + recordIdx = i; + } + + /** + * link to the vector of blocks for the storage + */ + public void setBlockVector(List v) { + blockvec = v; + } + + /** + * get the index of this Block in the storage + * Vector + */ + public int getBlockIndex() { + if (blockvec == null) + return this.recordIdx; + return blockvec.indexOf(this); + } + + /** + * set the original BB position in the file + */ + public void setOriginalIdx(int x) { + originalidx = x; + } + + public void setNextBlock(Block b) { + nextblock = b; /* if (LEOFile.DEBUG) Logger.logInfo( "INFO: BlockImpl setNextBlock(): " + b.toString());*/ - } - - public boolean hasNext() { - if (nextblock != null) - return true; - return false; - } - - public Object next() { - return nextblock; - } - - public void remove() { - this.mystore.removeBlock(this); - nextblock = null; - } - - /** set the storage for this Block - */ - public void setStorage(Storage s) { - mystore = s; - } - - /** get the storage for this Block - */ - public Storage getStorage() { - return mystore; - } - - /** return the original position of this BIGBLOCK - */ - public int getOriginalIdx() { - return originalidx; - } - - /** returns the original BB pos - - public int getOriginalPos(){return this.originalpos;} - */ - /** returns whether this block has been - added to the output stream - */ - public boolean getStreamed() { - return streamed; - } - - /** sets whether this block has been - added to the output stream - */ - public void setStreamed(boolean b) { - streamed = b; - } - - /** set whether this Block has been read yet... - */ - public void setInitialized(boolean b) { - initialized = b; - } - - /** returns whether this Block has been read yet... - */ - public boolean getInitialized() { - return initialized; - } - - /** returns true if this is a Block Depot block - that needs to be ignored when reading byte storages - */ - public boolean getIsSpecialBlock() { - return isSpecialBlock; - } - - /** returns true if this is a Block Depot block - that needs to be ignored when reading byte storages - */ - public boolean getIsDepotBlock() { - return isBBDepotBlock; - } - - /** set to true if this is a Block Depot block - */ - public void setIsDepotBlock(boolean b) { - isSpecialBlock = b; - isBBDepotBlock = b; - } - - /** init the BIGBLOCK Data - */ - public void init(ByteBuffer d, int origidx, int origp) { - originalidx = origidx; - originalpos = origp; - this.setBytes(d); - } - - /** return the original position of this - BIGBLOCK record in the array of BIGBLOCKS - that make up the file. - */ - public int getOriginalPos() { - return originalpos; - } - - /** - * @return - */ - public boolean isXBAT() { - return isXBAT; - } - - /** - * true if ths block is represents an extra DIFAT sector - * @param b - */ - public void setIsExtraSector(boolean b) { - isXBAT = b; - } + } + + public boolean hasNext() { + return nextblock != null; + } + + public Object next() { + return nextblock; + } + + public void remove() { + this.mystore.removeBlock(this); + nextblock = null; + } + + /** + * set the storage for this Block + */ + public void setStorage(Storage s) { + mystore = s; + } + + /** + * get the storage for this Block + */ + public Storage getStorage() { + return mystore; + } + + /** + * return the original position of this BIGBLOCK + */ + public int getOriginalIdx() { + return originalidx; + } + + /** returns the original BB pos + + public int getOriginalPos(){return this.originalpos;} + */ + /** + * returns whether this block has been + * added to the output stream + */ + public boolean getStreamed() { + return streamed; + } + + /** + * sets whether this block has been + * added to the output stream + */ + public void setStreamed(boolean b) { + streamed = b; + } + + /** + * set whether this Block has been read yet... + */ + public void setInitialized(boolean b) { + initialized = b; + } + + /** + * returns whether this Block has been read yet... + */ + public boolean getInitialized() { + return initialized; + } + + /** + * returns true if this is a Block Depot block + * that needs to be ignored when reading byte storages + */ + public boolean getIsSpecialBlock() { + return isSpecialBlock; + } + + /** + * returns true if this is a Block Depot block + * that needs to be ignored when reading byte storages + */ + public boolean getIsDepotBlock() { + return isBBDepotBlock; + } + + /** + * set to true if this is a Block Depot block + */ + public void setIsDepotBlock(boolean b) { + isSpecialBlock = b; + isBBDepotBlock = b; + } + + /** + * init the BIGBLOCK Data + */ + public void init(ByteBuffer d, int origidx, int origp) { + originalidx = origidx; + originalpos = origp; + this.setBytes(d); + } + + /** + * return the original position of this + * BIGBLOCK record in the array of BIGBLOCKS + * that make up the file. + */ + public int getOriginalPos() { + return originalpos; + } + + /** + * @return + */ + public boolean isXBAT() { + return isXBAT; + } + + /** + * true if ths block is represents an extra DIFAT sector + * + * @param b + */ + public void setIsExtraSector(boolean b) { + isXBAT = b; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/FileBuffer.java b/src/main/java/io/starter/formats/LEO/FileBuffer.java index f7f879a..e7413d2 100644 --- a/src/main/java/io/starter/formats/LEO/FileBuffer.java +++ b/src/main/java/io/starter/formats/LEO/FileBuffer.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,14 +27,18 @@ * Window - Preferences - Java - Code Style - Code Templates */ package io.starter.formats.LEO; -import java.io.*; -import java.nio.*; -import java.nio.channels.FileChannel; import io.starter.toolkit.TempFileManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; + /** - * * TODO To change the template for this generated type comment go to * Window - Preferences - Java - Code Style - Code Templates */ @@ -44,93 +48,94 @@ public class FileBuffer { private File tempfile = null; FileChannel channel = null; FileInputStream input = null; - + public void close() - throws IOException{ + throws IOException { input.close(); channel.close(); if (tempfile != null) { - tempfile.deleteOnExit(); + tempfile.deleteOnExit(); tempfile.delete(); } - tempfile= null; + tempfile = null; } - + /** - * + * */ public FileBuffer() { super(); // TODO Auto-generated constructor stub } - + public static FileBuffer readFile(String fpath) { try { File fx0 = new File(fpath); return readFile(fx0); } catch (Throwable e) { - throw new InvalidFileException("LEO FileBuffer.readFile() failed: "+ e.toString()); - } - } + throw new InvalidFileException("LEO FileBuffer.readFile() failed: " + e.toString()); + } + } - public static FileBuffer readFile(File fx0){ - try{ - FileBuffer fb = new FileBuffer(); - fb.input = new FileInputStream(fx0); - fb.channel = fb.input.getChannel(); - int fileLength = (int) fb.channel.size(); - // MappedByteBuffer - fb.buffer = - fb.channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength); - fb.buffer.order(ByteOrder.LITTLE_ENDIAN); - return fb; - } catch (Throwable e) { - throw new InvalidFileException("LEO FileBuffer.readFile() failed: "+ e.toString()); - } + public static FileBuffer readFile(File fx0) { + try { + FileBuffer fb = new FileBuffer(); + fb.input = new FileInputStream(fx0); + fb.channel = fb.input.getChannel(); + int fileLength = (int) fb.channel.size(); + // MappedByteBuffer + fb.buffer = + fb.channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength); + fb.buffer.order(ByteOrder.LITTLE_ENDIAN); + return fb; + } catch (Throwable e) { + throw new InvalidFileException("LEO FileBuffer.readFile() failed: " + e.toString()); + } } - + //TODO: reimplement temp files and deal with cleanup -jm - public static FileBuffer readFileUsingTemp(String fpath) { + public static FileBuffer readFileUsingTemp(String fpath) { return readFileUsingTemp(new File(fpath)); - } - - public static FileBuffer readFileUsingTemp(File fx0){ - try{ - FileBuffer fb = new FileBuffer(); - // create Temp file and populate - fb.tempfile = TempFileManager.createTempFile("LEOFile_",".tmp"); - fb.tempfile.delete(); - - FileInputStream input0 = new FileInputStream(fx0); - FileChannel channel0 = input0.getChannel(); - FileOutputStream output0 = new FileOutputStream(fb.tempfile); - FileChannel channel1 = output0.getChannel(); - channel0.transferTo(0,fx0.length(),channel1); - - channel0.close(); - channel1.close(); - input0.close(); - output0.close(); - - fb.input = new FileInputStream(fb.tempfile); - fb.channel = fb.input.getChannel(); - int fileLength = (int) fb.channel.size(); - // MappedByteBuffer - fb.buffer = - fb.channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength); - fb.buffer.order(ByteOrder.LITTLE_ENDIAN); - return fb; - } catch (Throwable e) { - throw new InvalidFileException("LEO FileBuffer.readFile() failed: "+ e.toString()); - } } - + + public static FileBuffer readFileUsingTemp(File fx0) { + try { + FileBuffer fb = new FileBuffer(); + // create Temp file and populate + fb.tempfile = TempFileManager.createTempFile("LEOFile_", ".tmp"); + fb.tempfile.delete(); + + FileInputStream input0 = new FileInputStream(fx0); + FileChannel channel0 = input0.getChannel(); + FileOutputStream output0 = new FileOutputStream(fb.tempfile); + FileChannel channel1 = output0.getChannel(); + channel0.transferTo(0, fx0.length(), channel1); + + channel0.close(); + channel1.close(); + input0.close(); + output0.close(); + + fb.input = new FileInputStream(fb.tempfile); + fb.channel = fb.input.getChannel(); + int fileLength = (int) fb.channel.size(); + // MappedByteBuffer + fb.buffer = + fb.channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength); + fb.buffer.order(ByteOrder.LITTLE_ENDIAN); + return fb; + } catch (Throwable e) { + throw new InvalidFileException("LEO FileBuffer.readFile() failed: " + e.toString()); + } + } + /** * @return Returns the buffer. */ public ByteBuffer getBuffer() { return buffer; } + /** * @param buffer The buffer to set. */ diff --git a/src/main/java/io/starter/formats/LEO/InvalidFileException.java b/src/main/java/io/starter/formats/LEO/InvalidFileException.java index cfbc662..016809c 100644 --- a/src/main/java/io/starter/formats/LEO/InvalidFileException.java +++ b/src/main/java/io/starter/formats/LEO/InvalidFileException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,24 +22,23 @@ */ package io.starter.formats.LEO; -/** This Exception is thrown whenever a LEOFile cannot be parsed or contains invalid data. - - +/** + * This Exception is thrown whenever a LEOFile cannot be parsed or contains invalid data. */ public class InvalidFileException extends RuntimeException { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2547604121903676738L; - private String err = "Not a valid OLE File."; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2547604121903676738L; + private String err = "Not a valid OLE File."; - public InvalidFileException(String er) { - this.err = er; - } + public InvalidFileException(String er) { + this.err = er; + } - public String toString() { - return "InvalidFileException: " + err; - } + public String toString() { + return "InvalidFileException: " + err; + } } diff --git a/src/main/java/io/starter/formats/LEO/LEOFile.java b/src/main/java/io/starter/formats/LEO/LEOFile.java index 47fbe20..bf7fabb 100644 --- a/src/main/java/io/starter/formats/LEO/LEOFile.java +++ b/src/main/java/io/starter/formats/LEO/LEOFile.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,1342 +29,1353 @@ */ package io.starter.formats.LEO; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; +import io.starter.formats.XLS.WorkBookException; +import io.starter.toolkit.*; + +import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; -import java.util.Vector; +import java.util.*; -import io.starter.formats.XLS.WorkBookException; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.JFileWriter; -import io.starter.toolkit.Logger; -import io.starter.toolkit.ResourceLoader; -import io.starter.toolkit.TempFileManager; - -/** LEOFile is an archive format compatible with other popular archive formats such as OLE. - - It contains multiple files or "Storages" which can - subsequently contain data in popular desktop application formats. - - -*/ +/** + * LEOFile is an archive format compatible with other popular archive formats such as OLE. + *

        + * It contains multiple files or "Storages" which can + * subsequently contain data in popular desktop application formats. + */ public class LEOFile implements Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2760792940329331096L; - public final static int MAXDIFATLEN = 109; // maximum - // DIFATLEN - // = - // 109; - // if - // more - // sectors - // are - // needed - // goes - // into - // extraDIFAT - public final static int IDXBLOCKSIZE = 128; // number - // of - // indexes - // that - // can - // be - // stored - // in - // 1 - // block - public final static boolean DEBUG = false; - public int DEBUGLEVEL = 0; - public static int actualOutput = 0; - private List bigBlocks; - private boolean readok = false; - private LEOHeader header = null; - private StorageTable directories; - private FileBuffer fb = null; - String fileName = "New Spreadsheet"; - byte[] encryptionStorageOverage = null; - boolean encryptedXLSX = false; - - // TODO: fix temp file issues and implement shutdown cleanup - // -- currently no way to delete -jm - - public LEOHeader getHeader() { - return header; - } - - /** Close the underlying IO streams for this LEOFile. - * - * Also deletes the temp file. - * - * @throws Exception - */ - public void close() throws IOException { - if (fb != null) - fb.close(); - // KSC: close out other object refs - fb = null; - // header.getBytes().clear(); - header = null;// new LEOHeader(); - if (directories != null) { - directories.close(); - directories = null; - } - if (bigBlocks != null) { - for (int i = 0; i < bigBlocks.size(); i++) { - BlockImpl b = (BlockImpl) bigBlocks.get(i); - b.close(); - } - bigBlocks.clear(); - } - bigBlocks = null; - // FAT= null; - } - - /** - * just closes the filebuffer withut clearing out buffers and storage tables - */ - public void closefb() throws IOException { - if (fb != null) - fb.close(); - fb = null; - } - - public void shutdown() { - try { - this.close(); - } catch (Exception e) { - if (DEBUGLEVEL > 0) - Logger.logWarn("could not close workbook cleanly." + e); - } - } - - /** a new LEO file containing LEO archive entries - - @param String a file path containing a valid LEOfile - */ - public LEOFile(String fname) { - if (fname.indexOf(".ser") > -1) { - this.initFromPrototype(fname); - return; - } - this.fileName = fname; - fb = LEOFile.readFile(fname); - this.initWrapper(fb.getBuffer()); - } - - /** - * Create a leo file from a prototype string/path - * - * PROTOTYPE_LEO_ENCRYPTED - * - * @param fname - */ - private void initFromPrototype(String fname) { - try { - byte[] b = ResourceLoader.getBytesFromJar(fname); - if (b == null) { - throw new io.starter.formats.XLS.WorkBookException( - "Required Class files not on the CLASSPATH. Check location of .jar file and/or jarloc System property.", - WorkBookException.LICENSING_FAILED); - } - ByteBuffer bbf = ByteBuffer.wrap(b); - bbf.order(ByteOrder.LITTLE_ENDIAN); - this.initWrapper(bbf); - } catch (Exception e) { - throw new InvalidFileException( - "WorkBook could not be instantiated: " + e.toString()); - } - } - - /** Instantiate a Leo file from an encrypted document. We currently have a hack - * in place for encrypted documents due to their having truncated bigblocks that our - * interface does not correctly handle. - * - @param a file containing a valid LEOfile (XLS BIFF8) - @param whether to use a temp file - @param if the file is encrypted xlsx format - */ - public LEOFile(File fpath, boolean usetempfile, boolean encryptedXLSX) { - this.encryptedXLSX = encryptedXLSX; - this.fileName = fpath.getAbsolutePath(); - fb = LEOFile.readFile(fpath, usetempfile); - this.initWrapper(fb.getBuffer()); - } - - /** Checks whether the given byte array starts with the LEO magic number. - */ - public static boolean checkIsLEO(byte[] data, int count) { - if (count < LEOHeader.majick.length) - return false; - for (int idx = 0; idx < LEOHeader.majick.length; idx++) - if (data[idx] != LEOHeader.majick[idx]) - return false; - return true; - } - - /** a new LEO file containing LEO archive entries - * - @param a file containing a valid LEOfile (XLS BIFF8) - @param whether to use a temp file - */ - public LEOFile(File fpath, boolean usetempfile) { - this.fileName = fpath.getAbsolutePath(); - fb = LEOFile.readFile(fpath, usetempfile); - this.initWrapper(fb.getBuffer()); - } - - /** a new LEO file containing LEO archive entries - @param a file containing a valid LEOfile (XLS BIFF8) - */ - public LEOFile(File fpath, int DEBUGLEVEL) { - this.fileName = fpath.getAbsolutePath(); - this.DEBUGLEVEL = DEBUGLEVEL; - fb = LEOFile.readFile(fpath); - this.initWrapper(fb.getBuffer()); - } - - /** a new LEO file containing LEO archive entries - @param byte[] a byte array containing a valid LEOfile - */ - public LEOFile(ByteBuffer bytebuff) { - this.initWrapper(bytebuff); - } - - /** - * This is just removing some duplicate code from our constructors - * - * We should add some exception handling in here! - */ - public void initWrapper(ByteBuffer bytebuff) { - int[] FAT = this.init(bytebuff); - if (FAT != null) { - directories.initDirectories(bigBlocks, FAT); - // KSC: TESTING: XLS-97 - if (DEBUG || DEBUGLEVEL > 200) - directories.DEBUG(); - FAT = null; - readok = true; - } else - readok = false; - } - - public void clearAfterInit() { - this.bigBlocks.clear(); - } - - /** - * Reads in an encrypted LEO stream. May be able to remove this and use the standard - * constructor, we shall see. - * - * @param encryptedFile - */ - public void readEncryptedFile(File encryptedFile) { - fb = readFile(encryptedFile); - this.initWrapper(fb.getBuffer()); - } - - public String getFileName() { - return fileName; - } - - /** return whether the LEOFile contains a valid workbook - */ - public boolean hasWorkBook() { - Storage book; - try { - book = directories.getDirectoryByName("Workbook"); - } catch (StorageNotFoundException e) { - try { - book = directories.getDirectoryByName("Book"); - } catch (StorageNotFoundException e1) { - return false; - } - } - return true; - } - - /** return whether the LEOFile contains a valid doc - */ - public boolean hasDoc() { - if (readok) { - try { - Storage doc = this.directories - .getDirectoryByName("WordDocument"); - } catch (StorageNotFoundException e) { - return false; - } - return true; - } - return false; - } - - /** - * return whether the LEOFile contains the _SX_DB_CUR Pivot Table Cache storage (required for Pivot Table) - * @return - */ - public boolean hasPivotCache() { - if (readok) { - try { - Storage doc = this.directories.getDirectoryByName("_SX_DB_CUR"); - } catch (StorageNotFoundException e) { - return false; - } - return true; - } - return false; - } - - /** - * - */ - public LEOFile() { - super(); - } - - /** - * Create a LEOFile from an input stream. Unfortunately because of our byte backer - * we need to write to a temporary file in order to do this - * - * @param stream - */ - public LEOFile(InputStream stream) throws IOException { - File target = TempFileManager.createTempFile("ExtenXLS_temp", ".leo"); - JFileWriter.writeToFile(stream, target); - - this.fileName = target.getAbsolutePath(); - fb = LEOFile.readFile(target); - this.initWrapper(fb.getBuffer()); - target.deleteOnExit(); - target.delete(); - } - - public LEOFile(File fx) { - // TODO Auto-generated constructor stub - } - - /** calculate number of FAT blocks necessary to describe compound file - */ - final static int getNumFATSectors(int storageTotal) { - int nFAT = storageTotal * 4; - nFAT /= BIGBLOCK.SIZE; - float realnum = ((float) (storageTotal * 4) / BIGBLOCK.SIZE); - if ((realnum - nFAT) > 0 || (nFAT > MAXDIFATLEN)) { - nFAT++; - } - return nFAT; - } - - /** - * Creates all the necessary headers and associated storages/structures that, - * along with the actual workbook data, makes up the document - * - * Basic Structure - * 1st sector or block= Header + 1st 109 sector chains (==FAT===array of sector numbers) - * Location of FAT==DIFAT - * Header denotes sector of "Root Directory" which in turn denotes sectors of all other directories - * (Workbook is but one of several directories) - * Each directory denotes the start block of any associated data into the FAT (sector chain index) - * (if directory is >0 an <4096 in size, it's associated data is stored in miniStorage - * - * - * - * - * - * - * - * - * Ok, so this is a little odd, it looks as if it collects all of the storages, and handles - * all of the preprocessing necessary to create FAT etc. - * returns a list of all the storages, presumably to be handled by ByteStreamer. - * - * - * A little more information: - * A list of storages is generated. These storages do not include - * a) the workbook storage(!) which is meant to be the first record written after receiving the byte array - * b) individual miniFAT storages, these are all combined into one container storage that is not represented in the header - * c) the LeoHeader, which is written to the output stream already - * - * - * It seems as if this method needs several things - * 1) better header comments on its functions - * 2) break out logic to private methods to keep overall functions understandable. - * 3) Handle file formats other than XLS (ie doc, encrypted xls, ppt, etc) - * 4) be clear about its usage to outputStream. Why only the 1 storage written, maybe none would be better? - * 5) probably throw an exception - * - * 6) WHAT IS WITH THE STORAGES THAT ARE NOT STORAGES? -- these are non-directory storages used to write out the bytes contained within in the correct order - * for the document. These non-directory storages include the FAT and the minFAT blocks and miniStream - * - * - * @param outputstream to write out - * @param book to write - * @param book bytes size - * @return storages to write out in streamer - */ - public synchronized List writeBytes(OutputStream out, int workbook_byte_size) { - /***** storages to be written - rebuild from saved storages and recreated workbook storage*/ - AbstractList storages = new Vector(); - // header variables - int numFATSectors = 0; // num FAT indexes - int rootstart = 0; // block position where root sector is located - int numExtraDIFATSectors = 0; // num extra sectors/blocks - int extraDIFATStart = -2; // extra sector start (-2 for none) - int numMiniFATSectors = 0; // num short sectors/miniFAT indexes - int miniFATStart = -2; // start block position of short sector container - // blocks (-2 for none) - int sbidxpos = -2; // start position of short sector idx chain (-2 for - // none) - int sbsz = 0; // size of short sector container - int[] DIFAT; // holds the sector id chains for the 1st 109 sector index - // blocks (DIFAT) - int[] extraDIFAT; // holds the sector id chains for any sector index - // blocks (DIFAT) > 109 - int numblocks = 1; // header is 1st - - // workbook directory - Storage book = null; - boolean isEncrypted = false; - try { - book = directories.getDirectoryByName("Workbook"); - } catch (StorageNotFoundException e3) { - try { - book = directories.getDirectoryByName("EncryptedPackage"); - isEncrypted = !true; - } catch (StorageNotFoundException e1) { - // this is an error state, ideally we would be throwing a - // (non-runtime) exception? - throw new InvalidFileException( - "Input LEO file not valid for output"); - } - } - - // root directory - RootStorage rootStore = null; - try { - rootStore = directories.getRootStorage(); - } catch (StorageNotFoundException e2) { - // this is an error state, ideally we would be throwing a - // (non-runtime) exception? - throw new InvalidFileException( - "Input LEO file not valid for output"); - } - - // get existing directories + store all except workbook + - // root (which are rebuilt) - Enumeration e = directories.getAllDirectories().elements(); - while (e.hasMoreElements()) { - Storage thisStore = (Storage) e.nextElement(); - if (thisStore != book && thisStore != rootStore) { // && - // thisStore!=encryptionInfo? - storages.add(thisStore); - if (thisStore.getBlockType() == Block.SMALL) { // count number - // of miniStream - // blocks - thisStore.miniStreamStorage = true; // TODO: investigate why - // this setting gets - // lost! - numMiniFATSectors += thisStore.getBlockVect().size(); - } - } - } - - // if have miniStream sectors, rebuild miniFAT index - // + convert mini blocks to one or more big blocks (=miniFAT - // container) - if (numMiniFATSectors > 0) { - Storage[] sbs = buildMiniFAT(storages, numMiniFATSectors); // returns - // two - // non-directory - // storages: - // miniStream - // Storage - // + - // miniFAT - // index - storages.add(sbs[0]); - numMiniFATSectors = sbs[1].getBlockVect().size(); // trap number of - // miniStream - // block indexes - storages.add(sbs[1]); - } - - // we need to count up the total block array - // count number of blocks: - // add blocks necessary for root storage - numblocks += Math.ceil(directories.directoryVector.size() / 4); // each - // 4 - // directories==1 - // block - // (512 - // byte - // sector) - // count Storage Blocks (except root and workbook) - for (int t = 0; t < storages.size(); t++) { - Storage nstr = (Storage) storages.get(t); // saved stores containt - // thier original blocks - // + padding block - // signifying end of - // sector/block - if (!nstr.miniStreamStorage) - numblocks += nstr.getSizeInBlocks(); - } - - // take current workbook byte size and calculate # bigblocks - book.miniStreamStorage = false; // if had any initially, now it's big - // blocks - - int workbook_idx_block_size = LEOFile - .getSizeInBlocks(workbook_byte_size, BIGBLOCK.SIZE); - workbook_idx_block_size = Math - .max(workbook_idx_block_size, getMinBlocks() - 1);// ensure - // minimum # - // blocks - numblocks += workbook_idx_block_size + 1;// to account for end of block - // sector - if (isEncrypted) { // Encrypted workbooks are already set to correct - // size - numblocks--; - workbook_idx_block_size--; - } - // Given amount of blocks to write, calculate id chain - // necessary to describe - numFATSectors = LEOFile.getNumFATSectors(numblocks); // get # of secids - // necessary to - // describe - // blocks - - // now include the FAT itself - // total number of Sectors(blocks)= number of Sectors + FAT - // chain block(s) - numFATSectors = getNumFATSectors(numFATSectors + numblocks); - int[] FAT = new int[IDXBLOCKSIZE * numFATSectors]; - for (int i = 0; i < FAT.length; i++) - FAT[i] = -1; // create byte array of -1's 128 bytes - total number - // of sector ids - - // allocate FAT array index - // important positions that will be recorded in the header - // or FAT/miniFAT - DIFAT = new int[Math.min(numFATSectors, MAXDIFATLEN)]; // 1 up to the - // 1st 109 - // sector index - // blocks used - // to describe - // this file - extraDIFAT = new int[Math.max(numFATSectors - MAXDIFATLEN, 0)]; // if - // necessary, - // sector - // index - // blocks - // in - // excess - // of - // 109 - - /************************ now start to lay out block positions of all directories' associated data ************************/ - /************************ note: order of directories in storages is order that they will be written out - * this order is indexed in the FAT via the block indexes used for each sector - * upon return, storages contains all the directories, their associated data, the FAT ... - * in the calling method, the workbook records (==associated data) is written first because - * the workbook storage is first in the FAT. - Eventually, want to change this so all other storages and the FAT are written first, then - the calling method only needs to write the workbook records. - **************************************************************************************************************************/ - // first start block index with workbook blocks (they will - // be written out in ByteStreamer before all other blocks - // except the header) - int blockpos = 0; // initial sector position - book.setStartBlock(blockpos); - for (int t = 0; t < workbook_idx_block_size; t++) { - FAT[blockpos++] = blockpos; - } - FAT[blockpos++] = -2; // end of sector flag - if (!isEncrypted) { - // this is a questionable line. It is setting the storage - // length to that of the padded bigblock. - // causes errors in encrypted files - book.setActualFileSize((workbook_idx_block_size + 1) - * BIGBLOCK.SIZE); - if (DEBUG) - Logger.logInfo("Workbook actual bytes: " - + book.getActualFileSize()); - } - - // now rest of "static" stores (summary, document summary, - // comp obj ...) - for (int t = 0; t < storages.size(); t++) { - Storage nstr = (Storage) storages.get(t); - if (!nstr.miniStreamStorage) { // miniStream blocks are handled - // separately - if (nstr.getName().equals("miniStream")) { // miniStream (short - // sector container) - miniFATStart = blockpos; // this goes in rootstore - // startblock to denote start of - // short sector container - sbsz = nstr.getActualFileSize(); // actual size of short - // sector (miniFAT) - // container - stored in - // root storage - } else if (nstr.getName().equals("miniFAT")) { // miniFAT (short - // sector) index - sbidxpos = blockpos; - } - nstr.setStartBlock(-2); // default, not linked to any blocks - // 20100325 KSC: since original blocks are kept for - // non-workbook storage, - // and since size is not under our control, keep original - // size - otherwise can error - // nstr.setActualFileSize(0); // default, 0 bytes - // record index of block position for each block of the - // storage - Block[] blks = nstr.getBlocks(); - if (blks != null) { - nstr.setStartBlock(blockpos); // start of block chain - for (int i = 0; i < blks.length - 1; i++) { // -1 to trap - // last block - // with special - // end-of-block - // flag (-2) - FAT[blockpos++] = blockpos; // start of this store's - // blocks as indexed in - // block index - } - FAT[blockpos++] = -2; // end of sector/block flag - // nstr.setActualFileSize((blks.length)*512); // set file - // size -- see above why we can't do this at this time - } - } - } - - // after other storages, add root store: - // must rebuild after info from above as root storage is - // built from from all other storages - rootStore.setStartBlock(miniFATStart); // root store start points to - // miniFAT container, or -2 if - // none - rootStore.setActualFileSize(sbsz); // 0 or miniFAT container size - // handle rootstorage blocks = data of all other directory - // storages - rootStore.setBytes(directories.rebuildRootStore()); // now rebuild - // rootstore from - // all other - // storages - rootstart = blockpos; - for (int i = 0; i < rootStore.getBlockVect().size() - 1; i++) { - FAT[blockpos++] = blockpos; - } - FAT[blockpos++] = -2; // end of sector/block flag - // add rootstore to list of blocks writing out - storages.add(rootStore); - - // mark position of FAT itself within blocks (==DIFAT) - // the 1st MAXDIFATLEN DIFAT indexes goes into header - for (int i = 0; i < DIFAT.length; i++) { - DIFAT[i] = blockpos + 1; // each pos is decremented in method below, - // so increment here - FAT[blockpos++] = -3; // mark loc of FAT with special value -3 - } - - // if any FAT sectors > MAX, goes into extra blocks - if (numFATSectors > MAXDIFATLEN) { // handle extra blocks - need extra - // blocks to store the indexes - for (int n = MAXDIFATLEN; n < numFATSectors; n++) { - extraDIFAT[n - MAXDIFATLEN] = blockpos + 1; - FAT[blockpos++] = -3; - } - for (int i = 0; i < (int) Math.ceil((numFATSectors - MAXDIFATLEN) - / (IDXBLOCKSIZE * 1.0)); i++) - FAT[blockpos++] = -4; // flag for MSAT or XBB - } - - // now that all the blocks referenced by storages such as - // workbook and document summary, - // plus blocks referenced by the root storage, - // transform the blockindex to blocks & input a phantom or - // special store so can write out - // have been indexed, store the index blocks themselves - Storage idxstore = new Storage(); - idxstore.setName("IDXStorage"); - Block[] FATSectors = getIDXBlocks(FAT); // convert each int to 4-bytes - // in sector/block format --> - // FAT Sectors - idxstore.setBlocks(FATSectors); // input the FAT Sectors to a - // non-directory store for later writing - storages.add(idxstore); // output FAT Sectors after all other blocks - - if (numFATSectors > MAXDIFATLEN) { - // now build and add the extra sectors necessary to store - // the FAT - extraDIFATStart = blockpos - (int) Math - .ceil((numFATSectors - MAXDIFATLEN) / (IDXBLOCKSIZE * 1.0)); - Storage xbbstore = buildExtraDIFAT(extraDIFAT, extraDIFATStart); // create - // block(s) - // from - // the - // extraDIFAT - numExtraDIFATSectors = xbbstore.getBlockVect().size(); - storages.add(xbbstore); - } - - // now that blocks have been accounted for and indexed and - // storages and all their blocks - // (except for workbook blocks) are stored, we can build and - // output the header block - // build header block: - - header = LEOHeader.getPrototype(DIFAT); // input secIdChain to header - // loc 76 - // start sector of short sector/miniFAT index chain or -2 if - // n/a - header.setMiniFATStart(sbidxpos); - // the block # where the storages begin - header.setRootStorageStart(rootstart); - // number of block indexes needed to describe 1st 109 - // sectors/blocks (= DIFAT) - header.setNumFATSectors(numFATSectors); - // number of block indexes needed to describe short - // sectors/miniFAT sectors if any - header.setNumMiniFATSectors(numMiniFATSectors); - // number of block indexes necessary to describe extra - // sectors if any - header.setNumExtraDIFATSectors(numExtraDIFATSectors); - // start of extra sector block index or -2 if n/a - header.setExtraDIFATStart(extraDIFATStart); - - // setup the header info... - if (!header.init()) - throw new RuntimeException("LEO File Header Not Initialized"); // invalid - // WorkBook - // File - - // return the storages - return storages; - } - - /** - * build Extra Storage Sector index (extra DIFAT) - * @param difatidx - indexes of blocks > 13952 (= 109 blocks described by DIFAT * 128 indexes in each block) - * @return - */ - private Storage buildExtraDIFAT(int[] difatidx, int xbbpos) { - // handle Extra blocks beyond the 109*128 blocks described - // by the regular DIFAT (109 limit) - if (difatidx != null) { - ArrayList outblocks = new ArrayList(); - byte[] xbytes = new byte[difatidx.length * 4]; - // convert the int difatidx to bytes - for (int i = 0; i < difatidx.length; i++) { - byte[] idx = ByteTools.cLongToLEBytes(difatidx[i] - 1); - System.arraycopy(idx, 0, xbytes, i * 4, 4); - } - int counter = 0; - for (int i = 0; i < xbytes.length;) { - ByteBuffer bl = BlockFactory.getPrototypeBlock(Block.BIG) - .getByteBuffer(); - int len = xbytes.length - i; - - // handle eob - if (len > BIGBLOCK.SIZE - 4) - len = BIGBLOCK.SIZE - 4; - bl.position(0); - bl.put(xbytes, i, len); - - // handle eob - counter++; - if (len == BIGBLOCK.SIZE - 4) - bl.putInt(xbbpos + counter); - - BIGBLOCK xbbBlock = new BIGBLOCK(); - xbbBlock.init(bl, 0, 0); - outblocks.add(xbbBlock); - - i += BIGBLOCK.SIZE - 4; - } - // create new storage so can be added to end - Storage xbbstore = new Storage(); - xbbstore.setName("XBBStore"); - Block[] xblx = new Block[outblocks.size()]; - outblocks.toArray(xblx); - xbbstore.setBlocks(xblx); - return xbbstore; - } - return null; - } - - /** - * Gather up all existing smallblocks from the existing storages - * and create contiguous bigblocks from it; since smallblocks are - * only 128 bytes long, 4 smallblocks fit into 1 bigblock - * while building the miniFAT Sectors,also build the miniFAT index - * which corresponds to it - * - * @param storages existing Storages (except workbook and root, which are handled separately) - * @param numsbs total number of miniFAT blocks - * @return Storage[] - two storage containers: 1 contains smallblocks, 2 contains smallblock index - */ - private Storage[] buildMiniFAT(List storages, int numsbs) { - // once more, now gathering the sbidx's (miniFAT index) - int[] sbidx = new int[(int) Math.ceil(numsbs / (IDXBLOCKSIZE * 1.0)) - * IDXBLOCKSIZE]; - byte[] smallblocks = new byte[0]; // sum up all short sectors, will be - // subsequently converted to big - // blocks - for (int i = 0; i < sbidx.length; i++) - sbidx[i] = -1; // init sbidx index - int z = 0; - for (int i = 0; i < storages.size(); i++) { - Storage thisStore = (Storage) storages.get(i); - if (thisStore.getBlockType() == Block.SMALL) { - // since these are static blocks we can copy original data - thisStore.setStartBlock(z); // set start position of miniFATs as - // may have changed - // keep length as that hasn't changed - for (int j = 0; j < thisStore.getBlockVect().size() - 1; j++) - sbidx[z++] = z; - sbidx[z++] = -2; // end of sector - smallblocks = ByteTools - .append(thisStore.getBytes(), smallblocks); - // 20100407 KSC: this screws up subsequent usages of book - // after writing ... see TestCreateNewName NPE - // thisStore.setBlocks(new Block[0]); - } - } - - // concatenate all miniFAT sectors to build the miniFAT or - // short sector container - // i.e. miniFAT blocks are concatenated and stored in big - // block(s): - Block[] smallBlocksToBig = BlockFactory - .getBlocksFromByteArray(smallblocks, Block.BIG); - Storage miniFATContainer = new Storage(); - miniFATContainer.setName("miniStream"); - miniFATContainer.setBlocks(smallBlocksToBig); - miniFATContainer.setActualFileSize(smallblocks.length); - smallblocks = null; // no need anymore - Storage miniFAT = new Storage(); - miniFAT.setName("miniFAT"); - Block[] sbIDX = getIDXBlocks(sbidx); // convert each int to 4-bytes in - // sector/block format --> BBDIX - miniFAT.setBlocks(sbIDX); - return new Storage[] { miniFATContainer, miniFAT }; - } - - /** create the idx from the actual locations - of the Blocks in the outblock array - */ - final static void initSmallBlockIndex(int[] newidx, Block b) { - while (b.hasNext() && !(b == (Block) b.next())) { - int origps = b.getBlockIndex(); - if (origps < 0) { - Logger.logWarn("WARNING: LEOFile Block Not In MINIFAT vector: " - + String.valueOf(b.getOriginalIdx())); - } else { - int newp = ((Block) b.next()).getBlockIndex(); - if (false) - Logger.logInfo("INFO: LEOFile Initializing block index: " - + origps + " val: " + newp + " idxlen: " - + newidx.length); - newidx[origps] = newp; - } - b = (Block) b.next(); - } - if (b.getBlockIndex() >= 0) - newidx[b.getBlockIndex()] = -2; // end of storage - } - - /** create the idx from the actual locations - of the Blocks in the outblock array - * - final static int initWBBlockIndex(int[] newidx, int sz) { - int start = 0; - for(int r=newidx.length-1;r>=0;r--) { - if(newidx[r]!=-1) { - start = r+1; - break; - } - } - for(int i=0;i0) - newidx[(start+sz-1)] = -2; // end of storage - else - newidx[(start+sz-1)] = -2; // end of storage - return start; - } - */ - - /** returns an empty new FAT which - accounts for the size of its own blocks - */ - final static int[] getEmptyDIFAT(int totblocks, int numFATSectors) { - - // allocate space in idx for FAT recs, StorageTable and for - // final -2 - int[] bbdi = new int[numFATSectors + totblocks + 1]; - for (int x = 0; x < bbdi.length; x++) - bbdi[x] = (byte) -1; - return bbdi; - } - - /** Create the array of BB Idx blocks - - */ - final static Block[] getIDXBlocks(int[] bbdidx) { - // step through and create index recs for all Blocks - byte[] b = new byte[bbdidx.length * 4]; - int bv = 0; - - // can we wrap t in an nio buffered and read? - - for (int t = 0; t < b.length/* 20100304 KSC: why? - 4 */;) { - byte[] bs = ByteTools.cLongToLEBytes(bbdidx[bv++]); - // for(int z=0;z<4;z++) - b[t++] = bs[0]; - b[t++] = bs[1]; - b[t++] = bs[2]; - b[t++] = bs[3]; - } - return BlockFactory.getBlocksFromByteArray(b, Block.BIG); - } - - /** get a handle to a Storage within this LEO file - */ - public Storage getStorageByName(String s) throws StorageNotFoundException { - return directories.getDirectoryByName(s); - } - - /** get all Directories in this LEO file - */ - public Storage[] getAllDirectories() { - CompatibleVector v = directories.getAllDirectories(); - Storage[] s = new Storage[v.size()]; - s = (Storage[]) v.toArray(s); - return s; - } - - /** - * return the Directory Array for this LEOFile - *
        holds all directories (storages and streams) in correct order - *
        Directories in turn can reference streams of data - * @return - */ - public StorageTable getDirectoryArray() { - return directories; - } - - /** Simply setting the byte size on the book storage - * - private void checkIfSmallBlockOutput(int blen) throws StorageNotFoundException { - Storage book; - try { - book = storageTable.getStorageByName("Workbook"); - }catch(StorageNotFoundException e) { - book = storageTable.getStorageByName("EncryptedPackage"); - } - - if (blen >= StorageTable.BIGSTORAGE_SIZE) { - if (book.getBlockType() == Block.SMALL) { - Logger.logWarn( - "WARNING: Modifying SmallBlock Workbook: "+ this.getFileName() + - ". Modifying SmallBlock-type Workbooks " + - " can cause corrupt output files. See: " + - "http://extentech.com/uimodules/docs/docs_detail.jsp?meme_id=195"); - throw new WorkBookException("SmallBlock File Detected reading: "+this.getFileName(), WorkBookException.SMALLBLOCK_FILE); - // this.convertSBtoBB(book); - } - book.setActualFileSize(blen); - } else - book.setActualFileSize(StorageTable.BIGSTORAGE_SIZE); // set at the minimum - } - */ - - /** get the bytes of the DOC file. - */ - public BlockByteReader getDocBlockBytes() { - // get the bytes for the WordDocument substream only - Storage doc; - try { - doc = directories.getDirectoryByName("WordDocument"); - } catch (StorageNotFoundException e) { - throw new InvalidFileException( - "InvalidFileException: Not Word '97 or later version. Unsupported file format."); - } - return doc.getBlockReader(); - } - - /** get the bytes of the XLS file. - */ - public BlockByteReader getXLSBlockBytes() { - // get the bytes for the Workbook substream only - Storage book; - try { - book = directories.getDirectoryByName("Workbook"); - } catch (StorageNotFoundException e) { - try { - book = directories.getDirectoryByName("Book"); - } catch (StorageNotFoundException e1) { - Logger.logInfo("Not Excel '97 (BIFF8) or later version. Unsupported file format."); - throw new InvalidFileException( - "InvalidFileException: Not Excel '97 (BIFF8) or later version. Unsupported file format."); - } - } - return book.getBlockReader(); - } - - /** read LEO file information from header. - */ - public synchronized int[] init(ByteBuffer bbuf) { - int pos = 0; - CompatibleVector FATSectors = new CompatibleVector(); // one or more - // sectors which - // hold the FAT - // (File - // Allocation - // Table or - // indexes into - // the sectors) - - bigBlocks = new ArrayList(); - int len = bbuf.limit() / BIGBLOCK.SIZE; - // get ALL BIGBLOCKS (512 byte chunks of file) - if (DEBUG) - Logger.logInfo("\nINIT: Total Number of bigblocks: " + len); - for (int i = 0; i < len; i++) { - BIGBLOCK bbd = new BIGBLOCK(); - bbd.init(bbuf, i, pos); - pos += BIGBLOCK.SIZE; - this.bigBlocks.add(bbd); - } - - // Encrypted workbooks can have random overages. - // not ideal, but store this value in LEO and get from the - // storage if its named 'EncryptedPackage' - int encryptionStorageOverageLen = (bbuf.limit() % BIGBLOCK.SIZE); - if (encryptionStorageOverageLen > 0) { - int filepos = len * BIGBLOCK.SIZE; - if (this.encryptedXLSX) { - bbuf.position(filepos); - encryptionStorageOverage = new byte[encryptionStorageOverageLen]; - bbuf.get(encryptionStorageOverage, 0, encryptionStorageOverage.length); - } else { - BIGBLOCK bbd = new BIGBLOCK(); - bbd.init(bbuf, len, pos); // filepos); - pos += encryptionStorageOverageLen; - this.bigBlocks.add(bbd); - } - } - - /***** Read in the file header */ - // header holds directory start sector and - // FAT/miniFAT/extraFAT or Sector Chain index info - header = new LEOHeader(); // read the LEO file header rec - - // is a valid WorkBook File? - if (!header.init(bbuf)) - throw new InvalidFileException( - this.getFileName() + " is not a valid OLE File."); - - if (DEBUG) { - Logger.logInfo("Header: "); - Logger.logInfo("numbFATSectors: " + header.getNumFATSectors()); - Logger.logInfo("numMiniFATSectors: " - + header.getNumMiniFATSectors()); - Logger.logInfo("numbExtraDIFATSectors: " - + header.getNumExtraDIFATSectors()); - Logger.logInfo("rootstart: " + header.getRootStartPos()); - Logger.logInfo("miniFATStart: " + header.getMiniFATStart()); - } - BIGBLOCK headerblock = (BIGBLOCK) bigBlocks.get(0); - headerblock.setInitialized(true); - - /***** Read in the FAT sectors - the sectors or blocks used for the FAT (==Sector Chain Index or File Allocation Table) */ - FATSectors = getFATSectors(); - - /***** turn the FAT Sectors into the FAT or Sector Index Chain */ - byte[] blx = LEOFile.getBytes(FATSectors); - int[] FAT = null; - FAT = LEOFile.readFAT(blx); - blx = null; // done - - if (DEBUG) - Logger.logInfo("FAT:\n" + Arrays.toString(FAT)); - - // if (DEBUG) - // StorageTable.writeitout(FATSectors, "FAT.dat"); - - /***** Read the Directory blocks */ - directories = new StorageTable(); - directories.init(bbuf, header, bigBlocks, FAT); - return FAT; - } - - /** - * create a FileBuffer from a file, use system property to determine - * whether to use a temp file. - * - * @param file containing XLS bytes - * @param fpath - * @return - */ - public final static FileBuffer readFile(File fpath) { - boolean usetempfile = false; - String tmpfu = (String) System.getProperties() - .get("io.starter.formats.LEO.usetempfile"); - - if (tmpfu != null) - usetempfile = tmpfu.equalsIgnoreCase("true"); - - return readFile(fpath, usetempfile); - } - - /** - * create a FileBuffer from a file, use boolean parameter to determine - * whether to use a temp file. - * - * @param file containing XLS bytes - * @param whether to use a tempfile - * @return - */ - public final static FileBuffer readFile(File fpath, boolean usetempfile) { - if (usetempfile) - return FileBuffer.readFileUsingTemp(fpath); - return FileBuffer.readFile(fpath); - } - - /** read in a WorkBook ByteBuffer from a file path. - - * from here on out, we're reading pointers to the bytes on disk - * - * access data directly on disk through the ByteBuffer. - * - *
        By default, OpenXLS will lock open WorkBook files, to close the file after parsing and work with a - temporary file instead, use the following setting: -

        - System.getProperties().put("io.starter.formats.LEO.usetempfile", "true"); -

        - IMPORTANT NOTE: You will need to clean up temp files occasionally in your user directory (temp filenames will begin - with "ExtenXLS_".) -

        - * - **/ - public final static FileBuffer readFile(String fpath) { - return LEOFile.readFile(new File(fpath)); - } - - /** returns the table of int locations - in the file for the BIGBLOCK linked list. - */ - public final static int[] readFAT(List vect) { - byte[] data = getBytes(vect); - return readFAT(data); - } - - /** returns the table of int locations - in the file for the BIGBLOCK linked list. - */ - private final static int[] readFAT(byte[] data) { - int[] bbs = new int[data.length / 4]; - int pos = 0; - for (int i = 0; i < data.length;) { - bbs[pos++] = ByteTools - .readInt(data[i++], data[i++], data[i++], data[i++]); - } - data = null; - return bbs; - } - - public final static byte[] getBytes(Block[] outblocks) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - getBytes(outblocks, out); - return out.toByteArray(); - } - - public final static OutputStream getByteStream(Block[] outblocks) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - getBytes(outblocks, out); - return out; - } - - /** Call this method for each storage, root storage first... - * - * - * @param outblocks - * @param out - */ - public final static void getBytes(Block[] outblocks, OutputStream out) { - List blxt = new ArrayList(); - for (int tx = 0; tx < outblocks.length; tx++) - blxt.add(outblocks[tx]); - getBytes(blxt, out); - } - - /** Call this method for each storage, root storage first... - * - * - * @param outblocks - * @param out - */ - public final static void getBytes(List outblocks, OutputStream out) { - Iterator it = outblocks.iterator(); - int i = 0; - while (it.hasNext()) { - Block boc = (Block) it.next(); - try { - if (boc != null) { - // if (LEOFile.DEBUG)Logger.logInfo("INFO: - // LEOFile.getBytes() getting bytes from Block: "+ i++ + ":" - // + boc.getBlockIndex()); - out.write(boc.getBytes()); - i++; // count how many blocks written - } - } catch (IOException a) { - Logger.logWarn("ERROR: gettting bytes from blocks failed: " - + a); - } - - } - } - - /** return the bytes for the blocks in a vector - */ - public final static byte[] getBytes(List bbvect) { - if (bbvect == null) - return new byte[] {}; - Block[] b = new Block[bbvect.size()]; - b = (Block[]) bbvect.toArray(b); - return LEOFile.getBytes(b); - } - - /** return the bytes for the blocks in a vector - */ - public final static OutputStream getByteStream(List bbvect) { - if (bbvect == null) - return new ByteArrayOutputStream(); - Block[] b = new Block[bbvect.size()]; - b = (Block[]) bbvect.toArray(b); - return LEOFile.getByteStream(b); - } - - /** get the number of Block records - that this Storage needs to store its byte array - */ - public final static int getSizeInBlocks(int sz, int blocksize) { - int size = sz / blocksize; - float realnum = ((float) (sz) / blocksize); - if ((realnum - size) > 0) { - size++; - } - return size; - } - - /** - * Get the minimum blocks that this file can encompass - * @return - */ - public int getMinBlocks() { - return (header.getMinStreamSize() / BIGBLOCK.SIZE); - } - - /** - * @return Returns the encryptionStorageOverage. - */ - public byte[] getEncryptionStorageOverage() { - if (encryptionStorageOverage == null) - encryptionStorageOverage = new byte[0]; - return encryptionStorageOverage; - } - - /** - * reads in the FAT sectors from the compound file using info from the LEOFile header - * @return FATSectors == one or more sectors used to define the Sector Chain Index (==File Allocation Table == FAT) - */ - private CompatibleVector getFATSectors() { - CompatibleVector FATSectors = new CompatibleVector(); - int[] DIFAT = header.getDIFAT(); // the Index for the FAT(which is the - // File Allocation Table or Sector - // Chain Array), id's of 1st 109 - // sectors or blocks - if (DEBUG) - Logger.logInfo("FAT Blocks:\n" + Arrays.toString(DIFAT)); - // **** read in the FAT index ****// - // **** First get the inital blocks before the extra DIFAT - // sectors ****// - int FATidx = 0; - int blockidx = 0; - int bsz = bigBlocks.size() - 1; - for (; blockidx < Math.min(DIFAT.length, MAXDIFATLEN); blockidx++) { - FATidx = DIFAT[blockidx]; - if (FATidx <= bsz) { - BIGBLOCK bbd = (BIGBLOCK) bigBlocks.get(FATidx); - // each FAT entry is a 1-based index - // if (DEBUG)Logger.logInfo("INFO: LEOFile Got A FAT index - // at: "+ String.valueOf(FATidx)); - bbd.setIsDepotBlock(true); - FATSectors.add(bbd); - } else { - // Usually caused by CHECK ByteStreamer.writeOut() - // dlen/filler handling - Logger.logErr("LEOFile.init failed. FAT Index Attempting to fetch Block past end of blocks."); - throw new InvalidFileException( - "Input file truncated. LEOFile.init failed. FAT Index Attempting to fetch Block past end of blocks."); - } - } - - // ******* read in any Exrra DIFAT sectors *******// - int numExtraDIFATSectors = header.getNumExtraDIFATSectors(); - int extraDIFATStart = header.getExtraDIFATStart() + 1; - int chainIndex = extraDIFATStart; - for (int x = 0; x < numExtraDIFATSectors; x++) { - BIGBLOCK xind = (BIGBLOCK) bigBlocks.get(chainIndex); - ByteBuffer extraSectorBytes = ByteBuffer.wrap(xind.getBytes()); - extraSectorBytes.position(0); - extraSectorBytes.order(ByteOrder.LITTLE_ENDIAN); - int numExtraSectorElements = Math - .min((header.getNumFATSectors() - blockidx), IDXBLOCKSIZE); - for (int i = 0; i < numExtraSectorElements; i++) { - if (i < (IDXBLOCKSIZE - 1)) { // -1 because must always have - // room for end of sector mark - int bbloc = extraSectorBytes.getInt() + 1; - if (bbloc <= bsz) { - BIGBLOCK bbd = (BIGBLOCK) bigBlocks.get(bbloc); // fails - // at - // 41k - // recs - // jpm - // test - bbd.setIsDepotBlock(true); - bbd.setIsExtraSector(true); - FATSectors.add(bbd); - blockidx++; - } else { - Logger.logErr("LEOFile.init failed. Attempting to fetch Invalid Extra Sector Block."); - throw new InvalidFileException( - "LEOFile.init failed. Attempting to fetch Invalid Extra Sector Block."); - } - } else { - // the index to the next extra DIFAT sector is the last int - // in the chain - chainIndex = extraSectorBytes.getInt() + 1; - } - } - } - return FATSectors; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2760792940329331096L; + public final static int MAXDIFATLEN = 109; // maximum + // DIFATLEN + // = + // 109; + // if + // more + // sectors + // are + // needed + // goes + // into + // extraDIFAT + public final static int IDXBLOCKSIZE = 128; // number + // of + // indexes + // that + // can + // be + // stored + // in + // 1 + // block + public final static boolean DEBUG = false; + public int DEBUGLEVEL = 0; + public static int actualOutput = 0; + private List bigBlocks; + private boolean readok = false; + private LEOHeader header = null; + private StorageTable directories; + private FileBuffer fb = null; + String fileName = "New Spreadsheet"; + byte[] encryptionStorageOverage = null; + boolean encryptedXLSX = false; + + // TODO: fix temp file issues and implement shutdown cleanup + // -- currently no way to delete -jm + + public LEOHeader getHeader() { + return header; + } + + /** + * Close the underlying IO streams for this LEOFile. + *

        + * Also deletes the temp file. + * + * @throws Exception + */ + public void close() throws IOException { + if (fb != null) + fb.close(); + // KSC: close out other object refs + fb = null; + // header.getBytes().clear(); + header = null;// new LEOHeader(); + if (directories != null) { + directories.close(); + directories = null; + } + if (bigBlocks != null) { + for (int i = 0; i < bigBlocks.size(); i++) { + BlockImpl b = (BlockImpl) bigBlocks.get(i); + b.close(); + } + bigBlocks.clear(); + } + bigBlocks = null; + // FAT= null; + } + + /** + * just closes the filebuffer withut clearing out buffers and storage tables + */ + public void closefb() throws IOException { + if (fb != null) + fb.close(); + fb = null; + } + + public void shutdown() { + try { + this.close(); + } catch (Exception e) { + if (DEBUGLEVEL > 0) + Logger.logWarn("could not close workbook cleanly." + e); + } + } + + /** + * a new LEO file containing LEO archive entries + * + * @param String a file path containing a valid LEOfile + */ + public LEOFile(String fname) { + if (fname.indexOf(".ser") > -1) { + this.initFromPrototype(fname); + return; + } + this.fileName = fname; + fb = LEOFile.readFile(fname); + this.initWrapper(fb.getBuffer()); + } + + /** + * Create a leo file from a prototype string/path + *

        + * PROTOTYPE_LEO_ENCRYPTED + * + * @param fname + */ + private void initFromPrototype(String fname) { + try { + byte[] b = ResourceLoader.getBytesFromJar(fname); + if (b == null) { + throw new io.starter.formats.XLS.WorkBookException( + "Required Class files not on the CLASSPATH. Check location of .jar file and/or jarloc System property.", + WorkBookException.LICENSING_FAILED); + } + ByteBuffer bbf = ByteBuffer.wrap(b); + bbf.order(ByteOrder.LITTLE_ENDIAN); + this.initWrapper(bbf); + } catch (Exception e) { + throw new InvalidFileException( + "WorkBook could not be instantiated: " + e.toString()); + } + } + + /** + * Instantiate a Leo file from an encrypted document. We currently have a hack + * in place for encrypted documents due to their having truncated bigblocks that our + * interface does not correctly handle. + * + * @param a file containing a valid LEOfile (XLS BIFF8) + * @param whether to use a temp file + * @param if the file is encrypted xlsx format + */ + public LEOFile(File fpath, boolean usetempfile, boolean encryptedXLSX) { + this.encryptedXLSX = encryptedXLSX; + this.fileName = fpath.getAbsolutePath(); + fb = LEOFile.readFile(fpath, usetempfile); + this.initWrapper(fb.getBuffer()); + } + + /** + * Checks whether the given byte array starts with the LEO magic number. + */ + public static boolean checkIsLEO(byte[] data, int count) { + if (count < LEOHeader.majick.length) + return false; + for (int idx = 0; idx < LEOHeader.majick.length; idx++) + if (data[idx] != LEOHeader.majick[idx]) + return false; + return true; + } + + /** + * a new LEO file containing LEO archive entries + * + * @param a file containing a valid LEOfile (XLS BIFF8) + * @param whether to use a temp file + */ + public LEOFile(File fpath, boolean usetempfile) { + this.fileName = fpath.getAbsolutePath(); + fb = LEOFile.readFile(fpath, usetempfile); + this.initWrapper(fb.getBuffer()); + } + + /** + * a new LEO file containing LEO archive entries + * + * @param a file containing a valid LEOfile (XLS BIFF8) + */ + public LEOFile(File fpath, int DEBUGLEVEL) { + this.fileName = fpath.getAbsolutePath(); + this.DEBUGLEVEL = DEBUGLEVEL; + fb = LEOFile.readFile(fpath); + this.initWrapper(fb.getBuffer()); + } + + /** + * a new LEO file containing LEO archive entries + * + * @param byte[] a byte array containing a valid LEOfile + */ + public LEOFile(ByteBuffer bytebuff) { + this.initWrapper(bytebuff); + } + + /** + * This is just removing some duplicate code from our constructors + *

        + * We should add some exception handling in here! + */ + public void initWrapper(ByteBuffer bytebuff) { + int[] FAT = this.init(bytebuff); + if (FAT != null) { + directories.initDirectories(bigBlocks, FAT); + // KSC: TESTING: XLS-97 + if (DEBUG || DEBUGLEVEL > 200) + directories.DEBUG(); + FAT = null; + readok = true; + } else + readok = false; + } + + public void clearAfterInit() { + this.bigBlocks.clear(); + } + + /** + * Reads in an encrypted LEO stream. May be able to remove this and use the standard + * constructor, we shall see. + * + * @param encryptedFile + */ + public void readEncryptedFile(File encryptedFile) { + fb = readFile(encryptedFile); + this.initWrapper(fb.getBuffer()); + } + + public String getFileName() { + return fileName; + } + + /** + * return whether the LEOFile contains a valid workbook + */ + public boolean hasWorkBook() { + Storage book; + try { + book = directories.getDirectoryByName("Workbook"); + } catch (StorageNotFoundException e) { + try { + book = directories.getDirectoryByName("Book"); + } catch (StorageNotFoundException e1) { + return false; + } + } + return true; + } + + /** + * return whether the LEOFile contains a valid doc + */ + public boolean hasDoc() { + if (readok) { + try { + Storage doc = this.directories + .getDirectoryByName("WordDocument"); + } catch (StorageNotFoundException e) { + return false; + } + return true; + } + return false; + } + + /** + * return whether the LEOFile contains the _SX_DB_CUR Pivot Table Cache storage (required for Pivot Table) + * + * @return + */ + public boolean hasPivotCache() { + if (readok) { + try { + Storage doc = this.directories.getDirectoryByName("_SX_DB_CUR"); + } catch (StorageNotFoundException e) { + return false; + } + return true; + } + return false; + } + + /** + * + */ + public LEOFile() { + super(); + } + + /** + * Create a LEOFile from an input stream. Unfortunately because of our byte backer + * we need to write to a temporary file in order to do this + * + * @param stream + */ + public LEOFile(InputStream stream) throws IOException { + File target = TempFileManager.createTempFile("ExtenXLS_temp", ".leo"); + JFileWriter.writeToFile(stream, target); + + this.fileName = target.getAbsolutePath(); + fb = LEOFile.readFile(target); + this.initWrapper(fb.getBuffer()); + target.deleteOnExit(); + target.delete(); + } + + public LEOFile(File fx) { + // TODO Auto-generated constructor stub + } + + /** + * calculate number of FAT blocks necessary to describe compound file + */ + final static int getNumFATSectors(int storageTotal) { + int nFAT = storageTotal * 4; + nFAT /= BIGBLOCK.SIZE; + float realnum = ((float) (storageTotal * 4) / BIGBLOCK.SIZE); + if ((realnum - nFAT) > 0 || (nFAT > MAXDIFATLEN)) { + nFAT++; + } + return nFAT; + } + + /** + * Creates all the necessary headers and associated storages/structures that, + * along with the actual workbook data, makes up the document + *

        + * Basic Structure + * 1st sector or block= Header + 1st 109 sector chains (==FAT===array of sector numbers) + * Location of FAT==DIFAT + * Header denotes sector of "Root Directory" which in turn denotes sectors of all other directories + * (Workbook is but one of several directories) + * Each directory denotes the start block of any associated data into the FAT (sector chain index) + * (if directory is >0 an <4096 in size, it's associated data is stored in miniStorage + *

        + *

        + *

        + *

        + *

        + *

        + *

        + *

        + * Ok, so this is a little odd, it looks as if it collects all of the storages, and handles + * all of the preprocessing necessary to create FAT etc. + * returns a list of all the storages, presumably to be handled by ByteStreamer. + *

        + *

        + * A little more information: + * A list of storages is generated. These storages do not include + * a) the workbook storage(!) which is meant to be the first record written after receiving the byte array + * b) individual miniFAT storages, these are all combined into one container storage that is not represented in the header + * c) the LeoHeader, which is written to the output stream already + *

        + *

        + * It seems as if this method needs several things + * 1) better header comments on its functions + * 2) break out logic to private methods to keep overall functions understandable. + * 3) Handle file formats other than XLS (ie doc, encrypted xls, ppt, etc) + * 4) be clear about its usage to outputStream. Why only the 1 storage written, maybe none would be better? + * 5) probably throw an exception + *

        + * 6) WHAT IS WITH THE STORAGES THAT ARE NOT STORAGES? -- these are non-directory storages used to write out the bytes contained within in the correct order + * for the document. These non-directory storages include the FAT and the minFAT blocks and miniStream + * + * @param outputstream to write out + * @param book to write + * @param book bytes size + * @return storages to write out in streamer + */ + public synchronized List writeBytes(OutputStream out, int workbook_byte_size) { + /***** storages to be written - rebuild from saved storages and recreated workbook storage*/ + AbstractList storages = new Vector(); + // header variables + int numFATSectors = 0; // num FAT indexes + int rootstart = 0; // block position where root sector is located + int numExtraDIFATSectors = 0; // num extra sectors/blocks + int extraDIFATStart = -2; // extra sector start (-2 for none) + int numMiniFATSectors = 0; // num short sectors/miniFAT indexes + int miniFATStart = -2; // start block position of short sector container + // blocks (-2 for none) + int sbidxpos = -2; // start position of short sector idx chain (-2 for + // none) + int sbsz = 0; // size of short sector container + int[] DIFAT; // holds the sector id chains for the 1st 109 sector index + // blocks (DIFAT) + int[] extraDIFAT; // holds the sector id chains for any sector index + // blocks (DIFAT) > 109 + int numblocks = 1; // header is 1st + + // workbook directory + Storage book = null; + boolean isEncrypted = false; + try { + book = directories.getDirectoryByName("Workbook"); + } catch (StorageNotFoundException e3) { + try { + book = directories.getDirectoryByName("EncryptedPackage"); + isEncrypted = !true; + } catch (StorageNotFoundException e1) { + // this is an error state, ideally we would be throwing a + // (non-runtime) exception? + throw new InvalidFileException( + "Input LEO file not valid for output"); + } + } + + // root directory + RootStorage rootStore = null; + try { + rootStore = directories.getRootStorage(); + } catch (StorageNotFoundException e2) { + // this is an error state, ideally we would be throwing a + // (non-runtime) exception? + throw new InvalidFileException( + "Input LEO file not valid for output"); + } + + // get existing directories + store all except workbook + + // root (which are rebuilt) + Enumeration e = directories.getAllDirectories().elements(); + while (e.hasMoreElements()) { + Storage thisStore = (Storage) e.nextElement(); + if (thisStore != book && thisStore != rootStore) { // && + // thisStore!=encryptionInfo? + storages.add(thisStore); + if (thisStore.getBlockType() == Block.SMALL) { // count number + // of miniStream + // blocks + thisStore.miniStreamStorage = true; // TODO: investigate why + // this setting gets + // lost! + numMiniFATSectors += thisStore.getBlockVect().size(); + } + } + } + + // if have miniStream sectors, rebuild miniFAT index + // + convert mini blocks to one or more big blocks (=miniFAT + // container) + if (numMiniFATSectors > 0) { + Storage[] sbs = buildMiniFAT(storages, numMiniFATSectors); // returns + // two + // non-directory + // storages: + // miniStream + // Storage + // + + // miniFAT + // index + storages.add(sbs[0]); + numMiniFATSectors = sbs[1].getBlockVect().size(); // trap number of + // miniStream + // block indexes + storages.add(sbs[1]); + } + + // we need to count up the total block array + // count number of blocks: + // add blocks necessary for root storage + numblocks += Math.ceil(directories.directoryVector.size() / 4); // each + // 4 + // directories==1 + // block + // (512 + // byte + // sector) + // count Storage Blocks (except root and workbook) + for (int t = 0; t < storages.size(); t++) { + Storage nstr = (Storage) storages.get(t); // saved stores containt + // thier original blocks + // + padding block + // signifying end of + // sector/block + if (!nstr.miniStreamStorage) + numblocks += nstr.getSizeInBlocks(); + } + + // take current workbook byte size and calculate # bigblocks + book.miniStreamStorage = false; // if had any initially, now it's big + // blocks + + int workbook_idx_block_size = LEOFile + .getSizeInBlocks(workbook_byte_size, BIGBLOCK.SIZE); + workbook_idx_block_size = Math + .max(workbook_idx_block_size, getMinBlocks() - 1);// ensure + // minimum # + // blocks + numblocks += workbook_idx_block_size + 1;// to account for end of block + // sector + if (isEncrypted) { // Encrypted workbooks are already set to correct + // size + numblocks--; + workbook_idx_block_size--; + } + // Given amount of blocks to write, calculate id chain + // necessary to describe + numFATSectors = LEOFile.getNumFATSectors(numblocks); // get # of secids + // necessary to + // describe + // blocks + + // now include the FAT itself + // total number of Sectors(blocks)= number of Sectors + FAT + // chain block(s) + numFATSectors = getNumFATSectors(numFATSectors + numblocks); + int[] FAT = new int[IDXBLOCKSIZE * numFATSectors]; + for (int i = 0; i < FAT.length; i++) + FAT[i] = -1; // create byte array of -1's 128 bytes - total number + // of sector ids + + // allocate FAT array index + // important positions that will be recorded in the header + // or FAT/miniFAT + DIFAT = new int[Math.min(numFATSectors, MAXDIFATLEN)]; // 1 up to the + // 1st 109 + // sector index + // blocks used + // to describe + // this file + extraDIFAT = new int[Math.max(numFATSectors - MAXDIFATLEN, 0)]; // if + // necessary, + // sector + // index + // blocks + // in + // excess + // of + // 109 + + /************************ now start to lay out block positions of all directories' associated data ************************/ + /************************ note: order of directories in storages is order that they will be written out + * this order is indexed in the FAT via the block indexes used for each sector + * upon return, storages contains all the directories, their associated data, the FAT ... + * in the calling method, the workbook records (==associated data) is written first because + * the workbook storage is first in the FAT. + Eventually, want to change this so all other storages and the FAT are written first, then + the calling method only needs to write the workbook records. + **************************************************************************************************************************/ + // first start block index with workbook blocks (they will + // be written out in ByteStreamer before all other blocks + // except the header) + int blockpos = 0; // initial sector position + book.setStartBlock(blockpos); + for (int t = 0; t < workbook_idx_block_size; t++) { + FAT[blockpos++] = blockpos; + } + FAT[blockpos++] = -2; // end of sector flag + if (!isEncrypted) { + // this is a questionable line. It is setting the storage + // length to that of the padded bigblock. + // causes errors in encrypted files + book.setActualFileSize((workbook_idx_block_size + 1) + * BIGBLOCK.SIZE); + if (DEBUG) + Logger.logInfo("Workbook actual bytes: " + + book.getActualFileSize()); + } + + // now rest of "static" stores (summary, document summary, + // comp obj ...) + for (int t = 0; t < storages.size(); t++) { + Storage nstr = (Storage) storages.get(t); + if (!nstr.miniStreamStorage) { // miniStream blocks are handled + // separately + if (nstr.getName().equals("miniStream")) { // miniStream (short + // sector container) + miniFATStart = blockpos; // this goes in rootstore + // startblock to denote start of + // short sector container + sbsz = nstr.getActualFileSize(); // actual size of short + // sector (miniFAT) + // container - stored in + // root storage + } else if (nstr.getName().equals("miniFAT")) { // miniFAT (short + // sector) index + sbidxpos = blockpos; + } + nstr.setStartBlock(-2); // default, not linked to any blocks + // 20100325 KSC: since original blocks are kept for + // non-workbook storage, + // and since size is not under our control, keep original + // size - otherwise can error + // nstr.setActualFileSize(0); // default, 0 bytes + // record index of block position for each block of the + // storage + Block[] blks = nstr.getBlocks(); + if (blks != null) { + nstr.setStartBlock(blockpos); // start of block chain + for (int i = 0; i < blks.length - 1; i++) { // -1 to trap + // last block + // with special + // end-of-block + // flag (-2) + FAT[blockpos++] = blockpos; // start of this store's + // blocks as indexed in + // block index + } + FAT[blockpos++] = -2; // end of sector/block flag + // nstr.setActualFileSize((blks.length)*512); // set file + // size -- see above why we can't do this at this time + } + } + } + + // after other storages, add root store: + // must rebuild after info from above as root storage is + // built from from all other storages + rootStore.setStartBlock(miniFATStart); // root store start points to + // miniFAT container, or -2 if + // none + rootStore.setActualFileSize(sbsz); // 0 or miniFAT container size + // handle rootstorage blocks = data of all other directory + // storages + rootStore.setBytes(directories.rebuildRootStore()); // now rebuild + // rootstore from + // all other + // storages + rootstart = blockpos; + for (int i = 0; i < rootStore.getBlockVect().size() - 1; i++) { + FAT[blockpos++] = blockpos; + } + FAT[blockpos++] = -2; // end of sector/block flag + // add rootstore to list of blocks writing out + storages.add(rootStore); + + // mark position of FAT itself within blocks (==DIFAT) + // the 1st MAXDIFATLEN DIFAT indexes goes into header + for (int i = 0; i < DIFAT.length; i++) { + DIFAT[i] = blockpos + 1; // each pos is decremented in method below, + // so increment here + FAT[blockpos++] = -3; // mark loc of FAT with special value -3 + } + + // if any FAT sectors > MAX, goes into extra blocks + if (numFATSectors > MAXDIFATLEN) { // handle extra blocks - need extra + // blocks to store the indexes + for (int n = MAXDIFATLEN; n < numFATSectors; n++) { + extraDIFAT[n - MAXDIFATLEN] = blockpos + 1; + FAT[blockpos++] = -3; + } + for (int i = 0; i < (int) Math.ceil((numFATSectors - MAXDIFATLEN) + / (IDXBLOCKSIZE * 1.0)); i++) + FAT[blockpos++] = -4; // flag for MSAT or XBB + } + + // now that all the blocks referenced by storages such as + // workbook and document summary, + // plus blocks referenced by the root storage, + // transform the blockindex to blocks & input a phantom or + // special store so can write out + // have been indexed, store the index blocks themselves + Storage idxstore = new Storage(); + idxstore.setName("IDXStorage"); + Block[] FATSectors = getIDXBlocks(FAT); // convert each int to 4-bytes + // in sector/block format --> + // FAT Sectors + idxstore.setBlocks(FATSectors); // input the FAT Sectors to a + // non-directory store for later writing + storages.add(idxstore); // output FAT Sectors after all other blocks + + if (numFATSectors > MAXDIFATLEN) { + // now build and add the extra sectors necessary to store + // the FAT + extraDIFATStart = blockpos - (int) Math + .ceil((numFATSectors - MAXDIFATLEN) / (IDXBLOCKSIZE * 1.0)); + Storage xbbstore = buildExtraDIFAT(extraDIFAT, extraDIFATStart); // create + // block(s) + // from + // the + // extraDIFAT + numExtraDIFATSectors = xbbstore.getBlockVect().size(); + storages.add(xbbstore); + } + + // now that blocks have been accounted for and indexed and + // storages and all their blocks + // (except for workbook blocks) are stored, we can build and + // output the header block + // build header block: + + header = LEOHeader.getPrototype(DIFAT); // input secIdChain to header + // loc 76 + // start sector of short sector/miniFAT index chain or -2 if + // n/a + header.setMiniFATStart(sbidxpos); + // the block # where the storages begin + header.setRootStorageStart(rootstart); + // number of block indexes needed to describe 1st 109 + // sectors/blocks (= DIFAT) + header.setNumFATSectors(numFATSectors); + // number of block indexes needed to describe short + // sectors/miniFAT sectors if any + header.setNumMiniFATSectors(numMiniFATSectors); + // number of block indexes necessary to describe extra + // sectors if any + header.setNumExtraDIFATSectors(numExtraDIFATSectors); + // start of extra sector block index or -2 if n/a + header.setExtraDIFATStart(extraDIFATStart); + + // setup the header info... + if (!header.init()) + throw new RuntimeException("LEO File Header Not Initialized"); // invalid + // WorkBook + // File + + // return the storages + return storages; + } + + /** + * build Extra Storage Sector index (extra DIFAT) + * + * @param difatidx - indexes of blocks > 13952 (= 109 blocks described by DIFAT * 128 indexes in each block) + * @return + */ + private Storage buildExtraDIFAT(int[] difatidx, int xbbpos) { + // handle Extra blocks beyond the 109*128 blocks described + // by the regular DIFAT (109 limit) + if (difatidx != null) { + ArrayList outblocks = new ArrayList(); + byte[] xbytes = new byte[difatidx.length * 4]; + // convert the int difatidx to bytes + for (int i = 0; i < difatidx.length; i++) { + byte[] idx = ByteTools.cLongToLEBytes(difatidx[i] - 1); + System.arraycopy(idx, 0, xbytes, i * 4, 4); + } + int counter = 0; + for (int i = 0; i < xbytes.length; ) { + ByteBuffer bl = BlockFactory.getPrototypeBlock(Block.BIG) + .getByteBuffer(); + int len = xbytes.length - i; + + // handle eob + if (len > BIGBLOCK.SIZE - 4) + len = BIGBLOCK.SIZE - 4; + bl.position(0); + bl.put(xbytes, i, len); + + // handle eob + counter++; + if (len == BIGBLOCK.SIZE - 4) + bl.putInt(xbbpos + counter); + + BIGBLOCK xbbBlock = new BIGBLOCK(); + xbbBlock.init(bl, 0, 0); + outblocks.add(xbbBlock); + + i += BIGBLOCK.SIZE - 4; + } + // create new storage so can be added to end + Storage xbbstore = new Storage(); + xbbstore.setName("XBBStore"); + Block[] xblx = new Block[outblocks.size()]; + outblocks.toArray(xblx); + xbbstore.setBlocks(xblx); + return xbbstore; + } + return null; + } + + /** + * Gather up all existing smallblocks from the existing storages + * and create contiguous bigblocks from it; since smallblocks are + * only 128 bytes long, 4 smallblocks fit into 1 bigblock + * while building the miniFAT Sectors,also build the miniFAT index + * which corresponds to it + * + * @param storages existing Storages (except workbook and root, which are handled separately) + * @param numsbs total number of miniFAT blocks + * @return Storage[] - two storage containers: 1 contains smallblocks, 2 contains smallblock index + */ + private Storage[] buildMiniFAT(List storages, int numsbs) { + // once more, now gathering the sbidx's (miniFAT index) + int[] sbidx = new int[(int) Math.ceil(numsbs / (IDXBLOCKSIZE * 1.0)) + * IDXBLOCKSIZE]; + byte[] smallblocks = new byte[0]; // sum up all short sectors, will be + // subsequently converted to big + // blocks + for (int i = 0; i < sbidx.length; i++) + sbidx[i] = -1; // init sbidx index + int z = 0; + for (int i = 0; i < storages.size(); i++) { + Storage thisStore = (Storage) storages.get(i); + if (thisStore.getBlockType() == Block.SMALL) { + // since these are static blocks we can copy original data + thisStore.setStartBlock(z); // set start position of miniFATs as + // may have changed + // keep length as that hasn't changed + for (int j = 0; j < thisStore.getBlockVect().size() - 1; j++) + sbidx[z++] = z; + sbidx[z++] = -2; // end of sector + smallblocks = ByteTools + .append(thisStore.getBytes(), smallblocks); + // 20100407 KSC: this screws up subsequent usages of book + // after writing ... see TestCreateNewName NPE + // thisStore.setBlocks(new Block[0]); + } + } + + // concatenate all miniFAT sectors to build the miniFAT or + // short sector container + // i.e. miniFAT blocks are concatenated and stored in big + // block(s): + Block[] smallBlocksToBig = BlockFactory + .getBlocksFromByteArray(smallblocks, Block.BIG); + Storage miniFATContainer = new Storage(); + miniFATContainer.setName("miniStream"); + miniFATContainer.setBlocks(smallBlocksToBig); + miniFATContainer.setActualFileSize(smallblocks.length); + smallblocks = null; // no need anymore + Storage miniFAT = new Storage(); + miniFAT.setName("miniFAT"); + Block[] sbIDX = getIDXBlocks(sbidx); // convert each int to 4-bytes in + // sector/block format --> BBDIX + miniFAT.setBlocks(sbIDX); + return new Storage[]{miniFATContainer, miniFAT}; + } + + /** + * create the idx from the actual locations + * of the Blocks in the outblock array + */ + final static void initSmallBlockIndex(int[] newidx, Block b) { + while (b.hasNext() && !(b == b.next())) { + int origps = b.getBlockIndex(); + if (origps < 0) { + Logger.logWarn("WARNING: LEOFile Block Not In MINIFAT vector: " + + b.getOriginalIdx()); + } else { + int newp = ((Block) b.next()).getBlockIndex(); + if (false) + Logger.logInfo("INFO: LEOFile Initializing block index: " + + origps + " val: " + newp + " idxlen: " + + newidx.length); + newidx[origps] = newp; + } + b = (Block) b.next(); + } + if (b.getBlockIndex() >= 0) + newidx[b.getBlockIndex()] = -2; // end of storage + } + + /** create the idx from the actual locations + of the Blocks in the outblock array + * + final static int initWBBlockIndex(int[] newidx, int sz) { + int start = 0; + for(int r=newidx.length-1;r>=0;r--) { + if(newidx[r]!=-1) { + start = r+1; + break; + } + } + for(int i=0;i0) + newidx[(start+sz-1)] = -2; // end of storage + else + newidx[(start+sz-1)] = -2; // end of storage + return start; + } + */ + + /** + * returns an empty new FAT which + * accounts for the size of its own blocks + */ + final static int[] getEmptyDIFAT(int totblocks, int numFATSectors) { + + // allocate space in idx for FAT recs, StorageTable and for + // final -2 + int[] bbdi = new int[numFATSectors + totblocks + 1]; + for (int x = 0; x < bbdi.length; x++) + bbdi[x] = (byte) -1; + return bbdi; + } + + /** + * Create the array of BB Idx blocks + */ + final static Block[] getIDXBlocks(int[] bbdidx) { + // step through and create index recs for all Blocks + byte[] b = new byte[bbdidx.length * 4]; + int bv = 0; + + // can we wrap t in an nio buffered and read? + + for (int t = 0; t < b.length/* 20100304 KSC: why? - 4 */; ) { + byte[] bs = ByteTools.cLongToLEBytes(bbdidx[bv++]); + // for(int z=0;z<4;z++) + b[t++] = bs[0]; + b[t++] = bs[1]; + b[t++] = bs[2]; + b[t++] = bs[3]; + } + return BlockFactory.getBlocksFromByteArray(b, Block.BIG); + } + + /** + * get a handle to a Storage within this LEO file + */ + public Storage getStorageByName(String s) throws StorageNotFoundException { + return directories.getDirectoryByName(s); + } + + /** + * get all Directories in this LEO file + */ + public Storage[] getAllDirectories() { + CompatibleVector v = directories.getAllDirectories(); + Storage[] s = new Storage[v.size()]; + s = (Storage[]) v.toArray(s); + return s; + } + + /** + * return the Directory Array for this LEOFile + *
        holds all directories (storages and streams) in correct order + *
        Directories in turn can reference streams of data + * + * @return + */ + public StorageTable getDirectoryArray() { + return directories; + } + + /** Simply setting the byte size on the book storage + * + private void checkIfSmallBlockOutput(int blen) throws StorageNotFoundException { + Storage book; + try { + book = storageTable.getStorageByName("Workbook"); + }catch(StorageNotFoundException e) { + book = storageTable.getStorageByName("EncryptedPackage"); + } + + if (blen >= StorageTable.BIGSTORAGE_SIZE) { + if (book.getBlockType() == Block.SMALL) { + Logger.logWarn( + "WARNING: Modifying SmallBlock Workbook: "+ this.getFileName() + + ". Modifying SmallBlock-type Workbooks " + + " can cause corrupt output files. See: " + + "http://extentech.com/uimodules/docs/docs_detail.jsp?meme_id=195"); + throw new WorkBookException("SmallBlock File Detected reading: "+this.getFileName(), WorkBookException.SMALLBLOCK_FILE); + // this.convertSBtoBB(book); + } + book.setActualFileSize(blen); + } else + book.setActualFileSize(StorageTable.BIGSTORAGE_SIZE); // set at the minimum + } + */ + + /** + * get the bytes of the DOC file. + */ + public BlockByteReader getDocBlockBytes() { + // get the bytes for the WordDocument substream only + Storage doc; + try { + doc = directories.getDirectoryByName("WordDocument"); + } catch (StorageNotFoundException e) { + throw new InvalidFileException( + "InvalidFileException: Not Word '97 or later version. Unsupported file format."); + } + return doc.getBlockReader(); + } + + /** + * get the bytes of the XLS file. + */ + public BlockByteReader getXLSBlockBytes() { + // get the bytes for the Workbook substream only + Storage book; + try { + book = directories.getDirectoryByName("Workbook"); + } catch (StorageNotFoundException e) { + try { + book = directories.getDirectoryByName("Book"); + } catch (StorageNotFoundException e1) { + Logger.logInfo("Not Excel '97 (BIFF8) or later version. Unsupported file format."); + throw new InvalidFileException( + "InvalidFileException: Not Excel '97 (BIFF8) or later version. Unsupported file format."); + } + } + return book.getBlockReader(); + } + + /** + * read LEO file information from header. + */ + public synchronized int[] init(ByteBuffer bbuf) { + int pos = 0; + CompatibleVector FATSectors = new CompatibleVector(); // one or more + // sectors which + // hold the FAT + // (File + // Allocation + // Table or + // indexes into + // the sectors) + + bigBlocks = new ArrayList(); + int len = bbuf.limit() / BIGBLOCK.SIZE; + // get ALL BIGBLOCKS (512 byte chunks of file) + if (DEBUG) + Logger.logInfo("\nINIT: Total Number of bigblocks: " + len); + for (int i = 0; i < len; i++) { + BIGBLOCK bbd = new BIGBLOCK(); + bbd.init(bbuf, i, pos); + pos += BIGBLOCK.SIZE; + this.bigBlocks.add(bbd); + } + + // Encrypted workbooks can have random overages. + // not ideal, but store this value in LEO and get from the + // storage if its named 'EncryptedPackage' + int encryptionStorageOverageLen = (bbuf.limit() % BIGBLOCK.SIZE); + if (encryptionStorageOverageLen > 0) { + int filepos = len * BIGBLOCK.SIZE; + if (this.encryptedXLSX) { + bbuf.position(filepos); + encryptionStorageOverage = new byte[encryptionStorageOverageLen]; + bbuf.get(encryptionStorageOverage, 0, encryptionStorageOverage.length); + } else { + BIGBLOCK bbd = new BIGBLOCK(); + bbd.init(bbuf, len, pos); // filepos); + pos += encryptionStorageOverageLen; + this.bigBlocks.add(bbd); + } + } + + /***** Read in the file header */ + // header holds directory start sector and + // FAT/miniFAT/extraFAT or Sector Chain index info + header = new LEOHeader(); // read the LEO file header rec + + // is a valid WorkBook File? + if (!header.init(bbuf)) + throw new InvalidFileException( + this.getFileName() + " is not a valid OLE File."); + + if (DEBUG) { + Logger.logInfo("Header: "); + Logger.logInfo("numbFATSectors: " + header.getNumFATSectors()); + Logger.logInfo("numMiniFATSectors: " + + header.getNumMiniFATSectors()); + Logger.logInfo("numbExtraDIFATSectors: " + + header.getNumExtraDIFATSectors()); + Logger.logInfo("rootstart: " + header.getRootStartPos()); + Logger.logInfo("miniFATStart: " + header.getMiniFATStart()); + } + BIGBLOCK headerblock = (BIGBLOCK) bigBlocks.get(0); + headerblock.setInitialized(true); + + /***** Read in the FAT sectors - the sectors or blocks used for the FAT (==Sector Chain Index or File Allocation Table) */ + FATSectors = getFATSectors(); + + /***** turn the FAT Sectors into the FAT or Sector Index Chain */ + byte[] blx = LEOFile.getBytes(FATSectors); + int[] FAT = null; + FAT = LEOFile.readFAT(blx); + blx = null; // done + + if (DEBUG) + Logger.logInfo("FAT:\n" + Arrays.toString(FAT)); + + // if (DEBUG) + // StorageTable.writeitout(FATSectors, "FAT.dat"); + + /***** Read the Directory blocks */ + directories = new StorageTable(); + directories.init(bbuf, header, bigBlocks, FAT); + return FAT; + } + + /** + * create a FileBuffer from a file, use system property to determine + * whether to use a temp file. + * + * @param file containing XLS bytes + * @param fpath + * @return + */ + public final static FileBuffer readFile(File fpath) { + boolean usetempfile = false; + String tmpfu = (String) System.getProperties() + .get("io.starter.formats.LEO.usetempfile"); + + if (tmpfu != null) + usetempfile = tmpfu.equalsIgnoreCase("true"); + + return readFile(fpath, usetempfile); + } + + /** + * create a FileBuffer from a file, use boolean parameter to determine + * whether to use a temp file. + * + * @param file containing XLS bytes + * @param whether to use a tempfile + * @return + */ + public final static FileBuffer readFile(File fpath, boolean usetempfile) { + if (usetempfile) + return FileBuffer.readFileUsingTemp(fpath); + return FileBuffer.readFile(fpath); + } + + /** + * read in a WorkBook ByteBuffer from a file path. + *

        + * from here on out, we're reading pointers to the bytes on disk + *

        + * access data directly on disk through the ByteBuffer. + * + *
        By default, OpenXLS will lock open WorkBook files, to close the file after parsing and work with a + * temporary file instead, use the following setting: + *

        + * System.getProperties().put("io.starter.formats.LEO.usetempfile", "true"); + *

        + * IMPORTANT NOTE: You will need to clean up temp files occasionally in your user directory (temp filenames will begin + * with "ExtenXLS_".) + *

        + **/ + public final static FileBuffer readFile(String fpath) { + return LEOFile.readFile(new File(fpath)); + } + + /** + * returns the table of int locations + * in the file for the BIGBLOCK linked list. + */ + public final static int[] readFAT(List vect) { + byte[] data = getBytes(vect); + return readFAT(data); + } + + /** + * returns the table of int locations + * in the file for the BIGBLOCK linked list. + */ + private final static int[] readFAT(byte[] data) { + int[] bbs = new int[data.length / 4]; + int pos = 0; + for (int i = 0; i < data.length; ) { + bbs[pos++] = ByteTools + .readInt(data[i++], data[i++], data[i++], data[i++]); + } + data = null; + return bbs; + } + + public final static byte[] getBytes(Block[] outblocks) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + getBytes(outblocks, out); + return out.toByteArray(); + } + + public final static OutputStream getByteStream(Block[] outblocks) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + getBytes(outblocks, out); + return out; + } + + /** + * Call this method for each storage, root storage first... + * + * @param outblocks + * @param out + */ + public final static void getBytes(Block[] outblocks, OutputStream out) { + List blxt = new ArrayList(); + for (int tx = 0; tx < outblocks.length; tx++) + blxt.add(outblocks[tx]); + getBytes(blxt, out); + } + + /** + * Call this method for each storage, root storage first... + * + * @param outblocks + * @param out + */ + public final static void getBytes(List outblocks, OutputStream out) { + Iterator it = outblocks.iterator(); + int i = 0; + while (it.hasNext()) { + Block boc = (Block) it.next(); + try { + if (boc != null) { + // if (LEOFile.DEBUG)Logger.logInfo("INFO: + // LEOFile.getBytes() getting bytes from Block: "+ i++ + ":" + // + boc.getBlockIndex()); + out.write(boc.getBytes()); + i++; // count how many blocks written + } + } catch (IOException a) { + Logger.logWarn("ERROR: gettting bytes from blocks failed: " + + a); + } + + } + } + + /** + * return the bytes for the blocks in a vector + */ + public final static byte[] getBytes(List bbvect) { + if (bbvect == null) + return new byte[]{}; + Block[] b = new Block[bbvect.size()]; + b = (Block[]) bbvect.toArray(b); + return LEOFile.getBytes(b); + } + + /** + * return the bytes for the blocks in a vector + */ + public final static OutputStream getByteStream(List bbvect) { + if (bbvect == null) + return new ByteArrayOutputStream(); + Block[] b = new Block[bbvect.size()]; + b = (Block[]) bbvect.toArray(b); + return LEOFile.getByteStream(b); + } + + /** + * get the number of Block records + * that this Storage needs to store its byte array + */ + public final static int getSizeInBlocks(int sz, int blocksize) { + int size = sz / blocksize; + float realnum = ((float) (sz) / blocksize); + if ((realnum - size) > 0) { + size++; + } + return size; + } + + /** + * Get the minimum blocks that this file can encompass + * + * @return + */ + public int getMinBlocks() { + return (header.getMinStreamSize() / BIGBLOCK.SIZE); + } + + /** + * @return Returns the encryptionStorageOverage. + */ + public byte[] getEncryptionStorageOverage() { + if (encryptionStorageOverage == null) + encryptionStorageOverage = new byte[0]; + return encryptionStorageOverage; + } + + /** + * reads in the FAT sectors from the compound file using info from the LEOFile header + * + * @return FATSectors == one or more sectors used to define the Sector Chain Index (==File Allocation Table == FAT) + */ + private CompatibleVector getFATSectors() { + CompatibleVector FATSectors = new CompatibleVector(); + int[] DIFAT = header.getDIFAT(); // the Index for the FAT(which is the + // File Allocation Table or Sector + // Chain Array), id's of 1st 109 + // sectors or blocks + if (DEBUG) + Logger.logInfo("FAT Blocks:\n" + Arrays.toString(DIFAT)); + // **** read in the FAT index ****// + // **** First get the inital blocks before the extra DIFAT + // sectors ****// + int FATidx = 0; + int blockidx = 0; + int bsz = bigBlocks.size() - 1; + for (; blockidx < Math.min(DIFAT.length, MAXDIFATLEN); blockidx++) { + FATidx = DIFAT[blockidx]; + if (FATidx <= bsz) { + BIGBLOCK bbd = (BIGBLOCK) bigBlocks.get(FATidx); + // each FAT entry is a 1-based index + // if (DEBUG)Logger.logInfo("INFO: LEOFile Got A FAT index + // at: "+ String.valueOf(FATidx)); + bbd.setIsDepotBlock(true); + FATSectors.add(bbd); + } else { + // Usually caused by CHECK ByteStreamer.writeOut() + // dlen/filler handling + Logger.logErr("LEOFile.init failed. FAT Index Attempting to fetch Block past end of blocks."); + throw new InvalidFileException( + "Input file truncated. LEOFile.init failed. FAT Index Attempting to fetch Block past end of blocks."); + } + } + + // ******* read in any Exrra DIFAT sectors *******// + int numExtraDIFATSectors = header.getNumExtraDIFATSectors(); + int extraDIFATStart = header.getExtraDIFATStart() + 1; + int chainIndex = extraDIFATStart; + for (int x = 0; x < numExtraDIFATSectors; x++) { + BIGBLOCK xind = (BIGBLOCK) bigBlocks.get(chainIndex); + ByteBuffer extraSectorBytes = ByteBuffer.wrap(xind.getBytes()); + extraSectorBytes.position(0); + extraSectorBytes.order(ByteOrder.LITTLE_ENDIAN); + int numExtraSectorElements = Math + .min((header.getNumFATSectors() - blockidx), IDXBLOCKSIZE); + for (int i = 0; i < numExtraSectorElements; i++) { + if (i < (IDXBLOCKSIZE - 1)) { // -1 because must always have + // room for end of sector mark + int bbloc = extraSectorBytes.getInt() + 1; + if (bbloc <= bsz) { + BIGBLOCK bbd = (BIGBLOCK) bigBlocks.get(bbloc); // fails + // at + // 41k + // recs + // jpm + // test + bbd.setIsDepotBlock(true); + bbd.setIsExtraSector(true); + FATSectors.add(bbd); + blockidx++; + } else { + Logger.logErr("LEOFile.init failed. Attempting to fetch Invalid Extra Sector Block."); + throw new InvalidFileException( + "LEOFile.init failed. Attempting to fetch Invalid Extra Sector Block."); + } + } else { + // the index to the next extra DIFAT sector is the last int + // in the chain + chainIndex = extraSectorBytes.getInt() + 1; + } + } + } + return FATSectors; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/LEOHeader.java b/src/main/java/io/starter/formats/LEO/LEOHeader.java index af0af42..c659218 100644 --- a/src/main/java/io/starter/formats/LEO/LEOHeader.java +++ b/src/main/java/io/starter/formats/LEO/LEOHeader.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,380 +23,391 @@ package io.starter.formats.LEO; // NIO based API requires JDK1.3+ -import java.io.Serializable; -import java.nio.*; import io.starter.toolkit.Logger; - -/** Header record containing information on the Storage records in the LEOFile. - - Header (block 1) -- 512 (0x200) bytes - Field Description Offset Length Default value or const - FILETYPE Magic number identifying this as a LEO filesystem. 0x0000 Long 0xE11AB1A1E011CFD0 - UK1 Unknown constant 0x0008 Integer 0 - UK2 Unknown Constant 0x000C Integer 0 - UK3 Unknown Constant 0x0014 Integer 0 - UK4 Unknown Constant (revision?) 0x0018 Short 0x003B - UK5 Unknown Constant (version?) 0x001A Short 0x0003 - UK6 Unknown Constant 0x001C Short -2 - LOG_2_BIG_BLOCK_SIZE Log, base 2, of the big block size 0x001E Short 9 (2 ^ 9 = 512 bytes) - LOG_2_SMALL_BLOCK_SIZE Log, base 2, of the miniFAT size 0x0020 Integer 6 (2 ^ 6 = 64 bytes) - UK7 Unknown Constant 0x0024 Integer 0 - UK8 Unknown Constant 0x0028 Integer 0 - FAT_COUNT Number of elements in the FAT array 0x002C Integer required - PROPERTIES_START Block index of the first block of the property table 0x0030 Integer required - UK9 Unknown Constant 0x0034 Integer 0 - UK10 Unknown Constant 0x0038 Integer 0x00001000 - MINIFAT_START Block index of first big block containing the mini FATallocation table (MINIFAT) 0x003C Integer -2 - UK11 Unknown Constant 0x0040 Integer 1 - extraDIFAT Block index of the first block in the Extended Block Allocation Table 0x0044 Integer -2 - extraDIFATCount Number of elements in the Extended File Allocation Table (to be added to the FAT) 0x0048 Integer 0 - FAT_ARRAY Array of block indicies constituting the File Allocation Table (FAT) 0x004C, 0x0050, 0x0054 ... 0x01FC Integer[ ] -1 for unused elements, at least first element must be filled. - N/A Header block data not otherwise described in this table N/A - - - -*/ -public class LEOHeader implements Serializable{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -422489164065975273L; - public final static int HEADER_SIZE = 0x200; - private transient ByteBuffer data; - private static int DIFATPOSITION= 76; // DIFAT start position WITHIN HEADER (DIFAT=where to find the FAT i.e the index to the FAT) - private int numFATSectors = -1; // FAT= array of Sector numbers - private int rootstart = -1; // Root Directory sector start - private int miniFATStart = -2; // miniFAT stores sectors for storages < 4096 - private int extraDIFATStart = -1; // if more than 109 FAT sectors are needed this stores start position of remaining DIFAT - private int numExtraDIFATSectors = -1; // usually 109 FAT sectors is enough; if > -1 more DIFAT is stored in other sectors - private int numMiniFATSectors= 0; - private int minStreamSize= 4096; // = 8 blocks minimum for a stream - public final static byte[] majick = - { - (byte) 0xd0, - (byte) 0xcf, - (byte) 0x11, - (byte) 0xe0, - (byte) 0xa1, - (byte) 0xb1, - (byte) 0x1a, - (byte) 0xe1, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00 }; - - /** initialize from existing data - */ - protected boolean init() { - return this.init(this.data); - } - - /** - * structure of typical header: - * // 24, 25= 62, 0 (rev #) = 0x3E - // 26, 27= 3, 0 (vers #) = 0x03 - // 28, 29= -2, -1 (little endian) - // 30, 31= 9, 0 (size of sector (9= 512, 7= 128)) - // 32, 33= 6, 0 (size of short sector (6=64 bytes)) - // 34->43= 0 (unused) - // 44->47= Total # sectors in SAT *** - // 48->51= Sector id of 1st sector - // 52->55= 0 (unused) - // 56->59= Minimum size of a standard stream (usually 4096) - // 60->63= SecID of first sector in short-SAT - // 64->67= Total # sectors in Short-SAT *** - // 68->71= SecID of 1st sector of MSAT or -2 if no additional sectors are needed - // 72->75= Total # sectors used for the MSAT - // 76->436= MSAT (1st 109 sectors) - */ - private static void displayHeader(ByteBuffer data) { - for (int i= 0; i < data.capacity(); i++) { - io.starter.toolkit.Logger.log("[" + i + "] " + data.get(i)); - } - } - - - /** initialize the leo doc header - */ - protected boolean init(ByteBuffer dta) { - this.data = dta; - int pos; - - if(dta.limit() < 1) - throw new InvalidFileException("Empty input file."); - - try{ - // sanity check -- is it a valid WorkBook? - for (int t = 0; t < 16; t++) { - if (dta.get(t) != majick[t]) { - throw new InvalidFileException("File is not valid OLE format: Bad Majick."); - } - } - }catch(Exception e){ - throw new InvalidFileException("File is not valid OLE format:" + dta.limit()); - } - - // get the rootstart 0x30 - pos = 0x30; - data.position(pos); // secId of 1st storage pos (==rootstart)[48] - rootstart = data.getInt(); - rootstart++; - rootstart *= BIGBLOCK.SIZE; - - // has DIFAT Extra sectors? -- extended data blocks for large XLS files - pos = 0x44; // [68] - data.position(pos); - extraDIFATStart = data.getInt(); // sector id of 1st sector of extra DIFAT sectors or -2 of no extra sectors used - numExtraDIFATSectors = data.getInt(); // total # sectors used for the DIFAT -- if # sectors or blocks > 109, more sectors are used - - // get minimum size of standard stream - pos= 56; - data.position(pos); - minStreamSize= data.getInt(); - - // starting at 0x2c (44) THE numFATSectors - pos = 0x2C; - data.position(pos); - numFATSectors = data.getInt(); // total # blocks used in the FAT chain (each block can hold 128 sector ids) - - - // 0x3c (60) THE miniFAT start - pos = 60; - data.position(pos); - miniFATStart = data.getInt(); // secId of 1st sector of the miniFAT sector chain or -2 if non-existant - - // pos 0x40= # miniFAT sectors [64] - pos = 0x40; - data.position(pos); - numMiniFATSectors = data.getInt(); - return true; - } - - /** get the number of the Extended DIFAT sectors - */ - public int getNumExtraDIFATSectors() { - return numExtraDIFATSectors; - } - - /** get the position of the extra DIFAT sector start - */ - public int getExtraDIFATStart() { - return this.extraDIFATStart; - } - - /** get the position of the Root Storage/1st Directory - */ - public int getRootStartPos() { - return rootstart; - } - - /** get the position of the miniFAT Start Block - */ - public int getMiniFATStart() { - return miniFATStart; - } - - /** get the FAT Sectors (sectors which hold the FAT or the chain that references all sectors in the file) - */ - public int[] getDIFAT() { - int numblks = Math.min(this.numFATSectors, 109); // more than 109 goes into DIFAT - int[] FAT = new int[numblks]; - int pos = DIFATPOSITION; // START OF DIFAT (indexes the FAT) - data.position(pos); // start of the 1st 109 secIds (4 bytes each==436 bytes) - for (int i = 0; i < numblks; i++) { - FAT[i] = data.getInt(); - FAT[i]++; - } - return FAT; - } - - /** get the Header bytes - */ - public ByteBuffer getBytes() { - return this.data; - } - - /** get a BIGBLOCK containing this Header's data - ie: create a new header byte block. - */ - static final LEOHeader getPrototype(int[] FAT) { - - LEOHeader retval = new LEOHeader(); - - // get an empty BIGBLOCK - BIGBLOCK retblock = - (BIGBLOCK) BlockFactory.getPrototypeBlock(Block.BIG); - retval.setData(retblock.data); - retval.initMajickBytes(); - retval.initConstants(); - - // recreate the FAT and indexes to ... - retval.setFAT(FAT); - retval.setMiniFATStart(-2); // we don't rebuild miniFATs at this time ... - retval.setRootStorageStart(1); - retval.setExtraDIFATStart(-2); - retval.setNumExtraDIFATSectors(0); - return retval; - } - - void initConstants() { - int pos = 0x08; - data.position(pos); - data.putInt(0); - - pos = 0xc; - data.position(pos); - data.putInt(0); - - pos = 0x10; - data.position(pos); - data.putInt(0); - - pos = 0x14; - data.position(pos); - data.putInt(0); - - pos = 0x18; - data.position(pos); - data.putShort((short) 0x3e); - - pos = 0x1a; - data.position(pos); - data.putShort((short) 0x3); - - pos = 0x1c; - data.position(pos); - data.putShort((short) - 2); - - pos = 0x1e; - data.position(pos); - data.putShort((short) 9); - - pos = 0x20; - data.position(pos); - data.putInt(6); - - pos = 0x24; - data.position(pos); - data.putInt(0); - - pos = 0x28; - data.position(pos); - data.putInt(0); - - pos = 0x34; - data.position(pos); - data.putInt(0); - - pos = 0x38; - data.position(pos); - data.putInt(0x00001000); - - } - - void initMajickBytes() { - // create the majick number - this.data.position(0); - data.put(majick); - } - - void setData(ByteBuffer dta) { - this.data = dta; - } - - /** Set the FAT or sector chain index - * - * @param FAT - */ - void setFAT(int[] FAT) { - int pos = DIFATPOSITION; - data.position(pos); - for (int i = 0; i < FAT.length; i++) { - if ((FAT.length * 4) >= (this.data.limit() - 4)) { - // TODO: Create extra DIFAT sectors FAT.length too big... - Logger.logWarn("WARNING: LEOHeader.setFAT() creating Extra FAT Sectors Not Implemented. Output file too large."); - } else { - data.putInt((FAT[i]-1)); // todo: why decrement here? necessitates an increment in LEOFile.writeBytes - } - } - // fill rest with -1's === Empty Sector denotation - for (int i= data.position(); i < 512; i+=4) - data.putInt(-1); - - setNumFATSectors(FAT.length); - } - - void setRootStorageStart(int i) { - int pos = 0x30; - data.position(pos); - data.putInt(i); - } - - void setMiniFATStart(int i) { - int pos = 0x3c; - this.miniFATStart = i; - data.position(pos); - data.putInt(i); - miniFATStart= i; - } - - void setExtraDIFATStart(int i) { - int pos = 0x44; - data.position(pos); - data.putInt(i); - extraDIFATStart= i; - } - - void setNumExtraDIFATSectors(int i) { - int pos = 0x48; - data.position(pos); - data.putInt(i); - numExtraDIFATSectors= i; - } - - void setNumFATSectors(int i) { - int pos = 0x2c; - data.position(pos); - data.putInt(i); - numFATSectors= i; - } - - int getNumFATSectors() { - return numFATSectors; - } - - /** - * set the number of miniFAT sectors (each sector=64 bytes) - */ - void setNumMiniFATSectors(int i) { - int pos= 0x40; - data.position(pos); - data.putInt(i); - numMiniFATSectors= i; - } - - /** - * return the number of miniFAT sectors - * @return - */ - int getNumMiniFATSectors() { - return numMiniFATSectors; - } - - /** - * return the minimum stream size - * usually 4096 or 8 blocks - * @return - */ - int getMinStreamSize() { - return minStreamSize; - } +import java.io.Serializable; +import java.nio.ByteBuffer; + + +/** + * Header record containing information on the Storage records in the LEOFile. + *

        + * Header (block 1) -- 512 (0x200) bytes + * Field Description Offset Length Default value or const + * FILETYPE Magic number identifying this as a LEO filesystem. 0x0000 Long 0xE11AB1A1E011CFD0 + * UK1 Unknown constant 0x0008 Integer 0 + * UK2 Unknown Constant 0x000C Integer 0 + * UK3 Unknown Constant 0x0014 Integer 0 + * UK4 Unknown Constant (revision?) 0x0018 Short 0x003B + * UK5 Unknown Constant (version?) 0x001A Short 0x0003 + * UK6 Unknown Constant 0x001C Short -2 + * LOG_2_BIG_BLOCK_SIZE Log, base 2, of the big block size 0x001E Short 9 (2 ^ 9 = 512 bytes) + * LOG_2_SMALL_BLOCK_SIZE Log, base 2, of the miniFAT size 0x0020 Integer 6 (2 ^ 6 = 64 bytes) + * UK7 Unknown Constant 0x0024 Integer 0 + * UK8 Unknown Constant 0x0028 Integer 0 + * FAT_COUNT Number of elements in the FAT array 0x002C Integer required + * PROPERTIES_START Block index of the first block of the property table 0x0030 Integer required + * UK9 Unknown Constant 0x0034 Integer 0 + * UK10 Unknown Constant 0x0038 Integer 0x00001000 + * MINIFAT_START Block index of first big block containing the mini FATallocation table (MINIFAT) 0x003C Integer -2 + * UK11 Unknown Constant 0x0040 Integer 1 + * extraDIFAT Block index of the first block in the Extended Block Allocation Table 0x0044 Integer -2 + * extraDIFATCount Number of elements in the Extended File Allocation Table (to be added to the FAT) 0x0048 Integer 0 + * FAT_ARRAY Array of block indicies constituting the File Allocation Table (FAT) 0x004C, 0x0050, 0x0054 ... 0x01FC Integer[ ] -1 for unused elements, at least first element must be filled. + * N/A Header block data not otherwise described in this table N/A + */ +public class LEOHeader implements Serializable { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -422489164065975273L; + public final static int HEADER_SIZE = 0x200; + private transient ByteBuffer data; + private static int DIFATPOSITION = 76; // DIFAT start position WITHIN HEADER (DIFAT=where to find the FAT i.e the index to the FAT) + private int numFATSectors = -1; // FAT= array of Sector numbers + private int rootstart = -1; // Root Directory sector start + private int miniFATStart = -2; // miniFAT stores sectors for storages < 4096 + private int extraDIFATStart = -1; // if more than 109 FAT sectors are needed this stores start position of remaining DIFAT + private int numExtraDIFATSectors = -1; // usually 109 FAT sectors is enough; if > -1 more DIFAT is stored in other sectors + private int numMiniFATSectors = 0; + private int minStreamSize = 4096; // = 8 blocks minimum for a stream + public final static byte[] majick = + { + (byte) 0xd0, + (byte) 0xcf, + (byte) 0x11, + (byte) 0xe0, + (byte) 0xa1, + (byte) 0xb1, + (byte) 0x1a, + (byte) 0xe1, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00}; + + /** + * initialize from existing data + */ + protected boolean init() { + return this.init(this.data); + } + + /** + * structure of typical header: + * // 24, 25= 62, 0 (rev #) = 0x3E + * // 26, 27= 3, 0 (vers #) = 0x03 + * // 28, 29= -2, -1 (little endian) + * // 30, 31= 9, 0 (size of sector (9= 512, 7= 128)) + * // 32, 33= 6, 0 (size of short sector (6=64 bytes)) + * // 34->43= 0 (unused) + * // 44->47= Total # sectors in SAT *** + * // 48->51= Sector id of 1st sector + * // 52->55= 0 (unused) + * // 56->59= Minimum size of a standard stream (usually 4096) + * // 60->63= SecID of first sector in short-SAT + * // 64->67= Total # sectors in Short-SAT *** + * // 68->71= SecID of 1st sector of MSAT or -2 if no additional sectors are needed + * // 72->75= Total # sectors used for the MSAT + * // 76->436= MSAT (1st 109 sectors) + */ + private static void displayHeader(ByteBuffer data) { + for (int i = 0; i < data.capacity(); i++) { + io.starter.toolkit.Logger.log("[" + i + "] " + data.get(i)); + } + } + + + /** + * initialize the leo doc header + */ + protected boolean init(ByteBuffer dta) { + this.data = dta; + int pos; + + if (dta.limit() < 1) + throw new InvalidFileException("Empty input file."); + + try { + // sanity check -- is it a valid WorkBook? + for (int t = 0; t < 16; t++) { + if (dta.get(t) != majick[t]) { + throw new InvalidFileException("File is not valid OLE format: Bad Majick."); + } + } + } catch (Exception e) { + throw new InvalidFileException("File is not valid OLE format:" + dta.limit()); + } + + // get the rootstart 0x30 + pos = 0x30; + data.position(pos); // secId of 1st storage pos (==rootstart)[48] + rootstart = data.getInt(); + rootstart++; + rootstart *= BIGBLOCK.SIZE; + + // has DIFAT Extra sectors? -- extended data blocks for large XLS files + pos = 0x44; // [68] + data.position(pos); + extraDIFATStart = data.getInt(); // sector id of 1st sector of extra DIFAT sectors or -2 of no extra sectors used + numExtraDIFATSectors = data.getInt(); // total # sectors used for the DIFAT -- if # sectors or blocks > 109, more sectors are used + + // get minimum size of standard stream + pos = 56; + data.position(pos); + minStreamSize = data.getInt(); + + // starting at 0x2c (44) THE numFATSectors + pos = 0x2C; + data.position(pos); + numFATSectors = data.getInt(); // total # blocks used in the FAT chain (each block can hold 128 sector ids) + + + // 0x3c (60) THE miniFAT start + pos = 60; + data.position(pos); + miniFATStart = data.getInt(); // secId of 1st sector of the miniFAT sector chain or -2 if non-existant + + // pos 0x40= # miniFAT sectors [64] + pos = 0x40; + data.position(pos); + numMiniFATSectors = data.getInt(); + return true; + } + + /** + * get the number of the Extended DIFAT sectors + */ + public int getNumExtraDIFATSectors() { + return numExtraDIFATSectors; + } + + /** + * get the position of the extra DIFAT sector start + */ + public int getExtraDIFATStart() { + return this.extraDIFATStart; + } + + /** + * get the position of the Root Storage/1st Directory + */ + public int getRootStartPos() { + return rootstart; + } + + /** + * get the position of the miniFAT Start Block + */ + public int getMiniFATStart() { + return miniFATStart; + } + + /** + * get the FAT Sectors (sectors which hold the FAT or the chain that references all sectors in the file) + */ + public int[] getDIFAT() { + int numblks = Math.min(this.numFATSectors, 109); // more than 109 goes into DIFAT + int[] FAT = new int[numblks]; + int pos = DIFATPOSITION; // START OF DIFAT (indexes the FAT) + data.position(pos); // start of the 1st 109 secIds (4 bytes each==436 bytes) + for (int i = 0; i < numblks; i++) { + FAT[i] = data.getInt(); + FAT[i]++; + } + return FAT; + } + + /** + * get the Header bytes + */ + public ByteBuffer getBytes() { + return this.data; + } + + /** + * get a BIGBLOCK containing this Header's data + * ie: create a new header byte block. + */ + static final LEOHeader getPrototype(int[] FAT) { + + LEOHeader retval = new LEOHeader(); + + // get an empty BIGBLOCK + BIGBLOCK retblock = + (BIGBLOCK) BlockFactory.getPrototypeBlock(Block.BIG); + retval.setData(retblock.data); + retval.initMajickBytes(); + retval.initConstants(); + + // recreate the FAT and indexes to ... + retval.setFAT(FAT); + retval.setMiniFATStart(-2); // we don't rebuild miniFATs at this time ... + retval.setRootStorageStart(1); + retval.setExtraDIFATStart(-2); + retval.setNumExtraDIFATSectors(0); + return retval; + } + + void initConstants() { + int pos = 0x08; + data.position(pos); + data.putInt(0); + + pos = 0xc; + data.position(pos); + data.putInt(0); + + pos = 0x10; + data.position(pos); + data.putInt(0); + + pos = 0x14; + data.position(pos); + data.putInt(0); + + pos = 0x18; + data.position(pos); + data.putShort((short) 0x3e); + + pos = 0x1a; + data.position(pos); + data.putShort((short) 0x3); + + pos = 0x1c; + data.position(pos); + data.putShort((short) -2); + + pos = 0x1e; + data.position(pos); + data.putShort((short) 9); + + pos = 0x20; + data.position(pos); + data.putInt(6); + + pos = 0x24; + data.position(pos); + data.putInt(0); + + pos = 0x28; + data.position(pos); + data.putInt(0); + + pos = 0x34; + data.position(pos); + data.putInt(0); + + pos = 0x38; + data.position(pos); + data.putInt(0x00001000); + + } + + void initMajickBytes() { + // create the majick number + this.data.position(0); + data.put(majick); + } + + void setData(ByteBuffer dta) { + this.data = dta; + } + + /** + * Set the FAT or sector chain index + * + * @param FAT + */ + void setFAT(int[] FAT) { + int pos = DIFATPOSITION; + data.position(pos); + for (int i = 0; i < FAT.length; i++) { + if ((FAT.length * 4) >= (this.data.limit() - 4)) { + // TODO: Create extra DIFAT sectors FAT.length too big... + Logger.logWarn("WARNING: LEOHeader.setFAT() creating Extra FAT Sectors Not Implemented. Output file too large."); + } else { + data.putInt((FAT[i] - 1)); // todo: why decrement here? necessitates an increment in LEOFile.writeBytes + } + } + // fill rest with -1's === Empty Sector denotation + for (int i = data.position(); i < 512; i += 4) + data.putInt(-1); + + setNumFATSectors(FAT.length); + } + + void setRootStorageStart(int i) { + int pos = 0x30; + data.position(pos); + data.putInt(i); + } + + void setMiniFATStart(int i) { + int pos = 0x3c; + this.miniFATStart = i; + data.position(pos); + data.putInt(i); + miniFATStart = i; + } + + void setExtraDIFATStart(int i) { + int pos = 0x44; + data.position(pos); + data.putInt(i); + extraDIFATStart = i; + } + + void setNumExtraDIFATSectors(int i) { + int pos = 0x48; + data.position(pos); + data.putInt(i); + numExtraDIFATSectors = i; + } + + void setNumFATSectors(int i) { + int pos = 0x2c; + data.position(pos); + data.putInt(i); + numFATSectors = i; + } + + int getNumFATSectors() { + return numFATSectors; + } + + /** + * set the number of miniFAT sectors (each sector=64 bytes) + */ + void setNumMiniFATSectors(int i) { + int pos = 0x40; + data.position(pos); + data.putInt(i); + numMiniFATSectors = i; + } + + /** + * return the number of miniFAT sectors + * + * @return + */ + int getNumMiniFATSectors() { + return numMiniFATSectors; + } + + /** + * return the minimum stream size + * usually 4096 or 8 blocks + * + * @return + */ + int getMinStreamSize() { + return minStreamSize; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/LEOIndexingException.java b/src/main/java/io/starter/formats/LEO/LEOIndexingException.java index 6ff8bb8..41ebc6b 100644 --- a/src/main/java/io/starter/formats/LEO/LEOIndexingException.java +++ b/src/main/java/io/starter/formats/LEO/LEOIndexingException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,17 @@ */ package io.starter.formats.LEO; -/** This exception is thrown when an invalid indexing scheme occurs, currently only happens in +/** + * This exception is thrown when an invalid indexing scheme occurs, currently only happens in * miniFAT indexing - - */ public class LEOIndexingException extends RuntimeException { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -795980000366851485L; - private String err = "Error in FAT indexing"; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -795980000366851485L; + private String err = "Error in FAT indexing"; public LEOIndexingException(String er) { this.err = er; diff --git a/src/main/java/io/starter/formats/LEO/RootStorage.java b/src/main/java/io/starter/formats/LEO/RootStorage.java index 2db38b4..e519dda 100644 --- a/src/main/java/io/starter/formats/LEO/RootStorage.java +++ b/src/main/java/io/starter/formats/LEO/RootStorage.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,36 +22,36 @@ */ package io.starter.formats.LEO; -import io.starter.toolkit.*; -import java.util.*; -import java.nio.*; +import io.starter.toolkit.Logger; + +import java.nio.ByteBuffer; +import java.util.ArrayList; /** - * The Root Storage == The Root Directory is the 1st Directory in the Directory Stream + * The Root Storage == The Root Directory is the 1st Directory in the Directory Stream * This Directory is the root for all objects; it also stores the size and starting sector of the miniStream - * + *

        * The Root Directory entry behaves as both a stream and a storage object. It's name MUST = "Root Entry" - * */ -public class RootStorage extends io.starter.formats.LEO.Storage -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6568586717509723981L; +public class RootStorage extends io.starter.formats.LEO.Storage { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -6568586717509723981L; int DEBUGLEVEL = 0; - - - RootStorage(ByteBuffer b){ - super(b); + + + RootStorage(ByteBuffer b) { + super(b); } - - /** set the underlying byte array for this - Storage - - This appears to be a special case for RootStorage, as it seems to convert from a - smallblock based storage to a bigblock based storage. works.... I guess? - */ + + /** + * set the underlying byte array for this + * Storage + *

        + * This appears to be a special case for RootStorage, as it seems to convert from a + * smallblock based storage to a bigblock based storage. works.... I guess? + */ public void setBytes(byte[] b) { Block[] bs = BlockFactory.getBlocksFromByteArray(b, Block.BIG); if (super.myblocks != null) { @@ -63,10 +63,10 @@ public void setBytes(byte[] b) { for (int d = 0; d < bs.length; d++) this.addBlock(bs[d]); } - - - public byte[] getBytes(){ - if(DEBUGLEVEL>5)Logger.logInfo("Getting Root Storage Bytes...."); + + + public byte[] getBytes() { + if (DEBUGLEVEL > 5) Logger.logInfo("Getting Root Storage Bytes...."); return super.getBytes(); - } + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/SMALLBLOCK.java b/src/main/java/io/starter/formats/LEO/SMALLBLOCK.java index cb3d323..b0467d5 100644 --- a/src/main/java/io/starter/formats/LEO/SMALLBLOCK.java +++ b/src/main/java/io/starter/formats/LEO/SMALLBLOCK.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,31 +23,32 @@ package io.starter.formats.LEO; -/** LEO File SMALLBLOCK Information Record - - These blocks of data contain information related to the - LEO file format data blocks. - - depending on the size and complexity of the file, - these records may all be contained in the 'header' - block of the LEO Stream File. - - In files over that size, one or more SMALLBLOCK records - are inserted directly in the midst of substream records. - -*/ +/** + * LEO File SMALLBLOCK Information Record + *

        + * These blocks of data contain information related to the + * LEO file format data blocks. + *

        + * depending on the size and complexity of the file, + * these records may all be contained in the 'header' + * block of the LEO Stream File. + *

        + * In files over that size, one or more SMALLBLOCK records + * are inserted directly in the midst of substream records. + */ public class SMALLBLOCK extends BlockImpl { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7432771150988897281L; - public final static int SIZE = 64; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7432771150988897281L; + public final static int SIZE = 64; - /** returns the int representing the block type - */ - public final int getBlockType() { - return SMALL; - } + /** + * returns the int representing the block type + */ + public final int getBlockType() { + return SMALL; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/Storable.java b/src/main/java/io/starter/formats/LEO/Storable.java index 8f200a3..e29f334 100644 --- a/src/main/java/io/starter/formats/LEO/Storable.java +++ b/src/main/java/io/starter/formats/LEO/Storable.java @@ -2,46 +2,48 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.LEO; + import java.util.Vector; -/** Create a Storage interface/Generic implementation - to represent a file or index of files within a LEO filesystem. - - +/** + * Create a Storage interface/Generic implementation + * to represent a file or index of files within a LEO filesystem. */ public interface Storable { - /** causes the raw file data to be read in as a byte array - */ + /** causes the raw file data to be read in as a byte array + */ - /** associate this storage with its Block - table entries and data - */ - public void init(Vector dta, int[] tab); + /** + * associate this storage with its Block + * table entries and data + */ + void init(Vector dta, int[] tab); - /** sets whether this Storage's Data blocks are contained - in the Small or Big Block arrays. - */ - public void setBlockType(int type); + /** + * sets whether this Storage's Data blocks are contained + * in the Small or Big Block arrays. + */ + void setBlockType(int type); } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/Storage.java b/src/main/java/io/starter/formats/LEO/Storage.java index 20c224e..fd44742 100644 --- a/src/main/java/io/starter/formats/LEO/Storage.java +++ b/src/main/java/io/starter/formats/LEO/Storage.java @@ -2,418 +2,395 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.LEO; -import io.starter.toolkit.*; + import io.starter.formats.XLS.XLSConstants; -import java.util.*; -import java.io.*; -import java.nio.*; -import java.nio.charset.UnsupportedCharsetException; - - -/** Defines a 'file' in the LEO filesystem. contains pointers - to Block storages as well as the storage type etc. - - - Header (128 bytes): - Directory Entry Name (64 bytes): This field MUST contain a Unicode string for the storage or stream name encoded in UTF-16. The name MUST be terminated with a UTF-16 terminating null character. Thus storage and stream names are limited to 32 UTF-16 code points, including the terminating null character. When locating an object in the compound file except for the root storage, the directory entry name is compared using a special case-insensitive upper-case mapping, described in Red-Black Tree. The following characters are illegal and MUST NOT be part of the name: '/', '\', ':', '!'. - Directory Entry Name Length (2 bytes): This field MUST match the length of the Directory Entry Name Unicode string in bytes. The length MUST be a multiple of 2, and include the terminating null character in the count. This length MUST NOT exceed 64, the maximum size of the Directory Entry Name field. - Object Type (1 byte): This field MUST be 0x00, 0x01, 0x02, or 0x05, depending on the actual type of object. All other values are not valid. - 0= Unknown or unallocated - 1= Storage Object - 2- Stream Object - 5= Root Storage Object - Color Flag (1 byte): This field MUST be 0x00 (red) or 0x01 (black). All other values are not valid. - Left Sibling ID (4 bytes): This field contains the Stream ID of the left sibling. If there is no left sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF). - Right Sibling ID (4 bytes): This field contains the Stream ID of the right sibling. If there is no right sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF). - Child ID (4 bytes): This field contains the Stream ID of a child object. If there is no child object, then the field MUST be set to NOSTREAM (0xFFFFFFFF). - CLSID (16 bytes): This field contains an object classGUID, if this entry is a storage or root storage. If there is no object class GUID set on this object, then the field MUST be set to all zeroes. In a stream object, this field MUST be set to all zeroes. If not NULL, the object class GUID can be used as a parameter to launch applications. - State Bits (4 bytes): This field contains the user-defined flags if this entry is a storage object or root storage object. If there are no state bits set on the object, then this field MUST be set to all zeroes. - Creation Time (8 bytes): This field contains the creation time for a storage object. The Windows FILETIME structure is used to represent this field in UTC. If there is no creation time set on the object, this field MUST be all zeroes. For a root storage object, this field MUST be all zeroes, and the creation time is retrieved or set on the compound file itself. - Modified Time (8 bytes): This field contains the modification time for a storage object. The Windows FILETIME structure is used to represent this field in UTC. If there is no modified time set on the object, this field MUST be all zeroes. For a root storage object, this field MUST be all zeroes, and the modified time is retrieved or set on the compound file itself. - Starting Sector Location (4 bytes): This field contains the first sector location if this is a stream object. For a root storage object, this field MUST contain the first sector of the mini st ream, if the mini stream exists. - Stream Size (8 bytes): This 64-bit integer field contains the size of the user-defined data, if this is a stream object. For a root storage object, this field contains the size of the mini stream. - - ?? - offset type value const? function - 00: stream $pps_rawname ! name of the pps - 40: word $pps_sizeofname ! size of $pps_rawname - 42: byte $pps_type ! type of pps (1=storage|2=stream|5=root) - 43: byte $pps_uk0 ! ? - 44: long $pps_prev ! previous pps - 48: long $pps_next ! next pps - 4c: long $pps_dir ! directory pps - 50: stream 00 09 02 00 . ? - 54: long 0 . ? - 58: long c0 . ? - 5c: stream 00 00 00 46 . ? - 60: long 0 . ? - 64: long $pps_ts1s ! timestamp 1 : "seconds" creation time - 68: long $pps_ts1d ! timestamp 1 : "days" - 6c: long $pps_ts2s ! timestamp 2 : "seconds" modified time - 70: long $pps_ts2d ! timestamp 2 : "days" - 74: long $pps_sb ! starting block of property - 78: long $pps_size ! size of property - 7c: long . ? - - - -*/ +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.CompatibleVector; +import io.starter.toolkit.Logger; + +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + +/** + * Defines a 'file' in the LEO filesystem. contains pointers + * to Block storages as well as the storage type etc. + *

        + *

        + * Header (128 bytes): + * Directory Entry Name (64 bytes): This field MUST contain a Unicode string for the storage or stream name encoded in UTF-16. The name MUST be terminated with a UTF-16 terminating null character. Thus storage and stream names are limited to 32 UTF-16 code points, including the terminating null character. When locating an object in the compound file except for the root storage, the directory entry name is compared using a special case-insensitive upper-case mapping, described in Red-Black Tree. The following characters are illegal and MUST NOT be part of the name: '/', '\', ':', '!'. + * Directory Entry Name Length (2 bytes): This field MUST match the length of the Directory Entry Name Unicode string in bytes. The length MUST be a multiple of 2, and include the terminating null character in the count. This length MUST NOT exceed 64, the maximum size of the Directory Entry Name field. + * Object Type (1 byte): This field MUST be 0x00, 0x01, 0x02, or 0x05, depending on the actual type of object. All other values are not valid. + * 0= Unknown or unallocated + * 1= Storage Object + * 2- Stream Object + * 5= Root Storage Object + * Color Flag (1 byte): This field MUST be 0x00 (red) or 0x01 (black). All other values are not valid. + * Left Sibling ID (4 bytes): This field contains the Stream ID of the left sibling. If there is no left sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF). + * Right Sibling ID (4 bytes): This field contains the Stream ID of the right sibling. If there is no right sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF). + * Child ID (4 bytes): This field contains the Stream ID of a child object. If there is no child object, then the field MUST be set to NOSTREAM (0xFFFFFFFF). + * CLSID (16 bytes): This field contains an object classGUID, if this entry is a storage or root storage. If there is no object class GUID set on this object, then the field MUST be set to all zeroes. In a stream object, this field MUST be set to all zeroes. If not NULL, the object class GUID can be used as a parameter to launch applications. + * State Bits (4 bytes): This field contains the user-defined flags if this entry is a storage object or root storage object. If there are no state bits set on the object, then this field MUST be set to all zeroes. + * Creation Time (8 bytes): This field contains the creation time for a storage object. The Windows FILETIME structure is used to represent this field in UTC. If there is no creation time set on the object, this field MUST be all zeroes. For a root storage object, this field MUST be all zeroes, and the creation time is retrieved or set on the compound file itself. + * Modified Time (8 bytes): This field contains the modification time for a storage object. The Windows FILETIME structure is used to represent this field in UTC. If there is no modified time set on the object, this field MUST be all zeroes. For a root storage object, this field MUST be all zeroes, and the modified time is retrieved or set on the compound file itself. + * Starting Sector Location (4 bytes): This field contains the first sector location if this is a stream object. For a root storage object, this field MUST contain the first sector of the mini st ream, if the mini stream exists. + * Stream Size (8 bytes): This 64-bit integer field contains the size of the user-defined data, if this is a stream object. For a root storage object, this field contains the size of the mini stream. + *

        + * ?? + * offset type value const? function + * 00: stream $pps_rawname ! name of the pps + * 40: word $pps_sizeofname ! size of $pps_rawname + * 42: byte $pps_type ! type of pps (1=storage|2=stream|5=root) + * 43: byte $pps_uk0 ! ? + * 44: long $pps_prev ! previous pps + * 48: long $pps_next ! next pps + * 4c: long $pps_dir ! directory pps + * 50: stream 00 09 02 00 . ? + * 54: long 0 . ? + * 58: long c0 . ? + * 5c: stream 00 00 00 46 . ? + * 60: long 0 . ? + * 64: long $pps_ts1s ! timestamp 1 : "seconds" creation time + * 68: long $pps_ts1d ! timestamp 1 : "days" + * 6c: long $pps_ts2s ! timestamp 2 : "seconds" modified time + * 70: long $pps_ts2d ! timestamp 2 : "days" + * 74: long $pps_sb ! starting block of property + * 78: long $pps_size ! size of property + * 7c: long . ? + */ public class Storage extends BlockByteReader { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2065921767253066667L; - // make an enum in > 1.4 - static byte TYPE_INVALID = 0; - static byte TYPE_DIRECTORY = 1; - static byte TYPE_STREAM = 2; - static byte TYPE_LOCKBYTES = 3; - static byte TYPE_PROPERTY = 4; - static byte TYPE_ROOT = 5; - + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2065921767253066667L; + // make an enum in > 1.4 + static byte TYPE_INVALID = 0; + static byte TYPE_DIRECTORY = 1; + static byte TYPE_STREAM = 2; + static byte TYPE_LOCKBYTES = 3; + static byte TYPE_PROPERTY = 4; + static byte TYPE_ROOT = 5; + byte DIR_COLOR_RED = 0; byte DIR_COLOR_BLACK = 1; - - - private transient ByteBuffer headerData = ByteBuffer.allocate(128); - - // properties of this storage file. - String name = ""; - int nameSize = -1; - - public byte storageType = -1; - public byte directoryColor = -1; - public int prevStorageID = -1; - public int nextStorageID = -1; - public int childStorageID = -1; - - public int sz= -1; - public boolean miniStreamStorage= false; - private boolean isSpecial = false; - - protected List myblocks; - private int startBlock=0; - private int SIZE = -1; - private int blockType = -1; - private boolean initialized = false; - AbstractList idxs = new CompatibleVector(); - - Block lastblock = null; - - public void setBlocks(Block[] blks) { - myblocks = new ArrayList(); - for(int t=0;t 0) { - // get the name - pos = 0; - byte[] namebuf = new byte[nameSize]; - try{ - for (int i = 0; i < nameSize; i++) { - namebuf[i] = headerData.get(i); - } - }catch(Exception e){ - ; - } - try { - name = new String(namebuf, XLSConstants.UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logWarn( - "Storage error decoding storage name " + e); - } - name = name.substring(0, name.length() - 1); - // if this line fails, your header BBD index is wrong... - - } else { - // empty storage - } - pos = 0x42; - headerData.position(pos); - storageType = headerData.get(); - directoryColor = headerData.get(); - prevStorageID = headerData.getInt(); - nextStorageID = headerData.getInt(); - childStorageID = headerData.getInt(); - - sz= this.getActualFileSize(); - if (sz > 0 && sz < BIGBLOCK.SIZE) - miniStreamStorage= true; - if (LEOFile.DEBUG) - Logger.logInfo("Storage: " + name + " storageType: " + storageType + " directoryColor:" + directoryColor + - " prevSID:" + prevStorageID + " nextSID:" + nextStorageID + " childSID:" + childStorageID + " sz:" + sz); - } - - /** get the value of the prevProp variable - */ - public int getPrevStorageID() { - return this.prevStorageID; - } - - /** set the value of the prevProp variable - */ - public void setPrevStorageID(int o) { - int pos = 0x44; - headerData.position(pos); - headerData.putInt(o); - this.prevStorageID = o; - } - - /** get the value of the nextProp variable - */ - public int getNextStorageID() { - return this.nextStorageID; - } - - /** set the value of the nextProp variable - */ - public void setNextStorageID(int o) { - int pos = 0x48; - headerData.position(pos); - headerData.putInt(o); - this.nextStorageID = o; - } - - /** get the value of the child storage id - */ - public int getChildStorageID() { - return this.childStorageID; - } - - /** set the value of the child storage id - */ - public void setChildStorageID(int o) { - int pos = 0x4C; - headerData.position(pos); - headerData.putInt(o); - this.childStorageID = o; - } - - /** get the position of this Storage in the file bytes - */ - public int getFilePos() { - int ret = (this.getStartBlock() + 1) * SIZE; - return ret; - } - - /** return the existing header headerData for this storage - */ - public ByteBuffer getHeaderData() { - return headerData; - } - - /** return the underlying byte array for this - Storage - */ - public byte[] getBytes() { - return LEOFile.getBytes(myblocks); - } - - /** return the underlying byte array for this - Storage - */ + + public boolean getInitialized() { + return this.initialized; + } + + public void setInitialized(boolean b) { + this.initialized = b; + } + + + /** + * remove a block from this Storage's headerData + */ + void removeBlock(Block b) { + myblocks.remove(b); + } + + /** + * sets whether this Storage's headerData blocks are contained + * in the Small or Big Block arrays. + */ + public void setBlockType(int type) { + this.blockType = type; + switch (type) { + case Block.BIG: + SIZE = BIGBLOCK.SIZE; + break; + case Block.SMALL: + SIZE = SMALLBLOCK.SIZE; + break; + } + } + + /** + * returns whether this Storage's headerData blocks are contained + * in the Small or Big Block arrays. + */ + public int getBlockType() { + return this.blockType; + } + + public int getStorageType() { + return storageType; + } + + public int getDirectoryColor() { + return directoryColor; + } + + /** + * set the value of the prevProp variable + */ + public void setDirectoryColor(int o) { + int pos = 0x43; + headerData.position(pos); + headerData.put((byte) o); + this.directoryColor = (byte) o; + } + + + public String getName() { + return name; + } + + public String toString() { + return getName() + " n:" + nextStorageID + " p:" + prevStorageID + " c:" + childStorageID + " sz:" + sz; + } + + /** + * sets the storage name + * + * @param nm + */ + public void setName(String nm) { + try { + byte[] b = nm.getBytes(XLSConstants.UNICODEENCODING); + int pos = 0; // unicode name bytes + headerData.position(pos); + headerData.put(b); + pos = 0x40; // short name size + headerData.position(pos); + headerData.putShort((short) (b.length + 2)); + } catch (UnsupportedEncodingException e) { + } + name = nm; + } + + /**/ + protected Storage() { + // empty constructor... + } + + /** + * create a new Storage record with a byte + * array containing its record headerData. + */ + protected Storage(ByteBuffer buff) { + headerData = buff; + int pos = 0x40; + headerData.position(pos); + nameSize = headerData.getShort(); + if (nameSize > 0) { + // get the name + pos = 0; + byte[] namebuf = new byte[nameSize]; + try { + for (int i = 0; i < nameSize; i++) { + namebuf[i] = headerData.get(i); + } + } catch (Exception e) { + } + try { + name = new String(namebuf, XLSConstants.UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logWarn( + "Storage error decoding storage name " + e); + } + name = name.substring(0, name.length() - 1); + // if this line fails, your header BBD index is wrong... + + } else { + // empty storage + } + pos = 0x42; + headerData.position(pos); + storageType = headerData.get(); + directoryColor = headerData.get(); + prevStorageID = headerData.getInt(); + nextStorageID = headerData.getInt(); + childStorageID = headerData.getInt(); + + sz = this.getActualFileSize(); + if (sz > 0 && sz < BIGBLOCK.SIZE) + miniStreamStorage = true; + if (LEOFile.DEBUG) + Logger.logInfo("Storage: " + name + " storageType: " + storageType + " directoryColor:" + directoryColor + + " prevSID:" + prevStorageID + " nextSID:" + nextStorageID + " childSID:" + childStorageID + " sz:" + sz); + } + + /** + * get the value of the prevProp variable + */ + public int getPrevStorageID() { + return this.prevStorageID; + } + + /** + * set the value of the prevProp variable + */ + public void setPrevStorageID(int o) { + int pos = 0x44; + headerData.position(pos); + headerData.putInt(o); + this.prevStorageID = o; + } + + /** + * get the value of the nextProp variable + */ + public int getNextStorageID() { + return this.nextStorageID; + } + + /** + * set the value of the nextProp variable + */ + public void setNextStorageID(int o) { + int pos = 0x48; + headerData.position(pos); + headerData.putInt(o); + this.nextStorageID = o; + } + + /** + * get the value of the child storage id + */ + public int getChildStorageID() { + return this.childStorageID; + } + + /** + * set the value of the child storage id + */ + public void setChildStorageID(int o) { + int pos = 0x4C; + headerData.position(pos); + headerData.putInt(o); + this.childStorageID = o; + } + + /** + * get the position of this Storage in the file bytes + */ + public int getFilePos() { + int ret = (this.getStartBlock() + 1) * SIZE; + return ret; + } + + /** + * return the existing header headerData for this storage + */ + public ByteBuffer getHeaderData() { + return headerData; + } + + /** + * return the underlying byte array for this + * Storage + */ + public byte[] getBytes() { + return LEOFile.getBytes(myblocks); + } + + /** + * return the underlying byte array for this + * Storage + */ public OutputStream getByteStream() { return LEOFile.getByteStream(myblocks); } - /** return the underlying byte array for this - Storage - */ - public void writeBytes(OutputStream out) { - Iterator itx = myblocks.iterator(); - while(itx.hasNext()) { - ((Block)itx.next()).writeBytes(out); - } - } - - /** set the underlying byte array for this - Storage - */ - public void writeBytes(OutputStream out, int blen) { - Block[] bs = BlockFactory.getBlocksFromOutputStream(out, blen, Block.BIG); - if (myblocks != null) { - myblocks.clear(); - lastblock = null; - } else { - myblocks = new ArrayList(bs.length); - } - for (int d = 0; d < bs.length; d++) - this.addBlock(bs[d]); - } - - /** set the underlying byte array for this - Storage - */ - public void setOutputBytes(OutputStream b, int blen) { - Block[] bs = BlockFactory.getBlocksFromOutputStream(b, blen, Block.BIG); - if (myblocks != null) { - myblocks.clear(); - lastblock = null; - } else { - myblocks = new ArrayList(bs.length); - } - for (int d = 0; d < bs.length; d++) - this.addBlock(bs[d]); - } - - /** set the underlying byte array for this - Storage - */ - public void setBytes(byte[] b) { - if(this.miniStreamStorage) { - this.setMiniFATSectorBytes(b); - return; - } - Block[] bs = BlockFactory.getBlocksFromByteArray(b, Block.BIG); - if (myblocks != null) { - myblocks.clear(); - lastblock = null; - } else { - myblocks = new ArrayList(bs.length); - } - for (int d = 0; d < bs.length; d++) - this.addBlock(bs[d]); - } - - /** - * Sets bytes on a miniFAT storage - * @param b - */ - private void setMiniFATSectorBytes(byte[] b) { - Block[] bs = BlockFactory.getBlocksFromByteArray(b, Block.SMALL); + /** + * return the underlying byte array for this + * Storage + */ + public void writeBytes(OutputStream out) { + Iterator itx = myblocks.iterator(); + while (itx.hasNext()) { + ((Block) itx.next()).writeBytes(out); + } + } + + /** + * set the underlying byte array for this + * Storage + */ + public void writeBytes(OutputStream out, int blen) { + Block[] bs = BlockFactory.getBlocksFromOutputStream(out, blen, Block.BIG); if (myblocks != null) { myblocks.clear(); lastblock = null; @@ -424,300 +401,365 @@ private void setMiniFATSectorBytes(byte[] b) { this.addBlock(bs[d]); } - /** - * sets bytes for this storage; length of newbytes determines - * whether the storage is miniFAT or regular - *
        Incldes padding of bytes to ensure blocks are a factor of required block size - * @param newbytes - */ - public void setBytesWithOverage(byte[] newbytes) { - int actuallen= newbytes.length; - myblocks = new ArrayList(); // clear out - if (newbytes.length < StorageTable.BIGSTORAGE_SIZE) { // usual case - int overage= newbytes.length%128; - if (overage > 0) { - byte[] b= new byte[128-overage]; - newbytes= ByteTools.append(b, newbytes); - } - Block[] smallblocks= BlockFactory.getBlocksFromByteArray(newbytes, Block.SMALL); - for (int i= 0; i < smallblocks.length; i++) - this.addBlock(smallblocks[i]); - this.setBlockType(Block.SMALL); - } else { - int overage= newbytes.length%BIGBLOCK.SIZE; - if (overage > 0) { - byte[] b= new byte[BIGBLOCK.SIZE-overage]; - newbytes= ByteTools.append(b, newbytes); - } - Block[] blocks= BlockFactory.getBlocksFromByteArray(newbytes, Block.BIG); - for (int i= 0; i < blocks.length; i++) - this.addBlock(blocks[i]); - this.setBlockType(Block.BIG); - } - this.setActualFileSize(actuallen); - } + /** + * set the underlying byte array for this + * Storage + */ + public void setOutputBytes(OutputStream b, int blen) { + Block[] bs = BlockFactory.getBlocksFromOutputStream(b, blen, Block.BIG); + if (myblocks != null) { + myblocks.clear(); + lastblock = null; + } else { + myblocks = new ArrayList(bs.length); + } + for (int d = 0; d < bs.length; d++) + this.addBlock(bs[d]); + } + + /** + * set the underlying byte array for this + * Storage + */ + public void setBytes(byte[] b) { + if (this.miniStreamStorage) { + this.setMiniFATSectorBytes(b); + return; + } + Block[] bs = BlockFactory.getBlocksFromByteArray(b, Block.BIG); + if (myblocks != null) { + myblocks.clear(); + lastblock = null; + } else { + myblocks = new ArrayList(bs.length); + } + for (int d = 0; d < bs.length; d++) + this.addBlock(bs[d]); + } + + /** + * Sets bytes on a miniFAT storage + * + * @param b + */ + private void setMiniFATSectorBytes(byte[] b) { + Block[] bs = BlockFactory.getBlocksFromByteArray(b, Block.SMALL); + if (myblocks != null) { + myblocks.clear(); + lastblock = null; + } else { + myblocks = new ArrayList(bs.length); + } + for (int d = 0; d < bs.length; d++) + this.addBlock(bs[d]); + } + + /** + * sets bytes for this storage; length of newbytes determines + * whether the storage is miniFAT or regular + *
        Incldes padding of bytes to ensure blocks are a factor of required block size + * + * @param newbytes + */ + public void setBytesWithOverage(byte[] newbytes) { + int actuallen = newbytes.length; + myblocks = new ArrayList(); // clear out + if (newbytes.length < StorageTable.BIGSTORAGE_SIZE) { // usual case + int overage = newbytes.length % 128; + if (overage > 0) { + byte[] b = new byte[128 - overage]; + newbytes = ByteTools.append(b, newbytes); + } + Block[] smallblocks = BlockFactory.getBlocksFromByteArray(newbytes, Block.SMALL); + for (int i = 0; i < smallblocks.length; i++) + this.addBlock(smallblocks[i]); + this.setBlockType(Block.SMALL); + } else { + int overage = newbytes.length % BIGBLOCK.SIZE; + if (overage > 0) { + byte[] b = new byte[BIGBLOCK.SIZE - overage]; + newbytes = ByteTools.append(b, newbytes); + } + Block[] blocks = BlockFactory.getBlocksFromByteArray(newbytes, Block.BIG); + for (int i = 0; i < blocks.length; i++) + this.addBlock(blocks[i]); + this.setBlockType(Block.BIG); + } + this.setActualFileSize(actuallen); + } + /** * Associate this storage with it's data * (obtained by walking the miniFAT sector index to reference blocks in the miniStream - */ - public void initFromMiniStream(List miniStream, int[] miniFAT)throws LEOIndexingException { - if (this.getStartBlock() < 0) - return; - - if (miniFAT==null) { // error: trying to access smallblocks but no smallblock container found - if (LEOFile.DEBUG) - Logger.logWarn("initMiniFAT: no miniFAT container found"); - return; - } - myblocks = new ArrayList(); - boolean endloop = false; - Block thisBlock = null; - - int idx= this.getStartBlock(); - while (idx>=0) { - switch (idx) { - case -1 : // unused sector, shouldn't get to here - case -2 : // end of sector marker, exit - endloop = true; - break; - - default : - if (idx >= miniStream.size()) { - Logger.logWarn("MiniStream Error initting Storage: "+ this.getName()); - } else { - thisBlock = (Block) miniStream.get(idx); // miniFAT is 0-based (no header sector at position 0 as in regular FAT) - this.addBlock(thisBlock); - } - } - if (endloop) - break; - idx= miniFAT[idx]; // otherwise, walk the sector id chain - } - if (LEOFile.DEBUG) { - if ((int)Math.ceil(this.getActualFileSize()/64.0)!=myblocks.size()) - Logger.logErr("Number of miniStream Sectors does not equal storage size. Expected: " + (int)Math.ceil(this.getActualFileSize()/64.0) + ". Is: " + myblocks.size()); - } - this.setInitialized(true); - } - /** associate this storage with its blocks of data - * (obtained by walking down the FAT sector index chain to obtain blocks from the dta block store) - - for some bizarre reason, at idx 30208 the extraDIFAT jumps 5420 to 35628 - - */ - public void init(List dta, int[] FAT, boolean keepStartBlock) { - myblocks = new ArrayList(); - boolean endloop = false; - if (getStartBlock() < 0) - return; - Block thisbb = null; - int nextIdx = 0; //, lastIdx = 0, specialOffset = 1; - - // ksc: for root block and miniFAT cont., we add start block to block list - if (keepStartBlock) { - // for root storages, add rootstart block - thisbb = (Block) dta.get(startBlock+1); - this.addBlock(thisbb); //; - } - for (int i = startBlock; i < FAT.length;) { - nextIdx = FAT[i]; - - switch (nextIdx) { - - case -4 : // extraDIFAT sector - Logger.logInfo( - "INFO: Storage.init() encountered extra DIFAT sector."); - break; - - case -3 : // special block = DIFAT - defines the FAT - if (this.getActualFileSize() > 0) { - if (LEOFile.DEBUG) - Logger.logWarn( - "WARNING: Storage.init() Special block containing headerData."); - this.setIsSpecial(true); - - thisbb = (Block) dta.get(i++); - if (!thisbb.getIsSpecialBlock()) { - this.addBlock(thisbb); //; - } - nextIdx = i; - } else { - endloop = true; - } - break; - - case -1 : // unused - endloop = true; - break; // ksc - - case -2 : // end of Storage - keep end block - if (i + 1 < dta.size()) { - // get the "padding" block for later retrieval - thisbb = (Block) dta.get(i + 1); - if (thisbb == null) - break; - this.addBlock(thisbb); // - //} - } - endloop = true; - break; - - default : // normal block - if (dta.size() > nextIdx) - thisbb = (Block) dta.get(nextIdx); - if (thisbb == null) - break; - if (nextIdx != i + 1) { - //the next is a jumper, pickup the orphan - if (LEOFile.DEBUG) - Logger.logInfo( - "INFO: Storage init: jumper skipping: " - + String.valueOf(i)); - Block skipbb = (Block) dta.get(i + 1); - - this.addBlock(skipbb); // - } else - if (!thisbb.getIsSpecialBlock()) { // just skip as probably a bbdix in the midst of the secid chain - this.addBlock(thisbb); // - - } - } - i = nextIdx; - if (endloop) - break; - } - - if (LEOFile.DEBUG) { - int sz= this.getActualFileSize(); - if (sz!=0) { - if(Math.ceil(sz/512.0)!= myblocks.size()) - Logger.logWarn("Storage.init: Number of blocks do not equal storage size"); - } - } - this.setInitialized(true); - } - - /** - * adds a block of data to this storage - * @param b - */ - public void addBlock(Block b) { - if (lastblock != null) - lastblock.setNextBlock(b); - - if (b.getInitialized()) { - if (LEOFile.DEBUG)Logger.logWarn("ERROR: "+ this.toString()+ " - Block is already initialized."); - return; - } - b.setStorage(this); - b.setInitialized(true); - if(myblocks==null) - myblocks = new ArrayList(); - myblocks.add(b); - lastblock = b; - } - - /** set the storage type - */ - public void setStorageType(int i) { - int pos = 0x42; - headerData.position(pos); - headerData.put((byte)i); - storageType= (byte) i; - } - - /** set the size of the Storage headerData - */ - public void setActualFileSize(int i) { - int pos = 0x78; - this.sz= i; - headerData.position(pos); - headerData.putInt(i); - } - - /** get the size of the Storage headerData - */ - public int getActualFileSize() { - int pos = 0x78; - headerData.position(pos); - this.sz= headerData.getInt(); - return this.sz; - } - - /** set the starting block in the Block table - array for this Storage's headerData. - */ - public void setStartBlock(int i) { - this.startBlock = i; - int pos = 0x74; - this.headerData.position(pos); - this.headerData.putInt(i); - } - - /** get the starting block for the Storage headerData - */ - public int getStartBlock() { - int pos = 0x74; - headerData.position(pos); - startBlock = headerData.getInt(); - return startBlock; - } - - /** return this Storage's existing headerData Blocks. - */ - Block[] getBlocks() { - if (myblocks.size() < 1) { - return this.initBigBlocks(); - } - Block[] blox = new Block[this.myblocks.size()]; - blox = (Block[]) myblocks.toArray(blox); - return blox; - } - - /** return this Storage's headerData in new Blocks - */ - private Block[] initBigBlocks() { - // byte[] bb = this.getBytes(); - if (this.getLength() > 0) { - Block[] blks = BlockFactory.getBlocksFromByteArray(this.getBytes(), Block.BIG); - int t = 0; - myblocks.clear(); - for (; t < blks.length; t++) { - if (t + 1 < blks.length) - blks[t].setNextBlock(blks[t + 1]); - this.addBlock(blks[t]); - } - return blks; - } - return null; - } - - /** get the number of Block records - that this Storage needs to store its - byte array - */ - public int getSizeInBlocks() { - return myblocks.size(); - } - - /** Track BB Index info - */ - public void addIdx(int x) { - idxs.add(Integer.valueOf(x)); - } - public boolean equals(Object other) { - if(other.toString().equals(this.toString()))return true; - return false; - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - if (myblocks!=null) - myblocks.clear(); - if (idxs!=null) - idxs.clear(); - lastblock = null; - } + */ + public void initFromMiniStream(List miniStream, int[] miniFAT) throws LEOIndexingException { + if (this.getStartBlock() < 0) + return; + + if (miniFAT == null) { // error: trying to access smallblocks but no smallblock container found + if (LEOFile.DEBUG) + Logger.logWarn("initMiniFAT: no miniFAT container found"); + return; + } + myblocks = new ArrayList(); + boolean endloop = false; + Block thisBlock = null; + + int idx = this.getStartBlock(); + while (idx >= 0) { + switch (idx) { + case -1: // unused sector, shouldn't get to here + case -2: // end of sector marker, exit + endloop = true; + break; + + default: + if (idx >= miniStream.size()) { + Logger.logWarn("MiniStream Error initting Storage: " + this.getName()); + } else { + thisBlock = (Block) miniStream.get(idx); // miniFAT is 0-based (no header sector at position 0 as in regular FAT) + this.addBlock(thisBlock); + } + } + if (endloop) + break; + idx = miniFAT[idx]; // otherwise, walk the sector id chain + } + if (LEOFile.DEBUG) { + if ((int) Math.ceil(this.getActualFileSize() / 64.0) != myblocks.size()) + Logger.logErr("Number of miniStream Sectors does not equal storage size. Expected: " + (int) Math.ceil(this.getActualFileSize() / 64.0) + ". Is: " + myblocks.size()); + } + this.setInitialized(true); + } + + /** + * associate this storage with its blocks of data + * (obtained by walking down the FAT sector index chain to obtain blocks from the dta block store) + *

        + * for some bizarre reason, at idx 30208 the extraDIFAT jumps 5420 to 35628 + */ + public void init(List dta, int[] FAT, boolean keepStartBlock) { + myblocks = new ArrayList(); + boolean endloop = false; + if (getStartBlock() < 0) + return; + Block thisbb = null; + int nextIdx = 0; //, lastIdx = 0, specialOffset = 1; + + // ksc: for root block and miniFAT cont., we add start block to block list + if (keepStartBlock) { + // for root storages, add rootstart block + thisbb = (Block) dta.get(startBlock + 1); + this.addBlock(thisbb); //; + } + for (int i = startBlock; i < FAT.length; ) { + nextIdx = FAT[i]; + + switch (nextIdx) { + + case -4: // extraDIFAT sector + Logger.logInfo( + "INFO: Storage.init() encountered extra DIFAT sector."); + break; + + case -3: // special block = DIFAT - defines the FAT + if (this.getActualFileSize() > 0) { + if (LEOFile.DEBUG) + Logger.logWarn( + "WARNING: Storage.init() Special block containing headerData."); + this.setIsSpecial(true); + + thisbb = (Block) dta.get(i++); + if (!thisbb.getIsSpecialBlock()) { + this.addBlock(thisbb); //; + } + nextIdx = i; + } else { + endloop = true; + } + break; + + case -1: // unused + endloop = true; + break; // ksc + + case -2: // end of Storage - keep end block + if (i + 1 < dta.size()) { + // get the "padding" block for later retrieval + thisbb = (Block) dta.get(i + 1); + if (thisbb == null) + break; + this.addBlock(thisbb); // + //} + } + endloop = true; + break; + + default: // normal block + if (dta.size() > nextIdx) + thisbb = (Block) dta.get(nextIdx); + if (thisbb == null) + break; + if (nextIdx != i + 1) { + //the next is a jumper, pickup the orphan + if (LEOFile.DEBUG) + Logger.logInfo( + "INFO: Storage init: jumper skipping: " + + i); + Block skipbb = (Block) dta.get(i + 1); + + this.addBlock(skipbb); // + } else if (!thisbb.getIsSpecialBlock()) { // just skip as probably a bbdix in the midst of the secid chain + this.addBlock(thisbb); // + + } + } + i = nextIdx; + if (endloop) + break; + } + + if (LEOFile.DEBUG) { + int sz = this.getActualFileSize(); + if (sz != 0) { + if (Math.ceil(sz / 512.0) != myblocks.size()) + Logger.logWarn("Storage.init: Number of blocks do not equal storage size"); + } + } + this.setInitialized(true); + } + + /** + * adds a block of data to this storage + * + * @param b + */ + public void addBlock(Block b) { + if (lastblock != null) + lastblock.setNextBlock(b); + + if (b.getInitialized()) { + if (LEOFile.DEBUG) Logger.logWarn("ERROR: " + this.toString() + " - Block is already initialized."); + return; + } + b.setStorage(this); + b.setInitialized(true); + if (myblocks == null) + myblocks = new ArrayList(); + myblocks.add(b); + lastblock = b; + } + + /** + * set the storage type + */ + public void setStorageType(int i) { + int pos = 0x42; + headerData.position(pos); + headerData.put((byte) i); + storageType = (byte) i; + } + + /** + * set the size of the Storage headerData + */ + public void setActualFileSize(int i) { + int pos = 0x78; + this.sz = i; + headerData.position(pos); + headerData.putInt(i); + } + + /** + * get the size of the Storage headerData + */ + public int getActualFileSize() { + int pos = 0x78; + headerData.position(pos); + this.sz = headerData.getInt(); + return this.sz; + } + + /** + * set the starting block in the Block table + * array for this Storage's headerData. + */ + public void setStartBlock(int i) { + this.startBlock = i; + int pos = 0x74; + this.headerData.position(pos); + this.headerData.putInt(i); + } + + /** + * get the starting block for the Storage headerData + */ + public int getStartBlock() { + int pos = 0x74; + headerData.position(pos); + startBlock = headerData.getInt(); + return startBlock; + } + + /** + * return this Storage's existing headerData Blocks. + */ + Block[] getBlocks() { + if (myblocks.size() < 1) { + return this.initBigBlocks(); + } + Block[] blox = new Block[this.myblocks.size()]; + blox = (Block[]) myblocks.toArray(blox); + return blox; + } + + /** + * return this Storage's headerData in new Blocks + */ + private Block[] initBigBlocks() { + // byte[] bb = this.getBytes(); + if (this.getLength() > 0) { + Block[] blks = BlockFactory.getBlocksFromByteArray(this.getBytes(), Block.BIG); + int t = 0; + myblocks.clear(); + for (; t < blks.length; t++) { + if (t + 1 < blks.length) + blks[t].setNextBlock(blks[t + 1]); + this.addBlock(blks[t]); + } + return blks; + } + return null; + } + + /** + * get the number of Block records + * that this Storage needs to store its + * byte array + */ + public int getSizeInBlocks() { + return myblocks.size(); + } + + /** + * Track BB Index info + */ + public void addIdx(int x) { + idxs.add(Integer.valueOf(x)); + } + + public boolean equals(Object other) { + return other.toString().equals(this.toString()); + } + + /** + * clear out object references in prep for closing workbook + */ + public void close() { + if (myblocks != null) + myblocks.clear(); + if (idxs != null) + idxs.clear(); + lastblock = null; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/StorageNotFoundException.java b/src/main/java/io/starter/formats/LEO/StorageNotFoundException.java index 6645949..db7370c 100644 --- a/src/main/java/io/starter/formats/LEO/StorageNotFoundException.java +++ b/src/main/java/io/starter/formats/LEO/StorageNotFoundException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,16 +23,16 @@ package io.starter.formats.LEO; /** - * An exception to be thrown when a storage is not found in a LEO operation* + * An exception to be thrown when a storage is not found in a LEO operation* */ public class StorageNotFoundException extends Exception { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4253743894652780395L; - public StorageNotFoundException(Throwable cause) { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4253743894652780395L; + + public StorageNotFoundException(Throwable cause) { super(cause); } diff --git a/src/main/java/io/starter/formats/LEO/StorageTable.java b/src/main/java/io/starter/formats/LEO/StorageTable.java index ba70d66..b3be8b3 100644 --- a/src/main/java/io/starter/formats/LEO/StorageTable.java +++ b/src/main/java/io/starter/formats/LEO/StorageTable.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,275 +22,279 @@ */ package io.starter.formats.LEO; +import io.starter.formats.XLS.BiffRec; +import io.starter.formats.XLS.WorkBookException; +import io.starter.formats.XLS.XLSRecordFactory; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.CompatibleVector; +import io.starter.toolkit.Logger; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.WorkBookException; -import io.starter.formats.XLS.XLSRecordFactory; -import java.nio.*; +/** + * The directory system for an LEO file + * + * Extentech Inc. + */ +public class StorageTable implements Serializable { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 3399830613453524580L; + public final static int TABLE_SIZE = 0x200; + public final static int DIRECTORY_SIZE = 0x80; + public final static int BIGSTORAGE_SIZE = 4096; // default, should read from LEOHeader -/** The directory system for an LEO file - - Extentech Inc. + private LEOHeader myheader; -*/ -public class StorageTable implements Serializable{ + CompatibleVector directoryVector = new CompatibleVector(); // all directories in the LEO file + + // Directory collection + Hashtable directoryHashtable = new Hashtable(100, 0.9f); - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3399830613453524580L; - public final static int TABLE_SIZE = 0x200; - public final static int DIRECTORY_SIZE = 0x80; - public final static int BIGSTORAGE_SIZE = 4096; // default, should read from LEOHeader + private int dupct = 0; - private LEOHeader myheader; - CompatibleVector directoryVector = new CompatibleVector(); // all directories in the LEO file + /** + * clear out object references in prep for closing workbook + */ + public void close() { + myheader = null; - // Directory collection - Hashtable directoryHashtable = new Hashtable(100, 0.9f); + Iterator ii = directoryHashtable.keySet().iterator(); + while (ii.hasNext()) { + Storage s = (Storage) directoryHashtable.get(ii.next()); + s.close(); + } + directoryHashtable = new Hashtable(100, 0.9f); - private int dupct = 0; - + for (int i = 0; i < directoryVector.size(); i++) { + Storage s = (Storage) directoryVector.get(i); + s.close(); + } + directoryVector.clear(); + } + /** - * clear out object references in prep for closing workbook + * initialize the directory entry array + */ + public void init(ByteBuffer dta, LEOHeader h, List blockvect, int[] FAT) { + this.myheader = h; + byte[] data = LEOFile.getBytes(this.initDirectoryStream(blockvect, FAT)); + int psbsize = data.length; + int numRecs = psbsize / DIRECTORY_SIZE; + if (LEOFile.DEBUG) { + Logger.logInfo("Number of Directories: " + numRecs); + Logger.logInfo("Directories: " + Arrays.toString(data)); + } + + int pos = 0; + for (int i = 0; i < numRecs; i++) { + ByteBuffer b = ByteBuffer.allocate(DIRECTORY_SIZE); + b.order(ByteOrder.LITTLE_ENDIAN); + b.put(data, pos, DIRECTORY_SIZE); + pos += DIRECTORY_SIZE; + Storage rec = null; + try { + rec = new Storage(b); + } catch (Exception ex) { + throw new WorkBookException("StorageTable.init failed:" + ex.toString(), WorkBookException.UNSPECIFIED_INIT_ERROR); + } + if (i == 0) { + rec = new RootStorage(b); + if (!rec.getName().equals("Root Entry")) + rec.setName("Root Entry"); // can happen upon a mac-sourced file + } + this.addStorage(rec, -1); + } + } + + /** + * return the number of existing directories + * + * @return + */ + public int getNumDirectories() { + return directoryVector.size(); + } + + /** + * Create a new Storage and add to the directory array + *
        NOTE: add any associated data separately + * + * @param name Storage name - must be unique + * @param type Storage Type - 1= storage 2= stream 5= root 0=unknown or unallocated + * @param insertIdx id where to insert, or -1 to insert at end + * @return + */ + public Storage createStorage(String name, int type, int insertIdx) { + Storage s = null; + try { + ByteBuffer b = ByteBuffer.allocate(DIRECTORY_SIZE); + b.order(ByteOrder.LITTLE_ENDIAN); + b.put(new byte[DIRECTORY_SIZE]); + s = new Storage(b); + s.setName(name); + s.setStorageType(type); + s.setBlocks(new Block[0]); // init empty storages with 0 blocksW + s.setPrevStorageID(-1); + s.setNextStorageID(-1); + s.setChildStorageID(-1); + addStorage(s, insertIdx); + } catch (Exception ex) { + throw new WorkBookException("Storage.createStorage failed:" + ex.toString(), WorkBookException.UNSPECIFIED_INIT_ERROR); + } + return s; + } + + /** + * init the directories and gather their associated data (if present) + * including those directories whose data is gathered from the miniStream */ - public void close() { - myheader= null; - - Iterator ii= directoryHashtable.keySet().iterator(); - while (ii.hasNext()) { - Storage s= (Storage) directoryHashtable.get(ii.next()); - s.close(); - } - directoryHashtable = new Hashtable(100, 0.9f); - - - for (int i= 0; i < directoryVector.size(); i++) { - Storage s= (Storage) directoryVector.get(i); - s.close(); - } - directoryVector.clear(); - } - - /** - * initialize the directory entry array - * - */ - public void init(ByteBuffer dta,LEOHeader h,List blockvect,int[] FAT) { - this.myheader = h; - byte[] data = LEOFile.getBytes(this.initDirectoryStream(blockvect, FAT)); - int psbsize = data.length; - int numRecs = psbsize / DIRECTORY_SIZE; - if (LEOFile.DEBUG) { - Logger.logInfo("Number of Directories: " + numRecs); - Logger.logInfo("Directories: " + Arrays.toString(data)); - } - - int pos = 0; - for (int i = 0; i < numRecs; i++) { - ByteBuffer b = ByteBuffer.allocate(DIRECTORY_SIZE); - b.order(ByteOrder.LITTLE_ENDIAN); - b.put(data, pos, DIRECTORY_SIZE); - pos += DIRECTORY_SIZE; - Storage rec = null; - try{ - rec = new Storage(b); - }catch(Exception ex){ - throw new WorkBookException("StorageTable.init failed:" + ex.toString(), WorkBookException.UNSPECIFIED_INIT_ERROR); - } - if (i==0) { - rec = new RootStorage(b); - if (!rec.getName().equals("Root Entry")) - rec.setName("Root Entry"); // can happen upon a mac-sourced file - } - this.addStorage(rec, -1); - } - } - - /** - * return the number of existing directories - * @return - */ - public int getNumDirectories() { - return directoryVector.size(); - } - - /** - * Create a new Storage and add to the directory array - *
        NOTE: add any associated data separately - * @param name Storage name - must be unique - * @param type Storage Type - 1= storage 2= stream 5= root 0=unknown or unallocated - * @param insertIdx id where to insert, or -1 to insert at end - * @return - */ - public Storage createStorage(String name, int type, int insertIdx) { - Storage s= null; - try{ - ByteBuffer b = ByteBuffer.allocate(DIRECTORY_SIZE); - b.order(ByteOrder.LITTLE_ENDIAN); - b.put(new byte[DIRECTORY_SIZE]); - s= new Storage(b); - s.setName(name); - s.setStorageType(type); - s.setBlocks(new Block[0]); // init empty storages with 0 blocksW - s.setPrevStorageID(-1); - s.setNextStorageID(-1); - s.setChildStorageID(-1); - addStorage(s, insertIdx); - }catch(Exception ex){ - throw new WorkBookException("Storage.createStorage failed:" + ex.toString(), WorkBookException.UNSPECIFIED_INIT_ERROR); - } - return s; - } - - /** - * init the directories and gather their associated data (if present) - * including those directories whose data is gathered from the miniStream - */ - int[] miniFAT= null; // index into the miniStream - public void initDirectories(List blockvect, int[] FAT) { - Enumeration rcs = directoryVector.elements(); - List miniStream= null; - int totrecsize = 0; - - while (rcs.hasMoreElements()) { - Storage rec = (Storage) rcs.nextElement(); - String name = rec.getName(); - int recsize = rec.getActualFileSize(); + int[] miniFAT = null; // index into the miniStream + + public void initDirectories(List blockvect, int[] FAT) { + Enumeration rcs = directoryVector.elements(); + List miniStream = null; + int totrecsize = 0; + + while (rcs.hasMoreElements()) { + Storage rec = (Storage) rcs.nextElement(); + String name = rec.getName(); + int recsize = rec.getActualFileSize(); // if (LEOFile.DEBUG) // Logger.logInfo("Initializing Directory: " + name + ". Start=" + rec.getStartBlock() + ". Size=" + recsize); - totrecsize += recsize; - if (name.equals("Root Entry")) { - // also sets miniFAT ... ugly, I know ... - miniStream= this.initMiniStream(blockvect, FAT); // grab the mini stream (short sector container) (if any), indexed by miniFAT - if (LEOFile.DEBUG && miniFAT!=null) - Logger.logInfo("miniFAT: " + Arrays.toString(miniFAT)); - } - // this storage has it's data in the miniStream - else if ((recsize > 0) && (recsize < BIGSTORAGE_SIZE)) { - rec.setBlockType(Block.SMALL); - this.initStorage(rec, miniStream, miniFAT, Block.SMALL); // the miniStream is indexed by the miniFAT - // Regular Sector file storage - } else if ((recsize >= BIGSTORAGE_SIZE)) { - this.initStorage(rec, blockvect, FAT, Block.BIG); - - // a storage-less directory - } else if (recsize == 0) { - rec.setBlocks(new Block[0]); // init empty storages with 0 blocksW - } else { - if (LEOFile.DEBUG) - Logger.logWarn("Storage has no Block Type."); - } - } - if (LEOFile.DEBUG) - Logger.logInfo( - "Total Size used by Directories : " + String.valueOf(totrecsize)); - - } - - /** - * - // whenever a stream is shorter than a specific length - // it is stored as a short-stream or ministream. ministreamentries do not directly - // use sectors to store their data, but are all embedded in a specific - // internal control stream. - // first used sector is obtained from the root store ==> miniFAT chain - // it's secID chain is contained in the miniFAT - // The data used by all of the short-sectors container stream are concatenated - // in order of the secID chain. There is no header, so the first - // mini sector (secId= 0) is always located at position 0 in the mini Stream - // The miniFAT is the same as the FAT except the secID chains refer - // to miniSectors (64 bytes) rather than regular sectors or blocks (512 bytes) - */ - public List initMiniStream(List blockvect, int[] FAT) { - int pos = myheader.getMiniFATStart(); - if (pos==-2) - return null; // no miniStream sectors - - List miniFATSectors= getMiniFAT(pos, blockvect, FAT); - if (miniFATSectors.size() > 0) { + totrecsize += recsize; + if (name.equals("Root Entry")) { + // also sets miniFAT ... ugly, I know ... + miniStream = this.initMiniStream(blockvect, FAT); // grab the mini stream (short sector container) (if any), indexed by miniFAT + if (LEOFile.DEBUG && miniFAT != null) + Logger.logInfo("miniFAT: " + Arrays.toString(miniFAT)); + } + // this storage has it's data in the miniStream + else if ((recsize > 0) && (recsize < BIGSTORAGE_SIZE)) { + rec.setBlockType(Block.SMALL); + this.initStorage(rec, miniStream, miniFAT, Block.SMALL); // the miniStream is indexed by the miniFAT + // Regular Sector file storage + } else if ((recsize >= BIGSTORAGE_SIZE)) { + this.initStorage(rec, blockvect, FAT, Block.BIG); + + // a storage-less directory + } else if (recsize == 0) { + rec.setBlocks(new Block[0]); // init empty storages with 0 blocksW + } else { + if (LEOFile.DEBUG) + Logger.logWarn("Storage has no Block Type."); + } + } + if (LEOFile.DEBUG) + Logger.logInfo( + "Total Size used by Directories : " + totrecsize); + + } + + /** + * // whenever a stream is shorter than a specific length + * // it is stored as a short-stream or ministream. ministreamentries do not directly + * // use sectors to store their data, but are all embedded in a specific + * // internal control stream. + * // first used sector is obtained from the root store ==> miniFAT chain + * // it's secID chain is contained in the miniFAT + * // The data used by all of the short-sectors container stream are concatenated + * // in order of the secID chain. There is no header, so the first + * // mini sector (secId= 0) is always located at position 0 in the mini Stream + * // The miniFAT is the same as the FAT except the secID chains refer + * // to miniSectors (64 bytes) rather than regular sectors or blocks (512 bytes) + */ + public List initMiniStream(List blockvect, int[] FAT) { + int pos = myheader.getMiniFATStart(); + if (pos == -2) + return null; // no miniStream sectors + + List miniFATSectors = getMiniFAT(pos, blockvect, FAT); + if (miniFATSectors.size() > 0) { try { - Iterator sbz = miniFATSectors.iterator(); - while (sbz.hasNext()) { - BIGBLOCK sbb = (BIGBLOCK) sbz.next(); - sbb.setIsDepotBlock(true); - } - - miniFAT= LEOFile.readFAT(miniFATSectors); - - RootStorage rootStore; + Iterator sbz = miniFATSectors.iterator(); + while (sbz.hasNext()) { + BIGBLOCK sbb = (BIGBLOCK) sbz.next(); + sbb.setIsDepotBlock(true); + } + + miniFAT = LEOFile.readFAT(miniFATSectors); + + RootStorage rootStore; try { rootStore = (RootStorage) this.getDirectoryByName("Root Entry"); } catch (StorageNotFoundException e) { throw new InvalidFileException("Error parsing OLE File. OLE FileSystem Out of Spec: No Root Entry."); - } - // capture the short-stream container stream - int miniStreamStart= rootStore.getStartBlock(); - int miniStreamSize= rootStore.getActualFileSize(); - - Storage miniStream= new Storage(); - miniStream.setStartBlock(miniStreamStart); - miniStream.setActualFileSize(miniStreamSize); - miniStream.setName("miniStream"); - // obtain the miniStream from the regular bigblock store - this.initStorage(miniStream, blockvect, FAT, Block.BIG); - // now that we have the entire miniStream , break it up into mini Sector-sized blocks - // NOTE: only miniStreamSize bytes are usable - ignore rest - byte[] b= new byte[miniStreamSize]; - System.arraycopy(miniStream.getBytes(), 0, b, 0, miniStreamSize); - Block[] miniStreamBlocks = BlockFactory.getBlocksFromByteArray(b, Block.SMALL); - ArrayList miniStreamBlockList= new ArrayList(); - for (int i= 0; i < miniStreamBlocks.length; i++) { // should equal sbbsize - miniStreamBlockList.add(miniStreamBlocks[i]); - } - return miniStreamBlockList; - }catch(LEOIndexingException e) { - Logger.logWarn("initSBStorages: Error obtaining sbdIdx"); + } + // capture the short-stream container stream + int miniStreamStart = rootStore.getStartBlock(); + int miniStreamSize = rootStore.getActualFileSize(); + + Storage miniStream = new Storage(); + miniStream.setStartBlock(miniStreamStart); + miniStream.setActualFileSize(miniStreamSize); + miniStream.setName("miniStream"); + // obtain the miniStream from the regular bigblock store + this.initStorage(miniStream, blockvect, FAT, Block.BIG); + // now that we have the entire miniStream , break it up into mini Sector-sized blocks + // NOTE: only miniStreamSize bytes are usable - ignore rest + byte[] b = new byte[miniStreamSize]; + System.arraycopy(miniStream.getBytes(), 0, b, 0, miniStreamSize); + Block[] miniStreamBlocks = BlockFactory.getBlocksFromByteArray(b, Block.SMALL); + ArrayList miniStreamBlockList = new ArrayList(); + for (int i = 0; i < miniStreamBlocks.length; i++) { // should equal sbbsize + miniStreamBlockList.add(miniStreamBlocks[i]); + } + return miniStreamBlockList; + } catch (LEOIndexingException e) { + Logger.logWarn("initSBStorages: Error obtaining sbdIdx"); } - } - return null; - } - - /** - * extract the miniFAT sector index from the blockvect - * @param pos - * @param blockvect - * @param FAT - * @return - */ - private List getMiniFAT(int pos, List blockvect, int[] FAT) { - Storage miniFATContainer = null; - miniFATContainer = new Storage(); - miniFATContainer.setBlockType(Block.BIG); - miniFATContainer.setStartBlock(pos); - miniFATContainer.setStorageType(5); // set as root type to distinguish from regular storages - if (LEOFile.DEBUG) - Logger.logInfo("StorageTable.getMiniFAT() Initializing miniFAT Container."); - miniFATContainer.init(blockvect, FAT, true); - // miniFAT.setName("SBidx"); - // miniFAT index - return miniFATContainer.getBlockVect(); - } - - /** add a Storage to the directory array - * @param Storage storage to insert - * @param insertIdx -1 if add at end, otherwise insert at spot - */ - void addStorage(Storage rec, int insertIdx) { - String nm = rec.getName(); - if (directoryHashtable.get(nm) != null && !nm.equals("")) { + } + return null; + } + + /** + * extract the miniFAT sector index from the blockvect + * + * @param pos + * @param blockvect + * @param FAT + * @return + */ + private List getMiniFAT(int pos, List blockvect, int[] FAT) { + Storage miniFATContainer = null; + miniFATContainer = new Storage(); + miniFATContainer.setBlockType(Block.BIG); + miniFATContainer.setStartBlock(pos); + miniFATContainer.setStorageType(5); // set as root type to distinguish from regular storages + if (LEOFile.DEBUG) + Logger.logInfo("StorageTable.getMiniFAT() Initializing miniFAT Container."); + miniFATContainer.init(blockvect, FAT, true); + // miniFAT.setName("SBidx"); + // miniFAT index + return miniFATContainer.getBlockVect(); + } + + /** + * add a Storage to the directory array + * + * @param Storage storage to insert + * @param insertIdx -1 if add at end, otherwise insert at spot + */ + void addStorage(Storage rec, int insertIdx) { + String nm = rec.getName(); + if (directoryHashtable.get(nm) != null && !nm.equals("")) { /* KSC: with 2012 code changes, this breaks output: * if (LEOFile.DEBUG) Logger.logInfo( @@ -299,311 +303,322 @@ void addStorage(Storage rec, int insertIdx) { rec.setName(nm); Does not appear necessary */ - } - directoryHashtable.put(rec.getName(), rec); - if (insertIdx==-1) - directoryVector.add(rec); - else { - directoryVector.add(insertIdx, rec); - for (int i= 0; i < directoryVector.size(); i++) { // adjust prev, next, child ids if necessary - Storage s= (Storage) directoryVector.get(i); - if (s.getChildStorageID()>=insertIdx) - s.setChildStorageID(s.getChildStorageID()+1); - if (s.getPrevStorageID()>=insertIdx) - s.setPrevStorageID(s.getPrevStorageID()+1); - if (s.getNextStorageID()>=insertIdx) - s.setNextStorageID(s.getNextStorageID()+1); - - } - } - - if (false && LEOFile.DEBUG) { - Logger.logInfo( + } + directoryHashtable.put(rec.getName(), rec); + if (insertIdx == -1) + directoryVector.add(rec); + else { + directoryVector.add(insertIdx, rec); + for (int i = 0; i < directoryVector.size(); i++) { // adjust prev, next, child ids if necessary + Storage s = (Storage) directoryVector.get(i); + if (s.getChildStorageID() >= insertIdx) + s.setChildStorageID(s.getChildStorageID() + 1); + if (s.getPrevStorageID() >= insertIdx) + s.setPrevStorageID(s.getPrevStorageID() + 1); + if (s.getNextStorageID() >= insertIdx) + s.setNextStorageID(s.getNextStorageID() + 1); + + } + } + + if (false && LEOFile.DEBUG) { + Logger.logInfo( /*"INFO: StorageTable.addStorage() Storage size: " +*/ rec.getName() - + " Size: " - + String.valueOf(rec.getActualFileSize()) - + " Start Block: " - + rec.getStartBlock()); - } - } - - /* remove a Storage - */ - void removeStorage(Storage st) { - this.directoryHashtable.remove(st); - this.directoryVector.remove(st); - } - - /** - * init a Storage - */ - void initStorage(Storage rec, List sourceblocks, int[] idx, int blocktype) { - String name = rec.getName(); - int recsize = rec.getActualFileSize(); - if (LEOFile.DEBUG) - Logger.logInfo( - "Initializing Storage: " + name + " Retrieving Data." + - " Size: " - + String.valueOf(recsize) - + " type: " - + String.valueOf(blocktype) - + " startidx: " - + rec.getStartBlock() + (rec.getBlockType()==Block.SMALL?" MiniFAT":"")); - rec.setBlockType(blocktype); - if (("Root Entry").equals(name)) // ksc: shouldn't! - return; - - - if (rec.getBlockType() == Block.BIG) - rec.init(sourceblocks, idx, false); - else if (rec.getBlockType() == Block.SMALL) - rec.initFromMiniStream(sourceblocks, idx); - - if (LEOFile.DEBUG){ - if(rec.getBytes() != null){ - if (name == null) - name = "noname.dat"; - if (name.charAt(0) == '') - name = name.substring(1); - if (name.charAt(0) == '') - name = name.substring(1); - // if(blocktype == Block.BIG) - StorageTable.writeitout(rec.getBlockVect(), name + ".stor"); - } - } - rec.idxs = null; - } - - /** get the directory BLOCKS or Sectors - contains the header info for all of the directories - */ - List initDirectoryStream(List blockvect, int[] FAT) { - //getDirectoryBlocks() - Storage directories = null; - directories = new Storage(); - int pstart = (this.myheader.getRootStartPos() / BIGBLOCK.SIZE)-1; - directories.setStartBlock(pstart); - directories.setBlockType(Block.BIG); - directories.setStorageType(5); // set to root directory - directories.init(blockvect, FAT, true); // get additional directory stores, if any + + " Size: " + + rec.getActualFileSize() + + " Start Block: " + + rec.getStartBlock()); + } + } + + /* remove a Storage + */ + void removeStorage(Storage st) { + this.directoryHashtable.remove(st); + this.directoryVector.remove(st); + } + + /** + * init a Storage + */ + void initStorage(Storage rec, List sourceblocks, int[] idx, int blocktype) { + String name = rec.getName(); + int recsize = rec.getActualFileSize(); + if (LEOFile.DEBUG) + Logger.logInfo( + "Initializing Storage: " + name + " Retrieving Data." + + " Size: " + + recsize + + " type: " + + blocktype + + " startidx: " + + rec.getStartBlock() + (rec.getBlockType() == Block.SMALL ? " MiniFAT" : "")); + rec.setBlockType(blocktype); + if (("Root Entry").equals(name)) // ksc: shouldn't! + return; + + + if (rec.getBlockType() == Block.BIG) + rec.init(sourceblocks, idx, false); + else if (rec.getBlockType() == Block.SMALL) + rec.initFromMiniStream(sourceblocks, idx); + + if (LEOFile.DEBUG) { + if (rec.getBytes() != null) { + if (name == null) + name = "noname.dat"; + if (name.charAt(0) == '') + name = name.substring(1); + if (name.charAt(0) == '') + name = name.substring(1); + // if(blocktype == Block.BIG) + StorageTable.writeitout(rec.getBlockVect(), name + ".stor"); + } + } + rec.idxs = null; + } + + /** + * get the directory BLOCKS or Sectors + * contains the header info for all of the directories + */ + List initDirectoryStream(List blockvect, int[] FAT) { + //getDirectoryBlocks() + Storage directories = null; + directories = new Storage(); + int pstart = (this.myheader.getRootStartPos() / BIGBLOCK.SIZE) - 1; + directories.setStartBlock(pstart); + directories.setBlockType(Block.BIG); + directories.setStorageType(5); // set to root directory + directories.init(blockvect, FAT, true); // get additional directory stores, if any // directories.setName("StorageTable"); // if (LEOFile.DEBUG) // StorageTable.writeitout(directories.getBlockVect(), // "directoryStorage.dat"); - return directories.getBlockVect(); - - - - } - - /** generate new RootStorage bytes from - all of the Storage directories - */ - byte[] rebuildRootStore() { - /* - * Free (unused) directory entries are marked with Object Type 0x0 (unknown or unallocated). - * The entire directory entry should consist of all zeroes except for the child, right sibling, - * and left sibling pointers, which should be initialized to NOSTREAM (0xFFFFFFFF). - */ - while ((directoryVector.size()%4)!=0) { // add "null" storages to ensure multiples of 4 (128*4=512==minimum size) - this.createStorage("", 0, -1); - } - Enumeration e = directoryVector.elements(); - byte[] bytebuff = new byte[directoryVector.size() * DIRECTORY_SIZE]; - int pos = 0; - while (e.hasMoreElements()) { - Storage s= (Storage) e.nextElement(); - ByteBuffer buff = - this.getDirectoryHeaderBytes(s); - System.arraycopy(buff.array(), 0, bytebuff, pos, DIRECTORY_SIZE); - pos += DIRECTORY_SIZE; - } - return bytebuff; - } - - /** get the Hashtable of Storages within the LEO file - */ - Hashtable getStorageHash() { - return directoryHashtable; - } - - /** get the CompatibleVector of all Directories within the LEO file - */ - CompatibleVector getAllDirectories() { - return directoryVector; - } - - /** get the Root Storage Record - */ - RootStorage getRootStorage() throws StorageNotFoundException { - return (RootStorage) this.getDirectoryByName("Root Entry"); - } - - /** get the directory by name. throws StorageNotFoundException if not found. - */ - public Storage getDirectoryByName(String name) throws StorageNotFoundException { - if (directoryHashtable.get(name) != null) - return (Storage) directoryHashtable.get(name); - throw new StorageNotFoundException("Storage: " + name + " not located"); - } - - /** - * returns the Stream ID of the named directory, or -1 if not found - * @param name - * @return - * @throws StorageNotFoundException - */ - public int getDirectoryStreamID(String name) throws StorageNotFoundException { - for (int i= 0; i < directoryVector.size(); i++) { - Storage s= (Storage) directoryVector.get(i); - if (s.getName().equals(name)) - return i; - } - return -1; - } - - /** - * get the child directory (storage or stream) of the named storage - * @param name - * @return Storage or null - * @throws StorageNotFoundException - */ - public Storage getChild(String name) throws StorageNotFoundException { - Storage s= getDirectoryByName(name); - if (s!=null) { - int child= s.getChildStorageID(); - if (child>-1) - return (Storage) directoryVector.get(child); - } - return null; - } - /** - * get the next directory (storage or stream) of the named storage - * @param name - * @return Storage or null - * @throws StorageNotFoundException - */ - public Storage getNext(String name) throws StorageNotFoundException { - Storage s= getDirectoryByName(name); - if (s!=null) { - int next= s.getNextStorageID(); - if (next>-1) - return (Storage) directoryVector.get(next); - } - return null; - } - - /** - * get the previous directory (storage or stream) of the named storage - * @param name - * @return Storage or null - * @throws StorageNotFoundException - */ - public Storage getPrevious(String name) throws StorageNotFoundException { - Storage s= getDirectoryByName(name); - if (s!=null) { - int prev= s.getNextStorageID(); - if (prev>-1) - return (Storage) directoryVector.get(prev); - } - return null; - } - - /** get the header record for a Directory - */ - ByteBuffer getDirectoryHeaderBytes(Storage thisStorage) { - // create a new byte buffer - ByteBuffer buff = null; - buff = thisStorage.getHeaderData(); - // 20100304 KSC: don't reset anything, just return - return buff; - } - - /** write out the directory bytes for debugging - */ - public final static void writeitout(List blocks, String name) { - try { - FileOutputStream fos = - new FileOutputStream(new File(System.getProperty("user.dir") + "\\storages\\" + name)); - fos.write(LEOFile.getBytes(blocks)); - } catch (IOException a) { - ; - } - } - - public void DEBUG() { - io.starter.toolkit.Logger.log("DIRECTORY CONTENTS:"); - for (int i= 0; i < directoryVector.size(); i++) { - Storage s= (Storage) directoryVector.get(i); - String n= s.getName(); - Logger.logInfo("Storage: " + n+ " storageType: " + s.getStorageType() + " directoryColor:" + s.getDirectoryColor() + - " prevSID:" + s.getPrevStorageID() + " nextSID:" + s.getNextStorageID() + " childSID:" + s.getChildStorageID()+ " sz:" + s.getActualFileSize()); - // special storages - if (n.equals("Root Entry")) { - Logger.logInfo("Root Header: " + Arrays.toString(s.getHeaderData().array())); - - /*********************************** - // KSC: TESTING for XLS-97: - !!! Set creation time and modified time on root storage to 0 - int p = 100; - s.getHeaderData().position(p); - Long tsCreated= s.getHeaderData().getLong(); - Long tsModified= s.getHeaderData().getLong(); - */ - if (s.myblocks!=null) { - int zz= 0; - if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) - io.starter.toolkit.Logger.log("BLOCK 1:\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK)s.myblocks.get(zz)).getBytes())); - else - io.starter.toolkit.Logger.log("BLOCK 1:\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK)s.myblocks.get(zz)).getBytes())); - } - } else if (n.equals("Workbook")) { - ; //skip - } else if (n.equals("\1CompObj")) { - BlockByteReader bytes= s.getBlockReader(); - int len= bytes.getLength(); - BiffRec rec = new io.starter.formats.XLS.XLSRecord(); // 4 bytes are header ... - rec.setByteReader(bytes); - rec.setLength(len); - int slen= ByteTools.readInt(rec.getBytesAt(24, 4)); // actually position 28 - if (slen >= 0) { - String ss= new String (rec.getBytesAt(28, slen)); // AnsiUserType= a display name of the linked object or embedded object. - io.starter.toolkit.Logger.log("\tOLE Object:" + ss); - } - // AnsiClipboardFormat (variable) + return directories.getBlockVect(); + + + } + + /** + * generate new RootStorage bytes from + * all of the Storage directories + */ + byte[] rebuildRootStore() { + /* + * Free (unused) directory entries are marked with Object Type 0x0 (unknown or unallocated). + * The entire directory entry should consist of all zeroes except for the child, right sibling, + * and left sibling pointers, which should be initialized to NOSTREAM (0xFFFFFFFF). + */ + while ((directoryVector.size() % 4) != 0) { // add "null" storages to ensure multiples of 4 (128*4=512==minimum size) + this.createStorage("", 0, -1); + } + Enumeration e = directoryVector.elements(); + byte[] bytebuff = new byte[directoryVector.size() * DIRECTORY_SIZE]; + int pos = 0; + while (e.hasMoreElements()) { + Storage s = (Storage) e.nextElement(); + ByteBuffer buff = + this.getDirectoryHeaderBytes(s); + System.arraycopy(buff.array(), 0, bytebuff, pos, DIRECTORY_SIZE); + pos += DIRECTORY_SIZE; + } + return bytebuff; + } + + /** + * get the Hashtable of Storages within the LEO file + */ + Hashtable getStorageHash() { + return directoryHashtable; + } + + /** + * get the CompatibleVector of all Directories within the LEO file + */ + CompatibleVector getAllDirectories() { + return directoryVector; + } + + /** + * get the Root Storage Record + */ + RootStorage getRootStorage() throws StorageNotFoundException { + return (RootStorage) this.getDirectoryByName("Root Entry"); + } + + /** + * get the directory by name. throws StorageNotFoundException if not found. + */ + public Storage getDirectoryByName(String name) throws StorageNotFoundException { + if (directoryHashtable.get(name) != null) + return (Storage) directoryHashtable.get(name); + throw new StorageNotFoundException("Storage: " + name + " not located"); + } + + /** + * returns the Stream ID of the named directory, or -1 if not found + * + * @param name + * @return + * @throws StorageNotFoundException + */ + public int getDirectoryStreamID(String name) throws StorageNotFoundException { + for (int i = 0; i < directoryVector.size(); i++) { + Storage s = (Storage) directoryVector.get(i); + if (s.getName().equals(name)) + return i; + } + return -1; + } + + /** + * get the child directory (storage or stream) of the named storage + * + * @param name + * @throws StorageNotFoundException + * @return Storage or null + */ + public Storage getChild(String name) throws StorageNotFoundException { + Storage s = getDirectoryByName(name); + if (s != null) { + int child = s.getChildStorageID(); + if (child > -1) + return (Storage) directoryVector.get(child); + } + return null; + } + + /** + * get the next directory (storage or stream) of the named storage + * + * @param name + * @throws StorageNotFoundException + * @return Storage or null + */ + public Storage getNext(String name) throws StorageNotFoundException { + Storage s = getDirectoryByName(name); + if (s != null) { + int next = s.getNextStorageID(); + if (next > -1) + return (Storage) directoryVector.get(next); + } + return null; + } + + /** + * get the previous directory (storage or stream) of the named storage + * + * @param name + * @throws StorageNotFoundException + * @return Storage or null + */ + public Storage getPrevious(String name) throws StorageNotFoundException { + Storage s = getDirectoryByName(name); + if (s != null) { + int prev = s.getNextStorageID(); + if (prev > -1) + return (Storage) directoryVector.get(prev); + } + return null; + } + + /** + * get the header record for a Directory + */ + ByteBuffer getDirectoryHeaderBytes(Storage thisStorage) { + // create a new byte buffer + ByteBuffer buff = null; + buff = thisStorage.getHeaderData(); + // 20100304 KSC: don't reset anything, just return + return buff; + } + + /** + * write out the directory bytes for debugging + */ + public final static void writeitout(List blocks, String name) { + try { + FileOutputStream fos = + new FileOutputStream(new File(System.getProperty("user.dir") + "\\storages\\" + name)); + fos.write(LEOFile.getBytes(blocks)); + } catch (IOException a) { + } + } + + public void DEBUG() { + io.starter.toolkit.Logger.log("DIRECTORY CONTENTS:"); + for (int i = 0; i < directoryVector.size(); i++) { + Storage s = (Storage) directoryVector.get(i); + String n = s.getName(); + Logger.logInfo("Storage: " + n + " storageType: " + s.getStorageType() + " directoryColor:" + s.getDirectoryColor() + + " prevSID:" + s.getPrevStorageID() + " nextSID:" + s.getNextStorageID() + " childSID:" + s.getChildStorageID() + " sz:" + s.getActualFileSize()); + // special storages + if (n.equals("Root Entry")) { + Logger.logInfo("Root Header: " + Arrays.toString(s.getHeaderData().array())); + + /*********************************** + // KSC: TESTING for XLS-97: + !!! Set creation time and modified time on root storage to 0 + int p = 100; + s.getHeaderData().position(p); + Long tsCreated= s.getHeaderData().getLong(); + Long tsModified= s.getHeaderData().getLong(); + */ + if (s.myblocks != null) { + int zz = 0; + if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) + io.starter.toolkit.Logger.log("BLOCK 1:\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK) s.myblocks.get(zz)).getBytes())); + else + io.starter.toolkit.Logger.log("BLOCK 1:\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK) s.myblocks.get(zz)).getBytes())); + } + } else if (n.equals("Workbook")) { + //skip + } else if (n.equals("\1CompObj")) { + BlockByteReader bytes = s.getBlockReader(); + int len = bytes.getLength(); + BiffRec rec = new io.starter.formats.XLS.XLSRecord(); // 4 bytes are header ... + rec.setByteReader(bytes); + rec.setLength(len); + int slen = ByteTools.readInt(rec.getBytesAt(24, 4)); // actually position 28 + if (slen >= 0) { + String ss = new String(rec.getBytesAt(28, slen)); // AnsiUserType= a display name of the linked object or embedded object. + io.starter.toolkit.Logger.log("\tOLE Object:" + ss); + } + // AnsiClipboardFormat (variable) // io.starter.toolkit.Logger.log("\t" + Arrays.toString(rec.getData())); - } else if (n.startsWith("000")) { // pivot cache - if (s.myblocks!=null) { - for (int zz= 0; zz < s.myblocks.size(); zz++) - if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) - io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK)s.myblocks.get(zz)).getBytes())); - else - io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK)s.myblocks.get(zz)).getBytes())); - } - BlockByteReader bytes= s.getBlockReader(); - int len= bytes.getLength(); - for (int z = 0; z <= len - 4;) { - byte[] headerbytes = bytes.getHeaderBytes(z); - short opcode = ByteTools.readShort(headerbytes[0], headerbytes[1]); - int reclen = ByteTools.readShort(headerbytes[2], headerbytes[3]); - BiffRec rec = XLSRecordFactory.getBiffRecord( opcode ); - rec.setByteReader(bytes); - rec.setOffset(z); - rec.setLength((short) reclen); - rec.init(); - io.starter.toolkit.Logger.log("\t\t" + rec.toString()); - z+=reclen+4; - } - } else { - if (s.myblocks!=null) { - for (int zz= 0; zz < s.myblocks.size(); zz++) - if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) - io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK)s.myblocks.get(zz)).getBytes())); - else - io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK)s.myblocks.get(zz)).getBytes())); - } - } - } - } + } else if (n.startsWith("000")) { // pivot cache + if (s.myblocks != null) { + for (int zz = 0; zz < s.myblocks.size(); zz++) + if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK) s.myblocks.get(zz)).getBytes())); + else + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK) s.myblocks.get(zz)).getBytes())); + } + BlockByteReader bytes = s.getBlockReader(); + int len = bytes.getLength(); + for (int z = 0; z <= len - 4; ) { + byte[] headerbytes = bytes.getHeaderBytes(z); + short opcode = ByteTools.readShort(headerbytes[0], headerbytes[1]); + int reclen = ByteTools.readShort(headerbytes[2], headerbytes[3]); + BiffRec rec = XLSRecordFactory.getBiffRecord(opcode); + rec.setByteReader(bytes); + rec.setOffset(z); + rec.setLength((short) reclen); + rec.init(); + io.starter.toolkit.Logger.log("\t\t" + rec.toString()); + z += reclen + 4; + } + } else { + if (s.myblocks != null) { + for (int zz = 0; zz < s.myblocks.size(); zz++) + if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK) s.myblocks.get(zz)).getBytes())); + else + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK) s.myblocks.get(zz)).getBytes())); + } + } + } + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/Alignment.java b/src/main/java/io/starter/formats/OOXML/Alignment.java index 20ebcd2..9ac22a6 100644 --- a/src/main/java/io/starter/formats/OOXML/Alignment.java +++ b/src/main/java/io/starter/formats/OOXML/Alignment.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,99 +22,99 @@ */ package io.starter.formats.OOXML; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; -import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; /** * alignment (Alignment) OOMXL element - * + *

        * Formatting information pertaining to text alignment in cells. There are a variety of choices for how text is * aligned both horizontally and vertically, as well as indentation settings, and so on. - + *

        * parent: (styles.xml) xf, dxf * children: none - * */ -public class Alignment implements OOXMLElement{ +public class Alignment implements OOXMLElement { + + private static final long serialVersionUID = 995367747930839216L; + private HashMap attrs = null; + + public Alignment(HashMap attrs) { + this.attrs = attrs; + } + + public Alignment(Alignment a) { + this.attrs = a.attrs; + } - private static final long serialVersionUID = 995367747930839216L; - private HashMap attrs= null; - - public Alignment(HashMap attrs) { - this.attrs= attrs; - } - public Alignment(Alignment a) { - this.attrs= a.attrs; - } - public static OOXMLElement parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("alignment")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("alignment")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("alignment.parseOOXML: " + e.toString()); - } - Alignment a= new Alignment(attrs); - return a; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("alignment")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("alignment")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("alignment.parseOOXML: " + e.toString()); + } + Alignment a = new Alignment(attrs); + return a; } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= i.next(); - String val= attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new Alignment(this); - } - - /** - * - * @param type horizontal vertical - * @return - */ - public String getAlignment(String type) { - // attributes: - // horizontal: center, centerContinuous, fill, general, justify, left, right, distributed - // indent: int value - // justifyLastLine: bool - // readingOrder: 0=Context Dependent, 1=Left-to-Right, 2=Right-to-Left - // relativeIndent: # - // shrinkToFit: bool - // textRotation: degrees from 0-180 - // vertical: bottom, centered, distributed, justify, top - // wrapText - true/false - if (attrs!=null) { - return attrs.get(type); - } - return null; - } + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Alignment(this); + } + + /** + * @param type horizontal vertical + * @return + */ + public String getAlignment(String type) { + // attributes: + // horizontal: center, centerContinuous, fill, general, justify, left, right, distributed + // indent: int value + // justifyLastLine: bool + // readingOrder: 0=Context Dependent, 1=Left-to-Right, 2=Right-to-Left + // relativeIndent: # + // shrinkToFit: bool + // textRotation: degrees from 0-180 + // vertical: bottom, centered, distributed, justify, top + // wrapText - true/false + if (attrs != null) { + return attrs.get(type); + } + return null; + } } diff --git a/src/main/java/io/starter/formats/OOXML/AutoFilter.java b/src/main/java/io/starter/formats/OOXML/AutoFilter.java index 931ae76..51e7026 100644 --- a/src/main/java/io/starter/formats/OOXML/AutoFilter.java +++ b/src/main/java/io/starter/formats/OOXML/AutoFilter.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,87 +22,87 @@ */ package io.starter.formats.OOXML; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; -import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; /** * autoFilter (AutoFilter Settings) * AutoFilter temporarily hides rows based on a filter criteria, which is applied column by column to a table of data * in the worksheet. This collection expresses AutoFilter settings. - * + *

        * parent: worksheet, table, filter, customSheetView * children: filterColumn (0+), sortState * attributes: ref - * - * */ // TODO: finish sortState // TODO: finish filterColumn children filters->filter, dataGroupItem -public class AutoFilter implements OOXMLElement{ - - private static final long serialVersionUID = 7111401348177004218L; - private String ref= null; - private ArrayList filterColumns= null; - - public AutoFilter(String ref, ArrayList f) { - this.ref= ref; - this.filterColumns= f; - } - public AutoFilter(AutoFilter a) { - this.ref= a.ref; - this.filterColumns= a.filterColumns; - } - - +public class AutoFilter implements OOXMLElement { + + private static final long serialVersionUID = 7111401348177004218L; + private String ref = null; + private ArrayList filterColumns = null; + + public AutoFilter(String ref, ArrayList f) { + this.ref = ref; + this.filterColumns = f; + } + + public AutoFilter(AutoFilter a) { + this.ref = a.ref; + this.filterColumns = a.filterColumns; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp) { - String ref= null; - ArrayList f= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("autoFilter")) { // get ref attribute - if (xpp.getAttributeCount()==1) ref= xpp.getAttributeValue(0); - } else if (tnm.equals("sortState")) { - } else if (tnm.equals("filterColumn")) { - if (f==null) f= new ArrayList(); - f.add((FilterColumn) FilterColumn.parseOOXML(xpp)); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("autoFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("autoFilter.parseOOXML: " + e.toString()); - } - AutoFilter a= new AutoFilter(ref, f); - return a; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (filterColumns!=null) { - for (int i= 0; i < filterColumns.size(); i++) - ooxml.append(((FilterColumn) filterColumns.get(i)).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new AutoFilter(this); - } + String ref = null; + ArrayList f = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("autoFilter")) { // get ref attribute + if (xpp.getAttributeCount() == 1) ref = xpp.getAttributeValue(0); + } else if (tnm.equals("sortState")) { + } else if (tnm.equals("filterColumn")) { + if (f == null) f = new ArrayList(); + f.add(FilterColumn.parseOOXML(xpp)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("autoFilter")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("autoFilter.parseOOXML: " + e.toString()); + } + AutoFilter a = new AutoFilter(ref, f); + return a; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (filterColumns != null) { + for (int i = 0; i < filterColumns.size(); i++) + ooxml.append(filterColumns.get(i).getOOXML()); + } + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new AutoFilter(this); + } } /** @@ -110,88 +110,87 @@ public OOXMLElement cloneElement() { * The filterColumn collection identifies a particular column in the AutoFilter range and specifies filter information * that has been applied to this column. If a column in the AutoFilter range has no criteria specified, then there is * no corresponding filterColumn collection expressed for that column - * + *

        * parent: autoFilter * children: CHOICE OF: colorFilter, customFilters, dynamicFilter, filters, iconFilter, top10 * attributes: colId REQ, hiddenButton, showButton - * - * */ -class FilterColumn implements OOXMLElement{ - - private static final long serialVersionUID = 5005589034415840928L; - private HashMap attrs= null; - private Object filter= null; // CHOICE of filter - - public FilterColumn(HashMap attrs, Object filter) { - this.attrs= attrs; - this.filter= filter; - } - public FilterColumn(FilterColumn f) { - this.attrs= f.attrs; - this.filter= f.filter; - } - - +class FilterColumn implements OOXMLElement { + + private static final long serialVersionUID = 5005589034415840928L; + private HashMap attrs = null; + private Object filter = null; // CHOICE of filter + + public FilterColumn(HashMap attrs, Object filter) { + this.attrs = attrs; + this.filter = filter; + } + + public FilterColumn(FilterColumn f) { + this.attrs = f.attrs; + this.filter = f.filter; + } + + public static FilterColumn parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - Object filter= null; // CHOICE of filter - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("filterColumn")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("colorFilter")) { - filter= (ColorFilter) ColorFilter.parseOOXML(xpp); - } else if (tnm.equals("customFilters")) { - } else if (tnm.equals("dynamicFilter")) { - } else if (tnm.equals("filters")) { - } else if (tnm.equals("iconFilter")) { - } else if (tnm.equals("top10")) { - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("filterColumn")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("filterColumn.parseOOXML: " + e.toString()); - } - FilterColumn f= new FilterColumn(attrs, filter); - return f; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (filter instanceof ColorFilter) ooxml.append(((ColorFilter) filter).getOOXML()); - if (filter instanceof CustomFilters) ooxml.append(((CustomFilters) filter).getOOXML()); - if (filter instanceof DynamicFilter) ooxml.append(((DynamicFilter) filter).getOOXML()); - if (filter instanceof Filters) ooxml.append(((Filters) filter).getOOXML()); - if (filter instanceof IconFilter) ooxml.append(((IconFilter) filter).getOOXML()); - if (filter instanceof Top10) ooxml.append(((Top10) filter).getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FilterColumn(this); - } + HashMap attrs = new HashMap(); + Object filter = null; // CHOICE of filter + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("filterColumn")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("colorFilter")) { + filter = ColorFilter.parseOOXML(xpp); + } else if (tnm.equals("customFilters")) { + } else if (tnm.equals("dynamicFilter")) { + } else if (tnm.equals("filters")) { + } else if (tnm.equals("iconFilter")) { + } else if (tnm.equals("top10")) { + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("filterColumn")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("filterColumn.parseOOXML: " + e.toString()); + } + FilterColumn f = new FilterColumn(attrs, filter); + return f; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (filter instanceof ColorFilter) ooxml.append(((ColorFilter) filter).getOOXML()); + if (filter instanceof CustomFilters) ooxml.append(((CustomFilters) filter).getOOXML()); + if (filter instanceof DynamicFilter) ooxml.append(((DynamicFilter) filter).getOOXML()); + if (filter instanceof Filters) ooxml.append(((Filters) filter).getOOXML()); + if (filter instanceof IconFilter) ooxml.append(((IconFilter) filter).getOOXML()); + if (filter instanceof Top10) ooxml.append(((Top10) filter).getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new FilterColumn(this); + } } /** @@ -199,67 +198,67 @@ public OOXMLElement cloneElement() { * This element specifies the color to filter by and whether to use the cell's fill or font color in the filter criteria. If * the cell's font or fill color does not match the color specified in the criteria, the rows corresponding to those cells * are hidden from view. - * + *

        * parent: filterColumn * children: none - * */ class ColorFilter implements OOXMLElement { - - private static final long serialVersionUID = 7077951504723033275L; - private HashMap attrs= null; - - public ColorFilter(HashMap attrs) { - this.attrs= attrs; - } - public ColorFilter(ColorFilter c) { - this.attrs= c.attrs; - } - + + private static final long serialVersionUID = 7077951504723033275L; + private HashMap attrs = null; + + public ColorFilter(HashMap attrs) { + this.attrs = attrs; + } + + public ColorFilter(ColorFilter c) { + this.attrs = c.attrs; + } + public static ColorFilter parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("colorFilter")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("colorFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("colorFilter.parseOOXML: " + e.toString()); - } - ColorFilter oe= new ColorFilter(attrs); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ColorFilter(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("colorFilter")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("colorFilter")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("colorFilter.parseOOXML: " + e.toString()); + } + ColorFilter oe = new ColorFilter(attrs); + return oe; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = (String) i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new ColorFilter(this); + } } /** @@ -268,343 +267,343 @@ public OOXMLElement cloneElement() { * either with the data itself (e.g., "above average") or with the current system date (e.g., show values for "today"). * For any cells whose values do not meet the specified criteria, the corresponding rows shall be hidden from view * when the filter is applied. - * + *

        * parent: filterColumn * children: none - * */ -class DynamicFilter implements OOXMLElement{ - - private static final long serialVersionUID = -473171074711686551L; - private HashMap attrs= null; - - public DynamicFilter(HashMap attrs) { - this.attrs= attrs; - } - public DynamicFilter(DynamicFilter d) { - this.attrs= d.attrs; - } - - +class DynamicFilter implements OOXMLElement { + + private static final long serialVersionUID = -473171074711686551L; + private HashMap attrs = null; + + public DynamicFilter(HashMap attrs) { + this.attrs = attrs; + } + + public DynamicFilter(DynamicFilter d) { + this.attrs = d.attrs; + } + + public static DynamicFilter parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("dynamicFilter")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("dynamicFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("dynamicFilter.parseOOXML: " + e.toString()); - } - DynamicFilter d= new DynamicFilter(attrs); - return d; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new DynamicFilter(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("dynamicFilter")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("dynamicFilter")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("dynamicFilter.parseOOXML: " + e.toString()); + } + DynamicFilter d = new DynamicFilter(attrs); + return d; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new DynamicFilter(this); + } } /** * iconFilter (Icon Filter) - * + *

        * This element specifies the icon set and particular icon within that set to filter by. For any cells whose icon does * not match the specified criteria, the corresponding rows shall be hidden from view when the filter is applied. - * + *

        * parent: filterColumn * children: none - * */ class IconFilter implements OOXMLElement { - - private static final long serialVersionUID = -5897037678209125965L; - private HashMap attrs= null; - - public IconFilter(HashMap attrs) { - this.attrs= attrs; - } - public IconFilter(IconFilter i) { - this.attrs= i.attrs; - } - - + + private static final long serialVersionUID = -5897037678209125965L; + private HashMap attrs = null; + + public IconFilter(HashMap attrs) { + this.attrs = attrs; + } + + public IconFilter(IconFilter i) { + this.attrs = i.attrs; + } + + public static IconFilter parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("iconFilter")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("iconFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("iconFilter.parseOOXML: " + e.toString()); - } - IconFilter i= new IconFilter(attrs); - return i; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new IconFilter(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("iconFilter")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("iconFilter")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("iconFilter.parseOOXML: " + e.toString()); + } + IconFilter i = new IconFilter(attrs); + return i; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new IconFilter(this); + } } /** * customFilters (Custom Filters) * When there is more than one custom filter criteria to apply (an 'and' or 'or' joining two criteria), then this * element groups the customFilter elements together. - - * */ -class CustomFilters implements OOXMLElement{ - - private static final long serialVersionUID = -2491942158519963335L; - private boolean and= false; - private CustomFilter[] custfilter= null; - - public CustomFilters(boolean and, CustomFilter[] custfilter) { - this.and= and; - this.custfilter= custfilter; - } - public CustomFilters(CustomFilters c) { - this.and= c.and; - this.custfilter= c.custfilter; - } - - +class CustomFilters implements OOXMLElement { + + private static final long serialVersionUID = -2491942158519963335L; + private boolean and = false; + private CustomFilter[] custfilter = null; + + public CustomFilters(boolean and, CustomFilter[] custfilter) { + this.and = and; + this.custfilter = custfilter; + } + + public CustomFilters(CustomFilters c) { + this.and = c.and; + this.custfilter = c.custfilter; + } + + public static CustomFilters parseOOXML(XmlPullParser xpp) { - boolean and= false; - CustomFilter[] custfilter= new CustomFilter[2]; - int idx= 0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("customFilters")) { // get attributes - if (xpp.getAttributeCount()==1) and= (xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("customFilter")) { // 1-2 - custfilter[idx++]= (CustomFilter) CustomFilter.parseOOXML(xpp); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("customFilters")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("customFilters.parseOOXML: " + e.toString()); - } - CustomFilters c= new CustomFilters(and, custfilter); - return c; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (custfilter!=null) { // shouln't be! - if (custfilter[0]!=null)// shouldn't be! - ooxml.append(custfilter[0].getOOXML()); - if (custfilter[1]!=null) ooxml.append(custfilter[1].getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CustomFilters(this); - } + boolean and = false; + CustomFilter[] custfilter = new CustomFilter[2]; + int idx = 0; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("customFilters")) { // get attributes + if (xpp.getAttributeCount() == 1) and = (xpp.getAttributeValue(0).equals("1")); + } else if (tnm.equals("customFilter")) { // 1-2 + custfilter[idx++] = CustomFilter.parseOOXML(xpp); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("customFilters")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("customFilters.parseOOXML: " + e.toString()); + } + CustomFilters c = new CustomFilters(and, custfilter); + return c; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (custfilter != null) { // shouln't be! + if (custfilter[0] != null)// shouldn't be! + ooxml.append(custfilter[0].getOOXML()); + if (custfilter[1] != null) ooxml.append(custfilter[1].getOOXML()); + } + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CustomFilters(this); + } } /** - customFilter (Custom Filter Criteria) + * customFilter (Custom Filter Criteria) * A custom AutoFilter specifies an operator and a value. There can be at most two customFilters specified, and in * that case the parent element specifies whether the two conditions are joined by 'and' or 'or'. For any cells * whose values do not meet the specified criteria, the corresponding rows shall be hidden from view when the * filter is applied. - * + *

        * parent: customFilters * children: none */ class CustomFilter implements OOXMLElement { - private static final long serialVersionUID = 7995078604042667255L; - private HashMap attrs= null; - - public CustomFilter(HashMap attrs) { - this.attrs= attrs; - } - public CustomFilter(CustomFilter c) { - this.attrs= c.attrs; - } - + private static final long serialVersionUID = 7995078604042667255L; + private HashMap attrs = null; + + public CustomFilter(HashMap attrs) { + this.attrs = attrs; + } + + public CustomFilter(CustomFilter c) { + this.attrs = c.attrs; + } + public static CustomFilter parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("customFilter")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("customFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("customFilter.parseOOXML: " + e.toString()); - } - CustomFilter c= new CustomFilter(attrs); - return c; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CustomFilter(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("customFilter")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("customFilter")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("customFilter.parseOOXML: " + e.toString()); + } + CustomFilter c = new CustomFilter(attrs); + return c; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CustomFilter(this); + } } /** * top10 (Top 10) * This element specifies the top N (percent or number of items) to filter by. - * + *

        * parent: filterColumn * children: none - * */ class Top10 implements OOXMLElement { - - private static final long serialVersionUID = 77735498689922082L; - private HashMap attrs= null; - - public Top10(HashMap attrs) { - this.attrs= attrs; - } - public Top10(Top10 t) { - this.attrs= t.attrs; - } - - + + private static final long serialVersionUID = 77735498689922082L; + private HashMap attrs = null; + + public Top10(HashMap attrs) { + this.attrs = attrs; + } + + public Top10(Top10 t) { + this.attrs = t.attrs; + } + + public static Top10 parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("top10")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("top10")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("top10.parseOOXML: " + e.toString()); - } - Top10 t= new Top10(attrs); - return t; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Top10(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("top10")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("top10")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("top10.parseOOXML: " + e.toString()); + } + Top10 t = new Top10(attrs); + return t; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Top10(this); + } } @@ -612,73 +611,74 @@ public OOXMLElement cloneElement() { * filters (Filter Criteria) * When multiple values are chosen to filter by, or when a group of date values are chosen to filter by, this element * groups those criteria together. - * + *

        * parent: filterColumn * children: filter (0+), dateGroupItem (0+) */ // TODO: finish children -class Filters implements OOXMLElement{ - - private static final long serialVersionUID = 921424089049938924L; - private HashMap attrs= null; - - public Filters(HashMap attrs) { - this.attrs= attrs; - } - public Filters(Filters f) { - this.attrs= f.attrs; - } - +class Filters implements OOXMLElement { + + private static final long serialVersionUID = 921424089049938924L; + private HashMap attrs = null; + + public Filters(HashMap attrs) { + this.attrs = attrs; + } + + public Filters(Filters f) { + this.attrs = f.attrs; + } + public static Filters parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("filters")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("filter")) { - } else if (tnm.equals("dateGroupItem")) { - //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("filters")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("filters.parseOOXML: " + e.toString()); - } - Filters oe= new Filters(attrs); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - // TODO: one or more filter elements - // TODO: one or more dateGroupItem elements - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Filters(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("filters")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("filter")) { + } else if (tnm.equals("dateGroupItem")) { + //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("filters")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("filters.parseOOXML: " + e.toString()); + } + Filters oe = new Filters(attrs); + return oe; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = (String) i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + // TODO: one or more filter elements + // TODO: one or more dateGroupItem elements + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Filters(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/AvLst.java b/src/main/java/io/starter/formats/OOXML/AvLst.java index 130ab2d..6311bbf 100644 --- a/src/main/java/io/starter/formats/OOXML/AvLst.java +++ b/src/main/java/io/starter/formats/OOXML/AvLst.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,81 +22,81 @@ */ package io.starter.formats.OOXML; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.ArrayList; import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; /** * avLst (List of Shape Adjust Values) - + *

        * This element specifies the adjust values that will be applied to the specified shape. An adjust value is simply a * guide that has a value based formula specified. That is, no calculation takes place for an adjust value guide. * Instead, this guide specifies a parameter value that is used for calculations within the shape guides. - * + *

        * parent: prstGeom, prstTxWarp, custGeom * children: gd (shape guide) (0 or more) - * */ -public class AvLst implements OOXMLElement{ - - private static final long serialVersionUID = 4823524943145191780L; - private ArrayList gds= null; - - public AvLst() { - - } - - public AvLst(ArrayList gds) { - this.gds= gds; - } - - public AvLst(AvLst av) { - this.gds= av.gds; - } - +public class AvLst implements OOXMLElement { + + private static final long serialVersionUID = 4823524943145191780L; + private ArrayList gds = null; + + public AvLst() { + + } + + public AvLst(ArrayList gds) { + this.gds = gds; + } + + public AvLst(AvLst av) { + this.gds = av.gds; + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - ArrayList gds= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gd")) { - lastTag.push(tnm); - if (gds==null) gds= new ArrayList(); - gds.add((Gd) Gd.parseOOXML(xpp, lastTag)); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("avLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("avLst.parseOOXML: " + e.toString()); - } - AvLst av= new AvLst(gds); - return av; + ArrayList gds = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("gd")) { + lastTag.push(tnm); + if (gds == null) gds = new ArrayList(); + gds.add((Gd) Gd.parseOOXML(xpp, lastTag)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("avLst")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("avLst.parseOOXML: " + e.toString()); + } + AvLst av = new AvLst(gds); + return av; } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (gds!=null) { - for (int i= 0; i < gds.size(); i++) - ooxml.append(((Gd)gds.get(i)).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new AvLst(this); - } + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (gds != null) { + for (int i = 0; i < gds.size(); i++) + ooxml.append(((Gd) gds.get(i)).getOOXML()); + } + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new AvLst(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/BodyPr.java b/src/main/java/io/starter/formats/OOXML/BodyPr.java index 80c017f..c03ec45 100644 --- a/src/main/java/io/starter/formats/OOXML/BodyPr.java +++ b/src/main/java/io/starter/formats/OOXML/BodyPr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,195 +22,199 @@ */ package io.starter.formats.OOXML; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; - -import io.starter.toolkit.Logger; - /** * bodyPr (Body Properties) - * + *

        * This element defines the body properties for the text body within a shape - * + *

        * parents: many, including txBody and txPr * attributes: many * children: flatTx, noAutoFit, normAutoFit, prstTxWarp, scene3d, sp3d, spAutoFit - * */ // TODO: Handle CHILDREN *********************************** -public class BodyPr implements OOXMLElement{ +public class BodyPr implements OOXMLElement { + + private static final long serialVersionUID = 3693893834015788452L; + private HashMap attrs = new HashMap(); + private PrstTxWarp txwarp = null; + private boolean spAutoFit = false; - private static final long serialVersionUID = 3693893834015788452L; - private HashMap attrs= new HashMap(); - private PrstTxWarp txwarp= null; - private boolean spAutoFit= false; - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - PrstTxWarp txwarp= null; - boolean spAutoFit= false; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bodyPr")) { // body text properties - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - // TODO: handle flatTx element - // TODO: handle scene3d element - // TODO: handle sp3d element - } else if (tnm.equals("spAutoFit")) { // TODO: should be a choice of autofit options - spAutoFit= true; // no attributes or children - } else if (tnm.equals("prstTxWarp")) { - lastTag.push(tnm); - txwarp= (PrstTxWarp) PrstTxWarp.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("bodyPr")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("bodyPr.parseOOXML: " + e.toString()); - } - BodyPr bpr= new BodyPr(attrs, txwarp, spAutoFit); - return bpr; + HashMap attrs = new HashMap(); + PrstTxWarp txwarp = null; + boolean spAutoFit = false; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("bodyPr")) { // body text properties + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + // TODO: handle flatTx element + // TODO: handle scene3d element + // TODO: handle sp3d element + } else if (tnm.equals("spAutoFit")) { // TODO: should be a choice of autofit options + spAutoFit = true; // no attributes or children + } else if (tnm.equals("prstTxWarp")) { + lastTag.push(tnm); + txwarp = PrstTxWarp.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("bodyPr")) { + lastTag.pop(); // pop this tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("bodyPr.parseOOXML: " + e.toString()); + } + BodyPr bpr = new BodyPr(attrs, txwarp, spAutoFit); + return bpr; + } + + public BodyPr() { + } + + public BodyPr(HashMap attrs, PrstTxWarp txwarp, boolean spAutoFit) { + this.attrs = attrs; + this.txwarp = txwarp; + this.spAutoFit = spAutoFit; + } + + public BodyPr(BodyPr tpr) { + this.attrs = tpr.attrs; + this.txwarp = tpr.txwarp; + this.spAutoFit = tpr.spAutoFit; + } + + /** + * defines the body properties for the text body within a shape + * + * @param hrot + * @param vert Determines if the text within the given text body should be displayed vertically. If this attribute is omitted, then a value of horz, or no vertical text is implied. + * vert Determines if all of the text is vertical orientation (each line is 90 degrees rotated clockwise, so it goes from top to bottom; each next line is to the left from the previous one). + * vert270 Determines if all of the text is vertical orientation (each line is 270 degrees rotated clockwise, so it goes from bottom to top; each next line is to the right from the previous one). + * wordArtVert Determines if all of the text is vertical ("one letter on top of another"). + * wordArtVertRtl Specifies that vertical WordArt should be shown from right to left rather than left to right. + * eaVert A special version of vertical text, where some fonts are displayed as if rotated by 90 degrees while some fonts (mostly East Asian) are displayed vertical. + */ + public BodyPr(int hrot, String vert) { + attrs = new HashMap(); + attrs.put("rot", String.valueOf(hrot)); + if (vert != null) + attrs.put("vert", vert); } - - public BodyPr() { } - public BodyPr(HashMap attrs, PrstTxWarp txwarp, boolean spAutoFit) { - this.attrs= attrs; - this.txwarp= txwarp; - this.spAutoFit= spAutoFit; - } - public BodyPr(BodyPr tpr) { - this.attrs= tpr.attrs; - this.txwarp= tpr.txwarp; - this.spAutoFit= tpr.spAutoFit; - } - - /** - * defines the body properties for the text body within a shape - * @param hrot - * @param vert Determines if the text within the given text body should be displayed vertically. If this attribute is omitted, then a value of horz, or no vertical text is implied. - vert Determines if all of the text is vertical orientation (each line is 90 degrees rotated clockwise, so it goes from top to bottom; each next line is to the left from the previous one). - vert270 Determines if all of the text is vertical orientation (each line is 270 degrees rotated clockwise, so it goes from bottom to top; each next line is to the right from the previous one). - wordArtVert Determines if all of the text is vertical ("one letter on top of another"). - wordArtVertRtl Specifies that vertical WordArt should be shown from right to left rather than left to right. - eaVert A special version of vertical text, where some fonts are displayed as if rotated by 90 degrees while some fonts (mostly East Asian) are displayed vertical. - - */ - public BodyPr(int hrot, String vert) { - attrs= new HashMap(); - attrs.put("rot", String.valueOf(hrot)); - if (vert!=null) - attrs.put("vert", vert); - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (txwarp!=null) ooxml.append(txwarp.getOOXML()); - if (spAutoFit) ooxml.append(""); // TODO: Should be a choice of autofit options - // scene3d - // text3d choice - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BodyPr(this); - } - + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (txwarp != null) ooxml.append(txwarp.getOOXML()); + if (spAutoFit) ooxml.append(""); // TODO: Should be a choice of autofit options + // scene3d + // text3d choice + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new BodyPr(this); + } + } /** * prstTxWarp (Preset Text Warp) - * + *

        * This element specifies when a preset geometric shape should be used to transform a piece of text. This * operation is known formally as a text warp. The generating application should be able to render all preset * geometries enumerated in the ST_TextShapeType list. - * + *

        * parent: bodyPr * children: avLst - * */ class PrstTxWarp implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2627323317407321668L; - private String prst= null; - private AvLst av= null; - public PrstTxWarp() { } - public PrstTxWarp(String prst, AvLst av) { - this.prst= prst; - this.av= av; - } - public PrstTxWarp(PrstTxWarp p) { - this.prst= p.prst; - this.av= p.av; - } - - public static PrstTxWarp parseOOXML(XmlPullParser xpp, Stack lastTag) { - String prst= null; - AvLst av= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstTxWarp")) { // prst is only attribute and is required - prst= xpp.getAttributeValue(0); - } else if (tnm.equals("avLst")) { - lastTag.push(tnm); - av= (AvLst) AvLst.parseOOXML(xpp, lastTag); - - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("prstTxWarp")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("prstTxWarp.parseOOXML: " + e.toString()); - } - PrstTxWarp p= new PrstTxWarp(prst, av); - return p; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (av!=null) ooxml.append(av.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PrstTxWarp(this); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2627323317407321668L; + private String prst = null; + private AvLst av = null; + + public PrstTxWarp() { + } + + public PrstTxWarp(String prst, AvLst av) { + this.prst = prst; + this.av = av; + } + + public PrstTxWarp(PrstTxWarp p) { + this.prst = p.prst; + this.av = p.av; + } + + public static PrstTxWarp parseOOXML(XmlPullParser xpp, Stack lastTag) { + String prst = null; + AvLst av = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("prstTxWarp")) { // prst is only attribute and is required + prst = xpp.getAttributeValue(0); + } else if (tnm.equals("avLst")) { + lastTag.push(tnm); + av = (AvLst) AvLst.parseOOXML(xpp, lastTag); + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("prstTxWarp")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("prstTxWarp.parseOOXML: " + e.toString()); + } + PrstTxWarp p = new PrstTxWarp(prst, av); + return p; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (av != null) ooxml.append(av.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new PrstTxWarp(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Border.java b/src/main/java/io/starter/formats/OOXML/Border.java index 5dc6db5..46cf5ba 100644 --- a/src/main/java/io/starter/formats/OOXML/Border.java +++ b/src/main/java/io/starter/formats/OOXML/Border.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,482 +23,504 @@ package io.starter.formats.OOXML; -import java.util.HashMap; -import java.util.Iterator; -import org.xmlpull.v1.XmlPullParser; import io.starter.OpenXLS.FormatHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.Xf; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Iterator; /** * border OOXML element - * + *

        * parent: styleSheet/borders element in styles.xml * children: SEQ: left, right, top, bottom, diagonal, vertical, horizontal - * */ -public class Border implements OOXMLElement{ - - private static final long serialVersionUID = 4340789910636828223L; - private HashMap attrs= null; - private HashMap borderElements= null; - - public Border() { } - /** - * @param styles int array {top, left, top, bottom, right, [diagonal]} - * @param colors int array {top, left, top, bottom, right, [diagonal]} - */ - public Border(HashMap attrs, HashMap borderElements) { - this.attrs= attrs; - this.borderElements= borderElements; - } - public Border(Border b) { - this.attrs= b.attrs; - this.borderElements= b.borderElements; - } - - /** - * set borders - * @param bk - * @param styles t, l, b, r - * @param colors - */ - public Border(WorkBookHandle bk, int[] styles, int[] colors) { - this.borderElements= new HashMap(); - String[] borderElements= {"top", "left", "bottom", "right"}; - for (int i= 0; i < 4; i++) { - if (styles[i]>0) { - String style= OOXMLConstants.borderStyle[styles[i]]; - this.borderElements.put(borderElements[i], new BorderElement(style, colors[i], borderElements[i], bk)); - } - } - // diagonal? vertical? horizontal? - } - +public class Border implements OOXMLElement { + + private static final long serialVersionUID = 4340789910636828223L; + private HashMap attrs = null; + private HashMap borderElements = null; + + public Border() { + } + + /** + * @param styles int array {top, left, top, bottom, right, [diagonal]} + * @param colors int array {top, left, top, bottom, right, [diagonal]} + */ + public Border(HashMap attrs, HashMap borderElements) { + this.attrs = attrs; + this.borderElements = borderElements; + } + + public Border(Border b) { + this.attrs = b.attrs; + this.borderElements = b.borderElements; + } + + /** + * set borders + * + * @param bk + * @param styles t, l, b, r + * @param colors + */ + public Border(WorkBookHandle bk, int[] styles, int[] colors) { + this.borderElements = new HashMap(); + String[] borderElements = {"top", "left", "bottom", "right"}; + for (int i = 0; i < 4; i++) { + if (styles[i] > 0) { + String style = OOXMLConstants.borderStyle[styles[i]]; + this.borderElements.put(borderElements[i], new BorderElement(style, colors[i], borderElements[i], bk)); + } + } + // diagonal? vertical? horizontal? + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - HashMap attrs= new HashMap(); - HashMap borderElements= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("border")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("left")) { - borderElements.put("left", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("right")) { - borderElements.put("right", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("top")) { - borderElements.put("top", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("bottom")) { - borderElements.put("bottom", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("diagonal")) { - borderElements.put("diagonal", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("vertical")) { - borderElements.put("vertical", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("horizontal")) { - borderElements.put("horizontal", BorderElement.parseOOXML(xpp, bk)); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("border")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("border.parseOOXML: " + e.toString()); - } - Border b= new Border(attrs, borderElements); - return b; + HashMap attrs = new HashMap(); + HashMap borderElements = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("border")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("left")) { + borderElements.put("left", BorderElement.parseOOXML(xpp, bk)); + } else if (tnm.equals("right")) { + borderElements.put("right", BorderElement.parseOOXML(xpp, bk)); + } else if (tnm.equals("top")) { + borderElements.put("top", BorderElement.parseOOXML(xpp, bk)); + } else if (tnm.equals("bottom")) { + borderElements.put("bottom", BorderElement.parseOOXML(xpp, bk)); + } else if (tnm.equals("diagonal")) { + borderElements.put("diagonal", BorderElement.parseOOXML(xpp, bk)); + } else if (tnm.equals("vertical")) { + borderElements.put("vertical", BorderElement.parseOOXML(xpp, bk)); + } else if (tnm.equals("horizontal")) { + borderElements.put("horizontal", BorderElement.parseOOXML(xpp, bk)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("border")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("border.parseOOXML: " + e.toString()); + } + Border b = new Border(attrs, borderElements); + return b; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + } + ooxml.append(">"); + if (borderElements.get("left") != null) ooxml.append(borderElements.get("left").getOOXML()); + if (borderElements.get("right") != null) ooxml.append(borderElements.get("right").getOOXML()); + if (borderElements.get("top") != null) ooxml.append(borderElements.get("top").getOOXML()); + if (borderElements.get("bottom") != null) ooxml.append(borderElements.get("bottom").getOOXML()); + if (borderElements.get("diagonal") != null) ooxml.append(borderElements.get("diagonal").getOOXML()); + if (borderElements.get("vertical") != null) ooxml.append(borderElements.get("vertical").getOOXML()); + if (borderElements.get("horizontal") != null) ooxml.append(borderElements.get("horizontal").getOOXML()); + + ooxml.append(""); + return ooxml.toString(); + } + + /** + * returns an array representing the border sizes + *
        top, left, bottom, right, diag + * + * @return int[5] representing border sizes + */ + public int[] getBorderSizes() { + int[] sizes = new int[5]; + if (borderElements.get("top") != null) sizes[0] = borderElements.get("top").getBorderSize(); + if (borderElements.get("left") != null) sizes[1] = borderElements.get("left").getBorderSize(); + if (borderElements.get("bottom") != null) sizes[2] = borderElements.get("bottom").getBorderSize(); + if (borderElements.get("right") != null) sizes[3] = borderElements.get("right").getBorderSize(); + if (borderElements.get("diagonal") != null) sizes[4] = borderElements.get("diagonal").getBorderSize(); + return sizes; + } + + /** + * returns an array representing the border styles + * translated from OOXML String value to 2003-int value + *
        top, left, bottom, right, diag + * + * @return int[5] + */ + public int[] getBorderStyles() { + int[] styles = new int[5]; + if (borderElements.get("top") != null) styles[0] = borderElements.get("top").getBorderStyle(); + if (borderElements.get("left") != null) styles[1] = borderElements.get("left").getBorderStyle(); + if (borderElements.get("bottom") != null) styles[2] = borderElements.get("bottom").getBorderStyle(); + if (borderElements.get("right") != null) styles[3] = borderElements.get("right").getBorderStyle(); + if (borderElements.get("diagonal") != null) styles[4] = borderElements.get("diagonal").getBorderStyle(); + return styles; + } + + /** + * returns an array representing the border colors as rgb string + *
        top, left, bottom, right, diag + * + * @return String[6] + */ + public String[] getBorderColors() { + try { + String[] clrs = new String[5]; + if (borderElements.get("top") != null) clrs[0] = borderElements.get("top").getBorderColor(); + if (borderElements.get("left") != null) clrs[1] = borderElements.get("left").getBorderColor(); + if (borderElements.get("bottom") != null) clrs[2] = borderElements.get("bottom").getBorderColor(); + if (borderElements.get("right") != null) clrs[3] = borderElements.get("right").getBorderColor(); + if (borderElements.get("diagonal") != null) clrs[4] = borderElements.get("diagonal").getBorderColor(); + return clrs; + } catch (NullPointerException e) { + return new String[5]; + } + } + + /** + * returns an array representing the border colors as rgb string + *
        top, left, bottom, right, diag + * + * @return String[6] + */ + public int[] getBorderColorInts() { + try { + int[] clrs = new int[5]; + if (borderElements.get("top") != null) clrs[0] = borderElements.get("top").getBorderColorInt(); + if (borderElements.get("left") != null) clrs[1] = borderElements.get("left").getBorderColorInt(); + if (borderElements.get("bottom") != null) clrs[2] = borderElements.get("bottom").getBorderColorInt(); + if (borderElements.get("right") != null) clrs[3] = borderElements.get("right").getBorderColorInt(); + if (borderElements.get("diagonal") != null) clrs[4] = borderElements.get("diagonal").getBorderColorInt(); + return clrs; + } catch (NullPointerException e) { + return new int[5]; + } + } + + public OOXMLElement cloneElement() { + return new Border(this); + } + + public String toString() { + if (borderElements != null) + return borderElements.toString(); + return ""; + } + + /** + * return an OOXML representation of this border based on this FormatHandle object + */ + public static String getOOXML(Xf xf) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + int[] lineStyles = new int[5]; + lineStyles[0] = xf.getLeftBorderLineStyle(); + lineStyles[1] = xf.getRightBorderLineStyle(); + lineStyles[2] = xf.getTopBorderLineStyle(); + lineStyles[3] = xf.getBottomBorderLineStyle(); + lineStyles[4] = xf.getDiagBorderLineStyle(); + + int[] colors = new int[5]; + colors[0] = xf.getLeftBorderColor(); + colors[1] = xf.getRightBorderColor(); + colors[2] = xf.getTopBorderColor(); + colors[3] = xf.getBottomBorderColor(); + colors[4] = xf.getDiagBorderColor(); + + if (lineStyles[0] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + + if (lineStyles[1] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + + if (lineStyles[2] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + + if (lineStyles[3] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + + if (lineStyles[4] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + ooxml.append(""); + return ooxml.toString(); + } + + /** + * return the OOMXL to define a border based on the below specifications: + * + * @param styles int array {top, left, top, bottom, right, [diagonal]} + * @param colors int array {top, left, top, bottom, right, [diagonal]} + */ + public static String getOOXML(int[] styles, int[] colors) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + // left + if (styles[0] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + // right + if (styles[1] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + // top + if (styles[2] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + // bottom + if (styles[3] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + // diagonal + if (styles[4] > 0) { + ooxml.append(" 0) + ooxml.append(">"); + else ooxml.append("/>"); + } + ooxml.append(""); + return ooxml.toString(); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= i.next(); - String val= attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (borderElements.get("left")!=null) ooxml.append(borderElements.get("left").getOOXML()); - if (borderElements.get("right")!=null) ooxml.append(borderElements.get("right").getOOXML()); - if (borderElements.get("top")!=null) ooxml.append(borderElements.get("top").getOOXML()); - if (borderElements.get("bottom")!=null) ooxml.append(borderElements.get("bottom").getOOXML()); - if (borderElements.get("diagonal")!=null) ooxml.append(borderElements.get("diagonal").getOOXML()); - if (borderElements.get("vertical")!=null) ooxml.append(borderElements.get("vertical").getOOXML()); - if (borderElements.get("horizontal")!=null) ooxml.append(borderElements.get("horizontal").getOOXML()); - - ooxml.append(""); - return ooxml.toString(); - } - /** - * returns an array representing the border sizes - *
        top, left, bottom, right, diag - * @return int[5] representing border sizes - */ - public int[] getBorderSizes() { - int[] sizes= new int[5]; - if (borderElements.get("top")!=null) sizes[0]= borderElements.get("top").getBorderSize(); - if (borderElements.get("left")!=null) sizes[1]= borderElements.get("left").getBorderSize(); - if (borderElements.get("bottom")!=null) sizes[2]= borderElements.get("bottom").getBorderSize(); - if (borderElements.get("right")!=null) sizes[3]= borderElements.get("right").getBorderSize(); - if (borderElements.get("diagonal")!=null) sizes[4]= borderElements.get("diagonal").getBorderSize(); - return sizes; - } - - /** - * returns an array representing the border styles - * translated from OOXML String value to 2003-int value - *
        top, left, bottom, right, diag - * @return int[5] - */ - public int[] getBorderStyles() { - int[] styles= new int[5]; - if (borderElements.get("top")!=null) styles[0]= borderElements.get("top").getBorderStyle(); - if (borderElements.get("left")!=null) styles[1]= borderElements.get("left").getBorderStyle(); - if (borderElements.get("bottom")!=null) styles[2]= borderElements.get("bottom").getBorderStyle(); - if (borderElements.get("right")!=null) styles[3]= borderElements.get("right").getBorderStyle(); - if (borderElements.get("diagonal")!=null) styles[4]= borderElements.get("diagonal").getBorderStyle(); - return styles; - } - - /** - * returns an array representing the border colors as rgb string - *
        top, left, bottom, right, diag - * @return String[6] - */ - public String[] getBorderColors() { - try { - String[] clrs= new String[5]; - if (borderElements.get("top")!=null) clrs[0]= borderElements.get("top").getBorderColor(); - if (borderElements.get("left")!=null) clrs[1]= borderElements.get("left").getBorderColor(); - if (borderElements.get("bottom")!=null) clrs[2]= borderElements.get("bottom").getBorderColor(); - if (borderElements.get("right")!=null) clrs[3]= borderElements.get("right").getBorderColor(); - if (borderElements.get("diagonal")!=null) clrs[4]= borderElements.get("diagonal").getBorderColor(); - return clrs; - }catch(NullPointerException e) { - return new String[5]; - } - } - - /** - * returns an array representing the border colors as rgb string - *
        top, left, bottom, right, diag - * @return String[6] - */ - public int[] getBorderColorInts() { - try { - int[] clrs= new int[5]; - if (borderElements.get("top")!=null) clrs[0]= borderElements.get("top").getBorderColorInt(); - if (borderElements.get("left")!=null) clrs[1]= borderElements.get("left").getBorderColorInt(); - if (borderElements.get("bottom")!=null) clrs[2]= borderElements.get("bottom").getBorderColorInt(); - if (borderElements.get("right")!=null) clrs[3]= borderElements.get("right").getBorderColorInt(); - if (borderElements.get("diagonal")!=null) clrs[4]= borderElements.get("diagonal").getBorderColorInt(); - return clrs; - }catch(NullPointerException e) { - return new int[5]; - } - } - - public OOXMLElement cloneElement() { - return new Border(this); - } - - public String toString() { - if (borderElements!=null) - return borderElements.toString(); - return ""; - } - - /** - * return an OOXML representation of this border based on this FormatHandle object - */ - public static String getOOXML(Xf xf) { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - int[] lineStyles= new int[5]; - lineStyles[0]= xf.getLeftBorderLineStyle(); - lineStyles[1]= xf.getRightBorderLineStyle(); - lineStyles[2]= xf.getTopBorderLineStyle(); - lineStyles[3]= xf.getBottomBorderLineStyle(); - lineStyles[4]= xf.getDiagBorderLineStyle(); - - int[] colors= new int[5]; - colors[0]= xf.getLeftBorderColor(); - colors[1]= xf.getRightBorderColor(); - colors[2]= xf.getTopBorderColor(); - colors[3]= xf.getBottomBorderColor(); - colors[4]= xf.getDiagBorderColor(); - - if (lineStyles[0]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - - if (lineStyles[1]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - - if (lineStyles[2]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - - if (lineStyles[3]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - - if (lineStyles[4]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - ooxml.append(""); - return ooxml.toString(); - } - - /** - * return the OOMXL to define a border based on the below specifications: - * @param styles int array {top, left, top, bottom, right, [diagonal]} - * @param colors int array {top, left, top, bottom, right, [diagonal]} - */ - public static String getOOXML(int[] styles, int[] colors) { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - // left - if (styles[0]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - // right - if (styles[1]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - // top - if (styles[2]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - // bottom - if (styles[3]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - // diagonal - if (styles[4]>0) { - ooxml.append("0) ooxml.append(">"); - else ooxml.append("/>"); - } - ooxml.append(""); - return ooxml.toString(); - } } -/** one of: - * left, right, top, bottom, diagonal, vertical, horizontal - * - * parent: border - * children: color - * - * +/** + * one of: + * left, right, top, bottom, diagonal, vertical, horizontal + *

        + * parent: border + * children: color */ class BorderElement implements OOXMLElement { - private static final long serialVersionUID = -8040551653089261574L; - private String style; + private static final long serialVersionUID = -8040551653089261574L; + private String style; private Color color; private String borderElement; - - public BorderElement(String style, Color c, String borderElement) { - this.style= style; - this.color= c; - this.borderElement= borderElement; - } - - public BorderElement(BorderElement b) { - this.style= b.style; - this.color= b.color; - this.borderElement= b.borderElement; - } - - /** - * - * @return the border size for this border element, translated from OOXML string to 2003-style int - */ - public int getBorderSize() { - int st= getBorderStyle(); - if (st<=4) - return st+1; - // otherwise, interpret style --> size??? - if (st==7) // hair - return 1; - if (st==6 || st==8 || st==0xC) - return 3; - return 2; - } - - /** - * return the border style for this border element, translated from OOXML string to 2003-style int - * @return - */ - public int getBorderStyle() { - if (style==null || style.equals("none")) - return -1; - else if (style.equals("thin")) - return 1; - else if (style.equals("medium")) - return 2; - else if (style.equals("dashed")) - return 3; - else if (style.equals("dotted")) - return 4; - else if (style.equals("thick")) - return 5; - else if (style.equals("double")) - return 6; - else if (style.equals("hair")) - return 7; - else if (style.equals("mediumDashed")) - return 8; - else if (style.equals("dashDot")) - return 9; - else if (style.equals("mediumDashDot")) - return 0xA; - else if (style.equals("dashDotDot")) - return 0xB; - else if (style.equals("mediumDashDotDot")) - return 0xC; - else if (style.equals("slantDashDot")) - return 0xD; - return -1; - } - - /** - * return the rgb color string for this border element - * @return - */ - public String getBorderColor() { - if (color!=null) - return this.color.getColorAsOOXMLRBG(); - return null; - } - - public int getBorderColorInt() { - if (color!=null) - return this.color.getColorInt(); - return 0; - } - - /** - * constructor from string representation of a border element - * @param style "thin", "thick" ... - * @param val rgb color value - * @param borderElement "left", "right", "top", "bottom", "diagonal" - */ - public BorderElement(String style, String val, String borderElement, WorkBookHandle bk) { - this.style= style; - if (style!=null && val!=null) - this.color= new Color("color", false, Color.COLORTYPERGB, val, 0.0, (short)0, bk.getWorkBook().getTheme()); - this.borderElement= borderElement; - } - - /** - * create a new border element - * @param style "thin", "thick" ... - * @param val color int - * @param borderElement "left", "right", "top", "bottom", "diagonal" - */ - public BorderElement(String style, int val, String borderElement, WorkBookHandle bk) { - this.style= style; - if (style!=null && val!=-1) - this.color= new Color("color", false, Color.COLORTYPEINDEXED, String.valueOf(val), 0.0, (short)0, bk.getWorkBook().getTheme()); - this.borderElement= borderElement; - } - + + public BorderElement(String style, Color c, String borderElement) { + this.style = style; + this.color = c; + this.borderElement = borderElement; + } + + public BorderElement(BorderElement b) { + this.style = b.style; + this.color = b.color; + this.borderElement = b.borderElement; + } + + /** + * @return the border size for this border element, translated from OOXML string to 2003-style int + */ + public int getBorderSize() { + int st = getBorderStyle(); + if (st <= 4) + return st + 1; + // otherwise, interpret style --> size??? + if (st == 7) // hair + return 1; + if (st == 6 || st == 8 || st == 0xC) + return 3; + return 2; + } + + /** + * return the border style for this border element, translated from OOXML string to 2003-style int + * + * @return + */ + public int getBorderStyle() { + if (style == null || style.equals("none")) + return -1; + else if (style.equals("thin")) + return 1; + else if (style.equals("medium")) + return 2; + else if (style.equals("dashed")) + return 3; + else if (style.equals("dotted")) + return 4; + else if (style.equals("thick")) + return 5; + else if (style.equals("double")) + return 6; + else if (style.equals("hair")) + return 7; + else if (style.equals("mediumDashed")) + return 8; + else if (style.equals("dashDot")) + return 9; + else if (style.equals("mediumDashDot")) + return 0xA; + else if (style.equals("dashDotDot")) + return 0xB; + else if (style.equals("mediumDashDotDot")) + return 0xC; + else if (style.equals("slantDashDot")) + return 0xD; + return -1; + } + + /** + * return the rgb color string for this border element + * + * @return + */ + public String getBorderColor() { + if (color != null) + return this.color.getColorAsOOXMLRBG(); + return null; + } + + public int getBorderColorInt() { + if (color != null) + return this.color.getColorInt(); + return 0; + } + + /** + * constructor from string representation of a border element + * + * @param style "thin", "thick" ... + * @param val rgb color value + * @param borderElement "left", "right", "top", "bottom", "diagonal" + */ + public BorderElement(String style, String val, String borderElement, WorkBookHandle bk) { + this.style = style; + if (style != null && val != null) + this.color = new Color("color", false, Color.COLORTYPERGB, val, 0.0, (short) 0, bk.getWorkBook().getTheme()); + this.borderElement = borderElement; + } + + /** + * create a new border element + * + * @param style "thin", "thick" ... + * @param val color int + * @param borderElement "left", "right", "top", "bottom", "diagonal" + */ + public BorderElement(String style, int val, String borderElement, WorkBookHandle bk) { + this.style = style; + if (style != null && val != -1) + this.color = new Color("color", false, Color.COLORTYPEINDEXED, String.valueOf(val), 0.0, (short) 0, bk.getWorkBook().getTheme()); + this.borderElement = borderElement; + } + public static BorderElement parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - String style= null; - Color c= null; - String borderElement= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("color")) { - c= (Color) Color.parseOOXML(xpp, (short)0, bk); - } else { // one of the border elements - if (xpp.getAttributeCount()>0) // style - style= xpp.getAttributeValue(0); - borderElement= tnm; - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals(borderElement)) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("borderElement.parseOOXML: " + e.toString()); - } - BorderElement b= new BorderElement(style, c, borderElement); - return b; + String style = null; + Color c = null; + String borderElement = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("color")) { + c = (Color) Color.parseOOXML(xpp, (short) 0, bk); + } else { // one of the border elements + if (xpp.getAttributeCount() > 0) // style + style = xpp.getAttributeValue(0); + borderElement = tnm; + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals(borderElement)) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("borderElement.parseOOXML: " + e.toString()); + } + BorderElement b = new BorderElement(style, c, borderElement); + return b; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append("<" + borderElement); + if (style != null) { + ooxml.append(" style=\"" + style + "\">"); + if (color != null) ooxml.append(color.getOOXML()); + ooxml.append(""); + } else + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new BorderElement(this); + } + + public String toString() { + return ((style != null) ? style : "") + + " c:" + ((color != null) ? color.toString() : ""); + } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append("<" + borderElement); - if (style!=null) { - ooxml.append(" style=\"" + style + "\">"); - if (color!=null) ooxml.append(color.getOOXML()); - ooxml.append(""); - } else - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BorderElement(this); - } - - public String toString() { - return ((style!=null)?style:"") + - " c:" + ((color!=null)?color.toString():""); - - } } diff --git a/src/main/java/io/starter/formats/OOXML/CNvPr.java b/src/main/java/io/starter/formats/OOXML/CNvPr.java index b22704a..9eff840 100644 --- a/src/main/java/io/starter/formats/OOXML/CNvPr.java +++ b/src/main/java/io/starter/formats/OOXML/CNvPr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,149 +22,170 @@ */ package io.starter.formats.OOXML; - import java.util.Stack; - - import org.xmlpull.v1.XmlPullParser; - import io.starter.formats.XLS.OOXMLAdapter; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * cNvPr (Non-Visual Drawing Properties) - * + *

        * OOXML/DrawingML element specifies non-visual canvas properties. This allows for additional information that does not affect * the appearance of the picture to be stored. - * - * attributes: descr, hidden, id REQ, name REQ, - * parents: nvSpPr, nvPicPr ... - * children: hlinkClick, hlinkHover - * + *

        + * attributes: descr, hidden, id REQ, name REQ, + * parents: nvSpPr, nvPicPr ... + * children: hlinkClick, hlinkHover */ // TODO: Handle Child elements hlinkClick, hlinkHover -public class CNvPr implements OOXMLElement{ - - private static final long serialVersionUID = -3382139449400844949L; - //private hlinkClick hc; - //private hlinkHover hh; - private String descr= null, name= null; - private boolean hidden= false; - private int id= -1; - - public CNvPr() { } - public CNvPr(/*hlinkClick hc, hlinkHover hh, */int id, String name, String descr, boolean hidden) { - //this.hc= hc; - //this.hh= hh; - this.id= id; - this.name= name; - this.descr= descr; - this.hidden= hidden; - } - - public CNvPr(CNvPr cnv) { - //this.hc= cnv.hc; - //this.hh= cnv.hh; - this.id= cnv.id; - this.name= cnv.name; - this.descr= cnv.descr; - this.hidden= cnv.hidden; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - //hlinkClick hc= null; - //hlinkHover hh= null; - String descr= null, name= null; - boolean hidden= false; - int id= -1; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); - String val= xpp.getAttributeValue(i); - if (nm.equals("id")) { - id= Integer.valueOf(val).intValue(); - } else if (nm.equals("name")) { - name= val; - } else if (nm.equals("descr")) { - descr= val; - } else if (nm.equals("hidden")) { - hidden= val.equals("1"); - } - } - } else if (tnm.equals("hlinkClick")) { - // hc= (hlinkClick) hlinkClick.parseOOXML(xpp).clone(); - } else if (tnm.equals("hlinkHover")) { - // hh= (hlinkHover) hlinkHover.parseOOXML(xpp).clone(); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("cNvPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cNvPr.parseOOXML: " + e.toString()); - } - CNvPr cnv= new CNvPr(/*hc, hh, */id, name, descr, hidden); - return cnv; - } - - public String getOOXML() { - StringBuffer tooxml= new StringBuffer(); - tooxml.append(""); - return tooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvPr(this); - } - - /** - * get name attribute - * @return - */ - public String getName() { return name; } - /** - * set name attribute - * @param name - */ - public void setName(String name) { this.name= name; } - /** - * get descr attribute - * @return - */ - - public String getDescr() { return descr; } - /** - * set description attribute - * sometimes associated with shape name - * @param descr - */ - public void setDescr(String descr) { this.descr= descr; } - - /** - * set the id for this element - * @param id - */ - public void setId(int id) { this.id= id; } - - /** - * return the id for this element - * @return - */ - public int getId() { return this.id; } - } +public class CNvPr implements OOXMLElement { + + private static final long serialVersionUID = -3382139449400844949L; + //private hlinkClick hc; + //private hlinkHover hh; + private String descr = null, name = null; + private boolean hidden = false; + private int id = -1; + + public CNvPr() { + } + + public CNvPr(/*hlinkClick hc, hlinkHover hh, */int id, String name, String descr, boolean hidden) { + //this.hc= hc; + //this.hh= hh; + this.id = id; + this.name = name; + this.descr = descr; + this.hidden = hidden; + } + + public CNvPr(CNvPr cnv) { + //this.hc= cnv.hc; + //this.hh= cnv.hh; + this.id = cnv.id; + this.name = cnv.name; + this.descr = cnv.descr; + this.hidden = cnv.hidden; + } + + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { + //hlinkClick hc= null; + //hlinkHover hh= null; + String descr = null, name = null; + boolean hidden = false; + int id = -1; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cNvPr")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + String val = xpp.getAttributeValue(i); + if (nm.equals("id")) { + id = Integer.valueOf(val).intValue(); + } else if (nm.equals("name")) { + name = val; + } else if (nm.equals("descr")) { + descr = val; + } else if (nm.equals("hidden")) { + hidden = val.equals("1"); + } + } + } else if (tnm.equals("hlinkClick")) { + // hc= (hlinkClick) hlinkClick.parseOOXML(xpp).clone(); + } else if (tnm.equals("hlinkHover")) { + // hh= (hlinkHover) hlinkHover.parseOOXML(xpp).clone(); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("cNvPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("cNvPr.parseOOXML: " + e.toString()); + } + CNvPr cnv = new CNvPr(/*hc, hh, */id, name, descr, hidden); + return cnv; + } + + public String getOOXML() { + StringBuffer tooxml = new StringBuffer(); + tooxml.append(""); + return tooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CNvPr(this); + } + + /** + * get name attribute + * + * @return + */ + public String getName() { + return name; + } + + /** + * set name attribute + * + * @param name + */ + public void setName(String name) { + this.name = name; + } + + /** + * get descr attribute + * + * @return + */ + + public String getDescr() { + return descr; + } + + /** + * set description attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setDescr(String descr) { + this.descr = descr; + } + + /** + * set the id for this element + * + * @param id + */ + public void setId(int id) { + this.id = id; + } + + /** + * return the id for this element + * + * @return + */ + public int getId() { + return this.id; + } +} diff --git a/src/main/java/io/starter/formats/OOXML/CfRule.java b/src/main/java/io/starter/formats/OOXML/CfRule.java index cbe2077..22a539d 100644 --- a/src/main/java/io/starter/formats/OOXML/CfRule.java +++ b/src/main/java/io/starter/formats/OOXML/CfRule.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,222 +22,240 @@ */ package io.starter.formats.OOXML; +import io.starter.formats.XLS.OOXMLAdapter; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; -import org.xmlpull.v1.XmlPullParser; - -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.toolkit.Logger; - /** -* cfRule (Conditional Formatting Rule) -* This collection represents a description of a conditional formatting rule. -*
        -* NOTE: now is used merely to parse the cfRule OOXML element -* the data is stored in a BIFF-8 Cf object -* -* parent: conditionalFormatting -* children: SEQ: formula (0-3), colorScale, dataBar, iconSet -* attributes: type, dxfId, priority (REQ), stopIfTrue, aboveAverage, -* percent, bottom, operator, text, timePeriod, rank, stdDev, equalAverage -*/ + * cfRule (Conditional Formatting Rule) + * This collection represents a description of a conditional formatting rule. + *
        + * NOTE: now is used merely to parse the cfRule OOXML element + * the data is stored in a BIFF-8 Cf object + *

        + * parent: conditionalFormatting + * children: SEQ: formula (0-3), colorScale, dataBar, iconSet + * attributes: type, dxfId, priority (REQ), stopIfTrue, aboveAverage, + * percent, bottom, operator, text, timePeriod, rank, stdDev, equalAverage + */ //TODO: Finish children colorScale, dataBar, iconSet -public class CfRule implements OOXMLElement { - - private static final long serialVersionUID = 8509907308100079138L; - private HashMap attrs; - private ArrayList formulas; - - public CfRule(HashMap attrs, ArrayList formulas) { - this.attrs= attrs; - this.formulas= formulas; - } - - public CfRule(CfRule cf) { - this.attrs= cf.attrs; - this.formulas= cf.formulas; - } - - /** - * generate a cfRule based on OOXML input stream - * @param xpp - * @return - */ - // TODO: finish children colorScale, dataBar, iconSet - public static CfRule parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - ArrayList formulas= new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cfRule")) { // get attributes: priority, type, operator, dxfId - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("formula")) { - formulas.add(OOXMLAdapter.getNextText(xpp)); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("cfRule")) { +public class CfRule implements OOXMLElement { + + private static final long serialVersionUID = 8509907308100079138L; + private HashMap attrs; + private ArrayList formulas; + + public CfRule(HashMap attrs, ArrayList formulas) { + this.attrs = attrs; + this.formulas = formulas; + } + + public CfRule(CfRule cf) { + this.attrs = cf.attrs; + this.formulas = cf.formulas; + } + + /** + * generate a cfRule based on OOXML input stream + * + * @param xpp + * @return + */ + // TODO: finish children colorScale, dataBar, iconSet + public static CfRule parseOOXML(XmlPullParser xpp) { + HashMap attrs = new HashMap(); + ArrayList formulas = new ArrayList(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cfRule")) { // get attributes: priority, type, operator, dxfId + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("formula")) { + formulas.add(OOXMLAdapter.getNextText(xpp)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("cfRule")) { break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cfRule.parseOOXML: " + e.toString()); - } - CfRule cf= new CfRule(attrs, formulas); - return cf; - } - - /** - * generate OOXML for this cfRule - * NOW Cf is parsed to obtain OOXML and CfRule is not retained - * @deprecated - * @return - */ - public String getOOXML(int priority) { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (formulas!=null) { - for (int j= 0; j < formulas.size(); j++) { - ooxml.append("" + OOXMLAdapter.stripNonAsciiRetainQuote((String) formulas.get(j)) + ""); - } - } - // TODO: finish children dataBar, colorScale, iconSet - ooxml.append(""); - return ooxml.toString(); - } - - /** - * get the dxfId (the incremental style associated with this conditional formatting rule) - * @return - */ - public int getDxfId() { - if (this.attrs!=null) - try { - return Integer.valueOf((String)this.attrs.get("dxfId")).intValue(); - } catch (Exception e) { - // it's possible to not specify a dxfId - ; - } - return -1; - } - - /** - * set the dxfId (the incremental style associated with this conditional formatting rule) - * @deprecated - * @param dxfId - */ - public void setDxfId(int dxfId) { - if (this.attrs==null) this.attrs= new HashMap(); - attrs.put("dxfId", Integer.valueOf(dxfId).toString()); - } - public OOXMLElement cloneElement() { - return new CfRule(this); - } - - public String getOOXML() { - // TODO Auto-generated method stub - return null; - } - - /** - * get methods - */ - public String getOperator() { - // valid only when type=="cellIs" - if (attrs!=null) - return (String) attrs.get("operator"); - return null; - } - - public String getType() { - String type= null; - if (attrs!=null) - type= (String) attrs.get("type"); - if (type==null) - type= "cellIs"; - return type; - } - - /** - * returns the text to test in a containsText type of condition - *
        Only valid for containsText conditions - * @return - */ - public String getContainsText() { - if (attrs!=null) - return (String) attrs.get("text"); - return null; - } - - public String getFormula1() { - if (formulas!=null && formulas.size()>0) - return (String) formulas.get(0); - return null; - } - - public String getFormula2() { - if (formulas!=null && formulas.size()>1) - return (String) formulas.get(1); - return null; - } + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("cfRule.parseOOXML: " + e.toString()); + } + CfRule cf = new CfRule(attrs, formulas); + return cf; + } + + /** + * generate OOXML for this cfRule + * NOW Cf is parsed to obtain OOXML and CfRule is not retained + * + * @return + * @deprecated + */ + public String getOOXML(int priority) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (formulas != null) { + for (int j = 0; j < formulas.size(); j++) { + ooxml.append("" + OOXMLAdapter.stripNonAsciiRetainQuote(formulas.get(j)) + ""); + } + } + // TODO: finish children dataBar, colorScale, iconSet + ooxml.append(""); + return ooxml.toString(); + } + + /** + * get the dxfId (the incremental style associated with this conditional formatting rule) + * + * @return + */ + public int getDxfId() { + if (this.attrs != null) + try { + return Integer.valueOf(this.attrs.get("dxfId")).intValue(); + } catch (Exception e) { + // it's possible to not specify a dxfId + } + return -1; + } + + /** + * set the dxfId (the incremental style associated with this conditional formatting rule) + * + * @param dxfId + * @deprecated + */ + public void setDxfId(int dxfId) { + if (this.attrs == null) this.attrs = new HashMap(); + attrs.put("dxfId", Integer.valueOf(dxfId).toString()); + } + + public OOXMLElement cloneElement() { + return new CfRule(this); + } + + public String getOOXML() { + // TODO Auto-generated method stub + return null; + } + + /** + * get methods + */ + public String getOperator() { + // valid only when type=="cellIs" + if (attrs != null) + return attrs.get("operator"); + return null; + } + + public String getType() { + String type = null; + if (attrs != null) + type = attrs.get("type"); + if (type == null) + type = "cellIs"; + return type; + } + + /** + * returns the text to test in a containsText type of condition + *
        Only valid for containsText conditions + * + * @return + */ + public String getContainsText() { + if (attrs != null) + return attrs.get("text"); + return null; + } + + public String getFormula1() { + if (formulas != null && formulas.size() > 0) + return formulas.get(0); + return null; + } + + public String getFormula2() { + if (formulas != null && formulas.size() > 1) + return formulas.get(1); + return null; + } } -/** dataBar (Data Bar) -* Describes a data bar conditional formatting rule. -* [Example: -* In this example a data bar conditional format is expressed, which spreads across all cell values in the cell range, -* and whose color is blue. -* -* -* -* -* -* end example] -* The length of the data bar for any cell can be calculated as follows: -* Data bar length = minLength + (cell value - minimum value in the range) / 1 (maximum value in the range - -* minimum value in the range) * (maxLength - minLength), -* where min and max length are a fixed percentage of the column width (by default, 10% and 90% respectively.) -* The minimum difference in length (or increment amount) is 1 pixel. -* -* parent: cfRule -* children: SEQ: cfvo (2), color (1) -* attributes: minLength (def=10), maxLength (def=90), showValue (def=true) -*/ +/** + * dataBar (Data Bar) + * Describes a data bar conditional formatting rule. + * [Example: + * In this example a data bar conditional format is expressed, which spreads across all cell values in the cell range, + * and whose color is blue. + * + * + * + * + * + * end example] + * The length of the data bar for any cell can be calculated as follows: + * Data bar length = minLength + (cell value - minimum value in the range) / 1 (maximum value in the range - + * minimum value in the range) * (maxLength - minLength), + * where min and max length are a fixed percentage of the column width (by default, 10% and 90% respectively.) + * The minimum difference in length (or increment amount) is 1 pixel. + *

        + * parent: cfRule + * children: SEQ: cfvo (2), color (1) + * attributes: minLength (def=10), maxLength (def=90), showValue (def=true) + *

        + * colorScale (Color Scale) + * Describes a graduated color scale in this conditional formatting rule. + *

        + * parent: cfRule + * children: SEQ: cfvo (2+), color (2+) + *

        + * cfvo (Conditional Format Value Object) + * Describes the values of the interpolation points in a gradient scale. + *

        + * parents: colorScale, dataBar, iconSet + * children: extLst + * attributes: type (REQ), val, gte (def=true) + */ /** -* colorScale (Color Scale) -* Describes a graduated color scale in this conditional formatting rule. -* -* parent: cfRule -* children: SEQ: cfvo (2+), color (2+) -*/ + * colorScale (Color Scale) + * Describes a graduated color scale in this conditional formatting rule. + * + * parent: cfRule + * children: SEQ: cfvo (2+), color (2+) + */ /** -* cfvo (Conditional Format Value Object) -* Describes the values of the interpolation points in a gradient scale. -* -* parents: colorScale, dataBar, iconSet -* children: extLst -* attributes: type (REQ), val, gte (def=true) -* -*/ + * cfvo (Conditional Format Value Object) + * Describes the values of the interpolation points in a gradient scale. + * + * parents: colorScale, dataBar, iconSet + * children: extLst + * attributes: type (REQ), val, gte (def=true) + * + */ diff --git a/src/main/java/io/starter/formats/OOXML/Color.java b/src/main/java/io/starter/formats/OOXML/Color.java index 5e2e897..4686783 100644 --- a/src/main/java/io/starter/formats/OOXML/Color.java +++ b/src/main/java/io/starter/formats/OOXML/Color.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,17 +22,16 @@ */ package io.starter.formats.OOXML; -import org.xmlpull.v1.XmlPullParser; import io.starter.OpenXLS.FormatHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; /** - * * color (Data Bar Color) * One of the colors associated with the data bar or color scale. Also font ... * Note: the auto attribute is not used in the context of data bars - * + *

        * NOTE: both color, fgColor and bgColor use same CT_COLOR schema: * * @@ -41,730 +40,747 @@ * * * - + *

        * fgColor (Foreground Color) * Foreground color of the cell fill pattern. Cell fill patterns operate with two colors: a background color and a * foreground color. These combine together to make a patterned cell fill. - * - * + *

        + *

        * bgColor (Background Color) * Background color of the cell fill pattern. Cell fill patterns operate with two colors: a background color and a * foreground color. These combine together to make a patterned cell fill. + */ +public class Color implements OOXMLElement { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2546003092245407502L; + private boolean auto = false; + public static int COLORTYPEINDEXED = 0; + public static int COLORTYPERGB = 1; + public static int COLORTYPETHEME = 2; + private int colortype = -1; + private String colorval = null; // value of colortype + private double tint = 0.0; + private String element = null; + private int colorint = -1; // parsed color (tint + value) translated to OpenXLS color int value + private String colorstr = null; // parsed color (tint + value) translated to HTML color string + private Theme theme = null; + + public Color() { + } + public Color(String element, boolean auto, int colortype, String colorval, double tint, short type, Theme t) { + this.element = element; + this.auto = auto; + this.colortype = colortype; + this.colorval = colorval; + this.tint = tint; + this.theme = t; + this.parseColor(type); + } - */ -public class Color implements OOXMLElement{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2546003092245407502L; - private boolean auto= false; - public static int COLORTYPEINDEXED= 0; - public static int COLORTYPERGB= 1; - public static int COLORTYPETHEME= 2; - private int colortype= -1; - private String colorval= null; // value of colortype - private double tint= 0.0; - private String element= null; - private int colorint= -1; // parsed color (tint + value) translated to OpenXLS color int value - private String colorstr= null; // parsed color (tint + value) translated to HTML color string - private Theme theme= null; - public Color() { } - - public Color(String element, boolean auto, int colortype, String colorval, double tint, short type, Theme t) { - this.element= element; - this.auto= auto; - this.colortype= colortype; - this.colorval= colorval; - this.tint= tint; - this.theme= t; - this.parseColor(type); - } - - public Color(Color c) { - this.element= c.element; - this.auto= c.auto; - this.colortype= c.colortype; - this.colorval= c.colorval; - this.tint= c.tint; - this.colorint= c.colorint; - this.colorstr= c.colorstr; - this.theme= c.theme; - } - - - /** - * creates a new Color object based upon a java.awt.Color - * @param clr Color objeect - * @param element "color", "fgColor" or "bgColor" - */ - public Color(java.awt.Color c, String element, Theme t) { - this.colortype= COLORTYPERGB; - this.colorval= "FF" + FormatHandle.colorToHexString(c).substring(1); - this.element= element; - this.theme= t; // ok if it's null - this.parseColor((short)0); - } - - /** - * creates a new Color object based upon a web-compliant Hex Color string - * @param clr - * @param element "color", "fgColor" or "bgColor" - */ - public Color(String clr, String element, Theme t) { - this.colortype= COLORTYPERGB; - if (clr.startsWith("#")) - this.colorval= "FF" + clr.substring(1); - else if (clr.length()==6) - this.colorval= "FF" + clr; - else - this.colorval= clr; - this.element= element; - this.theme= t; // ok if it's null - this.parseColor((short)0); - } - /** - * parses a color element - * root= color, fgColor or bgColor - * @param xpp - * @return - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, short type, WorkBookHandle bk) { - String element= null; - boolean auto= false; - int colortype= -1; - String colorval= null; - double tint= 0.0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("color") || - tnm.equals("fgColor") || - tnm.equals("bgColor") ) { // get attributes - element= tnm; // save element name - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String n= xpp.getAttributeName(i); - String val= xpp.getAttributeValue(i); - if (n.equals("auto")) { - auto= true; - } else if (n.equals("indexed")) { - colortype= Color.COLORTYPEINDEXED; - colorval= val; - } else if (n.equals("rgb")) { - colortype= COLORTYPERGB; - colorval= val; - } else if (n.equals("theme")) { - colortype= COLORTYPETHEME; - colorval= val; - } else if (n.equals("tint")) { - tint= new Double(val).doubleValue(); - } - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals(element)) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("color.parseOOXML: " + e.toString()); - } - Color c= new Color(element, auto, colortype, colorval, tint, type, bk.getWorkBook().getTheme()); - return c; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append("<" + this.element); - if (this.auto) ooxml.append(" auto=\"1\""); - else if (this.colortype==COLORTYPERGB)// rgb - ooxml.append(" rgb=\"" + colorstr + "\""); - else if (this.colortype==COLORTYPEINDEXED) - ooxml.append(" indexed=\"" + colorval + "\""); - else if (this.colortype==COLORTYPETHEME) // theme - ooxml.append(" theme=\"" + colorval + "\""); - if (tint!=0) - ooxml.append(" tint=\"" + tint + "\""); - ooxml.append("/>"); - return ooxml.toString(); - } - - public static String getOOXML(String element, int colortype, int colorval, String colorstr, double tint) { - StringBuffer ooxml= new StringBuffer(); - ooxml.append("<" + element); - if (colortype==COLORTYPERGB) // rgb - ooxml.append(" rgb=\"" + colorstr + "\""); - else if (colortype==COLORTYPEINDEXED) // indexed - ooxml.append(" indexed=\"" + colorval + "\""); - else if (colortype==COLORTYPETHEME) // theme - ooxml.append(" theme=\"" + colorval + "\""); - if (tint!=0) - ooxml.append(" tint=\"" + tint + "\""); - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Color(this); - } - - public String toString() { - String ret= ""; - if (colortype==COLORTYPERGB) // rgb - ret= " rgb=" + colorstr ; - else if (colortype==COLORTYPEINDEXED) // indexed - ret= " indexed=" + colorval; - else if (colortype==COLORTYPETHEME) // theme - ret= " theme=" + colorval ; - if (tint!=0) - ret= " tint=" + tint; - return ret; - } - - /** - * return the translated color int for this OOXML color - * @return - */ - public int getColorInt() { - return this.colorint; - } - - /** - * return the color type for this OOXML color - * (indexed= 0, rgb= 1, theme= 2) - * @return - */ - public int getColorType() { - return this.colortype; - } - - /** - * return the translated HTML color string for this OOXML color - * @return - */ - public String getColorAsOOXMLRBG() { - return this.colorstr; - } - - public java.awt.Color getColorAsColor() { - return FormatHandle.HexStringToColor(this.getColorAsOOXMLRBG()); - } - - /** - * manually set color int - * automatically defaults to indexed color, and looks up color in COLORTABLE - * @param clr - */ - public void setColorInt(int clr) { - this.colorint= clr; - this.colortype= 0; // indexed - // reset other vars as well - if (clr>-1) - this.colorstr= FormatHandle.colorToHexString(FormatHandle.COLORTABLE[clr]).substring(1); - else - this.colorstr= null; - this.tint= 0.0; - this.auto= false; - } - - /** - * sets the color via java.awt.Color - * @param c - */ - public void setColor(java.awt.Color c) { - this.colortype= COLORTYPERGB; - this.colorval= "FF" + FormatHandle.colorToHexString(c).substring(1); - this.parseColor((short)0); - this.tint= 0.0; - this.auto= false; - } - - - /** - * sets the color via a web-compliant Hex Color String - * @param clr - */ - public void setColor(String clr) { - this.colortype= COLORTYPERGB; - if (clr.startsWith("#")) - this.colorval= "FF" + clr.substring(1); - else if (clr.length()==6) - this.colorval= "FF" + clr; - else - this.colorval= clr; - this.parseColor((short)0); - } - /** - * static version of parseColor --takes a color value of OOXML type type - *
        COLORTYPERBG, COLORTYPEINDEXED or COLORTYPETHEME - *
        and returns the - * @param val String OOXML color value, value depends upon colortype - * @param colortype int val's colortype, one of: Color.COLORTYPERGB, Color.COLORTYPEINDEXED, Color.COLORTYPETHEME - * @param type 0= font, 1= fill - * @return String HEX-style color string - */ - public static String parseColor(String val, int colortype, short type, Theme t) { - Color c= new Color("", false, colortype, val, 0, type, t); - return c.colorstr; - } - - /** - * static version of parseColor --takes a color value of OOXML type type - *
        COLORTYPERBG, COLORTYPEINDEXED or COLORTYPETHEME - *
        and returns the indexed color int it represents - * @param val String OOXML color value, value depends upon colortype - * @param colortype int val's colortype, one of: Color.COLORTYPERGB, Color.COLORTYPEINDEXED, Color.COLORTYPETHEME - * @param type 0= font, 1= fill - * @return int - */ - public static int parseColorInt(String val, int colortype, short type, Theme t) { - Color c= new Color("", false, colortype, val, 0, type, t); + public Color(Color c) { + this.element = c.element; + this.auto = c.auto; + this.colortype = c.colortype; + this.colorval = c.colorval; + this.tint = c.tint; + this.colorint = c.colorint; + this.colorstr = c.colorstr; + this.theme = c.theme; + } + + + /** + * creates a new Color object based upon a java.awt.Color + * + * @param clr Color objeect + * @param element "color", "fgColor" or "bgColor" + */ + public Color(java.awt.Color c, String element, Theme t) { + this.colortype = COLORTYPERGB; + this.colorval = "FF" + FormatHandle.colorToHexString(c).substring(1); + this.element = element; + this.theme = t; // ok if it's null + this.parseColor((short) 0); + } + + /** + * creates a new Color object based upon a web-compliant Hex Color string + * + * @param clr + * @param element "color", "fgColor" or "bgColor" + */ + public Color(String clr, String element, Theme t) { + this.colortype = COLORTYPERGB; + if (clr.startsWith("#")) + this.colorval = "FF" + clr.substring(1); + else if (clr.length() == 6) + this.colorval = "FF" + clr; + else + this.colorval = clr; + this.element = element; + this.theme = t; // ok if it's null + this.parseColor((short) 0); + } + + /** + * parses a color element + * root= color, fgColor or bgColor + * + * @param xpp + * @return + */ + public static OOXMLElement parseOOXML(XmlPullParser xpp, short type, WorkBookHandle bk) { + String element = null; + boolean auto = false; + int colortype = -1; + String colorval = null; + double tint = 0.0; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("color") || + tnm.equals("fgColor") || + tnm.equals("bgColor")) { // get attributes + element = tnm; // save element name + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String n = xpp.getAttributeName(i); + String val = xpp.getAttributeValue(i); + if (n.equals("auto")) { + auto = true; + } else if (n.equals("indexed")) { + colortype = Color.COLORTYPEINDEXED; + colorval = val; + } else if (n.equals("rgb")) { + colortype = COLORTYPERGB; + colorval = val; + } else if (n.equals("theme")) { + colortype = COLORTYPETHEME; + colorval = val; + } else if (n.equals("tint")) { + tint = new Double(val).doubleValue(); + } + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals(element)) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("color.parseOOXML: " + e.toString()); + } + Color c = new Color(element, auto, colortype, colorval, tint, type, bk.getWorkBook().getTheme()); + return c; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append("<" + this.element); + if (this.auto) ooxml.append(" auto=\"1\""); + else if (this.colortype == COLORTYPERGB)// rgb + ooxml.append(" rgb=\"" + colorstr + "\""); + else if (this.colortype == COLORTYPEINDEXED) + ooxml.append(" indexed=\"" + colorval + "\""); + else if (this.colortype == COLORTYPETHEME) // theme + ooxml.append(" theme=\"" + colorval + "\""); + if (tint != 0) + ooxml.append(" tint=\"" + tint + "\""); + ooxml.append("/>"); + return ooxml.toString(); + } + + public static String getOOXML(String element, int colortype, int colorval, String colorstr, double tint) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append("<" + element); + if (colortype == COLORTYPERGB) // rgb + ooxml.append(" rgb=\"" + colorstr + "\""); + else if (colortype == COLORTYPEINDEXED) // indexed + ooxml.append(" indexed=\"" + colorval + "\""); + else if (colortype == COLORTYPETHEME) // theme + ooxml.append(" theme=\"" + colorval + "\""); + if (tint != 0) + ooxml.append(" tint=\"" + tint + "\""); + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Color(this); + } + + public String toString() { + String ret = ""; + if (colortype == COLORTYPERGB) // rgb + ret = " rgb=" + colorstr; + else if (colortype == COLORTYPEINDEXED) // indexed + ret = " indexed=" + colorval; + else if (colortype == COLORTYPETHEME) // theme + ret = " theme=" + colorval; + if (tint != 0) + ret = " tint=" + tint; + return ret; + } + + /** + * return the translated color int for this OOXML color + * + * @return + */ + public int getColorInt() { + return this.colorint; + } + + /** + * return the color type for this OOXML color + * (indexed= 0, rgb= 1, theme= 2) + * + * @return + */ + public int getColorType() { + return this.colortype; + } + + /** + * return the translated HTML color string for this OOXML color + * + * @return + */ + public String getColorAsOOXMLRBG() { + return this.colorstr; + } + + public java.awt.Color getColorAsColor() { + return FormatHandle.HexStringToColor(this.getColorAsOOXMLRBG()); + } + + /** + * manually set color int + * automatically defaults to indexed color, and looks up color in COLORTABLE + * + * @param clr + */ + public void setColorInt(int clr) { + this.colorint = clr; + this.colortype = 0; // indexed + // reset other vars as well + if (clr > -1) + this.colorstr = FormatHandle.colorToHexString(FormatHandle.COLORTABLE[clr]).substring(1); + else + this.colorstr = null; + this.tint = 0.0; + this.auto = false; + } + + /** + * sets the color via java.awt.Color + * + * @param c + */ + public void setColor(java.awt.Color c) { + this.colortype = COLORTYPERGB; + this.colorval = "FF" + FormatHandle.colorToHexString(c).substring(1); + this.parseColor((short) 0); + this.tint = 0.0; + this.auto = false; + } + + + /** + * sets the color via a web-compliant Hex Color String + * + * @param clr + */ + public void setColor(String clr) { + this.colortype = COLORTYPERGB; + if (clr.startsWith("#")) + this.colorval = "FF" + clr.substring(1); + else if (clr.length() == 6) + this.colorval = "FF" + clr; + else + this.colorval = clr; + this.parseColor((short) 0); + } + + /** + * static version of parseColor --takes a color value of OOXML type type + *
        COLORTYPERBG, COLORTYPEINDEXED or COLORTYPETHEME + *
        and returns the + * + * @param val String OOXML color value, value depends upon colortype + * @param colortype int val's colortype, one of: Color.COLORTYPERGB, Color.COLORTYPEINDEXED, Color.COLORTYPETHEME + * @param type 0= font, 1= fill + * @return String HEX-style color string + */ + public static String parseColor(String val, int colortype, short type, Theme t) { + Color c = new Color("", false, colortype, val, 0, type, t); + return c.colorstr; + } + + /** + * static version of parseColor --takes a color value of OOXML type type + *
        COLORTYPERBG, COLORTYPEINDEXED or COLORTYPETHEME + *
        and returns the indexed color int it represents + * + * @param val String OOXML color value, value depends upon colortype + * @param colortype int val's colortype, one of: Color.COLORTYPERGB, Color.COLORTYPEINDEXED, Color.COLORTYPETHEME + * @param type 0= font, 1= fill + * @return int + */ + public static int parseColorInt(String val, int colortype, short type, Theme t) { + Color c = new Color("", false, colortype, val, 0, type, t); // c.parseColor(type); - return c.colorint; - } - - /** - * simple utility to parse correct colors (int + html string) from OOXML style color - * @param type 0= font, -1= fill - */ - private void parseColor(short type) { - try { - if (this.colortype==COLORTYPERGB) { // rgb - color string - this.colorint= FormatHandle.HexStringToColorInt(this.colorval, type); // find best match - this.colorstr= this.colorval; - } else if (this.colortype==COLORTYPEINDEXED) { // indexed (corresponds to either our color int or a custom set of indexed colors - this.colorint= Integer.valueOf(this.colorval).intValue(); - if (this.colorint==64 && type==FormatHandle.colorFONT) // means system foreground: Default foreground color. This is the window text color in the sheet display. - this.colorstr = FormatHandle.colorToHexString(FormatHandle.getColor(0)); + return c.colorint; + } + + /** + * simple utility to parse correct colors (int + html string) from OOXML style color + * + * @param type 0= font, -1= fill + */ + private void parseColor(short type) { + try { + if (this.colortype == COLORTYPERGB) { // rgb - color string + this.colorint = FormatHandle.HexStringToColorInt(this.colorval, type); // find best match + this.colorstr = this.colorval; + } else if (this.colortype == COLORTYPEINDEXED) { // indexed (corresponds to either our color int or a custom set of indexed colors + this.colorint = Integer.valueOf(this.colorval).intValue(); + if (this.colorint == 64 && type == FormatHandle.colorFONT) // means system foreground: Default foreground color. This is the window text color in the sheet display. + this.colorstr = FormatHandle.colorToHexString(FormatHandle.getColor(0)); // this.colorint= 0; // black - else - this.colorstr = FormatHandle.colorToHexString(FormatHandle.getColor(this.colorint)); - } else if (this.colortype==COLORTYPETHEME) { // theme - Object[] o= Color.parseThemeColor(this.colorval, this.tint, type, this.theme); - this.colorint= (Integer) o[0]; - this.colorstr= (String) o[1]; - } - } catch (Exception e) { - Logger.logWarn("color.parseColor: " + colortype + ":" + colorval + ": " + e.toString()); - } - } - - /** - * interprets theme colorval and tint - * TODO: read in theme colors from theme1.xml - * @param colorval String theme colorval (see SchemeClr and Theme for details) - EITHER an Index into the collection, referencing a particular or value expressed in the Theme part. - OR the actual clrScheme string - * @param tint double tint to apply to the base color (0 for none) - * @param type short 0= font, 1= fill - * @return - */ - public static Object[] parseThemeColor(String colorval, double tint, short type, Theme t) { - /** - * TODO: read this in from THEME!!! - */ - final String[][] themeColors= { - {"dk1", "000000"}, - {"lt1", "FFFFFF"}, - {"dk2", "1F497D"}, - {"lt2", "EEECE1"}, - {"accent1", "4F81BD"}, - {"accent1", "C0504D"}, - {"accent3", "9BBB59"}, - {"accent4", "8064A2"}, - {"accent5", "4BACC6"}, - {"accent6", "F79646"}, - {"hlink", "0000FF"}, - {"folHlink", "800080"}, - }; - - // TODO: read theme colors from file - Object[] o= new Object[2]; - int i= 0; - String clr= ""; - try { - i= Integer.valueOf(colorval).intValue(); - clr= t.genericThemeClrs[i]; - } catch (Exception e) { // OpenXLS-created xlsx files will not have theme entries - for (i= 0; i < themeColors.length; i++) - if (themeColors[i][0].equals(colorval)) { - clr= themeColors[i][1]; - break; - } - } - if (Math.abs(tint) > .005){ - java.awt.Color c=applyTint(tint, clr); - o[0]= FormatHandle.getColorInt(c); - o[1]=FormatHandle.colorToHexString(c).substring(1); // avoid # - }else { - o[0]= FormatHandle.HexStringToColorInt("#" + clr, type); - o[1]= "#" + clr; - } - return o; - } - - - /** - If tint is supplied, then it is applied to the RGB value of the color to determine the final - color applied. - The tint value is stored as a double from -1.0 .. 1.0, where -1.0 means 100% darken and - 1.0 means 100% lighten. Also, 0.0 means no change. - In loading the RGB value, it is converted to HLS where HLS values are (0..HLSMAX), where - HLSMAX is currently 255. - */ - private static java.awt.Color applyTint(double tint, String clr) { - final int HLSMAX= 255; - int r = Integer.parseInt(clr.substring(0, 2), 16); - int g = Integer.parseInt(clr.substring(2, 4), 16); - int b = Integer.parseInt(clr.substring(4, 6), 16); - - HSLColor hsl= new HSLColor(); - hsl.initHSLbyRGB(r, g, b); - double l= hsl.getLuminence(); - if (tint < 0) //darken - l = l * (1 + tint); - else // lighten - l = l * (1- tint) + (HLSMAX - HLSMAX * (1.0 - tint)); - l= Math.round(l); - hsl.initRGBbyHSL(hsl.getHue(), hsl.getSaturation(), new Double(l).intValue()); - return new java.awt.Color(hsl.getRed(), hsl.getGreen(), hsl.getBlue()); - - } + else + this.colorstr = FormatHandle.colorToHexString(FormatHandle.getColor(this.colorint)); + } else if (this.colortype == COLORTYPETHEME) { // theme + Object[] o = Color.parseThemeColor(this.colorval, this.tint, type, this.theme); + this.colorint = (Integer) o[0]; + this.colorstr = (String) o[1]; + } + } catch (Exception e) { + Logger.logWarn("color.parseColor: " + colortype + ":" + colorval + ": " + e.toString()); + } + } + + /** + * interprets theme colorval and tint + * TODO: read in theme colors from theme1.xml + * + * @param colorval String theme colorval (see SchemeClr and Theme for details) + * EITHER an Index into the collection, referencing a particular or value expressed in the Theme part. + * OR the actual clrScheme string + * @param tint double tint to apply to the base color (0 for none) + * @param type short 0= font, 1= fill + * @return + */ + public static Object[] parseThemeColor(String colorval, double tint, short type, Theme t) { + /** + * TODO: read this in from THEME!!! + */ + final String[][] themeColors = { + {"dk1", "000000"}, + {"lt1", "FFFFFF"}, + {"dk2", "1F497D"}, + {"lt2", "EEECE1"}, + {"accent1", "4F81BD"}, + {"accent1", "C0504D"}, + {"accent3", "9BBB59"}, + {"accent4", "8064A2"}, + {"accent5", "4BACC6"}, + {"accent6", "F79646"}, + {"hlink", "0000FF"}, + {"folHlink", "800080"}, + }; + + // TODO: read theme colors from file + Object[] o = new Object[2]; + int i = 0; + String clr = ""; + try { + i = Integer.valueOf(colorval).intValue(); + clr = t.genericThemeClrs[i]; + } catch (Exception e) { // OpenXLS-created xlsx files will not have theme entries + for (i = 0; i < themeColors.length; i++) + if (themeColors[i][0].equals(colorval)) { + clr = themeColors[i][1]; + break; + } + } + if (Math.abs(tint) > .005) { + java.awt.Color c = applyTint(tint, clr); + o[0] = FormatHandle.getColorInt(c); + o[1] = FormatHandle.colorToHexString(c).substring(1); // avoid # + } else { + o[0] = FormatHandle.HexStringToColorInt("#" + clr, type); + o[1] = "#" + clr; + } + return o; + } + + + /** + * If tint is supplied, then it is applied to the RGB value of the color to determine the final + * color applied. + * The tint value is stored as a double from -1.0 .. 1.0, where -1.0 means 100% darken and + * 1.0 means 100% lighten. Also, 0.0 means no change. + * In loading the RGB value, it is converted to HLS where HLS values are (0..HLSMAX), where + * HLSMAX is currently 255. + */ + private static java.awt.Color applyTint(double tint, String clr) { + final int HLSMAX = 255; + int r = Integer.parseInt(clr.substring(0, 2), 16); + int g = Integer.parseInt(clr.substring(2, 4), 16); + int b = Integer.parseInt(clr.substring(4, 6), 16); + + HSLColor hsl = new HSLColor(); + hsl.initHSLbyRGB(r, g, b); + double l = hsl.getLuminence(); + if (tint < 0) //darken + l = l * (1 + tint); + else // lighten + l = l * (1 - tint) + (HLSMAX - HLSMAX * (1.0 - tint)); + l = Math.round(l); + hsl.initRGBbyHSL(hsl.getHue(), hsl.getSaturation(), new Double(l).intValue()); + return new java.awt.Color(hsl.getRed(), hsl.getGreen(), hsl.getBlue()); + + } } - /* HSL stands for hue, saturation, lightness - * Both HSL and HSV describe colors as points in a cylinder - * whose central axis ranges from black at the bottom to white at the top - * with neutral colors between them, - * where angle around the axis corresponds to hue, - * distance from the axis corresponds to saturation, - * and distance along the axis corresponds to lightness, value, or brightness.*/ +/* HSL stands for hue, saturation, lightness + * Both HSL and HSV describe colors as points in a cylinder + * whose central axis ranges from black at the bottom to white at the top + * with neutral colors between them, + * where angle around the axis corresponds to hue, + * distance from the axis corresponds to saturation, + * and distance along the axis corresponds to lightness, value, or brightness.*/ - /* - * Conversion from RGB to HSL - - Let r, g, b E [0,1] be the red, green, and blue coordinates, respectively, of a color in RGB space. - Let max be the greatest of r, g, and b, and min the least. - - hue angle h: - - h= 0 if max=min (for grays) - = 60 deg x (g-b)/(max-min) + 360 deg) mod 360 deg if max=r - = 60 deg x (b-r)/(max-min) + 120 deg) if max=g - = 60 deg x (r-g)/(max-min) + 240 deg) if max=b - - lightness l= (max-min)/2 - - Saturation s - - s= 0 if max=min - = (max-min)/2l if l <= 1/2 - = (max-min)/(2-2l) if l > 1/2 - - The value of h is generally normalized to lie between 0 and 360, and h = 0 is used when max = min (that is, for grays) - though the hue has no geometric meaning there, where the saturation s is zero. - Similarly, the choice of 0 as the value for s when l is equal to 0 or 1 is arbitrary. - - Conversion from HSL to RGB - - Given a color defined by (h, s, l) values in HSL space, with h in the range [0, 360), indicating the angle, - in degrees of the hue, and with s and l in the range [0, 1], representing the saturation and lightness, respectively, - a corresponding (r, g, b) triplet in RGB space, with r, g, and b also in range [0, 1], and corresponding to red, green, and blue, - respectively, can be computed as follows: - - First, if s = 0, then the resulting color is achromatic, or gray. In this special case, r, g, and b all equal l. - Note that the value of h is ignored, and may be undefined in this situation. - - The following procedure can be used, even when s is zero: - - q= l x (1+s) if l < 1/2 - = l + s - (l x s) if l >= 1/2 - - p= 2 x l - q - - hk= h / 360 (h normalized in the range 0-1) - - tr= hk + 1/3 - - tg= hk - - tb= hk - 1/3 - - if tc +/* + * Conversion from RGB to HSL - */ - class HSLColor { - private final static int HSLMAX = 255; - private final static int RGBMAX = 255; - private final static int UNDEFINED = 170; - - private int pHue; - private int pSat; - private int pLum; - private int pRed; - private int pGreen; - private int pBlue; - - public void initHSLbyRGB(int R, int G, int B) { - // sets Hue, Sat, Lum - int cMax; - int cMin; - int RDelta; - int GDelta; - int BDelta; - int cMinus; - int cPlus; - - pRed = R; - pGreen = G; - pBlue = B; - - //Set Max & MinColor Values - cMax = iMax(iMax(R, G), B); - cMin = iMin(iMin(R, G), B); - - cMinus = cMax - cMin; - cPlus = cMax + cMin; - - // Calculate luminescence (lightness) - pLum = ((cPlus * HSLMAX) + RGBMAX) / (2 * RGBMAX); - - if (cMax == cMin) { - // greyscale - pSat = 0; - pHue = UNDEFINED; - } else { - // Calculate color saturation - if (pLum <= (HSLMAX / 2)) { - pSat = (int)(((cMinus * HSLMAX) + 0.5) / cPlus); - } else { - pSat = (int)(((cMinus * HSLMAX) + 0.5) / (2 * RGBMAX - cPlus)); - } - - //Calculate hue - RDelta = (int)((((cMax - R) * (HSLMAX / 6)) + 0.5) / cMinus); - GDelta = (int)((((cMax - G) * (HSLMAX / 6)) + 0.5) / cMinus); - BDelta = (int)((((cMax - B) * (HSLMAX / 6)) + 0.5) / cMinus); - - if (cMax == R) { - pHue = BDelta - GDelta; - } else if (cMax == G) { - pHue = (HSLMAX / 3) + RDelta - BDelta; - } else if (cMax == B) { - pHue = ((2 * HSLMAX) / 3) + GDelta - RDelta; - } - - if (pHue < 0) { - pHue = pHue + HSLMAX; - } - } - } - - public void initRGBbyHSL(int H, int S, int L) { - int Magic1; - int Magic2; - - pHue = H; - pLum = L; - pSat = S; - - if (S == 0) { //Greyscale - pRed = (L * RGBMAX) / HSLMAX; //luminescence: set to range - pGreen = pRed; - pBlue = pRed; - } else { - if (L <= HSLMAX / 2) { - Magic2 = (L * (HSLMAX + S) + (HSLMAX / 2)) / (HSLMAX); - } else { - Magic2 = L + S - ((L * S) + (HSLMAX / 2)) / HSLMAX; - } - Magic1 = 2 * L - Magic2; - - //get R, G, B; change units from HSLMAX range to RGBMAX range - pRed = (hueToRGB(Magic1, Magic2, H + (HSLMAX / 3)) * RGBMAX + (HSLMAX / 2)) / HSLMAX; - if (pRed > RGBMAX) { - pRed = RGBMAX; - } - - pGreen = (hueToRGB(Magic1, Magic2, H) * RGBMAX + (HSLMAX / 2)) / HSLMAX; - if (pGreen > RGBMAX) { - pGreen = RGBMAX; - } - - pBlue = (hueToRGB(Magic1, Magic2, H - (HSLMAX / 3)) * RGBMAX + (HSLMAX / 2)) / HSLMAX; - if (pBlue > RGBMAX) { - pBlue = RGBMAX; - } - } - } - - private int hueToRGB(int mag1, int mag2, int Hue) { - // check the range - if (Hue < 0) { - Hue = Hue + HSLMAX; - } else if (Hue > HSLMAX) { - Hue = Hue - HSLMAX; - } - - if (Hue < (HSLMAX / 6)) - return (mag1 + (((mag2 - mag1) * Hue + (HSLMAX / 12)) / (HSLMAX / 6))); - - if (Hue < (HSLMAX / 2)) - return mag2; - - if (Hue < (HSLMAX * 2 / 3)) - return (mag1 + (((mag2 - mag1) * ((HSLMAX * 2 / 3) - Hue) + (HSLMAX / 12)) / (HSLMAX / 6))); - - return mag1; - } - - private int iMax(int a, int b) { - if (a > b) return a; else return b; - } - private int iMin(int a, int b) { - if (a < b) return a; else return b; - } - - - - public void greyscale() { - initRGBbyHSL(UNDEFINED, 0, pLum); - } - - - // -- - - public int getHue() { - return pHue; - } - - public void setHue(int iToValue) { - while (iToValue < 0) { - iToValue = HSLMAX + iToValue; - } - while (iToValue > HSLMAX) { - iToValue = iToValue - HSLMAX; - } - - initRGBbyHSL(iToValue, pSat, pLum); - } - - // -- - - public int getSaturation() { - return pSat; - } - - public void setSaturation(int iToValue) { - if (iToValue < 0) { - iToValue = 0; - } else if (iToValue > HSLMAX) { - iToValue = HSLMAX; - } - - initRGBbyHSL(pHue, iToValue, pLum); - } - - // -- - - public int getLuminence() { - return pLum; - } - - public void setLuminence(int iToValue) { - if (iToValue < 0) { - iToValue = 0; - } else if (iToValue > HSLMAX) { - iToValue = HSLMAX; - } + Let r, g, b E [0,1] be the red, green, and blue coordinates, respectively, of a color in RGB space. + Let max be the greatest of r, g, and b, and min the least. + + hue angle h: + + h= 0 if max=min (for grays) + = 60 deg x (g-b)/(max-min) + 360 deg) mod 360 deg if max=r + = 60 deg x (b-r)/(max-min) + 120 deg) if max=g + = 60 deg x (r-g)/(max-min) + 240 deg) if max=b - initRGBbyHSL(pHue, pSat, iToValue); - } + lightness l= (max-min)/2 - // -- + Saturation s - public int getRed() { - return pRed; - } + s= 0 if max=min + = (max-min)/2l if l <= 1/2 + = (max-min)/(2-2l) if l > 1/2 - public void setRed(int iNewValue) { - initHSLbyRGB(iNewValue, pGreen, pBlue); - } + The value of h is generally normalized to lie between 0 and 360, and h = 0 is used when max = min (that is, for grays) + though the hue has no geometric meaning there, where the saturation s is zero. + Similarly, the choice of 0 as the value for s when l is equal to 0 or 1 is arbitrary. - // -- + Conversion from HSL to RGB - public int getGreen() { - return pGreen; - } + Given a color defined by (h, s, l) values in HSL space, with h in the range [0, 360), indicating the angle, + in degrees of the hue, and with s and l in the range [0, 1], representing the saturation and lightness, respectively, + a corresponding (r, g, b) triplet in RGB space, with r, g, and b also in range [0, 1], and corresponding to red, green, and blue, + respectively, can be computed as follows: - public void setGreen(int iNewValue) { - initHSLbyRGB(pRed, iNewValue, pBlue); - } + First, if s = 0, then the resulting color is achromatic, or gray. In this special case, r, g, and b all equal l. + Note that the value of h is ignored, and may be undefined in this situation. - // -- + The following procedure can be used, even when s is zero: - public int getBlue() { - return pBlue; - } + q= l x (1+s) if l < 1/2 + = l + s - (l x s) if l >= 1/2 - public void setBlue(int iNewValue) { - initHSLbyRGB(pRed, pGreen, iNewValue); - } + p= 2 x l - q - // -- + hk= h / 360 (h normalized in the range 0-1) - public void reverseColor() { - setHue(pHue + (HSLMAX / 2)); - } + tr= hk + 1/3 - // -- + tg= hk - public void reverseLight() { - setLuminence(HSLMAX - pLum); - } + tb= hk - 1/3 - // -- + if tc - public void brighten(float fPercent) { - int L; + */ +class HSLColor { + private final static int HSLMAX = 255; + private final static int RGBMAX = 255; + private final static int UNDEFINED = 170; + + private int pHue; + private int pSat; + private int pLum; + private int pRed; + private int pGreen; + private int pBlue; + + public void initHSLbyRGB(int R, int G, int B) { + // sets Hue, Sat, Lum + int cMax; + int cMin; + int RDelta; + int GDelta; + int BDelta; + int cMinus; + int cPlus; + + pRed = R; + pGreen = G; + pBlue = B; + + //Set Max & MinColor Values + cMax = iMax(iMax(R, G), B); + cMin = iMin(iMin(R, G), B); + + cMinus = cMax - cMin; + cPlus = cMax + cMin; + + // Calculate luminescence (lightness) + pLum = ((cPlus * HSLMAX) + RGBMAX) / (2 * RGBMAX); + + if (cMax == cMin) { + // greyscale + pSat = 0; + pHue = UNDEFINED; + } else { + // Calculate color saturation + if (pLum <= (HSLMAX / 2)) { + pSat = (int) (((cMinus * HSLMAX) + 0.5) / cPlus); + } else { + pSat = (int) (((cMinus * HSLMAX) + 0.5) / (2 * RGBMAX - cPlus)); + } + + //Calculate hue + RDelta = (int) ((((cMax - R) * (HSLMAX / 6)) + 0.5) / cMinus); + GDelta = (int) ((((cMax - G) * (HSLMAX / 6)) + 0.5) / cMinus); + BDelta = (int) ((((cMax - B) * (HSLMAX / 6)) + 0.5) / cMinus); + + if (cMax == R) { + pHue = BDelta - GDelta; + } else if (cMax == G) { + pHue = (HSLMAX / 3) + RDelta - BDelta; + } else if (cMax == B) { + pHue = ((2 * HSLMAX) / 3) + GDelta - RDelta; + } + + if (pHue < 0) { + pHue = pHue + HSLMAX; + } + } + } - if (fPercent == 0) { - return; - } + public void initRGBbyHSL(int H, int S, int L) { + int Magic1; + int Magic2; + + pHue = H; + pLum = L; + pSat = S; + + if (S == 0) { //Greyscale + pRed = (L * RGBMAX) / HSLMAX; //luminescence: set to range + pGreen = pRed; + pBlue = pRed; + } else { + if (L <= HSLMAX / 2) { + Magic2 = (L * (HSLMAX + S) + (HSLMAX / 2)) / (HSLMAX); + } else { + Magic2 = L + S - ((L * S) + (HSLMAX / 2)) / HSLMAX; + } + Magic1 = 2 * L - Magic2; + + //get R, G, B; change units from HSLMAX range to RGBMAX range + pRed = (hueToRGB(Magic1, Magic2, H + (HSLMAX / 3)) * RGBMAX + (HSLMAX / 2)) / HSLMAX; + if (pRed > RGBMAX) { + pRed = RGBMAX; + } + + pGreen = (hueToRGB(Magic1, Magic2, H) * RGBMAX + (HSLMAX / 2)) / HSLMAX; + if (pGreen > RGBMAX) { + pGreen = RGBMAX; + } + + pBlue = (hueToRGB(Magic1, Magic2, H - (HSLMAX / 3)) * RGBMAX + (HSLMAX / 2)) / HSLMAX; + if (pBlue > RGBMAX) { + pBlue = RGBMAX; + } + } + } - L = (int)(pLum * fPercent); - if (L < 0) L = 0; - if (L > HSLMAX) L = HSLMAX; + private int hueToRGB(int mag1, int mag2, int Hue) { + // check the range + if (Hue < 0) { + Hue = Hue + HSLMAX; + } else if (Hue > HSLMAX) { + Hue = Hue - HSLMAX; + } - setLuminence(L); - } + if (Hue < (HSLMAX / 6)) + return (mag1 + (((mag2 - mag1) * Hue + (HSLMAX / 12)) / (HSLMAX / 6))); - // -- - // -- + if (Hue < (HSLMAX / 2)) + return mag2; - public void blend(int R, int G, int B, float fPercent) { - if (fPercent >= 1) { - initHSLbyRGB(R, G, B); - return; - } - if (fPercent <= 0) - return; + if (Hue < (HSLMAX * 2 / 3)) + return (mag1 + (((mag2 - mag1) * ((HSLMAX * 2 / 3) - Hue) + (HSLMAX / 12)) / (HSLMAX / 6))); + + return mag1; + } - int newR = (int)((R * fPercent) + (pRed * (1.0 - fPercent))); - int newG = (int)((G * fPercent) + (pGreen * (1.0 - fPercent))); - int newB = (int)((B * fPercent) + (pBlue * (1.0 - fPercent))); + private int iMax(int a, int b) { + if (a > b) return a; + else return b; + } + + private int iMin(int a, int b) { + if (a < b) return a; + else return b; + } + + + public void greyscale() { + initRGBbyHSL(UNDEFINED, 0, pLum); + } + + + // -- + + public int getHue() { + return pHue; + } + + public void setHue(int iToValue) { + while (iToValue < 0) { + iToValue = HSLMAX + iToValue; + } + while (iToValue > HSLMAX) { + iToValue = iToValue - HSLMAX; + } + + initRGBbyHSL(iToValue, pSat, pLum); + } + + // -- + + public int getSaturation() { + return pSat; + } + + public void setSaturation(int iToValue) { + if (iToValue < 0) { + iToValue = 0; + } else if (iToValue > HSLMAX) { + iToValue = HSLMAX; + } + + initRGBbyHSL(pHue, iToValue, pLum); + } - initHSLbyRGB(newR, newG, newB); - } - } + // -- + + public int getLuminence() { + return pLum; + } + + public void setLuminence(int iToValue) { + if (iToValue < 0) { + iToValue = 0; + } else if (iToValue > HSLMAX) { + iToValue = HSLMAX; + } + + initRGBbyHSL(pHue, pSat, iToValue); + } + + // -- + + public int getRed() { + return pRed; + } + + public void setRed(int iNewValue) { + initHSLbyRGB(iNewValue, pGreen, pBlue); + } + + // -- + + public int getGreen() { + return pGreen; + } + + public void setGreen(int iNewValue) { + initHSLbyRGB(pRed, iNewValue, pBlue); + } + + // -- + + public int getBlue() { + return pBlue; + } + + public void setBlue(int iNewValue) { + initHSLbyRGB(pRed, pGreen, iNewValue); + } + + // -- + + public void reverseColor() { + setHue(pHue + (HSLMAX / 2)); + } + + // -- + + public void reverseLight() { + setLuminence(HSLMAX - pLum); + } + + // -- + + public void brighten(float fPercent) { + int L; + + if (fPercent == 0) { + return; + } + + L = (int) (pLum * fPercent); + if (L < 0) L = 0; + if (L > HSLMAX) L = HSLMAX; + + setLuminence(L); + } + + // -- + // -- + + public void blend(int R, int G, int B, float fPercent) { + if (fPercent >= 1) { + initHSLbyRGB(R, G, B); + return; + } + if (fPercent <= 0) + return; + + int newR = (int) ((R * fPercent) + (pRed * (1.0 - fPercent))); + int newG = (int) ((G * fPercent) + (pGreen * (1.0 - fPercent))); + int newB = (int) ((B * fPercent) + (pBlue * (1.0 - fPercent))); + + initHSLbyRGB(newR, newG, newB); + } +} /* Color.getHSBColor(hue, saturation, brightness diff --git a/src/main/java/io/starter/formats/OOXML/ColorChoice.java b/src/main/java/io/starter/formats/OOXML/ColorChoice.java index 65b9464..6296c46 100644 --- a/src/main/java/io/starter/formats/OOXML/ColorChoice.java +++ b/src/main/java/io/starter/formats/OOXML/ColorChoice.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,144 +22,140 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import java.util.HashMap; -import java.util.Iterator; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.FormatHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Stack; /** - * * ColorChoice: choice of: hslClr (Hue, Saturation, Luminance Color Model) * prstClr (Preset Color) §5.1.2.2.22 schemeClr (Scheme Color) §5.1.2.2.29 * scrgbClr (RGB Color Model - Percentage Variant) srgbClr (RGB Color Model - * Hex Variant) sysClr (System Color) - * - * */ // TODO: FINISH: child elements governing color transformations // finish hslClr public class ColorChoice implements OOXMLElement { - private static final long serialVersionUID = -4117811305941771643L; - private SchemeClr s; - private SrgbClr srgb; - private SysClr sys; - private ScrgbClr scrgb; - private PrstClr p; - public Theme theme; - - public ColorChoice(SchemeClr s, SrgbClr srgb, SysClr sys, ScrgbClr scrgb, - PrstClr p) { - this.s = s; - this.srgb = srgb; - this.sys = sys; - this.scrgb = scrgb; - this.p = p; - } - - private void setTheme(Theme t) { - this.theme = t; - } - - public ColorChoice(ColorChoice c) { - this.s = c.s; - this.srgb = c.srgb; - this.sys = c.sys; - this.scrgb = c.scrgb; - this.p = c.p; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - SchemeClr s = null; - SrgbClr srgb = null; - SysClr sys = null; - ScrgbClr scrgb = null; - PrstClr p = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("schemeClr")) { - lastTag.push(tnm); - s = (SchemeClr) SchemeClr.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("srgbClr")) { - lastTag.push(tnm); - srgb = (SrgbClr) SrgbClr.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("sysClr")) { - lastTag.push(tnm); - sys = (SysClr) SysClr.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("scrgbClr")) { - lastTag.push(tnm); - scrgb = (ScrgbClr) ScrgbClr.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("prstClr")) { - lastTag.push(tnm); - p = (PrstClr) PrstClr.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } - /* tnm.equals("hslClr") */// TODO: finish - - } else if (eventType == XmlPullParser.END_TAG) { // shouldn't - // get here - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ColorChoice.parseOOXML: " + e.toString()); - } - ColorChoice c = new ColorChoice(s, srgb, sys, scrgb, p); - c.setTheme(bk.getWorkBook().getTheme()); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (s != null) - ooxml.append(s.getOOXML()); - else if (sys != null) - ooxml.append(sys.getOOXML()); - else if (srgb != null) - ooxml.append(srgb.getOOXML()); - else if (scrgb != null) - ooxml.append(scrgb.getOOXML()); - else if (p != null) - ooxml.append(p.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ColorChoice(this); - } - - public int getColor() { - if (s != null) - return s.getColor(); - else if (sys != null) - sys.getColor(); - else if (srgb != null) - srgb.getColor(); - else if (scrgb != null) - scrgb.getColor(); - else if (p != null) - p.getColor(); - return -1; - - } + private static final long serialVersionUID = -4117811305941771643L; + private SchemeClr s; + private SrgbClr srgb; + private SysClr sys; + private ScrgbClr scrgb; + private PrstClr p; + public Theme theme; + + public ColorChoice(SchemeClr s, SrgbClr srgb, SysClr sys, ScrgbClr scrgb, + PrstClr p) { + this.s = s; + this.srgb = srgb; + this.sys = sys; + this.scrgb = scrgb; + this.p = p; + } + + private void setTheme(Theme t) { + this.theme = t; + } + + public ColorChoice(ColorChoice c) { + this.s = c.s; + this.srgb = c.srgb; + this.sys = c.sys; + this.scrgb = c.scrgb; + this.p = c.p; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + SchemeClr s = null; + SrgbClr srgb = null; + SysClr sys = null; + ScrgbClr scrgb = null; + PrstClr p = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("schemeClr")) { + lastTag.push(tnm); + s = SchemeClr.parseOOXML(xpp, lastTag, bk); + lastTag.pop(); + break; + } else if (tnm.equals("srgbClr")) { + lastTag.push(tnm); + srgb = SrgbClr.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } else if (tnm.equals("sysClr")) { + lastTag.push(tnm); + sys = SysClr.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } else if (tnm.equals("scrgbClr")) { + lastTag.push(tnm); + scrgb = ScrgbClr.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } else if (tnm.equals("prstClr")) { + lastTag.push(tnm); + p = PrstClr.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } + /* tnm.equals("hslClr") */// TODO: finish + + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't + // get here + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("ColorChoice.parseOOXML: " + e.toString()); + } + ColorChoice c = new ColorChoice(s, srgb, sys, scrgb, p); + c.setTheme(bk.getWorkBook().getTheme()); + return c; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + if (s != null) + ooxml.append(s.getOOXML()); + else if (sys != null) + ooxml.append(sys.getOOXML()); + else if (srgb != null) + ooxml.append(srgb.getOOXML()); + else if (scrgb != null) + ooxml.append(scrgb.getOOXML()); + else if (p != null) + ooxml.append(p.getOOXML()); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new ColorChoice(this); + } + + public int getColor() { + if (s != null) + return s.getColor(); + else if (sys != null) + sys.getColor(); + else if (srgb != null) + srgb.getColor(); + else if (scrgb != null) + scrgb.getColor(); + else if (p != null) + p.getColor(); + return -1; + + } } @@ -167,7 +163,7 @@ else if (p != null) * schemeClr (Scheme Color) This element specifies a color bound to a user's * theme. As with all elements which define a color, it is possible to apply a * list of color transforms to the base color defined. - * + *

        * accent1 (Accent Color 1) Extra scheme color 1 accent2 (Accent Color 2) Extra * scheme color 2 accent3 (Accent Color 3) Extra scheme color 3 accent4 (Accent * Color 4) Extra scheme color 4 accent5 (Accent Color 5) Extra scheme color 5 @@ -180,454 +176,444 @@ else if (p != null) * color used in theme definitions which means to use the color of the style. * tx1 (Text Color 1) Semantic text color tx2 (Text Color 2) Semantic additional * text color - * + *

        * parent: many children: many - TODO: handle color transformation children * (alpha ...) - * - * */ class SchemeClr implements OOXMLElement { - private static final long serialVersionUID = 2127868578801669266L; - private String val; - private ColorTransform clrTransform; - private Theme theme; - - public SchemeClr(String val, ColorTransform clrTransform, Theme t) { - this.val = val; - this.clrTransform = clrTransform; - this.theme= t; - } - - public SchemeClr(SchemeClr sc) { - this.val = sc.val; - this.clrTransform = sc.clrTransform; - this.theme= sc.theme; - } - - public static SchemeClr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String val = null; - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("schemeClr")) { - val = xpp.getAttributeValue(0); - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("schemeClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("schemeClr.parseOOXML: " + e.toString()); - } - SchemeClr sc = new SchemeClr(val, clrTransform, bk.getWorkBook().getTheme()); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SchemeClr(this); - } - - public int getColor() { - double tint = (clrTransform == null ? 0 : clrTransform.getTint()); - Object[] o = Color.parseThemeColor(val, tint, (short) 0, this.theme); - return (Integer) o[0]; - } + private static final long serialVersionUID = 2127868578801669266L; + private String val; + private ColorTransform clrTransform; + private Theme theme; + + public SchemeClr(String val, ColorTransform clrTransform, Theme t) { + this.val = val; + this.clrTransform = clrTransform; + this.theme = t; + } + + public SchemeClr(SchemeClr sc) { + this.val = sc.val; + this.clrTransform = sc.clrTransform; + this.theme = sc.theme; + } + + public static SchemeClr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + String val = null; + ColorTransform clrTransform = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("schemeClr")) { + val = xpp.getAttributeValue(0); + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("schemeClr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("schemeClr.parseOOXML: " + e.toString()); + } + SchemeClr sc = new SchemeClr(val, clrTransform, bk.getWorkBook().getTheme()); + return sc; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (clrTransform != null) + ooxml.append(clrTransform.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new SchemeClr(this); + } + + public int getColor() { + double tint = (clrTransform == null ? 0 : clrTransform.getTint()); + Object[] o = Color.parseThemeColor(val, tint, (short) 0, this.theme); + return (Integer) o[0]; + } } /** * sysClr (System Color) This element specifies a color bound to predefined * operating system elements. // TODO: appropriate to hard-code??? - * + *

        * parent: many children: COLORSTRANSFORM - * - * */ class SysClr implements OOXMLElement { - private static final long serialVersionUID = 8307422721346337409L; - private String val; // This simple type specifies a system color - // value. This color is based upon the value - // that this color currently has within the - // system on which the document is being viewed. - private String lastClr; // Specifies the color value that was last - // computed by the generating application. - // Applications shall use the lastClr - // attribute to determine the absolute value - // of the last color used if system colors - // are not supported. - private ColorTransform clrTransform; - - public SysClr(String val, String lastClr, ColorTransform clrTransform) { - this.val = val; - this.lastClr = lastClr; - this.clrTransform = clrTransform; - } - - public SysClr(SysClr sc) { - this.val = sc.val; - this.lastClr = sc.lastClr; - this.clrTransform = sc.clrTransform; - } - - public static SysClr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String val = null; - String lastClr = null; - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sysClr")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("val")) { // - val = xpp.getAttributeValue(i); - } else if (nm.equals("lastClr")) { - lastClr = xpp.getAttributeValue(i); - } - } - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("sysClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("sysClr.parseOOXML: " + e.toString()); - } - SysClr sc = new SysClr(val, lastClr, clrTransform); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - // TODO: Handle child elements - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SysClr(this); - } - - /** - * return the color int that represents this system color - * - * @return - */ - public int getColor() { - for (int i = 0; i < OOXMLConstants.systemColors.length; i++) { - if (OOXMLConstants.systemColors[i][0].equals(val)) { - return FormatHandle.HexStringToColorInt( - OOXMLConstants.systemColors[i][1], (short) 0); - } - } - return -1; - } + private static final long serialVersionUID = 8307422721346337409L; + private String val; // This simple type specifies a system color + // value. This color is based upon the value + // that this color currently has within the + // system on which the document is being viewed. + private String lastClr; // Specifies the color value that was last + // computed by the generating application. + // Applications shall use the lastClr + // attribute to determine the absolute value + // of the last color used if system colors + // are not supported. + private ColorTransform clrTransform; + + public SysClr(String val, String lastClr, ColorTransform clrTransform) { + this.val = val; + this.lastClr = lastClr; + this.clrTransform = clrTransform; + } + + public SysClr(SysClr sc) { + this.val = sc.val; + this.lastClr = sc.lastClr; + this.clrTransform = sc.clrTransform; + } + + public static SysClr parseOOXML(XmlPullParser xpp, Stack lastTag) { + String val = null; + String lastClr = null; + ColorTransform clrTransform = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("sysClr")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("val")) { // + val = xpp.getAttributeValue(i); + } else if (nm.equals("lastClr")) { + lastClr = xpp.getAttributeValue(i); + } + } + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("sysClr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("sysClr.parseOOXML: " + e.toString()); + } + SysClr sc = new SysClr(val, lastClr, clrTransform); + return sc; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (clrTransform != null) + ooxml.append(clrTransform.getOOXML()); + ooxml.append(""); + // TODO: Handle child elements + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new SysClr(this); + } + + /** + * return the color int that represents this system color + * + * @return + */ + public int getColor() { + for (int i = 0; i < OOXMLConstants.systemColors.length; i++) { + if (OOXMLConstants.systemColors[i][0].equals(val)) { + return FormatHandle.HexStringToColorInt( + OOXMLConstants.systemColors[i][1], (short) 0); + } + } + return -1; + } } /** * srgbClr (RGB Color Model - Hex Variant) - * + *

        * This element specifies a color using the red, green, blue RGB color model. * Red, green, and blue is expressed as sequence of hex digits, RRGGBB. A * perceptual gamma of 2.2 is used. Specifies the level of red as expressed by a * percentage offset increase or decrease relative to the input color. - * + *

        * parent: many children: COLORSTRANSFORM - * - * */ class SrgbClr implements OOXMLElement { - - private static final long serialVersionUID = -999813417659560045L; - private String val; - private ColorTransform clrTransform; - - public SrgbClr(String val, ColorTransform clrTransform) { - this.val = val; - this.clrTransform = clrTransform; - } - - public SrgbClr(SrgbClr sc) { - this.val = sc.val; - this.clrTransform = sc.clrTransform; - } - - public static SrgbClr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String val = null; - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("srgbClr")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("val")) { - val = xpp.getAttributeValue(i); - } - } - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("srgbClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("srgbClr.parseOOXML: " + e.toString()); - } - SrgbClr sc = new SrgbClr(val, clrTransform); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SrgbClr(this); - } - - /** - * interpret val and return color int from color table - * - * @return - */ - public int getColor() { - return FormatHandle.HexStringToColorInt(val, (short) 0); - } + + private static final long serialVersionUID = -999813417659560045L; + private String val; + private ColorTransform clrTransform; + + public SrgbClr(String val, ColorTransform clrTransform) { + this.val = val; + this.clrTransform = clrTransform; + } + + public SrgbClr(SrgbClr sc) { + this.val = sc.val; + this.clrTransform = sc.clrTransform; + } + + public static SrgbClr parseOOXML(XmlPullParser xpp, Stack lastTag) { + String val = null; + ColorTransform clrTransform = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("srgbClr")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("val")) { + val = xpp.getAttributeValue(i); + } + } + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("srgbClr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("srgbClr.parseOOXML: " + e.toString()); + } + SrgbClr sc = new SrgbClr(val, clrTransform); + return sc; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (clrTransform != null) + ooxml.append(clrTransform.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new SrgbClr(this); + } + + /** + * interpret val and return color int from color table + * + * @return + */ + public int getColor() { + return FormatHandle.HexStringToColorInt(val, (short) 0); + } } /** * scrgbClr (Scheme Color) - * + *

        * This element specifies a color using the red, green, blue RGB color model. * Each component, red, green, and blue is expressed as a percentage from 0% to * 100%. A linear gamma of 1.0 is assumed. - * + *

        * parent: many children: COLORSTRANSFORM - * - * */ class ScrgbClr implements OOXMLElement { - private static final long serialVersionUID = -8782954669829478560L; - private HashMap attrs; - private ColorTransform clrTransform; - - public ScrgbClr(HashMap attrs, ColorTransform clrTransform) { - this.attrs = attrs; - this.clrTransform = clrTransform; - } - - public ScrgbClr(ScrgbClr sc) { - this.attrs = sc.attrs; - this.clrTransform = sc.clrTransform; - } - - public static ScrgbClr parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("scrgbClr")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), - xpp.getAttributeValue(i)); // r, g, b - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("scrgbClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("scrgbClr.parseOOXML: " + e.toString()); - } - ScrgbClr sc = new ScrgbClr(attrs, clrTransform); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ScrgbClr(this); - } - - /** - * interpret the rbg value into a color int/index into color table - * - * @return - */ - public int getColor() { - // r, g, b in percentages (in 1000th of a percentage) - double rval = Integer.valueOf(attrs.get("r")) / 100000; // perecentage - rval *= 255; - double gval = Integer.valueOf(attrs.get("g")) / 100000; - gval *= 255; - double bval = Integer.valueOf(attrs.get("b")) / 100000; - bval *= 255; - if (clrTransform != null) - Logger.logWarn("Scheme Color must process color transforms"); - java.awt.Color c = new java.awt.Color((int) rval, (int) gval, - (int) bval); - return FormatHandle.getColorInt(c); - } + private static final long serialVersionUID = -8782954669829478560L; + private HashMap attrs; + private ColorTransform clrTransform; + + public ScrgbClr(HashMap attrs, ColorTransform clrTransform) { + this.attrs = attrs; + this.clrTransform = clrTransform; + } + + public ScrgbClr(ScrgbClr sc) { + this.attrs = sc.attrs; + this.clrTransform = sc.clrTransform; + } + + public static ScrgbClr parseOOXML(XmlPullParser xpp, Stack lastTag) { + HashMap attrs = new HashMap(); + ColorTransform clrTransform = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("scrgbClr")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) + attrs.put(xpp.getAttributeName(i), + xpp.getAttributeValue(i)); // r, g, b + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("scrgbClr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("scrgbClr.parseOOXML: " + e.toString()); + } + ScrgbClr sc = new ScrgbClr(attrs, clrTransform); + return sc; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (clrTransform != null) + ooxml.append(clrTransform.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new ScrgbClr(this); + } + + /** + * interpret the rbg value into a color int/index into color table + * + * @return + */ + public int getColor() { + // r, g, b in percentages (in 1000th of a percentage) + double rval = Integer.valueOf(attrs.get("r")) / 100000; // perecentage + rval *= 255; + double gval = Integer.valueOf(attrs.get("g")) / 100000; + gval *= 255; + double bval = Integer.valueOf(attrs.get("b")) / 100000; + bval *= 255; + if (clrTransform != null) + Logger.logWarn("Scheme Color must process color transforms"); + java.awt.Color c = new java.awt.Color((int) rval, (int) gval, + (int) bval); + return FormatHandle.getColorInt(c); + } } /** * prstClr (Preset Color) This element specifies a color which is bound to one * of a predefined collection of colors. - * + *

        * parent: many children: many - * - * */ class PrstClr implements OOXMLElement { - - private static final long serialVersionUID = -5773022185972396279L; - private String val; - private ColorTransform clrTransform; - - public PrstClr(String val, ColorTransform clrTransform) { - this.val = val; - this.clrTransform = clrTransform; - } - - public PrstClr(PrstClr sc) { - this.val = sc.val; - this.clrTransform = sc.clrTransform; - } - - public static PrstClr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String val = null; - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstClr")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("val")) { - val = xpp.getAttributeValue(i); - } - } - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("prstClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("prstClr.parseOOXML: " + e.toString()); - } - PrstClr sc = new PrstClr(val, clrTransform); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PrstClr(this); - } - - public int getColor() { - if (clrTransform != null) - Logger.logWarn("Preset Color must process color transforms"); - for (int i = 0; i < FormatHandle.COLORNAMES.length; i++) { - if (FormatHandle.COLORNAMES[i].equalsIgnoreCase(val)) - return i; - } - return -1; - } + + private static final long serialVersionUID = -5773022185972396279L; + private String val; + private ColorTransform clrTransform; + + public PrstClr(String val, ColorTransform clrTransform) { + this.val = val; + this.clrTransform = clrTransform; + } + + public PrstClr(PrstClr sc) { + this.val = sc.val; + this.clrTransform = sc.clrTransform; + } + + public static PrstClr parseOOXML(XmlPullParser xpp, Stack lastTag) { + String val = null; + ColorTransform clrTransform = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("prstClr")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("val")) { + val = xpp.getAttributeValue(i); + } + } + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("prstClr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("prstClr.parseOOXML: " + e.toString()); + } + PrstClr sc = new PrstClr(val, clrTransform); + return sc; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (clrTransform != null) + ooxml.append(clrTransform.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new PrstClr(this); + } + + public int getColor() { + if (clrTransform != null) + Logger.logWarn("Preset Color must process color transforms"); + for (int i = 0; i < FormatHandle.COLORNAMES.length; i++) { + if (FormatHandle.COLORNAMES[i].equalsIgnoreCase(val)) + return i; + } + return -1; + } } /** @@ -635,203 +621,201 @@ public int getColor() { * hslColor, presetColor, sRgbColor, scRgbColor color adjustments are in * percentage units
        * // TODO: Finish // comp // inv // gamma // invGamma // gray, red, green, blue - * - * */ class ColorTransform { - private int[] lum; - private int[] hue; - private int[] sat; - private int[] alpha; - private int tint; - private int shade; - - // TODO: Finish - // comp - // inv - // gamma - // gray, red, green, blue - - public int getTint() { - return tint; - } - - public ColorTransform(int[] lum, int[] hue, int[] sat, int[] alpha, - int tint, int shade) { - this.lum = lum; - this.hue = hue; - this.sat = sat; - this.alpha = alpha; - this.tint = tint; - this.shade = shade; - } - - /** - * parse color transform elements, common children of color-type elements:
        - * schemeColor, systemColor, hslColor, presetColor, sRgbColor, scRgbColor - * - * @param xpp - * @param lastTag - * @return - */ - public static ColorTransform parseOOXML(XmlPullParser xpp, Stack lastTag) { - int[] lum = null; - int[] hue = null; - int[] sat = null; - int[] alpha = null; - int tint = 0; - int shade = 0; - try { - String parentEl = lastTag.peek(); - - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("lum")) { // This element specifies the input - // color with its luminance - // modulated by the given - // percentage. - if (lum == null) - lum = new int[3]; - lum[0] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("lumMod")) { // This element specifies - // the input color with - // its luminance - // modulated by the - // given percentage. - if (lum == null) - lum = new int[3]; - lum[1] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("lumOff")) { // This element specifies - // the input color with - // its luminance - // shifted, but with its - // hue and saturation - // unchanged. - if (lum == null) - lum = new int[3]; - lum[2] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("hue")) { // This element specifies - // the input color with the - // specified hue, but with - // its saturation and - // luminance unchanged. - if (hue == null) - hue = new int[3]; - hue[0] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("hueMod")) { - if (hue == null) - hue = new int[3]; - hue[1] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("hueOff")) { - if (hue == null) - hue = new int[3]; - hue[2] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("sat")) { // This element specifies - // the input color with the - // specified saturation, but - // with its hue and - // luminance unchanged. - if (sat == null) - sat = new int[3]; - sat[0] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("satMod")) { - if (sat == null) - sat = new int[3]; - sat[1] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("satOff")) { - if (sat == null) - sat = new int[3]; - sat[2] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("shade")) { // This element specifies - // a darker version of - // its input color - shade = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("tint")) { - tint = Integer.valueOf(xpp.getAttributeValue(0)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals(parentEl)) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ColorTransform.parseOOXML: " + e.toString()); - } - return new ColorTransform(lum, hue, sat, alpha, tint, shade); - } - - /** - * returns the OOXML associated with color transforms of a parent color - * element
        - * note that these color transforms must be part of either
        - * schemeColor, systemColor, hslColor, presetColor, sRgbColor, scRgbColor - * - * @return - */ - public StringBuffer getOOXML() { - StringBuffer ooxml = new StringBuffer(); - - /** - * a:tint Tint a:shade Shade a:comp Complement a:inv Inverse a:gray Gray - * a:alpha Alpha a:alphaOff Alpha Offset a:alphaMod Alpha Modulation - * a:hue Hue a:hueOff Hue Offset a:hueMod Hue Modulate a:sat Saturation - * a:satOff Saturation Offset a:satMod Saturation Modulation a:lum - * Luminance a:lumOff Luminance Offset a:lumMod Luminance Modulation - * a:red Red a:redOff Red Offset a:redMod Red Modulation a:green Green - * a:greenOff Green Offset a:greenMod Green Modification a:blue Blue - * a:blueOff Blue Offset a:blueMod Blue Modification a:gamma Gamma - * a:invGamma Inverse Gamma - */ - if (tint != 0) - ooxml.append(""); - if (shade != 0) - ooxml.append(""); - // Complement - // Inverse - // Gray - if (alpha != null) { - if (alpha[0] != 0) - ooxml.append(""); - if (alpha[2] != 0) - ooxml.append(""); - if (alpha[1] != 0) - ooxml.append(""); - } - if (hue != null) { - if (hue[0] != 0) - ooxml.append(""); - if (hue[2] != 0) - ooxml.append(""); - if (hue[1] != 0) - ooxml.append(""); - } - if (sat != null) { - if (sat[0] != 0) - ooxml.append(""); - if (sat[2] != 0) - ooxml.append(""); - if (sat[1] != 0) - ooxml.append(""); - } - if (lum != null) { - if (lum[0] != 0) - ooxml.append(""); - if (lum[2] != 0) - ooxml.append(""); - if (lum[1] != 0) - ooxml.append(""); - } - // red, - // green - // blue - // gamma - // invGamma - return ooxml; - } + private int[] lum; + private int[] hue; + private int[] sat; + private int[] alpha; + private int tint; + private int shade; + + // TODO: Finish + // comp + // inv + // gamma + // gray, red, green, blue + + public int getTint() { + return tint; + } + + public ColorTransform(int[] lum, int[] hue, int[] sat, int[] alpha, + int tint, int shade) { + this.lum = lum; + this.hue = hue; + this.sat = sat; + this.alpha = alpha; + this.tint = tint; + this.shade = shade; + } + + /** + * parse color transform elements, common children of color-type elements:
        + * schemeColor, systemColor, hslColor, presetColor, sRgbColor, scRgbColor + * + * @param xpp + * @param lastTag + * @return + */ + public static ColorTransform parseOOXML(XmlPullParser xpp, Stack lastTag) { + int[] lum = null; + int[] hue = null; + int[] sat = null; + int[] alpha = null; + int tint = 0; + int shade = 0; + try { + String parentEl = lastTag.peek(); + + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("lum")) { // This element specifies the input + // color with its luminance + // modulated by the given + // percentage. + if (lum == null) + lum = new int[3]; + lum[0] = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("lumMod")) { // This element specifies + // the input color with + // its luminance + // modulated by the + // given percentage. + if (lum == null) + lum = new int[3]; + lum[1] = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("lumOff")) { // This element specifies + // the input color with + // its luminance + // shifted, but with its + // hue and saturation + // unchanged. + if (lum == null) + lum = new int[3]; + lum[2] = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("hue")) { // This element specifies + // the input color with the + // specified hue, but with + // its saturation and + // luminance unchanged. + if (hue == null) + hue = new int[3]; + hue[0] = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("hueMod")) { + if (hue == null) + hue = new int[3]; + hue[1] = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("hueOff")) { + if (hue == null) + hue = new int[3]; + hue[2] = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("sat")) { // This element specifies + // the input color with the + // specified saturation, but + // with its hue and + // luminance unchanged. + if (sat == null) + sat = new int[3]; + sat[0] = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("satMod")) { + if (sat == null) + sat = new int[3]; + sat[1] = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("satOff")) { + if (sat == null) + sat = new int[3]; + sat[2] = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("shade")) { // This element specifies + // a darker version of + // its input color + shade = Integer.valueOf(xpp.getAttributeValue(0)); + } else if (tnm.equals("tint")) { + tint = Integer.valueOf(xpp.getAttributeValue(0)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals(parentEl)) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("ColorTransform.parseOOXML: " + e.toString()); + } + return new ColorTransform(lum, hue, sat, alpha, tint, shade); + } + + /** + * returns the OOXML associated with color transforms of a parent color + * element
        + * note that these color transforms must be part of either
        + * schemeColor, systemColor, hslColor, presetColor, sRgbColor, scRgbColor + * + * @return + */ + public StringBuffer getOOXML() { + StringBuffer ooxml = new StringBuffer(); + + /** + * a:tint Tint a:shade Shade a:comp Complement a:inv Inverse a:gray Gray + * a:alpha Alpha a:alphaOff Alpha Offset a:alphaMod Alpha Modulation + * a:hue Hue a:hueOff Hue Offset a:hueMod Hue Modulate a:sat Saturation + * a:satOff Saturation Offset a:satMod Saturation Modulation a:lum + * Luminance a:lumOff Luminance Offset a:lumMod Luminance Modulation + * a:red Red a:redOff Red Offset a:redMod Red Modulation a:green Green + * a:greenOff Green Offset a:greenMod Green Modification a:blue Blue + * a:blueOff Blue Offset a:blueMod Blue Modification a:gamma Gamma + * a:invGamma Inverse Gamma + */ + if (tint != 0) + ooxml.append(""); + if (shade != 0) + ooxml.append(""); + // Complement + // Inverse + // Gray + if (alpha != null) { + if (alpha[0] != 0) + ooxml.append(""); + if (alpha[2] != 0) + ooxml.append(""); + if (alpha[1] != 0) + ooxml.append(""); + } + if (hue != null) { + if (hue[0] != 0) + ooxml.append(""); + if (hue[2] != 0) + ooxml.append(""); + if (hue[1] != 0) + ooxml.append(""); + } + if (sat != null) { + if (sat[0] != 0) + ooxml.append(""); + if (sat[2] != 0) + ooxml.append(""); + if (sat[1] != 0) + ooxml.append(""); + } + if (lum != null) { + if (lum[0] != 0) + ooxml.append(""); + if (lum[2] != 0) + ooxml.append(""); + if (lum[1] != 0) + ooxml.append(""); + } + // red, + // green + // blue + // gamma + // invGamma + return ooxml; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/CxnSp.java b/src/main/java/io/starter/formats/OOXML/CxnSp.java index 897612e..f77c230 100644 --- a/src/main/java/io/starter/formats/OOXML/CxnSp.java +++ b/src/main/java/io/starter/formats/OOXML/CxnSp.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,346 +22,366 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import java.util.HashMap; -import java.util.Iterator; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Stack; /** - * * parents: absoluteAnchor, oneCellAnchor, twoCellAnchor * children: nvCxnSpPr, spPr, style - * - * */ //TODO: finish nvCxnSpPr.cNvCxnSpPr element -public class CxnSp implements OOXMLElement{ - - private static final long serialVersionUID = -8492664135843926551L; - private HashMap attrs= new HashMap(); +public class CxnSp implements OOXMLElement { + + private static final long serialVersionUID = -8492664135843926551L; + private HashMap attrs = new HashMap(); private NvCxnSpPr nvc; private SpPr spPr; private Style style; - public CxnSp(HashMap attrs, NvCxnSpPr nvc, SpPr sp, Style s) { - this.attrs= attrs; - this.nvc= nvc; - this.spPr= sp; - this.style= s; - } - public CxnSp(CxnSp c) { - this.attrs= c.attrs; - this.nvc= c.nvc; - this.spPr= c.spPr; - this.style= c.style; - } - - + public CxnSp(HashMap attrs, NvCxnSpPr nvc, SpPr sp, Style s) { + this.attrs = attrs; + this.nvc = nvc; + this.spPr = sp; + this.style = s; + } + + public CxnSp(CxnSp c) { + this.attrs = c.attrs; + this.nvc = c.nvc; + this.spPr = c.spPr; + this.style = c.style; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs= new HashMap(); - NvCxnSpPr nvc= null; - SpPr sp= null; - Style s= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cxnSp")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("nvCxnSpPr")) { - lastTag.push(tnm); - nvc= (NvCxnSpPr) NvCxnSpPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp= (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); + HashMap attrs = new HashMap(); + NvCxnSpPr nvc = null; + SpPr sp = null; + Style s = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cxnSp")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("nvCxnSpPr")) { + lastTag.push(tnm); + nvc = NvCxnSpPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("spPr")) { + lastTag.push(tnm); + sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); // sp.setNS("xdr"); - } else if (tnm.equals("style")) { - lastTag.push(tnm); - s= (Style) Style.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("cxnSp")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cxnSp.parseOOXML: " + e.toString()); - } - CxnSp c= new CxnSp(attrs, nvc, sp, s); - return c; + } else if (tnm.equals("style")) { + lastTag.push(tnm); + s = (Style) Style.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("cxnSp")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("cxnSp.parseOOXML: " + e.toString()); + } + CxnSp c = new CxnSp(attrs, nvc, sp, s); + return c; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (nvc != null) ooxml.append(nvc.getOOXML()); + if (spPr != null) ooxml.append(spPr.getOOXML()); + if (style != null) ooxml.append(style.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CxnSp(this); + } + + /** + * get cNvPr name attribute + * + * @return + */ + public String getName() { + if (nvc != null) + return nvc.getName(); + return null; + } + + /** + * set cNvPr name attribute + * + * @param name + */ + public void setName(String name) { + if (nvc != null) + nvc.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (nvc != null) + return nvc.getDescr(); + return null; + } + + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setDescr(String descr) { + if (nvc != null) + nvc.setDescr(descr); + } + + /** + * get Macro attribute + */ + public String getMacro() { + if (attrs.get("macro") != null) + return attrs.get("macro"); + return null; + } + + /** + * set Macro attribute + * + * @param macro + */ + public void setMacro(String macro) { + attrs.put("macro", macro); + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (nvc != null) + nvc.setId(id); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (nvc != null) + return nvc.getId(); + return -1; + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (spPr != null) + return spPr.getEmbed(); + return null; + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (spPr != null) + return spPr.getLink(); + return null; + } + + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + public void setEmbed(String embed) { + if (spPr != null) spPr.setEmbed(embed); + } + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + public void setLink(String link) { + if (spPr != null) spPr.setLink(link); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (nvc!=null) ooxml.append(nvc.getOOXML()); - if (spPr!=null) ooxml.append(spPr.getOOXML()); - if (style!=null) ooxml.append(style.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CxnSp(this); - } - /** - * get cNvPr name attribute - * @return - */ - public String getName() { - if (nvc!=null) - return nvc.getName(); - return null; - } - - /** - * set cNvPr name attribute - * @param name - */ - public void setName(String name) { - if (nvc!=null) - nvc.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (nvc!=null) - return nvc.getDescr(); - return null; - } - /** - * set cNvPr description attribute - * sometimes associated with shape name - * @param descr - */ - public void setDescr(String descr) { - if (nvc!=null) - nvc.setDescr(descr); - } - - /** - * get Macro attribute - */ - public String getMacro() { - if (attrs.get("macro")!=null) - return (String) attrs.get("macro"); - return null; - } - - /** - * set Macro attribute - * @param macro - */ - public void setMacro(String macro) { - attrs.put("macro", macro); - } - - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (nvc!=null) - nvc.setId(id); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (nvc!=null) - return nvc.getId(); - return -1; - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * @return - */ - public String getEmbed() { - if (spPr!=null) - return spPr.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * @return - */ - public String getLink() { - if (spPr!=null) - return spPr.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * @param embed - */ - public void setEmbed(String embed) { - if (spPr!=null) spPr.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * @param embed - */ - public void setLink(String link) { - if (spPr!=null) spPr.setLink(link); - } } /** * nvCxnSpPr (Non-Visual Properties for a Connection Shape) * This element specifies all non-visual properties for a connection shape. This element is a container for the non15 * visual identification properties, shape properties and application properties that are to be associated with a - * DrawingML Reference Material - DrawingML - SpreadsheetML Drawing connection shape. + * DrawingML Reference Material - DrawingML - SpreadsheetML Drawing connection shape. * This allows for additional information that does not affect 1 the appearance of the connection * shape to be stored. - * + *

        * parent: cxnSp - * children: cNvPr, cNvCxnSpPr - * + * children: cNvPr, cNvCxnSpPr */ // TODO: finish cNvCxnSpPr -class NvCxnSpPr implements OOXMLElement{ - - private static final long serialVersionUID = -4808617992996239153L; - private CNvPr cpr; +class NvCxnSpPr implements OOXMLElement { + + private static final long serialVersionUID = -4808617992996239153L; + private CNvPr cpr; // private cNvCxnSpPr sppr= null; - - public NvCxnSpPr(CNvPr cpr/*, cNvCxnSpPr sppr*/) { - this.cpr= cpr; - //this.sppr= sppr; - } - public NvCxnSpPr(NvCxnSpPr n) { - this.cpr= n.cpr; - //this.sppr= n.sppr; - } - - + + public NvCxnSpPr(CNvPr cpr/*, cNvCxnSpPr sppr*/) { + this.cpr = cpr; + //this.sppr= sppr; + } + + public NvCxnSpPr(NvCxnSpPr n) { + this.cpr = n.cpr; + //this.sppr= n.sppr; + } + + public static NvCxnSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cpr= null;; -// cNvCxnSpPr sppr= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cpr= (CNvPr) CNvPr.parseOOXML(xpp, lastTag); + CNvPr cpr = null; + // cNvCxnSpPr sppr= null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cNvPr")) { + lastTag.push(tnm); + cpr = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); /* } else if (tnm.equals("cNvCxnSpPr")) { lastTag.push(tnm); sppr= (cNvCxnSpPr) cNvCxnSpPr.parseOOXML(xpp, lastTag).clone(); -*/ } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("nvCxnSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("nvCxnSpPr.parseOOXML: " + e.toString()); - } - NvCxnSpPr n= new NvCxnSpPr(cpr/*, sppr*/); - return n; +*/ + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("nvCxnSpPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("nvCxnSpPr.parseOOXML: " + e.toString()); + } + NvCxnSpPr n = new NvCxnSpPr(cpr/*, sppr*/); + return n; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (cpr != null) ooxml.append(cpr.getOOXML()); + // TODO: finihs cNvCxnSpPr + ooxml.append(""); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new NvCxnSpPr(this); + } + + /** + * get cNvPr name attribute + * + * @return + */ + public String getName() { + if (cpr != null) + return cpr.getName(); + return null; + } + + /** + * set cNvPr name attribute + * + * @param name + */ + public void setName(String name) { + if (cpr != null) + cpr.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (cpr != null) + return cpr.getDescr(); + return null; + } + + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setDescr(String descr) { + if (cpr != null) + cpr.setDescr(descr); + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (cpr != null) + cpr.setId(id); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (cpr != null) + return cpr.getId(); + return -1; } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (cpr!=null) ooxml.append(cpr.getOOXML()); - // TODO: finihs cNvCxnSpPr - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NvCxnSpPr(this); - } - /** - * get cNvPr name attribute - * @return - */ - public String getName() { - if (cpr!=null) - return cpr.getName(); - return null; - } - - /** - * set cNvPr name attribute - * @param name - */ - public void setName(String name) { - if (cpr!=null) - cpr.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (cpr!=null) - return cpr.getDescr(); - return null; - } - /** - * set cNvPr description attribute - * sometimes associated with shape name - * @param descr - */ - public void setDescr(String descr) { - if (cpr!=null) - cpr.setDescr(descr); - } - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (cpr!=null) - cpr.setId(id); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (cpr!=null) - return cpr.getId(); - return -1; - } } diff --git a/src/main/java/io/starter/formats/OOXML/DLbls.java b/src/main/java/io/starter/formats/OOXML/DLbls.java index 10f63d6..53f39e2 100644 --- a/src/main/java/io/starter/formats/OOXML/DLbls.java +++ b/src/main/java/io/starter/formats/OOXML/DLbls.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,194 +22,217 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * dLbls (Data Labels) * This element serves as a root element that specifies the settings for the data labels for an entire series or the * entire chart. It contains child elements that specify the specific formatting and positioning settings. - * - * parent: chart types, ser - * children: dLbl (0 or more times), GROUPDLBLS (numFmt, spPr, txPr, dLblPos, showLegendKey, showVal, showCatName, showSerName,showPercent, showBubbleSize, separator, showLeaderLines - * - * + *

        + * parent: chart types, ser + * children: dLbl (0 or more times), GROUPDLBLS (numFmt, spPr, txPr, dLblPos, showLegendKey, showVal, showCatName, showSerName,showPercent, showBubbleSize, separator, showLeaderLines */ // TODO: Finish All Children!!!! leaderLines, numFmt, separator -public class DLbls implements OOXMLElement{ - - private static final long serialVersionUID = -3765320144606034211L; - private SpPr sp= null; - private TxPr tx= null; - // all of these are child elements with one attribute=val, all default to "1" (true) - private int showVal= -1; - private int showLeaderLines= -1; - private int showLegendKey= -1; - private int showCatName= -1; - private int showSerName= -1; - private int showPercent= -1; - private int showBubbleSize= -1; - - public DLbls(int showVal, int showLeaderLines, int showLegendKey, int showCatName, - int showSerName, int showPercent, int showBubbleSize, SpPr sp, TxPr tx) { - this.showVal= showVal; - this.showLeaderLines= showLeaderLines; - this.showLegendKey= showLegendKey; - this.showCatName= showCatName; - this.showSerName= showSerName; - this.showPercent= showPercent; - this.showBubbleSize= showBubbleSize; - this.sp= sp; - this.tx= tx; - } - public DLbls(boolean showVal, boolean showLeaderLines, boolean showLegendKey, boolean showCatName, - boolean showSerName, boolean showPercent, boolean showBubbleSize, SpPr sp, TxPr tx) { - if (showVal) this.showVal= 1; - if (showLeaderLines) this.showLeaderLines= 1; - if (showLegendKey) this.showLegendKey= 1; - if (showCatName) this.showCatName= 1; - if (showSerName) this.showSerName= 1; - if (showPercent) this.showPercent= 1; - if (showBubbleSize) this.showBubbleSize= 1; - this.sp= sp; - this.tx= tx; - } - public DLbls(DLbls d) { - this.showVal= d.showVal; - this.showLeaderLines= d.showLeaderLines; - this.showLegendKey= d.showLegendKey; - this.showCatName= d.showCatName; - this.showSerName= d.showSerName; - this.showPercent= d.showPercent; - this.showBubbleSize= d.showBubbleSize; - this.sp= d.sp; - this.tx= d.tx; - } - - +public class DLbls implements OOXMLElement { + + private static final long serialVersionUID = -3765320144606034211L; + private SpPr sp = null; + private TxPr tx = null; + // all of these are child elements with one attribute=val, all default to "1" (true) + private int showVal = -1; + private int showLeaderLines = -1; + private int showLegendKey = -1; + private int showCatName = -1; + private int showSerName = -1; + private int showPercent = -1; + private int showBubbleSize = -1; + + public DLbls(int showVal, int showLeaderLines, int showLegendKey, int showCatName, + int showSerName, int showPercent, int showBubbleSize, SpPr sp, TxPr tx) { + this.showVal = showVal; + this.showLeaderLines = showLeaderLines; + this.showLegendKey = showLegendKey; + this.showCatName = showCatName; + this.showSerName = showSerName; + this.showPercent = showPercent; + this.showBubbleSize = showBubbleSize; + this.sp = sp; + this.tx = tx; + } + + public DLbls(boolean showVal, boolean showLeaderLines, boolean showLegendKey, boolean showCatName, + boolean showSerName, boolean showPercent, boolean showBubbleSize, SpPr sp, TxPr tx) { + if (showVal) this.showVal = 1; + if (showLeaderLines) this.showLeaderLines = 1; + if (showLegendKey) this.showLegendKey = 1; + if (showCatName) this.showCatName = 1; + if (showSerName) this.showSerName = 1; + if (showPercent) this.showPercent = 1; + if (showBubbleSize) this.showBubbleSize = 1; + this.sp = sp; + this.tx = tx; + } + + public DLbls(DLbls d) { + this.showVal = d.showVal; + this.showLeaderLines = d.showLeaderLines; + this.showLegendKey = d.showLegendKey; + this.showCatName = d.showCatName; + this.showSerName = d.showSerName; + this.showPercent = d.showPercent; + this.showBubbleSize = d.showBubbleSize; + this.sp = d.sp; + this.tx = d.tx; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - SpPr sp= null; - TxPr tx= null; - int showVal= -1; - int showLeaderLines= -1; - int showLegendKey= -1; - int showCatName= -1; - int showSerName= -1; - int showPercent= -1; - int showBubbleSize= -1; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp= (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); + SpPr sp = null; + TxPr tx = null; + int showVal = -1; + int showLeaderLines = -1; + int showLegendKey = -1; + int showCatName = -1; + int showSerName = -1; + int showPercent = -1; + int showBubbleSize = -1; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("spPr")) { + lastTag.push(tnm); + sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); // sp.setNS("c"); - } else if (tnm.equals("txPr")) { - lastTag.push(tnm); - tx= (TxPr) TxPr.parseOOXML(xpp, lastTag, bk).cloneElement(); - } else if (tnm.equals("showVal")) { - if (xpp.getAttributeCount()>0) - showVal=Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showLeaderLines")) { - if (xpp.getAttributeCount()>0) - showLeaderLines=Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showLegendKey")) { - if (xpp.getAttributeCount()>0) - showLegendKey=Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showCatName")) { - if (xpp.getAttributeCount()>0) - showCatName=Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showSerName")) { - if (xpp.getAttributeCount()>0) - showSerName=Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showPercent")) { - if (xpp.getAttributeCount()>0) - showPercent=Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showBubbleSize")) { - if (xpp.getAttributeCount()>0) - showBubbleSize=Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("dLbls")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("dLbls.parseOOXML: " + e.toString()); - } - DLbls d= new DLbls(showVal, showLeaderLines, showLegendKey, showCatName, showSerName, - showPercent, showBubbleSize, sp, tx); - return d; + } else if (tnm.equals("txPr")) { + lastTag.push(tnm); + tx = (TxPr) TxPr.parseOOXML(xpp, lastTag, bk).cloneElement(); + } else if (tnm.equals("showVal")) { + if (xpp.getAttributeCount() > 0) + showVal = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("showLeaderLines")) { + if (xpp.getAttributeCount() > 0) + showLeaderLines = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("showLegendKey")) { + if (xpp.getAttributeCount() > 0) + showLegendKey = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("showCatName")) { + if (xpp.getAttributeCount() > 0) + showCatName = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("showSerName")) { + if (xpp.getAttributeCount() > 0) + showSerName = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("showPercent")) { + if (xpp.getAttributeCount() > 0) + showPercent = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("showBubbleSize")) { + if (xpp.getAttributeCount() > 0) + showBubbleSize = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("dLbls")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("dLbls.parseOOXML: " + e.toString()); + } + DLbls d = new DLbls(showVal, showLeaderLines, showLegendKey, showCatName, showSerName, + showPercent, showBubbleSize, sp, tx); + return d; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + // TODO: numFmt + if (sp != null) ooxml.append(sp.getOOXML()); + if (tx != null) ooxml.append(tx.getOOXML()); + // TODO: dLblPos + if (showLegendKey != -1) ooxml.append(""); + if (showVal != -1) ooxml.append(""); + if (showCatName != -1) ooxml.append(""); + if (showSerName != -1) ooxml.append(""); + if (showPercent != -1) ooxml.append(""); + if (showBubbleSize != -1) ooxml.append(""); + // TODO: separator + if (showLeaderLines != -1) ooxml.append(""); + // TODO: leaderLines + ooxml.append(""); + return ooxml.toString(); + } + + /** + * generate the ooxml necessary to define the data labels for a chart + * Controls view of Series name, Category Name, Percents, Leader Lines, Bubble Sizes where applicable + * + * @return public String getOOXML(ChartFormat cf) { + * StringBuffer ooxml= new StringBuffer(); + * ooxml.append(""); ooxml.append("\r\n"); + * // TODO: c:numFmt, c:spPr, c:txPr + * if (cf.getChartOption("ShowBubbleSizes")=="1") + * ooxml.append(""); + * if (cf.getChartOption("ShowValueLabel")=="1") + * ooxml.append(""); + * if (cf.getChartOption("ShowLabel")=="1") + * ooxml.append(""); + * if (cf.getChartOption("ShowCatLabel")=="1") + * ooxml.append(""); + * // Pie specific + * if (cf.getChartOption("ShowLabelPct")=="1") + * ooxml.append(""); + * if (cf.getChartOption("ShowLdrLines")=="true") + * ooxml.append(""); + *

        + *

        + * ooxml.append(""); ooxml.append("\r\n"); + * return ooxml.toString(); + * } + */ + + public OOXMLElement cloneElement() { + return new DLbls(this); + } + + /** + * get methods + */ + public boolean showLegendKey() { + return showLegendKey == 1; + } + + public boolean showVal() { + return showVal == 1; + } + + public boolean showCatName() { + return showCatName == 1; + } + + public boolean showSerName() { + return showSerName == 1; + } + + public boolean showPercent() { + return showPercent == 1; + } + + public boolean showBubbleSize() { + return showBubbleSize == 1; + } + + public boolean showLeaderLines() { + return showLeaderLines == 1; } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - // TODO: numFmt - if (sp!=null) ooxml.append(sp.getOOXML()); - if (tx!=null) ooxml.append(tx.getOOXML()); - // TODO: dLblPos - if (showLegendKey!=-1) ooxml.append(""); - if (showVal!=-1) ooxml.append(""); - if (showCatName!=-1) ooxml.append(""); - if (showSerName!=-1) ooxml.append(""); - if (showPercent!=-1) ooxml.append(""); - if (showBubbleSize!=-1) ooxml.append(""); - // TODO: separator - if (showLeaderLines!=-1) ooxml.append(""); - // TODO: leaderLines - ooxml.append(""); - return ooxml.toString(); - } - /** - * generate the ooxml necessary to define the data labels for a chart - * Controls view of Series name, Category Name, Percents, Leader Lines, Bubble Sizes where applicable - * @return - * - public String getOOXML(ChartFormat cf) { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); ooxml.append("\r\n"); - // TODO: c:numFmt, c:spPr, c:txPr - if (cf.getChartOption("ShowBubbleSizes")=="1") - ooxml.append(""); - if (cf.getChartOption("ShowValueLabel")=="1") - ooxml.append(""); - if (cf.getChartOption("ShowLabel")=="1") - ooxml.append(""); - if (cf.getChartOption("ShowCatLabel")=="1") - ooxml.append(""); - // Pie specific - if (cf.getChartOption("ShowLabelPct")=="1") - ooxml.append(""); - if (cf.getChartOption("ShowLdrLines")=="true") - ooxml.append(""); - - - ooxml.append(""); ooxml.append("\r\n"); - return ooxml.toString(); - } -*/ - - public OOXMLElement cloneElement() { - return new DLbls(this); - } - /** get methods */ - public boolean showLegendKey() { return showLegendKey==1; } - public boolean showVal() { return showVal==1; } - public boolean showCatName() { return showCatName==1; } - public boolean showSerName() { return showSerName==1; } - public boolean showPercent() { return showPercent==1; } - public boolean showBubbleSize() { return showBubbleSize==1; } - public boolean showLeaderLines() { return showLeaderLines==1; } } diff --git a/src/main/java/io/starter/formats/OOXML/DPt.java b/src/main/java/io/starter/formats/OOXML/DPt.java index 5d68317..ab26bc5 100644 --- a/src/main/java/io/starter/formats/OOXML/DPt.java +++ b/src/main/java/io/starter/formats/OOXML/DPt.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,114 +22,113 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * dPt (Data Point) * This element specifies a single data point. - * + *

        * parent: series * children: idx REQ, invertIfNegative, marker, bubble3D, explosion, spPr, pictureOptions - * - * */ // TODO: finish pictureOptions -public class DPt implements OOXMLElement{ +public class DPt implements OOXMLElement { - private static final long serialVersionUID = 8354707071603571747L; - private int idx; + private static final long serialVersionUID = 8354707071603571747L; + private int idx; private boolean invertIfNegative; private boolean bubble3D; private Marker marker; private SpPr spPr; private int explosion; - - public DPt(int idx, boolean invertIfNegative, boolean bubble3D, Marker m, SpPr sp, int explosion) { - this.idx= idx; - this.invertIfNegative= invertIfNegative; - this.bubble3D= bubble3D; - this.marker= m; - this.spPr= sp; - this.explosion= explosion; - } - public DPt(DPt d) { - this.idx= d.idx; - this.invertIfNegative= d.invertIfNegative; - this.bubble3D= d.bubble3D; - this.marker= d.marker; - this.spPr= d.spPr; - this.explosion= d.explosion; - } - - + + public DPt(int idx, boolean invertIfNegative, boolean bubble3D, Marker m, SpPr sp, int explosion) { + this.idx = idx; + this.invertIfNegative = invertIfNegative; + this.bubble3D = bubble3D; + this.marker = m; + this.spPr = sp; + this.explosion = explosion; + } + + public DPt(DPt d) { + this.idx = d.idx; + this.invertIfNegative = d.invertIfNegative; + this.bubble3D = d.bubble3D; + this.marker = d.marker; + this.spPr = d.spPr; + this.explosion = d.explosion; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - int idx= -1; - boolean invertIfNegative= true; - boolean bubble3D= true; - Marker m= null; - SpPr sp= null; - int explosion= 0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("idx")) { // child element only contains 1 element - if (xpp.getAttributeCount() > 0) - idx= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("invertIfNegative")) { // child element only contains 1 element - if (xpp.getAttributeCount() > 0) - invertIfNegative= (xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("bubble3D")) { // child element only contains 1 element - if (xpp.getAttributeCount() > 0) - bubble3D= (xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("explosion")) { // child element only contains 1 element - if (xpp.getAttributeCount() > 0) - explosion= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp= (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); + int idx = -1; + boolean invertIfNegative = true; + boolean bubble3D = true; + Marker m = null; + SpPr sp = null; + int explosion = 0; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("idx")) { // child element only contains 1 element + if (xpp.getAttributeCount() > 0) + idx = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("invertIfNegative")) { // child element only contains 1 element + if (xpp.getAttributeCount() > 0) + invertIfNegative = (xpp.getAttributeValue(0).equals("1")); + } else if (tnm.equals("bubble3D")) { // child element only contains 1 element + if (xpp.getAttributeCount() > 0) + bubble3D = (xpp.getAttributeValue(0).equals("1")); + } else if (tnm.equals("explosion")) { // child element only contains 1 element + if (xpp.getAttributeCount() > 0) + explosion = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("spPr")) { + lastTag.push(tnm); + sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); // sp.setNS("c"); - } else if (tnm.equals("marker")) { - lastTag.push(tnm); - m= (Marker) Marker.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("dPt")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("dPt.parseOOXML: " + e.toString()); - } - DPt oe= new DPt(idx, invertIfNegative, bubble3D, m, sp, explosion); - return oe; + } else if (tnm.equals("marker")) { + lastTag.push(tnm); + m = (Marker) Marker.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("dPt")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("dPt.parseOOXML: " + e.toString()); + } + DPt oe = new DPt(idx, invertIfNegative, bubble3D, m, sp, explosion); + return oe; } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - ooxml.append(""); - if (!invertIfNegative) ooxml.append(""); // default= true - if (marker!=null) ooxml.append(marker.getOOXML()); - if (!bubble3D) ooxml.append(""); // default= true - if (explosion!=0) ooxml.append(""); - if (spPr!=null) ooxml.append(spPr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new DPt(this); - } + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + ooxml.append(""); + if (!invertIfNegative) ooxml.append(""); // default= true + if (marker != null) ooxml.append(marker.getOOXML()); + if (!bubble3D) ooxml.append(""); // default= true + if (explosion != 0) ooxml.append(""); + if (spPr != null) ooxml.append(spPr.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new DPt(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/DefRPr.java b/src/main/java/io/starter/formats/OOXML/DefRPr.java index 38ca814..6e7675b 100644 --- a/src/main/java/io/starter/formats/OOXML/DefRPr.java +++ b/src/main/java/io/starter/formats/OOXML/DefRPr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,196 +22,197 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; +import java.util.Stack; /** - * defRPr (Default Text Run Properties) - * + * defRPr (Default Text Run Properties) + *

        * This element contains all default run level text properties for the text runs within a containing paragraph. These * properties are to be used when overriding properties have not been defined within the rPr element - * + *

        * parent: many, including pPr * children: ln, FILLS, EFFECTS, highlight, TEXTUNDERLINE, TEXTUNDERLINEFILL, latin, ea, cs, sym, hlinkClick, hlinkMouseOver * many attributes - * - * */ // TODO: FINISH CHILD ELEMENTS highlight TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver -public class DefRPr implements OOXMLElement{ +public class DefRPr implements OOXMLElement { + + private static final long serialVersionUID = 6764149567499222506L; + private FillGroup fillGroup = null; + private EffectPropsGroup effect = null; + private Ln line = null; + private HashMap attrs = null; + private String latin = null, ea = null, cs = null; // really children but only have 1 attribute and no children + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + FillGroup fill = null; + EffectPropsGroup effect = null; + Ln l = null; + String latin = null, ea = null, cs = null; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("defRPr")) { // default text properties + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("ln")) { + lastTag.push(tnm); + l = (Ln) Ln.parseOOXML(xpp, lastTag, bk); + } else if ( + tnm.equals("solidFill") || + tnm.equals("noFill") || + tnm.equals("gradFill") || + tnm.equals("grpFill") || + tnm.equals("pattFill") || + tnm.equals("blipFill")) { + lastTag.push(tnm); + fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); + } else if ( + tnm.equals("effectLst") || + tnm.equals("effectDag")) { + lastTag.push(tnm); + effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); + // TODO: Eventually these will be objects + } else if (tnm.equals("latin")) { + latin = xpp.getAttributeValue(0); + } else if (tnm.equals("ea")) { + ea = xpp.getAttributeValue(0); + } else if (tnm.equals("cs")) { + cs = xpp.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("defRPr")) { + lastTag.pop(); // pop this tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("defPr.parseOOXML: " + e.toString()); + } + DefRPr dp = new DefRPr(fill, effect, l, attrs, latin, ea, cs); + return dp; + } + + /** + * return all the text properties in hashmap from + * + * @return + */ + public HashMap getTextProperties() { + HashMap textprops = new HashMap(); + textprops.putAll(attrs); + textprops.put("latin_typeface", latin); + textprops.put("ea_typeface", ea); + textprops.put("cs_typeface", cs); + // TODO: Fill, line ... + return textprops; + } + + /** + * create a default default Run Properties + * for BIFF8 compatibility + * qaa + */ + public DefRPr() { + this.attrs = new HashMap(); + this.attrs.put("sz", "900"); + this.attrs.put("b", "1"); + this.attrs.put("i", "0"); + this.attrs.put("u", "none"); + this.attrs.put("strike", "noStrike"); + this.attrs.put("baseline", "0"); + this.fillGroup = new FillGroup(null, null, null, null, new SolidFill()); + this.latin = "Arial"; + this.ea = "Arial"; + this.cs = "Arial"; + } + + /** + * create a default paragraph property from the specified information + * + * @param fontFace String font face e.g. "Arial" + * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) + * @param b boolean true if bold + * @param i boolean true if italic + * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted + * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) + * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none + * @param clr String fill color in hex form without the # + */ + public DefRPr(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { + this.attrs = new HashMap(); + this.attrs.put("sz", String.valueOf(sz)); + this.attrs.put("b", (b ? "1" : "0")); + this.attrs.put("i", (i ? "1" : "0")); + this.attrs.put("u", u); + this.attrs.put("strike", strike); + this.attrs.put("baseline", "0"); + this.fillGroup = new FillGroup(null, null, null, null, new SolidFill(clr)); + this.latin = fontFace; + this.ea = fontFace; + this.cs = fontFace; + } - private static final long serialVersionUID = 6764149567499222506L; - private FillGroup fillGroup= null; - private EffectPropsGroup effect= null; - private Ln line= null; - private HashMap attrs= null; - private String latin= null, ea= null, cs= null; // really children but only have 1 attribute and no children + public DefRPr(FillGroup fill, EffectPropsGroup effect, Ln l, HashMap attrs, String latin, String ea, String cs) { + this.fillGroup = fill; + this.effect = effect; + this.line = l; + this.latin = latin; + this.ea = ea; + this.cs = cs; + this.attrs = attrs; + } - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - FillGroup fill= null; - EffectPropsGroup effect= null; - Ln l= null; - String latin= null, ea= null, cs= null; - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("defRPr")) { // default text properties - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("ln")) { - lastTag.push(tnm); - l= (Ln) Ln.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill= (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect= (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - // TODO: Eventually these will be objects - } else if (tnm.equals("latin")) { - latin= xpp.getAttributeValue(0); - } else if (tnm.equals("ea")) { - ea= xpp.getAttributeValue(0); - } else if (tnm.equals("cs")) { - cs= xpp.getAttributeValue(0); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("defRPr")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("defPr.parseOOXML: " + e.toString()); - } - DefRPr dp= new DefRPr(fill, effect, l, attrs, latin, ea, cs); - return dp; - } + public DefRPr(DefRPr dp) { + this.fillGroup = dp.fillGroup; + this.effect = dp.effect; + this.line = dp.line; + this.latin = dp.latin; + this.ea = dp.ea; + this.cs = dp.cs; + this.attrs = dp.attrs; + } - /** - * return all the text properties in hashmap from - * @return - */ - public HashMap getTextProperties() { - HashMap textprops= new HashMap(); - textprops.putAll(attrs); - textprops.put("latin_typeface", latin); - textprops.put("ea_typeface", ea); - textprops.put("cs_typeface", cs); - // TODO: Fill, line ... - return textprops; - } - - /** - * create a default default Run Properties - * for BIFF8 compatibility - qaa*/ - public DefRPr() { - this.attrs= new HashMap(); - this.attrs.put("sz", "900"); - this.attrs.put("b", "1"); - this.attrs.put("i", "0"); - this.attrs.put("u", "none"); - this.attrs.put("strike", "noStrike"); - this.attrs.put("baseline", "0"); - this.fillGroup= new FillGroup(null, null, null, null, new SolidFill()); - this.latin= "Arial"; - this.ea= "Arial"; - this.cs= "Arial"; - } - - /** - * create a default paragraph property from the specified information - * @param fontFace String font face e.g. "Arial" - * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) - * @param b boolean true if bold - * @param i boolean true if italic - * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted - * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) - * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none - * @param clr String fill color in hex form without the # - */ - public DefRPr(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { - this.attrs= new HashMap(); - this.attrs.put("sz", String.valueOf(sz)); - this.attrs.put("b", (b?"1":"0")); - this.attrs.put("i", (i?"1":"0")); - this.attrs.put("u", u); - this.attrs.put("strike", strike); - this.attrs.put("baseline", "0"); - this.fillGroup= new FillGroup(null, null, null, null, new SolidFill(clr)); - this.latin= fontFace; - this.ea= fontFace; - this.cs= fontFace; - } - - public DefRPr(FillGroup fill, EffectPropsGroup effect, Ln l, HashMap attrs, String latin, String ea, String cs) { - this.fillGroup= fill; - this.effect= effect; - this.line= l; - this.latin= latin; - this.ea= ea; - this.cs= cs; - this.attrs= attrs; - } - public DefRPr(DefRPr dp) { - this.fillGroup= dp.fillGroup; - this.effect= dp.effect; - this.line= dp.line; - this.latin= dp.latin; - this.ea= dp.ea; - this.cs= dp.cs; - this.attrs= dp.attrs; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (line!=null) ooxml.append(line.getOOXML()); - if (fillGroup!=null) ooxml.append(fillGroup.getOOXML()); // group fill - if (effect!=null) ooxml.append(effect.getOOXML()); // group effect - // highlight - // TEXTUNDERLINELINE - // TEXTUNDERLINEFILL - if (latin!=null) ooxml.append(""); - if (ea!=null) ooxml.append(""); - if (cs!=null) ooxml.append(""); - // hLinkClick - // hLinkMouseOver - ooxml.append(""); - return ooxml.toString(); - } + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (line != null) ooxml.append(line.getOOXML()); + if (fillGroup != null) ooxml.append(fillGroup.getOOXML()); // group fill + if (effect != null) ooxml.append(effect.getOOXML()); // group effect + // highlight + // TEXTUNDERLINELINE + // TEXTUNDERLINEFILL + if (latin != null) ooxml.append(""); + if (ea != null) ooxml.append(""); + if (cs != null) ooxml.append(""); + // hLinkClick + // hLinkMouseOver + ooxml.append(""); + return ooxml.toString(); + } - public OOXMLElement cloneElement() { - return new DefRPr(this); - } + public OOXMLElement cloneElement() { + return new DefRPr(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Dxf.java b/src/main/java/io/starter/formats/OOXML/Dxf.java index e05080a..b4c5ec1 100644 --- a/src/main/java/io/starter/formats/OOXML/Dxf.java +++ b/src/main/java/io/starter/formats/OOXML/Dxf.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,347 +22,354 @@ */ package io.starter.formats.OOXML; -import java.util.Arrays; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.FormatHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.Font; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Arrays; /** * dxf (Formatting) OOXML Element - * - * A single dxf record, expressing incremental formatting to be applied. - * Used for Conditional Formatting, Tables, Sort Conditions, Color filters ... - * - * Differntial Formatting: - * define formatting for all non-cell formatting in the workbook. Whereas xf records fully specify a particular aspect of formatting (e.g., cell borders) + *

        + * A single dxf record, expressing incremental formatting to be applied. + * Used for Conditional Formatting, Tables, Sort Conditions, Color filters ... + *

        + * Differntial Formatting: + * define formatting for all non-cell formatting in the workbook. Whereas xf records fully specify a particular aspect of formatting (e.g., cell borders) * by referencing those formatting definitions elsewhere in the Styles part, dxf records specify incremental (or * differential) aspects of formatting directly inline within the dxf element. The dxf formatting is to be applied on * top of or in addition to any formatting already present on the object using the dxf record. - * - * parent: (StyleSheet styles.xml) dxfs - * chilren: SEQUENCE: font, numFmt, fill, alignment, border, protection - * + *

        + * parent: (StyleSheet styles.xml) dxfs + * chilren: SEQUENCE: font, numFmt, fill, alignment, border, protection */ // TODO: protection element -public class Dxf implements OOXMLElement{ - - private static final long serialVersionUID = -5999328795988018131L; - private Font font = null; - private NumFmt numFmt = null; - private Fill fill = null; - private Alignment alignment = null; - private Border border= null; - private WorkBookHandle wbh= null; - - public Dxf(Font fnt, NumFmt nf, Fill f, Alignment a, Border b, WorkBookHandle wbh) { - this.font= fnt; - this.numFmt= nf; - this.fill= f; - this.alignment= a; - this.border= b; - this.wbh= wbh; - } - - public Dxf(Dxf d) { - this.font= d.font; - this.numFmt= d.numFmt; - this.fill= d.fill; - this.alignment= d.alignment; - this.border= d.border; - this.wbh= d.wbh; - } - - public Dxf() { } +public class Dxf implements OOXMLElement { + + private static final long serialVersionUID = -5999328795988018131L; + private Font font = null; + private NumFmt numFmt = null; + private Fill fill = null; + private Alignment alignment = null; + private Border border = null; + private WorkBookHandle wbh = null; + + public Dxf(Font fnt, NumFmt nf, Fill f, Alignment a, Border b, WorkBookHandle wbh) { + this.font = fnt; + this.numFmt = nf; + this.fill = f; + this.alignment = a; + this.border = b; + this.wbh = wbh; + } + + public Dxf(Dxf d) { + this.font = d.font; + this.numFmt = d.numFmt; + this.fill = d.fill; + this.alignment = d.alignment; + this.border = d.border; + this.wbh = d.wbh; + } + + public Dxf() { + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - Font fnt= null; - NumFmt nf= null; - Fill f= null; - Alignment a= null; - Border b= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("font")) { - fnt= (Font) Font.parseOOXML(xpp, bk); - } else if (tnm.equals("numFmt")) { - nf= (NumFmt) NumFmt.parseOOXML(xpp); - } else if (tnm.equals("fill")) { - f= (Fill) Fill.parseOOXML(xpp, true, bk); - } else if (tnm.equals("alignment")) { - a= (Alignment) Alignment.parseOOXML(xpp); - } else if (tnm.equals("border")) { - b= (Border) Border.parseOOXML(xpp, bk); - } else if (tnm.equals("protection")) { - // TODO: finish - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("dxf")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("dxf.parseOOXML: " + e.toString()); - } - Dxf d= new Dxf(fnt, nf, f, a, b, bk); - return d; + Font fnt = null; + NumFmt nf = null; + Fill f = null; + Alignment a = null; + Border b = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("font")) { + fnt = Font.parseOOXML(xpp, bk); + } else if (tnm.equals("numFmt")) { + nf = (NumFmt) NumFmt.parseOOXML(xpp); + } else if (tnm.equals("fill")) { + f = (Fill) Fill.parseOOXML(xpp, true, bk); + } else if (tnm.equals("alignment")) { + a = (Alignment) Alignment.parseOOXML(xpp); + } else if (tnm.equals("border")) { + b = (Border) Border.parseOOXML(xpp, bk); + } else if (tnm.equals("protection")) { + // TODO: finish + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("dxf")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("dxf.parseOOXML: " + e.toString()); + } + Dxf d = new Dxf(fnt, nf, f, a, b, bk); + return d; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (font != null) ooxml.append(font.getOOXML()); + if (numFmt != null) ooxml.append(numFmt.getOOXML()); + if (fill != null) ooxml.append(fill.getOOXML(true)); + if (alignment != null) ooxml.append(alignment.getOOXML()); + if (border != null) ooxml.append(border.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public int[] getBorderColors() { + if (border != null) + return border.getBorderColorInts(); + return null; + } + + public int[] getBorderStyles() { + if (border != null) { + int[] styles = border.getBorderStyles(); + if (Arrays.equals(new int[]{0, 0, 0, 0}, styles)) + return null; + return styles; + } + return null; + } + + public int[] getBorderSizes() { + if (border != null) + return border.getBorderSizes(); + return null; + } + + public boolean isStriken() { + if (font != null) + return font.getStricken(); + return false; + } + + public int getFg() { + if (fill != null) + return fill.getFgColorAsInt(wbh.getWorkBook().getTheme()); + return -1; + } + + public int getFillPatternInt() { + if (fill != null) + return fill.getFillPatternInt(); + return -1; + } + + /** + * returns the OOXML Fill element + * + * @return + */ + public Fill getFill() { + return fill; + } + + public int getBg() { + if (fill != null) + return fill.getBgColorAsInt(wbh.getWorkBook().getTheme()); + return -1; + } + + public String getBgColorAsString() { + if (fill != null) + return fill.getBgColorAsRGB(wbh.getWorkBook().getTheme()); + return null; + } + + + public String getHorizontalAlign() { + if (alignment != null) + return alignment.getAlignment("horizontal"); + return null; + } + + public String getVerticalAlign() { + if (alignment != null) + return alignment.getAlignment("vertical"); + return null; + } + + public String getNumberFormat() { + if (numFmt != null) + return numFmt.getFormatId(); + return null; + } + + /** + * returns the Font for ths dxf, if any + * + * @return + */ + public Font getFont() { + return font; + } + + public int getFontHeight() { + if (font != null) + return font.getFontHeight(); + return -1; + } + + public int getFontWeight() { + if (font != null) + return font.getFontWeight(); + return -1; + } + + public String getFontName() { + if (font != null) + return font.getFontName(); + return null; + } + + public int getFontColor() { + if (font != null) + return font.getColor(); + return -1; + } + + public boolean isItalic() { + if (font != null) + return font.getItalic(); + return false; + } + + public int getFontUnderline() { + if (font != null) + return font.getUnderlineStyle(); + return -1; + } + + /** + * return a String representation of this Dxf in "style properties" notation + * + * @return String representation of this Dxf + * @see Cf.setStylePropsFromString + */ + public String getStyleProps() { + StringBuffer props = new StringBuffer(); + + // fill + if (fill != null) { + props.append("pattern:" + fill.getFillPatternInt() + ";"); + String s = fill.getFgColorAsRGB(wbh.getWorkBook().getTheme()); + if (s != null) // fg is pattern color + props.append("patterncolor:#" + s + ";"); + s = fill.getBgColorAsRGB(wbh.getWorkBook().getTheme()); + if (s != null) + props.append("background:#" + s + ";"); + } + + // font + if (font != null) { // note: since this is differential, many of these may not be set + if (!font.getFontName().equals("")) + props.append("font-name" + font.getFontName() + ";"); + if (font.getFontWeight() > -1) + props.append("font-weight:" + font.getFontWeight() + ";"); + if (font.getFontHeight() > -1) + props.append("font-Height:" + font.getFontHeight() + ";"); + props.append("font-ColorIndex:" + font.getColor() + ";"); + if (font.getStricken()) props.append("font-Striken:" + font.getStricken() + ";"); + if (font.getItalic()) props.append("font-italic:" + font.getItalic() + ";"); + if (font.getUnderlineStyle() != 0) props.append("font-UnderlineStyle:" + font.getUnderlineStyle() + ";"); + // TODO: italic, bold + } + + // borders + if (border != null) { + int[] sizes = border.getBorderSizes(); + int[] styles = border.getBorderStyles(); + String[] colors = border.getBorderColors(); + props.append("border-top:" + sizes[0] + " " + FormatHandle.BORDER_NAMES[styles[0]] + " " + colors[0] + ";"); + props.append("border-left:" + sizes[1] + " " + FormatHandle.BORDER_NAMES[styles[1]] + " " + colors[1] + ";"); + props.append("border-bottom:" + sizes[2] + " " + FormatHandle.BORDER_NAMES[styles[2]] + " " + colors[2] + ";"); + props.append("border-right:" + sizes[3] + " " + FormatHandle.BORDER_NAMES[styles[3]] + " " + colors[3] + ";"); + } + + // alignment + if (alignment != null) { + String s = alignment.getAlignment("vertical"); + if (s != null) + props.append("alignment-vertical" + s + ";"); + s = alignment.getAlignment("horizontal"); + if (s != null) + props.append("alignment-horizontal" + s + ";"); + } + + // number format + if (numFmt != null) { + String s = numFmt.getFormatId(); + props.append("numberformat:" + s + ";"); + + } + + return props.toString(); + } + + + /** + * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info + */ + public void createFont(int w, boolean i, int ustyle, int cl, int h) { + font = new Font("", w, h); + if (w == 700) + font.setBold(true); // why doesn't constructor do this? + if (i) font.setItalic(i); + if (ustyle != 0) font.setUnderlineStyle((byte) ustyle); + font.setColor(cl); + } + + /** + * Sets the fill for this dxf from an existing Fill element + * + * @param f + */ + public void setFill(Fill f) { + this.fill = (Fill) f.cloneElement(); + } + + /** + * Sts the Font for this dxf from an existing Font + * + * @param f + */ + public void setFont(Font f) { + this.font = f; + } + + /** + * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info + */ + public void createFill(int fs, int fg, int bg, WorkBookHandle bk) { + if (fs < 0 || fs > OOXMLConstants.patternFill.length) + this.fill = new Fill(null, fg, bg, bk.getWorkBook().getTheme()); // meaning it's the default (solid bg) pattern + else + this.fill = new Fill(OOXMLConstants.patternFill[fs], fg, bg, bk.getWorkBook().getTheme()); + } + + /** + * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info + */ + public void createBorder(WorkBookHandle bk, int[] styles, int[] colors) { + border = new Border(bk, styles, colors); + } + + public OOXMLElement cloneElement() { + return new Dxf(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (font!=null) ooxml.append(font.getOOXML()); - if (numFmt!=null) ooxml.append(numFmt.getOOXML()); - if (fill!=null) ooxml.append(fill.getOOXML(true)); - if (alignment!=null) ooxml.append(alignment.getOOXML()); - if (border!=null) ooxml.append(border.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public int[] getBorderColors(){ - if (border!=null) - return border.getBorderColorInts(); - return null; - } - - public int[] getBorderStyles() { - if (border!=null) { - int[] styles= border.getBorderStyles(); - if (Arrays.equals(new int[] {0, 0, 0, 0}, styles)) - return null; - return styles; - } - return null; - } - - public int[] getBorderSizes() { - if (border!=null) - return border.getBorderSizes(); - return null; - } - - public boolean isStriken() { - if (font!=null) - return font.getStricken(); - return false; - } - - public int getFg() { - if (fill!=null) - return fill.getFgColorAsInt(wbh.getWorkBook().getTheme()); - return -1; - } - - public int getFillPatternInt() { - if (fill!=null) - return fill.getFillPatternInt(); - return -1; - } - - /** - * returns the OOXML Fill element - * @return - */ - public Fill getFill() { - return fill; - } - - public int getBg() { - if (fill!=null) - return fill.getBgColorAsInt(wbh.getWorkBook().getTheme()); - return -1; - } - - public String getBgColorAsString() { - if (fill!=null) - return fill.getBgColorAsRGB(wbh.getWorkBook().getTheme()); - return null; - } - - - public String getHorizontalAlign() { - if (alignment!=null) - return alignment.getAlignment("horizontal"); - return null; - } - - public String getVerticalAlign() { - if (alignment!=null) - return alignment.getAlignment("vertical"); - return null; - } - - public String getNumberFormat() { - if (numFmt!=null) - return numFmt.getFormatId(); - return null; - } - - /** - * returns the Font for ths dxf, if any - * @return - */ - public Font getFont() { return font; } - - public int getFontHeight() { - if (font!=null) - return font.getFontHeight(); - return -1; - } - - public int getFontWeight() { - if (font!=null) - return font.getFontWeight(); - return -1; - } - - public String getFontName() { - if (font!=null) - return font.getFontName(); - return null; - } - - public int getFontColor() { - if (font!=null) - return font.getColor(); - return -1; - } - - public boolean isItalic() { - if (font!=null) - return font.getItalic(); - return false; - } - - public int getFontUnderline() { - if (font!=null) - return font.getUnderlineStyle(); - return -1; - } - - /** - * return a String representation of this Dxf in "style properties" notation - * @return String representation of this Dxf - * @see Cf.setStylePropsFromString - */ - public String getStyleProps() { - StringBuffer props= new StringBuffer(); - - // fill - if (fill!=null) { - props.append("pattern:" + fill.getFillPatternInt() + ";"); - String s= fill.getFgColorAsRGB(wbh.getWorkBook().getTheme()); - if (s!=null) // fg is pattern color - props.append("patterncolor:#" + s + ";"); - s= fill.getBgColorAsRGB(wbh.getWorkBook().getTheme()); - if (s!=null) - props.append("background:#" + s + ";"); - } - - // font - if (font!=null) { // note: since this is differential, many of these may not be set - if (!font.getFontName().equals("")) - props.append("font-name" + font.getFontName() + ";"); - if (font.getFontWeight()>-1) - props.append("font-weight:" + font.getFontWeight() + ";"); - if (font.getFontHeight()>-1) - props.append("font-Height:" + font.getFontHeight() + ";"); - props.append("font-ColorIndex:" + font.getColor() + ";"); - if (font.getStricken()) props.append("font-Striken:" + font.getStricken() + ";"); - if (font.getItalic()) props.append("font-italic:" + font.getItalic() + ";"); - if (font.getUnderlineStyle()!=0) props.append("font-UnderlineStyle:" + font.getUnderlineStyle() + ";"); - // TODO: italic, bold - } - - // borders - if (border!=null) { - int[] sizes= border.getBorderSizes(); - int[] styles= border.getBorderStyles(); - String[] colors= border.getBorderColors(); - props.append("border-top:" +sizes[0]+" "+FormatHandle.BORDER_NAMES[styles[0]]+" "+colors[0]+";"); - props.append("border-left:" +sizes[1]+" "+FormatHandle.BORDER_NAMES[styles[1]]+" "+colors[1]+";"); - props.append("border-bottom:"+sizes[2]+" "+FormatHandle.BORDER_NAMES[styles[2]]+" "+colors[2]+";"); - props.append("border-right:" +sizes[3]+" "+FormatHandle.BORDER_NAMES[styles[3]]+" "+colors[3]+";"); - } - - // alignment - if (alignment!=null) { - String s= alignment.getAlignment("vertical"); - if (s!=null) - props.append("alignment-vertical" + s + ";"); - s= alignment.getAlignment("horizontal"); - if (s!=null) - props.append("alignment-horizontal" + s + ";"); - } - - // number format - if (numFmt!=null) { - String s= numFmt.getFormatId(); - props.append("numberformat:" + s + ";"); - - } - - return props.toString(); - } - - - /** - * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info - */ - public void createFont(int w, boolean i, int ustyle, int cl, int h) { - font= new Font("", w, h); - if (w==700) - font.setBold(true); // why doesn't constructor do this? - if (i) font.setItalic(i); - if (ustyle!=0) font.setUnderlineStyle((byte) ustyle); - font.setColor(cl); - } - - /** - * Sets the fill for this dxf from an existing Fill element - * @param f - */ - public void setFill(Fill f) { - this.fill= (Fill) f.cloneElement(); - } - - /** - * Sts the Font for this dxf from an existing Font - * @param f - */ - public void setFont(Font f) { - this.font= (Font)f; - } - - /** - * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info - */ - public void createFill(int fs, int fg, int bg, WorkBookHandle bk) { - if (fs<0 || fs > OOXMLConstants.patternFill.length) - this.fill= new Fill(null, fg, bg, bk.getWorkBook().getTheme()); // meaning it's the default (solid bg) pattern - else - this.fill= new Fill(OOXMLConstants.patternFill[fs], fg, bg, bk.getWorkBook().getTheme()); - } - - /** - * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info - */ - public void createBorder(WorkBookHandle bk, int[] styles, int[] colors) { - border= new Border(bk, styles, colors); - } - - public OOXMLElement cloneElement() { - return new Dxf(this); - } } diff --git a/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.java b/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.java index c3d25a8..1813718 100644 --- a/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.java +++ b/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,215 +22,212 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import java.util.HashMap; -import java.util.Iterator; - +import io.starter.toolkit.Logger; import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Stack; /** - * EffectPropsGroup Effect Properties either effectDag or effectLst + * EffectPropsGroup Effect Properties either effectDag or effectLst */ //TODO: FINISH CHILD ELEMENTS for both effectDag and effectLst -public class EffectPropsGroup implements OOXMLElement{ - - private static final long serialVersionUID = 8250236905326475833L; - +public class EffectPropsGroup implements OOXMLElement { + + private static final long serialVersionUID = 8250236905326475833L; + private EffectDag effectDag; private EffectLst effectLst; - - public EffectPropsGroup(EffectDag ed, EffectLst el) { - this.effectDag= ed; - this.effectLst= el; - } - public EffectPropsGroup(EffectPropsGroup e) { - this.effectDag= e.effectDag; - this.effectLst= e.effectLst; - } - - + + public EffectPropsGroup(EffectDag ed, EffectLst el) { + this.effectDag = ed; + this.effectLst = el; + } + + public EffectPropsGroup(EffectPropsGroup e) { + this.effectDag = e.effectDag; + this.effectLst = e.effectLst; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - EffectDag ed= null; - EffectLst el= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("effectDag")) { - lastTag.push(tnm); - ed = (EffectDag) EffectDag.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("effectLst")) { - lastTag.push(tnm); - el = (EffectLst) EffectLst.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } - } else if (eventType== XmlPullParser.END_TAG) { // shouldn't get here - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("EffectPropsGroup.parseOOXML: " + e.toString()); - } - EffectPropsGroup e= new EffectPropsGroup(ed, el); - return e; + EffectDag ed = null; + EffectLst el = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("effectDag")) { + lastTag.push(tnm); + ed = EffectDag.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } else if (tnm.equals("effectLst")) { + lastTag.push(tnm); + el = EffectLst.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here + lastTag.pop(); + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("EffectPropsGroup.parseOOXML: " + e.toString()); + } + EffectPropsGroup e = new EffectPropsGroup(ed, el); + return e; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + if (effectDag != null) ooxml.append(effectDag.getOOXML()); + if (effectLst != null) ooxml.append(effectLst.getOOXML()); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new EffectPropsGroup(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - if (effectDag!=null) ooxml.append(effectDag.getOOXML()); - if (effectLst!=null) ooxml.append(effectLst.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new EffectPropsGroup(this); - } } /** * effectDag (Effect Container) - * This element specifies a list of effects. Effects are applied in the order specified by the container type (sibling or + * This element specifies a list of effects. Effects are applied in the order specified by the container type (sibling or * tree). - * + *

        * parent: many * children: MANY (EFFECT) - * - * */ // TODO: FINISH CHILD ELEMENTS class EffectDag implements OOXMLElement { - private static final long serialVersionUID = 4786440439664356745L; - private HashMap attrs= null; - - public EffectDag(HashMap attrs) { - this.attrs= attrs; - } - public EffectDag(EffectDag e) { - this.attrs= e.attrs; - } - - + private static final long serialVersionUID = 4786440439664356745L; + private HashMap attrs = null; + + public EffectDag(HashMap attrs) { + this.attrs = attrs; + } + + public EffectDag(EffectDag e) { + this.attrs = e.attrs; + } + + public static EffectDag parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("effectDag")) { - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("CHILDELEMENT")) { - lastTag.push(tnm); - //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); - - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("effectDag")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("effectDag.parseOOXML: " + e.toString()); - } - EffectDag e= new EffectDag(attrs); - return e; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("effectDag")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("CHILDELEMENT")) { + lastTag.push(tnm); + //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("effectDag")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("effectDag.parseOOXML: " + e.toString()); + } + EffectDag e = new EffectDag(attrs); + return e; } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); // if (CHILD!=null) { ooxml.append(CHILD.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } + ooxml.append(""); + return ooxml.toString(); + } - public OOXMLElement cloneElement() { - return new EffectDag(this); - } + public OOXMLElement cloneElement() { + return new EffectDag(this); + } } -/** - * effectLst (Effect Container) +/** + * effectLst (Effect Container) * This element specifies a list of effects. Effects in an effectLst are applied in the default order by the rendering - * engine. The following diagrams illustrate the order in which effects are to be applied, both for shapes and for - * group shapes. - * + * engine. The following diagrams illustrate the order in which effects are to be applied, both for shapes and for + * group shapes. + *

        * parent: many * children: MANY (EFFECT) - * - * */ // TODO: FINISH CHILD ELEMENTS class EffectLst implements OOXMLElement { - private static final long serialVersionUID = -6164888373165090983L; + private static final long serialVersionUID = -6164888373165090983L; + + // public effectLst() { } + public EffectLst() { + } + + public EffectLst(EffectLst e) { + } + - // public effectLst() { } - public EffectLst() { - } - - public EffectLst(EffectLst e) { - } - - public static EffectLst parseOOXML(XmlPullParser xpp, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("CHILDELEMENT")) { - //lastTag.push(tnm); - //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("effectLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("effectLst.parseOOXML: " + e.toString()); - } - EffectLst e= new EffectLst(); - return e; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("CHILDELEMENT")) { + //lastTag.push(tnm); + //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("effectLst")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("effectLst.parseOOXML: " + e.toString()); + } + EffectLst e = new EffectLst(); + return e; } - - public String getOOXML() { - //StringBuffer ooxml= new StringBuffer(); + + public String getOOXML() { + //StringBuffer ooxml= new StringBuffer(); // ooxml.append(""); - return ""; // TODO: FINISH CHILD ELEMENTS + return ""; // TODO: FINISH CHILD ELEMENTS // return ooxml.toString(); - } + } - public OOXMLElement cloneElement() { - return new EffectLst(this); - } + public OOXMLElement cloneElement() { + return new EffectLst(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Ext.java b/src/main/java/io/starter/formats/OOXML/Ext.java index 3e193f4..bb86621 100644 --- a/src/main/java/io/starter/formats/OOXML/Ext.java +++ b/src/main/java/io/starter/formats/OOXML/Ext.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,85 +22,91 @@ */ package io.starter.formats.OOXML; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; +public class Ext implements OOXMLElement { -import io.starter.toolkit.Logger; + private static final long serialVersionUID = 2827740330905704185L; + private HashMap attrs; + private String nameSpace = null; + + public Ext() { + attrs = new HashMap(); + attrs.put("cx", "0"); + attrs.put("cy", "0"); + } + + public Ext(HashMap attrs, String ns) { + this.attrs = attrs; + this.nameSpace = ns; + } + + public Ext(Ext e) { + this.attrs = e.attrs; + this.nameSpace = e.nameSpace; + } -public class Ext implements OOXMLElement{ - private static final long serialVersionUID = 2827740330905704185L; - private HashMap attrs; - private String nameSpace= null; - - public Ext() { - attrs= new HashMap(); - attrs.put("cx", new String("0")); - attrs.put("cy", new String("0")); - } - public Ext(HashMap attrs, String ns) { - this.attrs= attrs; - this.nameSpace= ns; - } - public Ext(Ext e) { - this.attrs= e.attrs; - this.nameSpace= e.nameSpace; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - String ns= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ext")) { // get attributes - ns= xpp.getPrefix(); - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("ext")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ext.parseOOXML: " + e.toString()); - } - Ext e= new Ext(attrs, ns); - return e; + HashMap attrs = new HashMap(); + String ns = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("ext")) { // get attributes + ns = xpp.getPrefix(); + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("ext")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("ext.parseOOXML: " + e.toString()); + } + Ext e = new Ext(attrs, ns); + return e; } - /** set the namespace for ext element - * @param ns - */ - public void setNamespace(String ns) { this.nameSpace= ns; } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append("<" + this.nameSpace + ":ext"); - // attributes - Iterator i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new Ext(this); - } + /** + * set the namespace for ext element + * + * @param ns + */ + public void setNamespace(String ns) { + this.nameSpace = ns; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append("<" + this.nameSpace + ":ext"); + // attributes + Iterator i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Ext(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/ExtLst.java b/src/main/java/io/starter/formats/OOXML/ExtLst.java index d4bc5e4..376d04b 100644 --- a/src/main/java/io/starter/formats/OOXML/ExtLst.java +++ b/src/main/java/io/starter/formats/OOXML/ExtLst.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,73 +22,78 @@ */ package io.starter.formats.OOXML; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; +// TODO: FINISH +public class ExtLst implements OOXMLElement { -import io.starter.toolkit.Logger; + private static final long serialVersionUID = -4122012942547055359L; + private HashMap attrs = null; + private String nameSpace = null; + + public ExtLst() { + attrs = new HashMap(); + attrs.put("cx", "0"); + attrs.put("cy", "0"); + } + + public ExtLst(HashMap attrs, String ns) { + this.attrs = attrs; + this.nameSpace = ns; + } + + public ExtLst(ExtLst e) { + this.attrs = e.attrs; + this.nameSpace = e.nameSpace; + } -// TODO: FINISH -public class ExtLst implements OOXMLElement{ - private static final long serialVersionUID = -4122012942547055359L; - private HashMap attrs= null; - private String nameSpace= null; - - public ExtLst() { - attrs= new HashMap(); - attrs.put("cx", new String("0")); - attrs.put("cy", new String("0")); - } - public ExtLst(HashMap attrs, String ns) { - this.attrs= attrs; - this.nameSpace= ns; - } - public ExtLst(ExtLst e) { - this.attrs= e.attrs; - this.nameSpace= e.nameSpace; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - String ns= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("extLst")) { // get attributes - ns= xpp.getPrefix(); - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("extLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("extLst.parseOOXML: " + e.toString()); - } - Ext e= new Ext(attrs, ns); - return e; + HashMap attrs = new HashMap(); + String ns = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("extLst")) { // get attributes + ns = xpp.getPrefix(); + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("extLst")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("extLst.parseOOXML: " + e.toString()); + } + Ext e = new Ext(attrs, ns); + return e; } - /** set the namespace for ext element - * - * @param ns - */ - public void setNamespace(String ns) { this.nameSpace= ns; } - - public String getOOXML() { - //TODO: FINISH - return ""; + + /** + * set the namespace for ext element + * + * @param ns + */ + public void setNamespace(String ns) { + this.nameSpace = ns; + } + + public String getOOXML() { + //TODO: FINISH + return ""; /* StringBuffer ooxml= new StringBuffer(); ooxml.append("<" + this.ns + ":ext"); @@ -102,10 +107,10 @@ public String getOOXML() { ooxml.append("/>"); return ooxml.toString(); */ - } + } - public OOXMLElement cloneElement() { - return new ExtLst(this); - } + public OOXMLElement cloneElement() { + return new ExtLst(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Fill.java b/src/main/java/io/starter/formats/OOXML/Fill.java index 440a2ad..f37b33e 100644 --- a/src/main/java/io/starter/formats/OOXML/Fill.java +++ b/src/main/java/io/starter/formats/OOXML/Fill.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,368 +22,362 @@ */ package io.starter.formats.OOXML; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.FormatHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.Xf; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; /** * fill OOXML element - * + *

        * fill (Fill) This element specifies fill formatting - * + *

        * parent: styleSheet/fills element in styles.xml, dxf->fills children: REQ * CHOICE OF: patternFill, gradientFill - * - * */ public class Fill implements OOXMLElement { - private static final long serialVersionUID = -4510508531435037641L; - private PatternFill patternFill = null; - private GradientFill gradientFill = null; - private Theme theme= null; - - public Fill(PatternFill p, GradientFill g, Theme t) { - this.patternFill = p; - this.gradientFill = g; - this.theme= t; - } - - public Fill(Fill f) { - if (f.patternFill != null) - this.patternFill = (PatternFill) f.patternFill.cloneElement(); - if (f.gradientFill != null) - this.gradientFill = (GradientFill) f.gradientFill.cloneElement(); - this.theme= f.theme; - } - - /** - * create a new Fill from external vals - * @param fs String pattern type - * @param fg int color index - * @param bg int color index - */ - public Fill(String fs, int fg, int bg, Theme t) { - this.patternFill = new PatternFill(fs, fg, bg); - this.theme= t; - } - - /** - * create a new Fill from external vals - * - * @param i XLS indexed pattern - * - * @param fg int color index - * @param fgColorCustom - * @param bg int color index - */ - public Fill(int pattern, int fg, String fgColorCustom, int bg, - String bgColorCustom, Theme t) { - - this.patternFill = new PatternFill(PatternFill.translateIndexedFillPattern(pattern), - fg, fgColorCustom, bg, bgColorCustom); - this.theme= t; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, boolean isDxf, WorkBookHandle bk) { - PatternFill p = null; - GradientFill g = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("patternFill")) { - p = (PatternFill) PatternFill.parseOOXML(xpp, isDxf, bk); - - } else if (tnm.equals("gradientFill")) { - g = (GradientFill) GradientFill.parseOOXML(xpp, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fill")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fill.parseOOXML: " + e.toString()); - } - Fill oe = new Fill(p, g, bk.getWorkBook().getTheme()); - return oe; - } - - /** - * OOXML values are stored with an intiall FF in their value, - * - * this method assures that values returned are 6 digits + # for web usage - * @param rgbcolor - */ - protected static String transformToWebRGBColor(String rgbcolor){ - if(rgbcolor.indexOf("#")==0)return rgbcolor; - if(rgbcolor.indexOf("FF")==0 && rgbcolor.length()==8){ - return "#" + rgbcolor.substring(2, rgbcolor.length()); - } - return "#" + rgbcolor; - } - - /** - * OOXML values are stored with an intiall FF in their value, - * - * this method assures that values returned are 8 digits with - * ff appended to rgb value for ooxml usage - * @param rgbcolor - */ - protected static String transformToOOXMLRGBColor(String rgbcolor){ - if(rgbcolor.indexOf("#")==0){ - return "FF" + rgbcolor.substring(1, rgbcolor.length()); - } - if(rgbcolor.indexOf("FF")==0 && rgbcolor.length()==8){ - return rgbcolor; - } - return "FF" + rgbcolor; - } - - public String getOOXML() { - return getOOXML(false); - } - - /** - * dxfs apparently have different pattern fill syntax -- UNDOCUMENTED **** - * - * @param isDxf - * if this is an Dxf-generated fill, solid fills are handled - * differently than regular fills - * @return - */ - public String getOOXML(boolean isDxf) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (patternFill != null) - ooxml.append(patternFill.getOOXML(isDxf)); - if (gradientFill != null) - ooxml.append(gradientFill.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Fill(this); - } - - /** - * returns the OOXML specifying the fill based on this FormatHandle object - */ - public static String getOOXML(Xf xf) { - if (xf.getFill() != null) - return xf.getFill().getOOXML(); - - // otherwise, create fill from 2003-style xf - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - try { - ooxml.append(""); - } catch (IndexOutOfBoundsException e) { - ooxml.append(""); // apparently there are less patterns - // in xlsx? some other way of - // storage - } - int fg = xf.getForegroundColor(); - if (fg > -1 && fg != 64) - ooxml.append(""); - int bg = xf.getBackgroundColor(); - if (bg > -1 && bg != 64) - ooxml.append(""); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - return ooxml.toString(); - } - - /** - * returns the OOXML specifying the fill based on fill pattern fs, - * foreground color fg, background color bg - */ - public static String getOOXML(int fs, int fg, int bg) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - try { - ooxml.append(""); - } catch (ArrayIndexOutOfBoundsException e) { - ooxml.append(""); - } - if (fg > -1 && fg != 64) { - ooxml.append(""); - ooxml.append("\r\n"); - } - if (bg > -1 && bg != 64) { - ooxml.append(""); - ooxml.append("\r\n"); - } - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - return ooxml.toString(); - } - - /** - * return the foreground color of this fill, if any - * - * @return - */ - public String getFgColorAsRGB(Theme t) { - if (patternFill != null) - return this.patternFill.getFgColorAsRGB(t); - return null; - } - - /** - * return the fg color in indexed (int) representation - * - * @return - */ - public int getFgColorAsInt(Theme t) { - if (patternFill != null) - return this.patternFill.getFgColorAsInt(t); - return 0; // default= black - } - - /** - * return the bg color of this fill, if any - * - * @return - */ - public String getBgColorAsRGB(Theme t) { - if (patternFill != null) - return this.patternFill.getBgColorAsRGB(t); - return null; - } - - - /** - * return the bg color in indexed (int) representation - * - * @return - */ - public int getBgColorAsInt(Theme t) { - if (patternFill != null) - return this.patternFill.getBgColorAsInt(t); - return -1; - } - - /** - * sets the foreground fill color via color int - * - * @param t - */ - public void setFgColor(int t) { - if (patternFill != null) { - patternFill.setFgColor(t); - return; - } - } - - /** - * sets the foreground fill color to a color string and Excel-2003-mapped - * color int - * - * @param t - * Excel-2003-mapped color int for the hex color string - * @param colorString - * hex color string - */ - public void setFgColor(int t, String colorString) { - if (patternFill != null) - patternFill.setFgColor(t, colorString); - else - this.patternFill = new PatternFill("none", t, colorString, -1, null); - } - - /** - * sets the bg fill color via color int - * - * @param t - */ - public void setBgColor(int t) { - if (patternFill != null) { - patternFill.setBgColor(t); - return; - } - } - - /** - * sets the foreground fill color to a color string and Excel-2003-mapped - * color int - * - * @param t - * Excel-2003-mapped color int for the hex color string - * @param colorString - * hex color string - */ - public void setBgColor(int t, String colorString) { - if (patternFill != null) - patternFill.setBgColor(t, colorString); - else - this.patternFill = new PatternFill("none", -1, null, t, colorString); - } - - public String getFillPattern() { - if (patternFill != null) - return this.patternFill.getFillPattern(); - return null; - } - - /** - * return the fill pattern in 2003 int representation - * - * @return - */ - public int getFillPatternInt() { - if (patternFill != null) { - return this.patternFill.getFillPatternInt(); - } - return -1; - } - - /** - * sets the fill pattern - */ - public void setFillPattern(int t) { - if (patternFill != null) - this.patternFill.setFillPattern(t); - } - - /** - * returns true if the background pattern is solid - * - * @return - */ - public boolean isBackgroundSolid() { - if (patternFill != null) - return (this.patternFill.getFillPattern().equalsIgnoreCase("solid")); - return false; - } + private static final long serialVersionUID = -4510508531435037641L; + private PatternFill patternFill = null; + private GradientFill gradientFill = null; + private Theme theme = null; + + public Fill(PatternFill p, GradientFill g, Theme t) { + this.patternFill = p; + this.gradientFill = g; + this.theme = t; + } + + public Fill(Fill f) { + if (f.patternFill != null) + this.patternFill = (PatternFill) f.patternFill.cloneElement(); + if (f.gradientFill != null) + this.gradientFill = (GradientFill) f.gradientFill.cloneElement(); + this.theme = f.theme; + } + + /** + * create a new Fill from external vals + * + * @param fs String pattern type + * @param fg int color index + * @param bg int color index + */ + public Fill(String fs, int fg, int bg, Theme t) { + this.patternFill = new PatternFill(fs, fg, bg); + this.theme = t; + } + + /** + * create a new Fill from external vals + * + * @param i XLS indexed pattern + * @param fg int color index + * @param fgColorCustom + * @param bg int color index + */ + public Fill(int pattern, int fg, String fgColorCustom, int bg, + String bgColorCustom, Theme t) { + + this.patternFill = new PatternFill(PatternFill.translateIndexedFillPattern(pattern), + fg, fgColorCustom, bg, bgColorCustom); + this.theme = t; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, boolean isDxf, WorkBookHandle bk) { + PatternFill p = null; + GradientFill g = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("patternFill")) { + p = PatternFill.parseOOXML(xpp, isDxf, bk); + + } else if (tnm.equals("gradientFill")) { + g = GradientFill.parseOOXML(xpp, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("fill")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("fill.parseOOXML: " + e.toString()); + } + Fill oe = new Fill(p, g, bk.getWorkBook().getTheme()); + return oe; + } + + /** + * OOXML values are stored with an intiall FF in their value, + *

        + * this method assures that values returned are 6 digits + # for web usage + * + * @param rgbcolor + */ + protected static String transformToWebRGBColor(String rgbcolor) { + if (rgbcolor.indexOf("#") == 0) return rgbcolor; + if (rgbcolor.indexOf("FF") == 0 && rgbcolor.length() == 8) { + return "#" + rgbcolor.substring(2); + } + return "#" + rgbcolor; + } + + /** + * OOXML values are stored with an intiall FF in their value, + *

        + * this method assures that values returned are 8 digits with + * ff appended to rgb value for ooxml usage + * + * @param rgbcolor + */ + protected static String transformToOOXMLRGBColor(String rgbcolor) { + if (rgbcolor.indexOf("#") == 0) { + return "FF" + rgbcolor.substring(1); + } + if (rgbcolor.indexOf("FF") == 0 && rgbcolor.length() == 8) { + return rgbcolor; + } + return "FF" + rgbcolor; + } + + public String getOOXML() { + return getOOXML(false); + } + + /** + * dxfs apparently have different pattern fill syntax -- UNDOCUMENTED **** + * + * @param isDxf if this is an Dxf-generated fill, solid fills are handled + * differently than regular fills + * @return + */ + public String getOOXML(boolean isDxf) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (patternFill != null) + ooxml.append(patternFill.getOOXML(isDxf)); + if (gradientFill != null) + ooxml.append(gradientFill.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Fill(this); + } + + /** + * returns the OOXML specifying the fill based on this FormatHandle object + */ + public static String getOOXML(Xf xf) { + if (xf.getFill() != null) + return xf.getFill().getOOXML(); + + // otherwise, create fill from 2003-style xf + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + try { + ooxml.append(""); + } catch (IndexOutOfBoundsException e) { + ooxml.append(""); // apparently there are less patterns + // in xlsx? some other way of + // storage + } + int fg = xf.getForegroundColor(); + if (fg > -1 && fg != 64) + ooxml.append(""); + int bg = xf.getBackgroundColor(); + if (bg > -1 && bg != 64) + ooxml.append(""); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + return ooxml.toString(); + } + + /** + * returns the OOXML specifying the fill based on fill pattern fs, + * foreground color fg, background color bg + */ + public static String getOOXML(int fs, int fg, int bg) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + try { + ooxml.append(""); + } catch (ArrayIndexOutOfBoundsException e) { + ooxml.append(""); + } + if (fg > -1 && fg != 64) { + ooxml.append(""); + ooxml.append("\r\n"); + } + if (bg > -1 && bg != 64) { + ooxml.append(""); + ooxml.append("\r\n"); + } + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + return ooxml.toString(); + } + + /** + * return the foreground color of this fill, if any + * + * @return + */ + public String getFgColorAsRGB(Theme t) { + if (patternFill != null) + return this.patternFill.getFgColorAsRGB(t); + return null; + } + + /** + * return the fg color in indexed (int) representation + * + * @return + */ + public int getFgColorAsInt(Theme t) { + if (patternFill != null) + return this.patternFill.getFgColorAsInt(t); + return 0; // default= black + } + + /** + * return the bg color of this fill, if any + * + * @return + */ + public String getBgColorAsRGB(Theme t) { + if (patternFill != null) + return this.patternFill.getBgColorAsRGB(t); + return null; + } + + + /** + * return the bg color in indexed (int) representation + * + * @return + */ + public int getBgColorAsInt(Theme t) { + if (patternFill != null) + return this.patternFill.getBgColorAsInt(t); + return -1; + } + + /** + * sets the foreground fill color via color int + * + * @param t + */ + public void setFgColor(int t) { + if (patternFill != null) { + patternFill.setFgColor(t); + return; + } + } + + /** + * sets the foreground fill color to a color string and Excel-2003-mapped + * color int + * + * @param t Excel-2003-mapped color int for the hex color string + * @param colorString hex color string + */ + public void setFgColor(int t, String colorString) { + if (patternFill != null) + patternFill.setFgColor(t, colorString); + else + this.patternFill = new PatternFill("none", t, colorString, -1, null); + } + + /** + * sets the bg fill color via color int + * + * @param t + */ + public void setBgColor(int t) { + if (patternFill != null) { + patternFill.setBgColor(t); + return; + } + } + + /** + * sets the foreground fill color to a color string and Excel-2003-mapped + * color int + * + * @param t Excel-2003-mapped color int for the hex color string + * @param colorString hex color string + */ + public void setBgColor(int t, String colorString) { + if (patternFill != null) + patternFill.setBgColor(t, colorString); + else + this.patternFill = new PatternFill("none", -1, null, t, colorString); + } + + public String getFillPattern() { + if (patternFill != null) + return this.patternFill.getFillPattern(); + return null; + } + + /** + * return the fill pattern in 2003 int representation + * + * @return + */ + public int getFillPatternInt() { + if (patternFill != null) { + return this.patternFill.getFillPatternInt(); + } + return -1; + } + + /** + * sets the fill pattern + */ + public void setFillPattern(int t) { + if (patternFill != null) + this.patternFill.setFillPattern(t); + } + + /** + * returns true if the background pattern is solid + * + * @return + */ + public boolean isBackgroundSolid() { + if (patternFill != null) + return (this.patternFill.getFillPattern().equalsIgnoreCase("solid")); + return false; + } } /** @@ -391,383 +385,376 @@ public boolean isBackgroundSolid() { * for pattern and solid color cell fills. For solid cell fills (no pattern), * fgColor is used. For cell fills with patterns specified, then the cell fill * color is specified by the bgColor element. - * + *

        * parent: fill children: SEQ: fgColor, bgColor - * - * */ class PatternFill implements OOXMLElement { - - private static final long serialVersionUID = -4399355217499895956L; - private String patternType = null; - private FgColor fgColor = null; - private BgColor bgColor = null; - private Theme theme= null; - - public PatternFill(String patternType, FgColor fg, BgColor bg, Theme t) { - this.patternType = patternType; - this.fgColor = fg; - this.bgColor = bg; - this.theme= t; - } - - public PatternFill(PatternFill p) { - this.patternType = p.patternType; - if (p.fgColor != null) - this.fgColor = (FgColor) p.fgColor.cloneElement(); - if (p.bgColor != null) - this.bgColor = (BgColor) p.bgColor.cloneElement(); - this.theme= p.theme; - } - - /** - * create a new pattern fill from external vals - * - * @param patternType - * String OOXML pattern type - * @param fg - * int color index - * @param bg - * int color index - */ - public PatternFill(String patternType, int fg, int bg) { - this.patternType = patternType; - if (fg > -1 && fg != 64) { - HashMap attrs = new HashMap(); - attrs.put( - "rgb", - "FF" + FormatHandle.colorToHexString(FormatHandle.COLORTABLE[fg]).substring(1)); - this.fgColor = new FgColor(attrs); - } - if (bg > -1 && bg != 65) { - HashMap attrs = new HashMap(); - attrs.put( - "rgb", - "FF" - + FormatHandle.colorToHexString( - FormatHandle.COLORTABLE[bg]).substring(1)); - this.bgColor = new BgColor(attrs); - } - } - - public PatternFill(String patternType, int fg, String fgCustom, int bg, - String bgCustom) { - this.patternType = patternType; - if (fg > 0 || fgCustom != null) { // 64= default fg color - HashMap attrs = new HashMap(); - if (fgCustom == null) - attrs.put("indexed", String.valueOf(fg)); - else - attrs.put("rgb", Fill.transformToOOXMLRGBColor(fgCustom)); - this.fgColor = new FgColor(attrs); - } - if (bg > -1 || bgCustom != null) { // 65= default bg color - HashMap attrs = new HashMap(); - if (bgCustom == null) - attrs.put("indexed", String.valueOf(bg)); - else - attrs.put("rgb", Fill.transformToOOXMLRGBColor(bgCustom)); - this.bgColor = new BgColor(attrs); - } - } - - public static PatternFill parseOOXML(XmlPullParser xpp, boolean isDxf, WorkBookHandle bk) { - String patternType = null; // "none"; // default when missing -- so sez - // the doc but doesn't appear what Excel - // does - /** - * APPARENLTY patternFills in dxfs are DIFFERENT AND NOT FOLLOWING THE - * DOCUMENTATION on regular patternFills - * - * APPARENTLY patternType="none" and missing patternType ARE NOT THE - * SAME: if Missing, APPARENTLY means to fill with BG color ("solid" - * means to fill with FG color) - */ - FgColor fg = null; - BgColor bg = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("patternFill")) { // get attributes - if (xpp.getAttributeCount() > 0) - patternType = xpp.getAttributeValue(0); - } else if (tnm.equals("fgColor")) { - fg = (FgColor) FgColor.parseOOXML(xpp); - } else if (tnm.equals("bgColor")) { - bg = (BgColor) BgColor.parseOOXML(xpp); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("patternFill")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("patternFill.parseOOXML: " + e.toString()); - } - if (isDxf) { - if (patternType == null) // null apparently does NOT mean none for - // Dxf's - patternType = "solid"; // see Dxf and Cf handling: null means - // solid fill with bg color as cell - // background - if (patternType.equals("solid")) { - if (bg != null) {// shouldn't! - fg = new FgColor(bg.getAttrs()); // so 2003-v can properly - // set solid cell - // pattern color - } - bg = new BgColor(64); - } - } - PatternFill p = new PatternFill(patternType, fg, bg, bk.getWorkBook().getTheme()); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (fgColor != null) - ooxml.append(fgColor.getOOXML()); - if (bgColor != null) - ooxml.append(bgColor.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - /** - * apparently Fill OOXML from Dxf has differnt syntax - UNDOCUMENTED - * - * @param isDxf - * if this is an Dxf-generated fill, solid fills are handled - * differently than regular fills - * @return - */ - public String getOOXML(boolean isDxf) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (fgColor != null) - ooxml.append(fgColor.getOOXML()); - if (bgColor != null) - ooxml.append(bgColor.getOOXML()); - } else { - if (patternType.equals("solid")) { // dxf needs "none" or "soild" to - // have bg color set, not fg - // color as is normal - ooxml.append(">"); - if (fgColor != null) { // shoudln't! - BgColor tempbg = new BgColor(fgColor.getAttrs()); - ooxml.append(tempbg.getOOXML()); - } - } else { - ooxml.append(" patternType=\"" + patternType + "\">"); - if (fgColor != null) - ooxml.append(fgColor.getOOXML()); - if (bgColor != null) - ooxml.append(bgColor.getOOXML()); - } - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PatternFill(this); - } - - public String toString() { - return ((patternType != null) ? patternType : "") + " fg:" - + ((fgColor != null) ? fgColor.toString() : "") + " bg:" - + ((bgColor != null) ? bgColor.toString() : ""); - } - - /** - * return the pattern type of this Fill in String representation - * - * @return - */ - public String getFillPattern() { - return patternType; - } - - /** - * return the pattern type of this fill pattern in 2003 int representation
        - * BIG NOTE: Apparently doc is wrong in that if patternType is missing it - * does NOT == none; instead, it is a SOLID FILL with BG==fill color
        - * THIS METHOD returns -1 in those cases - * - * @return int pattern fill integer - * @see PatternFill.setFillPattern - */ - public int getFillPatternInt() { - if (patternType == null) // a missing entry *should*==none or 0, but - // apparently is a distinct value in and of - // itself - return OOXMLConstants.patternFill.length + 1; // none --SHOULD BE - // NONE BUT IS - // 1==SOLID???? - for (int i = 0; i < OOXMLConstants.patternFill.length; i++) { - if (OOXMLConstants.patternFill[i].equals(patternType)) - return i; - } - return -1; // none - } - - /** - * sets the pattern type of this Fill
        - * One of:

      • "none",
      • "solid",
      • "mediumGray",
      • "darkGray",
      • - * "lightGray",
      • "darkHorizontal",
      • "darkVertical",
      • "darkDown", - *
      • "darkUp",
      • "darkGrid",
      • "darkTrellis",
      • "lightHorizontal", - *
      • "lightVertical",
      • "lightDown",
      • "lightUp",
      • "lightGrid",
      • - * "lightTrellis",
      • "gray125",
      • "gray0625", - * - * @param s - */ - public void setFillPattern(String s) { - patternType = s; - } - - /** - * sets the pattern type of this fill via a 2003-style pattern int
        - * One of:
      • FLSNULL 0x00 No fill pattern
      • FLSSOLID 0x01 Solid
      • - * FLSMEDGRAY 0x02 50% gray
      • FLSDKGRAY 0x03 75% gray
      • FLSLTGRAY 0x04 - * 25% gray
      • FLSDKHOR 0x05 Horizontal stripe
      • FLSDKVER 0x06 Vertical - * stripe
      • FLSDKDOWN 0x07 Reverse diagonal stripe
      • FLSDKUP 0x08 - * Diagonal stripe
      • FLSDKGRID 0x09 Diagonal crosshatch
      • FLSDKTRELLIS - * 0x0A Thick Diagonal crosshatch
      • FLSLTHOR 0x0B Thin horizontal stripe - *
      • FLSLTVER 0x0C Thin vertical stripe
      • FLSLTDOWN 0x0D Thin reverse - * diagonal stripe
      • FLSLTUP 0x0E Thin diagonal stripe
      • FLSLTGRID 0x0F - * Thin horizontal crosshatch
      • FLSLTTRELLIS 0x10 Thin diagonal crosshatch - *
      • FLSGRAY125 0x11 12.5% gray
      • FLSGRAY0625 0x12 6.25% gray
        - * NOTE: There is a "Special Code" that indicates a missing patternType, - * which has a significant meaning in OOXML - * - * @param t - */ - public void setFillPattern(int t) { - this.patternType = translateIndexedFillPattern(t); - } - - public static String translateIndexedFillPattern(int pattern){ - String newPattern = null; - if (pattern == OOXMLConstants.patternFill.length + 1)// special code - newPattern = null; - else if (pattern >= 0 && pattern < OOXMLConstants.patternFill.length) - newPattern = OOXMLConstants.patternFill[pattern]; - - return newPattern; - } - - /** - * return the foreground color of this fill as an RGB string - * - * @return - */ - public String getFgColorAsRGB(Theme t) { - if (fgColor != null) - return Fill.transformToWebRGBColor(fgColor.getColorAsRGB(t)); - return null; - } - - /** - * return the foreground color of this fill as indexed color int - * - * @return - */ - public int getFgColorAsInt(Theme t) { - if (fgColor != null) - return fgColor.getColorAsInt(t); - if ("solid".equals(patternType)) - return 0; - return -1; - } - - /** - * sets the foreground color of this pattern fill via color int - * - * @param t - */ - public void setFgColor(int t) { - if (fgColor != null) - fgColor.setColor(t); - } - - /** - * sets the foreground fill color to a color string and Excel-2003-mapped - * color int - * - * @param t - * Excel-2003-mapped color int for the hex color string - * @param colorString - * hex color string - */ - public void setFgColor(int t, String colorString) { - if (t > 0 || colorString != null) { // 64= default fg color - HashMap attrs = new HashMap(); - if (colorString == null) - attrs.put("indexed", String.valueOf(t)); - else - attrs.put("rgb", colorString); - this.fgColor = new FgColor(attrs); - } - } - - /** - * return the background color of this fill as an RGB string - * - * @return - */ - public String getBgColorAsRGB(Theme t) { - if (bgColor != null) - return Fill.transformToWebRGBColor(bgColor.getColorAsRGB(t)); - return null; - } - - - /** - * return the background color of this fill as an indexed color int - * - * @return - */ - public int getBgColorAsInt(Theme t) { - if (bgColor != null) - return bgColor.getColorAsInt(t); - return -1; // default=white - } - - /** - * sets the background color of this pattern fill via color int - * - * @param t - */ - public void setBgColor(int t) { - if (bgColor != null) - bgColor.setColor(t); - } - - /** - * sets the background fill color to a color string and Excel-2003-mapped - * color int - * @param t Excel-2003-mapped color int for the hex color string - * @param colorString hex color string - */ - public void setBgColor(int t, String colorString) { - if (t > 0 || colorString != null) { // 65= default bg color - HashMap attrs = new HashMap(); - if (colorString == null) - attrs.put("indexed", String.valueOf(t)); - else - attrs.put("rgb", Fill.transformToOOXMLRGBColor(colorString)); - this.bgColor = new BgColor(attrs); - } - } + + private static final long serialVersionUID = -4399355217499895956L; + private String patternType = null; + private FgColor fgColor = null; + private BgColor bgColor = null; + private Theme theme = null; + + public PatternFill(String patternType, FgColor fg, BgColor bg, Theme t) { + this.patternType = patternType; + this.fgColor = fg; + this.bgColor = bg; + this.theme = t; + } + + public PatternFill(PatternFill p) { + this.patternType = p.patternType; + if (p.fgColor != null) + this.fgColor = (FgColor) p.fgColor.cloneElement(); + if (p.bgColor != null) + this.bgColor = (BgColor) p.bgColor.cloneElement(); + this.theme = p.theme; + } + + /** + * create a new pattern fill from external vals + * + * @param patternType String OOXML pattern type + * @param fg int color index + * @param bg int color index + */ + public PatternFill(String patternType, int fg, int bg) { + this.patternType = patternType; + if (fg > -1 && fg != 64) { + HashMap attrs = new HashMap(); + attrs.put( + "rgb", + "FF" + FormatHandle.colorToHexString(FormatHandle.COLORTABLE[fg]).substring(1)); + this.fgColor = new FgColor(attrs); + } + if (bg > -1 && bg != 65) { + HashMap attrs = new HashMap(); + attrs.put( + "rgb", + "FF" + + FormatHandle.colorToHexString( + FormatHandle.COLORTABLE[bg]).substring(1)); + this.bgColor = new BgColor(attrs); + } + } + + public PatternFill(String patternType, int fg, String fgCustom, int bg, + String bgCustom) { + this.patternType = patternType; + if (fg > 0 || fgCustom != null) { // 64= default fg color + HashMap attrs = new HashMap(); + if (fgCustom == null) + attrs.put("indexed", String.valueOf(fg)); + else + attrs.put("rgb", Fill.transformToOOXMLRGBColor(fgCustom)); + this.fgColor = new FgColor(attrs); + } + if (bg > -1 || bgCustom != null) { // 65= default bg color + HashMap attrs = new HashMap(); + if (bgCustom == null) + attrs.put("indexed", String.valueOf(bg)); + else + attrs.put("rgb", Fill.transformToOOXMLRGBColor(bgCustom)); + this.bgColor = new BgColor(attrs); + } + } + + public static PatternFill parseOOXML(XmlPullParser xpp, boolean isDxf, WorkBookHandle bk) { + String patternType = null; // "none"; // default when missing -- so sez + // the doc but doesn't appear what Excel + // does + /** + * APPARENLTY patternFills in dxfs are DIFFERENT AND NOT FOLLOWING THE + * DOCUMENTATION on regular patternFills + * + * APPARENTLY patternType="none" and missing patternType ARE NOT THE + * SAME: if Missing, APPARENTLY means to fill with BG color ("solid" + * means to fill with FG color) + */ + FgColor fg = null; + BgColor bg = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("patternFill")) { // get attributes + if (xpp.getAttributeCount() > 0) + patternType = xpp.getAttributeValue(0); + } else if (tnm.equals("fgColor")) { + fg = FgColor.parseOOXML(xpp); + } else if (tnm.equals("bgColor")) { + bg = BgColor.parseOOXML(xpp); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("patternFill")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("patternFill.parseOOXML: " + e.toString()); + } + if (isDxf) { + if (patternType == null) // null apparently does NOT mean none for + // Dxf's + patternType = "solid"; // see Dxf and Cf handling: null means + // solid fill with bg color as cell + // background + if (patternType.equals("solid")) { + if (bg != null) {// shouldn't! + fg = new FgColor(bg.getAttrs()); // so 2003-v can properly + // set solid cell + // pattern color + } + bg = new BgColor(64); + } + } + PatternFill p = new PatternFill(patternType, fg, bg, bk.getWorkBook().getTheme()); + return p; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (fgColor != null) + ooxml.append(fgColor.getOOXML()); + if (bgColor != null) + ooxml.append(bgColor.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + /** + * apparently Fill OOXML from Dxf has differnt syntax - UNDOCUMENTED + * + * @param isDxf if this is an Dxf-generated fill, solid fills are handled + * differently than regular fills + * @return + */ + public String getOOXML(boolean isDxf) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (fgColor != null) + ooxml.append(fgColor.getOOXML()); + if (bgColor != null) + ooxml.append(bgColor.getOOXML()); + } else { + if (patternType.equals("solid")) { // dxf needs "none" or "soild" to + // have bg color set, not fg + // color as is normal + ooxml.append(">"); + if (fgColor != null) { // shoudln't! + BgColor tempbg = new BgColor(fgColor.getAttrs()); + ooxml.append(tempbg.getOOXML()); + } + } else { + ooxml.append(" patternType=\"" + patternType + "\">"); + if (fgColor != null) + ooxml.append(fgColor.getOOXML()); + if (bgColor != null) + ooxml.append(bgColor.getOOXML()); + } + } + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new PatternFill(this); + } + + public String toString() { + return ((patternType != null) ? patternType : "") + " fg:" + + ((fgColor != null) ? fgColor.toString() : "") + " bg:" + + ((bgColor != null) ? bgColor.toString() : ""); + } + + /** + * return the pattern type of this Fill in String representation + * + * @return + */ + public String getFillPattern() { + return patternType; + } + + /** + * return the pattern type of this fill pattern in 2003 int representation
        + * BIG NOTE: Apparently doc is wrong in that if patternType is missing it + * does NOT == none; instead, it is a SOLID FILL with BG==fill color
        + * THIS METHOD returns -1 in those cases + * + * @return int pattern fill integer + * @see PatternFill.setFillPattern + */ + public int getFillPatternInt() { + if (patternType == null) // a missing entry *should*==none or 0, but + // apparently is a distinct value in and of + // itself + return OOXMLConstants.patternFill.length + 1; // none --SHOULD BE + // NONE BUT IS + // 1==SOLID???? + for (int i = 0; i < OOXMLConstants.patternFill.length; i++) { + if (OOXMLConstants.patternFill[i].equals(patternType)) + return i; + } + return -1; // none + } + + /** + * sets the pattern type of this Fill
        + * One of:
      • "none",
      • "solid",
      • "mediumGray",
      • "darkGray",
      • + * "lightGray",
      • "darkHorizontal",
      • "darkVertical",
      • "darkDown", + *
      • "darkUp",
      • "darkGrid",
      • "darkTrellis",
      • "lightHorizontal", + *
      • "lightVertical",
      • "lightDown",
      • "lightUp",
      • "lightGrid",
      • + * "lightTrellis",
      • "gray125",
      • "gray0625", + * + * @param s + */ + public void setFillPattern(String s) { + patternType = s; + } + + /** + * sets the pattern type of this fill via a 2003-style pattern int
        + * One of:
      • FLSNULL 0x00 No fill pattern
      • FLSSOLID 0x01 Solid
      • + * FLSMEDGRAY 0x02 50% gray
      • FLSDKGRAY 0x03 75% gray
      • FLSLTGRAY 0x04 + * 25% gray
      • FLSDKHOR 0x05 Horizontal stripe
      • FLSDKVER 0x06 Vertical + * stripe
      • FLSDKDOWN 0x07 Reverse diagonal stripe
      • FLSDKUP 0x08 + * Diagonal stripe
      • FLSDKGRID 0x09 Diagonal crosshatch
      • FLSDKTRELLIS + * 0x0A Thick Diagonal crosshatch
      • FLSLTHOR 0x0B Thin horizontal stripe + *
      • FLSLTVER 0x0C Thin vertical stripe
      • FLSLTDOWN 0x0D Thin reverse + * diagonal stripe
      • FLSLTUP 0x0E Thin diagonal stripe
      • FLSLTGRID 0x0F + * Thin horizontal crosshatch
      • FLSLTTRELLIS 0x10 Thin diagonal crosshatch + *
      • FLSGRAY125 0x11 12.5% gray
      • FLSGRAY0625 0x12 6.25% gray
        + * NOTE: There is a "Special Code" that indicates a missing patternType, + * which has a significant meaning in OOXML + * + * @param t + */ + public void setFillPattern(int t) { + this.patternType = translateIndexedFillPattern(t); + } + + public static String translateIndexedFillPattern(int pattern) { + String newPattern = null; + if (pattern == OOXMLConstants.patternFill.length + 1)// special code + newPattern = null; + else if (pattern >= 0 && pattern < OOXMLConstants.patternFill.length) + newPattern = OOXMLConstants.patternFill[pattern]; + + return newPattern; + } + + /** + * return the foreground color of this fill as an RGB string + * + * @return + */ + public String getFgColorAsRGB(Theme t) { + if (fgColor != null) + return Fill.transformToWebRGBColor(fgColor.getColorAsRGB(t)); + return null; + } + + /** + * return the foreground color of this fill as indexed color int + * + * @return + */ + public int getFgColorAsInt(Theme t) { + if (fgColor != null) + return fgColor.getColorAsInt(t); + if ("solid".equals(patternType)) + return 0; + return -1; + } + + /** + * sets the foreground color of this pattern fill via color int + * + * @param t + */ + public void setFgColor(int t) { + if (fgColor != null) + fgColor.setColor(t); + } + + /** + * sets the foreground fill color to a color string and Excel-2003-mapped + * color int + * + * @param t Excel-2003-mapped color int for the hex color string + * @param colorString hex color string + */ + public void setFgColor(int t, String colorString) { + if (t > 0 || colorString != null) { // 64= default fg color + HashMap attrs = new HashMap(); + if (colorString == null) + attrs.put("indexed", String.valueOf(t)); + else + attrs.put("rgb", colorString); + this.fgColor = new FgColor(attrs); + } + } + + /** + * return the background color of this fill as an RGB string + * + * @return + */ + public String getBgColorAsRGB(Theme t) { + if (bgColor != null) + return Fill.transformToWebRGBColor(bgColor.getColorAsRGB(t)); + return null; + } + + + /** + * return the background color of this fill as an indexed color int + * + * @return + */ + public int getBgColorAsInt(Theme t) { + if (bgColor != null) + return bgColor.getColorAsInt(t); + return -1; // default=white + } + + /** + * sets the background color of this pattern fill via color int + * + * @param t + */ + public void setBgColor(int t) { + if (bgColor != null) + bgColor.setColor(t); + } + + /** + * sets the background fill color to a color string and Excel-2003-mapped + * color int + * + * @param t Excel-2003-mapped color int for the hex color string + * @param colorString hex color string + */ + public void setBgColor(int t, String colorString) { + if (t > 0 || colorString != null) { // 65= default bg color + HashMap attrs = new HashMap(); + if (colorString == null) + attrs.put("indexed", String.valueOf(t)); + else + attrs.put("rgb", Fill.transformToOOXMLRGBColor(colorString)); + this.bgColor = new BgColor(attrs); + } + } } @@ -775,436 +762,425 @@ public void setBgColor(int t, String colorString) { * gradientFill (Gradient) This element defines a gradient-style cell fill. * Gradient cell fills can use one or two colors as the end points of color * interpolation. - * + *

        * parent: fill children: stop (0 or more) attributes: many - * - * */ class GradientFill implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3633230059631047503L; - private HashMap attrs = null; - private ArrayList stops = null; - - public GradientFill(HashMap attrs, ArrayList stops) { - this.attrs = attrs; - this.stops = stops; - } - - public GradientFill(GradientFill g) { - this.attrs = g.attrs; - this.stops = g.stops; - } - - public static GradientFill parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - ArrayList stops = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gradientFill")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), - xpp.getAttributeValue(i)); - } - } else if (tnm.equals("stop")) { - if (stops == null) - stops = new ArrayList(); - stops.add((Stop) Stop.parseOOXML(xpp, bk)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gradientFill")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gradientFill.parseOOXML: " + e.toString()); - } - GradientFill g = new GradientFill(attrs, stops); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (stops != null) { - for (int j = 0; j < stops.size(); j++) - ooxml.append(((Stop) stops.get(j)).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GradientFill(this); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 3633230059631047503L; + private HashMap attrs = null; + private ArrayList stops = null; + + public GradientFill(HashMap attrs, ArrayList stops) { + this.attrs = attrs; + this.stops = stops; + } + + public GradientFill(GradientFill g) { + this.attrs = g.attrs; + this.stops = g.stops; + } + + public static GradientFill parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { + HashMap attrs = new HashMap(); + ArrayList stops = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("gradientFill")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), + xpp.getAttributeValue(i)); + } + } else if (tnm.equals("stop")) { + if (stops == null) + stops = new ArrayList(); + stops.add(Stop.parseOOXML(xpp, bk)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("gradientFill")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("gradientFill.parseOOXML: " + e.toString()); + } + GradientFill g = new GradientFill(attrs, stops); + return g; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (stops != null) { + for (int j = 0; j < stops.size(); j++) + ooxml.append(stops.get(j).getOOXML()); + } + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GradientFill(this); + } } /** * fgColor (Foreground Color) Foreground color of the cell fill pattern. Cell * fill patterns operate with two colors: a background color and a foreground * color. These combine together to make a patterned cell fill - * + *

        * parent: patternFill chilren: none - * - * */ class FgColor implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1274598491373019241L; - private HashMap attrs = null; - - protected FgColor(HashMap attrs) { - this.attrs = attrs; - } - - protected FgColor(FgColor f) { - this.attrs = (HashMap) f.attrs.clone(); - } - - protected FgColor(int c) { - attrs = new HashMap(); - attrs.put("indexed", String.valueOf(c)); - } - - protected HashMap getAttrs() { - return attrs; - } - - protected static FgColor parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fgColor")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fgColor")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fgColor.parseOOXML: " + e.toString()); - } - FgColor f = new FgColor(attrs); - return f; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FgColor(this); - } - - /** - * return the Html string representation of this foreground color - * - * Note that this will return an HTML correct value, such as #000000. - * - * Values set in ooxml need to look like FF000000 - * - * @return - */ - protected String getColorAsRGB(Theme t) { - String val = (String) attrs.get("rgb"); - if (val != null) - return val; - val = (String) attrs.get("indexed"); - if (val != null) { - if (Integer.parseInt(val) == 64) // default fg color - return null; // return "#000000"; //null; - return Color.parseColor(val, Color.COLORTYPEINDEXED,FormatHandle.colorFOREGROUND, t); - } - val = (String) attrs.get("theme"); - if (val != null) - return Color.parseColor(val, Color.COLORTYPETHEME, FormatHandle.colorFOREGROUND, t); - val = (String) attrs.get("auto"); - if (val != null) - return "#000000"; - return null; - } - - /** - * sets the foreground color to the indexed color integer - * - * @param c - */ - protected void setColor(int c) { - attrs.clear(); - attrs.put("indexed", String.valueOf(c)); - } - - /** - * returns the fg color as an indexed color int - */ - protected int getColorAsInt(Theme t) { - String val = (String) attrs.get("auto"); - if (val != null) - return 0; - val = (String) attrs.get("rgb"); - if (val != null) - return Color.parseColorInt(val, Color.COLORTYPERGB, FormatHandle.colorFOREGROUND, t); - val = (String) attrs.get("indexed"); - if (val != null) - return Integer.valueOf(val); - val = (String) attrs.get("theme"); - if (val != null) - return Color.parseColorInt(val, Color.COLORTYPETHEME, FormatHandle.colorFOREGROUND, t); - return -1; - } - - public String toString() { - if (attrs != null) { - String s = attrs.toString(); - return s.substring(1, s.length() - 1); - } - return "none"; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1274598491373019241L; + private HashMap attrs = null; + + protected FgColor(HashMap attrs) { + this.attrs = attrs; + } + + protected FgColor(FgColor f) { + this.attrs = (HashMap) f.attrs.clone(); + } + + protected FgColor(int c) { + attrs = new HashMap(); + attrs.put("indexed", String.valueOf(c)); + } + + protected HashMap getAttrs() { + return attrs; + } + + protected static FgColor parseOOXML(XmlPullParser xpp) { + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("fgColor")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("fgColor")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("fgColor.parseOOXML: " + e.toString()); + } + FgColor f = new FgColor(attrs); + return f; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new FgColor(this); + } + + /** + * return the Html string representation of this foreground color + *

        + * Note that this will return an HTML correct value, such as #000000. + *

        + * Values set in ooxml need to look like FF000000 + * + * @return + */ + protected String getColorAsRGB(Theme t) { + String val = attrs.get("rgb"); + if (val != null) + return val; + val = attrs.get("indexed"); + if (val != null) { + if (Integer.parseInt(val) == 64) // default fg color + return null; // return "#000000"; //null; + return Color.parseColor(val, Color.COLORTYPEINDEXED, FormatHandle.colorFOREGROUND, t); + } + val = attrs.get("theme"); + if (val != null) + return Color.parseColor(val, Color.COLORTYPETHEME, FormatHandle.colorFOREGROUND, t); + val = attrs.get("auto"); + if (val != null) + return "#000000"; + return null; + } + + /** + * sets the foreground color to the indexed color integer + * + * @param c + */ + protected void setColor(int c) { + attrs.clear(); + attrs.put("indexed", String.valueOf(c)); + } + + /** + * returns the fg color as an indexed color int + */ + protected int getColorAsInt(Theme t) { + String val = attrs.get("auto"); + if (val != null) + return 0; + val = attrs.get("rgb"); + if (val != null) + return Color.parseColorInt(val, Color.COLORTYPERGB, FormatHandle.colorFOREGROUND, t); + val = attrs.get("indexed"); + if (val != null) + return Integer.valueOf(val); + val = attrs.get("theme"); + if (val != null) + return Color.parseColorInt(val, Color.COLORTYPETHEME, FormatHandle.colorFOREGROUND, t); + return -1; + } + + public String toString() { + if (attrs != null) { + String s = attrs.toString(); + return s.substring(1, s.length() - 1); + } + return "none"; + } } /** * bgColor (Background Color) Background color of the cell fill pattern. Cell * fill patterns operate with two colors: a background color and a foreground * color. These combine together to make a patterned cell fill. - * + *

        * parent: patternFill children: none - * - * */ class BgColor implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 43028503491956217L; - private HashMap attrs = null; - - protected BgColor(HashMap attrs) { - this.attrs = attrs; - } - - protected BgColor(BgColor f) { - this.attrs = (HashMap) f.attrs.clone(); - } - - protected BgColor(int c) { - attrs = new HashMap(); - attrs.put("indexed", String.valueOf(c)); - } - - protected HashMap getAttrs() { - return attrs; - } - - public static BgColor parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bgColor")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), - xpp.getAttributeValue(i)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("bgColor")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("bgColor.parseOOXML: " + e.toString()); - } - BgColor f = new BgColor(attrs); - return f; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BgColor(this); - } - - /** - * return the Html string representation of this background color - * - * @return - */ - protected String getColorAsRGB(Theme t) { - String val = (String) attrs.get("rgb"); - if (val != null) - return val; - val = (String) attrs.get("indexed"); - if (val != null) { - if (Integer.parseInt(val) == 65) // default bg color - return null; // return "#FFFFFF"; //return null; - return Color.parseColor(val, Color.COLORTYPEINDEXED, FormatHandle.colorBACKGROUND, t); - } - val = (String) attrs.get("theme"); - if (val != null) - return Color.parseColor(val, Color.COLORTYPETHEME, FormatHandle.colorBACKGROUND, t); - return null; - } - - - - /** - * sets the background color to the indexed color integer - * */ - protected void setColor(int c) { - attrs.clear(); - attrs.put("indexed", String.valueOf(c)); - } - - /** - * returns the bg color as an indexed color int - */ - protected int getColorAsInt(Theme t) { - String val = (String) attrs.get("rgb"); - if (val != null) - return Color.parseColorInt(val, Color.COLORTYPERGB, FormatHandle.colorBACKGROUND, t); - val = (String) attrs.get("indexed"); - if (val != null) - return Integer.valueOf(val); - val = (String) attrs.get("theme"); - if (val != null) - return Color.parseColorInt(val, Color.COLORTYPETHEME, FormatHandle.colorBACKGROUND, t); - return 64; // default - } - - public String toString() { - if (attrs != null) { - String s = attrs.toString(); - return s.substring(1, s.length() - 1); - } - return "none"; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 43028503491956217L; + private HashMap attrs = null; + + protected BgColor(HashMap attrs) { + this.attrs = attrs; + } + + protected BgColor(BgColor f) { + this.attrs = (HashMap) f.attrs.clone(); + } + + protected BgColor(int c) { + attrs = new HashMap(); + attrs.put("indexed", String.valueOf(c)); + } + + protected HashMap getAttrs() { + return attrs; + } + + public static BgColor parseOOXML(XmlPullParser xpp) { + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("bgColor")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) + attrs.put(xpp.getAttributeName(i), + xpp.getAttributeValue(i)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("bgColor")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("bgColor.parseOOXML: " + e.toString()); + } + BgColor f = new BgColor(attrs); + return f; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new BgColor(this); + } + + /** + * return the Html string representation of this background color + * + * @return + */ + protected String getColorAsRGB(Theme t) { + String val = attrs.get("rgb"); + if (val != null) + return val; + val = attrs.get("indexed"); + if (val != null) { + if (Integer.parseInt(val) == 65) // default bg color + return null; // return "#FFFFFF"; //return null; + return Color.parseColor(val, Color.COLORTYPEINDEXED, FormatHandle.colorBACKGROUND, t); + } + val = attrs.get("theme"); + if (val != null) + return Color.parseColor(val, Color.COLORTYPETHEME, FormatHandle.colorBACKGROUND, t); + return null; + } + + + /** + * sets the background color to the indexed color integer + */ + protected void setColor(int c) { + attrs.clear(); + attrs.put("indexed", String.valueOf(c)); + } + + /** + * returns the bg color as an indexed color int + */ + protected int getColorAsInt(Theme t) { + String val = attrs.get("rgb"); + if (val != null) + return Color.parseColorInt(val, Color.COLORTYPERGB, FormatHandle.colorBACKGROUND, t); + val = attrs.get("indexed"); + if (val != null) + return Integer.valueOf(val); + val = attrs.get("theme"); + if (val != null) + return Color.parseColorInt(val, Color.COLORTYPETHEME, FormatHandle.colorBACKGROUND, t); + return 64; // default + } + + public String toString() { + if (attrs != null) { + String s = attrs.toString(); + return s.substring(1, s.length() - 1); + } + return "none"; + } } /** * stop (Gradient Stop) One of a sequence of two or more gradient stops, * constituting this gradient fill. - * + *

        * parent: gradientFill children: color REQ - * - * */ class Stop implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -9215564484103992694L; - private String position = null; - private Color c = null; - - public Stop(String position, Color c) { - this.position = position; - this.c = c; - } - - public Stop(Stop s) { - this.position = s.position; - this.c = s.c; - } - - public static Stop parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - String position = null; - Color c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("stop")) { - position = xpp.getAttributeValue(0); // position= - // REQUIRED - } else if (tnm.equals("color")) { - c = (Color) Color.parseOOXML(xpp, (short) -1, bk) - .cloneElement(); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("stop")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("stop.parseOOXML: " + e.toString()); - } - Stop s = new Stop(position, c); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (c != null) - ooxml.append(c.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Stop(this); - } - - - - + /** + * serialVersionUID + */ + private static final long serialVersionUID = -9215564484103992694L; + private String position = null; + private Color c = null; + + public Stop(String position, Color c) { + this.position = position; + this.c = c; + } + + public Stop(Stop s) { + this.position = s.position; + this.c = s.c; + } + + public static Stop parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { + String position = null; + Color c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("stop")) { + position = xpp.getAttributeValue(0); // position= + // REQUIRED + } else if (tnm.equals("color")) { + c = (Color) Color.parseOOXML(xpp, (short) -1, bk) + .cloneElement(); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("stop")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("stop.parseOOXML: " + e.toString()); + } + Stop s = new Stop(position, c); + return s; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (c != null) + ooxml.append(c.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Stop(this); + } + + } diff --git a/src/main/java/io/starter/formats/OOXML/FillGroup.java b/src/main/java/io/starter/formats/OOXML/FillGroup.java index 4c7fb97..6c8a5e2 100644 --- a/src/main/java/io/starter/formats/OOXML/FillGroup.java +++ b/src/main/java/io/starter/formats/OOXML/FillGroup.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,930 +22,905 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import java.util.HashMap; -import java.util.Iterator; -import java.util.ArrayList; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Stack; /** * FillGroup -- holds ONE OF the Fill-type OOXML/DrawingML Elements: blipFill * gradFill grpFill pattFill solidFill noFill - * + *

        * parents: bgPr, spPr, fmtScheme, endParaRPr, rPr ... - * - * */ // TODO: Handle blip element children // TODO: Handle gradFill element shade properties -public class FillGroup implements OOXMLElement -{ - private static final long serialVersionUID = 8320871291479597945L; - private BlipFill bf; - private GradFill gpf; - private GrpFill grpf; - private PattFill pf; - private SolidFill sf; - - public FillGroup(BlipFill bf, GradFill gpf, GrpFill grpf, PattFill pf, - SolidFill sf) { - this.bf = bf; - this.gpf = gpf; - this.grpf = grpf; - this.pf = pf; - this.sf = sf; - } - - public FillGroup(FillGroup f) { - this.bf = f.bf; - this.gpf = f.gpf; - this.grpf = f.grpf; - this.pf = f.pf; - this.sf = f.sf; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - BlipFill bf = null; - GradFill gpf = null; - GrpFill grpf = null; - PattFill pf = null; - SolidFill sf = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("solidFill")) { - lastTag.push(tnm); - sf = (SolidFill) SolidFill.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("noFill")) { - // do nothing - } else if (tnm.equals("gradFill")) { - lastTag.push(tnm); - gpf = (GradFill) GradFill.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("grpFill")) { - lastTag.push(tnm); - grpf = (GrpFill) GrpFill.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("pattFill")) { - lastTag.push(tnm); - pf = (PattFill) PattFill.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("blipFill")) { - lastTag.push(tnm); - bf = (BlipFill) BlipFill.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("FillGroup.parseOOXML: " + e.toString()); - } - FillGroup f = new FillGroup(bf, gpf, grpf, pf, sf); - return f; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - // CHOICE OF fill - if (sf != null) - ooxml.append(sf.getOOXML()); - else if (gpf != null) - ooxml.append(gpf.getOOXML()); - else if (grpf != null) - ooxml.append(grpf.getOOXML()); - else if (bf != null) - ooxml.append(bf.getOOXML()); - else if (pf != null) - ooxml.append(pf.getOOXML()); - else - // no fill - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FillGroup(this); - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (bf != null) - return bf.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (bf != null) - return bf.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - if (bf == null) - bf = new BlipFill(); - bf.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (bf == null) - bf = new BlipFill(); - bf.setLink(link); - } - - /** - * set the color for this fill - * NOTE: at this time only solid fills may be specified - * - * @param clr - */ - public void setColor(String clr) { - if (bf!=null) - return; - gpf = null; - grpf = null; - pf = null; - sf = new SolidFill(); - sf.setColor(clr); - } - - public int getColor() { - if (sf!=null) - return sf.getColor(); - return -1; - } +public class FillGroup implements OOXMLElement { + private static final long serialVersionUID = 8320871291479597945L; + private BlipFill bf; + private GradFill gpf; + private GrpFill grpf; + private PattFill pf; + private SolidFill sf; + + public FillGroup(BlipFill bf, GradFill gpf, GrpFill grpf, PattFill pf, + SolidFill sf) { + this.bf = bf; + this.gpf = gpf; + this.grpf = grpf; + this.pf = pf; + this.sf = sf; + } + + public FillGroup(FillGroup f) { + this.bf = f.bf; + this.gpf = f.gpf; + this.grpf = f.grpf; + this.pf = f.pf; + this.sf = f.sf; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + BlipFill bf = null; + GradFill gpf = null; + GrpFill grpf = null; + PattFill pf = null; + SolidFill sf = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("solidFill")) { + lastTag.push(tnm); + sf = SolidFill.parseOOXML(xpp, lastTag, bk); + lastTag.pop(); + break; + } else if (tnm.equals("noFill")) { + // do nothing + } else if (tnm.equals("gradFill")) { + lastTag.push(tnm); + gpf = GradFill.parseOOXML(xpp, lastTag, bk); + lastTag.pop(); + break; + } else if (tnm.equals("grpFill")) { + lastTag.push(tnm); + grpf = GrpFill.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } else if (tnm.equals("pattFill")) { + lastTag.push(tnm); + pf = PattFill.parseOOXML(xpp, lastTag, bk); + lastTag.pop(); + break; + } else if (tnm.equals("blipFill")) { + lastTag.push(tnm); + bf = BlipFill.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here + lastTag.pop(); + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("FillGroup.parseOOXML: " + e.toString()); + } + FillGroup f = new FillGroup(bf, gpf, grpf, pf, sf); + return f; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + // CHOICE OF fill + if (sf != null) + ooxml.append(sf.getOOXML()); + else if (gpf != null) + ooxml.append(gpf.getOOXML()); + else if (grpf != null) + ooxml.append(grpf.getOOXML()); + else if (bf != null) + ooxml.append(bf.getOOXML()); + else if (pf != null) + ooxml.append(pf.getOOXML()); + else + // no fill + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new FillGroup(this); + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (bf != null) + return bf.getEmbed(); + return null; + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (bf != null) + return bf.getLink(); + return null; + } + + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + public void setEmbed(String embed) { + if (bf == null) + bf = new BlipFill(); + bf.setEmbed(embed); + } + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + public void setLink(String link) { + if (bf == null) + bf = new BlipFill(); + bf.setLink(link); + } + + /** + * set the color for this fill + * NOTE: at this time only solid fills may be specified + * + * @param clr + */ + public void setColor(String clr) { + if (bf != null) + return; + gpf = null; + grpf = null; + pf = null; + sf = new SolidFill(); + sf.setColor(clr); + } + + public int getColor() { + if (sf != null) + return sf.getColor(); + return -1; + } } /** * gradFill (Gradient Fill) - * + *

        * This element defines a gradient fill. - * + *

        * parents: many children: gsLst, SHADEPROPERTIES, tileRect - * - - * */ // TODO: finish SHADEPROPERTIES class GradFill implements OOXMLElement { - private static final long serialVersionUID = 8965776942160065286L; - private GsLst g; - // private ShadeProps sp= null; - private TileRect tr; - private HashMap attrs; - - public GradFill(GsLst g, /* ShadeProps sp, */TileRect tr, HashMap attrs) { - this.g = g; - // this.sp= sp; - this.tr = tr; - this.attrs = attrs; - } - - public GradFill(GradFill gf) { - this.g = gf.g; - // this.sp= gf.sp; - this.tr = gf.tr; - this.attrs = gf.attrs; - } - - public static GradFill parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - GsLst g = null; - // ShadeProps sp= null; - TileRect tr = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gradFill")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } else if (tnm.equals("tileRect")) { - lastTag.push(tnm); - tr = (TileRect) TileRect.parseOOXML(xpp, lastTag); - } else if (tnm.equals("gsLst")) { - lastTag.push(tnm); - g = (GsLst) GsLst.parseOOXML(xpp, lastTag, bk); - /* - * } else if (tnm.equals("lin") || tnm.equals("path")) { - * lastTag.push(tnm); sp= (shadeProps) - * shadeProps.parseOOXML(xpp, lastTag).clone(); - */ - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gradFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gradFill.parseOOXML: " + e.toString()); - } - GradFill gf = new GradFill(g, /* sp, */tr, attrs); - return gf; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (g != null) - ooxml.append(g.getOOXML()); - // if (sp!=null) ooxml.append(sp.getOOXML()); - if (tr != null) - ooxml.append(tr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GradFill(this); - } + private static final long serialVersionUID = 8965776942160065286L; + private GsLst g; + // private ShadeProps sp= null; + private TileRect tr; + private HashMap attrs; + + public GradFill(GsLst g, /* ShadeProps sp, */TileRect tr, HashMap attrs) { + this.g = g; + // this.sp= sp; + this.tr = tr; + this.attrs = attrs; + } + + public GradFill(GradFill gf) { + this.g = gf.g; + // this.sp= gf.sp; + this.tr = gf.tr; + this.attrs = gf.attrs; + } + + public static GradFill parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + HashMap attrs = new HashMap(); + GsLst g = null; + // ShadeProps sp= null; + TileRect tr = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("gradFill")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp + .getAttributeValue(i)); + } + } else if (tnm.equals("tileRect")) { + lastTag.push(tnm); + tr = TileRect.parseOOXML(xpp, lastTag); + } else if (tnm.equals("gsLst")) { + lastTag.push(tnm); + g = GsLst.parseOOXML(xpp, lastTag, bk); + /* + * } else if (tnm.equals("lin") || tnm.equals("path")) { + * lastTag.push(tnm); sp= (shadeProps) + * shadeProps.parseOOXML(xpp, lastTag).clone(); + */ + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("gradFill")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("gradFill.parseOOXML: " + e.toString()); + } + GradFill gf = new GradFill(g, /* sp, */tr, attrs); + return gf; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (g != null) + ooxml.append(g.getOOXML()); + // if (sp!=null) ooxml.append(sp.getOOXML()); + if (tr != null) + ooxml.append(tr.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GradFill(this); + } } /** * blipFill (Picture Fill) - * + *

        * This element specifies the type of picture fill that the picture object will * have. Because a picture has a picture fill already by default, it is possible * to have two fills specified for a picture object. An example of this is shown * below. - * + *

        * parents: many children: blip, srcRect, FillModeProperties - * - - * */ -class BlipFill implements OOXMLElement{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2030570462677450734L; - private HashMap attrs = null; - private Blip blip = null; - private SrcRect srcRect; - private FillMode fillMode; - private String ns= "xdr"; // default namespace - - public BlipFill() { // no-parameter constructor, set common defaults - srcRect= new SrcRect(); - fillMode= new FillMode(null, false); - } - - public BlipFill(String ns, HashMap attrs, Blip b, SrcRect s, FillMode f) { - this.ns= ns; - this.attrs = attrs; - this.blip = b; - this.srcRect = s; - this.fillMode = f; - } - - public BlipFill(BlipFill bf) { - this.ns= bf.ns; - this.attrs = bf.attrs; - this.blip = bf.blip; - this.srcRect = bf.srcRect; - this.fillMode = bf.fillMode; - } - - public static BlipFill parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - Blip b = null; - SrcRect s = null; - FillMode f = null; - String ns= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("blipFill")) { // get attributes - ns= xpp.getPrefix(); - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } else if (tnm.equals("blip")) { - lastTag.push(tnm); - b = (Blip) Blip.parseOOXML(xpp, lastTag); - } else if (tnm.equals("srcRect")) { - lastTag.push(tnm); - s = (SrcRect) SrcRect.parseOOXML(xpp, lastTag); - } else if (tnm.equals("stretch") || tnm.equals("tile")) { - lastTag.push(tnm); - f = (FillMode) FillMode.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("blipFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("blipFill.parseOOXML: " + e.toString()); - } - BlipFill bf = new BlipFill(ns, attrs, b, s, f); - return bf; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); -if (ns==null) - Logger.logErr("Error: BlipFill Namespace is null"); - ooxml.append("<" + ns + ":blipFill"); - // attributes - if (attrs != null) { - Iterator i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (blip != null) - ooxml.append(blip.getOOXML()); - if (srcRect != null) - ooxml.append(srcRect.getOOXML()); - if (fillMode != null) - ooxml.append(fillMode.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BlipFill(this); - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (blip != null) - return blip.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (blip != null) - return blip.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - if (blip == null) - blip = new Blip(); - blip.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (blip == null) - blip = new Blip(); - blip.setLink(link); - } +class BlipFill implements OOXMLElement { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2030570462677450734L; + private HashMap attrs = null; + private Blip blip = null; + private SrcRect srcRect; + private FillMode fillMode; + private String ns = "xdr"; // default namespace + + public BlipFill() { // no-parameter constructor, set common defaults + srcRect = new SrcRect(); + fillMode = new FillMode(null, false); + } + + public BlipFill(String ns, HashMap attrs, Blip b, SrcRect s, FillMode f) { + this.ns = ns; + this.attrs = attrs; + this.blip = b; + this.srcRect = s; + this.fillMode = f; + } + + public BlipFill(BlipFill bf) { + this.ns = bf.ns; + this.attrs = bf.attrs; + this.blip = bf.blip; + this.srcRect = bf.srcRect; + this.fillMode = bf.fillMode; + } + + public static BlipFill parseOOXML(XmlPullParser xpp, Stack lastTag) { + HashMap attrs = new HashMap(); + Blip b = null; + SrcRect s = null; + FillMode f = null; + String ns = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("blipFill")) { // get attributes + ns = xpp.getPrefix(); + for (int i = 0; i < xpp.getAttributeCount(); i++) + attrs.put(xpp.getAttributeName(i), xpp + .getAttributeValue(i)); + } else if (tnm.equals("blip")) { + lastTag.push(tnm); + b = Blip.parseOOXML(xpp, lastTag); + } else if (tnm.equals("srcRect")) { + lastTag.push(tnm); + s = SrcRect.parseOOXML(xpp, lastTag); + } else if (tnm.equals("stretch") || tnm.equals("tile")) { + lastTag.push(tnm); + f = FillMode.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("blipFill")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("blipFill.parseOOXML: " + e.toString()); + } + BlipFill bf = new BlipFill(ns, attrs, b, s, f); + return bf; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + if (ns == null) + Logger.logErr("Error: BlipFill Namespace is null"); + ooxml.append("<" + ns + ":blipFill"); + // attributes + if (attrs != null) { + Iterator i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + } + ooxml.append(">"); + if (blip != null) + ooxml.append(blip.getOOXML()); + if (srcRect != null) + ooxml.append(srcRect.getOOXML()); + if (fillMode != null) + ooxml.append(fillMode.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new BlipFill(this); + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (blip != null) + return blip.getEmbed(); + return null; + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (blip != null) + return blip.getLink(); + return null; + } + + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + public void setEmbed(String embed) { + if (blip == null) + blip = new Blip(); + blip.setEmbed(embed); + } + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + public void setLink(String link) { + if (blip == null) + blip = new Blip(); + blip.setLink(link); + } } /** * solidFill (Solid Fill) This element specifies a solid color fill. The shape * is filled entirely with the specified color - * + *

        * parents: many children: COLORCHOICE - * - - * */ -class SolidFill implements OOXMLElement{ - - private static final long serialVersionUID = 3341509200573989744L; - private ColorChoice color; - - public SolidFill() { // no-param constructor, set up common defaults - this.color = new ColorChoice(null, new SrgbClr("000000", null), null, null, null); - } - - public SolidFill(ColorChoice c) { - this.color = c; - } - - public SolidFill(SolidFill s) { - this.color = s.color; - } - - /** - * creates a solid fill of a specific color - * @param clr hex color string without # - */ - public SolidFill(String clr) { - if (clr==null) clr= "FFFFFF"; - this.color = new ColorChoice(null, new SrgbClr(clr, null), null, null, null); - } - - public static SolidFill parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("hslClr") || tnm.equals("prstClr") - || tnm.equals("schemeClr") - || tnm.equals("scrgbClr") || tnm.equals("srgbClr") - || tnm.equals("sysClr")) { // get attributes - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk) - .cloneElement(); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("solidFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("solidFill.parseOOXML: " + e.toString()); - } - SolidFill s = new SolidFill(c); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (this.color != null) - ooxml.append(this.color.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SolidFill(this); - } - - /** - * set the color for this solid fill in hex (html) string format - * @param clr - */ - public void setColor(String clr) { - this.color = new ColorChoice(null, new SrgbClr(clr, null), null, null, null); - } - - public int getColor() { - return color.getColor(); - } +class SolidFill implements OOXMLElement { + + private static final long serialVersionUID = 3341509200573989744L; + private ColorChoice color; + + public SolidFill() { // no-param constructor, set up common defaults + this.color = new ColorChoice(null, new SrgbClr("000000", null), null, null, null); + } + + public SolidFill(ColorChoice c) { + this.color = c; + } + + public SolidFill(SolidFill s) { + this.color = s.color; + } + + /** + * creates a solid fill of a specific color + * + * @param clr hex color string without # + */ + public SolidFill(String clr) { + if (clr == null) clr = "FFFFFF"; + this.color = new ColorChoice(null, new SrgbClr(clr, null), null, null, null); + } + + public static SolidFill parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + ColorChoice c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("hslClr") || tnm.equals("prstClr") + || tnm.equals("schemeClr") + || tnm.equals("scrgbClr") || tnm.equals("srgbClr") + || tnm.equals("sysClr")) { // get attributes + lastTag.push(tnm); + c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk) + .cloneElement(); + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("solidFill")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("solidFill.parseOOXML: " + e.toString()); + } + SolidFill s = new SolidFill(c); + return s; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (this.color != null) + ooxml.append(this.color.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new SolidFill(this); + } + + /** + * set the color for this solid fill in hex (html) string format + * + * @param clr + */ + public void setColor(String clr) { + this.color = new ColorChoice(null, new SrgbClr(clr, null), null, null, null); + } + + public int getColor() { + return color.getColor(); + } } /** * pattFill (Pattern Fill) This element specifies a pattern fill. A repeated * pattern is used to fill the object. - * + *

        * parent: many children: bgClr, fgClr - * - - * */ -class PattFill implements OOXMLElement{ - - private static final long serialVersionUID = -1052627959661249692L; - private String prst; - private BgClr bg; - private FgClr fg; - - public PattFill(String prst, BgClr bg, FgClr fg) { - this.prst = prst; - this.bg = bg; - this.fg = fg; - } - - public PattFill(PattFill p) { - this.prst = p.prst; - this.bg = p.bg; - this.fg = p.fg; - } - - public static PattFill parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String prst = null; - BgClr bg = null; - FgClr fg = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pattFill")) { // get attributes - prst = xpp.getAttributeValue(0); - } else if (tnm.equals("bgClr")) { - lastTag.push(tnm); - bg = (BgClr) BgClr.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("fgClr")) { - lastTag.push(tnm); - fg = (FgClr) FgClr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("pattFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pattFill.parseOOXML: " + e.toString()); - } - PattFill p = new PattFill(prst, bg, fg); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (fg != null) - ooxml.append(fg.getOOXML()); - if (bg != null) - ooxml.append(bg.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PattFill(this); - } +class PattFill implements OOXMLElement { + + private static final long serialVersionUID = -1052627959661249692L; + private String prst; + private BgClr bg; + private FgClr fg; + + public PattFill(String prst, BgClr bg, FgClr fg) { + this.prst = prst; + this.bg = bg; + this.fg = fg; + } + + public PattFill(PattFill p) { + this.prst = p.prst; + this.bg = p.bg; + this.fg = p.fg; + } + + public static PattFill parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + String prst = null; + BgClr bg = null; + FgClr fg = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pattFill")) { // get attributes + prst = xpp.getAttributeValue(0); + } else if (tnm.equals("bgClr")) { + lastTag.push(tnm); + bg = BgClr.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("fgClr")) { + lastTag.push(tnm); + fg = FgClr.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("pattFill")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("pattFill.parseOOXML: " + e.toString()); + } + PattFill p = new PattFill(prst, bg, fg); + return p; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (fg != null) + ooxml.append(fg.getOOXML()); + if (bg != null) + ooxml.append(bg.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new PattFill(this); + } } /** * grpFill (Group Fill) This element specifies a group fill. When specified, * this setting indicates that the parent element is part of a group and should * inherit the fill properties of the group. - * + *

        * parent: many children: CT_GROUPFILL: ??? contains nothing? - * - - * */ -class GrpFill implements OOXMLElement{ - private static final long serialVersionUID = 2388879629485740996L; - - public static GrpFill parseOOXML(XmlPullParser xpp, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("grpFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("grpFill.parseOOXML: " + e.toString()); - } - GrpFill g = new GrpFill(); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - // TODO: no attributes or children? - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GrpFill(); - } +class GrpFill implements OOXMLElement { + private static final long serialVersionUID = 2388879629485740996L; + + public static GrpFill parseOOXML(XmlPullParser xpp, Stack lastTag) { + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("grpFill")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("grpFill.parseOOXML: " + e.toString()); + } + GrpFill g = new GrpFill(); + return g; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + // TODO: no attributes or children? + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GrpFill(); + } } /** * bgClr background color - * + *

        * parent: pattFill - * - - * */ class BgClr implements OOXMLElement { - private static final long serialVersionUID = -879409152334931909L; - private ColorChoice colorChoice; - - - public BgClr(ColorChoice c) { - this.colorChoice = c; - } - - public BgClr(BgClr s) { - this.colorChoice = s.colorChoice; - } - - public static BgClr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("hslClr") || tnm.equals("prstClr") - || tnm.equals("schemeClr") - || tnm.equals("scrgbClr") || tnm.equals("srgbClr") - || tnm.equals("sysClr")) { // get attributes - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("bgClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("bgClr.parseOOXML: " + e.toString()); - } - BgClr s = new BgClr(c); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (this.colorChoice != null) - ooxml.append(this.colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BgClr(this); - } + private static final long serialVersionUID = -879409152334931909L; + private ColorChoice colorChoice; + + + public BgClr(ColorChoice c) { + this.colorChoice = c; + } + + public BgClr(BgClr s) { + this.colorChoice = s.colorChoice; + } + + public static BgClr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + ColorChoice c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("hslClr") || tnm.equals("prstClr") + || tnm.equals("schemeClr") + || tnm.equals("scrgbClr") || tnm.equals("srgbClr") + || tnm.equals("sysClr")) { // get attributes + lastTag.push(tnm); + c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); + + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("bgClr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("bgClr.parseOOXML: " + e.toString()); + } + BgClr s = new BgClr(c); + return s; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (this.colorChoice != null) + ooxml.append(this.colorChoice.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new BgClr(this); + } } /** * fgClr Foreground Color - * + *

        * parent: pattFill - * - - * */ -class FgClr implements OOXMLElement -{ - private static final long serialVersionUID = 6836994790529289731L; - private ColorChoice colorChoice; - - public FgClr(ColorChoice c) { - this.colorChoice = c; - } - - public FgClr(FgClr s) { - this.colorChoice = s.colorChoice; - } - - public static FgClr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("hslClr") || tnm.equals("prstClr") - || tnm.equals("schemeClr") - || tnm.equals("scrgbClr") || tnm.equals("srgbClr") - || tnm.equals("sysClr")) { // get attributes - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fgClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fgClr.parseOOXML: " + e.toString()); - } - FgClr s = new FgClr(c); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (this.colorChoice != null) - ooxml.append(this.colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FgClr(this); - } +class FgClr implements OOXMLElement { + private static final long serialVersionUID = 6836994790529289731L; + private ColorChoice colorChoice; + + public FgClr(ColorChoice c) { + this.colorChoice = c; + } + + public FgClr(FgClr s) { + this.colorChoice = s.colorChoice; + } + + public static FgClr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + ColorChoice c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("hslClr") || tnm.equals("prstClr") + || tnm.equals("schemeClr") + || tnm.equals("scrgbClr") || tnm.equals("srgbClr") + || tnm.equals("sysClr")) { // get attributes + lastTag.push(tnm); + c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("fgClr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("fgClr.parseOOXML: " + e.toString()); + } + FgClr s = new FgClr(c); + return s; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (this.colorChoice != null) + ooxml.append(this.colorChoice.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new FgClr(this); + } } /** * blip (Blip) This element specifies the existence of an image (binary large * image or picture) and contains a reference to the image data. - * + *

        * parent: blipFill, buFill children: MANY - * */ // TODO: HANDLE THE MANY CHILDREN -class Blip implements OOXMLElement{ - - private static final long serialVersionUID = 5188967633123620513L; - private HashMap attrs = new HashMap(); - - public Blip() { - } - - public Blip(HashMap attrs) { - this.attrs = attrs; - } - - public Blip(Blip b) { - this.attrs = b.attrs; - } - - public static Blip parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("blip")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - if (xpp.getAttributePrefix(i) != null) - attrs.put(xpp.getAttributePrefix(i) + ":" - + xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - else - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - // } else if (tnm.equals("CHILDELEMENT")) { - // lastTag.push(tnm); - // layout = (layout) layout.parseOOXML(xpp, - // lastTag).clone(); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("blip")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("blip.parseOOXML: " + e.toString()); - } - Blip b = new Blip(attrs); - return b; - } - - // TODO: cstate= "print" - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - // TODO: HANDLE CHILDREN - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Blip(this); - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (attrs != null && attrs.get("r:embed") != null) { - return (String) attrs.get("r:embed"); - } - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (attrs != null && attrs.get("link") != null) { - return (String) attrs.get("link"); - } - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - attrs.put("r:embed", embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - attrs.put("link", link); - } +class Blip implements OOXMLElement { + + private static final long serialVersionUID = 5188967633123620513L; + private HashMap attrs = new HashMap(); + + public Blip() { + } + + public Blip(HashMap attrs) { + this.attrs = attrs; + } + + public Blip(Blip b) { + this.attrs = b.attrs; + } + + public static Blip parseOOXML(XmlPullParser xpp, Stack lastTag) { + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("blip")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + if (xpp.getAttributePrefix(i) != null) + attrs.put(xpp.getAttributePrefix(i) + ":" + + xpp.getAttributeName(i), xpp + .getAttributeValue(i)); + else + attrs.put(xpp.getAttributeName(i), xpp + .getAttributeValue(i)); + } + // } else if (tnm.equals("CHILDELEMENT")) { + // lastTag.push(tnm); + // layout = (layout) layout.parseOOXML(xpp, + // lastTag).clone(); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("blip")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("blip.parseOOXML: " + e.toString()); + } + Blip b = new Blip(attrs); + return b; + } + + // TODO: cstate= "print" + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + // TODO: HANDLE CHILDREN + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Blip(this); + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (attrs != null && attrs.get("r:embed") != null) { + return attrs.get("r:embed"); + } + return null; + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (attrs != null && attrs.get("link") != null) { + return attrs.get("link"); + } + return null; + } + + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + public void setEmbed(String embed) { + attrs.put("r:embed", embed); + } + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + public void setLink(String link) { + attrs.put("link", link); + } } /** @@ -954,217 +929,210 @@ public void setLink(String link) { * remaining area of the shape to complete the fill. The tile rectangle is * defined by percentage offsets from the sides of the shape's bounding box. * parent: gradFill children: none - * - - * */ -class TileRect implements OOXMLElement{ - - private static final long serialVersionUID = 5380575948049571420L; - private HashMap attrs; - - public TileRect(HashMap attrs) { - this.attrs = attrs; - } - - public TileRect(TileRect t) { - this.attrs = t.attrs; - } - - public static TileRect parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tileRect")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("tileRect")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("tileRect.parseOOXML: " + e.toString()); - } - TileRect t = new TileRect(attrs); - return t; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TileRect(this); - } +class TileRect implements OOXMLElement { + + private static final long serialVersionUID = 5380575948049571420L; + private HashMap attrs; + + public TileRect(HashMap attrs) { + this.attrs = attrs; + } + + public TileRect(TileRect t) { + this.attrs = t.attrs; + } + + public static TileRect parseOOXML(XmlPullParser xpp, Stack lastTag) { + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("tileRect")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) + attrs.put(xpp.getAttributeName(i), xpp + .getAttributeValue(i)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("tileRect")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("tileRect.parseOOXML: " + e.toString()); + } + TileRect t = new TileRect(attrs); + return t; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new TileRect(this); + } } /** * srcRect (Source Rectangle) This element specifies the portion of the blip * used for the fill - * + *

        * parent: blipFill children: NONE - * */ class SrcRect implements OOXMLElement { - private static final long serialVersionUID = -6407800173040857433L; - private HashMap attrs = null; - - public SrcRect() { - } - - public SrcRect(HashMap attrs) { - this.attrs = attrs; - } - - public SrcRect(SrcRect s) { - this.attrs = s.attrs; - } - - public static SrcRect parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("srcRect")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("srcRect")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("srcRect.parseOOXML: " + e.toString()); - } - SrcRect s = new SrcRect(attrs); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SrcRect(this); - } + private static final long serialVersionUID = -6407800173040857433L; + private HashMap attrs = null; + + public SrcRect() { + } + + public SrcRect(HashMap attrs) { + this.attrs = attrs; + } + + public SrcRect(SrcRect s) { + this.attrs = s.attrs; + } + + public static SrcRect parseOOXML(XmlPullParser xpp, Stack lastTag) { + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("srcRect")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp + .getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("srcRect")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("srcRect.parseOOXML: " + e.toString()); + } + SrcRect s = new SrcRect(attrs); + return s; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new SrcRect(this); + } } /** * fillRect (Fill Rectangle) This element specifies a fill rectangle. When * stretching of an image is specified, a source rectangle, srcRect, is scaled * to fit the specified fill rectangle. parent: stretch children: NONE - * - - * */ class FillRect implements OOXMLElement { - private static final long serialVersionUID = 7200764163180402065L; - private HashMap attrs = null; - - public FillRect() { - } - - public FillRect(HashMap attrs) { - this.attrs = attrs; - } - - public FillRect(FillRect f) { - this.attrs = f.attrs; - } - - public static FillRect parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fillRect")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fillRect")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fillRect.parseOOXML: " + e.toString()); - } - FillRect oe = new FillRect(); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FillRect(this); - } + private static final long serialVersionUID = 7200764163180402065L; + private HashMap attrs = null; + + public FillRect() { + } + + public FillRect(HashMap attrs) { + this.attrs = attrs; + } + + public FillRect(FillRect f) { + this.attrs = f.attrs; + } + + public static FillRect parseOOXML(XmlPullParser xpp, Stack lastTag) { + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("fillRect")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) + attrs.put(xpp.getAttributeName(i), xpp + .getAttributeValue(i)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("fillRect")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("fillRect.parseOOXML: " + e.toString()); + } + FillRect oe = new FillRect(); + return oe; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = (String) i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new FillRect(this); + } } /** * FillMode Choice of either tile or stretch elements - * + *

        * tile (Tile) This element specifies that a BLIP should be tiled to fill the * available space. This element defines a "tile" rectangle within the bounding * box. The image is encompassed within the tile rectangle, and the tile @@ -1172,233 +1140,224 @@ public OOXMLElement cloneElement() { * (Stretch) This element specifies that a BLIP should be stretched to fill the * target rectangle. The other option is a tile where a BLIP is tiled to fill * the available area. - * + *

        * parent: blipFill choice of : stretch or tile - * - - * */ class FillMode implements OOXMLElement { - private static final long serialVersionUID = 967269629502516244L; - // Since both "child" elements are so small, just output OOXML "by hand" - private HashMap attrs = null; - private boolean tile = false; - - public FillMode() { - } - - public FillMode(HashMap attrs, boolean tile) { - this.attrs = attrs; - this.tile = tile; - } - - public FillMode(FillMode f) { - this.attrs = f.attrs; - this.tile = f.tile; - } - - public static FillMode parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - boolean tile = false; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tile")) { - tile = true; - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } else if (tnm.equals("fillRect")) { // only child of - // stretch - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("tile") || endTag.equals("stretch")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("FillMode.parseOOXML: " + e.toString()); - } - FillMode fm = new FillMode(attrs, tile); - return fm; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - // Since both "child" elements are so small, just output OOXML "by hand" - if (this.tile) - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - if (this.tile) - ooxml.append("/>"); - else - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FillMode(this); - } + private static final long serialVersionUID = 967269629502516244L; + // Since both "child" elements are so small, just output OOXML "by hand" + private HashMap attrs = null; + private boolean tile = false; + + public FillMode() { + } + + public FillMode(HashMap attrs, boolean tile) { + this.attrs = attrs; + this.tile = tile; + } + + public FillMode(FillMode f) { + this.attrs = f.attrs; + this.tile = f.tile; + } + + public static FillMode parseOOXML(XmlPullParser xpp, Stack lastTag) { + HashMap attrs = new HashMap(); + boolean tile = false; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("tile")) { + tile = true; + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp + .getAttributeValue(i)); + } + } else if (tnm.equals("fillRect")) { // only child of + // stretch + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp + .getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("tile") || endTag.equals("stretch")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("FillMode.parseOOXML: " + e.toString()); + } + FillMode fm = new FillMode(attrs, tile); + return fm; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + // Since both "child" elements are so small, just output OOXML "by hand" + if (this.tile) + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + } + if (this.tile) + ooxml.append("/>"); + else + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new FillMode(this); + } } /** * gsLst (Gradient Stop List) The list of gradient stops that specifies the * gradient colors and their relative positions in the color band. - * + *

        * parent: gradFill children: gs - * - - * */ -class GsLst implements OOXMLElement{ - - private static final long serialVersionUID = 6576320251327916221L; - private ArrayList gs; - - public GsLst(ArrayList g) { - this.gs = g; - } - - public GsLst(GsLst gl) { - this.gs = gl.gs; - } - - public static GsLst parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - ArrayList g = new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gs")) { - lastTag.push(tnm); - g.add((Gs) Gs.parseOOXML(xpp, lastTag, bk)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gsLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gsLst.parseOOXML: " + e.toString()); - } - GsLst gl = new GsLst(g); - return gl; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - for (int i = 0; i < gs.size(); i++) { - ooxml.append(((Gs) gs.get(i)).getOOXML()); - } - ooxml.append(""); - } else - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GsLst(this); - } +class GsLst implements OOXMLElement { + + private static final long serialVersionUID = 6576320251327916221L; + private ArrayList gs; + + public GsLst(ArrayList g) { + this.gs = g; + } + + public GsLst(GsLst gl) { + this.gs = gl.gs; + } + + public static GsLst parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + ArrayList g = new ArrayList(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("gs")) { + lastTag.push(tnm); + g.add(Gs.parseOOXML(xpp, lastTag, bk)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("gsLst")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("gsLst.parseOOXML: " + e.toString()); + } + GsLst gl = new GsLst(g); + return gl; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + for (int i = 0; i < gs.size(); i++) { + ooxml.append(gs.get(i).getOOXML()); + } + ooxml.append(""); + } else + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GsLst(this); + } } /** * gs (Gradient stops) This element defines a gradient stop. A gradient stop * consists of a position where the stop appears in the color band. - * + *

        * parent: gsLst children: COLORCHOICE - * - - * */ class Gs implements OOXMLElement { - private static final long serialVersionUID = 7626866241477598159L; - private String pos = null; - private ColorChoice colorChoice = null; - - public Gs(String pos, ColorChoice c) { - this.pos = pos; - this.colorChoice = c; - } - - public Gs(Gs g) { - this.pos = g.pos; - this.colorChoice = g.colorChoice; - } - - public static Gs parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String pos = null; - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gs")) { // get attributes - pos = xpp.getAttributeValue(0); - } else if (tnm.equals("hslClr") || tnm.equals("prstClr") - || tnm.equals("schemeClr") - || tnm.equals("scrgbClr") || tnm.equals("srgbClr") - || tnm.equals("sysClr")) { // get attributes - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gs")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gs.parseOOXML: " + e.toString()); - } - Gs g = new Gs(pos, c); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Gs(this); - } + private static final long serialVersionUID = 7626866241477598159L; + private String pos = null; + private ColorChoice colorChoice = null; + + public Gs(String pos, ColorChoice c) { + this.pos = pos; + this.colorChoice = c; + } + + public Gs(Gs g) { + this.pos = g.pos; + this.colorChoice = g.colorChoice; + } + + public static Gs parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + String pos = null; + ColorChoice c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("gs")) { // get attributes + pos = xpp.getAttributeValue(0); + } else if (tnm.equals("hslClr") || tnm.equals("prstClr") + || tnm.equals("schemeClr") + || tnm.equals("scrgbClr") || tnm.equals("srgbClr") + || tnm.equals("sysClr")) { // get attributes + lastTag.push(tnm); + c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("gs")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("gs.parseOOXML: " + e.toString()); + } + Gs g = new Gs(pos, c); + return g; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + ooxml.append(colorChoice.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Gs(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Gd.java b/src/main/java/io/starter/formats/OOXML/Gd.java index d9882c1..4f99263 100644 --- a/src/main/java/io/starter/formats/OOXML/Gd.java +++ b/src/main/java/io/starter/formats/OOXML/Gd.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,81 +22,80 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import java.util.HashMap; -import java.util.Iterator; - +import io.starter.toolkit.Logger; import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Stack; /** * gd (Shape Guide) - * + *

        * This element specifies the presence of a shape guide that will be used to govern the geometry of the specified * shape. A shape guide consists of a formula and a name that the result of the formula is assigned to. Recognized * formulas are listed with the fmla attribute documentation for this element. - * + *

        * parents: avLst, gdLst * children: none - * */ -public class Gd implements OOXMLElement{ - - private static final long serialVersionUID = -633176234309521998L; - private HashMap attrs= null; - - public Gd(HashMap attrs) { - this.attrs= attrs; - } - public Gd(Gd g) { - this.attrs= g.attrs; - } - +public class Gd implements OOXMLElement { + + private static final long serialVersionUID = -633176234309521998L; + private HashMap attrs = null; + + public Gd(HashMap attrs) { + this.attrs = attrs; + } + + public Gd(Gd g) { + this.attrs = g.attrs; + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gd")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("gd")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gd.parseOOXML: " + e.toString()); - } - Gd g= new Gd(attrs); - return g; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("gd")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("gd")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("gd.parseOOXML: " + e.toString()); + } + Gd g = new Gd(attrs); + return g; } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new Gd(this); - } + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Gd(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/GeomGroup.java b/src/main/java/io/starter/formats/OOXML/GeomGroup.java index be3fa55..4ea05be 100644 --- a/src/main/java/io/starter/formats/OOXML/GeomGroup.java +++ b/src/main/java/io/starter/formats/OOXML/GeomGroup.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,146 +22,147 @@ */ package io.starter.formats.OOXML; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.ArrayList; -import java.util.Stack; import java.util.HashMap; import java.util.Iterator; - -import org.xmlpull.v1.XmlPullParser; - -import io.starter.toolkit.Logger; +import java.util.Stack; /** * Geometry Group: one of prstGeom (Preset shapes) or custGeom (custom shapes) - * */ //TODO: Finish custGeom child elements ahLst, cxnLst, rect (+ finish path element) -public class GeomGroup implements OOXMLElement{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7202561792070909825L; - private PrstGeom p= null; - private CustGeom c= null; - public GeomGroup() { - } - public GeomGroup(PrstGeom p, CustGeom c) { - this.p= p; - this.c= c; - } - public GeomGroup(GeomGroup g) { - this.p= g.p; - this.c= g.c; - } - +public class GeomGroup implements OOXMLElement { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7202561792070909825L; + private PrstGeom p = null; + private CustGeom c = null; + + public GeomGroup() { + } + + public GeomGroup(PrstGeom p, CustGeom c) { + this.p = p; + this.c = c; + } + + public GeomGroup(GeomGroup g) { + this.p = g.p; + this.c = g.c; + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - PrstGeom p= null; - CustGeom c= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstGeom")) { - lastTag.push(tnm); - p= (PrstGeom) PrstGeom.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("custGeom")) { - lastTag.push(tnm); - c= (CustGeom) CustGeom.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } - } else if (eventType== XmlPullParser.END_TAG) { // shouldn't get here - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("GeomGroup.parseOOXML: " + e.toString()); - } - GeomGroup g= new GeomGroup(p, c); - return g; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - if (this.p!=null) ooxml.append(p.getOOXML()); - else if (this.c!=null) ooxml.append(c.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GeomGroup(this); - } - + PrstGeom p = null; + CustGeom c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("prstGeom")) { + lastTag.push(tnm); + p = PrstGeom.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } else if (tnm.equals("custGeom")) { + lastTag.push(tnm); + c = CustGeom.parseOOXML(xpp, lastTag); + lastTag.pop(); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here + lastTag.pop(); + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("GeomGroup.parseOOXML: " + e.toString()); + } + GeomGroup g = new GeomGroup(p, c); + return g; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + if (this.p != null) ooxml.append(p.getOOXML()); + else if (this.c != null) ooxml.append(c.getOOXML()); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GeomGroup(this); + } + } - + class PrstGeom implements OOXMLElement { - private static final long serialVersionUID = 8327708502983472577L; - private String prst= null; - private AvLst avLst= null; - - public PrstGeom() { // no-param constructor, set up common defaults - prst= "rect"; - avLst= new AvLst(); - } - - public PrstGeom(String prst, AvLst a) { - this.prst= prst; - this.avLst= a; - } - - public PrstGeom(PrstGeom p) { - this.prst= p.prst; - this.avLst= p.avLst; - } - + private static final long serialVersionUID = 8327708502983472577L; + private String prst = null; + private AvLst avLst = null; + + public PrstGeom() { // no-param constructor, set up common defaults + prst = "rect"; + avLst = new AvLst(); + } + + public PrstGeom(String prst, AvLst a) { + this.prst = prst; + this.avLst = a; + } + + public PrstGeom(PrstGeom p) { + this.prst = p.prst; + this.avLst = p.avLst; + } + public static PrstGeom parseOOXML(XmlPullParser xpp, Stack lastTag) { - String prst= null; - AvLst a= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstGeom")) { // get attributes - prst= xpp.getAttributeValue(0); - } else if (tnm.equals("avLst")) { - lastTag.push(tnm); - a= (AvLst) AvLst.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("prstGeom")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("prstGeom.parseOOXML: " + e.toString()); - } - PrstGeom p= new PrstGeom(prst, a); - return p; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (avLst!=null) ooxml.append(avLst.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PrstGeom(this); - } - + String prst = null; + AvLst a = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("prstGeom")) { // get attributes + prst = xpp.getAttributeValue(0); + } else if (tnm.equals("avLst")) { + lastTag.push(tnm); + a = (AvLst) AvLst.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("prstGeom")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("prstGeom.parseOOXML: " + e.toString()); + } + PrstGeom p = new PrstGeom(prst, a); + return p; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (avLst != null) ooxml.append(avLst.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new PrstGeom(this); + } + } @@ -170,397 +171,397 @@ public OOXMLElement cloneElement() { * This element specifies the existence of a custom geometric shape. This shape will consist of a series of lines and * curves described within a creation path. In addition to this there may also be adjust values, guides, adjust * handles, connection sites and an inscribed rectangle specified for this custom geometric shape. - * + *

        * parent: soPr * children: avLst, gdLst, ahLst, cxnLst, rect, pathLst (REQ) - * */ // TODO: Finish child elements ahLst, cxnLst class CustGeom implements OOXMLElement { - private static final long serialVersionUID = 4036207867619551810L; - private PathLst pathLst; - private GdLst gdLst; + private static final long serialVersionUID = 4036207867619551810L; + private PathLst pathLst; + private GdLst gdLst; private AvLst avLst; - private CxnLst cxnLst; + private CxnLst cxnLst; private Rect rect; - public CustGeom(PathLst p, GdLst g, AvLst a, CxnLst cx, Rect r) { - this.pathLst= p; - this.gdLst= g; - this.avLst= a; - this.cxnLst= cx; - this.rect= r; - } - public CustGeom(CustGeom c) { - this.pathLst= c.pathLst; - this.gdLst= c.gdLst; - this.avLst= c.avLst; - this.cxnLst= c.cxnLst; - this.rect= c.rect; - } - + public CustGeom(PathLst p, GdLst g, AvLst a, CxnLst cx, Rect r) { + this.pathLst = p; + this.gdLst = g; + this.avLst = a; + this.cxnLst = cx; + this.rect = r; + } + + public CustGeom(CustGeom c) { + this.pathLst = c.pathLst; + this.gdLst = c.gdLst; + this.avLst = c.avLst; + this.cxnLst = c.cxnLst; + this.rect = c.rect; + } + public static CustGeom parseOOXML(XmlPullParser xpp, Stack lastTag) { - PathLst p= null; - GdLst g= null; - AvLst a= null; - CxnLst cx= null; - Rect r= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pathLst")) { // REQ - lastTag.push(tnm); - p= (PathLst) PathLst.parseOOXML(xpp, lastTag); - } else if (tnm.equals("gdLst")) { - lastTag.push(tnm); - g= (GdLst) GdLst.parseOOXML(xpp, lastTag); - } else if (tnm.equals("avLst")) { - lastTag.push(tnm); - a= (AvLst) AvLst.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cxnLst")) { - lastTag.push(tnm); - cx= (CxnLst) CxnLst.parseOOXML(xpp, lastTag); - } else if (tnm.equals("rect")) { - lastTag.push(tnm); - r= (Rect) Rect.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("custGeom")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("custGeom.parseOOXML: " + e.toString()); - } - CustGeom c= new CustGeom(p, g, a, cx, r); - return c; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (avLst!=null) ooxml.append(avLst.getOOXML()); // avLst - if (gdLst!=null) ooxml.append(gdLst.getOOXML()); // gdLst - // TODO: ahLst - ooxml.append(""); - if (cxnLst!=null) ooxml.append(cxnLst.getOOXML()); // cxnLst - if (rect!=null) ooxml.append(rect.getOOXML()); // rect - if (pathLst!=null) ooxml.append(pathLst.getOOXML()); // pathLst - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CustGeom(this); - } + PathLst p = null; + GdLst g = null; + AvLst a = null; + CxnLst cx = null; + Rect r = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pathLst")) { // REQ + lastTag.push(tnm); + p = PathLst.parseOOXML(xpp, lastTag); + } else if (tnm.equals("gdLst")) { + lastTag.push(tnm); + g = GdLst.parseOOXML(xpp, lastTag); + } else if (tnm.equals("avLst")) { + lastTag.push(tnm); + a = (AvLst) AvLst.parseOOXML(xpp, lastTag); + } else if (tnm.equals("cxnLst")) { + lastTag.push(tnm); + cx = CxnLst.parseOOXML(xpp, lastTag); + } else if (tnm.equals("rect")) { + lastTag.push(tnm); + r = Rect.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("custGeom")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("custGeom.parseOOXML: " + e.toString()); + } + CustGeom c = new CustGeom(p, g, a, cx, r); + return c; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (avLst != null) ooxml.append(avLst.getOOXML()); // avLst + if (gdLst != null) ooxml.append(gdLst.getOOXML()); // gdLst + // TODO: ahLst + ooxml.append(""); + if (cxnLst != null) ooxml.append(cxnLst.getOOXML()); // cxnLst + if (rect != null) ooxml.append(rect.getOOXML()); // rect + if (pathLst != null) ooxml.append(pathLst.getOOXML()); // pathLst + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CustGeom(this); + } } /** * pathLst (List of Shape Paths) * This element specifies the entire path that is to make up a single geometric shape. The pathLst can consist of * many individual paths within it. - * + *

        * parent: custGeom * children path (multiple) - * */ -class PathLst implements OOXMLElement{ - - private static final long serialVersionUID = -1996347204024728000L; - private ArrayList path; - private HashMap attrs= null; - - - public PathLst(HashMap attrs, ArrayList p) { - this.attrs= attrs; - this.path= p; - } - public PathLst(PathLst pl) { - this.attrs= pl.attrs; - this.path= pl.path; - } - - +class PathLst implements OOXMLElement { + + private static final long serialVersionUID = -1996347204024728000L; + private ArrayList path; + private HashMap attrs = null; + + + public PathLst(HashMap attrs, ArrayList p) { + this.attrs = attrs; + this.path = p; + } + + public PathLst(PathLst pl) { + this.attrs = pl.attrs; + this.path = pl.path; + } + + public static PathLst parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - ArrayList p= new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pathLst")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } else if (tnm.equals("path")) { // get one or more path children - lastTag.push(tnm); - p.add((Path) Path.parseOOXML(xpp, lastTag)); - - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("pathLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pathLst.parseOOXML: " + e.toString()); - } - PathLst pl= new PathLst(attrs, p); - return pl; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" ii= attrs.keySet().iterator(); - while (ii.hasNext()) { - String key= (String) ii.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (path!=null) { - for (int i= 0; i < path.size(); i++) - ooxml.append(((Path)path.get(i)).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PathLst(this); - } + HashMap attrs = new HashMap(); + ArrayList p = new ArrayList(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pathLst")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } else if (tnm.equals("path")) { // get one or more path children + lastTag.push(tnm); + p.add(Path.parseOOXML(xpp, lastTag)); + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("pathLst")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("pathLst.parseOOXML: " + e.toString()); + } + PathLst pl = new PathLst(attrs, p); + return pl; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" ii = attrs.keySet().iterator(); + while (ii.hasNext()) { + String key = ii.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (path != null) { + for (int i = 0; i < path.size(); i++) + ooxml.append(path.get(i).getOOXML()); + } + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new PathLst(this); + } } /** - * * */ -class Path implements OOXMLElement{ - - private static final long serialVersionUID = 6906237439620322589L; - private HashMap attrs= null; - - public Path(HashMap attrs){ - this.attrs= attrs; - } - public Path(Path p) { - this.attrs= p.attrs; - } - - +class Path implements OOXMLElement { + + private static final long serialVersionUID = 6906237439620322589L; + private HashMap attrs = null; + + public Path(HashMap attrs) { + this.attrs = attrs; + } + + public Path(Path p) { + this.attrs = p.attrs; + } + + public static Path parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("path")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("moveTo")) { - } else if (tnm.equals("lnTo")) { - } else if (tnm.equals("close")) { - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("path")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("path.parseOOXML: " + e.toString()); - } - Path p= new Path(attrs); - return p; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Path(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("path")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("moveTo")) { + } else if (tnm.equals("lnTo")) { + } else if (tnm.equals("close")) { + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("path")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("path.parseOOXML: " + e.toString()); + } + Path p = new Path(attrs); + return p; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Path(this); + } } /** * gdLst (List of Shape Guides) * This element specifies all the guides that will be used for this shape. A guide is specified by the gd element and * defines a calculated value that may be used for the construction of the corresponding shape. - * - * */ class GdLst implements OOXMLElement { - private static final long serialVersionUID = -7852193131141462744L; - private ArrayList gds; - - public GdLst(ArrayList gds) { - this.gds= gds; - } - public GdLst(GdLst g) { - this.gds= g.gds; - } - - + private static final long serialVersionUID = -7852193131141462744L; + private ArrayList gds; + + public GdLst(ArrayList gds) { + this.gds = gds; + } + + public GdLst(GdLst g) { + this.gds = g.gds; + } + + public static GdLst parseOOXML(XmlPullParser xpp, Stack lastTag) { - ArrayList gds= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gd")) { - lastTag.push(tnm); - if (gds==null) gds= new ArrayList(); - gds.add((Gd) Gd.parseOOXML(xpp, lastTag)); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("gdLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gdLst.parseOOXML: " + e.toString()); - } - GdLst g= new GdLst(gds); - return g; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (gds!=null) { - for (int i= 0; i < gds.size(); i++) - ooxml.append(((Gd)gds.get(i)).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GdLst(this); - } + ArrayList gds = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("gd")) { + lastTag.push(tnm); + if (gds == null) gds = new ArrayList(); + gds.add((Gd) Gd.parseOOXML(xpp, lastTag)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("gdLst")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("gdLst.parseOOXML: " + e.toString()); + } + GdLst g = new GdLst(gds); + return g; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (gds != null) { + for (int i = 0; i < gds.size(); i++) + ooxml.append(gds.get(i).getOOXML()); + } + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GdLst(this); + } } /** - ahLst (List of Shape Adjust Handles) -* This element specifies the adjust handles that will be applied to a custom geometry. These adjust handles will -* specify points within the geometric shape that can be used to perform certain transform operations on the -* shape. -* [Example: Consider the scenario where a custom geometry, an arrow in this case, has been drawn and adjust -* handles have been placed at the top left corner of both the arrow head and arrow body. The user interface can -* then be made to transform only certain parts of the shape by using the corresponding adjust handle -* -* parent: custGeom -* children: one or more of [ahXY, ahPolar (both REQ)] -*/ + * ahLst (List of Shape Adjust Handles) + * This element specifies the adjust handles that will be applied to a custom geometry. These adjust handles will + * specify points within the geometric shape that can be used to perform certain transform operations on the + * shape. + * [Example: Consider the scenario where a custom geometry, an arrow in this case, has been drawn and adjust + * handles have been placed at the top left corner of both the arrow head and arrow body. The user interface can + * then be made to transform only certain parts of the shape by using the corresponding adjust handle + *

        + * parent: custGeom + * children: one or more of [ahXY, ahPolar (both REQ)] + */ /** -* rect (Shape Text Rectangle) -* -* This element specifies the rectangular bounding box for text within a custGeom shape. The default for this -* rectangle is the bounding box for the shape. This can be modified using this elements four attributes to inset or -* extend the text bounding box. -* -* parent: custGeom -* children: none -*/ + * rect (Shape Text Rectangle) + * + * This element specifies the rectangular bounding box for text within a custGeom shape. The default for this + * rectangle is the bounding box for the shape. This can be modified using this elements four attributes to inset or + * extend the text bounding box. + * + * parent: custGeom + * children: none + */ class Rect implements OOXMLElement { - private static final long serialVersionUID = 2790708601254975676L; - private HashMap attrs; - - public Rect(HashMap attrs) { - this.attrs= attrs; - } - public Rect(Rect r) { - this.attrs= r.attrs; - } - - + private static final long serialVersionUID = 2790708601254975676L; + private HashMap attrs; + + public Rect(HashMap attrs) { + this.attrs = attrs; + } + + public Rect(Rect r) { + this.attrs = r.attrs; + } + + public static Rect parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("rect")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("CHILDELEMENT")) { - lastTag.push(tnm); - //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); - - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("rect")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("rect.parseOOXML: " + e.toString()); - } - Rect r= new Rect(attrs); - return r; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Rect(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("rect")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("CHILDELEMENT")) { + lastTag.push(tnm); + //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("rect")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("rect.parseOOXML: " + e.toString()); + } + Rect r = new Rect(attrs); + return r; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Rect(this); + } } /** @@ -568,67 +569,67 @@ public OOXMLElement cloneElement() { * This element specifies all the connection sites that will be used for this shape. A connection site is specified by * defining a point within the shape bounding box that can have a cxnSp element attached to it. These connection * sites are specified using the shape coordinate system that is specified within the ext transform element. - * + * * parents: custGeom * children: cxn (0 or more) */ class CxnLst implements OOXMLElement { - - private static final long serialVersionUID = -562847539163221621L; - private ArrayList cxns; - - public CxnLst(ArrayList cxns) { - this.cxns= cxns; - } - public CxnLst(CxnLst c) { - this.cxns= c.cxns; - } - + + private static final long serialVersionUID = -562847539163221621L; + private ArrayList cxns; + + public CxnLst(ArrayList cxns) { + this.cxns = cxns; + } + + public CxnLst(CxnLst c) { + this.cxns = c.cxns; + } + public static CxnLst parseOOXML(XmlPullParser xpp, Stack lastTag) { - ArrayList cxns= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cxn")) { - lastTag.push(tnm); - if (cxns==null) cxns= new ArrayList(); - cxns.add((Cxn) Cxn.parseOOXML(xpp, lastTag)); - - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("cxnLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cxnLst.parseOOXML: " + e.toString()); - } - CxnLst c= new CxnLst(cxns); - return c; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (cxns!=null) { - for (int i= 0; i < cxns.size(); i++) - ooxml.append(((Cxn)cxns.get(i)).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CxnLst(this); - } -} + ArrayList cxns = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cxn")) { + lastTag.push(tnm); + if (cxns == null) cxns = new ArrayList(); + cxns.add(Cxn.parseOOXML(xpp, lastTag)); + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("cxnLst")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("cxnLst.parseOOXML: " + e.toString()); + } + CxnLst c = new CxnLst(cxns); + return c; + } + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (cxns != null) { + for (int i = 0; i < cxns.size(); i++) + ooxml.append(cxns.get(i).getOOXML()); + } + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CxnLst(this); + } +} /** @@ -638,82 +639,80 @@ public OOXMLElement cloneElement() { * document. It should be noted that this connection is placed within the shape bounding box using the transform * coordinate system which is also called the shape coordinate system, as it encompasses theentire shape. The * width and height for this coordinate system are specified within the ext transform element. - * + * * parents: cxnLst * children: pos REQ */ -class Cxn implements OOXMLElement{ - - private static final long serialVersionUID = -4193511102420582252L; - private HashMap attrs; - private Pos pos= null; - - public Cxn(HashMap attrs, Pos p) { - this.attrs= attrs; - this.pos= p; - } - public Cxn(Cxn c) { - this.attrs= c.attrs; - this.pos= c.pos; - } - - - public static Cxn parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - Pos p= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cxn")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); // ang REQ - } - } else if (tnm.equals("pos")) { - lastTag.push(tnm); - p= (Pos) Pos.parseOOXML(xpp, lastTag); - - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("cxn")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cxn.parseOOXML: " + e.toString()); - } - Cxn c= new Cxn(attrs, p); - return c; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (pos!=null) ooxml.append(pos.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Cxn(this); - } -} +class Cxn implements OOXMLElement { + private static final long serialVersionUID = -4193511102420582252L; + private HashMap attrs; + private Pos pos = null; + public Cxn(HashMap attrs, Pos p) { + this.attrs = attrs; + this.pos = p; + } + public Cxn(Cxn c) { + this.attrs = c.attrs; + this.pos = c.pos; + } + + + public static Cxn parseOOXML(XmlPullParser xpp, Stack lastTag) { + HashMap attrs = new HashMap(); + Pos p = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cxn")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); // ang REQ + } + } else if (tnm.equals("pos")) { + lastTag.push(tnm); + p = Pos.parseOOXML(xpp, lastTag); + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("cxn")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("cxn.parseOOXML: " + e.toString()); + } + Cxn c = new Cxn(attrs, p); + return c; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (pos != null) ooxml.append(pos.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Cxn(this); + } +} /** @@ -722,67 +721,68 @@ public OOXMLElement cloneElement() { * within the shape bounding box using the transform coordinate system which is also called the shape coordinate * system, as it encompasses the entire shape. The width and height for this coordinate system are specified within * the ext transform element. - * + * * parents: cxn, ahPolar, ahXY * children: none */ class Pos implements OOXMLElement { - private static final long serialVersionUID = 5500991309750603125L; - private HashMap attrs; - - public Pos(HashMap attrs) { - this.attrs= attrs; - } - public Pos(Pos p) { - this.attrs= p.attrs; - } - - + private static final long serialVersionUID = 5500991309750603125L; + private HashMap attrs; + + public Pos(HashMap attrs) { + this.attrs = attrs; + } + + public Pos(Pos p) { + this.attrs = p.attrs; + } + + public static Pos parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pos")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("pos")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pos.parseOOXML: " + e.toString()); - } - Pos p= new Pos(attrs); - return p; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Pos(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pos")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("pos")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("pos.parseOOXML: " + e.toString()); + } + Pos p = new Pos(attrs); + return p; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Pos(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Graphic.java b/src/main/java/io/starter/formats/OOXML/Graphic.java index 266e165..9af0c01 100644 --- a/src/main/java/io/starter/formats/OOXML/Graphic.java +++ b/src/main/java/io/starter/formats/OOXML/Graphic.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,203 +23,214 @@ package io.starter.formats.OOXML; -import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * graphic (Graphic Object) * This element specifies the existence of a single graphic object. Document authors should refer to this element * when they wish to persist a graphical object of some kind. The specification for this graphical object will be * provided entirely by the document author and referenced within the graphicData child element - * + *

        * parent: anchor, graphicFrame, inline * children: graphicData - * - * */ -public class Graphic implements OOXMLElement{ - - private static final long serialVersionUID = -7027946026352255398L; - private GraphicData graphicData= new GraphicData(); - - public Graphic() { } - public Graphic(GraphicData g) { - this.graphicData= g; - } - public Graphic(Graphic gr) { - this.graphicData= gr.graphicData; - } - +public class Graphic implements OOXMLElement { + + private static final long serialVersionUID = -7027946026352255398L; + private GraphicData graphicData = new GraphicData(); + + public Graphic() { + } + + public Graphic(GraphicData g) { + this.graphicData = g; + } + + public Graphic(Graphic gr) { + this.graphicData = gr.graphicData; + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - GraphicData g= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("graphicData")) { - lastTag.push(tnm); - g= (GraphicData) GraphicData.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("graphic")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("graphic.parseOOXML: " + e.toString()); - } - Graphic gr= new Graphic(g); - return gr; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (graphicData!=null) ooxml.append(graphicData.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Graphic(this); - } - - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (graphicData!=null) - return graphicData.getURI(); - return null; - } - - public void setURI(String uri) { - if (graphicData!=null) - graphicData.setURI(uri); - } - /** - * return the rid of the chart element, if exists - * @return - */ - public String getChartRId() { - if (graphicData!=null) - return graphicData.getChartRId(); - return null; - } - - /** - * set the rid of the chart element - */ - public void setChartRId(String rid) { - if (graphicData!=null) graphicData.setChartRId(rid); - } + GraphicData g = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("graphicData")) { + lastTag.push(tnm); + g = GraphicData.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("graphic")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("graphic.parseOOXML: " + e.toString()); + } + Graphic gr = new Graphic(g); + return gr; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (graphicData != null) ooxml.append(graphicData.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Graphic(this); + } + + /** + * get the URI associated with this graphic Data + */ + public String getURI() { + if (graphicData != null) + return graphicData.getURI(); + return null; + } + + public void setURI(String uri) { + if (graphicData != null) + graphicData.setURI(uri); + } + + /** + * return the rid of the chart element, if exists + * + * @return + */ + public String getChartRId() { + if (graphicData != null) + return graphicData.getChartRId(); + return null; + } + + /** + * set the rid of the chart element + */ + public void setChartRId(String rid) { + if (graphicData != null) graphicData.setChartRId(rid); + } } /** * graphicData (Graphic Object Data) * This element specifies the reference to a graphic object within the document. This graphic object is provided * entirely by the document authors who choose to persist this data within the document. - * + *

        * parent: graphic * children: chart ... anything else? - * */ -class GraphicData implements OOXMLElement{ - - private static final long serialVersionUID = 7395991759307532325L; - private String uri= OOXMLConstants.chartns; //xmlns:r=\"" + OOXMLConstants.relns + "\""; // default - private String rid= null; - public GraphicData() { } - public GraphicData(String uri, String rid) { - this.uri= uri; - this.rid= rid; - } - public GraphicData(GraphicData gd) { - this.uri= gd.uri; - this.rid= gd.rid; - } - +class GraphicData implements OOXMLElement { + + private static final long serialVersionUID = 7395991759307532325L; + private String uri = OOXMLConstants.chartns; //xmlns:r=\"" + OOXMLConstants.relns + "\""; // default + private String rid = null; + + public GraphicData() { + } + + public GraphicData(String uri, String rid) { + this.uri = uri; + this.rid = rid; + } + + public GraphicData(GraphicData gd) { + this.uri = gd.uri; + this.rid = gd.rid; + } + public static GraphicData parseOOXML(XmlPullParser xpp, Stack lastTag) { - String uri= null; - String rid= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("graphicData")) { // get attributes - if (xpp.getAttributeCount()>0) - uri= xpp.getAttributeValue(0); - } else if (tnm.equals("chart")) { // one of many possible children - if (xpp.getAttributeCount()>0) - rid= xpp.getAttributeValue(0); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("graphicData")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("graphicData.parseOOXML: " + e.toString()); - } - GraphicData g= new GraphicData(uri, rid); - return g; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - } else - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GraphicData(this); - } - /** - * return the URI attribute associated with this graphic Data - * @return - */ - public String getURI() { - return uri; - } - - /** - * set the URI attribute for this graphic data - * @param uri - */ - public void setURI(String uri) { - this.uri= uri; - } - - /** - * return the rid of the chart element, if exists - * @return - */ - public String getChartRId() { - return rid; - } - - /** - * set the rid of the chart element - */ - public void setChartRId(String rid) { - this.rid= rid; - } + String uri = null; + String rid = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("graphicData")) { // get attributes + if (xpp.getAttributeCount() > 0) + uri = xpp.getAttributeValue(0); + } else if (tnm.equals("chart")) { // one of many possible children + if (xpp.getAttributeCount() > 0) + rid = xpp.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("graphicData")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("graphicData.parseOOXML: " + e.toString()); + } + GraphicData g = new GraphicData(uri, rid); + return g; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + } else + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GraphicData(this); + } + + /** + * return the URI attribute associated with this graphic Data + * + * @return + */ + public String getURI() { + return uri; + } + + /** + * set the URI attribute for this graphic data + * + * @param uri + */ + public void setURI(String uri) { + this.uri = uri; + } + + /** + * return the rid of the chart element, if exists + * + * @return + */ + public String getChartRId() { + return rid; + } + + /** + * set the rid of the chart element + */ + public void setChartRId(String rid) { + this.rid = rid; + } } diff --git a/src/main/java/io/starter/formats/OOXML/GraphicFrame.java b/src/main/java/io/starter/formats/OOXML/GraphicFrame.java index b604ba5..3d38bcb 100644 --- a/src/main/java/io/starter/formats/OOXML/GraphicFrame.java +++ b/src/main/java/io/starter/formats/OOXML/GraphicFrame.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,417 +22,437 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import java.util.HashMap; -import java.util.Iterator; - +import io.starter.toolkit.Logger; import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Stack; /** * graphicFrame (Graphic Frame) * This element describes a single graphical object frame for a spreadsheet which contains a graphical object - * + *

        * parent: oneCellAnchor, twoCellAnchor, absoluteAnchor, grpSp * children: graphic, nvGraphicFramePr, xfrm (all required and in sequence) - * - * */ //TODO: finish cNvGraphicFramePr.graphicFrameLocks element -public class GraphicFrame implements OOXMLElement{ - - private static final long serialVersionUID = 2494490998000511917L; - private HashMap attrs= new HashMap(); - private Graphic graphic= new Graphic(); - - private NvGraphicFramePr graphicFramePr= new NvGraphicFramePr(); - private Xfrm xfrm= new Xfrm(); - - public GraphicFrame() { - attrs.put("macro", ""); - } - - public GraphicFrame(HashMap attrs, Graphic g, NvGraphicFramePr gfp, Xfrm x) { - this.attrs= attrs; - this.graphic= g; - this.graphicFramePr= gfp; - this.xfrm= x; - } - public GraphicFrame(GraphicFrame gf) { - this.attrs= gf.attrs; - this.graphic= gf.graphic; - this.graphicFramePr= gf.graphicFramePr; - this.xfrm= gf.xfrm; - } - - +public class GraphicFrame implements OOXMLElement { + + private static final long serialVersionUID = 2494490998000511917L; + private HashMap attrs = new HashMap(); + private Graphic graphic = new Graphic(); + + private NvGraphicFramePr graphicFramePr = new NvGraphicFramePr(); + private Xfrm xfrm = new Xfrm(); + + public GraphicFrame() { + attrs.put("macro", ""); + } + + public GraphicFrame(HashMap attrs, Graphic g, NvGraphicFramePr gfp, Xfrm x) { + this.attrs = attrs; + this.graphic = g; + this.graphicFramePr = gfp; + this.xfrm = x; + } + + public GraphicFrame(GraphicFrame gf) { + this.attrs = gf.attrs; + this.graphic = gf.graphic; + this.graphicFramePr = gf.graphicFramePr; + this.xfrm = gf.xfrm; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - Graphic g= null; - NvGraphicFramePr gfp= null; - Xfrm x= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("graphicFrame")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("xfrm")) { - lastTag.push(tnm); - x= (Xfrm) Xfrm.parseOOXML(xpp, lastTag); - x.setNS("xdr"); - } else if (tnm.equals("graphic")) { - lastTag.push(tnm); - g= (Graphic) Graphic.parseOOXML(xpp, lastTag); - } else if (tnm.equals("nvGraphicFramePr")) { - lastTag.push(tnm); - gfp= (NvGraphicFramePr) NvGraphicFramePr.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("graphicFrame")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("graphicFrame.parseOOXML: " + e.toString()); - } - GraphicFrame gf= new GraphicFrame(attrs,g,gfp,x); - return gf; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - // all are required so no null checks - must ensure x.ns is set - ooxml.append(graphicFramePr.getOOXML()); - ooxml.append(xfrm.getOOXML()); - ooxml.append(graphic.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GraphicFrame(this); - } - /** - * get graphicFrame Macro attribute - */ - public String getMacro() { - if (attrs.get("macro")!=null) - return (String) attrs.get("macro"); - return null; - } - - /** - * set graphicFrame Macro attribute - * @param macro - */ - public void setMacro(String macro) { - attrs.put("macro", macro); - } - - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (graphic!=null) - return graphic.getURI(); - return null; - } - - /** - * set the URI associated with this graphic data - * @param uri - */ - public void setURI(String uri) { - if (graphic!=null) - graphic.setURI(uri); - } - - /** - * return the rid of the chart element, if exists - * @return - */ - public String getChartRId() { - if (graphic!=null) - return graphic.getChartRId(); - return null; - } - - /** - * set the rid of the chart element - */ - public void setChartRId(String rid) { - if (graphic!=null) graphic.setChartRId(rid); - } - /** - * get the cNvPr name attribute - * @return - */ - public String getName() { - if (graphicFramePr!=null) - return graphicFramePr.getName(); - return null; - } - - /** - * set cNvPr name attribute - * @param name - */ - public void setName(String name) { - if (graphicFramePr!=null) - graphicFramePr.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (graphicFramePr!=null) - return graphicFramePr.getDescr(); - return null; - } - /** - * set cNvPr description attribute - * sometimes associated with shape name - * @param descr - */ - public void setDescr(String descr) { - if (graphicFramePr!=null) - graphicFramePr.setDescr(descr); - } - - - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (graphicFramePr!=null) - graphicFramePr.setId(id); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (graphicFramePr!=null) - return graphicFramePr.getId(); - return -1; - } + HashMap attrs = new HashMap(); + Graphic g = null; + NvGraphicFramePr gfp = null; + Xfrm x = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("graphicFrame")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("xfrm")) { + lastTag.push(tnm); + x = (Xfrm) Xfrm.parseOOXML(xpp, lastTag); + x.setNS("xdr"); + } else if (tnm.equals("graphic")) { + lastTag.push(tnm); + g = (Graphic) Graphic.parseOOXML(xpp, lastTag); + } else if (tnm.equals("nvGraphicFramePr")) { + lastTag.push(tnm); + gfp = NvGraphicFramePr.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("graphicFrame")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("graphicFrame.parseOOXML: " + e.toString()); + } + GraphicFrame gf = new GraphicFrame(attrs, g, gfp, x); + return gf; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + // all are required so no null checks - must ensure x.ns is set + ooxml.append(graphicFramePr.getOOXML()); + ooxml.append(xfrm.getOOXML()); + ooxml.append(graphic.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GraphicFrame(this); + } + + /** + * get graphicFrame Macro attribute + */ + public String getMacro() { + if (attrs.get("macro") != null) + return attrs.get("macro"); + return null; + } + + /** + * set graphicFrame Macro attribute + * + * @param macro + */ + public void setMacro(String macro) { + attrs.put("macro", macro); + } + + /** + * get the URI associated with this graphic Data + */ + public String getURI() { + if (graphic != null) + return graphic.getURI(); + return null; + } + + /** + * set the URI associated with this graphic data + * + * @param uri + */ + public void setURI(String uri) { + if (graphic != null) + graphic.setURI(uri); + } + + /** + * return the rid of the chart element, if exists + * + * @return + */ + public String getChartRId() { + if (graphic != null) + return graphic.getChartRId(); + return null; + } + + /** + * set the rid of the chart element + */ + public void setChartRId(String rid) { + if (graphic != null) graphic.setChartRId(rid); + } + + /** + * get the cNvPr name attribute + * + * @return + */ + public String getName() { + if (graphicFramePr != null) + return graphicFramePr.getName(); + return null; + } + + /** + * set cNvPr name attribute + * + * @param name + */ + public void setName(String name) { + if (graphicFramePr != null) + graphicFramePr.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (graphicFramePr != null) + return graphicFramePr.getDescr(); + return null; + } + + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setDescr(String descr) { + if (graphicFramePr != null) + graphicFramePr.setDescr(descr); + } + + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (graphicFramePr != null) + graphicFramePr.setId(id); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (graphicFramePr != null) + return graphicFramePr.getId(); + return -1; + } } /** * nvGraphicFramePr (Non-Visual Properties for a Graphic Frame) * This element specifies all non-visual properties for a graphic frame. This element is a container for the non-visual * identification properties, shape properties and application properties that are to be associated with a graphic - * frame. This allows for additional information that does not affect the appearance of the graphic frame to be - * stored. - * - * parent: graphicFrame - * children: cNvPr REQ cNvGraphicFramePr REQ - * + * frame. This allows for additional information that does not affect the appearance of the graphic frame to be + * stored. + *

        + * parent: graphicFrame + * children: cNvPr REQ cNvGraphicFramePr REQ */ -class NvGraphicFramePr implements OOXMLElement{ - private static final long serialVersionUID = -47476384268955296L; - private CNvPr cp= new CNvPr(); - private CNvGraphicFramePr nvpr= new CNvGraphicFramePr(); - - public NvGraphicFramePr() { } - public NvGraphicFramePr(CNvPr cp, CNvGraphicFramePr nvpr) { - this.cp= cp; - this.nvpr= nvpr; - } - public NvGraphicFramePr(NvGraphicFramePr nvg) { - this.cp= nvg.cp; - this.nvpr= nvg.nvpr; - } - - +class NvGraphicFramePr implements OOXMLElement { + private static final long serialVersionUID = -47476384268955296L; + private CNvPr cp = new CNvPr(); + private CNvGraphicFramePr nvpr = new CNvGraphicFramePr(); + + public NvGraphicFramePr() { + } + + public NvGraphicFramePr(CNvPr cp, CNvGraphicFramePr nvpr) { + this.cp = cp; + this.nvpr = nvpr; + } + + public NvGraphicFramePr(NvGraphicFramePr nvg) { + this.cp = nvg.cp; + this.nvpr = nvg.nvpr; + } + + public static NvGraphicFramePr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cp= null; - CNvGraphicFramePr nvpr= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvGraphicFramePr")) { - lastTag.push(tnm); - nvpr= (CNvGraphicFramePr) CNvGraphicFramePr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cp= (CNvPr) CNvPr.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("nvGraphicFramePr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("nvGraphicFramePr.parseOOXML: " + e.toString()); - } - NvGraphicFramePr gfp= new NvGraphicFramePr(cp, nvpr); - return gfp; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (cp!=null) ooxml.append(cp.getOOXML()); - if (nvpr!=null) ooxml.append(nvpr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NvGraphicFramePr(this); - } - - /** - * get cNvPr name attribute - * @return - */ - public String getName() { - if (cp!=null) - return cp.getName(); - return null; - } - - /** - * set cNvPr name attribute - * @param name - */ - public void setName(String name) { - if (cp!=null) - cp.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (cp!=null) - return cp.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * @param name - */ - public void setDescr(String descr) { - if (cp!=null) - cp.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (cp!=null) - cp.setId(id); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (cp!=null) - return cp.getId(); - return -1; - } - - + CNvPr cp = null; + CNvGraphicFramePr nvpr = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cNvGraphicFramePr")) { + lastTag.push(tnm); + nvpr = CNvGraphicFramePr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("cNvPr")) { + lastTag.push(tnm); + cp = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("nvGraphicFramePr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("nvGraphicFramePr.parseOOXML: " + e.toString()); + } + NvGraphicFramePr gfp = new NvGraphicFramePr(cp, nvpr); + return gfp; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (cp != null) ooxml.append(cp.getOOXML()); + if (nvpr != null) ooxml.append(nvpr.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new NvGraphicFramePr(this); + } + + /** + * get cNvPr name attribute + * + * @return + */ + public String getName() { + if (cp != null) + return cp.getName(); + return null; + } + + /** + * set cNvPr name attribute + * + * @param name + */ + public void setName(String name) { + if (cp != null) + cp.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (cp != null) + return cp.getDescr(); + return null; + } + + /** + * set cNvPr desc attribute + * + * @param name + */ + public void setDescr(String descr) { + if (cp != null) + cp.setDescr(descr); + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (cp != null) + cp.setId(id); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (cp != null) + return cp.getId(); + return -1; + } + + } /** * cNvGraphicFramePr (Non-Visual Graphic Frame Drawing Properties) - * + *

        * This element specifies the non-visual properties for a single graphical object frame within a spreadsheet. These * are the set of properties of a frame which do not affect its display within a spreadsheet - * + *

        * parent: nvGraphicFramePr * children: graphicFrameLocks - * */ // TODO: finish graphicFrameLocks class CNvGraphicFramePr implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 769474804434194488L; - - // private graphicFrameLocks gf= null; - public CNvGraphicFramePr() { } - public CNvGraphicFramePr(CNvGraphicFramePr g) { } - - + /** + * serialVersionUID + */ + private static final long serialVersionUID = 769474804434194488L; + + // private graphicFrameLocks gf= null; + public CNvGraphicFramePr() { + } + + public CNvGraphicFramePr(CNvGraphicFramePr g) { + } + + public static CNvGraphicFramePr parseOOXML(XmlPullParser xpp, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { /* String tnm = xpp.getName(); if (tnm.equals("graphicFrameLocks")) { lastTag.push(tnm); //gf= (graphicFrameLocks) graphicFrameLocks.parseOOXML(xpp, lastTag).clone(); } -*/ - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("cNvGraphicFramePr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cNvGraphicFramePr.parseOOXML: " + e.toString()); - } - CNvGraphicFramePr cpr= new CNvGraphicFramePr(); - return cpr; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - // TODO: Finish child graphicFrameLocks - //if (gf!=null) ooxml.append(gf.getOOXML()); - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvGraphicFramePr(this); - } - +*/ + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("cNvGraphicFramePr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("cNvGraphicFramePr.parseOOXML: " + e.toString()); + } + CNvGraphicFramePr cpr = new CNvGraphicFramePr(); + return cpr; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + // TODO: Finish child graphicFrameLocks + //if (gf!=null) ooxml.append(gf.getOOXML()); + ooxml.append(""); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CNvGraphicFramePr(this); + } + } diff --git a/src/main/java/io/starter/formats/OOXML/GrpSp.java b/src/main/java/io/starter/formats/OOXML/GrpSp.java index 2366132..f8a74ae 100644 --- a/src/main/java/io/starter/formats/OOXML/GrpSp.java +++ b/src/main/java/io/starter/formats/OOXML/GrpSp.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,772 +22,804 @@ */ package io.starter.formats.OOXML; +import io.starter.OpenXLS.WorkBookHandle; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; - /** * grpSp (Group Shape) * This element specifies a group shape that represents many shapes grouped together. * Within a group shape 1 each of the shapes that make up the group are - specified just as they normally would. The idea behind grouping elements however is that a - single transform can apply to many shapes at the same time. - * + * specified just as they normally would. The idea behind grouping elements however is that a + * single transform can apply to many shapes at the same time. + *

        * parents: absoluteAnchor, grpSp, oneCellAnchor, twoCellAnchor * children: nvGrpSpPr (req), grpSpPr (req), choice of: (sp, grpSp, graphicFrame, cxnSp, pic) 0 to unbounded */ -public class GrpSp implements OOXMLElement{ - - private static final long serialVersionUID = -3276180769601314853L; - private NvGrpSpPr nvpr= null; - private GrpSpPr sppr= null; - private ArrayList choice= null; +public class GrpSp implements OOXMLElement { + + private static final long serialVersionUID = -3276180769601314853L; + private NvGrpSpPr nvpr = null; + private GrpSpPr sppr = null; + private ArrayList choice = null; public GrpSp(NvGrpSpPr nvpr, GrpSpPr sppr, ArrayList choice) { - this.nvpr= nvpr; - this.sppr= sppr; - this.choice= choice; - } - public GrpSp(GrpSp g) { - this.nvpr= g.nvpr; - this.sppr= g.sppr; - this.choice= g.choice; - } - - /** - * parse grpSp element OOXML - * @param xpp - * @param lastTag - * @return - */ + this.nvpr = nvpr; + this.sppr = sppr; + this.choice = choice; + } + + public GrpSp(GrpSp g) { + this.nvpr = g.nvpr; + this.sppr = g.sppr; + this.choice = g.choice; + } + + /** + * parse grpSp element OOXML + * + * @param xpp + * @param lastTag + * @return + */ public static GrpSp parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - NvGrpSpPr nvpr= null; - GrpSpPr sppr= null; - ArrayList choice= new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("nvGrpSpPr")) { - lastTag.push(tnm); - nvpr= (NvGrpSpPr) NvGrpSpPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("grpSpPr")) { - lastTag.push(tnm); - sppr= (GrpSpPr) GrpSpPr.parseOOXML(xpp, lastTag, bk); - // choice of: 0 or more below: - } else if (tnm.equals("sp")) { - lastTag.push(tnm); - choice.add((Sp) Sp.parseOOXML(xpp, lastTag, bk)); - } else if (tnm.equals("grpSp")) { - if (nvpr!=null) { // if not the initial start attribute, this is a child - lastTag.push(tnm); - choice.add((GrpSp) GrpSp.parseOOXML(xpp, lastTag, bk)); - } - } else if (tnm.equals("graphicFrame")) { - lastTag.push(tnm); - choice.add((GraphicFrame) GraphicFrame.parseOOXML(xpp, lastTag)); - } else if (tnm.equals("cxnSp")) { - lastTag.push(tnm); - choice.add((CxnSp) CxnSp.parseOOXML(xpp, lastTag, bk)); - } else if (tnm.equals("pic")) { - lastTag.push(tnm); - choice.add((Pic) Pic.parseOOXML(xpp, lastTag, bk)); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("grpSp")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("GrpSp.parseOOXML: " + e.toString()); - } - GrpSp gf= new GrpSp(nvpr, sppr, choice); - return gf; + NvGrpSpPr nvpr = null; + GrpSpPr sppr = null; + ArrayList choice = new ArrayList(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("nvGrpSpPr")) { + lastTag.push(tnm); + nvpr = NvGrpSpPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("grpSpPr")) { + lastTag.push(tnm); + sppr = GrpSpPr.parseOOXML(xpp, lastTag, bk); + // choice of: 0 or more below: + } else if (tnm.equals("sp")) { + lastTag.push(tnm); + choice.add(Sp.parseOOXML(xpp, lastTag, bk)); + } else if (tnm.equals("grpSp")) { + if (nvpr != null) { // if not the initial start attribute, this is a child + lastTag.push(tnm); + choice.add(GrpSp.parseOOXML(xpp, lastTag, bk)); + } + } else if (tnm.equals("graphicFrame")) { + lastTag.push(tnm); + choice.add(GraphicFrame.parseOOXML(xpp, lastTag)); + } else if (tnm.equals("cxnSp")) { + lastTag.push(tnm); + choice.add(CxnSp.parseOOXML(xpp, lastTag, bk)); + } else if (tnm.equals("pic")) { + lastTag.push(tnm); + choice.add(Pic.parseOOXML(xpp, lastTag, bk)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("grpSp")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("GrpSp.parseOOXML: " + e.toString()); + } + GrpSp gf = new GrpSp(nvpr, sppr, choice); + return gf; } /** * return grpSp element OOXML */ - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - ooxml.append(nvpr.getOOXML()); - ooxml.append(sppr.getOOXML()); - if (choice!=null) { - for (int i= 0; i < choice.size(); i++) { - ooxml.append(((OOXMLElement)choice.get(i)).getOOXML()); - } - } - ooxml.append(""); - return ooxml.toString(); - } - - static int SP= 0; - static int PIC= 1; - static int CXN= 2; - static int GRAPHICFRAME= 3; - private OOXMLElement getObject(int type) { - if (choice!=null) { - for (int i= 0; i < choice.size(); i++) { - OOXMLElement oe= (OOXMLElement) choice.get(i); - if (oe instanceof Sp && type==SP) - return oe; - else if (oe instanceof Pic && type==PIC) - return oe; - else if (oe instanceof CxnSp && type==CXN) - return oe; - else if (oe instanceof GraphicFrame && type==GRAPHICFRAME) - return oe; - else if (oe instanceof GrpSp) - return ((GrpSp)oe).getObject(type); - - } - } - return null; - } - - /** - * get name attribute of the group shape - * @return - */ - public String getName() { - return nvpr.getName(); - } - /** - * set name attribute of the group shape - * @param name - */ - public void setName(String name) { - nvpr.setName(name); // set the group name - } - - /** - * get macro attribute of the group shape - * @return - */ - public String getMacro() { - String macro= null; - // Have a shape with an embed? - OOXMLElement oe= getObject(SP); - if (oe!=null) - macro= ((Sp)oe).getMacro(); - // how's about a picture? - if (macro==null) - oe= getObject(PIC); - if (oe!=null && macro==null) - macro= ((Pic)oe).getMacro(); - // or a connection shape? - if (macro==null) - oe= getObject(CXN); - if (oe!=null && macro==null) - macro= ((CxnSp)oe).getMacro(); - return macro; - } - /** - * set macro attribute of the group shape - * @param name - */ - public void setMacro(String macro) { - // Have a shape with a macro? - String m= null; - OOXMLElement oe= getObject(SP); - if (oe!=null) - m= ((Sp)oe).getMacro(); - if (m!=null) { - ((Sp)oe).setMacro(macro); - return; - } - // how's about a picture? - oe= getObject(PIC); - if (oe!=null) - m= ((Pic)oe).getMacro(); - if (m!=null) { - ((Pic)oe).setMacro(macro); - return; - } - // or a connection shape? - oe= getObject(CXN); - if (oe!=null) - m= ((CxnSp)oe).getMacro(); - if (m!=null) - ((CxnSp)oe).setMacro(macro); - } - - - /** - * get cthe descr attribute of the group shape - * @return - */ - public String getDescr() { - return nvpr.getDescr(); - } - - /** - * set description attribute of the group shape - * sometimes associated with shape name - * @param descr - */ - public void setDescr(String descr) { - nvpr.setDescr(descr); - } - - /** - * return the rid for the embedded object (picture or chart or picture shape) (i.e. resides within the file) - * @return - */ - public String getEmbed() { - String embed= null; - // Have a shape with an embed? - OOXMLElement oe= getObject(SP); - if (oe!=null) - embed= ((Sp)oe).getEmbed(); - // how's about a picture? - if (embed==null) - oe= getObject(PIC); - if (oe!=null && embed==null) - embed= ((Pic)oe).getEmbed(); - // or a connection shape? - if (embed==null) - oe= getObject(CXN); - if (oe!=null && embed==null) - embed= ((CxnSp)oe).getEmbed(); - return embed; - } - - /** - * set the rid for this object (picture or chart or picture shape) (resides within the file) - * @param embed - */ - public void setEmbed(String embed) { - // Have a shape with an embed? - String e= null; - OOXMLElement oe= getObject(SP); - if (oe!=null) - e= ((Sp)oe).getEmbed(); - if (e!=null) { - ((Sp)oe).setEmbed(embed); - return; - } - // how's about a picture? - oe= getObject(PIC); - if (oe!=null) - e= ((Pic)oe).getEmbed(); - if (e!=null) { - ((Pic)oe).setEmbed(embed); - return; - } - // or a connection shape? - oe= getObject(CXN); - if (oe!=null) - e= ((CxnSp)oe).getEmbed(); - if (e!=null) - ((CxnSp)oe).setEmbed(embed); - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * @return - */ - public String getLink() { - String link= null; - // Have a shape with an embed? - OOXMLElement oe= getObject(SP); - if (oe!=null) - link= ((Sp)oe).getLink(); - // how's about a picture? - if (link==null) - oe= getObject(PIC); - if (oe!=null && link==null) - link= ((Pic)oe).getLink(); - // or a connection shape? - if (link==null) - oe= getObject(CXN); - if (oe!=null && link==null) - link= ((CxnSp)oe).getLink(); - return link; - } - - /** - * rset the id for the linked picture (i.e. doesn't reside in the file) - * @return - */ - public void setLink(String link) { - String l= null; - // Have a shape with an embed? - OOXMLElement oe= getObject(SP); - if (oe!=null) - l= ((Sp)oe).getLink(); - if (l!=null) { - ((Sp)oe).setLink(link); - return; - } - // how's about a picture? - oe= getObject(PIC); - if (oe!=null) - l= ((Pic)oe).getLink(); - if (l!=null) { - ((Pic)oe).setLink(link); - return; - } - // or a connection shape? - oe= getObject(CXN); - if (oe!=null) - l= ((CxnSp)oe).getLink(); - if (l!=null) - ((CxnSp)oe).setLink(link); - } - - /** - * return the rid of the chart element, if exists - * @return - */ - public String getChartRId() { - OOXMLElement oe= getObject(GRAPHICFRAME); - if (oe!=null) - return ((GraphicFrame) oe).getChartRId(); - return null; - } - - /** - * set the URI associated with this graphic data - * @param uri - */ - public void setURI(String uri) { - OOXMLElement oe= getObject(GRAPHICFRAME); - ((GraphicFrame) oe).setURI(uri); - } - - /** utility to return the shape properties element for this picture - * should be depreciated when OOXML is completely distinct from BIFF8 - * @return - */ - public SpPr getSppr() { - OOXMLElement oe= getObject(PIC); - if (oe!=null) - return ((Pic) oe).getSppr(); - return null; - - } - - /** - * return if this Group refers to a shape, as opposed a chart or an image - * @return - */ - public boolean hasShape() { - return (getObject(SP)!=null || getObject(CXN)!=null); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - - if (choice!=null){ - // get the first?? - OOXMLElement oe= (OOXMLElement) choice.get(0); - if (oe instanceof Sp) - return ((Sp)oe).getId(); - else if (oe instanceof Pic) - return ((Pic)oe).getId(); - else if (oe instanceof GraphicFrame) - return ((GraphicFrame)oe).getId(); - else if (oe instanceof CxnSp) - return ((CxnSp)oe).getId(); - else if (oe instanceof GrpSp) - return ((GrpSp)oe).getId(); - } - return -1; - } - - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (choice!=null){ - // get the first?? - OOXMLElement oe= (OOXMLElement) choice.get(0); - if (oe instanceof Sp) - ((Sp)oe).setId(id); - else if (oe instanceof Pic) - ((Pic)oe).setId(id); - else if (oe instanceof GraphicFrame) - ((GraphicFrame)oe).setId(id); - else if (oe instanceof CxnSp) - ((CxnSp)oe).setId(id); - else if (oe instanceof GrpSp) - ((GrpSp)oe).setId(id); - } - } - public OOXMLElement cloneElement() { - return new GrpSp(this); - } + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + ooxml.append(nvpr.getOOXML()); + ooxml.append(sppr.getOOXML()); + if (choice != null) { + for (int i = 0; i < choice.size(); i++) { + ooxml.append(choice.get(i).getOOXML()); + } + } + ooxml.append(""); + return ooxml.toString(); + } + + static int SP = 0; + static int PIC = 1; + static int CXN = 2; + static int GRAPHICFRAME = 3; + + private OOXMLElement getObject(int type) { + if (choice != null) { + for (int i = 0; i < choice.size(); i++) { + OOXMLElement oe = choice.get(i); + if (oe instanceof Sp && type == SP) + return oe; + else if (oe instanceof Pic && type == PIC) + return oe; + else if (oe instanceof CxnSp && type == CXN) + return oe; + else if (oe instanceof GraphicFrame && type == GRAPHICFRAME) + return oe; + else if (oe instanceof GrpSp) + return ((GrpSp) oe).getObject(type); + + } + } + return null; + } + + /** + * get name attribute of the group shape + * + * @return + */ + public String getName() { + return nvpr.getName(); + } + + /** + * set name attribute of the group shape + * + * @param name + */ + public void setName(String name) { + nvpr.setName(name); // set the group name + } + + /** + * get macro attribute of the group shape + * + * @return + */ + public String getMacro() { + String macro = null; + // Have a shape with an embed? + OOXMLElement oe = getObject(SP); + if (oe != null) + macro = ((Sp) oe).getMacro(); + // how's about a picture? + if (macro == null) + oe = getObject(PIC); + if (oe != null && macro == null) + macro = ((Pic) oe).getMacro(); + // or a connection shape? + if (macro == null) + oe = getObject(CXN); + if (oe != null && macro == null) + macro = ((CxnSp) oe).getMacro(); + return macro; + } + + /** + * set macro attribute of the group shape + * + * @param name + */ + public void setMacro(String macro) { + // Have a shape with a macro? + String m = null; + OOXMLElement oe = getObject(SP); + if (oe != null) + m = ((Sp) oe).getMacro(); + if (m != null) { + ((Sp) oe).setMacro(macro); + return; + } + // how's about a picture? + oe = getObject(PIC); + if (oe != null) + m = ((Pic) oe).getMacro(); + if (m != null) { + ((Pic) oe).setMacro(macro); + return; + } + // or a connection shape? + oe = getObject(CXN); + if (oe != null) + m = ((CxnSp) oe).getMacro(); + if (m != null) + ((CxnSp) oe).setMacro(macro); + } + + + /** + * get cthe descr attribute of the group shape + * + * @return + */ + public String getDescr() { + return nvpr.getDescr(); + } + + /** + * set description attribute of the group shape + * sometimes associated with shape name + * + * @param descr + */ + public void setDescr(String descr) { + nvpr.setDescr(descr); + } + + /** + * return the rid for the embedded object (picture or chart or picture shape) (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + String embed = null; + // Have a shape with an embed? + OOXMLElement oe = getObject(SP); + if (oe != null) + embed = ((Sp) oe).getEmbed(); + // how's about a picture? + if (embed == null) + oe = getObject(PIC); + if (oe != null && embed == null) + embed = ((Pic) oe).getEmbed(); + // or a connection shape? + if (embed == null) + oe = getObject(CXN); + if (oe != null && embed == null) + embed = ((CxnSp) oe).getEmbed(); + return embed; + } + + /** + * set the rid for this object (picture or chart or picture shape) (resides within the file) + * + * @param embed + */ + public void setEmbed(String embed) { + // Have a shape with an embed? + String e = null; + OOXMLElement oe = getObject(SP); + if (oe != null) + e = ((Sp) oe).getEmbed(); + if (e != null) { + ((Sp) oe).setEmbed(embed); + return; + } + // how's about a picture? + oe = getObject(PIC); + if (oe != null) + e = ((Pic) oe).getEmbed(); + if (e != null) { + ((Pic) oe).setEmbed(embed); + return; + } + // or a connection shape? + oe = getObject(CXN); + if (oe != null) + e = ((CxnSp) oe).getEmbed(); + if (e != null) + ((CxnSp) oe).setEmbed(embed); + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + String link = null; + // Have a shape with an embed? + OOXMLElement oe = getObject(SP); + if (oe != null) + link = ((Sp) oe).getLink(); + // how's about a picture? + if (link == null) + oe = getObject(PIC); + if (oe != null && link == null) + link = ((Pic) oe).getLink(); + // or a connection shape? + if (link == null) + oe = getObject(CXN); + if (oe != null && link == null) + link = ((CxnSp) oe).getLink(); + return link; + } + + /** + * rset the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public void setLink(String link) { + String l = null; + // Have a shape with an embed? + OOXMLElement oe = getObject(SP); + if (oe != null) + l = ((Sp) oe).getLink(); + if (l != null) { + ((Sp) oe).setLink(link); + return; + } + // how's about a picture? + oe = getObject(PIC); + if (oe != null) + l = ((Pic) oe).getLink(); + if (l != null) { + ((Pic) oe).setLink(link); + return; + } + // or a connection shape? + oe = getObject(CXN); + if (oe != null) + l = ((CxnSp) oe).getLink(); + if (l != null) + ((CxnSp) oe).setLink(link); + } + + /** + * return the rid of the chart element, if exists + * + * @return + */ + public String getChartRId() { + OOXMLElement oe = getObject(GRAPHICFRAME); + if (oe != null) + return ((GraphicFrame) oe).getChartRId(); + return null; + } + + /** + * set the URI associated with this graphic data + * + * @param uri + */ + public void setURI(String uri) { + OOXMLElement oe = getObject(GRAPHICFRAME); + ((GraphicFrame) oe).setURI(uri); + } + + /** + * utility to return the shape properties element for this picture + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + public SpPr getSppr() { + OOXMLElement oe = getObject(PIC); + if (oe != null) + return ((Pic) oe).getSppr(); + return null; + + } + + /** + * return if this Group refers to a shape, as opposed a chart or an image + * + * @return + */ + public boolean hasShape() { + return (getObject(SP) != null || getObject(CXN) != null); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + + if (choice != null) { + // get the first?? + OOXMLElement oe = choice.get(0); + if (oe instanceof Sp) + return ((Sp) oe).getId(); + else if (oe instanceof Pic) + return ((Pic) oe).getId(); + else if (oe instanceof GraphicFrame) + return ((GraphicFrame) oe).getId(); + else if (oe instanceof CxnSp) + return ((CxnSp) oe).getId(); + else if (oe instanceof GrpSp) + return ((GrpSp) oe).getId(); + } + return -1; + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (choice != null) { + // get the first?? + OOXMLElement oe = choice.get(0); + if (oe instanceof Sp) + ((Sp) oe).setId(id); + else if (oe instanceof Pic) + ((Pic) oe).setId(id); + else if (oe instanceof GraphicFrame) + ((GraphicFrame) oe).setId(id); + else if (oe instanceof CxnSp) + ((CxnSp) oe).setId(id); + else if (oe instanceof GrpSp) + ((GrpSp) oe).setId(id); + } + } + + public OOXMLElement cloneElement() { + return new GrpSp(this); + } } - + /** * nvGrpSpPr 1 (Non-Visual Properties for a Group Shape) */ class NvGrpSpPr implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4404072268706949318L; - CNvPr cp= null; - CNvGrpSpPr cgrpsppr= null; - public NvGrpSpPr(CNvPr cp, CNvGrpSpPr cgrpsppr) { - this.cp= cp; - this.cgrpsppr= cgrpsppr; - } - public NvGrpSpPr(NvGrpSpPr g) { - this.cp= g.cp; - this.cgrpsppr= g.cgrpsppr; - } - + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4404072268706949318L; + CNvPr cp = null; + CNvGrpSpPr cgrpsppr = null; + + public NvGrpSpPr(CNvPr cp, CNvGrpSpPr cgrpsppr) { + this.cp = cp; + this.cgrpsppr = cgrpsppr; + } + + public NvGrpSpPr(NvGrpSpPr g) { + this.cp = g.cp; + this.cgrpsppr = g.cgrpsppr; + } + public static NvGrpSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cp= null; - CNvGrpSpPr cgrpsppr= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cp= (CNvPr) CNvPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cNvGrpSpPr")) { - lastTag.push(tnm); - cgrpsppr= (CNvGrpSpPr) CNvGrpSpPr.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("nvGrpSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("NvGrpSpPr.parseOOXML: " + e.toString()); - } - NvGrpSpPr grpsppr= new NvGrpSpPr(cp, cgrpsppr); - return grpsppr; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - ooxml.append(cp.getOOXML()); - ooxml.append(cgrpsppr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NvGrpSpPr(this); - } - - /** - * get cNvPr name attribute - * @return - */ - public String getName() { - if (cp!=null) - return cp.getName(); - return null; - } - - /** - * set cNvPr name attribute - * @param name - */ - public void setName(String name) { - if (cp!=null) - cp.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (cp!=null) - return cp.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * @param name - */ - public void setDescr(String descr) { - if (cp!=null) - cp.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (cp!=null) - cp.setId(id); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (cp!=null) - return cp.getId(); - return -1; - } - - + CNvPr cp = null; + CNvGrpSpPr cgrpsppr = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cNvPr")) { + lastTag.push(tnm); + cp = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("cNvGrpSpPr")) { + lastTag.push(tnm); + cgrpsppr = CNvGrpSpPr.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("nvGrpSpPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("NvGrpSpPr.parseOOXML: " + e.toString()); + } + NvGrpSpPr grpsppr = new NvGrpSpPr(cp, cgrpsppr); + return grpsppr; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + ooxml.append(cp.getOOXML()); + ooxml.append(cgrpsppr.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new NvGrpSpPr(this); + } + + /** + * get cNvPr name attribute + * + * @return + */ + public String getName() { + if (cp != null) + return cp.getName(); + return null; + } + + /** + * set cNvPr name attribute + * + * @param name + */ + public void setName(String name) { + if (cp != null) + cp.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (cp != null) + return cp.getDescr(); + return null; + } + + /** + * set cNvPr desc attribute + * + * @param name + */ + public void setDescr(String descr) { + if (cp != null) + cp.setDescr(descr); + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (cp != null) + cp.setId(id); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (cp != null) + return cp.getId(); + return -1; + } + + } /** * grpSpPr (Group Shape Properties) - This element specifies the properties that are to be common across all of the shapes - within the corresponding group. If there are any conflicting properties within the - group shape properties and the individual shape properties then the individual shape properties - should take precedence. - - children: xfrm, fillproperties (group), effectproperties (group), scene3d, extLst (all optional) + * This element specifies the properties that are to be common across all of the shapes + * within the corresponding group. If there are any conflicting properties within the + * group shape properties and the individual shape properties then the individual shape properties + * should take precedence. + *

        + * children: xfrm, fillproperties (group), effectproperties (group), scene3d, extLst (all optional) */ // TODO: FINISH scene3d, extLst -class GrpSpPr implements OOXMLElement { - - private static final long serialVersionUID = 7464871024304781512L; - private Xfrm xf= null; - private String bwmode= null; - private FillGroup fill= null; - private EffectPropsGroup effect= null; - - public GrpSpPr(Xfrm xf, String bwmode, FillGroup fill, EffectPropsGroup effect) { - this.xf= xf; - this.bwmode= bwmode; - this.fill= fill; - this.effect= effect; - } - - public GrpSpPr(GrpSpPr g) { - this.xf= g.xf; - this.bwmode= g.bwmode; - this.fill= g.fill; - this.effect= g.effect; - } - - public static GrpSpPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - Xfrm xf= null; - String bwmode= null; - FillGroup fill= null; - EffectPropsGroup effect= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("grpSpPr")) { // get attributes - if (xpp.getAttributeCount()==1) { - bwmode= xpp.getAttributeValue(0); - } - } else if (tnm.equals("xfrm")) { - lastTag.push(tnm); - xf= (Xfrm) Xfrm.parseOOXML(xpp, lastTag); - // scene3d, extLst- finish - } else if ( - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill= (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect= (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("grpSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("GrpSpPr.parseOOXML: " + e.toString()); - } - GrpSpPr g= new GrpSpPr(xf, bwmode, fill, effect); - return g; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (xf!=null) ooxml.append(xf.getOOXML()); - if (fill!=null) ooxml.append(fill.getOOXML()); - if (effect!=null) ooxml.append(effect.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GrpSpPr(this); - } +class GrpSpPr implements OOXMLElement { + + private static final long serialVersionUID = 7464871024304781512L; + private Xfrm xf = null; + private String bwmode = null; + private FillGroup fill = null; + private EffectPropsGroup effect = null; + + public GrpSpPr(Xfrm xf, String bwmode, FillGroup fill, EffectPropsGroup effect) { + this.xf = xf; + this.bwmode = bwmode; + this.fill = fill; + this.effect = effect; + } + + public GrpSpPr(GrpSpPr g) { + this.xf = g.xf; + this.bwmode = g.bwmode; + this.fill = g.fill; + this.effect = g.effect; + } + + public static GrpSpPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + Xfrm xf = null; + String bwmode = null; + FillGroup fill = null; + EffectPropsGroup effect = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("grpSpPr")) { // get attributes + if (xpp.getAttributeCount() == 1) { + bwmode = xpp.getAttributeValue(0); + } + } else if (tnm.equals("xfrm")) { + lastTag.push(tnm); + xf = (Xfrm) Xfrm.parseOOXML(xpp, lastTag); + // scene3d, extLst- finish + } else if ( + tnm.equals("solidFill") || + tnm.equals("noFill") || + tnm.equals("gradFill") || + tnm.equals("grpFill") || + tnm.equals("pattFill") || + tnm.equals("blipFill")) { + lastTag.push(tnm); + fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); + } else if ( + tnm.equals("effectLst") || + tnm.equals("effectDag")) { + lastTag.push(tnm); + effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("grpSpPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("GrpSpPr.parseOOXML: " + e.toString()); + } + GrpSpPr g = new GrpSpPr(xf, bwmode, fill, effect); + return g; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (xf != null) ooxml.append(xf.getOOXML()); + if (fill != null) ooxml.append(fill.getOOXML()); + if (effect != null) ooxml.append(effect.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GrpSpPr(this); + } } /** * cNvGrpSpPr (Non-Visual Group Shape Drawing Properties) - * + *

        * parent: nvGrpSpPr * children: grpSpLocks, extLst */ class CNvGrpSpPr implements OOXMLElement { - - private static final long serialVersionUID = -1106010927060582127L; - private GrpSpLocks gsl= null; - - public CNvGrpSpPr(GrpSpLocks gsl) { - this.gsl= gsl; - } - public CNvGrpSpPr(CNvGrpSpPr g) { - this.gsl= g.gsl; - } - - - public static CNvGrpSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - GrpSpLocks gsl= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("grpSpLocks")) { - lastTag.push(tnm); - gsl= (GrpSpLocks) GrpSpLocks.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("cNvGrpSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("CNvGrpSpPr.parseOOXML: " + e.toString()); - } - CNvGrpSpPr c= new CNvGrpSpPr(gsl); - return c; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (gsl!=null) - ooxml.append(gsl.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvGrpSpPr(this); - } + + private static final long serialVersionUID = -1106010927060582127L; + private GrpSpLocks gsl = null; + + public CNvGrpSpPr(GrpSpLocks gsl) { + this.gsl = gsl; + } + + public CNvGrpSpPr(CNvGrpSpPr g) { + this.gsl = g.gsl; + } + + + public static CNvGrpSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { + GrpSpLocks gsl = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("grpSpLocks")) { + lastTag.push(tnm); + gsl = GrpSpLocks.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("cNvGrpSpPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("CNvGrpSpPr.parseOOXML: " + e.toString()); + } + CNvGrpSpPr c = new CNvGrpSpPr(gsl); + return c; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (gsl != null) + ooxml.append(gsl.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CNvGrpSpPr(this); + } } /** * grpSpLocks (Group Shape Locks) - * This element specifies all locking properties for a connection shape. These properties inform the generating - application about specific properties that have been previously locked and thus should not be changed. - * + * This element specifies all locking properties for a connection shape. These properties inform the generating + * application about specific properties that have been previously locked and thus should not be changed. + *

        * parent: cNvGrpSpPr * attributes: many, all optional * children: extLst */ class GrpSpLocks implements OOXMLElement { - - private static final long serialVersionUID = -2592038952923879415L; - private HashMap attrs; - - public GrpSpLocks(HashMap attrs) { - this.attrs= attrs; - } - public GrpSpLocks(GrpSpLocks g) { - this.attrs= g.attrs; - } - - - public static GrpSpLocks parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("grpSpLocks")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("grpSpLocks")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("CNvGrpSpPr.parseOOXML: " + e.toString()); - } - GrpSpLocks g= new GrpSpLocks(attrs); - return g; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - // attributes - Iterator i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GrpSpLocks(this); - } + + private static final long serialVersionUID = -2592038952923879415L; + private HashMap attrs; + + public GrpSpLocks(HashMap attrs) { + this.attrs = attrs; + } + + public GrpSpLocks(GrpSpLocks g) { + this.attrs = g.attrs; + } + + + public static GrpSpLocks parseOOXML(XmlPullParser xpp, Stack lastTag) { + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("grpSpLocks")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("grpSpLocks")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("CNvGrpSpPr.parseOOXML: " + e.toString()); + } + GrpSpLocks g = new GrpSpLocks(attrs); + return g; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + // attributes + Iterator i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new GrpSpLocks(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Layout.java b/src/main/java/io/starter/formats/OOXML/Layout.java index 35a262e..d2aa2ca 100644 --- a/src/main/java/io/starter/formats/OOXML/Layout.java +++ b/src/main/java/io/starter/formats/OOXML/Layout.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,234 +22,237 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; - +import io.starter.toolkit.Logger; import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; +import java.util.Stack; /** - * * chart layout element - * - * + *

        + *

        * parent: plotarea, title ... * children: manualLayout - * */ -public final class Layout implements OOXMLElement{ - - private static final long serialVersionUID = -6547994902298821138L; - private ManualLayout ml; - - - private Layout(ManualLayout ml) { - this.ml= ml; - } - private Layout(Layout l) { - this.ml= l.ml; - } - - /** - * create a new plot area Layout/manual layout element - *
        note that the layout is calculated via "edge" i.e the w and h are the bottom and right edges - * @param target "inner" or "outer"
        inner specifies the plot area size does not include tick marks and axis labels
        outer does - * @param offs x, y, w, h as a fraction of the width or height of the actual chart - */ - public Layout(String target, double[] offs) { - String[] modes= new String[] {"edge", "edge", null, null}; - String[] soffs= new String[4]; - for (int i= 0; i < 4; i++) { - if (offs[i] > 0) - soffs[i]= String.valueOf(offs[i]); - } - this.ml= new ManualLayout(target, modes, soffs); - } - - /** - * return manual layout coords - * TODO: interpret xMode, yMode, hMode, wMode - * @return - */ - public float[] getCoords() { - float[] coords= new float[4]; - for (int i= 0; i < 4; i++) - if (ml.offs[i]!=null) - coords[i]= new Float(ml.offs[i]); - return coords; - } - /** - * parse title OOXML element title - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spPr object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - ManualLayout ml= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("manualLayout")) { - lastTag.push(tnm); - ml= (ManualLayout) ManualLayout.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("layout")) { - lastTag.pop(); // pop layout tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("layout.parseOOXML: " + e.toString()); - } - Layout l= new Layout(ml); - return l; +public final class Layout implements OOXMLElement { + + private static final long serialVersionUID = -6547994902298821138L; + private ManualLayout ml; + + + private Layout(ManualLayout ml) { + this.ml = ml; + } + + private Layout(Layout l) { + this.ml = l.ml; + } + + /** + * create a new plot area Layout/manual layout element + *
        note that the layout is calculated via "edge" i.e the w and h are the bottom and right edges + * + * @param target "inner" or "outer"
        inner specifies the plot area size does not include tick marks and axis labels
        outer does + * @param offs x, y, w, h as a fraction of the width or height of the actual chart + */ + public Layout(String target, double[] offs) { + String[] modes = new String[]{"edge", "edge", null, null}; + String[] soffs = new String[4]; + for (int i = 0; i < 4; i++) { + if (offs[i] > 0) + soffs[i] = String.valueOf(offs[i]); } - - /** - * generate ooxml to define a layout - * - * @return - */ - public String getOOXML() { - StringBuffer looxml= new StringBuffer(); - if (ml!=null) { - looxml.append(""); - looxml.append(ml.getOOXML()); - looxml.append(""); - } else - looxml.append(""); - return looxml.toString(); + this.ml = new ManualLayout(target, modes, soffs); + } + + /** + * return manual layout coords + * TODO: interpret xMode, yMode, hMode, wMode + * + * @return + */ + public float[] getCoords() { + float[] coords = new float[4]; + for (int i = 0; i < 4; i++) + if (ml.offs[i] != null) + coords[i] = new Float(ml.offs[i]); + return coords; + } + + /** + * parse title OOXML element title + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object + */ + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { + ManualLayout ml = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("manualLayout")) { + lastTag.push(tnm); + ml = ManualLayout.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("layout")) { + lastTag.pop(); // pop layout tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("layout.parseOOXML: " + e.toString()); } + Layout l = new Layout(ml); + return l; + } + + /** + * generate ooxml to define a layout + * + * @return + */ + public String getOOXML() { + StringBuffer looxml = new StringBuffer(); + if (ml != null) { + looxml.append(""); + looxml.append(ml.getOOXML()); + looxml.append(""); + } else + looxml.append(""); + return looxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Layout(this); + } - public OOXMLElement cloneElement() { - return new Layout(this); - } - - /** - * static version for 2003-style charts, takes offsets and creates a layout element - * @param offs int offsets of chart - * @return - */ - public static String getOOXML(double[] offs) { - StringBuffer looxml= new StringBuffer(); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - return looxml.toString(); - } + /** + * static version for 2003-style charts, takes offsets and creates a layout element + * + * @param offs int offsets of chart + * @return + */ + public static String getOOXML(double[] offs) { + StringBuffer looxml = new StringBuffer(); + looxml.append(""); + looxml.append(""); + looxml.append(""); + looxml.append(""); + looxml.append(""); + looxml.append(""); + looxml.append(""); + looxml.append(""); + looxml.append(""); + looxml.append(""); + looxml.append(""); + return looxml.toString(); + } } /** - * manualLayout - * specifies exact position of chart - * - * parent: layout - * children: h, hMode, layoutTarget, w, wMode, x, xMode, y, yMode - * + * manualLayout + * specifies exact position of chart + *

        + * parent: layout + * children: h, hMode, layoutTarget, w, wMode, x, xMode, y, yMode */ class ManualLayout implements OOXMLElement { - - private static final long serialVersionUID = 6460833211809500902L; - String[] modes; // xMode, yMode, wMode, hMode - String[] offs; // x, y, w, h; - String target; - - public ManualLayout(String target, String[] modes, String[] offs) { - this.modes= modes; - this.target= target; - this.offs= offs; - } - public ManualLayout(ManualLayout ml) { - this.modes= ml.modes; - this.target= ml.target; - this.offs= ml.offs; - } - - + + private static final long serialVersionUID = 6460833211809500902L; + String[] modes; // xMode, yMode, wMode, hMode + String[] offs; // x, y, w, h; + String target; + + public ManualLayout(String target, String[] modes, String[] offs) { + this.modes = modes; + this.target = target; + this.offs = offs; + } + + public ManualLayout(ManualLayout ml) { + this.modes = ml.modes; + this.target = ml.target; + this.offs = ml.offs; + } + + public static ManualLayout parseOOXML(XmlPullParser xpp, Stack lastTag) { - String[] modes= new String[4]; // xMode, yMode, wMode, hMode - String[] offs= new String[4]; // x, y, w, h; - String target=null; - - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("manualLayout")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String n= xpp.getAttributeName(i); - if (n.equals("ATTR")) { - //sz= Integer.valueOf(xpp.getAttributeValue(i)).intValue(); - } - } - } else if (tnm.equals("layoutTarget")) { - target= xpp.getAttributeValue(0); - } else if (tnm.equals("xMode")) { - modes[0]= xpp.getAttributeValue(0); - } else if (tnm.equals("yMode")) { - modes[1]= xpp.getAttributeValue(0); - } else if (tnm.equals("wMode")) { - modes[2]= xpp.getAttributeValue(0); - } else if (tnm.equals("hMode")) { - modes[3]= xpp.getAttributeValue(0); - } else if (tnm.equals("x")) { - offs[0]= xpp.getAttributeValue(0); - } else if (tnm.equals("y")) { - offs[1]= xpp.getAttributeValue(0); - } else if (tnm.equals("w")) { - offs[2]= xpp.getAttributeValue(0); - } else if (tnm.equals("h")) { - offs[3]= xpp.getAttributeValue(0); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("manualLayout")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("manualLayout.parseOOXML: " + e.toString()); - } - ManualLayout l= new ManualLayout(target, modes, offs); - return l; + String[] modes = new String[4]; // xMode, yMode, wMode, hMode + String[] offs = new String[4]; // x, y, w, h; + String target = null; + + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("manualLayout")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String n = xpp.getAttributeName(i); + if (n.equals("ATTR")) { + //sz= Integer.valueOf(xpp.getAttributeValue(i)).intValue(); + } + } + } else if (tnm.equals("layoutTarget")) { + target = xpp.getAttributeValue(0); + } else if (tnm.equals("xMode")) { + modes[0] = xpp.getAttributeValue(0); + } else if (tnm.equals("yMode")) { + modes[1] = xpp.getAttributeValue(0); + } else if (tnm.equals("wMode")) { + modes[2] = xpp.getAttributeValue(0); + } else if (tnm.equals("hMode")) { + modes[3] = xpp.getAttributeValue(0); + } else if (tnm.equals("x")) { + offs[0] = xpp.getAttributeValue(0); + } else if (tnm.equals("y")) { + offs[1] = xpp.getAttributeValue(0); + } else if (tnm.equals("w")) { + offs[2] = xpp.getAttributeValue(0); + } else if (tnm.equals("h")) { + offs[3] = xpp.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("manualLayout")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("manualLayout.parseOOXML: " + e.toString()); + } + ManualLayout l = new ManualLayout(target, modes, offs); + return l; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (target != null) ooxml.append(""); + if (modes[0] != null) ooxml.append(""); + if (this.modes[1] != null) ooxml.append(""); + if (this.modes[2] != null) ooxml.append(""); + if (this.modes[3] != null) ooxml.append(""); + if (this.offs[0] != null) ooxml.append(""); + if (this.offs[1] != null) ooxml.append(""); + if (this.offs[2] != null) ooxml.append(""); + if (this.offs[3] != null) ooxml.append(""); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new ManualLayout(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (target!=null) ooxml.append(""); - if (modes[0]!=null) ooxml.append(""); - if (this.modes[1]!=null) ooxml.append(""); - if (this.modes[2]!=null) ooxml.append(""); - if (this.modes[3]!=null) ooxml.append(""); - if (this.offs[0]!=null) ooxml.append(""); - if (this.offs[1]!=null) ooxml.append(""); - if (this.offs[2]!=null) ooxml.append(""); - if (this.offs[3]!=null) ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ManualLayout(this); - } } diff --git a/src/main/java/io/starter/formats/OOXML/Legend.java b/src/main/java/io/starter/formats/OOXML/Legend.java index bec0e20..02ab887 100644 --- a/src/main/java/io/starter/formats/OOXML/Legend.java +++ b/src/main/java/io/starter/formats/OOXML/Legend.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,243 +22,245 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * OOXML Legend element - * + *

        * parent: chart * children: layout, legendEntry, legendPos, overlay, spPr, txPr - * - * - * */ -public class Legend implements OOXMLElement{ - - private static final long serialVersionUID = 419453456635220517L; - private String legendpos; // actually an element but only contains 1 attribute: val +public class Legend implements OOXMLElement { + + private static final long serialVersionUID = 419453456635220517L; + private String legendpos; // actually an element but only contains 1 attribute: val private LegendEntry le; private Layout layout; - private SpPr shapeProps; // defines the shape properties for the legend for this chart - private TxPr txpr; // defines text properties + private SpPr shapeProps; // defines the shape properties for the legend for this chart + private TxPr txpr; // defines text properties private String overlay; - - + + public Legend(String pos, String overlay, Layout l, LegendEntry le, SpPr sp, TxPr txpr) { - this.legendpos= pos; - this.le= le; - this.overlay= overlay; - this.layout= l; - this.shapeProps= sp; - this.txpr= txpr; + this.legendpos = pos; + this.le = le; + this.overlay = overlay; + this.layout = l; + this.shapeProps = sp; + this.txpr = txpr; } + public Legend(Legend l) { - this.legendpos= l.legendpos; - this.le= l.le; - this.overlay= l.overlay; - this.layout= l.layout; - this.shapeProps= l.shapeProps; - this.txpr= l.txpr; + this.legendpos = l.legendpos; + this.le = l.le; + this.overlay = l.overlay; + this.layout = l.layout; + this.shapeProps = l.shapeProps; + this.txpr = l.txpr; } + /** - * parse OOXML legend element + * parse OOXML legend element + * * @param xpp */ public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String legendpos= null; - LegendEntry le= null; - String overlay= null; - Layout layout= null; - SpPr shapeProps= null; // defines the shape properties for the legend for this chart - TxPr txpr= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("legendPos")) { - legendpos= xpp.getAttributeValue(0); - } else if (tnm.equals("layout")) { - lastTag.push(tnm); - layout = (Layout) Layout.parseOOXML(xpp, lastTag); - } else if (tnm.equals("legendEntry")) { - lastTag.push(tnm); - le= (LegendEntry) LegendEntry.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - shapeProps= (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - //shapeProps.setNS("c"); - } else if (tnm.equals("txPr")) { - lastTag.push(tnm); - txpr= (TxPr)TxPr.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("overlay")) { - overlay= xpp.getAttributeValue(0); - } - - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("legend")){ - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parseLegendElement: " + e.toString()); - } - Legend l= new Legend(legendpos, overlay, layout, le, shapeProps, txpr); - return l; - } - - + String legendpos = null; + LegendEntry le = null; + String overlay = null; + Layout layout = null; + SpPr shapeProps = null; // defines the shape properties for the legend for this chart + TxPr txpr = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("legendPos")) { + legendpos = xpp.getAttributeValue(0); + } else if (tnm.equals("layout")) { + lastTag.push(tnm); + layout = (Layout) Layout.parseOOXML(xpp, lastTag); + } else if (tnm.equals("legendEntry")) { + lastTag.push(tnm); + le = LegendEntry.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("spPr")) { + lastTag.push(tnm); + shapeProps = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); + //shapeProps.setNS("c"); + } else if (tnm.equals("txPr")) { + lastTag.push(tnm); + txpr = (TxPr) TxPr.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("overlay")) { + overlay = xpp.getAttributeValue(0); + } + + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("legend")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("OOXMLAdapter.parseLegendElement: " + e.toString()); + } + Legend l = new Legend(legendpos, overlay, layout, le, shapeProps, txpr); + return l; + } + + /** * fill 2003-style legend from OOXML Legend + * * @param l_2003 */ public void fill2003Legend(io.starter.formats.XLS.charts.Legend l_2003) { - // 0= bottom, 1= corner, 2= top, 3= right, 4= left, 7= not docked - String[] pos= { "b", "tr", "t", "r", "l"}; - for (int i= 0; i < pos.length; i++) { - if (pos[i].equals(legendpos)) { - l_2003.setLegendPosition((short)i); - break; - } - } - if (this.hasBox()) - l_2003.addBox(); - + // 0= bottom, 1= corner, 2= top, 3= right, 4= left, 7= not docked + String[] pos = {"b", "tr", "t", "r", "l"}; + for (int i = 0; i < pos.length; i++) { + if (pos[i].equals(legendpos)) { + l_2003.setLegendPosition((short) i); + break; + } + } + if (this.hasBox()) + l_2003.addBox(); + } - + /** * generate the ooxml necessary to display chart legend + * * @return */ public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - // sequence - if (legendpos!=null) ooxml.append(""); - if (le!=null) ooxml.append(le.getOOXML()); - if (layout!=null) ooxml.append(layout.getOOXML()); - if (overlay!=null) ooxml.append(""); - if (shapeProps!=null) ooxml.append(shapeProps.getOOXML()); - if (txpr!=null) ooxml.append(txpr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new Legend(this); - } - - /** - * returns true if this legend should have a box around it - * @return - */ - public boolean hasBox() { - if (this.shapeProps!=null) - return this.shapeProps.hasLine(); - return false; - } - - /** - * create an OOXML legend from a 2003-vers legend - * @param l - */ - public static Legend createLegend(io.starter.formats.XLS.charts.Legend l) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + // sequence + if (legendpos != null) ooxml.append(""); + if (le != null) ooxml.append(le.getOOXML()); + if (layout != null) ooxml.append(layout.getOOXML()); + if (overlay != null) ooxml.append(""); + if (shapeProps != null) ooxml.append(shapeProps.getOOXML()); + if (txpr != null) ooxml.append(txpr.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } - Legend ooxmllegend= null; - try { - SpPr sp= null; - sp= new SpPr("c"); - sp.setLine(3175, "000000"); - l.getFnt(); - ooxmllegend= new Legend(l.getLegendPositionString(), "1", null, null, sp, null); - } catch (Exception e) { - Logger.logWarn("Error creating 2007+ version Legend: " + e.toString()); - } - return ooxmllegend; - } + public OOXMLElement cloneElement() { + return new Legend(this); + } + + /** + * returns true if this legend should have a box around it + * + * @return + */ + public boolean hasBox() { + if (this.shapeProps != null) + return this.shapeProps.hasLine(); + return false; + } + + /** + * create an OOXML legend from a 2003-vers legend + * + * @param l + */ + public static Legend createLegend(io.starter.formats.XLS.charts.Legend l) { + + Legend ooxmllegend = null; + try { + SpPr sp = null; + sp = new SpPr("c"); + sp.setLine(3175, "000000"); + l.getFnt(); + ooxmllegend = new Legend(l.getLegendPositionString(), "1", null, null, sp, null); + } catch (Exception e) { + Logger.logWarn("Error creating 2007+ version Legend: " + e.toString()); + } + return ooxmllegend; + } } /** * legend Entry element - * + *

        * parent: legend * attributes; val= position of legend * children: idx, choice of: delete or txPr - * - * */ class LegendEntry implements OOXMLElement { - - private static final long serialVersionUID = 1859347855337611982L; - private TxPr tx; - private int idx= -1; - private boolean delete; - - public LegendEntry(int idx, boolean d, TxPr tx) { - this.idx= idx; - this.delete= d; - this.tx= tx; - } - - public LegendEntry(LegendEntry le) { - this.idx= le.idx; - this.delete= le.delete; - this.tx= le.tx; - } - - - public static LegendEntry parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - TxPr tx= null; - int idx= -1; - boolean delete= true; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("idx")) { - idx= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("delete")) { - delete= Boolean.valueOf(xpp.getAttributeValue(0)).booleanValue(); - } else if (tnm.equals("txPr")) { - lastTag.push(tnm); - tx= (TxPr)TxPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("legendEntry")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("legendEntry.parseOOXML: " + e.toString()); - } - LegendEntry le= new LegendEntry(idx, delete, tx); - return le; + + private static final long serialVersionUID = 1859347855337611982L; + private TxPr tx; + private int idx = -1; + private boolean delete; + + public LegendEntry(int idx, boolean d, TxPr tx) { + this.idx = idx; + this.delete = d; + this.tx = tx; + } + + public LegendEntry(LegendEntry le) { + this.idx = le.idx; + this.delete = le.delete; + this.tx = le.tx; } - - public String getOOXML() { - StringBuffer tooxml= new StringBuffer(); - tooxml.append(""); - tooxml.append(""); - if (!delete) tooxml.append(""); - if (tx!=null) tooxml.append(tx.getOOXML()); - tooxml.append(""); - return tooxml.toString(); - } - public OOXMLElement cloneElement() { - return new LegendEntry(this); - } + + public static LegendEntry parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + TxPr tx = null; + int idx = -1; + boolean delete = true; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("idx")) { + idx = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("delete")) { + delete = Boolean.valueOf(xpp.getAttributeValue(0)).booleanValue(); + } else if (tnm.equals("txPr")) { + lastTag.push(tnm); + tx = (TxPr) TxPr.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("legendEntry")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("legendEntry.parseOOXML: " + e.toString()); + } + LegendEntry le = new LegendEntry(idx, delete, tx); + return le; + } + + public String getOOXML() { + StringBuffer tooxml = new StringBuffer(); + tooxml.append(""); + tooxml.append(""); + if (!delete) tooxml.append(""); + if (tx != null) tooxml.append(tx.getOOXML()); + tooxml.append(""); + return tooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new LegendEntry(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Ln.java b/src/main/java/io/starter/formats/OOXML/Ln.java index 27fd1be..a05285b 100644 --- a/src/main/java/io/starter/formats/OOXML/Ln.java +++ b/src/main/java/io/starter/formats/OOXML/Ln.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,580 +22,594 @@ */ package io.starter.formats.OOXML; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; -import java.util.Stack; +import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; +import java.util.Stack; + /** * ln (Outline) - * + *

        * This OOXML/DrawingML element specifies an outline style that can be applied to a number of different objects such as shapes and * text. The line allows for the specifying of many different types of outlines including even line dashes and bevels - * + *

        * parent: many * children: bevel, custDash, gradFill, headEnd, miter, noFill, pattFill, prstDash, round, solidFill, tailEnd * attributes: w (line width) cap (line ending cap) cmpd (compound line type) algn (stroke alignment) - * - * */ // TODO: Finish custDash -public class Ln implements OOXMLElement{ - - private static final long serialVersionUID = -161619607936083688L; - private HashMap attrs= null; - private FillGroup fill= null; - private JoinGroup join= null; - private DashGroup dash= null; - private HeadEnd h= null; - private TailEnd t= null; - - /** - * create a new solid line in width w with color clr - * @param w int specifies the width of a line in EMUs. 1 pt = 12700 EMUs - * @param clr String hex color without the # - */ - public Ln(int w, String clr) { - this.fill= new FillGroup(null, null, null, null, new SolidFill(clr)); - this.dash= new DashGroup(new PrstDash("solid")); - this.setWidth(w); - } - public Ln() { // no-param constructor, set up common defaults - this.fill= new FillGroup(null, null, null, null, new SolidFill()); - this.join= new JoinGroup("800000", true, false, false); - this.h= new HeadEnd(); - this.t= new TailEnd(); - } - - public Ln(HashMap attrs, FillGroup fill, JoinGroup join, DashGroup dash, HeadEnd h, TailEnd t) { - this.attrs= attrs; - this.fill= fill; - this.join= join; - this.dash= dash; - this.t= t; - this.h= h; - } - public Ln(Ln l) { - this.attrs= l.attrs; - this.fill= l.fill; - this.join= l.join; - this.dash= l.dash; - this.t= l.t; - this.h= l.h; - } - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs= new HashMap(); - FillGroup fill= null; - JoinGroup join= null; - DashGroup dash= null; - HeadEnd h= null; - TailEnd t= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ln")) { // get ln attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("noFill") || - tnm.equals("solidFill") || - tnm.equals("pattFill") || - tnm.equals("gradFill")) { - lastTag.push(tnm); - fill= (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("bevel") || - tnm.equals("round") || - tnm.equals("miter")) { - lastTag.push(tnm); - join= (JoinGroup) JoinGroup.parseOOXML(xpp, lastTag); - } else if (tnm.equals("prstDash") || - tnm.equals("custDash")) { - lastTag.push(tnm); - dash= (DashGroup) DashGroup.parseOOXML(xpp, lastTag); - } else if (tnm.equals("headEnd")) { - lastTag.push(tnm); - h= (HeadEnd) HeadEnd.parseOOXML(xpp, lastTag); - } else if (tnm.equals("tailEnd")) { - lastTag.push(tnm); - t= (TailEnd) TailEnd.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("ln")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ln.parseOOXML: " + e.toString()); - } - Ln l= new Ln(attrs, fill, join, dash, h, t); - return l; - } - - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (fill!=null) ooxml.append(fill.getOOXML()); - if (dash!=null) ooxml.append(dash.getOOXML()); - if (join!=null) ooxml.append(join.getOOXML()); - if (h!=null) ooxml.append(h.getOOXML()); - if (t!=null) ooxml.append(t.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Ln(this); - } - - /** - * set the line width - * @param w - */ - public void setWidth(int w) { - if (attrs==null) - attrs= new HashMap(); - attrs.put("w", String.valueOf(w)); - - } - - /** - * return line width in 2003-style units, 0 for none - * @return - */ - public int getWidth() { - if (attrs!=null && attrs.get("w")!=null) - // Specifies the width to be used for the underline stroke. - // If this attribute is omitted, then a value of 0 is assumed. - return Integer.valueOf((String) attrs.get("w"))/12700; // 1 pt = 12700 EMUs. - return 0; // default - } - - public int getColor() { - if (fill==null) - return -1; - return fill.getColor(); - } - - public void setColor(String clr) { - if (fill==null) - this.fill= new FillGroup(null, null, null, null, new SolidFill()); - fill.setColor(clr); - } - - /** - * convert 2007 preset dasing scheme to 2003 line stype - *
        0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, - *6= dk gray pattern, 7= med. gray, 8= light gray - - * @return - */ - public int getLineStyle() { - if (dash!=null) { - String style= dash.getPresetDashingScheme(); - if (style.equals("solid")) - return 0; // solid - else if (style.equals("dash") || - style.equals("sysDash") || - style.equals("lgDash")) - return 1; - else if (style.equals("sysDot")) - return 2; - else if (style.equals("dashDot") || - style.equals("sysDashDot") || - style.equals("lgDashDot")) - return 3; - else if (style.equals("sysDashDashDot") || - style.equals("lgDashDotDot")) - return 4; - } - // ? none = 5 ??? - return 0; // solid - } +public class Ln implements OOXMLElement { + + private static final long serialVersionUID = -161619607936083688L; + private HashMap attrs = null; + private FillGroup fill = null; + private JoinGroup join = null; + private DashGroup dash = null; + private HeadEnd h = null; + private TailEnd t = null; + + /** + * create a new solid line in width w with color clr + * + * @param w int specifies the width of a line in EMUs. 1 pt = 12700 EMUs + * @param clr String hex color without the # + */ + public Ln(int w, String clr) { + this.fill = new FillGroup(null, null, null, null, new SolidFill(clr)); + this.dash = new DashGroup(new PrstDash("solid")); + this.setWidth(w); + } + + public Ln() { // no-param constructor, set up common defaults + this.fill = new FillGroup(null, null, null, null, new SolidFill()); + this.join = new JoinGroup("800000", true, false, false); + this.h = new HeadEnd(); + this.t = new TailEnd(); + } + + public Ln(HashMap attrs, FillGroup fill, JoinGroup join, DashGroup dash, HeadEnd h, TailEnd t) { + this.attrs = attrs; + this.fill = fill; + this.join = join; + this.dash = dash; + this.t = t; + this.h = h; + } + + public Ln(Ln l) { + this.attrs = l.attrs; + this.fill = l.fill; + this.join = l.join; + this.dash = l.dash; + this.t = l.t; + this.h = l.h; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + HashMap attrs = new HashMap(); + FillGroup fill = null; + JoinGroup join = null; + DashGroup dash = null; + HeadEnd h = null; + TailEnd t = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("ln")) { // get ln attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("noFill") || + tnm.equals("solidFill") || + tnm.equals("pattFill") || + tnm.equals("gradFill")) { + lastTag.push(tnm); + fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("bevel") || + tnm.equals("round") || + tnm.equals("miter")) { + lastTag.push(tnm); + join = JoinGroup.parseOOXML(xpp, lastTag); + } else if (tnm.equals("prstDash") || + tnm.equals("custDash")) { + lastTag.push(tnm); + dash = DashGroup.parseOOXML(xpp, lastTag); + } else if (tnm.equals("headEnd")) { + lastTag.push(tnm); + h = HeadEnd.parseOOXML(xpp, lastTag); + } else if (tnm.equals("tailEnd")) { + lastTag.push(tnm); + t = TailEnd.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("ln")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("ln.parseOOXML: " + e.toString()); + } + Ln l = new Ln(attrs, fill, join, dash, h, t); + return l; + } + + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (fill != null) ooxml.append(fill.getOOXML()); + if (dash != null) ooxml.append(dash.getOOXML()); + if (join != null) ooxml.append(join.getOOXML()); + if (h != null) ooxml.append(h.getOOXML()); + if (t != null) ooxml.append(t.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Ln(this); + } + + /** + * set the line width + * + * @param w + */ + public void setWidth(int w) { + if (attrs == null) + attrs = new HashMap(); + attrs.put("w", String.valueOf(w)); + + } + + /** + * return line width in 2003-style units, 0 for none + * + * @return + */ + public int getWidth() { + if (attrs != null && attrs.get("w") != null) + // Specifies the width to be used for the underline stroke. + // If this attribute is omitted, then a value of 0 is assumed. + return Integer.valueOf(attrs.get("w")) / 12700; // 1 pt = 12700 EMUs. + return 0; // default + } + + public int getColor() { + if (fill == null) + return -1; + return fill.getColor(); + } + + public void setColor(String clr) { + if (fill == null) + this.fill = new FillGroup(null, null, null, null, new SolidFill()); + fill.setColor(clr); + } + + /** + * convert 2007 preset dasing scheme to 2003 line stype + *
        0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, + * 6= dk gray pattern, 7= med. gray, 8= light gray + * + * @return + */ + public int getLineStyle() { + if (dash != null) { + String style = dash.getPresetDashingScheme(); + if (style.equals("solid")) + return 0; // solid + else if (style.equals("dash") || + style.equals("sysDash") || + style.equals("lgDash")) + return 1; + else if (style.equals("sysDot")) + return 2; + else if (style.equals("dashDot") || + style.equals("sysDashDot") || + style.equals("lgDashDot")) + return 3; + else if (style.equals("sysDashDashDot") || + style.equals("lgDashDotDot")) + return 4; + } + // ? none = 5 ??? + return 0; // solid + } } /** * line dash group: prstDash, custDash - * */ // TODO: Finish custDash -class DashGroup implements OOXMLElement{ - - private static final long serialVersionUID = -6892326040716070609L; - private PrstDash prstDash; - - public DashGroup(PrstDash p) { - this.prstDash= p; - } - public DashGroup(DashGroup d) { - this.prstDash= d.prstDash; - } - - +class DashGroup implements OOXMLElement { + + private static final long serialVersionUID = -6892326040716070609L; + private PrstDash prstDash; + + public DashGroup(PrstDash p) { + this.prstDash = p; + } + + public DashGroup(DashGroup d) { + this.prstDash = d.prstDash; + } + + public static DashGroup parseOOXML(XmlPullParser xpp, Stack lastTag) { - PrstDash p= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (/*tnm.equals("custDash") ||*/ - tnm.equals("prstDash")) { - p= (PrstDash) PrstDash.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("custDash") || - endTag.equals("prstDash")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("custDash.parseOOXML: " + e.toString()); - } - DashGroup dg= new DashGroup(p); - return dg; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - if (prstDash!=null) ooxml.append(prstDash.getOOXML()); - // TODO: finish custDash - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new DashGroup(this); - } - - /** - * returns the preset dashing scheme for the line, if any -
        One of: -
        dash, dashDot, lgDash, lgDashDot, lgDashDotDot, solid, sysDash, sysDashDot, - sysDashDotDot, sysDot - * @return - */ - public String getPresetDashingScheme() { - if (prstDash!=null) - return prstDash.getPresetDashingScheme(); - return null; - } + PrstDash p = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (/*tnm.equals("custDash") ||*/ + tnm.equals("prstDash")) { + p = PrstDash.parseOOXML(xpp, lastTag); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("custDash") || + endTag.equals("prstDash")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("custDash.parseOOXML: " + e.toString()); + } + DashGroup dg = new DashGroup(p); + return dg; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + if (prstDash != null) ooxml.append(prstDash.getOOXML()); + // TODO: finish custDash + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new DashGroup(this); + } + + /** + * returns the preset dashing scheme for the line, if any + *
        One of: + *
        dash, dashDot, lgDash, lgDashDot, lgDashDotDot, solid, sysDash, sysDashDot, + * sysDashDotDot, sysDot + * + * @return + */ + public String getPresetDashingScheme() { + if (prstDash != null) + return prstDash.getPresetDashingScheme(); + return null; + } } - + /** * choice of miter, bevel or round - * + *

        * parent: ln - * */ /* since each child element is so simple, we will just store which element it is rather than create separate object */ class JoinGroup implements OOXMLElement { - - private static final long serialVersionUID = -6107424300366896696L; - private boolean miter; - private boolean round; - private boolean bevel; - private String miterVal; - - public JoinGroup(String a, boolean m, boolean r, boolean b) { - this.miterVal= a; - this.miter= m; - this.round= r; - this.bevel= b; - } - public JoinGroup(JoinGroup j) { - this.miterVal= j.miterVal; - this.miter= j.miter; - this.round= j.round; - this.bevel= j.bevel; - } - + + private static final long serialVersionUID = -6107424300366896696L; + private boolean miter; + private boolean round; + private boolean bevel; + private String miterVal; + + public JoinGroup(String a, boolean m, boolean r, boolean b) { + this.miterVal = a; + this.miter = m; + this.round = r; + this.bevel = b; + } + + public JoinGroup(JoinGroup j) { + this.miterVal = j.miterVal; + this.miter = j.miter; + this.round = j.round; + this.bevel = j.bevel; + } + public static JoinGroup parseOOXML(XmlPullParser xpp, Stack lastTag) { - boolean miter= false; - boolean round= false; - boolean bevel= false; - String a= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bevel")) { - bevel= true; - } else if (tnm.equals("round")) { - round= true; - } else if (tnm.equals("miter")) { - miter= true; - a= xpp.getAttributeValue(0); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("bevel") || - endTag.equals("round") || - endTag.equals("miter")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("JoinGroup.parseOOXML: " + e.toString()); - } - JoinGroup jg= new JoinGroup(a, miter, round, bevel); - return jg; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - if (miter) { - ooxml.append(""); - } else if (round) { - ooxml.append(""); - } else if (bevel) { - ooxml.append(""); - } - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new JoinGroup(this); - } + boolean miter = false; + boolean round = false; + boolean bevel = false; + String a = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("bevel")) { + bevel = true; + } else if (tnm.equals("round")) { + round = true; + } else if (tnm.equals("miter")) { + miter = true; + a = xpp.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("bevel") || + endTag.equals("round") || + endTag.equals("miter")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("JoinGroup.parseOOXML: " + e.toString()); + } + JoinGroup jg = new JoinGroup(a, miter, round, bevel); + return jg; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + if (miter) { + ooxml.append(""); + } else if (round) { + ooxml.append(""); + } else if (bevel) { + ooxml.append(""); + } + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new JoinGroup(this); + } } /** * headEnd (Line Head/End Style) - * - * This element specifies decorations which can be added to the head of a line. - * + *

        + * This element specifies decorations which can be added to the head of a line. */ -class HeadEnd implements OOXMLElement{ - private static final long serialVersionUID = -6744308104003922477L; - private String len= null; - private String type= null; - private String w= null; - - public HeadEnd() { - - } - - public HeadEnd(String len, String type, String w) { - this.len= len; - this.type= type; - this.w= w; - } - public HeadEnd(HeadEnd te) { - this.len= te.len; - this.type= te.type; - this.w= te.w; - } - +class HeadEnd implements OOXMLElement { + private static final long serialVersionUID = -6744308104003922477L; + private String len = null; + private String type = null; + private String w = null; + + public HeadEnd() { + + } + + public HeadEnd(String len, String type, String w) { + this.len = len; + this.type = type; + this.w = w; + } + + public HeadEnd(HeadEnd te) { + this.len = te.len; + this.type = te.type; + this.w = te.w; + } + public static HeadEnd parseOOXML(XmlPullParser xpp, Stack lastTag) { - String len= null; - String type= null; - String w= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("headEnd")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String n= xpp.getAttributeName(i); - if (n.equals("len")) - len= xpp.getAttributeValue(i); - else if (n.equals("type")) - type= xpp.getAttributeValue(i); - else if (n.equals("w")) - w= xpp.getAttributeValue(i); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("headEnd")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("headEnd.parseOOXML: " + e.toString()); - } - HeadEnd te= new HeadEnd(len, type, w); - return te; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new HeadEnd(this); - } + String len = null; + String type = null; + String w = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("headEnd")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String n = xpp.getAttributeName(i); + if (n.equals("len")) + len = xpp.getAttributeValue(i); + else if (n.equals("type")) + type = xpp.getAttributeValue(i); + else if (n.equals("w")) + w = xpp.getAttributeValue(i); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("headEnd")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("headEnd.parseOOXML: " + e.toString()); + } + HeadEnd te = new HeadEnd(len, type, w); + return te; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new HeadEnd(this); + } } /** * tailEnd (Tail line end style) - * -* This element specifies decorations which can be added to the tail of a line. - * + *

        + * This element specifies decorations which can be added to the tail of a line. */ class TailEnd implements OOXMLElement { - private static final long serialVersionUID = -5587427916156543370L; - private String len= null; - private String type= null; - private String w= null; - - public TailEnd() { } - public TailEnd(String len, String type, String w) { - this.len= len; - this.type= type; - this.w= w; - } - public TailEnd(TailEnd te) { - this.len= te.len; - this.type= te.type; - this.w= te.w; - } + private static final long serialVersionUID = -5587427916156543370L; + private String len = null; + private String type = null; + private String w = null; + + public TailEnd() { + } + + public TailEnd(String len, String type, String w) { + this.len = len; + this.type = type; + this.w = w; + } + + public TailEnd(TailEnd te) { + this.len = te.len; + this.type = te.type; + this.w = te.w; + } public static TailEnd parseOOXML(XmlPullParser xpp, Stack lastTag) { - String len= null; - String type= null; - String w= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tailEnd")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String n= xpp.getAttributeName(i); - if (n.equals("len")) - len= xpp.getAttributeValue(i); - else if (n.equals("type")) - type= xpp.getAttributeValue(i); - else if (n.equals("w")) - w= xpp.getAttributeValue(i); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("tailEnd")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("tailEnd.parseOOXML: " + e.toString()); - } - TailEnd te= new TailEnd(len, type, w); - return te; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TailEnd(this); - } + String len = null; + String type = null; + String w = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("tailEnd")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String n = xpp.getAttributeName(i); + if (n.equals("len")) + len = xpp.getAttributeValue(i); + else if (n.equals("type")) + type = xpp.getAttributeValue(i); + else if (n.equals("w")) + w = xpp.getAttributeValue(i); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("tailEnd")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("tailEnd.parseOOXML: " + e.toString()); + } + TailEnd te = new TailEnd(len, type, w); + return te; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new TailEnd(this); + } } class PrstDash implements OOXMLElement { - - private static final long serialVersionUID = -4645986946936173151L; - private String val= null; - public PrstDash() { } - public PrstDash(String val) { - this.val= val; - } - public PrstDash(PrstDash p) { - this.val= p.val; - } - - + + private static final long serialVersionUID = -4645986946936173151L; + private String val = null; + + public PrstDash() { + } + + public PrstDash(String val) { + this.val = val; + } + + public PrstDash(PrstDash p) { + this.val = p.val; + } + + public static PrstDash parseOOXML(XmlPullParser xpp, Stack lastTag) { - String val= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstDash")) { // get val attribute - val= xpp.getAttributeValue(0); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("prstDash")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("prstDash.parseOOXML: " + e.toString()); - } - PrstDash p= new PrstDash(val); - return p; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PrstDash(this); - } - /** - * returns the preset dashing scheme for the line, if any -
        One of: -
        dash, dashDot, lgDash, lgDashDot, lgDashDotDot, solid, sysDash, sysDashDot, - sysDashDotDot, sysDot - * @return - */ - public String getPresetDashingScheme() { - return val; - } + String val = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("prstDash")) { // get val attribute + val = xpp.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("prstDash")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("prstDash.parseOOXML: " + e.toString()); + } + PrstDash p = new PrstDash(val); + return p; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new PrstDash(this); + } + + /** + * returns the preset dashing scheme for the line, if any + *
        One of: + *
        dash, dashDot, lgDash, lgDashDot, lgDashDotDot, solid, sysDash, sysDashDot, + * sysDashDotDot, sysDot + * + * @return + */ + public String getPresetDashingScheme() { + return val; + } } diff --git a/src/main/java/io/starter/formats/OOXML/Marker.java b/src/main/java/io/starter/formats/OOXML/Marker.java index e8fb63b..404d106 100644 --- a/src/main/java/io/starter/formats/OOXML/Marker.java +++ b/src/main/java/io/starter/formats/OOXML/Marker.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,92 +22,92 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * class holds OOXML to define markers for a chart (radar, scatter or line) - * + *

        * parent: dPt, Ser ... * children: symbol, size, spPr * NOTE: child elements symbol and size have only 1 attribute and no children, and so are treated as strings - * */ -public final class Marker implements OOXMLElement{ +public final class Marker implements OOXMLElement { + + private static final long serialVersionUID = -5070227633357072878L; + private SpPr sp; + private String size; + private String symbol; - private static final long serialVersionUID = -5070227633357072878L; - private SpPr sp; - private String size; - private String symbol; - - public Marker(String s, String sz, SpPr sp) { - this.symbol= s; - this.size= sz; - this.sp= sp; - } + public Marker(String s, String sz, SpPr sp) { + this.symbol = s; + this.size = sz; + this.sp = sp; + } + + public Marker(Marker m) { + this.symbol = m.symbol; + this.size = m.size; + this.sp = m.sp; + } - public Marker(Marker m) { - this.symbol= m.symbol; - this.size= m.size; - this.sp= m.sp; - } - - /** + /** * parse marker OOXML element - * @param xpp XmlPullParser - * @param lastTag element stack - * @return marker object + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return marker object */ public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - SpPr sp=null; - String size=null; // size element: val is only attribute + no children - String symbol=null; // symbol element: val is only attribute + no children - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("symbol")) { - symbol= xpp.getAttributeValue(0); - } else if (tnm.equals("size")) { - size= xpp.getAttributeValue(0); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp= (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - //sp.setNS("c"); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("marker")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("marker.parseOOXML: " + e.toString()); - } - Marker m= new Marker(symbol, size, sp); - return m; + SpPr sp = null; + String size = null; // size element: val is only attribute + no children + String symbol = null; // symbol element: val is only attribute + no children + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("symbol")) { + symbol = xpp.getAttributeValue(0); + } else if (tnm.equals("size")) { + size = xpp.getAttributeValue(0); + } else if (tnm.equals("spPr")) { + lastTag.push(tnm); + sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); + //sp.setNS("c"); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("marker")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("marker.parseOOXML: " + e.toString()); + } + Marker m = new Marker(symbol, size, sp); + return m; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (this.symbol != null) + ooxml.append(""); + if (this.size != null) + ooxml.append(""); + if (this.sp != null) ooxml.append(sp.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Marker(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (this.symbol!=null) - ooxml.append(""); - if (this.size!=null) - ooxml.append(""); - if (this.sp!=null) ooxml.append(sp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Marker(this); - } } diff --git a/src/main/java/io/starter/formats/OOXML/NumFmt.java b/src/main/java/io/starter/formats/OOXML/NumFmt.java index 9129b5d..a3fd2e0 100644 --- a/src/main/java/io/starter/formats/OOXML/NumFmt.java +++ b/src/main/java/io/starter/formats/OOXML/NumFmt.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,16 +22,16 @@ */ package io.starter.formats.OOXML; -import org.xmlpull.v1.XmlPullParser; import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.OOXMLAdapter; import io.starter.formats.XLS.Xf; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; +import org.xmlpull.v1.XmlPullParser; /** * numFmt OOXML element - * + *

        * numFmt (Number Format) * This element specifies number format properties which indicate how to format and render the numeric value of * a cell. @@ -39,103 +39,107 @@ * the file. In this case a numFmtId value is written on the xf record, but no corresponding numFmt element is * written. Some of these Ids are interpreted differently, depending on the UI language of the implementing * application. - * + *

        * parent: styleSheet/numFmts element in styles.xml * NOTE: numFmt element also occurs in drawingML, numFmt is replaced with sourceLinked * children: none - * */ -public class NumFmt implements OOXMLElement{ +public class NumFmt implements OOXMLElement { + + private static final long serialVersionUID = -206715418106414662L; + private String formatCode, numFmtId; + private boolean sourceLinked = false; + + public NumFmt(String formatCode, String numFmtId, boolean sourceLinked) { + this.formatCode = formatCode; + this.numFmtId = numFmtId; + this.sourceLinked = sourceLinked; + } + + public NumFmt(NumFmt n) { + this.formatCode = n.formatCode; + this.numFmtId = n.numFmtId; + this.sourceLinked = n.sourceLinked; + } - private static final long serialVersionUID = -206715418106414662L; - private String formatCode, numFmtId; - private boolean sourceLinked= false; - - public NumFmt(String formatCode, String numFmtId, boolean sourceLinked) { - this.formatCode= formatCode; - this.numFmtId= numFmtId; - this.sourceLinked= sourceLinked; - } - public NumFmt(NumFmt n) { - this.formatCode= n.formatCode; - this.numFmtId= n.numFmtId; - this.sourceLinked= n.sourceLinked; - } - public static OOXMLElement parseOOXML(XmlPullParser xpp) { - String formatCode= null, numFmtId= null; - boolean sourceLinked= false; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("numFmt")) { - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String n= xpp.getAttributeName(i); - String v= xpp.getAttributeValue(i); - if (n.equals("formatCode")) { - formatCode= v; - } else if (n.equals("numFmtId")) - numFmtId= v; - else if (n.equals("sourceLinked")) - sourceLinked= (v.equals("1")); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("numFmt")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("numFmt.parseOOXML: " + e.toString()); - } - NumFmt oe= new NumFmt(formatCode, numFmtId, sourceLinked); - return oe; + String formatCode = null, numFmtId = null; + boolean sourceLinked = false; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("numFmt")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String n = xpp.getAttributeName(i); + String v = xpp.getAttributeValue(i); + if (n.equals("formatCode")) { + formatCode = v; + } else if (n.equals("numFmtId")) + numFmtId = v; + else if (n.equals("sourceLinked")) + sourceLinked = (v.equals("1")); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("numFmt")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("numFmt.parseOOXML: " + e.toString()); + } + NumFmt oe = new NumFmt(formatCode, numFmtId, sourceLinked); + return oe; + } + + public String getOOXML() { + return getOOXML(""); + } + + public String getOOXML(String ns) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append("<" + ns + "numFmt"); + // attributes + ooxml.append(" formatCode=\"" + OOXMLAdapter.stripNonAscii(formatCode) + "\""); + if (numFmtId != null) ooxml.append(" numFmtId=\"" + numFmtId + "\""); + if (sourceLinked) ooxml.append(" sourceLinked=\"1\""); + ooxml.append("/>"); + return ooxml.toString(); } - - public String getOOXML() { - return getOOXML(""); - } - public String getOOXML(String ns) { - StringBuffer ooxml= new StringBuffer(); - ooxml.append("<" + ns + "numFmt"); - // attributes - ooxml.append(" formatCode=\"" + OOXMLAdapter.stripNonAscii(formatCode) + "\""); - if (numFmtId!=null) ooxml.append(" numFmtId=\"" + numFmtId + "\""); - if (sourceLinked) ooxml.append(" sourceLinked=\"1\""); - ooxml.append("/>"); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new NumFmt(this); - } - - /** - * returns the format id assoc with this number format - * @return - */ - public String getFormatId() { - return numFmtId; - } + public OOXMLElement cloneElement() { + return new NumFmt(this); + } + + /** + * returns the format id assoc with this number format + * + * @return + */ + public String getFormatId() { + return numFmtId; + } - - /** - * returns the OOXML specifying the fill based on this FormatHandle object - */ - public static String getOOXML(Xf xf) { - // Number Format - StringBuffer ooxml= new StringBuffer(); - if (xf.getIfmt()>FormatConstants.BUILTIN_FORMATS.length) { // only input user defined formats ... - String s= xf.getFormatPattern(); - if (s!=null) s= StringTool.replaceText(s, "\"", """); // replace internal quotes // 1.6 only s= s.replace('"', """); // replace internal quotes - ooxml.append(""); ooxml.append("\r\n"); - } - return ooxml.toString(); - } + + /** + * returns the OOXML specifying the fill based on this FormatHandle object + */ + public static String getOOXML(Xf xf) { + // Number Format + StringBuffer ooxml = new StringBuffer(); + if (xf.getIfmt() > FormatConstants.BUILTIN_FORMATS.length) { // only input user defined formats ... + String s = xf.getFormatPattern(); + if (s != null) + s = StringTool.replaceText(s, "\"", """); // replace internal quotes // 1.6 only s= s.replace('"', """); // replace internal quotes + ooxml.append(""); + ooxml.append("\r\n"); + } + return ooxml.toString(); + } } diff --git a/src/main/java/io/starter/formats/OOXML/OOXMLConstants.java b/src/main/java/io/starter/formats/OOXML/OOXMLConstants.java index 03c72c8..58d0e86 100644 --- a/src/main/java/io/starter/formats/OOXML/OOXMLConstants.java +++ b/src/main/java/io/starter/formats/OOXML/OOXMLConstants.java @@ -2,26 +2,26 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ /** - * + * */ package io.starter.formats.OOXML; @@ -29,238 +29,238 @@ * */ public interface OOXMLConstants { - public static final String xmlHeader= ""; - public static final String xmlns= "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; - public static final String pkgrelns= "http://schemas.openxmlformats.org/package/2006/relationships"; // .rels ns - public static final String relns= "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; - public static final String typens= "http://schemas.openxmlformats.org/package/2006/content-types"; - public static final String drawingns= "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"; - public static final String drawingmlns= "http://schemas.openxmlformats.org/drawingml/2006/main"; - public static final String chartns= "http://schemas.openxmlformats.org/drawingml/2006/chart"; - public static final String drawingDir= "xl/drawings"; - public static final String mediaDir= "xl/media"; - public static final String chartDir= "xl/charts"; - public static final String themeDir= "xl/theme"; - - public static final String[][] contentTypes= { - {"document", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"}, - {"documentMacroEnabled", "application/vnd.ms-excel.sheet.macroEnabled.main+xml"}, - {"documentTemplateMacroEnabled", "application/vnd.ms-excel.template.macroEnabled.main+xml"}, - {"documentTemplate", "application/vnd.openxmlformats-officedocument.spreadsheetml.template+xml"}, /* TODO: is this correct?? */ - {"sheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"}, - {"styles", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"}, - {"sst", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"}, - {"drawing", "application/vnd.openxmlformats-officedocument.drawing+xml"}, - {"chart", "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"}, - {"theme", "application/vnd.openxmlformats-officedocument.theme+xml"}, - {"themeOverride", "application/vnd.openxmlformats-officedocument.themeOverride+xml"}, - {"props", "application/vnd.openxmlformats-package.core-properties+xml"}, - {"exprops", "application/vnd.openxmlformats-officedocument.extended-properties+xml"}, - {"custprops", "application/vnd.openxmlformats-officedocument.custom-properties+xml"}, - {"connections", "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"}, - {"calc", "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"}, - {"vba", "application/vnd.ms-office.vbaProject"}, - {"vdependencies", "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"}, - {"table", "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"}, - {"vmldrawing", "application/vnd.openxmlformats-officedocument.vmlDrawing"}, - {"externalLink","application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"}, - {"oleObject", "application/vnd.openxmlformats-officedocument.oleObject"}, - {"activeX", "application/vnd.ms-office.activeX+xml"}, - {"comments", "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"}, - {"image", "image"}, // images don't go into Content_Types - {"userShape", "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"}, - {"activeXBinary", "application/vnd.ms-office.activeX"}, - {"pivotCacheDefinition", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"}, - {"pivotCacheRecords", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"}, - {"pivotTable", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"}, - }; - - public static final String[][] relsContentTypes= { - {"document", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, - {"documentMacroEnabled", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, - {"documentTemplate", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, - {"documentTemplateMacroEnabled", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, - {"sheet", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"}, - {"styles", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"}, - {"sst", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"}, - {"drawing", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"}, - {"image", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"}, - {"chart", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"}, - {"theme", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"}, - {"themeOverride", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride"}, - {"props", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"}, - {"exprops", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"}, - {"custprops", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"}, - {"connections", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections"}, - {"calc", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain"}, - {"printerSettings", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings"}, - {"vba", "http://schemas.microsoft.com/office/2006/relationships/vbaProject"}, - {"vdependencies", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies"}, - {"table", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"}, - {"vmldrawing", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"}, - {"externalLink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink"}, - {"externalLinkPath", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLinkPath"}, - {"macro", "http://schemas.microsoft.com/office/2006/relationships/xlMacrosheet"}, - {"hyperlink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"}, - {"activeX", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/control"}, - {"oleObject", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"}, - {"comments", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"}, - {"userShape", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes"}, - {"activeXBinary", "http://schemas.microsoft.com/office/2006/relationships/activeXControlBinary"}, - {"pivotCacheDefinition", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"}, - {"pivotCacheRecords", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords"}, - {"pivotTable", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"}, - }; - - public static final String[] builtInNames= { - "_xlnm.Consolidate_Area", - "", //protected static final byte AUTO_OPEN = 0x1; - "", //protected static final byte AUTO_CLOSE = 0x2; - "_xlnm.Extract", - "_xlnm.Database", - "_xlnm.Criteria", - "_xlnm.Print_Area", - "_xlnm.Print_Titles", - "", //protected static final byte RECORDER = 0x8; - "", //protected static final byte DATA_FORM = 0x9; - "", //protected static final byte AUTO_ACTIVATE = 0xA; - "", //protected static final byte AUTO_DEACTIVATE = 0xB; - "_xlnm.Sheet_Title", - "_xlnm._FilterDatabase", - }; - - public static final String[] patternFill = { - "none", - "solid", - "mediumGray", - "darkGray", - "lightGray", - "darkHorizontal", - "darkVertical", - "darkDown", - "darkUp", - "darkGrid", - "darkTrellis", - "lightHorizontal", - "lightVertical", - "lightDown", - "lightUp", - "lightGrid", - "lightTrellis", - "gray125", - "gray0625", - }; - public static final String[] horizontalAlignment= { - "general", - "left", - "center", - "right", - "fill", - "justify", - "centerContinuous", // 6 - "distributed", - }; - - public static final String[] verticalAlignment= { - "top", - "center", - "bottom", - "justify", - "distributed", - }; - - public static final String[] fontScheme= { - "none", - "major", - "minor", - }; - public static final String[] cellType= { - "b", // boolean - "n", // number - "e", // error - "s", // shared (sst) string - "str", // formula string - "inlineStr", // - }; - public static final String[] cellFormulaType= { - "normal", - "array", - "dataTable", - "shared", - }; - public static final String[] borderStyle= { - "none", - "thin", - "medium", - "dashed", - "dotted", - "thick", - "double", - "hair", - "mediumDashed", - "dashDot", - "mediumDashDot", - "dashDotDot", - "mediumDashDotDot", - "slantDashDot", - }; + String xmlHeader = ""; + String xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; + String pkgrelns = "http://schemas.openxmlformats.org/package/2006/relationships"; // .rels ns + String relns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; + String typens = "http://schemas.openxmlformats.org/package/2006/content-types"; + String drawingns = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"; + String drawingmlns = "http://schemas.openxmlformats.org/drawingml/2006/main"; + String chartns = "http://schemas.openxmlformats.org/drawingml/2006/chart"; + String drawingDir = "xl/drawings"; + String mediaDir = "xl/media"; + String chartDir = "xl/charts"; + String themeDir = "xl/theme"; - public static final String[] fontFamily= { - "Roman", //1 - "Swiss", - "Modern", - "Script", - "Decorative", - }; - /** - * indexed via ChartConstants Chart Types - * @see ChartHandle.getOOXML - */ - public static final String[] twoDchartTypes= { - "barChart", // including col and all shaped bar types such as pyramid, cone ... - "barChart", - "lineChart", // 2 - "pieChart", // 3 - "areaChart", // 4 - "scatterChart", // 5 - "radarChart", // 6 - "surfaceChart", // 7 - "doughnutChart", // 8 - "bubbleChart", // 9 - "ofPieChart", // 10 - "", // radararea - not used in OOXML - "", // pyrmaid == bar in OOXML - "", /// cylinder "" - "", // cone "" - "", // pyramidbar "" 15 - "", // cylinderbar "" - "", // condebar "" - "stockChart" - }; - /** - * indexed via ChartConstants Chart Types - * @see ChartHandle.getOOXML - */ - public static final String[] threeDchartTypes= { - "bar3DChart", // including col and all shaped bar types such as pyramid, cone ... - "bar3DChart", - "line3DChart", - "pie3DChart", - "area3DChart", - "", // scatter - 5 no 3d - "", // radar - 6 no 3d - "surface3DChart", // 7 - "", // doughnut - 8 - no 3d - "", // bubble - 9 - no 3d - "", // ofPie 10 - no 3d - }; - public static final String[] legendPos={ - "b", - "l", - "r", // default - "t", - "tr", - }; + String[][] contentTypes = { + {"document", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"}, + {"documentMacroEnabled", "application/vnd.ms-excel.sheet.macroEnabled.main+xml"}, + {"documentTemplateMacroEnabled", "application/vnd.ms-excel.template.macroEnabled.main+xml"}, + {"documentTemplate", "application/vnd.openxmlformats-officedocument.spreadsheetml.template+xml"}, /* TODO: is this correct?? */ + {"sheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"}, + {"styles", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"}, + {"sst", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"}, + {"drawing", "application/vnd.openxmlformats-officedocument.drawing+xml"}, + {"chart", "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"}, + {"theme", "application/vnd.openxmlformats-officedocument.theme+xml"}, + {"themeOverride", "application/vnd.openxmlformats-officedocument.themeOverride+xml"}, + {"props", "application/vnd.openxmlformats-package.core-properties+xml"}, + {"exprops", "application/vnd.openxmlformats-officedocument.extended-properties+xml"}, + {"custprops", "application/vnd.openxmlformats-officedocument.custom-properties+xml"}, + {"connections", "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"}, + {"calc", "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"}, + {"vba", "application/vnd.ms-office.vbaProject"}, + {"vdependencies", "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"}, + {"table", "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"}, + {"vmldrawing", "application/vnd.openxmlformats-officedocument.vmlDrawing"}, + {"externalLink", "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"}, + {"oleObject", "application/vnd.openxmlformats-officedocument.oleObject"}, + {"activeX", "application/vnd.ms-office.activeX+xml"}, + {"comments", "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"}, + {"image", "image"}, // images don't go into Content_Types + {"userShape", "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"}, + {"activeXBinary", "application/vnd.ms-office.activeX"}, + {"pivotCacheDefinition", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"}, + {"pivotCacheRecords", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"}, + {"pivotTable", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"}, + }; + + String[][] relsContentTypes = { + {"document", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, + {"documentMacroEnabled", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, + {"documentTemplate", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, + {"documentTemplateMacroEnabled", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, + {"sheet", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"}, + {"styles", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"}, + {"sst", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"}, + {"drawing", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"}, + {"image", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"}, + {"chart", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"}, + {"theme", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"}, + {"themeOverride", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride"}, + {"props", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"}, + {"exprops", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"}, + {"custprops", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"}, + {"connections", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections"}, + {"calc", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain"}, + {"printerSettings", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings"}, + {"vba", "http://schemas.microsoft.com/office/2006/relationships/vbaProject"}, + {"vdependencies", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies"}, + {"table", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"}, + {"vmldrawing", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"}, + {"externalLink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink"}, + {"externalLinkPath", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLinkPath"}, + {"macro", "http://schemas.microsoft.com/office/2006/relationships/xlMacrosheet"}, + {"hyperlink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"}, + {"activeX", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/control"}, + {"oleObject", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"}, + {"comments", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"}, + {"userShape", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes"}, + {"activeXBinary", "http://schemas.microsoft.com/office/2006/relationships/activeXControlBinary"}, + {"pivotCacheDefinition", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"}, + {"pivotCacheRecords", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords"}, + {"pivotTable", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"}, + }; + + String[] builtInNames = { + "_xlnm.Consolidate_Area", + "", //protected static final byte AUTO_OPEN = 0x1; + "", //protected static final byte AUTO_CLOSE = 0x2; + "_xlnm.Extract", + "_xlnm.Database", + "_xlnm.Criteria", + "_xlnm.Print_Area", + "_xlnm.Print_Titles", + "", //protected static final byte RECORDER = 0x8; + "", //protected static final byte DATA_FORM = 0x9; + "", //protected static final byte AUTO_ACTIVATE = 0xA; + "", //protected static final byte AUTO_DEACTIVATE = 0xB; + "_xlnm.Sheet_Title", + "_xlnm._FilterDatabase", + }; + + String[] patternFill = { + "none", + "solid", + "mediumGray", + "darkGray", + "lightGray", + "darkHorizontal", + "darkVertical", + "darkDown", + "darkUp", + "darkGrid", + "darkTrellis", + "lightHorizontal", + "lightVertical", + "lightDown", + "lightUp", + "lightGrid", + "lightTrellis", + "gray125", + "gray0625", + }; + String[] horizontalAlignment = { + "general", + "left", + "center", + "right", + "fill", + "justify", + "centerContinuous", // 6 + "distributed", + }; + + String[] verticalAlignment = { + "top", + "center", + "bottom", + "justify", + "distributed", + }; + + String[] fontScheme = { + "none", + "major", + "minor", + }; + String[] cellType = { + "b", // boolean + "n", // number + "e", // error + "s", // shared (sst) string + "str", // formula string + "inlineStr", // + }; + String[] cellFormulaType = { + "normal", + "array", + "dataTable", + "shared", + }; + String[] borderStyle = { + "none", + "thin", + "medium", + "dashed", + "dotted", + "thick", + "double", + "hair", + "mediumDashed", + "dashDot", + "mediumDashDot", + "dashDotDot", + "mediumDashDotDot", + "slantDashDot", + }; + + String[] fontFamily = { + "Roman", //1 + "Swiss", + "Modern", + "Script", + "Decorative", + }; + /** + * indexed via ChartConstants Chart Types + * @see ChartHandle.getOOXML + */ + String[] twoDchartTypes = { + "barChart", // including col and all shaped bar types such as pyramid, cone ... + "barChart", + "lineChart", // 2 + "pieChart", // 3 + "areaChart", // 4 + "scatterChart", // 5 + "radarChart", // 6 + "surfaceChart", // 7 + "doughnutChart", // 8 + "bubbleChart", // 9 + "ofPieChart", // 10 + "", // radararea - not used in OOXML + "", // pyrmaid == bar in OOXML + "", /// cylinder "" + "", // cone "" + "", // pyramidbar "" 15 + "", // cylinderbar "" + "", // condebar "" + "stockChart" + }; + /** + * indexed via ChartConstants Chart Types + * @see ChartHandle.getOOXML + */ + String[] threeDchartTypes = { + "bar3DChart", // including col and all shaped bar types such as pyramid, cone ... + "bar3DChart", + "line3DChart", + "pie3DChart", + "area3DChart", + "", // scatter - 5 no 3d + "", // radar - 6 no 3d + "surface3DChart", // 7 + "", // doughnut - 8 - no 3d + "", // bubble - 9 - no 3d + "", // ofPie 10 - no 3d + }; + String[] legendPos = { + "b", + "l", + "r", // default + "t", + "tr", + }; /* public static String[] indexedColors= { "00000000", @@ -326,47 +326,47 @@ public interface OOXMLConstants { "00333399", "00333333", }; -*/ - - /** - * System Colors - * note some of these are guesswork - * @see - * */ - public static String[][] systemColors= { - {"3dDkShadow", "#A0A0A0"},/*(3D Dark System Color) Specifies a Dark shadow color for three-dimensional display elements.*/ - {"3dLight", "#FFFFFF"}, /* (3D Light System Color) Specifies a Light color for three-dimensional display elements (for edges facing the light source).*/ - {"activeBorder", "#B4B4B4"}, /* (Active Border System Color) Specifies an Active Window Border Color.*/ - {"activeCaption", "#99B4D1"}, /* (Active Caption System Color) Specifies the active window title bar color. In particular the left side color in the color gradient of an active +*/ + + /** + * System Colors + * note some of these are guesswork + * @see + * */ + String[][] systemColors = { + {"3dDkShadow", "#A0A0A0"},/*(3D Dark System Color) Specifies a Dark shadow color for three-dimensional display elements.*/ + {"3dLight", "#FFFFFF"}, /* (3D Light System Color) Specifies a Light color for three-dimensional display elements (for edges facing the light source).*/ + {"activeBorder", "#B4B4B4"}, /* (Active Border System Color) Specifies an Active Window Border Color.*/ + {"activeCaption", "#99B4D1"}, /* (Active Caption System Color) Specifies the active window title bar color. In particular the left side color in the color gradient of an active window's title bar if the gradient effect is enabled.*/ - {"appWorkspace", "#ABABAB"}, /* (Application Workspace System Color) Specifies the Background color of multiple document interface (MDI) applications.*/ - {"background", "#FFFFFF"}, /* (Background System Color) Specifies the desktop background color.*/ - {"btnFace", "#F0F0F0"}, /* (Button Face System Color) Specifies the face color for three-dimensional display elements and for dialog box backgrounds.*/ - {"btnHighlight", "#FFFFFF"}, /* (Button Highlight System Color) Specifies the highlight color for three-dimensional display elements (for edges facing the light source).*/ - {"btnShadow", "#A0A0A0"}, /* (Button Shadow System Color) Specifies the shadow color for three-dimensional display elements (for edges facing away from the light source).*/ - {"btnText", "#000000"}, /* (Button Text System Color) Specifies the color of text on push buttons.*/ - {"captionText", "#000000"}, /* (Caption Text System Color) Specifies the color of text in the caption, size box, and scroll bar arrow box.*/ - {"gradientActiveCaption", "#B9D1EA"}, /* (Gradient Active Caption System Color) Specifies the right side color in the color gradient of an active window's title bar.*/ - {"gradientInactiveCaption", "#D7E4F2"}, /* (Gradient Inactive Caption System Color) Specifies the right side color in the color gradient of an inactive window's title bar.*/ - {"grayText", "#6D6D6D"}, /* (Gray Text System Color) Specifies a grayed (disabled) text. This color is set to 0 if the current display driver does not support a solid gray color.*/ - {"highlight", "#0000FF"}, /* (Highlight System Color) Specifies the color of Item(s) selected in a control.*/ - {"highlightText", "#FFFFFF"}, /* (Highlight Text System Color) Specifies the text color of item(s) selected in a control.*/ - {"hotLight", "#0066CC"}, /* (Hot Light System Color) Specifies the color for a hyperlink or hot-tracked item.*/ - {"inactiveBorder", "#F4F7FC"}, /* (Inactive Border System Color) Specifies the color of the Inactive window border.*/ - {"inactiveCaption", "#BFCDDB"}, /* (Inactive Caption System Color) Specifies the color of the Inactive window caption. Specifies the left side color in the color gradient of an + {"appWorkspace", "#ABABAB"}, /* (Application Workspace System Color) Specifies the Background color of multiple document interface (MDI) applications.*/ + {"background", "#FFFFFF"}, /* (Background System Color) Specifies the desktop background color.*/ + {"btnFace", "#F0F0F0"}, /* (Button Face System Color) Specifies the face color for three-dimensional display elements and for dialog box backgrounds.*/ + {"btnHighlight", "#FFFFFF"}, /* (Button Highlight System Color) Specifies the highlight color for three-dimensional display elements (for edges facing the light source).*/ + {"btnShadow", "#A0A0A0"}, /* (Button Shadow System Color) Specifies the shadow color for three-dimensional display elements (for edges facing away from the light source).*/ + {"btnText", "#000000"}, /* (Button Text System Color) Specifies the color of text on push buttons.*/ + {"captionText", "#000000"}, /* (Caption Text System Color) Specifies the color of text in the caption, size box, and scroll bar arrow box.*/ + {"gradientActiveCaption", "#B9D1EA"}, /* (Gradient Active Caption System Color) Specifies the right side color in the color gradient of an active window's title bar.*/ + {"gradientInactiveCaption", "#D7E4F2"}, /* (Gradient Inactive Caption System Color) Specifies the right side color in the color gradient of an inactive window's title bar.*/ + {"grayText", "#6D6D6D"}, /* (Gray Text System Color) Specifies a grayed (disabled) text. This color is set to 0 if the current display driver does not support a solid gray color.*/ + {"highlight", "#0000FF"}, /* (Highlight System Color) Specifies the color of Item(s) selected in a control.*/ + {"highlightText", "#FFFFFF"}, /* (Highlight Text System Color) Specifies the text color of item(s) selected in a control.*/ + {"hotLight", "#0066CC"}, /* (Hot Light System Color) Specifies the color for a hyperlink or hot-tracked item.*/ + {"inactiveBorder", "#F4F7FC"}, /* (Inactive Border System Color) Specifies the color of the Inactive window border.*/ + {"inactiveCaption", "#BFCDDB"}, /* (Inactive Caption System Color) Specifies the color of the Inactive window caption. Specifies the left side color in the color gradient of an inactive window's title bar if the gradient effect is enabled.*/ - {"inactiveCaptionText", "#434E54"}, /* (Inactive Caption Text System Color) Specifies the color of text in an inactive caption.*/ - {"infoBk", "#FFFFE1"}, /* (Info Back System Color) Specifies the background color for tooltip controls.*/ - {"infoText", "#000000"}, /* (Info Text System Color) Specifies the text color for tooltip controls.*/ - {"menu", "#F0F0F0"}, /* (Menu System Color) Specifies the menu background color. */ - {"menuBar", "#F0F0F0"}, /* (Menu Bar System Color) Specifies the background color for the menu bar when menus appear as flat menus.*/ - {"menuHighlight", "#3399FF"}, /* (Menu Highlight System Color) Specifies the color used to highlight menu items when the menu appears as a flat menu.*/ - {"menuText", "#000000" }, /* (Menu Text System Color) Specifies the color of Text in menus.*/ - {"scrollBar", "#C8C8C8"}, /* (Scroll Bar System Color) Specifies the scroll bar gray area color.*/ - {"window", "#FFFFFF"}, /* (Window System Color) Specifies window background color.*/ - {"windowFrame", "#646464"}, /* (Window Frame System Color) Specifies the window frame color.*/ - {"windowText", "#000000"} /* (Window Text System Color) Specifies the color of text in windows.*/ - - }; - + {"inactiveCaptionText", "#434E54"}, /* (Inactive Caption Text System Color) Specifies the color of text in an inactive caption.*/ + {"infoBk", "#FFFFE1"}, /* (Info Back System Color) Specifies the background color for tooltip controls.*/ + {"infoText", "#000000"}, /* (Info Text System Color) Specifies the text color for tooltip controls.*/ + {"menu", "#F0F0F0"}, /* (Menu System Color) Specifies the menu background color. */ + {"menuBar", "#F0F0F0"}, /* (Menu Bar System Color) Specifies the background color for the menu bar when menus appear as flat menus.*/ + {"menuHighlight", "#3399FF"}, /* (Menu Highlight System Color) Specifies the color used to highlight menu items when the menu appears as a flat menu.*/ + {"menuText", "#000000"}, /* (Menu Text System Color) Specifies the color of Text in menus.*/ + {"scrollBar", "#C8C8C8"}, /* (Scroll Bar System Color) Specifies the scroll bar gray area color.*/ + {"window", "#FFFFFF"}, /* (Window System Color) Specifies window background color.*/ + {"windowFrame", "#646464"}, /* (Window Frame System Color) Specifies the window frame color.*/ + {"windowText", "#000000"} /* (Window Text System Color) Specifies the color of text in windows.*/ + + }; + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/OOXMLElement.java b/src/main/java/io/starter/formats/OOXML/OOXMLElement.java index e2730f0..52b4e08 100644 --- a/src/main/java/io/starter/formats/OOXML/OOXMLElement.java +++ b/src/main/java/io/starter/formats/OOXML/OOXMLElement.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,15 +24,14 @@ import java.io.Serializable; -public interface OOXMLElement extends Serializable{ - /** +public interface OOXMLElement extends Serializable { + /** * @see io.starter.formats.OOXML.OOXMLElement#getOOXML() */ - public String getOOXML(); + String getOOXML(); - /** - * + /** * @see io.starter.formats.OOXML.OOXMLElement#cloneElement() */ - public OOXMLElement cloneElement(); + OOXMLElement cloneElement(); } diff --git a/src/main/java/io/starter/formats/OOXML/OOXMLHandle.java b/src/main/java/io/starter/formats/OOXML/OOXMLHandle.java index bc32a0b..1c97079 100644 --- a/src/main/java/io/starter/formats/OOXML/OOXMLHandle.java +++ b/src/main/java/io/starter/formats/OOXML/OOXMLHandle.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,63 +22,61 @@ */ package io.starter.formats.OOXML; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -import org.xmlpull.v1.XmlPullParserException; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.CellNotFoundException; import io.starter.formats.XLS.OOXMLReader; import io.starter.formats.XLS.OOXMLWriter; import io.starter.toolkit.StringTool; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; /** - * - * * */ public class OOXMLHandle { - - /** - * generates OOXML for a workbook - * Creates the ZIP file and writes all files into proper directory structure - * Will create either an .xlsx or an .xlsm output, depending upon - * whether WorkBookHandle bk contains macros - * - * @param bk workbookhandle - * @param path output filename and path - */ - public static void getOOXML(WorkBookHandle bk, String path) - throws IOException{ - try { - if (!io.starter.formats.XLS.OOXMLAdapter.hasMacros(bk)) - path= StringTool.replaceExtension(path, ".xlsx"); - else // it's a macro-enabled workbook - path= StringTool.replaceExtension(path, ".xlsm"); - - java.io.File fout = new java.io.File(path); - File dirs= fout.getParentFile(); - if (dirs!=null && !dirs.exists()) - dirs.mkdirs(); - OOXMLWriter oe = new OOXMLWriter(); - oe.getOOXML(bk, new FileOutputStream(path)); - }catch(Exception e) { - throw new IOException("Error parsing OOXML file: " + e.toString()); - } - } - /** - * OOXML parseNBind - reads in an OOXML (Excel 7) workbook - * @param bk WorkBookHandle - workbook to input - * @param fName OOXML filename (must be a ZIP file in OPC format) - * @throws XmlPullParserException - * @throws IOException - * @throws CellNotFoundException - */ - public static void parseNBind(WorkBookHandle bk, String fName) throws XmlPullParserException, IOException, CellNotFoundException { - OOXMLReader oe = new OOXMLReader(); - oe.parseNBind(bk, fName); - } + /** + * generates OOXML for a workbook + * Creates the ZIP file and writes all files into proper directory structure + * Will create either an .xlsx or an .xlsm output, depending upon + * whether WorkBookHandle bk contains macros + * + * @param bk workbookhandle + * @param path output filename and path + */ + public static void getOOXML(WorkBookHandle bk, String path) + throws IOException { + try { + if (!io.starter.formats.XLS.OOXMLAdapter.hasMacros(bk)) + path = StringTool.replaceExtension(path, ".xlsx"); + else // it's a macro-enabled workbook + path = StringTool.replaceExtension(path, ".xlsm"); + + java.io.File fout = new java.io.File(path); + File dirs = fout.getParentFile(); + if (dirs != null && !dirs.exists()) + dirs.mkdirs(); + OOXMLWriter oe = new OOXMLWriter(); + oe.getOOXML(bk, new FileOutputStream(path)); + } catch (Exception e) { + throw new IOException("Error parsing OOXML file: " + e.toString()); + } + } + + /** + * OOXML parseNBind - reads in an OOXML (Excel 7) workbook + * + * @param bk WorkBookHandle - workbook to input + * @param fName OOXML filename (must be a ZIP file in OPC format) + * @throws XmlPullParserException + * @throws IOException + * @throws CellNotFoundException + */ + public static void parseNBind(WorkBookHandle bk, String fName) throws XmlPullParserException, IOException, CellNotFoundException { + OOXMLReader oe = new OOXMLReader(); + oe.parseNBind(bk, fName); + } } diff --git a/src/main/java/io/starter/formats/OOXML/ObjectChoice.java b/src/main/java/io/starter/formats/OOXML/ObjectChoice.java index 20879a7..c28d4ac 100644 --- a/src/main/java/io/starter/formats/OOXML/ObjectChoice.java +++ b/src/main/java/io/starter/formats/OOXML/ObjectChoice.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,394 +22,416 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * One of: cxnSp, graphicFrame, grpSp, pic, sp - * */ -public class ObjectChoice implements OOXMLElement{ - - private static final long serialVersionUID = 3548474869557092714L; - private CxnSp cxnSp= null; - private GraphicFrame graphicFrame= null; - private GrpSp grpSp= null; - private Pic pic= null; - private Sp sp= null; - - public ObjectChoice() { - - } - - public ObjectChoice(CxnSp c, GraphicFrame g, GrpSp grp, Pic p, Sp s) { - this.cxnSp= c; - this.graphicFrame= g; - this.grpSp= grp; - this.pic= p; - this.sp= s; - } - public ObjectChoice(ObjectChoice oc) { - this.cxnSp= oc.cxnSp; - this.graphicFrame= oc.graphicFrame; - this.grpSp= oc.grpSp; - this.pic= oc.pic; - this.sp= oc.sp; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - CxnSp c= null; - GraphicFrame g= null; - GrpSp grp= null; - Pic p= null; - Sp s= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cxnSp")) { // connection shape - lastTag.push(tnm); - c= (CxnSp) CxnSp.parseOOXML(xpp, lastTag, bk); - break; - } else if (tnm.equals("graphicFrame")) { // graphic data usually chart - lastTag.push(tnm); - g= (GraphicFrame) GraphicFrame.parseOOXML(xpp, lastTag); - break; - } else if (tnm.equals("grpSp")) { // group shape - combines one or more of sp/pic/graphicFrame/cxnSp - lastTag.push(tnm); - grp= (GrpSp) GrpSp.parseOOXML(xpp, lastTag, bk); - break; - } else if (tnm.equals("sp")) { // shape - lastTag.push(tnm); - s= (Sp) Sp.parseOOXML(xpp, lastTag, bk); - break; - } else if (tnm.equals("pic")) { // picture/image - lastTag.push(tnm); - p= (Pic) Pic.parseOOXML(xpp, lastTag, bk); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ObjectChoice.parseOOXML: " + e.toString()); - } - ObjectChoice o= new ObjectChoice(c, g, grp, p, s); - return o; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - if (cxnSp!=null) ooxml.append(cxnSp.getOOXML()); - if (graphicFrame!=null) ooxml.append(graphicFrame.getOOXML()); - if (grpSp!=null) ooxml.append(grpSp.getOOXML()); - if (pic!=null) ooxml.append(pic.getOOXML()); - if (sp!=null) ooxml.append(sp.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ObjectChoice(this); - } - - /** - * return if this Object Choice refers to an image rather than a chart or shape - * @return - */ - public boolean hasImage() { - // o will be a pic element or a group shape containing a pic element, it's blipFill.blip child references the rId of the embedded file - if (this.getEmbed()!=null) - return true; - return false; - } - - /** - * return if this Object Choice refers to a shape, as opposed a chart or an image - * @return - */ - public boolean hasShape() { - return (cxnSp!=null || sp!=null || (grpSp!=null && grpSp.hasShape())); - } - - /** - * return if this Object Choice element refers to a chart as opposed to a shape or image - * @return - */ - public boolean hasChart() { - return this.getChartRId()!=null; - } - - /** - * get cNvPr name attribute - * @return - */ - public String getName() { - if (cxnSp!=null) - return cxnSp.getName(); - else if (sp!=null) - return sp.getName(); - else if (pic!=null) - return pic.getName(); - else if (graphicFrame!=null) - return graphicFrame.getName(); - else if (grpSp!=null) - return grpSp.getName(); - return null; - } - - /** - * set cNvPr name attribute - * @param name - */ - public void setName(String name) { - if (cxnSp!=null) - cxnSp.setName(name); - else if (sp!=null) - sp.setName(name); - else if (pic!=null) - pic.setName(name); - else if (graphicFrame!=null) - graphicFrame.setName(name); - else if (grpSp!=null) - grpSp.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (cxnSp!=null) - return cxnSp.getDescr(); - else if (sp!=null) - return sp.getDescr(); - else if (pic!=null) - return pic.getDescr(); - else if (graphicFrame!=null) - return graphicFrame.getDescr(); - else if (grpSp!=null) - return grpSp.getDescr(); - return null; - } - /** - * set cNvPr description attribute - * sometimes associated with shape name - * @param descr - */ - public void setDescr(String descr) { - if (cxnSp!=null) - cxnSp.setDescr(descr); - else if (sp!=null) - sp.setDescr(descr); - else if (pic!=null) - pic.setDescr(descr); - else if (graphicFrame!=null) - graphicFrame.setDescr(descr); - else if (grpSp!=null) - grpSp.setDescr(descr); - } - - /** - * get macro attribute (valid for cnxSp, sp and graphicFrame) - * @return - */ - public String getMacro() { - if (cxnSp!=null) - return cxnSp.getMacro(); - else if (graphicFrame!=null) - return graphicFrame.getMacro(); - else if (sp!=null) - return sp.getMacro(); - return null; - } - /** - * set Macro attribute (valid for cnxSp, sp and graphicFrame) - * sometimes associated with shape name - * @param descr - */ - public void setMacro(String macro) { - if (cxnSp!=null) - cxnSp.setMacro(macro); - else if (graphicFrame!=null) - graphicFrame.setMacro(macro); - else if (sp!=null) - sp.setMacro(macro); - else if (grpSp!=null) - grpSp.setMacro(macro); - } - - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (graphicFrame!=null) - return graphicFrame.getURI(); - return null; - } - - /** - * set the URI associated with this graphic data - * @param uri - */ - public void setURI(String uri) { - if (graphicFrame!=null) - graphicFrame.setURI(uri); - else if (grpSp!=null) - grpSp.setURI(uri); - } - /** - * return the rid for the embedded object (picture or picture shape) (i.e. resides within the file) - * @return - */ - public String getEmbed() { - if (sp!=null) - return sp.getEmbed(); // embedded blip/pict - else if (pic!=null) - return pic.getEmbed(); // embedded image - else if (grpSp!=null) - return grpSp.getEmbed(); // group shape embedded image - return null; - } - - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * @return - */ - public String getLink() { - if (sp!=null) - return sp.getLink(); - else if (pic!=null) - return pic.getLink(); - else if (grpSp!=null) - return grpSp.getLink(); - return null; - } - - /** - * set the rid for this object (picture or picture shape) (resides within the file) - * @param embed - */ - public void setEmbed(String embed) { - if (sp!=null) sp.setEmbed(embed); - else if (pic!=null) pic.setEmbed(embed); - else if (grpSp!=null) grpSp.setEmbed(embed); - } - - /** - * set the rid for this chart (resides within the file) - * @param rId - */ - public void setChartRId(String rId) { - if (graphicFrame!=null) graphicFrame.setChartRId(rId); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * @param link - */ - public void setLink(String link) { - if (sp!=null) sp.setLink(link); - else if (pic!=null) pic.setLink(link); - else if (grpSp!=null) grpSp.setLink(link); - } - - /** - * return the rid of the chart element, if exists - * @return - */ - public String getChartRId() { - if (graphicFrame!=null) - return graphicFrame.getChartRId(); - else if (grpSp!=null) - return grpSp.getChartRId(); - return null; - } - - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (sp!=null) - sp.setId(id); // embedded blip/pict - else if (pic!=null) - pic.setId(id); // embedded image - else if (graphicFrame!=null) - graphicFrame.setId(id); // chart - else if (cxnSp!=null) - cxnSp.setId(id); - else if (grpSp!=null) - grpSp.setId(id); // embedded image - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (sp!=null) return sp.getId(); // embedded blip/pict - else if (pic!=null) - return pic.getId(); // embedded image - else if (graphicFrame!=null) - return graphicFrame.getId(); // chart - else if (cxnSp!=null) - return cxnSp.getId(); - else if (grpSp!=null) - return grpSp.getId(); // embedded image - return -1; - } - - /** utility to return the shape properties element for this picture - * should be depreciated when OOXML is completely distinct from BIFF8 - * @return - */ - public SpPr getSppr() { - if (pic!=null) - return pic.getSppr(); - else if (grpSp!=null) - return grpSp.getSppr(); - return null; - } - - /** - * return the actual object associated with this ObjectChoice - * @return - */ - public Object getObject() { - if (cxnSp!=null) - return cxnSp; - else if (graphicFrame!=null) - return graphicFrame; - else if (pic!=null) - return pic; - else if (sp!=null) - return sp; - else if (grpSp!=null) - return grpSp; - return null; - } - - /** - * set the object associated with this ObjectChoice - * @param o - */ - public void setObject(Object o) { - if (o instanceof GraphicFrame) { - graphicFrame= (GraphicFrame) o; - } else if (o instanceof Pic) { - pic= (Pic) o; - } else if (o instanceof Sp) { - sp= (Sp) o; - } else if (o instanceof CxnSp) { - cxnSp= (CxnSp) o; - } else if (o instanceof GrpSp) { - grpSp= (GrpSp) o; - } - } +public class ObjectChoice implements OOXMLElement { + + private static final long serialVersionUID = 3548474869557092714L; + private CxnSp cxnSp = null; + private GraphicFrame graphicFrame = null; + private GrpSp grpSp = null; + private Pic pic = null; + private Sp sp = null; + + public ObjectChoice() { + + } + + public ObjectChoice(CxnSp c, GraphicFrame g, GrpSp grp, Pic p, Sp s) { + this.cxnSp = c; + this.graphicFrame = g; + this.grpSp = grp; + this.pic = p; + this.sp = s; + } + + public ObjectChoice(ObjectChoice oc) { + this.cxnSp = oc.cxnSp; + this.graphicFrame = oc.graphicFrame; + this.grpSp = oc.grpSp; + this.pic = oc.pic; + this.sp = oc.sp; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + CxnSp c = null; + GraphicFrame g = null; + GrpSp grp = null; + Pic p = null; + Sp s = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cxnSp")) { // connection shape + lastTag.push(tnm); + c = (CxnSp) CxnSp.parseOOXML(xpp, lastTag, bk); + break; + } else if (tnm.equals("graphicFrame")) { // graphic data usually chart + lastTag.push(tnm); + g = (GraphicFrame) GraphicFrame.parseOOXML(xpp, lastTag); + break; + } else if (tnm.equals("grpSp")) { // group shape - combines one or more of sp/pic/graphicFrame/cxnSp + lastTag.push(tnm); + grp = GrpSp.parseOOXML(xpp, lastTag, bk); + break; + } else if (tnm.equals("sp")) { // shape + lastTag.push(tnm); + s = (Sp) Sp.parseOOXML(xpp, lastTag, bk); + break; + } else if (tnm.equals("pic")) { // picture/image + lastTag.push(tnm); + p = (Pic) Pic.parseOOXML(xpp, lastTag, bk); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("ObjectChoice.parseOOXML: " + e.toString()); + } + ObjectChoice o = new ObjectChoice(c, g, grp, p, s); + return o; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + if (cxnSp != null) ooxml.append(cxnSp.getOOXML()); + if (graphicFrame != null) ooxml.append(graphicFrame.getOOXML()); + if (grpSp != null) ooxml.append(grpSp.getOOXML()); + if (pic != null) ooxml.append(pic.getOOXML()); + if (sp != null) ooxml.append(sp.getOOXML()); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new ObjectChoice(this); + } + + /** + * return if this Object Choice refers to an image rather than a chart or shape + * + * @return + */ + public boolean hasImage() { + // o will be a pic element or a group shape containing a pic element, it's blipFill.blip child references the rId of the embedded file + return this.getEmbed() != null; + } + + /** + * return if this Object Choice refers to a shape, as opposed a chart or an image + * + * @return + */ + public boolean hasShape() { + return (cxnSp != null || sp != null || (grpSp != null && grpSp.hasShape())); + } + + /** + * return if this Object Choice element refers to a chart as opposed to a shape or image + * + * @return + */ + public boolean hasChart() { + return this.getChartRId() != null; + } + + /** + * get cNvPr name attribute + * + * @return + */ + public String getName() { + if (cxnSp != null) + return cxnSp.getName(); + else if (sp != null) + return sp.getName(); + else if (pic != null) + return pic.getName(); + else if (graphicFrame != null) + return graphicFrame.getName(); + else if (grpSp != null) + return grpSp.getName(); + return null; + } + + /** + * set cNvPr name attribute + * + * @param name + */ + public void setName(String name) { + if (cxnSp != null) + cxnSp.setName(name); + else if (sp != null) + sp.setName(name); + else if (pic != null) + pic.setName(name); + else if (graphicFrame != null) + graphicFrame.setName(name); + else if (grpSp != null) + grpSp.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (cxnSp != null) + return cxnSp.getDescr(); + else if (sp != null) + return sp.getDescr(); + else if (pic != null) + return pic.getDescr(); + else if (graphicFrame != null) + return graphicFrame.getDescr(); + else if (grpSp != null) + return grpSp.getDescr(); + return null; + } + + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setDescr(String descr) { + if (cxnSp != null) + cxnSp.setDescr(descr); + else if (sp != null) + sp.setDescr(descr); + else if (pic != null) + pic.setDescr(descr); + else if (graphicFrame != null) + graphicFrame.setDescr(descr); + else if (grpSp != null) + grpSp.setDescr(descr); + } + + /** + * get macro attribute (valid for cnxSp, sp and graphicFrame) + * + * @return + */ + public String getMacro() { + if (cxnSp != null) + return cxnSp.getMacro(); + else if (graphicFrame != null) + return graphicFrame.getMacro(); + else if (sp != null) + return sp.getMacro(); + return null; + } + + /** + * set Macro attribute (valid for cnxSp, sp and graphicFrame) + * sometimes associated with shape name + * + * @param descr + */ + public void setMacro(String macro) { + if (cxnSp != null) + cxnSp.setMacro(macro); + else if (graphicFrame != null) + graphicFrame.setMacro(macro); + else if (sp != null) + sp.setMacro(macro); + else if (grpSp != null) + grpSp.setMacro(macro); + } + + /** + * get the URI associated with this graphic Data + */ + public String getURI() { + if (graphicFrame != null) + return graphicFrame.getURI(); + return null; + } + + /** + * set the URI associated with this graphic data + * + * @param uri + */ + public void setURI(String uri) { + if (graphicFrame != null) + graphicFrame.setURI(uri); + else if (grpSp != null) + grpSp.setURI(uri); + } + + /** + * return the rid for the embedded object (picture or picture shape) (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (sp != null) + return sp.getEmbed(); // embedded blip/pict + else if (pic != null) + return pic.getEmbed(); // embedded image + else if (grpSp != null) + return grpSp.getEmbed(); // group shape embedded image + return null; + } + + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (sp != null) + return sp.getLink(); + else if (pic != null) + return pic.getLink(); + else if (grpSp != null) + return grpSp.getLink(); + return null; + } + + /** + * set the rid for this object (picture or picture shape) (resides within the file) + * + * @param embed + */ + public void setEmbed(String embed) { + if (sp != null) sp.setEmbed(embed); + else if (pic != null) pic.setEmbed(embed); + else if (grpSp != null) grpSp.setEmbed(embed); + } + + /** + * set the rid for this chart (resides within the file) + * + * @param rId + */ + public void setChartRId(String rId) { + if (graphicFrame != null) graphicFrame.setChartRId(rId); + } + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param link + */ + public void setLink(String link) { + if (sp != null) sp.setLink(link); + else if (pic != null) pic.setLink(link); + else if (grpSp != null) grpSp.setLink(link); + } + + /** + * return the rid of the chart element, if exists + * + * @return + */ + public String getChartRId() { + if (graphicFrame != null) + return graphicFrame.getChartRId(); + else if (grpSp != null) + return grpSp.getChartRId(); + return null; + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (sp != null) + sp.setId(id); // embedded blip/pict + else if (pic != null) + pic.setId(id); // embedded image + else if (graphicFrame != null) + graphicFrame.setId(id); // chart + else if (cxnSp != null) + cxnSp.setId(id); + else if (grpSp != null) + grpSp.setId(id); // embedded image + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (sp != null) return sp.getId(); // embedded blip/pict + else if (pic != null) + return pic.getId(); // embedded image + else if (graphicFrame != null) + return graphicFrame.getId(); // chart + else if (cxnSp != null) + return cxnSp.getId(); + else if (grpSp != null) + return grpSp.getId(); // embedded image + return -1; + } + + /** + * utility to return the shape properties element for this picture + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + public SpPr getSppr() { + if (pic != null) + return pic.getSppr(); + else if (grpSp != null) + return grpSp.getSppr(); + return null; + } + + /** + * return the actual object associated with this ObjectChoice + * + * @return + */ + public Object getObject() { + if (cxnSp != null) + return cxnSp; + else if (graphicFrame != null) + return graphicFrame; + else if (pic != null) + return pic; + else if (sp != null) + return sp; + else if (grpSp != null) + return grpSp; + return null; + } + + /** + * set the object associated with this ObjectChoice + * + * @param o + */ + public void setObject(Object o) { + if (o instanceof GraphicFrame) { + graphicFrame = (GraphicFrame) o; + } else if (o instanceof Pic) { + pic = (Pic) o; + } else if (o instanceof Sp) { + sp = (Sp) o; + } else if (o instanceof CxnSp) { + cxnSp = (CxnSp) o; + } else if (o instanceof GrpSp) { + grpSp = (GrpSp) o; + } + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/OneCellAnchor.java b/src/main/java/io/starter/formats/OOXML/OneCellAnchor.java index 9005da8..2c92142 100644 --- a/src/main/java/io/starter/formats/OOXML/OneCellAnchor.java +++ b/src/main/java/io/starter/formats/OOXML/OneCellAnchor.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,298 +22,323 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * oneCellAnchor (One Cell Anchor Shape Size) - * + *

        * This element specifies a one cell anchor placeholder for a group, a shape, or a drawing element. It moves with * the cell and its extents is in EMU units. - * + *

        * parent: wsDr * children: from, ext, OBJECTCHOICES (sp, grpSp, graphicFrame, cxnSp, pic), clientData - * */ //TODO: finish grpSp Group Shape // TODO: finish clientData element -public class OneCellAnchor implements OOXMLElement{ +public class OneCellAnchor implements OOXMLElement { - private static final long serialVersionUID = -8498556079325357165L; - public static final short EMU=1270; + private static final long serialVersionUID = -8498556079325357165L; + public static final short EMU = 1270; private From from; - private Ext ext; - private ObjectChoice objectChoice; - - public OneCellAnchor(From f, Ext e, ObjectChoice o) { - this.from= f; - this.ext= e; - this.objectChoice= o; - } - public OneCellAnchor(OneCellAnchor oca) { - this.from= oca.from; - this.ext= oca.ext; - this.objectChoice= oca.objectChoice; - } - + private Ext ext; + private ObjectChoice objectChoice; + + public OneCellAnchor(From f, Ext e, ObjectChoice o) { + this.from = f; + this.ext = e; + this.objectChoice = o; + } + + public OneCellAnchor(OneCellAnchor oca) { + this.from = oca.from; + this.ext = oca.ext; + this.objectChoice = oca.objectChoice; + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - From f= null; - Ext e= null; - ObjectChoice o= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("from")) { - lastTag.push(tnm); - f= (From) From.parseOOXML(xpp, lastTag); - } else if (tnm.equals("ext")) { - lastTag.push(tnm); - e= (Ext) Ext.parseOOXML(xpp, lastTag).cloneElement(); - //e.setNS("xdr"); - } else if (tnm.equals("cxnSp") || // connection shape - tnm.equals("graphicFrame") || - tnm.equals("grpSp") || // group shape - tnm.equals("pic") || // picture - tnm.equals("sp")) { // shape - lastTag.push(tnm); - o = (ObjectChoice) ObjectChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("oneCellAnchor")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception ex) { - Logger.logErr("oneCellAnchor.parseOOXML: " + ex.toString()); - } - OneCellAnchor oca= new OneCellAnchor(f, e, o); - return oca; + From f = null; + Ext e = null; + ObjectChoice o = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("from")) { + lastTag.push(tnm); + f = From.parseOOXML(xpp, lastTag); + } else if (tnm.equals("ext")) { + lastTag.push(tnm); + e = (Ext) Ext.parseOOXML(xpp, lastTag).cloneElement(); + //e.setNS("xdr"); + } else if (tnm.equals("cxnSp") || // connection shape + tnm.equals("graphicFrame") || + tnm.equals("grpSp") || // group shape + tnm.equals("pic") || // picture + tnm.equals("sp")) { // shape + lastTag.push(tnm); + o = (ObjectChoice) ObjectChoice.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("oneCellAnchor")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception ex) { + Logger.logErr("oneCellAnchor.parseOOXML: " + ex.toString()); + } + OneCellAnchor oca = new OneCellAnchor(f, e, o); + return oca; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (from != null) ooxml.append(from.getOOXML()); + ooxml.append(ext.getOOXML()); + ooxml.append(objectChoice.getOOXML()); + ooxml.append(""); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new OneCellAnchor(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (from!=null) ooxml.append(from.getOOXML()); - ooxml.append(ext.getOOXML()); - ooxml.append(objectChoice.getOOXML()); - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new OneCellAnchor(this); - } - - // access methods ****** - /** - * return the bounds of this object - * bounds represent: COL, COLOFFSET, ROW, ROWOFFST - * @return bounds short[4] - */ - public short[] getBounds() { - short[] bounds= new short[8]; - System.arraycopy(from.getBounds(), 0, bounds, 0, 4); // from bounds - return bounds; - } - - /** - * set the bounds of this object - * bounds represent: COL, COLOFFSET, ROW, ROWOFFST - * @param bounds short[4] - */ - public void setBounds(int[] bounds) { - int[] b= new int[4]; - System.arraycopy(bounds, 0, b, 0, 4); - if (from==null) from= new From(b); - else from.setBounds(b); - } - - /** - * get cNvPr name attribute - * @return - */ - public String getName() { - if (objectChoice!=null) - return objectChoice.getName(); - return null; - } + // access methods ****** + + /** + * return the bounds of this object + * bounds represent: COL, COLOFFSET, ROW, ROWOFFST + * + * @return bounds short[4] + */ + public short[] getBounds() { + short[] bounds = new short[8]; + System.arraycopy(from.getBounds(), 0, bounds, 0, 4); // from bounds + return bounds; + } - /** - * set cNvPr name attribute - * @param name - */ - public void setName(String name) { - if (objectChoice!=null) - objectChoice.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (objectChoice!=null) - return objectChoice.getDescr(); - return null; - } - /** - * set cNvPr descr attribute - * sometimes associated with shape name - * @param descr - */ - public void setDescr(String descr) { - if (objectChoice!=null) - objectChoice.setDescr(descr); - } - - /** - * get macro attribute - * @return - */ - public String getMacro() { - if (objectChoice!=null) - return objectChoice.getMacro(); - return null; - } - /** - * set Macro attribute - * sometimes associated with shape name - * @param descr - */ - public void setMacro(String macro) { - if (objectChoice!=null) - objectChoice.setMacro(macro); - } + /** + * set the bounds of this object + * bounds represent: COL, COLOFFSET, ROW, ROWOFFST + * + * @param bounds short[4] + */ + public void setBounds(int[] bounds) { + int[] b = new int[4]; + System.arraycopy(bounds, 0, b, 0, 4); + if (from == null) from = new From(b); + else from.setBounds(b); + } - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (objectChoice!=null) - return objectChoice.getURI(); - return null; - } - - /** - * set the URI associated with this graphic data - * @param uri - */ - public void setURI(String uri) { - if (objectChoice!=null) - objectChoice.setURI(uri); - } - /** - * return the id for the embedded picture, shape or chart (i.e. resides within the file) - * @return - */ - public String getEmbed() { - if (objectChoice!=null) - return objectChoice.getEmbed(); - return null; - } - - /** - * return the id for the linked object (i.e. doesn't reside in the file) - * @return - */ - public String getLink() { - if (objectChoice!=null) - return objectChoice.getLink(); - return null; - } - - /** - * set the embed or rId attribute for the embedded picture, shape or chart (i.e. resides within the file) - * @param embed - */ - public void setEmbed(String embed) { - if (objectChoice!=null) objectChoice.setEmbed(embed); - } + /** + * get cNvPr name attribute + * + * @return + */ + public String getName() { + if (objectChoice != null) + return objectChoice.getName(); + return null; + } + + /** + * set cNvPr name attribute + * + * @param name + */ + public void setName(String name) { + if (objectChoice != null) + objectChoice.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (objectChoice != null) + return objectChoice.getDescr(); + return null; + } + + /** + * set cNvPr descr attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setDescr(String descr) { + if (objectChoice != null) + objectChoice.setDescr(descr); + } + + /** + * get macro attribute + * + * @return + */ + public String getMacro() { + if (objectChoice != null) + return objectChoice.getMacro(); + return null; + } - /** - * set the link attribute for this blip (the id for the linked picture) - * @param embed - */ - public void setLink(String link) { - if (objectChoice!=null) objectChoice.setLink(link); - } - - /** - * return if this oneCellAnchor element refers to an image rather than a chart or shape - * @return - */ - public boolean hasImage() { - if (objectChoice!=null) // o will be a pic element, it's blipFill.blip child references the rId of the embedded file - return ((objectChoice.getObject() instanceof Pic) && objectChoice.getEmbed()!=null); - return false; - } - - /** utility to return the shape properties element (picture element only) - * should be depreciated when OOXML is moved into ImageHandle - * @return - */ - public SpPr getSppr() { - if (objectChoice!=null) - return objectChoice.getSppr(); - return null; - } - - /** - * return if this oneCellAnchor element refers to a chart as opposed to a shape or image - * @return - */ - public boolean hasChart() { - if (objectChoice!=null) - return (objectChoice.getObject() instanceof GraphicFrame && objectChoice.getChartRId()!=null); - return false; - } - - /** - * return if this oneCellAnchor element refers to a shape, as opposed a chart or an image - * @return - */ - public boolean hasShape() { - if (objectChoice!=null) - return (objectChoice.getObject() instanceof CxnSp || objectChoice.getObject() instanceof Sp); - return false; - } - - /** - * set this oneCellAnchor as a chart element - * @param rid - * @param name - * @param bounds - */ - public void setAsChart(int rid, String name, int[] bounds) { - objectChoice= new ObjectChoice(); - objectChoice.setObject(new GraphicFrame()); - objectChoice.setName(name); - objectChoice.setEmbed("rId" + Integer.valueOf(rid).toString()); - objectChoice.setId(rid); - this.setBounds(bounds); - // id??? - } - - /** - * set this oneCellAnchor as an image - * @param rid - * @param name - * @param id - */ - public void setAsImage(String rid, String name, String id) { - ObjectChoice o= new ObjectChoice(); - o.setObject(new Pic()); - o.setName(name); - o.setEmbed(rid); - } + /** + * set Macro attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setMacro(String macro) { + if (objectChoice != null) + objectChoice.setMacro(macro); + } + + /** + * get the URI associated with this graphic Data + */ + public String getURI() { + if (objectChoice != null) + return objectChoice.getURI(); + return null; + } + + /** + * set the URI associated with this graphic data + * + * @param uri + */ + public void setURI(String uri) { + if (objectChoice != null) + objectChoice.setURI(uri); + } + + /** + * return the id for the embedded picture, shape or chart (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (objectChoice != null) + return objectChoice.getEmbed(); + return null; + } + + /** + * return the id for the linked object (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (objectChoice != null) + return objectChoice.getLink(); + return null; + } + + /** + * set the embed or rId attribute for the embedded picture, shape or chart (i.e. resides within the file) + * + * @param embed + */ + public void setEmbed(String embed) { + if (objectChoice != null) objectChoice.setEmbed(embed); + } + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + public void setLink(String link) { + if (objectChoice != null) objectChoice.setLink(link); + } + + /** + * return if this oneCellAnchor element refers to an image rather than a chart or shape + * + * @return + */ + public boolean hasImage() { + if (objectChoice != null) // o will be a pic element, it's blipFill.blip child references the rId of the embedded file + return ((objectChoice.getObject() instanceof Pic) && objectChoice.getEmbed() != null); + return false; + } + + /** + * utility to return the shape properties element (picture element only) + * should be depreciated when OOXML is moved into ImageHandle + * + * @return + */ + public SpPr getSppr() { + if (objectChoice != null) + return objectChoice.getSppr(); + return null; + } + + /** + * return if this oneCellAnchor element refers to a chart as opposed to a shape or image + * + * @return + */ + public boolean hasChart() { + if (objectChoice != null) + return (objectChoice.getObject() instanceof GraphicFrame && objectChoice.getChartRId() != null); + return false; + } + + /** + * return if this oneCellAnchor element refers to a shape, as opposed a chart or an image + * + * @return + */ + public boolean hasShape() { + if (objectChoice != null) + return (objectChoice.getObject() instanceof CxnSp || objectChoice.getObject() instanceof Sp); + return false; + } + + /** + * set this oneCellAnchor as a chart element + * + * @param rid + * @param name + * @param bounds + */ + public void setAsChart(int rid, String name, int[] bounds) { + objectChoice = new ObjectChoice(); + objectChoice.setObject(new GraphicFrame()); + objectChoice.setName(name); + objectChoice.setEmbed("rId" + Integer.valueOf(rid).toString()); + objectChoice.setId(rid); + this.setBounds(bounds); + // id??? + } + + /** + * set this oneCellAnchor as an image + * + * @param rid + * @param name + * @param id + */ + public void setAsImage(String rid, String name, String id) { + ObjectChoice o = new ObjectChoice(); + o.setObject(new Pic()); + o.setName(name); + o.setEmbed(rid); + } } diff --git a/src/main/java/io/starter/formats/OOXML/P.java b/src/main/java/io/starter/formats/OOXML/P.java index 2311934..e9a6519 100644 --- a/src/main/java/io/starter/formats/OOXML/P.java +++ b/src/main/java/io/starter/formats/OOXML/P.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,446 +22,454 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.FormatHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.Font; import io.starter.formats.XLS.FormatConstants; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; +import java.util.Stack; /** * p (Text Paragraph) - * + *

        * This element specifies the presence of a paragraph of text within the containing text body. The paragraph is the * highest level text separation mechanism within a text body. A paragraph may contain text paragraph properties * associated with the paragraph. If no properties are listed then properties specified in the defPPr element are * used. - * + *

        * parent: r, t, txBody, txpr - * children: pPr, (r, br or fld), endParaRPr - - * + * children: pPr, (r, br or fld), endParaRPr */ // TODO: Finish pPr Text Paragraph Properties -- MANY child elements not handled //TODO: Finish endParaRPr children TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver, -public class P implements OOXMLElement{ - - private static final long serialVersionUID = 6302706683933521698L; - private TextRun run= null; - private PPr ppr= null; - private EndParaRPr ep= null; - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - PPr ppr= null; - TextRun run= null; - EndParaRPr ep= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pPr")) { // paragraph-level text props - lastTag.push(tnm); - ppr= (PPr) PPr.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("r") || - tnm.equals("br") || - tnm.equals("fld")) { // text run - lastTag.push(tnm); - run= (TextRun) TextRun.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("endParaRPr")) { - lastTag.push(tnm); - ep= (EndParaRPr) EndParaRPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("p")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("p.parseOOXML: " + e.toString()); - - } - P pr= new P(ppr, run, ep); - return pr; - } - - public P(String s) { - this.run= new TextRun(s); - this.ppr= new PPr(new DefRPr(), null); - } - public P(PPr ppr, TextRun run, EndParaRPr ep) { - this.ppr= ppr; - this.ep= ep; - this.run= run; - } - public P(P p) { - this.ppr= p.ppr; - this.ep= p.ep; - this.run= p.run ; - } - - /** - * create a default paragraph property from the specified information - * @param fontFace String font face e.g. "Arial" - * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) - * @param b boolean true if bold - * @param i boolean true if italic - * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted - * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) - * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none - * @param clr String fill color in hex form without the # - */ - public P(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { - this.ppr= new PPr(fontFace, sz, b, i, u, strike, clr); - } - - - /** - * create a default paragraph property from the specified Font and text s - * @param f Font - */ - public P(Font f, String s) { - int u= f.getUnderlineStyle(); - String usty= "none"; - switch (u) { - case FormatConstants.STYLE_UNDERLINE_SINGLE: - usty= "sng"; - break; - case FormatConstants.STYLE_UNDERLINE_DOUBLE: - usty= "dbl"; - break; - case FormatConstants.STYLE_UNDERLINE_SINGLE_ACCTG: - usty= "sng"; - break; - case FormatConstants.STYLE_UNDERLINE_DOUBLE_ACCTG: - usty= "dbl"; - break; - } - String strike= (f.getStricken()?"sngStrike":"noStrike"); - String clr= FormatHandle.colorToHexString(FormatHandle.getColor(f.getColor())).substring(1); - this.ppr= new PPr(f.getFontName(), (int)f.getFontHeightInPoints()*100, f.getBold(), f.getItalic(), usty, strike, clr); - this.run= new TextRun(s); - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (ppr!=null) ooxml.append(ppr.getOOXML()); - if (run!=null) ooxml.append(run.getOOXML()); // 20090526 KSC: order of children was wrong (Kaylan/Rajesh chart error) - if (ep!=null) ooxml.append(ep.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new P(this); - } - public String getTitle() { if (run!=null) return run.getTitle(); return "";} - - /** - * returns a map of all text properties defined for this paragraph - * @return - */ - public HashMap getTextProperties() { - HashMap textprops= new HashMap(); - if (ppr!=null) { - // algn- left, right, centered, just, distributed - // defTabSz - // fontAlgn (Font Alignment) - // hangingPunct (Hanging Punctuation) bool - // indent (Indent) - // lvl (Level) - // marL (Left Margin) - // marR (Right Margin) - // rtl (Right To Left) bool +public class P implements OOXMLElement { + + private static final long serialVersionUID = 6302706683933521698L; + private TextRun run = null; + private PPr ppr = null; + private EndParaRPr ep = null; + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + PPr ppr = null; + TextRun run = null; + EndParaRPr ep = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pPr")) { // paragraph-level text props + lastTag.push(tnm); + ppr = PPr.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("r") || + tnm.equals("br") || + tnm.equals("fld")) { // text run + lastTag.push(tnm); + run = (TextRun) TextRun.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("endParaRPr")) { + lastTag.push(tnm); + ep = EndParaRPr.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("p")) { + lastTag.pop(); // pop this tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("p.parseOOXML: " + e.toString()); + + } + P pr = new P(ppr, run, ep); + return pr; + } + + public P(String s) { + this.run = new TextRun(s); + this.ppr = new PPr(new DefRPr(), null); + } + + public P(PPr ppr, TextRun run, EndParaRPr ep) { + this.ppr = ppr; + this.ep = ep; + this.run = run; + } + + public P(P p) { + this.ppr = p.ppr; + this.ep = p.ep; + this.run = p.run; + } + + /** + * create a default paragraph property from the specified information + * + * @param fontFace String font face e.g. "Arial" + * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) + * @param b boolean true if bold + * @param i boolean true if italic + * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted + * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) + * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none + * @param clr String fill color in hex form without the # + */ + public P(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { + this.ppr = new PPr(fontFace, sz, b, i, u, strike, clr); + } + + + /** + * create a default paragraph property from the specified Font and text s + * + * @param f Font + */ + public P(Font f, String s) { + int u = f.getUnderlineStyle(); + String usty = "none"; + switch (u) { + case FormatConstants.STYLE_UNDERLINE_SINGLE: + usty = "sng"; + break; + case FormatConstants.STYLE_UNDERLINE_DOUBLE: + usty = "dbl"; + break; + case FormatConstants.STYLE_UNDERLINE_SINGLE_ACCTG: + usty = "sng"; + break; + case FormatConstants.STYLE_UNDERLINE_DOUBLE_ACCTG: + usty = "dbl"; + break; + } + String strike = (f.getStricken() ? "sngStrike" : "noStrike"); + String clr = FormatHandle.colorToHexString(FormatHandle.getColor(f.getColor())).substring(1); + this.ppr = new PPr(f.getFontName(), (int) f.getFontHeightInPoints() * 100, f.getBold(), f.getItalic(), usty, strike, clr); + this.run = new TextRun(s); + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (ppr != null) ooxml.append(ppr.getOOXML()); + if (run != null) + ooxml.append(run.getOOXML()); // 20090526 KSC: order of children was wrong (Kaylan/Rajesh chart error) + if (ep != null) ooxml.append(ep.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new P(this); + } + + public String getTitle() { + if (run != null) return run.getTitle(); + return ""; + } + + /** + * returns a map of all text properties defined for this paragraph + * + * @return + */ + public HashMap getTextProperties() { + HashMap textprops = new HashMap(); + if (ppr != null) { + // algn- left, right, centered, just, distributed + // defTabSz + // fontAlgn (Font Alignment) + // hangingPunct (Hanging Punctuation) bool + // indent (Indent) + // lvl (Level) + // marL (Left Margin) + // marR (Right Margin) + // rtl (Right To Left) bool /* This element contains all default run level text properties for the text runs within a containing paragraph. These 10 properties are to be used when overriding properties have not been defined within the rPr element.*/ - textprops.putAll(ppr.getTextProperties()); - textprops.putAll(ppr.getDefaultTextProperties()); - /* - * altLang (Alternative Language) - * b (Bold) bool - * baseline (Baseline) - * bmk (Bookmark Link Target) - * cap (Capitalization) - * i (Italics) bool - * kern (Kerning) - * kumimoji - * lang (Language ID) - * spc (Spacing) - * strike (Strikethrough) - * sz (Font Size) size - * u (Underline) underline style - * - * PLUS-- fill, line, blipFill, cs/ea/latin font (attribute: typeface) **** - */ - } - if (run!=null) { - textprops.putAll(run.getTextProperties()); - } - return textprops; - } + textprops.putAll(ppr.getTextProperties()); + textprops.putAll(ppr.getDefaultTextProperties()); + /* + * altLang (Alternative Language) + * b (Bold) bool + * baseline (Baseline) + * bmk (Bookmark Link Target) + * cap (Capitalization) + * i (Italics) bool + * kern (Kerning) + * kumimoji + * lang (Language ID) + * spc (Spacing) + * strike (Strikethrough) + * sz (Font Size) size + * u (Underline) underline style + * + * PLUS-- fill, line, blipFill, cs/ea/latin font (attribute: typeface) **** + */ + } + if (run != null) { + textprops.putAll(run.getTextProperties()); + } + return textprops; + } } /** * pPr (Text Paragraph Properties) - * + *

        * This element contains all paragraph level text properties for the containing paragraph. These paragraph * properties should override any and all conflicting properties that are associated with the paragraph in question - * + *

        * parent: p, fld * children: many * attributes: many - * */ // TODO: Handle child elements: lnSpc, spcBef, spcAft, TEXTBULLETCOLOR, TEXTBULLETSIZE, TEXTBULLET, tabLst -class PPr implements OOXMLElement{ - - private static final long serialVersionUID = -6909210948618654877L; - private DefRPr dp; +class PPr implements OOXMLElement { + + private static final long serialVersionUID = -6909210948618654877L; + private DefRPr dp; private HashMap attrs; - - public PPr(DefRPr dp, HashMap attrs){ - this.dp= dp; - this.attrs= attrs; - } - - public PPr(PPr p) { - this.dp= p.dp; - this.attrs= p.attrs; - } - - /** - * create a default paragraph property from the specified information - * @param fontFace String font face e.g. "Arial" - * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) - * @param b boolean true if bold - * @param i boolean true if italic - * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted - * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) - * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none - * @param clr String fill color in hex form without the # - */ - public PPr(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { - this.dp= new DefRPr(fontFace, sz, b, i, u, strike, clr); - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (dp!=null) ooxml.append(dp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } + + public PPr(DefRPr dp, HashMap attrs) { + this.dp = dp; + this.attrs = attrs; + } + + public PPr(PPr p) { + this.dp = p.dp; + this.attrs = p.attrs; + } + + /** + * create a default paragraph property from the specified information + * + * @param fontFace String font face e.g. "Arial" + * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) + * @param b boolean true if bold + * @param i boolean true if italic + * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted + * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) + * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none + * @param clr String fill color in hex form without the # + */ + public PPr(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { + this.dp = new DefRPr(fontFace, sz, b, i, u, strike, clr); + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + } + ooxml.append(">"); + if (dp != null) ooxml.append(dp.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } public static PPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - DefRPr dp= null; - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pPr")) { // t element of text run -- the title string we are interested in - for (int i= 0; i < xpp.getAttributeCount(); i++) { // align, defTabSz, fontAlgn, hangingPunct, indent, lvl, rtl, marL, marR ... - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("defRPr")) { // default text properties - lastTag.push(tnm); - dp= (DefRPr) DefRPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("pPr")) { - lastTag.pop(); // pop this element - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("paraText.parseOOXML: " + e.toString()); - } - PPr pt= new PPr(dp, attrs); - return pt; + DefRPr dp = null; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pPr")) { // t element of text run -- the title string we are interested in + for (int i = 0; i < xpp.getAttributeCount(); i++) { // align, defTabSz, fontAlgn, hangingPunct, indent, lvl, rtl, marL, marR ... + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("defRPr")) { // default text properties + lastTag.push(tnm); + dp = (DefRPr) DefRPr.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("pPr")) { + lastTag.pop(); // pop this element + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("paraText.parseOOXML: " + e.toString()); + } + PPr pt = new PPr(dp, attrs); + return pt; } + public OOXMLElement cloneElement() { - return new PPr(this); - } - + return new PPr(this); + } + /** * return the default run properties for this para + * * @return */ - public HashMap getDefaultTextProperties() { - return dp.getTextProperties(); - } - + public HashMap getDefaultTextProperties() { + return dp.getTextProperties(); + } + /** * return the text properties defined by PPr: - algn- left, right, centered, just, distributed - defTabSz - fontAlgn (Font Alignment) - hangingPunct (Hanging Punctuation) bool - indent (Indent) - lvl (Level) - marL (Left Margin) - marR (Right Margin) - rtl (Right To Left) bool - * @return + * algn- left, right, centered, just, distributed + * defTabSz + * fontAlgn (Font Alignment) + * hangingPunct (Hanging Punctuation) bool + * indent (Indent) + * lvl (Level) + * marL (Left Margin) + * marR (Right Margin) + * rtl (Right To Left) bool + * + * @return */ - public HashMap getTextProperties() { - if (attrs!=null) return attrs; - return new HashMap(); - } + public HashMap getTextProperties() { + if (attrs != null) return attrs; + return new HashMap(); + } } /** * endParaRPr (End Paragraph Run Properties) - * + *

        * This element specifies the text run properties that are to be used if another run is inserted after the last run * specified. This effectively saves the run property state so that it may be applied when the user enters additional * text. If this element is omitted, then the application may determine which default properties to apply. It is * recommended that this element be specified at the end of the list of text runs within the paragraph so that an * orderly list is maintained. - * + *

        * parent: p - * children: ln, FillGroup, EffectGroup, highlight, TEXTUNDERLINE, TEXTUNDERLINEFILL, latin, ea, cs, sym, hlinkClick, hlinkMouseOver, - * - * - * + * children: ln, FillGroup, EffectGroup, highlight, TEXTUNDERLINE, TEXTUNDERLINEFILL, latin, ea, cs, sym, hlinkClick, hlinkMouseOver, */ // TODO: Finish children TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver, -class EndParaRPr implements OOXMLElement{ +class EndParaRPr implements OOXMLElement { - private static final long serialVersionUID = -7094231887468090281L; - private HashMap attrs= null; + private static final long serialVersionUID = -7094231887468090281L; + private HashMap attrs = null; private Ln l; private FillGroup fill; private EffectPropsGroup effect; - private String latin, ea, cs; // really children but only have 1 attribute and no children - - public EndParaRPr(HashMap attrs, Ln l, FillGroup fill, EffectPropsGroup effect, String latin, String ea, String cs) { - this.attrs= attrs; - this.l= l; - this.fill= fill; - this.effect= effect; - this.latin= latin; - this.ea= ea; - this.cs= cs; - } - public EndParaRPr(EndParaRPr ep) { - this.attrs= ep.attrs; - this.l= ep.l; - this.fill= ep.fill; - this.effect= ep.effect; - this.latin= ep.latin; - this.ea= ep.ea; - this.cs= ep.cs; - } - - - public static EndParaRPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs= new HashMap(); - FillGroup fill= null; - EffectPropsGroup effect= null; - Ln l= null; - String latin= null, ea= null, cs= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("endParaRPr")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("ln")) { - lastTag.push(tnm); - l= (Ln) Ln.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill= (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect= (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - // TODO: Eventually these will be objects - } else if (tnm.equals("latin")) { - latin= xpp.getAttributeValue(0); - } else if (tnm.equals("ea")) { - ea= xpp.getAttributeValue(0); - } else if (tnm.equals("cs")) { - cs= xpp.getAttributeValue(0); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("endParaRPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("endParaRPr.parseOOXML: " + e.toString()); - } - EndParaRPr oe= new EndParaRPr(attrs, l, fill, effect, latin, ea, cs); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (l!=null) ooxml.append(l.getOOXML()); - if (fill!=null) ooxml.append(fill.getOOXML()); // group fill - if (latin!=null) ooxml.append(""); - if (ea!=null) ooxml.append(""); - if (cs!=null) ooxml.append(""); - // sym - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new EndParaRPr(this); - } + private String latin, ea, cs; // really children but only have 1 attribute and no children + + public EndParaRPr(HashMap attrs, Ln l, FillGroup fill, EffectPropsGroup effect, String latin, String ea, String cs) { + this.attrs = attrs; + this.l = l; + this.fill = fill; + this.effect = effect; + this.latin = latin; + this.ea = ea; + this.cs = cs; + } + + public EndParaRPr(EndParaRPr ep) { + this.attrs = ep.attrs; + this.l = ep.l; + this.fill = ep.fill; + this.effect = ep.effect; + this.latin = ep.latin; + this.ea = ep.ea; + this.cs = ep.cs; + } + + + public static EndParaRPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + HashMap attrs = new HashMap(); + FillGroup fill = null; + EffectPropsGroup effect = null; + Ln l = null; + String latin = null, ea = null, cs = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("endParaRPr")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("ln")) { + lastTag.push(tnm); + l = (Ln) Ln.parseOOXML(xpp, lastTag, bk); + } else if ( + tnm.equals("solidFill") || + tnm.equals("noFill") || + tnm.equals("gradFill") || + tnm.equals("grpFill") || + tnm.equals("pattFill") || + tnm.equals("blipFill")) { + lastTag.push(tnm); + fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); + } else if ( + tnm.equals("effectLst") || + tnm.equals("effectDag")) { + lastTag.push(tnm); + effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); + // TODO: Eventually these will be objects + } else if (tnm.equals("latin")) { + latin = xpp.getAttributeValue(0); + } else if (tnm.equals("ea")) { + ea = xpp.getAttributeValue(0); + } else if (tnm.equals("cs")) { + cs = xpp.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("endParaRPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("endParaRPr.parseOOXML: " + e.toString()); + } + EndParaRPr oe = new EndParaRPr(attrs, l, fill, effect, latin, ea, cs); + return oe; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + } + ooxml.append(">"); + if (l != null) ooxml.append(l.getOOXML()); + if (fill != null) ooxml.append(fill.getOOXML()); // group fill + if (latin != null) ooxml.append(""); + if (ea != null) ooxml.append(""); + if (cs != null) ooxml.append(""); + // sym + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new EndParaRPr(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Pic.java b/src/main/java/io/starter/formats/OOXML/Pic.java index ec1b479..7af0275 100644 --- a/src/main/java/io/starter/formats/OOXML/Pic.java +++ b/src/main/java/io/starter/formats/OOXML/Pic.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,261 +22,277 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import java.util.HashMap; -import java.util.Iterator; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Stack; /** * pic (Picture) * This element specifies the existence of a picture object within the spreadsheet. - * + *

        * parent: absoluteAnchor, grpSp, oneCellAnchor, twoCellAnchor - * children: nvPicPr, blipFill, spPr, style - * - * + * children: nvPicPr, blipFill, spPr, style */ //TODO: handle nvPicPr.cNvPicPr.picLocks element -public class Pic implements OOXMLElement{ +public class Pic implements OOXMLElement { - private static final long serialVersionUID = -4929177274389163606L; - private HashMap attrs; + private static final long serialVersionUID = -4929177274389163606L; + private HashMap attrs; private NvPicPr nvPicPr; private BlipFill blipFill; private SpPr spPr; private Style style; - - public Pic() { // set common defaults - nvPicPr= new NvPicPr(); - blipFill= new BlipFill(); - spPr= new SpPr("xdr"); - spPr.setNS("xdr"); - attrs = null; - } - public Pic(HashMap attrs, NvPicPr nv, BlipFill bf, SpPr sp, Style s) { - this.attrs= attrs; - this.nvPicPr= nv; - this.blipFill= bf; - this.spPr= sp; - this.style= s; - } - public Pic(Pic p) { - this.attrs= p.attrs; - this.nvPicPr= p.nvPicPr; - this.blipFill= p.blipFill; - this.spPr= p.spPr; - this.style= p.style; - } - + + public Pic() { // set common defaults + nvPicPr = new NvPicPr(); + blipFill = new BlipFill(); + spPr = new SpPr("xdr"); + spPr.setNS("xdr"); + attrs = null; + } + + public Pic(HashMap attrs, NvPicPr nv, BlipFill bf, SpPr sp, Style s) { + this.attrs = attrs; + this.nvPicPr = nv; + this.blipFill = bf; + this.spPr = sp; + this.style = s; + } + + public Pic(Pic p) { + this.attrs = p.attrs; + this.nvPicPr = p.nvPicPr; + this.blipFill = p.blipFill; + this.spPr = p.spPr; + this.style = p.style; + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs= new HashMap(); - NvPicPr nv= null; - BlipFill bf= null; - SpPr sp= null; - Style s= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pic")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } else if (tnm.equals("nvPicPr")) { - lastTag.push(tnm); - nv= (NvPicPr) NvPicPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("blipFill")) { - lastTag.push(tnm); - bf= (BlipFill) BlipFill.parseOOXML(xpp, lastTag); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp= (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - //sp.setNS("xdr"); - } else if (tnm.equals("style")) { - lastTag.push(tnm); - s= (Style) Style.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("pic")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pic.parseOOXML: " + e.toString()); - } - Pic p= new Pic(attrs, nv, bf, sp, s); - return p; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (nvPicPr!=null) ooxml.append(nvPicPr.getOOXML()); - if (blipFill!=null) ooxml.append(blipFill.getOOXML()); - if (spPr!=null) ooxml.append(spPr.getOOXML()); - if (style!=null) ooxml.append(style.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Pic(this); - } - /** - * return the id for the embedded picture (i.e. resides within the file) - * @return - */ - public String getEmbed() { - if (blipFill!=null) - return blipFill.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * @return - */ - public String getLink() { - if (blipFill!=null) - return blipFill.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * @param embed - */ - public void setEmbed(String embed) { - if (blipFill!=null) - blipFill.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * @param embed - */ - public void setLink(String link) { - if (blipFill!=null) - blipFill.setLink(link); - } - - /** - * return the name of this shape, if any - * @return - */ - public String getName() { - if (nvPicPr!=null) - return nvPicPr.getName(); - return null; - } - - /** - * set the name of this shape, if any - */ - public void setName(String name) { - if (nvPicPr!=null) - nvPicPr.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (nvPicPr!=null) - return nvPicPr.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * @param name - */ - public void setDescr(String descr) { - if (nvPicPr!=null) - nvPicPr.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (nvPicPr!=null) - nvPicPr.setId(id); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (nvPicPr==null) - nvPicPr= new NvPicPr(); - return nvPicPr.getId(); - } - /** - * get Macro attribute - */ - public String getMacro() { - if (attrs.get("macro")!=null) - return (String) attrs.get("macro"); - return null; - } - - /** - * set Macro attribute - * @param macro - */ - public void setMacro(String macro) { - attrs.put("macro", macro); - } - - /** - * add a line to this image - * @param w int line width in - * @param clr String color html string - * @return - */ - public void setLine(int w, String clr) { - if (spPr!=null) { - spPr.setLine(w, clr); - } - } - - /** utility to return the shape properties element for this picture - * should be depreciated when OOXML is completely distinct from BIFF8 - * @return - */ - public SpPr getSppr() { - return spPr; - } - - /** utility to return the shape properties element for this picture - * should be depreciated when OOXML is completely distinct from BIFF8 - * @return - */ - public void setSppr(SpPr sp) { - this.spPr= sp; - } + HashMap attrs = new HashMap(); + NvPicPr nv = null; + BlipFill bf = null; + SpPr sp = null; + Style s = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pic")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } else if (tnm.equals("nvPicPr")) { + lastTag.push(tnm); + nv = NvPicPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("blipFill")) { + lastTag.push(tnm); + bf = BlipFill.parseOOXML(xpp, lastTag); + } else if (tnm.equals("spPr")) { + lastTag.push(tnm); + sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); + //sp.setNS("xdr"); + } else if (tnm.equals("style")) { + lastTag.push(tnm); + s = (Style) Style.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("pic")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("pic.parseOOXML: " + e.toString()); + } + Pic p = new Pic(attrs, nv, bf, sp, s); + return p; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + } + ooxml.append(">"); + if (nvPicPr != null) ooxml.append(nvPicPr.getOOXML()); + if (blipFill != null) ooxml.append(blipFill.getOOXML()); + if (spPr != null) ooxml.append(spPr.getOOXML()); + if (style != null) ooxml.append(style.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Pic(this); + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (blipFill != null) + return blipFill.getEmbed(); + return null; + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (blipFill != null) + return blipFill.getLink(); + return null; + } + + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + public void setEmbed(String embed) { + if (blipFill != null) + blipFill.setEmbed(embed); + } + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + public void setLink(String link) { + if (blipFill != null) + blipFill.setLink(link); + } + + /** + * return the name of this shape, if any + * + * @return + */ + public String getName() { + if (nvPicPr != null) + return nvPicPr.getName(); + return null; + } + + /** + * set the name of this shape, if any + */ + public void setName(String name) { + if (nvPicPr != null) + nvPicPr.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (nvPicPr != null) + return nvPicPr.getDescr(); + return null; + } + + /** + * set cNvPr desc attribute + * + * @param name + */ + public void setDescr(String descr) { + if (nvPicPr != null) + nvPicPr.setDescr(descr); + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (nvPicPr != null) + nvPicPr.setId(id); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (nvPicPr == null) + nvPicPr = new NvPicPr(); + return nvPicPr.getId(); + } + + /** + * get Macro attribute + */ + public String getMacro() { + if (attrs.get("macro") != null) + return attrs.get("macro"); + return null; + } + + /** + * set Macro attribute + * + * @param macro + */ + public void setMacro(String macro) { + attrs.put("macro", macro); + } + + /** + * add a line to this image + * + * @param w int line width in + * @param clr String color html string + * @return + */ + public void setLine(int w, String clr) { + if (spPr != null) { + spPr.setLine(w, clr); + } + } + + /** + * utility to return the shape properties element for this picture + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + public SpPr getSppr() { + return spPr; + } + + /** + * utility to return the shape properties element for this picture + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + public void setSppr(SpPr sp) { + this.spPr = sp; + } } /** @@ -284,199 +300,205 @@ public void setSppr(SpPr sp) { * This element specifies all non-visual properties for a picture. This element is a container for the non-visual * identification properties, shape properties and application properties that are to be associated with a picture. * This allows for additional information that does not affect the appearance of the picture to be stored. - * + *

        * parent: pic - * children: cNvPr, cNvPicPr - * + * children: cNvPr, cNvPicPr */ class NvPicPr implements OOXMLElement { - - private static final long serialVersionUID = -3722424348721713313L; - private CNvPr cpr; - private CNvPicPr ppr; - - public NvPicPr() { // set common defaults - this.cpr= new CNvPr(); - this.ppr= new CNvPicPr(); - } - public NvPicPr(CNvPr cpr, CNvPicPr ppr) { - this.cpr= cpr; - this.ppr= ppr; - } - public NvPicPr(NvPicPr n) { - this.cpr= n.cpr; - this.ppr= n.ppr; - } - - + + private static final long serialVersionUID = -3722424348721713313L; + private CNvPr cpr; + private CNvPicPr ppr; + + public NvPicPr() { // set common defaults + this.cpr = new CNvPr(); + this.ppr = new CNvPicPr(); + } + + public NvPicPr(CNvPr cpr, CNvPicPr ppr) { + this.cpr = cpr; + this.ppr = ppr; + } + + public NvPicPr(NvPicPr n) { + this.cpr = n.cpr; + this.ppr = n.ppr; + } + + public static NvPicPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cpr= null; - CNvPicPr ppr= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cpr= (CNvPr) CNvPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cNvPicPr")) { - lastTag.push(tnm); - ppr= (CNvPicPr) CNvPicPr.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("nvPicPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("nvPicPr.parseOOXML: " + e.toString()); - } - NvPicPr n= new NvPicPr(cpr, ppr); - return n; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - ooxml.append(cpr.getOOXML()); - ooxml.append(ppr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NvPicPr(this); - } - /** - * return the name of this shape, if any - * @return - */ - public String getName() { - if (cpr!=null) - return cpr.getName(); - return null; - } - - /** - * set the name of this shape, if any - */ - public void setName(String name) { - if (cpr!=null) - cpr.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (cpr!=null) - return cpr.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * @param name - */ - public void setDescr(String descr) { - if (cpr!=null) - cpr.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (cpr!=null) - cpr.setId(id); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (cpr!=null) - return cpr.getId(); - return -1; - } - + CNvPr cpr = null; + CNvPicPr ppr = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cNvPr")) { + lastTag.push(tnm); + cpr = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("cNvPicPr")) { + lastTag.push(tnm); + ppr = CNvPicPr.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("nvPicPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("nvPicPr.parseOOXML: " + e.toString()); + } + NvPicPr n = new NvPicPr(cpr, ppr); + return n; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + ooxml.append(cpr.getOOXML()); + ooxml.append(ppr.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new NvPicPr(this); + } + + /** + * return the name of this shape, if any + * + * @return + */ + public String getName() { + if (cpr != null) + return cpr.getName(); + return null; + } + + /** + * set the name of this shape, if any + */ + public void setName(String name) { + if (cpr != null) + cpr.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (cpr != null) + return cpr.getDescr(); + return null; + } + + /** + * set cNvPr desc attribute + * + * @param name + */ + public void setDescr(String descr) { + if (cpr != null) + cpr.setDescr(descr); + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (cpr != null) + cpr.setId(id); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (cpr != null) + return cpr.getId(); + return -1; + } + } /** * cNvPicPr (Non-Visual Picture Drawing Properties) * This element describes the non-visual properties of a picture within a spreadsheet. These are the set of * properties of a picture which do not affect its display within a spreadsheet. - * */ // TODO: handle child picLocks -class CNvPicPr implements OOXMLElement{ - - private static final long serialVersionUID = 3690228348761065940L; - private String preferRelativeResize= null; - - public CNvPicPr() { - - } - - public CNvPicPr(String preferRelativeResize) { - this.preferRelativeResize= preferRelativeResize; - } - - public CNvPicPr(CNvPicPr c) { - this.preferRelativeResize= c.preferRelativeResize; - } - - +class CNvPicPr implements OOXMLElement { + + private static final long serialVersionUID = 3690228348761065940L; + private String preferRelativeResize = null; + + public CNvPicPr() { + + } + + public CNvPicPr(String preferRelativeResize) { + this.preferRelativeResize = preferRelativeResize; + } + + public CNvPicPr(CNvPicPr c) { + this.preferRelativeResize = c.preferRelativeResize; + } + + public static CNvPicPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String preferRelativeResize= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPicPr")) { // get attributes - if (xpp.getAttributeCount()>0) - preferRelativeResize= xpp.getAttributeValue(0); + String preferRelativeResize = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cNvPicPr")) { // get attributes + if (xpp.getAttributeCount() > 0) + preferRelativeResize = xpp.getAttributeValue(0); /* TODO: Finish } else if (tnm.equals("picLocks")) { lastTag.push(tnm); //p = (picLocks) picLocks.parseOOXML(xpp, lastTag).clone(); -*/ - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("cNvPicPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cNvPicPr.parseOOXML: " + e.toString()); - } - CNvPicPr c= new CNvPicPr(preferRelativeResize); - return c; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - // TODO: finish picLocks - //if (p!=null) ooxml.append(p.getOOXML()); - //ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvPicPr(this); - } +*/ + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("cNvPicPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("cNvPicPr.parseOOXML: " + e.toString()); + } + CNvPicPr c = new CNvPicPr(preferRelativeResize); + return c; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + // TODO: finish picLocks + //if (p!=null) ooxml.append(p.getOOXML()); + //ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CNvPicPr(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.java b/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.java index 80478fc..1ed4b40 100644 --- a/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.java +++ b/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,110 +22,119 @@ */ package io.starter.formats.OOXML; -import java.io.InputStream; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserFactory; import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.InputStream; + +public class PivotCacheDefinition implements OOXMLElement { + + private static final long serialVersionUID = -5070227633357072878L; + private String ref = null; + private String sheet = null; + private int icache; -public class PivotCacheDefinition implements OOXMLElement{ - - private static final long serialVersionUID = -5070227633357072878L; - private String ref= null; - private String sheet= null; - private int icache; - - public OOXMLElement cloneElement() { - return null; - } - - public PivotCacheDefinition(String ref, String sheet, int icache) { - this.ref= ref; - this.sheet= sheet; - this.icache= icache; - } - public static PivotCacheDefinition parseOOXML(WorkBookHandle bk, String cacheid, InputStream ii) { - String ref= null; - String sheet= null; - int icache= 1; - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pivotCacheDefinition")) { // get attributes - // r:id="rId1" refreshedBy="Kaia" refreshedDate="41038.467970833335" createdVersion="1" refreshedVersion="3" recordCount="4" upgradeOnRefresh="1"> - } else if (tnm.equals("cacheSource")) { - for (int z= 0; z < xpp.getAttributeCount(); z++) { - String nm = xpp.getAttributeName(z); - String v = xpp.getAttributeValue(z); - if (nm.equals("type")) - if (!v.equals("worksheet")) { - // consolidation, external, scenario -- - Logger.logWarn("PivotCacheDefinition: Data Souce " + v + " Not Supported"); - return null; - } - } - } else if (tnm.equals("worksheetSource")) { - // ref, sheet, id (sheet rid), name (range) - for (int z= 0; z < xpp.getAttributeCount(); z++) { - String nm = xpp.getAttributeName(z); - String v = xpp.getAttributeValue(z); - if (nm.equals("ref")) { - ref= v; - } else if (nm.equals("sheet")) { - sheet= v; - } else if (nm.equals("name")) { - ref= v; - } else if (nm.equals("id")) { - - } - } - } else if (tnm.equals("cacheFields")) { - } - } else if (eventType == XmlPullParser.END_TAG) { // go to end of file - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("PivotCacheDefinition.parseOOXML: " + e.toString()); - } - - if (cacheid!=null) { + public OOXMLElement cloneElement() { + return null; + } + + public PivotCacheDefinition(String ref, String sheet, int icache) { + this.ref = ref; + this.sheet = sheet; + this.icache = icache; + } + + public static PivotCacheDefinition parseOOXML(WorkBookHandle bk, String cacheid, InputStream ii) { + String ref = null; + String sheet = null; + int icache = 1; + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(ii, null); // using XML 1.0 specification + int eventType = xpp.getEventType(); + + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pivotCacheDefinition")) { // get attributes + // r:id="rId1" refreshedBy="Kaia" refreshedDate="41038.467970833335" createdVersion="1" refreshedVersion="3" recordCount="4" upgradeOnRefresh="1"> + } else if (tnm.equals("cacheSource")) { + for (int z = 0; z < xpp.getAttributeCount(); z++) { + String nm = xpp.getAttributeName(z); + String v = xpp.getAttributeValue(z); + if (nm.equals("type")) + if (!v.equals("worksheet")) { + // consolidation, external, scenario -- + Logger.logWarn("PivotCacheDefinition: Data Souce " + v + " Not Supported"); + return null; + } + } + } else if (tnm.equals("worksheetSource")) { + // ref, sheet, id (sheet rid), name (range) + for (int z = 0; z < xpp.getAttributeCount(); z++) { + String nm = xpp.getAttributeName(z); + String v = xpp.getAttributeValue(z); + if (nm.equals("ref")) { + ref = v; + } else if (nm.equals("sheet")) { + sheet = v; + } else if (nm.equals("name")) { + ref = v; + } else if (nm.equals("id")) { + + } + } + } else if (tnm.equals("cacheFields")) { + } + } else if (eventType == XmlPullParser.END_TAG) { // go to end of file + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("PivotCacheDefinition.parseOOXML: " + e.toString()); + } + + if (cacheid != null) { // KSC: TESTING!!! icache= Integer.valueOf((String)cacheid)+1; -icache= Integer.valueOf(1); - } - icache= bk.getWorkBook().addPivotStream(ref, sheet, icache); - return new PivotCacheDefinition(ref, sheet, icache); - } - - /** - * return the pivot cache id - */ - public int getICache() { return icache; } - - /** - * returns the data source reference - * @return - */ - public String getRef() { return ref; } - - /** - * return the sheet the data reference is on - * @return - */ - public String getSheet() { return sheet; } - - public String getOOXML() { - // TODO: Finish - return null; - } - + icache = Integer.valueOf(1); + } + icache = bk.getWorkBook().addPivotStream(ref, sheet, icache); + return new PivotCacheDefinition(ref, sheet, icache); + } + + /** + * return the pivot cache id + */ + public int getICache() { + return icache; + } + + /** + * returns the data source reference + * + * @return + */ + public String getRef() { + return ref; + } + + /** + * return the sheet the data reference is on + * + * @return + */ + public String getSheet() { + return sheet; + } + + public String getOOXML() { + // TODO: Finish + return null; + } + } diff --git a/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.java b/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.java index 244437b..0f6550b 100644 --- a/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.java +++ b/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -29,378 +29,380 @@ import io.starter.formats.XLS.Sxvd; import io.starter.formats.XLS.Sxview; import io.starter.toolkit.Logger; -import java.io.InputStream; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; -public class PivotTableDefinition implements OOXMLElement{ - - private static final long serialVersionUID = -5070227633357072878L; - Sxview ptview= null; - - - /** - * NOT COMPLETED DO NOT USE - * parse a pivotTable OOXML element
        - * Top-level attributes

      • Location information
      • Collection of fields - *
      • Fields on the row axis
      • Items on the row axis (specific values) - *
      • Fields on the column axis
      • Items on the column axis (specific - * values)
      • Fields on the report filter region
      • Fields in the values - * region
      • Style information
        - * Outline of the XML for a pivotTableDefinition (sequence)
      • - * pivotTableDefinition
      • location
      • pivotFields
      • rowFields
      • - * rowItems
      • colFields
      • colItems
      • pageFields
      • dataFields
      • - * conditionalFormats
      • pivotTableStyleInfo - * - *
        - * - *
        -	 * A PivotTable report that has more than one row field has one inner row field,
        -	 * 	the one closest to the data area.
        -	 * 	
        -	 * 	Any other row fields are outer row fields 
        -	 * 	
        -	 * 	Items in the outermost row field are displayed only once, 
        -	 * 	but items in the rest of the row fields are repeated as needed
        -	 * 	
        -	 * 	Page fields allow you to filter the entire PivotTable report to 
        -	 * 	display data for a single item or all the items. 	
        -	 * 	
        -	 * 	Data fields provide the data values to be summarized. Usually data fields contain numbers, 
        -	 * 	which are combined with the Sum summary function, but data fields can also contain text, 
        -	 * 	in which case the PivotTable report uses the Count summary function.
        -	 * 	
        -	 * 	If a report has more than one data field, a single field button named Data 
        -	 * 	appears in the report for access to all of the data fields.
        -	 * 
        - * - * @param bk - * @param sheet - * @param ii - */ - public static PivotTableHandle parseOOXML(WorkBookHandle bk, /*Object cacheid, */Boundsheet sheet, InputStream ii) { - Sxview ptview= null; - - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pivotTableDefinition")) { // get attributes - String cacheId= "", tablename= ""; - for (int z= 0; z < xpp.getAttributeCount(); z++) { - String nm = xpp.getAttributeName(z); - String v = xpp.getAttributeValue(z); - if (nm.equals("name")) - tablename= v; - else if (nm.equals("cacheId")) { - cacheId= v; - } - //dataOnRows - //applyNumberFormats - //applyBorderFormats - //applyFontFormats - //applyPatternFormats - //applyAlignmentFormats - //applyWidthHeightFormats - //dataCaption - //updatedVersion - //showMemberPropertyTips - //useAutoFormatting - //itemPrintTitles - //createdVersion - //indent - //compact - //compactData - //gridDropZones - } +import java.io.InputStream; + +public class PivotTableDefinition implements OOXMLElement { + + private static final long serialVersionUID = -5070227633357072878L; + Sxview ptview = null; + + + /** + * NOT COMPLETED DO NOT USE + * parse a pivotTable OOXML element
        + * Top-level attributes
      • Location information
      • Collection of fields + *
      • Fields on the row axis
      • Items on the row axis (specific values) + *
      • Fields on the column axis
      • Items on the column axis (specific + * values)
      • Fields on the report filter region
      • Fields in the values + * region
      • Style information
        + * Outline of the XML for a pivotTableDefinition (sequence)
      • + * pivotTableDefinition
      • location
      • pivotFields
      • rowFields
      • + * rowItems
      • colFields
      • colItems
      • pageFields
      • dataFields
      • + * conditionalFormats
      • pivotTableStyleInfo + * + *
        + * + *
        +     * A PivotTable report that has more than one row field has one inner row field,
        +     * 	the one closest to the data area.
        +     *
        +     * 	Any other row fields are outer row fields
        +     *
        +     * 	Items in the outermost row field are displayed only once,
        +     * 	but items in the rest of the row fields are repeated as needed
        +     *
        +     * 	Page fields allow you to filter the entire PivotTable report to
        +     * 	display data for a single item or all the items.
        +     *
        +     * 	Data fields provide the data values to be summarized. Usually data fields contain numbers,
        +     * 	which are combined with the Sum summary function, but data fields can also contain text,
        +     * 	in which case the PivotTable report uses the Count summary function.
        +     *
        +     * 	If a report has more than one data field, a single field button named Data
        +     * 	appears in the report for access to all of the data fields.
        +     * 
        + * + * @param bk + * @param sheet + * @param ii + */ + public static PivotTableHandle parseOOXML(WorkBookHandle bk, /*Object cacheid, */Boundsheet sheet, InputStream ii) { + Sxview ptview = null; + + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(ii, null); // using XML 1.0 specification + int eventType = xpp.getEventType(); + + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pivotTableDefinition")) { // get attributes + String cacheId = "", tablename = ""; + for (int z = 0; z < xpp.getAttributeCount(); z++) { + String nm = xpp.getAttributeName(z); + String v = xpp.getAttributeValue(z); + if (nm.equals("name")) + tablename = v; + else if (nm.equals("cacheId")) { + cacheId = v; + } + //dataOnRows + //applyNumberFormats + //applyBorderFormats + //applyFontFormats + //applyPatternFormats + //applyAlignmentFormats + //applyWidthHeightFormats + //dataCaption + //updatedVersion + //showMemberPropertyTips + //useAutoFormatting + //itemPrintTitles + //createdVersion + //indent + //compact + //compactData + //gridDropZones + } // KSC: TESTING!!! -cacheId="0"; - short cid=(short) (Integer.valueOf(cacheId).shortValue() ); - SxStreamID ptstream= bk.getWorkBook().getPivotStream(cid + 1); - ptview= sheet.addPivotTable(ptstream.getCellRange().toString(), bk, cid + 1, tablename); - ptview.setDataName("Values"); // default - ptview.setICache(cid); - } else if (tnm.equals("location")) { - parseLocationOOXML(xpp, ptview, bk); - } else if (tnm.equals("pivotFields")) { // Represents the collection of fields that appear on the PivotTable. - parsePivotFields(xpp, ptview); - } else if (tnm.equals("pageFields")) { // Represents the collection of items in the page or report filter region of the PivotTable. + cacheId = "0"; + short cid = Integer.valueOf(cacheId).shortValue(); + SxStreamID ptstream = bk.getWorkBook().getPivotStream(cid + 1); + ptview = sheet.addPivotTable(ptstream.getCellRange().toString(), bk, cid + 1, tablename); + ptview.setDataName("Values"); // default + ptview.setICache(cid); + } else if (tnm.equals("location")) { + parseLocationOOXML(xpp, ptview, bk); + } else if (tnm.equals("pivotFields")) { // Represents the collection of fields that appear on the PivotTable. + parsePivotFields(xpp, ptview); + } else if (tnm.equals("pageFields")) { // Represents the collection of items in the page or report filter region of the PivotTable. // short count = Integer.valueOf(xpp.getAttributeValue(0)).shortValue(); // ptview.setCDimPg(count); - } else if (tnm.equals("pageField")) { // count: # of pageField elements - parsePageFieldOOXML(xpp, ptview); - } else if (tnm.equals("dataFields")) { // Represents the collection of items in the data region of the PivotTable. + } else if (tnm.equals("pageField")) { // count: # of pageField elements + parsePageFieldOOXML(xpp, ptview); + } else if (tnm.equals("dataFields")) { // Represents the collection of items in the data region of the PivotTable. // short count = Integer.valueOf(xpp.getAttributeValue(0)).shortValue(); // ptview.setCDimData(count); - } else if (tnm.equals("dataField")) { // count: # of dataField elements - parseDataFieldOOXML(xpp, ptview); - } else if (tnm.equals("colFields") || tnm.equals("rowFields")) { // the collection of fields on the column or row axis - parseFieldOOXML(xpp, ptview); - } else if (tnm.equals("rowItems") || tnm.equals("colItems")) { // the collection of column items or row items - parseLineItemOOXML(xpp, ptview); - } else if (tnm.equals("formats")) { - parseFormatsOOXML(xpp, ptview); - } else if (tnm.equals("chartFormats")) { - parseFormatsOOXML(xpp, ptview); - } else if (tnm.equals("pivotTableStyleInfo")) { - ; - } - } else if (eventType == XmlPullParser.END_TAG) { // go to end of file - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("PivotTableDefinition.parseOOXML: " + e.toString()); - } - PivotTableHandle pth= new PivotTableHandle(ptview, bk); - return pth; - } - - /** - * parses a pivotTableDefinition location firstDataCol, firstDataRow, - * firstHeaderRow, ref [all req], colPageCount, rowPageCount - * - * @param xpp - * @param pth - * @return - * @throws XmlPullParserException - */ - private static void parseLocationOOXML(XmlPullParser xpp, Sxview ptview, WorkBookHandle bk) throws XmlPullParserException { - try { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equalsIgnoreCase("ref")) // req; Specifies the first row of the actual PivotTable (NOT the data) - ptview.setLocation(v); - else if (nm.equalsIgnoreCase("firstDataCol")) // req - // Specifies the first column of the PivotTable data, relative to the top left cell in the ref value. - ptview.setColFirstData((short)(Integer.valueOf(v).shortValue())); - else if (nm.equalsIgnoreCase("firstDataRow")) // req - // Specifies the first row of the PivotTable data, relative to the top left cell in the ref value. - ptview.setRwFirstData((short) (Integer.valueOf(v).shortValue())); - else if (nm.equalsIgnoreCase("firstHeaderRow")) // req - // Specifies the first row of the PivotTable header relative to the top left cell in the ref value. - ptview.setRwFirstHead((short)(Integer.valueOf(v).shortValue())); - else if (nm.equalsIgnoreCase("rowPageCount")) // def= 0 - // Specifies the number of rows per page for this PivotTable that the filter area will occupy. By default there is a - // single column of filter fields per page and the fields occupy as many rows as there are fields. - ; - else if (nm.equalsIgnoreCase("colPageCount")) // def= 0 - // Specifies the number of columns per page for this PivotTable that the filter area will occupy. By default - // there is a single column of filter fields per page and the fields occupy as many rows as there are fields. - ; - } - } catch (Exception e) { - throw new XmlPullParserException("PivotTableHandle.parseLocation:"); - } - } - - /** - parses the pivotFields element of the pivotTableDefinition parent -
        Represents the collection of fields that appear on the PivotTable. - - * @param xpp - * @throws XmlPullParserException - */ - private static void parsePivotFields(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { - try { - int eventType = xpp.getEventType(); - String elname = xpp.getName(); - int fcount= 0; - Sxvd curAxis= null; // up to 4 axes: ROW, COL, PAGE or DATA - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pivotField")) { // Represents a single field in the PivotTable. This complex type contains information about the field, including the collection of items in the field. - curAxis= null; - for (int i = 0; i < xpp.getAttributeCount(); i++) { // TODO: HANDLE ALL ATTRIBUTES ***** - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equalsIgnoreCase("axis")) // Specifies the region of the PivotTable that this field is displayed - curAxis= ptview.addPivotFieldToAxis(axisLookup(v), fcount++); // axisPage, axisRow, axisCol - else if (nm.equalsIgnoreCase("showAll")) // Specifies a boolean value that indicates whether to show all items for this field. - ; // A value of off, 0, or false indicates items be shown according to user specified criteria - else if (nm.equalsIgnoreCase("defaultSubtotal")) // Specifies a boolean value that indicates whether the default subtotal aggregation // function is displayed for this field. - ; - else if (nm.equalsIgnoreCase("numFmtId")) // Specifies the identifier of the number format to apply to this field. - ; - else if (nm.equalsIgnoreCase("dataField")) { // Specifies a boolean value that indicates whether this field appears in the data region of the PivotTable. - if (v.equals("1")&& curAxis==null) - curAxis= ptview.addPivotFieldToAxis(axisLookup("axisValues"), fcount++); // DATA axis - }else if (nm.equalsIgnoreCase("multipleItemSelectionAllowed")) // Specifies a boolean value that indicates whether the field can have multiple items selected in the page field. - ; - else if (nm.equalsIgnoreCase("sortType")) // ascending, descending or manual - ; - } - - } else if (tnm.equals("items")) { // Represents the collection of items in a PivotTable field. The items in the collection are ordered by index. Items - // represent the unique entries from the field in the source data. - parsePivotItemOOXML(xpp, ptview, curAxis); - } else if (tnm.equals("pivotArea")) { // parent= autoSortScope, which has no attributes or other children - parsePivotAreaOOXML(xpp); - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - throw new XmlPullParserException("parsePivotFields:" + e.toString()); - } - } - - /** - * parses pivot items -- Represents the collection of items in a PivotTable - * field. The items in the collection are ordered by index. Items represent - * the unique entries from the field in the source data The order in which - * the items are listed is the order they would appear on a particular axis - * (row or column, for example). parent= pivotField - * - * @param xpp - * @throws XmlPullParserException - */ - private static void parsePivotItemOOXML(XmlPullParser xpp, Sxview ptview, Sxvd axis) throws XmlPullParserException { + } else if (tnm.equals("dataField")) { // count: # of dataField elements + parseDataFieldOOXML(xpp, ptview); + } else if (tnm.equals("colFields") || tnm.equals("rowFields")) { // the collection of fields on the column or row axis + parseFieldOOXML(xpp, ptview); + } else if (tnm.equals("rowItems") || tnm.equals("colItems")) { // the collection of column items or row items + parseLineItemOOXML(xpp, ptview); + } else if (tnm.equals("formats")) { + parseFormatsOOXML(xpp, ptview); + } else if (tnm.equals("chartFormats")) { + parseFormatsOOXML(xpp, ptview); + } else if (tnm.equals("pivotTableStyleInfo")) { + } + } else if (eventType == XmlPullParser.END_TAG) { // go to end of file + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("PivotTableDefinition.parseOOXML: " + e.toString()); + } + PivotTableHandle pth = new PivotTableHandle(ptview, bk); + return pth; + } + + /** + * parses a pivotTableDefinition location firstDataCol, firstDataRow, + * firstHeaderRow, ref [all req], colPageCount, rowPageCount + * + * @param xpp + * @param pth + * @return + * @throws XmlPullParserException + */ + private static void parseLocationOOXML(XmlPullParser xpp, Sxview ptview, WorkBookHandle bk) throws XmlPullParserException { + try { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + String v = xpp.getAttributeValue(i); + if (nm.equalsIgnoreCase("ref")) // req; Specifies the first row of the actual PivotTable (NOT the data) + ptview.setLocation(v); + else if (nm.equalsIgnoreCase("firstDataCol")) // req + // Specifies the first column of the PivotTable data, relative to the top left cell in the ref value. + ptview.setColFirstData(Integer.valueOf(v).shortValue()); + else if (nm.equalsIgnoreCase("firstDataRow")) // req + // Specifies the first row of the PivotTable data, relative to the top left cell in the ref value. + ptview.setRwFirstData(Integer.valueOf(v).shortValue()); + else if (nm.equalsIgnoreCase("firstHeaderRow")) // req + // Specifies the first row of the PivotTable header relative to the top left cell in the ref value. + ptview.setRwFirstHead(Integer.valueOf(v).shortValue()); + else if (nm.equalsIgnoreCase("rowPageCount")) // def= 0 + // Specifies the number of rows per page for this PivotTable that the filter area will occupy. By default there is a + // single column of filter fields per page and the fields occupy as many rows as there are fields. + ; + else if (nm.equalsIgnoreCase("colPageCount")) // def= 0 + // Specifies the number of columns per page for this PivotTable that the filter area will occupy. By default + // there is a single column of filter fields per page and the fields occupy as many rows as there are fields. + ; + } + } catch (Exception e) { + throw new XmlPullParserException("PivotTableHandle.parseLocation:"); + } + } + + /** + * parses the pivotFields element of the pivotTableDefinition parent + *
        Represents the collection of fields that appear on the PivotTable. + * + * @param xpp + * @throws XmlPullParserException + */ + private static void parsePivotFields(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { try { int eventType = xpp.getEventType(); - String elname= xpp.getName(); - //int count = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // count # of item elements + String elname = xpp.getName(); + int fcount = 0; + Sxvd curAxis = null; // up to 4 axes: ROW, COL, PAGE or DATA while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("item")) { // Represents a single item in PivotTable field. - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equals("c")) // Specifies a boolean value that indicates whether the approximate number of child items for this item is greater than zero. - ; - else if (nm.equals("d")) // Specifies a boolean value that indicates whether this item has been expanded in the PivotTable view. - ; - else if (nm.equals("e")) // Specifies a boolean value that indicates whether attribute hierarchies nested next to each other on a PivotTable row or column will offer drilling "across" each other or not. - ; - else if (nm.equals("f")) // Specifies a boolean value that indicates whether this item is a calculated member. - ; - else if (nm.equals("h")) // Specifies a boolean value that indicates whether the item is hidden. - ; - else if (nm.equals("m")) // Specifies a boolean value that indicate whether the item has a missing value. - ; - else if (nm.equals("n")) // Specifies the user caption of the item. - ; - else if (nm.equals("s")) // Specifies a boolean value that indicates whether the item has a character value. - ; - else if (nm.equals("sd")) // Specifies a boolean value that indicates whether the details are hidden for this item. - ; - else if (nm.equals("t")) // Specifies the type of this item. A value of 'default' indicates the subtotal or total item. - if (v.equals("default")) - ptview.addPivotItem(axis, 1, -1); - else - Logger.logWarn("PivitItem: Unknown type"); // REMOVE WHEN TESTED - else if (nm.equals("x")) // Specifies the item index in pivotFields collection in the PivotCache. Applies only non- OLAP PivotTables. - ptview.addPivotItem(axis, 0, Integer.valueOf(v).intValue()); - } - } - } else if(eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pivotField")) { // Represents a single field in the PivotTable. This complex type contains information about the field, including the collection of items in the field. + curAxis = null; + for (int i = 0; i < xpp.getAttributeCount(); i++) { // TODO: HANDLE ALL ATTRIBUTES ***** + String nm = xpp.getAttributeName(i); + String v = xpp.getAttributeValue(i); + if (nm.equalsIgnoreCase("axis")) // Specifies the region of the PivotTable that this field is displayed + curAxis = ptview.addPivotFieldToAxis(axisLookup(v), fcount++); // axisPage, axisRow, axisCol + else if (nm.equalsIgnoreCase("showAll")) // Specifies a boolean value that indicates whether to show all items for this field. + ; // A value of off, 0, or false indicates items be shown according to user specified criteria + else if (nm.equalsIgnoreCase("defaultSubtotal")) // Specifies a boolean value that indicates whether the default subtotal aggregation // function is displayed for this field. + ; + else if (nm.equalsIgnoreCase("numFmtId")) // Specifies the identifier of the number format to apply to this field. + ; + else if (nm.equalsIgnoreCase("dataField")) { // Specifies a boolean value that indicates whether this field appears in the data region of the PivotTable. + if (v.equals("1") && curAxis == null) + curAxis = ptview.addPivotFieldToAxis(axisLookup("axisValues"), fcount++); // DATA axis + } else if (nm.equalsIgnoreCase("multipleItemSelectionAllowed")) // Specifies a boolean value that indicates whether the field can have multiple items selected in the page field. + ; + else if (nm.equalsIgnoreCase("sortType")) // ascending, descending or manual + ; + } + + } else if (tnm.equals("items")) { // Represents the collection of items in a PivotTable field. The items in the collection are ordered by index. Items + // represent the unique entries from the field in the source data. + parsePivotItemOOXML(xpp, ptview, curAxis); + } else if (tnm.equals("pivotArea")) { // parent= autoSortScope, which has no attributes or other children + parsePivotAreaOOXML(xpp); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals(elname)) + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + throw new XmlPullParserException("parsePivotFields:" + e.toString()); + } + } + + /** + * parses pivot items -- Represents the collection of items in a PivotTable + * field. The items in the collection are ordered by index. Items represent + * the unique entries from the field in the source data The order in which + * the items are listed is the order they would appear on a particular axis + * (row or column, for example). parent= pivotField + * + * @param xpp + * @throws XmlPullParserException + */ + private static void parsePivotItemOOXML(XmlPullParser xpp, Sxview ptview, Sxvd axis) throws XmlPullParserException { + try { + int eventType = xpp.getEventType(); + String elname = xpp.getName(); + //int count = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // count # of item elements + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("item")) { // Represents a single item in PivotTable field. + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + String v = xpp.getAttributeValue(i); + if (nm.equals("c")) // Specifies a boolean value that indicates whether the approximate number of child items for this item is greater than zero. + ; + else if (nm.equals("d")) // Specifies a boolean value that indicates whether this item has been expanded in the PivotTable view. + ; + else if (nm.equals("e")) // Specifies a boolean value that indicates whether attribute hierarchies nested next to each other on a PivotTable row or column will offer drilling "across" each other or not. + ; + else if (nm.equals("f")) // Specifies a boolean value that indicates whether this item is a calculated member. + ; + else if (nm.equals("h")) // Specifies a boolean value that indicates whether the item is hidden. + ; + else if (nm.equals("m")) // Specifies a boolean value that indicate whether the item has a missing value. + ; + else if (nm.equals("n")) // Specifies the user caption of the item. + ; + else if (nm.equals("s")) // Specifies a boolean value that indicates whether the item has a character value. + ; + else if (nm.equals("sd")) // Specifies a boolean value that indicates whether the details are hidden for this item. + ; + else if (nm.equals("t")) // Specifies the type of this item. A value of 'default' indicates the subtotal or total item. + if (v.equals("default")) + ptview.addPivotItem(axis, 1, -1); + else + Logger.logWarn("PivitItem: Unknown type"); // REMOVE WHEN TESTED + else if (nm.equals("x")) // Specifies the item index in pivotFields collection in the PivotCache. Applies only non- OLAP PivotTables. + ptview.addPivotItem(axis, 0, Integer.valueOf(v).intValue()); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals(elname)) + break; } - eventType = xpp.next(); + eventType = xpp.next(); } } catch (Exception e) { - throw new XmlPullParserException("parsePivotItemOOXML:" + e.toString()); - } + throw new XmlPullParserException("parsePivotItemOOXML:" + e.toString()); + } } - - /** - * parses the format element of the pivotTableDefinition. Represents the collection of formats applied to PivotTable. - * @param xpp - * @throws XmlPullParserException - */ - private static void parseFormatsOOXML(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { - try { - int eventType = xpp.getEventType(); - String elname = xpp.getName(); - //int count = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // count # of item elements - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("format")) { // parent= formats - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - //String v = xpp.getAttributeValue(i); - if (nm.equals("dxfId")) { // Specifies the identifier of the format the application is currently using for the PivotTable. Formatting information is written to the styles part. See the Styles section (§3.8) for more information on formats. - - } else if (nm.equals("action")) { + + /** + * parses the format element of the pivotTableDefinition. Represents the collection of formats applied to PivotTable. + * + * @param xpp + * @throws XmlPullParserException + */ + private static void parseFormatsOOXML(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { + try { + int eventType = xpp.getEventType(); + String elname = xpp.getName(); + //int count = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // count # of item elements + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("format")) { // parent= formats + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + //String v = xpp.getAttributeValue(i); + if (nm.equals("dxfId")) { // Specifies the identifier of the format the application is currently using for the PivotTable. Formatting information is written to the styles part. See the Styles section (§3.8) for more information on formats. + + } else if (nm.equals("action")) { /* Specifies the formatting behavior for the area indicated in the pivotArea element. The default value for this attribute is "formatting," which indicates that the specified cells have some formatting applied. The format is specified in the dxfId attribute. If the formatting is cleared from the cells, then the value of this attribute becomes "blank." - */ - } - } - } else if (tnm.equals("chartFormat")) { // parent= chartFormats - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - //String v = xpp.getAttributeValue(i); - if (nm.equals("chart")) { // Specifies the index of the chart part to which the formatting applies. - } else if (nm.equals("format")) { // Specifies the index of the pivot format that is currently in use. This index corresponds to a dxf element in the Styles part. - } else if (nm.equals("series")) { // Specifies a boolean value that indicates whether format applies to a series. (default=false) - } - } - } else if (tnm.equals("pivotArea")) - parsePivotAreaOOXML(xpp); - } else if(eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; + */ + } + } + } else if (tnm.equals("chartFormat")) { // parent= chartFormats + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + //String v = xpp.getAttributeValue(i); + if (nm.equals("chart")) { // Specifies the index of the chart part to which the formatting applies. + } else if (nm.equals("format")) { // Specifies the index of the pivot format that is currently in use. This index corresponds to a dxf element in the Styles part. + } else if (nm.equals("series")) { // Specifies a boolean value that indicates whether format applies to a series. (default=false) + } + } + } else if (tnm.equals("pivotArea")) + parsePivotAreaOOXML(xpp); + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals(elname)) + break; } - eventType = xpp.next(); + eventType = xpp.next(); } } catch (Exception e) { - throw new XmlPullParserException("parsePivotAreaOOXML:" + e.toString()); + throw new XmlPullParserException("parsePivotAreaOOXML:" + e.toString()); } - } - - /** - * parse pivotArea element: Rule describing a PivotTable selection (format, pivotField ...) - * @param xpp - * @throws XmlPullParserException - */ - private static void parsePivotAreaOOXML(XmlPullParser xpp) throws XmlPullParserException { - try { - int eventType = xpp.getEventType(); - String elname = xpp.getName(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pivotArea")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - //String v = xpp.getAttributeValue(i); - if (nm.equals("field")) // Index of the field that this selection rule refers to. - ; - else if (nm.equals("type")) // all, button, data, none, normal, origin, topRight - ; - else if (nm.equals("dataOnly")) // Flag indicating whether only the data values (in the data area of the view) for an item selection are selected and does not include the item labels. - ; - else if (nm.equals("labelOnly")) // Flag indicating whether only the item labels for an item selection are selected and does not include the data values (in the data area of the view). - ; - else if (nm.equals("outline")) // Flag indicating whether the rule refers to an area that is in outline mode. - ; - else if (nm.equals("axis")) //The region of the PivotTable to which this rule applies. - ; - else if (nm.equals("fieldPosition")) // Position of the field within the axis to which this rule applies. - ; - // grandRow, grandCol, cacheIndex, offset, collapsedLevelsAreSubtotals - } - } else if (tnm.equals("references")) { // Represents the set of selected fields and the selected items within those fields - // count - } else if (tnm.equals("reference")) { + } + + /** + * parse pivotArea element: Rule describing a PivotTable selection (format, pivotField ...) + * + * @param xpp + * @throws XmlPullParserException + */ + private static void parsePivotAreaOOXML(XmlPullParser xpp) throws XmlPullParserException { + try { + int eventType = xpp.getEventType(); + String elname = xpp.getName(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pivotArea")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + //String v = xpp.getAttributeValue(i); + if (nm.equals("field")) // Index of the field that this selection rule refers to. + ; + else if (nm.equals("type")) // all, button, data, none, normal, origin, topRight + ; + else if (nm.equals("dataOnly")) // Flag indicating whether only the data values (in the data area of the view) for an item selection are selected and does not include the item labels. + ; + else if (nm.equals("labelOnly")) // Flag indicating whether only the item labels for an item selection are selected and does not include the data values (in the data area of the view). + ; + else if (nm.equals("outline")) // Flag indicating whether the rule refers to an area that is in outline mode. + ; + else if (nm.equals("axis")) //The region of the PivotTable to which this rule applies. + ; + else if (nm.equals("fieldPosition")) // Position of the field within the axis to which this rule applies. + ; + // grandRow, grandCol, cacheIndex, offset, collapsedLevelsAreSubtotals + } + } else if (tnm.equals("references")) { // Represents the set of selected fields and the selected items within those fields + // count + } else if (tnm.equals("reference")) { /* * 8 @@ -420,180 +422,181 @@ else if (nm.equals("fieldPosition")) // Position of the field within the axis to 22 23 */ - } else if (tnm.equals("x")) { - //int index= parseItemIndexOOXML(xpp); - } - } else if(eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; + } else if (tnm.equals("x")) { + //int index= parseItemIndexOOXML(xpp); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals(elname)) + break; } - eventType = xpp.next(); + eventType = xpp.next(); } } catch (Exception e) { - throw new XmlPullParserException("parsePivotAreaOOXML:"); - } - - } - - /** - * element i parent= rowItem or colItem - *
        the collection of items in the row axis -- index corresponds to that in the location range - *
        OR - *
        the collection of column items-- index corresponds to that in the location range - *
        The first collection represents all item values for the first column in the column axis area - *
        The first in the first corresponds to the first field in the columns area - * or - *
        Represents the collection of items in the row region of the PivotTable. - * - * @param xpp - * @throws XmlPullParserException - */ - private static void parseLineItemOOXML(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { - try { - // number of row or col lines == cRw or cCol of SxVIEW - //int linecount= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // parent element= rowItems or colItems - int eventType = xpp.getEventType(); - String elname = xpp.getName(); - boolean isRowItems= (elname.equals("rowItems")); - - final Class enumType = ITEMTYPES.class; - int type= 0, repeat= 0; - short[] indexes= null; - int nIndexes= (isRowItems)?ptview.getCDimRw():ptview.getCDimCol(); - int index= 0, nLines= 0; - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("i")) { // a colItem or rowItem - indexes= new short[nIndexes]; - nLines= nIndexes; - index= 0; - type= repeat= 0; - for (int i = 0; i < xpp.getAttributeCount(); i++) { // i, r, t - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equals("i")) { // Specifies a zero-based index indicating the referenced data item it in a data field with multiple data items. - } else if (nm.equals("r")) {// Specifies the number of items to repeat from the previous row item. Note: The first item has no @r explicitly written. Since a default of "0" is specified in the schema, for any item - // whose @r is missing, a default value of "0" is implied. - repeat= Integer.valueOf(v).intValue(); - index+=repeat; - nLines-=repeat; - } else if (nm.equals("t")) {// Specifies the type of the item. Value of 'default' indicates a grand total as the last row item value - // default= data, avg, blank, count, countA, data, grand, max, min, product, stdDev, stdDevP, sum, var, varP - type= Enum.valueOf(enumType, "_" + v ).ordinal(); - if (type!=0 || type!=0xE) { + throw new XmlPullParserException("parsePivotAreaOOXML:"); + } + + } + + /** + * element i parent= rowItem or colItem + *
        the collection of items in the row axis -- index corresponds to that in the location range + *
        OR + *
        the collection of column items-- index corresponds to that in the location range + *
        The first collection represents all item values for the first column in the column axis area + *
        The first in the first corresponds to the first field in the columns area + * or + *
        Represents the collection of items in the row region of the PivotTable. + * + * @param xpp + * @throws XmlPullParserException + */ + private static void parseLineItemOOXML(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { + try { + // number of row or col lines == cRw or cCol of SxVIEW + //int linecount= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // parent element= rowItems or colItems + int eventType = xpp.getEventType(); + String elname = xpp.getName(); + boolean isRowItems = (elname.equals("rowItems")); + + final Class enumType = ITEMTYPES.class; + int type = 0, repeat = 0; + short[] indexes = null; + int nIndexes = (isRowItems) ? ptview.getCDimRw() : ptview.getCDimCol(); + int index = 0, nLines = 0; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("i")) { // a colItem or rowItem + indexes = new short[nIndexes]; + nLines = nIndexes; + index = 0; + type = repeat = 0; + for (int i = 0; i < xpp.getAttributeCount(); i++) { // i, r, t + String nm = xpp.getAttributeName(i); + String v = xpp.getAttributeValue(i); + if (nm.equals("i")) { // Specifies a zero-based index indicating the referenced data item it in a data field with multiple data items. + } else if (nm.equals("r")) {// Specifies the number of items to repeat from the previous row item. Note: The first item has no @r explicitly written. Since a default of "0" is specified in the schema, for any item + // whose @r is missing, a default value of "0" is implied. + repeat = Integer.valueOf(v).intValue(); + index += repeat; + nLines -= repeat; + } else if (nm.equals("t")) {// Specifies the type of the item. Value of 'default' indicates a grand total as the last row item value + // default= data, avg, blank, count, countA, data, grand, max, min, product, stdDev, stdDevP, sum, var, varP + type = Enum.valueOf(enumType, "_" + v).ordinal(); + if (type != 0 || type != 0xE) { // index++; stil confused on this ... - nLines--; - } - } - - } - } else if (tnm.equals("x")) { - indexes[index++]= (short)parseItemIndexOOXML(xpp); // v - } - } else if (eventType == XmlPullParser.END_TAG) { - String endname= xpp.getName(); - if (endname.equals(elname)) - break; - else if (endname.equals("i")) { - if (isRowItems) - ptview.addPivotLineToROWAxis(repeat, nLines, type, indexes); - else - ptview.addPivotLineToCOLAxis(repeat, nLines, type, indexes); - } - - } - eventType = xpp.next(); - } - } catch (Exception e) { - throw new XmlPullParserException("parseItemOOXML:" + e.toString()); - } - } - - /* - * item types enum -- add an underscore because "default" is not a valid entry - */ - enum ITEMTYPES { - _data, - _default, - _sum, - _countA, - _count, - _avg, - _max, - _min, - _product, - _stdDev, - _stdDevP, - _var, - _varP, - _grand, - _blank, - }; - - /** - * Represents a generic field that can appear either on the column or the - * row region of the PivotTable. There will be as many elements as there - * are item values in any particular column or row. attribute: x: Specifies - * the index to a pivotField item value - * - * @param xpp - * @throws XmlPullParserException - */ - private static void parseFieldOOXML(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { - try { - //int fieldcount = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - int eventType = xpp.getEventType(); - String elname = xpp.getName(); + nLines--; + } + } + + } + } else if (tnm.equals("x")) { + indexes[index++] = (short) parseItemIndexOOXML(xpp); // v + } + } else if (eventType == XmlPullParser.END_TAG) { + String endname = xpp.getName(); + if (endname.equals(elname)) + break; + else if (endname.equals("i")) { + if (isRowItems) + ptview.addPivotLineToROWAxis(repeat, nLines, type, indexes); + else + ptview.addPivotLineToCOLAxis(repeat, nLines, type, indexes); + } + + } + eventType = xpp.next(); + } + } catch (Exception e) { + throw new XmlPullParserException("parseItemOOXML:" + e.toString()); + } + } + + /* + * item types enum -- add an underscore because "default" is not a valid entry + */ + enum ITEMTYPES { + _data, + _default, + _sum, + _countA, + _count, + _avg, + _max, + _min, + _product, + _stdDev, + _stdDevP, + _var, + _varP, + _grand, + _blank, + } + + /** + * Represents a generic field that can appear either on the column or the + * row region of the PivotTable. There will be as many elements as there + * are item values in any particular column or row. attribute: x: Specifies + * the index to a pivotField item value + * + * @param xpp + * @throws XmlPullParserException + */ + private static void parseFieldOOXML(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { + try { + //int fieldcount = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + int eventType = xpp.getEventType(); + String elname = xpp.getName(); /* if (elname.equals("rowFields")) ptview.setCDimRw((short)fieldcount); else - ptview.setCDimCol((short)fieldcount);*/ - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("field")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("x")) { - String v = xpp.getAttributeValue(i); - if (elname.equals("rowFields")) - ptview.addRowField(Integer.valueOf(v)); - else - ptview.addColField(Integer.valueOf(v)); - } - } - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - throw new XmlPullParserException("parseFieldOOXML:" + e.toString()); - } - } - - /** - * This element represents an array of indexes to cached shared item values - *
        element x -
        child of reference (...pivotField), i (rowField, colField) - * - * @param xpp - * @return int index - * @throws XmlPullParserException - */ - private static int parseItemIndexOOXML(XmlPullParser xpp) { - int v= 0; - try { - if (xpp.getAttributeCount()>0) - v = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // index - } catch (Exception e) { } - return v; - } - - private static void parseDataFieldOOXML(XmlPullParser xpp, Sxview ptview) { + ptview.setCDimCol((short)fieldcount);*/ + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("field")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("x")) { + String v = xpp.getAttributeValue(i); + if (elname.equals("rowFields")) + ptview.addRowField(Integer.valueOf(v)); + else + ptview.addColField(Integer.valueOf(v)); + } + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals(elname)) + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + throw new XmlPullParserException("parseFieldOOXML:" + e.toString()); + } + } + + /** + * This element represents an array of indexes to cached shared item values + *
        element x + *
        child of reference (...pivotField), i (rowField, colField) + * + * @param xpp + * @throws XmlPullParserException + * @return int index + */ + private static int parseItemIndexOOXML(XmlPullParser xpp) { + int v = 0; + try { + if (xpp.getAttributeCount() > 0) + v = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // index + } catch (Exception e) { + } + return v; + } + + private static void parseDataFieldOOXML(XmlPullParser xpp, Sxview ptview) { /* 9 @@ -604,112 +607,111 @@ private static void parseDataFieldOOXML(XmlPullParser xpp, Sxview ptview) { * default="1048832"/> 12 */ - int fieldIndex= 0; - String aggregateFunction= null; - String name= null; - for (int z= 0; z < xpp.getAttributeCount(); z++) { - String nm = xpp.getAttributeName(z); - String v = xpp.getAttributeValue(z); - if (nm.equals("name")) - name= v; - else if (nm.equals("fld")) - fieldIndex= Integer.valueOf(xpp.getAttributeValue(z)); - else if (nm.equals("subtotal")) // default= "sum" - aggregateFunction= v; - } + int fieldIndex = 0; + String aggregateFunction = null; + String name = null; + for (int z = 0; z < xpp.getAttributeCount(); z++) { + String nm = xpp.getAttributeName(z); + String v = xpp.getAttributeValue(z); + if (nm.equals("name")) + name = v; + else if (nm.equals("fld")) + fieldIndex = Integer.valueOf(xpp.getAttributeValue(z)); + else if (nm.equals("subtotal")) // default= "sum" + aggregateFunction = v; + } // TODO: // showDataAs, baseItem, baseField --> display format // numFmtId - ptview.addDataField(fieldIndex, aggregateFunction, name); - } - - /** - * parse the pageField element, which defines a pivot field on the PAGE axis - * @param xpp - * @param ptview - */ - private static void parsePageFieldOOXML(XmlPullParser xpp, Sxview ptview) { - int fieldIndex= 0, itemIndex= 0x7FFD; - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("fld")) { - fieldIndex= Integer.valueOf(xpp.getAttributeValue(i)); - } else if (nm.equals("item")) { - itemIndex= Integer.valueOf(xpp.getAttributeValue(i)); - } - } - ptview.addPageField(fieldIndex, itemIndex); - } - /** - * Changes a range in a PivotTable to expand until it includes the cell - * address from CellHandle. - * - * Example: - * - * CellHandle cell = new Cellhandle("D4") PivotTable = SUM(A1:B2) - * addCellToRange("A1:B2",cell); would change the PivotTable to look like - * "SUM(A1:D4)" - * - * Returns false if PivotTable does not contain the PivotTableLoc range. - * - * @param String - * - the Cell Range as a String to add the Cell to - * @param CellHandle - * - the CellHandle to add to the range - * - * public boolean addCellToRange(String PivotTableLoc, CellHandle - * handle) throws PivotTableNotFoundException{ int[] - * PivotTableaddr = ExcelTools.getRangeRowCol(PivotTableLoc); - * String handleaddr = handle.getCellAddress(); int[] celladdr = - * ExcelTools.getRowColFromString(handleaddr); - * - * // check existing range and set new range vals if the new Cell - * is outside if(celladdr[0] > - * PivotTableaddr[2])PivotTableaddr[2] = celladdr[0]; - * if(celladdr[0] < PivotTableaddr[0])PivotTableaddr[0] = - * celladdr[0]; if(celladdr[1] > - * PivotTableaddr[3])PivotTableaddr[3] = celladdr[1]; - * if(celladdr[1] < PivotTableaddr[1])PivotTableaddr[1] = - * celladdr[1]; String newaddr = - * ExcelTools.formatRange(PivotTableaddr); boolean b = - * this.changePivotTableLocation(PivotTableLoc, newaddr); return - * b; } - */ - - /** - * Returns the "SXVIEW" record for this PivotTable. - * - * @return - * - * public Sxview getPt() { return pt; } - */ - - /** - * @param sxview - * - * public void setPt(Sxview sxview) { pt= sxview; } - */ - - public OOXMLElement cloneElement() { - return null; - } - - public String getOOXML() { - // TODO: Finish - return null; - } - - private static int axisLookup(String axis) { - if (axis.equals("axisRow")) - return Sxvd.AXIS_ROW; - else if (axis.equals("axisCol")) - return Sxvd.AXIS_COL; - else if (axis.equals("axisPage")) - return Sxvd.AXIS_PAGE; - else if (axis.equals("axisValues")) - return Sxvd.AXIS_DATA; - return Sxvd.AXIS_NONE; - - } - + ptview.addDataField(fieldIndex, aggregateFunction, name); + } + + /** + * parse the pageField element, which defines a pivot field on the PAGE axis + * + * @param xpp + * @param ptview + */ + private static void parsePageFieldOOXML(XmlPullParser xpp, Sxview ptview) { + int fieldIndex = 0, itemIndex = 0x7FFD; + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("fld")) { + fieldIndex = Integer.valueOf(xpp.getAttributeValue(i)); + } else if (nm.equals("item")) { + itemIndex = Integer.valueOf(xpp.getAttributeValue(i)); + } + } + ptview.addPageField(fieldIndex, itemIndex); + } + /** + * Changes a range in a PivotTable to expand until it includes the cell + * address from CellHandle. + * + * Example: + * + * CellHandle cell = new Cellhandle("D4") PivotTable = SUM(A1:B2) + * addCellToRange("A1:B2",cell); would change the PivotTable to look like + * "SUM(A1:D4)" + * + * Returns false if PivotTable does not contain the PivotTableLoc range. + * + * @param String + * - the Cell Range as a String to add the Cell to + * @param CellHandle + * - the CellHandle to add to the range + * + * public boolean addCellToRange(String PivotTableLoc, CellHandle + * handle) throws PivotTableNotFoundException{ int[] + * PivotTableaddr = ExcelTools.getRangeRowCol(PivotTableLoc); + * String handleaddr = handle.getCellAddress(); int[] celladdr = + * ExcelTools.getRowColFromString(handleaddr); + * + * // check existing range and set new range vals if the new Cell + * is outside if(celladdr[0] > + * PivotTableaddr[2])PivotTableaddr[2] = celladdr[0]; + * if(celladdr[0] < PivotTableaddr[0])PivotTableaddr[0] = + * celladdr[0]; if(celladdr[1] > + * PivotTableaddr[3])PivotTableaddr[3] = celladdr[1]; + * if(celladdr[1] < PivotTableaddr[1])PivotTableaddr[1] = + * celladdr[1]; String newaddr = + * ExcelTools.formatRange(PivotTableaddr); boolean b = + * this.changePivotTableLocation(PivotTableLoc, newaddr); return + * b; } + */ + + /** + * Returns the "SXVIEW" record for this PivotTable. + * + * @return + * + * public Sxview getPt() { return pt; } + */ + + /** + * @param sxview public void setPt(Sxview sxview) { pt= sxview; } + */ + + public OOXMLElement cloneElement() { + return null; + } + + public String getOOXML() { + // TODO: Finish + return null; + } + + private static int axisLookup(String axis) { + if (axis.equals("axisRow")) + return Sxvd.AXIS_ROW; + else if (axis.equals("axisCol")) + return Sxvd.AXIS_COL; + else if (axis.equals("axisPage")) + return Sxvd.AXIS_PAGE; + else if (axis.equals("axisValues")) + return Sxvd.AXIS_DATA; + return Sxvd.AXIS_NONE; + + } + } diff --git a/src/main/java/io/starter/formats/OOXML/SheetPr.java b/src/main/java/io/starter/formats/OOXML/SheetPr.java index 9ab7b62..559d1fd 100644 --- a/src/main/java/io/starter/formats/OOXML/SheetPr.java +++ b/src/main/java/io/starter/formats/OOXML/SheetPr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,339 +22,344 @@ */ package io.starter.formats.OOXML; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; -import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; /** * sheetPr (Sheet Properties) - * + *

        * Sheet-level properties. - * - * parent: worksheet, dialogsheet - * children: tabColor, outlinePr, pageSetUpPr - * + *

        + * parent: worksheet, dialogsheet + * children: tabColor, outlinePr, pageSetUpPr */ // TODO: Finish pageSetUpPr + input into 2003 sheet settings ** -public class SheetPr implements OOXMLElement{ - - private static final long serialVersionUID = 1781567781060400234L; - private HashMap attrs; +public class SheetPr implements OOXMLElement { + + private static final long serialVersionUID = 1781567781060400234L; + private HashMap attrs; private TabColor tab; private OutlinePr outlinePr; private PageSetupPr pageSetupPr; - public SheetPr(HashMap attrs, TabColor tab, OutlinePr op, PageSetupPr pr) { - this.attrs= attrs; - this.tab= tab; - this.outlinePr= op; - this.pageSetupPr= pr; - } - public SheetPr(SheetPr sp) { - this.attrs= sp.attrs; - this.tab= sp.tab; - this.outlinePr= sp.outlinePr; - this.pageSetupPr= sp.pageSetupPr; - } - -/** - * outlinePr - * pageSetUpPr - * tabColor - * codeName - * enableFormatConditionsCalculation - * filterMode - * published - * syncHorizontal - * syncRef - * syncVertical - * transitionEntry - * transitionEvaluation - * - * @param xpp - * @return - */ + public SheetPr(HashMap attrs, TabColor tab, OutlinePr op, PageSetupPr pr) { + this.attrs = attrs; + this.tab = tab; + this.outlinePr = op; + this.pageSetupPr = pr; + } + + public SheetPr(SheetPr sp) { + this.attrs = sp.attrs; + this.tab = sp.tab; + this.outlinePr = sp.outlinePr; + this.pageSetupPr = sp.pageSetupPr; + } + + /** + * outlinePr + * pageSetUpPr + * tabColor + * codeName + * enableFormatConditionsCalculation + * filterMode + * published + * syncHorizontal + * syncRef + * syncVertical + * transitionEntry + * transitionEvaluation + * + * @param xpp + * @return + */ public static SheetPr parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - TabColor tab= null; - OutlinePr op= null; - PageSetupPr pr= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sheetPr")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("pageSetUpPr")) { // Page setup properties of the worksheet - pr= (PageSetupPr) PageSetupPr.parseOOXML(xpp); - } else if (tnm.equals("outlinePr")) { - op= (OutlinePr) OutlinePr.parseOOXML(xpp); - } else if (tnm.equals("tabColor")) { - tab= (TabColor) TabColor.parseOOXML(xpp); - - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("sheetPr")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("sheetPr.parseOOXML: " + e.toString()); - } - SheetPr sp= new SheetPr(attrs, tab, op, pr); - return sp; + HashMap attrs = new HashMap(); + TabColor tab = null; + OutlinePr op = null; + PageSetupPr pr = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("sheetPr")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("pageSetUpPr")) { // Page setup properties of the worksheet + pr = PageSetupPr.parseOOXML(xpp); + } else if (tnm.equals("outlinePr")) { + op = OutlinePr.parseOOXML(xpp); + } else if (tnm.equals("tabColor")) { + tab = TabColor.parseOOXML(xpp); + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("sheetPr")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("sheetPr.parseOOXML: " + e.toString()); + } + SheetPr sp = new SheetPr(attrs, tab, op, pr); + return sp; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + + ooxml.append(">"); + // ordered sequence: + if (tab != null) ooxml.append(tab.getOOXML()); + if (outlinePr != null) ooxml.append(outlinePr.getOOXML()); + if (pageSetupPr != null) ooxml.append(pageSetupPr.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new SheetPr(this); + } + + /** + * return the codename used to link this sheet to vba code + * + * @return + */ + public String getCodename() { + if (attrs != null) + return attrs.get("codeName"); + return null; + } + + /** + * set the codename used to link this sheet to vba code + * + * @param codename + */ + public void setCodename(String codename) { + if (attrs == null) + attrs = new HashMap(); + attrs.put("codeName", codename); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - - ooxml.append(">"); - // ordered sequence: - if (tab!=null) ooxml.append(tab.getOOXML()); - if (outlinePr!=null) ooxml.append(outlinePr.getOOXML()); - if (pageSetupPr!=null) ooxml.append(pageSetupPr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SheetPr(this); - } - - /** - * return the codename used to link this sheet to vba code - * @return - */ - public String getCodename() { - if (attrs!=null) - return (String) attrs.get("codeName"); - return null; - } - - /** - * set the codename used to link this sheet to vba code - * @param codename - */ - public void setCodename(String codename) { - if (attrs==null) - attrs= new HashMap(); - attrs.put("codeName", codename); - } } /** * (Sheet Tab Color) * Background color of the sheet tab. - * + *

        * parent: sheetPr - * children: none - * + * children: none */ -class TabColor implements OOXMLElement{ - - private static final long serialVersionUID = -2862996863147633555L; - private HashMap attrs; - - public TabColor(HashMap attrs) { - this.attrs= attrs; - } - public TabColor(TabColor t) { - this.attrs= t.attrs; - } - - +class TabColor implements OOXMLElement { + + private static final long serialVersionUID = -2862996863147633555L; + private HashMap attrs; + + public TabColor(HashMap attrs) { + this.attrs = attrs; + } + + public TabColor(TabColor t) { + this.attrs = t.attrs; + } + + public static TabColor parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tabColor")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("tabColor")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("tabColor.parseOOXML: " + e.toString()); - } - TabColor t= new TabColor(attrs); - return t; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("tabColor")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("tabColor")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("tabColor.parseOOXML: " + e.toString()); + } + TabColor t = new TabColor(attrs); + return t; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new TabColor(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TabColor(this); - } } /** * outlinePr - * Sheet Group Outline Settings + * Sheet Group Outline Settings * all attributes are optional, default values: - applyStyles="0" showOutlineSymbols="1" summaryBelow="1" summaryRight="1" + * applyStyles="0" showOutlineSymbols="1" summaryBelow="1" summaryRight="1" */ -class OutlinePr implements OOXMLElement{ - - private static final long serialVersionUID = 3030511803286369045L; - private HashMap attrs; - - public OutlinePr(HashMap attrs) { - this.attrs= attrs; - } - public OutlinePr(OutlinePr op) { - this.attrs= op.attrs; - } - - +class OutlinePr implements OOXMLElement { + + private static final long serialVersionUID = 3030511803286369045L; + private HashMap attrs; + + public OutlinePr(HashMap attrs) { + this.attrs = attrs; + } + + public OutlinePr(OutlinePr op) { + this.attrs = op.attrs; + } + + public static OutlinePr parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("outlinePr")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("outlinePr")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OutlinePr.parseOOXML: " + e.toString()); - } - OutlinePr op= new OutlinePr(attrs); - return op; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("outlinePr")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("outlinePr")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("OutlinePr.parseOOXML: " + e.toString()); + } + OutlinePr op = new OutlinePr(attrs); + return op; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new OutlinePr(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new OutlinePr(this); - } } + /** - * * pageSetUpPr (Page Setup Properties) - Page setup properties of the worksheet - attributes: autoPageBreaks (default=true), fitToPage (default=false) - * + * Page setup properties of the worksheet + * attributes: autoPageBreaks (default=true), fitToPage (default=false) */ -class PageSetupPr implements OOXMLElement{ - - private static final long serialVersionUID = 3030511803286369045L; - private boolean autoPageBreaks= true, fitToPage= false; - - public PageSetupPr(boolean autoPageBreaks , boolean fitToPage) { - this.autoPageBreaks= autoPageBreaks; - this.fitToPage= fitToPage; - } - public PageSetupPr(PageSetupPr pr) { - this.autoPageBreaks= pr.autoPageBreaks; - this.fitToPage= pr.fitToPage; - } - - public static PageSetupPr parseOOXML(XmlPullParser xpp) { - boolean autoPageBreaks= true, fitToPage= false; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pageSetUpPr")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); - if (nm.equals("fitToPage")) - fitToPage= (xpp.getAttributeValue(i).equals("1")); - else if (nm.equals("autoPageBreaks")) - autoPageBreaks= (xpp.getAttributeValue(i).equals("1")); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("pageSetUpPr")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("PageSetupPr: " + e.toString()); - } - PageSetupPr pr= new PageSetupPr(autoPageBreaks, fitToPage); - return pr; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PageSetupPr(this); - } +class PageSetupPr implements OOXMLElement { + + private static final long serialVersionUID = 3030511803286369045L; + private boolean autoPageBreaks = true, fitToPage = false; + + public PageSetupPr(boolean autoPageBreaks, boolean fitToPage) { + this.autoPageBreaks = autoPageBreaks; + this.fitToPage = fitToPage; + } + + public PageSetupPr(PageSetupPr pr) { + this.autoPageBreaks = pr.autoPageBreaks; + this.fitToPage = pr.fitToPage; + } + + public static PageSetupPr parseOOXML(XmlPullParser xpp) { + boolean autoPageBreaks = true, fitToPage = false; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pageSetUpPr")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("fitToPage")) + fitToPage = (xpp.getAttributeValue(i).equals("1")); + else if (nm.equals("autoPageBreaks")) + autoPageBreaks = (xpp.getAttributeValue(i).equals("1")); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("pageSetUpPr")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("PageSetupPr: " + e.toString()); + } + PageSetupPr pr = new PageSetupPr(autoPageBreaks, fitToPage); + return pr; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new PageSetupPr(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/SheetView.java b/src/main/java/io/starter/formats/OOXML/SheetView.java index 5b476fd..a72d559 100644 --- a/src/main/java/io/starter/formats/OOXML/SheetView.java +++ b/src/main/java/io/starter/formats/OOXML/SheetView.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,283 +22,287 @@ */ package io.starter.formats.OOXML; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; -import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; /** * sheetView (Worksheet View) - * + *

        * A single sheet view definition. When more than 1 sheet view is defined in the file, it means that when opening * the workbook, each sheet view corresponds to a separate window within the spreadsheet application, where * each window is showing the particular sheet. containing the same workbookViewId value, the last sheetView * definition is loaded, and the others are discarded. When multiple windows are viewing the same sheet, multiple * sheetView elements (with corresponding workbookView entries) are saved. - * + *

        * parent: sheetViews * children: pane, selection, pivotSelection - * */ // TODO: finish pivotSelection -public class SheetView implements OOXMLElement{ - - private static final long serialVersionUID = 8750051341951797617L; - private HashMap attrs= new HashMap(); - private Pane pane= null; - private ArrayList selections= new ArrayList(); - - public SheetView() { - - } - - public SheetView(HashMap attrs, Pane p, ArrayList selections) { - this.attrs= attrs; - this.pane= p; - this.selections= selections; - } - - public SheetView(SheetView s) { - this.attrs= s.attrs; - this.pane= s.pane; - this.selections= s.selections; - } - +public class SheetView implements OOXMLElement { + + private static final long serialVersionUID = 8750051341951797617L; + private HashMap attrs = new HashMap(); + private Pane pane = null; + private ArrayList selections = new ArrayList(); + + public SheetView() { + + } + + public SheetView(HashMap attrs, Pane p, ArrayList selections) { + this.attrs = attrs; + this.pane = p; + this.selections = selections; + } + + public SheetView(SheetView s) { + this.attrs = s.attrs; + this.pane = s.pane; + this.selections = s.selections; + } + public static OOXMLElement parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - Pane p= null; - ArrayList selections= new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sheetView")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("pane")) { - p= (Pane) Pane.parseOOXML(xpp); - } else if (tnm.equals("selection")) { - selections.add((Selection) Selection.parseOOXML(xpp)); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("sheetView")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("sheetView.parseOOXML: " + e.toString()); - } - SheetView s= new SheetView(attrs, p, selections); - return s; + HashMap attrs = new HashMap(); + Pane p = null; + ArrayList selections = new ArrayList(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("sheetView")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("pane")) { + p = Pane.parseOOXML(xpp); + } else if (tnm.equals("selection")) { + selections.add(Selection.parseOOXML(xpp)); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("sheetView")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("sheetView.parseOOXML: " + e.toString()); + } + SheetView s = new SheetView(attrs, p, selections); + return s; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = (String) attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + if (pane != null) ooxml.append(pane.getOOXML()); + if (selections.size() > 0) { + for (int j = 0; j < selections.size(); j++) { + ooxml.append(selections.get(j).getOOXML()); + } + } + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new SheetView(this); + } + + + /** + * return the attribute value for key null if not found + * + * @param key + * @return + */ + public Object getAttr(String key) { + return this.attrs.get(key); + } + + /** + * set the atttribute value for key to val + * + * @param key + * @param val + */ + public void setAttr(String key, Object val) { + this.attrs.put(key, val); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (pane!=null) ooxml.append(pane.getOOXML()); - if (selections.size() > 0) { - for (int j= 0; j < selections.size(); j++) { - ooxml.append(((Selection) selections.get(j)).getOOXML()); - } - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SheetView(this); - } - - - /** - * return the attribute value for key null if not found - * @param key - * @return - */ - public Object getAttr(String key) { - return this.attrs.get(key); - } - - /** - * set the atttribute value for key to val - * @param key - * @param val - */ - public void setAttr(String key, Object val) { - this.attrs.put(key, val); - } - - /** - * remove a previously set attribute, if found - * @param key - */ - public void removeAttr(String key) { - this.attrs.remove(key); - } - - public void removeSelection() { - this.removeAttr("tabSelected"); - selections= new ArrayList(); - } - /** - * return the attribute value for key - * in String form "" if not found - * @param key - * @return - */ - public String getAttrS(String key) { - Object o= this.attrs.get(key); - if (o==null) - return ""; - return o.toString(); - } - + + /** + * remove a previously set attribute, if found + * + * @param key + */ + public void removeAttr(String key) { + this.attrs.remove(key); + } + + public void removeSelection() { + this.removeAttr("tabSelected"); + selections = new ArrayList(); + } + + /** + * return the attribute value for key + * in String form "" if not found + * + * @param key + * @return + */ + public String getAttrS(String key) { + Object o = this.attrs.get(key); + if (o == null) + return ""; + return o.toString(); + } + } /** * pane (View Pane) * Worksheet view pane - * + *

        * parent: sheetView, customSheetView * children: none - * */ class Pane implements OOXMLElement { - private static final long serialVersionUID = 5570779997661362205L; - private HashMap attrs= null; - - public Pane(HashMap attrs) { - this.attrs= attrs; - } - public Pane(Pane p) { - this.attrs= p.attrs; - } - - + private static final long serialVersionUID = 5570779997661362205L; + private HashMap attrs = null; + + public Pane(HashMap attrs) { + this.attrs = attrs; + } + + public Pane(Pane p) { + this.attrs = p.attrs; + } + + public static Pane parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pane")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("pane")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pane.parseOOXML: " + e.toString()); - } - Pane p= new Pane(attrs); - return p; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("pane")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("pane")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("pane.parseOOXML: " + e.toString()); + } + Pane p = new Pane(attrs); + return p; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Pane(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Pane(this); - } } /** * selection (Selection) * Worksheet view selection. - * + *

        * parent: sheetView, customSheetView * children: none - * */ class Selection implements OOXMLElement { - private static final long serialVersionUID = -5411798327743116154L; - private HashMap attrs= null; - - public Selection(HashMap attrs) { - this.attrs= attrs; - } - - public Selection(Selection s) { - this.attrs= s.attrs; - } - + private static final long serialVersionUID = -5411798327743116154L; + private HashMap attrs = null; + + public Selection(HashMap attrs) { + this.attrs = attrs; + } + + public Selection(Selection s) { + this.attrs = s.attrs; + } + public static Selection parseOOXML(XmlPullParser xpp) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("selection")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("selection")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("selection.parseOOXML: " + e.toString()); - } - Selection s= new Selection(attrs); - return s; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("selection")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("selection")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("selection.parseOOXML: " + e.toString()); + } + Selection s = new Selection(attrs); + return s; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Selection(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Selection(this); - } } diff --git a/src/main/java/io/starter/formats/OOXML/Sp.java b/src/main/java/io/starter/formats/OOXML/Sp.java index 3f539cc..8d6043c 100644 --- a/src/main/java/io/starter/formats/OOXML/Sp.java +++ b/src/main/java/io/starter/formats/OOXML/Sp.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,502 +22,524 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; +import io.starter.OpenXLS.WorkBookHandle; +import io.starter.toolkit.Logger; import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; +import java.util.Stack; /** * sp (Shape) - * - * OOXML/DrawingML element representing a single shape + *

        + * OOXML/DrawingML element representing a single shape * A shape can either be a preset or a custom geometry, * defined using the DrawingML framework. In addition to a geometry each shape can have both visual and non - * visual properties attached. Text and corresponding styling information can also be attached to a shape. - * + * visual properties attached. Text and corresponding styling information can also be attached to a shape. + *

        * parent = twoCellAnchor, oneCellAnchor, absoluteAnchor, grpSp (group shape) * children: nvSpPr, spPr, style, txBody - * - * */ -public class Sp implements OOXMLElement{ - - private static final long serialVersionUID = 7454285931503575078L; - private NvSpPr nvsp; - private SpPr sppr; - private Style sty; - private TxBody txb; - private HashMap attrs= null; - - public Sp(NvSpPr nvsp, SpPr sppr, Style sty, TxBody txb, HashMap attrs) { - this.nvsp= nvsp; - this.sppr= sppr; - this.sty= sty; - this.txb= txb; - this.attrs= attrs; - } - - public Sp(Sp shp) { - this.nvsp= shp.nvsp; - this.sppr= shp.sppr; - this.sty= shp.sty; - this.txb= shp.txb; - this.attrs= shp.attrs; - } - - - /** - * return the OOXML specific for this object - * @param xpp - * @param lastTag - * @return - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - NvSpPr nvsp= null; - SpPr sppr= null; - Style sty= null; - TxBody txb= null; - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sp")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("nvSpPr")) { // non-visual shape props - lastTag.push(tnm); - nvsp= (NvSpPr) NvSpPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("spPr")) { // shape properties - lastTag.push(tnm); - sppr= (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - //sppr.setNS("xdr"); - } else if (tnm.equals("style")) { // shape style - lastTag.push(tnm); - sty= (Style) Style.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("txBody")) { // shape text body - lastTag.push(tnm); - txb= (TxBody) TxBody.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("sp")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("sp.parseOOXML: " + e.toString()); - } - Sp shp= new Sp(nvsp, sppr, sty, txb, attrs); - return shp; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - ooxml.append(nvsp.getOOXML()); - ooxml.append(sppr.getOOXML()); - if (sty!=null) ooxml.append(sty.getOOXML()); - if (txb!=null) ooxml.append(txb.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Sp(this); - } - /** - * get Macro attribute - */ - public String getMacro() { - if (attrs.get("macro")!=null) - return (String) attrs.get("macro"); - return null; - } - - /** - * set Macro attribute - * @param macro - */ - public void setMacro(String macro) { - attrs.put("macro", macro); - } - /** - * return the id for the embedded picture (i.e. resides within the file) - * @return - */ - public String getEmbed() { - if (sppr!=null) - return sppr.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * @return - */ - public String getLink() { - if (sppr!=null) - return sppr.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * @param embed - */ - public void setEmbed(String embed) { - if (sppr!=null) sppr.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * @param embed - */ - public void setLink(String link) { - if (sppr!=null) sppr.setLink(link); - } - - /** - * return the name of this shape, if any - * @return - */ - public String getName() { - if (nvsp!=null) - return nvsp.getName(); - return null; - } - - /** - * set the name of this shape, if any - */ - public void setName(String name) { - if (nvsp!=null) - nvsp.setName(name); - } - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (nvsp!=null) - return nvsp.getDescr(); - return null; - } - /** - * set cNvPr description attribute - * sometimes associated with shape name - * @param descr - */ - public void setDescr(String descr) { - if (nvsp!=null) - nvsp.setDescr(descr); - } - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (nvsp!=null) - nvsp.setId(id); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (nvsp!=null) - return nvsp.getId(); - return -1; - } - +public class Sp implements OOXMLElement { + + private static final long serialVersionUID = 7454285931503575078L; + private NvSpPr nvsp; + private SpPr sppr; + private Style sty; + private TxBody txb; + private HashMap attrs = null; + + public Sp(NvSpPr nvsp, SpPr sppr, Style sty, TxBody txb, HashMap attrs) { + this.nvsp = nvsp; + this.sppr = sppr; + this.sty = sty; + this.txb = txb; + this.attrs = attrs; + } + + public Sp(Sp shp) { + this.nvsp = shp.nvsp; + this.sppr = shp.sppr; + this.sty = shp.sty; + this.txb = shp.txb; + this.attrs = shp.attrs; + } + + + /** + * return the OOXML specific for this object + * + * @param xpp + * @param lastTag + * @return + */ + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + NvSpPr nvsp = null; + SpPr sppr = null; + Style sty = null; + TxBody txb = null; + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("sp")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("nvSpPr")) { // non-visual shape props + lastTag.push(tnm); + nvsp = NvSpPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("spPr")) { // shape properties + lastTag.push(tnm); + sppr = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); + //sppr.setNS("xdr"); + } else if (tnm.equals("style")) { // shape style + lastTag.push(tnm); + sty = (Style) Style.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("txBody")) { // shape text body + lastTag.push(tnm); + txb = (TxBody) TxBody.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("sp")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("sp.parseOOXML: " + e.toString()); + } + Sp shp = new Sp(nvsp, sppr, sty, txb, attrs); + return shp; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append(">"); + ooxml.append(nvsp.getOOXML()); + ooxml.append(sppr.getOOXML()); + if (sty != null) ooxml.append(sty.getOOXML()); + if (txb != null) ooxml.append(txb.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Sp(this); + } + + /** + * get Macro attribute + */ + public String getMacro() { + if (attrs.get("macro") != null) + return attrs.get("macro"); + return null; + } + + /** + * set Macro attribute + * + * @param macro + */ + public void setMacro(String macro) { + attrs.put("macro", macro); + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (sppr != null) + return sppr.getEmbed(); + return null; + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (sppr != null) + return sppr.getLink(); + return null; + } + + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + public void setEmbed(String embed) { + if (sppr != null) sppr.setEmbed(embed); + } + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + public void setLink(String link) { + if (sppr != null) sppr.setLink(link); + } + + /** + * return the name of this shape, if any + * + * @return + */ + public String getName() { + if (nvsp != null) + return nvsp.getName(); + return null; + } + + /** + * set the name of this shape, if any + */ + public void setName(String name) { + if (nvsp != null) + nvsp.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (nvsp != null) + return nvsp.getDescr(); + return null; + } + + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setDescr(String descr) { + if (nvsp != null) + nvsp.setDescr(descr); + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (nvsp != null) + nvsp.setId(id); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (nvsp != null) + return nvsp.getId(); + return -1; + } + } -/** - * - * This element specifies all non-visual properties for a shape. This element is a container for the non-visual - * identification properties, shape properties and application properties that are to be associated with a shape. - * This allows for additional information that does not affect the appearance of the shape to be stored - * - * parent: sp - * children: cNvPr REQ cNvSpPr REQ - * +/** + * This element specifies all non-visual properties for a shape. This element is a container for the non-visual + * identification properties, shape properties and application properties that are to be associated with a shape. + * This allows for additional information that does not affect the appearance of the shape to be stored + *

        + * parent: sp + * children: cNvPr REQ cNvSpPr REQ */ class NvSpPr implements OOXMLElement { - private static final long serialVersionUID = 9121235009516398367L; - private CNvPr cnv= null; - private CNvSpPr cnvsp= null; - - public NvSpPr(CNvPr cnv, CNvSpPr cnvsp) { - this.cnv= cnv; - this.cnvsp= cnvsp; - } - public NvSpPr(NvSpPr nvsp) { - this.cnv= nvsp.cnv; - this.cnvsp= nvsp.cnvsp; - } - - public static NvSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cnv= null; - CNvSpPr cnvsp= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cnv= (CNvPr) CNvPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cNvSpPr")) { - lastTag.push(tnm); - cnvsp= (CNvSpPr) CNvSpPr.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("nvSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("txBody.parseOOXML: " + e.toString()); - } - NvSpPr nvp= new NvSpPr(cnv, cnvsp); - return nvp; - } - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - ooxml.append(cnv.getOOXML()); - ooxml.append(cnvsp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new NvSpPr(this); - } - - /** - * get name attribute - * @return - */ - public String getName() { - if (cnv!=null) - return cnv.getName(); - return null; - } - - /** - * set name attribute - * @param name - */ - public void setName(String name) { - if (cnv!=null) - cnv.setName(name); - } - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (cnv!=null) - return cnv.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * @param name - */ - public void setDescr(String descr) { - if (cnv!=null) - cnv.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * @param id - */ - public void setId(int id) { - if (cnv!=null) - cnv.setId(id); - } - - /** - * return the cNvPr id for this element - * @return - */ - public int getId() { - if (cnv!=null) - return cnv.getId(); - return -1; - } + private static final long serialVersionUID = 9121235009516398367L; + private CNvPr cnv = null; + private CNvSpPr cnvsp = null; + + public NvSpPr(CNvPr cnv, CNvSpPr cnvsp) { + this.cnv = cnv; + this.cnvsp = cnvsp; + } + + public NvSpPr(NvSpPr nvsp) { + this.cnv = nvsp.cnv; + this.cnvsp = nvsp.cnvsp; + } + + public static NvSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { + CNvPr cnv = null; + CNvSpPr cnvsp = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cNvPr")) { + lastTag.push(tnm); + cnv = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("cNvSpPr")) { + lastTag.push(tnm); + cnvsp = CNvSpPr.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("nvSpPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("txBody.parseOOXML: " + e.toString()); + } + NvSpPr nvp = new NvSpPr(cnv, cnvsp); + return nvp; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + ooxml.append(cnv.getOOXML()); + ooxml.append(cnvsp.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new NvSpPr(this); + } + + /** + * get name attribute + * + * @return + */ + public String getName() { + if (cnv != null) + return cnv.getName(); + return null; + } + + /** + * set name attribute + * + * @param name + */ + public void setName(String name) { + if (cnv != null) + cnv.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (cnv != null) + return cnv.getDescr(); + return null; + } + + /** + * set cNvPr desc attribute + * + * @param name + */ + public void setDescr(String descr) { + if (cnv != null) + cnv.setDescr(descr); + } + + /** + * set the cNvPr id for this element + * + * @param id + */ + public void setId(int id) { + if (cnv != null) + cnv.setId(id); + } + + /** + * return the cNvPr id for this element + * + * @return + */ + public int getId() { + if (cnv != null) + return cnv.getId(); + return -1; + } } - + /** * cNvSpPr (Non-visual Drawing Properties for a shape - * - * This OOXML/DrawingML element specifies the non-visual drawing properties for a shape. + *

        + * This OOXML/DrawingML element specifies the non-visual drawing properties for a shape. * These properties are to be used by the generating application to determine how the shape should be dealt with -* -* attributes: txBox (optional boolean) -* parent: nvSpPr -* children: spLocks (shapeLocks) - * + *

        + * attributes: txBox (optional boolean) + * parent: nvSpPr + * children: spLocks (shapeLocks) */ class CNvSpPr implements OOXMLElement { - private static final long serialVersionUID = 7895953516797436713L; - private String txBox= null; - private SpLocks sp= null; - - public CNvSpPr(String t, SpLocks sp) { - this.txBox= t; - this.sp= sp; - } - public CNvSpPr(CNvSpPr c) { - this.txBox= c.txBox; - this.sp= c.sp; - } - - + private static final long serialVersionUID = 7895953516797436713L; + private String txBox = null; + private SpLocks sp = null; + + public CNvSpPr(String t, SpLocks sp) { + this.txBox = t; + this.sp = sp; + } + + public CNvSpPr(CNvSpPr c) { + this.txBox = c.txBox; + this.sp = c.sp; + } + + public static CNvSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String txBox= null; - SpLocks sp= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvSpPr")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - if (xpp.getAttributeName(i).equals("txBox")) - txBox= xpp.getAttributeValue(i); - } - } else if (tnm.equals("spLocks")) { - lastTag.push(tnm); - sp= (SpLocks) SpLocks.parseOOXML(xpp, lastTag); - - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("cNvSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cNvSpPr.parseOOXML: " + e.toString()); - } - CNvSpPr cnv= new CNvSpPr(txBox, sp); - return cnv; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (sp!=null) ooxml.append(sp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvSpPr(this); - } + String txBox = null; + SpLocks sp = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("cNvSpPr")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + if (xpp.getAttributeName(i).equals("txBox")) + txBox = xpp.getAttributeValue(i); + } + } else if (tnm.equals("spLocks")) { + lastTag.push(tnm); + sp = SpLocks.parseOOXML(xpp, lastTag); + + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("cNvSpPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("cNvSpPr.parseOOXML: " + e.toString()); + } + CNvSpPr cnv = new CNvSpPr(txBox, sp); + return cnv; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (sp != null) ooxml.append(sp.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new CNvSpPr(this); + } } /** * spLocks (Shape Locks) - * - * OOXML/DrawingML element specifies all locking properties for a shape. + *

        + * OOXML/DrawingML element specifies all locking properties for a shape. * These properties inform the generating application * about specific properties that have been previously locked and thus should not be changed. - * - * parents: cNvSpPr - * + *

        + * parents: cNvSpPr */ class SpLocks implements OOXMLElement { - private static final long serialVersionUID = -3805557220039550941L; - private HashMap attrs= null; - - public SpLocks(HashMap attrs) { - this.attrs= attrs; - } - public SpLocks(SpLocks sp) { - this.attrs= sp.attrs; - } - - + private static final long serialVersionUID = -3805557220039550941L; + private HashMap attrs = null; + + public SpLocks(HashMap attrs) { + this.attrs = attrs; + } + + public SpLocks(SpLocks sp) { + this.attrs = sp.attrs; + } + + public static SpLocks parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spLocks")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("spLocks")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("spLocks.parseOOXML: " + e.toString()); - } - SpLocks sp= new SpLocks(attrs); - return sp; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SpLocks(this); - } + HashMap attrs = new HashMap(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("spLocks")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("spLocks")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("spLocks.parseOOXML: " + e.toString()); + } + SpLocks sp = new SpLocks(attrs); + return sp; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new SpLocks(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/SpPr.java b/src/main/java/io/starter/formats/OOXML/SpPr.java index 68492ba..a04a3e2 100644 --- a/src/main/java/io/starter/formats/OOXML/SpPr.java +++ b/src/main/java/io/starter/formats/OOXML/SpPr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,297 +23,318 @@ package io.starter.formats.OOXML; -import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** - * spPr + * spPr * This OOXML/DrawingML element specifies the visual shape properties that can be applied to a shape. These properties include the * shape fill, outline, geometry, effects, and 3D orientation. - * + *

        * parents: cxnSp, pic, sp - * children: -blipFill (Picture Fill) §5.1.10.14 -custGeom (Custom Geometry) §5.1.11.8 -effectDag (Effect Container) §5.1.10.25 -effectLst (Effect Container) §5.1.10.26 -extLst (Extension List) §5.1.2.1.15 -gradFill (Gradient Fill) §5.1.10.33 -grpFill (Group Fill) §5.1.10.35 -ln (Outline) §5.1.2.1.24 -noFill (No Fill) §5.1.10.44 -pattFill (Pattern Fill) §5.1.10.47 -prstGeom (Preset geometry) §5.1.11.18 -scene3d (3D Scene Properties) §5.1.4.1.26 -solidFill (Solid Fill) §5.1.10.54 -sp3d (Apply 3D shape properties) §5.1.7.12 -xfrm (2D Transform) - - - * + * children: + * blipFill (Picture Fill) §5.1.10.14 + * custGeom (Custom Geometry) §5.1.11.8 + * effectDag (Effect Container) §5.1.10.25 + * effectLst (Effect Container) §5.1.10.26 + * extLst (Extension List) §5.1.2.1.15 + * gradFill (Gradient Fill) §5.1.10.33 + * grpFill (Group Fill) §5.1.10.35 + * ln (Outline) §5.1.2.1.24 + * noFill (No Fill) §5.1.10.44 + * pattFill (Pattern Fill) §5.1.10.47 + * prstGeom (Preset geometry) §5.1.11.18 + * scene3d (3D Scene Properties) §5.1.4.1.26 + * solidFill (Solid Fill) §5.1.10.54 + * sp3d (Apply 3D shape properties) §5.1.7.12 + * xfrm (2D Transform) */ -public class SpPr implements OOXMLElement{ - - private static final long serialVersionUID = 4542844402486023785L; - private Xfrm x= null; - private GeomGroup geom= null; - private FillGroup fill= null; - private Ln l= null; - private EffectPropsGroup effect= null; - // scene3d, sp3d - String bwMode= null; - // namespace - private String ns= null; - - public SpPr(String ns) { // no-param constructor, set up common defaults - x= new Xfrm(); - x.setNS("a"); - this.ns= ns; - geom= new GeomGroup(new PrstGeom(), null); - bwMode= "auto"; - } - public SpPr(Xfrm x, GeomGroup geom, FillGroup fill, Ln l, EffectPropsGroup effect, String bwMode, String ns) { - this.x= x; - this.geom= geom; - this.fill= fill; - this.l= l; - this.effect= effect; - this.bwMode= bwMode; - this.ns= ns; - } - public SpPr(SpPr clone) { - this.x= clone.x; - this.geom= clone.geom; - this.fill= clone.fill; - this.l= clone.l; - this.effect= clone.effect; - this.bwMode= clone.bwMode; - this.ns= clone.ns; - } - - /** - * create a default shape property with a solid fill and a line - * @param solidfill - * @param w - * @param lnClr - */ - public SpPr(String ns, String solidfill, int w, String lnClr) { - this.ns= ns; - if (solidfill!=null) - this.fill= new FillGroup(null, null, null, null, new SolidFill(solidfill)); - this.l= new Ln(w, lnClr); - } - - /** set the namespace for spPr element - * - * @param ns - */ - public void setNS(String ns) { this.ns= ns; } - - /** - * parse shape OOXML element spPr - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spPr object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - Xfrm x= null; - FillGroup fill= null; - Ln l= null; - EffectPropsGroup effect= null; - GeomGroup geom=null; - // scene3d, sp3d - String bwMode= null; - String ns= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spPr")) { - ns= xpp.getPrefix(); - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); - if (nm.equals("bwMode")) - bwMode= xpp.getAttributeValue(i); - } - } else if (tnm.equals("xfrm")) { - lastTag.push(tnm); - x= (Xfrm) Xfrm.parseOOXML(xpp, lastTag); - //x.setNS("a"); - } else if (tnm.equals("ln")) { - lastTag.push(tnm); - l= (Ln) Ln.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("prstGeom") || - tnm.equals("custGeom")) { // GEOMETRY GROUP - lastTag.push(tnm); - geom= (GeomGroup) GeomGroup.parseOOXML(xpp, lastTag); - } else if ( // FILL GROUP - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill= (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("extLst")) { - lastTag.push(tnm); - ExtLst.parseOOXML(xpp, lastTag); // ignore for now TODO: FINISH - } else if ( // EFFECT GROUP - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect= (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("spPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("spPr.parseOOXML: " + e.toString()); - } - SpPr sp= new SpPr(x, geom, fill, l, effect, bwMode, ns); - return sp; +public class SpPr implements OOXMLElement { + + private static final long serialVersionUID = 4542844402486023785L; + private Xfrm x = null; + private GeomGroup geom = null; + private FillGroup fill = null; + private Ln l = null; + private EffectPropsGroup effect = null; + // scene3d, sp3d + String bwMode = null; + // namespace + private String ns = null; + + public SpPr(String ns) { // no-param constructor, set up common defaults + x = new Xfrm(); + x.setNS("a"); + this.ns = ns; + geom = new GeomGroup(new PrstGeom(), null); + bwMode = "auto"; + } + + public SpPr(Xfrm x, GeomGroup geom, FillGroup fill, Ln l, EffectPropsGroup effect, String bwMode, String ns) { + this.x = x; + this.geom = geom; + this.fill = fill; + this.l = l; + this.effect = effect; + this.bwMode = bwMode; + this.ns = ns; + } + + public SpPr(SpPr clone) { + this.x = clone.x; + this.geom = clone.geom; + this.fill = clone.fill; + this.l = clone.l; + this.effect = clone.effect; + this.bwMode = clone.bwMode; + this.ns = clone.ns; + } + + /** + * create a default shape property with a solid fill and a line + * + * @param solidfill + * @param w + * @param lnClr + */ + public SpPr(String ns, String solidfill, int w, String lnClr) { + this.ns = ns; + if (solidfill != null) + this.fill = new FillGroup(null, null, null, null, new SolidFill(solidfill)); + this.l = new Ln(w, lnClr); + } + + /** + * set the namespace for spPr element + * + * @param ns + */ + public void setNS(String ns) { + this.ns = ns; + } + + /** + * parse shape OOXML element spPr + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object + */ + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + Xfrm x = null; + FillGroup fill = null; + Ln l = null; + EffectPropsGroup effect = null; + GeomGroup geom = null; + // scene3d, sp3d + String bwMode = null; + String ns = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("spPr")) { + ns = xpp.getPrefix(); + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("bwMode")) + bwMode = xpp.getAttributeValue(i); + } + } else if (tnm.equals("xfrm")) { + lastTag.push(tnm); + x = (Xfrm) Xfrm.parseOOXML(xpp, lastTag); + //x.setNS("a"); + } else if (tnm.equals("ln")) { + lastTag.push(tnm); + l = (Ln) Ln.parseOOXML(xpp, lastTag, bk); + } else if ( + tnm.equals("prstGeom") || + tnm.equals("custGeom")) { // GEOMETRY GROUP + lastTag.push(tnm); + geom = (GeomGroup) GeomGroup.parseOOXML(xpp, lastTag); + } else if ( // FILL GROUP + tnm.equals("solidFill") || + tnm.equals("noFill") || + tnm.equals("gradFill") || + tnm.equals("grpFill") || + tnm.equals("pattFill") || + tnm.equals("blipFill")) { + lastTag.push(tnm); + fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("extLst")) { + lastTag.push(tnm); + ExtLst.parseOOXML(xpp, lastTag); // ignore for now TODO: FINISH + } else if ( // EFFECT GROUP + tnm.equals("effectLst") || + tnm.equals("effectDag")) { + lastTag.push(tnm); + effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("spPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("spPr.parseOOXML: " + e.toString()); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append("<" + this.ns + ":spPr"); - if (bwMode!=null) ooxml.append(" bwMode=\"" + bwMode + "\">"); - else ooxml.append(">"); - if (x!=null) ooxml.append(x.getOOXML()); // must pass namespace to xfrm - if (geom!=null) ooxml.append(geom.getOOXML()); // geometry choice - if (fill!=null) - ooxml.append(fill.getOOXML()); // fill choice - if (l!=null) ooxml.append(l.getOOXML()); // ln element - if (effect!=null) ooxml.append(effect.getOOXML()); // effect properties choice - // scene3d, sp3d - ooxml.append(""); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new SpPr(this); - } - /** - * return the id for the embedded picture (i.e. resides within the file) - * @return - */ - public String getEmbed() { - if (fill!=null) - return fill.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * @return - */ - public String getLink() { - if (fill!=null) - return fill.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * @param embed - */ - public void setEmbed(String embed) { - if (fill!=null) fill.setEmbed(embed); - } + SpPr sp = new SpPr(x, geom, fill, l, effect, bwMode, ns); + return sp; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append("<" + this.ns + ":spPr"); + if (bwMode != null) ooxml.append(" bwMode=\"" + bwMode + "\">"); + else ooxml.append(">"); + if (x != null) ooxml.append(x.getOOXML()); // must pass namespace to xfrm + if (geom != null) ooxml.append(geom.getOOXML()); // geometry choice + if (fill != null) + ooxml.append(fill.getOOXML()); // fill choice + if (l != null) ooxml.append(l.getOOXML()); // ln element + if (effect != null) ooxml.append(effect.getOOXML()); // effect properties choice + // scene3d, sp3d + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new SpPr(this); + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (fill != null) + return fill.getEmbed(); + return null; + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (fill != null) + return fill.getLink(); + return null; + } + + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + public void setEmbed(String embed) { + if (fill != null) fill.setEmbed(embed); + } + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + public void setLink(String link) { + if (fill != null) fill.setLink(link); + } + + /** + * returns the ln element of this shape property set, if any + * + * @return + */ + public Ln getLn() { + return l; + } + + /** + * add a line for this shape property + * + * @param w line width + * @param clr html color string + */ + public void setLine(int w, String clr) { + l = new Ln(); + l.setWidth(w); + l.setColor(clr); + } + + /** + * returns the width of the line of this shape proeprty, + * or -1 if no line is present + * + * @return + */ + public int getLineWidth() { + if (l != null) return l.getWidth(); + return -1; + } + + /** + * returns the color of the line of this shape property, or -1 if no line is present + * + * @return + */ + public int getLineColor() { + if (l != null) return l.getColor(); + return -1; + } + + /** + * returns the line style of the line of this shape property, or -1 if no line is present + * + * @return + */ + public int getLineStyle() { + if (l != null) + return l.getLineStyle(); + return -1; + } + + /** + * return the fill color + * + * @return + */ + public int getColor() { + if (fill != null) + return fill.getColor(); + return 1; + } + + /** + * returns the fill of this shape property set, if any + * + * @return + */ + public FillGroup getFill() { + return fill; + } + + /** + * remove the line, if any, for this shape property + */ + public void removeLine() { + l = null; + } - /** - * set the link attribute for this blip (the id for the linked picture) - * @param embed - */ - public void setLink(String link) { - if (fill!=null) fill.setLink(link); - } - - /** - * returns the ln element of this shape property set, if any - * @return - */ - public Ln getLn() { - return l; - } - /** - * add a line for this shape property - * @param w line width - * @param clr html color string - */ - public void setLine(int w, String clr) { - l= new Ln(); - l.setWidth(w); - l.setColor(clr); - } - - /** - * returns the width of the line of this shape proeprty, - * or -1 if no line is present - * @return - */ - public int getLineWidth() { - if (l!=null) return l.getWidth(); - return -1; - } - - /** - * returns the color of the line of this shape property, or -1 if no line is present - * @return - */ - public int getLineColor() { - if (l!=null) return l.getColor(); - return -1; - } - - /** - * returns the line style of the line of this shape property, or -1 if no line is present - * @return - */ - public int getLineStyle() { - if (l!=null) - return l.getLineStyle(); - return -1; - } - - /** - * return the fill color - * @return - */ - public int getColor() { - if (fill!=null) - return fill.getColor(); - return 1; - } - - /** - * returns the fill of this shape property set, if any - * @return - */ - public FillGroup getFill() { - return fill; - } - /** - * remove the line, if any, for this shape property - */ - public void removeLine() { - l= null; - } - - /** - * return true if this shape properties contains a line - */ - public boolean hasLine() { return (l!=null); } + /** + * return true if this shape properties contains a line + */ + public boolean hasLine() { + return (l != null); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Ss_rPr.java b/src/main/java/io/starter/formats/OOXML/Ss_rPr.java index 0cf199e..1639f6f 100644 --- a/src/main/java/io/starter/formats/OOXML/Ss_rPr.java +++ b/src/main/java/io/starter/formats/OOXML/Ss_rPr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,294 +22,294 @@ */ package io.starter.formats.OOXML; -import java.util.HashMap; -import java.util.Iterator; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.DocumentHandle; import io.starter.OpenXLS.FormatHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.Font; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Iterator; /** * rPr (Run Properties for Shared Strings (see SharedStrings.xml)) - * + *

        * This element represents a set of properties to apply to the contents of this rich text run. * This element corresponds to Unicode String formatting runs where a specific font is applied to a sub-section of a string - * + *

        * parent: r (rich text run) * children: many - - * */ -public class Ss_rPr implements OOXMLElement{ - - private static final long serialVersionUID = 8940630588129002652L; - private HashMap attrs= new HashMap(); - private Color color= null; - - public Ss_rPr() { - - } - - public Ss_rPr(HashMap attrs, Color c) { - this.attrs= attrs; - this.color= c; - } - - public Ss_rPr(Ss_rPr r) { - this.attrs= r.attrs; - this.color= r.color; - } - +public class Ss_rPr implements OOXMLElement { + + private static final long serialVersionUID = 8940630588129002652L; + private HashMap attrs = new HashMap(); + private Color color = null; + + public Ss_rPr() { + + } + + public Ss_rPr(HashMap attrs, Color c) { + this.attrs = attrs; + this.color = c; + } + + public Ss_rPr(Ss_rPr r) { + this.attrs = r.attrs; + this.color = r.color; + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - HashMap attrs= new HashMap(); - Color c= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("rFont")) { - attrs.put("rFont", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("charset")) { - attrs.put("charset", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("family")) { - attrs.put("family", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("b")) { - attrs.put("b", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("i")) { - attrs.put("i", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("strike")) { - attrs.put("strike", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("outline")) { - attrs.put("outline", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("shadow")) { - attrs.put("shadow", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("condense")) { - attrs.put("condense", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("extend")) { - attrs.put("extend", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("sz")) { - attrs.put("sz", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("u")) { - attrs.put("u", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("vertAlign")) { - attrs.put("vertAlign", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("scheme")) { - attrs.put("scheme", ((xpp.getAttributeCount()>0)?xpp.getAttributeValue(0):"")); // val - } else if (tnm.equals("color")) { - c= (Color) Color.parseOOXML(xpp, FormatHandle.colorFONT, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("rPr")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("rPr.parseOOXML: " + e.toString()); - } - Ss_rPr r= new Ss_rPr(attrs, c); - return r; + HashMap attrs = new HashMap(); + Color c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("rFont")) { + attrs.put("rFont", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("charset")) { + attrs.put("charset", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("family")) { + attrs.put("family", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("b")) { + attrs.put("b", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("i")) { + attrs.put("i", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("strike")) { + attrs.put("strike", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("outline")) { + attrs.put("outline", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("shadow")) { + attrs.put("shadow", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("condense")) { + attrs.put("condense", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("extend")) { + attrs.put("extend", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("sz")) { + attrs.put("sz", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("u")) { + attrs.put("u", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("vertAlign")) { + attrs.put("vertAlign", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("scheme")) { + attrs.put("scheme", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val + } else if (tnm.equals("color")) { + c = (Color) Color.parseOOXML(xpp, FormatHandle.colorFONT, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("rPr")) { + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("rPr.parseOOXML: " + e.toString()); + } + Ss_rPr r = new Ss_rPr(attrs, c); + return r; } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - // attributes - Iterator i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= i.next(); - String val= attrs.get(key); - if (val.equals("")) // same as true for ... - ooxml.append("<" + key + "/>"); - else - ooxml.append("<" + key + " val=\"" + val + "\"/>"); - } - if (color!=null) ooxml.append(color.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - public OOXMLElement cloneElement() { - return new Ss_rPr(this); - } + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + // attributes + Iterator i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + if (val.equals("")) // same as true for ... + ooxml.append("<" + key + "/>"); + else + ooxml.append("<" + key + " val=\"" + val + "\"/>"); + } + if (color != null) ooxml.append(color.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } - /** - * retrieve one of the following key values, if already set - * b, charset, condense, extend, family, i, outline, rFont, scheme, shadow, strike, sz, u, vertAlign + public OOXMLElement cloneElement() { + return new Ss_rPr(this); + } - * @param key - * @return - */ - public String getAttr(String key) { - return attrs.get(key); - } - - /** - * set the value for one of the rPr children: - * b, charset, condense, extend, family, i, outline, rFont, scheme, shadow, strike, sz, u, vertAlign - * - * most are boolean string values "0" or "1" - * except for sz, rFont, family, scheme - * - * @param key - * @param val - */ - public void setAttr(String key, String val) { - attrs.put(key, val); - } + /** + * retrieve one of the following key values, if already set + * b, charset, condense, extend, family, i, outline, rFont, scheme, shadow, strike, sz, u, vertAlign + * + * @param key + * @return + */ + public String getAttr(String key) { + return attrs.get(key); + } - /** family: - 0 Not applicable. - 1 Roman - 2 Swiss - SpreadsheetML Reference Material - Styles - 2113 - Value Font Family - 3 Modern - 4 Script - 5 Decorative - */ - /** - * charset (Character Set) - * This element defines the font character set of this font. - * This field is used in font creation and selection if a font of the given facename is not available on the system. - * Although it is not required to have around when resolving font facename, the information can be stored for - * when needed to help resolve which font face to use of all available fonts on a system. - * - * int 0-255. - * - * The following are some of the possible the character sets: - INT - Value - Character Set - 0 ANSI_CHARSET - 1 DEFAULT_CHARSET - 2 SYMBOL_CHARSET - 77 MAC_CHARSET - 128 SHIFTJIS_CHARSET - 129 HANGEUL_CHARSET - 129 HANGUL_CHARSET - 130 JOHAB_CHARSET - 134 GB2312_CHARSET - 136 CHINESEBIG5_CHARSET - 161 GREEK_CHARSET - 162 TURKISH_CHARSET - 163 VIETNAMESE_CHARSET - 177 HEBREW_CHARSET - 178 ARABIC_CHARSET - 186 BALTIC_CHARSET - 204 RUSSIAN_CHARSET - 222 THAI_CHARSET - 238 EASTEUROPE_CHARSET - 255 OEM_CHARSET - */ - /** - * condense (Condense) - Macintosh compatibility setting. Represents special word/character rendering on Macintosh, when this flag is - set. The effect is to condense the text (squeeze it together). SpreadsheetML applications are not required to - render according to this flag. - */ - /** - * extend (Extend) - * This element specifies a compatibility setting used for previous spreadsheet applications, resulting in special - * word/character rendering on those legacy applications, when this flag is set. The effect extends or stretches out - * the text. SpreadsheetML applications are not required to render according to this flag. - */ - /** - * shadow (Shadow) - * Macintosh compatibility setting. Represents special word/character rendering on Macintosh, when this flag is - * set. The effect is to render a shadow behind, beneath and to the right of the text. SpreadsheetML applications - * are not required to render according to this flag. - */ - /** - * outline (Outline) - * This element displays only the inner and outer borders of each character. This is very similar to Bold in behavior - */ - /** - * vertAlign (Vertical Alignment) - * This element adjusts the vertical position of the text relative to the text's default appearance for this run. It is - * used to get 'superscript' or 'subscript' texts, and shall reduce the font size (if a smaller size is available) - * accordingly. - * - * val= An enumeration representing the vertical-alignment setting. - * baseline, subscript, superscript - Setting this to either subscript or superscript shall make the font size smaller if a - smaller font size is available. - */ - - /** - * given an rPr OOXML text run properties, create an OpenXLS font - * @param bk - * @return - */ - public Font generateFont(DocumentHandle bk) { - // not using attributes: charset, family, condense, extend, shadow, scheme, outline==bold - Font f= new Font("Arial",400,200); - Object o; - - o= this.getAttr("rFont"); - f.setFontName((String) o); - o= this.getAttr("sz"); - if (o!=null) f.setFontHeight(Font.PointsToFontHeight(Double.parseDouble((String)o))); - - // boolean attributes - o= this.getAttr("b"); - if (o!=null) f.setBold(true); - o= this.getAttr("i"); - if (o!=null) f.setItalic(true); - o= this.getAttr("u"); - if (o!=null) f.setUnderlined(true); - o= this.getAttr("strike"); - if (o!=null) f.setStricken(true); - o= this.getAttr("outline"); - if (o!=null) f.setBold(true); - o= this.getAttr("vertAlign"); - if (o!=null) { - String s= (String) o; - if (s.equals("baseline")) - f.setScript(0); - else if (s.equals("superscript")) - f.setScript(1); - else if (s.equals("subscript")) - f.setScript(2); - } - f.setOOXMLColor(color); - return f; - } - - /** - * create a new OOXML ss_rPr shared string table text run properties object using attributes from Font f - * @param f - * @return - */ - public static Ss_rPr createFromFont(Font f) { - // not using attributes: charset, family, condense, extend, shadow, scheme, outline==bold - Ss_rPr rp= new Ss_rPr(); - rp.setAttr("rFont", f.getFontName()); - rp.setAttr("sz", new Double(f.getFontHeightInPoints()).toString()); + /** + * set the value for one of the rPr children: + * b, charset, condense, extend, family, i, outline, rFont, scheme, shadow, strike, sz, u, vertAlign + *

        + * most are boolean string values "0" or "1" + * except for sz, rFont, family, scheme + * + * @param key + * @param val + */ + public void setAttr(String key, String val) { + attrs.put(key, val); + } + + /** family: + 0 Not applicable. + 1 Roman + 2 Swiss + SpreadsheetML Reference Material - Styles + 2113 + Value Font Family + 3 Modern + 4 Script + 5 Decorative + */ + /** + * charset (Character Set) + * This element defines the font character set of this font. + * This field is used in font creation and selection if a font of the given facename is not available on the system. + * Although it is not required to have around when resolving font facename, the information can be stored for + * when needed to help resolve which font face to use of all available fonts on a system. + * + * int 0-255. + * + * The following are some of the possible the character sets: + INT + Value + Character Set + 0 ANSI_CHARSET + 1 DEFAULT_CHARSET + 2 SYMBOL_CHARSET + 77 MAC_CHARSET + 128 SHIFTJIS_CHARSET + 129 HANGEUL_CHARSET + 129 HANGUL_CHARSET + 130 JOHAB_CHARSET + 134 GB2312_CHARSET + 136 CHINESEBIG5_CHARSET + 161 GREEK_CHARSET + 162 TURKISH_CHARSET + 163 VIETNAMESE_CHARSET + 177 HEBREW_CHARSET + 178 ARABIC_CHARSET + 186 BALTIC_CHARSET + 204 RUSSIAN_CHARSET + 222 THAI_CHARSET + 238 EASTEUROPE_CHARSET + 255 OEM_CHARSET + */ + /** + * condense (Condense) + Macintosh compatibility setting. Represents special word/character rendering on Macintosh, when this flag is + set. The effect is to condense the text (squeeze it together). SpreadsheetML applications are not required to + render according to this flag. + */ + /** + * extend (Extend) + * This element specifies a compatibility setting used for previous spreadsheet applications, resulting in special + * word/character rendering on those legacy applications, when this flag is set. The effect extends or stretches out + * the text. SpreadsheetML applications are not required to render according to this flag. + */ + /** + * shadow (Shadow) + * Macintosh compatibility setting. Represents special word/character rendering on Macintosh, when this flag is + * set. The effect is to render a shadow behind, beneath and to the right of the text. SpreadsheetML applications + * are not required to render according to this flag. + */ + /** + * outline (Outline) + * This element displays only the inner and outer borders of each character. This is very similar to Bold in behavior + */ + /** + * vertAlign (Vertical Alignment) + * This element adjusts the vertical position of the text relative to the text's default appearance for this run. It is + * used to get 'superscript' or 'subscript' texts, and shall reduce the font size (if a smaller size is available) + * accordingly. + * + * val= An enumeration representing the vertical-alignment setting. + * baseline, subscript, superscript + Setting this to either subscript or superscript shall make the font size smaller if a + smaller font size is available. + */ + + /** + * given an rPr OOXML text run properties, create an OpenXLS font + * + * @param bk + * @return + */ + public Font generateFont(DocumentHandle bk) { + // not using attributes: charset, family, condense, extend, shadow, scheme, outline==bold + Font f = new Font("Arial", 400, 200); + Object o; + + o = this.getAttr("rFont"); + f.setFontName((String) o); + o = this.getAttr("sz"); + if (o != null) f.setFontHeight(Font.PointsToFontHeight(Double.parseDouble((String) o))); - // boolean attributes - if (f.getBold()) - rp.setAttr("b", ""); - if (f.getItalic()) - rp.setAttr("i", ""); - if (f.getUnderlined()) - rp.setAttr("u", ""); - if (f.getStricken()) - rp.setAttr("strike", ""); - int s= f.getScript(); - if (s==1) - rp.setAttr("vertAlign", "superscript"); - else if (s==2) - rp.setAttr("vertAlign", "subscript"); - rp.color= f.getOOXMLColor(); - return rp; - } + // boolean attributes + o = this.getAttr("b"); + if (o != null) f.setBold(true); + o = this.getAttr("i"); + if (o != null) f.setItalic(true); + o = this.getAttr("u"); + if (o != null) f.setUnderlined(true); + o = this.getAttr("strike"); + if (o != null) f.setStricken(true); + o = this.getAttr("outline"); + if (o != null) f.setBold(true); + o = this.getAttr("vertAlign"); + if (o != null) { + String s = (String) o; + if (s.equals("baseline")) + f.setScript(0); + else if (s.equals("superscript")) + f.setScript(1); + else if (s.equals("subscript")) + f.setScript(2); + } + f.setOOXMLColor(color); + return f; + } + + /** + * create a new OOXML ss_rPr shared string table text run properties object using attributes from Font f + * + * @param f + * @return + */ + public static Ss_rPr createFromFont(Font f) { + // not using attributes: charset, family, condense, extend, shadow, scheme, outline==bold + Ss_rPr rp = new Ss_rPr(); + rp.setAttr("rFont", f.getFontName()); + rp.setAttr("sz", new Double(f.getFontHeightInPoints()).toString()); + + // boolean attributes + if (f.getBold()) + rp.setAttr("b", ""); + if (f.getItalic()) + rp.setAttr("i", ""); + if (f.getUnderlined()) + rp.setAttr("u", ""); + if (f.getStricken()) + rp.setAttr("strike", ""); + int s = f.getScript(); + if (s == 1) + rp.setAttr("vertAlign", "superscript"); + else if (s == 2) + rp.setAttr("vertAlign", "subscript"); + rp.color = f.getOOXMLColor(); + return rp; + } } diff --git a/src/main/java/io/starter/formats/OOXML/StrRef.java b/src/main/java/io/starter/formats/OOXML/StrRef.java index 82f7909..c65a0ee 100644 --- a/src/main/java/io/starter/formats/OOXML/StrRef.java +++ b/src/main/java/io/starter/formats/OOXML/StrRef.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,171 +22,170 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; - +import io.starter.toolkit.Logger; import org.xmlpull.v1.XmlPullParser; -import io.starter.toolkit.Logger; +import java.util.Stack; /** * OOXML element strRef, string reference, child of tx (chart text) element or cat (category) element - * */ -public class StrRef implements OOXMLElement{ - - private static final long serialVersionUID = -5992001371281543027L; - private String stringRef= null; - private StrCache strCache= null; - - public StrRef(String f, StrCache s) { - this.stringRef= f; - this.strCache= s; - } - +public class StrRef implements OOXMLElement { + + private static final long serialVersionUID = -5992001371281543027L; + private String stringRef = null; + private StrCache strCache = null; + + public StrRef(String f, StrCache s) { + this.stringRef = f; + this.strCache = s; + } + /** - * parse strRef OOXML element - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spRef object + * parse strRef OOXML element + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spRef object */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - String f= null; - StrCache s= null; - - /** - * contains (in Sequence) - * f - * strRef - */ - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("f")) { - f= io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); - } else if (tnm.equals("strCache")) { - lastTag.push(tnm); - s= (StrCache) StrCache.parseOOXML(xpp, lastTag); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("strRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("title.parseOOXML: " + e.toString()); - } - StrRef sr= new StrRef(f, s); - return sr; + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { + String f = null; + StrCache s = null; + + /** + * contains (in Sequence) + * f + * strRef + */ + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("f")) { + f = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); + } else if (tnm.equals("strCache")) { + lastTag.push(tnm); + s = StrCache.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("strRef")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("title.parseOOXML: " + e.toString()); + } + StrRef sr = new StrRef(f, s); + return sr; } - + /** * generate ooxml to define a strRef, part of tx element or cat element - * + * * @return */ /** * strRef contains f + strRef elements */ public String getOOXML() { - StringBuffer tooxml= new StringBuffer(); - tooxml.append(""); - if (this.stringRef!=null) tooxml.append("" + this.stringRef + ""); - if (this.strCache!=null) tooxml.append(strCache.getOOXML()); - tooxml.append(""); - return tooxml.toString(); + StringBuffer tooxml = new StringBuffer(); + tooxml.append(""); + if (this.stringRef != null) tooxml.append("" + this.stringRef + ""); + if (this.strCache != null) tooxml.append(strCache.getOOXML()); + tooxml.append(""); + return tooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new StrRef(this.stringRef, this.strCache); } - public OOXMLElement cloneElement() { - return new StrRef(this.stringRef, this.strCache); - } - } /** - * define OOXML strCache element - * + * define OOXML strCache element */ -class StrCache implements OOXMLElement{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4914374179641060956L; - private int ptCount= -1, idx= -1; - private String pt= null; - - public StrCache(int ptCount, int idx, String pt) { - this.ptCount= ptCount; - this.idx= idx; - this.pt= pt; - } - +class StrCache implements OOXMLElement { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4914374179641060956L; + private int ptCount = -1, idx = -1; + private String pt = null; + + public StrCache(int ptCount, int idx, String pt) { + this.ptCount = ptCount; + this.idx = idx; + this.pt = pt; + } + /** * parse title OOXML element title - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spCache object + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spCache object */ - public static StrCache parseOOXML(XmlPullParser xpp, Stack lastTag) { - int ptCount= -1, idx= -1; - String pt= null; - - /** - * contains (in Sequence) - * ptCount - * pt - */ - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ptCount")) { - ptCount= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("pt")) { - idx= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - pt= io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("strCache")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("strCache.parseOOXML: " + e.toString()); - } - StrCache sc= new StrCache(ptCount, idx, pt); - return sc; + public static StrCache parseOOXML(XmlPullParser xpp, Stack lastTag) { + int ptCount = -1, idx = -1; + String pt = null; + + /** + * contains (in Sequence) + * ptCount + * pt + */ + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("ptCount")) { + ptCount = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + } else if (tnm.equals("pt")) { + idx = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + pt = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("strCache")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("strCache.parseOOXML: " + e.toString()); + } + StrCache sc = new StrCache(ptCount, idx, pt); + return sc; } - + /** - * generate ooxml to define a strCache element, part of strRef element - * + * generate ooxml to define a strCache element, part of strRef element + * * @return */ public String getOOXML() { - StringBuffer tooxml= new StringBuffer(); - tooxml.append(""); - tooxml.append(""); - tooxml.append(""); - tooxml.append("" + this.pt + ""); - tooxml.append(""); - tooxml.append(""); - return tooxml.toString(); + StringBuffer tooxml = new StringBuffer(); + tooxml.append(""); + tooxml.append(""); + tooxml.append(""); + tooxml.append("" + this.pt + ""); + tooxml.append(""); + tooxml.append(""); + return tooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new StrCache(this.ptCount, this.idx, this.pt); } - public OOXMLElement cloneElement() { - return new StrCache(this.ptCount, this.idx, this.pt); - } - } diff --git a/src/main/java/io/starter/formats/OOXML/Style.java b/src/main/java/io/starter/formats/OOXML/Style.java index bb44078..1333bba 100644 --- a/src/main/java/io/starter/formats/OOXML/Style.java +++ b/src/main/java/io/starter/formats/OOXML/Style.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,103 +22,100 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * style - * + *

        * This element specifies the style information for a shape. This is used to define a shape's appearance in terms of -* the preset styles defined by the style matrix for the theme. - * + * the preset styles defined by the style matrix for the theme. + *

        * parent: sp, pic, cnxSp - * children (required and in sequence): lnRef, fillRef, effectRef, fontRef - - * + * children (required and in sequence): lnRef, fillRef, effectRef, fontRef */ -public class Style implements OOXMLElement{ - - private static final long serialVersionUID = -583023685473342509L; - private EffectRef effectRef; - private FontRef fontRef; - private FillRef fillRef; - private lnRef lRef; - - public Style(lnRef lr, FillRef flr, EffectRef er, FontRef fr) { - this.lRef= lr; - this.fillRef= flr; - this.effectRef= er; - this.fontRef= fr; - } - - public Style(Style s) { - this.lRef= s.lRef; - this.fillRef= s.fillRef; - this.effectRef= s.effectRef; - this.fontRef= s.fontRef; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - EffectRef er= null; - FontRef fr= null; - FillRef flr= null; - lnRef lr= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("effectRef")) { - lastTag.push(tnm); - er= (EffectRef) EffectRef.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("fontRef")) { - lastTag.push(tnm); - fr= (FontRef) FontRef.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("fillRef")) { - lastTag.push(tnm); - flr= (FillRef) FillRef.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("lnRef")) { - lastTag.push(tnm); - lr= (lnRef) lnRef.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("style")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("style.parseOOXML: " + e.toString()); - } - Style s= new Style(lr, flr, er, fr); - return s; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - ooxml.append(lRef.getOOXML()); - ooxml.append(fillRef.getOOXML()); - ooxml.append(effectRef.getOOXML()); - ooxml.append(fontRef.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public String toString(){ - return getOOXML(); - } - - public OOXMLElement cloneElement() { - return new Style(this); - } +public class Style implements OOXMLElement { + + private static final long serialVersionUID = -583023685473342509L; + private EffectRef effectRef; + private FontRef fontRef; + private FillRef fillRef; + private lnRef lRef; + + public Style(lnRef lr, FillRef flr, EffectRef er, FontRef fr) { + this.lRef = lr; + this.fillRef = flr; + this.effectRef = er; + this.fontRef = fr; + } + + public Style(Style s) { + this.lRef = s.lRef; + this.fillRef = s.fillRef; + this.effectRef = s.effectRef; + this.fontRef = s.fontRef; + } + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + EffectRef er = null; + FontRef fr = null; + FillRef flr = null; + lnRef lr = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("effectRef")) { + lastTag.push(tnm); + er = EffectRef.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("fontRef")) { + lastTag.push(tnm); + fr = FontRef.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("fillRef")) { + lastTag.push(tnm); + flr = FillRef.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("lnRef")) { + lastTag.push(tnm); + lr = lnRef.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("style")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("style.parseOOXML: " + e.toString()); + } + Style s = new Style(lr, flr, er, fr); + return s; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + ooxml.append(lRef.getOOXML()); + ooxml.append(fillRef.getOOXML()); + ooxml.append(effectRef.getOOXML()); + ooxml.append(fontRef.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public String toString() { + return getOOXML(); + } + + public OOXMLElement cloneElement() { + return new Style(this); + } } @@ -126,78 +123,76 @@ public OOXMLElement cloneElement() { * effectRef (Effect Reference) * This element defines a reference to an effect style within the style matrix. The idx attribute refers the index of * an effect style within the effectStyleLst element. - * + *

        * parent: many * children: COLORCHOICE - * */ -class EffectRef implements OOXMLElement -{ - private static final long serialVersionUID = -7572271663955122478L; - private int idx; - private ColorChoice colorChoice= null; - - protected EffectRef (int idx, ColorChoice c) { - this.idx= idx; - this.colorChoice= c; - } - - protected EffectRef(EffectRef er) { - this.colorChoice= er.colorChoice; - this.idx= er.idx; - } - - protected static EffectRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - int idx=-1; - ColorChoice c= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("effectRef")) { - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); - if (nm.equals("idx")) { - idx= Integer.valueOf(xpp.getAttributeValue(i)).intValue(); - } - } - } else if (tnm.equals("schemeClr") || - tnm.equals("hslClr") || - tnm.equals("prstClr") || - tnm.equals("scrgbClr") || - tnm.equals("srgbClr") || - tnm.equals("sysClr")) { - lastTag.push(tnm); - c= (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("effectRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("effectRef.parseOOXML: " + e.toString()); - } - EffectRef er= new EffectRef(idx, c); - return er; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (colorChoice!=null) ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new EffectRef(this); - } +class EffectRef implements OOXMLElement { + private static final long serialVersionUID = -7572271663955122478L; + private int idx; + private ColorChoice colorChoice = null; + + protected EffectRef(int idx, ColorChoice c) { + this.idx = idx; + this.colorChoice = c; + } + + protected EffectRef(EffectRef er) { + this.colorChoice = er.colorChoice; + this.idx = er.idx; + } + + protected static EffectRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + int idx = -1; + ColorChoice c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("effectRef")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("idx")) { + idx = Integer.valueOf(xpp.getAttributeValue(i)).intValue(); + } + } + } else if (tnm.equals("schemeClr") || + tnm.equals("hslClr") || + tnm.equals("prstClr") || + tnm.equals("scrgbClr") || + tnm.equals("srgbClr") || + tnm.equals("sysClr")) { + lastTag.push(tnm); + c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("effectRef")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("effectRef.parseOOXML: " + e.toString()); + } + EffectRef er = new EffectRef(idx, c); + return er; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (colorChoice != null) ooxml.append(colorChoice.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new EffectRef(this); + } } /** @@ -208,236 +203,231 @@ public OOXMLElement cloneElement() { * element, and values 1001 and above refer to the index of a background fill style within the bgFillStyleLst * element. The value 1001 corresponds to the first background fill style, 1002 to the second background fill style, * and so on. - * + *

        * parent: many * children: COLORCHOICE - * */ -class FillRef implements OOXMLElement -{ - private static final long serialVersionUID = 7691131082710785068L; - private int idx; - private ColorChoice colorChoice= null; - - protected FillRef(int idx, ColorChoice c) { - this.idx= idx; - this.colorChoice= c; - } - - protected FillRef(FillRef fr) { - this.colorChoice= fr.colorChoice; - this.idx= fr.idx; - } - - protected static FillRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - int idx=-1; - ColorChoice c= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fillRef")) { - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); - if (nm.equals("idx")) { - idx= Integer.valueOf(xpp.getAttributeValue(i)).intValue(); - } - } - } else if (tnm.equals("schemeClr") || - tnm.equals("hslClr") || - tnm.equals("prstClr") || - tnm.equals("scrgbClr") || - tnm.equals("srgbClr") || - tnm.equals("sysClr")) { - lastTag.push(tnm); - c= (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk).cloneElement(); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("fillRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fillRef.parseOOXML: " + e.toString()); - } - FillRef fr= new FillRef(idx, c); - return fr; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (colorChoice!=null) ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FillRef(this); - } +class FillRef implements OOXMLElement { + private static final long serialVersionUID = 7691131082710785068L; + private int idx; + private ColorChoice colorChoice = null; + + protected FillRef(int idx, ColorChoice c) { + this.idx = idx; + this.colorChoice = c; + } + + protected FillRef(FillRef fr) { + this.colorChoice = fr.colorChoice; + this.idx = fr.idx; + } + + protected static FillRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + int idx = -1; + ColorChoice c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("fillRef")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("idx")) { + idx = Integer.valueOf(xpp.getAttributeValue(i)).intValue(); + } + } + } else if (tnm.equals("schemeClr") || + tnm.equals("hslClr") || + tnm.equals("prstClr") || + tnm.equals("scrgbClr") || + tnm.equals("srgbClr") || + tnm.equals("sysClr")) { + lastTag.push(tnm); + c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk).cloneElement(); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("fillRef")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("fillRef.parseOOXML: " + e.toString()); + } + FillRef fr = new FillRef(idx, c); + return fr; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (colorChoice != null) ooxml.append(colorChoice.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new FillRef(this); + } } /** * fontRef (Font Reference) * This element represents a reference to a themed font. When used it specifies which themed font to use along * with a choice of color. - * + *

        * parent: many * children: COLORCHOICE - * */ -class FontRef implements OOXMLElement -{ - - private static final long serialVersionUID = 2907761758443581273L; - private String idx = null; - private ColorChoice colorChoice = null; - - protected FontRef(String idx, ColorChoice c) { - this.idx= idx; - this.colorChoice= c; - } - - protected FontRef(FontRef fr) { - this.colorChoice= fr.colorChoice; - this.idx= fr.idx; - } - - protected static FontRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String idx=null; - ColorChoice c= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fontRef")) { - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); - if (nm.equals("idx")) { - idx= xpp.getAttributeValue(i); - } - } - } else if (tnm.equals("schemeClr") || - tnm.equals("hslClr") || - tnm.equals("prstClr") || - tnm.equals("scrgbClr") || - tnm.equals("srgbClr") || - tnm.equals("sysClr")) { - lastTag.push(tnm); - c= (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("fontRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fontRef.parseOOXML: " + e.toString()); - } - FontRef fr= new FontRef(idx, c); - return fr; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (colorChoice!=null) ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FontRef(this); - } +class FontRef implements OOXMLElement { + + private static final long serialVersionUID = 2907761758443581273L; + private String idx = null; + private ColorChoice colorChoice = null; + + protected FontRef(String idx, ColorChoice c) { + this.idx = idx; + this.colorChoice = c; + } + + protected FontRef(FontRef fr) { + this.colorChoice = fr.colorChoice; + this.idx = fr.idx; + } + + protected static FontRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + String idx = null; + ColorChoice c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("fontRef")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("idx")) { + idx = xpp.getAttributeValue(i); + } + } + } else if (tnm.equals("schemeClr") || + tnm.equals("hslClr") || + tnm.equals("prstClr") || + tnm.equals("scrgbClr") || + tnm.equals("srgbClr") || + tnm.equals("sysClr")) { + lastTag.push(tnm); + c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("fontRef")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("fontRef.parseOOXML: " + e.toString()); + } + FontRef fr = new FontRef(idx, c); + return fr; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (colorChoice != null) ooxml.append(colorChoice.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new FontRef(this); + } } /** * lnRef (Line Reference) * This element defines a reference to a line style within the style matrix. The idx attribute refers the index of a * line style within the fillStyleLst element - * + *

        * parent: many * children: COLORCHOICE - * */ -class lnRef implements OOXMLElement{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4349076266006929729L; - private int idx; - private ColorChoice colorChoice = null; - - protected lnRef(int idx, ColorChoice c) { - this.idx= idx; - this.colorChoice= c; - } - - protected lnRef(lnRef lr) { - this.colorChoice= lr.colorChoice; - this.idx= lr.idx; - } - - protected static lnRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - int idx=-1; - ColorChoice c= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("lnRef")) { - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String nm= xpp.getAttributeName(i); - if (nm.equals("idx")) { - idx= Integer.valueOf(xpp.getAttributeValue(i)).intValue(); - } - } - } else if (tnm.equals("schemeClr") || - tnm.equals("hslClr") || - tnm.equals("prstClr") || - tnm.equals("scrgbClr") || - tnm.equals("srgbClr") || - tnm.equals("sysClr")) { - lastTag.push(tnm); - c= (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("lnRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("lnRef.parseOOXML: " + e.toString()); - } - lnRef lr= new lnRef(idx, c); - return lr; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (colorChoice!=null) ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new lnRef(this); - } +class lnRef implements OOXMLElement { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4349076266006929729L; + private int idx; + private ColorChoice colorChoice = null; + + protected lnRef(int idx, ColorChoice c) { + this.idx = idx; + this.colorChoice = c; + } + + protected lnRef(lnRef lr) { + this.colorChoice = lr.colorChoice; + this.idx = lr.idx; + } + + protected static lnRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + int idx = -1; + ColorChoice c = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("lnRef")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String nm = xpp.getAttributeName(i); + if (nm.equals("idx")) { + idx = Integer.valueOf(xpp.getAttributeValue(i)).intValue(); + } + } + } else if (tnm.equals("schemeClr") || + tnm.equals("hslClr") || + tnm.equals("prstClr") || + tnm.equals("scrgbClr") || + tnm.equals("srgbClr") || + tnm.equals("sysClr")) { + lastTag.push(tnm); + c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("lnRef")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("lnRef.parseOOXML: " + e.toString()); + } + lnRef lr = new lnRef(idx, c); + return lr; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (colorChoice != null) ooxml.append(colorChoice.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new lnRef(this); + } } diff --git a/src/main/java/io/starter/formats/OOXML/Text.java b/src/main/java/io/starter/formats/OOXML/Text.java index c84c058..b53191b 100644 --- a/src/main/java/io/starter/formats/OOXML/Text.java +++ b/src/main/java/io/starter/formats/OOXML/Text.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,178 +22,184 @@ */ package io.starter.formats.OOXML; -import java.util.ArrayList; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.Font; import io.starter.formats.XLS.OOXMLAdapter; import io.starter.formats.XLS.Sst; import io.starter.formats.XLS.Unicodestring; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Stack; /** * text (Comment Text) - This element contains rich text which represents the text of a comment. The maximum length for this text is a - spreadsheet application implementation detail. A recommended guideline is 32767 chars + * This element contains rich text which represents the text of a comment. The maximum length for this text is a + * spreadsheet application implementation detail. A recommended guideline is 32767 chars * parent: comment * children: t (text), r (Rich Text Run), rPh (phonetic run), phoneticPr (phonetic properties) - * */ // TODO: finish elements rPh and phoneticPr // TODO: preserve -public class Text implements OOXMLElement{ +public class Text implements OOXMLElement { + + private static final long serialVersionUID = 5886384020139606328L; + private Unicodestring str = null; - private static final long serialVersionUID = 5886384020139606328L; - private Unicodestring str= null; - - /** - * create a new comment WITH formatting - * @param strref - */ - public Text(Unicodestring str) { - this.str= str; - } - public Text(Text t) { - this.str= t.str; - } - - /** - * create a new comment with NO formatting - * @param s - */ - public Text(String s) { - this.str= Sst.createUnicodeString(s, null, Sst.STRING_ENCODING_AUTO); - } + /** + * create a new comment WITH formatting + * + * @param strref + */ + public Text(Unicodestring str) { + this.str = str; + } - /** - * parse this Text element into a unicode string with formatting runs - */ - public static Text parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - Unicodestring str= null; - String s= ""; - ArrayList formattingRuns= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm= xpp.getName(); - if (tnm.equals("rPr")) { // intra-string formatting properties - int idx= s.length(); // index into character string to apply formatting to - Ss_rPr rp= (Ss_rPr) Ss_rPr.parseOOXML(xpp, bk); //.cloneElement(); - Font f= rp.generateFont(bk); // NOW CONVERT ss_rPr to a font!! - int fIndex= bk.getWorkBook().getFontIdx(f); // index for specific font formatting - if (fIndex==-1) // must insert new font - fIndex= bk.getWorkBook().insertFont(f)+1; - if (formattingRuns==null) formattingRuns= new ArrayList(); - formattingRuns.add(new short[] {Integer.valueOf(idx).shortValue(), Integer.valueOf(fIndex).shortValue()}); - } else if (tnm.equals("t")) { + public Text(Text t) { + this.str = t.str; + } + + /** + * create a new comment with NO formatting + * + * @param s + */ + public Text(String s) { + this.str = Sst.createUnicodeString(s, null, Sst.STRING_ENCODING_AUTO); + } + + /** + * parse this Text element into a unicode string with formatting runs + */ + public static Text parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + Unicodestring str = null; + String s = ""; + ArrayList formattingRuns = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("rPr")) { // intra-string formatting properties + int idx = s.length(); // index into character string to apply formatting to + Ss_rPr rp = (Ss_rPr) Ss_rPr.parseOOXML(xpp, bk); //.cloneElement(); + Font f = rp.generateFont(bk); // NOW CONVERT ss_rPr to a font!! + int fIndex = bk.getWorkBook().getFontIdx(f); // index for specific font formatting + if (fIndex == -1) // must insert new font + fIndex = bk.getWorkBook().insertFont(f) + 1; + if (formattingRuns == null) formattingRuns = new ArrayList(); + formattingRuns.add(new short[]{Integer.valueOf(idx).shortValue(), Integer.valueOf(fIndex).shortValue()}); + } else if (tnm.equals("t")) { /*boolean bPreserve= false; if (xpp.getAttributeCount()>0) { if (xpp.getAttributeName(0).equals("space") && xpp.getAttributeValue(0).equals("preserve")) bPreserve= true; } */ - eventType = xpp.next(); - while (eventType != XmlPullParser.END_DOCUMENT && - eventType != XmlPullParser.END_TAG && - eventType != XmlPullParser.TEXT) { - eventType = xpp.next(); - } - if (eventType==XmlPullParser.TEXT) { - s= s+ xpp.getText(); - } - } - }else if(eventType == XmlPullParser.END_TAG && xpp.getName().equals("text")) { - str= Sst.createUnicodeString(s, formattingRuns, Sst.STRING_ENCODING_UNICODE); // create a new unicode string with formatting runs - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("r.parseOOXML: " + e.toString()); - } - Text oe= new Text(str); - return oe; - } + eventType = xpp.next(); + while (eventType != XmlPullParser.END_DOCUMENT && + eventType != XmlPullParser.END_TAG && + eventType != XmlPullParser.TEXT) { + eventType = xpp.next(); + } + if (eventType == XmlPullParser.TEXT) { + s = s + xpp.getText(); + } + } + } else if (eventType == XmlPullParser.END_TAG && xpp.getName().equals("text")) { + str = Sst.createUnicodeString(s, formattingRuns, Sst.STRING_ENCODING_UNICODE); // create a new unicode string with formatting runs + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("r.parseOOXML: " + e.toString()); + } + Text oe = new Text(str); + return oe; + } + + public String getOOXML() { + return null; + } - public String getOOXML() { - return null; - } - /** - * return the OOXML representation of this Text (Comment) element - * @param bk - * @return - */ - public String getOOXML(io.starter.formats.XLS.WorkBook bk) { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (str!=null) { - String s= OOXMLAdapter.stripNonAsciiRetainQuote(str.getStringVal()).toString(); - ArrayList runs= str.getFormattingRuns(); - if (runs==null) { // no intra-string formatting - if(s.indexOf(" ")==0 || s.lastIndexOf(" ")==s.length()-1) { - ooxml.append("" + s + ""); - }else { - ooxml.append("" + s + ""); - } - ooxml.append("\r\n"); - } else { // have formatting runs which split up string into areas with separate formats applied + /** + * return the OOXML representation of this Text (Comment) element + * + * @param bk + * @return + */ + public String getOOXML(io.starter.formats.XLS.WorkBook bk) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (str != null) { + String s = OOXMLAdapter.stripNonAsciiRetainQuote(str.getStringVal()).toString(); + ArrayList runs = str.getFormattingRuns(); + if (runs == null) { // no intra-string formatting + if (s.indexOf(" ") == 0 || s.lastIndexOf(" ") == s.length() - 1) { + ooxml.append("" + s + ""); + } else { + ooxml.append("" + s + ""); + } + ooxml.append("\r\n"); + } else { // have formatting runs which split up string into areas with separate formats applied /* * */ - int begIdx= 0; - ooxml.append(""); // new rich text run - for (int j= 0; j < runs.size(); j++) { - short[] idxs= (short[])runs.get(j); - if (idxs[0]>begIdx) { - ooxml.append(("" + OOXMLAdapter.stripNonAscii(s.substring(begIdx, idxs[0])) + "")); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); - begIdx= idxs[0]; - } - Ss_rPr rp= Ss_rPr.createFromFont(bk.getFont(idxs[1])); - ooxml.append(rp.getOOXML()); - } - if (begIdx" + OOXMLAdapter.stripNonAscii(s) + ""); - ooxml.append("\r\n"); - ooxml.append(""); - } - } - ooxml.append(""); - ooxml.append("\r\n"); - return ooxml.toString(); - } + int begIdx = 0; + ooxml.append(""); // new rich text run + for (int j = 0; j < runs.size(); j++) { + short[] idxs = (short[]) runs.get(j); + if (idxs[0] > begIdx) { + ooxml.append(("" + OOXMLAdapter.stripNonAscii(s.substring(begIdx, idxs[0])) + "")); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + begIdx = idxs[0]; + } + Ss_rPr rp = Ss_rPr.createFromFont(bk.getFont(idxs[1])); + ooxml.append(rp.getOOXML()); + } + if (begIdx < s.length()) // output remaining string + s = s.substring(begIdx); + else + s = ""; + ooxml.append("" + OOXMLAdapter.stripNonAscii(s) + ""); + ooxml.append("\r\n"); + ooxml.append(""); + } + } + ooxml.append(""); + ooxml.append("\r\n"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Text(this); + } + + /** + * return the String value of this Text (Comment) element + * i.e. without formatting + * + * @return + */ + public String getComment() { + if (str != null) + return str.getStringVal(); + return null; + } - public OOXMLElement cloneElement() { - return new Text(this); - } - - /** - * return the String value of this Text (Comment) element - * i.e. without formatting - * @return - */ - public String getComment() { - if (str!=null) - return str.getStringVal(); - return null; - } - - /** - * return the String value of this Text (Comment) element - * Including formatting runs - * @return - */ - public Unicodestring getCommentWithFormatting() { - return str; - } + /** + * return the String value of this Text (Comment) element + * Including formatting runs + * + * @return + */ + public Unicodestring getCommentWithFormatting() { + return str; + } } diff --git a/src/main/java/io/starter/formats/OOXML/TextRun.java b/src/main/java/io/starter/formats/OOXML/TextRun.java index 173a5b4..2cc95d7 100644 --- a/src/main/java/io/starter/formats/OOXML/TextRun.java +++ b/src/main/java/io/starter/formats/OOXML/TextRun.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,472 +22,484 @@ */ package io.starter.formats.OOXML; +import io.starter.OpenXLS.WorkBookHandle; +import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; - /** * textRun group, either r (regular text), br (line break) or fld (text Field) * parent: p * children: either r, br or fld - * - * */ //TODO: Finish rPr children highlight TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver, -public class TextRun implements OOXMLElement{ - - private static final long serialVersionUID = -6224636879471246452L; - private r run= null; - private Br brk= null; - private Fld f= null; - - public TextRun(r run, Br brk, Fld f) { - this.run= run; - this.brk= brk; - this.f= f; - } - public TextRun(TextRun r) { - this.run= r.run; - this.brk= r.brk; - this.f= r.f; - } - - /** - * create a new regular text text run (OOXML element r) - * @param s - */ - public TextRun(String s) { - this.run= new r(s, null); - } - +public class TextRun implements OOXMLElement { + + private static final long serialVersionUID = -6224636879471246452L; + private r run = null; + private Br brk = null; + private Fld f = null; + + public TextRun(r run, Br brk, Fld f) { + this.run = run; + this.brk = brk; + this.f = f; + } + + public TextRun(TextRun r) { + this.run = r.run; + this.brk = r.brk; + this.f = r.f; + } + + /** + * create a new regular text text run (OOXML element r) + * + * @param s + */ + public TextRun(String s) { + this.run = new r(s, null); + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - r run= null; - Br brk= null; - Fld f= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("r")) { - lastTag.push(tnm); - run= (r) r.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("br")) { - lastTag.push(tnm); - brk= (Br) Br.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("fld")) { - lastTag.push(tnm); - f= (Fld) Fld.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } - } else if (eventType== XmlPullParser.END_TAG) { // shouldn't get here - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("textRun.parseOOXML: " + e.toString()); - } - TextRun oe= new TextRun(run, brk, f); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - if (run!=null) ooxml.append(run.getOOXML()); - else if (brk!=null) ooxml.append(brk.getOOXML()); - else if (f!=null) ooxml.append(f.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TextRun(this); - } - public String getTitle() { - if (run!=null) return run.getTitle(); - if (f!=null) return f.getTitle(); - return null; - } - - /** - * return the text properties for this text run - * @return - */ - public HashMap getTextProperties() { - if (run!=null) - return run.getTextProperties(); - return new HashMap(); - } + r run = null; + Br brk = null; + Fld f = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("r")) { + lastTag.push(tnm); + run = r.parseOOXML(xpp, lastTag, bk); + lastTag.pop(); + break; + } else if (tnm.equals("br")) { + lastTag.push(tnm); + brk = Br.parseOOXML(xpp, lastTag, bk); + lastTag.pop(); + break; + } else if (tnm.equals("fld")) { + lastTag.push(tnm); + f = Fld.parseOOXML(xpp, lastTag, bk); + lastTag.pop(); + break; + } + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here + lastTag.pop(); + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("textRun.parseOOXML: " + e.toString()); + } + TextRun oe = new TextRun(run, brk, f); + return oe; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + if (run != null) ooxml.append(run.getOOXML()); + else if (brk != null) ooxml.append(brk.getOOXML()); + else if (f != null) ooxml.append(f.getOOXML()); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new TextRun(this); + } + + public String getTitle() { + if (run != null) return run.getTitle(); + if (f != null) return f.getTitle(); + return null; + } + + /** + * return the text properties for this text run + * + * @return + */ + public HashMap getTextProperties() { + if (run != null) + return run.getTextProperties(); + return new HashMap(); + } } - + /** * OOXML element r, text run, sub-element of p (paragraph) - * - * children: rPr, t (actual text string) - * + *

        + * children: rPr, t (actual text string) */ -class r implements OOXMLElement{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 863254651451294443L; - private String t=""; // t element just contains string - private RPr rp= null; - - public r(String title, RPr rp) { - this.rp= rp; - this.t= title; - } - public r(r run) { - this.rp= run.rp; - this.t= run.t; - } - - public String getOOXML() { - if (t==null || t.equals("")) return ""; - - t= io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(t).toString(); - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); // text run - if (rp!=null) ooxml.append(rp.getOOXML()); - ooxml.append("" + t + ""); - ooxml.append(""); - return ooxml.toString(); - } +class r implements OOXMLElement { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 863254651451294443L; + private String t = ""; // t element just contains string + private RPr rp = null; + + public r(String title, RPr rp) { + this.rp = rp; + this.t = title; + } + + public r(r run) { + this.rp = run.rp; + this.t = run.t; + } + + public String getOOXML() { + if (t == null || t.equals("")) return ""; + + t = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(t).toString(); + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); // text run + if (rp != null) ooxml.append(rp.getOOXML()); + ooxml.append("" + t + ""); + ooxml.append(""); + return ooxml.toString(); + } public static r parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String t= ""; - RPr rp= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("t")) { // t element of text run -- the title string we are interested in - t= io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); - } else if (tnm.equals("rPr")) { // text run properties - lastTag.push(tnm); - rp= (RPr) RPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("r")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("r.parseOOXML: " + e.toString()); - } - r run= new r(t, rp); - return run; + String t = ""; + RPr rp = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("t")) { // t element of text run -- the title string we are interested in + t = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); + } else if (tnm.equals("rPr")) { // text run properties + lastTag.push(tnm); + rp = RPr.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("r")) { + lastTag.pop(); // pop this tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("r.parseOOXML: " + e.toString()); + } + r run = new r(t, rp); + return run; } + public OOXMLElement cloneElement() { - return new r(this); - } - public String getTitle() { return t; } - - public HashMap getTextProperties() { - if (rp!=null) - return rp.getTextProperties(); - return new HashMap(); - } + return new r(this); + } + + public String getTitle() { + return t; + } + + public HashMap getTextProperties() { + if (rp != null) + return rp.getTextProperties(); + return new HashMap(); + } } /** * OOXML element br, vertical break, sub-element of p (paragraph) - * - * children: rPr - * + *

        + * children: rPr */ class Br implements OOXMLElement { - private static final long serialVersionUID = -1724086871866480013L; - private RPr rp; - - public Br(RPr rp) { - this.rp= rp; - } - public Br(Br b) { - this.rp= b.rp; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (rp!=null) ooxml.append(rp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } + private static final long serialVersionUID = -1724086871866480013L; + private RPr rp; + + public Br(RPr rp) { + this.rp = rp; + } + + public Br(Br b) { + this.rp = b.rp; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (rp != null) ooxml.append(rp.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } public static Br parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - RPr rp= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("rPr")) { // text run properties - lastTag.push(tnm); - rp= (RPr) RPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("br")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("br.parseOOXML: " + e.toString()); - } - Br b= new Br(rp); - return b; + RPr rp = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("rPr")) { // text run properties + lastTag.push(tnm); + rp = RPr.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("br")) { + lastTag.pop(); // pop this tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("br.parseOOXML: " + e.toString()); + } + Br b = new Br(rp); + return b; } + public OOXMLElement cloneElement() { - return new Br(this); - } + return new Br(this); + } } /** * OOXML element fld, text field, sub-element of p (paragraph) - * - * children: pPr, rPr, t (actual text string) - * + *

        + * children: pPr, rPr, t (actual text string) */ -class Fld implements OOXMLElement{ - - private static final long serialVersionUID = -7060602732912595402L; - private String t; // t element just contains string - private RPr rp; - private PPr p; - String id; - String type; - - public Fld(String id, String type, String title, RPr rp, PPr p) { - this.id= id; - this.type= type; - this.rp= rp; - this.t= title; - this.p= p; - } - public Fld(Fld f) { - this.id= f.id; - this.type= f.type; - this.rp= f.rp; - this.t= f.t; - this.p= f.p; - } - - public String getOOXML() { - if (t==null || t.equals("")) return ""; - - t= io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(t).toString(); - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (rp!=null) ooxml.append(rp.getOOXML()); - if (p!=null) ooxml.append(p.getOOXML()); - ooxml.append("" + t + ""); - ooxml.append(""); - return ooxml.toString(); - } +class Fld implements OOXMLElement { + + private static final long serialVersionUID = -7060602732912595402L; + private String t; // t element just contains string + private RPr rp; + private PPr p; + String id; + String type; + + public Fld(String id, String type, String title, RPr rp, PPr p) { + this.id = id; + this.type = type; + this.rp = rp; + this.t = title; + this.p = p; + } + + public Fld(Fld f) { + this.id = f.id; + this.type = f.type; + this.rp = f.rp; + this.t = f.t; + this.p = f.p; + } + + public String getOOXML() { + if (t == null || t.equals("")) return ""; + + t = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(t).toString(); + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (rp != null) ooxml.append(rp.getOOXML()); + if (p != null) ooxml.append(p.getOOXML()); + ooxml.append("" + t + ""); + ooxml.append(""); + return ooxml.toString(); + } public static Fld parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String t= ""; - String id= ""; - String type= ""; - PPr p= null; - RPr rp= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fld")) { - for (int i= 0; i < xpp.getAttributeCount(); i++) { - String n= xpp.getAttributeName(i); - if (n.equals("id")) { - id= xpp.getAttributeValue(i); - } else if (n.equals("type")) { - type= xpp.getAttributeValue(i); - } - } - } else if (tnm.equals("t")) { // t element -- the title string we are interested in - t= io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); - } else if (tnm.equals("rPr")) { // text run properties - lastTag.push(tnm); - rp= (RPr) RPr.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("pPr")) { // text field properties - lastTag.push(tnm); - p= (PPr) PPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("fld")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("textRun.parseOOXML: " + e.toString()); - } - Fld f= new Fld(id, type, t, rp, p); - return f; + String t = ""; + String id = ""; + String type = ""; + PPr p = null; + RPr rp = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("fld")) { + for (int i = 0; i < xpp.getAttributeCount(); i++) { + String n = xpp.getAttributeName(i); + if (n.equals("id")) { + id = xpp.getAttributeValue(i); + } else if (n.equals("type")) { + type = xpp.getAttributeValue(i); + } + } + } else if (tnm.equals("t")) { // t element -- the title string we are interested in + t = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); + } else if (tnm.equals("rPr")) { // text run properties + lastTag.push(tnm); + rp = RPr.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("pPr")) { // text field properties + lastTag.push(tnm); + p = PPr.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("fld")) { + lastTag.pop(); // pop this tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("textRun.parseOOXML: " + e.toString()); + } + Fld f = new Fld(id, type, t, rp, p); + return f; } + public OOXMLElement cloneElement() { - return new Fld(this); - } - public String getTitle() { return t; } - + return new Fld(this); + } + + public String getTitle() { + return t; + } + } class RPr implements OOXMLElement { - private static final long serialVersionUID = 228716184734751439L; - private HashMap attrs; - private Ln l; - private FillGroup fill; - private EffectPropsGroup effect; - private String latin, ea, cs; - - public RPr(HashMap attrs, Ln l, FillGroup fill, EffectPropsGroup effect, String latin, String ea, String cs) { - this.attrs= attrs; - this.l= l; - this.fill= fill; - this.effect= effect; - this.latin= latin; - this.ea= ea; - this.cs= cs; - } - public RPr(RPr rp) { - this.attrs= rp.attrs; - this.l= rp.l; - this.fill= rp.fill; - this.effect= rp.effect; - this.latin= rp.latin; - this.ea= rp.ea; - this.cs= rp.cs; - } - + private static final long serialVersionUID = 228716184734751439L; + private HashMap attrs; + private Ln l; + private FillGroup fill; + private EffectPropsGroup effect; + private String latin, ea, cs; + + public RPr(HashMap attrs, Ln l, FillGroup fill, EffectPropsGroup effect, String latin, String ea, String cs) { + this.attrs = attrs; + this.l = l; + this.fill = fill; + this.effect = effect; + this.latin = latin; + this.ea = ea; + this.cs = cs; + } + + public RPr(RPr rp) { + this.attrs = rp.attrs; + this.l = rp.l; + this.fill = rp.fill; + this.effect = rp.effect; + this.latin = rp.latin; + this.ea = rp.ea; + this.cs = rp.cs; + } + public static RPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs= new HashMap(); - Ln l= null; - FillGroup fill= null; - EffectPropsGroup effect= null; - String latin= null, ea= null, cs= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("rPr")) { // get attributes - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("ln")) { - lastTag.push(tnm); - l= (Ln) Ln.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill= (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect= (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - // TODO: Eventually these will be objects - } else if (tnm.equals("latin")) { - latin= xpp.getAttributeValue(0); - } else if (tnm.equals("ea")) { - ea= xpp.getAttributeValue(0); - } else if (tnm.equals("cs")) { - cs= xpp.getAttributeValue(0); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("rPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("rPr.parseOOXML: " + e.toString()); - } - RPr rp= new RPr(attrs, l, fill, effect, latin, ea, cs); - return rp; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(" i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (l!=null) ooxml.append(l.getOOXML()); - if (fill!=null) ooxml.append(fill.getOOXML()); // group fill - if (effect!=null) ooxml.append(effect.getOOXML()); // group effect - // highlight - // TEXTUNDERLINELINE - // TEXTUNDERLINEFILL - if (latin!=null) ooxml.append(""); - if (ea!=null) ooxml.append(""); - if (cs!=null) ooxml.append(""); - // sym - // hlinkClick - // hlinkMouseOver - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new RPr(this); - } - - /** - * return the text properties for this text run - * @return - */ - public HashMap getTextProperties() { - HashMap textprops= new HashMap(); - textprops.putAll(attrs); - if (latin!=null)textprops.put("latin_typeface", latin); - if (ea!=null)textprops.put("ea_typeface", ea); - if (cs!=null)textprops.put("cs_typeface", cs); - // TODO: Fill, line ... - return textprops; - } + HashMap attrs = new HashMap(); + Ln l = null; + FillGroup fill = null; + EffectPropsGroup effect = null; + String latin = null, ea = null, cs = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("rPr")) { // get attributes + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("ln")) { + lastTag.push(tnm); + l = (Ln) Ln.parseOOXML(xpp, lastTag, bk); + } else if ( + tnm.equals("solidFill") || + tnm.equals("noFill") || + tnm.equals("gradFill") || + tnm.equals("grpFill") || + tnm.equals("pattFill") || + tnm.equals("blipFill")) { + lastTag.push(tnm); + fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); + } else if ( + tnm.equals("effectLst") || + tnm.equals("effectDag")) { + lastTag.push(tnm); + effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); + // TODO: Eventually these will be objects + } else if (tnm.equals("latin")) { + latin = xpp.getAttributeValue(0); + } else if (tnm.equals("ea")) { + ea = xpp.getAttributeValue(0); + } else if (tnm.equals("cs")) { + cs = xpp.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("rPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("rPr.parseOOXML: " + e.toString()); + } + RPr rp = new RPr(attrs, l, fill, effect, latin, ea, cs); + return rp; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(" i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + } + ooxml.append(">"); + if (l != null) ooxml.append(l.getOOXML()); + if (fill != null) ooxml.append(fill.getOOXML()); // group fill + if (effect != null) ooxml.append(effect.getOOXML()); // group effect + // highlight + // TEXTUNDERLINELINE + // TEXTUNDERLINEFILL + if (latin != null) ooxml.append(""); + if (ea != null) ooxml.append(""); + if (cs != null) ooxml.append(""); + // sym + // hlinkClick + // hlinkMouseOver + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new RPr(this); + } + + /** + * return the text properties for this text run + * + * @return + */ + public HashMap getTextProperties() { + HashMap textprops = new HashMap(); + textprops.putAll(attrs); + if (latin != null) textprops.put("latin_typeface", latin); + if (ea != null) textprops.put("ea_typeface", ea); + if (cs != null) textprops.put("cs_typeface", cs); + // TODO: Fill, line ... + return textprops; + } } diff --git a/src/main/java/io/starter/formats/OOXML/Theme.java b/src/main/java/io/starter/formats/OOXML/Theme.java index ce70f75..73ddfc0 100644 --- a/src/main/java/io/starter/formats/OOXML/Theme.java +++ b/src/main/java/io/starter/formats/OOXML/Theme.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,140 +22,139 @@ */ package io.starter.formats.OOXML; -import java.io.InputStream; - +import io.starter.OpenXLS.WorkBookHandle; +import io.starter.toolkit.Logger; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserFactory; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; +import java.io.InputStream; /** * not fully implemented yet * Need to handle fonts, fills ... * Until then themes will not be re-created; instead, it will be treated as a pass-through - * */ public class Theme implements OOXMLElement { - private static final long serialVersionUID = -9201334460078323287L; - /** - * Generic "Office" Color Scheme TODO: Read from theme1.xml - * 12 colors: 2 darks, 2 lights, 6 accents, 2 hyperlinks - * - * NOTE: APPEARS that 1st 4 are "swapped" so in theme1.xml - * dk1 is index 0, then lt1, dk2, lt2 - * but appears to be a known bug (or a known mystery) that - * one must swap the 1st two pairs - */ - public String[] genericThemeClrs= { - "FFFFFF", // text/bg lt1 = window bg (white) - "000000", // text/bg dk1 = window text (black) - "EEECE1", // text/bg lt2 = secondary window bg color (grayish) - "1F497D", // text/bg dk2 = secondary window text color (deep blue) - "4F81BD", // accent1 -- med-deep blue - "C0504D", // accent2 -- maroon - "9BBB59", // accent3 -- lime greenish - "8064A2", // accent4 -- purplish blue - "4BACC6", // accent5 -- med blue - "F79646", // accent6 -- orange-coral - "0000FF", // hlink -- blue - "800080", // folhlink -- dk purple (followed hlink) - }; + private static final long serialVersionUID = -9201334460078323287L; + /** + * Generic "Office" Color Scheme TODO: Read from theme1.xml + * 12 colors: 2 darks, 2 lights, 6 accents, 2 hyperlinks + *

        + * NOTE: APPEARS that 1st 4 are "swapped" so in theme1.xml + * dk1 is index 0, then lt1, dk2, lt2 + * but appears to be a known bug (or a known mystery) that + * one must swap the 1st two pairs + */ + public String[] genericThemeClrs = { + "FFFFFF", // text/bg lt1 = window bg (white) + "000000", // text/bg dk1 = window text (black) + "EEECE1", // text/bg lt2 = secondary window bg color (grayish) + "1F497D", // text/bg dk2 = secondary window text color (deep blue) + "4F81BD", // accent1 -- med-deep blue + "C0504D", // accent2 -- maroon + "9BBB59", // accent3 -- lime greenish + "8064A2", // accent4 -- purplish blue + "4BACC6", // accent5 -- med blue + "F79646", // accent6 -- orange-coral + "0000FF", // hlink -- blue + "800080", // folhlink -- dk purple (followed hlink) + }; + + public static Theme parseThemeOOXML(WorkBookHandle bk, InputStream ii) { + Theme t = new Theme(); + t.parseOOXML(bk, ii); + return t; + } + + /** + * given Theme OOXML inputstream, retrieve theme colors for later use + * This element holds all the different formatting options available to a document through a theme and defines the overall look + * and feel of the document when themed objects are used within the document. + * + * @param bk WorkBookHandle + * @param ii InputStream + * @see parseSheetOOXML + */ + public void parseOOXML(WorkBookHandle bk, InputStream ii) { + try { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(ii, null); // using XML 1.0 specification + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + int idx = 0; + String tnm = xpp.getName(); + if (tnm.equals("clrScheme")) { // in both themeX.xml, themeOverrideX.xml. 12 colors defined. + eventType = xpp.next(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + tnm = xpp.getName(); + if (tnm.equals("dk1")) { + idx = 1; + } else if (tnm.equals("lt1")) { + idx = 0; + } else if (tnm.equals("dk2")) { + idx = 3; + } else if (tnm.equals("lt2")) { + idx = 2; + } else if (tnm.equals("accent1")) { + idx = 4; + } else if (tnm.equals("accent2")) { + idx = 5; + } else if (tnm.equals("accent3")) { + idx = 6; + } else if (tnm.equals("accent4")) { + idx = 7; + } else if (tnm.equals("accent5")) { + idx = 8; + } else if (tnm.equals("accent6")) { + idx = 9; + } else if (tnm.equals("hlink")) { + idx = 10; + } else if (tnm.equals("folHlink")) { + idx = 11; + } else if (tnm.equals("sysClr")) { // system color attributes val, lastClr + this.genericThemeClrs[idx] = xpp.getAttributeValue("", "lastClr"); + } else if (tnm.equals("srgbClr")) { + this.genericThemeClrs[idx] = xpp.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.END_TAG + && xpp.getName().equals("clrScheme")) { + break; + } + eventType = xpp.next(); + } + } else if (tnm.equals("fmtScheme")) { + // This element contains the background fill styles, effect styles, fill styles, and line styles which define the style matrix for a theme. + // The style matrix consists of subtle, moderate, and intense fills, lines, and effects. + } else if (tnm.equals("fontScheme")) { + // majorFont defines the set of major fonts which are to be used under different languages or locals. + // minorFont defines the set of minor fonts that are to be used under different languages or locals + } else if (tnm.equals("objectDefaults")) { // only in themeX.xml + + } else if (tnm.equals("extraClrSchemeList")) { // only in themeX.xml - public static Theme parseThemeOOXML(WorkBookHandle bk, InputStream ii) { - Theme t= new Theme(); - t.parseOOXML(bk, ii); - return t; - } - - /** - * given Theme OOXML inputstream, retrieve theme colors for later use - * This element holds all the different formatting options available to a document through a theme and defines the overall look - and feel of the document when themed objects are used within the document. - * - * @param bk WorkBookHandle - * @param ii InputStream - * @see parseSheetOOXML - */ - public void parseOOXML(WorkBookHandle bk, InputStream ii) { - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - int idx = 0; - String tnm = xpp.getName(); - if (tnm.equals("clrScheme")) { // in both themeX.xml, themeOverrideX.xml. 12 colors defined. - eventType = xpp.next(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - tnm = xpp.getName(); - if (tnm.equals("dk1")) { - idx = 1; - } else if (tnm.equals("lt1")) { - idx = 0; - } else if (tnm.equals("dk2")) { - idx = 3; - } else if (tnm.equals("lt2")) { - idx = 2; - } else if (tnm.equals("accent1")) { - idx = 4; - } else if (tnm.equals("accent2")) { - idx = 5; - } else if (tnm.equals("accent3")) { - idx = 6; - } else if (tnm.equals("accent4")) { - idx = 7; - } else if (tnm.equals("accent5")) { - idx = 8; - } else if (tnm.equals("accent6")) { - idx = 9; - } else if (tnm.equals("hlink")) { - idx = 10; - } else if (tnm.equals("folHlink")) { - idx = 11; - } else if (tnm.equals("sysClr")) { // system color attributes val, lastClr - this.genericThemeClrs[idx] = xpp.getAttributeValue("", "lastClr"); - } else if (tnm.equals("srgbClr")) { - this.genericThemeClrs[idx] = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG - && xpp.getName().equals("clrScheme")) { - break; - } - eventType = xpp.next(); - } - } else if (tnm.equals("fmtScheme")) { - // This element contains the background fill styles, effect styles, fill styles, and line styles which define the style matrix for a theme. - // The style matrix consists of subtle, moderate, and intense fills, lines, and effects. - } else if (tnm.equals("fontScheme")) { - // majorFont defines the set of major fonts which are to be used under different languages or locals. - // minorFont defines the set of minor fonts that are to be used under different languages or locals - } else if (tnm.equals("objectDefaults")) { // only in themeX.xml + } + } else if (eventType == XmlPullParser.END_TAG) { + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("Theme.parseOOXML: " + e.toString()); + } + return; + } - } else if (tnm.equals("extraClrSchemeList")) { // only in themeX.xml + // TODO: implement + public OOXMLElement cloneElement() { + return null; + } - } - } else if (eventType == XmlPullParser.END_TAG) { - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("Theme.parseOOXML: " + e.toString()); - } - return; - } - - // TODO: implement - public OOXMLElement cloneElement() { - return null; - } - public String getOOXML() { - return null; - } + public String getOOXML() { + return null; + } } diff --git a/src/main/java/io/starter/formats/OOXML/Title.java b/src/main/java/io/starter/formats/OOXML/Title.java index 81958e5..33916e7 100644 --- a/src/main/java/io/starter/formats/OOXML/Title.java +++ b/src/main/java/io/starter/formats/OOXML/Title.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,337 +22,336 @@ */ package io.starter.formats.OOXML; -import java.util.HashMap; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.Layout; -import io.starter.formats.OOXML.SpPr; import io.starter.formats.XLS.Font; import io.starter.formats.XLS.WorkBook; import io.starter.formats.XLS.charts.TextDisp; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Stack; /** * class holds OOXML title Property used to define chart and axis titles - * - * */ public class Title implements OOXMLElement { - private static final long serialVersionUID = -3889674575558708481L; - private Layout layout = null; - private SpPr sp = null; - private ChartText chartText = null; // tx - private TxPr txpr = null; // xPr + private static final long serialVersionUID = -3889674575558708481L; + private Layout layout = null; + private SpPr sp = null; + private ChartText chartText = null; // tx + private TxPr txpr = null; // xPr - public Title(ChartText ct, TxPr txpr, Layout l, SpPr sp) { - this.layout = l; - this.sp = sp; - this.chartText = ct; - this.txpr = txpr; - } + public Title(ChartText ct, TxPr txpr, Layout l, SpPr sp) { + this.layout = l; + this.sp = sp; + this.chartText = ct; + this.txpr = txpr; + } - /** - * for BIFF8 compatibility, create a Title element from the title string - * - * @param t - */ - public Title(String t) { - this.chartText = new ChartText(t); - this.sp= new SpPr("c"); - // no spPr - } - - /** - * create an OOXML title from a 2003-v TextDisp record - * @param td - */ - public Title(TextDisp td, WorkBook bk) { - P para= new P(td.getFont(bk), td.toString()); - this.chartText= new ChartText(null, para, null); - } + /** + * for BIFF8 compatibility, create a Title element from the title string + * + * @param t + */ + public Title(String t) { + this.chartText = new ChartText(t); + this.sp = new SpPr("c"); + // no spPr + } - public void setLayout(double x, double y) { - this.layout= new Layout(null, new double[]{x, y, -1, -1}); - } - /** - * parse title OOXML element title - * - * @param xpp - * XmlPullParser - * @param lastTag - * element stack - * @return spPr object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - TxPr txpr = null; - ChartText ct = null; - Layout l = null; - SpPr sp = null; + /** + * create an OOXML title from a 2003-v TextDisp record + * + * @param td + */ + public Title(TextDisp td, WorkBook bk) { + P para = new P(td.getFont(bk), td.toString()); + this.chartText = new ChartText(null, para, null); + } - /* - * TextDisp td= (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_TITLE, - * str, this.getWorkBook()); this.addChartRecord((BiffRec) td); // add - * TextDisp title to end of chart recs ... charttitle= td; - */ - /** - * contains (in Sequence) layout overlay -- not handled yet spPr tx txPr - */ - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tx")) { // chart text - lastTag.push(tnm); - ct = (ChartText) ChartText.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("manualLayout")) { - lastTag.push(tnm); - l = (Layout) Layout.parseOOXML(xpp, lastTag); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - // sp.setNS("c"); - } else if (tnm.equals("txPr")) { - lastTag.push(tnm); - txpr = (TxPr) TxPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("title")) { - lastTag.pop(); // pop title tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("title.parseOOXML: " + e.toString()); - } - Title tt = new Title(ct, txpr, l, sp); - return tt; - } - - public Layout getLayout() { return layout; } - public SpPr getSpPr() { return sp; } + public void setLayout(double x, double y) { + this.layout = new Layout(null, new double[]{x, y, -1, -1}); + } - /** - * generate ooxml to define a title - * - * @return - */ - /** - * tx chart text layout overlay spPr txPr - */ - public String getOOXML() { - StringBuffer tooxml = new StringBuffer(); - tooxml.append(""); - if (this.chartText != null) - tooxml.append(chartText.getOOXML()); - if (this.layout != null) - tooxml.append(layout.getOOXML()); - // TODO: overlay - if (this.sp != null) - tooxml.append(sp.getOOXML()); - if (this.txpr != null) - tooxml.append(this.txpr.getOOXML()); - tooxml.append(""); - return tooxml.toString(); - } + /** + * parse title OOXML element title + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object + */ + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + TxPr txpr = null; + ChartText ct = null; + Layout l = null; + SpPr sp = null; - public OOXMLElement cloneElement() { - return new Title(this.chartText, this.txpr, this.layout, this.sp); - } + /* + * TextDisp td= (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_TITLE, + * str, this.getWorkBook()); this.addChartRecord((BiffRec) td); // add + * TextDisp title to end of chart recs ... charttitle= td; + */ + /** + * contains (in Sequence) layout overlay -- not handled yet spPr tx txPr + */ + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("tx")) { // chart text + lastTag.push(tnm); + ct = ChartText.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("manualLayout")) { + lastTag.push(tnm); + l = (Layout) Layout.parseOOXML(xpp, lastTag); + } else if (tnm.equals("spPr")) { + lastTag.push(tnm); + sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); + // sp.setNS("c"); + } else if (tnm.equals("txPr")) { + lastTag.push(tnm); + txpr = (TxPr) TxPr.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("title")) { + lastTag.pop(); // pop title tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("title.parseOOXML: " + e.toString()); + } + Title tt = new Title(ct, txpr, l, sp); + return tt; + } - public String getTitle() { - if (chartText != null) - return chartText.getTitle(); - return ""; - } + public Layout getLayout() { + return layout; + } - /** - * return the font index for this title - * - * @param wb - * @return - */ - public int getFontId(WorkBookHandle wb) { - if (chartText != null) - return chartText.getFontId(wb); - return -1; - } + public SpPr getSpPr() { + return sp; + } + + /** + * generate ooxml to define a title + * + * @return + */ + /** + * tx chart text layout overlay spPr txPr + */ + public String getOOXML() { + StringBuffer tooxml = new StringBuffer(); + tooxml.append(""); + if (this.chartText != null) + tooxml.append(chartText.getOOXML()); + if (this.layout != null) + tooxml.append(layout.getOOXML()); + // TODO: overlay + if (this.sp != null) + tooxml.append(sp.getOOXML()); + if (this.txpr != null) + tooxml.append(this.txpr.getOOXML()); + tooxml.append(""); + return tooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Title(this.chartText, this.txpr, this.layout, this.sp); + } + + public String getTitle() { + if (chartText != null) + return chartText.getTitle(); + return ""; + } + + /** + * return the font index for this title + * + * @param wb + * @return + */ + public int getFontId(WorkBookHandle wb) { + if (chartText != null) + return chartText.getFontId(wb); + return -1; + } } /** * chart text element tx - * - * */ + /** * contains either strRef -- contains f, strCache rich -- contains bodyPr, * lstStyle, p */ class ChartText implements OOXMLElement { - - private static final long serialVersionUID = -1175394918747218776L; - StrRef strref = null; - P para = null; - BodyPr bpr = null; - /** - * for BIFF8 compatibility, create an OOXML chartText element from title - * string - * - * @param s - */ - public ChartText(String s) { - this.para = new P(s); - } + private static final long serialVersionUID = -1175394918747218776L; + StrRef strref = null; + P para = null; + BodyPr bpr = null; + + /** + * for BIFF8 compatibility, create an OOXML chartText element from title + * string + * + * @param s + */ + public ChartText(String s) { + this.para = new P(s); + } + + public ChartText(StrRef s, P para, BodyPr bpr) { + this.strref = s; + this.para = para; + this.bpr = bpr; + } + + public static ChartText parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + P para = null; + BodyPr bpr = null; + StrRef s = null; - public ChartText(StrRef s, P para, BodyPr bpr) { - this.strref = s; - this.para = para; - this.bpr = bpr; - } - - public static ChartText parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - P para = null; - BodyPr bpr = null; - StrRef s = null; + try { // title->tx->rich->bodyPr lstStyle, p->pPr, r + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("p")) { // text Paragraph props - part of rich + lastTag.push(tnm); + para = (P) P.parseOOXML(xpp, lastTag, bk); + } else if (tnm.equals("bodyPr")) { // body-level Paragraph props -- part of rich + lastTag.push(tnm); + bpr = (BodyPr) BodyPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("strRef")) { + lastTag.push(tnm); + s = (StrRef) StrRef.parseOOXML(xpp, lastTag); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("tx")) { + lastTag.pop(); // pop title tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("chartText.parseOOXML: " + e.toString()); + } + ChartText ct = new ChartText(s, para, bpr); + return ct; + } - try { // title->tx->rich->bodyPr lstStyle, p->pPr, r - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("p")) { // text Paragraph props - part of rich - lastTag.push(tnm); - para = (P) P.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("bodyPr")) { // body-level Paragraph props -- part of rich - lastTag.push(tnm); - bpr = (BodyPr) BodyPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("strRef")) { - lastTag.push(tnm); - s = (StrRef) StrRef.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("tx")) { - lastTag.pop(); // pop title tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("chartText.parseOOXML: " + e.toString()); - } - ChartText ct = new ChartText(s, para, bpr); - return ct; - } + public String getOOXML() { + StringBuffer cooxml = new StringBuffer(); + cooxml.append(""); // chart text + if (strref == null) { // it has a rich element + cooxml.append(""); + if (bpr != null) + cooxml.append(bpr.getOOXML()); + else + cooxml.append(""); + cooxml.append(""); // TODO: Handle!!! + if (para != null) + cooxml.append(para.getOOXML()); // text paragraph + cooxml.append(""); + } else { // it has a strRef element + cooxml.append(strref.getOOXML()); + } + cooxml.append(""); + return cooxml.toString(); + } - public String getOOXML() { - StringBuffer cooxml = new StringBuffer(); - cooxml.append(""); // chart text - if (strref == null) { // it has a rich element - cooxml.append(""); - if (bpr != null) - cooxml.append(bpr.getOOXML()); - else - cooxml.append(""); - cooxml.append(""); // TODO: Handle!!! - if (para != null) - cooxml.append(para.getOOXML()); // text paragraph - cooxml.append(""); - } else { // it has a strRef element - cooxml.append(strref.getOOXML()); - } - cooxml.append(""); - return cooxml.toString(); - } + public String getTitle() { + if (para != null) + return para.getTitle(); + return ""; + } - public String getTitle() { - if (para != null) - return para.getTitle(); - return ""; - } + public OOXMLElement cloneElement() { + return new ChartText(this.strref, this.para, this.bpr); + } - public OOXMLElement cloneElement() { - return new ChartText(this.strref, this.para, this.bpr); - } - - /** - * concatenate the 3 levels of text properties and either find an existing - * font or add new - * - * @return - */ - public int getFontId(WorkBookHandle wb) { - HashMap textprops = new HashMap(); - if (bpr != null) { - /* - * noAutofit (No AutoFit) §5.1.5.1.2 normAutofit (Normal AutoFit) - * §5.1.5.1.3 prstTxWarp (Preset Text Warp) §5.1.11.19 scene3d (3D - * Scene Properties) §5.1.4.1.26 sp3d (Apply 3D shape properties) - * §5.1.7.12 spAutoFit (Shape AutoFit) - */ - // TODO: set any of the above?? - } - if (para != null) { - textprops = para.getTextProperties(); - } - /* - * altLang (Alternative Language) b (Bold) bool baseline (Baseline) bmk - * (Bookmark Link Target) cap (Capitalization) i (Italics) bool kern - * (Kerning) kumimoji lang (Language ID) spc (Spacing) strike - * (Strikethrough) sz (Font Size) size u (Underline) underline style - * - * PLUS-- fill, line, blipFill, cs/ea/latin font (attribute: typeface) - * **** - */ - int w = 400, u = 0; - double h = 200; // default - boolean b = false, i = false; - String face = "Arial"; - if (textprops.get("b") != null) - b = ("1".equals((String) textprops.get("b"))); - if (textprops.get("i") != null) - i = ("1".equals((String) textprops.get("i"))); - if (textprops.get("latin_typeface") != null) - face = (String) textprops.get("latin_typeface"); - // if (textprops.get("u")!=null) - // u= textprops.get("u").toString(); - Object o = textprops.get("sz"); // Whole points are specified in - // increments of 100 starting with 100 - // being a point size of 1 - if (o != null) - h = Font.PointsToFontHeight(Integer.parseInt((String) o) / 100); - Font f = new Font(face, w, new Float(h).intValue()); - if (b) - f.setBold(true); - if (i) - f.setItalic(i); - if (u != 0) - f.setUnderlineStyle((byte) u); - o = textprops.get("vertAlign"); - if (o != null) { - String s = (String) o; - if (s.equals("baseline")) - f.setScript(0); - else if (s.equals("superscript")) - f.setScript(1); - else if (s.equals("subscript")) - f.setScript(2); - } - o = textprops.get("strike"); - if (o != null) - f.setStricken(true); - // f.setFontColor(cl); - return io.starter.OpenXLS.FormatHandle.addFont(f, wb); - } + /** + * concatenate the 3 levels of text properties and either find an existing + * font or add new + * + * @return + */ + public int getFontId(WorkBookHandle wb) { + HashMap textprops = new HashMap(); + if (bpr != null) { + /* + * noAutofit (No AutoFit) §5.1.5.1.2 normAutofit (Normal AutoFit) + * §5.1.5.1.3 prstTxWarp (Preset Text Warp) §5.1.11.19 scene3d (3D + * Scene Properties) §5.1.4.1.26 sp3d (Apply 3D shape properties) + * §5.1.7.12 spAutoFit (Shape AutoFit) + */ + // TODO: set any of the above?? + } + if (para != null) { + textprops = para.getTextProperties(); + } + /* + * altLang (Alternative Language) b (Bold) bool baseline (Baseline) bmk + * (Bookmark Link Target) cap (Capitalization) i (Italics) bool kern + * (Kerning) kumimoji lang (Language ID) spc (Spacing) strike + * (Strikethrough) sz (Font Size) size u (Underline) underline style + * + * PLUS-- fill, line, blipFill, cs/ea/latin font (attribute: typeface) + * **** + */ + int w = 400, u = 0; + double h = 200; // default + boolean b = false, i = false; + String face = "Arial"; + if (textprops.get("b") != null) + b = ("1".equals(textprops.get("b"))); + if (textprops.get("i") != null) + i = ("1".equals(textprops.get("i"))); + if (textprops.get("latin_typeface") != null) + face = (String) textprops.get("latin_typeface"); + // if (textprops.get("u")!=null) + // u= textprops.get("u").toString(); + Object o = textprops.get("sz"); // Whole points are specified in + // increments of 100 starting with 100 + // being a point size of 1 + if (o != null) + h = Font.PointsToFontHeight(Integer.parseInt((String) o) / 100); + Font f = new Font(face, w, new Float(h).intValue()); + if (b) + f.setBold(true); + if (i) + f.setItalic(i); + if (u != 0) + f.setUnderlineStyle((byte) u); + o = textprops.get("vertAlign"); + if (o != null) { + String s = (String) o; + if (s.equals("baseline")) + f.setScript(0); + else if (s.equals("superscript")) + f.setScript(1); + else if (s.equals("subscript")) + f.setScript(2); + } + o = textprops.get("strike"); + if (o != null) + f.setStricken(true); + // f.setFontColor(cl); + return io.starter.OpenXLS.FormatHandle.addFont(f, wb); + } } diff --git a/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.java b/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.java index e7679bb..52e2123 100644 --- a/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.java +++ b/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,435 +22,470 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; -import org.xmlpull.v1.XmlPullParser; import io.starter.OpenXLS.ColHandle; import io.starter.OpenXLS.RowHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * twoCellAnchor (Two Cell Anchor Shape Size) - * - * This element specifies a two cell anchor placeholder for a group, a shape, or a drawing element. - * It moves with cells and its extents are in EMU units. - * + *

        + * This element specifies a two cell anchor placeholder for a group, a shape, or a drawing element. + * It moves with cells and its extents are in EMU units. + *

        * This is the root element for charts, images and shapes. - * + *

        * parent: wsDr * children: from, to, OBJECTCHOICES (sp, grpSp, graphicFrame, cxnSp, pic), clientData - * */ //TODO: finish grpSp Group Shape // TODO: finish clientData element -public class TwoCellAnchor implements OOXMLElement{ - - private static final long serialVersionUID = 4180396678197959710L; - // EMU = pixel * 914400 / Resolution (96?) - public static final short EMU=1270; - private String editAs= null; - private String embedName= null; - private From from= null; - private To to= null; - private ObjectChoice o= null; - - public TwoCellAnchor(String editAs) { - this.editAs= editAs; - } - public TwoCellAnchor(String editAs, From f, To t, ObjectChoice o) { - this.editAs= editAs; - this.from= f; - this.to= t; - this.o= o; - } - public TwoCellAnchor(TwoCellAnchor tce) { - this.editAs= tce.editAs; - this.from= tce.from; - this.to= tce.to; - this.o= tce.o; - } - +public class TwoCellAnchor implements OOXMLElement { + + private static final long serialVersionUID = 4180396678197959710L; + // EMU = pixel * 914400 / Resolution (96?) + public static final short EMU = 1270; + private String editAs = null; + private String embedName = null; + private From from = null; + private To to = null; + private ObjectChoice o = null; + + public TwoCellAnchor(String editAs) { + this.editAs = editAs; + } + + public TwoCellAnchor(String editAs, From f, To t, ObjectChoice o) { + this.editAs = editAs; + this.from = f; + this.to = t; + this.o = o; + } + + public TwoCellAnchor(TwoCellAnchor tce) { + this.editAs = tce.editAs; + this.from = tce.from; + this.to = tce.to; + this.o = tce.o; + } + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String editAs= null; - From f= null; - To t= null; - ObjectChoice o= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("twoCellAnchor")) { // get attributes - if (xpp.getAttributeCount()>0) - editAs= xpp.getAttributeValue(0); - } else if (tnm.equals("from")) { - lastTag.push(tnm); - f= (From) From.parseOOXML(xpp, lastTag); - } else if (tnm.equals("to")) { - lastTag.push(tnm); - t= (To) To.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cxnSp") || - tnm.equals("graphicFrame") || - tnm.equals("grpSp") || - tnm.equals("pic") || - tnm.equals("sp")) { - o= (ObjectChoice) ObjectChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("twoCellAnchor")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("twoCellAnchor.parseOOXML: " + e.toString()); - } - TwoCellAnchor tca= new TwoCellAnchor(editAs, f, t, o); - return tca; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - if (from!=null) ooxml.append(from.getOOXML()); - if (to!=null) ooxml.append(to.getOOXML()); - ooxml.append(o.getOOXML()); - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TwoCellAnchor(this); - } - - // access methods ****** - /** - * return the (to, from) bounds of this object - * by concatenating the bounds for the to and the from - * bounds represent: COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, ROWOFFSET1 - * @return bounds short[8] from [4] and to [4] - */ - public int[] getBounds() { - int[] bounds= new int[8]; - System.arraycopy(from.getBounds(), 0, bounds, 0, 4); // from bounds - System.arraycopy(to.getBounds(), 0, bounds, 4, 4); // to bounds - return bounds; - } - - /** - * set the (to, from) bounds of this object - * bounds represent: COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, ROWOFFSET1 - * NOTE: COL - * @param bounds int[8] from [4] and to [4] - */ - public void setBounds(int[] bounds) { - int[] b= new int[4]; - System.arraycopy(bounds, 0, b, 0, 4); - if (from==null) from= new From(b); - else from.setBounds(b); - System.arraycopy(bounds, 4, b, 0, 4); - if (to==null) to= new To(b); - else to.setBounds(b); - } - - /** - * get cNvPr name attribute - * @return - */ - public String getName() { - if (o!=null) - return o.getName(); - return null; - } - - public String toString() { return this.getName(); } - - /** - * return if this twoCellAnchor element refers to an image rather than a chart or shape - * @return - */ - public boolean hasImage() { - return o.hasImage(); - } - - /** - * return if this twoCellAnchor element refers to a chart as opposed to a shape or image - * @return - */ - public boolean hasChart() { - if (o!=null) - return o.hasChart(); - return false; - } - - /** - * return if this twoCellAnchor element refers to a shape, as opposed a chart or an image - * @return - */ - public boolean hasShape() { - if (o!=null) - return o.hasShape(); - return false; - } - - /** - * set cNvPr name attribute - * @param name - */ - public void setName(String name) { - if (o!=null) - o.setName(name); - } - - /** - * get cNvPr descr attribute - * @return - */ - public String getDescr() { - if (o!=null) - return o.getDescr(); - return null; - } - /** - * set cNvPr descr attribute - * sometimes associated with shape name - * @param descr - */ - public void setDescr(String descr) { - if (o!=null) - o.setDescr(descr); - } - - /** - * get macro attribute - * @return - */ - public String getMacro() { - if (o!=null) - return o.getMacro(); - return null; - } - /** - * set Macro attribute - * sometimes associated with shape name - * @param descr - */ - public void setMacro(String macro) { - if (o!=null) - o.setMacro(macro); - } - - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (o!=null) - return o.getURI(); - return null; - } - - /** - * set the URI associated with this graphic data - * @param uri - */ - public void setURI(String uri) { - if (o!=null) - o.setURI(uri); - } - - /** - * return the rid for the chart defined by this twocellanchor - * @return - */ - public String getChartRId() { - if (o!=null) - return o.getChartRId(); - return null; - } - - public void setChartRId(String rId) { - if (o!=null) o.setChartRId(rId); - } - /** - * return the id for the embedded picture or shape (i.e. resides within the file) - * @return - */ - public String getEmbed() { - if (o!=null) - return o.getEmbed(); - return null; - } - - /** - * set the embed for the embedded picture or shape (i.e. resides within the file) - * @param embed - */ - public void setEmbed(String embed) { - if (o!=null) o.setEmbed(embed); - } - - - /** - * return the Embedded Object's filename as saved on disk - * @return - */ - public String getEmbedFilename() { - return embedName; - } - - /** - * set the Embedded Object's filename as saved on disk - * @param embed - */ - public void setEmbedFilename(String embedFile) { - this.embedName= embedFile; - } - - - /** - * return the id for the linked object (i.e. doesn't reside in the file) - * @return - */ - public String getLink() { - if (o!=null) - return o.getLink(); - return null; - } - - - /** - * set the link attribute for this blip (the id for the linked picture) - * @param embed - */ - public void setLink(String link) { - if (o!=null) o.setLink(link); - } - - /** - * return the editAs editMovement attribute - * @return - */ - public String getEditAs() { - return editAs; - } - - /** - * set the editAs editMovement attribute - * @return - */ - public void setEditAs(String editAs) { - this.editAs= editAs; - } - - /** utility to return the shape properties element (picture element only) - * should be depreciated when OOXML is completely distinct from BIFF8 - * @return - */ - public SpPr getSppr() { - if (o!=null) - return o.getSppr(); - return null; - } - - /** - * set this twoCellAnchor as a chart element - * used for - * @param rid - * @param name - * @param bounds - */ - public void setAsChart(int rid, String name, int[] bounds) { - o= new ObjectChoice(); - o.setObject(new GraphicFrame()); - o.setName(name); - o.setChartRId("rId" + Integer.valueOf(rid).toString()); - o.setId(rid); - this.setBounds(bounds); - // id??? - } - - /** - * set this twoCellAnchor as an image - * @param rid - * @param name - * @param id - */ - public void setAsImage(int rid, String name, String descr, int spid, SpPr sp) { - o= new ObjectChoice(); - o.setObject(new Pic()); - o.setName(name); - o.setDescr(descr); - o.setEmbed("rId" + Integer.valueOf(rid).toString()); - o.setId(spid); - if (sp!=null) { - ((Pic) o.getObject()).setSppr(sp); - } - } - - - /** - * given bounds[8] in BIFF 8 coordinates and convert to OOXML units - * mostly this means adjusting ROWOFFSET 0 & 1 (bounds[5] & bounds[7] - * and COLOFFSET 0 & 1 (bounds[1] & bounds[5] to EMU units + - * adjust according to emperical calc garnered from observation - * @param sheet - * @param bbounds short[] bounds in BIFF8 units - * @return new bounds int[] bounds in OOXML units - */ - public static int[] convertBoundsFromBIFF8(io.starter.formats.XLS.Boundsheet sheet, short[] bbounds) { - // note on bounds: - // -- offsets (bounds 1,3,5 & 7) are %'s of column width or row height, respectively - // below calculations are garnered from OOXML info + by comparing to Excel's values ... may not be 100% but appears to work for both 2003 and 2007 versions - // NOTE: if change offset algorithm here must modify algorithm in twoCellAnchor from and to classes - int[] bounds= new int[8]; - bounds[0]= bbounds[0]; - double cw= ColHandle.getWidth(sheet, bbounds[0]); ///5.0; + String editAs = null; + From f = null; + To t = null; + ObjectChoice o = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("twoCellAnchor")) { // get attributes + if (xpp.getAttributeCount() > 0) + editAs = xpp.getAttributeValue(0); + } else if (tnm.equals("from")) { + lastTag.push(tnm); + f = From.parseOOXML(xpp, lastTag); + } else if (tnm.equals("to")) { + lastTag.push(tnm); + t = To.parseOOXML(xpp, lastTag); + } else if (tnm.equals("cxnSp") || + tnm.equals("graphicFrame") || + tnm.equals("grpSp") || + tnm.equals("pic") || + tnm.equals("sp")) { + o = (ObjectChoice) ObjectChoice.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("twoCellAnchor")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("twoCellAnchor.parseOOXML: " + e.toString()); + } + TwoCellAnchor tca = new TwoCellAnchor(editAs, f, t, o); + return tca; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + if (from != null) ooxml.append(from.getOOXML()); + if (to != null) ooxml.append(to.getOOXML()); + ooxml.append(o.getOOXML()); + ooxml.append(""); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new TwoCellAnchor(this); + } + + // access methods ****** + + /** + * return the (to, from) bounds of this object + * by concatenating the bounds for the to and the from + * bounds represent: COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, ROWOFFSET1 + * + * @return bounds short[8] from [4] and to [4] + */ + public int[] getBounds() { + int[] bounds = new int[8]; + System.arraycopy(from.getBounds(), 0, bounds, 0, 4); // from bounds + System.arraycopy(to.getBounds(), 0, bounds, 4, 4); // to bounds + return bounds; + } + + /** + * set the (to, from) bounds of this object + * bounds represent: COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, ROWOFFSET1 + * NOTE: COL + * + * @param bounds int[8] from [4] and to [4] + */ + public void setBounds(int[] bounds) { + int[] b = new int[4]; + System.arraycopy(bounds, 0, b, 0, 4); + if (from == null) from = new From(b); + else from.setBounds(b); + System.arraycopy(bounds, 4, b, 0, 4); + if (to == null) to = new To(b); + else to.setBounds(b); + } + + /** + * get cNvPr name attribute + * + * @return + */ + public String getName() { + if (o != null) + return o.getName(); + return null; + } + + public String toString() { + return this.getName(); + } + + /** + * return if this twoCellAnchor element refers to an image rather than a chart or shape + * + * @return + */ + public boolean hasImage() { + return o.hasImage(); + } + + /** + * return if this twoCellAnchor element refers to a chart as opposed to a shape or image + * + * @return + */ + public boolean hasChart() { + if (o != null) + return o.hasChart(); + return false; + } + + /** + * return if this twoCellAnchor element refers to a shape, as opposed a chart or an image + * + * @return + */ + public boolean hasShape() { + if (o != null) + return o.hasShape(); + return false; + } + + /** + * set cNvPr name attribute + * + * @param name + */ + public void setName(String name) { + if (o != null) + o.setName(name); + } + + /** + * get cNvPr descr attribute + * + * @return + */ + public String getDescr() { + if (o != null) + return o.getDescr(); + return null; + } + + /** + * set cNvPr descr attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setDescr(String descr) { + if (o != null) + o.setDescr(descr); + } + + /** + * get macro attribute + * + * @return + */ + public String getMacro() { + if (o != null) + return o.getMacro(); + return null; + } + + /** + * set Macro attribute + * sometimes associated with shape name + * + * @param descr + */ + public void setMacro(String macro) { + if (o != null) + o.setMacro(macro); + } + + /** + * get the URI associated with this graphic Data + */ + public String getURI() { + if (o != null) + return o.getURI(); + return null; + } + + /** + * set the URI associated with this graphic data + * + * @param uri + */ + public void setURI(String uri) { + if (o != null) + o.setURI(uri); + } + + /** + * return the rid for the chart defined by this twocellanchor + * + * @return + */ + public String getChartRId() { + if (o != null) + return o.getChartRId(); + return null; + } + + public void setChartRId(String rId) { + if (o != null) o.setChartRId(rId); + } + + /** + * return the id for the embedded picture or shape (i.e. resides within the file) + * + * @return + */ + public String getEmbed() { + if (o != null) + return o.getEmbed(); + return null; + } + + /** + * set the embed for the embedded picture or shape (i.e. resides within the file) + * + * @param embed + */ + public void setEmbed(String embed) { + if (o != null) o.setEmbed(embed); + } + + + /** + * return the Embedded Object's filename as saved on disk + * + * @return + */ + public String getEmbedFilename() { + return embedName; + } + + /** + * set the Embedded Object's filename as saved on disk + * + * @param embed + */ + public void setEmbedFilename(String embedFile) { + this.embedName = embedFile; + } + + + /** + * return the id for the linked object (i.e. doesn't reside in the file) + * + * @return + */ + public String getLink() { + if (o != null) + return o.getLink(); + return null; + } + + + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + public void setLink(String link) { + if (o != null) o.setLink(link); + } + + /** + * return the editAs editMovement attribute + * + * @return + */ + public String getEditAs() { + return editAs; + } + + /** + * set the editAs editMovement attribute + * + * @return + */ + public void setEditAs(String editAs) { + this.editAs = editAs; + } + + /** + * utility to return the shape properties element (picture element only) + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + public SpPr getSppr() { + if (o != null) + return o.getSppr(); + return null; + } + + /** + * set this twoCellAnchor as a chart element + * used for + * + * @param rid + * @param name + * @param bounds + */ + public void setAsChart(int rid, String name, int[] bounds) { + o = new ObjectChoice(); + o.setObject(new GraphicFrame()); + o.setName(name); + o.setChartRId("rId" + Integer.valueOf(rid).toString()); + o.setId(rid); + this.setBounds(bounds); + // id??? + } + + /** + * set this twoCellAnchor as an image + * + * @param rid + * @param name + * @param id + */ + public void setAsImage(int rid, String name, String descr, int spid, SpPr sp) { + o = new ObjectChoice(); + o.setObject(new Pic()); + o.setName(name); + o.setDescr(descr); + o.setEmbed("rId" + Integer.valueOf(rid).toString()); + o.setId(spid); + if (sp != null) { + ((Pic) o.getObject()).setSppr(sp); + } + } + + + /** + * given bounds[8] in BIFF 8 coordinates and convert to OOXML units + * mostly this means adjusting ROWOFFSET 0 & 1 (bounds[5] & bounds[7] + * and COLOFFSET 0 & 1 (bounds[1] & bounds[5] to EMU units + + * adjust according to emperical calc garnered from observation + * + * @param sheet + * @param bbounds short[] bounds in BIFF8 units + * @return new bounds int[] bounds in OOXML units + */ + public static int[] convertBoundsFromBIFF8(io.starter.formats.XLS.Boundsheet sheet, short[] bbounds) { + // note on bounds: + // -- offsets (bounds 1,3,5 & 7) are %'s of column width or row height, respectively + // below calculations are garnered from OOXML info + by comparing to Excel's values ... may not be 100% but appears to work for both 2003 and 2007 versions + // NOTE: if change offset algorithm here must modify algorithm in twoCellAnchor from and to classes + int[] bounds = new int[8]; + bounds[0] = bbounds[0]; + double cw = ColHandle.getWidth(sheet, bbounds[0]); ///5.0; // below is more correct (???) bounds[1]= (int)(EMU*cw*(bbounds[1]/1024.0)); - bounds[1]= (int)(cw*256*(bbounds[1]/1024.0)); - double rh= RowHandle.getHeight(sheet, bbounds[2]); ///2.0; - bounds[2]= bbounds[2]; - bounds[3]= (int)(EMU*rh*(bbounds[3]/256.0)); - cw= ColHandle.getWidth(sheet, bbounds[4]); ///5.0; - bounds[4]= bbounds[4]; + bounds[1] = (int) (cw * 256 * (bbounds[1] / 1024.0)); + double rh = RowHandle.getHeight(sheet, bbounds[2]); ///2.0; + bounds[2] = bbounds[2]; + bounds[3] = (int) (EMU * rh * (bbounds[3] / 256.0)); + cw = ColHandle.getWidth(sheet, bbounds[4]); ///5.0; + bounds[4] = bbounds[4]; // bounds[5]= (int)(EMU*cw*(bbounds[5]/1024.0)); - bounds[5]= (int)(cw*256*(bbounds[5]/1024.0)); - rh= RowHandle.getHeight(sheet, bbounds[6]); ///2.0; - bounds[6]= bbounds[6]; - bounds[7]= (int)(EMU*rh*(bbounds[7]/256.0)); - return bounds; - } - - /** - * convert bounds[8] from OOXML to BIFF8 Units - * basically must adjust COLOFFSETs and ROWOFFSETs to BIFF8 units - * @param sheet - * @param bounds int[] - * @return bounds short[] (BIFF8 uses short[] + different units) - */ - public static short[] convertBoundsToBIFF8(io.starter.formats.XLS.Boundsheet sheet, int[] bounds) { - short[] bbounds= new short[8]; - bbounds[0]= (short) bounds[0]; - double cw= ColHandle.getWidth(sheet, bounds[0]); ///5.0; + bounds[5] = (int) (cw * 256 * (bbounds[5] / 1024.0)); + rh = RowHandle.getHeight(sheet, bbounds[6]); ///2.0; + bounds[6] = bbounds[6]; + bounds[7] = (int) (EMU * rh * (bbounds[7] / 256.0)); + return bounds; + } + + /** + * convert bounds[8] from OOXML to BIFF8 Units + * basically must adjust COLOFFSETs and ROWOFFSETs to BIFF8 units + * + * @param sheet + * @param bounds int[] + * @return bounds short[] (BIFF8 uses short[] + different units) + */ + public static short[] convertBoundsToBIFF8(io.starter.formats.XLS.Boundsheet sheet, int[] bounds) { + short[] bbounds = new short[8]; + bbounds[0] = (short) bounds[0]; + double cw = ColHandle.getWidth(sheet, bounds[0]); ///5.0; // below is more correct (???) bbounds[1]= (short)((bounds[1]*1024)/(TwoCellAnchor.EMU*cw)); - bbounds[1]= (short )((bounds[1]*1024.0)/(cw*256)); - bbounds[2]= (short) bounds[2]; - double rh= RowHandle.getHeight(sheet, bounds[2]); ///2.0; - bbounds[3]= (short) ((bounds[3]*256.0)/(TwoCellAnchor.EMU*rh)); - bbounds[4]= (short) bounds[4]; - cw= ColHandle.getWidth(sheet, bounds[4]); ///5.0; + bbounds[1] = (short) ((bounds[1] * 1024.0) / (cw * 256)); + bbounds[2] = (short) bounds[2]; + double rh = RowHandle.getHeight(sheet, bounds[2]); ///2.0; + bbounds[3] = (short) ((bounds[3] * 256.0) / (TwoCellAnchor.EMU * rh)); + bbounds[4] = (short) bounds[4]; + cw = ColHandle.getWidth(sheet, bounds[4]); ///5.0; // bbounds[5]= (short)((bounds[5]*1024)/(TwoCellAnchor.EMU*cw)); - bbounds[5]= (short )((bounds[5]*1024.0)/(cw*256)); - bbounds[6]= (short) bounds[6]; - rh= RowHandle.getHeight(sheet, bounds[6]); ///2.0; - bbounds[7]= (short) ((bounds[7]*256.0)/(TwoCellAnchor.EMU*rh)); - return bbounds; - } + bbounds[5] = (short) ((bounds[5] * 1024.0) / (cw * 256)); + bbounds[6] = (short) bounds[6]; + rh = RowHandle.getHeight(sheet, bounds[6]); ///2.0; + bbounds[7] = (short) ((bounds[7] * 256.0) / (TwoCellAnchor.EMU * rh)); + return bbounds; + } } /** @@ -458,83 +493,84 @@ public static short[] convertBoundsToBIFF8(io.starter.formats.XLS.Boundsheet she * This element specifies the first anchor point for the drawing element. This will be used to anchor the top and left * sides of the shape within the spreadsheet. That is when the cell that is specified in the from element is adjusted, * the shape will also be adjusted. - * + *

        * NOTE: Coordinates are in OOXML units; they are converted to BIFF8 units using twoCellAnchor.convertBoundsToBIFF8 * parent: oneCellAnchor, twoCellAnchor * children: col, colOff, row, rowOff - * - * */ class From implements OOXMLElement { - private static final long serialVersionUID = -4776435343244555855L; - private int[] bounds; - - - public From(int[] bounds) { - this.bounds= new int[4]; - System.arraycopy(bounds, 0, this.bounds, 0, 4); - } - public From(From f) { - this.bounds= f.bounds; - } - + private static final long serialVersionUID = -4776435343244555855L; + private int[] bounds; + + + public From(int[] bounds) { + this.bounds = new int[4]; + System.arraycopy(bounds, 0, this.bounds, 0, 4); + } + + public From(From f) { + this.bounds = f.bounds; + } + public static From parseOOXML(XmlPullParser xpp, Stack lastTag) { - int[] bounds= new int[4]; - int boundsidx= 0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("col")) { - bounds[boundsidx++]= Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("colOff")) { - bounds[boundsidx++]= Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("row")) { - bounds[boundsidx++]= Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("rowOff")) { - bounds[boundsidx++]= Integer.parseInt(xpp.nextText()); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("from")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("from.parseOOXML: " + e.toString()); - } - From f= new From(bounds); - return f; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - ooxml.append("" + bounds[0] + ""); - ooxml.append("" + bounds[1]+ ""); - ooxml.append("" + bounds[2] + ""); - ooxml.append("" + bounds[3]+ ""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new From(this); - } - public int[] getBounds() { - if (bounds==null)bounds= new int[4]; - return bounds; - } - public void setBounds(int[] bounds) { - System.arraycopy(bounds, 0, this.bounds, 0, 4); - } + int[] bounds = new int[4]; + int boundsidx = 0; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("col")) { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); + } else if (tnm.equals("colOff")) { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); + } else if (tnm.equals("row")) { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); + } else if (tnm.equals("rowOff")) { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("from")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("from.parseOOXML: " + e.toString()); + } + From f = new From(bounds); + return f; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + ooxml.append("" + bounds[0] + ""); + ooxml.append("" + bounds[1] + ""); + ooxml.append("" + bounds[2] + ""); + ooxml.append("" + bounds[3] + ""); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new From(this); + } + + public int[] getBounds() { + if (bounds == null) bounds = new int[4]; + return bounds; + } + + public void setBounds(int[] bounds) { + System.arraycopy(bounds, 0, this.bounds, 0, 4); + } } - + /** * to (Ending Anchor Point) * This element specifies the second anchor point for the drawing element. This will be used to anchor the bottom @@ -543,79 +579,82 @@ public void setBounds(int[] bounds) { * This element specifies the second anchor point for the drawing element. This will be used to anchor the bottom * and right sides of the shape within the spreadsheet. That is when the cell that is specified in the to element is * adjusted, the shape will also be adjusted. - * + *

        * NOTE: Coordinates are in OOXML units; they are converted to BIFF8 units using twoCellAnchor.convertBoundsToBIFF8 * parent: twoCellAnchor * children: col, colOff, row, rowOff - * */ -class To implements OOXMLElement{ - - private static final long serialVersionUID = 1500243445505400113L; - private int[] bounds; - - public To(int[] bounds) { - this.bounds= new int[4]; - System.arraycopy(bounds, 0, this.bounds, 0, 4); - } - public To(To f) { - this.bounds= f.bounds; - } - +class To implements OOXMLElement { + + private static final long serialVersionUID = 1500243445505400113L; + private int[] bounds; + + public To(int[] bounds) { + this.bounds = new int[4]; + System.arraycopy(bounds, 0, this.bounds, 0, 4); + } + + public To(To f) { + this.bounds = f.bounds; + } + public static To parseOOXML(XmlPullParser xpp, Stack lastTag) { - int[] bounds= new int[4]; - int boundsidx= 0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("col")) { - bounds[boundsidx++]= Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("colOff")) { - bounds[boundsidx++]= Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("row")) { - bounds[boundsidx++]= Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("rowOff")) { - bounds[boundsidx++]= Integer.parseInt(xpp.nextText()); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("to")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("to.parseOOXML: " + e.toString()); - } - To f= new To(bounds); - return f; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - ooxml.append("" + bounds[0] + ""); - ooxml.append("" + bounds[1] + ""); - ooxml.append("" + bounds[2] + ""); - ooxml.append("" + bounds[3] + ""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new To(this); - } - // Access Methods - public int[] getBounds() { - if (this.bounds==null) this.bounds= new int[4]; - return bounds; } - public void setBounds(int[] bounds) { - this.bounds= new int[4]; - System.arraycopy(bounds, 0, this.bounds, 0, 4); - } + int[] bounds = new int[4]; + int boundsidx = 0; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("col")) { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); + } else if (tnm.equals("colOff")) { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); + } else if (tnm.equals("row")) { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); + } else if (tnm.equals("rowOff")) { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("to")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("to.parseOOXML: " + e.toString()); + } + To f = new To(bounds); + return f; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + ooxml.append("" + bounds[0] + ""); + ooxml.append("" + bounds[1] + ""); + ooxml.append("" + bounds[2] + ""); + ooxml.append("" + bounds[3] + ""); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new To(this); + } + + // Access Methods + public int[] getBounds() { + if (this.bounds == null) this.bounds = new int[4]; + return bounds; + } + + public void setBounds(int[] bounds) { + this.bounds = new int[4]; + System.arraycopy(bounds, 0, this.bounds, 0, 4); + } } diff --git a/src/main/java/io/starter/formats/OOXML/TxBody.java b/src/main/java/io/starter/formats/OOXML/TxBody.java index 1a5ae36..e695bfc 100644 --- a/src/main/java/io/starter/formats/OOXML/TxBody.java +++ b/src/main/java/io/starter/formats/OOXML/TxBody.java @@ -2,102 +2,102 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.OOXML; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; import io.starter.OpenXLS.WorkBookHandle; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * txBody (Shape Text Body) - * + *

        * OOXML/DrawingML element specifies the existence of text to be contained within the corresponding shape. All visible text and * visible text related properties are contained within this element. There can be multiple paragraphs and within * paragraphs multiple runs of text - * + *

        * parent: sp (shape) * children: bodyPr REQ, lstStyle, p REQ */ // TODO: handle lstStyle Text List Styles -public class TxBody implements OOXMLElement{ +public class TxBody implements OOXMLElement { + + private static final long serialVersionUID = 2407194628070113668L; + private BodyPr bPr; + private P para; + + public TxBody(BodyPr b, P para) { + this.bPr = b; + this.para = para; + } + + public TxBody(TxBody tbd) { + this.bPr = tbd.bPr; + this.para = tbd.para; + } + + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + BodyPr b = null; + P para = null; + try { // need: endParaRPr? + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("bodyPr")) { // default text properties + lastTag.push(tnm); + b = (BodyPr) BodyPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("p")) { // part of p element + lastTag.push(tnm); + para = (P) P.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("txBody")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("txBody.parseOOXML: " + e.toString()); + } + TxBody tBd = new TxBody(b, para); + return tBd; + } - private static final long serialVersionUID = 2407194628070113668L; - private BodyPr bPr; - private P para; - - public TxBody(BodyPr b, P para) { - this.bPr= b; - this.para= para; - } - - public TxBody(TxBody tbd) { - this.bPr= tbd.bPr; - this.para= tbd.para; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - BodyPr b= null; - P para= null; - try { // need: endParaRPr? - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bodyPr")) { // default text properties - lastTag.push(tnm); - b= (BodyPr) BodyPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("p")) { // part of p element - lastTag.push(tnm); - para= (P) P.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("txBody")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("txBody.parseOOXML: " + e.toString()); - } - TxBody tBd= new TxBody(b, para); - return tBd; - } - - public String getOOXML() { - StringBuffer tooxml= new StringBuffer(); - tooxml.append(""); - if (bPr!=null) tooxml.append(bPr.getOOXML()); - tooxml.append(""); // TODO: HANDLE - if (para!=null) tooxml.append(para.getOOXML()); - tooxml.append(""); - return tooxml.toString(); - } + public String getOOXML() { + StringBuffer tooxml = new StringBuffer(); + tooxml.append(""); + if (bPr != null) tooxml.append(bPr.getOOXML()); + tooxml.append(""); // TODO: HANDLE + if (para != null) tooxml.append(para.getOOXML()); + tooxml.append(""); + return tooxml.toString(); + } - public OOXMLElement cloneElement() { - return new TxBody(this); - } - } + public OOXMLElement cloneElement() { + return new TxBody(this); + } +} diff --git a/src/main/java/io/starter/formats/OOXML/TxPr.java b/src/main/java/io/starter/formats/OOXML/TxPr.java index 42807e8..327b859 100644 --- a/src/main/java/io/starter/formats/OOXML/TxPr.java +++ b/src/main/java/io/starter/formats/OOXML/TxPr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,103 +22,103 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.Font; import io.starter.formats.XLS.FormatConstants; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * txpr (Text Properties) - * + *

        * OOXML/DrawingML element specifies text formatting. The lstStyle element is not supported - * + *

        * parent: axes, title, labels .. * children: bodyPr, lstStyle, p - * - * */ // TODO: Handle lstStyle -public class TxPr implements OOXMLElement{ +public class TxPr implements OOXMLElement { private static final long serialVersionUID = -4293247897525807479L; - // TODO: handle lstStyle Text List Styles - private BodyPr bPr; - private P para; - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - BodyPr b= null; - P para= null; - try { // need: endParaRPr? - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bodyPr")) { // default text properties - lastTag.push(tnm); - b= (BodyPr) BodyPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("p")) { // part of p element - lastTag.push(tnm); - para= (P) P.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("txPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("txPr.parseOOXML: " + e.toString()); - } - TxPr tPr= new TxPr(b, para); - return tPr; - } - - public TxPr(BodyPr b, P para) { - this.bPr= b; - this.para= para; - } - public TxPr(TxPr tpr) { - this.bPr= tpr.bPr; - this.para= tpr.para; - } - - public String getOOXML() { - StringBuffer tooxml= new StringBuffer(); - tooxml.append(""); - if (bPr!=null) tooxml.append(bPr.getOOXML()); - tooxml.append(""); // TODO: HANDLE - if (para!=null) tooxml.append(para.getOOXML()); - tooxml.append(""); - return tooxml.toString(); - } + // TODO: handle lstStyle Text List Styles + private BodyPr bPr; + private P para; + + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + BodyPr b = null; + P para = null; + try { // need: endParaRPr? + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("bodyPr")) { // default text properties + lastTag.push(tnm); + b = (BodyPr) BodyPr.parseOOXML(xpp, lastTag); + } else if (tnm.equals("p")) { // part of p element + lastTag.push(tnm); + para = (P) P.parseOOXML(xpp, lastTag, bk); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("txPr")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("txPr.parseOOXML: " + e.toString()); + } + TxPr tPr = new TxPr(b, para); + return tPr; + } + + public TxPr(BodyPr b, P para) { + this.bPr = b; + this.para = para; + } + + public TxPr(TxPr tpr) { + this.bPr = tpr.bPr; + this.para = tpr.para; + } + + public String getOOXML() { + StringBuffer tooxml = new StringBuffer(); + tooxml.append(""); + if (bPr != null) tooxml.append(bPr.getOOXML()); + tooxml.append(""); // TODO: HANDLE + if (para != null) tooxml.append(para.getOOXML()); + tooxml.append(""); + return tooxml.toString(); + } - public OOXMLElement cloneElement() { - return new TxPr(this); - } - - /** - * specify text formatting properties - * @param fontFace font face - * @param sz size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) - * @param b true if bold - * @param i true if italic - * @param u underline: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted - * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) - * @param strike one of: dblStrike, noStrike or sngStrike or null if none - * @param clr fill color in hex form without the # - */ - public TxPr(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { - this.para= new P(fontFace, sz, b, i, u, strike, clr); - } - - public TxPr(Font fx, int hrot, String vrot) { + public OOXMLElement cloneElement() { + return new TxPr(this); + } + + /** + * specify text formatting properties + * + * @param fontFace font face + * @param sz size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) + * @param b true if bold + * @param i true if italic + * @param u underline: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted + * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) + * @param strike one of: dblStrike, noStrike or sngStrike or null if none + * @param clr fill color in hex form without the # + */ + public TxPr(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { + this.para = new P(fontFace, sz, b, i, u, strike, clr); + } + + public TxPr(Font fx, int hrot, String vrot) { /* * Specifies the rotation that is being applied to the text within the bounding box. If it not specified, the rotation of the accompanying shape is used. If it is specified, then this is @@ -130,31 +130,31 @@ public TxPr(Font fx, int hrot, String vrot) { 5400000=90 degrees -5400000=90 degrees counter-clockwise */ - int u= fx.getUnderlineStyle(); - String usty= "none"; - switch (u) { - case FormatConstants.STYLE_UNDERLINE_SINGLE: - usty= "sng"; - break; - case FormatConstants.STYLE_UNDERLINE_DOUBLE: - usty= "dbl"; - break; - case FormatConstants.STYLE_UNDERLINE_SINGLE_ACCTG: - usty= "sng"; - break; - case FormatConstants.STYLE_UNDERLINE_DOUBLE_ACCTG: - usty= "dbl"; - break; - } - String strike= (fx.getStricken()?"sngStrike":"noStrike"); - bPr= new BodyPr(hrot, "horz"); - this.para= new P(fx.getFontName(), (int) fx.getFontHeightInPoints()*100, fx.getBold(), fx.getItalic(), usty, strike, - fx.getColorAsOOXMLRBG()); -/* - * - * - * - * - */ - } + int u = fx.getUnderlineStyle(); + String usty = "none"; + switch (u) { + case FormatConstants.STYLE_UNDERLINE_SINGLE: + usty = "sng"; + break; + case FormatConstants.STYLE_UNDERLINE_DOUBLE: + usty = "dbl"; + break; + case FormatConstants.STYLE_UNDERLINE_SINGLE_ACCTG: + usty = "sng"; + break; + case FormatConstants.STYLE_UNDERLINE_DOUBLE_ACCTG: + usty = "dbl"; + break; + } + String strike = (fx.getStricken() ? "sngStrike" : "noStrike"); + bPr = new BodyPr(hrot, "horz"); + this.para = new P(fx.getFontName(), (int) fx.getFontHeightInPoints() * 100, fx.getBold(), fx.getItalic(), usty, strike, + fx.getColorAsOOXMLRBG()); + /* + * + * + * + * + */ + } } diff --git a/src/main/java/io/starter/formats/OOXML/Xfrm.java b/src/main/java/io/starter/formats/OOXML/Xfrm.java index 06d70ec..14e4239 100644 --- a/src/main/java/io/starter/formats/OOXML/Xfrm.java +++ b/src/main/java/io/starter/formats/OOXML/Xfrm.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,178 +22,186 @@ */ package io.starter.formats.OOXML; -import java.util.Stack; - +import io.starter.toolkit.Logger; import org.xmlpull.v1.XmlPullParser; + import java.util.HashMap; import java.util.Iterator; +import java.util.Stack; -import io.starter.toolkit.Logger; +class Xfrm implements OOXMLElement { + + private static final long serialVersionUID = 5383438744617393878L; + private HashMap attrs = null; + private Off o = null; + private Ext e = null; + private String ns = null; + + public Xfrm() { + this.ns = "xdr"; // set default + } + + public Xfrm(HashMap attrs, Off o, Ext e, String ns) { + this.attrs = attrs; + this.o = o; + this.e = e; + this.ns = ns; + } + + public Xfrm(Xfrm x) { + this.attrs = x.attrs; + this.o = x.o; + this.e = x.e; + this.ns = x.ns; + } + + /** + * set the namespace for xfrm element + * xdr (graphicFrame) or a(spPr) + * + * @param ns + */ + public void setNS(String ns) { + this.ns = ns; + } -class Xfrm implements OOXMLElement{ - - private static final long serialVersionUID = 5383438744617393878L; - private HashMap attrs= null; - private Off o= null; - private Ext e= null; - private String ns= null; - public Xfrm() { - this.ns= "xdr"; // set default - } - public Xfrm(HashMap attrs, Off o, Ext e, String ns) { - this.attrs= attrs; - this.o= o; - this.e= e; - this.ns= ns; - } - public Xfrm(Xfrm x) { - this.attrs= x.attrs; - this.o= x.o; - this.e= x.e; - this.ns= x.ns; - } - - /** set the namespace for xfrm element - * xdr (graphicFrame) or a(spPr) - * @param ns - */ - public void setNS(String ns) { this.ns= ns; } - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - Off o= null; - Ext e= null; - String ns= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("xfrm")) { // get attributes - ns= xpp.getPrefix(); - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("off")) { - lastTag.push(tnm); - o= (Off) Off.parseOOXML(xpp, lastTag); - //o.setNS("a"); - } else if (tnm.equals("ext")) { - lastTag.push(tnm); - e= (Ext) Ext.parseOOXML(xpp, lastTag); - //e.setNS("a"); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("xfrm")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception ex) { - Logger.logErr("xfrm.parseOOXML: " + ex.toString()); - } - Xfrm x= new Xfrm(attrs, o, e, ns); - return x; + HashMap attrs = new HashMap(); + Off o = null; + Ext e = null; + String ns = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("xfrm")) { // get attributes + ns = xpp.getPrefix(); + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } else if (tnm.equals("off")) { + lastTag.push(tnm); + o = Off.parseOOXML(xpp, lastTag); + //o.setNS("a"); + } else if (tnm.equals("ext")) { + lastTag.push(tnm); + e = (Ext) Ext.parseOOXML(xpp, lastTag); + //e.setNS("a"); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("xfrm")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception ex) { + Logger.logErr("xfrm.parseOOXML: " + ex.toString()); + } + Xfrm x = new Xfrm(attrs, o, e, ns); + return x; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append("<" + this.ns + ":xfrm"); + // attributes + if (attrs != null) { + Iterator i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + } + ooxml.append(">"); + if (o != null) ooxml.append(o.getOOXML()); + if (e != null) ooxml.append(e.getOOXML()); + ooxml.append(""); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Xfrm(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append("<" + this.ns + ":xfrm"); - // attributes - if (attrs!=null) { - Iterator i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (o!=null) ooxml.append(o.getOOXML()); - if (e!=null) ooxml.append(e.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Xfrm(this); - } } -class Off implements OOXMLElement{ +class Off implements OOXMLElement { - private static final long serialVersionUID = -7624630398053353694L; - private HashMap attrs= null; - private String ns= ""; + private static final long serialVersionUID = -7624630398053353694L; + private HashMap attrs = null; + private String ns = ""; + + public Off() { + attrs = new HashMap(); + attrs.put("x", "0"); + attrs.put("y", "0"); + } - public Off() { - attrs= new HashMap(); - attrs.put("x", new String("0")); - attrs.put("y", new String("0")); - } - public Off(HashMap attrs, String ns) { - this.attrs= attrs; - this.ns= ns; - } - + this.attrs = attrs; + this.ns = ns; + } + public Off(Off o) { - this.attrs= o.attrs; - this.ns= o.ns; - } - - public void setNS(String ns) { this.ns= ns; } - + this.attrs = o.attrs; + this.ns = o.ns; + } + + public void setNS(String ns) { + this.ns = ns; + } + public static Off parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs= new HashMap(); - String ns= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("off")) { // get attributes - ns= xpp.getPrefix(); - for (int i= 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("off")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("off.parseOOXML: " + e.toString()); - } - Off o= new Off(attrs, ns); - return o; + HashMap attrs = new HashMap(); + String ns = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("off")) { // get attributes + ns = xpp.getPrefix(); + for (int i = 0; i < xpp.getAttributeCount(); i++) { + attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("off")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("off.parseOOXML: " + e.toString()); + } + Off o = new Off(attrs, ns); + return o; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + ooxml.append("<" + this.ns + ":off"); + // attributes + Iterator i = attrs.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + String val = attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Off(this); } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append("<" + this.ns + ":off"); - // attributes - Iterator i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Off(this); - } } diff --git a/src/main/java/io/starter/formats/XLS/Array.java b/src/main/java/io/starter/formats/XLS/Array.java index 2dd1eaf..3284fa7 100644 --- a/src/main/java/io/starter/formats/XLS/Array.java +++ b/src/main/java/io/starter/formats/XLS/Array.java @@ -2,65 +2,64 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS; -import java.util.Stack; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; + import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.formulas.*; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.Logger; + +import java.util.Stack; /** * The Array class describes a formula that was Array-entered into a range of cells. - * + *

        * The range of Cells in which the array is entered is defined by the rwFirst, last, colFirst and last fields. - * + *

        * The Array record occurs directly after the Formula record for the cell in the upper-left corner of the array -- that is, the cell * defined by the rwFirst and colFirst fields. - * + *

        * The parsed expression is the array formula -- consisting of Ptgs. - * + *

        * You should ignore the chn field when you read the file, it must be 0 if written. - * - * + *

        + *

        * OFFSET NAME SIZE CONTENTS * ----- * 4 rwFirst 2 FirstRow of the array * 6 fwLast 2 Last Row of the array - * 8 colFirst 1 First Column of the array + * 8 colFirst 1 First Column of the array * 9 colLast 1 Last Column of the array * 10 grbit 2 Option Flags * 12 chn 4 set to 0, ignore * 16 cce 2 Length of the parsed expression * 18 rgce var Parsed Expression - * + *

        * grbit fields * bit 1 = fAlwaysCalc - always calc the formula * bit 2 = fCalcOnLoad - calc formula on load - * - */ -public final class Array extends io.starter.formats.XLS.XLSRecord -{ - +public final class Array extends io.starter.formats.XLS.XLSRecord { + private static final long serialVersionUID = -7316545663448065447L; private short rwFirst; private short rwLast; @@ -69,253 +68,271 @@ public final class Array extends io.starter.formats.XLS.XLSRecord private short cce; private short grbit; private byte[] rgce; - private Formula parentRec= null; - - public int getFirstRow(){ - return (int)rwFirst; + private Formula parentRec = null; + + public int getFirstRow() { + return (int) rwFirst; } - public int getLastRow(){ - return (int)rwLast; + + public int getLastRow() { + return (int) rwLast; } - public int getFirstCol(){ - return (int)colFirst; + + public int getFirstCol() { + return (int) colFirst; } - public int getLastCol(){ - return (int)colLast; + + public int getLastCol() { + return (int) colLast; } - + /* * For getRow() and getCol() we are going to return the upper-right hand * location of the sharedformula. This should be the same location referenced * in the PTGExp associated with these formulas as well */ - public int getRowNumber(){ - return getFirstRow(); + public int getRowNumber() { + return getFirstRow(); } - public int getCol(){ - return getFirstCol(); + + public int getCol() { + return getFirstCol(); } - public void setFirstRow(int i){ + + public void setFirstRow(int i) { rwFirst = (short) i; } - public void setLastRow(int i){ + + public void setLastRow(int i) { rwLast = (short) i; } - public void setFirstCol(int i){ - colFirst = (byte) i; + + public void setFirstCol(int i) { + colFirst = (byte) i; } - public void setLastCol(int i){ - colLast = (byte) i; + + public void setLastCol(int i) { + colLast = (byte) i; } - private Stack expression; - - public void init(){ + private Stack expression; + + public void init() { super.init(); - this.setOpcode(ARRAY); + this.setOpcode(ARRAY); // NO NOT TRUE!!!!! super.setIsValueForCell(true); // this ensures that it gets picked up by the Formula... // will get "picked up" by formula due to WorkBook.addRecord code // isValueForCell will DELETE THE CELL if remove/change NOT WHAT WE WANT HERE - rwFirst= (short) io.starter.toolkit.ByteTools.readInt(this.getByteAt(0),this.getByteAt(1), (byte)0, (byte)0); - rwLast= (short) io.starter.toolkit.ByteTools.readInt(this.getByteAt(2),this.getByteAt(3), (byte)0, (byte)0); - - colFirst = (short)ByteTools.readUnsignedShort(this.getByteAt(4), (byte) 0); - colLast = (short)ByteTools.readUnsignedShort(this.getByteAt(5), (byte) 0); - grbit= ByteTools.readShort(this.getByteAt(6),this.getByteAt(7)); - cce = ByteTools.readShort(this.getByteAt(12),this.getByteAt(13)); - rgce = this.getBytesAt(14,this.cce); - - expression = ExpressionParser.parseExpression(rgce,this); - - // for PtgArray and PtgMemAreas, have Constant Array Tokens following expression - // this length is NOT included in cce - int posExtraData= cce+14; - int len= this.getData().length; - for (int i= 0; i < expression.size(); i++) { - if (expression.get(i) instanceof PtgArray) { - try { - byte[] b= new byte[(len-posExtraData)]; - System.arraycopy(this.getData(), posExtraData, b, 0, len-posExtraData); - PtgArray pa= (PtgArray) expression.get(i); - posExtraData+=pa.setArrVals(b); - } catch (Exception e) { - Logger.logErr("Array: error getting Constants " + e.getLocalizedMessage()); - } - } else if (expression.get(i) instanceof PtgMemArea) { - try { - PtgMemArea pm= (PtgMemArea) expression.get(i); - byte[] b= new byte[pm.getnTokens()+8]; - System.arraycopy(pm.getRecord(), 0, b, 0, 7); - System.arraycopy(this.getData(), posExtraData, b, 7, pm.getnTokens()); - pm.init(b); - posExtraData+=pm.getnTokens(); - } catch (Exception e) { - Logger.logErr("Array: error getting memarea constants " + e.toString()); - } - } - } - if(DEBUGLEVEL > DEBUG_LOW) - Logger.logInfo("Array encountered at: " + this.wkbook.getLastbound().getSheetName() + "!" + ExcelTools.getAlphaVal(colFirst)+(rwFirst+1) + ":" + ExcelTools.getAlphaVal(colLast)+(rwLast+1)); + rwFirst = (short) io.starter.toolkit.ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), (byte) 0, (byte) 0); + rwLast = (short) io.starter.toolkit.ByteTools.readInt(this.getByteAt(2), this.getByteAt(3), (byte) 0, (byte) 0); + + colFirst = (short) ByteTools.readUnsignedShort(this.getByteAt(4), (byte) 0); + colLast = (short) ByteTools.readUnsignedShort(this.getByteAt(5), (byte) 0); + grbit = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + cce = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + rgce = this.getBytesAt(14, this.cce); + + expression = ExpressionParser.parseExpression(rgce, this); + + // for PtgArray and PtgMemAreas, have Constant Array Tokens following expression + // this length is NOT included in cce + int posExtraData = cce + 14; + int len = this.getData().length; + for (int i = 0; i < expression.size(); i++) { + if (expression.get(i) instanceof PtgArray) { + try { + byte[] b = new byte[(len - posExtraData)]; + System.arraycopy(this.getData(), posExtraData, b, 0, len - posExtraData); + PtgArray pa = (PtgArray) expression.get(i); + posExtraData += pa.setArrVals(b); + } catch (Exception e) { + Logger.logErr("Array: error getting Constants " + e.getLocalizedMessage()); + } + } else if (expression.get(i) instanceof PtgMemArea) { + try { + PtgMemArea pm = (PtgMemArea) expression.get(i); + byte[] b = new byte[pm.getnTokens() + 8]; + System.arraycopy(pm.getRecord(), 0, b, 0, 7); + System.arraycopy(this.getData(), posExtraData, b, 7, pm.getnTokens()); + pm.init(b); + posExtraData += pm.getnTokens(); + } catch (Exception e) { + Logger.logErr("Array: error getting memarea constants " + e.toString()); + } + } + } + if (DEBUGLEVEL > DEBUG_LOW) + Logger.logInfo("Array encountered at: " + this.wkbook.getLastbound().getSheetName() + "!" + ExcelTools.getAlphaVal(colFirst) + (rwFirst + 1) + ":" + ExcelTools.getAlphaVal(colLast) + (rwLast + 1)); } - - /** Associate this record with a worksheet. - * init array refs as well + + /** + * Associate this record with a worksheet. + * init array refs as well */ - public void setSheet(Sheet b){ - this.worksheet = b; - // add to array formula references since this is the parent - if (expression!=null) { // it's been initted - String loc = ExcelTools.formatLocation(new int[] {rwFirst, colFirst}); // this formula address == array formula references for OOXML usage - String ref= ExcelTools.formatRangeRowCol(new int[] {rwFirst, colFirst, rwLast, colLast}); - ((Boundsheet) b).addParentArrayRef(loc, ref); // formula address, array formula references OOXML usage - } - + public void setSheet(Sheet b) { + this.worksheet = b; + // add to array formula references since this is the parent + if (expression != null) { // it's been initted + String loc = ExcelTools.formatLocation(new int[]{rwFirst, colFirst}); // this formula address == array formula references for OOXML usage + String ref = ExcelTools.formatRangeRowCol(new int[]{rwFirst, colFirst, rwLast, colLast}); + ((Boundsheet) b).addParentArrayRef(loc, ref); // formula address, array formula references OOXML usage + } + } - + /** * init Array record from formula string + * * @param fmla */ - public void init(String fmla, int rw, int col) { + public void init(String fmla, int rw, int col) { // NO NOT TRUE!!!!! super.setIsValueForCell(true); // this ensures that it gets picked up by the Formula... // will get "picked up" by formula due to WorkBook.addRecord code // isValueForCell will DELETE THE CELL if remove/change NOT WHAT WE WANT HERE // TODO: ever a case of rwFirst!=rwLast, colFirst!=colLast ????? - this.setOpcode(ARRAY); - rwFirst = (short) rw; + this.setOpcode(ARRAY); + rwFirst = (short) rw; rwLast = (short) rw; colFirst = (byte) col; colLast = (byte) col; - grbit= 0x2; // calc on load = default 20090824 KSC [BugTracker 2683] - fmla= fmla.substring(1, fmla.length()-1); // parse formula string and add stack to Array record - Stack newptgs = FormulaParser.getPtgsFromFormulaString(this, fmla); - expression= newptgs; - this.updateRecord(); - + grbit = 0x2; // calc on load = default 20090824 KSC [BugTracker 2683] + fmla = fmla.substring(1, fmla.length() - 1); // parse formula string and add stack to Array record + Stack newptgs = FormulaParser.getPtgsFromFormulaString(this, fmla); + expression = newptgs; + this.updateRecord(); + } - + /* Update the record byte array with the modified ptg records */ - public void updateRecord(){ - // first, get expression length - cce = 0; // sum up expression (Ptg) records - for (int i = 0; i< expression.size();i++){ - Ptg ptg = (Ptg)expression.elementAt(i); - cce+= ptg.getRecord().length; - if(ptg instanceof PtgRef) { - ((PtgRef)ptg).setArrayTypeRef(); - } - } - - byte[] newdata = new byte[14+cce]; // total record data (not including extra data, if any) - - int pos= 0; - // 20090824 KSC: [BugTracker 2683] - // use setOpcode rather than setting 1st byte as it's a record not a ptg newdata[0]= (byte) XLSConstants.ARRAY; pos++; - this.setOpcode(ARRAY); - System.arraycopy(ByteTools.shortToLEBytes(rwFirst), 0, newdata, pos, 2); - pos+=2; - System.arraycopy(ByteTools.shortToLEBytes(rwLast), 0, newdata, pos, 2); - pos+=2; - newdata[pos++]= (byte) colFirst; - newdata[pos++]= (byte) colLast; - System.arraycopy(ByteTools.shortToLEBytes(grbit), 0, newdata, pos, 2); // 20090824 KSC: Added [BugTracker 2683] - - pos= 12; - System.arraycopy(ByteTools.shortToLEBytes(cce), 0, newdata, pos, 2); - pos+=2; - - // expression - rgce= new byte[cce]; // expression record data - pos= 0; - byte[] arraybytes = new byte[0]; - for (int i = 0; i< expression.size();i++){ + public void updateRecord() { + // first, get expression length + cce = 0; // sum up expression (Ptg) records + for (int i = 0; i < expression.size(); i++) { + Ptg ptg = (Ptg) expression.elementAt(i); + cce += ptg.getRecord().length; + if (ptg instanceof PtgRef) { + ((PtgRef) ptg).setArrayTypeRef(); + } + } + + byte[] newdata = new byte[14 + cce]; // total record data (not including extra data, if any) + + int pos = 0; + // 20090824 KSC: [BugTracker 2683] + // use setOpcode rather than setting 1st byte as it's a record not a ptg newdata[0]= (byte) XLSConstants.ARRAY; pos++; + this.setOpcode(ARRAY); + System.arraycopy(ByteTools.shortToLEBytes(rwFirst), 0, newdata, pos, 2); + pos += 2; + System.arraycopy(ByteTools.shortToLEBytes(rwLast), 0, newdata, pos, 2); + pos += 2; + newdata[pos++] = (byte) colFirst; + newdata[pos++] = (byte) colLast; + System.arraycopy(ByteTools.shortToLEBytes(grbit), 0, newdata, pos, 2); // 20090824 KSC: Added [BugTracker 2683] + + pos = 12; + System.arraycopy(ByteTools.shortToLEBytes(cce), 0, newdata, pos, 2); + pos += 2; + + // expression + rgce = new byte[cce]; // expression record data + pos = 0; + byte[] arraybytes = new byte[0]; + for (int i = 0; i < expression.size(); i++) { Ptg ptg = (Ptg) expression.elementAt(i); // trap extra data after expression (not included in cce count) - if (ptg instanceof PtgArray){ - PtgArray pa = (PtgArray)ptg; - arraybytes = ByteTools.append(pa.getPostRecord(), arraybytes); - }else if (ptg instanceof PtgMemArea){ - PtgMemArea pm = (PtgMemArea)ptg; - arraybytes = ByteTools.append(pm.getPostRecord(), arraybytes); - } - byte[] b = ptg.getRecord(); + if (ptg instanceof PtgArray) { + PtgArray pa = (PtgArray) ptg; + arraybytes = ByteTools.append(pa.getPostRecord(), arraybytes); + } else if (ptg instanceof PtgMemArea) { + PtgMemArea pm = (PtgMemArea) ptg; + arraybytes = ByteTools.append(pm.getPostRecord(), arraybytes); + } + byte[] b = ptg.getRecord(); System.arraycopy(b, 0, rgce, pos, ptg.getLength()); - pos= pos+ ptg.getLength(); + pos = pos + ptg.getLength(); } System.arraycopy(rgce, 0, newdata, 14, cce); - newdata= ByteTools.append(arraybytes, newdata); - this.setData(newdata); - } - + newdata = ByteTools.append(arraybytes, newdata); + this.setData(newdata); + } + /** * Returns the top left location of the array, used for identifying which array goes with what formula. */ - public String getParentLocation(){ - int[] in = new int[2]; - in[0] = colFirst; - in[1] = rwFirst; - String s = ExcelTools.formatLocation(in); - return s; + public String getParentLocation() { + int[] in = new int[2]; + in[0] = colFirst; + in[1] = rwFirst; + String s = ExcelTools.formatLocation(in); + return s; } /** - * Determines whether the address is part of the array Formula range + * Determines whether the address is part of the array Formula range */ - boolean isInRange(String addr){ + boolean isInRange(String addr) { return io.starter.OpenXLS.ExcelTools.isInRange(addr, rwFirst, rwLast, colFirst, colLast); } - - - public Object getValue(PtgExp pxp){ - //try{ - return FormulaCalculator.calculateFormula(expression); - //}catch(FunctionNotSupportedException e){ - //Logger.logWarn("Array.getValue() failed: " + e); - //return null; - // } - } + + + public Object getValue(PtgExp pxp) { + //try{ + return FormulaCalculator.calculateFormula(expression); + //}catch(FunctionNotSupportedException e){ + //Logger.logWarn("Array.getValue() failed: " + e); + //return null; + // } + } + /** * link this shared formula to it's parent formula */ - public void setParentRec(Formula f) { - this.parentRec= f; + public void setParentRec(Formula f) { + this.parentRec = f; } + /** * return parent formula + * * @return */ public Formula getParentRec() { - return parentRec; + return parentRec; + } + + /** + * return the string representation of the array formula + */ + public String getFormulaString() { + String expressString = FormulaParser.getExpressionString(expression); + if (!"".equals(expressString)) + return expressString.substring(1); + return ""; + } + + /** + * allow access to expression + * + * @return + */ + public Stack getExpression() { + return expression; } - /** - * return the string representation of the array formula - */ - public String getFormulaString(){ - String expressString = FormulaParser.getExpressionString(expression); - if (!"".equals( expressString )) - return expressString.substring(1); - return ""; + + /** + * return the cells referenced by this array in string range form + * + * @return + */ + public String getArrayRefs() { + int[] rc = new int[2]; + rc[0] = rwFirst; + rc[1] = colFirst; + String rowcol1 = ExcelTools.formatLocation(rc); + rc[0] = rwLast; + rc[1] = colLast; + String rowcol2 = ExcelTools.formatLocation(rc); + return rowcol1 + ":" + rowcol2; } - - /** - * allow access to expression - * @return - */ - public Stack getExpression() { return expression; } - - /** - * return the cells referenced by this array in string range form - * @return - */ - public String getArrayRefs() { - int[] rc= new int[2]; - rc[0] = rwFirst; - rc[1] = colFirst; - String rowcol1 = ExcelTools.formatLocation(rc); - rc[0] = rwLast; - rc[1] = colLast; - String rowcol2 = ExcelTools.formatLocation(rc); - return rowcol1 + ":" + rowcol2; - } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/AutoFilter.java b/src/main/java/io/starter/formats/XLS/AutoFilter.java index f78c4c1..bc8aa03 100644 --- a/src/main/java/io/starter/formats/XLS/AutoFilter.java +++ b/src/main/java/io/starter/formats/XLS/AutoFilter.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,45 +23,44 @@ package io.starter.formats.XLS; -import java.util.ArrayList; - import io.starter.OpenXLS.ExcelTools; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.util.ArrayList; + /** - * Auto filter controls the auto-filter capabilities of Excel. It has numerous sub-records and references - * + * Auto filter controls the auto-filter capabilities of Excel. It has numerous sub-records and references + *

        * p>

        -    offset  name        size    contents
        -    ---
        -    4       iEntry          2       Index of the active autofilter
        -    6       grbit           2       Option flags
        -    8       doper1          10      DOPER struct for the first filter condition
        -    18      doper2          10      DOPER struct for the second filter condition
        -    28      rgch            var     String storage for vtString DOPER
        -    
        -    

        -*/ + * offset name size contents + * --- + * 4 iEntry 2 Index of the active autofilter + * 6 grbit 2 Option flags + * 8 doper1 10 DOPER struct for the first filter condition + * 18 doper2 10 DOPER struct for the second filter condition + * 28 rgch var String storage for vtString DOPER + * + *

        + */ /* TODO: ************************************************************************************************************ - * Creation of new AutoFilters, removal of existing (see Boundsheet) + * Creation of new AutoFilters, removal of existing (see Boundsheet) * - * fix: iEntry is not always the index of the column + * fix: iEntry is not always the index of the column * APPARENTLY if there are more than two autofilters, iEntry is the index of the column * if there is only 1 autofilter, iEntry is 0 --- dependent upon Obj record???? - * + * * Finish: setTop10, setVal, setVal2: verify all is correctly done .... * ******************************************************************************************************************* */ -public final class AutoFilter extends io.starter.formats.XLS.XLSRecord -{ +public final class AutoFilter extends io.starter.formats.XLS.XLSRecord { private static final long serialVersionUID = -5228830347211523997L; - - short iEntry; // *** this is the column number *** oops! not always!!! + + short iEntry; // *** this is the column number *** oops! not always!!! Doper doper1, doper2; - + // booleans used here for memory space/grbit fields, these are really 1/0 values. whas the diff? boolean wJoin;// true if custom filter conditions are ORed boolean fSimple1;// true if the first condition is a simple equality;, @@ -70,29 +69,30 @@ public final class AutoFilter extends io.starter.formats.XLS.XLSRecord boolean fTop; // true if the top 10 AutoFilter shows the top itemsl 0 if it shows the bottom items boolean fPercent; // true if the Top 10 AutoFilter shows percentage, 0 if it shows items short wTop10; //The number of items to show (from 1-500) - + private byte[] PROTOTYPE_BYTES = { - 00, 00, /* iEntry */ - 00, 00, /* grbit */ - 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, /* unused Doper1 */ - 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, /* unused Doper2 */ - }; + 00, 00, /* iEntry */ + 00, 00, /* grbit */ + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, /* unused Doper1 */ + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, /* unused Doper2 */ + }; + /** * creates a new AutoFilter record */ public static XLSRecord getPrototype() { - AutoFilter af= new AutoFilter(); + AutoFilter af = new AutoFilter(); af.setOpcode(AUTOFILTER); - af.setData(af.PROTOTYPE_BYTES); // 20090630 KSC: don't use static PROTOTYPE_BYTES as changes are propogated + af.setData(af.PROTOTYPE_BYTES); // 20090630 KSC: don't use static PROTOTYPE_BYTES as changes are propogated af.init(); return af; } - + /** * initialize the AutoFilter record */ - public void init(){ - super.init(); + public void init() { + super.init(); iEntry = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); //parse out grbit flags short grbit = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); @@ -101,398 +101,404 @@ public void init(){ doper1 = parseDoper(this.getBytesAt(4, 10)); doper2 = parseDoper(this.getBytesAt(14, 10)); // parse string data, if any, appended to end of data record - if (this.getData().length>25) { - byte[] rgch= this.getBytesAt(25, this.getData().length-25); - int pos= 0; - if (doper1 instanceof StringDoper) { - ((StringDoper)doper1).setString(rgch, pos); - pos+=((StringDoper)doper1).getCch(); // set position pointer to next string data, if any - pos++; - } - if (doper2 instanceof StringDoper) { - ((StringDoper)doper2).setString(rgch, pos); - } - } + if (this.getData().length > 25) { + byte[] rgch = this.getBytesAt(25, this.getData().length - 25); + int pos = 0; + if (doper1 instanceof StringDoper) { + ((StringDoper) doper1).setString(rgch, pos); + pos += ((StringDoper) doper1).getCch(); // set position pointer to next string data, if any + pos++; + } + if (doper2 instanceof StringDoper) { + ((StringDoper) doper2).setString(rgch, pos); + } + } } - + /** - * Parse out the grbit - * + * Parse out the grbit + * * @param grbt */ - private void decodeGrbit(short grbit) { // top 500: grbit= -1488 - wJoin = ((grbit & 0x3)==0x3); // 0= AND, 3= OR - fSimple1 = ((grbit & 0x4)==0x4); - fSimple2 = ((grbit & 0x8)==0x8); - fTop10 = ((grbit & 0x10)==0x10); - fTop = ((grbit & 0x20)==0x20); - fPercent = ((grbit & 0x40)==0x40); - wTop10 = (short)((grbit & 0xFF80) >> 7); + private void decodeGrbit(short grbit) { // top 500: grbit= -1488 + wJoin = ((grbit & 0x3) == 0x3); // 0= AND, 3= OR + fSimple1 = ((grbit & 0x4) == 0x4); + fSimple2 = ((grbit & 0x8) == 0x8); + fTop10 = ((grbit & 0x10) == 0x10); + fTop = ((grbit & 0x20) == 0x20); + fPercent = ((grbit & 0x40) == 0x40); + wTop10 = (short) ((grbit & 0xFF80) >> 7); } - + /** * encode the grbit from the source flags + * * @return short encoded grbit */ private short encodeGrbit() { - short grbit= 0; - if (wJoin) grbit= 3; // Or 0= AND - grbit= ByteTools.updateGrBit(grbit, fSimple1, 2); - grbit= ByteTools.updateGrBit(grbit, fSimple2, 3); - grbit= ByteTools.updateGrBit(grbit, fTop10, 4); - grbit= ByteTools.updateGrBit(grbit, fTop, 5); - grbit= ByteTools.updateGrBit(grbit, fPercent, 6); - grbit= (short) ((wTop10 << 7) | grbit); + short grbit = 0; + if (wJoin) grbit = 3; // Or 0= AND + grbit = ByteTools.updateGrBit(grbit, fSimple1, 2); + grbit = ByteTools.updateGrBit(grbit, fSimple2, 3); + grbit = ByteTools.updateGrBit(grbit, fTop10, 4); + grbit = ByteTools.updateGrBit(grbit, fTop, 5); + grbit = ByteTools.updateGrBit(grbit, fPercent, 6); + grbit = (short) ((wTop10 << 7) | grbit); return grbit; } - + /** * Take an array of 10 bytes and parse out the doper + * * @param doperBytes * @return */ private Doper parseDoper(byte[] doperBytes) { Doper retDoper = null; - switch(doperBytes[0]) { + switch (doperBytes[0]) { case 0x0: - retDoper = (Doper) new UnusedDoper(doperBytes); + retDoper = new UnusedDoper(doperBytes); break; case 0x2: - retDoper = (Doper) new RKDoper(doperBytes); + retDoper = new RKDoper(doperBytes); break; case 0x4: - retDoper = (Doper) new IEEEDoper(doperBytes); + retDoper = new IEEEDoper(doperBytes); break; case 0x6: - retDoper = (Doper) new StringDoper(doperBytes); + retDoper = new StringDoper(doperBytes); break; case 0x8: - retDoper = (Doper) new ErrorDoper(doperBytes); + retDoper = new ErrorDoper(doperBytes); break; case 0xC: - retDoper = (Doper) new AllBlanksDoper(doperBytes); + retDoper = new AllBlanksDoper(doperBytes); break; case 0xE: - retDoper = (Doper) new NoBlanksDoper(doperBytes); + retDoper = new NoBlanksDoper(doperBytes); break; } return retDoper; } - + /** * commit all the flags, dopers, etc to the byte rec array - * */ public void update() { - byte[] data= new byte[25]; - byte[] b= ByteTools.shortToLEBytes(iEntry); - System.arraycopy(b, 0, data, 0, 2); - b= ByteTools.shortToLEBytes(encodeGrbit()); - System.arraycopy(b, 0, data, 2, 2); - System.arraycopy(doper1.getRecord(), 0, data, 4, 10); - System.arraycopy(doper2.getRecord(), 0, data, 14, 10); - if (doper1 instanceof StringDoper) // append rgch bytes - data= ByteTools.append(((StringDoper)doper1).getStrBytes(), data); - if (doper2 instanceof StringDoper) // append rgch bytes - data= ByteTools.append(((StringDoper)doper2).getStrBytes(), data); - setData(data); + byte[] data = new byte[25]; + byte[] b = ByteTools.shortToLEBytes(iEntry); + System.arraycopy(b, 0, data, 0, 2); + b = ByteTools.shortToLEBytes(encodeGrbit()); + System.arraycopy(b, 0, data, 2, 2); + System.arraycopy(doper1.getRecord(), 0, data, 4, 10); + System.arraycopy(doper2.getRecord(), 0, data, 14, 10); + if (doper1 instanceof StringDoper) // append rgch bytes + data = ByteTools.append(((StringDoper) doper1).getStrBytes(), data); + if (doper2 instanceof StringDoper) // append rgch bytes + data = ByteTools.append(((StringDoper) doper2).getStrBytes(), data); + setData(data); } /** - * Evaluates this AutoFilter's condition for each cell in the indicated column + * Evaluates this AutoFilter's condition for each cell in the indicated column * over all rows in the sheet; if the condition is not met, the row is set to hidden *
        NOTE: since there may be other conditions (other AutoFilters, for instance) * setting the row to it's hidden state, this method will not * set the row to unhidden if the condition passes. */ public void evaluate() { - Object val1, val2= null; - val1= getVal(doper1); // get the doper value from the 1st doper/comparison, if any - boolean hasDoper2= !(doper2 instanceof UnusedDoper); + Object val1, val2 = null; + val1 = getVal(doper1); // get the doper value from the 1st doper/comparison, if any + boolean hasDoper2 = !(doper2 instanceof UnusedDoper); if (hasDoper2) - val2= getVal(doper2); - - String op1= "=", op2= ""; - boolean passes= true; - if (!fSimple1) { - op1= doper1.getComparisonOperator(); - } + val2 = getVal(doper2); + + String op1 = "=", op2 = ""; + boolean passes = true; + if (!fSimple1) { + op1 = doper1.getComparisonOperator(); + } if (!fSimple2 && hasDoper2) { - op2= doper2.getComparisonOperator(); + op2 = doper2.getComparisonOperator(); } // TODO: // above/below average? ooxml only? // date/time comparisons???? // begins with, ends with ... - if (fTop10) { - if (fTop) { // ascending - evaluateTopN(); - } else // descending - evaluateBottomN(); + if (fTop10) { + if (fTop) { // ascending + evaluateTopN(); + } else // descending + evaluateBottomN(); } else if ((doper1 instanceof AllBlanksDoper) || - (doper1 instanceof NoBlanksDoper)) { - boolean filterBlanks= (doper1 instanceof NoBlanksDoper); - int n= this.getSheet().getNumRows(); - for (int i= 0; i < n; i++) { - Row r= this.getSheet().getRowByNumber(i); - if (r==null) {// it's blank - // create a blank cell and then set to hidden? - if (filterBlanks) { - this.getSheet().addValue("", ExcelTools.formatLocation(new int[]{i, iEntry})); - r= this.getSheet().getRowByNumber(i); - r.setHidden(true); - } - } else { //row is not blank, check to see if cell is blank - try { - BiffRec c= r.getCell(iEntry); - if (c instanceof Blank && filterBlanks) - r.setHidden(true); - else if (!filterBlanks) - r.setHidden(true); - } catch (NullPointerException e) { - // NPE= blank - if (filterBlanks) - r.setHidden(true); - } catch (CellNotFoundException e) { - - } - } - } - Row[] rows= this.getSheet().getRows(); - if (!filterBlanks) { // easy; everything that is NOT BLANK is hidden - for (int i=1; i
      • value -- The data item value is displayed. + *
      • difference --Display as the difference between this data item value and the value of the pivot item + *
      • percentageValue -- Display as a percentage of the value of the pivot item + *
      • percentageDifference -- Display as a percentage difference from the value of the pivot item + *
      • runningTotal -- Display as the running total for successive pivot items in the pivot field + *
      • percentageTotalRow -- Display as a percentage of the total for the row containing this data item. + *
      • percentageTotalCol -- Display as a percentage of the total for the column containing this data item. + *
      • grandTotal -- Display as a percentage of the grand total of the data item. + *
      • calculated --Calculate the value to display using the following formula: + *
        ((this data item value) * (grand total of grand totals)) / ((row grand total) * (column grand total))
        + */ + public enum DISPLAYTYPES {value, difference, percentageValue, percentageDifference, runningTotal, percentageTotalRow, percentageTotalCol, grandTotal, calculated} - The value of the sxaxis.sxaxisData field of the Sxvd record of the referenced pivot field MUST be 1. + public void init() { + super.init(); + isxvdData = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); // pivot field index + iiftab = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); // aggregation function -- see aggregationfunctions + df = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); // display calculation + isxvd = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); // specifies a pivot field index used in calculations as specified by the df field. + isxvi = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); // A signed integer that specifies the pivot item used by df. + ifmt = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); // number format index + cchName = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + if (cchName != -1) { + byte encoding = this.getByteAt(14); + byte[] tmp = this.getBytesAt(15, (cchName) * (encoding + 1)); + try { + if (encoding == 0) + name = new String(tmp, DEFAULTENCODING); + else + name = new String(tmp, UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding PivotTable caption name in Sxvd: " + e); + } + } + if (DEBUGLEVEL > 3) + Logger.logInfo("SXDI - isxvdData:" + isxvdData + " iiftab:" + iiftab + " df:" + df + " isxvd:" + isxvd + " isxvi:" + isxvi + " ifmt:" + ifmt + " name:" + name); + } - iiftab (2 bytes): A signed integer that specifies the aggregation function. - MUST be a value from the following table: - Value Meaning - 0x0000 Sum of values - 0x0001 Count of values - 0x0002 Average of values - 0x0003 Max of values - 0x0004 Min of values - 0x0005 Product of values - 0x0006 Count of numbers - 0x0007 Statistical standard deviation (sample) - 0x0008 Statistical standard deviation (population) - 0x0009 Statistical variance (sample) - 0x000A Statistical variance (population) + /** + * returns the pivot field index for this data item; + *
        the values in the source data associated with the associated cache field of the referenced pivot field are aggregated as specified in this record. + * + * @return + */ + public short getPivotFieldIndex() { + return isxvdData; + } - df (2 bytes): A signed integer that specifies the calculation used to display the value of this data item. - MUST be a value from the following table: - Value Meaning - 0x0000 The data item value is displayed. - 0x0001 Display as the difference between this data item value and the value of the pivot item specified by isxvi. - 0x0002 Display as a percentage of the value of the pivot item specified by isxvi. - 0x0003 Display as a percentage difference from the value of the pivot item specified by isxvi. - 0x0004 Display as the running total for successive pivot items in the pivot field specified by isxvd. - 0x0005 Display as a percentage of the total for the row containing this data item. - 0x0006 Display as a percentage of the total for the column containing this data item. - 0x0007 Display as a percentage of the grand total of the data item. - 0x0008 Calculate the value to display using the following formula: - ((this data item value) * (grand total of grand totals)) / ((row grand total) * (column grand total)) + /** + * sets the pivot field index for this data item; + *
        the values in the source data associated with the associated cache field of the referenced pivot field are aggregated as specified in this record. + * + * @return + */ + public void setPivotFieldIndex(int fi) { + isxvdData = (short) fi; + byte[] b = ByteTools.shortToLEBytes(isxvdData); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } - isxvd (2 bytes): A signed integer that specifies a pivot field index as specified in Pivot Fields. - The referenced pivot field is used in calculations as specified by the df field. - If df is 0x0001, 0x0002, 0x0003, or 0x0004 then the value of isxvd MUST be greater than or equal to zero and - less than the value of the cDim field in the preceding SxView record. - Otherwise, the value of isxvd is undefined and MUST be ignored. + /** + * returns the aggregation function for this data field + * + * @return + * @see AGGREGATIONFUNCTIONS + */ + public int getAggregationFunction() { + return iiftab; + } - isxvi (2 bytes): A signed integer that specifies the pivot item used by df. + /** + * sets the aggregation function for this data field + * + * @see AGGREGATIONFUNCTIONS + */ + public void setAggregationFunction(int af) { + iiftab = (short) af; + byte[] b = ByteTools.shortToLEBytes(iiftab); + this.getData()[2] = b[0]; + this.getData()[3] = b[1]; + } - If df is 0x0001, 0x0002, or 0x0003 then the value of this field MUST be a value from the following table: - Value Meaning - 0 to 0x7EFE A pivot item index, as specified by Pivot Items, that specifies a pivot item in the pivot field specified by isxvd. - MUST be less than the cItm field of the Sxvd record of the pivot field specified by isxvd. - 0x7FFB The previous pivot item in the pivot field specified by isxvd. - 0x7FFC The next pivot item in the pivot field specified by isxvd. - Otherwise, the value is undefined and MUST be ignored. + /** + * adds the data field to the DATA axis and it's aggregation function ("sum" is default) + * + * @param fieldIndex + * @param aggregationFunction + * @param name + */ + public void addDataField(int fieldIndex, String aggregationFunction, String name) { + setPivotFieldIndex(fieldIndex); + setAggregationFunction(AGGREGATIONFUNCTIONS.get(aggregationFunction)); + setName(name); + } - ifmt (2 bytes): An IFmt structure that specifies the number format for this item. + /** + * A signed integer that specifies the calculation used to display the value of this data item. + * + * @return + * @see DISPLAYTYPES + */ + public int getDisplayCalculation() { + return df; + } - cchName (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stName field. - If the value is 0xFFFF then stName does not exist. Otherwise, the value MUST be greater than zero and less than or equal to 0x00FF. + /** + * sets the Display Calculation for the Data item: A signed integer that specifies the calculation used to display the value of this data item. + * + * @see DISPLAYTYPES + */ + public void setDisplayCalculation(int dc) { + df = (short) dc; + byte[] b = ByteTools.shortToLEBytes(df); + this.getData()[4] = b[0]; + this.getData()[5] = b[1]; + } - MUST NOT be 0xFFFF when the PivotCache functionality level is less than 3, or for non-OLAP PivotTable view . + /** + * specifies a pivot field index used in calculations as specified by the Display Calculation function + * + * @return + */ + public short getCalculationPivotFieldIndex() { + return isxvd; + } - stName (variable): An XLUnicodeStringNoCch structure that specifies the name of this data item. - A value that is not NULL specifies that this string is used to override the name in the corresponding cache field. + /** + * specifies a pivot field index used in calculations as specified by the Display Calculation function + * + * @return + */ + public void setCalculationPivotFieldIndex(int ci) { + isxvd = (short) ci; + byte[] b = ByteTools.shortToLEBytes(isxvd); + this.getData()[6] = b[0]; + this.getData()[7] = b[1]; + } - MUST NOT exist if cchName is 0xFFFF. Otherwise, MUST exist and the length MUST equal cchName. + /** + * specifies a pivot item index used in calculations as specified by the Display Calculation function + * + * @return + */ + public short getCalculationPivotItemIndex() { + return isxvi; + } - If this string is not NULL and the PivotTable view is a non-OLAP PivotTable view, this field MUST be unique within all SXDI records in this PivotTable view. - - * - */ -public class SxDI extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - short isxvdData, isxvd, iiftab, df, isxvi, cchName, ifmt ; - String name= null; - private static final long serialVersionUID = 2639291289806138985L; - - /** - * enum possible aggregation functions for Data axis fields - *
        Sum, Counta, Average, Max, Min, Product, Count, StdDev, StdDevP, Var, VarP - */ - public enum AGGREGATIONFUNCTIONS { - Sum("sum"), - Count ("count"), - Average("average"), - Max("max"), - Min("min"), - Product("product"), - CountNums("countnums"), - StdDev("stdDev"), - StdDevP("stdDevp"), - Var("var"), - VarP("varp"); - private final String agf; - AGGREGATIONFUNCTIONS(String s) { - this.agf= s; - } - - public static int get(String s) { - for (AGGREGATIONFUNCTIONS c : values()) { - if (c.agf.equals(s)) - return c.ordinal(); - } - return 0; - } - }; - - /** - * enum possible display types for Data axis fields -
      • value -- The data item value is displayed. -
      • difference --Display as the difference between this data item value and the value of the pivot item -
      • percentageValue -- Display as a percentage of the value of the pivot item -
      • percentageDifference -- Display as a percentage difference from the value of the pivot item -
      • runningTotal -- Display as the running total for successive pivot items in the pivot field -
      • percentageTotalRow -- Display as a percentage of the total for the row containing this data item. -
      • percentageTotalCol -- Display as a percentage of the total for the column containing this data item. -
      • grandTotal -- Display as a percentage of the grand total of the data item. -
      • calculated --Calculate the value to display using the following formula: -
        ((this data item value) * (grand total of grand totals)) / ((row grand total) * (column grand total))
        - */ - public enum DISPLAYTYPES { value, difference, percentageValue, percentageDifference, runningTotal, percentageTotalRow, percentageTotalCol, grandTotal, calculated }; - public void init(){ - super.init(); - isxvdData = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); // pivot field index - iiftab = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); // aggregation function -- see aggregationfunctions - df = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); // display calculation - isxvd = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); // specifies a pivot field index used in calculations as specified by the df field. - isxvi = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); // A signed integer that specifies the pivot item used by df. - ifmt = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); // number format index - cchName= ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - if (cchName!=-1) { - byte encoding= this.getByteAt(14); - byte[] tmp = this.getBytesAt(15, (cchName) * (encoding+1)); - try{ - if (encoding==0) - name= new String(tmp, DEFAULTENCODING); - else - name= new String(tmp, UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding PivotTable caption name in Sxvd: " + e);} - } - if (DEBUGLEVEL > 3) Logger.logInfo("SXDI - isxvdData:" +isxvdData + " iiftab:" + iiftab + " df:" + df + " isxvd:" + isxvd + " isxvi:" + isxvi + " ifmt:" + ifmt + " name:" + name); - } - - /** - * returns the pivot field index for this data item; - *
        the values in the source data associated with the associated cache field of the referenced pivot field are aggregated as specified in this record. - * @return - */ - public short getPivotFieldIndex() { return isxvdData; } - /** - * sets the pivot field index for this data item; - *
        the values in the source data associated with the associated cache field of the referenced pivot field are aggregated as specified in this record. - * @return - */ - public void setPivotFieldIndex(int fi) { - isxvdData= (short) fi; - byte[] b= ByteTools.shortToLEBytes(isxvdData); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; - } - - /** - * returns the aggregation function for this data field - * @see AGGREGATIONFUNCTIONS - * @return - */ - public int getAggregationFunction() { - return iiftab; - } - - /** - * sets the aggregation function for this data field - * @see AGGREGATIONFUNCTIONS - */ - public void setAggregationFunction(int af) { - iiftab= (short) af; - byte[] b= ByteTools.shortToLEBytes(iiftab); - this.getData()[2]= b[0]; - this.getData()[3]= b[1]; - } - - /** - * adds the data field to the DATA axis and it's aggregation function ("sum" is default) - * @param fieldIndex - * @param aggregationFunction - * @param name - */ - public void addDataField(int fieldIndex, String aggregationFunction, String name) { - setPivotFieldIndex(fieldIndex); - setAggregationFunction(AGGREGATIONFUNCTIONS.get(aggregationFunction)); - setName(name); - } - - /** - * A signed integer that specifies the calculation used to display the value of this data item. - * @see DISPLAYTYPES - * @return - */ - public int getDisplayCalculation() { return df; } - - /** - * sets the Display Calculation for the Data item: A signed integer that specifies the calculation used to display the value of this data item. - * @see DISPLAYTYPES - */ - public void setDisplayCalculation(int dc) { - df= (short) dc; - byte[] b= ByteTools.shortToLEBytes(df); - this.getData()[4]= b[0]; - this.getData()[5]= b[1]; - } - /** - * specifies a pivot field index used in calculations as specified by the Display Calculation function - * @return - */ - public short getCalculationPivotFieldIndex() { return isxvd; } - /** - * specifies a pivot field index used in calculations as specified by the Display Calculation function - * @return - */ - public void setCalculationPivotFieldIndex(int ci) { - isxvd= (short) ci; - byte[] b= ByteTools.shortToLEBytes(isxvd); - this.getData()[6]= b[0]; - this.getData()[7]= b[1]; - } - /** - * specifies a pivot item index used in calculations as specified by the Display Calculation function - * @return - */ - public short getCalculationPivotItemIndex() { return isxvi; } - /** - * specifies a pivot item index used in calculations as specified by the Display Calculation function - * @return - */ - public void setCalculationPivotItemIndex(int ci) { - isxvi= (short) ci; - byte[] b= ByteTools.shortToLEBytes(isxvi); - this.getData()[8]= b[0]; - this.getData()[9]= b[1]; - } + /** + * specifies a pivot item index used in calculations as specified by the Display Calculation function + * + * @return + */ + public void setCalculationPivotItemIndex(int ci) { + isxvi = (short) ci; + byte[] b = ByteTools.shortToLEBytes(isxvi); + this.getData()[8] = b[0]; + this.getData()[9] = b[1]; + } - /** - * returns the index to the number format pattern for this data field - * @return - */ - public short getNumberFormat() { return ifmt; } - /** - * sets the index to the number format pattern for this data field - */ - public void setNumberFormat(int i) { - ifmt= (short) i; - byte[] b= ByteTools.shortToLEBytes(ifmt); - this.getData()[10]= b[0]; - this.getData()[11]= b[1]; - } - - /** - * returns the name of this data item; if not null, overrides the name in the corresponding cache field - * @return - */ - public String getName() { return name; } - /** - * sets the name of this data item; if not null, overrides the name in the corresponding cache field - * for this pivot item - */ - public void setName(String name) { - this.name= name; - byte[] data= new byte[14]; - System.arraycopy(this.getData(), 0, data, 0, 13); - if (name!=null) { - byte[] strbytes = null; - try{ - strbytes = this.name.getBytes(DEFAULTENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding pivot table name in SXVI: " + e);} - - //update the lengths: - cchName = (short)strbytes.length; - byte[] nm = ByteTools.shortToLEBytes(cchName); - data[12] = nm[0]; - data[13] = nm[1]; - - // now append variable-length string data - byte[] newrgch = new byte[cchName + 1]; // account for encoding bytes - System.arraycopy(strbytes, 0, newrgch, 1, cchName); + /** + * returns the index to the number format pattern for this data field + * + * @return + */ + public short getNumberFormat() { + return ifmt; + } + + /** + * sets the index to the number format pattern for this data field + */ + public void setNumberFormat(int i) { + ifmt = (short) i; + byte[] b = ByteTools.shortToLEBytes(ifmt); + this.getData()[10] = b[0]; + this.getData()[11] = b[1]; + } - data= ByteTools.append(newrgch, data); - } else { - data[12] = -1; - data[13] = -1; - } + /** + * returns the name of this data item; if not null, overrides the name in the corresponding cache field + * + * @return + */ + public String getName() { + return name; + } + + /** + * sets the name of this data item; if not null, overrides the name in the corresponding cache field + * for this pivot item + */ + public void setName(String name) { + this.name = name; + byte[] data = new byte[14]; + System.arraycopy(this.getData(), 0, data, 0, 13); + if (name != null) { + byte[] strbytes = null; + try { + strbytes = this.name.getBytes(DEFAULTENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding pivot table name in SXVI: " + e); + } + + //update the lengths: + cchName = (short) strbytes.length; + byte[] nm = ByteTools.shortToLEBytes(cchName); + data[12] = nm[0]; + data[13] = nm[1]; + + // now append variable-length string data + byte[] newrgch = new byte[cchName + 1]; // account for encoding bytes + System.arraycopy(strbytes, 0, newrgch, 1, cchName); + + data = ByteTools.append(newrgch, data); + } else { + data[12] = -1; + data[13] = -1; + } this.setData(data); - } - - - private byte[] PROTOTYPE_BYTES = new byte[] { - 0, 0, /* isxvdData */ - 0, 0, /* iiftab */ - 0, 0, /* df */ - 0, 0, /* isxvd */ - 0, 0, /* isxvi */ - 0, 0, /* ifmt */ - -1, -1, /* cchname */ + } + + + private byte[] PROTOTYPE_BYTES = new byte[]{ + 0, 0, /* isxvdData */ + 0, 0, /* iiftab */ + 0, 0, /* df */ + 0, 0, /* isxvd */ + 0, 0, /* isxvi */ + 0, 0, /* ifmt */ + -1, -1, /* cchname */ }; + public static XLSRecord getPrototype() { - SxDI di= new SxDI(); - di.setOpcode(SXDI); - di.setData(di.PROTOTYPE_BYTES); - di.init(); - return di; + SxDI di = new SxDI(); + di.setOpcode(SXDI); + di.setData(di.PROTOTYPE_BYTES); + di.init(); + return di; } } diff --git a/src/main/java/io/starter/formats/XLS/SxEX.java b/src/main/java/io/starter/formats/XLS/SxEX.java index af7ed0e..aa6b056 100644 --- a/src/main/java/io/starter/formats/XLS/SxEX.java +++ b/src/main/java/io/starter/formats/XLS/SxEX.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,124 +25,124 @@ import io.starter.toolkit.Logger; /** - * SxEX 0xF1 - * - The SXEx record specifies additional properties of a PivotTable view and specifies the beginning of a collection of records as defined by the Worksheet substreamABNF. - The collection of records specifies selection and formatting properties for the PivotTable view. - - csxformat (2 bytes): An unsigned integer that specifies the number of SxFormat records that follow this record. MUST be less than or equal to 0xFFFF. - - cchErrorString (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stError field. - If the value is 0xFFFF, then stError does not exist. MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - - cchNullString (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stDisplayNull field. - If the value is 0xFFFF, then stDisplayNull does not exist. MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - - cchTag (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stTag field. - If the value is 0xFFFF, then stTag does not exist. MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - - csxselect (2 bytes): An unsigned integer that specifies the number of SxSelect records that follow this record. MUST be less than or equal to 0xFFFF. - - crwPage (2 bytes): A DRw structure that specifies the number of rows in the page area (see Location and Body) of the PivotTable view. - - ccolPage (2 bytes): A DCol structure that specifies the number of columns in the page area (see Location and Body) of the PivotTable view. - - A - fAcrossPageLay (1 bit): A bit that specifies how pivot fields are laid out in the page area (see Location and Body) when there are multiple pivot fields on the page axis. MUST be a value from the following table: - Value Meaning - 0x0 Pivot fields are displayed in the page area from the top to the bottom first, as fields are added, before moving to another column. - 0x1 Pivot fields are displayed in the page area from left to right first, as fields are added, before moving to another row. - - cWrapPage (8 bits): An unsigned integer that specifies the number of pivot fields in the page area (see Location and Body) to - display before moving to another row or column, as specified by fAcrossPageLay. - MUST be less than or equal to 0xFF. A value of 0 means that no wrap is allowed. - - B - unused (1 bit): Undefined and MUST be ignored. - - C - reserved1 (1 bit): MUST be zero and MUST be ignored. - - reserved2 (5 bits): MUST be zero and MUST be ignored. - - D - fEnableWizard (1 bit): A bit that specifies whether a wizard user interface is displayed to work with the PivotTable view. - - E - fEnableDrilldown (1 bit): A bit that specifies whether details can be shown for cells in the data area, as specified by PivotTable Layout. - - F - fEnableFieldDialog (1 bit): A bit that specifies whether a user interface for setting properties of a pivot field can be displayed. - - G - fPreserveFormatting (1 bit): A bit that specifies whether formatting is preserved when the PivotTable view is recalculated. - If the value is 1, csxformat MUST be 0 and there MUST be no SxFormat records following this record. - - H - fMergeLabels (1 bit): A bit that specifies whether empty cells adjacent to the cells displaying pivot item captions of - pivot fields on the row axis and column axis of the PivotTable view are merged into a single cell with center-aligned text. - - I - fDisplayErrorString (1 bit): A bit that specifies whether the PivotTable view displays the custom error string stError in cells that contain errors. - - J - fDisplayNullString (1 bit): A bit that specifies whether the PivotTable view displays the custom string stDisplayNull in cells that contain NULL values. - - K - fSubtotalHiddenPageItems (1 bit): A bit that specifies whether hidden pivot items, as specified by SXVI records with the fHidden field equal to 1, of a - pivot field on the page axis with the isxvi field of the corresponding SXPI_Item structure equal to 0x7FFD are filtered out when calculating the PivotTable view. - MUST be 0 for non-OLAPdata sources (1) if the PivotCache functionality level is 3. - - reserved3 (8 bits): MUST be zero and MUST be ignored. - - cchPageFieldStyle (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stPageFieldStyle field. - If the value is 0xFFFF, then stPageFieldStyle does not exist. - MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - - cchTableStyle (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stTableStyle field. - If the value is 0xFFFF, then stTableStyle does not exist. - MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - - cchVacateStyle (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stVacateStyle field. - If the value is 0xFFFF, then stVacateStyle does not exist. - MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - - stError (variable): An XLUnicodeStringNoCch structure that specifies a custom string displayed in cells that contain - errors when the value of fDisplayErrorString is 1. The length is specified in cchErrorString. - This field is optional and MUST NOT exist if cchErrorString is 0xFFFF. - - stDisplayNull (variable): An XLUnicodeStringNoCch structure that specifies a custom string displayed in cells that contain - NULL values when fDisplayNullString is 1. The length is specified in cchNullString. - This field is optional and MUST NOT exist if cchNullString is 0xFFFF. - - stTag (variable): An XLUnicodeStringNoCch structure that specifies a custom string saved with the PivotTable view. The length is specified in cchTag. - This field is optional and MUST NOT exist if cchTag is 0xFFFF. - - stPageFieldStyle (variable): An XLUnicodeStringNoCch structure that specifies the style used in the page area (see Location and Body) of the PivotTable view. - The style is specified by the StyleExt record with its stName field equal to this field's value. - If cchPageFieldStyle is 0xFFFF or less than 1, no style is applied. The length is specified in cchPageFieldStyle. - This field is optional and MUST NOT exist if cchPageFieldStyle is 0xFFFF. - - stTableStyle (variable): An XLUnicodeStringNoCch structure that specifies the style used in the body of the PivotTable view. - The style is specified by the StyleExt record with its stName field equal to this field's value. - If cchTableStyle is 0xFFFF or less than 1, no style is applied. The length is specified in cchTableStyle. - This field is optional and MUST NOT exist if cchTableStyle is 0xFFFF. - - stVacateStyle (variable): An XLUnicodeStringNoCch structure that specifies the style applied to cells that - become empty when the PivotTable view is recalculated. The style is specified by the StyleExt record - with its stName field equal to this field's value. If cchVacateStyle is 0xFFFF or less than 1, no style is applied. - The length is specified in cchVacateStyle. - This field is optional and MUST NOT exist if cchVacateStyle is 0xFFFF. - - - * + * SxEX 0xF1 + *

        + * The SXEx record specifies additional properties of a PivotTable view and specifies the beginning of a collection of records as defined by the Worksheet substreamABNF. + * The collection of records specifies selection and formatting properties for the PivotTable view. + *

        + * csxformat (2 bytes): An unsigned integer that specifies the number of SxFormat records that follow this record. MUST be less than or equal to 0xFFFF. + *

        + * cchErrorString (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stError field. + * If the value is 0xFFFF, then stError does not exist. MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. + *

        + * cchNullString (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stDisplayNull field. + * If the value is 0xFFFF, then stDisplayNull does not exist. MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. + *

        + * cchTag (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stTag field. + * If the value is 0xFFFF, then stTag does not exist. MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. + *

        + * csxselect (2 bytes): An unsigned integer that specifies the number of SxSelect records that follow this record. MUST be less than or equal to 0xFFFF. + *

        + * crwPage (2 bytes): A DRw structure that specifies the number of rows in the page area (see Location and Body) of the PivotTable view. + *

        + * ccolPage (2 bytes): A DCol structure that specifies the number of columns in the page area (see Location and Body) of the PivotTable view. + *

        + * A - fAcrossPageLay (1 bit): A bit that specifies how pivot fields are laid out in the page area (see Location and Body) when there are multiple pivot fields on the page axis. MUST be a value from the following table: + * Value Meaning + * 0x0 Pivot fields are displayed in the page area from the top to the bottom first, as fields are added, before moving to another column. + * 0x1 Pivot fields are displayed in the page area from left to right first, as fields are added, before moving to another row. + *

        + * cWrapPage (8 bits): An unsigned integer that specifies the number of pivot fields in the page area (see Location and Body) to + * display before moving to another row or column, as specified by fAcrossPageLay. + * MUST be less than or equal to 0xFF. A value of 0 means that no wrap is allowed. + *

        + * B - unused (1 bit): Undefined and MUST be ignored. + *

        + * C - reserved1 (1 bit): MUST be zero and MUST be ignored. + *

        + * reserved2 (5 bits): MUST be zero and MUST be ignored. + *

        + * D - fEnableWizard (1 bit): A bit that specifies whether a wizard user interface is displayed to work with the PivotTable view. + *

        + * E - fEnableDrilldown (1 bit): A bit that specifies whether details can be shown for cells in the data area, as specified by PivotTable Layout. + *

        + * F - fEnableFieldDialog (1 bit): A bit that specifies whether a user interface for setting properties of a pivot field can be displayed. + *

        + * G - fPreserveFormatting (1 bit): A bit that specifies whether formatting is preserved when the PivotTable view is recalculated. + * If the value is 1, csxformat MUST be 0 and there MUST be no SxFormat records following this record. + *

        + * H - fMergeLabels (1 bit): A bit that specifies whether empty cells adjacent to the cells displaying pivot item captions of + * pivot fields on the row axis and column axis of the PivotTable view are merged into a single cell with center-aligned text. + *

        + * I - fDisplayErrorString (1 bit): A bit that specifies whether the PivotTable view displays the custom error string stError in cells that contain errors. + *

        + * J - fDisplayNullString (1 bit): A bit that specifies whether the PivotTable view displays the custom string stDisplayNull in cells that contain NULL values. + *

        + * K - fSubtotalHiddenPageItems (1 bit): A bit that specifies whether hidden pivot items, as specified by SXVI records with the fHidden field equal to 1, of a + * pivot field on the page axis with the isxvi field of the corresponding SXPI_Item structure equal to 0x7FFD are filtered out when calculating the PivotTable view. + * MUST be 0 for non-OLAPdata sources (1) if the PivotCache functionality level is 3. + *

        + * reserved3 (8 bits): MUST be zero and MUST be ignored. + *

        + * cchPageFieldStyle (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stPageFieldStyle field. + * If the value is 0xFFFF, then stPageFieldStyle does not exist. + * MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. + *

        + * cchTableStyle (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stTableStyle field. + * If the value is 0xFFFF, then stTableStyle does not exist. + * MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. + *

        + * cchVacateStyle (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stVacateStyle field. + * If the value is 0xFFFF, then stVacateStyle does not exist. + * MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. + *

        + * stError (variable): An XLUnicodeStringNoCch structure that specifies a custom string displayed in cells that contain + * errors when the value of fDisplayErrorString is 1. The length is specified in cchErrorString. + * This field is optional and MUST NOT exist if cchErrorString is 0xFFFF. + *

        + * stDisplayNull (variable): An XLUnicodeStringNoCch structure that specifies a custom string displayed in cells that contain + * NULL values when fDisplayNullString is 1. The length is specified in cchNullString. + * This field is optional and MUST NOT exist if cchNullString is 0xFFFF. + *

        + * stTag (variable): An XLUnicodeStringNoCch structure that specifies a custom string saved with the PivotTable view. The length is specified in cchTag. + * This field is optional and MUST NOT exist if cchTag is 0xFFFF. + *

        + * stPageFieldStyle (variable): An XLUnicodeStringNoCch structure that specifies the style used in the page area (see Location and Body) of the PivotTable view. + * The style is specified by the StyleExt record with its stName field equal to this field's value. + * If cchPageFieldStyle is 0xFFFF or less than 1, no style is applied. The length is specified in cchPageFieldStyle. + * This field is optional and MUST NOT exist if cchPageFieldStyle is 0xFFFF. + *

        + * stTableStyle (variable): An XLUnicodeStringNoCch structure that specifies the style used in the body of the PivotTable view. + * The style is specified by the StyleExt record with its stName field equal to this field's value. + * If cchTableStyle is 0xFFFF or less than 1, no style is applied. The length is specified in cchTableStyle. + * This field is optional and MUST NOT exist if cchTableStyle is 0xFFFF. + *

        + * stVacateStyle (variable): An XLUnicodeStringNoCch structure that specifies the style applied to cells that + * become empty when the PivotTable view is recalculated. The style is specified by the StyleExt record + * with its stName field equal to this field's value. If cchVacateStyle is 0xFFFF or less than 1, no style is applied. + * The length is specified in cchVacateStyle. + * This field is optional and MUST NOT exist if cchVacateStyle is 0xFFFF. */ -public class SxEX extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - public void init(){ - super.init(); - if (DEBUGLEVEL > 3) Logger.logInfo("SXEX - "); - } - private byte[] PROTOTYPE_BYTES = new byte[] { // default configuration - 0, 0, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 2, 79, 0, -1, -1, -1, -1, -1, -1 +public class SxEX extends XLSRecord implements XLSConstants { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2639291289806138985L; + + public void init() { + super.init(); + if (DEBUGLEVEL > 3) Logger.logInfo("SXEX - "); + } + + private byte[] PROTOTYPE_BYTES = new byte[]{ // default configuration + 0, 0, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 2, 79, 0, -1, -1, -1, -1, -1, -1 }; + public static XLSRecord getPrototype() { - SxEX sex= new SxEX(); - sex.setOpcode(SXEX); - sex.setData(sex.PROTOTYPE_BYTES); - sex.init(); - return sex; + SxEX sex = new SxEX(); + sex.setOpcode(SXEX); + sex.setData(sex.PROTOTYPE_BYTES); + sex.init(); + return sex; } } diff --git a/src/main/java/io/starter/formats/XLS/SxFDB.java b/src/main/java/io/starter/formats/XLS/SxFDB.java index 52a3bf4..15733f8 100644 --- a/src/main/java/io/starter/formats/XLS/SxFDB.java +++ b/src/main/java/io/starter/formats/XLS/SxFDB.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,282 +24,289 @@ /** * The SXFDB record specifies properties for a cache field within a PivotCache. - * - * + *

        + *

        * grbit: - * A - fAllAtoms (1 bit): A bit that specifies whether this cache field has a collection of cache items. If fSomeUnhashed is equal to 1, this value MUST be equal to 0. - B - fSomeUnhashed (1 bit): Undefined, and MUST be ignored. If the fAllAtoms field is equal to 1, MUST be equal to 0. - C - fUsed (1 bit): Undefined, and MUST be ignored. - D - fHasParent (1 bit): A bit that specifies whether ifdbParent specifies a reference to a parent grouping cache field. For more information, see Grouping. If the fCalculatedField field is equal to 1, then this field MUST be equal to 0. - E - fRangeGroup (1 bit): A bit that specifies whether this cache field is grouped by using numeric grouping or date grouping, as specified by Grouping. If this field is equal to 1, then this record MUST be followed by a sequence of SXString records, as specified by the GRPSXOPER rule. The quantity of SXString records is specified by csxoper. If this field is equal to 1, then this record MUST be followed by a sequence of records that conforms to the SXRANGE rule that specifies the grouping properties for the ranges of values. - F - fNumField (1 bit): A bit that specifies whether the cache items in this cache field contain at least one numeric cache item, as specified by SXNum. If fDateInField is equal to 1, this field MUST be equal to 0. - G - unused1 (1 bit): Undefined and MUST be ignored. - H - fTextEtcField (1 bit): A bit that specifies whether the cache items contain text data. If fNumField is 1, this field MUST be ignored. - I - fnumMinMaxValid (1 bit): A bit that specifies whether a valid minimum or maximum value can be computed for the cache field. MUST be equal to 1 if fDateInField or fNumField is equal to 1. - J - fShortIitms (1 bit): A bit that specifies whether there are more than 255 cache items in this cache field. If catm is greater than 255, this value MUST be equal to 1; otherwise it MUST be 0. - K - fNonDates (1 bit): A bit that specifies whether the cache items in this cache field contain values that are not time or date values. If this cache field is a grouping cache field, as specified by Grouping, then this field MUST be ignored. Otherwise, if fDateInField is equal to 1, then this field MUST be 0. - L - fDateInField (1 bit): A bit that specifies whether the cache items in this cache field contain at least one time or date cache item, as specified by SXDtr. If fNonDates is equal to 1, then this field MUST be equal to 0. - M - unused2 (1 bit): Undefined and MUST be ignored. - N - fServerBased (1 bit): A bit that specifies whether this cache field is a server-based page field when the corresponding pivot field is on the page axis of the PivotTable view, as specified in source data. - This value applies only to an ODBC PivotCache. MUST NOT be equal to 1 if fCantGetUniqueItems is equal to 1. If fCantGetUniqueItems is equal to 1, then the ODBC connection cannot provide a list of unique items for the cache field. - MUST be 0 for a cache field in a non-ODBC PivotCache. - O - fCantGetUniqueItems (1 bit): A bit that specifies whether a list of unique values for the cache field was not available while refreshing the source data. This field applies only to a PivotCache that uses ODBC source data and is intended to be used in conjunction with optimization features. For example, the application can optimize memory usage when populating PivotCache records if it has a list of unique values for a cache field before all the records are retrieved from the ODBC connection. Or, the application can determine the appropriate setting of fServerBased based on this value. - MUST be 0 for fields in a non-ODBC PivotCache. - P - fCalculatedField (1 bit): A bit that specifies whether this field is a calculated field. The formula (section 2.2.2) of the calculated field is stored in a directly following SXFormula record. If fHasParent is equal to 1, this field MUST be equal to 0. - - ifdbParent (2 bytes): An unsigned integer that specifies the cache field index, - as specified by Cache Fields, of the grouping cache field for this cache field. MUST be greater than or equal to 0x0000 and less than or equal to the cfdbTot field of the SXDB record of this PivotCache. If fHasParent is equal to 0, then this field MUST be ignored. If fHasParent is equal to 1, and fRangeGroup is equal to 1, and the iByType field of the SXRng record of this cache field is greater than 0, then the fRangeGroup of the SXFDB record of the cache field specified by ifdbParent MUST be 1 and the iByType field of the SXRng record of the cache field - specified by ifdbParent MUST be greater than the iByType field of the SXRng record of this cache field. - - ifdbBase (2 bytes): An unsigned integer that specifies the cache field index, as specified by Cache Fields, of the base cache field, - as specified by Grouping, for the cache field specified by this record. - MUST be greater than or equal to 0x0000 and less than the value of the cfdbdb field of the SXDB record of this PivotCache. - If the cache field specified by this record is not a grouping cache field, then this field MUST be ignored. - - citmUnq (2 bytes): Undefined and MUST be ignored. - - csxoper (2 bytes): An unsigned integer that specifies the number of cache items in this cache field when this cache field is a grouping cache field, - as specified by Grouping. There MUST be an equivalent number of sequences of records that conform to the GRPSXOPER rule - following this record that specify the cache items. If the fRangeGroup field and the fCalculatedField field are equal to 0 - and this cache field corresponds to a source data entity, this field MUST be equal to 0. - If the fRangeGroup field is equal to 1, this value MUST be greater than or equal to 1. - - cisxoper (2 bytes): An unsigned integer that specifies the number of cache items in the base cache field that are grouped by this cache field. - There MUST be an equivalent number of SxIsxoper records following this record that specify which cache item in this cache - field groups each of the cache items in the base cache field. For more information, see Grouping. - - catm (2 bytes): An unsigned integer that specifies the number of cache items in the collection sequences of records that conform to the - SRCSXOPER rule in this cache field. If fAllAtoms is 0, then this field MUST be equal to 0x0000. - If this cache field corresponds to source data entities then there MUST be an equal number of SRCSXOPER rules in this cache field. - - stFieldName (variable): An XLUnicodeString structure that specifies the name of the cache field. MUST be less than or equal to 255 characters long. - - - GROUPING: There are three different types of grouping: numeric grouping, date grouping, and discrete grouping. - Numeric grouping combines numeric cache items into ranges of values. Date grouping combines date cache items into date ranges. - Discrete grouping combines specifically selected cache items into groups. - The cache field that contains the cache items that are to be grouped is called the base cache field. - The resultant cache field that contains the groups of cache items is called the parent grouping cache field. - Each group of cache items in the base cache field is associated with a single cache item in the parent grouping cache field. - Often cache items in parent grouping cache fields can be further grouped, creating a hierarchy of parent grouping cache fields. - The base cache field is at the lowest level of the hierarchy. - * + * A - fAllAtoms (1 bit): A bit that specifies whether this cache field has a collection of cache items. If fSomeUnhashed is equal to 1, this value MUST be equal to 0. + * B - fSomeUnhashed (1 bit): Undefined, and MUST be ignored. If the fAllAtoms field is equal to 1, MUST be equal to 0. + * C - fUsed (1 bit): Undefined, and MUST be ignored. + * D - fHasParent (1 bit): A bit that specifies whether ifdbParent specifies a reference to a parent grouping cache field. For more information, see Grouping. If the fCalculatedField field is equal to 1, then this field MUST be equal to 0. + * E - fRangeGroup (1 bit): A bit that specifies whether this cache field is grouped by using numeric grouping or date grouping, as specified by Grouping. If this field is equal to 1, then this record MUST be followed by a sequence of SXString records, as specified by the GRPSXOPER rule. The quantity of SXString records is specified by csxoper. If this field is equal to 1, then this record MUST be followed by a sequence of records that conforms to the SXRANGE rule that specifies the grouping properties for the ranges of values. + * F - fNumField (1 bit): A bit that specifies whether the cache items in this cache field contain at least one numeric cache item, as specified by SXNum. If fDateInField is equal to 1, this field MUST be equal to 0. + * G - unused1 (1 bit): Undefined and MUST be ignored. + * H - fTextEtcField (1 bit): A bit that specifies whether the cache items contain text data. If fNumField is 1, this field MUST be ignored. + * I - fnumMinMaxValid (1 bit): A bit that specifies whether a valid minimum or maximum value can be computed for the cache field. MUST be equal to 1 if fDateInField or fNumField is equal to 1. + * J - fShortIitms (1 bit): A bit that specifies whether there are more than 255 cache items in this cache field. If catm is greater than 255, this value MUST be equal to 1; otherwise it MUST be 0. + * K - fNonDates (1 bit): A bit that specifies whether the cache items in this cache field contain values that are not time or date values. If this cache field is a grouping cache field, as specified by Grouping, then this field MUST be ignored. Otherwise, if fDateInField is equal to 1, then this field MUST be 0. + * L - fDateInField (1 bit): A bit that specifies whether the cache items in this cache field contain at least one time or date cache item, as specified by SXDtr. If fNonDates is equal to 1, then this field MUST be equal to 0. + * M - unused2 (1 bit): Undefined and MUST be ignored. + * N - fServerBased (1 bit): A bit that specifies whether this cache field is a server-based page field when the corresponding pivot field is on the page axis of the PivotTable view, as specified in source data. + * This value applies only to an ODBC PivotCache. MUST NOT be equal to 1 if fCantGetUniqueItems is equal to 1. If fCantGetUniqueItems is equal to 1, then the ODBC connection cannot provide a list of unique items for the cache field. + * MUST be 0 for a cache field in a non-ODBC PivotCache. + * O - fCantGetUniqueItems (1 bit): A bit that specifies whether a list of unique values for the cache field was not available while refreshing the source data. This field applies only to a PivotCache that uses ODBC source data and is intended to be used in conjunction with optimization features. For example, the application can optimize memory usage when populating PivotCache records if it has a list of unique values for a cache field before all the records are retrieved from the ODBC connection. Or, the application can determine the appropriate setting of fServerBased based on this value. + * MUST be 0 for fields in a non-ODBC PivotCache. + * P - fCalculatedField (1 bit): A bit that specifies whether this field is a calculated field. The formula (section 2.2.2) of the calculated field is stored in a directly following SXFormula record. If fHasParent is equal to 1, this field MUST be equal to 0. + *

        + * ifdbParent (2 bytes): An unsigned integer that specifies the cache field index, + * as specified by Cache Fields, of the grouping cache field for this cache field. MUST be greater than or equal to 0x0000 and less than or equal to the cfdbTot field of the SXDB record of this PivotCache. If fHasParent is equal to 0, then this field MUST be ignored. If fHasParent is equal to 1, and fRangeGroup is equal to 1, and the iByType field of the SXRng record of this cache field is greater than 0, then the fRangeGroup of the SXFDB record of the cache field specified by ifdbParent MUST be 1 and the iByType field of the SXRng record of the cache field + * specified by ifdbParent MUST be greater than the iByType field of the SXRng record of this cache field. + *

        + * ifdbBase (2 bytes): An unsigned integer that specifies the cache field index, as specified by Cache Fields, of the base cache field, + * as specified by Grouping, for the cache field specified by this record. + * MUST be greater than or equal to 0x0000 and less than the value of the cfdbdb field of the SXDB record of this PivotCache. + * If the cache field specified by this record is not a grouping cache field, then this field MUST be ignored. + *

        + * citmUnq (2 bytes): Undefined and MUST be ignored. + *

        + * csxoper (2 bytes): An unsigned integer that specifies the number of cache items in this cache field when this cache field is a grouping cache field, + * as specified by Grouping. There MUST be an equivalent number of sequences of records that conform to the GRPSXOPER rule + * following this record that specify the cache items. If the fRangeGroup field and the fCalculatedField field are equal to 0 + * and this cache field corresponds to a source data entity, this field MUST be equal to 0. + * If the fRangeGroup field is equal to 1, this value MUST be greater than or equal to 1. + *

        + * cisxoper (2 bytes): An unsigned integer that specifies the number of cache items in the base cache field that are grouped by this cache field. + * There MUST be an equivalent number of SxIsxoper records following this record that specify which cache item in this cache + * field groups each of the cache items in the base cache field. For more information, see Grouping. + *

        + * catm (2 bytes): An unsigned integer that specifies the number of cache items in the collection sequences of records that conform to the + * SRCSXOPER rule in this cache field. If fAllAtoms is 0, then this field MUST be equal to 0x0000. + * If this cache field corresponds to source data entities then there MUST be an equal number of SRCSXOPER rules in this cache field. + *

        + * stFieldName (variable): An XLUnicodeString structure that specifies the name of the cache field. MUST be less than or equal to 255 characters long. + *

        + *

        + * GROUPING: There are three different types of grouping: numeric grouping, date grouping, and discrete grouping. + * Numeric grouping combines numeric cache items into ranges of values. Date grouping combines date cache items into date ranges. + * Discrete grouping combines specifically selected cache items into groups. + * The cache field that contains the cache items that are to be grouped is called the base cache field. + * The resultant cache field that contains the groups of cache items is called the parent grouping cache field. + * Each group of cache items in the base cache field is associated with a single cache item in the parent grouping cache field. + * Often cache items in parent grouping cache fields can be further grouped, creating a hierarchy of parent grouping cache fields. + * The base cache field is at the lowest level of the hierarchy. */ -import java.io.UnsupportedEncodingException; -import java.util.Arrays; - import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; + public class SxFDB extends XLSRecord implements XLSConstants, PivotCacheRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 9027599480633995587L; - private short ifdbParent, ifdbBase, csxoper, cisxoper, catm, grbit; - private String stFieldName; - // significant bit fields - boolean fAllAtoms, fRangeGroup, fNumField, fTextEtcField, fnumMinMaxValid, fNonDates, fDateInField, fCalculatedField, fShortItms ; - + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 9027599480633995587L; + private short ifdbParent, ifdbBase, csxoper, cisxoper, catm, grbit; + private String stFieldName; + // significant bit fields + boolean fAllAtoms, fRangeGroup, fNumField, fTextEtcField, fnumMinMaxValid, fNonDates, fDateInField, fCalculatedField, fShortItms; + // TODO: handle ranges/grouping and all the complications that it entails - public void init(){ + public void init() { super.init(); - grbit= ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); // grouping-related - ifdbParent= ByteTools.readShort(this.getByteAt(2),this.getByteAt(3)); // specifies the cache field index, as specified by Cache Fields, of the grouping cache field for this cache field - ifdbBase= ByteTools.readShort(this.getByteAt(4),this.getByteAt(5)); // specifies the cache field index, as specified by Cache Fields, of the base cache field + ifdbParent = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); // specifies the cache field index, as specified by Cache Fields, of the grouping cache field for this cache field + ifdbBase = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); // specifies the cache field index, as specified by Cache Fields, of the base cache field //ignored: citmUnq= ByteTools.readShort(this.getByteAt(6),this.getByteAt(7)); // "Undefined and MUST be ignored." - csxoper= ByteTools.readShort(this.getByteAt(8),this.getByteAt(9)); // specifies the number of cache items in this cache field when this cache field is a grouping cache field, - cisxoper= ByteTools.readShort(this.getByteAt(10),this.getByteAt(11)); // specifies the number of cache items in the base cache field that are grouped by this cache field + csxoper = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); // specifies the number of cache items in this cache field when this cache field is a grouping cache field, + cisxoper = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); // specifies the number of cache items in the base cache field that are grouped by this cache field // + fHasParent, fRangeGroup ... - catm= ByteTools.readShort(this.getByteAt(12),this.getByteAt(13)); // number of cache items - int cch= ByteTools.readShort(this.getByteAt(14),this.getByteAt(15)); + catm = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); // number of cache items + int cch = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); if (cch > 0) { // 0xFFFF if none - byte encoding= this.getByteAt(16); - byte[] tmp = this.getBytesAt(17, (cch) * (encoding+1)); - try{ - if (encoding==0) - stFieldName = new String(tmp, DEFAULTENCODING); - else - stFieldName = new String(tmp, UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("SXString.init: " + e);} + byte encoding = this.getByteAt(16); + byte[] tmp = this.getBytesAt(17, (cch) * (encoding + 1)); + try { + if (encoding == 0) + stFieldName = new String(tmp, DEFAULTENCODING); + else + stFieldName = new String(tmp, UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("SXString.init: " + e); + } } - fAllAtoms= (grbit & 0x1)==0x1; - // KSC: TESTING + fAllAtoms = (grbit & 0x1) == 0x1; + // KSC: TESTING //if (!fAllAtoms) // Logger.logWarn("SXFDB: not all cache items are used"); - fRangeGroup= (grbit & 0x10)==0x10; // A bit that specifies whether this cache field is grouped by using numeric grouping or date grouping, as specified by Grouping. - fNumField= (grbit & 0x20)==0x20; // at least one numeric field (SxNum records follow) - fTextEtcField= (grbit & 0x80)==0x80; // text field (SxString records follow)... if fNumField must be false - fnumMinMaxValid= (grbit & 0x100)==0x100; // true date or num field - fShortItms= (grbit & 0x200)==0x200; // true if > 255 cache items - fNonDates= (grbit & 0x400)==0x400; - fDateInField= (grbit & 0x4000)==0x4000; // at least one date field (SxDtr follows) - fCalculatedField= (grbit & 0x8000)==0x8000; // Sxformulas follow "A calculated field is a cache field (section 2.2.5.3.5) and does not correspond to a column in the source data (section 2.2.5.3.2). The values for a calculated field are calculated based on the formula specified for the calculated field" - if (DEBUGLEVEL > 3) - Logger.logInfo(this.toString()); + fRangeGroup = (grbit & 0x10) == 0x10; // A bit that specifies whether this cache field is grouped by using numeric grouping or date grouping, as specified by Grouping. + fNumField = (grbit & 0x20) == 0x20; // at least one numeric field (SxNum records follow) + fTextEtcField = (grbit & 0x80) == 0x80; // text field (SxString records follow)... if fNumField must be false + fnumMinMaxValid = (grbit & 0x100) == 0x100; // true date or num field + fShortItms = (grbit & 0x200) == 0x200; // true if > 255 cache items + fNonDates = (grbit & 0x400) == 0x400; + fDateInField = (grbit & 0x4000) == 0x4000; // at least one date field (SxDtr follows) + fCalculatedField = (grbit & 0x8000) == 0x8000; // Sxformulas follow "A calculated field is a cache field (section 2.2.5.3.5) and does not correspond to a column in the source data (section 2.2.5.3.2). The values for a calculated field are calculated based on the formula specified for the calculated field" + if (DEBUGLEVEL > 3) + Logger.logInfo(this.toString()); + } + + public String toString() { + return String.format("SXFDB -p: %d d: %d csxoper: %d cisxoper: %d catm: %d stFieldName: %s fAllAtoms? %b text? %b num? %b calculated? %b", ifdbParent, ifdbBase, csxoper, cisxoper, catm, stFieldName, fAllAtoms, fTextEtcField, fNumField, fCalculatedField); + } + + /** + * return the bytes describing this record, including the header + * @return + */ + public byte[] getRecord() { + byte[] b = new byte[4]; + System.arraycopy(ByteTools.shortToLEBytes(this.getOpcode()), 0, b, 0, 2); + System.arraycopy(ByteTools.shortToLEBytes((short) this.getData().length), 0, b, 2, 2); + return ByteTools.append(this.getData(), b); + } - - public String toString() { - return String.format("SXFDB -p: %d d: %d csxoper: %d cisxoper: %d catm: %d stFieldName: %s fAllAtoms? %b text? %b num? %b calculated? %b", ifdbParent, ifdbBase, csxoper, cisxoper, catm, stFieldName, fAllAtoms, fTextEtcField, fNumField, fCalculatedField); - } - /** - * return the bytes describing this record, including the header - * @return - */ - public byte[] getRecord() { - byte[] b= new byte[4]; - System.arraycopy(ByteTools.shortToLEBytes(this.getOpcode()), 0, b, 0, 2); - System.arraycopy(ByteTools.shortToLEBytes((short)this.getData().length), 0, b, 2, 2); - return ByteTools.append(this.getData(), b); - } - - private byte[] PROTOTYPE_BYTES = new byte[] { + private byte[] PROTOTYPE_BYTES = new byte[]{ // 1, 20, /* grbit 5121 -- fAllAtoms no dates or nums...*/ - -128, 4, /* grbit 1152 -- the most basic setting i.e. no fAllAtoms ...*/ - 0, 0, - 0, 0, - 1, 0, /* unique count -- ignored?? */ - 0, 0, - 0, 0, /* cisxoper */ - 0, 0, /* catm */ - -1, -1}; // cch - - public static XLSRecord getPrototype() { - SxFDB sxfdb= new SxFDB(); - sxfdb.setOpcode(SXFDB); - sxfdb.setData(sxfdb.PROTOTYPE_BYTES); - sxfdb.init(); - return sxfdb; - } - - /** - * sets the number of cache items (column cells in range) - * TODO: only set fAllAtoms if cache item is source data and not calculated ... - * - * @param n the number of cache items is the number of cache items actually on axes - */ - public void setNCacheItems(int n) { - catm= (short)n; // If this cache field corresponds to source data entities then there MUST be an equal number of SRCSXOPER rules in this cache field. - byte[] b= ByteTools.shortToLEBytes(catm); - this.getData()[12]= b[0]; - this.getData()[13]= b[1]; - - if (n > 0) { - // set that this has atoms (placed on pivot table axes) - fAllAtoms= true; - grbit |= 0x1; - } else { - fAllAtoms= false; - grbit &= ~0x1; // turn off fAllAtoms ... - } - b= ByteTools.shortToLEBytes(grbit); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; - } - - /** - * returns the number of cache items (column cells in range) - * @return - */ - public int getNCacheItems() { return catm; } - - /** - * sets the cache field name == cache row cell label - * @param s - */ - public void setCacheField(String s) { - stFieldName= s; - byte[] strbytes= new byte[0]; - if (stFieldName!=null) { - try{ - strbytes = stFieldName.getBytes(DEFAULTENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("SxFDB: " + e);} - } - - short cch= (short)strbytes.length; - byte[] nm = ByteTools.shortToLEBytes(cch); - byte[] data= new byte[14]; - System.arraycopy(this.getData(), 0, data, 0, 14); - byte[] newdata = new byte[cch + 3]; // account for encoding bytes + cch - System.arraycopy(nm, 0, newdata, 0, 2); - System.arraycopy(strbytes, 0, newdata, 3, cch); - - data= ByteTools.append(newdata, data); - this.setData(data); - } - - /** - * returns the cache field name == cache row cell label - */ - public String getCachefield() { return stFieldName; } - - /** - * sets the type of the cache items within this cache field (== the column cells below the row header field in the pivot cache range) - * @param type - */ - public void setCacheItemsType(int type) { - switch (type) { - case XLSConstants.TYPE_STRING: - fTextEtcField= true; - fNumField= false; - fNonDates= true; - fDateInField= false; - fnumMinMaxValid= false; - fCalculatedField= false; - break; - case XLSConstants.TYPE_FP: - case XLSConstants.TYPE_INT: - case XLSConstants.TYPE_DOUBLE: - fTextEtcField= false; - fNumField= true; - fNonDates= true; - fDateInField= false; - fnumMinMaxValid= true; - fCalculatedField= false; - break; - case TYPE_FORMULA: - fCalculatedField= true; - break; - //TYPE_BOOLEAN = 4, - case 6:// date - fTextEtcField= false; - fNumField= false; - fNonDates= false; - fDateInField= true; - fnumMinMaxValid= true; - fCalculatedField= false; - } - if (fNumField) - grbit |= 0x20; - else - grbit &= ~0x20; - if (fTextEtcField) - grbit |= 0x80; - else - grbit &= ~0x80; - if (fnumMinMaxValid) - grbit |= 0x100; - else - grbit &= ~0x100; - if (fNonDates) - grbit |= 0x400; - else - grbit &= ~0x400; - if (fDateInField) - grbit |= 0x4000; - else - grbit &= ~0x4000; - if (fCalculatedField) - grbit |= 0x8000; - else - grbit &= ~0x8000; - - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; - } + -128, 4, /* grbit 1152 -- the most basic setting i.e. no fAllAtoms ...*/ + 0, 0, + 0, 0, + 1, 0, /* unique count -- ignored?? */ + 0, 0, + 0, 0, /* cisxoper */ + 0, 0, /* catm */ + -1, -1}; // cch + + public static XLSRecord getPrototype() { + SxFDB sxfdb = new SxFDB(); + sxfdb.setOpcode(SXFDB); + sxfdb.setData(sxfdb.PROTOTYPE_BYTES); + sxfdb.init(); + return sxfdb; + } + + /** + * sets the number of cache items (column cells in range) + * TODO: only set fAllAtoms if cache item is source data and not calculated ... + * + * @param n the number of cache items is the number of cache items actually on axes + */ + public void setNCacheItems(int n) { + catm = (short) n; // If this cache field corresponds to source data entities then there MUST be an equal number of SRCSXOPER rules in this cache field. + byte[] b = ByteTools.shortToLEBytes(catm); + this.getData()[12] = b[0]; + this.getData()[13] = b[1]; + + if (n > 0) { + // set that this has atoms (placed on pivot table axes) + fAllAtoms = true; + grbit |= 0x1; + } else { + fAllAtoms = false; + grbit &= ~0x1; // turn off fAllAtoms ... + } + b = ByteTools.shortToLEBytes(grbit); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } + + /** + * returns the number of cache items (column cells in range) + * @return + */ + public int getNCacheItems() { + return catm; + } + + /** + * sets the cache field name == cache row cell label + * @param s + */ + public void setCacheField(String s) { + stFieldName = s; + byte[] strbytes = new byte[0]; + if (stFieldName != null) { + try { + strbytes = stFieldName.getBytes(DEFAULTENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("SxFDB: " + e); + } + } + + short cch = (short) strbytes.length; + byte[] nm = ByteTools.shortToLEBytes(cch); + byte[] data = new byte[14]; + System.arraycopy(this.getData(), 0, data, 0, 14); + byte[] newdata = new byte[cch + 3]; // account for encoding bytes + cch + System.arraycopy(nm, 0, newdata, 0, 2); + System.arraycopy(strbytes, 0, newdata, 3, cch); + + data = ByteTools.append(newdata, data); + this.setData(data); + } + + /** + * returns the cache field name == cache row cell label + */ + public String getCachefield() { + return stFieldName; + } + + /** + * sets the type of the cache items within this cache field (== the column cells below the row header field in the pivot cache range) + * @param type + */ + public void setCacheItemsType(int type) { + switch (type) { + case XLSConstants.TYPE_STRING: + fTextEtcField = true; + fNumField = false; + fNonDates = true; + fDateInField = false; + fnumMinMaxValid = false; + fCalculatedField = false; + break; + case XLSConstants.TYPE_FP: + case XLSConstants.TYPE_INT: + case XLSConstants.TYPE_DOUBLE: + fTextEtcField = false; + fNumField = true; + fNonDates = true; + fDateInField = false; + fnumMinMaxValid = true; + fCalculatedField = false; + break; + case TYPE_FORMULA: + fCalculatedField = true; + break; + //TYPE_BOOLEAN = 4, + case 6:// date + fTextEtcField = false; + fNumField = false; + fNonDates = false; + fDateInField = true; + fnumMinMaxValid = true; + fCalculatedField = false; + } + if (fNumField) + grbit |= 0x20; + else + grbit &= ~0x20; + if (fTextEtcField) + grbit |= 0x80; + else + grbit &= ~0x80; + if (fnumMinMaxValid) + grbit |= 0x100; + else + grbit &= ~0x100; + if (fNonDates) + grbit |= 0x400; + else + grbit &= ~0x400; + if (fDateInField) + grbit |= 0x4000; + else + grbit &= ~0x4000; + if (fCalculatedField) + grbit |= 0x8000; + else + grbit &= ~0x8000; + + byte[] b = ByteTools.shortToLEBytes(grbit); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } } diff --git a/src/main/java/io/starter/formats/XLS/SxPI.java b/src/main/java/io/starter/formats/XLS/SxPI.java index 19bc9fd..4e40759 100644 --- a/src/main/java/io/starter/formats/XLS/SxPI.java +++ b/src/main/java/io/starter/formats/XLS/SxPI.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,33 +22,32 @@ */ package io.starter.formats.XLS; -import java.util.Arrays; - import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.util.Arrays; + /** - * * SXPI 0x86 - * + *

        * The SXPI record specifies the pivot fields and information about filtering on * the page axis of a PivotTable view. MUST exist if and only if the value of * the cDimPg field of the SxView record of the PivotTable view is greater than * zero. - * + *

        * rgsxpi (variable): An array of SXPI_Items that specifies the pivot fields and * information about filtering on the page axis of a PivotTable view. The number * of array elements MUST equal the value of the cDimPg field of the SxView * record of the PivotTable view. - * + *

        * The SXPI_Item structure specifies information about a pivot field and its * filtering on the page axis of a PivotTable view. - * + *

        * isxvd (2 bytes): A signed integer that specifies a pivot field index as * specified by Pivot Fields. The referenced pivot field is specified to be on * the page axis. MUST be greater than or equal to zero and less than the cDim * field of the SxView record of the PivotTable view. - * + *

        * isxvi (2 bytes): A signed integer that specifies the pivot item used for the * page axis filtering. MUST be a value from the following table: Value Meaning * 0x0000 to 0x7FFC This value specifies a pivot item index that specifies a @@ -58,110 +57,114 @@ * non-OLAP PivotTable view the value MUST be 0x7FFD or greater than or equal to * zero and less than the cItm field of the Sxvd record of the pivot field. * Otherwise the value MUST be 0x7FFD. - * + *

        * idObj (2 bytes): A signed integer that specifies the object identifier of the * Obj record with the page item drop-down arrow. - * - * - * */ public class SxPI extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - private SXPI_Item[] sxpis; - public void init() { - super.init(); - byte[] rgsxpi= this.getData(); // each item is 6 bytes - if (rgsxpi!=null) { - if ((rgsxpi.length % 6)!=0) - Logger.logWarn("PivotTable: Irregular SxPI structure"); - sxpis= new SXPI_Item[rgsxpi.length/6]; - for (int j= 0; j< sxpis.length; j++) { - sxpis[j]= new SXPI_Item(rgsxpi, j*6); - } - if (DEBUGLEVEL > 3) Logger.logInfo("SXPI - n: " + sxpis.length + ": " + Arrays.toString(data)); - } else - if (DEBUGLEVEL > 3) Logger.logInfo("SXPI - NULL"); - } - - public String toString() { - if (sxpis!=null) - return "SXPI - n: " + sxpis.length + ": " + Arrays.toString(sxpis); - else - return "SXPI - NULL"; - } - /** - * returns the pivot field index and item index for page axis field i - * @param i page axis field - * @return - */ + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2639291289806138985L; + private SXPI_Item[] sxpis; + + public void init() { + super.init(); + byte[] rgsxpi = this.getData(); // each item is 6 bytes + if (rgsxpi != null) { + if ((rgsxpi.length % 6) != 0) + Logger.logWarn("PivotTable: Irregular SxPI structure"); + sxpis = new SXPI_Item[rgsxpi.length / 6]; + for (int j = 0; j < sxpis.length; j++) { + sxpis[j] = new SXPI_Item(rgsxpi, j * 6); + } + if (DEBUGLEVEL > 3) Logger.logInfo("SXPI - n: " + sxpis.length + ": " + Arrays.toString(data)); + } else if (DEBUGLEVEL > 3) Logger.logInfo("SXPI - NULL"); + } + + public String toString() { + if (sxpis != null) + return "SXPI - n: " + sxpis.length + ": " + Arrays.toString(sxpis); + else + return "SXPI - NULL"; + } + + /** + * returns the pivot field index and item index for page axis field i + * + * @param i page axis field + * @return + */ public int[] getPivotFieldItem(int i) { - if (i >=0 && i < sxpis.length) { - return new int[] { sxpis[i].isxvd, sxpis[i].idObj }; - } - return new int[] {-1, -1}; + if (i >= 0 && i < sxpis.length) { + return new int[]{sxpis[i].isxvd, sxpis[i].idObj}; + } + return new int[]{-1, -1}; } /** * sets the pivot field index and item index for page axis field i + * * @param i * @param fieldindex * @param itemindex */ public void setPageFieldIndex(int i, int fieldindex, int itemindex) { - if (i >=0 && i < sxpis.length) { - sxpis[i].isxvd= (short)fieldindex; - sxpis[i].isxvi= (short)itemindex; - byte[] b= ByteTools.shortToLEBytes((short)fieldindex); - this.getData()[(i*6)]= b[0]; - this.getData()[(i*6)+1]= b[1]; - b= ByteTools.shortToLEBytes((short)itemindex); - this.getData()[(i*6)+2]= b[0]; - this.getData()[(i*6)+3]= b[1]; - } + if (i >= 0 && i < sxpis.length) { + sxpis[i].isxvd = (short) fieldindex; + sxpis[i].isxvi = (short) itemindex; + byte[] b = ByteTools.shortToLEBytes((short) fieldindex); + this.getData()[(i * 6)] = b[0]; + this.getData()[(i * 6) + 1] = b[1]; + b = ByteTools.shortToLEBytes((short) itemindex); + this.getData()[(i * 6) + 2] = b[0]; + this.getData()[(i * 6) + 3] = b[1]; + } } - + public static XLSRecord getPrototype() { - SxPI sp= new SxPI(); - sp.setOpcode(SXPI); - // no data, initially??? - sp.setData(new byte[] {}); - sp.init(); - return sp; + SxPI sp = new SxPI(); + sp.setOpcode(SXPI); + // no data, initially??? + sp.setData(new byte[]{}); + sp.init(); + return sp; } - + /** * add a pivot field to the page axis + * * @param fieldIndex * @param itemIndex */ public void addPageField(int fieldIndex, int itemIndex) { - getData(); - data= ByteTools.append(new byte[6], data); - SXPI_Item[] tmp= new SXPI_Item[sxpis.length+1]; - System.arraycopy(sxpis, 0, tmp, 0, sxpis.length); - sxpis= tmp; - sxpis[sxpis.length-1]= new SXPI_Item(); + getData(); + data = ByteTools.append(new byte[6], data); + SXPI_Item[] tmp = new SXPI_Item[sxpis.length + 1]; + System.arraycopy(sxpis, 0, tmp, 0, sxpis.length); + sxpis = tmp; + sxpis[sxpis.length - 1] = new SXPI_Item(); + + setPageFieldIndex(sxpis.length - 1, fieldIndex, itemIndex); - setPageFieldIndex(sxpis.length-1, fieldIndex, itemIndex); - } - + } + /** * helper class defines SxPI structure - * */ class SXPI_Item { - public short isxvd; // pivot field index - public short isxvi; // specifies the pivot item used for the page axis filtering; if 0x7FFD This valuespecifies all pivot items, otherwise it's item index of pivot field (isxvd) - public short idObj; - SXPI_Item() {} - SXPI_Item(byte[] rgsxpi, int idx) { - isxvd= ByteTools.readShort(rgsxpi[idx++], rgsxpi[idx++]); - isxvi= ByteTools.readShort(rgsxpi[idx++], rgsxpi[idx++]); - idObj= ByteTools.readShort(rgsxpi[idx++], rgsxpi[idx++]); - } + public short isxvd; // pivot field index + public short isxvi; // specifies the pivot item used for the page axis filtering; if 0x7FFD This valuespecifies all pivot items, otherwise it's item index of pivot field (isxvd) + public short idObj; + + SXPI_Item() { + } + + SXPI_Item(byte[] rgsxpi, int idx) { + isxvd = ByteTools.readShort(rgsxpi[idx++], rgsxpi[idx++]); + isxvi = ByteTools.readShort(rgsxpi[idx++], rgsxpi[idx++]); + idObj = ByteTools.readShort(rgsxpi[idx++], rgsxpi[idx++]); + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/SxStreamID.java b/src/main/java/io/starter/formats/XLS/SxStreamID.java index 9231d23..0686ff9 100644 --- a/src/main/java/io/starter/formats/XLS/SxStreamID.java +++ b/src/main/java/io/starter/formats/XLS/SxStreamID.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,194 +22,208 @@ */ package io.starter.formats.XLS; -import java.util.ArrayList; - import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.SxAddl.SxcCache; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.util.ArrayList; + /** * SXStreamID 0xD5 * The SXStreamID record specifies the start of the stream in the PivotCache storage. - * - *idStm (2 bytes): An unsigned integer that specifies a stream in the PivotCache storage. The stream specified is the one that has its name equal to the hexadecimal representation of this field. The four-digit hexadecimal string representation of this field, where each hexadecimal letter digit is a capital letter, MUST be equal to the name of a stream (1) in the PivotCache storage. - + *

        + * idStm (2 bytes): An unsigned integer that specifies a stream in the PivotCache storage. The stream specified is the one that has its name equal to the hexadecimal representation of this field. The four-digit hexadecimal string representation of this field, where each hexadecimal letter digit is a capital letter, MUST be equal to the name of a stream (1) in the PivotCache storage. */ public class SxStreamID extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private short streamId=-1; - private static final long serialVersionUID = 2639291289806138985L; - private ArrayList subRecs= new ArrayList(); - /** - * init method - */ - public void init(){ - super.init(); - streamId= ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); + /** + * serialVersionUID + */ + private short streamId = -1; + private static final long serialVersionUID = 2639291289806138985L; + private ArrayList subRecs = new ArrayList(); + + /** + * init method + */ + public void init() { + super.init(); + streamId = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); if (DEBUGLEVEL > 3) Logger.logInfo("SXSTREAMID: streamid:" + streamId); - } - - /** - * store like cache-related records under SxStreamID - * @param r - */ + } + + /** + * store like cache-related records under SxStreamID + * + * @param r + */ public void addSubrecord(BiffRec r) { - subRecs.add(r); + subRecs.add(r); } - - /** - * creates new, default SxStreamID - * @return - */ + + /** + * creates new, default SxStreamID + * + * @return + */ public static XLSRecord getPrototype() { - SxStreamID ss = new SxStreamID(); - ss.setOpcode(SXSTREAMID); - ss.setData(new byte[] {0, 0}); - ss.init(); - return ss; + SxStreamID ss = new SxStreamID(); + ss.setOpcode(SXSTREAMID); + ss.setData(new byte[]{0, 0}); + ss.init(); + return ss; } + /** * returns the streamId -- index linked to appropriate SxView pivot table view + * * @return */ - public short getStreamID() { return streamId; } - - /** - * sets the streamId -- index linked to approriate SxView pivot table view - * @param sid - */ - public void setStreamID(int sid) { - streamId= (short) sid; - byte[] b= ByteTools.shortToLEBytes(streamId); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; - } - - - /** - * returns the cache data sources - *
        NOT FULLY IMPLEMENTED - only valid for sheet data range data soures - * @return - */ - public CellRange getCellRange() { - for (int i= 0; i < subRecs.size(); i++) { - BiffRec br= (BiffRec) subRecs.get(i); - if (br.getOpcode()==SXVS) { - if (((SxVS)br).getSourceType()!=SxVS.TYPE_SHEET) { - Logger.logErr("SXSTREAMID.getCellRange: Pivot Table Data Sources other than Sheet are not supported"); - return null; - } - } else if (br.getOpcode()==DCONREF) { - return ((DConRef)br).getCellRange(); - } else if (br.getOpcode()==DCONNAME) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return null; - } else if (br.getOpcode()==DCONBIN) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return null; - } - } - return null; - } - - /** - * sets the cell range for this pivot cache - * @param cr - */ - public void setCellRange(CellRange cr) { - for (int i= 0; i < subRecs.size(); i++) { - BiffRec br= (BiffRec) subRecs.get(i); - if (br.getOpcode()==SXVS) { - if (((SxVS)br).getSourceType()!=SxVS.TYPE_SHEET) { - Logger.logErr("SXSTREAMID.setCellRange: Pivot Table Data Sources other than Sheet are not supported"); - return; - } - } else if (br.getOpcode()==DCONREF) { - ((DConRef)br).setCellRange(cr); - return; - } else if (br.getOpcode()==DCONNAME) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return; - } else if (br.getOpcode()==DCONBIN) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return; - } - } - } - - /** - * sets the cell range for this pivot cache - * @param cr - */ - public void setCellRange(String cr) { - for (int i= 0; i < subRecs.size(); i++) { - BiffRec br= (BiffRec) subRecs.get(i); - if (br.getOpcode()==SXVS) { - if (((SxVS)br).getSourceType()!=SxVS.TYPE_SHEET) { - Logger.logErr("SXSTREAMID.setCellRange: Pivot Table Data Sources other than Sheet are not supported"); - return; - } - } else if (br.getOpcode()==DCONREF) { - ((DConRef)br).setCellRange(cr); - return; - } else if (br.getOpcode()==DCONNAME) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return; - } else if (br.getOpcode()==DCONBIN) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return; - } - } - } - - /** - * creates the basic, default records necessary to define a pivot cache - * @param bk - * @param ref string datasource range or named range reference - * @param sheetName string datasource sheetname where ref is located - * @return arraylist of records - */ - public ArrayList addInitialRecords(WorkBook bk, String ref, String sheetName) { - ArrayList initialrecs= new ArrayList(); - int sid= this.getStreamID(); - SxVS sxvs= (SxVS) SxVS.getPrototype(); - addInit(initialrecs, sxvs, bk); - if (bk.getName(ref)!=null) { - // DConName or DConBin - Logger.logErr("PivotCache: Name Data Sources are Not Supported"); - } else { // assume it's a regular reference - // DConRef - DConRef dc= (DConRef) DConRef.getPrototype(); - int[] rc= ExcelTools.getRangeRowCol(ref); - dc.setRange(rc, sheetName); - addInit(initialrecs, dc, bk); - } - // required SxAddl records: stores additional PivotTableView, PivotCache info of a variety of types - byte[] b= ByteTools.cLongToLEBytes(sid); b= ByteTools.append(new byte[] {0, 0}, b); // add 2 reserved bytes - SxAddl sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdId.sxd(), b); //4 bytes sid, 2 bytes reserved - addInit(initialrecs, sa, bk); - sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdVer10Info.sxd(), null); - addInit(initialrecs, sa, bk); - sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdVerSxMacro.sxd(), null); - addInit(initialrecs, sa, bk); - sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdEnd.sxd(), null); - addInit(initialrecs, sa, bk); - return initialrecs; - } + public short getStreamID() { + return streamId; + } + + /** + * sets the streamId -- index linked to approriate SxView pivot table view + * + * @param sid + */ + public void setStreamID(int sid) { + streamId = (short) sid; + byte[] b = ByteTools.shortToLEBytes(streamId); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } + + + /** + * returns the cache data sources + *
        NOT FULLY IMPLEMENTED - only valid for sheet data range data soures + * + * @return + */ + public CellRange getCellRange() { + for (int i = 0; i < subRecs.size(); i++) { + BiffRec br = (BiffRec) subRecs.get(i); + if (br.getOpcode() == SXVS) { + if (((SxVS) br).getSourceType() != SxVS.TYPE_SHEET) { + Logger.logErr("SXSTREAMID.getCellRange: Pivot Table Data Sources other than Sheet are not supported"); + return null; + } + } else if (br.getOpcode() == DCONREF) { + return ((DConRef) br).getCellRange(); + } else if (br.getOpcode() == DCONNAME) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); + return null; + } else if (br.getOpcode() == DCONBIN) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); + return null; + } + } + return null; + } + + /** + * sets the cell range for this pivot cache + * + * @param cr + */ + public void setCellRange(CellRange cr) { + for (int i = 0; i < subRecs.size(); i++) { + BiffRec br = (BiffRec) subRecs.get(i); + if (br.getOpcode() == SXVS) { + if (((SxVS) br).getSourceType() != SxVS.TYPE_SHEET) { + Logger.logErr("SXSTREAMID.setCellRange: Pivot Table Data Sources other than Sheet are not supported"); + return; + } + } else if (br.getOpcode() == DCONREF) { + ((DConRef) br).setCellRange(cr); + return; + } else if (br.getOpcode() == DCONNAME) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); + return; + } else if (br.getOpcode() == DCONBIN) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); + return; + } + } + } + + /** + * sets the cell range for this pivot cache + * + * @param cr + */ + public void setCellRange(String cr) { + for (int i = 0; i < subRecs.size(); i++) { + BiffRec br = (BiffRec) subRecs.get(i); + if (br.getOpcode() == SXVS) { + if (((SxVS) br).getSourceType() != SxVS.TYPE_SHEET) { + Logger.logErr("SXSTREAMID.setCellRange: Pivot Table Data Sources other than Sheet are not supported"); + return; + } + } else if (br.getOpcode() == DCONREF) { + ((DConRef) br).setCellRange(cr); + return; + } else if (br.getOpcode() == DCONNAME) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); + return; + } else if (br.getOpcode() == DCONBIN) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); + return; + } + } + } + + /** + * creates the basic, default records necessary to define a pivot cache + * + * @param bk + * @param ref string datasource range or named range reference + * @param sheetName string datasource sheetname where ref is located + * @return arraylist of records + */ + public ArrayList addInitialRecords(WorkBook bk, String ref, String sheetName) { + ArrayList initialrecs = new ArrayList(); + int sid = this.getStreamID(); + SxVS sxvs = (SxVS) SxVS.getPrototype(); + addInit(initialrecs, sxvs, bk); + if (bk.getName(ref) != null) { + // DConName or DConBin + Logger.logErr("PivotCache: Name Data Sources are Not Supported"); + } else { // assume it's a regular reference + // DConRef + DConRef dc = (DConRef) DConRef.getPrototype(); + int[] rc = ExcelTools.getRangeRowCol(ref); + dc.setRange(rc, sheetName); + addInit(initialrecs, dc, bk); + } + // required SxAddl records: stores additional PivotTableView, PivotCache info of a variety of types + byte[] b = ByteTools.cLongToLEBytes(sid); + b = ByteTools.append(new byte[]{0, 0}, b); // add 2 reserved bytes + SxAddl sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdId.sxd(), b); //4 bytes sid, 2 bytes reserved + addInit(initialrecs, sa, bk); + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdVer10Info.sxd(), null); + addInit(initialrecs, sa, bk); + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdVerSxMacro.sxd(), null); + addInit(initialrecs, sa, bk); + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdEnd.sxd(), null); + addInit(initialrecs, sa, bk); + return initialrecs; + } + /** - * utility function to properly add a Pivot Table View subrec + * utility function to properly add a Pivot Table View subrec + * * @param initialrecs * @param rec * @param addToInitRecords * @param sheet */ private void addInit(ArrayList initialrecs, XLSRecord rec, WorkBook bk) { - rec.setWorkBook(bk); - initialrecs.add(rec); - this.addSubrecord(rec); + rec.setWorkBook(bk); + initialrecs.add(rec); + this.addSubrecord(rec); } } diff --git a/src/main/java/io/starter/formats/XLS/SxVIEWEX9.java b/src/main/java/io/starter/formats/XLS/SxVIEWEX9.java index 9517ee0..881bd80 100644 --- a/src/main/java/io/starter/formats/XLS/SxVIEWEX9.java +++ b/src/main/java/io/starter/formats/XLS/SxVIEWEX9.java @@ -2,88 +2,87 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS; -/** - * SXViewEx9 0x810 - * - The SXViewEx9 record specifies extensions to the PivotTable view. - - rt (2 bytes): An unsigned integer that specifies the record type identifier. The value MUST be 0x0810. - - A - reserved1 (1 bit): MUST be zero, and MUST be ignored. - - B - fFrtAlert (1 bit): A bit that specifies whether features of this PivotTable are not supported in earlier versions of the BIFF. - An application can alert the user of possible problems when saving as an earlier version of the BIFF. - - reserved2 (14 bits): MUST be zero, and MUST be ignored. - - reserved3 (4 bytes): MUST be zero, and MUST be ignored. - - C - reserved4 (1 bit): MUST be zero, and MUST be ignored. - - D - fPrintTitles (1 bit): A bit that specifies whether the print titles for the worksheet are set based on the PivotTable report. - The row print titles are set to the pivot item captions on the column axis and the column print titles are set to the pivot item captions on the row axis. - - E - fLineMode (1 bit): A bit that specifies whether any pivot field is in outline mode. See Subtotalling for more information. - F - reserved5 (2 bits): MUST be zero, and MUST be ignored. - - G - fRepeatItemsOnEachPrintedPage (1 bit): A bit that specifies whether pivot item captions on the row axis are repeated on each printed page for pivot fields in tabular form. - - reserved6 (26 bits): MUST be zero, and MUST be ignored. - - itblAutoFmt (2 bytes): An AutoFmt8 structure that specifies the PivotTable AutoFormat. If the value of this field is not 1, - this field overrides the itblAutoFmt field in the previous SxView record. - - chGrand (variable): An XLUnicodeString structure that specifies a user-entered caption to display for grand totals when the PivotTable is recalculated. - The length MUST be less than or equal to 255 characters. - - - * +/** + * SXViewEx9 0x810 + *

        + * The SXViewEx9 record specifies extensions to the PivotTable view. + *

        + * rt (2 bytes): An unsigned integer that specifies the record type identifier. The value MUST be 0x0810. + *

        + * A - reserved1 (1 bit): MUST be zero, and MUST be ignored. + *

        + * B - fFrtAlert (1 bit): A bit that specifies whether features of this PivotTable are not supported in earlier versions of the BIFF. + * An application can alert the user of possible problems when saving as an earlier version of the BIFF. + *

        + * reserved2 (14 bits): MUST be zero, and MUST be ignored. + *

        + * reserved3 (4 bytes): MUST be zero, and MUST be ignored. + *

        + * C - reserved4 (1 bit): MUST be zero, and MUST be ignored. + *

        + * D - fPrintTitles (1 bit): A bit that specifies whether the print titles for the worksheet are set based on the PivotTable report. + * The row print titles are set to the pivot item captions on the column axis and the column print titles are set to the pivot item captions on the row axis. + *

        + * E - fLineMode (1 bit): A bit that specifies whether any pivot field is in outline mode. See Subtotalling for more information. + *

        + * F - reserved5 (2 bits): MUST be zero, and MUST be ignored. + *

        + * G - fRepeatItemsOnEachPrintedPage (1 bit): A bit that specifies whether pivot item captions on the row axis are repeated on each printed page for pivot fields in tabular form. + *

        + * reserved6 (26 bits): MUST be zero, and MUST be ignored. + *

        + * itblAutoFmt (2 bytes): An AutoFmt8 structure that specifies the PivotTable AutoFormat. If the value of this field is not 1, + * this field overrides the itblAutoFmt field in the previous SxView record. + *

        + * chGrand (variable): An XLUnicodeString structure that specifies a user-entered caption to display for grand totals when the PivotTable is recalculated. + * The length MUST be less than or equal to 255 characters. */ public class SxVIEWEX9 extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - public void init(){ - super.init(); - } - - private byte[] PROTOTYPE_BYTES = new byte[] { - 0x10, 0x8, /* rt= 0x810 */ - 00, 00, /* flags + reserved */ - 00, 00, - 00, 00, - 0x20, 00, - 00, 00, - 01, 00, /* itblAutoFmt*/ - 00, 00, 00 /* chGrand-- cch= 0, encoding= 0 */ + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2639291289806138985L; + + public void init() { + super.init(); + } + + private byte[] PROTOTYPE_BYTES = new byte[]{ + 0x10, 0x8, /* rt= 0x810 */ + 00, 00, /* flags + reserved */ + 00, 00, + 00, 00, + 0x20, 00, + 00, 00, + 01, 00, /* itblAutoFmt*/ + 00, 00, 00 /* chGrand-- cch= 0, encoding= 0 */ }; - + public static XLSRecord getPrototype() { - SxVIEWEX9 sxv= new SxVIEWEX9(); - sxv.setOpcode(SXVIEWEX9); - sxv.setData(sxv.PROTOTYPE_BYTES); - sxv.init(); - return sxv; + SxVIEWEX9 sxv = new SxVIEWEX9(); + sxv.setOpcode(SXVIEWEX9); + sxv.setData(sxv.PROTOTYPE_BYTES); + sxv.init(); + return sxv; } } diff --git a/src/main/java/io/starter/formats/XLS/SxVS.java b/src/main/java/io/starter/formats/XLS/SxVS.java index 5dd7a7e..a432bd8 100644 --- a/src/main/java/io/starter/formats/XLS/SxVS.java +++ b/src/main/java/io/starter/formats/XLS/SxVS.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,48 +27,52 @@ /** * SXVS 0xE3 - * The SXVS record specifies the type of source data used for a PivotCache. + * The SXVS record specifies the type of source data used for a PivotCache. * This record is followed by a sequence of records that specify additional information about the source data. - * + *

        * sxvs (2 bytes): An unsigned integer that specifies the type of source data used for the PivotCache. The types of records that follow this record are dictated by the value of this field. MUST be a value from the following table: + *

        + * Name Value Meaning + *

        + * SHEET 0x0001 Specifies that the source data is a range. This record MUST be followed by a DConRef record that specifies a simple range, or a DConName record that specifies a named range, or a DConBin record that specifies a built-in named range. + * EXTERNAL 0x0002 Specifies that external source data is used. This record MUST be followed by a sequence of records beginning with a DbQuery record that specifies connection and query information that is used to retrieve external data. + * CONSOLIDATION 0x0004 Specifies that multiple consolidation ranges are used as the source data. This record MUST be followed by a sequence of records beginning with an SXTbl record that specifies information about the multiple consolidation ranges. + * SCENARIO 0x0010 The source data is populated from a temporary internal structure. In this case there is no additional source data information because the raw data does not exist as a permanent structure and the logic to produce it is application-dependent. + */ +public class SxVS extends XLSRecord implements XLSConstants { + /** + * serialVersionUID + */ + private short sourceType = -1; + public static final short TYPE_SHEET = 0x1; + public static final short TYPE_EXTERNAL = 0x0002; + public static final short TYPE_CONSOLIDATION = 0x0004; + public static final short TYPE_SCENARIO = 0x0010; - Name Value Meaning + private static final long serialVersionUID = 2639291289806138985L; + + public void init() { + super.init(); + sourceType = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + if (DEBUGLEVEL > 3) Logger.logInfo("SXVS - sourceType:" + sourceType); + } + + public short getSourceType() { + return sourceType; + } + + public void setSourceType(int st) { + sourceType = (short) st; + byte[] b = ByteTools.shortToLEBytes(sourceType); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } - SHEET 0x0001 Specifies that the source data is a range. This record MUST be followed by a DConRef record that specifies a simple range, or a DConName record that specifies a named range, or a DConBin record that specifies a built-in named range. - EXTERNAL 0x0002 Specifies that external source data is used. This record MUST be followed by a sequence of records beginning with a DbQuery record that specifies connection and query information that is used to retrieve external data. - CONSOLIDATION 0x0004 Specifies that multiple consolidation ranges are used as the source data. This record MUST be followed by a sequence of records beginning with an SXTbl record that specifies information about the multiple consolidation ranges. - SCENARIO 0x0010 The source data is populated from a temporary internal structure. In this case there is no additional source data information because the raw data does not exist as a permanent structure and the logic to produce it is application-dependent. - * - * - */ -public class SxVS extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private short sourceType= -1; - public static final short TYPE_SHEET= 0x1; - public static final short TYPE_EXTERNAL= 0x0002; - public static final short TYPE_CONSOLIDATION= 0x0004; - public static final short TYPE_SCENARIO= 0x0010; - - private static final long serialVersionUID = 2639291289806138985L; - public void init(){ - super.init(); - sourceType= ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); - if (DEBUGLEVEL > 3) Logger.logInfo("SXVS - sourceType:" + sourceType); - } - public short getSourceType() { return sourceType; } - public void setSourceType(int st) { - sourceType= (short) st; - byte[] b= ByteTools.shortToLEBytes(sourceType); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; - } public static XLSRecord getPrototype() { - SxVS sv= new SxVS(); - sv.setOpcode(SXVS); - sv.setData(new byte[] {1, 0}); - sv.init(); - return sv; + SxVS sv = new SxVS(); + sv.setOpcode(SXVS); + sv.setData(new byte[]{1, 0}); + sv.init(); + return sv; } } diff --git a/src/main/java/io/starter/formats/XLS/SxVdEX.java b/src/main/java/io/starter/formats/XLS/SxVdEX.java index 9077c49..cf8182c 100644 --- a/src/main/java/io/starter/formats/XLS/SxVdEX.java +++ b/src/main/java/io/starter/formats/XLS/SxVdEX.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,133 +27,133 @@ /** * SxVDEx 0x100 - * + *

        * The SXVDEx record specifies extended pivot field properties. - * - A - fShowAllItems (1 bit): A bit that specifies whether to show all pivot items for this pivot field, - including pivot items that do not currently exist in the source data. The value MUST be 0 for an OLAP PivotTable view. - MUST be a value from the following table: - Value Meaning - 0x0 Specifies that all pivot items are not displayed. - 0x1 Specifies that all pivot items are displayed. - - B - fDragToRow (1 bit): A bit that specifies whether this pivot field can be placed on the row axis. This value MUST be ignored for an OLAP PivotTable view. - MUST be a value from the following table: - Value Meaning - 0x0 Specifies that the user is prevented from placing this pivot field on the row axis. - 0x1 Specifies that the user is not prevented from placing this pivot field on the row axis. - - C - fDragToColumn (1 bit): A bit that specifies whether this pivot field can be placed on the column axis. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: - Value Meaning - 0x0 Specifies that the user is prevented from placing this pivot field on the column axis. - 0x1 Specifies that the user is not prevented from placing this pivot field on the column axis. - - D - fDragToPage (1 bit): A bit that specifies whether this pivot field can be placed on the page axis. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: - Value Meaning - 0x0 Specifies that the user is prevented from placing this pivot field on the page axis. - 0x1 Specifies that the user is not prevented from placing this pivot field on the page axis. - - E - fDragToHide (1 bit): A bit that specifies whether this pivot field can be removed from the PivotTable view. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: - Value Meaning - 0x0 Specifies that the user is prevented from removing this pivot field from the PivotTable view. - 0x1 Specifies that the user is not prevented from removing this pivot field from the PivotTable view. - - F - fNotDragToData (1 bit): A bit that specifies whether this pivot field can be placed on the data axis. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: - Value Meaning - 0x0 Specifies that the user is not prevented from placing this pivot field on the data axis. - 0x1 Specifies that the user is prevented from placing this pivot field on the data axis. - - G - reserved1 (1 bit): MUST be zero, and MUST be ignored. - - H - fServerBased (1 bit): A bit that specifies whether this pivot field is server-based when on the page axis. For more information, see Source Data. - A value of 1 specifies that this pivot field is a server-based pivot field. - - MUST be 1 if and only if the value of the fServerBased field of the SXFDB record of the associated cache field of this pivot field is 1. - - I - reserved2 (1 bit): MUST be zero, and MUST be ignored. - - J - fAutoSort (1 bit): A bit that specifies whether AutoSort will be applied to this pivot field. For more information, see Pivot Field Sorting. - - K - fAscendSort (1 bit): A bit that specifies whether any AutoSort applied to this pivot field will sort in ascending order. MUST be a value from the following table: - Value Meaning - 0x0 Sort in descending order. - 0x1 Sort in ascending order. - - L - fAutoShow (1 bit): A bit that specifies whether an AutoShowfilter is applied to this pivot field. For more information, see Simple Filters. - - M - fTopAutoShow (1 bit): A bit that specifies whether any AutoShow filter applied to this pivot field shows the top-ranked or bottom-ranked values. For more information, see Simple Filters. MUST be a value from the following table: - Value Meaning - 0x0 Any AutoShow filter applied to this pivot field shows the bottom-ranked values. - 0x1 Any AutoShow filter applied to this pivot field shows the top-ranked values. - - N - fCalculatedField (1 bit): A bit that specifies whether this pivot field is a calculated field. A value of 1 specifies that this pivot field is a calculated field. - - MUST be 1 if and only if the value of the fCalculatedField field of the SXFDB record of the cache field associated with this pivot field is 1. - - O - fPageBreaksBetweenItems (1 bit): A bit that specifies whether a page break (2) is inserted after each pivot item when the PivotTable is printed. - - P - fHideNewItems (1 bit): A bit that specifies whether new pivot items that appear after a refresh are hidden by default. This value MUST be equal to 0 for a non-OLAP PivotTable view. - Value Meaning - 0x0 New pivot items are shown by default. - 0x1 New pivot items are hidden by default. - - reserved3 (5 bits): MUST be zero, and MUST be ignored. - - Q - fOutline (1 bit): A bit that specifies whether this pivot field is in outline form. For more information, see PivotTable layout. - - R - fInsertBlankRow (1 bit): A bit that specifies whether to insert a blank row after each pivot item. - - S - fSubtotalAtTop (1 bit): A bit that specifies whether subtotals are displayed at the top of the group when the fOutline field is equal to 1. For more information, see PivotTable layout. - - citmAutoShow (8 bits): An unsigned integer that specifies the number of pivot items to show when the fAutoShow field is equal to 1. - The value MUST be greater than or equal to 1 and less than or equal to 255. - - isxdiAutoSort (2 bytes): A signed integer that specifies the data item that AutoSort uses when the fAutoSort field is equal to 1. If the value of the fAutoSort field is one, - the value MUST be greater than or equal to zero and less than the count of SXDI records. MUST be a value from the following table: - Value Meaning - -1 Specifies that the values of the pivot items themselves are used. - Greater than or equal to zero Specifies a data item index, as specified in Data Items, of the data item that is used. - - isxdiAutoShow (2 bytes): A signed integer that specifies the data item that AutoShow ranks by when the fAutoShow field is equal to 1. - For more information, see Simple Filters. If the value of the fAutoShow field is 1, this value MUST be greater than or equal to zero and less than the count of SXDI records. - MUST be a value from the following table: - Value Meaning - -1 AutoShow is not enabled for this pivot field. - Greater than or equal to zero Specifies a data item index, as specified in Data Items, of the data item that is used. - - ifmt (2 bytes): An IFmt structure that specifies the number format of this pivot field. - - subName (variable): An optional SXVDEx_Opt structure that specifies the name of the aggregate function used to calculate this pivot field's subtotals. SHOULD<124> be present. - - * - * - * + *

        + * A - fShowAllItems (1 bit): A bit that specifies whether to show all pivot items for this pivot field, + * including pivot items that do not currently exist in the source data. The value MUST be 0 for an OLAP PivotTable view. + * MUST be a value from the following table: + * Value Meaning + * 0x0 Specifies that all pivot items are not displayed. + * 0x1 Specifies that all pivot items are displayed. + *

        + * B - fDragToRow (1 bit): A bit that specifies whether this pivot field can be placed on the row axis. This value MUST be ignored for an OLAP PivotTable view. + * MUST be a value from the following table: + * Value Meaning + * 0x0 Specifies that the user is prevented from placing this pivot field on the row axis. + * 0x1 Specifies that the user is not prevented from placing this pivot field on the row axis. + *

        + * C - fDragToColumn (1 bit): A bit that specifies whether this pivot field can be placed on the column axis. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: + * Value Meaning + * 0x0 Specifies that the user is prevented from placing this pivot field on the column axis. + * 0x1 Specifies that the user is not prevented from placing this pivot field on the column axis. + *

        + * D - fDragToPage (1 bit): A bit that specifies whether this pivot field can be placed on the page axis. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: + * Value Meaning + * 0x0 Specifies that the user is prevented from placing this pivot field on the page axis. + * 0x1 Specifies that the user is not prevented from placing this pivot field on the page axis. + *

        + * E - fDragToHide (1 bit): A bit that specifies whether this pivot field can be removed from the PivotTable view. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: + * Value Meaning + * 0x0 Specifies that the user is prevented from removing this pivot field from the PivotTable view. + * 0x1 Specifies that the user is not prevented from removing this pivot field from the PivotTable view. + *

        + * F - fNotDragToData (1 bit): A bit that specifies whether this pivot field can be placed on the data axis. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: + * Value Meaning + * 0x0 Specifies that the user is not prevented from placing this pivot field on the data axis. + * 0x1 Specifies that the user is prevented from placing this pivot field on the data axis. + *

        + * G - reserved1 (1 bit): MUST be zero, and MUST be ignored. + *

        + * H - fServerBased (1 bit): A bit that specifies whether this pivot field is server-based when on the page axis. For more information, see Source Data. + * A value of 1 specifies that this pivot field is a server-based pivot field. + *

        + * MUST be 1 if and only if the value of the fServerBased field of the SXFDB record of the associated cache field of this pivot field is 1. + *

        + * I - reserved2 (1 bit): MUST be zero, and MUST be ignored. + *

        + * J - fAutoSort (1 bit): A bit that specifies whether AutoSort will be applied to this pivot field. For more information, see Pivot Field Sorting. + *

        + * K - fAscendSort (1 bit): A bit that specifies whether any AutoSort applied to this pivot field will sort in ascending order. MUST be a value from the following table: + * Value Meaning + * 0x0 Sort in descending order. + * 0x1 Sort in ascending order. + *

        + * L - fAutoShow (1 bit): A bit that specifies whether an AutoShowfilter is applied to this pivot field. For more information, see Simple Filters. + *

        + * M - fTopAutoShow (1 bit): A bit that specifies whether any AutoShow filter applied to this pivot field shows the top-ranked or bottom-ranked values. For more information, see Simple Filters. MUST be a value from the following table: + * Value Meaning + * 0x0 Any AutoShow filter applied to this pivot field shows the bottom-ranked values. + * 0x1 Any AutoShow filter applied to this pivot field shows the top-ranked values. + *

        + * N - fCalculatedField (1 bit): A bit that specifies whether this pivot field is a calculated field. A value of 1 specifies that this pivot field is a calculated field. + *

        + * MUST be 1 if and only if the value of the fCalculatedField field of the SXFDB record of the cache field associated with this pivot field is 1. + *

        + * O - fPageBreaksBetweenItems (1 bit): A bit that specifies whether a page break (2) is inserted after each pivot item when the PivotTable is printed. + *

        + * P - fHideNewItems (1 bit): A bit that specifies whether new pivot items that appear after a refresh are hidden by default. This value MUST be equal to 0 for a non-OLAP PivotTable view. + * Value Meaning + * 0x0 New pivot items are shown by default. + * 0x1 New pivot items are hidden by default. + *

        + * reserved3 (5 bits): MUST be zero, and MUST be ignored. + *

        + * Q - fOutline (1 bit): A bit that specifies whether this pivot field is in outline form. For more information, see PivotTable layout. + *

        + * R - fInsertBlankRow (1 bit): A bit that specifies whether to insert a blank row after each pivot item. + *

        + * S - fSubtotalAtTop (1 bit): A bit that specifies whether subtotals are displayed at the top of the group when the fOutline field is equal to 1. For more information, see PivotTable layout. + *

        + * citmAutoShow (8 bits): An unsigned integer that specifies the number of pivot items to show when the fAutoShow field is equal to 1. + * The value MUST be greater than or equal to 1 and less than or equal to 255. + *

        + * isxdiAutoSort (2 bytes): A signed integer that specifies the data item that AutoSort uses when the fAutoSort field is equal to 1. If the value of the fAutoSort field is one, + * the value MUST be greater than or equal to zero and less than the count of SXDI records. MUST be a value from the following table: + * Value Meaning + * -1 Specifies that the values of the pivot items themselves are used. + * Greater than or equal to zero Specifies a data item index, as specified in Data Items, of the data item that is used. + *

        + * isxdiAutoShow (2 bytes): A signed integer that specifies the data item that AutoShow ranks by when the fAutoShow field is equal to 1. + * For more information, see Simple Filters. If the value of the fAutoShow field is 1, this value MUST be greater than or equal to zero and less than the count of SXDI records. + * MUST be a value from the following table: + * Value Meaning + * -1 AutoShow is not enabled for this pivot field. + * Greater than or equal to zero Specifies a data item index, as specified in Data Items, of the data item that is used. + *

        + * ifmt (2 bytes): An IFmt structure that specifies the number format of this pivot field. + *

        + * subName (variable): An optional SXVDEx_Opt structure that specifies the name of the aggregate function used to calculate this pivot field's subtotals. SHOULD<124> be present. */ public class SxVdEX extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - private short citmAutoShow, isxdiAutoSort, isxdiAutoShow, ifmt; - public void init(){ + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2639291289806138985L; + private short citmAutoShow, isxdiAutoSort, isxdiAutoShow, ifmt; + + public void init() { super.init(); // TODO: flags citmAutoShow = this.getByteAt(4); - isxdiAutoSort= ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - isxdiAutoShow= ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - ifmt= ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + isxdiAutoSort = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + isxdiAutoShow = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + ifmt = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); // TODO: subName (variable): An optional SXVDEx_Opt structure that specifies the name of the aggregate function used to calculate this pivot field's subtotals. SHOULD<124> be present. - - if (DEBUGLEVEL > 3) Logger.logInfo("SXVDEX - citmAutoShow:" + citmAutoShow + " isxdiAutoSort:" + isxdiAutoSort + " isxdoAutoShow:" + isxdiAutoShow + " ifmt:" + ifmt); - } - private byte[] PROTOTYPE_BYTES = new byte[] { // default configuration - 30, 20, 0, 10, -1, -1, -1, -1, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 + + if (DEBUGLEVEL > 3) + Logger.logInfo("SXVDEX - citmAutoShow:" + citmAutoShow + " isxdiAutoSort:" + isxdiAutoSort + " isxdoAutoShow:" + isxdiAutoShow + " ifmt:" + ifmt); + } + + private byte[] PROTOTYPE_BYTES = new byte[]{ // default configuration + 30, 20, 0, 10, -1, -1, -1, -1, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 }; + public static XLSRecord getPrototype() { - SxVdEX sv= new SxVdEX(); - sv.setOpcode(SXVDEX); - sv.setData(sv.PROTOTYPE_BYTES); - sv.init(); - return sv; + SxVdEX sv = new SxVdEX(); + sv.setOpcode(SXVDEX); + sv.setData(sv.PROTOTYPE_BYTES); + sv.init(); + return sv; } } diff --git a/src/main/java/io/starter/formats/XLS/Sxformat.java b/src/main/java/io/starter/formats/XLS/Sxformat.java index 49eb89e..b66e840 100644 --- a/src/main/java/io/starter/formats/XLS/Sxformat.java +++ b/src/main/java/io/starter/formats/XLS/Sxformat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,35 +24,35 @@ import io.starter.toolkit.Logger; -/** SXFORMAT FBh: This record contains formatting data - -

        -    offset  name        size    contents
        -    ---    
        -    4       rlType      2       0x0 = clear
        -                                0x1 = format applied
        -    6       cbData      2       length of data
        -    

        - -**/ - - -public class Sxformat extends XLSRecord implements XLSConstants{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8702313274711819140L; - byte[] data = null; - - public void init(){ +/** + * SXFORMAT FBh: This record contains formatting data + * + *

        + * offset  name        size    contents
        + * ---
        + * 4       rlType      2       0x0 = clear
        + * 0x1 = format applied
        + * 6       cbData      2       length of data
        + * 

        + **/ + + +public class Sxformat extends XLSRecord implements XLSConstants { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8702313274711819140L; + byte[] data = null; + + public void init() { super.init(); - if(this.getLength() <= 0){ // Is this record populated? - if(DEBUGLEVEL > -1)Logger.logInfo("no data in Sxformat"); + if (this.getLength() <= 0) { // Is this record populated? + if (DEBUGLEVEL > -1) Logger.logInfo("no data in Sxformat"); } else { // parse out all the fields // Logger.logInfo(ExcelTools.getRecordByteDef(this)); } } - - + + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxivd.java b/src/main/java/io/starter/formats/XLS/Sxivd.java index c24f5ec..f6bc484 100644 --- a/src/main/java/io/starter/formats/XLS/Sxivd.java +++ b/src/main/java/io/starter/formats/XLS/Sxivd.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,113 +22,115 @@ */ package io.starter.formats.XLS; -import java.util.Arrays; - import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.util.Arrays; -/** SXIVD B4h: Array of field ID numbers for the rows and columns in a PT. - - There are at most two of these recs per Table -- one for rows, one for cols. +/** + * SXIVD B4h: Array of field ID numbers for the rows and columns in a PT. + *

        + * There are at most two of these recs per Table -- one for rows, one for cols. + * + *

        + * offset  name        size    contents
        + * ---
        + * 4       rgisxvd     var     Array of 2 byte field ids (row or col)
        + *
        + * rgSxivd (variable): An array of SxIvdRw or SxIvdCol items.
        + * If this is an array of SxIvdRw,
        + * the count of elements in the array MUST equal the value of the cDimRw
        + * field of the SxView record.
        + *
        + * If this is an array of SxIvdCol,
        + * the count of elements in the array MUST equal the value of the cDimCol
        + * field of the SxView record.
        + *
        + * The SxIvdRw structure specifies a reference to a pivot field or data field on the row axis.
        + * The SxIvdCol structure specifies a reference to a pivot field or data field on the column axis
        + * rw (2 bytes): A signed integer that specifies a pivot field or data field for the row axis of the PivotTable view.
        + * MUST be a value from the following table:
        + * or
        + * col (2 bytes): A signed integer that specifies a pivot field or data field for the column axis of the PivotTable view.
        + * MUST be a value from the following table:
        + *
        + * Value	    Meaning
        + * -2		    This value specifies that the data field is on the row (or col) axis.
        + * The sxaxisRw (or sxaxisCol) field of sxaxis4Data of SxView record
        + * MUST equal 1 and the sxaxisData field of sxaxis4Data of the SxView record
        + * MUST equal zero.
        + * 0+		    This value specifies a pivot field index as specified in Pivot Fields.
        + * The pivot field index specifies a pivot field on the row (or col) axis
        + * of the PivotTable view.
        + * MUST be less than the cDim field of the SxView record of the PivotTable view.
        + * If the referenced pivot field is not a hidden field in an OLAP PivotTable view
        + * then the sxaxisRw (or sxaxisCol) field of SXAxis of the Sxvd record of the pivot
        + * field MUST equal 1.
        + *
        + * A pivot field is a hidden field if an SXAddl_SXCField12_SXDVer12Info record exists
        + * for the pivot field, and the fHiddenLvl field of the SXAddl_SXCField12_SXDVer12Info record
        + * is 1.
        + *
        + *
        + * 

        + **/ -

        -    offset  name        size    contents
        -    ---    
        -    4       rgisxvd     var     Array of 2 byte field ids (row or col)
        -    
        -    rgSxivd (variable): An array of SxIvdRw or SxIvdCol items.
        -    	If this is an array of SxIvdRw, 
        -    	the count of elements in the array MUST equal the value of the cDimRw 
        -    	field of the SxView record.
         
        -    	If this is an array of SxIvdCol, 
        -    	the count of elements in the array MUST equal the value of the cDimCol 
        -    	field of the SxView record.
        -    
        -    The SxIvdRw structure specifies a reference to a pivot field or data field on the row axis.
        -    The SxIvdCol structure specifies a reference to a pivot field or data field on the column axis
        -        rw (2 bytes): A signed integer that specifies a pivot field or data field for the row axis of the PivotTable view. 
        -        MUST be a value from the following table:
        -     or
        -		col (2 bytes): A signed integer that specifies a pivot field or data field for the column axis of the PivotTable view. 
        -		MUST be a value from the following table:
        -		     
        -    	Value	    Meaning
        -    	-2		    This value specifies that the data field is on the row (or col) axis. 
        -    				The sxaxisRw (or sxaxisCol) field of sxaxis4Data of SxView record 
        -    				MUST equal 1 and the sxaxisData field of sxaxis4Data of the SxView record 
        -    				MUST equal zero.
        -    	0+		    This value specifies a pivot field index as specified in Pivot Fields. 
        -    				The pivot field index specifies a pivot field on the row (or col) axis 
        -    				of the PivotTable view. 
        -    				MUST be less than the cDim field of the SxView record of the PivotTable view. 
        -    				If the referenced pivot field is not a hidden field in an OLAP PivotTable view 
        -    				then the sxaxisRw (or sxaxisCol) field of SXAxis of the Sxvd record of the pivot 
        -    				field MUST equal 1.
        +public class Sxivd extends XLSRecord implements XLSConstants {
        +    /**
        +     * serialVersionUID
        +     */
        +    private static final long serialVersionUID = 9027599480633995587L;
         
        -    A pivot field is a hidden field if an SXAddl_SXCField12_SXDVer12Info record exists 
        -    for the pivot field, and the fHiddenLvl field of the SXAddl_SXCField12_SXDVer12Info record 
        -    is 1.
        +    public void init() {
        +        super.init();
        +        if (DEBUGLEVEL > 3)
        +            Logger.logInfo("SXIVD - n: " + this.getData().length + " array:" + Arrays.toString(this.getData()));
        +    }
         
        -    
        -    

        - -**/ + public String toString() { + return "SXIVD - n: " + this.getData().length + " array:" + Arrays.toString(this.getData()); + } + /** + * adds a field to the end of this field list + * + * @param fieldNumber + */ + public void addField(int fieldNumber) { + data = this.getData(); + byte[] b = ByteTools.shortToLEBytes((short) fieldNumber); + data = ByteTools.append(b, data); + } -public class Sxivd extends XLSRecord implements XLSConstants{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 9027599480633995587L; - - public void init(){ - super.init(); - if (DEBUGLEVEL > 3) Logger.logInfo("SXIVD - n: " + this.getData().length + " array:" + Arrays.toString(this.getData())); + /** + * for each two-byte pair in an array of [number of items]*2 bytes, + * // *
        specifies either a pivot field index (must less than the total number of fields as specfied by Sxview) + * or -2 means that the data field is on the row or col axis + * + * @param items + */ + public void setRowOrColItems(int[] items) { + byte[] data = intToByteArray(items); + this.setData(data); } - public String toString() { - return "SXIVD - n: " + this.getData().length + " array:" + Arrays.toString(this.getData()); - } - - /** - * adds a field to the end of this field list - * @param fieldNumber - */ - public void addField(int fieldNumber) { - data= this.getData(); - byte[] b= ByteTools.shortToLEBytes((short)fieldNumber); - data= ByteTools.append(b, data); - } - - /** - * for each two-byte pair in an array of [number of items]*2 bytes, -// *
        specifies either a pivot field index (must less than the total number of fields as specfied by Sxview) - * or -2 means that the data field is on the row or col axis - * @param items - */ - public void setRowOrColItems(int[] items) { - byte[] data= intToByteArray(items); - this.setData(data); - } - public static XLSRecord getPrototype() { - Sxivd si= new Sxivd(); - si.setOpcode(SXIVD); - si.setData(new byte[] {}); - si.init(); - return si; + Sxivd si = new Sxivd(); + si.setOpcode(SXIVD); + si.setData(new byte[]{}); + si.init(); + return si; } - + public static final byte[] intToByteArray(int[] value) { - if (value==null) return null; - byte[] b= new byte[value.length]; - int j= 0; - for (int i : value) { - b[j++]= (byte)i; - } - return b; - } + if (value == null) return null; + byte[] b = new byte[value.length]; + int j = 0; + for (int i : value) { + b[j++] = (byte) i; + } + return b; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxli.java b/src/main/java/io/starter/formats/XLS/Sxli.java index 42e60e8..d2b88d8 100644 --- a/src/main/java/io/starter/formats/XLS/Sxli.java +++ b/src/main/java/io/starter/formats/XLS/Sxli.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,40 +22,38 @@ */ package io.starter.formats.XLS; -import java.util.ArrayList; -import java.util.Arrays; - -import io.starter.formats.XLS.SxAddl.ADDL_CLASSES; -import io.starter.formats.XLS.SxDI.AGGREGATIONFUNCTIONS; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.Arrays; + /** * SxLI: The SXLI record specifies pivot lines for the row area or column area * of a PivotTable view. - * + *

        * SXLI B5h: This record stores an array of variable-length SXLI structures * which describe row and col items. - * + *

        * Thre are 2 of these recs per Table -- one for rows, one for cols - * + * *

        - * + * *

          *     offset  name        size    contents
        - *     ---    
        + *     ---
          *     4       rgsxli      var     Array of SXLI structures
        - * 
        - * 
        + *
        + *
          *     SXLI Structures have variable length but will always be at least 10 bytes long.
        - *     
        + *
          *     offset  name        size    contents
        - *     ---    
        - *     0       cSic        2       count of identical items to previous 
        - *     					A signed integer that specifies the count of pivot item indexes in the beginning of the rgisxvi array that are identical to the same number of pivot item indexes 
        - *     					in the beginning of the rgisxvi array of the previous SXLIItem structure in the rgsxli array of the preceding SXLI record. 
        - *     					The value MUST be greater than or equal to 0 and less than the isxviMac field. If the fGrand field equals 1, then this value MUST be 0. 
        + *     ---
        + *     0       cSic        2       count of identical items to previous
        + *     					A signed integer that specifies the count of pivot item indexes in the beginning of the rgisxvi array that are identical to the same number of pivot item indexes
        + *     					in the beginning of the rgisxvi array of the previous SXLIItem structure in the rgsxli array of the preceding SXLI record.
        + *     					The value MUST be greater than or equal to 0 and less than the isxviMac field. If the fGrand field equals 1, then this value MUST be 0.
          *     2       itmtype     2       type:
          *                                 0x0 = data
          *                                 0x1 = default
        @@ -74,52 +72,52 @@
          *     4       isxviMac    2	   Number of elements in the rgisxvi array that are displayed in this pivot line. MUST be greater than or equal to 0.
          *     				   If the fGrand field equals 1, then the value of this field MUST be 1. If the fGrand field equals zero and the preceding SXLI record contains row area pivot items, then this value MUST be less than or equal to the cDimRw field of the preceding SxView. If the fGrand field equals zero and the preceding SXLI record contains column area pivot items, then this value MUST be less than or equal to the cDimCol field of the preceding SxView.
          *             fMultiDataName (1 bit):  A bit that specifies whether the data field name is used for the total or the subtotal.
        - *             iData (8 bits): An unsigned integer that specifies a data item index as specified in Data Items, for an SXDI record specifying a data item used for a subtotal. 
        - *             fSbt (1 bit): A bit that specifies whether this pivot line is a subtotal. 
        + *             iData (8 bits): An unsigned integer that specifies a data item index as specified in Data Items, for an SXDI record specifying a data item used for a subtotal.
        + *             fSbt (1 bit): A bit that specifies whether this pivot line is a subtotal.
          *             fBlock (1 bit): A bit that specifies whether this pivot line is a block total.
          *             fGrand (1 bit): A bit that specifies whether this pivot line is a grand total.
        - *             fMultiDataOnAxis (1 bit): A bit that specifies whether a pivot line entry in this pivot line is a data item index.            
        - *		3 bits- unused
        - *	rgisxvi (variable): An array of 2-byte signed integers that specifies a pivot line entry.  --> if 0x7FFF means no pivot line/blank      
        - *     
        + *             fMultiDataOnAxis (1 bit): A bit that specifies whether a pivot line entry in this pivot line is a data item index.
        + * 		3 bits- unused
        + * 	rgisxvi (variable): An array of 2-byte signed integers that specifies a pivot line entry.  --> if 0x7FFF means no pivot line/blank
        + *
          *     6       grbit       2       option flags
          * 8       rgisxvi     2       Array of indices to SXVI records -- number is isxviMac+1
        - * 
        + *
          * 

        *
        - * + *

        * more info: rgsxli (variable): An array of SXLIItem. - * + *

        * Zero or two records of this type appear in the file for each PivotTable view * depending on the values of the cRw and cCol fields of the associated SxView * record. - * + *

        * If the value of either of the cRw or cCol fields of the associated SxView is * greater than zero, then two records of this type MUST exist in the file for * the associated SxView. The first record contains row area pivot lines and the * second record contains column area pivot lines. - * + *

        * The count of SXLIItem structures in rgsxli, which are row area pivot lines, * MUST equal the cRw field of SxView. - * + *

        * The count of SXLIItem structures in rgsxli, which are column area pivot * lines, MUST equal the cCol field of SxView. - * + *

        * The associated SxView record is the SxView record of the PivotTable view. - * + *

        * The SXLIItem structure specifies a pivot line in the row area or column area * of a PivotTable view. - * + *

        * cSic (2 bytes): A signed integer that specifies the count of pivot item * indexes in the beginning of the rgisxvi array that are identical to the same * number of pivot item indexes in the beginning of the rgisxvi array of the * previous SXLIItem structure in the rgsxli array of the preceding SXLI record. * The value MUST be greater than or equal to 0 and less than the isxviMac * field. If the fGrand field equals 1, then this value MUST be 0. - * + *

        * itmType (15 bits): An unsigned integer that specifies the type of this pivot * line. MUST be a value from the table: - * + *

        * ITMTYPEDATA 0x0000 A value in the data ITMTYPEDEFAULT 0x0001 Automatic * subtotal selection ITMTYPESUM 0x0002 Sum of values in the data ITMTYPECOUNTA * 0x0003 Count of values in the data ITMTYPECOUNT 0x0004 Count of numbers in @@ -130,9 +128,9 @@ * standard deviation (entire population) ITMTYPEVAR 0x000B Statistical variance * (estimate) ITMTYPEVARP 0x000C Statistical variance (entire population) * ITMTYPEGRAND 0x000D Grand total ITMTYPEBLANK 0x000E Blank line - * + *

        * A - reserved1 (1 bit): MUST be zero and MUST be ignored. - * + *

        * isxviMac (2 bytes): A signed integer that specifies the number of elements in * the rgisxvi array that are displayed in this pivot line. MUST be greater than * or equal to 0. If the fGrand field equals 1, then the value of this field @@ -141,29 +139,29 @@ * the cDimRw field of the preceding SxView. If the fGrand field equals zero and * the preceding SXLI record contains column area pivot items, then this value * MUST be less than or equal to the cDimCol field of the preceding SxView. - * + *

        * B - fMultiDataName (1 bit): A bit that specifies whether the data field name * is used for the total or the subtotal. MUST be a value from the following * table: Value Meaning 0 The data field name is used for the total. 1 The data * field name is used for the subtotal. - * + *

        * If the fGrand field equals 1 or the fBlock field equals 1, then this value * MUST equal the value in the fMultiDataOnAxis field. If the fGrand and fBlock * fields equal zero, the fSbt and fMultiDataOnAxis fields equal 1, and the cSic * field is less than iposData, then this value MUST equal 1. Otherwise, this * value MUST be zero. - * + *

        * iposData is specified as follows: If the preceding SXLI record contains row * area pivot items, iposData equals the index of the SxIvdRw record in the * rgSxivd array of the SxIvd containing SxIvdRw records where the rw field * equals -2. If there is not an SxIvdRw record with the rw field equal to -2, * iposData equals zero. - * + *

        * If the preceding SXLI record contains column area pivot items, iposData * equals the index of the SxIvdCol record in the rgSxivd array of the SxIvd * containing SxIvdCol records where the col field equals -2. If there is not an * SxIvdCol record with the col field equal to -2, iposData equals zero. - * + *

        * iData (8 bits): An unsigned integer that specifies a data item index as * specified in Data Items, for an SXDI record specifying a data item used for a * subtotal. This field MUST be 0 if the cDimData field of the preceding SxView @@ -175,44 +173,44 @@ * fMultiDataName, then the value of this field MUST equal the value of the * element of the rgisxvi array in the position equal to iposData as specified * in fMultiDataName. - * + *

        * C - fSbt (1 bit): A bit that specifies whether this pivot line is a subtotal. * This value MUST equal 1 if the itmType field is greater than or equal to * ITMTYPEDEFAULT and the itmType field is less than or equal to ITMTYPEGRAND * and the fBlock field equals 0. Otherwise, this value MUST be 0. - * + *

        * D - fBlock (1 bit): A bit that specifies whether this pivot line is a block * total. A block total is a total of a group of pivot items. For more details * see Grouping. If the fGrand field equals 0 and the fBlock field in the * previous SXLIItem record equals 1, this value MUST be 1. - * + *

        * E - fGrand (1 bit): A bit that specifies whether this pivot line is a grand * total. If the fGrand field in the previous SXLIItem record is 1, then this * value MUST be 1. Otherwise, if the itmType field equals ITMTYPEGRAND this * field MUST equal 1 and if the itmType field does not equal ITMTYPEGRAND this * field MUST equal 0. - * + *

        * F - fMultiDataOnAxis (1 bit): A bit that specifies whether a pivot line entry * in this pivot line is a data item index. - * + *

        * If the preceding SXLI record contains row area pivot items, the cDimData * field of the preceding SxView record is greater than 1, the * sxaxis4Data.sxaxisRw field of the preceding SxView equals 1 and itmType is * not equal to ITMTYPEBLANK, then this value MUST be 1. Otherwise, this value * MUST be 0. - * + *

        * If the preceding SXLI record contains column area pivot items, the cDimData * field of the preceding SxView record is greater than 1, the * sxaxis4Data.sxaxisCol field of the preceding SxView equals 1 and itmType is * not equal to ITMTYPEBLANK, then this value MUST be 1. Otherwise, this value * MUST be 0. - * + *

        * G - unused1 (1 bit): Undefined, and MUST be ignored. - * + *

        * H - unused2 (1 bit): Undefined, and MUST be ignored. - * + *

        * I - reserved2 (1 bit): MUST be zero and MUST be ignored. - * + *

        * rgisxvi (variable): An array of 2-byte signed integers that specifies a pivot * line entry. Each element of this array is either a pivot item index or a data * item index. If fGrand is 1 or itmType is ITMTYPEBLANK then all elements of @@ -221,195 +219,193 @@ * specifies a data item index or pivot item index in the associated pivot field * as specified in Pivot Items. 0x7FFF This value specifies that there is no * pivot item and that the cell in the pivot line is blank. - * - * - * - * "http://www.extentech.com">Extentech Inc. + *

        + *

        + *

        + * "http://www.extentech.com">Extentech Inc. **/ public class Sxli extends XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4157827774990504633L; - ArrayList items; - int nItemsPerLine; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4157827774990504633L; + ArrayList items; + int nItemsPerLine; - public void init() { - super.init(); - Sxview sx= wkbook.getAllPivotTableViews()[wkbook.getNPivotTableViews()-1]; - nItemsPerLine= (sx.hasRowPivotItemsRecord()?sx.cDimCol:sx.cDimRw); - // total # items will be sx.cRw or sx.cCol - // per each pivot line, # items [see SXLI_Item.rgisxvi] will be sx.cDimRw or sx.cDimCol - items = SXLI_Item.parse(this.getData(), nItemsPerLine); - if (DEBUGLEVEL > 3) - Logger.logInfo("SXLI: rgsxli:" + items.toString()); - } + public void init() { + super.init(); + Sxview sx = wkbook.getAllPivotTableViews()[wkbook.getNPivotTableViews() - 1]; + nItemsPerLine = (sx.hasRowPivotItemsRecord() ? sx.cDimCol : sx.cDimRw); + // total # items will be sx.cRw or sx.cCol + // per each pivot line, # items [see SXLI_Item.rgisxvi] will be sx.cDimRw or sx.cDimCol + items = SXLI_Item.parse(this.getData(), nItemsPerLine); + if (DEBUGLEVEL > 3) + Logger.logInfo("SXLI: rgsxli:" + items.toString()); + } - public String toString() { - return "SXLI: rgsxli:" + items.toString(); - } + public String toString() { + return "SXLI: rgsxli:" + items.toString(); + } - /** - * adds a field to the end of this field list - * - */ - public void addField(int repeat, int nLines, int type, short[] indexes) { - SXLI_Item sxitem= new SXLI_Item(repeat, nLines, type, indexes, nItemsPerLine); - items.add(sxitem); - updateRecord(true); - } + /** + * adds a field to the end of this field list + */ + public void addField(int repeat, int nLines, int type, short[] indexes) { + SXLI_Item sxitem = new SXLI_Item(repeat, nLines, type, indexes, nItemsPerLine); + items.add(sxitem); + updateRecord(true); + } - /** - * return a new, blank SxLi - * - * @return - */ - public static XLSRecord getPrototype(WorkBook wkbook) { - Sxli li = new Sxli(); - li.setWorkBook(wkbook); - li.setOpcode(SXLI); - li.setData(new byte[] {}); - li.init(); - return li; - } - - void updateRecord(boolean appendLast) { - if (!appendLast) { // add all - this.data= new byte[0]; - for (SXLI_Item sxitem : items) { - data= ByteTools.append(sxitem.getData(), data); - } - } else - data= ByteTools.append(items.get(items.size()-1).getData(), data); - - } + /** + * return a new, blank SxLi + * + * @return + */ + public static XLSRecord getPrototype(WorkBook wkbook) { + Sxli li = new Sxli(); + li.setWorkBook(wkbook); + li.setOpcode(SXLI); + li.setData(new byte[]{}); + li.init(); + return li; + } + + void updateRecord(boolean appendLast) { + if (!appendLast) { // add all + this.data = new byte[0]; + for (SXLI_Item sxitem : items) { + data = ByteTools.append(sxitem.getData(), data); + } + } else + data = ByteTools.append(items.get(items.size() - 1).getData(), data); + + } } /** * The SXLIItem structure specifies a pivot line in the row area or column area * of a PivotTable view. - * - * */ class SXLI_Item { - public enum ITEMTYPE { - ITMTYPEDATA("data"), /* A value in the data */ - ITMTYPEDEFAULT("default"), /* Automatic subtotal selection */ - ITMTYPESUM("sum"), /* Sum of values in the data */ - ITMTYPECOUNTA("countA"), /* Count of values in the data */ - ITMTYPECOUNT("count"), /* Count of numbers in the data */ - ITMTYPEAVERAGE("avg"), /* Average of values in the data */ - ITMTYPEMAX("max"), /* Maximum value in the data */ - ITMTYPEMIN("min"), /* Minimum value in the data */ - ITMTYPEPRODUCT("product"), /* Product of values in the data */ - ITMTYPESTDEV("stdDev"), /* Statistical standard deviation (estimate) */ - ITMTYPESTDEVP("stdDevP"), /* - * Statistical standard deviation (entire - * population) - */ - ITMTYPEVAR("var"), /* Statistical variance (estimate) */ - ITMTYPEVARP("varP"), /* Statistical variance (entire population) */ - ITMTYPEGRAND("grand"), /* Grand total */ - ITMTYPEBLANK("blank"); /* Blank line */ - private final String itm; + public enum ITEMTYPE { + ITMTYPEDATA("data"), /* A value in the data */ + ITMTYPEDEFAULT("default"), /* Automatic subtotal selection */ + ITMTYPESUM("sum"), /* Sum of values in the data */ + ITMTYPECOUNTA("countA"), /* Count of values in the data */ + ITMTYPECOUNT("count"), /* Count of numbers in the data */ + ITMTYPEAVERAGE("avg"), /* Average of values in the data */ + ITMTYPEMAX("max"), /* Maximum value in the data */ + ITMTYPEMIN("min"), /* Minimum value in the data */ + ITMTYPEPRODUCT("product"), /* Product of values in the data */ + ITMTYPESTDEV("stdDev"), /* Statistical standard deviation (estimate) */ + ITMTYPESTDEVP("stdDevP"), /* + * Statistical standard deviation (entire + * population) + */ + ITMTYPEVAR("var"), /* Statistical variance (estimate) */ + ITMTYPEVARP("varP"), /* Statistical variance (entire population) */ + ITMTYPEGRAND("grand"), /* Grand total */ + ITMTYPEBLANK("blank"); /* Blank line */ + private final String itm; + + ITEMTYPE(String s) { + this.itm = s; + } - ITEMTYPE(String s) { - this.itm = s; - } + public static int get(String s) { + for (ITEMTYPE c : values()) { + if (c.itm.equals(s)) + return c.ordinal(); + } + return 0; + } - public static int get(String s) { - for (ITEMTYPE c : values()) { - if (c.itm.equals(s)) - return c.ordinal(); - } - return 0; - } - - public static ITEMTYPE get(int id) { - for (ITEMTYPE c : values()) { - if (c.ordinal()== id) - return c; - } - return null; - } - - }; + public static ITEMTYPE get(int id) { + for (ITEMTYPE c : values()) { + if (c.ordinal() == id) + return c; + } + return null; + } - short cSic, /* 0x0000 specifies that no pivot items in the rgisxvi array are identical to the first pivot items in the previous pivot line item in this record. - */ - itmType, /* see ITEMTYPE enum */ - isxviMac; /* number of pivot items on the pivot line */ - byte iData; /* specifies a data item index as specified in Data Items, for an SXDI record specifying a data item used for a subtotal. This field MUST be 0 if the cDimData field of the preceding SxView record is 0 or if the fGrand field equals */ - boolean fMultiDataName, /* specifies whether the data field name is used for the total or the subtotal */ - fSbt, /* Specifies whether this pivot line is a subtotal. */ - fBlock, /* Specifies whether this pivot line is a block total */ - fGrand, /* Specifies whether this pivot line is a grand total. */ - fMultiDataOnAxis; /* Specifies whether a pivot line entry in this pivot line is a data item index. */ - byte[] rgisxvi; /* An array of 2-byte signed integers that specifies a pivot line entry. + } + + short cSic, /* 0x0000 specifies that no pivot items in the rgisxvi array are identical to the first pivot items in the previous pivot line item in this record. + */ + itmType, /* see ITEMTYPE enum */ + isxviMac; /* number of pivot items on the pivot line */ + byte iData; /* specifies a data item index as specified in Data Items, for an SXDI record specifying a data item used for a subtotal. This field MUST be 0 if the cDimData field of the preceding SxView record is 0 or if the fGrand field equals */ + boolean fMultiDataName, /* specifies whether the data field name is used for the total or the subtotal */ + fSbt, /* Specifies whether this pivot line is a subtotal. */ + fBlock, /* Specifies whether this pivot line is a block total */ + fGrand, /* Specifies whether this pivot line is a grand total. */ + fMultiDataOnAxis; /* Specifies whether a pivot line entry in this pivot line is a data item index. */ + byte[] rgisxvi; /* An array of 2-byte signed integers that specifies a pivot line entry. Each element of this array is either a pivot item index or a data item index. 0x7FFF means blank, no pivot item */ - static int pos = 0; + static int pos = 0; - public static ArrayList parse(byte[] data, int nItemsPerLine) { - pos = 0; - ArrayList items = new ArrayList(); + public static ArrayList parse(byte[] data, int nItemsPerLine) { + pos = 0; + ArrayList items = new ArrayList(); - while (pos < data.length - 7) { - items.add(new SXLI_Item(data, nItemsPerLine)); - } - return items; - } + while (pos < data.length - 7) { + items.add(new SXLI_Item(data, nItemsPerLine)); + } + return items; + } - SXLI_Item(byte[] data, int nItemsPerLine) { - try { - cSic = ByteTools.readShort(data[pos + 0], data[pos + 1]); - itmType = (short) (ByteTools.readShort(data[pos + 2], data[pos + 3]) & 0x7FFF); // 1st // 15 bits - isxviMac = ByteTools.readShort(data[pos + 4], data[pos + 5]); - short tmp = ByteTools.readShort(data[pos + 6], data[pos + 7]); - fMultiDataName = (tmp & 0x8000) == 0x8000; - iData = (byte) ((tmp >> 7) & 0x80); - tmp= (byte) (tmp >> 9); - fSbt= (tmp & 0x1)==0x1; - fBlock= (tmp & 0x2)==0x2; - fGrand= (tmp & 0x4)==0x4; - fMultiDataOnAxis= (tmp & 0x8)==0x8; - pos += 8; - // not in 1.5 rgisxvi = Arrays.copyOfRange(data, pos, pos + (nItemsPerLine * 2)); - rgisxvi= new byte[nItemsPerLine * 2]; - System.arraycopy(data, pos, rgisxvi, 0, rgisxvi.length); - pos+= rgisxvi.length; - } catch (Exception e) { - } - } + SXLI_Item(byte[] data, int nItemsPerLine) { + try { + cSic = ByteTools.readShort(data[pos + 0], data[pos + 1]); + itmType = (short) (ByteTools.readShort(data[pos + 2], data[pos + 3]) & 0x7FFF); // 1st // 15 bits + isxviMac = ByteTools.readShort(data[pos + 4], data[pos + 5]); + short tmp = ByteTools.readShort(data[pos + 6], data[pos + 7]); + fMultiDataName = (tmp & 0x8000) == 0x8000; + iData = (byte) ((tmp >> 7) & 0x80); + tmp = (byte) (tmp >> 9); + fSbt = (tmp & 0x1) == 0x1; + fBlock = (tmp & 0x2) == 0x2; + fGrand = (tmp & 0x4) == 0x4; + fMultiDataOnAxis = (tmp & 0x8) == 0x8; + pos += 8; + // not in 1.5 rgisxvi = Arrays.copyOfRange(data, pos, pos + (nItemsPerLine * 2)); + rgisxvi = new byte[nItemsPerLine * 2]; + System.arraycopy(data, pos, rgisxvi, 0, rgisxvi.length); + pos += rgisxvi.length; + } catch (Exception e) { + } + } - /** - * create a new SXLI item from speicifications for particular row or column line(s) - * @param repeat if a field is repeated NOT IMPLEMENTED YET - * @param nLines # lines for - * @param type - * @param indexes - * @param nItemsPerLine - */ - SXLI_Item(int repeat, int nLines, int type, short[] indexes, int nItemsPerLine) { - cSic= (short)repeat; - isxviMac= (short) nLines; - ITEMTYPE t= ITEMTYPE.get(type); - itmType= (short) t.ordinal(); - // fMultiDataName ??? - // iData ??? - // fBlock ??? - // fMultiDataOnAxis ??? - switch (t) { - case ITMTYPEDATA: - case ITMTYPEBLANK: - break; - case ITMTYPEGRAND: - fGrand= true; - default: - fSbt= true; + /** + * create a new SXLI item from speicifications for particular row or column line(s) + * + * @param repeat if a field is repeated NOT IMPLEMENTED YET + * @param nLines # lines for + * @param type + * @param indexes + * @param nItemsPerLine + */ + SXLI_Item(int repeat, int nLines, int type, short[] indexes, int nItemsPerLine) { + cSic = (short) repeat; + isxviMac = (short) nLines; + ITEMTYPE t = ITEMTYPE.get(type); + itmType = (short) t.ordinal(); + // fMultiDataName ??? + // iData ??? + // fBlock ??? + // fMultiDataOnAxis ??? + switch (t) { + case ITMTYPEDATA: + case ITMTYPEBLANK: + break; + case ITMTYPEGRAND: + fGrand = true; + default: + fSbt = true; /* case ITMTYPEDEFAULT: case ITMTYPEAVERAGE: case ITMTYPECOUNT: @@ -422,49 +418,50 @@ public static ArrayList parse(byte[] data, int nItemsPerLine) { case ITMTYPESUM: case ITMTYPEVAR: case ITMTYPEVARP:*/ - } - rgisxvi= new byte[indexes.length*2]; - for (int i= 0; i < indexes.length*2; i+=2) { - byte[] b= ByteTools.shortToLEBytes(indexes[i/2]); - rgisxvi[i]= b[0]; - rgisxvi[i+1]= b[1]; - } - } + } + rgisxvi = new byte[indexes.length * 2]; + for (int i = 0; i < indexes.length * 2; i += 2) { + byte[] b = ByteTools.shortToLEBytes(indexes[i / 2]); + rgisxvi[i] = b[0]; + rgisxvi[i + 1] = b[1]; + } + } + + /** + * package SXLI_ITEM into byte array + * + * @return byte[] + */ + byte[] getData() { + byte[] data = new byte[8]; + byte[] b = ByteTools.shortToLEBytes(cSic); + data[0] = b[0]; + data[1] = b[1]; + b = ByteTools.shortToLEBytes(itmType); + data[2] = b[0]; + data[3] = b[1]; + b = ByteTools.shortToLEBytes(isxviMac); + data[4] = b[0]; + data[5] = b[1]; + short tmp = (short) ((fMultiDataName) ? 1 : 0); + tmp = (short) (tmp | (iData << 1)); + if (fSbt) + tmp = (short) (tmp | 0x200); + if (fBlock) + tmp = (short) (tmp | 0x400); + if (fGrand) + tmp = (short) (tmp | 0x800); + if (fMultiDataOnAxis) + tmp = (short) (tmp | 0x1000); + b = ByteTools.shortToLEBytes(tmp); + data[6] = b[0]; + data[7] = b[1]; + data = ByteTools.append(rgisxvi, data); + return data; + } - /** - * package SXLI_ITEM into byte array - * @return byte[] - */ - byte[] getData() { - byte[] data= new byte[8]; - byte[] b= ByteTools.shortToLEBytes(cSic); - data[0]= b[0]; - data[1]= b[1]; - b= ByteTools.shortToLEBytes(itmType); - data[2]= b[0]; - data[3]= b[1]; - b= ByteTools.shortToLEBytes(isxviMac); - data[4]= b[0]; - data[5]= b[1]; - short tmp= (short) ((fMultiDataName)?1:0); - tmp= (short) (tmp | (iData << 1)); - if (fSbt) - tmp= (short)(tmp | 0x200); - if (fBlock) - tmp= (short)(tmp | 0x400); - if (fGrand) - tmp= (short)(tmp | 0x800); - if (fMultiDataOnAxis) - tmp= (short)(tmp | 0x1000); - b= ByteTools.shortToLEBytes(tmp); - data[6]= b[0]; - data[7]= b[1]; - data= ByteTools.append(rgisxvi, data); - return data; - } - - public String toString() { - return String.format("[rep %d typ %d imax %d iData %d fSbt %b fBlock %b fGrand %b rgi: %s", - cSic, itmType, isxviMac, iData, fSbt, fBlock, fGrand, Arrays.toString(rgisxvi)); - } + public String toString() { + return String.format("[rep %d typ %d imax %d iData %d fSbt %b fBlock %b fGrand %b rgi: %s", + cSic, itmType, isxviMac, iData, fSbt, fBlock, fGrand, Arrays.toString(rgisxvi)); + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxvd.java b/src/main/java/io/starter/formats/XLS/Sxvd.java index 74a7df3..91ff93e 100644 --- a/src/main/java/io/starter/formats/XLS/Sxvd.java +++ b/src/main/java/io/starter/formats/XLS/Sxvd.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,318 +22,325 @@ */ package io.starter.formats.XLS; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; - import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; -/** SXVD b[1]h: View Fields - * The Sxvd record specifies pivot field properties and specifies the - * beginning of a collection of records. - * This collection of records specifies details for a pivot field. - -

        -    offset  name        size    contents
        -    ---    
        -    4       sxaxis      2       0x0 = no axis
        -                                0x1 = row
        -                                0x2 = col
        -                                0x4 = page
        -                                0x8 = data
        -    6       cSub        2       Number of subtotals attached
        -    8       grbitSub    2       Item subtotal type (see subtable)
        -    10      cItm        2       Number of items
        -    12      cchName     2       Length of the name if name= 0xFFFF, rgch is null and name in cache used.
        -    14      rgch        var     name
        -
        -    name        contents
        -    --------------------------------
        -    bitFNone    0000
        -    bitFDefault 0001
        -    bitFSum     0002
        -    bitFCounta  0004
        -    bitFAvg     0008
        -    bitFMax     0010
        -    bitFMin     0020
        -    
        -    

        - - MORE INFO: - - sxaxis (2 bytes): An SXAxis structure that specifies the PivotTable axis that this pivot field is on. - If the sxaxis.sxaxisData field equals 1, there MUST be a corresponding SXDI record with an isxvd field that specifies this Sxvd record. - - cSub (2 bytes): An unsigned integer that specifies the number of subtotal functions used for this pivot field. - MUST equal the count of subtotal fields of this record whose value is 1. - The subtotal fields of this record are fDefault, fSum, fCounta, fAverage, fMax, fMin, fProduct, fCount, fStdev, fStdevp, fVariance, and fVariancep. - For more information, see Subtotalling. - - A - fDefault (1 bit): A bit that specifies whether the default subtotal function is applied. - The default subtotal is separately determined for each data item. If the fDefault field equals 1 - and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage - field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 1. - MUST be a value from the following table: - Value Meaning - 0 The default subtotal function is not applied. - 1 The default subtotal function is applied. - - B - fSum (1 bit): A bit that specifies whether the sum subtotal function is displayed. If the fDefault field equals 1, - this value MUST be zero. If the fSum field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol - field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the - SXVI record equal to 2. MUST be a value from the following table: - Value Meaning - 0 The sum subtotal function is not displayed. - 1 The sum subtotal function is displayed. - - C - fCounta (1 bit): A bit that specifies whether the count subtotal function is displayed. If the fDefault field equals 1, - this value MUST be zero. If the fCounta field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol - field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 3. - MUST be a value from the following table: - Value Meaning - 0 The count subtotal function is not displayed. - 1 The count subtotal function is displayed. - - D - fAverage (1 bit): A bit that specifies whether the average subtotal function is displayed. If the fDefault field equals 1, this value MUST be zero. - If the fAverage field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage - field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 4. - MUST be a value from the following table: - Value Meaning - 0 The average subtotal function is not displayed. - 1 The average subtotal function is displayed. - - E - fMax (1 bit): A bit that specifies whether the max subtotal function is displayed. If the fDefault field equals 1, this value MUST be zero. - If the fMax field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage - field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 5. - MUST be a value from the following table: - Value Meaning - 0 The max subtotal function is not displayed. - 1 The max subtotal function is displayed. +/** + * SXVD b[1]h: View Fields + * The Sxvd record specifies pivot field properties and specifies the + * beginning of a collection of records. + * This collection of records specifies details for a pivot field. + * + *

        + * offset  name        size    contents
        + * ---
        + * 4       sxaxis      2       0x0 = no axis
        + * 0x1 = row
        + * 0x2 = col
        + * 0x4 = page
        + * 0x8 = data
        + * 6       cSub        2       Number of subtotals attached
        + * 8       grbitSub    2       Item subtotal type (see subtable)
        + * 10      cItm        2       Number of items
        + * 12      cchName     2       Length of the name if name= 0xFFFF, rgch is null and name in cache used.
        + * 14      rgch        var     name
        + *
        + * name        contents
        + * --------------------------------
        + * bitFNone    0000
        + * bitFDefault 0001
        + * bitFSum     0002
        + * bitFCounta  0004
        + * bitFAvg     0008
        + * bitFMax     0010
        + * bitFMin     0020
        + *
        + * 

        + *

        + * MORE INFO: + *

        + * sxaxis (2 bytes): An SXAxis structure that specifies the PivotTable axis that this pivot field is on. + * If the sxaxis.sxaxisData field equals 1, there MUST be a corresponding SXDI record with an isxvd field that specifies this Sxvd record. + *

        + * cSub (2 bytes): An unsigned integer that specifies the number of subtotal functions used for this pivot field. + * MUST equal the count of subtotal fields of this record whose value is 1. + * The subtotal fields of this record are fDefault, fSum, fCounta, fAverage, fMax, fMin, fProduct, fCount, fStdev, fStdevp, fVariance, and fVariancep. + * For more information, see Subtotalling. + *

        + * A - fDefault (1 bit): A bit that specifies whether the default subtotal function is applied. + * The default subtotal is separately determined for each data item. If the fDefault field equals 1 + * and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage + * field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 1. + * MUST be a value from the following table: + * Value Meaning + * 0 The default subtotal function is not applied. + * 1 The default subtotal function is applied. + *

        + * B - fSum (1 bit): A bit that specifies whether the sum subtotal function is displayed. If the fDefault field equals 1, + * this value MUST be zero. If the fSum field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol + * field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the + * SXVI record equal to 2. MUST be a value from the following table: + * Value Meaning + * 0 The sum subtotal function is not displayed. + * 1 The sum subtotal function is displayed. + *

        + * C - fCounta (1 bit): A bit that specifies whether the count subtotal function is displayed. If the fDefault field equals 1, + * this value MUST be zero. If the fCounta field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol + * field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 3. + * MUST be a value from the following table: + * Value Meaning + * 0 The count subtotal function is not displayed. + * 1 The count subtotal function is displayed. + *

        + * D - fAverage (1 bit): A bit that specifies whether the average subtotal function is displayed. If the fDefault field equals 1, this value MUST be zero. + * If the fAverage field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage + * field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 4. + * MUST be a value from the following table: + * Value Meaning + * 0 The average subtotal function is not displayed. + * 1 The average subtotal function is displayed. + *

        + * E - fMax (1 bit): A bit that specifies whether the max subtotal function is displayed. If the fDefault field equals 1, this value MUST be zero. + * If the fMax field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage + * field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 5. + * MUST be a value from the following table: + * Value Meaning + * 0 The max subtotal function is not displayed. + * 1 The max subtotal function is displayed. + *

        + * F - fMin (1 bit): A bit that specifies whether the min subtotal function is displayed. If the fDefault field equals 1, this value MUST be zero. + * If the fMin field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage + * field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 6. + * MUST be a value from the following table: + * Value Meaning + * 0 The min subtotal function is not displayed. + * 1 The min subtotal function is displayed. + *

        + * G - fProduct (1 bit): A bit that specifies whether the product subtotal function is displayed. If the fDefault field is 1, this value MUST be zero. + * If the fProduct field is 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage + * field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 7. + * MUST be a value from the following table: + * Value Meaning + * 0 The product subtotal function is not displayed. + * 1 The product subtotal function is displayed. + *

        + * H - fCount (1 bit): A bit that specifies whether the count numbers subtotal function is displayed. If the fDefault field is 1, + * this value MUST be zero. If the fCount field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol + * field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 8. + * MUST be a value from the following table: + * Value Meaning + * 0 The count numbers subtotal function is not displayed. + * 1 The count numbers subtotal function is displayed. + *

        + * I - fStdev (1 bit): A bit that specifies whether the standard deviation subtotal function is displayed. If the fDefault field is 1, + * this value MUST be zero. If the fStdev field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field + * equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 9. + * MUST be a value from the following table: + * Value Meaning + * 0 The standard deviation subtotal function is not displayed. + * 1 The standard deviation subtotal function is displayed. + *

        + * J - fStdevp (1 bit): A bit that specifies whether the standard deviation population subtotal function is displayed. If the fDefault field equals 1, + * this value MUST be zero. If the fStdevp field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field + * equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 10. + * MUST be a value from the following table: + * Value Meaning + * 0 The standard deviation population subtotal function is not displayed. + * 1 The standard deviation population subtotal function is displayed. + *

        + * K - fVariance (1 bit): A bit that specifies whether the variance subtotal function is displayed. If the fDefault field is 1, + * this value MUST be zero. If the fVariance field is 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol + * field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 11. + * MUST be a value from the following table: + * Value Meaning + * 0 The variance subtotal function is not displayed. + * 1 The variance subtotal function is displayed. + *

        + * L - fVariancep (1 bit): A bit that specifies whether the variance population subtotal function is displayed. If the fDefault field is 1, + * the value MUST be zero. If the fVariancep field equals 1 and sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field + * equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 12. + * MUST be a value from the following table: + * Value Meaning + * 0 The variance population subtotal function is not displayed. + * 1 The variance population subtotal function is displayed. + *

        + * M - reserved (4 bits): MUST be zero, and MUST be ignored. + *

        + * cItm (2 bytes): A signed integer that specifies the number of pivot items for this pivot field. This value MUST match the number of + * SXVI records following this record and MUST be less than or equal to the following formula: + * 32500 + the cSub field + *

        + * cchName (2 bytes): An unsigned integer that specifies the length, in characters, of the stName field. + * If the value is 0xFFFF then stName is NULL. The value MUST be 0xFFFF or greater than zero and less than or equal to 255. + *

        + * stName (variable): An XLUnicodeStringNoCch structure that specifies the caption of this pivot field. + * A non-NULL value specifies that this string is used to override the stFieldName field in + * SXFDB record from the associated cache field, as specified in pivot fields. The length is specified in cchName. + * This field exists only if the value of cchName is not 0xFFFF. If this PivotTable view is not an OLAP PivotTable view + * and this string is non-NULL, then stName MUST be unique within all Sxvd records in this PivotTable view. + **/ +public class Sxvd extends XLSRecord { - F - fMin (1 bit): A bit that specifies whether the min subtotal function is displayed. If the fDefault field equals 1, this value MUST be zero. - If the fMin field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage - field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 6. - MUST be a value from the following table: - Value Meaning - 0 The min subtotal function is not displayed. - 1 The min subtotal function is displayed. + /** + * serialVersionUID + */ + private static final long serialVersionUID = -6537376162863865578L; - G - fProduct (1 bit): A bit that specifies whether the product subtotal function is displayed. If the fDefault field is 1, this value MUST be zero. - If the fProduct field is 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage - field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 7. - MUST be a value from the following table: - Value Meaning - 0 The product subtotal function is not displayed. - 1 The product subtotal function is displayed. + public static final short AXIS_NONE = 0; + public static final short AXIS_ROW = 1; + public static final short AXIS_COL = 2; + public static final short AXIS_PAGE = 4; + public static final short AXIS_DATA = 8; - H - fCount (1 bit): A bit that specifies whether the count numbers subtotal function is displayed. If the fDefault field is 1, - this value MUST be zero. If the fCount field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol - field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 8. - MUST be a value from the following table: - Value Meaning - 0 The count numbers subtotal function is not displayed. - 1 The count numbers subtotal function is displayed. + private short cSub = -1; + private short cItm = -1; + private short axis = -1; + private short cchName = -1; + private String caption = null; - I - fStdev (1 bit): A bit that specifies whether the standard deviation subtotal function is displayed. If the fDefault field is 1, - this value MUST be zero. If the fStdev field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field - equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 9. - MUST be a value from the following table: - Value Meaning - 0 The standard deviation subtotal function is not displayed. - 1 The standard deviation subtotal function is displayed. + // flags: + boolean fDefault, fSum, fCounta, fAverage, fMax, fMin, fProduct, fCount, fStdev, fStdevp, fVariance, fVariancep; - J - fStdevp (1 bit): A bit that specifies whether the standard deviation population subtotal function is displayed. If the fDefault field equals 1, - this value MUST be zero. If the fStdevp field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field - equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 10. - MUST be a value from the following table: - Value Meaning - 0 The standard deviation population subtotal function is not displayed. - 1 The standard deviation population subtotal function is displayed. - K - fVariance (1 bit): A bit that specifies whether the variance subtotal function is displayed. If the fDefault field is 1, - this value MUST be zero. If the fVariance field is 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol - field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 11. - MUST be a value from the following table: - Value Meaning - 0 The variance subtotal function is not displayed. - 1 The variance subtotal function is displayed. + public void init() { + super.init(); + axis = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + cSub = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + cItm = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + cchName = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + if (cchName != -1) { + byte encoding = this.getByteAt(10); + byte[] tmp = this.getBytesAt(11, (cchName) * (encoding + 1)); + try { + if (encoding == 0) + caption = new String(tmp, DEFAULTENCODING); + else + caption = new String(tmp, UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding PivotTable caption name in Sxvd: " + e); + } + } + // type of subtotal funtion + byte b0 = this.getByteAt(4); + byte b1 = this.getByteAt(5); + fDefault = ((b0 & 0x1) == 0x1); // default subtotal -- if 1, the rest of these flags are ignored + fSum = ((b0 & 0x2) == 0x2); // sum subtotal function -if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 2. + fCounta = ((b0 & 0x4) == 0x4); // count subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 3. + fAverage = ((b0 & 0x8) == 0x8); // average subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 4. + fMax = ((b0 & 0x10) == 0x10); // max subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 5. + fMin = ((b0 & 0x20) == 0x20); // min subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 6. + fProduct = ((b0 & 0x40) == 0x40); // product subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 7. + fCount = ((b0 & 0x80) == 0x80); // count numbers function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 8. + fStdev = ((b1 & 0x1) == 0x1); // standard deviation function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 9. + fStdevp = ((b1 & 0x2) == 0x2); // standard deviation population function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 10. + fVariance = ((b1 & 0x4) == 0x4);// variance subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 11. + fVariancep = ((b1 & 0x8) == 0x8);// variance population subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 12. - L - fVariancep (1 bit): A bit that specifies whether the variance population subtotal function is displayed. If the fDefault field is 1, - the value MUST be zero. If the fVariancep field equals 1 and sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field - equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 12. - MUST be a value from the following table: - Value Meaning - 0 The variance population subtotal function is not displayed. - 1 The variance population subtotal function is displayed. + if (DEBUGLEVEL > 3) + Logger.logInfo("SXVD - axis:" + getAxisTypeName() + " cSub:" + cSub + " cItm:" + cItm + " default?" + fDefault + " sum?" + fSum + " caption:" + caption); + } - M - reserved (4 bits): MUST be zero, and MUST be ignored. + /** + * returns the String representation of the subtotal function for the axis defined in this record + * + * @return + */ + public String getSubTotalFunction() { + if (fDefault) return "Default"; + if (fSum) return "Sum"; + if (fCounta) return "Count"; + if (fAverage) return "Average"; + if (fMax) return "Max"; + if (fMin) return "Min"; + if (fProduct) return "Product"; + //if (fCount) + if (fStdev) return "Stddev"; + if (fStdevp) return "StddevP"; + if (fVariance) return "Variance"; + if (fVariancep) return "VarianceP"; + return "Default"; + } - cItm (2 bytes): A signed integer that specifies the number of pivot items for this pivot field. This value MUST match the number of - SXVI records following this record and MUST be less than or equal to the following formula: - 32500 + the cSub field + /** + * Sets the subtotal function(s) for this pivot field. + *

      • "Default"; + *
      • "Sum"; + *
      • "Count"; + *
      • "Average"; + *
      • "Max"; + *
      • "Min"; + *
      • "Product"; + *
      • "Stddev"; + *
      • "StddevP"; + *
      • "Variance"; + *
      • "VarianceP"; + * + * @param f + * @return + */ + public void setSubTotalFunction(String[] f) { + if (f == null) f = new String[]{"Default"}; + cSub = (short) f.length; + for (int i = 0; i < cSub; i++) { + fDefault = f[i].equalsIgnoreCase("Default"); + fSum = f[i].equalsIgnoreCase("Sum"); + fCounta = f[i].equalsIgnoreCase("Count"); + fAverage = f[i].equalsIgnoreCase("Average"); + fMax = f[i].equalsIgnoreCase("Max"); + fMin = f[i].equalsIgnoreCase("Min"); + fProduct = f[i].equalsIgnoreCase("Product"); + //if (fCount) + fStdev = f[i].equalsIgnoreCase("Stddev"); + fStdevp = f[i].equalsIgnoreCase("StddevP"); + fVariance = f[i].equalsIgnoreCase("Variance"); + fVariancep = f[i].equalsIgnoreCase("VarianceP"); + } + if (fDefault) cSub = 1; // is this correct? + // update record + byte[] b = ByteTools.shortToLEBytes(cSub); + this.getData()[2] = b[0]; + this.getData()[3] = b[1]; - cchName (2 bytes): An unsigned integer that specifies the length, in characters, of the stName field. - If the value is 0xFFFF then stName is NULL. The value MUST be 0xFFFF or greater than zero and less than or equal to 255. - - stName (variable): An XLUnicodeStringNoCch structure that specifies the caption of this pivot field. - A non-NULL value specifies that this string is used to override the stFieldName field in - SXFDB record from the associated cache field, as specified in pivot fields. The length is specified in cchName. - This field exists only if the value of cchName is not 0xFFFF. If this PivotTable view is not an OLAP PivotTable view - and this string is non-NULL, then stName MUST be unique within all Sxvd records in this PivotTable view. - -**/ -public class Sxvd extends XLSRecord{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6537376162863865578L; + b[0] = 0; + b[1] = 0; + if (fDefault) b[0] |= 0x1; + if (fSum) b[0] |= 0x2; + if (fCounta) b[0] |= 0x4; + if (fAverage) b[0] |= 0x8; + if (fMax) b[0] |= 0x10; + if (fMin) b[0] |= 0x20; + if (fProduct) b[0] |= 0x40; + if (fCount) b[0] |= (byte) 0x80; + if (fStdev) b[1] |= 0x1; + if (fStdevp) b[1] |= 0x2; + if (fVariance) b[1] |= 0x4; + if (fVariancep) b[1] |= 0x8; - public static final short AXIS_NONE = 0; - public static final short AXIS_ROW = 1; - public static final short AXIS_COL = 2; - public static final short AXIS_PAGE = 4; - public static final short AXIS_DATA = 8; - - private short cSub = -1; - private short cItm = -1; - private short axis = -1; - private short cchName= -1; - private String caption= null; - - // flags: - boolean fDefault, fSum, fCounta, fAverage, fMax, fMin, fProduct, fCount, fStdev, fStdevp, fVariance, fVariancep; + this.getData()[4] = b[0]; + this.getData()[5] = b[1]; + } - - public void init(){ - super.init(); - axis= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - cSub= ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - cItm= ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - cchName= ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - if (cchName!=-1) { - byte encoding= this.getByteAt(10); - byte[] tmp = this.getBytesAt(11, (cchName) * (encoding+1)); - try{ - if (encoding==0) - caption= new String(tmp, DEFAULTENCODING); - else - caption= new String(tmp, UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding PivotTable caption name in Sxvd: " + e);} - } - // type of subtotal funtion - byte b0= this.getByteAt(4); - byte b1= this.getByteAt(5); - fDefault= ((b0 & 0x1)==0x1); // default subtotal -- if 1, the rest of these flags are ignored - fSum= ((b0 & 0x2)==0x2); // sum subtotal function -if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 2. - fCounta= ((b0 & 0x4)==0x4); // count subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 3. - fAverage= ((b0 & 0x8)==0x8); // average subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 4. - fMax= ((b0 & 0x10)==0x10); // max subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 5. - fMin= ((b0 & 0x20)==0x20); // min subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 6. - fProduct= ((b0 & 0x40)==0x40); // product subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 7. - fCount= ((b0 & 0x80)==0x80); // count numbers function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 8. - fStdev= ((b1 & 0x1)==0x1); // standard deviation function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 9. - fStdevp= ((b1 & 0x2)==0x2); // standard deviation population function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 10. - fVariance= ((b1 & 0x4)==0x4);// variance subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 11. - fVariancep= ((b1 & 0x8)==0x8);// variance population subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 12. - - if (DEBUGLEVEL > 3) Logger.logInfo("SXVD - axis:" + getAxisTypeName() + " cSub:" + cSub + " cItm:" + cItm + " default?" + fDefault + " sum?" + fSum+ " caption:" + caption); + /** + * returns the axis type int + * + * @return + */ + int getAxisType() { + return axis; } - - /** - * returns the String representation of the subtotal function for the axis defined in this record - * @return - */ - public String getSubTotalFunction() { - if (fDefault) return "Default"; - if (fSum) return "Sum"; - if (fCounta) return "Count"; - if (fAverage) return "Average"; - if (fMax) return "Max"; - if (fMin) return "Min"; - if (fProduct) return "Product"; - //if (fCount) - if (fStdev) return "Stddev"; - if (fStdevp) return "StddevP"; - if (fVariance) return "Variance"; - if (fVariancep) return "VarianceP"; - return "Default"; - } - - /** - * Sets the subtotal function(s) for this pivot field. -
      • "Default"; -
      • "Sum"; -
      • "Count"; -
      • "Average"; -
      • "Max"; -
      • "Min"; -
      • "Product"; -
      • "Stddev"; -
      • "StddevP"; -
      • "Variance"; -
      • "VarianceP"; - - * @param f - * @return - */ - public void setSubTotalFunction(String[] f) { - if (f==null) f= new String[] {"Default"}; - cSub= (short) f.length; - for (int i= 0; i < cSub; i++) { - fDefault= f[i].equalsIgnoreCase("Default"); - fSum= f[i].equalsIgnoreCase("Sum"); - fCounta= f[i].equalsIgnoreCase("Count"); - fAverage= f[i].equalsIgnoreCase("Average"); - fMax= f[i].equalsIgnoreCase("Max"); - fMin= f[i].equalsIgnoreCase("Min"); - fProduct= f[i].equalsIgnoreCase("Product"); - //if (fCount) - fStdev= f[i].equalsIgnoreCase("Stddev"); - fStdevp= f[i].equalsIgnoreCase("StddevP"); - fVariance= f[i].equalsIgnoreCase("Variance"); - fVariancep= f[i].equalsIgnoreCase("VarianceP"); - } - if (fDefault) cSub= 1; // is this correct? - // update record - byte[] b= ByteTools.shortToLEBytes(cSub); - this.getData()[2]= b[0]; - this.getData()[3]= b[1]; - - b[0]= 0; b[1]= 0; - if (fDefault) b[0]|= 0x1; - if (fSum) b[0]|= 0x2; - if (fCounta) b[0]|= 0x4; - if (fAverage) b[0]|= 0x8; - if (fMax) b[0]|= 0x10; - if (fMin) b[0]|= 0x20; - if (fProduct) b[0]|= 0x40; - if (fCount) b[0]|= (byte) 0x80; - if (fStdev) b[1]|= 0x1; - if (fStdevp) b[1]|= 0x2; - if (fVariance) b[1]|= 0x4; - if (fVariancep) b[1]|= 0x8; - - this.getData()[4]= b[0]; - this.getData()[5]= b[1]; - } - - /** returns the axis type int - * - * @return - */ - int getAxisType() { return axis; } - - /** - * returns the Pivot Table Axis this Sxvd record defines - * @see AXIS_ constants - * @return - */ - String getAxisTypeName(){ + + /** + * returns the Pivot Table Axis this Sxvd record defines + * + * @return + * @see AXIS_ constants + */ + String getAxisTypeName() { String ret = ""; - switch(axis){ + switch (axis) { case AXIS_NONE: ret = "NONE"; break; @@ -347,70 +354,74 @@ String getAxisTypeName(){ ret = "PAGE"; break; case Sxvd.AXIS_DATA: - ret = "DATA"; + ret = "DATA"; break; } return ret; } + /** * sets the axis for this pivot field *
      • 1= row *
      • 2= col *
      • 4= page *
      • 8= data - * @see Sxvd.AXIS_ constants + * * @param axis + * @see Sxvd.AXIS_ constants */ public void setAxis(int axis) { - if (!(axis==1 || axis==2 || axis==4 || axis==8)) return; - this.axis= (short) axis; - byte[] b= ByteTools.shortToLEBytes(this.axis); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; - // TODO if axis is data MUST have a SxDI otherwise if axis WAS a data axis must remove SxDI + if (!(axis == 1 || axis == 2 || axis == 4 || axis == 8)) return; + this.axis = (short) axis; + byte[] b = ByteTools.shortToLEBytes(this.axis); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + // TODO if axis is data MUST have a SxDI otherwise if axis WAS a data axis must remove SxDI } /** - * returns the number of pivot items for this pivot field + * returns the number of pivot items for this pivot field + * * @return */ - int getNumItems(){ - return cItm; + int getNumItems() { + return cItm; } - + public void setNumItems(int n) { - cItm= (short)n; - byte[] b= ByteTools.shortToLEBytes(cItm); - this.getData()[6]= b[0]; - this.getData()[7]= b[1]; + cItm = (short) n; + byte[] b = ByteTools.shortToLEBytes(cItm); + this.getData()[6] = b[0]; + this.getData()[7] = b[1]; } - - /** + + /** * returns the number of subtotal functions set for this pivot field + * * @return */ int getNumSubtotals() { - return cSub; + return cSub; } - private byte[] PROTOTYPE_BYTES = new byte[] { - 0, 0, /* axis */ - 1, 0, /* cSub */ - 1, 0, /* flags */ - 0, 0, /* cItm */ - -1, -1, /* cchName */ + private byte[] PROTOTYPE_BYTES = new byte[]{ + 0, 0, /* axis */ + 1, 0, /* cSub */ + 1, 0, /* flags */ + 0, 0, /* cItm */ + -1, -1, /* cchName */ }; - + public static XLSRecord getPrototype() { - Sxvd sv= new Sxvd(); - sv.setOpcode(SXVD); - sv.setData(sv.PROTOTYPE_BYTES); - sv.init(); - return sv; + Sxvd sv = new Sxvd(); + sv.setOpcode(SXVD); + sv.setData(sv.PROTOTYPE_BYTES); + sv.init(); + return sv; } - + public String toString() { - return "SXVD - axis:" + getAxisTypeName() + " cSub:" + cSub + " cItm:" + cItm + " default?" + fDefault + " sum?" + fSum+ " caption:" + caption; + return "SXVD - axis:" + getAxisTypeName() + " cSub:" + cSub + " cItm:" + cItm + " default?" + fDefault + " sum?" + fSum + " caption:" + caption; } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxvi.java b/src/main/java/io/starter/formats/XLS/Sxvi.java index ac0fbc4..bdd056c 100644 --- a/src/main/java/io/starter/formats/XLS/Sxvi.java +++ b/src/main/java/io/starter/formats/XLS/Sxvi.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,275 +22,299 @@ */ package io.starter.formats.XLS; -import java.io.UnsupportedEncodingException; - import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; -/** SXVI B2h: This record stores view information about an Item. - itmType (2 bytes): A signed integer that specifies the pivot item type. - The value MUST be one of the following values: - Value Name Meaning - 0x0000 itmtypeData A data value - 0x0001 itmtypeDEFAULT Default subtotal for the pivot field - 0x0002 itmtypeSUM Sum of values in the pivot field - 0x0003 itmtypeCOUNTA Count of values in the pivot field - 0x0004 itmtypeAVERAGE Average of values in the pivot field - 0x0005 itmtypeMAX Max of values in the pivot field - 0x0006 itmtypeMIN Min of values in the pivot field - 0x0007 itmtypePRODUCT Product of values in the pivot field - 0x0008 itmtypeCOUNT Count of numbers in the pivot field - 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field - 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field - 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field - 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field +/** + * SXVI B2h: This record stores view information about an Item. + * itmType (2 bytes): A signed integer that specifies the pivot item type. + * The value MUST be one of the following values: + * Value Name Meaning + * 0x0000 itmtypeData A data value + * 0x0001 itmtypeDEFAULT Default subtotal for the pivot field + * 0x0002 itmtypeSUM Sum of values in the pivot field + * 0x0003 itmtypeCOUNTA Count of values in the pivot field + * 0x0004 itmtypeAVERAGE Average of values in the pivot field + * 0x0005 itmtypeMAX Max of values in the pivot field + * 0x0006 itmtypeMIN Min of values in the pivot field + * 0x0007 itmtypePRODUCT Product of values in the pivot field + * 0x0008 itmtypeCOUNT Count of numbers in the pivot field + * 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field + * 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field + * 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field + * 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field + *

        + * A - fHidden (1 bit): A bit that specifies whether this pivot item is hidden. + * MUST be zero if itmType is not itmtypeData. MUST be zero for OLAP PivotTable view. + *

        + * B - fHideDetail (1 bit): A bit that specifies whether the pivot item detail is collapsed. + * MUST be zero for OLAP PivotTable view. + *

        + * C - reserved1 (1 bit): MUST be zero, and MUST be ignored. + *

        + * D - fFormula (1 bit): A bit that specifies whether this pivot item is a calculated item. + * This field MUST be zero if any of the following apply: + * itmType is not zero. + * This item is in an OLAP PivotTable view. + * The sxaxisPage field of sxaxis in the Sxvd record of the pivot field equals 1 (the associated Sxvd is the last Sxvd record before this record in the stream (1)). + * The fCalculatedField field in the SXVDEx record of the pivot field equals 1. + * There is not an associated SXFDB record in the associated PivotCache. + * The fRangeGroup field of the SXFDB record, of the associated cache field of the pivot field, equals 1. + * The fCalculatedField field of the SXFDB record, of the associated cache field of the pivot field, equals 1. + *

        + * E - fMissing (1 bit): A bit that specifies if this pivot item does not exist in the data source (1). + * MUST be zero if itmType is not zero. MUST be zero for OLAP PivotTable view. + *

        + * reserved2 (11 bits): MUST be zero, and MUST be ignored. + *

        + * iCache (2 bytes): A signed integer that specifies a reference to a cache item. + * MUST be a value from the following table: + * Value Meaning + * -1 No cache item is referenced. + * 0+ A cache item index in the cache field associated with the pivot field, as specified by Cache Items. + * If itmType is not zero, a reference to a cache item is not specified and this value MUST be -1. + * Otherwise, this value MUST be greater than or equal to 0. + *

        + * cchName (2 bytes): An unsigned integer that specifies the length of the stName string. + * If the value is 0xFFFF then stName is NULL. + * Otherwise, the value MUST be less than or equal to 254. + *

        + * stName (variable): An XLUnicodeStringNoCch structure that specifies the name of this pivot item. + * If not NULL, this is used as the caption of the pivot item instead of the value in the + * cache item specified by iCache. The length of this field is specified in cchName. + * This field exists only if cchName is not 0xFFFF. If this is in a non-OLAP PivotTable + * view and this string is not NULL, it MUST be unique within all SXVI records in associated + * with the pivot field. + **/ - A - fHidden (1 bit): A bit that specifies whether this pivot item is hidden. - MUST be zero if itmType is not itmtypeData. MUST be zero for OLAP PivotTable view. +public class Sxvi extends XLSRecord { - B - fHideDetail (1 bit): A bit that specifies whether the pivot item detail is collapsed. - MUST be zero for OLAP PivotTable view. + /** + * serialVersionUID + */ + private static final long serialVersionUID = 6399665481118265257L; + byte[] data = null; + short itemtype = -1; + short cchName = -1; + String name = null; + short iCache = -1; + boolean fHidden, fHideDetail, fFormula, fMissing; - C - reserved1 (1 bit): MUST be zero, and MUST be ignored. + public static final short itmtypeData = 0x0000; //A data value + public static final short itmtypeDEFAULT = 0x0001; //Default subtotal for the pivot field + public static final short itmtypeSUM = 0x0002; //Sum of values in the pivot field + public static final short itmtypeCOUNTA = 0x0003; //Count of values in the pivot field + public static final short itmtypeAVERAGE = 0x0004; // Average of values in the pivot field + public static final short itmtypeMAX = 0x0005; // Max of values in the pivot field + public static final short itmtypeMIN = 0x0006; //Min of values in the pivot field + public static final short itmtypePRODUCT = 0x0007; //Product of values in the pivot field + public static final short itmtypeCOUNT = 0x0008; //Count of numbers in the pivot field + public static final short itmtypeSTDEV = 0x0009; //Statistical standard deviation (estimate) of the pivot field + public static final short itmtypeSTDEVP = 0x000A; //Statistical standard deviation (entire population) of the pivot field + public static final short itmtypeVAR = 0x000B; //Statistical variance (estimate) of the pivot field + public static final short itmtypeVARP = 0x000C; //Statistical variance (entire population) of the pivot field - D - fFormula (1 bit): A bit that specifies whether this pivot item is a calculated item. - This field MUST be zero if any of the following apply: - itmType is not zero. - This item is in an OLAP PivotTable view. - The sxaxisPage field of sxaxis in the Sxvd record of the pivot field equals 1 (the associated Sxvd is the last Sxvd record before this record in the stream (1)). - The fCalculatedField field in the SXVDEx record of the pivot field equals 1. - There is not an associated SXFDB record in the associated PivotCache. - The fRangeGroup field of the SXFDB record, of the associated cache field of the pivot field, equals 1. - The fCalculatedField field of the SXFDB record, of the associated cache field of the pivot field, equals 1. - E - fMissing (1 bit): A bit that specifies if this pivot item does not exist in the data source (1). - MUST be zero if itmType is not zero. MUST be zero for OLAP PivotTable view. + public void init() { + super.init(); + itemtype = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + byte b = this.getByteAt(2); + fHidden = (b & 0x1) == 0x1; + fHideDetail = (b & 0x2) == 0x2; + // bit 3- reserved + fFormula = (b & 0x8) == 0x8; + fMissing = (b & 0x10) == 0x10; + iCache = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + cchName = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + if (cchName != -1) { + byte encoding = this.getByteAt(10); + byte[] tmp = this.getBytesAt(11, (cchName) * (encoding + 1)); + try { + if (encoding == 0) + name = new String(tmp, DEFAULTENCODING); + else + name = new String(tmp, UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding PivotTable caption name in Sxvd: " + e); + } + } + if (DEBUGLEVEL > 3) Logger.logInfo("SXVI - itemtype:" + itemtype + " iCache: " + iCache + " name:" + name); + } - reserved2 (11 bits): MUST be zero, and MUST be ignored. + public String toString() { + return "SXVI - itemtype:" + itemtype + " iCache: " + iCache + " name:" + name; + } - iCache (2 bytes): A signed integer that specifies a reference to a cache item. - MUST be a value from the following table: - Value Meaning - -1 No cache item is referenced. - 0+ A cache item index in the cache field associated with the pivot field, as specified by Cache Items. - If itmType is not zero, a reference to a cache item is not specified and this value MUST be -1. - Otherwise, this value MUST be greater than or equal to 0. + /** + * returns the type of this pivot item + *
        one of: + *

      • 0x0000 itmtypeData A data value + *
      • 0x0001 itmtypeDEFAULT Default subtotal for the pivot field + *
      • 0x0002 itmtypeSUM Sum of values in the pivot field + *
      • 0x0003 itmtypeCOUNTA Count of values in the pivot field + *
      • 0x0004 itmtypeAVERAGE Average of values in the pivot field + *
      • 0x0005 itmtypeMAX Max of values in the pivot field + *
      • 0x0006 itmtypeMIN Min of values in the pivot field + *
      • 0x0007 itmtypePRODUCT Product of values in the pivot field + *
      • 0x0008 itmtypeCOUNT Count of numbers in the pivot field + *
      • 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field + *
      • 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field + *
      • 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field + *
      • 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field + * + * @return + */ + public short getItemType() { + return itemtype; + } - cchName (2 bytes): An unsigned integer that specifies the length of the stName string. - If the value is 0xFFFF then stName is NULL. - Otherwise, the value MUST be less than or equal to 254. + /** + * sets the pivot item type: + *
        one of: + *
      • 0x0000 itmtypeData A data value + *
      • 0x0001 itmtypeDEFAULT Default subtotal for the pivot field + *
      • 0x0002 itmtypeSUM Sum of values in the pivot field + *
      • 0x0003 itmtypeCOUNTA Count of values in the pivot field + *
      • 0x0004 itmtypeAVERAGE Average of values in the pivot field + *
      • 0x0005 itmtypeMAX Max of values in the pivot field + *
      • 0x0006 itmtypeMIN Min of values in the pivot field + *
      • 0x0007 itmtypePRODUCT Product of values in the pivot field + *
      • 0x0008 itmtypeCOUNT Count of numbers in the pivot field + *
      • 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field + *
      • 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field + *
      • 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field + *
      • 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field + * + * @return + */ + public void setItemType(int type) { + itemtype = (short) type; + byte[] b = ByteTools.shortToLEBytes(itemtype); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } - stName (variable): An XLUnicodeStringNoCch structure that specifies the name of this pivot item. - If not NULL, this is used as the caption of the pivot item instead of the value in the - cache item specified by iCache. The length of this field is specified in cchName. - This field exists only if cchName is not 0xFFFF. If this is in a non-OLAP PivotTable - view and this string is not NULL, it MUST be unique within all SXVI records in associated - with the pivot field. - - -**/ + /** + * reference to a cache item : + *
        -1 No cache item is referenced. + *
        0+ A cache item index in the cache field associated with the pivot field, as specified by Cache Items. + * + * @param icache + */ + public void setCacheItem(int icache) { + this.iCache = (short) icache; + byte[] b = ByteTools.shortToLEBytes(this.iCache); + this.getData()[4] = b[0]; + this.getData()[5] = b[1]; + } -public class Sxvi extends XLSRecord{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6399665481118265257L; - byte[] data = null; - short itemtype = -1; - short cchName= -1; - String name= null; - short iCache = -1; - boolean fHidden, fHideDetail, fFormula, fMissing; - - public static final short itmtypeData= 0x0000; //A data value - public static final short itmtypeDEFAULT= 0x0001; //Default subtotal for the pivot field - public static final short itmtypeSUM= 0x0002; //Sum of values in the pivot field - public static final short itmtypeCOUNTA= 0x0003; //Count of values in the pivot field - public static final short itmtypeAVERAGE= 0x0004; // Average of values in the pivot field - public static final short itmtypeMAX= 0x0005; // Max of values in the pivot field - public static final short itmtypeMIN= 0x0006; //Min of values in the pivot field - public static final short itmtypePRODUCT= 0x0007; //Product of values in the pivot field - public static final short itmtypeCOUNT= 0x0008; //Count of numbers in the pivot field - public static final short itmtypeSTDEV= 0x0009; //Statistical standard deviation (estimate) of the pivot field - public static final short itmtypeSTDEVP= 0x000A; //Statistical standard deviation (entire population) of the pivot field - public static final short itmtypeVAR= 0x000B; //Statistical variance (estimate) of the pivot field - public static final short itmtypeVARP= 0x000C; //Statistical variance (entire population) of the pivot field + /** + * returns the name of this pivot item; if not null, is the caption + * for this pivot item + * + * @return + */ + public String getName() { + return name; + } - - public void init(){ - super.init(); - itemtype = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - byte b= this.getByteAt(2); - fHidden= (b & 0x1)==0x1; - fHideDetail= (b & 0x2)== 0x2; - // bit 3- reserved - fFormula= (b & 0x8)==0x8; - fMissing= (b & 0x10)==0x10; - iCache = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - cchName= ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - if (cchName!=-1) { - byte encoding= this.getByteAt(10); - byte[] tmp = this.getBytesAt(11, (cchName) * (encoding+1)); - try{ - if (encoding==0) - name= new String(tmp, DEFAULTENCODING); - else - name= new String(tmp, UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding PivotTable caption name in Sxvd: " + e);} - } - if (DEBUGLEVEL > 3) Logger.logInfo("SXVI - itemtype:" + itemtype + " iCache: " + iCache + " name:" + name); - } + /** + * returns the name of this pivot item; if not null, is the caption + * for this pivot item + */ + public void setName(String name) { + this.name = name; + byte[] data = new byte[8]; + System.arraycopy(this.getData(), 0, data, 0, 7); + if (name != null) { + byte[] strbytes = null; + try { + strbytes = this.name.getBytes(DEFAULTENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding pivot table name in SXVI: " + e); + } - public String toString() { - return "SXVI - itemtype:" + itemtype + " iCache: " + iCache + " name:" + name; - } - /** - * returns the type of this pivot item - *
        one of: -
      • 0x0000 itmtypeData A data value -
      • 0x0001 itmtypeDEFAULT Default subtotal for the pivot field -
      • 0x0002 itmtypeSUM Sum of values in the pivot field -
      • 0x0003 itmtypeCOUNTA Count of values in the pivot field -
      • 0x0004 itmtypeAVERAGE Average of values in the pivot field -
      • 0x0005 itmtypeMAX Max of values in the pivot field -
      • 0x0006 itmtypeMIN Min of values in the pivot field -
      • 0x0007 itmtypePRODUCT Product of values in the pivot field -
      • 0x0008 itmtypeCOUNT Count of numbers in the pivot field -
      • 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field -
      • 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field -
      • 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field -
      • 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field - * @return - */ - public short getItemType() { return itemtype; } - - /** - * sets the pivot item type: - *
        one of: -
      • 0x0000 itmtypeData A data value -
      • 0x0001 itmtypeDEFAULT Default subtotal for the pivot field -
      • 0x0002 itmtypeSUM Sum of values in the pivot field -
      • 0x0003 itmtypeCOUNTA Count of values in the pivot field -
      • 0x0004 itmtypeAVERAGE Average of values in the pivot field -
      • 0x0005 itmtypeMAX Max of values in the pivot field -
      • 0x0006 itmtypeMIN Min of values in the pivot field -
      • 0x0007 itmtypePRODUCT Product of values in the pivot field -
      • 0x0008 itmtypeCOUNT Count of numbers in the pivot field -
      • 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field -
      • 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field -
      • 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field -
      • 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field - * @return - */ - public void setItemType(int type) { - itemtype= (short) type; - byte[] b= ByteTools.shortToLEBytes(itemtype); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; - } + //update the lengths: + cchName = (short) strbytes.length; + byte[] nm = ByteTools.shortToLEBytes(cchName); + data[6] = nm[0]; + data[7] = nm[1]; - /** - * reference to a cache item : -
        -1 No cache item is referenced. -
        0+ A cache item index in the cache field associated with the pivot field, as specified by Cache Items. - * @param icache - */ - public void setCacheItem(int icache) { - this.iCache=(short)icache; - byte[] b= ByteTools.shortToLEBytes(this.iCache); - this.getData()[4]= b[0]; - this.getData()[5]= b[1]; - } - /** - * returns the name of this pivot item; if not null, is the caption - * for this pivot item - * @return - */ - public String getName() { return name; } - /** - * returns the name of this pivot item; if not null, is the caption - * for this pivot item - */ - public void setName(String name) { - this.name= name; - byte[] data= new byte[8]; - System.arraycopy(this.getData(), 0, data, 0, 7); - if (name!=null) { - byte[] strbytes = null; - try{ - strbytes = this.name.getBytes(DEFAULTENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding pivot table name in SXVI: " + e);} - - //update the lengths: - cchName = (short)strbytes.length; - byte[] nm = ByteTools.shortToLEBytes(cchName); - data[6] = nm[0]; - data[7] = nm[1]; - - // now append variable-length string data - byte[] newrgch = new byte[cchName + 1]; // account for encoding bytes - System.arraycopy(strbytes, 0, newrgch, 1, cchName); + // now append variable-length string data + byte[] newrgch = new byte[cchName + 1]; // account for encoding bytes + System.arraycopy(strbytes, 0, newrgch, 1, cchName); - data= ByteTools.append(newrgch, data); - } else { - data[6] = -1; - data[7] = -1; - } + data = ByteTools.append(newrgch, data); + } else { + data[6] = -1; + data[7] = -1; + } this.setData(data); - } - - /** - * returns true if this pivot item is hidden - * @return - */ - public boolean getIsHidden() { return fHidden; } - /** - * sets the hidden state for this pivot item - * @param b - */ - public void setIsHidden(boolean b) { - fHidden= b; - byte by= this.getByteAt(2); - if (fHidden) - this.getData()[2]= (byte)(by & 0x1); - else - this.getData()[2]= (byte) (by ^ 0x1); - } - /** - * specifies whether the pivot item detail is collapsed. - * @return - */ - public boolean getIsCollapsed() { return fHideDetail; } - /** - * specifies whether the pivot item detail is collapsed. - * @return - */ - public void setIsCollapsed(boolean b) { - fHideDetail= b; - byte by= this.getByteAt(2); - if (fHideDetail) - this.getData()[2]= (byte)(by & 0x2); - else - this.getData()[2]= (byte) (by ^ 0x2); - } - // TODO: fFormula, fMissing - - - private byte[] PROTOTYPE_BYTES = new byte[] { - 0, 0, /* itmtype */ - 0, 0, /* flags */ - 0, 0, /* icache */ - -1, -1, /* cchName */ + } + + /** + * returns true if this pivot item is hidden + * + * @return + */ + public boolean getIsHidden() { + return fHidden; + } + + /** + * sets the hidden state for this pivot item + * + * @param b + */ + public void setIsHidden(boolean b) { + fHidden = b; + byte by = this.getByteAt(2); + if (fHidden) + this.getData()[2] = (byte) (by & 0x1); + else + this.getData()[2] = (byte) (by ^ 0x1); + } + + /** + * specifies whether the pivot item detail is collapsed. + * + * @return + */ + public boolean getIsCollapsed() { + return fHideDetail; + } + + /** + * specifies whether the pivot item detail is collapsed. + * + * @return + */ + public void setIsCollapsed(boolean b) { + fHideDetail = b; + byte by = this.getByteAt(2); + if (fHideDetail) + this.getData()[2] = (byte) (by & 0x2); + else + this.getData()[2] = (byte) (by ^ 0x2); + } + // TODO: fFormula, fMissing + + + private byte[] PROTOTYPE_BYTES = new byte[]{ + 0, 0, /* itmtype */ + 0, 0, /* flags */ + 0, 0, /* icache */ + -1, -1, /* cchName */ }; - + public static XLSRecord getPrototype() { - Sxvi si= new Sxvi(); - si.setOpcode(SXVI); - si.setData(si.PROTOTYPE_BYTES); - si.init(); - return si; + Sxvi si = new Sxvi(); + si.setOpcode(SXVI); + si.setData(si.PROTOTYPE_BYTES); + si.init(); + return si; } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxview.java b/src/main/java/io/starter/formats/XLS/Sxview.java index 89d1cdf..3be2e19 100644 --- a/src/main/java/io/starter/formats/XLS/Sxview.java +++ b/src/main/java/io/starter/formats/XLS/Sxview.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,122 +22,120 @@ */ package io.starter.formats.XLS; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; - import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.SxAddl.SxcView; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; -/** SXVIEW B0h: This record contains top-level pivot table information.
        -

        -    ref (8 bytes): A Ref8U structure that specifies the PivotTable report body. For more information, see Location and Body.
        -    rwFirstHead (2 bytes): An RwU structure that specifies the first row of the row area. 
        -    			MUST be 1 if none of the axes are assigned in this PivotTable view. 
        -    			Otherwise, the value MUST be greater than or equal to ref.rwFirst.
        -
        -    rwFirstData (2 bytes): An RwU structure that specifies the first row of the data area. 
        -    			MUST be 1 if none of the axes are assigned in this PivotTable view. 
        -    			Otherwise, it MUST be equal to the value as specified by the following formula:
        -
        -    rwFirstData = rwFirstHead + cDimCol
        -
        -    colFirstData (2 bytes): A ColU structure that specifies the first column of the data area. 
        -    			It MUST be 1 if none of the axes are assigned in this PivotTable view. 
        -    			Otherwise, the value MUST be greater than or equal to ref.colFirst, and if the value of cDimCol or cDimData is not zero, 
        -    			it MUST be less than or equal to ref.colLast.
        -
        -    iCache (2 bytes): A signed integer that specifies the zero-based index of an SXStreamID record in the Globals substream. 
        -    		MUST be greater than or equal to zero and less than the number of SXStreamID records in the Globals substream.
        -
        -    reserved (2 bytes): MUST be zero, and MUST be ignored.
        -
        -    sxaxis4Data (2 bytes): An SXAxis structure that specifies the default axis for the data field. 
        -    		Either the sxaxis4Data.sxaxisRw field MUST be 1 or the sxaxis4Data.sxaxisCol field MUST be 1. 
        -    		The sxaxis4Data.sxaxisPage field MUST be 0 and the sxaxis4Data.sxaxisData field MUST be 0.
        -
        -    ipos4Data (2 bytes): A signed integer that specifies the row or column position for the data field in the PivotTable view. 
        -    		The sxaxis4Data field specifies whether this is a row or column position. 
        -    		MUST be greater than or equal to -1 and less than or equal to 0x7FFF. A value of -1 specifies the default position.
        -
        -    cDim (2 bytes): A signed integer that specifies the number of pivot fields in the PivotTable view. 
        -    		MUST equal the number of Sxvd records following this record. 
        -    		MUST equal the number of fields in the associated PivotCache specified by iCache.
        -
        -    cDimRw (2 bytes): An unsigned integer that specifies the number of fields on the row axis of the PivotTable view. 
        -    		MUST be less than or equal to 0x7FFF. MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain row items.
        -
        -    cDimCol (2 bytes): An unsigned integer that specifies the number of fields on the column axis of the PivotTable view. 
        -    		MUST be less than or equal to 0x7FFF. 
        -    		MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain column items.
        -
        -    cDimPg (2 bytes): An unsigned integer that specifies the number of page fields in the PivotTable view. 
        -    		MUST be less than or equal to 0x7FFF. 
        -    		MUST equal the number of array elements in the SXPI record in this PivotTable view.
        -
        -    cDimData (2 bytes): A signed integer that specifies the number of data fields in the PivotTable view. 
        -    		MUST be greater than or equal to zero and less than or equal to 0x7FFF. 
        -    		MUST equal the number of SXDI records in this PivotTable view.
        -
        -    cRw (2 bytes): An unsigned integer that specifies the number of pivot lines in the row area of the PivotTable view. 
        -    		MUST be less than or equal to 0x7FFF. 
        -    		MUST equal the number of array elements in the first SXLI record in this PivotTable view.
        -
        -    cCol (2 bytes): An unsigned integer that specifies the number of pivot lines in the column area of the PivotTable view. 
        -    		MUST equal the number of array elements in the second SXLI record in this PivotTable view.
        -
        -    A - fRwGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for rows. 
        -    		MUST be 0 if none of the axes have been assigned in this PivotTable view.
        -
        -    B - fColGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for columns. 
        -    		MUST be 1 if none of the axes are assigned in this PivotTable view.
        -
        -    C - unused1 (1 bit): Undefined and MUST be ignored.
        -
        -    D - fAutoFormat (1 bit): A bit that specifies whether the PivotTable has AutoFormat applied.
        -
        -    E - fAtrNum (1 bit): A bit that specifies whether the PivotTable has number AutoFormat applied.
        -
        -    F - fAtrFnt (1 bit): A bit that specifies whether the PivotTable has font AutoFormat applied.
        -
        -    G - fAtrAlc (1 bit): A bit that specifies whether the PivotTable has alignment AutoFormat applied.
        -
        -    H - fAtrBdr (1 bit): A bit that specifies whether the PivotTable has border AutoFormat applied.
        -
        -    I - fAtrPat (1 bit): A bit that specifies whether the PivotTable has pattern AutoFormat applied.
        -
        -    J - fAtrProc (1 bit): A bit that specifies whether the PivotTable has width/height AutoFormat applied.
        -
        -    unused2 (6 bits): Undefined and MUST be ignored.
        -
        -    itblAutoFmt (2 bytes): An AutoFmt8 structure that specifies the PivotTable AutoFormat. 
        -    		If the value of itblAutoFmt in the associated SXViewEx9 record is not 1, this field is overridden by the value of itblAutoFmt in the associated SXViewEx9.
        -
        -    cchTableName (2 bytes): An unsigned integer that specifies the length, in characters, of stTable. 
        -    		MUST be greater than or equal to zero and less than or equal to 0x00FF.
        -
        -    cchDataName (2 bytes): An unsigned integer that specifies the length, in characters of stData. 
        -    		MUST be greater than zero and less than or equal to 0x00FE.
        -
        -    stTable (variable): An XLUnicodeStringNoCch structure that specifies the name of the PivotTable. 
        -    		The length of this field is specified by cchTableName.
         
        -    stData (variable): An XLUnicodeStringNoCch structure that specifies the name of the data field. 
        -    		The length of this field is specified by cchDataName.
        -   
        -   
        - -**/ -public class Sxview extends XLSRecord implements XLSConstants -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - short rwFirst = 0x0; // First Row of Pivot Table +/** + * SXVIEW B0h: This record contains top-level pivot table information.
        + *

        + * ref (8 bytes): A Ref8U structure that specifies the PivotTable report body. For more information, see Location and Body.
        + * rwFirstHead (2 bytes): An RwU structure that specifies the first row of the row area.
        + * MUST be 1 if none of the axes are assigned in this PivotTable view.
        + * Otherwise, the value MUST be greater than or equal to ref.rwFirst.
        + *
        + * rwFirstData (2 bytes): An RwU structure that specifies the first row of the data area.
        + * MUST be 1 if none of the axes are assigned in this PivotTable view.
        + * Otherwise, it MUST be equal to the value as specified by the following formula:
        + *
        + * rwFirstData = rwFirstHead + cDimCol
        + *
        + * colFirstData (2 bytes): A ColU structure that specifies the first column of the data area.
        + * It MUST be 1 if none of the axes are assigned in this PivotTable view.
        + * Otherwise, the value MUST be greater than or equal to ref.colFirst, and if the value of cDimCol or cDimData is not zero,
        + * it MUST be less than or equal to ref.colLast.
        + *
        + * iCache (2 bytes): A signed integer that specifies the zero-based index of an SXStreamID record in the Globals substream.
        + * MUST be greater than or equal to zero and less than the number of SXStreamID records in the Globals substream.
        + *
        + * reserved (2 bytes): MUST be zero, and MUST be ignored.
        + *
        + * sxaxis4Data (2 bytes): An SXAxis structure that specifies the default axis for the data field.
        + * Either the sxaxis4Data.sxaxisRw field MUST be 1 or the sxaxis4Data.sxaxisCol field MUST be 1.
        + * The sxaxis4Data.sxaxisPage field MUST be 0 and the sxaxis4Data.sxaxisData field MUST be 0.
        + *
        + * ipos4Data (2 bytes): A signed integer that specifies the row or column position for the data field in the PivotTable view.
        + * The sxaxis4Data field specifies whether this is a row or column position.
        + * MUST be greater than or equal to -1 and less than or equal to 0x7FFF. A value of -1 specifies the default position.
        + *
        + * cDim (2 bytes): A signed integer that specifies the number of pivot fields in the PivotTable view.
        + * MUST equal the number of Sxvd records following this record.
        + * MUST equal the number of fields in the associated PivotCache specified by iCache.
        + *
        + * cDimRw (2 bytes): An unsigned integer that specifies the number of fields on the row axis of the PivotTable view.
        + * MUST be less than or equal to 0x7FFF. MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain row items.
        + *
        + * cDimCol (2 bytes): An unsigned integer that specifies the number of fields on the column axis of the PivotTable view.
        + * MUST be less than or equal to 0x7FFF.
        + * MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain column items.
        + *
        + * cDimPg (2 bytes): An unsigned integer that specifies the number of page fields in the PivotTable view.
        + * MUST be less than or equal to 0x7FFF.
        + * MUST equal the number of array elements in the SXPI record in this PivotTable view.
        + *
        + * cDimData (2 bytes): A signed integer that specifies the number of data fields in the PivotTable view.
        + * MUST be greater than or equal to zero and less than or equal to 0x7FFF.
        + * MUST equal the number of SXDI records in this PivotTable view.
        + *
        + * cRw (2 bytes): An unsigned integer that specifies the number of pivot lines in the row area of the PivotTable view.
        + * MUST be less than or equal to 0x7FFF.
        + * MUST equal the number of array elements in the first SXLI record in this PivotTable view.
        + *
        + * cCol (2 bytes): An unsigned integer that specifies the number of pivot lines in the column area of the PivotTable view.
        + * MUST equal the number of array elements in the second SXLI record in this PivotTable view.
        + *
        + * A - fRwGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for rows.
        + * MUST be 0 if none of the axes have been assigned in this PivotTable view.
        + *
        + * B - fColGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for columns.
        + * MUST be 1 if none of the axes are assigned in this PivotTable view.
        + *
        + * C - unused1 (1 bit): Undefined and MUST be ignored.
        + *
        + * D - fAutoFormat (1 bit): A bit that specifies whether the PivotTable has AutoFormat applied.
        + *
        + * E - fAtrNum (1 bit): A bit that specifies whether the PivotTable has number AutoFormat applied.
        + *
        + * F - fAtrFnt (1 bit): A bit that specifies whether the PivotTable has font AutoFormat applied.
        + *
        + * G - fAtrAlc (1 bit): A bit that specifies whether the PivotTable has alignment AutoFormat applied.
        + *
        + * H - fAtrBdr (1 bit): A bit that specifies whether the PivotTable has border AutoFormat applied.
        + *
        + * I - fAtrPat (1 bit): A bit that specifies whether the PivotTable has pattern AutoFormat applied.
        + *
        + * J - fAtrProc (1 bit): A bit that specifies whether the PivotTable has width/height AutoFormat applied.
        + *
        + * unused2 (6 bits): Undefined and MUST be ignored.
        + *
        + * itblAutoFmt (2 bytes): An AutoFmt8 structure that specifies the PivotTable AutoFormat.
        + * If the value of itblAutoFmt in the associated SXViewEx9 record is not 1, this field is overridden by the value of itblAutoFmt in the associated SXViewEx9.
        + *
        + * cchTableName (2 bytes): An unsigned integer that specifies the length, in characters, of stTable.
        + * MUST be greater than or equal to zero and less than or equal to 0x00FF.
        + *
        + * cchDataName (2 bytes): An unsigned integer that specifies the length, in characters of stData.
        + * MUST be greater than zero and less than or equal to 0x00FE.
        + *
        + * stTable (variable): An XLUnicodeStringNoCch structure that specifies the name of the PivotTable.
        + * The length of this field is specified by cchTableName.
        + *
        + * stData (variable): An XLUnicodeStringNoCch structure that specifies the name of the data field.
        + * The length of this field is specified by cchDataName.
        + *
        + * 
        + **/ +public class Sxview extends XLSRecord implements XLSConstants { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2639291289806138985L; + short rwFirst = 0x0; // First Row of Pivot Table short rwLast = 0x0; // Last Row of Pivot Table short colFirst = 0x0; // First Column of Pivot Table short colLast = 0x0; // Last Column of Pivot Table @@ -161,7 +159,7 @@ public class Sxview extends XLSRecord implements XLSConstants short cchName = 0x0; // Length the Pivot Table name short cchData = 0x0; // Length of the data field name byte[] rgch;// PivotTableName followed by the name of a data field - + /* The following member variables are populated from parsing the grbit field */ @@ -175,127 +173,133 @@ public class Sxview extends XLSRecord implements XLSConstants boolean fBorder = false; // = 1 if the border autoformat is applied boolean fPattern = false; // = 1 if the pattern autoformat is applied boolean fNumber = false; // = 1 if the number autoformat is applied - + /* The following member variables are derived from the rgch field */ String PivotTableName = null; String DataFieldName = null; - - private ArrayList subRecs= new ArrayList(); - - private byte[] PROTOTYPE_BYTES = new byte[] { - 0, 0, 0, 0, 0, 0, 0, 0, /* ref */ - 0, 0, /* rwfirstHead */ - 0, 0, /* rwfirstData */ - 0, 0, /* colfirstHead */ - 0, 0, /* iCache */ - 0, 0, /* reserved */ - 1, 0, /* saxis4Data */ - -1, -1, /* ipos4Data */ - 0, 0, /* cDim */ - 0, 0, /* cDimRw */ - 0, 0, /* cDimCol */ - 0, 0, /* cDimPg */ - 0, 0, /* cDimData */ - 0, 0, /* cRw */ - 0, 0, /* cCol */ - 11, 2, /* grbit + reserved */ - 1, 0, /* itblAutoFmt */ - 0, 0, /* cchTableName */ - 0, 0, /* cchDataName */ - + + private ArrayList subRecs = new ArrayList(); + + private byte[] PROTOTYPE_BYTES = new byte[]{ + 0, 0, 0, 0, 0, 0, 0, 0, /* ref */ + 0, 0, /* rwfirstHead */ + 0, 0, /* rwfirstData */ + 0, 0, /* colfirstHead */ + 0, 0, /* iCache */ + 0, 0, /* reserved */ + 1, 0, /* saxis4Data */ + -1, -1, /* ipos4Data */ + 0, 0, /* cDim */ + 0, 0, /* cDimRw */ + 0, 0, /* cDimCol */ + 0, 0, /* cDimPg */ + 0, 0, /* cDimData */ + 0, 0, /* cRw */ + 0, 0, /* cCol */ + 11, 2, /* grbit + reserved */ + 1, 0, /* itblAutoFmt */ + 0, 0, /* cchTableName */ + 0, 0, /* cchDataName */ + }; - + public static XLSRecord getPrototype() { - Sxview sx= new Sxview(); - sx.setOpcode(SXVIEW); - sx.setData(sx.PROTOTYPE_BYTES); - sx.init(); - return sx; - } - - public void init(){ - super.init(); - if(this.getLength() <= 0){ // Is this record populated? - if(DEBUGLEVEL > -1)Logger.logInfo("no data in SXVIEW"); + Sxview sx = new Sxview(); + sx.setOpcode(SXVIEW); + sx.setData(sx.PROTOTYPE_BYTES); + sx.init(); + return sx; + } + + public void init() { + super.init(); + if (this.getLength() <= 0) { // Is this record populated? + if (DEBUGLEVEL > -1) Logger.logInfo("no data in SXVIEW"); } else { // parse out all the fields - rwFirst = ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); - rwLast = ByteTools.readShort(this.getByteAt(2),this.getByteAt(3)); - colFirst = ByteTools.readShort(this.getByteAt(4),this.getByteAt(5)); - colLast = ByteTools.readShort(this.getByteAt(6),this.getByteAt(7)); - rwFirstHead = ByteTools.readShort(this.getByteAt(8),this.getByteAt(9)); - rwFirstData = ByteTools.readShort(this.getByteAt(10),this.getByteAt(11)); - colFirstData = ByteTools.readShort(this.getByteAt(12),this.getByteAt(13)); - iCache = ByteTools.readShort(this.getByteAt(14),this.getByteAt(15)); - + rwFirst = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + rwLast = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + colFirst = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + colLast = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + rwFirstHead = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + rwFirstData = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); + colFirstData = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + iCache = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); + // 16 & 17 - reserved must be zero - - sxaxis4Data = ByteTools.readShort(this.getByteAt(18),this.getByteAt(19)); - ipos4Data = ByteTools.readShort(this.getByteAt(20),this.getByteAt(21)); - cDim = ByteTools.readShort(this.getByteAt(22),this.getByteAt(23)); - cDimRw = ByteTools.readShort(this.getByteAt(24),this.getByteAt(25)); - cDimCol = ByteTools.readShort(this.getByteAt(26),this.getByteAt(27)); - cDimPg = ByteTools.readShort(this.getByteAt(28),this.getByteAt(29)); - cDimData = ByteTools.readShort(this.getByteAt(30),this.getByteAt(31)); - cRw = ByteTools.readShort(this.getByteAt(32),this.getByteAt(33)); - cCol = ByteTools.readShort(this.getByteAt(34),this.getByteAt(35)); + + sxaxis4Data = ByteTools.readShort(this.getByteAt(18), this.getByteAt(19)); + ipos4Data = ByteTools.readShort(this.getByteAt(20), this.getByteAt(21)); + cDim = ByteTools.readShort(this.getByteAt(22), this.getByteAt(23)); + cDimRw = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); + cDimCol = ByteTools.readShort(this.getByteAt(26), this.getByteAt(27)); + cDimPg = ByteTools.readShort(this.getByteAt(28), this.getByteAt(29)); + cDimData = ByteTools.readShort(this.getByteAt(30), this.getByteAt(31)); + cRw = ByteTools.readShort(this.getByteAt(32), this.getByteAt(33)); + cCol = ByteTools.readShort(this.getByteAt(34), this.getByteAt(35)); grbit1 = this.getByteAt(37); grbit2 = this.getByteAt(36); - + this.initGrbit(); // note the manual hibyting - itblAutoFmt = ByteTools.readShort(this.getByteAt(38),this.getByteAt(39)); - cchName = ByteTools.readShort(this.getByteAt(40),this.getByteAt(41)); - cchData = ByteTools.readShort(this.getByteAt(42),this.getByteAt(43)); - int fullnamelen = (int)((int)cchName) + ((int)cchData); + itblAutoFmt = ByteTools.readShort(this.getByteAt(38), this.getByteAt(39)); + cchName = ByteTools.readShort(this.getByteAt(40), this.getByteAt(41)); + cchData = ByteTools.readShort(this.getByteAt(42), this.getByteAt(43)); + int fullnamelen = (int) cchName + ((int) cchData); rgch = new byte[fullnamelen]; - int pos= 44; + int pos = 44; if (cchName > 0) { - //A - fHighByte (1 bit): A bit that specifies whether the characters in rgb are double-byte characters. - // 0x0 All the characters in the string have a high byte of 0x00 and only the low bytes are in rgb. - // 0x1 All the characters in the string are saved as double-byte characters in rgb. - // reserved (7 bits): MUST be zero, and MUST be ignored. - - byte encoding= this.getByteAt(pos++); - - byte[] tmp = this.getBytesAt(pos, (cchName) * (encoding+1)); - try{ - if (encoding==0) - PivotTableName = new String(tmp, DEFAULTENCODING); - else - PivotTableName = new String(tmp, UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding PivotTable name in Sxview: " + e);} - pos+=cchName * (encoding+1); + //A - fHighByte (1 bit): A bit that specifies whether the characters in rgb are double-byte characters. + // 0x0 All the characters in the string have a high byte of 0x00 and only the low bytes are in rgb. + // 0x1 All the characters in the string are saved as double-byte characters in rgb. + // reserved (7 bits): MUST be zero, and MUST be ignored. + + byte encoding = this.getByteAt(pos++); + + byte[] tmp = this.getBytesAt(pos, (cchName) * (encoding + 1)); + try { + if (encoding == 0) + PivotTableName = new String(tmp, DEFAULTENCODING); + else + PivotTableName = new String(tmp, UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding PivotTable name in Sxview: " + e); + } + pos += cchName * (encoding + 1); } if (cchData > 0) { - byte encoding= this.getByteAt(pos++); - byte[] tmp = this.getBytesAt(pos, (cchData) * (encoding+1)); - try{ - if (encoding==0) - DataFieldName = new String(tmp, DEFAULTENCODING); - else - DataFieldName = new String(tmp, UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding PivotTable name in Sxview: " + e);} - } + byte encoding = this.getByteAt(pos++); + byte[] tmp = this.getBytesAt(pos, (cchData) * (encoding + 1)); + try { + if (encoding == 0) + DataFieldName = new String(tmp, DEFAULTENCODING); + else + DataFieldName = new String(tmp, UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding PivotTable name in Sxview: " + e); + } + } } - if (DEBUGLEVEL > 3) Logger.logInfo("SXVIEW: name:" + this.getTableName() + " iCache:" + iCache + " cDim:" + cDim + " cDimRw:" + cDimRw + " cDimCol:" + cDimCol + " cDimPg:" + cDimPg + " cDimData:" + cDimData + " cRw:" + cRw + " cCol:" + cCol +" datafieldname:" + DataFieldName); + if (DEBUGLEVEL > 3) + Logger.logInfo("SXVIEW: name:" + this.getTableName() + " iCache:" + iCache + " cDim:" + cDim + " cDimRw:" + cDimRw + " cDimCol:" + cDimCol + " cDimPg:" + cDimPg + " cDimData:" + cDimData + " cRw:" + cRw + " cCol:" + cCol + " datafieldname:" + DataFieldName); } - + /** * specifies the default axis for the data field: *
      • 1- row *
      • 2= col *
      • 4= page *
      • 8= data + * * @param s */ - protected void setAxis4Data(short s){ + protected void setAxis4Data(short s) { sxaxis4Data = s; byte[] b = ByteTools.shortToLEBytes(sxaxis4Data); - System.arraycopy(b, 0, data, 18, 2); + System.arraycopy(b, 0, data, 18, 2); } - + /** * retieves the default axis for the data field: *
      • 1- row @@ -303,68 +307,72 @@ protected void setAxis4Data(short s){ *
      • 4= page *
      • 8= data */ - public short getSxaxis4Data(){ + public short getSxaxis4Data() { return sxaxis4Data; } - + /** * A signed integer that specifies the row or column position for the data field in the PivotTable view. *
        see getSxaxis4Data to determine whether this is a row or column position + * * @param s */ - protected void setIpos4Data(short s){ + protected void setIpos4Data(short s) { ipos4Data = s; byte[] b = ByteTools.shortToLEBytes(ipos4Data); - System.arraycopy(b, 0, data, 20, 2); + System.arraycopy(b, 0, data, 20, 2); } - + /** * A signed integer that specifies the row or column position for the data field in the PivotTable view. *
        see getSxaxis4Data to determine whether this is a row or column position */ - public short getIpos4Data(){return ipos4Data;} - - + public short getIpos4Data() { + return ipos4Data; + } + + /** * sets the number of pivot fields (i.e. columns in the source data) for this pivot table *
        NOTE: this method wipes any existing data of the pivot table + * * @param s */ - public void setNPivotFields(short s){ - cDim = s; // number of pivot fields MUST==# of SxVd records + public void setNPivotFields(short s) { + cDim = s; // number of pivot fields MUST==# of SxVd records byte[] b = ByteTools.shortToLEBytes(cDim); System.arraycopy(b, 0, data, 22, 2); // remove ALL field-related records - for (int i= 0; i < subRecs.size(); i++) { - BiffRec br= ((BiffRec)subRecs.get(i)); - if (br.getOpcode()!=SXEX) { - this.getSheet().removeRecFromVec(br); - subRecs.remove(i); - i--; - } else - break; + for (int i = 0; i < subRecs.size(); i++) { + BiffRec br = subRecs.get(i); + if (br.getOpcode() != SXEX) { + this.getSheet().removeRecFromVec(br); + subRecs.remove(i); + i--; + } else + break; } // reset variables - cDimRw= 0; - cDimCol= 0; - cDimPg= 0; - cDimData= 0; - cRw= 0; - cCol= 0; + cDimRw = 0; + cDimCol = 0; + cDimPg = 0; + cDimData = 0; + cRw = 0; + cCol = 0; // now add the required records for each field - int zz= this.getRecordIndex()+1; - for (int i= 0; i < cDim; i++) { - Sxvd svd= (Sxvd) Sxvd.getPrototype(); // for each pivot field (which goes on an axis) - svd.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(zz++, svd); - this.subRecs.add(i*2,svd); - SxVdEX svdex= (SxVdEX) SxVdEX.getPrototype(); - svdex.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(zz++, svdex); - this.subRecs.add((i*2)+1,svdex); + int zz = this.getRecordIndex() + 1; + for (int i = 0; i < cDim; i++) { + Sxvd svd = (Sxvd) Sxvd.getPrototype(); // for each pivot field (which goes on an axis) + svd.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(zz++, svd); + this.subRecs.add(i * 2, svd); + SxVdEX svdex = (SxVdEX) SxVdEX.getPrototype(); + svdex.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(zz++, svdex); + this.subRecs.add((i * 2) + 1, svdex); } } - + /** * adds the pivot field corresponding to cache field at index fieldIndex to the desired axis *
        A pivot field is a cache field that has been added to the pivot table @@ -375,27 +383,28 @@ public void setNPivotFields(short s){ *
      • 2= col *
      • 4= page *
      • 8= data + * * @param axis - * @param fieldIndex 0-based pivot field index + * @param fieldIndex 0-based pivot field index * @see SxVd.AXIS_ constants */ public Sxvd addPivotFieldToAxis(int axis, int fieldIndex) { - int zz= this.getRecordIndex()+1; - SxVdEX sxvdex= (SxVdEX) getSubRec(SXVDEX, fieldIndex); // end of last pivot field set (PIVOTVD rule) - if (sxvdex!=null) - zz= sxvdex.getRecordIndex()+1; + int zz = this.getRecordIndex() + 1; + SxVdEX sxvdex = (SxVdEX) getSubRec(SXVDEX, fieldIndex); // end of last pivot field set (PIVOTVD rule) + if (sxvdex != null) + zz = sxvdex.getRecordIndex() + 1; - Sxvd sxvd= (Sxvd) Sxvd.getPrototype(); // for each pivot field (which goes on an axis) + Sxvd sxvd = (Sxvd) Sxvd.getPrototype(); // for each pivot field (which goes on an axis) sxvd.setSheet(this.getSheet()); this.getSheet().getSheetRecs().add(zz++, sxvd); - this.subRecs.add(cDim*2,sxvd); - SxVdEX svdex= (SxVdEX) SxVdEX.getPrototype(); + this.subRecs.add(cDim * 2, sxvd); + SxVdEX svdex = (SxVdEX) SxVdEX.getPrototype(); svdex.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(zz++, svdex); - this.subRecs.add((cDim*2)+1,svdex); + this.getSheet().getSheetRecs().add(zz++, svdex); + this.subRecs.add((cDim * 2) + 1, svdex); cDim++; - sxvd.setAxis(axis); - return sxvd; + sxvd.setAxis(axis); + return sxvd; /* Sxvd sxvd= (Sxvd) getSubRec(SXVD, fieldIndex); @@ -406,154 +415,173 @@ public Sxvd addPivotFieldToAxis(int axis, int fieldIndex) { } return null;*/ } - - + + /** - * returns the number of pivot fields (==columns in the pivot table data range) + * returns the number of pivot fields (==columns in the pivot table data range) + * * @return */ - public short getNPivotFields(){return cDim;} - + public short getNPivotFields() { + return cDim; + } + /** - adds a pivot field (0-based index) to the ROW axis + * adds a pivot field (0-based index) to the ROW axis + * * @param fieldNumber */ public void addRowField(int fieldNumber) { - cDimRw++; // MUST==# row elements in SxIVD + cDimRw++; // MUST==# row elements in SxIVD byte[] b = ByteTools.shortToLEBytes(cDimRw); System.arraycopy(b, 0, data, 24, 2); - Sxivd sxivd= (Sxivd) getSubRec(SXIVD, (cDimCol>0)?1:0); - if (sxivd==null) { - int zz= getPivotRecordInsertionIndexes(SXIVD, 1, -1); - if (zz>0) { // should!!! - sxivd= (Sxivd) Sxivd.getPrototype(); - sxivd.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxivd); - this.subRecs.add(zz,sxivd); - } - } - sxivd.addField(fieldNumber); + Sxivd sxivd = (Sxivd) getSubRec(SXIVD, (cDimCol > 0) ? 1 : 0); + if (sxivd == null) { + int zz = getPivotRecordInsertionIndexes(SXIVD, 1, -1); + if (zz > 0) { // should!!! + sxivd = (Sxivd) Sxivd.getPrototype(); + sxivd.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxivd); + this.subRecs.add(zz, sxivd); + } + } + sxivd.addField(fieldNumber); } - + /** - adds a pivot field (0-based index) to the COL axis - */ + * adds a pivot field (0-based index) to the COL axis + */ public void addColField(int fieldNumber) { cDimCol++; byte[] b = ByteTools.shortToLEBytes(cDimCol); System.arraycopy(b, 0, data, 26, 2); - Sxivd sxivd= (Sxivd) getSubRec(SXIVD, 0); - if (sxivd==null) { - int zz= getPivotRecordInsertionIndexes(SXIVD, 0, -1); - if (zz>0) { // should!! - sxivd= (Sxivd) Sxivd.getPrototype(); - sxivd.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxivd); - this.subRecs.add(zz,sxivd); - } - } - sxivd.addField(fieldNumber); + Sxivd sxivd = (Sxivd) getSubRec(SXIVD, 0); + if (sxivd == null) { + int zz = getPivotRecordInsertionIndexes(SXIVD, 0, -1); + if (zz > 0) { // should!! + sxivd = (Sxivd) Sxivd.getPrototype(); + sxivd.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxivd); + this.subRecs.add(zz, sxivd); + } + } + sxivd.addField(fieldNumber); } - + /** * returns the number of pivot fields on the ROW axis + * * @return */ - public short getCDimRw(){return cDimRw;} + public short getCDimRw() { + return cDimRw; + } + - /** * returns the number of pivot fields on the COL axis + * * @return */ - public short getCDimCol(){return cDimCol;} - + public short getCDimCol() { + return cDimCol; + } + /** * adds a pivot field to the page axis + * * @param strref */ - public void addPageField(int fieldIndex, int itemIndex){ + public void addPageField(int fieldIndex, int itemIndex) { cDimPg++; byte[] b = ByteTools.shortToLEBytes(cDimPg); System.arraycopy(b, 0, data, 28, 2); - SxPI sxpi= (SxPI) getSubRec(SXPI, -1); - if (sxpi==null) { - int zz= getPivotRecordInsertionIndexes(SXPI, -1, -1); - if (zz>0) { // should!!! - sxpi= (SxPI) SxPI.getPrototype(); - sxpi.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxpi); - this.subRecs.add(zz,sxpi); - } - } - sxpi.addPageField(fieldIndex, itemIndex); + SxPI sxpi = (SxPI) getSubRec(SXPI, -1); + if (sxpi == null) { + int zz = getPivotRecordInsertionIndexes(SXPI, -1, -1); + if (zz > 0) { // should!!! + sxpi = (SxPI) SxPI.getPrototype(); + sxpi.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxpi); + this.subRecs.add(zz, sxpi); + } + } + sxpi.addPageField(fieldIndex, itemIndex); } - + /** * returns the number of fields on the page axis + * * @return */ - public short getCDimPg(){return cDimPg;} - + public short getCDimPg() { + return cDimPg; + } + /** * adds a pivot field to the DATA axis + * * @param fieldIndex * @param aggregateFunction */ - public void addDataField(int fieldIndex, String aggregateFunction, String name){ + public void addDataField(int fieldIndex, String aggregateFunction, String name) { cDimData++; byte[] b = ByteTools.shortToLEBytes(cDimData); System.arraycopy(b, 0, data, 30, 2); - SxDI sxdi= null; + SxDI sxdi = null; /* SxDI sxdi= (SxDI) getSubRec(SXDI, -1); - if (sxdi==null) { */ - int zz= getPivotRecordInsertionIndexes(SXDI, -1, -1); - if (zz>0) { // should!!! - sxdi= (SxDI) SxDI.getPrototype(); - sxdi.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxdi); - this.subRecs.add(zz,sxdi); - } -/* }*/ - sxdi.addDataField(fieldIndex, aggregateFunction, name); - } - + if (sxdi==null) { */ + int zz = getPivotRecordInsertionIndexes(SXDI, -1, -1); + if (zz > 0) { // should!!! + sxdi = (SxDI) SxDI.getPrototype(); + sxdi.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxdi); + this.subRecs.add(zz, sxdi); + } + /* }*/ + sxdi.addDataField(fieldIndex, aggregateFunction, name); + } + /** * returns the number of pivot fields on the data axis + * * @return */ - public short getCDimData(){return cDimData;} - - /** - * adds a pivot item to the end of the list of items on this axis (ROW, COL, DATA or PAGE) - * @param axis Axis int: ROW, COL, DATA or PAGE - * @param itemType one of: -
      • 0x0000 itmtypeData A data value -
      • 0x0001 itmtypeDEFAULT Default subtotal for the pivot field -
      • 0x0002 itmtypeSUM Sum of values in the pivot field -
      • 0x0003 itmtypeCOUNTA Count of values in the pivot field -
      • 0x0004 itmtypeAVERAGE Average of values in the pivot field -
      • 0x0005 itmtypeMAX Max of values in the pivot field -
      • 0x0006 itmtypeMIN Min of values in the pivot field -
      • 0x0007 itmtypePRODUCT Product of values in the pivot field -
      • 0x0008 itmtypeCOUNT Count of numbers in the pivot field -
      • 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field -
      • 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field -
      • 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field -
      • 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field - * @param cacheItem A cache item index in the cache field associated with the pivot field, as specified by Cache Items. - */ - public void addPivotItem(Sxvd axis, int itemType, int cacheItem) { - int n= axis.getNumItems(); // Axis record = Sxvd. Identifies pivot field on axis. Pivot items records follow until SXVDEX. - axis.setNumItems(n+1); - int axisIndex= getSubRecIndex(axis); - int zz= getPivotRecordInsertionIndexes(SXVI, n, axisIndex); // get LAST pivot field on axis - Sxvi sxvi= (Sxvi) Sxvi.getPrototype(); // pivot item record - sxvi.setItemType(itemType); - sxvi.setCacheItem(cacheItem); - sxvi.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxvi); - subRecs.add(zz, sxvi); + public short getCDimData() { + return cDimData; + } + + /** + * adds a pivot item to the end of the list of items on this axis (ROW, COL, DATA or PAGE) + * + * @param axis Axis int: ROW, COL, DATA or PAGE + * @param itemType one of: + *
      • 0x0000 itmtypeData A data value + *
      • 0x0001 itmtypeDEFAULT Default subtotal for the pivot field + *
      • 0x0002 itmtypeSUM Sum of values in the pivot field + *
      • 0x0003 itmtypeCOUNTA Count of values in the pivot field + *
      • 0x0004 itmtypeAVERAGE Average of values in the pivot field + *
      • 0x0005 itmtypeMAX Max of values in the pivot field + *
      • 0x0006 itmtypeMIN Min of values in the pivot field + *
      • 0x0007 itmtypePRODUCT Product of values in the pivot field + *
      • 0x0008 itmtypeCOUNT Count of numbers in the pivot field + *
      • 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field + *
      • 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field + *
      • 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field + *
      • 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field + * @param cacheItem A cache item index in the cache field associated with the pivot field, as specified by Cache Items. + */ + public void addPivotItem(Sxvd axis, int itemType, int cacheItem) { + int n = axis.getNumItems(); // Axis record = Sxvd. Identifies pivot field on axis. Pivot items records follow until SXVDEX. + axis.setNumItems(n + 1); + int axisIndex = getSubRecIndex(axis); + int zz = getPivotRecordInsertionIndexes(SXVI, n, axisIndex); // get LAST pivot field on axis + Sxvi sxvi = (Sxvi) Sxvi.getPrototype(); // pivot item record + sxvi.setItemType(itemType); + sxvi.setCacheItem(cacheItem); + sxvi.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxvi); + subRecs.add(zz, sxvi); /*if (axis.getAxisType()==Sxvd.AXIS_ROW) { } else if (axis.getAxisType()==Sxvd.AXIS_COL) { } else if (axis.getAxisType()==Sxvd.AXIS_PAGE) { @@ -561,14 +589,15 @@ public void addPivotItem(Sxvd axis, int itemType, int cacheItem) { } else if (axis.getAxisType()==Sxvd.AXIS_DATA) { }*/ - if (cacheItem!=-1) { - PivotCache pc= this.getWorkBook().getPivotCache(); // TODO should this be here or in Sxstream? - pc.addCacheItem(iCache, cacheItem); // adds the cache item to the "used" list, as it were - } - } - + if (cacheItem != -1) { + PivotCache pc = this.getWorkBook().getPivotCache(); // TODO should this be here or in Sxstream? + pc.addCacheItem(iCache, cacheItem); // adds the cache item to the "used" list, as it were + } + } + /** * sets the number of pivot items or lines on the ROW axis + * * @param strref */ public void addPivotLineToROWAxis(int repeat, int nLines, int type, short[] indexes) { @@ -576,568 +605,645 @@ public void addPivotLineToROWAxis(int repeat, int nLines, int type, short[] inde byte[] b = ByteTools.shortToLEBytes(cRw); System.arraycopy(b, 0, data, 32, 2); /** - If the value of either of the cRw or cCol fields of the associated SxView is greater than zero, - then two records of this type MUST exist in the file for the associated SxView. - The first record contains row area pivot lines and the second record contains column area pivot lines. */ - Sxli sxli= (Sxli) getSubRec(SXLI, 0); - if (sxli==null) { - int zz= getPivotRecordInsertionIndexes(SXLI, 0, -1); - if (zz>0) { // should!!! - sxli= (Sxli) Sxli.getPrototype(this.getWorkBook()); - sxli.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxli); - this.subRecs.add(zz,sxli); - sxli.addField(repeat, nLines, type, indexes); - sxli= (Sxli) Sxli.getPrototype(this.getWorkBook()); - sxli.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz+1).getRecordIndex(), sxli); - this.subRecs.add(zz+1,sxli); - } - } else - sxli.addField(repeat, nLines, type, indexes); - } - /** - * returns the number of pivot items or lines on the ROW axis - * @return - */ - public short getCRw(){return cRw;} - - protected boolean hasRowPivotItemsRecord() { - return (getSubRec(SXLI, 0)!=null); - } - - + If the value of either of the cRw or cCol fields of the associated SxView is greater than zero, + then two records of this type MUST exist in the file for the associated SxView. + The first record contains row area pivot lines and the second record contains column area pivot lines. */ + Sxli sxli = (Sxli) getSubRec(SXLI, 0); + if (sxli == null) { + int zz = getPivotRecordInsertionIndexes(SXLI, 0, -1); + if (zz > 0) { // should!!! + sxli = (Sxli) Sxli.getPrototype(this.getWorkBook()); + sxli.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxli); + this.subRecs.add(zz, sxli); + sxli.addField(repeat, nLines, type, indexes); + sxli = (Sxli) Sxli.getPrototype(this.getWorkBook()); + sxli.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(subRecs.get(zz + 1).getRecordIndex(), sxli); + this.subRecs.add(zz + 1, sxli); + } + } else + sxli.addField(repeat, nLines, type, indexes); + } + /** - * set the number of pivot items or lines on the COL axis + * returns the number of pivot items or lines on the ROW axis + * + * @return + */ + public short getCRw() { + return cRw; + } + + protected boolean hasRowPivotItemsRecord() { + return (getSubRec(SXLI, 0) != null); + } + + + /** + * set the number of pivot items or lines on the COL axis + * * @param strref */ public void addPivotLineToCOLAxis(int repeat, int nLines, int type, short[] indexes) { cCol++; byte[] b = ByteTools.shortToLEBytes(cCol); - System.arraycopy(b, 0, data, 34, 2); - Sxli sxli= (Sxli) getSubRec(SXLI, 1); - if (sxli==null) { - int zz= getPivotRecordInsertionIndexes(SXLI, 0, -1); - if (zz==-1) { // shouldn't! - sxli= (Sxli) Sxli.getPrototype(); - sxli.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxli); - this.subRecs.add(zz,sxli); - } - } - sxli.addField(repeat, nLines, type, indexes); - } - + System.arraycopy(b, 0, data, 34, 2); + Sxli sxli = (Sxli) getSubRec(SXLI, 1); + if (sxli == null) { + int zz = getPivotRecordInsertionIndexes(SXLI, 0, -1); + if (zz == -1) { // shouldn't! + sxli = (Sxli) Sxli.getPrototype(); + sxli.setSheet(this.getSheet()); + this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxli); + this.subRecs.add(zz, sxli); + } + } + sxli.addField(repeat, nLines, type, indexes); + } + /** - * returns the number of pivot items or lines on the COL axis + * returns the number of pivot items or lines on the COL axis + * * @return */ - public short getCCol(){return cCol;} + public short getCCol() { + return cCol; + } - /** - * iCache links this pivot table to a pivot data cache - * @param s - */ - public void setICache(short s){ + /** + * iCache links this pivot table to a pivot data cache + * + * @param s + */ + public void setICache(short s) { iCache = s; byte[] b = ByteTools.shortToLEBytes(iCache); - System.arraycopy(b, 0, data, 14, 2); + System.arraycopy(b, 0, data, 14, 2); } - - /** - * iCache links this pivot table to a pivot data cache - */ - public short getICache(){ + + /** + * iCache links this pivot table to a pivot data cache + */ + public short getICache() { return iCache; } - - - + + /** - Init the grbit, populate the member variables of the SXVIEW object. - This can be called both at initial init, and later to reup the boolean fields - after changes to the grbit. - */ - private void initGrbit(){ - if ((grbit1 & 0x1) == 0x1){fRwGrand = true;}else{fRwGrand=false;} - if ((grbit1 & 0x2) == 0x2){fColGrand = true;}else{fColGrand=false;} - if ((grbit1 & 0x8) == 0x8){fAutoFormat = true;}else{fAutoFormat=false;} - if ((grbit1 & 0x10) == 0x10){fWH = true;}else{fWH=false;} - if ((grbit1 & 0x20) == 0x20){fFont = true;}else{fFont=false;} - if ((grbit1 & 0x40) == 0x40){fAlign = true;}else{fAlign=false;} - if ((grbit1 & 0x80) == 0x80){fBorder = true;}else{fBorder=false;} - if ((grbit2 & 0x1) == 0x1){fPattern = true;}else{fPattern=false;} - if ((grbit2 & 0x2) == 0x2){fNumber = true;}else{fNumber=false;} - this.getData()[36] = grbit2; - this.getData()[37] = grbit1; + * Init the grbit, populate the member variables of the SXVIEW object. + * This can be called both at initial init, and later to reup the boolean fields + * after changes to the grbit. + */ + private void initGrbit() { + fRwGrand = (grbit1 & 0x1) == 0x1; + fColGrand = (grbit1 & 0x2) == 0x2; + fAutoFormat = (grbit1 & 0x8) == 0x8; + fWH = (grbit1 & 0x10) == 0x10; + fFont = (grbit1 & 0x20) == 0x20; + fAlign = (grbit1 & 0x40) == 0x40; + fBorder = (grbit1 & 0x80) == 0x80; + fPattern = (grbit2 & 0x1) == 0x1; + fNumber = (grbit2 & 0x2) == 0x2; + this.getData()[36] = grbit2; + this.getData()[37] = grbit1; } /** * store associated records for ease of lookup + * * @param r */ public void addSubrecord(BiffRec r) { - subRecs.add(r); + subRecs.add(r); } /* The following methods all change or get grbit fields */ - + /** - * specifies whether the PivotTable contains grand totals for rows. - * MUST be 0 if none of the axes have been assigned in this PivotTable view. + * specifies whether the PivotTable contains grand totals for rows. + * MUST be 0 if none of the axes have been assigned in this PivotTable view. */ - public void setFRwGrand(boolean b){ - if (b != fRwGrand){ - if (b == false){ - grbit1 = (byte)(grbit1 & 0xFE); - }else{ - grbit1 = (byte)(grbit1 | 0x1); - } - this.initGrbit(); + public void setFRwGrand(boolean b) { + if (b != fRwGrand) { + if (b == false) { + grbit1 = (byte) (grbit1 & 0xFE); + } else { + grbit1 = (byte) (grbit1 | 0x1); + } + this.initGrbit(); } - } - /** - * specifies whether the PivotTable contains grand totals for rows. - * MUST be 0 if none of the axes have been assigned in this PivotTable view. - */ - public boolean getFRwGrand(){return fRwGrand;} - - /** - * specifies whether the PivotTable contains grand totals for columns. - * MUST be 1 if none of the axes are assigned in this PivotTable view. - * @param b - */ - public void setColGrand(boolean b){ - if (b != fColGrand){ - if (b == false){ - grbit1 = (byte)(grbit1 & 0xFD); - }else{ - grbit1 = (byte)(grbit1 | 0x2); - } - this.initGrbit(); + } + + /** + * specifies whether the PivotTable contains grand totals for rows. + * MUST be 0 if none of the axes have been assigned in this PivotTable view. + */ + public boolean getFRwGrand() { + return fRwGrand; + } + + /** + * specifies whether the PivotTable contains grand totals for columns. + * MUST be 1 if none of the axes are assigned in this PivotTable view. + * + * @param b + */ + public void setColGrand(boolean b) { + if (b != fColGrand) { + if (b == false) { + grbit1 = (byte) (grbit1 & 0xFD); + } else { + grbit1 = (byte) (grbit1 | 0x2); + } + this.initGrbit(); } - } - /** -specifies whether the PivotTable contains grand totals for columns. MUST be 1 if none of the axes are assigned in this PivotTable view. */ - public boolean getFColGrand(){return fColGrand;} - - /** - * specifies whether the PivotTable has AutoFormat applied. - * @param b - */ - public void setFAutoFormat(boolean b){ - if (b != fAutoFormat){ - if (b == false){ - grbit1 = (byte)(grbit1 & 0xFB); - }else{ - grbit1 = (byte)(grbit1 | 0x4); - } - this.initGrbit(); + } + + /** + * specifies whether the PivotTable contains grand totals for columns. MUST be 1 if none of the axes are assigned in this PivotTable view. + */ + public boolean getFColGrand() { + return fColGrand; + } + + /** + * specifies whether the PivotTable has AutoFormat applied. + * + * @param b + */ + public void setFAutoFormat(boolean b) { + if (b != fAutoFormat) { + if (b == false) { + grbit1 = (byte) (grbit1 & 0xFB); + } else { + grbit1 = (byte) (grbit1 | 0x4); + } + this.initGrbit(); } - } - /** - * specifies whether the PivotTable has AutoFormat applied. - * @return - */ - public boolean getFAutoFormat(){return fAutoFormat;} - - - public void setFWH(boolean b){ - if (b != fWH){ - if (b == false){ - grbit1 = (byte)(grbit1 & 0xEF); - }else{ - grbit1 = (byte)(grbit1 | 0x10); - } - this.initGrbit(); + } + + /** + * specifies whether the PivotTable has AutoFormat applied. + * + * @return + */ + public boolean getFAutoFormat() { + return fAutoFormat; + } + + + public void setFWH(boolean b) { + if (b != fWH) { + if (b == false) { + grbit1 = (byte) (grbit1 & 0xEF); + } else { + grbit1 = (byte) (grbit1 | 0x10); + } + this.initGrbit(); } - } - public boolean getFWH(){return fWH;} - - /** - * specifies whether the PivotTable has font AutoFormat applied. - * @param b - */ - public void setFFont(boolean b){ - if (b != fFont){ - if (b == false){ - grbit1 = (byte)(grbit1 & 0xDF); - }else{ - grbit1 = (byte)(grbit1 | 0x20); - } - this.initGrbit(); + } + + public boolean getFWH() { + return fWH; + } + + /** + * specifies whether the PivotTable has font AutoFormat applied. + * + * @param b + */ + public void setFFont(boolean b) { + if (b != fFont) { + if (b == false) { + grbit1 = (byte) (grbit1 & 0xDF); + } else { + grbit1 = (byte) (grbit1 | 0x20); + } + this.initGrbit(); } - } - /** - * specifies whether the PivotTable has font AutoFormat applied. - * @return - */ - public boolean getFFont(){return fFont;} - - /** - * specifies whether the PivotTable has alignment AutoFormat applied. - * @param b - */ - public void setFAlign(boolean b){ - if (b != fAlign){ - if (b == false){ - grbit1 = (byte)(grbit1 & 0xBF); - }else{ - grbit1 = (byte)(grbit1 | 0x40); - } - this.initGrbit(); + } + + /** + * specifies whether the PivotTable has font AutoFormat applied. + * + * @return + */ + public boolean getFFont() { + return fFont; + } + + /** + * specifies whether the PivotTable has alignment AutoFormat applied. + * + * @param b + */ + public void setFAlign(boolean b) { + if (b != fAlign) { + if (b == false) { + grbit1 = (byte) (grbit1 & 0xBF); + } else { + grbit1 = (byte) (grbit1 | 0x40); + } + this.initGrbit(); } - } - /** - * specifies whether the PivotTable has alignment AutoFormat applied. - * @return - */ - public boolean getFAlign(){return fAlign;} - - /** - * specifies whether the PivotTable has border AutoFormat applied. - * @param b - */ - public void setFBorder(boolean b){ - if (b != fBorder){ - if (b == false){ - grbit1 = (byte)(grbit1 & 0x7F); - }else{ - grbit1 = (byte)(grbit1 | 0x80); - } - this.initGrbit(); + } + + /** + * specifies whether the PivotTable has alignment AutoFormat applied. + * + * @return + */ + public boolean getFAlign() { + return fAlign; + } + + /** + * specifies whether the PivotTable has border AutoFormat applied. + * + * @param b + */ + public void setFBorder(boolean b) { + if (b != fBorder) { + if (b == false) { + grbit1 = (byte) (grbit1 & 0x7F); + } else { + grbit1 = (byte) (grbit1 | 0x80); + } + this.initGrbit(); } - } + } + /** - * specifies whether the PivotTable has border AutoFormat applied. + * specifies whether the PivotTable has border AutoFormat applied. + * * @return */ - public boolean getFBorder(){return fBorder;} - - /** - * specifies whether the PivotTable has pattern AutoFormat applied. - * @param b - */ - public void setFPattern(boolean b){ - if (b != fPattern){ - if (b == false){ - grbit2 = (byte)(grbit2 & 0xFE); - }else{ - grbit2 = (byte)(grbit2 | 0x1); - } - this.initGrbit(); + public boolean getFBorder() { + return fBorder; + } + + /** + * specifies whether the PivotTable has pattern AutoFormat applied. + * + * @param b + */ + public void setFPattern(boolean b) { + if (b != fPattern) { + if (b == false) { + grbit2 = (byte) (grbit2 & 0xFE); + } else { + grbit2 = (byte) (grbit2 | 0x1); + } + this.initGrbit(); } - } - /** - * specifies whether the PivotTable has pattern AutoFormat applied. - * @return - */ - public boolean getFPattern(){return fPattern;} - - /** - * - * @param b - */ - public void setFNumber(boolean b){ - if (b != fNumber){ - if (b == false){ - grbit2 = (byte)(grbit2 & 0xFE); - }else{ - grbit2 = (byte)(grbit2 | 0x1); - } - this.initGrbit(); + } + + /** + * specifies whether the PivotTable has pattern AutoFormat applied. + * + * @return + */ + public boolean getFPattern() { + return fPattern; + } + + /** + * @param b + */ + public void setFNumber(boolean b) { + if (b != fNumber) { + if (b == false) { + grbit2 = (byte) (grbit2 & 0xFE); + } else { + grbit2 = (byte) (grbit2 | 0x1); + } + this.initGrbit(); } - } - public boolean getFNumber(){return fNumber;} - - - /** - * sets the location and size of this pivot table view - * @param range - */ - public void setLocation(String range) { - int[] rc= ExcelTools.getRangeRowCol(range); - setRwFirst((short)(rc[0])); - setColFirst((short)rc[1]); - setRwLast((short)(rc[2])); - setColLast((short)rc[3]); - } + } + + public boolean getFNumber() { + return fNumber; + } + + + /** + * sets the location and size of this pivot table view + * + * @param range + */ + public void setLocation(String range) { + int[] rc = ExcelTools.getRangeRowCol(range); + setRwFirst((short) (rc[0])); + setColFirst((short) rc[1]); + setRwLast((short) (rc[2])); + setColLast((short) rc[3]); + } /* Set and get methods, these all basically do the same thing, update the member variable, then update the body data with the new information. Simple, but large XLSRecord */ - /** - * sets the first row of the pivot table - */ - public void setRwFirst(short s){ - rwFirst = s; - byte[] b = ByteTools.shortToLEBytes(rwFirst); - System.arraycopy(b, 0, data, 0, 2); + + /** + * sets the first row of the pivot table + */ + public void setRwFirst(short s) { + rwFirst = s; + byte[] b = ByteTools.shortToLEBytes(rwFirst); + System.arraycopy(b, 0, data, 0, 2); } + /** * retrieves the first row of the pivot table */ - public short getRwFirst(){return rwFirst;} + public short getRwFirst() { + return rwFirst; + } /** * sets the last row of the pivot table */ - public void setRwLast(short s){ - rwLast = s; - byte[] b = ByteTools.shortToLEBytes(rwLast); - System.arraycopy(b, 0, data, 2, 2); + public void setRwLast(short s) { + rwLast = s; + byte[] b = ByteTools.shortToLEBytes(rwLast); + System.arraycopy(b, 0, data, 2, 2); } + /** * retieves the first row of the pivot table */ - public short getRwLast(){return rwLast;} - + public short getRwLast() { + return rwLast; + } + /** * sets the first column of the pivot table */ - public void setColFirst(short s){ - colFirst = s; - byte[] b = ByteTools.shortToLEBytes(colFirst); - System.arraycopy(b, 0, data, 4, 2); + public void setColFirst(short s) { + colFirst = s; + byte[] b = ByteTools.shortToLEBytes(colFirst); + System.arraycopy(b, 0, data, 4, 2); } + /** * retrieves the first column of the pivot table */ - public short getColFirst(){return colFirst;} - + public short getColFirst() { + return colFirst; + } + /** * sets the last column of the pivot table */ - public void setColLast(short s){ - colLast = s; - byte[] b = ByteTools.shortToLEBytes(colLast); - System.arraycopy(b, 0, data, 6, 2); + public void setColLast(short s) { + colLast = s; + byte[] b = ByteTools.shortToLEBytes(colLast); + System.arraycopy(b, 0, data, 6, 2); } + /** * sets the last column of the pivot table */ - public short getColLast(){return colLast;} - - public void setRwFirstHead(short s){ - rwFirstHead = s; - byte[] b = ByteTools.shortToLEBytes(rwFirstHead); - System.arraycopy(b, 0, data, 8, 2); - } - - public short getRwFirstHead(){ + public short getColLast() { + return colLast; + } + + public void setRwFirstHead(short s) { + rwFirstHead = s; + byte[] b = ByteTools.shortToLEBytes(rwFirstHead); + System.arraycopy(b, 0, data, 8, 2); + } + + public short getRwFirstHead() { return rwFirstHead; } - - public void setRwFirstData(short s){ + + public void setRwFirstData(short s) { rwFirstData = s; byte[] b = ByteTools.shortToLEBytes(rwFirstData); - System.arraycopy(b, 0, data, 10, 2); + System.arraycopy(b, 0, data, 10, 2); } - - public short getRwFirstData(){ + + public short getRwFirstData() { return rwFirstData; } - - public void setColFirstData(short s){ + + public void setColFirstData(short s) { colFirstData = s; byte[] b = ByteTools.shortToLEBytes(colFirstData); - System.arraycopy(b, 0, data, 12, 2); + System.arraycopy(b, 0, data, 12, 2); + } + + public short getColFirstData() { + return colFirstData; } - - public short getColFirstData(){return colFirstData;} - + /* Probably want to use the individual grbit options instead of this, but hey, I'm being thorough. */ - public void setGrbit(short s){ + public void setGrbit(short s) { byte[] b = ByteTools.shortToLEBytes(s); grbit2 = b[0]; grbit1 = b[1]; - System.arraycopy(b, 0, data, 36, 2); + System.arraycopy(b, 0, data, 36, 2); this.initGrbit(); } - - - public void setItblAutoFmt(short s){ + + + public void setItblAutoFmt(short s) { itblAutoFmt = s; byte[] b = ByteTools.shortToLEBytes(itblAutoFmt); - System.arraycopy(b, 0, data, 38, 2); + System.arraycopy(b, 0, data, 38, 2); + } + + public short getItblAutoFmt() { + return itblAutoFmt; } - public short getItblAutoFmt(){return itblAutoFmt;} - + /** - Sets the name of the Pivot Table. - */ - public void setTableName(String s){ + * Sets the name of the Pivot Table. + */ + public void setTableName(String s) { PivotTableName = s; this.buildRgch(); // also set associated qsitag pivot view name - QsiSXTag qsi= (QsiSXTag)getSubRec(QSISXTAG, -1); - if (qsi!=null) - qsi.setName(s); + QsiSXTag qsi = (QsiSXTag) getSubRec(QSISXTAG, -1); + if (qsi != null) + qsi.setName(s); // find SXADDL_SxView_SxDID record and set PivotTableView name - must match this view tablename - for (int i= subRecs.size()-1; i > 0; i--) { - BiffRec br= ((BiffRec)subRecs.get(i)); - if (br.getOpcode()==SXADDL) { - if (((SxAddl)br).getRecordId()==SxAddl.SxcView.sxdId) { - ((SxAddl)br).setViewName(s); - break; - } - } + for (int i = subRecs.size() - 1; i > 0; i--) { + BiffRec br = subRecs.get(i); + if (br.getOpcode() == SXADDL) { + if (((SxAddl) br).getRecordId() == SxAddl.SxcView.sxdId) { + ((SxAddl) br).setViewName(s); + break; + } + } } - + } - + /** - return the name of the Pivot Table. + * return the name of the Pivot Table. */ - public String getTableName(){return PivotTableName;} - + public String getTableName() { + return PivotTableName; + } + /** - Sets the name of the Data field - */ - public void setDataName(String s){ + * Sets the name of the Data field + */ + public void setDataName(String s) { DataFieldName = s; this.buildRgch(); } - public String getDataName(){return DataFieldName;} - + + public String getDataName() { + return DataFieldName; + } + /* Builds a new rgch field, changes the length of the entire record.... */ - private void buildRgch(){ - byte[] data= new byte[44]; - System.arraycopy(this.getData(), 0, data, 0, 44); + private void buildRgch() { + byte[] data = new byte[44]; + System.arraycopy(this.getData(), 0, data, 0, 44); byte[] strbytes = new byte[0]; byte[] databytes = new byte[0]; - try{ - if (PivotTableName!=null) - strbytes = PivotTableName.getBytes(DEFAULTENCODING); - if (DataFieldName!=null) - databytes = DataFieldName.getBytes(DEFAULTENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding pivot table name in SXVIEW: " + e);} - + try { + if (PivotTableName != null) + strbytes = PivotTableName.getBytes(DEFAULTENCODING); + if (DataFieldName != null) + databytes = DataFieldName.getBytes(DEFAULTENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding pivot table name in SXVIEW: " + e); + } + //update the lengths: - cchName = (short)strbytes.length; + cchName = (short) strbytes.length; byte[] nm = ByteTools.shortToLEBytes(cchName); data[40] = nm[0]; data[41] = nm[1]; - + cchData = (short) databytes.length; nm = ByteTools.shortToLEBytes(cchData); data[42] = nm[0]; data[43] = nm[1]; - + // now append variable-length string data - byte[] newrgch = new byte[cchName + cchData + 2]; // account for encoding bytes + byte[] newrgch = new byte[cchName + cchData + 2]; // account for encoding bytes System.arraycopy(strbytes, 0, newrgch, 1, cchName); - System.arraycopy(databytes, 0, newrgch, cchName+2, cchData); + System.arraycopy(databytes, 0, newrgch, cchName + 2, cchData); - data= ByteTools.append(newrgch, data); + data = ByteTools.append(newrgch, data); this.setData(data); } - + /** - * utility method to retrieve the nth pivot table subrecord (SxVd, SxPI, SxDI ...) - * @param opcode opcode to look up - * @param index if > -1, the index or occurrence of the record to return + * utility method to retrieve the nth pivot table subrecord (SxVd, SxPI, SxDI ...) + * + * @param opcode opcode to look up + * @param index if > -1, the index or occurrence of the record to return * @return */ private BiffRec getSubRec(int opcode, int index) { - int j= 0; - for (int i= 0; i < subRecs.size(); i++) { - BiffRec br= subRecs.get(i); - if (br.getOpcode()==opcode) { - if (index==-1 || (j++==index)) - return br; - } - } - return null; + int j = 0; + for (int i = 0; i < subRecs.size(); i++) { + BiffRec br = subRecs.get(i); + if (br.getOpcode() == opcode) { + if (index == -1 || (j++ == index)) + return br; + } + } + return null; } - + /** * utility method to retrieve the subrecord index of the desired pivot table record - * @param br pivot table record to look up + * + * @param br pivot table record to look up * @return */ private int getSubRecIndex(BiffRec br) { - int i= 0; - for (; i < subRecs.size(); i++) { - if (br==subRecs.get(i)) - break; - } - return i; + int i = 0; + for (; i < subRecs.size(); i++) { + if (br == subRecs.get(i)) + break; + } + return i; } - + /** - * utility method to retrieve a pivot table subrecord (SxVd, SxPI, SxDI ...) index - * @param opcode opcode to look up - * @param index if > -1, the index or occurrence of the record to return + * utility method to retrieve a pivot table subrecord (SxVd, SxPI, SxDI ...) index + * + * @param opcode opcode to look up + * @param index if > -1, the index or occurrence of the record to return * @return index in subrecords of desired record */ private int getSubRecIndex(int opcode, int index) { - int j= 0; - for (int i= 0; i < subRecs.size(); i++) { - BiffRec br= subRecs.get(i); - if (br.getOpcode()==opcode) { - if (index==-1 || (j++==index)) - return i; - } - } - return -1; + int j = 0; + for (int i = 0; i < subRecs.size(); i++) { + BiffRec br = subRecs.get(i); + if (br.getOpcode() == opcode) { + if (index == -1 || (j++ == index)) + return i; + } + } + return -1; } - /**i + /** + * i * finds the proper sheet rec insertion index for the desired opcode - * as well as the proper insertion index into the subrecord array + * as well as the proper insertion index into the subrecord array *
        this method contains alot of intelligence regarding the order of pivot table records - * @param opcode one of SXVD, SXIVD, SXPI, SXDI, SXLI, or SXVDEX - * @param index either 0 (for ROW records) or 1 (for COL records) - * - * @return int[] {sheet record index, subrecord index} or -1's + * + * @param opcode one of SXVD, SXIVD, SXPI, SXDI, SXLI, or SXVDEX + * @param index either 0 (for ROW records) or 1 (for COL records) + * @return int[] {sheet record index, subrecord index} or -1's */ private int getPivotRecordInsertionIndexes(int opcode, int index, int pivotFieldIndex) { - int i, j= 0; + int i, j = 0; if (pivotFieldIndex < 0) { - for (i= subRecs.size()-1; i >= 0; i--) { - BiffRec br= subRecs.get(i); - int bropcode= br.getOpcode(); - if (bropcode==opcode) { // - if (j < index) { - j++; - continue; // haven't found correct record jet - } - break; - } else if (opcode==SXLI && (bropcode==SXDI || bropcode==SXPI || bropcode==SXIVD || bropcode==SXVDEX)) { - break; - } else if (opcode==SXDI && (bropcode==SXPI || bropcode==SXIVD || bropcode==SXVDEX)) { - break; - } else if (opcode==SXPI && (bropcode==SXIVD || bropcode==SXVDEX)) { - break; - } else if (opcode==SXIVD && (bropcode==SXVDEX)) { - break; - } else if (opcode==SXVD && (bropcode==SXEX)) { - break; - } - } - i++; // counter reverse order - } else { // only lookup within the current pivot field - for (i= pivotFieldIndex+1; i < subRecs.size(); i++) { - BiffRec br= subRecs.get(i); - int bropcode= br.getOpcode(); - if (bropcode==opcode) { // - if (j < index) { - j++; - continue; // haven't found correct record jet - } - break; - } else if (bropcode==SXVDEX) { - break; - } - } + for (i = subRecs.size() - 1; i >= 0; i--) { + BiffRec br = subRecs.get(i); + int bropcode = br.getOpcode(); + if (bropcode == opcode) { // + if (j < index) { + j++; + continue; // haven't found correct record jet + } + break; + } else if (opcode == SXLI && (bropcode == SXDI || bropcode == SXPI || bropcode == SXIVD || bropcode == SXVDEX)) { + break; + } else if (opcode == SXDI && (bropcode == SXPI || bropcode == SXIVD || bropcode == SXVDEX)) { + break; + } else if (opcode == SXPI && (bropcode == SXIVD || bropcode == SXVDEX)) { + break; + } else if (opcode == SXIVD && (bropcode == SXVDEX)) { + break; + } else if (opcode == SXVD && (bropcode == SXEX)) { + break; + } + } + i++; // counter reverse order + } else { // only lookup within the current pivot field + for (i = pivotFieldIndex + 1; i < subRecs.size(); i++) { + BiffRec br = subRecs.get(i); + int bropcode = br.getOpcode(); + if (bropcode == opcode) { // + if (j < index) { + j++; + continue; // haven't found correct record jet + } + break; + } else if (bropcode == SXVDEX) { + break; + } + } } return i; /* if (i >= 0) { @@ -1145,78 +1251,80 @@ private int getPivotRecordInsertionIndexes(int opcode, int index, int pivotField } return new int[] {-1, i};*/ } - - - /** - * creates the basic, default records necessary to define a pivot table - * @param sheet string sheetname where pivot table is located - * @return arraylist of records - */ + + + /** + * creates the basic, default records necessary to define a pivot table + * + * @param sheet string sheetname where pivot table is located + * @return arraylist of records + */ public ArrayList addInitialRecords(Boundsheet sheet) { - /* - * basic blank pivot table: - * SXVIEW - * after SXVIEW pivot field and item records will go: - * for each pivot field: SxVd, SxVi* n items, SxVDEx - * SXIVD if cDimRw > 0 - * SXIVD if cDimCol > 0 - * SXPI if cDimPg > 0 - * SXDI if cDimData > 0 - * SXLI if cRw > 0 - * SXLI if cCol > 0 - * SXEX - * QXISTAG - * SXADDL records - * - */ - ArrayList initialrecs= new ArrayList(); - this.setSheet(sheet); - this.setWorkBook(sheet.getWorkBook()); - // SXEX - SxEX sxex= (SxEX) SxEX.getPrototype(); - addInit(initialrecs, sxex, sheet); - // QSISXTAG - QsiSXTag qsi= (QsiSXTag) QsiSXTag.getPrototype(); - addInit(initialrecs, qsi, sheet); - SxVIEWEX9 sxv= (SxVIEWEX9) SxVIEWEX9.getPrototype(); - addInit(initialrecs, sxv, sheet); - // SXADDLs -required SxAddl records: stores additional PivotTableView info of a variety of types + /* + * basic blank pivot table: + * SXVIEW + * after SXVIEW pivot field and item records will go: + * for each pivot field: SxVd, SxVi* n items, SxVDEx + * SXIVD if cDimRw > 0 + * SXIVD if cDimCol > 0 + * SXPI if cDimPg > 0 + * SXDI if cDimData > 0 + * SXLI if cRw > 0 + * SXLI if cCol > 0 + * SXEX + * QXISTAG + * SXADDL records + * + */ + ArrayList initialrecs = new ArrayList(); + this.setSheet(sheet); + this.setWorkBook(sheet.getWorkBook()); + // SXEX + SxEX sxex = (SxEX) SxEX.getPrototype(); + addInit(initialrecs, sxex, sheet); + // QSISXTAG + QsiSXTag qsi = (QsiSXTag) QsiSXTag.getPrototype(); + addInit(initialrecs, qsi, sheet); + SxVIEWEX9 sxv = (SxVIEWEX9) SxVIEWEX9.getPrototype(); + addInit(initialrecs, sxv, sheet); + // SXADDLs -required SxAddl records: stores additional PivotTableView info of a variety of types // byte[] b= ByteTools.cLongToLEBytes(sid); b= ByteTosols.append(new byte[] {0, 0}, b); // add 2 reserved bytes - SxAddl sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdId.sxd(), null); - addInit(initialrecs, sa, sheet); - //SXADDL_sxcView: record=sxdVer10Info data:[1, 65, 0, 0, 0, 0] - sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVer10Info.sxd(), null); - addInit(initialrecs, sa, sheet); - sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdTableStyleClient.sxd(), new byte[] {0, 0, 0, 0, 0, 0, 51, 0, 0, 0}); - addInit(initialrecs, sa, sheet); - sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVerUpdInv.sxd(), new byte[] {2, 0, 0, 0, 0, 0}); - addInit(initialrecs, sa, sheet); - sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVerUpdInv.sxd(), new byte[] {-1, 0, 0, 0, 0, 0}); - addInit(initialrecs, sa, sheet); - sa= SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdEnd.sxd(), null); - addInit(initialrecs, sa, sheet); - return initialrecs; + SxAddl sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdId.sxd(), null); + addInit(initialrecs, sa, sheet); + //SXADDL_sxcView: record=sxdVer10Info data:[1, 65, 0, 0, 0, 0] + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVer10Info.sxd(), null); + addInit(initialrecs, sa, sheet); + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdTableStyleClient.sxd(), new byte[]{0, 0, 0, 0, 0, 0, 51, 0, 0, 0}); + addInit(initialrecs, sa, sheet); + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVerUpdInv.sxd(), new byte[]{2, 0, 0, 0, 0, 0}); + addInit(initialrecs, sa, sheet); + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVerUpdInv.sxd(), new byte[]{-1, 0, 0, 0, 0, 0}); + addInit(initialrecs, sa, sheet); + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdEnd.sxd(), null); + addInit(initialrecs, sa, sheet); + return initialrecs; } /** - * utility function to properly add a Pivot Table View subrec + * utility function to properly add a Pivot Table View subrec + * * @param initialrecs * @param rec * @param addToInitRecords * @param sheet */ private void addInit(ArrayList initialrecs, XLSRecord rec, Boundsheet sheet) { - initialrecs.add(rec); - rec.setSheet(sheet); - rec.setWorkBook(this.getWorkBook()); - this.addSubrecord(rec); + initialrecs.add(rec); + rec.setSheet(sheet); + rec.setWorkBook(this.getWorkBook()); + this.addSubrecord(rec); } public String toString() { - return "SXVIEW: name:" + this.getTableName() + " iCache:" + iCache + " cDim:" + cDim + " cDimRw:" + cDimRw + " cDimCol:" + cDimCol + - " cDimPg:" + cDimPg + " cDimData:" + cDimData + - " cRw:" + cRw + " cCol:" + cCol + - " datafieldname:" + DataFieldName; + return "SXVIEW: name:" + this.getTableName() + " iCache:" + iCache + " cDim:" + cDim + " cDimRw:" + cDimRw + " cDimCol:" + cDimCol + + " cDimPg:" + cDimPg + " cDimData:" + cDimData + + " cRw:" + cRw + " cCol:" + cCol + + " datafieldname:" + DataFieldName; } } diff --git a/src/main/java/io/starter/formats/XLS/TabID.java b/src/main/java/io/starter/formats/XLS/TabID.java index 82d5b5b..8bbb89a 100644 --- a/src/main/java/io/starter/formats/XLS/TabID.java +++ b/src/main/java/io/starter/formats/XLS/TabID.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,102 +27,104 @@ import io.starter.toolkit.Logger; -/** RRTabID: Revision Tab ID Record
        - - The RRTabId record specifies an array of unique sheet identifiers, - each of which is associated with a sheet in the workbook. - The order of the sheet identifiers in the array matches the order of - the BoundSheet8 records as they appear in the Globals substream. -*/ -public final class TabID extends io.starter.formats.XLS.XLSRecord -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 722748113519841817L; - CompatibleVector tabIDs = new CompatibleVector(); - +/** + * RRTabID: Revision Tab ID Record
        + *

        + * The RRTabId record specifies an array of unique sheet identifiers, + * each of which is associated with a sheet in the workbook. + * The order of the sheet identifiers in the array matches the order of + * the BoundSheet8 records as they appear in the Globals substream. + */ +public final class TabID extends io.starter.formats.XLS.XLSRecord { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 722748113519841817L; + CompatibleVector tabIDs = new CompatibleVector(); + /** * Default init */ - public void init(){ + public void init() { super.init(); - for (int i=0;i largest) largest = newshort; - } - tabIDs.remove(Short.valueOf(largest)); - this.updateRecord(); - } - - /** Looks sequentally at the tabIDs and - makes a new one larger than the previous largest... - */ - void addNewRecord(){ + + /** + * Looks sequentally at the tabIDs and + * makes a new one larger than the previous largest... + */ + void removeRecord() { short largest = 0; - for (int i=0;i largest) largest = newshort; + } + tabIDs.remove(Short.valueOf(largest)); + this.updateRecord(); + } + + + /** + * Looks sequentally at the tabIDs and + * makes a new one larger than the previous largest... + */ + void addNewRecord() { + short largest = 0; + for (int i = 0; i < tabIDs.size(); i++) { Short sh = (Short) tabIDs.get(i); short newshort = sh.shortValue(); if (newshort > largest) largest = newshort; } largest += 0x1; Short sh = Short.valueOf(largest); - tabIDs.add(sh); + tabIDs.add(sh); this.updateRecord(); - } - - - /** - This DOES NOT do what was expected. Sheet order is soley based off of physical Boundsheet ordering - in the output file. I'm keeping this code in here in case we start supporting revisions. - */ - private boolean changeOrder(int sheet, int newpos){ + } + + + /** + * This DOES NOT do what was expected. Sheet order is soley based off of physical Boundsheet ordering + * in the output file. I'm keeping this code in here in case we start supporting revisions. + */ + private boolean changeOrder(int sheet, int newpos) { int sz = tabIDs.size(); - if(((sheet < 0)||(newpos < 0))||((sheet >= sz)||(newpos >= sz))){ + if (((sheet < 0) || (newpos < 0)) || ((sheet >= sz) || (newpos >= sz))) { Logger.logWarn("changing Sheet order failed: invalid Sheet Index: " + sheet + ":" + newpos); return false; } Object b = tabIDs.get(sheet); tabIDs.remove(b); - tabIDs.insertElementAt(b,newpos); + tabIDs.insertElementAt(b, newpos); this.updateRecord(); return true; } - + /** * Updates the underlying byte array with the ordered tabId's * Call after any modification to this record */ - public void updateRecord(){ - short newlen = (short) (tabIDs.size()*2); + public void updateRecord() { + short newlen = (short) (tabIDs.size() * 2); byte[] newbody = new byte[newlen]; int counter = 0; - for (int i=0;i. @@ -22,72 +22,76 @@ */ package io.starter.formats.XLS; -import java.io.UnsupportedEncodingException; - import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; + /** * TableStyles 0x88E - * - * The TableStyles record specifies the default table and PivotTabletable styles and - * specifies the beginning of a collection of TableStyle records as defined by the Globals substream. + *

        + * The TableStyles record specifies the default table and PivotTabletable styles and + * specifies the beginning of a collection of TableStyle records as defined by the Globals substream. * The collection of TableStyle records specifies user-defined table styles. - * - frtHeader (12 bytes): An FrtHeader structure. The frtHeader.rt field MUST be 0x088E. - - cts (4 bytes): An unsigned integer that specifies the total number of table styles in this document. This is the sum of the standard built-in table styles and all of the custom table styles. This value MUST be greater than or equal to 144 (the number of built-in table styles). - - cchDefTableStyle (2 bytes): An unsigned integer that specifies the count of characters in the rgchDefTableStyle field. This value MUST be less than or equal to 255. - - cchDefPivotStyle (2 bytes): An unsigned integer that specifies the count of characters in the rgchDefPivotStyle field. This value MUST be less than or equal to 255. - - rgchDefTableStyle (variable): An array of Unicode characters whose length is specified by cchDefTableStyle that specifies the name of the default table style. - - rgchDefPivotStyle (variable): An array of Unicode characters whose length is specified by cchDefPivotStyle that specifies the name of the default PivotTable style. - - * + *

        + * frtHeader (12 bytes): An FrtHeader structure. The frtHeader.rt field MUST be 0x088E. + *

        + * cts (4 bytes): An unsigned integer that specifies the total number of table styles in this document. This is the sum of the standard built-in table styles and all of the custom table styles. This value MUST be greater than or equal to 144 (the number of built-in table styles). + *

        + * cchDefTableStyle (2 bytes): An unsigned integer that specifies the count of characters in the rgchDefTableStyle field. This value MUST be less than or equal to 255. + *

        + * cchDefPivotStyle (2 bytes): An unsigned integer that specifies the count of characters in the rgchDefPivotStyle field. This value MUST be less than or equal to 255. + *

        + * rgchDefTableStyle (variable): An array of Unicode characters whose length is specified by cchDefTableStyle that specifies the name of the default table style. + *

        + * rgchDefPivotStyle (variable): An array of Unicode characters whose length is specified by cchDefPivotStyle that specifies the name of the default PivotTable style. */ public class TableStyles extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - short cts, cchDefTableStyle, cchDefPivotStyle; - String rgchDefTableStyle= null, rgchDefPivotStyle= null; - private static final long serialVersionUID = 2639291289806138985L; - public void init(){ - super.init(); + /** + * serialVersionUID + */ + short cts, cchDefTableStyle, cchDefPivotStyle; + String rgchDefTableStyle = null, rgchDefPivotStyle = null; + private static final long serialVersionUID = 2639291289806138985L; + + public void init() { + super.init(); // -114, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // old frtHeader //-112, 0, 0, 0, == 144 // An unsigned integer that specifies the total number of table styles in this document. This is the sum of the standard built-in table styles and all of the custom table styles. This value MUST be greater than or equal to 144 (the number of built-in table styles). - cts= (short)ByteTools.readInt(this.getByteAt(12),this.getByteAt(13), this.getByteAt(14),this.getByteAt(14)); - cchDefTableStyle= ByteTools.readShort(this.getByteAt(16),this.getByteAt(17)); - cchDefPivotStyle= ByteTools.readShort(this.getByteAt(18),this.getByteAt(19)); - int pos= 20; + cts = (short) ByteTools.readInt(this.getByteAt(12), this.getByteAt(13), this.getByteAt(14), this.getByteAt(14)); + cchDefTableStyle = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); + cchDefPivotStyle = ByteTools.readShort(this.getByteAt(18), this.getByteAt(19)); + int pos = 20; if (cchDefTableStyle > 0) { - byte[] tmp = this.getBytesAt(pos, (cchDefTableStyle) * (2)); - try{ - rgchDefTableStyle = new String(tmp, UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding Table Style name in TableStyles: " + e);} - pos+=cchDefTableStyle * (2); + byte[] tmp = this.getBytesAt(pos, (cchDefTableStyle) * (2)); + try { + rgchDefTableStyle = new String(tmp, UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding Table Style name in TableStyles: " + e); + } + pos += cchDefTableStyle * (2); } if (cchDefPivotStyle > 0) { - byte[] tmp = this.getBytesAt(pos, (cchDefPivotStyle) * (2)); - try{ - rgchDefPivotStyle = new String(tmp, UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("encoding Pivot Style name in TableStyles: " + e);} - } - } - public static XLSRecord getPrototype() { - TableStyles tx= new TableStyles(); - tx.setOpcode(TABLESTYLES); - tx.setData(new byte[] {-114, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* required id */ - -112, 0, 0, 0, /* cts */ - 17, 0, /* cch */ - 17, 0, /* cch */ - 84, 0, 97, 0, 98, 0, 108, 0, 101, 0, 83, 0, 116, 0, 121, 0, 108, 0, 101, 0, 77, 0, 101, 0, 100, 0, 105, 0, 117, 0, 109, 0, 57, 0, 80, 0, 105, 0, 118, 0, 111, 0, 116, 0, 83, 0, 116, 0, 121, 0, 108, 0, 101, 0, 76, 0, 105, 0, 103, 0, 104, 0, 116, 0, 49, 0, 54, 0 - }); - tx.init(); - return tx; - } + byte[] tmp = this.getBytesAt(pos, (cchDefPivotStyle) * (2)); + try { + rgchDefPivotStyle = new String(tmp, UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("encoding Pivot Style name in TableStyles: " + e); + } + } + } + + public static XLSRecord getPrototype() { + TableStyles tx = new TableStyles(); + tx.setOpcode(TABLESTYLES); + tx.setData(new byte[]{-114, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* required id */ + -112, 0, 0, 0, /* cts */ + 17, 0, /* cch */ + 17, 0, /* cch */ + 84, 0, 97, 0, 98, 0, 108, 0, 101, 0, 83, 0, 116, 0, 121, 0, 108, 0, 101, 0, 77, 0, 101, 0, 100, 0, 105, 0, 117, 0, 109, 0, 57, 0, 80, 0, 105, 0, 118, 0, 111, 0, 116, 0, 83, 0, 116, 0, 121, 0, 108, 0, 101, 0, 76, 0, 105, 0, 103, 0, 104, 0, 116, 0, 49, 0, 54, 0 + }); + tx.init(); + return tx; + } } diff --git a/src/main/java/io/starter/formats/XLS/TopMargin.java b/src/main/java/io/starter/formats/XLS/TopMargin.java index d79785b..633bf4b 100644 --- a/src/main/java/io/starter/formats/XLS/TopMargin.java +++ b/src/main/java/io/starter/formats/XLS/TopMargin.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,36 +24,38 @@ import io.starter.toolkit.ByteTools; -/** Record specifying the top margin of the sheet for printing. +/** + * Record specifying the top margin of the sheet for printing. */ public class TopMargin -extends XLSRecord { - private static final long serialVersionUID = -3649192673573344145L; - - double margin; - - public void init() { - super.init(); - - margin = ByteTools.eightBytetoLEDouble( getBytesAt(0, 8) ); - } - - public TopMargin() { - this.setOpcode(TOPMARGIN); - margin = 1.0; // default - setData( ByteTools.doubleToLEByteArray( margin )); - } - public void setSheet (Sheet sheet ) { - super.setSheet( sheet ); - ((Boundsheet)sheet).addPrintRec( this ); - } - - public double getMargin() { - return margin; - } - - public void setMargin (double value) { - margin = value; - setData( ByteTools.doubleToLEByteArray( value )); - } + extends XLSRecord { + private static final long serialVersionUID = -3649192673573344145L; + + double margin; + + public void init() { + super.init(); + + margin = ByteTools.eightBytetoLEDouble(getBytesAt(0, 8)); + } + + public TopMargin() { + this.setOpcode(TOPMARGIN); + margin = 1.0; // default + setData(ByteTools.doubleToLEByteArray(margin)); + } + + public void setSheet(Sheet sheet) { + super.setSheet(sheet); + ((Boundsheet) sheet).addPrintRec(this); + } + + public double getMargin() { + return margin; + } + + public void setMargin(double value) { + margin = value; + setData(ByteTools.doubleToLEByteArray(value)); + } } diff --git a/src/main/java/io/starter/formats/XLS/Txo.java b/src/main/java/io/starter/formats/XLS/Txo.java index e358190..21a0842 100644 --- a/src/main/java/io/starter/formats/XLS/Txo.java +++ b/src/main/java/io/starter/formats/XLS/Txo.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,345 +22,351 @@ */ package io.starter.formats.XLS; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; - import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; -/** Txo: Text Object (1B6h)
        - This record stores a text object. This record is followed - by two CONTINUE records which contain the text data and the - formatting runs respectively. - - If there is no text, the two CONTINUE records are absent. - -

        -    offset  name        size    contents
        -    ---    
        -    4       grbit       2       Option flags.  See table.
        -    6       rot         2       Orientation of text within the object.
        -    8       Reserved    6       Must be zero.
        -    14      cchText     2       Length of text in first CONTINUE rec.
        -    16      cbRuns      2       Length of formatting runs in second CONTINUE rec.
        -    18      Reserved    4       Must be zero.
        -    
        -    
        -   The grbit field contains the following option flags:
        -   
        -    bits    mask    name                contents
        -    ----
        -    0       0x01    Reserved            
        -    3-1     0x0e    alcH                Horizontal text alignment
        -                                        1 = left
        -                                        2 = centered
        -                                        3 = right
        -                                        4 = justified
        -    6-4     0x70    alcV                Vertical text alignment
        -                                        1 = top
        -                                        2 = center
        -                                        3 = bottom
        -                                        4 = justified
        -    8-7     0x180   Reserved            
        -    9       0x200   fLockText           1 = lock text option is on
        -    15-10   0xfc00  Reserved            
        -    
        -    
        -    The first CONTINUE record contains text -- the length is cchText of this object.
        -    The first byte of the CONTINUE record's body data is 0x0 = compressed unicode.  The rest is text.
        -    
        -    

        +/** + * Txo: Text Object (1B6h)
        + * This record stores a text object. This record is followed + * by two CONTINUE records which contain the text data and the + * formatting runs respectively. + *

        + * If there is no text, the two CONTINUE records are absent. + * + *

        + * offset  name        size    contents
        + * ---
        + * 4       grbit       2       Option flags.  See table.
        + * 6       rot         2       Orientation of text within the object.
        + * 8       Reserved    6       Must be zero.
        + * 14      cchText     2       Length of text in first CONTINUE rec.
        + * 16      cbRuns      2       Length of formatting runs in second CONTINUE rec.
        + * 18      Reserved    4       Must be zero.
        + *
        + *
        + * The grbit field contains the following option flags:
        + *
        + * bits    mask    name                contents
        + * ----
        + * 0       0x01    Reserved
        + * 3-1     0x0e    alcH                Horizontal text alignment
        + * 1 = left
        + * 2 = centered
        + * 3 = right
        + * 4 = justified
        + * 6-4     0x70    alcV                Vertical text alignment
        + * 1 = top
        + * 2 = center
        + * 3 = bottom
        + * 4 = justified
        + * 8-7     0x180   Reserved
        + * 9       0x200   fLockText           1 = lock text option is on
        + * 15-10   0xfc00  Reserved
        + *
        + *
        + * The first CONTINUE record contains text -- the length is cchText of this object.
        + * The first byte of the CONTINUE record's body data is 0x0 = compressed unicode.  The rest is text.
        + *
        + * 

        + * * @see LABELSST * @see STRING * @see CONTINUE -*/ + */ public final class Txo extends io.starter.formats.XLS.XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7043468034346138525L; - Continue text, formattingruns; //20100430 KSC: garbagetxo is really a masked mso, garbagetxo; // garbagetxo is a third continue that appears to be cropping up in infoteria files. We are removing from the file stream currently, but may need to integrate - int state = 0; - short grbit = 0; - short cchText = 0; - short cbRuns = 0; - short rot = 0; + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7043468034346138525L; + Continue text, formattingruns; //20100430 KSC: garbagetxo is really a masked mso, garbagetxo; // garbagetxo is a third continue that appears to be cropping up in infoteria files. We are removing from the file stream currently, but may need to integrate + int state = 0; + short grbit = 0; + short cchText = 0; + short cbRuns = 0; + short rot = 0; boolean compressedUnicode = false; - - public void init() { - super.init(); - int datalen = this.getLength(); // should be 18 - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - rot = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - cchText = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - cbRuns = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + public void init() { + super.init(); + int datalen = this.getLength(); // should be 18 - this.setIsValueForCell(false); - // -- not always true... does it bother anybody though? - this.isString = true; - this.isContinueMerged = true; - } - - - /** returns the String value of this Text Object - */ - public String getStringVal(){ + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + rot = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + cchText = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); + cbRuns = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + + this.setIsValueForCell(false); + // -- not always true... does it bother anybody though? + this.isString = true; + this.isContinueMerged = true; + } + + + /** + * returns the String value of this Text Object + */ + public String getStringVal() { String s = ""; - if(text==null)return null; + if (text == null) return null; byte[] barr; - int encoding= text.getData()[0]; - barr = new byte[text.getData().length - 1]; - System.arraycopy(text.getData(), 1, barr, 0, barr.length); - try{ - if (encoding==0) {// normal case (Default encoding) - s = new String(barr, WorkBookFactory.DEFAULTENCODING); + int encoding = text.getData()[0]; + barr = new byte[text.getData().length - 1]; + System.arraycopy(text.getData(), 1, barr, 0, barr.length); + try { + if (encoding == 0) {// normal case (Default encoding) + s = new String(barr, WorkBookFactory.DEFAULTENCODING); } else // encoding=1 (are there other encoding options?? s = new String(barr, WorkBookFactory.UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("reading Text Object failed: " + e.toString());} + } catch (UnsupportedEncodingException e) { + Logger.logInfo("reading Text Object failed: " + e.toString()); + } return s; } - - /** sets the String value of this Text Object + + /** + * sets the String value of this Text Object *
        if present, will parse embedded formats within the string as: - *
        the format of the embedded information is as follows: - *
        <font specifics>text segment<font specifics for next segment>text segment... - *
        where font specifics can be one or more of: - *
          b if present, bold - *
          i if present, italic - *
          s if present, strikethru - *
          u if present, underlined - *
          f="" font name e.g. "Arial" - *
          sz="" font size in points e.g. "10" - *
          delimited by ;'s - *
          For Example: - *
          <f="Tahoma";b;sz="16">Note: <f="Tahoma";sz="12">This is an important point - * - * @throws IllegalArgumentException if String is incorrect format - */ + *
          the format of the embedded information is as follows: + *
          <font specifics>text segment<font specifics for next segment>text segment... + *
          where font specifics can be one or more of: + *
            b if present, bold + *
            i if present, italic + *
            s if present, strikethru + *
            u if present, underlined + *
            f="" font name e.g. "Arial" + *
            sz="" font size in points e.g. "10" + *
            delimited by ;'s + *
            For Example: + *
            <f="Tahoma";b;sz="16">Note: <f="Tahoma";sz="12">This is an important point + * + * @throws IllegalArgumentException if String is incorrect format + */ // TODO: if length of string is > 8218, must have another continues ****************** - public void setStringVal(String v) throws IllegalArgumentException{ - if (v!=null && v.indexOf('<') >= 0) - v= parseFormatting(v); // extracts text string from formats and sets formatting runs - else // no formatting present: - this.setFormattingRuns(null); // reset formatting runs + public void setStringVal(String v) throws IllegalArgumentException { + if (v != null && v.indexOf('<') >= 0) + v = parseFormatting(v); // extracts text string from formats and sets formatting runs + else // no formatting present: + this.setFormattingRuns(null); // reset formatting runs // get the length of the first CONTINUE byte[] a = v.getBytes(); - byte[] b = new byte[a.length+1]; + byte[] b = new byte[a.length + 1]; System.arraycopy(a, 0, b, 1, a.length); - if(DEBUGLEVEL > 1)Logger.logInfo("Txo CHANGING: " + this.getStringVal()); + if (DEBUGLEVEL > 1) Logger.logInfo("Txo CHANGING: " + this.getStringVal()); // TODO: checked for Compressed UNICODE in text CONTINUE b[0] = 0x0; - if (text!=null) + if (text != null) text.setData(b); - else // create new text-type continues - though should be already set (see getPrototype) - text= Continue.getTextContinues(v); - b= ByteTools.shortToLEBytes((short)a.length); - this.getData()[10]= b[0]; - this.getData()[11]= b[1]; - cchText = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); // reset text length - if(DEBUGLEVEL > 1)Logger.logInfo(" TO: " + this.getStringVal()); + else // create new text-type continues - though should be already set (see getPrototype) + text = Continue.getTextContinues(v); + b = ByteTools.shortToLEBytes((short) a.length); + this.getData()[10] = b[0]; + this.getData()[11] = b[1]; + cchText = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); // reset text length + if (DEBUGLEVEL > 1) Logger.logInfo(" TO: " + this.getStringVal()); } - - + + /** - * String s contains formatting information; extracts text string from + * String s contains formatting information; extracts text string from * formats and sets formatting runs - *
            the format of the embedded information is as follows: - *
            < font specifics>text segment< font specifics for next segment>text segment... - *
            where font specifics can be one or more of: - *
              b if present, bold - *
              i if present, italic - *
              s if present, strikethru - *
              u if present, underlined - *
              f="" font name e.g. "Arial" - *
              sz="" font size in points e.g. "10" - *
              delimited by ;'s - *
              For Example: - *
              < f="Tahoma";b;sz="16">Note: < f="Tahoma";sz="12">This is an impotant point - * @param s String with formatting info - * @return txt- string with all formatting info removed + *
              the format of the embedded information is as follows: + *
              < font specifics>text segment< font specifics for next segment>text segment... + *
              where font specifics can be one or more of: + *
                b if present, bold + *
                i if present, italic + *
                s if present, strikethru + *
                u if present, underlined + *
                f="" font name e.g. "Arial" + *
                sz="" font size in points e.g. "10" + *
                delimited by ;'s + *
                For Example: + *
                < f="Tahoma";b;sz="16">Note: < f="Tahoma";sz="12">This is an impotant point + * + * @param s String with formatting info + * @return txt- string with all formatting info removed */ private String parseFormatting(String s) throws IllegalArgumentException { - // parse string for formatting run info - try { - boolean informatting= false; - StringBuffer txt= new StringBuffer(); - short[] frs= new short[2]; - ArrayList formattingRuns= new ArrayList(); - boolean b, it, st, u; - b=it=st=u=false; - String font= "Arial"; // default - int sz= 10; // default - for (int i= 0; i < s.length(); i++) { - char c= s.charAt(i); - if (!informatting) { - if (c!='<') { - txt.append(c); - } else { - informatting= true; - // initialize formatting run - frs= new short[2]; - frs[0]= (short)txt.length(); - frs[1]= 0; - b= it= st= u= false; - sz= 10; // default - font= "Arial"; // default - } - } else { - String[] z= s.substring(i).split("[;>]"); - if (z==null || z.length==0 /*would happen on invalid formats such as <;>*/ - || (z.length==1 && - (!(z[0].endsWith(">") || z[0].endsWith(";"))))) { - txt.append('<'); // not a real embedded format - i--; - informatting= false; - continue; - } - String section= z[0]; - // gather up font info - if (section.equals("b")) { - b= true; - } else if (section.equals("i")) { - it= true; - } else if (section.equals("u")) { - u= true; - } else if (section.equals("s")) { - st= true; - } else if (section.startsWith("f=")) { - // font name - font= section.substring(3); - font= font.substring(0, font.indexOf('"')); - } else if (section.startsWith("sz=")) { - String ssz= section.substring(4); - ssz= ssz.substring(0, ssz.indexOf('"')); - sz= Integer.valueOf(ssz).intValue(); - } - i+= section.length(); - if (i < s.length() && s.charAt(i)=='>') { // if got end of formatting section - // store formatting run - informatting= false; - Font f= new Font(font, 400, sz*20);// sz must be in points - if (b) f.setBold(b); - if (it) f.setItalic(it); - if (u) f.setUnderlined(u); - if (st) f.setStricken(st); - int fIndex= this.getWorkBook().getFontIdx(f); // index for specific font formatting - if (fIndex==-1) // must insert new font - fIndex= this.getWorkBook().insertFont(f)+1; - frs[1]= (short) fIndex; - formattingRuns.add(frs); - } - } - } - if (formattingRuns.size()>0) { - formattingRuns.add(new short[] {(short)txt.toString().length(), 15}); // 20100430 KSC: add "extra" formatting run -- necessary for Excel 2003 - this.setFormattingRuns(formattingRuns); - } - return txt.toString(); - } catch (Exception e) { - throw new IllegalArgumentException("Unable to parse String Pattern: " + s); - } + // parse string for formatting run info + try { + boolean informatting = false; + StringBuffer txt = new StringBuffer(); + short[] frs = new short[2]; + ArrayList formattingRuns = new ArrayList(); + boolean b, it, st, u; + b = it = st = u = false; + String font = "Arial"; // default + int sz = 10; // default + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (!informatting) { + if (c != '<') { + txt.append(c); + } else { + informatting = true; + // initialize formatting run + frs = new short[2]; + frs[0] = (short) txt.length(); + frs[1] = 0; + b = it = st = u = false; + sz = 10; // default + font = "Arial"; // default + } + } else { + String[] z = s.substring(i).split("[;>]"); + if (z == null || z.length == 0 /*would happen on invalid formats such as <;>*/ + || (z.length == 1 && + (!(z[0].endsWith(">") || z[0].endsWith(";"))))) { + txt.append('<'); // not a real embedded format + i--; + informatting = false; + continue; + } + String section = z[0]; + // gather up font info + if (section.equals("b")) { + b = true; + } else if (section.equals("i")) { + it = true; + } else if (section.equals("u")) { + u = true; + } else if (section.equals("s")) { + st = true; + } else if (section.startsWith("f=")) { + // font name + font = section.substring(3); + font = font.substring(0, font.indexOf('"')); + } else if (section.startsWith("sz=")) { + String ssz = section.substring(4); + ssz = ssz.substring(0, ssz.indexOf('"')); + sz = Integer.valueOf(ssz).intValue(); + } + i += section.length(); + if (i < s.length() && s.charAt(i) == '>') { // if got end of formatting section + // store formatting run + informatting = false; + Font f = new Font(font, 400, sz * 20);// sz must be in points + if (b) f.setBold(b); + if (it) f.setItalic(it); + if (u) f.setUnderlined(u); + if (st) f.setStricken(st); + int fIndex = this.getWorkBook().getFontIdx(f); // index for specific font formatting + if (fIndex == -1) // must insert new font + fIndex = this.getWorkBook().insertFont(f) + 1; + frs[1] = (short) fIndex; + formattingRuns.add(frs); + } + } + } + if (formattingRuns.size() > 0) { + formattingRuns.add(new short[]{(short) txt.toString().length(), 15}); // 20100430 KSC: add "extra" formatting run -- necessary for Excel 2003 + this.setFormattingRuns(formattingRuns); + } + return txt.toString(); + } catch (Exception e) { + throw new IllegalArgumentException("Unable to parse String Pattern: " + s); + } } /** * sets the text for this object, including formatting information + * * @param txt */ public void setStringVal(Unicodestring txt) { - this.setStringVal(txt.getStringVal()); - this.setFormattingRuns(txt.getFormattingRuns()); + this.setStringVal(txt.getStringVal()); + this.setFormattingRuns(txt.getFormattingRuns()); } /** * get the formatting runs - fonts per character index, basically, for this text object, * as an arraylist of short[] {char index, font index} - + *

                * NOTE: formatting runs in actuality differ from doc: * apparently each formatting run in the Continue record occupies 8 bytes (not 4) * plus there is an additional entry appended to the end, that indicates the last char index of the - * string (this last entry is added by Excel 2003 in the Continues Record and is not present in Unicode Strings + * string (this last entry is added by Excel 2003 in the Continues Record and is not present in Unicode Strings */ public java.util.ArrayList getFormattingRuns() { - java.util.ArrayList formattingRuns= new java.util.ArrayList(); - int frcontinues= this.getSheet().getSheetRecs().indexOf(this) + 2; - Continue fr= (Continue)this.getSheet().getSheetRecs().get(frcontinues); - byte[] frdata= fr.getData(); - int nFormattingRuns= (frdata.length/8); - if (nFormattingRuns<=1) return null; // only have the "NO FONT" entry - for (int i= 0; i < nFormattingRuns*8;) { - short idx, font; - idx= ByteTools.readShort(frdata[i++],frdata[i++]); - font= ByteTools.readShort(frdata[i++],frdata[i++]); - formattingRuns.add(new short[] {idx, font}); - i+=4; // skip the 4 "reserved" bytes - } - return formattingRuns; + java.util.ArrayList formattingRuns = new java.util.ArrayList(); + int frcontinues = this.getSheet().getSheetRecs().indexOf(this) + 2; + Continue fr = (Continue) this.getSheet().getSheetRecs().get(frcontinues); + byte[] frdata = fr.getData(); + int nFormattingRuns = (frdata.length / 8); + if (nFormattingRuns <= 1) return null; // only have the "NO FONT" entry + for (int i = 0; i < nFormattingRuns * 8; ) { + short idx, font; + idx = ByteTools.readShort(frdata[i++], frdata[i++]); + font = ByteTools.readShort(frdata[i++], frdata[i++]); + formattingRuns.add(new short[]{idx, font}); + i += 4; // skip the 4 "reserved" bytes + } + return formattingRuns; } - - + + /** * set the formatting runs - fonts per character index, basically, for this text object * as an arraylist of short[] {char index, font index} - * @param formattingRuns - - // NOTES: apparently must have a minimum of 24 bytes in the Continue formatting run to work - // even though each formatting run=4 bytes, must have 4 bytes of padding between runs...? - - // NOTES: apparently formatting runs are different than documentation indicates: - // each formatting run is 8 bytes: charIndex (2), fontIndex (2), 4 bytes (ignored) - // at end of formatting runs, must have an extra 8 bytes: charIndex==length/fontIndex, 4 bytes - // for the purposes of this method, the extra entry must be already present - * - * **/ + * + * @param formattingRuns // NOTES: apparently must have a minimum of 24 bytes in the Continue formatting run to work + * // even though each formatting run=4 bytes, must have 4 bytes of padding between runs...? + *

                + * // NOTES: apparently formatting runs are different than documentation indicates: + * // each formatting run is 8 bytes: charIndex (2), fontIndex (2), 4 bytes (ignored) + * // at end of formatting runs, must have an extra 8 bytes: charIndex==length/fontIndex, 4 bytes + * // for the purposes of this method, the extra entry must be already present + **/ public void setFormattingRuns(ArrayList formattingRuns) { - byte[] frs= new byte[4]; // minimum "null formatting run" is 4? apparently must always have a "null" formatting run - if (formattingRuns!=null) { - // formatting runs (charindex, fontindex)*n after string data - frs= new byte[formattingRuns.size()*8]; - for (int i= 0; i < formattingRuns.size(); i++) { - short[] o= (short[]) formattingRuns.get(i); - byte[] charIndex= ByteTools.shortToLEBytes(o[0]); - byte[] fontIndex= ByteTools.shortToLEBytes(o[1]); - System.arraycopy(charIndex, 0, frs, (i*8), 2); - System.arraycopy(fontIndex, 0, frs, (i*8)+2, 2); - } - } - int frcontinues= this.getSheet().getSheetRecs().indexOf(this) + 2; - Continue fr= (Continue)this.getSheet().getSheetRecs().get(frcontinues); - fr.setData(frs); - cbRuns= (short) frs.length; - byte[] b= ByteTools.shortToLEBytes(cbRuns); - this.getData()[12]= b[0]; - this.getData()[13]= b[1]; - + byte[] frs = new byte[4]; // minimum "null formatting run" is 4? apparently must always have a "null" formatting run + if (formattingRuns != null) { + // formatting runs (charindex, fontindex)*n after string data + frs = new byte[formattingRuns.size() * 8]; + for (int i = 0; i < formattingRuns.size(); i++) { + short[] o = (short[]) formattingRuns.get(i); + byte[] charIndex = ByteTools.shortToLEBytes(o[0]); + byte[] fontIndex = ByteTools.shortToLEBytes(o[1]); + System.arraycopy(charIndex, 0, frs, (i * 8), 2); + System.arraycopy(fontIndex, 0, frs, (i * 8) + 2, 2); + } + } + int frcontinues = this.getSheet().getSheetRecs().indexOf(this) + 2; + Continue fr = (Continue) this.getSheet().getSheetRecs().get(frcontinues); + fr.setData(frs); + cbRuns = (short) frs.length; + byte[] b = ByteTools.shortToLEBytes(cbRuns); + this.getData()[12] = b[0]; + this.getData()[13] = b[1]; + } - + /** - generates a skeleton Txo with 0 for text length - and the minimum length for formatting runs (=2) - */ + * generates a skeleton Txo with 0 for text length + * and the minimum length for formatting runs (=2) + */ public static XLSRecord getPrototype() { - Txo t = new Txo(); + Txo t = new Txo(); t.setOpcode(TXO); t.setData(t.PROTOTYPE_BYTES); - t.init(); - t.text= Continue.getTextContinues(""); + t.init(); + t.text = Continue.getTextContinues(""); return t; } - private byte[] PROTOTYPE_BYTES = new byte[] {18, 2, /* grbit= 530 */ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, /* cch */ - 4, 0, /* formatting run length */ - 0, 0, 0, 0 /* reserved must be 0 */ - }; - + + private byte[] PROTOTYPE_BYTES = new byte[]{18, 2, /* grbit= 530 */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, /* cch */ + 4, 0, /* formatting run length */ + 0, 0, 0, 0 /* reserved must be 0 */ + }; + public String toString() { - return getStringVal(); + return getStringVal(); } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.java b/src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.java index c5e28c8..e0c2bae 100644 --- a/src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.java +++ b/src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,19 +24,18 @@ /** * UnencryptedXLSRecord is an XLS record that upon output - * to the workbook stream should not be encrypted, if encryption is turned + * to the workbook stream should not be encrypted, if encryption is turned * on, and Biff8 is the output format. - * */ public class UnencryptedXLSRecord extends XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 356283084373599557L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 356283084373599557L; - public boolean shouldEncrypt() { + public boolean shouldEncrypt() { return false; } } diff --git a/src/main/java/io/starter/formats/XLS/Unicodestring.java b/src/main/java/io/starter/formats/XLS/Unicodestring.java index 32a546f..8828e41 100644 --- a/src/main/java/io/starter/formats/XLS/Unicodestring.java +++ b/src/main/java/io/starter/formats/XLS/Unicodestring.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,96 +22,92 @@ */ package io.starter.formats.XLS; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.Logger; + import java.io.Serializable; import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; import java.util.ArrayList; -import java.util.Iterator; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; /** - * * To change the template for this generated type comment go to * Window>Preferences>Java>Code Generation>Code and Comments */ /** Unicode String: BIFF8 Compressed String format
                -

                -    offer  name        size    contents
                -    ---    
                -    0       cch         2       Count of characters in string (NOT the number of bytes)
                -    2       grbit       1       Option flags
                -    3       rgb         var     Array of string characters and formatting runs.
                -    
                -    GRBIT Option codes:
                -    bits    mask        name        contents
                -    ---    
                -    0       0x1         fHighByte   =0 if all chars have high byte of 0x0 
                -                                    (low bytes only are stored.)  
                -                                    =1 if at least one char in string has nonzero
                -                                    highbyte.  All chars are double byte (uncompressed)
                -    1       0x2         (Reserved)  Must be zero.
                -    2       0x4         fExtSt      Far East Extended Strings.  Asian Phonetic Settings (phonetic)
                -    3       0x8         fRichSt     Rich String
                -    7-4     0xF0        (Reserved)  Must be zero.
                -
                -    rgb:
                -    [2 or 3] 	2 		(optional, only if richtext=1) Number of Rich-Text formatting runs (rt)
                -	[var.] 		4 		(optional, only if phonetic=1) Size of Asian phonetic settings block (in bytes, sz)
                -	var. 	ln or 2∙ln 	Character array (8-bit characters or 16-bit characters, dependent on ccompr)
                -	[var.] 		4∙rt 	(optional, only if richtext=1) List of rt formatting runs 
                -	[var.] 		sz 		(optional, only if phonetic=1) Asian Phonetic Settings Block 
                -    
                -    
                -	Asian phonetic text can be used to provide extended phonetic information for specific characters or words. It
                -	appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or
                -	entire words.
                -	Offset Size Contents
                -	0 		2 	identifier 0001H
                -	2 		2 	Size of the following data (10 + 2∙ln + 6∙np)
                -	4 		2 	Index to FONT record used for the Asian phonetic text
                -	6 		2 	Additional settings for the Asian phonetic text:
                -				Bit Mask Contents
                -				1-0 0003H Type of Japanese phonetic text (type):
                -					00 = Katakana (narrow) 10 = Hiragana
                -					01 = Katakana (wide)
                -				3-2 000CH Alignment of all portions of the Asian phonetic text (align):
                -					00 = Not specified (Japanese only) 10 = Centered
                -					01 = Left (Top for vertical text)  11 = Distributed
                -				5-4 0030H 11 (always set)
                -	8 		2 	Number of portions the Asian phonetic text is broken into (np).
                -				If np = 0, the Asian phonetic text refers to the entire cell text.
                -	10 		2 	Total length of the following Asian phonetic text (number of characters, ln)
                -	12 		2 	Repeated total length of the text
                -	14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters.
                -				Note: If ln = 0, this field is not empty but contains 0000H.
                -	14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each
                -				structure contains the following fields:
                -				Offset Size Contents
                -				0 2 First character in the Asian phonetic text of this portion (cpa)
                -				2 2 First character of the main text belonging to this portion (cpm)
                -				4 2 Number of characters in main text belonging to this portion (ccm)
                -    
                -    
                -    

                - - * @see Sst - * @see Labelsst -*/ -public final class Unicodestring implements XLSConstants, Serializable{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1800227752945355535L; - private int DEBUGLEVEL = -1; +

                + offer  name        size    contents
                + ---
                + 0       cch         2       Count of characters in string (NOT the number of bytes)
                + 2       grbit       1       Option flags
                + 3       rgb         var     Array of string characters and formatting runs.
                +
                + GRBIT Option codes:
                + bits    mask        name        contents
                + ---
                + 0       0x1         fHighByte   =0 if all chars have high byte of 0x0
                + (low bytes only are stored.)
                + =1 if at least one char in string has nonzero
                + highbyte.  All chars are double byte (uncompressed)
                + 1       0x2         (Reserved)  Must be zero.
                + 2       0x4         fExtSt      Far East Extended Strings.  Asian Phonetic Settings (phonetic)
                + 3       0x8         fRichSt     Rich String
                + 7-4     0xF0        (Reserved)  Must be zero.
                +
                + rgb:
                + [2 or 3] 	2 		(optional, only if richtext=1) Number of Rich-Text formatting runs (rt)
                + [var.] 		4 		(optional, only if phonetic=1) Size of Asian phonetic settings block (in bytes, sz)
                + var. 	ln or 2∙ln 	Character array (8-bit characters or 16-bit characters, dependent on ccompr)
                + [var.] 		4∙rt 	(optional, only if richtext=1) List of rt formatting runs
                + [var.] 		sz 		(optional, only if phonetic=1) Asian Phonetic Settings Block
                +
                +
                + Asian phonetic text can be used to provide extended phonetic information for specific characters or words. It
                + appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or
                + entire words.
                + Offset Size Contents
                + 0 		2 	identifier 0001H
                + 2 		2 	Size of the following data (10 + 2∙ln + 6∙np)
                + 4 		2 	Index to FONT record used for the Asian phonetic text
                + 6 		2 	Additional settings for the Asian phonetic text:
                + Bit Mask Contents
                + 1-0 0003H Type of Japanese phonetic text (type):
                + 00 = Katakana (narrow) 10 = Hiragana
                + 01 = Katakana (wide)
                + 3-2 000CH Alignment of all portions of the Asian phonetic text (align):
                + 00 = Not specified (Japanese only) 10 = Centered
                + 01 = Left (Top for vertical text)  11 = Distributed
                + 5-4 0030H 11 (always set)
                + 8 		2 	Number of portions the Asian phonetic text is broken into (np).
                + If np = 0, the Asian phonetic text refers to the entire cell text.
                + 10 		2 	Total length of the following Asian phonetic text (number of characters, ln)
                + 12 		2 	Repeated total length of the text
                + 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters.
                + Note: If ln = 0, this field is not empty but contains 0000H.
                + 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each
                + structure contains the following fields:
                + Offset Size Contents
                + 0 2 First character in the Asian phonetic text of this portion (cpa)
                + 2 2 First character of the main text belonging to this portion (cpm)
                + 4 2 Number of characters in main text belonging to this portion (ccm)
                +
                +
                + 

                + * @see Sst + * @see Labelsst + */ +public final class Unicodestring implements XLSConstants, Serializable { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1800227752945355535L; + private int DEBUGLEVEL = -1; int cch; private int cchExtRst = 0; private byte[] ExtRst; - // String stringVal; + // String stringVal; private boolean fHighByte = false; private int formatlen = 0; private byte[] formatrunnum = new byte[2]; @@ -119,92 +115,93 @@ public final class Unicodestring implements XLSConstants, Serializable{ private boolean fRichSt = false; private byte grbit; - // private Isstinf mybucket = null; + // private Isstinf mybucket = null; private int numformattingruns = 0, sstpos = -1; private byte[] stringarray; private int stringlen, offer; private boolean westernencoding = true; - + /** override equals based on value - of String. - */ - public boolean equals(Object obj){ - if(obj instanceof Unicodestring){ - // compare Unicode strings at byte level rather than just string value + of String. + */ + public boolean equals(Object obj) { + if (obj instanceof Unicodestring) { + // compare Unicode strings at byte level rather than just string value // if(val.equals(obj.toString()))return true; - if (this.westernencoding) { - if (java.util.Arrays.equals(this.getWesternBytes(), ((Unicodestring)obj).getWesternBytes())) - return true; - } else { - if (java.util.Arrays.equals(this.getEasternBytes(), ((Unicodestring)obj).getEasternBytes())) - return true; - } - }else if(obj instanceof String){ - String val = this.toString(); - if(val.equals(obj))return true; + if (this.westernencoding) { + return java.util.Arrays.equals(this.getWesternBytes(), ((Unicodestring) obj).getWesternBytes()); + } else { + return java.util.Arrays.equals(this.getEasternBytes(), ((Unicodestring) obj).getEasternBytes()); + } + } else if (obj instanceof String) { + String val = this.toString(); + return val.equals(obj); } return false; } - /* return the eastern bytes for this Ustring - */ - byte[] getEasternBytes(){ + */ + byte[] getEasternBytes() { byte[] data = new byte[stringarray.length + formatlen + offer + ExtRst.length]; byte[] cchbyte = ByteTools.cLongToLEBytes(cch); System.arraycopy(cchbyte, 0, data, 0, 2); data[2] = grbit; //cchExtRst = ExtRst.length; byte[] cchExtByte = ByteTools.cLongToLEBytes(cchExtRst); - + int tempoffset = 3; if (fRichSt) tempoffset = 5; System.arraycopy(cchExtByte, 0, data, tempoffset, 4); - + stringlen = stringarray.length; System.arraycopy(stringarray, 0, data, offer, stringlen); - if(isRichString()){ - formatrunnum = ByteTools.shortToLEBytes((short)numformattingruns); + if (isRichString()) { + formatrunnum = ByteTools.shortToLEBytes((short) numformattingruns); // put the number of formatting runs back data[3] = formatrunnum[0]; data[4] = formatrunnum[1]; System.arraycopy(formattingarray, 0, data, stringlen + offer, formatlen); } - System.arraycopy(ExtRst, 0, data, (stringlen + offer + formatlen),ExtRst.length); - if(data[0]==0x0) Logger.logInfo("Unicodestring has zero length."); + System.arraycopy(ExtRst, 0, data, (stringlen + offer + formatlen), ExtRst.length); + if (data[0] == 0x0) Logger.logInfo("Unicodestring has zero length."); return data; } - + /** return the grbit for this UString - */ - byte getGrbit(){return this.grbit;} + */ + byte getGrbit() { + return this.grbit; + } /** returns the length of the UNICODESTRING's bytes - */ - int getLength(){ - return stringarray.length + formatlen + offer; + */ + int getLength() { + return stringarray.length + formatlen + offer; } - + /** provide a hint to the CompatibleVector - about this objects likely position. - */ - // public int getRecordIndexHint(){return this.recordIdx;} - + about this objects likely position. + */ + // public int getRecordIndexHint(){return this.recordIdx;} + /** set the position of this string in the SST - */ - int getSSTPos(){return sstpos;} - + */ + int getSSTPos() { + return sstpos; + } + // ride'em cowboy! - byte[] getWesternBytes(){ + byte[] getWesternBytes() { byte[] data = new byte[stringarray.length + formatlen + offer]; byte[] cchbyte = ByteTools.cLongToLEBytes(cch); System.arraycopy(cchbyte, 0, data, 0, 2); data[2] = grbit; stringlen = stringarray.length; System.arraycopy(stringarray, 0, data, offer, stringlen); - if(isRichString()){ - formatrunnum = ByteTools.shortToLEBytes((short)numformattingruns); + if (isRichString()) { + formatrunnum = ByteTools.shortToLEBytes((short) numformattingruns); // put the number of formatting runs back data[3] = formatrunnum[0]; data[4] = formatrunnum[1]; @@ -212,188 +209,185 @@ byte[] getWesternBytes(){ } return data; } - - int len =0; - - + + int len = 0; + + /**Your standard init method. Choose the format of the string and init it. - * + * * @param ustrdata - the data to init the String from * @param extrstbrk - whether the string ExtRst data spans a continue boundary */ - void init(byte[] ustrdata, boolean extrstbrk){ - setLen(ustrdata.length); + void init(byte[] ustrdata, boolean extrstbrk) { + setLen(ustrdata.length); grbit = ustrdata[2]; try { - if((grbit & 0x4) == 0x4){ - westernencoding = false; - this.initEasternEncoding(ustrdata, extrstbrk); - }else{ - this.initWesternEncoding(ustrdata); - } + if ((grbit & 0x4) == 0x4) { + westernencoding = false; + this.initEasternEncoding(ustrdata, extrstbrk); + } else { + this.initWesternEncoding(ustrdata); + } } catch (NegativeArraySizeException e) { - ; // error processing string but don't fail entirely + // error processing string but don't fail entirely } - } - + } + /** - Init method for japanese/chinese/? style UnicodeStrings - */ - void initEasternEncoding(byte[] ustrdata, boolean extrstbrk) throws NegativeArraySizeException{ - // get the row, col and ixfe information - cch = ByteTools.readShort(ustrdata[0],ustrdata[1]); + Init method for japanese/chinese/? style UnicodeStrings + */ + void initEasternEncoding(byte[] ustrdata, boolean extrstbrk) throws NegativeArraySizeException { + // get the row, col and ixfe information + cch = ByteTools.readShort(ustrdata[0], ustrdata[1]); int dlen = cch; grbit = ustrdata[2]; offer = 7; - + // double byte? almost always here - if(((grbit & 0x1) == 0x1)){ + if (((grbit & 0x1) == 0x1)) { dlen *= 2; fHighByte = true; } - + stringarray = new byte[dlen]; // is rich text? has formatting runs? - if((grbit & 0x8) == 0x8){ + if ((grbit & 0x8) == 0x8) { fRichSt = true; } - - if (fRichSt){ + + if (fRichSt) { offer = 9; - try{ + try { System.arraycopy(ustrdata, offer, stringarray, 0, dlen); // string data - formatrunnum[0] = ustrdata[3]; + formatrunnum[0] = ustrdata[3]; formatrunnum[1] = ustrdata[4]; - numformattingruns = ByteTools.readShort(formatrunnum[0],formatrunnum[1]); + numformattingruns = ByteTools.readShort(formatrunnum[0], formatrunnum[1]); formatlen = numformattingruns * 4; formattingarray = new byte[formatlen]; System.arraycopy(ustrdata, (dlen + offer), formattingarray, 0, formatlen); // formatting info cchExtRst = ByteTools.readInt(ustrdata[5], ustrdata[6], ustrdata[7], ustrdata[8]); // report error??? -if ((ustrdata.length-dlen-offer-cchExtRst)!=formatlen) -io.starter.toolkit.Logger.log("Unicodestring.initEasternEncoding: Format runs are not correct"); + if ((ustrdata.length - dlen - offer - cchExtRst) != formatlen) + io.starter.toolkit.Logger.log("Unicodestring.initEasternEncoding: Format runs are not correct"); ExtRst = new byte[cchExtRst]; System.arraycopy(ustrdata, (dlen + offer + formatlen), ExtRst, 0, cchExtRst); // Extendadata //report error??? - if (ExtRst[0]!=1 || ExtRst[1]!=0) - Logger.logWarn("Unicodestring.initEasternEncoding: Phonetic Data is not correct"); - }catch(Throwable e){ + if (ExtRst[0] != 1 || ExtRst[1] != 0) + Logger.logWarn("Unicodestring.initEasternEncoding: Phonetic Data is not correct"); + } catch (Throwable e) { Logger.logInfo("Problem parsing rich text Eastern Unicodestring. len:" + dlen + " rich:" + fRichSt + ". " + e); } - }else{ - try{ + } else { + try { cchExtRst = ByteTools.readInt(ustrdata[3], ustrdata[4], ustrdata[5], ustrdata[6]); - int extrstLen = ustrdata.length - (formatlen + offer + dlen); - int off =0; - + int extrstLen = ustrdata.length - (formatlen + offer + dlen); + int off = 0; + // the most important code in the world - if((cchExtRst!=extrstLen)||(extrstbrk)){ - if(DEBUGLEVEL > 0)Logger.logWarn("Unicodestring ExtRst Inconsistent."); + if ((cchExtRst != extrstLen) || (extrstbrk)) { + if (DEBUGLEVEL > 0) Logger.logWarn("Unicodestring ExtRst Inconsistent."); //off = 1; cchExtRst = extrstLen; } ExtRst = new byte[cchExtRst]; - + System.arraycopy(ustrdata, 7, stringarray, 0, dlen); // string data - System.arraycopy(ustrdata, (ustrdata.length - ExtRst.length) -off, ExtRst, 0, ExtRst.length); // Extendadata + System.arraycopy(ustrdata, (ustrdata.length - ExtRst.length) - off, ExtRst, 0, ExtRst.length); // Extendadata // report error??? - if (ExtRst[0]!=1 || ExtRst[1]!=0) - Logger.logWarn("Unicodestring.initEasternEncoding: Phonetic Data is not correct"); - }catch(Throwable t){ + if (ExtRst[0] != 1 || ExtRst[1] != 0) + Logger.logWarn("Unicodestring.initEasternEncoding: Phonetic Data is not correct"); + } catch (Throwable t) { Logger.logInfo("Problem Parsing non-rich Eastern Unicodestring. len:" + dlen + " rich:" + fRichSt + ". " + t.toString()); } } } - + /** - Init method for default unicode string types. - */ + Init method for default unicode string types. + */ void initWesternEncoding(byte[] ustrdata) throws NegativeArraySizeException { // get the row, col and ixfe information - cch = ByteTools.readShort(ustrdata[0],ustrdata[1]); - int dlen = cch +0; + cch = ByteTools.readShort(ustrdata[0], ustrdata[1]); + int dlen = cch + 0; grbit = ustrdata[2]; offer = 3; - + // is is double byte stream? - if((grbit & 0x1) == 0x1){ + if ((grbit & 0x1) == 0x1) { dlen *= 2; fHighByte = true; - }else{ + } else { fHighByte = false; } - + // handle data being greater than the length of the bytes - if(dlen+offer > ustrdata.length){ - dlen = ustrdata.length - offer; + if (dlen + offer > ustrdata.length) { + dlen = ustrdata.length - offer; } - + // is rich text? does it have formatting runs? - if((grbit & 0x8) == 0x8){ + if ((grbit & 0x8) == 0x8) { fRichSt = true; } - - if (fRichSt){ - formatrunnum[0] = ustrdata[3]; + + if (fRichSt) { + formatrunnum[0] = ustrdata[3]; formatrunnum[1] = ustrdata[4]; - numformattingruns = ByteTools.readShort(formatrunnum[0],formatrunnum[1]); + numformattingruns = ByteTools.readShort(formatrunnum[0], formatrunnum[1]); offer = 5; formatlen = (ustrdata.length - dlen) - offer; formattingarray = new byte[formatlen]; System.arraycopy(ustrdata, dlen + offer, formattingarray, 0, formatlen); } - stringarray = new byte[dlen+0]; - try{ - System.arraycopy(ustrdata, offer, stringarray, 0, dlen); - }catch(Exception e){ + stringarray = new byte[dlen + 0]; + try { + System.arraycopy(ustrdata, offer, stringarray, 0, dlen); + } catch (Exception e) { Logger.logInfo("Problem Parsing Western Unicodestring. len:" + dlen + " rich:" + fRichSt + ". " + e); } } - + /** returns whether the Stream position passed in - falls within the UString's character data as opposed - to its length/formatting runs and other non character data - - Read as "typical plot by microsoft to make our lives miserable" - * + falls within the UString's character data as opposed + to its length/formatting runs and other non character data + + Read as "typical plot by microsoft to make our lives miserable" + * * @param pos - the position of the String in the Sst data * @return whether the string can be broken into parts to span a continue - * + * * FROM OPENOFFICE.ORG DOCS... * Unicode strings are split in a special way. At the beginning of each CONTINUE record the option flags byte is repeated. - Only the character size flag will be set in this flags byte, the Rich-Text flag and the Far-East flag are set to zero. - - In each CONTINUE record it is possible that the character size changes from 8-bit characters to 16-bit characters - and vice versa. ! - Never a Unicode string is split until and including the first character. That means, all header fields (string length, - option flags, optional Rich-Text size, and optional Far-East data size) and the first character of the string have to - occur together in the leading record, or have to be moved completely into the CONTINUE record. ! - Formatting runs (?2.1) cannot be split between their components (character index and FONT record index). If a - string is split between two formatting runs, the option flags field will not be repeated in the CONTINUE record. - * + Only the character size flag will be set in this flags byte, the Rich-Text flag and the Far-East flag are set to zero. + + In each CONTINUE record it is possible that the character size changes from 8-bit characters to 16-bit characters + and vice versa. ! + Never a Unicode string is split until and including the first character. That means, all header fields (string length, + option flags, optional Rich-Text size, and optional Far-East data size) and the first character of the string have to + occur together in the leading record, or have to be moved completely into the CONTINUE record. ! + Formatting runs (?2.1) cannot be split between their components (character index and FONT record index). If a + string is split between two formatting runs, the option flags field will not be repeated in the CONTINUE record. + * */ - boolean isBreakable(int pos){ - int mypos = this.getSSTPos(); - int strstart = mypos+offer; + boolean isBreakable(int pos) { + int mypos = this.getSSTPos(); + int strstart = mypos + offer; int strend = strstart + stringarray.length; byte[] bts = this.read(); int brkpos = (strstart - pos); - if(brkpos < 0)brkpos*=-1; - if(this.cch < 2) return false; // don't break one-char strings -- the 0x1 is confused as a grbit - - if(cchExtRst > 0) return false; - if(pos>(mypos + cchExtRst+stringarray.length+offer)) return false; // in ExtRst data... - - if((pos > strstart) && (pos < strend)){ - if (this.getLength() > 8220) { - return true; - } - return false; + if (brkpos < 0) brkpos *= -1; + if (this.cch < 2) return false; // don't break one-char strings -- the 0x1 is confused as a grbit + + if (cchExtRst > 0) return false; + if (pos > (mypos + cchExtRst + stringarray.length + offer)) return false; // in ExtRst data... + + if ((pos > strstart) && (pos < strend)) { + return this.getLength() > 8220; } return false; } - + /** * Returns wheter the specified index is breaking in the middle of a character. Only useful or needed * for double byte strings, where the continue boundary cannot be in the middle of a char. @@ -401,174 +395,178 @@ boolean isBreakable(int pos){ */ public boolean charBreakOnBounds(int pos) { int mypos = this.getSSTPos(); - int strstart = mypos+offer; + int strstart = mypos + offer; int strend = strstart + stringarray.length; byte[] bts = this.read(); int brkpos = (strstart - pos); - if(brkpos < 0)brkpos*=-1; - if((brkpos % 2)==0)return false; - return true; - + if (brkpos < 0) brkpos *= -1; + return (brkpos % 2) != 0; + } - - - boolean isRichString(){ + + + boolean isRichString() { return fRichSt; } - + /** * returns true if this unicode string is of Eastern Character Set * @return */ public boolean isEasternString() { - return !westernencoding; + return !westernencoding; } - + /** * returns true if this unicode string is in Western Character Set * @return */ public boolean isWesternString() { - return westernencoding; + return westernencoding; } + /** returns the UNICODESTRING's bytes - */ - - public byte[] read(){ - if (westernencoding)return this.getWesternBytes(); - return this.getEasternBytes(); + */ + + public byte[] read() { + if (westernencoding) return this.getWesternBytes(); + return this.getEasternBytes(); } - + /** * returns just the string array portion of this Unicode String * minus header, formatting, etc. * @return */ public byte[] readStr() { - return stringarray; + return stringarray; } - + /** set the Extsst Isstinf bucket for the USTRING see page 313 XL'97 book - */ + */ //void setBucket(Isstinf i){this.mybucket = i;} - + /** set the position of this string in the SST - */ - void setSSTPos(int p){sstpos = p;} - + */ + void setSSTPos(int p) { + sstpos = p; + } + /** return the String representation of this Unicodestring */ - public String toString(){ - try{ - if (fHighByte){ - return new String(stringarray, UNICODEENCODING); // defaultEncoding); - } - return new String(stringarray, DEFAULTENCODING); - }catch(UnsupportedEncodingException e){ + public String toString() { + try { + if (fHighByte) { + return new String(stringarray, UNICODEENCODING); // defaultEncoding); + } + return new String(stringarray, DEFAULTENCODING); + } catch (UnsupportedEncodingException e) { Logger.logInfo("Problem decoding Unicodestring. " + e + " Resorting to default encoding: " + DEFAULTENCODING); - try{ - return new String(stringarray, DEFAULTENCODING); // supported by JDK1.1 + - }catch(UnsupportedEncodingException t){ - if(DEBUGLEVEL > -1)Logger.logInfo("Problem decoding Unicodestring. " + t); + try { + return new String(stringarray, DEFAULTENCODING); // supported by JDK1.1 + + } catch (UnsupportedEncodingException t) { + if (DEBUGLEVEL > -1) Logger.logInfo("Problem decoding Unicodestring. " + t); } - } - return null; - } - + } + return null; + } + /** return the String representation of this Unicodestring. Sets a caching string value in the Unicode String, * primarily needed for lookups when shared strings = true - */ - public String toCachingString(){ - try{ - if (fHighByte){ + */ + public String toCachingString() { + try { + if (fHighByte) { /* KSC: TESTING: for handling japanese fonts... boolean isjp; // KSC: testing if (!westernencoding || Unicodestring.containsJapanese(this.getStringVal())) isjp= true; -*/ +*/ return new String(stringarray, UNICODEENCODING); // defaultEncoding); - }else { - return new String(stringarray, DEFAULTENCODING); + } else { + return new String(stringarray, DEFAULTENCODING); } - }catch(UnsupportedEncodingException e){ + } catch (UnsupportedEncodingException e) { Logger.logInfo("Problem decoding Unicodestring. " + e + " Resorting to default encoding: " + DEFAULTENCODING); - try{ - return new String(stringarray, DEFAULTENCODING); // supported by JDK1.1 + - }catch(UnsupportedEncodingException t){ - if(DEBUGLEVEL > -1)Logger.logInfo("Problem decoding Unicodestring. " + t); + try { + return new String(stringarray, DEFAULTENCODING); // supported by JDK1.1 + + } catch (UnsupportedEncodingException t) { + if (DEBUGLEVEL > -1) Logger.logInfo("Problem decoding Unicodestring. " + t); return null; } } } - + /** updates the unicode string - */ - public void updateUnicodeString(String s){ - if(s.equals(this.toString()))return; + */ + public void updateUnicodeString(String s) { + if (s.equals(this.toString())) return; // 0 cch 2 Count of characters in string (NOT the number of bytes) // 2 grbit 1 Option flags // make sure to get formatting runs if present - byte[] strbytes = null; - try{ - if (!ByteTools.isUnicode(s)){ + byte[] strbytes = null; + try { + if (!ByteTools.isUnicode(s)) { strbytes = s.getBytes(DEFAULTENCODING); grbit = (byte) (grbit & 0xFE); - }else{ + } else { strbytes = s.getBytes(UNICODEENCODING); - if(!((grbit & 0x1) == 0x1)){ + if (!((grbit & 0x1) == 0x1)) { grbit += 0x1; } } - }catch(UnsupportedEncodingException e){Logger.logInfo("Problem encoding string: " + e);} + } catch (UnsupportedEncodingException e) { + Logger.logInfo("Problem encoding string: " + e); + } int strdatalen = strbytes.length; int strlen = s.length(); byte[] blen = ByteTools.cLongToLEBytes(strlen); - if((grbit & 0x4) == 0x4){ // it was an eastern string. Blow that extrst out... + if ((grbit & 0x4) == 0x4) { // it was an eastern string. Blow that extrst out... grbit = (byte) (grbit & 0xFB); ExtRst = null; offer -= 4; westernencoding = true; - } - if ((grbit & 0x8)==0x8) { // had formatting runs - remove (since cannot keep with new string) - formatlen= 0; - fRichSt= false; - grbit ^= 0x8; - offer-= 2; + } + if ((grbit & 0x8) == 0x8) { // had formatting runs - remove (since cannot keep with new string) + formatlen = 0; + fRichSt = false; + grbit ^= 0x8; + offer -= 2; } byte[] newbytes = new byte[strdatalen + offer + formatlen]; newbytes[2] = grbit; - System.arraycopy(blen,0,newbytes,0,2); - - System.arraycopy(strbytes,0, newbytes, offer, strdatalen); - if(this.isRichString()){ + System.arraycopy(blen, 0, newbytes, 0, 2); + + System.arraycopy(strbytes, 0, newbytes, offer, strdatalen); + if (this.isRichString()) { System.arraycopy(this.formattingarray, 0, newbytes, strdatalen + this.offer, this.formatlen); // put the number of formatting runs back newbytes[3] = this.formatrunnum[0]; newbytes[4] = this.formatrunnum[1]; } - this.init(newbytes,false); + this.init(newbytes, false); } - + public int getLen() { return len; } + public void setLen(int leng) { this.len = leng; } + public String getStringVal() { return toString(); } - + /** * Return true if the string contains formatting runs embedded within it - - * + * * @return */ public boolean hasFormattingRuns() { - if (numformattingruns>0)return true; - return false; + return numformattingruns > 0; } - + /** * return formatting runs, if any, for this unicode strings * formatting runs are a list of one or more pairs of [char index, font index] @@ -576,288 +574,285 @@ public boolean hasFormattingRuns() { * @return */ public ArrayList getFormattingRuns() { - if (numformattingruns==0) return null; - ArrayList formattingruns= new ArrayList(); - for (int i= 0; i < numformattingruns; i++) { - short charIndex= ByteTools.readShort(formattingarray[(i*4)], formattingarray[((i*4)+1)]); - short fontIndex= ByteTools.readShort(formattingarray[(i*4)+2], formattingarray[((i*4)+3)]); - formattingruns.add(new short[] {charIndex, fontIndex}); + if (numformattingruns == 0) return null; + ArrayList formattingruns = new ArrayList(); + for (int i = 0; i < numformattingruns; i++) { + short charIndex = ByteTools.readShort(formattingarray[(i * 4)], formattingarray[((i * 4) + 1)]); + short fontIndex = ByteTools.readShort(formattingarray[(i * 4) + 2], formattingarray[((i * 4) + 3)]); + formattingruns.add(new short[]{charIndex, fontIndex}); } - return formattingruns; + return formattingruns; } - + /** returns true if the char c is a double-byte character */ private static boolean isJapanese(char c) { - if (c >= '\u0100' && c<='\uffff') return true; - return false; - // simpler: return c>'\u00ff'; + return c >= '\u0100' && c <= '\uffff'; + // simpler: return c>'\u00ff'; } - /** returns true if the String s contains any Japanese characters */ + /** returns true if the String s contains any Japanese characters */ public static boolean containsJapanese(String s) { - for (int i=0; i= '\u30a0' && c<='\u30ff') return true; - // hiragana - if (c >= '\u3040' && c<='\u309f') return true; - // CJK Unified Ideographs - if (c >= '\u4e00' && c<='\u9fff') return true; - // CJK symbols & punctuation - if (c >= '\u3000' && c<='\u303f') return true; - // KangXi (kanji) - if (c >= '\u2f00' && c<='\u2fdf') return true; - // KanBun - if (c >= '\u3190' && c <='\u319f') return true; - // CJK Unified Ideographs Extension A - if (c >= '\u3400' && c <='\u4db5') return true; - // CJK Compatibility Forms - if (c >= '\ufe30' && c <='\ufe4f') return true; - // CJK Compatibility - if (c >= '\u3300' && c <='\u33ff') return true; - // CJK Radicals Supplement - if (c >= '\u2e80' && c <='\u2eff') return true; - // other character.. - return false; - } - + // katakana: + if (c >= '\u30a0' && c <= '\u30ff') return true; + // hiragana + if (c >= '\u3040' && c <= '\u309f') return true; + // CJK Unified Ideographs + if (c >= '\u4e00' && c <= '\u9fff') return true; + // CJK symbols & punctuation + if (c >= '\u3000' && c <= '\u303f') return true; + // KangXi (kanji) + if (c >= '\u2f00' && c <= '\u2fdf') return true; + // KanBun + if (c >= '\u3190' && c <= '\u319f') return true; + // CJK Unified Ideographs Extension A + if (c >= '\u3400' && c <= '\u4db5') return true; + // CJK Compatibility Forms + if (c >= '\ufe30' && c <= '\ufe4f') return true; + // CJK Compatibility + if (c >= '\u3300' && c <= '\u33ff') return true; + // CJK Radicals Supplement + return c >= '\u2e80' && c <= '\u2eff'; + // other character.. + } + /** info on encoding: - * * -16 code from 0 x0000 to 0 x007F) is the ASCII characters -the next 128 Unicode characters (x0080 code from 0 to 0 x00FF) is ISO? 8859-1 on the expansion of ASCII. -Unicode in different parts of the same characters are based on existing standards. -This is to facilitate the conversion. -Greek alphabet x0370 use from 0 to 0 x03FF code, -The use of Slavic language x0400 from 0 to 0x04FF code -United States use x0530 from 0 to 0x058F code -Hebrew x0590 from 0 to 0 x05FF code. -China, Japan and South Korea hieroglyphs (known as the CJK) occupiers from 0x3000 to 0x9FFF code. + * + 16 code from 0 x0000 to 0 x007F) is the ASCII characters + the next 128 Unicode characters (x0080 code from 0 to 0 x00FF) is ISO? 8859-1 on the expansion of ASCII. + Unicode in different parts of the same characters are based on existing standards. + This is to facilitate the conversion. + Greek alphabet x0370 use from 0 to 0 x03FF code, + The use of Slavic language x0400 from 0 to 0x04FF code + United States use x0530 from 0 to 0x058F code + Hebrew x0590 from 0 to 0 x05FF code. + China, Japan and South Korea hieroglyphs (known as the CJK) occupiers from 0x3000 to 0x9FFF code. + * There are four types of Shift-JIS encoding available (although you may need to customize the encoding list to see them all). + Japanes(Shift JIS) contains the half-width alphanumeric characters, symbols, and katakana, along with JIS X0208 (JIS 1&2) characters. + Japanese (Mac OS) contains all of Japanese (Shift JIS) along with some Mac-specific symbols. + Japanese (Windows, DOS) contains all of Japanese (Shift JIS) along with some Windows-specific symbols. + Japanese (Shift JIS X0213) contains all of Japanese (Shift JIS) along with kanji specified in JIS 3&4 for a total of 13,000 characters total. + Japanese text created in Classic OS is encoded in Japanese (Mac OS), so this is the safest option among the various Shift JIS encodings. + Asian characters are Kanji, Hiragana, Katakana + (full and half-width), full-width numbers and punctuation, + and Chinese and Korean characters. This is often abbreviated as “CJK.” - * There are four types of Shift-JIS encoding available (although you may need to customize the encoding list to see them all). -Japanes(Shift JIS) contains the half-width alphanumeric characters, symbols, and katakana, along with JIS X0208 (JIS 1&2) characters. -Japanese (Mac OS) contains all of Japanese (Shift JIS) along with some Mac-specific symbols. -Japanese (Windows, DOS) contains all of Japanese (Shift JIS) along with some Windows-specific symbols. -Japanese (Shift JIS X0213) contains all of Japanese (Shift JIS) along with kanji specified in JIS 3&4 for a total of 13,000 characters total. -Japanese text created in Classic OS is encoded in Japanese (Mac OS), so this is the safest option among the various Shift JIS encodings. - - Asian characters are Kanji, Hiragana, Katakana - (full and half-width), full-width numbers and punctuation, - and Chinese and Korean characters. This is often abbreviated as “CJK.” - - -Japanese Language Encoding -Encoding Other Names Vendor/Standard Body Other Rosette Names -CCSID 1027 EBCDIK Microsoft & IBM CCSID-1027, CCSID1027 -CCSID 290 EBCDIK Microsoft & IBM CCSID-290, CCSID290 -CCSID 930 IBM CCSID-930, CCSID930 -CCSID 939 IBM CCSID-939, CCSID939 -CCSID 942 Microsoft & IBM CCSID-942, CCSID942 -CP10001 Macintosh Japanese Microsoft & IBM CP10001 -CP20290 (full/half width Latin & halfwidth katakana) Microsoft & IBM CP20290 -CP21027 (halfwidth Latin, halfwidth katakana&private use) Microsoft & IBM CP21027 -EUC-JP Unix EUC-JP, EUC-J -EUC-JP-JISROMAN Unix EUC-JP-JISROMAN -ISO 2022-JP International or National Standard ISO-2022-JP -JapaneseAutoDetect For encodings, see JapaneseAutodetect Rosette Autodetect JapaneseAutoDetect - -Encoding Other Names Vendor/Standard Body Other Rosette Names -JIS_X_0201 HalfWidthKatakana International or National Standard JIS_X_0201, IBM897 -JIS_X_0208 International or National Standard JIS_X_0208 -Shift-JISMS MS_Kanji, CP932 Microsoft & IBM Shift-JIS, SJIS -Shift_JIS-2004 ShiftJISX0213 Microsoft & IBM Shift-Jis2004, Shift_JISX0213,Shift-X -Shift-JIS78 Shift-JIS without MS/IBM extensions Unix/Macintosh Shift-JIS78, SJIS78 - -Chinese Language Encoding -Encoding Other Names Vendor/Standard Body Other Rosette Names -ChineseAutoDetect For encodings, see ChineseAutodetect Rosette Autodetect ChineseAutoDetect -HKSCS International or National Standard HKSCS -ISO 2022-CN International or National Standard ISO-2022-CN -GB 18030 International or National Standard GB18030 -Chinese, Simplified -CCSID 935 IBM CCSID-935, CCSID935 -EUC-CN GB2312, EUC-SC Unix GB2312 -GB2312 EUC-CN, EUC-SC International or National Standard GB2312 -HZ-GB-2312 HZ-GB-2312 International or National Standard HZ, HZ-GB-2312 -CP936 GBK Microsoft & IBM CP936, GBK -MacChineseSimplified Macintosh MacChineseSimplified -Chinese, Traditional -CCSID 937 IBM CCSID-937, CCSID937 -CNS-11643-1986 EUC-TW International or National Standard CNS-11643-1986 -CNS-11643-1992 EUC-TW International or National Standard CNS-11643, CNS-11643-1992 -EUC-TW CNS-11643-1986, CNS-11643-1992 Unix CNS-11643, CNS-11643-1992 -GB12345 International or National Standard GB12345 -Big5 International or National Standard Big5 -Big5+ International or National Standard Big5+, Big5Plus -CP10002 Macintosh Traditional Chinese Microsoft & IBM CP10002 -CP950 Microsoft & IBM CP950 -MacChineseTraditional Macintosh MacChineseTraditional - -The CCSID in the iSeries objects must be 935 (Simplified Chinese) -The code page in the PC for Simplified Chinese is 1388 -The Data Type in the DSPF/PRTF must be O (Other) - -more info: - Shift_JIS DBCS 16-bit Japanese encoding (Note that you must use an underscore character (_), not a hyphen (-) in the name in CFML attributes.) - (same as MS932) - EUC-KR DBCS 16-bit Korean encoding - UCS-2 DBCS Two-byte Unicode encoding - UTF-8 MBCS Multibyte Unicode encoding. ASCII is 7-bit; non-ASCII characters used in European and many Middle Eastern languages are two-byte; and most Asian characters are three-byte - - - - ExtRst data is defined as: - - ID (WORD): 0x0001 - Length (WORD) - unknown (WORD) - Flag (WORD) - Number of relation informations between Katakana and Kanji (WORD) - Number of characters #1 (WORD) - Number of characters #2 (WORD) - >>> always the same ? - Katakana characters - Relation informations between Katakana and Kanji (6 bytes each) - - - -mdImeMode (8 bits): An unsigned integer that specifies the Input Method Editor (IME) mode - enforced by this data validation. This value is only used when the input language is one of the - following languages: - -1. Chinese Simplified (Locale ID = 2052) -2. Chinese Traditional (Locale ID = 1028) -3. Japanese (Locale ID = 1041) -4. Korean (Locale ID = 1042) + + Japanese Language Encoding + Encoding Other Names Vendor/Standard Body Other Rosette Names + CCSID 1027 EBCDIK Microsoft & IBM CCSID-1027, CCSID1027 + CCSID 290 EBCDIK Microsoft & IBM CCSID-290, CCSID290 + CCSID 930 IBM CCSID-930, CCSID930 + CCSID 939 IBM CCSID-939, CCSID939 + CCSID 942 Microsoft & IBM CCSID-942, CCSID942 + CP10001 Macintosh Japanese Microsoft & IBM CP10001 + CP20290 (full/half width Latin & halfwidth katakana) Microsoft & IBM CP20290 + CP21027 (halfwidth Latin, halfwidth katakana&private use) Microsoft & IBM CP21027 + EUC-JP Unix EUC-JP, EUC-J + EUC-JP-JISROMAN Unix EUC-JP-JISROMAN + ISO 2022-JP International or National Standard ISO-2022-JP + JapaneseAutoDetect For encodings, see JapaneseAutodetect Rosette Autodetect JapaneseAutoDetect + + Encoding Other Names Vendor/Standard Body Other Rosette Names + JIS_X_0201 HalfWidthKatakana International or National Standard JIS_X_0201, IBM897 + JIS_X_0208 International or National Standard JIS_X_0208 + Shift-JISMS MS_Kanji, CP932 Microsoft & IBM Shift-JIS, SJIS + Shift_JIS-2004 ShiftJISX0213 Microsoft & IBM Shift-Jis2004, Shift_JISX0213,Shift-X + Shift-JIS78 Shift-JIS without MS/IBM extensions Unix/Macintosh Shift-JIS78, SJIS78 + + Chinese Language Encoding + Encoding Other Names Vendor/Standard Body Other Rosette Names + ChineseAutoDetect For encodings, see ChineseAutodetect Rosette Autodetect ChineseAutoDetect + HKSCS International or National Standard HKSCS + ISO 2022-CN International or National Standard ISO-2022-CN + GB 18030 International or National Standard GB18030 + Chinese, Simplified + CCSID 935 IBM CCSID-935, CCSID935 + EUC-CN GB2312, EUC-SC Unix GB2312 + GB2312 EUC-CN, EUC-SC International or National Standard GB2312 + HZ-GB-2312 HZ-GB-2312 International or National Standard HZ, HZ-GB-2312 + CP936 GBK Microsoft & IBM CP936, GBK + MacChineseSimplified Macintosh MacChineseSimplified + Chinese, Traditional + CCSID 937 IBM CCSID-937, CCSID937 + CNS-11643-1986 EUC-TW International or National Standard CNS-11643-1986 + CNS-11643-1992 EUC-TW International or National Standard CNS-11643, CNS-11643-1992 + EUC-TW CNS-11643-1986, CNS-11643-1992 Unix CNS-11643, CNS-11643-1992 + GB12345 International or National Standard GB12345 + Big5 International or National Standard Big5 + Big5+ International or National Standard Big5+, Big5Plus + CP10002 Macintosh Traditional Chinese Microsoft & IBM CP10002 + CP950 Microsoft & IBM CP950 + MacChineseTraditional Macintosh MacChineseTraditional + + The CCSID in the iSeries objects must be 935 (Simplified Chinese) + The code page in the PC for Simplified Chinese is 1388 + The Data Type in the DSPF/PRTF must be O (Other) + + more info: + Shift_JIS DBCS 16-bit Japanese encoding (Note that you must use an underscore character (_), not a hyphen (-) in the name in CFML attributes.) + (same as MS932) + EUC-KR DBCS 16-bit Korean encoding + UCS-2 DBCS Two-byte Unicode encoding + UTF-8 MBCS Multibyte Unicode encoding. ASCII is 7-bit; non-ASCII characters used in European and many Middle Eastern languages are two-byte; and most Asian characters are three-byte + + + + ExtRst data is defined as: + + ID (WORD): 0x0001 + Length (WORD) + unknown (WORD) + Flag (WORD) + Number of relation informations between Katakana and Kanji (WORD) + Number of characters #1 (WORD) + Number of characters #2 (WORD) + >>> always the same ? + Katakana characters + Relation informations between Katakana and Kanji (6 bytes each) + + + + mdImeMode (8 bits): An unsigned integer that specifies the Input Method Editor (IME) mode + enforced by this data validation. This value is only used when the input language is one of the + following languages: + + 1. Chinese Simplified (Locale ID = 2052) + 2. Chinese Traditional (Locale ID = 1028) + 3. Japanese (Locale ID = 1041) + 4. Korean (Locale ID = 1042) The input for the cell can be restricted to specific sets of characters, as specified by the value of mdImeMode. MUST be a value from the following table: - Value Meaning - 0x00 No Control - 0x01 On - 0x02 Off (English) - 0x04 Hiragana - 0x05 wide katakana - 0x06 narrow katakana - 0x07 Full-width alphanumeric - 0x08 Half-width alphanumeric - 0x09 Full-width hangul - 0x0A Half-width hangul - - -ExtRst: -Asian phonetic text5 (Ruby) can be used to provide extended phonetic information for specific characters or words. It -appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or -entire words. -Offset Size Contents -0 2 Unknown identifier 0001H -2 2 Size of the following data (10 + 2∙ln + 6∙np) -4 2 Index to FONT record (➜5.45) used for the Asian phonetic text -6 2 Additional settings for the Asian phonetic text: - Bit Mask Contents - 1-0 0003H Type of Japanese phonetic text (type): - 002 = Katakana (narrow) 102 = Hiragana - 012 = Katakana (wide) - 3-2 000CH Alignment of all portions of the Asian phonetic text (align): - 002 = Not specified (Japanese only) 102 = Centered - 012 = Left (Top for vertical text) 112 = Distributed - 5-4 0030H 112 (always set) - 8 2 Number of portions the Asian phonetic text is broken into (np). - If np = 0, the Asian phonetic text refers to the entire cell text. - 10 2 Total length of the following Asian phonetic text (number of characters, ln) - 12 2 Repeated total length of the text - 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters. - Note: If ln = 0, this field is not empty but contains 0000H. - 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each - structure contains the following fields: - Offset Size Contents - 0 2 First character in the Asian phonetic text of this portion (cpa) - 2 2 First character of the main text belonging to this portion (cpm) - 4 2 Number of characters in main text belonging to this portion (ccm) - - Example: Japanese word Tokyo (東京) with added hiragana (とうきょう)6. The following examples show the - contents of the important fields of the Asian Phonetic Settings Block. - Example 1: Hiragana centered over the entire word: - とうきょう - 東京 - type = 102 (hiragana) - align = 102 (centered) - np = 0 (no portions, hiragana refers to entire text) - ln = 5 (length of entire hiragana text) - No portion structures - - -Asian phonetic text5 (Ruby) can be used to provide extended phonetic information for specific characters or words. It -appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or -entire words. -Offset Size Contents -0 2 Unknown identifier 0001H -2 2 Size of the following data (10 + 2∙ln + 6∙np) -4 2 Index to FONT record (➜5.45) used for the Asian phonetic text -6 2 Additional settings for the Asian phonetic text: -Bit Mask Contents -1-0 0003H Type of Japanese phonetic text (type): -002 = Katakana (narrow) 102 = Hiragana -012 = Katakana (wide) -3-2 000CH Alignment of all portions of the Asian phonetic text (align): -002 = Not specified (Japanese only) 102 = Centered -012 = Left (Top for vertical text) 112 = Distributed -5-4 0030H 112 (always set) -8 2 Number of portions the Asian phonetic text is broken into (np). -If np = 0, the Asian phonetic text refers to the entire cell text. -10 2 Total length of the following Asian phonetic text (number of characters, ln) -12 2 Repeated total length of the text -14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters. -Note: If ln = 0, this field is not empty but contains 0000H. -14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each -structure contains the following fields: -Offset Size Contents -0 2 First character in the Asian phonetic text of this portion (cpa) -2 2 First character of the main text belonging to this portion (cpm) -4 2 Number of characters in main text belonging to this portion (ccm) -5 */ + Value Meaning + 0x00 No Control + 0x01 On + 0x02 Off (English) + 0x04 Hiragana + 0x05 wide katakana + 0x06 narrow katakana + 0x07 Full-width alphanumeric + 0x08 Half-width alphanumeric + 0x09 Full-width hangul + 0x0A Half-width hangul + + + ExtRst: + Asian phonetic text5 (Ruby) can be used to provide extended phonetic information for specific characters or words. It + appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or + entire words. + Offset Size Contents + 0 2 Unknown identifier 0001H + 2 2 Size of the following data (10 + 2∙ln + 6∙np) + 4 2 Index to FONT record (➜5.45) used for the Asian phonetic text + 6 2 Additional settings for the Asian phonetic text: + Bit Mask Contents + 1-0 0003H Type of Japanese phonetic text (type): + 002 = Katakana (narrow) 102 = Hiragana + 012 = Katakana (wide) + 3-2 000CH Alignment of all portions of the Asian phonetic text (align): + 002 = Not specified (Japanese only) 102 = Centered + 012 = Left (Top for vertical text) 112 = Distributed + 5-4 0030H 112 (always set) + 8 2 Number of portions the Asian phonetic text is broken into (np). + If np = 0, the Asian phonetic text refers to the entire cell text. + 10 2 Total length of the following Asian phonetic text (number of characters, ln) + 12 2 Repeated total length of the text + 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters. + Note: If ln = 0, this field is not empty but contains 0000H. + 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each + structure contains the following fields: + Offset Size Contents + 0 2 First character in the Asian phonetic text of this portion (cpa) + 2 2 First character of the main text belonging to this portion (cpm) + 4 2 Number of characters in main text belonging to this portion (ccm) + + Example: Japanese word Tokyo (東京) with added hiragana (とうきょう)6. The following examples show the + contents of the important fields of the Asian Phonetic Settings Block. + Example 1: Hiragana centered over the entire word: + とうきょう + 東京 + type = 102 (hiragana) + align = 102 (centered) + np = 0 (no portions, hiragana refers to entire text) + ln = 5 (length of entire hiragana text) + No portion structures + + + Asian phonetic text5 (Ruby) can be used to provide extended phonetic information for specific characters or words. It + appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or + entire words. + Offset Size Contents + 0 2 Unknown identifier 0001H + 2 2 Size of the following data (10 + 2∙ln + 6∙np) + 4 2 Index to FONT record (➜5.45) used for the Asian phonetic text + 6 2 Additional settings for the Asian phonetic text: + Bit Mask Contents + 1-0 0003H Type of Japanese phonetic text (type): + 002 = Katakana (narrow) 102 = Hiragana + 012 = Katakana (wide) + 3-2 000CH Alignment of all portions of the Asian phonetic text (align): + 002 = Not specified (Japanese only) 102 = Centered + 012 = Left (Top for vertical text) 112 = Distributed + 5-4 0030H 112 (always set) + 8 2 Number of portions the Asian phonetic text is broken into (np). + If np = 0, the Asian phonetic text refers to the entire cell text. + 10 2 Total length of the following Asian phonetic text (number of characters, ln) + 12 2 Repeated total length of the text + 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters. + Note: If ln = 0, this field is not empty but contains 0000H. + 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each + structure contains the following fields: + Offset Size Contents + 0 2 First character in the Asian phonetic text of this portion (cpa) + 2 2 First character of the main text belonging to this portion (cpm) + 4 2 Number of characters in main text belonging to this portion (ccm) + 5 */ } /** -string 1= - [-115, -1, 111, -1, 107, -1, 102, -1, 114, 0, 108, 0, 100, 0, 33, 0] - phonetic - [1, 0, 12, 0, 1, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0] - new String(stringarray, "UTF-16LE") - (java.lang.String) ヘッォヲrld! -String 3= - [-40, 48, -61, 48, -87, 48, -14, 48, 82, -1, 76, -1, 68, -1, 1, -1] - phonetic - [1, 0, 12, 0, 1, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0] - new String(stringarray, "UTF-16LE") - (java.lang.String) ヘッォヲrld! - - -Excel 2007 Supported Character Sets: -0x00 Specifies the ANSI character set. -0x01 Specifies the default character set. -0x02 Specifies the Symbol character set. -0x4D Specifies a Macintosh (Standard Roman) character set. -0x80 Specifies the JIS character set. -0x81 Specifies the Hangul character set. -0x82 Specifies a Johab character set. -0x86 Specifies the GB-2312 character set. -0x88 Specifies the Chinese Big Five character set. -0xA1 Specifies a Greek character set. -0xA2 Specifies a Turkish character set. -0xA3 Specifies a Vietnamese character set. -0xB1 Specifies a Hebrew character set. -0xB2 Specifies an Arabic character set. -0xBA Specifies a Baltic character set. -0xCC Specifies a Russian character set. -0xDE Specifies a Thai character set. -0xEE Specifies an Eastern European character set. -0xFF Specifies an OEM character set not defined by this Office Open XML Standard. -Any other value Application-defined, may be ignored. -*/ + string 1= + [-115, -1, 111, -1, 107, -1, 102, -1, 114, 0, 108, 0, 100, 0, 33, 0] + phonetic + [1, 0, 12, 0, 1, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0] + new String(stringarray, "UTF-16LE") + (java.lang.String) ヘッォヲrld! + String 3= + [-40, 48, -61, 48, -87, 48, -14, 48, 82, -1, 76, -1, 68, -1, 1, -1] + phonetic + [1, 0, 12, 0, 1, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0] + new String(stringarray, "UTF-16LE") + (java.lang.String) ヘッォヲrld! + + + Excel 2007 Supported Character Sets: + 0x00 Specifies the ANSI character set. + 0x01 Specifies the default character set. + 0x02 Specifies the Symbol character set. + 0x4D Specifies a Macintosh (Standard Roman) character set. + 0x80 Specifies the JIS character set. + 0x81 Specifies the Hangul character set. + 0x82 Specifies a Johab character set. + 0x86 Specifies the GB-2312 character set. + 0x88 Specifies the Chinese Big Five character set. + 0xA1 Specifies a Greek character set. + 0xA2 Specifies a Turkish character set. + 0xA3 Specifies a Vietnamese character set. + 0xB1 Specifies a Hebrew character set. + 0xB2 Specifies an Arabic character set. + 0xBA Specifies a Baltic character set. + 0xCC Specifies a Russian character set. + 0xDE Specifies a Thai character set. + 0xEE Specifies an Eastern European character set. + 0xFF Specifies an OEM character set not defined by this Office Open XML Standard. + Any other value Application-defined, may be ignored. + */ diff --git a/src/main/java/io/starter/formats/XLS/Usersviewbegin.java b/src/main/java/io/starter/formats/XLS/Usersviewbegin.java index 62afda6..1a1153b 100644 --- a/src/main/java/io/starter/formats/XLS/Usersviewbegin.java +++ b/src/main/java/io/starter/formats/XLS/Usersviewbegin.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,34 +26,34 @@ import io.starter.toolkit.Logger; -/** USERSVIEWBEGIN: Custom View Settings (1AAh)
                - - USERSVIEWBEGIN describes the settings for a custom view for the sheet - -

                -    offset  name            size    contents
                -    ---
                -    4       guid            16      GID for custom view
                -    20      iTabid          4       Tab index for the sheet (1-based)
                -    24      wScale          4       Window Zoom
                -    28      icv             4       Index to color val
                -    32      pnnSel          4       Pane number of active pane
                -    36      grbit           4       Option flags
                -    40      refTopLeft      8       Ref struct describing the visible area of top left pane
                -    48      operNum         16      array of 2 floats specifying vert/horiz pane split
                -    64      colRPane        2       first visible right pane col
                -    66      rwBPane         2       first visible bottom pane col
                -
                -    

                -*/ -public final class Usersviewbegin extends io.starter.formats.XLS.XLSRecord -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1877650235927064991L; - // record fields +/** + * USERSVIEWBEGIN: Custom View Settings (1AAh)
                + *

                + * USERSVIEWBEGIN describes the settings for a custom view for the sheet + * + *

                + * offset  name            size    contents
                + * ---
                + * 4       guid            16      GID for custom view
                + * 20      iTabid          4       Tab index for the sheet (1-based)
                + * 24      wScale          4       Window Zoom
                + * 28      icv             4       Index to color val
                + * 32      pnnSel          4       Pane number of active pane
                + * 36      grbit           4       Option flags
                + * 40      refTopLeft      8       Ref struct describing the visible area of top left pane
                + * 48      operNum         16      array of 2 floats specifying vert/horiz pane split
                + * 64      colRPane        2       first visible right pane col
                + * 66      rwBPane         2       first visible bottom pane col
                + *
                + * 

                + */ +public final class Usersviewbegin extends io.starter.formats.XLS.XLSRecord { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1877650235927064991L; + // record fields private int tabid = -1; private int wScale = -1; private int icv = -1; @@ -61,102 +61,100 @@ public final class Usersviewbegin extends io.starter.formats.XLS.XLSRecord private int grbit = -1; //private float operNum1 = 0; //private float operNum2 = 0; - //private short colRPane = 0; + //private short colRPane = 0; //private short rwBPane = 0; - + //66 Long! - //byte[] RECBYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0}; - /** Constructor for a Usersviewbegin to be made on the fly. - * + //byte[] RECBYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0}; + + /** + * Constructor for a Usersviewbegin to be made on the fly. */ - Usersviewbegin(){ - // TODO: init Usersviewbegin - this(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0}); + Usersviewbegin() { + // TODO: init Usersviewbegin + this(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); } - - Usersviewbegin(byte[] b){ - setData(b); - setOpcode(USERSVIEWBEGIN); - setLength((short)6); - this.init(); - } + + Usersviewbegin(byte[] b) { + setData(b); + setOpcode(USERSVIEWBEGIN); + setLength((short) 6); + this.init(); + } + // grbit fields private boolean fDspGutsSv = false; // true if outline symbols are displayed - + // TODO: implement this class - public void init(){ + public void init() { super.init(); short num1 = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); short num2 = ByteTools.readShort(this.getByteAt(18), this.getByteAt(19)); - tabid = ByteTools.readInt(num2,num1); - num1 = ByteTools.readShort(this.getByteAt(20), this.getByteAt(21)); - num2 = ByteTools.readShort(this.getByteAt(22), this.getByteAt(23)); - wScale = ByteTools.readInt(num1, num2); - num1 = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); - num2 = ByteTools.readShort(this.getByteAt(26), this.getByteAt(27)); - icv = ByteTools.readInt(num1, num2); - num1 = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); - num2 = ByteTools.readShort(this.getByteAt(26), this.getByteAt(27)); - pnnSel = ByteTools.readInt(num1, num2); - num1 = ByteTools.readShort(this.getByteAt(28), this.getByteAt(29)); - num2 = ByteTools.readShort(this.getByteAt(30), this.getByteAt(31)); - grbit = ByteTools.readInt(num1, num2); - this.decodeGrbit(); - if(DEBUGLEVEL > 3)Logger.logInfo("Usersviewbegin Tab Index: " + tabid); + tabid = ByteTools.readInt(num2, num1); + num1 = ByteTools.readShort(this.getByteAt(20), this.getByteAt(21)); + num2 = ByteTools.readShort(this.getByteAt(22), this.getByteAt(23)); + wScale = ByteTools.readInt(num1, num2); + num1 = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); + num2 = ByteTools.readShort(this.getByteAt(26), this.getByteAt(27)); + icv = ByteTools.readInt(num1, num2); + num1 = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); + num2 = ByteTools.readShort(this.getByteAt(26), this.getByteAt(27)); + pnnSel = ByteTools.readInt(num1, num2); + num1 = ByteTools.readShort(this.getByteAt(28), this.getByteAt(29)); + num2 = ByteTools.readShort(this.getByteAt(30), this.getByteAt(31)); + grbit = ByteTools.readInt(num1, num2); + this.decodeGrbit(); + if (DEBUGLEVEL > 3) Logger.logInfo("Usersviewbegin Tab Index: " + tabid); } - + /** * decodeGrbit does masking to determine the grbit settings - * */ - private void decodeGrbit(){ - if ((grbit & 0x00000010) == 0x00000010){ - fDspGutsSv = true; - }else{ - fDspGutsSv = false; - } - + private void decodeGrbit() { + fDspGutsSv = (grbit & 0x00000010) == 0x00000010; + } - + /** * updateGrbit looks at all the grbit variables and rebuilds the grbit * field based off those. */ - private void updateGrbit(){ - if (fDspGutsSv){ - grbit = (0x00000010 | grbit); - }else{ - grbit = (0xFFFFFFEF & grbit); - } - // update the record bytes - byte[] b = this.getData(); - byte grbytes[] = ByteTools.cLongToLEBytes(grbit); - System.arraycopy(grbytes, 0, b, 28, 4); - this.setData(b); + private void updateGrbit() { + if (fDspGutsSv) { + grbit = (0x00000010 | grbit); + } else { + grbit = (0xFFFFFFEF & grbit); + } + // update the record bytes + byte[] b = this.getData(); + byte[] grbytes = ByteTools.cLongToLEBytes(grbit); + System.arraycopy(grbytes, 0, b, 28, 4); + this.setData(b); } - + /** * Checks to see if outlines are being displayed on the worksheet + * * @return fDspGutsSv */ - public boolean getDisplayOutlines(){ - return fDspGutsSv; + public boolean getDisplayOutlines() { + return fDspGutsSv; } - + /** * Sets whether outlines are displayed on the worksheet. Should be set to true * when using any of the 'grouping' methods. + * * @param disp */ - public void setDisplayOutlines(boolean disp){ - fDspGutsSv = disp; - updateGrbit(); + public void setDisplayOutlines(boolean disp) { + fDspGutsSv = disp; + updateGrbit(); } /* 20 iTabid 4 Tab index for the sheet (1-based) */ - - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Usersviewend.java b/src/main/java/io/starter/formats/XLS/Usersviewend.java index a7829ca..76b1486 100644 --- a/src/main/java/io/starter/formats/XLS/Usersviewend.java +++ b/src/main/java/io/starter/formats/XLS/Usersviewend.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,37 +25,36 @@ import io.starter.toolkit.ByteTools; -/** USERSVIEWEND: End Custom View Settings (1ABh)
                - - USERSVIEWEND marks the end of a custom view for the sheet - -

                -    offset  name            size    contents
                -    ---
                -    4       fValid          2       = 1 if the settings saved are valid
                -
                -    

                +/** + * USERSVIEWEND: End Custom View Settings (1ABh)
                + *

                + * USERSVIEWEND marks the end of a custom view for the sheet + * + *

                + * offset  name            size    contents
                + * ---
                + * 4       fValid          2       = 1 if the settings saved are valid
                + *
                + * 

                + */ +public final class Usersviewend extends io.starter.formats.XLS.XLSRecord { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -3120417369791123931L; + int fValid = -1; -*/ -public final class Usersviewend extends io.starter.formats.XLS.XLSRecord -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3120417369791123931L; - int fValid = -1; - - /** return whether the settings - for this user view are valid - */ - public boolean isValid(){ - if(fValid == 1)return true; - return false; + /** + * return whether the settings + * for this user view are valid + */ + public boolean isValid() { + return fValid == 1; } - - public void init(){ - super.init(); - fValid = (int) ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); + + public void init() { + super.init(); + fValid = (int) ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/UsrExcl.java b/src/main/java/io/starter/formats/XLS/UsrExcl.java index 12abe9c..8fecf15 100644 --- a/src/main/java/io/starter/formats/XLS/UsrExcl.java +++ b/src/main/java/io/starter/formats/XLS/UsrExcl.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,14 +23,14 @@ package io.starter.formats.XLS; /** - * + * */ public class UsrExcl extends UnencryptedXLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -9123474758922477791L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -9123474758922477791L; } diff --git a/src/main/java/io/starter/formats/XLS/VCenter.java b/src/main/java/io/starter/formats/XLS/VCenter.java index 7682aa3..c638861 100644 --- a/src/main/java/io/starter/formats/XLS/VCenter.java +++ b/src/main/java/io/starter/formats/XLS/VCenter.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,29 +22,29 @@ */ package io.starter.formats.XLS; -/** Record specifying whether the sheet is to be centered vertically +/** + * Record specifying whether the sheet is to be centered vertically * when printed. - * */ public class VCenter -extends XLSRecord { - private static final long serialVersionUID = -3649192673573344145L; + extends XLSRecord { + private static final long serialVersionUID = -3649192673573344145L; - public void init() { - super.init(); - } - - public void setSheet (Sheet sheet ) { - super.setSheet( sheet ); - ((Boundsheet)sheet).addPrintRec( this ); - } - - public boolean isVCenter() { - return (getData()[0] & 0x01) == 0x01; - } - - public void setVCenter (boolean center) { - if (center) getData()[0] |= 0x01; - else getData()[0] &= ~0x01; - } + public void init() { + super.init(); + } + + public void setSheet(Sheet sheet) { + super.setSheet(sheet); + ((Boundsheet) sheet).addPrintRec(this); + } + + public boolean isVCenter() { + return (getData()[0] & 0x01) == 0x01; + } + + public void setVCenter(boolean center) { + if (center) getData()[0] |= 0x01; + else getData()[0] &= ~0x01; + } } diff --git a/src/main/java/io/starter/formats/XLS/ValidationException.java b/src/main/java/io/starter/formats/XLS/ValidationException.java index 1a92816..cc07fbc 100644 --- a/src/main/java/io/starter/formats/XLS/ValidationException.java +++ b/src/main/java/io/starter/formats/XLS/ValidationException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,41 +25,40 @@ /** * Validation Exceptions are thrown when a cell value is set to a value that does not pass * validity of an Excel validation record affecting said cell - * - * - * */ public class ValidationException -extends Exception { + extends Exception { private static final long serialVersionUID = -6448974788123912538L; - + private String errorTitle = ""; private String errorText = ""; - - - public ValidationException(String eTitle, String eText){ + + + public ValidationException(String eTitle, String eText) { super(); errorTitle = eTitle; errorText = eText; } - - /** Returns the title of the validation error dialog. */ + + /** + * Returns the title of the validation error dialog. + */ public String getTitle() { - return errorTitle; + return errorTitle; } - - /** Returns the body of the validation error dialog. */ + + /** + * Returns the body of the validation error dialog. + */ public String getText() { - return errorText; + return errorText; } - - public String getMessage() - { + + public String getMessage() { return this.toString(); } - - public String toString() - { + + public String toString() { return errorTitle + ": " + errorText; } diff --git a/src/main/java/io/starter/formats/XLS/Window1.java b/src/main/java/io/starter/formats/XLS/Window1.java index 90b2ea8..01b967f 100644 --- a/src/main/java/io/starter/formats/XLS/Window1.java +++ b/src/main/java/io/starter/formats/XLS/Window1.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,23 +25,24 @@ import io.starter.toolkit.ByteTools; -/** WINDOW1 0x3D: Contains window attributes for a Workbook.
                -

                -    offset  name        size    contents
                -    ---    
                -    4       xWn         2       Horizontal Position of the window
                -    6       yWn         2       Vertical Position of the window
                -    8       dxWn        2       Width of the window
                -    10      dyWn        2       Height of the window
                -    12      grbit       2       Option Flags
                -    14      itabCur     2       Index of the selected workbook tab (0 based)
                -    16      itabFirst   2       Index of the first displayed workbook tab (0 based)
                -    18      ctabSel     2       Number of workbook tabs that are selected
                -    20      wTabRatio   2       Ratio of the width of the workbook tabs to the width of 
                -                                the horizontal scroll bar; to obtain the ratio, convert to 
                -                                decimal and then divide by 1000
                -    

                - +/** + * WINDOW1 0x3D: Contains window attributes for a Workbook.
                + *

                + * offset  name        size    contents
                + * ---
                + * 4       xWn         2       Horizontal Position of the window
                + * 6       yWn         2       Vertical Position of the window
                + * 8       dxWn        2       Width of the window
                + * 10      dyWn        2       Height of the window
                + * 12      grbit       2       Option Flags
                + * 14      itabCur     2       Index of the selected workbook tab (0 based)
                + * 16      itabFirst   2       Index of the first displayed workbook tab (0 based)
                + * 18      ctabSel     2       Number of workbook tabs that are selected
                + * 20      wTabRatio   2       Ratio of the width of the workbook tabs to the width of
                + * the horizontal scroll bar; to obtain the ratio, convert to
                + * decimal and then divide by 1000
                + * 

                + * * @see WorkBook * @see BOUNDSHEET * @see INDEX @@ -51,69 +52,71 @@ * @see XLSRecord */ -public class Window1 extends io.starter.formats.XLS.XLSRecord{ - +public class Window1 extends io.starter.formats.XLS.XLSRecord { + /** - * - */ - private static final long serialVersionUID = 2770922305028029883L; - short xWn = 0; - short yWn = 0; - short dxWn = 0; - short dyWn = 0; - short grbit = 0; - short itabCur = 0; - short itabFirst = 0; - short ctabSel = 0; - short wTabRatio = 0; + * + */ + private static final long serialVersionUID = 2770922305028029883L; + short xWn = 0; + short yWn = 0; + short dxWn = 0; + short dyWn = 0; + short grbit = 0; + short itabCur = 0; + short itabFirst = 0; + short ctabSel = 0; + short wTabRatio = 0; Boundsheet mybs = null; - - public int getCurrentTab(){ + + public int getCurrentTab() { return itabCur; } - + /** * Sets the current tab that is displayed on opening. * Note, this is not really the same thing as "selected". * The selected parameter is from the Window2 record. As we - * don't really have much need to select more than one sheet - * on output, this method just delselects every other sheet than + * don't really have much need to select more than one sheet + * on output, this method just delselects every other sheet than * the one that is passed in, and selects that one in it's Window2 + * * @param bs */ - public void setCurrentTab(Boundsheet bs){ + public void setCurrentTab(Boundsheet bs) { mybs = bs; int t = mybs.getSheetNum(); Boundsheet[] bounds = this.getWorkBook().getWorkSheets(); - for (int i=0;i. @@ -26,32 +26,33 @@ import io.starter.toolkit.ByteTools; -/** WINDOW2 0x23E: Contains window attributes for a Sheet.
                -

                -    offset  name        size    contents
                -    ---    
                -    4       grbit       2       Option Flags
                -    6       rwTop       2       Top row visible in the window
                -    8       colLeft     2       Leftmost visible col in window
                -    10      icvHdr      4       Index to color val for row/col headings & grids
                -    14      wScaleSLV   2       Zoom mag in page break preview
                -    16      wScaleNorm  2       Zoom mag in Normal preview
                -    18      reserved    4       
                -   
                -   grbit Option flags
                -	0 0001H 0 = Show formula results 1 = Show formulas
                -	1 0002H 0 = Do not show grid lines 1 = Show grid lines
                -	2 0004H 0 = Do not show sheet headers 1 = Show sheet headers
                -	3 0008H 0 = Panes are not frozen 1 = Panes are frozen (freeze)
                -	4 0010H 0 = Show zero values as empty cells 1 = Show zero values
                -	5 0020H 0 = Manual grid line colour 1 = Automatic grid line colour
                -	6 0040H 0 = Columns from left to right 1 = Columns from right to left
                -	7 0080H 0 = Do not show outline symbols 1 = Show outline symbols
                -	8 0100H 0 = Keep splits if pane freeze is removed 1 = Remove splits if pane freeze is removed
                -	9 0200H 0 = Sheet not selected 1 = Sheet selected (BIFF5-BIFF8)
                -	10 0400H 0 = Sheet not visible 1 = Sheet visible (BIFF5-BIFF8)
                -	11 0800H 0 = Show in normal view 1 = Show in page break preview (BIFF8)    

                - +/** + * WINDOW2 0x23E: Contains window attributes for a Sheet.
                + *

                + * offset  name        size    contents
                + * ---
                + * 4       grbit       2       Option Flags
                + * 6       rwTop       2       Top row visible in the window
                + * 8       colLeft     2       Leftmost visible col in window
                + * 10      icvHdr      4       Index to color val for row/col headings & grids
                + * 14      wScaleSLV   2       Zoom mag in page break preview
                + * 16      wScaleNorm  2       Zoom mag in Normal preview
                + * 18      reserved    4
                + *
                + * grbit Option flags
                + * 0 0001H 0 = Show formula results 1 = Show formulas
                + * 1 0002H 0 = Do not show grid lines 1 = Show grid lines
                + * 2 0004H 0 = Do not show sheet headers 1 = Show sheet headers
                + * 3 0008H 0 = Panes are not frozen 1 = Panes are frozen (freeze)
                + * 4 0010H 0 = Show zero values as empty cells 1 = Show zero values
                + * 5 0020H 0 = Manual grid line colour 1 = Automatic grid line colour
                + * 6 0040H 0 = Columns from left to right 1 = Columns from right to left
                + * 7 0080H 0 = Do not show outline symbols 1 = Show outline symbols
                + * 8 0100H 0 = Keep splits if pane freeze is removed 1 = Remove splits if pane freeze is removed
                + * 9 0200H 0 = Sheet not selected 1 = Sheet selected (BIFF5-BIFF8)
                + * 10 0400H 0 = Sheet not visible 1 = Sheet visible (BIFF5-BIFF8)
                + * 11 0800H 0 = Show in normal view 1 = Show in page break preview (BIFF8)    

                + * * @see WorkBook * @see BOUNDSHEET * @see INDEX @@ -61,178 +62,193 @@ * @see XLSRecord */ -public class Window2 extends io.starter.formats.XLS.XLSRecord -{ +public class Window2 extends io.starter.formats.XLS.XLSRecord { /** - * - */ - private static final long serialVersionUID = -8316509425117672619L; - int grbit = -1; - int rwTop = -1; - int colLeft = -1; - int icvHdr = -1; - int wScaleSLV = -1; - int wScaleNorm = -1; + * + */ + private static final long serialVersionUID = -8316509425117672619L; + int grbit = -1; + int rwTop = -1; + int colLeft = -1; + int icvHdr = -1; + int wScaleSLV = -1; + int wScaleNorm = -1; //20060308 KSC: Added for get/set access to Window2 options - static final int BITMASK_SHOWFORMULARESULTS= 0x0001; - static final int BITMASK_SHOWGRIDLINES= 0x0002; - static final int BITMASK_SHOWSHEETHEADERS= 0x0004; - static final int BITMASK_FREEZEPANES= 0x0008; - static final int BITMASK_SHOWZEROVALUES= 0x0010; - static final int BITMASK_GRIDLINECOLOR= 0x0020; - static final int BITMASK_COLUMNDIRECTION= 0x0040; - static final int BITMASK_SHOWOUTLINESYMBOLS= 0x0080; - static final int BITMASK_KEEPSPLITS= 0x0100; - static final int BITMASK_SHEETSELECTED= 0x0200; - static final int BITMASK_SHEETVISIBLE= 0x0400; - static final int BITMASK_SHOWINPRINTPREVIEW= 0x0800; - - public void init(){ + static final int BITMASK_SHOWFORMULARESULTS = 0x0001; + static final int BITMASK_SHOWGRIDLINES = 0x0002; + static final int BITMASK_SHOWSHEETHEADERS = 0x0004; + static final int BITMASK_FREEZEPANES = 0x0008; + static final int BITMASK_SHOWZEROVALUES = 0x0010; + static final int BITMASK_GRIDLINECOLOR = 0x0020; + static final int BITMASK_COLUMNDIRECTION = 0x0040; + static final int BITMASK_SHOWOUTLINESYMBOLS = 0x0080; + static final int BITMASK_KEEPSPLITS = 0x0100; + static final int BITMASK_SHEETSELECTED = 0x0200; + static final int BITMASK_SHEETVISIBLE = 0x0400; + static final int BITMASK_SHOWINPRINTPREVIEW = 0x0800; + + public void init() { super.init(); - short s1,s2; - - grbit = ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); - rwTop = ByteTools.readShort(this.getByteAt(2),this.getByteAt(3)); - colLeft = ByteTools.readShort(this.getByteAt(4),this.getByteAt(5)); - - s1 = ByteTools.readShort(this.getByteAt(6),this.getByteAt(7)); - s2 = ByteTools.readShort(this.getByteAt(8),this.getByteAt(9)); - icvHdr = ByteTools.readInt(s1,s2); - + short s1, s2; + + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + rwTop = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + colLeft = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + + s1 = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + s2 = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + icvHdr = ByteTools.readInt(s1, s2); + // the following do not necessarily exist in VB-manipulated windows - if(this.getLength() > 10){ - wScaleSLV = ByteTools.readShort(this.getByteAt(10),this.getByteAt(11)); - wScaleNorm = ByteTools.readShort(this.getByteAt(12),this.getByteAt(13)); + if (this.getLength() > 10) { + wScaleSLV = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); + wScaleNorm = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); } } - - void setSelected(boolean b){ - if (b) - this.getData()[1] |= 0x2; - else - this.getData()[1] &= 0xFD; - grbit = ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); + + void setSelected(boolean b) { + if (b) + this.getData()[1] |= 0x2; + else + this.getData()[1] &= 0xFD; + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); } - + /** * returns the String Address of first visible cell on the sheet + * * @return String */ public String getTopLeftCell() { - return ExcelTools.formatLocation(new int[] {rwTop, colLeft}); + return ExcelTools.formatLocation(new int[]{rwTop, colLeft}); } - + // add get/set for Window2 options public void setGrbit() { byte[] data = this.getData(); - byte[] b= ByteTools.shortToLEBytes((short) grbit); + byte[] b = ByteTools.shortToLEBytes((short) grbit); System.arraycopy(b, 0, data, 0, 2); this.setData(data); - + } - + public boolean getShowFormulaResults() { - return ((grbit & BITMASK_SHOWFORMULARESULTS)==BITMASK_SHOWFORMULARESULTS); + return ((grbit & BITMASK_SHOWFORMULARESULTS) == BITMASK_SHOWFORMULARESULTS); } + public void setShowFormulaResults(boolean b) { - if (b) - grbit= grbit | BITMASK_SHOWFORMULARESULTS; - else - grbit= grbit & ~BITMASK_SHOWFORMULARESULTS; - this.setGrbit(); - } - public boolean getShowGridlines() { - return ((grbit & BITMASK_SHOWGRIDLINES)==BITMASK_SHOWGRIDLINES); - } + if (b) + grbit = grbit | BITMASK_SHOWFORMULARESULTS; + else + grbit = grbit & ~BITMASK_SHOWFORMULARESULTS; + this.setGrbit(); + } + + public boolean getShowGridlines() { + return ((grbit & BITMASK_SHOWGRIDLINES) == BITMASK_SHOWGRIDLINES); + } + public void setShowGridlines(boolean b) { - if (b) - grbit= grbit | BITMASK_SHOWGRIDLINES; - else - grbit= grbit & ~BITMASK_SHOWGRIDLINES; - this.setGrbit(); - } - public boolean getShowSheetHeaders() { - return ((grbit & BITMASK_SHOWSHEETHEADERS)==BITMASK_SHOWSHEETHEADERS); - } + if (b) + grbit = grbit | BITMASK_SHOWGRIDLINES; + else + grbit = grbit & ~BITMASK_SHOWGRIDLINES; + this.setGrbit(); + } + + public boolean getShowSheetHeaders() { + return ((grbit & BITMASK_SHOWSHEETHEADERS) == BITMASK_SHOWSHEETHEADERS); + } + public void setShowSheetHeaders(boolean b) { - if (b) - grbit= grbit | BITMASK_SHOWSHEETHEADERS; - else - grbit= grbit & ~BITMASK_SHOWSHEETHEADERS; - this.setGrbit(); - } - - + if (b) + grbit = grbit | BITMASK_SHOWSHEETHEADERS; + else + grbit = grbit & ~BITMASK_SHOWSHEETHEADERS; + this.setGrbit(); + } + + public int getScaleNorm() { - //return wScaleSLV; - return this.wScaleNorm; + //return wScaleSLV; + return this.wScaleNorm; } - + public void setScaleNorm(int zm) { wScaleNorm = zm; byte[] data = this.getData(); - byte[] b= ByteTools.shortToLEBytes((short) zm); + byte[] b = ByteTools.shortToLEBytes((short) zm); // wScaleSLV 10,11 // wScaleNorm 12,13; System.arraycopy(b, 0, data, 12, 2); this.setData(data); } - - - public boolean getShowZeroValues() { - return ((grbit & BITMASK_SHOWZEROVALUES)==BITMASK_SHOWZEROVALUES); - } + + + public boolean getShowZeroValues() { + return ((grbit & BITMASK_SHOWZEROVALUES) == BITMASK_SHOWZEROVALUES); + } + public void setShowZeroValues(boolean b) { - if (b) - grbit= grbit | BITMASK_SHOWZEROVALUES; - else - grbit= grbit & ~BITMASK_SHOWZEROVALUES; - this.setGrbit(); - } - public boolean getShowOutlineSymbols() { - return ((grbit & BITMASK_SHOWOUTLINESYMBOLS)==BITMASK_SHOWOUTLINESYMBOLS); - } + if (b) + grbit = grbit | BITMASK_SHOWZEROVALUES; + else + grbit = grbit & ~BITMASK_SHOWZEROVALUES; + this.setGrbit(); + } + + public boolean getShowOutlineSymbols() { + return ((grbit & BITMASK_SHOWOUTLINESYMBOLS) == BITMASK_SHOWOUTLINESYMBOLS); + } + public void setShowOutlineSymbols(boolean b) { - if (b) - grbit= grbit | BITMASK_SHOWOUTLINESYMBOLS; - else - grbit= grbit & ~BITMASK_SHOWOUTLINESYMBOLS; - this.setGrbit(); + if (b) + grbit = grbit | BITMASK_SHOWOUTLINESYMBOLS; + else + grbit = grbit & ~BITMASK_SHOWOUTLINESYMBOLS; + this.setGrbit(); } + /** * true if sheet is in normal view mode (false if in page break preview mode) + * * @return */ - public boolean getShowInNormalView() { - return ((grbit & BITMASK_SHOWINPRINTPREVIEW)==0); - } + public boolean getShowInNormalView() { + return ((grbit & BITMASK_SHOWINPRINTPREVIEW) == 0); + } + public void setShowInNormalView(boolean b) { - if (b) - grbit= grbit & ~BITMASK_SHOWINPRINTPREVIEW; - else - grbit= grbit | BITMASK_SHOWINPRINTPREVIEW; - this.setGrbit(); + if (b) + grbit = grbit & ~BITMASK_SHOWINPRINTPREVIEW; + else + grbit = grbit | BITMASK_SHOWINPRINTPREVIEW; + this.setGrbit(); } - public boolean getFreezePanes() { - return ((grbit & BITMASK_FREEZEPANES)==BITMASK_FREEZEPANES); + + public boolean getFreezePanes() { + return ((grbit & BITMASK_FREEZEPANES) == BITMASK_FREEZEPANES); } + public void setFreezePanes(boolean b) { - if (b) - grbit= grbit | BITMASK_FREEZEPANES; - else - grbit= grbit &~ BITMASK_FREEZEPANES; - this.setGrbit(); - } - public boolean getManualGridLineColor() { - return ((grbit & BITMASK_GRIDLINECOLOR)==BITMASK_GRIDLINECOLOR); - } + if (b) + grbit = grbit | BITMASK_FREEZEPANES; + else + grbit = grbit & ~BITMASK_FREEZEPANES; + this.setGrbit(); + } + + public boolean getManualGridLineColor() { + return ((grbit & BITMASK_GRIDLINECOLOR) == BITMASK_GRIDLINECOLOR); + } + public void setManualGridLineColor(boolean b) { - if (b) - grbit= grbit | BITMASK_GRIDLINECOLOR; - else - grbit= grbit & ~BITMASK_GRIDLINECOLOR; - this.setGrbit(); + if (b) + grbit = grbit | BITMASK_GRIDLINECOLOR; + else + grbit = grbit & ~BITMASK_GRIDLINECOLOR; + this.setGrbit(); } /* // TODO: finish these static final int BITMASK_COLUMNDIRECTION= 0x0040; @@ -240,5 +256,5 @@ public void setManualGridLineColor(boolean b) { static final int BITMASK_SHEETSELECTED= 0x0200; static final int BITMASK_SHEETVISIBLE= 0x0400; */ - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/WorkBook.java b/src/main/java/io/starter/formats/XLS/WorkBook.java index fe85034..2b39ed3 100644 --- a/src/main/java/io/starter/formats/XLS/WorkBook.java +++ b/src/main/java/io/starter/formats/XLS/WorkBook.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,52 +29,30 @@ */ package io.starter.formats.XLS; -import java.awt.Color; -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.TreeMap; -import java.util.Vector; - -import io.starter.OpenXLS.Cell; -import io.starter.OpenXLS.DateConverter; -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.ImageHandle; import io.starter.OpenXLS.WorkBookException; -import io.starter.OpenXLS.WorkBookHandle; +import io.starter.OpenXLS.*; import io.starter.formats.OOXML.Theme; import io.starter.formats.XLS.charts.Ai; import io.starter.formats.XLS.charts.Chart; import io.starter.formats.XLS.charts.Fontx; import io.starter.formats.XLS.charts.GenericChartObject; -import io.starter.formats.XLS.formulas.IlblListener; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.formats.XLS.formulas.PtgArea3d; -import io.starter.formats.XLS.formulas.PtgExp; -import io.starter.formats.XLS.formulas.PtgNameX; -import io.starter.formats.XLS.formulas.PtgRef; +import io.starter.formats.XLS.formulas.*; import io.starter.toolkit.FastAddVector; import io.starter.toolkit.Logger; -/**
                -    The WorkBook record represents an XLS workbook substream containing worksheets and associated records.
                -    
                -    
                +import java.awt.Color; +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.*; + +/** + *
                + * The WorkBook record represents an XLS workbook substream containing worksheets and associated records.
                + *
                + * 
                + * * @see WorkBook * @see Boundsheet * @see Index @@ -86,3782 +64,3883 @@ public class WorkBook implements Serializable, XLSConstants, Book { - private static final long serialVersionUID = 2282017774412632087L; - - DateConverter.DateFormat dateFormat = DateConverter.DateFormat.LEGACY_1900; - - // public constants - public static int STRING_ENCODING_AUTO = 0; - public static int STRING_ENCODING_UNICODE = 1; - public static int STRING_ENCODING_COMPRESSED = 2; - public static int ALLOWDUPES = 0; - public static int SHAREDUPES = 1; - - private int DEBUGLEVEL = 0; - private int bofct = 0, eofct = 0, - indexnum = 0; - private int defaultIxfe = 15; - private int CalcMode = CALCULATE_AUTO; - private int defaultLanguage = 0; // default - // language - // code - // for - // the - // current - // workbook - private boolean copying = false; - private boolean sharedupes = false; - private AbstractList xfrecs = new Vector(); - private AbstractList indexes = new Vector(); - private AbstractList names = new Vector(); // ALL - // of - // the - // names - // (including - // worksheet - // scoped, - // etc) - private AbstractList orphanedPtgNames = new Vector(); - private AbstractList externalnames = new Vector(); - AbstractList formulas = new FastAddVector(); - private AbstractList fonts = new Vector(); - public Color[] colorTable; - - /** The list of Format records indexed by format ID. */ - private TreeMap formats = new TreeMap(); - private AbstractList charts = new Vector(); - /** OOXML-specific */ - private AbstractList ooxmlObjects = new Vector(); // stores - // OOXML - // objects - // external - // to - // workbook - // e.g. - // oleObjects, - private String ooxmlcodename = null; // stores - // OOXML - // codename - private ArrayList dxfs = null; // 20090622 - // KSC: - // stores - // dxf's - // (incremental - // style - // info) - // per - // workbook - private int firstSheet = 0; // specifies - // first - // sheet - // (ooxml) - private Theme theme = null; - - // Reference Tracking - private ReferenceTracker refTracker = new ReferenceTracker(); - - // various - private AbstractList boundsheets = new Vector(); // TODO: - // remove - // this - // variable? - // its - // duplicated - // in - // workSheets - private AbstractList hlinklookup = new Vector( - 20); - private AbstractList mergecelllookup = new Vector( - 20); - - /* - * Indirect formulas that need to be calculated after load - * for reftracker - */ - private AbstractList indirectFormulas = new ArrayList(); - private AbstractList supBooks = new Vector(); - - /* - * This is here only to allow client code to compile, should - * be removed - */ - public static String CONVERTMULBLANKS = "deprecated"; - - // We should consider using an ordered collections class for - // some of these? - // an enumeration of worksheets for instance will not always - // be in the same - // order, causing tests to fail... - private Hashtable workSheets = new Hashtable( - 100, .950f); - private HashMap bookNameRecs = new HashMap(); - private Hashtable formulashash = new Hashtable(); - - /** Maps number format patterns to their IDs. */ - private Hashtable formatlookup = new Hashtable( - 30); - - private Hashtable ptViews = new Hashtable( - 20); // TODO: - // move - // to - // sheet - // - - // sheet-level - // pivot - // table - // view - // recordss - private ArrayList ptstream = new ArrayList(); // wb-level - // pivot - // cache - // definitions - // usually - // only - // 1 - private PivotCache ptcache = null; // if - // has - // pivot - // tables - // this - // is - // the - // one - // and - // only - // pivot - // cache - - public HashMap formatCache = new HashMap(); - - private Index lastidx; - private Sst stringTable; - private Bof lastBOF; - private Externsheet myexternsheet; - private Bof firstBOF; - private ByteStreamer streamer = createByteStreamer(); - private TabID tabs; - private Window1 win1; - private CalcMode calcmoderec; // determines - // recalculation - // mode - // for - // workbook - // - - // Manual, - // Auto - // ... - private DefaultRowHeight drh; - private Chart currchart; - private Ai currai; - private Supbook myADDINSUPBOOK = null; // for - // external - // names - // SUPBOOK - WorkBookFactory factory; - private ContinueHandler contHandler = new ContinueHandler( - this); - private Usersviewbegin usersview; - private Eof lasteof; - private BiffRec xl2k = null; - public AbstractList msodgMerge = new Vector(); - public MSODrawingGroup msodg = null; - public int lastSPID = 1024; // 20071030 - // last - // or - // next - // SPID - // (= - // shape - // ID - // or - // image - // ID); - // incremented - // upon - // new - // images - // ... - // appropriate - // to - // store - // at - // book - // level - // (?) - private MSODrawing currdrw = null; - - private BookProtectionManager protector; - - protected void reflectiveClone(WorkBook source) { - for (Field field : WorkBook.class.getDeclaredFields()) { - if (Modifier.isStatic(field.getModifiers())) - continue; - - try { - field.set(this, field.get(source)); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - } - - protected ByteStreamer createByteStreamer() { - return new ByteStreamer(this); - } - - /** Gets this sheet's SheetProtectionManager. - */ - public BookProtectionManager getProtectionManager() { - if (protector == null) - protector = new BookProtectionManager(this); - return protector; - } - - /** init the ImageHandles - * - * - */ - void initImages() { - lastSPID = Math.max(lastSPID, msodg.getSpidMax()); // 20090508 KSC: - // lastSPID should - // also account for - // charts - // [BUGTRACKER 2372 - // copyChartToSheet - // error] - // 20071217 KSC: clear out imageMap before inputting! - for (int x = 0; x < this.getWorkSheets().length; x++) { - (this.getWorkSheets()[x]).imageMap.clear(); - } - for (int i = 0; i < msodg.getMsodrawingrecs().size(); i++) { // 20070914 - // KSC: - // store - // msodrawingrecs - // with - // MSODrawingGroup - // instead - // of - // here - MSODrawing rec = (MSODrawing) msodg.getMsodrawingrecs().get(i); - lastSPID = Math.max(lastSPID, rec.getlastSPID()); // valid for - // header - // msodrawing - // record(s) - int imgdx = rec.getImageIndex() - 1; // it's 1-based - byte[] imageData = msodg.getImageBytes(imgdx); - if (imageData != null) { - ImageHandle im = new ImageHandle(imageData, rec.getSheet()); - im.setMsgdrawing(rec); // Link 2 actual Msodrawing rec - im.setName(rec.getName()); // set image name from rec ... - im.setShapeName(rec.getShapeName()); // set shape name as well - // ... - im.setImageType(msodg.getImageType(imgdx));// 20100519 KSC: - // added! - rec.getSheet().imageMap.put(im, Integer.valueOf(imgdx)); - } - } - } - - /* - * Once all the records are parsed, the msodrawinggroup - * needs to be parsed. - * We first merge all the msodrawinggroup records. From the - * till now experiment, - * there are maximum of two such records, adjacent to each - * other. If there are two, - * the size of the first is only 8228 and the remaining data - * is in second record. - * However, when writing, it doesn't seem important to break - * it into two and can be written - * as one. - * - * Once msodrawingrecords are parsed, it is not used. - * However, we have a msodrawingglobal class that - * records the current count of msodrawing related records, - * like shape count, max shape id till now etc. - * We need to maintain that for writing (creating the - * msodrawinggroup records) later on. - */ - public void mergeMSODrawingRecords() { - // 20070915 KSC: Now don't re-initialize Msodrawing recs; - // just merge and parse - if (msodg != null) { - msodg.mergeAndRemoveContinues(); - for (int i = 1; i < msodgMerge.size(); i++) { - msodg.mergeRecords((MSODrawingGroup) msodgMerge.get(i)); // merges - // and - // removes - // secondary - // msodrawinggroups - // 20071003 KSC: get rid of secondary msodg's, will be - // created upon createMSODGContinues - this.getStreamer() - .removeRecord((MSODrawingGroup) msodgMerge.get(i)); // remove - // existing - // continues - // from - // stream - } - while (msodgMerge.size() > 1) - msodgMerge.remove(msodgMerge.size() - 1); - msodg.parse(); - this.initImages(); - } - } - - /** after changing the MSODrawings on a sheet, this is called to - * update the header Msodrawing rec. - *
                must sum up all other mso's on the particular sheet SPCONTAINERLEN and update the sheet mso header ... - *
                this calculation is quite experimental, so far it's working in all known cases ... - * - * @param bs - */ - public void updateMsodrawingHeaderRec(Boundsheet bs) { - MSODrawing msdHeader = msodg.getMsoHeaderRec(bs); - if (msdHeader != null) { - int spContainerLength = 0; // count of all other spcontainer lengths - // (sum=header spgroupcontainer) - int otherContainerLength = 0; // count of other containers - // (solvercontainer,etc) added to - // dgcontainerlength - int numshapes = 2; // 20100324 KSC: total guess, really - int totalDrawingRecs = msodg.getMsodrawingrecs().size(); - for (int z = 0; z < totalDrawingRecs; z++) { - MSODrawing rec = (MSODrawing) msodg.getMsodrawingrecs().get(z); - if (rec.getSheet().equals(bs)) { - if (rec != msdHeader && !rec.isHeader()) { // added header - // check- seems - // like *can* - // have multiple - // header recs - // in charts! - spContainerLength += rec.getSPContainerLength(); - otherContainerLength += rec.getSOLVERContainerLength(); - if (rec.isShape) // if it's a shape-type mso, count; - // there are other mso-types that - // are not SPCONTAINERS; apparently - // don't count these ... - numshapes++; - } - } - } - msdHeader - .updateHeader(spContainerLength, otherContainerLength, numshapes, msdHeader - .getlastSPID()); - } - } - - /** - * Get the MsoDrawingGroup for this workbook - * @return msodrawinggroup - */ - public MSODrawingGroup getMSODrawingGroup() { - return msodg; - } - - /** - * Set the msodrawinggroup for this workbook - * @param msodg - */ - public void setMSODrawingGroup(MSODrawingGroup msodg) { - this.msodg = msodg; - this.msodg.setWorkBook(this); - this.msodg.setStreamer(this.getStreamer()); - } - - /** - * For workbooks that do not contain an MSODrawing group create a new one, - * if the drawing group already exists return the existing - * @return - */ - public MSODrawingGroup createMSODrawingGroup() { - if (msodg != null) - return msodg; - this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup - .getPrototype()); - return msodg; - } - - /** Return some useful statistics about the WorkBook - * - * @return - */ - public String getStats() { - return getStats(false); - } - - /** Return some useful statistics about the WorkBook - * - * @return - * - */ - public String getStats(boolean usehtml) { - String rex = "\r\n"; - if (usehtml) - rex = "
                "; - - String ret = "-------------------------------------------" + rex; - ret += "OpenXLS Version: " + WorkBookHandle.getVersion() + rex; - ret += "Excel Version: " + getXLSVersionString() + rex; - ret += "-------------------------------------------------\r\n"; - ret += "Statistics for: " + this.toString() + rex; - ret += "Number of Worksheets: " + this.getNumWorkSheets() + rex; - ret += "Number of Cells: " + this.getNumCells() + rex; - ret += "Number of Formulas: " + this.getNumFormulas() + rex; - ret += "Number of Charts: " + this.getChartVect().size() + rex; - ret += "Number of Fonts: " + this.getNumFonts() + rex; - ret += "Number of Formats: " + this.getNumFormats() + rex; - ret += "Number of Xfs: " + this.getNumXfs() + rex; - // ret += "StringTable: " + this.stringTable.toString() + - // rex; - ret += "-------------------------------------------------\r\n"; - return ret; - } - - public String getXLSVersionString() { - return lastBOF.getXLSVersionString(); - } - - private void addMergedcells(Mergedcells c) { - this.mergecelllookup.add(c); - } - - private void addHlink(Hlink r) { - this.hlinklookup.add(r); - } - - /** take care of any lazy updating before output - * - */ - public void prestream() { - if (this.getMSODrawingGroup() != null) - this.getMSODrawingGroup().prestream(); - } - - public Chart[] getCharts() { - Chart[] chts = new Chart[this.charts.size()]; - return (Chart[]) charts.toArray(chts); - } - - public Supbook[] getSupBooks() { - Supbook[] sbs = new Supbook[this.supBooks.size()]; - return (Supbook[]) this.supBooks.toArray(sbs); - } - - /** - * returns the list of OOXML objects which are external or auxillary to the main workbook - * e.g. theme, doc properties - * @return - */ - public List getOOXMLObjects() { - return ooxmlObjects; - } - - /** - * adds the object-specific signature of the external or auxillary OOXML object - * Object should be of String[] form, - * key, path, local path + filename [, rid, [extra info], [embedded information]] - * e.g. theme, doc properties - * @param o - */ - public void addOOXMLObject(Object o) { - if (!((String[]) o)[0].equals("externalLink")) - ooxmlObjects.add(o); - else - ooxmlObjects.add(0, o); // ensure ExternalLinks are 1st because they - // are linked via rId in workbook.xml - } - - /** - * return the OOXML theme for this workbook, if any - * @return - */ - public Theme getTheme() { - return theme; - } - - /** - * sets the OOXML theme for this 2007 verison workbook - * @param t - */ - public void setTheme(Theme t) { - theme = t; - } - - /** - * return the External Supbook record associated with the desired externalWorkbook - * will create if bCreate - * @param externalWorkbook String URL (name) of External Workbook - * @param bCreate if true, will create an external SUPBOOK record for the externalWorkbook - * @return Supbook - */ - public Supbook getExternalSupbook(String externalWorkbook, boolean bCreate) { - Supbook sb = null; - if (externalWorkbook == null) - return null; - for (int i = 0; i < this.supBooks.size(); i++) { - if (((Supbook) this.supBooks.get(i)).isExternalRecord()) - if (externalWorkbook - .equalsIgnoreCase(((Supbook) this.supBooks.get(i)) - .getExternalWorkBook())) - sb = (Supbook) this.supBooks.get(i); - } - if (sb == null && bCreate) { // create - sb = (Supbook) Supbook.getExternalPrototype(externalWorkbook); - int loc = ((Supbook) supBooks.get(supBooks.size() - 1)) - .getRecordIndex(); // must have at least one global supbook - // present - streamer.addRecordAt(sb, loc + 1); // external supbooks appear to be - // before "normal" supbooks - // [BugTracker 1434] - this.supBooks.add(sb); // 20080714 KSC: add at beginning -- correct - // in all cases? - // this.addRecord(sb,false); // "" no need - } - return sb; - } - - /** - * return the index into the Supbook records for this supbook - * @param sb - * @return int - */ - public int getSupbookIndex(Supbook sb) { - for (int i = 0; i < this.supBooks.size(); i++) { - if (((Supbook) this.supBooks.get(i)) == sb) - return i; - } - return -1; - } - - public AbstractList getChartVect() { - return charts; - } - - public Sxview getPivotTableView(String nm) { - return (Sxview) ptViews.get(nm); - } - - protected void addPivotTable(Sxview sx) { - this.ptViews.put(sx.getTableName(), sx); // Pivot Table View ==Top-level - // record for a Pivot Table - } - - /** - * return all pivot table views (==Sxview records) - *
                SxView is the top-level record of a Pivot Table - as distinct from the PivotCache (stored data source in a LEOFile Storage) - and PivotTable Stream (SxStream top-level record) - * @return - */ - public Sxview[] getAllPivotTableViews() { - Sxview[] sv = new Sxview[ptViews.size()]; - Enumeration x = ptViews.elements(); - int t = 0; - while (x.hasMoreElements()) { - sv[t++] = (Sxview) x.nextElement(); - } - return sv; - } - - public int getNPivotTableViews() { - return ptViews.size(); - } - - /** return the Externsheet for this book - * - * @param create a new Externsheet if it does not exist - * @return the Externsheet - */ - public Externsheet getExternSheet(boolean create) { - if ((myexternsheet == null) && create) { - addExternsheet(); - } - return myexternsheet; - } - - /** get the Externsheet - */ - public Externsheet getExternSheet() { - if (myexternsheet == null) - addExternsheet(); - return myexternsheet; - } - - /** default constructor -- do init - */ - public WorkBook() { - - Object cm = System.getProperties().get(WorkBook.CALC_MODE_PROP); - if (cm != null) { - try { - this.CalcMode = Integer.parseInt(cm.toString()); - } catch (Exception e) { - Logger.logWarn("Invalid Calc Mode Setting in System properties:" - + cm); - } - } - if (System.getProperties() - .get("io.starter.OpenXLS.sharedupes") != null) { - this.sharedupes = System.getProperties() - .get("io.starter.OpenXLS.sharedupes").equals("true"); - if (this.sharedupes) { - this.setDupeStringMode(WorkBook.SHAREDUPES); - } - } - this.initBuiltinFormats(); - // re-init color table: initial state of color table if - // Pallete record exists, changes may occur - colorTable = new java.awt.Color[FormatHandle.COLORTABLE.length]; - for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) - colorTable[i] = FormatHandle.COLORTABLE[i]; - } - - /** Gets the format ID for a given number format pattern. - * This lookup is completely case-insensitive. For most patterns this - * correctly reflects the case-insensitivity of the tokens. Custom patterns - * containing string literals could be matched incorrectly. - * - * @param pattern the number format pattern to look up - * @return the format ID of the given pattern or -1 if it's not recognized - */ - public short getFormatId(String pattern) { - Short res = (Short) formatlookup.get(pattern.toUpperCase()); - if (res != null) - return res.shortValue(); - return -1; - } - - /** Initializes the format lookup to contain the built-in formats. */ - private void initBuiltinFormats() { - String[][] formats = FormatConstantsImpl.getBuiltinFormats(); - - for (int i = 0; i < formats.length; i++) - formatlookup.put(formats[i][0].toUpperCase(), Short - .valueOf(formats[i][1], 16)); - } - - /** - * Init names at Post-load - */ - public void initializeNames() { - for (int i = 0; i < names.size(); i++) { - Name n = (Name) names.get(i); - n.parseExpression(); // evaluate expression at postload, after sheet - // recs are loaded - } - } - - /** add a Name object to the collection of names - */ - public int addName(Name n) { - if (n.getItab() != 0) { - // its a sheet level name - try { - Boundsheet b = this.getWorkSheetByNumber(n.getItab() - 1);// one - // based - // pointer - b.addLocalName(n); - n.setSheet(b); - } catch (WorkSheetNotFoundException e) {} - } else { - String sName = n.getNameA(); // returns upper case name - Object existo = bookNameRecs.get(sName); - if (existo != null) { // handle duplicate named ranges - String bnam = n.toString(); - if (bnam.indexOf("Built-in:") != 0) { - try { - if (((Name) existo).getLocation() != null) // use - // original - // - as good - // a guess - // as any - return -1; // an invalid sheet - } catch (Exception e) {} - // if original does not have a location set, use this one - // instead - this.names.remove(this.names.indexOf(existo)); - this.bookNameRecs.remove(sName); - } - } - this.bookNameRecs.put(sName, n); - } - this.names.add(n); - if ((myexternsheet != null) && (n.getExternsheet() == null)) { - try { - n.setExternsheet(myexternsheet); // update sheet reference - } catch (WorkSheetNotFoundException e) { - Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " - + e.toString()); - } - } - return names.size() - 1; - } - - public void addNameUpdateSheetRefs(Name n, String origWorkBookName) { - if (bookNameRecs.get(n.getNameA()) == null) { - Name newName = new Name(this, n.getName()); - try { - newName.setLocation(n.getLocation()); - } catch (Exception e) {} - if ((myexternsheet != null) && (newName.getExternsheet() == null)) { - try { - newName.setExternsheet(myexternsheet); // update sheet - // reference - } catch (WorkSheetNotFoundException e) { - Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " - + e.toString()); - } - } - newName.updateSheetRefs(origWorkBookName); - } - } - - /** - * Store an external name - * @param n = String describing the name - * @return int location of the name - */ - public int addExternalName(String n) { - this.externalnames.add(n); - return externalnames.size(); // one-based index - } - - /** - * Get a string array of external names - * @return externalNames - */ - public String[] getExternalNames() { - String[] n = new String[externalnames.size()]; - externalnames.toArray(n); - return n; - } - - /** - * Get the external name at the specified index. - * @param t index of the name - * @return name at the index, empty string if it doesn't exist. - * - * Why are we calling getExternalName one based, then removing for ordinal, internal processes should always - * be 0,1,2,3... -NR 1/06 - */ - public String getExternalName(int t) { - if (t > 0) - return (String) this.externalnames.get(t - 1); // one-based index - return ""; - } - - /** - * For workbooks that do not contain an externsheet - * this creates the externsheet record with one 0000 record - * and the related Supbook rec - - * - */ - public void addDefaultExternsheet() { - Supbook sbb = (Supbook) Supbook.getPrototype(this.getNumWorkSheets()); - int l = this.stringTable.getRecordIndex(); - streamer.addRecordAt(sbb, l++); - supBooks.add(sbb); - Externsheet ex = (Externsheet) Externsheet - .getPrototype(0x0000, 0x0000, this); - streamer.addRecordAt(ex, l); - this.addRecord(ex, false); - } - - /** apparently this method adds an External name rec and returns the ilbl - * - * Correct structure is - * Supbook - * Externname - * Supbook - * Externsheet - - * - * @see PtgNameX - * @param s - * @return - */ - public int getExtenalNameNumber(String s) { - int i = externalnames.indexOf(s); - if (i > -1) // got it - return i + 1; - if (this.getExternSheet() == null) - this.addDefaultExternsheet(); - - // not found; add a new EXTERNNAME record to list of add-ins - int n = addExternalName(s); - try { - int loc; - if (myADDINSUPBOOK == null) { - Supbook sb = (Supbook) Supbook.getAddInPrototype(); - loc = this.getExternSheet().getRecordIndex(); - streamer.addRecordAt(sb, loc++); - this.addRecord(sb, false); - myADDINSUPBOOK = sb; - supBooks.add(sb); - int externref = this.getExternSheet().getVirtualReference(); - // Add EXTERNNAME record after ADD-IN SUPBOOK record and - // after existing EXTERNNAME records - Externname exn = (Externname) Externname.getPrototype(s); - streamer.addRecordAt(exn, loc++); - this.addRecord(exn, false); - } else { - loc = streamer.getRecordIndex(myADDINSUPBOOK); - // Add EXTERNNAME record after ADD-IN SUPBOOK record and - // after existing EXTERNNAME records - Externname exn = (Externname) Externname.getPrototype(s); - streamer.addRecordAt(exn, loc + externalnames.size()); - this.addRecord(exn, false); - } - - } catch (Exception e) { - Logger.logWarn("Error adding externname: " + e); - } - return n; - } - - /** Get a collection of all names in the workbook - */ - public Name[] getNames() { - Name[] n = new Name[names.size()]; - names.toArray(n); - return n; - } - - /** Get a collection of all names in the workbook - */ - public Name[] getWorkbookScopedNames() { - ArrayList a = new ArrayList(this.bookNameRecs.values()); - Name[] n = new Name[a.size()]; - a.toArray(n); - return n; - } - - /** returns the List of Formulas in the book - - * - * @return - */ - public List getFormulaList() { - return formulas; - } - - /** returns the array of Formulas in the book - - * - * @return - */ - public Formula[] getFormulas() { - Formula[] n = new Formula[formulas.size()]; - formulas.toArray(n); - return n; - } - - /** remove a formula from the book - * - * @param fmla - */ - public void removeFormula(Formula fmla) { - this.formulashash.remove(fmla.getCellAddressWithSheet()); - formulas.remove(fmla); - fmla.destroy(); - } - - /** - * Returns the recalculation mode for the Workbook: - *
                0= Manual - *
                1= Automatic - *
                2= Automatic except for multiple table operations - * @return int - */ - public int getRecalculationMode() { - return this.calcmoderec.getRecalcuationMode(); - } - - /** - * Sets the recalculation mode for the Workbook: - *
                0= Manual - *
                1= Automatic - *
                2= Automatic except for multiple table operations - */ - public void setRecalcuationMode(int mode) { - this.calcmoderec.setRecalculationMode(mode); - } - - /** returns a Named range by number - - * - * @param t - * @return - */ - public Name getName(int t) { - return (Name) this.names.get(t - 1); - } - - /** rename the NamedRange in the lookup map - - * - * @param t - * @return - */ - public void setNewName(String oldname, String newname) { - if (oldname.equals(newname)) - return; - oldname = oldname.toUpperCase(); // case-insensitive - newname = newname.toUpperCase(); // "" - Object old = bookNameRecs.get(oldname); - if (old == null) - return; // new name? - bookNameRecs.remove(oldname); - bookNameRecs.put(newname, old); - - } - - /** - * Re-assocates ptgrefs that are pointing to a name that has been deleted then - * is recreated - * @param name - */ - public void associateDereferencedNames(Name name) { - Iterator i = orphanedPtgNames.iterator(); - String theName = name.getName(); - while (i.hasNext()) { - IlblListener x = (IlblListener) i.next(); - if (x.getStoredName().equalsIgnoreCase(theName)) { - x.setIlbl((short) this.getNameNumber(theName)); - x.addListener(); - } - } - } - - /** returns a named range by name string - * - * This method will first attempt to look in the book names, then the sheet names, - * obviously different scoped names can have the same identifying name, so this could return - * one of multiple names if this is the case - * - * @param t - * @return - */ - public Name getName(String nameRef) { - nameRef = nameRef.toUpperCase(); // case-insensitive - Object o = this.bookNameRecs.get(nameRef); - if (o == null) { - Boundsheet[] shts = this.getWorkSheets(); - for (int i = 0; i < shts.length; i++) { - o = shts[i].getName(nameRef); - if (o != null) - return (Name) o; - } - } - return (Name) o; - } - - /** returns a scoped named range by name string - - * - * @param t - * @return - */ - public Name getScopedName(String nameRef) { - Object o = this.bookNameRecs.get(nameRef.toUpperCase()); // case-insensitive - if (o == null) { - return null; - } - return (Name) o; - } - - /** - * Returns the ilbl of the name record associated with the string passed in. - * If the name does not exist, it get's created without a location reference. - * This is needed to support formula creation with non-existent names referenced. - * - * @param t, the name record to search for - * @return the index of the name - */ - public int getNameNumber(String nameStr) { - for (int i = 0; i < names.size(); i++) { - Name n = (Name) this.names.get(i); - if (n.getName().equalsIgnoreCase(nameStr)) - return i + 1; - } - // no name exists, we need to create one. - Name myName; - myName = new Name(this, nameStr); - // myName = new Name(this, true); - // myName.setName(nameStr); - Name[] nmx = this.getNames(); - int namepos = -1; - if (nmx.length >= 1) { - namepos = nmx[nmx.length - 1].getRecordIndex(); - } else { - namepos = this.getExternSheet().getRecordIndex(); - } - namepos++; - this.getStreamer().addRecordAt(myName, namepos); - return this.getNameNumber(nameStr); - } - - /** - * Get's the index for this particular front. - * - * NOTE: this doesn't actually get a "matching" font, it has to be the exact font. - * 20070826 KSC: changed to match font characterstics, not just return exact matching font - */ - public int getFontIdx(Font f) { - // 20070819 KSC: Try this to see if better! Matches 6 key - // attributes (size, name, color, etc.) - for (int i = fonts.size() - 1; i >= 0; i--) { // start from the back so - // don't initially match - // defaults... - if (f.matches((Font) fonts.get(i))) - return ((i > 3) ? i + 1 : i); - } - // return fonts.indexOf(f); - return -1; - } - - /** - * Get's the index for this font, based on matching through - * xml strings. If the font doesn't exist in the book it returns -1; - * @return - * KSC: is this method necessary now with above getFontIdx changes? - */ - public int getMatchingFontIndex(Font f) { - Map fontmap = this.getFontRecsAsXML(); - Object o = fontmap.get("<" + f.getXML() + "/>"); - if (o != null) { - Integer I = (Integer) o; - return I.intValue(); - } else { - return -1; - } - } - - /** - * InsertFont inserts a Font record into the workbook level stream, - * For some reason, the addFont only puts it into an array that is never accessed - * on output. This may have a reason, so I am not overwriting it currently, but - * let's check it out? - * - * */ - public int insertFont(Font x) { - int insertIdx = this.getFont(this.getNumFonts()).getRecordIndex(); - // perform default add rec actions - this.getStreamer().addRecordAt(x, insertIdx + 1); - x.setIdx(-1); // flag to add into font array - this.addRecord(x, false); // also adds to font array so no need for - // additional addFont below - return fonts.indexOf(x); - } - - /** add a Font object to the collection of Fonts - */ - public int addFont(Font f) { - fonts.add(f); - if (fonts.size() > 4) // fake the evil 4! - return fonts.size(); - return fonts.size() - 1; - } - - public int getNumFonts() { - return fonts.size(); - } - - /** - * Get the font at the specified index. Note that the number 4 does not exist, so index correctly based of that. - * - * So, if you call getFont(5), you are really doing getFont(4) from the internal array - * @param t - * @return - */ - public Font getFont(int t) { - if (t >= 4) { - t--; - } - if (this.fonts.size() >= t) { - if (t >= fonts.size()) { - Logger.logWarn("font " + t - + " not found. Workbook contains only: " + fonts.size() - + " defined fonts."); - return (Font) fonts.get(0); - } else { - return (Font) fonts.get(t); - } - } - return (Font) this.fonts.get(0); - } - - /** Inserts a newly created Format record into the workbook. - * This method handles assigning the format ID and adding the record to the - * workbook. If the record is already part of the workbook use - * {@link #addFormat} instead. - */ - public int insertFormat(Format format) { - Format last; - try { - last = (Format) formats.get(formats.lastKey()); - } catch (NoSuchElementException e) { - /* - * There are no other Format records in the workbook. - * This shouldn't happen because most (all?) Excel files - * contain - * Format records for the locale-specific (and thus not - * implied) - * built-in formats. If it does happen, either we need to - * re-assess - * the above assumption or this method was called before the - * Format - * records were parsed. Either way we need to know about it. - */ - throw new AssertionError("WorkBook.insertFormat called but no " - + "Format records exist. This should not happen. Please " - + "report this error to support@extentech.com."); - } - - // Add it to the streamer and workbook - getStreamer().addRecordAt(format, last.getRecordIndex() + 1); - addRecord(format, false); - - // Give it a format ID - if (format.getIfmt() == -1) - format.setIfmt((short) Math.max(last.getIfmt() + 1, 164)); - - // Add it to the format lookups - addFormat(format); - - return format.getIfmt(); - } - - public Formula getFormula(String cellAddress) throws FormulaNotFoundException { - Formula formula = (Formula) formulashash.get(cellAddress); - if (formula == null) - throw new FormulaNotFoundException( - "no formula found at " + cellAddress); - - return (formula); - } - - /** Adds an existing format record to the list of known formats. - * This method does not add the record to the workbook! If the format is - * not already in the workbook use {@link #insertFormat} instead. - * @param format the Format record to add - */ - public int addFormat(Format format) { - Short ifmt = Short.valueOf(format.getIfmt()); - - // Add it to the format record lookup - formats.put(ifmt, format); - - // Add it to the format string lookup - formatlookup.put(format.getFormat().toUpperCase(), ifmt); - - return format.getIfmt(); - } - - /** Gets the number of custom number formats registered on this book. - */ - public int getNumFormats() { - return formats.size(); - } - - /** Gets a custom number format by its format ID. - */ - public Format getFormat(int id) { - return (Format) formats.get(Short.valueOf((short) id)); - } - - public TabID getTabID() { - return this.tabs; - } - - /** associate default row/col size recs - */ - void setDefaultRowHeightRec(DefaultRowHeight dr) { - this.drh = dr; - } - - /** set Default row height in twips (1/20 of a point) - */ - // should be a double as Excel units are 1/20 of what is - // stored in defaultrowheight - // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 - // (approx) - // should expect users to use Excel units and target method - // do the 20* conversion - public void setDefaultRowHeight(int t) { - drh.setDefaultRowHeight(t); - } - - /** set Default col width for all worksheets in the workbook, - * - * Default column width can also be set on individual worksheets - */ - public void setDefaultColWidth(int t) { - Boundsheet[] b = this.getWorkSheets(); - for (int i = 0; i < b.length; i++) { - b[i].setDefaultColumnWidth(t); - } - } - - /** sets the selected worksheet - */ - public int getSelectedSheetNum() { - return win1.getCurrentTab(); - } - - /** sets the selected worksheet - */ - public void setSelectedSheet(Boundsheet bs) { - Boundsheet[] bsx = this.getWorkSheets(); - for (int t = 0; t < bsx.length; t++) { - if (bsx[t] != bs) - bsx[t].setSelected(false); - } - this.win1.setCurrentTab(bs); - } - - Formula lastFormula = null; - XLSRecord countryRec = null; - boolean inChartSubstream = false; - ArrayList chartTemp = new ArrayList(); - - /** - * for those cases where a formula calculation adds a new string rec - * need to explicitly set lastFormula before calling addRecord - * @param f - */ - protected void setLastFormula(Formula f) { - lastFormula = f; - } - - /** Associate a record with its containers and related records. - - */ - @Override - public BiffRec addRecord(BiffRec rec, boolean addtorec) { - short opc = rec.getOpcode(); - rec.setStreamer(streamer); - rec.setWorkBook(this); - - Boundsheet bs = null; - Long lbplypos = new Long(0l); - - // get the relevant Boundsheet for this rec - if (rec instanceof Bof) { - if (this.getFirstBof() == null) - this.setFirstBof((Bof) rec); - if (bofct == eofct) { // not a chart or other non Sheet Bof - this.setLastBOF((Bof) rec); - } - if (((Bof) rec).isChartBof()) { - inChartSubstream = true; - } - } - - if (this.lastBOF == null) - Logger.logWarn("WorkBook: NULL Last BOF"); - long lb = this.lastBOF.getLbPlyPos(); - if (!this.lastBOF.isValidBIFF8()) - lb += 8; - lbplypos = new Long(lb); // use last - - bs = getSheetFromRec(rec, lbplypos); - if (bs != null) - lbplypos = new Long(bs.getLbPlyPos()); - - if ((bs != null)) { // &&){ - lastbound = bs; - if (addtorec) - rec.setSheet(bs);// we don't include Bof or other Book-recs - // because it lives in the Streamer recvec - if /* - * ((rec.isValueForCell()) - * && - */ (!copying) { - if ((lastFormula != null) && (opc == STRINGREC)) { - lastFormula.addInternalRecord(rec); - } else if ((lastFormula != null) && (opc == ARRAY)) { - lastFormula.addInternalRecord(rec); - } else if (rec.isValueForCell()) { - if (currchart == null) - bs.addCell((CellRec) rec); - - } - } - } - - if (inChartSubstream) { - if (currchart == null) { - if (rec.getOpcode() == CHART) { - charts.add(rec); - if (bs != null) - bs.addChart((Chart) rec); - currchart = (Chart) rec; - currchart.setPreRecords(chartTemp); - chartTemp = new ArrayList(); // clear out - } else { - chartTemp.add(rec); - } - } else { - currchart.addInitialChartRecord(rec); - if (rec.getOpcode() == EOF) { - currchart.initChartRecords(); // finished - currchart = null; - inChartSubstream = false; - } - } - addtorec = false; - } - - // Rows, valrecs, dbcells, and muls are stored in the row, - // not the byte streamer - if (opc == XLSRecord.DBCELL || opc == XLSRecord.ROW - || rec.isValueForCell() || opc == XLSRecord.MULRK - /* || opc==MULBLANK */ - || opc == CHART || opc == XLSRecord.FILEPASS - || opc == XLSRecord.SHRFMLA || opc == XLSRecord.ARRAY - || opc == XLSRecord.STRINGREC) { - addtorec = false; - } - - // add it to the record stream - if (addtorec) { - - if (lbplypos.longValue() > 0) - streamer.addRecord(rec); - else - streamer.records.add(rec); - } - - switch (opc) { - case AUTOFILTER: - bs.getAutoFilters().add(rec); - break; - - case CONDFMT: - bs.getConditionalFormats().add(rec); - ((Condfmt) rec).initializeReferences(); - break; - - case CF: - Condfmt cfmt = (Condfmt) bs.getConditionalFormats() - .get(bs.getConditionalFormats().size() - 1); - cfmt.addRule((Cf) rec); - break; - - case MERGEDCELLS: - bs.addMergedCellsRec((Mergedcells) rec); - this.addMergedcells((Mergedcells) rec); - break; - - // give protection records to the relevant ProtectionManager - case PASSWORD: - case PROTECT: - case PROT4REV: - case OBJPROTECT: - case SCENPROTECT: - case FEATHEADR: - ProtectionManager manager; - if (bs != null) - manager = bs.getProtectionManager(); - else - manager = this.getProtectionManager(); - manager.addRecord(rec); - break; - - case DVAL: - if (bs != null) - bs.setDvalRec((Dval) rec); - break; - - case DV: - if (bs != null) { - if (bs.getDvalRec() != null) { - bs.getDvalRec().addDvRec((Dv) rec); - } - } - break; - - case INDEX: - Index id = (Index) rec; - id.setIndexNum(indexnum++); - indexes.add(indexes.size(), id); - this.setLastINDEX(id); - if (bs == null) { - Logger.logWarn("ERROR: WorkBook.addRecord( Index ) error: BAD LBPLYPOS. The wrong LB:" - + lbplypos); - try { - bs = this.getWorkSheetByNumber(indexnum - 1); - Logger.logInfo(" The RIGHT LB:" + bs.getLbPlyPos()); - } catch (WorkSheetNotFoundException e) { - Logger.logInfo("problem getting WorkSheetByNumber: " + e); - } - - } - bs.setSheetIDX(id); - break; - - case ROW: - Row rw = (Row) rec; - if (bs != null) - bs.addRowRec(rw); - - break; - - case FORMULA: - this.addFormula((Formula) rec); - lastFormula = (Formula) rec; - break; - - case ARRAY: - Array arr = (Array) rec; - if (bs != null) - bs.addArrayFormula(arr); - arr.setParentRec(lastFormula); // [BugTracker 1869] link array - // formula to it's parent formula - // rec - break; - - /* - * case SHRFMLA : done in shrfmla.init - * Shrfmla form = (Shrfmla) rec; - * try{ // throws exceptipon during pullparse - * form.setHostCell( lastFormula ); - * }catch(Exception e){;} - * break; - */ - - case DATE1904: - if (((NineteenOhFour) rec).is1904) { - this.dateFormat = DateConverter.DateFormat.LEGACY_1904; - } - break; - - /* - * case PALETTE : // palette now correctly read into - * COLORTABLE - * this.pal = (Palette) rec; - * break; - */ - - case HLINK: - Hlink hl = (Hlink) rec; - this.addHlink(hl); - break; - - case DSF: - Dsf dsf = (Dsf) rec; - if (dsf.fDSF == 1) { - Logger.logErr("DOUBLE STREAM FILE DETECTED!"); - Logger.logErr(" OpenXLS is compatible with Excel 97 and above only."); - throw new WorkBookException( - "ERROR: DOUBLE STREAM FILE DETECTED! OpenXLS is compatible with Excel 97 + only.", - WorkBookException.DOUBLE_STREAM_FILE); - } - break; - - case GUTS: - if (bs != null) { - bs.setGuts((Guts) rec); - } - break; - - case DBCELL: - break; - - case BOF: - if (DEBUGLEVEL > 5) - Logger.logInfo("BOF:" + bofct + " - " + rec); - if (eofct == bofct) { - if (bs != null) { - bs.setBOF((Bof) rec); - } - } - bofct++; - break; - - case EXTERNSHEET: - myexternsheet = (Externsheet) rec; - break; - - case DEFCOLWIDTH: - if (bs != null) { - bs.setDefColWidth((DefColWidth) rec); - } - break; - - case EOF: - this.lasteof = (Eof) rec; - eofct++; - if (eofct == bofct) { - if (bs != null) { - bs.setEOF((Eof) rec); - } - eofct--; - bofct--; - } - break; - - case SELECTION: // only used for Recvec index - bs.setLastselection((Selection) rec); - break; - - case COUNTRY: - // Added to save position of 1st bound sheet, which is 1 - // record - // before COUNTRY RECORD (= 2 before 1st SUPBOOK record - - // true in all cases?) - countryRec = (XLSRecord) rec; - // USA=1, Canada=1, Japan=81, China=86, Thailand= 66, Korea= - // 82, India=91 ... - this.defaultLanguage = ((Country) rec).getDefaultLanguage(); - break; - - case SUPBOOK: // KSC: must store ordinal positions of SupBooks, for - // adding Externsheets - supBooks.add(rec); - if (myADDINSUPBOOK == null) { // see if this is the ADD-IN SUPBOOK - // rec - Supbook sb = (Supbook) rec; - if (sb.isAddInRecord()) - myADDINSUPBOOK = sb; - } - break; - - case BOUNDSHEET: - Boundsheet sh = (Boundsheet) rec; - - /* - * Here we need to set the selected variable, - * but not mess with selected tabs - * when all of the sheets aren't in the book yet. - * -jm - */ - int ctab = 1; // default- select 1st sheet if no Windows1 record - if (win1 != null) {// Windows1 record is optional 20101004 - // TestCorruption.TestNPEOnOpen - ctab = win1.getCurrentTab(); - } - int shts = boundsheets.size(); - if (ctab == shts) - sh.selected = true; - - this.addWorkSheet(new Long(sh.getLbPlyPos()), sh); - break; - - case MULRK: - Mulrk mul = (Mulrk) rec; - Iterator xit = mul.getRecs().iterator(); - while (xit.hasNext()) { - this.addRecord(((Rk) xit.next()), false); - } - break; - - case SST: - this.setSharedStringTable((Sst) rec); - break; - - case EXTSST: - ((Extsst) rec).setSst(this.getSharedStringTable()); - break; - - case SXSTREAMID: - this.ptstream.add(rec); // Pivot Stream - break; - - case SXVS: - case DCONREF: - case DCONNAME: - case DCONBIN: - try { - SxStreamID sid = (SxStreamID) ptstream.get(ptstream.size() - 1); - sid.addSubrecord(rec); - } catch (Exception e) {} - break; - - case SXVIEW: - addPivotTable(((Sxview) rec)); // Pivot Table View ==Top-level - // record for a Pivot Table - break; - - // all* possible records associated with SxView (=PivotTable - // View) (*hopefully) - case SXVD: - // case SXVI: // subrecords of SxVD - // case SXVDEX: - case SXIVD: - case SXPI: - case SXDI: - case SXLI: - case SXEX: - case SXVIEWEX9: - case QSISXTAG: - try { - Sxview sx = (Sxview) this.ptViews.values() - .toArray()[this.ptViews.size() - 1]; - sx.addSubrecord(rec); - } catch (Exception e) { - ; - } - break; - - case TABID: - this.tabs = (TabID) rec; - break; - - case NAME: - addName((Name) rec); - break; - - case CALCMODE: - this.calcmoderec = (CalcMode) rec; - break; - - case WINDOW1: - this.win1 = (Window1) rec; - break; - - case WINDOW2: - if (bs != null) - bs.setWindow2((Window2) rec); - break; - - case SCL: // scl is for zoom - if (bs != null) - bs.setScl((Scl) rec); - break; - - case PANE: - if (bs != null) - bs.setPane((Pane) rec); - break; - case EXCEL2K: - xl2k = rec; - break; - - case PHONETIC: - // TODO: this isn't necessary anymore! look at and remove - if (this.currdrw != null) { - this.currdrw.setMystery((Phonetic) rec); - } - break; - - case MSODRAWINGGROUP: - if (msodg == null) - msodg = (MSODrawingGroup) rec; - msodgMerge.add(rec); - - break; - - case MSODRAWING: - rec.setSheet(bs); - if (msodg != null) - msodg.addMsodrawingrec((MSODrawing) rec); - else - ; - // do what???io.starter.toolkit.Logger.log("PROBLEM with - // MSODG!"); - break; - - case COLINFO: - bs.addColinfo((Colinfo) rec); - break; - - case USERSVIEWBEGIN: - this.usersview = (Usersviewbegin) rec; - break; - - case WSBOOL: - if (bs != null) { - bs.setWsBool((WsBool) rec); - } - break; - - // Handle continue records which are actually masked Mso's - case CONTINUE: - if (((Continue) rec).maskedMso != null) { - ((Continue) rec).maskedMso.setSheet(bs); - if (msodg != null) - msodg.addMsodrawingrec(((Continue) rec).maskedMso); - } - break; - - case XF: - try { - this.addXf((Xf) rec); - } catch (Exception e) { - ;// throws exceptions during PullParse - } - break; - - default: - // DO NOTHING - - } - - // finish up - rec.setIndex(getLastINDEX()); - rec.setXFRecord(); - return rec; - } - - /** - * Dec 15, 2010 - * @param rec - * @return - */ - @Override - public Boundsheet getSheetFromRec(BiffRec rec, Long lbplypos) { - Boundsheet bs = null; - - if (rec.getSheet() != null) { - bs = rec.getSheet(); - } else if (lbplypos != null) { - bs = this.getWorkSheet(lbplypos); - } else - bs = lastbound; - return bs; - } - - /** get a handle to the ContinueHandler - */ - @Override - public ContinueHandler getContinueHandler() { - return this.contHandler; - } - - /** get a handle to the Usersviewbegin for the workbook - */ - public Usersviewbegin getUsersviewbegin() { - if (usersview == null) { - usersview = new Usersviewbegin(); - streamer.addRecord(usersview); - this.addRecord(usersview, false); - } - return usersview; - } - - /** set the Debug level - */ - @Override - public void setDebugLevel(int i) { - this.DEBUGLEVEL = i; - } - - /** get a handle to the Reader for this - WorkBook. - */ - @Override - public void setFactory(WorkBookFactory r) { - this.factory = r; - } - - /** Get the number of worksheets in this WorkBook - */ - public int getNumWorkSheets() { - return this.boundsheets.size(); - } - - /** get the number of formulas in this WorkBook - - * - * @return - */ - public int getNumFormulas() { - return formulas.size(); - } - - /** get the number of Cells in this WorkBook - */ - public int getNumCells() { - int cellnum = 0; - Enumeration e = workSheets.elements(); - while (e.hasMoreElements()) { - Boundsheet b = (Boundsheet) e.nextElement(); - cellnum += b.getNumCells(); - } - return cellnum; - } - - /** get all of the Cells in this WorkBook - */ - public BiffRec[] getCells() { - List cellz = new FastAddVector(); - for (int i = 0; i < workSheets.size(); i++) { - try { - Boundsheet b = this.getWorkSheetByNumber(i); - BiffRec[] cz = b.getCells(); - for (int x = 0; x < cz.length; x++) { - cellz.add(cz[x]); - } - } catch (Exception e) { - Logger.logErr("Error retrieving worksheet for getCells: " + e); - } - } - BiffRec[] cellzr = new BiffRec[cellz.size()]; - cellz.toArray(cellzr); - return cellzr; - } - - /** get the cell by the following String Pattern - - BiffRec c = getCell("SheetName!C17"); - */ - public BiffRec getCell(String cellname) throws CellNotFoundException, WorkSheetNotFoundException { - int semi = cellname.indexOf("!"); - String sname = cellname.substring(0, semi); - String cname = cellname.substring(semi + 1); - return getCell(sname, cname); - } - - /** get the cell by the following String Pattern - - BiffRec c = getCell("Sheet1", "C17"); - */ - public BiffRec getCell(String sheetname, String cellname) throws CellNotFoundException, WorkSheetNotFoundException { - cellname = cellname.toUpperCase(); - try { - Boundsheet bs = this.getWorkSheetByName(sheetname); - BiffRec ret = bs.getCell(cellname); - if (ret == null) - throw new CellNotFoundException(sheetname + ":" + cellname); - return ret; - } catch (WorkSheetNotFoundException a) { - throw new WorkSheetNotFoundException(sheetname + " not found"); - } catch (NullPointerException e) { - throw new CellNotFoundException(sheetname + ":" + cellname); - } - } - - private Boundsheet lastbound = null; - - /** add a Boundsheet to the WorkBook - */ - private void addWorkSheet(Long lbplypos, Boundsheet sheet) { - if (sheet == null) { - Logger.logWarn("WorkBook.addWorkSheet() attempting to add null sheet."); - return; - } else { - this.lastbound = sheet; - if (DEBUGLEVEL > 10) - Logger.logInfo("Workbook Adding Sheet: " + sheet.getSheetName() - + ":" + String.valueOf(lbplypos)); - workSheets.put(lbplypos, sheet); - boundsheets.add(sheet); - } - } - - /** - * - * @param n - * @return - */ - public boolean removeName(Name n) { - if (this.names.contains(n)) { - this.names.remove(n); - if (n.getItab() != 0) { - try { - this.getWorkSheetByNumber(n.getItab() - 1) - .removeLocalName(n); - } catch (WorkSheetNotFoundException e) {} - } else { - this.bookNameRecs.remove(n.toString().toUpperCase()); // case-insensitive - } - } - ArrayList al = n.getIlblListeners(); - this.orphanedPtgNames.addAll(al); - - this.updateNameIlbls(); - return this.getStreamer().removeRecord(n); - } - - /** - * Add a sheet-scoped name record to the boundsheet - * - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this book, adding them here will not add them to the workbook; - * - * @param bookNameRecs - */ - public void addLocalName(Name name) { - bookNameRecs.put(name.getNameA(), name); - } - - /** - * Remove a sheet-scoped name record from the boundsheet. - * - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this book, removing them here will not remove them completely from the workbook. - * - * In order to do that you will need to call book.removeName - * - * @param bookNameRecs - */ - public void removeLocalName(Name name) { - bookNameRecs.remove(name.getNameA()); - } - - /** - * - * After any changes in the name records - * this method needs to be called in order to - * update ilbl records - - * - */ - public void updateNameIlbls() { - for (int i = 0; i < names.size(); i++) { - Name n = (Name) names.get(i); - n.updateIlblListeners(); - } - } - - /** remove a Boundsheet from the WorkBook - - */ - public void removeWorkSheet(Boundsheet sheet) { - - int sheetNum = sheet.getSheetNum(); - // remove the sheet - // automatically deletes Named ranges scoped to the sheet - Name[] namesOnSheet = sheet.getAllNames(); - for (int i = 0; i < namesOnSheet.length; i++) - this.removeName(namesOnSheet[i]); - - // Remove Externsheet ref before removing sheet - // update any Externsheet references... - try { - Externsheet ext = this.getExternSheet(); - if (ext != null) - ext.removeSheet(sheet.getSheetNum()); - } catch (WorkSheetNotFoundException e) { - Logger.logInfo("could not update Externsheet reference from " - + sheet.toString() + " : " + e.toString()); - } - - sheet.removeAllRecords(); - streamer.removeRecord(sheet); - workSheets.remove(new Long(sheet.getLbPlyPos())); - boundsheets.remove(sheet); - // we need to reset the lastbound for adding new worksheets. - // Currently assume it is - // the last one in the vector. - if (boundsheets.size() > 0) { - lastbound = (Boundsheet) (boundsheets.get(boundsheets.size() - 1)); - lastBOF = lastbound.getMyBof(); - } - - // decrement the tab ids... - this.tabs.removeRecord(); - this.updateScopedNamedRanges(); - - // update wb chart cache - remove charts referenced by - // deleted sheet - for (int i = getChartVect().size() - 1; i >= 0; i--) { - if (((Chart) this.getChartVect().get(i)).getSheet() == sheet) - this.getChartVect().remove(i); - } - - if (this.getNumWorkSheets() == 0) - return; // empty book - try { // set the next sheet selected... - while (sheetNum <= this.getNumWorkSheets()) { - Boundsheet s2 = this.getWorkSheetByNumber(sheetNum++); - s2.setSelected(true); - if (!s2.getHidden()) - break; - - } - } catch (WorkSheetNotFoundException e) { - try { - Boundsheet s2 = this.getWorkSheetByNumber(0); - s2.setSelected(true); - } catch (Exception ee) { - throw new WorkBookException( - "Invalid WorkBook. WorkBook must contain at least one Sheet.", - WorkBookException.RUNTIME_ERROR); - } ; - } - } - - /** - * Updates all the name records in the workbook that are bound to a - * worksheet scope (as opposed to a workbook scope). Name records use - * their own non-externsheet based sheet references, so need to be modified - * whenever a sheet delete (or non-last sheet insert) operation occurs - */ - private void updateScopedNamedRanges() { - for (int i = 0; i < boundsheets.size(); i++) { - ((Boundsheet) boundsheets.get(i)).updateLocalNameReferences(); - } - } - - /** returns the Boundsheet identified by its - offset to the BOF record indicating the - start of the Boundsheet data stream. - - used internally to access the Sheets to - ensure that the lbplypos is correct -- essential - to proper operation of XLS file. - - @param Long lbplypos of Boundsheet - */ - private Boundsheet getWorkSheet(Long lbplypos) { - return (Boundsheet) workSheets.get(lbplypos); - } - - /** returns the Boundsheet with the specific name - - @param String name of Boundsheet - */ - public Boundsheet getWorkSheetByName(String bstr) throws WorkSheetNotFoundException { - try { - if (bstr.startsWith("'") || bstr.startsWith("\"")) - bstr = bstr.substring(1, bstr.trim().length() - 1); - Iterator bs = boundsheets.iterator(); - while (bs.hasNext()) { - Boundsheet bsi = (Boundsheet) bs.next(); - String bsin = bsi.getSheetName(); - // TODO: check if we can have dupe names different case - if (bsin.equalsIgnoreCase(bstr)) - return bsi; - } - } catch (Exception ex) { - Logger.logWarn("WorkBook.getWorkSheetByName failed: " - + ex.toString()); - } - throw new WorkSheetNotFoundException( - "Worksheet " + bstr + " not found in " + this.toString()); - } - - /** returns the Boundsheet with the specific Hashname - - @param String hashname of Boundsheet - */ - public Boundsheet getWorkSheetByHash(String s) throws WorkSheetNotFoundException { - Boundsheet[] bs = this.getWorkSheets(); - for (int i = 0; i < bs.length; i++) { - if (bs[i].getSheetHash().equalsIgnoreCase(s)) - return bs[i]; - } - return null; - } - - /** returns the Boundsheet at the specific index - - @param int index of Boundsheet - */ - public Boundsheet getWorkSheetByNumber(int i) throws WorkSheetNotFoundException { - Boundsheet bs = null; - try { - bs = (Boundsheet) boundsheets.get(i); - } catch (ArrayIndexOutOfBoundsException e) {} ; - if (bs == null) { // External Sheet Ref NOT FOUND - throw new WorkSheetNotFoundException( - String.valueOf(i) + " not found"); - } - return bs; - } - - /** set the last processed Index record - */ - private void setLastINDEX(Index id) { - lastidx = id; - } - - Index getLastINDEX() { - return lastidx; - } - - void setSharedStringTable(Sst s) { - stringTable = s; - } - - public Sst getSharedStringTable() { - return stringTable; - } - - /** returns the Vector of Boundsheets - */ - public AbstractList getSheetVect() { - return this.boundsheets; - } - - /** returns the boundsheets for this book as an array - */ - Boundsheet[] getWorkSheets() { - Boundsheet[] ret = new Boundsheet[boundsheets.size()]; - return (Boundsheet[]) boundsheets.toArray(ret); - } - - /** set the last BOF read in the stream */ - void setLastBOF(Bof b) { - lastBOF = b; - } - /** return the last BOF read in the stream */ - // Bof lastBOF{return lastBOF;} - - /** get a handle to the first BOF to perform offset functions which don't know where the - start of the file is due to the compound file format. - - Referred to in Boundsheet as the 'lbPlyPos', this - is the position of the BOF for the Boundsheet relative - to the *first* BOF in the file (the firstBOF of the WorkBook) - - @see Boundsheet - */ - @Override - public void setFirstBof(Bof b) { - firstBOF = b; - } - - Bof getFirstBof() { - return firstBOF; - } - - @Override - public String toString() { - return this.getFileName(); - } - - @Override - public String getFileName() { - if (this.factory != null) // 2003-vers - return this.factory.getFileName(); - return "New Spreadsheet"; - } - - /** Returns whether the sheet selection tabs should be shown. */ - public boolean showSheetTabs() { - return win1.showSheetTabs(); - } - - /** Sets whether the sheet selection tabs should be shown. */ - public void setShowSheetTabs(boolean show) { - win1.setShowSheetTabs(show); - } - - /** set the first visible tab - */ - public void setFirstVisibleSheet(Boundsheet bs2) { - win1.setFirstTab(bs2.getSheetNum()); - } - - // Associate related records - /** return the XF record at the specified index - */ - public Xf getXf(int i) { - if (xfrecs.size() < (i - 1)) - return null; - return (Xf) xfrecs.get(i); - } - - public int getNumXfs() { - return xfrecs.size(); - } - - /** - * InsertXF inserts an XF record into the workbook level stream, - * For some reason, the addXf only puts it into an array that is never accessed - * on output. This may have a reason, so I am not overwriting it currently, but - * let's check it out? - * - * */ - int insertXf(Xf x) { - int insertIdx = this.getXf(this.getNumXfs() - 1).getRecordIndex(); - // perform default add rsec actions - this.getStreamer().addRecordAt(x, insertIdx + 1); - this.addRecord(x, false); // updates xfrecs + formatcache - x.ixfe = x.tableidx; - return x.tableidx; - } - - /** - * internally used in preparation for reading an 2007 and above workbook - */ - public void removeXfRecs() { - // must keep the 1st xf rec as default - for (int i = xfrecs.size() - 1; i > 0; i--) { - Xf xf = (Xf) xfrecs.get(i); - this.streamer.removeRecord(xf); - xfrecs.remove(i); - - } - } - - /** - * TODO: Does this function as desired? See comment for insertXf() above... - * tracks existing xf recs, used when testing whether xfrec exists or not ... - * -NR 1/06 - * ya should - called now from addRecord every time an xf record is added - * NOTE: this is the only place addXf is called - * - * @param xf - * @return - */ - int addXf(Xf xf) { - xfrecs.add(xf); - xf.tableidx = xfrecs.size() - 1; // flag that it's been added to records - this.updateFormatCache(xf); // links tostring of xf to xf rec for - // updating/reuse purposes - return xf.tableidx; - } - - /** - * formatCache: - * links tostring of xf to xf rec for updating/reuse purposes - * - * @see FormatHandle.updateXf - * @see WorkBook.addXf - * @param xf - */ - public void updateFormatCache(Xf xf) { - if (xf.tableidx != -1) { // if this xf has been already added to the - // workbook - if (formatCache.containsValue(xf)) { // xf signature has - // changed/it's been updated - Iterator ii = formatCache.keySet().iterator(); // remove and - // update below - while (ii.hasNext()) { - String key = (String) ii.next(); - Xf x = (Xf) formatCache.get(key); - if (x.equals(xf)) { - formatCache.remove(key); - break; - } - } - } - String formatStr = xf.toString(); - - if (!formatCache.containsKey(formatStr)) - formatCache.put(formatStr, xf); - } - } - - /** - * retrieve the format cache - links string vers. of xf to xf rec - * used for resusing xf's - * @see FormatHandle.updateXf - * @return - */ - public HashMap getFormatCache() { - return formatCache; - } - - /** Get a substream by name.*/ - @Override - public ByteStreamer getStreamer() { - return this.streamer; - } - - public void setSubStream(ByteStreamer s) { - this.streamer = s; - } - - /** Get the typename for this object.*/ - String getTypeName() { - return "WorkBook"; - } - - /** Write the contents of the WorkBook bytes to an OutputStream - - * - * @param _out - */ - @Override - public int stream(OutputStream _out) { - return streamer.streamOut(_out); - } - - /** get a handle to the factory*/ - @Override - public WorkBookFactory getFactory() { - return this.factory; - } - - /** - * Copies a complete boundsheet within the workbook - * - * If a name exists that refers directly to this sheet then duplicate it, otherwise workbook scoped names are - * not copied - */ - public void copyWorkSheet(String SourceSheetName, String NewSheetName) throws Exception { - Boundsheet origSheet = null; - origSheet = this.getWorkSheetByName(SourceSheetName); - List chts = origSheet.getCharts(); // 20080630 KSC: Added - for (int i = 0; i < chts.size(); i++) { - Chart cxi = (Chart) chts.get(i); - cxi.populateForTransfer(); - } - byte[] inbytes = origSheet.getSheetBytes(); - this.addBoundsheet(inbytes, SourceSheetName, NewSheetName, null, false); - Boundsheet bnd = this.getWorkSheetByName(NewSheetName); - // handle moving the built-in name records. These handle - // such items as print area, header/footer, etc - Name[] ns = this.getNames(); - for (int i = 0; i < ns.length; i++) { // 20100404 KSC: take out +1? - if (ns[i].getItab() == origSheet.getSheetNum() + 1) { - // it's a built in record, move it to the new sheet - int sheetnum = bnd.getSheetNum(); - int xref = this.getExternSheet(true) - .insertLocation(sheetnum, sheetnum); - Name n = (Name) ns[i].clone(); - n.setExternsheetRef(xref); - n.updateSheetReferences(bnd); - n.setSheet(bnd); - n.setItab((short) (bnd.getSheetNum() + 1)); - this.insertName(n); - } - } - } - - /** - * Inserts a newly created Name record into the correct location in the streamer. - - * - * @param n - */ - public void insertName(Name n) { - int namepos = -1; - Name[] nmx = getNames(); - if (nmx.length > 0) { - if (nmx[nmx.length - 1].getSheet() != null) { - namepos = nmx[nmx.length - 1].getRecordIndex(); - } else { - namepos = getExternSheet(true).getRecordIndex() + nmx.length; - } - } else { - namepos = getExternSheet(true).getRecordIndex(); - } - namepos++; - getStreamer().addRecordToBookStreamerAt(n, namepos); - addRecord(n, false); - } - - /** Copies an existing Chart to another WorkSheet - * - * @param chartname - * @param sheetname - */ - public void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException { - Chart ct = this.getChart(chartname); - Boundsheet sht = this.getWorkSheetByName(sheetname); - byte[] bt = ct.getChartBytes(); - sht.addChart(bt, chartname, ct.getCoords()); - } - - /** Inserts a serialized boundsheet chart into the workboook - */ - public Chart addChart(Chart destChart, String NewChartName, Boundsheet boundsht) { - destChart.setWorkBook(this); - destChart.setSheet(boundsht); - List recs = destChart.getXLSrecs(); - for (int x = 0; x < recs.size(); x++) { - XLSRecord rec = (XLSRecord) recs.get(x); - rec.setWorkBook(this); - rec.setSheet(boundsht); - if (rec.getOpcode() == MSODRAWING) { - addChartUpdateMsodg((MSODrawing) rec, boundsht); - continue; - } - if (!(rec instanceof Bof)) // TODO: error/problem with the BOF - // record!!! - rec.init(); - if (rec instanceof Dimensions) - destChart.setDimensions((Dimensions) rec); - try { - ((GenericChartObject) rec).setParentChart(destChart); - } catch (ClassCastException e) { // Scl, Obj and others are not - // chart objects - } - - } - destChart.setTitle(NewChartName); - destChart.setId(boundsht.lastObjId + 1); // track last obj id per sheet - // ... - this.charts.add(destChart); - boundsht.getCharts().add(destChart); // should really have two lists??? - return destChart; - } - - /** - * updates Mso (MSODrawingGroup + Msodrawing) records upon add/copy worksheet and add/copy charts - * NOTE: this code is mainly garnered via trial and error, works - * @param mso Msodrawing record that is being added or copied - * @param sht Boundsheet - */ - public void addChartUpdateMsodg(MSODrawing mso, Boundsheet sht) { - if (msodg == null) { - this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup - .getPrototype()); - msodg.initNewMSODrawingGroup(); // generate and add required records - // for drawing records - } - msodg.addMsodrawingrec(mso); - MSODrawing hdr = msodg.getMsoHeaderRec(sht); - if (hdr != null && hdr != mso) { // already have a header rec - if (sht.getCharts().size() > 0) { - mso.makeNonHeader(); - hdr.setNumShapes(hdr.getNumShapes() + 1); - } - } else if (hdr == null) { - mso.setIsHeader(); - hdr = mso; - } - this.updateMsodrawingHeaderRec(sht); - msodg.dirtyflag = true; // flag to reset SPIDs on write - msodg.setSpidMax(++lastSPID); - msodg.updateRecord(); - } - - /** JM - - * Add the requisite records in the book streamer for the chart. \ - * Supbook, externsheet & msodrawinggroup - * - * I think this is due to the fact that the referenced series are usually stored - * in the fashon 'Sheet1!A4:B6' The sheet1 reference requires a supbook, though the - * reference is internal. - */ - public void addPreChart() { - this.addExternsheet(); - if (msodg == null) { - this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup - .getPrototype()); - msodg.initNewMSODrawingGroup(); // generate and add required records - // for drawing records - } - - } - - /** - * remove an existing chart from the workbook - * NOTE: STILL EXPERIMENTAL TESTS OK IN BASIC CIRCUMSTANCES BUT MUST BE TESTED FURTHER - */ - public void deleteChart(String chartname, Boundsheet sheet) throws ChartNotFoundException { - Chart chart = this.getChart(chartname); - // TODO: Update Dimensions record?? - List recs = chart.getXLSrecs(); - // first rec SHOULD BE MsoDrawing!!! - try { - MSODrawing rec = (MSODrawing) recs.get(0); - msodg.removeMsodrawingrec(rec, sheet, true); // also remove - // associated Obj - // record - } catch (Exception e) { - Logger.logErr("deleteChart: expected Msodrawing record"); - } - /* - * shouldn't be necessary to remove chart recs as they are - * separated upon init of workbook and reassebmbled upon - * write - */ - this.removeChart(chartname); - } - - /** - * Inserts a serialized boundsheet into the workboook. - * - * @param inbytes original sheet bytes - * @param NewSheetName new Sheet Name - * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * - * @throws ClassNotFoundException - * @throws IOException - * @boolean SSTPopulatedBoundsheet - a boundsheet that has all of it's sst data saved off in LabelSST records. - * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. - * Do not use this if the data already exists in the SST, you are just causing bloat! - */ - public void addBoundsheet(byte[] inbytes, String origSheetName, String NewSheetName, String origWorkBookName, boolean SSTPopulatedBoundsheet) throws IOException, ClassNotFoundException { - Boundsheet destSheet = null; - ByteArrayInputStream bais = new ByteArrayInputStream(inbytes); - BufferedInputStream bufstr = new BufferedInputStream(bais); - ObjectInputStream o = new ObjectInputStream(bufstr); - destSheet = (Boundsheet) o.readObject(); - - if (destSheet != null) { - this.addBoundsheet(destSheet, origSheetName, NewSheetName, origWorkBookName, SSTPopulatedBoundsheet); - } - } - - /** change the tab order of a boundsheet - */ - public void changeWorkSheetOrder(Boundsheet bs, int idx) { - // reorder the sheet vector - if (idx >= 0 && idx < boundsheets.size()) { - boundsheets.remove(bs); - boundsheets.add(idx, bs); - for (int x = 0; x < boundsheets.size(); x++) { - Boundsheet bs1 = (Boundsheet) boundsheets.get(x); - boolean udpatewin1 = bs1.selected(); - if (udpatewin1) - bs1.setSelected(true); - } - } - - int insertLoc = Integer.MAX_VALUE; - // remove the existing boundsheet records in the streamer - for (int i = 0; i < boundsheets.size(); i++) { - Boundsheet bound = (Boundsheet) boundsheets.get(i); - int position = bound.getRecordIndex(); - insertLoc = Math.min(insertLoc, position); - streamer.removeRecord((XLSRecord) boundsheets.get(i)); - } - // enter the boundsheet records back in the streamer in - // correct order - for (int i = 0; i < boundsheets.size(); i++) { - Boundsheet bound = (Boundsheet) boundsheets.get(i); - streamer.addRecordAt(bound, insertLoc + i); - } - } - - /** add a deserialized boundsheet to this workbook. - * - * @param bound new (copied) sheet - * @param newSheetName new sheetname - * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * - * @boolean SSTPopulatedBoundsheet - the boundsheet has all of it's sst data saved off in LabelSST records. - * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. - * Do not use this if the data already exists in the SST, you are just causing bloat! - */ - public void addBoundsheet(Boundsheet bound, String origSheetName, String newSheetName, String origWorkBookName, boolean SSTPopulatedBoundsheet) { - bound.streamer = streamer; - boolean old_allowdupes = this.isSharedupes(); - this.setDupeStringMode(ALLOWDUPES); - - bound.mc.clear(); - bound.setWorkBook(this); - - // Check if sheetname already exists! - try { - while (this.getWorkSheetByName(newSheetName) != null) - newSheetName = newSheetName + "Copy"; - } catch (WorkSheetNotFoundException we) { - ; - /* good !!! */ } - bound.setSheetName(newSheetName); - - // get a hold of the lbplypos number that we will need for - // the new boundsheet - int recvecOffset = streamer.getRecVecSize() - 1; - XLSRecord x = null; - if (lastbound != null) { - try { // lastbound must be reset because other operations could - // alter - lastbound = this - .getWorkSheetByNumber(this.getNumWorkSheets() - 1); - x = (XLSRecord) lastbound.getSheetRecs() - .get(lastbound.getSheetRecs().size() - 1); - } catch (Exception e) { - ; - } - } else if (countryRec != null) { - x = (XLSRecord) streamer.getRecordAt(countryRec.getRecordIndex()); - } else - x = this.lasteof; - // last record is a junkrec. We are going to move that down - // and put in the new BOF here - // TODO: recvecOffset position when no sheets?????? - if (x.getOpcode() != EOF) { - recvecOffset -= 1; - } - int newloc = x.offset; - // modify the boundsheet rec for its new location/info/name - int listenerpos = -1, newoffset = -1; - if (lastbound != null) { - listenerpos = lastbound.getRecordIndex(); - newoffset = lastbound.offset + lastbound.getLength() + 4; - // offset + reclen + headerlen - } else if (x != null) { - listenerpos = x.getRecordIndex() - 1; // account for +1 below - newoffset = x.offset + x.getLength() + 4; - } else { - listenerpos = recvecOffset - 1; - newoffset = streamer.getRecordAt(recvecOffset).getLength() + 4; - } - - // put the serialized recs from localrecs into the normal - // SheetRecs - bound.setLocalRecs(new FastAddVector()); // reset localrecs - List newrecs = bound.getSheetRecs(); - Bof newbof = (Bof) newrecs.get(0); - - newloc += newbof.getLength() + 4; - newbof.setOffset(newloc); - bound.setBOF(newbof); - this.addRecord(newbof, false); - - recvecOffset += 1; // move it past that last Eof - newoffset = newloc + newbof.getLength() + 4; - lastbound = bound; - // insert the actual boundsheet record into the recvec - streamer.addRecordAt(bound, listenerpos + 1); - - this.addRecord(bound, false); - - // modify the TabID record to reflect new sheet - tabs.addNewRecord(); - recvecOffset = newbof.getRecordIndex(); - - int tout = 0; - copying = true; - - // Add an externsheet ref for the new sheet - if (this.myexternsheet == null) - this.addExternsheet(); - try { - int sheetref = this.getNumWorkSheets() - 1; - myexternsheet.insertLocation(sheetref, sheetref); - } catch (Exception e) { - Logger.logWarn("Adding new sheetRef failed in addBoundsheet()" - + e.toString()); - } - - // update the chart references + add to wb - List chts = bound.getCharts(); - for (int i = 0; i < chts.size(); i++) { - Chart chart = (Chart) chts.get(i); // obviously algorithm has - // changed and chart is NOT - // removed :) [discovered by - // Shigeo/Infoteria/formatbroken273193.sce] - // // 20080702 KSC: since it's - // removed, don't inc index - chart.updateSheetRefs(bound.getSheetName(), origWorkBookName); - this.charts.add(chart); - } - - bound.lastObjId = 1; // see if resetting obj id helps in file open - // errors; if so, must reset Note obj id's as - // well ... - - /********** This loop handles Boundsheet records contained in the sheet level streamer, that is, not the valrecs *****/ - int numrecs = newrecs.size(); - for (int z = 1; z < numrecs; z++) { - XLSRecord xl = (XLSRecord) newrecs.get(z); - this.addRecord(xl, false); - if (DEBUGLEVEL > 5) - try { - Logger.logInfo("Copying: " + xl.toString() + ":" - + String.valueOf(newoffset) + ":" + xl.getLength()); - } catch (Exception e) { - ; - } - if (xl instanceof Codename) { - Codename secretagent = (Codename) xl; // lol -nr - secretagent.setName(newSheetName); - } else if (xl instanceof Name) { - // Name records specify data ranges -- update to point to - // new sheet - Name n = (Name) xl; - int refnum = myexternsheet.getcXTI(); - n.setExternsheetRef(refnum); - } else if (xl instanceof Cf) { // must check Conditional Format - // formula refs and handle any - // external references - try { - updateFormulaPtgRefs(((Cf) xl) - .getFormula1(), origSheetName, newSheetName, origWorkBookName); - // NOTE: FORMULA2 can be null -- TODO: should check here - updateFormulaPtgRefs(((Cf) xl) - .getFormula2(), origSheetName, newSheetName, origWorkBookName); - } catch (Exception e) {} - } else if (xl.getOpcode() == OBJ) { - ((Obj) xl).setObjId(bound.lastObjId++); - } else if (xl.getOpcode() == MSODRAWING - || (xl.getOpcode() == CONTINUE - && ((Continue) xl).maskedMso != null)) { // 20100510 - // KSC: - // handle - // masked - // mso's - MSODrawing mso; - if (xl.getOpcode() == MSODRAWING) - mso = (MSODrawing) xl; - else - mso = ((Continue) xl).maskedMso; - if (msodg == null) { - this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup - .getPrototype()); - msodg.initNewMSODrawingGroup(); // generate and add required - // records for drawing - // records - msodg.addMsodrawingrec(mso); // only add when msodg is null - // b/c otherwise it's added - // via the addRecord - // statement above - } - if (mso.getImageIndex() > 0) { // add image bytes as well, if - // any - ImageHandle im = bound - .getImageByMsoIndex(mso.getImageIndex()); - int idx = msodg.addImage(im.getImageBytes(), im - .getImageType(), false); - bound.imageMap.put(im, Integer.valueOf(idx)); // 20100518 - // KSC: - // makes - // more - // sense? - // im.getImageIndex())); - // // add - // new image - // to map - // and link - // to actual - // imageIndex - // - moved - // from - // above - if (idx != mso.getImageIndex()) - mso.updateImageIndex(idx); - } - mso.setSPID(this.lastSPID); - msodg.setSpidMax(++this.lastSPID); - // resets drawing id's - necessarily correct? - // msodg.dirtyflag= true; // flag to reset SPIDs on write - } - xl.setOffset(newoffset); - tout += xl.getLength(); - newoffset += xl.getLength(); - } - if (msodg != null) {// Moved from above so don't udpate at every mso - // addition - // necessary? all mso sub-records on the sheet should have - // stayed the same ...this.updateMsodrawingHeaderRec(bound); - msodg.updateRecord(); - } - /*************** END handling of boundsheet streamer records *************************/ - - /*************** HANDLE Formats + PtgRefs in Cell Records ****************************/ - updateTransferedCellReferences(bound, origSheetName, origWorkBookName); - - // associate the records in the sheet - this.setSharedupes(old_allowdupes); - - if (SSTPopulatedBoundsheet) { - // bring over the sst - Sst sst = this.getSharedStringTable(); - BiffRec[] b = bound.getCells(); - for (int i = 0; i < b.length; i++) { - b[i].setWorkBook(this); - if (b[i].getOpcode() == XLSConstants.LABELSST) { - Labelsst s = (Labelsst) b[i]; - s.insertUnsharedString(sst); - } - } - } - - if (this.getNumWorkSheets() > 1) - bound.setSelected(false); - else - bound.setSelected(true); - - if (DEBUGLEVEL > 5) - Logger.logInfo("changesize for new boundsheet: " - + bound.getSheetName() + ": " + tout); - copying = false; - } - - /** - * traverses all rows and their associated cells in the newly transfered sheet, - * ensuring formula/cell references and format references are correctly transfered - * into the current workbook - * - * @param bound source sheet - */ - private void updateTransferedCellReferences(Boundsheet bound, String origSheetName, String origWorkBookName) { - HashMap localFonts = (HashMap) this.getFontRecsAsXML(); - List boundFonts = bound.getTransferFonts(); // ALL fonts in the source - // workbook - HashMap localXfs = (HashMap) this.getXfrecsAsString(); - List boundXfs = bound.getTransferXfs(); - // Set the workbook on all the cells - Row[] rows = bound.getRows(); - for (int i = 0; i < rows.length; i++) { - rows[i].setWorkBook(this); - if (rows[i].getIxfe() != this.getDefaultIxfe()) - transferFormatRecs(rows[i], localFonts, boundFonts, localXfs, boundXfs); // 20080709 - // KSC: - // handle - // default - // ixfe - // for - // row - Iterator rowcells = rows[i].getCells().iterator(); - Mulblank aMul = null; - short c = 0; - while (rowcells.hasNext()) { - BiffRec b = (BiffRec) rowcells.next(); - if (b.getOpcode() == MULBLANK) { - if (aMul == b) - c++; - else { - aMul = (Mulblank) b; - c = (short) aMul.getColFirst(); - } - aMul.setCurrentCell(c); - } - b.setWorkBook(this); // Moved to before updateFormulaPtgRefs - // [BugTracker 1434] - if (b instanceof Formula) { // Examine Ptg Refs to handle - // external sheet references not - // contained in this workbook - updateFormulaPtgRefs((Formula) b, origSheetName, bound - .getSheetName(), origWorkBookName); - if (((Formula) b).shared != null) - ((Formula) b).shared.setWorkBook(this); - - } - // 20080226 KSC: transfer format, fonts and xf here instead - // of populateWorkbookWithRemoteData() - transferFormatRecs(b, localFonts, boundFonts, localXfs, boundXfs); - } - } - // 20080226 KSC: handle xf's for columns - for (Colinfo co : bound.getColinfos()) { - transferFormatRecs(co, localFonts, boundFonts, localXfs, boundXfs); - } - List c = bound.getCharts(); - for (int i = 0; i < c.size(); i++) { - Chart cht = (Chart) c.get(i); - ArrayList fontrefs = cht.getFontxRecs(); - for (int x = 0; x < fontrefs.size(); x++) { - Fontx fontx = (Fontx) fontrefs.get(x); - int fid = fontx.getIfnt(); - if (fid > 3) { - fid = bound.translateFontIndex(fid, localFonts); - fontx.setIfnt(fid); - } - } - } - } - - /** - * examine all Ptg's referenced by this formula, looking for hanging or missing sheet references - * if found, sets sheet reference to the current sheet (TODO: a better way?) - * - * @param f Formula Rec - */ - private void updateFormulaPtgRefs(Formula f, String origSheetName, String newSheetName, String origWorkBookName) { - try { - if (f == null) - return; // 20100222 KSC - f.populateExpression(); - Ptg[] p = f.getCellRangePtgs(); - for (int k = 0; k < p.length; k++) { - if (p[k] instanceof PtgRef) { - PtgRef ptg = (PtgRef) p[k]; - try { - if (!(ptg instanceof PtgArea3d) || ((PtgArea3d) ptg) - .getFirstSheet() - .equals(((PtgArea3d) ptg).getLastSheet())) { - String sheetName = ptg.getSheetName(); - if (sheetName.equals(origSheetName)) - ptg.setSheetName(newSheetName); - ptg.addToRefTracker(); - /* - * changed to use above. don't understand this: - * if (!sheetName.equals(origSheetName)) { - * this.getWorkSheetByName(ptg.getSheetName()); - * ptg.setSheetName(newSheetName); - * } else - * ptg.setSheetName(newSheetName); - */ - } else { // uncommon case of two sheet range - PtgArea3d pref = (PtgArea3d) ptg; - // this.getWorkSheetByName(pref.getFirstPtg().getSheetName()); - // don't understand this - // this.getWorkSheetByName(pref.getLastPtg().getSheetName()); - ptg.setLocation(ptg.toString()); // reset ixti if - // nec. - } - } catch (WorkSheetNotFoundException we) { - Logger.logWarn("External Reference encountered upon updating formula references: Worksheet Reference Found: " - + ptg.getSheetName()); - ptg.setExternalReference(origWorkBookName); - } - } else if (p[k] instanceof PtgExp) { - PtgExp ptgexp = (PtgExp) p[k]; - try { - Ptg[] pe = ptgexp.getConvertedExpression(); // will fail - // if - // ShrFmla - // hasn't - // been - // input yet - for (int j = 0; j < pe.length; j++) { - if (pe[j] instanceof PtgRef) { - PtgRef ptg = (PtgRef) pe[j]; - try { - if (ptg instanceof PtgArea3d) { // PtgRef3d, - // etc. - this.getWorkSheetByName(ptg - .getSheetName()); - ptg.setLocation(ptg.toString()); // reset - // ixti - // if - // nec. - } - // otherwise, we're good - } catch (WorkSheetNotFoundException we) { - Logger.logWarn("External References Not Supported: UpdateFormulaReferences: External Worksheet Reference Found: " - + ptg.getSheetName()); - ptg.setExternalReference(origWorkBookName); - } - } - } - } catch (Exception e) { - // if links to "main" ShrFmla, won't be set yet and will - // give exception - see Shrfmla WorkBook.addRecord - } - } - } - } catch (Exception e) { - Logger.logErr("WorkBook.updateFormulaRefs: error parsing expression: " - + e); - } - } - - /** - * given a record in an previously external workbook, ensure that xf and font records - * are correctly input into the current workbook and that the pointers are correctly updated - * - * @param b BiffRec - * @param localFonts HashMap of string version of all fonts, font nums in current workbook - * @param boundFonts List of string version of all fonts, font nums in external workbook - * @param localXfs HashMap of string version of all xfs, xf nums in current workbook - * @param boundXfs List of string version of all xfs, xf nums in external workbook - */ - private void transferFormatRecs(BiffRec b, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { - int oldXfNum = b.getIxfe(); - int localNum = this - .transferFormatRecs(oldXfNum, localFonts, boundFonts, localXfs, boundXfs); - if (localNum != -1) - b.setIxfe(localNum); - } - - /** given a record in an previously external workbook, ensure that xf and font records - * are correctly input into the current workbook and that the pointers are correctly updated - * - * @param b BiffRec - * @param localFonts HashMap of string version of all fonts, font nums in current workbook - * @param boundFonts List of string version of all fonts, font nums in external workbook - * @param localXfs HashMap of string version of all xfs, xf nums in current workbook - * @param boundXfs List of string version of all xfs, xf nums in external workbook - */ - private int transferFormatRecs(int oldXfNum, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { - int localNum = -1; - if (boundXfs.size() > oldXfNum) {// if haven't populatedForTransfer i.e. - // haven't opted to transfer formats - // ... - Xf origxf = (Xf) boundXfs.get(oldXfNum); // clone xf so modifcations - // don't affect original - if (origxf != null) { - /** FONT **/ - // must handle font first in order to create xf below - // see if referenced xf + fonts are already in workbook; if - // not, add - int localfNum; - // check to see if the font needs to be added - int fnum = origxf.getIfnt(); - if (fnum > 3) - fnum--; - Font thisFont = (Font) boundFonts.get(fnum); - String xmlFont = "<" + thisFont.getXML() + "/>"; - Object fontNum = localFonts.get(xmlFont); - if (fontNum != null) { // then get the fontnum in this book - localfNum = ((Integer) fontNum).intValue(); - } else { // it's a new font for this workbook, add it in - localfNum = this.insertFont(thisFont) + 1; - localFonts.put(xmlFont, Integer.valueOf(localfNum)); - } - - /** XF **/ - Xf localxf = FormatHandle - .cloneXf(origxf, origxf.getFont(), this); // clone xf so - // modifcations - // don't - // affect - // original - // input "local" versions of format and font - - /** FORMAT **/ - Format fmt = origxf.getFormat(); // number format - is null if - // format is general ... - if (fmt != null) // add if necessary - localxf.setFormatPattern(fmt.getFormat()); // adds new - // format - // pattern if - // not found - localxf.setFont(localfNum); - - // now check out to see if xf needs to be added - String xmlxf = localxf.toString(); - Object xfNum = localXfs.get(xmlxf); - if (xfNum == null) { // insert it into the book - localNum = this.insertXf(localxf); - localXfs.put(xmlxf, Integer.valueOf(localNum)); - } else // already exists in the destination - localNum = ((Integer) xfNum).intValue(); - - } - } - return localNum; - } - - public void setStringEncodingMode(int mode) { - this.getSharedStringTable().setStringEncodingMode(mode); - } - - public void setDupeStringMode(int mode) { - if (mode == ALLOWDUPES) - this.setSharedupes(false); - else if (mode == SHAREDUPES) - this.setSharedupes(true); - } - - /** Returns a Chart Handle - @return ChartHandle a Chart in the WorkBook - */ - public Chart getChart(String chartname) throws ChartNotFoundException { - AbstractList cv = this.getChartVect(); - Chart cht = null; - // Get by MSODG Drawing Name - for (int x = 0; x < cv.size(); x++) { - cht = (Chart) cv.get(x); - MSODrawing titlemso = cht.getMsodrawobj(); - if (titlemso != null) { - String mson = titlemso.getName(); // shapeName; - if (mson.equalsIgnoreCase(chartname)) - return cht; - } - } - boolean untitled = chartname.equals("[Untitled]"); - // Try to get by title - for (int x = 0; x < cv.size(); x++) { - cht = (Chart) cv.get(x); - String cname = cht.getTitle(); - if (cname.equalsIgnoreCase(chartname)) - return cht; - else if (untitled && cname.equals("")) - return cht; - } - throw new ChartNotFoundException(chartname); - } - - /** - * removes the desired chart from the list of charts - * @param chartname - * @throws ChartNotFoundException - */ - public void removeChart(String chartname) throws ChartNotFoundException { - AbstractList cv = this.getChartVect(); - Chart cht = null; - for (int x = 0; x < cv.size(); x++) { - cht = (Chart) cv.get(x); - if (cht.getTitle().equalsIgnoreCase(chartname)) { - cv.remove(x); - return; - } - } - throw new ChartNotFoundException(chartname); - } - - /** - * NOT 100% IMPLEMENTE YET - * creates the initial records for a Pivot Cache - *
                A Pivot Cache identifies the data used in a Pivot Table - *
                NOTE: only SHEET cache sources are supported at this time - * @param ref String reference: either reference or named range - * @param sheetName String sheetname - * @param cacheid if > 0, the desired cacheid (useful only in OOXML parsing) - * @return int cacheid - */ - public int addPivotStream(String ref, String sheetName, int sid) { - // in wb substream, DIRECTLY AFTER STYLE records: - // STYLE/STYLEEX [TableStyle TableStyleElement] [Palette] - // [ClrtClient] - if (sid < 0) - sid = 0; // initial cache id if none already present - List records = this.getStreamer().records; - int z = -1; - for (int i = records.size() - 1; i > 0 && z == -1; i--) { - int opcode = ((BiffRec) records.get(i)).getOpcode(); - if (opcode == SXADDL) { // find last cache id and increment - /* - * while (i > 0 && opcode!=SXSTREAMID) - * opcode= ((BiffRec) records.get(i--)).getOpcode(); - * if (opcode==SXSTREAMID) { - * sid= ((SxStreamID) records.get(i+1)).getStreamID() + 1; - * } - */ - z = i + 1; - } else if (opcode == 4188) // ClrtClient - z = i + 1; - else if (opcode == PALETTE) - z = i + 1; - else if (opcode == 2192) // TableStyleElement - z = i + 1; - else if (opcode == 2194) // StyleEx - z = i + 1; - else if (opcode == STYLE) // Style - z = i + 1; - } - - TableStyles tx = (TableStyles) TableStyles.getPrototype(); // see if - // this is - // really - // necessary - // ... - this.getStreamer().addRecordAt(tx, z++); - SxStreamID sxid = (SxStreamID) SxStreamID.getPrototype(); - this.getStreamer().addRecordAt(sxid, z++); - this.ptstream.add(sxid); // Pivot Cache - - sxid.setStreamID(sid); // cache id - this.getStreamer().records - .addAll(z, sxid.addInitialRecords(this, ref, sheetName)); - - return sid; - } - - /** - * adds the Pivot Cache Directory Storage +Stream records necessary to - * define the pivot cache (==pivot table data) for pivot table(s) - *
                NOTE: at this time only 1 pivot cache is supported - * - * @param ref Cell Range which identifies pivot table data range - * @param wbh - * @param sId Stream or cachid Id -- links back to SxStream set of records - */ - public void addPivotCache(String ref, WorkBookHandle wbh, int sId) { - if (ptcache == null) { - ptcache = new PivotCache(); - ptcache.createPivotCache(factory.myLEO - .getDirectoryArray(), wbh, ref, sId); - } - } - - /** - * returns the start of the stream defining the desired pivot cache - * @param cacheid - * @return - */ - public SxStreamID getPivotStream(int cacheid) { - // int z= 0; - for (int i = 0; i < ptstream.size(); i++) { - int sid = ((SxStreamID) ptstream.get(i)).getStreamID(); - if (sid == cacheid) - // if (z++==cacheid) - return (SxStreamID) ptstream.get(i); - } - /* - * List records= this.getStreamer().records; - * for (int i= 0; i < records.size(); i++) { - * int opcode= ((BiffRec) records.get(i)).getOpcode(); - * if (opcode==SXSTREAMID) { - * int sid= ((SxStreamID) records.get(i)).getStreamID() + 1; - * if (sid==cacheid) - * return (SxStreamID) records.get(i); - * } - * } - */ - return null; - } - - /** - * @return - */ - public AbstractList getHlinklookup() { - return hlinklookup; - } - - /** - * @return - */ - public AbstractList getMergecelllookup() { - return mergecelllookup; - } - - public void addIndirectFormula(Formula f) { - indirectFormulas.add(f); - } - - /** - * Initialize the indirect functions in this workbook by calculating the formulas - * - * */ - public void initializeIndirectFormulas() { - Iterator i = indirectFormulas.iterator(); // contains all INDIRECT - // funcvars + params - while (i.hasNext()) { - Formula f = (Formula) i.next(); - f.calculateIndirectFunction(); - } - indirectFormulas = new ArrayList(); // clear out - } - - /** - * Inserts an externsheet into the recvec, provided one does not yet exist. - * also calls add supBook - * - */ - public void addExternsheet() { - if (myexternsheet == null) { - int numsheets = this.getNumWorkSheets(); - Supbook sb = (Supbook) Supbook.getPrototype(numsheets); - // put it in after the last boundsheet record - try { - Boundsheet b = this.getWorkSheetByNumber(numsheets - 1); - int loc = b.getRecordIndex() + 1; - if (streamer.getRecordAt(loc).getOpcode() == COUNTRY) - loc++; - streamer.addRecordAt(sb, loc); // 20080306 KSC: do first 'cause - // externsheet now references - // global sb store - this.addRecord(sb, false); - Externsheet ex = (Externsheet) Externsheet - .getPrototype(0, 0, this); - streamer.addRecordAt(ex, loc + 1);// 20080306 KSC: must inc loc - // since now inserting after - // sb - this.addRecord(ex, false); - myexternsheet = ex; - } catch (WorkSheetNotFoundException e) { - Logger.logWarn("WorkBook.addExternSheet() locating Sheet for adding Externsheet failed: " - + e); - } - - } - } - - /** - * Sets the OpenXLS calculation mode for the workbook. - * @see WorkBookHandle.setFormulaCalculationMode() - * @param CalcMode - */ - public void setCalcMode(int mode) { - CalcMode = mode; - } - - /** - * Sets the calculation mode for the workbook. - * @see WorkBookHandle.setFormulaCalculationMode() - * @param CalcMode - */ - public int getCalcMode() { - return CalcMode; - } - - /** - * @return Returns the xfrecs. - */ - public AbstractList getXfrecs() { - return xfrecs; - } - - /** - * Return the font records - */ - public AbstractList getFontRecs() { - return fonts; - } - - /** - * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. - * These are used as a comparitor to determine if additional xf's need to be brought in or - * not and to give the new XF number if the xf exists. - * - * Changed 20080226 KSC: to use XF toString as XML is limited in format, toString is more complete - * @return map (String XfXml, Integer xfLookup) - */ - public Map getXfrecsAsString() { - Map retMap = new HashMap(); - for (int xfNum = 1; xfNum < this.getNumXfs(); xfNum++) { - Xf x = this.getXf(xfNum); - String xml = x.toString(); - retMap.put(xml, Integer.valueOf(xfNum)); - } - return retMap; - } - - /** - * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. - * These are used as a comparitor to determine if additional xf's need to be brought in or - * not and to give the new XF number if the xf exists. - * @return map (String XfXml, Integer xfLookup) - */ - public Map getFontRecsAsXML() { - Map retMap = new HashMap(); - for (int i = this.fonts.size() - 1; i >= 0; i--) { - Font fnt = (Font) this.fonts.get(i); - String xml = "<" + fnt.getXML() + "/>"; - retMap.put(xml, Integer.valueOf(fnt.getIdx())); - } - return retMap; - } - - /** - * @return Returns the lastbound. - */ - public Boundsheet getLastbound() { - return lastbound; - } - - /** - * @param lastbound The lastbound to set. - */ - public void setLastbound(Boundsheet lastbound) { - this.lastbound = lastbound; - } - - /** add formula to book and init the ptgs - * - * @param rec - */ - public void addFormula(Formula rec) { - this.formulas.add(rec); - String shn = rec.getSheet().getSheetName() + "!" + rec.getCellAddress(); - this.formulashash.put(shn, rec); - } - - public boolean isSharedupes() { - return sharedupes; - } - - public void setSharedupes(boolean sharedupes) { - this.sharedupes = sharedupes; - } - - /** Returns whether this book uses the 1904 date format. - * @deprecated Use {@link #getDateFormat()} instead. - */ - @Deprecated - public boolean is1904() { - return this.dateFormat == DateConverter.DateFormat.LEGACY_1904; - } - - /** Gets the date format used by this book. */ - public DateConverter.DateFormat getDateFormat() { - return this.dateFormat; - } - - public ReferenceTracker getRefTracker() { - return refTracker; - } - - // OOXML Additions - private boolean isExcel2007 = false; - - /** - * set truth of "Is Excel 2007" - * true increases maximums of base storage, etc. - * @param b - */ - public void setIsExcel2007(boolean b) { - isExcel2007 = b; - } - - /** - * returns truth of "Excel 2007" format - */ - public boolean getIsExcel2007() { - return isExcel2007; - } - - /** - * returns the workbook codename used by vba macros OOXML-specific - */ - public String getCodename() { - return ooxmlcodename; - } - - /** - * returns true if the default language selected in Excel is one of - * the DBCS (Double-Byte Code Set) languages - *
                - The languages that support DBCS include -
                - Japanese, Chinese (Simplified), Chinese (Traditional), and Korean -
                   
                -	Language        Country code    Countries/regions 
                -	-------------------------------------------------------------
                -	
                -	Arabic                966       (Saudi Arabia)
                -	Czech                 42        (Czech Republic)
                -	Danish                45        (Denmark)
                -	Dutch                 31        (The Netherlands)
                -	English               1         (The United States of America)
                -	Farsi                 98        (Iran)
                -	Finnish               358       (Finland)
                -	French                33        (France)
                -	German                49        (Germany)
                -	Greek                 30        (Greece)
                -	Hebrew                972       (Israel)
                -	Hungarian             36        (Hungary)
                -	Indian                91        (India)
                -	Italian               39        (Italy)
                -	Japanese              81        (Japan)
                -	Korean                82        (Korea)
                -	Norwegian             47        (Norway)
                -	Polish                48        (Poland)
                -	Portuguese (Brazil)   55        (Brazil)
                -	Portuguese            351       (Portugal)
                -	Russian               7         (Russian Federation)
                -	Simplified Chinese    86        (People's Republic of China)
                -	Spanish               34        (Spain)
                -	Swedish               46        (Sweden)
                -	Thai                  66        (Thailand)
                -	Traditional Chinese   886       (Taiwan)
                -	Turkish               90        (Turkey)
                -	Urdu                  92        (Pakistan)
                -	Vietnamese            84        (Vietnam)   
                -		
                - * @return boolean - */ - public boolean defaultLanguageIsDBCS() { - return (this.defaultLanguage == 81 || this.defaultLanguage == 886 - || this.defaultLanguage == 86 || this.defaultLanguage == 82); - // PROBLEM WITH THIS: POSSIBLE TO BE SET AS DBCS DEFAULT - // LANGUAGE - // BUT HAVE NON-DBCS TEXT or VISA VERSA - - /* - * In a double-byte character set, some characters require - * two bytes, - * while some require only one byte. - * The language driver can distinguish between these two - * types of characters by designating - * some characters as "lead bytes." - * A lead byte will be followed by another byte (a - * "tail byte") to create a - * Double-Byte Character (DBC). - * The set of lead bytes is different for each language. - * - * Lead bytes are always guaranteed to be extended - * characters; no 7-bit ASCII characters - * can be lead bytes. - * The tail byte may be any byte except a NULL byte. - * The end of a string is always defined as the first NULL - * byte in the string. - * Lead bytes are legal tail bytes; the only way to tell if - * a byte is acting as a - * lead byte is from the context. - */ - } - - /** - * sets the workbook codename used by vba macros OOXML-specific - * @param s - */ - public void setCodename(String s) { - ooxmlcodename = s; - } // TODO: input into Codename record - - /** - * sets the first sheet (int) in the workbook OOXML-specific - * - * naive implementaiton does not account for hidden sheets - * - * Mar 15, 2010 - * @param f - */ - public void setFirstSheet(int f) { - firstSheet = f; - } - - /** - * returns the first non-hidden - * - * sheet (int) in the workbook OOXML-specific - * - * Mar 15, 2010 - * @return - */ - public int getFirstSheet() { - try { - if (!getWorkSheetByNumber(firstSheet).getHidden()) - return this.firstSheet; - } catch (Exception x) { - ; - } - - // first sheet is hidden -- fix - for (int t = 0; t < this.getNumWorkSheets(); t++) { - try { - if (!getWorkSheetByNumber(t).getHidden()) { - firstSheet = t; - return firstSheet; - } - } catch (Exception x) { - ; - } - } - // all else failed - return firstSheet; - } - - /** - * sets the list of dxf's (incremental style info) (int) OOXML-specific - */ - public void setDxfs(ArrayList dxfs) { - this.dxfs = dxfs; - } - - /** - * returns the list of dxf's (incremental style info) (int) OOXML-specific - */ - public ArrayList getDxfs() { - return dxfs; - } - - /** - * Returns all strings that are in the SharedStringTable for this workbook. The SST contains - * all standard string records in cells, but may not include such things as strings that are contained - * within formulas. This is useful for such things as full text indexing of workbooks - * - * @return Strings in the workbook. - */ - public String[] getAllStrings() { - ArrayList al = this.getSharedStringTable().getAllStrings(); - String[] s = new String[al.size()]; - s = (String[]) al.toArray(s); - return s; - } - - /** - * set the pivot cache pointer - * @param pc initialized pivot cache - */ - public void setPivotCache(PivotCache pc) { - ptcache = pc; - } - - /** - * return the pivot cache, if any - * @return - */ - public PivotCache getPivotCache() { - return ptcache; - } - - /** - * clear out ALL sheet object refs - */ - public void closeSheets() { - for (int i = boundsheets.size() - 1; i > 0; i--) { - Boundsheet b = (Boundsheet) boundsheets.get(i); - if (b.streamer != null) { // do separately because may call - // boundsheet close - b.streamer.close(); - b.streamer = null; - } - b.close(); - if (tabs != null) - tabs.removeRecord(); - } - Object[] recs = this.getStreamer().getBiffRecords(); - boolean resetVars = (recs[0] != null); - boundsheets.clear(); - for (int i = 0; i < formulas.size(); i++) { - Formula f = (Formula) formulas.get(i); - f.close(); - } - this.formulas.clear(); - if (this.refTracker != null) - this.refTracker.clearCaches(); - this.formulashash.clear(); - // TODO: handle - this.indirectFormulas.clear(); - this.charts.clear(); - this.chartTemp.clear(); - if (firstBOF != null) { - firstBOF.close(); - this.firstBOF = null; - } - if (lasteof != null) { - lasteof.close(); - lasteof = null; - } - if (resetVars) { // just clearing out sheets instead of closing workbook - // reset lasteof for possible new insertion of sheets (if - // not removing workbook) ... - int i = recs.length - 1; - while (i > 0 && lasteof == null) { - if (recs[i] != null) { - if (((BiffRec) recs[i]).getOpcode() == EOF) - lasteof = (Eof) recs[i]; - } - i--; - } - } - if (lastBOF != null) { - lastBOF.close(); - lastBOF = null; - } - if (resetVars) { // just clearing out sheets instead of closing workbook - if (((BiffRec) recs[0]).getOpcode() == BOF) {// should!! - lastBOF = (Bof) recs[0]; - firstBOF = (Bof) recs[0]; - } - } - if (lastbound != null) { - lastbound.close(); - this.lastbound = null; - } - if (lastFormula != null) { - lastFormula.close(); - this.lastFormula = null; - } - this.workSheets.clear(); - } - - protected void closeRecords() {} - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - closeSheets(); - - if (this.isExcel2007) { - try { - String externalDir = OOXMLAdapter - .getTempDir(this.getFactory().getFileName()); - OOXMLAdapter.deleteDir(new File(externalDir)); - } catch (Exception e) { - ; - } - } - contHandler.close(); - contHandler = new ContinueHandler(this); - - // clear out array list references - Iterator ii = bookNameRecs.keySet().iterator(); - while (ii.hasNext()) { - Name n = (Name) bookNameRecs.get(ii.next()); - n.close(); - } - this.bookNameRecs.clear(); - for (int i = 0; i < xfrecs.size(); i++) { - Xf x = (Xf) xfrecs.get(i); - x.close(); - } - this.xfrecs.clear(); - this.formatCache.clear(); - this.formatlookup.clear(); - this.formats.clear(); - this.fonts.clear(); - if (this.dxfs != null) - this.dxfs.clear(); - for (int i = 0; i < msodgMerge.size(); i++) { - MSODrawingGroup m = (MSODrawingGroup) msodgMerge.get(i); - m.close(); - } - this.msodgMerge.clear(); - if (this.msodg != null) { - msodg.close(); - msodg = null; - } - // TODO: handle - this.mergecelllookup.clear(); - this.hlinklookup.clear(); - this.externalnames.clear(); - for (int i = 0; i < names.size(); i++) { - Name n = (Name) names.get(i); - n.close(); - } - this.names.clear(); - for (int i = 0; i < indexes.size(); i++) { - Index ind = (Index) indexes.get(i); - ind.close(); - } - this.indexes.clear(); - if (lastidx != null) { - lastidx.close(); - lastidx = null; - } - if (this.stringTable != null) { - this.stringTable.close(); - this.stringTable = null; - } - - if (countryRec != null) { - this.countryRec.close(); - this.countryRec = null; - } - if (win1 != null) { - win1.close(); - win1 = null; - } - - if (this.drh != null) { - this.drh.close(); - drh = null; - } - - // integration point for subclasses - this.closeRecords(); - - this.contHandler = new ContinueHandler(this); - if (this.currai != null) { - this.currai.close(); - this.currai = null; - } - if (calcmoderec != null) { - calcmoderec.close(); - calcmoderec = null; - } - this.currchart = null; - this.currdrw = null; - if (this.protector != null) { - this.protector.close(); - this.protector = null; - } - if (this.myexternsheet != null) { - this.myexternsheet.close(); - this.myexternsheet = null; - } - if (this.refTracker != null) { - this.refTracker.close(); - this.refTracker = null; - } - if (tabs != null) { - tabs.close(); - tabs = null; - } - // TODO: deal - this.factory = null; - streamer = createByteStreamer(); - if (xl2k != null) - ((XLSRecord) xl2k).close(); - xl2k = null; - - } - - public Color[] getColorTable() { - return colorTable; - } - - public void setColorTable(Color[] clrtable) { - colorTable = clrtable; - } - - public int getDefaultIxfe() { - return defaultIxfe; - } - - public void setDefaultIxfe(int defaultIxfe) { - this.defaultIxfe = defaultIxfe; - } - - public int getCalcId() { - // TODO Auto-generated method stub - return 0; - } - - public void setCalcId(int i) { - // TODO Auto-generated method stub - - } + private static final long serialVersionUID = 2282017774412632087L; + + DateConverter.DateFormat dateFormat = DateConverter.DateFormat.LEGACY_1900; + + // public constants + public static int STRING_ENCODING_AUTO = 0; + public static int STRING_ENCODING_UNICODE = 1; + public static int STRING_ENCODING_COMPRESSED = 2; + public static int ALLOWDUPES = 0; + public static int SHAREDUPES = 1; + + private int DEBUGLEVEL = 0; + private int bofct = 0, eofct = 0, + indexnum = 0; + private int defaultIxfe = 15; + private int CalcMode = CALCULATE_AUTO; + private int defaultLanguage = 0; // default + // language + // code + // for + // the + // current + // workbook + private boolean copying = false; + private boolean sharedupes = false; + private AbstractList xfrecs = new Vector(); + private AbstractList indexes = new Vector(); + private AbstractList names = new Vector(); // ALL + // of + // the + // names + // (including + // worksheet + // scoped, + // etc) + private AbstractList orphanedPtgNames = new Vector(); + private AbstractList externalnames = new Vector(); + AbstractList formulas = new FastAddVector(); + private AbstractList fonts = new Vector(); + public Color[] colorTable; + + /** + * The list of Format records indexed by format ID. + */ + private TreeMap formats = new TreeMap(); + private AbstractList charts = new Vector(); + /** + * OOXML-specific + */ + private AbstractList ooxmlObjects = new Vector(); // stores + // OOXML + // objects + // external + // to + // workbook + // e.g. + // oleObjects, + private String ooxmlcodename = null; // stores + // OOXML + // codename + private ArrayList dxfs = null; // 20090622 + // KSC: + // stores + // dxf's + // (incremental + // style + // info) + // per + // workbook + private int firstSheet = 0; // specifies + // first + // sheet + // (ooxml) + private Theme theme = null; + + // Reference Tracking + private ReferenceTracker refTracker = new ReferenceTracker(); + + // various + private AbstractList boundsheets = new Vector(); // TODO: + // remove + // this + // variable? + // its + // duplicated + // in + // workSheets + private AbstractList hlinklookup = new Vector( + 20); + private AbstractList mergecelllookup = new Vector( + 20); + + /* + * Indirect formulas that need to be calculated after load + * for reftracker + */ + private AbstractList indirectFormulas = new ArrayList(); + private AbstractList supBooks = new Vector(); + + /* + * This is here only to allow client code to compile, should + * be removed + */ + public static String CONVERTMULBLANKS = "deprecated"; + + // We should consider using an ordered collections class for + // some of these? + // an enumeration of worksheets for instance will not always + // be in the same + // order, causing tests to fail... + private Hashtable workSheets = new Hashtable( + 100, .950f); + private HashMap bookNameRecs = new HashMap(); + private Hashtable formulashash = new Hashtable(); + + /** + * Maps number format patterns to their IDs. + */ + private Hashtable formatlookup = new Hashtable( + 30); + + private Hashtable ptViews = new Hashtable( + 20); // TODO: + // move + // to + // sheet + // - + // sheet-level + // pivot + // table + // view + // recordss + private ArrayList ptstream = new ArrayList(); // wb-level + // pivot + // cache + // definitions + // usually + // only + // 1 + private PivotCache ptcache = null; // if + // has + // pivot + // tables + // this + // is + // the + // one + // and + // only + // pivot + // cache + + public HashMap formatCache = new HashMap(); + + private Index lastidx; + private Sst stringTable; + private Bof lastBOF; + private Externsheet myexternsheet; + private Bof firstBOF; + private ByteStreamer streamer = createByteStreamer(); + private TabID tabs; + private Window1 win1; + private CalcMode calcmoderec; // determines + // recalculation + // mode + // for + // workbook + // - + // Manual, + // Auto + // ... + private DefaultRowHeight drh; + private Chart currchart; + private Ai currai; + private Supbook myADDINSUPBOOK = null; // for + // external + // names + // SUPBOOK + WorkBookFactory factory; + private ContinueHandler contHandler = new ContinueHandler( + this); + private Usersviewbegin usersview; + private Eof lasteof; + private BiffRec xl2k = null; + public AbstractList msodgMerge = new Vector(); + public MSODrawingGroup msodg = null; + public int lastSPID = 1024; // 20071030 + // last + // or + // next + // SPID + // (= + // shape + // ID + // or + // image + // ID); + // incremented + // upon + // new + // images + // ... + // appropriate + // to + // store + // at + // book + // level + // (?) + private MSODrawing currdrw = null; + + private BookProtectionManager protector; + + protected void reflectiveClone(WorkBook source) { + for (Field field : WorkBook.class.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) + continue; + + try { + field.set(this, field.get(source)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + + protected ByteStreamer createByteStreamer() { + return new ByteStreamer(this); + } + + /** + * Gets this sheet's SheetProtectionManager. + */ + public BookProtectionManager getProtectionManager() { + if (protector == null) + protector = new BookProtectionManager(this); + return protector; + } + + /** + * init the ImageHandles + */ + void initImages() { + lastSPID = Math.max(lastSPID, msodg.getSpidMax()); // 20090508 KSC: + // lastSPID should + // also account for + // charts + // [BUGTRACKER 2372 + // copyChartToSheet + // error] + // 20071217 KSC: clear out imageMap before inputting! + for (int x = 0; x < this.getWorkSheets().length; x++) { + (this.getWorkSheets()[x]).imageMap.clear(); + } + for (int i = 0; i < msodg.getMsodrawingrecs().size(); i++) { // 20070914 + // KSC: + // store + // msodrawingrecs + // with + // MSODrawingGroup + // instead + // of + // here + MSODrawing rec = (MSODrawing) msodg.getMsodrawingrecs().get(i); + lastSPID = Math.max(lastSPID, rec.getlastSPID()); // valid for + // header + // msodrawing + // record(s) + int imgdx = rec.getImageIndex() - 1; // it's 1-based + byte[] imageData = msodg.getImageBytes(imgdx); + if (imageData != null) { + ImageHandle im = new ImageHandle(imageData, rec.getSheet()); + im.setMsgdrawing(rec); // Link 2 actual Msodrawing rec + im.setName(rec.getName()); // set image name from rec ... + im.setShapeName(rec.getShapeName()); // set shape name as well + // ... + im.setImageType(msodg.getImageType(imgdx));// 20100519 KSC: + // added! + rec.getSheet().imageMap.put(im, Integer.valueOf(imgdx)); + } + } + } + + /* + * Once all the records are parsed, the msodrawinggroup + * needs to be parsed. + * We first merge all the msodrawinggroup records. From the + * till now experiment, + * there are maximum of two such records, adjacent to each + * other. If there are two, + * the size of the first is only 8228 and the remaining data + * is in second record. + * However, when writing, it doesn't seem important to break + * it into two and can be written + * as one. + * + * Once msodrawingrecords are parsed, it is not used. + * However, we have a msodrawingglobal class that + * records the current count of msodrawing related records, + * like shape count, max shape id till now etc. + * We need to maintain that for writing (creating the + * msodrawinggroup records) later on. + */ + public void mergeMSODrawingRecords() { + // 20070915 KSC: Now don't re-initialize Msodrawing recs; + // just merge and parse + if (msodg != null) { + msodg.mergeAndRemoveContinues(); + for (int i = 1; i < msodgMerge.size(); i++) { + msodg.mergeRecords((MSODrawingGroup) msodgMerge.get(i)); // merges + // and + // removes + // secondary + // msodrawinggroups + // 20071003 KSC: get rid of secondary msodg's, will be + // created upon createMSODGContinues + this.getStreamer() + .removeRecord((MSODrawingGroup) msodgMerge.get(i)); // remove + // existing + // continues + // from + // stream + } + while (msodgMerge.size() > 1) + msodgMerge.remove(msodgMerge.size() - 1); + msodg.parse(); + this.initImages(); + } + } + + /** + * after changing the MSODrawings on a sheet, this is called to + * update the header Msodrawing rec. + *
                must sum up all other mso's on the particular sheet SPCONTAINERLEN and update the sheet mso header ... + *
                this calculation is quite experimental, so far it's working in all known cases ... + * + * @param bs + */ + public void updateMsodrawingHeaderRec(Boundsheet bs) { + MSODrawing msdHeader = msodg.getMsoHeaderRec(bs); + if (msdHeader != null) { + int spContainerLength = 0; // count of all other spcontainer lengths + // (sum=header spgroupcontainer) + int otherContainerLength = 0; // count of other containers + // (solvercontainer,etc) added to + // dgcontainerlength + int numshapes = 2; // 20100324 KSC: total guess, really + int totalDrawingRecs = msodg.getMsodrawingrecs().size(); + for (int z = 0; z < totalDrawingRecs; z++) { + MSODrawing rec = (MSODrawing) msodg.getMsodrawingrecs().get(z); + if (rec.getSheet().equals(bs)) { + if (rec != msdHeader && !rec.isHeader()) { // added header + // check- seems + // like *can* + // have multiple + // header recs + // in charts! + spContainerLength += rec.getSPContainerLength(); + otherContainerLength += rec.getSOLVERContainerLength(); + if (rec.isShape) // if it's a shape-type mso, count; + // there are other mso-types that + // are not SPCONTAINERS; apparently + // don't count these ... + numshapes++; + } + } + } + msdHeader + .updateHeader(spContainerLength, otherContainerLength, numshapes, msdHeader + .getlastSPID()); + } + } + + /** + * Get the MsoDrawingGroup for this workbook + * + * @return msodrawinggroup + */ + public MSODrawingGroup getMSODrawingGroup() { + return msodg; + } + + /** + * Set the msodrawinggroup for this workbook + * + * @param msodg + */ + public void setMSODrawingGroup(MSODrawingGroup msodg) { + this.msodg = msodg; + this.msodg.setWorkBook(this); + this.msodg.setStreamer(this.getStreamer()); + } + + /** + * For workbooks that do not contain an MSODrawing group create a new one, + * if the drawing group already exists return the existing + * + * @return + */ + public MSODrawingGroup createMSODrawingGroup() { + if (msodg != null) + return msodg; + this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup + .getPrototype()); + return msodg; + } + + /** + * Return some useful statistics about the WorkBook + * + * @return + */ + public String getStats() { + return getStats(false); + } + + /** + * Return some useful statistics about the WorkBook + * + * @return + */ + public String getStats(boolean usehtml) { + String rex = "\r\n"; + if (usehtml) + rex = "
                "; + + String ret = "-------------------------------------------" + rex; + ret += "OpenXLS Version: " + WorkBookHandle.getVersion() + rex; + ret += "Excel Version: " + getXLSVersionString() + rex; + ret += "-------------------------------------------------\r\n"; + ret += "Statistics for: " + this.toString() + rex; + ret += "Number of Worksheets: " + this.getNumWorkSheets() + rex; + ret += "Number of Cells: " + this.getNumCells() + rex; + ret += "Number of Formulas: " + this.getNumFormulas() + rex; + ret += "Number of Charts: " + this.getChartVect().size() + rex; + ret += "Number of Fonts: " + this.getNumFonts() + rex; + ret += "Number of Formats: " + this.getNumFormats() + rex; + ret += "Number of Xfs: " + this.getNumXfs() + rex; + // ret += "StringTable: " + this.stringTable.toString() + + // rex; + ret += "-------------------------------------------------\r\n"; + return ret; + } + + public String getXLSVersionString() { + return lastBOF.getXLSVersionString(); + } + + private void addMergedcells(Mergedcells c) { + this.mergecelllookup.add(c); + } + + private void addHlink(Hlink r) { + this.hlinklookup.add(r); + } + + /** + * take care of any lazy updating before output + */ + public void prestream() { + if (this.getMSODrawingGroup() != null) + this.getMSODrawingGroup().prestream(); + } + + public Chart[] getCharts() { + Chart[] chts = new Chart[this.charts.size()]; + return (Chart[]) charts.toArray(chts); + } + + public Supbook[] getSupBooks() { + Supbook[] sbs = new Supbook[this.supBooks.size()]; + return (Supbook[]) this.supBooks.toArray(sbs); + } + + /** + * returns the list of OOXML objects which are external or auxillary to the main workbook + * e.g. theme, doc properties + * + * @return + */ + public List getOOXMLObjects() { + return ooxmlObjects; + } + + /** + * adds the object-specific signature of the external or auxillary OOXML object + * Object should be of String[] form, + * key, path, local path + filename [, rid, [extra info], [embedded information]] + * e.g. theme, doc properties + * + * @param o + */ + public void addOOXMLObject(Object o) { + if (!((String[]) o)[0].equals("externalLink")) + ooxmlObjects.add(o); + else + ooxmlObjects.add(0, o); // ensure ExternalLinks are 1st because they + // are linked via rId in workbook.xml + } + + /** + * return the OOXML theme for this workbook, if any + * + * @return + */ + public Theme getTheme() { + return theme; + } + + /** + * sets the OOXML theme for this 2007 verison workbook + * + * @param t + */ + public void setTheme(Theme t) { + theme = t; + } + + /** + * return the External Supbook record associated with the desired externalWorkbook + * will create if bCreate + * + * @param externalWorkbook String URL (name) of External Workbook + * @param bCreate if true, will create an external SUPBOOK record for the externalWorkbook + * @return Supbook + */ + public Supbook getExternalSupbook(String externalWorkbook, boolean bCreate) { + Supbook sb = null; + if (externalWorkbook == null) + return null; + for (int i = 0; i < this.supBooks.size(); i++) { + if (((Supbook) this.supBooks.get(i)).isExternalRecord()) + if (externalWorkbook + .equalsIgnoreCase(((Supbook) this.supBooks.get(i)) + .getExternalWorkBook())) + sb = (Supbook) this.supBooks.get(i); + } + if (sb == null && bCreate) { // create + sb = (Supbook) Supbook.getExternalPrototype(externalWorkbook); + int loc = ((Supbook) supBooks.get(supBooks.size() - 1)) + .getRecordIndex(); // must have at least one global supbook + // present + streamer.addRecordAt(sb, loc + 1); // external supbooks appear to be + // before "normal" supbooks + // [BugTracker 1434] + this.supBooks.add(sb); // 20080714 KSC: add at beginning -- correct + // in all cases? + // this.addRecord(sb,false); // "" no need + } + return sb; + } + + /** + * return the index into the Supbook records for this supbook + * + * @param sb + * @return int + */ + public int getSupbookIndex(Supbook sb) { + for (int i = 0; i < this.supBooks.size(); i++) { + if (this.supBooks.get(i) == sb) + return i; + } + return -1; + } + + public AbstractList getChartVect() { + return charts; + } + + public Sxview getPivotTableView(String nm) { + return (Sxview) ptViews.get(nm); + } + + protected void addPivotTable(Sxview sx) { + this.ptViews.put(sx.getTableName(), sx); // Pivot Table View ==Top-level + // record for a Pivot Table + } + + /** + * return all pivot table views (==Sxview records) + *
                SxView is the top-level record of a Pivot Table + * as distinct from the PivotCache (stored data source in a LEOFile Storage) + * and PivotTable Stream (SxStream top-level record) + * + * @return + */ + public Sxview[] getAllPivotTableViews() { + Sxview[] sv = new Sxview[ptViews.size()]; + Enumeration x = ptViews.elements(); + int t = 0; + while (x.hasMoreElements()) { + sv[t++] = (Sxview) x.nextElement(); + } + return sv; + } + + public int getNPivotTableViews() { + return ptViews.size(); + } + + /** + * return the Externsheet for this book + * + * @param create a new Externsheet if it does not exist + * @return the Externsheet + */ + public Externsheet getExternSheet(boolean create) { + if ((myexternsheet == null) && create) { + addExternsheet(); + } + return myexternsheet; + } + + /** + * get the Externsheet + */ + public Externsheet getExternSheet() { + if (myexternsheet == null) + addExternsheet(); + return myexternsheet; + } + + /** + * default constructor -- do init + */ + public WorkBook() { + + Object cm = System.getProperties().get(WorkBook.CALC_MODE_PROP); + if (cm != null) { + try { + this.CalcMode = Integer.parseInt(cm.toString()); + } catch (Exception e) { + Logger.logWarn("Invalid Calc Mode Setting in System properties:" + + cm); + } + } + if (System.getProperties() + .get("io.starter.OpenXLS.sharedupes") != null) { + this.sharedupes = System.getProperties() + .get("io.starter.OpenXLS.sharedupes").equals("true"); + if (this.sharedupes) { + this.setDupeStringMode(WorkBook.SHAREDUPES); + } + } + this.initBuiltinFormats(); + // re-init color table: initial state of color table if + // Pallete record exists, changes may occur + colorTable = new java.awt.Color[FormatHandle.COLORTABLE.length]; + for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) + colorTable[i] = FormatHandle.COLORTABLE[i]; + } + + /** + * Gets the format ID for a given number format pattern. + * This lookup is completely case-insensitive. For most patterns this + * correctly reflects the case-insensitivity of the tokens. Custom patterns + * containing string literals could be matched incorrectly. + * + * @param pattern the number format pattern to look up + * @return the format ID of the given pattern or -1 if it's not recognized + */ + public short getFormatId(String pattern) { + Short res = (Short) formatlookup.get(pattern.toUpperCase()); + if (res != null) + return res.shortValue(); + return -1; + } + + /** + * Initializes the format lookup to contain the built-in formats. + */ + private void initBuiltinFormats() { + String[][] formats = FormatConstantsImpl.getBuiltinFormats(); + + for (int i = 0; i < formats.length; i++) + formatlookup.put(formats[i][0].toUpperCase(), Short + .valueOf(formats[i][1], 16)); + } + + /** + * Init names at Post-load + */ + public void initializeNames() { + for (int i = 0; i < names.size(); i++) { + Name n = (Name) names.get(i); + n.parseExpression(); // evaluate expression at postload, after sheet + // recs are loaded + } + } + + /** + * add a Name object to the collection of names + */ + public int addName(Name n) { + if (n.getItab() != 0) { + // its a sheet level name + try { + Boundsheet b = this.getWorkSheetByNumber(n.getItab() - 1);// one + // based + // pointer + b.addLocalName(n); + n.setSheet(b); + } catch (WorkSheetNotFoundException e) { + } + } else { + String sName = n.getNameA(); // returns upper case name + Object existo = bookNameRecs.get(sName); + if (existo != null) { // handle duplicate named ranges + String bnam = n.toString(); + if (bnam.indexOf("Built-in:") != 0) { + try { + if (((Name) existo).getLocation() != null) // use + // original + // - as good + // a guess + // as any + return -1; // an invalid sheet + } catch (Exception e) { + } + // if original does not have a location set, use this one + // instead + this.names.remove(existo); + this.bookNameRecs.remove(sName); + } + } + this.bookNameRecs.put(sName, n); + } + this.names.add(n); + if ((myexternsheet != null) && (n.getExternsheet() == null)) { + try { + n.setExternsheet(myexternsheet); // update sheet reference + } catch (WorkSheetNotFoundException e) { + Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " + + e.toString()); + } + } + return names.size() - 1; + } + + public void addNameUpdateSheetRefs(Name n, String origWorkBookName) { + if (bookNameRecs.get(n.getNameA()) == null) { + Name newName = new Name(this, n.getName()); + try { + newName.setLocation(n.getLocation()); + } catch (Exception e) { + } + if ((myexternsheet != null) && (newName.getExternsheet() == null)) { + try { + newName.setExternsheet(myexternsheet); // update sheet + // reference + } catch (WorkSheetNotFoundException e) { + Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " + + e.toString()); + } + } + newName.updateSheetRefs(origWorkBookName); + } + } + + /** + * Store an external name + * + * @param n = String describing the name + * @return int location of the name + */ + public int addExternalName(String n) { + this.externalnames.add(n); + return externalnames.size(); // one-based index + } + + /** + * Get a string array of external names + * + * @return externalNames + */ + public String[] getExternalNames() { + String[] n = new String[externalnames.size()]; + externalnames.toArray(n); + return n; + } + + /** + * Get the external name at the specified index. + * + * @param t index of the name + * @return name at the index, empty string if it doesn't exist. + *

                + * Why are we calling getExternalName one based, then removing for ordinal, internal processes should always + * be 0,1,2,3... -NR 1/06 + */ + public String getExternalName(int t) { + if (t > 0) + return (String) this.externalnames.get(t - 1); // one-based index + return ""; + } + + /** + * For workbooks that do not contain an externsheet + * this creates the externsheet record with one 0000 record + * and the related Supbook rec + */ + public void addDefaultExternsheet() { + Supbook sbb = (Supbook) Supbook.getPrototype(this.getNumWorkSheets()); + int l = this.stringTable.getRecordIndex(); + streamer.addRecordAt(sbb, l++); + supBooks.add(sbb); + Externsheet ex = (Externsheet) Externsheet + .getPrototype(0x0000, 0x0000, this); + streamer.addRecordAt(ex, l); + this.addRecord(ex, false); + } + + /** + * apparently this method adds an External name rec and returns the ilbl + *

                + * Correct structure is + * Supbook + * Externname + * Supbook + * Externsheet + * + * @param s + * @return + * @see PtgNameX + */ + public int getExtenalNameNumber(String s) { + int i = externalnames.indexOf(s); + if (i > -1) // got it + return i + 1; + if (this.getExternSheet() == null) + this.addDefaultExternsheet(); + + // not found; add a new EXTERNNAME record to list of add-ins + int n = addExternalName(s); + try { + int loc; + if (myADDINSUPBOOK == null) { + Supbook sb = (Supbook) Supbook.getAddInPrototype(); + loc = this.getExternSheet().getRecordIndex(); + streamer.addRecordAt(sb, loc++); + this.addRecord(sb, false); + myADDINSUPBOOK = sb; + supBooks.add(sb); + int externref = this.getExternSheet().getVirtualReference(); + // Add EXTERNNAME record after ADD-IN SUPBOOK record and + // after existing EXTERNNAME records + Externname exn = (Externname) Externname.getPrototype(s); + streamer.addRecordAt(exn, loc++); + this.addRecord(exn, false); + } else { + loc = streamer.getRecordIndex(myADDINSUPBOOK); + // Add EXTERNNAME record after ADD-IN SUPBOOK record and + // after existing EXTERNNAME records + Externname exn = (Externname) Externname.getPrototype(s); + streamer.addRecordAt(exn, loc + externalnames.size()); + this.addRecord(exn, false); + } + + } catch (Exception e) { + Logger.logWarn("Error adding externname: " + e); + } + return n; + } + + /** + * Get a collection of all names in the workbook + */ + public Name[] getNames() { + Name[] n = new Name[names.size()]; + names.toArray(n); + return n; + } + + /** + * Get a collection of all names in the workbook + */ + public Name[] getWorkbookScopedNames() { + ArrayList a = new ArrayList(this.bookNameRecs.values()); + Name[] n = new Name[a.size()]; + a.toArray(n); + return n; + } + + /** + * returns the List of Formulas in the book + * + * @return + */ + public List getFormulaList() { + return formulas; + } + + /** + * returns the array of Formulas in the book + * + * @return + */ + public Formula[] getFormulas() { + Formula[] n = new Formula[formulas.size()]; + formulas.toArray(n); + return n; + } + + /** + * remove a formula from the book + * + * @param fmla + */ + public void removeFormula(Formula fmla) { + this.formulashash.remove(fmla.getCellAddressWithSheet()); + formulas.remove(fmla); + fmla.destroy(); + } + + /** + * Returns the recalculation mode for the Workbook: + *
                0= Manual + *
                1= Automatic + *
                2= Automatic except for multiple table operations + * + * @return int + */ + public int getRecalculationMode() { + return this.calcmoderec.getRecalcuationMode(); + } + + /** + * Sets the recalculation mode for the Workbook: + *
                0= Manual + *
                1= Automatic + *
                2= Automatic except for multiple table operations + */ + public void setRecalcuationMode(int mode) { + this.calcmoderec.setRecalculationMode(mode); + } + + /** + * returns a Named range by number + * + * @param t + * @return + */ + public Name getName(int t) { + return (Name) this.names.get(t - 1); + } + + /** + * rename the NamedRange in the lookup map + * + * @param t + * @return + */ + public void setNewName(String oldname, String newname) { + if (oldname.equals(newname)) + return; + oldname = oldname.toUpperCase(); // case-insensitive + newname = newname.toUpperCase(); // "" + Object old = bookNameRecs.get(oldname); + if (old == null) + return; // new name? + bookNameRecs.remove(oldname); + bookNameRecs.put(newname, old); + + } + + /** + * Re-assocates ptgrefs that are pointing to a name that has been deleted then + * is recreated + * + * @param name + */ + public void associateDereferencedNames(Name name) { + Iterator i = orphanedPtgNames.iterator(); + String theName = name.getName(); + while (i.hasNext()) { + IlblListener x = (IlblListener) i.next(); + if (x.getStoredName().equalsIgnoreCase(theName)) { + x.setIlbl((short) this.getNameNumber(theName)); + x.addListener(); + } + } + } + + /** + * returns a named range by name string + *

                + * This method will first attempt to look in the book names, then the sheet names, + * obviously different scoped names can have the same identifying name, so this could return + * one of multiple names if this is the case + * + * @param t + * @return + */ + public Name getName(String nameRef) { + nameRef = nameRef.toUpperCase(); // case-insensitive + Object o = this.bookNameRecs.get(nameRef); + if (o == null) { + Boundsheet[] shts = this.getWorkSheets(); + for (int i = 0; i < shts.length; i++) { + o = shts[i].getName(nameRef); + if (o != null) + return (Name) o; + } + } + return (Name) o; + } + + /** + * returns a scoped named range by name string + * + * @param t + * @return + */ + public Name getScopedName(String nameRef) { + Object o = this.bookNameRecs.get(nameRef.toUpperCase()); // case-insensitive + if (o == null) { + return null; + } + return (Name) o; + } + + /** + * Returns the ilbl of the name record associated with the string passed in. + * If the name does not exist, it get's created without a location reference. + * This is needed to support formula creation with non-existent names referenced. + * + * @param t, the name record to search for + * @return the index of the name + */ + public int getNameNumber(String nameStr) { + for (int i = 0; i < names.size(); i++) { + Name n = (Name) this.names.get(i); + if (n.getName().equalsIgnoreCase(nameStr)) + return i + 1; + } + // no name exists, we need to create one. + Name myName; + myName = new Name(this, nameStr); + // myName = new Name(this, true); + // myName.setName(nameStr); + Name[] nmx = this.getNames(); + int namepos = -1; + if (nmx.length >= 1) { + namepos = nmx[nmx.length - 1].getRecordIndex(); + } else { + namepos = this.getExternSheet().getRecordIndex(); + } + namepos++; + this.getStreamer().addRecordAt(myName, namepos); + return this.getNameNumber(nameStr); + } + + /** + * Get's the index for this particular front. + *

                + * NOTE: this doesn't actually get a "matching" font, it has to be the exact font. + * 20070826 KSC: changed to match font characterstics, not just return exact matching font + */ + public int getFontIdx(Font f) { + // 20070819 KSC: Try this to see if better! Matches 6 key + // attributes (size, name, color, etc.) + for (int i = fonts.size() - 1; i >= 0; i--) { // start from the back so + // don't initially match + // defaults... + if (f.matches((Font) fonts.get(i))) + return ((i > 3) ? i + 1 : i); + } + // return fonts.indexOf(f); + return -1; + } + + /** + * Get's the index for this font, based on matching through + * xml strings. If the font doesn't exist in the book it returns -1; + * + * @return KSC: is this method necessary now with above getFontIdx changes? + */ + public int getMatchingFontIndex(Font f) { + Map fontmap = this.getFontRecsAsXML(); + Object o = fontmap.get("<" + f.getXML() + "/>"); + if (o != null) { + Integer I = (Integer) o; + return I.intValue(); + } else { + return -1; + } + } + + /** + * InsertFont inserts a Font record into the workbook level stream, + * For some reason, the addFont only puts it into an array that is never accessed + * on output. This may have a reason, so I am not overwriting it currently, but + * let's check it out? + */ + public int insertFont(Font x) { + int insertIdx = this.getFont(this.getNumFonts()).getRecordIndex(); + // perform default add rec actions + this.getStreamer().addRecordAt(x, insertIdx + 1); + x.setIdx(-1); // flag to add into font array + this.addRecord(x, false); // also adds to font array so no need for + // additional addFont below + return fonts.indexOf(x); + } + + /** + * add a Font object to the collection of Fonts + */ + public int addFont(Font f) { + fonts.add(f); + if (fonts.size() > 4) // fake the evil 4! + return fonts.size(); + return fonts.size() - 1; + } + + public int getNumFonts() { + return fonts.size(); + } + + /** + * Get the font at the specified index. Note that the number 4 does not exist, so index correctly based of that. + *

                + * So, if you call getFont(5), you are really doing getFont(4) from the internal array + * + * @param t + * @return + */ + public Font getFont(int t) { + if (t >= 4) { + t--; + } + if (this.fonts.size() >= t) { + if (t >= fonts.size()) { + Logger.logWarn("font " + t + + " not found. Workbook contains only: " + fonts.size() + + " defined fonts."); + return (Font) fonts.get(0); + } else { + return (Font) fonts.get(t); + } + } + return (Font) this.fonts.get(0); + } + + /** + * Inserts a newly created Format record into the workbook. + * This method handles assigning the format ID and adding the record to the + * workbook. If the record is already part of the workbook use + * {@link #addFormat} instead. + */ + public int insertFormat(Format format) { + Format last; + try { + last = (Format) formats.get(formats.lastKey()); + } catch (NoSuchElementException e) { + /* + * There are no other Format records in the workbook. + * This shouldn't happen because most (all?) Excel files + * contain + * Format records for the locale-specific (and thus not + * implied) + * built-in formats. If it does happen, either we need to + * re-assess + * the above assumption or this method was called before the + * Format + * records were parsed. Either way we need to know about it. + */ + throw new AssertionError("WorkBook.insertFormat called but no " + + "Format records exist. This should not happen. Please " + + "report this error to support@extentech.com."); + } + + // Add it to the streamer and workbook + getStreamer().addRecordAt(format, last.getRecordIndex() + 1); + addRecord(format, false); + + // Give it a format ID + if (format.getIfmt() == -1) + format.setIfmt((short) Math.max(last.getIfmt() + 1, 164)); + + // Add it to the format lookups + addFormat(format); + + return format.getIfmt(); + } + + public Formula getFormula(String cellAddress) throws FormulaNotFoundException { + Formula formula = (Formula) formulashash.get(cellAddress); + if (formula == null) + throw new FormulaNotFoundException( + "no formula found at " + cellAddress); + + return (formula); + } + + /** + * Adds an existing format record to the list of known formats. + * This method does not add the record to the workbook! If the format is + * not already in the workbook use {@link #insertFormat} instead. + * + * @param format the Format record to add + */ + public int addFormat(Format format) { + Short ifmt = Short.valueOf(format.getIfmt()); + + // Add it to the format record lookup + formats.put(ifmt, format); + + // Add it to the format string lookup + formatlookup.put(format.getFormat().toUpperCase(), ifmt); + + return format.getIfmt(); + } + + /** + * Gets the number of custom number formats registered on this book. + */ + public int getNumFormats() { + return formats.size(); + } + + /** + * Gets a custom number format by its format ID. + */ + public Format getFormat(int id) { + return (Format) formats.get(Short.valueOf((short) id)); + } + + public TabID getTabID() { + return this.tabs; + } + + /** + * associate default row/col size recs + */ + void setDefaultRowHeightRec(DefaultRowHeight dr) { + this.drh = dr; + } + + /** + * set Default row height in twips (1/20 of a point) + */ + // should be a double as Excel units are 1/20 of what is + // stored in defaultrowheight + // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 + // (approx) + // should expect users to use Excel units and target method + // do the 20* conversion + public void setDefaultRowHeight(int t) { + drh.setDefaultRowHeight(t); + } + + /** + * set Default col width for all worksheets in the workbook, + *

                + * Default column width can also be set on individual worksheets + */ + public void setDefaultColWidth(int t) { + Boundsheet[] b = this.getWorkSheets(); + for (int i = 0; i < b.length; i++) { + b[i].setDefaultColumnWidth(t); + } + } + + /** + * sets the selected worksheet + */ + public int getSelectedSheetNum() { + return win1.getCurrentTab(); + } + + /** + * sets the selected worksheet + */ + public void setSelectedSheet(Boundsheet bs) { + Boundsheet[] bsx = this.getWorkSheets(); + for (int t = 0; t < bsx.length; t++) { + if (bsx[t] != bs) + bsx[t].setSelected(false); + } + this.win1.setCurrentTab(bs); + } + + Formula lastFormula = null; + XLSRecord countryRec = null; + boolean inChartSubstream = false; + ArrayList chartTemp = new ArrayList(); + + /** + * for those cases where a formula calculation adds a new string rec + * need to explicitly set lastFormula before calling addRecord + * + * @param f + */ + protected void setLastFormula(Formula f) { + lastFormula = f; + } + + /** + * Associate a record with its containers and related records. + */ + @Override + public BiffRec addRecord(BiffRec rec, boolean addtorec) { + short opc = rec.getOpcode(); + rec.setStreamer(streamer); + rec.setWorkBook(this); + + Boundsheet bs = null; + Long lbplypos = new Long(0l); + + // get the relevant Boundsheet for this rec + if (rec instanceof Bof) { + if (this.getFirstBof() == null) + this.setFirstBof((Bof) rec); + if (bofct == eofct) { // not a chart or other non Sheet Bof + this.setLastBOF((Bof) rec); + } + if (((Bof) rec).isChartBof()) { + inChartSubstream = true; + } + } + + if (this.lastBOF == null) + Logger.logWarn("WorkBook: NULL Last BOF"); + long lb = this.lastBOF.getLbPlyPos(); + if (!this.lastBOF.isValidBIFF8()) + lb += 8; + lbplypos = new Long(lb); // use last + + bs = getSheetFromRec(rec, lbplypos); + if (bs != null) + lbplypos = new Long(bs.getLbPlyPos()); + + if ((bs != null)) { // &&){ + lastbound = bs; + if (addtorec) + rec.setSheet(bs);// we don't include Bof or other Book-recs + // because it lives in the Streamer recvec + if /* + * ((rec.isValueForCell()) + * && + */ (!copying) { + if ((lastFormula != null) && (opc == STRINGREC)) { + lastFormula.addInternalRecord(rec); + } else if ((lastFormula != null) && (opc == ARRAY)) { + lastFormula.addInternalRecord(rec); + } else if (rec.isValueForCell()) { + if (currchart == null) + bs.addCell((CellRec) rec); + + } + } + } + + if (inChartSubstream) { + if (currchart == null) { + if (rec.getOpcode() == CHART) { + charts.add(rec); + if (bs != null) + bs.addChart((Chart) rec); + currchart = (Chart) rec; + currchart.setPreRecords(chartTemp); + chartTemp = new ArrayList(); // clear out + } else { + chartTemp.add(rec); + } + } else { + currchart.addInitialChartRecord(rec); + if (rec.getOpcode() == EOF) { + currchart.initChartRecords(); // finished + currchart = null; + inChartSubstream = false; + } + } + addtorec = false; + } + + // Rows, valrecs, dbcells, and muls are stored in the row, + // not the byte streamer + if (opc == XLSRecord.DBCELL || opc == XLSRecord.ROW + || rec.isValueForCell() || opc == XLSRecord.MULRK + /* || opc==MULBLANK */ + || opc == CHART || opc == XLSRecord.FILEPASS + || opc == XLSRecord.SHRFMLA || opc == XLSRecord.ARRAY + || opc == XLSRecord.STRINGREC) { + addtorec = false; + } + + // add it to the record stream + if (addtorec) { + + if (lbplypos.longValue() > 0) + streamer.addRecord(rec); + else + streamer.records.add(rec); + } + + switch (opc) { + case AUTOFILTER: + bs.getAutoFilters().add(rec); + break; + + case CONDFMT: + bs.getConditionalFormats().add(rec); + ((Condfmt) rec).initializeReferences(); + break; + + case CF: + Condfmt cfmt = (Condfmt) bs.getConditionalFormats() + .get(bs.getConditionalFormats().size() - 1); + cfmt.addRule((Cf) rec); + break; + + case MERGEDCELLS: + bs.addMergedCellsRec((Mergedcells) rec); + this.addMergedcells((Mergedcells) rec); + break; + + // give protection records to the relevant ProtectionManager + case PASSWORD: + case PROTECT: + case PROT4REV: + case OBJPROTECT: + case SCENPROTECT: + case FEATHEADR: + ProtectionManager manager; + if (bs != null) + manager = bs.getProtectionManager(); + else + manager = this.getProtectionManager(); + manager.addRecord(rec); + break; + + case DVAL: + if (bs != null) + bs.setDvalRec((Dval) rec); + break; + + case DV: + if (bs != null) { + if (bs.getDvalRec() != null) { + bs.getDvalRec().addDvRec((Dv) rec); + } + } + break; + + case INDEX: + Index id = (Index) rec; + id.setIndexNum(indexnum++); + indexes.add(indexes.size(), id); + this.setLastINDEX(id); + if (bs == null) { + Logger.logWarn("ERROR: WorkBook.addRecord( Index ) error: BAD LBPLYPOS. The wrong LB:" + + lbplypos); + try { + bs = this.getWorkSheetByNumber(indexnum - 1); + Logger.logInfo(" The RIGHT LB:" + bs.getLbPlyPos()); + } catch (WorkSheetNotFoundException e) { + Logger.logInfo("problem getting WorkSheetByNumber: " + e); + } + + } + bs.setSheetIDX(id); + break; + + case ROW: + Row rw = (Row) rec; + if (bs != null) + bs.addRowRec(rw); + + break; + + case FORMULA: + this.addFormula((Formula) rec); + lastFormula = (Formula) rec; + break; + + case ARRAY: + Array arr = (Array) rec; + if (bs != null) + bs.addArrayFormula(arr); + arr.setParentRec(lastFormula); // [BugTracker 1869] link array + // formula to it's parent formula + // rec + break; + + /* + * case SHRFMLA : done in shrfmla.init + * Shrfmla form = (Shrfmla) rec; + * try{ // throws exceptipon during pullparse + * form.setHostCell( lastFormula ); + * }catch(Exception e){;} + * break; + */ + + case DATE1904: + if (((NineteenOhFour) rec).is1904) { + this.dateFormat = DateConverter.DateFormat.LEGACY_1904; + } + break; + + /* + * case PALETTE : // palette now correctly read into + * COLORTABLE + * this.pal = (Palette) rec; + * break; + */ + + case HLINK: + Hlink hl = (Hlink) rec; + this.addHlink(hl); + break; + + case DSF: + Dsf dsf = (Dsf) rec; + if (dsf.fDSF == 1) { + Logger.logErr("DOUBLE STREAM FILE DETECTED!"); + Logger.logErr(" OpenXLS is compatible with Excel 97 and above only."); + throw new WorkBookException( + "ERROR: DOUBLE STREAM FILE DETECTED! OpenXLS is compatible with Excel 97 + only.", + WorkBookException.DOUBLE_STREAM_FILE); + } + break; + + case GUTS: + if (bs != null) { + bs.setGuts((Guts) rec); + } + break; + + case DBCELL: + break; + + case BOF: + if (DEBUGLEVEL > 5) + Logger.logInfo("BOF:" + bofct + " - " + rec); + if (eofct == bofct) { + if (bs != null) { + bs.setBOF((Bof) rec); + } + } + bofct++; + break; + + case EXTERNSHEET: + myexternsheet = (Externsheet) rec; + break; + + case DEFCOLWIDTH: + if (bs != null) { + bs.setDefColWidth((DefColWidth) rec); + } + break; + + case EOF: + this.lasteof = (Eof) rec; + eofct++; + if (eofct == bofct) { + if (bs != null) { + bs.setEOF((Eof) rec); + } + eofct--; + bofct--; + } + break; + + case SELECTION: // only used for Recvec index + bs.setLastselection((Selection) rec); + break; + + case COUNTRY: + // Added to save position of 1st bound sheet, which is 1 + // record + // before COUNTRY RECORD (= 2 before 1st SUPBOOK record - + // true in all cases?) + countryRec = (XLSRecord) rec; + // USA=1, Canada=1, Japan=81, China=86, Thailand= 66, Korea= + // 82, India=91 ... + this.defaultLanguage = ((Country) rec).getDefaultLanguage(); + break; + + case SUPBOOK: // KSC: must store ordinal positions of SupBooks, for + // adding Externsheets + supBooks.add(rec); + if (myADDINSUPBOOK == null) { // see if this is the ADD-IN SUPBOOK + // rec + Supbook sb = (Supbook) rec; + if (sb.isAddInRecord()) + myADDINSUPBOOK = sb; + } + break; + + case BOUNDSHEET: + Boundsheet sh = (Boundsheet) rec; + + /* + * Here we need to set the selected variable, + * but not mess with selected tabs + * when all of the sheets aren't in the book yet. + * -jm + */ + int ctab = 1; // default- select 1st sheet if no Windows1 record + if (win1 != null) {// Windows1 record is optional 20101004 + // TestCorruption.TestNPEOnOpen + ctab = win1.getCurrentTab(); + } + int shts = boundsheets.size(); + if (ctab == shts) + sh.selected = true; + + this.addWorkSheet(new Long(sh.getLbPlyPos()), sh); + break; + + case MULRK: + Mulrk mul = (Mulrk) rec; + Iterator xit = mul.getRecs().iterator(); + while (xit.hasNext()) { + this.addRecord(((Rk) xit.next()), false); + } + break; + + case SST: + this.setSharedStringTable((Sst) rec); + break; + + case EXTSST: + ((Extsst) rec).setSst(this.getSharedStringTable()); + break; + + case SXSTREAMID: + this.ptstream.add(rec); // Pivot Stream + break; + + case SXVS: + case DCONREF: + case DCONNAME: + case DCONBIN: + try { + SxStreamID sid = (SxStreamID) ptstream.get(ptstream.size() - 1); + sid.addSubrecord(rec); + } catch (Exception e) { + } + break; + + case SXVIEW: + addPivotTable(((Sxview) rec)); // Pivot Table View ==Top-level + // record for a Pivot Table + break; + + // all* possible records associated with SxView (=PivotTable + // View) (*hopefully) + case SXVD: + // case SXVI: // subrecords of SxVD + // case SXVDEX: + case SXIVD: + case SXPI: + case SXDI: + case SXLI: + case SXEX: + case SXVIEWEX9: + case QSISXTAG: + try { + Sxview sx = (Sxview) this.ptViews.values() + .toArray()[this.ptViews.size() - 1]; + sx.addSubrecord(rec); + } catch (Exception e) { + } + break; + + case TABID: + this.tabs = (TabID) rec; + break; + + case NAME: + addName((Name) rec); + break; + + case CALCMODE: + this.calcmoderec = (CalcMode) rec; + break; + + case WINDOW1: + this.win1 = (Window1) rec; + break; + + case WINDOW2: + if (bs != null) + bs.setWindow2((Window2) rec); + break; + + case SCL: // scl is for zoom + if (bs != null) + bs.setScl((Scl) rec); + break; + + case PANE: + if (bs != null) + bs.setPane((Pane) rec); + break; + case EXCEL2K: + xl2k = rec; + break; + + case PHONETIC: + // TODO: this isn't necessary anymore! look at and remove + if (this.currdrw != null) { + this.currdrw.setMystery((Phonetic) rec); + } + break; + + case MSODRAWINGGROUP: + if (msodg == null) + msodg = (MSODrawingGroup) rec; + msodgMerge.add(rec); + + break; + + case MSODRAWING: + rec.setSheet(bs); + if (msodg != null) + msodg.addMsodrawingrec((MSODrawing) rec); + else + ; + // do what???io.starter.toolkit.Logger.log("PROBLEM with + // MSODG!"); + break; + + case COLINFO: + bs.addColinfo((Colinfo) rec); + break; + + case USERSVIEWBEGIN: + this.usersview = (Usersviewbegin) rec; + break; + + case WSBOOL: + if (bs != null) { + bs.setWsBool((WsBool) rec); + } + break; + + // Handle continue records which are actually masked Mso's + case CONTINUE: + if (((Continue) rec).maskedMso != null) { + ((Continue) rec).maskedMso.setSheet(bs); + if (msodg != null) + msodg.addMsodrawingrec(((Continue) rec).maskedMso); + } + break; + + case XF: + try { + this.addXf((Xf) rec); + } catch (Exception e) { + // throws exceptions during PullParse + } + break; + + default: + // DO NOTHING + + } + + // finish up + rec.setIndex(getLastINDEX()); + rec.setXFRecord(); + return rec; + } + + /** + * Dec 15, 2010 + * + * @param rec + * @return + */ + @Override + public Boundsheet getSheetFromRec(BiffRec rec, Long lbplypos) { + Boundsheet bs = null; + + if (rec.getSheet() != null) { + bs = rec.getSheet(); + } else if (lbplypos != null) { + bs = this.getWorkSheet(lbplypos); + } else + bs = lastbound; + return bs; + } + + /** + * get a handle to the ContinueHandler + */ + @Override + public ContinueHandler getContinueHandler() { + return this.contHandler; + } + + /** + * get a handle to the Usersviewbegin for the workbook + */ + public Usersviewbegin getUsersviewbegin() { + if (usersview == null) { + usersview = new Usersviewbegin(); + streamer.addRecord(usersview); + this.addRecord(usersview, false); + } + return usersview; + } + + /** + * set the Debug level + */ + @Override + public void setDebugLevel(int i) { + this.DEBUGLEVEL = i; + } + + /** + * get a handle to the Reader for this + * WorkBook. + */ + @Override + public void setFactory(WorkBookFactory r) { + this.factory = r; + } + + /** + * Get the number of worksheets in this WorkBook + */ + public int getNumWorkSheets() { + return this.boundsheets.size(); + } + + /** + * get the number of formulas in this WorkBook + * + * @return + */ + public int getNumFormulas() { + return formulas.size(); + } + + /** + * get the number of Cells in this WorkBook + */ + public int getNumCells() { + int cellnum = 0; + Enumeration e = workSheets.elements(); + while (e.hasMoreElements()) { + Boundsheet b = (Boundsheet) e.nextElement(); + cellnum += b.getNumCells(); + } + return cellnum; + } + + /** + * get all of the Cells in this WorkBook + */ + public BiffRec[] getCells() { + List cellz = new FastAddVector(); + for (int i = 0; i < workSheets.size(); i++) { + try { + Boundsheet b = this.getWorkSheetByNumber(i); + BiffRec[] cz = b.getCells(); + for (int x = 0; x < cz.length; x++) { + cellz.add(cz[x]); + } + } catch (Exception e) { + Logger.logErr("Error retrieving worksheet for getCells: " + e); + } + } + BiffRec[] cellzr = new BiffRec[cellz.size()]; + cellz.toArray(cellzr); + return cellzr; + } + + /** + * get the cell by the following String Pattern + *

                + * BiffRec c = getCell("SheetName!C17"); + */ + public BiffRec getCell(String cellname) throws CellNotFoundException, WorkSheetNotFoundException { + int semi = cellname.indexOf("!"); + String sname = cellname.substring(0, semi); + String cname = cellname.substring(semi + 1); + return getCell(sname, cname); + } + + /** + * get the cell by the following String Pattern + *

                + * BiffRec c = getCell("Sheet1", "C17"); + */ + public BiffRec getCell(String sheetname, String cellname) throws CellNotFoundException, WorkSheetNotFoundException { + cellname = cellname.toUpperCase(); + try { + Boundsheet bs = this.getWorkSheetByName(sheetname); + BiffRec ret = bs.getCell(cellname); + if (ret == null) + throw new CellNotFoundException(sheetname + ":" + cellname); + return ret; + } catch (WorkSheetNotFoundException a) { + throw new WorkSheetNotFoundException(sheetname + " not found"); + } catch (NullPointerException e) { + throw new CellNotFoundException(sheetname + ":" + cellname); + } + } + + private Boundsheet lastbound = null; + + /** + * add a Boundsheet to the WorkBook + */ + private void addWorkSheet(Long lbplypos, Boundsheet sheet) { + if (sheet == null) { + Logger.logWarn("WorkBook.addWorkSheet() attempting to add null sheet."); + return; + } else { + this.lastbound = sheet; + if (DEBUGLEVEL > 10) + Logger.logInfo("Workbook Adding Sheet: " + sheet.getSheetName() + + ":" + lbplypos); + workSheets.put(lbplypos, sheet); + boundsheets.add(sheet); + } + } + + /** + * @param n + * @return + */ + public boolean removeName(Name n) { + if (this.names.contains(n)) { + this.names.remove(n); + if (n.getItab() != 0) { + try { + this.getWorkSheetByNumber(n.getItab() - 1) + .removeLocalName(n); + } catch (WorkSheetNotFoundException e) { + } + } else { + this.bookNameRecs.remove(n.toString().toUpperCase()); // case-insensitive + } + } + ArrayList al = n.getIlblListeners(); + this.orphanedPtgNames.addAll(al); + + this.updateNameIlbls(); + return this.getStreamer().removeRecord(n); + } + + /** + * Add a sheet-scoped name record to the boundsheet + *

                + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this book, adding them here will not add them to the workbook; + * + * @param bookNameRecs + */ + public void addLocalName(Name name) { + bookNameRecs.put(name.getNameA(), name); + } + + /** + * Remove a sheet-scoped name record from the boundsheet. + *

                + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this book, removing them here will not remove them completely from the workbook. + *

                + * In order to do that you will need to call book.removeName + * + * @param bookNameRecs + */ + public void removeLocalName(Name name) { + bookNameRecs.remove(name.getNameA()); + } + + /** + * After any changes in the name records + * this method needs to be called in order to + * update ilbl records + */ + public void updateNameIlbls() { + for (int i = 0; i < names.size(); i++) { + Name n = (Name) names.get(i); + n.updateIlblListeners(); + } + } + + /** + * remove a Boundsheet from the WorkBook + */ + public void removeWorkSheet(Boundsheet sheet) { + + int sheetNum = sheet.getSheetNum(); + // remove the sheet + // automatically deletes Named ranges scoped to the sheet + Name[] namesOnSheet = sheet.getAllNames(); + for (int i = 0; i < namesOnSheet.length; i++) + this.removeName(namesOnSheet[i]); + + // Remove Externsheet ref before removing sheet + // update any Externsheet references... + try { + Externsheet ext = this.getExternSheet(); + if (ext != null) + ext.removeSheet(sheet.getSheetNum()); + } catch (WorkSheetNotFoundException e) { + Logger.logInfo("could not update Externsheet reference from " + + sheet.toString() + " : " + e.toString()); + } + + sheet.removeAllRecords(); + streamer.removeRecord(sheet); + workSheets.remove(new Long(sheet.getLbPlyPos())); + boundsheets.remove(sheet); + // we need to reset the lastbound for adding new worksheets. + // Currently assume it is + // the last one in the vector. + if (boundsheets.size() > 0) { + lastbound = (Boundsheet) (boundsheets.get(boundsheets.size() - 1)); + lastBOF = lastbound.getMyBof(); + } + + // decrement the tab ids... + this.tabs.removeRecord(); + this.updateScopedNamedRanges(); + + // update wb chart cache - remove charts referenced by + // deleted sheet + for (int i = getChartVect().size() - 1; i >= 0; i--) { + if (((Chart) this.getChartVect().get(i)).getSheet() == sheet) + this.getChartVect().remove(i); + } + + if (this.getNumWorkSheets() == 0) + return; // empty book + try { // set the next sheet selected... + while (sheetNum <= this.getNumWorkSheets()) { + Boundsheet s2 = this.getWorkSheetByNumber(sheetNum++); + s2.setSelected(true); + if (!s2.getHidden()) + break; + + } + } catch (WorkSheetNotFoundException e) { + try { + Boundsheet s2 = this.getWorkSheetByNumber(0); + s2.setSelected(true); + } catch (Exception ee) { + throw new WorkBookException( + "Invalid WorkBook. WorkBook must contain at least one Sheet.", + WorkBookException.RUNTIME_ERROR); + } + } + } + + /** + * Updates all the name records in the workbook that are bound to a + * worksheet scope (as opposed to a workbook scope). Name records use + * their own non-externsheet based sheet references, so need to be modified + * whenever a sheet delete (or non-last sheet insert) operation occurs + */ + private void updateScopedNamedRanges() { + for (int i = 0; i < boundsheets.size(); i++) { + ((Boundsheet) boundsheets.get(i)).updateLocalNameReferences(); + } + } + + /** + * returns the Boundsheet identified by its + * offset to the BOF record indicating the + * start of the Boundsheet data stream. + *

                + * used internally to access the Sheets to + * ensure that the lbplypos is correct -- essential + * to proper operation of XLS file. + * + * @param Long lbplypos of Boundsheet + */ + private Boundsheet getWorkSheet(Long lbplypos) { + return (Boundsheet) workSheets.get(lbplypos); + } + + /** + * returns the Boundsheet with the specific name + * + * @param String name of Boundsheet + */ + public Boundsheet getWorkSheetByName(String bstr) throws WorkSheetNotFoundException { + try { + if (bstr.startsWith("'") || bstr.startsWith("\"")) + bstr = bstr.substring(1, bstr.trim().length() - 1); + Iterator bs = boundsheets.iterator(); + while (bs.hasNext()) { + Boundsheet bsi = (Boundsheet) bs.next(); + String bsin = bsi.getSheetName(); + // TODO: check if we can have dupe names different case + if (bsin.equalsIgnoreCase(bstr)) + return bsi; + } + } catch (Exception ex) { + Logger.logWarn("WorkBook.getWorkSheetByName failed: " + + ex.toString()); + } + throw new WorkSheetNotFoundException( + "Worksheet " + bstr + " not found in " + this.toString()); + } + + /** + * returns the Boundsheet with the specific Hashname + * + * @param String hashname of Boundsheet + */ + public Boundsheet getWorkSheetByHash(String s) throws WorkSheetNotFoundException { + Boundsheet[] bs = this.getWorkSheets(); + for (int i = 0; i < bs.length; i++) { + if (bs[i].getSheetHash().equalsIgnoreCase(s)) + return bs[i]; + } + return null; + } + + /** + * returns the Boundsheet at the specific index + * + * @param int index of Boundsheet + */ + public Boundsheet getWorkSheetByNumber(int i) throws WorkSheetNotFoundException { + Boundsheet bs = null; + try { + bs = (Boundsheet) boundsheets.get(i); + } catch (ArrayIndexOutOfBoundsException e) { + } + if (bs == null) { // External Sheet Ref NOT FOUND + throw new WorkSheetNotFoundException( + i + " not found"); + } + return bs; + } + + /** + * set the last processed Index record + */ + private void setLastINDEX(Index id) { + lastidx = id; + } + + Index getLastINDEX() { + return lastidx; + } + + void setSharedStringTable(Sst s) { + stringTable = s; + } + + public Sst getSharedStringTable() { + return stringTable; + } + + /** + * returns the Vector of Boundsheets + */ + public AbstractList getSheetVect() { + return this.boundsheets; + } + + /** + * returns the boundsheets for this book as an array + */ + Boundsheet[] getWorkSheets() { + Boundsheet[] ret = new Boundsheet[boundsheets.size()]; + return (Boundsheet[]) boundsheets.toArray(ret); + } + + /** + * set the last BOF read in the stream + */ + void setLastBOF(Bof b) { + lastBOF = b; + } + /** return the last BOF read in the stream */ + // Bof lastBOF{return lastBOF;} + + /** + * get a handle to the first BOF to perform offset functions which don't know where the + * start of the file is due to the compound file format. + *

                + * Referred to in Boundsheet as the 'lbPlyPos', this + * is the position of the BOF for the Boundsheet relative + * to the *first* BOF in the file (the firstBOF of the WorkBook) + * + * @see Boundsheet + */ + @Override + public void setFirstBof(Bof b) { + firstBOF = b; + } + + Bof getFirstBof() { + return firstBOF; + } + + @Override + public String toString() { + return this.getFileName(); + } + + @Override + public String getFileName() { + if (this.factory != null) // 2003-vers + return this.factory.getFileName(); + return "New Spreadsheet"; + } + + /** + * Returns whether the sheet selection tabs should be shown. + */ + public boolean showSheetTabs() { + return win1.showSheetTabs(); + } + + /** + * Sets whether the sheet selection tabs should be shown. + */ + public void setShowSheetTabs(boolean show) { + win1.setShowSheetTabs(show); + } + + /** + * set the first visible tab + */ + public void setFirstVisibleSheet(Boundsheet bs2) { + win1.setFirstTab(bs2.getSheetNum()); + } + + // Associate related records + + /** + * return the XF record at the specified index + */ + public Xf getXf(int i) { + if (xfrecs.size() < (i - 1)) + return null; + return (Xf) xfrecs.get(i); + } + + public int getNumXfs() { + return xfrecs.size(); + } + + /** + * InsertXF inserts an XF record into the workbook level stream, + * For some reason, the addXf only puts it into an array that is never accessed + * on output. This may have a reason, so I am not overwriting it currently, but + * let's check it out? + */ + int insertXf(Xf x) { + int insertIdx = this.getXf(this.getNumXfs() - 1).getRecordIndex(); + // perform default add rsec actions + this.getStreamer().addRecordAt(x, insertIdx + 1); + this.addRecord(x, false); // updates xfrecs + formatcache + x.ixfe = x.tableidx; + return x.tableidx; + } + + /** + * internally used in preparation for reading an 2007 and above workbook + */ + public void removeXfRecs() { + // must keep the 1st xf rec as default + for (int i = xfrecs.size() - 1; i > 0; i--) { + Xf xf = (Xf) xfrecs.get(i); + this.streamer.removeRecord(xf); + xfrecs.remove(i); + + } + } + + /** + * TODO: Does this function as desired? See comment for insertXf() above... + * tracks existing xf recs, used when testing whether xfrec exists or not ... + * -NR 1/06 + * ya should - called now from addRecord every time an xf record is added + * NOTE: this is the only place addXf is called + * + * @param xf + * @return + */ + int addXf(Xf xf) { + xfrecs.add(xf); + xf.tableidx = xfrecs.size() - 1; // flag that it's been added to records + this.updateFormatCache(xf); // links tostring of xf to xf rec for + // updating/reuse purposes + return xf.tableidx; + } + + /** + * formatCache: + * links tostring of xf to xf rec for updating/reuse purposes + * + * @param xf + * @see FormatHandle.updateXf + * @see WorkBook.addXf + */ + public void updateFormatCache(Xf xf) { + if (xf.tableidx != -1) { // if this xf has been already added to the + // workbook + if (formatCache.containsValue(xf)) { // xf signature has + // changed/it's been updated + Iterator ii = formatCache.keySet().iterator(); // remove and + // update below + while (ii.hasNext()) { + String key = (String) ii.next(); + Xf x = (Xf) formatCache.get(key); + if (x.equals(xf)) { + formatCache.remove(key); + break; + } + } + } + String formatStr = xf.toString(); + + if (!formatCache.containsKey(formatStr)) + formatCache.put(formatStr, xf); + } + } + + /** + * retrieve the format cache - links string vers. of xf to xf rec + * used for resusing xf's + * + * @return + * @see FormatHandle.updateXf + */ + public HashMap getFormatCache() { + return formatCache; + } + + /** + * Get a substream by name. + */ + @Override + public ByteStreamer getStreamer() { + return this.streamer; + } + + public void setSubStream(ByteStreamer s) { + this.streamer = s; + } + + /** + * Get the typename for this object. + */ + String getTypeName() { + return "WorkBook"; + } + + /** + * Write the contents of the WorkBook bytes to an OutputStream + * + * @param _out + */ + @Override + public int stream(OutputStream _out) { + return streamer.streamOut(_out); + } + + /** + * get a handle to the factory + */ + @Override + public WorkBookFactory getFactory() { + return this.factory; + } + + /** + * Copies a complete boundsheet within the workbook + *

                + * If a name exists that refers directly to this sheet then duplicate it, otherwise workbook scoped names are + * not copied + */ + public void copyWorkSheet(String SourceSheetName, String NewSheetName) throws Exception { + Boundsheet origSheet = null; + origSheet = this.getWorkSheetByName(SourceSheetName); + List chts = origSheet.getCharts(); // 20080630 KSC: Added + for (int i = 0; i < chts.size(); i++) { + Chart cxi = (Chart) chts.get(i); + cxi.populateForTransfer(); + } + byte[] inbytes = origSheet.getSheetBytes(); + this.addBoundsheet(inbytes, SourceSheetName, NewSheetName, null, false); + Boundsheet bnd = this.getWorkSheetByName(NewSheetName); + // handle moving the built-in name records. These handle + // such items as print area, header/footer, etc + Name[] ns = this.getNames(); + for (int i = 0; i < ns.length; i++) { // 20100404 KSC: take out +1? + if (ns[i].getItab() == origSheet.getSheetNum() + 1) { + // it's a built in record, move it to the new sheet + int sheetnum = bnd.getSheetNum(); + int xref = this.getExternSheet(true) + .insertLocation(sheetnum, sheetnum); + Name n = (Name) ns[i].clone(); + n.setExternsheetRef(xref); + n.updateSheetReferences(bnd); + n.setSheet(bnd); + n.setItab((short) (bnd.getSheetNum() + 1)); + this.insertName(n); + } + } + } + + /** + * Inserts a newly created Name record into the correct location in the streamer. + * + * @param n + */ + public void insertName(Name n) { + int namepos = -1; + Name[] nmx = getNames(); + if (nmx.length > 0) { + if (nmx[nmx.length - 1].getSheet() != null) { + namepos = nmx[nmx.length - 1].getRecordIndex(); + } else { + namepos = getExternSheet(true).getRecordIndex() + nmx.length; + } + } else { + namepos = getExternSheet(true).getRecordIndex(); + } + namepos++; + getStreamer().addRecordToBookStreamerAt(n, namepos); + addRecord(n, false); + } + + /** + * Copies an existing Chart to another WorkSheet + * + * @param chartname + * @param sheetname + */ + public void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException { + Chart ct = this.getChart(chartname); + Boundsheet sht = this.getWorkSheetByName(sheetname); + byte[] bt = ct.getChartBytes(); + sht.addChart(bt, chartname, ct.getCoords()); + } + + /** + * Inserts a serialized boundsheet chart into the workboook + */ + public Chart addChart(Chart destChart, String NewChartName, Boundsheet boundsht) { + destChart.setWorkBook(this); + destChart.setSheet(boundsht); + List recs = destChart.getXLSrecs(); + for (int x = 0; x < recs.size(); x++) { + XLSRecord rec = (XLSRecord) recs.get(x); + rec.setWorkBook(this); + rec.setSheet(boundsht); + if (rec.getOpcode() == MSODRAWING) { + addChartUpdateMsodg((MSODrawing) rec, boundsht); + continue; + } + if (!(rec instanceof Bof)) // TODO: error/problem with the BOF + // record!!! + rec.init(); + if (rec instanceof Dimensions) + destChart.setDimensions((Dimensions) rec); + try { + ((GenericChartObject) rec).setParentChart(destChart); + } catch (ClassCastException e) { // Scl, Obj and others are not + // chart objects + } + + } + destChart.setTitle(NewChartName); + destChart.setId(boundsht.lastObjId + 1); // track last obj id per sheet + // ... + this.charts.add(destChart); + boundsht.getCharts().add(destChart); // should really have two lists??? + return destChart; + } + + /** + * updates Mso (MSODrawingGroup + Msodrawing) records upon add/copy worksheet and add/copy charts + * NOTE: this code is mainly garnered via trial and error, works + * + * @param mso Msodrawing record that is being added or copied + * @param sht Boundsheet + */ + public void addChartUpdateMsodg(MSODrawing mso, Boundsheet sht) { + if (msodg == null) { + this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup + .getPrototype()); + msodg.initNewMSODrawingGroup(); // generate and add required records + // for drawing records + } + msodg.addMsodrawingrec(mso); + MSODrawing hdr = msodg.getMsoHeaderRec(sht); + if (hdr != null && hdr != mso) { // already have a header rec + if (sht.getCharts().size() > 0) { + mso.makeNonHeader(); + hdr.setNumShapes(hdr.getNumShapes() + 1); + } + } else if (hdr == null) { + mso.setIsHeader(); + hdr = mso; + } + this.updateMsodrawingHeaderRec(sht); + msodg.dirtyflag = true; // flag to reset SPIDs on write + msodg.setSpidMax(++lastSPID); + msodg.updateRecord(); + } + + /** + * JM - + * Add the requisite records in the book streamer for the chart. \ + * Supbook, externsheet & msodrawinggroup + *

                + * I think this is due to the fact that the referenced series are usually stored + * in the fashon 'Sheet1!A4:B6' The sheet1 reference requires a supbook, though the + * reference is internal. + */ + public void addPreChart() { + this.addExternsheet(); + if (msodg == null) { + this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup + .getPrototype()); + msodg.initNewMSODrawingGroup(); // generate and add required records + // for drawing records + } + + } + + /** + * remove an existing chart from the workbook + * NOTE: STILL EXPERIMENTAL TESTS OK IN BASIC CIRCUMSTANCES BUT MUST BE TESTED FURTHER + */ + public void deleteChart(String chartname, Boundsheet sheet) throws ChartNotFoundException { + Chart chart = this.getChart(chartname); + // TODO: Update Dimensions record?? + List recs = chart.getXLSrecs(); + // first rec SHOULD BE MsoDrawing!!! + try { + MSODrawing rec = (MSODrawing) recs.get(0); + msodg.removeMsodrawingrec(rec, sheet, true); // also remove + // associated Obj + // record + } catch (Exception e) { + Logger.logErr("deleteChart: expected Msodrawing record"); + } + /* + * shouldn't be necessary to remove chart recs as they are + * separated upon init of workbook and reassebmbled upon + * write + */ + this.removeChart(chartname); + } + + /** + * Inserts a serialized boundsheet into the workboook. + * + * @throws ClassNotFoundException + * @throws IOException + * @param inbytes original sheet bytes + * @param NewSheetName new Sheet Name + * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * + * @boolean SSTPopulatedBoundsheet - a boundsheet that has all of it's sst data saved off in LabelSST records. + * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. + * Do not use this if the data already exists in the SST, you are just causing bloat! + */ + public void addBoundsheet(byte[] inbytes, String origSheetName, String NewSheetName, String origWorkBookName, boolean SSTPopulatedBoundsheet) throws IOException, ClassNotFoundException { + Boundsheet destSheet = null; + ByteArrayInputStream bais = new ByteArrayInputStream(inbytes); + BufferedInputStream bufstr = new BufferedInputStream(bais); + ObjectInputStream o = new ObjectInputStream(bufstr); + destSheet = (Boundsheet) o.readObject(); + + if (destSheet != null) { + this.addBoundsheet(destSheet, origSheetName, NewSheetName, origWorkBookName, SSTPopulatedBoundsheet); + } + } + + /** + * change the tab order of a boundsheet + */ + public void changeWorkSheetOrder(Boundsheet bs, int idx) { + // reorder the sheet vector + if (idx >= 0 && idx < boundsheets.size()) { + boundsheets.remove(bs); + boundsheets.add(idx, bs); + for (int x = 0; x < boundsheets.size(); x++) { + Boundsheet bs1 = (Boundsheet) boundsheets.get(x); + boolean udpatewin1 = bs1.selected(); + if (udpatewin1) + bs1.setSelected(true); + } + } + + int insertLoc = Integer.MAX_VALUE; + // remove the existing boundsheet records in the streamer + for (int i = 0; i < boundsheets.size(); i++) { + Boundsheet bound = (Boundsheet) boundsheets.get(i); + int position = bound.getRecordIndex(); + insertLoc = Math.min(insertLoc, position); + streamer.removeRecord((XLSRecord) boundsheets.get(i)); + } + // enter the boundsheet records back in the streamer in + // correct order + for (int i = 0; i < boundsheets.size(); i++) { + Boundsheet bound = (Boundsheet) boundsheets.get(i); + streamer.addRecordAt(bound, insertLoc + i); + } + } + + /** + * add a deserialized boundsheet to this workbook. + * + * @param bound new (copied) sheet + * @param newSheetName new sheetname + * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * + * @boolean SSTPopulatedBoundsheet - the boundsheet has all of it's sst data saved off in LabelSST records. + * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. + * Do not use this if the data already exists in the SST, you are just causing bloat! + */ + public void addBoundsheet(Boundsheet bound, String origSheetName, String newSheetName, String origWorkBookName, boolean SSTPopulatedBoundsheet) { + bound.streamer = streamer; + boolean old_allowdupes = this.isSharedupes(); + this.setDupeStringMode(ALLOWDUPES); + + bound.mc.clear(); + bound.setWorkBook(this); + + // Check if sheetname already exists! + try { + while (this.getWorkSheetByName(newSheetName) != null) + newSheetName = newSheetName + "Copy"; + } catch (WorkSheetNotFoundException we) { + /* good !!! */ + } + bound.setSheetName(newSheetName); + + // get a hold of the lbplypos number that we will need for + // the new boundsheet + int recvecOffset = streamer.getRecVecSize() - 1; + XLSRecord x = null; + if (lastbound != null) { + try { // lastbound must be reset because other operations could + // alter + lastbound = this + .getWorkSheetByNumber(this.getNumWorkSheets() - 1); + x = (XLSRecord) lastbound.getSheetRecs() + .get(lastbound.getSheetRecs().size() - 1); + } catch (Exception e) { + } + } else if (countryRec != null) { + x = (XLSRecord) streamer.getRecordAt(countryRec.getRecordIndex()); + } else + x = this.lasteof; + // last record is a junkrec. We are going to move that down + // and put in the new BOF here + // TODO: recvecOffset position when no sheets?????? + if (x.getOpcode() != EOF) { + recvecOffset -= 1; + } + int newloc = x.offset; + // modify the boundsheet rec for its new location/info/name + int listenerpos = -1, newoffset = -1; + if (lastbound != null) { + listenerpos = lastbound.getRecordIndex(); + newoffset = lastbound.offset + lastbound.getLength() + 4; + // offset + reclen + headerlen + } else if (x != null) { + listenerpos = x.getRecordIndex() - 1; // account for +1 below + newoffset = x.offset + x.getLength() + 4; + } else { + listenerpos = recvecOffset - 1; + newoffset = streamer.getRecordAt(recvecOffset).getLength() + 4; + } + + // put the serialized recs from localrecs into the normal + // SheetRecs + bound.setLocalRecs(new FastAddVector()); // reset localrecs + List newrecs = bound.getSheetRecs(); + Bof newbof = (Bof) newrecs.get(0); + + newloc += newbof.getLength() + 4; + newbof.setOffset(newloc); + bound.setBOF(newbof); + this.addRecord(newbof, false); + + recvecOffset += 1; // move it past that last Eof + newoffset = newloc + newbof.getLength() + 4; + lastbound = bound; + // insert the actual boundsheet record into the recvec + streamer.addRecordAt(bound, listenerpos + 1); + + this.addRecord(bound, false); + + // modify the TabID record to reflect new sheet + tabs.addNewRecord(); + recvecOffset = newbof.getRecordIndex(); + + int tout = 0; + copying = true; + + // Add an externsheet ref for the new sheet + if (this.myexternsheet == null) + this.addExternsheet(); + try { + int sheetref = this.getNumWorkSheets() - 1; + myexternsheet.insertLocation(sheetref, sheetref); + } catch (Exception e) { + Logger.logWarn("Adding new sheetRef failed in addBoundsheet()" + + e.toString()); + } + + // update the chart references + add to wb + List chts = bound.getCharts(); + for (int i = 0; i < chts.size(); i++) { + Chart chart = (Chart) chts.get(i); // obviously algorithm has + // changed and chart is NOT + // removed :) [discovered by + // Shigeo/Infoteria/formatbroken273193.sce] + // // 20080702 KSC: since it's + // removed, don't inc index + chart.updateSheetRefs(bound.getSheetName(), origWorkBookName); + this.charts.add(chart); + } + + bound.lastObjId = 1; // see if resetting obj id helps in file open + // errors; if so, must reset Note obj id's as + // well ... + + /********** This loop handles Boundsheet records contained in the sheet level streamer, that is, not the valrecs *****/ + int numrecs = newrecs.size(); + for (int z = 1; z < numrecs; z++) { + XLSRecord xl = (XLSRecord) newrecs.get(z); + this.addRecord(xl, false); + if (DEBUGLEVEL > 5) + try { + Logger.logInfo("Copying: " + xl.toString() + ":" + + newoffset + ":" + xl.getLength()); + } catch (Exception e) { + } + if (xl instanceof Codename) { + Codename secretagent = (Codename) xl; // lol -nr + secretagent.setName(newSheetName); + } else if (xl instanceof Name) { + // Name records specify data ranges -- update to point to + // new sheet + Name n = (Name) xl; + int refnum = myexternsheet.getcXTI(); + n.setExternsheetRef(refnum); + } else if (xl instanceof Cf) { // must check Conditional Format + // formula refs and handle any + // external references + try { + updateFormulaPtgRefs(((Cf) xl) + .getFormula1(), origSheetName, newSheetName, origWorkBookName); + // NOTE: FORMULA2 can be null -- TODO: should check here + updateFormulaPtgRefs(((Cf) xl) + .getFormula2(), origSheetName, newSheetName, origWorkBookName); + } catch (Exception e) { + } + } else if (xl.getOpcode() == OBJ) { + ((Obj) xl).setObjId(bound.lastObjId++); + } else if (xl.getOpcode() == MSODRAWING + || (xl.getOpcode() == CONTINUE + && ((Continue) xl).maskedMso != null)) { // 20100510 + // KSC: + // handle + // masked + // mso's + MSODrawing mso; + if (xl.getOpcode() == MSODRAWING) + mso = (MSODrawing) xl; + else + mso = ((Continue) xl).maskedMso; + if (msodg == null) { + this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup + .getPrototype()); + msodg.initNewMSODrawingGroup(); // generate and add required + // records for drawing + // records + msodg.addMsodrawingrec(mso); // only add when msodg is null + // b/c otherwise it's added + // via the addRecord + // statement above + } + if (mso.getImageIndex() > 0) { // add image bytes as well, if + // any + ImageHandle im = bound + .getImageByMsoIndex(mso.getImageIndex()); + int idx = msodg.addImage(im.getImageBytes(), im + .getImageType(), false); + bound.imageMap.put(im, Integer.valueOf(idx)); // 20100518 + // KSC: + // makes + // more + // sense? + // im.getImageIndex())); + // // add + // new image + // to map + // and link + // to actual + // imageIndex + // - moved + // from + // above + if (idx != mso.getImageIndex()) + mso.updateImageIndex(idx); + } + mso.setSPID(this.lastSPID); + msodg.setSpidMax(++this.lastSPID); + // resets drawing id's - necessarily correct? + // msodg.dirtyflag= true; // flag to reset SPIDs on write + } + xl.setOffset(newoffset); + tout += xl.getLength(); + newoffset += xl.getLength(); + } + if (msodg != null) {// Moved from above so don't udpate at every mso + // addition + // necessary? all mso sub-records on the sheet should have + // stayed the same ...this.updateMsodrawingHeaderRec(bound); + msodg.updateRecord(); + } + /*************** END handling of boundsheet streamer records *************************/ + + /*************** HANDLE Formats + PtgRefs in Cell Records ****************************/ + updateTransferedCellReferences(bound, origSheetName, origWorkBookName); + + // associate the records in the sheet + this.setSharedupes(old_allowdupes); + + if (SSTPopulatedBoundsheet) { + // bring over the sst + Sst sst = this.getSharedStringTable(); + BiffRec[] b = bound.getCells(); + for (int i = 0; i < b.length; i++) { + b[i].setWorkBook(this); + if (b[i].getOpcode() == XLSConstants.LABELSST) { + Labelsst s = (Labelsst) b[i]; + s.insertUnsharedString(sst); + } + } + } + + if (this.getNumWorkSheets() > 1) + bound.setSelected(false); + else + bound.setSelected(true); + + if (DEBUGLEVEL > 5) + Logger.logInfo("changesize for new boundsheet: " + + bound.getSheetName() + ": " + tout); + copying = false; + } + + /** + * traverses all rows and their associated cells in the newly transfered sheet, + * ensuring formula/cell references and format references are correctly transfered + * into the current workbook + * + * @param bound source sheet + */ + private void updateTransferedCellReferences(Boundsheet bound, String origSheetName, String origWorkBookName) { + HashMap localFonts = (HashMap) this.getFontRecsAsXML(); + List boundFonts = bound.getTransferFonts(); // ALL fonts in the source + // workbook + HashMap localXfs = (HashMap) this.getXfrecsAsString(); + List boundXfs = bound.getTransferXfs(); + // Set the workbook on all the cells + Row[] rows = bound.getRows(); + for (int i = 0; i < rows.length; i++) { + rows[i].setWorkBook(this); + if (rows[i].getIxfe() != this.getDefaultIxfe()) + transferFormatRecs(rows[i], localFonts, boundFonts, localXfs, boundXfs); // 20080709 + // KSC: + // handle + // default + // ixfe + // for + // row + Iterator rowcells = rows[i].getCells().iterator(); + Mulblank aMul = null; + short c = 0; + while (rowcells.hasNext()) { + BiffRec b = (BiffRec) rowcells.next(); + if (b.getOpcode() == MULBLANK) { + if (aMul == b) + c++; + else { + aMul = (Mulblank) b; + c = (short) aMul.getColFirst(); + } + aMul.setCurrentCell(c); + } + b.setWorkBook(this); // Moved to before updateFormulaPtgRefs + // [BugTracker 1434] + if (b instanceof Formula) { // Examine Ptg Refs to handle + // external sheet references not + // contained in this workbook + updateFormulaPtgRefs((Formula) b, origSheetName, bound + .getSheetName(), origWorkBookName); + if (((Formula) b).shared != null) + ((Formula) b).shared.setWorkBook(this); + + } + // 20080226 KSC: transfer format, fonts and xf here instead + // of populateWorkbookWithRemoteData() + transferFormatRecs(b, localFonts, boundFonts, localXfs, boundXfs); + } + } + // 20080226 KSC: handle xf's for columns + for (Colinfo co : bound.getColinfos()) { + transferFormatRecs(co, localFonts, boundFonts, localXfs, boundXfs); + } + List c = bound.getCharts(); + for (int i = 0; i < c.size(); i++) { + Chart cht = (Chart) c.get(i); + ArrayList fontrefs = cht.getFontxRecs(); + for (int x = 0; x < fontrefs.size(); x++) { + Fontx fontx = (Fontx) fontrefs.get(x); + int fid = fontx.getIfnt(); + if (fid > 3) { + fid = bound.translateFontIndex(fid, localFonts); + fontx.setIfnt(fid); + } + } + } + } + + /** + * examine all Ptg's referenced by this formula, looking for hanging or missing sheet references + * if found, sets sheet reference to the current sheet (TODO: a better way?) + * + * @param f Formula Rec + */ + private void updateFormulaPtgRefs(Formula f, String origSheetName, String newSheetName, String origWorkBookName) { + try { + if (f == null) + return; // 20100222 KSC + f.populateExpression(); + Ptg[] p = f.getCellRangePtgs(); + for (int k = 0; k < p.length; k++) { + if (p[k] instanceof PtgRef) { + PtgRef ptg = (PtgRef) p[k]; + try { + if (!(ptg instanceof PtgArea3d) || ((PtgArea3d) ptg) + .getFirstSheet() + .equals(((PtgArea3d) ptg).getLastSheet())) { + String sheetName = ptg.getSheetName(); + if (sheetName.equals(origSheetName)) + ptg.setSheetName(newSheetName); + ptg.addToRefTracker(); + /* + * changed to use above. don't understand this: + * if (!sheetName.equals(origSheetName)) { + * this.getWorkSheetByName(ptg.getSheetName()); + * ptg.setSheetName(newSheetName); + * } else + * ptg.setSheetName(newSheetName); + */ + } else { // uncommon case of two sheet range + PtgArea3d pref = (PtgArea3d) ptg; + // this.getWorkSheetByName(pref.getFirstPtg().getSheetName()); + // don't understand this + // this.getWorkSheetByName(pref.getLastPtg().getSheetName()); + ptg.setLocation(ptg.toString()); // reset ixti if + // nec. + } + } catch (WorkSheetNotFoundException we) { + Logger.logWarn("External Reference encountered upon updating formula references: Worksheet Reference Found: " + + ptg.getSheetName()); + ptg.setExternalReference(origWorkBookName); + } + } else if (p[k] instanceof PtgExp) { + PtgExp ptgexp = (PtgExp) p[k]; + try { + Ptg[] pe = ptgexp.getConvertedExpression(); // will fail + // if + // ShrFmla + // hasn't + // been + // input yet + for (int j = 0; j < pe.length; j++) { + if (pe[j] instanceof PtgRef) { + PtgRef ptg = (PtgRef) pe[j]; + try { + if (ptg instanceof PtgArea3d) { // PtgRef3d, + // etc. + this.getWorkSheetByName(ptg + .getSheetName()); + ptg.setLocation(ptg.toString()); // reset + // ixti + // if + // nec. + } + // otherwise, we're good + } catch (WorkSheetNotFoundException we) { + Logger.logWarn("External References Not Supported: UpdateFormulaReferences: External Worksheet Reference Found: " + + ptg.getSheetName()); + ptg.setExternalReference(origWorkBookName); + } + } + } + } catch (Exception e) { + // if links to "main" ShrFmla, won't be set yet and will + // give exception - see Shrfmla WorkBook.addRecord + } + } + } + } catch (Exception e) { + Logger.logErr("WorkBook.updateFormulaRefs: error parsing expression: " + + e); + } + } + + /** + * given a record in an previously external workbook, ensure that xf and font records + * are correctly input into the current workbook and that the pointers are correctly updated + * + * @param b BiffRec + * @param localFonts HashMap of string version of all fonts, font nums in current workbook + * @param boundFonts List of string version of all fonts, font nums in external workbook + * @param localXfs HashMap of string version of all xfs, xf nums in current workbook + * @param boundXfs List of string version of all xfs, xf nums in external workbook + */ + private void transferFormatRecs(BiffRec b, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { + int oldXfNum = b.getIxfe(); + int localNum = this + .transferFormatRecs(oldXfNum, localFonts, boundFonts, localXfs, boundXfs); + if (localNum != -1) + b.setIxfe(localNum); + } + + /** + * given a record in an previously external workbook, ensure that xf and font records + * are correctly input into the current workbook and that the pointers are correctly updated + * + * @param b BiffRec + * @param localFonts HashMap of string version of all fonts, font nums in current workbook + * @param boundFonts List of string version of all fonts, font nums in external workbook + * @param localXfs HashMap of string version of all xfs, xf nums in current workbook + * @param boundXfs List of string version of all xfs, xf nums in external workbook + */ + private int transferFormatRecs(int oldXfNum, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { + int localNum = -1; + if (boundXfs.size() > oldXfNum) {// if haven't populatedForTransfer i.e. + // haven't opted to transfer formats + // ... + Xf origxf = (Xf) boundXfs.get(oldXfNum); // clone xf so modifcations + // don't affect original + if (origxf != null) { + /** FONT **/ + // must handle font first in order to create xf below + // see if referenced xf + fonts are already in workbook; if + // not, add + int localfNum; + // check to see if the font needs to be added + int fnum = origxf.getIfnt(); + if (fnum > 3) + fnum--; + Font thisFont = (Font) boundFonts.get(fnum); + String xmlFont = "<" + thisFont.getXML() + "/>"; + Object fontNum = localFonts.get(xmlFont); + if (fontNum != null) { // then get the fontnum in this book + localfNum = ((Integer) fontNum).intValue(); + } else { // it's a new font for this workbook, add it in + localfNum = this.insertFont(thisFont) + 1; + localFonts.put(xmlFont, Integer.valueOf(localfNum)); + } + + /** XF **/ + Xf localxf = FormatHandle + .cloneXf(origxf, origxf.getFont(), this); // clone xf so + // modifcations + // don't + // affect + // original + // input "local" versions of format and font + + /** FORMAT **/ + Format fmt = origxf.getFormat(); // number format - is null if + // format is general ... + if (fmt != null) // add if necessary + localxf.setFormatPattern(fmt.getFormat()); // adds new + // format + // pattern if + // not found + localxf.setFont(localfNum); + + // now check out to see if xf needs to be added + String xmlxf = localxf.toString(); + Object xfNum = localXfs.get(xmlxf); + if (xfNum == null) { // insert it into the book + localNum = this.insertXf(localxf); + localXfs.put(xmlxf, Integer.valueOf(localNum)); + } else // already exists in the destination + localNum = ((Integer) xfNum).intValue(); + + } + } + return localNum; + } + + public void setStringEncodingMode(int mode) { + this.getSharedStringTable().setStringEncodingMode(mode); + } + + public void setDupeStringMode(int mode) { + if (mode == ALLOWDUPES) + this.setSharedupes(false); + else if (mode == SHAREDUPES) + this.setSharedupes(true); + } + + /** + * Returns a Chart Handle + * + * @return ChartHandle a Chart in the WorkBook + */ + public Chart getChart(String chartname) throws ChartNotFoundException { + AbstractList cv = this.getChartVect(); + Chart cht = null; + // Get by MSODG Drawing Name + for (int x = 0; x < cv.size(); x++) { + cht = (Chart) cv.get(x); + MSODrawing titlemso = cht.getMsodrawobj(); + if (titlemso != null) { + String mson = titlemso.getName(); // shapeName; + if (mson.equalsIgnoreCase(chartname)) + return cht; + } + } + boolean untitled = chartname.equals("[Untitled]"); + // Try to get by title + for (int x = 0; x < cv.size(); x++) { + cht = (Chart) cv.get(x); + String cname = cht.getTitle(); + if (cname.equalsIgnoreCase(chartname)) + return cht; + else if (untitled && cname.equals("")) + return cht; + } + throw new ChartNotFoundException(chartname); + } + + /** + * removes the desired chart from the list of charts + * + * @param chartname + * @throws ChartNotFoundException + */ + public void removeChart(String chartname) throws ChartNotFoundException { + AbstractList cv = this.getChartVect(); + Chart cht = null; + for (int x = 0; x < cv.size(); x++) { + cht = (Chart) cv.get(x); + if (cht.getTitle().equalsIgnoreCase(chartname)) { + cv.remove(x); + return; + } + } + throw new ChartNotFoundException(chartname); + } + + /** + * NOT 100% IMPLEMENTE YET + * creates the initial records for a Pivot Cache + *
                A Pivot Cache identifies the data used in a Pivot Table + *
                NOTE: only SHEET cache sources are supported at this time + * + * @param ref String reference: either reference or named range + * @param sheetName String sheetname + * @param cacheid if > 0, the desired cacheid (useful only in OOXML parsing) + * @return int cacheid + */ + public int addPivotStream(String ref, String sheetName, int sid) { + // in wb substream, DIRECTLY AFTER STYLE records: + // STYLE/STYLEEX [TableStyle TableStyleElement] [Palette] + // [ClrtClient] + if (sid < 0) + sid = 0; // initial cache id if none already present + List records = this.getStreamer().records; + int z = -1; + for (int i = records.size() - 1; i > 0 && z == -1; i--) { + int opcode = ((BiffRec) records.get(i)).getOpcode(); + if (opcode == SXADDL) { // find last cache id and increment + /* + * while (i > 0 && opcode!=SXSTREAMID) + * opcode= ((BiffRec) records.get(i--)).getOpcode(); + * if (opcode==SXSTREAMID) { + * sid= ((SxStreamID) records.get(i+1)).getStreamID() + 1; + * } + */ + z = i + 1; + } else if (opcode == 4188) // ClrtClient + z = i + 1; + else if (opcode == PALETTE) + z = i + 1; + else if (opcode == 2192) // TableStyleElement + z = i + 1; + else if (opcode == 2194) // StyleEx + z = i + 1; + else if (opcode == STYLE) // Style + z = i + 1; + } + + TableStyles tx = (TableStyles) TableStyles.getPrototype(); // see if + // this is + // really + // necessary + // ... + this.getStreamer().addRecordAt(tx, z++); + SxStreamID sxid = (SxStreamID) SxStreamID.getPrototype(); + this.getStreamer().addRecordAt(sxid, z++); + this.ptstream.add(sxid); // Pivot Cache - + sxid.setStreamID(sid); // cache id + this.getStreamer().records + .addAll(z, sxid.addInitialRecords(this, ref, sheetName)); + + return sid; + } + + /** + * adds the Pivot Cache Directory Storage +Stream records necessary to + * define the pivot cache (==pivot table data) for pivot table(s) + *
                NOTE: at this time only 1 pivot cache is supported + * + * @param ref Cell Range which identifies pivot table data range + * @param wbh + * @param sId Stream or cachid Id -- links back to SxStream set of records + */ + public void addPivotCache(String ref, WorkBookHandle wbh, int sId) { + if (ptcache == null) { + ptcache = new PivotCache(); + ptcache.createPivotCache(factory.myLEO + .getDirectoryArray(), wbh, ref, sId); + } + } + + /** + * returns the start of the stream defining the desired pivot cache + * + * @param cacheid + * @return + */ + public SxStreamID getPivotStream(int cacheid) { + // int z= 0; + for (int i = 0; i < ptstream.size(); i++) { + int sid = ((SxStreamID) ptstream.get(i)).getStreamID(); + if (sid == cacheid) + // if (z++==cacheid) + return (SxStreamID) ptstream.get(i); + } + /* + * List records= this.getStreamer().records; + * for (int i= 0; i < records.size(); i++) { + * int opcode= ((BiffRec) records.get(i)).getOpcode(); + * if (opcode==SXSTREAMID) { + * int sid= ((SxStreamID) records.get(i)).getStreamID() + 1; + * if (sid==cacheid) + * return (SxStreamID) records.get(i); + * } + * } + */ + return null; + } + + /** + * @return + */ + public AbstractList getHlinklookup() { + return hlinklookup; + } + + /** + * @return + */ + public AbstractList getMergecelllookup() { + return mergecelllookup; + } + + public void addIndirectFormula(Formula f) { + indirectFormulas.add(f); + } + + /** + * Initialize the indirect functions in this workbook by calculating the formulas + */ + public void initializeIndirectFormulas() { + Iterator i = indirectFormulas.iterator(); // contains all INDIRECT + // funcvars + params + while (i.hasNext()) { + Formula f = (Formula) i.next(); + f.calculateIndirectFunction(); + } + indirectFormulas = new ArrayList(); // clear out + } + + /** + * Inserts an externsheet into the recvec, provided one does not yet exist. + * also calls add supBook + */ + public void addExternsheet() { + if (myexternsheet == null) { + int numsheets = this.getNumWorkSheets(); + Supbook sb = (Supbook) Supbook.getPrototype(numsheets); + // put it in after the last boundsheet record + try { + Boundsheet b = this.getWorkSheetByNumber(numsheets - 1); + int loc = b.getRecordIndex() + 1; + if (streamer.getRecordAt(loc).getOpcode() == COUNTRY) + loc++; + streamer.addRecordAt(sb, loc); // 20080306 KSC: do first 'cause + // externsheet now references + // global sb store + this.addRecord(sb, false); + Externsheet ex = (Externsheet) Externsheet + .getPrototype(0, 0, this); + streamer.addRecordAt(ex, loc + 1);// 20080306 KSC: must inc loc + // since now inserting after + // sb + this.addRecord(ex, false); + myexternsheet = ex; + } catch (WorkSheetNotFoundException e) { + Logger.logWarn("WorkBook.addExternSheet() locating Sheet for adding Externsheet failed: " + + e); + } + + } + } + + /** + * Sets the OpenXLS calculation mode for the workbook. + * + * @param CalcMode + * @see WorkBookHandle.setFormulaCalculationMode() + */ + public void setCalcMode(int mode) { + CalcMode = mode; + } + + /** + * Sets the calculation mode for the workbook. + * + * @param CalcMode + * @see WorkBookHandle.setFormulaCalculationMode() + */ + public int getCalcMode() { + return CalcMode; + } + + /** + * @return Returns the xfrecs. + */ + public AbstractList getXfrecs() { + return xfrecs; + } + + /** + * Return the font records + */ + public AbstractList getFontRecs() { + return fonts; + } + + /** + * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. + * These are used as a comparitor to determine if additional xf's need to be brought in or + * not and to give the new XF number if the xf exists. + *

                + * Changed 20080226 KSC: to use XF toString as XML is limited in format, toString is more complete + * + * @return map (String XfXml, Integer xfLookup) + */ + public Map getXfrecsAsString() { + Map retMap = new HashMap(); + for (int xfNum = 1; xfNum < this.getNumXfs(); xfNum++) { + Xf x = this.getXf(xfNum); + String xml = x.toString(); + retMap.put(xml, Integer.valueOf(xfNum)); + } + return retMap; + } + + /** + * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. + * These are used as a comparitor to determine if additional xf's need to be brought in or + * not and to give the new XF number if the xf exists. + * + * @return map (String XfXml, Integer xfLookup) + */ + public Map getFontRecsAsXML() { + Map retMap = new HashMap(); + for (int i = this.fonts.size() - 1; i >= 0; i--) { + Font fnt = (Font) this.fonts.get(i); + String xml = "<" + fnt.getXML() + "/>"; + retMap.put(xml, Integer.valueOf(fnt.getIdx())); + } + return retMap; + } + + /** + * @return Returns the lastbound. + */ + public Boundsheet getLastbound() { + return lastbound; + } + + /** + * @param lastbound The lastbound to set. + */ + public void setLastbound(Boundsheet lastbound) { + this.lastbound = lastbound; + } + + /** + * add formula to book and init the ptgs + * + * @param rec + */ + public void addFormula(Formula rec) { + this.formulas.add(rec); + String shn = rec.getSheet().getSheetName() + "!" + rec.getCellAddress(); + this.formulashash.put(shn, rec); + } + + public boolean isSharedupes() { + return sharedupes; + } + + public void setSharedupes(boolean sharedupes) { + this.sharedupes = sharedupes; + } + + /** + * Returns whether this book uses the 1904 date format. + * + * @deprecated Use {@link #getDateFormat()} instead. + */ + @Deprecated + public boolean is1904() { + return this.dateFormat == DateConverter.DateFormat.LEGACY_1904; + } + + /** + * Gets the date format used by this book. + */ + public DateConverter.DateFormat getDateFormat() { + return this.dateFormat; + } + + public ReferenceTracker getRefTracker() { + return refTracker; + } + + // OOXML Additions + private boolean isExcel2007 = false; + + /** + * set truth of "Is Excel 2007" + * true increases maximums of base storage, etc. + * + * @param b + */ + public void setIsExcel2007(boolean b) { + isExcel2007 = b; + } + + /** + * returns truth of "Excel 2007" format + */ + public boolean getIsExcel2007() { + return isExcel2007; + } + + /** + * returns the workbook codename used by vba macros OOXML-specific + */ + public String getCodename() { + return ooxmlcodename; + } + + /** + * returns true if the default language selected in Excel is one of + * the DBCS (Double-Byte Code Set) languages + *
                + * The languages that support DBCS include + *
                + * Japanese, Chinese (Simplified), Chinese (Traditional), and Korean + *

                +     * Language        Country code    Countries/regions
                +     * -------------------------------------------------------------
                +     *
                +     * Arabic                966       (Saudi Arabia)
                +     * Czech                 42        (Czech Republic)
                +     * Danish                45        (Denmark)
                +     * Dutch                 31        (The Netherlands)
                +     * English               1         (The United States of America)
                +     * Farsi                 98        (Iran)
                +     * Finnish               358       (Finland)
                +     * French                33        (France)
                +     * German                49        (Germany)
                +     * Greek                 30        (Greece)
                +     * Hebrew                972       (Israel)
                +     * Hungarian             36        (Hungary)
                +     * Indian                91        (India)
                +     * Italian               39        (Italy)
                +     * Japanese              81        (Japan)
                +     * Korean                82        (Korea)
                +     * Norwegian             47        (Norway)
                +     * Polish                48        (Poland)
                +     * Portuguese (Brazil)   55        (Brazil)
                +     * Portuguese            351       (Portugal)
                +     * Russian               7         (Russian Federation)
                +     * Simplified Chinese    86        (People's Republic of China)
                +     * Spanish               34        (Spain)
                +     * Swedish               46        (Sweden)
                +     * Thai                  66        (Thailand)
                +     * Traditional Chinese   886       (Taiwan)
                +     * Turkish               90        (Turkey)
                +     * Urdu                  92        (Pakistan)
                +     * Vietnamese            84        (Vietnam)
                +     * 
                + * + * @return boolean + */ + public boolean defaultLanguageIsDBCS() { + return (this.defaultLanguage == 81 || this.defaultLanguage == 886 + || this.defaultLanguage == 86 || this.defaultLanguage == 82); + // PROBLEM WITH THIS: POSSIBLE TO BE SET AS DBCS DEFAULT + // LANGUAGE + // BUT HAVE NON-DBCS TEXT or VISA VERSA + + /* + * In a double-byte character set, some characters require + * two bytes, + * while some require only one byte. + * The language driver can distinguish between these two + * types of characters by designating + * some characters as "lead bytes." + * A lead byte will be followed by another byte (a + * "tail byte") to create a + * Double-Byte Character (DBC). + * The set of lead bytes is different for each language. + * + * Lead bytes are always guaranteed to be extended + * characters; no 7-bit ASCII characters + * can be lead bytes. + * The tail byte may be any byte except a NULL byte. + * The end of a string is always defined as the first NULL + * byte in the string. + * Lead bytes are legal tail bytes; the only way to tell if + * a byte is acting as a + * lead byte is from the context. + */ + } + + /** + * sets the workbook codename used by vba macros OOXML-specific + * + * @param s + */ + public void setCodename(String s) { + ooxmlcodename = s; + } // TODO: input into Codename record + + /** + * sets the first sheet (int) in the workbook OOXML-specific + *

                + * naive implementaiton does not account for hidden sheets + *

                + * Mar 15, 2010 + * + * @param f + */ + public void setFirstSheet(int f) { + firstSheet = f; + } + + /** + * returns the first non-hidden + *

                + * sheet (int) in the workbook OOXML-specific + *

                + * Mar 15, 2010 + * + * @return + */ + public int getFirstSheet() { + try { + if (!getWorkSheetByNumber(firstSheet).getHidden()) + return this.firstSheet; + } catch (Exception x) { + } + + // first sheet is hidden -- fix + for (int t = 0; t < this.getNumWorkSheets(); t++) { + try { + if (!getWorkSheetByNumber(t).getHidden()) { + firstSheet = t; + return firstSheet; + } + } catch (Exception x) { + } + } + // all else failed + return firstSheet; + } + + /** + * sets the list of dxf's (incremental style info) (int) OOXML-specific + */ + public void setDxfs(ArrayList dxfs) { + this.dxfs = dxfs; + } + + /** + * returns the list of dxf's (incremental style info) (int) OOXML-specific + */ + public ArrayList getDxfs() { + return dxfs; + } + + /** + * Returns all strings that are in the SharedStringTable for this workbook. The SST contains + * all standard string records in cells, but may not include such things as strings that are contained + * within formulas. This is useful for such things as full text indexing of workbooks + * + * @return Strings in the workbook. + */ + public String[] getAllStrings() { + ArrayList al = this.getSharedStringTable().getAllStrings(); + String[] s = new String[al.size()]; + s = (String[]) al.toArray(s); + return s; + } + + /** + * set the pivot cache pointer + * + * @param pc initialized pivot cache + */ + public void setPivotCache(PivotCache pc) { + ptcache = pc; + } + + /** + * return the pivot cache, if any + * + * @return + */ + public PivotCache getPivotCache() { + return ptcache; + } + + /** + * clear out ALL sheet object refs + */ + public void closeSheets() { + for (int i = boundsheets.size() - 1; i > 0; i--) { + Boundsheet b = (Boundsheet) boundsheets.get(i); + if (b.streamer != null) { // do separately because may call + // boundsheet close + b.streamer.close(); + b.streamer = null; + } + b.close(); + if (tabs != null) + tabs.removeRecord(); + } + Object[] recs = this.getStreamer().getBiffRecords(); + boolean resetVars = (recs[0] != null); + boundsheets.clear(); + for (int i = 0; i < formulas.size(); i++) { + Formula f = (Formula) formulas.get(i); + f.close(); + } + this.formulas.clear(); + if (this.refTracker != null) + this.refTracker.clearCaches(); + this.formulashash.clear(); + // TODO: handle + this.indirectFormulas.clear(); + this.charts.clear(); + this.chartTemp.clear(); + if (firstBOF != null) { + firstBOF.close(); + this.firstBOF = null; + } + if (lasteof != null) { + lasteof.close(); + lasteof = null; + } + if (resetVars) { // just clearing out sheets instead of closing workbook + // reset lasteof for possible new insertion of sheets (if + // not removing workbook) ... + int i = recs.length - 1; + while (i > 0 && lasteof == null) { + if (recs[i] != null) { + if (((BiffRec) recs[i]).getOpcode() == EOF) + lasteof = (Eof) recs[i]; + } + i--; + } + } + if (lastBOF != null) { + lastBOF.close(); + lastBOF = null; + } + if (resetVars) { // just clearing out sheets instead of closing workbook + if (((BiffRec) recs[0]).getOpcode() == BOF) {// should!! + lastBOF = (Bof) recs[0]; + firstBOF = (Bof) recs[0]; + } + } + if (lastbound != null) { + lastbound.close(); + this.lastbound = null; + } + if (lastFormula != null) { + lastFormula.close(); + this.lastFormula = null; + } + this.workSheets.clear(); + } + + protected void closeRecords() { + } + + /** + * clear out object references in prep for closing workbook + */ + public void close() { + closeSheets(); + + if (this.isExcel2007) { + try { + String externalDir = OOXMLAdapter + .getTempDir(this.getFactory().getFileName()); + OOXMLAdapter.deleteDir(new File(externalDir)); + } catch (Exception e) { + } + } + contHandler.close(); + contHandler = new ContinueHandler(this); + + // clear out array list references + Iterator ii = bookNameRecs.keySet().iterator(); + while (ii.hasNext()) { + Name n = (Name) bookNameRecs.get(ii.next()); + n.close(); + } + this.bookNameRecs.clear(); + for (int i = 0; i < xfrecs.size(); i++) { + Xf x = (Xf) xfrecs.get(i); + x.close(); + } + this.xfrecs.clear(); + this.formatCache.clear(); + this.formatlookup.clear(); + this.formats.clear(); + this.fonts.clear(); + if (this.dxfs != null) + this.dxfs.clear(); + for (int i = 0; i < msodgMerge.size(); i++) { + MSODrawingGroup m = (MSODrawingGroup) msodgMerge.get(i); + m.close(); + } + this.msodgMerge.clear(); + if (this.msodg != null) { + msodg.close(); + msodg = null; + } + // TODO: handle + this.mergecelllookup.clear(); + this.hlinklookup.clear(); + this.externalnames.clear(); + for (int i = 0; i < names.size(); i++) { + Name n = (Name) names.get(i); + n.close(); + } + this.names.clear(); + for (int i = 0; i < indexes.size(); i++) { + Index ind = (Index) indexes.get(i); + ind.close(); + } + this.indexes.clear(); + if (lastidx != null) { + lastidx.close(); + lastidx = null; + } + if (this.stringTable != null) { + this.stringTable.close(); + this.stringTable = null; + } + + if (countryRec != null) { + this.countryRec.close(); + this.countryRec = null; + } + if (win1 != null) { + win1.close(); + win1 = null; + } + + if (this.drh != null) { + this.drh.close(); + drh = null; + } + + // integration point for subclasses + this.closeRecords(); + + this.contHandler = new ContinueHandler(this); + if (this.currai != null) { + this.currai.close(); + this.currai = null; + } + if (calcmoderec != null) { + calcmoderec.close(); + calcmoderec = null; + } + this.currchart = null; + this.currdrw = null; + if (this.protector != null) { + this.protector.close(); + this.protector = null; + } + if (this.myexternsheet != null) { + this.myexternsheet.close(); + this.myexternsheet = null; + } + if (this.refTracker != null) { + this.refTracker.close(); + this.refTracker = null; + } + if (tabs != null) { + tabs.close(); + tabs = null; + } + // TODO: deal + this.factory = null; + streamer = createByteStreamer(); + if (xl2k != null) + ((XLSRecord) xl2k).close(); + xl2k = null; + + } + + public Color[] getColorTable() { + return colorTable; + } + + public void setColorTable(Color[] clrtable) { + colorTable = clrtable; + } + + public int getDefaultIxfe() { + return defaultIxfe; + } + + public void setDefaultIxfe(int defaultIxfe) { + this.defaultIxfe = defaultIxfe; + } + + public int getCalcId() { + // TODO Auto-generated method stub + return 0; + } + + public void setCalcId(int i) { + // TODO Auto-generated method stub + + } } diff --git a/src/main/java/io/starter/formats/XLS/WorkBookAssembler.java b/src/main/java/io/starter/formats/XLS/WorkBookAssembler.java index bb2fbcb..84d7d6d 100644 --- a/src/main/java/io/starter/formats/XLS/WorkBookAssembler.java +++ b/src/main/java/io/starter/formats/XLS/WorkBookAssembler.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,39 +22,34 @@ */ package io.starter.formats.XLS; +import io.starter.formats.XLS.charts.Chart; + import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random; -import io.starter.formats.XLS.charts.Chart; - /** * WorkBookAssembler handles the details of how a biff8 file * should be constructed, what records go in what order, etc - * - * - * - * */ public class WorkBookAssembler implements XLSConstants { - - + + /** * assembleSheetRecs assembles the array of records, then ouputs * the ordered list to the bytestreamer, which should be the only * thing calling this. - * + *

                * The SheetRecs should contain all records excepting valrecs, rows, index, and dbcells at this - * point, where they should be created. - * + * point, where they should be created. + *

                * The dbcell creation and population also occurs here. The first pointer is the offset between * the beginning of the second row and the first valrec. After that it is just incrementing valrecs - * + *

                * TODO: John, please review use of collections & performance. Check the Dbcell.initDbCell method as well. * TODO: Add handling for creation of mulblank and mulrk records on the fly. We no longer have them! - * - * */ + */ public static List assembleSheetRecs(Boundsheet thissheet) { List addVec = new ArrayList(); WorkBookAssembler.preProcessSheet(thissheet); @@ -62,33 +57,30 @@ public static List assembleSheetRecs(Boundsheet thissheet) { if (!thissheet.isChartOnlySheet()) { addVec = WorkBookAssembler.assembleRows(thissheet, addVec); } - if(thissheet.getWorkBook().getCharts().length>0) { + if (thissheet.getWorkBook().getCharts().length > 0) { addVec = WorkBookAssembler.assembleChartRecs(thissheet, addVec); } return addVec; } - + /** * Handles functionality that needs to occur before the boundsheet can be * reliably created - - * */ private static void preProcessSheet(Boundsheet thissheet) { - if (thissheet.hasMergedCells()) { + if (thissheet.hasMergedCells()) { // update mergedcells first, as they may grow in record size and are not handled by continues. Iterator itx = thissheet.getMergedCellsRecs().iterator(); - while(itx.hasNext()){ - Mergedcells mrg = (Mergedcells)itx.next(); - mrg.update(); + while (itx.hasNext()) { + Mergedcells mrg = (Mergedcells) itx.next(); + mrg.update(); } } } - + /** * Add the rows of data into the worksheet level stream - - * + * * @param thissheet * @param addVec * @return @@ -98,7 +90,7 @@ private static List assembleRows(Boundsheet thissheet, List addVec) { Random gen = new Random(); int randomNumber = gen.nextInt(); int insertRowidx = 0; - if(dim !=null)insertRowidx = dim.getRecordIndex()+1; + if (dim != null) insertRowidx = dim.getRecordIndex() + 1; int insertValidx = insertRowidx; int rowCount = 0; // use this to break every 32 rows for a new dbcell int dbOffset = 0; // the offset between the first row and the dbcell @@ -107,92 +99,90 @@ private static List assembleRows(Boundsheet thissheet, List addVec) { List dbOffsets = new ArrayList(); // dbcell offsets int valrecOffset = 0; // KSC: clear out index dbcells - if (thissheet.getSheetIDX()!=null) - thissheet.getSheetIDX().resetDBCells(); + if (thissheet.getSheetIDX() != null) + thissheet.getSheetIDX().resetDBCells(); // NOTE: if below sorting is time-consuming, should input sort inserting and deleting rows and not here ... - Iterator outRows = thissheet.getSortedRows().keySet().iterator(); - while (outRows.hasNext()) { - if (rowCount==32) { - // Add a new dbcell for every 32 rows - Dbcell d = (Dbcell)Dbcell.getPrototype(); - dbOffsets.add(0, Short.valueOf((short)((rowCount-1)*20))); - d.initDbCell(dbOffset, dbOffsets); - addVec.add(insertValidx++, d); - if (thissheet.getSheetIDX()!=null) - thissheet.getSheetIDX().addDBCell(d); - insertRowidx = insertValidx; - dbOffsets = new ArrayList(); - rowCount = 0; - dbOffset = 0; - } - Row r = (Row)thissheet.getRowMap().get(outRows.next()); - rowCount++; - maxRow = Math.max(r.getRowNumber(),maxRow); - dbOffset += r.getLength(); - addVec.add(insertRowidx++, r); - insertValidx++; + Iterator outRows = thissheet.getSortedRows().keySet().iterator(); + while (outRows.hasNext()) { + if (rowCount == 32) { + // Add a new dbcell for every 32 rows + Dbcell d = (Dbcell) Dbcell.getPrototype(); + dbOffsets.add(0, Short.valueOf((short) ((rowCount - 1) * 20))); + d.initDbCell(dbOffset, dbOffsets); + addVec.add(insertValidx++, d); + if (thissheet.getSheetIDX() != null) + thissheet.getSheetIDX().addDBCell(d); + insertRowidx = insertValidx; + dbOffsets = new ArrayList(); + rowCount = 0; + dbOffset = 0; + } + Row r = (Row) thissheet.getRowMap().get(outRows.next()); + rowCount++; + maxRow = Math.max(r.getRowNumber(), maxRow); + dbOffset += r.getLength(); + addVec.add(insertRowidx++, r); + insertValidx++; - // insert the valrec and child recs from the row - Mulblank skipMull= null; - List outRecs = r.getValRecs(randomNumber); - Iterator it = outRecs.iterator(); - while(it.hasNext()) { - BiffRec or = (BiffRec)it.next(); - if (skipMull!=null) { - if (or==skipMull) - continue; - skipMull= null; - } - addVec.add(insertValidx++,or ); - valrecOffset += or.getLength(); - dbOffset+= or.getLength(); - short orc = or.getColNumber(); - if ((!it.hasNext()) && orc>maxCol) - maxCol = orc; - if (or.getOpcode()==MULBLANK) - skipMull= (Mulblank)or; + // insert the valrec and child recs from the row + Mulblank skipMull = null; + List outRecs = r.getValRecs(randomNumber); + Iterator it = outRecs.iterator(); + while (it.hasNext()) { + BiffRec or = (BiffRec) it.next(); + if (skipMull != null) { + if (or == skipMull) + continue; + skipMull = null; } - dbOffsets.add(Short.valueOf((short)valrecOffset)); - valrecOffset = 0; + addVec.add(insertValidx++, or); + valrecOffset += or.getLength(); + dbOffset += or.getLength(); + short orc = or.getColNumber(); + if ((!it.hasNext()) && orc > maxCol) + maxCol = orc; + if (or.getOpcode() == MULBLANK) + skipMull = (Mulblank) or; } + dbOffsets.add(Short.valueOf((short) valrecOffset)); + valrecOffset = 0; + } // add the final dbcell. Chart only sheets will not have an index, so ignore if so. - if (dbOffsets.size()>0 ) { - Dbcell d = (Dbcell)Dbcell.getPrototype(); - dbOffsets.add(0, Short.valueOf((short)((rowCount-1)*20))); + if (dbOffsets.size() > 0) { + Dbcell d = (Dbcell) Dbcell.getPrototype(); + dbOffsets.add(0, Short.valueOf((short) ((rowCount - 1) * 20))); d.initDbCell(dbOffset, dbOffsets); - d.setOffset(dbOffset); // KSC: Added to set dbCell offset + d.setOffset(dbOffset); // KSC: Added to set dbCell offset addVec.add(insertValidx++, d); - if (thissheet.getSheetIDX()!=null) + if (thissheet.getSheetIDX() != null) thissheet.getSheetIDX().addDBCell(d); } thissheet.updateDimensions(maxRow, maxCol/* 20100225 KSC: incrementing does not match Excel results: take out +1*/); return addVec; } - + /** * Get the index to the last db cell in the boundsheet * this is where objects can start being inserted - - * + * * @return */ private static int getLastDBCellLocation(List addVec) { - for (int i=addVec.size()-1;i>=0;i--) { - BiffRec b = (BiffRec)addVec.get(i); - if(b.getOpcode()==DBCELL)return i; + for (int i = addVec.size() - 1; i >= 0; i--) { + BiffRec b = (BiffRec) addVec.get(i); + if (b.getOpcode() == DBCELL) return i; } return 0; } - + /** * Add the chart records to the array. There are two types of charts to be added, * those that have a pointer into the worksheet level stream (ie they existed when the file was parsed) * and those that have been added post parse and/or are converted from ooxml. - * + *

                * We hold the location of the parsed ones to deal with odd inconsistencies that exist * with multiple drawing objects and txos - - * + * * @param thissheet * @param addVec * @return @@ -201,56 +191,56 @@ private static List assembleChartRecs(Boundsheet thissheet, List addVec) { // insert charts that have bound obj records in the stream int insertValidx = WorkBookAssembler.getLastDBCellLocation(addVec); ArrayList insertedCharts = new ArrayList(); - int chartInsert = insertValidx; - while (chartInsertspid && spid !=0 && !mm.isHeader())|| isHeader) { - insertValidx = chartInsert; - chartInsert = addVec.size(); - } + MSODrawing mm = (MSODrawing) x; + if ((mm.getSPID() > spid && spid != 0 && !mm.isHeader()) || isHeader) { + insertValidx = chartInsert; + chartInsert = addVec.size(); + } } - if (x.getOpcode() == WINDOW2 || x.getOpcode() == MSODRAWINGSELECTION - || x.getOpcode()==NOTE) { + if (x.getOpcode() == WINDOW2 || x.getOpcode() == MSODRAWINGSELECTION + || x.getOpcode() == NOTE) { insertValidx = chartInsert; chartInsert = addVec.size(); } - chartInsert++; + chartInsert++; } addVec.addAll(insertValidx, l); - insertValidx+=l.size(); + insertValidx += l.size(); } } return addVec; diff --git a/src/main/java/io/starter/formats/XLS/WorkBookException.java b/src/main/java/io/starter/formats/XLS/WorkBookException.java index d44b92b..68906f1 100644 --- a/src/main/java/io/starter/formats/XLS/WorkBookException.java +++ b/src/main/java/io/starter/formats/XLS/WorkBookException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,49 +22,52 @@ */ package io.starter.formats.XLS; -/** +/** + * */ public class WorkBookException -extends RuntimeException { - private static final long serialVersionUID = 6406202417397276014L; - - public final static int DOUBLE_STREAM_FILE = 0; - public final static int NOT_BIFF8_FILE = 1; - public final static int LICENSING_FAILED = 2; + extends RuntimeException { + private static final long serialVersionUID = 6406202417397276014L; + + public final static int DOUBLE_STREAM_FILE = 0; + public final static int NOT_BIFF8_FILE = 1; + public final static int LICENSING_FAILED = 2; public final static int UNSPECIFIED_INIT_ERROR = 3; public final static int RUNTIME_ERROR = 4; public final static int SMALLBLOCK_FILE = 5; - public final static int WRITING_ERROR = 6; - public final static int DECRYPTION_ERROR = 7; + public final static int WRITING_ERROR = 6; + public final static int DECRYPTION_ERROR = 7; public final static int DECRYPTION_INCORRECT_PASSWORD = 8; - public static final int ENCRYPTION_ERROR = 9; + public static final int ENCRYPTION_ERROR = 9; public final static int DECRYPTION_INCORRECT_FORMAT = 10; - + private final int error_code; - - public WorkBookException (String message, int code) { - super( message ); + + public WorkBookException(String message, int code) { + super(message); error_code = code; } - - public WorkBookException (String message, int code, Throwable cause) { - super( message, cause ); - error_code = code; + + public WorkBookException(String message, int code, Throwable cause) { + super(message, cause); + error_code = code; } - + public int getErrorCode() { return error_code; } - + public String toString() { return "WorkBook initialization failed: '" + getMessage() + "'"; } - - /** Obsolete synonym for getCause(). + + /** + * Obsolete synonym for getCause(). + * * @deprecated Use {@link Throwable#getCause()} instead. */ public Exception getWrappedException() { - Throwable cause = getCause(); + Throwable cause = getCause(); return cause instanceof Exception ? (Exception) cause : null; } } diff --git a/src/main/java/io/starter/formats/XLS/WorkBookFactory.java b/src/main/java/io/starter/formats/XLS/WorkBookFactory.java index 83192d6..8dd2e2b 100644 --- a/src/main/java/io/starter/formats/XLS/WorkBookFactory.java +++ b/src/main/java/io/starter/formats/XLS/WorkBookFactory.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,19 +22,22 @@ */ package io.starter.formats.XLS; -import java.io.*; +import io.starter.OpenXLS.WorkBookHandle; +import io.starter.formats.LEO.BlockByteConsumer; +import io.starter.formats.LEO.BlockByteReader; +import io.starter.formats.LEO.LEOFile; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.Logger; +import io.starter.toolkit.ProgressListener; + +import java.io.Serializable; import java.util.Iterator; import java.util.LinkedHashMap; -import io.starter.OpenXLS.CellHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.LEO.*; -import io.starter.toolkit.*; - /** * Factory for creating WorkBook objects from Byte streams. - * + * * @see WorkBook * @see XLSRecord */ @@ -42,16 +45,16 @@ public class WorkBookFactory implements io.starter.toolkit.ProgressNotifier, XLS public static final long serialVersionUID = 1233423412323l; protected int DEBUGLEVEL = 0; - + protected LEOFile myLEO; private String fname; public void setDebugLevel(int d) { - DEBUGLEVEL = d; + DEBUGLEVEL = d; } public int getDebugLevel() { - return DEBUGLEVEL; + return DEBUGLEVEL; } // Methods from ProgressNotifier @@ -61,34 +64,34 @@ public int getDebugLevel() { private String progresstext = ""; public void register(ProgressListener j) { - progresslistener = j; - j.addTarget(this); + progresslistener = j; + j.addTarget(this); } public void fireProgressChanged() { - // if (progresslistener != null) { - // progresslistener.updateProgress(); - // } + // if (progresslistener != null) { + // progresslistener.updateProgress(); + // } } public int getProgress() { - return progress; + return progress; } public String getProgressText() { - return progresstext; + return progresstext; } public void setProgress(int progress) { - this.progress = progress; + this.progress = progress; } public void setProgressText(String s) { - progresstext = s; + progresstext = s; } public boolean iscompleted() { - return done; + return done; } // end ProgressNotifier methods @@ -97,51 +100,51 @@ public boolean iscompleted() { * get the file name for the WorkBook */ public String getFileName() { - if (fname == null) - fname = myLEO.getFileName(); - return fname; + if (fname == null) + fname = myLEO.getFileName(); + return fname; } /** * sets the workbook filename associated with this wbfactory - * + * * @param f */ public void setFileName(String f) { - fname = f; + fname = f; } /** * return the next opcode/length in the Stream from the given record. */ public short lookAhead(BiffRec rec) { - int i = rec.getOffset() + rec.getLength(); - BlockByteReader parsedata = rec.getByteReader(); - if (parsedata == null) { - return rec.getOpcode(); - } + int i = rec.getOffset() + rec.getLength(); + BlockByteReader parsedata = rec.getByteReader(); + if (parsedata == null) { + return rec.getOpcode(); + } - byte[] b1 = parsedata.getHeaderBytes(i); + byte[] b1 = parsedata.getHeaderBytes(i); - short opcode = ByteTools.readShort(b1[0], b1[1]); - return opcode; + short opcode = ByteTools.readShort(b1[0], b1[1]); + return opcode; } LEOFile getLEOFile() { - return myLEO; + return myLEO; } /** * read in a WorkBook from a byte array. */ public Book getWorkBook(BlockByteReader parsedata, LEOFile leo) throws InvalidRecordException { - Book book = new WorkBook(); - return this.initWorkBook(book, parsedata, leo); + Book book = new WorkBook(); + return this.initWorkBook(book, parsedata, leo); } /** * Initialize the workbook - * + * * @param book * @param parsedata * @param leo @@ -149,165 +152,165 @@ public Book getWorkBook(BlockByteReader parsedata, LEOFile leo) throws InvalidRe * @throws InvalidRecordException */ public Book initWorkBook(Book book, BlockByteReader parsedata, LEOFile leo) throws InvalidRecordException { - - BlockByteReader blockByteReader = parsedata; - blockByteReader.setApplyRelativePosition(true); - - /** KSC: record-level validation */ - boolean bPerformRecordLevelValidation= false; // perform record-level validation if set - if (System.getProperty(VALIDATEWORKBOOK)!=null) { - if (System.getProperty(VALIDATEWORKBOOK).equals("true")) - bPerformRecordLevelValidation= true; - } - java.util.LinkedHashMap curSubstream= null; - java.util.LinkedHashMap sheetSubstream= null; - if (bPerformRecordLevelValidation) { - java.util.LinkedHashMap globalSubstream = new java.util.LinkedHashMap(); - fillGlobalSubstream(globalSubstream); - sheetSubstream = new java.util.LinkedHashMap(); - fillWorksSheetSubstream(sheetSubstream); - curSubstream = globalSubstream; - } - - myLEO = leo; - - book.setDebugLevel(this.DEBUGLEVEL); - book.setFactory(this); - boolean infile = false; - boolean isWBBOF= true; - short opcode = 0x00, reclen = 0x00, lastOpcode= 0x00; - int BofCount = 0; // track the number of 'Bof' records - - BiffRec rec = null; - int blen = parsedata.getLength(); - - // init the progress listener - this.progresstext = "Initializing Workbook..."; - progress = 0; - if (progresslistener != null) - progresslistener.setMaxProgress(blen); - this.fireProgressChanged(); - if (DEBUGLEVEL > 1) - Logger.logInfo("XLS File Size: " + String.valueOf(blen)); - - for (int i = 0; i <= blen - 4;) { - - this.fireProgressChanged(); // "" - byte[] headerbytes = parsedata.getHeaderBytes(i); - opcode = ByteTools.readShort(headerbytes[0], headerbytes[1]); - reclen = ByteTools.readShort(headerbytes[2], headerbytes[3]); - - if (lastOpcode==EOF && (opcode == 0) || (opcode == 0xffffffff)) { - int startpos = i - 3, junkreclen = 0, offset = 0; - - if (offset != 0) - junkreclen = (offset - startpos); - else - junkreclen = blen - i; - i += junkreclen - 1; - i += junkreclen - 1; - } else { // REAL REC - // sanity checks - if (reclen < 0) - throw new InvalidRecordException("WorkBookFactory.getWorkBook() Negative Reclen encountered pos:" + i + " opcode:0x" + Integer.toHexString(opcode)); - else if ((reclen + 1) > blen) - throw new InvalidRecordException("WorkBookFactory.getWorkBook() Reclen longer than data pos:" + i + " opcode:0x" + Integer.toHexString(opcode)); - - if (opcode == BOF || infile) { // if the first Bof has been - // reached, start - infile = true; - - // Init Record' - rec = this.parse(book, opcode, i, reclen, blockByteReader); - - if (progresslistener != null) { - progresslistener.setValue(i); - } - - /**** KSC: record-level validation ****/ - if (bPerformRecordLevelValidation && curSubstream != null) { - markRecord(curSubstream, rec, opcode); - } - - // write to the dump file if necessary - if (WorkBookHandle.dump_input != null) - try { - WorkBookHandle.dump_input.write("-------------------------------------" + "-------------------------\n" + ((XLSRecord) rec).getRecDesc() + ByteTools.getByteDump(blockByteReader.get((XLSRecord) rec, 0, reclen), 0) + "\n"); - WorkBookHandle.dump_input.flush(); - } catch (Exception e) { - Logger.logErr("error writing to dump file, " + "ceasing dump output: " + e); - WorkBookHandle.dump_input = null; - } - - if (rec == null) { // Effectively an EOF - if (DEBUGLEVEL > 0) - Logger.logInfo("done parsing WorkBook storage."); - this.done = true; - this.progresstext = "Done Reading WorkBook."; - this.fireProgressChanged(); - return book; - } else { - // not used anymore ((XLSRecord)rec).resetCacheBytes(); - } - // int reco = rec.getOffset() ; - // int recl = rec.getLength(); - int thisrecpos = i + reclen + 4; - - if (opcode == BOF) { - if (isWBBOF) { // do first Bof initialization - book.setFirstBof((io.starter.formats.XLS.Bof) rec); - isWBBOF = false; - } else if (bPerformRecordLevelValidation && BofCount==0 && lastOpcode!=EOF && curSubstream!=null) { - /***** KSC: record-level validation ****/ - // invalid record structure- no EOF before BOF - validateRecords(curSubstream, book, rec.getSheet()); - } - - BofCount++; - /***** KSC: record-level validation ****/ - if (bPerformRecordLevelValidation && curSubstream==null) { - // after global substream is processed, switch to sheet substream - reInitSubstream(sheetSubstream); - curSubstream= sheetSubstream; - curSubstream.get(BOF).isPresent= true; - curSubstream.get(BOF).recordPos= 0; - } - } else if (opcode == EOF) { - BofCount--; - - /***** KSC: record-level validation ****/ - if (bPerformRecordLevelValidation && BofCount==0 && curSubstream != null) { - validateRecords(curSubstream, book, rec.getSheet()); - curSubstream= null; - } - } - // end of Workbook - if (BofCount == -1) { - if (DEBUGLEVEL > 2) - Logger.logInfo("Last Bof"); - i += reclen; - thisrecpos = blen; - } - if (thisrecpos > 0) - i = thisrecpos; - lastOpcode= opcode; - } else { - throw new InvalidRecordException("No valid record found."); - } - - } - } - if (DEBUGLEVEL > 0) - Logger.logInfo("done"); - progress = blen; - this.progresstext = "Done Reading WorkBook."; - this.fireProgressChanged(); - - this.done = true; - // flag the book so we know it's ready for shared access - // book.setReady(true); ENTERPRISE ONLY - // recordata.setApplyRelativePosition(false); - return book; + + BlockByteReader blockByteReader = parsedata; + blockByteReader.setApplyRelativePosition(true); + + /** KSC: record-level validation */ + boolean bPerformRecordLevelValidation = false; // perform record-level validation if set + if (System.getProperty(VALIDATEWORKBOOK) != null) { + if (System.getProperty(VALIDATEWORKBOOK).equals("true")) + bPerformRecordLevelValidation = true; + } + java.util.LinkedHashMap curSubstream = null; + java.util.LinkedHashMap sheetSubstream = null; + if (bPerformRecordLevelValidation) { + java.util.LinkedHashMap globalSubstream = new java.util.LinkedHashMap(); + fillGlobalSubstream(globalSubstream); + sheetSubstream = new java.util.LinkedHashMap(); + fillWorksSheetSubstream(sheetSubstream); + curSubstream = globalSubstream; + } + + myLEO = leo; + + book.setDebugLevel(this.DEBUGLEVEL); + book.setFactory(this); + boolean infile = false; + boolean isWBBOF = true; + short opcode = 0x00, reclen = 0x00, lastOpcode = 0x00; + int BofCount = 0; // track the number of 'Bof' records + + BiffRec rec = null; + int blen = parsedata.getLength(); + + // init the progress listener + this.progresstext = "Initializing Workbook..."; + progress = 0; + if (progresslistener != null) + progresslistener.setMaxProgress(blen); + this.fireProgressChanged(); + if (DEBUGLEVEL > 1) + Logger.logInfo("XLS File Size: " + blen); + + for (int i = 0; i <= blen - 4; ) { + + this.fireProgressChanged(); // "" + byte[] headerbytes = parsedata.getHeaderBytes(i); + opcode = ByteTools.readShort(headerbytes[0], headerbytes[1]); + reclen = ByteTools.readShort(headerbytes[2], headerbytes[3]); + + if (lastOpcode == EOF && (opcode == 0) || (opcode == 0xffffffff)) { + int startpos = i - 3, junkreclen = 0, offset = 0; + + if (offset != 0) + junkreclen = (offset - startpos); + else + junkreclen = blen - i; + i += junkreclen - 1; + i += junkreclen - 1; + } else { // REAL REC + // sanity checks + if (reclen < 0) + throw new InvalidRecordException("WorkBookFactory.getWorkBook() Negative Reclen encountered pos:" + i + " opcode:0x" + Integer.toHexString(opcode)); + else if ((reclen + 1) > blen) + throw new InvalidRecordException("WorkBookFactory.getWorkBook() Reclen longer than data pos:" + i + " opcode:0x" + Integer.toHexString(opcode)); + + if (opcode == BOF || infile) { // if the first Bof has been + // reached, start + infile = true; + + // Init Record' + rec = this.parse(book, opcode, i, reclen, blockByteReader); + + if (progresslistener != null) { + progresslistener.setValue(i); + } + + /**** KSC: record-level validation ****/ + if (bPerformRecordLevelValidation && curSubstream != null) { + markRecord(curSubstream, rec, opcode); + } + + // write to the dump file if necessary + if (WorkBookHandle.dump_input != null) + try { + WorkBookHandle.dump_input.write("-------------------------------------" + "-------------------------\n" + ((XLSRecord) rec).getRecDesc() + ByteTools.getByteDump(blockByteReader.get((XLSRecord) rec, 0, reclen), 0) + "\n"); + WorkBookHandle.dump_input.flush(); + } catch (Exception e) { + Logger.logErr("error writing to dump file, " + "ceasing dump output: " + e); + WorkBookHandle.dump_input = null; + } + + if (rec == null) { // Effectively an EOF + if (DEBUGLEVEL > 0) + Logger.logInfo("done parsing WorkBook storage."); + this.done = true; + this.progresstext = "Done Reading WorkBook."; + this.fireProgressChanged(); + return book; + } else { + // not used anymore ((XLSRecord)rec).resetCacheBytes(); + } + // int reco = rec.getOffset() ; + // int recl = rec.getLength(); + int thisrecpos = i + reclen + 4; + + if (opcode == BOF) { + if (isWBBOF) { // do first Bof initialization + book.setFirstBof((io.starter.formats.XLS.Bof) rec); + isWBBOF = false; + } else if (bPerformRecordLevelValidation && BofCount == 0 && lastOpcode != EOF && curSubstream != null) { + /***** KSC: record-level validation ****/ + // invalid record structure- no EOF before BOF + validateRecords(curSubstream, book, rec.getSheet()); + } + + BofCount++; + /***** KSC: record-level validation ****/ + if (bPerformRecordLevelValidation && curSubstream == null) { + // after global substream is processed, switch to sheet substream + reInitSubstream(sheetSubstream); + curSubstream = sheetSubstream; + curSubstream.get(BOF).isPresent = true; + curSubstream.get(BOF).recordPos = 0; + } + } else if (opcode == EOF) { + BofCount--; + + /***** KSC: record-level validation ****/ + if (bPerformRecordLevelValidation && BofCount == 0 && curSubstream != null) { + validateRecords(curSubstream, book, rec.getSheet()); + curSubstream = null; + } + } + // end of Workbook + if (BofCount == -1) { + if (DEBUGLEVEL > 2) + Logger.logInfo("Last Bof"); + i += reclen; + thisrecpos = blen; + } + if (thisrecpos > 0) + i = thisrecpos; + lastOpcode = opcode; + } else { + throw new InvalidRecordException("No valid record found."); + } + + } + } + if (DEBUGLEVEL > 0) + Logger.logInfo("done"); + progress = blen; + this.progresstext = "Done Reading WorkBook."; + this.fireProgressChanged(); + + this.done = true; + // flag the book so we know it's ready for shared access + // book.setReady(true); ENTERPRISE ONLY + // recordata.setApplyRelativePosition(false); + return book; } /** @@ -315,48 +318,48 @@ else if ((reclen + 1) > blen) */ protected synchronized BiffRec parse(Book book, short opcode, int offset, int datalen, BlockByteReader bytebuf) throws InvalidRecordException { - // sanity checks - if ((datalen < 0) || (datalen > XLSConstants.MAXRECLEN)) - throw new InvalidRecordException("InvalidRecordException BAD RECORD LENGTH: " + " off: " + offset + " op: " + Integer.toHexString(opcode) + " len: " + datalen); - if (offset + datalen > bytebuf.getLength()) - throw new InvalidRecordException("InvalidRecordException RECORD LENGTH LONGER THAN FILE: " + " off: " + offset + " op: " + Integer.toHexString(opcode) + " len: " + datalen + " buflen:" + bytebuf.getLength()); - - // Create a new Record - BiffRec rec = XLSRecordFactory.getBiffRecord(opcode); - - // init the mighty rec - rec.setWorkBook((WorkBook) book); - rec.setByteReader(bytebuf); - rec.setLength((short) datalen); - rec.setOffset(offset); - rec.setDebugLevel(this.DEBUGLEVEL); - rec.setStreamer(book.getStreamer()); - - // send it to the CONTINUE handler - book.getContinueHandler().addRec(rec, (short) datalen); - // add it to the record stream - return book.addRecord(rec, true); + // sanity checks + if ((datalen < 0) || (datalen > XLSConstants.MAXRECLEN)) + throw new InvalidRecordException("InvalidRecordException BAD RECORD LENGTH: " + " off: " + offset + " op: " + Integer.toHexString(opcode) + " len: " + datalen); + if (offset + datalen > bytebuf.getLength()) + throw new InvalidRecordException("InvalidRecordException RECORD LENGTH LONGER THAN FILE: " + " off: " + offset + " op: " + Integer.toHexString(opcode) + " len: " + datalen + " buflen:" + bytebuf.getLength()); + + // Create a new Record + BiffRec rec = XLSRecordFactory.getBiffRecord(opcode); + + // init the mighty rec + rec.setWorkBook((WorkBook) book); + rec.setByteReader(bytebuf); + rec.setLength((short) datalen); + rec.setOffset(offset); + rec.setDebugLevel(this.DEBUGLEVEL); + rec.setStreamer(book.getStreamer()); + + // send it to the CONTINUE handler + book.getContinueHandler().addRec(rec, (short) datalen); + // add it to the record stream + return book.addRecord(rec, true); } /** * Find the location of the next particular opcode */ protected static int getNextOpcodeOffset(short op, BlockByteConsumer rec, BlockByteReader parsedata) { - boolean found = false; - int x = rec.getOffset(); - short opcode = 0x0; - while (!found && x < parsedata.getLength() - 2) { - opcode = ByteTools.readShort(parsedata.get(rec, x), parsedata.get(rec, ++x)); - if (opcode == op) { - found = true; - break; - } - } - if (!found) - return 0; - return x - 3; + boolean found = false; + int x = rec.getOffset(); + short opcode = 0x0; + while (!found && x < parsedata.getLength() - 2) { + opcode = ByteTools.readShort(parsedata.get(rec, x), parsedata.get(rec, ++x)); + if (opcode == op) { + found = true; + break; + } + } + if (!found) + return 0; + return x - 3; } - + /** * record-level validation: *

              • have ordered list of records for each substream (wb/global, worksheet) @@ -368,263 +371,265 @@ protected static int getNextOpcodeOffset(short op, BlockByteConsumer rec, BlockB /** * fill map with EVERY possible global (workbook)-level record, IN ORDER, plus flag if they are required or not * Used in record-level validation + * * @param map */ private void fillGlobalSubstream(LinkedHashMap map) { - // ordered list of all records in the global (workbook) substream, along - // with if they are required or not - map.put(BOF, new R(true)); - map.put((short) 134, new R(false)); // WriteProtect - map.put(FILEPASS, new R(false)); - map.put((short) 96, new R(false)); // Template - map.put(INTERFACE_HDR, new R(true)); - map.put((short) 193, new R(true)); // Mms - map.put((short) 226, new R(true)); // InterfaceEnd - map.put((short) 92, new R(true)); // WriteAccess - map.put((short) 91, new R(false)); // FileSharing - map.put((short) 66, new R(true)); // CodePage - map.put((short) 441, new R(false)); // LEL - SHOULD BE is required?? - map.put((short) 353, new R(true)); // DSF - is required?? - map.put((short) 448, new R(false)); // Excel9File - map.put(TABID, new R(true)); - map.put((short) 211, new R(false)); // ObjProj - map.put((short) 445, new R(false)); // *[ObNoMacros] - map.put(CODENAME, new R(false)); - map.put((short) 156, new R(false)); // BuiltInFnGroupCount - map.put((short) 154, new R(false)); // FnGroupName - map.put((short) 2200, new R(false)); // FnGrp12 - map.put((short) 222, new R(false)); // OleObjectSize - map.put(WINDOW_PROTECT, new R(true)); - map.put(PROTECT, new R(true)); - map.put(PASSWORD, new R(true)); - map.put(PROT4REV, new R(true)); - map.put((short) 444, new R(true)); // Prot4RevPass - map.put(WINDOW1, new R(true)); - map.put(BACKUP, new R(true)); - map.put((short) 141, new R(true)); // HideObj - map.put(DATE1904, new R(true)); - map.put((short) 14, new R(true)); // CalcPrecision - map.put((short) 439, new R(true)); // RefreshAll - map.put(BOOKBOOL, new R(true)); - map.put(FONT, new R(true)); // Is required??? - map.put(FORMAT, new R(true)); // Is required?? - map.put(XF, new R(true)); // Is required??? - map.put((short) 2172, new R(false)); // XfCRC - map.put((short) 2173, new R(false)); // XfExt - map.put((short) 2189, new R(false)); // DXF- Is required??? - map.put(STYLE, new R(true)); // Is required??? - map.put((short) 2194, new R(false)); // StyleExt - map.put(TABLESTYLES, new R(false)); - map.put((short) 2191, new R(false)); // TableStyle - map.put((short) 2192, new R(false)); // TABLESTYLEELEMENT - map.put(PALETTE, new R(false)); - map.put((short) 4188, new R(false)); // CLRTCLIENT - map.put(SXSTREAMID, new R(false)); - map.put(SXVS, new R(false)); - map.put(DCONNAME, new R(false)); - map.put(DCONBIN, new R(false)); - map.put(DCONREF, new R(false)); - map.put((short) 208, new R(false)); // SXTbl - map.put((short) 210, new R(false)); // SxTbpg - map.put((short) 209, new R(false)); // SXTBRGIITM - map.put(SXSTRING, new R(false)); - map.put((short) 220, new R(false)); // DbOrParamQry - map.put(SXADDL, new R(false)); - - map.put((short) 184, new R(false)); // DocRoute - map.put((short) 185, new R(false)); // RECIPNAME - map.put(USERBVIEW, new R(false)); // SHOULD BE Is but isn't present - // ************************* - map.put((short) 352, new R(true)); // UsesELFs - map.put(BOUNDSHEET, new R(true)); - map.put((short) 2180, new R(false)); // MDTInfo - // *ContinueFrt12 - map.put((short) 2181, new R(false)); // MDXStr - // *ContinueFrt12 - map.put((short) 2182, new R(false)); // MDXTuple - // *ContinueFrt12 - map.put((short) 2183, new R(false)); // MDXSet - map.put((short) 2184, new R(false)); // MDXProp - map.put((short) 2185, new R(false)); // MDXKPI - map.put((short) 2186, new R(false)); // MDB - map.put((short) 2202, new R(false)); // MTRSettings - map.put((short) 2211, new R(false)); // ForceFullCalculation - map.put(COUNTRY, new R(true)); - map.put(SUPBOOK, new R(false)); // SHOULD BE IsRequired but isn't present ****************** - map.put(EXTERNNAME, new R(false)); - map.put(XCT, new R(false)); - map.put(CRN, new R(false)); - map.put(EXTERNSHEET, new R(false)); - map.put(NAME, new R(false)); // Name is Required??? - map.put((short) 2196, new R(false)); // NameCmt - map.put((short) 2201, new R(false)); // NameFnGrp12 - map.put((short) 2195, new R(false)); // NamePublish - map.put((short) 2067, new R(false)); // RealTimeData A required record - // is not present: 430 - // *ContinueFrt - map.put((short) 449, new R(false)); // RecalcId - map.put((short) 2150, new R(false)); // HFPicture should be required???? - map.put(MSODRAWINGGROUP, new R(false)); // should be required?? - // Continue - map.put(SST, new R(true)); // should be required?? - // Continue - map.put(EXTSST, new R(true)); - map.put((short) 2049, new R(false)); // WebPub should be required??? - map.put((short) 2059, new R(false)); // WOpt - map.put((short) 2149, new R(false)); // CrErr - map.put((short) 2147, new R(false)); // BookExt - map.put((short) 2151, new R(false)); // FeatHdr should be required??? - map.put((short) 2166, new R(false)); // DConn should be required??? - map.put((short) 2198, new R(false)); // Theme - // *ContinueFrt12 - map.put((short) 2203, new R(false)); // CompressPictures - map.put((short) 2188, new R(false)); // Compat12 - map.put((short) 2199, new R(false)); // GUIDTypeLib - map.put(EOF, new R(true)); + // ordered list of all records in the global (workbook) substream, along + // with if they are required or not + map.put(BOF, new R(true)); + map.put((short) 134, new R(false)); // WriteProtect + map.put(FILEPASS, new R(false)); + map.put((short) 96, new R(false)); // Template + map.put(INTERFACE_HDR, new R(true)); + map.put((short) 193, new R(true)); // Mms + map.put((short) 226, new R(true)); // InterfaceEnd + map.put((short) 92, new R(true)); // WriteAccess + map.put((short) 91, new R(false)); // FileSharing + map.put((short) 66, new R(true)); // CodePage + map.put((short) 441, new R(false)); // LEL - SHOULD BE is required?? + map.put((short) 353, new R(true)); // DSF - is required?? + map.put((short) 448, new R(false)); // Excel9File + map.put(TABID, new R(true)); + map.put((short) 211, new R(false)); // ObjProj + map.put((short) 445, new R(false)); // *[ObNoMacros] + map.put(CODENAME, new R(false)); + map.put((short) 156, new R(false)); // BuiltInFnGroupCount + map.put((short) 154, new R(false)); // FnGroupName + map.put((short) 2200, new R(false)); // FnGrp12 + map.put((short) 222, new R(false)); // OleObjectSize + map.put(WINDOW_PROTECT, new R(true)); + map.put(PROTECT, new R(true)); + map.put(PASSWORD, new R(true)); + map.put(PROT4REV, new R(true)); + map.put((short) 444, new R(true)); // Prot4RevPass + map.put(WINDOW1, new R(true)); + map.put(BACKUP, new R(true)); + map.put((short) 141, new R(true)); // HideObj + map.put(DATE1904, new R(true)); + map.put((short) 14, new R(true)); // CalcPrecision + map.put((short) 439, new R(true)); // RefreshAll + map.put(BOOKBOOL, new R(true)); + map.put(FONT, new R(true)); // Is required??? + map.put(FORMAT, new R(true)); // Is required?? + map.put(XF, new R(true)); // Is required??? + map.put((short) 2172, new R(false)); // XfCRC + map.put((short) 2173, new R(false)); // XfExt + map.put((short) 2189, new R(false)); // DXF- Is required??? + map.put(STYLE, new R(true)); // Is required??? + map.put((short) 2194, new R(false)); // StyleExt + map.put(TABLESTYLES, new R(false)); + map.put((short) 2191, new R(false)); // TableStyle + map.put((short) 2192, new R(false)); // TABLESTYLEELEMENT + map.put(PALETTE, new R(false)); + map.put((short) 4188, new R(false)); // CLRTCLIENT + map.put(SXSTREAMID, new R(false)); + map.put(SXVS, new R(false)); + map.put(DCONNAME, new R(false)); + map.put(DCONBIN, new R(false)); + map.put(DCONREF, new R(false)); + map.put((short) 208, new R(false)); // SXTbl + map.put((short) 210, new R(false)); // SxTbpg + map.put((short) 209, new R(false)); // SXTBRGIITM + map.put(SXSTRING, new R(false)); + map.put((short) 220, new R(false)); // DbOrParamQry + map.put(SXADDL, new R(false)); + + map.put((short) 184, new R(false)); // DocRoute + map.put((short) 185, new R(false)); // RECIPNAME + map.put(USERBVIEW, new R(false)); // SHOULD BE Is but isn't present + // ************************* + map.put((short) 352, new R(true)); // UsesELFs + map.put(BOUNDSHEET, new R(true)); + map.put((short) 2180, new R(false)); // MDTInfo + // *ContinueFrt12 + map.put((short) 2181, new R(false)); // MDXStr + // *ContinueFrt12 + map.put((short) 2182, new R(false)); // MDXTuple + // *ContinueFrt12 + map.put((short) 2183, new R(false)); // MDXSet + map.put((short) 2184, new R(false)); // MDXProp + map.put((short) 2185, new R(false)); // MDXKPI + map.put((short) 2186, new R(false)); // MDB + map.put((short) 2202, new R(false)); // MTRSettings + map.put((short) 2211, new R(false)); // ForceFullCalculation + map.put(COUNTRY, new R(true)); + map.put(SUPBOOK, new R(false)); // SHOULD BE IsRequired but isn't present ****************** + map.put(EXTERNNAME, new R(false)); + map.put(XCT, new R(false)); + map.put(CRN, new R(false)); + map.put(EXTERNSHEET, new R(false)); + map.put(NAME, new R(false)); // Name is Required??? + map.put((short) 2196, new R(false)); // NameCmt + map.put((short) 2201, new R(false)); // NameFnGrp12 + map.put((short) 2195, new R(false)); // NamePublish + map.put((short) 2067, new R(false)); // RealTimeData A required record + // is not present: 430 + // *ContinueFrt + map.put((short) 449, new R(false)); // RecalcId + map.put((short) 2150, new R(false)); // HFPicture should be required???? + map.put(MSODRAWINGGROUP, new R(false)); // should be required?? + // Continue + map.put(SST, new R(true)); // should be required?? + // Continue + map.put(EXTSST, new R(true)); + map.put((short) 2049, new R(false)); // WebPub should be required??? + map.put((short) 2059, new R(false)); // WOpt + map.put((short) 2149, new R(false)); // CrErr + map.put((short) 2147, new R(false)); // BookExt + map.put((short) 2151, new R(false)); // FeatHdr should be required??? + map.put((short) 2166, new R(false)); // DConn should be required??? + map.put((short) 2198, new R(false)); // Theme + // *ContinueFrt12 + map.put((short) 2203, new R(false)); // CompressPictures + map.put((short) 2188, new R(false)); // Compat12 + map.put((short) 2199, new R(false)); // GUIDTypeLib + map.put(EOF, new R(true)); } /** * fill map with EVERY possible worksheet-level record, IN ORDER, plus flag if they are required or not. * Used in record-level validation + * * @param map */ private void fillWorksSheetSubstream(LinkedHashMap map) { - map.put(BOF, new R(true)); - map.put((short) 94, new R(false)); // Uncalced - map.put(INDEX, new R(true)); - map.put(CALCMODE, new R(true)); - map.put(CALCCOUNT, new R(true)); - map.put((short) 15, new R(true)); //CalcRefMode - map.put((short) 17, new R(true)); // CalcIter - map.put((short) 16, new R(true)); // CalcDelta - map.put((short) 95, new R(true)); // CalcSaveRecalc - map.put(PRINTROWCOL, new R(true)); - map.put(PRINTGRID, new R(true)); - map.put((short) 130, new R(true)); // GridSet - map.put(GUTS, new R(true)); - map.put(DEFAULTROWHEIGHT, new R(true)); - map.put(WSBOOL, new R(true)); - map.put((short) 151, new R(false)); // [Sync] - map.put((short) 152, new R(false)); // [LPr] - map.put(HORIZONTAL_PAGE_BREAKS, new R(false)); //[HorizontalPageBreaks] - map.put(VERTICAL_PAGE_BREAKS, new R(false)); //[VerticalPageBreaks] - map.put(HEADERREC, new R(true)); - map.put(FOOTERREC, new R(true)); - map.put(HCENTER, new R(true)); - map.put(VCENTER, new R(true)); - map.put(LEFT_MARGIN, new R(false)); - map.put(RIGHT_MARGIN, new R(false)); - map.put(TOP_MARGIN, new R(false)); - map.put(BOTTOM_MARGIN, new R(false)); - map.put(PLS, new R(false)); + map.put(BOF, new R(true)); + map.put((short) 94, new R(false)); // Uncalced + map.put(INDEX, new R(true)); + map.put(CALCMODE, new R(true)); + map.put(CALCCOUNT, new R(true)); + map.put((short) 15, new R(true)); //CalcRefMode + map.put((short) 17, new R(true)); // CalcIter + map.put((short) 16, new R(true)); // CalcDelta + map.put((short) 95, new R(true)); // CalcSaveRecalc + map.put(PRINTROWCOL, new R(true)); + map.put(PRINTGRID, new R(true)); + map.put((short) 130, new R(true)); // GridSet + map.put(GUTS, new R(true)); + map.put(DEFAULTROWHEIGHT, new R(true)); + map.put(WSBOOL, new R(true)); + map.put((short) 151, new R(false)); // [Sync] + map.put((short) 152, new R(false)); // [LPr] + map.put(HORIZONTAL_PAGE_BREAKS, new R(false)); //[HorizontalPageBreaks] + map.put(VERTICAL_PAGE_BREAKS, new R(false)); //[VerticalPageBreaks] + map.put(HEADERREC, new R(true)); + map.put(FOOTERREC, new R(true)); + map.put(HCENTER, new R(true)); + map.put(VCENTER, new R(true)); + map.put(LEFT_MARGIN, new R(false)); + map.put(RIGHT_MARGIN, new R(false)); + map.put(TOP_MARGIN, new R(false)); + map.put(BOTTOM_MARGIN, new R(false)); + map.put(PLS, new R(false)); // Continue - map.put(SETUP, new R(true)); - map.put((short) 0x89c, new R(false)); //[HeaderFooter] - map.put((short) 233, new R(false)); //[ BkHim ] - map.put((short) 1048, new R(false)); // BigName + map.put(SETUP, new R(true)); + map.put((short) 0x89c, new R(false)); //[HeaderFooter] + map.put((short) 233, new R(false)); //[ BkHim ] + map.put((short) 1048, new R(false)); // BigName // *ContinueBigName - map.put(PROTECT, new R(false)); - map.put(SCENPROTECT, new R(false)); - map.put(OBJPROTECT, new R(false)); - map.put(PASSWORD, new R(false)); - map.put(DEFCOLWIDTH, new R(true)); - map.put(COLINFO, new R(false)); - map.put((short) 174, new R(false)); // [ScenMan *( - map.put((short) 175, new R(false)); // SCENARIO - // Continue - map.put((short) 144, new R(false)); // Sort - map.put((short) 2197, new R(false)); // SortData - // ContinueFrt12 - map.put((short) 155, new R(false)); // Filtermode - map.put((short) 2164, new R(false)); // DropDownObjIds - map.put((short)157, new R(false)); // AutoFilterInfo - map.put(AUTOFILTER, new R(false)); - map.put((short) 2174, new R(false)); // AutoFilter12 - map.put((short) 2197, new R(false)); // SortData + map.put(PROTECT, new R(false)); + map.put(SCENPROTECT, new R(false)); + map.put(OBJPROTECT, new R(false)); + map.put(PASSWORD, new R(false)); + map.put(DEFCOLWIDTH, new R(true)); + map.put(COLINFO, new R(false)); + map.put((short) 174, new R(false)); // [ScenMan *( + map.put((short) 175, new R(false)); // SCENARIO + // Continue + map.put((short) 144, new R(false)); // Sort + map.put((short) 2197, new R(false)); // SortData + // ContinueFrt12 + map.put((short) 155, new R(false)); // Filtermode + map.put((short) 2164, new R(false)); // DropDownObjIds + map.put((short) 157, new R(false)); // AutoFilterInfo + map.put(AUTOFILTER, new R(false)); + map.put((short) 2174, new R(false)); // AutoFilter12 + map.put((short) 2197, new R(false)); // SortData //*ContinueFrt12] - map.put(DIMENSIONS, new R(true)); + map.put(DIMENSIONS, new R(true)); // [CELLTABLE] - map.put(ROW, new R(false)); - // celltable entries - map.put(BLANK, new R(false)); - map.put(MULBLANK, new R(false)); - map.put(RK,new R(false)); - map.put(BOOLERR, new R(false)); - map.put(NUMBER, new R(false)); - map.put(LABELSST, new R(false)); - map.put((short) 94, new R(false)); // uncalced - map.put(FORMULA, new R(false)); - map.put(MULRK, new R(false)); - map.put(STRINGREC, new R(false));; - map.put(SHRFMLA, new R(false)); - map.put(ARRAY, new R(false)); - map.put(TABLE, new R(false)); - map.put((short) 450, new R(false)); // EntExU2 + map.put(ROW, new R(false)); + // celltable entries + map.put(BLANK, new R(false)); + map.put(MULBLANK, new R(false)); + map.put(RK, new R(false)); + map.put(BOOLERR, new R(false)); + map.put(NUMBER, new R(false)); + map.put(LABELSST, new R(false)); + map.put((short) 94, new R(false)); // uncalced + map.put(FORMULA, new R(false)); + map.put(MULRK, new R(false)); + map.put(STRINGREC, new R(false)); + map.put(SHRFMLA, new R(false)); + map.put(ARRAY, new R(false)); + map.put(TABLE, new R(false)); + map.put((short) 450, new R(false)); // EntExU2 // [OBJECTS] - map.put(MSODRAWING, new R(false)); - map.put(TXO, new R(false)); - map.put(OBJ, new R(false)); - map.get(OBJ).altPrecedor= new short[]{MSODRAWING}; // Obj can follow MSODRAWING or TXO - //Charts -- most record opcodes are > 4000 - ignore !! - map.put(CHARTFRTINFO, new R(false)); - // do not include the majority of chart records but do include these: - map.put(STARTBLOCK, new R(false)); - map.put(CRTLAYOUT12, new R(false)); - map.put(CRTLAYOUT12A, new R(false)); - map.put(CATLAB, new R(false)); - map.put(DATALABEXT, new R(false)); - map.put(DATALABEXTCONTENTS, new R(false)); - map.put((short) 2138, new R(false)); // FrtFontList - map.put((short) 2213, new R(false)); // TextPropsStream - map.put((short) 2214, new R(false)); // RichTextStream - map.put((short) 2212, new R(false)); // ShapePropsStream - map.put((short) 2206, new R(false)); // CtrlMlFrt - map.put(ENDBLOCK, new R(false)); - map.put(STARTOBJECT, new R(false)); - map.put(FRTWRAPPER, new R(false)); - map.put(ENDOBJECT, new R(false)); + map.put(MSODRAWING, new R(false)); + map.put(TXO, new R(false)); + map.put(OBJ, new R(false)); + map.get(OBJ).altPrecedor = new short[]{MSODRAWING}; // Obj can follow MSODRAWING or TXO + //Charts -- most record opcodes are > 4000 - ignore !! + map.put(CHARTFRTINFO, new R(false)); + // do not include the majority of chart records but do include these: + map.put(STARTBLOCK, new R(false)); + map.put(CRTLAYOUT12, new R(false)); + map.put(CRTLAYOUT12A, new R(false)); + map.put(CATLAB, new R(false)); + map.put(DATALABEXT, new R(false)); + map.put(DATALABEXTCONTENTS, new R(false)); + map.put((short) 2138, new R(false)); // FrtFontList + map.put((short) 2213, new R(false)); // TextPropsStream + map.put((short) 2214, new R(false)); // RichTextStream + map.put((short) 2212, new R(false)); // ShapePropsStream + map.put((short) 2206, new R(false)); // CtrlMlFrt + map.put(ENDBLOCK, new R(false)); + map.put(STARTOBJECT, new R(false)); + map.put(FRTWRAPPER, new R(false)); + map.put(ENDOBJECT, new R(false)); // CHARTSHEETCONTENT = BOF [WriteProtect] [SheetExt] [WebPub] *HFPicture PAGESETUP PrintSize [HeaderFooter] [BACKGROUND] *Fbi *Fbi2 [ClrtClient] [PROTECTION] [Palette] [SXViewLink] [PivotChartBits] [SBaseRef] [MsoDrawingGroup] OBJECTS Units CHARTFOMATS SERIESDATA *WINDOW *CUSTOMVIEW [CodeName] [CRTMLFRT] EOF //Chart Begin *2FONTLIST SclPlotGrowth [FRAME] *SERIESFORMAT *SS ShtProps *2DFTTEXT AxesUsed 1*2AXISPARENT [CrtLayout12A] [DAT] *ATTACHEDLABEL [CRTMLFRT] *([DataLabExt StartObject] ATTACHEDLABEL [EndObject]) [TEXTPROPS] *2CRTMLFRT End - map.put(MSODRAWINGSELECTION, new R(false)); - map.put((short) 2150, new R(false)); //*HFPicture - map.put(NOTE, new R(false)); - //*PIVOTVIEW - map.put(SXVIEW, new R(false)); - map.put(SXVD, new R(false)); - map.put(SXVI, new R(false)); - map.put(SXVDEX, new R(false)); - map.put(SXIVD, new R(false)); - map.put(SXPI, new R(false)); - map.put(SXDI, new R(false)); - map.put(SXLI, new R(false)); - map.put(SXEX, new R(false)); - map.put((short) 247, new R(false)); //SXSelect - map.put((short) 240, new R(false)); // SXRULE - map.put(SXFORMAT, new R(false)); + map.put(MSODRAWINGSELECTION, new R(false)); + map.put((short) 2150, new R(false)); //*HFPicture + map.put(NOTE, new R(false)); + //*PIVOTVIEW + map.put(SXVIEW, new R(false)); + map.put(SXVD, new R(false)); + map.put(SXVI, new R(false)); + map.put(SXVDEX, new R(false)); + map.put(SXIVD, new R(false)); + map.put(SXPI, new R(false)); + map.put(SXDI, new R(false)); + map.put(SXLI, new R(false)); + map.put(SXEX, new R(false)); + map.put((short) 247, new R(false)); //SXSelect + map.put((short) 240, new R(false)); // SXRULE + map.put(SXFORMAT, new R(false)); // PIVOTRULE - map.put((short) 244, new R(false)); // SXDXF - map.put(QSISXTAG, new R(false)); - //DBQUERYEXT - map.put((short) 2051, new R(false)); // DBQUERYEX - map.put((short) 2052, new R(false)); // EXTSTRING - map.put(SXSTRING, new R(false)); - map.put((short) 2060, new R(false)); // SXVIEWEX - map.put((short) 2061, new R(false)); // SXTH - map.put((short) 2062, new R(false)); // SXPIEx - map.put((short) 256, new R(false)); // SXVDTEx - map.put(SXVIEWEX9, new R(false)); - map.put(SXADDL, new R(false)); - - map.put(DCON, new R(false)); - map.put(DCONNAME, new R(false)); - map.put(DCONBIN, new R(false)); - map.put(DCONREF, new R(false)); - map.put(WINDOW2, new R(true)); - map.put(PLV, new R(false)); - map.put(SCL, new R(false)); - map.put(PANE, new R(false)); - map.put(SELECTION, new R(false)); - map.put((short) 426, new R(false)); // UserSViewBegin + map.put((short) 244, new R(false)); // SXDXF + map.put(QSISXTAG, new R(false)); + //DBQUERYEXT + map.put((short) 2051, new R(false)); // DBQUERYEX + map.put((short) 2052, new R(false)); // EXTSTRING + map.put(SXSTRING, new R(false)); + map.put((short) 2060, new R(false)); // SXVIEWEX + map.put((short) 2061, new R(false)); // SXTH + map.put((short) 2062, new R(false)); // SXPIEx + map.put((short) 256, new R(false)); // SXVDTEx + map.put(SXVIEWEX9, new R(false)); + map.put(SXADDL, new R(false)); + + map.put(DCON, new R(false)); + map.put(DCONNAME, new R(false)); + map.put(DCONBIN, new R(false)); + map.put(DCONREF, new R(false)); + map.put(WINDOW2, new R(true)); + map.put(PLV, new R(false)); + map.put(SCL, new R(false)); + map.put(PANE, new R(false)); + map.put(SELECTION, new R(false)); + map.put((short) 426, new R(false)); // UserSViewBegin /* map.put(SELECTION, new R(false)); map.put(HORIZONTAL_PAGE_BREAKS, new R(false)); map.put(VERTICAL_PAGE_BREAKS, new R(false)); @@ -639,102 +644,104 @@ private void fillWorksSheetSubstream(LinkedHashMap map) { map.put(PLS, new R(false)); map.put(SETUP, new R(false)); map.put((short) 51, new R(false)); //([PrintSize] - map.put((short) 2204, new R(false)); //[HeaderFooter]*/ - // here ??? [AUTOFILTER] - map.put((short) 427, new R(false)); //UserSViewEnd - - map.put((short) 319, new R(false)); // RRSort - map.put((short) 153, new R(false)); //[DxGCol] - map.put(MERGEDCELLS, new R(false)); //*MergeCells - map.put((short) 351, new R(false)); // [LRng] + map.put((short) 2204, new R(false)); //[HeaderFooter]*/ + // here ??? [AUTOFILTER] + map.put((short) 427, new R(false)); //UserSViewEnd + + map.put((short) 319, new R(false)); // RRSort + map.put((short) 153, new R(false)); //[DxGCol] + map.put(MERGEDCELLS, new R(false)); //*MergeCells + map.put((short) 351, new R(false)); // [LRng] // *QUERYTABLE - map.put(PHONETIC, new R(false)); - map.put(CONDFMT, new R(false)); - map.put(CF, new R(false)); - map.put(CONDFMT12, new R(false)); - map.put(CF12, new R(false)); - map.put((short) 2171, new R(false)); // CFEx - map.put(HLINK, new R(false)); - map.put((short) 2048, new R(false)); //HLinkTooltip - map.put(DVAL, new R(false)); - map.put(DV, new R(false)); - map.put(CODENAME, new R(false)); // [CodeName] - map.put((short) 2049, new R(false)); // *WebPub - map.put((short) 2156, new R(false)); // *CellWatch - map.put((short) 2146, new R(false)); // SheetExt] - map.put(FEATHEADR, new R(false)); - map.put((short)2152, new R(false)); // FEAT + map.put(PHONETIC, new R(false)); + map.put(CONDFMT, new R(false)); + map.put(CF, new R(false)); + map.put(CONDFMT12, new R(false)); + map.put(CF12, new R(false)); + map.put((short) 2171, new R(false)); // CFEx + map.put(HLINK, new R(false)); + map.put((short) 2048, new R(false)); //HLinkTooltip + map.put(DVAL, new R(false)); + map.put(DV, new R(false)); + map.put(CODENAME, new R(false)); // [CodeName] + map.put((short) 2049, new R(false)); // *WebPub + map.put((short) 2156, new R(false)); // *CellWatch + map.put((short) 2146, new R(false)); // SheetExt] + map.put(FEATHEADR, new R(false)); + map.put((short) 2152, new R(false)); // FEAT // *FEAT11 // *RECORD12 - map.put((short)2248, new R(false)); // UNKNOWN RECORD!!! - map.put(EOF, new R(true)); + map.put((short) 2248, new R(false)); // UNKNOWN RECORD!!! + map.put(EOF, new R(true)); } - + /** * add all missing REQUIRED records from the appropriate substream for record-level validation - * @param map substream list storing if present, required ... - * @param book + * + * @param map substream list storing if present, required ... + * @param book */ private void validateRecords(LinkedHashMap map, Book book, Boundsheet bs) { - // use array instead of iterator as may have to traverse more than once - Short[] opcodes= new Short[0]; - java.util.Set ss= map.keySet(); - opcodes= ss.toArray(opcodes); - R lastR = new R(false); - lastR.recordPos= 0; - int lastOp= -1; - if (bs!=null && bs.isChartOnlySheet()) - return; // don't validate chart-only sheets - - // traverse thru stream list, ensuring required records are present; create if not - for (int i= 0; i < opcodes.length; i++) { - short op= opcodes[i]; - R r = map.get(op); - if (!r.isPresent && r.isRequired) { - // io.starter.toolkit.Logger.log("A required record is not present: " + op); - // Create a new Record - BiffRec rec= createMissingRequiredRecord(op, book, bs); - rec.setDebugLevel(this.DEBUGLEVEL); - int recPos= lastR.recordPos+1; - try { - while (true) { // now get to LAST record if there are multiple records - BiffRec lastRec= (bs==null)?book.getStreamer().getRecordAt(recPos):(BiffRec)bs.getSheetRecs().get(recPos); - if (lastRec.getOpcode()!=lastOp) - break; - recPos++; - } - } catch (IndexOutOfBoundsException e) { - } - book.getStreamer().addRecordAt(rec, recPos); - book.addRecord(rec, false); // false= already added to streamer or sheet - if (bs!=null) - rec.setSheet(bs); - - r.recordPos= rec.getRecordIndex(); - - // now must adjust ensuing record positions to account for inserted record - for (int zz= 0; zz < opcodes.length; zz++) { - R nextR = map.get(opcodes[zz]); - if (nextR.isPresent && !r.equals(nextR) && nextR.recordPos >= r.recordPos) - nextR.recordPos++; - } - r.isPresent= true; - } - if (r.isPresent) { - lastR= r; - lastOp= op; - } - } - // go thru 1 more time to ensure record order is correct - validateRecordOrder(map, ((bs==null)?book.getStreamer().records:bs.getSheetRecs()), opcodes); + // use array instead of iterator as may have to traverse more than once + Short[] opcodes = new Short[0]; + java.util.Set ss = map.keySet(); + opcodes = ss.toArray(opcodes); + R lastR = new R(false); + lastR.recordPos = 0; + int lastOp = -1; + if (bs != null && bs.isChartOnlySheet()) + return; // don't validate chart-only sheets + + // traverse thru stream list, ensuring required records are present; create if not + for (int i = 0; i < opcodes.length; i++) { + short op = opcodes[i]; + R r = map.get(op); + if (!r.isPresent && r.isRequired) { + // io.starter.toolkit.Logger.log("A required record is not present: " + op); + // Create a new Record + BiffRec rec = createMissingRequiredRecord(op, book, bs); + rec.setDebugLevel(this.DEBUGLEVEL); + int recPos = lastR.recordPos + 1; + try { + while (true) { // now get to LAST record if there are multiple records + BiffRec lastRec = (bs == null) ? book.getStreamer().getRecordAt(recPos) : (BiffRec) bs.getSheetRecs().get(recPos); + if (lastRec.getOpcode() != lastOp) + break; + recPos++; + } + } catch (IndexOutOfBoundsException e) { + } + book.getStreamer().addRecordAt(rec, recPos); + book.addRecord(rec, false); // false= already added to streamer or sheet + if (bs != null) + rec.setSheet(bs); + + r.recordPos = rec.getRecordIndex(); + + // now must adjust ensuing record positions to account for inserted record + for (int zz = 0; zz < opcodes.length; zz++) { + R nextR = map.get(opcodes[zz]); + if (nextR.isPresent && !r.equals(nextR) && nextR.recordPos >= r.recordPos) + nextR.recordPos++; + } + r.isPresent = true; + } + if (r.isPresent) { + lastR = r; + lastOp = op; + } + } + // go thru 1 more time to ensure record order is correct + validateRecordOrder(map, ((bs == null) ? book.getStreamer().records : bs.getSheetRecs()), opcodes); } - + /** * given substream map, actual list of records and list of ordered opcodes, validate record order in substream *
                if necessary, reorgainze records to follow order in map - * @param map ordered map of substream records indexed by opcodes - * @param list list of actual records - * @param opcodes ordered list of opcodes + * + * @param map ordered map of substream records indexed by opcodes + * @param list list of actual records + * @param opcodes ordered list of opcodes */ private void validateRecordOrder(LinkedHashMap map, java.util.List list, Short[] opcodes) { /* debugging: @@ -742,336 +749,339 @@ private void validateRecordOrder(LinkedHashMap map, java.util.List lis for (int zz= 0; zz < list.size(); zz++) { io.starter.toolkit.Logger.log(zz + "-" + list.get(zz)); }*/ - R lastR= map.get(BOF); - short lastOp= BOF; - for (int i= 1; i < opcodes.length; i++) { - short op= opcodes[i]; - R r = map.get(op); - if (r.isPresent) { - // compare ordered list of records (R) to actual order (denoted via recordPos) - if (r.recordPos < lastR.recordPos && r.recordPos>=0) { // Out Of Order (NOTE: CellTable entries will have a record pos = -1) - if (r.altPrecedor!=null) { // record can have more than 1 valid predecessor - // TODO: this is ugly - do a different way ... - for (int zz=0; zz newR.recordPos) { - lastR= newR; - break; - } - } - if (r.recordPos > lastR.recordPos) - continue; - } - - int origRecPos= r.recordPos; + R lastR = map.get(BOF); + short lastOp = BOF; + for (int i = 1; i < opcodes.length; i++) { + short op = opcodes[i]; + R r = map.get(op); + if (r.isPresent) { + // compare ordered list of records (R) to actual order (denoted via recordPos) + if (r.recordPos < lastR.recordPos && r.recordPos >= 0) { // Out Of Order (NOTE: CellTable entries will have a record pos = -1) + if (r.altPrecedor != null) { // record can have more than 1 valid predecessor + // TODO: this is ugly - do a different way ... + for (int zz = 0; zz < r.altPrecedor.length; zz++) { + R newR = map.get(r.altPrecedor[zz]); + if (r.recordPos > newR.recordPos) { + lastR = newR; + break; + } + } + if (r.recordPos > lastR.recordPos) + continue; + } + + int origRecPos = r.recordPos; //io.starter.toolkit.Logger.log("Record out of order: r:" + op + "/" + r.recordPos + " lastR:" + lastOp + "/" + lastR.recordPos); - // find correct insertion point by looking at ordered map - for (int zz= i-1; zz > 0; zz--) { - R prevr = map.get(opcodes[zz]); - if (prevr.isPresent && r.recordPos < prevr.recordPos) { + // find correct insertion point by looking at ordered map + for (int zz = i - 1; zz > 0; zz--) { + R prevr = map.get(opcodes[zz]); + if (prevr.isPresent && r.recordPos < prevr.recordPos) { //io.starter.toolkit.Logger.log("\tInsert at " + prevr.recordPos + " before op= " + opcodes[zz]); - int recsMovedCount= 0; - BiffRec recToMove= (BiffRec)list.get(origRecPos); - do { - list.remove(origRecPos); - list.add(prevr.recordPos /*+ recsMovedCount*/, recToMove); - if (recsMovedCount==0) - r.recordPos= recToMove.getRecordIndex(); + int recsMovedCount = 0; + BiffRec recToMove = (BiffRec) list.get(origRecPos); + do { + list.remove(origRecPos); + list.add(prevr.recordPos /*+ recsMovedCount*/, recToMove); + if (recsMovedCount == 0) + r.recordPos = recToMove.getRecordIndex(); //io.starter.toolkit.Logger.log("\tMoved To " + recToMove.getRecordIndex()); - recsMovedCount++; - recToMove= (BiffRec)list.get(origRecPos); - } while (recToMove.getOpcode()==op); - - // after moved all the records necessary, adjust record positions - for (int jj= 0; jj < opcodes.length; jj++) { - R nextR = map.get(opcodes[jj]); - if (nextR.isPresent && nextR.recordPos >= origRecPos && nextR.recordPos <= r.recordPos && opcodes[jj]!=op) - nextR.recordPos-=recsMovedCount; - } - break; - } - } - } - lastR= r; - lastOp= op; - } - } + recsMovedCount++; + recToMove = (BiffRec) list.get(origRecPos); + } while (recToMove.getOpcode() == op); + + // after moved all the records necessary, adjust record positions + for (int jj = 0; jj < opcodes.length; jj++) { + R nextR = map.get(opcodes[jj]); + if (nextR.isPresent && nextR.recordPos >= origRecPos && nextR.recordPos <= r.recordPos && opcodes[jj] != op) + nextR.recordPos -= recsMovedCount; + } + break; + } + } + } + lastR = r; + lastOp = op; + } + } /*io.starter.toolkit.Logger.log("AFTER order:"); for (int zz= 0; zz < list.size(); zz++) { io.starter.toolkit.Logger.log(zz + "-" + list.get(zz)); }*/ } - + // TODO: handle fonts,formats,xf, style -- what's minimum necessary?? // TODO: handle TabId // TODO: is Index OK? + /** * create missing required records for record-level validation - * @param opcode opcode of missing record - * @param book - * @param bs boundsheet- if null it's a wb level record - * @return new BiffRec + * + * @param opcode opcode of missing record + * @param book + * @param bs boundsheet- if null it's a wb level record + * @return new BiffRec */ private BiffRec createMissingRequiredRecord(short opcode, Book book, Boundsheet bs) { - BiffRec record= XLSRecordFactory.getBiffRecord( opcode ); - if (bs!=null) - record.setSheet(bs); - byte[] data= null; - try { - switch (opcode) { - case INTERFACE_HDR: - data= new byte[]{(byte)0xB0, 0x04 }; //codePage (2 bytes): An unsigned integer that specifies the code page. 1200==Unicode - break; - case (short) 193: // Mms - data= new byte[] {0, 0}; - break; - case (short) 226: // InterfaceEnd - data = new byte[] {}; - break ; - case (short)92: // WriteAccess -- user name - can be all blank - data= new byte[] { - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }; - break; - case (short)66: // CodePage same as interfacehdr 1200==Unicode - data= new byte[]{(byte)0xB0, 0x04 }; - break; - case (short)353: // DSF- truly is required? - data= new byte[] {0, 0}; - break; - case TABID: // - // count how many sheets in wb - int nSheets= 0; - for (int z= book.getStreamer().records.size()-1; z > 0; z--) { - BiffRec b= (BiffRec) book.getStreamer().records.get(z); - if (b.getOpcode()==BOUNDSHEET) { - while (z > 0 && b.getOpcode()==BOUNDSHEET) { - nSheets++; - b= (BiffRec) book.getStreamer().records.get(--z); - } - break; - } - } - data= new byte[] {}; - for (int i= 0; i < nSheets; i++) { - data= ByteTools.append(ByteTools.shortToLEBytes((short) i), data); - } - break; - case WINDOW_PROTECT: - data= new byte[] {0, 0}; - break; - case PROTECT: - data= new byte[] {0, 0}; - break; - case PASSWORD: - data= new byte[] {0, 0}; - break; - case PROT4REV: - data= new byte[] {0, 0}; - break; - case (short)444: // Prot4RevPass - data= new byte[] {0, 0}; - break; - case WINDOW1: // very general window settings - data= new byte[] {(byte)0xE0, 1, 0x69, 0, 0x13, 0x38, 0x1F, 0x1D, 0x38, 0, 0, 0, 0, 0, 1, 0, 0x58, 0x02}; - break; - case BACKUP: - data= new byte[] {0, 0}; - break; - case (short) 141: // HideObj - data= new byte[] {0, 0}; - break; - case DATE1904: - data= new byte[] {0, 0}; - break; - case (short) 14: // CalcPrecision - data= new byte[] {1, 0}; - break; - case (short) 439: // RefreshAll - data= new byte[] {0, 0}; - break; - case BOOKBOOL: - data= new byte[] {0, 0}; - break; - case FONT: // truly required?? - data= new byte[] {(byte) 0xC8, 0, 0, 0, (byte) 0xFF, 0x7F, (byte)0x90, 1, 0, 0, 0, 0, 0, 0, 5, 1, 0x41, 0, 0x72, 0, 0x69, 0, 0x61, 0, 0x6C, 0}; - break; + BiffRec record = XLSRecordFactory.getBiffRecord(opcode); + if (bs != null) + record.setSheet(bs); + byte[] data = null; + try { + switch (opcode) { + case INTERFACE_HDR: + data = new byte[]{(byte) 0xB0, 0x04}; //codePage (2 bytes): An unsigned integer that specifies the code page. 1200==Unicode + break; + case (short) 193: // Mms + data = new byte[]{0, 0}; + break; + case (short) 226: // InterfaceEnd + data = new byte[]{}; + break; + case (short) 92: // WriteAccess -- user name - can be all blank + data = new byte[]{ + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20}; + break; + case (short) 66: // CodePage same as interfacehdr 1200==Unicode + data = new byte[]{(byte) 0xB0, 0x04}; + break; + case (short) 353: // DSF- truly is required? + data = new byte[]{0, 0}; + break; + case TABID: // + // count how many sheets in wb + int nSheets = 0; + for (int z = book.getStreamer().records.size() - 1; z > 0; z--) { + BiffRec b = (BiffRec) book.getStreamer().records.get(z); + if (b.getOpcode() == BOUNDSHEET) { + while (z > 0 && b.getOpcode() == BOUNDSHEET) { + nSheets++; + b = (BiffRec) book.getStreamer().records.get(--z); + } + break; + } + } + data = new byte[]{}; + for (int i = 0; i < nSheets; i++) { + data = ByteTools.append(ByteTools.shortToLEBytes((short) i), data); + } + break; + case WINDOW_PROTECT: + data = new byte[]{0, 0}; + break; + case PROTECT: + data = new byte[]{0, 0}; + break; + case PASSWORD: + data = new byte[]{0, 0}; + break; + case PROT4REV: + data = new byte[]{0, 0}; + break; + case (short) 444: // Prot4RevPass + data = new byte[]{0, 0}; + break; + case WINDOW1: // very general window settings + data = new byte[]{(byte) 0xE0, 1, 0x69, 0, 0x13, 0x38, 0x1F, 0x1D, 0x38, 0, 0, 0, 0, 0, 1, 0, 0x58, 0x02}; + break; + case BACKUP: + data = new byte[]{0, 0}; + break; + case (short) 141: // HideObj + data = new byte[]{0, 0}; + break; + case DATE1904: + data = new byte[]{0, 0}; + break; + case (short) 14: // CalcPrecision + data = new byte[]{1, 0}; + break; + case (short) 439: // RefreshAll + data = new byte[]{0, 0}; + break; + case BOOKBOOL: + data = new byte[]{0, 0}; + break; + case FONT: // truly required?? + data = new byte[]{(byte) 0xC8, 0, 0, 0, (byte) 0xFF, 0x7F, (byte) 0x90, 1, 0, 0, 0, 0, 0, 0, 5, 1, 0x41, 0, 0x72, 0, 0x69, 0, 0x61, 0, 0x6C, 0}; + break; // case FORMAT: // truly required?? // break; - case XF: // truly is required?? - data= new byte[] {0, 0, 0, 0, (byte) 0xF5, (byte) 0xFF, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte)0xC0, 0x20}; - break; + case XF: // truly is required?? + data = new byte[]{0, 0, 0, 0, (byte) 0xF5, (byte) 0xFF, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xC0, 0x20}; + break; // case STYLE: // truly is required? // break; - case COUNTRY: - data= new byte[] {1, 0, 1, 0}; - break; - case SST: - data= new byte[] {0, 0, 0, 0, 0, 0, 0, 0}; - break; - case EXTSST: - data= new byte[] {0, 0}; // should be rebuilt upon output ... - break; - case (short)352: // UsesELFs - data= new byte[] {0, 0}; - break; - case INDEX: - data= new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0x4E, 6, 0, 0}; - break; - case CALCMODE: - data= new byte[] {1, 0}; - break; - case CALCCOUNT: - data= new byte[] {0x64, 0}; - break; - case 15: //CalcRefMode - data= new byte[] {1, 0}; - break; - case 17: // CalcIter - data= new byte[] {0, 0}; - break; - case 16: // CalcDelta An Xnum value that specifies the amount of change in value for a given cell from the previously calculated value for that cell that MUST exist for the iteration to continue. The value MUST be greater than or equal to 0. - data= new byte[] {(byte)0xFC, (byte)0xA9, (byte)0xF1, (byte)0xD2, 0x4D, 0x62, 0x50, 0x3F }; - case 95: // CalcSaveRecalc - data= new byte[] {1, 0}; - break; - case PRINTROWCOL: - data= new byte[] {0, 0}; - break; - case PRINTGRID: - data= new byte[] {0, 0}; - break; - case 130: // GridSet - data= new byte[] {0, 0}; - break; - case GUTS: - data= new byte[] {0, 0, 0, 0, 0, 0, 0, 0}; - break; - case DEFAULTROWHEIGHT: - data= new byte[] {0, 0, (byte) 0xFF, 0}; - break; - case WSBOOL: - data= new byte[] {(byte)0xC1, 4}; - break; - case HEADERREC: - case FOOTERREC: - data = new byte[] {}; - break; - case HCENTER: - data= new byte[] {0, 0}; - break; - case VCENTER: - data= new byte[] {0, 0}; - break; - case SETUP: - data= new byte[] { - 0, 0, (byte) 0xFF, 0, 1, 0, 1, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xE0, 0x3F, 0, 0, 0, 0, 0, 0, (byte) 0xE0, 0x3F, 0, 0 }; - break; - case DEFCOLWIDTH: - data= new byte[] {8, 0}; - break; - case DIMENSIONS: - data= new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - record.setData(data); - if (bs!=null) { // see if rows have already been added - bs.setDimensions((Dimensions)record); - if (bs.getRowMap().size() > 0) { - int z= bs.getRows()[bs.getRowMap().size()-1].getRowNumber(); - ((Dimensions) record).setRowLast(z); - } + case COUNTRY: + data = new byte[]{1, 0, 1, 0}; + break; + case SST: + data = new byte[]{0, 0, 0, 0, 0, 0, 0, 0}; + break; + case EXTSST: + data = new byte[]{0, 0}; // should be rebuilt upon output ... + break; + case (short) 352: // UsesELFs + data = new byte[]{0, 0}; + break; + case INDEX: + data = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0x4E, 6, 0, 0}; + break; + case CALCMODE: + data = new byte[]{1, 0}; + break; + case CALCCOUNT: + data = new byte[]{0x64, 0}; + break; + case 15: //CalcRefMode + data = new byte[]{1, 0}; + break; + case 17: // CalcIter + data = new byte[]{0, 0}; + break; + case 16: // CalcDelta An Xnum value that specifies the amount of change in value for a given cell from the previously calculated value for that cell that MUST exist for the iteration to continue. The value MUST be greater than or equal to 0. + data = new byte[]{(byte) 0xFC, (byte) 0xA9, (byte) 0xF1, (byte) 0xD2, 0x4D, 0x62, 0x50, 0x3F}; + case 95: // CalcSaveRecalc + data = new byte[]{1, 0}; + break; + case PRINTROWCOL: + data = new byte[]{0, 0}; + break; + case PRINTGRID: + data = new byte[]{0, 0}; + break; + case 130: // GridSet + data = new byte[]{0, 0}; + break; + case GUTS: + data = new byte[]{0, 0, 0, 0, 0, 0, 0, 0}; + break; + case DEFAULTROWHEIGHT: + data = new byte[]{0, 0, (byte) 0xFF, 0}; + break; + case WSBOOL: + data = new byte[]{(byte) 0xC1, 4}; + break; + case HEADERREC: + case FOOTERREC: + data = new byte[]{}; + break; + case HCENTER: + data = new byte[]{0, 0}; + break; + case VCENTER: + data = new byte[]{0, 0}; + break; + case SETUP: + data = new byte[]{ + 0, 0, (byte) 0xFF, 0, 1, 0, 1, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xE0, 0x3F, 0, 0, 0, 0, 0, 0, (byte) 0xE0, 0x3F, 0, 0}; + break; + case DEFCOLWIDTH: + data = new byte[]{8, 0}; + break; + case DIMENSIONS: + data = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + record.setData(data); + if (bs != null) { // see if rows have already been added + bs.setDimensions((Dimensions) record); + if (bs.getRowMap().size() > 0) { + int z = bs.getRows()[bs.getRowMap().size() - 1].getRowNumber(); + ((Dimensions) record).setRowLast(z); + } // z= ((Colinfo)bs.getColinfos().get(bs.getColinfos().size())).getColLast(); // ((Dimensions) record).setColLast(z); - } - break; - case WINDOW2: - data= new byte[] {(byte) 0xB6, 6, 0, 0, 0, 0, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - break; - case EOF: - break; -default: - io.starter.toolkit.Logger.log("Must create required rec: " + opcode); - } - - - record.setData(data); - try { - record.init(); - } catch (Exception e) { - // TODO: ExtSst needs sst ... - } - - } catch (Exception e) { - Logger.logErr("Record Validation: Error creating missing record: " + opcode); - } - return record; - + } + break; + case WINDOW2: + data = new byte[]{(byte) 0xB6, 6, 0, 0, 0, 0, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + break; + case EOF: + break; + default: + io.starter.toolkit.Logger.log("Must create required rec: " + opcode); + } + + + record.setData(data); + try { + record.init(); + } catch (Exception e) { + // TODO: ExtSst needs sst ... + } + + } catch (Exception e) { + Logger.logErr("Record Validation: Error creating missing record: " + opcode); + } + return record; + } - + /** * reset Record members in stream so can be used again for record-level validation + * * @param map */ private void reInitSubstream(LinkedHashMap map) { - Iterator ii= map.keySet().iterator(); - while (ii.hasNext()) { - short op= (Short) ii.next(); - R r= map.get(op); - r.isPresent= false; - r.recordPos= -1; - } + Iterator ii = map.keySet().iterator(); + while (ii.hasNext()) { + short op = (Short) ii.next(); + R r = map.get(op); + r.isPresent = false; + r.recordPos = -1; + } } - + /** * mark record present and record pertinent information for record-level validation - */ + */ private void markRecord(LinkedHashMap map, BiffRec rec, short opcode) { - try { - R r = map.get(opcode); - if (!r.isPresent) { // THIS STATEMENT FOR SOME REASON IS VERY VERY VERY TIME CONSUMING -- see testPerformance3 - r.isPresent = true; - r.recordPos = rec.getRecordIndex(); - } - } catch (NullPointerException ne) { + try { + R r = map.get(opcode); + if (!r.isPresent) { // THIS STATEMENT FOR SOME REASON IS VERY VERY VERY TIME CONSUMING -- see testPerformance3 + r.isPresent = true; + r.recordPos = rec.getRecordIndex(); + } + } catch (NullPointerException ne) { /* if (opcode != CONTINUE && opcode!=DBCELL && opcode < 4000 /* chart records * / && !rec.isValueForCell()) // ignore CELLTABLE records // io.starter.toolkit.Logger.log("COULDN'T FIND Opcode: " + opcode);*/ - - } + + } } - /** debug utility */ - private void displayRecsInStream(LinkedHashMap map) { - Iterator ii= map.keySet().iterator(); - io.starter.toolkit.Logger.log("Present Records"); - while (ii.hasNext()) { - short op= ii.next(); - R r= map.get(op); - if (r.isPresent) { - io.starter.toolkit.Logger.log(op + " at " + r.recordPos); - } - } - } + /** + * debug utility + */ + private void displayRecsInStream(LinkedHashMap map) { + Iterator ii = map.keySet().iterator(); + io.starter.toolkit.Logger.log("Present Records"); + while (ii.hasNext()) { + short op = ii.next(); + R r = map.get(op); + if (r.isPresent) { + io.starter.toolkit.Logger.log(op + " at " + r.recordPos); + } + } + } } /** * represents pertinent info for a BiffRec in an easy-to-access class - * - * */ class R { public boolean isRequired, isPresent; public int recordPos = -1; - public short[] altPrecedor= null; + public short[] altPrecedor = null; public R(boolean req) { - isRequired = req; + isRequired = req; } } @@ -1110,49 +1120,49 @@ public R(boolean req) { /* * record order issues: 659= Style 352= UsesELFs 140= Country 146= Palette - * + * * 2173= XfExt 2189= DXF 2190= TableStyles 2211= ForceFullCalculation - * - * + * + * * testValidationHandle.testEquilarFile workingdir + * "equilar/proxycomp3_formatted_new.xls" Record out of order: r:659/682 * lastR:2189/903 Record out of order: r:352/732 lastR:2190/956 Record out of * order: r:140/753 lastR:2211/958 - * + * * TestScenarios.borderbrokenxx, formularesultxx, numericerrorxx * TestFormulaParse.testRajeshOOM: workingdir + "OOXML/proxycomp3_cap_new.xls" * Record out of order: r:659/1304 lastR:2189/1557 Record out of order: * r:146/1375 lastR:2190/1610 Record out of order: r:140/1403 lastR:2211/1612 - * + * * TestAutoFilter.XXX: workingdir + "testAutoFilter.xls" Record out of order: * r:659/128 lastR:2173/204 Record out of order: r:352/175 lastR:2190/292 Record * out of order: r:140/178 lastR:2211/294 - * + * * also see TestAddAF w.r.t. insertion position -- supbook is wrong? ... - * + * * TestMemoryUsage.testMemUsageWithManyReferences: workingdir + * "equilar/proxycomp3_pmp_Model_T_new.xls" Record out of order: r:659/1202 * lastR:2189/1448 Record out of order: r:146/1273 lastR:2190/1501 Record out of * order: r:140/1297 lastR:2211/1503 Record out of order: r:89/1300 * lastR:35/1308 - * + * * TestInsertRows.testInsertRowsWithFormulas.this.workingsheetsterdir + * "aviasphere/AirframeMaster_F20Import.xls" Record out of order: r:659/274 * lastR:2173/352 Record out of order: r:352/323 lastR:2190/537 Record out of * order: r:140/330 lastR:2211/539 - * + * * TestXMLSS.testClasicXMLReadWriteError: workingdir + "xmlsavecorruption.xls" * Record out of order: r:659/144 lastR:2173/208 Record out of order: r:352/194 * lastR:2190/364 Record out of order: r:140/196 lastR:2211/366 - * + * * TestRowCols.testRowInsertionFormatLoss.workingdir + "claritas/input/400.xls" * Record out of order: r:659/230 lastR:2189/426 Record out of order: r:352/280 * lastR:2190/481 Record out of order: r:140/288 lastR:2211/483 - * + * * TEstRowCols.testRowFormats:testRowFormats.xls Record out of order: r:659/278 * lastR:2173/356 Record out of order: r:352/327 lastR:2190/541 Record out of * order: r:140/334 lastR:2211/543 - * + * * TestRowCols.testAutoAjdustRowHeight.C:\eclipse\workspace\Testfiles\OpenXLS\input * \equilar/tcr_formatted_2003.xls Record out of order: r:659/709 lastR:2189/973 * Record out of order: r:352/774 lastR:2190/1038 Record out of order: r:140/800 diff --git a/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java b/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java index ee31647..5f93302 100644 --- a/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java +++ b/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,35 +26,35 @@ /** * No WorkSheet Found. - * + * * @see Cell * @see WorkBook */ public final class WorkSheetNotFoundException extends java.lang.Exception { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1722195057857012811L; - String message = ""; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1722195057857012811L; + String message = ""; - public WorkSheetNotFoundException(String n) { - super(); - message = n; - } + public WorkSheetNotFoundException(String n) { + super(); + message = n; + } - @Override - public String getMessage() { - // This method is derived from class java.lang.Throwable - // to do: code goes here - return this.toString(); - } + @Override + public String getMessage() { + // This method is derived from class java.lang.Throwable + // to do: code goes here + return this.toString(); + } - @Override - public String toString() { - // This method is derived from class java.lang.Throwable - // to do: code goes here - return message; - } + @Override + public String toString() { + // This method is derived from class java.lang.Throwable + // to do: code goes here + return message; + } } diff --git a/src/main/java/io/starter/formats/XLS/Writeaccess.java b/src/main/java/io/starter/formats/XLS/Writeaccess.java index 9ac4f56..26826df 100644 --- a/src/main/java/io/starter/formats/XLS/Writeaccess.java +++ b/src/main/java/io/starter/formats/XLS/Writeaccess.java @@ -2,71 +2,74 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS; -import java.io.UnsupportedEncodingException; import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; + -/** WRITEACCESS 0x5C: Contains name of Excel installed user.
                -

                -    offset  name        size    contents
                -    ---    
                -    4       stName      112     User Name as unformatted Unicodestring
                -   
                -    

                - +/** + * WRITEACCESS 0x5C: Contains name of Excel installed user.
                + *

                + * offset  name        size    contents
                + * ---
                + * 4       stName      112     User Name as unformatted Unicodestring
                + *
                + * 

                + * * @see WorkBook */ -public class Writeaccess extends io.starter.formats.XLS.XLSRecord -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8868603864018600260L; - private Unicodestring strname; - - /** set the Writeaccess username - - */ - public void setName(String str){ - try{ +public class Writeaccess extends io.starter.formats.XLS.XLSRecord { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8868603864018600260L; + private Unicodestring strname; + + /** + * set the Writeaccess username + */ + public void setName(String str) { + try { byte[] nameb = str.getBytes(DEFAULTENCODING); byte[] newb = new byte[112]; int diff = 112 - nameb.length; - if(diff < 0)System.arraycopy(nameb, 0, newb, 0, 112); + if (diff < 0) System.arraycopy(nameb, 0, newb, 0, 112); else System.arraycopy(nameb, 0, newb, 0, nameb.length); - strname.init(newb,false); + strname.init(newb, false); this.setData(newb); - }catch(UnsupportedEncodingException e){Logger.logInfo("setting name in Writeaccess record failed: " + e);} + } catch (UnsupportedEncodingException e) { + Logger.logInfo("setting name in Writeaccess record failed: " + e); + } } - - public String getName(){ - return strname.toString(); + + public String getName() { + return strname.toString(); } - - public void init(){ + + public void init() { super.init(); strname = new Unicodestring(); - strname.init(getBytes(),false); + strname.init(getBytes(), false); } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/WsBool.java b/src/main/java/io/starter/formats/XLS/WsBool.java index df83a0a..cf6e98b 100644 --- a/src/main/java/io/starter/formats/XLS/WsBool.java +++ b/src/main/java/io/starter/formats/XLS/WsBool.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,51 +22,54 @@ */ package io.starter.formats.XLS; -/** Record containing miscellaneous sheet-level boolean values. - * - A - fShowAutoBreaks (1 bit): A bit that specifies whether page breaks (2) inserted automatically are visible on the sheet. - B - reserved1 (3 bits): MUST be zero, and MUST be ignored. - C - fDialog (1 bit): A bit that specifies whether the sheet is a dialog sheet. - D - fApplyStyles (1 bit): A bit that specifies whether to apply styles in an outline when an outline is applied. - E - fRowSumsBelow (1 bit): A bit that specifies whether summary rows appear below an outline's detail rows. - F - fColSumsRight (1 bit): A bit that specifies whether summary columns appear to the right or left of an outline's detail columns. Valid values are specified in the following table: - Value Meaning - 0 The summary columns appear to the right, if the sheet is displayed left-to-right, or appear to the left, if the sheet is displayed right-to-left. - 1 The summary columns appear to the left, if the sheet is displayed left-to-right, or appear to the right, if the sheet is displayed right-to-left. - G - fFitToPage (1 bit): A bit that specifies whether to fit the printable contents to a single page when printing this sheet. - H - reserved2 (1 bit): MUST be zero, and MUST be ignored. - I - unused (2 bits): Undefined and MUST be ignored. - J - fSyncHoriz (1 bit): A bit that specifies whether horizontal scrolling is synchronized across multiple windows displaying this sheet. - K - fSyncVert (1 bit): A bit that specifies whether vertical scrolling is synchronized across multiple windows displaying this sheet. - L - fAltExprEval (1 bit): A bit that specifies whether the sheet uses transition formula evaluation. - M - fAltFormulaEntry (1 bit): A bit that specifies whether the sheet uses transition formula entry. - - * +/** + * Record containing miscellaneous sheet-level boolean values. + *

                + * A - fShowAutoBreaks (1 bit): A bit that specifies whether page breaks (2) inserted automatically are visible on the sheet. + * B - reserved1 (3 bits): MUST be zero, and MUST be ignored. + * C - fDialog (1 bit): A bit that specifies whether the sheet is a dialog sheet. + * D - fApplyStyles (1 bit): A bit that specifies whether to apply styles in an outline when an outline is applied. + * E - fRowSumsBelow (1 bit): A bit that specifies whether summary rows appear below an outline's detail rows. + * F - fColSumsRight (1 bit): A bit that specifies whether summary columns appear to the right or left of an outline's detail columns. Valid values are specified in the following table: + * Value Meaning + * 0 The summary columns appear to the right, if the sheet is displayed left-to-right, or appear to the left, if the sheet is displayed right-to-left. + * 1 The summary columns appear to the left, if the sheet is displayed left-to-right, or appear to the right, if the sheet is displayed right-to-left. + * G - fFitToPage (1 bit): A bit that specifies whether to fit the printable contents to a single page when printing this sheet. + * H - reserved2 (1 bit): MUST be zero, and MUST be ignored. + * I - unused (2 bits): Undefined and MUST be ignored. + * J - fSyncHoriz (1 bit): A bit that specifies whether horizontal scrolling is synchronized across multiple windows displaying this sheet. + * K - fSyncVert (1 bit): A bit that specifies whether vertical scrolling is synchronized across multiple windows displaying this sheet. + * L - fAltExprEval (1 bit): A bit that specifies whether the sheet uses transition formula evaluation. + * M - fAltFormulaEntry (1 bit): A bit that specifies whether the sheet uses transition formula entry. */ public final class WsBool -extends XLSRecord { + extends XLSRecord { private static final long serialVersionUID = 2794181135988750779L; - public void init() { - super.init(); - // Make sure the data array is read in - getData(); - - } - - public void setSheet (Sheet sheet ) { - super.setSheet( sheet ); - ((Boundsheet)sheet).addPrintRec( this ); - } - - /** Gets whether the sheet will be printed fit to some number of pages. */ - public boolean isFitToPage() { - return (data[1] & 0x01) == 0x01; - } - - /** Sets whether the sheet will be printed fit to some number of pages. */ - public void setFitToPage (boolean value) { - if (value) data[1] |= 0x01; - else data[1] &= ~0x01; - } + public void init() { + super.init(); + // Make sure the data array is read in + getData(); + + } + + public void setSheet(Sheet sheet) { + super.setSheet(sheet); + ((Boundsheet) sheet).addPrintRec(this); + } + + /** + * Gets whether the sheet will be printed fit to some number of pages. + */ + public boolean isFitToPage() { + return (data[1] & 0x01) == 0x01; + } + + /** + * Sets whether the sheet will be printed fit to some number of pages. + */ + public void setFitToPage(boolean value) { + if (value) data[1] |= 0x01; + else data[1] &= ~0x01; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSCellRecord.java b/src/main/java/io/starter/formats/XLS/XLSCellRecord.java index c5a9ad9..dec37a2 100644 --- a/src/main/java/io/starter/formats/XLS/XLSCellRecord.java +++ b/src/main/java/io/starter/formats/XLS/XLSCellRecord.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,23 +22,24 @@ */ package io.starter.formats.XLS; -/** A BIFF8 record that contains the value of a single cell. +/** + * A BIFF8 record that contains the value of a single cell. * For the moment this contains very little. Eventually most of the * cell-specific methods from {@link XLSRecord} should be moved here. */ public abstract class XLSCellRecord -extends XLSRecord implements CellRec { - private static final long serialVersionUID = 7387720078386279196L; + extends XLSRecord implements CellRec { + private static final long serialVersionUID = 7387720078386279196L; - public int getColFirst() { - return this.getColNumber(); - } - - public int getColLast() { - return this.getColNumber(); - } - - public boolean isSingleCol(){ - return (this.getColFirst()==this.getColLast()); - } + public int getColFirst() { + return this.getColNumber(); + } + + public int getColLast() { + return this.getColNumber(); + } + + public boolean isSingleCol() { + return (this.getColFirst() == this.getColLast()); + } } diff --git a/src/main/java/io/starter/formats/XLS/XLSConstants.java b/src/main/java/io/starter/formats/XLS/XLSConstants.java index f498dcc..eeb342e 100644 --- a/src/main/java/io/starter/formats/XLS/XLSConstants.java +++ b/src/main/java/io/starter/formats/XLS/XLSConstants.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,303 +23,299 @@ package io.starter.formats.XLS; /** - * - - - + * */ -public interface XLSConstants -{ +public interface XLSConstants { // Stream types - static final short WK_GLOBALS = 0x5; - static final short VB_MODULE = 0x6; - static final short WK_WORKSHEET = 0x10; - static final short WK_CHART = 0x20; - static final short WK_MACROSHEET = 0x40; - static final short WK_FILE = 0x100; - + short WK_GLOBALS = 0x5; + short VB_MODULE = 0x6; + short WK_WORKSHEET = 0x10; + short WK_CHART = 0x20; + short WK_MACROSHEET = 0x40; + short WK_FILE = 0x100; + // Cell types - public static final int - TYPE_BLANK = -1, - TYPE_STRING = 0, - TYPE_FP = 1, - TYPE_INT = 2, - TYPE_FORMULA = 3, - TYPE_BOOLEAN = 4, - TYPE_DOUBLE = 5; - - // Book Options and constants - + int + TYPE_BLANK = -1, + TYPE_STRING = 0, + TYPE_FP = 1, + TYPE_INT = 2, + TYPE_FORMULA = 3, + TYPE_BOOLEAN = 4, + TYPE_DOUBLE = 5; + + // Book Options and constants + // CalculationOptions - public static int CALCULATE_ALWAYS = 0; - public static int CALCULATE_EXPLICIT = 1; - public static int CALCULATE_AUTO = 2; // replacement for calc always - public static String CALC_MODE_PROP = "io.starter.OpenXLS.calcmode"; - public static String REFTRACK_PROP = "io.starter.OpenXLS.trackreferences"; - public static String USETEMPFILE_PROP = "io.starter.formats.LEO.usetempfile"; - public static String VALIDATEWORKBOOK = "io.starter.formats.LEO.validateworkbook"; - // Debug options - public static int DEBUG_LOW = 50; - public static int DEBUG_MEDIUM = 60; - public static int DEBUG_HIGH = 100; - - // String table handling - public static int STRING_ENCODING_AUTO = 0; - public static int STRING_ENCODING_UNICODE = 1; - public static int STRING_ENCODING_COMPRESSED = 2; - public static int ALLOWDUPES = 0; - public static int SHAREDUPES = 1; - public static String DEFAULTENCODING = "ISO-8859-1"; // "UTF-8";"UTF-8"; - public static String UNICODEENCODING = "UTF-16LE"; // "UnicodeLittleUnmarked"; - + int CALCULATE_ALWAYS = 0; + int CALCULATE_EXPLICIT = 1; + int CALCULATE_AUTO = 2; // replacement for calc always + String CALC_MODE_PROP = "io.starter.OpenXLS.calcmode"; + String REFTRACK_PROP = "io.starter.OpenXLS.trackreferences"; + String USETEMPFILE_PROP = "io.starter.formats.LEO.usetempfile"; + String VALIDATEWORKBOOK = "io.starter.formats.LEO.validateworkbook"; + // Debug options + int DEBUG_LOW = 50; + int DEBUG_MEDIUM = 60; + int DEBUG_HIGH = 100; + + // String table handling + int STRING_ENCODING_AUTO = 0; + int STRING_ENCODING_UNICODE = 1; + int STRING_ENCODING_COMPRESSED = 2; + int ALLOWDUPES = 0; + int SHAREDUPES = 1; + String DEFAULTENCODING = "ISO-8859-1"; // "UTF-8";"UTF-8"; + String UNICODEENCODING = "UTF-16LE"; // "UnicodeLittleUnmarked"; + // XLSRecord Opcodes - static final short EXCEL2K = 0x1C0; - static final short GARBAGE = 0xFFFFFFFE; - static final short TXO = 0x1B6; - static final short MSODRAWINGGROUP = 0xEB; - static final short MSODRAWING = 0xEC; - static final short MSODRAWINGSELECTION = 0xED; - static final short PHONETIC = 0xEF; - static final short CONTINUE = 0x3C; - static final short COLINFO = 0x7D; - static final short SST = 0xFC; - static final short DSF = 0x161; - static final short EXTSST = 0xFF; - static final short ENDEXTSST = 0xFE; - static final short BOF = 0x809; - static final short FILEPASS = 0x2F; - static final short INDEX = 0x20B; - static final short DBCELL = 0xD7; - static final short BOUNDSHEET = 0x85; - static final short COUNTRY = 0x8C; // record just after bound sheet - static final short BOOKBOOL = 0xDA; - static final short CALCCOUNT = 0x0C; - static final short CALCMODE = 0x0D; - static final short PRECISION = 0x0E; - static final short REFMODE = 0x0F; - static final short DELTA = 0x10; - static final short ITERATION = 0x11; - static final short DATE1904 = 0x22; - static final short BACKUP = 0x40; - static final short PRINT_ROW_HEADERS = 0x2A; - static final short PRINT_GRIDLINES = 0x2B; - static final short HORIZONTAL_PAGE_BREAKS = 0x1B; - static final short HLINK = 0x1B8; - static final short VERTICAL_PAGE_BREAKS = 0x1A; - static final short DEFAULTROWHEIGHT = 0x225; - static final short FONT = 0x31; - static final short HEADERREC = 0x14; - static final short FOOTERREC = 0x15; - static final short LEFT_MARGIN = 0x26; - static final short RIGHT_MARGIN = 0x27; - static final short TOP_MARGIN = 0x28; - static final short BOTTOM_MARGIN = 0x29; - static final short DCON = 0x50; - static final short DEFCOLWIDTH = 0x55; - static final short EXTERNCOUNT = 0x16; - static final short EXTERNSHEET = 0x17; - static final short EXTERNNAME = 0x23; - static final short FORMAT = 0x41E; - static final short XF = 0xE0; - static final short NAME = 0x18; - static final short DIMENSIONS = 0x200; - static final short FILE_LOCK = 0x195; - static final short RRD_INFO = 0x196; - static final short RRD_HEAD = 0x138; - static final short EOF = 0x0A; - static final short BLANK = 0x201; - static final short MERGEDCELLS = 0xE5; - static final short MULBLANK = 0xBE; - static final short MULRK = 0xBD; - static final short NOTE = 0x1C; - static final short NUMBER = 0x203; - static final short LABEL = 0x204; - static final short LABELSST = 0xFD; - static final short BOOLERR = 0x205; - static final short FORMULA = 0x06; // 0x406; - static final short ARRAY = 0x221;//0x21; // - static final short SELECTION = 0x1D; - static final short STYLE = 0x293; - static final short ROW = 0x208; - static final short RK = 0x27E; // this is wrong according to the documentation (0x27) ... -jm - static final short RSTRING = 0xD6; - static final short SHRFMLA = 0x4BC; // according to docs this is 0xBC - static final short STRINGREC = 0x207; - static final short TABLE = 0x236; - static final short PANE = 0x41; - static final short PASSWORD = 0x13; - static final short INTERFACE_HDR = 0xE1; - static final short USR_EXCL = 0x194; - static final short PALETTE = 0x92; - static final short PROTECT = 0x12; - static final short OBJPROTECT = 0x63; - static final short SCENPROTECT = 0xDD; - static final short FEATHEADR = 0x867; // extra protection settings + smarttag settings - static final short SCL = 0xA0; // zoom - static final short SHEETPROTECTION = 0x867; // - static final short SHEETLAYOUT = 0x862; - static final short RANGEPROTECTION = 0x868; - static final short PROT4REV = 0x1AF; - static final short WINDOW_PROTECT = 0x19; - static final short WINDOW1 = 0x3D; - static final short WINDOW2 = 0x23E; - static final short PLV = 0x88B; - static final short RTENTEXU = 0x1B; - static final short DV = 0x1BE; - static final short DVAL = 0x1B2; - static final short RTMERGECELLS = 0xE5; - static final short SUPBOOK = 0x1AE; - static final short USERSVIEWBEGIN = 0x1AA; - static final short USERSVIEWEND = 0x1AB; - static final short USERBVIEW = 0x1A9; - static final short PLS = 0x4D; - static final short WSBOOL = 0x81; - static final short OBJ = 0x5D; - static final short OBPROJ = 0xD3; - static final short XLS_MAX_COLS = 0x100; - static final short TABID = 0x13d; - static final short GUTS = 0x80; - static final short CODENAME = 0x1BA; - static final short XCT = 0x59; // 20080122 KSC: - static final short CRN = 0x5A; // "" + short EXCEL2K = 0x1C0; + short GARBAGE = 0xFFFFFFFE; + short TXO = 0x1B6; + short MSODRAWINGGROUP = 0xEB; + short MSODRAWING = 0xEC; + short MSODRAWINGSELECTION = 0xED; + short PHONETIC = 0xEF; + short CONTINUE = 0x3C; + short COLINFO = 0x7D; + short SST = 0xFC; + short DSF = 0x161; + short EXTSST = 0xFF; + short ENDEXTSST = 0xFE; + short BOF = 0x809; + short FILEPASS = 0x2F; + short INDEX = 0x20B; + short DBCELL = 0xD7; + short BOUNDSHEET = 0x85; + short COUNTRY = 0x8C; // record just after bound sheet + short BOOKBOOL = 0xDA; + short CALCCOUNT = 0x0C; + short CALCMODE = 0x0D; + short PRECISION = 0x0E; + short REFMODE = 0x0F; + short DELTA = 0x10; + short ITERATION = 0x11; + short DATE1904 = 0x22; + short BACKUP = 0x40; + short PRINT_ROW_HEADERS = 0x2A; + short PRINT_GRIDLINES = 0x2B; + short HORIZONTAL_PAGE_BREAKS = 0x1B; + short HLINK = 0x1B8; + short VERTICAL_PAGE_BREAKS = 0x1A; + short DEFAULTROWHEIGHT = 0x225; + short FONT = 0x31; + short HEADERREC = 0x14; + short FOOTERREC = 0x15; + short LEFT_MARGIN = 0x26; + short RIGHT_MARGIN = 0x27; + short TOP_MARGIN = 0x28; + short BOTTOM_MARGIN = 0x29; + short DCON = 0x50; + short DEFCOLWIDTH = 0x55; + short EXTERNCOUNT = 0x16; + short EXTERNSHEET = 0x17; + short EXTERNNAME = 0x23; + short FORMAT = 0x41E; + short XF = 0xE0; + short NAME = 0x18; + short DIMENSIONS = 0x200; + short FILE_LOCK = 0x195; + short RRD_INFO = 0x196; + short RRD_HEAD = 0x138; + short EOF = 0x0A; + short BLANK = 0x201; + short MERGEDCELLS = 0xE5; + short MULBLANK = 0xBE; + short MULRK = 0xBD; + short NOTE = 0x1C; + short NUMBER = 0x203; + short LABEL = 0x204; + short LABELSST = 0xFD; + short BOOLERR = 0x205; + short FORMULA = 0x06; // 0x406; + short ARRAY = 0x221;//0x21; // + short SELECTION = 0x1D; + short STYLE = 0x293; + short ROW = 0x208; + short RK = 0x27E; // this is wrong according to the documentation (0x27) ... -jm + short RSTRING = 0xD6; + short SHRFMLA = 0x4BC; // according to docs this is 0xBC + short STRINGREC = 0x207; + short TABLE = 0x236; + short PANE = 0x41; + short PASSWORD = 0x13; + short INTERFACE_HDR = 0xE1; + short USR_EXCL = 0x194; + short PALETTE = 0x92; + short PROTECT = 0x12; + short OBJPROTECT = 0x63; + short SCENPROTECT = 0xDD; + short FEATHEADR = 0x867; // extra protection settings + smarttag settings + short SCL = 0xA0; // zoom + short SHEETPROTECTION = 0x867; // + short SHEETLAYOUT = 0x862; + short RANGEPROTECTION = 0x868; + short PROT4REV = 0x1AF; + short WINDOW_PROTECT = 0x19; + short WINDOW1 = 0x3D; + short WINDOW2 = 0x23E; + short PLV = 0x88B; + short RTENTEXU = 0x1B; + short DV = 0x1BE; + short DVAL = 0x1B2; + short RTMERGECELLS = 0xE5; + short SUPBOOK = 0x1AE; + short USERSVIEWBEGIN = 0x1AA; + short USERSVIEWEND = 0x1AB; + short USERBVIEW = 0x1A9; + short PLS = 0x4D; + short WSBOOL = 0x81; + short OBJ = 0x5D; + short OBPROJ = 0xD3; + short XLS_MAX_COLS = 0x100; + short TABID = 0x13d; + short GUTS = 0x80; + short CODENAME = 0x1BA; + short XCT = 0x59; // 20080122 KSC: + short CRN = 0x5A; // "" + + // Pivot Table records + short SXVIEW = 0xB0; + short TABLESTYLES = 0x88E; + short SXSTREAMID = 0xD5; + short SXVS = 0xE3; + short SXADDL = 0x864; + short SXVDEX = 0x100; + short SXPI = 0xB6; + short SXDI = 0xC5; + short SXDB = 0xC6; + short SXFDB = 0xC7; + short SXEX = 0xF1; + short QSISXTAG = 0x802; + short SXVIEWEX9 = 0x810; + short DCONREF = 0x51; + short DCONNAME = 0x52; + short DCONBIN = 0x1B5; + short SXFORMAT = 0xFB; + short SXLI = 0xB5; + short SXVI = 0xB2; + short SXVD = 0xB1; + short SXIVD = 0xB4; + short SXDBEX = 0x122; + short SXFDBTYPE = 0x1BB; + short SXDBB = 0xC8; + short SXNUM = 0xC9; + short SXBOOL = 0xCA; + short SXSTRING = 0xCD; + + // Printing records + short SETUP = 0xA1; + short HCENTER = 0x83; + short VCENTER = 0x84; + short LEFTMARGIN = 0x26; + short RIGHTMARGIN = 0x27; + short TOPMARGIN = 0x28; + short BOTTOMMARGIN = 0x29; + short PRINTGRID = 0x2B; + short PRINTROWCOL = 0x2A; + + // Conditional Formatting + short CF = 0x1B1; + short CONDFMT = 0x1B0; + // 2007 Conditional Formatting + short CF12 = 0x87A; + short CONDFMT12 = 0x879; + + // AutoFilter + short AUTOFILTER = 0x9E; + + // Chart items + short UNITS = 0x1001; + short CHART = 0x1002; + short SERIES = 0x1003; + short DATAFORMAT = 0x1006; + short LINEFORMAT = 0x1007; + short MARKERFORMAT = 0x1009; + short AREAFORMAT = 0x100A; + short PIEFORMAT = 0x100B; + short ATTACHEDLABEL = 0x100C; + short SERIESTEXT = 0x100D; + short CHARTFORMAT = 0x1014; + short LEGEND = 0x1015; + short SERIESLIST = 0x1016; + short BAR = 0x1017; + short LINE = 0x1018; + short PIE = 0x1019; + short AREA = 0x101A; + short SCATTER = 0x101B; + short CHARTLINE = 0x101C; + short AXIS = 0x101D; + short TICK = 0x101E; + short VALUERANGE = 0x101F; + short CATSERRANGE = 0x1020; + short AXISLINEFORMAT = 0x1021; + short CHARTFORMATLINK = 0x1022; + short DEFAULTTEXT = 0x1024; + short TEXTDISP = 0x1025; + short FONTX = 0x1026; + short OBJECTLINK = 0x1027; + short FRAME = 0x1032; + short BEGIN = 0x1033; + short END = 0x1034; + short PLOTAREA = 0x1035; + short THREED = 0x103A; + short PICF = 0x103C; + short DROPBAR = 0x103D; + short RADAR = 0x103E; + short SURFACE = 0x103F; + short RADARAREA = 0x1040; + short AXISPARENT = 0x1041; + short LEGENDXN = 0x1043; + short SHTPROPS = 0x1044; + short SERTOCRT = 0x1045; + short AXESUSED = 0x1046; + short SBASEREF = 0x1048; + short SERPARENT = 0x104A; + short SERAUXTREND = 0x104B; + short IFMT = 0x104E; + short POS = 0x104F; + short ALRUNS = 0x1450; + short AI = 0x1051; + short SERAUXERRBAR = 0x105B; + short SERFMT = 0x105D; + short CHART3DBARSHAPE = 0x105F; + short FBI = 0x1460; + short BOPPOP = 0x1061; + short AXCENT = 0x1062; + short DAT = 0x1063; + short PLOTGROWTH = 0x1064; + short SIIINDEX = 0x1065; + short GELFRAME = 0x1066; + short BOPPOPCUSTOM = 0x1067; + //20080703 KSC: Excel 9 Chart Records + short CRTLAYOUT12 = 0x89D; + short CRTLAYOUT12A = 0x08A7; + short CHARTFRTINFO = 0x0850; + short FRTWRAPPER = 0x0851; + short STARTBLOCK = 0x0852; + short ENDBLOCK = 0x0853; + short STARTOBJECT = 0x0854; + short ENDOBJECT = 0x0855; + short CATLAB = 0x0856; + short YMULT = 0x0857; + short SXVIEWLINK = 0x0858; + short PIVOTCHARTBITS = 0x0859; + short FRTFONTLIST = 0x085A; + short PIVOTCHARTLINK = 0x0861; + short DATALABEXT = 0x086A; + short DATALABEXTCONTENTS = 0x086B; + short FONTBASIS = 0x1060; - // Pivot Table records - static final short SXVIEW = 0xB0; - static final short TABLESTYLES = 0x88E; - static final short SXSTREAMID = 0xD5; - static final short SXVS = 0xE3; - static final short SXADDL = 0x864; - static final short SXVDEX = 0x100; - static final short SXPI = 0xB6; - static final short SXDI = 0xC5; - static final short SXDB = 0xC6; - static final short SXFDB = 0xC7; - static final short SXEX = 0xF1; - static final short QSISXTAG = 0x802; - static final short SXVIEWEX9 = 0x810; - static final short DCONREF = 0x51; - static final short DCONNAME = 0x52; - static final short DCONBIN = 0x1B5; - static final short SXFORMAT = 0xFB; - static final short SXLI = 0xB5; - static final short SXVI = 0xB2; - static final short SXVD = 0xB1; - static final short SXIVD = 0xB4; - static final short SXDBEX = 0x122; - static final short SXFDBTYPE = 0x1BB; - static final short SXDBB = 0xC8; - static final short SXNUM = 0xC9; - static final short SXBOOL = 0xCA; - static final short SXSTRING = 0xCD; - - // Printing records - static final short SETUP = 0xA1; - static final short HCENTER = 0x83; - static final short VCENTER = 0x84; - static final short LEFTMARGIN = 0x26; - static final short RIGHTMARGIN = 0x27; - static final short TOPMARGIN = 0x28; - static final short BOTTOMMARGIN = 0x29; - static final short PRINTGRID = 0x2B; - static final short PRINTROWCOL = 0x2A; - - // Conditional Formatting - static final short CF = 0x1B1; - static final short CONDFMT = 0x1B0; - // 2007 Conditional Formatting - static final short CF12 = 0x87A; - static final short CONDFMT12 = 0x879; - - // AutoFilter - static final short AUTOFILTER = 0x9E; - - // Chart items - static final short UNITS = 0x1001; - static final short CHART = 0x1002; - static final short SERIES = 0x1003; - static final short DATAFORMAT = 0x1006; - static final short LINEFORMAT = 0x1007; - static final short MARKERFORMAT = 0x1009; - static final short AREAFORMAT = 0x100A; - static final short PIEFORMAT = 0x100B; - static final short ATTACHEDLABEL = 0x100C; - static final short SERIESTEXT = 0x100D; - static final short CHARTFORMAT = 0x1014; - static final short LEGEND = 0x1015; - static final short SERIESLIST = 0x1016; - static final short BAR = 0x1017; - static final short LINE = 0x1018; - static final short PIE = 0x1019; - static final short AREA = 0x101A; - static final short SCATTER = 0x101B; - static final short CHARTLINE = 0x101C; - static final short AXIS = 0x101D; - static final short TICK = 0x101E; - static final short VALUERANGE = 0x101F; - static final short CATSERRANGE = 0x1020; - static final short AXISLINEFORMAT = 0x1021; - static final short CHARTFORMATLINK = 0x1022; - static final short DEFAULTTEXT = 0x1024; - static final short TEXTDISP = 0x1025; - static final short FONTX = 0x1026; - static final short OBJECTLINK = 0x1027; - static final short FRAME = 0x1032; - static final short BEGIN = 0x1033; - static final short END = 0x1034; - static final short PLOTAREA = 0x1035; - static final short THREED = 0x103A; - static final short PICF = 0x103C; - static final short DROPBAR = 0x103D; - static final short RADAR = 0x103E; - static final short SURFACE = 0x103F; - static final short RADARAREA = 0x1040; - static final short AXISPARENT = 0x1041; - static final short LEGENDXN = 0x1043; - static final short SHTPROPS = 0x1044; - static final short SERTOCRT = 0x1045; - static final short AXESUSED = 0x1046; - static final short SBASEREF = 0x1048; - static final short SERPARENT = 0x104A; - static final short SERAUXTREND = 0x104B; - static final short IFMT = 0x104E; - static final short POS = 0x104F; - static final short ALRUNS = 0x1450; - static final short AI = 0x1051; - static final short SERAUXERRBAR = 0x105B; - static final short SERFMT = 0x105D; - static final short CHART3DBARSHAPE = 0x105F; - static final short FBI = 0x1460; - static final short BOPPOP = 0x1061; - static final short AXCENT = 0x1062; - static final short DAT = 0x1063; - static final short PLOTGROWTH = 0x1064; - static final short SIIINDEX = 0x1065; - static final short GELFRAME = 0x1066; - static final short BOPPOPCUSTOM = 0x1067; - //20080703 KSC: Excel 9 Chart Records - static final short CRTLAYOUT12 = 0x89D; - static final short CRTLAYOUT12A = 0x08A7; - static final short CHARTFRTINFO = 0x0850; - static final short FRTWRAPPER = 0x0851; - static final short STARTBLOCK = 0x0852; - static final short ENDBLOCK = 0x0853; - static final short STARTOBJECT = 0x0854; - static final short ENDOBJECT = 0x0855; - static final short CATLAB = 0x0856; - static final short YMULT = 0x0857; - static final short SXVIEWLINK = 0x0858; - static final short PIVOTCHARTBITS = 0x0859; - static final short FRTFONTLIST = 0x085A; - static final short PIVOTCHARTLINK = 0x0861; - static final short DATALABEXT = 0x086A; - static final short DATALABEXTCONTENTS = 0x086B; - static final short FONTBASIS = 0x1060; - // max size of records -- depends on XLS version - static final int MAXRECLEN = 8224; - static final int MAXROWS_BIFF8 = 65536; - static final int MAXCOLS_BIFF8 = 256; - static final int MAXROWS = 1048576; // new Excel 2007 limits - static final int MAXCOLS = 16384; // new Excel 2007 limits + int MAXRECLEN = 8224; + int MAXROWS_BIFF8 = 65536; + int MAXCOLS_BIFF8 = 256; + int MAXROWS = 1048576; // new Excel 2007 limits + int MAXCOLS = 16384; // new Excel 2007 limits } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSRecord.java b/src/main/java/io/starter/formats/XLS/XLSRecord.java index ae605fa..bdc5456 100644 --- a/src/main/java/io/starter/formats/XLS/XLSRecord.java +++ b/src/main/java/io/starter/formats/XLS/XLSRecord.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,951 +22,1027 @@ */ package io.starter.formats.XLS; -import java.io.*; - -import java.util.*; - import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.ExcelTools; import io.starter.OpenXLS.FormatHandle; -import io.starter.toolkit.*; -import io.starter.formats.LEO.*; - +import io.starter.formats.LEO.BlockByteConsumer; +import io.starter.formats.LEO.BlockByteReader; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.CompatibleVector; +import io.starter.toolkit.Logger; -/**

                -    The XLS byte stream is composed of records delimited by a header with type
                -    and data length information, followed by a record Body which contains
                +import java.io.ByteArrayOutputStream;
                +import java.io.IOException;
                +import java.io.Serializable;
                +import java.util.AbstractList;
                +import java.util.ArrayList;
                +import java.util.Iterator;
                +import java.util.List;
                 
                -    the byte[] data for the record.
                -    
                -    XLSRecord subclasses provide specific functionality.
                 
                -    
                - * +/** + *
                + * The XLS byte stream is composed of records delimited by a header with type
                + * and data length information, followed by a record Body which contains
                + *
                + * the byte[] data for the record.
                + *
                + * XLSRecord subclasses provide specific functionality.
                + *
                + * 
                + * * @see WorkBook * @see Boundsheet * @see Index * @see Dbcell * @see Row * @see Cell - */ -public class XLSRecord implements BiffRec, BlockByteConsumer, Serializable, XLSConstants { - - private static final long serialVersionUID = -106915096753184441L; - - private short opcode; - int reclen; - byte[] data; - private transient BlockByteReader databuf; - private transient BlockByteReader encryptedDatabuf; - protected boolean isContinueMerged = false; - protected transient int DEBUGLEVEL = 0; - boolean isValueForCell; - boolean isFPNumber; - boolean isDoubleNumber = false; - boolean isIntNumber; - public boolean isString; - public boolean isBoolean; - boolean isFormula; - public boolean isBlank; - boolean isReadOnly = false; - protected int rw = -1; - protected short col; - public int offset = 0; // byte stream offset of rec - protected transient Index idx; - protected Sheet worksheet; - public transient Xf myxf; - protected transient WorkBook wkbook; - protected transient ByteStreamer streamer; - protected AbstractList continues; - int originalsize = -1, +public class XLSRecord implements BiffRec, BlockByteConsumer, Serializable, XLSConstants { + + private static final long serialVersionUID = -106915096753184441L; + + private short opcode; + int reclen; + byte[] data; + private transient BlockByteReader databuf; + private transient BlockByteReader encryptedDatabuf; + protected boolean isContinueMerged = false; + protected transient int DEBUGLEVEL = 0; + boolean isValueForCell; + boolean isFPNumber; + boolean isDoubleNumber = false; + boolean isIntNumber; + public boolean isString; + public boolean isBoolean; + boolean isFormula; + public boolean isBlank; + boolean isReadOnly = false; + protected int rw = -1; + protected short col; + public int offset = 0; // byte stream offset of rec + protected transient Index idx; + protected Sheet worksheet; + public transient Xf myxf; + protected transient WorkBook wkbook; + protected transient ByteStreamer streamer; + protected AbstractList continues; + int originalsize = -1, originalIndex = 0, originalOffset = 0, ixfe = -1; - public Hlink hyperlink = null; - Row myrow = null; - CellRange mergeRange; - private int firstblock, lastblock; - - - public short getOpcode(){ return opcode; } - public void setOpcode(short op){ this.opcode = op; } - - public void setHyperlink(Hlink hl) { - this.hyperlink = hl; - } - public Formula getFormulaRec(){ - if(this instanceof Formula) { - ((Formula)this).populateExpression(); - return (Formula) this; - } - return null; - } - - public boolean shouldEncrypt() { - return true; - } - - public void setRow(Row r) { - myrow = r; - } - - /** get the row of this cell - */ - public Row getRow(){ - if (myrow == null){ - myrow = this.worksheet.getRowByNumber(rw); - } - return myrow; - } - - public Xf getXfRec(){ - if (myxf==null) { - if((ixfe > -1)&&(ixfe < wkbook.getNumXfs())){ - this.myxf = wkbook.getXf(this.ixfe); - } - } - return myxf; - } - - - /** returns the existing font record - for this Cell - */ - public Font getFont(){ - WorkBook b = this.getWorkBook(); - if(b==null)return null; - this.getXfRec(); - if(myxf==null)return null; - return myxf.getFont(); - } - - /** - * @return - */ - public CellRange getMergeRange() { - return mergeRange; - } - - /** - * @param range - */ - public void setMergeRange(CellRange range) { - mergeRange= range; - } - - - - /** Removes this BiffRec from the WorkSheet - - @param whether to nullify this Cell - */ - public boolean remove(boolean nullme){ - boolean success = false; - if(worksheet != null && isValueForCell) - getSheet().removeCell(this); - - if(streamer !=null) - streamer.removeRecord(this); - - if(nullme){ - try{this.finalize();}catch(Throwable t){;} - } - this.worksheet = null; - return true; - } - - /** set the Formatting for this BiffRec from the pattern - match. - - case insensitive pattern match is performed... - */ - public String getFormatPattern(){ - if(myxf==null)return ""; - return myxf.getFormatPattern(); - } - /** get the int val of the type for the valrec - */ - public int getCellType(){ - if(this.isBlank)return TYPE_BLANK; - if(this.isString)return TYPE_STRING; - if(this.isDoubleNumber) return TYPE_DOUBLE; - if(this.isFPNumber)return TYPE_FP; - if(this.isIntNumber)return TYPE_INT; - if(this.isFormula)return TYPE_FORMULA; - if(this.isBoolean)return TYPE_BOOLEAN; - return -1; - } - + public Hlink hyperlink = null; + Row myrow = null; + CellRange mergeRange; + private int firstblock, lastblock; + + + public short getOpcode() { + return opcode; + } + + public void setOpcode(short op) { + this.opcode = op; + } + + public void setHyperlink(Hlink hl) { + this.hyperlink = hl; + } + + public Formula getFormulaRec() { + if (this instanceof Formula) { + ((Formula) this).populateExpression(); + return (Formula) this; + } + return null; + } + + public boolean shouldEncrypt() { + return true; + } + + public void setRow(Row r) { + myrow = r; + } + + /** + * get the row of this cell + */ + public Row getRow() { + if (myrow == null) { + myrow = this.worksheet.getRowByNumber(rw); + } + return myrow; + } + + public Xf getXfRec() { + if (myxf == null) { + if ((ixfe > -1) && (ixfe < wkbook.getNumXfs())) { + this.myxf = wkbook.getXf(this.ixfe); + } + } + return myxf; + } + + + /** + * returns the existing font record + * for this Cell + */ + public Font getFont() { + WorkBook b = this.getWorkBook(); + if (b == null) return null; + this.getXfRec(); + if (myxf == null) return null; + return myxf.getFont(); + } + + /** + * @return + */ + public CellRange getMergeRange() { + return mergeRange; + } + + /** + * @param range + */ + public void setMergeRange(CellRange range) { + mergeRange = range; + } + + + /** + * Removes this BiffRec from the WorkSheet + * + * @param whether to nullify this Cell + */ + public boolean remove(boolean nullme) { + boolean success = false; + if (worksheet != null && isValueForCell) + getSheet().removeCell(this); + + if (streamer != null) + streamer.removeRecord(this); + + if (nullme) { + try { + this.finalize(); + } catch (Throwable t) { + } + } + this.worksheet = null; + return true; + } + + /** + * set the Formatting for this BiffRec from the pattern + * match. + *

                + * case insensitive pattern match is performed... + */ + public String getFormatPattern() { + if (myxf == null) return ""; + return myxf.getFormatPattern(); + } + + /** + * get the int val of the type for the valrec + */ + public int getCellType() { + if (this.isBlank) return TYPE_BLANK; + if (this.isString) return TYPE_STRING; + if (this.isDoubleNumber) return TYPE_DOUBLE; + if (this.isFPNumber) return TYPE_FP; + if (this.isIntNumber) return TYPE_INT; + if (this.isFormula) return TYPE_FORMULA; + if (this.isBoolean) return TYPE_BOOLEAN; + return -1; + } + // Methods from BlockByteConsumer // - /** Set the relative position within the data - * underlying the block vector represented by - * the BlockByteReader. - * - * In other words, this is the relative position - * used by the BlockByteReader to offset the Consumer's - * read position within the collection of Data Blocks. - * - * This may be an offset relative to the data in a file, - * or within a Storage contained in a file. - * - * The Workbook Storage for example will contain a non-contiguous - * collection of Blocks containing data from any number of - * positions in a file. - * - * This collection forms a contiguous span of bytes comprising - * an XLS Workbook. The XLSRecords within this span of bytes will - * set their relative position within this 'virtual' array. Thus - * the XLSRecord positions are relative to the order of bytes contained - * in the Block collection. The BOF record then is at offset 0 within the - * data of the first Block, even though the underlying data of this - * first Block may be anywhere on disk. - * - * @param pos - */ - public void setOffset(int pos) { - if(originalOffset<1)originalOffset = pos; - offset = pos; - } - - /** Get the relative position within the data - * underlying the block vector represented by - * the BlockByteReader. - * - * @return relative position - */ - public int getOffset() { - if(this.data ==null)return this.originalOffset; - return (int) offset; - } - - /** Get the blocks containing this Consumer's data - * - * @return - */ + /** + * Set the relative position within the data + * underlying the block vector represented by + * the BlockByteReader. + *

                + * In other words, this is the relative position + * used by the BlockByteReader to offset the Consumer's + * read position within the collection of Data Blocks. + *

                + * This may be an offset relative to the data in a file, + * or within a Storage contained in a file. + *

                + * The Workbook Storage for example will contain a non-contiguous + * collection of Blocks containing data from any number of + * positions in a file. + *

                + * This collection forms a contiguous span of bytes comprising + * an XLS Workbook. The XLSRecords within this span of bytes will + * set their relative position within this 'virtual' array. Thus + * the XLSRecord positions are relative to the order of bytes contained + * in the Block collection. The BOF record then is at offset 0 within the + * data of the first Block, even though the underlying data of this + * first Block may be anywhere on disk. + * + * @param pos + */ + public void setOffset(int pos) { + if (originalOffset < 1) originalOffset = pos; + offset = pos; + } + + /** + * Get the relative position within the data + * underlying the block vector represented by + * the BlockByteReader. + * + * @return relative position + */ + public int getOffset() { + if (this.data == null) return this.originalOffset; + return offset; + } + + /** Get the blocks containing this Consumer's data + * + * @return + */ /*unused: public Block[] getBlocks() { return myblocks; }*/ - /** Set the blocks containing this Consumer's data - * - * @param myblocks - */ + /** Set the blocks containing this Consumer's data + * + * @param myblocks + */ /* unused: public void setBlocks(Block[] myb) { myblocks = myb; }*/ - /** Sets the index of the first block - * - * @return - */ - public void setFirstBlock(int i) { - firstblock = i; - } - - /** Sets the index of the last block - * - * @return - */ - public void setLastBlock(int i) { - lastblock = i; - } - - /** Returns the index of the first block - * - * @return - */ - public int getFirstBlock() { - return firstblock; - } - - /** Returns the index of the last block - * - * @return - */ - public int getLastBlock() { - return lastblock; - } - - + /** + * Sets the index of the first block + * + * @return + */ + public void setFirstBlock(int i) { + firstblock = i; + } + + /** + * Sets the index of the last block + * + * @return + */ + public void setLastBlock(int i) { + lastblock = i; + } + + /** + * Returns the index of the first block + * + * @return + */ + public int getFirstBlock() { + return firstblock; + } + + /** + * Returns the index of the last block + * + * @return + */ + public int getLastBlock() { + return lastblock; + } + + protected void initRowCol() { - int pos = 0; + int pos = 0; - byte[] bt = this.getBytesAt(pos,2); - rw = ByteTools.readUnsignedShort(bt[0],bt[1]); - pos+=2; - col = ByteTools.readShort(this.getByteAt(pos++), this.getByteAt(pos++)); + byte[] bt = this.getBytesAt(pos, 2); + rw = ByteTools.readUnsignedShort(bt[0], bt[1]); + pos += 2; + col = ByteTools.readShort(this.getByteAt(pos++), this.getByteAt(pos++)); } - - + + public String toString() { - return getRecDesc(); - } - - public String getRecDesc(){ - String ret = ""; - String name = this.getClass().getSimpleName(); - - // record name - ret += (name.equals("XLSRecord") ? "unknown" : name.toUpperCase()); - // hex record number - ret += " (" + Integer.toHexString( opcode ).toUpperCase() + "h)"; - // stream offset - ret += " at " + Integer.toHexString( offset ).toUpperCase() + "h"; - // size - ret += " length " + Integer.toHexString( reclen ).toUpperCase() + "h"; - // file offset - ret += " file " + (databuf == null ? "no file"/*originalFileOffset*/ : - databuf.getFileOffsetString( offset, reclen ) ); - // cell address, if applicable - if (this.isValueForCell()) ret += " cell " + this.getCellAddress(); - - return ret; - } - - /** Dumps this record as a human-readable string. - */ - public String toHexDump() { - return getRecDesc() + "\n" + ByteTools.getByteDump(this.getData(), 0); - } - - /** Copy all formatting info from source biffrec - - * + return getRecDesc(); + } + + public String getRecDesc() { + String ret = ""; + String name = this.getClass().getSimpleName(); + + // record name + ret += (name.equals("XLSRecord") ? "unknown" : name.toUpperCase()); + // hex record number + ret += " (" + Integer.toHexString(opcode).toUpperCase() + "h)"; + // stream offset + ret += " at " + Integer.toHexString(offset).toUpperCase() + "h"; + // size + ret += " length " + Integer.toHexString(reclen).toUpperCase() + "h"; + // file offset + ret += " file " + (databuf == null ? "no file"/*originalFileOffset*/ : + databuf.getFileOffsetString(offset, reclen)); + // cell address, if applicable + if (this.isValueForCell()) ret += " cell " + this.getCellAddress(); + + return ret; + } + + /** + * Dumps this record as a human-readable string. + */ + public String toHexDump() { + return getRecDesc() + "\n" + ByteTools.getByteDump(this.getData(), 0); + } + + /** + * Copy all formatting info from source biffrec + * * @see io.starter.formats.XLS.BiffRec#copyFormat(io.starter.formats.XLS.BiffRec) */ - public void copyFormat(BiffRec source) { - Xf clone = (Xf) source.getXfRec().clone(); - Font fontClone = (Font)source.getXfRec().getFont().clone(); - - Logger.logInfo(source + ":" + source.getXfRec() +":" + clone); - int fid = -1; - int xid = -1; - // see if we have an equivalent Xf/Font combo - if(this.getWorkBook().getFontRecs().contains(fontClone)) { - fid = this.getWorkBook().getFontRecs().indexOf(fontClone); - } - if(this.getWorkBook().getXfrecs().contains(clone)) { - xid = this.getWorkBook().getXfrecs().indexOf(clone); - } - - // add the xf/font and set the ixfe - this.getWorkBook().addRecord(clone,false); - this.getWorkBook().addRecord(fontClone,false); - clone.setFont(fontClone.getIdx()); - this.setXFRecord(clone.getIdx()); - - } - - /** clone a record - */ - public Object clone(){ - try{ + public void copyFormat(BiffRec source) { + Xf clone = (Xf) source.getXfRec().clone(); + Font fontClone = (Font) source.getXfRec().getFont().clone(); + + Logger.logInfo(source + ":" + source.getXfRec() + ":" + clone); + int fid = -1; + int xid = -1; + // see if we have an equivalent Xf/Font combo + if (this.getWorkBook().getFontRecs().contains(fontClone)) { + fid = this.getWorkBook().getFontRecs().indexOf(fontClone); + } + if (this.getWorkBook().getXfrecs().contains(clone)) { + xid = this.getWorkBook().getXfrecs().indexOf(clone); + } + + // add the xf/font and set the ixfe + this.getWorkBook().addRecord(clone, false); + this.getWorkBook().addRecord(fontClone, false); + clone.setFont(fontClone.getIdx()); + this.setXFRecord(clone.getIdx()); + + } + + /** + * clone a record + */ + public Object clone() { + try { String cn = getClass().getName(); - XLSRecord rec = (XLSRecord)Class.forName(cn).newInstance(); + XLSRecord rec = (XLSRecord) Class.forName(cn).newInstance(); byte[] inb = getBytes(); rec.setData(inb); - rec.streamer=this.streamer; + rec.streamer = this.streamer; rec.setWorkBook(getWorkBook()); - rec.setOpcode(getOpcode()); - rec.setLength(getLength()); - rec.setSheet(getSheet()); //20081120 KSC: otherwise may set sheet incorrectly in init + rec.setOpcode(getOpcode()); + rec.setLength(getLength()); + rec.setSheet(getSheet()); //20081120 KSC: otherwise may set sheet incorrectly in init rec.init(); return rec; - }catch(Exception e){Logger.logInfo("cloning XLSRecord " + this.getCellAddress() + " failed: "+ e);} + } catch (Exception e) { + Logger.logInfo("cloning XLSRecord " + this.getCellAddress() + " failed: " + e); + } return null; } - - /** return whether this is a numeric type - * + + /** + * return whether this is a numeric type */ public boolean isNumber() { - if(this.opcode == RK)return true; - if(this.opcode == NUMBER) return true; - return false; + if (this.opcode == RK) return true; + return this.opcode == NUMBER; } - - /** return whether this is a formula record - * + + /** + * return whether this is a formula record */ public boolean isFormula() { - return this.isFormula; + return this.isFormula; } - + /** methods from CompatibleVectorHints - - protected transient int recordIdx = -1; - */ + + protected transient int recordIdx = -1; + */ /** provide a hint to the CompatibleVector - about this objects likely position. - - public int getRecordIndexHint(){return recordIdx;} - */ - + about this objects likely position. + + public int getRecordIndexHint(){return recordIdx;} + */ + /** set index information about this - objects likely position. - - public void setRecordIndexHint(int i){ - lastidx = i; - recordIdx = i; - } - */ - /** set the DEBUG level*/ - public void setDebugLevel(int b){DEBUGLEVEL=b;} - + objects likely position. + + public void setRecordIndexHint(int i){ + lastidx = i; + recordIdx = i; + } + */ + /** + * set the DEBUG level + */ + public void setDebugLevel(int b) { + DEBUGLEVEL = b; + } + public int lastidx = -1; - /** return the real (not just boundsheet) record index of this object + /** + * return the real (not just boundsheet) record index of this object */ public int getRealRecordIndex() { return streamer.getRealRecordIndex(this); } - - /** return the record index of this object + + /** + * return the record index of this object */ - public int getRecordIndex(){ - if(streamer == null){ - if (this.getSheet()!=null) // KSC: Added - return this.getSheet().getSheetRecs().indexOf(this); - return -1; + public int getRecordIndex() { + if (streamer == null) { + if (this.getSheet() != null) // KSC: Added + return this.getSheet().getSheetRecs().indexOf(this); + return -1; } - return streamer.getRecordIndex(this); - } - - /** adds a CONTINUE record to the array of CONTINUE records - for this record, containing all data - beyond the 8224 byte record size limit. - */ - public void addContinue(Continue c){ - if(continues == null)continues = new ArrayList(); + return streamer.getRecordIndex(this); + } + + /** + * adds a CONTINUE record to the array of CONTINUE records + * for this record, containing all data + * beyond the 8224 byte record size limit. + */ + public void addContinue(Continue c) { + if (continues == null) continues = new ArrayList(); continues.add(c); } - - /** remove all Continue records - */ - public void removeContinues(){ - if(continues != null)continues.clear(); + + /** + * remove all Continue records + */ + public void removeContinues() { + if (continues != null) continues.clear(); } - public List getContinueVect(){ - if(continues != null)return this.continues; + public List getContinueVect() { + if (continues != null) return this.continues; continues = new CompatibleVector(); return continues; } - - /** returns whether this record has a CONTINUE - record containing data beyond the 8228 byte - record size limit. - - XLSRecords can have 0 or more CONTINUE records. - */ - public boolean hasContinues(){ - if(continues == null)return false; - if(this.continues.size() > 0)return true; - return false; + + /** + * returns whether this record has a CONTINUE + * record containing data beyond the 8228 byte + * record size limit. + *

                + * XLSRecords can have 0 or more CONTINUE records. + */ + public boolean hasContinues() { + if (continues == null) return false; + return this.continues.size() > 0; + } + + + /** + * set whether this record contains the value + * of the Cell. + */ + public void setIsValueForCell(boolean b) { + isValueForCell = b; + } + + /** + * associate this record with its Index record + */ + public void setIndex(Index id) { + idx = id; } - - - /** set whether this record contains the value - of the Cell. - */ - public void setIsValueForCell(boolean b){isValueForCell = b;} - - /** associate this record with its Index record - */ - public void setIndex(Index id){idx = id;} - - /** Associate this record with a worksheet. - First checks to see if there is already - a cell with this address. - - */ - public void setSheet(Sheet b){ + + /** + * Associate this record with a worksheet. + * First checks to see if there is already + * a cell with this address. + */ + public void setSheet(Sheet b) { this.worksheet = b; } - - /** get the WorkSheet for this record. - */ - public Boundsheet getSheet(){return (Boundsheet)worksheet;} - - public void setWorkBook(WorkBook wk){wkbook = (WorkBook)wk;} - - public WorkBook getWorkBook(){ - if((wkbook==null)&&(worksheet!=null)) - wkbook = worksheet.getWorkBook(); - - return (WorkBook)wkbook; - } - - /** set the column - */ - public void setCol(short i){ - byte[] c = ByteTools.shortToLEBytes((short) i); + + /** + * get the WorkSheet for this record. + */ + public Boundsheet getSheet() { + return (Boundsheet) worksheet; + } + + public void setWorkBook(WorkBook wk) { + wkbook = wk; + } + + public WorkBook getWorkBook() { + if ((wkbook == null) && (worksheet != null)) + wkbook = worksheet.getWorkBook(); + + return wkbook; + } + + /** + * set the column + */ + public void setCol(short i) { + byte[] c = ByteTools.shortToLEBytes(i); System.arraycopy(c, 0, getData(), 2, 2); col = i; } - - public void setRowCol(int[] x){ - this.setRowNumber(x[0]); - this.setCol((short)x[1]); + + public void setRowCol(int[] x) { + this.setRowNumber(x[0]); + this.setCol((short) x[1]); } - - /** set the row - */ - public void setRowNumber(int i){ + + /** + * set the row + */ + public void setRowNumber(int i) { byte[] r = ByteTools.cLongToLEBytes(i); - System.arraycopy(r, 0,getData(), 0, 2); + System.arraycopy(r, 0, getData(), 0, 2); rw = i; } - - - public short getColNumber(){return col;} - - public int getRowNumber(){ - if(rw < 0) { - int rowi = rw * -1; - if (wkbook.getIsExcel2007()) - rw = MAXROWS - rowi; - else - rw= MAXCOLS_BIFF8 - rowi; - } + + + public short getColNumber() { + return col; + } + + public int getRowNumber() { + if (rw < 0) { + int rowi = rw * -1; + if (wkbook.getIsExcel2007()) + rw = MAXROWS - rowi; + else + rw = MAXCOLS_BIFF8 - rowi; + } return rw; } - - /** get a string address for the - cell based on row and col ie: "H22" - - */ - public String getCellAddress(){ - int rownum = rw+1; - if((rownum < 0) && (col >= 0)){ // > 32k and the rows go negative... ! - rownum = MAXROWS+rownum; - }else if((rownum == 0) && (col >= 0)){ // the very last row...MAXROWS_BIFF8 - rownum = MAXROWS; - } - if((rownum > MAXROWS) || (col < 0)){ - if(DEBUGLEVEL > -1)Logger.logWarn("XLSRecord.getCellAddress() Row/Col info incorrect for Cell:" + ExcelTools.getAlphaVal(col) + String.valueOf(rownum)); - return ""; + + /** + * get a string address for the + * cell based on row and col ie: "H22" + */ + public String getCellAddress() { + int rownum = rw + 1; + if ((rownum < 0) && (col >= 0)) { // > 32k and the rows go negative... ! + rownum = MAXROWS + rownum; + } else if ((rownum == 0) && (col >= 0)) { // the very last row...MAXROWS_BIFF8 + rownum = MAXROWS; + } + if ((rownum > MAXROWS) || (col < 0)) { + if (DEBUGLEVEL > -1) + Logger.logWarn("XLSRecord.getCellAddress() Row/Col info incorrect for Cell:" + ExcelTools.getAlphaVal(col) + rownum); + return ""; } - return ExcelTools.getAlphaVal(col) + rownum; + return ExcelTools.getAlphaVal(col) + rownum; } - + /** * returns the cell address in int[] {row, col} format */ public int[] getIntLocation() { - return new int[] {rw, col}; + return new int[]{rw, col}; } - - - + + /** * return the cell address with sheet reference * eg Sheet!A12 + * * @return String */ public String getCellAddressWithSheet() { - if (this.getSheet()!=null) - return this.getSheet().getSheetName() + "!" + this.getCellAddress(); - return this.getCellAddress(); - } - /** perform record initialization - */ - public void init(){ - if(originalsize == 0)originalsize = reclen; - } - - /** get a default "empty" data value for this record - */ - public Object getDefaultVal(){ - if(this.isDoubleNumber) return new Double(0.0); - if(this.isFPNumber) return new Float(0.0f); - if(this.isBoolean) return Boolean.valueOf(false); - if(this.isIntNumber) return Integer.valueOf(0); - if(this.isString) return ""; - if(this.isFormula) return this.getFormulaRec().getFormulaString(); - if(this.isBlank) return ""; - return null; - } - - /** get the data type name for this record - */ - public String getDataType(){ - if(this.isValueForCell){ - if(this.isBlank) return "Blank"; - if(this.isDoubleNumber) return "Double"; - if(this.isFPNumber) return "Float"; - if(this.isBoolean) return "Boolean"; - if(this.isIntNumber) return "Integer"; - if(this.isString) return "String"; - if(this.isFormula) return "Formula"; + if (this.getSheet() != null) + return this.getSheet().getSheetName() + "!" + this.getCellAddress(); + return this.getCellAddress(); + } + + /** + * perform record initialization + */ + public void init() { + if (originalsize == 0) originalsize = reclen; + } + + /** + * get a default "empty" data value for this record + */ + public Object getDefaultVal() { + if (this.isDoubleNumber) return new Double(0.0); + if (this.isFPNumber) return new Float(0.0f); + if (this.isBoolean) return Boolean.valueOf(false); + if (this.isIntNumber) return Integer.valueOf(0); + if (this.isString) return ""; + if (this.isFormula) return this.getFormulaRec().getFormulaString(); + if (this.isBlank) return ""; + return null; + } + + /** + * get the data type name for this record + */ + public String getDataType() { + if (this.isValueForCell) { + if (this.isBlank) return "Blank"; + if (this.isDoubleNumber) return "Double"; + if (this.isFPNumber) return "Float"; + if (this.isBoolean) return "Boolean"; + if (this.isIntNumber) return "Integer"; + if (this.isString) return "String"; + if (this.isFormula) return "Formula"; } return null; } - - /** get the int val of the type for the valrec - */ - public Object getInternalVal(){ - try{ - switch(this.getCellType()) { - case TYPE_BLANK: - return getStringVal(); //essentially return ""; - - case TYPE_STRING: - return getStringVal(); - - case TYPE_FP: // always use Doubles to avoid loss of precision... see: - // details http://stackoverflow.com/questions/916081/convert-float-to-double-without-losing-precision - return new Double(getDblVal()); - - case TYPE_DOUBLE: - return new Double(getDblVal()); - - case TYPE_INT: - return Integer.valueOf(getIntVal()); - - case TYPE_FORMULA: - // OK this is broken, obviously we need to return a calced Object - Object obx = ((Formula)this).calculateFormula(); - return obx; + + /** + * get the int val of the type for the valrec + */ + public Object getInternalVal() { + try { + switch (this.getCellType()) { + case TYPE_BLANK: + return getStringVal(); //essentially return ""; + + case TYPE_STRING: + return getStringVal(); + + case TYPE_FP: // always use Doubles to avoid loss of precision... see: + // details http://stackoverflow.com/questions/916081/convert-float-to-double-without-losing-precision + return new Double(getDblVal()); + + case TYPE_DOUBLE: + return new Double(getDblVal()); + + case TYPE_INT: + return Integer.valueOf(getIntVal()); + + case TYPE_FORMULA: + // OK this is broken, obviously we need to return a calced Object + Object obx = ((Formula) this).calculateFormula(); + return obx; // return getStringVal(); - - case TYPE_BOOLEAN: - return Boolean.valueOf(getBooleanVal()); - - default: - return null; - } - }catch(Exception e){return null;} // should never happen here... - } - - - - /** Get the value of the record as an Object. - To use the Object, cast it to the native - type for the record. Ie: a String value - would need to be cast to a String, an Integer - to an Integer, etc. - - */ - Object getVal(){ + + case TYPE_BOOLEAN: + return Boolean.valueOf(getBooleanVal()); + + default: + return null; + } + } catch (Exception e) { + return null; + } // should never happen here... + } + + + /** + * Get the value of the record as an Object. + * To use the Object, cast it to the native + * type for the record. Ie: a String value + * would need to be cast to a String, an Integer + * to an Integer, etc. + */ + Object getVal() { return null; } - /** Get the value of the record as a Boolean. - Value must be parseable as a Boolean. - */ - public boolean getBooleanVal(){ + /** + * Get the value of the record as a Boolean. + * Value must be parseable as a Boolean. + */ + public boolean getBooleanVal() { return false; } - - /** Get the value of the record as an Integer. - Value must be parseable as an Integer or it - will throw a NumberFormatException. - */ - public int getIntVal(){ - return (int)Float.NaN; + + /** + * Get the value of the record as an Integer. + * Value must be parseable as an Integer or it + * will throw a NumberFormatException. + */ + public int getIntVal() { + return (int) Float.NaN; } - /** Get the value of the record as a Double. - Value must be parseable as an Double or it - will throw a NumberFormatException. - */ - public double getDblVal(){ - return (double)Float.NaN; + /** + * Get the value of the record as a Double. + * Value must be parseable as an Double or it + * will throw a NumberFormatException. + */ + public double getDblVal() { + return (double) Float.NaN; } - /** Get the value of the record as a Float. - Value must be parseable as an Float or it - will throw a NumberFormatException. - */ - public float getFloatVal(){ + /** + * Get the value of the record as a Float. + * Value must be parseable as an Float or it + * will throw a NumberFormatException. + */ + public float getFloatVal() { return Float.NaN; } - /** Get the value of the record as a String. - - */ - public String getStringVal(){return null;} - - - /** Get the value of the record as a String. - - */ - public String getStringVal(String encoding){return null;} - - public void setStringVal(String v){ + /** + * Get the value of the record as a String. + */ + public String getStringVal() { + return null; + } + + + /** + * Get the value of the record as a String. + */ + public String getStringVal(String encoding) { + return null; + } + + public void setStringVal(String v) { Logger.logErr("Setting String Val on generic XLSRecord, value not held"); } - - public void setBooleanVal(boolean b){ + + public void setBooleanVal(boolean b) { Logger.logErr("Setting Boolean Val on generic XLSRecord, value not held"); } - - public void setIntVal(int v){ + + public void setIntVal(int v) { Logger.logErr("Setting int Val on generic XLSRecord, value not held"); } - - public void setFloatVal(float v){ + + public void setFloatVal(float v) { Logger.logErr("Setting float Val on generic XLSRecord, value not held"); } - - public void setDoubleVal(double v){ + + public void setDoubleVal(double v) { Logger.logErr("Setting Double Val on generic XLSRecord, value not held"); } - - /** do any pre-streaming processing such as expensive - index updates or other deferrable processing. - */ - public void preStream(){ + + /** + * do any pre-streaming processing such as expensive + * index updates or other deferrable processing. + */ + public void preStream() { // override in sub-classes } - /** set the XF (format) record for this rec - */ - public void setXFRecord(){ - if(wkbook==null) - return; - if((ixfe > -1)&&(ixfe < wkbook.getNumXfs())){ - if (myxf==null || myxf.tableidx!= ixfe) { + /** + * set the XF (format) record for this rec + */ + public void setXFRecord() { + if (wkbook == null) + return; + if ((ixfe > -1) && (ixfe < wkbook.getNumXfs())) { + if (myxf == null || myxf.tableidx != ixfe) { this.myxf = wkbook.getXf(this.ixfe); this.myxf.incUseCount(); } } } - - /** set the XF (format) record for this rec - */ - public void setXFRecord(int i){ - if (i!=ixfe || myxf==null) { - this.setIxfe(i); - this.setXFRecord(); - } - } - - /** set the XF (format) record for this rec - */ - public void setIxfe(int i){ + + /** + * set the XF (format) record for this rec + */ + public void setXFRecord(int i) { + if (i != ixfe || myxf == null) { + this.setIxfe(i); + this.setXFRecord(); + } + } + + /** + * set the XF (format) record for this rec + */ + public void setIxfe(int i) { this.ixfe = i; byte[] newxfe = ByteTools.cLongToLEBytes(i); byte[] b = this.getData(); - if(b!=null) + if (b != null) System.arraycopy(newxfe, 0, b, 4, 2); this.setData(b); } - - /** get the ixfe - */ - public int getIxfe(){ + + /** + * get the ixfe + */ + public int getIxfe() { return this.ixfe; } - - - /** get a new, generic instance of a Record. - */ - protected static XLSRecord getPrototype(){ + + /** + * get a new, generic instance of a Record. + */ + protected static XLSRecord getPrototype() { Logger.logWarn("Attempt to get prototype XLSRecord failed. There is no prototype record defined for this record type."); return null; } - - - - public void setByteReader(BlockByteReader db) { - databuf = db; - data = null; - } - - public BlockByteReader getByteReader() { - return databuf; - } - - /** - * Hold onto the original encrypted bytes so we can do a look ahead on records - */ - public void setEncryptedByteReader(BlockByteReader db) { - encryptedDatabuf = db; - - } - - public BlockByteReader getEncryptedByteReader() { - return encryptedDatabuf; - } - - public void setData(byte[] b) { - data = b; - this.databuf = null; - } - - /** gets the record data merging any Continue record - data. - */ - public byte[] getData() { - int len= 0; - if ((len=this.getLength()) == 0) - return new byte[] { - }; - if (data != null) - return data; - if (len>MAXRECLEN) { - setData( this.getBytesAt(0, MAXRECLEN)); - }else { - setData( this.getBytesAt(0, len-4)); + + + public void setByteReader(BlockByteReader db) { + databuf = db; + data = null; + } + + public BlockByteReader getByteReader() { + return databuf; + } + + /** + * Hold onto the original encrypted bytes so we can do a look ahead on records + */ + public void setEncryptedByteReader(BlockByteReader db) { + encryptedDatabuf = db; + + } + + public BlockByteReader getEncryptedByteReader() { + return encryptedDatabuf; + } + + public void setData(byte[] b) { + data = b; + this.databuf = null; + } + + /** + * gets the record data merging any Continue record + * data. + */ + public byte[] getData() { + int len = 0; + if ((len = this.getLength()) == 0) + return new byte[]{ + }; + if (data != null) + return data; + if (len > MAXRECLEN) { + setData(this.getBytesAt(0, MAXRECLEN)); + } else { + setData(this.getBytesAt(0, len - 4)); } - if (this.opcode == SST || this.opcode == SXLI) - return data; + if (this.opcode == SST || this.opcode == SXLI) + return data; if (!isContinueMerged && hasContinues()) { - mergeContinues(); + mergeContinues(); } - return data; - } - + return data; + } + /** * Merge continue data in to the record data array - - * */ protected void mergeContinues() { - List cx = this.getContinueVect(); - if (cx != null) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - // get the main bytes first!!! - try { - out.write(data); - } catch (IOException e) { - Logger.logWarn( + List cx = this.getContinueVect(); + if (cx != null) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // get the main bytes first!!! + try { + out.write(data); + } catch (IOException e) { + Logger.logWarn( "ERROR: parsing record continues failed: " - + this.toString() - + ": " - + e); - } - Iterator it = cx.iterator(); - while(it.hasNext()) { - Continue c = (Continue)it.next(); - byte[] nb = c.getData(); - if (c.getHasGrbit()) { // remove it - happens in continued StringRec ... a rare case - byte[] newnb= new byte[nb.length-1]; - System.arraycopy(nb, 1, newnb, 0, newnb.length); - nb= newnb; - } - try { - out.write(nb); - } catch (IOException a) { - Logger.logWarn( - "ERROR: parsing record continues failed: " + this.toString() + ": " - + a); - } + + e); + } + Iterator it = cx.iterator(); + while (it.hasNext()) { + Continue c = (Continue) it.next(); + byte[] nb = c.getData(); + if (c.getHasGrbit()) { // remove it - happens in continued StringRec ... a rare case + byte[] newnb = new byte[nb.length - 1]; + System.arraycopy(nb, 1, newnb, 0, newnb.length); + nb = newnb; + } + try { + out.write(nb); + } catch (IOException a) { + Logger.logWarn( + "ERROR: parsing record continues failed: " + + this.toString() + + ": " + + a); } - this.data = out.toByteArray(); } - isContinueMerged = true; - } - - - /** Gets the byte from the specified position in the - * record byte array. - * - * @param off - * @return - */ - public byte[] getBytes() { - return this.getBytesAt(0, this.getLength()); - } - - /** Gets the byte from the specified position in the - * record byte array. - * - * @param off - * @return - */ - public byte[] getBytesAt(int off, int len) { - if(this.data != null) { - if(len+off>data.length)len = data.length-off; // deal with bad requests - byte[] ret = new byte[len]; - System.arraycopy(data,off,ret,0,len); - return ret; - } - if(databuf == null)return null; - return this.databuf.get(this, off, len); - } - - /** Sets a subset of temporary bytes for fast access during init methods... - * - - public void initCacheBytes(int start, int len) { - data = this.getBytesAt(start, len); - } */ - - /** resets the cache bytes so they do not take up space - * - */ - public void resetCacheBytes(){ - // data = null; - } - - /** Gets the byte from the specified position in the - * record byte array. - * - * @param off - * @return - */ - public byte getByteAt(int off) { - if(this.data!=null) { + this.data = out.toByteArray(); + } + isContinueMerged = true; + } + + + /** + * Gets the byte from the specified position in the + * record byte array. + * + * @param off + * @return + */ + public byte[] getBytes() { + return this.getBytesAt(0, this.getLength()); + } + + /** + * Gets the byte from the specified position in the + * record byte array. + * + * @param off + * @return + */ + public byte[] getBytesAt(int off, int len) { + if (this.data != null) { + if (len + off > data.length) len = data.length - off; // deal with bad requests + byte[] ret = new byte[len]; + System.arraycopy(data, off, ret, 0, len); + return ret; + } + if (databuf == null) return null; + return this.databuf.get(this, off, len); + } + + /** Sets a subset of temporary bytes for fast access during init methods... + * + + public void initCacheBytes(int start, int len) { + data = this.getBytesAt(start, len); + } */ + + /** + * resets the cache bytes so they do not take up space + */ + public void resetCacheBytes() { + // data = null; + } + + /** + * Gets the byte from the specified position in the + * record byte array. + * + * @param off + * @return + */ + public byte getByteAt(int off) { + if (this.data != null) { return data[off]; } - if(this.databuf==null) + if (this.databuf == null) throw new InvalidRecordException("XLSRecord has no data buffer." + this.getCellAddress()); - return this.databuf.get(this, off); - } - - public void setLength(int len) { - if(this.originalsize <= 0)this.originalsize = len; // returns the original len always - this.reclen = len; // returns updated lengths - } - - /**Returns the length of this - * record, including the 4 header bytes - * - */ - public int getLength() { - if (data != null) - return data.length+4; - else if (this.databuf == null) // a new rec - return -1; - if ((hasContinues())&& - (!isContinueMerged)&& - !(this.opcode == SST)&& - !(this.opcode == SXLI)){ - if (this.reclen>MAXRECLEN) { - setData( this.getBytesAt(0, MAXRECLEN)); - }else{ - setData( this.getBytesAt(0, this.reclen)); - } - mergeContinues(); - return data.length+4; - } - return this.reclen+4; - } - + return this.databuf.get(this, off); + } + + public void setLength(int len) { + if (this.originalsize <= 0) this.originalsize = len; // returns the original len always + this.reclen = len; // returns updated lengths + } + + /** + * Returns the length of this + * record, including the 4 header bytes + */ + public int getLength() { + if (data != null) + return data.length + 4; + else if (this.databuf == null) // a new rec + return -1; + if ((hasContinues()) && + (!isContinueMerged) && + !(this.opcode == SST) && + !(this.opcode == SXLI)) { + if (this.reclen > MAXRECLEN) { + setData(this.getBytesAt(0, MAXRECLEN)); + } else { + setData(this.getBytesAt(0, this.reclen)); + } + mergeContinues(); + return data.length + 4; + } + return this.reclen + 4; + } + /** * @return Returns the isValueForCell. */ public boolean isValueForCell() { return isValueForCell; } + /** * @param isVal true if this is a cell-type record */ public void setValueForCell(boolean isVal) { this.isValueForCell = isVal; } + /** * @return Returns the isReadOnly. */ @@ -980,6 +1056,7 @@ public boolean isReadOnly() { public ByteStreamer getStreamer() { return streamer; } + /** * @param streamer The streamer to set. */ @@ -993,52 +1070,52 @@ public void setStreamer(ByteStreamer str) { public Hlink getHyperlink() { return hyperlink; } - + public void postStream() { - // nothing here -- use to blow out data + // nothing here -- use to blow out data } - + /** * clear out object references in prep for closing workbook */ public void close() { - if (databuf!=null) { - databuf.clear(); - databuf= null; - } - idx= null; - worksheet= null; - myxf= null; - wkbook= null; - streamer= null; - if (continues!=null) { - for (int i= 0; i < continues.size(); i++) - ((XLSRecord) continues.get(i)).close(); - continues.clear(); + if (databuf != null) { + databuf.clear(); + databuf = null; + } + idx = null; + worksheet = null; + myxf = null; + wkbook = null; + streamer = null; + if (continues != null) { + for (int i = 0; i < continues.size(); i++) + ((XLSRecord) continues.get(i)).close(); + continues.clear(); } - if (hyperlink!=null) { - hyperlink.close(); - hyperlink = null; + if (hyperlink != null) { + hyperlink.close(); + hyperlink = null; } - mergeRange= null; - if (myrow!=null) { - myrow= null; + mergeRange = null; + if (myrow != null) { + myrow = null; } - mergeRange= null; - + mergeRange = null; + } - + /** - Get the color table for the associated workbook. If the workbook is null - then the default colortable will be returned. - */ - public java.awt.Color[] getColorTable(){ - try{ - return this.getWorkBook().getColorTable(); - }catch(Exception e){ - return FormatHandle.COLORTABLE; - } + * Get the color table for the associated workbook. If the workbook is null + * then the default colortable will be returned. + */ + public java.awt.Color[] getColorTable() { + try { + return this.getWorkBook().getColorTable(); + } catch (Exception e) { + return FormatHandle.COLORTABLE; + } } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSRecordFactory.java b/src/main/java/io/starter/formats/XLS/XLSRecordFactory.java index 52526f8..928e321 100644 --- a/src/main/java/io/starter/formats/XLS/XLSRecordFactory.java +++ b/src/main/java/io/starter/formats/XLS/XLSRecordFactory.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,577 +22,975 @@ */ package io.starter.formats.XLS; -import io.starter.formats.XLS.formulas.*; import io.starter.formats.XLS.charts.*; +import io.starter.formats.XLS.formulas.Ptg; -import java.util.*; - -/** Factory to create XLSRecords and Ptgs. +/** + * Factory to create XLSRecords and Ptgs. */ public class XLSRecordFactory -implements XLSConstants { - - /** This class is static only, prohibit construction. - */ - private XLSRecordFactory() { - throw new UnsupportedOperationException( - "XLSRecordFactory is purely static" ); - } - - // 20060504 KSC: add separate array for unary prefix operators: - // maps regular operator to unary pseudo version located in ptgLookup below - public static Object[][] ptgPrefixOperators= { - {(Object)"+","u+"}, - {(Object)"-","u-"}, - // should $ be in here??? - }; - - /** Maps BIFF8 record opcodes to classes. */ - // Removed static object instance creations as very difficult (impossible) to dereference so as can release memeory ... + implements XLSConstants { + + /** + * This class is static only, prohibit construction. + */ + private XLSRecordFactory() { + throw new UnsupportedOperationException( + "XLSRecordFactory is purely static"); + } + + // 20060504 KSC: add separate array for unary prefix operators: + // maps regular operator to unary pseudo version located in ptgLookup below + public static Object[][] ptgPrefixOperators = { + {"+", "u+"}, + {"-", "u-"}, + // should $ be in here??? + }; + + /** + * Maps BIFF8 record opcodes to classes. + */ + // Removed static object instance creations as very difficult (impossible) to dereference so as can release memeory ... // TRY : private static final Map records; - - // subset of ptgLookup below used for pattern matching in formula strings - public static String[][] ptgOps= { - {"#VALUE!", "PtgErr"}, - {"#NULL!", "PtgErr"}, - {"#DIV/0!", "PtgErr"}, - {"#VALUE!", "PtgErr"}, - {"#REF!", "PtgErr"}, - {"#NUM!", "PtgErr"}, - {"#N/A", "PtgErr"}, - // operators - {"(", "PtgParen"}, - {"*","PtgMlt"}, - {"/","PtgDiv"}, - {"^","PtgPower"}, - {"&","PtgConcat"}, - {"<>", "PtgNE"}, - {"<=","PtgLE"}, - {"<","PtgLT"}, - {">=","PtgGE"}, - {">","PtgGT"}, - {"=","PtgEQ"}, - {"!=","PtgNE"}, - {"+","PtgAdd"}, // moved to AFTER other operators - {"-","PtgSub"}, - }; - // this is how you init a 2D array of Objects - public static String[][] ptgLookup={ - // 20070215 KSC: added constant strings to avoid PtgRef3d matches - {"#VALUE!", "PtgErr"}, - {"#NULL!", "PtgErr"}, - {"#DIV/0!", "PtgErr"}, - {"#VALUE!", "PtgErr"}, - {"#REF!", "PtgErr"}, - {"#NUM!", "PtgErr"}, - {"#N/A", "PtgErr"}, - // operators - {"(", "PtgParen"}, - {"*","PtgMlt"}, - {"/","PtgDiv"}, - {"^","PtgPower"}, - {"&","PtgConcat"}, - {"<>", "PtgNE"}, - {"<=","PtgLE"}, - {"<","PtgLT"}, - {">=","PtgGE"}, - {">","PtgGT"}, - {"=","PtgEQ"}, - {"!=","PtgNE"}, - //{" ","PtgIsect"}, intersection operator, need to work out how to use a space as operator-- SEE PTGMEMFUNC/MEMAREA -- only valid when parsing complex ranges - //{",","PtgUnion"}, problems matching as a separator in string parsing - //{":","PtgRange"}, // may have issues with ptgArea? - {"+","PtgAdd"}, // moved to AFTER other operators - {"-","PtgSub"}, - //operands - {"PtgStr","PtgStr"}, - {"PtgNumber","PtgNumber"}, - {"PtgInt","PtgInt"}, - {"PtgRef","PtgRef"}, - {"PtgArea","PtgArea"}, - {"false","PtgBool"}, - {"true","PtgBool"}, - {"PtgArea3d","PtgArea3d"}, - {"PtgRef3d","PtgRef3d"}, - {"PtgArray","PtgArray"}, - {"PtgMissArg","PtgMissArg"}, - {"PtgMemFunc","PtgMemFunc"}, - {"PtgAtr", "PtgAtr"}, - //functions - {"PtgFunc","PtgFunc"}, - {"PtgFuncVar","PtgFuncVar"}, - // unary prefix operators: see FormulaParser.splitString - {"u+","PtgUPlus"}, - {"u-","PtgUMinus"}, - {")", "PtgParen"}, - - - }; -/* DO DIFFERENTLY SO TO AVOID HANGING OBJECT REFERENCES - static { - HashMap recmap = new HashMap(); - - // Most Frequent - case BLANK), new Blank() ); - case ROW), new Row() ); - case XF), new Xf() ); - case INDEX), new Index() ); - case COUNTRY), new Country() ); - case CALCMODE), new CalcMode() ); - case DIMENSIONS), new Dimensions() ); - case SELECTION), new Selection() ); - case DEFAULTROWHEIGHT), new DefaultRowHeight() ); - case DEFCOLWIDTH), new DefColWidth() ); - case DBCELL), new Dbcell() ); - case BOF), new Bof() ); - case BOUNDSHEET), new Boundsheet() ); - case EOF), new Eof() ); - case FORMAT), new Format() ); - case STYLE), new Style() ); - case PASSWORD), new Password() ); - case PALETTE), new Palette() ); - case ARRAY), new Array() ); - case BOOLERR), new Boolerr() ); - case EXTERNSHEET), new Externsheet() ); - case EXTERNNAME), new Externname() ); - case FORMULA), new Formula() ); - case LABEL), new Label() ); - case TXO), new Txo() ); - case CONTINUE), new Continue() ); - case SST), new Sst() ); - case GUTS), new Guts() ); - case EXTSST), new Extsst() ); - case HLINK), new Hlink() ); - case LABELSST), new Labelsst() ); - case NUMBER), new NumberRec() ); - case MERGEDCELLS), new Mergedcells() ); - case MULBLANK), new Mulblank() ); - case MULRK), new Mulrk() ); - case RK), new Rk() ); - case RSTRING), new Rstring() ); - case SHRFMLA), new Shrfmla() ); - case STRINGREC), new StringRec() ); - case SUPBOOK), new Supbook() ); - case DV), new Dv() ); - case DVAL), new Dval() ); - case SETUP), new Setup() ); - case HCENTER), new HCenter() ); - case VCENTER), new VCenter() ); - case LEFTMARGIN), new LeftMargin() ); - case RIGHTMARGIN), new RightMargin() ); - case TOPMARGIN), new TopMargin() ); - case BOTTOMMARGIN), new BottomMargin() ); - case PRINTGRID), new PrintGrid() ); - case PRINTROWCOL), new PrintRowCol() ); - case XCT), new Xct() ); - case CRN), new Crn() ); - case NOTE), new Note() ); - - // Named Ranges and References - case NAME), new Name() ); - - // Workbook Settings - case FONT), new Font() ); - case DSF), new Dsf() ); - case WINDOW1), new Window1() ); - case WINDOW2), new Window2() ); - case CODENAME), new Codename() ); - case PROTECT), new Protect() ); - case OBJPROTECT), new ObjProtect() ); - case SCENPROTECT), new ScenProtect() ); - case FEATHEADR), new FeatHeadr() ); - case PROT4REV), new Prot4rev() ); - case COLINFO), new Colinfo() ); - case USERSVIEWBEGIN), new Usersviewbegin() ); - case USERSVIEWEND), new Usersviewend() ); - case WSBOOL), new WsBool() ); - case BOOKBOOL), new BookBool() ); - case USR_EXCL), new UsrExcl() ); - case INTERFACE_HDR), new InterfaceHdr() ); - case RRD_INFO), new RrdInfo() ); - case RRD_HEAD), new RrdHead() ); - case FILE_LOCK), new FileLock() ); - case PLS), new Pls() ); - case HEADERREC), new Headerrec() ); - case DATE1904), new NineteenOhFour() ); - - // Sheet Settings - case OBJ), new Obj() ); - case OBPROJ), new Obproj() ); - case FOOTERREC), new Footerrec() ); - case TABID), new TabID() ); - case PANE), new Pane() ); - case SCL), new Scl() ); - - // Protection settings - case FILEPASS), new Filepass() ); - - // Conditional Formatting - case CF), new Cf() ); - case CONDFMT), new Condfmt() ); - - // Auto filter - case AUTOFILTER), new AutoFilter() ); - - // Chart Records - case CHART), new Chart() ); - case SERIES), new Series() ); - case SERIESTEXT), new SeriesText() ); - case SERIESLIST), new SeriesList() ); - case AI), new Ai() ); - case BEGIN), new Begin() ); - case END), new End() ); - case UNITS), new Units() ); - case CHART), new Chart() ); - case DATAFORMAT), new DataFormat() ); - case LINEFORMAT), new LineFormat() ); - case MARKERFORMAT), new MarkerFormat() ); - case AREAFORMAT), new AreaFormat() ); - case PIEFORMAT), new PieFormat() ); - case ATTACHEDLABEL), new AttachedLabel() ); - case CHARTFORMAT), new ChartFormat() ); - case LEGEND), new Legend() ); - case BAR), new Bar() ); - case LINE), new Line() ); - case PIE), new Pie() ); - case AREA), new Area() ); - case SCATTER), new Scatter() ); - case CHARTLINE), new ChartLine() ); - case AXIS), new Axis() ); - case TICK), new Tick() ); - case VALUERANGE), new ValueRange() ); - case CATSERRANGE), new CatserRange() ); - case AXISLINEFORMAT), new AxisLineFormat() ); - case CHARTFORMATLINK), new ChartFormatLink() ); - case DEFAULTTEXT), new DefaultText() ); - case TEXTDISP), new TextDisp() ); - case FONTX), new Fontx() ); - case OBJECTLINK), new ObjectLink() ); - case FRAME), new Frame() ); - case BEGIN), new Begin() ); - case END), new End() ); - case PLOTAREA), new PlotArea() ); - case THREED), new ThreeD() ); - case PICF), new Picf() ); - case DROPBAR), new Dropbar() ); - case RADAR), new Radar() ); - case SURFACE), new Surface() ); - case RADARAREA), new RadarArea() ); - case AXISPARENT), new AxisParent() ); - case LEGENDXN), new Legendxn() ); - case SHTPROPS), new ShtProps() ); - case SERTOCRT), new SerToCrt() ); - case AXESUSED), new Axesused() ); - case SBASEREF), new SbaseRef() ); - case SERPARENT), new SerParent() ); - case SERAUXTREND), new SerauxTrend() ); - case IFMT), new Ifmt() ); - case POS), new Pos() ); - case ALRUNS), new AlRuns() ); - case AI), new Ai() ); - case SERAUXERRBAR), new SerauxErrBar() ); - case SERFMT), new Serfmt() ); - case CHART3DBARSHAPE), new Chart3DBarShape() ); - case FBI), new Fbi() ); - case BOPPOP), new Boppop() ); - case AXCENT), new Axcent() ); - case DAT), new Dat() ); - case PLOTGROWTH), new PlotGrowth() ); - case SIIINDEX), new SiIndex() ); - case GELFRAME), new GelFrame() ); - case BOPPOPCUSTOM), new BoppopCustom() ); - case FONTBASIS), new FontBasis() ); - - // PivotTable Records - case SXVIEW), new Sxview() ); - case SXFORMAT), new Sxformat() ); - case SXLI), new Sxli() ); - case SXVI), new Sxvi() ); - case SXVD), new Sxvd() ); - case SXIVD), new Sxivd() ); - - // Object and Picture Records - case PHONETIC), new Phonetic() ); - case MSODRAWING), new MSODrawing() ); - case MSODRAWINGGROUP), new MSODrawingGroup() ); - case MSODRAWINGSELECTION), new MSODrawingSelection() ); - - // Excel 9 Chart Records - case CHARTFRTINFO), new ChartFrtInfo() ); - case FRTWRAPPER), new FrtWrapper() ); - case STARTBLOCK), new StartBlock() ); - case ENDBLOCK), new EndBlock() ); - case STARTOBJECT), new StartObject() ); - case ENDOBJECT), new EndObject() ); - case CATLAB), new CatLab() ); - case YMULT), new YMult() ); - case SXVIEWLINK), new SxViewLink() ); - case PIVOTCHARTBITS), new PivotChartBits() ); - case FRTFONTLIST), new FrtFontList() ); - case PIVOTCHARTLINK), new PivotChartLink() ); - case DATALABEXTCONTENTS), new DataLabExtContents() ); - case DATALABEXT), new DataLabExt() ); - - records = Collections.unmodifiableMap( recmap ); - } -*/ - /* - * Create a ptg record from a name, will be init'ed elsewhere if needed. - * I am keeping this seperate from getBiffRecord for performance reasons. Why search - * through all the ptg/formula stuff every time you deal with a XLS record an vice-versa. Also, - * init'ing may be different. Small duplication of code, but I think it is worth it. - */ - public static Ptg getPtgRecord(String name) throws InvalidRecordException - { - for(int t=0;t", "PtgNE"}, + {"<=", "PtgLE"}, + {"<", "PtgLT"}, + {">=", "PtgGE"}, + {">", "PtgGT"}, + {"=", "PtgEQ"}, + {"!=", "PtgNE"}, + {"+", "PtgAdd"}, // moved to AFTER other operators + {"-", "PtgSub"}, + }; + // this is how you init a 2D array of Objects + public static String[][] ptgLookup = { + // 20070215 KSC: added constant strings to avoid PtgRef3d matches + {"#VALUE!", "PtgErr"}, + {"#NULL!", "PtgErr"}, + {"#DIV/0!", "PtgErr"}, + {"#VALUE!", "PtgErr"}, + {"#REF!", "PtgErr"}, + {"#NUM!", "PtgErr"}, + {"#N/A", "PtgErr"}, + // operators + {"(", "PtgParen"}, + {"*", "PtgMlt"}, + {"/", "PtgDiv"}, + {"^", "PtgPower"}, + {"&", "PtgConcat"}, + {"<>", "PtgNE"}, + {"<=", "PtgLE"}, + {"<", "PtgLT"}, + {">=", "PtgGE"}, + {">", "PtgGT"}, + {"=", "PtgEQ"}, + {"!=", "PtgNE"}, + //{" ","PtgIsect"}, intersection operator, need to work out how to use a space as operator-- SEE PTGMEMFUNC/MEMAREA -- only valid when parsing complex ranges + //{",","PtgUnion"}, problems matching as a separator in string parsing + //{":","PtgRange"}, // may have issues with ptgArea? + {"+", "PtgAdd"}, // moved to AFTER other operators + {"-", "PtgSub"}, + //operands + {"PtgStr", "PtgStr"}, + {"PtgNumber", "PtgNumber"}, + {"PtgInt", "PtgInt"}, + {"PtgRef", "PtgRef"}, + {"PtgArea", "PtgArea"}, + {"false", "PtgBool"}, + {"true", "PtgBool"}, + {"PtgArea3d", "PtgArea3d"}, + {"PtgRef3d", "PtgRef3d"}, + {"PtgArray", "PtgArray"}, + {"PtgMissArg", "PtgMissArg"}, + {"PtgMemFunc", "PtgMemFunc"}, + {"PtgAtr", "PtgAtr"}, + //functions + {"PtgFunc", "PtgFunc"}, + {"PtgFuncVar", "PtgFuncVar"}, + // unary prefix operators: see FormulaParser.splitString + {"u+", "PtgUPlus"}, + {"u-", "PtgUMinus"}, + {")", "PtgParen"}, + + + }; + + /* DO DIFFERENTLY SO TO AVOID HANGING OBJECT REFERENCES + static { + HashMap recmap = new HashMap(); + + // Most Frequent + case BLANK), new Blank() ); + case ROW), new Row() ); + case XF), new Xf() ); + case INDEX), new Index() ); + case COUNTRY), new Country() ); + case CALCMODE), new CalcMode() ); + case DIMENSIONS), new Dimensions() ); + case SELECTION), new Selection() ); + case DEFAULTROWHEIGHT), new DefaultRowHeight() ); + case DEFCOLWIDTH), new DefColWidth() ); + case DBCELL), new Dbcell() ); + case BOF), new Bof() ); + case BOUNDSHEET), new Boundsheet() ); + case EOF), new Eof() ); + case FORMAT), new Format() ); + case STYLE), new Style() ); + case PASSWORD), new Password() ); + case PALETTE), new Palette() ); + case ARRAY), new Array() ); + case BOOLERR), new Boolerr() ); + case EXTERNSHEET), new Externsheet() ); + case EXTERNNAME), new Externname() ); + case FORMULA), new Formula() ); + case LABEL), new Label() ); + case TXO), new Txo() ); + case CONTINUE), new Continue() ); + case SST), new Sst() ); + case GUTS), new Guts() ); + case EXTSST), new Extsst() ); + case HLINK), new Hlink() ); + case LABELSST), new Labelsst() ); + case NUMBER), new NumberRec() ); + case MERGEDCELLS), new Mergedcells() ); + case MULBLANK), new Mulblank() ); + case MULRK), new Mulrk() ); + case RK), new Rk() ); + case RSTRING), new Rstring() ); + case SHRFMLA), new Shrfmla() ); + case STRINGREC), new StringRec() ); + case SUPBOOK), new Supbook() ); + case DV), new Dv() ); + case DVAL), new Dval() ); + case SETUP), new Setup() ); + case HCENTER), new HCenter() ); + case VCENTER), new VCenter() ); + case LEFTMARGIN), new LeftMargin() ); + case RIGHTMARGIN), new RightMargin() ); + case TOPMARGIN), new TopMargin() ); + case BOTTOMMARGIN), new BottomMargin() ); + case PRINTGRID), new PrintGrid() ); + case PRINTROWCOL), new PrintRowCol() ); + case XCT), new Xct() ); + case CRN), new Crn() ); + case NOTE), new Note() ); + + // Named Ranges and References + case NAME), new Name() ); + + // Workbook Settings + case FONT), new Font() ); + case DSF), new Dsf() ); + case WINDOW1), new Window1() ); + case WINDOW2), new Window2() ); + case CODENAME), new Codename() ); + case PROTECT), new Protect() ); + case OBJPROTECT), new ObjProtect() ); + case SCENPROTECT), new ScenProtect() ); + case FEATHEADR), new FeatHeadr() ); + case PROT4REV), new Prot4rev() ); + case COLINFO), new Colinfo() ); + case USERSVIEWBEGIN), new Usersviewbegin() ); + case USERSVIEWEND), new Usersviewend() ); + case WSBOOL), new WsBool() ); + case BOOKBOOL), new BookBool() ); + case USR_EXCL), new UsrExcl() ); + case INTERFACE_HDR), new InterfaceHdr() ); + case RRD_INFO), new RrdInfo() ); + case RRD_HEAD), new RrdHead() ); + case FILE_LOCK), new FileLock() ); + case PLS), new Pls() ); + case HEADERREC), new Headerrec() ); + case DATE1904), new NineteenOhFour() ); + + // Sheet Settings + case OBJ), new Obj() ); + case OBPROJ), new Obproj() ); + case FOOTERREC), new Footerrec() ); + case TABID), new TabID() ); + case PANE), new Pane() ); + case SCL), new Scl() ); + + // Protection settings + case FILEPASS), new Filepass() ); + + // Conditional Formatting + case CF), new Cf() ); + case CONDFMT), new Condfmt() ); + + // Auto filter + case AUTOFILTER), new AutoFilter() ); + + // Chart Records + case CHART), new Chart() ); + case SERIES), new Series() ); + case SERIESTEXT), new SeriesText() ); + case SERIESLIST), new SeriesList() ); + case AI), new Ai() ); + case BEGIN), new Begin() ); + case END), new End() ); + case UNITS), new Units() ); + case CHART), new Chart() ); + case DATAFORMAT), new DataFormat() ); + case LINEFORMAT), new LineFormat() ); + case MARKERFORMAT), new MarkerFormat() ); + case AREAFORMAT), new AreaFormat() ); + case PIEFORMAT), new PieFormat() ); + case ATTACHEDLABEL), new AttachedLabel() ); + case CHARTFORMAT), new ChartFormat() ); + case LEGEND), new Legend() ); + case BAR), new Bar() ); + case LINE), new Line() ); + case PIE), new Pie() ); + case AREA), new Area() ); + case SCATTER), new Scatter() ); + case CHARTLINE), new ChartLine() ); + case AXIS), new Axis() ); + case TICK), new Tick() ); + case VALUERANGE), new ValueRange() ); + case CATSERRANGE), new CatserRange() ); + case AXISLINEFORMAT), new AxisLineFormat() ); + case CHARTFORMATLINK), new ChartFormatLink() ); + case DEFAULTTEXT), new DefaultText() ); + case TEXTDISP), new TextDisp() ); + case FONTX), new Fontx() ); + case OBJECTLINK), new ObjectLink() ); + case FRAME), new Frame() ); + case BEGIN), new Begin() ); + case END), new End() ); + case PLOTAREA), new PlotArea() ); + case THREED), new ThreeD() ); + case PICF), new Picf() ); + case DROPBAR), new Dropbar() ); + case RADAR), new Radar() ); + case SURFACE), new Surface() ); + case RADARAREA), new RadarArea() ); + case AXISPARENT), new AxisParent() ); + case LEGENDXN), new Legendxn() ); + case SHTPROPS), new ShtProps() ); + case SERTOCRT), new SerToCrt() ); + case AXESUSED), new Axesused() ); + case SBASEREF), new SbaseRef() ); + case SERPARENT), new SerParent() ); + case SERAUXTREND), new SerauxTrend() ); + case IFMT), new Ifmt() ); + case POS), new Pos() ); + case ALRUNS), new AlRuns() ); + case AI), new Ai() ); + case SERAUXERRBAR), new SerauxErrBar() ); + case SERFMT), new Serfmt() ); + case CHART3DBARSHAPE), new Chart3DBarShape() ); + case FBI), new Fbi() ); + case BOPPOP), new Boppop() ); + case AXCENT), new Axcent() ); + case DAT), new Dat() ); + case PLOTGROWTH), new PlotGrowth() ); + case SIIINDEX), new SiIndex() ); + case GELFRAME), new GelFrame() ); + case BOPPOPCUSTOM), new BoppopCustom() ); + case FONTBASIS), new FontBasis() ); + + // PivotTable Records + case SXVIEW), new Sxview() ); + case SXFORMAT), new Sxformat() ); + case SXLI), new Sxli() ); + case SXVI), new Sxvi() ); + case SXVD), new Sxvd() ); + case SXIVD), new Sxivd() ); + + // Object and Picture Records + case PHONETIC), new Phonetic() ); + case MSODRAWING), new MSODrawing() ); + case MSODRAWINGGROUP), new MSODrawingGroup() ); + case MSODRAWINGSELECTION), new MSODrawingSelection() ); + + // Excel 9 Chart Records + case CHARTFRTINFO), new ChartFrtInfo() ); + case FRTWRAPPER), new FrtWrapper() ); + case STARTBLOCK), new StartBlock() ); + case ENDBLOCK), new EndBlock() ); + case STARTOBJECT), new StartObject() ); + case ENDOBJECT), new EndObject() ); + case CATLAB), new CatLab() ); + case YMULT), new YMult() ); + case SXVIEWLINK), new SxViewLink() ); + case PIVOTCHARTBITS), new PivotChartBits() ); + case FRTFONTLIST), new FrtFontList() ); + case PIVOTCHARTLINK), new PivotChartLink() ); + case DATALABEXTCONTENTS), new DataLabExtContents() ); + case DATALABEXT), new DataLabExt() ); + + records = Collections.unmodifiableMap( recmap ); + } + */ + /* + * Create a ptg record from a name, will be init'ed elsewhere if needed. + * I am keeping this seperate from getBiffRecord for performance reasons. Why search + * through all the ptg/formula stuff every time you deal with a XLS record an vice-versa. Also, + * init'ing may be different. Small duplication of code, but I think it is worth it. + */ + public static Ptg getPtgRecord(String name) throws InvalidRecordException { + for (int t = 0; t < ptgLookup.length; t++) { + if (ptgLookup[t][0].equalsIgnoreCase(name)) { + try { + String classname = ptgLookup[t][1]; + return (Ptg) Class.forName("io.starter.formats.XLS.formulas." + classname).newInstance(); + } catch (Exception e) { + throw new InvalidRecordException("ERROR: Creating Record: " + name + "failed: " + e.toString()); + } + } + } + return null; + } + + + /** + * Get an instance of the record type corresponding to the given opcode. + * + * @param opcode the BIFF8 record opcode to be resolved + * @return an instance of the class corresponding to the given opcode + * or an XLSRecord if the opcode is unknown + * @throws RuntimeException if instantiation of the record fails + */ + public static BiffRec getBiffRecord(short opcode) { + // TRY THIS: + BiffRec record = null; + try { + switch (opcode) { + case BLANK: + record = new Blank(); + break; + case ROW: + record = new Row(); + break; + case XF: + record = new Xf(); + break; + case INDEX: + record = new Index(); + break; + case COUNTRY: + record = new Country(); + break; + case CALCMODE: + record = new CalcMode(); + break; + case DIMENSIONS: + record = new Dimensions(); + break; + case SELECTION: + record = new Selection(); + break; + case DEFAULTROWHEIGHT: + record = new DefaultRowHeight(); + break; + case DEFCOLWIDTH: + record = new DefColWidth(); + break; + case DBCELL: + record = new Dbcell(); + break; + case BOF: + record = new Bof(); + break; + case BOUNDSHEET: + record = new Boundsheet(); + break; + case EOF: + record = new Eof(); + break; + case FORMAT: + record = new Format(); + break; + case STYLE: + record = new Style(); + break; + case PASSWORD: + record = new Password(); + break; + case PALETTE: + record = new Palette(); + break; + case ARRAY: + record = new Array(); + break; + case BOOLERR: + record = new Boolerr(); + break; + case EXTERNSHEET: + record = new Externsheet(); + break; + case EXTERNNAME: + record = new Externname(); + break; + case FORMULA: + record = new Formula(); + break; + case LABEL: + record = new Label(); + break; + case TXO: + record = new Txo(); + break; + case CONTINUE: + record = new Continue(); + break; + case SST: + record = new Sst(); + break; + case GUTS: + record = new Guts(); + break; + case EXTSST: + record = new Extsst(); + break; + case HLINK: + record = new Hlink(); + break; + case LABELSST: + record = new Labelsst(); + break; + case NUMBER: + record = new NumberRec(); + break; + case MERGEDCELLS: + record = new Mergedcells(); + break; + case MULBLANK: + record = new Mulblank(); + break; + case MULRK: + record = new Mulrk(); + break; + case RK: + record = new Rk(); + break; + case RSTRING: + record = new Rstring(); + break; + case SHRFMLA: + record = new Shrfmla(); + break; + case STRINGREC: + record = new StringRec(); + break; + case SUPBOOK: + record = new Supbook(); + break; + case DV: + record = new Dv(); + break; + case DVAL: + record = new Dval(); + break; + case SETUP: + record = new Setup(); + break; + case HCENTER: + record = new HCenter(); + break; + case VCENTER: + record = new VCenter(); + break; + case LEFTMARGIN: + record = new LeftMargin(); + break; + case RIGHTMARGIN: + record = new RightMargin(); + break; + case TOPMARGIN: + record = new TopMargin(); + break; + case BOTTOMMARGIN: + record = new BottomMargin(); + break; + case PRINTGRID: + record = new PrintGrid(); + break; + case PRINTROWCOL: + record = new PrintRowCol(); + break; + case XCT: + record = new Xct(); + break; + case CRN: + record = new Crn(); + break; + case NOTE: + record = new Note(); + break; + + // Named Ranges and References + case NAME: + record = new Name(); + break; + + // Workbook Settings + case FONT: + record = new Font(); + break; + case DSF: + record = new Dsf(); + break; + case WINDOW1: + record = new Window1(); + break; + case WINDOW2: + record = new Window2(); + break; + case PLV: + record = new PLV(); + break; + case CODENAME: + record = new Codename(); + break; + case PROTECT: + record = new Protect(); + break; + case OBJPROTECT: + record = new ObjProtect(); + break; + case SCENPROTECT: + record = new ScenProtect(); + break; + case FEATHEADR: + record = new FeatHeadr(); + break; + case PROT4REV: + record = new Prot4rev(); + break; + case COLINFO: + record = new Colinfo(); + break; + case USERSVIEWBEGIN: + record = new Usersviewbegin(); + break; + case USERSVIEWEND: + record = new Usersviewend(); + break; + case WSBOOL: + record = new WsBool(); + break; + case BOOKBOOL: + record = new BookBool(); + break; + case USR_EXCL: + record = new UsrExcl(); + break; + case INTERFACE_HDR: + record = new InterfaceHdr(); + break; + case RRD_INFO: + record = new RrdInfo(); + break; + case RRD_HEAD: + record = new RrdHead(); + break; + case FILE_LOCK: + record = new FileLock(); + break; + case PLS: + record = new Pls(); + break; + case HEADERREC: + record = new Headerrec(); + break; + case DATE1904: + record = new NineteenOhFour(); + break; + + // Sheet Settings + case OBJ: + record = new Obj(); + break; + case OBPROJ: + record = new Obproj(); + break; + case FOOTERREC: + record = new Footerrec(); + break; + case TABID: + record = new TabID(); + break; + case PANE: + record = new Pane(); + break; + case SCL: + record = new Scl(); + break; + + // Conditional Formatting + case CF: + record = new Cf(); + break; + case CONDFMT: + record = new Condfmt(); + break; + + // Auto filter + case AUTOFILTER: + record = new AutoFilter(); + break; + + // Chart Records + case CHART: + record = new Chart(); + break; + case SERIES: + record = new Series(); + break; + case SERIESTEXT: + record = new SeriesText(); + break; + case SERIESLIST: + record = new SeriesList(); + break; + case AI: + record = new Ai(); + break; + case UNITS: + record = new Units(); + break; + case DATAFORMAT: + record = new DataFormat(); + break; + case LINEFORMAT: + record = new LineFormat(); + break; + case MARKERFORMAT: + record = new MarkerFormat(); + break; + case AREAFORMAT: + record = new AreaFormat(); + break; + case PIEFORMAT: + record = new PieFormat(); + break; + case ATTACHEDLABEL: + record = new AttachedLabel(); + break; + case CHARTFORMAT: + record = new ChartFormat(); + break; + case LEGEND: + record = new Legend(); + break; + case BAR: + record = new Bar(); + break; + case LINE: + record = new Line(); + break; + case PIE: + record = new Pie(); + break; + case AREA: + record = new Area(); + break; + case SCATTER: + record = new Scatter(); + break; + case CHARTLINE: + record = new ChartLine(); + break; + case AXIS: + record = new Axis(); + break; + case TICK: + record = new Tick(); + break; + case VALUERANGE: + record = new ValueRange(); + break; + case CATSERRANGE: + record = new CatserRange(); + break; + case AXISLINEFORMAT: + record = new AxisLineFormat(); + break; + case CHARTFORMATLINK: + record = new ChartFormatLink(); + break; + case DEFAULTTEXT: + record = new DefaultText(); + break; + case TEXTDISP: + record = new TextDisp(); + break; + case FONTX: + record = new Fontx(); + break; + case OBJECTLINK: + record = new ObjectLink(); + break; + case FRAME: + record = new Frame(); + break; + case BEGIN: + record = new Begin(); + break; + case END: + record = new End(); + break; + case PLOTAREA: + record = new PlotArea(); + break; + case THREED: + record = new ThreeD(); + break; + case PICF: + record = new Picf(); + break; + case DROPBAR: + record = new Dropbar(); + break; + case RADAR: + record = new Radar(); + break; + case SURFACE: + record = new Surface(); + break; + case RADARAREA: + record = new RadarArea(); + break; + case AXISPARENT: + record = new AxisParent(); + break; + case LEGENDXN: + record = new Legendxn(); + break; + case SHTPROPS: + record = new ShtProps(); + break; + case SERTOCRT: + record = new SerToCrt(); + break; + case AXESUSED: + record = new Axesused(); + break; + case SBASEREF: + record = new SbaseRef(); + break; + case SERPARENT: + record = new SerParent(); + break; + case SERAUXTREND: + record = new SerauxTrend(); + break; + case IFMT: + record = new Ifmt(); + break; + case POS: + record = new Pos(); + break; + case ALRUNS: + record = new AlRuns(); + break; + case SERAUXERRBAR: + record = new SerauxErrBar(); + break; + case SERFMT: + record = new Serfmt(); + break; + case CHART3DBARSHAPE: + record = new Chart3DBarShape(); + break; + case FBI: + record = new Fbi(); + break; + case BOPPOP: + record = new Boppop(); + break; + case AXCENT: + record = new Axcent(); + break; + case DAT: + record = new Dat(); + break; + case PLOTGROWTH: + record = new PlotGrowth(); + break; + case SIIINDEX: + record = new SiIndex(); + break; + case GELFRAME: + record = new GelFrame(); + break; + case BOPPOPCUSTOM: + record = new BoppopCustom(); + break; + case FONTBASIS: + record = new FontBasis(); + break; + + // PivotTable Records + case SXVIEW: + record = new Sxview(); + break; + case TABLESTYLES: + record = new TableStyles(); + break; + case SXFORMAT: + record = new Sxformat(); + break; + case SXLI: + record = new Sxli(); + break; + case SXVI: + record = new Sxvi(); + break; + case SXVD: + record = new Sxvd(); + break; + case SXIVD: + record = new Sxivd(); + break; + case SXSTREAMID: + record = new SxStreamID(); + break; + case SXVS: + record = new SxVS(); + break; + case SXADDL: + record = new SxAddl(); + break; + case SXVDEX: + record = new SxVdEX(); + break; + case SXPI: + record = new SxPI(); + break; + case SXDI: + record = new SxDI(); + break; + case SXDB: + record = new SxDB(); + break; + case SXFDB: + record = new SxFDB(); + break; + case SXDBEX: + record = new SXDBEx(); + break; + case SXFDBTYPE: + record = new SXFDBType(); + break; + case SXSTRING: + record = new SXString(); + break; + case SXNUM: + record = new SXNum(); + break; + case SXDBB: + record = new SxDBB(); + break; + case SXEX: + record = new SxEX(); + break; + case QSISXTAG: + record = new QsiSXTag(); + break; + case SXVIEWEX9: + record = new SxVIEWEX9(); + break; + case DCONREF: + record = new DConRef(); + break; + case DCONNAME: + record = new DConName(); + break; + case DCONBIN: + record = new DConBin(); + break; + + + // Object and Picture Records + case PHONETIC: + record = new Phonetic(); + break; + case MSODRAWING: + record = new MSODrawing(); + break; + case MSODRAWINGGROUP: + record = new MSODrawingGroup(); + break; + case MSODRAWINGSELECTION: + record = new MSODrawingSelection(); + break; + + // Excel 9 Chart Records + case CHARTFRTINFO: + record = new ChartFrtInfo(); + break; + case FRTWRAPPER: + record = new FrtWrapper(); + break; + case STARTBLOCK: + record = new StartBlock(); + break; + case ENDBLOCK: + record = new EndBlock(); + break; + case STARTOBJECT: + record = new StartObject(); + break; + case ENDOBJECT: + record = new EndObject(); + break; + case CATLAB: + record = new CatLab(); + break; + case YMULT: + record = new YMult(); + break; + case SXVIEWLINK: + record = new SxViewLink(); + break; + case PIVOTCHARTBITS: + record = new PivotChartBits(); + break; + case FRTFONTLIST: + record = new FrtFontList(); + break; + case PIVOTCHARTLINK: + record = new PivotChartLink(); + break; + case DATALABEXTCONTENTS: + record = new DataLabExtContents(); + break; + case DATALABEXT: + record = new DataLabExt(); + break; + case CRTLAYOUT12A: + record = new CrtLayout12A(); + break; + case CRTLAYOUT12: + record = new CrtLayout12(); + break; + default: + record = new XLSRecord(); + } + record.setOpcode(opcode); + } catch (Exception e) { + throw new RuntimeException("failed to instantiate record", e); + } + return record; + } + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSSubstream.java b/src/main/java/io/starter/formats/XLS/XLSSubstream.java index 6b6da5f..2e0e803 100644 --- a/src/main/java/io/starter/formats/XLS/XLSSubstream.java +++ b/src/main/java/io/starter/formats/XLS/XLSSubstream.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,28 +23,29 @@ package io.starter.formats.XLS; -/** Classes implementing this interface need to be able to - stream the bytes of their contained records to the calling - class. - +/** + * Classes implementing this interface need to be able to + * stream the bytes of their contained records to the calling + * class. + * * @see WorkBook * @see WorkSheet */ -public interface XLSSubstream -{ +public interface XLSSubstream { String getName(); + String getSubstreamTypeName(); - + short getSubstreamType(); - + byte[] stream(); - - short WK_GLOBALS = 0x5; - short VB_MODULE = 0x6; - short WK_WORKSHEET = 0x10; - short WK_CHART = 0x20; + + short WK_GLOBALS = 0x5; + short VB_MODULE = 0x6; + short WK_WORKSHEET = 0x10; + short WK_CHART = 0x20; short WK_MACROSHEET = 0x40; - short WK_FILE = 0x100; + short WK_FILE = 0x100; } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Xct.java b/src/main/java/io/starter/formats/XLS/Xct.java index d09c5e2..16ceb9f 100644 --- a/src/main/java/io/starter/formats/XLS/Xct.java +++ b/src/main/java/io/starter/formats/XLS/Xct.java @@ -2,26 +2,26 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ /** - * + * */ package io.starter.formats.XLS; @@ -29,32 +29,34 @@ /** * XCT CRN Count (0059h)
                - * -

                + *
                + 

                  * 	This record stores the number of immediately following Crn records. 
                  * 	These records are used to store the cell contents of external references.
                  *
                -	offset  size 	contents
                -	---
                -	0 		2 		Number of following CRN records
                -	2 		2 		Index into sheet table of the involved SUPBOOK record
                -	

                - * -*/ + offset size contents + --- + 0 2 Number of following CRN records + 2 2 Index into sheet table of the involved SUPBOOK record +

                + * + */ public class Xct extends XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5112701341711255711L; - private int nCRNs; // number of External Cell References CRN record, similar to EXTERNNAME - private int supBookIndex; - public void init(){ + /** + * serialVersionUID + */ + private static final long serialVersionUID = -5112701341711255711L; + private int nCRNs; // number of External Cell References CRN record, similar to EXTERNNAME + private int supBookIndex; + + public void init() { super.init(); - nCRNs= ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); - supBookIndex= ByteTools.readShort(this.getByteAt(2),this.getByteAt(3)); + nCRNs = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + supBookIndex = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + } + + public String toString() { + return "XTC: nCRNS=" + nCRNs + " SupBook Index: " + supBookIndex; } - public String toString() { - return "XTC: nCRNS="+ nCRNs + " SupBook Index: " + supBookIndex; - } } diff --git a/src/main/java/io/starter/formats/XLS/Xf.java b/src/main/java/io/starter/formats/XLS/Xf.java index a77dbaf..d57ffb2 100644 --- a/src/main/java/io/starter/formats/XLS/Xf.java +++ b/src/main/java/io/starter/formats/XLS/Xf.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,50 +22,49 @@ */ package io.starter.formats.XLS; -import java.awt.Color; - +import io.starter.OpenXLS.FormatHandle; import io.starter.formats.OOXML.Fill; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; -import io.starter.OpenXLS.FormatHandle; +import java.awt.Color; + + +/** + * XF: Extended Format (E0h)
                + * The XF record stores formatting properties. + *

                + * If fStyle bit is true, then the XF is a style XF, otherwise + * it is a BiffRec XF. Cells and Styles both contain ixfe pointers + * which correspond to their associated XF record. + * + *

                + * BiffRec XF Record
                + *
                + * offset  Bits   MASK     name        contents
                + * ---
                + * 0       15-0   0xFFFF   ifnt        Index to the FONT record.
                + * 2       15-0   0xFFFF   ifmt        Index to the FORMAT record.
                + * 4       0      0x0001   fLocked     =1 if the cell is locked.
                + * 1      0x0002   fHidden     =1 if the cell formula is hidden (value still shown)
                + * 2      0x0004   fStyle      =0 for cell XF.
                + * =1 for style XF.
                + *
                + * ~~~ additional option flags omitted ~~~
                + *
                + * 
                + * + * @see SST + * @see LABELSST + * @see EXTSST + */ -/** XF: Extended Format (E0h)
                - The XF record stores formatting properties. - - If fStyle bit is true, then the XF is a style XF, otherwise - it is a BiffRec XF. Cells and Styles both contain ixfe pointers - which correspond to their associated XF record. - -
                -	BiffRec XF Record
                -    
                -	offset  Bits   MASK     name        contents
                -	---    
                -	0       15-0   0xFFFF   ifnt        Index to the FONT record.
                -	2       15-0   0xFFFF   ifmt        Index to the FORMAT record.
                -	4       0      0x0001   fLocked     =1 if the cell is locked.
                -			1      0x0002   fHidden     =1 if the cell formula is hidden (value still shown)
                -			2      0x0004   fStyle      =0 for cell XF.
                -										=1 for style XF.
                -
                -	~~~ additional option flags omitted ~~~
                -    
                -	
                - - * @see SST - * @see LABELSST - * @see EXTSST -*/ - - -public class Xf extends io.starter.formats.XLS.XLSRecord -{ - +public class Xf extends io.starter.formats.XLS.XLSRecord { + private static final long serialVersionUID = -419388613530529316L; - int tableidx = -1; + int tableidx = -1; // Shared variables. private short ifnt; private short ifmt; @@ -85,7 +84,7 @@ public class Xf extends io.starter.formats.XLS.XLSRecord private short fShrinkToFit = 0; private short fMergeCell = 0; private short iReadingOrder; - private short fAtrNum = 0; + private short fAtrNum = 0; private short fAtrFnt = 0; private short fAtrAlc = 0; private short fAtrBdr = 0; @@ -106,208 +105,224 @@ public class Xf extends io.starter.formats.XLS.XLSRecord private short icvFore; private short icvBack; private short fSxButton = 0; - private short icvColorFlag = 0; + private short icvColorFlag = 0; int Iflag = 0; - byte mystery; - public final static int NDEFAULTXFS= 20; - private String pat = null; - /** OOXML fill, if any */ - public Fill fill= null; // ugly that it's public ... + byte mystery; + public final static int NDEFAULTXFS = 20; + private String pat = null; + /** + * OOXML fill, if any + */ + public Fill fill = null; // ugly that it's public ... // These should only be populated for boundsheet transferral issues. private Font myFont; private Format myFormat; - private short useCount= 0; // KSC: added 20121003 to keep track of xf usage by biffrecs - - public Xf(){ - //empty constructor - } - - /** create a new Xf with pointer to its font - */ - public Xf(int f){ - byte[] bl = { 0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32}; - this.setOpcode(XF); - this.setLength((short)(bl.length)); + private short useCount = 0; // KSC: added 20121003 to keep track of xf usage by biffrecs + + public Xf() { + //empty constructor + } + + /** + * create a new Xf with pointer to its font + */ + public Xf(int f) { + byte[] bl = {0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32}; + this.setOpcode(XF); + this.setLength((short) (bl.length)); this.setData(bl); this.setFont(f); - this.init(); - } - - /** create a new Xf with pointer to its font and workbook set - */ - public Xf(int f, WorkBook wkbook){ - byte[] bl = { 0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32}; - this.setOpcode(XF); - this.setLength((short)(bl.length)); + this.init(); + } + + /** + * create a new Xf with pointer to its font and workbook set + */ + public Xf(int f, WorkBook wkbook) { + byte[] bl = {0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32}; + this.setOpcode(XF); + this.setLength((short) (bl.length)); this.setData(bl); - super.setWorkBook(wkbook); // set workbook but don't insert rec or add to xfrecs + super.setWorkBook(wkbook); // set workbook but don't insert rec or add to xfrecs this.setFont(f); - this.init(); + this.init(); } - + /** * constructor which takes a Font object + a workbook * useful for cloning xf's from other workbooks - * @param f font + * + * @param f font * @param wkbook */ public Xf(Font f, WorkBook wkbook) { - byte[] bl = { 0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32}; - this.setOpcode(XF); - this.setLength((short)(bl.length)); - myFont= f; + byte[] bl = {0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32}; + this.setOpcode(XF); + this.setLength((short) (bl.length)); + myFont = f; System.arraycopy(ByteTools.shortToLEBytes((short) f.getIdx()), 0, bl, 0, 2); this.setData(bl); - super.setWorkBook(wkbook); // set workbook but don't insert rec or add to xfrecs - this.init(); - } - - /** - * Set the workbook for this XF - * - * This can get called multiple times. This results in a disparity within - * xf counting in workbook. - */ - public void setWorkBook(WorkBook b){ - super.setWorkBook(b); - } - - /** - * Create a string representation of the Xf - */ - public String toString(){ - String f= "unknown"; //Handle missing formats - try { f= this.getFormatPattern(); } catch (Exception e) {;} - String thisToString= - " format:" + f + " fill:" + this.getFillPattern() + - " fg:" + this.getForegroundColor() + - " bg:" + this.getBackgroundColor() + - " border:["+ - this.getTopBorderLineStyle() +"-"+this.getTopBorderColor()+":"+ - this.getLeftBorderLineStyle() +"-"+this.getLeftBorderColor()+":"+ - this.getBottomBorderLineStyle() +"-"+this.getBottomBorderColor()+":"+ - this.getRightBorderLineStyle() +"-"+this.getRightBorderColor()+"]" + - "W:" + this.getWrapText() + - "R:" + this.getRotation() + - "H:" + this.getHorizontalAlignment()+ "V:" + this.getVerticalAlignment() + - "I:" + this.getIndent() + - "L:" + this.isLocked() + - "F:" + this.isFormulaHidden() + - "D:" + this.getRightToLeftReadingOrder(); - return this.getFont().toString() + thisToString; - } - + super.setWorkBook(wkbook); // set workbook but don't insert rec or add to xfrecs + this.init(); + } + + /** + * Set the workbook for this XF + *

                + * This can get called multiple times. This results in a disparity within + * xf counting in workbook. + */ + public void setWorkBook(WorkBook b) { + super.setWorkBook(b); + } + + /** + * Create a string representation of the Xf + */ + public String toString() { + String f = "unknown"; //Handle missing formats + try { + f = this.getFormatPattern(); + } catch (Exception e) { + } + String thisToString = + " format:" + f + " fill:" + this.getFillPattern() + + " fg:" + this.getForegroundColor() + + " bg:" + this.getBackgroundColor() + + " border:[" + + this.getTopBorderLineStyle() + "-" + this.getTopBorderColor() + ":" + + this.getLeftBorderLineStyle() + "-" + this.getLeftBorderColor() + ":" + + this.getBottomBorderLineStyle() + "-" + this.getBottomBorderColor() + ":" + + this.getRightBorderLineStyle() + "-" + this.getRightBorderColor() + "]" + + "W:" + this.getWrapText() + + "R:" + this.getRotation() + + "H:" + this.getHorizontalAlignment() + "V:" + this.getVerticalAlignment() + + "I:" + this.getIndent() + + "L:" + this.isLocked() + + "F:" + this.isFormulaHidden() + + "D:" + this.getRightToLeftReadingOrder(); + return this.getFont().toString() + thisToString; + } + /** * inc # records using this xf */ - public void incUseCount() { useCount++; } + public void incUseCount() { + useCount++; + } + /** * dec # records using this xf */ - public void decUseCoount() { useCount--; } + public void decUseCoount() { + useCount--; + } + /** * return # records using this xf + * * @return */ - public short getUseCount() { return useCount; } - + public short getUseCount() { + return useCount; + } + /** * Populates the myFont and myFormat variables to be held onto - * when the xf record is serialized for boundsheet transfer - * + * when the xf record is serialized for boundsheet transfer */ protected void populateForTransfer() { myFont = this.getFont(); myFormat = this.getWorkBook().getFormat(ifmt); this.getData(); } - - - public boolean getMerged(){ - if(fMergeCell == 1)return true; - else return false; - } - - - // marginal! - public void setMerged(boolean mgd){ - byte[] rkdata = this.getData(); - rkdata[9] = (byte)0x78; // 0xf4 ? - if (DEBUGLEVEL > 1)Logger.logInfo("Xf The merge style bit is: " + fMergeCell); - - } - - /** The XF record can either be a style XF or a Cell XF. - */ - public void init(){ - super.init(); - ifnt = ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); - ifnt = (short)(ifnt & 0xffff); - ifmt = ByteTools.readShort(this.getByteAt(2),this.getByteAt(3)); - ifmt = (short)(ifmt & 0xffff); - - + + + public boolean getMerged() { + return fMergeCell == 1; + } + + + // marginal! + public void setMerged(boolean mgd) { + byte[] rkdata = this.getData(); + rkdata[9] = (byte) 0x78; // 0xf4 ? + if (DEBUGLEVEL > 1) Logger.logInfo("Xf The merge style bit is: " + fMergeCell); + + } + + /** + * The XF record can either be a style XF or a Cell XF. + */ + public void init() { + super.init(); + ifnt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + ifnt = (short) (ifnt & 0xffff); + ifmt = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + ifmt = (short) (ifmt & 0xffff); + + short flag = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - // is the cell locked? - if ((flag & 0x1) == 0x1){ - fLocked = 0x1; - }else{ - fLocked = 0; - } - // is the cell hidden? - if ((flag & 0x2) == 0x2){ - fHidden = 0x1; - }else{ - fHidden = 0; - } - - // is it a cell rec or a style rec? - if ((flag & 0x4) == 0x4){ - fStyle = 1; - }else{ - fStyle = 0; - } - if ((flag & 0x8) == 0x0008) - f123Prefix = 0x1; - ixfParent = (short)((flag & 0xFFF0) >> 4); - - - initXF(); - - pat= null; // ensure reset if xf has changed - if(DEBUGLEVEL> DEBUG_LOW)Logger.logInfo("Xf.init() ifnt: " + ifnt - + " ifmt: " + ifmt + ":" + - this.toString() - + " border: " - + "l:"+ this.getLeftBorderColor() + ":" - + "b:"+ this.getBottomBorderColor() + ":" - + "r:"+ this.getRightBorderColor() + ":" - + "t:"+ this.getTopBorderColor() + ":"); -} - - /** - * read and interpret bytes 6-18) - */ - void initXF(){ - short flag; - - // bytes 6, 7: alignment, rotation, text break - flag = ByteTools.readShort(this.getByteAt(6),this.getByteAt(7)); - alc = (short)(flag & 0x7); - if ((flag & 0x8) == 0x8) fWrap = 1; - alcV = (short)((flag & 0x70) >> 4); - trot = (short)((flag & 0xFF00) >> 8); - - // byte 8: indent, reading order, shrink - flag= this.getByteAt(8); - cIndent = (short)(flag & 0xF); - if ((flag & 0x10) == 0x10) fShrinkToFit = 1; - if ((flag & 0x20) == 0x20) fMergeCell = 1; - if (DEBUGLEVEL > 5)Logger.logInfo("Xf The merge cell bit is: " + fMergeCell + " and the int is " + flag); - - iReadingOrder = (short)((flag & 0xC0));// >> 6); // reading order is byte 7-6 mask 0xCO - // USED_ATTRIB: bits 7-2 of byte 9 - flag= this.getByteAt(9); + // is the cell locked? + if ((flag & 0x1) == 0x1) { + fLocked = 0x1; + } else { + fLocked = 0; + } + // is the cell hidden? + if ((flag & 0x2) == 0x2) { + fHidden = 0x1; + } else { + fHidden = 0; + } + + // is it a cell rec or a style rec? + if ((flag & 0x4) == 0x4) { + fStyle = 1; + } else { + fStyle = 0; + } + if ((flag & 0x8) == 0x0008) + f123Prefix = 0x1; + ixfParent = (short) ((flag & 0xFFF0) >> 4); + + + initXF(); + + pat = null; // ensure reset if xf has changed + if (DEBUGLEVEL > DEBUG_LOW) Logger.logInfo("Xf.init() ifnt: " + ifnt + + " ifmt: " + ifmt + ":" + + this.toString() + + " border: " + + "l:" + this.getLeftBorderColor() + ":" + + "b:" + this.getBottomBorderColor() + ":" + + "r:" + this.getRightBorderColor() + ":" + + "t:" + this.getTopBorderColor() + ":"); + } + + /** + * read and interpret bytes 6-18) + */ + void initXF() { + short flag; + + // bytes 6, 7: alignment, rotation, text break + flag = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + alc = (short) (flag & 0x7); + if ((flag & 0x8) == 0x8) fWrap = 1; + alcV = (short) ((flag & 0x70) >> 4); + trot = (short) ((flag & 0xFF00) >> 8); + + // byte 8: indent, reading order, shrink + flag = this.getByteAt(8); + cIndent = (short) (flag & 0xF); + if ((flag & 0x10) == 0x10) fShrinkToFit = 1; + if ((flag & 0x20) == 0x20) fMergeCell = 1; + if (DEBUGLEVEL > 5) Logger.logInfo("Xf The merge cell bit is: " + fMergeCell + " and the int is " + flag); + + iReadingOrder = (short) ((flag & 0xC0));// >> 6); // reading order is byte 7-6 mask 0xCO + // USED_ATTRIB: bits 7-2 of byte 9 + flag = this.getByteAt(9); /* for all these flags, a cleared bit means use Parent Style XF attribute if set, means the attributes of THIS xf is used bit mask meaning @@ -319,821 +334,861 @@ void initXF(){ 4 10H Flag for background area style 5 20H Flag for cell protection (cell locked and formula hidden) */ - if ((flag & 0x4) == 0x4) fAtrNum = 1; // number format - if ((flag & 0x8) == 0x8) fAtrFnt = 1; // font - if ((flag & 0x10) == 0x10) fAtrAlc = 1; // alignment (h + v) text wrap rotation direction indent - if ((flag & 0x20) == 0x20) fAtrBdr = 1; // border lines - if ((flag & 0x40) == 0x40) fAtrPat = 1; // background format pattern - if ((flag & 0x80) == 0x80) fAtrProt = 1; // cell protection - - // BORDER Section - flag = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - dgLeft = (short)(flag & 0xF); - dgRight = (short)((flag & 0xF0) >> 4); - dgTop = (short)((flag & 0xF00) >> 8); - dgBottom = (short)((flag & 0xF000) >> 12); - - flag = ByteTools.readShort(this.getByteAt(12),this.getByteAt(13)); - icvLeft = (short)(flag & 0x7f); - icvRight = (short)((flag & 0x3F80) >> 7); - grbitDiag = (short)((flag & 0xC000) >> 15); - - // bytes 14-17 color and fill - Iflag = ByteTools.readInt(this.getByteAt(14),this.getByteAt(15),this.getByteAt(16),this.getByteAt(17)); - icvTop = (short)(Iflag & 0x7F); - icvBottom = (short)((Iflag & 0x3F80) >> 7); - icvDiag = (short)((Iflag & 0x1FC000) >> 14); - dgDiag = (short)((Iflag & 0x1E00000) >> 21); - mystery = (byte) ((Iflag & 0x3800000) >> 25); - fls = (short)((Iflag & 0xFC000000) >> 26); // fill pattern - - if (DEBUGLEVEL > 5 && icvTop>0)Logger.logInfo("Xf The cell outline is true"); - // bytes 18, 19: fill pattern colors - icvColorFlag = ByteTools.readShort(this.getByteAt(18),this.getByteAt(19)); - icvFore = (short)(icvColorFlag & 0x7F); // = Pattern Color - icvBack = (short)((icvColorFlag & 0x3F80) >> 7); // = Pattern Background Color - if ((icvColorFlag & 0x4000) == 0x4000) fSxButton = 1; - - // Logger.logInfo(io.starter.OpenXLS.ExcelTools.getRecordByteDef(this)); - } - /** returns the associated Font record for this XF - */ - public Font getFont(){ - if (myFont!=null)return myFont; - myFont= this.getWorkBook().getFont(ifnt); - return myFont; - } - - /** returns whether this Format is a Date - * + if ((flag & 0x4) == 0x4) fAtrNum = 1; // number format + if ((flag & 0x8) == 0x8) fAtrFnt = 1; // font + if ((flag & 0x10) == 0x10) fAtrAlc = 1; // alignment (h + v) text wrap rotation direction indent + if ((flag & 0x20) == 0x20) fAtrBdr = 1; // border lines + if ((flag & 0x40) == 0x40) fAtrPat = 1; // background format pattern + if ((flag & 0x80) == 0x80) fAtrProt = 1; // cell protection + + // BORDER Section + flag = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); + dgLeft = (short) (flag & 0xF); + dgRight = (short) ((flag & 0xF0) >> 4); + dgTop = (short) ((flag & 0xF00) >> 8); + dgBottom = (short) ((flag & 0xF000) >> 12); + + flag = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + icvLeft = (short) (flag & 0x7f); + icvRight = (short) ((flag & 0x3F80) >> 7); + grbitDiag = (short) ((flag & 0xC000) >> 15); + + // bytes 14-17 color and fill + Iflag = ByteTools.readInt(this.getByteAt(14), this.getByteAt(15), this.getByteAt(16), this.getByteAt(17)); + icvTop = (short) (Iflag & 0x7F); + icvBottom = (short) ((Iflag & 0x3F80) >> 7); + icvDiag = (short) ((Iflag & 0x1FC000) >> 14); + dgDiag = (short) ((Iflag & 0x1E00000) >> 21); + mystery = (byte) ((Iflag & 0x3800000) >> 25); + fls = (short) ((Iflag & 0xFC000000) >> 26); // fill pattern + + if (DEBUGLEVEL > 5 && icvTop > 0) Logger.logInfo("Xf The cell outline is true"); + // bytes 18, 19: fill pattern colors + icvColorFlag = ByteTools.readShort(this.getByteAt(18), this.getByteAt(19)); + icvFore = (short) (icvColorFlag & 0x7F); // = Pattern Color + icvBack = (short) ((icvColorFlag & 0x3F80) >> 7); // = Pattern Background Color + if ((icvColorFlag & 0x4000) == 0x4000) fSxButton = 1; + + // Logger.logInfo(io.starter.OpenXLS.ExcelTools.getRecordByteDef(this)); + } + + /** + * returns the associated Font record for this XF + */ + public Font getFont() { + if (myFont != null) return myFont; + myFont = this.getWorkBook().getFont(ifnt); + return myFont; + } + + /** + * returns whether this Format is a Date + *

                * Needs to be revisited. Currently I am only returning true for the standard "built in" dates - */ - public boolean isDatePattern(){ - - // Check the format ID against all known date formats. Why do we do - // this instead of letting it be caught by the string matching below? - for(int x = 0;x-1||myfmt.indexOf("yy")>-1||myfmt.indexOf("dd")>-1)return true; - return false; - } - - /** - * Parses an escaped xml format pattern (from ooxml) and returns an io.starter.OpenXLS compatible - * pattern. - * - * This method certainly has weaknesses, but my intention is that if it is not a fairly standard format and/or - * we are not sure how to parse it we should leave the existent format intact soas to not break read/write operations - * - * Oddly enough, excel seems to be able to handle biff8 patterns, in my testing so far there has been no need - * to reencode, that could obviously change... - * - * @param xmlFormatPattern - * @return compatible biff8 formatPattern - */ + String myfmt = fmt.getFormat().toLowerCase(); + return isDatePattern(myfmt); + } + + public static boolean isDatePattern(String myfmt) { + // Search for the format string in the list of known date formats + for (int x = 0; x < FormatConstants.DATE_FORMATS.length; x++) { + if (FormatConstants.DATE_FORMATS[x][0].equals(myfmt)) { + return true; + } + } + + // check for string patterns that only exist within date records (as far as we know, may need refining) + return myfmt.indexOf("mm") > -1 || myfmt.indexOf("yy") > -1 || myfmt.indexOf("dd") > -1; + } + + /** + * Parses an escaped xml format pattern (from ooxml) and returns an io.starter.OpenXLS compatible + * pattern. + *

                + * This method certainly has weaknesses, but my intention is that if it is not a fairly standard format and/or + * we are not sure how to parse it we should leave the existent format intact soas to not break read/write operations + *

                + * Oddly enough, excel seems to be able to handle biff8 patterns, in my testing so far there has been no need + * to reencode, that could obviously change... + * + * @param xmlFormatPattern + * @return compatible biff8 formatPattern + */ public static String unescapeFormatPattern(String xmlFormatPattern) { // strip escaping for currency pattern. Probably should explore all currency types and do an iteration xmlFormatPattern = xmlFormatPattern.replace("\"$\"", "$"); - + // separator between positive/negative - xmlFormatPattern = xmlFormatPattern.replace("_);", ";"); - + xmlFormatPattern = xmlFormatPattern.replace("_);", ";"); + // unescape parens - xmlFormatPattern = xmlFormatPattern.replace("\\(", "("); - xmlFormatPattern = xmlFormatPattern.replace("\\)", ")"); - return xmlFormatPattern; - } - - - /** returns whether this Format is a Currency - */ - public boolean isCurrencyPattern(){ - if(pat == null) { - setFormatPattern(getFormatPattern()); - } - for(int x = 0;x + * case insensitive pattern match is performed... + */ + public String getFormatPattern() { + if (pat != null) return pat; String[][] fmts = FormatConstantsImpl.getBuiltinFormats(); - for (int x=0; x < fmts.length; x++) { - if (this.ifmt==Integer.parseInt(fmts[x][1], 16)) { - pat= fmts[x][0]; - return pat; - } - } - - Format fmt = this.getWorkBook().getFormat(ifmt); - if (fmt!=null) { - pat= fmt.toString(); - return fmt.getFormat(); - } - return null; - } - - /** Sets the number format pattern for this format. - */ - public void setFormatPattern (String pattern) { - this.pat = pattern; - - if (this.getWorkBook() == null) throw new IllegalStateException( - "attempting to set format pattern but workbook is null" ); - - this.setFormat( addFormatPattern( getWorkBook(), pattern ) ); - } - - /** Ensures that the given format pattern exists on the given workbook. - * @param book the workbook to which the pattern should belong - * @param pattern the number format pattern to ensure exists - * @return the format ID of the given format pattern - */ - public static short addFormatPattern (WorkBook book, String pattern) { - short ifmt = -1; - - // Look up the pattern on the workbook - ifmt = book.getFormatId( pattern ); - - // If the pattern is unknown, create and add a Format record - if (ifmt == -1) { - Format format = new Format( book, pattern ); - ifmt = format.getIfmt(); - } - - return ifmt; - } - - /** set the pointer to the XF's Format in the WorkBook - */ - public void setFormat(short ifm){ - ifmt = (short)ifm; - byte[] nef = ByteTools.shortToLEBytes(ifmt); - this.getData()[2] = nef[0]; - this.getData()[3] = nef[1]; - this.pat= null; // 20080228 KSC: flag to re-input - } - - - /** set the pointer to the XF's Font in the WorkBook - */ - public void setFont(int ifn){ - ifnt = (short)ifn; - byte[] nef = ByteTools.shortToLEBytes(ifnt); - this.getData()[0] = nef[0]; - this.getData()[1] = nef[1]; + for (int x = 0; x < fmts.length; x++) { + if (this.ifmt == Integer.parseInt(fmts[x][1], 16)) { + pat = fmts[x][0]; + return pat; + } + } + + Format fmt = this.getWorkBook().getFormat(ifmt); + if (fmt != null) { + pat = fmt.toString(); + return fmt.getFormat(); + } + return null; + } + + /** + * Sets the number format pattern for this format. + */ + public void setFormatPattern(String pattern) { + this.pat = pattern; + + if (this.getWorkBook() == null) throw new IllegalStateException( + "attempting to set format pattern but workbook is null"); + + this.setFormat(addFormatPattern(getWorkBook(), pattern)); + } + + /** + * Ensures that the given format pattern exists on the given workbook. + * + * @param book the workbook to which the pattern should belong + * @param pattern the number format pattern to ensure exists + * @return the format ID of the given format pattern + */ + public static short addFormatPattern(WorkBook book, String pattern) { + short ifmt = -1; + + // Look up the pattern on the workbook + ifmt = book.getFormatId(pattern); + + // If the pattern is unknown, create and add a Format record + if (ifmt == -1) { + Format format = new Format(book, pattern); + ifmt = format.getIfmt(); + } + + return ifmt; + } + + /** + * set the pointer to the XF's Format in the WorkBook + */ + public void setFormat(short ifm) { + ifmt = ifm; + byte[] nef = ByteTools.shortToLEBytes(ifmt); + this.getData()[2] = nef[0]; + this.getData()[3] = nef[1]; + this.pat = null; // 20080228 KSC: flag to re-input + } + + + /** + * set the pointer to the XF's Font in the WorkBook + */ + public void setFont(int ifn) { + ifnt = (short) ifn; + byte[] nef = ByteTools.shortToLEBytes(ifnt); + this.getData()[0] = nef[0]; + this.getData()[1] = nef[1]; // reset the pointer for xf's and font's brought from other workbooks. - if (this.getWorkBook()!=null) { + if (this.getWorkBook() != null) { myFont = this.getWorkBook().getFont(ifn); } - } + } - /** - * Set myFont in XF to the same as Workbook's + /** + * Set myFont in XF to the same as Workbook's */ public void setMyFont(Font f) { - myFont = f; - } - - /** set the Fill Pattern for this Format - */ - public void setPattern(int t){ - fls = (short)t; - this.updatePattern(); - if (fill!=null) - fill.setFillPattern(t); - } - - /** - * get the format pattern for this particular XF + myFont = f; + } + + /** + * set the Fill Pattern for this Format + */ + public void setPattern(int t) { + fls = (short) t; + this.updatePattern(); + if (fill != null) + fill.setFillPattern(t); + } + + /** + * get the format pattern for this particular XF + * * @return - */ - public int getFillPattern(){ - if (fill!=null) return fill.getFillPatternInt(); - return fls; - } - - + */ + public int getFillPattern() { + if (fill != null) return fill.getFillPatternInt(); + return fls; + } + + // BORDER SECTION - - public short getBottomBorderLineStyle(){ - return this.dgBottom; - } - - public short getTopBorderLineStyle(){ - return this.dgTop; - } - - public short getLeftBorderLineStyle(){ - return this.dgLeft; - } - - public short getRightBorderLineStyle(){ - return this.dgRight; - } - - - public short getDiagBorderLineStyle(){ - return this.dgDiag; - } - - /** set the Top Border Color for this Format - */ - public void setTopBorderColor(int t){ - if (t==0) t= 64; // 20080118 KSC - icvTop = (short)t; - updateBorderColors(); - } - - public int getTopBorderColor(){ - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvTop==64) return 65; - return (int) icvTop; - } - - /** set the Bottom Border Color for this Format - */ - public void setBottomBorderColor(int t){ - if (t==0) t= 64; // 20080118 KSC - icvBottom = (short)t; - updateBorderColors(); - } - - public int getBottomBorderColor(){ - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvBottom==64) return 65; - return (int)icvBottom; - } - - /** set the Left Border Color for this Format - */ - public void setLeftBorderColor(int t){ - if (t==0) t= 64; // 20080118 KSC - icvLeft = (short)t; - updateBorderColors(); - } - - public int getLeftBorderColor(){ - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvLeft==64) return 65; - return (int)icvLeft; - } - - /** set the Right Border Color for this Format - */ - public void setRightBorderColor(int t){ - if (t==0) t= 64; // 20080118 KSC - icvRight = (short) t; - updateBorderColors(); - - } - - public short getRightBorderColor(){ - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvRight==64) return 65; - return icvRight; - } - - /** set the diagonal Border Color for this Format - */ - public void setDiagBorderColor(int t){ - if (t==0) t= 64; // 20080118 KSC - icvDiag= (short) t; - updateBorderColors(); - - } - - /** - * get the diagonal border color - * @return - */ - public short getDiagBorderColor(){ - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvDiag==64) return 65; - return icvDiag; - } - - /** set the Left Border Color for this Format - */ - public void setLeftBorderColor(short t){ - if (t==0) t= 64; // 20080118 KSC - icvLeft = t; - updateBorderColors(); - } - - /** set the diagonal border for this Format - */ - public void setBorderDiag(int t){ - Iflag = 0; - Iflag |= ((short) icvTop); - Iflag |= ((short) icvBottom << 7); - Iflag |= ((short) t << 14); - Iflag |= ((short) dgDiag << 21); - Iflag |= ((short) mystery << 25); - Iflag |= ((short) fls << 26); - this.updatePattern(); - } - - /** set the border line style for this Format - */ - public void setBorderLineStyle(short t){ - dgLeft = t; - dgRight = t; - dgTop = t; - dgBottom = t; - this.updateBorders(); - } - - /** - * set border line styles via array of ints representing border styles - * order= top, left, bottom, right [diagonal] - * @param b int[] - */ - public void setAllBorderLineStyles(int[] b) { - try { - if (b[0]>-1) dgTop= (short) b[0]; - if (b[1]>-1) dgLeft= (short) b[1]; - if (b[2]>-1) dgBottom= (short) b[2]; - if (b[3]>-1) dgRight= (short) b[3]; - if (b[4]>-1) dgDiag= (short) b[4]; - } catch (ArrayIndexOutOfBoundsException e) {} - this.updateBorders(); - } - - /** - * set all border colors via an array of ints representing border color ints - * order= top, left, bottom, right, [diagonal] - * @param b int[] - */ - public void setAllBorderColors(int[] b) { - try { - if (b[0]>-1) icvTop = (short) b[0]; - if (b[1]>-1) icvLeft = (short) b[1]; - if (b[2]>-1) icvBottom = (short) b[2]; - if (b[3]>-1) icvRight = (short) b[3]; - if (b[4]>-1) icvDiag= (short) b[4]; - } catch (ArrayIndexOutOfBoundsException e) {} - this.updateBorderColors(); - } - - public void setTopBorderLineStyle(short t){ - dgTop = t; - this.updateBorders(); - } - public void setBottomBorderLineStyle(short t){ - dgBottom = t; - this.updateBorders(); - } - public void setLeftBorderLineStyle(short t){ - dgLeft = t; - this.updateBorders(); - } - public void setRightBorderLineStyle(short t){ - dgRight = t; - this.updateBorders(); - } - - public void updateBorders(){ - short borderflag= 0; - borderflag= (short) dgLeft; - borderflag= (short)((borderflag | (dgRight) << 4)); - borderflag= (short)((borderflag | (dgTop) << 8)); - borderflag= (short)((borderflag | (dgBottom) << 12)); - //byte[] rkdata = this.getData(); - byte[] bords = ByteTools.shortToLEBytes(borderflag); - this.getData()[10] = bords[0]; - this.getData()[11] = bords[1]; - setAttributeFlag(); - } - - /** - * removes all borders for the style - */ - public void removeBorders(){ - this.dgBottom = 0; - this.dgTop = 0; - this.dgDiag = 0; - this.dgLeft = 0; - this.dgRight = 0; - this.dgBottom = 0; - this.updateBorders(); - } - - public void updateBorderColors(){ - setAttributeFlag(); - } - - public void updatePattern(){ + + public short getBottomBorderLineStyle() { + return this.dgBottom; + } + + public short getTopBorderLineStyle() { + return this.dgTop; + } + + public short getLeftBorderLineStyle() { + return this.dgLeft; + } + + public short getRightBorderLineStyle() { + return this.dgRight; + } + + + public short getDiagBorderLineStyle() { + return this.dgDiag; + } + + /** + * set the Top Border Color for this Format + */ + public void setTopBorderColor(int t) { + if (t == 0) t = 64; // 20080118 KSC + icvTop = (short) t; + updateBorderColors(); + } + + public int getTopBorderColor() { + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + if (icvTop == 64) return 65; + return (int) icvTop; + } + + /** + * set the Bottom Border Color for this Format + */ + public void setBottomBorderColor(int t) { + if (t == 0) t = 64; // 20080118 KSC + icvBottom = (short) t; + updateBorderColors(); + } + + public int getBottomBorderColor() { + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + if (icvBottom == 64) return 65; + return (int) icvBottom; + } + + /** + * set the Left Border Color for this Format + */ + public void setLeftBorderColor(int t) { + if (t == 0) t = 64; // 20080118 KSC + icvLeft = (short) t; + updateBorderColors(); + } + + public int getLeftBorderColor() { + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + if (icvLeft == 64) return 65; + return (int) icvLeft; + } + + /** + * set the Right Border Color for this Format + */ + public void setRightBorderColor(int t) { + if (t == 0) t = 64; // 20080118 KSC + icvRight = (short) t; + updateBorderColors(); + + } + + public short getRightBorderColor() { + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + if (icvRight == 64) return 65; + return icvRight; + } + + /** + * set the diagonal Border Color for this Format + */ + public void setDiagBorderColor(int t) { + if (t == 0) t = 64; // 20080118 KSC + icvDiag = (short) t; + updateBorderColors(); + + } + + /** + * get the diagonal border color + * + * @return + */ + public short getDiagBorderColor() { + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + if (icvDiag == 64) return 65; + return icvDiag; + } + + /** + * set the Left Border Color for this Format + */ + public void setLeftBorderColor(short t) { + if (t == 0) t = 64; // 20080118 KSC + icvLeft = t; + updateBorderColors(); + } + + /** + * set the diagonal border for this Format + */ + public void setBorderDiag(int t) { + Iflag = 0; + Iflag |= icvTop; + Iflag |= (icvBottom << 7); + Iflag |= ((short) t << 14); + Iflag |= (dgDiag << 21); + Iflag |= ((short) mystery << 25); + Iflag |= (fls << 26); + this.updatePattern(); + } + + /** + * set the border line style for this Format + */ + public void setBorderLineStyle(short t) { + dgLeft = t; + dgRight = t; + dgTop = t; + dgBottom = t; + this.updateBorders(); + } + + /** + * set border line styles via array of ints representing border styles + * order= top, left, bottom, right [diagonal] + * + * @param b int[] + */ + public void setAllBorderLineStyles(int[] b) { + try { + if (b[0] > -1) dgTop = (short) b[0]; + if (b[1] > -1) dgLeft = (short) b[1]; + if (b[2] > -1) dgBottom = (short) b[2]; + if (b[3] > -1) dgRight = (short) b[3]; + if (b[4] > -1) dgDiag = (short) b[4]; + } catch (ArrayIndexOutOfBoundsException e) { + } + this.updateBorders(); + } + + /** + * set all border colors via an array of ints representing border color ints + * order= top, left, bottom, right, [diagonal] + * + * @param b int[] + */ + public void setAllBorderColors(int[] b) { + try { + if (b[0] > -1) icvTop = (short) b[0]; + if (b[1] > -1) icvLeft = (short) b[1]; + if (b[2] > -1) icvBottom = (short) b[2]; + if (b[3] > -1) icvRight = (short) b[3]; + if (b[4] > -1) icvDiag = (short) b[4]; + } catch (ArrayIndexOutOfBoundsException e) { + } + this.updateBorderColors(); + } + + public void setTopBorderLineStyle(short t) { + dgTop = t; + this.updateBorders(); + } + + public void setBottomBorderLineStyle(short t) { + dgBottom = t; + this.updateBorders(); + } + + public void setLeftBorderLineStyle(short t) { + dgLeft = t; + this.updateBorders(); + } + + public void setRightBorderLineStyle(short t) { + dgRight = t; + this.updateBorders(); + } + + public void updateBorders() { + short borderflag = 0; + borderflag = dgLeft; + borderflag = (short) ((borderflag | (dgRight) << 4)); + borderflag = (short) ((borderflag | (dgTop) << 8)); + borderflag = (short) ((borderflag | (dgBottom) << 12)); + //byte[] rkdata = this.getData(); + byte[] bords = ByteTools.shortToLEBytes(borderflag); + this.getData()[10] = bords[0]; + this.getData()[11] = bords[1]; + setAttributeFlag(); + } + + /** + * removes all borders for the style + */ + public void removeBorders() { + this.dgBottom = 0; + this.dgTop = 0; + this.dgDiag = 0; + this.dgLeft = 0; + this.dgRight = 0; + this.dgBottom = 0; + this.updateBorders(); + } + + public void updateBorderColors() { + setAttributeFlag(); + } + + public void updatePattern() { byte[] rkdata = this.getData(); short thisFlag = 0; - thisFlag |= ((short) icvLeft); - thisFlag |= ((short) icvRight << 7); - thisFlag |= ((short) grbitDiag << 14); + thisFlag |= icvLeft; + thisFlag |= (icvRight << 7); + thisFlag |= (grbitDiag << 14); byte[] bytes = ByteTools.shortToLEBytes(thisFlag); rkdata[12] = bytes[0]; rkdata[13] = bytes[1]; - - Iflag = 0; - Iflag |= ((short) icvTop); - Iflag |= ((short) icvBottom << 7); - Iflag |= ((short) icvDiag << 14); - Iflag |= ((short) dgDiag << 21); - Iflag |= ((short) mystery << 25); - Iflag |= ((short) fls << 26); - byte[] nef = ByteTools.cLongToLEBytes(Iflag); - rkdata[14] = nef[0]; - rkdata[15] = nef[1]; - rkdata[16] = nef[2]; - rkdata[17] = nef[3]; - // update format cache upon change - pat= null; - this.wkbook.updateFormatCache(this); - } - - /** set the Foreground Color for this Format - * THIS SETS THE BACKGROUND COLOR when PATTERN (fls) = PATTERN_SOLID - * THIS SETS THE PATTERN COLOR when PATTERN (fls) > PATTERN_SOLID - *
                "If the fill style is solid: When solid is specified, the - foreground color (fgColor) is the only color rendered, - even when a background color (bgColor) is also specified" - * icvFore==Pattern Background Color - * @param t best match index into 2003-style Color tabe - * @param clr java.awt.Color or null if use standard Excel 2003 Color Table - */ - public void setForeColor(int t, Color clr){ - icvColorFlag = 0; - icvColorFlag |= ((short)t); - icvColorFlag |= ((short)icvBack << 7); - if (clr!=null) { - if (!clr.equals(FormatHandle.COLORTABLE[t])) { // no exact match for color - if (fill == null) - fill = new Fill(getFillPattern(), t, FormatHandle.colorToHexString(clr), icvBack, null, this.getWorkBook().getTheme()); - else - fill.setFgColor(t, FormatHandle.colorToHexString(clr)); - } - } else if (fill!=null) - fill.setFgColor(t); - this.updateColors(); - } - /** set the Background Color for this Format (when PATTERN - fls != PATTERN_SOLID) - * When PATTERN is PATTERN_SOLID, == 64 - * @param t best-match index into 2003-style Color table - * @param clr java.awt.Color or null if use standard Excel 2003 Color Table - */ - public void setBackColor(int t, Color clr){ - icvColorFlag = 0; - icvColorFlag |= ((short)icvFore); - icvColorFlag |= ((short)t << 7); - if (clr!=null) { - if (!clr.equals(FormatHandle.COLORTABLE[t])) { // no exact match for color - store custom color - if (fill == null) - fill= new Fill(getFillPattern(), icvFore, null, t, FormatHandle.colorToHexString(clr), this.getWorkBook().getTheme()); - else - fill.setBgColor(t, FormatHandle.colorToHexString(clr)); - } - } else if (fill!=null) - fill.setBgColor(t); - - this.updateColors(); - } - - void updateColors(){ - byte[] rkdata = this.getData(); - byte[] nef = ByteTools.shortToLEBytes(icvColorFlag); - rkdata[18] = nef[0]; - rkdata[19] = nef[1]; - icvFore = (short)(icvColorFlag & 0x7F); - icvBack = (short)((icvColorFlag & 0x3F80) >> 7); - // update format cache upon change - pat= null; - this.wkbook.updateFormatCache(this); - } - - /** - * PATTERN_SOLID is a special case where icvFore= the background color and icvBack=64. - "If the fill style is solid: When solid is specified, the - foreground color (fgColor) is the only color rendered, - even when a background color (bgColor) is also - specified" - */ - public static final int PATTERN_SOLID= 1; // was set to 4 but tht's wrong!! - -/** - * Sets the fill pattern to solid, which renders the background to 64=="the default fg color" - "If the fill style is solid: When solid is specified, the - foreground color (fgColor) is the only color rendered, - even when a background color (bgColor) is also - specified" - */ - public void setBackgroundSolid(){ - setPattern(PATTERN_SOLID); - setBackColor(64, null); - if (fill!=null) fill.setFillPattern(PATTERN_SOLID); - } - - public boolean isBackgroundSolid(){ - if (fill!=null) return fill.isBackgroundSolid(); - byte[] rkdata = this.getData(); - return (rkdata[17]==(byte)PATTERN_SOLID); - } - - - /** Sets the attribute flags for this xf record. These flags consist of - * // bit 8= fAtrProt - // 7= fAtrPat - // 6= fAtrBdr - // 5= fAtrAlc (Alignment) - // 4= fAtrFnt - // 3= fAtrNum - * - */ - private void setAttributeFlag(){ - setToCellXF(); - byte[] rkdata = this.getData(); - byte used_attrib = rkdata[9]; - byte borderFlag= (byte) ((dgBottom>0 || dgTop>0 || dgLeft>0 || dgRight>0 || dgDiag>0)?1:0); // if border is set - if (borderFlag==1) - used_attrib= (byte) (used_attrib | 0x20); // set bit # 6 - else - used_attrib= (byte) (used_attrib & 0xDF); // clear it - if (cIndent!=0 || iReadingOrder!=0 || alc!=0 || alcV!=0 || fWrap!=0 || trot!=0) // set bit # 5 - used_attrib= (byte) (used_attrib | 0x10); - else - used_attrib= (byte) (used_attrib & 0xEF); // clear it - - rkdata[9]= used_attrib; - fAtrNum= (short) ((used_attrib & 0x04)==0x04?1:0); - fAtrFnt= (short) ((used_attrib & 0x08)==0x08?1:0); - fAtrAlc= (short) ((used_attrib & 0x10)==0x10?1:0); - fAtrBdr= (short) ((used_attrib & 0x20)==0x20?1:0); - fAtrPat= (short) ((used_attrib & 0x40)==0x40?1:0); - fAtrProt= (short) ((used_attrib & 0x80)==0x80?1:0); - - - // must set color flag for borders or Excel will not like [BugTracker 2861] - if (dgTop > 0 && icvTop==0) - icvTop= 64; - if (dgBottom > 0 && icvBottom==0) - icvBottom= 64; - if (dgRight > 0 && icvRight==0) - icvRight= 64; - if (dgLeft > 0 && icvLeft==0) - icvLeft= 64; - if (dgDiag > 0 && icvDiag==0) - icvDiag= 64; - this.updatePattern(); - } - - /** - * Switch the record to a cell XF record - */ - public void setToCellXF() { - if (fStyle!=0) {// must set to cell xf (fStyle==0) as changes will not show [BugTracker 2861] - fStyle = 0; - byte flag= (byte) fLocked; - flag= (byte)((flag| (fHidden) << 1)); - this.getData()[4]= flag; - this.getData()[5]= 0; // upper bits are style parent rec index + + Iflag = 0; + Iflag |= icvTop; + Iflag |= (icvBottom << 7); + Iflag |= (icvDiag << 14); + Iflag |= (dgDiag << 21); + Iflag |= ((short) mystery << 25); + Iflag |= (fls << 26); + byte[] nef = ByteTools.cLongToLEBytes(Iflag); + rkdata[14] = nef[0]; + rkdata[15] = nef[1]; + rkdata[16] = nef[2]; + rkdata[17] = nef[3]; + // update format cache upon change + pat = null; + this.wkbook.updateFormatCache(this); + } + + /** + * set the Foreground Color for this Format + * THIS SETS THE BACKGROUND COLOR when PATTERN (fls) = PATTERN_SOLID + * THIS SETS THE PATTERN COLOR when PATTERN (fls) > PATTERN_SOLID + *
                "If the fill style is solid: When solid is specified, the + * foreground color (fgColor) is the only color rendered, + * even when a background color (bgColor) is also specified" + * icvFore==Pattern Background Color + * + * @param clr java.awt.Color or null if use standard Excel 2003 Color Table + * @param t best match index into 2003-style Color tabe + */ + public void setForeColor(int t, Color clr) { + icvColorFlag = 0; + icvColorFlag |= ((short) t); + icvColorFlag |= (icvBack << 7); + if (clr != null) { + if (!clr.equals(FormatHandle.COLORTABLE[t])) { // no exact match for color + if (fill == null) + fill = new Fill(getFillPattern(), t, FormatHandle.colorToHexString(clr), icvBack, null, this.getWorkBook().getTheme()); + else + fill.setFgColor(t, FormatHandle.colorToHexString(clr)); + } + } else if (fill != null) + fill.setFgColor(t); + this.updateColors(); + } + + /** + * set the Background Color for this Format (when PATTERN - fls != PATTERN_SOLID) + * When PATTERN is PATTERN_SOLID, == 64 + * + * @param clr java.awt.Color or null if use standard Excel 2003 Color Table + * @param t best-match index into 2003-style Color table + */ + public void setBackColor(int t, Color clr) { + icvColorFlag = 0; + icvColorFlag |= icvFore; + icvColorFlag |= ((short) t << 7); + if (clr != null) { + if (!clr.equals(FormatHandle.COLORTABLE[t])) { // no exact match for color - store custom color + if (fill == null) + fill = new Fill(getFillPattern(), icvFore, null, t, FormatHandle.colorToHexString(clr), this.getWorkBook().getTheme()); + else + fill.setBgColor(t, FormatHandle.colorToHexString(clr)); + } + } else if (fill != null) + fill.setBgColor(t); + + this.updateColors(); + } + + void updateColors() { + byte[] rkdata = this.getData(); + byte[] nef = ByteTools.shortToLEBytes(icvColorFlag); + rkdata[18] = nef[0]; + rkdata[19] = nef[1]; + icvFore = (short) (icvColorFlag & 0x7F); + icvBack = (short) ((icvColorFlag & 0x3F80) >> 7); + // update format cache upon change + pat = null; + this.wkbook.updateFormatCache(this); + } + + /** + * PATTERN_SOLID is a special case where icvFore= the background color and icvBack=64. + * "If the fill style is solid: When solid is specified, the + * foreground color (fgColor) is the only color rendered, + * even when a background color (bgColor) is also + * specified" + */ + public static final int PATTERN_SOLID = 1; // was set to 4 but tht's wrong!! + + /** + * Sets the fill pattern to solid, which renders the background to 64=="the default fg color" + * "If the fill style is solid: When solid is specified, the + * foreground color (fgColor) is the only color rendered, + * even when a background color (bgColor) is also + * specified" + */ + public void setBackgroundSolid() { + setPattern(PATTERN_SOLID); + setBackColor(64, null); + if (fill != null) fill.setFillPattern(PATTERN_SOLID); + } + + public boolean isBackgroundSolid() { + if (fill != null) return fill.isBackgroundSolid(); + byte[] rkdata = this.getData(); + return (rkdata[17] == (byte) PATTERN_SOLID); + } + + + /** + * Sets the attribute flags for this xf record. These flags consist of + * // bit 8= fAtrProt + * // 7= fAtrPat + * // 6= fAtrBdr + * // 5= fAtrAlc (Alignment) + * // 4= fAtrFnt + * // 3= fAtrNum + */ + private void setAttributeFlag() { + setToCellXF(); + byte[] rkdata = this.getData(); + byte used_attrib = rkdata[9]; + byte borderFlag = (byte) ((dgBottom > 0 || dgTop > 0 || dgLeft > 0 || dgRight > 0 || dgDiag > 0) ? 1 : 0); // if border is set + if (borderFlag == 1) + used_attrib = (byte) (used_attrib | 0x20); // set bit # 6 + else + used_attrib = (byte) (used_attrib & 0xDF); // clear it + if (cIndent != 0 || iReadingOrder != 0 || alc != 0 || alcV != 0 || fWrap != 0 || trot != 0) // set bit # 5 + used_attrib = (byte) (used_attrib | 0x10); + else + used_attrib = (byte) (used_attrib & 0xEF); // clear it + + rkdata[9] = used_attrib; + fAtrNum = (short) ((used_attrib & 0x04) == 0x04 ? 1 : 0); + fAtrFnt = (short) ((used_attrib & 0x08) == 0x08 ? 1 : 0); + fAtrAlc = (short) ((used_attrib & 0x10) == 0x10 ? 1 : 0); + fAtrBdr = (short) ((used_attrib & 0x20) == 0x20 ? 1 : 0); + fAtrPat = (short) ((used_attrib & 0x40) == 0x40 ? 1 : 0); + fAtrProt = (short) ((used_attrib & 0x80) == 0x80 ? 1 : 0); + + + // must set color flag for borders or Excel will not like [BugTracker 2861] + if (dgTop > 0 && icvTop == 0) + icvTop = 64; + if (dgBottom > 0 && icvBottom == 0) + icvBottom = 64; + if (dgRight > 0 && icvRight == 0) + icvRight = 64; + if (dgLeft > 0 && icvLeft == 0) + icvLeft = 64; + if (dgDiag > 0 && icvDiag == 0) + icvDiag = 64; + this.updatePattern(); + } + + /** + * Switch the record to a cell XF record + */ + public void setToCellXF() { + if (fStyle != 0) {// must set to cell xf (fStyle==0) as changes will not show [BugTracker 2861] + fStyle = 0; + byte flag = (byte) fLocked; + flag = (byte) ((flag | (fHidden) << 1)); + this.getData()[4] = flag; + this.getData()[5] = 0; // upper bits are style parent rec index } - } - - - /** - * @return Returns the ifnt. - */ - public short getIfnt() { - return ifnt; - } - /** - * @param ifnt The ifnt to set. - */ - public void setIfnt(short ifnt) { - this.ifnt = ifnt; - } - + } + + + /** + * @return Returns the ifnt. + */ + public short getIfnt() { + return ifnt; + } + + /** + * @param ifnt The ifnt to set. + */ + public void setIfnt(short ifnt) { + this.ifnt = ifnt; + } + public short getIfmt() { return ifmt; } - public void setHorizontalAlignment(int hAlign){ - alc = (short)hAlign; - updateAlignment(); - setAttributeFlag(); - } - - /** - * set the indent (1=3 spaces) - * @param indent - */ - public void setIndent(int indent) { // indent # = 3 spaces - cIndent= (short) indent; // mask = 0xF, 4 bits, - byte b= (byte) (this.getData()[8] & 0xF0); - b |= (cIndent); // 1st 4 bits - this.getData()[8]= b; - if (alc!=FormatConstants.ALIGN_LEFT || alc!=FormatConstants.ALIGN_RIGHT) // indent only valid for Left and Right (apparently - setHorizontalAlignment(FormatConstants.ALIGN_LEFT); - setAttributeFlag(); - } - - /** - * return the indent setting (1=3 spaces) - * @return - */ - public int getIndent() { - return cIndent; - } - - /** - * sets the Right to Left Text Direction or reading order of this style - * @param rtl possible values: - *
                0=Context Dependent - *
                1=Left-to-Right - *
                2=Right-to-Let - * - */ - public void setRightToLeftReadingOrder(int rtl) { - // iReadingOrder= bits 7-6 - // 00= According to Context - // 01= Left to Right (0x40) - // 10= Right to Left (0x80) - if (rtl==2) - iReadingOrder=0x80; - else if (rtl==1) - iReadingOrder=0x40; - else - iReadingOrder= 0; - byte b= this.getData()[8]; - b |= (iReadingOrder); - this.getData()[8]= b; - this.wkbook.updateFormatCache(this); - setAttributeFlag(); - } - - /** - * returns true if this style is set to Right-to-Left text direction (reading order) - * @return - */ - public int getRightToLeftReadingOrder() { - return (int)(iReadingOrder >> 6); - } - - public int getHorizontalAlignment(){ - return (int)alc; - } - - public void setWrapText(boolean wraptext){ - if (wraptext){ - fWrap = 1; - }else{ - fWrap = 0; - } - updateAlignment(); - setAttributeFlag(); - } - - public boolean getWrapText(){ - return ( fWrap == 1 ) ? true : false; - } - - public void setVerticalAlignment(int vAlign){ - alcV = (short)vAlign; - updateAlignment(); - setAttributeFlag(); - } - - public int getVerticalAlignment(){ - return (int)alcV; - } - - public void setRotation(int rot){ - trot = (short)rot; - updateAlignment(); - setAttributeFlag(); - } - - public int getRotation(){ - return trot; - } - - private void updateAlignment(){ - //short tempAlc = (short)(alc << 3); - short tempfWrap = (short)(fWrap << 3); - short tempAlcV = (short)(alcV << 4); - short tempTrot = (short)(trot << 8); - short res = 0x0; - res = (short)(res | alc); - res = (short)(res | tempfWrap); - res = (short)(res | tempAlcV); - res = (short)(res | tempTrot); - byte[] rkdata = this.getData(); - byte[] bords = ByteTools.shortToLEBytes(res); - rkdata[6] = bords[0]; - rkdata[7] = bords[1]; - // update format cache upon change - this.wkbook.updateFormatCache(this); - } - - /** + + public void setHorizontalAlignment(int hAlign) { + alc = (short) hAlign; + updateAlignment(); + setAttributeFlag(); + } + + /** + * set the indent (1=3 spaces) + * + * @param indent + */ + public void setIndent(int indent) { // indent # = 3 spaces + cIndent = (short) indent; // mask = 0xF, 4 bits, + byte b = (byte) (this.getData()[8] & 0xF0); + b |= (cIndent); // 1st 4 bits + this.getData()[8] = b; + if (alc != FormatConstants.ALIGN_LEFT || alc != FormatConstants.ALIGN_RIGHT) // indent only valid for Left and Right (apparently + setHorizontalAlignment(FormatConstants.ALIGN_LEFT); + setAttributeFlag(); + } + + /** + * return the indent setting (1=3 spaces) + * + * @return + */ + public int getIndent() { + return cIndent; + } + + /** + * sets the Right to Left Text Direction or reading order of this style + * + * @param rtl possible values: + *
                0=Context Dependent + *
                1=Left-to-Right + *
                2=Right-to-Let + */ + public void setRightToLeftReadingOrder(int rtl) { + // iReadingOrder= bits 7-6 + // 00= According to Context + // 01= Left to Right (0x40) + // 10= Right to Left (0x80) + if (rtl == 2) + iReadingOrder = 0x80; + else if (rtl == 1) + iReadingOrder = 0x40; + else + iReadingOrder = 0; + byte b = this.getData()[8]; + b |= (iReadingOrder); + this.getData()[8] = b; + this.wkbook.updateFormatCache(this); + setAttributeFlag(); + } + + /** + * returns true if this style is set to Right-to-Left text direction (reading order) + * + * @return + */ + public int getRightToLeftReadingOrder() { + return iReadingOrder >> 6; + } + + public int getHorizontalAlignment() { + return (int) alc; + } + + public void setWrapText(boolean wraptext) { + if (wraptext) { + fWrap = 1; + } else { + fWrap = 0; + } + updateAlignment(); + setAttributeFlag(); + } + + public boolean getWrapText() { + return fWrap == 1; + } + + public void setVerticalAlignment(int vAlign) { + alcV = (short) vAlign; + updateAlignment(); + setAttributeFlag(); + } + + public int getVerticalAlignment() { + return (int) alcV; + } + + public void setRotation(int rot) { + trot = (short) rot; + updateAlignment(); + setAttributeFlag(); + } + + public int getRotation() { + return trot; + } + + private void updateAlignment() { + //short tempAlc = (short)(alc << 3); + short tempfWrap = (short) (fWrap << 3); + short tempAlcV = (short) (alcV << 4); + short tempTrot = (short) (trot << 8); + short res = 0x0; + res = (short) (res | alc); + res = (short) (res | tempfWrap); + res = (short) (res | tempAlcV); + res = (short) (res | tempTrot); + byte[] rkdata = this.getData(); + byte[] bords = ByteTools.shortToLEBytes(res); + rkdata[6] = bords[0]; + rkdata[7] = bords[1]; + // update format cache upon change + this.wkbook.updateFormatCache(this); + } + + /** * Returns an XML fragment representing the XF backing the format Handle. The XF record is style information * associated with a cell. Font information/lookup is not included in this output so it can be used as a comparitor * style */ public String getXML() { StringBuffer sb = new StringBuffer(""); + sb.append(">"); Font myf = this.getFont(); // font info... - sb.append(""); // get the border.. sb.append(""); - if(getRightBorderLineStyle()!=0) { + if (getRightBorderLineStyle() != 0) { sb.append(""); - } - if(getBottomBorderLineStyle()!=0) { + } + if (getBottomBorderLineStyle() != 0) { sb.append(""); - } - if(getLeftBorderLineStyle()!=0) { + } + if (getLeftBorderLineStyle() != 0) { sb.append(""); - } - if(getTopBorderLineStyle()!=0) { + } + if (getTopBorderLineStyle() != 0) { sb.append(""); - } + } sb.append(""); - + // get the alignment.. sb.append(""); - + // get the background color - if(wkbook.colorTable[getForegroundColor()] != Color.WHITE) { + if (wkbook.colorTable[getForegroundColor()] != Color.WHITE) { sb.append(""); + FormatHandle.colorToHexString(wkbook.colorTable[getForegroundColor()]) + + "\"/>"); } - + sb.append(""); return sb.toString(); } - + /** * @return Returns the myFormat. */ public Format getFormat() { return myFormat; } + /** * @param myFormat The myFormat to set. */ public void setFormat(Format myFormat) { this.myFormat = myFormat; } - - /** get whether this cell formula is hidden - * + + /** + * get whether this cell formula is hidden + * * @return */ - public boolean isFormulaHidden(){ + public boolean isFormulaHidden() { return (this.fHidden == 0x1); } - - /** sets the cell formula as hidden - * + + /** + * sets the cell formula as hidden + * * @param hd */ - public void setFormulaHidden(boolean hd){ - if(hd) + public void setFormulaHidden(boolean hd) { + if (hd) this.fHidden = 0x1; else this.fHidden = 0x0; updateLockedHidden(); } - - /** get whether this is a locked Cell - * + + /** + * get whether this is a locked Cell + * * @return */ - public boolean isLocked(){ + public boolean isLocked() { return (this.fLocked == 0x1); } - + /** * return whether this cell is set to "shrink to fit" + * * @return */ public boolean isShrinkToFit() { - return (this.fShrinkToFit== 0x1); + return (this.fShrinkToFit == 0x1); } - + public void setShrinkToFit(boolean b) { - if (b) { - this.fShrinkToFit= 0x1; - this.getData()[9]|=0x10; - }else { - this.fShrinkToFit= 0x0; // turn off bit 4 - this.getData()[9]&=0xF7; // set bit 4 - } - } - /** sets the cell as locked - * + if (b) { + this.fShrinkToFit = 0x1; + this.getData()[9] |= 0x10; + } else { + this.fShrinkToFit = 0x0; // turn off bit 4 + this.getData()[9] &= 0xF7; // set bit 4 + } + } + + /** + * sets the cell as locked + * * @param lk */ - public void setLocked(boolean lk){ - if(lk) - this.fLocked = 0x1; + public void setLocked(boolean lk) { + if (lk) + this.fLocked = 0x1; else - this.fLocked = 0x0; + this.fLocked = 0x0; updateLockedHidden(); } - - + + /** - * * 2 2 XF type, cell protection, and parent style XF: - Bit Mask Contents - 2-0 0007H XF_TYPE_PROT – XF type, cell protection (see above) - 15-4 FFF0H Index to parent style XF (always FFFH in style XFs) - * * Bit Mask Contents - 0 01H 1 = Cell is locked - 1 02H 1 = Formula is hidden - 2 04H 0 = Cell XF; 1 = Style XF - - * + * 2-0 0007H XF_TYPE_PROT – XF type, cell protection (see above) + * 15-4 FFF0H Index to parent style XF (always FFFH in style XFs) + *

                + * Bit Mask Contents + * 0 01H 1 = Cell is locked + * 1 02H 1 = Formula is hidden + * 2 04H 0 = Cell XF; 1 = Style XF */ - private void updateLockedHidden(){ + private void updateLockedHidden() { + + short tempFL = (short) (fLocked << 0x0); + short tempFH = (short) (fHidden << 0x1); + short tempST = (short) (fStyle << 0x2); - short tempFL = (short)(fLocked << 0x0); - short tempFH = (short)(fHidden << 0x1); - short tempST = (short)(fStyle << 0x2); - - short flag = 0x0; - flag = (short)(flag | tempFL); - flag = (short)(flag | tempFH); - flag = (short)(flag | tempST); + short flag = 0x0; + flag = (short) (flag | tempFL); + flag = (short) (flag | tempFH); + flag = (short) (flag | tempST); - byte[] dx = this.getData(); - byte[] nef = ByteTools.shortToLEBytes(flag); - dx[4] = nef[0]; - dx[5] = nef[1]; - // update format cache upon change - pat= null; - this.wkbook.updateFormatCache(this); + byte[] dx = this.getData(); + byte[] nef = ByteTools.shortToLEBytes(flag); + dx[4] = nef[0]; + dx[5] = nef[1]; + // update format cache upon change + pat = null; + this.wkbook.updateFormatCache(this); } - - - - /** - * + + /** * @return */ - public boolean getStricken() { - if(myFont!=null)return myFont.getStricken(); + public boolean getStricken() { + if (myFont != null) return myFont.getStricken(); return false; - } + } + public void setStricken(boolean b) { - if(myFont!=null) myFont.setStricken(b); + if (myFont != null) myFont.setStricken(b); } - - - /** - * + + /** * @return */ - public boolean getItalic() { - if(myFont!=null)return myFont.getItalic(); + public boolean getItalic() { + if (myFont != null) return myFont.getItalic(); return false; - } + } + public void setItalic(boolean b) { - if(myFont!=null) myFont.setItalic(b); + if (myFont != null) myFont.setItalic(b); } - - - /** - * + + /** * @return */ - public boolean getUnderlined() { - if(myFont!=null)return myFont.getUnderlined(); + public boolean getUnderlined() { + if (myFont != null) return myFont.getUnderlined(); return false; - } + } + public void setUnderlined(boolean b) { - if(myFont!=null) myFont.setUnderlined(b); + if (myFont != null) myFont.setUnderlined(b); } - - /** - * + /** * @return */ - public boolean getBold() { - if(myFont!=null)return myFont.getBold(); + public boolean getBold() { + if (myFont != null) return myFont.getBold(); return false; } + public void setBold(boolean b) { - if(myFont!=null) myFont.setBold(b); + if (myFont != null) myFont.setBold(b); + } + + public int getIdx() { + return tableidx; } - - public int getIdx(){return tableidx;} - + /** * return truth of "this Xf rec is a style xf" + * * @return */ - public boolean isStyleXf() { return (fStyle==1); } + public boolean isStyleXf() { + return (fStyle == 1); + } + /** * clone the xf and add to streamer + * * @param xf * @return */ private static Xf cloneXf(Xf xf, WorkBook wkbook) { - Xf clone; - if (xf.getIdx()>-1) { // it's in the wb already - clone = new Xf(xf.ifnt, wkbook); - byte[] xfbytes = xf.getBytesAt(0, xf.getLength()-4); - clone.setData(xfbytes); - clone.init(); - } else { // xf hasn't been added to wb yet, no need to clone - clone= xf; - } - clone.fill= xf.fill; - clone.setToCellXF(); // changes will not be seen if fstyle bit is set TODO: is this correct in all cases??? - clone.tableidx= wkbook.insertXf(clone); + Xf clone; + if (xf.getIdx() > -1) { // it's in the wb already + clone = new Xf(xf.ifnt, wkbook); + byte[] xfbytes = xf.getBytesAt(0, xf.getLength() - 4); + clone.setData(xfbytes); + clone.init(); + } else { // xf hasn't been added to wb yet, no need to clone + clone = xf; + } + clone.fill = xf.fill; + clone.setToCellXF(); // changes will not be seen if fstyle bit is set TODO: is this correct in all cases??? + clone.tableidx = wkbook.insertXf(clone); return clone; } - + /** - * if xf parameter doesn't exist, create; if it does, create a new xf based on it - * @param xf original xf - * @param fontIdx font to link xf to - * @param wkbook - * @return new xf + * if xf parameter doesn't exist, create; if it does, create a new xf based on it + * + * @param xf original xf + * @param fontIdx font to link xf to + * @param wkbook + * @return new xf */ public static Xf updateXf(Xf xf, int fontIdx, WorkBook wkbook) { - if(xf== null) { - xf= new Xf(fontIdx, wkbook); - xf.tableidx= wkbook.insertXf(xf); // insert new xf into stream ... - return xf; - } - else { - xf= Xf.cloneXf(xf, wkbook); - } - return xf; - } - + if (xf == null) { + xf = new Xf(fontIdx, wkbook); + xf.tableidx = wkbook.insertXf(xf); // insert new xf into stream ... + return xf; + } else { + xf = Xf.cloneXf(xf, wkbook); + } + return xf; + } + /** * set the OOXML fill for this xf + * * @param f */ public void setFill(Fill f) { - this.fill= (Fill) f.cloneElement(); - fls= (short)this.fill.getFillPatternInt(); - icvFore= (short)this.fill.getFgColorAsInt(getWorkBook().getTheme()); - icvBack= (short)this.fill.getBgColorAsInt(getWorkBook().getTheme()); + this.fill = (Fill) f.cloneElement(); + fls = (short) this.fill.getFillPatternInt(); + icvFore = (short) this.fill.getFgColorAsInt(getWorkBook().getTheme()); + icvBack = (short) this.fill.getBgColorAsInt(getWorkBook().getTheme()); } - + /** * return the OOXML fill for this xf, if any */ - public Fill getFill() { return this.fill; } - + public Fill getFill() { + return this.fill; + } + /** * clear out object references in prep for closing workbook */ public void close() { - super.close(); - this.myFont.close(); - this.myFormat= null; + super.close(); + this.myFont.close(); + this.myFormat = null; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Ai.java b/src/main/java/io/starter/formats/XLS/charts/Ai.java index e3b2e34..833bde7 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Ai.java +++ b/src/main/java/io/starter/formats/XLS/charts/Ai.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,785 +29,789 @@ */ package io.starter.formats.XLS.charts; -import java.util.Iterator; -import java.util.List; -import java.util.Stack; - -import io.starter.formats.XLS.ExpressionParser; -import io.starter.formats.XLS.Externsheet; -import io.starter.formats.XLS.Formula; -import io.starter.formats.XLS.FormulaNotFoundException; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.formulas.GenericPtg; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.formats.XLS.formulas.PtgArea3d; -import io.starter.formats.XLS.formulas.PtgMemFunc; -import io.starter.formats.XLS.formulas.PtgMystery; -import io.starter.formats.XLS.formulas.PtgParen; -import io.starter.formats.XLS.formulas.PtgRef; -import io.starter.formats.XLS.formulas.PtgRef3d; +import io.starter.formats.XLS.*; +import io.starter.formats.XLS.formulas.*; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; -/** Ai: Linked Chart Data (1051h)
                - - This record specifies linked series data or text. - - Offset Name Size Contents - -- - 4 id 1 index id: (0=title or text, 1=series vals, 2=series cats + (3=bubbles) - 5 rt 1 reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) - 6 grbit 2 flags - 8 ifmt 2 Index to number format (used if fCustomIfmt= true) - 10 cce 2 size of rgce (in bytes) - 12 rgce var Parsed formula of link - -The grbit field contains the following option flags. - - 0 0 0x1 fCustomIfmt TRUE if this object has a custom number format; FALSE if number format is linked to data source - a 1 0x2 (reserved) Reserved; must be zero - 0 5-2 0x3C st Source type (always zero) - a 7-6 0xCO (reserved) Reserved; must be zero - 1 7-0 0xFF (reserved) Reserved; must be zero - - - - * @see Formula - * @see Chart +import java.util.Iterator; +import java.util.List; +import java.util.Stack; + +/** + * Ai: Linked Chart Data (1051h)
                + *

                + * This record specifies linked series data or text. + *

                + * Offset Name Size Contents + * -- + * 4 id 1 index id: (0=title or text, 1=series vals, 2=series cats + (3=bubbles) + * 5 rt 1 reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) + * 6 grbit 2 flags + * 8 ifmt 2 Index to number format (used if fCustomIfmt= true) + * 10 cce 2 size of rgce (in bytes) + * 12 rgce var Parsed formula of link + *

                + * The grbit field contains the following option flags. + *

                + * 0 0 0x1 fCustomIfmt TRUE if this object has a custom number format; FALSE if number format is linked to data source + * a 1 0x2 (reserved) Reserved; must be zero + * 0 5-2 0x3C st Source type (always zero) + * a 7-6 0xCO (reserved) Reserved; must be zero + * 1 7-0 0xFF (reserved) Reserved; must be zero + * + * + * + * @see Formula + * @see Chart */ public final class Ai extends GenericChartObject implements ChartObject { - /** - * - */ - private static final long serialVersionUID = -6647823755603289012L; - private Stack expression; - protected int id = -1, ifmt = -1, cce = -1; - private short grbit = -1, rt = -1; - private boolean fCustomIfmt = false; - // private CompatibleVector xlsrecs = new - // CompatibleVector(); - private SeriesText st = null; - // define the type of Ai record - public static final int TYPE_TEXT = 0; - public static final int TYPE_VALS = 1; - public static final int TYPE_CATEGORIES = 2; - public static final int TYPE_BUBBLES = 3; - - /** - * This is for storage of the boundsheet name when the ai record is being moved from - * one workbook to another. In these cases, populate this value, and pull it back out - * to locate the cxti and reset the reerences - * @return the name of the original boundsheet this record is associated with. - */ - private String boundName = ""; - private String origSheetName = ""; - private int boundXti = -1; - - /** - * returns the bound sheet name - must be called after populateForTransfer - * @return String - */ // 20080116 KSC: Changed name for clarity - public String getBoundName() { - return boundName; - } - - /** - * returns the sheet reference index - must call after populateForTransfer - * @return - */ - public int getBoundXti() { - return boundXti; - } - - /** - * Sets the boundsheet name for the data referenced in this AI. - * + sets the xti of the boundsheet reference (necessary upon sheet copy/move) - * Does not currently support multi-boundsheet references. - * @see updateSheetRef - */ - public void populateForTransfer(String origSheetName) { - if ("".equals(boundName)) { - this.origSheetName = origSheetName; // 20080708 KSC: trap original - // sheet name for - // updateSheetRefs comparison - for (int t = 0; t < expression.size(); t++) { - Ptg p = expression.get(t); - if (p instanceof PtgArea3d) { - PtgArea3d pt = (PtgArea3d) p; - try { - boundName = pt.getSheetName(); // original boundname + - // xti sheet ref - boundXti = pt.getIxti(); - } catch (Exception e) { - Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); - } - } else if (p instanceof PtgRef3d) { - PtgRef3d pt = (PtgRef3d) p; - try { - boundName = pt.getSheetName(); // original boundname + - // xti sheet ref - boundXti = pt.getIxti(); - } catch (Exception e) { - Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); - } - } else if (p instanceof PtgMemFunc) { // 20091015 KSC: for - // non-contiguous ranges - Ptg ptg = ((PtgMemFunc) p).getFirstloc(); - if (ptg instanceof PtgRef3d) { - PtgRef3d pr = (PtgRef3d) ptg; - try { - boundName = pr.getSheetName(); // original boundname - // + xti sheet ref - boundXti = pr.getIxti(); - } catch (Exception e) { - Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); - } - } else { // should be a PtgArea3d - PtgArea3d pr = (PtgArea3d) ptg; - try { - boundName = pr.getSheetName(); // original boundname - // + xti sheet ref - boundXti = pr.getIxti(); - } catch (Exception e) { - Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); - } - } - } - } - } - } - - public void setLegend(String newLegend) { - this.setRt(1); - st.setText(newLegend); - expression = null; // if setting to a string, no expression! - } - - /** - * Refer to the associated Series Text. - * @param txt - * @return - */ - public boolean setText(String txt) { - try { - // XLSRecord x = getRecord(0); - // ((SeriesText)x ).setText(txt); - st.setText(txt); - return true; - } catch (ClassCastException e) { - // Logger.logInfo("Error getting Chart String value: " + e); - return false; - } - } - - public String getText() { - try { - // XLSRecord x = getRecord(0); - // if(x instanceof SeriesText)return ((SeriesText)x - // ).toString(); - if (st != null) - return st.toString(); - } catch (Exception e) { - if (DEBUGLEVEL > 0) - Logger.logWarn("Error getting Chart String value: " + e); - return getDefinition(); - } - // TODO: figure out why this doesn't find the title -- see - // "reportS01Template.xls" - return "undefined"; - } - - @Override - public String toString() { - switch (id) { - case Ai.TYPE_TEXT: - return getText(); - case Ai.TYPE_VALS: - return getDefinition(); - case Ai.TYPE_CATEGORIES: - return getDefinition(); - case Ai.TYPE_BUBBLES: - return getDefinition(); - } - return super.toString(); - } - - // public void addRecord(BiffRec rec){ - // xlsrecs.add(rec); - // } - public void setSeriesText(SeriesText s) { - st = s; - } - - // protected XLSRecord getRecord(int i){ - // return (XLSRecord) xlsrecs.get(i); - // } - - /** set the Externsheet reference - for any associated PtgArea3d's - */ - public void setExternsheetRef(int x) throws WorkSheetNotFoundException { - byte[] dt = this.getData(); - int pos = 8; - - for (int t = 0; t < expression.size(); t++) { - Ptg p = expression.get(t); - if (p instanceof PtgArea3d) { - PtgArea3d pt = (PtgArea3d) p; - pt.setIxti((short) x); - pt.addToRefTracker(); - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pt.toString() + " in Ai record."); - // register with the Externsheet reference - this.getWorkBook().getExternSheet().addPtgListener(pt); - updateRecord(); - } else if (p instanceof PtgRef3d) { // 20091015 KSC: Added - PtgRef3d pr = (PtgRef3d) p; - pr.setIxti((short) x); - pr.addToRefTracker(); - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pr.toString() + " in Ai record."); - // register with the Externsheet reference - this.getWorkBook().getExternSheet().addPtgListener(pr); - updateRecord(); - } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added - Ptg pr = ((PtgMemFunc) p).getFirstloc(); - if (pr instanceof PtgRef3d) { - ((PtgRef3d) pr).setIxti((short) x); - ((PtgRef3d) pr).addToRefTracker(); - this.getWorkBook().getExternSheet() - .addPtgListener((PtgRef3d) pr); - } else { // should be a PtgArea3d - ((PtgArea3d) pr).setIxti((short) x); - ((PtgArea3d) pr).addToRefTracker(); - this.getWorkBook().getExternSheet() - .addPtgListener((PtgArea3d) pr); - } - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pr.toString() + " in Ai record."); - // register with the Externsheet reference - updateRecord(); - } else { - Logger.logInfo("Ai.setExternsheetRef: unknown Ptg"); - } - } - } - - /** set the Externsheet reference - for any associated PtgArea3d's that match the old reference. - - invaluble for modifying only one sheets worth of references (ie a move sheet situation) - */ - public void setExternsheetRef(int oldRef, int newRef) throws WorkSheetNotFoundException { - for (int t = 0; t < expression.size(); t++) { - Ptg p = expression.get(t); - if (p instanceof PtgArea3d) { - PtgArea3d pt = (PtgArea3d) p; - int oRef = pt.getIxti(); - if (oRef == oldRef) { // got the one to update - pt.removeFromRefTracker(); // 20100506 KSC: added - pt.setSheetName(this.getSheet().getSheetName()); // 20100415 - // KSC: - // added - pt.setIxti((short) newRef); - pt.addToRefTracker(); // 20080709 KSC - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pt.toString() + " in Ai record."); - // register with the Externsheet reference - this.getWorkBook().getExternSheet().addPtgListener(pt); - updateRecord(); - } - } else if (p instanceof PtgRef3d) { - PtgRef3d pr = (PtgRef3d) p; - int oRef = pr.getIxti(); - if (oRef == oldRef) { - pr.removeFromRefTracker(); - pr.setSheetName(this.getSheet().getSheetName()); // 20100415 - // KSC: - // added - pr.setIxti((short) newRef); - if (!pr.getIsRefErr()) - pr.addToRefTracker(); - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pr.toString() + " in Ai record."); - // register with the Externsheet reference - this.getWorkBook().getExternSheet().addPtgListener(pr); - updateRecord(); - } - } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added - Ptg pr = ((PtgMemFunc) p).getFirstloc(); - if (pr instanceof PtgRef3d) { - int oRef = ((PtgRef3d) pr).getIxti(); - if (oRef == oldRef) { - ((PtgRef3d) pr).removeFromRefTracker(); // 20100506 KSC: - // added - ((PtgArea3d) pr) - .setSheetName(this.getSheet().getSheetName()); // 20100415 - // KSC: - // added - ((PtgRef3d) pr).setIxti((short) newRef); - ((PtgRef3d) pr).addToRefTracker(); - this.getWorkBook().getExternSheet() - .addPtgListener((PtgRef3d) pr); - } - } else { // should be a PtgArea3d - int oRef = ((PtgRef3d) pr).getIxti(); - if (oRef == oldRef) { - ((PtgRef3d) pr).removeFromRefTracker(); // 20100506 KSC: - // added - ((PtgArea3d) pr) - .setSheetName(this.getSheet().getSheetName()); // 20100415 - // KSC: - // added - ((PtgArea3d) pr).setIxti((short) newRef); - ((PtgArea3d) pr).addToRefTracker(); - this.getWorkBook().getExternSheet() - .addPtgListener((PtgArea3d) pr); - } - } - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pr.toString() + " in Ai record."); - // register with the Externsheet reference - updateRecord(); - } else if (p instanceof PtgMystery) { - // TODO: do what??? - } - } - } - - /** - * take the original boundSheet + boundSheet xti reference and update it to - * the sheet reference in the new workbook (or same workbook but different ixti reference) - */ - public void updateSheetRef(String newSheetName, String origWorkBookName) { - try { - // 20080630/20080708 KSC: Fixes for [BugTracker 1799] + - // [BugTracker 1434] - if (boundXti > -1) { // has populate for transfer been called - - // should! - int newSheetNum = -1; - try { - if (!boundName.equalsIgnoreCase(origSheetName)) { // Ai - // reference - // is on - // a - // dfferent - // sheet, - // see - // if it - // exists - // in - // new - // workbook - newSheetNum = this.getWorkBook() - .getWorkSheetByName(boundName).getSheetNum(); - } else // Ai reference is on same sheet, point now to new - // sheet - newSheetNum = this.getWorkBook() - .getWorkSheetByName(newSheetName).getSheetNum(); - } catch (Exception e) { // 20080123 KSC: if links arent there, - // fix == try to make an External ref - for (int t = 0; t < expression.size(); t++) { - if (expression.get(t) instanceof PtgArea3d) { - PtgArea3d p = (PtgArea3d) expression.get(t); - Logger.logWarn("External References are unsupported: External reference found in Chart: " - + p.getSheetName()); - p.setSheetName(boundName); // set external reference - // to original - // boundsheet name - p.setExternalReference(origWorkBookName); - this.setExternsheetRef(p.getIxti()); - } else { - Logger.logInfo("Ai.updateSheetRef:"); - } - } - } - if (newSheetNum != -1) { - this.setSheet(this.getWorkBook() - .getWorkSheetByName(newSheetName)); // 20100415 KSC: - // set Ai sheet - // ref to new - // sheet - Externsheet xsht = this.getWorkBook().getExternSheet(true); // create - // if - // necessary - int newXRef = xsht.insertLocation(newSheetNum, newSheetNum); - this.setExternsheetRef(boundXti, newXRef); - boundXti = newXRef; // 20100506 KSC: reset - boundName = newSheetName; // "" - } - } else {// debugging 20100415 - // Logger.logErr("Ai.updateSheetRef: boundxti is -1 for AI " - // + this.toString()); - } - } catch (Exception e) { - Logger.logErr("Ai.updateSheetRef: " + e.toString()); - } - } - - /** get the display name - */ - String getName() { - return "Chart Ai"; - } - - /** get the definition text - the definition is stored - in Excel parsed format - */ - public String getDefinition() { - StringBuffer sb = new StringBuffer(); - Ptg[] ep = new Ptg[expression.size()]; - ep = expression.toArray(ep); - for (int t = 0; t < ep.length; t++) { - if (!(ep[t] instanceof PtgParen)) // 20091019 KSC: if complex - // series, will have a PtgParen - // after PtgMemFunc; - /* - * if (ep[t] instanceof PtgMemFunc) - * sb.append("(" + ep[t].getString() + ")"); - * else - */ - sb.append(ep[t].getString()); - } - return sb.toString(); - } - - /* - * Returns an array of ptgs that represent any BiffRec - * ranges in the formula. - * Ranges can either be in the format "C5" or "Sheet1!C4:D9" - */ - public Ptg[] getCellRangePtgs() throws FormulaNotFoundException { - return ExpressionParser.getCellRangePtgs(expression); - } - - /** - * Return the type (ID) of this Ai record - * @return int rt - */ - public int getType() { - return id; - } - - /** - * return the custom number format for this AI (category, series or bubble) - *
                if 0, use default number format as specified by Axis or Chart - * @return - */ - public int getIfmt() { - return ifmt; - } - - /* - * Returns the ptg that matches the string location sent to - * it. - * this can either be in the format "C5" or a range, such as - * "C4:D9" - */ - public List getPtgsByLocation(String loc) { - try { - return ExpressionParser.getPtgsByLocation(loc, expression); - } catch (FormulaNotFoundException e) { - Logger.logWarn("failed to update Chart Series Location: " + e); - } - return null; - } - - /** locks the Ptg at the specified location - */ - - public boolean setLocationPolicy(String loc, int l) { - List dx = this.getPtgsByLocation(loc); - Iterator lx = dx.iterator(); - while (lx.hasNext()) { - Ptg d = lx.next(); - if (d != null) { - d.setLocationPolicy(l); - } - } - return true; - } - - /** - * simplified version of changeAiLocation which locates current Ptg and updates expression - * NOTE: newLoc is expected to be a valid reference - * @param p - * @param newLoc - * @return - */ - public boolean changeAiLocation(Ptg p, String newLoc) { - String[] aiLocs = StringTool.splitString(newLoc, ","); - for (int i = 0; i < aiLocs.length; i++) { - try { // NOTE: Ai has only 1 expression OR 2: 1= PtgMemFunc - // 2=PtgParen - // looking up via getExpressionLocByPtg errors if loc is the - // same but Ptg's are different objects eg. PtgRef3d vs - // PtgArea3d ... - if (expression.get(0) instanceof PtgMemFunc) { - try { // must find particular ptg in PtgMemFunc's - // subexression to reset - // APPARENTLY WE DO NOT NEED TO UDPATE PTGMEMFUNC - // SUBEXPRESSION EXPLICITLY - // int z= ExpressionParser.getExpressionLocByPtg(p, - // ((PtgMemFunc) - // expression.get(0)).getSubExpression()); - // Stack subexp= ((PtgMemFunc) - // expression.get(0)).getSubExpression(); - // for (int z= 0; z < subexp.size(); z++) { - // if (p.equals(subexp.get(z))) { - p.setLocation(aiLocs[i]); // updates ref. tracker - // ((PtgMemFunc) - // expression.get(0)).getSubExpression().set(z, p); // - // update expression with new Ptg - // } - // } - } catch (Exception ex) { - Logger.logErr("Ai.changeAiLocation: Error updating Location in non-contiguous range " - + ex.toString()); - } - } else { - p.setLocation(aiLocs[i]); // updates ref. tracker - expression.set(0, p); // update expression with new Ptg - if (this.getType() == Ai.TYPE_TEXT) { // must reset text for - // SeriesText as - // well - try { - Object o = ((PtgRef) p).getValue(); - this.setText(o.toString()); - } catch (Exception e) { - ; - } - } - } - } catch (Exception e) { - Logger.logErr("Ai.changeAiLocation: Error updating Location to " - + newLoc + ":" + e.toString()); - return false; - } - } - updateRecord(); - return true; - } - - /** Takes a string as a current formula location, and changes - that pointer in the formula to the new string that is sent. - This can take single cells "A5" and cell ranges, "A3:d4" - Returns true if the cell range specified in formulaLoc exists & can be changed - else false. This also cannot change a cell pointer to a cell range or vice - versa. - - @param String - range of Cells within Formula to modify - @param String - new range of Cells within Formula - - */ - public boolean changeAiLocation(String loc, String newLoc) { - // TODO: Implement formula policy!! -jm - Ptg ptg = null; - int z = -1; - try { - if (expression.size() > 0) { - z = ExpressionParser - .getExpressionLocByLocation(loc, expression); - ptg = expression.get(z); // 20090917 KSC: since creating - // new ptgs below, must remove - // original from reftracker "by - // hand" - } - if (ptg != null) - ((PtgRef) ptg).removeFromRefTracker(); - } catch (Exception e) {} - if (z == -1 && newLoc.equals("")) {// no reference -- happens on - // legends, category ai's ... - this.getData()[1] = 1; // text reference rather than worksheet - // reference - return false; - } - ptg = PtgRef.createPtgRefFromString(newLoc, this); - if (z != -1) // then must change original - expression.set(z, ptg); // update expression with new Ptg - else - expression.add(ptg); - updateRecord(); - return true; - } - - /* - * Update the record byte array with the modified ptg - * records - */ - public void updateRecord() { - int offy = 8; // the start of the parsed expression - byte[] rkdata = this.getData(); - byte[] updated = new byte[rkdata.length]; - System.arraycopy(rkdata, 0, updated, 0, offy); - for (int i = 0; i < expression.size(); i++) { - Object o = expression.elementAt(i); - Ptg ptg = (Ptg) o; - byte[] b = ptg.getRecord(); - // must inc. size if Ptgs have inc.'d ... see - // changeAiLocation - int len = b.length; - if ((updated.length - offy) < len) { - byte[] newArr = new byte[offy + len]; - System.arraycopy(updated, 0, newArr, 0, updated.length); - // update cce in array as well ... - cce += (newArr.length - updated.length); - updated = newArr; - byte[] ix = ByteTools.shortToLEBytes((short) cce); - System.arraycopy(ix, 0, updated, 6, 2); - } - System.arraycopy(b, 0, updated, offy, len); - offy = offy + len; - } - this.setData(updated); - } - - @Override - public void init() { - super.init(); - id = this.getByteAt(0); - // index id: (0=title or text, 1=series vals, 2=series cats, - // 3= bubbles - rt = this.getByteAt(1); - // reference type(0=default,1=text in formula bar, - // 2=worksheet, 4=error) - grbit = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - setfCustomIfmt((grbit & 0x1) == 0x1); - // flags - ifmt = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - // Index to number format - cce = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - // size of rgce (in bytes) - int pos = 8; - // Parsed formula of link - - // get the parsed expression - byte[] expressionbytes = this.getBytesAt(pos, cce); - expression = ExpressionParser.parseExpression(expressionbytes, this); - pos += cce; - if (DEBUGLEVEL > 10) - Logger.logInfo(this.getName() + ":" + this.getDefinition()); - } - - /** - * set reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) - * @param i - */ - public void setRt(int i) { - rt = (short) i; - this.getData()[1] = (byte) rt; - } - - /** - * Get a prototype with the specified ai types. - * - * 0 = Legend AI - * 1= Series Value Ai - * 2 = Category Ai - * 3 = Unknown, undocumented, but neccesarry AI - * 4 = Blank Legend AI with no reference. - */ - public static ChartObject getPrototype(byte[] aiType) { - Ai ai = new Ai(); - ai.setOpcode(AI); - ai.setData(aiType); - ai.init(); - return ai; - } - - // 20070801 KSC: since changeAiLocation now allows addition - // of new expression bytes, alter - // default prototype bytes here to not include any - // expression bytes .. - protected static byte[] AI_TYPE_LEGEND = new byte[] { 0, 2, 0, 0, 0, 0, - 0, 0 }; // , - // 7, - // 0, - // 58, - // 0, - // 0, - // 0, - // 0, - // 0, - // 0}; - protected static byte[] AI_TYPE_SERIES = new byte[] { 1, 2, 0, 0, 0, 0, - 0, 0 }; // , - // 11, - // 0, - // 59, - // 0, - // 0, - // 1, - // 0, - // 1, - // 0, - // 1, - // 0, - // 3, - // 0}; - protected static byte[] AI_TYPE_CATEGORY = new byte[] { 2, 2, 0, 0, 0, 0, - 0, 0 }; // 11, - // 0, - // 59, - // 0, - // 0, - // 0, - // 0, - // 0, - // 0, - // 1, - // 0, - // 3, - // 0}; - protected static byte[] AI_TYPE_BUBBLE = new byte[] { 3, 1, 0, 0, 0, 0, - 0, 0 }; - protected static byte[] AI_TYPE_NULL_LEGEND = new byte[] { 0, 1, 0, 0, 0, 0, - 0, 0 }; - - public Stack getExpression() { - return expression; - } - - @Override - public void close() { - if (expression != null) { - while (!expression.isEmpty()) { - GenericPtg p = (GenericPtg) expression.pop(); - if (p instanceof PtgRef) - ((PtgRef) p).close(); - else - p.close(); - p = null; - } - } - super.close(); - } - - @Override - protected void finalize() { - this.close(); - } - - /** - * @return the fCustomIfmt - */ - public boolean isfCustomIfmt() { - return fCustomIfmt; - } - - /** - * @param fCustomIfmt the fCustomIfmt to set - */ - public void setfCustomIfmt(boolean fCustomIfmt) { - this.fCustomIfmt = fCustomIfmt; - } + /** + * + */ + private static final long serialVersionUID = -6647823755603289012L; + private Stack expression; + protected int id = -1, ifmt = -1, cce = -1; + private short grbit = -1, rt = -1; + private boolean fCustomIfmt = false; + // private CompatibleVector xlsrecs = new + // CompatibleVector(); + private SeriesText st = null; + // define the type of Ai record + public static final int TYPE_TEXT = 0; + public static final int TYPE_VALS = 1; + public static final int TYPE_CATEGORIES = 2; + public static final int TYPE_BUBBLES = 3; + + /** + * This is for storage of the boundsheet name when the ai record is being moved from + * one workbook to another. In these cases, populate this value, and pull it back out + * to locate the cxti and reset the reerences + * + * @return the name of the original boundsheet this record is associated with. + */ + private String boundName = ""; + private String origSheetName = ""; + private int boundXti = -1; + + /** + * returns the bound sheet name - must be called after populateForTransfer + * + * @return String + */ // 20080116 KSC: Changed name for clarity + public String getBoundName() { + return boundName; + } + + /** + * returns the sheet reference index - must call after populateForTransfer + * + * @return + */ + public int getBoundXti() { + return boundXti; + } + + /** + * Sets the boundsheet name for the data referenced in this AI. + * + sets the xti of the boundsheet reference (necessary upon sheet copy/move) + * Does not currently support multi-boundsheet references. + * + * @see updateSheetRef + */ + public void populateForTransfer(String origSheetName) { + if ("".equals(boundName)) { + this.origSheetName = origSheetName; // 20080708 KSC: trap original + // sheet name for + // updateSheetRefs comparison + for (int t = 0; t < expression.size(); t++) { + Ptg p = expression.get(t); + if (p instanceof PtgArea3d) { + PtgArea3d pt = (PtgArea3d) p; + try { + boundName = pt.getSheetName(); // original boundname + + // xti sheet ref + boundXti = pt.getIxti(); + } catch (Exception e) { + Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); + } + } else if (p instanceof PtgRef3d) { + PtgRef3d pt = (PtgRef3d) p; + try { + boundName = pt.getSheetName(); // original boundname + + // xti sheet ref + boundXti = pt.getIxti(); + } catch (Exception e) { + Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); + } + } else if (p instanceof PtgMemFunc) { // 20091015 KSC: for + // non-contiguous ranges + Ptg ptg = ((PtgMemFunc) p).getFirstloc(); + if (ptg instanceof PtgRef3d) { + PtgRef3d pr = (PtgRef3d) ptg; + try { + boundName = pr.getSheetName(); // original boundname + // + xti sheet ref + boundXti = pr.getIxti(); + } catch (Exception e) { + Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); + } + } else { // should be a PtgArea3d + PtgArea3d pr = (PtgArea3d) ptg; + try { + boundName = pr.getSheetName(); // original boundname + // + xti sheet ref + boundXti = pr.getIxti(); + } catch (Exception e) { + Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); + } + } + } + } + } + } + + public void setLegend(String newLegend) { + this.setRt(1); + st.setText(newLegend); + expression = null; // if setting to a string, no expression! + } + + /** + * Refer to the associated Series Text. + * + * @param txt + * @return + */ + public boolean setText(String txt) { + try { + // XLSRecord x = getRecord(0); + // ((SeriesText)x ).setText(txt); + st.setText(txt); + return true; + } catch (ClassCastException e) { + // Logger.logInfo("Error getting Chart String value: " + e); + return false; + } + } + + public String getText() { + try { + // XLSRecord x = getRecord(0); + // if(x instanceof SeriesText)return ((SeriesText)x + // ).toString(); + if (st != null) + return st.toString(); + } catch (Exception e) { + if (DEBUGLEVEL > 0) + Logger.logWarn("Error getting Chart String value: " + e); + return getDefinition(); + } + // TODO: figure out why this doesn't find the title -- see + // "reportS01Template.xls" + return "undefined"; + } + + @Override + public String toString() { + switch (id) { + case Ai.TYPE_TEXT: + return getText(); + case Ai.TYPE_VALS: + return getDefinition(); + case Ai.TYPE_CATEGORIES: + return getDefinition(); + case Ai.TYPE_BUBBLES: + return getDefinition(); + } + return super.toString(); + } + + // public void addRecord(BiffRec rec){ + // xlsrecs.add(rec); + // } + public void setSeriesText(SeriesText s) { + st = s; + } + + // protected XLSRecord getRecord(int i){ + // return (XLSRecord) xlsrecs.get(i); + // } + + /** + * set the Externsheet reference + * for any associated PtgArea3d's + */ + public void setExternsheetRef(int x) throws WorkSheetNotFoundException { + byte[] dt = this.getData(); + int pos = 8; + + for (int t = 0; t < expression.size(); t++) { + Ptg p = expression.get(t); + if (p instanceof PtgArea3d) { + PtgArea3d pt = (PtgArea3d) p; + pt.setIxti((short) x); + pt.addToRefTracker(); + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pt.toString() + " in Ai record."); + // register with the Externsheet reference + this.getWorkBook().getExternSheet().addPtgListener(pt); + updateRecord(); + } else if (p instanceof PtgRef3d) { // 20091015 KSC: Added + PtgRef3d pr = (PtgRef3d) p; + pr.setIxti((short) x); + pr.addToRefTracker(); + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record."); + // register with the Externsheet reference + this.getWorkBook().getExternSheet().addPtgListener(pr); + updateRecord(); + } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added + Ptg pr = ((PtgMemFunc) p).getFirstloc(); + if (pr instanceof PtgRef3d) { + ((PtgRef3d) pr).setIxti((short) x); + ((PtgRef3d) pr).addToRefTracker(); + this.getWorkBook().getExternSheet() + .addPtgListener((PtgRef3d) pr); + } else { // should be a PtgArea3d + ((PtgArea3d) pr).setIxti((short) x); + ((PtgArea3d) pr).addToRefTracker(); + this.getWorkBook().getExternSheet() + .addPtgListener((PtgArea3d) pr); + } + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record."); + // register with the Externsheet reference + updateRecord(); + } else { + Logger.logInfo("Ai.setExternsheetRef: unknown Ptg"); + } + } + } + + /** + * set the Externsheet reference + * for any associated PtgArea3d's that match the old reference. + *

                + * invaluble for modifying only one sheets worth of references (ie a move sheet situation) + */ + public void setExternsheetRef(int oldRef, int newRef) throws WorkSheetNotFoundException { + for (int t = 0; t < expression.size(); t++) { + Ptg p = expression.get(t); + if (p instanceof PtgArea3d) { + PtgArea3d pt = (PtgArea3d) p; + int oRef = pt.getIxti(); + if (oRef == oldRef) { // got the one to update + pt.removeFromRefTracker(); // 20100506 KSC: added + pt.setSheetName(this.getSheet().getSheetName()); // 20100415 + // KSC: + // added + pt.setIxti((short) newRef); + pt.addToRefTracker(); // 20080709 KSC + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pt.toString() + " in Ai record."); + // register with the Externsheet reference + this.getWorkBook().getExternSheet().addPtgListener(pt); + updateRecord(); + } + } else if (p instanceof PtgRef3d) { + PtgRef3d pr = (PtgRef3d) p; + int oRef = pr.getIxti(); + if (oRef == oldRef) { + pr.removeFromRefTracker(); + pr.setSheetName(this.getSheet().getSheetName()); // 20100415 + // KSC: + // added + pr.setIxti((short) newRef); + if (!pr.getIsRefErr()) + pr.addToRefTracker(); + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record."); + // register with the Externsheet reference + this.getWorkBook().getExternSheet().addPtgListener(pr); + updateRecord(); + } + } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added + Ptg pr = ((PtgMemFunc) p).getFirstloc(); + if (pr instanceof PtgRef3d) { + int oRef = ((PtgRef3d) pr).getIxti(); + if (oRef == oldRef) { + ((PtgRef3d) pr).removeFromRefTracker(); // 20100506 KSC: + // added + ((PtgArea3d) pr) + .setSheetName(this.getSheet().getSheetName()); // 20100415 + // KSC: + // added + ((PtgRef3d) pr).setIxti((short) newRef); + ((PtgRef3d) pr).addToRefTracker(); + this.getWorkBook().getExternSheet() + .addPtgListener((PtgRef3d) pr); + } + } else { // should be a PtgArea3d + int oRef = ((PtgRef3d) pr).getIxti(); + if (oRef == oldRef) { + ((PtgRef3d) pr).removeFromRefTracker(); // 20100506 KSC: + // added + ((PtgArea3d) pr) + .setSheetName(this.getSheet().getSheetName()); // 20100415 + // KSC: + // added + ((PtgArea3d) pr).setIxti((short) newRef); + ((PtgArea3d) pr).addToRefTracker(); + this.getWorkBook().getExternSheet() + .addPtgListener((PtgArea3d) pr); + } + } + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record."); + // register with the Externsheet reference + updateRecord(); + } else if (p instanceof PtgMystery) { + // TODO: do what??? + } + } + } + + /** + * take the original boundSheet + boundSheet xti reference and update it to + * the sheet reference in the new workbook (or same workbook but different ixti reference) + */ + public void updateSheetRef(String newSheetName, String origWorkBookName) { + try { + // 20080630/20080708 KSC: Fixes for [BugTracker 1799] + + // [BugTracker 1434] + if (boundXti > -1) { // has populate for transfer been called - + // should! + int newSheetNum = -1; + try { + if (!boundName.equalsIgnoreCase(origSheetName)) { // Ai + // reference + // is on + // a + // dfferent + // sheet, + // see + // if it + // exists + // in + // new + // workbook + newSheetNum = this.getWorkBook() + .getWorkSheetByName(boundName).getSheetNum(); + } else // Ai reference is on same sheet, point now to new + // sheet + newSheetNum = this.getWorkBook() + .getWorkSheetByName(newSheetName).getSheetNum(); + } catch (Exception e) { // 20080123 KSC: if links arent there, + // fix == try to make an External ref + for (int t = 0; t < expression.size(); t++) { + if (expression.get(t) instanceof PtgArea3d) { + PtgArea3d p = (PtgArea3d) expression.get(t); + Logger.logWarn("External References are unsupported: External reference found in Chart: " + + p.getSheetName()); + p.setSheetName(boundName); // set external reference + // to original + // boundsheet name + p.setExternalReference(origWorkBookName); + this.setExternsheetRef(p.getIxti()); + } else { + Logger.logInfo("Ai.updateSheetRef:"); + } + } + } + if (newSheetNum != -1) { + this.setSheet(this.getWorkBook() + .getWorkSheetByName(newSheetName)); // 20100415 KSC: + // set Ai sheet + // ref to new + // sheet + Externsheet xsht = this.getWorkBook().getExternSheet(true); // create + // if + // necessary + int newXRef = xsht.insertLocation(newSheetNum, newSheetNum); + this.setExternsheetRef(boundXti, newXRef); + boundXti = newXRef; // 20100506 KSC: reset + boundName = newSheetName; // "" + } + } else {// debugging 20100415 + // Logger.logErr("Ai.updateSheetRef: boundxti is -1 for AI " + // + this.toString()); + } + } catch (Exception e) { + Logger.logErr("Ai.updateSheetRef: " + e.toString()); + } + } + + /** + * get the display name + */ + String getName() { + return "Chart Ai"; + } + + /** + * get the definition text + * the definition is stored + * in Excel parsed format + */ + public String getDefinition() { + StringBuffer sb = new StringBuffer(); + Ptg[] ep = new Ptg[expression.size()]; + ep = expression.toArray(ep); + for (int t = 0; t < ep.length; t++) { + if (!(ep[t] instanceof PtgParen)) // 20091019 KSC: if complex + // series, will have a PtgParen + // after PtgMemFunc; + /* + * if (ep[t] instanceof PtgMemFunc) + * sb.append("(" + ep[t].getString() + ")"); + * else + */ + sb.append(ep[t].getString()); + } + return sb.toString(); + } + + /* + * Returns an array of ptgs that represent any BiffRec + * ranges in the formula. + * Ranges can either be in the format "C5" or "Sheet1!C4:D9" + */ + public Ptg[] getCellRangePtgs() throws FormulaNotFoundException { + return ExpressionParser.getCellRangePtgs(expression); + } + + /** + * Return the type (ID) of this Ai record + * + * @return int rt + */ + public int getType() { + return id; + } + + /** + * return the custom number format for this AI (category, series or bubble) + *
                if 0, use default number format as specified by Axis or Chart + * + * @return + */ + public int getIfmt() { + return ifmt; + } + + /* + * Returns the ptg that matches the string location sent to + * it. + * this can either be in the format "C5" or a range, such as + * "C4:D9" + */ + public List getPtgsByLocation(String loc) { + try { + return ExpressionParser.getPtgsByLocation(loc, expression); + } catch (FormulaNotFoundException e) { + Logger.logWarn("failed to update Chart Series Location: " + e); + } + return null; + } + + /** + * locks the Ptg at the specified location + */ + + public boolean setLocationPolicy(String loc, int l) { + List dx = this.getPtgsByLocation(loc); + Iterator lx = dx.iterator(); + while (lx.hasNext()) { + Ptg d = lx.next(); + if (d != null) { + d.setLocationPolicy(l); + } + } + return true; + } + + /** + * simplified version of changeAiLocation which locates current Ptg and updates expression + * NOTE: newLoc is expected to be a valid reference + * + * @param p + * @param newLoc + * @return + */ + public boolean changeAiLocation(Ptg p, String newLoc) { + String[] aiLocs = StringTool.splitString(newLoc, ","); + for (int i = 0; i < aiLocs.length; i++) { + try { // NOTE: Ai has only 1 expression OR 2: 1= PtgMemFunc + // 2=PtgParen + // looking up via getExpressionLocByPtg errors if loc is the + // same but Ptg's are different objects eg. PtgRef3d vs + // PtgArea3d ... + if (expression.get(0) instanceof PtgMemFunc) { + try { // must find particular ptg in PtgMemFunc's + // subexression to reset + // APPARENTLY WE DO NOT NEED TO UDPATE PTGMEMFUNC + // SUBEXPRESSION EXPLICITLY + // int z= ExpressionParser.getExpressionLocByPtg(p, + // ((PtgMemFunc) + // expression.get(0)).getSubExpression()); + // Stack subexp= ((PtgMemFunc) + // expression.get(0)).getSubExpression(); + // for (int z= 0; z < subexp.size(); z++) { + // if (p.equals(subexp.get(z))) { + p.setLocation(aiLocs[i]); // updates ref. tracker + // ((PtgMemFunc) + // expression.get(0)).getSubExpression().set(z, p); // + // update expression with new Ptg + // } + // } + } catch (Exception ex) { + Logger.logErr("Ai.changeAiLocation: Error updating Location in non-contiguous range " + + ex.toString()); + } + } else { + p.setLocation(aiLocs[i]); // updates ref. tracker + expression.set(0, p); // update expression with new Ptg + if (this.getType() == Ai.TYPE_TEXT) { // must reset text for + // SeriesText as + // well + try { + Object o = p.getValue(); + this.setText(o.toString()); + } catch (Exception e) { + } + } + } + } catch (Exception e) { + Logger.logErr("Ai.changeAiLocation: Error updating Location to " + + newLoc + ":" + e.toString()); + return false; + } + } + updateRecord(); + return true; + } + + /** + * Takes a string as a current formula location, and changes + * that pointer in the formula to the new string that is sent. + * This can take single cells "A5" and cell ranges, "A3:d4" + * Returns true if the cell range specified in formulaLoc exists & can be changed + * else false. This also cannot change a cell pointer to a cell range or vice + * versa. + * + * @param String - range of Cells within Formula to modify + * @param String - new range of Cells within Formula + */ + public boolean changeAiLocation(String loc, String newLoc) { + // TODO: Implement formula policy!! -jm + Ptg ptg = null; + int z = -1; + try { + if (expression.size() > 0) { + z = ExpressionParser + .getExpressionLocByLocation(loc, expression); + ptg = expression.get(z); // 20090917 KSC: since creating + // new ptgs below, must remove + // original from reftracker "by + // hand" + } + if (ptg != null) + ((PtgRef) ptg).removeFromRefTracker(); + } catch (Exception e) { + } + if (z == -1 && newLoc.equals("")) {// no reference -- happens on + // legends, category ai's ... + this.getData()[1] = 1; // text reference rather than worksheet + // reference + return false; + } + ptg = PtgRef.createPtgRefFromString(newLoc, this); + if (z != -1) // then must change original + expression.set(z, ptg); // update expression with new Ptg + else + expression.add(ptg); + updateRecord(); + return true; + } + + /* + * Update the record byte array with the modified ptg + * records + */ + public void updateRecord() { + int offy = 8; // the start of the parsed expression + byte[] rkdata = this.getData(); + byte[] updated = new byte[rkdata.length]; + System.arraycopy(rkdata, 0, updated, 0, offy); + for (int i = 0; i < expression.size(); i++) { + Object o = expression.elementAt(i); + Ptg ptg = (Ptg) o; + byte[] b = ptg.getRecord(); + // must inc. size if Ptgs have inc.'d ... see + // changeAiLocation + int len = b.length; + if ((updated.length - offy) < len) { + byte[] newArr = new byte[offy + len]; + System.arraycopy(updated, 0, newArr, 0, updated.length); + // update cce in array as well ... + cce += (newArr.length - updated.length); + updated = newArr; + byte[] ix = ByteTools.shortToLEBytes((short) cce); + System.arraycopy(ix, 0, updated, 6, 2); + } + System.arraycopy(b, 0, updated, offy, len); + offy = offy + len; + } + this.setData(updated); + } + + @Override + public void init() { + super.init(); + id = this.getByteAt(0); + // index id: (0=title or text, 1=series vals, 2=series cats, + // 3= bubbles + rt = this.getByteAt(1); + // reference type(0=default,1=text in formula bar, + // 2=worksheet, 4=error) + grbit = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + setfCustomIfmt((grbit & 0x1) == 0x1); + // flags + ifmt = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + // Index to number format + cce = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + // size of rgce (in bytes) + int pos = 8; + // Parsed formula of link + + // get the parsed expression + byte[] expressionbytes = this.getBytesAt(pos, cce); + expression = ExpressionParser.parseExpression(expressionbytes, this); + pos += cce; + if (DEBUGLEVEL > 10) + Logger.logInfo(this.getName() + ":" + this.getDefinition()); + } + + /** + * set reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) + * + * @param i + */ + public void setRt(int i) { + rt = (short) i; + this.getData()[1] = (byte) rt; + } + + /** + * Get a prototype with the specified ai types. + *

                + * 0 = Legend AI + * 1= Series Value Ai + * 2 = Category Ai + * 3 = Unknown, undocumented, but neccesarry AI + * 4 = Blank Legend AI with no reference. + */ + public static ChartObject getPrototype(byte[] aiType) { + Ai ai = new Ai(); + ai.setOpcode(AI); + ai.setData(aiType); + ai.init(); + return ai; + } + + // 20070801 KSC: since changeAiLocation now allows addition + // of new expression bytes, alter + // default prototype bytes here to not include any + // expression bytes .. + protected static byte[] AI_TYPE_LEGEND = new byte[]{0, 2, 0, 0, 0, 0, + 0, 0}; // , + // 7, + // 0, + // 58, + // 0, + // 0, + // 0, + // 0, + // 0, + // 0}; + protected static byte[] AI_TYPE_SERIES = new byte[]{1, 2, 0, 0, 0, 0, + 0, 0}; // , + // 11, + // 0, + // 59, + // 0, + // 0, + // 1, + // 0, + // 1, + // 0, + // 1, + // 0, + // 3, + // 0}; + protected static byte[] AI_TYPE_CATEGORY = new byte[]{2, 2, 0, 0, 0, 0, + 0, 0}; // 11, + // 0, + // 59, + // 0, + // 0, + // 0, + // 0, + // 0, + // 0, + // 1, + // 0, + // 3, + // 0}; + protected static byte[] AI_TYPE_BUBBLE = new byte[]{3, 1, 0, 0, 0, 0, + 0, 0}; + protected static byte[] AI_TYPE_NULL_LEGEND = new byte[]{0, 1, 0, 0, 0, 0, + 0, 0}; + + public Stack getExpression() { + return expression; + } + + @Override + public void close() { + if (expression != null) { + while (!expression.isEmpty()) { + GenericPtg p = (GenericPtg) expression.pop(); + if (p instanceof PtgRef) + p.close(); + else + p.close(); + p = null; + } + } + super.close(); + } + + @Override + protected void finalize() { + this.close(); + } + + /** + * @return the fCustomIfmt + */ + public boolean isfCustomIfmt() { + return fCustomIfmt; + } + + /** + * @param fCustomIfmt the fCustomIfmt to set + */ + public void setfCustomIfmt(boolean fCustomIfmt) { + this.fCustomIfmt = fCustomIfmt; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/AlRuns.java b/src/main/java/io/starter/formats/XLS/charts/AlRuns.java index 22e519a..7d2af01 100644 --- a/src/main/java/io/starter/formats/XLS/charts/AlRuns.java +++ b/src/main/java/io/starter/formats/XLS/charts/AlRuns.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,18 +23,16 @@ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * AiRuns: Text Formatting (0x1050) - * */ public class AlRuns extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5255886234371123315L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -5255886234371123315L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Area.java b/src/main/java/io/starter/formats/XLS/charts/Area.java index 37f7c7a..a926c13 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Area.java +++ b/src/main/java/io/starter/formats/XLS/charts/Area.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -28,43 +28,41 @@ import io.starter.toolkit.ByteTools; /** - * * Area: Chart Group is an Area Chart Group (0x101a) - * 4 grbit 2 formatflags - * - * grbit: - * 0 0 01h fStacked Series in this group are stacked - * 1 02h f100 Each cat is broken down as a percentge - * 2 04h fHasShadow 1= this are has a shadow - * 1 7-0 FFh reserved 0 - * - * + * 4 grbit 2 formatflags + *

                + * grbit: + * 0 0 01h fStacked Series in this group are stacked + * 1 02h f100 Each cat is broken down as a percentge + * 2 04h fHasShadow 1= this are has a shadow + * 1 7-0 FFh reserved 0 */ public class Area extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4600344312324775780L; - private short grbit = 0; - protected boolean fStacked= false, f100= false, fHasShadow= false; - + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4600344312324775780L; + private short grbit = 0; + protected boolean fStacked = false, f100 = false, fHasShadow = false; + public void init() { - super.init(); + super.init(); grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fStacked= ((grbit & 0x1)==0x1); - f100= ((grbit & 0x2)==0x2); - fHasShadow= ((grbit & 0x4)==0x4); - chartType= ChartConstants.AREACHART; // 20070703 KSC - } + fStacked = ((grbit & 0x1) == 0x1); + f100 = ((grbit & 0x2) == 0x2); + fHasShadow = ((grbit & 0x4) == 0x4); + chartType = ChartConstants.AREACHART; // 20070703 KSC + } + protected void updateRecord() { - grbit= ByteTools.updateGrBit(grbit, fStacked, 0); - grbit= ByteTools.updateGrBit(grbit, f100, 1); - grbit= ByteTools.updateGrBit(grbit, fHasShadow, 2); + grbit = ByteTools.updateGrBit(grbit, fStacked, 0); + grbit = ByteTools.updateGrBit(grbit, f100, 1); + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 2); byte[] b = ByteTools.shortToLEBytes(grbit); this.getData()[0] = b[0]; this.getData()[1] = b[1]; } - + public static XLSRecord getPrototype() { Area b = new Area(); b.setOpcode(AREA); @@ -72,57 +70,65 @@ public static XLSRecord getPrototype() { b.init(); return b; } - - private byte[] PROTOTYPE_BYTES = new byte[] {0, 0}; + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; public void setIsStacked(boolean bIsStacked) { - fStacked= bIsStacked; - grbit= ByteTools.updateGrBit(grbit, fStacked, 0); - updateRecord(); + fStacked = bIsStacked; + grbit = ByteTools.updateGrBit(grbit, fStacked, 0); + updateRecord(); } + public void setIs100Percent(boolean bOn) { - f100= bOn; - grbit= ByteTools.updateGrBit(grbit, f100, 1); - updateRecord(); + f100 = bOn; + grbit = ByteTools.updateGrBit(grbit, f100, 1); + updateRecord(); } + /** * @return truth of "Chart is Stacked" */ - public boolean isStacked() { return fStacked; } - public boolean is100Percent() { return f100; } - + public boolean isStacked() { + return fStacked; + } + + public boolean is100Percent() { + return f100; + } + /** - * Handle setting options + * Handle setting options */ public boolean setChartOption(String op, String val) { - boolean bHandled= false; - if (op.equalsIgnoreCase("Stacked")) { - this.fStacked= val.equals("true"); - bHandled= true; - } else if (op.equalsIgnoreCase("PercentageDisplay")) { - this.f100= val.equals("true"); - bHandled= true; - } else if (op.equalsIgnoreCase("Shadow")) { - this.fHasShadow= val.equals("true"); - bHandled= true; - } - if (bHandled) - this.updateRecord(); - return bHandled; + boolean bHandled = false; + if (op.equalsIgnoreCase("Stacked")) { + this.fStacked = val.equals("true"); + bHandled = true; + } else if (op.equalsIgnoreCase("PercentageDisplay")) { + this.f100 = val.equals("true"); + bHandled = true; + } else if (op.equalsIgnoreCase("Shadow")) { + this.fHasShadow = val.equals("true"); + bHandled = true; + } + if (bHandled) + this.updateRecord(); + return bHandled; } - /** + + /** * @return String XML representation of this chart-type's options */ public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - if (this.fStacked) - sb.append(" Stacked=\"true\""); - if (this.f100) - sb.append(" PercentageDisplay=\"true\""); - if (this.fHasShadow) - sb.append(" Shadow=\"true\""); - return sb.toString(); + StringBuffer sb = new StringBuffer(); + if (this.fStacked) + sb.append(" Stacked=\"true\""); + if (this.f100) + sb.append(" PercentageDisplay=\"true\""); + if (this.fHasShadow) + sb.append(" Shadow=\"true\""); + return sb.toString(); } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Area3DChart.java b/src/main/java/io/starter/formats/XLS/charts/Area3DChart.java index c068e3a..4369960 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Area3DChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/Area3DChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,54 +25,61 @@ import io.starter.formats.XLS.WorkBook; public class Area3DChart extends AreaChart { - public Area3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - area= (Area) charttype; - } - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); + public Area3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + area = (Area) charttype; + } - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - // vary colors??? + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); - //DropLines - ChartLine cl= cf.getChartLinesRec(); - if (cl!=null) - cooxml.append(cl.getOOXML()); + if (this.is100PercentStacked()) + cooxml.append("percentStacked"); + else if (this.isStacked()) + cooxml.append("stacked"); + else + cooxml.append("standard"); + cooxml.append("\"/>"); + cooxml.append("\r\n"); + // vary colors??? - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - cooxml.append(""); cooxml.append("\r\n"); + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + // TODO: get real value + int gapdepth = this.getGapDepth(); // 150 is default + if (gapdepth != 0 && gapdepth != 150) + cooxml.append(""); + //DropLines + ChartLine cl = cf.getChartLinesRec(); + if (cl != null) + cooxml.append(cl.getOOXML()); + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } - return cooxml; - } - } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/AreaChart.java b/src/main/java/io/starter/formats/XLS/charts/AreaChart.java index 003f2bd..e39efac 100644 --- a/src/main/java/io/starter/formats/XLS/charts/AreaChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/AreaChart.java @@ -29,268 +29,268 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.ChartSeriesHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; /** * non-stacked area chart - * */ public class AreaChart extends ChartType { - Area area = null; + Area area = null; - public AreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - area = (Area) charttype; - } + public AreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + area = (Area) charttype; + } - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) throws JSONException { - final JSONObject chartObjectJSON = new JSONObject(); - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); + public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) throws JSONException { + final JSONObject chartObjectJSON = new JSONObject(); + // Type JSON + chartObjectJSON.put("type", this.getTypeJSON()); - // Series - double yMax = 0.0, yMin = 0.0; - int nSeries = 0; - final JSONArray seriesJSON = new JSONArray(); - final JSONArray seriesCOLORS = new JSONArray(); - try { - for (int i = 0; i < series.length; i++) { - final JSONArray seriesvals = CellRange - .getValuesAsJSON(series[i].getSeriesRange(), wbh); - // must trap min and max for axis tick and units - double sum = 0.0; // for area-type charts, ymax is the sum of - // all points in same series - nSeries = Math.max(nSeries, seriesvals.length()); - for (int j = 0; j < seriesvals.length(); j++) { - try { - sum += seriesvals.getDouble(j); - yMax = Math.max(yMax, sum); - yMin = Math.min(yMin, seriesvals.getDouble(j)); - } catch (final NumberFormatException n) { - ; - } - } - seriesJSON.put(seriesvals); - seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i] - .getSeriesColor()]); - } - chartObjectJSON.put("Series", seriesJSON); - chartObjectJSON.put("SeriesFills", seriesCOLORS); - } catch (final JSONException je) { - // TODO: Log error - } - minMax[0] = new Double(yMin); - minMax[1] = new Double(yMax); - minMax[2] = new Double(nSeries); - return chartObjectJSON; + // Series + double yMax = 0.0, yMin = 0.0; + int nSeries = 0; + final JSONArray seriesJSON = new JSONArray(); + final JSONArray seriesCOLORS = new JSONArray(); + try { + for (int i = 0; i < series.length; i++) { + final JSONArray seriesvals = CellRange + .getValuesAsJSON(series[i].getSeriesRange(), wbh); + // must trap min and max for axis tick and units + double sum = 0.0; // for area-type charts, ymax is the sum of + // all points in same series + nSeries = Math.max(nSeries, seriesvals.length()); + for (int j = 0; j < seriesvals.length(); j++) { + try { + sum += seriesvals.getDouble(j); + yMax = Math.max(yMax, sum); + yMin = Math.min(yMin, seriesvals.getDouble(j)); + } catch (final NumberFormatException n) { + } + } + seriesJSON.put(seriesvals); + seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i] + .getSeriesColor()]); + } + chartObjectJSON.put("Series", seriesJSON); + chartObjectJSON.put("SeriesFills", seriesCOLORS); + } catch (final JSONException je) { + // TODO: Log error + } + minMax[0] = new Double(yMin); + minMax[1] = new Double(yMax); + minMax[2] = new Double(nSeries); + return chartObjectJSON; - } + } - /** - * return the type JSON for this Chart Object - * @return - */ - @Override - public JSONObject getTypeJSON() throws JSONException { - final JSONObject typeJSON = new JSONObject(); - String dojoType; - if (!this.isStacked()) { - dojoType = "Areas"; - } else { - dojoType = "StackedAreas"; - } - typeJSON.put("type", dojoType); - return typeJSON; - } + /** + * return the type JSON for this Chart Object + * + * @return + */ + @Override + public JSONObject getTypeJSON() throws JSONException { + final JSONObject typeJSON = new JSONObject(); + String dojoType; + if (!this.isStacked()) { + dojoType = "Areas"; + } else { + dojoType = "StackedAreas"; + } + typeJSON.put("type", dojoType); + return typeJSON; + } - /** - * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - @Override - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - final double x = chartMetrics.get("x"); - final double y = chartMetrics.get("y"); - final double w = chartMetrics.get("w"); - final double h = chartMetrics.get("h"); - final double max = chartMetrics.get("max"); - chartMetrics.get("min"); - final Object[] categories = s.getCategories(); - final ArrayList series = s.getSeriesValues(); - final String[] seriescolors = s.getSeriesBarColors(); - final String[] legends = s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is - // xv*2 then increases from there - // y value for each point= h/YMAX - if (series.size() == 0) { - Logger.logErr("Area.getSVG: error in series"); - return ""; - } - final StringBuffer svg = new StringBuffer(); - final int[] dls = getDataLabelInts(); // get array of data labels (can - // be specific per series ...) + /** + * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + @Override + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + final double x = chartMetrics.get("x"); + final double y = chartMetrics.get("y"); + final double w = chartMetrics.get("w"); + final double h = chartMetrics.get("h"); + final double max = chartMetrics.get("max"); + chartMetrics.get("min"); + final Object[] categories = s.getCategories(); + final ArrayList series = s.getSeriesValues(); + final String[] seriescolors = s.getSeriesBarColors(); + final String[] legends = s.getLegends(); + // x value for each point= w/(ncategories + 1) 1st one is + // xv*2 then increases from there + // y value for each point= h/YMAX + if (series.size() == 0) { + Logger.logErr("Area.getSVG: error in series"); + return ""; + } + final StringBuffer svg = new StringBuffer(); + final int[] dls = getDataLabelInts(); // get array of data labels (can + // be specific per series ...) - double xfactor = 0, yfactor = 0; // - if (categories.length > 1) { - xfactor = w / (categories.length - 1); // w/#categories - } - if (max != 0) { - yfactor = h / max; // h/YMAXSCALE - } + double xfactor = 0, yfactor = 0; // + if (categories.length > 1) { + xfactor = w / (categories.length - 1); // w/#categories + } + if (max != 0) { + yfactor = h / max; // h/YMAXSCALE + } - // for each series - final int n = series.size(); - for (int i = n - 1; i >= 0; i--) { // "paint" right to left - svg.append("\r\n"); - String points = ""; - double x1 = 0; - double y1 = 0; - String labels = null; - final double[] curseries = (double[]) series.get(i); - for (int j = 0; j < curseries.length; j++) { - x1 = (x) + j * xfactor; - final double yval = curseries[j]; // areapoints[j][i]; // - // current point - points += ((x) + (j) * xfactor) + "," - + ((y + h) - (yval * yfactor)); + // for each series + final int n = series.size(); + for (int i = n - 1; i >= 0; i--) { // "paint" right to left + svg.append("\r\n"); + String points = ""; + double x1 = 0; + double y1 = 0; + String labels = null; + final double[] curseries = (double[]) series.get(i); + for (int j = 0; j < curseries.length; j++) { + x1 = (x) + j * xfactor; + final double yval = curseries[j]; // areapoints[j][i]; // + // current point + points += ((x) + (j) * xfactor) + "," + + ((y + h) - (yval * yfactor)); - if (j == 0) { - y1 = ((y + h) - (yval * yfactor)); // end point (==start - // point) for path - // statement below - } - points += " "; - // DATA LABELS - final String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j] - .toString()); - if (l != null) { - // if only category label, center over all series; anything - // else, position at data point - final boolean showCategories = (dls[i] - & AttachedLabel.CATEGORYLABEL) == AttachedLabel.CATEGORYLABEL; - final boolean showValueLabel = (dls[i] - & AttachedLabel.VALUELABEL) == AttachedLabel.VALUELABEL; - final boolean showValue = (dls[i] - & AttachedLabel.VALUE) == AttachedLabel.VALUE; - if (showCategories && !(showValue || showValueLabel) - && j == 0) { // only 1 label, centered along - // category axis within area - final double hh = y1; // (areapoints[areapoints.length/2][i]*yfactor); - final double yy = (y + h) - hh + 10; - if (labels == null) { - labels = ""; - } - labels = "" + l - + "\r\n"; - } else if (showValue || showValueLabel) { // labels at each - // data point - if (labels == null) { - labels = ""; - } - final double yy = (((y + h) - - ((yval - (curseries[j] * .5)) * yfactor))); - labels += "" - + l + "\r\n"; - } - } - } - // pointsends connects up area to beginning - final double x0 = x; - final String pointsend = x1 + "," + (y + h) + " " + x0 + "," - + (y + h) + " " + x0 + "," + y1; - // String clr= getDarkColor(); - /* - * try { clr= - * FormatConstants.SVGCOLORSTRINGS[seriescolors[i]]; } - * catch(ArrayIndexOutOfBoundsException e) {; } - */ - svg.append("\r\n"); + if (j == 0) { + y1 = ((y + h) - (yval * yfactor)); // end point (==start + // point) for path + // statement below + } + points += " "; + // DATA LABELS + final String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j] + .toString()); + if (l != null) { + // if only category label, center over all series; anything + // else, position at data point + final boolean showCategories = (dls[i] + & AttachedLabel.CATEGORYLABEL) == AttachedLabel.CATEGORYLABEL; + final boolean showValueLabel = (dls[i] + & AttachedLabel.VALUELABEL) == AttachedLabel.VALUELABEL; + final boolean showValue = (dls[i] + & AttachedLabel.VALUE) == AttachedLabel.VALUE; + if (showCategories && !(showValue || showValueLabel) + && j == 0) { // only 1 label, centered along + // category axis within area + final double hh = y1; // (areapoints[areapoints.length/2][i]*yfactor); + final double yy = (y + h) - hh + 10; + if (labels == null) { + labels = ""; + } + labels = "" + l + + "\r\n"; + } else if (showValue || showValueLabel) { // labels at each + // data point + if (labels == null) { + labels = ""; + } + final double yy = (((y + h) + - ((yval - (curseries[j] * .5)) * yfactor))); + labels += "" + + l + "\r\n"; + } + } + } + // pointsends connects up area to beginning + final double x0 = x; + final String pointsend = x1 + "," + (y + h) + " " + x0 + "," + + (y + h) + " " + x0 + "," + y1; + // String clr= getDarkColor(); + /* + * try { clr= + * FormatConstants.SVGCOLORSTRINGS[seriescolors[i]]; } + * catch(ArrayIndexOutOfBoundsException e) {; } + */ + svg.append("\r\n"); - // Now print data labels, if any - if (labels != null) { - svg.append(labels); - } - svg.append("\r\n"); - } - return svg.toString(); - } + // Now print data labels, if any + if (labels != null) { + svg.append(labels); + } + svg.append("\r\n"); + } + return svg.toString(); + } - /** gets the chart-type specific ooxml representation: - * - * @return - */ - @Override - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - final StringBuffer cooxml = new StringBuffer(); + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + @Override + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + final StringBuffer cooxml = new StringBuffer(); - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? + if (this.is100PercentStacked()) { + cooxml.append("percentStacked"); + } else if (this.isStacked()) { + cooxml.append("stacked"); + // } else if (this.isClustered()) + // grouping="clustered"; + } else { + cooxml.append("standard"); + } + cooxml.append("\"/>"); + cooxml.append("\r\n"); + // vary colors??? - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries() - .getOOXML(this.getChartType(), false, 0)); + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries() + .getOOXML(this.getChartType(), false, 0)); - // TODO: FINISH - // chart data labels, if any - // cooxml.append(getDataLabelsOOXML(cf)); - if (this.cf.getChartLinesRec() != null) { - cooxml.append(this.cf.getChartLinesRec().getOOXML()); - } + // TODO: FINISH + // chart data labels, if any + // cooxml.append(getDataLabelsOOXML(cf)); + if (this.cf.getChartLinesRec() != null) { + cooxml.append(this.cf.getChartLinesRec().getOOXML()); + } - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); - return cooxml; - } + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/AreaFormat.java b/src/main/java/io/starter/formats/XLS/charts/AreaFormat.java index f12b8a2..227b416 100644 --- a/src/main/java/io/starter/formats/XLS/charts/AreaFormat.java +++ b/src/main/java/io/starter/formats/XLS/charts/AreaFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,130 +23,133 @@ package io.starter.formats.XLS.charts; +import io.starter.OpenXLS.FormatHandle; import io.starter.formats.OOXML.FillGroup; import io.starter.formats.OOXML.SpPr; +import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -import io.starter.formats.XLS.FormatConstants; -import io.starter.OpenXLS.FormatHandle;; /** * AreaFormat: Colors and Patterns for an area(0x100a) - * + *

                * 4 rgbFore 4 FgColor RGB Value (High byte is 0) * 8 rgbBack 4 Bg Color "" * 12 fls 2 Pattern * 14 grbit 2 flags * 16 icvFore 2 index to fg color * 18 icvBack 2 index to bg color - * + *

                * grbit: - * 0 0x1 fAuto Automatic Format - * 1 0x2 fInvertNeg Fg + Bg is swapped when data is neg. - * + * 0 0x1 fAuto Automatic Format + * 1 0x2 fInvertNeg Fg + Bg is swapped when data is neg. */ /** * more info on colors: - * + * * The chart color table is a subset of the full color table. * icv (2 bytes): An Icv that specifies a color from the chart color table. * MUST be greater than or equal to 0x0008 and less than or equal to 0x003F, or greater than or equal to 0x004D and less than or equal to 0x004F. - * + * * This info is not yet verified: - * + * * For icvFore, icvBack, must be either 0-7 or 0x40 or 0x41 or icv???? * "The default value of this field is selected automatically from the next available color in the Chart color table." - * + * * For icvBack, must be either 0-7 or 0x40 or 0x41 * The default value of this field is 0x0009. - * + * * icv (chart color table index): > 0x0008 < 003F OR 0x4D, 0x4E, 0x4F OR 0x7FFF - * + * * 0-7 are bascially the normal COLORTABLE entries * The icv values greater than or equal to 0x0008 and less than or equal to 0x003F, specify the palette colors in the table. * If a Palette record exists in this file, these icv values specify colors from the rgColor array in the Palette record. * If no Palette record exists, these values specify colors in the default palette. - * + * * The next 56 values in this part of the color table are specified as follows: * 0x0040 - Default foreground color. This is the window text color in the data sheet display. - 0x0041 - Default background color. This is the window background color in the data sheet display and is the default background color for a cell. - 0x004D - Default chart foreground color. This is the window text color in the chart display. - 0x004E - Default chart background color. This is the window background color in the chart display. - 0x004F - Chart neutral color which is black, an RGB value of (0,0,0). - 0x7FFF - Font automatic color. This is the window text color. + Default foreground color. This is the window text color in the data sheet display. + 0x0041 + Default background color. This is the window background color in the data sheet display and is the default background color for a cell. + 0x004D + Default chart foreground color. This is the window text color in the chart display. + 0x004E + Default chart background color. This is the window background color in the chart display. + 0x004F + Chart neutral color which is black, an RGB value of (0,0,0). + 0x7FFF + Font automatic color. This is the window text color. */ public class AreaFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -437132913972684937L; - private java.awt.Color rgbFore, rgbBack; - private short fls= 0; - private short grbit= 0; - private short icvFore= 0; - private short icvBack= 0; - boolean fAuto, fInvertNeg; - public void init() { - super.init(); - byte[] data= this.getData(); - grbit= ByteTools.readShort(data[10], data[11]); - fAuto= (grbit & 0x1)==0x1; - fInvertNeg= (grbit & 0x2)==0x2; - rgbFore= new java.awt.Color((data[0]<0?255+data[0]:data[0]), (data[1]<0?255+data[1]:data[1]), (data[2]<0?255+data[2]:data[2])); - rgbBack= new java.awt.Color((data[4]<0?255+data[4]:data[4]), (data[5]<0?255+data[5]:data[5]), (data[6]<0?255+data[6]:data[6])); - fls= ByteTools.readShort(data[8], data[9]); - icvFore= ByteTools.readShort(data[12], data[13]); - icvBack= ByteTools.readShort(data[14], data[15]); - } - // 20070716 KSC: Need to create new records + /** + * serialVersionUID + */ + private static final long serialVersionUID = -437132913972684937L; + private java.awt.Color rgbFore, rgbBack; + private short fls = 0; + private short grbit = 0; + private short icvFore = 0; + private short icvBack = 0; + boolean fAuto, fInvertNeg; + + public void init() { + super.init(); + byte[] data = this.getData(); + grbit = ByteTools.readShort(data[10], data[11]); + fAuto = (grbit & 0x1) == 0x1; + fInvertNeg = (grbit & 0x2) == 0x2; + rgbFore = new java.awt.Color((data[0] < 0 ? 255 + data[0] : data[0]), (data[1] < 0 ? 255 + data[1] : data[1]), (data[2] < 0 ? 255 + data[2] : data[2])); + rgbBack = new java.awt.Color((data[4] < 0 ? 255 + data[4] : data[4]), (data[5] < 0 ? 255 + data[5] : data[5]), (data[6] < 0 ? 255 + data[6] : data[6])); + fls = ByteTools.readShort(data[8], data[9]); + icvFore = ByteTools.readShort(data[12], data[13]); + icvBack = ByteTools.readShort(data[14], data[15]); + } + + // 20070716 KSC: Need to create new records public static XLSRecord getPrototype() { AreaFormat af = new AreaFormat(); af.setOpcode(AREAFORMAT); af.setData(af.PROTOTYPE_BYTES); af.init(); return af; - } // changed prototype bytes to ensure has default settings i.e. no pattern and automatic color - private byte[] PROTOTYPE_BYTES = new byte[] {0,0,0,0, 0,0,0,0, 0,0, 1,0, 77,0,77,0}; - private byte[] PROTOTYPE_BYTES_1 = new byte[] {-64, -64, -64, 0, 0, 0, 0, 0, 1, 0, 0, 0, 22, 0, 79, 0}; - private byte[] PROTOTYPE_BYTES_2 = new byte[] {0, 0, 0, 0, -1, -1, -1, 0, 1, 0, 1, 0, 77, 0, 78, 0}; + } // changed prototype bytes to ensure has default settings i.e. no pattern and automatic color + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 77, 0, 77, 0}; + private byte[] PROTOTYPE_BYTES_1 = new byte[]{-64, -64, -64, 0, 0, 0, 0, 0, 1, 0, 0, 0, 22, 0, 79, 0}; + private byte[] PROTOTYPE_BYTES_2 = new byte[]{0, 0, 0, 0, -1, -1, -1, 0, 1, 0, 1, 0, 77, 0, 78, 0}; + public static XLSRecord getPrototype(int type) { AreaFormat af = new AreaFormat(); af.setOpcode(AREAFORMAT); - if (type==0) - af.setData(af.PROTOTYPE_BYTES_1); // default is certain color combo - else if (type==1) - af.setData(af.PROTOTYPE_BYTES_2); // "" + if (type == 0) + af.setData(af.PROTOTYPE_BYTES_1); // default is certain color combo + else if (type == 1) + af.setData(af.PROTOTYPE_BYTES_2); // "" else - af.setData(af.PROTOTYPE_BYTES); + af.setData(af.PROTOTYPE_BYTES); af.init(); return af; } - - + + private void updateRecord() { - byte[] b = new byte[4]; - b[0]= (byte) rgbFore.getRed(); - b[1]= (byte) rgbFore.getGreen(); - b[2]= (byte) rgbFore.getBlue(); - b[3]= 0; // reserved/0 + byte[] b = new byte[4]; + b[0] = (byte) rgbFore.getRed(); + b[1] = (byte) rgbFore.getGreen(); + b[2] = (byte) rgbFore.getBlue(); + b[3] = 0; // reserved/0 System.arraycopy(b, 0, this.getData(), 0, 4); - b[0]= (byte) rgbBack.getRed(); - b[1]= (byte) rgbBack.getGreen(); - b[2]= (byte) rgbBack.getBlue(); - b[3]= 0; // reserved/0 + b[0] = (byte) rgbBack.getRed(); + b[1] = (byte) rgbBack.getGreen(); + b[2] = (byte) rgbBack.getBlue(); + b[3] = 0; // reserved/0 System.arraycopy(b, 0, this.getData(), 4, 4); b = ByteTools.shortToLEBytes(fls); this.getData()[8] = b[0]; this.getData()[9] = b[1]; - grbit= 0; - if (fAuto) grbit= 0x1; - if (fInvertNeg) grbit |= 0x2; + grbit = 0; + if (fAuto) grbit = 0x1; + if (fInvertNeg) grbit |= 0x2; b = ByteTools.shortToLEBytes(grbit); this.getData()[10] = b[0]; this.getData()[11] = b[1]; @@ -157,140 +160,142 @@ private void updateRecord() { this.getData()[14] = b[0]; this.getData()[15] = b[1]; } - - - - public String toString() { return "AreaFormat: Pattern=" + fls + " ForeColor=" + icvFore + " BackColor=" + icvBack + " Automatic Format=" + ((grbit&0x1)==0x1); } + + + public String toString() { + return "AreaFormat: Pattern=" + fls + " ForeColor=" + icvFore + " BackColor=" + icvBack + " Automatic Format=" + ((grbit & 0x1) == 0x1); + } + /** * return the bg color index for this Area Format * @return */ public int geticvBack() { - if (icvBack > this.getColorTable().length) { // then it's one of the special codes: - return FormatHandle.interpretSpecialColorIndex(icvBack); - } - return icvBack; + if (icvBack > this.getColorTable().length) { // then it's one of the special codes: + return FormatHandle.interpretSpecialColorIndex(icvBack); + } + return icvBack; } - + /** * return the area fill color * @return int index into color table */ public int getFillColor() { - if (fls==0) // no fill - return FormatConstants.COLOR_WHITE; - if (fls==1) // solid; forecolor is bg - return geticvFore(); - if (fls==2) // medium grey - return FormatConstants.COLOR_GRAY50; - if (fls==3) // dark grey (possible for more options not yet handled - return FormatConstants.COLOR_GRAY80; - if (fls==4) // light grey - return FormatConstants.COLOR_GRAY25; - // rest are actual fill patterns TODO handle - return geticvFore(); + if (fls == 0) // no fill + return FormatConstants.COLOR_WHITE; + if (fls == 1) // solid; forecolor is bg + return geticvFore(); + if (fls == 2) // medium grey + return FormatConstants.COLOR_GRAY50; + if (fls == 3) // dark grey (possible for more options not yet handled + return FormatConstants.COLOR_GRAY80; + if (fls == 4) // light grey + return FormatConstants.COLOR_GRAY25; + // rest are actual fill patterns TODO handle + return geticvFore(); } - + /** * return the area fill color * @return String color hex string */ public String getFillColorStr() { - if (fAuto) - return null; - if (fls==0) // no fill - return "#FFFFFF"; - if (fls==1) // solid; forecolor is bg - return FormatHandle.colorToHexString(rgbFore); - if (fls==2) // medium grey - return FormatHandle.colorToHexString(FormatConstants.Gray50); - if (fls==3) // dark grey (possible for more options not yet handled - return FormatHandle.colorToHexString(FormatConstants.Gray80); - if (fls==4) // light grey - return FormatHandle.colorToHexString(FormatConstants.Gray25); - // rest are actual fill patterns TODO handle - return FormatHandle.colorToHexString(rgbFore); + if (fAuto) + return null; + if (fls == 0) // no fill + return "#FFFFFF"; + if (fls == 1) // solid; forecolor is bg + return FormatHandle.colorToHexString(rgbFore); + if (fls == 2) // medium grey + return FormatHandle.colorToHexString(FormatConstants.Gray50); + if (fls == 3) // dark grey (possible for more options not yet handled + return FormatHandle.colorToHexString(FormatConstants.Gray80); + if (fls == 4) // light grey + return FormatHandle.colorToHexString(FormatConstants.Gray25); + // rest are actual fill patterns TODO handle + return FormatHandle.colorToHexString(rgbFore); } - + /** * return the fg color index for this Area Format * @return */ - public int geticvFore() { - if (icvFore > this.getColorTable().length) { // then it's one of the special codes: - return FormatHandle.interpretSpecialColorIndex(icvFore); - } - return icvFore; + public int geticvFore() { + if (icvFore > this.getColorTable().length) { // then it's one of the special codes: + return FormatHandle.interpretSpecialColorIndex(icvFore); + } + return icvFore; } - + public void seticvBack(int clr) { - if (clr > -1 && clr < this.getColorTable().length) { - icvBack= (short) clr; - rgbBack= this.getColorTable()[clr]; - updateRecord(); - } else if (clr==0x4D) { // special flag, default bg - icvBack= (short) clr; - rgbBack= this.getColorTable()[0]; - updateRecord(); - } - } - + if (clr > -1 && clr < this.getColorTable().length) { + icvBack = (short) clr; + rgbBack = this.getColorTable()[clr]; + updateRecord(); + } else if (clr == 0x4D) { // special flag, default bg + icvBack = (short) clr; + rgbBack = this.getColorTable()[0]; + updateRecord(); + } + } + /** * sets the fill color for the area * @param clr color index */ public void seticvFore(int clr) { - - // fls= 1 The fill pattern is solid. When solid is specified, - // rgbFore is the only color rendered, even if rgbBack is specified - if (clr > -1 && clr < this.getColorTable().length) { - fAuto= false; - fls= 1; - icvFore= (short) clr; - rgbFore= this.getColorTable()[clr]; - // must also set bg to 9 - seticvBack(9); - //updateRecord(); - } else if (clr==0x4E) { // default fg - icvFore= (short) clr; - rgbFore= this.getColorTable()[1]; - updateRecord(); - } + + // fls= 1 The fill pattern is solid. When solid is specified, + // rgbFore is the only color rendered, even if rgbBack is specified + if (clr > -1 && clr < this.getColorTable().length) { + fAuto = false; + fls = 1; + icvFore = (short) clr; + rgbFore = this.getColorTable()[clr]; + // must also set bg to 9 + seticvBack(9); + //updateRecord(); + } else if (clr == 0x4E) { // default fg + icvFore = (short) clr; + rgbFore = this.getColorTable()[1]; + updateRecord(); + } } - + /** * sets the fill color for the area * @param clr Hex Color String */ public void seticvFore(String clr) { - fAuto= false; - fls= 1; - rgbFore= FormatHandle.HexStringToColor(clr); - icvFore= (short) FormatHandle.HexStringToColorInt(clr, FormatHandle.colorBACKGROUND); // finds best match - updateRecord(); + fAuto = false; + fls = 1; + rgbFore = FormatHandle.HexStringToColor(clr); + icvFore = (short) FormatHandle.HexStringToColorInt(clr, FormatHandle.colorBACKGROUND); // finds best match + updateRecord(); } - - + + /** * sets the OOXML settings for this Area Format * @param sp */ public void setFromOOXML(SpPr sp) { - FillGroup f= sp.getFill(); - if (f!=null) { - this.seticvFore(f.getColor()); - //this.seticvBack() - // fls= fill pattern - - } - } - + FillGroup f = sp.getFill(); + if (f != null) { + this.seticvFore(f.getColor()); + //this.seticvBack() + // fls= fill pattern + + } + } + public StringBuffer getOOXML() { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); // ooxml.append(""); - ooxml.append(""); - return ooxml; + ooxml.append(""); + return ooxml; } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java b/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java index 2363c7a..245399f 100644 --- a/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java +++ b/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -33,179 +33,182 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -/** +/** * AttachedLabel: Series Data/Value Labels (0x100c) - * - * - * bit - * 0 = show value label -- a bit that specifies whether the value, or the vertical value on bubble or scatter chart groups, is displayed in the data label. - * 1 = show value as percentage -- (must be 0 for non-pie) - * 2 = show cat/label as percentage -- A bit that specifies whether the category (3) name and value, - * represented as a percentage of the sum of the values of the series with - * which the data label is associated, are displayed in the data label. (pie charts only) - * 3 = unused - * 4 = show cat or series label -- A bit that specifies whether the category, or the horizontal value on bubble or scatter chart groups, - * is displayed in the data label on a non-area chart group, or the series name is displayed in the data label on an area chart group. - * 5 = show bubble label -- A bit that specifies whether the bubble size is displayed in the data label. - * 6 = show series name -- A bit that specifies whether the data label contains the name of the series. - * + *

                + *

                + * bit + * 0 = show value label -- a bit that specifies whether the value, or the vertical value on bubble or scatter chart groups, is displayed in the data label. + * 1 = show value as percentage -- (must be 0 for non-pie) + * 2 = show cat/label as percentage -- A bit that specifies whether the category (3) name and value, + * represented as a percentage of the sum of the values of the series with + * which the data label is associated, are displayed in the data label. (pie charts only) + * 3 = unused + * 4 = show cat or series label -- A bit that specifies whether the category, or the horizontal value on bubble or scatter chart groups, + * is displayed in the data label on a non-area chart group, or the series name is displayed in the data label on an area chart group. + * 5 = show bubble label -- A bit that specifies whether the bubble size is displayed in the data label. + * 6 = show series name -- A bit that specifies whether the data label contains the name of the series. + *

                * ???? Series DataLabels do not appear to be done via AttachedLabel - * */ public class AttachedLabel extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2532517522176536995L; - public static final int VALUE = 0x1; - public static final int VALUEPERCENT = 0x2; - public static final int CATEGORYPERCENT = 0x4; - public static final int CATEGORYLABEL = 0x10; - public static final int BUBBLELABEL = 0x20; - public static final int VALUELABEL = 0x40; - - private short grbit = 0; - - @Override - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - - public static XLSRecord getPrototype() { - AttachedLabel al = new AttachedLabel(); - al.setOpcode(ATTACHEDLABEL); - al.setData(al.PROTOTYPE_BYTES); - al.init(); - return al; - } - - private byte[] PROTOTYPE_BYTES = new byte[] { 0, 0 }; - - /** - * return a string of all the label options chosen - * @return - */ - public String getType() { - String ret = ""; - if ((grbit & VALUE) == VALUE) // bit 0 - ret = "Value "; - if ((grbit & VALUEPERCENT) == VALUEPERCENT) // bit 1 - ret = ret + "ValuePerecentage "; - if ((grbit & CATEGORYPERCENT) == CATEGORYPERCENT) // bit 2 - ret = ret + "CategoryPercentage "; // Pie only - if ((grbit & CATEGORYLABEL) == CATEGORYLABEL) // bit 4 - ret = ret + "CategoryLabel "; - if ((grbit & BUBBLELABEL) == BUBBLELABEL) // bit 5 - ret = ret + "BubbleLabel "; - if ((grbit & VALUELABEL) == VALUELABEL) // bit 6 - ret = ret + "SeriesLabel "; - return ret.trim(); - } - - /** - * return the data label options as an int - * @see AttachedLabel constants -
                SHOWVALUE= 0x1; -
                SHOWVALUEPERCENT= 0x2; -
                SHOWCATEGORYPERCENT= 0x4; -
                SHOWCATEGORYLABEL= 0x10; -
                SHOWBUBBLELABEL= 0x20; -
                SHOWSERIESLABEL= 0x40; - * @return a combination of data label options above or 0 if none - */ - public int getTypeInt() { - return grbit; - } - - public void setType(short type) { - grbit = type; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - /** - * Show or Hide AttachedLabel Option -
                ShowValueLabel, -
                ShowValueAsPercent, -
                ShowLabelAsPercent, -
                ShowLabel, -
                ShowBubbleLabel -
                ShowSeriesName - * @param val true or 1 to set - */ - public void setType(String type, String val) { - boolean bSet = val.equals("true") || val.equals("1"); - if (type.equals("ShowValueLabel")) - grbit = ByteTools.updateGrBit(grbit, bSet, 0); - if (type.equals("ShowValueAsPercent")) - grbit = ByteTools.updateGrBit(grbit, bSet, 1); - if (type.equals("ShowLabelAsPercent")) - grbit = ByteTools.updateGrBit(grbit, bSet, 2); - if (type.equals("ShowLabel")) - grbit = ByteTools.updateGrBit(grbit, bSet, 4); - if (type.equals("ShowBubbleLabel")) - grbit = ByteTools.updateGrBit(grbit, bSet, 5); - if (type.equals("ShowSeriesName")) - grbit = ByteTools.updateGrBit(grbit, bSet, 6); - byte[] bb = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = bb[0]; - this.getData()[1] = bb[1]; - } - - /** - * return the value of the specified option - * @param type Sting option - * @return string true or false - */ - public String getType(String type) { - boolean b = false; - - if (type.equals("ShowValueLabel")) - b = ((grbit & VALUE) == VALUE); // bit 0 - if (type.equals("ShowValueAsPercent")) - b = ((grbit & VALUEPERCENT) == VALUEPERCENT); - if (type.equals("ShowLabelAsPercent")) - b = (((grbit & CATEGORYPERCENT) == CATEGORYPERCENT)); - if (type.equals("ShowLabel")) - b = (((grbit & CATEGORYLABEL) == CATEGORYLABEL)); - if (type.equals("ShowBubbleLabel")) - b = (((grbit & BUBBLELABEL) == BUBBLELABEL)); - if (type.equals("ShowSeriesName")) - b = (((grbit & VALUELABEL) == VALUELABEL)); - return (b) ? ("1") : ("0"); - } - - /** - * @deprecated - * @param type - */ - @Deprecated - public void setType(String type) { - short t = 0; - if (type.equalsIgnoreCase("Value") || type.equalsIgnoreCase("Y Value")) - t = 1; - else if (type.equalsIgnoreCase("ValuePercentage")) - t = 2; - else if (type.equalsIgnoreCase("CategoryPercentage")) - t = 3; - else if (type.equalsIgnoreCase("Category") - || type.equalsIgnoreCase("X Value")) - t = 16; - else if (type.equalsIgnoreCase("CandP")) - t = 22; - else if (type.equalsIgnoreCase("Bubble")) - t = 32; - grbit = t; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - @Override - public String toString() { - return this.getType(); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2532517522176536995L; + public static final int VALUE = 0x1; + public static final int VALUEPERCENT = 0x2; + public static final int CATEGORYPERCENT = 0x4; + public static final int CATEGORYLABEL = 0x10; + public static final int BUBBLELABEL = 0x20; + public static final int VALUELABEL = 0x40; + + private short grbit = 0; + + @Override + public void init() { + super.init(); + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + } + + public static XLSRecord getPrototype() { + AttachedLabel al = new AttachedLabel(); + al.setOpcode(ATTACHEDLABEL); + al.setData(al.PROTOTYPE_BYTES); + al.init(); + return al; + } + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; + + /** + * return a string of all the label options chosen + * + * @return + */ + public String getType() { + String ret = ""; + if ((grbit & VALUE) == VALUE) // bit 0 + ret = "Value "; + if ((grbit & VALUEPERCENT) == VALUEPERCENT) // bit 1 + ret = ret + "ValuePerecentage "; + if ((grbit & CATEGORYPERCENT) == CATEGORYPERCENT) // bit 2 + ret = ret + "CategoryPercentage "; // Pie only + if ((grbit & CATEGORYLABEL) == CATEGORYLABEL) // bit 4 + ret = ret + "CategoryLabel "; + if ((grbit & BUBBLELABEL) == BUBBLELABEL) // bit 5 + ret = ret + "BubbleLabel "; + if ((grbit & VALUELABEL) == VALUELABEL) // bit 6 + ret = ret + "SeriesLabel "; + return ret.trim(); + } + + /** + * return the data label options as an int + * + * @return a combination of data label options above or 0 if none + * @see AttachedLabel constants + *
                SHOWVALUE= 0x1; + *
                SHOWVALUEPERCENT= 0x2; + *
                SHOWCATEGORYPERCENT= 0x4; + *
                SHOWCATEGORYLABEL= 0x10; + *
                SHOWBUBBLELABEL= 0x20; + *
                SHOWSERIESLABEL= 0x40; + */ + public int getTypeInt() { + return grbit; + } + + public void setType(short type) { + grbit = type; + byte[] b = ByteTools.shortToLEBytes(grbit); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } + + /** + * Show or Hide AttachedLabel Option + *
                ShowValueLabel, + *
                ShowValueAsPercent, + *
                ShowLabelAsPercent, + *
                ShowLabel, + *
                ShowBubbleLabel + *
                ShowSeriesName + * + * @param val true or 1 to set + */ + public void setType(String type, String val) { + boolean bSet = val.equals("true") || val.equals("1"); + if (type.equals("ShowValueLabel")) + grbit = ByteTools.updateGrBit(grbit, bSet, 0); + if (type.equals("ShowValueAsPercent")) + grbit = ByteTools.updateGrBit(grbit, bSet, 1); + if (type.equals("ShowLabelAsPercent")) + grbit = ByteTools.updateGrBit(grbit, bSet, 2); + if (type.equals("ShowLabel")) + grbit = ByteTools.updateGrBit(grbit, bSet, 4); + if (type.equals("ShowBubbleLabel")) + grbit = ByteTools.updateGrBit(grbit, bSet, 5); + if (type.equals("ShowSeriesName")) + grbit = ByteTools.updateGrBit(grbit, bSet, 6); + byte[] bb = ByteTools.shortToLEBytes(grbit); + this.getData()[0] = bb[0]; + this.getData()[1] = bb[1]; + } + + /** + * return the value of the specified option + * + * @param type Sting option + * @return string true or false + */ + public String getType(String type) { + boolean b = false; + + if (type.equals("ShowValueLabel")) + b = ((grbit & VALUE) == VALUE); // bit 0 + if (type.equals("ShowValueAsPercent")) + b = ((grbit & VALUEPERCENT) == VALUEPERCENT); + if (type.equals("ShowLabelAsPercent")) + b = (((grbit & CATEGORYPERCENT) == CATEGORYPERCENT)); + if (type.equals("ShowLabel")) + b = (((grbit & CATEGORYLABEL) == CATEGORYLABEL)); + if (type.equals("ShowBubbleLabel")) + b = (((grbit & BUBBLELABEL) == BUBBLELABEL)); + if (type.equals("ShowSeriesName")) + b = (((grbit & VALUELABEL) == VALUELABEL)); + return (b) ? ("1") : ("0"); + } + + /** + * @param type + * @deprecated + */ + @Deprecated + public void setType(String type) { + short t = 0; + if (type.equalsIgnoreCase("Value") || type.equalsIgnoreCase("Y Value")) + t = 1; + else if (type.equalsIgnoreCase("ValuePercentage")) + t = 2; + else if (type.equalsIgnoreCase("CategoryPercentage")) + t = 3; + else if (type.equalsIgnoreCase("Category") + || type.equalsIgnoreCase("X Value")) + t = 16; + else if (type.equalsIgnoreCase("CandP")) + t = 22; + else if (type.equalsIgnoreCase("Bubble")) + t = 32; + grbit = t; + byte[] b = ByteTools.shortToLEBytes(grbit); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } + + @Override + public String toString() { + return this.getType(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Axcent.java b/src/main/java/io/starter/formats/XLS/charts/Axcent.java index 712ee6d..cbefddc 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Axcent.java +++ b/src/main/java/io/starter/formats/XLS/charts/Axcent.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,77 +27,111 @@ /** * Axcent: Axis Options(0x1062) - * - * 4 catMin 2 minimum date on axis. - * If fAutoMin is set to 1, MUST be ignored. - * If fDateAxis is set to 0, MUST be ignored. - * 6 catMax 2 maximum date on axis. - * fAutoMax is set to 1, MUST be ignored. - * If fDateAxis is set to 0, MUST be ignored. - * 8 catMajor 2 value of major unit - * MUST be greater than or equal to catMinor when duMajor is equal to duMinor. - * If fAutoMajor is set to 1, MUST be ignored. - * If fDateAxis is set to 0, MUST be ignored. - * 10 duMajor 2 Date Enumeration specifies unit of time for use of catMajor - * If fDateAxis is set to 0, MUST be ignored. - * 12 catMinor 2 value of minor unit - * 14 duMinor 2 time units of minor unit - * If fDateAxis is set to 0, MUST be ignored. - * 16 duBase 2 smallest unit of time used by the axis. - * If fAutoBase is set to 1, this field MUST be ignored. - * If fDateAxis is set to 0, MUST be ignored. - * 18 catCrossDate 2 crossing point of value axis (date) - * 20 grbit 2 - * - * 0 0x1 fAutoMin 1= use default - * 1 0x2 fAutoMax "" - * 2 0x4 fAutoMajor "" - * 3 0x8 fAutoMinor "" - * 4 0x10 fdateAxis 1= this is a date axis - * 5 0x20 fAutoBase 1= use default base - * 6 0x40 fAutoCross "" - * 7 0x80 fAutoDate 1= use default date settings for axis - * + *

                + * 4 catMin 2 minimum date on axis. + * If fAutoMin is set to 1, MUST be ignored. + * If fDateAxis is set to 0, MUST be ignored. + * 6 catMax 2 maximum date on axis. + * fAutoMax is set to 1, MUST be ignored. + * If fDateAxis is set to 0, MUST be ignored. + * 8 catMajor 2 value of major unit + * MUST be greater than or equal to catMinor when duMajor is equal to duMinor. + * If fAutoMajor is set to 1, MUST be ignored. + * If fDateAxis is set to 0, MUST be ignored. + * 10 duMajor 2 Date Enumeration specifies unit of time for use of catMajor + * If fDateAxis is set to 0, MUST be ignored. + * 12 catMinor 2 value of minor unit + * 14 duMinor 2 time units of minor unit + * If fDateAxis is set to 0, MUST be ignored. + * 16 duBase 2 smallest unit of time used by the axis. + * If fAutoBase is set to 1, this field MUST be ignored. + * If fDateAxis is set to 0, MUST be ignored. + * 18 catCrossDate 2 crossing point of value axis (date) + * 20 grbit 2 + *

                + * 0 0x1 fAutoMin 1= use default + * 1 0x2 fAutoMax "" + * 2 0x4 fAutoMajor "" + * 3 0x8 fAutoMinor "" + * 4 0x10 fdateAxis 1= this is a date axis + * 5 0x20 fAutoBase 1= use default base + * 6 0x40 fAutoCross "" + * 7 0x80 fAutoDate 1= use default date settings for axis */ public class Axcent extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -660100252646337769L; - short catMin, catMax, catMajor, duMajor, catMinor, duMinor, duBase, catCrossDate, grbit; - public void init() { - super.init(); - // 20071223 KSC: Start parsing of options - catMin= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - catMax= ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - catMajor= ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - duMajor= ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - catMinor= ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - duMinor= ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - duBase= ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - catCrossDate= ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); - grbit= ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); - } - // 20070723 KSC: Need to create new records + /** + * serialVersionUID + */ + private static final long serialVersionUID = -660100252646337769L; + short catMin, catMax, catMajor, duMajor, catMinor, duMinor, duBase, catCrossDate, grbit; + + public void init() { + super.init(); + // 20071223 KSC: Start parsing of options + catMin = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + catMax = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + catMajor = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + duMajor = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + catMinor = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + duMinor = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); + duBase = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + catCrossDate = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); + grbit = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); + } + + // 20070723 KSC: Need to create new records public static XLSRecord getPrototype() { - Axcent a = new Axcent(); + Axcent a = new Axcent(); a.setOpcode(AXCENT); a.setData(a.PROTOTYPE_BYTES); - a.init(); // important when we parse these options ... + a.init(); // important when we parse these options ... return a; } - private byte[] PROTOTYPE_BYTES = new byte[] {0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -17, 0}; - + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -17, 0}; + // 20071223 KSC: add access methods - public short getCatMin() { return catMin; } - public short getCatMax() { return catMax; } - public short getCatMajor() { return catMajor; } - public short getDuMajor() { return duMajor; } - public short getCatMinor() { return catMinor; } - public short getDuMinor() { return duMinor; } - public short getDuBase() { return duBase; } - public boolean isDefaultMin() { return ((grbit & 0x1)==0x1); } - public boolean isDefaultMax() { return ((grbit & 0x2)==0x2); } - public boolean isDefaultMajorUnits() { return ((grbit & 0x4)==0x4); } - public boolean isDefaultMinorUnits() { return ((grbit & 0x8)==0x8); } + public short getCatMin() { + return catMin; + } + + public short getCatMax() { + return catMax; + } + + public short getCatMajor() { + return catMajor; + } + + public short getDuMajor() { + return duMajor; + } + + public short getCatMinor() { + return catMinor; + } + + public short getDuMinor() { + return duMinor; + } + + public short getDuBase() { + return duBase; + } + + public boolean isDefaultMin() { + return ((grbit & 0x1) == 0x1); + } + + public boolean isDefaultMax() { + return ((grbit & 0x2) == 0x2); + } + + public boolean isDefaultMajorUnits() { + return ((grbit & 0x4) == 0x4); + } + + public boolean isDefaultMinorUnits() { + return ((grbit & 0x8) == 0x8); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Axesused.java b/src/main/java/io/starter/formats/XLS/charts/Axesused.java index 1c525f5..b9d54fa 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Axesused.java +++ b/src/main/java/io/starter/formats/XLS/charts/Axesused.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -31,18 +31,18 @@ /** * Axesused: Number of Axes Sets (0x1046) - * - * 4 cAxes 2 number of axes sets + *

                + * 4 cAxes 2 number of axes sets */ public class Axesused extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4809979818183513617L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4809979818183513617L; - @Override - public void init() { - super.init(); - } + @Override + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Axis.java b/src/main/java/io/starter/formats/XLS/charts/Axis.java index 33b00ac..e3d6127 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Axis.java +++ b/src/main/java/io/starter/formats/XLS/charts/Axis.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,1126 +22,1176 @@ */ package io.starter.formats.XLS.charts; -import java.util.Stack; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.ChartHandle; import io.starter.OpenXLS.WorkBookHandle; +import io.starter.formats.OOXML.*; import io.starter.formats.XLS.BiffRec; import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.Format; -import io.starter.formats.XLS.FormatConstantsImpl; import io.starter.formats.XLS.XLSRecord; import io.starter.formats.cellformat.CellFormatFactory; -//OOXML-specific structures -import io.starter.formats.OOXML.*; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; + +//OOXML-specific structures //OOXML-specific structures -import io.starter.formats.OOXML.SpPr; /** - * - * Axis: Axis Type (0x101d) - * - * 4 wType 2 axis type (0= category axis or x axis on a scatter chart, 1= value axis, 2= series axis - * 6 (reserved) 16 0 - * - * Order of Axis Subrecords: - * X (Cat Axis) - * - * CatSerRange - * AxcExt - * CatLab - * [IfmtRecord] - * [Tick] - * [FontX] - * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor - * [AreaFormat, GelFrame] - * [Shape or TextPropsStream] - * [CrtMltFrt] - * - * Y (Value Axis) - * ValueRange - * [YMult -->Text-->Pos...] -- display units - * [IfmtRecord] - * [Tick] - * [FontX] - * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor - * [AreaFormat, GelFrame] - * [Shape or TextPropsStream] - * [CrtMltFrt] - * - * XY (Series Axis) - * CatSerRange - * [IfmtRecord] - * [Tick] - * [FontX] - * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor - * [AreaFormat, GelFrame] - * [Shape or TextPropsStream] - * [CrtMltFrt] - * + * Axis: Axis Type (0x101d) + *

                + * 4 wType 2 axis type (0= category axis or x axis on a scatter chart, 1= value axis, 2= series axis + * 6 (reserved) 16 0 + *

                + * Order of Axis Subrecords: + * X (Cat Axis) + *

                + * CatSerRange + * AxcExt + * CatLab + * [IfmtRecord] + * [Tick] + * [FontX] + * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor + * [AreaFormat, GelFrame] + * [Shape or TextPropsStream] + * [CrtMltFrt] + *

                + * Y (Value Axis) + * ValueRange + * [YMult -->Text-->Pos...] -- display units + * [IfmtRecord] + * [Tick] + * [FontX] + * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor + * [AreaFormat, GelFrame] + * [Shape or TextPropsStream] + * [CrtMltFrt] + *

                + * XY (Series Axis) + * CatSerRange + * [IfmtRecord] + * [Tick] + * [FontX] + * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor + * [AreaFormat, GelFrame] + * [Shape or TextPropsStream] + * [CrtMltFrt] */ /* General info on Excel axis types: - * In Microsoft Excel charts, there are different types of X axes. - * While the Y axis is a Value type axis (i.e. containing a ValueRange record), - * the X axis can be a Category type axis or a Value type axis. - * Using a Value axis, the data is treated as continuously varying numerical data, - * and the marker is placed at a point along the axis which varies according to its - * numerical value. - * Using a Category axis, the data is treated as a sequence of non-numerical text labels, - * and the marker is placed at a point along the axis according to its position in - * the sequence. - * + * In Microsoft Excel charts, there are different types of X axes. + * While the Y axis is a Value type axis (i.e. containing a ValueRange record), + * the X axis can be a Category type axis or a Value type axis. + * Using a Value axis, the data is treated as continuously varying numerical data, + * and the marker is placed at a point along the axis which varies according to its + * numerical value. + * Using a Category axis, the data is treated as a sequence of non-numerical text labels, + * and the marker is placed at a point along the axis according to its position in + * the sequence. + * * Note that Scatter (x/y) charts and Bubble charts have two Value axes, pie and donut charts have no axes - * - * How do you arrange your chart so the categories are displayed along the Y axis? - * The method involves adding a dummy series along the Y axis, - * applying data labels to its points for category labels, + * + * How do you arrange your chart so the categories are displayed along the Y axis? + * The method involves adding a dummy series along the Y axis, + * applying data labels to its points for category labels, * and making the original Y axis disappear. */ public class Axis extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8592219101790307789L; - short wType= 0; - private TextDisp linkedtd= null; // 20070730 KSC: hold axis legend TextDisp for this axis - private AxisParent ap= null; // 20090108 KSC: links to this axes parent -- - // Axis placement - public static final int INVISIBLE= 0; - public static final int LOW= 1; - public static final int HIGH= 2; - public static final int NEXTTO= 3; - // OOXML-specific - private SpPr shapeProps= null; // 20081224 KSC: OOXML-specific holds the shape properties (line and fill) for this axis - private Title ttl= null; // OOXML title element - private TxPr txpr= null; // text properties for axis - private NumFmt nf= null; // NumFmt prop for axis - String axPos= null; - - public void init() { - super.init(); - wType= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - private byte[] PROTOTYPE_BYTES = new byte[] {0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0}; - - public short getAxis() { return wType; } - public void setAxis(int wType) { - this.wType= (short) wType; - if (wType==XVALAXIS) wType= XAXIS; // 20090108 KSC: XVALAXIS is type of X axis with VAL records - byte[] b = ByteTools.shortToLEBytes((short)wType); + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8592219101790307789L; + short wType = 0; + private TextDisp linkedtd = null; // 20070730 KSC: hold axis legend TextDisp for this axis + private AxisParent ap = null; // 20090108 KSC: links to this axes parent -- + // Axis placement + public static final int INVISIBLE = 0; + public static final int LOW = 1; + public static final int HIGH = 2; + public static final int NEXTTO = 3; + // OOXML-specific + private SpPr shapeProps = null; // 20081224 KSC: OOXML-specific holds the shape properties (line and fill) for this axis + private Title ttl = null; // OOXML title element + private TxPr txpr = null; // text properties for axis + private NumFmt nf = null; // NumFmt prop for axis + String axPos = null; + + public void init() { + super.init(); + wType = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + } + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + public short getAxis() { + return wType; + } + + public void setAxis(int wType) { + this.wType = (short) wType; + if (wType == XVALAXIS) wType = XAXIS; // 20090108 KSC: XVALAXIS is type of X axis with VAL records + byte[] b = ByteTools.shortToLEBytes((short) wType); this.getData()[0] = b[0]; this.getData()[1] = b[1]; } - + public static XLSRecord getPrototype(int wType) { Axis a = new Axis(); a.setOpcode(AXIS); a.setData(a.PROTOTYPE_BYTES); // if (wType!=XVALAXIS) - a.setAxis(wType); + a.setAxis(wType); // else // a.setAxis(XAXIS); // also add the associated records switch (wType) { - case XAXIS: - a.addChartRecord((CatserRange) CatserRange.getPrototype()); - a.addChartRecord((Axcent) Axcent.getPrototype()); - a.addChartRecord((Tick) Tick.getPrototype()); - break; - case YAXIS: - case XVALAXIS: - a.addChartRecord((ValueRange) ValueRange.getPrototype()); - a.addChartRecord((Tick) Tick.getPrototype()); - AxisLineFormat alf= (AxisLineFormat) AxisLineFormat.getPrototype(); - alf.setId(AxisLineFormat.ID_MAJOR_GRID); // default has major gridlines - a.addChartRecord(alf); - a.addChartRecord((LineFormat) LineFormat.getPrototype()); - break; - case ZAXIS: - // KSC: TODO: Set CatserRange options correctly when get def!!! ********** - CatserRange c= (CatserRange) CatserRange.getPrototype(); - a.addChartRecord(c); - a.addChartRecord((Tick) Tick.getPrototype()); // TODO: Tick should have - break; - } + case XAXIS: + a.addChartRecord(CatserRange.getPrototype()); + a.addChartRecord(Axcent.getPrototype()); + a.addChartRecord(Tick.getPrototype()); + break; + case YAXIS: + case XVALAXIS: + a.addChartRecord(ValueRange.getPrototype()); + a.addChartRecord(Tick.getPrototype()); + AxisLineFormat alf = (AxisLineFormat) AxisLineFormat.getPrototype(); + alf.setId(AxisLineFormat.ID_MAJOR_GRID); // default has major gridlines + a.addChartRecord(alf); + a.addChartRecord(LineFormat.getPrototype()); + break; + case ZAXIS: + // KSC: TODO: Set CatserRange options correctly when get def!!! ********** + CatserRange c = (CatserRange) CatserRange.getPrototype(); + a.addChartRecord(c); + a.addChartRecord(Tick.getPrototype()); // TODO: Tick should have + break; + } return a; } - - /** + + /** * @return String XML representation of this chart-type's options */ public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - boolean hasMajorGridLines= false; - // Axis 0: CatserRange, Axcent, Tick [AxisLineFormat, LineFormat, AreaFormat] last 3 recs are for 3d formatting - // Axis 1: ValueRange, Tick, AxisLineFormat, LineFormat [AreaFormat, LineFormat, AreaFormat] " " + StringBuffer sb = new StringBuffer(); + boolean hasMajorGridLines = false; + // Axis 0: CatserRange, Axcent, Tick [AxisLineFormat, LineFormat, AreaFormat] last 3 recs are for 3d formatting + // Axis 1: ValueRange, Tick, AxisLineFormat, LineFormat [AreaFormat, LineFormat, AreaFormat] " " // Axis 2: [CatserRange, Tick] Z axis, for surface charts (only??) - for (int i= 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec)chartArr.get(i); + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); // Handle subordinate record options here rather than in the specific rec if (b instanceof CatserRange) { - CatserRange c= ((CatserRange) b); - // record deviations from defaults - if (c.getCatCross()!=1) - sb.append(" CatCross=\"" + c.getCatCross() + "\""); - if (c.getCatLabel()!=1) - sb.append(" LabelCross=\"" + c.getCatLabel() + "\""); - if (c.getCatMark()!=1) - sb.append(" Marks=\"" + c.getCatMark() + "\""); - if (!c.getCrossBetween()) - sb.append(" CrossBetween=\"false\""); - if (c.getCrossMax()) - sb.append(" CrossMax=\"true\""); - } else if (b instanceof AxisLineFormat) { // necessary for 3d charts and bg colors ... - int id= ((AxisLineFormat)b).getId(); - if (id==AxisLineFormat.ID_MAJOR_GRID) // Y Axis draw gridline usually true, trap if do not have (see below) - hasMajorGridLines= true; - else if (id==AxisLineFormat.ID_WALLORFLOOR) // indicates area - sb.append(" AddArea=\"true\""); + CatserRange c = ((CatserRange) b); + // record deviations from defaults + if (c.getCatCross() != 1) + sb.append(" CatCross=\"" + c.getCatCross() + "\""); + if (c.getCatLabel() != 1) + sb.append(" LabelCross=\"" + c.getCatLabel() + "\""); + if (c.getCatMark() != 1) + sb.append(" Marks=\"" + c.getCatMark() + "\""); + if (!c.getCrossBetween()) + sb.append(" CrossBetween=\"false\""); + if (c.getCrossMax()) + sb.append(" CrossMax=\"true\""); + } else if (b instanceof AxisLineFormat) { // necessary for 3d charts and bg colors ... + int id = ((AxisLineFormat) b).getId(); + if (id == AxisLineFormat.ID_MAJOR_GRID) // Y Axis draw gridline usually true, trap if do not have (see below) + hasMajorGridLines = true; + else if (id == AxisLineFormat.ID_WALLORFLOOR) // indicates area + sb.append(" AddArea=\"true\""); } else if (b instanceof AreaFormat) { // should ONLY be present if AxisLineFormat AddArea - // if not default background colors, record and reset - int icvFore= ((AreaFormat)b).geticvFore(); - int icvBack= ((AreaFormat)b).geticvBack(); - if (icvBack==-1) icvBack= 0x4D; - if (icvFore==1) icvFore= 0x4E; - // if not defaults, set fore and back of walls, sides or floor - if ((wType==0 && icvFore!=22) // xaxis - || (wType==1 && icvFore!=-1)) // yaxis - sb.append(" AreaFg=\"" + icvFore + "\""); - if ((wType==0 && icvBack!=0) // xaxis - || (wType==1 && icvBack!=1)) // yaxis - sb.append(" AreaBg=\"" + icvBack + "\""); + // if not default background colors, record and reset + int icvFore = ((AreaFormat) b).geticvFore(); + int icvBack = ((AreaFormat) b).geticvBack(); + if (icvBack == -1) icvBack = 0x4D; + if (icvFore == 1) icvFore = 0x4E; + // if not defaults, set fore and back of walls, sides or floor + if ((wType == 0 && icvFore != 22) // xaxis + || (wType == 1 && icvFore != -1)) // yaxis + sb.append(" AreaFg=\"" + icvFore + "\""); + if ((wType == 0 && icvBack != 0) // xaxis + || (wType == 1 && icvBack != 1)) // yaxis + sb.append(" AreaBg=\"" + icvBack + "\""); } - // TODO: parse LineFormat and, if not standard, add as option - // TODO: Parse Tick for options - } - if (wType==1 && !hasMajorGridLines) // most Y Axes have major grid lines; flag if do not - sb.append(" MajorGridLines=\"false\""); - return sb.toString(); + // TODO: parse LineFormat and, if not standard, add as option + // TODO: Parse Tick for options + } + if (wType == 1 && !hasMajorGridLines) // most Y Axes have major grid lines; flag if do not + sb.append(" MajorGridLines=\"false\""); + return sb.toString(); } + /** * get/set linked TextDisp (legend for this axis) - * used for setting axis options + * used for setting axis options + * * @param td */ - public void setTd(TextDisp td) { linkedtd= td; } + public void setTd(TextDisp td) { + linkedtd = td; + } + public TextDisp getTd() { - if (linkedtd==null) - getTitleTD(false); - return linkedtd; + if (linkedtd == null) + getTitleTD(false); + return linkedtd; } /** * remove the title for this axis */ public void removeTitle() { - if (linkedtd!=null) { - int x= Chart.findRecPosition(ap.chartArr, TextDisp.class); - while (x > -1) { - if (((TextDisp) ap.chartArr.get(x)).getType()==TextDisp.convertType(wType)) { - ap.chartArr.remove(x); - break; - } - x++; - if (((BiffRec)ap.chartArr.get(x)).getOpcode()!=TEXTDISP) - break; - } - } - linkedtd= null; - } - + if (linkedtd != null) { + int x = Chart.findRecPosition(ap.chartArr, TextDisp.class); + while (x > -1) { + if (((TextDisp) ap.chartArr.get(x)).getType() == TextDisp.convertType(wType)) { + ap.chartArr.remove(x); + break; + } + x++; + if (((BiffRec) ap.chartArr.get(x)).getOpcode() != TEXTDISP) + break; + } + } + linkedtd = null; + } + /** * returns true if plot area has a bonding box/border + * * @return */ public boolean hasPlotAreaBorder() { - Frame f= (Frame) Chart.findRec(ap.chartArr, Frame.class); - if (f!=null) - return f.hasBox(); - return false; + Frame f = (Frame) Chart.findRec(ap.chartArr, Frame.class); + if (f != null) + return f.hasBox(); + return false; } - - /** + + /** * link this axis with it's parent + * * @param a */ - public void setAP(AxisParent a) { ap= a; } - + public void setAP(AxisParent a) { + ap = a; + } + /** * return the Title associated with this Axis (through the linked TextDisp record) - * @deprecated use getTitle + * * @return + * @deprecated use getTitle */ public String getLabel() { - return getTitle(); - + return getTitle(); + } - + public String getTitle() { - if (linkedtd==null) - getTitleTD(false); - if (linkedtd!=null) - return linkedtd.toString(); - return ""; - } - - - /** return the Font object associated with the Axis Title, or null if none */ + if (linkedtd == null) + getTitleTD(false); + if (linkedtd != null) + return linkedtd.toString(); + return ""; + } + + + /** + * return the Font object associated with the Axis Title, or null if none + */ public Font getTitleFont() { - try { - if (linkedtd!=null) { - Fontx fx= (Fontx) Chart.findRec(linkedtd.chartArr, Fontx.class); - return this.getParentChart().getWorkBook().getFont(fx.getIfnt()); - } - } catch (Exception e) {} - return null; + try { + if (linkedtd != null) { + Fontx fx = (Fontx) Chart.findRec(linkedtd.chartArr, Fontx.class); + return this.getParentChart().getWorkBook().getFont(fx.getIfnt()); + } + } catch (Exception e) { + } + return null; } + /** * finds or adds TextDisp records for this axis (for labels and fonts) */ private void getTitleTD(boolean add) { - int tdtype= TextDisp.convertType(wType); - int pos= 0; + int tdtype = TextDisp.convertType(wType); + int pos = 0; // Must add if can't find existing label - int x= Chart.findRecPosition(ap.chartArr, TextDisp.class); - pos= x; - while (x > 0 && linkedtd==null) { - TextDisp td= (TextDisp) ap.chartArr.get(x); - if (td.getType()==tdtype) // found it - linkedtd= td; - else { - if (!(ap.chartArr.get(++x) instanceof TextDisp)) - x= -1; - } - } - if (linkedtd==null && add) { - linkedtd= (TextDisp) TextDisp.getPrototype(tdtype, "", this.wkbook); + int x = Chart.findRecPosition(ap.chartArr, TextDisp.class); + pos = x; + while (x > 0 && linkedtd == null) { + TextDisp td = (TextDisp) ap.chartArr.get(x); + if (td.getType() == tdtype) // found it + linkedtd = td; + else { + if (!(ap.chartArr.get(++x) instanceof TextDisp)) + x = -1; + } + } + if (linkedtd == null && add) { + linkedtd = (TextDisp) TextDisp.getPrototype(tdtype, "", this.wkbook); if (pos < 0) - pos= Chart.findRecPosition(ap.chartArr, PlotArea.class); - else { // otherwise, TextDisp(s) exist already; position correctly - // set font to other axis font as default - TextDisp td= (TextDisp) ap.chartArr.get(pos); - linkedtd.setFontId(td.getFontId()); - if (wType!=XAXIS) - pos++; + pos = Chart.findRecPosition(ap.chartArr, PlotArea.class); + else { // otherwise, TextDisp(s) exist already; position correctly + // set font to other axis font as default + TextDisp td = (TextDisp) ap.chartArr.get(pos); + linkedtd.setFontId(td.getFontId()); + if (wType != XAXIS) + pos++; } - if (pos < 0) - pos= Chart.findRecPosition(ap.chartArr, ChartFormat.class); - linkedtd.setParentChart(this.getParentChart()); + if (pos < 0) + pos = Chart.findRecPosition(ap.chartArr, ChartFormat.class); + linkedtd.setParentChart(this.getParentChart()); ap.chartArr.add(pos, linkedtd); - } + } } - + /** * set the Title associated with this Axis (through the linked TextDisp record) + * * @param l */ public void setTitle(String l) { - if (l==null) { - this.removeTitle(); - return; - } - if (linkedtd==null) { - getTitleTD(true); // finds or adds TextDisp records for this axis (for labels and fonts) - } - linkedtd.setText(l); - } - + if (l == null) { + this.removeTitle(); + return; + } + if (linkedtd == null) { + getTitleTD(true); // finds or adds TextDisp records for this axis (for labels and fonts) + } + linkedtd.setText(l); + } + /** * set the font index for this Axis (for title) + * * @param fondId */ public void setFont(int fondId) { - if (linkedtd==null) { - getTitleTD(true); // finds or adds TextDisp records for this axis (for labels and fonts) - } - linkedtd.setFontId(fondId); + if (linkedtd == null) { + getTitleTD(true); // finds or adds TextDisp records for this axis (for labels and fonts) + } + linkedtd.setFontId(fondId); } - + /** * returns the font of the Axis Title */ public io.starter.formats.XLS.Font getFont() { - if (linkedtd==null) { - getTitleTD(true); // finds or adds TextDisp records for this axis (for labels and fonts) - } - int idx= linkedtd.getFontId(); - return this.getWorkBook().getFont(idx); + if (linkedtd == null) { + getTitleTD(true); // finds or adds TextDisp records for this axis (for labels and fonts) + } + int idx = linkedtd.getFontId(); + return this.getWorkBook().getFont(idx); } - + /** - * return the Font object used for Axis labels + * return the Font object used for Axis labels + * * @return */ public io.starter.formats.XLS.Font getLabelFont() { - try { - Fontx fx= (Fontx) Chart.findRec(chartArr, Fontx.class); - return this.getParentChart().getWorkBook().getFont(fx.getIfnt()); - } catch (NullPointerException e ) { - return this.getParentChart().getDefaultFont(); - } + try { + Fontx fx = (Fontx) Chart.findRec(chartArr, Fontx.class); + return this.getParentChart().getWorkBook().getFont(fx.getIfnt()); + } catch (NullPointerException e) { + return this.getParentChart().getDefaultFont(); + } } + /** * utility method to find the CatserRange rec associated with this Axis + * * @return */ protected CatserRange getCatserRange(boolean bCreate) { - CatserRange csr= (CatserRange) Chart.findRec(chartArr, CatserRange.class); - if (csr==null) { - csr= (CatserRange) CatserRange.getPrototype(); - csr.setParentChart(this.getParentChart()); - chartArr.add(0, csr); - } - return csr; - } + CatserRange csr = (CatserRange) Chart.findRec(chartArr, CatserRange.class); + if (csr == null) { + csr = (CatserRange) CatserRange.getPrototype(); + csr.setParentChart(this.getParentChart()); + chartArr.add(0, csr); + } + return csr; + } /** * returns true of this axis displays major gridlines + * * @return */ protected boolean hasGridlines(int type) { - int j= Chart.findRecPosition(chartArr, AxisLineFormat.class); - if (j!=-1) { - try { - while (j < chartArr.size()) { - AxisLineFormat al= (AxisLineFormat) chartArr.get(j); - int id= al.getId(); - if (id==type) // Y Axis draw gridline usually true, trap if do not have (see below) - return true; - j+=2; // Skip line format - } - } - catch (ClassCastException e) {} - } - return false; - } - + int j = Chart.findRecPosition(chartArr, AxisLineFormat.class); + if (j != -1) { + try { + while (j < chartArr.size()) { + AxisLineFormat al = (AxisLineFormat) chartArr.get(j); + int id = al.getId(); + if (id == type) // Y Axis draw gridline usually true, trap if do not have (see below) + return true; + j += 2; // Skip line format + } + } catch (ClassCastException e) { + } + } + return false; + } + /** * returns the SVG necessary to describe the desired line (referenced by id) - * @param id @see AxisLineFormat id types + * + * @param id @see AxisLineFormat id types * @return */ protected String getLineSVG(int id) { - LineFormat lf= getAxisLine(id); - if (lf!=null) - return lf.getSVG(); - return ""; - } - + LineFormat lf = getAxisLine(id); + if (lf != null) + return lf.getSVG(); + return ""; + } + /** * return the lineformat rec for the given axis line type - * @param type AxisLineFormat type + * + * @param type AxisLineFormat type + * @return Line Format * @see AxisLineFormat.ID_MAJOR_GRID, et. atl. - * @return Line Format */ protected LineFormat getAxisLine(int type) { - int j= getAxisLineFormat(type); - if (j>-1) - return (LineFormat) chartArr.get(j+1); - - return null; + int j = getAxisLineFormat(type); + if (j > -1) + return (LineFormat) chartArr.get(j + 1); + + return null; } - + /** * return the AxisLineFormat of the desired type, or create if none and bCreate - * @param type AxisLineFormat Type: AxisLineFormat.ID_AXIS_LINE, AxisLineFormat.ID_MAJOR_GRID, AxisLineFormat.ID_MINOR_GRID, AxisLineFormat.ID_WALLORFLOOR + * + * @param type AxisLineFormat Type: AxisLineFormat.ID_AXIS_LINE, AxisLineFormat.ID_MAJOR_GRID, AxisLineFormat.ID_MINOR_GRID, AxisLineFormat.ID_WALLORFLOOR * @param bCreate * @return */ protected AxisLineFormat getAxisLineFormat(int type, boolean bCreate) { - AxisLineFormat alf= null; - int j= Chart.findRecPosition(chartArr, AxisLineFormat.class); - if (j==-1 && !bCreate) - return null; - if (j > -1) { - try { - while (j < chartArr.size()) { - alf= (AxisLineFormat) chartArr.get(j); - if (alf.getId()==type) - return alf; - else if (alf.getId()>type) - break; - j+=2; - } - } catch (ClassCastException e) { - ; - } - } - j= 1; - for (; j < chartArr.size(); j++) { - BiffRec b= (BiffRec) chartArr.get(j); - if (b.getOpcode()==AREAFORMAT || - b.getOpcode()==GELFRAME || - b.getOpcode()==2213 || /* TextPropsStream */ - b.getOpcode()==2212 || /* ShapePropsStream */ - b.getOpcode()==2206) /* CtrlMlFrt */ - break; - } - alf= (AxisLineFormat) AxisLineFormat.getPrototype(); - alf.setId(type); // default has major gridlines + AxisLineFormat alf = null; + int j = Chart.findRecPosition(chartArr, AxisLineFormat.class); + if (j == -1 && !bCreate) + return null; + if (j > -1) { + try { + while (j < chartArr.size()) { + alf = (AxisLineFormat) chartArr.get(j); + if (alf.getId() == type) + return alf; + else if (alf.getId() > type) + break; + j += 2; + } + } catch (ClassCastException e) { + } + } + j = 1; + for (; j < chartArr.size(); j++) { + BiffRec b = chartArr.get(j); + if (b.getOpcode() == AREAFORMAT || + b.getOpcode() == GELFRAME || + b.getOpcode() == 2213 || /* TextPropsStream */ + b.getOpcode() == 2212 || /* ShapePropsStream */ + b.getOpcode() == 2206) /* CtrlMlFrt */ + break; + } + alf = (AxisLineFormat) AxisLineFormat.getPrototype(); + alf.setId(type); // default has major gridlines chartArr.add(j++, alf); alf.setParentChart(this.getParentChart()); - LineFormat lf= (LineFormat) LineFormat.getPrototype(); + LineFormat lf = (LineFormat) LineFormat.getPrototype(); lf.setParentChart(this.getParentChart()); chartArr.add(j, lf); - return alf; - + return alf; + } + /** * return the index of the AxisLineFormat of the desired type, -1 if none - * @param type AxisLineFormat Type: AxisLineFormat.ID_AXIS_LINE, AxisLineFormat.ID_MAJOR_GRID, AxisLineFormat.ID_MINOR_GRID, AxisLineFormat.ID_WALLORFLOOR + * + * @param type AxisLineFormat Type: AxisLineFormat.ID_AXIS_LINE, AxisLineFormat.ID_MAJOR_GRID, AxisLineFormat.ID_MINOR_GRID, AxisLineFormat.ID_WALLORFLOOR * @return */ protected int getAxisLineFormat(int type) { - AxisLineFormat alf= null; - int j= Chart.findRecPosition(chartArr, AxisLineFormat.class); - if (j==-1) return j; - try { - while (j < chartArr.size()) { - alf= (AxisLineFormat) chartArr.get(j); - if (alf.getId()==type) - return j; - else if (alf.getId()>type) - break; - j+=2; - } - } catch (ClassCastException e) { - ; - } - if (j>chartArr.size()) - return -1; // not found - return j; - - } - + AxisLineFormat alf = null; + int j = Chart.findRecPosition(chartArr, AxisLineFormat.class); + if (j == -1) return j; + try { + while (j < chartArr.size()) { + alf = (AxisLineFormat) chartArr.get(j); + if (alf.getId() == type) + return j; + else if (alf.getId() > type) + break; + j += 2; + } + } catch (ClassCastException e) { + } + if (j > chartArr.size()) + return -1; // not found + return j; + + } + /** * gets or creates YMult (value multiplier) record + * * @param bCreate * @return */ protected YMult getYMultRec(boolean bCreate) { - YMult ym= (YMult) Chart.findRec(chartArr, YMult.class); - if (ym==null && bCreate) { - ym= (YMult) YMult.getPrototype(); - ym.setParentChart(this.getParentChart()); - chartArr.add(1, ym); // 2nd, after ValueRange - } - return ym; - } - + YMult ym = (YMult) Chart.findRec(chartArr, YMult.class); + if (ym == null && bCreate) { + ym = (YMult) YMult.getPrototype(); + ym.setParentChart(this.getParentChart()); + chartArr.add(1, ym); // 2nd, after ValueRange + } + return ym; + } + /** * returns true if this axis is reversed *
                If horizontal axis, default= on bottom, reversed= on top *
                If vertical axis, default= LHS, reversed= RHS + * * @return */ public boolean isReversed() { - if (wType==XAXIS) { - CatserRange c= getCatserRange(false); - if (c!=null) - return c.isReversed(); - } - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { // shouldn't - return v.isReversed(); - } + if (wType == XAXIS) { + CatserRange c = getCatserRange(false); + if (c != null) + return c.isReversed(); + } + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { // shouldn't + return v.isReversed(); + } return false; } - + public String getNumberFormat() { - Ifmt f= (Ifmt) Chart.findRec(this.chartArr, Ifmt.class); - int i= 0; - if (f!=null) { - i= f.getFmt(); - } else { - // see if have series-specific formats - java.util.Vector s = this.getParentChart().getAllSeries(-1); - if (s.size() > 0) { - if (wType==YAXIS) { - return ((Series)s.get(0)).getSeriesFormatPattern(); - } else // see if it's a value X axis with a custom - return ((Series)s.get(0)).getCategoryFormatPattern(); - } - } - return "General"; - } - + Ifmt f = (Ifmt) Chart.findRec(this.chartArr, Ifmt.class); + int i = 0; + if (f != null) { + i = f.getFmt(); + } else { + // see if have series-specific formats + java.util.Vector s = this.getParentChart().getAllSeries(-1); + if (s.size() > 0) { + if (wType == YAXIS) { + return ((Series) s.get(0)).getSeriesFormatPattern(); + } else // see if it's a value X axis with a custom + return ((Series) s.get(0)).getCategoryFormatPattern(); + } + } + return "General"; + } + /** * return the JSON/Dojo representation of this axis - * chartType int necessary for parsing AXIS options: horizontal charts "switch" axes ... + * chartType int necessary for parsing AXIS options: horizontal charts "switch" axes ... * All options have NOT been gathered at this point - * @return JSONObject + * + * @return JSONObject */ public JSONObject getJSON(io.starter.OpenXLS.WorkBookHandle wbh, int chartType, double yMax, double yMin, int nSeries) { - JSONObject axisJSON= new JSONObject(); - JSONObject axisOptions= new JSONObject(); - try { - if (wType==YAXIS && chartType!=ChartConstants.BARCHART) { - axisOptions.put("vertical", true); - } else if (wType==XAXIS && chartType==ChartConstants.BARCHART) - axisOptions.put("vertical", true); - - // 20090721 KSC: dojo 1.3.1 has label element - axisOptions.put("label", getTitle()); - - // TODO: Dojo Axis Options: - // fixLower, fixUpper ("minor"/"major") - // includeZero, natural (true) - // min, max - // minorTicks, majorTicks, microTicks (true/false) - // minorTickStep, majorTickStep, microTickStep (#) - for (int i= 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec)chartArr.get(i); - if (b instanceof CatserRange) { - CatserRange c= ((CatserRange) b); - // for x/Category axis: if has labels, gather and input into axis label JSON array - String[] categories= this.getParentChart().getCategories(-1); - if (categories!=null) { - // Category Labels - JSONArray labels= new JSONArray(); - if (c.getCrossBetween()) { // categories appear mid-axis so put "spacers" at 0 and max - axisOptions.put("includeZero", true); // always true???? - JSONObject nullCat= new JSONObject(); - nullCat.put("value", 0); - nullCat.put("text", ""); - labels.put(nullCat); - } - JSONArray cats= CellRange.getValuesAsJSON(categories[0], wbh); // parse category range into JSON Array - for (int z= 0; z < cats.length(); z++) { - JSONObject aCat= new JSONObject(); - aCat.put("value", z+c.getCatLabel()); // should= +1 i.e. a category label appears with each category - aCat.put("text", cats.get(z)); - labels.put(aCat); - } - if (c.getCrossBetween()) { // categories appear mid-axis so put "spacers" at 0 and max - JSONObject nullCat= new JSONObject(); - nullCat.put("value", cats.length()+c.getCatLabel()); - nullCat.put("text", ""); - labels.put(nullCat); - axisOptions.put("max", cats.length()+c.getCatLabel()/*+1*/); - } - axisOptions.put("labels", labels); - // Defaults axis values ... - axisOptions.put("fixLower", "major"); // what do these do?? - axisOptions.put("fixUpper", "major"); - } - + JSONObject axisJSON = new JSONObject(); + JSONObject axisOptions = new JSONObject(); + try { + if (wType == YAXIS && chartType != ChartConstants.BARCHART) { + axisOptions.put("vertical", true); + } else if (wType == XAXIS && chartType == ChartConstants.BARCHART) + axisOptions.put("vertical", true); + + // 20090721 KSC: dojo 1.3.1 has label element + axisOptions.put("label", getTitle()); + + // TODO: Dojo Axis Options: + // fixLower, fixUpper ("minor"/"major") + // includeZero, natural (true) + // min, max + // minorTicks, majorTicks, microTicks (true/false) + // minorTickStep, majorTickStep, microTickStep (#) + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b instanceof CatserRange) { + CatserRange c = ((CatserRange) b); + // for x/Category axis: if has labels, gather and input into axis label JSON array + String[] categories = this.getParentChart().getCategories(-1); + if (categories != null) { + // Category Labels + JSONArray labels = new JSONArray(); + if (c.getCrossBetween()) { // categories appear mid-axis so put "spacers" at 0 and max + axisOptions.put("includeZero", true); // always true???? + JSONObject nullCat = new JSONObject(); + nullCat.put("value", 0); + nullCat.put("text", ""); + labels.put(nullCat); + } + JSONArray cats = CellRange.getValuesAsJSON(categories[0], wbh); // parse category range into JSON Array + for (int z = 0; z < cats.length(); z++) { + JSONObject aCat = new JSONObject(); + aCat.put("value", z + c.getCatLabel()); // should= +1 i.e. a category label appears with each category + aCat.put("text", cats.get(z)); + labels.put(aCat); + } + if (c.getCrossBetween()) { // categories appear mid-axis so put "spacers" at 0 and max + JSONObject nullCat = new JSONObject(); + nullCat.put("value", cats.length() + c.getCatLabel()); + nullCat.put("text", ""); + labels.put(nullCat); + axisOptions.put("max", cats.length() + c.getCatLabel()/*+1*/); + } + axisOptions.put("labels", labels); + // Defaults axis values ... + axisOptions.put("fixLower", "major"); // what do these do?? + axisOptions.put("fixUpper", "major"); + } + // if (c.getCatCross()!=1) - if (c.getCatMark()!=1) { -/* The catMark field defines how often tick marks appear along the category or series axis. A value of 01 indicates - that a tick mark will appear between each category or series; a value of 02 means a label appears between every + if (c.getCatMark() != 1) { +/* The catMark field defines how often tick marks appear along the category or series axis. A value of 01 indicates + that a tick mark will appear between each category or series; a value of 02 means a label appears between every other category or series, etc. -*/ } - +*/ + } + // if (c.getCrossMax()) - } else if (b instanceof ValueRange) { - ValueRange v= (ValueRange) b; - if (wType==YAXIS) // normal - v.setMaxMin(yMax, yMin); // must do first - else - v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series - - // y major/minor scales - axisOptions.put("min", v.getMin()); - axisOptions.put("max", v.getMax()); - axisOptions.put("majorTickStep", v.getMajorTick()); - } else if (b instanceof AxisLineFormat) { - JSONObject gridJSON= new JSONObject(); - gridJSON.put("type", "Grid"); - int id= ((AxisLineFormat) b).getId(); - switch (id) { - case AxisLineFormat.ID_MAJOR_GRID: - if (wType==XAXIS || chartType==ChartConstants.BARCHART) - gridJSON.put("hMajorLines", false); - else - gridJSON.put("vMajorLines", false); - break; - case AxisLineFormat.ID_MINOR_GRID: - break; - } - axisJSON.put("back_grid", gridJSON); - } - } - if (wType==YAXIS) - axisJSON.put("y", axisOptions); - if (wType==XAXIS) - axisJSON.put("x", axisOptions); - }catch (JSONException e) { - Logger.logErr("Error getting Axis JSON: " + e); - } - return axisJSON; - } - - /** - * interface for setting Axis rec-specific XML options - * in a generic fashion - * @see OpenXLS.handleChartElement - * @see ChartHandle.getXML - */ - public boolean setChartOption(String op, String val) { - if (op.equalsIgnoreCase("Label")) { - this.setTitle(val); - return true; - } else if (op.equalsIgnoreCase("CatCross")) { - getCatserRange(true).setCatCross(Integer.parseInt(val)); - return true; - } else if (op.equalsIgnoreCase("LabelCross")) { - getCatserRange(true).setCatLabel(Integer.parseInt(val)); - return true; - } else if (op.equalsIgnoreCase("Marks")) { - getCatserRange(true).setCatMark(Integer.parseInt(val)); - return true; - } else if (op.equalsIgnoreCase("CrossBetween")) { - getCatserRange(true).setCrossBetween(val.equals("true")); - return true; - } else if (op.equalsIgnoreCase("CrossMax")) { - getCatserRange(true).setCrossMax(val.equals("true")); + } else if (b instanceof ValueRange) { + ValueRange v = (ValueRange) b; + if (wType == YAXIS) // normal + v.setMaxMin(yMax, yMin); // must do first + else + v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series + + // y major/minor scales + axisOptions.put("min", v.getMin()); + axisOptions.put("max", v.getMax()); + axisOptions.put("majorTickStep", v.getMajorTick()); + } else if (b instanceof AxisLineFormat) { + JSONObject gridJSON = new JSONObject(); + gridJSON.put("type", "Grid"); + int id = ((AxisLineFormat) b).getId(); + switch (id) { + case AxisLineFormat.ID_MAJOR_GRID: + if (wType == XAXIS || chartType == ChartConstants.BARCHART) + gridJSON.put("hMajorLines", false); + else + gridJSON.put("vMajorLines", false); + break; + case AxisLineFormat.ID_MINOR_GRID: + break; + } + axisJSON.put("back_grid", gridJSON); + } + } + if (wType == YAXIS) + axisJSON.put("y", axisOptions); + if (wType == XAXIS) + axisJSON.put("x", axisOptions); + } catch (JSONException e) { + Logger.logErr("Error getting Axis JSON: " + e); + } + return axisJSON; + } + + /** + * interface for setting Axis rec-specific XML options + * in a generic fashion + * + * @see OpenXLS.handleChartElement + * @see ChartHandle.getXML + */ + public boolean setChartOption(String op, String val) { + if (op.equalsIgnoreCase("Label")) { + this.setTitle(val); + return true; + } else if (op.equalsIgnoreCase("CatCross")) { + getCatserRange(true).setCatCross(Integer.parseInt(val)); + return true; + } else if (op.equalsIgnoreCase("LabelCross")) { + getCatserRange(true).setCatLabel(Integer.parseInt(val)); + return true; + } else if (op.equalsIgnoreCase("Marks")) { + getCatserRange(true).setCatMark(Integer.parseInt(val)); + return true; + } else if (op.equalsIgnoreCase("CrossBetween")) { + getCatserRange(true).setCrossBetween(val.equals("true")); return true; - } else if (op.equalsIgnoreCase("MajorGridLines")) { - if (val.equals("false")) { - int j= getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID); - if (j>-1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true); - } - } else if (op.equalsIgnoreCase("MinorGridLines")) { - if (val.equals("false")) { - int j= getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID); - if (j>-1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true); - } - } else if (op.equalsIgnoreCase("AddArea")) { - if (wType==XAXIS) { - AxisLineFormat alf0= (AxisLineFormat) AxisLineFormat.getPrototype(); - alf0.setId(AxisLineFormat.ID_AXIS_LINE); - this.addChartRecord(alf0); - LineFormat lf0= (LineFormat) LineFormat.getPrototype(0, 0); - this.addChartRecord(lf0); - } - AxisLineFormat alf= (AxisLineFormat) AxisLineFormat.getPrototype(); - alf.setId(AxisLineFormat.ID_WALLORFLOOR); - this.addChartRecord(alf); - LineFormat lf= (LineFormat) LineFormat.getPrototype(0, -1); - if (wType==1) - lf.setLineStyle(5); //none - this.addChartRecord(lf); - AreaFormat af= (AreaFormat) AreaFormat.getPrototype(wType); - this.addChartRecord(af); - return true; - } else if (op.equals("AreaFg")) { // custom foreground on Wall, Side or Floor - AreaFormat af= (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); - af.seticvFore(Integer.valueOf(val).intValue()); - return true; - } else if (op.equals("AreaBg")) { // custom bg on Wall, SIde or Floor - AreaFormat af= (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); - af.seticvBack(Integer.valueOf(val).intValue()); - return true; - } else if (linkedtd!=null) { // see if associated TextDisp can handle - return linkedtd.setChartOption(op, val); - } else if (op.equalsIgnoreCase("MajorGridLines")) { - if (val.equals("false")) { - int j= getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID); - if (j>-1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true); - } - } else if (op.equalsIgnoreCase("MinorGridLines")) { - if (val.equals("false")) { - int j= getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID); - if (j>-1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true); - } - } - return false; - } - + } else if (op.equalsIgnoreCase("CrossMax")) { + getCatserRange(true).setCrossMax(val.equals("true")); + return true; + } else if (op.equalsIgnoreCase("MajorGridLines")) { + if (val.equals("false")) { + int j = getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID); + if (j > -1) { + chartArr.remove(j); // remove AxisLineFormat + chartArr.remove(j); // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true); + } + } else if (op.equalsIgnoreCase("MinorGridLines")) { + if (val.equals("false")) { + int j = getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID); + if (j > -1) { + chartArr.remove(j); // remove AxisLineFormat + chartArr.remove(j); // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true); + } + } else if (op.equalsIgnoreCase("AddArea")) { + if (wType == XAXIS) { + AxisLineFormat alf0 = (AxisLineFormat) AxisLineFormat.getPrototype(); + alf0.setId(AxisLineFormat.ID_AXIS_LINE); + this.addChartRecord(alf0); + LineFormat lf0 = (LineFormat) LineFormat.getPrototype(0, 0); + this.addChartRecord(lf0); + } + AxisLineFormat alf = (AxisLineFormat) AxisLineFormat.getPrototype(); + alf.setId(AxisLineFormat.ID_WALLORFLOOR); + this.addChartRecord(alf); + LineFormat lf = (LineFormat) LineFormat.getPrototype(0, -1); + if (wType == 1) + lf.setLineStyle(5); //none + this.addChartRecord(lf); + AreaFormat af = (AreaFormat) AreaFormat.getPrototype(wType); + this.addChartRecord(af); + return true; + } else if (op.equals("AreaFg")) { // custom foreground on Wall, Side or Floor + AreaFormat af = (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); + af.seticvFore(Integer.valueOf(val).intValue()); + return true; + } else if (op.equals("AreaBg")) { // custom bg on Wall, SIde or Floor + AreaFormat af = (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); + af.seticvBack(Integer.valueOf(val).intValue()); + return true; + } else if (linkedtd != null) { // see if associated TextDisp can handle + return linkedtd.setChartOption(op, val); + } else if (op.equalsIgnoreCase("MajorGridLines")) { + if (val.equals("false")) { + int j = getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID); + if (j > -1) { + chartArr.remove(j); // remove AxisLineFormat + chartArr.remove(j); // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true); + } + } else if (op.equalsIgnoreCase("MinorGridLines")) { + if (val.equals("false")) { + int j = getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID); + if (j > -1) { + chartArr.remove(j); // remove AxisLineFormat + chartArr.remove(j); // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true); + } + } + return false; + } + /** * sets a specific OOXML axis option; most options apply to any type of axis (Cat, Value, Ser, Date) *
                can be one of: *
                axPos - position of the axis (b, t, r, l) - *
                crosses possible crossing points (autoZero, max, min) + *
                crosses possible crossing points (autoZero, max, min) *
                crossBeteween whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) *
                crossesAt where on axis the perpendicular axis crosses (double val) *
                lblAlign text alignment for tick labels (ctr, l, r) (cat only) *
                lblOffset distance of labels from the axis (0-1000) (cat only) - *
                majorTickMark major tick mark position (cross, in, none, out) + *
                majorTickMark major tick mark position (cross, in, none, out) *
                minorTickMark minor tick mark position ("") - *
                tickLblPos tick label position (high, low, nextTo, none) + *
                tickLblPos tick label position (high, low, nextTo, none) *
                tickLblSkip how many tick labels to skip between label (int >= 1) (cat only) *
                tickMarkSkip how many tick marks to skip betwen ticks (int >= 1) (cat only) *
                majorUnit distance between major tick marks (val, date ax only) (double >= 0) - *
                minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + *
                minorUnit distance between minor tick marks (val, date ax only) (double >= 0) *
                MajorGridLines - *
                MinorGridLines + *
                MinorGridLines + * * @param op * @param val */ private void setOption(String op, String val) { - if (op.equals("axPos")) { // val= "b" (bottom) "l", "t", "r" -->????? - axPos= val; // for now - } else if (op.equals("lblOffset") || - op.equals("lblAlgn")) { - CatLab cl= (CatLab) Chart.findRec(chartArr, CatLab.class); - if (cl==null) { - cl= (CatLab) CatLab.getPrototype(); - cl.setParentChart(this.getParentChart()); - chartArr.add(1, cl); // second in chart array, after CatSerRange - } - cl.setOption(op, val); - } else if (op.equals("tickLblPos") || // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick - op.equals("majorTickMark") || // major tick marks (cross, in, none, out) - op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) - Tick t= (Tick) Chart.findRec(chartArr, Tick.class); - t.setOption(op, val); - } else if (op.equalsIgnoreCase("MajorGridLines")) { - if (val.equals("false")) { - int j= getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID); - if (j>-1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true); - } - } else if (op.equalsIgnoreCase("MinorGridLines")) { - if (val.equals("false")) { - int j= getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID); - if (j>-1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true); - } - } else { // valuerange, caterrange options -- crosses, crossBetween, crossesAt, tickMarkSkip (cat only), tickLblSkip (cat only), majorUnit (val only), minorUnit (val only) + if (op.equals("axPos")) { // val= "b" (bottom) "l", "t", "r" -->????? + axPos = val; // for now + } else if (op.equals("lblOffset") || + op.equals("lblAlgn")) { + CatLab cl = (CatLab) Chart.findRec(chartArr, CatLab.class); + if (cl == null) { + cl = (CatLab) CatLab.getPrototype(); + cl.setParentChart(this.getParentChart()); + chartArr.add(1, cl); // second in chart array, after CatSerRange + } + cl.setOption(op, val); + } else if (op.equals("tickLblPos") || // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick + op.equals("majorTickMark") || // major tick marks (cross, in, none, out) + op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) + Tick t = (Tick) Chart.findRec(chartArr, Tick.class); + t.setOption(op, val); + } else if (op.equalsIgnoreCase("MajorGridLines")) { + if (val.equals("false")) { + int j = getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID); + if (j > -1) { + chartArr.remove(j); // remove AxisLineFormat + chartArr.remove(j); // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true); + } + } else if (op.equalsIgnoreCase("MinorGridLines")) { + if (val.equals("false")) { + int j = getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID); + if (j > -1) { + chartArr.remove(j); // remove AxisLineFormat + chartArr.remove(j); // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true); + } + } else { // valuerange, caterrange options -- crosses, crossBetween, crossesAt, tickMarkSkip (cat only), tickLblSkip (cat only), majorUnit (val only), minorUnit (val only) // KSC: TESTING //Logger.logInfo("Setting option: " + op + "=" + val); - for (int i= 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec)chartArr.get(i); - if (b instanceof CatserRange) { - if (((CatserRange)b).setOption(op, val)) - break; - }else if (b instanceof ValueRange) { - if (((ValueRange)b).setOption(op, val)) - break; - } - } - } - } - + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b instanceof CatserRange) { + if (((CatserRange) b).setOption(op, val)) + break; + } else if (b instanceof ValueRange) { + if (((ValueRange) b).setOption(op, val)) + break; + } + } + } + } + /** * get the desired axis option for this axis *
                can be one of: *
                axPos - position of the axis (b, t, r, l) - *
                crosses possible crossing points (autoZero, max, min) + *
                crosses possible crossing points (autoZero, max, min) *
                crossBeteween whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) *
                crossesAt where on axis the perpendicular axis crosses (double val) *
                lblAlign text alignment for tick labels (ctr, l, r) (cat only) *
                lblOffset distance of labels from the axis (0-1000) (cat only) - *
                majorTickMark major tick mark position (cross, in, none, out) + *
                majorTickMark major tick mark position (cross, in, none, out) *
                minorTickMark minor tick mark position ("") - *
                tickLblPos tick label position (high, low, nextTo, none) + *
                tickLblPos tick label position (high, low, nextTo, none) *
                tickLblSkip how many tick labels to skip between label (int >= 1) (cat only) *
                tickMarkSkip how many tick marks to skip betwen ticks (int >= 1) (cat only) *
                majorUnit distance between major tick marks (val, date ax only) (double >= 0) - *
                minorUnit distance between minor tick marks (val, date ax only) (double >= 0) - * @param op String option name - * @return String val of option or "" + *
                minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + * + * @param op String option name + * @return String val of option or "" */ public String getOption(String op) { - if (op.equals("axPos")) { // val= "b" (bottom) "l", "t", "r" -->????? - return axPos; // for now -- can't find matching Axis attribute - } else if (op.equals("lblAlign") || - op.equals("lblOffset")) { - CatLab c= (CatLab) Chart.findRec(chartArr, CatLab.class); - if (c!=null) - return ((CatLab) c).getOption(op); - return null; // use defaults - } else if (op.equals("crossesAt") || // specifies where axis crosses -- numCross or catCross - op.equals("orientation") || // axis orientation minMax or maxMin -- fReverse - op.equals("crosses") || // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross - op.equals("max") || // axis max - valueRange only? - op.equals("max") || // axis min- valueRange only? - op.equals("tickLblSkip") || //val= how many tick labels to skip btwn label -- catLabel -- Catserrange only?? - op.equals("tickMarkSkip") || //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? - op.equals("crossBetween")) { // value axis only -- val= between, midCat, crossBetween - // logScale-- ValueRange - for (int i= 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec)chartArr.get(i); - if (b instanceof CatserRange) { - return ((CatserRange)b).getOption(op); - }else if (b instanceof ValueRange) { - return ((ValueRange)b).getOption(op); - } - } - //TICK Options - } else if (op.equals("tickLblPos") || // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick - op.equals("majorTickMark") || // major tick marks (cross, in, none, out) - op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) - Tick t= (Tick) Chart.findRec(chartArr, Tick.class); - return t.getOption(op); - } - return null; - } - + if (op.equals("axPos")) { // val= "b" (bottom) "l", "t", "r" -->????? + return axPos; // for now -- can't find matching Axis attribute + } else if (op.equals("lblAlign") || + op.equals("lblOffset")) { + CatLab c = (CatLab) Chart.findRec(chartArr, CatLab.class); + if (c != null) + return c.getOption(op); + return null; // use defaults + } else if (op.equals("crossesAt") || // specifies where axis crosses -- numCross or catCross + op.equals("orientation") || // axis orientation minMax or maxMin -- fReverse + op.equals("crosses") || // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross + op.equals("max") || // axis max - valueRange only? + op.equals("max") || // axis min- valueRange only? + op.equals("tickLblSkip") || //val= how many tick labels to skip btwn label -- catLabel -- Catserrange only?? + op.equals("tickMarkSkip") || //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? + op.equals("crossBetween")) { // value axis only -- val= between, midCat, crossBetween + // logScale-- ValueRange + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b instanceof CatserRange) { + return ((CatserRange) b).getOption(op); + } else if (b instanceof ValueRange) { + return ((ValueRange) b).getOption(op); + } + } + //TICK Options + } else if (op.equals("tickLblPos") || // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick + op.equals("majorTickMark") || // major tick marks (cross, in, none, out) + op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) + Tick t = (Tick) Chart.findRec(chartArr, Tick.class); + return t.getOption(op); + } + return null; + } + /** * return the JSON/Dojo representation of this axis - * chartType int necessary for parsing AXIS options: horizontal charts "switch" axes ... - * @return JSONObject + * chartType int necessary for parsing AXIS options: horizontal charts "switch" axes ... + * + * @return JSONObject */ public JSONObject getMinMaxJSON(io.starter.OpenXLS.WorkBookHandle wbh, int chartType, double yMax, double yMin, int nSeries) { - JSONObject axisJSON= new JSONObject(); - JSONObject axisOptions= new JSONObject(); - try { - for (int i= 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec)chartArr.get(i); - if (b instanceof CatserRange) { - CatserRange c= ((CatserRange) b); - // for x/Category axis: if has labels, gather and input into axis label JSON array - String[] categories= this.getParentChart().getCategories(-1); - if (categories!=null) { - JSONArray cats= CellRange.getValuesAsJSON(categories[0], wbh); // parse category range into JSON Array - axisOptions.put("max", cats.length()+c.getCatLabel()/*+1*/); - } - break; // only need this per axis - } else if (b instanceof ValueRange) { - ValueRange v= (ValueRange) b; - if (wType==YAXIS) // normal - v.setMaxMin(yMax, yMin); // must do first - else - v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series - - // y major/minor scales - axisOptions.put("min", v.getMin()); - axisOptions.put("max", v.getMax()); - axisOptions.put("majorTickStep", v.getMajorTick()); - break; // only need this per axis - } - } - if (wType==YAXIS) - axisJSON.put("y", axisOptions); - if (wType==XAXIS) - axisJSON.put("x", axisOptions); - }catch (JSONException e) { - Logger.logErr("Error getting Axis JSON: " + e); - } - return axisJSON; + JSONObject axisJSON = new JSONObject(); + JSONObject axisOptions = new JSONObject(); + try { + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b instanceof CatserRange) { + CatserRange c = ((CatserRange) b); + // for x/Category axis: if has labels, gather and input into axis label JSON array + String[] categories = this.getParentChart().getCategories(-1); + if (categories != null) { + JSONArray cats = CellRange.getValuesAsJSON(categories[0], wbh); // parse category range into JSON Array + axisOptions.put("max", cats.length() + c.getCatLabel()/*+1*/); + } + break; // only need this per axis + } else if (b instanceof ValueRange) { + ValueRange v = (ValueRange) b; + if (wType == YAXIS) // normal + v.setMaxMin(yMax, yMin); // must do first + else + v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series + + // y major/minor scales + axisOptions.put("min", v.getMin()); + axisOptions.put("max", v.getMax()); + axisOptions.put("majorTickStep", v.getMajorTick()); + break; // only need this per axis + } + } + if (wType == YAXIS) + axisJSON.put("y", axisOptions); + if (wType == XAXIS) + axisJSON.put("x", axisOptions); + } catch (JSONException e) { + Logger.logErr("Error getting Axis JSON: " + e); + } + return axisJSON; } + /** - * return the OOXML shape property for this axis + * return the OOXML shape property for this axis + * * @return - */ - public SpPr getSpPr() { return shapeProps; } + */ + public SpPr getSpPr() { + return shapeProps; + } /** * define the OOXML shape property for this axis from an existing spPr element */ public void setSpPr(SpPr sp) { - shapeProps= sp; - //shapeProps.setNS("c"); + shapeProps = sp; + //shapeProps.setNS("c"); } /** * return the OOXML title element for this axis + * * @return */ - public io.starter.formats.OOXML.Title getOOXMLTitle() { - return ttl; } - + public io.starter.formats.OOXML.Title getOOXMLTitle() { + return ttl; + } + /** * set the OOXML title element for this axis + * * @param t */ public void setOOXMLTitle(Title t) { - ttl= t; + ttl = t; } + /** * return the OOXML txPr element for this axis + * * @return */ - public TxPr gettxPr() { - return txpr; } - + public TxPr gettxPr() { + return txpr; + } + /** * set the OOXML title element for this axis + * * @param t */ public void settxPr(TxPr t) { - txpr= (TxPr)t.cloneElement(); + txpr = (TxPr) t.cloneElement(); } - + public String toString() { - String s= ""; - switch (wType) { - case XAXIS: - s= "XAxis"; - break; - case YAXIS: - s= "YAxis"; - break; - case ZAXIS: - s= "ZAxis"; - break; - case XVALAXIS: - s= "XValAxis"; - break; - } - if (linkedtd!=null) - s= s + " " + linkedtd.toString(); - return s; - } - + String s = ""; + switch (wType) { + case XAXIS: + s = "XAxis"; + break; + case YAXIS: + s = "YAxis"; + break; + case ZAXIS: + s = "ZAxis"; + break; + case XVALAXIS: + s = "XValAxis"; + break; + } + if (linkedtd != null) + s = s + " " + linkedtd.toString(); + return s; + } + /** * return the maximum value of this Value or Y axis scale + * * @return */ public double getMaxScale(double[] minmax) { - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { // shouldn't - if (v.isAutomaticMax()) - v.setMaxMin(minmax[1], minmax[0]); - return v.getMax(); - } + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { // shouldn't + if (v.isAutomaticMax()) + v.setMaxMin(minmax[1], minmax[0]); + return v.getMax(); + } return -1; } - + protected double[] getMinMax() { - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { // shouldn't - return new double[] {v.getMin(), v.getMax()}; - } - return new double[] { 0, 0 }; - + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { // shouldn't + return new double[]{v.getMin(), v.getMax()}; + } + return new double[]{0, 0}; + } + /** - * * return the minimum value of this value or Y axis scale + * * @return */ public double getMinScale(double[] minmax) { -/* - * Because a horizontal (category) axis (axis: A line bordering the chart plot area used as - * a frame of reference for measurement. * The y axis is usually the vertical axis and contains data. The x-axis is usually the horizontal axis and contains categories.) - * displays text labels instead of numeric intervals, - * there are fewer scaling options that you can change than there are for a vertical (value) axis. - * However, you can change the number of categories to display between tick marks, the order in which - * to display categories, and the point where the two axes cross. - */ - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { + /* + * Because a horizontal (category) axis (axis: A line bordering the chart plot area used as + * a frame of reference for measurement. * The y axis is usually the vertical axis and contains data. The x-axis is usually the horizontal axis and contains categories.) + * displays text labels instead of numeric intervals, + * there are fewer scaling options that you can change than there are for a vertical (value) axis. + * However, you can change the number of categories to display between tick marks, the order in which + * to display categories, and the point where the two axes cross. + */ + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { /* - if (wType==YAXIS) // normal + if (wType==YAXIS) // normal v.setMaxMin(yMax, yMin); // must do first else - v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series + v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series */ // why? should already v.setParentChart(this.getParentChart()); - if (v.isAutomaticMin()) - v.setMaxMin(minmax[1], minmax[0]); - return v.getMin(); - } + if (v.isAutomaticMin()) + v.setMaxMin(minmax[1], minmax[0]); + return v.getMin(); + } return -1; } /** * return the major tick unit of this Y or Value axis + * * @return */ public int getMajorUnit() { - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { - return new Double(v.getMajorTick()).intValue(); - } - return 10; // try a default + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { + return new Double(v.getMajorTick()).intValue(); + } + return 10; // try a default } - + /** * return the minor tick unit of this Y or Value axis + * * @return */ public int getMinorUnit() { - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { - return new Double(v.getMinorTick()).intValue(); - } - return 0; // try a default + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { + return new Double(v.getMinorTick()).intValue(); + } + return 0; // try a default } - + /** * returns true if either Automatic min or max scale is set for the Y or Value axis + * * @return */ public boolean isAutomaticScale() { - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { - return (v.isAutomaticMin() || v.isAutomaticMax()); - } - return false; - } - - + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { + return (v.isAutomaticMin() || v.isAutomaticMax()); + } + return false; + } + + /** * sets the automatic scale option on or off for the Y or Value axis *
                Automatic Scaling will automatically set the scale maximum, minimum and tick units + * * @param b */ public void setAutomaticScale(boolean b) { - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { - v.setAutomaticMin(b); - v.setAutomaticMax(b); - } + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { + v.setAutomaticMin(b); + v.setAutomaticMax(b); + } } /** * set the minimum value of this axis scale *
                Note: this disables automatic scaling + * * @param Min */ public void setMinScale(int min) { - // TODO: also update ticks? - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { // shouldn't - v.setMin(min); - } + // TODO: also update ticks? + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { // shouldn't + v.setMin(min); + } } - + /** * set the maximum value of this axis scale *
                Note: this disables automatic scaling + * * @param Max */ public void setMaxScale(int max) { - // TODO: also update ticks? - ValueRange v= (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v!=null) { // shouldn't - v.setMax(max); - } + // TODO: also update ticks? + ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); + if (v != null) { // shouldn't + v.setMax(max); + } } + /** * sets the axis labels position or placement to the desired value (these match Excel placement options) *

                Possible options: @@ -1149,1104 +1199,1134 @@ public void setMaxScale(int max) { *
                Axis.LOW - low end of plot area *
                Axis.HIGH - high end of plot area *
                Axis.NEXTTO- next to axis (default) - * + * * @param Placement - int one of the Axis placement constants listed above */ public void setAxisPlacement(int Placement) { - Tick t= (Tick) Chart.findRec(this.chartArr, Tick.class); - if (t!=null) { // shoudn't - switch (Placement) { - case Axis.INVISIBLE: - t.setOption("tickLblPos", "none"); - break; - case Axis.LOW: - t.setOption("tickLblPos", "low"); - break; - case Axis.HIGH: - t.setOption("tickLblPos", "high"); - break; - case Axis.NEXTTO: - t.setOption("tickLblPos", "nextTo"); - } - } - } - + Tick t = (Tick) Chart.findRec(this.chartArr, Tick.class); + if (t != null) { // shoudn't + switch (Placement) { + case Axis.INVISIBLE: + t.setOption("tickLblPos", "none"); + break; + case Axis.LOW: + t.setOption("tickLblPos", "low"); + break; + case Axis.HIGH: + t.setOption("tickLblPos", "high"); + break; + case Axis.NEXTTO: + t.setOption("tickLblPos", "nextTo"); + } + } + } + /** - * returns the Axis Label Placement or position as an int + * returns the Axis Label Placement or position as an int *

                One of: *
                Axis.INVISIBLE - axis is hidden *
                Axis.LOW - low end of plot area *
                Axis.HIGH - high end of plot area *
                Axis.NEXTTO- next to axis (default) + * * @return int - one of the Axis Label placement constants above */ public int getAxisPlacement() { - Tick t= (Tick) Chart.findRec(this.chartArr, Tick.class); - if (t!=null) { // shoudn't - String p= t.getOption("tickLblPos"); - if (p==null || p.equals("none")) - return Axis.INVISIBLE; - else if (p.equals("low")) - return Axis.LOW; - else if (p.equals("high")) - return Axis.HIGH; - else if (p.equals("nextTo")) - return Axis.NEXTTO; - } - return Axis.INVISIBLE; + Tick t = (Tick) Chart.findRec(this.chartArr, Tick.class); + if (t != null) { // shoudn't + String p = t.getOption("tickLblPos"); + if (p == null || p.equals("none")) + return Axis.INVISIBLE; + else if (p.equals("low")) + return Axis.LOW; + else if (p.equals("high")) + return Axis.HIGH; + else if (p.equals("nextTo")) + return Axis.NEXTTO; + } + return Axis.INVISIBLE; } + /** - * parse OOXML axis element - * @param xpp XmlPullParser positioned at correct elemnt - * @param axisTag catAx, valAx, serAx, dateAx - * @param lastTag Stack of element names + * parse OOXML axis element + * + * @param xpp XmlPullParser positioned at correct elemnt + * @param axisTag catAx, valAx, serAx, dateAx + * @param lastTag Stack of element names */ // noMultiLvlLbl -- val= 1 (true) means draw labels as flat text; not included or 0 (false)= draw labels as a heirarchy public void parseOOXML(XmlPullParser xpp, String axisTag, Stack lastTag, WorkBookHandle bk) { // crossAx -- need to parse? -// auto -- need to parse? - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("scaling")) { // additional axis settings - lastTag.push(tnm); - Scaling sc= (Scaling)Scaling.parseOOXML(xpp, lastTag); - String s= sc.getOption("orientation"); - if (s!=null) this.setOption("orientation", s); - s= sc.getOption("min"); - if (s!=null) this.setOption("min", s); - s= sc.getOption("max"); - if (s!=null) this.setOption("max", s); - // the below children only have 1 attribute: val - } else if (tnm.equals("axPos")) { // // position of the axis (b, t, r, l) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if (tnm.equals("majorGridlines") || - tnm.equals("minorGridlines")) { - lastTag.push(tnm); - parseGridlinesOOXML(xpp, lastTag, bk); - } else if (tnm.equals("title")) { - lastTag.push(tnm); - this.setOOXMLTitle((Title)Title.parseOOXML(xpp, lastTag, bk).cloneElement()); - this.setTitle(this.getOOXMLTitle().getTitle()); - } else if (tnm.equals("numFmt")) { - this.nf= (NumFmt) NumFmt.parseOOXML(xpp).cloneElement(); - } else if ( - tnm.equals("majorTickMark") || // major tick mark position (cross, in, none, out) - tnm.equals("minorTickMark") || // minor tick mark position ("") - tnm.equals("tickLblPos")) { // tick label position (high, low, nextTo, none) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if (tnm.equals("spPr")) { // axis shape properties - for axis or gridlines - lastTag.push(tnm); - this.setSpPr((SpPr)SpPr.parseOOXML(xpp, lastTag, bk).cloneElement()); - } else if (tnm.equals("txPr")) { // text Properties for axis - lastTag.push(tnm); - this.settxPr((TxPr) TxPr.parseOOXML(xpp, lastTag, bk).cloneElement()); - // crossesAx = crossing axis id - need ? - } else if ( - tnm.equals("crosses") || // possible crossing points (autoZero, max, min) - tnm.equals("crossesAt")) { // where on axis the perpendicular axis crosses (double val) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if (tnm.equals("crossBetween")) { // whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if ( // cat, date, ser ax only - // auto -- Date only? - tnm.equals("lblAlign") || // text alignment for tick labels (ctr, l, r) only for cat - tnm.equals("lblOffset") || // distance of labels from the axis (0-1000) only for cat, date - tnm.equals("tickLblSkip") || // how many tick labels to skip between label (int >= 1) - tnm.equals("tickMarkSkip")) { // how many tick marks to skip betwen ticks (int >= 1) - this.setOption(tnm, xpp.getAttributeValue(0)); -// TODO: noMultiLvlLbl - } else if ( // val, ser ax + some date ax - tnm.equals("crossBeteween") || // whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) - tnm.equals("majorUnit") || // distance between major tick marks (val, date ax only) (double >= 0) - tnm.equals("minorUnit")) { // distance between minor tick marks (val, date ax only) (double >= 0) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if (tnm.equals("dispUnits")) { // valAx only - parseDispUnitsOOXML(xpp, lastTag); - } -// TODO: date ax specifics - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals(axisTag)){ - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("Axis: " + e.toString()); - } +// auto -- need to parse? + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("scaling")) { // additional axis settings + lastTag.push(tnm); + Scaling sc = (Scaling) Scaling.parseOOXML(xpp, lastTag); + String s = sc.getOption("orientation"); + if (s != null) this.setOption("orientation", s); + s = sc.getOption("min"); + if (s != null) this.setOption("min", s); + s = sc.getOption("max"); + if (s != null) this.setOption("max", s); + // the below children only have 1 attribute: val + } else if (tnm.equals("axPos")) { // // position of the axis (b, t, r, l) + this.setOption(tnm, xpp.getAttributeValue(0)); + } else if (tnm.equals("majorGridlines") || + tnm.equals("minorGridlines")) { + lastTag.push(tnm); + parseGridlinesOOXML(xpp, lastTag, bk); + } else if (tnm.equals("title")) { + lastTag.push(tnm); + this.setOOXMLTitle((Title) Title.parseOOXML(xpp, lastTag, bk).cloneElement()); + this.setTitle(this.getOOXMLTitle().getTitle()); + } else if (tnm.equals("numFmt")) { + this.nf = (NumFmt) NumFmt.parseOOXML(xpp).cloneElement(); + } else if ( + tnm.equals("majorTickMark") || // major tick mark position (cross, in, none, out) + tnm.equals("minorTickMark") || // minor tick mark position ("") + tnm.equals("tickLblPos")) { // tick label position (high, low, nextTo, none) + this.setOption(tnm, xpp.getAttributeValue(0)); + } else if (tnm.equals("spPr")) { // axis shape properties - for axis or gridlines + lastTag.push(tnm); + this.setSpPr((SpPr) SpPr.parseOOXML(xpp, lastTag, bk).cloneElement()); + } else if (tnm.equals("txPr")) { // text Properties for axis + lastTag.push(tnm); + this.settxPr((TxPr) TxPr.parseOOXML(xpp, lastTag, bk).cloneElement()); + // crossesAx = crossing axis id - need ? + } else if ( + tnm.equals("crosses") || // possible crossing points (autoZero, max, min) + tnm.equals("crossesAt")) { // where on axis the perpendicular axis crosses (double val) + this.setOption(tnm, xpp.getAttributeValue(0)); + } else if (tnm.equals("crossBetween")) { // whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) + this.setOption(tnm, xpp.getAttributeValue(0)); + } else if ( // cat, date, ser ax only + // auto -- Date only? + tnm.equals("lblAlign") || // text alignment for tick labels (ctr, l, r) only for cat + tnm.equals("lblOffset") || // distance of labels from the axis (0-1000) only for cat, date + tnm.equals("tickLblSkip") || // how many tick labels to skip between label (int >= 1) + tnm.equals("tickMarkSkip")) { // how many tick marks to skip betwen ticks (int >= 1) + this.setOption(tnm, xpp.getAttributeValue(0)); +// TODO: noMultiLvlLbl + } else if ( // val, ser ax + some date ax + tnm.equals("crossBeteween") || // whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) + tnm.equals("majorUnit") || // distance between major tick marks (val, date ax only) (double >= 0) + tnm.equals("minorUnit")) { // distance between minor tick marks (val, date ax only) (double >= 0) + this.setOption(tnm, xpp.getAttributeValue(0)); + } else if (tnm.equals("dispUnits")) { // valAx only + parseDispUnitsOOXML(xpp, lastTag); + } +// TODO: date ax specifics + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals(axisTag)) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("Axis: " + e.toString()); + } } /** * parse major or minor Gridlines element + * * @param xpp * @param lastTag */ private void parseGridlinesOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String endTag= (String) lastTag.peek(); - this.setOption(endTag, "true"); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spPr")) { - lastTag.push(tnm); - SpPr sppr= (SpPr)SpPr.parseOOXML(xpp, lastTag, bk).cloneElement(); - LineFormat lf= getAxisLine((endTag.equals("majorGridlines")?AxisLineFormat.ID_MAJOR_GRID:AxisLineFormat.ID_MINOR_GRID)); - lf.setFromOOXML(sppr); - } - } else if (eventType== XmlPullParser.END_TAG) { - if (xpp.getName().equals(endTag)){ - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("parseGridLinesOOXML: " + e.toString()); - } - } - + String endTag = lastTag.peek(); + this.setOption(endTag, "true"); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("spPr")) { + lastTag.push(tnm); + SpPr sppr = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk).cloneElement(); + LineFormat lf = getAxisLine((endTag.equals("majorGridlines") ? AxisLineFormat.ID_MAJOR_GRID : AxisLineFormat.ID_MINOR_GRID)); + lf.setFromOOXML(sppr); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals(endTag)) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("parseGridLinesOOXML: " + e.toString()); + } + } + /** * parse the dispUnits child element of valAx - *
                TODO: do not know how to interpret most of these options + *
                TODO: do not know how to interpret most of these options + * * @param xpp * @param lastTag */ private void parseDispUnitsOOXML(XmlPullParser xpp, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - YMult ym= null; - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("custUnit")) { - ym= getYMultRec(true); - ym.setCustomMultiplier(Double.valueOf(xpp.getAttributeValue(0))); - }else if (tnm.equals("builtInUnit")) { - ym= getYMultRec(true); - ym.setAxMultiplierId(xpp.getAttributeValue(0)); - } - // TODO: dispUnitLbl - } else if (eventType== XmlPullParser.END_TAG) { - if (xpp.getName().equals("dispUnits")){ - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("parseDispUnitsOOXML: " + e.toString()); - } - } - + try { + int eventType = xpp.getEventType(); + YMult ym = null; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("custUnit")) { + ym = getYMultRec(true); + ym.setCustomMultiplier(Double.valueOf(xpp.getAttributeValue(0))); + } else if (tnm.equals("builtInUnit")) { + ym = getYMultRec(true); + ym.setAxMultiplierId(xpp.getAttributeValue(0)); + } + // TODO: dispUnitLbl + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals("dispUnits")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("parseDispUnitsOOXML: " + e.toString()); + } + } + /** - * generate the appropriate ooxml for the given axis - * @param type 0= Category Axis, 1= Value Axis, 2= Ser Axis, 3= Date Axis + * generate the appropriate ooxml for the given axis + * + * @param type 0= Category Axis, 1= Value Axis, 2= Ser Axis, 3= Date Axis * @param id - * @param crossId crossing axis id - * @return - * - * ORDER: + * @param crossId crossing axis id + * @return ORDER: * Axis Common: - * axisId REQ - * scaling -- orientation - * delete - * axPos REQ? - * majorGridlines - * minorGridlines - * title - * numFmt - * majorTickMark - * minorTickMark - * tickLblPos - * spPr - * txPr - * crossAx - * crosses OR - * crossesAt - * after: + * axisId REQ + * scaling -- orientation + * delete + * axPos REQ? + * majorGridlines + * minorGridlines + * title + * numFmt + * majorTickMark + * minorTickMark + * tickLblPos + * spPr + * txPr + * crossAx + * crosses OR + * crossesAt + * after: * valAx: - * crossBetween - * majorUnit - * minorUnit - * dispUnits + * crossBetween + * majorUnit + * minorUnit + * dispUnits * catAx: - * auto - * lblAlign - * lblOffet - * tickLblSkip - * tickMarkSkip - * noMultiLvlLbl + * auto + * lblAlign + * lblOffet + * tickLblSkip + * tickMarkSkip + * noMultiLvlLbl * serAx: - * tickLblSkip - * tickMarkSkip + * tickLblSkip + * tickMarkSkip */ - + public String getOOXML(int type, String id, String crossId) { - if (this.getParentChart()==null) // happens on ZAxis, XYValAxis ... - this.setParentChart(this.ap.getParentChart()); - boolean from2003= (!parentChart.getWorkBook().getIsExcel2007()); - - StringBuffer axisooxml= new StringBuffer(); - String axis= ""; - switch (type) { - case 0: // cat axis "X" - axis= "catAx"; - break; - case 1: // val axis "Y" - axis= "valAx"; - break; - case 2: // xval axis "X" axis for multiple val axes: bubble & scatter charts - axis= "valAx"; - break; - case 3: // ser ("Z" axis) - axis= "serAx"; - break; - case 4: // date TODO: Not correct or tested! - axis= "dateAx"; - } - // axis main element - axisooxml.append(""); axisooxml.append("\r\n"); - // axId - required - axisooxml.append(""); axisooxml.append("\r\n"); - // scaling - required - String s= this.getOption("orientation"); - double[] d= this.getMinMax(); - if (s!=null || d[0]!=d[1]) { // if have orientation or min/max set .. - axisooxml.append("\r\n"); - if (s!=null) axisooxml.append("\r\n"); - axisooxml.append("\r\n"); - } - // axPos - required - if (this.getOption("axPos")!=null) { - axisooxml.append(""); axisooxml.append("\r\n"); } - else {// it's required - if (this.getParentChart().getChartType()!=BARCHART) { - if (axis.equals("catAx") || axis.equals("serAx")) - axisooxml.append(""); - else - axisooxml.append(""); - } else { - if (axis.equals("catAx") || axis.equals("serAx")) - axisooxml.append(""); - else - axisooxml.append(""); - } - axisooxml.append("\r\n"); - } - // major Gridlines - if (this.hasGridlines(AxisLineFormat.ID_MAJOR_GRID)) { - axisooxml.append(""); - axisooxml.append(getAxisLine(AxisLineFormat.ID_MAJOR_GRID).getOOXML()); - axisooxml.append("\r\n"); - } - // minor Gridlines - if (this.hasGridlines(AxisLineFormat.ID_MINOR_GRID)) { - axisooxml.append(""); - axisooxml.append(getAxisLine(AxisLineFormat.ID_MINOR_GRID).getOOXML()); - axisooxml.append("\r\n"); - } - // Title - if (this.getOOXMLTitle()!=null) axisooxml.append(this.getOOXMLTitle().getOOXML()); - else if (from2003) { // create OOXML title - if (!this.getTitle().equals("")) { - io.starter.formats.OOXML.Title ttl= new io.starter.formats.OOXML.Title(this.getTitle()); - if (type==0) - ttl.setLayout(.026, .378); - else if (type==1) - ttl.setLayout(.468, .863); - axisooxml.append(ttl.getOOXML()); - } - } - // numFmt - if (this.nf!=null) - axisooxml.append(nf.getOOXML("c:")); //need a default???: axisooxml.append(""); axisooxml.append("\r\n"); - // majorTickMark - s= this.getOption("majorTickMark"); // default= "cross" - if (s!=null) axisooxml.append(""); - // minorTickMark - s= this.getOption("minorTickMark"); // default= "cross" - if (s!=null) axisooxml.append(""); - // tickLblPos - s= this.getOption("tickLblPos"); // default= "nextTo" - if (s!=null) axisooxml.append(""); - // shape properties - if (this.getSpPr()!=null) axisooxml.append(this.getSpPr().getOOXML()); - // text props - if (this.gettxPr()!=null) axisooxml.append(this.gettxPr().getOOXML()); - else if (from2003){ // XLS->XLSX -/* + if (this.getParentChart() == null) // happens on ZAxis, XYValAxis ... + this.setParentChart(this.ap.getParentChart()); + boolean from2003 = (!parentChart.getWorkBook().getIsExcel2007()); + + StringBuffer axisooxml = new StringBuffer(); + String axis = ""; + switch (type) { + case 0: // cat axis "X" + axis = "catAx"; + break; + case 1: // val axis "Y" + axis = "valAx"; + break; + case 2: // xval axis "X" axis for multiple val axes: bubble & scatter charts + axis = "valAx"; + break; + case 3: // ser ("Z" axis) + axis = "serAx"; + break; + case 4: // date TODO: Not correct or tested! + axis = "dateAx"; + } + // axis main element + axisooxml.append(""); + axisooxml.append("\r\n"); + // axId - required + axisooxml.append(""); + axisooxml.append("\r\n"); + // scaling - required + String s = this.getOption("orientation"); + double[] d = this.getMinMax(); + if (s != null || d[0] != d[1]) { // if have orientation or min/max set .. + axisooxml.append("\r\n"); + if (s != null) axisooxml.append("\r\n"); + axisooxml.append("\r\n"); + } + // axPos - required + if (this.getOption("axPos") != null) { + axisooxml.append(""); + axisooxml.append("\r\n"); + } else {// it's required + if (this.getParentChart().getChartType() != BARCHART) { + if (axis.equals("catAx") || axis.equals("serAx")) + axisooxml.append(""); + else + axisooxml.append(""); + } else { + if (axis.equals("catAx") || axis.equals("serAx")) + axisooxml.append(""); + else + axisooxml.append(""); + } + axisooxml.append("\r\n"); + } + // major Gridlines + if (this.hasGridlines(AxisLineFormat.ID_MAJOR_GRID)) { + axisooxml.append(""); + axisooxml.append(getAxisLine(AxisLineFormat.ID_MAJOR_GRID).getOOXML()); + axisooxml.append("\r\n"); + } + // minor Gridlines + if (this.hasGridlines(AxisLineFormat.ID_MINOR_GRID)) { + axisooxml.append(""); + axisooxml.append(getAxisLine(AxisLineFormat.ID_MINOR_GRID).getOOXML()); + axisooxml.append("\r\n"); + } + // Title + if (this.getOOXMLTitle() != null) axisooxml.append(this.getOOXMLTitle().getOOXML()); + else if (from2003) { // create OOXML title + if (!this.getTitle().equals("")) { + io.starter.formats.OOXML.Title ttl = new io.starter.formats.OOXML.Title(this.getTitle()); + if (type == 0) + ttl.setLayout(.026, .378); + else if (type == 1) + ttl.setLayout(.468, .863); + axisooxml.append(ttl.getOOXML()); + } + } + // numFmt + if (this.nf != null) + axisooxml.append(nf.getOOXML("c:")); //need a default???: axisooxml.append(""); axisooxml.append("\r\n"); + // majorTickMark + s = this.getOption("majorTickMark"); // default= "cross" + if (s != null) axisooxml.append(""); + // minorTickMark + s = this.getOption("minorTickMark"); // default= "cross" + if (s != null) axisooxml.append(""); + // tickLblPos + s = this.getOption("tickLblPos"); // default= "nextTo" + if (s != null) axisooxml.append(""); + // shape properties + if (this.getSpPr() != null) axisooxml.append(this.getSpPr().getOOXML()); + // text props + if (this.gettxPr() != null) axisooxml.append(this.gettxPr().getOOXML()); + else if (from2003) { // XLS->XLSX +/* * label font: * Fontx fx= (Fontx) Chart.findRec(chartArr, Fontx.class); return this.getParentChart().getWorkBook().getFont(fx.getIfnt()); */ - int rot= 0; - Tick t= (Tick) Chart.findRec(this.chartArr, Tick.class); - if (t!=null) { // shoudn't - rot= t.getRotation(); - /** - 0= no rotation (text appears left-to-right), - 1= text appears top-~~ are upright, - 2= text is rotated 90 degrees counterclockwise, - 3= text is rotated - */ - // convert BIFF8 rotation to TxPr rotation: - switch (rot) { - case 1: - //???? - break; - case 2: - rot= -5400000; - break; - case 3: - rot= 5400000; - break; - } - // TODO: is vert rotation from td? - TxPr txpr= new TxPr(this.getLabelFont(), rot, null); - axisooxml.append(txpr.getOOXML()); - } - axisooxml.append("\r\n"); - } - // crossesAx - axisooxml.append(""); axisooxml.append("\r\n"); // crosses axis ... - // crosses -- autoZero, max, min - if (this.getOption("crosses")!=null) - axisooxml.append(""); axisooxml.append("\r\n");// where axis crosses it's perpendicular axis - if (axis.equals("catAx") || axis.equals("serAx")) { - // auto - axisooxml.append("\r\n"); - s= this.getOption("lblAlgn"); - if (s!=null) axisooxml.append("\r\n"); - s= this.getOption("lblOffset"); - if (s!=null) axisooxml.append("\r\n"); - s= this.getOption("tickLblSkip"); - if (s!=null) axisooxml.append("\r\n"); - s= this.getOption("tickMarkSkip"); - if (s!=null) axisooxml.append("\r\n"); - // TODO: noMutliLvlLbl - } else { // val or date - s= this.getOption("crossBetween"); - if (s!=null) axisooxml.append("\r\n"); - s= this.getOption("majorUnit"); - if (s!=null) axisooxml.append("\r\n"); - s= this.getOption("minorUnit"); - if (s!=null) axisooxml.append("\r\n"); + int rot = 0; + Tick t = (Tick) Chart.findRec(this.chartArr, Tick.class); + if (t != null) { // shoudn't + rot = t.getRotation(); + /** + 0= no rotation (text appears left-to-right), + 1= text appears top-~~ are upright, + 2= text is rotated 90 degrees counterclockwise, + 3= text is rotated + */ + // convert BIFF8 rotation to TxPr rotation: + switch (rot) { + case 1: + //???? + break; + case 2: + rot = -5400000; + break; + case 3: + rot = 5400000; + break; + } + // TODO: is vert rotation from td? + TxPr txpr = new TxPr(this.getLabelFont(), rot, null); + axisooxml.append(txpr.getOOXML()); + } + axisooxml.append("\r\n"); + } + // crossesAx + axisooxml.append(""); + axisooxml.append("\r\n"); // crosses axis ... + // crosses -- autoZero, max, min + if (this.getOption("crosses") != null) + axisooxml.append(""); + axisooxml.append("\r\n");// where axis crosses it's perpendicular axis + if (axis.equals("catAx") || axis.equals("serAx")) { + // auto + axisooxml.append("\r\n"); + s = this.getOption("lblAlgn"); + if (s != null) axisooxml.append("\r\n"); + s = this.getOption("lblOffset"); + if (s != null) axisooxml.append("\r\n"); + s = this.getOption("tickLblSkip"); + if (s != null) axisooxml.append("\r\n"); + s = this.getOption("tickMarkSkip"); + if (s != null) axisooxml.append("\r\n"); + // TODO: noMutliLvlLbl + } else { // val or date + s = this.getOption("crossBetween"); + if (s != null) axisooxml.append("\r\n"); + s = this.getOption("majorUnit"); + if (s != null) axisooxml.append("\r\n"); + s = this.getOption("minorUnit"); + if (s != null) axisooxml.append("\r\n"); // TODO: dispUnit ************************************ - - } - axisooxml.append(""); axisooxml.append("\r\n"); - return axisooxml.toString(); - } - - - + + } + axisooxml.append(""); + axisooxml.append("\r\n"); + return axisooxml.toString(); + } + + /** - * returns the SVG representation of the desired axis + * returns the SVG representation of the desired axis + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max * @return String SVG */ public String getSVG(ChartAxes ca, java.util.Map chartMetrics, Object[] categories) { - StringBuffer svg= new StringBuffer(); - - // for all axies-- label and title fonts, rotation, tick info ... - // Title + Label SVG - String labelfontSVG= ""; - String titlefontSVG= ""; - - try { - labelfontSVG= this.getLabelFont().getSVG(); // uses specific or default for chart - } catch (Exception e) { // shouldn't - labelfontSVG= "font-family='Arial' font-size='9pt' fill='"+ ChartType.getDarkColor() + "' "; - } - try { - titlefontSVG= linkedtd.getFont(this.getParentChart().getWorkBook()).getSVG(); - } catch (NullPointerException e) { - } - boolean showMinorTickMarks= false, showMajorTickMarks= true; - try { - Tick t= (Tick) Chart.findRec(chartArr, Tick.class); - showMinorTickMarks= t.showMinorTicks(); - showMajorTickMarks= t.showMajorTicks(); - } catch (Exception e) {} - - // BAR CHART AXES ARE SWITCHED - handle seperately for clarity; radar axes are also handled separately as are significantly different than regualr charts - int charttype= this.getParentChart().getChartType(); - if (charttype==ChartConstants.BARCHART) return getSVGBARCHART(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories); - if (charttype==ChartConstants.RADARCHART) return getSVGRADARCHART(ca, titlefontSVG, labelfontSVG, chartMetrics, categories); - - int wtype= wType; - if (wtype==XAXIS && (charttype==ChartConstants.SCATTERCHART || charttype==ChartConstants.BUBBLECHART)) { // XY Charts - X Axis is a Value Axis - wtype= XVALAXIS; - } - - switch (wtype) { - case XAXIS: - svg.append(drawXAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories)); - - break; - case YAXIS: - svg.append(drawYAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics)); - - break; - case ZAXIS: // ?? - break; - - case XVALAXIS: // Scatter/Bubble Chart X Value Axis - svg.append(drawXYValAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories)); - break; - } - return svg.toString(); + StringBuffer svg = new StringBuffer(); + + // for all axies-- label and title fonts, rotation, tick info ... + // Title + Label SVG + String labelfontSVG = ""; + String titlefontSVG = ""; + + try { + labelfontSVG = this.getLabelFont().getSVG(); // uses specific or default for chart + } catch (Exception e) { // shouldn't + labelfontSVG = "font-family='Arial' font-size='9pt' fill='" + ChartType.getDarkColor() + "' "; + } + try { + titlefontSVG = linkedtd.getFont(this.getParentChart().getWorkBook()).getSVG(); + } catch (NullPointerException e) { + } + boolean showMinorTickMarks = false, showMajorTickMarks = true; + try { + Tick t = (Tick) Chart.findRec(chartArr, Tick.class); + showMinorTickMarks = t.showMinorTicks(); + showMajorTickMarks = t.showMajorTicks(); + } catch (Exception e) { + } + + // BAR CHART AXES ARE SWITCHED - handle seperately for clarity; radar axes are also handled separately as are significantly different than regualr charts + int charttype = this.getParentChart().getChartType(); + if (charttype == ChartConstants.BARCHART) + return getSVGBARCHART(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories); + if (charttype == ChartConstants.RADARCHART) + return getSVGRADARCHART(ca, titlefontSVG, labelfontSVG, chartMetrics, categories); + + int wtype = wType; + if (wtype == XAXIS && (charttype == ChartConstants.SCATTERCHART || charttype == ChartConstants.BUBBLECHART)) { // XY Charts - X Axis is a Value Axis + wtype = XVALAXIS; + } + + switch (wtype) { + case XAXIS: + svg.append(drawXAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories)); + + break; + case YAXIS: + svg.append(drawYAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics)); + + break; + case ZAXIS: // ?? + break; + + case XVALAXIS: // Scatter/Bubble Chart X Value Axis + svg.append(drawXYValAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories)); + break; + } + return svg.toString(); } + /** * generate SVG for a basic (non-bar-chart, non-textual) X Axis + * * @param ca * @param titlefontSVG * @param labelfontSVG * @param rot * @param showMinorTickMarks * @param showMajorTickMarks - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max * @return */ - private String drawXAxisSVG(ChartAxes ca, String titlefontSVG, String labelfontSVG, boolean showMinorTickMarks, boolean showMajorTickMarks, java.util.Map chartMetrics, Object[] categories) { - StringBuffer svg= new StringBuffer(); - // X Axis TICKS x= rectX, x2= x1, y1= recty+h, y2=y1+5 (major) x1 increments by (#Major Ticks-1)/Width (usually 4) - // x= rectX+17, x2= x1, y1= recty+h y2=y1+2 (minor) x1 increments by (approx 8) - // LABELS (CATEGORIES): - do before ticks NOTE: patterns and formatting is applied .adjustCoordinates in order to account for fitting in space - - // when x axis is reversed means that categories are right to left and the y axis is on the RHS - // when y axis is reversed means the categories are on TOP of the chart and y axis labels are reversed - double x0, x1, y0, y1; - double inc; - java.awt.Font f= null; - double x= chartMetrics.get("x"); - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double canvash= chartMetrics.get("canvash"); - boolean yAxisReversed= (Boolean)ca.getMetric("yAxisReversed"); - boolean xAxisReversed= (Boolean)ca.getMetric("xAxisReversed"); - int xAxisRotate= (Integer)ca.getMetric("xAxisRotate"); - double XAXISLABELOFFSET= (Double)ca.getMetric("XAXISLABELOFFSET"); - double XAXISTITLEOFFSET= (Double)ca.getMetric("XAXISTITLEOFFSET"); - - int labelRot= (wType==XAXIS?xAxisRotate:0); // TODO: handle Y axis rotation - if (labelRot!=0) { - // get font object so can calculate rotation point - io.starter.formats.XLS.Font lf= this.getLabelFont(); - try { - // get awt Font so can compute and fit category in width - f= new java.awt.Font(lf.getFontName(), lf.getFontWeight(), (int)lf.getFontHeightInPoints()); - } catch (Exception e) {} - } - if (categories!=null && categories.length>0) { // shouldn't - // Category Labels - centered within area on X Axis - inc= w/categories.length; - svg.append(getCategoriesSVG(x, y, w, h, inc, labelRot, categories, f, labelfontSVG, yAxisReversed, xAxisReversed, XAXISLABELOFFSET)); - // TICK MARKS - y0= y + (!yAxisReversed?h:0); // ticks at bottom edge of axis unless Y axiis is reversed - x0= x; // start at chart x - int rfY= (!yAxisReversed?1:-1); // reverse factor :) - int rfX= (!xAxisReversed?1:-1); // reverse factor :) - svg.append("\r\n"); - inc= w/(categories.length); // w/scale factor - double minorinc= 0; - if (showMinorTickMarks) - minorinc= inc/2; // half-marks for category axis - for (double i= 0; i <= categories.length; i++) { - y1= y0+2*rfY; // minor tick mark - if (showMinorTickMarks) { - for (int j= 0; j < 2; j++) { // for categories, only option is 1/2 major - svg.append("\r\n"); - x0+=minorinc; - } - } - y1= y0+5*rfY; // Major tick marks - if (showMajorTickMarks) - svg.append("\r\n" ); - x0+=inc; - } - // bounding edge line - if (hasPlotAreaBorder()) { - x0= x + (!xAxisReversed?w:0); - svg.append("\r\n" ); - } - svg.append("\r\n"); - } - // X AXIS TITLE - int titleRot= (linkedtd!=null)?linkedtd.getRotation():0; - x0= x + w/2; - if (!yAxisReversed) // TODO: why doesn't "normal" calc work??????? - y0= canvash-XAXISTITLEOFFSET; - else - y0= y -XAXISTITLEOFFSET - XAXISLABELOFFSET; - - svg.append(getAxisTitleSVG(x0, y0, titlefontSVG, titleRot, "xaxistitle")); - return svg.toString(); - } - - static int YLABELSSPACER_X=10; - static int YLABELSPACER_Y=4; + private String drawXAxisSVG(ChartAxes ca, String titlefontSVG, String labelfontSVG, boolean showMinorTickMarks, boolean showMajorTickMarks, java.util.Map chartMetrics, Object[] categories) { + StringBuffer svg = new StringBuffer(); + // X Axis TICKS x= rectX, x2= x1, y1= recty+h, y2=y1+5 (major) x1 increments by (#Major Ticks-1)/Width (usually 4) + // x= rectX+17, x2= x1, y1= recty+h y2=y1+2 (minor) x1 increments by (approx 8) + // LABELS (CATEGORIES): - do before ticks NOTE: patterns and formatting is applied .adjustCoordinates in order to account for fitting in space + + // when x axis is reversed means that categories are right to left and the y axis is on the RHS + // when y axis is reversed means the categories are on TOP of the chart and y axis labels are reversed + double x0, x1, y0, y1; + double inc; + java.awt.Font f = null; + double x = chartMetrics.get("x"); + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double canvash = chartMetrics.get("canvash"); + boolean yAxisReversed = (Boolean) ca.getMetric("yAxisReversed"); + boolean xAxisReversed = (Boolean) ca.getMetric("xAxisReversed"); + int xAxisRotate = (Integer) ca.getMetric("xAxisRotate"); + double XAXISLABELOFFSET = (Double) ca.getMetric("XAXISLABELOFFSET"); + double XAXISTITLEOFFSET = (Double) ca.getMetric("XAXISTITLEOFFSET"); + + int labelRot = (wType == XAXIS ? xAxisRotate : 0); // TODO: handle Y axis rotation + if (labelRot != 0) { + // get font object so can calculate rotation point + io.starter.formats.XLS.Font lf = this.getLabelFont(); + try { + // get awt Font so can compute and fit category in width + f = new java.awt.Font(lf.getFontName(), lf.getFontWeight(), (int) lf.getFontHeightInPoints()); + } catch (Exception e) { + } + } + if (categories != null && categories.length > 0) { // shouldn't + // Category Labels - centered within area on X Axis + inc = w / categories.length; + svg.append(getCategoriesSVG(x, y, w, h, inc, labelRot, categories, f, labelfontSVG, yAxisReversed, xAxisReversed, XAXISLABELOFFSET)); + // TICK MARKS + y0 = y + (!yAxisReversed ? h : 0); // ticks at bottom edge of axis unless Y axiis is reversed + x0 = x; // start at chart x + int rfY = (!yAxisReversed ? 1 : -1); // reverse factor :) + int rfX = (!xAxisReversed ? 1 : -1); // reverse factor :) + svg.append("\r\n"); + inc = w / (categories.length); // w/scale factor + double minorinc = 0; + if (showMinorTickMarks) + minorinc = inc / 2; // half-marks for category axis + for (double i = 0; i <= categories.length; i++) { + y1 = y0 + 2 * rfY; // minor tick mark + if (showMinorTickMarks) { + for (int j = 0; j < 2; j++) { // for categories, only option is 1/2 major + svg.append("\r\n"); + x0 += minorinc; + } + } + y1 = y0 + 5 * rfY; // Major tick marks + if (showMajorTickMarks) + svg.append("\r\n"); + x0 += inc; + } + // bounding edge line + if (hasPlotAreaBorder()) { + x0 = x + (!xAxisReversed ? w : 0); + svg.append("\r\n"); + } + svg.append("\r\n"); + } + // X AXIS TITLE + int titleRot = (linkedtd != null) ? linkedtd.getRotation() : 0; + x0 = x + w / 2; + if (!yAxisReversed) // TODO: why doesn't "normal" calc work??????? + y0 = canvash - XAXISTITLEOFFSET; + else + y0 = y - XAXISTITLEOFFSET - XAXISLABELOFFSET; + + svg.append(getAxisTitleSVG(x0, y0, titlefontSVG, titleRot, "xaxistitle")); + return svg.toString(); + } + + static int YLABELSSPACER_X = 10; + static int YLABELSPACER_Y = 4; + /** * generate SVG for a basic (non-bar-chart, value or numeric) Y Axis + * * @param ca * @param titlefontSVG * @param labelfontSVG * @param rot * @param showMinorTickMarks * @param showMajorTickMarks - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @return + */ + // TODO: label rotation + private String drawYAxisSVG(ChartAxes ca, String titlefontSVG, String labelfontSVG, boolean showMinorTickMarks, boolean showMajorTickMarks, java.util.Map chartMetrics) { + StringBuffer svg = new StringBuffer(); + // Y or Value Axis- must be non-textual; values obtained in calling method + // When Y Axis is reversed, scale is reversed and x axis labels and title are on top + // When X Axis is reversed, Y scale/labels and title are on RHS + double x0, x1, y0, y1; + double inc; + // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 + double x = chartMetrics.get("x"); + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + double minor = (Double) ca.getMetric("minor"); + double major = (Double) ca.getMetric("major"); + boolean scaleIsInteger = (major == Math.floor(major)); // true if scale is in integer form rather than double (used for formatting label text) + int titleRot = (linkedtd != null) ? linkedtd.getRotation() : 0; + boolean xAxisReversed = (Boolean) ca.getMetric("xAxisReversed"); + boolean yAxisReversed = (Boolean) ca.getMetric("yAxisReversed"); + String xPattern = (String) ca.getMetric("xPattern"); + String yPattern = (String) ca.getMetric("yPattern"); + double YAXISLABELOFFSET = (Double) ca.getMetric("YAXISLABELOFFSET"); + double YAXISTITLEOFFSET = (Double) ca.getMetric("YAXISTITLEOFFSET"); + + // Y Axis GRIDLINES: -- x1=rectX x2=rectx+rect w y1=y2 y1 starts with recty+h, decrements by= approx 27 ==rectheight/# lines ***************************** + // Major/Minor tick marks + if (major > 0) { // if is displaying tick marks/grid lines - usual case + inc = h / ((max - min) / major); + double minorinc = 0; + if (minor > 0) + minorinc = inc / (major / minor); + x0 = x; + y0 = y + h; + x1 = x + w; // entire width + // GRIDLINES + String lineSVG = getLineSVG(AxisLineFormat.ID_MAJOR_GRID); + svg.append("\r\n"); + for (double i = min; i <= max; i += major) { + svg.append("\r\n"); + y0 -= inc; + } + svg.append("\r\n"); + // TICK MARKS + svg.append("\r\n"); + int rfY = (!yAxisReversed ? 1 : -1); // reverse factor :) + int rfX = (!xAxisReversed ? 1 : -1); // reverse factor :) + y0 = y + (!yAxisReversed ? h : 0); // starts at bottom, goes up to top except if reversed + lineSVG = ChartType.getStrokeSVG(); + int scale = 0; + { // figure out if scale has a fractional portion; if so, determine desired scale and keep to that + String s = String.valueOf(major); + int z = s.indexOf("."); + if (z != -1) + scale = s.length() - (z + 1); + } + int k = 0; // axis label index + for (double i = min; i <= max; i += major) { + x0 = x + (!xAxisReversed ? 0 : w); + x1 = x0 - 2 * rfX;// minor ticks + y1 = y0; + if (i < max && minor > 0) { + if (showMinorTickMarks) { + for (int j = 0; j < (major / minor); j++) { + svg.append("\r\n"); + y0 -= minorinc; + } + } + } + y0 = y1; + x1 = x0 - 5 * rfX;// Major tick Marks + if (showMajorTickMarks) + svg.append("\r\n"); + // Y Axis Labels + java.math.BigDecimal bd = new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); // if fractional, ensure java's floating point crap is handled corectly + if (!xAxisReversed) + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); + else + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); + y0 -= inc * rfY; + } + svg.append("\r\n"); + // AXIS bounding line + x0 = x + (!xAxisReversed ? 0 : w); + y0 = y; + svg.append("\r\n"); + // Y AXIS TITLE + if (!xAxisReversed) + x0 = /*YAXISTITLEOFFSET + */10; // TODO: 10 should be actual font height (when rotated 90 as is the norm) + else + x0 = x + w + YAXISTITLEOFFSET; + svg.append(getAxisTitleSVG(x0, (y + h / 2), titlefontSVG, titleRot, "yaxistitle")); + } + return svg.toString(); + } + + /** + * return the svg to display an XYValue axis (bubble, scatter) + * + * @param ca + * @param titlefontSVG + * @param labelfontSVG + * @param showMinorTickMarks + * @param showMajorTickMarks + * @param chartMetrics * @return */ - // TODO: label rotation - private String drawYAxisSVG(ChartAxes ca, String titlefontSVG, String labelfontSVG, boolean showMinorTickMarks, boolean showMajorTickMarks, java.util.Map chartMetrics) { - StringBuffer svg= new StringBuffer(); - // Y or Value Axis- must be non-textual; values obtained in calling method - // When Y Axis is reversed, scale is reversed and x axis labels and title are on top - // When X Axis is reversed, Y scale/labels and title are on RHS - double x0, x1, y0, y1; - double inc; - // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 - double x= chartMetrics.get("x"); - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - double minor= (Double) ca.getMetric("minor"); - double major= (Double) ca.getMetric("major"); - boolean scaleIsInteger= (major==Math.floor(major)); // true if scale is in integer form rather than double (used for formatting label text) - int titleRot= (linkedtd!=null)?linkedtd.getRotation():0; - boolean xAxisReversed= (Boolean)ca.getMetric("xAxisReversed"); - boolean yAxisReversed= (Boolean)ca.getMetric("yAxisReversed"); - String xPattern= (String)ca.getMetric("xPattern"); - String yPattern= (String)ca.getMetric("yPattern"); - double YAXISLABELOFFSET= (Double)ca.getMetric("YAXISLABELOFFSET"); - double YAXISTITLEOFFSET= (Double)ca.getMetric("YAXISTITLEOFFSET"); - - // Y Axis GRIDLINES: -- x1=rectX x2=rectx+rect w y1=y2 y1 starts with recty+h, decrements by= approx 27 ==rectheight/# lines ***************************** - // Major/Minor tick marks - if (major>0) { // if is displaying tick marks/grid lines - usual case - inc= h/((max-min)/major); - double minorinc= 0; - if (minor > 0) - minorinc= inc/(major/minor); - x0= x; - y0= y + h; - x1= x+ w; // entire width - // GRIDLINES - String lineSVG= getLineSVG(AxisLineFormat.ID_MAJOR_GRID); - svg.append("\r\n"); - for (double i= min; i <= max; i+=major) { - svg.append("\r\n"); - y0-=inc; - } - svg.append("\r\n"); - // TICK MARKS - svg.append("\r\n"); - int rfY= (!yAxisReversed?1:-1); // reverse factor :) - int rfX= (!xAxisReversed?1:-1); // reverse factor :) - y0= y + (!yAxisReversed?h:0); // starts at bottom, goes up to top except if reversed - lineSVG= ChartType.getStrokeSVG(); - int scale= 0; - { // figure out if scale has a fractional portion; if so, determine desired scale and keep to that - String s= String.valueOf(major); - int z= s.indexOf("."); - if (z!=-1) - scale= s.length()-(z+1); - } - int k= 0; // axis label index - for (double i= min; i <= max; i+=major) { - x0= x + (!xAxisReversed?0:w); - x1= x0-2*rfX;// minor ticks - y1= y0; - if (i < max && minor > 0) { - if (showMinorTickMarks) { - for (int j= 0; j < (major/minor); j++) { - svg.append("\r\n"); - y0-=minorinc; - } - } - } - y0=y1; - x1= x0-5*rfX;// Major tick Marks - if (showMajorTickMarks) - svg.append("\r\n"); - // Y Axis Labels - java.math.BigDecimal bd= new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); // if fractional, ensure java's floating point crap is handled corectly - if (!xAxisReversed) - svg.append("" + CellFormatFactory.fromPatternString( yPattern ).format( bd ) + "\r\n"); - else - svg.append("" + CellFormatFactory.fromPatternString( yPattern ).format( bd) + "\r\n"); - y0-=inc*rfY; - } - svg.append("\r\n"); - // AXIS bounding line - x0= x + (!xAxisReversed?0:w); - y0= y; - svg.append("\r\n"); - // Y AXIS TITLE - if (!xAxisReversed) - x0= /*YAXISTITLEOFFSET + */10; // TODO: 10 should be actual font height (when rotated 90 as is the norm) - else - x0= x+w+YAXISTITLEOFFSET; - svg.append(getAxisTitleSVG(x0, (y + h/2), titlefontSVG, titleRot, "yaxistitle")); - } - return svg.toString(); - } - - /** - * return the svg to display an XYValue axis (bubble, scatter) - * @param ca - * @param titlefontSVG - * @param labelfontSVG - * @param showMinorTickMarks - * @param showMajorTickMarks - * @param chartMetrics - * @return - */ - private String drawXYValAxisSVG(ChartAxes ca, String titlefontSVG, String labelfontSVG, boolean showMinorTickMarks, boolean showMajorTickMarks, java.util.Map chartMetrics, Object[] categories) { - StringBuffer svg= new StringBuffer(); - double x0, x1, y0, y1; - double minorinc= 0; - double inc; - // Y or Value Axis- must be non-textual; values obtained in calling method - // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 - double x= chartMetrics.get("x"); - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - double minor= (Double) ca.getMetric("minor"); - double major= (Double) ca.getMetric("major"); - boolean scaleIsInteger= (major==Math.floor(major)); // true if scale is in integer form rather than double (used for formatting label text) - String yPattern= (String)ca.getMetric("yPattern"); - double XAXISLABELOFFSET= (Double)ca.getMetric("XAXISLABELOFFSET"); - double XAXISTITLEOFFSET= (Double)ca.getMetric("XAXISTITLEOFFSET"); - - if (categories!=null && categories.length>0) { // shouldn't - double xmin= Double.MAX_VALUE, xmax= Double.MIN_VALUE; - boolean TEXTUALXAXIS= true; - for (int j= 0; j < categories.length; j++) { - try { - double d= new Double(categories[j].toString()); - xmax= Math.max(xmax, d); - xmin= Math.min(xmin, d); - TEXTUALXAXIS= false; // if ANY category val is a double, assume it's a normal xyvalue axis - } catch (Exception e) { ; /* keep going */ } - } - if (!TEXTUALXAXIS) { - double d[]= ValueRange.calcMaxMin(xmax, xmin, w); - minor= (int) d[0]; - major= (int) d[1]; - xmax= d[2]; - - } else { // NO category values are numeric -- x axis is Textual - just use count - major= 1; - minor= 0; - xmax= categories.length+1; - XAXISLABELOFFSET= 30; // TODO: calculate - } - - // CATEGORY LABELS- centered within area on X Axis - y0= y + h + XAXISLABELOFFSET; - inc= w/(xmax/major); - x0= x; - int scale= 0; - { // figure out if scale has a fractional portion; if so, determine desired scale and keep to that - String s= String.valueOf(major); - int z= s.indexOf("."); - if (z!=-1) - scale= s.length()-(z+1); - } - int k= 0; // axis label index - for (double i= 0; i <= xmax; i+=major) { - if (!TEXTUALXAXIS) { - java.math.BigDecimal bd= new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); - svg.append("" + CellFormatFactory.fromPatternString( yPattern ).format( bd) + "\r\n"); - } else - svg.append("" + CellFormatFactory.fromPatternString( yPattern ).format( i) + "\r\n"); - x0+= inc; - } - - // TICK MARKS - y0= h + y; // origin at y+h - x0= x; // start at chart x - svg.append("\r\n"); - if (minor > 0) - minorinc= inc/minor; - for (int i= 0; i <= xmax; i+=major) { - y1= y0+2; // minor tick mark - for (int j= 0; j < minor; j++) { - svg.append("\r\n"); - x0+=minorinc; - } - y1= y0+5; // Major tick marks - svg.append("\r\n"); - if (minorinc==0) - x0+=inc; - } - svg.append("\r\n"); - } - // X AXIS TITLE - int titleRot= (linkedtd!=null)?linkedtd.getRotation():0; - svg.append(getAxisTitleSVG((x + w/2), (y + h + XAXISTITLEOFFSET), titlefontSVG, titleRot, "zaxistitle")); - return svg.toString(); - } + private String drawXYValAxisSVG(ChartAxes ca, String titlefontSVG, String labelfontSVG, boolean showMinorTickMarks, boolean showMajorTickMarks, java.util.Map chartMetrics, Object[] categories) { + StringBuffer svg = new StringBuffer(); + double x0, x1, y0, y1; + double minorinc = 0; + double inc; + // Y or Value Axis- must be non-textual; values obtained in calling method + // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 + double x = chartMetrics.get("x"); + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + double minor = (Double) ca.getMetric("minor"); + double major = (Double) ca.getMetric("major"); + boolean scaleIsInteger = (major == Math.floor(major)); // true if scale is in integer form rather than double (used for formatting label text) + String yPattern = (String) ca.getMetric("yPattern"); + double XAXISLABELOFFSET = (Double) ca.getMetric("XAXISLABELOFFSET"); + double XAXISTITLEOFFSET = (Double) ca.getMetric("XAXISTITLEOFFSET"); + + if (categories != null && categories.length > 0) { // shouldn't + double xmin = Double.MAX_VALUE, xmax = Double.MIN_VALUE; + boolean TEXTUALXAXIS = true; + for (int j = 0; j < categories.length; j++) { + try { + double d = new Double(categories[j].toString()); + xmax = Math.max(xmax, d); + xmin = Math.min(xmin, d); + TEXTUALXAXIS = false; // if ANY category val is a double, assume it's a normal xyvalue axis + } catch (Exception e) { + /* keep going */ + } + } + if (!TEXTUALXAXIS) { + double[] d = ValueRange.calcMaxMin(xmax, xmin, w); + minor = (int) d[0]; + major = (int) d[1]; + xmax = d[2]; + + } else { // NO category values are numeric -- x axis is Textual - just use count + major = 1; + minor = 0; + xmax = categories.length + 1; + XAXISLABELOFFSET = 30; // TODO: calculate + } + + // CATEGORY LABELS- centered within area on X Axis + y0 = y + h + XAXISLABELOFFSET; + inc = w / (xmax / major); + x0 = x; + int scale = 0; + { // figure out if scale has a fractional portion; if so, determine desired scale and keep to that + String s = String.valueOf(major); + int z = s.indexOf("."); + if (z != -1) + scale = s.length() - (z + 1); + } + int k = 0; // axis label index + for (double i = 0; i <= xmax; i += major) { + if (!TEXTUALXAXIS) { + java.math.BigDecimal bd = new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); + } else + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(i) + "\r\n"); + x0 += inc; + } + + // TICK MARKS + y0 = h + y; // origin at y+h + x0 = x; // start at chart x + svg.append("\r\n"); + if (minor > 0) + minorinc = inc / minor; + for (int i = 0; i <= xmax; i += major) { + y1 = y0 + 2; // minor tick mark + for (int j = 0; j < minor; j++) { + svg.append("\r\n"); + x0 += minorinc; + } + y1 = y0 + 5; // Major tick marks + svg.append("\r\n"); + if (minorinc == 0) + x0 += inc; + } + svg.append("\r\n"); + } + // X AXIS TITLE + int titleRot = (linkedtd != null) ? linkedtd.getRotation() : 0; + svg.append(getAxisTitleSVG((x + w / 2), (y + h + XAXISTITLEOFFSET), titlefontSVG, titleRot, "zaxistitle")); + return svg.toString(); + } /** * Bar chart Axes are switched so handle seperately * basically x axis holds Y labels and title, and visa versa + gridlines go up and down (traverse y) rather than across (traverse x) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max * @return String SVG */ private String getSVGBARCHART(ChartAxes ca, String titlefontSVG, String labelFontSVG, boolean showMinorTicks, boolean showMajorTicks, java.util.Map chartMetrics, Object[] categories) { - StringBuffer svg= new StringBuffer(); - - // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 - // X Axis/Cats in reversed order means X axis on Top, Y axis labels in reversed order (along with bars) - double x0, x1, y0, y1; - double inc; - int rfX= 1; // reverse factor used to reverse order + position of axes - int rfY= 1; // "" - boolean scaleIsInteger= true; // usual case - double x= chartMetrics.get("x"); - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double canvasw= chartMetrics.get("canvasw"); - double canvash= chartMetrics.get("canvash"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - boolean xAxisReversed= (Boolean)ca.getMetric("xAxisReversed"); - boolean yAxisReversed= (Boolean)ca.getMetric("yAxisReversed"); - double YAXISTITLEOFFSET= (Double)ca.getMetric("YAXISTITLEOFFSET"); - String yPattern= (String)ca.getMetric("yPattern"); - double XAXISLABELOFFSET= (Double)ca.getMetric("XAXISLABELOFFSET"); - double XAXISTITLEOFFSET= (Double)ca.getMetric("XAXISTITLEOFFSET"); - double major= (Double) ca.getMetric("major"); - - if (wType==XAXIS) { - scaleIsInteger= (major==Math.floor(major)); // true if scale is in integer form rather than double (used for formatting label text) - } - switch (wType) { - case XAXIS: - // X Axis TICKS x= rectX, x2= x1, y1= recty+h, y2=y1+5 (major) x1 increments by (#Major Ticks-1)/Width (usually 4) - // x= rectX+17, x2= x1, y1= recty+h y2=y1+2 (minor) x1 increments by (approx 8) - x0= x; // start at chart x - y0= y + (!xAxisReversed?h:0); // origin at y+h (unless reversed) - rfX= (!xAxisReversed?1:-1); - rfY= (!yAxisReversed?1:-1); - if (major > 0) { - svg.append("\r\n"); - inc= w/((max-min)/major); // w/scale factor - int scale= 0; - { // figure out if scale has a fractional portion; if so, determine desired scale and keep to that - String s= String.valueOf(major); - int z= s.indexOf("."); - if (z!=-1) - scale= s.length()-(z+1); - } - int k= 0; // axis label index - for (double i= min; i <= max; i+=major) {// traverse across bottom (or top, if reversed) of x axis) incrementing x value keeping y value constant - y1= y0+ 5*rfX; // Major tick marks - if (showMajorTicks) - svg.append("\r\n"); - // X axis labels= (Values) - java.math.BigDecimal bd= new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); - if (!xAxisReversed) - svg.append("" + CellFormatFactory.fromPatternString( yPattern ).format( bd) + "\r\n"); - else - svg.append("" + CellFormatFactory.fromPatternString( yPattern ).format( bd) + "\r\n"); - x0+=inc*rfY; - } - svg.append("\r\n"); - } - // AXIS bounding line - y0= y + (!xAxisReversed?h:0); // origin at y+h (unless reversed) - x0= x; // + (!yAxisReversed?0:ci.w); // start at chart x - svg.append("\r\n"); - // is the Y AXIS TITLE i.e. goes alongside Y AXIS - if (!yAxisReversed) - x0= YAXISTITLEOFFSET; - else - x0= x+w+YAXISTITLEOFFSET; - svg.append(getAxisTitleSVG(x0, (y + h/2), titlefontSVG, 90, "xaxistitle")); - break; - case YAXIS: - rfX= (!xAxisReversed?1:-1); // if reversed, y vals on top (x axis), cats are in reversed order (y axis) - rfY= (!yAxisReversed?1:-1); // if reversed, y vals in reverse order (x axis), cats are on RHS (y axis) - if (categories!=null && categories.length>0) { // should! - inc= h/categories.length; - x0= x; // + (!yAxisReversed?0:ci.w); // draw y axis tick marks + y axis labels (= categories) - y0= y + (!xAxisReversed?h:0); // starts at bottom, goes up to top unless reversed - int k= 0; // axis label index - for (int i= 0; i < categories.length; i++) { // traverse Y axis, spacing category labels (x is constant, y is segmented) - x1= x0-5*rfY;// Major tick Marks - if (showMajorTicks) - svg.append("\r\n"); - // Y Axis Labels = categories - y1= y0-inc*rfX+inc*.5*rfX; - if (!yAxisReversed) - svg.append("" + categories[i].toString() + "\r\n"); - else - svg.append("" + categories[i].toString() + "\r\n"); - y0-=inc*rfX; - } - // show gridlines (top to bottom) - String lineSVG=getLineSVG(AxisLineFormat.ID_MAJOR_GRID); - if (!lineSVG.equals("")) { - y0= y; - x0= x; // start at chart x - inc= w/((max-min)/major); // w/scale factor - // 1st line is axis line - svg.append("\r\n"); - // rest are grid lines - for (double i= min; i < max; i+=major) { - x0+=inc; - svg.append("\r\n"); - } - } - // is the X AXIS TITLE i.e. GOES ON THE X AXIS - y0= (!xAxisReversed?canvash-XAXISTITLEOFFSET:y-XAXISTITLEOFFSET); - int titleRot= (linkedtd!=null)?linkedtd.getRotation():0; - svg.append(getAxisTitleSVG((x+w/2), y0, titlefontSVG, titleRot, "yaxistitle")); - } - break; - } - return svg.toString(); - } - + StringBuffer svg = new StringBuffer(); + + // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 + // X Axis/Cats in reversed order means X axis on Top, Y axis labels in reversed order (along with bars) + double x0, x1, y0, y1; + double inc; + int rfX = 1; // reverse factor used to reverse order + position of axes + int rfY = 1; // "" + boolean scaleIsInteger = true; // usual case + double x = chartMetrics.get("x"); + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double canvasw = chartMetrics.get("canvasw"); + double canvash = chartMetrics.get("canvash"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + boolean xAxisReversed = (Boolean) ca.getMetric("xAxisReversed"); + boolean yAxisReversed = (Boolean) ca.getMetric("yAxisReversed"); + double YAXISTITLEOFFSET = (Double) ca.getMetric("YAXISTITLEOFFSET"); + String yPattern = (String) ca.getMetric("yPattern"); + double XAXISLABELOFFSET = (Double) ca.getMetric("XAXISLABELOFFSET"); + double XAXISTITLEOFFSET = (Double) ca.getMetric("XAXISTITLEOFFSET"); + double major = (Double) ca.getMetric("major"); + + if (wType == XAXIS) { + scaleIsInteger = (major == Math.floor(major)); // true if scale is in integer form rather than double (used for formatting label text) + } + switch (wType) { + case XAXIS: + // X Axis TICKS x= rectX, x2= x1, y1= recty+h, y2=y1+5 (major) x1 increments by (#Major Ticks-1)/Width (usually 4) + // x= rectX+17, x2= x1, y1= recty+h y2=y1+2 (minor) x1 increments by (approx 8) + x0 = x; // start at chart x + y0 = y + (!xAxisReversed ? h : 0); // origin at y+h (unless reversed) + rfX = (!xAxisReversed ? 1 : -1); + rfY = (!yAxisReversed ? 1 : -1); + if (major > 0) { + svg.append("\r\n"); + inc = w / ((max - min) / major); // w/scale factor + int scale = 0; + { // figure out if scale has a fractional portion; if so, determine desired scale and keep to that + String s = String.valueOf(major); + int z = s.indexOf("."); + if (z != -1) + scale = s.length() - (z + 1); + } + int k = 0; // axis label index + for (double i = min; i <= max; i += major) {// traverse across bottom (or top, if reversed) of x axis) incrementing x value keeping y value constant + y1 = y0 + 5 * rfX; // Major tick marks + if (showMajorTicks) + svg.append("\r\n"); + // X axis labels= (Values) + java.math.BigDecimal bd = new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); + if (!xAxisReversed) + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); + else + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); + x0 += inc * rfY; + } + svg.append("\r\n"); + } + // AXIS bounding line + y0 = y + (!xAxisReversed ? h : 0); // origin at y+h (unless reversed) + x0 = x; // + (!yAxisReversed?0:ci.w); // start at chart x + svg.append("\r\n"); + // is the Y AXIS TITLE i.e. goes alongside Y AXIS + if (!yAxisReversed) + x0 = YAXISTITLEOFFSET; + else + x0 = x + w + YAXISTITLEOFFSET; + svg.append(getAxisTitleSVG(x0, (y + h / 2), titlefontSVG, 90, "xaxistitle")); + break; + case YAXIS: + rfX = (!xAxisReversed ? 1 : -1); // if reversed, y vals on top (x axis), cats are in reversed order (y axis) + rfY = (!yAxisReversed ? 1 : -1); // if reversed, y vals in reverse order (x axis), cats are on RHS (y axis) + if (categories != null && categories.length > 0) { // should! + inc = h / categories.length; + x0 = x; // + (!yAxisReversed?0:ci.w); // draw y axis tick marks + y axis labels (= categories) + y0 = y + (!xAxisReversed ? h : 0); // starts at bottom, goes up to top unless reversed + int k = 0; // axis label index + for (int i = 0; i < categories.length; i++) { // traverse Y axis, spacing category labels (x is constant, y is segmented) + x1 = x0 - 5 * rfY;// Major tick Marks + if (showMajorTicks) + svg.append("\r\n"); + // Y Axis Labels = categories + y1 = y0 - inc * rfX + inc * .5 * rfX; + if (!yAxisReversed) + svg.append("" + categories[i].toString() + "\r\n"); + else + svg.append("" + categories[i].toString() + "\r\n"); + y0 -= inc * rfX; + } + // show gridlines (top to bottom) + String lineSVG = getLineSVG(AxisLineFormat.ID_MAJOR_GRID); + if (!lineSVG.equals("")) { + y0 = y; + x0 = x; // start at chart x + inc = w / ((max - min) / major); // w/scale factor + // 1st line is axis line + svg.append("\r\n"); + // rest are grid lines + for (double i = min; i < max; i += major) { + x0 += inc; + svg.append("\r\n"); + } + } + // is the X AXIS TITLE i.e. GOES ON THE X AXIS + y0 = (!xAxisReversed ? canvash - XAXISTITLEOFFSET : y - XAXISTITLEOFFSET); + int titleRot = (linkedtd != null) ? linkedtd.getRotation() : 0; + svg.append(getAxisTitleSVG((x + w / 2), y0, titlefontSVG, titleRot, "yaxistitle")); + } + break; + } + return svg.toString(); + } + /** * returns the SVG representation of this Radar Chart Axes -- *
                Radar Chart Axis look like a spider web - *
                Note that these coordinates, like all axis scales, match the coordinates and calculations in Rader.getSVG + *
                Note that these coordinates, like all axis scales, match the coordinates and calculations in Rader.getSVG + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max * @return String SVG */ private String getSVGRADARCHART(ChartAxes ca, String titleFontSVG, String labelFontSVG, java.util.Map chartMetrics, Object[] categories) { - double x= chartMetrics.get("x"); - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - double major= (Double) ca.getMetric("major"); - StringBuffer svg= new StringBuffer(); - if (wType==YAXIS) { - svg.append("\r\n"); - if (categories!=null && categories.length>0) { // shouldn't - major= major*2; // appears the scale calc doesn't follow other charts ... - double n= categories.length; - double centerx= w/2 + x; - double centery= h/2 + y; - double percentage= 1/n; // divide into equal sections - double radius= Math.min(w, h)/2.3; // should take up almost entire w/h of chart - double radiusinc= radius/(max/major); - double lastx= centerx, lasty= centery-radius; // again, start straight up - int k= 0; // axis label index - for (double j= min; j <= max; j+=major) { // each major unit is a concentric line - double angle= 90; // starts straight up - for (int i= 0; i <= n; i++) { // each category is a radial line; <= n so can complete the spider web line path - // get next point on circumference - double x1=centerx + radius*(Math.cos(Math.toRadians(angle))); - double y1=centery - radius*(Math.sin(Math.toRadians(angle))); - svg.append("\r\n"); - if (j==0 && i < n) { // print radial lines & labels at very end of chart/top of each line -- only do for 1st round - svg.append("\r\n"); - double labelx1=centerx + (radius+10)*(Math.cos(Math.toRadians(angle))); - double labely1=centery - (radius+10)*(Math.sin(Math.toRadians(angle))); - svg.append("" + categories[i].toString() + "\r\n"); - } - // next angle - angle-= (percentage*360); - lastx= x1; - lasty= y1; - } - radius-=radiusinc; - } - } - svg.append("\r\n"); - } - return svg.toString(); - } - - - /** - * return the svg necessary to display categories along an x axis - * @param x - * @param y - * @param w - * @param h - * @param inc - * @param labelRot - * @param categories - * @param f - * @param labelfontSVG - * @param yAxisReversed - * @param xAxisReversed - * @param XAXISLABELOFFSET - * @return - */ - private String getCategoriesSVG(double x, double y, double w, double h, double inc, int labelRot, Object[] categories, java.awt.Font f, String labelfontSVG, boolean yAxisReversed, boolean xAxisReversed, double XAXISLABELOFFSET) { - // Category Labels - centered within area on X Axis - StringBuffer svg= new StringBuffer(); - double x0, x1, y0, y1; - int k= labelfontSVG.indexOf("font-size=") + 11; - double fh= Double.parseDouble(labelfontSVG.substring(k, labelfontSVG.indexOf("pt"))); // approximate height of a line of labels - y0= y + (!yAxisReversed?h + XAXISLABELOFFSET/3:-XAXISLABELOFFSET); // draw on bottom edge of axis unless Y axis is reversed - int m= 0; // axis label index - for (int i= 0; i < categories.length; i++) { - if (!xAxisReversed) - x0= x + inc*i + (inc/2); - else // reversed: category labels start from LHS and go to RHS - x0= x + w - inc*i - (inc/2); - if (labelRot!=0) { - double len= StringTool.getApproximateStringWidthLB(f, CellFormatFactory.fromPatternString( null ).format( categories[i] )); - if (labelRot==45) - len= (int) Math.ceil(len*(Math.cos(Math.toRadians(labelRot)))); - int offset= (int) (len/2)+5; - y0= y + (!yAxisReversed?h+offset:-offset); - if (labelRot==45) - x0+=inc/2; - - } - // handle multiple lines in X axis labels - must do "by hand" - String[] s= categories[i].toString().split("\n"); - svg.append(""); - for (int z= 0; z" + s[z] + "\r\n"); - svg.append("\r\n"); - } - return svg.toString(); - } - - /** - * return the svg necessary to display an axis title - * @param x - * @param y - * @param titlefontSVG - * @param titleRot 0, 45 or -90 degrees - * @param scriptTitle either "xaxistitle", "yaxistitle" or "zaxistitle" - * @return - */ - private String getAxisTitleSVG(double x, double y, String titlefontSVG, int titleRot, String scriptTitle) { - StringBuffer svg= new StringBuffer(); - svg.append("\r\n"); - svg.append("" + this.getTitle() + "\r\n"); - svg.append("\r\n"); - return svg.toString(); - } + double x = chartMetrics.get("x"); + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + double major = (Double) ca.getMetric("major"); + StringBuffer svg = new StringBuffer(); + if (wType == YAXIS) { + svg.append("\r\n"); + if (categories != null && categories.length > 0) { // shouldn't + major = major * 2; // appears the scale calc doesn't follow other charts ... + double n = categories.length; + double centerx = w / 2 + x; + double centery = h / 2 + y; + double percentage = 1 / n; // divide into equal sections + double radius = Math.min(w, h) / 2.3; // should take up almost entire w/h of chart + double radiusinc = radius / (max / major); + double lastx = centerx, lasty = centery - radius; // again, start straight up + int k = 0; // axis label index + for (double j = min; j <= max; j += major) { // each major unit is a concentric line + double angle = 90; // starts straight up + for (int i = 0; i <= n; i++) { // each category is a radial line; <= n so can complete the spider web line path + // get next point on circumference + double x1 = centerx + radius * (Math.cos(Math.toRadians(angle))); + double y1 = centery - radius * (Math.sin(Math.toRadians(angle))); + svg.append("\r\n"); + if (j == 0 && i < n) { // print radial lines & labels at very end of chart/top of each line -- only do for 1st round + svg.append("\r\n"); + double labelx1 = centerx + (radius + 10) * (Math.cos(Math.toRadians(angle))); + double labely1 = centery - (radius + 10) * (Math.sin(Math.toRadians(angle))); + svg.append("" + categories[i].toString() + "\r\n"); + } + // next angle + angle -= (percentage * 360); + lastx = x1; + lasty = y1; + } + radius -= radiusinc; + } + } + svg.append("\r\n"); + } + return svg.toString(); + } + + + /** + * return the svg necessary to display categories along an x axis + * + * @param x + * @param y + * @param w + * @param h + * @param inc + * @param labelRot + * @param categories + * @param f + * @param labelfontSVG + * @param yAxisReversed + * @param xAxisReversed + * @param XAXISLABELOFFSET + * @return + */ + private String getCategoriesSVG(double x, double y, double w, double h, double inc, int labelRot, Object[] categories, java.awt.Font f, String labelfontSVG, boolean yAxisReversed, boolean xAxisReversed, double XAXISLABELOFFSET) { + // Category Labels - centered within area on X Axis + StringBuffer svg = new StringBuffer(); + double x0, x1, y0, y1; + int k = labelfontSVG.indexOf("font-size=") + 11; + double fh = Double.parseDouble(labelfontSVG.substring(k, labelfontSVG.indexOf("pt"))); // approximate height of a line of labels + y0 = y + (!yAxisReversed ? h + XAXISLABELOFFSET / 3 : -XAXISLABELOFFSET); // draw on bottom edge of axis unless Y axis is reversed + int m = 0; // axis label index + for (int i = 0; i < categories.length; i++) { + if (!xAxisReversed) + x0 = x + inc * i + (inc / 2); + else // reversed: category labels start from LHS and go to RHS + x0 = x + w - inc * i - (inc / 2); + if (labelRot != 0) { + double len = StringTool.getApproximateStringWidthLB(f, CellFormatFactory.fromPatternString(null).format(categories[i])); + if (labelRot == 45) + len = (int) Math.ceil(len * (Math.cos(Math.toRadians(labelRot)))); + int offset = (int) (len / 2) + 5; + y0 = y + (!yAxisReversed ? h + offset : -offset); + if (labelRot == 45) + x0 += inc / 2; + + } + // handle multiple lines in X axis labels - must do "by hand" + String[] s = categories[i].toString().split("\n"); + svg.append(""); + for (int z = 0; z < s.length; z++) + svg.append("" + s[z] + "\r\n"); + svg.append("\r\n"); + } + return svg.toString(); + } + + /** + * return the svg necessary to display an axis title + * + * @param x + * @param y + * @param titlefontSVG + * @param titleRot 0, 45 or -90 degrees + * @param scriptTitle either "xaxistitle", "yaxistitle" or "zaxistitle" + * @return + */ + private String getAxisTitleSVG(double x, double y, String titlefontSVG, int titleRot, String scriptTitle) { + StringBuffer svg = new StringBuffer(); + svg.append("\r\n"); + svg.append("" + this.getTitle() + "\r\n"); + svg.append("\r\n"); + return svg.toString(); + } } -class Scaling implements OOXMLElement{ - private String logBase, max, min, orientation; - public Scaling() { // no-param constructor, set up common defaults - } - public Scaling(String logBase, String max, String min, String orientation) { - this.logBase= logBase; - this.max= max; - this.min= min; - this.orientation= orientation; - } - public Scaling(Scaling sc) { - this.logBase= sc.logBase; - this.max= sc.max; - this.min= sc.min; - this.orientation= sc.orientation; - } - +class Scaling implements OOXMLElement { + private String logBase, max, min, orientation; + + public Scaling() { // no-param constructor, set up common defaults + } + + public Scaling(String logBase, String max, String min, String orientation) { + this.logBase = logBase; + this.max = max; + this.min = min; + this.orientation = orientation; + } + + public Scaling(Scaling sc) { + this.logBase = sc.logBase; + this.max = sc.max; + this.min = sc.min; + this.orientation = sc.orientation; + } + /** * parse Axis OOXML element (catAx, valAx, serAx or dateAx) - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spPr object + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object */ public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - String logBase= null, max= null, min= null, orientation= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("scaling")) { - } else if (tnm.equals("logBase")) { - logBase= xpp.getAttributeValue(0); - } else if (tnm.equals("max")) { - max= xpp.getAttributeValue(0); - } else if (tnm.equals("min")) { - min= xpp.getAttributeValue(0); - } else if (tnm.equals("orientation")) { - orientation= xpp.getAttributeValue(0); - } - } else if (eventType== XmlPullParser.END_TAG) { - String endTag= xpp.getName(); - if (endTag.equals("scaling")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("scaling.parseOOXML: " + e.toString()); - } - Scaling s= new Scaling(logBase, max, min, orientation); - return s; - } - + String logBase = null, max = null, min = null, orientation = null; + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("scaling")) { + } else if (tnm.equals("logBase")) { + logBase = xpp.getAttributeValue(0); + } else if (tnm.equals("max")) { + max = xpp.getAttributeValue(0); + } else if (tnm.equals("min")) { + min = xpp.getAttributeValue(0); + } else if (tnm.equals("orientation")) { + orientation = xpp.getAttributeValue(0); + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("scaling")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("scaling.parseOOXML: " + e.toString()); + } + Scaling s = new Scaling(logBase, max, min, orientation); + return s; + } + /** * return the specific scaling option + * * @param op * @return */ public String getOption(String op) { - if (op.equals("logBase")) - return logBase; - else if (op.equals("max")) - return max; - else if (op.equals("min")) - return min; - else if (op.equals("orientation")) - return orientation; - return null; - } - - public String getOOXML() { - StringBuffer ooxml= new StringBuffer(); - // sequence: logBase, orientation, max, min - ooxml.append(""); - if (orientation!=null) ooxml.append(""); - if (max!=null) ooxml.append(""); - if (min!=null) ooxml.append(""); - ooxml.append("\r\n"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Scaling(this); - } + if (op.equals("logBase")) + return logBase; + else if (op.equals("max")) + return max; + else if (op.equals("min")) + return min; + else if (op.equals("orientation")) + return orientation; + return null; + } + + public String getOOXML() { + StringBuffer ooxml = new StringBuffer(); + // sequence: logBase, orientation, max, min + ooxml.append(""); + if (orientation != null) ooxml.append(""); + if (max != null) ooxml.append(""); + if (min != null) ooxml.append(""); + ooxml.append("\r\n"); + return ooxml.toString(); + } + + public OOXMLElement cloneElement() { + return new Scaling(this); + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.java b/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.java index 314aa3e..ab47bfd 100644 --- a/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.java +++ b/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -28,29 +28,31 @@ /** * AxisLineFormat: Defines a Line that spans an Axis (0x1021) - * The AxisLineFormat record specifies which part of the axis is specified by the + * The AxisLineFormat record specifies which part of the axis is specified by the * LineFormat record that follows. *
                - * 4 id 2 Axis Line identifier: - 0= the axis line itself, - 1= major grid line, - 2= minor grid line, - 3= walls or floor - * if 3, MUST be preceded by an Axis record with the wType set to: - 0x0000 The walls of a 3-D chart - 0x0001 The floor of a 3-D chart + * 4 id 2 Axis Line identifier: + * 0= the axis line itself, + * 1= major grid line, + * 2= minor grid line, + * 3= walls or floor + * if 3, MUST be preceded by an Axis record with the wType set to: + * 0x0000 The walls of a 3-D chart + * 0x0001 The floor of a 3-D chart */ public class AxisLineFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5243346695500373630L; - private short id= 0; - public void init() { - super.init(); - id= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - // id MUST be greater than the id field values in preceding AxisLine records in the current axis + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5243346695500373630L; + private short id = 0; + + public void init() { + super.init(); + id = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + } + + // id MUST be greater than the id field values in preceding AxisLine records in the current axis public static XLSRecord getPrototype() { AxisLineFormat a = new AxisLineFormat(); a.setOpcode(AXISLINEFORMAT); @@ -58,19 +60,26 @@ public static XLSRecord getPrototype() { a.init(); return a; } - private byte[] PROTOTYPE_BYTES = new byte[] {0,0}; - public static final int ID_AXIS_LINE= 0; - public static final int ID_MAJOR_GRID= 1; - public static final int ID_MINOR_GRID= 2; - public static final int ID_WALLORFLOOR= 3; - public short getId() { return id; } - public void setId(int id) { - this.id= (short) id; + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; + public static final int ID_AXIS_LINE = 0; + public static final int ID_MAJOR_GRID = 1; + public static final int ID_MINOR_GRID = 2; + public static final int ID_WALLORFLOOR = 3; + + public short getId() { + return id; + } + + public void setId(int id) { + this.id = (short) id; byte[] b = ByteTools.shortToLEBytes(this.id); this.getData()[0] = b[0]; this.getData()[1] = b[1]; } - - public String toString() { return "AxisLineFormat: " +((id==0)?"Axis":((id==1)?"Major":((id==2)?"Minor":((id==3)?"Wall or Floor":"Unknown")))); } - + + public String toString() { + return "AxisLineFormat: " + ((id == 0) ? "Axis" : ((id == 1) ? "Major" : ((id == 2) ? "Minor" : ((id == 3) ? "Wall or Floor" : "Unknown")))); + } + } diff --git a/src/main/java/io/starter/formats/XLS/charts/AxisParent.java b/src/main/java/io/starter/formats/XLS/charts/AxisParent.java index 6d3abe4..7162cf9 100644 --- a/src/main/java/io/starter/formats/XLS/charts/AxisParent.java +++ b/src/main/java/io/starter/formats/XLS/charts/AxisParent.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,335 +22,339 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; - -import io.starter.OpenXLS.ChartHandle; -import io.starter.OpenXLS.FormatHandle; import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -/** +/** * AxisParent: Axis Size and Location (0x1014) - + *

                * This record specifies properties of an axis group and the beginning of a collection of records as defined by the chart sheet substream. - * - * + *

                + *

                * The Axis parent record stores most of the actual chart information, what type of chart, x and y labels, etc. - * - * 4 iax 2 axis index (0= main, 1= secondary) This field MUST be set to zero when it is in the first AxisParent record in the chart sheet substream, - * This field MUST be set to 1 when it is in the second AxisParent record in the chart sheet substream. + *

                + * 4 iax 2 axis index (0= main, 1= secondary) This field MUST be set to zero when it is in the first AxisParent record in the chart sheet substream, + * This field MUST be set to 1 when it is in the second AxisParent record in the chart sheet substream. * 16 unused (16 bytes): Undefined and MUST be ignored. - * - * + *

                + *

                * this doesn't appear correct given ms doc - * 6 x 4 + * 6 x 4 * 10 y 4 * 14 dx 4 len of x axis - * 18 dy 4 len of y axis - * - * + * 18 dy 4 len of y axis */ public class AxisParent extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2247258217367570732L; - private short iax= 0; - private int x= 0, y= 0, dx= 0, dy= 0; - public void init() { - super.init(); - iax = ByteTools.readShort(this.getData()[0],this.getData()[1]); - x= ByteTools.readInt(this.getBytesAt(2, 4)); - y= ByteTools.readInt(this.getBytesAt(6, 4)); - dx= ByteTools.readInt(this.getBytesAt(10, 4)); - dy= ByteTools.readInt(this.getBytesAt(14, 4)); - - } - + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2247258217367570732L; + private short iax = 0; + private int x = 0, y = 0, dx = 0, dy = 0; + + public void init() { + super.init(); + iax = ByteTools.readShort(this.getData()[0], this.getData()[1]); + x = ByteTools.readInt(this.getBytesAt(2, 4)); + y = ByteTools.readInt(this.getBytesAt(6, 4)); + dx = ByteTools.readInt(this.getBytesAt(10, 4)); + dy = ByteTools.readInt(this.getBytesAt(14, 4)); + + } + /** * default version, returns the 1sst chart format record *
                if there are more than 1 chart type in the chart - * there will be multiple chart format records + * there will be multiple chart format records + * * @return */ protected ChartFormat getChartFormat() { - return getChartFormat(0, false); + return getChartFormat(0, false); } - + /** * get the chart format collection - * + * * @return */ protected ChartFormat getChartFormat(int nChart, boolean addNew) { - for (int i=0;i0 and <=9) - * @return new ChartFormat + * + * @param nChart overlay chart number (>0 and <=9) + * @return new ChartFormat */ protected ChartFormat createNewChart(int nChart) { // muliple charts, create one - if (nChart>0 && nChart <=9) { - ChartFormat cf= (ChartFormat) ChartFormat.getPrototype(); - cf.setParentChart(this.getParentChart()); - Bar b= (Bar) Bar.getPrototype(); - cf.chartArr.add(b); // add a dummy chart object - will be replaced later - ChartFormatLink cfl= (ChartFormatLink) ChartFormatLink.getPrototype(); + if (nChart > 0 && nChart <= 9) { + ChartFormat cf = (ChartFormat) ChartFormat.getPrototype(); + cf.setParentChart(this.getParentChart()); + Bar b = (Bar) Bar.getPrototype(); + cf.chartArr.add(b); // add a dummy chart object - will be replaced later + ChartFormatLink cfl = (ChartFormatLink) ChartFormatLink.getPrototype(); cf.chartArr.add(cfl); - SeriesList sl= new SeriesList(); + SeriesList sl = new SeriesList(); sl.setOpcode(SERIESLIST); sl.setParentChart(this.getParentChart()); cf.chartArr.add(sl); - cf.setDrawingOrder(nChart); - this.chartArr.add(cf); // add chartformat to chart array of axis parent - return cf; + cf.setDrawingOrder(nChart); + this.chartArr.add(cf); // add chartformat to chart array of axis parent + return cf; } return null; } - + /** * remove axis records for pie-type charts ... */ public void removeAxes() { - if (chartArr.get(1) instanceof Axis) - chartArr.remove(1); // remove Axis - if (chartArr.get(1) instanceof Axis) - chartArr.remove(1); // remove Axis - if (chartArr.get(1) instanceof TextDisp) - chartArr.remove(1); // remove Text for Axis - if (chartArr.get(1) instanceof TextDisp) - chartArr.remove(1); // remove Text for Axis - if (chartArr.get(1) instanceof PlotArea) - chartArr.remove(1); // remove - if (chartArr.get(1) instanceof Frame) - chartArr.remove(1); // remove Frame - // all should be left is pos + chartFormat + if (chartArr.get(1) instanceof Axis) + chartArr.remove(1); // remove Axis + if (chartArr.get(1) instanceof Axis) + chartArr.remove(1); // remove Axis + if (chartArr.get(1) instanceof TextDisp) + chartArr.remove(1); // remove Text for Axis + if (chartArr.get(1) instanceof TextDisp) + chartArr.remove(1); // remove Text for Axis + if (chartArr.get(1) instanceof PlotArea) + chartArr.remove(1); // remove + if (chartArr.get(1) instanceof Frame) + chartArr.remove(1); // remove Frame + // all should be left is pos + chartFormat } - - /** - * remove the desired axis + associated records - * @param axisType - */ + + /** + * remove the desired axis + associated records + * + * @param axisType + */ public void removeAxis(int axisType) { - // Remove axis - for (int i= 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec)chartArr.get(i); - if (b.getOpcode()==AXIS) { - if (((Axis)b).getAxis()==axisType) { - chartArr.remove(i); - break; - } + // Remove axis + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b.getOpcode() == AXIS) { + if (((Axis) b).getAxis() == axisType) { + chartArr.remove(i); + break; + } } - } - int tdType= TextDisp.convertType(axisType); - // Remove TextDisp assoc with Axis - for (int i=0;i. @@ -25,132 +25,151 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -/** - * +/** * Bar: Chart Group is a Bar or Column Chart Group (0x1017) - * NOTE: Bar is also base type for Pyramid, Cone and Cylinder charts; - * actual chart type is determined also by bar shape - * see ChartFormat.getChartType for more information - * - * - * 4 pcOverlap 2 Space between bars (default= 0) - * values: -100 to -1 Size of the separation between data points - 0 No overlap. - 1 to 100 Size of the overlap between data points + * NOTE: Bar is also base type for Pyramid, Cone and Cylinder charts; + * actual chart type is determined also by bar shape + * see ChartFormat.getChartType for more information + *

                + *

                + * 4 pcOverlap 2 Space between bars (default= 0) + * values: -100 to -1 Size of the separation between data points + * 0 No overlap. + * 1 to 100 Size of the overlap between data points * 6 pcGap 2 Space between categories (%) (default=50%) - * An unsigned integer that specifies the width of the gap between the categories and the left and right edges of the plot area - * as a percentage of the data point width divided by 2. It also specifies the width of the gap between adjacent categories - * as a percentage of the data point width. MUST be less than or equal to 500. + * An unsigned integer that specifies the width of the gap between the categories and the left and right edges of the plot area + * as a percentage of the data point width divided by 2. It also specifies the width of the gap between adjacent categories + * as a percentage of the data point width. MUST be less than or equal to 500. * 8 grbit 2 - * + *

                * grbit: - * - * 0 0 0x1 fTranspose 1= horizontal bars (=bar chart) 0= vertical bars (= column chart) - * 1 0x2 fStacked Stack the displayed values - * 2 0x4 f100 Each category is displayed as a percentage - * 3 0x8 fHasShadow 1= this bar has a shadow + *

                + * 0 0 0x1 fTranspose 1= horizontal bars (=bar chart) 0= vertical bars (= column chart) + * 1 0x2 fStacked Stack the displayed values + * 2 0x4 f100 Each category is displayed as a percentage + * 3 0x8 fHasShadow 1= this bar has a shadow */ public class Bar extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8917510368688674273L; - private short grbit = 0; - protected boolean fStacked= false, f100= false, fHasShadow= false; - protected short pcOverlap= 0; - protected short pcGap= 50; - + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8917510368688674273L; + private short grbit = 0; + protected boolean fStacked = false, f100 = false, fHasShadow = false; + protected short pcOverlap = 0; + protected short pcGap = 50; + public void init() { - super.init(); - pcOverlap= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - // should be 50 default, but seems to be 150 ?????? - pcGap= ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - // pcGap: 0x0096 specifies that the width of the gap between adjacent categories is 150% of the data point width. It also specifies that the width of the gap between the categories (3) and the left and right edges of the plot area is 75% of the data point width. + super.init(); + pcOverlap = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + // should be 50 default, but seems to be 150 ?????? + pcGap = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + // pcGap: 0x0096 specifies that the width of the gap between adjacent categories is 150% of the data point width. It also specifies that the width of the gap between the categories (3) and the left and right edges of the plot area is 75% of the data point width. grbit = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - if ((grbit & 0x1) == 0x1){ - chartType = BARCHART; - }else{ - chartType = COLCHART; - } - fStacked= ((grbit & 0x2)==0x2); - f100= ((grbit & 0x4)==0x4); - fHasShadow= ((grbit & 0x8)==0x8); - } - + if ((grbit & 0x1) == 0x1) { + chartType = BARCHART; + } else { + chartType = COLCHART; + } + fStacked = ((grbit & 0x2) == 0x2); + f100 = ((grbit & 0x4) == 0x4); + fHasShadow = ((grbit & 0x8) == 0x8); + } + // 20070716 KSC: get/set methods for format options - public boolean isStacked() { return fStacked; } - public boolean is100Percent() { return f100; } - public boolean hasShadow() { return fHasShadow; } - public int getGap() { return pcGap; } - public int getOverlap() { return pcOverlap; } - + public boolean isStacked() { + return fStacked; + } + + public boolean is100Percent() { + return f100; + } + + public boolean hasShadow() { + return fHasShadow; + } + + public int getGap() { + return pcGap; + } + + public int getOverlap() { + return pcOverlap; + } + /** * sets this bar/col chart to have stacked series: series are drawn next to each other along the value axis + * * @param bIsClustered */ public void setIsStacked(boolean bIsStacked) { - fStacked= bIsStacked; - grbit= ByteTools.updateGrBit(grbit, fStacked, 1); - if (bIsStacked) { - pcOverlap= -100; - pcGap= 150; - } - updateRecord(); + fStacked = bIsStacked; + grbit = ByteTools.updateGrBit(grbit, fStacked, 1); + if (bIsStacked) { + pcOverlap = -100; + pcGap = 150; + } + updateRecord(); } + public void setIs100Percent(boolean bOn) { - f100= bOn; - grbit= ByteTools.updateGrBit(grbit, f100, 2); - if (bOn) { - pcOverlap= -100; - pcGap= 150; - } - updateRecord(); + f100 = bOn; + grbit = ByteTools.updateGrBit(grbit, f100, 2); + if (bOn) { + pcOverlap = -100; + pcGap = 150; + } + updateRecord(); } + public void setHasShadow(boolean bHasShadow) { - fHasShadow= bHasShadow; - grbit= ByteTools.updateGrBit(grbit, fHasShadow, 3); - updateRecord(); + fHasShadow = bHasShadow; + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 3); + updateRecord(); } - + /** * sets this bar/col chart to have clustered series: series are drawn next to each other along the category axis + * * @param bIsClustered */ public void setIsClustered() { - setIsStacked(false); - setIs100Percent(false); + setIsStacked(false); + setIs100Percent(false); } /** * sets the Space between bars (default= 0) + * * @param overlap */ public void setpcOverlap(int overlap) { - pcOverlap= (short) overlap; - updateRecord(); + pcOverlap = (short) overlap; + updateRecord(); } - + /** * sets the Space between categories (%) (default=50%) + * * @param gap */ public void setpcGap(int gap) { - pcGap= (short) gap; - updateRecord(); + pcGap = (short) gap; + updateRecord(); } - + public void setAsBarChart() { - grbit = ByteTools.updateGrBit(grbit, true, 0); // set 0'th bit - chartType= ChartConstants.BARCHART; + grbit = ByteTools.updateGrBit(grbit, true, 0); // set 0'th bit + chartType = ChartConstants.BARCHART; this.updateRecord(); } - + public void setAsColumnChart() { - grbit = ByteTools.updateGrBit(grbit, false, 0); // clear 0'th bit - chartType= ChartConstants.COLCHART; + grbit = ByteTools.updateGrBit(grbit, false, 0); // clear 0'th bit + chartType = ChartConstants.COLCHART; this.updateRecord(); } - + private void updateRecord() { byte[] b = ByteTools.shortToLEBytes(pcOverlap); this.getData()[0] = b[0]; @@ -162,7 +181,7 @@ private void updateRecord() { this.getData()[4] = b[0]; this.getData()[5] = b[1]; } - + public static XLSRecord getPrototype() { Bar b = new Bar(); b.setOpcode(BAR); @@ -171,41 +190,43 @@ public static XLSRecord getPrototype() { b.setAsColumnChart(); return b; } - - private byte[] PROTOTYPE_BYTES = new byte[] {0, 0, -106, 0, 0, 0}; + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, -106, 0, 0, 0}; + /** - * Set specific options + * Set specific options */ public boolean setChartOption(String op, String val) { - boolean bHandled= false; - if (op.equalsIgnoreCase("Stacked")) { - setIsStacked(true); - bHandled= true; - } else if (op.equalsIgnoreCase("PercentageDisplay")) { - setIs100Percent(true); - bHandled= true; - } else if (op.equalsIgnoreCase("Shadow")) { - setHasShadow(true); - bHandled= true; - } else if (op.equalsIgnoreCase("Overlap")) { - setpcOverlap(Integer.parseInt(val)); - bHandled= true; - } else if (op.equalsIgnoreCase("Gap")) { - setpcGap(Integer.parseInt(val)); - bHandled= true; - } - return bHandled; + boolean bHandled = false; + if (op.equalsIgnoreCase("Stacked")) { + setIsStacked(true); + bHandled = true; + } else if (op.equalsIgnoreCase("PercentageDisplay")) { + setIs100Percent(true); + bHandled = true; + } else if (op.equalsIgnoreCase("Shadow")) { + setHasShadow(true); + bHandled = true; + } else if (op.equalsIgnoreCase("Overlap")) { + setpcOverlap(Integer.parseInt(val)); + bHandled = true; + } else if (op.equalsIgnoreCase("Gap")) { + setpcGap(Integer.parseInt(val)); + bHandled = true; + } + return bHandled; } + /** * look up bar-specific chart options such as "Gap" or "Overlap" setting */ public String getChartOption(String op) { - if (op.equals("Gap")) { // Bar - return String.valueOf(this.getGap()); - } else if (op.equals("Overlap")) { // Bar + if (op.equals("Gap")) { // Bar + return String.valueOf(this.getGap()); + } else if (op.equals("Overlap")) { // Bar // return String.valueOf(Math.abs(this.getOverlap())); // KSC: TESTING: OOXML apparently needs +100 pcOverlap NOT -100 ... WHY and TRUE FOR ALL CASES????? - return String.valueOf(this.getOverlap()); // KSC: TESTING: OOXML apparently needs +100 pcOverlap NOT -100 ... WHY and TRUE FOR ALL CASES????? - } else - return super.getChartOption(op); + return String.valueOf(this.getOverlap()); // KSC: TESTING: OOXML apparently needs +100 pcOverlap NOT -100 ... WHY and TRUE FOR ALL CASES????? + } else + return super.getChartOption(op); } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.java b/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.java index 0e0cbae..fd2bd7c 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,72 +22,72 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import org.json.JSONException; -import org.json.JSONObject; - -import io.starter.OpenXLS.ChartSeriesHandle; -import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; public class Bar3DChart extends BarChart { - public Bar3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - bar= (Bar) charttype; - } - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); + public Bar3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + bar = (Bar) charttype; + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append(""); + cooxml.append("\r\n"); + // vary colors??? - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); cooxml.append("\r\n"); - // vary colors??? + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - - if (!this.getChartOption("Gap").equals("150")) cooxml.append(""); // default= 0 - int gapdepth= this.getGapDepth(); - if (gapdepth!=0) - cooxml.append(""); - cooxml.append(""); + if (!this.getChartOption("Gap").equals("150")) + cooxml.append(""); // default= 0 + int gapdepth = this.getGapDepth(); + if (gapdepth != 0) + cooxml.append(""); + cooxml.append(""); - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - if (this.getParentChart().getAxes().hasAxis(ZAXIS)) { - cooxml.append(""); cooxml.append("\r\n"); - } else {// KSC: appears to be necessary but very unclear as to why - cooxml.append(""); cooxml.append("\r\n"); - } + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + if (this.getParentChart().getAxes().hasAxis(ZAXIS)) { + cooxml.append(""); + cooxml.append("\r\n"); + } else {// KSC: appears to be necessary but very unclear as to why + cooxml.append(""); + cooxml.append("\r\n"); + } - cooxml.append(""); cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); - return cooxml; - } + return cooxml; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/BarChart.java b/src/main/java/io/starter/formats/XLS/charts/BarChart.java index b974e69..e9825a6 100644 --- a/src/main/java/io/starter/formats/XLS/charts/BarChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/BarChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,85 +22,88 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Stack; - -import org.json.JSONException; -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.ChartSeriesHandle; import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.Marker; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; public class BarChart extends ChartType { - protected Bar bar= null; + protected Bar bar = null; - public BarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - bar= (Bar) charttype; - defaultShape= SHAPEDEFAULT; - } - /** - * set the default shape for all bars in this chart - * @param shape - */ - public void setDefaultShape(int shape) { - defaultShape= shape; - } - /** - * return the default shape for all bars in this chart - * @return - */ - public int getDefaultShape() { return defaultShape; } + public BarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + bar = (Bar) charttype; + defaultShape = SHAPEDEFAULT; + } + + /** + * set the default shape for all bars in this chart + * + * @param shape + */ + public void setDefaultShape(int shape) { + defaultShape = shape; + } + + /** + * return the default shape for all bars in this chart + * + * @return + */ + public int getDefaultShape() { + return defaultShape; + } /** * @return truth of "Chart is Clustered" (Bar/Col only) */ - public boolean isClustered() { - return (/*cf.isClustered() || */(!isStacked() && !is100PercentStacked())); - } - - /** - * returns the bar shape for a column or bar type chart - * can be one of: - *
                SHAPECOLUMN default - *
                SHAPECONEd - *
                SHAPECONETOMAX - *
                SHAPECYLINDER - *
                SHAPEPYRAMID - *
                SHAPEPYRAMIDTOMAX - * @return int bar shape - */ - public int getBarShape() { - return cf.getBarShape(); - } - + public boolean isClustered() { + return (/*cf.isClustered() || */(!isStacked() && !is100PercentStacked())); + } - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - if (bar.fStacked) - sb.append(" Stacked=\"true\""); - if (bar.f100) - sb.append(" PercentageDisplay=\"true\""); - if (bar.fHasShadow) - sb.append(" Shadow=\"true\""); - if (bar.pcOverlap!=0) - sb.append(" Overlap=\"" + bar.pcOverlap + "\""); - if (bar.pcGap!=50 && bar.pcGap!=150) - sb.append(" Gap=\"" + bar.pcGap+ "\""); - return sb.toString(); - } - - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON= new JSONObject(); - // Dojo: Regular, Stacked, Clustered + /** + * returns the bar shape for a column or bar type chart + * can be one of: + *
                SHAPECOLUMN default + *
                SHAPECONEd + *
                SHAPECONETOMAX + *
                SHAPECYLINDER + *
                SHAPEPYRAMID + *
                SHAPEPYRAMIDTOMAX + * + * @return int bar shape + */ + public int getBarShape() { + return cf.getBarShape(); + } + + + /** + * @return String XML representation of this chart-type's options + */ + public String getOptionsXML() { + StringBuffer sb = new StringBuffer(); + if (bar.fStacked) + sb.append(" Stacked=\"true\""); + if (bar.f100) + sb.append(" PercentageDisplay=\"true\""); + if (bar.fHasShadow) + sb.append(" Shadow=\"true\""); + if (bar.pcOverlap != 0) + sb.append(" Overlap=\"" + bar.pcOverlap + "\""); + if (bar.pcGap != 50 && bar.pcGap != 150) + sb.append(" Gap=\"" + bar.pcGap + "\""); + return sb.toString(); + } + + public JSONObject getTypeJSON() throws JSONException { + JSONObject typeJSON = new JSONObject(); + // Dojo: Regular, Stacked, Clustered /* String dojoType; int shape=ChartConstants.SHAPECOLUMN; //controls Data Legends, % Distance of sections, Line Format, Area Format, Bar Shapes ... @@ -133,14 +136,16 @@ else if (cf.isClustered()) dojoType="StackedColumns"; } typeJSON.put("type", dojoType); -*/ return typeJSON; - } - /** - * replaces generic getJSON with Bar/Column specifics (necessary for stacked-type charts) - */ - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) - throws JSONException { - JSONObject chartObjectJSON= new JSONObject(); +*/ + return typeJSON; + } + + /** + * replaces generic getJSON with Bar/Column specifics (necessary for stacked-type charts) + */ + public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) + throws JSONException { + JSONObject chartObjectJSON = new JSONObject(); /* // 20080428 KSC: get stacked state boolean bIsStacked= (cf.isThreeD() && !cf.isClustered()); @@ -195,119 +200,130 @@ public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double minMax[0]= new Double(yMin); minMax[1]= new Double(yMax); minMax[2]= new Double(nSeries); -*/ return chartObjectJSON; - } - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) +*/ + return chartObjectJSON; + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double x= chartMetrics.get("x"); - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - if (series.size()==0) { - Logger.logErr("Bar.getSVG: error in series"); - return ""; - } - StringBuffer svg= new StringBuffer(); - int shape=cf.getBarShape(); - if (shape==ChartConstants.SHAPECONE || - shape==ChartConstants.SHAPECYLINDER || - shape==ChartConstants.SHAPEPYRAMID) - ; // TODO: SOMETHING!!! Interpret Shape for Pyramid, etc. charts + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double x = chartMetrics.get("x"); + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + if (series.size() == 0) { + Logger.logErr("Bar.getSVG: error in series"); + return ""; + } + StringBuffer svg = new StringBuffer(); + int shape = cf.getBarShape(); + if (shape == ChartConstants.SHAPECONE || + shape == ChartConstants.SHAPECYLINDER || + shape == ChartConstants.SHAPEPYRAMID) + ; // TODO: SOMETHING!!! Interpret Shape for Pyramid, etc. charts + - - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - int n= series.size(); - svg.append("\r\n"); + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + int n = series.size(); + svg.append("\r\n"); - double barw= 0, yfactor= 0; // - barw= h/((categories.length+1.0)*(n+1)); // bar width= height/total number of bars+separators - if (max!=0) - yfactor= w/max; // w/yMax= scale unit - - int rf= 1; //(!yAxisReversed?1:-1); - double y0= h+y-barw*0.5; //start at bottom and work up (unless reversed) - // for each series - for (int i= 0; i < n; i++) { // each series group i.e. each bar with a different color - svg.append("\r\n"); - double[] curseries= (double[])series.get(i); - String[] curranges= (String[])s.getSeriesRanges().get(i); - for (int j= 0; j < curseries.length; j++) { // each category (group of series) + double barw = 0, yfactor = 0; // + barw = h / ((categories.length + 1.0) * (n + 1)); // bar width= height/total number of bars+separators + if (max != 0) + yfactor = w / max; // w/yMax= scale unit + + int rf = 1; //(!yAxisReversed?1:-1); + double y0 = h + y - barw * 0.5; //start at bottom and work up (unless reversed) + // for each series + for (int i = 0; i < n; i++) { // each series group i.e. each bar with a different color + svg.append("\r\n"); + double[] curseries = (double[]) series.get(i); + String[] curranges = (String[]) s.getSeriesRanges().get(i); + for (int j = 0; j < curseries.length; j++) { // each category (group of series) // double y= y0 - barw*(i+1)*rf - (j*n*barw*1.5)*rf; // y goes from 1 series to next, corresponding to bar/column color - double yy= y0 - (barw*(n+1)*1.1*(j)) /* each series (bar group) */ - - (barw*(i+1)) /* each bar in the series group */ - - (barw*1.5)*rf /* start */; - double ww= yfactor*curseries[j]; // width of bar -- measure of series - svg.append(""); - String l= getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); - if (l!=null) { - svg.append("" + l + "\r\n"); - } - } - svg.append("\r\n"); // each series group - } - svg.append("\r\n"); - return svg.toString(); - } - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); + double yy = y0 - (barw * (n + 1) * 1.1 * (j)) /* each series (bar group) */ + - (barw * (i + 1)) /* each bar in the series group */ + - (barw * 1.5) * rf /* start */; + double ww = yfactor * curseries[j]; // width of bar -- measure of series + svg.append(""); + String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); + if (l != null) { + svg.append("" + l + "\r\n"); + } + } + svg.append("\r\n"); // each series group + } + svg.append("\r\n"); + return svg.toString(); + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); cooxml.append("\r\n"); - // vary colors??? + if (this.is100PercentStacked()) + cooxml.append("percentStacked"); + else if (this.isStacked()) + cooxml.append("stacked"); + else if (this.isClustered()) + cooxml.append("clustered"); + else + cooxml.append("standard"); + cooxml.append("\"/>"); + cooxml.append("\r\n"); + // vary colors??? - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - if (!this.getChartOption("Gap").equals("150")) cooxml.append(""); // default= 0 - if (!this.getChartOption("Overlap").equals("0")) - cooxml.append(""); // default= 0 - // Series Lines - ChartLine cl= cf.getChartLinesRec(); - if (cl!=null) - cooxml.append(cl.getOOXML()); + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + if (!this.getChartOption("Gap").equals("150")) + cooxml.append(""); // default= 0 + if (!this.getChartOption("Overlap").equals("0")) + cooxml.append(""); // default= 0 + // Series Lines + ChartLine cl = cf.getChartLinesRec(); + if (cl != null) + cooxml.append(cl.getOOXML()); - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); - return cooxml; - } + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Begin.java b/src/main/java/io/starter/formats/XLS/charts/Begin.java index ffae1fa..1a55522 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Begin.java +++ b/src/main/java/io/starter/formats/XLS/charts/Begin.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,35 +23,32 @@ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.Blank; import io.starter.formats.XLS.XLSRecord; /** - * * Begin: Begin identifier (0x1033) - * + *

                * Begin is an identifier record for the chart record type. There is no data to a begin record, * and every begin record must have a corrosponding end record - * */ -public class Begin extends GenericChartObject implements ChartObject{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2732787714371128511L; - - public void init() { - super.init(); - } - - protected static XLSRecord getPrototype(){ +public class Begin extends GenericChartObject implements ChartObject { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2732787714371128511L; + + public void init() { + super.init(); + } + + protected static XLSRecord getPrototype() { Begin bl = new Begin(); bl.setOpcode(BEGIN); - bl.setData(new byte[] {}); + bl.setData(new byte[]{}); return bl; } - - + + } diff --git a/src/main/java/io/starter/formats/XLS/charts/Boppop.java b/src/main/java/io/starter/formats/XLS/charts/Boppop.java index 077fc82..cf251ef 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Boppop.java +++ b/src/main/java/io/starter/formats/XLS/charts/Boppop.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,84 +23,85 @@ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.*; +import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * Boppop: Bar of Pie/Pie of Pie chart options(0x1061) - * - * - * - - - pst (1 byte): An unsigned integer that specifies whether this chart group is a bar of pie chart group or a pie of pie chart group. MUST be a value from the following table: - 1= pie of pie 2= bar of pie - fAutoSplit (1 byte): A Boolean that specifies whether the split point of the chart group is determined automatically. - If the value is 1, when a bar of pie chart group or pie of pie chart group is initially created the data points from the primary pie are selected - and inserted into the secondary bar/pie automatically. - split (2 bytes): An unsigned integer that specifies what determines the split between the primary pie and the secondary bar/pie. - MUST be ignored if fAutoSplit is set to 1. MUST be a value from the following table: - - Value Type of split Meaning - 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. - 0x0001 Value The data is split based on a threshold value as specified by numSplitValue. - 0x0002 Percent The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. - 0x0003 Custom The data is split as arranged by the user. Custom split is specified in a following BopPopCustom record. - - iSplitPos (2 bytes): A signed integer that specifies how many data points are contained in the secondary bar/pie. - Data points are contained in the secondary bar/pie starting from the end of the series. - For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. - MUST be a value greater than or equal to 0 and less than or equal to 32000. - If the value is more than the number of data points in the series, the entire series will be in the secondary bar/pie, except for the first data point. - If split is not set to 0x0000 or fAutoSplit is set to 1, this value MUST be ignored. - - pcSplitPercent (2 bytes): A signed integer that specifies the percentage below which each data point is contained in the secondary bar/pie as opposed to the primary pie. - The percentage value of a data point is calculated using the following formula: - (value of the data point x 100) / sum of all data points in the series - If split is not set to 0x0002 or if fAutoSplit is set to 1, this value MUST be ignored - - pcPie2Size (2 bytes): A signed integer that specifies the size of the secondary bar/pie as a percentage of the size of the primary pie. - MUST be a value greater than or equal to 5 and less than or equal to 200. - - pcGap (2 bytes): A signed integer that specifies the distance between the primary pie and the secondary bar/pie. - The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. - MUST be a value greater than or equal to 0 and less than or equal to 500, - where 0 is 0% of the average width of the primary pie and the secondary bar/pie, and 500 is 250% of the average width of the primary pie and the secondary bar/pie. + *

                + *

                + *

                + *

                + *

                + * pst (1 byte): An unsigned integer that specifies whether this chart group is a bar of pie chart group or a pie of pie chart group. MUST be a value from the following table: + * 1= pie of pie 2= bar of pie + * fAutoSplit (1 byte): A Boolean that specifies whether the split point of the chart group is determined automatically. + * If the value is 1, when a bar of pie chart group or pie of pie chart group is initially created the data points from the primary pie are selected + * and inserted into the secondary bar/pie automatically. + * split (2 bytes): An unsigned integer that specifies what determines the split between the primary pie and the secondary bar/pie. + * MUST be ignored if fAutoSplit is set to 1. MUST be a value from the following table: + *

                + * Value Type of split Meaning + * 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. + * 0x0001 Value The data is split based on a threshold value as specified by numSplitValue. + * 0x0002 Percent The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. + * 0x0003 Custom The data is split as arranged by the user. Custom split is specified in a following BopPopCustom record. + *

                + * iSplitPos (2 bytes): A signed integer that specifies how many data points are contained in the secondary bar/pie. + * Data points are contained in the secondary bar/pie starting from the end of the series. + * For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. + * MUST be a value greater than or equal to 0 and less than or equal to 32000. + * If the value is more than the number of data points in the series, the entire series will be in the secondary bar/pie, except for the first data point. + * If split is not set to 0x0000 or fAutoSplit is set to 1, this value MUST be ignored. + *

                + * pcSplitPercent (2 bytes): A signed integer that specifies the percentage below which each data point is contained in the secondary bar/pie as opposed to the primary pie. + * The percentage value of a data point is calculated using the following formula: + * (value of the data point x 100) / sum of all data points in the series + * If split is not set to 0x0002 or if fAutoSplit is set to 1, this value MUST be ignored + *

                + * pcPie2Size (2 bytes): A signed integer that specifies the size of the secondary bar/pie as a percentage of the size of the primary pie. + * MUST be a value greater than or equal to 5 and less than or equal to 200. + *

                + * pcGap (2 bytes): A signed integer that specifies the distance between the primary pie and the secondary bar/pie. + * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. + * MUST be a value greater than or equal to 0 and less than or equal to 500, + * where 0 is 0% of the average width of the primary pie and the secondary bar/pie, and 500 is 250% of the average width of the primary pie and the secondary bar/pie. + *

                + * numSplitValue (8 bytes): An Xnum value that specifies the split when the split field is set to 0x0001 + * . The value of this field specifies the threshold that selects which data points of the primary pie move to the secondary bar/pie. + * The secondary bar/pie contains any data points with a value less than the value of this field. If split is not set to 0x0001 or if fAutoSplit is set to 1, this value MUST be ignored. + *

                + * A - fHasShadow (1 bit): A bit that specifies whether one or more data points in the chart group have shadows. + *

                + * reserved (15 bits): MUST be zero, and MUST be ignored. + */ +public class Boppop extends GenericChartObject implements ChartObject { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8071801452993935943L; + boolean fAutoSplit, fHasShadow; + short split, iSplitPos, pcSplitPercent, pcPieSize, pcGap; + float numSplitValue; + byte pst; - numSplitValue (8 bytes): An Xnum value that specifies the split when the split field is set to 0x0001 - . The value of this field specifies the threshold that selects which data points of the primary pie move to the secondary bar/pie. - The secondary bar/pie contains any data points with a value less than the value of this field. If split is not set to 0x0001 or if fAutoSplit is set to 1, this value MUST be ignored. + public void init() { + super.init(); + byte[] data = this.getData(); + pst = data[0]; + fAutoSplit = (data[1] == 1); + split = ByteTools.readShort(data[2], data[3]); + iSplitPos = ByteTools.readShort(data[4], data[5]); + pcSplitPercent = ByteTools.readShort(data[6], data[7]); + pcPieSize = ByteTools.readShort(data[8], data[9]); + pcGap = ByteTools.readShort(data[10], data[11]); + numSplitValue = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(12, 8)); + fHasShadow = (data[20] & 1) == 1; + chartType = OFPIECHART; + } - A - fHasShadow (1 bit): A bit that specifies whether one or more data points in the chart group have shadows. + private byte[] PROTOTYPE_BYTES = new byte[]{1, 1, 0, 0, 0, 0, 0, 0, 75, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - reserved (15 bits): MUST be zero, and MUST be ignored. - * - */ -public class Boppop extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8071801452993935943L; - boolean fAutoSplit, fHasShadow; - short split, iSplitPos, pcSplitPercent, pcPieSize, pcGap; - float numSplitValue; - byte pst; - - public void init() { - super.init(); - byte[] data= this.getData(); - pst= data[0]; - fAutoSplit= (data[1]==1); - split= ByteTools.readShort(data[2], data[3]); - iSplitPos= ByteTools.readShort(data[4], data[5]); - pcSplitPercent= ByteTools.readShort(data[6], data[7]); - pcPieSize= ByteTools.readShort(data[8], data[9]); - pcGap= ByteTools.readShort(data[10], data[11]); - numSplitValue= (float)ByteTools.eightBytetoLEDouble(this.getBytesAt(12, 8)); - fHasShadow= (data[20] & 1)==1; - chartType= OFPIECHART; - } - - private byte[] PROTOTYPE_BYTES = new byte[] {1, 1, 0, 0, 0, 0, 0, 0, 75, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; public static XLSRecord getPrototype() { Boppop b = new Boppop(); b.setOpcode(BOPPOP); @@ -111,48 +112,49 @@ public static XLSRecord getPrototype() { /** * returns true of pie of pie, false if bar of pie + * * @return */ public boolean isPieOfPie() { - return (pst==1); + return (pst == 1); } - + public void setIsPieOfPie(boolean b) { - if (b) - pst= 1; - else - pst= 2; - this.getData()[0]= pst; + if (b) + pst = 1; + else + pst = 2; + this.getData()[0] = pst; } - + /** - * specifies the distance between the primary pie and the secondary bar/pie. - * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. - * + * specifies the distance between the primary pie and the secondary bar/pie. + * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. + * * @param g */ public void setpcGap(int g) { - pcGap= (short)g; + pcGap = (short) g; byte[] b = ByteTools.shortToLEBytes(pcGap); this.getData()[10] = b[0]; this.getData()[11] = b[1]; } - + /** - * returns the distance between the primary pie and the secondary bar/pie. - * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. + * returns the distance between the primary pie and the secondary bar/pie. + * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. */ public int getpcGap() { - return pcGap; + return pcGap; } - + /** * specifies the size of the secondary bar/pie as a percentage of the size of the primary pie. - * + * * @param s */ public void setSecondPieSize(int s) { - pcPieSize= (short)s; + pcPieSize = (short) s; byte[] b = ByteTools.shortToLEBytes(pcPieSize); this.getData()[8] = b[0]; this.getData()[9] = b[1]; @@ -162,112 +164,118 @@ public void setSecondPieSize(int s) { * returns the size of the secondary bar/pie as a percentage of the size of the primary pie. */ public int getSecondPieSize() { - return pcPieSize; + return pcPieSize; } - + /** * specifies what determines the split between the primary pie and the secondary bar/pie. - 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. - 0x0001 Value The data is split based on a threshold value as specified by numSplitValue. - 0x0002 Percent The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. - 0x0003 Custom The data is split as arranged by the user. Custom split is specified in a following BopPopCustom record. + * 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. + * 0x0001 Value The data is split based on a threshold value as specified by numSplitValue. + * 0x0002 Percent The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. + * 0x0003 Custom The data is split as arranged by the user. Custom split is specified in a following BopPopCustom record. + * * @param t */ public void setSplitType(int t) { - fAutoSplit= false; - this.getData()[1]= 0; - split= (short) t; + fAutoSplit = false; + this.getData()[1] = 0; + split = (short) t; byte[] b = ByteTools.shortToLEBytes(split); this.getData()[2] = b[0]; this.getData()[3] = b[1]; } - - /** - * specifies what determines the split between the primary pie and the secondary bar/pie via OOXML string value: - * auto -- split point of the chart group is determined automatically - * pos -- The data is split based on the position of the data point in the series as specified by iSplitPos. - * val -- The data is split based on a threshold value as specified by numSplitValue. - * percent --The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. - * cust -- The data is split as arranged by the user. - - * @param s - */ + + /** + * specifies what determines the split between the primary pie and the secondary bar/pie via OOXML string value: + * auto -- split point of the chart group is determined automatically + * pos -- The data is split based on the position of the data point in the series as specified by iSplitPos. + * val -- The data is split based on a threshold value as specified by numSplitValue. + * percent --The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. + * cust -- The data is split as arranged by the user. + * + * @param s + */ public void setSplitType(String s) { - if (s==null) { - fAutoSplit= true; - this.getData()[1]= 0; - return; - } - fAutoSplit= false; - this.getData()[1]= 0; - if (s.equals("pos")) - split= 0; - else if (s.equals("val")) - split= 1; - else if (s.equals("percent")) - split= 2; - else if (s.equals("cust")) - split= 3; + if (s == null) { + fAutoSplit = true; + this.getData()[1] = 0; + return; + } + fAutoSplit = false; + this.getData()[1] = 0; + if (s.equals("pos")) + split = 0; + else if (s.equals("val")) + split = 1; + else if (s.equals("percent")) + split = 2; + else if (s.equals("cust")) + split = 3; byte[] b = ByteTools.shortToLEBytes(split); this.getData()[2] = b[0]; this.getData()[3] = b[1]; } + /** * returns split type, or -1 if autosplit - 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. - 0x0001 Value The data is split based on a threshold value as specified by numSplitValue. - 0x0002 Percent The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. - 0x0003 Custom The data is split as arranged by the user. Custom split is specified in a following BopPopCustom record. + * 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. + * 0x0001 Value The data is split based on a threshold value as specified by numSplitValue. + * 0x0002 Percent The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. + * 0x0003 Custom The data is split as arranged by the user. Custom split is specified in a following BopPopCustom record. + * * @return */ - public int getSplitType() { - if (fAutoSplit) return -1; - return split; + public int getSplitType() { + if (fAutoSplit) return -1; + return split; } - + public String getSplitTypeOOXML() { - if (fAutoSplit) - return "auto"; - switch (split) { - case 0: - return "pos"; - case 1: - return "val"; - case 2: - return "percent"; - case 3: - return "cust"; - } - return "auto"; // default + if (fAutoSplit) + return "auto"; + switch (split) { + case 0: + return "pos"; + case 1: + return "val"; + case 2: + return "percent"; + case 3: + return "cust"; + } + return "auto"; // default } /** - * specifies how many data points are contained in the secondary bar/pie. - Data points are contained in the secondary bar/pie starting from the end of the series. - For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. + * specifies how many data points are contained in the secondary bar/pie. + * Data points are contained in the secondary bar/pie starting from the end of the series. + * For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. + * * @param sp */ public void setSplitPos(int sp) { - iSplitPos= (short) sp; + iSplitPos = (short) sp; byte[] b = ByteTools.shortToLEBytes(iSplitPos); this.getData()[4] = b[0]; this.getData()[5] = b[1]; } - + /** - * returns how many data points are contained in the secondary bar/pie. - Data points are contained in the secondary bar/pie starting from the end of the series. - For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. - */ - public int getSplitPos() { return iSplitPos; } - + * returns how many data points are contained in the secondary bar/pie. + * Data points are contained in the secondary bar/pie starting from the end of the series. + * For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. + */ + public int getSplitPos() { + return iSplitPos; + } + /** - * Set specific options + * Set specific options */ public boolean setChartOption(String op, String val) { - if (op.equalsIgnoreCase("Gap")) { - setpcGap(Integer.parseInt(val)); - } - return true; + if (op.equalsIgnoreCase("Gap")) { + setpcGap(Integer.parseInt(val)); + } + return true; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/BoppopCustom.java b/src/main/java/io/starter/formats/XLS/charts/BoppopCustom.java index 97fa90e..8ded118 100644 --- a/src/main/java/io/starter/formats/XLS/charts/BoppopCustom.java +++ b/src/main/java/io/starter/formats/XLS/charts/BoppopCustom.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,16 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * BoppopCustom: Custom Bar of Pie/Pie of Pie Chart Options (0x1067) - * */ public class BoppopCustom extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 544717450572314091L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 544717450572314091L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java b/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java index 8bfc937..b603690 100644 --- a/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,192 +22,207 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.HashMap; + public class BubbleChart extends ChartType { - private Scatter bubble= null; - private boolean is3d= false; - public BubbleChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - bubble= (Scatter) charttype; - } - - /** - * Bubble charts handle 3d differently - * @param is3d - */ - public void setIs3d(boolean is3d) { - this.is3d= is3d; - } - - /** - * return true if this bubble chart series should be displayed as 3d - * @return - */ - public boolean is3d() { return is3d; } - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + private Scatter bubble = null; + private boolean is3d = false; + + public BubbleChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + bubble = (Scatter) charttype; + } + + /** + * Bubble charts handle 3d differently + * + * @param is3d + */ + public void setIs3d(boolean is3d) { + this.is3d = is3d; + } + + /** + * return true if this bubble chart series should be displayed as 3d + * + * @return + */ + public boolean is3d() { + return is3d; + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double x= chartMetrics.get("x"); - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there - // y value for each point= h/YMAX - StringBuffer svg= new StringBuffer(); - - if (series.size()==0) { - Logger.logErr("Scatter.getSVG: error in series"); - return ""; - } - // gather data labels, markers, has lines, series colors for chart - boolean threeD= cf.isThreeD(BUBBLECHART); - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - boolean hasLines= cf.getHasLines(); // should be per-series? - int[] markers= getMarkerFormats(); // get an array of marker formats per series - int n= series.size(); - double[] seriesx= null; - double xfactor= 0, yfactor= 0, bfactor= 0; // - boolean TEXTUALXAXIS= true; - // get x axis max/min for an x axis which is a value axis - seriesx= new double[categories.length]; - double xmin= Double.MAX_VALUE, xmax= Double.MIN_VALUE; - for (int j= 0; j < categories.length; j++) { - try { - seriesx[j]= new Double(categories[j].toString()); - xmax= Math.max(xmax, seriesx[j]); - xmin= Math.min(xmin, seriesx[j]); - TEXTUALXAXIS= false; // if ANY category val is a double, assume it's a normal xyvalue axis - } catch (Exception e) { ; /* keep going */ } - } - if (!TEXTUALXAXIS) { - double d[]= ValueRange.calcMaxMin(xmax, xmin, w); - xfactor= w/(d[2]); // w/maximum scale - } else - xfactor= w/(categories.length+1); // w/#categories - - if ((max-min)!=0) - yfactor= h/Math.abs(max-min); // h/YMAXSCALE - // get Bubble Size Factor - for (int i= 0; i < n; i++) { - double[] seriesy= (double[])series.get(i); - String[] curranges= (String[])s.getSeriesRanges().get(i); - int nseries= seriesy.length/2; - double bmin= Double.MAX_VALUE, bmax= Double.MIN_VALUE; - for (int j= nseries; j < seriesy.length; j++) { - bmax= Math.max(bmax, seriesy[j]); - bmin= Math.min(bmin, seriesy[j]); - } - if ((bmax-bmin)!=0) - bfactor= h/Math.abs(bmax-bmin)/5; - } - svg.append("\r\n"); - if (threeD) { - svg.append(get3DBubbleSVG(seriescolors)); - } - // for each series - for (int i= 0; i < n; i++) { - String labels= ""; - double[] seriesy= (double[])series.get(i); - String[] curranges= (String[])s.getSeriesRanges().get(i); - int nseries= seriesy.length/2; - for (int j= 0; j < nseries; j++) { - double xval= 0; - if (TEXTUALXAXIS /*|| i > 0*/) - xval= j+1; - else - xval= seriesx[j]; - double cx= x+xval*xfactor; - double cy= (y+h)-(seriesy[j]*yfactor); - double r= seriesy[j+nseries]*bfactor; + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double x = chartMetrics.get("x"); + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there + // y value for each point= h/YMAX + StringBuffer svg = new StringBuffer(); + + if (series.size() == 0) { + Logger.logErr("Scatter.getSVG: error in series"); + return ""; + } + // gather data labels, markers, has lines, series colors for chart + boolean threeD = cf.isThreeD(BUBBLECHART); + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + boolean hasLines = cf.getHasLines(); // should be per-series? + int[] markers = getMarkerFormats(); // get an array of marker formats per series + int n = series.size(); + double[] seriesx = null; + double xfactor = 0, yfactor = 0, bfactor = 0; // + boolean TEXTUALXAXIS = true; + // get x axis max/min for an x axis which is a value axis + seriesx = new double[categories.length]; + double xmin = Double.MAX_VALUE, xmax = Double.MIN_VALUE; + for (int j = 0; j < categories.length; j++) { + try { + seriesx[j] = new Double(categories[j].toString()); + xmax = Math.max(xmax, seriesx[j]); + xmin = Math.min(xmin, seriesx[j]); + TEXTUALXAXIS = false; // if ANY category val is a double, assume it's a normal xyvalue axis + } catch (Exception e) { + /* keep going */ + } + } + if (!TEXTUALXAXIS) { + double[] d = ValueRange.calcMaxMin(xmax, xmin, w); + xfactor = w / (d[2]); // w/maximum scale + } else + xfactor = w / (categories.length + 1); // w/#categories + + if ((max - min) != 0) + yfactor = h / Math.abs(max - min); // h/YMAXSCALE + // get Bubble Size Factor + for (int i = 0; i < n; i++) { + double[] seriesy = (double[]) series.get(i); + String[] curranges = (String[]) s.getSeriesRanges().get(i); + int nseries = seriesy.length / 2; + double bmin = Double.MAX_VALUE, bmax = Double.MIN_VALUE; + for (int j = nseries; j < seriesy.length; j++) { + bmax = Math.max(bmax, seriesy[j]); + bmin = Math.min(bmin, seriesy[j]); + } + if ((bmax - bmin) != 0) + bfactor = h / Math.abs(bmax - bmin) / 5; + } + svg.append("\r\n"); + if (threeD) { + svg.append(get3DBubbleSVG(seriescolors)); + } + // for each series + for (int i = 0; i < n; i++) { + String labels = ""; + double[] seriesy = (double[]) series.get(i); + String[] curranges = (String[]) s.getSeriesRanges().get(i); + int nseries = seriesy.length / 2; + for (int j = 0; j < nseries; j++) { + double xval = 0; + if (TEXTUALXAXIS /*|| i > 0*/) + xval = j + 1; + else + xval = seriesx[j]; + double cx = x + xval * xfactor; + double cy = (y + h) - (seriesy[j] * yfactor); + double r = seriesy[j + nseries] * bfactor; //io.starter.toolkit.Logger.log("x: " + xval + " val: " + seriesy[j] + " size: " + seriesy[j+nseries]); - if (!threeD) - svg.append("\r\n"); - else - svg.append("\r\n"); - String l= getSVGDataLabels(dls, axisMetrics, seriesy[j+nseries], 0, i, legends, categories[j].toString()); - if (l!=null) - labels+= "" + l + "\r\n"; - } - // labels after lines and markers - svg.append(labels); - } + if (!threeD) + svg.append("\r\n"); + else + svg.append("\r\n"); + String l = getSVGDataLabels(dls, axisMetrics, seriesy[j + nseries], 0, i, legends, categories[j].toString()); + if (l != null) + labels += "" + l + "\r\n"; + } + // labels after lines and markers + svg.append(labels); + } //io.starter.toolkit.Logger.log("Bubble svg: " + svg.toString()); - svg.append("\r\n"); - return svg.toString(); - } - /** - * returns the SVG necessary to define 3D bubbles (circles) for each series color - * @param seriescolors - * @return String SVG - */ - private String get3DBubbleSVG(String[] seriescolors) { - StringBuffer svg= new StringBuffer(); - svg.append("\r\n"); - for (int i= 0; i < seriescolors.length; i++) { - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append("\r\n"); - } - svg.append("\r\n"); - return svg.toString(); - } - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - if (this.is3d) - cooxml.append("bubble3d val=\"1\""); - // bubblescale - cooxml.append(""); // TODO: read correct value - // showNegBubbles - // sizeRepresents - - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - - cooxml.append(""); cooxml.append("\r\n"); - - return cooxml; - } + svg.append("\r\n"); + return svg.toString(); + } + + /** + * returns the SVG necessary to define 3D bubbles (circles) for each series color + * + * @param seriescolors + * @return String SVG + */ + private String get3DBubbleSVG(String[] seriescolors) { + StringBuffer svg = new StringBuffer(); + svg.append("\r\n"); + for (int i = 0; i < seriescolors.length; i++) { + svg.append(""); + svg.append(""); + svg.append(""); + svg.append(""); + svg.append(""); + svg.append(""); + svg.append("\r\n"); + } + svg.append("\r\n"); + return svg.toString(); + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + if (this.is3d) + cooxml.append("bubble3d val=\"1\""); + // bubblescale + cooxml.append(""); // TODO: read correct value + // showNegBubbles + // sizeRepresents + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/CatLab.java b/src/main/java/io/starter/formats/XLS/charts/CatLab.java index 813df63..a9fa478 100644 --- a/src/main/java/io/starter/formats/XLS/charts/CatLab.java +++ b/src/main/java/io/starter/formats/XLS/charts/CatLab.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,97 +24,100 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** - CATLAB: Category Labels (856h) - Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. - - Record Data - Offset Field Name Size Contents - 10 wOffset 2 Distance between category label levels - 12 at 2 Category axis label alignment - 14 grbit 2 Option flags for category axis labels (see description below) - 16 (unused) 2 Reserved; must be zero + * CATLAB: Category Labels (856h) + * Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. + *

                + * Record Data + * Offset Field Name Size Contents + * 10 wOffset 2 Distance between category label levels + * 12 at 2 Category axis label alignment + * 14 grbit 2 Option flags for category axis labels (see description below) + * 16 (unused) 2 Reserved; must be zero + *

                + * The grbit field contains the following category axis label option flags: + * Bits Mask Flag Name Contents + * 0 0001h fAutoCatLabelReal =1 if the category label skip is automatic =0 otherwise + * 15-1 FFFEh (unused) Reserved; must be zero + *

                + *

                + * wOffset (2 bytes): An unsigned integer that specifies the distance between the axis and axis label. + * It contains the offset as a percentage of the default distance. The default distance is equal to 1/3 the height of the font calculated in pixels. + * MUST be a value greater than or equal to 0 (0%) and less than or equal to 1000 (1000%). + * at (2 bytes): An unsigned integer that specifies the alignment of the axis label. MUST be a value from the following table: + * Value Alignment + * 0x0001 Top-aligned if the trot field of the Text record of the axis is not equal to 0. Left-aligned if the iReadingOrder field of the Text record of the axis specifies left-to-rightreading order; otherwise, right-aligned. + * 0x0002 Center-alignment + * 0x0003 Bottom-aligned if the trot field of the Text record of the axis is not equal to 0. Right-aligned if the iReadingOrder field of the Text record of the axis specifies left-to-right reading order; otherwise, left-aligned. + *

                + * A - cAutoCatLabelReal (1 bit): A bit that specifies whether the number of categories (3) between axis labels is set to the default value. MUST be a value from the following table: + * Value Description + * 0 The value is set to catLabel field as specified by CatSerRange record. + * 1 The value is set to the default value. The number of category (3) labels is automatically calculated by the application based on the data in the chart. + * unused (15 bits): Undefined, and MUST be ignored. + * reserved (2 bytes): MUST be zero, and MUST be ignored. + */ +public class CatLab extends GenericChartObject implements ChartObject { + /** + * serialVersionUID + */ + short wOffset, at; + private static final long serialVersionUID = 3042712098138741496L; - The grbit field contains the following category axis label option flags: - Bits Mask Flag Name Contents - 0 0001h fAutoCatLabelReal =1 if the category label skip is automatic =0 otherwise - 15-1 FFFEh (unused) Reserved; must be zero - - - wOffset (2 bytes): An unsigned integer that specifies the distance between the axis and axis label. - It contains the offset as a percentage of the default distance. The default distance is equal to 1/3 the height of the font calculated in pixels. - MUST be a value greater than or equal to 0 (0%) and less than or equal to 1000 (1000%). - at (2 bytes): An unsigned integer that specifies the alignment of the axis label. MUST be a value from the following table: - Value Alignment - 0x0001 Top-aligned if the trot field of the Text record of the axis is not equal to 0. Left-aligned if the iReadingOrder field of the Text record of the axis specifies left-to-rightreading order; otherwise, right-aligned. - 0x0002 Center-alignment - 0x0003 Bottom-aligned if the trot field of the Text record of the axis is not equal to 0. Right-aligned if the iReadingOrder field of the Text record of the axis specifies left-to-right reading order; otherwise, left-aligned. + public void init() { + super.init(); + wOffset = ByteTools.readShort(this.getData()[4], this.getData()[5]); + at = ByteTools.readShort(this.getData()[6], this.getData()[7]); + // cAutoCatLabelReal= (record[8] & 0x1==0x1 + } + + // TODO: need prototype bytes + private byte[] PROTOTYPE_BYTES = new byte[]{(byte) 86, 8, 0, 0, (byte) 100, 0, 2, 0, 86, 66, 0, 0}; - A - cAutoCatLabelReal (1 bit): A bit that specifies whether the number of categories (3) between axis labels is set to the default value. MUST be a value from the following table: - Value Description - 0 The value is set to catLabel field as specified by CatSerRange record. - 1 The value is set to the default value. The number of category (3) labels is automatically calculated by the application based on the data in the chart. - unused (15 bits): Undefined, and MUST be ignored. - reserved (2 bytes): MUST be zero, and MUST be ignored. - -*/ -public class CatLab extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - short wOffset, at; - private static final long serialVersionUID = 3042712098138741496L; - public void init() { - super.init(); - wOffset= ByteTools.readShort(this.getData()[4], this.getData()[5]); - at= ByteTools.readShort(this.getData()[6], this.getData()[7]); - // cAutoCatLabelReal= (record[8] & 0x1==0x1 - } - // TODO: need prototype bytes - private byte[] PROTOTYPE_BYTES = new byte[] {(byte)86, 8, 0, 0, (byte)100, 0, 2, 0, 86, 66, 0, 0}; public static XLSRecord getPrototype() { - CatLab cl= new CatLab(); - cl.setOpcode(CATLAB); - cl.setData(cl.PROTOTYPE_BYTES); - cl.init(); + CatLab cl = new CatLab(); + cl.setOpcode(CATLAB); + cl.setData(cl.PROTOTYPE_BYTES); + cl.init(); return cl; } - + public void setOption(String op, String val) { - if (op.equals("lblAlign")) { // ctr, l, r - if (val.equals("ctr")) - at= 2; - else if (val.equals("l")) - at= 1; - else - at= 3; - } else if (op.equals("lblOffset")) { // 0-100 - wOffset= (short)Integer.parseInt(val); - } - updateRecord(); + if (op.equals("lblAlign")) { // ctr, l, r + if (val.equals("ctr")) + at = 2; + else if (val.equals("l")) + at = 1; + else + at = 3; + } else if (op.equals("lblOffset")) { // 0-100 + wOffset = (short) Integer.parseInt(val); + } + updateRecord(); } - + public String getOption(String op) { - if (op.equals("lblAlign")) { // ctr, l, r - if (at==2) - return "ctr"; - else if (at==1) - return "l"; - else - return "r"; - } else if (op.equals("lblOffset")) // 0-100 - return Integer.toString(wOffset); - return null; + if (op.equals("lblAlign")) { // ctr, l, r + if (at == 2) + return "ctr"; + else if (at == 1) + return "l"; + else + return "r"; + } else if (op.equals("lblOffset")) // 0-100 + return Integer.toString(wOffset); + return null; } - + private void updateRecord() { - byte[] b= new byte[2]; + byte[] b = new byte[2]; b = ByteTools.shortToLEBytes(wOffset); - this.getData()[4]= b[0]; - this.getData()[5]= b[1]; + this.getData()[4] = b[0]; + this.getData()[5] = b[1]; b = ByteTools.shortToLEBytes(at); - this.getData()[6]= b[0]; - this.getData()[7]= b[1]; + this.getData()[6] = b[0]; + this.getData()[7] = b[1]; } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/CatserRange.java b/src/main/java/io/starter/formats/XLS/charts/CatserRange.java index aa7e537..e2473b7 100644 --- a/src/main/java/io/starter/formats/XLS/charts/CatserRange.java +++ b/src/main/java/io/starter/formats/XLS/charts/CatserRange.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,83 +24,87 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * CatserRange: Defines a Category or Series Axis (0x1020) - - 4 catCross 2 Value axis/category crossing point (2-D charts only) - If fMaxCross is set to 1, the value this field MUST be ignored. - for series axes, must be 0 - for cat: This field specifies the category at which the value axis crosses. - For example, if this field is 2, the value axis crosses this axis at the second category on this axis. - MUST be greater than or equal to 1 and less than or equal to 31999. - 6 catLabel 2 Frequency of labels. A signed integer that specifies the interval between axis labels on this axis. - MUST be greater than or equal to 1 and less than or equal to 31999. - MUST be ignored for a date axis. - 8 catMark 2 Frequency of tick marks. A signed integer that specifies the interval at which major tick marks - and minor tick marks are displayed on the axis. Major tick marks and minor tick marks that would - have been visible are hidden unless they are located at a multiple of this field. - MUST be greater than or equal to 1, and less than or equal to 31999. - MUST be ignored for a date axis. - 10 grbit 2 Format flags - - The catCross field defines the point on the category axis where the value axis crosses. - A value of 01 indicates that the value axis crosses to the left, or in the center, of the first category - (depending on the value of bit 0 of the grbit field); a value of 02 indicates that the value axis crosses - to the left or center of the second category, and so on. Bit 2 of the grbit field overrides the value of catCross when set to 1. - - The catLabel field defines how often labels appear along the category or series axis. A value of 01 indicates - that a category label will appear with each category, a value of 02 means a label appears every other category, and so on. - - The catMark field defines how often tick marks appear along the category or series axis. A value of 01 indicates - that a tick mark will appear between each category or series; a value of 02 means a label appears between every - other category or series, etc. - - format flags: - 0 0xOl fBetween Value axis crossing a = axis crosses midcategory I = axis crosses between categories - 1 0x02 fMaxCross Value axis crosses at the far right category (in a line, bar, column, scatter, or area chart; 2-D charts only) - 0 The value axis crosses this axis at the value specified by catCross. - 1 The value axis crosses this axis at the last category, the last series, or the maximum date. - 2 0x04 fReverse Display categories in reverse order - 7-3 0xF8 (reserved) Reserved; must be zero - 7-0 0xFF (reserved) Reserved; must be zero - * + *

                + * 4 catCross 2 Value axis/category crossing point (2-D charts only) + * If fMaxCross is set to 1, the value this field MUST be ignored. + * for series axes, must be 0 + * for cat: This field specifies the category at which the value axis crosses. + * For example, if this field is 2, the value axis crosses this axis at the second category on this axis. + * MUST be greater than or equal to 1 and less than or equal to 31999. + * 6 catLabel 2 Frequency of labels. A signed integer that specifies the interval between axis labels on this axis. + * MUST be greater than or equal to 1 and less than or equal to 31999. + * MUST be ignored for a date axis. + * 8 catMark 2 Frequency of tick marks. A signed integer that specifies the interval at which major tick marks + * and minor tick marks are displayed on the axis. Major tick marks and minor tick marks that would + * have been visible are hidden unless they are located at a multiple of this field. + * MUST be greater than or equal to 1, and less than or equal to 31999. + * MUST be ignored for a date axis. + * 10 grbit 2 Format flags + *

                + * The catCross field defines the point on the category axis where the value axis crosses. + * A value of 01 indicates that the value axis crosses to the left, or in the center, of the first category + * (depending on the value of bit 0 of the grbit field); a value of 02 indicates that the value axis crosses + * to the left or center of the second category, and so on. Bit 2 of the grbit field overrides the value of catCross when set to 1. + *

                + * The catLabel field defines how often labels appear along the category or series axis. A value of 01 indicates + * that a category label will appear with each category, a value of 02 means a label appears every other category, and so on. + *

                + * The catMark field defines how often tick marks appear along the category or series axis. A value of 01 indicates + * that a tick mark will appear between each category or series; a value of 02 means a label appears between every + * other category or series, etc. + *

                + * format flags: + * 0 0xOl fBetween Value axis crossing a = axis crosses midcategory I = axis crosses between categories + * 1 0x02 fMaxCross Value axis crosses at the far right category (in a line, bar, column, scatter, or area chart; 2-D charts only) + * 0 The value axis crosses this axis at the value specified by catCross. + * 1 The value axis crosses this axis at the last category, the last series, or the maximum date. + * 2 0x04 fReverse Display categories in reverse order + * 7-3 0xF8 (reserved) Reserved; must be zero + * 7-0 0xFF (reserved) Reserved; must be zero */ public class CatserRange extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 905038625844435651L; - private short grbit, catCross, catLabel, catMark; - private boolean fBetween, fMaxCross, fReverse; - // 20070723 KSC: Need to create new records + /** + * serialVersionUID + */ + private static final long serialVersionUID = 905038625844435651L; + private short grbit, catCross, catLabel, catMark; + private boolean fBetween, fMaxCross, fReverse; + + // 20070723 KSC: Need to create new records public static XLSRecord getPrototype() { CatserRange c = new CatserRange(); c.setOpcode(CATSERRANGE); c.setData(c.PROTOTYPE_BYTES); c.init(); return c; - } // 0, 0, 1, 0, 1, 0, 0, 0 - for axis 2 of surface chart - private byte[] PROTOTYPE_BYTES = new byte[] {1, 0, 1, 0, 1, 0, 1, 0}; - + } // 0, 0, 1, 0, 1, 0, 0, 0 - for axis 2 of surface chart + + private byte[] PROTOTYPE_BYTES = new byte[]{1, 0, 1, 0, 1, 0, 1, 0}; + // 20070727 KSC: TODO: Get data def and parse correctly! public void setOpt(int op) { byte[] b = ByteTools.shortToLEBytes((short) op); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; // 20070802 KSC: don't know what this means - this.getData()[6]= 0; + this.getData()[6] = 0; } + // 20070802 KSC: parse data public void init() { - super.init(); - catCross= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - catLabel= ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - catMark= ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - grbit= ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - fBetween= (grbit & 0x1)==0x1; - fMaxCross= (grbit & 0x2)==0x2; - fReverse= (grbit & 0x4)==0x4; + super.init(); + catCross = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + catLabel = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + catMark = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + grbit = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + fBetween = (grbit & 0x1) == 0x1; + fMaxCross = (grbit & 0x2) == 0x2; + fReverse = (grbit & 0x4) == 0x4; } + private void updateRecord() { byte[] b = ByteTools.shortToLEBytes(catCross); this.getData()[0] = b[0]; @@ -115,91 +119,130 @@ private void updateRecord() { this.getData()[6] = b[0]; this.getData()[7] = b[1]; } + // get/set methods - public int getCatCross() { return catCross; } - public int getCatLabel() { return catLabel; } - public int getCatMark() { return catMark; } - public boolean getCrossBetween() { return fBetween; } - public boolean getCrossMax() { return fMaxCross; } - public void setCatCross(int c) { catCross= (short) c; updateRecord(); } - public void setCatLabel(int c) { catLabel= (short) c; updateRecord(); } - public void setCatMark(int c) { catMark= (short) c; updateRecord(); } - public void setCrossBetween(boolean b) { fBetween= b; grbit= ByteTools.updateGrBit(grbit, fBetween, 0); updateRecord(); } - public void setCrossMax(boolean b) { fMaxCross= b; grbit= ByteTools.updateGrBit(grbit, fMaxCross, 1); updateRecord(); } - - /** - * sets a specific OOXML axis option + public int getCatCross() { + return catCross; + } + + public int getCatLabel() { + return catLabel; + } + + public int getCatMark() { + return catMark; + } + + public boolean getCrossBetween() { + return fBetween; + } + + public boolean getCrossMax() { + return fMaxCross; + } + + public void setCatCross(int c) { + catCross = (short) c; + updateRecord(); + } + + public void setCatLabel(int c) { + catLabel = (short) c; + updateRecord(); + } + + public void setCatMark(int c) { + catMark = (short) c; + updateRecord(); + } + + public void setCrossBetween(boolean b) { + fBetween = b; + grbit = ByteTools.updateGrBit(grbit, fBetween, 0); + updateRecord(); + } + + public void setCrossMax(boolean b) { + fMaxCross = b; + grbit = ByteTools.updateGrBit(grbit, fMaxCross, 1); + updateRecord(); + } + + /** + * sets a specific OOXML axis option *
                can be one of: *
                auto - *
                crosses possible crossing points (autoZero, max, min) + *
                crosses possible crossing points (autoZero, max, min) *
                crossesAt where on axis the perpendicular axis crosses (double val) *
                lblAlign text alignment for tick labels (ctr, l, r) (cat only) *
                lblOffset distance of labels from the axis (0-1000) (cat only) *
                tickLblSkip how many tick labels to skip between label (int >= 1) *
                tickMarkSkip how many tick marks to skip betwen ticks (int >= 1) - * @param op - * @param val - */ + * + * @param op + * @param val + */ // TODO: auto public boolean setOption(String op, String val) { - if (op.equals("crossesAt")) // specifies where axis crosses (double value - catCross= Short.valueOf(val).shortValue(); - else if (op.equals("orientation")) { // axis orientation minMax or maxMin -- fReverse - fReverse= (val.equals("maxMin")); // means in reverse order - ByteTools.updateGrBit(grbit, fReverse, 2); - } - else if (op.equals("crosses")) { // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross - if (val.equals("max")) { // TODO: this is probly wrong - fMaxCross= true; - ByteTools.updateGrBit(grbit, fMaxCross, 7); - } else if (val.equals("mid")) { - fBetween= false; - ByteTools.updateGrBit(grbit, fBetween, 0); - } else if (val.equals("autoZero")) { - fBetween= true; // is this correct?? - ByteTools.updateGrBit(grbit, fBetween, 0); - }else if (val.equals("min")) ; - // TODO: ??? - } else if (op.equals("tickMarkSkip")) //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? - catMark= Integer.valueOf(val).shortValue(); - else if (op.equals("tickLblSkip")) - catLabel= Integer.valueOf(val).shortValue(); - else - return false; // not handled - this.updateRecord(); - return true; - } - - + if (op.equals("crossesAt")) // specifies where axis crosses (double value + catCross = Short.valueOf(val).shortValue(); + else if (op.equals("orientation")) { // axis orientation minMax or maxMin -- fReverse + fReverse = (val.equals("maxMin")); // means in reverse order + ByteTools.updateGrBit(grbit, fReverse, 2); + } else if (op.equals("crosses")) { // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross + if (val.equals("max")) { // TODO: this is probly wrong + fMaxCross = true; + ByteTools.updateGrBit(grbit, fMaxCross, 7); + } else if (val.equals("mid")) { + fBetween = false; + ByteTools.updateGrBit(grbit, fBetween, 0); + } else if (val.equals("autoZero")) { + fBetween = true; // is this correct?? + ByteTools.updateGrBit(grbit, fBetween, 0); + } else if (val.equals("min")) ; + // TODO: ??? + } else if (op.equals("tickMarkSkip")) //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? + catMark = Integer.valueOf(val).shortValue(); + else if (op.equals("tickLblSkip")) + catLabel = Integer.valueOf(val).shortValue(); + else + return false; // not handled + this.updateRecord(); + return true; + } + + /** * retrieve generic Category axis option + * * @param op * @return */ public String getOption(String op) { - // TODO: auto, lblAlign, lblOffset - if (op.equals("crossesAt")) - return String.valueOf(catCross); - if (op.equals("orientation")) - return (fReverse)?"maxMin":"minMax"; - if (op.equals("crosses")) { - if (fMaxCross) return "max"; - if (fBetween)return "autoZero"; // correct?? - return "min"; // correct?? - } - if (op.equals("tickMarkSkip")) //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? - return String.valueOf(catMark); + // TODO: auto, lblAlign, lblOffset + if (op.equals("crossesAt")) + return String.valueOf(catCross); + if (op.equals("orientation")) + return (fReverse) ? "maxMin" : "minMax"; + if (op.equals("crosses")) { + if (fMaxCross) return "max"; + if (fBetween) return "autoZero"; // correct?? + return "min"; // correct?? + } + if (op.equals("tickMarkSkip")) //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? + return String.valueOf(catMark); if (op.equals("tickLblSkip")) - return String.valueOf(catLabel); - return null; + return String.valueOf(catLabel); + return null; } - + /** * returns true if the axis should be displayed at top of chart area * false if axis is displayed in the default bottom location + * * @return */ public boolean isReversed() { - return fReverse; + return fReverse; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Chart.java b/src/main/java/io/starter/formats/XLS/charts/Chart.java index fd59a10..383f62b 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Chart.java +++ b/src/main/java/io/starter/formats/XLS/charts/Chart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,79 +22,76 @@ */ package io.starter.formats.XLS.charts; -import io.starter.toolkit.*; - -import java.io.*; -import java.util.*; - -import io.starter.formats.XLS.*; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.OpenXLS.*; +import io.starter.OpenXLS.ChartHandle; import io.starter.OpenXLS.ChartHandle.ChartOptions; - +import io.starter.OpenXLS.ExcelTools; +import io.starter.OpenXLS.WorkBookHandle; +import io.starter.formats.XLS.*; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.Logger; import org.json.JSONArray; import org.json.JSONException; -import org.json.JSONObject; - - -/** Chart: Chart Location and Dimensions 0x1002h
                - - The Chart record determines the chart dimensions and - marks the beginning of the Chart records. - -

                -    ** Note that all these values are split up 2 bytes integer and 2 bytes fractional
                -    
                -    offset  name        size    contents
                -    ---    
                -    4       x           4       x pos of upper left corner
                -    8       y           4       y pos of upper left corner
                -    12      dx          4       x-size
                -    16      dy          4       y-size
                -        
                -    

                - +import java.io.*; +import java.util.*; - notes on implementation: - -
                A chart may have up to 9 chart types (specific instances of a chart e.g. bar, line, etc.) known as chart groups. These are identified by the chartgroup array list. -
                The parent of these chart groups are the axis group. -
                A chart's series and trendlines contains indexes into the chart group. - @see Ai - - +/** + * Chart: Chart Location and Dimensions 0x1002h
                + *

                + * The Chart record determines the chart dimensions and + * marks the beginning of the Chart records. + * + *

                + * * Note that all these values are split up 2 bytes integer and 2 bytes fractional
                + *
                + * offset  name        size    contents
                + * ---
                + * 4       x           4       x pos of upper left corner
                + * 8       y           4       y pos of upper left corner
                + * 12      dx          4       x-size
                + * 16      dy          4       y-size
                + *
                + * 

                + *

                + *

                + *

                + * notes on implementation: + * + *
                A chart may have up to 9 chart types (specific instances of a chart e.g. bar, line, etc.) known as chart groups. These are identified by the chartgroup array list. + *
                The parent of these chart groups are the axis group. + *
                A chart's series and trendlines contains indexes into the chart group. + * + * @see Ai */ -public class Chart extends GenericChartObject implements ChartObject -{ - static final long serialVersionUID = 6702247464633674375l; +public class Chart extends GenericChartObject implements ChartObject { + static final long serialVersionUID = 6702247464633674375l; // Objects which define a chart -- REMEMBER TO UPDATE OOXMLChart copy constructor if modify below member vars - protected ArrayList chartgroup= new ArrayList(); // can have up to 4 chart objects (Bar, Area, Line, etc.) per a given chart - protected int nCharts= 0; // total number of charts (0= the default chart + any overlay charts (up to 4 charts (9 in OOXML) see chartgroup list) - protected ChartAxes chartaxes= null; - protected ChartSeries chartseries= new ChartSeries(); - protected TextDisp charttitle= null; - protected Dimensions dimensions; // datarange dimensions ... + protected ArrayList chartgroup = new ArrayList(); // can have up to 4 chart objects (Bar, Area, Line, etc.) per a given chart + protected int nCharts = 0; // total number of charts (0= the default chart + any overlay charts (up to 4 charts (9 in OOXML) see chartgroup list) + protected ChartAxes chartaxes = null; + protected ChartSeries chartseries = new ChartSeries(); + protected TextDisp charttitle = null; + protected Dimensions dimensions; // datarange dimensions ... // The following two objects are external objects in the XLS stream that are associated with the chart. - protected Obj obj = null; - protected MSODrawing msodrawobj = null; // stores coordinates and shape info ... - HashMap chartMetrics= new HashMap(); // hold chartMetrics: x, y, w, h, canvasw, canvash - public transient WorkBookHandle wbh; - + protected Obj obj = null; + protected MSODrawing msodrawobj = null; // stores coordinates and shape info ... + HashMap chartMetrics = new HashMap(); // hold chartMetrics: x, y, w, h, canvasw, canvash + public transient WorkBookHandle wbh; + // TODO: MANAGE FONTS ---? - - // internal chart records - protected ArrayList chartRecs = new ArrayList(); + + // internal chart records + protected ArrayList chartRecs = new ArrayList(); protected AbstractList preRecs; protected AbstractList postRecs = new ArrayList(); - protected boolean dirtyflag = false; // if anything has changed in the chart (except series, which is handled via another var) - protected boolean metricsDirty= true; // initially true so creates min, max and other metrics, true if should be recalculated - // below vars used to save state in addInitialChartRecord recursion - protected Ai currentAi; // used in init only - protected int hierarchyDepth = 0; // "" - protected ArrayList initobs= new ArrayList(); + protected boolean dirtyflag = false; // if anything has changed in the chart (except series, which is handled via another var) + protected boolean metricsDirty = true; // initially true so creates min, max and other metrics, true if should be recalculated + // below vars used to save state in addInitialChartRecord recursion + protected Ai currentAi; // used in init only + protected int hierarchyDepth = 0; // "" + protected ArrayList initobs = new ArrayList(); /* @@ -104,942 +101,1000 @@ public Chart copy() throws InstantiationException, IllegalAccessException { }*/ public void init() { - super.init(); - getData(); - chartseries.setParentChart(this); + super.init(); + getData(); + chartseries.setParentChart(this); /** * NOTE: the x, y, dx (w) and dy (h) are in most cases * not used; the MSODRAWING coords governs the * chart canvas; see getMetrics for plot and other adjustments - * + * * New Info: have to see if this works * Get chart area width in pixels - chart area width in pixels = (dx field of Chart record - 8) * DPI of the display device / 72 - If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: - chart area width in pixels -= 2 * line width of the display device in pixels + chart area width in pixels = (dx field of Chart record - 8) * DPI of the display device / 72 + If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: + chart area width in pixels -= 2 * line width of the display device in pixels - Get chart area height in pixels - chart area height in pixels = (dy field of Chart record - 8) * DPI of the display device / 72 - If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: - chart area height in pixels -= 2 * line height of the display device in pixels + Get chart area height in pixels + chart area height in pixels = (dy field of Chart record - 8) * DPI of the display device / 72 + If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: + chart area height in pixels -= 2 * line height of the display device in pixels - NOTE: + NOTE: * Since the 1980s, the Microsoft Windows operating system has set the default display "DPI" to 96 PPI, * while Apple/Macintosh computers have used a default of 72 PPI.[2] * - byte[] rkdata = this.getData(); - try { - short s = ByteTools.readShort(rkdata[0],rkdata[1]); - short ss = ByteTools.readShort(rkdata[2],rkdata[3]); - - if(ss<0)ss=0; - String parser = s + "." + ss; - x = (new Float(parser)).floatValue(); - - s = ByteTools.readShort(rkdata[4],rkdata[5]); - ss = ByteTools.readShort(rkdata[6],rkdata[7]); - if(ss<0)ss=0; - - parser = s + "." + ss; - y = (new Float(parser)).floatValue(); - - - /*Value of the real number = Integral + (Fractional / 65536.0) - * Integral (2 bytes): A signed integer that specifies the integral part of the real number. - Fractional (2 bytes): An unsigned integer that specifies the fractional part of the real number. - * - int integral= 0; - integral |= rkdata[8] & 0xFF; - integral <<= 8; - integral |= rkdata[9] & 0xFF; - int fractional= ByteTools.readUnsignedShort(rkdata[10],rkdata[11]); - dx= (float)(integral + (fractional/65536.0)); - integral= ((rkdata[12] & 0xFF) << 8) | (rkdata[13] & 0xFF); - if (integral < 0) integral += 256; - fractional= ByteTools.readUnsignedShort(rkdata[14],rkdata[15]); - dy= (float) integral; - } catch (NumberFormatException e) { // 20080414 KSC: parsing appears to be off - TODO Check out Chart record specs - Logger.logErr("Chart.init: parsing dimensions failed: " + e); - } - - if ((DEBUGLEVEL > 3)) - Logger.logInfo("Chart Found @ x:" + x + " y:" + y + " dx:" + dx + " dy:" + dy);*/ - } + byte[] rkdata = this.getData(); + try { + short s = ByteTools.readShort(rkdata[0],rkdata[1]); + short ss = ByteTools.readShort(rkdata[2],rkdata[3]); + + if(ss<0)ss=0; + String parser = s + "." + ss; + x = (new Float(parser)).floatValue(); + + s = ByteTools.readShort(rkdata[4],rkdata[5]); + ss = ByteTools.readShort(rkdata[6],rkdata[7]); + if(ss<0)ss=0; + + parser = s + "." + ss; + y = (new Float(parser)).floatValue(); + + + /*Value of the real number = Integral + (Fractional / 65536.0) + * Integral (2 bytes): A signed integer that specifies the integral part of the real number. + Fractional (2 bytes): An unsigned integer that specifies the fractional part of the real number. + * + int integral= 0; + integral |= rkdata[8] & 0xFF; + integral <<= 8; + integral |= rkdata[9] & 0xFF; + int fractional= ByteTools.readUnsignedShort(rkdata[10],rkdata[11]); + dx= (float)(integral + (fractional/65536.0)); + integral= ((rkdata[12] & 0xFF) << 8) | (rkdata[13] & 0xFF); + if (integral < 0) integral += 256; + fractional= ByteTools.readUnsignedShort(rkdata[14],rkdata[15]); + dy= (float) integral; + } catch (NumberFormatException e) { // 20080414 KSC: parsing appears to be off - TODO Check out Chart record specs + Logger.logErr("Chart.init: parsing dimensions failed: " + e); + } + + if ((DEBUGLEVEL > 3)) + Logger.logInfo("Chart Found @ x:" + x + " y:" + y + " dx:" + dx + " dy:" + dy);*/ + } + - /* * Note, other important chart records we are not as of yet trapping: * CttMlFrt 0x89E -- additional properties for chart elements * ShapePropsStream 0x8A4 * TextPropsStream 0x8A5 * CrtLayout12 0x89D -- layout info for attached label, legend - * 0x89E=The CrtMlFrt record specifies additional properties for chart elements, as specified by the Chart Sheet SubstreamABNF. These properties complement the record to which they correspond, and are stored as a structure chain defined in XmlTkChain. An application can ignore this record without loss of functionality, except for the additional properties. If this record is longer than 8224 bytes, it MUST be split into several records. The first section of the data appears in this record and subsequent sections appear in one or more CrtMlFrtContinue records that follow this record. - * - * Legend --> Pos, TextDisp, Frame, CrtLayout12, TextPropsStream, CrtLayout12 + * 0x89E=The CrtMlFrt record specifies additional properties for chart elements, as specified by the Chart Sheet SubstreamABNF. These properties complement the record to which they correspond, and are stored as a structure chain defined in XmlTkChain. An application can ignore this record without loss of functionality, except for the additional properties. If this record is longer than 8224 bytes, it MUST be split into several records. The first section of the data appears in this record and subsequent sections appear in one or more CrtMlFrtContinue records that follow this record. + * + * Legend --> Pos, TextDisp, Frame, CrtLayout12, TextPropsStream, CrtLayout12 */ + /** * Adds chart records to the chartRecs array. This array should be completed with all chart records * before the initChartRecords method is called, creating the hierarchical chart structure. + * * @param br - a chart record * @return false if we reach the final "end" record, signifying the end of the chart object structure. - * + *

                * Be aware that there can be additional chart records after this structure, Chart only deals with it's own heirarchy. */ public boolean addInitialChartRecord(BiffRec br) { - if (br.getOpcode() == 0x1033) { + if (br.getOpcode() == 0x1033) { ++hierarchyDepth; - }else if (br.getOpcode() == 0x1034) { + } else if (br.getOpcode() == 0x1034) { --hierarchyDepth; if (hierarchyDepth == 0) { chartRecs.add(br); return false; - } + } } if (hierarchyDepth != 0) { if (br.getOpcode() == AI) { - currentAi = (Ai)br; + currentAi = (Ai) br; currentAi.setParentChart(this); // necessary for base ops currentAi.setSheet(this.getSheet()); // needed for cell change updates - }else if (currentAi !=null && br.getOpcode()==SERIESTEXT) { - currentAi.setSeriesText((SeriesText) br); - currentAi= null; // done - }else if (br.getOpcode()==SERIES) { - chartseries.add(new Object[] {br, Integer.valueOf(nCharts)}); // store series records - initially map all to default chart (normal case) - } else if (br.getOpcode()==AXISPARENT) { - chartaxes= new ChartAxes((AxisParent)br); - }else if (br.getOpcode()==AXIS) { - chartaxes.add((Axis)br); - }else if (br.getOpcode()==CHARTFORMAT) { // usually only 1 (default chart id= 0) but can have up to 9 overlay charts - nCharts++; - }else if (br.getOpcode()==SERIESLIST) { // maps chart # to series # - chartseries.addSeriesMapping(nCharts-1, ((SeriesList) br).getSeriesMappings());// 1-based series #'s to "assign" to the overlay chart - } - try { - if (((GenericChartObject) br).chartType!=-1) { // store the chart object which defines this chart (NOTE: may be up to 4 charts in one = overlay charts) - ChartFormat cf= null; - for (int i= chartRecs.size()-1; i >= 0; i--) { - if (((BiffRec) chartRecs.get(i)).getOpcode()==XLSConstants.CHARTFORMAT) { - cf= (ChartFormat) chartRecs.get(i); - break; - } - } + } else if (currentAi != null && br.getOpcode() == SERIESTEXT) { + currentAi.setSeriesText((SeriesText) br); + currentAi = null; // done + } else if (br.getOpcode() == SERIES) { + chartseries.add(new Object[]{br, Integer.valueOf(nCharts)}); // store series records - initially map all to default chart (normal case) + } else if (br.getOpcode() == AXISPARENT) { + chartaxes = new ChartAxes((AxisParent) br); + } else if (br.getOpcode() == AXIS) { + chartaxes.add((Axis) br); + } else if (br.getOpcode() == CHARTFORMAT) { // usually only 1 (default chart id= 0) but can have up to 9 overlay charts + nCharts++; + } else if (br.getOpcode() == SERIESLIST) { // maps chart # to series # + chartseries.addSeriesMapping(nCharts - 1, ((SeriesList) br).getSeriesMappings());// 1-based series #'s to "assign" to the overlay chart + } + try { + if (((GenericChartObject) br).chartType != -1) { // store the chart object which defines this chart (NOTE: may be up to 4 charts in one = overlay charts) + ChartFormat cf = null; + for (int i = chartRecs.size() - 1; i >= 0; i--) { + if (((BiffRec) chartRecs.get(i)).getOpcode() == XLSConstants.CHARTFORMAT) { + cf = (ChartFormat) chartRecs.get(i); + break; + } + } // sadly, can't do it here - have to wait until process hierarchy chartobj.add(ChartType.getChartObject((GenericChartObject)br, cf, this.getWorkBook())); - initobs.add(new BiffRec[] { br, cf}); + initobs.add(new BiffRec[]{br, cf}); } // Set Parent Chart as it's necessary for all basic chart ops - ((GenericChartObject) br).setParentChart(this); // Note: Scl is not a GenericChartObject + can have unknown (XLSRecords) - will cause ClassCastException - } catch (ClassCastException c) { ; } + ((GenericChartObject) br).setParentChart(this); // Note: Scl is not a GenericChartObject + can have unknown (XLSRecords) - will cause ClassCastException + } catch (ClassCastException c) { + } chartRecs.add(br); - }else { - if(br.getOpcode()==DIMENSIONS)dimensions= (Dimensions) br; + } else { + if (br.getOpcode() == DIMENSIONS) dimensions = (Dimensions) br; br.getData(); postRecs.add(br); } - + return true; } - - + + public void setDirtyFlag(boolean b) { dirtyflag = b; } - + /** - * Take the initial array of records for the chart, and create a + * Take the initial array of records for the chart, and create a * hierarchial array of objects for the chart. Also, populate initial values for easy access. - * */ public void initChartRecords() { - try { // Added to find/set obj + msodrawing records - replaces setObj - // see TestReadWrite.TestIOOBError - // For Normal charts, Obj rec is the last record before the Chart record - if (!this.getSheet().isChartOnlySheet()) { - int pos= this.getSheet().getSheetRecs().size()-1; - BiffRec rec= (BiffRec) this.getSheet().getSheetRecs().get(pos); - obj = (Obj)rec; - obj.setChart(this); - // Usually, MsoDrawing is just preceding Obj record, except in those rare carses where - // there are Continues and Txo's ... - while (--pos > 0) { - rec= (BiffRec) this.getSheet().getSheetRecs().get(pos); - if(rec.getOpcode()==MSODRAWING) { - this.msodrawobj = (MSODrawing)rec; - break; - } - } - } // chart-only worksheets have no obj/mso apparently - } catch (Exception e) { - Logger.logErr("initChartRecords: Error in Chart Records: " + e.toString()); - } - + try { // Added to find/set obj + msodrawing records - replaces setObj + // see TestReadWrite.TestIOOBError + // For Normal charts, Obj rec is the last record before the Chart record + if (!this.getSheet().isChartOnlySheet()) { + int pos = this.getSheet().getSheetRecs().size() - 1; + BiffRec rec = (BiffRec) this.getSheet().getSheetRecs().get(pos); + obj = (Obj) rec; + obj.setChart(this); + // Usually, MsoDrawing is just preceding Obj record, except in those rare carses where + // there are Continues and Txo's ... + while (--pos > 0) { + rec = (BiffRec) this.getSheet().getSheetRecs().get(pos); + if (rec.getOpcode() == MSODRAWING) { + this.msodrawobj = (MSODrawing) rec; + break; + } + } + } // chart-only worksheets have no obj/mso apparently + } catch (Exception e) { + Logger.logErr("initChartRecords: Error in Chart Records: " + e.toString()); + } + // Turn it into a static array initially to speed up random access BiffRec[] bArr = new BiffRec[chartRecs.size()]; - bArr = (BiffRec[])chartRecs.toArray(bArr); + bArr = (BiffRec[]) chartRecs.toArray(bArr); this.initChartObject(this, bArr); - for (int i= 0; i < initobs.size(); i++) { - BiffRec[] ios= (BiffRec[]) initobs.get(i); - chartgroup.add(ChartType.createChartTypeObject((GenericChartObject)ios[0], (ChartFormat)ios[1], this.getWorkBook())); - Legend l= (Legend) Chart.findRec(((ChartFormat)ios[1]).chartArr, Legend.class); - if (l!=null) - ((ChartType)chartgroup.get(chartgroup.size()-1)).addLegend(l); - } - initobs= new ArrayList(); // clear out + for (int i = 0; i < initobs.size(); i++) { + BiffRec[] ios = (BiffRec[]) initobs.get(i); + chartgroup.add(ChartType.createChartTypeObject((GenericChartObject) ios[0], (ChartFormat) ios[1], this.getWorkBook())); + Legend l = (Legend) Chart.findRec(((ChartFormat) ios[1]).chartArr, Legend.class); + if (l != null) + chartgroup.get(chartgroup.size() - 1).addLegend(l); + } + initobs = new ArrayList(); // clear out } - + /** * Handle the iteration and creation of the chart objects */ private ChartObject initChartObject(ChartObject cobj, BiffRec[] cRecs) { - - for (int i=0;ii+1)&&((cRecs[i+1]).getOpcode()==BEGIN)){ // this is an object with sub-data + if (!(b.getOpcode() == BEGIN) && !(b.getOpcode() == END)) { + if ((cRecs.length > i + 1) && ((cRecs[i + 1]).getOpcode() == BEGIN)) { // this is an object with sub-data try { - ChartObject co = (ChartObject)b; + ChartObject co = (ChartObject) b; int endloc = this.getMatchingEndRecordLocation(i, cRecs); - int arrlen = endloc-i; + int arrlen = endloc - i; BiffRec[] objArr = new BiffRec[arrlen]; - System.arraycopy(cRecs, i+1, objArr, 0, arrlen); - cobj.addChartRecord((XLSRecord)this.initChartObject(co, objArr)); + System.arraycopy(cRecs, i + 1, objArr, 0, arrlen); + cobj.addChartRecord((XLSRecord) this.initChartObject(co, objArr)); // necessary initialization of key elements if (co instanceof TextDisp) { - int type= ((TextDisp) co).getType(); - // -1 = default text ...? - if (type==ObjectLink.TYPE_TITLE) - charttitle= (TextDisp) co; - else if (type==ObjectLink.TYPE_XAXIS) - chartaxes.setTd(XAXIS, (TextDisp) co); - else if (type==ObjectLink.TYPE_YAXIS) - chartaxes.setTd(YAXIS, (TextDisp) co); - else if (type==ObjectLink.TYPE_ZAXIS) + int type = ((TextDisp) co).getType(); + // -1 = default text ...? + if (type == ObjectLink.TYPE_TITLE) + charttitle = (TextDisp) co; + else if (type == ObjectLink.TYPE_XAXIS) + chartaxes.setTd(XAXIS, (TextDisp) co); + else if (type == ObjectLink.TYPE_YAXIS) + chartaxes.setTd(YAXIS, (TextDisp) co); + else if (type == ObjectLink.TYPE_ZAXIS) chartaxes.setTd(ZAXIS, (TextDisp) co); - else if (type==ObjectLink.TYPE_DATAPOINTS) // series or data points - ; // do what?? - else if (type==ObjectLink.TYPE_DISPLAYUNITS) // - // KSC: TESTING!! Take out when done + else if (type == ObjectLink.TYPE_DATAPOINTS) // series or data points + ; // do what?? + else if (type == ObjectLink.TYPE_DISPLAYUNITS) // + // KSC: TESTING!! Take out when done // Logger.logInfo("Display Units"); - ; + ; } - try { - ((GenericChartObject) co).setParentChart(this); - } catch (Exception e) {} - i+=arrlen; - }catch(ClassCastException e) { + try { + co.setParentChart(this); + } catch (Exception e) { + } + i += arrlen; + } catch (ClassCastException e) { // it's not a defined chart object. Add it in!!! If we are missing a chart object containing other records we // will not be able to write these out correctly. Logger.logWarn("Error in parsing chart. Please add the correct object (opcode: " + b.getOpcode() + ") to be a Chart Object"); } - }else { - cobj.addChartRecord((XLSRecord)b); + } else { + cobj.addChartRecord((XLSRecord) b); } } } return cobj; } - + /** * Just a little helper method! Determines where the matching end record is for a begin record + * * @param startLoc * @param cRecs * @return */ private int getMatchingEndRecordLocation(int startLoc, BiffRec[] cRecs) { int offset = 0; - for (int i=startLoc+2;i + * Think of this kind of like the recvec & cell records in the XLS parsing we do. + *

                + * We have a pre and post array of biffrecs that are (for now) unmodifieable. + * These get appended into the whole array on ouput in append chart records. + * * @param recs */ - public void setPreRecords(AbstractList recs){ + public void setPreRecords(AbstractList recs) { preRecs = recs; } - - /** - * Returns a map of series ==> series ptgs - Ranges can either be in the format "C5" or "Sheet1!C4:D9" - */ - public HashMap getSeriesPtgs() { - return this.chartseries.getSeriesPtgs(); - } - - /** - Returns a serialized copy of this Chart - */ - public byte[] getChartBytes(){ - for(int i=0;i series ptgs + * Ranges can either be in the format "C5" or "Sheet1!C4:D9" + */ + public HashMap getSeriesPtgs() { + return this.chartseries.getSeriesPtgs(); + } + + /** + * Returns a serialized copy of this Chart + */ + public byte[] getChartBytes() { + for (int i = 0; i < preRecs.size(); i++) { + ((BiffRec) preRecs.get(i)).getData(); + } + + ObjectOutputStream obs = null; + byte[] b = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + obs = new ObjectOutputStream(baos); + obs.writeObject(this); + b = baos.toByteArray(); + } catch (IOException e) { + Logger.logErr(e); + } + return b; + } + + public byte[] getSerialBytes() { + for (int i = 0; i < preRecs.size(); i++) { + ((BiffRec) preRecs.get(i)).getData(); + } + ObjectOutputStream obs = null; + byte[] b = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BufferedOutputStream bufo = new BufferedOutputStream(baos); + obs = new ObjectOutputStream(bufo); + obs.writeObject(this); + bufo.flush(); + b = baos.toByteArray(); + } catch (IOException e) { + Logger.logErr(e); + } + return b; + } + + /** + * Gets the records for the chart, including the global references, ie the MSODrawing and Obj records + * + * @return + */ + public List getXLSrecs() { + List l = this.assembleChartRecords(); + if (obj != null) l.add(0, obj); + if (msodrawobj != null) l.add(0, msodrawobj); + return l; + } + + + /** + * Dimensions rec holds the datarange for the chart; must update ... + * + * @return + */ + public int getMinRow() { + return dimensions.getRowFirst(); + } + + public int getMaxRow() { + return dimensions.getRowLast(); + } + + public int getMinCol() { + return dimensions.getColFirst(); + } + + public int getMaxCol() { + return dimensions.getColLast(); + } + + public void setDimensionsRecord(int r0, int r1, int c0, int c1) { + dimensions.setRowFirst(r0 - 1); + dimensions.setRowLast(r1 - 1); + dimensions.setColFirst(c0); + dimensions.setColLast(c1 - 1); /* also must remove label and numberrec cached records if altered series otherwise causes errors when removing or altering series */ - for (int i= postRecs.size()-1; i > 0; i--) { - BiffRec b= (BiffRec) postRecs.get(i); - int op= b.getOpcode(); - if (op==NUMBER || op==LABEL) { - postRecs.remove(i); - } - } - } - - - public void setDimensions(Dimensions d) { dimensions= d; } - - public void setDimensionsRecord() { - Vector serieslist = this.getAllSeries(-1); - int nSeries= serieslist.size(); - int nPoints= 0; - for (int i= 0; i < serieslist.size(); i++) { - try { - Series s= (Series) serieslist.get(i); - int[] coords= ExcelTools.getRangeCoords(s.getSeriesValueAi().getDefinition()); - if (coords[3]>coords[1]) - nPoints= Math.max(nPoints, coords[3]-coords[1]+1); // c1-c0 - else - nPoints= Math.max(nPoints, coords[2]-coords[0]+1); // r1-r0 - } catch (Exception e) { } - } - this.setDimensionsRecord(0, nPoints, 0, nSeries); - } - - /** + for (int i = postRecs.size() - 1; i > 0; i--) { + BiffRec b = (BiffRec) postRecs.get(i); + int op = b.getOpcode(); + if (op == NUMBER || op == LABEL) { + postRecs.remove(i); + } + } + } + + + public void setDimensions(Dimensions d) { + dimensions = d; + } + + public void setDimensionsRecord() { + Vector serieslist = this.getAllSeries(-1); + int nSeries = serieslist.size(); + int nPoints = 0; + for (int i = 0; i < serieslist.size(); i++) { + try { + Series s = (Series) serieslist.get(i); + int[] coords = ExcelTools.getRangeCoords(s.getSeriesValueAi().getDefinition()); + if (coords[3] > coords[1]) + nPoints = Math.max(nPoints, coords[3] - coords[1] + 1); // c1-c0 + else + nPoints = Math.max(nPoints, coords[2] - coords[0] + 1); // r1-r0 + } catch (Exception e) { + } + } + this.setDimensionsRecord(0, nPoints, 0, nSeries); + } + + /** * returns the unique Obj Id # of this chart as seen in Vb macros et. al - * @return in id # + * + * @return in id # */ public int getId() { - if (this.obj!=null) - return this.obj.getObjId(); - return -1; + if (this.obj != null) + return this.obj.getObjId(); + return -1; } - + /** * sets the unique id # for this chart * used upon addChart + * * @param id */ public void setId(int id) { - if (this.obj!=null) - this.obj.setObjId(id); + if (this.obj != null) + this.obj.setObjId(id); } /** - * flags chart metrics should + * flags chart metrics should * to be recalculated */ public void setMetricsDirty() { - dirtyflag= true; - metricsDirty= true; + dirtyflag = true; + metricsDirty = true; } + /** * Change series ranges for ALL matching series + * * @param originalrange * @param newrange * @return */ - public boolean changeSeriesRange(String originalrange, String newrange){ - setMetricsDirty(); - return chartseries.changeSeriesRange(originalrange, newrange); + public boolean changeSeriesRange(String originalrange, String newrange) { + setMetricsDirty(); + return chartseries.changeSeriesRange(originalrange, newrange); } - + /** * Return a string representing all series in this chart + * * @param nChart 0=default, 1-9= overlay charts -1 for ALL charts * @return */ public String[] getSeries(int nChart) { - return chartseries.getSeries(nChart); + return chartseries.getSeries(nChart); } - + /** * Return an array of strings, one for each category + * * @param nChart 0=default, 1-9= overlay charts -1 for All * @return */ public String[] getCategories(int nChart) { - return chartseries.getCategories(nChart); + return chartseries.getCategories(nChart); } - + /** * get all the series objects for ALL charts * (i.e. even in overlay charts) + * * @return */ public Vector getAllSeries() { - return chartseries.getAllSeries(-1); + return chartseries.getAllSeries(-1); } - + /** * get all the series objects in the specified chart (-1 for ALL) + * * @param nChart 0=default, 1-9= overlay charts -1 for ALL series * @return */ public Vector getAllSeries(int nChart) { - return chartseries.getAllSeries(nChart); + return chartseries.getAllSeries(nChart); } /** * Add a series object to the array. - * @param seriesRange = one row range, expressed as (Sheet1!A1:A12); + * + * @param seriesRange = one row range, expressed as (Sheet1!A1:A12); * @param categoryRange = category range - * @param bubbleRange= bubble range, if any 20070731 KSC - * @param seriesText = label for the series; - * @param nChart 0=default, 1-9= overlay charts -s * @return + * @param bubbleRange= bubble range, if any 20070731 KSC + * @param seriesText = label for the series; + * @param nChart 0=default, 1-9= overlay charts + * s * @return */ public Series addSeries(String seriesRange, String categoryRange, String bubbleRange, String legendRange, String legendText, int nChart) { - Series s= chartseries.addSeries(seriesRange, categoryRange, bubbleRange, legendRange, legendText, chartgroup.get(nChart), nChart); + Series s = chartseries.addSeries(seriesRange, categoryRange, bubbleRange, legendRange, legendText, chartgroup.get(nChart), nChart); setMetricsDirty(); - return s; - } - + return s; + } + /** * return an array of legend text + * * @param nChart 0=default, 1-9= overlay charts -1 for ALL */ public String[] getLegends(int nChart) { - return chartseries.getLegends(nChart); + return chartseries.getLegends(nChart); } + /** * specialty method to take absoulte index of series and remove it *
                only used in WorkSheetHandle + * * @param index */ public void removeSeries(int index) { - setMetricsDirty(); - chartseries.removeSeries(index); + setMetricsDirty(); + chartseries.removeSeries(index); } - + /** * remove desired series from chart + * * @param index */ public void removeSeries(Series seriestodelete) { - // remove from cache - setMetricsDirty(); - chartseries.removeSeries(seriestodelete); - // remove from chartArray - int nSeries= -1; - for (int i=0;i0) { - DataFormat df= (DataFormat) s.chartArr.get(x); - df.setSeriesIndex(nSeries++); - } - } else // we've got 'em all - break; - } - // now make sure referenced label and number recs are removed + // remove from cache + setMetricsDirty(); + chartseries.removeSeries(seriestodelete); + // remove from chartArray + int nSeries = -1; + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b.getOpcode() == SERIES) { + nSeries++; + if (seriestodelete.equals(b)) { // this is the one to delete + chartArr.remove(i); + // now adjust series number for all subsequent Series + for (int j = i; j < chartArr.size(); j++) { + b = chartArr.get(j); + if (b.getOpcode() == SERIES) { + Series s = (Series) b; + int x = Chart.findRecPosition(s.chartArr, DataFormat.class); + if (x > 0) { + DataFormat df = (DataFormat) s.chartArr.get(x); + df.setSeriesIndex(nSeries++); + } + } else // we've got 'em all + break; + } + // now make sure referenced label and number recs are removed /** how does this work, it does not reference a particular series label, just all of them? - for (int j= 0; j < postRecs.size(); j++) { - if ((postRecs.get(j) instanceof NumberRec) || - (postRecs.get(j) instanceof Label)) { - postRecs.remove(j); - j--; - } - - } - */ - break; - } - } - } + for (int j= 0; j < postRecs.size(); j++) { + if ((postRecs.get(j) instanceof NumberRec) || + (postRecs.get(j) instanceof Label)) { + postRecs.remove(j); + j--; + } + + } + */ + break; + } + } + } } /** * get a chart series handle based off the series name + * * @param seriesName - * @param nChart 0=default, 1-9= overlay charts + * @param nChart 0=default, 1-9= overlay charts * @return */ public Series getSeries(String seriesName, int nChart) { - return chartseries.getSeries(seriesName, nChart); + return chartseries.getSeries(seriesName, nChart); } - + /** - * * @param originalrange * @param newrange * @return - */ - public boolean changeCategoryRange(String originalrange, String newrange){ - setMetricsDirty(); - return chartseries.changeCategoryRange(originalrange, newrange); + */ + public boolean changeCategoryRange(String originalrange, String newrange) { + setMetricsDirty(); + return chartseries.changeCategoryRange(originalrange, newrange); } - - + + public boolean changeTextValue(String originalval, String newval) { - setMetricsDirty(); - return chartseries.changeTextValue(originalval, newval); + setMetricsDirty(); + return chartseries.changeTextValue(originalval, newval); } + /** - * returns the plot area background color string (hex) + * returns the plot area background color string (hex) + * * @return color string */ public String getPlotAreaBgColor() { - String bg= null; - try{ - Frame f= (Frame) Chart.findRec(this.chartArr, Frame.class); - bg= f.getBgColor(); - }catch(Exception e){ - } - int ct= this.getChartType(); - if (bg==null || (ct!=PIECHART && ct!=RADARCHART)) { - bg= chartaxes.getPlotAreaBgColor(); - } - if (bg==null) - bg= "#FFFFFF"; - return bg; + String bg = null; + try { + Frame f = (Frame) Chart.findRec(this.chartArr, Frame.class); + bg = f.getBgColor(); + } catch (Exception e) { + } + int ct = this.getChartType(); + if (bg == null || (ct != PIECHART && ct != RADARCHART)) { + bg = chartaxes.getPlotAreaBgColor(); + } + if (bg == null) + bg = "#FFFFFF"; + return bg; } - + /** * sets the plot area background color - * @param bg color int + * + * @param bg color int */ - public void setPlotAreaBgColor(int bg) { - this.chartaxes.setPlotAreaBgColor(bg); - setMetricsDirty(); - } - - + public void setPlotAreaBgColor(int bg) { + this.chartaxes.setPlotAreaBgColor(bg); + setMetricsDirty(); + } + + /** - * returns the plot area line color string (hex) + * returns the plot area line color string (hex) + * * @return color string */ public String getPlotAreaLnColor() { - String clr= "#000000"; - try{ - Frame f= (Frame) Chart.findRec(this.chartArr, Frame.class); - clr= f.getLineColor(); - }catch(Exception e){ - } - return clr; - } - - /** Get the title of the Chart - * - * @return title of the Chart - */ - public String getTitle(){ - if(charttitle != null) - return charttitle.toString(); - return ""; - // getTitleTD(); - } - - /** - * return the title TextDisp element - * @return - */ - public TextDisp getTitleTd() { - return charttitle; - } - - /** - * return the textdisp object that defines the chart title - * @return - * - public TextDisp getTitleTD(){ - if(title != null){ - return title; - } - for (int i=0;i-1) - return wkbook.getFont(idx); - return null; - } - } + for (int i = 0; i < chartArr.size(); i++) { + if (((BiffRec) chartArr.get(i)).getOpcode() == DEFAULTTEXT) { + if (((DefaultText) chartArr.get(i)).getType() == 2) {// correct code???? 2 or 3 ?? + TextDisp td = (TextDisp) chartArr.get(i + 1); + int idx = td.getFontId(); + if (idx > -1) + return wkbook.getFont(idx); + return null; + } + } } return null; } + /** * Set the default font for the specific DefaultText rec + * * @param type * @param fontId */ public void setDefaultFont(int type, int fontId) { - for (int i=0;i=chartgroup.size()) // create new - cf= this.createNewChart(nChart); - else - cf= ((ChartType)chartgroup.get(nChart)).cf; - return cf; + ChartFormat cf = null; + if (nChart >= chartgroup.size()) // create new + cf = this.createNewChart(nChart); + else + cf = chartgroup.get(nChart).cf; + return cf; } - + /** * changes this chart to be a specific chart type with specific display options *
                for multiple charts, specify nChart 1-9, for the default chart, * nChart= 0 - * @param chartType chart type one of: BARCHART, LINECHART, AREACHART, COLCHART, PIECHART, - DONUGHTCHART, RADARCHART, RADARAREACHART, PYRAMIDCHART, CONECHART, CYLINDERCHART, - SURFACTCHART - * @param nChart chart # 0 is default + * + * @param chartType chart type one of: BARCHART, LINECHART, AREACHART, COLCHART, PIECHART, + * DONUGHTCHART, RADARCHART, RADARAREACHART, PYRAMIDCHART, CONECHART, CYLINDERCHART, + * SURFACTCHART + * @param nChart chart # 0 is default * @param EnumSet 0 or more chart options (Such as Stacked, Exploded ...) * @see ChartOptions */ public void setChartType(int chartType, int nChart, EnumSet options) { - GenericChartObject c; - - ChartFormat cf= getChartOjectParent(nChart); - ChartType ct= ChartType.create(chartType, this, cf); - ct.setOptions(options); - - // save and reset legend: - Legend l= this.getLegend(); - ct.addLegend(l); - - if (ct instanceof BubbleChart && options.contains(ChartOptions.THREED)) - ((BubbleChart) ct).setIs3d(true); // when set, every series created will + GenericChartObject c; + + ChartFormat cf = getChartOjectParent(nChart); + ChartType ct = ChartType.create(chartType, this, cf); + ct.setOptions(options); + + // save and reset legend: + Legend l = this.getLegend(); + ct.addLegend(l); - addChartType(ct, nChart); + if (ct instanceof BubbleChart && options.contains(ChartOptions.THREED)) + ((BubbleChart) ct).setIs3d(true); // when set, every series created will + + addChartType(ct, nChart); /* TODO: axes, other chart options ... Legend??? @@ -1050,549 +1105,602 @@ public void setChartType(int chartType, int nChart, EnumSet option } else { // ensure has proper axes // TODO: FINISH!!! IS NOT CORRECT UNLESS PROPER AXES ARE CREATED } -*/ - /** - The axis group MUST contain a series axis if and only if the chart group attached to the axis group is one of the following: - An area chart group with the fStacked field of the Area record equal to 0. - A column chart group with the fStacked field of the Bar record equal to 0 and the fClustered field of the Chart3d record equal to 0. - A line chart group with field fStacked of the Line record equal to 0. - A surface chart group - The chart group on the axis group MUST contain a Chart3d record if the axis group contains a series axis. - */ - /** - * for overlay charts i.e. multiple charts, restrictions: - * Because there are many different ways to represent data visually, each representation has specific requirements about the layout of the data and the way it is plotted. - * This results in restrictions on the combinations of chart group types that can be plotted on the same axis group, and the combinations of chart group types that can - be plotted in the same chart. - A chart MUST contain one of the following: - A single axis group that contains a single chart group that contains a Chart3d record. - One or two axis groups that each contain a single bubble chart group. - One or two axis groups that each conform to one of the following restrictions on chart group type combinations: - Zero or one of each of the following chart group types: area, column, line, and scatter. - Zero or one of each of the following chart group types: bar of pie, doughnut, pie, and pie of pie. - A single bar chart group. - A single filled radar chart group. - A single radar chart group. - - */ - } - +*/ + /** + The axis group MUST contain a series axis if and only if the chart group attached to the axis group is one of the following: + An area chart group with the fStacked field of the Area record equal to 0. + A column chart group with the fStacked field of the Bar record equal to 0 and the fClustered field of the Chart3d record equal to 0. + A line chart group with field fStacked of the Line record equal to 0. + A surface chart group + The chart group on the axis group MUST contain a Chart3d record if the axis group contains a series axis. + */ + /** + * for overlay charts i.e. multiple charts, restrictions: + * Because there are many different ways to represent data visually, each representation has specific requirements about the layout of the data and the way it is plotted. + * This results in restrictions on the combinations of chart group types that can be plotted on the same axis group, and the combinations of chart group types that can + be plotted in the same chart. + A chart MUST contain one of the following: + A single axis group that contains a single chart group that contains a Chart3d record. + One or two axis groups that each contain a single bubble chart group. + One or two axis groups that each conform to one of the following restrictions on chart group type combinations: + Zero or one of each of the following chart group types: area, column, line, and scatter. + Zero or one of each of the following chart group types: bar of pie, doughnut, pie, and pie of pie. + A single bar chart group. + A single filled radar chart group. + A single radar chart group. + + */ + } + /** - * * @return truth of "Chart is Three-D" */ - public boolean isThreeD() { return isThreeD(0); } + public boolean isThreeD() { + return isThreeD(0); + } + /** - * + * @param nChart 0=default, 1-9= overlay charts * @return truth of "Chart is Three-D" + */ + public boolean isThreeD(int nChart) { + return chartgroup.get(nChart).isThreeD(); + } + + /** + * return the 3d rec for this chart or null if it doesn't exist + * * @param nChart 0=default, 1-9= overlay charts + * @return */ - public boolean isThreeD(int nChart) { - return ((ChartType)chartgroup.get(nChart)).isThreeD(); - } - - /** - * return the 3d rec for this chart or null if it doesn't exist - * @param nChart 0=default, 1-9= overlay charts - * @return - */ - public ThreeD getThreeDRec(int nChart) { - return ((ChartType)chartgroup.get(nChart)).getThreeDRec(false); - } - - /** - * @return truth of "Chart is Stacked" - */ - public boolean isStacked() { return isStacked(0); } - /** - * @return truth of "Chart is Stacked" + public ThreeD getThreeDRec(int nChart) { + return chartgroup.get(nChart).getThreeDRec(false); + } + + /** + * @return truth of "Chart is Stacked" + */ + public boolean isStacked() { + return isStacked(0); + } + + /** * @param nChart 0=default, 1-9= overlay charts - */ - public boolean isStacked(int nChart) { - return ((ChartType)chartgroup.get(nChart)).isStacked(); - } - - /** - * return truth of "Chart is 100% Stacked" - * @return - */ - public boolean is100PercentStacked() { return is100PercentStacked(0); } - /** - * return truth of "Chart is 100% Stacked" + * @return truth of "Chart is Stacked" + */ + public boolean isStacked(int nChart) { + return chartgroup.get(nChart).isStacked(); + } + + /** + * return truth of "Chart is 100% Stacked" + * + * @return + */ + public boolean is100PercentStacked() { + return is100PercentStacked(0); + } + + /** + * return truth of "Chart is 100% Stacked" + * * @param nChart 0=default, 1-9= overlay charts - * @return - */ - public boolean is100PercentStacked(int nChart) { - return ((ChartType)chartgroup.get(nChart)).is100PercentStacked(); - } - - /** - * @return truth of "Chart is Clustered" (Bar/Col only) - */ - public boolean isClustered() { return isClustered(0); } - /** - * @return truth of "Chart is Clustered" (Bar/Col only) + * @return + */ + public boolean is100PercentStacked(int nChart) { + return chartgroup.get(nChart).is100PercentStacked(); + } + + /** + * @return truth of "Chart is Clustered" (Bar/Col only) + */ + public boolean isClustered() { + return isClustered(0); + } + + /** * @param nChart 0=default, 1-9= overlay charts - */ + * @return truth of "Chart is Clustered" (Bar/Col only) + */ public boolean isClustered(int nChart) { - return ((ChartType)chartgroup.get(nChart)).isClustered(); - } + return chartgroup.get(nChart).isClustered(); + } /** * return chart-type-specific options in XML form + * * @param nChart 0=default, 1-9= overlay charts - * @return String XML + * @return String XML */ public String getChartOptionsXML(int nChart) { - return ((ChartType)chartgroup.get(nChart)).getChartOptionsXML(); + return chartgroup.get(nChart).getChartOptionsXML(); } - - /** - * interface for setting chart-type-specific options - * in a generic fashion - * @param op option - * @param val value - * @see OpenXLS.handleChartElement - * @see ChartHandle.getXML - */ - public boolean setChartOption(String op, String val) { - dirtyflag= true; - return this.setChartOption(op, val, 0); - } - - /** - * interface for setting chart-type-specific options - * in a generic fashion - * @param op option - * @param val value - * @param nChart 0=default, 1-9= overlay charts - * @see OpenXLS.handleChartElement - * @see ChartHandle.getXML - */ - public boolean setChartOption(String op, String val, int nChart) { - dirtyflag= true; - return ((ChartType)chartgroup.get(nChart)).setChartOption(op, val); - } - - /** - * get the value of *almost* any chart option (axis options are in Axis) - * for the default chart - * - * @param op - * String option e.g. Shadow or Percentage - * @return String value of option - */ - public String getChartOption(String op) { - return ((ChartType)chartgroup.get(0)).getChartOption(op); - } - - /** - * return ThreeD settings for this chart in XML form - * @return String XML - */ - public String getThreeDXML() { return getThreeDXML(0); } - /** - * return ThreeD settings for this chart in XML form + + /** + * interface for setting chart-type-specific options + * in a generic fashion + * + * @param op option + * @param val value + * @see OpenXLS.handleChartElement + * @see ChartHandle.getXML + */ + public boolean setChartOption(String op, String val) { + dirtyflag = true; + return this.setChartOption(op, val, 0); + } + + /** + * interface for setting chart-type-specific options + * in a generic fashion + * + * @param op option + * @param val value * @param nChart 0=default, 1-9= overlay charts - * @return String XML - */ - public String getThreeDXML(int nChart) { - return ((ChartType)chartgroup.get(nChart)).getThreeDXML(); - } - - /** - * returns the 3d record of the desired chart + * @see OpenXLS.handleChartElement + * @see ChartHandle.getXML + */ + public boolean setChartOption(String op, String val, int nChart) { + dirtyflag = true; + return chartgroup.get(nChart).setChartOption(op, val); + } + + /** + * get the value of *almost* any chart option (axis options are in Axis) + * for the default chart + * + * @param op String option e.g. Shadow or Percentage + * @return String value of option + */ + public String getChartOption(String op) { + return chartgroup.get(0).getChartOption(op); + } + + /** + * return ThreeD settings for this chart in XML form + * + * @return String XML + */ + public String getThreeDXML() { + return getThreeDXML(0); + } + + /** + * return ThreeD settings for this chart in XML form + * * @param nChart 0=default, 1-9= overlay charts - * @return - */ - public ThreeD initThreeD() { return initThreeD(0, this.getChartType(0)); } - /** - * returns the 3d record of the desired chart + * @return String XML + */ + public String getThreeDXML(int nChart) { + return chartgroup.get(nChart).getThreeDXML(); + } + + /** + * returns the 3d record of the desired chart + * * @param nChart 0=default, 1-9= overlay charts + * @return + */ + public ThreeD initThreeD() { + return initThreeD(0, this.getChartType(0)); + } + + /** + * returns the 3d record of the desired chart + * + * @param nChart 0=default, 1-9= overlay charts * @param chartType one of the chart type constants - * @return - */ - public ThreeD initThreeD(int nChart, int chartType) { - return ((ChartType)chartgroup.get(nChart)).initThreeD(chartType); - } - - /** - * Return the Chart Axes object - * @return ChartAxis - */ - public ChartAxes getAxes() { - return this.chartaxes; - } - - /** - * return the Chart Series object - * @return - */ - public ChartSeries getChartSeries() { - return chartseries; - } - + * @return + */ + public ThreeD initThreeD(int nChart, int chartType) { + return chartgroup.get(nChart).initThreeD(chartType); + } + + /** + * Return the Chart Axes object + * + * @return ChartAxis + */ + public ChartAxes getAxes() { + return this.chartaxes; + } + + /** + * return the Chart Series object + * + * @return + */ + public ChartSeries getChartSeries() { + return chartseries; + } + /** * Returns true if the Y Axis (Value axis) is set to automatic scale *

                The default setting for charts is known as Automatic Scaling *
                When data changes, the chart automatically adjusts the scale (minimum, maximum values * plus major and minor tick units) as necessary + * * @return boolean true if Automatic Scaling is turned on * @see getAxisAutomaticScale(int axisType) */ - public boolean getAxisAutomaticScale(){ - return this.chartaxes.getAxisAutomaticScale(); - } + public boolean getAxisAutomaticScale() { + return this.chartaxes.getAxisAutomaticScale(); + } /** * returns the minimum and maximum values by examining all series on the chart - * @param bk - * @return double[] min, max + * + * @param bk + * @return double[] min, max */ public double[] getMinMax(WorkBookHandle wbh) { - if (metricsDirty) - getMetrics(wbh); - if (this.wbh==null) - this.wbh= wbh; - metricsDirty= false; - double[] minmaxcache= chartseries.getMetrics(metricsDirty); // Ignore Overlay charts for now! - return minmaxcache; + if (metricsDirty) + getMetrics(wbh); + if (this.wbh == null) + this.wbh = wbh; + metricsDirty = false; + double[] minmaxcache = chartseries.getMetrics(metricsDirty); // Ignore Overlay charts for now! + return minmaxcache; } + /** * Return all the Chart-specific font recs in XML form * These include the default fonts + title font + axis fonts ... + * * @return */ public String getChartFontRecsXML() { - HashMap fonts= new HashMap(); - int maxFont= 5; - for (int i=0;i"); - } + StringBuffer sb = new StringBuffer(); + for (int i = 5; i <= maxFont; i++) { + if (fonts.get(Integer.valueOf(i)) != null) { + sb.append("\n\t\t"); + } } return sb.toString(); } - - /** - * return the fontid for non-axis chart fonts (title, default ...) - * @return - */ - public String getChartFontsXML() { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec) chartArr.get(i); - if (b.getOpcode() == DEFAULTTEXT) { - sb.append(" Default" + ((DefaultText) b).getType() + "=\""); - i++; - b = (BiffRec) chartArr.get(i); - if (b.getOpcode() == TEXTDISP) { // should!! - TextDisp td = (TextDisp) b; - sb.append(td.getFontId()); - } - sb.append("\""); - } else if (b.getOpcode() == TEXTDISP) { - TextDisp td = (TextDisp) b; - if (td.isChartTitle()) { // should!! - sb.append(" Title=\""); - sb.append(td.getFontId()); - sb.append("\""); - } - } - } - return sb.toString(); - } - - /** - * Set the specific chart font (title, default ...) - * NOTE: Axis fonts are handled separately + + /** + * return the fontid for non-axis chart fonts (title, default ...) + * + * @return + */ + public String getChartFontsXML() { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b.getOpcode() == DEFAULTTEXT) { + sb.append(" Default" + ((DefaultText) b).getType() + "=\""); + i++; + b = chartArr.get(i); + if (b.getOpcode() == TEXTDISP) { // should!! + TextDisp td = (TextDisp) b; + sb.append(td.getFontId()); + } + sb.append("\""); + } else if (b.getOpcode() == TEXTDISP) { + TextDisp td = (TextDisp) b; + if (td.isChartTitle()) { // should!! + sb.append(" Title=\""); + sb.append(td.getFontId()); + sb.append("\""); + } + } + } + return sb.toString(); + } + + /** + * Set the specific chart font (title, default ...) + * NOTE: Axis fonts are handled separately + * * @param type * @param val * @see Axis.setChartOption, TextDisp.setChartOption */ public void setChartFont(String type, String val) { - if (type.equalsIgnoreCase("Title")) - for (int i=0;i -1) { - type= type.substring(7); - for (int i=0;i -1) { + type = type.substring(7); + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b.getOpcode() == DEFAULTTEXT) { + if (((DefaultText) b).getType() == Integer.parseInt(type)) { + i++; + b = chartArr.get(i); + if (b.getOpcode() == TEXTDISP) { // should be!! + TextDisp td = (TextDisp) b; + td.setFontId(Integer.parseInt(val)); + break; + } + } } - } - } - setMetricsDirty(); - } - - - /** - * set the fontId for the chart title rec - * @param fontId - */ + } + } + setMetricsDirty(); + } + + + /** + * set the fontId for the chart title rec + * + * @param fontId + */ public void setTitleFont(int fontId) { - for (int i=0;iNOTE: each series can override the default data label fo the chart - *
                can be one or more of: -
                VALUELABEL= 0x1; -
                VALUEPERCENT= 0x2; -
                CATEGORYPERCENT= 0x4; -
                CATEGORYLABEL= 0x10; -
                BUBBLELABEL= 0x20; -
                SERIESLABEL= 0x40; - * @return int default data label for chart - */ - protected int getDataLabel() { - return ((ChartType)chartgroup.get(0)).getDataLabel(); - - } - /** - * return data label options for each series as an int array - *
                each can be one or more of: -
                VALUELABEL= 0x1; -
                VALUEPERCENT= 0x2; -
                CATEGORYPERCENT= 0x4; -
                CATEGORYLABEL= 0x10; -
                BUBBLELABEL= 0x20; -
                SERIESLABEL= 0x40; - * @see AttachedLabel - * @return int array - */ - protected int[] getDataLabelsPerSeries(int defaultDL) { + } + + /** + * show or hide chart legend key + * + * @param bShow boolean show or hide + * @param vertical boolean show as vertical or horizontal + */ + public void showLegend(boolean bShow, boolean vertical) { + chartgroup.get(0).showLegend(bShow, vertical); + } + + /** + * remove the legend from the chart + */ + public void removeLegend() { + showLegend(false, false); + } + + /** + * return the Data Legend for this chart, if any + * + * @return + */ + public Legend getLegend() { + return chartgroup.get(0).getDataLegend(); + } + + /** + * return the default data label setting for the chart, if any + *
                NOTE: each series can override the default data label fo the chart + *
                can be one or more of: + *
                VALUELABEL= 0x1; + *
                VALUEPERCENT= 0x2; + *
                CATEGORYPERCENT= 0x4; + *
                CATEGORYLABEL= 0x10; + *
                BUBBLELABEL= 0x20; + *
                SERIESLABEL= 0x40; + * + * @return int default data label for chart + */ + protected int getDataLabel() { + return chartgroup.get(0).getDataLabel(); + + } + + /** + * return data label options for each series as an int array + *
                each can be one or more of: + *
                VALUELABEL= 0x1; + *
                VALUEPERCENT= 0x2; + *
                CATEGORYPERCENT= 0x4; + *
                CATEGORYLABEL= 0x10; + *
                BUBBLELABEL= 0x20; + *
                SERIESLABEL= 0x40; + * + * @return int array + * @see AttachedLabel + */ + protected int[] getDataLabelsPerSeries(int defaultDL) { /* NOTES: * * A data label is a label on a chart that is associated with a data point, or associated with a series on an area or filled radar chart group. * A data label contains information about the associated data point, such as the description of the data point, a legend key, or custom text. @@ -1610,98 +1718,105 @@ protected int[] getDataLabelsPerSeries(int defaultDL) { * The yi field of the DataFormat record MUST specify the zero-based index of the Series record associated with this series in the * collection of all Series records in the current chart sheet substream that contains the series. */ - return chartseries.getDataLabelsPerSeries(defaultDL, this.getChartType()); - } - - /** return an array of the type of markers for each series: -
                0 = no marker -
                1 = square -
                2 = diamond -
                3 = triangle -
                4 = X -
                5 = star -
                6 = Dow-Jones -
                7 = standard deviation -
                8 = circle -
                9 = plus sign - **/ - protected int[] getMarkerFormats(int nChart) { - int[] mf= chartseries.getMarkerFormats(); - for (int marker: mf) { - if (marker!=0) - return mf; - } - // see if chart format - return chartgroup.get(nChart).getMarkerFormats(); - } - - /** - * returns true if this chart has data markers (line, scatter and radar charts only) - * @return - */ - public boolean hasMarkers(int nChart) { - int[] markers= this.getMarkerFormats(nChart); - for (int marker: markers) { - if (marker!=0) - return true; - } - return false; - - } - - /** - * return truth if Chart has a data legend key showing - * @return - */ - public boolean hasDataLegend() { - return (((ChartType)chartgroup.get(0)).getDataLegend()!=null); - } - - public MSODrawing getMsodrawobj() { - return msodrawobj; - } + return chartseries.getDataLabelsPerSeries(defaultDL, this.getChartType()); + } + + /** + * return an array of the type of markers for each series: + *
                0 = no marker + *
                1 = square + *
                2 = diamond + *
                3 = triangle + *
                4 = X + *
                5 = star + *
                6 = Dow-Jones + *
                7 = standard deviation + *
                8 = circle + *
                9 = plus sign + **/ + protected int[] getMarkerFormats(int nChart) { + int[] mf = chartseries.getMarkerFormats(); + for (int marker : mf) { + if (marker != 0) + return mf; + } + // see if chart format + return chartgroup.get(nChart).getMarkerFormats(); + } + + /** + * returns true if this chart has data markers (line, scatter and radar charts only) + * + * @return + */ + public boolean hasMarkers(int nChart) { + int[] markers = this.getMarkerFormats(nChart); + for (int marker : markers) { + if (marker != 0) + return true; + } + return false; + + } + + /** + * return truth if Chart has a data legend key showing + * + * @return + */ + public boolean hasDataLegend() { + return (chartgroup.get(0).getDataLegend() != null); + } + + public MSODrawing getMsodrawobj() { + return msodrawobj; + } public Obj getObj() { return obj; } - + /** * sets bar colors to vary or not + * * @param vary * @param nChart */ public void setVaryColor(boolean vary, int nChart) { - this.getChartObject(nChart).cf.setVaryColor(vary); + this.getChartObject(nChart).cf.setVaryColor(vary); } /** * generic method to find a specific record in the list of recs in chartarr - * @param chartArr - * @param c class of record to find - * @return biffrec or null + * + * @param chartArr + * @param c class of record to find + * @return biffrec or null */ public static BiffRec findRec(ArrayList chartArr, Class c) { - for (int i= 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec)chartArr.get(i); - if (b.getClass()==c) - return b; - } - return null; + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = (BiffRec) chartArr.get(i); + if (b.getClass() == c) + return b; + } + return null; } + /** * generic method to find a specific record in the list of recs in chartArr - * @param c class of record to find - * @return position of record + * + * @param c class of record to find + * @return position of record */ public static int findRecPosition(ArrayList chartArr, Class c) { - for (int i= 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec)chartArr.get(i); - if (b.getClass()==c) - return i; - } - return -1; + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = (BiffRec) chartArr.get(i); + if (b.getClass() == c) + return i; + } + return -1; } - + // TODO: LATER // **** INCLUDE cell selection in series bars ... // chart.getSeries vs chart.getSeries(int) -- rename!!! @@ -1710,107 +1825,108 @@ public static int findRecPosition(ArrayList chartArr, Class c) { // merge minmaxcache into chartMetrics // metricsDirty - ensure all ops are covered ... // clean up Axes.getSVG w.r.t. label font, etc. - public HashMap getMetrics(WorkBookHandle wbh) { - if (metricsDirty) { - try { - this.wbh= wbh; - //chartseries.setWorkBook(wbh); - double[] minmax= chartseries.getMetrics(metricsDirty); // Ignore Overlay charts for now! - short[] coords= this.getCoords(); - chartMetrics.put("x", new Double(coords[0])); - chartMetrics.put("y", new Double(coords[1])); - chartMetrics.put("w", new Double(coords[2])); - chartMetrics.put("h", new Double(coords[3])); - chartMetrics.put("canvasw", new Double(coords[2])); - chartMetrics.put("canvash", new Double(coords[3])); - chartMetrics.put("min", minmax[0]); - chartMetrics.put("max", minmax[1]); - float[] plotcoords= null; - plotcoords = this.getPlotAreaCoords(chartMetrics.get("w").floatValue(), chartMetrics.get("h").floatValue()); - if (plotcoords==null) { - CrtLayout12A crt = (CrtLayout12A) Chart.findRec(this.chartArr, CrtLayout12A.class); - if (crt != null) - plotcoords= crt.getInnerPlotCoords(chartMetrics.get("w").floatValue(), chartMetrics.get("h").floatValue()); - } - - chartMetrics.put("x", new Double(plotcoords[0])); - chartMetrics.put("y", new Double(plotcoords[1])); - chartMetrics.put("w", new Double(plotcoords[2])); - chartMetrics.put("h", new Double(plotcoords[3])); - // Chart title offset - io.starter.formats.XLS.Font titlefont= this.getTitleFont(); - if (titlefont != null && !this.getTitle().equals("")) { // apparently can still have td even when no title is present ... - float[] tdcoords= this.charttitle.getCoords(); - double fh= titlefont.getFontHeightInPoints(); - if (tdcoords[1]==0) { - chartMetrics.put("TITLEOFFSET", Math.ceil(fh*1.5)); // with padding - } else { - chartMetrics.put("TITLEOFFSET", fh); // a little padding - } - } else if (chartMetrics.get("y") < 5.0) - chartMetrics.put("TITLEOFFSET", 10.0); // no title offset - a little padding - else - chartMetrics.put("TITLEOFFSET", 0.0); // no title offset and no need for padding - this.getAxes().getMetrics(this.getChartType(), chartMetrics, plotcoords, this.getChartSeries().getCategories()); - int[] lcoords= null; - double adjust= 10; - if (this.getLegend()!=null) { - this.getLegend().getMetrics(chartMetrics, this.getChartType(), this.getChartSeries()); - lcoords= this.getLegend().getCoords(); - if (lcoords!=null) - // TODO: legend adjustment may have to do with y title and label ofsets ...? -{ adjust= 2*lcoords[4]; // spacing before and after legend box TODO this isn't correct !! + public HashMap getMetrics(WorkBookHandle wbh) { + if (metricsDirty) { + try { + this.wbh = wbh; + //chartseries.setWorkBook(wbh); + double[] minmax = chartseries.getMetrics(metricsDirty); // Ignore Overlay charts for now! + short[] coords = this.getCoords(); + chartMetrics.put("x", new Double(coords[0])); + chartMetrics.put("y", new Double(coords[1])); + chartMetrics.put("w", new Double(coords[2])); + chartMetrics.put("h", new Double(coords[3])); + chartMetrics.put("canvasw", new Double(coords[2])); + chartMetrics.put("canvash", new Double(coords[3])); + chartMetrics.put("min", minmax[0]); + chartMetrics.put("max", minmax[1]); + float[] plotcoords = null; + plotcoords = this.getPlotAreaCoords(chartMetrics.get("w").floatValue(), chartMetrics.get("h").floatValue()); + if (plotcoords == null) { + CrtLayout12A crt = (CrtLayout12A) Chart.findRec(this.chartArr, CrtLayout12A.class); + if (crt != null) + plotcoords = crt.getInnerPlotCoords(chartMetrics.get("w").floatValue(), chartMetrics.get("h").floatValue()); + } + + chartMetrics.put("x", new Double(plotcoords[0])); + chartMetrics.put("y", new Double(plotcoords[1])); + chartMetrics.put("w", new Double(plotcoords[2])); + chartMetrics.put("h", new Double(plotcoords[3])); + // Chart title offset + io.starter.formats.XLS.Font titlefont = this.getTitleFont(); + if (titlefont != null && !this.getTitle().equals("")) { // apparently can still have td even when no title is present ... + float[] tdcoords = this.charttitle.getCoords(); + double fh = titlefont.getFontHeightInPoints(); + if (tdcoords[1] == 0) { + chartMetrics.put("TITLEOFFSET", Math.ceil(fh * 1.5)); // with padding + } else { + chartMetrics.put("TITLEOFFSET", fh); // a little padding + } + } else if (chartMetrics.get("y") < 5.0) + chartMetrics.put("TITLEOFFSET", 10.0); // no title offset - a little padding + else + chartMetrics.put("TITLEOFFSET", 0.0); // no title offset and no need for padding + this.getAxes().getMetrics(this.getChartType(), chartMetrics, plotcoords, this.getChartSeries().getCategories()); + int[] lcoords = null; + double adjust = 10; + if (this.getLegend() != null) { + this.getLegend().getMetrics(chartMetrics, this.getChartType(), this.getChartSeries()); + lcoords = this.getLegend().getCoords(); + if (lcoords != null) + // TODO: legend adjustment may have to do with y title and label ofsets ...? + { + adjust = 2 * lcoords[4]; // spacing before and after legend box TODO this isn't correct !! //KSC: TESTING! //io.starter.toolkit.Logger.log("Original lcoords: " + Arrays.toString(lcoords)); -} else { - lcoords= new int[6]; - lcoords[0]= chartMetrics.get("canvasw").intValue(); - } - } else { - lcoords= new int[6]; - lcoords[0]= chartMetrics.get("canvasw").intValue(); - } - double ldist= lcoords[0]-chartMetrics.get("w"); // save distance between legend box and w (significant if legend is on rhs) + } else { + lcoords = new int[6]; + lcoords[0] = chartMetrics.get("canvasw").intValue(); + } + } else { + lcoords = new int[6]; + lcoords[0] = chartMetrics.get("canvasw").intValue(); + } + double ldist = lcoords[0] - chartMetrics.get("w"); // save distance between legend box and w (significant if legend is on rhs) //io.starter.toolkit.Logger.log("Before Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); - // now adjust plot area coordinates based on canvas w, h, title and label offsets, and legend box, if any - chartMetrics.put("x", (Double)chartMetrics.get("x") + (Double) this.getAxes().axisMetrics.get("YAXISLABELOFFSET") + (Double) this.getAxes().axisMetrics.get("YAXISTITLEOFFSET")); - chartMetrics.put("y", (Double) chartMetrics.get("y") + chartMetrics.get("TITLEOFFSET")); - // TODO: seems that w is different doesn't need decrementing by x?? check out ... - chartMetrics.put("w", (Double)chartMetrics.get("w") - (Double) this.getAxes().axisMetrics.get("YAXISLABELOFFSET")); - chartMetrics.put("h", (Double) chartMetrics.get("canvash")- (Double)chartMetrics.get("y") - (Double) this.getAxes().axisMetrics.get("XAXISLABELOFFSET") - (Double) this.getAxes().axisMetrics.get("XAXISTITLEOFFSET") - 10); + // now adjust plot area coordinates based on canvas w, h, title and label offsets, and legend box, if any + chartMetrics.put("x", chartMetrics.get("x") + (Double) this.getAxes().axisMetrics.get("YAXISLABELOFFSET") + (Double) this.getAxes().axisMetrics.get("YAXISTITLEOFFSET")); + chartMetrics.put("y", chartMetrics.get("y") + chartMetrics.get("TITLEOFFSET")); + // TODO: seems that w is different doesn't need decrementing by x?? check out ... + chartMetrics.put("w", chartMetrics.get("w") - (Double) this.getAxes().axisMetrics.get("YAXISLABELOFFSET")); + chartMetrics.put("h", chartMetrics.get("canvash") - chartMetrics.get("y") - (Double) this.getAxes().axisMetrics.get("XAXISLABELOFFSET") - (Double) this.getAxes().axisMetrics.get("XAXISTITLEOFFSET") - 10); //io.starter.toolkit.Logger.log("After Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); - - double cw= chartMetrics.get("canvasw"); - // rhs legend has to have some extra adjustments to w and/or canvasw ... - if (lcoords[5]==Legend.RIGHT) { - double legendBeg= lcoords[0]-(chartMetrics.get("w")+chartMetrics.get("x")); - double legendEnd= cw-(lcoords[0]+lcoords[2]+adjust); - - if (legendBeg < 0 || legendEnd < 0) { // try to adjust - if (legendEnd < 0) { - chartMetrics.put("canvasw", (lcoords[0]+lcoords[2]+adjust)); - cw= (lcoords[0]+lcoords[2]+adjust); - } + + double cw = chartMetrics.get("canvasw"); + // rhs legend has to have some extra adjustments to w and/or canvasw ... + if (lcoords[5] == Legend.RIGHT) { + double legendBeg = lcoords[0] - (chartMetrics.get("w") + chartMetrics.get("x")); + double legendEnd = cw - (lcoords[0] + lcoords[2] + adjust); + + if (legendBeg < 0 || legendEnd < 0) { // try to adjust + if (legendEnd < 0) { + chartMetrics.put("canvasw", (lcoords[0] + lcoords[2] + adjust)); + cw = (lcoords[0] + lcoords[2] + adjust); + } // if (legendBeg < 0) // chartMetrics.put("w", lcoords[0]-10.0-chartMetrics.get("x")); - } - if (this.getAxes().hasAxis(XAXIS) && ldist > 0) // pie, donut, don't - // ensure distance between legend box and edge of plot area remains the same - chartMetrics.put("w", lcoords[0]-(Double)chartMetrics.get("x")-ldist); + } + if (this.getAxes().hasAxis(XAXIS) && ldist > 0) // pie, donut, don't + // ensure distance between legend box and edge of plot area remains the same + chartMetrics.put("w", lcoords[0] - chartMetrics.get("x") - ldist); //io.starter.toolkit.Logger.log("Adjusted LCoords: " + Arrays.toString(lcoords)); - - } else { - double w= chartMetrics.get("w")+chartMetrics.get("x"); - if (w > cw) - chartMetrics.put("w", (Double)chartMetrics.get("canvasw")-(Double) chartMetrics.get("x")-10); - } - - metricsDirty= false; - } catch (Exception e) { - Logger.logErr("Chart.getMetrics: " + e.toString()); - } - } - return chartMetrics; - } + + } else { + double w = chartMetrics.get("w") + chartMetrics.get("x"); + if (w > cw) + chartMetrics.put("w", chartMetrics.get("canvasw") - chartMetrics.get("x") - 10); + } + + metricsDirty = false; + } catch (Exception e) { + Logger.logErr("Chart.getMetrics: " + e.toString()); + } + } + return chartMetrics; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.java b/src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.java index 247a8b3..9ab5fe3 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.java +++ b/src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,66 +22,66 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; /** * Chart3DBarShape - * This record specifies the shape of the data points in a bar or column chart group. + * This record specifies the shape of the data points in a bar or column chart group. * This record is used only for a bar or column chart group and MUST be ignored for all other chart group - * - * 1 1 riser specifies the shape of the base of the data points in a bar or column chart group - * 0 =base is a rectangle. 1 =base is an ellipse - * 2 1 taper specifies how the data points in a bar or column chart group taper from base to tip. - * 0= no taper - * 1= The data points of the bar or column chart group taper to a point at the maximum value of each data point - * 2= he data points of the bar or column chart group taper towards a projected point at the position of the maximum value of all data points in the chart group, but are clipped at the value of each data point. - * + *

                + * 1 1 riser specifies the shape of the base of the data points in a bar or column chart group + * 0 =base is a rectangle. 1 =base is an ellipse + * 2 1 taper specifies how the data points in a bar or column chart group taper from base to tip. + * 0= no taper + * 1= The data points of the bar or column chart group taper to a point at the maximum value of each data point + * 2= he data points of the bar or column chart group taper towards a projected point at the position of the maximum value of all data points in the chart group, but are clipped at the value of each data point. */ public class Chart3DBarShape extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3029030180040933082L; - byte riser, taper; - public void init() { - super.init(); - riser= this.getByteAt(0); - taper= this.getByteAt(1); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 3029030180040933082L; + byte riser, taper; - public Chart3DBarShape() { - setData(new byte[2]); - } - - /** Bar Shapes - public static final int SHAPECOLUMN= 0; // default - public static final int SHAPECYLINDER= 1; - public static final int SHAPEPYRAMID= 256; - public static final int SHAPECONE= 257; - public static final int SHAPEPYRAMIDTOMAX= 516; - public static final int SHAPECONETOMAX= 517; - */ - public short getShape() { - return ByteTools.readShort(riser, taper); - } - - /** - * set the shape of the bars - *
                the shape is as follows: - public static final int SHAPECOLUMN= 0; // default - public static final int SHAPECYLINDER= 1; - public static final int SHAPEPYRAMID= 256; - public static final int SHAPECONE= 257; - public static final int SHAPEPYRAMIDTOMAX= 516; - public static final int SHAPECONETOMAX= 517; - */ - public void setShape(short shape) { + public void init() { + super.init(); + riser = this.getByteAt(0); + taper = this.getByteAt(1); + } + + public Chart3DBarShape() { + setData(new byte[2]); + } + + /** + * Bar Shapes + * public static final int SHAPECOLUMN= 0; // default + * public static final int SHAPECYLINDER= 1; + * public static final int SHAPEPYRAMID= 256; + * public static final int SHAPECONE= 257; + * public static final int SHAPEPYRAMIDTOMAX= 516; + * public static final int SHAPECONETOMAX= 517; + */ + public short getShape() { + return ByteTools.readShort(riser, taper); + } + + /** + * set the shape of the bars + *
                the shape is as follows: + * public static final int SHAPECOLUMN= 0; // default + * public static final int SHAPECYLINDER= 1; + * public static final int SHAPEPYRAMID= 256; + * public static final int SHAPECONE= 257; + * public static final int SHAPEPYRAMIDTOMAX= 516; + * public static final int SHAPECONETOMAX= 517; + */ + public void setShape(short shape) { byte[] b = ByteTools.shortToLEBytes(shape); this.getData()[0] = b[0]; this.getData()[1] = b[1]; - riser= this.getByteAt(0); - taper= this.getByteAt(1); - } - + riser = this.getByteAt(0); + taper = this.getByteAt(1); + } + } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartAxes.java b/src/main/java/io/starter/formats/XLS/charts/ChartAxes.java index 7f33f28..0e00896 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartAxes.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartAxes.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,728 +22,769 @@ */ package io.starter.formats.XLS.charts; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Stack; -import java.util.concurrent.atomic.AtomicInteger; - -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.Font; import io.starter.formats.cellformat.CellFormatFactory; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; +import org.json.JSONObject; +import org.xmlpull.v1.XmlPullParser; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Stack; +import java.util.concurrent.atomic.AtomicInteger; /** - * An axis group is a set of axes that specify a coordinate system, a set of chart groups that are plotted using these and the plot area that defines - where the axes are rendered on the chart. - - In BIFF8, the AxisParent record governs the Axis Group. A typical arrangement of records is: - AxisParent - Pos - Axis (X) - Axis (Y) - PlotArea - Frame - (Chart Group is one or more of:) - ChartFormat (defines the chart type) + Legend - * + * An axis group is a set of axes that specify a coordinate system, a set of chart groups that are plotted using these and the plot area that defines + * where the axes are rendered on the chart. + *

                + * In BIFF8, the AxisParent record governs the Axis Group. A typical arrangement of records is: + * AxisParent + * Pos + * Axis (X) + * Axis (Y) + * PlotArea + * Frame + * (Chart Group is one or more of:) + * ChartFormat (defines the chart type) + Legend */ public class ChartAxes implements ChartConstants, Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7862828186455339066L; - private AxisParent ap; - ArrayList axes= new ArrayList(); - HashMap axisMetrics= new HashMap(); // holds important axis metrics such as xAxisReversed, xPattern, for outside use + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7862828186455339066L; + private AxisParent ap; + ArrayList axes = new ArrayList(); + HashMap axisMetrics = new HashMap(); // holds important axis metrics such as xAxisReversed, xPattern, for outside use /** * The AxisParent record specifies if the axis group is the primary axis group or the secondary axis group on a chart. * Often the axes of the primary axis group are displayed to the left and bottom sides of the plot area, while axes of the secondary axis group are displayed on the right and top sides of the plot area. - The Pos record specifies the position and size of the outer plot area. The outer plot area is the bounding rectangle that includes the axis labels, the axis titles, - and data table of the chart. This record MUST be ignored on a secondary axis group. */ - /** The PlotArea record and the sequence of records that conforms to the FRAME rule in the sequence of records that conform to the AXES rule specify the properties of the inner plot area. The inner plot area is the rectangle bounded by the chart axes. - * The PlotArea record MUST not exist on a secondary axis group. - */ - /** - * @param ap - */ - public ChartAxes(AxisParent ap) { - this.ap= ap; - } - - /** - * store each axis - * @param a - */ - public void add(Axis a) { - axes.add(a); - a.setAP(this.ap); // ensure axis is linked to it's parent AxisParent - } - - public void setTd(int axisType, TextDisp td) { - Axis a= getAxis(axisType, false); - if (a!=null) // shouldn't - a.setTd(td); - } - - /** - * returns true if axisType is found on chart - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return - */ - public boolean hasAxis(int axisType) { - for (int i= 0; i < axes.size(); i++) { - if (((Axis) axes.get(i)).getAxis()==axisType) - return true; - } - return false; - } - - public void createAxis(int axisType) { - Axis a= getAxis(axisType, true); - return; - } - - /** - * Return the desired axis - * @return Axis - * - public Axis getAxis(int axisType) { - return ap.getAxis(axisType); - }*/ - - /** - * returns the desired axis if it exists - * if bCreateIfNecessary, will create if it doesn't exist - * otherwise returns null - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param bCreateIfNecessary - * @return - */ - private Axis getAxis(int axisType, boolean bCreateIfNecessary) { - for (int i= 0; i < axes.size(); i++) { - if (((Axis) axes.get(i)).getAxis()==axisType) - return ((Axis) axes.get(i)); - } - if (bCreateIfNecessary) { - axes.add(ap.getAxis(axisType, bCreateIfNecessary)); - return (Axis)axes.get(axes.size()-1); - } + The Pos record specifies the position and size of the outer plot area. The outer plot area is the bounding rectangle that includes the axis labels, the axis titles, + and data table of the chart. This record MUST be ignored on a secondary axis group. */ + /** The PlotArea record and the sequence of records that conforms to the FRAME rule in the sequence of records that conform to the AXES rule specify the properties of the inner plot area. The inner plot area is the rectangle bounded by the chart axes. + * The PlotArea record MUST not exist on a secondary axis group. + */ + /** + * @param ap + */ + public ChartAxes(AxisParent ap) { + this.ap = ap; + } + + /** + * store each axis + * + * @param a + */ + public void add(Axis a) { + axes.add(a); + a.setAP(this.ap); // ensure axis is linked to it's parent AxisParent + } + + public void setTd(int axisType, TextDisp td) { + Axis a = getAxis(axisType, false); + if (a != null) // shouldn't + a.setTd(td); + } + + /** + * returns true if axisType is found on chart + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return + */ + public boolean hasAxis(int axisType) { + for (int i = 0; i < axes.size(); i++) { + if (((Axis) axes.get(i)).getAxis() == axisType) + return true; + } + return false; + } + + public void createAxis(int axisType) { + Axis a = getAxis(axisType, true); + return; + } + + /** + * Return the desired axis + * @return Axis + * + public Axis getAxis(int axisType) { + return ap.getAxis(axisType); + }*/ + + /** + * returns the desired axis if it exists + * if bCreateIfNecessary, will create if it doesn't exist + * otherwise returns null + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param bCreateIfNecessary + * @return + */ + private Axis getAxis(int axisType, boolean bCreateIfNecessary) { + for (int i = 0; i < axes.size(); i++) { + if (((Axis) axes.get(i)).getAxis() == axisType) + return ((Axis) axes.get(i)); + } + if (bCreateIfNecessary) { + axes.add(ap.getAxis(axisType, bCreateIfNecessary)); + return (Axis) axes.get(axes.size() - 1); + } return null; - } - - /** - * returns true if Axis is reversed - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return - */ - protected boolean isReversed(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) - return a.isReversed(); - return false; - } - - /** - * returns the number format for the desired axis - * @param axisType - * @return number format string - */ - protected String getNumberFormat(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) - return a.getNumberFormat(); - return null; - } - - /** - * remove ALL axes from this chart - */ - public void removeAxes() { - ap.removeAxes(); - while (axes.size()>0) { - axes.remove(0); - } - } - - /** - * remove the desired axis + associated records - * @param axisType one of: XAXIS, YAXIS, ZAXIS - */ - public void removeAxis(int axisType) { - ap.removeAxis(axisType); - for (int i= 0; i < axes.size(); i++) { - if (((Axis) axes.get(i)).getAxis()==axisType) { - axes.remove(i); - break; - } - } - } - - /** - * return the coordinates of the outer plot area (plot + axes + labels) in pixels - * @return - */ - public float[] getPlotAreaCoords(float w, float h) { - Pos p= (Pos) Chart.findRec(ap.chartArr, Pos.class); - if (p!=null) { - float[] plotcoords= p.getCoords(); - if (plotcoords!=null) { - plotcoords[0]= Pos.convertFromSPRC(plotcoords[0], w, 0); - plotcoords[1]= Pos.convertFromSPRC(plotcoords[1], 0, h); - plotcoords[2]= Pos.convertFromSPRC(plotcoords[2], w, 0); // SPRC units see Pos - plotcoords[3]= Pos.convertFromSPRC(plotcoords[3], 0, h); // SPRC units see Pos - return plotcoords; - } - } - return null; - } - - /** - * returns the axis area background color hex string, or null if not set - * @return color hex string - */ - public String getPlotAreaBgColor() { - return ap.getPlotAreaBgColor(); - } - - - /** - * sets the plot area background color - * @param bg color int - */ - public void setPlotAreaBgColor(int bg) { - ap.setPlotAreaBgColor(bg); - } - - /** - * adds a border around the plot area with the desired line width and line color - * @param lw - * @param lclr - */ - public void setPlotAreaBorder(int lw, int lclr) { - ap.setPlotAreaBorder(lw, lclr); - } - - - - /** - * obtain the desired axis' label and other options, if present, in XML form - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return String XML representation of desired axis' label and options - * @see ObjectLink - */ - public String getAxisOptionsXML(int axisType) { - return ap.getAxisOptionsXML(axisType); - } - - /** Return the Axis Title, or "" if none - * @param axisType one of: XAXIS, YAXIS, ZAXIS - */ - public String getTitle(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) - return a.getTitle(); - return ""; - } - - /** - * set the axis title string - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param Title - */ - public void setTitle(int axisType, String Title) { - Axis a= getAxis(axisType, false); - if (a!=null) { - boolean defaultIsVert= (axisType==YAXIS && ("".equals(a.getTitle()))); - a.setTitle(Title); - if (defaultIsVert) a.getTd().setRotation(90); - } - } - + } + + /** + * returns true if Axis is reversed + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return + */ + protected boolean isReversed(int axisType) { + Axis a = getAxis(axisType, false); + if (a != null) + return a.isReversed(); + return false; + } + + /** + * returns the number format for the desired axis + * + * @param axisType + * @return number format string + */ + protected String getNumberFormat(int axisType) { + Axis a = getAxis(axisType, false); + if (a != null) + return a.getNumberFormat(); + return null; + } + + /** + * remove ALL axes from this chart + */ + public void removeAxes() { + ap.removeAxes(); + while (axes.size() > 0) { + axes.remove(0); + } + } + + /** + * remove the desired axis + associated records + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + */ + public void removeAxis(int axisType) { + ap.removeAxis(axisType); + for (int i = 0; i < axes.size(); i++) { + if (((Axis) axes.get(i)).getAxis() == axisType) { + axes.remove(i); + break; + } + } + } + + /** + * return the coordinates of the outer plot area (plot + axes + labels) in pixels + * + * @return + */ + public float[] getPlotAreaCoords(float w, float h) { + Pos p = (Pos) Chart.findRec(ap.chartArr, Pos.class); + if (p != null) { + float[] plotcoords = p.getCoords(); + if (plotcoords != null) { + plotcoords[0] = Pos.convertFromSPRC(plotcoords[0], w, 0); + plotcoords[1] = Pos.convertFromSPRC(plotcoords[1], 0, h); + plotcoords[2] = Pos.convertFromSPRC(plotcoords[2], w, 0); // SPRC units see Pos + plotcoords[3] = Pos.convertFromSPRC(plotcoords[3], 0, h); // SPRC units see Pos + return plotcoords; + } + } + return null; + } + + /** + * returns the axis area background color hex string, or null if not set + * + * @return color hex string + */ + public String getPlotAreaBgColor() { + return ap.getPlotAreaBgColor(); + } + + + /** + * sets the plot area background color + * + * @param bg color int + */ + public void setPlotAreaBgColor(int bg) { + ap.setPlotAreaBgColor(bg); + } + + /** + * adds a border around the plot area with the desired line width and line color + * + * @param lw + * @param lclr + */ + public void setPlotAreaBorder(int lw, int lclr) { + ap.setPlotAreaBorder(lw, lclr); + } + + + /** + * obtain the desired axis' label and other options, if present, in XML form + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return String XML representation of desired axis' label and options + * @see ObjectLink + */ + public String getAxisOptionsXML(int axisType) { + return ap.getAxisOptionsXML(axisType); + } + + /** + * Return the Axis Title, or "" if none + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + */ + public String getTitle(int axisType) { + Axis a = getAxis(axisType, false); + if (a != null) + return a.getTitle(); + return ""; + } + + /** + * set the axis title string + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param Title + */ + public void setTitle(int axisType, String Title) { + Axis a = getAxis(axisType, false); + if (a != null) { + boolean defaultIsVert = (axisType == YAXIS && ("".equals(a.getTitle()))); + a.setTitle(Title); + if (defaultIsVert) a.getTd().setRotation(90); + } + } + /** * return the rotation of the axis labels, if any - 0 to 90 Text rotated 0 to 90 degrees counter-clockwise - 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) - 255 Text top-to-bottom with letters upright - * @param axisType one of: XAXIS, YAXIS, ZAXIS - */ - public int getLabelRotation(int axisType) { - int rot= 0; - Axis a= getAxis(axisType, false); - if (a!=null) { - Tick t= (Tick) Chart.findRec(a.chartArr, Tick.class); - if (t!=null) { // shoudn't - rot= t.getRotation(); - switch (rot) { - case 0: - break; - case 1: // not correct ... - rot= 180; - break; - case 2: - rot= -90; - break; - case 3: - rot= 90; - break; - } - } - } - return rot; - } + * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise + * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) + * 255 Text top-to-bottom with letters upright + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + */ + public int getLabelRotation(int axisType) { + int rot = 0; + Axis a = getAxis(axisType, false); + if (a != null) { + Tick t = (Tick) Chart.findRec(a.chartArr, Tick.class); + if (t != null) { // shoudn't + rot = t.getRotation(); + switch (rot) { + case 0: + break; + case 1: // not correct ... + rot = 180; + break; + case 2: + rot = -90; + break; + case 3: + rot = 90; + break; + } + } + } + return rot; + } + /** * return the rotation of the axis Title, if any - 0 to 90 Text rotated 0 to 90 degrees counter-clockwise - 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) - 255 Text top-to-bottom with letters upright - * @param axisType one of: XAXIS, YAXIS, ZAXIS - */ - public int getTitleRotation(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) { - TextDisp td= a.getTd(); - return td.getRotation(); - } - return 0; - } - + * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise + * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) + * 255 Text top-to-bottom with letters upright + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + */ + public int getTitleRotation(int axisType) { + Axis a = getAxis(axisType, false); + if (a != null) { + TextDisp td = a.getTd(); + return td.getRotation(); + } + return 0; + } + /** * return the coordinates, in pixels, of the title text area, if possible - * @param axisType one of: XAXIS, YAXIS, ZAXIS + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @return float[] */ - public float[] getCoords(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) { - TextDisp td= a.getTd(); - return td.getCoords(); - } - return new float[] {0,0,0,0}; - } - - /** - * Returns the YAXIS scale elements (min value, max value, minor (tick) scale, major (tick) scale) - * the scale elements are calculated from the minimum and maximum values on the chart - * @param ymin the minimum value of all the series values - * @param ymax the maximum value of all the series values - * @return double[] min, max, minor, major - */ - public double[] getMinMax(double ymin, double ymax) { - return getMinMax(ymin, ymax, YAXIS); - } - - /** Returns the scale values of the the desired Value axis + public float[] getCoords(int axisType) { + Axis a = getAxis(axisType, false); + if (a != null) { + TextDisp td = a.getTd(); + return td.getCoords(); + } + return new float[]{0, 0, 0, 0}; + } + + /** + * Returns the YAXIS scale elements (min value, max value, minor (tick) scale, major (tick) scale) + * the scale elements are calculated from the minimum and maximum values on the chart + * + * @param ymin the minimum value of all the series values + * @param ymax the maximum value of all the series values + * @return double[] min, max, minor, major + */ + public double[] getMinMax(double ymin, double ymax) { + return getMinMax(ymin, ymax, YAXIS); + } + + /** + * Returns the scale values of the the desired Value axis *
                Scale elements: (min value, max value, minor (tick) scale, major (tick) scale) - *

                The Value axis contains numbers rather than labels, and is normally the Y axis, + *

                The Value axis contains numbers rather than labels, and is normally the Y axis, * but Scatter and Bubble charts may have a value axis on the X Axis as well + * * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, ChartHandle.ZAXIS * @return int Miminum Scale value for the desired axis * @see getAxisMinScale() */ public double[] getMinMax(double ymin, double ymax, int axisType) { - Axis a= getAxis(axisType, false); - double[] ret= new double[4]; - if (a!=null) { - ValueRange v = (ValueRange) Chart.findRec(a.getChartRecords(), ValueRange.class); - if (v.isAutomaticScale()) // major crazy Excel "automatic max/min/tickmarks calc" ... a monster - v.setMaxMin(ymax, ymin); - ret[0]= v.getMin(); - ret[1]= v.getMax(); - ret[2]= v.getMinorTick(); - ret[3]= v.getMajorTick(); - } - return ret; - } - + Axis a = getAxis(axisType, false); + double[] ret = new double[4]; + if (a != null) { + ValueRange v = (ValueRange) Chart.findRec(a.getChartRecords(), ValueRange.class); + if (v.isAutomaticScale()) // major crazy Excel "automatic max/min/tickmarks calc" ... a monster + v.setMaxMin(ymax, ymin); + ret[0] = v.getMin(); + ret[1] = v.getMax(); + ret[2] = v.getMinorTick(); + ret[3] = v.getMajorTick(); + } + return ret; + } + /** * Returns the major tick unit of the desired Value axis - *

                The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well - * @param axisType one of: XAXIS, YAXIS, ZAXIS + *

                The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @return int major tick unit * @see getAxisMajorUnit() */ public int getAxisMajorUnit(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) - return a.getMajorUnit(); - return 10; // TODO throw exception instead ??? + Axis a = getAxis(axisType, false); + if (a != null) + return a.getMajorUnit(); + return 10; // TODO throw exception instead ??? } + /** * Returns the minor tick unit of the desired Value axis - *

                The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well - * @param axisType one of: XAXIS, YAXIS, ZAXIS + *

                The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @return int - minor tick unit of the desired axis * @see getAxisMinorUnit() */ public int getAxisMinorUnit(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) - return a.getMinorUnit(); - return 1; // TODO throw exception instead + Axis a = getAxis(axisType, false); + if (a != null) + return a.getMinorUnit(); + return 1; // TODO throw exception instead } /** * Sets the maximum scale value of the desired Value axis - *

                The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well + *

                The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well *

                Note: The default scale setting for charts is known as Automatic Scaling *
                When data changes, the chart automatically adjusts the scale as necessary - *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param int MaxValue - desired maximum value of the desired axis + *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param int MaxValue - desired maximum value of the desired axis * @see setAxisMax(int MaxValue) */ public void setAxisMax(int axisType, int MaxValue) { - Axis a= getAxis(axisType, false); - if (a!=null) - a.setMaxScale(MaxValue); - // TODO: throw exception if no axis? - } + Axis a = getAxis(axisType, false); + if (a != null) + a.setMaxScale(MaxValue); + // TODO: throw exception if no axis? + } + /** * Sets the minimum scale value of the desired Value axis - *

                The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well + *

                The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well *

                Note: The default setting for charts is known as Automatic Scaling *
                When data values change, the chart automatically adjusts the scale as necessary - *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param int MinValue - the desired Minimum scale value + *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param int MinValue - the desired Minimum scale value * @see setAxisMin(int MinValue) */ public void setAxisMin(int axisType, int MinValue) { - Axis a= getAxis(axisType, false); - if (a!=null) - a.setMinScale(MinValue); - // TODO: throw exception if no axis? - } + Axis a = getAxis(axisType, false); + if (a != null) + a.setMinScale(MinValue); + // TODO: throw exception if no axis? + } /** * Sets the automatic scale option on or off for the desired Value axis - *

                The Value axis contains numbers rather than labels, and is normally the Y axis, + *

                The Value axis contains numbers rather than labels, and is normally the Y axis, * but Scatter and Bubble charts may have a value axis on the X Axis as well *

                Automatic Scaling automatically sets the scale maximum, minimum and tick units * upon data changes, and is the default setting for charts - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param boolean b - true if set Automatic scaling on, false otherwise + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param boolean b - true if set Automatic scaling on, false otherwise * @see setAxisAutomaticScale(boolean b) */ public void setAxisAutomaticScale(int axisType, boolean b) { - Axis a= getAxis(axisType, false); - if (a!=null) - a.setAutomaticScale(b); + Axis a = getAxis(axisType, false); + if (a != null) + a.setAutomaticScale(b); } - + /** * Returns true if the desired Value axis is set to automatic scale - *

                The Value axis contains numbers rather than labels, and is normally the Y axis, + *

                The Value axis contains numbers rather than labels, and is normally the Y axis, * but Scatter and Bubble charts may have a value axis on the X Axis as well *

                Note: The default setting for charts is known as Automatic Scaling *
                When data changes, the chart automatically adjusts the scale as necessary - *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling - * @param axisType one of: XAXIS, YAXIS, ZAXIS + *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @return boolean true if Automatic Scaling is turned on * @see getAxisAutomaticScale() */ - public boolean getAxisAutomaticScale(int axisType){ - Axis a= getAxis(axisType, false); - if (a!=null) - return a.isAutomaticScale(); // group min and max together ... - return false; // TODO: throw exception? + public boolean getAxisAutomaticScale(int axisType) { + Axis a = getAxis(axisType, false); + if (a != null) + return a.isAutomaticScale(); // group min and max together ... + return false; // TODO: throw exception? } + /** * Returns true if the Y Axis (Value axis) is set to automatic scale *

                The default setting for charts is known as Automatic Scaling *
                When data changes, the chart automatically adjusts the scale (minimum, maximum values * plus major and minor tick units) as necessary + * * @return boolean true if Automatic Scaling is turned on * @see getAxisAutomaticScale(int axisType) */ - public boolean getAxisAutomaticScale(){ - Axis a= getAxis(YAXIS, false); - if (a!=null) - return a.isAutomaticScale(); // group min and max together ... - return false; // TODO: throw exception? + public boolean getAxisAutomaticScale() { + Axis a = getAxis(YAXIS, false); + if (a != null) + return a.isAutomaticScale(); // group min and max together ... + return false; // TODO: throw exception? } - + /** * Sets the automatic scale option on or off for the Y Axis (Value axis) - *

                Automatic Scaling will automatically set the scale maximum, minimum and tick units + *

                Automatic Scaling will automatically set the scale maximum, minimum and tick units * upon data changes, and is the default chart setting + * * @param b * @see setAxisAutomaticScale(int axisType boolean b) */ public void setAxisAutomaticScale(boolean b) { - Axis a= getAxis(YAXIS, false); - if (a!=null) - a.setAutomaticScale(b); + Axis a = getAxis(YAXIS, false); + if (a != null) + a.setAutomaticScale(b); } - + /** - * Sets the maximum value of the Y Axis (Value Axis) Scale + * Sets the maximum value of the Y Axis (Value Axis) Scale *

                Note: The default scale setting for charts is known as Automatic Scaling *
                When data changes, the chart automatically adjusts the scale as necessary - *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * * @param int MaxValue - the desired maximum scale value * @see ChartHandle.setAxisMax(int axisType, int MaxValue) */ public void setAxisMax(int MaxValue) { - Axis a= getAxis(YAXIS, false); - if (a!=null) - a.setMaxScale(MaxValue); - // TODO: throw exception if no axis? - } + Axis a = getAxis(YAXIS, false); + if (a != null) + a.setMaxScale(MaxValue); + // TODO: throw exception if no axis? + } /** - * Sets the minimum value of the Y Axis (Value Axis) Scale + * Sets the minimum value of the Y Axis (Value Axis) Scale *

                Note: The default setting for charts is known as Automatic Scaling *
                When data changes, the chart automatically adjusts the scale as necessary - *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + *
                Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * * @param int MinValue - the desired minimum scale value * @see ChartHandle.setAxisMin(int axisType, int MinValue) */ public void setAxisMin(int MinValue) { - Axis a= getAxis(YAXIS, false); - if (a!=null) - a.setMinScale(MinValue); - // TODO: throw exception if no axis? - } - + Axis a = getAxis(YAXIS, false); + if (a != null) + a.setMinScale(MinValue); + // TODO: throw exception if no axis? + } + /** * returns the SVG necesssary to define the desired axis - * @param axisType one of: XAXIS, YAXIS, ZAXIS + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max * @return */ - public String getSVG(int axisType, java.util.Map chartMetrics, Object[] categories) { - Axis a= getAxis(axisType, false); - if (a!=null) { - return a.getSVG(this, chartMetrics, categories); + public String getSVG(int axisType, java.util.Map chartMetrics, Object[] categories) { + Axis a = getAxis(axisType, false); + if (a != null) { + return a.getSVG(this, chartMetrics, categories); } return null; } /** * returns the OOXML necessary to define the desired axis - * @param axisType one of: XAXIS, YAXIS, ZAXIS + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @param type * @param id * @param crossId * @return */ public String getOOXML(int axisType, int type, String id, String crossId) { - Axis a= getAxis(axisType, false); - if (a!=null) - return a.getOOXML(type, id, crossId); - return ""; + Axis a = getAxis(axisType, false); + if (a != null) + return a.getOOXML(type, id, crossId); + return ""; } - + /** * set the font index for this Axis (for title) - * @param axisType one of: XAXIS, YAXIS, ZAXIS + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @param fondId */ public void setTitleFont(int axisType, int fondId) { - Axis a= getAxis(axisType, false); - if (a!=null) - a.setFont(fondId); + Axis a = getAxis(axisType, false); + if (a != null) + a.setFont(fondId); } - + /** - * returns the font for the title for the desired axis - * @param axisType one of: XAXIS, YAXIS, ZAXIS + * returns the font for the title for the desired axis + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @return */ public Font getTitleFont(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) - return a.getFont(); - return null; + Axis a = getAxis(axisType, false); + if (a != null) + return a.getFont(); + return null; } - + /** - * returns the label font for the desired axis. + * returns the label font for the desired axis. * If not explicitly set, returns the default font - * @param axisType one of: XAXIS, YAXIS, ZAXIS + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @return */ public Font getLabelFont(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) - return a.getLabelFont(); - return null; + Axis a = getAxis(axisType, false); + if (a != null) + return a.getLabelFont(); + return null; } - - + + public JSONObject getJSON(int axisType, io.starter.OpenXLS.WorkBookHandle wbh, int chartType, double yMax, double yMin, int nSeries) { - return ap.getAxis(axisType, false).getJSON(wbh, chartType, yMax, yMin, nSeries); + return ap.getAxis(axisType, false).getJSON(wbh, chartType, yMax, yMin, nSeries); } /** * sets an option for this axis - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param op option name; one of: CatCross, LabelCross, Marks, CrossBetween, CrossMax, - * MajorGridLines, AddArea, AreaFg, AreaBg - * or Linked Text Display options: - * Label, ShowKey, ShowValue, ShowLabelPct, ShowPct, - * ShowCatLabel, ShowBubbleSizes, TextRotation, Font - * @param val option value - */ - public void setChartOption(int axisType, String op, String val) { - Axis a= getAxis(axisType, false); - if (a!=null) - a.setChartOption(op, val); - } - - - /** - * - * returns the Axis Label Placement or position as an int + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param op option name; one of: CatCross, LabelCross, Marks, CrossBetween, CrossMax, + * MajorGridLines, AddArea, AreaFg, AreaBg + * or Linked Text Display options: + * Label, ShowKey, ShowValue, ShowLabelPct, ShowPct, + * ShowCatLabel, ShowBubbleSizes, TextRotation, Font + * @param val option value + */ + public void setChartOption(int axisType, String op, String val) { + Axis a = getAxis(axisType, false); + if (a != null) + a.setChartOption(op, val); + } + + + /** + * returns the Axis Label Placement or position as an int *

                One of: *
                Axis.INVISIBLE - axis is hidden *
                Axis.LOW - low end of plot area *
                Axis.HIGH - high end of plot area *
                Axis.NEXTTO- next to axis (default) - * @param axisType one of: XAXIS, YAXIS, ZAXIS + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @return int - one of the Axis Label placement constants above - */ - public int getAxisPlacement(int axisType) { - Axis a= getAxis(axisType, false); - if (a!=null) - return a.getAxisPlacement(); - return -1; - } - - /** - * + */ + public int getAxisPlacement(int axisType) { + Axis a = getAxis(axisType, false); + if (a != null) + return a.getAxisPlacement(); + return -1; + } + + /** * sets the axis labels position or placement to the desired value (these match Excel placement options) *

                Possible options: *
                Axis.INVISIBLE - hides the axis *
                Axis.LOW - low end of plot area *
                Axis.HIGH - high end of plot area *
                Axis.NEXTTO- next to axis (default) - * @param axisType one of: XAXIS, YAXIS, ZAXIS + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS * @param Placement - int one of the Axis placement constants listed above - */ - public void setAxisPlacement(int axisType, int placement) { - Axis a= getAxis(axisType, false); - if (a!=null) - a.setAxisPlacement(placement); - } - - - /** - * parse OOXML axis element - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param xpp XmlPullParser positioned at correct elemnt - * @param axisTag catAx, valAx, serAx, dateAx - * @param lastTag Stack of element names - */ - public void parseOOXML(int axisType, XmlPullParser xpp, String tnm, Stack lastTag, WorkBookHandle bk) { - Axis a= getAxis(axisType, true); - if (a!=null) { - a.removeTitle(); - a.setChartOption("MajorGridLines", "false"); // initally, remove any - will be set in parseOOXML if necessary - a.parseOOXML(xpp, tnm, lastTag, bk); - } - } - - public void close() { - axes.clear(); - axes= null; - ap= null; - axisMetrics.clear(); - } - - - /** - * returns already-set axisMetrics - *
                Used only after calling chart.getMetrics - * @return axisMetrics -- map of useful chart display metrics - *
                Contains: - *
                - XAXISLABELOFFSET double - XAXISTITLEOFFSET double - YAXISLABELOFFSET double - YAXISTITLEOFFSET double - xAxisRotate integer x axis lable rotation angle - yPattern string numeric pattern for y axis - xPattern string numeric pattern for x axis - double major; // tick units - double minor; - */ + */ + public void setAxisPlacement(int axisType, int placement) { + Axis a = getAxis(axisType, false); + if (a != null) + a.setAxisPlacement(placement); + } + + + /** + * parse OOXML axis element + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param xpp XmlPullParser positioned at correct elemnt + * @param axisTag catAx, valAx, serAx, dateAx + * @param lastTag Stack of element names + */ + public void parseOOXML(int axisType, XmlPullParser xpp, String tnm, Stack lastTag, WorkBookHandle bk) { + Axis a = getAxis(axisType, true); + if (a != null) { + a.removeTitle(); + a.setChartOption("MajorGridLines", "false"); // initally, remove any - will be set in parseOOXML if necessary + a.parseOOXML(xpp, tnm, lastTag, bk); + } + } + + public void close() { + axes.clear(); + axes = null; + ap = null; + axisMetrics.clear(); + } + + + /** + * returns already-set axisMetrics + *
                Used only after calling chart.getMetrics + * + * @return axisMetrics -- map of useful chart display metrics + *
                Contains: + *
                + * XAXISLABELOFFSET double + * XAXISTITLEOFFSET double + * YAXISLABELOFFSET double + * YAXISTITLEOFFSET double + * xAxisRotate integer x axis lable rotation angle + * yPattern string numeric pattern for y axis + * xPattern string numeric pattern for x axis + * double major; // tick units + * double minor; + */ public HashMap getMetrics() { - return axisMetrics; - } - - /** - * returns a specific axis Metric - *
                Use only after calling chart.getMetrics - * @param metric String metric option one of - *
                - XAXISLABELOFFSET double - XAXISTITLEOFFSET double - YAXISLABELOFFSET double - YAXISTITLEOFFSET double - xAxisRotate integer x axis lable rotation angle - yPattern string numeric pattern for y axis - xPattern string numeric pattern for x axis - double major; // tick units - double minor; - * @return - */ - public Object getMetric(String metric) { - return axisMetrics.get(metric); - } - - /** - * generate Axis Metrics -- minor, major scale, title and label offsets, etc. - * for use in SVG generation and other operations - * @param charttype chart type constant + return axisMetrics; + } + + /** + * returns a specific axis Metric + *
                Use only after calling chart.getMetrics + * + * @param metric String metric option one of + *
                + * XAXISLABELOFFSET double + * XAXISTITLEOFFSET double + * YAXISLABELOFFSET double + * YAXISTITLEOFFSET double + * xAxisRotate integer x axis lable rotation angle + * yPattern string numeric pattern for y axis + * xPattern string numeric pattern for x axis + * double major; // tick units + * double minor; + * @return + */ + public Object getMetric(String metric) { + return axisMetrics.get(metric); + } + + /** + * generate Axis Metrics -- minor, major scale, title and label offsets, etc. + * for use in SVG generation and other operations + * + * @param charttype chart type constant * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param plotcoords == if set, plot-area coordinates (may not be set if automatic) - * @return axisMetrics Map: contains the important axis metrics for use in chart display - */ - public HashMap getMetrics(int charttype, HashMap chartMetrics, float[] plotcoords, Object[] categories) { - double[] minmax= this.getMinMax(chartMetrics.get("min"), chartMetrics.get("max")); // sets min/max on Value axis, based upon axis settings and actual minimun and maximum values - chartMetrics.put("min", minmax[0]); // set new values, if any - chartMetrics.put("max", minmax[1]); // "" - axisMetrics.put("minor", minmax[2]); - axisMetrics.put("major", minmax[3]); - axisMetrics.put("xAxisReversed", this.isReversed(XAXIS)); // default is Value axis crosses at bottom. reverse= crosses at top - axisMetrics.put("xPattern", this.getNumberFormat(XAXIS)); - axisMetrics.put("yAxisReversed", this.isReversed(YAXIS)); // if value (Y), default is on LHS; reverse= RHS - axisMetrics.put("yPattern", this.getNumberFormat(YAXIS)); - axisMetrics.put("XAXISLABELOFFSET", 0.0); - axisMetrics.put("XAXISTITLEOFFSET", 0.0); - axisMetrics.put("YAXISLABELOFFSET", 0.0); - axisMetrics.put("YAXISTITLEOFFSET", 0.0); - - // X axis title Offset - if (!this.getTitle(XAXIS).equals("")) { - io.starter.formats.XLS.Font ef= this.getTitleFont(XAXIS); - java.awt.Font f= new java.awt.Font(ef.getFontName(), ef.getFontWeight(), (int)ef.getFontHeightInPoints()); - AtomicInteger h= new AtomicInteger(0); - double w= getRotatedWidth(f, h, this.getTitle(XAXIS), this.getTitleRotation(XAXIS)); - axisMetrics.put("XAXISTITLEOFFSET", new Double(h.intValue()+10)); /* add a little padding */ - } - - // Y Axis Title Offsets - if (this.hasAxis(YAXIS)) { - String title= this.getTitle(YAXIS); - if (!title.equals("")) { - io.starter.formats.XLS.Font ef= this.getTitleFont(YAXIS); - java.awt.Font f= new java.awt.Font(ef.getFontName(), ef.getFontWeight(), (int)ef.getFontHeightInPoints()); - AtomicInteger h= new AtomicInteger(0); - double w= getRotatedWidthVert(f, h, this.getTitle(YAXIS), this.getTitleRotation(YAXIS)); - axisMetrics.put("YAXISTITLEOFFSET", w ); // add padding + * @param plotcoords == if set, plot-area coordinates (may not be set if automatic) + * @return axisMetrics Map: contains the important axis metrics for use in chart display + */ + public HashMap getMetrics(int charttype, HashMap chartMetrics, float[] plotcoords, Object[] categories) { + double[] minmax = this.getMinMax(chartMetrics.get("min"), chartMetrics.get("max")); // sets min/max on Value axis, based upon axis settings and actual minimun and maximum values + chartMetrics.put("min", minmax[0]); // set new values, if any + chartMetrics.put("max", minmax[1]); // "" + axisMetrics.put("minor", minmax[2]); + axisMetrics.put("major", minmax[3]); + axisMetrics.put("xAxisReversed", this.isReversed(XAXIS)); // default is Value axis crosses at bottom. reverse= crosses at top + axisMetrics.put("xPattern", this.getNumberFormat(XAXIS)); + axisMetrics.put("yAxisReversed", this.isReversed(YAXIS)); // if value (Y), default is on LHS; reverse= RHS + axisMetrics.put("yPattern", this.getNumberFormat(YAXIS)); + axisMetrics.put("XAXISLABELOFFSET", 0.0); + axisMetrics.put("XAXISTITLEOFFSET", 0.0); + axisMetrics.put("YAXISLABELOFFSET", 0.0); + axisMetrics.put("YAXISTITLEOFFSET", 0.0); + + // X axis title Offset + if (!this.getTitle(XAXIS).equals("")) { + io.starter.formats.XLS.Font ef = this.getTitleFont(XAXIS); + java.awt.Font f = new java.awt.Font(ef.getFontName(), ef.getFontWeight(), (int) ef.getFontHeightInPoints()); + AtomicInteger h = new AtomicInteger(0); + double w = getRotatedWidth(f, h, this.getTitle(XAXIS), this.getTitleRotation(XAXIS)); + axisMetrics.put("XAXISTITLEOFFSET", new Double(h.intValue() + 10)); /* add a little padding */ + } + + // Y Axis Title Offsets + if (this.hasAxis(YAXIS)) { + String title = this.getTitle(YAXIS); + if (!title.equals("")) { + io.starter.formats.XLS.Font ef = this.getTitleFont(YAXIS); + java.awt.Font f = new java.awt.Font(ef.getFontName(), ef.getFontWeight(), (int) ef.getFontHeightInPoints()); + AtomicInteger h = new AtomicInteger(0); + double w = getRotatedWidthVert(f, h, this.getTitle(YAXIS), this.getTitleRotation(YAXIS)); + axisMetrics.put("YAXISTITLEOFFSET", w); // add padding /* int rot= this.getTitleRotation(YAXIS); //0-180 or 255 (=vertical with letters upright) if (rot==90 || rot==255 || charttype==BARCHART) {// Y axis title is almost always rotated, so use font height as offset @@ -757,243 +798,246 @@ public HashMap getMetrics(int charttype, HashMap double len= StringTool.getApproximateStringWidth(f, ExcelTools.getFormattedStringVal(title, null)); axisMetrics.put("YAXISTITLEOFFSET", Math.ceil(len*(Math.cos(Math.toRadians(rot))))); }*/ - } else - axisMetrics.put("YAXISTITLEOFFSET", 20.0); // no axis title- add a little padding from edge - } - // Label offsets - StringBuffer[] series= new StringBuffer[0]; - try { - double major= (Double) getMetric("major"); - double min= chartMetrics.get("min"); - double max= chartMetrics.get("max"); - // get java awt font so can compute approximate width of largest y axis label in order to compute YAXISLABELOFFSET - if (major > 0) { - int nSeries= 0; - if (min==0) // usual case - nSeries= ((major!=0)?(int)(max/major)+1:0); - else - nSeries= ((major!=0)?(int)((max-min)/major)+1:0); - nSeries= Math.abs(nSeries); - series= new StringBuffer[nSeries]; - if (Math.floor(major)!=major) { // contains a fractional part ... avoid java floating point issues - // ensure y value matches scale/avoid double precision issues ... sigh ... - String s= String.valueOf(major); - int z= s.indexOf("."); - int scale= 0; - if (z!=-1) - scale= s.length()-(z+1); - z= 0; - for (double i= min; i <= max; i+=major) { - java.math.BigDecimal bd= new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); - series[z++]= new StringBuffer( - CellFormatFactory.fromPatternString( - (String) getMetric("yPattern") ).format( bd.toString() )); - } - } else { // usual case of an int scale - int z= 0; - for (double i= min; i <= max; i+=major) - series[z++]= new StringBuffer(CellFormatFactory.fromPatternString((String) getMetric("yPattern") ).format( i )); - } - } - } catch (Exception e) { - Logger.logWarn("ChartAxes.getMetrics. Error obtaining Series: " + e.toString()); - } - - // Label Offsets ... - if (this.hasAxis(XAXIS) && charttype!=RADARCHART && charttype!=RADARAREACHART) { //(Pie, donut, etc. don't have axes labels so disregard - Object[] s; // Determine X Axis Label offsets - double width; - AtomicInteger rot; - io.starter.formats.XLS.Font lf; - String pattern; - if (charttype!=BARCHART) { - s= categories; - pattern= (String) getMetric("xPattern"); - } else { // bar chart - series on x axis - s= series; - pattern= (String) getMetric("yPattern"); - } - width= (chartMetrics.get("w")/s.length)-6; // ensure a bit of padding on either side - lf= this.getLabelFont(XAXIS); - rot= new AtomicInteger(this.getLabelRotation(XAXIS)); // if rot==0 and xaxis labels do not fit in width, a forced rotation will happen. ... - double off= getLabelOffsets(lf, width, s, rot, pattern, true); - axisMetrics.put("xAxisRotate", rot.intValue()); // possibly changed when calculating label offsets - axisMetrics.put("XAXISLABELOFFSET", off); - } - if (this.hasAxis(YAXIS) && charttype!=RADARCHART && charttype!=RADARAREACHART) { //(Pie, Donut, etc. don't have axes labels so disregard - // for Y axis, determine width of labels and use as offset (except for bar charts, use height as offset) - Object[] s; - double width; - AtomicInteger rot; - io.starter.formats.XLS.Font lf; - String pattern; - if (charttype!=BARCHART) { - s= series; - pattern= (String) getMetric("yPattern"); - } else { // bar chart- categories on y axis - s= categories; - pattern= (String) getMetric("xPattern"); - } - rot= new AtomicInteger(this.getLabelRotation(YAXIS)); // if rot==0 and xaxis labels do not fit in width, a forced rotation will happen. ... - lf= this.getLabelFont(YAXIS); - width= (chartMetrics.get("w")/2)-10; // a good guess? - double off= getLabelOffsets(lf, width, s, rot, pattern, false); - axisMetrics.put("yAxisRotate", rot.intValue()); // possibly changed when calculating label offsets - axisMetrics.put("YAXISLABELOFFSET", off); // with padding - } - return axisMetrics; - } - - /** - * returns the offset of the labels for the axis given the length of the label strings, the width of the - *
                this will attempt to break apart longer strings; if so will increment offset to accomodate multiple lines - * @param lf label font - * @param width max width of labels - * @param strings label strings - * @param rot desired rotation, if any (0 if none) - * @param horiz true if this is a horizontal axis - * @return - */ - private double getLabelOffsets(io.starter.formats.XLS.Font lf, double width, Object[] strings, AtomicInteger rot, String pattern, boolean horiz) { - double retwidth= 0; - java.awt.Font f= null; - double h= 0; - try { - // get awt Font so can compute and fit category in width - f= new java.awt.Font(lf.getFontName(), lf.getFontWeight(), (int)lf.getFontHeightInPoints()); - for (int i=0; i < strings.length; i++) { - double w; - AtomicInteger height= new AtomicInteger(0); - StringBuffer s= null; - try { - s= new StringBuffer( CellFormatFactory.fromPatternString( pattern ).format( strings[i].toString() )); - } catch (IllegalArgumentException e) { // trap error formatting - s= new StringBuffer(strings[i].toString()); - } - if (horiz) // on horizontal axis - w= getRotatedWidth(f, height, s.toString(), rot.intValue()); - else // on vertical axis - w= getRotatedWidthVert(f, height, s.toString(), rot.intValue()); - h= Math.max(height.intValue(), h); - w= addLinesToFit(f, s, rot, w, width, height); - if (w> width) - width= w; - - strings[i]= s; - retwidth= Math.max(w, retwidth); - } - } catch (Exception e) { - } - if (horiz) - return h; - else - return retwidth; + } else + axisMetrics.put("YAXISTITLEOFFSET", 20.0); // no axis title- add a little padding from edge + } + // Label offsets + StringBuffer[] series = new StringBuffer[0]; + try { + double major = (Double) getMetric("major"); + double min = chartMetrics.get("min"); + double max = chartMetrics.get("max"); + // get java awt font so can compute approximate width of largest y axis label in order to compute YAXISLABELOFFSET + if (major > 0) { + int nSeries = 0; + if (min == 0) // usual case + nSeries = ((major != 0) ? (int) (max / major) + 1 : 0); + else + nSeries = ((major != 0) ? (int) ((max - min) / major) + 1 : 0); + nSeries = Math.abs(nSeries); + series = new StringBuffer[nSeries]; + if (Math.floor(major) != major) { // contains a fractional part ... avoid java floating point issues + // ensure y value matches scale/avoid double precision issues ... sigh ... + String s = String.valueOf(major); + int z = s.indexOf("."); + int scale = 0; + if (z != -1) + scale = s.length() - (z + 1); + z = 0; + for (double i = min; i <= max; i += major) { + java.math.BigDecimal bd = new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); + series[z++] = new StringBuffer( + CellFormatFactory.fromPatternString( + (String) getMetric("yPattern")).format(bd.toString())); + } + } else { // usual case of an int scale + int z = 0; + for (double i = min; i <= max; i += major) + series[z++] = new StringBuffer(CellFormatFactory.fromPatternString((String) getMetric("yPattern")).format(i)); + } + } + } catch (Exception e) { + Logger.logWarn("ChartAxes.getMetrics. Error obtaining Series: " + e.toString()); + } + + // Label Offsets ... + if (this.hasAxis(XAXIS) && charttype != RADARCHART && charttype != RADARAREACHART) { //(Pie, donut, etc. don't have axes labels so disregard + Object[] s; // Determine X Axis Label offsets + double width; + AtomicInteger rot; + io.starter.formats.XLS.Font lf; + String pattern; + if (charttype != BARCHART) { + s = categories; + pattern = (String) getMetric("xPattern"); + } else { // bar chart - series on x axis + s = series; + pattern = (String) getMetric("yPattern"); + } + width = (chartMetrics.get("w") / s.length) - 6; // ensure a bit of padding on either side + lf = this.getLabelFont(XAXIS); + rot = new AtomicInteger(this.getLabelRotation(XAXIS)); // if rot==0 and xaxis labels do not fit in width, a forced rotation will happen. ... + double off = getLabelOffsets(lf, width, s, rot, pattern, true); + axisMetrics.put("xAxisRotate", rot.intValue()); // possibly changed when calculating label offsets + axisMetrics.put("XAXISLABELOFFSET", off); + } + if (this.hasAxis(YAXIS) && charttype != RADARCHART && charttype != RADARAREACHART) { //(Pie, Donut, etc. don't have axes labels so disregard + // for Y axis, determine width of labels and use as offset (except for bar charts, use height as offset) + Object[] s; + double width; + AtomicInteger rot; + io.starter.formats.XLS.Font lf; + String pattern; + if (charttype != BARCHART) { + s = series; + pattern = (String) getMetric("yPattern"); + } else { // bar chart- categories on y axis + s = categories; + pattern = (String) getMetric("xPattern"); + } + rot = new AtomicInteger(this.getLabelRotation(YAXIS)); // if rot==0 and xaxis labels do not fit in width, a forced rotation will happen. ... + lf = this.getLabelFont(YAXIS); + width = (chartMetrics.get("w") / 2) - 10; // a good guess? + double off = getLabelOffsets(lf, width, s, rot, pattern, false); + axisMetrics.put("yAxisRotate", rot.intValue()); // possibly changed when calculating label offsets + axisMetrics.put("YAXISLABELOFFSET", off); // with padding + } + return axisMetrics; + } + + /** + * returns the offset of the labels for the axis given the length of the label strings, the width of the + *
                this will attempt to break apart longer strings; if so will increment offset to accomodate multiple lines + * + * @param lf label font + * @param width max width of labels + * @param strings label strings + * @param rot desired rotation, if any (0 if none) + * @param horiz true if this is a horizontal axis + * @return + */ + private double getLabelOffsets(io.starter.formats.XLS.Font lf, double width, Object[] strings, AtomicInteger rot, String pattern, boolean horiz) { + double retwidth = 0; + java.awt.Font f = null; + double h = 0; + try { + // get awt Font so can compute and fit category in width + f = new java.awt.Font(lf.getFontName(), lf.getFontWeight(), (int) lf.getFontHeightInPoints()); + for (int i = 0; i < strings.length; i++) { + double w; + AtomicInteger height = new AtomicInteger(0); + StringBuffer s = null; + try { + s = new StringBuffer(CellFormatFactory.fromPatternString(pattern).format(strings[i].toString())); + } catch (IllegalArgumentException e) { // trap error formatting + s = new StringBuffer(strings[i].toString()); + } + if (horiz) // on horizontal axis + w = getRotatedWidth(f, height, s.toString(), rot.intValue()); + else // on vertical axis + w = getRotatedWidthVert(f, height, s.toString(), rot.intValue()); + h = Math.max(height.intValue(), h); + w = addLinesToFit(f, s, rot, w, width, height); + if (w > width) + width = w; + + strings[i] = s; + retwidth = Math.max(w, retwidth); + } + } catch (Exception e) { + } + if (horiz) + return h; + else + return retwidth; // return offset + h; - } - - /** - * get the width of string s in font f rotated by rot (0, 90, -90, 180) - * @param f Font to display s in - * @param s string to display - * @param rot rotation (0= none) - */ - private double getRotatedWidth(java.awt.Font f, AtomicInteger height, String s, int rot) { - double retWidth= 0; - String[] slines= s.split("\n"); - for (int i= 0; i < slines.length; i++) { - double width; - if (rot==0) { - width= StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString( null ).format( slines[i] )); - height.set((int)Math.ceil(f.getSize()*3)); // width of the font + padding - } else if (Math.abs(rot)==90) { - width= Math.ceil(f.getSize()*2); // width of the font + padding - height.set( (int) Math.max(height.intValue(), StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString( null ).format( slines[i] )))); - } else { // 45 - width= StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString( null ).format( slines[i] )); - width= Math.ceil(width*(Math.cos(Math.toRadians(rot)))); - height.set((int)Math.ceil(width*(Math.sin(Math.toRadians(rot))))); - } - retWidth= Math.max(width, retWidth); - } - return retWidth; - } - - /** - * get the width of string s in font f rotated by rot (0, 90, -90, 180) - * on a Vertical axis - * @param f Font to display s in - * @param s string to display - * @param rot rotation (0= none) - */ - private double getRotatedWidthVert(java.awt.Font f, AtomicInteger height, String s, int rot) { - double retWidth= 0; - String[] slines= s.split("\n"); - for (int i= 0; i < slines.length; i++) { - double width; - if (Math.abs(rot)==90) { // means VERTICAL orientation - default - height.set((int)StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString( null ).format( slines[i] ) ) ); - width= Math.ceil(f.getSize()*2); // width of the font + padding - } else if (rot==0) { // means HORIZONTAL on vertical axis - height.set((int) Math.ceil(f.getSize()*3)); // width of font - width= Math.max(height.intValue(), StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString( null ).format( slines[i] ))); - }else { // 45 - width= StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString( null ).format( slines[i] )); - width= Math.ceil(width*(Math.cos(Math.toRadians(rot)))); - height.set((int)Math.ceil(width*(Math.sin(Math.toRadians(rot))))); - } - retWidth= Math.max(width, retWidth); - } - return retWidth; - } - - /** - * if formatted width of string doesn't fit into width, break apart at spaces with new lines - * return - * @param f font to display string in - * @param s target String - * @param rot rotation (0, 45, 90, -90 or 180) - * @param len formatted string length taking into account display font - * @param width maximum width to display string - * @return - */ - private double addLinesToFit(java.awt.Font f, StringBuffer s, AtomicInteger rot, double len, double width, AtomicInteger height) { - double retLen= Math.min(width, len); - String str= s.toString().trim(); - - while (len > width) { - int lastSpace= -1; - int j= s.lastIndexOf("\n")+1; - len= -1; - while (len < width && j < str.length()) { - len+= StringTool.getApproximateCharWidth(f, str.charAt(j)); - if (str.charAt(j)==' ') - lastSpace= j; - j++; - } - if (len < width) - break; // got it - - if (lastSpace==-1) { // no spaces to break apart via \n's - rotate! - if (str.indexOf(' ')==-1) { - // see if string will fit in 45 degree rotation - if (rot.intValue()!=-90) { - rot.set(45); - len= getRotatedWidth(f, height, str, rot.intValue()); - if (len > width){ // then all's fine - }else // doesn't fit in 45, must be 90 degrees - rot.set(-90); - } - if (rot.intValue()==-90) { - len= getRotatedWidth(f, height, str, rot.intValue()); - } - retLen= Math.max(len, retLen); - break; - } - else lastSpace= s.toString().indexOf(' '); - } - s.replace(lastSpace, lastSpace+1, "\n"); // + str.substring(lastSpace+1)); - str= s.toString(); - } - return retLen; - } + } + + /** + * get the width of string s in font f rotated by rot (0, 90, -90, 180) + * + * @param f Font to display s in + * @param s string to display + * @param rot rotation (0= none) + */ + private double getRotatedWidth(java.awt.Font f, AtomicInteger height, String s, int rot) { + double retWidth = 0; + String[] slines = s.split("\n"); + for (int i = 0; i < slines.length; i++) { + double width; + if (rot == 0) { + width = StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])); + height.set((int) Math.ceil(f.getSize() * 3)); // width of the font + padding + } else if (Math.abs(rot) == 90) { + width = Math.ceil(f.getSize() * 2); // width of the font + padding + height.set((int) Math.max(height.intValue(), StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])))); + } else { // 45 + width = StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])); + width = Math.ceil(width * (Math.cos(Math.toRadians(rot)))); + height.set((int) Math.ceil(width * (Math.sin(Math.toRadians(rot))))); + } + retWidth = Math.max(width, retWidth); + } + return retWidth; + } + + /** + * get the width of string s in font f rotated by rot (0, 90, -90, 180) + * on a Vertical axis + * + * @param f Font to display s in + * @param s string to display + * @param rot rotation (0= none) + */ + private double getRotatedWidthVert(java.awt.Font f, AtomicInteger height, String s, int rot) { + double retWidth = 0; + String[] slines = s.split("\n"); + for (int i = 0; i < slines.length; i++) { + double width; + if (Math.abs(rot) == 90) { // means VERTICAL orientation - default + height.set((int) StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i]))); + width = Math.ceil(f.getSize() * 2); // width of the font + padding + } else if (rot == 0) { // means HORIZONTAL on vertical axis + height.set((int) Math.ceil(f.getSize() * 3)); // width of font + width = Math.max(height.intValue(), StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i]))); + } else { // 45 + width = StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])); + width = Math.ceil(width * (Math.cos(Math.toRadians(rot)))); + height.set((int) Math.ceil(width * (Math.sin(Math.toRadians(rot))))); + } + retWidth = Math.max(width, retWidth); + } + return retWidth; + } + + /** + * if formatted width of string doesn't fit into width, break apart at spaces with new lines + * return + * + * @param f font to display string in + * @param s target String + * @param rot rotation (0, 45, 90, -90 or 180) + * @param len formatted string length taking into account display font + * @param width maximum width to display string + * @return + */ + private double addLinesToFit(java.awt.Font f, StringBuffer s, AtomicInteger rot, double len, double width, AtomicInteger height) { + double retLen = Math.min(width, len); + String str = s.toString().trim(); + + while (len > width) { + int lastSpace = -1; + int j = s.lastIndexOf("\n") + 1; + len = -1; + while (len < width && j < str.length()) { + len += StringTool.getApproximateCharWidth(f, str.charAt(j)); + if (str.charAt(j) == ' ') + lastSpace = j; + j++; + } + if (len < width) + break; // got it + + if (lastSpace == -1) { // no spaces to break apart via \n's - rotate! + if (str.indexOf(' ') == -1) { + // see if string will fit in 45 degree rotation + if (rot.intValue() != -90) { + rot.set(45); + len = getRotatedWidth(f, height, str, rot.intValue()); + if (len > width) { // then all's fine + } else // doesn't fit in 45, must be 90 degrees + rot.set(-90); + } + if (rot.intValue() == -90) { + len = getRotatedWidth(f, height, str, rot.intValue()); + } + retLen = Math.max(len, retLen); + break; + } else lastSpace = s.toString().indexOf(' '); + } + s.replace(lastSpace, lastSpace + 1, "\n"); // + str.substring(lastSpace+1)); + str = s.toString(); + } + return retLen; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartConstants.java b/src/main/java/io/starter/formats/XLS/charts/ChartConstants.java index 58830a5..14af0a7 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartConstants.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartConstants.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,51 +22,44 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.*; -import io.starter.OpenXLS.*; - - /** * Constants required for Chart functionality - * - * - * */ public interface ChartConstants { - // Chart Types (used in Chart Creation) - public static final int COLCHART = 0; - public static final int BARCHART = 1; - public static final int LINECHART = 2; - public static final int PIECHART = 3; - public static final int AREACHART = 4; // 20070703 KSC: - public static final int SCATTERCHART = 5; // "" - public static final int RADARCHART = 6; // "" - public static final int SURFACECHART = 7; // "" - public static final int DOUGHNUTCHART = 8; // "" - public static final int BUBBLECHART = 9; // "" - public static final int OFPIECHART= 10; - public static final int PYRAMIDCHART = 11; // column-type pyramid - public static final int CYLINDERCHART= 12; // column-type cylinder - public static final int CONECHART= 13; // column-type cone - public static final int PYRAMIDBARCHART = 14;// bar-type pyramid - public static final int CYLINDERBARCHART= 15; // bar-type cylinder - public static final int CONEBARCHART= 16; // bar-type cone - public static final int RADARAREACHART = 17; // "" - public static final int STOCKCHART= 18; - + // Chart Types (used in Chart Creation) + int COLCHART = 0; + int BARCHART = 1; + int LINECHART = 2; + int PIECHART = 3; + int AREACHART = 4; // 20070703 KSC: + int SCATTERCHART = 5; // "" + int RADARCHART = 6; // "" + int SURFACECHART = 7; // "" + int DOUGHNUTCHART = 8; // "" + int BUBBLECHART = 9; // "" + int OFPIECHART = 10; + int PYRAMIDCHART = 11; // column-type pyramid + int CYLINDERCHART = 12; // column-type cylinder + int CONECHART = 13; // column-type cone + int PYRAMIDBARCHART = 14;// bar-type pyramid + int CYLINDERBARCHART = 15; // bar-type cylinder + int CONEBARCHART = 16; // bar-type cone + int RADARAREACHART = 17; // "" + int STOCKCHART = 18; + // Bar Shapes - public static final int SHAPEDEFAULT= 0; - public static final int SHAPECOLUMN= SHAPEDEFAULT; // default - public static final int SHAPECYLINDER= 1; - public static final int SHAPEPYRAMID= 256; - public static final int SHAPECONE= 257; - public static final int SHAPEPYRAMIDTOMAX= 516; - public static final int SHAPECONETOMAX= 517; - + int SHAPEDEFAULT = 0; + int SHAPECOLUMN = SHAPEDEFAULT; // default + int SHAPECYLINDER = 1; + int SHAPEPYRAMID = 256; + int SHAPECONE = 257; + int SHAPEPYRAMIDTOMAX = 516; + int SHAPECONETOMAX = 517; + // Axis Types - public static final int XAXIS= 0; - public static final int YAXIS= 1; - public static final int ZAXIS= 2; - public static final int XVALAXIS= 3; // an X axis type but VAL records + int XAXIS = 0; + int YAXIS = 1; + int ZAXIS = 2; + int XVALAXIS = 3; // an X axis type but VAL records } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartFormat.java b/src/main/java/io/starter/formats/XLS/charts/ChartFormat.java index a60a7ca..6d41011 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartFormat.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,43 +22,42 @@ */ package io.starter.formats.XLS.charts; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.OOXML.SpPr; import io.starter.formats.XLS.BiffRec; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * ChartFormat: Parent Record for Chart Group (0x1014) - * + *

                * 4 reserved 16 0 20 grbit 2 format flags 22 icrt 2 drawing order (0= bottom of * z-order) - * - * - * 16 bytes- reserved must be 0 - * fVaried (1 bit): A bit that specifies whether the color for each data point and - the color and type for each data marker vary. If the chart group has multiple series or the chart group has one + *

                + *

                + * 16 bytes- reserved must be 0 + * fVaried (1 bit): A bit that specifies whether the color for each data point and + * the color and type for each data marker vary. If the chart group has multiple series or the chart group has one * series and the chart group type is a surface, stock, or area, this field MUST * be ignored, and the data points do not vary. For all other chart group types, * if the chart group has one series, a value of 0x1 specifies that the data - * points vary. - * 15 bits - reserved - 0 + * points vary. + * 15 bits - reserved - 0 * icrt (2 bytes): An unsigned integer that specifies the drawing order of the chart group relative to the other chart - * groups, where 0x0000 is the bottom of the z-order. -This value MUST be unique for each instance of this record and MUST be less than or equal to 0x0009. - * - * + * groups, where 0x0000 is the bottom of the z-order. + * This value MUST be unique for each instance of this record and MUST be less than or equal to 0x0009. + *

                + *

                * ORDER OF SUBRECS: * Bar/Pie/Scatter ... * ChartFormatLink * [SeriesList] - * [ThreeD] + * [ThreeD] * [Legend] * [DropBar] * [ChartLine, LineFormat] @@ -67,386 +66,396 @@ * [DataLabExtContents] * [DataFormat] * [ShapePropsStream] - * - * "http://www.extentech.com">Extentech Inc. + *

                + * "http://www.extentech.com">Extentech Inc. */ public class ChartFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4000704166442059677L; - private short grbit = 0; - private boolean fVaried = false; - private short drawingOrder = 0; - - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); - drawingOrder = ByteTools.readShort(this.getByteAt(18), - this.getByteAt(19)); - fVaried = ((grbit & 0x1) == 0x1); - } - - /** - * - * - */ - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[16] = b[0]; - this.getData()[17] = b[1]; - } - - /** - * specifies whether - * the color for each data point and the color and type for each data marker - * vary - * @param b - */ - public void setVaryColor(boolean vary) { - fVaried = vary; - grbit = ByteTools.updateGrBit(grbit, fVaried, 0); - updateRecord(); - } - - /** - * returns whether - * the color for each data point and the color and type for each data marker - * vary - * @param b - */ - public boolean getVaryColor() { - return fVaried; - } - - /** - * replace the existing chart object with the desired ChartObject, - * effectively changing the type of the chart - * @param co - */ - protected void setChartObject(ChartObject co) { - chartArr.remove(0); - chartArr.add(0, (XLSRecord) co); - - } - - /** - * - * @return truth of "Chart is Three D" - */ - public boolean isThreeD(int chartType) { - if (chartType!=ChartConstants.BUBBLECHART) { - for (int i=0;ithe shape is as follows: - public static final int SHAPECOLUMN= 0; // default - public static final int SHAPECYLINDER= 1; - public static final int SHAPEPYRAMID= 256; - public static final int SHAPECONE= 257; - public static final int SHAPEPYRAMIDTOMAX= 516; - public static final int SHAPECONETOMAX= 517; - * @param shape - */ - public void setBarShape(int shape) { - DataFormat df = this.getDataFormatRec(true); - // THIS DOES NOT MAKE SENSE ACCORDING TO DOC BUT IS WHAT EXCEL DOES - df.setPointNumber(0); - df.setSeriesIndex(0); - df.setSeriesNumber(-3); - df.setShape(shape); - } - /** - * sets chart options such as threed options, grid lines, etc ... these options - * are distinct from chart-type-specific options, which are handled by the - * appropriate chart record (pie, bar ...) - * - * @param op - * String option name - * @param val - * Object value - */ - public void setOption(String op, String val) { - if (op.equalsIgnoreCase("Percentage")) { - setPercentage(Short.valueOf(val).shortValue()); - } else if (op.equalsIgnoreCase("Shape")) { - setBarShape(Integer.parseInt(val)); - } else if (op.equals("ShowBubbleSizes") - || // TextDisp options - op.equals("ShowLabelPct") || op.equals("ShowCatLabel") - || op.equals("ShowPct") || op.equals("Rotation") || - // op.equals("ShowValue") || unknown - op.equals("Label") || op.equals("TextRotation")) { - TextDisp td = getDataLegendTextDisp(0); - td.setChartOption(op, val); - } else if (op.equals("Perspective") - || // ThreeD options - op.equals("Cluster") || op.equals("ThreeDScaling") - || op.equals("TwoDWalls") || op.equals("PcGap") - || op.equals("PcDepth") || op.equals("PcHeight") - || op.equals("PcDist") || op.equals("AnElev") - || op.equals("AnRot")) { - ThreeD td = this.getThreeDRec(true); - td.setChartOption(op, val); - } else if (op.equals("ShowValueLabel") - || // Attached Label Options - op.equals("ShowValueAsPercent") - || op.equals("ShowLabelAsPercent") - || op.equals("ShowLabel") - || op.equals("ShowSeriesName") || op.equals("ShowBubbleLabel")) { - DataFormat df= this.getDataFormatRec(true); - df.setDataLabels(op); - } else if (op.equalsIgnoreCase("SmoothedLine") - || op.equalsIgnoreCase("ThreeDBubbles") - || op.equalsIgnoreCase("ArShadow")) { - DataFormat df= this.getDataFormatRec(true); - if (op.equalsIgnoreCase("SmoothedLine")) df.setSmoothLines(true); - else if (op.equalsIgnoreCase("ThreeDBubbles")) df.setHas3DBubbles(true); - else df.setHasShadow(true); - } - } - - /** - * Return the ThreeD rec associated with this ChartFormat, create if not - * present - * - */ - // NOTES: - // The Chart3d record specifies that the plot area of the chart group is rendered in a 3-D scene - // and also specifies the attributes of the 3-D plot area. - // The preceding chart group type MUST be of type bar, pie, line, area, or surface. - public ThreeD getThreeDRec(boolean bCreate) { - ThreeD td = (ThreeD) Chart.findRec(this.chartArr, ThreeD.class); - if (td == null && bCreate) { // add ThreeD rec - for (int i = 0; i < this.chartArr.size(); i++) { - BiffRec b = (BiffRec) this.chartArr.get(i); - if (b.getOpcode() == CHARTFORMATLINK) { - if ((i+1) < chartArr.size() && ((BiffRec) this.chartArr.get(i+1)).getOpcode()==SERIESLIST) i++; // rare that SeriesList record appears - td = (ThreeD) ThreeD.getPrototype(); - td.setParentChart(this.getParentChart()); - this.chartArr.add(i + 1, td); - break; - } - } - } - return td; - } - - /** - * Add or Retrieve TextDisp and assoc records specific for Data Legends - * - * @return - */ - private TextDisp getDataLegendTextDisp(int type) { - int i = Chart.findRecPosition(this.chartArr, Legend.class); - TextDisp td = null; - if (this.chartArr.size() <= (i + 1) - || this.chartArr.get(i + 1).getClass() != DefaultText.class) { // then add one - DefaultText d = (DefaultText) DefaultText.getPrototype(); - d.setType((short) type); - d.setParentChart(this.getParentChart()); - this.chartArr.add(++i, d); - td = (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_DATAPOINTS, - "", this.getWorkBook()); - td.setParentChart(this.getParentChart()); - this.chartArr.add(++i, td); - } else { - DefaultText d = (DefaultText) this.chartArr.get(i + 1); - if (d.getType() != type) { // / add a new one - i += 2; // add after TextDisp - d = (DefaultText) DefaultText.getPrototype(); - d.setType((short) type); - d.setParentChart(this.getParentChart()); - this.chartArr.add(++i, d); - td = (TextDisp) TextDisp.getPrototype( - ObjectLink.TYPE_DATAPOINTS, "", this.getWorkBook()); - td.setParentChart(this.getParentChart()); - this.chartArr.add(++i, td); - } else { // it's the correct one - i += 2; - td = (TextDisp) this.chartArr.get(i); - } - } - return td; - } - - /** - * Gets the dataformat record associated with this ChartFormat If none - * present, creates a basic DataFormat set of records DataFormat controls - * Data Labels, % Distance from sections, line formats ... - * - * @return DataFormat Record - */ - private DataFormat getDataFormatRec(boolean bCreate) { - DataFormat df = (DataFormat) Chart.findRec(this.chartArr, DataFormat.class); - if (df == null && bCreate) { // create dataformat - df = (DataFormat) DataFormat.getPrototypeWithFormatRecs(this.getParentChart()); - this.addChartRecord(df); - } - return df; - } - - - /** - * return the Data Labels chosen for this chart, if any can be one or more - * of:
                - * Value
                - * ValuePerecentage
                - * CategoryPercentage
                - * CategoryLabel
                - * BubbleLabel
                - * SeriesLabel or an empty string if no data labels are chosen for the chart - * - * @return - */ - public String getDataLabels() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getDataLabelType(); - } - return ""; - } - - /** - * returns true if this chart displays lines (Line, Scatter, Radar) - * - * @return true if chart has lines (see Scatter, Line chart ...) - */ - public boolean getHasLines() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getHasLines(); - } - return false; - } - - - /** - * sets this chart to have default lines (Scatter, Line chart ...) - */ - public void setHasLines() { - DataFormat df = this.getDataFormatRec(true); - df.setHasLines(); - } - - - /** - * sets this chart to have lines (Scatter, Line chart ...) of style lineStyle - *
                Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - */ - public void setHasLines(int lineStyle) { - DataFormat df = this.getDataFormatRec(true); - df.setHasLines(lineStyle); - } - /** - * returns true if this chart has smoothed lines (Scatter, Line, Radar) - * @return - */ - public boolean getHasSmoothLines() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getSmoothedLines(); - } - return false; - } - - - /** - * sets this chart to have smoothed lines (Scatter, Line, Radar) - */ - public void setHasSmoothLines(boolean b) { - DataFormat df = this.getDataFormatRec(true); - df.setSmoothLines(b); - } - /** - * returns true if chart has Drop Lines - * @return - */ - public boolean getHasDropLines() { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4000704166442059677L; + private short grbit = 0; + private boolean fVaried = false; + private short drawingOrder = 0; + + public void init() { + super.init(); + grbit = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); + drawingOrder = ByteTools.readShort(this.getByteAt(18), + this.getByteAt(19)); + fVaried = ((grbit & 0x1) == 0x1); + } + + /** + * + */ + private void updateRecord() { + byte[] b = ByteTools.shortToLEBytes(grbit); + this.getData()[16] = b[0]; + this.getData()[17] = b[1]; + } + + /** + * specifies whether + * the color for each data point and the color and type for each data marker + * vary + * + * @param b + */ + public void setVaryColor(boolean vary) { + fVaried = vary; + grbit = ByteTools.updateGrBit(grbit, fVaried, 0); + updateRecord(); + } + + /** + * returns whether + * the color for each data point and the color and type for each data marker + * vary + * + * @param b + */ + public boolean getVaryColor() { + return fVaried; + } + + /** + * replace the existing chart object with the desired ChartObject, + * effectively changing the type of the chart + * + * @param co + */ + protected void setChartObject(ChartObject co) { + chartArr.remove(0); + chartArr.add(0, (XLSRecord) co); + + } + + /** + * @return truth of "Chart is Three D" + */ + public boolean isThreeD(int chartType) { + if (chartType != ChartConstants.BUBBLECHART) { + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b.getOpcode() == THREED) + return true; + } + } else { + DataFormat df = this.getDataFormatRec(false); + if (df != null) { + return df.getHas3DBubbles(); + } + } + return false; + } + + /** + * return truth of "chart is 3d clustered" + * + * @return + */ + public boolean is3DClustered() { + ThreeD td = (ThreeD) Chart.findRec(this.chartArr, ThreeD.class); + if (td != null) { + return td.isClustered(); + } + return false; + } + + /** + * sets if this chart has clustered bar/columns + * + * @param bIsClustered + */ + public void setIsClustered(boolean bIsClustered) { + ThreeD td = getThreeDRec(false); + if (td != null) { + td.setIsClustered(bIsClustered); + } else { + if (chartArr.get(0).getOpcode() == BAR) { + ((Bar) chartArr.get(0)).setIsClustered(); + } + } + + } + + /** + * sets the Space between points (50 or 150 is default) + * + * @param gap + */ + public void setGapDepth(int gap) { + ThreeD td = getThreeDRec(true); + td.setPcGap(gap); + } + + /** + * returns an int representing the space between points in a 3d area, bar or line chart, or 0 if not 3d + * + * @return + */ + public int getGapDepth() { + ThreeD td = getThreeDRec(false); + if (td != null) + td.getPcGap(); + return 0; + } + + /** + * return ThreeD options in XML form + * + * @return String XML + */ + public String getThreeDXML() { + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b.getOpcode() == THREED) + return ((ThreeD) b).getOptionsXML(); + } + return ""; + } + + /** + * percentage=distance of pie slice from center of pie as % + * + * @param p + */ + public void setPercentage(int p) { + DataFormat df = this.getDataFormatRec(true); + df.setPercentage(p); + } + + /** + * bar shape: + *
                the shape is as follows: + * public static final int SHAPECOLUMN= 0; // default + * public static final int SHAPECYLINDER= 1; + * public static final int SHAPEPYRAMID= 256; + * public static final int SHAPECONE= 257; + * public static final int SHAPEPYRAMIDTOMAX= 516; + * public static final int SHAPECONETOMAX= 517; + * + * @param shape + */ + public void setBarShape(int shape) { + DataFormat df = this.getDataFormatRec(true); + // THIS DOES NOT MAKE SENSE ACCORDING TO DOC BUT IS WHAT EXCEL DOES + df.setPointNumber(0); + df.setSeriesIndex(0); + df.setSeriesNumber(-3); + df.setShape(shape); + } + + /** + * sets chart options such as threed options, grid lines, etc ... these options + * are distinct from chart-type-specific options, which are handled by the + * appropriate chart record (pie, bar ...) + * + * @param op String option name + * @param val Object value + */ + public void setOption(String op, String val) { + if (op.equalsIgnoreCase("Percentage")) { + setPercentage(Short.valueOf(val).shortValue()); + } else if (op.equalsIgnoreCase("Shape")) { + setBarShape(Integer.parseInt(val)); + } else if (op.equals("ShowBubbleSizes") + || // TextDisp options + op.equals("ShowLabelPct") || op.equals("ShowCatLabel") + || op.equals("ShowPct") || op.equals("Rotation") || + // op.equals("ShowValue") || unknown + op.equals("Label") || op.equals("TextRotation")) { + TextDisp td = getDataLegendTextDisp(0); + td.setChartOption(op, val); + } else if (op.equals("Perspective") + || // ThreeD options + op.equals("Cluster") || op.equals("ThreeDScaling") + || op.equals("TwoDWalls") || op.equals("PcGap") + || op.equals("PcDepth") || op.equals("PcHeight") + || op.equals("PcDist") || op.equals("AnElev") + || op.equals("AnRot")) { + ThreeD td = this.getThreeDRec(true); + td.setChartOption(op, val); + } else if (op.equals("ShowValueLabel") + || // Attached Label Options + op.equals("ShowValueAsPercent") + || op.equals("ShowLabelAsPercent") + || op.equals("ShowLabel") + || op.equals("ShowSeriesName") || op.equals("ShowBubbleLabel")) { + DataFormat df = this.getDataFormatRec(true); + df.setDataLabels(op); + } else if (op.equalsIgnoreCase("SmoothedLine") + || op.equalsIgnoreCase("ThreeDBubbles") + || op.equalsIgnoreCase("ArShadow")) { + DataFormat df = this.getDataFormatRec(true); + if (op.equalsIgnoreCase("SmoothedLine")) df.setSmoothLines(true); + else if (op.equalsIgnoreCase("ThreeDBubbles")) df.setHas3DBubbles(true); + else df.setHasShadow(true); + } + } + + /** + * Return the ThreeD rec associated with this ChartFormat, create if not + * present + */ + // NOTES: + // The Chart3d record specifies that the plot area of the chart group is rendered in a 3-D scene + // and also specifies the attributes of the 3-D plot area. + // The preceding chart group type MUST be of type bar, pie, line, area, or surface. + public ThreeD getThreeDRec(boolean bCreate) { + ThreeD td = (ThreeD) Chart.findRec(this.chartArr, ThreeD.class); + if (td == null && bCreate) { // add ThreeD rec + for (int i = 0; i < this.chartArr.size(); i++) { + BiffRec b = this.chartArr.get(i); + if (b.getOpcode() == CHARTFORMATLINK) { + if ((i + 1) < chartArr.size() && ((BiffRec) this.chartArr.get(i + 1)).getOpcode() == SERIESLIST) + i++; // rare that SeriesList record appears + td = (ThreeD) ThreeD.getPrototype(); + td.setParentChart(this.getParentChart()); + this.chartArr.add(i + 1, td); + break; + } + } + } + return td; + } + + /** + * Add or Retrieve TextDisp and assoc records specific for Data Legends + * + * @return + */ + private TextDisp getDataLegendTextDisp(int type) { + int i = Chart.findRecPosition(this.chartArr, Legend.class); + TextDisp td = null; + if (this.chartArr.size() <= (i + 1) + || this.chartArr.get(i + 1).getClass() != DefaultText.class) { // then add one + DefaultText d = (DefaultText) DefaultText.getPrototype(); + d.setType((short) type); + d.setParentChart(this.getParentChart()); + this.chartArr.add(++i, d); + td = (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_DATAPOINTS, + "", this.getWorkBook()); + td.setParentChart(this.getParentChart()); + this.chartArr.add(++i, td); + } else { + DefaultText d = (DefaultText) this.chartArr.get(i + 1); + if (d.getType() != type) { // / add a new one + i += 2; // add after TextDisp + d = (DefaultText) DefaultText.getPrototype(); + d.setType((short) type); + d.setParentChart(this.getParentChart()); + this.chartArr.add(++i, d); + td = (TextDisp) TextDisp.getPrototype( + ObjectLink.TYPE_DATAPOINTS, "", this.getWorkBook()); + td.setParentChart(this.getParentChart()); + this.chartArr.add(++i, td); + } else { // it's the correct one + i += 2; + td = (TextDisp) this.chartArr.get(i); + } + } + return td; + } + + /** + * Gets the dataformat record associated with this ChartFormat If none + * present, creates a basic DataFormat set of records DataFormat controls + * Data Labels, % Distance from sections, line formats ... + * + * @return DataFormat Record + */ + private DataFormat getDataFormatRec(boolean bCreate) { + DataFormat df = (DataFormat) Chart.findRec(this.chartArr, DataFormat.class); + if (df == null && bCreate) { // create dataformat + df = (DataFormat) DataFormat.getPrototypeWithFormatRecs(this.getParentChart()); + this.addChartRecord(df); + } + return df; + } + + + /** + * return the Data Labels chosen for this chart, if any can be one or more + * of:
                + * Value
                + * ValuePerecentage
                + * CategoryPercentage
                + * CategoryLabel
                + * BubbleLabel
                + * SeriesLabel or an empty string if no data labels are chosen for the chart + * + * @return + */ + public String getDataLabels() { + DataFormat df = this.getDataFormatRec(false); + if (df != null) { + return df.getDataLabelType(); + } + return ""; + } + + /** + * returns true if this chart displays lines (Line, Scatter, Radar) + * + * @return true if chart has lines (see Scatter, Line chart ...) + */ + public boolean getHasLines() { + DataFormat df = this.getDataFormatRec(false); + if (df != null) { + return df.getHasLines(); + } + return false; + } + + + /** + * sets this chart to have default lines (Scatter, Line chart ...) + */ + public void setHasLines() { + DataFormat df = this.getDataFormatRec(true); + df.setHasLines(); + } + + + /** + * sets this chart to have lines (Scatter, Line chart ...) of style lineStyle + *
                Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + */ + public void setHasLines(int lineStyle) { + DataFormat df = this.getDataFormatRec(true); + df.setHasLines(lineStyle); + } + + /** + * returns true if this chart has smoothed lines (Scatter, Line, Radar) + * + * @return + */ + public boolean getHasSmoothLines() { + DataFormat df = this.getDataFormatRec(false); + if (df != null) { + return df.getSmoothedLines(); + } + return false; + } + + + /** + * sets this chart to have smoothed lines (Scatter, Line, Radar) + */ + public void setHasSmoothLines(boolean b) { + DataFormat df = this.getDataFormatRec(true); + df.setSmoothLines(b); + } + + /** + * returns true if chart has Drop Lines + * + * @return + */ + public boolean getHasDropLines() { /* chartline: line, chartformatlink, , <3d>, , chartline, lineformat, startblock, shapepropsstream, [-92, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -456,470 +465,476 @@ public boolean getHasDropLines() { endblock dropbar= chartline, lineformat - */ - return false; - } - - /** - * sets this chart (Area, Line or Stock) to have Drop Lines - */ - public void setHasDropLines() { - - } - - /** - * sets 3d bubble state - * @param has3dBubbles - */ - public void setHas3DBubbles(boolean has3dBubbles) { - DataFormat df= this.getDataFormatRec(true); - df.setHas3DBubbles(has3dBubbles); - - } - - /** - * return data label options as an int
                - * can be one or more of:
                - * SHOWVALUE= 0x1;
                - * SHOWVALUEPERCENT= 0x2;
                - * SHOWCATEGORYPERCENT= 0x4;
                - * SHOWCATEGORYLABEL= 0x10;
                - * SHOWBUBBLELABEL= 0x20;
                - * SHOWSERIESLABEL= 0x40; - * - * @see AttachedLabel - * @return a combination of data label options above or 0 if none - * - * NOTE: this returns the Data Labels settings for the entire chart, - * not a particular series - */ - public int getDataLabelsInt() { - int datalabels = 0; - int z= Chart.findRecPosition(this.chartArr, DataLabExtContents.class); - - // here we are assuming that the TextDisp is of the proper ObjectLink=4 type ... - if (z>0 && this.chartArr.get(z-1) instanceof TextDisp) { // Extended Label -- add to attachedlabel, if any - DataLabExtContents dl = (DataLabExtContents) chartArr.get(z); - datalabels = dl.getTypeInt(); // if so, no fontx record ... use default??? - } - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - datalabels |= df.getDataLabelTypeInt(); - } - return datalabels; - } - - /** - sets the data labels for the entire chart (as opposed to a specific series/data point). - A combination of: -

              • SHOWVALUE= 0x1; -
              • SHOWVALUEPERCENT= 0x2; -
              • SHOWCATEGORYPERCENT= 0x4; -
              • SHOWCATEGORYLABEL= 0x10; -
              • SHOWBUBBLELABEL= 0x20; -
              • SHOWSERIESLABEL= 0x40; */ - public void setHasDataLabels(int dl) { - DataFormat df = this.getDataFormatRec(true); - df.setHasDataLabels(dl); - } - - /** - * returns the bar shape for a column or bar type chart can be one of:
                - * ChartConstants.SHAPECOLUMN default
                - * ChartConstants.SHAPECONE
                - * ChartConstants.SHAPECONETOMAX
                - * ChartConstants.SHAPECYLINDER
                - * ChartConstants.SHAPEPYRAMID
                - * ChartConstants.SHAPEPYRAMIDTOMAX - * - * @return - */ - public int getBarShape() { - int shape = SHAPEDEFAULT; - DataFormat df = this.getDataFormatRec(false); - if (df != null) - shape = df.getShape(); - return shape; - } - - /** - * returns type of marker, if any
                - * 0 = no marker
                - * 1 = square
                - * 2 = diamond
                - * 3 = triangle
                - * 4 = X
                - * 5 = star
                - * 6 = Dow-Jones
                - * 7 = standard deviation
                - * 8 = circle
                - * 9 = plus sign - * - * @return - */ - public int getMarkerFormat() { - int markertype = 8; // default= circles - // default actually looks like: 2, 1, 5, 4 ... - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getMarkerFormat(); - } - return 0; - } - - /** - * 0 = no marker
                - * 1 = square
                - * 2 = diamond
                - * 3 = triangle
                - * 4 = X
                - * 5 = star
                - * 6 = Dow-Jones
                - * 7 = standard deviation
                - * 8 = circle
                - * 9 = plus sign - */ - public void setMarkers(int markerFormat) { - DataFormat df = this.getDataFormatRec(true); - df.setMarkerFormat(markerFormat); - } - - /** - * Chart Line options (not available on all charts): - *
                Drop Lines available on Line, Area and Stock charts - *
                HiLow Lines are available on Line and Stock charts - *
                Series Lines are available on Bar and OfPie charts - *
                -	0= drop lines below the data points of Line, Area and Stock charts 
                -	1= High-low lines around the data points of Line and Stock charts 
                -	2- Series Line connecting data points of stacked column and bar charts and OfPie Charts
                -	   
                - *
                - * @param lineType - */ - public ChartLine addChartLines(int lineType) { + return false; + } + + /** + * sets this chart (Area, Line or Stock) to have Drop Lines + */ + public void setHasDropLines() { + + } + + /** + * sets 3d bubble state + * + * @param has3dBubbles + */ + public void setHas3DBubbles(boolean has3dBubbles) { + DataFormat df = this.getDataFormatRec(true); + df.setHas3DBubbles(has3dBubbles); + + } + + /** + * return data label options as an int
                + * can be one or more of:
                + * SHOWVALUE= 0x1;
                + * SHOWVALUEPERCENT= 0x2;
                + * SHOWCATEGORYPERCENT= 0x4;
                + * SHOWCATEGORYLABEL= 0x10;
                + * SHOWBUBBLELABEL= 0x20;
                + * SHOWSERIESLABEL= 0x40; + * + * @return a combination of data label options above or 0 if none + *

                + * NOTE: this returns the Data Labels settings for the entire chart, + * not a particular series + * @see AttachedLabel + */ + public int getDataLabelsInt() { + int datalabels = 0; + int z = Chart.findRecPosition(this.chartArr, DataLabExtContents.class); + + // here we are assuming that the TextDisp is of the proper ObjectLink=4 type ... + if (z > 0 && this.chartArr.get(z - 1) instanceof TextDisp) { // Extended Label -- add to attachedlabel, if any + DataLabExtContents dl = (DataLabExtContents) chartArr.get(z); + datalabels = dl.getTypeInt(); // if so, no fontx record ... use default??? + } + DataFormat df = this.getDataFormatRec(false); + if (df != null) { + datalabels |= df.getDataLabelTypeInt(); + } + return datalabels; + } + + /** + * sets the data labels for the entire chart (as opposed to a specific series/data point). + * A combination of: + *

              • SHOWVALUE= 0x1; + *
              • SHOWVALUEPERCENT= 0x2; + *
              • SHOWCATEGORYPERCENT= 0x4; + *
              • SHOWCATEGORYLABEL= 0x10; + *
              • SHOWBUBBLELABEL= 0x20; + *
              • SHOWSERIESLABEL= 0x40; + */ + public void setHasDataLabels(int dl) { + DataFormat df = this.getDataFormatRec(true); + df.setHasDataLabels(dl); + } + + /** + * returns the bar shape for a column or bar type chart can be one of:
                + * ChartConstants.SHAPECOLUMN default
                + * ChartConstants.SHAPECONE
                + * ChartConstants.SHAPECONETOMAX
                + * ChartConstants.SHAPECYLINDER
                + * ChartConstants.SHAPEPYRAMID
                + * ChartConstants.SHAPEPYRAMIDTOMAX + * + * @return + */ + public int getBarShape() { + int shape = SHAPEDEFAULT; + DataFormat df = this.getDataFormatRec(false); + if (df != null) + shape = df.getShape(); + return shape; + } + + /** + * returns type of marker, if any
                + * 0 = no marker
                + * 1 = square
                + * 2 = diamond
                + * 3 = triangle
                + * 4 = X
                + * 5 = star
                + * 6 = Dow-Jones
                + * 7 = standard deviation
                + * 8 = circle
                + * 9 = plus sign + * + * @return + */ + public int getMarkerFormat() { + int markertype = 8; // default= circles + // default actually looks like: 2, 1, 5, 4 ... + DataFormat df = this.getDataFormatRec(false); + if (df != null) { + return df.getMarkerFormat(); + } + return 0; + } + + /** + * 0 = no marker
                + * 1 = square
                + * 2 = diamond
                + * 3 = triangle
                + * 4 = X
                + * 5 = star
                + * 6 = Dow-Jones
                + * 7 = standard deviation
                + * 8 = circle
                + * 9 = plus sign + */ + public void setMarkers(int markerFormat) { + DataFormat df = this.getDataFormatRec(true); + df.setMarkerFormat(markerFormat); + } + + /** + * Chart Line options (not available on all charts): + *
                Drop Lines available on Line, Area and Stock charts + *
                HiLow Lines are available on Line and Stock charts + *
                Series Lines are available on Bar and OfPie charts + *
                +     * 0= drop lines below the data points of Line, Area and Stock charts
                +     * 1= High-low lines around the data points of Line and Stock charts
                +     * 2- Series Line connecting data points of stacked column and bar charts and OfPie Charts
                +     * 
                + *
                + * + * @param lineType + */ + public ChartLine addChartLines(int lineType) { // ChartLine cl= (ChartLine) Chart.findRec(chartArr, ChartLine.class); // if (cl==null) { - ChartLine cl= null; - // goes After DropBar or legend or 3d (or CFL) - for (int i= chartArr.size()-1; i >= 1; i--) { - BiffRec br= (BiffRec) chartArr.get(i); - short op= br.getOpcode(); - if (op==DROPBAR || op== LEGEND || op==THREED || op==CHARTFORMATLINK) { - cl= (ChartLine) cl.getPrototype(); - cl.setParentChart(this.getParentChart()); - chartArr.add(++i, cl); - cl.setParentChart(this.getParentChart()); // ensure can find - LineFormat lf= (LineFormat) LineFormat.getPrototype(0, 1); - lf.setParentChart(this.getParentChart()); - chartArr.add(++i, lf); - break; - } - } + ChartLine cl = null; + // goes After DropBar or legend or 3d (or CFL) + for (int i = chartArr.size() - 1; i >= 1; i--) { + BiffRec br = chartArr.get(i); + short op = br.getOpcode(); + if (op == DROPBAR || op == LEGEND || op == THREED || op == CHARTFORMATLINK) { + cl = (ChartLine) ChartLine.getPrototype(); + cl.setParentChart(this.getParentChart()); + chartArr.add(++i, cl); + cl.setParentChart(this.getParentChart()); // ensure can find + LineFormat lf = (LineFormat) LineFormat.getPrototype(0, 1); + lf.setParentChart(this.getParentChart()); + chartArr.add(++i, lf); + break; + } + } // } - cl.setLineType(lineType); - return cl; - } - /** - * return ChartLines option, if any - *
                -	0= drop lines below the data points of Line, Area and Stock charts 
                -	1= High-low lines around the data points of Line and Stock charts 
                -	2- Series Line connecting data points of stacked column and bar charts and OfPie Charts
                -	   
                - * @return 0-2 or -1 if no chart lines - */ - public int getChartLines() { - ChartLine cl= (ChartLine) Chart.findRec(chartArr, ChartLine.class); - if (cl!=null) - return cl.getLineType(); - return -1; - } - - /** - * return the record governing chart lines: dropLines, Hi-low lines or Series Lines - * @return - */ - public ChartLine getChartLinesRec() { - return (ChartLine) Chart.findRec(chartArr, ChartLine.class); - } - - public ChartLine getChartLinesRec(int id) { - int i= Chart.findRecPosition(chartArr, ChartLine.class); - if (i >-1) { - while (i < chartArr.size()) { - if (((BiffRec) chartArr.get(i)).getOpcode()==CHARTLINE) { - ChartLine cl= (ChartLine) chartArr.get(i); - if (cl.getLineType()==id) - return cl; - } else - break; - i+=2; - } - } - return null; - } - - /** - * add up/down bars (line, area stock) - */ - public void addUpDownBars() { - if (Chart.findRec(chartArr, Dropbar.class)==null) { - // create necessary records to describe up/down bars - Dropbar upBar= (Dropbar) Dropbar.getPrototype(); - upBar.setParentChart(this.getParentChart()); - Dropbar downBar= (Dropbar) Dropbar.getPrototype(); - downBar.setParentChart(this.getParentChart()); - LineFormat lf= (LineFormat) LineFormat.getPrototype(); - lf.setParentChart(this.getParentChart()); - AreaFormat af= (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(this.getParentChart()); - upBar.chartArr.add(lf); - upBar.chartArr.add(af); - lf= (LineFormat) LineFormat.getPrototype(); - lf.setParentChart(this.getParentChart()); - af= (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(this.getParentChart()); - downBar.chartArr.add(lf); - downBar.chartArr.add(af); - - // add dropbar records to subarray - for (int i= chartArr.size()-1; i >= 0; i--) { - BiffRec br= (BiffRec) chartArr.get(i); - short op= br.getOpcode(); - if (op==SERIESLIST || op== LEGEND || op==THREED || op==CHARTFORMATLINK) { - chartArr.add(++i, upBar); - chartArr.add(++i, downBar); - break; - } - } - } - } - - - /** - * parse upDownBars OOXML element (controled by 2 DropBar records in this subArray) - *
                Valid for Line and Stock charts only - */ - public void parseUpDownBarsOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - // assume dropbar records are not present ... - addUpDownBars(); - int z= Chart.findRecPosition(chartArr, Dropbar.class); - Dropbar downBar= (Dropbar) chartArr.get(z++); - Dropbar upBar= (Dropbar) chartArr.get(z); - - - try { - Dropbar curbar= null; - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("downBars")) { - curbar= downBar; - } else if (tnm.equals("upBars")) { - curbar= upBar; - } else if (tnm.equals("gapWidth")) { // default=150 - upBar.setGapWidth(Integer.valueOf(xpp.getAttributeValue(0))); // TODO: should this be in 1st dropbar or both?? - downBar.setGapWidth(Integer.valueOf(xpp.getAttributeValue(0))); // TODO: should this be in 1st dropbar or both?? - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - SpPr sppr= (SpPr)SpPr.parseOOXML(xpp, lastTag, bk).cloneElement(); - LineFormat lf= (LineFormat) curbar.chartArr.get(0); - if (lf!=null) - lf.setFromOOXML(sppr); - // TODO: fill AreaFormat with sppr - } - } else if (eventType== XmlPullParser.END_TAG) { - if (xpp.getName().equals("upDownBars")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("parseUpDownBarsOOXML: " + e.toString()); - } - } - - /** - * return the OOXML to define the upDownBars element - *
                defined by 2 Dropbar records in this subarray - *
                Only valid for Line and Stock charts - * @return - */ - public String getUpDownBarOOXML() { - int z= Chart.findRecPosition(chartArr, Dropbar.class); // - if (z==-1) return ""; - StringBuffer ooxml= new StringBuffer(); - try { - Dropbar upBars= (Dropbar) chartArr.get(z++); - Dropbar downBars= (Dropbar) chartArr.get(z); - z++; - ooxml.append(""); - // c:gapWidth - int gw= upBars.getGapWidth(); // TODO: only upBar? Should they match? - if (gw!=150)// default - ooxml.append(""); - // c:upBars - ooxml.append(upBars.getOOXML(true)); - // c:downBars - ooxml.append(downBars.getOOXML(false)); - ooxml.append(""); - } catch (Exception e) { - } - return ooxml.toString(); - } - - - /** - * return the drawing order of this ChartFormat
                - * For multiple charts-in-one, drawing order determines the order of the - * charts - * - * @return - */ - public int getDrawingOrder() { - return drawingOrder; - } - - /** - * set the drawing order of this ChartFormat
                - * For multiple charts-in-one, drawing order determines the order of the - * charts - * - * @param order - */ - public void setDrawingOrder(int order) { - drawingOrder = (short) order; - byte[] b = ByteTools.shortToLEBytes(drawingOrder); - this.getData()[18] = b[0]; - this.getData()[19] = b[1]; - } - - private byte[] PROTOTYPE_BYTES = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - - public static XLSRecord getPrototype() { - ChartFormat cf = new ChartFormat(); - cf.setOpcode(CHARTFORMAT); - cf.setData(cf.PROTOTYPE_BYTES); - cf.init(); - return cf; - } - /** - * access chart-type record and return any options specific for this chart - * in XML form Gathers chart options such as show legend, grid lines, etc - * ... these options are distinct from chart-type-specific options, which - * are handled by the appropriate chart record (pie, bar ...) Also, since - * both ThreeD options and Axis-specific options are quite extensive, these - * are handled separately - * - * @see setChartOption - * @see setOption - * @see getThreeDXML() - * @see getAxesXML() - * @return String of options XML ("" if no options set) - */ - public String getChartOptionsXML() { - boolean bFoundDefaultText0 = false; - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec) chartArr.get(i); - if (b instanceof ChartObject) { - ChartObject co = (ChartObject) b; - if (b instanceof DataFormat) { - // controls Data Legends, % Distance of sections, Line - // Format, Area Format, Bar Shapes ... - DataFormat df = (DataFormat) b; - int shape = df.getShape(); - if (shape != 0) - sb.append(" Shape=\"" + shape + "\""); - for (int z = 0; z < df.chartArr.size(); z++) { - b = (BiffRec) df.chartArr.get(z); - if (b instanceof PieFormat) { - sb.append(((PieFormat) b).getOptionsXML()); - } else if (b instanceof AttachedLabel) { - String type = ((AttachedLabel) b).getType(); - sb.append(" DataLabel=\"" + type + "\""); - } else if (b instanceof Serfmt) { - sb.append(((Serfmt) b).getOptionsXML()); - } else if (b instanceof MarkerFormat) { - sb.append(((MarkerFormat) b).getOptionsXML()); - } - } - } else if (b instanceof DefaultText) { // controls Show Legend + // some Data Legends - if (!bFoundDefaultText0) - bFoundDefaultText0 = (((DefaultText) b).getType() == 0); - if (((DefaultText) b).getType() == 1 && bFoundDefaultText0) - sb.append(" ShowLegendKey=\"true\""); - } - } - } - return sb.toString(); - } - - /** - * get the value of *almost* any chart option (axis options are in Axis) - * - * @param op - * String option e.g. Shadow or Percentage - * @return String value of option - */ - public String getChartOption(String op) { - DataFormat df= this.getDataFormatRec(false); - try { - if (op.equals("Percentage")) { // pieformat - return String.valueOf(df.getPercentage()); - } else if (op.equals("ShowValueLabel") - || // Attached Label Options - op.equals("ShowValueAsPercent") - || op.equals("ShowLabelAsPercent") - || op.equals("ShowLabel") - || op.equals("ShowBubbleLabel")) { - return df.getDataLabelType(op); - } else if (op.equals("ShowBubbleSizes") - || // TextDisp options - op.equals("ShowLabelPct") || op.equals("ShowPct") - || op.equals("ShowCatLabel") - || - // op.equals("ShowValue") || unknown - op.equals("Rotation") || op.equals("Label") - || op.equals("TextRotation")) { - TextDisp td = getDataLegendTextDisp(0); - return td.getChartOption(op); - } else if (op.equals("Perspective") - || // ThreeD options - op.equals("Cluster") || op.equals("ThreeDScaling") - || op.equals("TwoDWalls") || op.equals("PcGap") - || op.equals("PcDepth") || op.equals("PcHeight") - || op.equals("PcDist") || op.equals("AnElev") - || op.equals("AnRot")) { - ThreeD td = this.getThreeDRec(false); - if (td != null) - return td.getChartOption(op); - return ""; - } else if (op.equals("ThreeDBubbles")) { - return String.valueOf(df.getHas3DBubbles()); - } else if (op.equals("ArShadow")) { - return String.valueOf(df.getHasShadow()); - } else if (op.equals("SmoothLines")) { - return String.valueOf(df.getSmoothedLines()); - // TODO: FINSIH REST! - } else if (op.equals("AxisLabels")) { // Radar, RadarArea - } else if (op.equals("BubbleSizeRatio")) { // Scatter - } else if (op.equals("BubbleSize")) { // Scatter - } else if (op.equals("ShowNeg")) { // Scatter - } else if (op.equals("ColorFill")) { // Surface - } else if (op.equals("Shading")) { // Surface - } else if (op.equals("MarkerFormat")) { // MarkerFormat - } - } catch (NullPointerException e) { - - } - return ""; - } + cl.setLineType(lineType); + return cl; + } + + /** + * return ChartLines option, if any + *
                +     * 0= drop lines below the data points of Line, Area and Stock charts
                +     * 1= High-low lines around the data points of Line and Stock charts
                +     * 2- Series Line connecting data points of stacked column and bar charts and OfPie Charts
                +     * 
                + * + * @return 0-2 or -1 if no chart lines + */ + public int getChartLines() { + ChartLine cl = (ChartLine) Chart.findRec(chartArr, ChartLine.class); + if (cl != null) + return cl.getLineType(); + return -1; + } + + /** + * return the record governing chart lines: dropLines, Hi-low lines or Series Lines + * + * @return + */ + public ChartLine getChartLinesRec() { + return (ChartLine) Chart.findRec(chartArr, ChartLine.class); + } + + public ChartLine getChartLinesRec(int id) { + int i = Chart.findRecPosition(chartArr, ChartLine.class); + if (i > -1) { + while (i < chartArr.size()) { + if (((BiffRec) chartArr.get(i)).getOpcode() == CHARTLINE) { + ChartLine cl = (ChartLine) chartArr.get(i); + if (cl.getLineType() == id) + return cl; + } else + break; + i += 2; + } + } + return null; + } + + /** + * add up/down bars (line, area stock) + */ + public void addUpDownBars() { + if (Chart.findRec(chartArr, Dropbar.class) == null) { + // create necessary records to describe up/down bars + Dropbar upBar = (Dropbar) Dropbar.getPrototype(); + upBar.setParentChart(this.getParentChart()); + Dropbar downBar = (Dropbar) Dropbar.getPrototype(); + downBar.setParentChart(this.getParentChart()); + LineFormat lf = (LineFormat) LineFormat.getPrototype(); + lf.setParentChart(this.getParentChart()); + AreaFormat af = (AreaFormat) AreaFormat.getPrototype(); + af.setParentChart(this.getParentChart()); + upBar.chartArr.add(lf); + upBar.chartArr.add(af); + lf = (LineFormat) LineFormat.getPrototype(); + lf.setParentChart(this.getParentChart()); + af = (AreaFormat) AreaFormat.getPrototype(); + af.setParentChart(this.getParentChart()); + downBar.chartArr.add(lf); + downBar.chartArr.add(af); + + // add dropbar records to subarray + for (int i = chartArr.size() - 1; i >= 0; i--) { + BiffRec br = chartArr.get(i); + short op = br.getOpcode(); + if (op == SERIESLIST || op == LEGEND || op == THREED || op == CHARTFORMATLINK) { + chartArr.add(++i, upBar); + chartArr.add(++i, downBar); + break; + } + } + } + } + + + /** + * parse upDownBars OOXML element (controled by 2 DropBar records in this subArray) + *
                Valid for Line and Stock charts only + */ + public void parseUpDownBarsOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { + // assume dropbar records are not present ... + addUpDownBars(); + int z = Chart.findRecPosition(chartArr, Dropbar.class); + Dropbar downBar = (Dropbar) chartArr.get(z++); + Dropbar upBar = (Dropbar) chartArr.get(z); + + + try { + Dropbar curbar = null; + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("downBars")) { + curbar = downBar; + } else if (tnm.equals("upBars")) { + curbar = upBar; + } else if (tnm.equals("gapWidth")) { // default=150 + upBar.setGapWidth(Integer.valueOf(xpp.getAttributeValue(0))); // TODO: should this be in 1st dropbar or both?? + downBar.setGapWidth(Integer.valueOf(xpp.getAttributeValue(0))); // TODO: should this be in 1st dropbar or both?? + } else if (tnm.equals("spPr")) { + lastTag.push(tnm); + SpPr sppr = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk).cloneElement(); + LineFormat lf = (LineFormat) curbar.chartArr.get(0); + if (lf != null) + lf.setFromOOXML(sppr); + // TODO: fill AreaFormat with sppr + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals("upDownBars")) { + lastTag.pop(); + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("parseUpDownBarsOOXML: " + e.toString()); + } + } + + /** + * return the OOXML to define the upDownBars element + *
                defined by 2 Dropbar records in this subarray + *
                Only valid for Line and Stock charts + * + * @return + */ + public String getUpDownBarOOXML() { + int z = Chart.findRecPosition(chartArr, Dropbar.class); // + if (z == -1) return ""; + StringBuffer ooxml = new StringBuffer(); + try { + Dropbar upBars = (Dropbar) chartArr.get(z++); + Dropbar downBars = (Dropbar) chartArr.get(z); + z++; + ooxml.append(""); + // c:gapWidth + int gw = upBars.getGapWidth(); // TODO: only upBar? Should they match? + if (gw != 150)// default + ooxml.append(""); + // c:upBars + ooxml.append(upBars.getOOXML(true)); + // c:downBars + ooxml.append(downBars.getOOXML(false)); + ooxml.append(""); + } catch (Exception e) { + } + return ooxml.toString(); + } + + + /** + * return the drawing order of this ChartFormat
                + * For multiple charts-in-one, drawing order determines the order of the + * charts + * + * @return + */ + public int getDrawingOrder() { + return drawingOrder; + } + + /** + * set the drawing order of this ChartFormat
                + * For multiple charts-in-one, drawing order determines the order of the + * charts + * + * @param order + */ + public void setDrawingOrder(int order) { + drawingOrder = (short) order; + byte[] b = ByteTools.shortToLEBytes(drawingOrder); + this.getData()[18] = b[0]; + this.getData()[19] = b[1]; + } + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + public static XLSRecord getPrototype() { + ChartFormat cf = new ChartFormat(); + cf.setOpcode(CHARTFORMAT); + cf.setData(cf.PROTOTYPE_BYTES); + cf.init(); + return cf; + } + + /** + * access chart-type record and return any options specific for this chart + * in XML form Gathers chart options such as show legend, grid lines, etc + * ... these options are distinct from chart-type-specific options, which + * are handled by the appropriate chart record (pie, bar ...) Also, since + * both ThreeD options and Axis-specific options are quite extensive, these + * are handled separately + * + * @return String of options XML ("" if no options set) + * @see setChartOption + * @see setOption + * @see getThreeDXML() + * @see getAxesXML() + */ + public String getChartOptionsXML() { + boolean bFoundDefaultText0 = false; + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); + if (b instanceof ChartObject) { + ChartObject co = (ChartObject) b; + if (b instanceof DataFormat) { + // controls Data Legends, % Distance of sections, Line + // Format, Area Format, Bar Shapes ... + DataFormat df = (DataFormat) b; + int shape = df.getShape(); + if (shape != 0) + sb.append(" Shape=\"" + shape + "\""); + for (int z = 0; z < df.chartArr.size(); z++) { + b = df.chartArr.get(z); + if (b instanceof PieFormat) { + sb.append(((PieFormat) b).getOptionsXML()); + } else if (b instanceof AttachedLabel) { + String type = ((AttachedLabel) b).getType(); + sb.append(" DataLabel=\"" + type + "\""); + } else if (b instanceof Serfmt) { + sb.append(((Serfmt) b).getOptionsXML()); + } else if (b instanceof MarkerFormat) { + sb.append(((MarkerFormat) b).getOptionsXML()); + } + } + } else if (b instanceof DefaultText) { // controls Show Legend + // some Data Legends + if (!bFoundDefaultText0) + bFoundDefaultText0 = (((DefaultText) b).getType() == 0); + if (((DefaultText) b).getType() == 1 && bFoundDefaultText0) + sb.append(" ShowLegendKey=\"true\""); + } + } + } + return sb.toString(); + } + + /** + * get the value of *almost* any chart option (axis options are in Axis) + * + * @param op String option e.g. Shadow or Percentage + * @return String value of option + */ + public String getChartOption(String op) { + DataFormat df = this.getDataFormatRec(false); + try { + if (op.equals("Percentage")) { // pieformat + return String.valueOf(df.getPercentage()); + } else if (op.equals("ShowValueLabel") + || // Attached Label Options + op.equals("ShowValueAsPercent") + || op.equals("ShowLabelAsPercent") + || op.equals("ShowLabel") + || op.equals("ShowBubbleLabel")) { + return df.getDataLabelType(op); + } else if (op.equals("ShowBubbleSizes") + || // TextDisp options + op.equals("ShowLabelPct") || op.equals("ShowPct") + || op.equals("ShowCatLabel") + || + // op.equals("ShowValue") || unknown + op.equals("Rotation") || op.equals("Label") + || op.equals("TextRotation")) { + TextDisp td = getDataLegendTextDisp(0); + return td.getChartOption(op); + } else if (op.equals("Perspective") + || // ThreeD options + op.equals("Cluster") || op.equals("ThreeDScaling") + || op.equals("TwoDWalls") || op.equals("PcGap") + || op.equals("PcDepth") || op.equals("PcHeight") + || op.equals("PcDist") || op.equals("AnElev") + || op.equals("AnRot")) { + ThreeD td = this.getThreeDRec(false); + if (td != null) + return td.getChartOption(op); + return ""; + } else if (op.equals("ThreeDBubbles")) { + return String.valueOf(df.getHas3DBubbles()); + } else if (op.equals("ArShadow")) { + return String.valueOf(df.getHasShadow()); + } else if (op.equals("SmoothLines")) { + return String.valueOf(df.getSmoothedLines()); + // TODO: FINSIH REST! + } else if (op.equals("AxisLabels")) { // Radar, RadarArea + } else if (op.equals("BubbleSizeRatio")) { // Scatter + } else if (op.equals("BubbleSize")) { // Scatter + } else if (op.equals("ShowNeg")) { // Scatter + } else if (op.equals("ColorFill")) { // Surface + } else if (op.equals("Shading")) { // Surface + } else if (op.equals("MarkerFormat")) { // MarkerFormat + } + } catch (NullPointerException e) { + + } + return ""; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.java b/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.java index 4eef50e..dbca0ce 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,24 +23,24 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; + /** * ChartFormatLink: Not Used. Great. (0x1022) - * */ public class ChartFormatLink extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6928761103400718842L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 6928761103400718842L; + + public void init() { + super.init(); + } - public void init() { - super.init(); - } - - public static XLSRecord getPrototype() { - ChartFormatLink cfl = new ChartFormatLink(); - cfl.setOpcode(CHARTFORMATLINK); - cfl.setData(new byte[] {0,0,0,0,0,0,0,0,0,0}); // unused - return cfl; - } + public static XLSRecord getPrototype() { + ChartFormatLink cfl = new ChartFormatLink(); + cfl.setOpcode(CHARTFORMATLINK); + cfl.setData(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); // unused + return cfl; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.java b/src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.java index 2b70bc3..ca4cfef 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,51 +23,55 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; -/** - CHARTFRTINFO: Chart Future Record Type Info (850h) - - Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. - This record contains information describing the versions of Excel that originally - created and last saved the file, and the FRT IDs that are used in the file. -
                In a file written by Excel 2000 or later, this record appears before the end of CHART - record block and before any other FRT in the Chart record stream. -
                In a file written by Excel 97, this record may be missing or will appear after - the CHART record block. If this record appears after the END record of CHART record block, - the verWriter field is assumed to be 8 for Excel 97 regardless of the actual value - in the record. - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0850h - 6 grbitFrt 2 FRT flags; must be zero - 8 verOriginator 1 Excel version that originally created the file - 9 verWriter 1 Excel version that last saved the file - 10 cCFRTID 2 Count of FRT ID value ranges in list - 12 rgCFRTID var List of FRT ID values used for charts - - CFRTID Structure - Offset Field Name Size Contents - 0 rtFirst 2 First FRT in range - 2 rtLast 2 Last FRT in range -*/ +/** + * CHARTFRTINFO: Chart Future Record Type Info (850h) + *

                + * Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. + * This record contains information describing the versions of Excel that originally + * created and last saved the file, and the FRT IDs that are used in the file. + *
                In a file written by Excel 2000 or later, this record appears before the end of CHART + * record block and before any other FRT in the Chart record stream. + *
                In a file written by Excel 97, this record may be missing or will appear after + * the CHART record block. If this record appears after the END record of CHART record block, + * the verWriter field is assumed to be 8 for Excel 97 regardless of the actual value + * in the record. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0850h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 verOriginator 1 Excel version that originally created the file + * 9 verWriter 1 Excel version that last saved the file + * 10 cCFRTID 2 Count of FRT ID value ranges in list + * 12 rgCFRTID var List of FRT ID values used for charts + *

                + * CFRTID Structure + * Offset Field Name Size Contents + * 0 rtFirst 2 First FRT in range + * 2 rtLast 2 Last FRT in range + */ public class ChartFrtInfo extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7722730813154117198L; - public void init() { - super.init(); - } - private byte[] PROTOTYPE_BYTES = new byte[] {80, 8, 0, 0, 10, 10, 3, 0, 80, 8, 90, 8, 97, 8, 97, 8, 106, 8, 107, 8 }; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 7722730813154117198L; + + public void init() { + super.init(); + } + + private byte[] PROTOTYPE_BYTES = new byte[]{80, 8, 0, 0, 10, 10, 3, 0, 80, 8, 90, 8, 97, 8, 97, 8, 106, 8, 107, 8}; + public static XLSRecord getPrototype() { - ChartFrtInfo cri= new ChartFrtInfo(); + ChartFrtInfo cri = new ChartFrtInfo(); cri.setOpcode(CHARTFRTINFO); cri.setData(cri.PROTOTYPE_BYTES); cri.init(); return cri; } + private void updateRecord() { } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartLine.java b/src/main/java/io/starter/formats/XLS/charts/ChartLine.java index 0afd05b..7511751 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartLine.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartLine.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,156 +22,158 @@ */ package io.starter.formats.XLS.charts; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.OOXML.SpPr; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; + /** * ChartLine: Drop/Hi-Lo/Series Lines on a Line Chart (0x101c) - * - * The CrtLine record specifies the presence of drop lines, high-low lines, series lines or leader lines on the chart group. + *

                + * The CrtLine record specifies the presence of drop lines, high-low lines, series lines or leader lines on the chart group. * This record is followed by a LineFormat record which specifies the format of the lines. - * - *
                id (2 bytes): An unsigned integer that specifies the type of line that is present on the chart group. - * This field value MUST be unique among the other id field values in CrtLine records in the current chart group. + * + *
                id (2 bytes): An unsigned integer that specifies the type of line that is present on the chart group. + * This field value MUST be unique among the other id field values in CrtLine records in the current chart group. * This field MUST be greater than the id field values in preceding CrtLine records in the current chart group. MUST be a value from the following table: - - Value Type of Line - 0x0000 Drop lines below the data points of line, area, and stock chart groups. - 0x0001 High-low lines around the data points of line and stock chart groups. - 0x0002 Series lines connecting data points of stacked column and bar chart groups, and the primary pie to the secondary bar/pie of bar of pie and pie of pie chart groups. - 0x0003 Leader lines with non-default formatting connecting data labels to the data point of pie and pie of pie chart groups. - * - * But also there is: - * DROPBAR = DropBar Begin LineFormat AreaFormat [GELFRAME] [SHAPEPROPS] End - * + *

                + * Value Type of Line + * 0x0000 Drop lines below the data points of line, area, and stock chart groups. + * 0x0001 High-low lines around the data points of line and stock chart groups. + * 0x0002 Series lines connecting data points of stacked column and bar chart groups, and the primary pie to the secondary bar/pie of bar of pie and pie of pie chart groups. + * 0x0003 Leader lines with non-default formatting connecting data labels to the data point of pie and pie of pie chart groups. + *

                + * But also there is: + * DROPBAR = DropBar Begin LineFormat AreaFormat [GELFRAME] [SHAPEPROPS] End */ public class ChartLine extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8311605814020380069L; - private short id; - private ChartFormat cf; // necessary to link to corresponding ChartFormat - public static byte TYPE_DROPLINE= 0; - public static byte TYPE_HILOWLINE= 1; - public static byte TYPE_SERIESLINE= 2; - public static byte TYPE_LEADERLINE= 3; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8311605814020380069L; + private short id; + private ChartFormat cf; // necessary to link to corresponding ChartFormat + public static byte TYPE_DROPLINE = 0; + public static byte TYPE_HILOWLINE = 1; + public static byte TYPE_SERIESLINE = 2; + public static byte TYPE_LEADERLINE = 3; + + public void init() { + super.init(); + id = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + } + + public static XLSRecord getPrototype() { + ChartLine cl = new ChartLine(); + cl.setOpcode(CHARTLINE); + cl.setData(new byte[]{0, 0}); + cl.init(); + return cl; + } + + /** + *
                0= drop lines below the data points of line, area and stock charts + *
                1= High-low lines around the data points of line and stock charts + *
                2- Series Line connecting data points of stacked column and bar charts + some pie chart configurations + *
                3= Leader lines with non-default formatting for pie and pie of pie + * + * @return + */ + public int getLineType() { + return id; + } + + /** + * sets the chart line type: + *
                0= drop lines below the data points of line, area and stock charts + *
                1= High-low lines around the data points of line and stock charts + *
                2- Series Line connecting data points of stacked column and bar charts + some pie chart configurations + *
                3= Leader lines with non-default formatting for pie and pie of pie + */ + public void setLineType(int id) { + this.id = (short) id; + byte[] b = ByteTools.shortToLEBytes(this.id); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } + + /** + * return the LineFormat rec associated with this ChartLine + * (== the next record in the cf chart array) + * + * @return + */ + private LineFormat findLineFormatRec() { + LineFormat lf = null; + for (int i = 0; i < cf.chartArr.size(); i++) { + if (cf.chartArr.get(i).equals(this)) { + lf = (LineFormat) cf.chartArr.get(i + 1); + break; + } + } + return lf; + } - public void init() { - super.init(); - id= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - - public static XLSRecord getPrototype() { - ChartLine cl= new ChartLine(); - cl.setOpcode(CHARTLINE); - cl.setData(new byte[] {0, 0}); - cl.init(); - return cl; - } - - /** - *
                0= drop lines below the data points of line, area and stock charts - *
                1= High-low lines around the data points of line and stock charts - *
                2- Series Line connecting data points of stacked column and bar charts + some pie chart configurations - *
                3= Leader lines with non-default formatting for pie and pie of pie - * @return - */ - public int getLineType() { - return id; - } - - /** - * sets the chart line type: - *
                0= drop lines below the data points of line, area and stock charts - *
                1= High-low lines around the data points of line and stock charts - *
                2- Series Line connecting data points of stacked column and bar charts + some pie chart configurations - *
                3= Leader lines with non-default formatting for pie and pie of pie - */ - public void setLineType(int id) { - this.id=(short) id; - byte[] b= ByteTools.shortToLEBytes(this.id); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; - } - - /** - * return the LineFormat rec associated with this ChartLine - * (== the next record in the cf chart array) - * @return - */ - private LineFormat findLineFormatRec() { - LineFormat lf= null; - for (int i= 0; i < cf.chartArr.size(); i++) { - if (cf.chartArr.get(i).equals(this)) { - lf= (LineFormat) cf.chartArr.get(i+1); - break; - } - } - return lf; - } + /** + * parse a Chart Line OOXML element: either + *

              • dropLines + *
              • hiLowLines + *
              • serLines + * + * @param xpp + * @param lastTag + * @param cf + */ + public void parseOOXML(XmlPullParser xpp, Stack lastTag, ChartFormat cf, WorkBookHandle bk) { + this.cf = cf; + String endTag = lastTag.peek(); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("spPr")) { + lastTag.push(tnm); + SpPr sppr = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk).cloneElement(); + LineFormat lf = findLineFormatRec(); + if (lf != null) + lf.setFromOOXML(sppr); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals(endTag)) + lastTag.pop(); + break; + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("ChartLine.parseOOXML: " + e.toString()); + } + } - /** - * parse a Chart Line OOXML element: either - *
              • dropLines - *
              • hiLowLines - *
              • serLines - * - * @param xpp - * @param lastTag - * @param cf - */ - public void parseOOXML(XmlPullParser xpp, Stack lastTag, ChartFormat cf, WorkBookHandle bk) { - this.cf= cf; - String endTag= (String) lastTag.peek(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spPr")) { - lastTag.push(tnm); - SpPr sppr= (SpPr)SpPr.parseOOXML(xpp, lastTag, bk).cloneElement(); - LineFormat lf= findLineFormatRec(); - if (lf!=null) - lf.setFromOOXML(sppr); - } - } else if (eventType== XmlPullParser.END_TAG) { - if (xpp.getName().equals(endTag)) - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ChartLine.parseOOXML: " + e.toString()); - } - } - - /** - * return the OOXML to define this ChartLine - * @return - */ - public StringBuffer getOOXML() { - StringBuffer cooxml= new StringBuffer(); - String tag= null; - if (id==TYPE_DROPLINE) - tag= "c:dropLines>"; - else if (id==TYPE_HILOWLINE) - tag= "c:hiLowLines>"; - else if (id==TYPE_SERIESLINE) - tag= "c:serLines>"; - cooxml.append("<" + tag); - LineFormat lf= findLineFormatRec(); - if (lf!=null) - cooxml.append(lf.getOOXML()); - cooxml.append(""; + else if (id == TYPE_HILOWLINE) + tag = "c:hiLowLines>"; + else if (id == TYPE_SERIESLINE) + tag = "c:serLines>"; + cooxml.append("<" + tag); + LineFormat lf = findLineFormatRec(); + if (lf != null) + cooxml.append(lf.getOOXML()); + cooxml.append(". @@ -28,10 +28,10 @@ * ChartObject is an interface for chart records that can be described as an object. Primarily * this is just a heierarchial system. Chart objects contain an array of records that could be value records * and/or other chartObjects containing their own arrays. - * - * -*/ -import io.starter.formats.XLS.*; + */ + +import io.starter.formats.XLS.XLSRecord; + import java.util.ArrayList; public interface ChartObject { @@ -40,30 +40,30 @@ public interface ChartObject { * Add a chart record to this chart object * */ - public void addChartRecord(XLSRecord b); - + void addChartRecord(XLSRecord b); + /** * Return a list of chart records from this chart object. This should include begin/end records, along with * those of the objects sub-object, correctly ordered. * @return */ - public ArrayList getChartRecords(); - + ArrayList getChartRecords(); + /** * Return the chart record that this object is associated with * @return */ - public Chart getParentChart(); - + Chart getParentChart(); + /** * Set the parent chart for this record */ - public void setParentChart(Chart c); - - + void setParentChart(Chart c); + + /** * Get the output array of records, including begin/end records and those of it's children. */ - public ArrayList getRecordArray(); - + ArrayList getRecordArray(); + } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java b/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java index 9e5d91a..b375d0f 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,17 +22,6 @@ */ package io.starter.formats.XLS.charts; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Stack; -import java.util.Vector; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.CellHandle; import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.ExcelTools; @@ -40,294 +29,302 @@ import io.starter.formats.OOXML.DLbls; import io.starter.formats.OOXML.DPt; import io.starter.formats.OOXML.Marker; -import io.starter.formats.OOXML.OOXMLConstants; import io.starter.formats.OOXML.SpPr; -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.formats.XLS.WorkBookException; -import io.starter.formats.XLS.XLSConstants; -import io.starter.formats.XLS.XLSRecord; -import io.starter.formats.XLS.Xf; +import io.starter.formats.XLS.*; import io.starter.formats.XLS.formulas.Ptg; import io.starter.toolkit.CompatibleVector; import io.starter.toolkit.Logger; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.xmlpull.v1.XmlPullParser; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Stack; +import java.util.Vector; public class ChartSeries implements ChartConstants, Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7862828186455339066L; - private ArrayList series= new ArrayList(); // ALL series in chart MAPPED to chart that "owns" it - private JSONArray seriesJSON= null; // save series JSON for update comparisons later - protected double[] minmaxcache= null; // stores minimum/maximum/minor/major for chart scale; cached; - protected String[] legends; - protected ArrayList seriesranges; - protected Object[] categories; - protected ArrayList seriesvalues; - protected String[] seriescolors; - protected Chart parentChart; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7862828186455339066L; + private ArrayList series = new ArrayList(); // ALL series in chart MAPPED to chart that "owns" it + private JSONArray seriesJSON = null; // save series JSON for update comparisons later + protected double[] minmaxcache = null; // stores minimum/maximum/minor/major for chart scale; cached; + protected String[] legends; + protected ArrayList seriesranges; + protected Object[] categories; + protected ArrayList seriesvalues; + protected String[] seriescolors; + protected Chart parentChart; // protected transient WorkBookHandle wbh; /** * series stores new Object[] {Series Record, Integer.valueOf(nCharts)} + * * @param o */ - public void add(Object[] o) { - series.add(o); - } + public void add(Object[] o) { + series.add(o); + } - public void setParentChart(Chart c) { - parentChart= c; - } + public void setParentChart(Chart c) { + parentChart = c; + } // public void setWorkBook(WorkBookHandle wbh) { this.wbh= wbh; } - /** - * returns an array containing the and maximum values of Y (Value) axis, along with the - * maximum number of series values of each series (bar, line, etc.) ... - *
                sets all series cache values: seriesvalues, seriesranges, seriescolors, legends and minmaxcache - * + + /** + * returns an array containing the and maximum values of Y (Value) axis, along with the + * maximum number of series values of each series (bar, line, etc.) ... + *
                sets all series cache values: seriesvalues, seriesranges, seriescolors, legends and minmaxcache + * *
                Note: this will only reset cached values if isDirty + * * @return double[] */ public double[] getMetrics(boolean isDirty) { - if (!isDirty && minmaxcache!=null) return minmaxcache; - // trap minimum, maximum + number of series - ChartType co= parentChart.getChartObject(); // default chart object TODO: overlay charts - seriesvalues = new ArrayList(); - seriesranges= new ArrayList(); - io.starter.formats.XLS.Boundsheet sht= parentChart.getSheet(); - java.util.Vector s = this.getAllSeries(-1); - // Category values ******************************************************************************* - if (s.size() > 0) { - try { - CellRange cr = new CellRange(((Series) s.get(0)).getCategoryValueAi().toString(), parentChart.wbh, true); - CellHandle[] ch = cr.getCells(); - if (ch != null) { // found a template with a chart with series but no categories - categories = new Object[ch.length]; - for (int j = 0; j < ch.length; j++) { - try { - categories[j] = ch[j].getFormattedStringVal(true); - } catch (IllegalArgumentException e) { // catch format exceptions - categories[j]= ch[j].getStringVal(); - } - } - } else if (s.size() > 0) { - cr = new CellRange(((Series) s.get(0)).getSeriesValueAi().toString(), parentChart.wbh, true); - int sz= cr.getCells().length; - categories= new Object[sz]; - for (int j= 0; j < sz; j++) - categories[j]= Integer.valueOf(j + 1).toString(); - } - } catch (Exception e) { - Logger.logWarn("ChartSeries.getMinMax: " + e.toString()); - } - } - // Series colors, labels and values *************************************************************** - double yMax = 0.0, yMin = Double.MAX_VALUE; - int nseries = 0; - seriescolors = null; - legends = null; - int charttype = co.getChartType(); - // obtain/store series colors, store series values and trap maximum and - // minimun values so can be used below for axis scale - /* - * A Scatter chart has two value axes, showing one set of numerical data along the x-axis and another along the y-axis. - * It combines these values into single data points and displays them in uneven intervals, or clusters - */ - if (charttype != PIECHART && charttype != DOUGHNUTCHART) { - seriescolors = new String[s.size()]; - legends = new String[s.size()]; - for (int i = 0; i < s.size(); i++) { - Series myseries = ((Series) s.get(i)); - seriescolors[i] = myseries.getSeriesColor(); - legends[i] = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegendText()).toString(); - CellRange cr = new CellRange(myseries.getSeriesValueAi().toString(), parentChart.wbh, true); - CellHandle[] ch = cr.getCells(); - nseries = Math.max(nseries, ch.length); - double[] seriesvals; - String[] sranges; - // String[] series_strings; - if (!myseries.hasBubbleSizes()) { - seriesvals= new double[nseries]; - sranges= new String[nseries]; - } else { - seriesvals = new double[nseries * 2]; - sranges= new String[nseries * 2]; - } - // series_strings = new String[seriesvals.length]; - - for (int j = 0; j < ch.length; j++) { - try { - sranges[j]= ch[j].getCellAddressWithSheet(); - seriesvals[j] = ch[j].getDoubleVal(); - if (Double.isNaN(seriesvals[j])) - seriesvals[j] = 0.0; - yMax = Math.max(yMax, seriesvals[j]); - yMin = Math.min(yMin, seriesvals[j]); - } catch (NumberFormatException n) { - ; - } - } - if (myseries.hasBubbleSizes()) { // append bubble sizes to series values ... see BubbleChart.getSVG for parsing - int z = ch.length; - CellRange crb= new CellRange(myseries.getBubbleValueAi().toString(), parentChart.wbh, true); - CellHandle[] chb = crb.getCells(); - for (int j = 0; j < ch.length; j++) { - seriesvals[j + z] = chb[j].getDoubleVal(); - sranges[j + z]= chb[j].getCellAddressWithSheet(); - } - } - seriesvalues.add(seriesvals); // trap and add series value points - seriesranges.add(sranges); // trap series range - } - } else if (charttype == DOUGHNUTCHART && s.size() > 1) { // like a PIE chart but can have multiple series - legends = new String[categories.length]; // for PIE/DONUT charts, legends are actually category labels, not series labels - for (int i = 0; i < categories.length; i++) - legends[i] = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(categories[i].toString()).toString(); - for (int i = 0; i < s.size(); i++) { - Series myseries = ((Series) s.get(i)); - // legends[i]= - // io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegend()); - CellRange cr = new CellRange(myseries.getSeriesValueAi().toString(), parentChart.wbh, true); - CellHandle[] ch = cr.getCells(); - double[] seriesvals= new double[ch.length]; - String[] sranges= new String[ch.length]; - if (seriescolors == null) - seriescolors = new String[ch.length]; - for (int j = 0; j < ch.length; j++) { - try { - seriesvals[j] = ch[j].getDoubleVal(); - if (ch[j].getWorkSheetHandle().getMysheet().equals(sht)) - sranges[j]= ch[j].getCellAddress(); - yMax = Math.max(yMax, seriesvals[j]); - yMin = Math.min(yMin, seriesvals[j]); - if (i == 0) { // only do for 1st series; will be the - // same for rest - seriescolors[j] = myseries.getPieSliceColor(j); + if (!isDirty && minmaxcache != null) return minmaxcache; + // trap minimum, maximum + number of series + ChartType co = parentChart.getChartObject(); // default chart object TODO: overlay charts + seriesvalues = new ArrayList(); + seriesranges = new ArrayList(); + io.starter.formats.XLS.Boundsheet sht = parentChart.getSheet(); + java.util.Vector s = this.getAllSeries(-1); + // Category values ******************************************************************************* + if (s.size() > 0) { + try { + CellRange cr = new CellRange(((Series) s.get(0)).getCategoryValueAi().toString(), parentChart.wbh, true); + CellHandle[] ch = cr.getCells(); + if (ch != null) { // found a template with a chart with series but no categories + categories = new Object[ch.length]; + for (int j = 0; j < ch.length; j++) { + try { + categories[j] = ch[j].getFormattedStringVal(true); + } catch (IllegalArgumentException e) { // catch format exceptions + categories[j] = ch[j].getStringVal(); + } + } + } else if (s.size() > 0) { + cr = new CellRange(((Series) s.get(0)).getSeriesValueAi().toString(), parentChart.wbh, true); + int sz = cr.getCells().length; + categories = new Object[sz]; + for (int j = 0; j < sz; j++) + categories[j] = Integer.valueOf(j + 1).toString(); + } + } catch (Exception e) { + Logger.logWarn("ChartSeries.getMinMax: " + e.toString()); + } + } + // Series colors, labels and values *************************************************************** + double yMax = 0.0, yMin = Double.MAX_VALUE; + int nseries = 0; + seriescolors = null; + legends = null; + int charttype = co.getChartType(); + // obtain/store series colors, store series values and trap maximum and + // minimun values so can be used below for axis scale + /* + * A Scatter chart has two value axes, showing one set of numerical data along the x-axis and another along the y-axis. + * It combines these values into single data points and displays them in uneven intervals, or clusters + */ + if (charttype != PIECHART && charttype != DOUGHNUTCHART) { + seriescolors = new String[s.size()]; + legends = new String[s.size()]; + for (int i = 0; i < s.size(); i++) { + Series myseries = ((Series) s.get(i)); + seriescolors[i] = myseries.getSeriesColor(); + legends[i] = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegendText()).toString(); + CellRange cr = new CellRange(myseries.getSeriesValueAi().toString(), parentChart.wbh, true); + CellHandle[] ch = cr.getCells(); + nseries = Math.max(nseries, ch.length); + double[] seriesvals; + String[] sranges; + // String[] series_strings; + if (!myseries.hasBubbleSizes()) { + seriesvals = new double[nseries]; + sranges = new String[nseries]; + } else { + seriesvals = new double[nseries * 2]; + sranges = new String[nseries * 2]; + } + // series_strings = new String[seriesvals.length]; + + for (int j = 0; j < ch.length; j++) { + try { + sranges[j] = ch[j].getCellAddressWithSheet(); + seriesvals[j] = ch[j].getDoubleVal(); + if (Double.isNaN(seriesvals[j])) + seriesvals[j] = 0.0; + yMax = Math.max(yMax, seriesvals[j]); + yMin = Math.min(yMin, seriesvals[j]); + } catch (NumberFormatException n) { + } + } + if (myseries.hasBubbleSizes()) { // append bubble sizes to series values ... see BubbleChart.getSVG for parsing + int z = ch.length; + CellRange crb = new CellRange(myseries.getBubbleValueAi().toString(), parentChart.wbh, true); + CellHandle[] chb = crb.getCells(); + for (int j = 0; j < ch.length; j++) { + seriesvals[j + z] = chb[j].getDoubleVal(); + sranges[j + z] = chb[j].getCellAddressWithSheet(); + } + } + seriesvalues.add(seriesvals); // trap and add series value points + seriesranges.add(sranges); // trap series range + } + } else if (charttype == DOUGHNUTCHART && s.size() > 1) { // like a PIE chart but can have multiple series + legends = new String[categories.length]; // for PIE/DONUT charts, legends are actually category labels, not series labels + for (int i = 0; i < categories.length; i++) + legends[i] = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(categories[i].toString()).toString(); + for (int i = 0; i < s.size(); i++) { + Series myseries = ((Series) s.get(i)); + // legends[i]= + // io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegend()); + CellRange cr = new CellRange(myseries.getSeriesValueAi().toString(), parentChart.wbh, true); + CellHandle[] ch = cr.getCells(); + double[] seriesvals = new double[ch.length]; + String[] sranges = new String[ch.length]; + if (seriescolors == null) + seriescolors = new String[ch.length]; + for (int j = 0; j < ch.length; j++) { + try { + seriesvals[j] = ch[j].getDoubleVal(); + if (ch[j].getWorkSheetHandle().getMysheet().equals(sht)) + sranges[j] = ch[j].getCellAddress(); + yMax = Math.max(yMax, seriesvals[j]); + yMin = Math.min(yMin, seriesvals[j]); + if (i == 0) { // only do for 1st series; will be the + // same for rest + seriescolors[j] = myseries.getPieSliceColor(j); /*if (seriescolors[j] == 0x4D || seriescolors[j] == 0x4E) seriescolors[j] = io.starter.formats.XLS.FormatConstants.COLOR_WHITE;*/ - } - - } catch (NumberFormatException n) { - ; - } - } - seriesvalues.add(seriesvals); // trap and add series value points - seriesranges.add(sranges); // trap series range - } - } else { // PIES - only 1 series - if (s.size() > 0) { - // PIE: 1 series data - CellHandle[] cats = new CellRange(((Series)s.get(0)).getCategoryValueAi().toString(), parentChart.wbh, true).getCells(); - if (cats != null) { - nseries = cats.length; - legends = new String[cats.length]; // for PIE charts, legends are actually category labels, not series labels - for (int i = 0; i < cats.length; i++) - legends[i] = cats[i].getFormattedStringVal(true); - } - seriescolors = new String[nseries]; - Series myseries = ((Series) s.get(0)); - try { - CellRange cr = new CellRange(myseries.getSeriesValueAi().toString(), parentChart.wbh, true); - CellHandle[] ch = cr.getCells(); - // error trap - shouldn't happen - if (ch.length != nseries) { - Logger.logWarn("ChartHandle.getSeriesInfo: unexpected Pie Chart structure"); - nseries = Math.min(nseries, ch.length); - } - double[] seriesvals = new double[nseries]; - String[] sranges= new String[nseries]; - for (int i = 0; i < nseries; i++) { - seriescolors[i] = myseries.getPieSliceColor(i); + } + + } catch (NumberFormatException n) { + } + } + seriesvalues.add(seriesvals); // trap and add series value points + seriesranges.add(sranges); // trap series range + } + } else { // PIES - only 1 series + if (s.size() > 0) { + // PIE: 1 series data + CellHandle[] cats = new CellRange(((Series) s.get(0)).getCategoryValueAi().toString(), parentChart.wbh, true).getCells(); + if (cats != null) { + nseries = cats.length; + legends = new String[cats.length]; // for PIE charts, legends are actually category labels, not series labels + for (int i = 0; i < cats.length; i++) + legends[i] = cats[i].getFormattedStringVal(true); + } + seriescolors = new String[nseries]; + Series myseries = ((Series) s.get(0)); + try { + CellRange cr = new CellRange(myseries.getSeriesValueAi().toString(), parentChart.wbh, true); + CellHandle[] ch = cr.getCells(); + // error trap - shouldn't happen + if (ch.length != nseries) { + Logger.logWarn("ChartHandle.getSeriesInfo: unexpected Pie Chart structure"); + nseries = Math.min(nseries, ch.length); + } + double[] seriesvals = new double[nseries]; + String[] sranges = new String[nseries]; + for (int i = 0; i < nseries; i++) { + seriescolors[i] = myseries.getPieSliceColor(i); /*if (seriescolors[i] == 0x4D || seriescolors[i] == 0x4E) seriescolors[i] = io.starter.formats.XLS.FormatConstants.COLOR_WHITE;*/ - // legends[i]= - // io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegend()); - // // same for every series ... - seriesvals[i] = ch[i].getDoubleVal(); - if (ch[i].getWorkSheetHandle().getMysheet().equals(sht)) - sranges[i]= ch[i].getCellAddress(); - yMax = Math.max(yMax, seriesvals[i]); - yMin = Math.min(yMin, seriesvals[i]); - } - seriesvalues.add(seriesvals); // trap and add series value points - seriesranges.add(sranges); // trap series range - } catch (IllegalArgumentException e) { - ; // error in cell range sheet ... - } - } - } - // For stacked-type charts, must sum values for ymax - if (co.isStacked()) { - // scale is SUM of values, yMax is maximum total per series point - double[] sum = new double[nseries]; - for (int i = 0; i < seriesvalues.size(); i++) { - double[] seriesv = (double[]) seriesvalues.get(i); - for (int j = 0; j < seriesv.length; j++) { - sum[j] = sum[j] + seriesv[j]; - } - } - yMax = 0; - for (int i = 0; i < nseries; i++) { - yMax = Math.max(sum[i], yMax); - } - } - minmaxcache= new double[2]; - minmaxcache[0]= yMin; - minmaxcache[1]= yMax; - // minmaxcache[2]= nSeries; - // minmaxcache= new double[3][]; // 3 possible axes: x, y and z - // minmaxcache[axisType]= minMax; - return minmaxcache; - } - + // legends[i]= + // io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegend()); + // // same for every series ... + seriesvals[i] = ch[i].getDoubleVal(); + if (ch[i].getWorkSheetHandle().getMysheet().equals(sht)) + sranges[i] = ch[i].getCellAddress(); + yMax = Math.max(yMax, seriesvals[i]); + yMin = Math.min(yMin, seriesvals[i]); + } + seriesvalues.add(seriesvals); // trap and add series value points + seriesranges.add(sranges); // trap series range + } catch (IllegalArgumentException e) { + // error in cell range sheet ... + } + } + } + // For stacked-type charts, must sum values for ymax + if (co.isStacked()) { + // scale is SUM of values, yMax is maximum total per series point + double[] sum = new double[nseries]; + for (int i = 0; i < seriesvalues.size(); i++) { + double[] seriesv = (double[]) seriesvalues.get(i); + for (int j = 0; j < seriesv.length; j++) { + sum[j] = sum[j] + seriesv[j]; + } + } + yMax = 0; + for (int i = 0; i < nseries; i++) { + yMax = Math.max(sum[i], yMax); + } + } + minmaxcache = new double[2]; + minmaxcache[0] = yMin; + minmaxcache[1] = yMax; + // minmaxcache[2]= nSeries; + // minmaxcache= new double[3][]; // 3 possible axes: x, y and z + // minmaxcache[axisType]= minMax; + return minmaxcache; + } + /** * Change series ranges for ALL matching series + * * @param originalrange * @param newrange * @return */ - public boolean changeSeriesRange(String originalrange, String newrange){ + public boolean changeSeriesRange(String originalrange, String newrange) { boolean changed = false; - for (int i= 0; i < series.size(); i++) { - Series s= (Series) ((Object[])series.get(i))[0]; + for (int i = 0; i < series.size(); i++) { + Series s = (Series) series.get(i)[0]; Ai ai = s.getSeriesValueAi(); - if (ai !=null) { - if(ai.toString().equalsIgnoreCase(originalrange)){ + if (ai != null) { + if (ai.toString().equalsIgnoreCase(originalrange)) { changed = ai.changeAiLocation(originalrange, newrange); } } } - - return changed; + + return changed; } - + /** * Return a string representing all series in this chart + * * @param nChart 0=default, 1-9= overlay charts -1 for ALL charts * @return */ - public String[] getSeries(int nChart) { + public String[] getSeries(int nChart) { Vector seriesperchart = this.getAllSeries(nChart); String[] retStr = new String[seriesperchart.size()]; - for (int i=0;i chartArr= s.getParentChart().chartArr; - for (int i=0;i chartArr = s.getParentChart().chartArr; + for (int i = 0; i < chartArr.size(); i++) { + BiffRec br = chartArr.get(i); BiffRec br2 = null; - if (ionly used in WorkSheetHandle + * * @param index */ public void removeSeries(int index) { Vector v = this.getAllSeries(); - this.removeSeries((Series) v.get(index)); + this.removeSeries((Series) v.get(index)); } + /** * remove desired series from chart + * * @param index */ public void removeSeries(Series seriestodelete) { - for (int z= 0; z0 = no marker -
                1 = square -
                2 = diamond -
                3 = triangle -
                4 = X -
                5 = star -
                6 = Dow-Jones -
                7 = standard deviation -
                8 = circle -
                9 = plus sign - **/ - public int[] getMarkerFormats() { - int[] markers= new int[series.size()]; - for (int i= 0; i < series.size(); i++) { - Series s= (Series) ((Object[])series.get(i))[0]; - markers[i]= s.getMarkerFormat(); - } - return markers; - } - - - /** - * parse a chartSpace->chartType->ser element into our Series record/structure - * @param xpp XML pullparser positioned at ser element - * @param wbh WorkBookHandle - * @param parentChart parent chart object - * @param lastTag - * @return - */ - public static Series parseOOXML(XmlPullParser xpp, WorkBookHandle wbh, ChartType parentChart, boolean hasPivotTableSource, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - int idx= 0; - int seriesidx= parentChart.getParentChart().getAllSeries().size(); - String[] ranges= {"","","",""}; //legend, cat, ser/value, bubble cell references - String legendText= ""; - SpPr sp= null; - DLbls d= null; - Marker m= null; - boolean smooth= false; - ArrayList dpts= null; - String cache= null; - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ser")) { // Represents a single field in the PivotTable. This complex type contains - idx= 0; - }else if (tnm.equals("order")) {// attr val= order - }else if (tnm.equals("cat") || tnm.equals("xVal")) { // children: CHOICE OF: multiLvlStrRef, numLiteral, numRef, strLit, strRef - idx= 1; - }else if (tnm.equals("val") || tnm.equals("yVal")) { // children: CHOICE OF: numLit, numRef - idx= 2; - } else if (tnm.equals("dLbls")) { // data labels - lastTag.push(tnm); // keep track of element hierarchy - d= (DLbls) DLbls.parseOOXML(xpp, lastTag, wbh).cloneElement(); - if (d.showBubbleSize()) parentChart.setChartOption("ShowBubbleSizes", "1"); - if (d.showCatName()) parentChart.setChartOption("ShowCatLabel", "1"); - if (d.showLeaderLines()) parentChart.setChartOption("ShowLdrLines", "1"); - if (d.showLegendKey()) -; // TODO: handle show legend key - if (d.showPercent()) parentChart.setChartOption("ShowLabelPct", "1"); - if (d.showSerName()) parentChart.setChartOption("ShowLabel", "1"); - if (d.showVal()) parentChart.setChartOption("ShowValueLabel", "1"); - // data label options - } else if (tnm.equals("dPt")) { // data point(s) - if (dpts==null) dpts= new ArrayList(); - lastTag.push(tnm); // keep track of element hierarchy - dpts.add((DPt) DPt.parseOOXML(xpp, lastTag, wbh).cloneElement()); - } else if (tnm.equals("spPr")) { // series spPr - lastTag.push(tnm); // keep track of element hierarchy - sp= (SpPr) SpPr.parseOOXML(xpp, lastTag, wbh).cloneElement(); - } else if (tnm.equals("marker")) { - lastTag.push(tnm); // keep track of element hierarchy - m= (Marker) Marker.parseOOXML(xpp, lastTag, wbh).cloneElement(); - }else if (tnm.equals("bubbleSize")) { - idx= 3; - } else if (tnm.equals("shape")) { // bar only - parentChart.convertShape(xpp.getAttributeValue(0)); - } else if (tnm.equals("smooth")) { // line chart - smooth= (xpp.getAttributeCount()== 0 || !xpp.getAttributeValue(0).equals("0")); - }else if (tnm.equals("explosion")) { - String v= xpp.getAttributeValue(0); - parentChart.setChartOption("Percentage", v); - // NOTE: two types of values; 1- reference denoted by f element parents can be numRef, strRef or multiLvlStrRef - // 2- text value denoted by v element parent is strRef - }else if (tnm.equals("formatCode")) { // part of numCache element - Xf.addFormatPattern(wbh.getWorkBook(),OOXMLAdapter.getNextText(xpp)); + Vector seriesperchart = this.getAllSeries(nChart); + String[] ret = new String[seriesperchart.size()]; + for (int i = 0; i < seriesperchart.size(); i++) { + ret[i] = ((Series) seriesperchart.get(i)).getLegendText(); + } + return ret; + } + + /** + * return the type of markers for each series: + *
                0 = no marker + *
                1 = square + *
                2 = diamond + *
                3 = triangle + *
                4 = X + *
                5 = star + *
                6 = Dow-Jones + *
                7 = standard deviation + *
                8 = circle + *
                9 = plus sign + **/ + public int[] getMarkerFormats() { + int[] markers = new int[series.size()]; + for (int i = 0; i < series.size(); i++) { + Series s = (Series) series.get(i)[0]; + markers[i] = s.getMarkerFormat(); + } + return markers; + } + + + /** + * parse a chartSpace->chartType->ser element into our Series record/structure + * + * @param xpp XML pullparser positioned at ser element + * @param wbh WorkBookHandle + * @param parentChart parent chart object + * @param lastTag + * @return + */ + public static Series parseOOXML(XmlPullParser xpp, WorkBookHandle wbh, ChartType parentChart, boolean hasPivotTableSource, Stack lastTag) { + try { + int eventType = xpp.getEventType(); + int idx = 0; + int seriesidx = parentChart.getParentChart().getAllSeries().size(); + String[] ranges = {"", "", "", ""}; //legend, cat, ser/value, bubble cell references + String legendText = ""; + SpPr sp = null; + DLbls d = null; + Marker m = null; + boolean smooth = false; + ArrayList dpts = null; + String cache = null; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + if (tnm.equals("ser")) { // Represents a single field in the PivotTable. This complex type contains + idx = 0; + } else if (tnm.equals("order")) {// attr val= order + } else if (tnm.equals("cat") || tnm.equals("xVal")) { // children: CHOICE OF: multiLvlStrRef, numLiteral, numRef, strLit, strRef + idx = 1; + } else if (tnm.equals("val") || tnm.equals("yVal")) { // children: CHOICE OF: numLit, numRef + idx = 2; + } else if (tnm.equals("dLbls")) { // data labels + lastTag.push(tnm); // keep track of element hierarchy + d = (DLbls) DLbls.parseOOXML(xpp, lastTag, wbh).cloneElement(); + if (d.showBubbleSize()) parentChart.setChartOption("ShowBubbleSizes", "1"); + if (d.showCatName()) parentChart.setChartOption("ShowCatLabel", "1"); + if (d.showLeaderLines()) parentChart.setChartOption("ShowLdrLines", "1"); + if (d.showLegendKey()) + ; // TODO: handle show legend key + if (d.showPercent()) parentChart.setChartOption("ShowLabelPct", "1"); + if (d.showSerName()) parentChart.setChartOption("ShowLabel", "1"); + if (d.showVal()) parentChart.setChartOption("ShowValueLabel", "1"); + // data label options + } else if (tnm.equals("dPt")) { // data point(s) + if (dpts == null) dpts = new ArrayList(); + lastTag.push(tnm); // keep track of element hierarchy + dpts.add(DPt.parseOOXML(xpp, lastTag, wbh).cloneElement()); + } else if (tnm.equals("spPr")) { // series spPr + lastTag.push(tnm); // keep track of element hierarchy + sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, wbh).cloneElement(); + } else if (tnm.equals("marker")) { + lastTag.push(tnm); // keep track of element hierarchy + m = (Marker) Marker.parseOOXML(xpp, lastTag, wbh).cloneElement(); + } else if (tnm.equals("bubbleSize")) { + idx = 3; + } else if (tnm.equals("shape")) { // bar only + parentChart.convertShape(xpp.getAttributeValue(0)); + } else if (tnm.equals("smooth")) { // line chart + smooth = (xpp.getAttributeCount() == 0 || !xpp.getAttributeValue(0).equals("0")); + } else if (tnm.equals("explosion")) { + String v = xpp.getAttributeValue(0); + parentChart.setChartOption("Percentage", v); + // NOTE: two types of values; 1- reference denoted by f element parents can be numRef, strRef or multiLvlStrRef + // 2- text value denoted by v element parent is strRef + } else if (tnm.equals("formatCode")) { // part of numCache element + Xf.addFormatPattern(wbh.getWorkBook(), OOXMLAdapter.getNextText(xpp)); // ******************************************* // TODO: add to y pattern *** // ******************************************* - }else if (tnm.equals("f")) { // range element -- legend cell, Cat range, Value range, Bubble data reference - ranges[idx]= OOXMLAdapter.getNextText(xpp); - }else if (tnm.equals("v")) { // value or text of series or category (parent=tx) - if (idx==0) // legend text - legendText= OOXMLAdapter.getNextText(xpp); // legend text; possible to have legend text without a legend cell range (ranges[1]) - else if (idx==-1 || ranges[idx].equals("")) { // shoudln't!! can't have a textual refernce in place of a series or cat value (can you?) - Logger.logWarn("ChartSeries.parseOOXML: unexpected text value"); - } - } else if (tnm.equals("numCache") || tnm.equals("strCache") || tnm.equals("multiLvlStrRef")) { // parent= cat or vals (series values) - cache= tnm; - } else if (tnm.equals("ptCount")) { // parent= numCache or strCache (governs either f element) - if (hasPivotTableSource) { // OK, if have a pivot table source then the range referenced in f is only a SUBSET - // unclear if at any other time the range referenced is a subset ... [NOTE: in testing, only pivot charts hit] - // another assumption: assume that range is only TRUNCATED -- in testing, true - int npoints= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - if (!ranges[idx].equals("") && ranges[idx].indexOf(",")==-1) { - try { - CellRange cells= new CellRange(ranges[idx], wbh, false, true); - if (cells.getCells().length!=npoints) { //must adjust - int z= 0; - CellHandle[] clist= cells.getCells(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - tnm= xpp.getName(); - if (tnm.equals("pt")) { - // format code idx - } else if (tnm.equals("v")) { + } else if (tnm.equals("f")) { // range element -- legend cell, Cat range, Value range, Bubble data reference + ranges[idx] = OOXMLAdapter.getNextText(xpp); + } else if (tnm.equals("v")) { // value or text of series or category (parent=tx) + if (idx == 0) // legend text + legendText = OOXMLAdapter.getNextText(xpp); // legend text; possible to have legend text without a legend cell range (ranges[1]) + else if (idx == -1 || ranges[idx].equals("")) { // shoudln't!! can't have a textual refernce in place of a series or cat value (can you?) + Logger.logWarn("ChartSeries.parseOOXML: unexpected text value"); + } + } else if (tnm.equals("numCache") || tnm.equals("strCache") || tnm.equals("multiLvlStrRef")) { // parent= cat or vals (series values) + cache = tnm; + } else if (tnm.equals("ptCount")) { // parent= numCache or strCache (governs either f element) + if (hasPivotTableSource) { // OK, if have a pivot table source then the range referenced in f is only a SUBSET + // unclear if at any other time the range referenced is a subset ... [NOTE: in testing, only pivot charts hit] + // another assumption: assume that range is only TRUNCATED -- in testing, true + int npoints = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + if (!ranges[idx].equals("") && ranges[idx].indexOf(",") == -1) { + try { + CellRange cells = new CellRange(ranges[idx], wbh, false, true); + if (cells.getCells().length != npoints) { //must adjust + int z = 0; + CellHandle[] clist = cells.getCells(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + tnm = xpp.getName(); + if (tnm.equals("pt")) { + // format code idx + } else if (tnm.equals("v")) { /* this case should NOT happen String s= OOXMLAdapter.getNextText(xpp); if (z < clist.length) @@ -727,234 +744,248 @@ else if (idx==-1 || ranges[idx].equals("")) { // shoudln't!! can't have a textua Logger.logWarn("ChartSeries.parseOOXML: unexpected pivot value order- skipping"); z++; */ - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(cache)) { - cache= null; - break; - } - } - eventType = xpp.next(); - } - // pivot charts: apparently always truncate/skip last cell in range (which represents the grand total) - if (npoints < clist.length) {// truncate! - int[] rc= cells.getRangeCoords(); - rc[0]--; rc[2]--; // make 0-based - if (rc[0]==rc[2]) - rc[3]-=(clist.length-npoints); - else - rc[2]-=(clist.length-npoints); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals(cache)) { + cache = null; + break; + } + } + eventType = xpp.next(); + } + // pivot charts: apparently always truncate/skip last cell in range (which represents the grand total) + if (npoints < clist.length) {// truncate! + int[] rc = cells.getRangeCoords(); + rc[0]--; + rc[2]--; // make 0-based + if (rc[0] == rc[2]) + rc[3] -= (clist.length - npoints); + else + rc[2] -= (clist.length - npoints); // KSC: TESTING: REMOVE WHEN DONE //io.starter.toolkit.Logger.log("Truncate list: old range: " + ranges[idx] + " new range: " + cells.getSheet().getQualifiedSheetName() + "!" + ExcelTools.formatLocation(rc)); - ranges[idx]= cells.getSheet().getQualifiedSheetName() + "!" + ExcelTools.formatLocation(rc); - } - - continue; // don't hit xpp.next() below - } - } catch (Exception e) { - Logger.logErr("ChartSeries.parseOOXML: Error adjusting pivot range for " + parentChart + ":" + e.toString()); - } // problems parsing range - skp - } - } - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals("ser")) { - lastTag.pop(); - // must only have 1 for pie-type charts (pie, of pie - bar of, pie of) - Series s= parentChart.getParentChart().getChartSeries().addSeries(ranges[2], ranges[1], ranges[3], ranges[0], legendText, parentChart, parentChart.getParentChart().getChartOrder(parentChart)); - if (sp!=null) - s.setSpPr(sp); - // TODO: " When you create a chart, by default - the first six series are the six accent colors in order - but not the exact color or any variation that appears in the palette. They're typically (unless the primary accent color being modified is extremely dark) a bit darker than the primary accent color. Chart series 7 - 12 use the actual primary accent colors 1 through 6 ... and then chart series 13 starts a set of lighter variations of the six accent colors that are also slightly different from any position in the palette." - else if (seriesidx < 7) // TODO: figure out where to get colors past 6 - if (seriesidx > 0 && parentChart instanceof PieChart) - Logger.logWarn("ChartSeries.parseOOXML: more than 1 series encountered for a Pie-style chart"); - else - s.setColor(wbh.getWorkBook().getTheme().genericThemeClrs[seriesidx+4]); // series colors start at 4 - if (d!=null) - s.setDLbls(d); - if (dpts!=null) { - for (int z= 0; z < dpts.size(); z++) - s.addDpt((DPt) dpts.get(z)); - } - if (m!=null) - s.setMarker(m); - if (smooth) - s.setHasSmoothLines(smooth); - return s; - } - } - eventType = xpp.next(); - } + ranges[idx] = cells.getSheet().getQualifiedSheetName() + "!" + ExcelTools.formatLocation(rc); + } + + continue; // don't hit xpp.next() below + } + } catch (Exception e) { + Logger.logErr("ChartSeries.parseOOXML: Error adjusting pivot range for " + parentChart + ":" + e.toString()); + } // problems parsing range - skp + } + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals("ser")) { + lastTag.pop(); + // must only have 1 for pie-type charts (pie, of pie - bar of, pie of) + Series s = parentChart.getParentChart().getChartSeries().addSeries(ranges[2], ranges[1], ranges[3], ranges[0], legendText, parentChart, parentChart.getParentChart().getChartOrder(parentChart)); + if (sp != null) + s.setSpPr(sp); + // TODO: " When you create a chart, by default - the first six series are the six accent colors in order - but not the exact color or any variation that appears in the palette. They're typically (unless the primary accent color being modified is extremely dark) a bit darker than the primary accent color. Chart series 7 - 12 use the actual primary accent colors 1 through 6 ... and then chart series 13 starts a set of lighter variations of the six accent colors that are also slightly different from any position in the palette." + else if (seriesidx < 7) // TODO: figure out where to get colors past 6 + if (seriesidx > 0 && parentChart instanceof PieChart) + Logger.logWarn("ChartSeries.parseOOXML: more than 1 series encountered for a Pie-style chart"); + else + s.setColor(wbh.getWorkBook().getTheme().genericThemeClrs[seriesidx + 4]); // series colors start at 4 + if (d != null) + s.setDLbls(d); + if (dpts != null) { + for (int z = 0; z < dpts.size(); z++) + s.addDpt((DPt) dpts.get(z)); + } + if (m != null) + s.setMarker(m); + if (smooth) + s.setHasSmoothLines(smooth); + return s; + } + } + eventType = xpp.next(); + } } catch (Exception e) { - Logger.logErr("ChartSeries.parseOOXML: Error parsing series for " + parentChart + ":" + e.toString()); + Logger.logErr("ChartSeries.parseOOXML: Error parsing series for " + parentChart + ":" + e.toString()); } - return null; - } - - /** - * Generate the OOXML used to represent all series for this chart type - * @param ct chart type - * @return - */ - /** all contain: - * idx (index) - * order (order) - * tx (series text) - * spPr (shape properties) - * then after, may contain: - * bubble: invertIfNegative, dPt, dLbls, trendline, errBars, xVal, yVal, bubbleSize, bubble3D (bubbleChart) - * line marker, dPt, dLbls, trendline, errBars, cat, val, smooth (line3DChart, lineChart, stockChart) - * pie: explosion, dPt,dLbls, cat, val (doughnutChart, ofPieChart, pie3DChart, pieChart) - * surface: cat, val (surfaceChart, surface3DChart) - * scatter: marker, dPt, dLbls, trendline, errBars, xVal, yVal, smooth (scatterChart) - * radar: marker, dPt, dLbls, cat, val (radarChart) - * area: pictureOptions, dPt, dLbls, trendline, errBars, cat, val (area3DChart, areaChart) - * bar: invertIfNegative, pictureOptions, dPt, dLbls, trendline, errBars, cat, val, shape (bar3DChart, barChart) - */ + return null; + } + + /** + * Generate the OOXML used to represent all series for this chart type + * @param ct chart type + * @return + */ + /** + * all contain: + * idx (index) + * order (order) + * tx (series text) + * spPr (shape properties) + * then after, may contain: + * bubble: invertIfNegative, dPt, dLbls, trendline, errBars, xVal, yVal, bubbleSize, bubble3D (bubbleChart) + * line marker, dPt, dLbls, trendline, errBars, cat, val, smooth (line3DChart, lineChart, stockChart) + * pie: explosion, dPt,dLbls, cat, val (doughnutChart, ofPieChart, pie3DChart, pieChart) + * surface: cat, val (surfaceChart, surface3DChart) + * scatter: marker, dPt, dLbls, trendline, errBars, xVal, yVal, smooth (scatterChart) + * radar: marker, dPt, dLbls, cat, val (radarChart) + * area: pictureOptions, dPt, dLbls, trendline, errBars, cat, val (area3DChart, areaChart) + * bar: invertIfNegative, pictureOptions, dPt, dLbls, trendline, errBars, cat, val, shape (bar3DChart, barChart) + */ // TODO: finish options - // TODO: refactor !!! - public void resetSeriesNumber() { seriesNumber= 0; } - int seriesNumber= 0; + // TODO: refactor !!! + public void resetSeriesNumber() { + seriesNumber = 0; + } + + int seriesNumber = 0; + public String getOOXML(int ct, boolean isBubble3d, int nChart) { - String catstr= (ct==SCATTERCHART || ct==BUBBLECHART)?("xVal"):("cat"); - String valstr= (ct==SCATTERCHART || ct==BUBBLECHART)?("yVal"):("val"); - StringBuffer ooxml= new StringBuffer(); - + String catstr = (ct == SCATTERCHART || ct == BUBBLECHART) ? ("xVal") : ("cat"); + String valstr = (ct == SCATTERCHART || ct == BUBBLECHART) ? ("yVal") : ("val"); + StringBuffer ooxml = new StringBuffer(); + Vector v = parentChart.getAllSeries(nChart); - int defaultDL= parentChart.getDataLabel(); - boolean from2003= (!parentChart.getWorkBook().getIsExcel2007()); - String[] cats= this.getCategories(nChart); // do 1x - - - for (int i= 0; i < v.size(); i++) { - Series s = (Series)v.get(i); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - // Series Legend - ooxml.append(s.getLegendOOXML(from2003)); - // Options for current series - if (ct==PIECHART /*&& i==0*/) - ooxml.append(""); - if (s.getMarker()!=null) ooxml.append(s.getMarker().getOOXML()); // only for Radar, Line or Scatter - if (s.getDPt()!=null) { - DPt[] datapoints= s.getDPt(); - for (int z= 0; z 0) { // todo: showLegendKey catpercent ? sppr + txpr - // TODO: spPr, txPr - DLbls dlbl= new DLbls(((dl | 0x1)==0x1) , ((dl | 0x08)==0x08), false, ((dl | 0x10)==0x10), ((dl | 0x40)==0x40), - ((dl | 0x2)==0x2), ((dl | 0x20)==0x20), null /*sppr*/, null /*txpr*/); - ooxml.append(dlbl.getOOXML()); - } - } - if (s.getHasSmoothedLines()) { - ooxml.append(""); - ooxml.append("\r\n"); - } - - // Categories NOTE: Categories==xVals for Scatter charts, cat for all others - ooxml.append(s.getCatOOXML(cats[i], catstr)); - // Series ("vals") NOTE: Series==yVals for Scatter charts, val for all others - ooxml.append(s.getValOOXML(valstr)); // gets the numeric data reference to define the series (values) - - if (ct==BUBBLECHART) { // also include bubble sizes - ooxml.append(s.getBubbleOOXML(isBubble3d)); - } - ooxml.append(""); ooxml.append("\r\n"); - } - return ooxml.toString(); - } - - - /** - * if has multiple or overlay charts, update series mappings - * @param sl - * @param thischartnumber - */ - protected void updateSeriesMappings(SeriesList sl, int thischartnumber) { - if (sl==null) - return; // NO seriesList record means no mapping - ArrayList seriesmappings= new ArrayList(); - for (int z= 0; z < series.size(); z++) { - int chartnumber= ((Integer) ((Object[])series.get(z))[1]).intValue(); - if (chartnumber==thischartnumber) // mappped to this chart - seriesmappings.add(Integer.valueOf(z+1)); - } - int[] mappings= new int[seriesmappings.size()]; - for (int z= 0; z 0) { - Series s= (Series) ((Object[])series.get(0))[0]; - int[] dls= s.getDataLabelsPIE(defaultDL); - if (dls==null) { - dls= new int[] { defaultDL }; - } - return dls; - } - } - int[] datalabels= new int[series.size()]; - for (int i= 0; i < series.size(); i++) { - Series s= (Series) ((Object[])series.get(i))[0]; - datalabels[i]= s.getDataLabel(); - datalabels[i]|=defaultDL; // if no per-series setting use overall chart setting - } - return datalabels; - } - - - // TODO: FINISH -- include cell range ... + overlay charts ...? - public String[] getLegends() { - if (legends==null) { - this.getMetrics(true); - } - return legends; - } - - - public Object[] getCategories() { - if (categories==null) - this.getMetrics(true); - return categories; - } - public ArrayList getSeriesRanges() { - if (seriesranges==null) - this.getMetrics(true); - return seriesranges; - } - public ArrayList getSeriesValues() { - if (seriesvalues==null) - this.getMetrics(true); - return seriesvalues; - } - - public String[] getSeriesBarColors() { - if (seriescolors==null) - this.getMetrics(true); - return seriescolors; - } - - + int defaultDL = parentChart.getDataLabel(); + boolean from2003 = (!parentChart.getWorkBook().getIsExcel2007()); + String[] cats = this.getCategories(nChart); // do 1x + + + for (int i = 0; i < v.size(); i++) { + Series s = (Series) v.get(i); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + // Series Legend + ooxml.append(s.getLegendOOXML(from2003)); + // Options for current series + if (ct == PIECHART /*&& i==0*/) + ooxml.append(""); + if (s.getMarker() != null) ooxml.append(s.getMarker().getOOXML()); // only for Radar, Line or Scatter + if (s.getDPt() != null) { + DPt[] datapoints = s.getDPt(); + for (int z = 0; z < datapoints.length; z++) { + ooxml.append(datapoints[z].getOOXML()); + } + } + if (s.getDLbls() != null) + ooxml.append(s.getDLbls().getOOXML()); + else if (from2003) { + int dl = s.getDataLabel() | defaultDL; + if (dl > 0) { // todo: showLegendKey catpercent ? sppr + txpr + // TODO: spPr, txPr + DLbls dlbl = new DLbls(((dl | 0x1) == 0x1), ((dl | 0x08) == 0x08), false, ((dl | 0x10) == 0x10), ((dl | 0x40) == 0x40), + ((dl | 0x2) == 0x2), ((dl | 0x20) == 0x20), null /*sppr*/, null /*txpr*/); + ooxml.append(dlbl.getOOXML()); + } + } + if (s.getHasSmoothedLines()) { + ooxml.append(""); + ooxml.append("\r\n"); + } + + // Categories NOTE: Categories==xVals for Scatter charts, cat for all others + ooxml.append(s.getCatOOXML(cats[i], catstr)); + // Series ("vals") NOTE: Series==yVals for Scatter charts, val for all others + ooxml.append(s.getValOOXML(valstr)); // gets the numeric data reference to define the series (values) + + if (ct == BUBBLECHART) { // also include bubble sizes + ooxml.append(s.getBubbleOOXML(isBubble3d)); + } + ooxml.append(""); + ooxml.append("\r\n"); + } + return ooxml.toString(); + } + + + /** + * if has multiple or overlay charts, update series mappings + * + * @param sl + * @param thischartnumber + */ + protected void updateSeriesMappings(SeriesList sl, int thischartnumber) { + if (sl == null) + return; // NO seriesList record means no mapping + ArrayList seriesmappings = new ArrayList(); + for (int z = 0; z < series.size(); z++) { + int chartnumber = ((Integer) series.get(z)[1]).intValue(); + if (chartnumber == thischartnumber) // mappped to this chart + seriesmappings.add(Integer.valueOf(z + 1)); + } + int[] mappings = new int[seriesmappings.size()]; + for (int z = 0; z < seriesmappings.size(); z++) { + mappings[z] = ((Integer) seriesmappings.get(z)).intValue(); + } + try { + sl.setSeriesMappings(mappings); + } catch (Exception e) { + throw new WorkBookException("ChartSeries.updateSeriesMappings failed:" + e.toString(), WorkBookException.RUNTIME_ERROR); + } + } + + /** + * return Data Labels Per Series or default Data Labels, if no overrides specified + * + * @param defaultDL Default Data labels + * @param charttype Chart Type Int + * @return + */ + public int[] getDataLabelsPerSeries(int defaultDL, int charttype) { + if (charttype == PIECHART || charttype == DOUGHNUTCHART) { // handled differently + if (series.size() > 0) { + Series s = (Series) series.get(0)[0]; + int[] dls = s.getDataLabelsPIE(defaultDL); + if (dls == null) { + dls = new int[]{defaultDL}; + } + return dls; + } + } + int[] datalabels = new int[series.size()]; + for (int i = 0; i < series.size(); i++) { + Series s = (Series) series.get(i)[0]; + datalabels[i] = s.getDataLabel(); + datalabels[i] |= defaultDL; // if no per-series setting use overall chart setting + } + return datalabels; + } + + + // TODO: FINISH -- include cell range ... + overlay charts ...? + public String[] getLegends() { + if (legends == null) { + this.getMetrics(true); + } + return legends; + } + + + public Object[] getCategories() { + if (categories == null) + this.getMetrics(true); + return categories; + } + + public ArrayList getSeriesRanges() { + if (seriesranges == null) + this.getMetrics(true); + return seriesranges; + } + + public ArrayList getSeriesValues() { + if (seriesvalues == null) + this.getMetrics(true); + return seriesvalues; + } + + public String[] getSeriesBarColors() { + if (seriescolors == null) + this.getMetrics(true); + return seriescolors; + } + + } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartType.java b/src/main/java/io/starter/formats/XLS/charts/ChartType.java index 4e2fcf7..10f1609 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartType.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartType.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,632 +22,635 @@ */ package io.starter.formats.XLS.charts; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; - import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.ChartHandle; -import io.starter.OpenXLS.WorkBookHandle; import io.starter.OpenXLS.ChartHandle.ChartOptions; +import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.OOXML.OOXMLConstants; -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.Label; -import io.starter.formats.XLS.MSODrawing; -import io.starter.formats.XLS.Obj; -import io.starter.formats.XLS.WorkBook; +import io.starter.formats.XLS.*; import io.starter.formats.cellformat.CellFormatFactory; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.xmlpull.v1.XmlPullParser; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; /** - * This abstract class defines a Chart Group for a BIFF Chart. An Excel Chart may have up to 4 (2003 and previous versions) or 9 (post-2003) charts within one chart. + * This abstract class defines a Chart Group for a BIFF Chart. An Excel Chart may have up to 4 (2003 and previous versions) or 9 (post-2003) charts within one chart. * The 0th chart type in the Chart Group is the default chart. *
                * NOTES: *
                * CRT = ChartFormat Begin (Bar / Line / (BopPop [BopPopCustom]) / Pie / Area / Scatter / Radar / RadarArea / Surf) CrtLink [SeriesList] [Chart3d] [LD] [2DROPBAR] *4(CrtLine LineFormat) *2DFTTEXT [DataLabExtContents] [SS] *4SHAPEPROPS End - * - MUST CHANGE ChartObject if change chart type - MUST ADD/REMOVE ChartObject if add/remove multiple charts - axisparent --> when add a new chart a new crt is added axes + titles/labels, number format, etc - - REFACTOR: TO FINISH - showDataTable -- Finish, TODO - NOTE: The SeriesList record specifies the series of the chart. This record MUST NOT exist in the first chart group in the chart sheet substream. - This record MUST exist when not in the first chart group in the chart sheet substream - - NOTE: - The Chart3d record specifies that the plot area, axis group, and chart group are rendered in a 3-D scene, rather than a 2-D scene, and specifies properties of the 3-D scene. If this record exists in the chart sheet substream, the chart sheet substream MUST have exactly one chart group. This record MUST NOT exist in a bar of pie, bubble, doughnut, filled radar, pie of pie, radar, or scatter chart group. - - NOTE: legends only in 1st chart group + *

                + * MUST CHANGE ChartObject if change chart type + * MUST ADD/REMOVE ChartObject if add/remove multiple charts + * axisparent --> when add a new chart a new crt is added axes + titles/labels, number format, etc + *

                + * REFACTOR: TO FINISH + * showDataTable -- Finish, TODO + * NOTE: The SeriesList record specifies the series of the chart. This record MUST NOT exist in the first chart group in the chart sheet substream. + * This record MUST exist when not in the first chart group in the chart sheet substream + *

                + * NOTE: + * The Chart3d record specifies that the plot area, axis group, and chart group are rendered in a 3-D scene, rather than a 2-D scene, and specifies properties of the 3-D scene. If this record exists in the chart sheet substream, the chart sheet substream MUST have exactly one chart group. This record MUST NOT exist in a bar of pie, bubble, doughnut, filled radar, pie of pie, radar, or scatter chart group. + *

                + * NOTE: legends only in 1st chart group */ public abstract class ChartType implements ChartConstants, Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7862828186455339066L; - protected GenericChartObject chartobj; - protected Legend legend= null; - protected ChartFormat cf= null; -// protected ChartSeries chartseries= new ChartSeries(); - protected transient WorkBook wb= null; - protected int defaultShape= 0; // controls default bar shape for all bars in the chart; used when adding or setting series - - public ChartType() { } - public ChartType(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - chartobj= charttype; // record that defines the chart type (Bar, Area, Line ...) - this.wb= wb; - this.cf= cf; - } - - public Chart getParentChart() { - return chartobj.getParentChart(); - } - - /** - * creates a new chart type object of the desired chart type. Will set options if already set via ChartFormat - * @param chartType - * @param parentChart - * @param cf - * @return - */ - public static ChartType create(int chartType, Chart parentChart, ChartFormat cf) { - GenericChartObject co= ChartType.createUnderlyingChartObject(chartType, parentChart, cf); //, ((ChartType)chartobj.get(0))); - - ChartType ct= ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); - return ct; - } - - - /** - * create and return the appropriate ChartType for the given - * chart record (Bar, Radar, Line, etc) - * @param ch GenericChartObject representing the Chart Type - *
                Must be one of: Area, Bar, Line, Surface, Pie, Radar, Scatter, BopPop, RadarArea - * @return ChartType chart object - */ - public static ChartType createChartTypeObject(GenericChartObject ch, ChartFormat cf, WorkBook wb) { - if (cf==null) // TODO: throw exception - return null; - - int barshape= cf.getBarShape(); - boolean threeD= cf.isThreeD(ch.chartType); - boolean isStacked= ch.isStacked(); - boolean is100Percent= ch.is100Percent(); - - switch (ch.chartType) { - case ChartConstants.COLCHART: - if (barshape==SHAPEDEFAULT) { // regular column - if (isStacked || is100Percent) - return new StackedColumn(ch, cf, wb); - if (threeD) - return new Col3DChart(ch, cf, wb); - return new ColChart(ch, cf, wb); - } else if (barshape==SHAPECONE) { // Cone chart always 3d - return new ConeChart(ch, cf, wb); - } else if (barshape==SHAPECYLINDER) { // Cylinder chart always 3d - return new CylinderChart(ch, cf, wb); - } else if (barshape==SHAPEPYRAMID) { // Pyramid chart alwasy 3d - return new PyramidChart(ch, cf, wb); - } - case ChartConstants.BARCHART: - if (barshape==SHAPEDEFAULT) { // regular Bar - if (threeD) - return new Bar3DChart(ch, cf, wb); - return new BarChart(ch, cf, wb); - } else if (barshape==SHAPECONE) { // ConeBarchart always 3d - return new ConeBarChart(ch, cf, wb); - } else if (barshape==SHAPECYLINDER) { // CylinderBar chart always 3d - return new CylinderBarChart(ch, cf, wb); - } else if (barshape==SHAPEPYRAMID) { // PyramidBar chart always 3d - return new PyramidBarChart(ch, cf, wb); - } - case ChartConstants.LINECHART: - if (threeD) - return new Line3DChart(ch, cf, wb); - return new LineChart(ch, cf, wb); - case ChartConstants.STOCKCHART: - return new StockChart(ch, cf, wb); - case ChartConstants.PIECHART: - cf.setPercentage(0); - if (threeD) - return new Pie3dChart(ch, cf, wb); - return new PieChart(ch, cf, wb); - case ChartConstants.AREACHART: - if (((Area)ch).isStacked()) - return new StackedAreaChart(ch, cf, wb); - if (threeD) - return new Area3DChart(ch, cf, wb); - return new AreaChart(ch, cf, wb); - case ChartConstants.SCATTERCHART: - return new ScatterChart(ch, cf, wb); - case ChartConstants.RADARCHART: - return new RadarChart(ch, cf, wb); - case ChartConstants.SURFACECHART: - if (((Surface) ch).getIs3d()) - return new Surface3DChart(ch, cf, wb); - return new SurfaceChart(ch, cf, wb); - case ChartConstants.DOUGHNUTCHART: - cf.setPercentage(0); - return new DoughnutChart(ch, cf, wb); - case ChartConstants.BUBBLECHART: - return new BubbleChart(ch, cf, wb); - case ChartConstants.RADARAREACHART: - return new RadarAreaChart(ch, cf, wb); - case ChartConstants.OFPIECHART: - cf.setPercentage(0); - return new OfPieChart(ch, cf, wb); - default: - return null; - } - } - - /** - * create a new low-level chart object which determines the ChartTypeObject, and update the existing chartobj - * @param chartType - * @param parentchart - * @param chartgroup - * @return - */ - public static GenericChartObject createUnderlyingChartObject(int chartType, Chart parentchart, ChartFormat cf){//, ChartType chartobj) { - GenericChartObject c= null; - switch (chartType) { - case ChartConstants.COLCHART: // column-type - Bar col = (Bar) Bar.getPrototype(); - col.setAsColumnChart(); - c= col; - break; - - case ChartConstants.BARCHART: // bar-type - Bar bar = (Bar) Bar.getPrototype(); - bar.setAsBarChart(); - c= bar; - break; - - case ChartConstants.PIECHART: // Pie - Pie p = (Pie) Pie.getPrototype(); - p.setAsPieChart(); - c= p; - break; - - case ChartConstants.STOCKCHART: - Line st = (Line) Line.getPrototype(); - st.setAsStockChart(); - c= st; - break; - - case ChartConstants.LINECHART: - Line l = (Line) Line.getPrototype(); - c= l; - break; - - case ChartConstants.AREACHART: - Area a = (Area) Area.getPrototype(); - c= a; - break; - - case ChartConstants.SCATTERCHART: - Scatter s = (Scatter) Scatter.getPrototype(); - s.setAsScatterChart(); - c= s; - break; - - case ChartConstants.RADARCHART: - Radar r = (Radar) Radar.getPrototype(); - c= r; - break; - - case ChartConstants.SURFACECHART: - Surface su = (Surface) Surface.getPrototype(); - c= su; - break; - - case ChartConstants.DOUGHNUTCHART: - Pie d = (Pie) Pie.getPrototype(); - d.setAsDoughnutChart(); - c= d; - break; - - case ChartConstants.BUBBLECHART: - Scatter bu = (Scatter) Scatter.getPrototype(); - bu.setAsBubbleChart(); - c= bu; - break; - - case ChartConstants.RADARAREACHART: - RadarArea ra = (RadarArea) RadarArea.getPrototype(); - c= ra; - break; - - // note that, for the below chart types, the underlying type will be either COL or BAR - // which actual chart is determined also by the bar shape - case ChartConstants.PYRAMIDCHART: - Bar pyramid = (Bar) Bar.getPrototype(); - pyramid.setAsColumnChart(); - cf.setBarShape(SHAPEPYRAMID); - c= pyramid; - break; - - case ChartConstants.CONECHART: - Bar cone = (Bar) Bar.getPrototype(); - cone.setAsColumnChart(); - cf.setBarShape(SHAPECONE); - c= cone; - break; - - case ChartConstants.CYLINDERCHART: - Bar cy = (Bar) Bar.getPrototype(); - cy.setAsColumnChart(); - cf.setBarShape(SHAPECYLINDER); - c= cy; - break; - - case ChartConstants.PYRAMIDBARCHART: - Bar pb = (Bar) Bar.getPrototype(); - pb.setAsBarChart(); - cf.setBarShape(SHAPEPYRAMID); - c= pb; - break; - - case ChartConstants.CONEBARCHART: - Bar cb = (Bar) Bar.getPrototype(); - cb.setAsBarChart(); - cf.setBarShape(SHAPECONE); - c= cb; - break; - - case ChartConstants.CYLINDERBARCHART: - Bar cyb = (Bar) Bar.getPrototype(); - cyb.setAsBarChart(); - cf.setBarShape(SHAPECYLINDER); - c= cyb; - break; - - case ChartConstants.OFPIECHART: - Boppop ofpie= (Boppop) Boppop.getPrototype(); - c= ofpie; - break; - } - if (c!=null) { - c.setParentChart(parentchart); - } - return c; - } - - public void setOptions(EnumSet options) { - // FYI: The CrtLine (section 2.4.68) LineFormat (section 2.4.156) record pairs and the sequences of records that conform to the SHAPEPROPS rule (section 2.1.7.20.1) - // specify the drop lines, high-low lines, series lines, and leader lines for the chart (section 2.2.3.3). - // NO 3d record for: bar of pie, bubble, doughnut, filled radar, pie of pie, radar, or scatter chart group. - // Has Ser (Z) axis: Surface, fStacked==0 & Line, Area, fStacked==0 && fClustered==0 && Bar (Col) (Must also have ThreeD record) - // 2 Value Axes: Scatter, Bubble - // NO Axes: Pie, Doughnut, PieOfPie, BarOfPie - - - ChartAxes ca= this.getParentChart().getAxes(); - int chartType= this.getChartType(); - if (options.contains(ChartOptions.STACKED)) // bar/col types, line, area - chartobj.setIsStacked(true); - - if (options.contains(ChartOptions.PERCENTSTACKED)) // bar/col types, line, area - chartobj.setIs100Percent(true); - - if (options.contains(ChartOptions.CLUSTERED)) // bar/col only - cf.setIsClustered(true); - - if (options.contains(ChartOptions.SERLINES)) // bar, line, stock - cf.addChartLines(ChartLine.TYPE_SERIESLINE); - - if (options.contains(ChartOptions.HILOWLINES)) // bar, OfPie - cf.addChartLines(ChartLine.TYPE_HILOWLINE); - - if (options.contains(ChartOptions.DROPLINES)) // Surface chart - cf.addChartLines(ChartLine.TYPE_DROPLINE); - - if (options.contains(ChartOptions.UPDOWNBARS)) // line, area,stock - cf.addUpDownBars(); - - if (options.contains(ChartOptions.HASLINES)) // line, scatter ... - cf.setHasLines(); - - if (options.contains(ChartOptions.SMOOTHLINES)) // line, scatter, radar - cf.setHasSmoothLines(true); + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7862828186455339066L; + protected GenericChartObject chartobj; + protected Legend legend = null; + protected ChartFormat cf = null; + // protected ChartSeries chartseries= new ChartSeries(); + protected transient WorkBook wb = null; + protected int defaultShape = 0; // controls default bar shape for all bars in the chart; used when adding or setting series + + public ChartType() { + } + + public ChartType(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + chartobj = charttype; // record that defines the chart type (Bar, Area, Line ...) + this.wb = wb; + this.cf = cf; + } + + public Chart getParentChart() { + return chartobj.getParentChart(); + } + + /** + * creates a new chart type object of the desired chart type. Will set options if already set via ChartFormat + * + * @param chartType + * @param parentChart + * @param cf + * @return + */ + public static ChartType create(int chartType, Chart parentChart, ChartFormat cf) { + GenericChartObject co = ChartType.createUnderlyingChartObject(chartType, parentChart, cf); //, ((ChartType)chartobj.get(0))); + + ChartType ct = ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); + return ct; + } + + + /** + * create and return the appropriate ChartType for the given + * chart record (Bar, Radar, Line, etc) + * + * @param ch GenericChartObject representing the Chart Type + *
                Must be one of: Area, Bar, Line, Surface, Pie, Radar, Scatter, BopPop, RadarArea + * @return ChartType chart object + */ + public static ChartType createChartTypeObject(GenericChartObject ch, ChartFormat cf, WorkBook wb) { + if (cf == null) // TODO: throw exception + return null; + + int barshape = cf.getBarShape(); + boolean threeD = cf.isThreeD(ch.chartType); + boolean isStacked = ch.isStacked(); + boolean is100Percent = ch.is100Percent(); + + switch (ch.chartType) { + case ChartConstants.COLCHART: + if (barshape == SHAPEDEFAULT) { // regular column + if (isStacked || is100Percent) + return new StackedColumn(ch, cf, wb); + if (threeD) + return new Col3DChart(ch, cf, wb); + return new ColChart(ch, cf, wb); + } else if (barshape == SHAPECONE) { // Cone chart always 3d + return new ConeChart(ch, cf, wb); + } else if (barshape == SHAPECYLINDER) { // Cylinder chart always 3d + return new CylinderChart(ch, cf, wb); + } else if (barshape == SHAPEPYRAMID) { // Pyramid chart alwasy 3d + return new PyramidChart(ch, cf, wb); + } + case ChartConstants.BARCHART: + if (barshape == SHAPEDEFAULT) { // regular Bar + if (threeD) + return new Bar3DChart(ch, cf, wb); + return new BarChart(ch, cf, wb); + } else if (barshape == SHAPECONE) { // ConeBarchart always 3d + return new ConeBarChart(ch, cf, wb); + } else if (barshape == SHAPECYLINDER) { // CylinderBar chart always 3d + return new CylinderBarChart(ch, cf, wb); + } else if (barshape == SHAPEPYRAMID) { // PyramidBar chart always 3d + return new PyramidBarChart(ch, cf, wb); + } + case ChartConstants.LINECHART: + if (threeD) + return new Line3DChart(ch, cf, wb); + return new LineChart(ch, cf, wb); + case ChartConstants.STOCKCHART: + return new StockChart(ch, cf, wb); + case ChartConstants.PIECHART: + cf.setPercentage(0); + if (threeD) + return new Pie3dChart(ch, cf, wb); + return new PieChart(ch, cf, wb); + case ChartConstants.AREACHART: + if (ch.isStacked()) + return new StackedAreaChart(ch, cf, wb); + if (threeD) + return new Area3DChart(ch, cf, wb); + return new AreaChart(ch, cf, wb); + case ChartConstants.SCATTERCHART: + return new ScatterChart(ch, cf, wb); + case ChartConstants.RADARCHART: + return new RadarChart(ch, cf, wb); + case ChartConstants.SURFACECHART: + if (((Surface) ch).getIs3d()) + return new Surface3DChart(ch, cf, wb); + return new SurfaceChart(ch, cf, wb); + case ChartConstants.DOUGHNUTCHART: + cf.setPercentage(0); + return new DoughnutChart(ch, cf, wb); + case ChartConstants.BUBBLECHART: + return new BubbleChart(ch, cf, wb); + case ChartConstants.RADARAREACHART: + return new RadarAreaChart(ch, cf, wb); + case ChartConstants.OFPIECHART: + cf.setPercentage(0); + return new OfPieChart(ch, cf, wb); + default: + return null; + } + } + + /** + * create a new low-level chart object which determines the ChartTypeObject, and update the existing chartobj + * + * @param chartType + * @param parentchart + * @param chartgroup + * @return + */ + public static GenericChartObject createUnderlyingChartObject(int chartType, Chart parentchart, ChartFormat cf) {//, ChartType chartobj) { + GenericChartObject c = null; + switch (chartType) { + case ChartConstants.COLCHART: // column-type + Bar col = (Bar) Bar.getPrototype(); + col.setAsColumnChart(); + c = col; + break; + + case ChartConstants.BARCHART: // bar-type + Bar bar = (Bar) Bar.getPrototype(); + bar.setAsBarChart(); + c = bar; + break; + + case ChartConstants.PIECHART: // Pie + Pie p = (Pie) Pie.getPrototype(); + p.setAsPieChart(); + c = p; + break; + + case ChartConstants.STOCKCHART: + Line st = (Line) Line.getPrototype(); + st.setAsStockChart(); + c = st; + break; + + case ChartConstants.LINECHART: + Line l = (Line) Line.getPrototype(); + c = l; + break; + + case ChartConstants.AREACHART: + Area a = (Area) Area.getPrototype(); + c = a; + break; + + case ChartConstants.SCATTERCHART: + Scatter s = (Scatter) Scatter.getPrototype(); + s.setAsScatterChart(); + c = s; + break; + + case ChartConstants.RADARCHART: + Radar r = (Radar) Radar.getPrototype(); + c = r; + break; + + case ChartConstants.SURFACECHART: + Surface su = (Surface) Surface.getPrototype(); + c = su; + break; + + case ChartConstants.DOUGHNUTCHART: + Pie d = (Pie) Pie.getPrototype(); + d.setAsDoughnutChart(); + c = d; + break; + + case ChartConstants.BUBBLECHART: + Scatter bu = (Scatter) Scatter.getPrototype(); + bu.setAsBubbleChart(); + c = bu; + break; + + case ChartConstants.RADARAREACHART: + RadarArea ra = (RadarArea) RadarArea.getPrototype(); + c = ra; + break; + + // note that, for the below chart types, the underlying type will be either COL or BAR + // which actual chart is determined also by the bar shape + case ChartConstants.PYRAMIDCHART: + Bar pyramid = (Bar) Bar.getPrototype(); + pyramid.setAsColumnChart(); + cf.setBarShape(SHAPEPYRAMID); + c = pyramid; + break; + + case ChartConstants.CONECHART: + Bar cone = (Bar) Bar.getPrototype(); + cone.setAsColumnChart(); + cf.setBarShape(SHAPECONE); + c = cone; + break; + + case ChartConstants.CYLINDERCHART: + Bar cy = (Bar) Bar.getPrototype(); + cy.setAsColumnChart(); + cf.setBarShape(SHAPECYLINDER); + c = cy; + break; + + case ChartConstants.PYRAMIDBARCHART: + Bar pb = (Bar) Bar.getPrototype(); + pb.setAsBarChart(); + cf.setBarShape(SHAPEPYRAMID); + c = pb; + break; + + case ChartConstants.CONEBARCHART: + Bar cb = (Bar) Bar.getPrototype(); + cb.setAsBarChart(); + cf.setBarShape(SHAPECONE); + c = cb; + break; + + case ChartConstants.CYLINDERBARCHART: + Bar cyb = (Bar) Bar.getPrototype(); + cyb.setAsBarChart(); + cf.setBarShape(SHAPECYLINDER); + c = cyb; + break; + + case ChartConstants.OFPIECHART: + Boppop ofpie = (Boppop) Boppop.getPrototype(); + c = ofpie; + break; + } + if (c != null) { + c.setParentChart(parentchart); + } + return c; + } + + public void setOptions(EnumSet options) { + // FYI: The CrtLine (section 2.4.68) LineFormat (section 2.4.156) record pairs and the sequences of records that conform to the SHAPEPROPS rule (section 2.1.7.20.1) + // specify the drop lines, high-low lines, series lines, and leader lines for the chart (section 2.2.3.3). + // NO 3d record for: bar of pie, bubble, doughnut, filled radar, pie of pie, radar, or scatter chart group. + // Has Ser (Z) axis: Surface, fStacked==0 & Line, Area, fStacked==0 && fClustered==0 && Bar (Col) (Must also have ThreeD record) + // 2 Value Axes: Scatter, Bubble + // NO Axes: Pie, Doughnut, PieOfPie, BarOfPie + + + ChartAxes ca = this.getParentChart().getAxes(); + int chartType = this.getChartType(); + if (options.contains(ChartOptions.STACKED)) // bar/col types, line, area + chartobj.setIsStacked(true); + + if (options.contains(ChartOptions.PERCENTSTACKED)) // bar/col types, line, area + chartobj.setIs100Percent(true); + + if (options.contains(ChartOptions.CLUSTERED)) // bar/col only + cf.setIsClustered(true); + + if (options.contains(ChartOptions.SERLINES)) // bar, line, stock + cf.addChartLines(ChartLine.TYPE_SERIESLINE); + + if (options.contains(ChartOptions.HILOWLINES)) // bar, OfPie + cf.addChartLines(ChartLine.TYPE_HILOWLINE); + + if (options.contains(ChartOptions.DROPLINES)) // Surface chart + cf.addChartLines(ChartLine.TYPE_DROPLINE); + + if (options.contains(ChartOptions.UPDOWNBARS)) // line, area,stock + cf.addUpDownBars(); + + if (options.contains(ChartOptions.HASLINES)) // line, scatter ... + cf.setHasLines(); + + if (options.contains(ChartOptions.SMOOTHLINES)) // line, scatter, radar + cf.setHasSmoothLines(true); // HANDLE FILLED for radar // HANDLE bubble 3d // HANDLE bar shapes *** - cf.setChartObject(chartobj); - boolean use3Ddefaults= true; // init 3D record with default values for specific chart type - ThreeD threeD= cf.getThreeDRec(false); - if (threeD==null) { - if (options.contains(ChartOptions.THREED) ||chartType==SURFACECHART) { // surface charts ALWAYS have a 3 record as does pyramid, cone and cylinder charts - if (chartType!=BUBBLECHART && chartType!=SCATTERCHART) // supposed to be also donught, radar as well ... - threeD=this.initThreeD(chartType); - else if (chartType==BUBBLECHART) { // scatter charts have no 3d option - cf.setHas3DBubbles(true); - } - } - } else // 3D record already set (via OOXML) - do not use defaults - use3Ddefaults= false; - switch (chartType) { - case BARCHART: - case COLCHART: - if (use3Ddefaults && threeD!=null) { - threeD.setChartOption("AnRot", "20"); - threeD.setChartOption("AnElev", "15"); - threeD.setChartOption("TwoDWalls", "true"); - threeD.setChartOption("ThreeDScaling", "false"); - threeD.setChartOption("Cluster", "false"); - threeD.setChartOption("PcDepth", "100"); - threeD.setChartOption("PcDist", "30"); - threeD.setChartOption("PcGap", "150"); - threeD.setChartOption("PcHeight", "72"); // ?????? - threeD.setChartOption("Perspective", "false"); - if (options.contains(ChartOptions.CLUSTERED)) { - threeD.setChartOption("Cluster", "true"); - threeD.setChartOption("PcHeight", "62"); // ?????? - if (chartType==COLCHART) - threeD.setChartOption("Perspective", "true"); - else { // bar chart - threeD.setChartOption("Perspective", "false"); - threeD.setChartOption("ThreeDScaling", "true"); - threeD.setChartOption("PcHeight", "150"); // ?????? - } - } - } - ca.setChartOption(XAXIS, "AddArea", "true"); - ca.setChartOption(YAXIS, "AddArea", "true"); - if (!isStacked() && !isClustered()) { - ca.createAxis(ZAXIS); - ca.setChartOption(ZAXIS, "AddArea", "true"); - } - break; - case LINECHART: - if (options.contains(ChartOptions.THREED)) { - ca.setChartOption(XAXIS, "AddArea", "true"); - ca.setChartOption(YAXIS, "AddArea", "true"); - if (!isStacked()) { - ca.createAxis(ZAXIS); - ca.setChartOption(ZAXIS, "AddArea", "true"); - } - } - break; - case STOCKCHART: - cf.addChartLines(ChartLine.TYPE_HILOWLINE); - cf.setMarkers(0); - break; - case SCATTERCHART: - if (!options.contains(ChartOptions.HASLINES)) { - cf.setHasLines(5); // no line style -- doesn't appear to work - } - break; - case AREACHART: - if (use3Ddefaults && threeD!=null) { - threeD.setChartOption("AnRot", "20"); - threeD.setChartOption("TwoDWalls", "true"); - threeD.setChartOption("ThreeDScaling", "false"); - threeD.setChartOption("Perspective", "true"); - } - this.setChartOption("Percentage", "25"); - this.setChartOption("SmoothedLine", "true"); - ca.setChartOption(XAXIS, "CrossBetween", "false"); - if (options.contains(ChartOptions.THREED)) { - ca.setChartOption(XAXIS, "AddArea", "true"); - ca.setChartOption(YAXIS, "AddArea", "true"); - if (!isStacked()) { - ca.createAxis(ZAXIS); - ca.setChartOption(ZAXIS, "AddArea", "true"); - } - } - break; - case BUBBLECHART: - if (options.contains(ChartOptions.THREED)) { - this.setChartOption("Percentage", "25"); - this.setChartOption("SmoothedLine", "true"); - this.setChartOption("ThreeDBubbles", "true"); - } - break; - case PIECHART: // Pie - cf.setVaryColor(true); // Should be true for all pie charts ... - if (options.contains(ChartOptions.EXPLODED)) { - this.setChartOption("SmoothedLine", "true"); - this.setChartOption("Percentage", "25"); - } - if (use3Ddefaults && options.contains(ChartOptions.THREED)) - this.setChartOption("AnRot", "236"); - ca.removeAxes(); - break; - case DOUGHNUTCHART: - if (options.contains(ChartOptions.EXPLODED)) { - this.setChartOption("SmoothedLine", "true"); - this.setChartOption("Percentage", "25"); - } - cf.setVaryColor(true); // Should be true for all pie charts ... - ca.removeAxes(); - break; - case SURFACECHART: // NOTE: For Surface charts, non-threeD==Contour - if (use3Ddefaults && threeD!=null) { // shouldn't - threeD.setChartOption("Cluster", "false"); - threeD.setChartOption("TwoDWalls", "true"); - threeD.setChartOption("ThreeDScaling", "true"); - threeD.setChartOption("Perspective", "true"); - } - ca.setChartOption(XAXIS, "AddArea", "true"); - ca.setChartOption(XAXIS, "AreaFg", "8"); - ca.setChartOption(XAXIS, "AreaBg", "78"); - ca.setChartOption(YAXIS, "AddArea", "true"); - ca.setChartOption(YAXIS, "AreaFg", "22"); - ca.setChartOption(YAXIS, "AreaBg", "78"); - ca.createAxis(ZAXIS); - if (use3Ddefaults && options.contains(ChartOptions.THREED)) { // "regular" 3d surface - threeD.setChartOption("AnElev", "15"); - threeD.setChartOption("AnRot", "20"); - threeD.setChartOption("PcDepth", "100"); - threeD.setChartOption("PcDist", "30"); - threeD.setChartOption("PcGap", "150"); - threeD.setChartOption("PcHeight", "50"); // ?????? - } else if (use3Ddefaults) { // contour (non-3d) - threeD.setChartOption("AnElev", "90"); - threeD.setChartOption("AnRot", "0"); - threeD.setChartOption("PcDepth", "100"); - threeD.setChartOption("PcDist", "0"); - threeD.setChartOption("PcGap", "150"); - threeD.setChartOption("PcHeight", "50"); // ?????? - } - if (options.contains(ChartOptions.WIREFRAME)) - ((Surface)chartobj).setIsWireframe(true); - if (!options.contains(ChartOptions.WIREFRAME) && options.contains(ChartOptions.THREED)) { - chartobj.setChartOption("ColorFill", "true"); - chartobj.setChartOption("Shading", "true"); - } else if (options.contains(ChartOptions.WIREFRAME)){ // conotur (flat, non-3d) wireframe - chartobj.setChartOption("Shading", "true"); - } else // contour filled (i.e. plain Surface) - chartobj.setChartOption("ColorFill", "true"); - break; - case RADARCHART: - if (options.contains(ChartOptions.FILLED)) - ((RadarChart) this).setFilled(true); - break; - case PYRAMIDCHART: - case CONECHART: - case CYLINDERCHART: - case PYRAMIDBARCHART: - case CONEBARCHART: - case CYLINDERBARCHART: - // Shaped Bar/Col charts are all 3d - if (threeD==null) threeD=this.initThreeD(chartType); - if (use3Ddefaults) { - threeD.setChartOption("AnElev", "15"); - threeD.setChartOption("AnRot", "20"); - threeD.setChartOption("Cluster", "true"); // only for regular pyramid charts; stacked, etc. will alter - threeD.setChartOption("TwoDWalls", "true"); - if (!options.contains(ChartOptions.THREED)) { - threeD.setChartOption("ThreeDScaling", "false"); - threeD.setChartOption("Cluster", "true"); - } - threeD.setChartOption("Perspective", "false"); - threeD.setChartOption("PcDepth", "100"); - threeD.setChartOption("PcDist", "30"); - threeD.setChartOption("PcGap", "150"); - threeD.setChartOption("PcHeight", "52"); // ?????? - } - if (chartType==PYRAMIDCHART || chartType==PYRAMIDBARCHART) - cf.setBarShape(ChartConstants.SHAPEPYRAMID); - else if (chartType==CONECHART || chartType==CONEBARCHART) - cf.setBarShape(ChartConstants.SHAPECONE); - else if (chartType==CYLINDERCHART || chartType==CYLINDERBARCHART) - cf.setBarShape(ChartConstants.SHAPECYLINDER); - break; - } - } - - - /** - * parse the chart object OOXML element (barchart, area3DChart, etc.) and create the corresponding chart type objects - * @param xpp XmlPullParser - * @param wbh workBookHandle - * @param parentChart parent Chart Object - * @param nChart chart grouping number, 0 for default, 1-9 for overlay - * @return - */ + cf.setChartObject(chartobj); + boolean use3Ddefaults = true; // init 3D record with default values for specific chart type + ThreeD threeD = cf.getThreeDRec(false); + if (threeD == null) { + if (options.contains(ChartOptions.THREED) || chartType == SURFACECHART) { // surface charts ALWAYS have a 3 record as does pyramid, cone and cylinder charts + if (chartType != BUBBLECHART && chartType != SCATTERCHART) // supposed to be also donught, radar as well ... + threeD = this.initThreeD(chartType); + else if (chartType == BUBBLECHART) { // scatter charts have no 3d option + cf.setHas3DBubbles(true); + } + } + } else // 3D record already set (via OOXML) - do not use defaults + use3Ddefaults = false; + switch (chartType) { + case BARCHART: + case COLCHART: + if (use3Ddefaults && threeD != null) { + threeD.setChartOption("AnRot", "20"); + threeD.setChartOption("AnElev", "15"); + threeD.setChartOption("TwoDWalls", "true"); + threeD.setChartOption("ThreeDScaling", "false"); + threeD.setChartOption("Cluster", "false"); + threeD.setChartOption("PcDepth", "100"); + threeD.setChartOption("PcDist", "30"); + threeD.setChartOption("PcGap", "150"); + threeD.setChartOption("PcHeight", "72"); // ?????? + threeD.setChartOption("Perspective", "false"); + if (options.contains(ChartOptions.CLUSTERED)) { + threeD.setChartOption("Cluster", "true"); + threeD.setChartOption("PcHeight", "62"); // ?????? + if (chartType == COLCHART) + threeD.setChartOption("Perspective", "true"); + else { // bar chart + threeD.setChartOption("Perspective", "false"); + threeD.setChartOption("ThreeDScaling", "true"); + threeD.setChartOption("PcHeight", "150"); // ?????? + } + } + } + ca.setChartOption(XAXIS, "AddArea", "true"); + ca.setChartOption(YAXIS, "AddArea", "true"); + if (!isStacked() && !isClustered()) { + ca.createAxis(ZAXIS); + ca.setChartOption(ZAXIS, "AddArea", "true"); + } + break; + case LINECHART: + if (options.contains(ChartOptions.THREED)) { + ca.setChartOption(XAXIS, "AddArea", "true"); + ca.setChartOption(YAXIS, "AddArea", "true"); + if (!isStacked()) { + ca.createAxis(ZAXIS); + ca.setChartOption(ZAXIS, "AddArea", "true"); + } + } + break; + case STOCKCHART: + cf.addChartLines(ChartLine.TYPE_HILOWLINE); + cf.setMarkers(0); + break; + case SCATTERCHART: + if (!options.contains(ChartOptions.HASLINES)) { + cf.setHasLines(5); // no line style -- doesn't appear to work + } + break; + case AREACHART: + if (use3Ddefaults && threeD != null) { + threeD.setChartOption("AnRot", "20"); + threeD.setChartOption("TwoDWalls", "true"); + threeD.setChartOption("ThreeDScaling", "false"); + threeD.setChartOption("Perspective", "true"); + } + this.setChartOption("Percentage", "25"); + this.setChartOption("SmoothedLine", "true"); + ca.setChartOption(XAXIS, "CrossBetween", "false"); + if (options.contains(ChartOptions.THREED)) { + ca.setChartOption(XAXIS, "AddArea", "true"); + ca.setChartOption(YAXIS, "AddArea", "true"); + if (!isStacked()) { + ca.createAxis(ZAXIS); + ca.setChartOption(ZAXIS, "AddArea", "true"); + } + } + break; + case BUBBLECHART: + if (options.contains(ChartOptions.THREED)) { + this.setChartOption("Percentage", "25"); + this.setChartOption("SmoothedLine", "true"); + this.setChartOption("ThreeDBubbles", "true"); + } + break; + case PIECHART: // Pie + cf.setVaryColor(true); // Should be true for all pie charts ... + if (options.contains(ChartOptions.EXPLODED)) { + this.setChartOption("SmoothedLine", "true"); + this.setChartOption("Percentage", "25"); + } + if (use3Ddefaults && options.contains(ChartOptions.THREED)) + this.setChartOption("AnRot", "236"); + ca.removeAxes(); + break; + case DOUGHNUTCHART: + if (options.contains(ChartOptions.EXPLODED)) { + this.setChartOption("SmoothedLine", "true"); + this.setChartOption("Percentage", "25"); + } + cf.setVaryColor(true); // Should be true for all pie charts ... + ca.removeAxes(); + break; + case SURFACECHART: // NOTE: For Surface charts, non-threeD==Contour + if (use3Ddefaults && threeD != null) { // shouldn't + threeD.setChartOption("Cluster", "false"); + threeD.setChartOption("TwoDWalls", "true"); + threeD.setChartOption("ThreeDScaling", "true"); + threeD.setChartOption("Perspective", "true"); + } + ca.setChartOption(XAXIS, "AddArea", "true"); + ca.setChartOption(XAXIS, "AreaFg", "8"); + ca.setChartOption(XAXIS, "AreaBg", "78"); + ca.setChartOption(YAXIS, "AddArea", "true"); + ca.setChartOption(YAXIS, "AreaFg", "22"); + ca.setChartOption(YAXIS, "AreaBg", "78"); + ca.createAxis(ZAXIS); + if (use3Ddefaults && options.contains(ChartOptions.THREED)) { // "regular" 3d surface + threeD.setChartOption("AnElev", "15"); + threeD.setChartOption("AnRot", "20"); + threeD.setChartOption("PcDepth", "100"); + threeD.setChartOption("PcDist", "30"); + threeD.setChartOption("PcGap", "150"); + threeD.setChartOption("PcHeight", "50"); // ?????? + } else if (use3Ddefaults) { // contour (non-3d) + threeD.setChartOption("AnElev", "90"); + threeD.setChartOption("AnRot", "0"); + threeD.setChartOption("PcDepth", "100"); + threeD.setChartOption("PcDist", "0"); + threeD.setChartOption("PcGap", "150"); + threeD.setChartOption("PcHeight", "50"); // ?????? + } + if (options.contains(ChartOptions.WIREFRAME)) + ((Surface) chartobj).setIsWireframe(true); + if (!options.contains(ChartOptions.WIREFRAME) && options.contains(ChartOptions.THREED)) { + chartobj.setChartOption("ColorFill", "true"); + chartobj.setChartOption("Shading", "true"); + } else if (options.contains(ChartOptions.WIREFRAME)) { // conotur (flat, non-3d) wireframe + chartobj.setChartOption("Shading", "true"); + } else // contour filled (i.e. plain Surface) + chartobj.setChartOption("ColorFill", "true"); + break; + case RADARCHART: + if (options.contains(ChartOptions.FILLED)) + ((RadarChart) this).setFilled(true); + break; + case PYRAMIDCHART: + case CONECHART: + case CYLINDERCHART: + case PYRAMIDBARCHART: + case CONEBARCHART: + case CYLINDERBARCHART: + // Shaped Bar/Col charts are all 3d + if (threeD == null) threeD = this.initThreeD(chartType); + if (use3Ddefaults) { + threeD.setChartOption("AnElev", "15"); + threeD.setChartOption("AnRot", "20"); + threeD.setChartOption("Cluster", "true"); // only for regular pyramid charts; stacked, etc. will alter + threeD.setChartOption("TwoDWalls", "true"); + if (!options.contains(ChartOptions.THREED)) { + threeD.setChartOption("ThreeDScaling", "false"); + threeD.setChartOption("Cluster", "true"); + } + threeD.setChartOption("Perspective", "false"); + threeD.setChartOption("PcDepth", "100"); + threeD.setChartOption("PcDist", "30"); + threeD.setChartOption("PcGap", "150"); + threeD.setChartOption("PcHeight", "52"); // ?????? + } + if (chartType == PYRAMIDCHART || chartType == PYRAMIDBARCHART) + cf.setBarShape(ChartConstants.SHAPEPYRAMID); + else if (chartType == CONECHART || chartType == CONEBARCHART) + cf.setBarShape(ChartConstants.SHAPECONE); + else if (chartType == CYLINDERCHART || chartType == CYLINDERBARCHART) + cf.setBarShape(ChartConstants.SHAPECYLINDER); + break; + } + } + + + /** + * parse the chart object OOXML element (barchart, area3DChart, etc.) and create the corresponding chart type objects + * + * @param xpp XmlPullParser + * @param wbh workBookHandle + * @param parentChart parent Chart Object + * @param nChart chart grouping number, 0 for default, 1-9 for overlay + * @return + */ public static ChartType parseOOXML(XmlPullParser xpp, WorkBookHandle wbh, Chart parentChart, int nChart) { - try { - String endTag= xpp.getName(); - String tnm= xpp.getName(); - int eventType = xpp.getEventType(); - int chartType= BARCHART; - ChartAxes ca= parentChart.getAxes(); - - java.util.Stack lastTag= new java.util.Stack(); // keep track of element hierarchy - ChartFormat cf= parentChart.getChartOjectParent(nChart); - - EnumSet options= EnumSet.noneOf(ChartOptions.class); // chart-specific options such as threed, stacked ... - if ((tnm.equals("bubble3D"))) { // bubble3D tag appears for each series in 3D bubble chart - options.add(ChartOptions.THREED); - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART])) { - chartType= BARCHART; - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.LINECHART])) { - chartType= LINECHART; - }else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.PIECHART])) { - chartType= PIECHART; - parentChart.getAxes().removeAxes(); - }else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.AREACHART])) { - chartType= AREACHART; - }else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SCATTERCHART])) { - chartType= SCATTERCHART; - ca.removeAxis(XAXIS); - ca.createAxis(XVALAXIS); - }else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.RADARCHART])) { - chartType= RADARCHART; - }else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SURFACECHART])) { - chartType= SURFACECHART; - }else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.DOUGHNUTCHART])) { - chartType= DOUGHNUTCHART; - parentChart.getAxes().removeAxes(); - }else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BUBBLECHART])) { - chartType= BUBBLECHART; - ca.removeAxis(XAXIS); - ca.createAxis(XVALAXIS); - }else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.BARCHART])) { - chartType= BARCHART; - options.add(ChartOptions.THREED); - }else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.LINECHART])) { - chartType= LINECHART; - options.add(ChartOptions.THREED); - }else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.PIECHART])) { - chartType= PIECHART; - options.add(ChartOptions.THREED); - parentChart.getAxes().removeAxes(); - }else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.AREACHART])) { - chartType= AREACHART; - options.add(ChartOptions.THREED); - }else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SCATTERCHART])) { - chartType= SCATTERCHART; - ca.removeAxis(XAXIS); - ca.createAxis(XVALAXIS); - options.add(ChartOptions.THREED); - }else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.RADARCHART])) { - chartType= RADARCHART; - options.add(ChartOptions.THREED); - }else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART])) { - chartType= SURFACECHART; - options.add(ChartOptions.THREED); - }else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.DOUGHNUTCHART])) { - chartType= DOUGHNUTCHART; - options.add(ChartOptions.THREED); - parentChart.getAxes().removeAxes(); - }else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.OFPIECHART])) { - chartType= OFPIECHART; - ca.removeAxis(XAXIS); - ca.removeAxis(YAXIS); - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.STOCKCHART])) { - chartType= STOCKCHART; - } - - GenericChartObject co= ChartType.createUnderlyingChartObject(chartType, parentChart, cf); //, ((ChartType)chartobj.get(0))); - cf.setChartObject(co); // sets the chart format (parent of chart item) to the specific chart item - // exception for surface charts in 3d - if (chartType==SURFACECHART && (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART]))) - ((Surface)co).setIs3d(true); - ChartType ct= ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); - parentChart.addChartType(ct, nChart); - - while (eventType != XmlPullParser.END_DOCUMENT) { - if(eventType == XmlPullParser.START_TAG) { - tnm = xpp.getName(); - lastTag.push(tnm); - String v= null; - try { - v= xpp.getAttributeValue(0); - } catch (IndexOutOfBoundsException e) {} + try { + String endTag = xpp.getName(); + String tnm = xpp.getName(); + int eventType = xpp.getEventType(); + int chartType = BARCHART; + ChartAxes ca = parentChart.getAxes(); + + java.util.Stack lastTag = new java.util.Stack(); // keep track of element hierarchy + ChartFormat cf = parentChart.getChartOjectParent(nChart); + + EnumSet options = EnumSet.noneOf(ChartOptions.class); // chart-specific options such as threed, stacked ... + if ((tnm.equals("bubble3D"))) { // bubble3D tag appears for each series in 3D bubble chart + options.add(ChartOptions.THREED); + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART])) { + chartType = BARCHART; + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.LINECHART])) { + chartType = LINECHART; + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.PIECHART])) { + chartType = PIECHART; + parentChart.getAxes().removeAxes(); + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.AREACHART])) { + chartType = AREACHART; + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SCATTERCHART])) { + chartType = SCATTERCHART; + ca.removeAxis(XAXIS); + ca.createAxis(XVALAXIS); + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.RADARCHART])) { + chartType = RADARCHART; + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SURFACECHART])) { + chartType = SURFACECHART; + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.DOUGHNUTCHART])) { + chartType = DOUGHNUTCHART; + parentChart.getAxes().removeAxes(); + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BUBBLECHART])) { + chartType = BUBBLECHART; + ca.removeAxis(XAXIS); + ca.createAxis(XVALAXIS); + } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.BARCHART])) { + chartType = BARCHART; + options.add(ChartOptions.THREED); + } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.LINECHART])) { + chartType = LINECHART; + options.add(ChartOptions.THREED); + } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.PIECHART])) { + chartType = PIECHART; + options.add(ChartOptions.THREED); + parentChart.getAxes().removeAxes(); + } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.AREACHART])) { + chartType = AREACHART; + options.add(ChartOptions.THREED); + } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SCATTERCHART])) { + chartType = SCATTERCHART; + ca.removeAxis(XAXIS); + ca.createAxis(XVALAXIS); + options.add(ChartOptions.THREED); + } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.RADARCHART])) { + chartType = RADARCHART; + options.add(ChartOptions.THREED); + } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART])) { + chartType = SURFACECHART; + options.add(ChartOptions.THREED); + } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.DOUGHNUTCHART])) { + chartType = DOUGHNUTCHART; + options.add(ChartOptions.THREED); + parentChart.getAxes().removeAxes(); + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.OFPIECHART])) { + chartType = OFPIECHART; + ca.removeAxis(XAXIS); + ca.removeAxis(YAXIS); + } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.STOCKCHART])) { + chartType = STOCKCHART; + } + + GenericChartObject co = ChartType.createUnderlyingChartObject(chartType, parentChart, cf); //, ((ChartType)chartobj.get(0))); + cf.setChartObject(co); // sets the chart format (parent of chart item) to the specific chart item + // exception for surface charts in 3d + if (chartType == SURFACECHART && (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART]))) + ((Surface) co).setIs3d(true); + ChartType ct = ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); + parentChart.addChartType(ct, nChart); + + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + tnm = xpp.getName(); + lastTag.push(tnm); + String v = null; + try { + v = xpp.getAttributeValue(0); + } catch (IndexOutOfBoundsException e) { + } // TODO: // hasMarkers **** // dLbls @@ -659,303 +662,323 @@ public static ChartType parseOOXML(XmlPullParser xpp, WorkBookHandle wbh, Chart // showNegBubbles // sizeRepresents // custSplit -- OfPie - if (tnm.equals("grouping")) { //This element specifies the type of grouping for a column, line, or area chart + 3d versions - if (v.equals("stacked")) - co.setIsStacked(true); - else if (v.equals("percentStacked")) - co.setIs100Percent(true); - else if (v.equals("clustered")) - cf.setIsClustered(true); // bar/col only - else if (v.equals("standard")) { // for Line, Line3d, Area, Area3d and Bar3d, Col3d - does not appear valid for Bar 2d and Col 2d - co.setIsStacked(false); - co.setIs100Percent(false); - } - } else if (tnm.equals("barDir")) { // - if (v.equals("col")) { - ((Bar)co).setAsColumnChart(); - ct= ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); - parentChart.addChartType(ct, nChart); - } - } else if (tnm.equals("shape")) { // bar3d - cf.setBarShape(ct.convertShape(v)); - if (ct.defaultShape!=0) { - ct= ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); - parentChart.addChartType(ct, nChart); - } - } else if (tnm.equals("radarStyle")) { - if (v.equals("filled")) { - ((RadarChart) ct).setFilled(true); - } else if (v.equals("marker")) { - - } - } else if (tnm.equals("wireframe")) { // surface - ((Surface)co).setIsWireframe(v!=null && v.equals("1")); - } else if (tnm.equals("scatterStyle")) { - if (v.equals("lineMarker")) { - cf.setHasLines(); - } else if (v.equals("smoothMarker")) { - cf.setHasSmoothLines(true); - }else if (v.equals("marker")) - ; //cf.seth; - else if (v.equals("line")) - cf.setHasLines(); - else if (v.equals("smooth")) - cf.setHasSmoothLines(true); - } else if (tnm.equals("varyColors")) { - cf.setVaryColor(xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("dropLines")) { - ChartLine cl= cf.addChartLines(ChartLine.TYPE_DROPLINE); - cl.parseOOXML(xpp, lastTag, cf, wbh); - } else if (tnm.equals("hiLowLines")) { - ChartLine cl= cf.addChartLines(ChartLine.TYPE_HILOWLINE); - cl.parseOOXML(xpp, lastTag, cf, wbh); - } else if (tnm.equals("upDownBars")) { - cf.parseUpDownBarsOOXML(xpp, lastTag, wbh); - } else if (tnm.equals("serLines")) { - ChartLine cl= cf.addChartLines(ChartLine.TYPE_SERIESLINE); - cl.parseOOXML(xpp, lastTag, cf, wbh); - } else if (tnm.equals("overlap")) { // bar - co.setChartOption("Overlap", v); - } else if (tnm.equals("gapWidth")) { - co.setChartOption("Gap", v); // bar - } else if (tnm.equals("ofPieType")) { - ((Boppop)co).setIsPieOfPie("pie".equals(v)); - } else if (tnm.equals("gapDepth")) { - cf.setGapDepth(Integer.valueOf(v)); // bar3d, area3d, line3d - } else if (tnm.equals("firstSliceAn")) { - ((Pie)co).setAnStart(Integer.valueOf(v)); // pie or doughnut - } else if (tnm.equals("holeSize")) { - ((Pie)co).setDoughnutSize(Integer.valueOf(v)); // pie or doughnut - } else if (tnm.equals("secondPieSize")) { - ((Boppop)co).setSecondPieSize(Integer.valueOf(v)); // OfPie (Pie of Pie, Bar of Pie) - } else if (tnm.equals("splitType")) { - ((Boppop)co).setSplitType(v); // OfPie (Pie of Pie, Bar of Pie) - } else if (tnm.equals("splitPos")) { - ((Boppop)co).setSplitPos(Integer.valueOf(v)); // OfPie (Pie of Pie, Bar of Pie) - }else if (tnm.equals("ser")) { - Series s= ChartSeries.parseOOXML(xpp, wbh, ct, false, lastTag); - } else if (tnm.equals("dLbls")) { - } else if (tnm.equals("marker")) { // line only? + if (tnm.equals("grouping")) { //This element specifies the type of grouping for a column, line, or area chart + 3d versions + if (v.equals("stacked")) + co.setIsStacked(true); + else if (v.equals("percentStacked")) + co.setIs100Percent(true); + else if (v.equals("clustered")) + cf.setIsClustered(true); // bar/col only + else if (v.equals("standard")) { // for Line, Line3d, Area, Area3d and Bar3d, Col3d - does not appear valid for Bar 2d and Col 2d + co.setIsStacked(false); + co.setIs100Percent(false); + } + } else if (tnm.equals("barDir")) { // + if (v.equals("col")) { + ((Bar) co).setAsColumnChart(); + ct = ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); + parentChart.addChartType(ct, nChart); + } + } else if (tnm.equals("shape")) { // bar3d + cf.setBarShape(ct.convertShape(v)); + if (ct.defaultShape != 0) { + ct = ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); + parentChart.addChartType(ct, nChart); + } + } else if (tnm.equals("radarStyle")) { + if (v.equals("filled")) { + ((RadarChart) ct).setFilled(true); + } else if (v.equals("marker")) { + + } + } else if (tnm.equals("wireframe")) { // surface + ((Surface) co).setIsWireframe(v != null && v.equals("1")); + } else if (tnm.equals("scatterStyle")) { + if (v.equals("lineMarker")) { + cf.setHasLines(); + } else if (v.equals("smoothMarker")) { + cf.setHasSmoothLines(true); + } else if (v.equals("marker")) + ; //cf.seth; + else if (v.equals("line")) + cf.setHasLines(); + else if (v.equals("smooth")) + cf.setHasSmoothLines(true); + } else if (tnm.equals("varyColors")) { + cf.setVaryColor(xpp.getAttributeValue(0).equals("1")); + } else if (tnm.equals("dropLines")) { + ChartLine cl = cf.addChartLines(ChartLine.TYPE_DROPLINE); + cl.parseOOXML(xpp, lastTag, cf, wbh); + } else if (tnm.equals("hiLowLines")) { + ChartLine cl = cf.addChartLines(ChartLine.TYPE_HILOWLINE); + cl.parseOOXML(xpp, lastTag, cf, wbh); + } else if (tnm.equals("upDownBars")) { + cf.parseUpDownBarsOOXML(xpp, lastTag, wbh); + } else if (tnm.equals("serLines")) { + ChartLine cl = cf.addChartLines(ChartLine.TYPE_SERIESLINE); + cl.parseOOXML(xpp, lastTag, cf, wbh); + } else if (tnm.equals("overlap")) { // bar + co.setChartOption("Overlap", v); + } else if (tnm.equals("gapWidth")) { + co.setChartOption("Gap", v); // bar + } else if (tnm.equals("ofPieType")) { + ((Boppop) co).setIsPieOfPie("pie".equals(v)); + } else if (tnm.equals("gapDepth")) { + cf.setGapDepth(Integer.valueOf(v)); // bar3d, area3d, line3d + } else if (tnm.equals("firstSliceAn")) { + ((Pie) co).setAnStart(Integer.valueOf(v)); // pie or doughnut + } else if (tnm.equals("holeSize")) { + ((Pie) co).setDoughnutSize(Integer.valueOf(v)); // pie or doughnut + } else if (tnm.equals("secondPieSize")) { + ((Boppop) co).setSecondPieSize(Integer.valueOf(v)); // OfPie (Pie of Pie, Bar of Pie) + } else if (tnm.equals("splitType")) { + ((Boppop) co).setSplitType(v); // OfPie (Pie of Pie, Bar of Pie) + } else if (tnm.equals("splitPos")) { + ((Boppop) co).setSplitPos(Integer.valueOf(v)); // OfPie (Pie of Pie, Bar of Pie) + } else if (tnm.equals("ser")) { + Series s = ChartSeries.parseOOXML(xpp, wbh, ct, false, lastTag); + } else if (tnm.equals("dLbls")) { + } else if (tnm.equals("marker")) { // line only? // m= (Marker) Marker.parseOOXML(xpp, lastTag).cloneElement(); - } - } else if (eventType== XmlPullParser.END_TAG) { - if (xpp.getName().equals(endTag)) - break; - - } - eventType = xpp.next(); - } - return ct; - + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.getName().equals(endTag)) + break; + + } + eventType = xpp.next(); + } + return ct; + // lastTag.pop(); // chart type tag will be added in parseOOXML //mychart.getChartObject(nChart).parseOOXML(xpp, this.wbh, lastTag); - } catch (Exception e) { - Logger.logErr("ChartType.parseChartType: " + e.toString()); - } - return null; - } - - /** - * return data label options for each series as an int array - *
                each can be one or more of: -
                VALUELABEL= 0x1; -
                VALUEPERCENT= 0x2; -
                CATEGORYPERCENT= 0x4; -
                SMOOTHEDLINE= 0x8; -
                CATEGORYLABEL= 0x10; -
                BUBBLELABEL= 0x20; -
                SERIESLABEL= 0x40; - * @see AttachedLabel - * @return int array - */ - protected int[] getDataLabelInts() { - return chartobj.getParentChart().getDataLabelsPerSeries(cf.getDataLabelsInt()); // data label options, if any, per series - } - - /** - * return the default data label setting for the chart, if any - *
                NOTE: each series can override the default data label for the chart - *
                can be one or more of: -
                VALUELABEL= 0x1; -
                VALUEPERCENT= 0x2; -
                CATEGORYPERCENT= 0x4; -
                SMOOTHEDLINE= 0x8; -
                CATEGORYLABEL= 0x10; -
                BUBBLELABEL= 0x20; -
                SERIESLABEL= 0x40; - * @return int default data label for chart - */ - protected int getDataLabel() { - return cf.getDataLabelsInt(); - } - - /** return an array of the type of markers for each series: -
                0 = no marker -
                1 = square -
                2 = diamond -
                3 = triangle -
                4 = X -
                5 = star -
                6 = Dow-Jones -
                7 = standard deviation -
                8 = circle -
                9 = plus sign - **/ - protected int[] getMarkerFormats() { - int mf= cf.getMarkerFormat(); - int[] markers= new int[getParentChart().getAllSeries(getParentChart().getChartOrder(this)).size()]; - if (mf > 0) { - for (int i= 0; i < markers.length; i++) { - markers[i]= mf; - } - } - return markers; - } - - /** - * specifies whether - * the color for each data point and the color and type for each data marker - * vary - * @param b - */ - protected void setVaryColor(boolean b) { - cf.setVaryColor(b); - } - - /** + } catch (Exception e) { + Logger.logErr("ChartType.parseChartType: " + e.toString()); + } + return null; + } + + /** + * return data label options for each series as an int array + *
                each can be one or more of: + *
                VALUELABEL= 0x1; + *
                VALUEPERCENT= 0x2; + *
                CATEGORYPERCENT= 0x4; + *
                SMOOTHEDLINE= 0x8; + *
                CATEGORYLABEL= 0x10; + *
                BUBBLELABEL= 0x20; + *
                SERIESLABEL= 0x40; + * + * @return int array + * @see AttachedLabel + */ + protected int[] getDataLabelInts() { + return chartobj.getParentChart().getDataLabelsPerSeries(cf.getDataLabelsInt()); // data label options, if any, per series + } + + /** + * return the default data label setting for the chart, if any + *
                NOTE: each series can override the default data label for the chart + *
                can be one or more of: + *
                VALUELABEL= 0x1; + *
                VALUEPERCENT= 0x2; + *
                CATEGORYPERCENT= 0x4; + *
                SMOOTHEDLINE= 0x8; + *
                CATEGORYLABEL= 0x10; + *
                BUBBLELABEL= 0x20; + *
                SERIESLABEL= 0x40; + * + * @return int default data label for chart + */ + protected int getDataLabel() { + return cf.getDataLabelsInt(); + } + + /** + * return an array of the type of markers for each series: + *
                0 = no marker + *
                1 = square + *
                2 = diamond + *
                3 = triangle + *
                4 = X + *
                5 = star + *
                6 = Dow-Jones + *
                7 = standard deviation + *
                8 = circle + *
                9 = plus sign + **/ + protected int[] getMarkerFormats() { + int mf = cf.getMarkerFormat(); + int[] markers = new int[getParentChart().getAllSeries(getParentChart().getChartOrder(this)).size()]; + if (mf > 0) { + for (int i = 0; i < markers.length; i++) { + markers[i] = mf; + } + } + return markers; + } + + /** + * specifies whether + * the color for each data point and the color and type for each data marker + * vary + * + * @param b + */ + protected void setVaryColor(boolean b) { + cf.setVaryColor(b); + } + + /** * returns true if this chart has smoothed lines + * * @return */ public boolean getHasSmoothLines() { - return cf.getHasSmoothLines(); - } - - - public void setHasSmoothLines(boolean b) { - cf.setHasSmoothLines(b); - } - - - /** - * returns the chart type for the default chart - */ - public int getChartType() { - return chartobj.chartType; - } - - public void addLegend(Legend l) { - legend= l; - } - - public String getSVG() { return null; } - public String getJSON() { return null; } - public JSONObject getOptionsJSON() { return null; } - - /** - * gets the chart-type specific OOXML representation (representing child element of plotArea element) - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { return null; } - + return cf.getHasSmoothLines(); + } + + + public void setHasSmoothLines(boolean b) { + cf.setHasSmoothLines(b); + } + + + /** + * returns the chart type for the default chart + */ + public int getChartType() { + return chartobj.chartType; + } + + public void addLegend(Legend l) { + legend = l; + } + + public String getSVG() { + return null; + } + + public String getJSON() { + return null; + } + + public JSONObject getOptionsJSON() { + return null; + } + + /** + * gets the chart-type specific OOXML representation (representing child element of plotArea element) + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + return null; + } + public JSONObject getJSON(ChartSeries s, WorkBookHandle wbh, Double[] minMax) throws JSONException { - JSONObject chartObjectJSON= new JSONObject(); - - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // Deal with Series - double yMax= 0.0, yMin= 0.0; - int nSeries= 0; - JSONArray seriesJSON= new JSONArray(); - JSONArray seriesCOLORS= new JSONArray(); - try { - ArrayList series= s.getSeriesRanges(); - String[] scolors= s.getSeriesBarColors(); - for (int i= 0; i < series.size(); i++) { - JSONArray seriesvals= CellRange.getValuesAsJSON(series.get(i).toString(), wbh); - // must trap min and max for axis tick and units - nSeries= Math.max(nSeries, seriesvals.length()); - for (int j= 0; j < seriesvals.length(); j++) { - try { - yMax= Math.max(yMax, seriesvals.getDouble(j)); - yMin= Math.min(yMin, seriesvals.getDouble(j)); - } catch (NumberFormatException n) {;} - } - seriesJSON.put(seriesvals); - seriesCOLORS.put(scolors[i]); - } - chartObjectJSON.put("Series", seriesJSON); - chartObjectJSON.put("SeriesFills", seriesCOLORS); - } catch (JSONException je) { - // TODO: Log error - } - minMax[0]= new Double(yMin); - minMax[1]= new Double(yMax); - minMax[2]= new Double(nSeries); - return chartObjectJSON; - } - + JSONObject chartObjectJSON = new JSONObject(); + + // Type JSON + chartObjectJSON.put("type", this.getTypeJSON()); + + // Deal with Series + double yMax = 0.0, yMin = 0.0; + int nSeries = 0; + JSONArray seriesJSON = new JSONArray(); + JSONArray seriesCOLORS = new JSONArray(); + try { + ArrayList series = s.getSeriesRanges(); + String[] scolors = s.getSeriesBarColors(); + for (int i = 0; i < series.size(); i++) { + JSONArray seriesvals = CellRange.getValuesAsJSON(series.get(i).toString(), wbh); + // must trap min and max for axis tick and units + nSeries = Math.max(nSeries, seriesvals.length()); + for (int j = 0; j < seriesvals.length(); j++) { + try { + yMax = Math.max(yMax, seriesvals.getDouble(j)); + yMin = Math.min(yMin, seriesvals.getDouble(j)); + } catch (NumberFormatException n) { + } + } + seriesJSON.put(seriesvals); + seriesCOLORS.put(scolors[i]); + } + chartObjectJSON.put("Series", seriesJSON); + chartObjectJSON.put("SeriesFills", seriesCOLORS); + } catch (JSONException je) { + // TODO: Log error + } + minMax[0] = new Double(yMin); + minMax[1] = new Double(yMax); + minMax[2] = new Double(nSeries); + return chartObjectJSON; + } + /** - * return Type JSON for generic chart types + * return Type JSON for generic chart types + * * @return * @throws JSONException */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON= new JSONObject(); - typeJSON.put("type", "Default"); - return typeJSON; - } - - /** - * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) + public JSONObject getTypeJSON() throws JSONException { + JSONObject typeJSON = new JSONObject(); + typeJSON.put("type", "Default"); + return typeJSON; + } + + /** + * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param s ChartSeries - holds legends, categories, seriesdata ... - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s){ - return ""; - } - - /** - * returns the SVG-ready data label for the given set of data label options - * TODO: get separator character - * @param datalabel int[] Data label options (indexed by current series # s) - *
                can be one or more of: -
                VALUELABEL= 0x1; -
                VALUEPERCENT= 0x2; -
                CATEGORYPERCENT= 0x4; -
                SMOOTHEDLINE= 0x8; -
                CATEGORYLABEL= 0x10; -
                BUBBLELABEL= 0x20; -
                SERIESLABEL= 0x40; - * @param series ArrayList of series and category data: structure: category data, series 0-n data, series colors, series Labels (Eventually will be an object) - * @param val double current series value - * @param percentage double percentage value (pie charts only) - * @param s int current series # - * @param cat string current cat - * @return - */ - public String getSVGDataLabels(int[] datalabels, HashMapaxisMetrics, double val, double percentage, int s, String[] legends, String cat) { - - if (s >= datalabels.length) // can happen with Pie-style charts - return null; - boolean showValueLabel= (datalabels[s] & AttachedLabel.VALUELABEL)==AttachedLabel.VALUELABEL; - boolean showValuePercent= (datalabels[s] & AttachedLabel.VALUEPERCENT)==AttachedLabel.VALUEPERCENT; - boolean showCatPercent= (datalabels[s] & AttachedLabel.CATEGORYPERCENT)==AttachedLabel.CATEGORYPERCENT; - boolean showCategories= (datalabels[s] & AttachedLabel.CATEGORYLABEL)==AttachedLabel.CATEGORYLABEL; - boolean showBubbleLabel= (datalabels[s] & AttachedLabel.BUBBLELABEL)==AttachedLabel.BUBBLELABEL; - boolean showValue= (datalabels[s] & AttachedLabel.VALUE)==AttachedLabel.VALUE; - if (showValue || showCategories || showValueLabel || showValuePercent || showBubbleLabel) { - String l= ""; - if (showValueLabel) - l+= legends[s] + " "; // series names - if (showCategories) - l += CellFormatFactory.fromPatternString( - (String) axisMetrics.get("xPattern") ).format( cat ) + " "; // categories - if (showValue || showBubbleLabel) { - l += CellFormatFactory.fromPatternString( - (String) axisMetrics.get("yPattern") ).format( String.valueOf(val) ); + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param s ChartSeries - holds legends, categories, seriesdata ... + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + return ""; + } + + /** + * returns the SVG-ready data label for the given set of data label options + * TODO: get separator character + * + * @param datalabel int[] Data label options (indexed by current series # s) + *
                can be one or more of: + *
                VALUELABEL= 0x1; + *
                VALUEPERCENT= 0x2; + *
                CATEGORYPERCENT= 0x4; + *
                SMOOTHEDLINE= 0x8; + *
                CATEGORYLABEL= 0x10; + *
                BUBBLELABEL= 0x20; + *
                SERIESLABEL= 0x40; + * @param series ArrayList of series and category data: structure: category data, series 0-n data, series colors, series Labels (Eventually will be an object) + * @param val double current series value + * @param percentage double percentage value (pie charts only) + * @param s int current series # + * @param cat string current cat + * @return + */ + public String getSVGDataLabels(int[] datalabels, HashMap axisMetrics, double val, double percentage, int s, String[] legends, String cat) { + + if (s >= datalabels.length) // can happen with Pie-style charts + return null; + boolean showValueLabel = (datalabels[s] & AttachedLabel.VALUELABEL) == AttachedLabel.VALUELABEL; + boolean showValuePercent = (datalabels[s] & AttachedLabel.VALUEPERCENT) == AttachedLabel.VALUEPERCENT; + boolean showCatPercent = (datalabels[s] & AttachedLabel.CATEGORYPERCENT) == AttachedLabel.CATEGORYPERCENT; + boolean showCategories = (datalabels[s] & AttachedLabel.CATEGORYLABEL) == AttachedLabel.CATEGORYLABEL; + boolean showBubbleLabel = (datalabels[s] & AttachedLabel.BUBBLELABEL) == AttachedLabel.BUBBLELABEL; + boolean showValue = (datalabels[s] & AttachedLabel.VALUE) == AttachedLabel.VALUE; + if (showValue || showCategories || showValueLabel || showValuePercent || showBubbleLabel) { + String l = ""; + if (showValueLabel) + l += legends[s] + " "; // series names + if (showCategories) + l += CellFormatFactory.fromPatternString( + (String) axisMetrics.get("xPattern")).format(cat) + " "; // categories + if (showValue || showBubbleLabel) { + l += CellFormatFactory.fromPatternString( + (String) axisMetrics.get("yPattern")).format(String.valueOf(val)); /*try { int v= new Double(val).intValue(); if (v==val) @@ -965,218 +988,234 @@ public String getSVGDataLabels(int[] datalabels, HashMapaxisMetr } catch (Exception e) { l+= val + " "; }*/ - } - if (showValuePercent) - l+= (int) Math.round(percentage*100) + "%"; - return l; - } - return null; - } - - /** Show or remove Data Table for Chart - * NOTE: METHOD IS STILL EXPERIMENTAL - * @param bShow - * - public void showDataTable(boolean bShow) { - int i= Chart.findRecPosition(chartArr, Dat.class); - if (bShow) { - if (i==-1) { // add Dat - Dat d= (Dat)Dat.getPrototype(true); // create data table - i= Chart.findRecPosition(chartArr, AxisParent.class); - this.chartArr.add(++i, d); - } - } else if (i > 0) { - chartArr.remove(i); // remove Dat - Data Table options + all associated recs - } - }*/ - - - /** - * return truth if Chart has a data legend key showing - * @return - */ - public boolean hasDataLegend() { - return (legend!=null); - } - - /** - * return the data legend for this chart - * @return - */ - public Legend getDataLegend() { - return legend; - } - - /** - * show or hide chart legend key - * @param bShow boolean show or hide - * @param vertical boolean show as vertical or horizontal - */ - public void showLegend(boolean bShow, boolean vertical) { - if (bShow && legend==null) { - legend = Legend.createDefaultLegend(wb); - legend.setParentChart(chartobj.getParentChart()); - for (int j = 0; j < legend.chartArr.size(); j++) - ((GenericChartObject) legend.chartArr.get(j)).setParentChart(chartobj.getParentChart()); - legend.setVertical(vertical); - cf.chartArr.add(legend); - } else if (bShow) { - legend.setVertical(vertical); - } else if (legend!=null) { - int i = Chart.findRecPosition(cf.chartArr, Legend.class); - cf.chartArr.remove(i); - legend= null; - } - } - - /** - * return the Data Labels chosen for this chart, if any - * can be one or more of: - *
                Value - *
                ValuePerecentage - *
                CategoryPercentage - *
                CategoryLabel - *
                BubbleLabel - *
                SeriesLabel - * or an empty string if no data labels are chosen for the chart - * @return - */ - public String getDataLabels() { + } + if (showValuePercent) + l += (int) Math.round(percentage * 100) + "%"; + return l; + } + return null; + } + + /** Show or remove Data Table for Chart + * NOTE: METHOD IS STILL EXPERIMENTAL + * @param bShow + * + public void showDataTable(boolean bShow) { + int i= Chart.findRecPosition(chartArr, Dat.class); + if (bShow) { + if (i==-1) { // add Dat + Dat d= (Dat)Dat.getPrototype(true); // create data table + i= Chart.findRecPosition(chartArr, AxisParent.class); + this.chartArr.add(++i, d); + } + } else if (i > 0) { + chartArr.remove(i); // remove Dat - Data Table options + all associated recs + } + }*/ + + + /** + * return truth if Chart has a data legend key showing + * + * @return + */ + public boolean hasDataLegend() { + return (legend != null); + } + + /** + * return the data legend for this chart + * + * @return + */ + public Legend getDataLegend() { + return legend; + } + + /** + * show or hide chart legend key + * + * @param bShow boolean show or hide + * @param vertical boolean show as vertical or horizontal + */ + public void showLegend(boolean bShow, boolean vertical) { + if (bShow && legend == null) { + legend = Legend.createDefaultLegend(wb); + legend.setParentChart(chartobj.getParentChart()); + for (int j = 0; j < legend.chartArr.size(); j++) + ((GenericChartObject) legend.chartArr.get(j)).setParentChart(chartobj.getParentChart()); + legend.setVertical(vertical); + cf.chartArr.add(legend); + } else if (bShow) { + legend.setVertical(vertical); + } else if (legend != null) { + int i = Chart.findRecPosition(cf.chartArr, Legend.class); + cf.chartArr.remove(i); + legend = null; + } + } + + /** + * return the Data Labels chosen for this chart, if any + * can be one or more of: + *
                Value + *
                ValuePerecentage + *
                CategoryPercentage + *
                CategoryLabel + *
                BubbleLabel + *
                SeriesLabel + * or an empty string if no data labels are chosen for the chart + * + * @return + */ + public String getDataLabels() { return cf.getDataLabels(); - } - + } + /** * return data label options as an int *
                can be one or more of: -
                VALUELABEL= 0x1; -
                VALUEPERCENT= 0x2; -
                CATEGORYPERCENT= 0x4; -
                SMOOTHEDLINE= 0x8; -
                CATEGORYLABEL= 0x10; -
                BUBBLELABEL= 0x20; -
                SERIESLABEL= 0x40; - * @see AttachedLabel - * @return a combination of data label options above or 0 if none + *
                VALUELABEL= 0x1; + *
                VALUEPERCENT= 0x2; + *
                CATEGORYPERCENT= 0x4; + *
                SMOOTHEDLINE= 0x8; + *
                CATEGORYLABEL= 0x10; + *
                BUBBLELABEL= 0x20; + *
                SERIESLABEL= 0x40; + * + * @return a combination of data label options above or 0 if none + * @see AttachedLabel */ public int getDataLabelsInt() { return cf.getDataLabelsInt(); - } - - /** - * returns the bar shape for a column or bar type chart - * can be one of: - *
                ChartConstants.SHAPECOLUMN default - *
                ChartConstants.SHAPECONEd - *
                ChartConstants.SHAPECONETOMAX - *
                ChartConstants.SHAPECYLINDER - *
                ChartConstants.SHAPEPYRAMID - *
                ChartConstants.SHAPEPYRAMIDTOMAX - * @return int bar shape - */ - public int getBarShape() { return SHAPEDEFAULT; } - + } + + /** + * returns the bar shape for a column or bar type chart + * can be one of: + *
                ChartConstants.SHAPECOLUMN default + *
                ChartConstants.SHAPECONEd + *
                ChartConstants.SHAPECONETOMAX + *
                ChartConstants.SHAPECYLINDER + *
                ChartConstants.SHAPEPYRAMID + *
                ChartConstants.SHAPEPYRAMIDTOMAX + * + * @return int bar shape + */ + public int getBarShape() { + return SHAPEDEFAULT; + } + /** - * returns type of marker for this chart, if any -
                0 = no marker -
                1 = square -
                2 = diamond -
                3 = triangle -
                4 = X -
                5 = star -
                6 = Dow-Jones -
                7 = standard deviation -
                8 = circle -
                9 = plus sign + * returns type of marker for this chart, if any + *
                0 = no marker + *
                1 = square + *
                2 = diamond + *
                3 = triangle + *
                4 = X + *
                5 = star + *
                6 = Dow-Jones + *
                7 = standard deviation + *
                8 = circle + *
                9 = plus sign + * * @return int marker type */ - public int getMarkerFormat() { - return cf.getMarkerFormat(); - } - - /** - * returns true if this chart has lines (see Scatter, Line Charts amongst others) - * @return - */ - public boolean getHasLines() { - return cf.getHasLines(); - } - - - /** - * returns true if this chart has drop lines - * @return - */ - public boolean getHasDropLines() { - return cf.getHasDropLines(); - } - - /** - * sets this chart (Area, Line, Scatter) to have drop lines - */ - public void setHasDropLines() { - cf.setHasDropLines(); - } - - /** - * look up a generic chart option - * @param op - * @return - */ - public String getChartOption(String op) { - String ret= chartobj.getChartOption(op); // if not a chart-specific option, see if it's more generic - if (ret==null) - return cf.getChartOption(op); - return ret; - } - - /** - * return chart-type-specific options in XML form - * @param nChart 0=default, 1-9= overlay charts - * @return String XML - */ - public String getChartOptionsXML() { - StringBuffer sb = new StringBuffer(); - sb.append(chartobj.getOptionsXML()); // chart-type-specific - sb.append(cf.getChartOptionsXML()); // governs threed settings and other misc. options - return sb.toString(); - } - /** - * interface for setting chart-type-specific options - * in a generic fashion - * @param op option - * @param val value - * @see OpenXLS.handleChartElement - * @see ChartHandle.getXML - */ - public boolean setChartOption(String op, String val) { - if (!chartobj.setChartOption(op, val)) // if not handled, - cf.setOption(op, val); - return true; - } - - - /** - * - * @return truth of "Chart is Three-D" - */ - public boolean isThreeD() { - return cf.isThreeD(chartobj.chartType); - } - - /** - * return ThreeD settings for this chart in XML form - * @return String XML - */ - public String getThreeDXML() { - return cf.getThreeDXML(); - } - - /** - * returns the 3d record of the desired chart - * @param chartType one of the chart type constants - * @return - */ - public ThreeD initThreeD(int charttype) { + public int getMarkerFormat() { + return cf.getMarkerFormat(); + } + + /** + * returns true if this chart has lines (see Scatter, Line Charts amongst others) + * + * @return + */ + public boolean getHasLines() { + return cf.getHasLines(); + } + + + /** + * returns true if this chart has drop lines + * + * @return + */ + public boolean getHasDropLines() { + return cf.getHasDropLines(); + } + + /** + * sets this chart (Area, Line, Scatter) to have drop lines + */ + public void setHasDropLines() { + cf.setHasDropLines(); + } + + /** + * look up a generic chart option + * + * @param op + * @return + */ + public String getChartOption(String op) { + String ret = chartobj.getChartOption(op); // if not a chart-specific option, see if it's more generic + if (ret == null) + return cf.getChartOption(op); + return ret; + } + + /** + * return chart-type-specific options in XML form + * + * @param nChart 0=default, 1-9= overlay charts + * @return String XML + */ + public String getChartOptionsXML() { + StringBuffer sb = new StringBuffer(); + sb.append(chartobj.getOptionsXML()); // chart-type-specific + sb.append(cf.getChartOptionsXML()); // governs threed settings and other misc. options + return sb.toString(); + } + + /** + * interface for setting chart-type-specific options + * in a generic fashion + * + * @param op option + * @param val value + * @see OpenXLS.handleChartElement + * @see ChartHandle.getXML + */ + public boolean setChartOption(String op, String val) { + if (!chartobj.setChartOption(op, val)) // if not handled, + cf.setOption(op, val); + return true; + } + + + /** + * @return truth of "Chart is Three-D" + */ + public boolean isThreeD() { + return cf.isThreeD(chartobj.chartType); + } + + /** + * return ThreeD settings for this chart in XML form + * + * @return String XML + */ + public String getThreeDXML() { + return cf.getThreeDXML(); + } + + /** + * returns the 3d record of the desired chart + * + * @param chartType one of the chart type constants + * @return + */ + public ThreeD initThreeD(int charttype) { /* TODO: test if this is necessary AxisParent ap = this.getAxisParent(); try { // first thing, remove the PlotArea and Frame - don't know why but is necessary!! @@ -1185,257 +1224,262 @@ public ThreeD initThreeD(int charttype) { x= Chart.findRecPosition(ap.chartArr, Frame.class); if (x!=-1) ap.chartArr.remove(x); } catch (Exception e) {}*/ - ThreeD td= cf.getThreeDRec(true); - td.setIsPie(charttype==PIECHART || charttype==DOUGHNUTCHART); + ThreeD td = cf.getThreeDRec(true); + td.setIsPie(charttype == PIECHART || charttype == DOUGHNUTCHART); return td; - } - - /** - * return the 3d record of the chart - *
                Creates if not present, if bCreate is true - * @return - */ - public ThreeD getThreeDRec(boolean bCreate) { + } + + /** + * return the 3d record of the chart + *
                Creates if not present, if bCreate is true + * + * @return + */ + public ThreeD getThreeDRec(boolean bCreate) { return cf.getThreeDRec(bCreate); - } + } - /** * @return truth of "Chart is Stacked" */ public boolean isStacked() { - return chartobj.isStacked(); + return chartobj.isStacked(); } + /** * return truth of "Chart is 100% Stacked" + * * @return */ public boolean is100PercentStacked() { - return (chartobj.is100Percent()); + return (chartobj.is100Percent()); } - + public void setIsStacked(boolean isstacked) { - chartobj.setIsStacked(isstacked); + chartobj.setIsStacked(isstacked); } public void setIs100Psercent(boolean ispercentage) { - chartobj.setIs100Percent(ispercentage); + chartobj.setIs100Percent(ispercentage); } - -/** - * @return truth of "Chart is Clustered" (Bar/Col only) - */ - public boolean isClustered() { return false; } - - public void addLegend() { - if (legend==null) { - // TODO: ADD LEGEND to cf - //int i = Chart.findRecPosition(cf.chartArr, Legend.class); - } - } - - // SVG Convenience Methods - public static String getScript(String range){ - return "onmouseover='highLight(evt); showRange(\"" + range + "\");' onclick='handleClick(evt);' onmouseout='restore(evt); hideRange();'"; + + /** + * @return truth of "Chart is Clustered" (Bar/Col only) + */ + public boolean isClustered() { + return false; + } + + public void addLegend() { + if (legend == null) { + // TODO: ADD LEGEND to cf + //int i = Chart.findRecPosition(cf.chartArr, Legend.class); + } + } + + // SVG Convenience Methods + public static String getScript(String range) { + return "onmouseover='highLight(evt); showRange(\"" + range + "\");' onclick='handleClick(evt);' onmouseout='restore(evt); hideRange();'"; // return "onmouseover='highLight(evt);' onclick='handleClick(evt);' onmouseout='restore(evt);'"; - } - - public static String getFillOpacity(){ - return ".75"; - } - - - public static String getTextColor(){ - return "#222222"; - } - - public static String getLightColor(){ - return "#CCCCCC"; - } - - public static String getMediumColor(){ - return "#555555"; - } - - - public static String getDarkColor(){ - return "#333333"; - } - - /** - * returns the SVG for the font style of this object - * - */ - protected String getFontSVG(){ - return getFontSVG(-1); - } - - /** - * Returns SVG used to define font for data labels - * TODO: read correct value from chart recs - * @return - */ - public static String getDataLabelFontSVG() { - int sz= 9; - return "font-family='Arial' font-size='" + sz + "' fill='"+ ChartType.getDarkColor()+"' "; - } - - /** - * returns the SVG for the font style of this object - * - * @param stroke size in pt - */ - public static String getStrokeSVG(){ - return getStrokeSVG(1f, getMediumColor()); - } - - /** - * returns the SVG for the font style of this object - * - * @param stroke size in pt - * @param String stroke color in HTML format - */ - public static String getStrokeSVG(float sz, String strokeclr){ - String stk = " stroke='"+strokeclr+"' stroke-opacity='1' stroke-width='"+sz+"' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='4'"; - return stk; - } - - - /** - * returns the SVG for the font style of this object - * - * @param font size in pt - */ - protected String getFontSVG(int sz){ + } + + public static String getFillOpacity() { + return ".75"; + } + + + public static String getTextColor() { + return "#222222"; + } + + public static String getLightColor() { + return "#CCCCCC"; + } + + public static String getMediumColor() { + return "#555555"; + } + + + public static String getDarkColor() { + return "#333333"; + } + + /** + * returns the SVG for the font style of this object + */ + protected String getFontSVG() { + return getFontSVG(-1); + } + + /** + * Returns SVG used to define font for data labels + * TODO: read correct value from chart recs + * + * @return + */ + public static String getDataLabelFontSVG() { + int sz = 9; + return "font-family='Arial' font-size='" + sz + "' fill='" + ChartType.getDarkColor() + "' "; + } + + /** + * returns the SVG for the font style of this object + * + * @param stroke size in pt + */ + public static String getStrokeSVG() { + return getStrokeSVG(1f, getMediumColor()); + } + + /** + * returns the SVG for the font style of this object + * + * @param stroke size in pt + * @param String stroke color in HTML format + */ + public static String getStrokeSVG(float sz, String strokeclr) { + String stk = " stroke='" + strokeclr + "' stroke-opacity='1' stroke-width='" + sz + "' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='4'"; + return stk; + } + + + /** + * returns the SVG for the font style of this object + * + * @param font size in pt + */ + protected String getFontSVG(int sz) { // io.starter.formats.XLS.Font f = this.getFont(); // this is not correct! - // if(f == null) // return a default - return "font-family='Arial' font-size='" + sz + "' fill='"+getDarkColor()+"' "; - - //return f.getSVG(); - } - - /** - * Convert the user-friendly OOXML shape string to 2003-v int shape flag - * @param shape - * @return - */ - public int convertShape(String shape) { - defaultShape= 0; - if (shape.equals("box")) - defaultShape= 0; - if (shape.equals("cone")) // 1 1 - defaultShape= 257; - if (shape.equals("coneToMax")) // 1 2 - defaultShape= 513; - if (shape.equals("cylinder")) // 1 0 - defaultShape= 1; - if (shape.equals("pyramid")) // 0 1 - defaultShape= 256; - if (shape.equals("pyramidToMax")) // 0 2 - defaultShape=512; - return defaultShape; - } - - /** - * convert the default shape flag to a user-friendly (OOXML-compliant) String - * @param shape int - * @return - */ - public String getShape() { - switch (defaultShape) { - case 0: - return "box"; - case 1: - return "cylinder"; - case 256: - return "pyramid"; - case 257: - return "cone"; - case 512: - return "pyramidToMax"; - case 513: - return "coneToMax"; - } - return null; - } - - /** - * returns an int representing the space between points in a 3d area, bar or line chart, or 0 if not 3d - * @return - */ - public int getGapDepth() { - return cf.getGapDepth(); - } - - /** - * return the SeriesList record for this chart object - * The SeriesList record maps the series for the chart. - * @return - */ - protected SeriesList getSeriesList() { - return (SeriesList) Chart.findRec(cf.chartArr, SeriesList.class); - } - - /** - * - * @param fName - */ - public void WriteMainChartRecs(String fName) { - class util { - public void writeRecs(BiffRec b, BufferedWriter writer, int level) throws IOException { - String tabs= "\t\t\t\t\t\t\t\t\t\t"; - if (b==null) return; - writer.write(tabs.substring(0, level) + b.getClass().toString().substring(b.getClass().toString().lastIndexOf('.')+1)); - if (b instanceof io.starter.formats.XLS.charts.SeriesText) - writer.write("\t[" + ((io.starter.formats.XLS.charts.SeriesText) b).toString()+ "]"); - else if (b instanceof MSODrawing) { - writer.write("\t[" + ((MSODrawing) b).toString() + "]"); - // writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); - writer.write(((MSODrawing) b).debugOutput()); - writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); - } else if (b instanceof Obj) { - writer.write(((Obj) b).debugOutput()); - } else if (b instanceof Label) { - writer.write("\t[" + ((Label) b).getStringVal()+ "]"); - } - else // all else, write bytes - writer.write("\t[" + ByteTools.getByteDump(ByteTools.shortToLEBytes(b.getOpcode()), 0) + "][" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); - writer.newLine(); - try { - if (((GenericChartObject) b).chartArr.size() > 0) { - ArrayList chartArr= ((GenericChartObject) b).chartArr; - for (int i= 0; i < chartArr.size(); i++) { - writeRecs( (BiffRec)chartArr.get(i), writer, level+1); - - } - } - } catch (ClassCastException ce) { - ; - } - } - }; - - - try { - java.io.File f = new java.io.File(fName); - BufferedWriter writer= new BufferedWriter(new FileWriter(f)); - util u= new util(); - - java.util.Vector v= this.getParentChart().getAllSeries(); - for (int i= 0; i < v.size(); i++) { - u.writeRecs( (BiffRec)v.get(i), writer, 0); - } - writer.newLine(); - ArrayList chartArr= this.cf.chartArr; - for (int i= 0; i < chartArr.size(); i++) { - u.writeRecs( (BiffRec)chartArr.get(i), writer, 0); - } - - writer.flush(); - writer.close(); - writer= null; - } catch (Exception e) { - } - } + // if(f == null) // return a default + return "font-family='Arial' font-size='" + sz + "' fill='" + getDarkColor() + "' "; + + //return f.getSVG(); + } + + /** + * Convert the user-friendly OOXML shape string to 2003-v int shape flag + * + * @param shape + * @return + */ + public int convertShape(String shape) { + defaultShape = 0; + if (shape.equals("box")) + defaultShape = 0; + if (shape.equals("cone")) // 1 1 + defaultShape = 257; + if (shape.equals("coneToMax")) // 1 2 + defaultShape = 513; + if (shape.equals("cylinder")) // 1 0 + defaultShape = 1; + if (shape.equals("pyramid")) // 0 1 + defaultShape = 256; + if (shape.equals("pyramidToMax")) // 0 2 + defaultShape = 512; + return defaultShape; + } + + /** + * convert the default shape flag to a user-friendly (OOXML-compliant) String + * + * @param shape int + * @return + */ + public String getShape() { + switch (defaultShape) { + case 0: + return "box"; + case 1: + return "cylinder"; + case 256: + return "pyramid"; + case 257: + return "cone"; + case 512: + return "pyramidToMax"; + case 513: + return "coneToMax"; + } + return null; + } + + /** + * returns an int representing the space between points in a 3d area, bar or line chart, or 0 if not 3d + * + * @return + */ + public int getGapDepth() { + return cf.getGapDepth(); + } + + /** + * return the SeriesList record for this chart object + * The SeriesList record maps the series for the chart. + * + * @return + */ + protected SeriesList getSeriesList() { + return (SeriesList) Chart.findRec(cf.chartArr, SeriesList.class); + } + + /** + * @param fName + */ + public void WriteMainChartRecs(String fName) { + class util { + public void writeRecs(BiffRec b, BufferedWriter writer, int level) throws IOException { + String tabs = "\t\t\t\t\t\t\t\t\t\t"; + if (b == null) return; + writer.write(tabs.substring(0, level) + b.getClass().toString().substring(b.getClass().toString().lastIndexOf('.') + 1)); + if (b instanceof io.starter.formats.XLS.charts.SeriesText) + writer.write("\t[" + b.toString() + "]"); + else if (b instanceof MSODrawing) { + writer.write("\t[" + ((MSODrawing) b).toString() + "]"); + // writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); + writer.write(((MSODrawing) b).debugOutput()); + writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11) + "]"); + } else if (b instanceof Obj) { + writer.write(((Obj) b).debugOutput()); + } else if (b instanceof Label) { + writer.write("\t[" + b.getStringVal() + "]"); + } else // all else, write bytes + writer.write("\t[" + ByteTools.getByteDump(ByteTools.shortToLEBytes(b.getOpcode()), 0) + "][" + ByteTools.getByteDump(b.getData(), 0).substring(11) + "]"); + writer.newLine(); + try { + if (((GenericChartObject) b).chartArr.size() > 0) { + ArrayList chartArr = ((GenericChartObject) b).chartArr; + for (int i = 0; i < chartArr.size(); i++) { + writeRecs(chartArr.get(i), writer, level + 1); + + } + } + } catch (ClassCastException ce) { + } + } + } + + + try { + java.io.File f = new java.io.File(fName); + BufferedWriter writer = new BufferedWriter(new FileWriter(f)); + util u = new util(); + + java.util.Vector v = this.getParentChart().getAllSeries(); + for (int i = 0; i < v.size(); i++) { + u.writeRecs((BiffRec) v.get(i), writer, 0); + } + writer.newLine(); + ArrayList chartArr = this.cf.chartArr; + for (int i = 0; i < chartArr.size(); i++) { + u.writeRecs(chartArr.get(i), writer, 0); + } + + writer.flush(); + writer.close(); + writer = null; + } catch (Exception e) { + } + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Col3DChart.java b/src/main/java/io/starter/formats/XLS/charts/Col3DChart.java index d1ccce5..6b3a387 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Col3DChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/Col3DChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,58 +25,68 @@ import io.starter.formats.XLS.WorkBook; public class Col3DChart extends ColChart { - public Col3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - col= (Bar) charttype; - } - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); + public Col3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + col = (Bar) charttype; + } - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); cooxml.append("\r\n"); - // vary colors??? + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append(""); // default= 0 - int gapdepth= this.getGapDepth(); - if (gapdepth!=0) - cooxml.append(""); - cooxml.append(""); - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - if (this.getParentChart().getAxes().hasAxis(ZAXIS)) { - cooxml.append(""); cooxml.append("\r\n"); - } else {// KSC: // KSC: appears to be necessary but very unclear as to why - cooxml.append(""); cooxml.append("\r\n"); - } + if (this.is100PercentStacked()) + cooxml.append("percentStacked"); + else if (this.isStacked()) + cooxml.append("stacked"); + else if (cf.is3DClustered()) + cooxml.append("clustered"); + else + cooxml.append("standard"); + cooxml.append("\"/>"); + cooxml.append("\r\n"); + // vary colors??? - cooxml.append(""); cooxml.append("\r\n"); + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + if (!this.getChartOption("Gap").equals("150")) + cooxml.append(""); // default= 0 + int gapdepth = this.getGapDepth(); + if (gapdepth != 0) + cooxml.append(""); + cooxml.append(""); + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + if (this.getParentChart().getAxes().hasAxis(ZAXIS)) { + cooxml.append(""); + cooxml.append("\r\n"); + } else {// KSC: // KSC: appears to be necessary but very unclear as to why + cooxml.append(""); + cooxml.append("\r\n"); + } + + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } - return cooxml; - } - } diff --git a/src/main/java/io/starter/formats/XLS/charts/ColChart.java b/src/main/java/io/starter/formats/XLS/charts/ColChart.java index 7377abd..eb7e8da 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ColChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/ColChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,139 +22,149 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.HashMap; + public class ColChart extends ChartType { - protected Bar col= null; + protected Bar col = null; + + public ColChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + col = (Bar) charttype; + defaultShape = SHAPEDEFAULT; + } - public ColChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - col= (Bar) charttype; - defaultShape= SHAPEDEFAULT; - } /** * @return truth of "Chart is Clustered" (Bar/Col only) */ - public boolean isClustered() { - return (/*cf.isClustered()); */ (!isStacked() && !is100PercentStacked())); - } - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + public boolean isClustered() { + return (/*cf.isClustered()); */ (!isStacked() && !is100PercentStacked())); + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double x= chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - if (series.size()==0) { - Logger.logErr("Bar.getSVG: error in series"); - return ""; - } - int n= series.size(); - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - boolean isXReversed= (Boolean)axisMetrics.get("xAxisReversed"); - boolean isYReversed= (Boolean)axisMetrics.get("yAxisReversed"); - - StringBuffer svg= new StringBuffer(); // return svg - svg.append("\r\n"); - - double barw= 0, yfactor= 0; // - if (n>0) { - barw= w/((categories.length*(n+1.0))+1); // w/(#cats * nseries+1) + 1 - if (max!=0) - yfactor= h/max; // h/YMAXSCALE - } - int rfX= (!isXReversed?1:-1); // reverse factor - int rfY= (!isYReversed?1:-1); - // for each series - for (int i= 0; i < n; i++) { // each series group - svg.append("\r\n"); - double y0= y+(!isYReversed?h:0); // start from bottom and work up (unless reversed) - double[] curseries= (double[])series.get(i); - String[] curranges= (String[])s.getSeriesRanges().get(i); - - //x+=barw; // a barwidth separates each series group - for (int j= 0; j < curseries.length; j++) { // each series - double xx= x + barw*(i+1) + (j*(n+1)*barw); // x goes from 1 series to next, corresponding to bar/column color - double hh= yfactor*curseries[j]; // bar height = measure of series value - double yy= y0-(!isYReversed?hh:0); // start drawing column - svg.append(""); - String l= getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); - if (l!=null) { - svg.append("" + l + "\r\n"); - } - } - svg.append("\r\n"); // each series group - } - svg.append("\r\n"); - return svg.toString(); - } - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - //TODO: FINISH DR0P LINES - /// if (this.hasDropLines() ) /* Area, Line, Stock*/ - // cooxml.append() - if (!this.getChartOption("Gap").equals("150")) cooxml.append(""); // default= 0 - if (!this.getChartOption("Overlap").equals("0")) - cooxml.append(""); // default= 0 - // Series Lines - ChartLine cl= cf.getChartLinesRec(); - if (cl!=null) - cooxml.append(cl.getOOXML()); - - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - - cooxml.append(""); cooxml.append("\r\n"); - - return cooxml; - } + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + if (series.size() == 0) { + Logger.logErr("Bar.getSVG: error in series"); + return ""; + } + int n = series.size(); + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + boolean isXReversed = (Boolean) axisMetrics.get("xAxisReversed"); + boolean isYReversed = (Boolean) axisMetrics.get("yAxisReversed"); + + StringBuffer svg = new StringBuffer(); // return svg + svg.append("\r\n"); + + double barw = 0, yfactor = 0; // + if (n > 0) { + barw = w / ((categories.length * (n + 1.0)) + 1); // w/(#cats * nseries+1) + 1 + if (max != 0) + yfactor = h / max; // h/YMAXSCALE + } + int rfX = (!isXReversed ? 1 : -1); // reverse factor + int rfY = (!isYReversed ? 1 : -1); + // for each series + for (int i = 0; i < n; i++) { // each series group + svg.append("\r\n"); + double y0 = y + (!isYReversed ? h : 0); // start from bottom and work up (unless reversed) + double[] curseries = (double[]) series.get(i); + String[] curranges = (String[]) s.getSeriesRanges().get(i); + + //x+=barw; // a barwidth separates each series group + for (int j = 0; j < curseries.length; j++) { // each series + double xx = x + barw * (i + 1) + (j * (n + 1) * barw); // x goes from 1 series to next, corresponding to bar/column color + double hh = yfactor * curseries[j]; // bar height = measure of series value + double yy = y0 - (!isYReversed ? hh : 0); // start drawing column + svg.append(""); + String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); + if (l != null) { + svg.append("" + l + "\r\n"); + } + } + svg.append("\r\n"); // each series group + } + svg.append("\r\n"); + return svg.toString(); + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append(""); + cooxml.append("\r\n"); + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + //TODO: FINISH DR0P LINES + /// if (this.hasDropLines() ) /* Area, Line, Stock*/ + // cooxml.append() + if (!this.getChartOption("Gap").equals("150")) + cooxml.append(""); // default= 0 + if (!this.getChartOption("Overlap").equals("0")) + cooxml.append(""); // default= 0 + // Series Lines + ChartLine cl = cf.getChartLinesRec(); + if (cl != null) + cooxml.append(cl.getOOXML()); + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.java b/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.java index e26bc64..bc76bf7 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,10 +26,13 @@ public class ConeBarChart extends Bar3DChart { - public ConeBarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape= SHAPECONE; - chartobj.chartType= CONEBARCHART; - } - public int getBarShape() { return defaultShape; } + public ConeBarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + defaultShape = SHAPECONE; + chartobj.chartType = CONEBARCHART; + } + + public int getBarShape() { + return defaultShape; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ConeChart.java b/src/main/java/io/starter/formats/XLS/charts/ConeChart.java index cdd0fc2..9b6aefc 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ConeChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/ConeChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,11 +26,14 @@ public class ConeChart extends Col3DChart { - public ConeChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape= SHAPECONE; - chartobj.chartType= CYLINDERCHART; + public ConeChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + defaultShape = SHAPECONE; + chartobj.chartType = CYLINDERCHART; - } - public int getBarShape() { return defaultShape; } + } + + public int getBarShape() { + return defaultShape; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/CrtLayout12.java b/src/main/java/io/starter/formats/XLS/charts/CrtLayout12.java index bb746f6..9ed93c6 100644 --- a/src/main/java/io/starter/formats/XLS/charts/CrtLayout12.java +++ b/src/main/java/io/starter/formats/XLS/charts/CrtLayout12.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,118 +27,121 @@ /** * The CrtLayout12 record specifies the layout information for attached label * (data label or legend) - * 12 frtHeader 0x89D - * 4 dwCheckSum : An unsigned integer that specifies the checksum of the values in the order as follows, if the checksum is incorrect, - * the layout information specified in this record MUST be ignored. - Checksum for type Values - AttachedLabel x1 field of the Pos record in the sequence of records that contains this CrtLayout12 record and conforms to the ATTACHEDLABEL rule. - y1 field of the Pos record in the in the sequence of records that contains this CrtLayout12 record and conforms to the ATTACHEDLABEL rule. - An unsigned integer that specifies whether the attached label is at its default position. MUST be 1 if the dlp field of the Text record in the in the sequence of records that contains this CrtLayout12 record and conforms to the ATTACHEDLABEL rule is equal to 0xA. Otherwise, MUST be zero. - Legend x1 field of the Pos record in the in the sequence of records that contains this CrtLayout12 record and conforms to the LD rule. - y1 field of the Pos record in the in the sequence of records that contains this CrtLayout12 record and conforms to the LD rule. - Width of the legend in pixels. - Height of the legend in pixels. - The fAutoPosX field of Legend record. - The fAutoPosY field of Legend record. - The fAutoSize of the Frame record in the in the sequence of records that contains this CrtLayout12 record and conforms to the LD rule. - The width and height of legend in pixels are calculated with the following steps: - Get chart area width in pixels - chart area width in pixels = (dx field of Chart record - 8) * DPI of the display device / 72 - If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: - chart area width in pixels -= 2 * line width of the display device in pixels - Get chart area height in pixels - chart area height in pixels = (dy field of Chart record - 8) * DPI of the display device / 72 - If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: - chart area height in pixels -= 2 * line height of the display device in pixels - Compute legend size in pixels - legend width in pixels = dx field of Legend / 4000 * chart area width in pixels - legend height in pixels = dy field of Legend / 4000 * chart area height in pixels - 2 1 bit- unused - autolayouttype (4 bits): An unsigned integer that specifies the automatic layout type of the legend. - MUST be ignored when this record is in the sequence of records that conforms to the ATTACHEDLABEL rule. - MUST be a value from the following table: - Value Meaning - 0x0 Align to the bottom - 0x1 Align to top right corner - 0x2 Align to the top - 0x3 Align to the right - 0x4 Align to the left - reserved1 (11 bits): MUST be zero, and MUST be ignored. - 2 wXMode A CrtLayout12Mode structure that specifies the meaning of x. - 2 wYMode A CrtLayout12Mode structure that specifies the meaning of y. - 2 wWidthMode A CrtLayout12Mode structure that specifies the meaning of dx. - 2 wHeightMode A CrtLayout12Mode structure that specifies the meaning of dy. - 8 x (8 bytes): An Xnum value that specifies a horizontal offset. The meaning is determined by wXMode. - 8 y (8 bytes): An Xnum value that specifies a vertical offset. The meaning is determined by wYMode. - 8 dx (8 bytes): An Xnum value that specifies a width or an horizontal offset. The meaning is determined by wWidthMode. - 8 dy (8 bytes): An Xnum value that specifies a height or an vertical offset. The meaning is determined by wHeightMode. - 2 reserved2 (2 bytes): MUST be zero, and MUST be ignored. - * + * 12 frtHeader 0x89D + * 4 dwCheckSum : An unsigned integer that specifies the checksum of the values in the order as follows, if the checksum is incorrect, + * the layout information specified in this record MUST be ignored. + * Checksum for type Values + * AttachedLabel x1 field of the Pos record in the sequence of records that contains this CrtLayout12 record and conforms to the ATTACHEDLABEL rule. + * y1 field of the Pos record in the in the sequence of records that contains this CrtLayout12 record and conforms to the ATTACHEDLABEL rule. + * An unsigned integer that specifies whether the attached label is at its default position. MUST be 1 if the dlp field of the Text record in the in the sequence of records that contains this CrtLayout12 record and conforms to the ATTACHEDLABEL rule is equal to 0xA. Otherwise, MUST be zero. + * Legend x1 field of the Pos record in the in the sequence of records that contains this CrtLayout12 record and conforms to the LD rule. + * y1 field of the Pos record in the in the sequence of records that contains this CrtLayout12 record and conforms to the LD rule. + * Width of the legend in pixels. + * Height of the legend in pixels. + * The fAutoPosX field of Legend record. + * The fAutoPosY field of Legend record. + * The fAutoSize of the Frame record in the in the sequence of records that contains this CrtLayout12 record and conforms to the LD rule. + * The width and height of legend in pixels are calculated with the following steps: + * Get chart area width in pixels + * chart area width in pixels = (dx field of Chart record - 8) * DPI of the display device / 72 + * If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: + * chart area width in pixels -= 2 * line width of the display device in pixels + * Get chart area height in pixels + * chart area height in pixels = (dy field of Chart record - 8) * DPI of the display device / 72 + * If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: + * chart area height in pixels -= 2 * line height of the display device in pixels + * Compute legend size in pixels + * legend width in pixels = dx field of Legend / 4000 * chart area width in pixels + * legend height in pixels = dy field of Legend / 4000 * chart area height in pixels + * 2 1 bit- unused + * autolayouttype (4 bits): An unsigned integer that specifies the automatic layout type of the legend. + * MUST be ignored when this record is in the sequence of records that conforms to the ATTACHEDLABEL rule. + * MUST be a value from the following table: + * Value Meaning + * 0x0 Align to the bottom + * 0x1 Align to top right corner + * 0x2 Align to the top + * 0x3 Align to the right + * 0x4 Align to the left + * reserved1 (11 bits): MUST be zero, and MUST be ignored. + * 2 wXMode A CrtLayout12Mode structure that specifies the meaning of x. + * 2 wYMode A CrtLayout12Mode structure that specifies the meaning of y. + * 2 wWidthMode A CrtLayout12Mode structure that specifies the meaning of dx. + * 2 wHeightMode A CrtLayout12Mode structure that specifies the meaning of dy. + * 8 x (8 bytes): An Xnum value that specifies a horizontal offset. The meaning is determined by wXMode. + * 8 y (8 bytes): An Xnum value that specifies a vertical offset. The meaning is determined by wYMode. + * 8 dx (8 bytes): An Xnum value that specifies a width or an horizontal offset. The meaning is determined by wWidthMode. + * 8 dy (8 bytes): An Xnum value that specifies a height or an vertical offset. The meaning is determined by wHeightMode. + * 2 reserved2 (2 bytes): MUST be zero, and MUST be ignored. */ public class CrtLayout12 extends GenericChartObject implements ChartObject { - byte autolayouttype; - short wXMode, wYMode; - short wWidthMode, wHeightMode; - float x, y, dx, dy; - public void init() { - super.init(); - byte[] data= this.getData(); - autolayouttype= (byte)(data[16] >> 1); - wXMode= ByteTools.readShort(data[18], data[19]); - wYMode= ByteTools.readShort(data[20], data[21]); - wWidthMode= ByteTools.readShort(data[22], data[23]); - wHeightMode= ByteTools.readShort(data[24], data[25]); - x= (float)ByteTools.eightBytetoLEDouble(this.getBytesAt(26, 8)); - y= (float)ByteTools.eightBytetoLEDouble(this.getBytesAt(34, 8)); - dx= (float)ByteTools.eightBytetoLEDouble(this.getBytesAt(42, 8)); - dy= (float)ByteTools.eightBytetoLEDouble(this.getBytesAt(50, 8)); - } - /** - * returns the legend autolayout type: - *
                - 0x0 Align to the bottom - 0x1 Align to top right corner - 0x2 Align to the top - 0x3 Align to the right - 0x4 Align to the left - * @return - */ - public int getLayout() { - return autolayouttype; - } + byte autolayouttype; + short wXMode, wYMode; + short wWidthMode, wHeightMode; + float x, y, dx, dy; + + public void init() { + super.init(); + byte[] data = this.getData(); + autolayouttype = (byte) (data[16] >> 1); + wXMode = ByteTools.readShort(data[18], data[19]); + wYMode = ByteTools.readShort(data[20], data[21]); + wWidthMode = ByteTools.readShort(data[22], data[23]); + wHeightMode = ByteTools.readShort(data[24], data[25]); + x = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(26, 8)); + y = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(34, 8)); + dx = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(42, 8)); + dy = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(50, 8)); + } - /** - * sets the layout position of the attached legend or attached label - * *
                - 0x0 Align to the bottom - 0x1 Align to top right corner - 0x2 Align to the top - 0x3 Align to the right - 0x4 Align to the left + /** + * returns the legend autolayout type: + *
                + * 0x0 Align to the bottom + * 0x1 Align to top right corner + * 0x2 Align to the top + * 0x3 Align to the right + * 0x4 Align to the left + * + * @return + */ + public int getLayout() { + return autolayouttype; + } - * @param pos - */ - public void setLayout(int pos) { - autolayouttype= (byte) pos; - this.getData()[16]= (byte)(autolayouttype << 1); - } - - public float[] getCoords() { + /** + * sets the layout position of the attached legend or attached label + * *
                + * 0x0 Align to the bottom + * 0x1 Align to top right corner + * 0x2 Align to the top + * 0x3 Align to the right + * 0x4 Align to the left + * + * @param pos + */ + public void setLayout(int pos) { + autolayouttype = (byte) pos; + this.getData()[16] = (byte) (autolayouttype << 1); + } + + public float[] getCoords() { /* return new float[] { Pos.convertFromSPRC(xTL, w, 0), // x offset Pos.convertFromSPRC(yTL, 0, h), // y offset Pos.convertFromSPRC(xBR, w, 0), // w Pos.convertFromSPRC(yBR, 0, h) }; // h */ - if (wWidthMode==0) { - return null; - } - if (wWidthMode==1) { - //x and y specify the offset of the top left corner, relative to its default position, as a fraction of the chart area. MUST be greater than or equal to -1.0 and MUST be less than or equal to 1.0. dx and dy specify the width and height, as a fraction of the chart area, MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. - - } if (wWidthMode==2) { - // x and y specify the offset of the upper-left corner; dx and dy specify the offset of the bottom-right corner. x, y, dx and dy are specified relative to the upper-left corner of the chart area as a fraction of the chart area. x, y, dx and dy MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. - } - return null; - } + if (wWidthMode == 0) { + return null; + } + if (wWidthMode == 1) { + //x and y specify the offset of the top left corner, relative to its default position, as a fraction of the chart area. MUST be greater than or equal to -1.0 and MUST be less than or equal to 1.0. dx and dy specify the width and height, as a fraction of the chart area, MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. + + } + if (wWidthMode == 2) { + // x and y specify the offset of the upper-left corner; dx and dy specify the offset of the bottom-right corner. x, y, dx and dy are specified relative to the upper-left corner of the chart area as a fraction of the chart area. x, y, dx and dy MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. + } + return null; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.java b/src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.java index 2a14c5d..8c64e6d 100644 --- a/src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.java +++ b/src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,101 +25,101 @@ /** * CrtLayout12A 0x08A7 * The CrtLayout12A record specifies layout information for a plot area. - * - * frtHeader (12 bytes): An FrtHeader structure. The frtheader.rt field MUST be 0x08A7. - - dwCheckSum (4 bytes): An unsigned integer that specifies the checksum. MUST be a value from the following table: - fManPlotArea field of ShtProps fAlwaysAutoPlotArea field of ShtProps dwCheckSum - 0x0 0x0 0x00000001 - 0x0 0x1 0x00000000 - 0x1 0x0 0x00000000 - 0x1 0x1 0x00000001 - - A - fLayoutTargetInner (1 bit): A bit that specifies the type of plot area for the layout target. - Value Meaning - 0x0 Outer plot area - The bounding rectangle that includes the axis labels, axis titles, data table (2) and plot area of the chart. - 0x1 Inner plot area – The rectangle bounded by the chart axes. - - reserved1 (15 bits): MUST be zero, and MUST be ignored. - - xTL (2 bytes): A signed integer that specifies the horizontal offset of the plot area’s upper-left corner, relative to the upper-left corner of the chart area, in SPRC. - yTL (2 bytes): A signed integer that specifies the vertical offset of the plot area’s upper-left corner, relative to the upper-left corner of the chart area, in SPRC. - xBR (2 bytes): A signed integer that specifies the width of the plot area, in SPRC. - yBR (2 bytes): A signed integer that specifies the height of the plot area, in SPRC. - wXMode (2 bytes): A CrtLayout12Mode structure that specifies the meaning of x. - wYMode (2 bytes): A CrtLayout12Mode structure that specifies the meaning of y. - wWidthMode (2 bytes): A CrtLayout12Mode structure that specifies the meaning of dx. - wHeightMode (2 bytes): A CrtLayout12Mode structure that specifies the meaning of dy. - x (8 bytes): An Xnum value that specifies a horizontal offset. The meaning is determined by wXMode. - y (8 bytes): An Xnum value that specifies a vertical offset. The meaning is determined by wYMode. - dx (8 bytes): An Xnum value that specifies a width or a horizontal offset. The meaning is determined by wWidthMode. - dy (8 bytes): An Xnum value that specifies a height or a vertical offset. The meaning is determined by wHeightMode. - reserved2 (2 bytes): MUST be zero, and MUST be ignored. - - The CrtLayout12Mode record specifies a layout mode. Each layout mode specifies a different meaning of the x, y, dx, and dy fields of CrtLayout12 and CrtLayout12A. - Name Value Meaning - L12MAUTO 0x0000 Position and dimension (2) are determined by the application. x, y, dx and dy MUST be ignored. - L12MFACTOR 0x0001 x and y specify the offset of the top left corner, relative to its default position, as a fraction of the chart area. MUST be greater than or equal to -1.0 and MUST be less than or equal to 1.0. dx and dy specify the width and height, as a fraction of the chart area, MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. - L12MEDGE 0x0002 x and y specify the offset of the upper-left corner; dx and dy specify the offset of the bottom-right corner. x, y, dx and dy are specified relative to the upper-left corner of the chart area as a fraction of the chart area. x, y, dx and dy MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0 - - Xnum is a 64-bit binary floating-point number as specified in [IEEE754]. This value MUST NOT be infinity, denormalized, not-a-number (NaN), nor negative zero. - - A SPRC is a unit of measurement that is 1/4000th of the height or width of the chart. If the field is being used to specify a width or horizontal distance, the SPRC is 1/4000th of the width of the chart. If the field is being used to specify a height or vertical distance, the SPRC is 1/4000th of the height of the chart. - + *

                + * frtHeader (12 bytes): An FrtHeader structure. The frtheader.rt field MUST be 0x08A7. + *

                + * dwCheckSum (4 bytes): An unsigned integer that specifies the checksum. MUST be a value from the following table: + * fManPlotArea field of ShtProps fAlwaysAutoPlotArea field of ShtProps dwCheckSum + * 0x0 0x0 0x00000001 + * 0x0 0x1 0x00000000 + * 0x1 0x0 0x00000000 + * 0x1 0x1 0x00000001 + *

                + * A - fLayoutTargetInner (1 bit): A bit that specifies the type of plot area for the layout target. + * Value Meaning + * 0x0 Outer plot area - The bounding rectangle that includes the axis labels, axis titles, data table (2) and plot area of the chart. + * 0x1 Inner plot area – The rectangle bounded by the chart axes. + *

                + * reserved1 (15 bits): MUST be zero, and MUST be ignored. + *

                + * xTL (2 bytes): A signed integer that specifies the horizontal offset of the plot area’s upper-left corner, relative to the upper-left corner of the chart area, in SPRC. + * yTL (2 bytes): A signed integer that specifies the vertical offset of the plot area’s upper-left corner, relative to the upper-left corner of the chart area, in SPRC. + * xBR (2 bytes): A signed integer that specifies the width of the plot area, in SPRC. + * yBR (2 bytes): A signed integer that specifies the height of the plot area, in SPRC. + * wXMode (2 bytes): A CrtLayout12Mode structure that specifies the meaning of x. + * wYMode (2 bytes): A CrtLayout12Mode structure that specifies the meaning of y. + * wWidthMode (2 bytes): A CrtLayout12Mode structure that specifies the meaning of dx. + * wHeightMode (2 bytes): A CrtLayout12Mode structure that specifies the meaning of dy. + * x (8 bytes): An Xnum value that specifies a horizontal offset. The meaning is determined by wXMode. + * y (8 bytes): An Xnum value that specifies a vertical offset. The meaning is determined by wYMode. + * dx (8 bytes): An Xnum value that specifies a width or a horizontal offset. The meaning is determined by wWidthMode. + * dy (8 bytes): An Xnum value that specifies a height or a vertical offset. The meaning is determined by wHeightMode. + * reserved2 (2 bytes): MUST be zero, and MUST be ignored. + *

                + * The CrtLayout12Mode record specifies a layout mode. Each layout mode specifies a different meaning of the x, y, dx, and dy fields of CrtLayout12 and CrtLayout12A. + * Name Value Meaning + * L12MAUTO 0x0000 Position and dimension (2) are determined by the application. x, y, dx and dy MUST be ignored. + * L12MFACTOR 0x0001 x and y specify the offset of the top left corner, relative to its default position, as a fraction of the chart area. MUST be greater than or equal to -1.0 and MUST be less than or equal to 1.0. dx and dy specify the width and height, as a fraction of the chart area, MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. + * L12MEDGE 0x0002 x and y specify the offset of the upper-left corner; dx and dy specify the offset of the bottom-right corner. x, y, dx and dy are specified relative to the upper-left corner of the chart area as a fraction of the chart area. x, y, dx and dy MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0 + *

                + * Xnum is a 64-bit binary floating-point number as specified in [IEEE754]. This value MUST NOT be infinity, denormalized, not-a-number (NaN), nor negative zero. + *

                + * A SPRC is a unit of measurement that is 1/4000th of the height or width of the chart. If the field is being used to specify a width or horizontal distance, the SPRC is 1/4000th of the width of the chart. If the field is being used to specify a height or vertical distance, the SPRC is 1/4000th of the height of the chart. */ + import io.starter.toolkit.ByteTools; public class CrtLayout12A extends GenericChartObject implements ChartObject { - byte fLayoutTargetInner; - short xTL, yTL; - short xBR, yBR; - short wXMode, wYMode; - short wWidthMode, wHeightMode; - float x, y, dx, dy; - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1868700214505277636L; - public void init() { - super.init(); - byte[] data= this.getData(); - fLayoutTargetInner= data[16]; - xTL= ByteTools.readShort(data[18], data[19]); - yTL= ByteTools.readShort(data[20], data[21]); - xBR= ByteTools.readShort(data[22], data[23]); - yBR= ByteTools.readShort(data[24], data[25]); - wXMode= ByteTools.readShort(data[26], data[27]); - wYMode= ByteTools.readShort(data[28], data[29]); - wWidthMode= ByteTools.readShort(data[30], data[31]); - wHeightMode= ByteTools.readShort(data[32], data[33]); - x= (float)ByteTools.eightBytetoLEDouble(this.getBytesAt(34, 8)); - y= (float)ByteTools.eightBytetoLEDouble(this.getBytesAt(42, 8)); - dx= (float)ByteTools.eightBytetoLEDouble(this.getBytesAt(50, 8)); - dy= (float)ByteTools.eightBytetoLEDouble(this.getBytesAt(58, 8)); - } - - /** - * If this CrtLayout contains information for the inner plot area coords - * and the coordinates are NOT determined by the application, - * calculate the plot area coordinates. - *
                If not possible, return null; - *
                [Inner plot area – The rectangle bounded by the chart axes] + byte fLayoutTargetInner; + short xTL, yTL; + short xBR, yBR; + short wXMode, wYMode; + short wWidthMode, wHeightMode; + float x, y, dx, dy; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1868700214505277636L; - * @param w chart width for SPRC calc - * @param h chart height for SPRC calc - * @return - */ - float[] getInnerPlotCoords(float w, float h) { + public void init() { + super.init(); + byte[] data = this.getData(); + fLayoutTargetInner = data[16]; + xTL = ByteTools.readShort(data[18], data[19]); + yTL = ByteTools.readShort(data[20], data[21]); + xBR = ByteTools.readShort(data[22], data[23]); + yBR = ByteTools.readShort(data[24], data[25]); + wXMode = ByteTools.readShort(data[26], data[27]); + wYMode = ByteTools.readShort(data[28], data[29]); + wWidthMode = ByteTools.readShort(data[30], data[31]); + wHeightMode = ByteTools.readShort(data[32], data[33]); + x = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(34, 8)); + y = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(42, 8)); + dx = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(50, 8)); + dy = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(58, 8)); + } + + /** + * If this CrtLayout contains information for the inner plot area coords + * and the coordinates are NOT determined by the application, + * calculate the plot area coordinates. + *
                If not possible, return null; + *
                [Inner plot area – The rectangle bounded by the chart axes] + * @param w chart width for SPRC calc + * @param h chart height for SPRC calc + * @return + */ + float[] getInnerPlotCoords(float w, float h) { /*L12MAUTO 0x0000 Position and dimension (2) are determined by the application. x, y, dx and dy MUST be ignored. L12MFACTOR 0x0001 x and y specify the offset of the top left corner, relative to its default position, as a fraction of the chart area. MUST be greater than or equal to -1.0 and MUST be less than or equal to 1.0. dx and dy specify the width and height, as a fraction of the chart area, MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. L12MEDGE 0x0002 x and y specify the offset of the upper-left corner; dx and dy specify the offset of the bottom-right corner. x, y, dx and dy are specified relative to the upper-left corner of the chart area as a fraction of the chart area. x, y, dx and dy MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0 */ - if (fLayoutTargetInner==0) - return null; - return new float[] { Pos.convertFromSPRC(xTL, w, 0), // x offset - Pos.convertFromSPRC(yTL, 0, h), // y offset - Pos.convertFromSPRC(xBR, w, 0), // w - Pos.convertFromSPRC(yBR, 0, h) }; // h - } - + if (fLayoutTargetInner == 0) + return null; + return new float[]{Pos.convertFromSPRC(xTL, w, 0), // x offset + Pos.convertFromSPRC(yTL, 0, h), // y offset + Pos.convertFromSPRC(xBR, w, 0), // w + Pos.convertFromSPRC(yBR, 0, h)}; // h + } + } diff --git a/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.java b/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.java index 19afdfa..d0db69d 100644 --- a/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,10 +26,13 @@ public class CylinderBarChart extends Bar3DChart { - public CylinderBarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape= SHAPECYLINDER; - chartobj.chartType= CYLINDERBARCHART; - } - public int getBarShape() { return defaultShape; } + public CylinderBarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + defaultShape = SHAPECYLINDER; + chartobj.chartType = CYLINDERBARCHART; + } + + public int getBarShape() { + return defaultShape; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/CylinderChart.java b/src/main/java/io/starter/formats/XLS/charts/CylinderChart.java index 7099651..0f9ed3b 100644 --- a/src/main/java/io/starter/formats/XLS/charts/CylinderChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/CylinderChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,10 +26,13 @@ public class CylinderChart extends Col3DChart { - public CylinderChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape= SHAPECYLINDER; - chartobj.chartType= CYLINDERCHART; - } - public int getBarShape() { return defaultShape; } + public CylinderChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + defaultShape = SHAPECYLINDER; + chartobj.chartType = CYLINDERCHART; + } + + public int getBarShape() { + return defaultShape; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Dat.java b/src/main/java/io/starter/formats/XLS/charts/Dat.java index b1ef15c..687b10f 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Dat.java +++ b/src/main/java/io/starter/formats/XLS/charts/Dat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,83 +22,86 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.BiffRec; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** - * * Dat: Data Table Options (0x1063) - * - Offset Name Size Contents - 4 grbit 2 Option flags (see following table) + *

                + * Offset Name Size Contents + * 4 grbit 2 Option flags (see following table) + *

                + *

                + * The grbit field contains the following flags. + *

                + * Offset Bits Mask Name Contents + * 0 0 01h fHasBordHorz 1 = data table has horizontal borders + * 1 02h fHasBordVert 1 = data table has vertical borders + * 2 04h fhasBordOutline 1 = data table has a border + * 3 08h fShowSeriesKey 1 = data table shows series keys + * 7-4 F0h reserved Reserved; must be zero + * 1 7-0 FFh reserved Reserved; must be zero + */ +public class Dat extends GenericChartObject implements ChartObject { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1138056714558134785L; + private short grbit; + boolean fHasBordHorz, fHasBordVert, fHasBordOutline, fShowSeriesKey; + + public void init() { + super.init(); + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + fHasBordHorz = ((grbit & 0x1) == 0x1); + fHasBordVert = ((grbit & 0x2) == 0x2); + fHasBordOutline = ((grbit & 0x4) == 0x4); + fShowSeriesKey = ((grbit & 0x8) == 0x8); + } - The grbit field contains the following flags. + private byte[] PROTOTYPE_BYTES = new byte[]{15, 0}; - Offset Bits Mask Name Contents - 0 0 01h fHasBordHorz 1 = data table has horizontal borders - 1 02h fHasBordVert 1 = data table has vertical borders - 2 04h fhasBordOutline 1 = data table has a border - 3 08h fShowSeriesKey 1 = data table shows series keys - 7-4 F0h reserved Reserved; must be zero - 1 7-0 FFh reserved Reserved; must be zero - - */ -public class Dat extends GenericChartObject implements ChartObject{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1138056714558134785L; - private short grbit; - boolean fHasBordHorz, fHasBordVert, fHasBordOutline, fShowSeriesKey; - public void init() { - super.init(); - grbit= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fHasBordHorz= ((grbit & 0x1)==0x1); - fHasBordVert= ((grbit & 0x2)==0x2); - fHasBordOutline= ((grbit & 0x4)==0x4); - fShowSeriesKey= ((grbit & 0x8)==0x8); - - } - private byte[] PROTOTYPE_BYTES = new byte[] {15, 0}; /** - * creates a new Dat record; if bCreateDataTable is true, + * creates a new Dat record; if bCreateDataTable is true, * will also add the associated records required to create * a Data Table + * * @param bCreateDataTable * @return */ public static XLSRecord getPrototype(boolean bCreateDataTable) { - Dat d= new Dat(); + Dat d = new Dat(); d.setOpcode(DAT); d.setData(d.PROTOTYPE_BYTES); d.init(); if (bCreateDataTable) { - Legend l= (Legend) Legend.getPrototype(); - l.setIsDataTable(true); - //l.setwType(Legend.NOT_DOCKED); - d.chartArr.add(l); + Legend l = (Legend) Legend.getPrototype(); + l.setIsDataTable(true); + //l.setwType(Legend.NOT_DOCKED); + d.chartArr.add(l); // add pos record - Pos p= (Pos) Pos.getPrototype(Pos.TYPE_DATATABLE); + Pos p = (Pos) Pos.getPrototype(Pos.TYPE_DATATABLE); // p.setWorkBook(book); l.addChartRecord(p); // TextDisp - TextDisp td= (TextDisp) TextDisp.getPrototype(); - l.addChartRecord(td); - // TextDisp sub-recs - p= (Pos) Pos.getPrototype(Pos.TYPE_TEXTDISP); + TextDisp td = (TextDisp) TextDisp.getPrototype(); + l.addChartRecord(td); + // TextDisp sub-recs + p = (Pos) Pos.getPrototype(Pos.TYPE_TEXTDISP); td.addChartRecord(p); - Fontx f= (Fontx) Fontx.getPrototype(); + Fontx f = (Fontx) Fontx.getPrototype(); // f.setWorkBook(book); // EVENTUALLY! - f.setIfnt(6); - td.addChartRecord(f); - Ai ai= (Ai) Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND); + f.setIfnt(6); + td.addChartRecord(f); + Ai ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND); // ai.setWorkBook(book); - td.addChartRecord(ai); + td.addChartRecord(ai); } return d; } + private void updateRecord() { byte[] b = ByteTools.shortToLEBytes(grbit); this.setData(b); diff --git a/src/main/java/io/starter/formats/XLS/charts/DataFormat.java b/src/main/java/io/starter/formats/XLS/charts/DataFormat.java index ea5278e..3d0cd6b 100644 --- a/src/main/java/io/starter/formats/XLS/charts/DataFormat.java +++ b/src/main/java/io/starter/formats/XLS/charts/DataFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,110 +24,112 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * DataFormat: Series and Data Point Numbers(0x1006) - * + *

                * 4 xi 2 the zero-based index of the data point within the series specified by yi. (FFFFh means entire series) - * 6 yi 2 the zero-based index of a Series record - * 8 iss 2 An unsigned integer that specifies properties of the data series, trendline or error bar, depending on the type of records in sequence of records: - - If does not contain a SerAuxTrend or SerAuxErrBar record, then this field specifies the plot order of the data series. - If the series order was changed, this field can be different from yi. MUST be less than or equal to the number of series in the chart. - MUST be unique among iss values for all instances of this record contained in the SERIESFORMAT rule that does not contain a SerAuxTrend or SerAuxErrBar record. - - If the SERIESFORMAT rule contains a SerAuxTrend record on the chart group, then this field specifies the trendline number for the series. - - If the SERIESFORMAT rule contains a SerAuxErrBar record on the chart group, then this field specifies a zero-based index into a Series record in the - collection of Series records in the current chart sheet substream for which the error bar applies to. - - * 10 grbit 2 flags (0?) ignored - - * - * ORDER OF SUB-RECS: - * [Chart3DBarShape] - * [LineFormat, AreaFormat, PieFormat] == lines, fill - * [SerFormat] == smoothed lines ... - * [GelFrame] - * [MarkerFormat] - * [AttachedLabel] == data labels - * [ShapeProps] - * [CtrlMltFrt] - * + * 6 yi 2 the zero-based index of a Series record + * 8 iss 2 An unsigned integer that specifies properties of the data series, trendline or error bar, depending on the type of records in sequence of records: + *

                + * If does not contain a SerAuxTrend or SerAuxErrBar record, then this field specifies the plot order of the data series. + * If the series order was changed, this field can be different from yi. MUST be less than or equal to the number of series in the chart. + * MUST be unique among iss values for all instances of this record contained in the SERIESFORMAT rule that does not contain a SerAuxTrend or SerAuxErrBar record. + *

                + * If the SERIESFORMAT rule contains a SerAuxTrend record on the chart group, then this field specifies the trendline number for the series. + *

                + * If the SERIESFORMAT rule contains a SerAuxErrBar record on the chart group, then this field specifies a zero-based index into a Series record in the + * collection of Series records in the current chart sheet substream for which the error bar applies to. + *

                + * 10 grbit 2 flags (0?) ignored + *

                + *

                + * ORDER OF SUB-RECS: + * [Chart3DBarShape] + * [LineFormat, AreaFormat, PieFormat] == lines, fill + * [SerFormat] == smoothed lines ... + * [GelFrame] + * [MarkerFormat] + * [AttachedLabel] == data labels + * [ShapeProps] + * [CtrlMltFrt] */ public class DataFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3526272512004348462L; - private short yi, xi; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -3526272512004348462L; + private short yi, xi; private short iss; - + public void init() { - super.init(); + super.init(); byte[] rkdata = this.getData(); - xi= ByteTools.readShort(rkdata[0],rkdata[1]); - yi = ByteTools.readShort(rkdata[2],rkdata[3]); - iss = (short)ByteTools.readUnsignedShort(rkdata[4],rkdata[5]); - } - - private byte[] PROTOTYPE_BYTES = new byte[] {-1, -1, 0, 0, 0, 0, 0, 0}; - - - public void initNew() { - this.setOpcode(DATAFORMAT); - this.setData(PROTOTYPE_BYTES); - this.init(); - Chart3DBarShape cs= new Chart3DBarShape(); - cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 - this.addChartRecord(cs); - } - - /** - * Create a new dataformat - * @param parentChart - * @return - */ - public static XLSRecord getPrototype() { - DataFormat df = new DataFormat(); - df.setOpcode(DATAFORMAT); - df.setData(df.PROTOTYPE_BYTES); - df.init(); - Chart3DBarShape cs= new Chart3DBarShape(); - cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 - df.addChartRecord(cs); - return df; - } - - public static XLSRecord getPrototypeWithFormatRecs(Chart parentChart) { - return getPrototypeWithFormatRecs(0, parentChart); - } - - - /** - * Create DataFormat Record that HOPEFULLY reflects the necessary associated recs - * @param type - * @return DataFormat Record - */ - public static XLSRecord getPrototypeWithFormatRecs(int seriesNumber, Chart parentChart) { - DataFormat df = new DataFormat(); - df.setOpcode(DATAFORMAT); - df.setData(df.PROTOTYPE_BYTES); - df.init(); - Chart3DBarShape cs= new Chart3DBarShape(); - cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 - df.addChartRecord(cs); - df.setSeriesNumber(seriesNumber); - LineFormat lf= (LineFormat) LineFormat.getPrototype(); - df.addChartRecord(lf); - AreaFormat af= (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(parentChart); - df.addChartRecord(af); - PieFormat pf= (PieFormat) PieFormat.getPrototype(); - pf.setParentChart(parentChart); - df.addChartRecord(pf); - MarkerFormat mf= (MarkerFormat) MarkerFormat.getPrototype(); - mf.setParentChart(parentChart); - df.addChartRecord(mf); + xi = ByteTools.readShort(rkdata[0], rkdata[1]); + yi = ByteTools.readShort(rkdata[2], rkdata[3]); + iss = (short) ByteTools.readUnsignedShort(rkdata[4], rkdata[5]); + } + + private byte[] PROTOTYPE_BYTES = new byte[]{-1, -1, 0, 0, 0, 0, 0, 0}; + + + public void initNew() { + this.setOpcode(DATAFORMAT); + this.setData(PROTOTYPE_BYTES); + this.init(); + Chart3DBarShape cs = new Chart3DBarShape(); + cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 + this.addChartRecord(cs); + } + + /** + * Create a new dataformat + * + * @param parentChart + * @return + */ + public static XLSRecord getPrototype() { + DataFormat df = new DataFormat(); + df.setOpcode(DATAFORMAT); + df.setData(df.PROTOTYPE_BYTES); + df.init(); + Chart3DBarShape cs = new Chart3DBarShape(); + cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 + df.addChartRecord(cs); + return df; + } + + public static XLSRecord getPrototypeWithFormatRecs(Chart parentChart) { + return getPrototypeWithFormatRecs(0, parentChart); + } + + + /** + * Create DataFormat Record that HOPEFULLY reflects the necessary associated recs + * + * @param type + * @return DataFormat Record + */ + public static XLSRecord getPrototypeWithFormatRecs(int seriesNumber, Chart parentChart) { + DataFormat df = new DataFormat(); + df.setOpcode(DATAFORMAT); + df.setData(df.PROTOTYPE_BYTES); + df.init(); + Chart3DBarShape cs = new Chart3DBarShape(); + cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 + df.addChartRecord(cs); + df.setSeriesNumber(seriesNumber); + LineFormat lf = (LineFormat) LineFormat.getPrototype(); + df.addChartRecord(lf); + AreaFormat af = (AreaFormat) AreaFormat.getPrototype(); + af.setParentChart(parentChart); + df.addChartRecord(af); + PieFormat pf = (PieFormat) PieFormat.getPrototype(); + pf.setParentChart(parentChart); + df.addChartRecord(pf); + MarkerFormat mf = (MarkerFormat) MarkerFormat.getPrototype(); + mf.setParentChart(parentChart); + df.addChartRecord(mf); /* pieformat: MUST not exist on chart group types other than ---> doesn't appear true (???) pie, @@ -137,424 +139,445 @@ public static XLSRecord getPrototypeWithFormatRecs(int seriesNumber, Chart paren MUST not exist if the chart group type is doughnut and the series is not the outermost series. MUST not exist on the data points on the secondary bar/pie of a bar of pie chart group. */ - return df; - } - - public void setPointNumber(int idx) { - xi = (short)idx; - byte[] rkdata = this.getData(); - byte[] num = ByteTools.shortToLEBytes((short)idx); - rkdata[0] = num[0]; - rkdata[1] = num[1]; - this.setData(rkdata); - } - /** - * Set the series index - * - */ - public void setSeriesIndex(int idx) { - yi = (short)idx; - byte[] rkdata = this.getData(); - byte[] num = ByteTools.shortToLEBytes((short)idx); - rkdata[2] = num[0]; - rkdata[3] = num[1]; - this.setData(rkdata); - } - - public void setSeriesNumber(int idx) { - iss = (short)idx; - byte[] rkdata = this.getData(); - byte[] num = ByteTools.shortToLEBytes((short)idx); - rkdata[4] = num[0]; - rkdata[5] = num[1]; - this.setData(rkdata); - } - - public short getSeriesIndex() { - return yi; - } - public short getSeriesNumber() { - return iss; - } - - public short getPointNumber() { - return xi; - } - - private AttachedLabel getAttachedLabelRec(boolean bCreate) { - AttachedLabel al = null; - al = (AttachedLabel) Chart.findRec(this.chartArr, AttachedLabel.class); - if (al == null && bCreate) { // basic options are handled via AttachedLabel rec - al = (AttachedLabel) AttachedLabel.getPrototype(); - int z= Chart.findRecPosition(this.chartArr, MarkerFormat.class); - if (z > 0) - chartArr.add(z+1, al); - else - this.addChartRecord(al); - } - return al; - } - - private AreaFormat getAreaFormatRec(boolean bCreate) { - AreaFormat af= (AreaFormat) Chart.findRec(this.chartArr, AreaFormat.class); - if (af==null) { - af= (AreaFormat) AreaFormat.getPrototype(0); - this.addChartRecord((LineFormat) LineFormat.getPrototype()); + return df; + } + + public void setPointNumber(int idx) { + xi = (short) idx; + byte[] rkdata = this.getData(); + byte[] num = ByteTools.shortToLEBytes((short) idx); + rkdata[0] = num[0]; + rkdata[1] = num[1]; + this.setData(rkdata); + } + + /** + * Set the series index + */ + public void setSeriesIndex(int idx) { + yi = (short) idx; + byte[] rkdata = this.getData(); + byte[] num = ByteTools.shortToLEBytes((short) idx); + rkdata[2] = num[0]; + rkdata[3] = num[1]; + this.setData(rkdata); + } + + public void setSeriesNumber(int idx) { + iss = (short) idx; + byte[] rkdata = this.getData(); + byte[] num = ByteTools.shortToLEBytes((short) idx); + rkdata[4] = num[0]; + rkdata[5] = num[1]; + this.setData(rkdata); + } + + public short getSeriesIndex() { + return yi; + } + + public short getSeriesNumber() { + return iss; + } + + public short getPointNumber() { + return xi; + } + + private AttachedLabel getAttachedLabelRec(boolean bCreate) { + AttachedLabel al = null; + al = (AttachedLabel) Chart.findRec(this.chartArr, AttachedLabel.class); + if (al == null && bCreate) { // basic options are handled via AttachedLabel rec + al = (AttachedLabel) AttachedLabel.getPrototype(); + int z = Chart.findRecPosition(this.chartArr, MarkerFormat.class); + if (z > 0) + chartArr.add(z + 1, al); + else + this.addChartRecord(al); + } + return al; + } + + private AreaFormat getAreaFormatRec(boolean bCreate) { + AreaFormat af = (AreaFormat) Chart.findRec(this.chartArr, AreaFormat.class); + if (af == null) { + af = (AreaFormat) AreaFormat.getPrototype(0); + this.addChartRecord(LineFormat.getPrototype()); this.addChartRecord(af); - this.addChartRecord((PieFormat) PieFormat.getPrototype()); - this.addChartRecord((MarkerFormat) MarkerFormat.getPrototype()); - } - return af; - } - - /** - * return XLSRecord data (2 bytes), which controls bar shape - */ - public short getShape() { - Chart3DBarShape cs= (Chart3DBarShape)Chart.findRec(this.chartArr, Chart3DBarShape.class); - return cs.getShape(); - } - /** - * set the shape bit of the associated XLSRecord - * @param shape - */ - public void setShape(int shape) { - Chart3DBarShape cs= (Chart3DBarShape)Chart.findRec(this.chartArr, Chart3DBarShape.class); - cs.setShape((short) shape); - } - /** - * set smooth lines setting (applicable for line, scatter charts) - * @param smooth - */ - public void setSmoothLines(boolean smooth) { - Serfmt sf= (Serfmt)Chart.findRec(this.chartArr, Serfmt.class); - if (sf==null) { - if (smooth) { - setHasLines(); - sf = (Serfmt) Serfmt.getPrototype(); - int i = Chart.findRecPosition(this.chartArr, PieFormat.class); - this.chartArr.add(i + 1, sf); - sf.setSmoothedLine(true); - } - } else { - sf.setSmoothedLine(smooth); - } - } - - /** - * returns true if this parent chart has smoothed lines (Line, Scatter, Radar charts) - * @return - */ - public boolean getSmoothedLines() { - Serfmt sf= (Serfmt)Chart.findRec(this.chartArr, Serfmt.class); - if (sf!=null) - return sf.getSmoothLine(); - return false; - } - - /** - * returns true if this parent chart has lines (Line, Scatter, Radar charts) - * @return - */ - public boolean getHasLines() { - LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); - if (l!=null) - return l.getLineStyle()!=LineFormat.NONE; - return false; - } - - /** - * sets this chart to have lines (line chart, radar, scatter ...) - */ - public void setHasLines() { - setHasLines(0); - } - - /** - * sets this chart to have lines (line chart, radar, scatter ...) of the specific line style - *
                Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - - */ - public void setHasLines(int lineStyle) { - LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); - if (l==null) { // these come as a group - assume none or only has Chart3DBarShape ... - int z= Chart.findRecPosition(chartArr, Chart3DBarShape.class) + 1; - LineFormat lf= (LineFormat) LineFormat.getPrototype(lineStyle, -1); - chartArr.add(z++, lf); - AreaFormat af= (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(parentChart); - chartArr.add(z++, af); - PieFormat pf= (PieFormat) PieFormat.getPrototype(); - pf.setParentChart(parentChart); - chartArr.add(z++, pf); - MarkerFormat mf= (MarkerFormat) MarkerFormat.getPrototype(); - mf.setParentChart(parentChart); - chartArr.add(z++, mf); - - } else { - l.setLineStyle(lineStyle); - } - } - - /** - * sets 3D Bubbles (Bubble Chart only) - * @param b true if has 3d Bubbles - */ - public void setHas3DBubbles(boolean b) { - Serfmt sf= (Serfmt)Chart.findRec(this.chartArr, Serfmt.class); - if (sf==null) { - if (b) { - sf = (Serfmt) Serfmt.getPrototype(); - int i = Chart.findRecPosition(this.chartArr, PieFormat.class); - sf.setParentChart(this.getParentChart()); - this.chartArr.add(i + 1, sf); - sf.setHas3dBubbles(true); - } - } else { - sf.setHas3dBubbles(b); - } - } - /** - * returns true if this paernt chart has 3D bubbles (Bubble chart only) - * @return - */ - public boolean getHas3DBubbles() { - Serfmt sf= (Serfmt)Chart.findRec(this.chartArr, Serfmt.class); - if (sf!=null) - return sf.get3DBubbles(); - return false; - } - - - /** - * return if data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. - */ - public boolean getHasShadow() { - Serfmt sf= (Serfmt)Chart.findRec(this.chartArr, Serfmt.class); - if (sf!=null) - return sf.hasShadow(); - return false; - } - /** - * data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. - * @param b - */ - public void setHasShadow(boolean b) { - Serfmt sf= (Serfmt)Chart.findRec(this.chartArr, Serfmt.class); - if (sf==null) { - if (b) { - sf = (Serfmt) Serfmt.getPrototype(); - int i = Chart.findRecPosition(this.chartArr, PieFormat.class); - sf.setParentChart(this.getParentChart()); - this.chartArr.add(i + 1, sf); - sf.setHasShadow(true); - } - } else { - sf.setHasShadow(b); - } - } - - /** - * percentage=distance of pie slice from center of pie as % - * @param p - */ - public void setPercentage(int p) { - PieFormat pf= (PieFormat) Chart.findRec(this.chartArr, PieFormat.class); - if (pf==null) { - setHasLines(LineFormat.NONE); - pf= (PieFormat) Chart.findRec(this.chartArr, PieFormat.class); - } - pf.setPercentage((short)p); - } - - /** - * return percentage=distance of pie slice from center of pie as % - */ - public int getPercentage() { - PieFormat pf= (PieFormat) Chart.findRec(this.chartArr, PieFormat.class); - if (pf!=null) - return pf.getPercentage(); - return 0; - - } - - /** - * sets the data labels to the desired type: - *

              • "ShowValueLabel" - *
              • "ShowValueAsPercent" - *
              • "ShowLabelAsPercent" - *
              • "ShowLabel" - *
              • "ShowSeriesName" - *
              • "ShowBubbleLabel" - * @param type - */ - public void setDataLabels(String type) { - AttachedLabel al = this.getAttachedLabelRec(true); - al.setType(type, "1"); - } - - /** - * returns true if has data labels - * @return - */ - public boolean getHasDataLabels() { - return (this.getAttachedLabelRec(false)!=null); - } - + this.addChartRecord(PieFormat.getPrototype()); + this.addChartRecord(MarkerFormat.getPrototype()); + } + return af; + } + + /** + * return XLSRecord data (2 bytes), which controls bar shape + */ + public short getShape() { + Chart3DBarShape cs = (Chart3DBarShape) Chart.findRec(this.chartArr, Chart3DBarShape.class); + return cs.getShape(); + } + + /** + * set the shape bit of the associated XLSRecord + * + * @param shape + */ + public void setShape(int shape) { + Chart3DBarShape cs = (Chart3DBarShape) Chart.findRec(this.chartArr, Chart3DBarShape.class); + cs.setShape((short) shape); + } + + /** + * set smooth lines setting (applicable for line, scatter charts) + * + * @param smooth + */ + public void setSmoothLines(boolean smooth) { + Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); + if (sf == null) { + if (smooth) { + setHasLines(); + sf = (Serfmt) Serfmt.getPrototype(); + int i = Chart.findRecPosition(this.chartArr, PieFormat.class); + this.chartArr.add(i + 1, sf); + sf.setSmoothedLine(true); + } + } else { + sf.setSmoothedLine(smooth); + } + } + + /** + * returns true if this parent chart has smoothed lines (Line, Scatter, Radar charts) + * + * @return + */ + public boolean getSmoothedLines() { + Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); + if (sf != null) + return sf.getSmoothLine(); + return false; + } + + /** + * returns true if this parent chart has lines (Line, Scatter, Radar charts) + * + * @return + */ + public boolean getHasLines() { + LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); + if (l != null) + return l.getLineStyle() != LineFormat.NONE; + return false; + } + + /** + * sets this chart to have lines (line chart, radar, scatter ...) + */ + public void setHasLines() { + setHasLines(0); + } + + /** + * sets this chart to have lines (line chart, radar, scatter ...) of the specific line style + *
                Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + */ + public void setHasLines(int lineStyle) { + LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); + if (l == null) { // these come as a group - assume none or only has Chart3DBarShape ... + int z = Chart.findRecPosition(chartArr, Chart3DBarShape.class) + 1; + LineFormat lf = (LineFormat) LineFormat.getPrototype(lineStyle, -1); + chartArr.add(z++, lf); + AreaFormat af = (AreaFormat) AreaFormat.getPrototype(); + af.setParentChart(parentChart); + chartArr.add(z++, af); + PieFormat pf = (PieFormat) PieFormat.getPrototype(); + pf.setParentChart(parentChart); + chartArr.add(z++, pf); + MarkerFormat mf = (MarkerFormat) MarkerFormat.getPrototype(); + mf.setParentChart(parentChart); + chartArr.add(z++, mf); + + } else { + l.setLineStyle(lineStyle); + } + } + + /** + * sets 3D Bubbles (Bubble Chart only) + * + * @param b true if has 3d Bubbles + */ + public void setHas3DBubbles(boolean b) { + Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); + if (sf == null) { + if (b) { + sf = (Serfmt) Serfmt.getPrototype(); + int i = Chart.findRecPosition(this.chartArr, PieFormat.class); + sf.setParentChart(this.getParentChart()); + this.chartArr.add(i + 1, sf); + sf.setHas3dBubbles(true); + } + } else { + sf.setHas3dBubbles(b); + } + } + + /** + * returns true if this paernt chart has 3D bubbles (Bubble chart only) + * + * @return + */ + public boolean getHas3DBubbles() { + Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); + if (sf != null) + return sf.get3DBubbles(); + return false; + } + + + /** + * return if data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. + */ + public boolean getHasShadow() { + Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); + if (sf != null) + return sf.hasShadow(); + return false; + } + + /** + * data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. + * + * @param b + */ + public void setHasShadow(boolean b) { + Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); + if (sf == null) { + if (b) { + sf = (Serfmt) Serfmt.getPrototype(); + int i = Chart.findRecPosition(this.chartArr, PieFormat.class); + sf.setParentChart(this.getParentChart()); + this.chartArr.add(i + 1, sf); + sf.setHasShadow(true); + } + } else { + sf.setHasShadow(b); + } + } + + /** + * percentage=distance of pie slice from center of pie as % + * + * @param p + */ + public void setPercentage(int p) { + PieFormat pf = (PieFormat) Chart.findRec(this.chartArr, PieFormat.class); + if (pf == null) { + setHasLines(LineFormat.NONE); + pf = (PieFormat) Chart.findRec(this.chartArr, PieFormat.class); + } + pf.setPercentage((short) p); + } + + /** + * return percentage=distance of pie slice from center of pie as % + */ + public int getPercentage() { + PieFormat pf = (PieFormat) Chart.findRec(this.chartArr, PieFormat.class); + if (pf != null) + return pf.getPercentage(); + return 0; + + } + + /** + * sets the data labels to the desired type: + *
              • "ShowValueLabel" + *
              • "ShowValueAsPercent" + *
              • "ShowLabelAsPercent" + *
              • "ShowLabel" + *
              • "ShowSeriesName" + *
              • "ShowBubbleLabel" + * + * @param type + */ + public void setDataLabels(String type) { + AttachedLabel al = this.getAttachedLabelRec(true); + al.setType(type, "1"); + } + + /** + * returns true if has data labels + * + * @return + */ + public boolean getHasDataLabels() { + return (this.getAttachedLabelRec(false) != null); + } + /** * return if has the specified data label - *
              • "ShowValueLabel" - *
              • "ShowValueAsPercent" - *
              • "ShowLabelAsPercent" - *
              • "ShowLabel" - *
              • "ShowSeriesName" - *
              • "ShowBubbleLabel" - * @param type String option - * @return string true or false - */ - public String getDataLabelType(String type) { - AttachedLabel al= this.getAttachedLabelRec(false); - if (al!=null) - return al.getType(type); - return null; - } + *
              • "ShowValueLabel" + *
              • "ShowValueAsPercent" + *
              • "ShowLabelAsPercent" + *
              • "ShowLabel" + *
              • "ShowSeriesName" + *
              • "ShowBubbleLabel" + * + * @param type String option + * @return string true or false + */ + public String getDataLabelType(String type) { + AttachedLabel al = this.getAttachedLabelRec(false); + if (al != null) + return al.getType(type); + return null; + } + /** * return a string of ALL the label options chosen. One or more of: -
              • Value -
              • ValuePerecentage -
              • CategoryPercentage // Pie only -
              • CategoryLabel -
              • BubbleLabel -
              • SeriesLabel - * @return string true or false - */ - public String getDataLabelType() { - AttachedLabel al= this.getAttachedLabelRec(false); - if (al!=null) - return al.getType(); - return null; - } - - /** - * return the data label int or 0 if no data labels chosen - * @return - */ - public int getDataLabelTypeInt() { - AttachedLabel al= this.getAttachedLabelRec(false); - if (al!=null) - return al.getTypeInt(); - return 0; - } - /** - sets the data labels for the entire chart (as opposed to a specific series/data point). - A combination of: -
              • SHOWVALUE= 0x1; -
              • SHOWVALUEPERCENT= 0x2; -
              • SHOWCATEGORYPERCENT= 0x4; -
              • SHOWCATEGORYLABEL= 0x10; -
              • SHOWBUBBLELABEL= 0x20; -
              • SHOWSERIESLABEL= 0x40; - */ - public void setHasDataLabels(int dl) { - AttachedLabel al= this.getAttachedLabelRec(true); - al.setType((short) dl); - } - - /** - * returns type of marker, if any
                - * 0 = no marker
                - * 1 = square
                - * 2 = diamond
                - * 3 = triangle
                - * 4 = X
                - * 5 = star
                - * 6 = Dow-Jones
                - * 7 = standard deviation
                - * 8 = circle
                - * 9 = plus sign - * - * @return - */ - public int getMarkerFormat() { - // default actually looks like: 2, 1, 5, 4 ... - MarkerFormat mf = (MarkerFormat) Chart.findRec(this.chartArr, MarkerFormat.class); - if (mf!=null) - return mf.getMarkerFormat(); - - return 0; - } + *
              • Value + *
              • ValuePerecentage + *
              • CategoryPercentage // Pie only + *
              • CategoryLabel + *
              • BubbleLabel + *
              • SeriesLabel + * + * @return string true or false + */ + public String getDataLabelType() { + AttachedLabel al = this.getAttachedLabelRec(false); + if (al != null) + return al.getType(); + return null; + } - /** - * 0 = no marker
                - * 1 = square
                - * 2 = diamond
                - * 3 = triangle
                - * 4 = X
                - * 5 = star
                - * 6 = Dow-Jones
                - * 7 = standard deviation
                - * 8 = circle
                - * 9 = plus sign - */ - public void setMarkerFormat(int marker) { - MarkerFormat mf = (MarkerFormat) Chart.findRec(this.chartArr, MarkerFormat.class); - if (mf==null) { - if (chartArr.isEmpty()) { // these records come in a set - Chart3DBarShape cs= new Chart3DBarShape(); - cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 - this.addChartRecord(cs); - LineFormat lf= (LineFormat) LineFormat.getPrototype(); - lf.setParentChart(this.parentChart); - lf.setLineStyle(5); - chartArr.add(lf); - AreaFormat af= (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(this.parentChart); - chartArr.add(af); - PieFormat pf= (PieFormat) PieFormat.getPrototype(); - pf.setParentChart(this.parentChart); - chartArr.add(pf); - mf= (MarkerFormat) MarkerFormat.getPrototype(); - mf.setParentChart(this.parentChart); - chartArr.add(mf); - } else { // shouldn't get here but it goes - mf= (MarkerFormat) MarkerFormat.getPrototype(); - mf.setParentChart(this.parentChart); - int z= Chart.findRecPosition(chartArr, PieFormat.class); - if (z>-1) - chartArr.add(z+1, mf); - else // dunno, add to end - chartArr.add(mf); - } - } - mf.setMarkerFormat((short)marker); - } + /** + * return the data label int or 0 if no data labels chosen + * + * @return + */ + public int getDataLabelTypeInt() { + AttachedLabel al = this.getAttachedLabelRec(false); + if (al != null) + return al.getTypeInt(); + return 0; + } + + /** + * sets the data labels for the entire chart (as opposed to a specific series/data point). + * A combination of: + *
              • SHOWVALUE= 0x1; + *
              • SHOWVALUEPERCENT= 0x2; + *
              • SHOWCATEGORYPERCENT= 0x4; + *
              • SHOWCATEGORYLABEL= 0x10; + *
              • SHOWBUBBLELABEL= 0x20; + *
              • SHOWSERIESLABEL= 0x40; + */ + public void setHasDataLabels(int dl) { + AttachedLabel al = this.getAttachedLabelRec(true); + al.setType((short) dl); + } + + /** + * returns type of marker, if any
                + * 0 = no marker
                + * 1 = square
                + * 2 = diamond
                + * 3 = triangle
                + * 4 = X
                + * 5 = star
                + * 6 = Dow-Jones
                + * 7 = standard deviation
                + * 8 = circle
                + * 9 = plus sign + * + * @return + */ + public int getMarkerFormat() { + // default actually looks like: 2, 1, 5, 4 ... + MarkerFormat mf = (MarkerFormat) Chart.findRec(this.chartArr, MarkerFormat.class); + if (mf != null) + return mf.getMarkerFormat(); + + return 0; + } + + /** + * 0 = no marker
                + * 1 = square
                + * 2 = diamond
                + * 3 = triangle
                + * 4 = X
                + * 5 = star
                + * 6 = Dow-Jones
                + * 7 = standard deviation
                + * 8 = circle
                + * 9 = plus sign + */ + public void setMarkerFormat(int marker) { + MarkerFormat mf = (MarkerFormat) Chart.findRec(this.chartArr, MarkerFormat.class); + if (mf == null) { + if (chartArr.isEmpty()) { // these records come in a set + Chart3DBarShape cs = new Chart3DBarShape(); + cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 + this.addChartRecord(cs); + LineFormat lf = (LineFormat) LineFormat.getPrototype(); + lf.setParentChart(this.parentChart); + lf.setLineStyle(5); + chartArr.add(lf); + AreaFormat af = (AreaFormat) AreaFormat.getPrototype(); + af.setParentChart(this.parentChart); + chartArr.add(af); + PieFormat pf = (PieFormat) PieFormat.getPrototype(); + pf.setParentChart(this.parentChart); + chartArr.add(pf); + mf = (MarkerFormat) MarkerFormat.getPrototype(); + mf.setParentChart(this.parentChart); + chartArr.add(mf); + } else { // shouldn't get here but it goes + mf = (MarkerFormat) MarkerFormat.getPrototype(); + mf.setParentChart(this.parentChart); + int z = Chart.findRecPosition(chartArr, PieFormat.class); + if (z > -1) + chartArr.add(z + 1, mf); + else // dunno, add to end + chartArr.add(mf); + } + } + mf.setMarkerFormat((short) marker); + } // 0x893 CtlMltFrt - // [-98, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 13, 19, 0, 6, -128, 34, 0, 35, 0, -67, 64, 0, 0] - - /** - * sets the color identified by this DataFormat in the group of records - * belonging to the parent series - * @param clr color int - */ - public void setSeriesColor(int clr) { - AreaFormat af= getAreaFormatRec(true); - // Finally got the AreaFormat record that governs the color for this series - af.seticvFore(clr); - } - - /** - * sets the color identified by this DataFormat in the group of records - * belonging to the parent series - * @param clr Hex color string - */ - public void setSeriesColor(String clr) { - AreaFormat af= getAreaFormatRec(true); - // Finally got the AreaFormat record that governs the color for this series - af.seticvFore(clr); - } + // [-98, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 13, 19, 0, 6, -128, 34, 0, 35, 0, -67, 64, 0, 0] + + /** + * sets the color identified by this DataFormat in the group of records + * belonging to the parent series + * + * @param clr color int + */ + public void setSeriesColor(int clr) { + AreaFormat af = getAreaFormatRec(true); + // Finally got the AreaFormat record that governs the color for this series + af.seticvFore(clr); + } + + /** + * sets the color identified by this DataFormat in the group of records + * belonging to the parent series + * + * @param clr Hex color string + */ + public void setSeriesColor(String clr) { + AreaFormat af = getAreaFormatRec(true); + // Finally got the AreaFormat record that governs the color for this series + af.seticvFore(clr); + } /* @@ -574,45 +597,49 @@ public int[] getDataLabelsPIE(int defaultdl) { } return dls; } -*/ - - /** - * get the bg color identified by this DataFormat - *
                Usually part of a series group of records - * @return - */ - public String getBgColor() { - String bg= Frame.getBgColor(this.chartArr); - return bg; - } +*/ + + /** + * get the bg color identified by this DataFormat + *
                Usually part of a series group of records + * + * @return + */ + public String getBgColor() { + String bg = Frame.getBgColor(this.chartArr); + return bg; + } /** - * sets the color of the desired pie slice - * @param clr color int - * @param slice 0-based pie slice number + * sets the color of the desired pie slice + * + * @param clr color int + * @param slice 0-based pie slice number */ public void setPieSliceColor(String clr, int slice) { - AreaFormat af= getAreaFormatPie(slice); - af.seticvFore(clr); + AreaFormat af = getAreaFormatPie(slice); + af.seticvFore(clr); } - + /** - * sets the color of the desired pie slice - * @param clr color int - * @param slice 0-based pie slice number + * sets the color of the desired pie slice + * + * @param clr color int + * @param slice 0-based pie slice number */ public void setPieSliceColor(int clr, int slice) { - AreaFormat af= getAreaFormatPie(slice); - af.seticvFore(clr); + AreaFormat af = getAreaFormatPie(slice); + af.seticvFore(clr); } - + /** * returns (creates if necessary) the area format for the desired pie slice (pie charts only) + * * @param slice int 0-based slice nmber - * @return AreaFormat record + * @return AreaFormat record */ private AreaFormat getAreaFormatPie(int slice) { - // must add x number of dataformat recs + // must add x number of dataformat recs // FINISH- not 100% /* if (i==s.chartArr.size()) { df= (DataFormat) DataFormat.getPrototypeWithFormatRecs(0); @@ -624,26 +651,29 @@ private AreaFormat getAreaFormatPie(int slice) { df.addChartRecord(al); s.chartArr.add(s.chartArr.size()-1, df); // -1 to skip SERTOCRT rec } -*/ AreaFormat af= (AreaFormat) Chart.findRec(this.chartArr, AreaFormat.class); - return af; +*/ + AreaFormat af = (AreaFormat) Chart.findRec(this.chartArr, AreaFormat.class); + return af; } + /** * returns (creates if necessary) the area format for this series- controls bar/series colors - * @return AreaFormat record + * + * @return AreaFormat record */ private AreaFormat getAreaFormat() { - AreaFormat af= (AreaFormat) Chart.findRec(this.chartArr, AreaFormat.class); - if (af==null) { - af= (AreaFormat) AreaFormat.getPrototype(0); - // NOTE: below list of records is what has been observed in Excel 2003 chart files - - // unsure if need marker format always ? - this.addChartRecord((LineFormat) LineFormat.getPrototype()); + AreaFormat af = (AreaFormat) Chart.findRec(this.chartArr, AreaFormat.class); + if (af == null) { + af = (AreaFormat) AreaFormat.getPrototype(0); + // NOTE: below list of records is what has been observed in Excel 2003 chart files - + // unsure if need marker format always ? + this.addChartRecord(LineFormat.getPrototype()); this.addChartRecord(af); - this.addChartRecord((PieFormat) PieFormat.getPrototype()); - this.addChartRecord((MarkerFormat) MarkerFormat.getPrototype()); - } - return af; + this.addChartRecord(PieFormat.getPrototype()); + this.addChartRecord(MarkerFormat.getPrototype()); + } + return af; } - - + + } diff --git a/src/main/java/io/starter/formats/XLS/charts/DataLabExt.java b/src/main/java/io/starter/formats/XLS/charts/DataLabExt.java index 079a299..33c5472 100644 --- a/src/main/java/io/starter/formats/XLS/charts/DataLabExt.java +++ b/src/main/java/io/starter/formats/XLS/charts/DataLabExt.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,35 +23,40 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; + /** - DATALABEXT: Chart Data Label Extension (86Ah) - Introduced in Excel 10 (2002) this BIFF record is an FRT record - for Charts. This record is the parent of DATALABEXTCONTENTS, but - contains no other information. - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =086Ah - 6 grbitFrt 2 FRT flags; must be zero - 8 (unused) 8 Reserved; must be zero -*/ + * DATALABEXT: Chart Data Label Extension (86Ah) + * Introduced in Excel 10 (2002) this BIFF record is an FRT record + * for Charts. This record is the parent of DATALABEXTCONTENTS, but + * contains no other information. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =086Ah + * 6 grbitFrt 2 FRT flags; must be zero + * 8 (unused) 8 Reserved; must be zero + */ public class DataLabExt extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1868700214505277636L; - public void init() { - super.init(); - } - private byte[] PROTOTYPE_BYTES = new byte[] {106, 8,0,0,0,0,0,0,0,0,0,0}; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1868700214505277636L; + + public void init() { + super.init(); + } + + private byte[] PROTOTYPE_BYTES = new byte[]{106, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + public static XLSRecord getPrototype() { - DataLabExt dl= new DataLabExt(); - dl.setOpcode(DATALABEXTCONTENTS); - dl.setData(dl.PROTOTYPE_BYTES); - dl.init(); + DataLabExt dl = new DataLabExt(); + dl.setOpcode(DATALABEXTCONTENTS); + dl.setData(dl.PROTOTYPE_BYTES); + dl.init(); return dl; } + private void updateRecord() { } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.java b/src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.java index 134702a..98c37f8 100644 --- a/src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.java +++ b/src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,43 +24,45 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** - The DataLabExtContents record specifies the contents of an extended data label. - 12 bytes- FRTHEADER - - A - fSerName (1 bit): A bit that specifies whether the name of the series is displayed in the extended data label. - B - fCatName (1 bit): A bit that specifies whether the category (3) name, or the horizontal value on bubble or scatter chart groups, is displayed in the extended data label. MUST be a value from the following table: - 0 Neither of the data values are displayed in the extended data label. - 1 If bubble or scatter chart group, the horizontal value is displayed in the extended data label. Otherwise, the category (3) name is displayed in the extended data label. - C - fValue (1 bit): A bit that specifies whether the data value, or the vertical value on bubble or scatter chart groups, is displayed in the extended data label. MUST be a value from the following table: - 0 Neither of the data values are displayed in the data label. - 1 If bubble or scatter chart group, the vertical value is displayed in the extended data label. Otherwise, the data value is displayed in the extended data label. - D - fPercent (1 bit): A bit that specifies whether the value of the corresponding data point, represented as a percentage of the sum of the values of the series the data label is associated with, is displayed in the extended data label. - MUST equal 0 if the chart group type of the corresponding chart group, series, or data point is not a bar of pie, doughnut, pie, or pie of pie chart group. - E - fBubSizes (1 bit): A bit that specifies whether the bubble size is displayed in the data label. - MUST equal 0 if the chart group type of the corresponding chart group, series, or data point is not a bubble chart group. - reserved (11 bits): MUST be zero, and MUST be ignored. - rgchSep (variable): A case-sensitive XLUnicodeStringMin2 structure that specifies the string that is inserted between every data value to form the extended data label. - For example, if fCatName and fValue are set to 1, the labels will look like "Category NameData Value". - The length of the string is contained in the cch field of the XLUnicodeStringMin2 structure. -*/ + * The DataLabExtContents record specifies the contents of an extended data label. + * 12 bytes- FRTHEADER + *

                + * A - fSerName (1 bit): A bit that specifies whether the name of the series is displayed in the extended data label. + * B - fCatName (1 bit): A bit that specifies whether the category (3) name, or the horizontal value on bubble or scatter chart groups, is displayed in the extended data label. MUST be a value from the following table: + * 0 Neither of the data values are displayed in the extended data label. + * 1 If bubble or scatter chart group, the horizontal value is displayed in the extended data label. Otherwise, the category (3) name is displayed in the extended data label. + * C - fValue (1 bit): A bit that specifies whether the data value, or the vertical value on bubble or scatter chart groups, is displayed in the extended data label. MUST be a value from the following table: + * 0 Neither of the data values are displayed in the data label. + * 1 If bubble or scatter chart group, the vertical value is displayed in the extended data label. Otherwise, the data value is displayed in the extended data label. + * D - fPercent (1 bit): A bit that specifies whether the value of the corresponding data point, represented as a percentage of the sum of the values of the series the data label is associated with, is displayed in the extended data label. + * MUST equal 0 if the chart group type of the corresponding chart group, series, or data point is not a bar of pie, doughnut, pie, or pie of pie chart group. + * E - fBubSizes (1 bit): A bit that specifies whether the bubble size is displayed in the data label. + * MUST equal 0 if the chart group type of the corresponding chart group, series, or data point is not a bubble chart group. + * reserved (11 bits): MUST be zero, and MUST be ignored. + * rgchSep (variable): A case-sensitive XLUnicodeStringMin2 structure that specifies the string that is inserted between every data value to form the extended data label. + * For example, if fCatName and fValue are set to 1, the labels will look like "Category NameData Value". + * The length of the string is contained in the cch field of the XLUnicodeStringMin2 structure. + */ public class DataLabExtContents extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1228364285066204304L; - private short grbit; - private boolean fSerName, fCatName, fValue, fPercent, fBubSizes; - public void init() { - super.init(); - byte[] data= this.getData(); - grbit=ByteTools.readShort(data[12], data[13]); - fSerName= (grbit & 0x1)==0x1; - fCatName= (grbit & 0x2)==0x2; - fValue= (grbit & 0x4)==0x4; - fPercent= (grbit & 0x8)==0x8; - fBubSizes= (grbit & 0x10)==0x10; - if (data.length>14) { // seperator value + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1228364285066204304L; + private short grbit; + private boolean fSerName, fCatName, fValue, fPercent, fBubSizes; + + public void init() { + super.init(); + byte[] data = this.getData(); + grbit = ByteTools.readShort(data[12], data[13]); + fSerName = (grbit & 0x1) == 0x1; + fCatName = (grbit & 0x2) == 0x2; + fValue = (grbit & 0x4) == 0x4; + fPercent = (grbit & 0x8) == 0x8; + fBubSizes = (grbit & 0x10) == 0x10; + if (data.length > 14) { // seperator value /* not following documentation: TODO: figure out int cch= data[14]; byte[] sepbytes= this.getBytesAt(16, data.length-16); @@ -69,42 +71,46 @@ public void init() { } catch(Exception e) { } -*/ - } - - - } - private byte[] PROTOTYPE_BYTES = new byte[] {107, 8, 0,0,0,0,0,0,0,0,0,0,1,0,0,0}; +*/ + } + + + } + + private byte[] PROTOTYPE_BYTES = new byte[]{107, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}; + public static XLSRecord getPrototype() { - DataLabExtContents dlc= new DataLabExtContents(); - dlc.setOpcode(DATALABEXTCONTENTS); - dlc.setData(dlc.PROTOTYPE_BYTES); - dlc.init(); + DataLabExtContents dlc = new DataLabExtContents(); + dlc.setOpcode(DATALABEXTCONTENTS); + dlc.setData(dlc.PROTOTYPE_BYTES); + dlc.init(); return dlc; } -/** - * return extended data label options as an int -
                SHOWVALUE= 0x1; -
                SHOWVALUEPERCENT= 0x2; -
                SHOWCATEGORYPERCENT= 0x4; -
                SMOOTHEDLINE= 0x8; -
                SHOWCATEGORYLABEL= 0x10; -
                SHOWBUBBLELABEL= 0x20; -
                SHOWSERIESLABEL= 0x40; - * @return a combination of data label options above or 0 if none - */ + + /** + * return extended data label options as an int + *
                SHOWVALUE= 0x1; + *
                SHOWVALUEPERCENT= 0x2; + *
                SHOWCATEGORYPERCENT= 0x4; + *
                SMOOTHEDLINE= 0x8; + *
                SHOWCATEGORYLABEL= 0x10; + *
                SHOWBUBBLELABEL= 0x20; + *
                SHOWSERIESLABEL= 0x40; + * + * @return a combination of data label options above or 0 if none + */ public int getTypeInt() { - short grbit= 0; - if (fValue) - grbit= ByteTools.updateGrBit(grbit, true, 0); - if (fPercent) - grbit= ByteTools.updateGrBit(grbit, true, 1); - if (fCatName) - grbit= ByteTools.updateGrBit(grbit, true, 4); - if (fBubSizes) - grbit= ByteTools.updateGrBit(grbit, true, 5); - if (fSerName) - grbit= ByteTools.updateGrBit(grbit, true, 6); - return grbit; + short grbit = 0; + if (fValue) + grbit = ByteTools.updateGrBit(grbit, true, 0); + if (fPercent) + grbit = ByteTools.updateGrBit(grbit, true, 1); + if (fCatName) + grbit = ByteTools.updateGrBit(grbit, true, 4); + if (fBubSizes) + grbit = ByteTools.updateGrBit(grbit, true, 5); + if (fSerName) + grbit = ByteTools.updateGrBit(grbit, true, 6); + return grbit; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/DefaultText.java b/src/main/java/io/starter/formats/XLS/charts/DefaultText.java index 45780e7..a716ae4 100644 --- a/src/main/java/io/starter/formats/XLS/charts/DefaultText.java +++ b/src/main/java/io/starter/formats/XLS/charts/DefaultText.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,30 +25,32 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * DefaultText: Default Data Label Text Properties(0x1024) - * - id (2 bytes): An unsigned integer that specifies the text elements that are formatted using the position and appearance information specified by the Text record immediately following this record. MUST be a value from the following table. - If this record is in a sequence of records that conforms to the CRT rule as specified by the Chart Sheet Substream ABNF, then this field MUST be 0x0000 or 0x0001. If this record is not in a sequence of records that conforms to the CRT rule as specified by the Chart Sheet Substream ABNF, then this field MUST be 0x0002 or 0x0003. - Value Meaning - 0x0000 Format all Text records in the chart group where fShowPercent is equal to 0 or fShowValue is equal to 0. - 0x0001 Format all Text records in the chart group where fShowPercent is equal to 1 or fShowValue is equal to 1. - 0x0002 Format all Text records in the chart where the value of fScaled of the associated FontInfo structure is equal to 0. - 0x0003 Format all Text records in the chart where the value of fScaled of the associated FontInfo structure is equal to 1. * + *

                + * id (2 bytes): An unsigned integer that specifies the text elements that are formatted using the position and appearance information specified by the Text record immediately following this record. MUST be a value from the following table. + * If this record is in a sequence of records that conforms to the CRT rule as specified by the Chart Sheet Substream ABNF, then this field MUST be 0x0000 or 0x0001. If this record is not in a sequence of records that conforms to the CRT rule as specified by the Chart Sheet Substream ABNF, then this field MUST be 0x0002 or 0x0003. + * Value Meaning + * 0x0000 Format all Text records in the chart group where fShowPercent is equal to 0 or fShowValue is equal to 0. + * 0x0001 Format all Text records in the chart group where fShowPercent is equal to 1 or fShowValue is equal to 1. + * 0x0002 Format all Text records in the chart where the value of fScaled of the associated FontInfo structure is equal to 0. + * 0x0003 Format all Text records in the chart where the value of fScaled of the associated FontInfo structure is equal to 1. * */ public class DefaultText extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6845131434077760152L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 6845131434077760152L; + + private short grbit = 0; - private short grbit= 0; - public void init() { - super.init(); + public void init() { + super.init(); grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } + } - // 20070716 KSC: Need to create new records + // 20070716 KSC: Need to create new records public static XLSRecord getPrototype() { DefaultText t = new DefaultText(); t.setOpcode(DEFAULTTEXT); @@ -56,19 +58,24 @@ public static XLSRecord getPrototype() { t.init(); return t; } - private byte[] PROTOTYPE_BYTES = new byte[] {0,0}; - - public static final int TYPE_SHOWLABELS= 0; - public static final int TYPE_VALUELABELS= 1; - public static final int TYPE_ALLTEXT= 2; - public static final int TYPE_UNKNOWN= 3; + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; + + public static final int TYPE_SHOWLABELS = 0; + public static final int TYPE_VALUELABELS = 1; + public static final int TYPE_ALLTEXT = 2; + public static final int TYPE_UNKNOWN = 3; + // try to interpret!!! - public int getType() { return grbit; } + public int getType() { + return grbit; + } + public void setType(short type) { - grbit= type; + grbit = type; byte[] b = ByteTools.shortToLEBytes(grbit); this.getData()[0] = b[0]; this.getData()[1] = b[1]; } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.java b/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.java index e239fed..d804645 100644 --- a/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,219 +22,228 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.ChartSeriesHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; public class DoughnutChart extends ChartType { - private Pie doughnut= null; - public DoughnutChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - doughnut= (Pie) charttype; - } - /** return the JSON that - * + private Pie doughnut = null; + + public DoughnutChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + doughnut = (Pie) charttype; + } + + /** + * return the JSON that + * * @param seriesvals * @param range * @return */ - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) - throws JSONException { - JSONObject chartObjectJSON= new JSONObject(); - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // TODO: check out labels options chosen: default label is Category name + percentage ... - double yMax= 0.0, yMin= 0.0; - double len= 0.0; - JSONArray pieSeries= new JSONArray(); - try { - String range= series[0].getCategoryRange(); // 20080516 KSC: retrieve cat range instead of parameter - JSONArray cats= CellRange.getValuesAsJSON(range, wbh); // parse category range into JSON Array - JSONArray seriesvals= CellRange.getValuesAsJSON(series[0].getSeriesRange(), wbh); - double piesum= 0; - for (int k= 0; k < seriesvals.length(); k++) { - piesum+=seriesvals.getDouble(k); - yMax= Math.max(yMax, seriesvals.getDouble(k)); - yMin= Math.min(yMin, seriesvals.getDouble(k)); - } - double percent= 100/piesum; - for (int k= 0; k < seriesvals.length(); k++) { - JSONObject piepoint= new JSONObject(); - piepoint.put("y", seriesvals.getDouble(k)); - piepoint.put("text", cats.getString(k) + "\n" + Math.round(percent*seriesvals.getDouble(k)) + "%"); - piepoint.put("color", FormatConstants.SVGCOLORSTRINGS[series[k].getPieChartSliceColor(k)]); - piepoint.put("stroke", getDarkColor()); - pieSeries.put(piepoint); - } - len= seriesvals.length(); - } catch (Exception e) { - // TODO: warning ...? - } - // 20090717 KSC: input outside of try/catch to always set - minMax[0]= new Double(yMin); - minMax[1]= new Double(yMax); - minMax[2]= new Double(len); - chartObjectJSON.put("Series", pieSeries); - chartObjectJSON.put("SeriesFills", ""); // not applicable for pie charts; color is set above - return chartObjectJSON; + public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) + throws JSONException { + JSONObject chartObjectJSON = new JSONObject(); + // Type JSON + chartObjectJSON.put("type", this.getTypeJSON()); + + // TODO: check out labels options chosen: default label is Category name + percentage ... + double yMax = 0.0, yMin = 0.0; + double len = 0.0; + JSONArray pieSeries = new JSONArray(); + try { + String range = series[0].getCategoryRange(); // 20080516 KSC: retrieve cat range instead of parameter + JSONArray cats = CellRange.getValuesAsJSON(range, wbh); // parse category range into JSON Array + JSONArray seriesvals = CellRange.getValuesAsJSON(series[0].getSeriesRange(), wbh); + double piesum = 0; + for (int k = 0; k < seriesvals.length(); k++) { + piesum += seriesvals.getDouble(k); + yMax = Math.max(yMax, seriesvals.getDouble(k)); + yMin = Math.min(yMin, seriesvals.getDouble(k)); + } + double percent = 100 / piesum; + for (int k = 0; k < seriesvals.length(); k++) { + JSONObject piepoint = new JSONObject(); + piepoint.put("y", seriesvals.getDouble(k)); + piepoint.put("text", cats.getString(k) + "\n" + Math.round(percent * seriesvals.getDouble(k)) + "%"); + piepoint.put("color", FormatConstants.SVGCOLORSTRINGS[series[k].getPieChartSliceColor(k)]); + piepoint.put("stroke", getDarkColor()); + pieSeries.put(piepoint); + } + len = seriesvals.length(); + } catch (Exception e) { + // TODO: warning ...? + } + // 20090717 KSC: input outside of try/catch to always set + minMax[0] = new Double(yMin); + minMax[1] = new Double(yMax); + minMax[2] = new Double(len); + chartObjectJSON.put("Series", pieSeries); + chartObjectJSON.put("SeriesFills", ""); // not applicable for pie charts; color is set above + return chartObjectJSON; + } + + /** + * return the type JSON for this Chart Object + * + * @return + */ + public JSONObject getTypeJSON() throws JSONException { + JSONObject typeJSON = new JSONObject(); + typeJSON.put("labelOffset", -25); // default + typeJSON.put("precision", 0); // default - rounds percentages up + typeJSON.put("type", "Pie"); + // TODO: Interpret distance ... + return typeJSON; } - /** - * return the type JSON for this Chart Object - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON= new JSONObject(); - typeJSON.put("labelOffset", -25); // default - typeJSON.put("precision", 0); // default - rounds percentages up - typeJSON.put("type", "Pie"); - // TODO: Interpret distance ... - return typeJSON; - } - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - StringBuffer svg= new StringBuffer(); - if (series.size()==0) { - Logger.logErr("DoughnutChart.getSVG: error in series"); - return ""; - } - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - boolean threeD= this.isThreeD(); - final int LABELOFFSET= 15; - - int n= series.size(); - double centerx= 0.0, centery= 0.0, radius= 0.0; - double radiusy= 0.0; - centerx= w/2 + chartMetrics.get("x"); - centery= h/2 + chartMetrics.get("y"); - if (threeD) { - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - } - svg.append("\r\n"); - - //radius= Math.min(w, h)/2.3; // should take up almost entire w/h of chart - radius= Math.min(w, h)/1.9; // should take up almost entire w/h of chart - double r0= (radius/2)/n; // radius/2 to account for hole - double r= radius; // start at outside and work inside - for (int i= n-1; i >= 0; i--) { // for each series - double[] curseries= (double[])series.get(i); // series data points - double total= 0.0; - for (int j= 0; j < curseries.length; j++) { // get total in order to calculate percentages - total+= curseries[j]; - } - if (dls.length==1) { // no seriess-specific data labels; expand to entire series for loop below - int dl= dls[0]; - dls= new int[curseries.length]; - for (int z= 0; z < dls.length; z++) - dls[z]= dl; - } - svg.append("\r\n"); - double x= centerx+r; - double y= centery; - String path= ""; - double percentage= 0; - double lasta= 0; - int largearcflag= 0; - int sweepflag= 0; - // Now create each pie wedge according to it's percentage value - for (int j= 0; j < curseries.length; j++) { - percentage= curseries[j]/total; - double angle= (percentage*360) + lasta; - double x1=centerx + r*(Math.cos(Math.toRadians(angle))); - double y1=centery - r*(Math.sin(Math.toRadians(angle))); - if ((percentage*360)> 180) { - sweepflag= 0; - largearcflag= 1; - } else - largearcflag= 0; - path= "M" + centerx + " " + centery + " L" + x + " " + y + " A" + r + " " + r + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z"; - // paint wedge of color to center of chart -- the inner will overwrite so don't have to worry about segments and arcs, etc - svg.append("\r\n"); - // data labels - String l= getSVGDataLabels(dls, axisMetrics, curseries[j], percentage, j, legends, categories[j].toString()); - if (l!=null) { - double halfa= ((percentage/2)*360) + lasta; // center in area - double x2=centerx + (r-r0/2)*(Math.cos(Math.toRadians(halfa))); - double y2=centery - (r-r0/2)*(Math.sin(Math.toRadians(halfa))); - svg.append("" + l + "\r\n"); - } - lasta= angle; - x= x1; - y= y1; - } // each point in current series - r-=r0; - } // each series - // complete inner circle & create "hole" - svg.append("\r\n"); - svg.append("\r\n"); - - return svg.toString(); - } - /** - * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), - * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). - * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, - * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) - * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). - * A value of 0 causes the arc to be drawn in a "negative-angle" direction - * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). - */ - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - // vary colors??? - cooxml.append(""); - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - // TODO: firstSLiceAng - cooxml.append(""); - cooxml.append(""); - - cooxml.append(""); cooxml.append("\r\n"); - - return cooxml; - } - + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + StringBuffer svg = new StringBuffer(); + if (series.size() == 0) { + Logger.logErr("DoughnutChart.getSVG: error in series"); + return ""; + } + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + boolean threeD = this.isThreeD(); + final int LABELOFFSET = 15; + + int n = series.size(); + double centerx = 0.0, centery = 0.0, radius = 0.0; + double radiusy = 0.0; + centerx = w / 2 + chartMetrics.get("x"); + centery = h / 2 + chartMetrics.get("y"); + if (threeD) { + svg.append("\r\n"); + svg.append("\r\n"); + svg.append("\r\n"); + svg.append("\r\n"); + svg.append("\r\n"); + } + svg.append("\r\n"); + + //radius= Math.min(w, h)/2.3; // should take up almost entire w/h of chart + radius = Math.min(w, h) / 1.9; // should take up almost entire w/h of chart + double r0 = (radius / 2) / n; // radius/2 to account for hole + double r = radius; // start at outside and work inside + for (int i = n - 1; i >= 0; i--) { // for each series + double[] curseries = (double[]) series.get(i); // series data points + double total = 0.0; + for (int j = 0; j < curseries.length; j++) { // get total in order to calculate percentages + total += curseries[j]; + } + if (dls.length == 1) { // no seriess-specific data labels; expand to entire series for loop below + int dl = dls[0]; + dls = new int[curseries.length]; + for (int z = 0; z < dls.length; z++) + dls[z] = dl; + } + svg.append("\r\n"); + double x = centerx + r; + double y = centery; + String path = ""; + double percentage = 0; + double lasta = 0; + int largearcflag = 0; + int sweepflag = 0; + // Now create each pie wedge according to it's percentage value + for (int j = 0; j < curseries.length; j++) { + percentage = curseries[j] / total; + double angle = (percentage * 360) + lasta; + double x1 = centerx + r * (Math.cos(Math.toRadians(angle))); + double y1 = centery - r * (Math.sin(Math.toRadians(angle))); + if ((percentage * 360) > 180) { + sweepflag = 0; + largearcflag = 1; + } else + largearcflag = 0; + path = "M" + centerx + " " + centery + " L" + x + " " + y + " A" + r + " " + r + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z"; + // paint wedge of color to center of chart -- the inner will overwrite so don't have to worry about segments and arcs, etc + svg.append("\r\n"); + // data labels + String l = getSVGDataLabels(dls, axisMetrics, curseries[j], percentage, j, legends, categories[j].toString()); + if (l != null) { + double halfa = ((percentage / 2) * 360) + lasta; // center in area + double x2 = centerx + (r - r0 / 2) * (Math.cos(Math.toRadians(halfa))); + double y2 = centery - (r - r0 / 2) * (Math.sin(Math.toRadians(halfa))); + svg.append("" + l + "\r\n"); + } + lasta = angle; + x = x1; + y = y1; + } // each point in current series + r -= r0; + } // each series + // complete inner circle & create "hole" + svg.append("\r\n"); + svg.append("\r\n"); + + return svg.toString(); + } + /** + * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), + * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). + * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, + * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) + * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). + * A value of 0 causes the arc to be drawn in a "negative-angle" direction + * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). + */ + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + // vary colors??? + cooxml.append(""); + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + // TODO: firstSLiceAng + cooxml.append(""); + cooxml.append(""); + + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } + } diff --git a/src/main/java/io/starter/formats/XLS/charts/Dropbar.java b/src/main/java/io/starter/formats/XLS/charts/Dropbar.java index 3fb8424..a1eee8d 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Dropbar.java +++ b/src/main/java/io/starter/formats/XLS/charts/Dropbar.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,96 +22,93 @@ */ package io.starter.formats.XLS.charts; -import java.util.Stack; - -import org.xmlpull.v1.XmlPullParser; - -import io.starter.formats.OOXML.SpPr; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; + /** * Dropbar: Defines Drop Bars (0x103d) * Controls up or down bars on a line (or stock, for 2007 v) chart with multiple series * the first dropBar record controls upBars * the second record controls downBars * Also, if these records exist, SeriesList cSer > 1 - * - * - - pcGap (2 bytes): A signed integer that specifies the width of the gap between the up bars or the down bars. MUST be a value between 0 and 500. - The width of the gap in SPRCs can be calculated by the following formula: - Width of the gap in SPRCs = 1 + pcGap - + *

                + *

                + *

                + * pcGap (2 bytes): A signed integer that specifies the width of the gap between the up bars or the down bars. MUST be a value between 0 and 500. + * The width of the gap in SPRCs can be calculated by the following formula: + * Width of the gap in SPRCs = 1 + pcGap + * *
                * The DropBar record occurs in the ChartFormat subrecord stream after the Legend record, * and contains subrecords LineFormat, AreaFormat, [GelFrame], [ShapeProps] - * - * */ public class Dropbar extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6826327230442065566L; - short pcGap; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 6826327230442065566L; + short pcGap; + + public void init() { + super.init(); + pcGap = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + } - public void init() { - super.init(); - pcGap= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - - public static XLSRecord getPrototype() { - Dropbar db= new Dropbar(); - db.setOpcode(DROPBAR); - db.setData(new byte[] {-106, 0}); // 150 is default gap width - db.init(); - return db; - } - - /** - * sets the width of the gap between the up bars or the down bars. MUST be a value between 0 and 500. - The width of the gap in SPRCs can be calculated by the following formula: - Width of the gap in SPRCs = 1 + pcGap - * @param gap - */ - public void setGapWidth(int gap) { - pcGap= (short) gap; - byte[] b= ByteTools.shortToLEBytes(pcGap); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; - } + public static XLSRecord getPrototype() { + Dropbar db = new Dropbar(); + db.setOpcode(DROPBAR); + db.setData(new byte[]{-106, 0}); // 150 is default gap width + db.init(); + return db; + } - /** - * returns the width of the gap between the up bars or the down bars. - *
                The gap width is a value between 0 and 500. -
                The width of the gap in SPRCs can be calculated by the following formula: - Width of the gap in SPRCs = 1 + pcGap + /** + * sets the width of the gap between the up bars or the down bars. MUST be a value between 0 and 500. + * The width of the gap in SPRCs can be calculated by the following formula: + * Width of the gap in SPRCs = 1 + pcGap + * + * @param gap + */ + public void setGapWidth(int gap) { + pcGap = (short) gap; + byte[] b = ByteTools.shortToLEBytes(pcGap); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } - * @return - */ - public short getGapWidth() { return pcGap; } + /** + * returns the width of the gap between the up bars or the down bars. + *
                The gap width is a value between 0 and 500. + *
                The width of the gap in SPRCs can be calculated by the following formula: + * Width of the gap in SPRCs = 1 + pcGap + * + * @return + */ + public short getGapWidth() { + return pcGap; + } - /** - * return the OOXML to define this ChartLine - * @return - */ - public StringBuffer getOOXML(boolean upBars) { - StringBuffer cooxml= new StringBuffer(); - String tag; - if (upBars) - tag= "c:upBars>"; - else - tag= "c:downBars>"; - cooxml.append("<" + tag); - LineFormat lf= (LineFormat) chartArr.get(0); - cooxml.append(lf.getOOXML()); - // TODO: finish this logic - if (!parentChart.getWorkBook().getIsExcel2007()) { - AreaFormat af= (AreaFormat) chartArr.get(1); - cooxml.append(af.getOOXML()); - } - cooxml.append(""; + else + tag = "c:downBars>"; + cooxml.append("<" + tag); + LineFormat lf = (LineFormat) chartArr.get(0); + cooxml.append(lf.getOOXML()); + // TODO: finish this logic + if (!parentChart.getWorkBook().getIsExcel2007()) { + AreaFormat af = (AreaFormat) chartArr.get(1); + cooxml.append(af.getOOXML()); + } + cooxml.append(". @@ -27,26 +27,25 @@ /** * End: End of chart substream (0x1034) - * + *

                * End is an identifier record for the chart record type. There is no data to a end record, * and every end record must have a corrosponding begin record - * */ -public class End extends GenericChartObject implements ChartObject{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 9022736093645720842L; +public class End extends GenericChartObject implements ChartObject { - public void init() { - super.init(); - } - - protected static XLSRecord getPrototype(){ + /** + * serialVersionUID + */ + private static final long serialVersionUID = 9022736093645720842L; + + public void init() { + super.init(); + } + + protected static XLSRecord getPrototype() { End bl = new End(); bl.setOpcode(END); - bl.setData(new byte[] {}); + bl.setData(new byte[]{}); return bl; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/EndBlock.java b/src/main/java/io/starter/formats/XLS/charts/EndBlock.java index 74b6cfe..dc7268a 100644 --- a/src/main/java/io/starter/formats/XLS/charts/EndBlock.java +++ b/src/main/java/io/starter/formats/XLS/charts/EndBlock.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,45 +24,51 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** - ENDBLOCK: Chart Future Record Type End Block (853h) - Introduced in Excel 9 (2000), this BIFF record is an FRT record for - Charts that indicates end of an object's scope for Pre-Excel 9 objects. - Paired with STARTBLOCK. - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0853h - 6 grbitFrt 2 FRT flags; must be zero - 8 iObjectKind 2 Sanity check for object scope being ended - 10 (unused) 6 Reserved; must be zero + * ENDBLOCK: Chart Future Record Type End Block (853h) + * Introduced in Excel 9 (2000), this BIFF record is an FRT record for + * Charts that indicates end of an object's scope for Pre-Excel 9 objects. + * Paired with STARTBLOCK. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0853h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 iObjectKind 2 Sanity check for object scope being ended + * 10 (unused) 6 Reserved; must be zero */ public class EndBlock extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1132544432743236942L; - short iObjectKind= 0; - public void init() { - super.init(); - iObjectKind= ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - } // iObjectKind= 5 or 0 or 13 - private byte[] PROTOTYPE_BYTES = new byte[] {83, 8, 0,0,5,0, 0,0,0,0,0,0}; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1132544432743236942L; + short iObjectKind = 0; + + public void init() { + super.init(); + iObjectKind = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + } // iObjectKind= 5 or 0 or 13 + + private byte[] PROTOTYPE_BYTES = new byte[]{83, 8, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0}; + public static XLSRecord getPrototype() { - EndBlock eb= new EndBlock(); + EndBlock eb = new EndBlock(); eb.setOpcode(ENDBLOCK); eb.setData(eb.PROTOTYPE_BYTES); eb.init(); return eb; } + public void setObjectKind(int i) { - iObjectKind= (short) i; - updateRecord(); + iObjectKind = (short) i; + updateRecord(); } + private void updateRecord() { byte[] b = ByteTools.shortToLEBytes(iObjectKind); this.getData()[4] = b[0]; this.getData()[5] = b[1]; } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/EndObject.java b/src/main/java/io/starter/formats/XLS/charts/EndObject.java index f9797da..f5661a3 100644 --- a/src/main/java/io/starter/formats/XLS/charts/EndObject.java +++ b/src/main/java/io/starter/formats/XLS/charts/EndObject.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,35 +23,40 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; + /** - ENDOBJECT: Chart Future Record Type End Object (855h) - Introduced in Excel 9 (2000), this BIFF record is an FRT record - for Charts that indicates the end of an object's scope for Excel 9 and later objects. - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0855h - 6 grbitFrt 2 FRT flags; must be zero - 8 iObjectKind 2 Sanity check for object scope being ended - 10 (unused) 6 Reserved; must be zero -*/ + * ENDOBJECT: Chart Future Record Type End Object (855h) + * Introduced in Excel 9 (2000), this BIFF record is an FRT record + * for Charts that indicates the end of an object's scope for Excel 9 and later objects. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0855h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 iObjectKind 2 Sanity check for object scope being ended + * 10 (unused) 6 Reserved; must be zero + */ public class EndObject extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8367476159843855287L; - public void init() { - super.init(); - } // iObjectKind of 18 appears to be Chart object - private byte[] PROTOTYPE_BYTES = new byte[] {85, 8, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0}; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8367476159843855287L; + + public void init() { + super.init(); + } // iObjectKind of 18 appears to be Chart object + + private byte[] PROTOTYPE_BYTES = new byte[]{85, 8, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0}; + public static XLSRecord getPrototype() { - EndObject eo= new EndObject(); + EndObject eo = new EndObject(); eo.setOpcode(ENDOBJECT); eo.setData(eo.PROTOTYPE_BYTES); eo.init(); return eo; } + private void updateRecord() { } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/Fbi.java b/src/main/java/io/starter/formats/XLS/charts/Fbi.java index 08c0c1b..4ffdf93 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Fbi.java +++ b/src/main/java/io/starter/formats/XLS/charts/Fbi.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,15 +24,14 @@ /** * Fbi: Font Basis(0x1060) - * */ public class Fbi extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6933614402245236374L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 6933614402245236374L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Fontx.java b/src/main/java/io/starter/formats/XLS/charts/Fontx.java index 4d399a1..21e2568 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Fontx.java +++ b/src/main/java/io/starter/formats/XLS/charts/Fontx.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,29 +24,32 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * Fontx: Font Index (0x1026) * Child of a TEXT record and defines a text font by indexing the appropriate font in * the font table. - * + *

                * 4 iFont 2 Index number into the font table - * If this field is less than or equal to the number of Font records - in the workbook, this field is a one-based index to a Font record in - the workbook. Otherwise, this field is a one-based index into the - collection of Font records in the chart sheet substream, where the index is equal to - iFont minus n and n is the number of Font records in the workbook. + * If this field is less than or equal to the number of Font records + * in the workbook, this field is a one-based index to a Font record in + * the workbook. Otherwise, this field is a one-based index into the + * collection of Font records in the chart sheet substream, where the index is equal to + * iFont minus n and n is the number of Font records in the workbook. */ public class Fontx extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4255798925225768809L; - // 20070806 KSC: Add init/update to control FontX opts - private short ifnt= 0; - public void init() { - super.init(); - ifnt= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4255798925225768809L; + // 20070806 KSC: Add init/update to control FontX opts + private short ifnt = 0; + + public void init() { + super.init(); + ifnt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + } + public static XLSRecord getPrototype() { Fontx f = new Fontx(); f.setOpcode(FONTX); @@ -54,32 +57,35 @@ public static XLSRecord getPrototype() { f.init(); return f; } - private byte[] PROTOTYPE_BYTES = new byte[] {5, 0}; - + + private byte[] PROTOTYPE_BYTES = new byte[]{5, 0}; + /** - returns the index into the wb font table referenced by this Fontx record for the chart - */ + * returns the index into the wb font table referenced by this Fontx record for the chart + */ /* If this field is less than or equal to the number of Font records in the workbook, this field is a one-based index to a Font record in the workbook. Otherwise, this field is a one-based index into the collection of Font records in the chart sheet substream, where the index is equal to iFont minus n and n is the number of Font records in the workbook. - */ + */ public int getIfnt() { - int n= 0; - try { - n= this.getWorkBook().getNumFonts(); - } catch (Exception e) {} - if (ifnt <= n) - return ifnt; - else - return (ifnt-n)+1; + int n = 0; + try { + n = this.getWorkBook().getNumFonts(); + } catch (Exception e) { + } + if (ifnt <= n) + return ifnt; + else + return (ifnt - n) + 1; } + public void setIfnt(int id) { - this.ifnt= (short) id; + this.ifnt = (short) id; byte[] b = ByteTools.shortToLEBytes(this.ifnt); this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } + this.getData()[1] = b[1]; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Frame.java b/src/main/java/io/starter/formats/XLS/charts/Frame.java index 9e9243d..261e34f 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Frame.java +++ b/src/main/java/io/starter/formats/XLS/charts/Frame.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,168 +24,169 @@ import io.starter.formats.XLS.BiffRec; import io.starter.formats.XLS.XLSRecord; -import io.starter.formats.XLS.FormatConstants; import io.starter.toolkit.ByteTools; /** * Frame: Defines Border Shape Around Displayed Text (0x1032) - * */ -/** - frt (2 bytes): An unsigned integer that specifies the type of frame to be drawn. MUST be a value from the following table: - Type of frame - 0x0000 A frame surrounding the chart element. - 0x0004 A frame with shadow surrounding the chart element. +/** + frt (2 bytes): An unsigned integer that specifies the type of frame to be drawn. MUST be a value from the following table: + Type of frame + 0x0000 A frame surrounding the chart element. + + 0x0004 A frame with shadow surrounding the chart element. - A - fAutoSize (1 bit): A bit that specifies if the size of the frame is automatically calculated. If the value is 1, the size of the frame is automatically calculated. In this case, the width and height specified by the chart element are ignored and the size of the frame is calculated automatically. If the value is 0, the width and height specified by the chart element are used as the size of the frame. + A - fAutoSize (1 bit): A bit that specifies if the size of the frame is automatically calculated. If the value is 1, the size of the frame is automatically calculated. In this case, the width and height specified by the chart element are ignored and the size of the frame is calculated automatically. If the value is 0, the width and height specified by the chart element are used as the size of the frame. - B - fAutoPosition (1 bit): A bit that specifies if the position of the frame is automatically calculated. If the value is 1, the position of the frame is automatically calculated. In this case, the (x, y) specified by the chart element are ignored, and the position of the frame is automatically calculated. If the value is 0, the (x, y) location specified by the chart element are used as the position of the frame. + B - fAutoPosition (1 bit): A bit that specifies if the position of the frame is automatically calculated. If the value is 1, the position of the frame is automatically calculated. In this case, the (x, y) specified by the chart element are ignored, and the position of the frame is automatically calculated. If the value is 0, the (x, y) location specified by the chart element are used as the position of the frame. */ public class Frame extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6302152932127918650L; - boolean fAutoSize, fAutoPosition; - int frt; - public void init() { - frt= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - byte flag= this.getByteAt(2);// 3= autosize, autoposition - fAutoSize= (flag & 0x01)==0x01; - fAutoPosition= (flag & 0x02)==0x02; - super.init(); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 6302152932127918650L; + boolean fAutoSize, fAutoPosition; + int frt; + + public void init() { + frt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + byte flag = this.getByteAt(2);// 3= autosize, autoposition + fAutoSize = (flag & 0x01) == 0x01; + fAutoPosition = (flag & 0x02) == 0x02; + super.init(); + } + public static XLSRecord getPrototype() { - Frame f= new Frame(); + Frame f = new Frame(); f.setOpcode(FRAME); - f.setData(new byte[] {0, 0, 2, 0}); + f.setData(new byte[]{0, 0, 2, 0}); f.init(); return f; } - - /** - * return the bg color assoc with this frame rec - * in main chart record - * NOTE that bg color is defined by the frame rec's associated - * AreaFormat Record's FOREGROUND color (icvFore) - * @return bg color Hex String - */ - public String getBgColor() { - return getBgColor(chartArr); - } - - - /** - * static utility to return the bg color assoc with the desired object - *
                first checks if a gelFrame record exists; if so, it uses that color. - *
                if no gelFrame record exists, it looks for an AreaFormat record. - * @return bg color Hex String - */ - public static String getBgColor(java.util.ArrayList chartArr) { - GelFrame gf= (GelFrame) Chart.findRec(chartArr, GelFrame.class); - if (gf!=null) - return gf.getFillColor(); - AreaFormat af= (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); - if (af!=null) { - return af.getFillColorStr(); - } - return null; // use default - } - - /** - * sets the background color assoc with this frame rec - * NOTE that bg color is defined by the frame rec's associated - * AreaFormat Record's FOREGROUND color (icvFore) - * @param bg color int - */ - public void setBgColor(int bg) { - for (int i=0;ifirst checks if a gelFrame record exists; if so, it uses that color. + *
                if no gelFrame record exists, it looks for an AreaFormat record. + * @return bg color Hex String + */ + public static String getBgColor(java.util.ArrayList chartArr) { + GelFrame gf = (GelFrame) Chart.findRec(chartArr, GelFrame.class); + if (gf != null) + return gf.getFillColor(); + AreaFormat af = (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); + if (af != null) { + return af.getFillColorStr(); + } + return null; // use default + } + + /** + * sets the background color assoc with this frame rec + * NOTE that bg color is defined by the frame rec's associated + * AreaFormat Record's FOREGROUND color (icvFore) + * @param bg color int + */ + public void setBgColor(int bg) { + for (int i = 0; i < chartArr.size(); i++) { + BiffRec b = chartArr.get(i); if (b instanceof AreaFormat) - ((AreaFormat) b).seticvFore(bg); + ((AreaFormat) b).seticvFore(bg); + } + } + + /** + * set the frame autosize/autoposition, necessary for legend expansion + * @see Legend.setAutoPosition, Pos.setAutosizeLegend + * [BugTracker 2844] + */ + public void setAutosize() { + this.getData()[2] = 3; // sets both fAutoSize and fAutoPostion + } + + /** + * adds a frame box with the desired lineweight and fill color + * @param lw if -1, none + * @param lclr + * @param bgcolor if -1, none + */ + public void addBox(int lw, int lclr, int bgcolor) { + LineFormat lf = (LineFormat) Chart.findRec(chartArr, LineFormat.class); + if (lf == null) { + lf = (LineFormat) LineFormat.getPrototype(0, 0); + this.addChartRecord(lf); + } + if (lw != -1) { + lf.setLineWeight(lw); + lf.setLineStyle(0); // solid + } else { + lf.setLineStyle(5); // none } - } - - /** - * set the frame autosize/autoposition, necessary for legend expansion - * @see Legend.setAutoPosition, Pos.setAutosizeLegend - * [BugTracker 2844] - */ - public void setAutosize() { - this.getData()[2]= 3; // sets both fAutoSize and fAutoPostion - } - - /** - * adds a frame box with the desired lineweight and fill color - * @param lw if -1, none - * @param lclr - * @param bgcolor if -1, none - */ - public void addBox(int lw, int lclr, int bgcolor) { - LineFormat lf= (LineFormat) Chart.findRec(chartArr, LineFormat.class); - if (lf==null) { - lf= (LineFormat) LineFormat.getPrototype(0, 0); - this.addChartRecord(lf); - } - if (lw!=-1) { - lf.setLineWeight(lw); - lf.setLineStyle(0); // solid - } else { - lf.setLineStyle(5); // none - } - if (lclr!=-1) - lf.setLineColor(lclr); - AreaFormat af= (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); - if (af==null) { - af= (AreaFormat) AreaFormat.getPrototype(1); - this.addChartRecord(af); - } - if(bgcolor!=-1) - af.seticvBack(bgcolor); - } - + if (lclr != -1) + lf.setLineColor(lclr); + AreaFormat af = (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); + if (af == null) { + af = (AreaFormat) AreaFormat.getPrototype(1); + this.addChartRecord(af); + } + if (bgcolor != -1) + af.seticvBack(bgcolor); + } + /** * returns true if this Frame is surrounded by a box * @return */ public boolean hasBox() { - LineFormat l= (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); - return (l.getLineStyle()!=LineFormat.NONE); + LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); + return (l.getLineStyle() != LineFormat.NONE); } - + /** * return the line color as a hex String * @return */ public String getLineColor() { - LineFormat l= (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); - if (l!=null) - return l.getLineColor(); - return null; + LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); + if (l != null) + return l.getLineColor(); + return null; } - - /** - * return the svg representation of this Frame element - * @param coords - * @return - */ - public StringBuffer getSVG(float[] coords) { - StringBuffer svg= new StringBuffer(); - String lineSVG= ""; - String bgclr= getBgColor(); + + /** + * return the svg representation of this Frame element + * @param coords + * @return + */ + public StringBuffer getSVG(float[] coords) { + StringBuffer svg = new StringBuffer(); + String lineSVG = ""; + String bgclr = getBgColor(); // String bgclr= "white"; - if (bgclr==null) - bgclr= "white"; - - LineFormat lf= (LineFormat) Chart.findRec(chartArr, LineFormat.class); - if (lf!=null) - lineSVG= lf.getSVG(); - - float x= coords[0] - coords[2]/2; // apparently coords are center-point; adjust - float y= coords[1] - coords[3]/2; - svg.append("\r\n"); - - return svg; - } + if (bgclr == null) + bgclr = "white"; + + LineFormat lf = (LineFormat) Chart.findRec(chartArr, LineFormat.class); + if (lf != null) + lineSVG = lf.getSVG(); + + float x = coords[0] - coords[2] / 2; // apparently coords are center-point; adjust + float y = coords[1] - coords[3] / 2; + svg.append("\r\n"); + + return svg; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/FrtFontList.java b/src/main/java/io/starter/formats/XLS/charts/FrtFontList.java index 44fec2e..f5cf642 100644 --- a/src/main/java/io/starter/formats/XLS/charts/FrtFontList.java +++ b/src/main/java/io/starter/formats/XLS/charts/FrtFontList.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,55 +23,60 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; -/** - FRTFONTLIST: Chart Font List (85Ah) - Introduced in Excel 9 (2000), this BIFF record is an FRT record for - Charts. This record stores font information for Excel 9 or later chart - objects. On round-tripping through an earlier version of Excel, the fonts - for new chart objects are lost from the font table because earlier version - of Excel do not load the newer objects and thus dont preserve the new objects - fonts. This record contains a list of the font indices used by Excel 9 or later - objects and whether the font is auto-scaled. The fonts themselves are stored - information in a STARTOBJECT/ENDOBJECT block that immediately follows. The block - has objectKind = 17, objectContext = 0, objectInstance1 = 0, objectInstance2 = 0. - The block has cfont FONT records and FBI records (for those with fScaled =1 only). - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =085Ah - 6 grbitFrt 2 FRT flags; must be zero - 8 verChart 1 Version of Charting this list applies to - 9 cfont 2 Number of fonts in list - 11 rgFontInfo var Array of font IDs - FontInfo Structure - Offset Field Name Size Contents - 0 grbit 2 Option flags for chart fonts (see description below) - 2 ifnt 2 Font ID of this font entry - - The grbit field contains the following chart font flags: - Bits Mask Flag Name Contents - 0 0001h fScaled =1 if the font is scaled =0 otherwise - 15-1 FFFEh (unused) Reserved; must be zero -*/ +/** + * FRTFONTLIST: Chart Font List (85Ah) + * Introduced in Excel 9 (2000), this BIFF record is an FRT record for + * Charts. This record stores font information for Excel 9 or later chart + * objects. On round-tripping through an earlier version of Excel, the fonts + * for new chart objects are lost from the font table because earlier version + * of Excel do not load the newer objects and thus dont preserve the new objects + * fonts. This record contains a list of the font indices used by Excel 9 or later + * objects and whether the font is auto-scaled. The fonts themselves are stored + * information in a STARTOBJECT/ENDOBJECT block that immediately follows. The block + * has objectKind = 17, objectContext = 0, objectInstance1 = 0, objectInstance2 = 0. + * The block has cfont FONT records and FBI records (for those with fScaled =1 only). + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =085Ah + * 6 grbitFrt 2 FRT flags; must be zero + * 8 verChart 1 Version of Charting this list applies to + * 9 cfont 2 Number of fonts in list + * 11 rgFontInfo var Array of font IDs + *

                + * FontInfo Structure + * Offset Field Name Size Contents + * 0 grbit 2 Option flags for chart fonts (see description below) + * 2 ifnt 2 Font ID of this font entry + *

                + * The grbit field contains the following chart font flags: + * Bits Mask Flag Name Contents + * 0 0001h fScaled =1 if the font is scaled =0 otherwise + * 15-1 FFFEh (unused) Reserved; must be zero + */ public class FrtFontList extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5374035624852924277L; - public void init() { - super.init(); - } - // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[] {}; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5374035624852924277L; + + public void init() { + super.init(); + } + + // TODO: Prototype Bytes + private byte[] PROTOTYPE_BYTES = new byte[]{}; + public static XLSRecord getPrototype() { - FrtFontList ffl= new FrtFontList(); - ffl.setOpcode(FRTFONTLIST); - ffl.setData(ffl.PROTOTYPE_BYTES); - ffl.init(); + FrtFontList ffl = new FrtFontList(); + ffl.setOpcode(FRTFONTLIST); + ffl.setData(ffl.PROTOTYPE_BYTES); + ffl.init(); return ffl; } + private void updateRecord() { } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.java b/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.java index a0d65fe..d206c71 100644 --- a/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.java +++ b/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,87 +23,92 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; + /** - FRTWRAPPER: Chart Future Record Type Wrapper (851h) - Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. - This record is used to disguise a normal, non-FRT record as a FRT record. - This is necessary whenever a new Excel element must save a pre-Excel 9 record - as a child record. As an FRT record, Excel 97 will keep the record together with - its associated STARTOBJECT/ENDOBJECT when round-tripping FRTs. The size of this - record varies depending on the record that was wrapped. - - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0851h - 6 grbitFrt 2 FRT flags; must be zero - 8 rt 2 RT of wrapped record - 10 cb 2 Size of wrapped RT's data in bytes - 12 rgb var RT's data -*/ + * FRTWRAPPER: Chart Future Record Type Wrapper (851h) + * Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. + * This record is used to disguise a normal, non-FRT record as a FRT record. + * This is necessary whenever a new Excel element must save a pre-Excel 9 record + * as a child record. As an FRT record, Excel 97 will keep the record together with + * its associated STARTOBJECT/ENDOBJECT when round-tripping FRTs. The size of this + * record varies depending on the record that was wrapped. + *

                + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0851h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 rt 2 RT of wrapped record + * 10 cb 2 Size of wrapped RT's data in bytes + * 12 rgb var RT's data + */ public class FrtWrapper extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4527467252753642328L; - private int type; - public static final int DEFAULTTEXT= 0, TEXTDISPWITHDATALABELS= 1, BEGIN= 2, POS= 3, AI= 4, OBJECTLINK= 5, END= 6, TEXTDISP= 7; - public void init() { - super.init(); - } - // 20080703 KSC: these bytes sequences haven't been entirelyy figured out but are necessary for Series Data Labels .. - private byte[] P_0= new byte[] {81, 8, 0, 0, 36, 16, 2, 0, 0, 0, 0, 0}; // rt= 1024, DEFTEXT + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4527467252753642328L; + private int type; + public static final int DEFAULTTEXT = 0, TEXTDISPWITHDATALABELS = 1, BEGIN = 2, POS = 3, AI = 4, OBJECTLINK = 5, END = 6, TEXTDISP = 7; + + public void init() { + super.init(); + } + + // 20080703 KSC: these bytes sequences haven't been entirelyy figured out but are necessary for Series Data Labels .. + private byte[] P_0 = new byte[]{81, 8, 0, 0, 36, 16, 2, 0, 0, 0, 0, 0}; // rt= 1024, DEFTEXT // rt= 1025, TEXTDISP - private byte[] P_1= new byte[] {81, 8, 0, 0, 37, 16, 32, 0, 2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -77, 0, 77, 0, 16, 61, 0, 0}; - private byte[] P_2= new byte[] {81, 8, 0, 0, 51, 16, 0, 0, 0, 0, 0, 0}; //rt= 1033, BEGIN - private byte[] P_3= new byte[] {81, 8, 0, 0, 79, 16, 20, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // rt= 104F, POS - private byte[] P_4= new byte[] {81, 8, 0, 0, 81, 16, 8, 0, 0, 1, 0, 0, 0, 0, 0, 0}; // rt= 1051, AI - private byte[] P_5= new byte[] {81, 8, 0, 0, 39, 16, 6, 0, 4, 0, 0, 0, 0, 0}; // rt= 1027, OBJECTLINK - private byte[] P_6= new byte[] {81, 8, 0, 0, 52, 16, 0, 0, 0, 0, 0, 0}; // rt= 1034, END - private byte[] P_7= new byte[] {81, 8, 0, 0, 37, 16, 32, 0, 2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -79, 0, 77, 0, 16, 61, 0, 0}; - + private byte[] P_1 = new byte[]{81, 8, 0, 0, 37, 16, 32, 0, 2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -77, 0, 77, 0, 16, 61, 0, 0}; + private byte[] P_2 = new byte[]{81, 8, 0, 0, 51, 16, 0, 0, 0, 0, 0, 0}; //rt= 1033, BEGIN + private byte[] P_3 = new byte[]{81, 8, 0, 0, 79, 16, 20, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // rt= 104F, POS + private byte[] P_4 = new byte[]{81, 8, 0, 0, 81, 16, 8, 0, 0, 1, 0, 0, 0, 0, 0, 0}; // rt= 1051, AI + private byte[] P_5 = new byte[]{81, 8, 0, 0, 39, 16, 6, 0, 4, 0, 0, 0, 0, 0}; // rt= 1027, OBJECTLINK + private byte[] P_6 = new byte[]{81, 8, 0, 0, 52, 16, 0, 0, 0, 0, 0, 0}; // rt= 1034, END + private byte[] P_7 = new byte[]{81, 8, 0, 0, 37, 16, 32, 0, 2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -79, 0, 77, 0, 16, 61, 0, 0}; + /** * create a new FrtWrapper record * each record wraps around another record - * @param type type of other record + * + * @param type type of other record * @return */ public static XLSRecord getPrototype(int type) { - FrtWrapper frt= new FrtWrapper(); - frt.type= type; // save wrapped type + FrtWrapper frt = new FrtWrapper(); + frt.type = type; // save wrapped type frt.setOpcode(FRTWRAPPER); - byte[] b= null; + byte[] b = null; switch (type) { - case DEFAULTTEXT: - b= frt.P_0; // generate a default text record with 0=show labels - break; - case TEXTDISPWITHDATALABELS: // generate a text display with showKey (I believe!) - b= frt.P_1; - break; - case TEXTDISP: // generate a text display with showKey (I believe!) and NO data labels - b= frt.P_7; - break; - case BEGIN: - b= frt.P_2; - break; - case POS: - b= frt.P_3; // generate a POS record with default values - break; - case AI: - b= frt.P_4; // generate an AI record for SERIES Values - break; - case OBJECTLINK: - b= frt.P_5; // generate record with TYPE_DATASERIES - break; - case END: - b= frt.P_6; - break; + case DEFAULTTEXT: + b = frt.P_0; // generate a default text record with 0=show labels + break; + case TEXTDISPWITHDATALABELS: // generate a text display with showKey (I believe!) + b = frt.P_1; + break; + case TEXTDISP: // generate a text display with showKey (I believe!) and NO data labels + b = frt.P_7; + break; + case BEGIN: + b = frt.P_2; + break; + case POS: + b = frt.P_3; // generate a POS record with default values + break; + case AI: + b = frt.P_4; // generate an AI record for SERIES Values + break; + case OBJECTLINK: + b = frt.P_5; // generate record with TYPE_DATASERIES + break; + case END: + b = frt.P_6; + break; } frt.setData(b); frt.init(); return frt; } + private void updateRecord() { } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/GelFrame.java b/src/main/java/io/starter/formats/XLS/charts/GelFrame.java index cf90309..5f55e63 100644 --- a/src/main/java/io/starter/formats/XLS/charts/GelFrame.java +++ b/src/main/java/io/starter/formats/XLS/charts/GelFrame.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,39 +24,39 @@ import io.starter.OpenXLS.FormatHandle; import io.starter.formats.XLS.MSODrawingConstants; -import io.starter.formats.XLS.XLSRecord; import io.starter.formats.escher.MsofbtOPT; + /** * GelFrame: Fill Data(0x1066) - * The GelFrame record specifies the properties of a fill pattern for parts of a chart. - * + * The GelFrame record specifies the properties of a fill pattern for parts of a chart. */ public class GelFrame extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 581278144607124129L; - private java.awt.Color fillColor= null; - private int fillType= 0; // default= solid - - public void init() { - super.init(); - // try to interpret - MsofbtOPT optrec= new MsofbtOPT(MSODrawingConstants.MSOFBTOPT,0,3); //version is always 3, inst is current count of properties. - optrec.setData(this.getData()); // sets and parses msoFbtOpt data, including imagename, shapename and imageindex - fillColor= optrec.getFillColor(); - } - - public String toString() { - return "GelFrame: fillType=" + fillType + " fillColor:" + fillColor.toString(); - } - - /** - * return the fill color for this frame - * @return Color Hex String - */ - public String getFillColor() { - if (fillColor==null) return null; - return FormatHandle.colorToHexString(fillColor); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 581278144607124129L; + private java.awt.Color fillColor = null; + private int fillType = 0; // default= solid + + public void init() { + super.init(); + // try to interpret + MsofbtOPT optrec = new MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3); //version is always 3, inst is current count of properties. + optrec.setData(this.getData()); // sets and parses msoFbtOpt data, including imagename, shapename and imageindex + fillColor = optrec.getFillColor(); + } + + public String toString() { + return "GelFrame: fillType=" + fillType + " fillColor:" + fillColor.toString(); + } + + /** + * return the fill color for this frame + * + * @return Color Hex String + */ + public String getFillColor() { + if (fillColor == null) return null; + return FormatHandle.colorToHexString(fillColor); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.java b/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.java index d3befe8..b074267 100644 --- a/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.java +++ b/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,118 +22,137 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; import io.starter.formats.XLS.BiffRec; import io.starter.formats.XLS.XLSRecord; +import java.util.ArrayList; + public class GenericChartObject extends XLSRecord implements ChartObject, ChartConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1919254120575019160L; - - protected int chartType = -1; // this will be >=0 when record defines the type of chart + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1919254120575019160L; + + protected int chartType = -1; // this will be >=0 when record defines the type of chart protected ArrayList chartArr = new ArrayList(); protected Chart parentChart; - - public boolean setChartOption(String op, String val) { return false; } - public boolean isStacked() { return false; } - public boolean is100Percent() { return false; } - public boolean hasShadow() { return false; } - public void setIsStacked(boolean bIsStacked) { ; } - public void setIs100Percent(boolean bOn) { ; } + + public boolean setChartOption(String op, String val) { + return false; + } + + public boolean isStacked() { + return false; + } + + public boolean is100Percent() { + return false; + } + + public boolean hasShadow() { + return false; + } + + public void setIsStacked(boolean bIsStacked) { + } + + public void setIs100Percent(boolean bOn) { + } /** - * get chart option common to almost all chart types + * get chart option common to almost all chart types + * * @param op * @return */ public String getChartOption(String op) { - if (op.equals("Stacked")) { // Area, Bar, Pie, Line - return String.valueOf(isStacked()); - } else if (op.equals("Shadow")) { // Pie, Area, Bar, Line, Radar, Scatter - return String.valueOf(hasShadow()); - } else if (op.equals("PercentageDisplay")) { // Area, Bar,Line - return String.valueOf(is100Percent()); - } - return null; + if (op.equals("Stacked")) { // Area, Bar, Pie, Line + return String.valueOf(isStacked()); + } else if (op.equals("Shadow")) { // Pie, Area, Bar, Line, Radar, Scatter + return String.valueOf(hasShadow()); + } else if (op.equals("PercentageDisplay")) { // Area, Bar,Line + return String.valueOf(is100Percent()); + } + return null; } - - /** + + /** * @return String XML representation of this chart-type's options */ - public String getOptionsXML() { - return ""; + public String getOptionsXML() { + return ""; } - + /** - * adds html id and handlers for generic chart svg element - * @param title id of element - * @return String html + * adds html id and handlers for generic chart svg element + * + * @param title id of element + * @return String html */ - public static String getScript(String id){ - String ret= ""; - if (id!=null) - ret= "id='" + id + "' "; - ret+="onmouseover='highLight(evt);' onclick='handleClick(evt);' onmouseout='restore(evt)'"; - return ret; + public static String getScript(String id) { + String ret = ""; + if (id != null) + ret = "id='" + id + "' "; + ret += "onmouseover='highLight(evt);' onclick='handleClick(evt);' onmouseout='restore(evt)'"; + return ret; } public ArrayList getChartRecords() { return chartArr; } - + public void addChartRecord(XLSRecord b) { chartArr.add(b); } - + public Chart getParentChart() { return parentChart; } - + public void setParentChart(Chart c) { parentChart = c; } - + /** * Get the output array of records, including begin/end records and those of it's children. */ public ArrayList getRecordArray() { ArrayList outputArr = new ArrayList(); outputArr.add(this); - int nChart= -1; - for (int i=0;i. @@ -22,23 +22,22 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * Ifmt: Number-Format Index(0x104e) - * */ public class Ifmt extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -509816915750594317L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -509816915750594317L; + + public void init() { + super.init(); + } - public void init() { - super.init(); - } - - public int getFmt() { - return ByteTools.readShort(this.getData()[0], this.getData()[1]); - } + public int getFmt() { + return ByteTools.readShort(this.getData()[0], this.getData()[1]); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Legend.java b/src/main/java/io/starter/formats/XLS/charts/Legend.java index 60b6bb3..561a799 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Legend.java +++ b/src/main/java/io/starter/formats/XLS/charts/Legend.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,308 +22,313 @@ */ package io.starter.formats.XLS.charts; -import java.util.Arrays; -import java.util.HashMap; - import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.MSODrawing; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; + +import java.util.HashMap; + /** * Legend: Legend Type and Position (0x1015) - * - * - * 4 x 4 x position of upper-left corner -- MUST be ignored and the x1 field from the following Pos record MUST be used instead. + *

                + *

                + * 4 x 4 x position of upper-left corner -- MUST be ignored and the x1 field from the following Pos record MUST be used instead. * 8 y 4 y position of upper-left corner -- MUST be ignored and the y1 field from the following Pos record MUST be used instead. * 12 dx 4 width in SPRC -- MUST be ignored and the x2 field from the following Pos record MUST be used instead. * 16 dy 4 height in SPRC -- MUST be ignored and the y2 field from the following Pos record MUST be used instead. * 20 1 Undefined and MUST be ignored. * 21 wSpacing1 Spacing (0= close, 1= medium, 2= open) (0x1= 40 twips==4 pts) * 22 grbit 2 Option Flags - * + *

                * grbit Option Flags * bits Mask - * 0 01h fAutoPostion Automatic positioning (1= legend is docked) - * 1 02h fAutoSeries Automatic series distribution - * 2 04h fAutoPosX X positioning is automatic - * 3 08h fAutoPosY Y positioning is automatic - * 4 10h fVert 1= vertical legend, 0= horizontal - * 5 20h fWasDataTable 1= chart contains data table - * - * NOTES: - * A SPRC is a unit of measurement that is 1/4000th of the height or width of the chart - * If the field is being used to specify a width or horizontal distance, the SPRC is 1/4000th - * of the width of the chart. If the field is being used to specify a height or vertical - * distance, the SPRC is 1/4000th of the height of the chart. - * - * Sequence of records: - * ATTACHEDLABEL = TextDisp Begin Pos [FontX] [AlRuns] AI [FRAME] [ObjectLink] [DataLabExtContents] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] End - * LD = Legend Begin Pos ATTACHEDLABEL [FRAME] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] End - - * + * 0 01h fAutoPostion Automatic positioning (1= legend is docked) + * 1 02h fAutoSeries Automatic series distribution + * 2 04h fAutoPosX X positioning is automatic + * 3 08h fAutoPosY Y positioning is automatic + * 4 10h fVert 1= vertical legend, 0= horizontal + * 5 20h fWasDataTable 1= chart contains data table + *

                + * NOTES: + * A SPRC is a unit of measurement that is 1/4000th of the height or width of the chart + * If the field is being used to specify a width or horizontal distance, the SPRC is 1/4000th + * of the width of the chart. If the field is being used to specify a height or vertical + * distance, the SPRC is 1/4000th of the height of the chart. + *

                + * Sequence of records: + * ATTACHEDLABEL = TextDisp Begin Pos [FontX] [AlRuns] AI [FRAME] [ObjectLink] [DataLabExtContents] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] End + * LD = Legend Begin Pos ATTACHEDLABEL [FRAME] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] End */ public class Legend extends GenericChartObject implements ChartObject, ChartConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4041111720696805018L; - protected int x_defunct=-1, y_defunct=-1, dx_defunct=-1, dy_defunct=-1; // these vars are now defunct; see doc above + Pos/getSVG for coordinate info - protected byte /*wType= -1, */wSpacing= -1; - protected short grbit= -1; - protected boolean fAutoPosition, fAutoSeries, fAutoPosX, fAutoPosY; - protected boolean fVert, fWasDataTable; - public static final int BOTTOM=0, CORNER= 1, TOP= 2, RIGHT= 3, LEFT= 4, NOT_DOCKED= 7; - int[] legendCoords= null; - - public void init() { - super.init(); + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4041111720696805018L; + protected int x_defunct = -1, y_defunct = -1, dx_defunct = -1, dy_defunct = -1; // these vars are now defunct; see doc above + Pos/getSVG for coordinate info + protected byte /*wType= -1, */wSpacing = -1; + protected short grbit = -1; + protected boolean fAutoPosition, fAutoSeries, fAutoPosX, fAutoPosY; + protected boolean fVert, fWasDataTable; + public static final int BOTTOM = 0, CORNER = 1, TOP = 2, RIGHT = 3, LEFT = 4, NOT_DOCKED = 7; + int[] legendCoords = null; + + public void init() { + super.init(); byte[] rkdata = this.getData(); - x_defunct= ByteTools.readInt(this.getBytesAt(0, 4)); - y_defunct= ByteTools.readInt(this.getBytesAt(4, 4)); - dx_defunct= ByteTools.readInt(this.getBytesAt(8, 4)); - dy_defunct= ByteTools.readInt(this.getBytesAt(12, 4)); + x_defunct = ByteTools.readInt(this.getBytesAt(0, 4)); + y_defunct = ByteTools.readInt(this.getBytesAt(4, 4)); + dx_defunct = ByteTools.readInt(this.getBytesAt(8, 4)); + dy_defunct = ByteTools.readInt(this.getBytesAt(12, 4)); // unused: wType= rkdata[16]; layout position is controled by CrtLayout12 record - wSpacing= rkdata[17]; - grbit= ByteTools.readShort(rkdata[18], rkdata[19]); - parseGrbit(); - } - /** - The following records and rules define the significant parts of a legend: - - The Legend record specifies the layout of the legend and specifies if the legend is automatically positioned. - The Pos record, CrtLayout12 record, specify the position of the legend. - The sequences of records that conform to the ATTACHEDLABEL (TextDisp ->Pos [FontX] [AlRuns] AI [FRAME] [ObjectLink] [DataLabExtContents] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] ) - and TEXTPROPS (RichTextStream|TextPropStream) rules specify the default text formatting for the legend entries. - The Pos record of the attached label MUST be ignored. The ObjectLink record of the attached label MUST NOT exist. - A series can specify formatting exceptions for individual legend entries. - The sequence of records that conforms to the FRAME (Frame ->LineFormat AreaFormat [GELFRAME] [SHAPEPROPS]) rule specifies the fill and border formatting properties of the legend. - */ - - protected void parseGrbit() { - byte[] grbytes = ByteTools.shortToLEBytes((short)grbit); - fAutoPosition= (((byte)grbytes[0] & 0x01) == 0x01); - fAutoSeries = (((byte)grbytes[0] & 0x02) == 0x02); - fAutoPosX = (((byte)grbytes[0] & 0x04) == 0x04); - fAutoPosY = (((byte)grbytes[0] & 0x08) == 0x08); - fVert = (((byte)grbytes[0] & 0x10) == 0x10); - fWasDataTable= (((byte)grbytes[0] & 0x20) == 0x20); - } - - - public void setIsDataTable(boolean isDataTable) { - fWasDataTable= isDataTable; - grbit= ByteTools.updateGrBit(grbit, fWasDataTable, 5); - updateRecord(); - } + wSpacing = rkdata[17]; + grbit = ByteTools.readShort(rkdata[18], rkdata[19]); + parseGrbit(); + } + + /** + * The following records and rules define the significant parts of a legend: + *

                + * The Legend record specifies the layout of the legend and specifies if the legend is automatically positioned. + * The Pos record, CrtLayout12 record, specify the position of the legend. + * The sequences of records that conform to the ATTACHEDLABEL (TextDisp ->Pos [FontX] [AlRuns] AI [FRAME] [ObjectLink] [DataLabExtContents] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] ) + * and TEXTPROPS (RichTextStream|TextPropStream) rules specify the default text formatting for the legend entries. + * The Pos record of the attached label MUST be ignored. The ObjectLink record of the attached label MUST NOT exist. + * A series can specify formatting exceptions for individual legend entries. + * The sequence of records that conforms to the FRAME (Frame ->LineFormat AreaFormat [GELFRAME] [SHAPEPROPS]) rule specifies the fill and border formatting properties of the legend. + */ + + protected void parseGrbit() { + byte[] grbytes = ByteTools.shortToLEBytes(grbit); + fAutoPosition = ((grbytes[0] & 0x01) == 0x01); + fAutoSeries = ((grbytes[0] & 0x02) == 0x02); + fAutoPosX = ((grbytes[0] & 0x04) == 0x04); + fAutoPosY = ((grbytes[0] & 0x08) == 0x08); + fVert = ((grbytes[0] & 0x10) == 0x10); + fWasDataTable = ((grbytes[0] & 0x20) == 0x20); + } + + + public void setIsDataTable(boolean isDataTable) { + fWasDataTable = isDataTable; + grbit = ByteTools.updateGrBit(grbit, fWasDataTable, 5); + updateRecord(); + } /* unused public void setwType(int type) { wType= (byte) type; updateRecord(); } */ - - public void setVertical(boolean isVertical) { - fVert= isVertical; - grbit= ByteTools.updateGrBit(grbit, fVert, 4); - updateRecord(); - } - - public static Legend createDefaultLegend(io.starter.formats.XLS.WorkBook book) { - Legend l= (Legend) Legend.getPrototype(); - Pos p= (Pos) Pos.getPrototype(Pos.TYPE_LEGEND); - l.chartArr.add(p); - TextDisp td= (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_TITLE, "", book); - l.chartArr.add(td); - return l; - } - - private void updateRecord() { - System.arraycopy(ByteTools.cLongToLEBytes(x_defunct), 0, this.getData(), 0, 4); - System.arraycopy(ByteTools.cLongToLEBytes(y_defunct), 0, this.getData(), 4, 4); - System.arraycopy(ByteTools.cLongToLEBytes(dx_defunct), 0, this.getData(), 8, 4); - System.arraycopy(ByteTools.cLongToLEBytes(dy_defunct), 0, this.getData(), 12, 4); - // unused this.getData()[16]= wType; - this.getData()[17]= wSpacing; + + public void setVertical(boolean isVertical) { + fVert = isVertical; + grbit = ByteTools.updateGrBit(grbit, fVert, 4); + updateRecord(); + } + + public static Legend createDefaultLegend(io.starter.formats.XLS.WorkBook book) { + Legend l = (Legend) Legend.getPrototype(); + Pos p = (Pos) Pos.getPrototype(Pos.TYPE_LEGEND); + l.chartArr.add(p); + TextDisp td = (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_TITLE, "", book); + l.chartArr.add(td); + return l; + } + + private void updateRecord() { + System.arraycopy(ByteTools.cLongToLEBytes(x_defunct), 0, this.getData(), 0, 4); + System.arraycopy(ByteTools.cLongToLEBytes(y_defunct), 0, this.getData(), 4, 4); + System.arraycopy(ByteTools.cLongToLEBytes(dx_defunct), 0, this.getData(), 8, 4); + System.arraycopy(ByteTools.cLongToLEBytes(dy_defunct), 0, this.getData(), 12, 4); + // unused this.getData()[16]= wType; + this.getData()[17] = wSpacing; byte[] b = ByteTools.shortToLEBytes(grbit); this.getData()[18] = b[0]; this.getData()[19] = b[1]; - } - - private byte[] PROTOTYPE_BYTES = new byte[] {-11, 13, 0, 0, -72, 3, 0, 0, -111, 1, 0, 0, -31, 4, 0, 0, 3, 1, 31, 0}; + } + + private byte[] PROTOTYPE_BYTES = new byte[]{-11, 13, 0, 0, -72, 3, 0, 0, -111, 1, 0, 0, -31, 4, 0, 0, 3, 1, 31, 0}; + public static XLSRecord getPrototype() { - Legend l= new Legend(); + Legend l = new Legend(); l.setOpcode(LEGEND); l.setData(l.PROTOTYPE_BYTES); l.init(); return l; } - - /** legend position: - * controlled by CrtLayout12 + + /** + * legend position: + * controlled by CrtLayout12 * 0= bottom, 1= corner, 2= top, 3= right, 4= left + * * @return */ public short getLegendPosition() { - CrtLayout12 crt= (CrtLayout12) Chart.findRec(this.chartArr, CrtLayout12.class); - if (crt!=null) - return (short)crt.getLayout(); - if (fVert || !fAutoPosition) - return RIGHT; // default - return BOTTOM; // default if not vert + CrtLayout12 crt = (CrtLayout12) Chart.findRec(this.chartArr, CrtLayout12.class); + if (crt != null) + return (short) crt.getLayout(); + if (fVert || !fAutoPosition) + return RIGHT; // default + return BOTTOM; // default if not vert } - + /** * return the legend position in string (OOXML) form + * * @return b, l, r, t, tr */ public String getLegendPositionString() { - int lpos= getLegendPosition(); - String[] pos= { "b", "tr", "t", "r", "l"}; - if (lpos >=0 && lpos < pos.length) - return pos[lpos]; - return "r"; + int lpos = getLegendPosition(); + String[] pos = {"b", "tr", "t", "r", "l"}; + if (lpos >= 0 && lpos < pos.length) + return pos[lpos]; + return "r"; } - + /** * set Legend Positon: one of: * 0= bottom, 1= corner, 2= top, 3= right, 4= left, 7= not docked + * * @param pos */ public void setLegendPosition(short pos) { - CrtLayout12 crt= (CrtLayout12) Chart.findRec(this.chartArr, CrtLayout12.class); - if (crt!=null) - crt.setLayout(pos); + CrtLayout12 crt = (CrtLayout12) Chart.findRec(this.chartArr, CrtLayout12.class); + if (crt != null) + crt.setLayout(pos); } /** * retrieves the specific font for these legends, if set (null if not) + * * @return */ private Fontx getLegendFont() { - TextDisp td= (TextDisp) Chart.findRec(chartArr, TextDisp.class); - if (td!=null) - return (Fontx) Chart.findRec(td.chartArr, Fontx.class); - return null; + TextDisp td = (TextDisp) Chart.findRec(chartArr, TextDisp.class); + if (td != null) + return (Fontx) Chart.findRec(td.chartArr, Fontx.class); + return null; } - + /** * returns true if this legend is surrounded by a box (the default) + * * @return */ public boolean hasBox() { - Frame f= (Frame) Chart.findRec(chartArr, Frame.class); - if (f!=null) { - return f.hasBox(); - } + Frame f = (Frame) Chart.findRec(chartArr, Frame.class); + if (f != null) { + return f.hasBox(); + } // return true; // the default - return false; + return false; } - + public void addBox() { - Frame f= (Frame) Chart.findRec(chartArr, Frame.class); - if (f==null) { - f= (Frame) Frame.getPrototype(); - f.addBox(0, -1, -1); - this.chartArr.add(f); - } + Frame f = (Frame) Chart.findRec(chartArr, Frame.class); + if (f == null) { + f = (Frame) Frame.getPrototype(); + f.addBox(0, -1, -1); + this.chartArr.add(f); + } } + /** * sets or turns off auto positioning * [BugTracker 2844] + * * @param auto */ public void setAutoPosition(boolean auto) { - if (auto && !fAutoPosition) { - // if setting to autosize/position and it wasn't currently set as so, - // check Pos and Frame records (if present) as they also controls automatic positioning ((: - if (this.chartArr.size()>0) { - try { - Pos p= (Pos)this.chartArr.get(0); - p.setAutosizeLegend(); - Frame f= (Frame) Chart.findRec(chartArr, Frame.class); // find the first one - if (f!=null) f.setAutosize(); - } catch (Exception e) { - } - } - } - fAutoPosition= auto; - fAutoSeries = auto; - fAutoPosX = auto; - fAutoPosY = auto; - //if (wType==3 || wType==4 && auto) - //fVert= true; - grbit= ByteTools.updateGrBit(grbit, fAutoPosition, 0); - grbit= ByteTools.updateGrBit(grbit, fAutoSeries, 1); - grbit= ByteTools.updateGrBit(grbit, fAutoPosX, 2); - grbit= ByteTools.updateGrBit(grbit, fAutoPosY, 3); - grbit= ByteTools.updateGrBit(grbit, fVert, 4); - this.updateRecord(); + if (auto && !fAutoPosition) { + // if setting to autosize/position and it wasn't currently set as so, + // check Pos and Frame records (if present) as they also controls automatic positioning ((: + if (this.chartArr.size() > 0) { + try { + Pos p = (Pos) this.chartArr.get(0); + p.setAutosizeLegend(); + Frame f = (Frame) Chart.findRec(chartArr, Frame.class); // find the first one + if (f != null) f.setAutosize(); + } catch (Exception e) { + } + } + } + fAutoPosition = auto; + fAutoSeries = auto; + fAutoPosX = auto; + fAutoPosY = auto; + //if (wType==3 || wType==4 && auto) + //fVert= true; + grbit = ByteTools.updateGrBit(grbit, fAutoPosition, 0); + grbit = ByteTools.updateGrBit(grbit, fAutoSeries, 1); + grbit = ByteTools.updateGrBit(grbit, fAutoPosX, 2); + grbit = ByteTools.updateGrBit(grbit, fAutoPosY, 3); + grbit = ByteTools.updateGrBit(grbit, fVert, 4); + this.updateRecord(); } - + /** - * a rough estimate of expanding legend dimensions of - * 1 normal entry - * + * a rough estimate of expanding legend dimensions of + * 1 normal entry */ public void incrementHeight(float h) { - Pos p= (Pos) Chart.findRec(this.chartArr, Pos.class); - int[] coords= p.getLegendCoords(); // x, y, w, h, fh, legendpos - Font f= this.getFnt(); - int fh= 10; // default - if (f!=null) - fh=(int)(f.getFontHeightInPoints()*1.2); // a little padding - if (coords!=null) { - int z= coords[1]-(int)Math.ceil(Pos.convertToSPRC(fh/2, 0, h)); - p.setY(z); - } + Pos p = (Pos) Chart.findRec(this.chartArr, Pos.class); + int[] coords = p.getLegendCoords(); // x, y, w, h, fh, legendpos + Font f = this.getFnt(); + int fh = 10; // default + if (f != null) + fh = (int) (f.getFontHeightInPoints() * 1.2); // a little padding + if (coords != null) { + int z = coords[1] - (int) Math.ceil(Pos.convertToSPRC(fh / 2, 0, h)); + p.setY(z); + } } /** * called up change of legend text to adjust width of the legend bounding box - * + * * @param chartMetrics - * @param chartType - * @param legends String[] text of legends (containing new legend text) + * @param chartType + * @param legends String[] text of legends (containing new legend text) */ public void adjustWidth(HashMap chartMetrics, int chartType, String[] legends) { - Pos p= (Pos) Chart.findRec(this.chartArr, Pos.class); - int[] coords= p.getLegendCoords(); // x, y, w, h, fh, legendpos - if (coords!=null) { - Font f= this.getFnt(); - // legend position LEFT and RIGHT display each legend on a separate line (fVert==true) - // TOP and BOTTOM are displayed horizontally with symbols and spacing between entries (fVert==false) - int position= this.getLegendPosition(); - float cw= chartMetrics.get("canvasw").floatValue(); - float x= (int)Math.ceil(Pos.convertFromSPRC(coords[0], cw, 0))-3; - float w= chartMetrics.get("w").floatValue(); - - // calculate how much width the legends take up -- algorithm works well for about 80% of the cases ... - double legendsWidth= 0; - java.awt.Font jf= new java.awt.Font(f.getFontName(), f.getFontWeight(), (int)f.getFontHeightInPoints()); - int extras= (chartType==ChartConstants.LINECHART|| chartType==ChartConstants.RADARCHART?15:5); // pad for legend symbols, etc - - for (int i= 0; i < legends.length; i++) { - if (fVert) - legendsWidth= Math.max(legendsWidth, StringTool.getApproximateStringWidth(jf, " " + legends[i] + " ")); - else - legendsWidth+= StringTool.getApproximateStringWidth(jf, " " + legends[i] + " ") + extras; - } - if (!fVert) - legendsWidth-= StringTool.getApproximateStringWidth(jf, " "); // decrement one space - else - legendsWidth += StringTool.getApproximateStringWidth(jf, " "); - + Pos p = (Pos) Chart.findRec(this.chartArr, Pos.class); + int[] coords = p.getLegendCoords(); // x, y, w, h, fh, legendpos + if (coords != null) { + Font f = this.getFnt(); + // legend position LEFT and RIGHT display each legend on a separate line (fVert==true) + // TOP and BOTTOM are displayed horizontally with symbols and spacing between entries (fVert==false) + int position = this.getLegendPosition(); + float cw = chartMetrics.get("canvasw").floatValue(); + float x = (int) Math.ceil(Pos.convertFromSPRC(coords[0], cw, 0)) - 3; + float w = chartMetrics.get("w").floatValue(); + + // calculate how much width the legends take up -- algorithm works well for about 80% of the cases ... + double legendsWidth = 0; + java.awt.Font jf = new java.awt.Font(f.getFontName(), f.getFontWeight(), (int) f.getFontHeightInPoints()); + int extras = (chartType == ChartConstants.LINECHART || chartType == ChartConstants.RADARCHART ? 15 : 5); // pad for legend symbols, etc - + for (int i = 0; i < legends.length; i++) { + if (fVert) + legendsWidth = Math.max(legendsWidth, StringTool.getApproximateStringWidth(jf, " " + legends[i] + " ")); + else + legendsWidth += StringTool.getApproximateStringWidth(jf, " " + legends[i] + " ") + extras; + } + if (!fVert) + legendsWidth -= StringTool.getApproximateStringWidth(jf, " "); // decrement one space + else + legendsWidth += StringTool.getApproximateStringWidth(jf, " "); + // io.starter.toolkit.Logger.log(this.getParentChart().toString() + String.format(": legend box x: %.1f legend box w: %.0f chart x: %.1f w: %.1f cw: %.1f font size: %.0f L.W: %.1f Auto? %b Vertical? %b", // x, (float)coords[2], chartMetrics.get("x"), w, cw, (float) jf.getSize(), legendsWidth, fAutoPosition, fVert)); - p.setLegendW((int)legendsWidth); - if (((x + legendsWidth) > cw) || (position==Legend.RIGHT || position==Legend.CORNER)) { - x= (float)(cw-(legendsWidth+5)); - if (x < 0) x= 0; - int z= (int)Math.ceil(Pos.convertToSPRC(x, cw, 0)); - p.setX(z); - } + p.setLegendW((int) legendsWidth); + if (((x + legendsWidth) > cw) || (position == Legend.RIGHT || position == Legend.CORNER)) { + x = (float) (cw - (legendsWidth + 5)); + if (x < 0) x = 0; + int z = (int) Math.ceil(Pos.convertToSPRC(x, cw, 0)); + p.setX(z); + } @@ -361,234 +366,239 @@ else if (position==Legend.LEFT) { if (overage > 0) chartMetrics.put("canvasw", chartMetrics.get("canvasw")+overage);* / }*/ - } + } } - + /** - * reset initial position of legend to accommodate nLines of legend text + * reset initial position of legend to accommodate nLines of legend text */ public void resetPos(double y, double h, double ch, int nLines) { - Pos p= (Pos) Chart.findRec(this.chartArr, Pos.class); + Pos p = (Pos) Chart.findRec(this.chartArr, Pos.class); /* apparently just setting to 1/2 h works well!! * Font f= this.getFnt(); int fh= 10; // default if (f!=null) fh=(int)(f.getFontHeightInPoints()); fh=(int)(f.getFontHeightInPoints()*1.2); // a little padding*/ - int z= (int)Math.ceil(Pos.convertToSPRC((float)(ch/2/*+((fh*nLines)/2)*/), 0, (float)ch)); - p.setY(z); - + int z = (int) Math.ceil(Pos.convertToSPRC((float) (ch / 2/*+((fh*nLines)/2)*/), 0, (float) ch)); + p.setY(z); + } - + /** * return the coordinates of the legend box in pixels *
                An approximation at this point + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param fh -- font height in points - * @return int[4] x, y, w, h + * @param fh -- font height in points + * @return int[4] x, y, w, h */ - public int[] getCoords(int charttype, HashMap chartMetrics, String[] legends, java.awt.Font f) { - // calcs are not 100% **** - // space between legend entries = 40 twips = 1 twip equals one-twentieth of a printer's point - Pos p= (Pos) Chart.findRec(this.chartArr, Pos.class); - int[] coords= p.getLegendCoords(); - int[] retcoords= new int[6]; - int fh=(int)(f.getSize()); //*1.2); // a little padding - retcoords[4]= f.getSize(); // store font height - retcoords[5]= this.getLegendPosition(); // store legend position - - boolean canMoveCW= false; - if (coords!=null) { - retcoords[0]= (int)Math.ceil(Pos.convertFromSPRC(coords[0], chartMetrics.get("canvasw").floatValue(), 0))-3; - retcoords[1]= (int)Math.ceil(Pos.convertFromSPRC(coords[1], 0, chartMetrics.get("canvash").floatValue())); - } else { // happens upon OOXML - retcoords[0]= (int)(chartMetrics.get("w") + chartMetrics.get("x") + 20); // start just after right side of plot - retcoords[1]= chartMetrics.get("y").intValue() + (int)(chartMetrics.get("h")/4); - coords= new int[4]; - canMoveCW= true; - } - if (coords[2]!=0) { - retcoords[2]= (int)(coords[2]*MSODrawing.PIXELCONVERSION); - retcoords[2]+=3; // pad slightly - } else { - double len= 0; - for (int i= 0; i < legends.length; i++) { - len= Math.max(len, StringTool.getApproximateStringWidth(f, legends[i])); - } - retcoords[2]= (int)Math.ceil(len); - retcoords[2]+=15 + (charttype==ChartConstants.LINECHART|| charttype==ChartConstants.RADARCHART?25:5); // pad for legend symbols, etc - - // if now legend box extends over edge reduce plot area width, not canvas width ... EXCEPT for OOXML; in those cases, extend CW - if (!canMoveCW && ((retcoords[0]+retcoords[2]) > chartMetrics.get("canvasw").floatValue())) { - double cw= chartMetrics.get("canvasw"); - double w= chartMetrics.get("w"); + public int[] getCoords(int charttype, HashMap chartMetrics, String[] legends, java.awt.Font f) { + // calcs are not 100% **** + // space between legend entries = 40 twips = 1 twip equals one-twentieth of a printer's point + Pos p = (Pos) Chart.findRec(this.chartArr, Pos.class); + int[] coords = p.getLegendCoords(); + int[] retcoords = new int[6]; + int fh = f.getSize(); //*1.2); // a little padding + retcoords[4] = f.getSize(); // store font height + retcoords[5] = this.getLegendPosition(); // store legend position + + boolean canMoveCW = false; + if (coords != null) { + retcoords[0] = (int) Math.ceil(Pos.convertFromSPRC(coords[0], chartMetrics.get("canvasw").floatValue(), 0)) - 3; + retcoords[1] = (int) Math.ceil(Pos.convertFromSPRC(coords[1], 0, chartMetrics.get("canvash").floatValue())); + } else { // happens upon OOXML + retcoords[0] = (int) (chartMetrics.get("w") + chartMetrics.get("x") + 20); // start just after right side of plot + retcoords[1] = chartMetrics.get("y").intValue() + (int) (chartMetrics.get("h") / 4); + coords = new int[4]; + canMoveCW = true; + } + if (coords[2] != 0) { + retcoords[2] = (int) (coords[2] * MSODrawing.PIXELCONVERSION); + retcoords[2] += 3; // pad slightly + } else { + double len = 0; + for (int i = 0; i < legends.length; i++) { + len = Math.max(len, StringTool.getApproximateStringWidth(f, legends[i])); + } + retcoords[2] = (int) Math.ceil(len); + retcoords[2] += 15 + (charttype == ChartConstants.LINECHART || charttype == ChartConstants.RADARCHART ? 25 : 5); // pad for legend symbols, etc - + // if now legend box extends over edge reduce plot area width, not canvas width ... EXCEPT for OOXML; in those cases, extend CW + if (!canMoveCW && ((retcoords[0] + retcoords[2]) > chartMetrics.get("canvasw").floatValue())) { + double cw = chartMetrics.get("canvasw"); + double w = chartMetrics.get("w"); // KSC: TESTING //io.starter.toolkit.Logger.log("Original Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); - double ldist= retcoords[0]-w; // original distance between legend and edge of plot area - retcoords[0]= (int)Math.ceil(cw-retcoords[2]); - if (ldist > 0 && (chartMetrics.get("w")+chartMetrics.get("x")) > retcoords[0]) - chartMetrics.put("w", retcoords[0]-ldist); + double ldist = retcoords[0] - w; // original distance between legend and edge of plot area + retcoords[0] = (int) Math.ceil(cw - retcoords[2]); + if (ldist > 0 && (chartMetrics.get("w") + chartMetrics.get("x")) > retcoords[0]) + chartMetrics.put("w", retcoords[0] - ldist); //io.starter.toolkit.Logger.log("After Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); - } - - } - if (canMoveCW) { - float overage= ((retcoords[0]+retcoords[2]+10)-(chartMetrics.get("canvasw").floatValue())); - if (overage > 0) - chartMetrics.put("canvasw", chartMetrics.get("canvasw")+overage); - } - if (coords[3]!=0) - retcoords[3]= (int)(coords[3]*MSODrawing.PIXELCONVERSION); - else - retcoords[3]= ((legends.length+2)*(fh+2)); - return retcoords; - } + } - /** - * tries to get the best match - * @return - */ - public Font getFnt() { - try { - Fontx fx= getLegendFont(); - Font f= this.getParentChart().getWorkBook().getFont(fx.getIfnt()); - if (f!=null) - return f; - else // shouldn't get here ... - return this.getParentChart().getDefaultFont(); - } catch (NullPointerException e) { - // this actually doesn't get the actual font for the legend but can't find correct Fontx record! - return this.getParentChart().getDefaultFont(); - } - } - - - public void getMetrics(HashMap chartMetrics, int chartType, ChartSeries s) { - String[] legends= s.getLegends(); - if (legends==null || legends.length==0) return; - - Font f= getFnt(); - if (f!=null) { - legendCoords= this.getCoords(chartType, chartMetrics, legends, new java.awt.Font(f.getFontName(), f.getFontWeight(), (int)f.getFontHeightInPoints())); - } else { // can't find any font ... shouldn't really happen ...? - legendCoords= this.getCoords(chartType, chartMetrics, legends, new java.awt.Font("Arial", 400, 10)); - } - } - - /** - * return the coordinates of the legend box, relative to the chart - * @return int[] coordinates x, y, w, h [fh, legendpos] - */ - public int[] getCoords(){ - return legendCoords; - } - - - /** - * returns the Data Legend Box svg for this chart + } + if (canMoveCW) { + float overage = ((retcoords[0] + retcoords[2] + 10) - (chartMetrics.get("canvasw").floatValue())); + if (overage > 0) + chartMetrics.put("canvasw", chartMetrics.get("canvasw") + overage); + } + if (coords[3] != 0) + retcoords[3] = (int) (coords[3] * MSODrawing.PIXELCONVERSION); + else + retcoords[3] = ((legends.length + 2) * (fh + 2)); + return retcoords; + } + + /** + * tries to get the best match + * + * @return + */ + public Font getFnt() { + try { + Fontx fx = getLegendFont(); + Font f = this.getParentChart().getWorkBook().getFont(fx.getIfnt()); + if (f != null) + return f; + else // shouldn't get here ... + return this.getParentChart().getDefaultFont(); + } catch (NullPointerException e) { + // this actually doesn't get the actual font for the legend but can't find correct Fontx record! + return this.getParentChart().getDefaultFont(); + } + } + + + public void getMetrics(HashMap chartMetrics, int chartType, ChartSeries s) { + String[] legends = s.getLegends(); + if (legends == null || legends.length == 0) return; + + Font f = getFnt(); + if (f != null) { + legendCoords = this.getCoords(chartType, chartMetrics, legends, new java.awt.Font(f.getFontName(), f.getFontWeight(), (int) f.getFontHeightInPoints())); + } else { // can't find any font ... shouldn't really happen ...? + legendCoords = this.getCoords(chartType, chartMetrics, legends, new java.awt.Font("Arial", 400, 10)); + } + } + + /** + * return the coordinates of the legend box, relative to the chart + * + * @return int[] coordinates x, y, w, h [fh, legendpos] + */ + public int[] getCoords() { + return legendCoords; + } + + + /** + * returns the Data Legend Box svg for this chart + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @return - */ - int XOFFSET= 12; - public String getSVG(HashMap chartMetrics, ChartType chartobj, ChartSeries s) { - StringBuffer svg= new StringBuffer(); - // position information fro Pos record: - /** - * legend MDCHART MDABS The values x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, - relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 specify the - width and height of the legend, in points. - legend MDCHART MDPARENT The values of x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, - relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 MUST be ignored. - The size of the legend is determined by the application. - legend MDKTH MDPARENT The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table. - - */ - - String[] legends= s.getLegends(); - String[] seriescolors= s.getSeriesBarColors(); - if (legends==null || legends.length==0) return ""; - - if (legendCoords==null) - this.getMetrics(chartMetrics, chartobj.getChartType(), s); - String font; // font svg - int fh; // font height - Font f= getFnt(); - if (f!=null) { - font= f.getSVG(); - font= "' " + /*"' vertical-align='bottom' " +*/ font; - fh= (int)Math.ceil(f.getFontHeightInPoints()); - } else { // can't find any font ... shouldn't really happen ...? - font= "' " + "font-family='Arial' font-size='9pt'"; - fh= 10; - } - // get legend info in order to get dimensions - final int YOFFSET= legendCoords[3]/(legends.length); - - int x= legendCoords[0]; - int y= legendCoords[1]; - int boxw= legendCoords[2]; - int boxh= legendCoords[3]; - - svg.append("\r\n"); - if (this.hasBox()) { - svg.append(""); - x+=5; // start of labels (offset from box) - y+=(YOFFSET/3); - } - if (chartobj.getChartType()==ChartConstants.BARCHART) { // same as below except order is reversed - // draw a little box in appropriate color - int h= 8; // box size - for (int i= legends.length-1; i >= 0; i--) { - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - if (markers[i]>0) - svg.append(MarkerFormat.getMarkerSVG(x+w/2-5, y+fh/2, seriescolors[i], markers[i])); - - svg.append("\r\n"); + if (this.hasBox()) { + svg.append(""); + x += 5; // start of labels (offset from box) + y += (YOFFSET / 3); + } + if (chartobj.getChartType() == ChartConstants.BARCHART) { // same as below except order is reversed + // draw a little box in appropriate color + int h = 8; // box size + for (int i = legends.length - 1; i >= 0; i--) { + svg.append(""); + svg.append(""); + svg.append(""); + svg.append(""); + if (markers[i] > 0) + svg.append(MarkerFormat.getMarkerSVG(x + w / 2 - 5, y + fh / 2, seriescolors[i], markers[i])); + + svg.append(". @@ -25,54 +25,62 @@ import io.starter.formats.XLS.WorkBook; public class Line3DChart extends LineChart { - public Line3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - line= (Line) charttype; - } - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); + public Line3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + line = (Line) charttype; + } - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - // vary colors??? + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); - - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - return cooxml; - } + if (this.is100PercentStacked()) + cooxml.append("percentStacked"); + else if (this.isStacked()) + cooxml.append("stacked"); + // } else if (this.isClustered()) + // grouping="clustered"; + else + cooxml.append("standard"); + cooxml.append("\"/>"); + cooxml.append("\r\n"); + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + //dropLines + ChartLine cl = this.cf.getChartLinesRec(ChartLine.TYPE_DROPLINE); + if (cl != null) + cooxml.append(cl.getOOXML()); + // gapDepth + int gapdepth = this.getGapDepth(); + if (gapdepth != 0) + cooxml.append(""); + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/LineChart.java b/src/main/java/io/starter/formats/XLS/charts/LineChart.java index a6ff3b5..e7287a3 100644 --- a/src/main/java/io/starter/formats/XLS/charts/LineChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/LineChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,35 +22,29 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Stack; - -import org.json.JSONArray; +import io.starter.formats.XLS.WorkBook; +import io.starter.toolkit.Logger; import org.json.JSONException; import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartSeriesHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.Marker; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.HashMap; public class LineChart extends ChartType { - Line line= null; - public LineChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - line= (Line) charttype; - } - /** - * return the type JSON for this Chart Object - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON= new JSONObject(); + Line line = null; + + public LineChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + line = (Line) charttype; + } + + /** + * return the type JSON for this Chart Object + * + * @return + */ + public JSONObject getTypeJSON() throws JSONException { + JSONObject typeJSON = new JSONObject(); /* String dojoType; if (!this.isStacked()) { dojoType="Default"; @@ -77,143 +71,150 @@ public JSONObject getTypeJSON() throws JSONException { } } typeJSON.put("type", dojoType);*/ - return typeJSON; - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + return typeJSON; + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double x= chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - if (series.size()==0) { - Logger.logErr("Line.getSVG: error in series"); - return ""; - } - - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there - // y value for each point= h/YMAX - StringBuffer svg= new StringBuffer(); - // get data labels, marker formats, series colors - int n= series.size(); - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - int[] markers= getMarkerFormats(); - double xfactor= 0, yfactor= 0; // - if (categories.length!=0) - xfactor= w/(categories.length); // w/#categories - else - xfactor= w; - if (max!=0) - yfactor= h/max; // h/YMAXSCALE - svg.append("\r\n"); - // define marker shapes for later use - svg.append(MarkerFormat.getMarkerSVGDefs()); - // for each series - for (int i= 0; i < n; i++) { - // each visible line on the chart consists of two lines- 1 black, 1 color - String points= ""; - String labels= ""; - double[] curseries= (double[])series.get(i); - String[] curranges= (String[])s.getSeriesRanges().get(i); - for (int j= 0; j < curseries.length; j++) { - points+= ((x)+(j+.5)*xfactor) + "," + ((y+h)-(curseries[j]*yfactor)); - points+=" "; - String l= getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); - if (l!=null) { - double xx= (2+(x)+(j+.5)*xfactor); - if (markers[i] > 0) xx+=10; // scoot over for markers - labels+= "" + l + "\r\n"; - } - } - // 1st line is black - svg.append("\r\n"); - // 2nd line is the series color - svg.append("\r\n"); - // Markers, if any, along data points in series - if (markers[i] > 0) { - String[] markerpoints= points.split(" "); - for (int j= 0; j < markerpoints.length; j++) { - String markerpoint= markerpoints[j]; - String[] xy= markerpoint.split(","); - double xx= Double.valueOf(xy[0]); - double yy= Double.valueOf(xy[1]); - svg.append(MarkerFormat.getMarkerSVG(xx, yy, seriescolors[i], markers[i])+ "\r\n"); - } - } - // data labels, if any, after lines and markers - svg.append(labels); - } - - svg.append("\r\n"); - return svg.toString(); - } - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH dlbls - //cooxml.append(getDataLabelsOOXML(cf)); - - //dropLines - ChartLine cl= this.cf.getChartLinesRec(ChartLine.TYPE_DROPLINE); - if (cl!=null) - cooxml.append(cl.getOOXML()); - // hiLowLines - cl= this.cf.getChartLinesRec(ChartLine.TYPE_HILOWLINE); - if (cl!=null) - cooxml.append(cl.getOOXML()); - // upDownBars - cooxml.append(cf.getUpDownBarOOXML()); - // marker - // smooth - - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - - cooxml.append(""); cooxml.append("\r\n"); - - return cooxml; - } + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + if (series.size() == 0) { + Logger.logErr("Line.getSVG: error in series"); + return ""; + } + + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there + // y value for each point= h/YMAX + StringBuffer svg = new StringBuffer(); + // get data labels, marker formats, series colors + int n = series.size(); + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + int[] markers = getMarkerFormats(); + double xfactor = 0, yfactor = 0; // + if (categories.length != 0) + xfactor = w / (categories.length); // w/#categories + else + xfactor = w; + if (max != 0) + yfactor = h / max; // h/YMAXSCALE + svg.append("\r\n"); + // define marker shapes for later use + svg.append(MarkerFormat.getMarkerSVGDefs()); + // for each series + for (int i = 0; i < n; i++) { + // each visible line on the chart consists of two lines- 1 black, 1 color + String points = ""; + String labels = ""; + double[] curseries = (double[]) series.get(i); + String[] curranges = (String[]) s.getSeriesRanges().get(i); + for (int j = 0; j < curseries.length; j++) { + points += ((x) + (j + .5) * xfactor) + "," + ((y + h) - (curseries[j] * yfactor)); + points += " "; + String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); + if (l != null) { + double xx = (2 + (x) + (j + .5) * xfactor); + if (markers[i] > 0) xx += 10; // scoot over for markers + labels += "" + l + "\r\n"; + } + } + // 1st line is black + svg.append("\r\n"); + // 2nd line is the series color + svg.append("\r\n"); + // Markers, if any, along data points in series + if (markers[i] > 0) { + String[] markerpoints = points.split(" "); + for (int j = 0; j < markerpoints.length; j++) { + String markerpoint = markerpoints[j]; + String[] xy = markerpoint.split(","); + double xx = Double.valueOf(xy[0]); + double yy = Double.valueOf(xy[1]); + svg.append(MarkerFormat.getMarkerSVG(xx, yy, seriescolors[i], markers[i]) + "\r\n"); + } + } + // data labels, if any, after lines and markers + svg.append(labels); + } + + svg.append("\r\n"); + return svg.toString(); + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + //TODO: FINISH dlbls + //cooxml.append(getDataLabelsOOXML(cf)); + + //dropLines + ChartLine cl = this.cf.getChartLinesRec(ChartLine.TYPE_DROPLINE); + if (cl != null) + cooxml.append(cl.getOOXML()); + // hiLowLines + cl = this.cf.getChartLinesRec(ChartLine.TYPE_HILOWLINE); + if (cl != null) + cooxml.append(cl.getOOXML()); + // upDownBars + cooxml.append(cf.getUpDownBarOOXML()); + // marker + // smooth + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/LineFormat.java b/src/main/java/io/starter/formats/XLS/charts/LineFormat.java index e898ab6..9a9daf2 100644 --- a/src/main/java/io/starter/formats/XLS/charts/LineFormat.java +++ b/src/main/java/io/starter/formats/XLS/charts/LineFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,70 +25,67 @@ import io.starter.OpenXLS.FormatHandle; import io.starter.formats.OOXML.SpPr; -import io.starter.formats.OOXML.Ln; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * LineFormat: Style of a Line or border(0x1007) - * + *

                * 4 rgb 4 Color of line: high byte must be 0 * 8 lnx 2 Pattern of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - * When the value of this field is 0x0005 (None), the values of we and icv MUST be set to: Line thickness (we)= 0xFFFF (Hairline) Line color (icv) 0x004D + * When the value of this field is 0x0005 (None), the values of we and icv MUST be set to: Line thickness (we)= 0xFFFF (Hairline) Line color (icv) 0x004D * 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) * 12 grbit 2 flags * 14 icv Index to color of line. - * An Icv that specifies a color from the chart color table. This value MUST be greater than or equal to 0x0000 and less than or equal to 0x0041, - * or greater than or equal to 0x004D and less than or equal to 0x00004F. This value SHOULD NOT be less than 0x0008. - * 0x0040 == Default foreground color. This is the window text color in the sheet display. - * 0x0041 == Default background color. This is the window background color in the sheet display and is the default background color for a cell. - * 0x004D == Default chart foreground color. This is the window text color in the chart display. - * - * grbit: - * 0 0x1 fAuto Automatic format - * 1 reserved, 0 - * 2 0x4 fAxisOn specifies whether axis line is displayed - * If the previous record is AxisLine and the value of the id field of the AxisLine record is equal to 0x0000, this field MUST be a value from the following table: - fAxisOn Lns Meaning - 0 0x0005 The axis line is not displayed. - 0 Any legal value except 0x0005 The axis line is displayed. - 1 Any legal value The axis line is displayed. - If the previous record is not AxisLine and the value of the id field of the AxisLine record is equal to 0x0000, this field MUST be zero, and MUST be ignored. - 3 0x8 fAutoColor specifies whether icv= 0x4D. if 1, icv must= 0x4D. - - - + * An Icv that specifies a color from the chart color table. This value MUST be greater than or equal to 0x0000 and less than or equal to 0x0041, + * or greater than or equal to 0x004D and less than or equal to 0x00004F. This value SHOULD NOT be less than 0x0008. + * 0x0040 == Default foreground color. This is the window text color in the sheet display. + * 0x0041 == Default background color. This is the window background color in the sheet display and is the default background color for a cell. + * 0x004D == Default chart foreground color. This is the window text color in the chart display. + *

                + * grbit: + * 0 0x1 fAuto Automatic format + * 1 reserved, 0 + * 2 0x4 fAxisOn specifies whether axis line is displayed + * If the previous record is AxisLine and the value of the id field of the AxisLine record is equal to 0x0000, this field MUST be a value from the following table: + * fAxisOn Lns Meaning + * 0 0x0005 The axis line is not displayed. + * 0 Any legal value except 0x0005 The axis line is displayed. + * 1 Any legal value The axis line is displayed. + * If the previous record is not AxisLine and the value of the id field of the AxisLine record is equal to 0x0000, this field MUST be zero, and MUST be ignored. + * 3 0x8 fAutoColor specifies whether icv= 0x4D. if 1, icv must= 0x4D. */ -public class LineFormat extends GenericChartObject implements ChartObject{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3051781109844837056L; - private java.awt.Color rgb; - private short lnx= 0; - private short we= 0; - private short grbit= 0; - private short icv= 0; - private SpPr sppr= null; - public static final int SOLID= 0; - public static final int DASH= 1; - public static final int DOT= 2; - public static final int DASHDOT= 3; - public static final int DASHDASHDOT= 4; - public static final int NONE= 5; - public static final int DKGRAY= 6; - public static final int MEDGRAY= 7; - public static final int LTGRAY= 8; - - public void init() { - super.init(); - byte[] data= this.getData(); - rgb= new java.awt.Color((data[0]<0?255+data[0]:data[0]), (data[1]<0?255+data[1]:data[1]), (data[2]<0?255+data[2]:data[2])); - lnx= ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - we= ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - grbit= ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - icv= ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - } +public class LineFormat extends GenericChartObject implements ChartObject { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 3051781109844837056L; + private java.awt.Color rgb; + private short lnx = 0; + private short we = 0; + private short grbit = 0; + private short icv = 0; + private SpPr sppr = null; + public static final int SOLID = 0; + public static final int DASH = 1; + public static final int DOT = 2; + public static final int DASHDOT = 3; + public static final int DASHDASHDOT = 4; + public static final int NONE = 5; + public static final int DKGRAY = 6; + public static final int MEDGRAY = 7; + public static final int LTGRAY = 8; + + public void init() { + super.init(); + byte[] data = this.getData(); + rgb = new java.awt.Color((data[0] < 0 ? 255 + data[0] : data[0]), (data[1] < 0 ? 255 + data[1] : data[1]), (data[2] < 0 ? 255 + data[2] : data[2])); + lnx = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + we = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + grbit = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + icv = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); + } + public static XLSRecord getPrototype() { LineFormat lf = new LineFormat(); lf.setOpcode(LINEFORMAT); @@ -96,17 +93,18 @@ public static XLSRecord getPrototype() { lf.init(); return lf; } - - private byte[] PROTOTYPE_BYTES = new byte[] {0,0,0,0,0,0,-1,-1,9,0,77,0}; // no line default + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, -1, -1, 9, 0, 77, 0}; // no line default // TODO: Figure this out!! - private byte[] PROTOTYPE_BYTES_1 = new byte[] {-128, -128, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + private byte[] PROTOTYPE_BYTES_1 = new byte[]{-128, -128, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + /** * get new Line Format in desired pattern and weight *
                pattern= Pattern of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray *
                Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) *
                note color is set to default - - * @return new LineFormat record + * + * @return new LineFormat record */ public static XLSRecord getPrototype(int style, int weight) { LineFormat lf = new LineFormat(); @@ -119,72 +117,77 @@ public static XLSRecord getPrototype(int style, int weight) { } /** - * 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) - */ + * 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) + */ public void setLineWeight(int weight) { - we= (short) weight; - updateRecord(); + we = (short) weight; + updateRecord(); } - + /** * Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + * * @param style */ public void setLineStyle(int style) { - lnx= (short) style; - // When the value of this field is 0x0005 (None), the values of we and icv MUST be set to: Line thickness (we)= 0xFFFF (Hairline) Line color (icv) 0x004D - //* 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) - if (lnx==5) { - we= -1; - grbit= 0x8; // auto color - setLineColor(0x4D); - } - updateRecord(); + lnx = (short) style; + // When the value of this field is 0x0005 (None), the values of we and icv MUST be set to: Line thickness (we)= 0xFFFF (Hairline) Line color (icv) 0x004D + //* 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) + if (lnx == 5) { + we = -1; + grbit = 0x8; // auto color + setLineColor(0x4D); + } + updateRecord(); } /** * return Pattern of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray */ - public int getLineStyle() { return lnx; } - - + public int getLineStyle() { + return lnx; + } + + /** * Index to color of line + * * @param clr */ public void setLineColor(int clr) { - if (clr > -1 && clr < this.getColorTable().length) { - icv= (short) clr; - rgb= this.getColorTable()[clr]; - updateRecord(); - } else if (clr==0x4D) { // special flag, default fg - icv= (short) clr; - rgb= this.getColorTable()[0]; - updateRecord(); - } - // TOOD: finish - if (sppr!=null) { + if (clr > -1 && clr < this.getColorTable().length) { + icv = (short) clr; + rgb = this.getColorTable()[clr]; + updateRecord(); + } else if (clr == 0x4D) { // special flag, default fg + icv = (short) clr; + rgb = this.getColorTable()[0]; + updateRecord(); + } + // TOOD: finish + if (sppr != null) { // sppr.setLine(we, clr); - } + } } - + /** * return the line color as a hex String + * * @return */ public String getLineColor() { - return FormatHandle.colorToHexString(rgb); + return FormatHandle.colorToHexString(rgb); } - + /** * update the underlying data */ private void updateRecord() { - byte[] b = new byte[4]; - b[0]= (byte) rgb.getRed(); - b[1]= (byte) rgb.getGreen(); - b[2]= (byte) rgb.getBlue(); - b[3]= 0; // reserved/0 + byte[] b = new byte[4]; + b[0] = (byte) rgb.getRed(); + b[1] = (byte) rgb.getGreen(); + b[2] = (byte) rgb.getBlue(); + b[3] = 0; // reserved/0 System.arraycopy(b, 0, this.getData(), 0, 4); b = ByteTools.shortToLEBytes(lnx); this.getData()[4] = b[0]; @@ -199,75 +202,79 @@ private void updateRecord() { this.getData()[10] = b[0]; this.getData()[11] = b[1]; } - - public String toString() { return "LineFormat: LinePattern=" + lnx + " Weight=" + we + " Draw Ticks=" + ((grbit & 0x4)==0x4); } - + + public String toString() { + return "LineFormat: LinePattern=" + lnx + " Weight=" + we + " Draw Ticks=" + ((grbit & 0x4) == 0x4); + } + public String getSVG() { - if (lnx==5) - return ""; // no line - float sz= 1f; - if (we==-1) // hairline - sz= 1f; - else if (we==0) // narrow - rest are just guesses really - sz= 2f; - else if (we==1) // medium - sz= 4f; - else if (we== 2) // wide - sz= 6f; - String clr= ChartType.getMediumColor(); - if (lnx==DKGRAY) // dark grey pattern - clr= ChartType.getDarkColor(); - else if (lnx==MEDGRAY) // medium grey pattern - clr= ChartType.getMediumColor(); - else if (lnx==LTGRAY) // light grey pattern - clr= ChartType.getLightColor(); - String style= ""; - if (lnx==DASH) { - style= " style='stroke-dasharray: 9, 5;' "; - } else if (lnx==DOT) { - style= " style='stroke-dasharray:2, 2;' "; - } else if (lnx==DASHDOT) { - style= " style='stroke-dasharray: 3, 2, 9, 2;' "; - } else if (lnx==DASHDASHDOT) { - style= " style='stroke-dasharray: 9, 5, 9, 5, 3, 2;' "; - } - return " stroke='"+clr+"' stroke-opacity='1' stroke-width='"+sz+"' " + style + "stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='4'"; + if (lnx == 5) + return ""; // no line + float sz = 1f; + if (we == -1) // hairline + sz = 1f; + else if (we == 0) // narrow - rest are just guesses really + sz = 2f; + else if (we == 1) // medium + sz = 4f; + else if (we == 2) // wide + sz = 6f; + String clr = ChartType.getMediumColor(); + if (lnx == DKGRAY) // dark grey pattern + clr = ChartType.getDarkColor(); + else if (lnx == MEDGRAY) // medium grey pattern + clr = ChartType.getMediumColor(); + else if (lnx == LTGRAY) // light grey pattern + clr = ChartType.getLightColor(); + String style = ""; + if (lnx == DASH) { + style = " style='stroke-dasharray: 9, 5;' "; + } else if (lnx == DOT) { + style = " style='stroke-dasharray:2, 2;' "; + } else if (lnx == DASHDOT) { + style = " style='stroke-dasharray: 3, 2, 9, 2;' "; + } else if (lnx == DASHDASHDOT) { + style = " style='stroke-dasharray: 9, 5, 9, 5, 3, 2;' "; + } + return " stroke='" + clr + "' stroke-opacity='1' stroke-width='" + sz + "' " + style + "stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='4'"; } - + /** * if have 2007+v settings, use them. Otherwise, interpret line settings to OOXML + * * @return */ public String getOOXML() { - // TODO: if changed lineformat info, change in sp - if (sppr!=null) { - return sppr.getOOXML(); - } - - if (!parentChart.getWorkBook().getIsExcel2007()) { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); - // TODO: line styles + weight - ooxml.append(""); - ooxml.append(""); - ooxml.append(""); - ooxml.append(""); - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - return ""; + // TODO: if changed lineformat info, change in sp + if (sppr != null) { + return sppr.getOOXML(); + } + + if (!parentChart.getWorkBook().getIsExcel2007()) { + StringBuffer ooxml = new StringBuffer(); + ooxml.append(""); + // TODO: line styles + weight + ooxml.append(""); + ooxml.append(""); + ooxml.append(""); + ooxml.append(""); + ooxml.append(""); + ooxml.append(""); + return ooxml.toString(); + } + return ""; } /** * sets the OOXML settings for this line + * * @param sp */ public void setFromOOXML(SpPr sp) { - this.sppr= sp; - int lw= sp.getLineWidth(); - this.setLineWeight(lw); // sp lw in emus. 1 pt= 12700 emus. - this.setLineColor(sp.getLineColor()); - this.setLineStyle(sp.getLineStyle()); - } + this.sppr = sp; + int lw = sp.getLineWidth(); + this.setLineWeight(lw); // sp lw in emus. 1 pt= 12700 emus. + this.setLineColor(sp.getLineColor()); + this.setLineStyle(sp.getLineStyle()); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.java b/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.java index 34a2a98..c01b553 100644 --- a/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.java +++ b/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,167 +24,187 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * MarkerFormat: Style of a Line Marker(0x1009) - * - * The MarkerFormat record specifies the color, size, and shape of the associated data markers that appear on line, - * radar, and scatter chart groups. The associated data markers are specified by the preceding DataFormat record. - * If this record is not present then the color, size, and shape of the associated data markers are specified by the - * default values of the fields of this record. - * - Offset Name Size Contents - 4 rgbFore 4 Foreground color: RGB value (high byte = 0) - 8 rgbBack 4 Background color: RGB value (high byte = 0) - 12 imk 2 Type of marker - 0 = no marker - 1 = square - 2 = diamond - 3 = triangle - 4 = X - 5 = star - 6 = Dow-Jones - 7 = standard deviation - 8 = circle - 9 = plus sign - 14 grbit 2 Format flags - 16 icvFore 2 Index to color of marker border - 18 icvBack 2 Index to color of marker fill - 20 miSize 4 Size of line markers. An unsigned integer that specifies the size in twips of the data marker. MUST be greater than or equal to 40 and less than or equal to 1440. The default value for this field is 100. - - The icvBack field describes the color of the marker's background, such as the center of the square, - while the icvFore field describes the color of the border or the marker itself. The imk field defines - the type of marker. - - The grbit field contains the following option flags. - - Offset Bits Mask Name Contents - 0 0 01h fAuto Automatic color - 0 3-1 0Eh (reserved) Reserved; must be zero - 0 4 10h fNotShowInt 1 = "background = none" - 0 5 20h fNotShowBrd 1 = "foreground = none" - 0 7-6 C0h (reserved) Reserved; must be zero - 1 7-0 FFh (reserved) Reserved; must be zero - + *

                + * The MarkerFormat record specifies the color, size, and shape of the associated data markers that appear on line, + * radar, and scatter chart groups. The associated data markers are specified by the preceding DataFormat record. + * If this record is not present then the color, size, and shape of the associated data markers are specified by the + * default values of the fields of this record. + *

                + * Offset Name Size Contents + * 4 rgbFore 4 Foreground color: RGB value (high byte = 0) + * 8 rgbBack 4 Background color: RGB value (high byte = 0) + * 12 imk 2 Type of marker + * 0 = no marker + * 1 = square + * 2 = diamond + * 3 = triangle + * 4 = X + * 5 = star + * 6 = Dow-Jones + * 7 = standard deviation + * 8 = circle + * 9 = plus sign + * 14 grbit 2 Format flags + * 16 icvFore 2 Index to color of marker border + * 18 icvBack 2 Index to color of marker fill + * 20 miSize 4 Size of line markers. An unsigned integer that specifies the size in twips of the data marker. MUST be greater than or equal to 40 and less than or equal to 1440. The default value for this field is 100. + *

                + * The icvBack field describes the color of the marker's background, such as the center of the square, + * while the icvFore field describes the color of the border or the marker itself. The imk field defines + * the type of marker. + *

                + * The grbit field contains the following option flags. + *

                + * Offset Bits Mask Name Contents + * 0 0 01h fAuto Automatic color + * 0 3-1 0Eh (reserved) Reserved; must be zero + * 0 4 10h fNotShowInt 1 = "background = none" + * 0 5 20h fNotShowBrd 1 = "foreground = none" + * 0 7-6 C0h (reserved) Reserved; must be zero + * 1 7-0 FFh (reserved) Reserved; must be zero */ public class MarkerFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7526015026522467305L; - private java.awt.Color rgbFore, rgbBack; - private int miSize= 0; - private short imk= 0; - private short icvFore= 0; - private short icvBack= 0; - private short grbit= 0; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 7526015026522467305L; + private java.awt.Color rgbFore, rgbBack; + private int miSize = 0; + private short imk = 0; + private short icvFore = 0; + private short icvBack = 0; + private short grbit = 0; + + public void init() { + super.init(); + byte[] data = this.getData(); + rgbFore = new java.awt.Color((data[0] < 0 ? 255 + data[0] : data[0]), (data[1] < 0 ? 255 + data[1] : data[1]), (data[2] < 0 ? 255 + data[2] : data[2])); + rgbBack = new java.awt.Color((data[4] < 0 ? 255 + data[4] : data[4]), (data[5] < 0 ? 255 + data[5] : data[5]), (data[6] < 0 ? 255 + data[6] : data[6])); + imk = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + grbit = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); + icvFore = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + icvBack = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + miSize = ByteTools.readInt(this.getBytesAt(14, 4)); + } - public void init() { - super.init(); - byte[] data= this.getData(); - rgbFore= new java.awt.Color((data[0]<0?255+data[0]:data[0]), (data[1]<0?255+data[1]:data[1]), (data[2]<0?255+data[2]:data[2])); - rgbBack= new java.awt.Color((data[4]<0?255+data[4]:data[4]), (data[5]<0?255+data[5]:data[5]), (data[6]<0?255+data[6]:data[6])); - imk= ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - grbit= ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - icvFore= ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - icvBack= ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - miSize= ByteTools.readInt(this.getBytesAt(14, 4)); - } - // 20070716 KSC: Need to create new records + // 20070716 KSC: Need to create new records public static XLSRecord getPrototype() { MarkerFormat mf = new MarkerFormat(); mf.setOpcode(MARKERFORMAT); mf.setData(mf.PROTOTYPE_BYTES); return mf; - } // imk was 2 in default - private byte[] PROTOTYPE_BYTES = new byte[] {0,0,0,0, 0,0,0,0, 0,0, 0,0,77,0,77,0,60,0,0,0}; - /** + } // imk was 2 in default + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 77, 0, 60, 0, 0, 0}; + + /** * @return String XML representation of this chart-type's options */ - // TODO: Finish MarkerFormat Options + // TODO: Finish MarkerFormat Options public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - //if (imk!=0) - sb.append(" MarkerFormat=\"" + imk + "\""); - return sb.toString(); + StringBuffer sb = new StringBuffer(); + //if (imk!=0) + sb.append(" MarkerFormat=\"" + imk + "\""); + return sb.toString(); } + /** * Handle setting options from XML in a generic manner */ public boolean setChartOption(String op, String val) { - boolean bHandled= false; - if (op.equalsIgnoreCase("MarkerFormat")) { - imk= Short.parseShort(val); - bHandled= true; - } - if (bHandled) - updateRecord(); - return bHandled; + boolean bHandled = false; + if (op.equalsIgnoreCase("MarkerFormat")) { + imk = Short.parseShort(val); + bHandled = true; + } + if (bHandled) + updateRecord(); + return bHandled; } + private void updateRecord() { - imk= ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + imk = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); byte[] b = ByteTools.shortToLEBytes(imk); this.getData()[8] = b[0]; this.getData()[9] = b[1]; } - /** return the Marker Format field imk + + /** + * return the Marker Format field imk + * * @return */ - public int getMarkerFormat() { return imk; } + public int getMarkerFormat() { + return imk; + } + /** * set the Marker Format field imk + update the record + * * @param imk */ - public void setMarkerFormat(short imk) { this.imk= imk; updateRecord();} - public String toString() { return "MarkerFormat: imk= " + imk + " grbit=" + grbit; } + public void setMarkerFormat(short imk) { + this.imk = imk; + updateRecord(); + } + + public String toString() { + return "MarkerFormat: imk= " + imk + " grbit=" + grbit; + } /** * returns the SVG defs element used, in conjuction with getMarkerSVG, to define markers on charts in SVG - * @return String SVG marker defs + * + * @return String SVG marker defs */ public static String getMarkerSVGDefs() { - return "\r\n" + //-5 -5 - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - // "star - // "dow-jones - // "stddev - "\r\n"; + return "\r\n" + //-5 -5 + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + // "star + // "dow-jones + // "stddev + "\r\n"; } - + /** * returns the svg line to define the marker at the point x,y, in color clr, of marker style marker (1-9) *
                NOTE: you must use getMarkerSVGdefs before using this method - * @param x double x point - * @param y double y point - * @param clr String SVG color string - * @param marker int marker format (1-9) + * + * @param x double x point + * @param y double y point + * @param clr String SVG color string + * @param marker int marker format (1-9) * @return SVG */ public static String getMarkerSVG(double x, double y, String clr, int marker) { - String markersvg=""; - else if (marker==2) // diamond - markersvg+= "diamond1'/>"; - else if (marker==3) // triangle - markersvg+= "triangle1'/>"; - else if (marker==4) // X - markersvg+= "cross1'/>"; - else if (marker==5) // Star ????????????? - markersvg+= "circle1'/>"; - else if (marker==6) // Dow-Jones ?????????? - markersvg+= "circle1'/>"; - else if (marker==7) // Std Dev ????????????? - markersvg+= "circle1'/>"; - else if (marker==8) // circle - markersvg+= "circle1'/>"; - else if (marker==9) // + sign - markersvg+= "plus1'/>"; - return markersvg; + String markersvg = ""; + else if (marker == 2) // diamond + markersvg += "diamond1'/>"; + else if (marker == 3) // triangle + markersvg += "triangle1'/>"; + else if (marker == 4) // X + markersvg += "cross1'/>"; + else if (marker == 5) // Star ????????????? + markersvg += "circle1'/>"; + else if (marker == 6) // Dow-Jones ?????????? + markersvg += "circle1'/>"; + else if (marker == 7) // Std Dev ????????????? + markersvg += "circle1'/>"; + else if (marker == 8) // circle + markersvg += "circle1'/>"; + else if (marker == 9) // + sign + markersvg += "plus1'/>"; + return markersvg; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java b/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java index f94189a..940c43c 100644 --- a/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,398 +23,411 @@ package io.starter.formats.XLS.charts; //OOXML-specific structures -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; - -import org.json.JSONException; -import org.json.JSONObject; import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.OOXMLConstants; -import io.starter.formats.OOXML.SpPr; import io.starter.formats.OOXML.Layout; +import io.starter.formats.OOXML.SpPr; import io.starter.formats.OOXML.Title; import io.starter.formats.OOXML.TxPr; -import io.starter.formats.XLS.Dimensions; -import io.starter.formats.XLS.MSODrawing; -import io.starter.formats.XLS.Obj; import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.HashMap; + public class OOXMLChart extends Chart { - public String lang="en-US"; // default - public boolean roundedCorners= false; - public Title ttl= null; // title element - public io.starter.formats.OOXML.Legend ooxmlLegend= null; - public Layout plotAreaLayout= null; - private SpPr plotareashapeProps= null; // defines the shape properties for this chart (line and fill) - private SpPr csshapeProps= null; // defines the shape properties for this chart space - private TxPr txpr= null; - private String editMovement= "twoCell"; // default - private String name= null; // name property of cNvPr - private ArrayList chartEmbeds= null; // if present, name(s) of chart embeds (images, userShape definitions (drawingml files that define shapes ontop of chart)) - - /** - * create a new OOXMLChart from a 2003-v chart object - * @param c - * @param wbh - */ - public OOXMLChart(Chart c, WorkBookHandle wbh) { - // Walk up the superclass hierarchy - //io.starter.toolkit.Logger.log("BEFORE: chartArr: " + Arrays.toString(chartArr.toArray())); - for (Class obj = c.getClass(); - !obj.equals(Object.class); - obj = obj.getSuperclass()) - { - java.lang.reflect.Field[] fields = obj.getDeclaredFields(); - for (int i = 0; i < fields.length; i++) - { - fields[i].setAccessible(true); - try - { - // for each class/suerclass, copy all fields - // from this object to the clone - fields[i].set(this, fields[i].get(c)); - } - catch (IllegalArgumentException e){} - catch (IllegalAccessException e){} - } - } - - - // ttl? - // chartlaout? - // txpr? - // name? - this.name= c.getTitle(); - if (c.hasDataLegend()) { - ooxmlLegend= io.starter.formats.OOXML.Legend.createLegend(c.getLegend()); - } - this.wbh= wbh; - } - - public String toString() { - String t= getTitle(); - if (!t.equals("")) - return t; - return name; // if no title, return OOXMLName - } - - + public String lang = "en-US"; // default + public boolean roundedCorners = false; + public Title ttl = null; // title element + public io.starter.formats.OOXML.Legend ooxmlLegend = null; + public Layout plotAreaLayout = null; + private SpPr plotareashapeProps = null; // defines the shape properties for this chart (line and fill) + private SpPr csshapeProps = null; // defines the shape properties for this chart space + private TxPr txpr = null; + private String editMovement = "twoCell"; // default + private String name = null; // name property of cNvPr + private ArrayList chartEmbeds = null; // if present, name(s) of chart embeds (images, userShape definitions (drawingml files that define shapes ontop of chart)) + + /** + * create a new OOXMLChart from a 2003-v chart object + * + * @param c + * @param wbh + */ + public OOXMLChart(Chart c, WorkBookHandle wbh) { + // Walk up the superclass hierarchy + //io.starter.toolkit.Logger.log("BEFORE: chartArr: " + Arrays.toString(chartArr.toArray())); + for (Class obj = c.getClass(); + !obj.equals(Object.class); + obj = obj.getSuperclass()) { + java.lang.reflect.Field[] fields = obj.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + fields[i].setAccessible(true); + try { + // for each class/suerclass, copy all fields + // from this object to the clone + fields[i].set(this, fields[i].get(c)); + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } + } + } + + + // ttl? + // chartlaout? + // txpr? + // name? + this.name = c.getTitle(); + if (c.hasDataLegend()) { + ooxmlLegend = io.starter.formats.OOXML.Legend.createLegend(c.getLegend()); + } + this.wbh = wbh; + } + + public String toString() { + String t = getTitle(); + if (!t.equals("")) + return t; + return name; // if no title, return OOXMLName + } + + /** * return the OOXML shape property for this chart - * @param type 0= chart shape props, 1=plot area shape props 2= chartspace shape props, 3= legend shape props + * * @return - */ + * @param type 0= chart shape props, 1=plot area shape props 2= chartspace shape props, 3= legend shape props + */ public SpPr getSpPr(int type) { - if (type==0) - return plotareashapeProps; - if (type==1) - return csshapeProps; - return null; + if (type == 0) + return plotareashapeProps; + if (type == 1) + return csshapeProps; + return null; } - + /** - * store the OOXML text formatting element for this chart + * store the OOXML text formatting element for this chart */ public void setTxPr(TxPr txPr) { - txpr= txPr; + txpr = txPr; } + /** * return the OOXML text formatting element for this chart, if present + * * @return - */ + */ public TxPr getTxPr() { - return txpr; + return txpr; } - + /** * define the OOXML shape property for this chart from an existing spPr element - * @param type 0=plot area shape props 1= chartspace shape props, 2= legend shape props + * + * @param type 0=plot area shape props 1= chartspace shape props, 2= legend shape props */ public void setSpPr(int type, SpPr spPr) { - if (type==0) { - plotareashapeProps= spPr; // plot area - int lw=-1, lclr= 0, bgcolor= -1; - lw= spPr.getLineWidth(); // TO DO: Style - lclr= spPr.getLineColor(); - //bgcolor= spPr.getColor(); - this.getAxes().setPlotAreaBorder(lw, lclr); - }else if (type==1) { - csshapeProps= spPr; - } - + if (type == 0) { + plotareashapeProps = spPr; // plot area + int lw = -1, lclr = 0, bgcolor = -1; + lw = spPr.getLineWidth(); // TO DO: Style + lclr = spPr.getLineColor(); + //bgcolor= spPr.getColor(); + this.getAxes().setPlotAreaBorder(lw, lclr); + } else if (type == 1) { + csshapeProps = spPr; + } + } - + /** * return the OOXML title element for this chart + * * @return */ - public Title getOOXMLTitle() { return ttl; } - + public Title getOOXMLTitle() { + return ttl; + } + /** * set the OOXML title element for this chart + * * @param t */ - public void setOOXMLTitle(Title t, WorkBookHandle wb) { - ttl = t; - int fid = ttl.getFontId(wb); - if (fid==-1) fid= 5; // default ...? - float[] coords= null; - int lw=-1, lclr= 0, bgcolor= 0; - if (ttl.getLayout()!=null) { // pos - coords= ttl.getLayout().getCoords(); - } - if (ttl.getSpPr()!=null) { // Area Fill, Line Format - SpPr sp= ttl.getSpPr(); - lw= sp.getLineWidth(); // TO DO: Style, fill/color ... - lclr= sp.getLineColor(); - bgcolor= sp.getColor(); - } - if (coords!=null) - charttitle.setFrame(lw, lclr, bgcolor, coords); - - // must also set the fontx id for the title - if (charttitle != null) - charttitle.setFontId(fid); - - } + public void setOOXMLTitle(Title t, WorkBookHandle wb) { + ttl = t; + int fid = ttl.getFontId(wb); + if (fid == -1) fid = 5; // default ...? + float[] coords = null; + int lw = -1, lclr = 0, bgcolor = 0; + if (ttl.getLayout() != null) { // pos + coords = ttl.getLayout().getCoords(); + } + if (ttl.getSpPr() != null) { // Area Fill, Line Format + SpPr sp = ttl.getSpPr(); + lw = sp.getLineWidth(); // TO DO: Style, fill/color ... + lclr = sp.getLineColor(); + bgcolor = sp.getColor(); + } + if (coords != null) + charttitle.setFrame(lw, lclr, bgcolor, coords); + + // must also set the fontx id for the title + if (charttitle != null) + charttitle.setFontId(fid); + + } /** - * specify how to resize or move upon edit OOXML specific + * specify how to resize or move upon edit OOXML specific + * * @param editMovement */ public void setEditMovement(String editMovement) { - this.editMovement= editMovement; - dirtyflag= true; + this.editMovement = editMovement; + dirtyflag = true; } - + /** * return state of how to resize or move upon edit OOXML specific + * * @return editMovement string */ - public String getEditMovement() { return this.editMovement; } - - /** - * remove the legend from the chart - */ - public void removeLegend() { - showLegend(false, false); - ooxmlLegend= null; - } - + public String getEditMovement() { + return this.editMovement; + } + + /** + * remove the legend from the chart + */ + public void removeLegend() { + showLegend(false, false); + ooxmlLegend = null; + } + /** + * */ - public String getOOXMLName() { return this.name; } - + public String getOOXMLName() { + return this.name; + } + /** * set the OOXML-specific name for this chart + * * @param name */ public void setOOXMLName(String name) { - this.name= name; - dirtyflag= true; + this.name = name; + dirtyflag = true; } - + /** * returns information regarding external files associated with this chart - *
                e.g. a chart user shape, an image + *
                e.g. a chart user shape, an image + * * @return */ public ArrayList getChartEmbeds() { - return chartEmbeds; + return chartEmbeds; } - + /** * sets external information linked to or "embedded" in this OOXML chart; * can be a chart user shape, an image ... *
                NOTE: a userShape is a drawingml file name which defines the userShape (if any) *
                a userShape is a drawing or shape ontop of a chart - * @param String[] embedType, filename e.g. {"userShape", "userShape file name"} + * + * @param String[] embedType, filename e.g. {"userShape", "userShape file name"} */ public void addChartEmbed(String[] ce) { - if (chartEmbeds==null) - chartEmbeds= new ArrayList(); - chartEmbeds.add(ce); + if (chartEmbeds == null) + chartEmbeds = new ArrayList(); + chartEmbeds.add(ce); } - - /** - * return the OOXML representation of this chart object "c:chart" representing OOXML element in chartX.xml - *
                below is complete the ordered sequence of child elements of the chart element - *
                c:chart - parent= chartSpace - *

              • title - *
              • autoTitleDeleted - *
              • pivotFmts - *
              • view3d - *
              • floor - *
              • sideWall - *
              • backWall - *
              • plotArea (see below) - *
              • legend - *
              • plotVisOnly - *
              • dispBlankAs - *
              • showDlblsOverMax - * - *
                plotArea: - *
              • layout - *
              • chart type (see below) - *
              • axes *** - *
              • dTable - *
              • spPr - * - *
                chart type: - *
              • barDir Bar, Bar3d only - *
              • radarStyle || scatterStyle - *
              • ofPieType - *
              • wireFrame surface - *
              • grouping Area, Area3d, Line, Line3d, Bar, Bar3D - *
              • varyColors not for Stock - *
              • ser *n series - *
              • dLbls not for surface - * Area Chart, AreaChart3D, LineChart, Line3D, Stock - *
              • dropLines - * Bar Chart, Bar3d, ofPieChart - *
              • gapWidth - * AreaChart3D, Line3D, Bar3D - *
              • gapDepth - * Line, Stock - *
              • hiLowLines - *
              • upDownBars - * Line - *
              • marker - *
              • smooth - * BarChart only - *
              • overlap - *
              • serLines - * Bar 3d only - *
              • shape - * ofPieChart - *
              • splitType - *
              • splitPos - *
              • custSplit - *
              • secondPieSize - *
              • serLines - * Pie, Doughnut - *
              • firstSliceAng - * Doughnut - *
              • holeSize - * Surface - *
              • bandFmts - * Bubble - *
              • bubble3D - *
              • bubbleScale - *
              • showNegBubbles - *
              • sizeRepresents - *
              • axId - - * @return StringBuffer - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - try { - int[] allCharts= this.getAllChartTypes(); // usually only 1 chart but some have overlay charts in addition to the default chart (chart 0) - - // lang - cooxml.append(""); cooxml.append("\r\n"); - // rounded corners - if (this.roundedCorners) - cooxml.append(""); - // chart - cooxml.append(""); cooxml.append("\r\n"); - // title - if (this.getOOXMLTitle()==null) {// if no OOXML title, see if have a BIFF8 title - if (!this.getTitle().equals("")) { - this.setOOXMLTitle(new Title(this.getTitleTd(), this.wbh.getWorkBook()), this.wbh); - } - } - if (this.getOOXMLTitle()!=null) // otherwise there's no title - cooxml.append(this.getOOXMLTitle().getOOXML()); - - if (allCharts[0]!=BUBBLECHART) { // bubble threeD handled in series for some reason - // Q: what if overlay charts are not 3D? what if default isn't and overlay is? - // view 3D - ThreeD td= this.getThreeDRec(0); - if (td!=null) - cooxml.append(td.getOOXML()); - } - // TODO: Handle: - // floor - // sideWall - // backWall - // plot area (contains all chart types such as barChart, lineChart ...) - cooxml.append(""); cooxml.append("\r\n"); - // layout: size and position - if (this.plotAreaLayout==null) { // if converted from XLS will hit here - HashMap chartMetrics= this.getMetrics(wbh); - double x= chartMetrics.get("x")/chartMetrics.get("canvasw"); - double y= chartMetrics.get("y")/chartMetrics.get("canvash"); - double w= chartMetrics.get("w")/chartMetrics.get("canvasw"); - double h= chartMetrics.get("h")/chartMetrics.get("canvash"); - this.plotAreaLayout= new Layout("inner", new double[]{x, y, w, h}); - } - cooxml.append(this.plotAreaLayout.getOOXML()); - - for (ChartType ch:chartgroup) { - cooxml.append(ch.getOOXML(catAxisId, valAxisId, serAxisId)); - } - -/* TODO: - * 1- varyColors ??? - * 2- getChartSeries.getOOXML -> nchart? - * 3- data labels - * 4- drop lines - * - * area charts -- bar colors!!! - * bar - serLines - * pie, doughnut - firstSliceArg - * radar -- filled? - * bubble -- bubbleScale, showNegBubbles, sizeRepresents - * surface -- wireframe, bandfmts - * - * pie of pie, bar of pie - * surface3d - * stock - * - * - */ - - // ****************************************************************************** - // after chart type ooxml, axes (if present) - cooxml.append(this.getAxes().getOOXML(XAXIS, 0, catAxisId, valAxisId)); - cooxml.append(this.getAxes().getOOXML(XVALAXIS, 2, catAxisId, valAxisId)); // valAx - for bubble/scatter - // val axis - cooxml.append(this.getAxes().getOOXML(YAXIS, 1, valAxisId, catAxisId)); // val axis - // ser axis - cooxml.append(this.getAxes().getOOXML(ZAXIS, 3, serAxisId, valAxisId)); // ser axis (crosses val axis) - // TODO: dateAx - if (this.getSpPr(0)!=null) { // plot area shape props - cooxml.append(this.getSpPr(0).getOOXML()); - } else if (!this.wbh.getIsExcel2007()) { - SpPr sp= new SpPr("c", this.getPlotAreaBgColor().substring(1), 12700, this.getPlotAreaLnColor().substring(1)); - cooxml.append(sp.getOOXML()); - - } - - cooxml.append(""); cooxml.append("\r\n"); - // legend - if (this.ooxmlLegend!=null) cooxml.append(this.ooxmlLegend.getOOXML()); - - cooxml.append(""); // specifies that only visible cells should be plotted on the chart + + /** + * return the OOXML representation of this chart object "c:chart" representing OOXML element in chartX.xml + *
                below is complete the ordered sequence of child elements of the chart element + *
                c:chart - parent= chartSpace + *
              • title + *
              • autoTitleDeleted + *
              • pivotFmts + *
              • view3d + *
              • floor + *
              • sideWall + *
              • backWall + *
              • plotArea (see below) + *
              • legend + *
              • plotVisOnly + *
              • dispBlankAs + *
              • showDlblsOverMax + * + *
                plotArea: + *
              • layout + *
              • chart type (see below) + *
              • axes *** + *
              • dTable + *
              • spPr + * + *
                chart type: + *
              • barDir Bar, Bar3d only + *
              • radarStyle || scatterStyle + *
              • ofPieType + *
              • wireFrame surface + *
              • grouping Area, Area3d, Line, Line3d, Bar, Bar3D + *
              • varyColors not for Stock + *
              • ser *n series + *
              • dLbls not for surface + * Area Chart, AreaChart3D, LineChart, Line3D, Stock + *
              • dropLines + * Bar Chart, Bar3d, ofPieChart + *
              • gapWidth + * AreaChart3D, Line3D, Bar3D + *
              • gapDepth + * Line, Stock + *
              • hiLowLines + *
              • upDownBars + * Line + *
              • marker + *
              • smooth + * BarChart only + *
              • overlap + *
              • serLines + * Bar 3d only + *
              • shape + * ofPieChart + *
              • splitType + *
              • splitPos + *
              • custSplit + *
              • secondPieSize + *
              • serLines + * Pie, Doughnut + *
              • firstSliceAng + * Doughnut + *
              • holeSize + * Surface + *
              • bandFmts + * Bubble + *
              • bubble3D + *
              • bubbleScale + *
              • showNegBubbles + *
              • sizeRepresents + *
              • axId + * + * @return StringBuffer + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + try { + int[] allCharts = this.getAllChartTypes(); // usually only 1 chart but some have overlay charts in addition to the default chart (chart 0) + + // lang + cooxml.append(""); + cooxml.append("\r\n"); + // rounded corners + if (this.roundedCorners) + cooxml.append(""); + // chart + cooxml.append(""); + cooxml.append("\r\n"); + // title + if (this.getOOXMLTitle() == null) {// if no OOXML title, see if have a BIFF8 title + if (!this.getTitle().equals("")) { + this.setOOXMLTitle(new Title(this.getTitleTd(), this.wbh.getWorkBook()), this.wbh); + } + } + if (this.getOOXMLTitle() != null) // otherwise there's no title + cooxml.append(this.getOOXMLTitle().getOOXML()); + + if (allCharts[0] != BUBBLECHART) { // bubble threeD handled in series for some reason + // Q: what if overlay charts are not 3D? what if default isn't and overlay is? + // view 3D + ThreeD td = this.getThreeDRec(0); + if (td != null) + cooxml.append(td.getOOXML()); + } + // TODO: Handle: + // floor + // sideWall + // backWall + // plot area (contains all chart types such as barChart, lineChart ...) + cooxml.append(""); + cooxml.append("\r\n"); + // layout: size and position + if (this.plotAreaLayout == null) { // if converted from XLS will hit here + HashMap chartMetrics = this.getMetrics(wbh); + double x = chartMetrics.get("x") / chartMetrics.get("canvasw"); + double y = chartMetrics.get("y") / chartMetrics.get("canvash"); + double w = chartMetrics.get("w") / chartMetrics.get("canvasw"); + double h = chartMetrics.get("h") / chartMetrics.get("canvash"); + this.plotAreaLayout = new Layout("inner", new double[]{x, y, w, h}); + } + cooxml.append(this.plotAreaLayout.getOOXML()); + + for (ChartType ch : chartgroup) { + cooxml.append(ch.getOOXML(catAxisId, valAxisId, serAxisId)); + } + + /* TODO: + * 1- varyColors ??? + * 2- getChartSeries.getOOXML -> nchart? + * 3- data labels + * 4- drop lines + * + * area charts -- bar colors!!! + * bar - serLines + * pie, doughnut - firstSliceArg + * radar -- filled? + * bubble -- bubbleScale, showNegBubbles, sizeRepresents + * surface -- wireframe, bandfmts + * + * pie of pie, bar of pie + * surface3d + * stock + * + * + */ + + // ****************************************************************************** + // after chart type ooxml, axes (if present) + cooxml.append(this.getAxes().getOOXML(XAXIS, 0, catAxisId, valAxisId)); + cooxml.append(this.getAxes().getOOXML(XVALAXIS, 2, catAxisId, valAxisId)); // valAx - for bubble/scatter + // val axis + cooxml.append(this.getAxes().getOOXML(YAXIS, 1, valAxisId, catAxisId)); // val axis + // ser axis + cooxml.append(this.getAxes().getOOXML(ZAXIS, 3, serAxisId, valAxisId)); // ser axis (crosses val axis) + // TODO: dateAx + if (this.getSpPr(0) != null) { // plot area shape props + cooxml.append(this.getSpPr(0).getOOXML()); + } else if (!this.wbh.getIsExcel2007()) { + SpPr sp = new SpPr("c", this.getPlotAreaBgColor().substring(1), 12700, this.getPlotAreaLnColor().substring(1)); + cooxml.append(sp.getOOXML()); + + } + + cooxml.append(""); + cooxml.append("\r\n"); + // legend + if (this.ooxmlLegend != null) cooxml.append(this.ooxmlLegend.getOOXML()); + + cooxml.append(""); // specifies that only visible cells should be plotted on the chart // "gap", "span", "zero" --> default - cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - if (this.getSpPr(1)!=null) { // chart space shape props - cooxml.append(this.getSpPr(1).getOOXML()); - } - if (this.getTxPr()!=null) { // text formatting - cooxml.append(this.getTxPr().getOOXML()); - } - } catch (Exception e) { - Logger.logErr("OOXMLChart.getOOXML: error generating OOXML. Chart not created: " + e.toString()); - } - return cooxml; //.toString(); - } - + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + if (this.getSpPr(1) != null) { // chart space shape props + cooxml.append(this.getSpPr(1).getOOXML()); + } + if (this.getTxPr() != null) { // text formatting + cooxml.append(this.getTxPr().getOOXML()); + } + } catch (Exception e) { + Logger.logErr("OOXMLChart.getOOXML: error generating OOXML. Chart not created: " + e.toString()); + } + return cooxml; //.toString(); + } + } diff --git a/src/main/java/io/starter/formats/XLS/charts/ObjectLink.java b/src/main/java/io/starter/formats/XLS/charts/ObjectLink.java index d819854..20221bb 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ObjectLink.java +++ b/src/main/java/io/starter/formats/XLS/charts/ObjectLink.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,69 +23,75 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.*;; +import io.starter.toolkit.ByteTools; + /** * ObjectLink: Attaches Text to Chart or to Chart Item (0x1027) - * + *

                * 4 wLinkObj 2 Object text is linked to (1= chart title, 2= Veritcal (y) axis title, 3= Category (x) axis title, 4= data series points, 7=Series Axis 12= Display Units * 6 wLinkVar1 2 0-based series number (only if wLinkObj=4, otherwise 0) - * 8 wLinkVar2 2 0-based category number within the series specified by wLinkVar1. (only if wLinkObj=4, otherwise 0). If attached to entire series rather - * than a single data point, = 0xFFFF. - * + * 8 wLinkVar2 2 0-based category number within the series specified by wLinkVar1. (only if wLinkObj=4, otherwise 0). If attached to entire series rather + * than a single data point, = 0xFFFF. */ public class ObjectLink extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -301929936750246017L; - short wLinkObj; - public static final int TYPE_TITLE= 1; - public static final int TYPE_XAXIS= 3; - public static final int TYPE_YAXIS= 2; - public static final int TYPE_DATAPOINTS= 4; - public static final int TYPE_ZAXIS= 7; - /** An axis-formatting option that determines how numeric units are displayed on a value axis. */ - public static final int TYPE_DISPLAYUNITS= 0xC; - - public void init() { - super.init(); + /** + * serialVersionUID + */ + private static final long serialVersionUID = -301929936750246017L; + short wLinkObj; + public static final int TYPE_TITLE = 1; + public static final int TYPE_XAXIS = 3; + public static final int TYPE_YAXIS = 2; + public static final int TYPE_DATAPOINTS = 4; + public static final int TYPE_ZAXIS = 7; + /** + * An axis-formatting option that determines how numeric units are displayed on a value axis. + */ + public static final int TYPE_DISPLAYUNITS = 0xC; + + public void init() { + super.init(); byte[] rkdata = this.getData(); wLinkObj = ByteTools.readShort(rkdata[0], rkdata[1]); - } - + } + /** * Does this object link refer to the chart title? + * * @return */ boolean isChartTitle() { - if (wLinkObj == TYPE_TITLE)return true; - return false; + return wLinkObj == TYPE_TITLE; } - + /** * Does this object link refer to the XAxis Label? + * * @return */ boolean isXAxisLabel() { - if (wLinkObj == TYPE_XAXIS)return true; - return false; + return wLinkObj == TYPE_XAXIS; } - - + + /** * Does this object link refer to the YAxis Label? + * * @return */ boolean isYAxisLabel() { - if (wLinkObj == TYPE_YAXIS)return true; - return false; + return wLinkObj == TYPE_YAXIS; + } + + public int getType() { + return wLinkObj; } - public int getType() { return wLinkObj; } - + public void setType(int type) { - wLinkObj= (short) type; - this.getData()[0]= (byte) wLinkObj; + wLinkObj = (short) type; + this.getData()[0] = (byte) wLinkObj; } + public static XLSRecord getPrototype(int type) { ObjectLink o = new ObjectLink(); o.setOpcode(OBJECTLINK); @@ -93,5 +99,6 @@ public static XLSRecord getPrototype(int type) { o.setType(type); return o; } - private byte[] PROTOTYPE_BYTES= new byte[] {1, 0, 0, 0, 0, 0}; + + private byte[] PROTOTYPE_BYTES = new byte[]{1, 0, 0, 0, 0, 0}; } diff --git a/src/main/java/io/starter/formats/XLS/charts/OfPieChart.java b/src/main/java/io/starter/formats/XLS/charts/OfPieChart.java index 28e8075..39d94e8 100644 --- a/src/main/java/io/starter/formats/XLS/charts/OfPieChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/OfPieChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,50 +25,53 @@ import io.starter.formats.XLS.WorkBook; public class OfPieChart extends ChartType { - Boppop ofPie= null; - - public OfPieChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - ofPie= (Boppop) charttype; - } - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); + Boppop ofPie = null; - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); + public OfPieChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + ofPie = (Boppop) charttype; + } - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - // gapWidth - if (ofPie.getpcGap()!=150) - cooxml.append(""); - // splitType - if (ofPie.getSplitType()!=-1) - cooxml.append(""); - // splitPos - if (ofPie.getSplitPos()!=0) - cooxml.append(""); - // custSplit TODO: FINISH - // secondPieSize - if (ofPie.getSecondPieSize()!=75) - cooxml.append(""); - // serLines - ChartLine cl= cf.getChartLinesRec(); - if (cl!=null) - cooxml.append(cl.getOOXML()); - cooxml.append(""); cooxml.append("\r\n"); + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); - return cooxml; - } + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append(""); + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + // gapWidth + if (ofPie.getpcGap() != 150) + cooxml.append(""); + // splitType + if (ofPie.getSplitType() != -1) + cooxml.append(""); + // splitPos + if (ofPie.getSplitPos() != 0) + cooxml.append(""); + // custSplit TODO: FINISH + // secondPieSize + if (ofPie.getSecondPieSize() != 75) + cooxml.append(""); + // serLines + ChartLine cl = cf.getChartLinesRec(); + if (cl != null) + cooxml.append(cl.getOOXML()); + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Picf.java b/src/main/java/io/starter/formats/XLS/charts/Picf.java index 73794b0..69895ab 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Picf.java +++ b/src/main/java/io/starter/formats/XLS/charts/Picf.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,16 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * Picf: Picture Format(0x103c) - * */ public class Picf extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7241922273506449916L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 7241922273506449916L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Pie.java b/src/main/java/io/starter/formats/XLS/charts/Pie.java index 2c2442c..841a246 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Pie.java +++ b/src/main/java/io/starter/formats/XLS/charts/Pie.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -28,41 +28,41 @@ /** * Pie: Chart Group Is a pie Chart Group(0x1019) - * - * 4 anStart 2 Angle of the first pie slice expressed in degrees. Must be <= 360 - * 6 pcDonut 2 0= true pie chart, non-zero= size of center hole in a donut chart, as a percentage - * 8 grbit 2 Option Flags - * - * - * grbit: - * 0 0x1 fHasShadow 1= has shadow - * 1 0x2 fShowLdrLines 1= show leader lines to data labels - * + *

                + * 4 anStart 2 Angle of the first pie slice expressed in degrees. Must be <= 360 + * 6 pcDonut 2 0= true pie chart, non-zero= size of center hole in a donut chart, as a percentage + * 8 grbit 2 Option Flags + *

                + *

                + * grbit: + * 0 0x1 fHasShadow 1= has shadow + * 1 0x2 fShowLdrLines 1= show leader lines to data labels */ public class Pie extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7851320124576950635L; - private short grbit = 0; - private boolean fHasShadow= false, fShowLdrLines= false; - protected short pcDonut= 0, anStart= 0; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7851320124576950635L; + private short grbit = 0; + private boolean fHasShadow = false, fShowLdrLines = false; + protected short pcDonut = 0, anStart = 0; public void init() { - super.init(); - byte[] data= getData(); - anStart= ByteTools.readShort(data[0], data[1]); - pcDonut= data[2]; - if (pcDonut==0) - chartType= ChartConstants.PIECHART; // 20070703 KSC - else - chartType= ChartConstants.DOUGHNUTCHART; + super.init(); + byte[] data = getData(); + anStart = ByteTools.readShort(data[0], data[1]); + pcDonut = data[2]; + if (pcDonut == 0) + chartType = ChartConstants.PIECHART; // 20070703 KSC + else + chartType = ChartConstants.DOUGHNUTCHART; grbit = ByteTools.readShort(data[4], data[5]); - fHasShadow= ((grbit & 0x1)==0x1); - fShowLdrLines= ((grbit & 0x2)==0x2); - } - - private byte[] PROTOTYPE_BYTES = new byte[] {0, 0, 0, 0, 2, 0}; + fHasShadow = ((grbit & 0x1) == 0x1); + fShowLdrLines = ((grbit & 0x2) == 0x2); + } + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 2, 0}; + public static XLSRecord getPrototype() { Pie b = new Pie(); b.setOpcode(PIE); @@ -73,120 +73,142 @@ public static XLSRecord getPrototype() { // 20070703 KSC and below ... private void updateRecord() { - this.getData()[2]= (byte)pcDonut; + this.getData()[2] = (byte) pcDonut; byte[] b = ByteTools.shortToLEBytes(grbit); this.getData()[4] = b[0]; this.getData()[5] = b[1]; } - + public void setAsPieChart() { - pcDonut= 0; - chartType= ChartConstants.PIECHART; + pcDonut = 0; + chartType = ChartConstants.PIECHART; this.updateRecord(); } + public void setAsDoughnutChart() { - pcDonut= 50; // default % - chartType= ChartConstants.DOUGHNUTCHART; - this.updateRecord(); + pcDonut = 50; // default % + chartType = ChartConstants.DOUGHNUTCHART; + this.updateRecord(); } - + /** * return the Doughnut hole size (if > 0) + * * @return */ - public int getDoughnutSize() { return pcDonut; } - - + public int getDoughnutSize() { + return pcDonut; + } + + /** * size of center hole in a donut chart, as a percentage. 0 for pie + * * @return */ public void setDoughnutSize(int s) { - pcDonut= (short) s; - byte[] b= ByteTools.shortToLEBytes(pcDonut); - this.getData()[2]= b[0]; - this.getData()[3]= b[1]; + pcDonut = (short) s; + byte[] b = ByteTools.shortToLEBytes(pcDonut); + this.getData()[2] = b[0]; + this.getData()[3] = b[1]; } - + /** * return the Angle of the first pie slice expressed in degrees. Must be <= 360 + * * @return */ - public int getAnStart() { return anStart; } - - + public int getAnStart() { + return anStart; + } + + /** * sets the Angle of the first pie slice expressed in degrees. Must be <= 360 + * * @return */ public void setAnStart(int a) { - anStart= (short) a; - byte[] b= ByteTools.shortToLEBytes(anStart); - this.getData()[0]= b[0]; - this.getData()[1]= b[1]; + anStart = (short) a; + byte[] b = ByteTools.shortToLEBytes(anStart); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; } - /** + + /** * @return String XML representation of this chart-type's options */ public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - if (fHasShadow) - sb.append(" Shadow=\"true\""); - if (fShowLdrLines) - sb.append(" ShowLdrLines=\"true\""); - if (pcDonut>0) - sb.append(" Donut=\"" + pcDonut + "\""); - return sb.toString(); - } - + StringBuffer sb = new StringBuffer(); + if (fHasShadow) + sb.append(" Shadow=\"true\""); + if (fShowLdrLines) + sb.append(" ShowLdrLines=\"true\""); + if (pcDonut > 0) + sb.append(" Donut=\"" + pcDonut + "\""); + return sb.toString(); + } + /** * Handle setting options from XML in a generic manner */ public boolean setChartOption(String op, String val) { - boolean bHandled= false; - if (op.equalsIgnoreCase("Shadow")) { - setHasShadow(true); - bHandled= true; - } else if (op.equalsIgnoreCase("ShowLdrLines")) { - setShowLdrLines(true); - bHandled= true; - } else if (op.equalsIgnoreCase("Donut")) { - setDonutPercentage(val); - bHandled= true; - } else if (op.equalsIgnoreCase("donutSize")) { - setDonutPercentage(val); - bHandled= true; - } - return bHandled; - } - + boolean bHandled = false; + if (op.equalsIgnoreCase("Shadow")) { + setHasShadow(true); + bHandled = true; + } else if (op.equalsIgnoreCase("ShowLdrLines")) { + setShowLdrLines(true); + bHandled = true; + } else if (op.equalsIgnoreCase("Donut")) { + setDonutPercentage(val); + bHandled = true; + } else if (op.equalsIgnoreCase("donutSize")) { + setDonutPercentage(val); + bHandled = true; + } + return bHandled; + } + public String getChartOption(String op) { - if (op.equals("ShowLdrLines")) { // Pie - return String.valueOf(this.showLdrLines()); - } else if (op.equals("donutSize")) { // Pie - return String.valueOf(this.getDonutPercentage()); - } else - return super.getChartOption(op); - } - - public boolean hasShadow() { return fHasShadow; } - public boolean showLdrLines() { return fShowLdrLines; } + if (op.equals("ShowLdrLines")) { // Pie + return String.valueOf(this.showLdrLines()); + } else if (op.equals("donutSize")) { // Pie + return String.valueOf(this.getDonutPercentage()); + } else + return super.getChartOption(op); + } + + public boolean hasShadow() { + return fHasShadow; + } + + public boolean showLdrLines() { + return fShowLdrLines; + } + public void setHasShadow(boolean bHasShadow) { - fHasShadow= bHasShadow; - grbit= ByteTools.updateGrBit(grbit, fHasShadow, 0); - updateRecord(); + fHasShadow = bHasShadow; + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 0); + updateRecord(); } + public void setShowLdrLines(boolean bShowLdrLines) { - fShowLdrLines= bShowLdrLines; - grbit= ByteTools.updateGrBit(grbit, fShowLdrLines, 1); - updateRecord(); + fShowLdrLines = bShowLdrLines; + grbit = ByteTools.updateGrBit(grbit, fShowLdrLines, 1); + updateRecord(); } + public void setDonutPercentage(String val) { - try { - pcDonut= Short.valueOf(val).shortValue(); - } catch (Exception e) { } - updateRecord(); + try { + pcDonut = Short.valueOf(val).shortValue(); + } catch (Exception e) { + } + updateRecord(); + } + + public short getDonutPercentage() { + return pcDonut; } - public short getDonutPercentage() { return pcDonut; } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.java b/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.java index 83a16ee..ef7a7c5 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,184 +22,181 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartSeriesHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.HashMap; + public class Pie3dChart extends PieChart { - public Pie3dChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - } - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + public Pie3dChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - StringBuffer svg= new StringBuffer(); - if (series.size()==0) { - Logger.logErr("Pie.getSVG: error in series"); - return ""; - } - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - final int LABELOFFSET= 15; - - int n= series.size(); - double centerx= 0.0, centery= 0.0, radius= 0.0; - double radiusy= 0.0; - - centerx= w/2 + chartMetrics.get("x"); - centery= h/2 + chartMetrics.get("y"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - - // if have any series - should :) - radius= Math.min(w, h)/2; - //radius= Math.min(w, h); // ????????????? just an estimate? - double depth3d= 0; // only used for 3d - radiusy= radius * 2/3; - depth3d= radius/5.5; - if (n>0) { - double[] oneseries= (double[])series.get(0); //FOR PIE CHARTS ONLY 1 SERIES VALUE POINTS ARE USED ********************************** - String[] curranges= (String[])s.getSeriesRanges().get(0); - double total= 0.0; - for (int j= 0; j < oneseries.length; j++) { // get total in order to calculate percentages - total+= oneseries[j]; - } - if (dls.length==1) { // no series-specific data labels; expand to entire series for loop below - int dl= dls[0]; - dls= new int[oneseries.length]; - for (int i= 0; i < dls.length; i++) - dls[i]= dl; - } - String path= ""; - double x= centerx+radius; - double y= centery; - double percentage= 0; - double lasta= 0; - int largearcflag= 0; - int sweepflag= 0; - - // Now create each pie wedge according to it's percentage value - for (int j= 0; j < oneseries.length; j++) { - if (total > 0) - percentage= oneseries[j]/total; - double angle= (percentage*360) + lasta; - double x1=centerx + radius*(Math.cos(Math.toRadians(angle))); - double y1=centery - radiusy*(Math.sin(Math.toRadians(angle))); - if ((percentage*360)/*angle*/ > 180) { - largearcflag= 1; - } else - largearcflag= 0; - - if (y1 >= centery) { // the lower portion of a 3d pie chart that makes it look 3d - double x0= x; - double y0= y; - if (centery > y) { // lower portion has to be below origin - y0= centery; - x0= centerx-radius; - } - path= "M" + x0 + "," + y0 + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + x1 + " " + (y1+depth3d); - path+= " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + "1" + " " + x0 + " " + (y0+depth3d) + " Z"; - svg.append("\r\n"); - } - path= "M" + centerx + " " + centery + " L" + x + " " + y + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z"; - svg.append("\r\n"); - - String l= getSVGDataLabels(dls, axisMetrics, oneseries[j], percentage, j, legends, categories[j].toString()); - if (l!=null) { - // apparently labels are outside of wedge unless angle is >= 30 ... - // category labels - double halfa= ((percentage/2)*360) + lasta; // center in area - double x2, y2; - if (percentage < .3) { // display label on outside with leader lines - x2=centerx + (radius+LABELOFFSET)*(Math.cos(Math.toRadians(halfa))); - y2=centery - (radiusy+LABELOFFSET)*(Math.sin(Math.toRadians(halfa))); - } else { // display label within wedge for > 30% - x2=centerx + (radius/2)*(Math.cos(Math.toRadians(halfa))); - y2=centery - (radiusy/2)*(Math.sin(Math.toRadians(halfa))); - } - String style= ""; - if (percentage >= .3) - style=" style='text-anchor: middle;'"; - else if (lasta > 90 && lasta < 270) { // right-align text for wedges on left side of pie - style= " style='text-anchor: end;'"; - // TODO: dec x2 - } - svg.append("" + l + "\r\n"); - // leaderline - not exactly like Excel's but ... :) do when NOT putting text within wedge - if (percentage < .3) { - double x0=centerx + (radius)*(Math.cos(Math.toRadians(halfa))); - double y0=centery - (radiusy)*(Math.sin(Math.toRadians(halfa))); - svg.append("\r\n"); - } - } - lasta= angle; - x= x1; - y= y1; - } - } - svg.append("\r\n"); - - return svg.toString(); - } - /** - * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), - * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). - * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, - * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) - * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). - * A value of 0 causes the arc to be drawn in a "negative-angle" direction - * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). - */ - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - - cooxml.append(""); cooxml.append("\r\n"); - - return cooxml; - } + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + StringBuffer svg = new StringBuffer(); + if (series.size() == 0) { + Logger.logErr("Pie.getSVG: error in series"); + return ""; + } + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + final int LABELOFFSET = 15; + + int n = series.size(); + double centerx = 0.0, centery = 0.0, radius = 0.0; + double radiusy = 0.0; + + centerx = w / 2 + chartMetrics.get("x"); + centery = h / 2 + chartMetrics.get("y"); + svg.append("\r\n"); + svg.append("\r\n"); + svg.append("\r\n"); + svg.append("\r\n"); + svg.append("\r\n"); + svg.append("\r\n"); + + // if have any series - should :) + radius = Math.min(w, h) / 2; + //radius= Math.min(w, h); // ????????????? just an estimate? + double depth3d = 0; // only used for 3d + radiusy = radius * 2 / 3; + depth3d = radius / 5.5; + if (n > 0) { + double[] oneseries = (double[]) series.get(0); //FOR PIE CHARTS ONLY 1 SERIES VALUE POINTS ARE USED ********************************** + String[] curranges = (String[]) s.getSeriesRanges().get(0); + double total = 0.0; + for (int j = 0; j < oneseries.length; j++) { // get total in order to calculate percentages + total += oneseries[j]; + } + if (dls.length == 1) { // no series-specific data labels; expand to entire series for loop below + int dl = dls[0]; + dls = new int[oneseries.length]; + for (int i = 0; i < dls.length; i++) + dls[i] = dl; + } + String path = ""; + double x = centerx + radius; + double y = centery; + double percentage = 0; + double lasta = 0; + int largearcflag = 0; + int sweepflag = 0; + + // Now create each pie wedge according to it's percentage value + for (int j = 0; j < oneseries.length; j++) { + if (total > 0) + percentage = oneseries[j] / total; + double angle = (percentage * 360) + lasta; + double x1 = centerx + radius * (Math.cos(Math.toRadians(angle))); + double y1 = centery - radiusy * (Math.sin(Math.toRadians(angle))); + if ((percentage * 360)/*angle*/ > 180) { + largearcflag = 1; + } else + largearcflag = 0; + + if (y1 >= centery) { // the lower portion of a 3d pie chart that makes it look 3d + double x0 = x; + double y0 = y; + if (centery > y) { // lower portion has to be below origin + y0 = centery; + x0 = centerx - radius; + } + path = "M" + x0 + "," + y0 + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + x1 + " " + (y1 + depth3d); + path += " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + "1" + " " + x0 + " " + (y0 + depth3d) + " Z"; + svg.append("\r\n"); + } + path = "M" + centerx + " " + centery + " L" + x + " " + y + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z"; + svg.append("\r\n"); + + String l = getSVGDataLabels(dls, axisMetrics, oneseries[j], percentage, j, legends, categories[j].toString()); + if (l != null) { + // apparently labels are outside of wedge unless angle is >= 30 ... + // category labels + double halfa = ((percentage / 2) * 360) + lasta; // center in area + double x2, y2; + if (percentage < .3) { // display label on outside with leader lines + x2 = centerx + (radius + LABELOFFSET) * (Math.cos(Math.toRadians(halfa))); + y2 = centery - (radiusy + LABELOFFSET) * (Math.sin(Math.toRadians(halfa))); + } else { // display label within wedge for > 30% + x2 = centerx + (radius / 2) * (Math.cos(Math.toRadians(halfa))); + y2 = centery - (radiusy / 2) * (Math.sin(Math.toRadians(halfa))); + } + String style = ""; + if (percentage >= .3) + style = " style='text-anchor: middle;'"; + else if (lasta > 90 && lasta < 270) { // right-align text for wedges on left side of pie + style = " style='text-anchor: end;'"; + // TODO: dec x2 + } + svg.append("" + l + "\r\n"); + // leaderline - not exactly like Excel's but ... :) do when NOT putting text within wedge + if (percentage < .3) { + double x0 = centerx + (radius) * (Math.cos(Math.toRadians(halfa))); + double y0 = centery - (radiusy) * (Math.sin(Math.toRadians(halfa))); + svg.append("\r\n"); + } + } + lasta = angle; + x = x1; + y = y1; + } + } + svg.append("\r\n"); + + return svg.toString(); + } + /** + * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), + * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). + * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, + * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) + * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). + * A value of 0 causes the arc to be drawn in a "negative-angle" direction + * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). + */ + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/PieChart.java b/src/main/java/io/starter/formats/XLS/charts/PieChart.java index b88dd0f..0e82728 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PieChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/PieChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,84 +22,88 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.ChartSeriesHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; public class PieChart extends ChartType { - Pie pie= null; - public PieChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - pie= (Pie) charttype; - } - /** return the JSON that - * + Pie pie = null; + + public PieChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + pie = (Pie) charttype; + } + + /** + * return the JSON that + * * @param seriesvals * @param range * @return */ - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) - throws JSONException { - JSONObject chartObjectJSON= new JSONObject(); - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // TODO: check out labels options chosen: default label is Category name + percentage ... - double yMax= 0.0, yMin= 0.0; - double len= 0.0; - JSONArray pieSeries= new JSONArray(); - try { - String range= series[0].getCategoryRange(); // 20080516 KSC: retrieve cat range instead of parameter - JSONArray cats= CellRange.getValuesAsJSON(range, wbh); // parse category range into JSON Array - JSONArray seriesvals= CellRange.getValuesAsJSON(series[0].getSeriesRange(), wbh); - double piesum= 0; - for (int k= 0; k < seriesvals.length(); k++) { - piesum+=seriesvals.getDouble(k); - yMax= Math.max(yMax, seriesvals.getDouble(k)); - yMin= Math.min(yMin, seriesvals.getDouble(k)); - } - double percent= 100/piesum; - for (int k= 0; k < seriesvals.length(); k++) { - JSONObject piepoint= new JSONObject(); - piepoint.put("y", seriesvals.getDouble(k)); - piepoint.put("text", cats.getString(k) + "\n" + Math.round(percent*seriesvals.getDouble(k)) + "%"); - piepoint.put("color", FormatConstants.SVGCOLORSTRINGS[series[k].getPieChartSliceColor(k)]); - piepoint.put("stroke", getDarkColor()); - pieSeries.put(piepoint); - } - len= seriesvals.length(); - } catch (Exception e) { - // TODO: warning ...? - } - // 20090717 KSC: input outside of try/catch to always set - minMax[0]= new Double(yMin); - minMax[1]= new Double(yMax); - minMax[2]= new Double(len); - chartObjectJSON.put("Series", pieSeries); - chartObjectJSON.put("SeriesFills", ""); // not applicable for pie charts; color is set above - return chartObjectJSON; + public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) + throws JSONException { + JSONObject chartObjectJSON = new JSONObject(); + // Type JSON + chartObjectJSON.put("type", this.getTypeJSON()); + + // TODO: check out labels options chosen: default label is Category name + percentage ... + double yMax = 0.0, yMin = 0.0; + double len = 0.0; + JSONArray pieSeries = new JSONArray(); + try { + String range = series[0].getCategoryRange(); // 20080516 KSC: retrieve cat range instead of parameter + JSONArray cats = CellRange.getValuesAsJSON(range, wbh); // parse category range into JSON Array + JSONArray seriesvals = CellRange.getValuesAsJSON(series[0].getSeriesRange(), wbh); + double piesum = 0; + for (int k = 0; k < seriesvals.length(); k++) { + piesum += seriesvals.getDouble(k); + yMax = Math.max(yMax, seriesvals.getDouble(k)); + yMin = Math.min(yMin, seriesvals.getDouble(k)); + } + double percent = 100 / piesum; + for (int k = 0; k < seriesvals.length(); k++) { + JSONObject piepoint = new JSONObject(); + piepoint.put("y", seriesvals.getDouble(k)); + piepoint.put("text", cats.getString(k) + "\n" + Math.round(percent * seriesvals.getDouble(k)) + "%"); + piepoint.put("color", FormatConstants.SVGCOLORSTRINGS[series[k].getPieChartSliceColor(k)]); + piepoint.put("stroke", getDarkColor()); + pieSeries.put(piepoint); + } + len = seriesvals.length(); + } catch (Exception e) { + // TODO: warning ...? + } + // 20090717 KSC: input outside of try/catch to always set + minMax[0] = new Double(yMin); + minMax[1] = new Double(yMax); + minMax[2] = new Double(len); + chartObjectJSON.put("Series", pieSeries); + chartObjectJSON.put("SeriesFills", ""); // not applicable for pie charts; color is set above + return chartObjectJSON; } - /** - * return the type JSON for this Chart Object - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON= new JSONObject(); - typeJSON.put("labelOffset", -25); // default - typeJSON.put("precision", 0); // default - rounds percentages up - typeJSON.put("type", "Pie"); - // TODO: Interpret distance ... + + /** + * return the type JSON for this Chart Object + * + * @return + */ + public JSONObject getTypeJSON() throws JSONException { + JSONObject typeJSON = new JSONObject(); + typeJSON.put("labelOffset", -25); // default + typeJSON.put("precision", 0); // default - rounds percentages up + typeJSON.put("type", "Pie"); + // TODO: Interpret distance ... /* DataFormat df= this.getParentChart().getChartFormat().getDataFormatRec(); for (int z= 0; z < df.chartArr.size(); z++) { @@ -110,151 +114,156 @@ public JSONObject getTypeJSON() throws JSONException { // typeJSON.put("percentage", ((PieFormat) b).getPercentage()); // TODO: Convert Excel percentage to Dojo labelOffset */ - return typeJSON; - } - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + return typeJSON; + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - StringBuffer svg= new StringBuffer(); - // if have any series - should :) - if (series.size()==0) { - Logger.logErr("Pie.getSVG: error in series"); - return ""; - } - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - final int LABELOFFSET= 15; + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + StringBuffer svg = new StringBuffer(); + // if have any series - should :) + if (series.size() == 0) { + Logger.logErr("Pie.getSVG: error in series"); + return ""; + } + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + final int LABELOFFSET = 15; + + int n = series.size(); + double centerx = 0.0, centery = 0.0, radius = 0.0; + double radiusy = 0.0; + centerx = w / 2 + chartMetrics.get("x"); + centery = h / 2 + chartMetrics.get("y"); + svg.append("\r\n"); - int n= series.size(); - double centerx= 0.0, centery= 0.0, radius= 0.0; - double radiusy= 0.0; - centerx= w/2+ chartMetrics.get("x"); - centery= h/2+ chartMetrics.get("y"); - svg.append("\r\n"); - - radius= (Math.min(w, h))/2; - //radius= Math.min(w, h)/2; // too small - //radius= Math.min(w, h); // ????????????? just an estimate? - radiusy= radius; - if (n>0) { - double[] oneseries= (double[])series.get(0); //FOR PIE CHARTS ONLY 1 SERIES VALUE POINTS ARE USED ********************************** - String[] curranges= (String[])s.getSeriesRanges().get(0); - double total= 0.0; - for (int j= 0; j < oneseries.length; j++) { // get total in order to calculate percentages - total+= oneseries[j]; - } - if (dls.length==1) { // no series-specific data labels; expand to entire series for loop below - int dl= dls[0]; - dls= new int[oneseries.length]; - for (int i= 0; i < dls.length; i++) - dls[i]= dl; - } - String path= ""; - double x= centerx+radius; - double y= centery; - double percentage= 0; - double lasta= 0; - int largearcflag= 0; - int sweepflag= 0; - - // Now create each pie wedge according to it's percentage value - for (int j= 0; j < oneseries.length; j++) { - if (total > 0) - percentage= oneseries[j]/total; - double angle= (percentage*360) + lasta; - double x1=centerx + radius*(Math.cos(Math.toRadians(angle))); - double y1=centery - radiusy*(Math.sin(Math.toRadians(angle))); - if ((percentage*360)/*angle*/ > 180) { - largearcflag= 1; - } else - largearcflag= 0; - path= "M" + centerx + " " + centery + " L" + x + " " + y + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z"; - svg.append("\r\n"); + radius = (Math.min(w, h)) / 2; + //radius= Math.min(w, h)/2; // too small + //radius= Math.min(w, h); // ????????????? just an estimate? + radiusy = radius; + if (n > 0) { + double[] oneseries = (double[]) series.get(0); //FOR PIE CHARTS ONLY 1 SERIES VALUE POINTS ARE USED ********************************** + String[] curranges = (String[]) s.getSeriesRanges().get(0); + double total = 0.0; + for (int j = 0; j < oneseries.length; j++) { // get total in order to calculate percentages + total += oneseries[j]; + } + if (dls.length == 1) { // no series-specific data labels; expand to entire series for loop below + int dl = dls[0]; + dls = new int[oneseries.length]; + for (int i = 0; i < dls.length; i++) + dls[i] = dl; + } + String path = ""; + double x = centerx + radius; + double y = centery; + double percentage = 0; + double lasta = 0; + int largearcflag = 0; + int sweepflag = 0; + + // Now create each pie wedge according to it's percentage value + for (int j = 0; j < oneseries.length; j++) { + if (total > 0) + percentage = oneseries[j] / total; + double angle = (percentage * 360) + lasta; + double x1 = centerx + radius * (Math.cos(Math.toRadians(angle))); + double y1 = centery - radiusy * (Math.sin(Math.toRadians(angle))); + if ((percentage * 360)/*angle*/ > 180) { + largearcflag = 1; + } else + largearcflag = 0; + path = "M" + centerx + " " + centery + " L" + x + " " + y + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z"; + svg.append("\r\n"); // " path='' d='" + path + "' fill-rule='evenodd' transform='matrix(1, 0, 0, 1, 0, 0)'/>\r\n"); - - String l= getSVGDataLabels(dls, axisMetrics, oneseries[j], percentage, j, legends, categories[j].toString()); - if (l!=null) { - // apparently labels are outside of wedge unless angle is >= 30 ... - // category labels - double halfa= ((percentage/2)*360) + lasta; // center in area - double x2, y2; - if (percentage < .3) { // display label on outside with leader lines - x2=centerx + (radius+LABELOFFSET)*(Math.cos(Math.toRadians(halfa))); - y2=centery - (radiusy+LABELOFFSET)*(Math.sin(Math.toRadians(halfa))); - } else { // display label within wedge for > 30% - x2=centerx + (radius/2)*(Math.cos(Math.toRadians(halfa))); - y2=centery - (radiusy/2)*(Math.sin(Math.toRadians(halfa))); - } - String style= ""; - if (percentage >= .3) - style=" style='text-anchor: middle;'"; - else if (lasta > 90 && lasta < 270) { // right-align text for wedges on left side of pie - style= " style='text-anchor: end;'"; - // TODO: dec x2 - } - svg.append("" + l + "\r\n"); - // leaderline - not exactly like Excel's but ... :) do when NOT putting text within wedge - if (percentage < .3) { - double x0=centerx + (radius)*(Math.cos(Math.toRadians(halfa))); - double y0=centery - (radiusy)*(Math.sin(Math.toRadians(halfa))); - svg.append("\r\n"); - } - } - lasta= angle; - x= x1; - y= y1; - } - } - svg.append("\r\n"); - - return svg.toString(); - } - /** - * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), - * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). - * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, - * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) - * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). - * A value of 0 causes the arc to be drawn in a "negative-angle" direction - * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). - */ - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); + String l = getSVGDataLabels(dls, axisMetrics, oneseries[j], percentage, j, legends, categories[j].toString()); + if (l != null) { + // apparently labels are outside of wedge unless angle is >= 30 ... + // category labels + double halfa = ((percentage / 2) * 360) + lasta; // center in area + double x2, y2; + if (percentage < .3) { // display label on outside with leader lines + x2 = centerx + (radius + LABELOFFSET) * (Math.cos(Math.toRadians(halfa))); + y2 = centery - (radiusy + LABELOFFSET) * (Math.sin(Math.toRadians(halfa))); + } else { // display label within wedge for > 30% + x2 = centerx + (radius / 2) * (Math.cos(Math.toRadians(halfa))); + y2 = centery - (radiusy / 2) * (Math.sin(Math.toRadians(halfa))); + } + String style = ""; + if (percentage >= .3) + style = " style='text-anchor: middle;'"; + else if (lasta > 90 && lasta < 270) { // right-align text for wedges on left side of pie + style = " style='text-anchor: end;'"; + // TODO: dec x2 + } + svg.append("" + l + "\r\n"); + // leaderline - not exactly like Excel's but ... :) do when NOT putting text within wedge + if (percentage < .3) { + double x0 = centerx + (radius) * (Math.cos(Math.toRadians(halfa))); + double y0 = centery - (radiusy) * (Math.sin(Math.toRadians(halfa))); + svg.append("\r\n"); + } + } + lasta = angle; + x = x1; + y = y1; + } + } + svg.append("\r\n"); - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + return svg.toString(); + } + /** + * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), + * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). + * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, + * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) + * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). + * A value of 0 causes the arc to be drawn in a "negative-angle" direction + * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). + */ + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); - // TODO: firstSLiceAng + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + + // TODO: firstSLiceAng // if (pie.getAnStart()!=0) - cooxml.append(""); - cooxml.append(""); cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } - return cooxml; - } - } diff --git a/src/main/java/io/starter/formats/XLS/charts/PieFormat.java b/src/main/java/io/starter/formats/XLS/charts/PieFormat.java index eb1b6f2..fcd5690 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PieFormat.java +++ b/src/main/java/io/starter/formats/XLS/charts/PieFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,25 +24,27 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * PieFormat: Position of the Pie Slice(0x100b) - * - * percentage 2 distance of pie slice from center of pie as % - * + *

                + * percentage 2 distance of pie slice from center of pie as % */ public class PieFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 28305957039802849L; - short percentage= 0; - public void init() { - super.init(); - this.getData(); - percentage= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + /** + * serialVersionUID + */ + private static final long serialVersionUID = 28305957039802849L; + short percentage = 0; + + public void init() { + super.init(); + this.getData(); + percentage = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + + } - } - // 20070716 KSC: Need to create new records + // 20070716 KSC: Need to create new records public static XLSRecord getPrototype() { PieFormat pf = new PieFormat(); pf.setOpcode(PIEFORMAT); @@ -50,20 +52,30 @@ public static XLSRecord getPrototype() { pf.init(); return pf; } - private byte[] PROTOTYPE_BYTES = new byte[] {0,0}; + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; + // 20070717 KSC: get/set methods for format options - public short getPercentage() { return percentage; } - public void setPercentage(short p) { - percentage= p; - updateRecord(); + public short getPercentage() { + return percentage; } + + public void setPercentage(short p) { + percentage = p; + updateRecord(); + } + private void updateRecord() { byte[] b = ByteTools.shortToLEBytes(percentage); this.getData()[0] = b[0]; this.getData()[1] = b[1]; } - public String getOptionsXML() { - return " Percentage=\"" + percentage + "\""; + + public String getOptionsXML() { + return " Percentage=\"" + percentage + "\""; + } + + public String toString() { + return "PieFormat: Percentage=" + percentage; } - public String toString() { return "PieFormat: Percentage=" + percentage; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/PivotChartBits.java b/src/main/java/io/starter/formats/XLS/charts/PivotChartBits.java index 7e2c6f7..2cdaef1 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PivotChartBits.java +++ b/src/main/java/io/starter/formats/XLS/charts/PivotChartBits.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,41 +23,46 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; -/** - PIVOTCHARTBITS: PivotChart Bits (859h) - Introduced in Excel 9 (2000), this BIFF record is an FRT - record for Charts. This stores flags for a PivotChart. - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0859h - 6 grbitFrt 2 FRT flags; must be zero - 8 grbit 2 Option flags for PivotCharts (see description below) - 10 (unused) 6 Reserved; must be zero - The grbit field contains the following PivotChart option flags: - Bits Mask Flag Name Contents - 0 0001h fGXHide =1 if the field buttons are hidden =0 otherwise - 15-1 FFFEh (unused) Reserved; must be zero +/** + * PIVOTCHARTBITS: PivotChart Bits (859h) + * Introduced in Excel 9 (2000), this BIFF record is an FRT + * record for Charts. This stores flags for a PivotChart. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0859h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 grbit 2 Option flags for PivotCharts (see description below) + * 10 (unused) 6 Reserved; must be zero + *

                + * The grbit field contains the following PivotChart option flags: + * Bits Mask Flag Name Contents + * 0 0001h fGXHide =1 if the field buttons are hidden =0 otherwise + * 15-1 FFFEh (unused) Reserved; must be zero */ public class PivotChartBits extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4587483948421928667L; - public void init() { - super.init(); - } - // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[] {}; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4587483948421928667L; + + public void init() { + super.init(); + } + + // TODO: Prototype Bytes + private byte[] PROTOTYPE_BYTES = new byte[]{}; + public static XLSRecord getPrototype() { - PivotChartBits pcb= new PivotChartBits(); - pcb.setOpcode(PIVOTCHARTBITS); - pcb.setData(pcb.PROTOTYPE_BYTES); - pcb.init(); + PivotChartBits pcb = new PivotChartBits(); + pcb.setOpcode(PIVOTCHARTBITS); + pcb.setData(pcb.PROTOTYPE_BYTES); + pcb.init(); return pcb; } + private void updateRecord() { } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/PivotChartLink.java b/src/main/java/io/starter/formats/XLS/charts/PivotChartLink.java index e1e7bcc..972312c 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PivotChartLink.java +++ b/src/main/java/io/starter/formats/XLS/charts/PivotChartLink.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,35 +23,40 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; + /** - PIVOTCHARTLINK: Pivot Chart Link (861h) - Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. - This record stores the link to a PivotTable for a PivotChart. Similar in function - to SXVIEWLINK but used only during copy & paste of a chart via BIFF. - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0861h - 6 grbitFrt 2 FRT flags; must be zero - 8 ai var same as AI record + * PIVOTCHARTLINK: Pivot Chart Link (861h) + * Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. + * This record stores the link to a PivotTable for a PivotChart. Similar in function + * to SXVIEWLINK but used only during copy & paste of a chart via BIFF. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0861h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 ai var same as AI record */ public class PivotChartLink extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6202325538826559210L; - public void init() { - super.init(); - } // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[] {}; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -6202325538826559210L; + + public void init() { + super.init(); + } // TODO: Prototype Bytes + + private byte[] PROTOTYPE_BYTES = new byte[]{}; + public static XLSRecord getPrototype() { - PivotChartLink pcl= new PivotChartLink(); - pcl.setOpcode(PIVOTCHARTLINK); - pcl.setData(pcl.PROTOTYPE_BYTES); - pcl.init(); + PivotChartLink pcl = new PivotChartLink(); + pcl.setOpcode(PIVOTCHARTLINK); + pcl.setData(pcl.PROTOTYPE_BYTES); + pcl.init(); return pcl; } + private void updateRecord() { } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/PlotArea.java b/src/main/java/io/starter/formats/XLS/charts/PlotArea.java index f25a239..b5cde5c 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PlotArea.java +++ b/src/main/java/io/starter/formats/XLS/charts/PlotArea.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,16 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * PlotArea: Frame Belongs to Plot Area(0x1035) - * */ -public class PlotArea extends GenericChartObject implements ChartObject{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 834805225367462259L; +public class PlotArea extends GenericChartObject implements ChartObject { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 834805225367462259L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.java b/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.java index 778d5c5..1f92b81 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.java +++ b/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,16 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * PlotGrowth: Font Scale Factors(0x1064) - * */ public class PlotGrowth extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 514340954251194805L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 514340954251194805L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Pos.java b/src/main/java/io/starter/formats/XLS/charts/Pos.java index 183e081..eb73b1d 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Pos.java +++ b/src/main/java/io/starter/formats/XLS/charts/Pos.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,270 +22,282 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.MSODrawing; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * Pos: Position Information(0x104f) - * - * + *

                + *

                * for TextDisp, sets the label position as an offset from the default position * for PlotArea, used only for main axis + describes the plot-area bounding box; the tMainPlotArea in the SHTPROPS rec must be 1 or the POS rec is ignored * for Legend, describes legend pos + size - * - * 4 rndTopLt 2 for PlotArea, TextDisp= 2; legend= 5 (what=3??? Data Table!) - * 6 rndTopRt 2 for PlotArea, TextDisp= 2; legend: 1= use x2 and y2 for legend size; 2= autosize legend (ignore x2 + y2; if so, the fAutoSize bit of FRAME rec should be 1) - * 8 x1 4 for PlotArea, x coord of bounding box; for TextDisp, horiz. offset from default pos; for legend, x coord in 1/4000 - * 12 y1 4 for PlotArea, y coord of bounding box; for TextDisp, vert. offset from default pos; for legend, y coord " " - * 16 x2 4 for PlotArea, w of bounding box; for TextDisp, ignored; for legend= width - * 20 y2 4 for PlotArea, h of bounding box; ""; for legend= height - * - * Above is not correct; - * Correct Information: - * mdTopLt (2 bytes): A PositionMode structure that specifies the positioning mode for the upper-left corner of a legend, - * an attached label, or the plot area. The valid combinations of mdTopLt and mdBotRt and the meaning of x1, y1, x2, y2 - * are specified in the Valid Combinations of mdTopLt and mdBotRt by Type table. - mdBotRt (2 bytes): A PositionMode structure that specifies the positioning mode for the lower-right corner of a legend, - an attached label, or the plot area. The valid combinations of mdTopLt and mdBotRt and the meaning of x1, y1, x2, y2 - are specified in the following table. - - x1 (2 bytes): A signed integer that specifies a position. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. - unused1 (2 bytes): Undefined and MUST be ignored. - y1 (2 bytes): A signed integer that specifies a position. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. - unused2 (2 bytes): Undefined and MUST be ignored. - x2 (2 bytes): A signed integer that specifies a width. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. - unused3 (2 bytes): Undefined and MUST be ignored. - y2 (2 bytes): A signed integer that specifies a height. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. - unused4 (2 bytes): Undefined and MUST be ignored. - - Table: - Type mdTopLtPosition Mode mdBotRt Position Mode Meaning - plot area (axis group) MDPARENT MDPARENT The values of x1 and y1 specify the horizontal and vertical offsets of the primary axis group's - upper-left corner, relative to the upper-left corner of the chart area, in SPRC. The values of x2 - and y2 specify the width and height of the primary axis group, in SPRC. - legend MDCHART MDABS The values x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, - relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 specify the - width and height of the legend, in points. - legend MDCHART MDPARENT The values of x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, - relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 MUST be ignored. - The size of the legend is determined by the application. - legend MDKTH MDPARENT The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table. - - attached label MDPARENT MDPARENT The meaning of x1 and y1 is specified in the Type of Attached Label table. x2 and y2 MUST be ignored. - The size of the attached label is determined by the application. - - - The PositionMode structure specifies positioning mode for position information saved in a Pos record. - Name Value Meaning - MDFX 0x0000 Relative position to the chart, in points. - MDABS 0x0001 Absolute width and height in points. It can only be applied to the mdBotRt field of Pos. - MDPARENT 0x0002 Owner of Pos determines how to interpret the position data. - MDKTH 0x0003 Offset to default position, in 1/1000th of the plot area size. - MDCHART 0x0005 Relative position to the chart, in SPRC (A SPRC is a unit of measurement that is 1/4000th of the height or width of the chart). - - Type of Attached Label Meaning - Chart title The value of x1 and y1 specify the horizontal and vertical offset of the title, relative to its default position, in SPRC. - Axis title The value of x1 and y1 specify the offset of the title along the direction of a specific axis. The value of x1 specifies an offset along the category (3) axis, date axis, or horizontal value axis. The value of y1 specifies an offset along the value axis. Both offsets are relative to the title's default position, in 1/1000th of the axis length. - Data label If the chart is not a pie chart group or a radar chart group, x1 and y1 specify the offset of the label along the direction of the specific axis. - The x1 value is an offset along the category (3) axis, date axis, or horizontal value axis. - The y1 value is an offset along the value axis, opposite to the direction of the value axis. - Both offsets are relative to the label's default position, in 1/1000th of the axis length. - For a pie chart group, the value of x1 specifies the clockwise angle, in degrees, and the value of y1 specifies the radius offset of the label relative to its default position, in 1/1000th of the pie radius length. A label moved toward the pie center has a negative radius offset. - For a radar chart group, the values of x1 and y1 specify the horizontal and vertical offset of the label relative to its default position, in 1/1000th of the axis length. + *

                + * 4 rndTopLt 2 for PlotArea, TextDisp= 2; legend= 5 (what=3??? Data Table!) + * 6 rndTopRt 2 for PlotArea, TextDisp= 2; legend: 1= use x2 and y2 for legend size; 2= autosize legend (ignore x2 + y2; if so, the fAutoSize bit of FRAME rec should be 1) + * 8 x1 4 for PlotArea, x coord of bounding box; for TextDisp, horiz. offset from default pos; for legend, x coord in 1/4000 + * 12 y1 4 for PlotArea, y coord of bounding box; for TextDisp, vert. offset from default pos; for legend, y coord " " + * 16 x2 4 for PlotArea, w of bounding box; for TextDisp, ignored; for legend= width + * 20 y2 4 for PlotArea, h of bounding box; ""; for legend= height + *

                + * Above is not correct; + * Correct Information: + * mdTopLt (2 bytes): A PositionMode structure that specifies the positioning mode for the upper-left corner of a legend, + * an attached label, or the plot area. The valid combinations of mdTopLt and mdBotRt and the meaning of x1, y1, x2, y2 + * are specified in the Valid Combinations of mdTopLt and mdBotRt by Type table. + * mdBotRt (2 bytes): A PositionMode structure that specifies the positioning mode for the lower-right corner of a legend, + * an attached label, or the plot area. The valid combinations of mdTopLt and mdBotRt and the meaning of x1, y1, x2, y2 + * are specified in the following table. + *

                + * x1 (2 bytes): A signed integer that specifies a position. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. + * unused1 (2 bytes): Undefined and MUST be ignored. + * y1 (2 bytes): A signed integer that specifies a position. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. + * unused2 (2 bytes): Undefined and MUST be ignored. + * x2 (2 bytes): A signed integer that specifies a width. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. + * unused3 (2 bytes): Undefined and MUST be ignored. + * y2 (2 bytes): A signed integer that specifies a height. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. + * unused4 (2 bytes): Undefined and MUST be ignored. + *

                + * Table: + * Type mdTopLtPosition Mode mdBotRt Position Mode Meaning + * plot area (axis group) MDPARENT MDPARENT The values of x1 and y1 specify the horizontal and vertical offsets of the primary axis group's + * upper-left corner, relative to the upper-left corner of the chart area, in SPRC. The values of x2 + * and y2 specify the width and height of the primary axis group, in SPRC. + * legend MDCHART MDABS The values x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, + * relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 specify the + * width and height of the legend, in points. + * legend MDCHART MDPARENT The values of x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, + * relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 MUST be ignored. + * The size of the legend is determined by the application. + * legend MDKTH MDPARENT The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table. + *

                + * attached label MDPARENT MDPARENT The meaning of x1 and y1 is specified in the Type of Attached Label table. x2 and y2 MUST be ignored. + * The size of the attached label is determined by the application. + *

                + *

                + * The PositionMode structure specifies positioning mode for position information saved in a Pos record. + * Name Value Meaning + * MDFX 0x0000 Relative position to the chart, in points. + * MDABS 0x0001 Absolute width and height in points. It can only be applied to the mdBotRt field of Pos. + * MDPARENT 0x0002 Owner of Pos determines how to interpret the position data. + * MDKTH 0x0003 Offset to default position, in 1/1000th of the plot area size. + * MDCHART 0x0005 Relative position to the chart, in SPRC (A SPRC is a unit of measurement that is 1/4000th of the height or width of the chart). + *

                + * Type of Attached Label Meaning + * Chart title The value of x1 and y1 specify the horizontal and vertical offset of the title, relative to its default position, in SPRC. + * Axis title The value of x1 and y1 specify the offset of the title along the direction of a specific axis. The value of x1 specifies an offset along the category (3) axis, date axis, or horizontal value axis. The value of y1 specifies an offset along the value axis. Both offsets are relative to the title's default position, in 1/1000th of the axis length. + * Data label If the chart is not a pie chart group or a radar chart group, x1 and y1 specify the offset of the label along the direction of the specific axis. + * The x1 value is an offset along the category (3) axis, date axis, or horizontal value axis. + * The y1 value is an offset along the value axis, opposite to the direction of the value axis. + * Both offsets are relative to the label's default position, in 1/1000th of the axis length. + * For a pie chart group, the value of x1 specifies the clockwise angle, in degrees, and the value of y1 specifies the radius offset of the label relative to its default position, in 1/1000th of the pie radius length. A label moved toward the pie center has a negative radius offset. + * For a radar chart group, the values of x1 and y1 specify the horizontal and vertical offset of the label relative to its default position, in 1/1000th of the axis length. */ public class Pos extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7920967716354683818L; - short rndTopLt, rndTopRt; - int x1, y1, x2, y2; - public static final int TYPE_TEXTDISP= 0; - public static final int TYPE_LEGEND= 1; - public static final int TYPE_PLOTAREA= 2; - public static final int TYPE_DATATABLE= 3; - public void init() { - super.init(); - // 0= in points, relative to the position of the chart - // 1= absolute, in points - // 2= parent of this rec determines - // 3= offset to default - // 5= relative, in 1/4000 of the w or h of the chart - rndTopLt= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - rndTopRt= ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - x1= ByteTools.readInt(this.getBytesAt(4, 4)); - y1= ByteTools.readInt(this.getBytesAt(8, 4)); - x2= ByteTools.readInt(this.getBytesAt(12, 4)); - y2= ByteTools.readInt(this.getBytesAt(16, 4)); - } - // TODO: Get def and parse options accordingly! + /** + * serialVersionUID + */ + private static final long serialVersionUID = 7920967716354683818L; + short rndTopLt, rndTopRt; + int x1, y1, x2, y2; + public static final int TYPE_TEXTDISP = 0; + public static final int TYPE_LEGEND = 1; + public static final int TYPE_PLOTAREA = 2; + public static final int TYPE_DATATABLE = 3; + + public void init() { + super.init(); + // 0= in points, relative to the position of the chart + // 1= absolute, in points + // 2= parent of this rec determines + // 3= offset to default + // 5= relative, in 1/4000 of the w or h of the chart + rndTopLt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + rndTopRt = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + x1 = ByteTools.readInt(this.getBytesAt(4, 4)); + y1 = ByteTools.readInt(this.getBytesAt(8, 4)); + x2 = ByteTools.readInt(this.getBytesAt(12, 4)); + y2 = ByteTools.readInt(this.getBytesAt(16, 4)); + } + + // TODO: Get def and parse options accordingly! public static XLSRecord getPrototype(int type) { Pos p = new Pos(); p.setOpcode(POS); - p.setData(p.PROTOTYPE_BYTES); - p.init(); - p.setType(type); + p.setData(p.PROTOTYPE_BYTES); + p.init(); + p.setType(type); return p; } + /** * set the correct bytes for the desired type + * * @param type */ public void setType(int type) { - switch (type) { - case TYPE_PLOTAREA: - case TYPE_TEXTDISP: - this.getData()[0]= 2; - this.getData()[1]= 0; - break; - case TYPE_LEGEND: - this.getData()[0]= 5; - this.getData()[1]= 2; - break; - case TYPE_DATATABLE: - this.getData()[0]= 3; - this.getData()[1]= 0; - } - this.getData()[2]= 2; - this.getData()[3]= 0; + switch (type) { + case TYPE_PLOTAREA: + case TYPE_TEXTDISP: + this.getData()[0] = 2; + this.getData()[1] = 0; + break; + case TYPE_LEGEND: + this.getData()[0] = 5; + this.getData()[1] = 2; + break; + case TYPE_DATATABLE: + this.getData()[0] = 3; + this.getData()[1] = 0; + } + this.getData()[2] = 2; + this.getData()[3] = 0; } - + public void setX(int x) { - x1= x; - byte[] b= ByteTools.cLongToLEBytes(x); - System.arraycopy(b, 0, this.getData(), 4, 4); + x1 = x; + byte[] b = ByteTools.cLongToLEBytes(x); + System.arraycopy(b, 0, this.getData(), 4, 4); } - + public void setY(int y) { - y1= y; - byte[] b= ByteTools.cLongToLEBytes(y); - System.arraycopy(b, 0, this.getData(), 8, 4); + y1 = y; + byte[] b = ByteTools.cLongToLEBytes(y); + System.arraycopy(b, 0, this.getData(), 8, 4); } - + /** - * for legends only, set width of bounds + * for legends only, set width of bounds + * * @param w */ public void setLegendW(int w) { - if (rndTopLt==5 && rndTopRt==1) { - x2= w; - byte[] b= ByteTools.cLongToLEBytes(x2); - System.arraycopy(b, 0, this.getData(), 12, 4); - } // else throw exception? + if (rndTopLt == 5 && rndTopRt == 1) { + x2 = w; + byte[] b = ByteTools.cLongToLEBytes(x2); + System.arraycopy(b, 0, this.getData(), 12, 4); + } // else throw exception? } - - private byte[] PROTOTYPE_BYTES = new byte[] {2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + private byte[] PROTOTYPE_BYTES = new byte[]{2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /** * set autosize bit for legend Pos's * valid only for legend-type Pos's (rndTopLt==5) - * also must set any associated Frame Autosize bits + * also must set any associated Frame Autosize bits * [BugTracker 2844] */ public void setAutosizeLegend() { - if (this.rndTopLt==5) {// it's a legend Pos - this.rndTopRt= 2;// set to autoposition - this.getData()[2]= 2; - } + if (this.rndTopLt == 5) {// it's a legend Pos + this.rndTopRt = 2;// set to autoposition + this.getData()[2] = 2; + } } - + /** * return the legend coordinates (x, y in 1/4000 of the chart height or width, w, h in points) or null, depending upon legend options *
                NOTE: if the w or h are 0, use default. + * * @return int[] x, y, w, h */ public int[] getLegendCoords() { - if (rndTopLt==5 && rndTopRt==1) { + if (rndTopLt == 5 && rndTopRt == 1) { /*The values x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 specify the width and height of the legend, in points.*/ - return new int[] {x1, y1, x2, y2}; - } else if (rndTopLt==5 && rndTopRt==2) { + return new int[]{x1, y1, x2, y2}; + } else if (rndTopLt == 5 && rndTopRt == 2) { /*The values of x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 MUST be ignored. The size of the legend is determined by the application.*/ - return new int[] {x1, y1, 0, 0}; - } else if (rndTopLt==3 && rndTopRt==2) { - /*The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table.*/ - return null; - } - return null; + return new int[]{x1, y1, 0, 0}; + } else if (rndTopLt == 3 && rndTopRt == 2) { + /*The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table.*/ + return null; + } + return null; } + /** * if plot area, * return the Plot Area (x, y, w, h in 1/4000 of the chart height or width) or null, depending upon options -
                NOTES: Pos record of the Axis Parent: -
                The Pos record specifies the position and size of the outer plot area. The outer plot area is the bounding rectangle that includes the axis labels, -
                the axis titles, and data table of the chart. - if attached label, - Chart title The value of x1 and y1 specify the horizontal and vertical offset of the title, relative to its default position, in SPRC. - Axis title The value of x1 and y1 specify the offset of the title along the direction of a specific axis. - The value of x1 specifies an offset along the category axis, date axis, or horizontal value axis. - The value of y1 specifies an offset along the value axis. Both offsets are relative to the title's default position, in 1/1000th of the axis length. - Data label If the chart is not a pie chart group or a radar chart group, x1 and y1 specify the offset of the label along the direction of the specific axis. - The x1 value is an offset along the category axis, date axis, or horizontal value axis. The y1 value is an offset along the value axis, - opposite to the direction of the value axis. Both offsets are relative to the label's default position, in 1/1000th of the axis length. - For a pie chart group, the value of x1 specifies the clockwise angle, in degrees, and the value of y1 specifies the radius offset of the label - relative to its default position, in 1/1000th of the pie radius length. A label moved toward the pie center has a negative radius offset. - For a radar chart group, the values of x1 and y1 specify the horizontal and vertical offset of the label relative to its default position, - in 1/1000th of the axis length. - * @return int[] x, y, w, h + *
                NOTES: Pos record of the Axis Parent: + *
                The Pos record specifies the position and size of the outer plot area. The outer plot area is the bounding rectangle that includes the axis labels, + *
                the axis titles, and data table of the chart. + * if attached label, + * Chart title The value of x1 and y1 specify the horizontal and vertical offset of the title, relative to its default position, in SPRC. + * Axis title The value of x1 and y1 specify the offset of the title along the direction of a specific axis. + * The value of x1 specifies an offset along the category axis, date axis, or horizontal value axis. + * The value of y1 specifies an offset along the value axis. Both offsets are relative to the title's default position, in 1/1000th of the axis length. + * Data label If the chart is not a pie chart group or a radar chart group, x1 and y1 specify the offset of the label along the direction of the specific axis. + * The x1 value is an offset along the category axis, date axis, or horizontal value axis. The y1 value is an offset along the value axis, + * opposite to the direction of the value axis. Both offsets are relative to the label's default position, in 1/1000th of the axis length. + * For a pie chart group, the value of x1 specifies the clockwise angle, in degrees, and the value of y1 specifies the radius offset of the label + * relative to its default position, in 1/1000th of the pie radius length. A label moved toward the pie center has a negative radius offset. + * For a radar chart group, the values of x1 and y1 specify the horizontal and vertical offset of the label relative to its default position, + * in 1/1000th of the axis length. + * + * @return int[] x, y, w, h */ public float[] getCoords() { - if (rndTopLt==2 && rndTopRt==2) { + if (rndTopLt == 2 && rndTopRt == 2) { /* The values of x1 and y1 specify the horizontal and vertical offsets of the primary axis group's upper-left corner, relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 specify the width and height of the primary axis group, in SPRC.*/ - return new float[] {x1, y1, x2, y2}; - } - return null; + return new float[]{x1, y1, x2, y2}; + } + return null; } - + /** * convert a coordinate value in SPRC units to points + * * @param val * @param w * @param h * @return */ public static float convertFromSPRC(float val, float w, float h) { - // try this: - if (w!=0) { - return (float)(val/4000.0)*w; - } else { - return (float)(val/4000.0)*h; - } + // try this: + if (w != 0) { + return (float) (val / 4000.0) * w; + } else { + return (float) (val / 4000.0) * h; + } } - + /** * convert a coordinate value in points to SPRC units *
                Experimental at this point + * * @param val * @param w * @param h * @return */ public static float convertToSPRC(float val, float w, float h) { - // try this: - if (w!=0) { - return (float)(val*4000.0)/w; - } else { - return (float)(val*4000.0)/h; - } + // try this: + if (w != 0) { + return (float) (val * 4000.0) / w; + } else { + return (float) (val * 4000.0) / h; + } } + /** * convert a coordinate value in SPRC units to points + * * @param val * @param w * @param h * @return */ public static float convertFromLabelUnits(float val, float w, float h) { - // try this: - if (w!=0) { - return (float)(val/1000.0)*w; - } else { - return (float)(val/1000.0)*h; - } + // try this: + if (w != 0) { + return (float) (val / 1000.0) * w; + } else { + return (float) (val / 1000.0) * h; + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.java b/src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.java index 917cd15..6adbd2f 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,10 +26,13 @@ public class PyramidBarChart extends Bar3DChart { - public PyramidBarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape= ChartConstants.SHAPEPYRAMID; - chartobj.chartType= PYRAMIDBARCHART; - } - public int getBarShape() { return defaultShape; } + public PyramidBarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + defaultShape = ChartConstants.SHAPEPYRAMID; + chartobj.chartType = PYRAMIDBARCHART; + } + + public int getBarShape() { + return defaultShape; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/PyramidChart.java b/src/main/java/io/starter/formats/XLS/charts/PyramidChart.java index 4a2516c..8d12409 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PyramidChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/PyramidChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,11 +26,13 @@ public class PyramidChart extends Col3DChart { - public PyramidChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape= ChartConstants.SHAPEPYRAMID; - chartobj.chartType= PYRAMIDCHART; -} + public PyramidChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + defaultShape = ChartConstants.SHAPEPYRAMID; + chartobj.chartType = PYRAMIDCHART; + } - public int getBarShape() { return defaultShape; } + public int getBarShape() { + return defaultShape; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Radar.java b/src/main/java/io/starter/formats/XLS/charts/Radar.java index 9df6550..8d692a6 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Radar.java +++ b/src/main/java/io/starter/formats/XLS/charts/Radar.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,41 +22,41 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; + /** * Radar: Chart Group Is a Radar Chart Group(0x103e) - * + *

                * 4 grbit 2 - * - * 0 0x1 fRdrAxLab 1= chart contains radar axis labels - * 1 0x2 fHasShadow 1= this radar series has a shadow + *

                + * 0 0x1 fRdrAxLab 1= chart contains radar axis labels + * 1 0x2 fHasShadow 1= this radar series has a shadow */ public class Radar extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3443368503725347910L; - private short grbit = 0; - private boolean fRdrAxLab= true; - private boolean fHasShadow= false; - public void init() { - super.init(); - chartType= ChartConstants.RADARCHART; - grbit= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fRdrAxLab= (grbit & 0x1) == 0x1; - fHasShadow= (grbit & 0x2) == 0x2; - } - - // 20070703 KSC: taken from Bar.java + /** + * serialVersionUID + */ + private static final long serialVersionUID = 3443368503725347910L; + private short grbit = 0; + private boolean fRdrAxLab = true; + private boolean fHasShadow = false; + + public void init() { + super.init(); + chartType = ChartConstants.RADARCHART; + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + fRdrAxLab = (grbit & 0x1) == 0x1; + fHasShadow = (grbit & 0x2) == 0x2; + } + + // 20070703 KSC: taken from Bar.java private void updateRecord() { byte[] b = ByteTools.shortToLEBytes(grbit); this.getData()[0] = b[0]; this.getData()[1] = b[1]; } - + public static XLSRecord getPrototype() { Radar b = new Radar(); b.setOpcode(RADAR); @@ -64,39 +64,40 @@ public static XLSRecord getPrototype() { b.init(); return b; } - - - private byte[] PROTOTYPE_BYTES = new byte[] {1, 0, 18, 0}; - /** + + private byte[] PROTOTYPE_BYTES = new byte[]{1, 0, 18, 0}; + + /** * @return String XML representation of this chart-type's options */ public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - if (fRdrAxLab) - sb.append(" AxisLabels=\"true\""); - if (fHasShadow) - sb.append(" Shadow=\"true\""); - return sb.toString(); + StringBuffer sb = new StringBuffer(); + if (fRdrAxLab) + sb.append(" AxisLabels=\"true\""); + if (fHasShadow) + sb.append(" Shadow=\"true\""); + return sb.toString(); } + /** * Handle setting options from XML in a generic manner */ public boolean setChartOption(String op, String val) { - boolean bHandled= false; - if (op.equalsIgnoreCase("AxisLabels")) { - fRdrAxLab= val.equals("true"); - grbit= ByteTools.updateGrBit(grbit, fRdrAxLab, 0); - bHandled= true; - } else if (op.equalsIgnoreCase("Shadow")) { - fHasShadow= val.equals("true"); - grbit= ByteTools.updateGrBit(grbit, fHasShadow, 1); - bHandled= true; - } - if (bHandled) - updateRecord(); - return bHandled; + boolean bHandled = false; + if (op.equalsIgnoreCase("AxisLabels")) { + fRdrAxLab = val.equals("true"); + grbit = ByteTools.updateGrBit(grbit, fRdrAxLab, 0); + bHandled = true; + } else if (op.equalsIgnoreCase("Shadow")) { + fHasShadow = val.equals("true"); + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 1); + bHandled = true; + } + if (bHandled) + updateRecord(); + return bHandled; } - - + + } diff --git a/src/main/java/io/starter/formats/XLS/charts/RadarArea.java b/src/main/java/io/starter/formats/XLS/charts/RadarArea.java index 2a0ee99..e6d5250 100644 --- a/src/main/java/io/starter/formats/XLS/charts/RadarArea.java +++ b/src/main/java/io/starter/formats/XLS/charts/RadarArea.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,42 +23,44 @@ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * RadarArea: Chart Group Is a Radar Area Chart Group(0x1040) - * (i.e. a filled radar chart) - * - A - fRdrAxLab (1 bit): A bit that specifies whether category (3) labels are displayed. - - B - fHasShadow (1 bit): A bit that specifies whether the data points in the chart group have shadows. - - reserved (14 bits): MUST be zero, and MUST be ignored. - - unused (2 bytes): Undefined and MUST be ignored. - - * + * (i.e. a filled radar chart) + *

                + * A - fRdrAxLab (1 bit): A bit that specifies whether category (3) labels are displayed. + *

                + * B - fHasShadow (1 bit): A bit that specifies whether the data points in the chart group have shadows. + *

                + * reserved (14 bits): MUST be zero, and MUST be ignored. + *

                + * unused (2 bytes): Undefined and MUST be ignored. */ public class RadarArea extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5731720802332312350L; - private short grbit = 0; - private boolean fRdrAxLab= false; - public void init() { - super.init(); - chartType= ChartConstants.RADARAREACHART; - grbit= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fRdrAxLab= (grbit & 0x1)==0x1; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5731720802332312350L; + private short grbit = 0; + private boolean fRdrAxLab = false; + + public void init() { + super.init(); + chartType = ChartConstants.RADARAREACHART; + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + fRdrAxLab = (grbit & 0x1) == 0x1; + } + private void updateRecord() { byte[] b = ByteTools.shortToLEBytes(grbit); this.getData()[0] = b[0]; this.getData()[1] = b[1]; } - - private byte[] PROTOTYPE_BYTES = new byte[] {1, 0}; + + private byte[] PROTOTYPE_BYTES = new byte[]{1, 0}; + public static XLSRecord getPrototype() { RadarArea r = new RadarArea(); r.setOpcode(RADARAREA); @@ -66,28 +68,29 @@ public static XLSRecord getPrototype() { r.init(); return r; } - - /** + + /** * @return String XML representation of this chart-type's options */ public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - if (fRdrAxLab) - sb.append(" AxisLabels=\"true\""); - return sb.toString(); + StringBuffer sb = new StringBuffer(); + if (fRdrAxLab) + sb.append(" AxisLabels=\"true\""); + return sb.toString(); } + /** * Handle setting options from XML in a generic manner */ public boolean setChartOption(String op, String val) { - boolean bHandled= false; - if (op.equalsIgnoreCase("AxisLabels")) { - fRdrAxLab= val.equals("true"); - grbit= ByteTools.updateGrBit(grbit, fRdrAxLab, 0); - bHandled= true; - } - if (bHandled) - updateRecord(); - return bHandled; + boolean bHandled = false; + if (op.equalsIgnoreCase("AxisLabels")) { + fRdrAxLab = val.equals("true"); + grbit = ByteTools.updateGrBit(grbit, fRdrAxLab, 0); + bHandled = true; + } + if (bHandled) + updateRecord(); + return bHandled; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.java b/src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.java index be99edd..19f486f 100644 --- a/src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,42 +24,46 @@ import io.starter.formats.XLS.WorkBook; -public class RadarAreaChart extends RadarChart { - protected RadarArea radararea = null; +public class RadarAreaChart extends RadarChart { + protected RadarArea radararea = null; - public RadarAreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - radararea = (RadarArea) charttype; - } - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId,String serAxisId) { - StringBuffer cooxml = new StringBuffer(); + public RadarAreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + radararea = (RadarArea) charttype; + } - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); - String style="filled"; - - cooxml.append(""); - // vary colors??? + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + String style = "filled"; - // chart data labels, if any - // TODO: FINISH - // cooxml.append(getDataLabelsOOXML(cf)); + cooxml.append(""); + // vary colors??? - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - cooxml.append(""); cooxml.append("\r\n"); - return cooxml; - } + // chart data labels, if any + // TODO: FINISH + // cooxml.append(getDataLabelsOOXML(cf)); + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + cooxml.append(""); + cooxml.append("\r\n"); + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/RadarChart.java b/src/main/java/io/starter/formats/XLS/charts/RadarChart.java index 4d56174..465c9d3 100644 --- a/src/main/java/io/starter/formats/XLS/charts/RadarChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/RadarChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,202 +22,193 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartSeriesHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.HashMap; + public class RadarChart extends ChartType { - //private Radar radar = null; can be Radar or RadarArea + //private Radar radar = null; can be Radar or RadarArea - public RadarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); + public RadarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); // radar = (Radar) charttype; - } - + } + public void setFilled(boolean isFilled) { - if (isFilled) - chartobj= (RadarArea) RadarArea.getPrototype(); - else - chartobj= (Radar) Radar.getPrototype(); - chartobj.setParentChart(cf.parentChart); - cf.chartArr.remove(0); - cf.chartArr.add(chartobj); + if (isFilled) + chartobj = (RadarArea) RadarArea.getPrototype(); + else + chartobj = (Radar) Radar.getPrototype(); + chartobj.setParentChart(cf.parentChart); + cf.chartArr.remove(0); + cf.chartArr.add(chartobj); } - + public boolean getIsFilled() { - return (chartobj.chartType==ChartConstants.RADARAREACHART); + return (chartobj.chartType == ChartConstants.RADARAREACHART); + } + + + /** + * returns SVG to represent the actual chart object i.e. the representation + * of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, + * max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, + HashMap axisMetrics, ChartSeries s) { + double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is + // constant at x origin unless + // reversed + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then + // increases from there + // y value for each point= h/YMAX + if (series.size() == 0) { + Logger.logErr("Radar.getSVG: error in series"); + return ""; + } + StringBuffer svg = new StringBuffer(); + + // obtain data label, marker info for chart + series colors + int[] dls = getDataLabelInts(); // get array of data labels (can be + // specific per series ...) + int[] markers = getMarkerFormats(); // get an array of marker formats + // per series + int n = series.size(); + int nseries = categories.length; + + double centerx = w / 2 + x; + double centery = h / 2 + y; + double percentage = 1.0 / nseries; // divide into equal sections + double radius = Math.min(w, h) / 2.3; // should take up almost entire + // w/h of chart + + svg.append("\r\n"); + // define marker shapes for later use + svg.append(MarkerFormat.getMarkerSVGDefs()); + // for each series + for (int i = 0; i < n; i++) { + String points = ""; + String labels = ""; + double angle = 90; // starts straight up + double[] curseries = (double[]) series.get(i); + String[] curranges = (String[]) s.getSeriesRanges().get(i); + double x0 = 0, y0 = 0; + for (int j = 0; j < curseries.length; j++) { + // get next point as a percentage of the radius + double r = radius * (curseries[j] / (max - min)); + double x1 = centerx + r * (Math.cos(Math.toRadians(angle))); // + double y1 = centery - r * (Math.sin(Math.toRadians(angle))); + if (j == 0) { // save initial points so can close the loop at + // end + x0 = x1; + y0 = y1; + } + points += x1 + "," + y1 + " "; + String l = getSVGDataLabels(dls, axisMetrics, curseries[j], + percentage, j, legends, categories[j].toString()); + if (l != null) { + double labelx1 = centerx + (r + 5) + * (Math.cos(Math.toRadians(angle))); // + double labely1 = centery - (r + 5) + * (Math.sin(Math.toRadians(angle))); + labels += "" + l + "\r\n"; + } + angle -= (percentage * 360); // next point on next category + // radial line + } + // close loop + points += x0 + "," + y0; + // 1st line is black + svg.append("\r\n"); + // 2nd line is the series color + svg.append("\r\n"); + // Markers, if any, along data points in series + if (markers[i] > 0) { + String[] markerpoints = points.split(" "); + for (int j = 0; j < markerpoints.length; j++) { + String markerpoint = markerpoints[j]; + String[] xy = markerpoint.split(","); + double xx = Double.valueOf(xy[0]); + double yy = Double.valueOf(xy[1]); + svg.append(MarkerFormat.getMarkerSVG(xx, yy, + seriescolors[i], markers[i]) + "\r\n"); + } + } + // labels after lines and markers + svg.append(labels); + } + + svg.append("\r\n"); + return svg.toString(); + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + + String style = "standard"; + if (this.getIsFilled()) + style = "filled"; + else { + int[] markers = getMarkerFormats(); + for (int m : markers) { + if (m != 0) { + style = "marker"; + break; + } + } + } + cooxml.append(""); + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + // TODO: FINISH + // cooxml.append(getDataLabelsOOXML(cf)); + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + cooxml.append(""); + cooxml.append("\r\n"); + return cooxml; } - - - /** - * returns SVG to represent the actual chart object i.e. the representation - * of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics - * maps chart coords in pixels x, y, w, h, canvasw, canvash, min, - * max - * @param axisMetrics - * maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series - * arraylist of double[] series values - * @param seriescolors - * int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, - HashMap axisMetrics, ChartSeries s) { - double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is - // constant at x origin unless - // reversed - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then - // increases from there - // y value for each point= h/YMAX - if (series.size() == 0) { - Logger.logErr("Radar.getSVG: error in series"); - return ""; - } - StringBuffer svg = new StringBuffer(); - - // obtain data label, marker info for chart + series colors - int[] dls = getDataLabelInts(); // get array of data labels (can be - // specific per series ...) - int[] markers = getMarkerFormats(); // get an array of marker formats - // per series - int n = series.size(); - int nseries = categories.length; - - double centerx = w / 2 + x; - double centery = h / 2 + y; - double percentage = 1.0 / nseries; // divide into equal sections - double radius = Math.min(w, h) / 2.3; // should take up almost entire - // w/h of chart - - svg.append("\r\n"); - // define marker shapes for later use - svg.append(MarkerFormat.getMarkerSVGDefs()); - // for each series - for (int i = 0; i < n; i++) { - String points = ""; - String labels = ""; - double angle = 90; // starts straight up - double[] curseries = (double[]) series.get(i); - String[] curranges = (String[]) s.getSeriesRanges().get(i); - double x0 = 0, y0 = 0; - for (int j = 0; j < curseries.length; j++) { - // get next point as a percentage of the radius - double r = radius * (curseries[j] / (max - min)); - double x1 = centerx + r * (Math.cos(Math.toRadians(angle))); // - double y1 = centery - r * (Math.sin(Math.toRadians(angle))); - if (j == 0) { // save initial points so can close the loop at - // end - x0 = x1; - y0 = y1; - } - points += x1 + "," + y1 + " "; - String l = getSVGDataLabels(dls, axisMetrics, curseries[j], - percentage, j, legends, categories[j].toString()); - if (l != null) { - double labelx1 = centerx + (r + 5) - * (Math.cos(Math.toRadians(angle))); // - double labely1 = centery - (r + 5) - * (Math.sin(Math.toRadians(angle))); - labels += "" + l + "\r\n"; - } - angle -= (percentage * 360); // next point on next category - // radial line - } - // close loop - points += x0 + "," + y0; - // 1st line is black - svg.append("\r\n"); - // 2nd line is the series color - svg.append("\r\n"); - // Markers, if any, along data points in series - if (markers[i] > 0) { - String[] markerpoints = points.split(" "); - for (int j = 0; j < markerpoints.length; j++) { - String markerpoint = markerpoints[j]; - String[] xy = markerpoint.split(","); - double xx = Double.valueOf(xy[0]); - double yy = Double.valueOf(xy[1]); - svg.append(MarkerFormat.getMarkerSVG(xx, yy, - seriescolors[i], markers[i]) + "\r\n"); - } - } - // labels after lines and markers - svg.append(labels); - } - - svg.append("\r\n"); - return svg.toString(); - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId,String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - - String style="standard"; - if (this.getIsFilled()) - style= "filled"; - else { - int[] markers= getMarkerFormats(); - for (int m:markers) { - if (m!=0) { - style= "marker"; - break; - } - } - } - cooxml.append(""); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - // TODO: FINISH - // cooxml.append(getDataLabelsOOXML(cf)); - - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - - cooxml.append(""); cooxml.append("\r\n"); - return cooxml; - } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SbaseRef.java b/src/main/java/io/starter/formats/XLS/charts/SbaseRef.java index 9c2833e..b4d8c86 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SbaseRef.java +++ b/src/main/java/io/starter/formats/XLS/charts/SbaseRef.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,16 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * SbaseRef: PivotTable Reference(0x1048) - * */ public class SbaseRef extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7832632121207578401L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 7832632121207578401L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Scatter.java b/src/main/java/io/starter/formats/XLS/charts/Scatter.java index 6d5b0ad..bf73610 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Scatter.java +++ b/src/main/java/io/starter/formats/XLS/charts/Scatter.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,48 +24,51 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * Scatter: Chart Group is a Scatter Chart Group(0x101b) - * + *

                * 4 pcBubbleSizeRatio 2 Percent of largest bubble compared to chart in general default= 100 * 6 wBubbleSize 2 Bubble size: 1= bubble size is area, 2= bubble size is width default= 1 * 8 grbit 2 flags - * + *

                * grbit - * 0 0x1 fBubbles 1= this is a bubble series - * 1 0x2 fShowNegBubbles 1= show negative bubbles - * 2 0x4 fHasShadow 1= bubble series has a shadow + * 0 0x1 fBubbles 1= this is a bubble series + * 1 0x2 fShowNegBubbles 1= show negative bubbles + * 2 0x4 fHasShadow 1= bubble series has a shadow */ -public class Scatter extends GenericChartObject implements ChartObject{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -289334135036242100L; - private short grbit = 0; - private short pcBubbleSizeRatio= 100; - private short wBubbleSize= 1; - private boolean fBubbles= false; - private boolean fShowNegBubbles= false; - private boolean fHasShadow= false; - public void init() { - super.init(); - // 20070703 KSC: - pcBubbleSizeRatio = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - wBubbleSize = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); +public class Scatter extends GenericChartObject implements ChartObject { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -289334135036242100L; + private short grbit = 0; + private short pcBubbleSizeRatio = 100; + private short wBubbleSize = 1; + private boolean fBubbles = false; + private boolean fShowNegBubbles = false; + private boolean fHasShadow = false; + + public void init() { + super.init(); + // 20070703 KSC: + pcBubbleSizeRatio = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + wBubbleSize = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); grbit = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - fBubbles= (grbit & 0x1) == 0x1; - fShowNegBubbles= (grbit & 0x2) == 0x2; - fHasShadow= (grbit & 0x4) == 0x4; + fBubbles = (grbit & 0x1) == 0x1; + fShowNegBubbles = (grbit & 0x2) == 0x2; + fHasShadow = (grbit & 0x4) == 0x4; if (fBubbles) - chartType= ChartConstants.BUBBLECHART; + chartType = ChartConstants.BUBBLECHART; else - chartType= ChartConstants.SCATTERCHART; - } - // 20070703 KSC + chartType = ChartConstants.SCATTERCHART; + } + + // 20070703 KSC private void updateRecord() { - grbit= ByteTools.updateGrBit(grbit, fBubbles, 0); - grbit= ByteTools.updateGrBit(grbit, fShowNegBubbles, 1); - grbit= ByteTools.updateGrBit(grbit, fHasShadow, 2); + grbit = ByteTools.updateGrBit(grbit, fBubbles, 0); + grbit = ByteTools.updateGrBit(grbit, fShowNegBubbles, 1); + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 2); byte[] b = ByteTools.shortToLEBytes(pcBubbleSizeRatio); this.getData()[0] = b[0]; this.getData()[1] = b[1]; @@ -76,19 +79,19 @@ private void updateRecord() { this.getData()[4] = b[0]; this.getData()[5] = b[1]; } - + public void setAsScatterChart() { - fBubbles= false; - chartType= ChartConstants.SCATTERCHART; + fBubbles = false; + chartType = ChartConstants.SCATTERCHART; this.updateRecord(); } - + public void setAsBubbleChart() { - fBubbles= true; - chartType= ChartConstants.BUBBLECHART; + fBubbles = true; + chartType = ChartConstants.BUBBLECHART; this.updateRecord(); } - + public static XLSRecord getPrototype() { Scatter b = new Scatter(); b.setOpcode(SCATTER); @@ -97,44 +100,46 @@ public static XLSRecord getPrototype() { b.setAsScatterChart(); return b; } - - private byte[] PROTOTYPE_BYTES = new byte[] {100, 0, 1, 0, 0, 0}; - /** + + private byte[] PROTOTYPE_BYTES = new byte[]{100, 0, 1, 0, 0, 0}; + + /** * @return String XML representation of this chart-type's options */ public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - if (pcBubbleSizeRatio != 100) - sb.append(" BubbleSizeRatio=\"" + pcBubbleSizeRatio + "\""); - if (wBubbleSize != 1) - sb.append(" BubbleSize=\"" + wBubbleSize + "\""); - if (fShowNegBubbles) - sb.append(" ShowNeg=\"true\""); - if (fHasShadow) - sb.append(" Shadow=\"true\""); - return sb.toString(); + StringBuffer sb = new StringBuffer(); + if (pcBubbleSizeRatio != 100) + sb.append(" BubbleSizeRatio=\"" + pcBubbleSizeRatio + "\""); + if (wBubbleSize != 1) + sb.append(" BubbleSize=\"" + wBubbleSize + "\""); + if (fShowNegBubbles) + sb.append(" ShowNeg=\"true\""); + if (fHasShadow) + sb.append(" Shadow=\"true\""); + return sb.toString(); } + /** * Handle setting options from XML in a generic manner */ public boolean setChartOption(String op, String val) { - boolean bHandled= false; - if (op.equalsIgnoreCase("BubbleSizeRatio")) { - pcBubbleSizeRatio= Short.parseShort(val); - bHandled= true; - } else if (op.equalsIgnoreCase("BubbleSize")) { - wBubbleSize= Short.parseShort(val); - bHandled= true; - } else if (op.equalsIgnoreCase("ShowNeg")) { - fShowNegBubbles= val.equals("true"); - bHandled= true; - } else if (op.equalsIgnoreCase("Shadow")) { - fHasShadow= val.equals("true"); - bHandled= true; - } - if (bHandled) - updateRecord(); - return bHandled; + boolean bHandled = false; + if (op.equalsIgnoreCase("BubbleSizeRatio")) { + pcBubbleSizeRatio = Short.parseShort(val); + bHandled = true; + } else if (op.equalsIgnoreCase("BubbleSize")) { + wBubbleSize = Short.parseShort(val); + bHandled = true; + } else if (op.equalsIgnoreCase("ShowNeg")) { + fShowNegBubbles = val.equals("true"); + bHandled = true; + } else if (op.equalsIgnoreCase("Shadow")) { + fHasShadow = val.equals("true"); + bHandled = true; + } + if (bHandled) + updateRecord(); + return bHandled; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ScatterChart.java b/src/main/java/io/starter/formats/XLS/charts/ScatterChart.java index 2489dec..8cb5ff5 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ScatterChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/ScatterChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,94 +22,97 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.ChartSeriesHandle; import io.starter.OpenXLS.WorkBookHandle; import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; public class ScatterChart extends ChartType { - private Scatter scatter= null; - public ScatterChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - scatter= (Scatter) charttype; - } - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) - throws JSONException { - JSONObject chartObjectJSON= new JSONObject(); - - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // Deal with Series - double yMax= 0.0, yMin= 0.0; - int nSeries= 0; - JSONArray seriesJSON= new JSONArray(); - JSONArray seriesCOLORS= new JSONArray(); - boolean bHasBubbles= false; - try { - // must trap min and max for axis tick and units - for (int i= 0; i < series.length; i++) { - JSONArray seriesvals= CellRange.getValuesAsJSON(series[i].getSeriesRange(), wbh); - nSeries= Math.max(nSeries, seriesvals.length()); - for (int j= 0; j < seriesvals.length(); j++) { - try { - yMax= Math.max(yMax, seriesvals.getDouble(j)); - yMin= Math.min(yMin, seriesvals.getDouble(j)); - } catch (NumberFormatException n) {;} - } - if (!series[i].hasBubbleSizes()) - seriesJSON.put(seriesvals); - else - bHasBubbles= true; - seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i].getSeriesColor()]); - } - if (bHasBubbles) { - // 20080423 KSC: Go thru a second time, after obtaining yMax and yMin, for bubble sizes ... - for (int i= 0; i < series.length; i++) { - JSONArray bubbles= new JSONArray(); - JSONArray seriesvals= CellRange.getValuesAsJSON(series[i].getSeriesRange(), wbh); - JSONArray catvals= CellRange.getValuesAsJSON(series[i].getCategoryRange(), wbh); - JSONArray bubblesizes= CellRange.getValuesAsJSON(series[i].getBubbleSizes(), wbh); - for (int j= 0; j < catvals.length(); j++) { - JSONObject jo= new JSONObject(); - try { - jo.put("x", catvals.getDouble(j)); - } catch (Exception e) { - jo.put("x", j+1); - } - jo.put("y", seriesvals.getDouble(j)); - jo.put("size", Math.round(bubblesizes.getDouble(j)/((yMax-yMin)/nSeries))); // TODO: bubble sizes ration is a guess!! - bubbles.put(jo); - } - seriesJSON.put(bubbles); - } - } - chartObjectJSON.put("Series", seriesJSON); - chartObjectJSON.put("SeriesFills", seriesCOLORS); - } catch (JSONException je) { - // TODO: Log error - } - minMax[0]= new Double(yMin); - minMax[1]= new Double(yMax); - minMax[2]= new Double(nSeries); - return chartObjectJSON; + private Scatter scatter = null; + + public ScatterChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + scatter = (Scatter) charttype; + } + + public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) + throws JSONException { + JSONObject chartObjectJSON = new JSONObject(); + + // Type JSON + chartObjectJSON.put("type", this.getTypeJSON()); + + // Deal with Series + double yMax = 0.0, yMin = 0.0; + int nSeries = 0; + JSONArray seriesJSON = new JSONArray(); + JSONArray seriesCOLORS = new JSONArray(); + boolean bHasBubbles = false; + try { + // must trap min and max for axis tick and units + for (int i = 0; i < series.length; i++) { + JSONArray seriesvals = CellRange.getValuesAsJSON(series[i].getSeriesRange(), wbh); + nSeries = Math.max(nSeries, seriesvals.length()); + for (int j = 0; j < seriesvals.length(); j++) { + try { + yMax = Math.max(yMax, seriesvals.getDouble(j)); + yMin = Math.min(yMin, seriesvals.getDouble(j)); + } catch (NumberFormatException n) { + } + } + if (!series[i].hasBubbleSizes()) + seriesJSON.put(seriesvals); + else + bHasBubbles = true; + seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i].getSeriesColor()]); + } + if (bHasBubbles) { + // 20080423 KSC: Go thru a second time, after obtaining yMax and yMin, for bubble sizes ... + for (int i = 0; i < series.length; i++) { + JSONArray bubbles = new JSONArray(); + JSONArray seriesvals = CellRange.getValuesAsJSON(series[i].getSeriesRange(), wbh); + JSONArray catvals = CellRange.getValuesAsJSON(series[i].getCategoryRange(), wbh); + JSONArray bubblesizes = CellRange.getValuesAsJSON(series[i].getBubbleSizes(), wbh); + for (int j = 0; j < catvals.length(); j++) { + JSONObject jo = new JSONObject(); + try { + jo.put("x", catvals.getDouble(j)); + } catch (Exception e) { + jo.put("x", j + 1); + } + jo.put("y", seriesvals.getDouble(j)); + jo.put("size", Math.round(bubblesizes.getDouble(j) / ((yMax - yMin) / nSeries))); // TODO: bubble sizes ration is a guess!! + bubbles.put(jo); + } + seriesJSON.put(bubbles); + } + } + chartObjectJSON.put("Series", seriesJSON); + chartObjectJSON.put("SeriesFills", seriesCOLORS); + } catch (JSONException je) { + // TODO: Log error + } + minMax[0] = new Double(yMin); + minMax[1] = new Double(yMax); + minMax[2] = new Double(nSeries); + return chartObjectJSON; } - - /** - * return the type JSON for this Chart Object - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON= new JSONObject(); + + /** + * return the type JSON for this Chart Object + * + * @return + */ + public JSONObject getTypeJSON() throws JSONException { + JSONObject typeJSON = new JSONObject(); /* String dojoType; if (this.chartType==ChartConstants.SCATTER) { dojoType="Default"; // = MarkersOnly @@ -137,185 +140,193 @@ public JSONObject getTypeJSON() throws JSONException { } } typeJSON.put("type", dojoType); -*/ return typeJSON; - } +*/ + return typeJSON; + } - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double x= chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there - // y value for each point= h/YMAX - StringBuffer svg= new StringBuffer(); + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there + // y value for each point= h/YMAX + StringBuffer svg = new StringBuffer(); - if (series.size()==0) { - Logger.logErr("Scatter.getSVG: error in series"); - return ""; - } - // gather data labels, markers, has lines, series colors for chart - boolean threeD= cf.isThreeD(ChartConstants.SCATTERCHART); - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - int n= series.size(); - boolean hasLines= cf.getHasLines(); // should be per-series? - int[] markers= getMarkerFormats(); // get an array of marker formats per series - if (!hasLines && (markers[0] == 0)) { - // if no lines AND no markers set, MUST use default markers (this is what excel does ...) - int[] defaultmarkers= {2, 3, 1, 4, 8, 9, 5, 6, 7}; - for (int i= 0; i < n; i++) { - markers[i]= defaultmarkers[i]; - } - } - /** - * A Scatter chart has two value axes, showing one set of numerical data along the x-axis - * and another along the y-axis. - * It combines these values into single data points and displays them in uneven intervals, - * or clusters. - */ - double[] seriesx= null; - double xfactor= 0, yfactor= 0; // - boolean TEXTUALXAXIS= true; - // get x axis max/min for an x axis which is a value axis - double xmin= Double.MAX_VALUE, xmax= Double.MIN_VALUE; - seriesx= new double[categories.length]; - for (int j= 0; j < categories.length; j++) { - try { - seriesx[j]= new Double(categories[j].toString()); - xmax= Math.max(xmax, seriesx[j]); - xmin= Math.min(xmin, seriesx[j]); - TEXTUALXAXIS= false; // if ANY category val is a double, assume it's a normal xyvalue axis - } catch (Exception e) { ; /* keep going */ } - } - if (!TEXTUALXAXIS) { - double d[]= ValueRange.calcMaxMin(xmax, xmin, w); - xfactor= w/(d[2]); // w/maximum scale - } else - xfactor= w/(categories.length+1); // w/#categories - - if (max!=0) - yfactor= h/max; // h/YMAXSCALE - svg.append("\r\n"); - // define marker shapes for later use - svg.append(MarkerFormat.getMarkerSVGDefs()); - // for each series - for (int i= 0; i < n; i++) { - // two lines- 1 black, 1 color - String points= ""; - String labels= ""; - double[] seriesy= (double[])series.get(i); - for (int j= 0; j < seriesy.length; j++) { - double xval= 0; - if (TEXTUALXAXIS /*|| i > 0*/) - xval= j+1; - else - xval= seriesx[j]; - points+= ((x)+xval*xfactor) + "," + ((y+h)-(seriesy[j]*yfactor)); - points+=" "; - String l= getSVGDataLabels(dls, axisMetrics, seriesy[j], 0, i, legends, categories[j].toString()); - if (l!=null) - labels+= "" + l + "\r\n"; - - } - if (hasLines) { - svg.append(getLineSVG(points, seriescolors[i])); - } - // Markers, if any, along data points in series - if (markers[i] > 0) { - String[] markerpoints= points.split(" "); - for (int j= 0; j < markerpoints.length; j++) { - String markerpoint= markerpoints[j]; - String[] xy= markerpoint.split(","); - double xx= Double.valueOf(xy[0]); - double yy= Double.valueOf(xy[1]); - svg.append(MarkerFormat.getMarkerSVG(xx, yy, seriescolors[i], markers[i])+ "\r\n"); - } - } - // labels after lines and markers - svg.append(labels); - } - svg.append("\r\n"); - return svg.toString(); - } - - /** - * returns the SVG necessary to define a line at points in color clr - * @param points String of SVG points - * @param clr SVG color String - * @return - */ - private String getLineSVG(String points, String clr) { - String s= ""; - // each line is comprised of 1 black line and 1 series color line: - // 1st line is black - s= "\r\n"; - // 2nd line is the series color - s+="\r\n"; - return s; - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, - String serAxisId) { - StringBuffer cooxml = new StringBuffer(); + if (series.size() == 0) { + Logger.logErr("Scatter.getSVG: error in series"); + return ""; + } + // gather data labels, markers, has lines, series colors for chart + boolean threeD = cf.isThreeD(ChartConstants.SCATTERCHART); + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + int n = series.size(); + boolean hasLines = cf.getHasLines(); // should be per-series? + int[] markers = getMarkerFormats(); // get an array of marker formats per series + if (!hasLines && (markers[0] == 0)) { + // if no lines AND no markers set, MUST use default markers (this is what excel does ...) + int[] defaultmarkers = {2, 3, 1, 4, 8, 9, 5, 6, 7}; + for (int i = 0; i < n; i++) { + markers[i] = defaultmarkers[i]; + } + } + /** + * A Scatter chart has two value axes, showing one set of numerical data along the x-axis + * and another along the y-axis. + * It combines these values into single data points and displays them in uneven intervals, + * or clusters. + */ + double[] seriesx = null; + double xfactor = 0, yfactor = 0; // + boolean TEXTUALXAXIS = true; + // get x axis max/min for an x axis which is a value axis + double xmin = Double.MAX_VALUE, xmax = Double.MIN_VALUE; + seriesx = new double[categories.length]; + for (int j = 0; j < categories.length; j++) { + try { + seriesx[j] = new Double(categories[j].toString()); + xmax = Math.max(xmax, seriesx[j]); + xmin = Math.min(xmin, seriesx[j]); + TEXTUALXAXIS = false; // if ANY category val is a double, assume it's a normal xyvalue axis + } catch (Exception e) { + /* keep going */ + } + } + if (!TEXTUALXAXIS) { + double[] d = ValueRange.calcMaxMin(xmax, xmin, w); + xfactor = w / (d[2]); // w/maximum scale + } else + xfactor = w / (categories.length + 1); // w/#categories - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - int[] markers= getMarkerFormats(); - String style=null; - for (int m:markers) { - if (m!=0) { - if (this.getHasSmoothLines()) - style= "smoothMarker"; - else if (this.getHasLines()) - style= "lineMarker"; - else - style= "marker"; - break; - } - } - if (style==null && this.getHasLines()) style= "line"; - if (style==null && this.getHasSmoothLines()) style= "smooth"; - if (style==null) style= "none"; - - cooxml.append(""); - // vary colors??? + if (max != 0) + yfactor = h / max; // h/YMAXSCALE + svg.append("\r\n"); + // define marker shapes for later use + svg.append(MarkerFormat.getMarkerSVGDefs()); + // for each series + for (int i = 0; i < n; i++) { + // two lines- 1 black, 1 color + String points = ""; + String labels = ""; + double[] seriesy = (double[]) series.get(i); + for (int j = 0; j < seriesy.length; j++) { + double xval = 0; + if (TEXTUALXAXIS /*|| i > 0*/) + xval = j + 1; + else + xval = seriesx[j]; + points += ((x) + xval * xfactor) + "," + ((y + h) - (seriesy[j] * yfactor)); + points += " "; + String l = getSVGDataLabels(dls, axisMetrics, seriesy[j], 0, i, legends, categories[j].toString()); + if (l != null) + labels += "" + l + "\r\n"; - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + } + if (hasLines) { + svg.append(getLineSVG(points, seriescolors[i])); + } + // Markers, if any, along data points in series + if (markers[i] > 0) { + String[] markerpoints = points.split(" "); + for (int j = 0; j < markerpoints.length; j++) { + String markerpoint = markerpoints[j]; + String[] xy = markerpoint.split(","); + double xx = Double.valueOf(xy[0]); + double yy = Double.valueOf(xy[1]); + svg.append(MarkerFormat.getMarkerSVG(xx, yy, seriescolors[i], markers[i]) + "\r\n"); + } + } + // labels after lines and markers + svg.append(labels); + } + svg.append("\r\n"); + return svg.toString(); + } + + /** + * returns the SVG necessary to define a line at points in color clr + * + * @param points String of SVG points + * @param clr SVG color String + * @return + */ + private String getLineSVG(String points, String clr) { + String s = ""; + // each line is comprised of 1 black line and 1 series color line: + // 1st line is black + s = "\r\n"; + // 2nd line is the series color + s += "\r\n"; + return s; + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, + String serAxisId) { + StringBuffer cooxml = new StringBuffer(); - // chart data labels, if any - // TODO: FINISH - // cooxml.append(getDataLabelsOOXML(cf)); + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + int[] markers = getMarkerFormats(); + String style = null; + for (int m : markers) { + if (m != 0) { + if (this.getHasSmoothLines()) + style = "smoothMarker"; + else if (this.getHasLines()) + style = "lineMarker"; + else + style = "marker"; + break; + } + } + if (style == null && this.getHasLines()) style = "line"; + if (style == null && this.getHasSmoothLines()) style = "smooth"; + if (style == null) style = "none"; - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); + cooxml.append(""); + // vary colors??? - cooxml.append(""); cooxml.append("\r\n"); - return cooxml; - } + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // chart data labels, if any + // TODO: FINISH + // cooxml.append(getDataLabelsOOXML(cf)); + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + cooxml.append(""); + cooxml.append("\r\n"); + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SerParent.java b/src/main/java/io/starter/formats/XLS/charts/SerParent.java index 9dd21d7..a665184 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SerParent.java +++ b/src/main/java/io/starter/formats/XLS/charts/SerParent.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,16 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * SerParent: Trendline or ErrorBar Series Index (0x104a) - * */ public class SerParent extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4960939713989511226L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4960939713989511226L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SerToCrt.java b/src/main/java/io/starter/formats/XLS/charts/SerToCrt.java index 46711b0..d945c1c 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SerToCrt.java +++ b/src/main/java/io/starter/formats/XLS/charts/SerToCrt.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,24 +24,25 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; + /** * SerToCrt: Series Chart-Broup Index (0x1045) - * 0 chartGroup 2 chart-group index: the number of the chart group (specified by a CHARTFORMAT record, starts at 0) + * 0 chartGroup 2 chart-group index: the number of the chart group (specified by a CHARTFORMAT record, starts at 0) */ public class SerToCrt extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8217594656389677975L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8217594656389677975L; + + public void init() { + super.init(); + } - public void init() { - super.init(); - } - - protected static XLSRecord getPrototype(){ + protected static XLSRecord getPrototype() { SerToCrt bl = new SerToCrt(); bl.setOpcode(SERTOCRT); - bl.setData(new byte[] {0,0}); + bl.setData(new byte[]{0, 0}); return bl; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.java b/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.java index 0154405..29810d9 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.java +++ b/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,16 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * SerauxErrBar: Series ErrorBar(0x105b) - * */ public class SerauxErrBar extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7334046087318159714L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7334046087318159714L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.java b/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.java index 4598446..849c4e8 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.java +++ b/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,16 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * SerauxTrend: Series Trendline(0x104b) - * */ public class SerauxTrend extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2866392332146662156L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2866392332146662156L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Serfmt.java b/src/main/java/io/starter/formats/XLS/charts/Serfmt.java index 0a8b572..0c08bed 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Serfmt.java +++ b/src/main/java/io/starter/formats/XLS/charts/Serfmt.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,35 +24,38 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * Serfmt: Series Format(0x105d) - * + *

                * Specifies series formatting information - * + *

                * 0 grbit 2 - * + *

                * bits - * 0 0x1 fSmoothedLine 1= the line series has a smoothed line (Line, Scatter or Radar) - * 1 0x2 f3DBubbles 1= draw bubbles with 3-D effects - * 2 0x4 fArShadow 1= specifies whether the data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. - * rest are reserved + * 0 0x1 fSmoothedLine 1= the line series has a smoothed line (Line, Scatter or Radar) + * 1 0x2 f3DBubbles 1= draw bubbles with 3-D effects + * 2 0x4 fArShadow 1= specifies whether the data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. + * rest are reserved */ -public class Serfmt extends GenericChartObject implements ChartObject{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8307035373276421283L; - // 20070810 KSC: parse options ... - private short grbit= 0; - private boolean fSmoothedLine= false, f3dBubbles=false, fArShadow= false; - public void init() { - super.init(); - grbit= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fSmoothedLine= (grbit & 0x1) == 0x1; - f3dBubbles= (grbit & 0x2) == 0x2; - fArShadow= (grbit & 0x4) == 0x4; - } +public class Serfmt extends GenericChartObject implements ChartObject { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8307035373276421283L; + // 20070810 KSC: parse options ... + private short grbit = 0; + private boolean fSmoothedLine = false, f3dBubbles = false, fArShadow = false; + + public void init() { + super.init(); + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + fSmoothedLine = (grbit & 0x1) == 0x1; + f3dBubbles = (grbit & 0x2) == 0x2; + fArShadow = (grbit & 0x4) == 0x4; + } + private void updateRecord() { grbit = ByteTools.updateGrBit(grbit, fSmoothedLine, 0); grbit = ByteTools.updateGrBit(grbit, f3dBubbles, 1); @@ -61,78 +64,92 @@ private void updateRecord() { this.getData()[0] = b[0]; this.getData()[1] = b[1]; } - private byte[] PROTOTYPE_BYTES = new byte[] {0, 0}; + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; + /** * Handle setting options from XML in a generic manner */ public boolean setChartOption(String op, String val) { - boolean bHandled= false; - if (op.equalsIgnoreCase("SmoothedLine")) { - fSmoothedLine= val.equals("true"); - bHandled= true; - } else if (op.equalsIgnoreCase("ThreeDBubbles")) { - f3dBubbles= val.equals("true"); - bHandled= true; - } else if (op.equalsIgnoreCase("ArShadow")) { - fArShadow= val.equals("true"); - bHandled= true; - } - if (bHandled) - updateRecord(); - return bHandled; + boolean bHandled = false; + if (op.equalsIgnoreCase("SmoothedLine")) { + fSmoothedLine = val.equals("true"); + bHandled = true; + } else if (op.equalsIgnoreCase("ThreeDBubbles")) { + f3dBubbles = val.equals("true"); + bHandled = true; + } else if (op.equalsIgnoreCase("ArShadow")) { + fArShadow = val.equals("true"); + bHandled = true; + } + if (bHandled) + updateRecord(); + return bHandled; } - - /** + + /** * @return String XML representation of this chart-type's options */ public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - if (fSmoothedLine) - sb.append(" SmoothedLine=\"true\""); - if (f3dBubbles) - sb.append(" ThreeDBubbles=\"true\""); - if (fArShadow) - sb.append(" ArShadow=\"true\""); - return sb.toString(); + StringBuffer sb = new StringBuffer(); + if (fSmoothedLine) + sb.append(" SmoothedLine=\"true\""); + if (f3dBubbles) + sb.append(" ThreeDBubbles=\"true\""); + if (fArShadow) + sb.append(" ArShadow=\"true\""); + return sb.toString(); } + public static XLSRecord getPrototype() { - Serfmt s = new Serfmt(); + Serfmt s = new Serfmt(); s.setOpcode(SERFMT); s.setData(s.PROTOTYPE_BYTES); s.init(); return s; } - + public void setHas3dBubbles(boolean has3dBubbles) { - f3dBubbles= has3dBubbles; - updateRecord(); + f3dBubbles = has3dBubbles; + updateRecord(); + } + + public boolean get3DBubbles() { + return f3dBubbles; } - public boolean get3DBubbles() { return f3dBubbles; } + /** * sets whether the parent chart or series has smoothed lines + * * @param b */ public void setSmoothedLine(boolean b) { - fSmoothedLine= b; - updateRecord(); - } - - public boolean getSmoothLine() { return fSmoothedLine; } + fSmoothedLine = b; + updateRecord(); + } + + public boolean getSmoothLine() { + return fSmoothedLine; + } + /** - * data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. + * data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. */ - public boolean getShadow() { return fArShadow; } - + public boolean getShadow() { + return fArShadow; + } + /** - * data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. + * data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. + * * @param b */ public void setHasShadow(boolean b) { - fArShadow= b; - updateRecord(); + fArShadow = b; + updateRecord(); } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/Series.java b/src/main/java/io/starter/formats/XLS/charts/Series.java index 5f7eac0..c208658 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Series.java +++ b/src/main/java/io/starter/formats/XLS/charts/Series.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,268 +27,276 @@ import io.starter.OpenXLS.CellRange; import io.starter.OpenXLS.FormatHandle; import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.*; - -import java.util.Vector; -import java.util.ArrayList; -import io.starter.toolkit.*; - +import io.starter.formats.OOXML.DLbls; import io.starter.formats.OOXML.DPt; import io.starter.formats.OOXML.Marker; import io.starter.formats.OOXML.SpPr; +import io.starter.formats.XLS.*; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.Vector; -/** Series: Series Definition (1003h)
                - - This record defines the Series data of a chart. - - sdtX and sdtY fields determine data type (numeric and text) - cValx and cValy fields determine number of cells in series - - Offset Name Size Contents - -- - 4 sdtX 2 Type of data in cats (1=num, 3=str) - 8 sdtY 2 Type of data in values (1=num, 3=str) - 10 cValx 2 Count of categories - 12 cValy 2 Count of Values - 14 sdtBSz 2 Type of data in Bubble size series (0=dates, 1=num, 2=seq., 3=text) - 16 sdtValBSz 2 Count of Bubble series vals - - sdtX (2 bytes): An unsigned integer that specifies the type of data in categories (3), or horizontal values on bubble and scatter chart groups, in the series. - value :0x0001 The series contains categories), or horizontal values on bubble and scatter chart groups, with numeric information. - value: 0x0003 The series contains categories, or horizontal values on bubble and scatter chart groups, with text information. - sdtY (2 bytes): An unsigned integer that specifies that the values, or vertical values on bubble and scatter chart groups, in the series contain numeric information. - MUST be set to 0x0001, and MUST be ignored. - cValx (2 bytes): An unsigned integer that specifies the count of categories (3), or horizontal values on bubble and scatter chart groups, in the series. - This value MUST be less than or equal to 0x0F9F. - cValy (2 bytes): An unsigned integer that specifies the count of values, or vertical values on bubble and scatter chart groups, in the series. - This value MUST be less than or equal to 0x0F9F. - sdtBSize (2 bytes): An unsigned integer that specifies that the bubble size values in the series contain numeric information. - This value MUST be set to 0x0001, and MUST be ignored. - cValBSize (2 bytes): An unsigned integer that specifies the count of bubble size values in the series. - This value MUST be less than or equal to 0x0F9F. - - - - The series object contains a collection of sub objects. Usually this will take the form of 4 ai records, - type 0-3, and supporting records such as labels. - - - - * @see Chart +/** + * Series: Series Definition (1003h)
                + *

                + * This record defines the Series data of a chart. + *

                + * sdtX and sdtY fields determine data type (numeric and text) + * cValx and cValy fields determine number of cells in series + *

                + * Offset Name Size Contents + * -- + * 4 sdtX 2 Type of data in cats (1=num, 3=str) + * 8 sdtY 2 Type of data in values (1=num, 3=str) + * 10 cValx 2 Count of categories + * 12 cValy 2 Count of Values + * 14 sdtBSz 2 Type of data in Bubble size series (0=dates, 1=num, 2=seq., 3=text) + * 16 sdtValBSz 2 Count of Bubble series vals + *

                + * sdtX (2 bytes): An unsigned integer that specifies the type of data in categories (3), or horizontal values on bubble and scatter chart groups, in the series. + * value :0x0001 The series contains categories), or horizontal values on bubble and scatter chart groups, with numeric information. + * value: 0x0003 The series contains categories, or horizontal values on bubble and scatter chart groups, with text information. + * sdtY (2 bytes): An unsigned integer that specifies that the values, or vertical values on bubble and scatter chart groups, in the series contain numeric information. + * MUST be set to 0x0001, and MUST be ignored. + * cValx (2 bytes): An unsigned integer that specifies the count of categories (3), or horizontal values on bubble and scatter chart groups, in the series. + * This value MUST be less than or equal to 0x0F9F. + * cValy (2 bytes): An unsigned integer that specifies the count of values, or vertical values on bubble and scatter chart groups, in the series. + * This value MUST be less than or equal to 0x0F9F. + * sdtBSize (2 bytes): An unsigned integer that specifies that the bubble size values in the series contain numeric information. + * This value MUST be set to 0x0001, and MUST be ignored. + * cValBSize (2 bytes): An unsigned integer that specifies the count of bubble size values in the series. + * This value MUST be less than or equal to 0x0F9F. + *

                + *

                + *

                + * The series object contains a collection of sub objects. Usually this will take the form of 4 ai records, + * type 0-3, and supporting records such as labels. + * + * + * + * @see Chart */ -import io.starter.formats.OOXML.*; /** - * sdtX (2 bytes): An unsigned integer that specifies the type of data in categories (3), or horizontal values on bubble and scatter chart groups, in the series. MUST be a value from the following table. - Value Meaning - 0x0001 The series contains categories, or horizontal values on bubble and scatter chart groups, with numeric information. - 0x0003 The series contains categories, or horizontal values on bubble and scatter chart groups, with text information. - sdtY (2 bytes): An unsigned integer that specifies that the values, or vertical values on bubble and scatter chart groups, in the series contain numeric information. MUST be set to 0x0001, and MUST be ignored. - cValx (2 bytes): An unsigned integer that specifies the count of categories (3), or horizontal values on bubble and scatter chart groups, in the series. This value MUST be less than or equal to 0x0F9F. - cValy (2 bytes): An unsigned integer that specifies the count of values, or vertical values on bubble and scatter chart groups, in the series. This value MUST be less than or equal to 0x0F9F. - sdtBSize (2 bytes): An unsigned integer that specifies that the bubble size values in the series contain numeric information. This value MUST be set to 0x0001, and MUST be ignored. - cValBSize (2 bytes): An unsigned integer that specifies the count of bubble size values in the series. This value MUST be less than or equal to 0x0F9F. - - * + * sdtX (2 bytes): An unsigned integer that specifies the type of data in categories (3), or horizontal values on bubble and scatter chart groups, in the series. MUST be a value from the following table. + * Value Meaning + * 0x0001 The series contains categories, or horizontal values on bubble and scatter chart groups, with numeric information. + * 0x0003 The series contains categories, or horizontal values on bubble and scatter chart groups, with text information. + * sdtY (2 bytes): An unsigned integer that specifies that the values, or vertical values on bubble and scatter chart groups, in the series contain numeric information. MUST be set to 0x0001, and MUST be ignored. + * cValx (2 bytes): An unsigned integer that specifies the count of categories (3), or horizontal values on bubble and scatter chart groups, in the series. This value MUST be less than or equal to 0x0F9F. + * cValy (2 bytes): An unsigned integer that specifies the count of values, or vertical values on bubble and scatter chart groups, in the series. This value MUST be less than or equal to 0x0F9F. + * sdtBSize (2 bytes): An unsigned integer that specifies that the bubble size values in the series contain numeric information. This value MUST be set to 0x0001, and MUST be ignored. + * cValBSize (2 bytes): An unsigned integer that specifies the count of bubble size values in the series. This value MUST be less than or equal to 0x0F9F. */ -public final class Series extends GenericChartObject implements ChartObject -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7290108485347063887L; - public static int SERIES_TYPE_NUMERIC = 1; - public static int SERIES_TYPE_STRING = 3; - - protected int sdtX = -1, sdtY = -1, cValx = -1, cValy = -1, sdtBSz = -1, sdtValBSz = -1; - - private SpPr shapeProps= null; // OOXML-specific holds the shape properties (line and fill) for this series (all charts) - private Marker m= null; // OOXML-specific object to hold marker properties for this series (radar, scatter and line charts only) - private DLbls d= null; // OOXML-specific object holds Data Labels properties for this series (all charts except surface) - private ArrayList dPts= null; // OOXML-specific object holds Data Labels properties for this series (all charts except surface) - - public void init(){ - super.init(); - sdtX = (int) ByteTools.readShort(this.getByteAt(0),this.getByteAt(1)); - sdtY = (int) ByteTools.readShort(this.getByteAt(2),this.getByteAt(3)); - cValx = (int) ByteTools.readShort(this.getByteAt(4),this.getByteAt(5)); - cValy = (int) ByteTools.readShort(this.getByteAt(6),this.getByteAt(7)); - sdtBSz = (int) ByteTools.readShort(this.getByteAt(8),this.getByteAt(9)); - sdtValBSz = (int) ByteTools.readShort(this.getByteAt(10),this.getByteAt(11)); - if(DEBUGLEVEL > 10)Logger.logInfo(toString()); - } - +public final class Series extends GenericChartObject implements ChartObject { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 7290108485347063887L; + public static int SERIES_TYPE_NUMERIC = 1; + public static int SERIES_TYPE_STRING = 3; + + protected int sdtX = -1, sdtY = -1, cValx = -1, cValy = -1, sdtBSz = -1, sdtValBSz = -1; + + private SpPr shapeProps = null; // OOXML-specific holds the shape properties (line and fill) for this series (all charts) + private Marker m = null; // OOXML-specific object to hold marker properties for this series (radar, scatter and line charts only) + private DLbls d = null; // OOXML-specific object holds Data Labels properties for this series (all charts except surface) + private ArrayList dPts = null; // OOXML-specific object holds Data Labels properties for this series (all charts except surface) + + public void init() { + super.init(); + sdtX = (int) ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + sdtY = (int) ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + cValx = (int) ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + cValy = (int) ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + sdtBSz = (int) ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + sdtValBSz = (int) ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); + if (DEBUGLEVEL > 10) Logger.logInfo(toString()); + } + public Series() { - + } - + public void update() { byte[] rkdata = this.getData(); - byte[] b = ByteTools.shortToLEBytes((short)sdtX); + byte[] b = ByteTools.shortToLEBytes((short) sdtX); rkdata[0] = b[0]; rkdata[1] = b[1]; - b = ByteTools.shortToLEBytes((short)sdtY); + b = ByteTools.shortToLEBytes((short) sdtY); rkdata[2] = b[0]; rkdata[3] = b[1]; - b = ByteTools.shortToLEBytes((short)cValx); + b = ByteTools.shortToLEBytes((short) cValx); rkdata[4] = b[0]; rkdata[5] = b[1]; - b = ByteTools.shortToLEBytes((short)cValy); + b = ByteTools.shortToLEBytes((short) cValy); rkdata[6] = b[0]; rkdata[7] = b[1]; - b = ByteTools.shortToLEBytes((short)sdtBSz); + b = ByteTools.shortToLEBytes((short) sdtBSz); rkdata[8] = b[0]; rkdata[9] = b[1]; - b = ByteTools.shortToLEBytes((short)sdtValBSz); + b = ByteTools.shortToLEBytes((short) sdtValBSz); rkdata[10] = b[0]; rkdata[11] = b[1]; this.setData(rkdata); } - + /** * Returns the series value AI associated with this series. + * * @return */ public Ai getSeriesValueAi() { - for (int i=0;ii+1) { - SeriesText st = (SeriesText)chartArr.get(i+1); - if (st!=null) - return st.toString(); - } - }catch(ClassCastException e) { + if (chartArr.size() > i + 1) { + SeriesText st = (SeriesText) chartArr.get(i + 1); + if (st != null) + return st.toString(); + } + } catch (ClassCastException e) { // couldn't find it! } } @@ -299,29 +307,31 @@ public String getLegendText() { /** * return the legend cell reference + * * @return */ public String getLegendRef() { Ai ai = this.getLegendAi(); - if (ai!=null)return ai.getDefinition(); + if (ai != null) return ai.getDefinition(); return null; } - + /** * Return the SeriesText object related to the Legend + * * @return */ protected SeriesText getLegendSeriesText() { - for (int i=0;ii+1) { + if (chartArr.size() > i + 1) { try { - SeriesText st = (SeriesText)chartArr.get(i+1); + SeriesText st = (SeriesText) chartArr.get(i + 1); return st; - }catch(ClassCastException e) { + } catch (ClassCastException e) { // couldn't find it! return null; } @@ -331,40 +341,42 @@ protected SeriesText getLegendSeriesText() { } return null; } + /** * Returns the legend value Ai associated with this series + * * @return */ public Ai getLegendAi() { - for (int i=0;i * REcords are... * AI * SeriesText (optional?) @@ -373,66 +385,74 @@ public Ai getBubbleValueAi() { * AI * DataFormat * SerToCrt + * * @param seriesData * @return */ protected static Series getPrototype(String seriesRange, String categoryRange, String bubbleRange, String legendRange, String legendText, ChartType chartobj) { - Series series = (Series)Series.getPrototype(); - Chart parentChart= chartobj.getParentChart(); + Series series = (Series) Series.getPrototype(); + Chart parentChart = chartobj.getParentChart(); WorkBook book = parentChart.getWorkBook(); Ai ai; // create Series text with Legend - if (legendRange!=null) { - ai = (Ai)Ai.getPrototype(Ai.AI_TYPE_LEGEND); + if (legendRange != null) { + ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_LEGEND); ai.setWorkBook(book); - ai.setSheet(parentChart.getSheet()); // 20080124 KSC: changeAiLocation from "A1" to "" - try { ai.changeAiLocation("", legendRange/*(seriesText.getWorkSheetName() + "!" + seriesText.getCellAddress())*/); - } catch (Exception e) { ; } // it's OK to not have a valid range + ai.setSheet(parentChart.getSheet()); // 20080124 KSC: changeAiLocation from "A1" to "" + try { + ai.changeAiLocation("", legendRange/*(seriesText.getWorkSheetName() + "!" + seriesText.getCellAddress())*/); + } catch (Exception e) { + } // it's OK to not have a valid range series.addChartRecord(ai); SeriesText st = SeriesText.getPrototype(legendText); ai.setSeriesText(st); series.addChartRecord(st); // 20091102 KSC: when adding series legend will not expand correctly if autopositioning is turned off // [BugTracker 2844] - Legend l= chartobj.getDataLegend(); - if (l!=null) { - l.setAutoPosition(true); - l.incrementHeight(parentChart.getCoords()[3]); + Legend l = chartobj.getDataLegend(); + if (l != null) { + l.setAutoPosition(true); + l.incrementHeight(parentChart.getCoords()[3]); } - - }else { - ai = (Ai)Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND); + + } else { + ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND); ai.setWorkBook(book); ai.setSheet(parentChart.getSheet()); series.addChartRecord(ai); } // parentChart.addAi(ai); // create Series Value Ai - ai = (Ai)Ai.getPrototype(Ai.AI_TYPE_SERIES); + ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_SERIES); ai.setParentChart(parentChart); ai.setWorkBook(book); ai.setSheet(parentChart.getSheet()); - try { ai.changeAiLocation(ai.toString(), seriesRange); - } catch (Exception e) { + try { + ai.changeAiLocation(ai.toString(), seriesRange); + } catch (Exception e) { // not necessary to report Logger.logErr("Error setting Series Range: '" + seriesRange + "'-" + e.toString()); } // it's OK to not have a valid range series.addChartRecord(ai); // parentChart.addAi(ai); // create Category Ai - ai = (Ai)Ai.getPrototype(Ai.AI_TYPE_CATEGORY); + ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_CATEGORY); ai.setWorkBook(book); ai.setSheet(parentChart.getSheet()); - try {ai.changeAiLocation(ai.toString(), categoryRange); - } catch (Exception e) { ; } // it's OK to not have a valid range + try { + ai.changeAiLocation(ai.toString(), categoryRange); + } catch (Exception e) { + } // it's OK to not have a valid range series.addChartRecord(ai); // parentChart.addAi(ai); // create Bubble (undocumented) Ai - ai = (Ai)Ai.getPrototype(Ai.AI_TYPE_BUBBLE); + ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_BUBBLE); ai.setWorkBook(book); ai.setSheet(parentChart.getSheet()); - if(!bubbleRange.equals("")) { - try{ ai.changeAiLocation(ai.toString(), bubbleRange); - } catch (Exception e) { ; } // it's OK to not have a valid range + if (!bubbleRange.equals("")) { + try { + ai.changeAiLocation(ai.toString(), bubbleRange); + } catch (Exception e) { + } // it's OK to not have a valid range ai.setRt(2); /* 20120123 KSC: confused vs. per-series format verses format in chartformat rec ... * if (((BubbleChart) parentChart.getChartObject()).is3d()) { @@ -440,72 +460,75 @@ protected static Series getPrototype(String seriesRange, String categoryRange, S }*/ } series.addChartRecord(ai); - DataFormat df= null; - df= (DataFormat) DataFormat.getPrototype(); + DataFormat df = null; + df = (DataFormat) DataFormat.getPrototype(); // update the data format correctly - Vector ser = parentChart.getAllSeries(); // get ALL series - int yi= -1; // Changed from 0 - int iss = -1; // "" - for (int i=0;iyi)yi = newYi; - if (newIss>iss)iss = newIss; + if (newYi > yi) yi = newYi; + if (newIss > iss) iss = newIss; } yi++; iss++; df.setSeriesIndex(yi); df.setSeriesNumber(iss); - if (chartobj.getBarShape()!=0) { // must ensure each series contains proper shape records - df.setShape(chartobj.getBarShape()); + if (chartobj.getBarShape() != 0) { // must ensure each series contains proper shape records + df.setShape(chartobj.getBarShape()); } series.addChartRecord(df); - SerToCrt stc = (SerToCrt)SerToCrt.getPrototype(); + SerToCrt stc = (SerToCrt) SerToCrt.getPrototype(); // get the correct chart index for the sertocrt int vCount = 0; int cCount = 0; int bCount = 0; - if (ser.size()> 0) { // 20070709 KSC: will be 0 if adding new blank chart - Series s = (Series)ser.get(0); - ArrayList cr = s.getChartRecords(); - for (int i=0;i 0) { // 20070709 KSC: will be 0 if adding new blank chart + Series s = (Series) ser.get(0); + ArrayList cr = s.getChartRecords(); + for (int i = 0; i < cr.size(); i++) { + BiffRec b = (BiffRec) cr.get(i); + if (b.getOpcode() == SERTOCRT) { + SerToCrt stcc = (SerToCrt) b; + stc.setData(stcc.getData()); + } + } + // set the series level variables correctly + vCount = s.getValueCount(); + cCount = s.getCategoryCount(); + bCount = s.getBubbleCount(); } series.init(); // 20070711 KSC: vCount and cCount are via current range, no?? try { - if (seriesRange.indexOf(":")!=-1) { - int coords[]= io.starter.OpenXLS.ExcelTools.getRangeCoords(seriesRange); - vCount= coords[4]; - }else { - vCount= 1; + if (seriesRange.indexOf(":") != -1) { + int[] coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(seriesRange); + vCount = coords[4]; + } else { + vCount = 1; } - series.setValueCount(vCount); - } catch (Exception e) { } + series.setValueCount(vCount); + } catch (Exception e) { + } try { - cCount= io.starter.OpenXLS.ExcelTools.getRangeCoords(categoryRange)[4]; + cCount = io.starter.OpenXLS.ExcelTools.getRangeCoords(categoryRange)[4]; series.setCategoryCount(cCount); - } catch(Exception e) { } - if(!bubbleRange.equals("")) { - try { - bCount= io.starter.OpenXLS.ExcelTools.getRangeCoords(bubbleRange)[4]; - series.setBubbleCount(bCount); - } catch(Exception e) { } - } + } catch (Exception e) { + } + if (!bubbleRange.equals("")) { + try { + bCount = io.starter.OpenXLS.ExcelTools.getRangeCoords(bubbleRange)[4]; + series.setBubbleCount(bCount); + } catch (Exception e) { + } + } series.addChartRecord(stc); return series; } - + public static XLSRecord getPrototype() { Series s = new Series(); s.setOpcode(SERIES); @@ -513,585 +536,653 @@ public static XLSRecord getPrototype() { s.init(); return s; } - private byte[] PROTOTYPE_BYTES = new byte[] {3, 0, 1, 0, 3, 0, 3, 0, 1, 0, 0, 0}; + + private byte[] PROTOTYPE_BYTES = new byte[]{3, 0, 1, 0, 3, 0, 3, 0, 1, 0, 0, 0}; + /** * Returns the category value AI associated with this series. + * * @return */ public Ai getCategoryValueAi() { - for (int i=0;i 0; } - public boolean hasBubbleSizes() { return sdtValBSz > 0; } - - /** - * Gets the dataformat record associated with this Series, if any. - * If none present, option to create a basic DataFormat set of records DataFormat controls - * - * @return DataFormat Record - */ - private DataFormat getDataFormatRec(boolean bCreate) { - DataFormat df = (DataFormat) Chart.findRec(this.chartArr, DataFormat.class); - if (df == null && bCreate) { // create dataformat - df = (DataFormat) DataFormat.getPrototypeWithFormatRecs(this.getParentChart()); - this.addChartRecord(df); - } - return df; - } - - - /** - * retrieves the data format record which corresponds to the desired pie slice - * @param slice - * @return - */ - private DataFormat getDataFormatRecSlice(int slice, boolean bCreate) { - DataFormat df= this.getDataFormatRec(false); - if (df==null) { - Logger.logErr("Series.getDataFormatRecSlice: cannot find data format record"); - return null; - } - int seriesNumber= df.getSeriesNumber(); - // for PIE charts, DataFormats are stored in 1st series, - // just after the initial data format rec - // must check point number to see if it's the desired df - Series s; - if (seriesNumber==0) // we're on the first one - s= this; - else { // should not have more than 1 series for a pie chart! - s= (Series) getParentChart().getAllSeries().get(seriesNumber); - } - int i= Chart.findRecPosition(s.chartArr, DataFormat.class); // get position of the first df - i++; // skip 1st - int lastSlice= 0; - while (i < s.chartArr.size()) { - if (s.chartArr.get(i) instanceof DataFormat) { - df= (DataFormat) s.chartArr.get(i); - lastSlice= df.getPointNumber(); - if (df.getPointNumber()==slice) - return df; - } - i++; - } - // create - if (bCreate) { - i--; - while (lastSlice <= slice) { - df = (DataFormat) DataFormat.getPrototypeWithFormatRecs(this.getParentChart()); - df.setPointNumber(lastSlice++); - df.setParentChart(this.getParentChart()); - s.chartArr.add(i++, df); - } - return df; - } - return null; - } - - /** - * returns the shape of the data point for this series - * @return - */ + + /** + * Gets the dataformat record associated with this Series, if any. + * If none present, option to create a basic DataFormat set of records DataFormat controls + * + * @return DataFormat Record + */ + private DataFormat getDataFormatRec(boolean bCreate) { + DataFormat df = (DataFormat) Chart.findRec(this.chartArr, DataFormat.class); + if (df == null && bCreate) { // create dataformat + df = (DataFormat) DataFormat.getPrototypeWithFormatRecs(this.getParentChart()); + this.addChartRecord(df); + } + return df; + } + + + /** + * retrieves the data format record which corresponds to the desired pie slice + * + * @param slice + * @return + */ + private DataFormat getDataFormatRecSlice(int slice, boolean bCreate) { + DataFormat df = this.getDataFormatRec(false); + if (df == null) { + Logger.logErr("Series.getDataFormatRecSlice: cannot find data format record"); + return null; + } + int seriesNumber = df.getSeriesNumber(); + // for PIE charts, DataFormats are stored in 1st series, + // just after the initial data format rec + // must check point number to see if it's the desired df + Series s; + if (seriesNumber == 0) // we're on the first one + s = this; + else { // should not have more than 1 series for a pie chart! + s = (Series) getParentChart().getAllSeries().get(seriesNumber); + } + int i = Chart.findRecPosition(s.chartArr, DataFormat.class); // get position of the first df + i++; // skip 1st + int lastSlice = 0; + while (i < s.chartArr.size()) { + if (s.chartArr.get(i) instanceof DataFormat) { + df = (DataFormat) s.chartArr.get(i); + lastSlice = df.getPointNumber(); + if (df.getPointNumber() == slice) + return df; + } + i++; + } + // create + if (bCreate) { + i--; + while (lastSlice <= slice) { + df = (DataFormat) DataFormat.getPrototypeWithFormatRecs(this.getParentChart()); + df.setPointNumber(lastSlice++); + df.setParentChart(this.getParentChart()); + s.chartArr.add(i++, df); + } + return df; + } + return null; + } + + /** + * returns the shape of the data point for this series + * + * @return + */ public int getShape() { - int ret= 0; - DataFormat df= this.getDataFormatRec(false); - if (df!=null) - ret= df.getShape(); - return ret; + int ret = 0; + DataFormat df = this.getDataFormatRec(false); + if (df != null) + ret = df.getShape(); + return ret; } - + public void setShape(int shape) { - DataFormat df= this.getDataFormatRec(true); - df.setShape(shape); - } - + DataFormat df = this.getDataFormatRec(true); + df.setShape(shape); + } + /** * returns true if this series has smoothed lines + * * @return */ public boolean getHasSmoothedLines() { - DataFormat df= this.getDataFormatRec(false); - if (df!=null) - return df.getSmoothedLines(); - return false; + DataFormat df = this.getDataFormatRec(false); + if (df != null) + return df.getSmoothedLines(); + return false; } /** * set smooth lines setting (applicable for line, scatter charts) + * * @param smooth */ - public void setHasSmoothLines(boolean smooth) - { - DataFormat df= this.getDataFormatRec(true); - df.setSmoothLines(smooth); + public void setHasSmoothLines(boolean smooth) { + DataFormat df = this.getDataFormatRec(true); + df.setSmoothLines(smooth); } /** * sets this series to have lines - *
                Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + *
                Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray */ public void setHasLines(int lineStyle) { - DataFormat df= this.getDataFormatRec(true); - df.setHasLines(lineStyle); + DataFormat df = this.getDataFormatRec(true); + df.setHasLines(lineStyle); } /** * sets the color for this series * NOTE: for PIE Charts, use setPieSliceColor - * @param clr String color hex String + * + * @param clr String color hex String * @see setPieSliceColor */ public void setColor(String clr) { - int type= this.getParentChart().getChartType(); - if (type==ChartConstants.PIECHART) { - setPieSliceColor(clr, this.getSeriesIndex()); - return; - } - DataFormat df= this.getDataFormatRec(true); - df.setSeriesColor(clr); + int type = this.getParentChart().getChartType(); + if (type == ChartConstants.PIECHART) { + setPieSliceColor(clr, this.getSeriesIndex()); + return; + } + DataFormat df = this.getDataFormatRec(true); + df.setSeriesColor(clr); } - + /** * if the exact/correct color index is not used, the fill color comes out black + * * @param clr * @return */ private int ensureCorrectColorInt(int clr) { - // "The Chart color table is a subset of the full color table" - if (clr==FormatConstants.COLOR_RED) - clr= FormatConstants.COLOR_RED_CHART; - if (clr==FormatConstants.COLOR_BLUE) - clr= FormatConstants.COLOR_BLUE_CHART; - if (clr==FormatConstants.COLOR_YELLOW) - clr= FormatConstants.COLOR_YELLOW_CHART; - if (clr==FormatConstants.COLOR_DARK_GREEN) // no standard chart dark green ... - clr= FormatConstants.COLOR_GREEN; - if (clr==FormatConstants.COLOR_DARK_YELLOW) // no standard chart dark yellow ... - clr= FormatConstants.COLOR_YELLOW_CHART; - if (clr==FormatConstants.COLOR_OLIVE_GREEN) - clr= FormatConstants.COLOR_OLIVE_GREEN_CHART; - if (clr==FormatConstants.COLOR_WHITE) - clr= FormatConstants.COLOR_WHITE3; - return clr; - + // "The Chart color table is a subset of the full color table" + if (clr == FormatConstants.COLOR_RED) + clr = FormatConstants.COLOR_RED_CHART; + if (clr == FormatConstants.COLOR_BLUE) + clr = FormatConstants.COLOR_BLUE_CHART; + if (clr == FormatConstants.COLOR_YELLOW) + clr = FormatConstants.COLOR_YELLOW_CHART; + if (clr == FormatConstants.COLOR_DARK_GREEN) // no standard chart dark green ... + clr = FormatConstants.COLOR_GREEN; + if (clr == FormatConstants.COLOR_DARK_YELLOW) // no standard chart dark yellow ... + clr = FormatConstants.COLOR_YELLOW_CHART; + if (clr == FormatConstants.COLOR_OLIVE_GREEN) + clr = FormatConstants.COLOR_OLIVE_GREEN_CHART; + if (clr == FormatConstants.COLOR_WHITE) + clr = FormatConstants.COLOR_WHITE3; + return clr; + } + /** * sets the color for this series * NOTE: for PIE Charts, use setPieSliceColor - * @param clr color int + * + * @param clr color int * @see setPieSliceColor */ public void setColor(int clr) { - clr= ensureCorrectColorInt(clr); - int type= this.getParentChart().getChartType(); - if (type==ChartConstants.PIECHART) { - setPieSliceColor(clr, this.getSeriesIndex()); - return; - } - DataFormat df= this.getDataFormatRec(true); - df.setSeriesColor(clr); - + clr = ensureCorrectColorInt(clr); + int type = this.getParentChart().getChartType(); + if (type == ChartConstants.PIECHART) { + setPieSliceColor(clr, this.getSeriesIndex()); + return; + } + DataFormat df = this.getDataFormatRec(true); + df.setSeriesColor(clr); + } - + /** - * sets the color of the desired pie slice - * @param clr color int - * @param slice 0-based pie slice number + * sets the color of the desired pie slice + * + * @param clr color int + * @param slice 0-based pie slice number */ public void setPieSliceColor(int clr, int slice) { - clr= ensureCorrectColorInt(clr); - int type= this.getParentChart().getChartType(); - if (type!=ChartConstants.PIECHART) - return; - DataFormat df= this.getDataFormatRecSlice(slice, true); - if (df!=null) - df.setPieSliceColor(clr, slice); - else - Logger.logErr("Series.setPieSliceColor: unable to fnd pie slice record"); + clr = ensureCorrectColorInt(clr); + int type = this.getParentChart().getChartType(); + if (type != ChartConstants.PIECHART) + return; + DataFormat df = this.getDataFormatRecSlice(slice, true); + if (df != null) + df.setPieSliceColor(clr, slice); + else + Logger.logErr("Series.setPieSliceColor: unable to fnd pie slice record"); } /** - * sets the color of the desired pie slice - * @param clr color int - * @param slice 0-based pie slice number + * sets the color of the desired pie slice + * + * @param clr color int + * @param slice 0-based pie slice number */ public void setPieSliceColor(String clr, int slice) { - int type= this.getParentChart().getChartType(); - if (type!=ChartConstants.PIECHART) - return; - DataFormat df= this.getDataFormatRecSlice(slice, true); - if (df!=null) - df.setPieSliceColor(clr, slice); - else - Logger.logErr("Series.setPieSliceColor: unable to fnd pie slice record"); + int type = this.getParentChart().getChartType(); + if (type != ChartConstants.PIECHART) + return; + DataFormat df = this.getDataFormatRecSlice(slice, true); + if (df != null) + df.setPieSliceColor(clr, slice); + else + Logger.logErr("Series.setPieSliceColor: unable to fnd pie slice record"); } // Periwinkle Plum+ Ivory Light Turquoise Dark Purple Coral Ocean Blue Ice Blue {17, 25, 19, 27, 28, 22, 23, 24}; // try these color int numbers instead: // alternative explanation: chart fills 16-23, chart lines 24-31 - public static int automaticSeriesColors[]= {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62}; // also used in mapping default colors in AreaFormat, MarkerFormat, Frame ... + public static int[] automaticSeriesColors = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62}; // also used in mapping default colors in AreaFormat, MarkerFormat, Frame ... /** * retrieve the series/bar color for this series * NOTE: for Pie Charts, must use getPieSliceColor + * * @return color int * @see getPieSliceColor */ public String getSeriesColor() { - DataFormat df= this.getDataFormatRec(false); - int type= this.getParentChart().getChartType(); - int seriesNumber= df.getSeriesNumber(); - if (type==ChartConstants.PIECHART) - return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[seriesNumber]]); - String bg= df.getBgColor(); - if (bg!=null) - return bg; - // otherwise, color is automatic or default chart series color - return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[seriesNumber]]); + DataFormat df = this.getDataFormatRec(false); + int type = this.getParentChart().getChartType(); + int seriesNumber = df.getSeriesNumber(); + if (type == ChartConstants.PIECHART) + return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[seriesNumber]]); + String bg = df.getBgColor(); + if (bg != null) + return bg; + // otherwise, color is automatic or default chart series color + return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[seriesNumber]]); } - - + + /** * get the pie slice color in this pie chart + * * @param slice * @return color int */ public String getPieSliceColor(int slice) { - int type= this.getParentChart().getChartType(); - if (type!=ChartConstants.PIECHART) - return null; - DataFormat df= this.getDataFormatRecSlice(slice, false); - if (df!=null) { - String bg= df.getBgColor(); - if (bg!=null) - return bg; - } - // otherwise, color is automatic or default chart series color - return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[slice]]); + int type = this.getParentChart().getChartType(); + if (type != ChartConstants.PIECHART) + return null; + DataFormat df = this.getDataFormatRecSlice(slice, false); + if (df != null) { + String bg = df.getBgColor(); + if (bg != null) + return bg; + } + // otherwise, color is automatic or default chart series color + return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[slice]]); } - - /** return the type of markers for each series: -
                0 = no marker -
                1 = square -
                2 = diamond -
                3 = triangle -
                4 = X -
                5 = star -
                6 = Dow-Jones -
                7 = standard deviation -
                8 = circle -
                9 = plus sign - **/ + + /** + * return the type of markers for each series: + *
                0 = no marker + *
                1 = square + *
                2 = diamond + *
                3 = triangle + *
                4 = X + *
                5 = star + *
                6 = Dow-Jones + *
                7 = standard deviation + *
                8 = circle + *
                9 = plus sign + **/ public int getMarkerFormat() { - DataFormat df= this.getDataFormatRec(false); - if (df!=null) { - return df.getMarkerFormat(); - } - return 0; + DataFormat df = this.getDataFormatRec(false); + if (df != null) { + return df.getMarkerFormat(); + } + return 0; } - - - /** return data label options as an int + + + /** + * return data label options as an int *
                can be one or more of: -
                SHOWVALUE= 0x1; -
                SHOWVALUEPERCENT= 0x2; -
                SHOWCATEGORYPERCENT= 0x4; -
                SMOOTHEDLINE= 0x8; -
                SHOWCATEGORYLABEL= 0x10; -
                SHOWBUBBLELABEL= 0x20; -
                SHOWSERIESLABEL= 0x40; - * @see AttachedLabel - * @return a combination of data label options above or 0 if none - */ + *
                SHOWVALUE= 0x1; + *
                SHOWVALUEPERCENT= 0x2; + *
                SHOWCATEGORYPERCENT= 0x4; + *
                SMOOTHEDLINE= 0x8; + *
                SHOWCATEGORYLABEL= 0x10; + *
                SHOWBUBBLELABEL= 0x20; + *
                SHOWSERIESLABEL= 0x40; + * + * @return a combination of data label options above or 0 if none + * @see AttachedLabel + */ public int getDataLabel() { - int datalabels= 0; - DataLabExtContents dl= (DataLabExtContents) Chart.findRec(this.chartArr, DataLabExtContents.class); - if (dl!=null) { // Extended Label -- add to attachedlabel, if any - datalabels= dl.getTypeInt(); - } - DataFormat df= this.getDataFormatRec(false); - if (df!=null) { - datalabels|=df.getDataLabelTypeInt(); - } - return datalabels; + int datalabels = 0; + DataLabExtContents dl = (DataLabExtContents) Chart.findRec(this.chartArr, DataLabExtContents.class); + if (dl != null) { // Extended Label -- add to attachedlabel, if any + datalabels = dl.getTypeInt(); + } + DataFormat df = this.getDataFormatRec(false); + if (df != null) { + datalabels |= df.getDataLabelTypeInt(); + } + return datalabels; } - + /** * PIE data label information is contained within the 1st series only *
                TODO: not implemented yet * return data label options as an int *
                can be one or more of: -
                SHOWVALUE= 0x1; -
                SHOWVALUEPERCENT= 0x2; -
                SHOWCATEGORYPERCENT= 0x4; -
                SMOOTHEDLINE= 0x8; -
                SHOWCATEGORYLABEL= 0x10; -
                SHOWBUBBLELABEL= 0x20; -
                SHOWSERIESLABEL= 0x40; - * @see AttachedLabel + *
                SHOWVALUE= 0x1; + *
                SHOWVALUEPERCENT= 0x2; + *
                SHOWCATEGORYPERCENT= 0x4; + *
                SMOOTHEDLINE= 0x8; + *
                SHOWCATEGORYLABEL= 0x10; + *
                SHOWBUBBLELABEL= 0x20; + *
                SHOWSERIESLABEL= 0x40; + * * @param defaultdl int default data label setting for overall chart - * @return int array of data labels for each pie slice + * @return int array of data labels for each pie slice + * @see AttachedLabel */ public int[] getDataLabelsPIE(int defaultdl) { - int datalabels= 0; - DataFormat df= this.getDataFormatRec(false); - if (df!=null) { - datalabels|=df.getDataLabelTypeInt(); - } - return null; - } - + int datalabels = 0; + DataFormat df = this.getDataFormatRec(false); + if (df != null) { + datalabels |= df.getDataLabelTypeInt(); + } + return null; + } + /** - * return the OOXML shape property for this series + * return the OOXML shape property for this series + * * @return - */ - public SpPr getSpPr() { return shapeProps; } + */ + public SpPr getSpPr() { + return shapeProps; + } + /** * set the OOXML shape properties for this series + * * @param sp */ public void setSpPr(SpPr sp) { - shapeProps= sp; - } + shapeProps = sp; + } + /** - * return the OOXML marker properties for this series + * return the OOXML marker properties for this series + * * @return - */ - public Marker getMarker() { return m; } + */ + public Marker getMarker() { + return m; + } /** * set the OOXML marker properties for this series + * * @param Sp */ public void setMarker(Marker m) { - this.m= m; - } + this.m = m; + } + /** - * return the OOXML dLbls (data labels) properties for this series + * return the OOXML dLbls (data labels) properties for this series + * * @return - */ - public DLbls getDLbls() { return d; } + */ + public DLbls getDLbls() { + return d; + } /** * set the OOXML dLbls (data labels) properties for this series + * * @param Sp */ public void setDLbls(DLbls d) { - this.d= d; - } + this.d = d; + } + /** - * return OOXML dPt (data points) for this series + * return OOXML dPt (data points) for this series + * * @return - */ - public DPt[] getDPt() { - if (dPts!=null) - return (DPt[]) dPts.toArray(new DPt[]{}); - return null; + */ + public DPt[] getDPt() { + if (dPts != null) + return (DPt[]) dPts.toArray(new DPt[]{}); + return null; } - + /** * add a dPt element (data point) for this series + * * @param Sp */ public void addDpt(DPt d) { - if (dPts==null) - dPts= new ArrayList(); - dPts.add(d); + if (dPts == null) + dPts = new ArrayList(); + dPts.add(d); } - + /** * returns the val OOXML element that defines the values for the series values - * @param valstr either "val" or "yval" for scatter/bubble charts + * + * @param valstr either "val" or "yval" for scatter/bubble charts * @return */ public StringBuffer getValOOXML(String valstr) { - StringBuffer ooxml= new StringBuffer(); - ooxml.append(""); ooxml.append("\r\n"); - - Ai seriesAi= null; - for (int i=0;i"); + ooxml.append("\r\n"); + + Ai seriesAi = null; + for (int i = 0; i < chartArr.size(); i++) { + BiffRec br = chartArr.get(i); + if (br.getOpcode() == AI) { + Ai thisAi = (Ai) br; if (thisAi.getType() == Ai.TYPE_VALS) { - seriesAi= thisAi; - break; + seriesAi = thisAi; + break; } - + } } - - ooxml.append(""); ooxml.append("\r\n"); // number reference - ooxml.append("" + OOXMLAdapter.stripNonAscii(seriesAi.toString()) + ""); ooxml.append("\r\n"); // string range - // Need numCache for chart lines apparently - ooxml.append(""); ooxml.append("\r\n"); // specifies the last data shown on the chart for a series - // formatCode == format pattern - ooxml.append("" + this.getSeriesFormatPattern() + ""); - CellRange cr = new CellRange(seriesAi.toString(), parentChart.wbh, false); - CellHandle[] ch= cr.getCells(); - // ptCount == point count - ooxml.append(getValueRangeOOXML(ch)); - // pt * n == a Numeric Point each has a child, an idx attribute and an optional formatcode attribute - ooxml.append(""); ooxml.append("\r\n"); // - - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - return ooxml; - - } + + ooxml.append(""); + ooxml.append("\r\n"); // number reference + ooxml.append("" + OOXMLAdapter.stripNonAscii(seriesAi.toString()) + ""); + ooxml.append("\r\n"); // string range + // Need numCache for chart lines apparently + ooxml.append(""); + ooxml.append("\r\n"); // specifies the last data shown on the chart for a series + // formatCode == format pattern + ooxml.append("" + this.getSeriesFormatPattern() + ""); + CellRange cr = new CellRange(seriesAi.toString(), parentChart.wbh, false); + CellHandle[] ch = cr.getCells(); + // ptCount == point count + ooxml.append(getValueRangeOOXML(ch)); + // pt * n == a Numeric Point each has a child, an idx attribute and an optional formatcode attribute + ooxml.append(""); + ooxml.append("\r\n"); // + + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + return ooxml; + + } /** - * return the cat OOXML element used to define a series category - * @param cat string category cell range for the given series -- - * almost always the same for each series (except for scatter/bubble charts) - * @param catstr either "cat" or "xval" for scatter/bubble charts - * cat elements must contain string references - * xval contain numeric references + * return the cat OOXML element used to define a series category + * + * @param cat string category cell range for the given series -- + * almost always the same for each series (except for scatter/bubble charts) + * @param catstr either "cat" or "xval" for scatter/bubble charts + * cat elements must contain string references + * xval contain numeric references * @return */ public StringBuffer getCatOOXML(String cat, String catstr) { - StringBuffer ooxml= new StringBuffer(); - if (!"".equals(cat)) { // causes 1004 vb error upon Excel SAVE - BAXTER SAVE BUG - ooxml.append(""); ooxml.append("\r\n"); // categories contain a string "formula" ref + string caches - if (catstr.equals("cat")) - ooxml.append(""); // string reference - else - ooxml.append(""); // number reference - ooxml.append("\r\n"); - ooxml.append("" + OOXMLAdapter.stripNonAscii(cat) + ""); ooxml.append("\r\n"); - /* 20090211 KSC: if errors in referenced cells whole chart will error; best to avoid caching at all */ - if (catstr.equals("cat")) - ooxml.append(""); // string reference - else - ooxml.append(""); // number reference - ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - } else { - // TESTING-- remove when done - //Logger.logWarn("ChartHandle.getOOXML: null category found- skipping"); - } - return ooxml; + StringBuffer ooxml = new StringBuffer(); + if (!"".equals(cat)) { // causes 1004 vb error upon Excel SAVE - BAXTER SAVE BUG + ooxml.append(""); + ooxml.append("\r\n"); // categories contain a string "formula" ref + string caches + if (catstr.equals("cat")) + ooxml.append(""); // string reference + else + ooxml.append(""); // number reference + ooxml.append("\r\n"); + ooxml.append("" + OOXMLAdapter.stripNonAscii(cat) + ""); + ooxml.append("\r\n"); + /* 20090211 KSC: if errors in referenced cells whole chart will error; best to avoid caching at all */ + if (catstr.equals("cat")) + ooxml.append(""); // string reference + else + ooxml.append(""); // number reference + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + } else { + // TESTING-- remove when done + //Logger.logWarn("ChartHandle.getOOXML: null category found- skipping"); + } + return ooxml; + } + + /** + * returns the bubbleSize OOXML element that defines the values for the series values + * + * @param isBubble3d true if it's a 3d bubble chart + * @return + */ + public StringBuffer getBubbleOOXML(boolean isBubble3d) { + StringBuffer ooxml = new StringBuffer(); + Ai bubbleAi = null; + for (int i = 0; i < chartArr.size(); i++) { + BiffRec br = chartArr.get(i); + if (br.getOpcode() == AI) { + Ai thisAi = (Ai) br; + if (thisAi.getType() == Ai.TYPE_BUBBLES) { + bubbleAi = thisAi; + break; + } + } + } + + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); // number reference + ooxml.append("" + bubbleAi.toString() + ""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + try { + CellHandle[] cells = CellRange.getCells(bubbleAi.toString(), parentChart.wbh); + ooxml.append(getValueRangeOOXML(cells)); + ooxml.append("\r\n"); + } catch (NumberFormatException e) { + Logger.logErr("geteriesOOXML: Number format exception for Bubble Range: " + bubbleAi.toString()); + } + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + if (isBubble3d) + ooxml.append(""); + ooxml.append("\r\n"); + return ooxml; } - + /** * returns the bubbleSize OOXML element that defines the values for the series values - * @param isBubble3d true if it's a 3d bubble chart + * + * @param isBubble3d true if it's a 3d bubble chart * @return */ - public StringBuffer getBubbleOOXML(boolean isBubble3d) { - StringBuffer ooxml= new StringBuffer(); - Ai bubbleAi= null; - for (int i=0;i"); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); // number reference - ooxml.append("" + bubbleAi.toString() + ""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - try { - CellHandle[] cells= CellRange.getCells(bubbleAi.toString(), parentChart.wbh); - ooxml.append(getValueRangeOOXML(cells)); ooxml.append("\r\n"); - } catch (NumberFormatException e) { - Logger.logErr("geteriesOOXML: Number format exception for Bubble Range: " + bubbleAi.toString()); - } - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - if (isBubble3d) - ooxml.append(""); ooxml.append("\r\n"); - return ooxml; - } - - /** - * returns the bubbleSize OOXML element that defines the values for the series values - * @param isBubble3d true if it's a 3d bubble chart - * @return - */ - public StringBuffer getLegendOOXML(boolean from2003) { - StringBuffer ooxml= new StringBuffer(); - String txt= this.getLegendText(); - Ai ai = this.getLegendAi(); + public StringBuffer getLegendOOXML(boolean from2003) { + StringBuffer ooxml = new StringBuffer(); + String txt = this.getLegendText(); + Ai ai = this.getLegendAi(); /* String txt= null; try { io.starter.formats.XLS.formulas.Ptg[] p= ai.getCellRangePtgs(); @@ -1106,54 +1197,70 @@ public StringBuffer getLegendOOXML(boolean from2003) { }catch(ClassCastException e) { // couldn't find it! } -*/ - - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); // string reference - if (ai!=null) { - ooxml.append("" + OOXMLAdapter.stripNonAscii(ai.getDefinition()) + ""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append("" + OOXMLAdapter.stripNonAscii(txt) + ""); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); ooxml.append("\r\n"); - if (this.getSpPr()!=null) - ooxml.append(this.getSpPr().getOOXML()); - else if (from2003) { - SpPr ss; - if (parentChart.getChartType()!=RADARCHART) - ss= new SpPr("c", this.getSeriesColor().substring(1), 12700, "000000"); - else - ss= new SpPr("c", null, 25400, this.getSeriesColor().substring(1)); - ooxml.append(ss.getOOXML()); - } +*/ + + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); // string reference + if (ai != null) { + ooxml.append("" + OOXMLAdapter.stripNonAscii(ai.getDefinition()) + ""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append("" + OOXMLAdapter.stripNonAscii(txt) + ""); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + ooxml.append(""); + ooxml.append("\r\n"); + if (this.getSpPr() != null) + ooxml.append(this.getSpPr().getOOXML()); + else if (from2003) { + SpPr ss; + if (parentChart.getChartType() != RADARCHART) + ss = new SpPr("c", this.getSeriesColor().substring(1), 12700, "000000"); + else + ss = new SpPr("c", null, 25400, this.getSeriesColor().substring(1)); + ooxml.append(ss.getOOXML()); + } } - return ooxml; - } - - /** - * generate the OOXML used to represent this set of value cells (element numRef) - * @param cells - * @return - */ - private static String getValueRangeOOXML(CellHandle[] cells) { - StringBuffer ooxml= new StringBuffer(); - if (cells==null) return ""; - ooxml.append(""); ooxml.append("\r\n"); - for (int j= 0; j < cells.length; j++) { - ooxml.append(""); ooxml.append("\r\n"); - if (!cells[j].getStringVal().equals("NaN")) { - ooxml.append("" + cells[j].getStringVal() + ""); ooxml.append("\r\n"); - }else { // appears that NaN is an invalid entry - ooxml.append("0"); ooxml.append("\r\n"); - } - ooxml.append(""); ooxml.append("\r\n"); - } - return ooxml.toString(); - } - - + return ooxml; + } + + /** + * generate the OOXML used to represent this set of value cells (element numRef) + * + * @param cells + * @return + */ + private static String getValueRangeOOXML(CellHandle[] cells) { + StringBuffer ooxml = new StringBuffer(); + if (cells == null) return ""; + ooxml.append(""); + ooxml.append("\r\n"); + for (int j = 0; j < cells.length; j++) { + ooxml.append(""); + ooxml.append("\r\n"); + if (!cells[j].getStringVal().equals("NaN")) { + ooxml.append("" + cells[j].getStringVal() + ""); + ooxml.append("\r\n"); + } else { // appears that NaN is an invalid entry + ooxml.append("0"); + ooxml.append("\r\n"); + } + ooxml.append(""); + ooxml.append("\r\n"); + } + return ooxml.toString(); + } + + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/SeriesList.java b/src/main/java/io/starter/formats/XLS/charts/SeriesList.java index 8f65b38..dc28ba9 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SeriesList.java +++ b/src/main/java/io/starter/formats/XLS/charts/SeriesList.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,64 +22,65 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; /** * SeriesList: Specifies the Series in an Overlay Chart (0x1016) - * - * bytes - 2 - nseries following - * 2 * nseries = An array of 2-byte unsigned integers, - * each of which specifies a one-based index of a Series record - * in the collection of Series records in the current chart sheet substream - * + *

                + * bytes - 2 - nseries following + * 2 * nseries = An array of 2-byte unsigned integers, + * each of which specifies a one-based index of a Series record + * in the collection of Series records in the current chart sheet substream */ public class SeriesList extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7852050067799624402L; - int[] seriesmap= null; - public void init() { - super.init(); - int nseries= ByteTools.readShort(this.getData()[0], this.getData()[1]); - seriesmap= new int[nseries]; - for (int i= 0; i < nseries; i++) { - int idx= ((i+1)*2); - seriesmap[i]= ByteTools.readShort(this.getData()[idx], this.getData()[idx+1]); - } - } - - /** - * return the series mappings for the associated overlay chart - *
                series mappings links the overlay chart to the absolute series number - * (determined by the actual order of the series in the chart array structure) - * @return - */ - public int[] getSeriesMappings() { - return seriesmap; - } - - /** - * set the series mappings for the associated overlay chart - *
                series mappings links the overlay chart to the absolute series number - * (determined by the actual order of the series in the chart array structure) - * @param seriesmap - */ - public void setSeriesMappings(int[] smap) { - short nseries= (short)smap.length; - seriesmap= new int[nseries]; - byte[] data= new byte[(nseries+1)*2]; - byte[] b= ByteTools.shortToLEBytes(nseries); - data[0]= b[0]; - data[1]= b[1]; - for (int i= 0; i < nseries; i++) { - int idx= ((i+1)*2); - seriesmap[i]= smap[i]; - b= ByteTools.shortToLEBytes((short)smap[i]); - data[idx]= b[0]; - data[idx+1]= b[1]; - } - setData(data); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7852050067799624402L; + int[] seriesmap = null; + + public void init() { + super.init(); + int nseries = ByteTools.readShort(this.getData()[0], this.getData()[1]); + seriesmap = new int[nseries]; + for (int i = 0; i < nseries; i++) { + int idx = ((i + 1) * 2); + seriesmap[i] = ByteTools.readShort(this.getData()[idx], this.getData()[idx + 1]); + } + } + + /** + * return the series mappings for the associated overlay chart + *
                series mappings links the overlay chart to the absolute series number + * (determined by the actual order of the series in the chart array structure) + * + * @return + */ + public int[] getSeriesMappings() { + return seriesmap; + } + + /** + * set the series mappings for the associated overlay chart + *
                series mappings links the overlay chart to the absolute series number + * (determined by the actual order of the series in the chart array structure) + * + * @param seriesmap + */ + public void setSeriesMappings(int[] smap) { + short nseries = (short) smap.length; + seriesmap = new int[nseries]; + byte[] data = new byte[(nseries + 1) * 2]; + byte[] b = ByteTools.shortToLEBytes(nseries); + data[0] = b[0]; + data[1] = b[1]; + for (int i = 0; i < nseries; i++) { + int idx = ((i + 1) * 2); + seriesmap[i] = smap[i]; + b = ByteTools.shortToLEBytes((short) smap[i]); + data[idx] = b[0]; + data[idx + 1] = b[1]; + } + setData(data); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SeriesText.java b/src/main/java/io/starter/formats/XLS/charts/SeriesText.java index bca2c61..7e76e82 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SeriesText.java +++ b/src/main/java/io/starter/formats/XLS/charts/SeriesText.java @@ -2,109 +2,111 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.WorkBookFactory; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.*; -import java.io.*; +import io.starter.formats.XLS.WorkBookFactory; +import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; -/** SeriesText: Chart Legend/Category/Value Text Definition (100Dh)
                - - This record defines the SeriesText data of a chart. - - sdtX and sdtY fields determine data type (numeric and text) - - cValx and cValy fields determine number of cells in series - - Offset Name Size Contents - -- - 4 id 2 Text identifier (should be zero) - 8 cch 2 length of String text - 10 rgch 2 String text - - - * @see Chart +/** + * SeriesText: Chart Legend/Category/Value Text Definition (100Dh)
                + *

                + * This record defines the SeriesText data of a chart. + *

                + * sdtX and sdtY fields determine data type (numeric and text) + *

                + * cValx and cValy fields determine number of cells in series + *

                + * Offset Name Size Contents + * -- + * 4 id 2 Text identifier (should be zero) + * 8 cch 2 length of String text + * 10 rgch 2 String text + * + * + * + * @see Chart */ -public final class SeriesText extends GenericChartObject implements ChartObject -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3794355940075116165L; - protected int id = -1, cch = -1; - private String text = ""; - - public void setText(String t){ - // create a new SeriesText value from the passed-in String - byte[] strbytes = null; - byte uni = 0x0; - int lent = 0; - try{ - strbytes = t.getBytes(WorkBookFactory.UNICODEENCODING); - uni = 0x1; - lent = strbytes.length /2; - }catch(Exception e) { - strbytes = t.getBytes(); - lent = strbytes.length; - } - byte[] newbytes = new byte[strbytes.length + 4]; +public final class SeriesText extends GenericChartObject implements ChartObject { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -3794355940075116165L; + protected int id = -1, cch = -1; + private String text = ""; + + public void setText(String t) { + // create a new SeriesText value from the passed-in String + byte[] strbytes = null; + byte uni = 0x0; + int lent = 0; + try { + strbytes = t.getBytes(WorkBookFactory.UNICODEENCODING); + uni = 0x1; + lent = strbytes.length / 2; + } catch (Exception e) { + strbytes = t.getBytes(); + lent = strbytes.length; + } + byte[] newbytes = new byte[strbytes.length + 4]; // byte[] lenbytes = ByteTools.shortToLEBytes((short)strbytes.length); - newbytes[0] = 0x0; - newbytes[1] = 0x0; - newbytes[2] = (byte) lent; - newbytes[3] = uni; - System.arraycopy(strbytes,0, newbytes, 4, strbytes.length); - this.setData(newbytes); - this.text = t; - } - - public void init(){ - super.init(); - //byte[] data = this.getData(); - int multi = 2; - if(this.getByteAt(3)==0x0)multi = 1; - cch = (int) this.getByteAt(2) * multi; - if(cch<0)cch*=-1; // strangely it can be negative... - try { - byte[] namebytes = this.getBytesAt(4,cch); - try{ - text = new String(namebytes, WorkBookFactory.UNICODEENCODING); - }catch(UnsupportedEncodingException e){Logger.logInfo("Unsupported Encoding error in SeriesText: " + e);} - if((DEBUGLEVEL > 10)) Logger.logInfo("Series Text Value: " + text); + newbytes[0] = 0x0; + newbytes[1] = 0x0; + newbytes[2] = (byte) lent; + newbytes[3] = uni; + System.arraycopy(strbytes, 0, newbytes, 4, strbytes.length); + this.setData(newbytes); + this.text = t; + } - }catch(Exception ex) { - Logger.logWarn("SeriesText.init failed: " + ex); - } - //Logger.logInfo("Initialized SeriesText: "+ text); - } - - public String toString(){ - return this.text; + public void init() { + super.init(); + //byte[] data = this.getData(); + int multi = 2; + if (this.getByteAt(3) == 0x0) multi = 1; + cch = (int) this.getByteAt(2) * multi; + if (cch < 0) cch *= -1; // strangely it can be negative... + try { + byte[] namebytes = this.getBytesAt(4, cch); + try { + text = new String(namebytes, WorkBookFactory.UNICODEENCODING); + } catch (UnsupportedEncodingException e) { + Logger.logInfo("Unsupported Encoding error in SeriesText: " + e); + } + if ((DEBUGLEVEL > 10)) Logger.logInfo("Series Text Value: " + text); + + } catch (Exception ex) { + Logger.logWarn("SeriesText.init failed: " + ex); + } + //Logger.logInfo("Initialized SeriesText: "+ text); + } + + public String toString() { + return this.text; } - + public static SeriesText getPrototype(String text) { SeriesText st = new SeriesText(); st.setOpcode(SERIESTEXT); @@ -113,5 +115,6 @@ public static SeriesText getPrototype(String text) { st.setText(text); return st; } - private byte[] PROTOTYPE_BYTES = new byte[] {0, 0, 7, 1, 74, 0, 97, 0, 110, 0, 117, 0, 97, 0, 114, 0, 121, 0}; + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 7, 1, 74, 0, 97, 0, 110, 0, 117, 0, 97, 0, 114, 0, 121, 0}; } diff --git a/src/main/java/io/starter/formats/XLS/charts/ShtProps.java b/src/main/java/io/starter/formats/XLS/charts/ShtProps.java index ed09cd1..4f26591 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ShtProps.java +++ b/src/main/java/io/starter/formats/XLS/charts/ShtProps.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,28 +22,26 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * ShtProps: Sheet Properties (0x1044) - * + *

                * 4 grbit 2 * 6 mdBlank 1 Empty cells plotted as: 0= not plotted, 1= 0, 2= interpolated - * + *

                * grbit: * 0 0x1 fManSerAlloc 1= chart has been changed from default * 1 0x2 fPlotVisOnly 1= plot visible cells only * 2 0x4 fNotSizeWith 1= do not size chart with window * 3 0x8 fManPlotArea 0= default dimensions 1= use POS rec * 4 0x10 fAlwaysAutoPlotArea 1= user has modified chart enough that fManPlotArea should be set to 0 (!!!) - * */ -public class ShtProps extends GenericChartObject implements ChartObject{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5462571460161191942L; +public class ShtProps extends GenericChartObject implements ChartObject { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5462571460161191942L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SiIndex.java b/src/main/java/io/starter/formats/XLS/charts/SiIndex.java index 062a80e..46a2bff 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SiIndex.java +++ b/src/main/java/io/starter/formats/XLS/charts/SiIndex.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,43 +22,42 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** * SiIndex: Series Index (0x1065) - * - * Indicates the type of data contained in the Number records following it. - + *

                + * Indicates the type of data contained in the Number records following it. + *

                * 2 bytes - * 0x0001 - Series values or vertical values (for scatter or bubble chart groups) - 0x0002 - Category labels or horizontal values (for scatter or bubble chart groups) - 0x0003 - Bubble sizes - - specifies the beginning of a sequence of records that contains a cache of the data for the sequence of records that conforms to a specific AI rule (section 2.1.7.20.1) in the series (section 2.2.3.9) and error bars (section 2.2.3.13). - The relationship between the series and the chart data cache is specified as follows: - The first SIIndex record in the chart sheet substream, which MUST contain a numIndex field - equal to 0x0001, corresponds to the second sequence of records that conforms to the AI rule - The second SIIndex record in the chart sheet substream, which MUST contain a numIndex field equal to 0x0002, - corresponds to the third sequence of records that conforms to the AI rule (section 2.1.7.20.1). - The third SIIndex record in the chart sheet substream, which MUST contain a numIndex field equal to 0x0003, - corresponds to the fourth sequence of records that conforms to the AI rule (section 2.1.7.20.1). - The Number, BoolErr , Blank , and Label records each specify an individual value stored in the cache. Each column in the cache - corresponds to a series or error bar, where the zero-based index of the column, specified by the cell.col field in the Number, BoolErr, Blank, or Label records, - equals the zero-based index of the Series record in the collection of Series records that corresponds to the series or error bar. - - * + * 0x0001 + * Series values or vertical values (for scatter or bubble chart groups) + * 0x0002 + * Category labels or horizontal values (for scatter or bubble chart groups) + * 0x0003 + * Bubble sizes + *

                + * specifies the beginning of a sequence of records that contains a cache of the data for the sequence of records that conforms to a specific AI rule (section 2.1.7.20.1) in the series (section 2.2.3.9) and error bars (section 2.2.3.13). + * The relationship between the series and the chart data cache is specified as follows: + * The first SIIndex record in the chart sheet substream, which MUST contain a numIndex field + * equal to 0x0001, corresponds to the second sequence of records that conforms to the AI rule + * The second SIIndex record in the chart sheet substream, which MUST contain a numIndex field equal to 0x0002, + * corresponds to the third sequence of records that conforms to the AI rule (section 2.1.7.20.1). + * The third SIIndex record in the chart sheet substream, which MUST contain a numIndex field equal to 0x0003, + * corresponds to the fourth sequence of records that conforms to the AI rule (section 2.1.7.20.1). + * The Number, BoolErr , Blank , and Label records each specify an individual value stored in the cache. Each column in the cache + * corresponds to a series or error bar, where the zero-based index of the column, specified by the cell.col field in the Number, BoolErr, Blank, or Label records, + * equals the zero-based index of the Series record in the collection of Series records that corresponds to the series or error bar. */ public class SiIndex extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6810089722566956477L; - short type; - public void init() { - super.init(); - type= ByteTools.readShort(this.getData()[0], this.getData()[1]); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -6810089722566956477L; + short type; + + public void init() { + super.init(); + type = ByteTools.readShort(this.getData()[0], this.getData()[1]); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.java b/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.java index 3876027..9a3b418 100644 --- a/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,107 +22,111 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.HashMap; + public class StackedAreaChart extends AreaChart { - public StackedAreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - area= (Area) charttype; - } - public boolean isStacked() { return true; } - /** - * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) + public StackedAreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + area = (Area) charttype; + } + + public boolean isStacked() { + return true; + } + + /** + * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double x= chartMetrics.get("x"); - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - double min= chartMetrics.get("min"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there - // y value for each point= h/YMAX - if (series.size()==0) { - Logger.logErr("Area.getSVG: error in series"); - return ""; - } - StringBuffer svg= new StringBuffer(); - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - - double xfactor= 0, yfactor= 0; // - if (categories.length>1) - xfactor= w/(categories.length-1); // w/#categories - if (max!=0) - yfactor= h/max; // h/YMAXSCALE + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double x = chartMetrics.get("x"); + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + double min = chartMetrics.get("min"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there + // y value for each point= h/YMAX + if (series.size() == 0) { + Logger.logErr("Area.getSVG: error in series"); + return ""; + } + StringBuffer svg = new StringBuffer(); + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + + double xfactor = 0, yfactor = 0; // + if (categories.length > 1) + xfactor = w / (categories.length - 1); // w/#categories + if (max != 0) + yfactor = h / max; // h/YMAXSCALE + + // first, calculate Area points- which are summed per series + int n = series.size(); + int nSeries = ((double[]) series.get(0)).length; + double[][] areapoints = new double[nSeries][n]; + for (int i = 0; i < n; i++) { + double[] seriesy = (double[]) series.get(i); + for (int j = 0; j < seriesy.length; j++) { + double yval = seriesy[j]; + areapoints[j][i] = yval + ((i > 0) ? areapoints[j][i - 1] : 0); + } + } + // for each series + for (int i = n - 1; i >= 0; i--) { // "paint" right to left + svg.append("\r\n"); + String points = ""; + double x1 = 0; + double y1 = 0; + String labels = null; + double[] curseries = (double[]) series.get(i); + for (int j = 0; j < curseries.length; j++) { + x1 = (x) + j * xfactor; + double yval = areapoints[j][i]; // current point + points += ((x) + (j) * xfactor) + "," + ((y + h) - (yval * yfactor)); - // first, calculate Area points- which are summed per series - int n= series.size(); - int nSeries= ((double[])series.get(0)).length; - double[][] areapoints= new double[nSeries][n]; - for (int i= 0; i < n; i++) { - double[] seriesy= (double[])series.get(i); - for (int j= 0; j < seriesy.length; j++) { - double yval= seriesy[j]; - areapoints[j][i]=yval+((i>0)?areapoints[j][i-1]:0); - } - } - // for each series - for (int i= n-1; i >= 0; i--) { // "paint" right to left - svg.append("\r\n"); - String points= ""; - double x1= 0; - double y1= 0; - String labels= null; - double[] curseries= (double[])series.get(i); - for (int j= 0; j < curseries.length; j++) { - x1= (x)+j*xfactor; - double yval= areapoints[j][i]; // current point - points+= ((x)+(j)*xfactor) + "," + ((y+h)-(yval*yfactor)); - - if (j==0) y1= ((y+h)-(yval*yfactor)); // end point (==start point) for path statement below - points+=" "; - // DATA LABELS - String l= getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); - if (l!=null) { - // if only category label, center over all series; anything else, position at data point - boolean showCategories= (dls[i] & AttachedLabel.CATEGORYLABEL)==AttachedLabel.CATEGORYLABEL; - boolean showValueLabel= (dls[i] & AttachedLabel.VALUELABEL)==AttachedLabel.VALUELABEL; - boolean showValue= (dls[i] & AttachedLabel.VALUE)==AttachedLabel.VALUE; - if (showCategories && !(showValue || showValueLabel) && j==0) { // only 1 label, centered along category axis within area - //y1+= (seriesx[seriesx.length/2]/2)*yfactor; - double hh= (areapoints[areapoints.length/2][i]*yfactor); - double yy= (y+h) - hh+10; - if (labels==null) labels= ""; - labels= "" + l + "\r\n"; - } else if (showValue || showValueLabel) { // labels at each data point - if (labels==null) labels= ""; - double yy= (((y+h)-((yval-(curseries[j]*.5))*yfactor))); - labels+= "" + l + "\r\n"; - } - } - } - // pointsends connects up area to beginning - double x0= x; - String pointsend= x1 + "," + (y+h) + - " " + x0 + "," + (y+h) + - " " + x0 + "," + y1; - svg.append("\r\n"); + if (j == 0) y1 = ((y + h) - (yval * yfactor)); // end point (==start point) for path statement below + points += " "; + // DATA LABELS + String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); + if (l != null) { + // if only category label, center over all series; anything else, position at data point + boolean showCategories = (dls[i] & AttachedLabel.CATEGORYLABEL) == AttachedLabel.CATEGORYLABEL; + boolean showValueLabel = (dls[i] & AttachedLabel.VALUELABEL) == AttachedLabel.VALUELABEL; + boolean showValue = (dls[i] & AttachedLabel.VALUE) == AttachedLabel.VALUE; + if (showCategories && !(showValue || showValueLabel) && j == 0) { // only 1 label, centered along category axis within area + //y1+= (seriesx[seriesx.length/2]/2)*yfactor; + double hh = (areapoints[areapoints.length / 2][i] * yfactor); + double yy = (y + h) - hh + 10; + if (labels == null) labels = ""; + labels = "" + l + "\r\n"; + } else if (showValue || showValueLabel) { // labels at each data point + if (labels == null) labels = ""; + double yy = (((y + h) - ((yval - (curseries[j] * .5)) * yfactor))); + labels += "" + l + "\r\n"; + } + } + } + // pointsends connects up area to beginning + double x0 = x; + String pointsend = x1 + "," + (y + h) + + " " + x0 + "," + (y + h) + + " " + x0 + "," + y1; + svg.append("\r\n"); /* john took out @@ -132,10 +136,10 @@ public String getSVG(HashMap chartMetrics, HashMap\r\n");*/ - // Now print data labels, if any - if (labels!=null) svg.append(labels); - svg.append("\r\n"); - } - return svg.toString(); - } + // Now print data labels, if any + if (labels != null) svg.append(labels); + svg.append("\r\n"); + } + return svg.toString(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/StackedColumn.java b/src/main/java/io/starter/formats/XLS/charts/StackedColumn.java index 4479fcd..26b5ef8 100644 --- a/src/main/java/io/starter/formats/XLS/charts/StackedColumn.java +++ b/src/main/java/io/starter/formats/XLS/charts/StackedColumn.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,103 +22,106 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; -import java.util.HashMap; - -import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.Logger; +import java.util.ArrayList; +import java.util.HashMap; + public class StackedColumn extends ColChart { - int defaultShape= 0; //???? + int defaultShape = 0; //???? + + public StackedColumn(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + col = (Bar) charttype; + } - public StackedColumn(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - col= (Bar) charttype; - } - public boolean isStacked() { return true; } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + public boolean isStacked() { + return true; + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMapaxisMetrics, ChartSeries s) { - double x= chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed - double y= chartMetrics.get("y"); - double w= chartMetrics.get("w"); - double h= chartMetrics.get("h"); - double max= chartMetrics.get("max"); - Object[] categories= s.getCategories(); - ArrayList series= s.getSeriesValues(); - String[] seriescolors= s.getSeriesBarColors(); - String[] legends= s.getLegends(); - if (series.size()==0) { - Logger.logErr("Bar.getSVG: error in series"); - return ""; - } - /* - * TODO: Stacked vs 100% Stacked - */ - StringBuffer svg= new StringBuffer(); - int[] dls= getDataLabelInts(); // get array of data labels (can be specific per series ...) - - double barw= 0, yfactor= 0; - if (categories.length > 0) - barw= w/(categories.length*2); // w/#categories (only 1 column per series) - if (max!=0) - yfactor= h/max; // h/YMAXSCALE + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { + double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed + double y = chartMetrics.get("y"); + double w = chartMetrics.get("w"); + double h = chartMetrics.get("h"); + double max = chartMetrics.get("max"); + Object[] categories = s.getCategories(); + ArrayList series = s.getSeriesValues(); + String[] seriescolors = s.getSeriesBarColors(); + String[] legends = s.getLegends(); + if (series.size() == 0) { + Logger.logErr("Bar.getSVG: error in series"); + return ""; + } + /* + * TODO: Stacked vs 100% Stacked + */ + StringBuffer svg = new StringBuffer(); + int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) + + double barw = 0, yfactor = 0; + if (categories.length > 0) + barw = w / (categories.length * 2); // w/#categories (only 1 column per series) + if (max != 0) + yfactor = h / max; // h/YMAXSCALE - double[] totalperseries= null; - boolean f100= col.is100Percent(); - if (f100) { //FOR 100% STACKED - // first, calculate points- which are summed per series - int n= series.size(); - int nSeries= ((double[])series.get(0)).length; - totalperseries= new double[nSeries]; - for (int i= 0; i < n; i++) { - double[] seriesy= (double[])series.get(i); - for (int j= 0; j < seriesy.length; j++) { - double yval= seriesy[j]; - totalperseries[j]=yval+totalperseries[j]; - } - } - } - // for each series - ONE COLUMN per series - double[] previousY= new double[0]; - for (int i= 0; i < series.size(); i++) { - svg.append("\r\n"); - double[] curseries= (double[])series.get(i); // for each data point - stacked on series column - String[] curranges= (String[])s.getSeriesRanges().get(i); - double xx, yy= y+h; // origin - if (i == 0) { - previousY= new double[curseries.length]; - for (int j= 0; j < previousY.length; j++) - previousY[j]= yy; // origin - } - double barh; - for (int j= 0; j < curseries.length; j++) { - xx= (x)+(j*barw*2)+barw/2; - if (previousY.length > j) // should - yy= previousY[j]; - if (f100) - barh= ((curseries[j]/totalperseries[j])*(h-10)); // height of current point as a percentage of total points per series - else - barh= (curseries[j]*yfactor); // height of current point - svg.append(""); - //TODO: DATA LABELS - // Now print data labels, if any - //if (labels!=null) svg.append(labels); - if (previousY.length > j) // should - previousY[j]= yy-barh; - } - svg.append("\r\n"); - } - return svg.toString(); - } + double[] totalperseries = null; + boolean f100 = col.is100Percent(); + if (f100) { //FOR 100% STACKED + // first, calculate points- which are summed per series + int n = series.size(); + int nSeries = ((double[]) series.get(0)).length; + totalperseries = new double[nSeries]; + for (int i = 0; i < n; i++) { + double[] seriesy = (double[]) series.get(i); + for (int j = 0; j < seriesy.length; j++) { + double yval = seriesy[j]; + totalperseries[j] = yval + totalperseries[j]; + } + } + } + // for each series - ONE COLUMN per series + double[] previousY = new double[0]; + for (int i = 0; i < series.size(); i++) { + svg.append("\r\n"); + double[] curseries = (double[]) series.get(i); // for each data point - stacked on series column + String[] curranges = (String[]) s.getSeriesRanges().get(i); + double xx, yy = y + h; // origin + if (i == 0) { + previousY = new double[curseries.length]; + for (int j = 0; j < previousY.length; j++) + previousY[j] = yy; // origin + } + double barh; + for (int j = 0; j < curseries.length; j++) { + xx = (x) + (j * barw * 2) + barw / 2; + if (previousY.length > j) // should + yy = previousY[j]; + if (f100) + barh = ((curseries[j] / totalperseries[j]) * (h - 10)); // height of current point as a percentage of total points per series + else + barh = (curseries[j] * yfactor); // height of current point + svg.append(""); + //TODO: DATA LABELS + // Now print data labels, if any + //if (labels!=null) svg.append(labels); + if (previousY.length > j) // should + previousY[j] = yy - barh; + } + svg.append("\r\n"); + } + return svg.toString(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/StartBlock.java b/src/main/java/io/starter/formats/XLS/charts/StartBlock.java index 5ff8a21..8aef41b 100644 --- a/src/main/java/io/starter/formats/XLS/charts/StartBlock.java +++ b/src/main/java/io/starter/formats/XLS/charts/StartBlock.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,107 +24,112 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; + /** - STARTBLOCK: Chart Future Record Type Start Block (852h) - Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. - Indicates the start of an object's scope for Pre-Excel 9 objects. These - records are used to push a chart element scope onto the parent element stack. - This stack is used to determine the containing element for records that are - used by more than one type of element. The FRAME record, for instance, is used - by at least four different elements. - - The STARTBLOCK/ENDBLOCK records are used for pre-Excel 9 elements with child records - (i.e., a record for the element followed by a BEGIN/END block for the child records.) - STARTBLOCK/ENDBLOCK are only written to enclose one or more child CFRT records and can - be placed outside the original BEGIN/END block. They may be omitted otherwise. - These records allow Excel 9 or later to determine the proper parent element even after - Excel 97 moves CFRTs to the end of the stream. Since these records are CFRTs, - they will stay with and keep contained any child CFRTs. - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0852h - 6 grbitFrt 2 FRT flags; must be zero - 8 iObjectKind 2 See table below - 10 iObjectContext 2 See table below - 12 iObjectInstance1 2 See table below - 14 iObjectInstance2 2 See table below - - The following table describes the meaning of each set of possible values for iObjectKind, - iObjectContext, iObjectInstance1, iObjectInstance2. In some cases, these fields are indexed, - and the indexes are described in the documentation for the parent rt. The table also lists - whether the STARTBLOCK/ENDBLOCK or STARTOBJECT/ENDOBJECT rts are used, and the parent rt. - - iObjectKind iObjectContext iObjectInstance1 iObjectInstance2 Class rt Description - 0 0 0 0 BLOCK AXIS PARENT Primary axis group - 0 0 1 0 BLOCK AXIS PARENT Secondary axis group - 2 0 0 0 BLOCK TEXT Chart title - 2 1 xi yi BLOCK TEXT Data label for point in hidden series - 2 2 0 0 BLOCK TEXT Default data label for other cases - 2 2 1 0 BLOCK TEXT Default data label for showing values only - 2 4 0 0 BLOCK TEXT Category axis title - 2 4 1 0 BLOCK TEXT Value axis title - 2 4 2 0 BLOCK TEXT Series axis title - 2 5 xi yi BLOCK TEXT Data label for point in visible series, iobjInstance1 and iobjInstance2 is the DATAFORMAT xi and yi - 2 6 0 0 OBJECT TEXT Display unit label - 4 0 0 0 BLOCK AXIS Category axis - 4 0 1 0 BLOCK AXIS Value axis - 4 0 2 0 BLOCK AXIS Series axis - 4 0 3 0 BLOCK AXIS X-axis on scatter chart - 5 0 index 0 BLOCK CHART FORMAT Chart group, iobjInstance1 is the index in the file - 6 0 0 0 BLOCK DAT Data table - 7 0 0 0 BLOCK FRAME Frame - 7 1 0 0 BLOCK FRAME Frame for an axis - 7 2 0 0 BLOCK FRAME Chart area frame - 8 0 0 0 BLOCK GELFRAME Frame fill - 8 1 0 0 BLOCK GELFRAME Series fill - 8 2 0 0 BLOCK GELFRAME Up/down bars fill - 8 3 0 0 BLOCK GELFRAME Floor fill - 8 3 1 0 BLOCK GELFRAME Walls fill - 9 0 0 0 BLOCK LEGEND Data table - 9 1 0 0 BLOCK LEGEND Legend - 10 0 iss 0 BLOCK LEGENDXN Legend entry - 11 0 0 0 BLOCK PICF Picture fill - 11 1 0 0 BLOCK PICF Data point picture fill - 12 0 index 0 BLOCK SERIES Series, iobjInstance1 is the index in the file - 13 0 0 0 BLOCK CHART Chart - 14 -1 0 0 BLOCK DATA FORMAT Series formatting - 14 yi xi 0 BLOCK DATA FORMAT Data point formatting - 15 0 0 0 BLOCK DROPBAR Up bars - 15 0 1 0 BLOCK DROPBAR Down bars - 15 0 2 0 BLOCK AXISLINE FORMAT Floor - 15 0 3 0 BLOCK AXISLINE FORMAT Walls - 16 0 0 0 OBJECT YMULT Axis multiplier - 17 0 verChart 0 OBJECT FRTFONT LIST Fonts - -*/ + * STARTBLOCK: Chart Future Record Type Start Block (852h) + * Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. + * Indicates the start of an object's scope for Pre-Excel 9 objects. These + * records are used to push a chart element scope onto the parent element stack. + * This stack is used to determine the containing element for records that are + * used by more than one type of element. The FRAME record, for instance, is used + * by at least four different elements. + *

                + * The STARTBLOCK/ENDBLOCK records are used for pre-Excel 9 elements with child records + * (i.e., a record for the element followed by a BEGIN/END block for the child records.) + * STARTBLOCK/ENDBLOCK are only written to enclose one or more child CFRT records and can + * be placed outside the original BEGIN/END block. They may be omitted otherwise. + * These records allow Excel 9 or later to determine the proper parent element even after + * Excel 97 moves CFRTs to the end of the stream. Since these records are CFRTs, + * they will stay with and keep contained any child CFRTs. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0852h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 iObjectKind 2 See table below + * 10 iObjectContext 2 See table below + * 12 iObjectInstance1 2 See table below + * 14 iObjectInstance2 2 See table below + *

                + * The following table describes the meaning of each set of possible values for iObjectKind, + * iObjectContext, iObjectInstance1, iObjectInstance2. In some cases, these fields are indexed, + * and the indexes are described in the documentation for the parent rt. The table also lists + * whether the STARTBLOCK/ENDBLOCK or STARTOBJECT/ENDOBJECT rts are used, and the parent rt. + *

                + * iObjectKind iObjectContext iObjectInstance1 iObjectInstance2 Class rt Description + * 0 0 0 0 BLOCK AXIS PARENT Primary axis group + * 0 0 1 0 BLOCK AXIS PARENT Secondary axis group + * 2 0 0 0 BLOCK TEXT Chart title + * 2 1 xi yi BLOCK TEXT Data label for point in hidden series + * 2 2 0 0 BLOCK TEXT Default data label for other cases + * 2 2 1 0 BLOCK TEXT Default data label for showing values only + * 2 4 0 0 BLOCK TEXT Category axis title + * 2 4 1 0 BLOCK TEXT Value axis title + * 2 4 2 0 BLOCK TEXT Series axis title + * 2 5 xi yi BLOCK TEXT Data label for point in visible series, iobjInstance1 and iobjInstance2 is the DATAFORMAT xi and yi + * 2 6 0 0 OBJECT TEXT Display unit label + * 4 0 0 0 BLOCK AXIS Category axis + * 4 0 1 0 BLOCK AXIS Value axis + * 4 0 2 0 BLOCK AXIS Series axis + * 4 0 3 0 BLOCK AXIS X-axis on scatter chart + * 5 0 index 0 BLOCK CHART FORMAT Chart group, iobjInstance1 is the index in the file + * 6 0 0 0 BLOCK DAT Data table + * 7 0 0 0 BLOCK FRAME Frame + * 7 1 0 0 BLOCK FRAME Frame for an axis + * 7 2 0 0 BLOCK FRAME Chart area frame + * 8 0 0 0 BLOCK GELFRAME Frame fill + * 8 1 0 0 BLOCK GELFRAME Series fill + * 8 2 0 0 BLOCK GELFRAME Up/down bars fill + * 8 3 0 0 BLOCK GELFRAME Floor fill + * 8 3 1 0 BLOCK GELFRAME Walls fill + * 9 0 0 0 BLOCK LEGEND Data table + * 9 1 0 0 BLOCK LEGEND Legend + * 10 0 iss 0 BLOCK LEGENDXN Legend entry + * 11 0 0 0 BLOCK PICF Picture fill + * 11 1 0 0 BLOCK PICF Data point picture fill + * 12 0 index 0 BLOCK SERIES Series, iobjInstance1 is the index in the file + * 13 0 0 0 BLOCK CHART Chart + * 14 -1 0 0 BLOCK DATA FORMAT Series formatting + * 14 yi xi 0 BLOCK DATA FORMAT Data point formatting + * 15 0 0 0 BLOCK DROPBAR Up bars + * 15 0 1 0 BLOCK DROPBAR Down bars + * 15 0 2 0 BLOCK AXISLINE FORMAT Floor + * 15 0 3 0 BLOCK AXISLINE FORMAT Walls + * 16 0 0 0 OBJECT YMULT Axis multiplier + * 17 0 verChart 0 OBJECT FRTFONT LIST Fonts + */ public class StartBlock extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1895593244077899106L; - short iObjectKind= 0; - public static final int CHART= 13, AXIS= 0, CHARTFORMAT= 5; - public void init() { - super.init(); - iObjectKind= ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - } // iObjectKind= 13 or 0 or 5 - private byte[] PROTOTYPE_BYTES = new byte[] {82, 8, 0,0,13,0,0,0,0,0,0,0}; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1895593244077899106L; + short iObjectKind = 0; + public static final int CHART = 13, AXIS = 0, CHARTFORMAT = 5; + + public void init() { + super.init(); + iObjectKind = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + } // iObjectKind= 13 or 0 or 5 + + private byte[] PROTOTYPE_BYTES = new byte[]{82, 8, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0}; + public static XLSRecord getPrototype() { - StartBlock sb= new StartBlock(); + StartBlock sb = new StartBlock(); sb.setOpcode(STARTBLOCK); sb.setData(sb.PROTOTYPE_BYTES); sb.init(); return sb; } + public void setObjectKind(int i) { - iObjectKind= (short) i; - updateRecord(); + iObjectKind = (short) i; + updateRecord(); } + private void updateRecord() { byte[] b = ByteTools.shortToLEBytes(iObjectKind); this.getData()[4] = b[0]; this.getData()[5] = b[1]; } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/StartObject.java b/src/main/java/io/starter/formats/XLS/charts/StartObject.java index 2eb3da9..4a3f22d 100644 --- a/src/main/java/io/starter/formats/XLS/charts/StartObject.java +++ b/src/main/java/io/starter/formats/XLS/charts/StartObject.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,38 +23,43 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.XLSRecord; -/** - STARTOBJECT: Chart Future Record Type Start Object (854h) - Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. - The STARTOBJECT/ENDOBJECT records are used for Excel 9+ elements with child - records instead of the BEGIN/END records. - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0854h - 6 grbitFrt 2 FRT flags; must be zero - 8 iObjectKind 2 Kinds of object, e.g., AI, CRT, SS, etc. - 10 iObjectContext 2 Where the object lives, object-specific - 12 iObjectInstance1 2 Which one from a collection, object-specific - 14 iObjectInstance2 2 Which one from a collection, object-specific. +/** + * STARTOBJECT: Chart Future Record Type Start Object (854h) + * Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. + * The STARTOBJECT/ENDOBJECT records are used for Excel 9+ elements with child + * records instead of the BEGIN/END records. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0854h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 iObjectKind 2 Kinds of object, e.g., AI, CRT, SS, etc. + * 10 iObjectContext 2 Where the object lives, object-specific + * 12 iObjectInstance1 2 Which one from a collection, object-specific + * 14 iObjectInstance2 2 Which one from a collection, object-specific. */ public class StartObject extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8933906882043608941L; - public void init() { - super.init(); - } // iObjectKind, 18 must be Chart, or maybe Chart label ... - private byte[] PROTOTYPE_BYTES = new byte[] {84, 8,0,0,18,0,0,0,0,0,0,0 }; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8933906882043608941L; + + public void init() { + super.init(); + } // iObjectKind, 18 must be Chart, or maybe Chart label ... + + private byte[] PROTOTYPE_BYTES = new byte[]{84, 8, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0}; + public static XLSRecord getPrototype() { - StartObject sb= new StartObject(); + StartObject sb = new StartObject(); sb.setOpcode(STARTOBJECT); sb.setData(sb.PROTOTYPE_BYTES); sb.init(); return sb; } + private void updateRecord() { } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/StockChart.java b/src/main/java/io/starter/formats/XLS/charts/StockChart.java index b8dad80..fce0f31 100644 --- a/src/main/java/io/starter/formats/XLS/charts/StockChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/StockChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,47 +23,53 @@ package io.starter.formats.XLS.charts; import io.starter.formats.XLS.WorkBook; + public class StockChart extends LineChart { - - public StockChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape= STOCKCHART; - } - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); + public StockChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + defaultShape = STOCKCHART; + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + // chart data labels, if any + //TODO: FINISH dlbls + //cooxml.append(getDataLabelsOOXML(cf)); - // chart data labels, if any - //TODO: FINISH dlbls - //cooxml.append(getDataLabelsOOXML(cf)); - - //dropLines - ChartLine cl= this.cf.getChartLinesRec(ChartLine.TYPE_DROPLINE); - if (cl!=null) - cooxml.append(cl.getOOXML()); - // hiLowLines - cl= this.cf.getChartLinesRec(ChartLine.TYPE_HILOWLINE); - if (cl!=null) - cooxml.append(cl.getOOXML()); - // upDownBars - cooxml.append(cf.getUpDownBarOOXML()); + //dropLines + ChartLine cl = this.cf.getChartLinesRec(ChartLine.TYPE_DROPLINE); + if (cl != null) + cooxml.append(cl.getOOXML()); + // hiLowLines + cl = this.cf.getChartLinesRec(ChartLine.TYPE_HILOWLINE); + if (cl != null) + cooxml.append(cl.getOOXML()); + // upDownBars + cooxml.append(cf.getUpDownBarOOXML()); - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); - return cooxml; - } + return cooxml; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Surface.java b/src/main/java/io/starter/formats/XLS/charts/Surface.java index 598d5ae..7155cde 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Surface.java +++ b/src/main/java/io/starter/formats/XLS/charts/Surface.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,37 +23,38 @@ package io.starter.formats.XLS.charts; +import io.starter.formats.XLS.XLSRecord; +import io.starter.toolkit.ByteTools; import org.json.JSONException; import org.json.JSONObject; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; /** * Surface: Chart Group is a Surface Chart Group (0x103f) - * + *

                * 4 grbit 2 - * - * 0 0x1 fFillSurface 1= chart contains color fill for surface - * 1 0x2 f3DPhongShade 1= this surface chart has shading + *

                + * 0 0x1 fFillSurface 1= chart contains color fill for surface + * 1 0x2 f3DPhongShade 1= this surface chart has shading */ public class Surface extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3243029185139320374L; - private short grbit = 0; - private boolean fFillSurface= true; - private boolean f3DPhoneShade= false; - private boolean is3d= false; /* since all surface charts contain a 3d record, must store 3d setting separately */ + /** + * serialVersionUID + */ + private static final long serialVersionUID = -3243029185139320374L; + private short grbit = 0; + private boolean fFillSurface = true; + private boolean f3DPhoneShade = false; + private boolean is3d = false; /* since all surface charts contain a 3d record, must store 3d setting separately */ + + public void init() { + super.init(); + grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + fFillSurface = (grbit & 0x1) == 0x1; + f3DPhoneShade = (grbit & 0x2) == 0x2; + chartType = ChartConstants.SURFACECHART; + } - public void init() { - super.init(); - grbit= ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fFillSurface= (grbit & 0x1) == 0x1; - f3DPhoneShade= (grbit & 0x2) == 0x2; - chartType= ChartConstants.SURFACECHART; - } - // 20070703 KSC: + // 20070703 KSC: private void updateRecord() { byte[] b = ByteTools.shortToLEBytes(grbit); this.getData()[0] = b[0]; @@ -67,82 +68,87 @@ public static XLSRecord getPrototype() { b.init(); return b; } - - private byte[] PROTOTYPE_BYTES = new byte[] {0, 0}; - + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; + /** * returns true if surface chart is wireframe, false if filled + * * @return */ public boolean isWireframe() { - return !fFillSurface; + return !fFillSurface; } - + /** - * sets this surface chart to wireframe (true) or filled(false) + * sets this surface chart to wireframe (true) or filled(false) */ public void setIsWireframe(boolean wireframe) { - fFillSurface= !wireframe; - grbit= ByteTools.updateGrBit(grbit, fFillSurface, 0); + fFillSurface = !wireframe; + grbit = ByteTools.updateGrBit(grbit, fFillSurface, 0); + } + + + /** + * surface charts always contain a 3d record so must determine if 3d separately + */ + public boolean getIs3d() { + return is3d; } - - - /** - * surface charts always contain a 3d record so must determine if 3d separately - */ - public boolean getIs3d() { - return is3d; - } - - /** - * set if this surface chart is "truly" 3d as all surface-type charts contain a 3d record - * @param is3d - */ - public void setIs3d(boolean is3d) { - this.is3d= is3d; - } - - /** + + /** + * set if this surface chart is "truly" 3d as all surface-type charts contain a 3d record + * + * @param is3d + */ + public void setIs3d(boolean is3d) { + this.is3d = is3d; + } + + /** * @return String XML representation of this chart-type's options */ public String getOptionsXML() { - StringBuffer sb= new StringBuffer(); - if (fFillSurface) - sb.append(" ColorFill=\"true\""); - if (f3DPhoneShade) - sb.append(" Shading=\"true\""); - return sb.toString(); + StringBuffer sb = new StringBuffer(); + if (fFillSurface) + sb.append(" ColorFill=\"true\""); + if (f3DPhoneShade) + sb.append(" Shading=\"true\""); + return sb.toString(); } + /** * Handle setting options from XML in a generic manner */ public boolean setChartOption(String op, String val) { - boolean bHandled= false; - if (op.equalsIgnoreCase("ColorFill")) { - fFillSurface= val.equals("true"); - grbit= ByteTools.updateGrBit(grbit, fFillSurface, 0); - bHandled= true; - } else if (op.equalsIgnoreCase("Shading")) { - f3DPhoneShade= val.equals("true"); - grbit= ByteTools.updateGrBit(grbit, f3DPhoneShade, 1); - bHandled= true; - } - if (bHandled) - updateRecord(); - return bHandled; + boolean bHandled = false; + if (op.equalsIgnoreCase("ColorFill")) { + fFillSurface = val.equals("true"); + grbit = ByteTools.updateGrBit(grbit, fFillSurface, 0); + bHandled = true; + } else if (op.equalsIgnoreCase("Shading")) { + f3DPhoneShade = val.equals("true"); + grbit = ByteTools.updateGrBit(grbit, f3DPhoneShade, 1); + bHandled = true; + } + if (bHandled) + updateRecord(); + return bHandled; + } + + /** + * return the (dojo) type JSON for this Chart Object + * + * @return + */ + public JSONObject getTypeJSON() throws JSONException { + JSONObject typeJSON = new JSONObject(); + String dojoType; + if (!this.isStacked()) + dojoType = "Areas"; + else + dojoType = "StackedAreas"; + typeJSON.put("type", dojoType); + return typeJSON; } - /** - * return the (dojo) type JSON for this Chart Object - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON= new JSONObject(); - String dojoType; - if (!this.isStacked()) - dojoType="Areas"; - else - dojoType="StackedAreas"; - typeJSON.put("type", dojoType); - return typeJSON; - } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Surface3DChart.java b/src/main/java/io/starter/formats/XLS/charts/Surface3DChart.java index 1cf4f26..9d4f0c6 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Surface3DChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/Surface3DChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,41 +22,45 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; - import io.starter.formats.XLS.WorkBook; public class Surface3DChart extends SurfaceChart { - public Surface3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - surface= (Surface) charttype; - } - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - // wireframe - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // bandfmts - - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - - cooxml.append(""); cooxml.append("\r\n"); - - return cooxml; - } - + public Surface3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + surface = (Surface) charttype; + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + // wireframe + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // bandfmts + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } + } diff --git a/src/main/java/io/starter/formats/XLS/charts/SurfaceChart.java b/src/main/java/io/starter/formats/XLS/charts/SurfaceChart.java index b11e740..6112f94 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SurfaceChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/SurfaceChart.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,42 +22,47 @@ */ package io.starter.formats.XLS.charts; -import java.util.ArrayList; - import io.starter.formats.XLS.WorkBook; public class SurfaceChart extends ChartType { - protected Surface surface= null; - public SurfaceChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - surface= (Surface) charttype; - } - - /** gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml= new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); cooxml.append("\r\n"); - // wireframe - if (surface.isWireframe()) - cooxml.append(""); - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // bandfmts - - // axis ids - unsigned int strings - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - cooxml.append(""); cooxml.append("\r\n"); - - cooxml.append(""); cooxml.append("\r\n"); - - return cooxml; - } - + protected Surface surface = null; + + public SurfaceChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { + super(charttype, cf, wb); + surface = (Surface) charttype; + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { + StringBuffer cooxml = new StringBuffer(); + + // chart type: contains chart options and series data + cooxml.append(""); + cooxml.append("\r\n"); + // wireframe + if (surface.isWireframe()) + cooxml.append(""); + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + + // bandfmts + + // axis ids - unsigned int strings + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + cooxml.append(""); + cooxml.append("\r\n"); + + cooxml.append(""); + cooxml.append("\r\n"); + + return cooxml; + } + } diff --git a/src/main/java/io/starter/formats/XLS/charts/SxViewLink.java b/src/main/java/io/starter/formats/XLS/charts/SxViewLink.java index 8ed2887..d04434b 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SxViewLink.java +++ b/src/main/java/io/starter/formats/XLS/charts/SxViewLink.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,37 +25,40 @@ import io.starter.formats.XLS.XLSRecord; /** - SXVIEWLINK: Chart PivotTable Name (858h) - Introduced in Excel 9 (2000), this BIFF record is an FRT record - for Charts. This record stores the name of the source PivotTable - when this chart is a PivotChart. New for Excel 9, PivotCharts are - charts based on PivotTables. - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0858h - 6 grbitFrt 2 FRT flags; must be zero - 8 brst var String containing name of PivotTable - -*/ + * SXVIEWLINK: Chart PivotTable Name (858h) + * Introduced in Excel 9 (2000), this BIFF record is an FRT record + * for Charts. This record stores the name of the source PivotTable + * when this chart is a PivotChart. New for Excel 9, PivotCharts are + * charts based on PivotTables. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0858h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 brst var String containing name of PivotTable + */ public class SxViewLink extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5291795207491688189L; - public void init() { - super.init(); - } - // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[] {}; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -5291795207491688189L; + + public void init() { + super.init(); + } + + // TODO: Prototype Bytes + private byte[] PROTOTYPE_BYTES = new byte[]{}; + public static XLSRecord getPrototype() { - SxViewLink sx= new SxViewLink(); - sx.setOpcode(SXVIEWLINK); - sx.setData(sx.PROTOTYPE_BYTES); - sx.init(); + SxViewLink sx = new SxViewLink(); + sx.setOpcode(SXVIEWLINK); + sx.setData(sx.PROTOTYPE_BYTES); + sx.init(); return sx; } + private void updateRecord() { } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/TextDisp.java b/src/main/java/io/starter/formats/XLS/charts/TextDisp.java index b865142..be5b975 100644 --- a/src/main/java/io/starter/formats/XLS/charts/TextDisp.java +++ b/src/main/java/io/starter/formats/XLS/charts/TextDisp.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,387 +22,387 @@ */ package io.starter.formats.XLS.charts; -import java.util.HashMap; - import io.starter.OpenXLS.FormatHandle; import io.starter.formats.XLS.BiffRec; import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.WorkBook; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; + +import java.util.HashMap; /** * TextDisp: (Text) Defines Display of Text Fields (0x1025) - * + *

                * Used in conjunction with several other records to define alignment, color, position, * size, and so on, of text fiedls that appear on the chart. * The fields in this record have meaning according to the TEXT record's parent * (CHART, LEGEND or DEFAULTTEXT) - * - * 4 at 1 Horizontal alignment (1=left, 2= center, 3=bottom, 4= justify, 7=distributed) - * 5 vat 1 Vertical alignment (1= top, 2= center, 3= bottom, 4= justify, 7=distributed) - * 6 wBkgMode 2 Display mode of bg 1= transparent, 2= opaque - * 8 rgbText 4 Color of the text - * LongRGB Structure: red (1 byte): An unsigned integer that specifies the relative intensity of red. - green (1 byte): An unsigned integer that specifies the relative intensity of green. - blue (1 byte): An unsigned integer that specifies the relative intensity of blue. - reserved (1 byte): MUST be zero, and MUST be ignored. - * 12 x 4 in SPRC (see Pos); ignore if preceded by DefaultText OR when followed by Pos - * 16 y 4 in SPRC (see Pos); "" - * 20 dx 4 A signed integer that specifies the horizontal size of the text, relative to the chart area in SPRC. - * This value MUST be ignored when this record is followed by a Pos record; - * otherwise MUST be greater than or equal to 0 and less than or equal to 32767. SHOULD be less than or equal to 4000. - * 24 dy 4 A signed integer that specifies the vertical size of the text, relative to the chart area in SPRC. - * This value MUST be ignored when this record is followed by a Pos record; - * otherwise MUST be greater than or equal to 0 and less than or equal to 32767. SHOULD<128> be less than or equal to 4000. - * 28 grbit 2 Option flags - * 30 icvText 2 Index to color value. icv structure. - * 32 dlp 2 (4 bits): An unsigned integer that specifies the data label positioning of the text, relative to the graph object item the text is attached to. - * For all data label text fields, MUST be a value from the following table: - Data label position Value Value for chart group type - Auto 0x0 Pie chart group - Right 0x0 Line, Bubble, or Scatter chart group - Outside 0x0 Bar or Column chart group with fStacked equal to 0 - Center 0x0 Bar or Column chart group with fStacked equal to 1 - Outside End 0x1 Bar, Column, or Pie chart group - Inside End 0x2 Bar, Column, or Pie chart group - Center 0x3 Bar, Column, Line, Bubble, Scatter, or Pie chart group - Inside Base 0x4 Bar or Column chart group - Above 0x5 Line, Bubble, or Scatter chart group - - Below 0x6 Line, Bubble, or Scatter chart group - Left 0x7 Line, Bubble, or Scatter chart group - Right 0x8 Line, Bubble, or Scatter chart group - Auto 0x9 Pie chart group - Moved by user 0xA All - unused 10 bits - iReadingOrder 2 bits - 34 trot An unsigned integer that specifies the text rotation. MUST be a value from the following table: - Value Angle description - 0 to 90 Text rotated 0 to 90 degrees counter-clockwise - 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) - 255 Text top-to-bottom with letters upright - * - * grbit - * 0 0x1 fAutoColor 1= auto color, 0= user-selected - * 1 0x2 fShowKey 1= the text is attached to a legend key. - * 2 0x4 fShowValue 1= the value, or the vertical value on bubble or scatter chart groups, is displayed in the data label. - * If the current attached label contains a DataLabExtContents record and the fPercent field of the DataLabExtContents record equals 0, this field MUST equal the fValue field of the DataLabExtContents record. - If the current attached label does not contain a DataLabExtContents record and fShowLabelAndPerc equals 1, this field MUST equal 0. - This field MUST equal 0 if the current attached label does not contain a DataLabExtContents record and one or more of the following conditions are satisfied: - The fShowLabelAndPerc field equals 1. - The fShowPercent field equals 1. - * 3 0x8 fVert unused - * 4 0x10 fAutoText 1= autogenerated text string - * 5 0x20 fGenerated 1= default 0= modified - * 6 0x40 zfDeleted 1= an automatic label has been deleted by user - * 7 0x80 fAutoMode 1= Bg is set to auto - * 10-8 0x700 unused - * 11 0x800 fShowLblPct 1= show category name and the value, represented as a percentage of the sum of the values of the series the data label is associated with, - MUST equal 0 if the chart group type of the corresponding chart group, series, or data point, is not a bar of pie, doughnut, pie, or pie of pie chart group. - This field MUST equal 1 if the current attached label contains a DataLabExtContents record and both of the following conditions are satisfied: - The fCatName and fPercent fields of the DataLabExtContents record equal 1. - The fSerName, fValue, and fBubSizes fields of the DataLabExtContents record equal 0. - This field MUST equal 0 if the current attached label contains a DataLabExtContents record and one or more of the following conditions is satisfied: - The fCatName or fPercent fields of the DataLabExtContents record equal 0. - The fSerName, fValue, or fBubSizes fields of the DataLabExtContents record equal 1. - MUST be ignored if fAutoText equals 0. - * 12 0x1000 fShowPct A bit that specifies whether the value, represented as a percentage of the sum of the values of the series the data label is associated with, is displayed in the data label. - MUST equal 0 if the chart group type of the corresponding chart group, series, or data point is not a bar of pie, doughnut, pie, or pie of pie chart group. - If the current attached label contains a DataLabExtContents record, this field MUST equal the value of the fPercent field of the DataLabExtContents record. - If the current attached label does not contain a DataLabExtContents record and fShowLabelAndPerc equals 1, this field MUST equal 1. - MUST be ignored if fAutoText equals 0. - * 13 0x2000 fShowBubbleSizes 1= show bubble sizes - * 14 0x4000 fShowCatLabel bit that specifies whether the category (3), or the horizontal value on bubble or scatter chart groups, is displayed in the data label on a non-area chart group, or the series name is displayed in the data label on an area chart group. - This field MUST equal the fCatNameLabel field of the DataLabExtContents record if the current attached label contains a DataLabExtContents record, the chart group is non-area, and both of the following conditions are satisfied: - The fValue field of the DataLabExtContents record equals 0. - The fShowLabelAndPerc field equals 1 or the fPercent field equals 0. - This field MUST equal the fCatNameLabel field of the DataLabExtContents record if the current attached label contains a DataLabExtContents record, the chart group is area or filled radar, and the following condition is satisfied: - The fValue field of the DataLabExtContents record equals 0. - If the current attached label contains a DataLabExtContents record and the fValue field of the DataLabExtContents record equals 1, this field MUST equal 0. - This field MUST equal 0 if the current attached label does not contain a DataLabExtContents record and one of the following conditions is satisfied: - The fShowValue field equals 1. - The fShowLabelAndPerc field equals 0 and the fShowPercent field equals 1. - MUST be ignored if fAutoText equals 0. - * + *

                + * 4 at 1 Horizontal alignment (1=left, 2= center, 3=bottom, 4= justify, 7=distributed) + * 5 vat 1 Vertical alignment (1= top, 2= center, 3= bottom, 4= justify, 7=distributed) + * 6 wBkgMode 2 Display mode of bg 1= transparent, 2= opaque + * 8 rgbText 4 Color of the text + * LongRGB Structure: red (1 byte): An unsigned integer that specifies the relative intensity of red. + * green (1 byte): An unsigned integer that specifies the relative intensity of green. + * blue (1 byte): An unsigned integer that specifies the relative intensity of blue. + * reserved (1 byte): MUST be zero, and MUST be ignored. + * 12 x 4 in SPRC (see Pos); ignore if preceded by DefaultText OR when followed by Pos + * 16 y 4 in SPRC (see Pos); "" + * 20 dx 4 A signed integer that specifies the horizontal size of the text, relative to the chart area in SPRC. + * This value MUST be ignored when this record is followed by a Pos record; + * otherwise MUST be greater than or equal to 0 and less than or equal to 32767. SHOULD be less than or equal to 4000. + * 24 dy 4 A signed integer that specifies the vertical size of the text, relative to the chart area in SPRC. + * This value MUST be ignored when this record is followed by a Pos record; + * otherwise MUST be greater than or equal to 0 and less than or equal to 32767. SHOULD<128> be less than or equal to 4000. + * 28 grbit 2 Option flags + * 30 icvText 2 Index to color value. icv structure. + * 32 dlp 2 (4 bits): An unsigned integer that specifies the data label positioning of the text, relative to the graph object item the text is attached to. + * For all data label text fields, MUST be a value from the following table: + * Data label position Value Value for chart group type + * Auto 0x0 Pie chart group + * Right 0x0 Line, Bubble, or Scatter chart group + * Outside 0x0 Bar or Column chart group with fStacked equal to 0 + * Center 0x0 Bar or Column chart group with fStacked equal to 1 + * Outside End 0x1 Bar, Column, or Pie chart group + * Inside End 0x2 Bar, Column, or Pie chart group + * Center 0x3 Bar, Column, Line, Bubble, Scatter, or Pie chart group + * Inside Base 0x4 Bar or Column chart group + * Above 0x5 Line, Bubble, or Scatter chart group + *

                + * Below 0x6 Line, Bubble, or Scatter chart group + * Left 0x7 Line, Bubble, or Scatter chart group + * Right 0x8 Line, Bubble, or Scatter chart group + * Auto 0x9 Pie chart group + * Moved by user 0xA All + * unused 10 bits + * iReadingOrder 2 bits + * 34 trot An unsigned integer that specifies the text rotation. MUST be a value from the following table: + * Value Angle description + * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise + * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) + * 255 Text top-to-bottom with letters upright + *

                + * grbit + * 0 0x1 fAutoColor 1= auto color, 0= user-selected + * 1 0x2 fShowKey 1= the text is attached to a legend key. + * 2 0x4 fShowValue 1= the value, or the vertical value on bubble or scatter chart groups, is displayed in the data label. + * If the current attached label contains a DataLabExtContents record and the fPercent field of the DataLabExtContents record equals 0, this field MUST equal the fValue field of the DataLabExtContents record. + * If the current attached label does not contain a DataLabExtContents record and fShowLabelAndPerc equals 1, this field MUST equal 0. + * This field MUST equal 0 if the current attached label does not contain a DataLabExtContents record and one or more of the following conditions are satisfied: + * The fShowLabelAndPerc field equals 1. + * The fShowPercent field equals 1. + * 3 0x8 fVert unused + * 4 0x10 fAutoText 1= autogenerated text string + * 5 0x20 fGenerated 1= default 0= modified + * 6 0x40 zfDeleted 1= an automatic label has been deleted by user + * 7 0x80 fAutoMode 1= Bg is set to auto + * 10-8 0x700 unused + * 11 0x800 fShowLblPct 1= show category name and the value, represented as a percentage of the sum of the values of the series the data label is associated with, + * MUST equal 0 if the chart group type of the corresponding chart group, series, or data point, is not a bar of pie, doughnut, pie, or pie of pie chart group. + * This field MUST equal 1 if the current attached label contains a DataLabExtContents record and both of the following conditions are satisfied: + * The fCatName and fPercent fields of the DataLabExtContents record equal 1. + * The fSerName, fValue, and fBubSizes fields of the DataLabExtContents record equal 0. + * This field MUST equal 0 if the current attached label contains a DataLabExtContents record and one or more of the following conditions is satisfied: + * The fCatName or fPercent fields of the DataLabExtContents record equal 0. + * The fSerName, fValue, or fBubSizes fields of the DataLabExtContents record equal 1. + * MUST be ignored if fAutoText equals 0. + * 12 0x1000 fShowPct A bit that specifies whether the value, represented as a percentage of the sum of the values of the series the data label is associated with, is displayed in the data label. + * MUST equal 0 if the chart group type of the corresponding chart group, series, or data point is not a bar of pie, doughnut, pie, or pie of pie chart group. + * If the current attached label contains a DataLabExtContents record, this field MUST equal the value of the fPercent field of the DataLabExtContents record. + * If the current attached label does not contain a DataLabExtContents record and fShowLabelAndPerc equals 1, this field MUST equal 1. + * MUST be ignored if fAutoText equals 0. + * 13 0x2000 fShowBubbleSizes 1= show bubble sizes + * 14 0x4000 fShowCatLabel bit that specifies whether the category (3), or the horizontal value on bubble or scatter chart groups, is displayed in the data label on a non-area chart group, or the series name is displayed in the data label on an area chart group. + * This field MUST equal the fCatNameLabel field of the DataLabExtContents record if the current attached label contains a DataLabExtContents record, the chart group is non-area, and both of the following conditions are satisfied: + * The fValue field of the DataLabExtContents record equals 0. + * The fShowLabelAndPerc field equals 1 or the fPercent field equals 0. + * This field MUST equal the fCatNameLabel field of the DataLabExtContents record if the current attached label contains a DataLabExtContents record, the chart group is area or filled radar, and the following condition is satisfied: + * The fValue field of the DataLabExtContents record equals 0. + * If the current attached label contains a DataLabExtContents record and the fValue field of the DataLabExtContents record equals 1, this field MUST equal 0. + * This field MUST equal 0 if the current attached label does not contain a DataLabExtContents record and one of the following conditions is satisfied: + * The fShowValue field equals 1. + * The fShowLabelAndPerc field equals 0 and the fShowPercent field equals 1. + * MUST be ignored if fAutoText equals 0. */ public class TextDisp extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7862828186455339066L; - private short grbit= 0, grbit2= 0; - private short at= 2, vat= 1, wBkgMode= 1, icvText= 0, trot= 0, dlp; - private int x= 0, y= 0, dx= 0, dy= 0; - private java.awt.Color rgbText= null; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7862828186455339066L; + private short grbit = 0, grbit2 = 0; + private short at = 2, vat = 1, wBkgMode = 1, icvText = 0, trot = 0, dlp; + private int x = 0, y = 0, dx = 0, dy = 0; + private java.awt.Color rgbText = null; private boolean fAutoColor, fShowKey, fShowValue, fVert, fAutoText, fGenerated, fDeleted, fAutoMode; - private boolean fShowLblPct, fShowPct, fShowBubbleSizes, fShowCatLabel; + private boolean fShowLblPct, fShowPct, fShowBubbleSizes, fShowCatLabel; + public void init() { - super.init(); - byte[] data= getData(); - at= data[0]; - vat= data[1]; - wBkgMode= ByteTools.readShort(data[2], data[3]); - rgbText= new java.awt.Color((data[4]<0?255+data[4]:data[4]), (data[5]<0?255+data[5]:data[5]), (data[6]<0?255+data[6]:data[6])); + super.init(); + byte[] data = getData(); + at = data[0]; + vat = data[1]; + wBkgMode = ByteTools.readShort(data[2], data[3]); + rgbText = new java.awt.Color((data[4] < 0 ? 255 + data[4] : data[4]), (data[5] < 0 ? 255 + data[5] : data[5]), (data[6] < 0 ? 255 + data[6] : data[6])); // rgbText= ByteTools.readInt(this.getBytesAt(4, 4)); - x= ByteTools.readInt(this.getBytesAt(8, 4)); - y= ByteTools.readInt(this.getBytesAt(12, 4)); - dx= ByteTools.readInt(this.getBytesAt(16, 4)); - dy= ByteTools.readInt(this.getBytesAt(20, 4)); - grbit= ByteTools.readShort(data[24], data[25]); - fAutoColor= (grbit & 0x1)==0x1; // usually T - fShowKey= (grbit & 0x2)==0x2; - fShowValue= (grbit & 0x4)==0x4; // pie-specific shows value label - fVert= (grbit & 0x8)==0x8; - fAutoText= (grbit & 0x10)==0x10; // usually T except for set labels - fGenerated= (grbit & 0x20)==0x20; // usually T - fDeleted= (grbit & 0x40)==0x40; - fAutoMode= (grbit & 0x80)==0x80; // usually T - fShowLblPct= (grbit & 0x800)==0x800; // pie-specific - fShowPct= (grbit & 0x1000)==0x1000; // pie-specific - fShowBubbleSizes= (grbit & 0x2000)==0x2000; // bubble-specific - fShowCatLabel= (grbit & 0x4000)==0x4000; // pie-specific shows category label - icvText= ByteTools.readShort(data[26], data[27]); - dlp= (short) (data[28] & 0xF); - grbit2= ByteTools.readShort(data[28], data[29]); - trot= ByteTools.readShort(data[30], data[31]); - } - + x = ByteTools.readInt(this.getBytesAt(8, 4)); + y = ByteTools.readInt(this.getBytesAt(12, 4)); + dx = ByteTools.readInt(this.getBytesAt(16, 4)); + dy = ByteTools.readInt(this.getBytesAt(20, 4)); + grbit = ByteTools.readShort(data[24], data[25]); + fAutoColor = (grbit & 0x1) == 0x1; // usually T + fShowKey = (grbit & 0x2) == 0x2; + fShowValue = (grbit & 0x4) == 0x4; // pie-specific shows value label + fVert = (grbit & 0x8) == 0x8; + fAutoText = (grbit & 0x10) == 0x10; // usually T except for set labels + fGenerated = (grbit & 0x20) == 0x20; // usually T + fDeleted = (grbit & 0x40) == 0x40; + fAutoMode = (grbit & 0x80) == 0x80; // usually T + fShowLblPct = (grbit & 0x800) == 0x800; // pie-specific + fShowPct = (grbit & 0x1000) == 0x1000; // pie-specific + fShowBubbleSizes = (grbit & 0x2000) == 0x2000; // bubble-specific + fShowCatLabel = (grbit & 0x4000) == 0x4000; // pie-specific shows category label + icvText = ByteTools.readShort(data[26], data[27]); + dlp = (short) (data[28] & 0xF); + grbit2 = ByteTools.readShort(data[28], data[29]); + trot = ByteTools.readShort(data[30], data[31]); + } + /** * @return the type of this TextDisp (X, Y, Z axis, title, data series ... */ public int getType() { - for (int i=0;i chartMetrics) { - StringBuffer svg= new StringBuffer(); - - float[] coords= this.getCoords(); - coords[0]= (int)Math.ceil(Pos.convertFromSPRC(coords[0], chartMetrics.get("canvasw").floatValue(), 0))-3; - coords[1]= (int)Math.ceil(Pos.convertFromSPRC(coords[1], 0, chartMetrics.get("canvash").floatValue())); - Font fx = this.getFont(wkbook); - - svg.append("\r\n"); - float fh=10; - if (fx!=null) fh= (float)fx.getFontHeightInPoints(); - float x= (float) (chartMetrics.get("x") + chartMetrics.get("w")/2); - float y= (float) (chartMetrics.get("TITLEOFFSET").floatValue() + fh)/2; - Frame f= (Frame) Chart.findRec(this.chartArr, Frame.class); - if (f!=null) { - coords[0]= coords[0]+x; - coords[1]= y+coords[1]+fh; - coords[3]= fh*2; // just a test really - svg.append(f.getSVG(coords)); - } else { - coords[0]= x; - coords[1]= y+fh/2; - } - - svg.append(""); - else - svg.append(" font-family='Arial' font-size='14pt' font-weight='bold'>"); - svg.append(this.toString() + "\r\n"); - svg.append("\r\n"); - return svg; + StringBuffer svg = new StringBuffer(); + + float[] coords = this.getCoords(); + coords[0] = (int) Math.ceil(Pos.convertFromSPRC(coords[0], chartMetrics.get("canvasw").floatValue(), 0)) - 3; + coords[1] = (int) Math.ceil(Pos.convertFromSPRC(coords[1], 0, chartMetrics.get("canvash").floatValue())); + Font fx = this.getFont(wkbook); + + svg.append("\r\n"); + float fh = 10; + if (fx != null) fh = (float) fx.getFontHeightInPoints(); + float x = (float) (chartMetrics.get("x") + chartMetrics.get("w") / 2); + float y = (chartMetrics.get("TITLEOFFSET").floatValue() + fh) / 2; + Frame f = (Frame) Chart.findRec(this.chartArr, Frame.class); + if (f != null) { + coords[0] = coords[0] + x; + coords[1] = y + coords[1] + fh; + coords[3] = fh * 2; // just a test really + svg.append(f.getSVG(coords)); + } else { + coords[0] = x; + coords[1] = y + fh / 2; + } + + svg.append(""); + else + svg.append(" font-family='Arial' font-size='14pt' font-weight='bold'>"); + svg.append(this.toString() + "\r\n"); + svg.append("\r\n"); + return svg; } - + /** * create a bg frame with the specified settings - * @param lw line width (-1 is none, 0= default ...) - * @param lclr line color - * @param bgcolor frame bg color - * @param coords frame coordinates + * + * @param lw line width (-1 is none, 0= default ...) + * @param lclr line color + * @param bgcolor frame bg color + * @param coords frame coordinates */ public void setFrame(int lw, int lclr, int bgcolor, float[] coords) { - Frame f= (Frame) Chart.findRec(this.chartArr, Frame.class); - if (f==null) { - f= (Frame) Frame.getPrototype(); - f.addBox(lw, lclr, bgcolor); - f.setParentChart(this.getParentChart()); - this.chartArr.add(f); - } + Frame f = (Frame) Chart.findRec(this.chartArr, Frame.class); + if (f == null) { + f = (Frame) Frame.getPrototype(); + f.addBox(lw, lclr, bgcolor); + f.setParentChart(this.getParentChart()); + this.chartArr.add(f); + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ThreeD.java b/src/main/java/io/starter/formats/XLS/charts/ThreeD.java index 42cd49c..8682b92 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ThreeD.java +++ b/src/main/java/io/starter/formats/XLS/charts/ThreeD.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,419 +22,419 @@ */ package io.starter.formats.XLS.charts; -import java.util.Stack; - -import org.json.JSONException; -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import io.starter.formats.OOXML.OOXMLElement; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Stack; /** * ThreeD(3D) Chart group is a 3-D Chart Group (0x103A) - * + *

                * anRot 2 Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for others -- def = 0 - * anElev 2 Elevation Angle (-90 to 90 degrees) (15 is default) 8 - * pcDist 2 Distance from eye to chart (0 to 100) (30 is default) 10 - * pcHeight 2 Height of plot volume relative to width and depth (100 is default) 12 - * pcDepth 2 Depth of points relative to width (100 is default) 14 + * anElev 2 Elevation Angle (-90 to 90 degrees) (15 is default) 8 + * pcDist 2 Distance from eye to chart (0 to 100) (30 is default) 10 + * pcHeight 2 Height of plot volume relative to width and depth (100 is default) 12 + * pcDepth 2 Depth of points relative to width (100 is default) 14 * pcGap 2 Space between points (150 is default - should be 50!!!) 16 grbit 2 - * - * grbit - * 0 0x1 fPerspective 1= use perspective transform - * 1 0x2 fCluster 1= 3-D columns are clustered or stacked - * 2 0x4 f3DScaling 1= use auto-scaling - * 3 reserved - * 4 0x8 fNotPieChart 1= NOT a pie chart + *

                + * grbit + * 0 0x1 fPerspective 1= use perspective transform + * 1 0x2 fCluster 1= 3-D columns are clustered or stacked + * 2 0x4 f3DScaling 1= use auto-scaling + * 3 reserved + * 4 0x8 fNotPieChart 1= NOT a pie chart * 5 0x10 f2DWalls use 2D walls and gridlines (if fPerspective MUST be ignored. if not of type BAR, AREA or - * PIE, ignore. if BAR and fCluster=0, ignore. specifies whether the walls are rendered in 2-D. + * PIE, ignore. if BAR and fCluster=0, ignore. specifies whether the walls are rendered in 2-D. * If fPerspective is 1 then this MUST be ignored. If the chart group type is not bar, area or pie this MUST be ignored. If the chart * group is of type bar and fCluster is 0, then this MUST be ignored. if PIE MUST be 0. - * - * "http://www.extentech.com">Extentech Inc. + *

                + * "http://www.extentech.com">Extentech Inc. */ public class ThreeD extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7501630910970731901L; - private short anRot = 0; - private short anElev = 15; - private short pcDist = 30; - private short pcHeight = 100; - private short pcDepth = 100; - private short pcGap = 150; - private short grbit = 0; // - private boolean fPerspective, fCluster, f3dScaling, f2DWalls; // 20070905 KSC: parse grbit + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7501630910970731901L; + private short anRot = 0; + private short anElev = 15; + private short pcDist = 30; + private short pcHeight = 100; + private short pcDepth = 100; + private short pcGap = 150; + private short grbit = 0; // + private boolean fPerspective, fCluster, f3dScaling, f2DWalls; // 20070905 KSC: parse grbit + + public void init() { + super.init(); + anRot = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + anElev = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); + pcDist = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + pcHeight = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); + pcDepth = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + pcGap = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); + grbit = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); + fPerspective = (grbit & 0x1) == 0x1; + fCluster = (grbit & 0x2) == 0x2; + f3dScaling = (grbit & 0x4) == 0x4; + f2DWalls = (grbit & 0x10) == 0x10; + } - public void init() { - super.init(); - anRot = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - anElev = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - pcDist = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - pcHeight = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - pcDepth = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - pcGap = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - grbit = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - fPerspective = (grbit & 0x1) == 0x1; - fCluster = (grbit & 0x2) == 0x2; - f3dScaling = (grbit & 0x4) == 0x4; - f2DWalls = (grbit & 0x10) == 0x10; - } + // 20070716 KSC: Need to create new records + public static XLSRecord getPrototype() { + ThreeD td = new ThreeD(); + td.setOpcode(THREED); + td.setData(td.PROTOTYPE_BYTES); + td.init(); + return td; + } - // 20070716 KSC: Need to create new records - public static XLSRecord getPrototype() { - ThreeD td = new ThreeD(); - td.setOpcode(THREED); - td.setData(td.PROTOTYPE_BYTES); - td.init(); - return td; - } + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 30, 0, 100, 0, + 100, 0, -106, 0, 0, 0}; - private byte[] PROTOTYPE_BYTES = new byte[] { 0, 0, 0, 0, 30, 0, 100, 0, - 100, 0, -106, 0, 0, 0 }; + private void updateRecord() { + byte[] b = ByteTools.shortToLEBytes(anRot); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + b = ByteTools.shortToLEBytes(anElev); + this.getData()[2] = b[0]; + this.getData()[3] = b[1]; + b = ByteTools.shortToLEBytes(pcDist); + this.getData()[4] = b[0]; + this.getData()[5] = b[1]; + b = ByteTools.shortToLEBytes(pcHeight); + this.getData()[6] = b[0]; + this.getData()[7] = b[1]; + b = ByteTools.shortToLEBytes(pcDepth); + this.getData()[8] = b[0]; + this.getData()[9] = b[1]; + b = ByteTools.shortToLEBytes(pcGap); + this.getData()[10] = b[0]; + this.getData()[11] = b[1]; + b = ByteTools.shortToLEBytes(grbit); + this.getData()[12] = b[0]; + this.getData()[13] = b[1]; + } - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(anRot); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - b = ByteTools.shortToLEBytes(anElev); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - b = ByteTools.shortToLEBytes(pcDist); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - b = ByteTools.shortToLEBytes(pcHeight); - this.getData()[6] = b[0]; - this.getData()[7] = b[1]; - b = ByteTools.shortToLEBytes(pcDepth); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; - b = ByteTools.shortToLEBytes(pcGap); - this.getData()[10] = b[0]; - this.getData()[11] = b[1]; - b = ByteTools.shortToLEBytes(grbit); - this.getData()[12] = b[0]; - this.getData()[13] = b[1]; - } + /** + * Handle setting options from XML in a generic manner + */ + public boolean setChartOption(String op, String val) { + boolean bHandled = false; + if (op.equalsIgnoreCase("AnRot")) { // specifies the clockwise rotation, in degrees, of the 3-D plot area around a vertical line through the center of the 3-D plot area. + anRot = Short.parseShort(val); // usually 20 + bHandled = true; + } + if (op.equalsIgnoreCase("AnElev")) { // signed integer that specifies the rotation, in degrees, of the 3-D plot area around a horizontal line through the center of the 3-D plot area + anElev = Short.parseShort(val); // usually 15 + bHandled = true; + } + if (op.equalsIgnoreCase("PcDist")) { // view angle for the 3-D plot area. + pcDist = Short.parseShort(val); // usually 30 + bHandled = true; + } + if (op.equalsIgnoreCase("PcHeight")) { // specifies the height of the 3-D plot area as a percentage of its width + pcHeight = Short.parseShort(val); + bHandled = true; + } + if (op.equalsIgnoreCase("PcDepth")) { // specifies the depth of the 3-D plot area as a percentage of its width. + pcDepth = Short.parseShort(val); // usually 100 + bHandled = true; + } + if (op.equalsIgnoreCase("PcGap")) { // specifies the width of the gap between the series and the front and back edges of the 3-D plot area as a percentage of the data point depth divided by 2. If + // fCluster is not set to 1 and chart group type is not a bar, this field also specifies the distance between adjacent series as a percentage of the data point depth. + pcGap = Short.parseShort(val); // usually 150 + bHandled = true; + } + /* + * 20070905 KSC: parse grbit options if + * (op.equalsIgnoreCase("FormatOptions")) { grbit= + * Short.parseShort(val); bHandled= true; } + */ + if (op.equalsIgnoreCase("Perspective")) { // specifies whether the 3-D plot area is rendered with a vanishing point. + fPerspective = Boolean.valueOf(val).booleanValue(); + grbit = ByteTools.updateGrBit(grbit, fPerspective, 0); + bHandled = true; + } + if (op.equalsIgnoreCase("Cluster")) { // specifies whether data points are clustered together in a bar chart group + fCluster = Boolean.valueOf(val).booleanValue(); + grbit = ByteTools.updateGrBit(grbit, fCluster, 1); + bHandled = true; + } + if (op.equalsIgnoreCase("ThreeDScaling")) { // specifies whether the height of the 3-D plot area is automatically determined + f3dScaling = Boolean.valueOf(val).booleanValue(); + grbit = ByteTools.updateGrBit(grbit, f3dScaling, 2); + bHandled = true; + } + if (op.equalsIgnoreCase("TwoDWalls")) { // A bit that specifies whether the chart walls are rendered in 2-D + f2DWalls = Boolean.valueOf(val).booleanValue(); + grbit = ByteTools.updateGrBit(grbit, f2DWalls, 4); + bHandled = true; + } + if (bHandled) + updateRecord(); + return bHandled; + } - /** - * Handle setting options from XML in a generic manner - * - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("AnRot")) { // specifies the clockwise rotation, in degrees, of the 3-D plot area around a vertical line through the center of the 3-D plot area. - anRot = Short.parseShort(val); // usually 20 - bHandled = true; - } - if (op.equalsIgnoreCase("AnElev")) { // signed integer that specifies the rotation, in degrees, of the 3-D plot area around a horizontal line through the center of the 3-D plot area - anElev = Short.parseShort(val); // usually 15 - bHandled = true; - } - if (op.equalsIgnoreCase("PcDist")) { // view angle for the 3-D plot area. - pcDist = Short.parseShort(val); // usually 30 - bHandled = true; - } - if (op.equalsIgnoreCase("PcHeight")) { // specifies the height of the 3-D plot area as a percentage of its width - pcHeight = Short.parseShort(val); - bHandled = true; - } - if (op.equalsIgnoreCase("PcDepth")) { // specifies the depth of the 3-D plot area as a percentage of its width. - pcDepth = Short.parseShort(val); // usually 100 - bHandled = true; - } - if (op.equalsIgnoreCase("PcGap")) { // specifies the width of the gap between the series and the front and back edges of the 3-D plot area as a percentage of the data point depth divided by 2. If - // fCluster is not set to 1 and chart group type is not a bar, this field also specifies the distance between adjacent series as a percentage of the data point depth. - pcGap = Short.parseShort(val); // usually 150 - bHandled = true; - } - /* - * 20070905 KSC: parse grbit options if - * (op.equalsIgnoreCase("FormatOptions")) { grbit= - * Short.parseShort(val); bHandled= true; } - */ - if (op.equalsIgnoreCase("Perspective")) { // specifies whether the 3-D plot area is rendered with a vanishing point. - fPerspective = Boolean.valueOf(val).booleanValue(); - grbit = ByteTools.updateGrBit(grbit, fPerspective, 0); - bHandled = true; - } - if (op.equalsIgnoreCase("Cluster")) { // specifies whether data points are clustered together in a bar chart group - fCluster = Boolean.valueOf(val).booleanValue(); - grbit = ByteTools.updateGrBit(grbit, fCluster, 1); - bHandled = true; - } - if (op.equalsIgnoreCase("ThreeDScaling")) { // specifies whether the height of the 3-D plot area is automatically determined - f3dScaling = Boolean.valueOf(val).booleanValue(); - grbit = ByteTools.updateGrBit(grbit, f3dScaling, 2); - bHandled = true; - } - if (op.equalsIgnoreCase("TwoDWalls")) { // A bit that specifies whether the chart walls are rendered in 2-D - f2DWalls = Boolean.valueOf(val).booleanValue(); - grbit = ByteTools.updateGrBit(grbit, f2DWalls, 4); - bHandled = true; - } - if (bHandled) - updateRecord(); - return bHandled; - } + /** + * return the desired option setting in string form + */ + public String getChartOption(String op) { + if (op.equalsIgnoreCase("AnRot")) { + return String.valueOf(anRot); + } + if (op.equalsIgnoreCase("AnElev")) { + return String.valueOf(anElev); + } + if (op.equalsIgnoreCase("PcDist")) { + return String.valueOf(pcDist); + } + if (op.equalsIgnoreCase("PcHeight")) { + return String.valueOf(pcHeight); + } + if (op.equalsIgnoreCase("PcDepth")) { + return String.valueOf(pcDepth); + } + if (op.equalsIgnoreCase("PcGap")) { + return String.valueOf(pcGap); + } + if (op.equalsIgnoreCase("Perspective")) { + return ((fPerspective) ? "1" : "0"); + } + if (op.equalsIgnoreCase("Cluster")) { + return ((fCluster) ? "1" : "0"); + } + if (op.equalsIgnoreCase("ThreeDScaling")) { + return ((f3dScaling) ? "1" : "0"); + } + if (op.equalsIgnoreCase("TwoDWalls")) { + return ((f2DWalls) ? "1" : "0"); + } + return ""; + } - /** - * return the desired option setting in string form - */ - public String getChartOption(String op) { - if (op.equalsIgnoreCase("AnRot")) { - return String.valueOf(anRot); - } - if (op.equalsIgnoreCase("AnElev")) { - return String.valueOf(anElev); - } - if (op.equalsIgnoreCase("PcDist")) { - return String.valueOf(pcDist); - } - if (op.equalsIgnoreCase("PcHeight")) { - return String.valueOf(pcHeight); - } - if (op.equalsIgnoreCase("PcDepth")) { - return String.valueOf(pcDepth); - } - if (op.equalsIgnoreCase("PcGap")) { - return String.valueOf(pcGap); - } - if (op.equalsIgnoreCase("Perspective")) { - return ((fPerspective) ? "1" : "0"); - } - if (op.equalsIgnoreCase("Cluster")) { - return ((fCluster) ? "1" : "0"); - } - if (op.equalsIgnoreCase("ThreeDScaling")) { - return ((f3dScaling) ? "1" : "0"); - } - if (op.equalsIgnoreCase("TwoDWalls")) { - return ((f2DWalls) ? "1" : "0"); - } - return ""; - } + /** + * @return String XML representation of this chart-type's options + */ + public String getOptionsXML() { + StringBuffer sb = new StringBuffer(); + if (anRot != 0) + sb.append(" AnRot=\"" + anRot + "\""); + if (anElev != 15) + sb.append(" AnElev=\"" + anElev + "\""); + if (pcDist != 30) + sb.append(" pcDist=\"" + pcDist + "\""); + if (pcHeight != 100) + sb.append(" pcHeight=\"" + pcHeight + "\""); + if (pcDepth != 100) + sb.append(" pcDepth=\"" + pcDepth + "\""); + if (pcGap != 150) + sb.append(" pcGap=\"" + pcGap + "\""); + if (fPerspective) + sb.append(" Perspective=\"true\""); + if (f3dScaling) + sb.append(" ThreeDScaling=\"true\""); + if (f2DWalls) + sb.append(" TwoDWalls=\"true\""); + // 20070913 KSC: need to track Cluster, whether on or off + sb.append(" Cluster=\"" + fCluster + "\""); + // sb.append(" formatOptions=\"" + grbit+ "\""); + return sb.toString(); + } - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (anRot != 0) - sb.append(" AnRot=\"" + anRot + "\""); - if (anElev != 15) - sb.append(" AnElev=\"" + anElev + "\""); - if (pcDist != 30) - sb.append(" pcDist=\"" + pcDist + "\""); - if (pcHeight != 100) - sb.append(" pcHeight=\"" + pcHeight + "\""); - if (pcDepth != 100) - sb.append(" pcDepth=\"" + pcDepth + "\""); - if (pcGap != 150) - sb.append(" pcGap=\"" + pcGap + "\""); - if (fPerspective) - sb.append(" Perspective=\"true\""); - if (f3dScaling) - sb.append(" ThreeDScaling=\"true\""); - if (f2DWalls) - sb.append(" TwoDWalls=\"true\""); - // 20070913 KSC: need to track Cluster, whether on or off - sb.append(" Cluster=\"" + fCluster + "\""); - // sb.append(" formatOptions=\"" + grbit+ "\""); - return sb.toString(); - } + /** + * @return truth of "Chart is Clustered" + */ + public boolean isClustered() { + return fCluster; + } - /** - * @return truth of "Chart is Clustered" - */ - public boolean isClustered() { - return fCluster; - } - - /** - * sets if this chart has clustered bar/columns - * @param bIsClustered - */ - public void setIsClustered(boolean bIsClustered) { - fCluster = bIsClustered; - grbit = ByteTools.updateGrBit(grbit, fCluster, 1); - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[12] = b[0]; - this.getData()[13] = b[1]; - } + /** + * sets if this chart has clustered bar/columns + * + * @param bIsClustered + */ + public void setIsClustered(boolean bIsClustered) { + fCluster = bIsClustered; + grbit = ByteTools.updateGrBit(grbit, fCluster, 1); + byte[] b = ByteTools.shortToLEBytes(grbit); + this.getData()[12] = b[0]; + this.getData()[13] = b[1]; + } - /** - * sets the proper bit for chart type (PIE or not) - * - * @param isPieChart true if pie-type chart - */ - public void setIsPie(boolean isPieChart) { - if (isPieChart) - grbit &= 0x8; // bit is true if "not a pie" - else - grbit |= 0x17; - updateRecord(); - } + /** + * sets the proper bit for chart type (PIE or not) + * + * @param isPieChart true if pie-type chart + */ + public void setIsPie(boolean isPieChart) { + if (isPieChart) + grbit &= 0x8; // bit is true if "not a pie" + else + grbit |= 0x17; + updateRecord(); + } - /** - * sets the Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for - * others - * - * @param rot - */ - public void setAnRot(int rot) { - anRot = (short) rot; - byte[] b = ByteTools.shortToLEBytes(anRot); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } + /** + * sets the Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for + * others + * + * @param rot + */ + public void setAnRot(int rot) { + anRot = (short) rot; + byte[] b = ByteTools.shortToLEBytes(anRot); + this.getData()[0] = b[0]; + this.getData()[1] = b[1]; + } - /** - * sets the Elevation Angle (-90 to 90 degrees) (15 is default) - * - * @param elev - */ - public void setAnElev(int elev) { - anElev = (short) elev; - byte[] b = ByteTools.shortToLEBytes(anElev); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - } + /** + * sets the Elevation Angle (-90 to 90 degrees) (15 is default) + * + * @param elev + */ + public void setAnElev(int elev) { + anElev = (short) elev; + byte[] b = ByteTools.shortToLEBytes(anElev); + this.getData()[2] = b[0]; + this.getData()[3] = b[1]; + } - /** - * sets the Distance from eye to chart (0 to 100) (30 is default) - * - * @param elev - */ - public void setPcDist(int dist) { - pcDist = (short) dist; - byte[] b = ByteTools.shortToLEBytes(pcDist); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - } + /** + * sets the Distance from eye to chart (0 to 100) (30 is default) + * + * @param elev + */ + public void setPcDist(int dist) { + pcDist = (short) dist; + byte[] b = ByteTools.shortToLEBytes(pcDist); + this.getData()[4] = b[0]; + this.getData()[5] = b[1]; + } - /** - * sets the Height of plot volume relative to width and depth (100 is - * default) - * - * @param elev - */ - public void setPcHeight(int dist) { - pcHeight = (short) dist; - byte[] b = ByteTools.shortToLEBytes(pcHeight); - this.getData()[6] = b[0]; - this.getData()[7] = b[1]; - } + /** + * sets the Height of plot volume relative to width and depth (100 is + * default) + * + * @param elev + */ + public void setPcHeight(int dist) { + pcHeight = (short) dist; + byte[] b = ByteTools.shortToLEBytes(pcHeight); + this.getData()[6] = b[0]; + this.getData()[7] = b[1]; + } - /** - * sets the Depth of points relative to width (100 is default) - * - * @param elev - */ - public void setPcDepth(int depth) { - pcDepth = (short) depth; - byte[] b = ByteTools.shortToLEBytes(pcDepth); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; - } + /** + * sets the Depth of points relative to width (100 is default) + * + * @param elev + */ + public void setPcDepth(int depth) { + pcDepth = (short) depth; + byte[] b = ByteTools.shortToLEBytes(pcDepth); + this.getData()[8] = b[0]; + this.getData()[9] = b[1]; + } - /** - * sets the Space between points (50 or 150 is default) - * @param gap - */ - public void setPcGap(int gap) { - pcGap = (short) gap; - byte[] b = ByteTools.shortToLEBytes(pcGap); - this.getData()[10] = b[0]; - this.getData()[11] = b[1]; - } + /** + * sets the Space between points (50 or 150 is default) + * + * @param gap + */ + public void setPcGap(int gap) { + pcGap = (short) gap; + byte[] b = ByteTools.shortToLEBytes(pcGap); + this.getData()[10] = b[0]; + this.getData()[11] = b[1]; + } - public int getPcGap() { return pcGap; } - /** - * return view3d OOXML representation - * - * @return - */ - public StringBuffer getOOXML() { - StringBuffer cooxml = new StringBuffer(); - cooxml.append(""); - cooxml.append("\r\n"); - // rotX == anElev - if (anElev != 0) // default - cooxml.append(""); - // hPercent -- a height percent between 5 and 500. - // rotY == anRot - if (anRot != 0 || anElev!=0) // default - cooxml.append(""); - // depthPercentage -- This element specifies the depth of a 3-D chart as a percentage of the chart width (between 20 and 2000 percent). - if (pcDepth!=100) - cooxml.append(""); - // rAngAx == !fPerspective - if (fPerspective) - cooxml.append(""); - // perspective == pcDist - if (pcDist != 30) // default - cooxml.append(""); - cooxml.append(""); - cooxml.append("\r\n"); - return cooxml; + public int getPcGap() { + return pcGap; + } - } + /** + * return view3d OOXML representation + * + * @return + */ + public StringBuffer getOOXML() { + StringBuffer cooxml = new StringBuffer(); + cooxml.append(""); + cooxml.append("\r\n"); + // rotX == anElev + if (anElev != 0) // default + cooxml.append(""); + // hPercent -- a height percent between 5 and 500. + // rotY == anRot + if (anRot != 0 || anElev != 0) // default + cooxml.append(""); + // depthPercentage -- This element specifies the depth of a 3-D chart as a percentage of the chart width (between 20 and 2000 percent). + if (pcDepth != 100) + cooxml.append(""); + // rAngAx == !fPerspective + if (fPerspective) + cooxml.append(""); + // perspective == pcDist + if (pcDist != 30) // default + cooxml.append(""); + cooxml.append(""); + cooxml.append("\r\n"); + return cooxml; - /** - * parse shape OOXML element view3D into a ThreeD record - * - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spPr object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, - OOXMLChart cht) { - // threeD MUST NOT EXIST in a bar of pie, bubble, doughnut, filled - // radar, pie of pie, radar, or scatter chart group. - ThreeD td = cht.getChartObject().getThreeDRec(true); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - String v = null; - try { - v = xpp.getAttributeValue(0); - } catch (/* XmlPullParser */Exception e) { - } - if (v!=null) { - if (tnm.equals("rotX")) { - td.setAnElev(Integer.valueOf(v)); - } else if (tnm.equals("rotY")) { - td.setAnRot(Integer.valueOf(v)); - } else if (tnm.equals("perspective")) { - td.setPcDist(Integer.valueOf(v)); - } else if (tnm.equals("depthPercent")) { - td.setPcDepth(Integer.valueOf(v)); - } else if (tnm.equals("rAngAx")) { + } + + /** + * parse shape OOXML element view3D into a ThreeD record + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object + */ + public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, + OOXMLChart cht) { + // threeD MUST NOT EXIST in a bar of pie, bubble, doughnut, filled + // radar, pie of pie, radar, or scatter chart group. + ThreeD td = cht.getChartObject().getThreeDRec(true); + try { + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + String tnm = xpp.getName(); + String v = null; + try { + v = xpp.getAttributeValue(0); + } catch (/* XmlPullParser */Exception e) { + } + if (v != null) { + if (tnm.equals("rotX")) { + td.setAnElev(Integer.valueOf(v)); + } else if (tnm.equals("rotY")) { + td.setAnRot(Integer.valueOf(v)); + } else if (tnm.equals("perspective")) { + td.setPcDist(Integer.valueOf(v)); + } else if (tnm.equals("depthPercent")) { + td.setPcDepth(Integer.valueOf(v)); + } else if (tnm.equals("rAngAx")) { // if (v!=null) - - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("view3D")) { - lastTag.pop(); // pop layout tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ThreeD.parseOOXML: " + e.toString()); - } - return null; - } + } + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTag = xpp.getName(); + if (endTag.equals("view3D")) { + lastTag.pop(); // pop layout tag + break; + } + } + eventType = xpp.next(); + } + } catch (Exception e) { + Logger.logErr("ThreeD.parseOOXML: " + e.toString()); + } + + return null; + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Tick.java b/src/main/java/io/starter/formats/XLS/charts/Tick.java index 28d5fa7..65ff57e 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Tick.java +++ b/src/main/java/io/starter/formats/XLS/charts/Tick.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -28,184 +28,190 @@ /** * Tick: Tick Marks and Labels Format (0x101e) - * + *

                * Offset Name Size Contents - * 4 tktMajor 1 Type of major tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line - * 5 tktMinor 1 Type of minor tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line - * 6 tit 1 Tick label position relative to axis line 0= invisible (none) 1 = low end of plot area 2 = high end of plot area 3 = next to axis - * 7 wBkgMode 2 Background mode: I = transparent 2 = opaque - * 8 rgb 4 Tick-label text color; ROB value, high byte = 0 - 12 (reserved) 16 Reserved; must be zero - 28 grbit 2 Display flags - 30 icv 2 Index to color of tick label - 32 (reserved) 2 Reserved; must be zero + * 4 tktMajor 1 Type of major tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line + * 5 tktMinor 1 Type of minor tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line + * 6 tit 1 Tick label position relative to axis line 0= invisible (none) 1 = low end of plot area 2 = high end of plot area 3 = next to axis + * 7 wBkgMode 2 Background mode: I = transparent 2 = opaque + * 8 rgb 4 Tick-label text color; ROB value, high byte = 0 + * 12 (reserved) 16 Reserved; must be zero + * 28 grbit 2 Display flags + * 30 icv 2 Index to color of tick label + * 32 (reserved) 2 Reserved; must be zero + *

                + * The grbit field contains the following option flags. + *

                + * Bits Mask Name + * 0 0xOl fAutoColor Automatic text color + * 1 0x02 fAutoMode Automatic text back~ + * 4-2 0xlC rot 0= no rotation (text appears left-to-right), 1= text appears top-~~ are upright, + * 2= text is rotated 90 degrees counterclockwise, 3= text is rotated + * 5 0x20 fAutoRot Automatic rotation + * 7-6 0xCO (reserved) Reserved; must be zero + * 7-0 0xFF (reserved) Reserved; must be zero + */ +public class Tick extends GenericChartObject implements ChartObject { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 3363212452589555220L; + byte tktMajor, tktMinor, tit; + short grbit; + short rot; -The grbit field contains the following option flags. + public void init() { + super.init(); + tktMajor = this.getByteAt(0); + tktMinor = this.getByteAt(1); + tit = this.getByteAt(2); + grbit = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); + // TODO: Finish ops + rot = (short) ((grbit & 0x1C) >> 2); + } - Bits Mask Name - 0 0xOl fAutoColor Automatic text color - 1 0x02 fAutoMode Automatic text back~ - 4-2 0xlC rot 0= no rotation (text appears left-to-right), 1= text appears top-~~ are upright, - 2= text is rotated 90 degrees counterclockwise, 3= text is rotated - 5 0x20 fAutoRot Automatic rotation - 7-6 0xCO (reserved) Reserved; must be zero - 7-0 0xFF (reserved) Reserved; must be zero - * - */ -public class Tick extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3363212452589555220L; - byte tktMajor, tktMinor, tit; - short grbit; - short rot; - - public void init() { - super.init(); - tktMajor= this.getByteAt(0); - tktMinor= this.getByteAt(1); - tit= this.getByteAt(2); - grbit= ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); - // TODO: Finish ops - rot= (short) ((grbit & 0x1C) >> 2); - } - - // 20070723 KSC: Need to create new records + // 20070723 KSC: Need to create new records public static XLSRecord getPrototype() { Tick t = new Tick(); t.setOpcode(TICK); t.setData(t.PROTOTYPE_BYTES); - t.init(); // important when we parse options ... + t.init(); // important when we parse options ... return t; - } - private byte[] PROTOTYPE_BYTES = new byte[] {2, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 77, 0, 0, 0}; - + } + + private byte[] PROTOTYPE_BYTES = new byte[]{2, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 77, 0, 0, 0}; + private void updateRecord() { - this.getData() [0]= tktMajor; - this.getData() [1]= tktMinor; - this.getData() [2]= tit; + this.getData()[0] = tktMajor; + this.getData()[1] = tktMinor; + this.getData()[2] = tit; byte[] b = ByteTools.shortToLEBytes(grbit); this.getData()[24] = b[0]; - this.getData()[25] = b[1]; + this.getData()[25] = b[1]; } - + /** * set generic Tick option *
                op/val can be one of: *
                tickLblPos none, low, high or nextTo *
                majorTickMark none, in, out, cross *
                minorTickMark none, in, out, cross + * * @param op * @param val */ public void setOption(String op, String val) { - if (op.equals("tickLblPos")) { // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick - if (val.equals("high")) - tit= 2; - else if (val.equals("low")) - tit= 1; - else if (val.equals("none")) - tit= 0; - else if (val.equals("nextTo")) - tit= 3; - } else if (op.equals("majorTickMark")) { // major tick marks (cross, in, none, out) - if (val.equals("cross")) - tktMajor= 3; - else if (val.equals("in")) - tktMajor= 1; - else if (val.equals("out")) - tktMajor= 2; - else if (val.equals("none")) - tktMajor= 0; - } else if (op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) - if (val.equals("cross")) - tktMinor= 3; - else if (val.equals("in")) - tktMinor= 1; - else if (val.equals("out")) - tktMinor= 2; - else if (val.equals("none")) - tktMinor= 0; - } - updateRecord(); + if (op.equals("tickLblPos")) { // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick + if (val.equals("high")) + tit = 2; + else if (val.equals("low")) + tit = 1; + else if (val.equals("none")) + tit = 0; + else if (val.equals("nextTo")) + tit = 3; + } else if (op.equals("majorTickMark")) { // major tick marks (cross, in, none, out) + if (val.equals("cross")) + tktMajor = 3; + else if (val.equals("in")) + tktMajor = 1; + else if (val.equals("out")) + tktMajor = 2; + else if (val.equals("none")) + tktMajor = 0; + } else if (op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) + if (val.equals("cross")) + tktMinor = 3; + else if (val.equals("in")) + tktMinor = 1; + else if (val.equals("out")) + tktMinor = 2; + else if (val.equals("none")) + tktMinor = 0; + } + updateRecord(); } - /* 4 tktMajor 1 Type of major tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line - * 5 tktMinor 1 Type of minor tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line - * 6 tit 1 Tick label position relative to axis line 0= invisible (none) 1 = low end of plot area 2 = high end of plot area 3 = next to axis - */ + /* 4 tktMajor 1 Type of major tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line + * 5 tktMinor 1 Type of minor tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line + * 6 tit 1 Tick label position relative to axis line 0= invisible (none) 1 = low end of plot area 2 = high end of plot area 3 = next to axis + */ + /** * retrieve generic Value axis option as OOXML string *
                can be one of: *
                tickLblPos none, low, high or nextTo *
                majorTickMark none, in, out, cross *
                minorTickMark none, in, out, cross + * * @param op * @return */ public String getOption(String op) { - if (op.equals("tickLblPos")) { // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick - switch (tit) { - case 0: - return "none"; - case 1: - return "low"; - case 2: - return "high"; - case 3: - return "nextTo"; - } - } else if (op.equals("majorTickMark")) {// major tick marks (cross, in, none, out) - switch (tktMajor) { - case 0: - return "none"; - case 1: - return "in"; - case 2: - return "out"; - case 3: - return "cross"; - } - } else if (op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) - switch (tktMinor) { - case 0: - return "none"; - case 1: - return "in"; - case 2: - return "out"; - case 3: - return "cross"; - } - } - return null; + if (op.equals("tickLblPos")) { // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick + switch (tit) { + case 0: + return "none"; + case 1: + return "low"; + case 2: + return "high"; + case 3: + return "nextTo"; + } + } else if (op.equals("majorTickMark")) {// major tick marks (cross, in, none, out) + switch (tktMajor) { + case 0: + return "none"; + case 1: + return "in"; + case 2: + return "out"; + case 3: + return "cross"; + } + } else if (op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) + switch (tktMinor) { + case 0: + return "none"; + case 1: + return "in"; + case 2: + return "out"; + case 3: + return "cross"; + } + } + return null; } - + /** * returns true if should show minor tick marks + * * @return */ public boolean showMinorTicks() { - return tktMinor!=0; + return tktMinor != 0; } - + /** * returns true if should show major tick marks + * * @return */ public boolean showMajorTicks() { - return tktMajor!=0; + return tktMajor != 0; } - + /** - 0= no rotation (text appears left-to-right), - 1= Text is drawn stacked, top-to-bottom, with the letters upright. - 2= text is rotated 90 degrees counterclockwise, - 3= text is rotated at 90 degrees clockwise. + * 0= no rotation (text appears left-to-right), + * 1= Text is drawn stacked, top-to-bottom, with the letters upright. + * 2= text is rotated 90 degrees counterclockwise, + * 3= text is rotated at 90 degrees clockwise. + * * @return */ public short getRotation() { - return rot; + return rot; } - + } diff --git a/src/main/java/io/starter/formats/XLS/charts/Units.java b/src/main/java/io/starter/formats/XLS/charts/Units.java index 73c662f..6ccdd86 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Units.java +++ b/src/main/java/io/starter/formats/XLS/charts/Units.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,18 +22,16 @@ */ package io.starter.formats.XLS.charts; -import io.starter.formats.XLS.XLSRecord; /** * Units: Chart Units (0x1001) - * */ public class Units extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8391773940364481970L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8391773940364481970L; - public void init() { - super.init(); - } + public void init() { + super.init(); + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ValueRange.java b/src/main/java/io/starter/formats/XLS/charts/ValueRange.java index 590f5bc..cd27a14 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ValueRange.java +++ b/src/main/java/io/starter/formats/XLS/charts/ValueRange.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,175 +22,185 @@ */ package io.starter.formats.XLS.charts; -import io.starter.OpenXLS.ChartHandle; import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; /** * ValueRange: Defines Value Axis Scale (0x101f) - Offset Name Size Contents - 4 numMin 8 Minimum value on axis. MUST be less than the value of numMax. If the value of fAutoMin is 1, this field MUST be ignored. - 12 numMax 8 Maximum value on axis. MUST be greater than the value of numMin. If the value of fAutoMax is 1, this field MUST be ignored. - 20 numMajor 8 Value of major increment. MUST be greater than or equal to the value of numMinor. If the value of fAutoMajor is 1, this field MUST be ignored. - 28 numMinor 8 Value of minor increment. MUST be greater than or equal to zero. If the value of fAutoMinor is 1, this field MUST be ignored. - 36 numCross 8 Value where category axis crosses. If the value of fAutoCross is 1, this field MUST be ignored. - 44 grbit 2 Format flags - - - grbit - 0 0x1 fAutoMin Automatic Minimum Selected - 0 The value specified by numMin is used as the minimum value of the value axis. - 1 numMin is calculated such that the data point with the minimum value can be displayed in the plot area. - 1 0x2 fAutoMax Automatic Maximum Selected - 0 The value specified by numMax is used as the maximum value of the value axis. - 1 numMax is calculated such that the data point with the maximum value can be displayed in the plot area. - 2 0x4 fAutoMajor Automatic Major Unit Selected - 0 The value specified by numMax is used as the maximum value of the value axis. - 1 numMax is calculated such that the data point with the maximum value can be displayed in the plot area. - 3 0x8 fAutoMinor Automatic Minor Unit Selected - 0 The value specified by numMinor is used as the interval at which minor tick marks and minor gridlines are displayed. - 1 numMinor is calculated automatically. - 4 0x10 fAutoCross Automatic Category Crossing Point Selected - 0 The value specified by numCross is used as the point at which the other axes in the axis group cross this value axis. - 1 numCross is calculated so that the crossing point is displayed in the plot area. - 5 0x20 fLogScale Log Scale - 0 The scale of the value axis is linear. - 1 The scale of the value axis is logarithmic. The default base of the logarithmic scale is 10, unless a CrtMlFrt record follows this record, specifying the base in a XmlTkLogBaseFrt structure. - 6 0x40 fReverse Values in reverse order - 0 Values are displayed from smallest-to-largest, from left-to-right, or from bottom-to-top, respectively, depending on the orientation of the axis. - 1 The values are displayed in reverse order, meaning largest-to-smallest, from left-to-right, or from bottom-to-top, respectively. - 7 0x80 fMaxCross Category is to cross at maximum value - 0 The other axes in the axis group cross this value axis at the value specified by numCross. - 1 The other axes in the axis group cross the value axis at the maximum value. If fMaxCross is 1, then both fAutoCross and numCross MUST be ignored. - -All 8-byte numbers in the preceding table are IEEE floating-point numbers. -The numMin field defines the minimum numeric value that appears along the value axis. This field is all zeros if Auto Minimum is selected on the Scale tab of the Format Axis dialog box. -The numMax field defines the maximum value displayed along the value axis and is all zeros if Auto Maximum is selected. -The numMajor field defines the increment (unit) of the major value divisions (gridlines) along the value axis. The numMajor field is all zeros if Auto Major Unit is selected on the Scale tab of the Format Axis dialog box. -The numMinor field defines the minor value divisions (gridlines) along the value axis and is all zeros if Auto Minor Unit is selected. -The numCross field defines the value along the value axis at which the category axis crosses. This field is all zeros if Auto Category Axis Crosses At is selected. - + * Offset Name Size Contents + * 4 numMin 8 Minimum value on axis. MUST be less than the value of numMax. If the value of fAutoMin is 1, this field MUST be ignored. + * 12 numMax 8 Maximum value on axis. MUST be greater than the value of numMin. If the value of fAutoMax is 1, this field MUST be ignored. + * 20 numMajor 8 Value of major increment. MUST be greater than or equal to the value of numMinor. If the value of fAutoMajor is 1, this field MUST be ignored. + * 28 numMinor 8 Value of minor increment. MUST be greater than or equal to zero. If the value of fAutoMinor is 1, this field MUST be ignored. + * 36 numCross 8 Value where category axis crosses. If the value of fAutoCross is 1, this field MUST be ignored. + * 44 grbit 2 Format flags + *

                + *

                + * grbit + * 0 0x1 fAutoMin Automatic Minimum Selected + * 0 The value specified by numMin is used as the minimum value of the value axis. + * 1 numMin is calculated such that the data point with the minimum value can be displayed in the plot area. + * 1 0x2 fAutoMax Automatic Maximum Selected + * 0 The value specified by numMax is used as the maximum value of the value axis. + * 1 numMax is calculated such that the data point with the maximum value can be displayed in the plot area. + * 2 0x4 fAutoMajor Automatic Major Unit Selected + * 0 The value specified by numMax is used as the maximum value of the value axis. + * 1 numMax is calculated such that the data point with the maximum value can be displayed in the plot area. + * 3 0x8 fAutoMinor Automatic Minor Unit Selected + * 0 The value specified by numMinor is used as the interval at which minor tick marks and minor gridlines are displayed. + * 1 numMinor is calculated automatically. + * 4 0x10 fAutoCross Automatic Category Crossing Point Selected + * 0 The value specified by numCross is used as the point at which the other axes in the axis group cross this value axis. + * 1 numCross is calculated so that the crossing point is displayed in the plot area. + * 5 0x20 fLogScale Log Scale + * 0 The scale of the value axis is linear. + * 1 The scale of the value axis is logarithmic. The default base of the logarithmic scale is 10, unless a CrtMlFrt record follows this record, specifying the base in a XmlTkLogBaseFrt structure. + * 6 0x40 fReverse Values in reverse order + * 0 Values are displayed from smallest-to-largest, from left-to-right, or from bottom-to-top, respectively, depending on the orientation of the axis. + * 1 The values are displayed in reverse order, meaning largest-to-smallest, from left-to-right, or from bottom-to-top, respectively. + * 7 0x80 fMaxCross Category is to cross at maximum value + * 0 The other axes in the axis group cross this value axis at the value specified by numCross. + * 1 The other axes in the axis group cross the value axis at the maximum value. If fMaxCross is 1, then both fAutoCross and numCross MUST be ignored. + *

                + * All 8-byte numbers in the preceding table are IEEE floating-point numbers. + * The numMin field defines the minimum numeric value that appears along the value axis. This field is all zeros if Auto Minimum is selected on the Scale tab of the Format Axis dialog box. + * The numMax field defines the maximum value displayed along the value axis and is all zeros if Auto Maximum is selected. + * The numMajor field defines the increment (unit) of the major value divisions (gridlines) along the value axis. The numMajor field is all zeros if Auto Major Unit is selected on the Scale tab of the Format Axis dialog box. + * The numMinor field defines the minor value divisions (gridlines) along the value axis and is all zeros if Auto Minor Unit is selected. + * The numCross field defines the value along the value axis at which the category axis crosses. This field is all zeros if Auto Category Axis Crosses At is selected. */ public class ValueRange extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2989883115978826628L; - double numMin, numMax, numMajor, numMinor, numCross; -// double yMin= 0.0, yMax= 0.0; - short grbit= 0; - boolean fAutoMin, fAutoMax, fAutoMajor, fAutoMinor, fAutoCross, fLogScale, fReverse, fMaxCross; - - public void init() { - super.init(); - numMin= ByteTools.eightBytetoLEDouble(this.getBytesAt(0, 8)); - numMax= ByteTools.eightBytetoLEDouble(this.getBytesAt(8, 16)); - numMajor= ByteTools.eightBytetoLEDouble(this.getBytesAt(16, 24)); - numMinor= ByteTools.eightBytetoLEDouble(this.getBytesAt(24, 32)); - numCross= ByteTools.eightBytetoLEDouble(this.getBytesAt(32, 40)); - grbit= ByteTools.readShort(this.getByteAt(40), this.getByteAt(41)); - fMaxCross= (grbit & 0x80)==0x80; - fAutoMin= (grbit & 0x1)==0x1; - fAutoMax= (grbit & 0x2)==0x2; - fAutoMajor= (grbit & 0x4)==0x4; - fAutoMinor= (grbit & 0x8)==0x8; - fAutoCross= (grbit & 0x10)==0x10; - fLogScale= (grbit & 0x20)==0x20; - fReverse= (grbit & 0x40)==0x40; - } - // 20070723 KSC: Need to create new records + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2989883115978826628L; + double numMin, numMax, numMajor, numMinor, numCross; + // double yMin= 0.0, yMax= 0.0; + short grbit = 0; + boolean fAutoMin, fAutoMax, fAutoMajor, fAutoMinor, fAutoCross, fLogScale, fReverse, fMaxCross; + + public void init() { + super.init(); + numMin = ByteTools.eightBytetoLEDouble(this.getBytesAt(0, 8)); + numMax = ByteTools.eightBytetoLEDouble(this.getBytesAt(8, 16)); + numMajor = ByteTools.eightBytetoLEDouble(this.getBytesAt(16, 24)); + numMinor = ByteTools.eightBytetoLEDouble(this.getBytesAt(24, 32)); + numCross = ByteTools.eightBytetoLEDouble(this.getBytesAt(32, 40)); + grbit = ByteTools.readShort(this.getByteAt(40), this.getByteAt(41)); + fMaxCross = (grbit & 0x80) == 0x80; + fAutoMin = (grbit & 0x1) == 0x1; + fAutoMax = (grbit & 0x2) == 0x2; + fAutoMajor = (grbit & 0x4) == 0x4; + fAutoMinor = (grbit & 0x8) == 0x8; + fAutoCross = (grbit & 0x10) == 0x10; + fLogScale = (grbit & 0x20) == 0x20; + fReverse = (grbit & 0x40) == 0x40; + } + + // 20070723 KSC: Need to create new records public static XLSRecord getPrototype() { ValueRange vr = new ValueRange(); vr.setOpcode(VALUERANGE); vr.setData(vr.PROTOTYPE_BYTES); - vr.init(); // important when we parse options + vr.init(); // important when we parse options return vr; } - private byte[] PROTOTYPE_BYTES = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 1}; - - - - /** Excel automatic calculations: + + private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 1}; + + + /** Excel automatic calculations: * Given: * yMax The maximum y value used in your chart. - yMin The minimum y value used in your chart. - xMax The maximum x value used in your chart. This applies only to - charts that use x values, such as scatter and bubble charts. - xMin The minimum x value used in your chart. This applies only to - charts that use x values, such as scatter and bubble charts. - - When you create a chart in Microsoft Excel, there are three possible scenarios that may apply to your data: - - The yMax and yMin values are both non-negative (greater than or equal to zero). This is Scenario One. - The yMax and yMin values are both non-positive (less than or equal to zero). This is Scenario Two. - The yMax value is positive, and the yMin value is negative. This is Scenario Three. - ************************************************************************************************************************************ - The major unit used by the y-axis is automatically determined by Microsoft Excel, based on all of the data included in the chart. - ************************************************************************************************************************************ - The following scenarios use this default major unit. + yMin The minimum y value used in your chart. + xMax The maximum x value used in your chart. This applies only to + charts that use x values, such as scatter and bubble charts. + xMin The minimum x value used in your chart. This applies only to + charts that use x values, such as scatter and bubble charts. + + When you create a chart in Microsoft Excel, there are three possible scenarios that may apply to your data: + + The yMax and yMin values are both non-negative (greater than or equal to zero). This is Scenario One. + The yMax and yMin values are both non-positive (less than or equal to zero). This is Scenario Two. + The yMax value is positive, and the yMin value is negative. This is Scenario Three. + ************************************************************************************************************************************ + The major unit used by the y-axis is automatically determined by Microsoft Excel, based on all of the data included in the chart. + ************************************************************************************************************************************ + The following scenarios use this default major unit. * @return */ - - + + /** * When you create a chart in Microsoft Excel, * there are three possible scenarios that can apply to your data: + * Scenario one: the yMax and yMin values are both positive or equal to zero. + * Scenario two: the yMax and yMin values are both negative or equal to zero. + * Scenario three: the yMax value is positive, and the yMin value is negative. + + The major unit used by the y-axis is automatically determined by Microsoft Excel, + based on all of the data included in the chart. + + The following scenarios use this default major unit. + Scenario one: + * If the chart is a 2-D area, column, bar, line or x-y scatter chart, the automatic maximum for the y-axis is the first major unit greater than or equal to the value returned by the following equation: + yMax + 0.05 * ( yMax - yMin ) + Otherwise, the automatic maximum for the y-axis is the first major unit greater than or equal to yMax. + * If the difference between yMax and yMin is greater than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is zero. + * If the difference between yMax and yMin is less than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is the first major unit less than or equal to the value returned by the following equation: + yMin - ( ( yMax - yMin ) / 2 ) + Exception: If the chart is an x-y scatter or bubble chart, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. + + Scenario two: + same as above except that + yMin + 0.05 * ( yMin - yMax ) + Otherwise, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. + and + yMax - ( ( yMin - yMax ) / 2 ) + + Scenario 3 + * The automatic maximum for the y-axis is the first major unit greater than or equal to the value returned by the following equation: + yMax + 0.05 * ( yMax - yMin ) + * The automatic minimum for the y-axis is the first major unit less than or equal to the value returned by the following equation: + yMin + 0.05 * ( yMin - yMax ) + + The above information also applies to charts that use x values, + such as x-y scatter charts and bubble charts. + For these types of charts, + substitute xMax and xMin for yMax and yMin in the above scenarios. + */ - * Scenario one: the yMax and yMin values are both positive or equal to zero. - * Scenario two: the yMax and yMin values are both negative or equal to zero. - * Scenario three: the yMax value is positive, and the yMin value is negative. - - The major unit used by the y-axis is automatically determined by Microsoft Excel, - based on all of the data included in the chart. - - The following scenarios use this default major unit. - Scenario one: - * If the chart is a 2-D area, column, bar, line or x-y scatter chart, the automatic maximum for the y-axis is the first major unit greater than or equal to the value returned by the following equation: - yMax + 0.05 * ( yMax - yMin ) - Otherwise, the automatic maximum for the y-axis is the first major unit greater than or equal to yMax. - * If the difference between yMax and yMin is greater than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is zero. - * If the difference between yMax and yMin is less than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is the first major unit less than or equal to the value returned by the following equation: - yMin - ( ( yMax - yMin ) / 2 ) - Exception: If the chart is an x-y scatter or bubble chart, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. - - Scenario two: - same as above except that - yMin + 0.05 * ( yMin - yMax ) - Otherwise, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. - and - yMax - ( ( yMin - yMax ) / 2 ) - - Scenario 3 - * The automatic maximum for the y-axis is the first major unit greater than or equal to the value returned by the following equation: - yMax + 0.05 * ( yMax - yMin ) - * The automatic minimum for the y-axis is the first major unit less than or equal to the value returned by the following equation: - yMin + 0.05 * ( yMin - yMax ) - - The above information also applies to charts that use x values, - such as x-y scatter charts and bubble charts. - For these types of charts, - substitute xMax and xMin for yMax and yMin in the above scenarios. - */ - /** * return minumum scale value -- see setMinMax for calc - * @return double + * + * @return double */ - public double getMin() { return numMin; } - + public double getMin() { + return numMin; + } + /** * return max scale value -- see setMinMax for calc - * @return double + * + * @return double */ - public double getMax() { return numMax; } + public double getMax() { + return numMax; + } /** - * all important major tick step - + * all important major tick step - + * * @return double major tick step value - @see setMinMax + * @see setMinMax */ - public double getMajorTick() { return numMajor; } - public double getMinorTick() { return numMinor; } // fautominor --woundn't even know how to calculate! + public double getMajorTick() { + return numMajor; + } + + public double getMinorTick() { + return numMinor; + } // fautominor --woundn't even know how to calculate! /** * sets the max and min values for the axis and uses these @@ -198,331 +208,335 @@ public static XLSRecord getPrototype() { *
                * NOTE that Excel keeps it's "automatic" calculation private so * below is the best guess looking at existing chart data - *
                max and min units are also based upon chart size, so this is only a rough approximation - * - * @param yMax double maximum series value - * @param yMin double minimum series value + *
                max and min units are also based upon chart size, so this is only a rough approximation + * + * @param yMax double maximum series value + * @param yMin double minimum series value */ public void setMaxMin(double yMax, double yMin) { - /** - * When you create a chart in Microsoft Excel, there are three possible scenarios that can apply to your data: - Scenario one: the yMax and yMin values are both positive or equal to zero. - Scenario two: the yMax and yMin values are both negative or equal to zero. - Scenario three: the yMax value is positive, and the yMin value is negative. - - NOTE: the difficultly in these calculations is the major unit, of which the maximum scale is based. - There is no true information regarding the major unit, unfortunately, except that it is based on - "all the data of the chart" - */ - if (fAutoMajor && fAutoMinor) { - int charttype= this.getParentChart().getChartType(); - if (yMax >= 0 && yMin >=0 && yMax!=yMin) { - // Major Unit Calculation -- best guest TODO: would be great to find out Excel's algorithm! + /** + * When you create a chart in Microsoft Excel, there are three possible scenarios that can apply to your data: + Scenario one: the yMax and yMin values are both positive or equal to zero. + Scenario two: the yMax and yMin values are both negative or equal to zero. + Scenario three: the yMax value is positive, and the yMin value is negative. + + NOTE: the difficultly in these calculations is the major unit, of which the maximum scale is based. + There is no true information regarding the major unit, unfortunately, except that it is based on + "all the data of the chart" + */ + if (fAutoMajor && fAutoMinor) { + int charttype = this.getParentChart().getChartType(); + if (yMax >= 0 && yMin >= 0 && yMax != yMin) { + // Major Unit Calculation -- best guest TODO: would be great to find out Excel's algorithm! // TODO: major unit is affected by height (or width, for bar charts) ***** develop algorithm!!! - // add a tiny pad for range ... - double diff= (yMax*1.1-yMin); - if (fAutoMax) { - double logDiff= Math.floor(Math.log10(diff)); - double f= (diff)/Math.pow(10, logDiff); - if (f <= 1) - f= 1; - else if (f <= 2) - f= 2; - else if (f <= 5) - f= 5; - else - f= 10; - f= f*Math.pow(10, logDiff); //scaled up max - numMajor= f*.1; // 1/10th of scaled up max - - } else { - numMajor= numMax/10.0; - } - /** - * If the chart is a 2-D area, column, bar, line or x-y scatter chart, - * the automatic maximum for the y-axis is the first major unit greater - * than or equal to the value returned by the following equation: - yMax + 0.05 * ( yMax - yMin ) - Otherwise, the automatic maximum for the y-axis is the first major unit - greater than or equal to yMax. - */ - if (fAutoMax) { - if (charttype==ChartConstants.AREACHART || - charttype==ChartConstants.COLCHART || - charttype==ChartConstants.BARCHART || - charttype==ChartConstants.LINECHART || - charttype==ChartConstants.SCATTERCHART || - charttype==ChartConstants.BUBBLECHART) { - if (numMajor==(int)numMajor) // int scale - usual case - numMax= Math.ceil(yMax+0.05*diff*1.1); - else - numMax= yMax+0.05*diff*1.1; - if (charttype==ChartConstants.BUBBLECHART) - numMax+=numMajor; // is this true in ALL CASES???? - if ((numMax%numMajor)!=0) // if not = to scale, scale up to next major unit - numMax= Math.floor((numMax+numMajor)/numMajor)*numMajor; - } else { - numMax= Math.floor((yMax+numMajor)/numMajor)*numMajor; - } - } - /** - * If the difference between yMax and yMin is greater than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is zero. - */ - if (fAutoMin) { - /** - * Exception: If the chart is an x-y scatter or bubble chart, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. - */ - if (charttype==ChartConstants.SCATTERCHART || - charttype==ChartConstants.BUBBLECHART) { - if (yMin%numMajor!=0) { - numMin= Math.floor((yMin-numMajor)/numMajor)*numMajor; - numMin= Math.round(numMin); - } - } else { - if ((yMax-yMin) > (numMax*.16667)) { - numMin= 0; - } else { // the first major unit less than or equal to the value from the below equation: - numMin= yMin-((numMax-yMin)/2); - if ((numMin%numMajor)!=0) - numMin= Math.floor((numMin-numMajor)/numMajor)*numMajor; - } - } - } - - // 20120905 KSC: recheck major to ensure not more than 10 steps ... - if (numMin >=0 && numMax >=0) { - if (((numMax-numMin)/numMajor) > 9) { - diff= (numMax*1.1-numMin); - double logDiff= Math.floor(Math.log10(diff)); - double f= (diff)/Math.pow(10, logDiff); - if (f <= 1) - f= 1; - else if (f <= 2) - f= 2; - else if (f <= 5) - f= 5; - else - f= 10; - f= f*Math.pow(10, logDiff); //scaled up max - numMajor= f*.1; // 1/10th of scaled up max - } - } - numMinor= numMajor/5; // seems to be the correct calculation ... - } else if (yMax < 0 && yMin < 0) { - double diff= (yMin-yMax); - numMin= yMin + 0.05*diff; - if (diff > (yMin*.16667)) { - numMax= 0; - } else { - numMax= yMax-(diff/2); - } - numMax= Math.floor((numMax+numMajor)/numMajor)*numMajor; - if (fAutoMinor) { - numMinor= numMajor/5; // just a guess, really - // Exception: If the chart is an x-y scatter or bubble chart, the automatic maximum for the y-axis is the first major unit greater than or equal to yMax. - } else { // yMax > 0 && yMin < 0 - numMax= yMax + 0.05 * ( yMax - yMin ); - numMin= yMin + 0.05 * ( yMin - yMax ); - } - } else { - numMax= yMax; - numMin= yMin; - } - } + // add a tiny pad for range ... + double diff = (yMax * 1.1 - yMin); + if (fAutoMax) { + double logDiff = Math.floor(Math.log10(diff)); + double f = (diff) / Math.pow(10, logDiff); + if (f <= 1) + f = 1; + else if (f <= 2) + f = 2; + else if (f <= 5) + f = 5; + else + f = 10; + f = f * Math.pow(10, logDiff); //scaled up max + numMajor = f * .1; // 1/10th of scaled up max + + } else { + numMajor = numMax / 10.0; + } + /** + * If the chart is a 2-D area, column, bar, line or x-y scatter chart, + * the automatic maximum for the y-axis is the first major unit greater + * than or equal to the value returned by the following equation: + yMax + 0.05 * ( yMax - yMin ) + Otherwise, the automatic maximum for the y-axis is the first major unit + greater than or equal to yMax. + */ + if (fAutoMax) { + if (charttype == ChartConstants.AREACHART || + charttype == ChartConstants.COLCHART || + charttype == ChartConstants.BARCHART || + charttype == ChartConstants.LINECHART || + charttype == ChartConstants.SCATTERCHART || + charttype == ChartConstants.BUBBLECHART) { + if (numMajor == (int) numMajor) // int scale - usual case + numMax = Math.ceil(yMax + 0.05 * diff * 1.1); + else + numMax = yMax + 0.05 * diff * 1.1; + if (charttype == ChartConstants.BUBBLECHART) + numMax += numMajor; // is this true in ALL CASES???? + if ((numMax % numMajor) != 0) // if not = to scale, scale up to next major unit + numMax = Math.floor((numMax + numMajor) / numMajor) * numMajor; + } else { + numMax = Math.floor((yMax + numMajor) / numMajor) * numMajor; + } + } + /** + * If the difference between yMax and yMin is greater than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is zero. + */ + if (fAutoMin) { + /** + * Exception: If the chart is an x-y scatter or bubble chart, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. + */ + if (charttype == ChartConstants.SCATTERCHART || + charttype == ChartConstants.BUBBLECHART) { + if (yMin % numMajor != 0) { + numMin = Math.floor((yMin - numMajor) / numMajor) * numMajor; + numMin = Math.round(numMin); + } + } else { + if ((yMax - yMin) > (numMax * .16667)) { + numMin = 0; + } else { // the first major unit less than or equal to the value from the below equation: + numMin = yMin - ((numMax - yMin) / 2); + if ((numMin % numMajor) != 0) + numMin = Math.floor((numMin - numMajor) / numMajor) * numMajor; + } + } + } + + // 20120905 KSC: recheck major to ensure not more than 10 steps ... + if (numMin >= 0 && numMax >= 0) { + if (((numMax - numMin) / numMajor) > 9) { + diff = (numMax * 1.1 - numMin); + double logDiff = Math.floor(Math.log10(diff)); + double f = (diff) / Math.pow(10, logDiff); + if (f <= 1) + f = 1; + else if (f <= 2) + f = 2; + else if (f <= 5) + f = 5; + else + f = 10; + f = f * Math.pow(10, logDiff); //scaled up max + numMajor = f * .1; // 1/10th of scaled up max + } + } + numMinor = numMajor / 5; // seems to be the correct calculation ... + } else if (yMax < 0 && yMin < 0) { + double diff = (yMin - yMax); + numMin = yMin + 0.05 * diff; + if (diff > (yMin * .16667)) { + numMax = 0; + } else { + numMax = yMax - (diff / 2); + } + numMax = Math.floor((numMax + numMajor) / numMajor) * numMajor; + if (fAutoMinor) { + numMinor = numMajor / 5; // just a guess, really + // Exception: If the chart is an x-y scatter or bubble chart, the automatic maximum for the y-axis is the first major unit greater than or equal to yMax. + } else { // yMax > 0 && yMin < 0 + numMax = yMax + 0.05 * (yMax - yMin); + numMin = yMin + 0.05 * (yMin - yMax); + } + } else { + numMax = yMax; + numMin = yMin; + } + } } - + /** * static utlity to calculate the min and max on a scale in a given area * NOTE: this is usually the Y Axis, but scatter charts can have a value axis on the X Axis - * @param MaxVal Actual Maximum Value on Axis - * @param MinVal Actual Mimumum Value on Axis - * @param area Area of Axis in pixels (I think :)) + * + * @param MaxVal Actual Maximum Value on Axis + * @param MinVal Actual Mimumum Value on Axis + * @param area Area of Axis in pixels (I think :)) * @return */ public static double[] calcMaxMin(double MaxVal, double MinVal, double area) { - // h==235 is normal, and the below alg. appear correct for it - // h==776 (bar chart) ??? SIGH ... - double ymax= MaxVal; - double ymin= MinVal; - double numMajor= ymax-ymin; - if (numMajor>0 && numMajor<20) - numMajor= 2; - else if (numMajor>20 && numMajor<100) - numMajor= 20; - else if (numMajor>100 && numMajor<500) - numMajor= 50; - if (ymax >= 0) { - ymax= Math.floor((ymax+numMajor)/numMajor)*numMajor; - } else { - ymax= Math.floor((ymax+numMajor)/numMajor)*numMajor; - } - - double numMin= ymin-numMajor; - if ((numMin%numMajor)>0) - numMin-=numMajor; - if (ymin >= 0) - numMin= Math.max(ymin-numMajor, 0); - numMin= Math.round(numMin); - - double numMax= 0; - if (ymax >= 0) { - numMax= (int) Math.floor((ymax+numMajor)/numMajor)*numMajor; - } else { - numMax= (int) Math.floor((ymax+numMajor)/numMajor)*numMajor; - } - - return new double[] {numMin, numMajor, ymax}; + // h==235 is normal, and the below alg. appear correct for it + // h==776 (bar chart) ??? SIGH ... + double ymax = MaxVal; + double ymin = MinVal; + double numMajor = ymax - ymin; + if (numMajor > 0 && numMajor < 20) + numMajor = 2; + else if (numMajor > 20 && numMajor < 100) + numMajor = 20; + else if (numMajor > 100 && numMajor < 500) + numMajor = 50; + if (ymax >= 0) { + ymax = Math.floor((ymax + numMajor) / numMajor) * numMajor; + } else { + ymax = Math.floor((ymax + numMajor) / numMajor) * numMajor; + } + + double numMin = ymin - numMajor; + if ((numMin % numMajor) > 0) + numMin -= numMajor; + if (ymin >= 0) + numMin = Math.max(ymin - numMajor, 0); + numMin = Math.round(numMin); + + double numMax = 0; + if (ymax >= 0) { + numMax = (int) Math.floor((ymax + numMajor) / numMajor) * numMajor; + } else { + numMax = (int) Math.floor((ymax + numMajor) / numMajor) * numMajor; + } + + return new double[]{numMin, numMajor, ymax}; } - + /** * sets a specific OOXML axis option *
                can be one of: - *
                crosses possible crossing points (autoZero, max, min) + *
                crosses possible crossing points (autoZero, max, min) *
                crossBeteween whether axis crosses the cat. axis between or on categories (between, midCat) *
                crossesAt where on axis the perpendicular axis crosses (double val) *
                majorTickMark major tick mark position (cross, in, none, out) *
                minorTickMark minor tick mark position ("") *
                tickLblPos tick label position (high, low, nextTo, none) *
                majorUnit distance between major tick marks (val, date ax only) (double >= 0) - *
                minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + *
                minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + * * @param op * @param val - */ + */ public boolean setOption(String op, String val) { - if (op.equals("crossesAt")) - // specifies where axis crosses -- numCross or catCross - numCross= new Double(val).doubleValue(); - else if (op.equals("orientation")) { // axis orientation minMax or maxMin -- fReverse - fReverse= (val.equals("maxMin")); // means in reverse order - ByteTools.updateGrBit(grbit, fReverse, 6); - } - else if (op.equals("crosses")) { // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross - if (val.equals("max")) { // TODO: this is probly wrong - fMaxCross= true; - ByteTools.updateGrBit(grbit, fMaxCross, 7); - } else if (val.equals("autoZero")) { - fAutoCross= true; // is this correct?? - ByteTools.updateGrBit(grbit, fAutoCross, 4); - }else if (val.equals("min")) { - fAutoCross= false; - ByteTools.updateGrBit(grbit, fAutoCross, 4); - } - } - else if (op.equals("crossBetween")) { // val= between, midCat, crossBetween - if (val.equals("between")) - fAutoCross= true; - // otherwise do what??? - } - else if (op.equals("max")) { // axis max - valueRange only? - numMax= new Double(val).doubleValue(); - // turn off automatic scaling - grbit= (short) (grbit & 0xFD); // turn off bit 2 - } else if (op.equals("min")) { // axis min- valueRange only? - numMin= new Double(val).doubleValue(); - // turn off automatic scaling - grbit= (short) (grbit & 0xFE); // turn off bit 1 - } else if (op.equals("majorUnit")) - numMajor= new Double(val).doubleValue(); - else if (op.equals("minorUnit")) - numMinor= new Double(val).doubleValue(); - else - return false; - this.updateRecord(); - return true; + if (op.equals("crossesAt")) + // specifies where axis crosses -- numCross or catCross + numCross = new Double(val).doubleValue(); + else if (op.equals("orientation")) { // axis orientation minMax or maxMin -- fReverse + fReverse = (val.equals("maxMin")); // means in reverse order + ByteTools.updateGrBit(grbit, fReverse, 6); + } else if (op.equals("crosses")) { // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross + if (val.equals("max")) { // TODO: this is probly wrong + fMaxCross = true; + ByteTools.updateGrBit(grbit, fMaxCross, 7); + } else if (val.equals("autoZero")) { + fAutoCross = true; // is this correct?? + ByteTools.updateGrBit(grbit, fAutoCross, 4); + } else if (val.equals("min")) { + fAutoCross = false; + ByteTools.updateGrBit(grbit, fAutoCross, 4); + } + } else if (op.equals("crossBetween")) { // val= between, midCat, crossBetween + if (val.equals("between")) + fAutoCross = true; + // otherwise do what??? + } else if (op.equals("max")) { // axis max - valueRange only? + numMax = new Double(val).doubleValue(); + // turn off automatic scaling + grbit = (short) (grbit & 0xFD); // turn off bit 2 + } else if (op.equals("min")) { // axis min- valueRange only? + numMin = new Double(val).doubleValue(); + // turn off automatic scaling + grbit = (short) (grbit & 0xFE); // turn off bit 1 + } else if (op.equals("majorUnit")) + numMajor = new Double(val).doubleValue(); + else if (op.equals("minorUnit")) + numMinor = new Double(val).doubleValue(); + else + return false; + this.updateRecord(); + return true; } - - + + /** * retrieve generic Value axis option *
                can be one of: - *
                crosses possible crossing points (autoZero, max, min) + *
                crosses possible crossing points (autoZero, max, min) *
                crossBeteween whether axis crosses the cat. axis between or on categories (between, midCat) *
                crossesAt where on axis the perpendicular axis crosses (double val) *
                majorTickMark major tick mark position (cross, in, none, out) *
                minorTickMark minor tick mark position ("") *
                tickLblPos tick label position (high, low, nextTo, none) *
                majorUnit distance between major tick marks (val, date ax only) (double >= 0) - *
                minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + *
                minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + * * @param op * @return */ public String getOption(String op) { - if (op.equals("crossesAt")) - return String.valueOf(numCross); - if (op.equals("orientation")) - return (fReverse)?"maxMin":"minMax"; - if (op.equals("crosses")) { - if (fMaxCross) return "max"; - if (fAutoCross)return "autoZero"; // correct?? - return "min"; // correct?? - } - if (op.equals("crossBetween")) // val= between, midCat, crossBetween - return "between"; // TODO: figure out! - if (op.equals("max")) - return String.valueOf(numMax); - if (op.equals("min")) - return String.valueOf(numMin); - if (op.equals("majorUnit")) - return String.valueOf(numMajor); - if (op.equals("minorUnit")) - return String.valueOf(numMinor); - return null; + if (op.equals("crossesAt")) + return String.valueOf(numCross); + if (op.equals("orientation")) + return (fReverse) ? "maxMin" : "minMax"; + if (op.equals("crosses")) { + if (fMaxCross) return "max"; + if (fAutoCross) return "autoZero"; // correct?? + return "min"; // correct?? + } + if (op.equals("crossBetween")) // val= between, midCat, crossBetween + return "between"; // TODO: figure out! + if (op.equals("max")) + return String.valueOf(numMax); + if (op.equals("min")) + return String.valueOf(numMin); + if (op.equals("majorUnit")) + return String.valueOf(numMajor); + if (op.equals("minorUnit")) + return String.valueOf(numMinor); + return null; } /** * set the minimum scale value of this Y or value axis - *
                Doing so turns automatic minimum off + *
                Doing so turns automatic minimum off + * * @param min */ public void setMin(double min) { - numMin= min; - // turn off automatic scaling - grbit= (short) (grbit & 0xFE); // turn off bit 1 - updateRecord(); + numMin = min; + // turn off automatic scaling + grbit = (short) (grbit & 0xFE); // turn off bit 1 + updateRecord(); } + /** * set the max scale value of this Y or value axis - *
                Doing so turns automatic maximum off + *
                Doing so turns automatic maximum off + * * @param max */ public void setMax(double max) { - numMax= max; - // turn off automatic scaling - grbit= (short) (grbit & 0xFD); // turn off bit 2 - updateRecord(); + numMax = max; + // turn off automatic scaling + grbit = (short) (grbit & 0xFD); // turn off bit 2 + updateRecord(); } - + public boolean isAutomaticScale() { - return fAutoMin || fAutoMax || fAutoMinor || fAutoMajor; + return fAutoMin || fAutoMax || fAutoMinor || fAutoMajor; } + public boolean isAutomaticMax() { - return fAutoMax; + return fAutoMax; } - + public boolean isAutomaticMin() { - return fAutoMin; + return fAutoMin; } - - public void setAutomaticMin(boolean b){ - fAutoMin= b; - if (b) { - grbit= (short) (grbit | 0x1); // turn on bit 1 - numMin= 0.0; - } else - grbit= (short) (grbit & 0xFE); // turn off bit 1 - updateRecord(); + + public void setAutomaticMin(boolean b) { + fAutoMin = b; + if (b) { + grbit = (short) (grbit | 0x1); // turn on bit 1 + numMin = 0.0; + } else + grbit = (short) (grbit & 0xFE); // turn off bit 1 + updateRecord(); } - public void setAutomaticMax(boolean b){ - fAutoMax= b; - if (b) { - grbit= (short) (grbit | 0x2); // turn on bit 2 - numMax= 0.0; - } else - grbit= (short) (grbit & 0xFD); // turn off bit 2 - updateRecord(); + public void setAutomaticMax(boolean b) { + fAutoMax = b; + if (b) { + grbit = (short) (grbit | 0x2); // turn on bit 2 + numMax = 0.0; + } else + grbit = (short) (grbit & 0xFD); // turn off bit 2 + updateRecord(); } - + private void updateRecord() { byte[] b = ByteTools.doubleToLEByteArray(numMin); System.arraycopy(b, 0, this.getData(), 0, 8); @@ -538,12 +552,14 @@ private void updateRecord() { this.getData()[40] = b[0]; this.getData()[41] = b[1]; } - /** returns true if axis should be displayed on RHS of chart + + /** + * returns true if axis should be displayed on RHS of chart * false for default LHS - * + * * @return */ public boolean isReversed() { - return fReverse; + return fReverse; } } diff --git a/src/main/java/io/starter/formats/XLS/charts/YMult.java b/src/main/java/io/starter/formats/XLS/charts/YMult.java index a9cf172..c2afa49 100644 --- a/src/main/java/io/starter/formats/XLS/charts/YMult.java +++ b/src/main/java/io/starter/formats/XLS/charts/YMult.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,181 +24,191 @@ import io.starter.formats.XLS.XLSRecord; import io.starter.toolkit.ByteTools; -/** - YMULT: Y Multiplier (857h) - Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. - This record describes the axis multiplier feature which scales the axis values - displayed by the axis tick labels. For instance, an axis multiplier value of - "millions" would cause the axis tick labels to show the axis value divided by one - million (e.g., the tick label for an axis value of 20,000,000 would show "20".) - This record is a "parent" record and is immediately followed by a set of records - surrounded by rtStartObject and rtEndObject which describes the axis multiplier label. - - Record Data - Offset Field Name Size Contents - 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0857h - 6 grbitFrt 2 FRT flags; must be zero - 8 axmid 2 Axis multiplier ID, one of the following values: - -1 = multiplier value is stored in numLabelMultiplier - 0 = no multiplier (same as 1.0) - 1 = Hundreds, 10 2nd - 2 = Thousands, 10 3rd - 3 = Ten Thousands, 10 4th - 4 = Hundred Thousands, 10 5th - 5 = Millions, 10 6th - 6 = Ten Millions, 10 7th - 7 = Hundred Millions, 10 8th - 8 = billion - 9 = trillion - 16 numLabelMultiplier 4 Numeric value - 18 grbit 2 Option flags for y axis multiplier (see description below)* - The grbit field contains the following category axis label option flags: - Bits Mask Flag Name Contents - 0 0001h fEnabled =1 if the multiplier is enabled =0 otherwise - 1 0002h fAutoShowMultiplier =1 if the multiplier label is shown =0 otherwise - 15-2 FFFCh (unused) Reserved; must be zero -*/ +/** + * YMULT: Y Multiplier (857h) + * Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. + * This record describes the axis multiplier feature which scales the axis values + * displayed by the axis tick labels. For instance, an axis multiplier value of + * "millions" would cause the axis tick labels to show the axis value divided by one + * million (e.g., the tick label for an axis value of 20,000,000 would show "20".) + * This record is a "parent" record and is immediately followed by a set of records + * surrounded by rtStartObject and rtEndObject which describes the axis multiplier label. + *

                + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0857h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 axmid 2 Axis multiplier ID, one of the following values: + * -1 = multiplier value is stored in numLabelMultiplier + * 0 = no multiplier (same as 1.0) + * 1 = Hundreds, 10 2nd + * 2 = Thousands, 10 3rd + * 3 = Ten Thousands, 10 4th + * 4 = Hundred Thousands, 10 5th + * 5 = Millions, 10 6th + * 6 = Ten Millions, 10 7th + * 7 = Hundred Millions, 10 8th + * 8 = billion + * 9 = trillion + * 16 numLabelMultiplier 4 Numeric value + * 18 grbit 2 Option flags for y axis multiplier (see description below)* + *

                + * The grbit field contains the following category axis label option flags: + * Bits Mask Flag Name Contents + * 0 0001h fEnabled =1 if the multiplier is enabled =0 otherwise + * 1 0002h fAutoShowMultiplier =1 if the multiplier label is shown =0 otherwise + * 15-2 FFFCh (unused) Reserved; must be zero + */ public class YMult extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - - private static final long serialVersionUID = -6166267220292885486L; - short axmid, grbit; - double numLabelMultiplier; - - public void init() { - super.init(); - axmid= ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - numLabelMultiplier= ByteTools.eightBytetoLEDouble(this.getBytesAt(6, 8)); - grbit= ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); - } - // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[] {}; + /** + * serialVersionUID + */ + + private static final long serialVersionUID = -6166267220292885486L; + short axmid, grbit; + double numLabelMultiplier; + + public void init() { + super.init(); + axmid = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); + numLabelMultiplier = ByteTools.eightBytetoLEDouble(this.getBytesAt(6, 8)); + grbit = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); + } + + // TODO: Prototype Bytes + private byte[] PROTOTYPE_BYTES = new byte[]{}; + public static XLSRecord getPrototype() { - YMult ym= new YMult(); - ym.setOpcode(YMULT); - ym.setData(ym.PROTOTYPE_BYTES); - ym.init(); + YMult ym = new YMult(); + ym.setOpcode(YMULT); + ym.setData(ym.PROTOTYPE_BYTES); + ym.init(); return ym; } + /** * returns the Axis multiplier ID, one of the following values: -

              • -1 = multiplier value is stored in numLabelMultiplier -
              • 0 = no multiplier (same as 1.0) -
              • 1 = Hundreds, 10 2nd -
              • 2 = Thousands, 10 3rd -
              • 3 = Ten Thousands, 10 4th -
              • 4 = Hundred Thousands, 10 5th -
              • 5 = Millions, 10 6th -
              • 6 = Ten Millions, 10 7th -
              • 7 = Hundred Millions, 10 8th -
              • 8 = Thousand Millions, 10 9th -
              • 9 = Billions, 10 12th + *
              • -1 = multiplier value is stored in numLabelMultiplier + *
              • 0 = no multiplier (same as 1.0) + *
              • 1 = Hundreds, 10 2nd + *
              • 2 = Thousands, 10 3rd + *
              • 3 = Ten Thousands, 10 4th + *
              • 4 = Hundred Thousands, 10 5th + *
              • 5 = Millions, 10 6th + *
              • 6 = Ten Millions, 10 7th + *
              • 7 = Hundred Millions, 10 8th + *
              • 8 = Thousand Millions, 10 9th + *
              • 9 = Billions, 10 12th + * * @return */ - public short getAxMultiplierId() { - return axmid; } - + public short getAxMultiplierId() { + return axmid; + } + public String getAxMultiplierIdAsString() { - switch (axmid) { - case -1: - return null; - case 0: - return null; //? - case 1: - return "hundreds"; - case 2: - return "thousands"; - case 3: - return "tenThousands"; - case 4: - return "hundredThousands"; - case 5: - return "millions"; - case 6: - return "tenMillions"; - case 7: - return "hundredMillions"; - case 8: - return "billions"; - case 9: - return "trillions"; - } - return null; + switch (axmid) { + case -1: + return null; + case 0: + return null; //? + case 1: + return "hundreds"; + case 2: + return "thousands"; + case 3: + return "tenThousands"; + case 4: + return "hundredThousands"; + case 5: + return "millions"; + case 6: + return "tenMillions"; + case 7: + return "hundredMillions"; + case 8: + return "billions"; + case 9: + return "trillions"; + } + return null; } - + /** * Sets Axis multiplier ID, one of the following values: -
              • -1 = multiplier value is stored in numLabelMultiplier -
              • 0 = no multiplier (same as 1.0) -
              • 1 = Hundreds, 10 2nd -
              • 2 = Thousands, 10 3rd -
              • 3 = Ten Thousands, 10 4th -
              • 4 = Hundred Thousands, 10 5th -
              • 5 = Millions, 10 6th -
              • 6 = Ten Millions, 10 7th -
              • 7 = Hundred Millions, 10 8th -
              • 8 = Thousand Millions, 10 9th -
              • 9 = Billions, 10 12th + *
              • -1 = multiplier value is stored in numLabelMultiplier + *
              • 0 = no multiplier (same as 1.0) + *
              • 1 = Hundreds, 10 2nd + *
              • 2 = Thousands, 10 3rd + *
              • 3 = Ten Thousands, 10 4th + *
              • 4 = Hundred Thousands, 10 5th + *
              • 5 = Millions, 10 6th + *
              • 6 = Ten Millions, 10 7th + *
              • 7 = Hundred Millions, 10 8th + *
              • 8 = Thousand Millions, 10 9th + *
              • 9 = Billions, 10 12th + * * @param m */ public void setAxMultiplierId(int m) { - if (!(m >-2 && m < 10)) - return; // report error? - axmid= (short) m; - byte[] b= ByteTools.shortToLEBytes(axmid); - this.getData()[4]= b[0]; - this.getData()[5]= b[1]; + if (!(m > -2 && m < 10)) + return; // report error? + axmid = (short) m; + byte[] b = ByteTools.shortToLEBytes(axmid); + this.getData()[4] = b[0]; + this.getData()[5] = b[1]; } /** * Sets Axis multiplier ID via OOXML String value: -
              • hundreds Hundreds -
              • thousands Thousands -
              • tenThousands Ten Thousands -
              • hundredThousands Hundred Thousands -
              • millions Millions -
              • tenMillions Ten Millions -
              • hundredMillions Hundred Millions -
              • billions Billions -
              • trillions Trillions -*/ + *
              • hundreds Hundreds + *
              • thousands Thousands + *
              • tenThousands Ten Thousands + *
              • hundredThousands Hundred Thousands + *
              • millions Millions + *
              • tenMillions Ten Millions + *
              • hundredMillions Hundred Millions + *
              • billions Billions + *
              • trillions Trillions + */ public void setAxMultiplierId(String m) { - if (m.equalsIgnoreCase("hundreds")) - axmid= 1; - else if (m.equalsIgnoreCase("thousands")) - axmid= 2; - else if (m.equalsIgnoreCase("tenThousands")) - axmid= 3; - else if (m.equalsIgnoreCase("hundredThousands")) - axmid= 4; - else if (m.equalsIgnoreCase("millions")) - axmid= 5; - else if (m.equalsIgnoreCase("tenMillions")) - axmid= 6; - else if (m.equalsIgnoreCase("hundredMillions")) - axmid= 7; - else if (m.equalsIgnoreCase("billions")) - axmid= 8; - else if (m.equalsIgnoreCase("trillions")) - axmid= 9; - else // default - axmid= 0; - byte[] b= ByteTools.shortToLEBytes(axmid); - this.getData()[4]= b[0]; - this.getData()[5]= b[1]; + if (m.equalsIgnoreCase("hundreds")) + axmid = 1; + else if (m.equalsIgnoreCase("thousands")) + axmid = 2; + else if (m.equalsIgnoreCase("tenThousands")) + axmid = 3; + else if (m.equalsIgnoreCase("hundredThousands")) + axmid = 4; + else if (m.equalsIgnoreCase("millions")) + axmid = 5; + else if (m.equalsIgnoreCase("tenMillions")) + axmid = 6; + else if (m.equalsIgnoreCase("hundredMillions")) + axmid = 7; + else if (m.equalsIgnoreCase("billions")) + axmid = 8; + else if (m.equalsIgnoreCase("trillions")) + axmid = 9; + else // default + axmid = 0; + byte[] b = ByteTools.shortToLEBytes(axmid); + this.getData()[4] = b[0]; + this.getData()[5] = b[1]; } - + // dispUnits -> builtInUnit (val: billions, - public double getCustomMultiplier() { return numLabelMultiplier; } + public double getCustomMultiplier() { + return numLabelMultiplier; + } + public void setCustomMultiplier(double m) { - numLabelMultiplier= m; - axmid= -1; // custom - byte[] b= ByteTools.shortToLEBytes(axmid); - this.getData()[4]= b[0]; - this.getData()[5]= b[1]; + numLabelMultiplier = m; + axmid = -1; // custom + byte[] b = ByteTools.shortToLEBytes(axmid); + this.getData()[4] = b[0]; + this.getData()[5] = b[1]; b = ByteTools.doubleToLEByteArray(numLabelMultiplier); System.arraycopy(b, 0, this.getData(), 6, 8); } diff --git a/src/main/java/io/starter/formats/XLS/formulas/CalculationException.java b/src/main/java/io/starter/formats/XLS/formulas/CalculationException.java index 15e2522..0abd6c8 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/CalculationException.java +++ b/src/main/java/io/starter/formats/XLS/formulas/CalculationException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,144 +22,160 @@ */ package io.starter.formats.XLS.formulas; -import java.util.Arrays; -import java.util.Collections; - -/** Indicates an error occurred during formula calculation. +/** + * Indicates an error occurred during formula calculation. */ public class CalculationException -extends Exception { - private static final long serialVersionUID = 2028428287133817627L; - static String[][] errorStrings= {{"#DIV/0!", "7"}, - {"#N/A", "42"}, - {"#NAME?", "29"}, - {"#NULL!", "0"}, - {"#NUM!", "36"}, - {"#REF!", "23"}, - {"#VALUE!", "15"}, - {"#CIR_ERR!", "15"} // false error with code for value, output for circular ref exceptions - }; - - /** Excel #NULL! error. - * Indicates that a range intersection returned no cells. - */ - public static final byte NULL = (byte)0x00; - - /** Excel #DIV/0! error. - * Indicates that the formula attempted to divide by zero. - */ - public static final byte DIV0 = (byte)0x07; - - /** Excel #VALUE! error. - * Indicates that there was an operand type mismatch. - */ - public static final byte VALUE = (byte)0x0F; - - /** Excel #REF! error. - * Indicates that a reference was made to a cell that doesn't exist. - */ - public static final byte REF = (byte)0x17; - - /** Excel #NAME? error. - * Indicates an unknown string was encountered in the formula. - */ - public static final byte NAME = (byte)0x1D; - - /** Excel #NUM! error. - * Indicates that a calculation result overflowed the number storage. - */ - public static final byte NUM = (byte)0x24; - - /** Excel #N/A error. - * Indicates that a lookup (e.g. VLOOKUP) returned no results. - */ - public static final byte NA = (byte)0x2A; - - /** - * Custom circular exception error, internally stores as a #VALUE - */ - public static final byte CIR_ERR = (byte)0xFF; - - /** The error code for this error. */ - private final byte error; - - /** Creates a new CaluculationException. - * @param error the error code. must be one of the defined error constants. - */ - public CalculationException (byte error) { - this.error = error; - } - - /** Gets the BIFF8 error code for this error. */ - public byte getErrorCode() { - if (error==CIR_ERR)return VALUE; - return error; - } - - /** static version, takes String error code and returns the correct error code - * - * @param error String - * @return - */ - public static byte getErrorCode(String error) { - if (error==null) return 0; // unknown - for (int i= 0; i < errorStrings.length; i++) { - if (error.equals(errorStrings[i][0])) { - return new Byte(errorStrings[i][1]).byteValue(); - } - } - return 0; - } - - /** Gets a human-readable message describing this error. */ - public String getMessage() { - switch (error) { - case NULL: - return "a range intersection returned no cells"; - case DIV0: - return "attempted to divide by zero"; - case VALUE: - return "operand type mismatch"; - case REF: - return "reference to a cell that doesn't exist"; - case NAME: - return "reference to an unknown function or defined name"; - case NUM: - return "number storage overflow"; - case NA: - return "lookup returned no value for the given criteria"; - case CIR_ERR: - return "circular reference error"; - default: - return "unknown error occurred"; - } - } - - /** Gets the string name of this error. */ - public String getName() { - switch (error) { - case NULL: - return "#NULL!"; - case DIV0: - return "#DIV/0!"; - case VALUE: - return "#VALUE!"; - case REF: - return "#REF!"; - case NAME: - return "#NAME?"; - case NUM: - return "#NUM!"; - case NA: - return "#N/A"; - case CIR_ERR: - return "#CIR_ERR!"; - default: - return null; - } - } - - public String toString() { - return getName(); - } + extends Exception { + private static final long serialVersionUID = 2028428287133817627L; + static String[][] errorStrings = {{"#DIV/0!", "7"}, + {"#N/A", "42"}, + {"#NAME?", "29"}, + {"#NULL!", "0"}, + {"#NUM!", "36"}, + {"#REF!", "23"}, + {"#VALUE!", "15"}, + {"#CIR_ERR!", "15"} // false error with code for value, output for circular ref exceptions + }; + + /** + * Excel #NULL! error. + * Indicates that a range intersection returned no cells. + */ + public static final byte NULL = (byte) 0x00; + + /** + * Excel #DIV/0! error. + * Indicates that the formula attempted to divide by zero. + */ + public static final byte DIV0 = (byte) 0x07; + + /** + * Excel #VALUE! error. + * Indicates that there was an operand type mismatch. + */ + public static final byte VALUE = (byte) 0x0F; + + /** + * Excel #REF! error. + * Indicates that a reference was made to a cell that doesn't exist. + */ + public static final byte REF = (byte) 0x17; + + /** + * Excel #NAME? error. + * Indicates an unknown string was encountered in the formula. + */ + public static final byte NAME = (byte) 0x1D; + + /** + * Excel #NUM! error. + * Indicates that a calculation result overflowed the number storage. + */ + public static final byte NUM = (byte) 0x24; + + /** + * Excel #N/A error. + * Indicates that a lookup (e.g. VLOOKUP) returned no results. + */ + public static final byte NA = (byte) 0x2A; + + /** + * Custom circular exception error, internally stores as a #VALUE + */ + public static final byte CIR_ERR = (byte) 0xFF; + + /** + * The error code for this error. + */ + private final byte error; + + /** + * Creates a new CaluculationException. + * + * @param error the error code. must be one of the defined error constants. + */ + public CalculationException(byte error) { + this.error = error; + } + + /** + * Gets the BIFF8 error code for this error. + */ + public byte getErrorCode() { + if (error == CIR_ERR) return VALUE; + return error; + } + + /** + * static version, takes String error code and returns the correct error code + * + * @param error String + * @return + */ + public static byte getErrorCode(String error) { + if (error == null) return 0; // unknown + for (int i = 0; i < errorStrings.length; i++) { + if (error.equals(errorStrings[i][0])) { + return new Byte(errorStrings[i][1]).byteValue(); + } + } + return 0; + } + + /** + * Gets a human-readable message describing this error. + */ + public String getMessage() { + switch (error) { + case NULL: + return "a range intersection returned no cells"; + case DIV0: + return "attempted to divide by zero"; + case VALUE: + return "operand type mismatch"; + case REF: + return "reference to a cell that doesn't exist"; + case NAME: + return "reference to an unknown function or defined name"; + case NUM: + return "number storage overflow"; + case NA: + return "lookup returned no value for the given criteria"; + case CIR_ERR: + return "circular reference error"; + default: + return "unknown error occurred"; + } + } + + /** + * Gets the string name of this error. + */ + public String getName() { + switch (error) { + case NULL: + return "#NULL!"; + case DIV0: + return "#DIV/0!"; + case VALUE: + return "#VALUE!"; + case REF: + return "#REF!"; + case NAME: + return "#NAME?"; + case NUM: + return "#NUM!"; + case NA: + return "#N/A"; + case CIR_ERR: + return "#CIR_ERR!"; + default: + return null; + } + } + + public String toString() { + return getName(); + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/Calculator.java b/src/main/java/io/starter/formats/XLS/formulas/Calculator.java index a0a8d7e..fef22a6 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/Calculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/Calculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,100 +24,100 @@ import io.starter.OpenXLS.DateConverter; import io.starter.formats.XLS.BiffRec; -import io.starter.toolkit.Logger; public class Calculator { /** * given a BiffRec Cell Record, an Object and an operator to compare to, * return true if the comparison passes, false otherwise - * @param c BiffRec cell record - * @param o Object value - one of Double, String or Boolean - * @param op - String operator - one of "=", ">", ">=", "<", "<=" or "<> - * @return true if comparison of operator and value with cell value passes, false otherwise + * + * @param c BiffRec cell record + * @param o Object value - one of Double, String or Boolean + * @param op - String operator - one of "=", ">", ">=", "<", "<=" or "<> + * @return true if comparison of operator and value with cell value passes, false otherwise */ public static boolean compareCellValue(BiffRec c, Object o, String op) { - // doper types: numeric: ieee, rk - // string: string doper - // boolean - // error - int compare; - try { - if (o instanceof Boolean) // TODO: 1.5 use Boolean.compareTo - compare= ((Boolean) o).toString().compareTo(Boolean.valueOf(c.getBooleanVal()).toString()); - else if (o instanceof String) { - // use "matches" to handle wildcards - if (((String) o).toUpperCase().matches(c.getStringVal().toUpperCase())) - compare= 0; // equal or matches - else - compare= -1; // doesn't equal - //compare= ((String) o).toUpperCase().compareTo(c.getStringVal().toUpperCase()); - } else // it's a Double - compare= ((Double) o).compareTo(new Double(c.getDblVal())); - } catch (Exception e) { - ; // report error? - return false; - } - if (op.equals("=")) { - return (compare==0); + // doper types: numeric: ieee, rk + // string: string doper + // boolean + // error + int compare; + try { + if (o instanceof Boolean) // TODO: 1.5 use Boolean.compareTo + compare = ((Boolean) o).toString().compareTo(Boolean.valueOf(c.getBooleanVal()).toString()); + else if (o instanceof String) { + // use "matches" to handle wildcards + if (((String) o).toUpperCase().matches(c.getStringVal().toUpperCase())) + compare = 0; // equal or matches + else + compare = -1; // doesn't equal + //compare= ((String) o).toUpperCase().compareTo(c.getStringVal().toUpperCase()); + } else // it's a Double + compare = ((Double) o).compareTo(new Double(c.getDblVal())); + } catch (Exception e) { + // report error? + return false; + } + if (op.equals("=")) { + return (compare == 0); } else if (op.equals("<")) { - return (compare > 0); - } else if (op.equals("<=")) { - return (compare >= 0); - } else if (op.equals(">")) { - return (compare < 0); - } else if (op.equals(">=")) { - return (compare <= 0); - } else if (op.equals("<>")) { - return (compare!=0); - } - return false; + return (compare > 0); + } else if (op.equals("<=")) { + return (compare >= 0); + } else if (op.equals(">")) { + return (compare < 0); + } else if (op.equals(">=")) { + return (compare <= 0); + } else if (op.equals("<>")) { + return (compare != 0); + } + return false; } public static boolean compareCellValue(Object val, String compareval, String op) { - // doper types: numeric: ieee, rk - // string: string doper - // boolean - // error - int compare= -1; - try { - if (val instanceof Boolean) // TODO: 1.5 use Boolean.compareTo - compare= (Boolean.valueOf(compareval).toString()).compareTo(((Boolean)val).toString()); - else if (val instanceof String) { - if ((compareval.indexOf('?')==-1) && - (compareval.indexOf('*')==-1)) // if no wildcards - compare= (compareval).compareTo(((String) val).toUpperCase()); - else { // use "matches" to handle wildcards - if (((String) val).toUpperCase().matches(compareval)) - compare= 0; // equal or matches - else - compare= -1; // doesn't equal - } - }else if (val instanceof Number) // assume it's a number - compare= (new Double(compareval)).compareTo(new Double(((Number)val).doubleValue())); - else - return false; - } catch (Exception e) { - try { // try date compare - double dt= DateConverter.getXLSDateVal(new java.util.Date(compareval)); - compare= (new Double(dt)).compareTo(new Double(((Number)val).doubleValue())); - } catch (Exception ex) { // just try string compare - compare= compareval.compareTo(val.toString()); - } - } - if (op.equals("=")) { - return (compare==0); + // doper types: numeric: ieee, rk + // string: string doper + // boolean + // error + int compare = -1; + try { + if (val instanceof Boolean) // TODO: 1.5 use Boolean.compareTo + compare = (Boolean.valueOf(compareval).toString()).compareTo(((Boolean) val).toString()); + else if (val instanceof String) { + if ((compareval.indexOf('?') == -1) && + (compareval.indexOf('*') == -1)) // if no wildcards + compare = (compareval).compareTo(((String) val).toUpperCase()); + else { // use "matches" to handle wildcards + if (((String) val).toUpperCase().matches(compareval)) + compare = 0; // equal or matches + else + compare = -1; // doesn't equal + } + } else if (val instanceof Number) // assume it's a number + compare = (new Double(compareval)).compareTo(new Double(((Number) val).doubleValue())); + else + return false; + } catch (Exception e) { + try { // try date compare + double dt = DateConverter.getXLSDateVal(new java.util.Date(compareval)); + compare = (new Double(dt)).compareTo(new Double(((Number) val).doubleValue())); + } catch (Exception ex) { // just try string compare + compare = compareval.compareTo(val.toString()); + } + } + if (op.equals("=")) { + return (compare == 0); } else if (op.equals("<")) { - return (compare > 0); - } else if (op.equals("<=")) { - return (compare >= 0); - } else if (op.equals(">")) { - return (compare < 0); - } else if (op.equals(">=")) { - return (compare <= 0); - } else if (op.equals("<>")) { - return (compare!=0); - } - return false; + return (compare > 0); + } else if (op.equals("<=")) { + return (compare >= 0); + } else if (op.equals(">")) { + return (compare < 0); + } else if (op.equals(">=")) { + return (compare <= 0); + } else if (op.equals("<>")) { + return (compare != 0); + } + return false; } /* } else if (op.equals("<")) { @@ -129,96 +129,99 @@ else if (val instanceof String) { } else if (op.equals(">=")) { passes= (compare >= 0); } - */ - + */ + /** * translate Excel-style wildcards into Java wildcards in criteria string * plus handle qualified wildcard characters + percentages ... - * @param sCriteria criteria string - * @return tranformed criteria string + * + * @param sCriteria criteria string + * @return tranformed criteria string */ public static String translateWildcardsInCriteria(String sCriteria) { - String s= ""; // handle wildcards - boolean qualified= false; - boolean isalldigits= true; - for (int i= 0; i < sCriteria.length(); i++) { - char c= sCriteria.charAt(i); - if (c=='~') { - qualified= true; // don't add tilde unless certain it's not qualifying a * or ? - } else if (c=='*') { - if (!qualified) - s+="."; - s+=c; - } else if (c=='?') { - if (!qualified) - s+="."; - s+=c; - } else if (c=='%') { // translate percentage into decimals - if (isalldigits) { - s= "0" + s; - s= s.substring(s.length()-2, 2); - s= "." + s; - } - } else { - if (qualified) // really add the tilde - s+='~'; - s+=c; - qualified= false; - if (!Character.isDigit(c)) - isalldigits= false; - } - } - sCriteria= s.toUpperCase(); // matching is case-insensitive - return sCriteria; + String s = ""; // handle wildcards + boolean qualified = false; + boolean isalldigits = true; + for (int i = 0; i < sCriteria.length(); i++) { + char c = sCriteria.charAt(i); + if (c == '~') { + qualified = true; // don't add tilde unless certain it's not qualifying a * or ? + } else if (c == '*') { + if (!qualified) + s += "."; + s += c; + } else if (c == '?') { + if (!qualified) + s += "."; + s += c; + } else if (c == '%') { // translate percentage into decimals + if (isalldigits) { + s = "0" + s; + s = s.substring(s.length() - 2, 2); + s = "." + s; + } + } else { + if (qualified) // really add the tilde + s += '~'; + s += c; + qualified = false; + if (!Character.isDigit(c)) + isalldigits = false; + } + } + sCriteria = s.toUpperCase(); // matching is case-insensitive + return sCriteria; } - + /** * given a criteria string that starts with an operator, - * parse and return the index that the operator ends and the crtieria starts - * @param criteria - * @return int i position in criteria which actual criteria starts + * parse and return the index that the operator ends and the crtieria starts + * + * @param criteria + * @return int i position in criteria which actual criteria starts */ public static int splitOperator(String criteria) { - int i= 0; - for (; i < criteria.length(); i++) { - char c= criteria.charAt(i); - if (Character.isJavaIdentifierPart(c)) - break; - else if (c=='*' || c=='?') - break; - } - return i; + int i = 0; + for (; i < criteria.length(); i++) { + char c = criteria.charAt(i); + if (Character.isJavaIdentifierPart(c)) + break; + else if (c == '*' || c == '?') + break; + } + return i; } - + /** * takes a Reference Type Ptg and deferences and PtgNames, etc. * to return a PtgArea + * * @param p * @return */ - public static PtgArea getRange(Ptg p) - throws IllegalArgumentException { - if (p instanceof PtgArea) - return (PtgArea) p; - if(p instanceof PtgName) { // get source range - Ptg[] pr= null; - try{ - pr=((PtgName) p).getName().getCellRangePtgs(); - return (PtgArea) pr[0]; - } catch (Exception e) { - try{ // if it's a PtgRef, convert to a PtgArea - if (!(pr[0] instanceof PtgArea) && pr[0] instanceof PtgRef) { - PtgArea pa= new PtgArea(); - pa.setParentRec(pr[0].getParentRec()); - pa.setLocation(pr[0].getLocation()); - return pa; - } else - throw new IllegalArgumentException("Expected a reference-type operand"); - } catch (Exception ex) { - throw new IllegalArgumentException("Expected a reference-type operand"); - } - } - } - return null; + public static PtgArea getRange(Ptg p) + throws IllegalArgumentException { + if (p instanceof PtgArea) + return (PtgArea) p; + if (p instanceof PtgName) { // get source range + Ptg[] pr = null; + try { + pr = ((PtgName) p).getName().getCellRangePtgs(); + return (PtgArea) pr[0]; + } catch (Exception e) { + try { // if it's a PtgRef, convert to a PtgArea + if (!(pr[0] instanceof PtgArea) && pr[0] instanceof PtgRef) { + PtgArea pa = new PtgArea(); + pa.setParentRec(pr[0].getParentRec()); + pa.setLocation(pr[0].getLocation()); + return pa; + } else + throw new IllegalArgumentException("Expected a reference-type operand"); + } catch (Exception ex) { + throw new IllegalArgumentException("Expected a reference-type operand"); + } + } + } + return null; } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.java b/src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.java index 69bddf9..f721b21 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.java +++ b/src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,16 +24,14 @@ /** * Handles circular reference calls within a calculation - * - * */ public class CircularReferenceException extends CalculationException { - public CircularReferenceException(byte error) { - super(error); - } + public CircularReferenceException(byte error) { + super(error); + } - public String getName(){ - return "#CIR_ERR!"; - } + public String getName() { + return "#CIR_ERR!"; + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.java index 309838c..240f105 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,13 +24,12 @@ /** - DDEExternalCalculator is a collection of static methods that operate - as the Microsoft Excel function calls do. - - All methods are called with an array of ptg's, which are then - acted upon. A Ptg of the type that makes sense (ie boolean, number) - is returned after each method. -*/ -public class DDEExternalCalculator -{ + * DDEExternalCalculator is a collection of static methods that operate + * as the Microsoft Excel function calls do. + *

                + * All methods are called with an array of ptg's, which are then + * acted upon. A Ptg of the type that makes sense (ie boolean, number) + * is returned after each method. + */ +public class DDEExternalCalculator { } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.java index c26d501..fd2c7f4 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,1170 +22,1176 @@ */ package io.starter.formats.XLS.formulas; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Vector; - +import io.starter.formats.XLS.ReferenceTracker; import io.starter.toolkit.Logger; -import io.starter.formats.XLS.*; +import java.util.*; /** - DatabaseCalculator is a collection of static methods that operate - as the Microsoft Excel function calls do. + * DatabaseCalculator is a collection of static methods that operate + * as the Microsoft Excel function calls do. + *

                + * All methods are called with an array of ptg's, which are then + * acted upon. A Ptg of the type that makes sense (ie boolean, number) + * is returned after each method. + *

                + *

                + * Database and List Management functions + * Microsoft Excel includes worksheet functions that analyze data stored in lists or databases. + * Each of these functions, referred to collectively as the Dfunctions, uses three arguments: database, field, and criteria. + *

                + * These arguments refer to the worksheet ranges that are used by the function. + *

                + * DAVERAGE Returns the average of selected database entries + *

                + * DCOUNT Counts the cells that contain numbers in a database + *

                + * DCOUNTA Counts nonblank cells in a database + *

                + * DGET Extracts from a database a single record that matches the specified criteria + *

                + * DMAX Returns the maximum value from selected database entries + *

                + * DMIN Returns the minimum value from selected database entries + *

                + * DPRODUCT Multiplies the values in a particular field of records that match the criteria in a database + *

                + * DSTDEV Estimates the standard deviation based on a sample of selected database entries + *

                + * DSTDEVP Calculates the standard deviation based on the entire population of selected database entries + *

                + * DSUM Adds the numbers in the field column of records in the database that match the criteria + *

                + * DVAR Estimates variance based on a sample from selected database entries + *

                + * DVARP Calculates variance based on the entire population of selected database entries + *

                + * GETPIVOTDATA Returns data stored in a PivotTable + *

                + *

                + * ABOUT DB + *

                + * All Database Formulas take 3 arguments: + * Database is the range of cells that makes up the list or database. + *

                + * A database is a list of related data in which rows of related information + * are records, and columns of data are fields. + *

                + * The first row of the list contains labels for each column. + * Field indicates which column is used in the function. + * Field can be given as text with the column label + * enclosed between double quotation marks, such as "Age" or "Yield," + * or as a number that represents the position of the column within the + * list: 1 for the first column, 2 for the second column, and so on. + *

                + * Criteria is the range of cells that contains the conditions you specify. + *

                + * You can use any range for the criteria argument, * as long as it + * includes at least one column label and at least one cell below the column + * label for specifying a condition for the column. + *

                + * Make sure the criteria range does not overlap the list. + *

                + *

                + *

                + * TODO To change the template for this generated type comment go to + * Window - Preferences - Java - Code Style - Code Templates + */ +public class DatabaseCalculator { + public static boolean DEBUG = false; - All methods are called with an array of ptg's, which are then - acted upon. A Ptg of the type that makes sense (ie boolean, number) - is returned after each method. + /** + * Fetch a DB from the cache or create a new one + *

                + *

                + * Dbs store Cell refs... + * + * @param operands + * @return + */ + private static DB getDb(Ptg operands) { + ReferenceTracker DBcache = operands.getParentRec().getWorkBook().getRefTracker(); + //gonna try never caching this... painful, but if we dont' + if (DBcache.getListDBs().get(operands.toString()) != null) { + //Logger.logInfo("getDB: " + operands.toString()+ "using cache."); + return (DB) DBcache.getListDBs().get(operands.toString()); + } + //} + // create new + //Logger.logErr("getDB: " + operands.toString()+ "NOT cached."); + Ptg[] dbrange = PtgCalculator.getAllComponents(operands); + DB ret = DB.parseList(dbrange); + DBcache.getListDBs().put(operands.toString(), ret); + return ret; + } -Database and List Management functions -Microsoft Excel includes worksheet functions that analyze data stored in lists or databases. -Each of these functions, referred to collectively as the Dfunctions, uses three arguments: database, field, and criteria. + private static Criteria getCriteria(Ptg operands) { + ReferenceTracker DBcache = operands.getParentRec().getWorkBook().getRefTracker(); - These arguments refer to the worksheet ranges that are used by the function. + // test without cache + if (DBcache.getCriteriaDBs().get(operands.toString()) != null) { + //Logger.logInfo("getCriteria: " + operands.toString()+ "using cache."); + return (Criteria) DBcache.getCriteriaDBs().get(operands.toString()); + } + //Logger.logErr("getCriteria: " + operands.toString()+ "NOT cached."); + Ptg[] criteria = PtgCalculator.getAllComponents(operands); + Criteria ret = Criteria.parseCriteria(criteria); + DBcache.getCriteriaDBs().put(operands.toString(), ret); + return ret; + } -DAVERAGE Returns the average of selected database entries + /** + * DAVERAGE Returns the average of selected database entries + */ + protected static Ptg calcDAverage(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + double average = 0; + int count = 0; -DCOUNT Counts the cells that contain numbers in a database + // this is the colname to match + String colname = operands[1].getValue().toString(); + for (int i = 0; i < db.rows.length; i++) { // loop thru all db rows + // check if current row passes criteria requirements + Ptg[] rwz = db.getRow(i); // slight optimization one less call to getRow -jm + if (crit.passes(colname, rwz, db)) { + // passes; now do action + Ptg vx = rwz[fNum]; + if (vx != null) { + try { + average += Double.parseDouble(vx.getValue().toString()); + count++; // if it can be parsed into a number, increment count + } catch (NumberFormatException exp) { + } + } + } + } + if (count > 0) average = average / count; + return new PtgNumber(average); + } -DCOUNTA Counts nonblank cells in a database + /** + * DCOUNT Counts the cells that contain numbers in a database + */ + protected static Ptg calcDCount(Ptg[] operands) { + if (operands.length != 3) + return new PtgErr(PtgErr.ERROR_NA); -DGET Extracts from a database a single record that matches the specified criteria + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); -DMAX Returns the maximum value from selected database entries + int count = 0; + int nrow = db.getNRows(); + // this is the colname to match + String colname = operands[1].getValue().toString(); + for (int i = 0; i < nrow; i++) { // loop thru all db rows + // check if current row passes criteria requirements + try { + Ptg[] rr = db.getRow(i); -DMIN Returns the minimum value from selected database entries + /* "passes" means that there is a matching + * cell in the row of the data db cells + * + */ + if (crit.passes(colname, rr, db)) { + // passes; now do action + Ptg cx = db.getCell(i, fNum); + String vtx = cx.getValue().toString(); + if (vtx != null) { + Double.parseDouble(vtx); + count++; // if it can be parsed into a number, increment count + } + } + } catch (NumberFormatException e) { + } + } + return new PtgNumber(count); + } -DPRODUCT Multiplies the values in a particular field of records that match the criteria in a database + /** + * DCOUNTA Counts nonblank cells in a database + */ + protected static Ptg calcDCountA(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); -DSTDEV Estimates the standard deviation based on a sample of selected database entries + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + int count = 0; -DSTDEVP Calculates the standard deviation based on the entire population of selected database entries + // this is the colname to match + String colname = operands[1].getValue().toString(); + for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + String s = db.getCell(i, fNum).getValue().toString(); + if (s != null && !s.trim().equals("")) + count++; // if field is not blank, increment count + } + } -DSUM Adds the numbers in the field column of records in the database that match the criteria + return new PtgNumber(count); + } -DVAR Estimates variance based on a sample from selected database entries + /** + * DGET Extracts from a database a single record that matches the specified criteria + */ + protected static Ptg calcDGet(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NULL); -DVARP Calculates variance based on the entire population of selected database entries + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + String val = ""; + int count = 0; -GETPIVOTDATA Returns data stored in a PivotTable + // this is the colname to match + String colname = operands[1].getValue().toString(); + for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + val = db.getCell(i, fNum).getValue().toString(); + count++; + } + } + if (count == 0) return new PtgErr(PtgErr.ERROR_VALUE); // no recs match + if (count > 1) return new PtgErr(PtgErr.ERROR_NUM); // if more than one record matches criteria + return new PtgStr(val); + } + /** + * DMAX Returns the maximum value from selected database entries + */ + protected static Ptg calcDMax(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - * ABOUT DB - * - * All Database Formulas take 3 arguments: - * Database is the range of cells that makes up the list or database. - * - * A database is a list of related data in which rows of related information - * are records, and columns of data are fields. - * - * The first row of the list contains labels for each column. - * Field indicates which column is used in the function. - * Field can be given as text with the column label - * enclosed between double quotation marks, such as "Age" or "Yield," - * or as a number that represents the position of the column within the - * list: 1 for the first column, 2 for the second column, and so on. - * - * Criteria is the range of cells that contains the conditions you specify. - * - * You can use any range for the criteria argument, * as long as it - * includes at least one column label and at least one cell below the column - * label for specifying a condition for the column. - * - * Make sure the criteria range does not overlap the list. + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + double max = Double.MIN_VALUE; + // this is the colname to match + String colname = operands[1].getValue().toString(); + for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + String vtx = db.getCell(i, fNum).getValue().toString(); + if (vtx != null) { + try { + if (vtx.length() > 0) max = Math.max(max, Double.parseDouble(vtx)); + } catch (NumberFormatException exp) { + } + } + } + } - * - * TODO To change the template for this generated type comment go to - * Window - Preferences - Java - Code Style - Code Templates - */ -public class DatabaseCalculator -{ - public static boolean DEBUG = false; - - /** Fetch a DB from the cache or create a new one - * - * - * Dbs store Cell refs... - * - * @param operands - * @return - */ - private static DB getDb(Ptg operands) { - ReferenceTracker DBcache = operands.getParentRec().getWorkBook().getRefTracker(); - - //gonna try never caching this... painful, but if we dont' - if(DBcache.getListDBs().get(operands.toString())!=null) { - //Logger.logInfo("getDB: " + operands.toString()+ "using cache."); - return (DB)DBcache.getListDBs().get(operands.toString()); - } - //} - // create new - //Logger.logErr("getDB: " + operands.toString()+ "NOT cached."); - Ptg[] dbrange= PtgCalculator.getAllComponents(operands); - DB ret = DB.parseList(dbrange); - DBcache.getListDBs().put(operands.toString(),ret); - return ret; - } - - private static Criteria getCriteria(Ptg operands) { - ReferenceTracker DBcache = operands.getParentRec().getWorkBook().getRefTracker(); - - // test without cache - if(DBcache.getCriteriaDBs().get(operands.toString())!=null) { - //Logger.logInfo("getCriteria: " + operands.toString()+ "using cache."); - return (Criteria)DBcache.getCriteriaDBs().get(operands.toString()); - } - //Logger.logErr("getCriteria: " + operands.toString()+ "NOT cached."); - Ptg[] criteria = PtgCalculator.getAllComponents(operands); - Criteria ret = Criteria.parseCriteria(criteria); - DBcache.getCriteriaDBs().put(operands.toString(),ret); - return ret; - } - -/** - * DAVERAGE Returns the average of selected database entries - * - */ - protected static Ptg calcDAverage(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NA); - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - double average= 0; - int count= 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i= 0; i < db.rows.length; i++) { // loop thru all db rows - // check if current row passes criteria requirements - Ptg[] rwz = db.getRow(i); // slight optimization one less call to getRow -jm - if (crit.passes(colname,rwz, db)) { - // passes; now do action - Ptg vx = rwz[fNum]; - if(vx!=null) { - try { - average+=Double.parseDouble(vx.getValue().toString()); - count++; // if it can be parsed into a number, increment count - }catch(NumberFormatException exp) {} - } - } - } - if (count>0) average= average/count; - return new PtgNumber(average); - } - -/** - * DCOUNT Counts the cells that contain numbers in a database - */ - protected static Ptg calcDCount(Ptg[] operands) { - if (operands.length!=3) - return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - - int count= 0; - int nrow = db.getNRows(); - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i= 0; i < nrow; i++) { // loop thru all db rows - // check if current row passes criteria requirements - try { - Ptg[] rr = db.getRow(i); - - /* "passes" means that there is a matching - * cell in the row of the data db cells - * - */ - if (crit.passes(colname,rr, db)) { - // passes; now do action - Ptg cx = db.getCell(i, fNum); - String vtx = cx.getValue().toString(); - if(vtx!=null) { - Double.parseDouble(vtx); - count++; // if it can be parsed into a number, increment count - } - } - } - catch (NumberFormatException e) {} - } - return new PtgNumber(count); - } - - /** DCOUNTA Counts nonblank cells in a database - */ - protected static Ptg calcDCountA(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - int count= 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i= 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname,db.getRow(i), db)) { - // passes; now do action - String s= db.getCell(i, fNum).getValue().toString(); - if (s!=null && !s.trim().equals("")) - count++; // if field is not blank, increment count - } - } - - return new PtgNumber(count); - } - - /** DGET Extracts from a database a single record that matches the specified criteria - */ - protected static Ptg calcDGet(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NULL); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - String val= ""; - int count= 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i= 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname,db.getRow(i), db)) { - // passes; now do action - val= db.getCell(i, fNum).getValue().toString(); - count++; - } - } - if (count==0) return new PtgErr(PtgErr.ERROR_VALUE); // no recs match - if (count > 1) return new PtgErr(PtgErr.ERROR_NUM); // if more than one record matches criteria - return new PtgStr(val); - } - - /** DMAX Returns the maximum value from selected database entries - */ - protected static Ptg calcDMax(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - double max= Double.MIN_VALUE; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i= 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname,db.getRow(i), db)) { - // passes; now do action - String vtx = db.getCell(i, fNum).getValue().toString(); - if(vtx!=null) { - try { - if(vtx.length()>0) max= Math.max(max, Double.parseDouble(vtx)); - }catch(NumberFormatException exp) {} - } - } - } - - if (max==Double.MIN_VALUE) max= 0; - return new PtgNumber(max); - } - - /** DMIN Returns the minimum value from selected database entries - */ - protected static Ptg calcDMin(Ptg[] operands) { - if (operands.length!=3) // sanity checks - return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - double min= Double.MAX_VALUE; - // this is the colname to match - String colnamx = operands[1].getValue().toString(); - - for (int i= 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - try { - Ptg[] rwz = db.getRow(i); - - if (crit.passes(colnamx,rwz, db)) { - // passes; now do action - try{ - Ptg dbx = db.getCell(i, fNum); - - if(dbx!=null){ - String dnb = dbx.getValue().toString(); - if(dnb!=null) { - if(dnb.length()>0) - min= Math.min(min, Double.parseDouble(dnb)); - } - } - }catch(Exception ex){ - ; // normal blanks etc. - } - } - }catch (NumberFormatException e) { } - } - if (min==Double.MAX_VALUE) min= 0; - return new PtgNumber(min); - } - - /** DPRODUCT Multiplies the values in a particular field of records that match the criteria in a database - */ - protected static Ptg calcDProduct(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - double product= 1; - // this is the colname to match - String colname = operands[1].getValue().toString(); - - for (int i= 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - try { - if (crit.passes(colname,db.getRow(i), db)) { - // passes; now do action - String fnx = db.getCell(i, fNum).getValue().toString(); - if(fnx!=null) - product*=Double.parseDouble(fnx); - } - } - catch (NumberFormatException e) {} - } - return new PtgNumber(product); - } - /** DSTDEV Estimates the standard deviation based on a sample of selected database entries - */ - protected static Ptg calcDStdDev(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - java.util.ArrayList vals= new java.util.ArrayList(); - double sum= 0; - int count= 0; - // this is the colname to match - String colname = operands[1].getValue().toString(); - - for (int i= 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname,db.getRow(i), db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if(fnx!=null) { - double x= Double.parseDouble(fnx); - sum+= x; - count++; - vals.add(Double.toString(x)); - } - } - catch (NumberFormatException e) {} - } - } - double stdev= 0; - if (count > 0) { - double average= sum/count; - // now have all values in vals - for (int i= 0; i < count; i++) { - double x= Double.parseDouble((String) vals.get(i)); - stdev+= Math.pow((x-average),2); - } - if (count > 1) count--; - stdev= Math.sqrt(stdev/count); - } - return new PtgNumber(stdev); - } -/** DSTDEVP Calculates the standard deviation based on the entire population of selected database entries - */ - protected static Ptg calcDStdDevP(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - java.util.ArrayList vals= new java.util.ArrayList(); - double sum= 0; - int count= 0; - // this is the colname to match - String colname = operands[1].getValue().toString(); - - for (int i= 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname,db.getRow(i), db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if(fnx!=null) { - double x= Double.parseDouble(fnx); - sum+= x; - count++; - vals.add(Double.toString(x)); - } - }catch (NumberFormatException e) {} - } - } - double stdevp= 0; - if (count > 0) { - double average= sum/count; - // now have all values in vals - for (int i= 0; i < count; i++) { - double x= Double.parseDouble((String) vals.get(i)); - stdevp+= Math.pow((x-average),2); - } - stdevp= Math.sqrt(stdevp/count); - } - return new PtgNumber(stdevp); - } - - /** DSUM Adds the numbers in the field column of records in the database that match the criteria - */ - protected static Ptg calcDSum(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - - int count= 0; - double sum = 0.0d; - int nrow = db.getNRows(); - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i= 0; i < nrow; i++) { // loop thru all db rows - // check if current row passes criteria requirements - try { - Ptg[] rr = db.getRow(i); - - /* "passes" means that there is a matching - * cell in the row of the data db cells - * - */ - if (crit.passes(colname,rr, db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if(fnx!=null) - sum+= Double.parseDouble(fnx); - } - catch (NumberFormatException e) {} - } - } - catch (NumberFormatException e) {} - } - return new PtgNumber(sum); - } - - - /** DVAR Estimates variance based on a sample from selected database entries - */ - protected static Ptg calcDVar(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - java.util.ArrayList vals= new java.util.ArrayList(); - double sum= 0; - int count= 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i= 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname,db.getRow(i), db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if(fnx!=null) { - double x= Double.parseDouble(db.getCell(i, fNum).toString()); - sum+= x; - count++; - vals.add(Double.toString(x)); - } - } - catch (NumberFormatException e) {} - } - } - double variance= 0; - if (count > 0) { - double average= sum/count; - // now have all values in vals - for (int i= 0; i < count; i++) { - double x= Double.parseDouble((String) vals.get(i)); - variance+= Math.pow((x-average),2); - } - if (count > 1) count--; - variance= variance/count; - } - return new PtgNumber(variance); - } - - /* DVARP Calculates variance based on the entire population of selected database entries - */ - protected static Ptg calcDVarP(Ptg[] operands) { - if (operands.length!=3) return new PtgErr(PtgErr.ERROR_NA); - - DB db= getDb(operands[0]); - Criteria crit= getCriteria(operands[2]); - if (db==null || crit==null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum= db.findCol(operands[1].getString().trim()); - if (fNum==-1) - return new PtgErr(PtgErr.ERROR_NUM); - java.util.ArrayList vals= new java.util.ArrayList(); - double sum= 0; - int count= 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i= 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname,db.getRow(i), db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if(fnx!=null) { - double x= Double.parseDouble(fnx); - sum+= x; - count++; - vals.add(Double.toString(x)); - } - } - catch (NumberFormatException e) {} - } - } - double varP= 0; - if (count > 0) { - double average= sum/count; - // now have all values in vals - for (int i= 0; i < count; i++) { - double x= Double.parseDouble((String) vals.get(i)); - varP+= Math.pow((x-average),2); - } - varP= varP/count; - } - return new PtgNumber(varP); - } -/* GETPIVOTDATA Returns data stored in a PivotTable - * - * - */ + if (max == Double.MIN_VALUE) max = 0; + return new PtgNumber(max); + } + + /** + * DMIN Returns the minimum value from selected database entries + */ + protected static Ptg calcDMin(Ptg[] operands) { + if (operands.length != 3) // sanity checks + return new PtgErr(PtgErr.ERROR_NA); + + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + double min = Double.MAX_VALUE; + // this is the colname to match + String colnamx = operands[1].getValue().toString(); + + for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows + // check if current row passes criteria requirements + try { + Ptg[] rwz = db.getRow(i); + + if (crit.passes(colnamx, rwz, db)) { + // passes; now do action + try { + Ptg dbx = db.getCell(i, fNum); + + if (dbx != null) { + String dnb = dbx.getValue().toString(); + if (dnb != null) { + if (dnb.length() > 0) + min = Math.min(min, Double.parseDouble(dnb)); + } + } + } catch (Exception ex) { + // normal blanks etc. + } + } + } catch (NumberFormatException e) { + } + } + if (min == Double.MAX_VALUE) min = 0; + return new PtgNumber(min); + } + + /** + * DPRODUCT Multiplies the values in a particular field of records that match the criteria in a database + */ + protected static Ptg calcDProduct(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); + + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + double product = 1; + // this is the colname to match + String colname = operands[1].getValue().toString(); + + for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows + // check if current row passes criteria requirements + try { + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + String fnx = db.getCell(i, fNum).getValue().toString(); + if (fnx != null) + product *= Double.parseDouble(fnx); + } + } catch (NumberFormatException e) { + } + } + return new PtgNumber(product); + } + + /** + * DSTDEV Estimates the standard deviation based on a sample of selected database entries + */ + protected static Ptg calcDStdDev(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); + + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + java.util.ArrayList vals = new java.util.ArrayList(); + double sum = 0; + int count = 0; + // this is the colname to match + String colname = operands[1].getValue().toString(); + + for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + try { + String fnx = db.getCell(i, fNum).getValue().toString(); + if (fnx != null) { + double x = Double.parseDouble(fnx); + sum += x; + count++; + vals.add(Double.toString(x)); + } + } catch (NumberFormatException e) { + } + } + } + double stdev = 0; + if (count > 0) { + double average = sum / count; + // now have all values in vals + for (int i = 0; i < count; i++) { + double x = Double.parseDouble((String) vals.get(i)); + stdev += Math.pow((x - average), 2); + } + if (count > 1) count--; + stdev = Math.sqrt(stdev / count); + } + return new PtgNumber(stdev); + } + + /** + * DSTDEVP Calculates the standard deviation based on the entire population of selected database entries + */ + protected static Ptg calcDStdDevP(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); + + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + java.util.ArrayList vals = new java.util.ArrayList(); + double sum = 0; + int count = 0; + // this is the colname to match + String colname = operands[1].getValue().toString(); + + for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + try { + String fnx = db.getCell(i, fNum).getValue().toString(); + if (fnx != null) { + double x = Double.parseDouble(fnx); + sum += x; + count++; + vals.add(Double.toString(x)); + } + } catch (NumberFormatException e) { + } + } + } + double stdevp = 0; + if (count > 0) { + double average = sum / count; + // now have all values in vals + for (int i = 0; i < count; i++) { + double x = Double.parseDouble((String) vals.get(i)); + stdevp += Math.pow((x - average), 2); + } + stdevp = Math.sqrt(stdevp / count); + } + return new PtgNumber(stdevp); + } + + /** + * DSUM Adds the numbers in the field column of records in the database that match the criteria + */ + protected static Ptg calcDSum(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); + + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + + int count = 0; + double sum = 0.0d; + int nrow = db.getNRows(); + // this is the colname to match + String colname = operands[1].getValue().toString(); + for (int i = 0; i < nrow; i++) { // loop thru all db rows + // check if current row passes criteria requirements + try { + Ptg[] rr = db.getRow(i); + + /* "passes" means that there is a matching + * cell in the row of the data db cells + * + */ + if (crit.passes(colname, rr, db)) { + // passes; now do action + try { + String fnx = db.getCell(i, fNum).getValue().toString(); + if (fnx != null) + sum += Double.parseDouble(fnx); + } catch (NumberFormatException e) { + } + } + } catch (NumberFormatException e) { + } + } + return new PtgNumber(sum); + } + + + /** + * DVAR Estimates variance based on a sample from selected database entries + */ + protected static Ptg calcDVar(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); + + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + java.util.ArrayList vals = new java.util.ArrayList(); + double sum = 0; + int count = 0; + + // this is the colname to match + String colname = operands[1].getValue().toString(); + for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + try { + String fnx = db.getCell(i, fNum).getValue().toString(); + if (fnx != null) { + double x = Double.parseDouble(db.getCell(i, fNum).toString()); + sum += x; + count++; + vals.add(Double.toString(x)); + } + } catch (NumberFormatException e) { + } + } + } + double variance = 0; + if (count > 0) { + double average = sum / count; + // now have all values in vals + for (int i = 0; i < count; i++) { + double x = Double.parseDouble((String) vals.get(i)); + variance += Math.pow((x - average), 2); + } + if (count > 1) count--; + variance = variance / count; + } + return new PtgNumber(variance); + } + + /* DVARP Calculates variance based on the entire population of selected database entries + */ + protected static Ptg calcDVarP(Ptg[] operands) { + if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); + + DB db = getDb(operands[0]); + Criteria crit = getCriteria(operands[2]); + if (db == null || crit == null) + return new PtgErr(PtgErr.ERROR_NUM); + int fNum = db.findCol(operands[1].getString().trim()); + if (fNum == -1) + return new PtgErr(PtgErr.ERROR_NUM); + java.util.ArrayList vals = new java.util.ArrayList(); + double sum = 0; + int count = 0; + + // this is the colname to match + String colname = operands[1].getValue().toString(); + for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + try { + String fnx = db.getCell(i, fNum).getValue().toString(); + if (fnx != null) { + double x = Double.parseDouble(fnx); + sum += x; + count++; + vals.add(Double.toString(x)); + } + } catch (NumberFormatException e) { + } + } + } + double varP = 0; + if (count > 0) { + double average = sum / count; + // now have all values in vals + for (int i = 0; i < count; i++) { + double x = Double.parseDouble((String) vals.get(i)); + varP += Math.pow((x - average), 2); + } + varP = varP / count; + } + return new PtgNumber(varP); + } + /* GETPIVOTDATA Returns data stored in a PivotTable + * + * + */ } /** - * * EXPLANATION of Database Formulas - * + *

                * Database is the range of cells that makes up the list or database. - * A database is a list of related data in which rows of related - * information are records, and columns of data are fields. - * The first row of the list contains labels for each column. - * + * A database is a list of related data in which rows of related + * information are records, and columns of data are fields. + * The first row of the list contains labels for each column. + *

                * Field indicates which column is used in the function. - * Field can be given as text with the column label enclosed between - * double quotation marks, such as "Age" or "Yield," or as a number - * that represents the position of the column within the list: - * - * 1 for the first column, - * 2 for the second column, and so on. - * - * NOTE: quotes around field text is optional: - * first row of columns and rows are field labels. - * - * Criteria is the range of cells that contains the conditions - * you specify. You can use any range for the criteria argument, - * as long as it includes at least one column label and at least one + * Field can be given as text with the column label enclosed between + * double quotation marks, such as "Age" or "Yield," or as a number + * that represents the position of the column within the list: + *

                + * 1 for the first column, + * 2 for the second column, and so on. + *

                + * NOTE: quotes around field text is optional: + * first row of columns and rows are field labels. + *

                + * Criteria is the range of cells that contains the conditions + * you specify. You can use any range for the criteria argument, + * as long as it includes at least one column label and at least one * cell below the column label for specifying a condition for the column. - -Example - - * + *

                + * Example */ class DB { - protected String[] colHeaders; - protected Ptg[][] rows; - - public DB(int nCols, int nRows) { - colHeaders= new String[nCols]; - // TODO: replace with PtgRef array! - rows= new Ptg[nRows][nCols]; - } - public int getNCols() { return colHeaders.length; } - public int getNRows() { return rows.length; } - - /** - * return the index of the col in the DB - * @param cname - * @return - */ - public int getCol(String cname){ - for(int t=0;t -1 && i < rows.length) - return rows[i]; - return null; - } - - /** return a col of ?? - * - * - * @param i - * @return - */ - public String getCol(int i) { - if (i > -1 && i < colHeaders.length) - return colHeaders[i]; - return null; - } - - public Ptg getCell(int row, int col) { - try { - return rows[row][col]; - } - catch (Exception e) { return null; } - } - - public int findCol(String f) { - for (int i= 0; i < colHeaders.length; i++) { - if (colHeaders[i].trim().equalsIgnoreCase(f)) { - return i; - } - } - try { - int j= Integer.parseInt(f); // one-based index into columns - return j-1; - } - catch (Exception e) { return -1; } - } - - /** Write some documentation here please... thanks! -jm - * - * - * @param dbrange - * @return - */ - public static DB parseList(Ptg[] dbrange) { - int prevCol= -1; - int nCols= 0, nRows= 0, maxRows= 0; - - // allocate the empty table for the dbrange - for (int i= 0; i < dbrange.length; i++) { - if (dbrange[i] instanceof PtgRef) { - PtgRef pref = (PtgRef) dbrange[i]; - int[] loc= pref.getIntLocation(); + protected String[] colHeaders; + protected Ptg[][] rows; + + public DB(int nCols, int nRows) { + colHeaders = new String[nCols]; + // TODO: replace with PtgRef array! + rows = new Ptg[nRows][nCols]; + } + + public int getNCols() { + return colHeaders.length; + } + + public int getNRows() { + return rows.length; + } + + /** + * return the index of the col in the DB + * + * @param cname + * @return + */ + public int getCol(String cname) { + for (int t = 0; t < colHeaders.length; t++) { + if (colHeaders[t].equalsIgnoreCase(cname)) { + return t; + } + } + return -1; + } + + /** + * return a row of DB ptgs + * + * @param i + * @return + */ + public Ptg[] getRow(int i) { + if (i > -1 && i < rows.length) + return rows[i]; + return null; + } + + /** + * return a col of ?? + * + * @param i + * @return + */ + public String getCol(int i) { + if (i > -1 && i < colHeaders.length) + return colHeaders[i]; + return null; + } + + public Ptg getCell(int row, int col) { + try { + return rows[row][col]; + } catch (Exception e) { + return null; + } + } + + public int findCol(String f) { + for (int i = 0; i < colHeaders.length; i++) { + if (colHeaders[i].trim().equalsIgnoreCase(f)) { + return i; + } + } + try { + int j = Integer.parseInt(f); // one-based index into columns + return j - 1; + } catch (Exception e) { + return -1; + } + } + + /** + * Write some documentation here please... thanks! -jm + * + * @param dbrange + * @return + */ + public static DB parseList(Ptg[] dbrange) { + int prevCol = -1; + int nCols = 0, nRows = 0, maxRows = 0; + + // allocate the empty table for the dbrange + for (int i = 0; i < dbrange.length; i++) { + if (dbrange[i] instanceof PtgRef) { + PtgRef pref = (PtgRef) dbrange[i]; + int[] loc = pref.getIntLocation(); // TODO: check rc sanity here - if (loc[1]!=prevCol) { // count # cols - prevCol= loc[1]; - nCols++; - nRows= 0; - }else { // count # rows - nRows++; - maxRows= Math.max(nRows, maxRows); - } - } - else return null; - } - - // now populate the table - DB dblist= new DB(nCols, maxRows); - prevCol= -1; - nCols= -1; - nRows= -1; - for (int i= 0; i < dbrange.length; i++) { - PtgRef db1 = (PtgRef)dbrange[i]; - int[] loc= db1.getIntLocation(); - Object vs= null; // 20081120 KSC: Must distinguish between blanks and 0's - if (!db1.isBlank()) - vs = db1.getValue(); - - // column headers - if (loc[1]!=prevCol) { - if (vs!=null) - dblist.colHeaders[++nCols]= vs.toString(); - prevCol= loc[1]; - nRows= 0; - }else { // get value Ptgs - try { - dblist.rows[nRows++][nCols]= db1; - } catch (ArrayIndexOutOfBoundsException e) { - ; // possible nCols==-1 - } - - } - } - return dblist; - } + if (loc[1] != prevCol) { // count # cols + prevCol = loc[1]; + nCols++; + nRows = 0; + } else { // count # rows + nRows++; + maxRows = Math.max(nRows, maxRows); + } + } else return null; + } + + // now populate the table + DB dblist = new DB(nCols, maxRows); + prevCol = -1; + nCols = -1; + nRows = -1; + for (int i = 0; i < dbrange.length; i++) { + PtgRef db1 = (PtgRef) dbrange[i]; + int[] loc = db1.getIntLocation(); + Object vs = null; // 20081120 KSC: Must distinguish between blanks and 0's + if (!db1.isBlank()) + vs = db1.getValue(); + + // column headers + if (loc[1] != prevCol) { + if (vs != null) + dblist.colHeaders[++nCols] = vs.toString(); + prevCol = loc[1]; + nRows = 0; + } else { // get value Ptgs + try { + dblist.rows[nRows++][nCols] = db1; + } catch (ArrayIndexOutOfBoundsException e) { + // possible nCols==-1 + } + + } + } + return dblist; + } } class Criteria extends DB { - public Criteria(int nCols, int nRows) { super(nCols, nRows); } - - public static Criteria parseCriteria(Ptg[] criteria) { - DB dblist= DB.parseList(criteria); - if (dblist==null) return null; - Criteria crit= new Criteria(dblist.getNCols(), dblist.getNRows()); - crit.colHeaders= dblist.colHeaders; - crit.rows= dblist.rows; - if (DatabaseCalculator.DEBUG) { - Logger.logInfo("\nCriteria:"); - for (int i= 0; i < crit.getNCols(); i++) { - Logger.logInfo("\t" + crit.getCol(i)); - } - for (int j= 0; j < crit.getNCols(); j++) { - for (int i= 0; i < crit.getNRows(); i++) { - Logger.logInfo("\t" + crit.getCell(i, j)); - } - } - } - return crit; - } - - // TODO: Handle formula criteria! - // TODO: To perform an operation on an entire column in a database, enter a blank line below the column labels in the criteria range - // TODO: Handle various EQUALS: currency, number ... - public boolean matches(String v, Object cx) { - boolean bMatches= false; - String c = ""; - - if(cx instanceof Ptg) - c = ((Ptg)cx).getValue().toString(); - else - c = cx.toString(); - - if (c==null || c.length()==0) - return false; - if (v==null) - return false; // 20070208 KSC: null means no match! - - // TODO: handle this using calc methods - - // relational - if (c.substring(0, 1).equals(">") ) { - try { - if (c.length() > 1 && c.substring(0,2).equals(">=")) { - c= c.substring(2); - bMatches= (Double.parseDouble(v)>=Double.parseDouble(c)); - }else { - c= c.substring(1); - bMatches= (Double.parseDouble(v)>Double.parseDouble(c)); - } - }catch (NumberFormatException e) {} - }else if (c.substring(0, 1).equals("<") ) { - try { - if (c.length() > 1 && c.substring(0,2).equals("<=")) { - c= c.substring(2); - bMatches= (Double.parseDouble(v)<=Double.parseDouble(c)); - }else { - c= c.substring(1); - bMatches= (Double.parseDouble(v)1 && ncols == 1) - crit_format = 0; - - // more than one column are AND searches (cola=x AND colb=y) - // ONE row of criteria is applied to N rows of data - if(nrows==1 && ncols > 1) - crit_format = 1; - - // more than one row and more than one column: - // OR's: (cola=x AND colb=y) || (cola=z AND colb=w) - // applied to each row of data - if(nrows>1 && ncols > 1) - crit_format = 2; - - if(nrows==1 && ncols == 1) - crit_format = 0; - - switch(crit_format){ - - case 0: - return criteriaCheck1( curRow,db); - - case 1: - return criteriaCheck2(field, curRow,db); - - case 2: - return criteriaCheck3(field, curRow,db); - - default: - return criteriaCheck3(field,curRow,db); - } - } - - /** - * for the current db row/column, see if all criteria for that column matches - * (criteria may run over many criteria rows & signifies an OR search) - * find db column that matches criteria col - * - * multiple rows of data one row of criteria - * - * age height - * <29 >99 - * - * age height - * 23 88 - * 21 99 - * 43 56 - * 23 56 - * 44 76 - * - * - * - * @param curRow - * @param db - * @return - */ - private boolean criteriaCheck1(Ptg[] curRow, DB db){ - boolean bColOK= false; - boolean bRowOK= true; - int nrows = getNRows(); - int ncols = getNCols(); - int ndbrows = db.getNRows(); - - String cl = getCol(0); - int j=db.findCol(cl); - if (j>=0) { - bColOK= false; // need one bColOK= true for it to pass - for (int k= 0; k < nrows && !bColOK; k++) { // - try{ - String v = curRow[j].getValue().toString(); - Ptg r = rows[k][0]; - String rv = r.getValue().toString(); - bColOK= matches(v, r); - - // fast succeed - if(bColOK) - return true; - - }catch(Exception ex){ - // Logger.logInfo("DBCalc"); // TODO: check that this is OK - } - } - if (!bColOK) - bRowOK= false; // if no criteria passes, row doesn't pass - } - return bRowOK; - } - - - /** - * - * There is only one row of criteria, - * but may be multiple criteria per field aka: - * - * type age age height - * blue >21 <50 44 - * - * - // [v1 v2 v3] - // [v2 v3 v4] - // ... - // [crit1 crit2 crit3] <- val must pass this - * - * - * - * @param field - * @param curRow - * @param db - * @return - */ - private boolean criteriaCheck2(String field, - Ptg[] curRow, - DB db){ - boolean pass = true; - for(int t=0;t21 >50 44 - * blue <30 <100 - * red >30 >40 99 - * - * - * AND criteria across criteria rows - * OR criteria down cols - * - * - * - * - * @param field - * @param curRow - * @param db - * @return - */ - private boolean criteriaCheck3(String field, - Ptg[] curRow, - DB db){ - boolean critRowMatch = false; - // for each value check all the criteria in a row - // multiple rows of criteria are combined - for(int x=0;x=0) { // matching col in dblist - // the current criteria matches on colname - Ptg curcrit = null; - if(rows[k].length>ncols){ // matched criteria cols and dbcols - curcrit = rows[k][coln]; - }else{ - Logger.logWarn("DatabaseCalculator.Criteria.criteriaCheck4: wrong criteria count for db value count"); - return false; - } - String rt =curcrit.getValue().toString(); - - // the current val is in the matching column - Ptg rx = curRow[coln]; - String mv =rx.getValue().toString(); - - // check field if this is a matching lookup - if((i==0)){ - if(!mv.equals(rt)) - return false; - }else{ // check criteria, criteria is fast fail - if(rt.equals("")){ // empty criteria is not false - - }else{ // for the current db row/column, see if all criteria for that column matches - //(criteria may run over many criteria rows & signifies an OR search) - bColOK= matches(rt, rx); - - if(!bColOK && nrows==1) // fast fail - return false; - - - if (bColOK) // fast succeed - return true; - } - } - }else { // see if it's a formula - defined to NOT match db column - // Logger.logWarn("DataBaseCalculator.Criteria.passes: no matching col in dblist."); - } - } - if (bColOK) - return true; // does this cut the fat? bRowOK= true; // if any row's column criteria passes, row passes - } - return bRowOK; - } - - private Map criteriaCache = null; - /** - * return cached array of criteria Ptgs for a given field - * - * TODO: cache reset - * - * - * @param field - * @return - */ - private List getCriteria(String field){ - if(true) //criteriaCache==null) - criteriaCache = new Hashtable(); // map of vector criteria - else - if(criteriaCache.get(field)!=null) - return (List)criteriaCache.get(field); - - List crits = new Vector(); - for(int x=0;x")) { + try { + if (c.length() > 1 && c.substring(0, 2).equals(">=")) { + c = c.substring(2); + bMatches = (Double.parseDouble(v) >= Double.parseDouble(c)); + } else { + c = c.substring(1); + bMatches = (Double.parseDouble(v) > Double.parseDouble(c)); + } + } catch (NumberFormatException e) { + } + } else if (c.substring(0, 1).equals("<")) { + try { + if (c.length() > 1 && c.substring(0, 2).equals("<=")) { + c = c.substring(2); + bMatches = (Double.parseDouble(v) <= Double.parseDouble(c)); + } else { + c = c.substring(1); + bMatches = (Double.parseDouble(v) < Double.parseDouble(c)); + } + } catch (NumberFormatException e) { + } + } else {// Equals + bMatches = (v.equalsIgnoreCase(c)); + } + return bMatches; + } + + /** + * "passes" means that there is a matching + * and valid (criteria passing) cell in the + * row of the data db cells + * + * @param field - the field or column that is being compared + * @param curRow - check the current row/col + * @param db - the db of vals + * @return + */ + public boolean passes(String field, Ptg[] curRow, DB db) { + int nrows = getNRows(); + int ncols = getNCols(); + + int crit_format = -1; // determine format of criteria table + // multiple rows of criteria for one column are OR searches + // on that column (col=x OR col=y OR col=z) + if (nrows > 1 && ncols == 1) + crit_format = 0; + + // more than one column are AND searches (cola=x AND colb=y) + // ONE row of criteria is applied to N rows of data + if (nrows == 1 && ncols > 1) + crit_format = 1; + + // more than one row and more than one column: + // OR's: (cola=x AND colb=y) || (cola=z AND colb=w) + // applied to each row of data + if (nrows > 1 && ncols > 1) + crit_format = 2; + + if (nrows == 1 && ncols == 1) + crit_format = 0; + + switch (crit_format) { + + case 0: + return criteriaCheck1(curRow, db); + + case 1: + return criteriaCheck2(field, curRow, db); + + case 2: + return criteriaCheck3(field, curRow, db); + + default: + return criteriaCheck3(field, curRow, db); + } + } + + /** + * for the current db row/column, see if all criteria for that column matches + * (criteria may run over many criteria rows & signifies an OR search) + * find db column that matches criteria col + *

                + * multiple rows of data one row of criteria + *

                + * age height + * <29 >99 + *

                + * age height + * 23 88 + * 21 99 + * 43 56 + * 23 56 + * 44 76 + * + * @param curRow + * @param db + * @return + */ + private boolean criteriaCheck1(Ptg[] curRow, DB db) { + boolean bColOK = false; + boolean bRowOK = true; + int nrows = getNRows(); + int ncols = getNCols(); + int ndbrows = db.getNRows(); + + String cl = getCol(0); + int j = db.findCol(cl); + if (j >= 0) { + bColOK = false; // need one bColOK= true for it to pass + for (int k = 0; k < nrows && !bColOK; k++) { // + try { + String v = curRow[j].getValue().toString(); + Ptg r = rows[k][0]; + String rv = r.getValue().toString(); + bColOK = matches(v, r); + + // fast succeed + if (bColOK) + return true; + + } catch (Exception ex) { + // Logger.logInfo("DBCalc"); // TODO: check that this is OK + } + } + if (!bColOK) + bRowOK = false; // if no criteria passes, row doesn't pass + } + return bRowOK; + } + + + /** + * There is only one row of criteria, + * but may be multiple criteria per field aka: + *

                + * type age age height + * blue >21 <50 44 + *

                + *

                + * // [v1 v2 v3] + * // [v2 v3 v4] + * // ... + * // [crit1 crit2 crit3] <- val must pass this + * + * @param field + * @param curRow + * @param db + * @return + */ + private boolean criteriaCheck2(String field, + Ptg[] curRow, + DB db) { + boolean pass = true; + for (int t = 0; t < curRow.length; t++) { + String valcheck = curRow[t].getValue().toString(); + // for each value check all the criteria + for (int x = 0; x < rows.length; x++) { + List r = this.getCriteria(db.colHeaders[t]); + Iterator tx = r.iterator(); + while (tx.hasNext()) { + Ptg cv = ((Ptg) tx.next()); + String vc = cv.getValue().toString(); + if (!vc.equals("")) { + pass = matches(valcheck, vc); + // fast fail is OK + if (!pass) + return false; + } + } + } + } + return pass; + } + + + /** + * othertimes we have a 2D criteria + *

                + * type age age height + * blue >21 >50 44 + * blue <30 <100 + * red >30 >40 99 + *

                + *

                + * AND criteria across criteria rows + * OR criteria down cols + * + * @param field + * @param curRow + * @param db + * @return + */ + private boolean criteriaCheck3(String field, + Ptg[] curRow, + DB db) { + boolean critRowMatch = false; + // for each value check all the criteria in a row + // multiple rows of criteria are combined + for (int x = 0; x < rows.length; x++) { + critRowMatch = true; // reset + // for each row of criteria, iterate criteria cols + for (int rs = 0; rs < this.colHeaders.length; rs++) { + String critField = this.colHeaders[rs]; + + List r = this.getCriteria(x, critField); + Iterator tx = r.iterator(); + int dv = db.getCol(critField); + String valcheck = curRow[dv].getValue().toString(); + // this criteria row may pass/fail subsequent rows may pass/fail + // only one has to pass OK to return true all crit in this row must pass + while (tx.hasNext() + && critRowMatch) { // stop if row failure + Ptg cv = ((Ptg) tx.next()); + String vc = cv.getValue().toString(); + + if (!vc.equals("")) { + critRowMatch = matches(valcheck, vc); + /* fast fail is not OK because we + * may pass one row OR another + * AND criteria across criteria rows + * OR criteria down cols + */ + } + } + } + if (critRowMatch) // fast succeed here, a row passed + return true; + } + return critRowMatch; + } + + + /** + * equal number of data rows/cols and criteria rows/cols + *

                + * compare each criteria cell with each corresponding db cell + * + * @param field + * @param curRow + * @param db + * @return + */ + private boolean criteriaCheck4(String field, Ptg[] curRow, DB db) { + boolean bColOK = false; + boolean bRowOK = false; + int nrows = getNRows(); + int ncols = getNCols(); + + for (int k = 0; k < nrows && !bRowOK; k++) { // for each row of criteria + // for each col check for valid criteria + for (int i = 0; i < ncols; i++) { + // find db column that matches criteria column + // String coli = getCol(i); // get the col + + int coln = db.findCol(field); // which db col is this? + + if (coln >= 0) { // matching col in dblist + // the current criteria matches on colname + Ptg curcrit = null; + if (rows[k].length > ncols) { // matched criteria cols and dbcols + curcrit = rows[k][coln]; + } else { + Logger.logWarn("DatabaseCalculator.Criteria.criteriaCheck4: wrong criteria count for db value count"); + return false; + } + String rt = curcrit.getValue().toString(); + + // the current val is in the matching column + Ptg rx = curRow[coln]; + String mv = rx.getValue().toString(); + + // check field if this is a matching lookup + if ((i == 0)) { + if (!mv.equals(rt)) + return false; + } else { // check criteria, criteria is fast fail + if (rt.equals("")) { // empty criteria is not false + + } else { // for the current db row/column, see if all criteria for that column matches + //(criteria may run over many criteria rows & signifies an OR search) + bColOK = matches(rt, rx); + + if (!bColOK && nrows == 1) // fast fail + return false; + + + if (bColOK) // fast succeed + return true; + } + } + } else { // see if it's a formula - defined to NOT match db column + // Logger.logWarn("DataBaseCalculator.Criteria.passes: no matching col in dblist."); + } + } + if (bColOK) + return true; // does this cut the fat? bRowOK= true; // if any row's column criteria passes, row passes + } + return bRowOK; + } + + private Map criteriaCache = null; + + /** + * return cached array of criteria Ptgs for a given field + *

                + * TODO: cache reset + * + * @param field + * @return + */ + private List getCriteria(String field) { + if (true) //criteriaCache==null) + criteriaCache = new Hashtable(); // map of vector criteria + else if (criteriaCache.get(field) != null) + return (List) criteriaCache.get(field); + + List crits = new Vector(); + for (int x = 0; x < rows.length; x++) { + for (int t = 0; t < colHeaders.length; t++) { + if (colHeaders[t].equals(field)) { + crits.add(rows[x][t]); + } + } + } + criteriaCache.put(field, crits); + return crits; + } + + /** + * return cached array of criteria Ptgs for a given field + *

                + * TODO: cache reset + * + * @param field + * @return + */ + private List getCriteria(int critRow, String field) { + List crits = new Vector(); + if ((critRow != -1)) { // option to return criteria per row + for (int t = 0; t < colHeaders.length; t++) { + if (colHeaders[t].equals(field)) { + crits.add(rows[critRow][t]); + } + } + } + return crits; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java index 6790102..8e0338b 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,610 +23,611 @@ package io.starter.formats.XLS.formulas; //import java.text.SimpleDateFormat; -import java.util.Date; + import io.starter.OpenXLS.DateConverter; -import io.starter.OpenXLS.WorkBookHandle; import java.util.ArrayList; -import java.util.GregorianCalendar; import java.util.Calendar; -import java.util.Locale; +import java.util.GregorianCalendar; import java.util.TimeZone; /** - DateTimeCalculator is a collection of static methods that operate - as the Microsoft Excel function calls do. - - All methods are called with an array of ptg's, which are then - acted upon. A Ptg of the type that makes sense (ie boolean, number) - is returned after each method. -*/ - -public class DateTimeCalculator -{ - /** - * utliity that takes either a PtgStr or a Number and converts it into a calendar - * for use in below functions - * @return - */ - private static GregorianCalendar getDateFromPtg(Ptg op) { - Object o; - if (op instanceof PtgStr) - o= calcDateValue(new Ptg[] {op}).getValue(); - else if (op instanceof PtgRef) { - o= op.getValue(); - if (o instanceof String) - o= calcDateValue(new Ptg[] {new PtgStr(o.toString())}).getValue(); - } else if (op instanceof PtgName) { - o= ((PtgName)op).getValue(); //getComponents()[0]; - o= op.getValue(); - if (o instanceof String) - o= calcDateValue(new Ptg[] {new PtgStr(o.toString())}).getValue(); - } else - o= op.getValue(); - - return(GregorianCalendar)DateConverter.getCalendarFromNumber(o); - } - /** - * DATE - * @param operands - * @return - */ - protected static Ptg calcDate(Ptg[] operands){ - long[] alloperands = PtgCalculator.getLongValueArray(operands); - if (alloperands.length != 3)return PtgCalculator.getError(); - int year = (int)alloperands[0]; - int month = (int)alloperands[1]; - month = month - 1; - int day = (int)alloperands[2]; - GregorianCalendar c = new GregorianCalendar(year, month, day); - double date = DateConverter.getXLSDateVal(c); - int i = (int) date; - PtgInt pi = new PtgInt(i); - return pi; - } - - /** - * DATEVALUE - Returns the serial number of the date represented by date_text. Use DATEVALUE to convert a date represented by text to a serial number. - - Syntax - DATEVALUE(date_text) - - Date_text is text that represents a date in a Microsoft Excel date format. For example, "1/30/2008" or "30-Jan-2008" are text strings - within quotation marks that represent dates. Using the default date system in Excel for Windows, - date_text must represent a date from January 1, 1900, to December 31, 9999. Using the default date system in Excel for the Macintosh, - date_text must represent a date from January 1, 1904, to December 31, 9999. DATEVALUE returns the #VALUE! error value if date_text is out of this range. - - If the year portion of date_text is omitted, DATEVALUE uses the current year from your computer's built-in clock. Time information in date_text is ignored. - - Remarks - - Excel stores dates as sequential serial numbers so they can be used in calculations. By default, January 1, 1900 is serial number 1, and January 1, 2008 is serial number 39448 because it is 39,448 days after January 1, 1900. Excel for the Macintosh uses a different date system as its default. - Most functions automatically convert date values to serial numbers. - - * @param operands Ptg[] - * @return Ptg - */ - - protected static Ptg calcDateValue(Ptg[] operands){ - // TODO: there may be formats that need to be input - if (operands==null || operands[0].getString()==null) - return new PtgErr(PtgErr.ERROR_VALUE); - - String dateString= operands[0].getString(); - Double d= DateConverter.calcDateValue(dateString); - if (d==null) - return new PtgErr(PtgErr.ERROR_VALUE); - return new PtgNumber(d); - - } - /** - * DAY - * Return the day of the month - * - */ - protected static Ptg calcDay(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getError(); - try { - Object o = operands[0].getValue(); - GregorianCalendar c = (GregorianCalendar)DateConverter.getCalendarFromNumber(o); - int retdate = c.get(Calendar.DAY_OF_MONTH); - PtgInt pint = new PtgInt(retdate); - return pint; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * DAYS360 - * Calculate the difference between 2 dates based on a 360 - * day year, 12 mos, 30days each. - * - * first date is lower than second, otherwise a negative - * number is returned - * - * Seems pretty dumb to me, but what do I know? - */ - protected static Ptg calcDays360(Ptg[] operands){ - if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); - try { - Object o1 = operands[0].getValue(); - Object o2 = operands[1].getValue(); - GregorianCalendar dt1 = (GregorianCalendar)DateConverter.getCalendarFromNumber(o1); - GregorianCalendar dt2 = (GregorianCalendar)DateConverter.getCalendarFromNumber(o2); - int yr1 = dt1.get(Calendar.YEAR); - int yr2 = dt2.get(Calendar.YEAR); - int diff = yr2-yr1; - diff *= 360; // turn years to days. - int mo1 = dt1.get(Calendar.MONTH); - int mo2 = dt2.get(Calendar.MONTH); - int mos = 0; - if (mo2 > mo1){ - mos = mo2-mo1; - }else{ - diff -= 360; - while (mo2 != mo1){ - mos++; - mo1++; - if (mo1 == 12){ - mo1 = 0; - } - } - } - diff += mos*30; - int dy1 = dt1.get(Calendar.DAY_OF_MONTH); - int dy2 = dt2.get(Calendar.DAY_OF_MONTH); - if (dy2 > dy1){ - diff += dy2 - dy1; - }else{ - diff -= 30; - while (dy2 != dy1){ - diff++; - dy1++; - if (dy1 == 30){ - dy1 = 0; - } - } - } - PtgInt pint = new PtgInt(diff); - return pint; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - - /** - * EDATE - Returns the serial number that represents the date that is the indicated number of months - before or after a specified date (the start_date). - Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. - - EDATE(start_date,months) - - Start_date is a date that represents the start date. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - - Months is the number of months before or after start_date. A positive value for months yields a future date; a negative value yields a past date. - - If start_date is not a valid date, EDATE returns the #VALUE! error value. - If months is not an integer, it is truncated. - - */ - protected static Ptg calcEdate(Ptg[] operands){ - try { - GregorianCalendar startDate= getDateFromPtg(operands[0]); - int inc= (int) PtgCalculator.getLongValue(operands[1]); - int mm = startDate.get(Calendar.MONTH) + inc; - int y= startDate.get(Calendar.YEAR); - int d= startDate.get(Calendar.DAY_OF_MONTH); - if (mm < 0) { - mm+=12; // 0-based - y--; - } else if (mm > 11) { - mm-=12; - y++; - } - GregorianCalendar resultDate; - resultDate = new GregorianCalendar(y, mm, d); - double retdate = DateConverter.getXLSDateVal(resultDate); - int i = (int)retdate; - return new PtgInt(i); - } catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - - /** - * EOMONTH - Returns the serial number for the last day of the month that is the indicated number of months - before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on - the last day of the month. - - EOMONTH(start_date,months) - - Start_date is a date that represents the starting date. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - - Months is the number of months before or after start_date. A positive value for months yields a future date; a negative value yields a past date. - - If months is not an integer, it is truncated. - - If start_date is not a valid date, EOMONTH returns the #NUM! error value. - If start_date plus months yields an invalid date, EOMONTH returns the #NUM! error value. - */ - protected static Ptg calcEOMonth(Ptg[] operands){ - try { - GregorianCalendar startDate= getDateFromPtg(operands[0]); - int inc= operands[1].getIntVal(); - int mm = startDate.get(Calendar.MONTH) + inc; - int y= startDate.get(Calendar.YEAR); - int d= startDate.get(Calendar.DAY_OF_MONTH); - if (mm < 0) { - mm+=12; // 0-based - y--; - } else if (mm > 11) { - mm-=12; - y++; - } - if (mm==3 || mm==5 || mm==8 || mm==10) // 0-based - d= 30; - else if (mm==1) {// february - if (y%4==0) - d= 29; - else - d= 28; - } else - d= 31; - GregorianCalendar resultDate; - resultDate = new GregorianCalendar(y, mm, d); - double retdate = DateConverter.getXLSDateVal(resultDate); - int i = (int)retdate; - return new PtgInt(i); - } catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_NUM); - } - - - /** - * HOUR - Converts a serial number to an hour - */ - protected static Ptg calcHour(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getError(); - GregorianCalendar dt= getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.HOUR); - PtgInt pint = new PtgInt(retdate); - return pint; - - } - - /** - * MINUTE - Converts a serial number to a minute - * - */ - protected static Ptg calcMinute(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getError(); - try { - Object o = operands[0].getValue(); - GregorianCalendar dt = (GregorianCalendar)DateConverter.getCalendarFromNumber(o); - int retdate = dt.get(Calendar.MINUTE); - PtgInt pint = new PtgInt(retdate); - return pint; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * MONTH - Converts a serial number to a month - * - */ - protected static Ptg calcMonth(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getError(); - try { - GregorianCalendar dt= getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.MONTH); - retdate++; //month is ordinal - PtgInt pint = new PtgInt(retdate); - return pint; - } catch (Exception e) {} - return new PtgErr(PtgErr.ERROR_NA); - } - - - /** - * NETWORKDAYS - Returns the number of whole working days between start_date and end_date. Working days exclude weekends and any dates identified in holidays. - Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days worked during a specific term. - - NETWORKDAYS(start_date,end_date,holidays) - - Start_date is a date that represents the start date. - End_date is a date that represents the end date. - Holidays is an optional range of one or more dates to exclude from the working calendar, such as state and federal holidays and floating holidays. The list can be either a range of cells that contains the dates or an array constant of the serial numbers that represent the dates. - - Remarks - If any argument is not a valid date, NETWORKDAYS returns the #VALUE! error value. - */ - protected static Ptg calcNetWorkdays(Ptg[] operands) { - try { - ArrayList holidays= new ArrayList(); - GregorianCalendar startDate= getDateFromPtg(operands[0]); - GregorianCalendar endDate= getDateFromPtg(operands[1]); - if (operands.length > 2 && operands[2]!=null) { - if (operands[2] instanceof PtgRef) { - Ptg[] dts= ((PtgRef)operands[2]).getComponents(); - for (int i= 0; i < dts.length; i++) { - holidays.add(getDateFromPtg(dts[i])); - } - } else // assume it's a string or a number rep of a date - holidays.add(getDateFromPtg(operands[2])); - } - int count= 0; - boolean countUp= endDate.after(startDate); - while (!startDate.equals(endDate)) { - int d= startDate.get(Calendar.DAY_OF_WEEK); - if (d!=Calendar.SATURDAY && d!=Calendar.SUNDAY) { - boolean OKtoIncrement= true; - // check if on a holidays - if (holidays.size()>0) { - for (int i= 0; i < holidays.size(); i++) { - if (startDate.equals(((Calendar) holidays.get(i)))) { - OKtoIncrement= false; - break; - } - } - } - if (OKtoIncrement) { - count++; - } - } - if (countUp) - startDate.add(Calendar.DAY_OF_MONTH, 1); - else - startDate.add(Calendar.DAY_OF_MONTH, -1); - } - return new PtgInt(count); - } catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * NOW - Returns the serial number of the current date and time - * - */ - protected static Ptg calcNow(Ptg[] operands){ - GregorianCalendar gc = new GregorianCalendar(TimeZone.getDefault());//java.sql.Date dt = new java.sql.Date(); - // io.starter.toolkit.Logger.log(dt.toGMTString()); - double retdate = DateConverter.getXLSDateVal(gc); - return new PtgNumber(retdate); - } - - /** - SECOND - Converts a serial number to a second - */ - protected static Ptg calcSecond(Ptg[] operands){ - GregorianCalendar dt= getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.SECOND); - PtgInt pint = new PtgInt(retdate); - return pint; - - } - -/** - TIME - Returns the serial number of a particular time - takes 3 arguments, hour, minute, second; + * DateTimeCalculator is a collection of static methods that operate + * as the Microsoft Excel function calls do. + *

                + * All methods are called with an array of ptg's, which are then + * acted upon. A Ptg of the type that makes sense (ie boolean, number) + * is returned after each method. */ - protected static Ptg calcTime(Ptg[] operands){ - Ptg o; - if (operands[0] instanceof PtgStr) - o = calcDateValue(new Ptg[] {operands[0]}); - else - o= operands[0]; - int hour = o.getIntVal(); - int minute =operands[1].getIntVal(); - int second = operands[1].getIntVal(); - GregorianCalendar g = new GregorianCalendar(2000,1,1,hour,minute,second); - GregorianCalendar g2 = new GregorianCalendar(2000,1,1,0,0,0); - double dub = DateConverter.getXLSDateVal(g); - double dub2 = DateConverter.getXLSDateVal(g2); - dub -= dub2; - return new PtgNumber(dub); - } - /** - * TIMEVALUE - Converts a time in the form of text to a serial number - Returns the decimal number of the time represented by a text string. - The decimal number is a value ranging from 0 (zero) to 0.99999999, representing the times from 0:00:00 (12:00:00 AM) to 23:59:59 (11:59:59 P.M.). - TIMEVALUE(time_text) - Time_text is a text string that represents a time in any one of the Microsoft Excel time formats; for example, "6:45 PM" and "18:45" text strings within quotation marks that represent time. - */ - protected static Ptg calcTimevalue(Ptg[] operands){ - double result= 0; - try { - GregorianCalendar d= getDateFromPtg(operands[0]); - int h= d.get(Calendar.HOUR_OF_DAY); - int m= d.get(Calendar.MINUTE); - int s= d.get(Calendar.SECOND); - double t= h+(m/60.0)+(s/(60*60.0)); - result= (t/24.0); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - return new PtgNumber(result); - } - - - /** - * -TODAY - Returns the serial number of today's date - */ - protected static Ptg calcToday(Ptg[] operands){ - java.sql.Date dt = new java.sql.Date(System.currentTimeMillis()); - double retdate = DateConverter.getXLSDateVal(dt); - int i = (int)retdate; - return new PtgInt(i); - } - /** - * - * WEEKDAY Converts a serial number to a day of the week - * - */ - protected static Ptg calcWeekday(Ptg[] operands){ - if (operands.length != 1) return new PtgErr(PtgErr.ERROR_VALUE); - GregorianCalendar dt= getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.DAY_OF_WEEK); - PtgInt pint = new PtgInt(retdate); - return pint; - } - - /** - * WEEKNUM - Returns a number that indicates where the week falls numerically within a year. - - WEEKNUM(serial_num,return_type) - - Serial_num is a date within the week. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - - Return_type is a number that determines on which day the week begins. The default is 1. - - - * @param operands - * @return - */ - protected static Ptg calcWeeknum(Ptg[] operands){ - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - int returnType= 1; - GregorianCalendar dt= getDateFromPtg(operands[0]); - if (operands[1]!=null) { - returnType= operands[1].getIntVal(); - } - returnType-=1; // 1 is default, 2 =start on monday - int retdate = dt.get(Calendar.WEEK_OF_YEAR) - returnType; - PtgInt pint = new PtgInt(retdate); - return pint; - } - - /** - * WORKDAY - Returns a number that represents a date that is the indicated number of working days before or after a date (the starting date). - Working days exclude weekends and any dates identified as holidays. Use WORKDAY to exclude weekends or holidays when you calculate - invoice due dates, expected delivery times, or the number of days of work performed. - - WORKDAY(start_date,days,holidays) - - Important Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - Start_date is a date that represents the start date. - Days is the number of nonweekend and nonholiday days before or after start_date. A positive value for days yields a future date; a negative value yields a past date. - Holidays is an optional list of one or more dates to exclude from the working calendar, such as state and federal holidays and floating holidays. The list can be either a range of cells that contain the dates or an array constant of the serial numbers that represent the dates. - - Remarks - - If any argument is not a valid date, WORKDAY returns the #VALUE! error value. - If start_date plus days yields an invalid date, WORKDAY returns the #NUM! error value. - If days is not an integer, it is truncated. - - * @param operands - * @return - */ - protected static Ptg calcWorkday(Ptg[] operands){ - int days; - ArrayList holidays= new ArrayList(); - try { - GregorianCalendar dt= getDateFromPtg(operands[0]); - days= operands[1].getIntVal(); - if (operands.length>2 && operands[2]!=null) { // holidays - if (operands[2] instanceof PtgRef) { - Ptg[] dts= ((PtgRef)operands[2]).getComponents(); - for (int i= 0; i < dts.length; i++) { - holidays.add(getDateFromPtg(dts[i])); - } - } else // assume it's a string or a number rep of a date - holidays.add(getDateFromPtg(operands[2])); - } - for (int absDays= Math.abs(days); absDays>0; ) { - int d= dt.get(Calendar.DAY_OF_WEEK); - if (d!=Calendar.SATURDAY && d!=Calendar.SUNDAY) { - boolean OKtoIncrement= true; - // check if on a holidays - if (holidays.size()>0) { - for (int i= 0; i < holidays.size(); i++) { - if (dt.equals(((Calendar) holidays.get(i)))) { - OKtoIncrement= false; - break; - } - } - } - if (OKtoIncrement) { - absDays--; - } - } - if (days>0) - dt.add(Calendar.DAY_OF_MONTH, 1); - else - dt.add(Calendar.DAY_OF_MONTH, -1); - } - return new PtgNumber(DateConverter.getXLSDateVal(dt)); - - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * - * YEAR - Converts a serial number to a year - * - */ - protected static Ptg calcYear(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getError(); - try { - GregorianCalendar dt= getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.YEAR); - PtgInt pint = new PtgInt(retdate); - return pint; - } catch (Exception e) {} - return new PtgErr(PtgErr.ERROR_NA); - } - /** - * YEARFRAC function - Calculates the fraction of the year represented by the number of whole days between two dates (the start_date and the end_date). Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or obligations to assign to a specific term. - YEARFRAC(start_date,end_date,basis) - Important Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - Start_date is a date that represents the start date. - End_date is a date that represents the end date. - Basis is the type of day count basis to use. - 0 or omitted US (NASD) 30/360 - 1 Actual/actual - 2 Actual/360 - 3 Actual/365 - 4 European 30/360 - */ - protected static Ptg calcYearFrac(Ptg[] operands){ - long startDate, endDate; - try { - GregorianCalendar d= getDateFromPtg(operands[0]); - startDate = (new Double(DateConverter.getXLSDateVal(d))).longValue(); - d= getDateFromPtg(operands[1]); - endDate = (new Double(DateConverter.getXLSDateVal(d))).longValue(); - } catch (Exception e) { - //If start_date or end_date are not valid dates, YEARFRAC returns the #VALUE! error value. - return new PtgErr(PtgErr.ERROR_VALUE); - } - int basis= 0; - if (operands.length > 2) - basis= operands[2].getIntVal(); - //If basis < 0 or if basis > 4, YEARFRAC returns the #NUM! error value. - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - double yf= FinancialCalculator.yearFrac(basis, startDate, endDate); - if (yf < 0) yf*=-1; // =# days between dates, no negatives - return new PtgNumber(yf); - } + +public class DateTimeCalculator { + /** + * utliity that takes either a PtgStr or a Number and converts it into a calendar + * for use in below functions + * + * @return + */ + private static GregorianCalendar getDateFromPtg(Ptg op) { + Object o; + if (op instanceof PtgStr) + o = calcDateValue(new Ptg[]{op}).getValue(); + else if (op instanceof PtgRef) { + o = op.getValue(); + if (o instanceof String) + o = calcDateValue(new Ptg[]{new PtgStr(o.toString())}).getValue(); + } else if (op instanceof PtgName) { + o = op.getValue(); //getComponents()[0]; + o = op.getValue(); + if (o instanceof String) + o = calcDateValue(new Ptg[]{new PtgStr(o.toString())}).getValue(); + } else + o = op.getValue(); + + return (GregorianCalendar) DateConverter.getCalendarFromNumber(o); + } + + /** + * DATE + * + * @param operands + * @return + */ + protected static Ptg calcDate(Ptg[] operands) { + long[] alloperands = PtgCalculator.getLongValueArray(operands); + if (alloperands.length != 3) return PtgCalculator.getError(); + int year = (int) alloperands[0]; + int month = (int) alloperands[1]; + month = month - 1; + int day = (int) alloperands[2]; + GregorianCalendar c = new GregorianCalendar(year, month, day); + double date = DateConverter.getXLSDateVal(c); + int i = (int) date; + PtgInt pi = new PtgInt(i); + return pi; + } + + /** + * DATEVALUE + * Returns the serial number of the date represented by date_text. Use DATEVALUE to convert a date represented by text to a serial number. + *

                + * Syntax + * DATEVALUE(date_text) + *

                + * Date_text is text that represents a date in a Microsoft Excel date format. For example, "1/30/2008" or "30-Jan-2008" are text strings + * within quotation marks that represent dates. Using the default date system in Excel for Windows, + * date_text must represent a date from January 1, 1900, to December 31, 9999. Using the default date system in Excel for the Macintosh, + * date_text must represent a date from January 1, 1904, to December 31, 9999. DATEVALUE returns the #VALUE! error value if date_text is out of this range. + *

                + * If the year portion of date_text is omitted, DATEVALUE uses the current year from your computer's built-in clock. Time information in date_text is ignored. + *

                + * Remarks + *

                + * Excel stores dates as sequential serial numbers so they can be used in calculations. By default, January 1, 1900 is serial number 1, and January 1, 2008 is serial number 39448 because it is 39,448 days after January 1, 1900. Excel for the Macintosh uses a different date system as its default. + * Most functions automatically convert date values to serial numbers. + * + * @param operands Ptg[] + * @return Ptg + */ + + protected static Ptg calcDateValue(Ptg[] operands) { + // TODO: there may be formats that need to be input + if (operands == null || operands[0].getString() == null) + return new PtgErr(PtgErr.ERROR_VALUE); + + String dateString = operands[0].getString(); + Double d = DateConverter.calcDateValue(dateString); + if (d == null) + return new PtgErr(PtgErr.ERROR_VALUE); + return new PtgNumber(d); + + } + + /** + * DAY + * Return the day of the month + */ + protected static Ptg calcDay(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getError(); + try { + Object o = operands[0].getValue(); + GregorianCalendar c = (GregorianCalendar) DateConverter.getCalendarFromNumber(o); + int retdate = c.get(Calendar.DAY_OF_MONTH); + PtgInt pint = new PtgInt(retdate); + return pint; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * DAYS360 + * Calculate the difference between 2 dates based on a 360 + * day year, 12 mos, 30days each. + *

                + * first date is lower than second, otherwise a negative + * number is returned + *

                + * Seems pretty dumb to me, but what do I know? + */ + protected static Ptg calcDays360(Ptg[] operands) { + if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); + try { + Object o1 = operands[0].getValue(); + Object o2 = operands[1].getValue(); + GregorianCalendar dt1 = (GregorianCalendar) DateConverter.getCalendarFromNumber(o1); + GregorianCalendar dt2 = (GregorianCalendar) DateConverter.getCalendarFromNumber(o2); + int yr1 = dt1.get(Calendar.YEAR); + int yr2 = dt2.get(Calendar.YEAR); + int diff = yr2 - yr1; + diff *= 360; // turn years to days. + int mo1 = dt1.get(Calendar.MONTH); + int mo2 = dt2.get(Calendar.MONTH); + int mos = 0; + if (mo2 > mo1) { + mos = mo2 - mo1; + } else { + diff -= 360; + while (mo2 != mo1) { + mos++; + mo1++; + if (mo1 == 12) { + mo1 = 0; + } + } + } + diff += mos * 30; + int dy1 = dt1.get(Calendar.DAY_OF_MONTH); + int dy2 = dt2.get(Calendar.DAY_OF_MONTH); + if (dy2 > dy1) { + diff += dy2 - dy1; + } else { + diff -= 30; + while (dy2 != dy1) { + diff++; + dy1++; + if (dy1 == 30) { + dy1 = 0; + } + } + } + PtgInt pint = new PtgInt(diff); + return pint; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + + /** + * EDATE + * Returns the serial number that represents the date that is the indicated number of months + * before or after a specified date (the start_date). + * Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. + *

                + * EDATE(start_date,months) + *

                + * Start_date is a date that represents the start date. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + *

                + * Months is the number of months before or after start_date. A positive value for months yields a future date; a negative value yields a past date. + *

                + * If start_date is not a valid date, EDATE returns the #VALUE! error value. + * If months is not an integer, it is truncated. + */ + protected static Ptg calcEdate(Ptg[] operands) { + try { + GregorianCalendar startDate = getDateFromPtg(operands[0]); + int inc = (int) PtgCalculator.getLongValue(operands[1]); + int mm = startDate.get(Calendar.MONTH) + inc; + int y = startDate.get(Calendar.YEAR); + int d = startDate.get(Calendar.DAY_OF_MONTH); + if (mm < 0) { + mm += 12; // 0-based + y--; + } else if (mm > 11) { + mm -= 12; + y++; + } + GregorianCalendar resultDate; + resultDate = new GregorianCalendar(y, mm, d); + double retdate = DateConverter.getXLSDateVal(resultDate); + int i = (int) retdate; + return new PtgInt(i); + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + + /** + * EOMONTH + * Returns the serial number for the last day of the month that is the indicated number of months + * before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on + * the last day of the month. + *

                + * EOMONTH(start_date,months) + *

                + * Start_date is a date that represents the starting date. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + *

                + * Months is the number of months before or after start_date. A positive value for months yields a future date; a negative value yields a past date. + *

                + * If months is not an integer, it is truncated. + *

                + * If start_date is not a valid date, EOMONTH returns the #NUM! error value. + * If start_date plus months yields an invalid date, EOMONTH returns the #NUM! error value. + */ + protected static Ptg calcEOMonth(Ptg[] operands) { + try { + GregorianCalendar startDate = getDateFromPtg(operands[0]); + int inc = operands[1].getIntVal(); + int mm = startDate.get(Calendar.MONTH) + inc; + int y = startDate.get(Calendar.YEAR); + int d = startDate.get(Calendar.DAY_OF_MONTH); + if (mm < 0) { + mm += 12; // 0-based + y--; + } else if (mm > 11) { + mm -= 12; + y++; + } + if (mm == 3 || mm == 5 || mm == 8 || mm == 10) // 0-based + d = 30; + else if (mm == 1) {// february + if (y % 4 == 0) + d = 29; + else + d = 28; + } else + d = 31; + GregorianCalendar resultDate; + resultDate = new GregorianCalendar(y, mm, d); + double retdate = DateConverter.getXLSDateVal(resultDate); + int i = (int) retdate; + return new PtgInt(i); + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_NUM); + } + + + /** + * HOUR + * Converts a serial number to an hour + */ + protected static Ptg calcHour(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getError(); + GregorianCalendar dt = getDateFromPtg(operands[0]); + int retdate = dt.get(Calendar.HOUR); + PtgInt pint = new PtgInt(retdate); + return pint; + + } + + /** + * MINUTE + * Converts a serial number to a minute + */ + protected static Ptg calcMinute(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getError(); + try { + Object o = operands[0].getValue(); + GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(o); + int retdate = dt.get(Calendar.MINUTE); + PtgInt pint = new PtgInt(retdate); + return pint; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * MONTH + * Converts a serial number to a month + */ + protected static Ptg calcMonth(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getError(); + try { + GregorianCalendar dt = getDateFromPtg(operands[0]); + int retdate = dt.get(Calendar.MONTH); + retdate++; //month is ordinal + PtgInt pint = new PtgInt(retdate); + return pint; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_NA); + } + + + /** + * NETWORKDAYS + * Returns the number of whole working days between start_date and end_date. Working days exclude weekends and any dates identified in holidays. + * Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days worked during a specific term. + *

                + * NETWORKDAYS(start_date,end_date,holidays) + *

                + * Start_date is a date that represents the start date. + * End_date is a date that represents the end date. + * Holidays is an optional range of one or more dates to exclude from the working calendar, such as state and federal holidays and floating holidays. The list can be either a range of cells that contains the dates or an array constant of the serial numbers that represent the dates. + *

                + * Remarks + * If any argument is not a valid date, NETWORKDAYS returns the #VALUE! error value. + */ + protected static Ptg calcNetWorkdays(Ptg[] operands) { + try { + ArrayList holidays = new ArrayList(); + GregorianCalendar startDate = getDateFromPtg(operands[0]); + GregorianCalendar endDate = getDateFromPtg(operands[1]); + if (operands.length > 2 && operands[2] != null) { + if (operands[2] instanceof PtgRef) { + Ptg[] dts = operands[2].getComponents(); + for (int i = 0; i < dts.length; i++) { + holidays.add(getDateFromPtg(dts[i])); + } + } else // assume it's a string or a number rep of a date + holidays.add(getDateFromPtg(operands[2])); + } + int count = 0; + boolean countUp = endDate.after(startDate); + while (!startDate.equals(endDate)) { + int d = startDate.get(Calendar.DAY_OF_WEEK); + if (d != Calendar.SATURDAY && d != Calendar.SUNDAY) { + boolean OKtoIncrement = true; + // check if on a holidays + if (holidays.size() > 0) { + for (int i = 0; i < holidays.size(); i++) { + if (startDate.equals(holidays.get(i))) { + OKtoIncrement = false; + break; + } + } + } + if (OKtoIncrement) { + count++; + } + } + if (countUp) + startDate.add(Calendar.DAY_OF_MONTH, 1); + else + startDate.add(Calendar.DAY_OF_MONTH, -1); + } + return new PtgInt(count); + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * NOW + * Returns the serial number of the current date and time + */ + protected static Ptg calcNow(Ptg[] operands) { + GregorianCalendar gc = new GregorianCalendar(TimeZone.getDefault());//java.sql.Date dt = new java.sql.Date(); + // io.starter.toolkit.Logger.log(dt.toGMTString()); + double retdate = DateConverter.getXLSDateVal(gc); + return new PtgNumber(retdate); + } + + /** + * SECOND + * Converts a serial number to a second + */ + protected static Ptg calcSecond(Ptg[] operands) { + GregorianCalendar dt = getDateFromPtg(operands[0]); + int retdate = dt.get(Calendar.SECOND); + PtgInt pint = new PtgInt(retdate); + return pint; + + } + + /** + * TIME + * Returns the serial number of a particular time + * takes 3 arguments, hour, minute, second; + */ + protected static Ptg calcTime(Ptg[] operands) { + Ptg o; + if (operands[0] instanceof PtgStr) + o = calcDateValue(new Ptg[]{operands[0]}); + else + o = operands[0]; + int hour = o.getIntVal(); + int minute = operands[1].getIntVal(); + int second = operands[1].getIntVal(); + GregorianCalendar g = new GregorianCalendar(2000, 1, 1, hour, minute, second); + GregorianCalendar g2 = new GregorianCalendar(2000, 1, 1, 0, 0, 0); + double dub = DateConverter.getXLSDateVal(g); + double dub2 = DateConverter.getXLSDateVal(g2); + dub -= dub2; + return new PtgNumber(dub); + } + + /** + * TIMEVALUE + * Converts a time in the form of text to a serial number + * Returns the decimal number of the time represented by a text string. + * The decimal number is a value ranging from 0 (zero) to 0.99999999, representing the times from 0:00:00 (12:00:00 AM) to 23:59:59 (11:59:59 P.M.). + * TIMEVALUE(time_text) + * Time_text is a text string that represents a time in any one of the Microsoft Excel time formats; for example, "6:45 PM" and "18:45" text strings within quotation marks that represent time. + */ + protected static Ptg calcTimevalue(Ptg[] operands) { + double result = 0; + try { + GregorianCalendar d = getDateFromPtg(operands[0]); + int h = d.get(Calendar.HOUR_OF_DAY); + int m = d.get(Calendar.MINUTE); + int s = d.get(Calendar.SECOND); + double t = h + (m / 60.0) + (s / (60 * 60.0)); + result = (t / 24.0); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + return new PtgNumber(result); + } + + + /** + * TODAY + * Returns the serial number of today's date + */ + protected static Ptg calcToday(Ptg[] operands) { + java.sql.Date dt = new java.sql.Date(System.currentTimeMillis()); + double retdate = DateConverter.getXLSDateVal(dt); + int i = (int) retdate; + return new PtgInt(i); + } + + /** + * WEEKDAY Converts a serial number to a day of the week + */ + protected static Ptg calcWeekday(Ptg[] operands) { + if (operands.length != 1) return new PtgErr(PtgErr.ERROR_VALUE); + GregorianCalendar dt = getDateFromPtg(operands[0]); + int retdate = dt.get(Calendar.DAY_OF_WEEK); + PtgInt pint = new PtgInt(retdate); + return pint; + } + + /** + * WEEKNUM + * Returns a number that indicates where the week falls numerically within a year. + *

                + * WEEKNUM(serial_num,return_type) + *

                + * Serial_num is a date within the week. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + *

                + * Return_type is a number that determines on which day the week begins. The default is 1. + * + * @param operands + * @return + */ + protected static Ptg calcWeeknum(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + int returnType = 1; + GregorianCalendar dt = getDateFromPtg(operands[0]); + if (operands[1] != null) { + returnType = operands[1].getIntVal(); + } + returnType -= 1; // 1 is default, 2 =start on monday + int retdate = dt.get(Calendar.WEEK_OF_YEAR) - returnType; + PtgInt pint = new PtgInt(retdate); + return pint; + } + + /** + * WORKDAY + * Returns a number that represents a date that is the indicated number of working days before or after a date (the starting date). + * Working days exclude weekends and any dates identified as holidays. Use WORKDAY to exclude weekends or holidays when you calculate + * invoice due dates, expected delivery times, or the number of days of work performed. + *

                + * WORKDAY(start_date,days,holidays) + *

                + * Important Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + * Start_date is a date that represents the start date. + * Days is the number of nonweekend and nonholiday days before or after start_date. A positive value for days yields a future date; a negative value yields a past date. + * Holidays is an optional list of one or more dates to exclude from the working calendar, such as state and federal holidays and floating holidays. The list can be either a range of cells that contain the dates or an array constant of the serial numbers that represent the dates. + *

                + * Remarks + *

                + * If any argument is not a valid date, WORKDAY returns the #VALUE! error value. + * If start_date plus days yields an invalid date, WORKDAY returns the #NUM! error value. + * If days is not an integer, it is truncated. + * + * @param operands + * @return + */ + protected static Ptg calcWorkday(Ptg[] operands) { + int days; + ArrayList holidays = new ArrayList(); + try { + GregorianCalendar dt = getDateFromPtg(operands[0]); + days = operands[1].getIntVal(); + if (operands.length > 2 && operands[2] != null) { // holidays + if (operands[2] instanceof PtgRef) { + Ptg[] dts = operands[2].getComponents(); + for (int i = 0; i < dts.length; i++) { + holidays.add(getDateFromPtg(dts[i])); + } + } else // assume it's a string or a number rep of a date + holidays.add(getDateFromPtg(operands[2])); + } + for (int absDays = Math.abs(days); absDays > 0; ) { + int d = dt.get(Calendar.DAY_OF_WEEK); + if (d != Calendar.SATURDAY && d != Calendar.SUNDAY) { + boolean OKtoIncrement = true; + // check if on a holidays + if (holidays.size() > 0) { + for (int i = 0; i < holidays.size(); i++) { + if (dt.equals(holidays.get(i))) { + OKtoIncrement = false; + break; + } + } + } + if (OKtoIncrement) { + absDays--; + } + } + if (days > 0) + dt.add(Calendar.DAY_OF_MONTH, 1); + else + dt.add(Calendar.DAY_OF_MONTH, -1); + } + return new PtgNumber(DateConverter.getXLSDateVal(dt)); + + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * YEAR + * Converts a serial number to a year + */ + protected static Ptg calcYear(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getError(); + try { + GregorianCalendar dt = getDateFromPtg(operands[0]); + int retdate = dt.get(Calendar.YEAR); + PtgInt pint = new PtgInt(retdate); + return pint; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_NA); + } + + /** + * YEARFRAC function + * Calculates the fraction of the year represented by the number of whole days between two dates (the start_date and the end_date). Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or obligations to assign to a specific term. + * YEARFRAC(start_date,end_date,basis) + * Important Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + * Start_date is a date that represents the start date. + * End_date is a date that represents the end date. + * Basis is the type of day count basis to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + */ + protected static Ptg calcYearFrac(Ptg[] operands) { + long startDate, endDate; + try { + GregorianCalendar d = getDateFromPtg(operands[0]); + startDate = (new Double(DateConverter.getXLSDateVal(d))).longValue(); + d = getDateFromPtg(operands[1]); + endDate = (new Double(DateConverter.getXLSDateVal(d))).longValue(); + } catch (Exception e) { + //If start_date or end_date are not valid dates, YEARFRAC returns the #VALUE! error value. + return new PtgErr(PtgErr.ERROR_VALUE); + } + int basis = 0; + if (operands.length > 2) + basis = operands[2].getIntVal(); + //If basis < 0 or if basis > 4, YEARFRAC returns the #NUM! error value. + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + double yf = FinancialCalculator.yearFrac(basis, startDate, endDate); + if (yf < 0) yf *= -1; // =# days between dates, no negatives + return new PtgNumber(yf); + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.java index a09aca7..c51a252 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,8 +22,6 @@ */ package io.starter.formats.XLS.formulas; -import java.math.BigDecimal; - import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; @@ -37,1002 +35,994 @@ acted upon. A Ptg of the type that makes sense (ie boolean, number) is returned after each method. */ -public class EngineeringCalculator -{ - public static boolean DEBUG = false; +public class EngineeringCalculator { + public static boolean DEBUG = false; /* BESSELI Returns the modified Bessel function In(x) - + BESSELJ Returns the Bessel function Jn(x) - + BESSELK Returns the modified Bessel function Kn(x) - + BESSELY Returns the Bessel function Yn(x) */ -/** - BIN2DEC - Converts a binary number to decimal -*/ - protected static Ptg calcBin2Dec(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "BIN2DEC"); - String bString= operands[0].getString().trim(); - // 10 bits at most - if (bString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - // must det. manually if binary string is negative because parseInt does not - // handle two's complement input!!! - boolean bIsNegative= (bString.length()==10 && bString.substring(0,1).equalsIgnoreCase("1")); - - int dec= 0; - try { - dec= Integer.parseInt(bString, 2); - if (bIsNegative) dec-= 1024; // 2^10 (= signed 11 bits) - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - PtgNumber pnum = new PtgNumber(dec); - if (DEBUG) Logger.logInfo("Result from BIN2DEC= " + pnum.getVal()); - return pnum; - } - -/** - BIN2HEX - Converts a binary number to hexadecimal -*/ - protected static Ptg calcBin2Hex(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "BIN2HEX"); - String bString= operands[0].getString().trim(); - int places= 0; - if (operands.length > 1) - places= operands[1].getIntVal(); - - // 10 bits at most - if (bString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - // must det. manually if binary string is negative because parseInt does not - // handle two's complement input!!! - boolean bIsNegative= (bString.length()==10 && bString.substring(0,1).equalsIgnoreCase("1")); - - long dec; - String hString; - try { - dec= Long.parseLong(bString, 2); - if (bIsNegative) dec-=1024; // 2^10 (= signed 11 bits) - hString = Long.toHexString(dec).toUpperCase(); - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - hString= hString.substring(Math.max(hString.length()-10, 0)); - } else if (places > 0) { - if (hString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - hString= ("0000000000" + hString); // maximum= 10 bits - hString= hString.substring(hString.length()-places); - } - - PtgStr pstr= new PtgStr(hString); - if (DEBUG) Logger.logInfo("Result from BIN2HEX= " + pstr.getString()); - return pstr; - } - -/** - BIN2OCT - Converts a binary number to octal -*/ - protected static Ptg calcBin2Oct(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "Bin2Oct"); - String bString= operands[0].getString().trim(); - int places= 0; - if (operands.length > 1) - places= operands[1].getIntVal(); - - // 10 bits at most - if (bString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - // must det. manually if binary string is negative because parseInt does not - // handle two's complement input!!! - boolean bIsNegative= (bString.length()==10 && bString.substring(0,1).equalsIgnoreCase("1")); - - int dec; - String oString; - try { - dec= Integer.parseInt(bString, 2); - if (bIsNegative) dec-=1024; // 2^10 (= signed 11 bits) - oString = Integer.toOctalString(dec); - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - oString= oString.substring(Math.max(oString.length()-10, 0)); - } else if (places > 0) { - if (oString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - oString= ("0000000000" + oString); // maximum= 10 bits - oString= oString.substring(oString.length()-places); - } - PtgStr pstr= new PtgStr(oString); - if (DEBUG) Logger.logInfo("Result from BIN2OCT= " + pstr.getString()); - return pstr; - } -/** - * COMPLEX - Converts real and imaginary coefficients into a complex number - */ - protected static Ptg calcComplex(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "Complex"); - int real= operands[0].getIntVal(); - int imaginary= operands[1].getIntVal(); - String suffix= "i"; - if (operands.length > 2) { - suffix= operands[2].getString().trim(); - if (!(suffix.equals("i") || suffix.equals("j"))) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - String complexString= ""; - - // result: real + imaginary suffix - // real - imaginary suffix - // real (if imaginary==0) - // real + suffix (if imaginary==1) - // imaginary suffix (if (real==0) - if (real!=0) { - complexString= String.valueOf(real); - if (imaginary > 0) - complexString+= " + "; - } - - if (imaginary!=0) - complexString+= (Math.abs(imaginary)!=1?String.valueOf(imaginary):"") + suffix; - - if (complexString.equals("")) complexString= "0"; - - PtgStr pstr= new PtgStr(complexString); - if (DEBUG) Logger.logInfo("Result from COMPLEX= " + pstr.getString()); - return pstr; - } -/** - CONVERT - Converts a number from one measurement system to another -Weight and mass From_unit or to_unit -Gram "g" -Slug "sg" -Pound mass (avoirdupois) "lbm" -U (atomic mass unit) "u" -Ounce mass (avoirdupois) "ozm" - -Distance From_unit or to_unit -Meter "m" -Statute mile "mi" -Nautical mile "Nmi" -Inch "in" -Foot "ft" -Yard "yd" -Angstrom "ang" -Pica (1/72 in.) "Pica" - -Time From_unit or to_unit -Year "yr" -Day "day" -Hour "hr" -Minute "mn" -Second "sec" - -Pressure From_unit or to_unit -Pascal "Pa" -Atmosphere "atm" -mm of Mercury "mmHg" - -Force From_unit or to_unit -Newton "N" -Dyne "dyn" -Pound force "lbf" - -Energy From_unit or to_unit -Joule "J" -Erg "e" -Thermodynamic calorie "c" -IT calorie "cal" -Electron volt "eV" -Horsepower-hour "HPh" -Watt-hour "Wh" -Foot-pound "flb" -BTU "BTU" - -Power From_unit or to_unit -Horsepower "HP" -Watt "W" - -Magnetism From_unit or to_unit -Tesla "T" -Gauss "ga" - -Temperature From_unit or to_unit -Degree Celsius "C" -Degree Fahrenheit "F" -Degree Kelvin "K" - -Liquid measure From_unit or to_unit -Teaspoon "tsp" -Tablespoon "tbs" -Fluid ounce "oz" -Cup "cup" -U.S. pint "pt" -U.K. pint "uk_pt" -Quart "qt" -Gallon "gal" -Liter "l" - - -The following abbreviated unit prefixes can be prepended to any metric -from_unit or to_unit. - -Prefix Multiplier Abbreviation -exa 1E+18 "E" -peta 1E+15 "P" -tera 1E+12 "T" -giga 1E+09 "G" -mega 1E+06 "M" -kilo 1E+03 "k" -hecto 1E+02 "h" -dekao 1E+01 "e" -deci 1E-01 "d" -centi 1E-02 "c" -milli 1E-03 "m" -micro 1E-06 "u" -nano 1E-09 "n" -pico 1E-12 "p" -femto 1E-15 "f" -atto 1E-18 "a" - - -Remarks - -If the input data types are incorrect, CONVERT returns the #VALUE! error value. -If the unit does not exist, CONVERT returns the #N/A error value. -If the unit does not support an abbreviated unit prefix, CONVERT returns the #N/A error value. -If the units are in different groups, CONVERT returns the #N/A error value. -Unit names and prefixes are case-sensitive. - -*/ - private static int findUnits(String u, String[] units) { - boolean bFound= false; - for (int i= 0; i < units.length; i++) { - if (u.equals(units[i])) return i; - } - return -1; - } - private static double prefixMultiplier(String p, String[] prefixes) { - double multiplier= 1.0; - if (p.equals("")) return multiplier; - for (int i= 0; i < prefixes.length; i++) { - if (p.equals(prefixes[i])) { - switch (i) { - case 0: // "E" exa - multiplier= 1E+18; - break; - case 1: // "P" peta - multiplier= 1E+15; - break; - case 2: // "T" tera - multiplier= 1E+12; - break; - case 3: // "G" giga - multiplier= 1E+09; - break; - case 4: // "M" mega - multiplier= 1E+06; - break; - case 5: // "k" kilo - multiplier= 1E+03; - break; - case 6: // "h" hecto - multiplier= 1E+02; - break; - case 7: // "e" dekao - multiplier= 1E+01; - break; - case 8: // "d" deci - multiplier= 1E-01; - break; - case 9: // "c" centi - multiplier= 1E-02; - break; - case 10: // "m" milli - multiplier= 1E-03; - break; - case 11: // "u" micro - multiplier= 1E-06; - break; - case 12: // "n" nano - multiplier= 1E-09; - break; - case 13: // "p" pico - multiplier= 1E-12; - break; - case 14: // "f" femto - multiplier= 1E-15; - break; - case 15: // "a" atto - multiplier= 1E-18; - break; - } - return multiplier; - } - } - - return multiplier; - } - protected static Ptg calcConvert(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "CONVERT"); - double number= operands[0].getDoubleVal(); - String fromUnits= operands[1].getString().trim(); - String toUnits= operands[2].getString().trim(); - - String[] allUnits= { "g", "sg", "lbm", "u", "ozm", "m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica" , - "yr", "day", "hr", "mn", "sec", "Pa", "atm", "mmHg", "N", "dyn", "lbf", - "J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU", "HP", "W", "T", "ga", - "C", "F", "K","tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l"}; - String[] weightUnits= { "g", "sg", "lbm", "u", "ozm" }; - String[] distanceUnits= { "m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica" }; - String[] timeUnits= {"yr", "day", "hr", "mn", "sec"}; - String[] pressureUnits= {"Pa", "atm", "mmHg"}; - String[] forceUnits= {"N", "dyn", "lbf"}; - String[] energyUnits= {"J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU"}; - String[] powerUnits= {"HP", "W"}; - String[] magnetismUnits= {"T", "ga"}; - String[] temperatureUnits= {"C", "F", "K"}; - String[] liquidMeasureUnits= {"tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l"}; - - // for any metric unit, may be prefixed with - String fromPrefix= "", toPrefix= ""; - String[] metricPrefixes= {"E", "P", "T", "G", "M", "k", "h", "e", "d", "c", "m", "u", "n", "p", "f", "a"}; - - // first, see if fromUnits and toUnits are in list of acceptable units - if (findUnits(fromUnits, allUnits) < 0) { // doesn't match; strip prefix and try again - if (fromUnits.length() > 1) { - fromPrefix= fromUnits.substring(0, 1); - fromUnits= fromUnits.substring(1); - // now recheck - if (findUnits(fromUnits, allUnits) < 0) - return new PtgErr(PtgErr.ERROR_NA); - // make sure that prefix is acceptable - if (findUnits(fromPrefix, metricPrefixes) < 0) - return new PtgErr(PtgErr.ERROR_NA); - } - else return new PtgErr(PtgErr.ERROR_NA); - } - if (findUnits(toUnits, allUnits) < 0) {// doesn't match; strip prefix and try again - if (toUnits.length() > 1) { - toPrefix= toUnits.substring(0, 1); - toUnits= toUnits.substring(1); - // now recheck - if (findUnits(toUnits, allUnits) < 0) - return new PtgErr(PtgErr.ERROR_NA); - // make sure that prefix is acceptable - if (findUnits(toPrefix, metricPrefixes) < 0) - return new PtgErr(PtgErr.ERROR_NA); - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // at here, we know that the prefixes and units are found, but we don't know if they match or make sense ... - double result= 0; - double from= 0; - int i, j= -1; - - // WEIGHT conversion - if ((i=findUnits(fromUnits, weightUnits))>=0) { - j= findUnits(toUnits, weightUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - // { "g", "sg", "lbm", "u", "ozm" } - // get fromUnit in grams - switch (i) { - // from: - case 0: // "g" - from= number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "sg" - from= 14593.84241892870000000000*number; - break; - case 2: // "lbm" - from= 453.5923097488115*number; - break; - case 3: // "u" - from= 1.660531004604650E-24*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 4: // "ozm" - from= 28.349515207973* number; - break; - } - // now convert - switch (j) { - case 0: // "g" - result= from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "sg" - result= from*0.00006852205000534780; - break; - case 2: // "lbm" - result= from*0.00220462291469134000; - break; - case 3: // "u" - result= from*6.02217000000000000000E+23/prefixMultiplier(toPrefix, metricPrefixes);; - break; - case 4: // "ozm" - result= from*0.03527397180036270000; - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // DISTANCE conversion - if (j==-1 && (i=findUnits(fromUnits, distanceUnits))>=0) { - j= findUnits(toUnits, distanceUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - // "m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica" ; - // get fromUnits in m - switch (i) { - case 0: //"m" - from= number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: //"mi" - from= 1609.344000000000*number; - break; - case 2: //"Nmi" - from= 1852.000000000000*number; - break; - case 3: //"in" - from= 0.025400000000*number; - break; - case 4: // "ft" - from= 0.304800000000*number; - break; - case 5: // "yd" - from= 0.914400000300*number; - break; - case 6: // "ang" - from= 0.000000000100*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 7: // "Pica" - from= 0.00035277777777780000*number; - break; - } - switch (j) { - case 0: //"m" - result= from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: //"mi" - result= 0.00062137119223733400*from; - break; - case 2: //"Nmi" - result= 0.00053995680345572400*from; - break; - case 3: //"in" - result= 39.37007874015750000000*from; - break; - case 4: // "ft" - result= 3.28083989501312000000*from; - break; - case 5: // "yd" - result= 1.09361329797891000000*from; - break; - case 6: // "ang" - result= 10000000000.000000000000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 7: // "Pica" - result= 2834.64566929116000000000*from; - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // TIME conversion - if (j==-1 && (i=findUnits(fromUnits, timeUnits))>=0) { - j= findUnits(toUnits, timeUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - //"yr", "day", "hr", "mn", "sec" - switch (i) { - case 0: // "yr" - from= number; - break; - case 1: // "day" - from= 0.00273785078713210000*number; - break; - case 2: // "hr" - from= 0.00011407711613050400*number; - break; - case 3: // "mn" - from= 0.00000190128526884174*number; - break; - case 4: // "sec" - from= 0.00000003168808781403*number; - break; - } - switch (j) { - case 0: // "yr" - result= from; - break; - case 1: // "day" - result= 365.250000000000*from; - break; - case 2: // "hr" - result= 8766.000000000000*from; - break; - case 3: // "mn" - result= 525960.000000000000*from; - break; - case 4: // "sec" - result= 31557600.000000000000*from; - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // PRESSURE conversion - if (j==-1 && (i=findUnits(fromUnits, pressureUnits))>=0) { - j= findUnits(toUnits, pressureUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - //"Pa", "atm", "mmHg" - switch (i) { - case 0: // "Pa" - from= number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "atm" - from= 101324.99658300000000000000*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 2: // "mmHg" - from= 133.32236392500000000000*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - } - switch (j) { - case 0: // "Pa" - result= from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "atm" - result= 0.00000986923299998193*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 2: // "mmHg" - result= 0.00750061707998627000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // FORCE conversion - if (j==-1 && (i=findUnits(fromUnits, forceUnits))>=0) { - j= findUnits(toUnits, forceUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - //"N", "dyn", "lbf" - switch (i) { - case 0: // "N" - from= number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "dyn" - from= 0.000010000000*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 2: // "lbf" - from= 4.448222000000*number; - break; - } - switch (j) { - case 0: // "N" - result= from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "dyn" - result= 100000.000000000000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 2: // "lbf" - result= 0.22480892365533900000*from; - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // ENERGY conversion - if (j==-1 && (i=findUnits(fromUnits, energyUnits))>=0) { - j= findUnits(toUnits, energyUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - //"J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU" - switch (i) { - case 0: // "J" - from= number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "e" - from= 0.00000010000004806570*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 2: // "c" - from= 4.18399101363672000000*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 3: // "cal" - from= 4.18679484613929000000*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 4: // "eV" - from= 1.60217646E-19*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 5: // "HPh" - from= 2684517.41316170000000000000*number; - break; - case 6: // "Wh" - from= 3599.99820554720000000000*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 7: // "flb" - from= 0.04214000032364240000*number; - break; - case 8: // "BTU" - from= 1055.05813786749000000000*number; - break; - } - switch (j) { - case 0: // "J" - result= from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "e" - result= 9999995.19343231000000000000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 2: // "c" - result= 0.23900624947346700000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 3: // "cal" - result= 0.23884619064201700000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 4: // "eV" - result= 6241457000000000000.00000000000000000000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 5: // "HPh" - result= 0.00000037250643080100*from; - break; - case 6: // "Wh" - result= 0.00027777791623871100*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 7: // "flb" - result= 23.73042221926510000000*from; - break; - case 8: // "BTU" - result= 0.00094781506734901500*from; - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // POWER conversion - if (j==-1 && (i=findUnits(fromUnits, powerUnits))>=0) { - j= findUnits(toUnits, powerUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - //"HP", "W" - switch (i) { - case 0: // "HP" - from= number; - break; - case 1: // "W" - from= 0.00134102006031908000*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - } - switch (j) { - case 0: // "HP" - result= from; - break; - case 1: // "W" - result= 745.70100000000000000000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // MAGNETISM conversion - if (j==-1 && (i=findUnits(fromUnits, magnetismUnits))>=0) { - j= findUnits(toUnits, magnetismUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - //"T", "ga" - switch (i) { - case 0: // "T" - from= number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "ga" - from= 0.000100000000*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - } - switch (j) { - case 0: // "T" - result= from/prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "ga" - result= 10000.000000000000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // TEMPERATURE conversion - if (j==-1 && (i=findUnits(fromUnits, temperatureUnits))>=0) { - j= findUnits(toUnits, temperatureUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - //"C", "F", "K" - switch (i) { - case 0: // "C" - from= number; - break; - case 1: // "F" - from= (number-32)/1.8; - break; - case 2: // "K" - from= (number*prefixMultiplier(fromPrefix, metricPrefixes))-273.15; - break; - } - switch (j) { - case 0: // "C" - result= from; - break; - case 1: // "F" - result= from*1.8 + 32; - break; - case 2: // "K" - result= (273.15000000000000000000+from)/prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - // LIQUID MEASURE conversion - if (j==-1 && (i=findUnits(fromUnits, liquidMeasureUnits))>=0) { - j= findUnits(toUnits, liquidMeasureUnits); - if (j>-1) { // both fromUnits and toUnits are in same family - //"tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l" - switch (i) { - case 0: // "tsp" - from= number; - break; - case 1: // "tbs" - from= 3.00000000000000000000*number; - break; - case 2: // "oz" - from= 6.00000000000000000000*number; - break; - case 3: // "cup" - from= 48.00000000000000000000*number; - break; - case 4: // "pt" - from= 96.00000000000000000000*number; - break; - case 5: // "uk_pt" - from= 115.26600000000000000000*number; - break; - case 6: // "qt" - from= 192.00000000000000000000*number; - break; - case 7: // "gal" - from= 768.00000000000000000000*number; - break; - case 8: // "l" - from= 202.84000000000000000000*number*prefixMultiplier(fromPrefix, metricPrefixes); - break; - } - switch (j) { - case 0: // "tsp" - result= from; - break; - case 1: // "tbs" - result= 0.33333333333333300000*from; - break; - case 2: // "oz" - result= 0.16666666666666700000*from; - break; - case 3: // "cup" - result= 0.02083333333333330000*from; - break; - case 4: // "pt" - result= 0.01041666666666670000*from; - break; - case 5: // "uk_pt" - result= 0.00867558516821960000*from; - break; - case 6: // "qt" - result= 0.00520833333333333000*from; - break; - case 7: // "gal" - result= 0.00130208333333333000*from; - break; - case 8: // "l" - result= 0.00492999408400710000*from/prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } - else return new PtgErr(PtgErr.ERROR_NA); - } - - PtgNumber pnum= new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from CONVERT= " + pnum.getString()); - return pnum; - } -/** - DEC2BIN - Converts a decimal number to binary -*/ - protected static Ptg calcDec2Bin(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "DEC2BIN"); - int dec= operands[0].getIntVal(); - int places= 0; - if (operands.length > 1) - places= operands[1].getIntVal(); - - if (dec < -512 || dec > 511 || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - - String bString= Integer.toBinaryString(dec); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - bString= bString.substring(Math.max(bString.length()-10, 0)); - } else if (places > 0) { - if (bString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - bString= ("0000000000" + bString); // maximum= 10 bits - bString= bString.substring(bString.length()-places); - } - PtgStr pstr= new PtgStr(bString); - if (DEBUG) Logger.logInfo("Result from DEC2BIN= " + pstr.getString()); - return pstr; - } - -/** - DEC2HEX - Converts a decimal number to hexadecimal - */ - protected static Ptg calcDec2Hex(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "DEC2HEX"); - long dec= PtgCalculator.getLongValue(operands[0]); - int places= 0; - if (operands.length > 1) - places= operands[1].getIntVal(); - if (dec < -549755813888L || dec > 549755813887L || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - String hString= Long.toHexString(dec).toUpperCase(); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - hString= hString.substring(Math.max(hString.length()-10, 0)); - } else if (places > 0) { - if (hString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - hString= ("0000000000" + hString); // maximum= 10 places - hString= hString.substring(hString.length()-places); - - } - PtgStr pstr= new PtgStr(hString); - if (DEBUG) Logger.logInfo("Result from DEC2HEX= " + pstr.getString()); - return pstr; - } -/** - DEC2OCT - Converts a decimal number to octal -*/ - protected static Ptg calcDec2Oct(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "DEC2OCT"); - long dec= PtgCalculator.getLongValue(operands[0]); - int places= 0; - if (operands.length > 1) - places= operands[1].getIntVal(); - if (dec < -536870912L || dec > 536870911L || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - String oString= Long.toOctalString(dec); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - oString= oString.substring(Math.max(oString.length()-10, 0)); - } else if (places > 0) { - if (oString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - oString= ("0000000000" + oString); // maximum= 10 places - oString= oString.substring(oString.length()-places); - - } - PtgStr pstr= new PtgStr(oString); - if (DEBUG) Logger.logInfo("Result from DEC2OCT= " + pstr.getString()); - return pstr; - } -/** - DELTA - Tests whether two values are equal - */ - protected static Ptg calcDelta(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "DELTA"); - - double number1= operands[0].getDoubleVal(); - double number2= 0; - if (operands.length > 1) - number2= operands[1].getDoubleVal(); - int result= 0; - if (number1 == number2) - result= 1; - - PtgNumber pnum= new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from DELTA= " + pnum.getString()); - return pnum; - } - - - /** - * helper erf calc - seems to work *ALRIGHT* for values over 1 - * NOTE: not accurate to 9 digits for every case - * @param x - * @return - */ - private static double erf_try1(double x) { - double t = x; - double x2= Math.pow(x, 2); - for (double n= 1000; n >= 0.5; n-=0.5) { - t = x + n/t; - } - t = 1.0 / t; - double tt = Math.exp(-x2) /Math.sqrt(Math.PI); - return (1 - tt * t); - } - - /** - ERF - Returns the error function integrated between lower_limit and upper_limit. - ERF(lower_limit,upper_limit) - - Lower_limit is the lower bound for integrating ERF. - Upper_limit is the upper bound for integrating ERF. If omitted, ERF integrates between zero and lower_limit. - - With a single argument ERF returns the error function, defined as - erf(x) = 2/sqrt(pi)* integral from 0 to x of exp(-t*t) dt. - If two arguments are supplied, they are the lower and upper limits of the integral. - NOTE: Accuracy is not always to 9 digits - NOTE: Version with two parameters is NOT supported - */ - protected static Ptg calcErf(Ptg[] operands) throws FunctionNotSupportedException { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - try { - double lower_limit= operands[0].getDoubleVal(); - double upper_limit= Double.NaN; //lower_limit; - if (operands.length==2) - upper_limit= operands[1].getDoubleVal(); - // If lower_limit is negative, ERF returns the #NUM! error value. - // If upper_limit is negative, ERF returns the #NUM! error value. + + /** + * BIN2DEC + * Converts a binary number to decimal + */ + protected static Ptg calcBin2Dec(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "BIN2DEC"); + String bString = operands[0].getString().trim(); + // 10 bits at most + if (bString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); + // must det. manually if binary string is negative because parseInt does not + // handle two's complement input!!! + boolean bIsNegative = (bString.length() == 10 && bString.substring(0, 1).equalsIgnoreCase("1")); + + int dec = 0; + try { + dec = Integer.parseInt(bString, 2); + if (bIsNegative) dec -= 1024; // 2^10 (= signed 11 bits) + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + PtgNumber pnum = new PtgNumber(dec); + if (DEBUG) Logger.logInfo("Result from BIN2DEC= " + pnum.getVal()); + return pnum; + } + + /** + * BIN2HEX + * Converts a binary number to hexadecimal + */ + protected static Ptg calcBin2Hex(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "BIN2HEX"); + String bString = operands[0].getString().trim(); + int places = 0; + if (operands.length > 1) + places = operands[1].getIntVal(); + + // 10 bits at most + if (bString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); + // must det. manually if binary string is negative because parseInt does not + // handle two's complement input!!! + boolean bIsNegative = (bString.length() == 10 && bString.substring(0, 1).equalsIgnoreCase("1")); + + long dec; + String hString; + try { + dec = Long.parseLong(bString, 2); + if (bIsNegative) dec -= 1024; // 2^10 (= signed 11 bits) + hString = Long.toHexString(dec).toUpperCase(); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + hString = hString.substring(Math.max(hString.length() - 10, 0)); + } else if (places > 0) { + if (hString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); + hString = ("0000000000" + hString); // maximum= 10 bits + hString = hString.substring(hString.length() - places); + } + + PtgStr pstr = new PtgStr(hString); + if (DEBUG) Logger.logInfo("Result from BIN2HEX= " + pstr.getString()); + return pstr; + } + + /** + * BIN2OCT + * Converts a binary number to octal + */ + protected static Ptg calcBin2Oct(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "Bin2Oct"); + String bString = operands[0].getString().trim(); + int places = 0; + if (operands.length > 1) + places = operands[1].getIntVal(); + + // 10 bits at most + if (bString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); + // must det. manually if binary string is negative because parseInt does not + // handle two's complement input!!! + boolean bIsNegative = (bString.length() == 10 && bString.substring(0, 1).equalsIgnoreCase("1")); + + int dec; + String oString; + try { + dec = Integer.parseInt(bString, 2); + if (bIsNegative) dec -= 1024; // 2^10 (= signed 11 bits) + oString = Integer.toOctalString(dec); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + oString = oString.substring(Math.max(oString.length() - 10, 0)); + } else if (places > 0) { + if (oString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); + oString = ("0000000000" + oString); // maximum= 10 bits + oString = oString.substring(oString.length() - places); + } + PtgStr pstr = new PtgStr(oString); + if (DEBUG) Logger.logInfo("Result from BIN2OCT= " + pstr.getString()); + return pstr; + } + + /** + * COMPLEX + * Converts real and imaginary coefficients into a complex number + */ + protected static Ptg calcComplex(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "Complex"); + int real = operands[0].getIntVal(); + int imaginary = operands[1].getIntVal(); + String suffix = "i"; + if (operands.length > 2) { + suffix = operands[2].getString().trim(); + if (!(suffix.equals("i") || suffix.equals("j"))) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + String complexString = ""; + + // result: real + imaginary suffix + // real - imaginary suffix + // real (if imaginary==0) + // real + suffix (if imaginary==1) + // imaginary suffix (if (real==0) + if (real != 0) { + complexString = String.valueOf(real); + if (imaginary > 0) + complexString += " + "; + } + + if (imaginary != 0) + complexString += (Math.abs(imaginary) != 1 ? String.valueOf(imaginary) : "") + suffix; + + if (complexString.equals("")) complexString = "0"; + + PtgStr pstr = new PtgStr(complexString); + if (DEBUG) Logger.logInfo("Result from COMPLEX= " + pstr.getString()); + return pstr; + } + + /** + * CONVERT + * Converts a number from one measurement system to another + * Weight and mass From_unit or to_unit + * Gram "g" + * Slug "sg" + * Pound mass (avoirdupois) "lbm" + * U (atomic mass unit) "u" + * Ounce mass (avoirdupois) "ozm" + *

                + * Distance From_unit or to_unit + * Meter "m" + * Statute mile "mi" + * Nautical mile "Nmi" + * Inch "in" + * Foot "ft" + * Yard "yd" + * Angstrom "ang" + * Pica (1/72 in.) "Pica" + *

                + * Time From_unit or to_unit + * Year "yr" + * Day "day" + * Hour "hr" + * Minute "mn" + * Second "sec" + *

                + * Pressure From_unit or to_unit + * Pascal "Pa" + * Atmosphere "atm" + * mm of Mercury "mmHg" + *

                + * Force From_unit or to_unit + * Newton "N" + * Dyne "dyn" + * Pound force "lbf" + *

                + * Energy From_unit or to_unit + * Joule "J" + * Erg "e" + * Thermodynamic calorie "c" + * IT calorie "cal" + * Electron volt "eV" + * Horsepower-hour "HPh" + * Watt-hour "Wh" + * Foot-pound "flb" + * BTU "BTU" + *

                + * Power From_unit or to_unit + * Horsepower "HP" + * Watt "W" + *

                + * Magnetism From_unit or to_unit + * Tesla "T" + * Gauss "ga" + *

                + * Temperature From_unit or to_unit + * Degree Celsius "C" + * Degree Fahrenheit "F" + * Degree Kelvin "K" + *

                + * Liquid measure From_unit or to_unit + * Teaspoon "tsp" + * Tablespoon "tbs" + * Fluid ounce "oz" + * Cup "cup" + * U.S. pint "pt" + * U.K. pint "uk_pt" + * Quart "qt" + * Gallon "gal" + * Liter "l" + *

                + *

                + * The following abbreviated unit prefixes can be prepended to any metric + * from_unit or to_unit. + *

                + * Prefix Multiplier Abbreviation + * exa 1E+18 "E" + * peta 1E+15 "P" + * tera 1E+12 "T" + * giga 1E+09 "G" + * mega 1E+06 "M" + * kilo 1E+03 "k" + * hecto 1E+02 "h" + * dekao 1E+01 "e" + * deci 1E-01 "d" + * centi 1E-02 "c" + * milli 1E-03 "m" + * micro 1E-06 "u" + * nano 1E-09 "n" + * pico 1E-12 "p" + * femto 1E-15 "f" + * atto 1E-18 "a" + *

                + *

                + * Remarks + *

                + * If the input data types are incorrect, CONVERT returns the #VALUE! error value. + * If the unit does not exist, CONVERT returns the #N/A error value. + * If the unit does not support an abbreviated unit prefix, CONVERT returns the #N/A error value. + * If the units are in different groups, CONVERT returns the #N/A error value. + * Unit names and prefixes are case-sensitive. + */ + private static int findUnits(String u, String[] units) { + boolean bFound = false; + for (int i = 0; i < units.length; i++) { + if (u.equals(units[i])) return i; + } + return -1; + } + + private static double prefixMultiplier(String p, String[] prefixes) { + double multiplier = 1.0; + if (p.equals("")) return multiplier; + for (int i = 0; i < prefixes.length; i++) { + if (p.equals(prefixes[i])) { + switch (i) { + case 0: // "E" exa + multiplier = 1E+18; + break; + case 1: // "P" peta + multiplier = 1E+15; + break; + case 2: // "T" tera + multiplier = 1E+12; + break; + case 3: // "G" giga + multiplier = 1E+09; + break; + case 4: // "M" mega + multiplier = 1E+06; + break; + case 5: // "k" kilo + multiplier = 1E+03; + break; + case 6: // "h" hecto + multiplier = 1E+02; + break; + case 7: // "e" dekao + multiplier = 1E+01; + break; + case 8: // "d" deci + multiplier = 1E-01; + break; + case 9: // "c" centi + multiplier = 1E-02; + break; + case 10: // "m" milli + multiplier = 1E-03; + break; + case 11: // "u" micro + multiplier = 1E-06; + break; + case 12: // "n" nano + multiplier = 1E-09; + break; + case 13: // "p" pico + multiplier = 1E-12; + break; + case 14: // "f" femto + multiplier = 1E-15; + break; + case 15: // "a" atto + multiplier = 1E-18; + break; + } + return multiplier; + } + } + + return multiplier; + } + + protected static Ptg calcConvert(Ptg[] operands) { + if (operands.length < 3) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "CONVERT"); + double number = operands[0].getDoubleVal(); + String fromUnits = operands[1].getString().trim(); + String toUnits = operands[2].getString().trim(); + + String[] allUnits = {"g", "sg", "lbm", "u", "ozm", "m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica", + "yr", "day", "hr", "mn", "sec", "Pa", "atm", "mmHg", "N", "dyn", "lbf", + "J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU", "HP", "W", "T", "ga", + "C", "F", "K", "tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l"}; + String[] weightUnits = {"g", "sg", "lbm", "u", "ozm"}; + String[] distanceUnits = {"m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica"}; + String[] timeUnits = {"yr", "day", "hr", "mn", "sec"}; + String[] pressureUnits = {"Pa", "atm", "mmHg"}; + String[] forceUnits = {"N", "dyn", "lbf"}; + String[] energyUnits = {"J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU"}; + String[] powerUnits = {"HP", "W"}; + String[] magnetismUnits = {"T", "ga"}; + String[] temperatureUnits = {"C", "F", "K"}; + String[] liquidMeasureUnits = {"tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l"}; + + // for any metric unit, may be prefixed with + String fromPrefix = "", toPrefix = ""; + String[] metricPrefixes = {"E", "P", "T", "G", "M", "k", "h", "e", "d", "c", "m", "u", "n", "p", "f", "a"}; + + // first, see if fromUnits and toUnits are in list of acceptable units + if (findUnits(fromUnits, allUnits) < 0) { // doesn't match; strip prefix and try again + if (fromUnits.length() > 1) { + fromPrefix = fromUnits.substring(0, 1); + fromUnits = fromUnits.substring(1); + // now recheck + if (findUnits(fromUnits, allUnits) < 0) + return new PtgErr(PtgErr.ERROR_NA); + // make sure that prefix is acceptable + if (findUnits(fromPrefix, metricPrefixes) < 0) + return new PtgErr(PtgErr.ERROR_NA); + } else return new PtgErr(PtgErr.ERROR_NA); + } + if (findUnits(toUnits, allUnits) < 0) {// doesn't match; strip prefix and try again + if (toUnits.length() > 1) { + toPrefix = toUnits.substring(0, 1); + toUnits = toUnits.substring(1); + // now recheck + if (findUnits(toUnits, allUnits) < 0) + return new PtgErr(PtgErr.ERROR_NA); + // make sure that prefix is acceptable + if (findUnits(toPrefix, metricPrefixes) < 0) + return new PtgErr(PtgErr.ERROR_NA); + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // at here, we know that the prefixes and units are found, but we don't know if they match or make sense ... + double result = 0; + double from = 0; + int i, j = -1; + + // WEIGHT conversion + if ((i = findUnits(fromUnits, weightUnits)) >= 0) { + j = findUnits(toUnits, weightUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + // { "g", "sg", "lbm", "u", "ozm" } + // get fromUnit in grams + switch (i) { + // from: + case 0: // "g" + from = number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 1: // "sg" + from = 14593.84241892870000000000 * number; + break; + case 2: // "lbm" + from = 453.5923097488115 * number; + break; + case 3: // "u" + from = 1.660531004604650E-24 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 4: // "ozm" + from = 28.349515207973 * number; + break; + } + // now convert + switch (j) { + case 0: // "g" + result = from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 1: // "sg" + result = from * 0.00006852205000534780; + break; + case 2: // "lbm" + result = from * 0.00220462291469134000; + break; + case 3: // "u" + result = from * 6.02217000000000000000E+23 / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 4: // "ozm" + result = from * 0.03527397180036270000; + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // DISTANCE conversion + if (j == -1 && (i = findUnits(fromUnits, distanceUnits)) >= 0) { + j = findUnits(toUnits, distanceUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + // "m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica" ; + // get fromUnits in m + switch (i) { + case 0: //"m" + from = number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 1: //"mi" + from = 1609.344000000000 * number; + break; + case 2: //"Nmi" + from = 1852.000000000000 * number; + break; + case 3: //"in" + from = 0.025400000000 * number; + break; + case 4: // "ft" + from = 0.304800000000 * number; + break; + case 5: // "yd" + from = 0.914400000300 * number; + break; + case 6: // "ang" + from = 0.000000000100 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 7: // "Pica" + from = 0.00035277777777780000 * number; + break; + } + switch (j) { + case 0: //"m" + result = from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 1: //"mi" + result = 0.00062137119223733400 * from; + break; + case 2: //"Nmi" + result = 0.00053995680345572400 * from; + break; + case 3: //"in" + result = 39.37007874015750000000 * from; + break; + case 4: // "ft" + result = 3.28083989501312000000 * from; + break; + case 5: // "yd" + result = 1.09361329797891000000 * from; + break; + case 6: // "ang" + result = 10000000000.000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 7: // "Pica" + result = 2834.64566929116000000000 * from; + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // TIME conversion + if (j == -1 && (i = findUnits(fromUnits, timeUnits)) >= 0) { + j = findUnits(toUnits, timeUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + //"yr", "day", "hr", "mn", "sec" + switch (i) { + case 0: // "yr" + from = number; + break; + case 1: // "day" + from = 0.00273785078713210000 * number; + break; + case 2: // "hr" + from = 0.00011407711613050400 * number; + break; + case 3: // "mn" + from = 0.00000190128526884174 * number; + break; + case 4: // "sec" + from = 0.00000003168808781403 * number; + break; + } + switch (j) { + case 0: // "yr" + result = from; + break; + case 1: // "day" + result = 365.250000000000 * from; + break; + case 2: // "hr" + result = 8766.000000000000 * from; + break; + case 3: // "mn" + result = 525960.000000000000 * from; + break; + case 4: // "sec" + result = 31557600.000000000000 * from; + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // PRESSURE conversion + if (j == -1 && (i = findUnits(fromUnits, pressureUnits)) >= 0) { + j = findUnits(toUnits, pressureUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + //"Pa", "atm", "mmHg" + switch (i) { + case 0: // "Pa" + from = number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 1: // "atm" + from = 101324.99658300000000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 2: // "mmHg" + from = 133.32236392500000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + } + switch (j) { + case 0: // "Pa" + result = from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 1: // "atm" + result = 0.00000986923299998193 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 2: // "mmHg" + result = 0.00750061707998627000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // FORCE conversion + if (j == -1 && (i = findUnits(fromUnits, forceUnits)) >= 0) { + j = findUnits(toUnits, forceUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + //"N", "dyn", "lbf" + switch (i) { + case 0: // "N" + from = number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 1: // "dyn" + from = 0.000010000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 2: // "lbf" + from = 4.448222000000 * number; + break; + } + switch (j) { + case 0: // "N" + result = from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 1: // "dyn" + result = 100000.000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 2: // "lbf" + result = 0.22480892365533900000 * from; + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // ENERGY conversion + if (j == -1 && (i = findUnits(fromUnits, energyUnits)) >= 0) { + j = findUnits(toUnits, energyUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + //"J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU" + switch (i) { + case 0: // "J" + from = number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 1: // "e" + from = 0.00000010000004806570 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 2: // "c" + from = 4.18399101363672000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 3: // "cal" + from = 4.18679484613929000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 4: // "eV" + from = 1.60217646E-19 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 5: // "HPh" + from = 2684517.41316170000000000000 * number; + break; + case 6: // "Wh" + from = 3599.99820554720000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 7: // "flb" + from = 0.04214000032364240000 * number; + break; + case 8: // "BTU" + from = 1055.05813786749000000000 * number; + break; + } + switch (j) { + case 0: // "J" + result = from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 1: // "e" + result = 9999995.19343231000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 2: // "c" + result = 0.23900624947346700000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 3: // "cal" + result = 0.23884619064201700000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 4: // "eV" + result = 6241457000000000000.00000000000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 5: // "HPh" + result = 0.00000037250643080100 * from; + break; + case 6: // "Wh" + result = 0.00027777791623871100 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 7: // "flb" + result = 23.73042221926510000000 * from; + break; + case 8: // "BTU" + result = 0.00094781506734901500 * from; + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // POWER conversion + if (j == -1 && (i = findUnits(fromUnits, powerUnits)) >= 0) { + j = findUnits(toUnits, powerUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + //"HP", "W" + switch (i) { + case 0: // "HP" + from = number; + break; + case 1: // "W" + from = 0.00134102006031908000 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + } + switch (j) { + case 0: // "HP" + result = from; + break; + case 1: // "W" + result = 745.70100000000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // MAGNETISM conversion + if (j == -1 && (i = findUnits(fromUnits, magnetismUnits)) >= 0) { + j = findUnits(toUnits, magnetismUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + //"T", "ga" + switch (i) { + case 0: // "T" + from = number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + case 1: // "ga" + from = 0.000100000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + } + switch (j) { + case 0: // "T" + result = from / prefixMultiplier(toPrefix, metricPrefixes); + break; + case 1: // "ga" + result = 10000.000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // TEMPERATURE conversion + if (j == -1 && (i = findUnits(fromUnits, temperatureUnits)) >= 0) { + j = findUnits(toUnits, temperatureUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + //"C", "F", "K" + switch (i) { + case 0: // "C" + from = number; + break; + case 1: // "F" + from = (number - 32) / 1.8; + break; + case 2: // "K" + from = (number * prefixMultiplier(fromPrefix, metricPrefixes)) - 273.15; + break; + } + switch (j) { + case 0: // "C" + result = from; + break; + case 1: // "F" + result = from * 1.8 + 32; + break; + case 2: // "K" + result = (273.15000000000000000000 + from) / prefixMultiplier(toPrefix, metricPrefixes); + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + // LIQUID MEASURE conversion + if (j == -1 && (i = findUnits(fromUnits, liquidMeasureUnits)) >= 0) { + j = findUnits(toUnits, liquidMeasureUnits); + if (j > -1) { // both fromUnits and toUnits are in same family + //"tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l" + switch (i) { + case 0: // "tsp" + from = number; + break; + case 1: // "tbs" + from = 3.00000000000000000000 * number; + break; + case 2: // "oz" + from = 6.00000000000000000000 * number; + break; + case 3: // "cup" + from = 48.00000000000000000000 * number; + break; + case 4: // "pt" + from = 96.00000000000000000000 * number; + break; + case 5: // "uk_pt" + from = 115.26600000000000000000 * number; + break; + case 6: // "qt" + from = 192.00000000000000000000 * number; + break; + case 7: // "gal" + from = 768.00000000000000000000 * number; + break; + case 8: // "l" + from = 202.84000000000000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); + break; + } + switch (j) { + case 0: // "tsp" + result = from; + break; + case 1: // "tbs" + result = 0.33333333333333300000 * from; + break; + case 2: // "oz" + result = 0.16666666666666700000 * from; + break; + case 3: // "cup" + result = 0.02083333333333330000 * from; + break; + case 4: // "pt" + result = 0.01041666666666670000 * from; + break; + case 5: // "uk_pt" + result = 0.00867558516821960000 * from; + break; + case 6: // "qt" + result = 0.00520833333333333000 * from; + break; + case 7: // "gal" + result = 0.00130208333333333000 * from; + break; + case 8: // "l" + result = 0.00492999408400710000 * from / prefixMultiplier(toPrefix, metricPrefixes); + break; + } + } else return new PtgErr(PtgErr.ERROR_NA); + } + + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) Logger.logInfo("Result from CONVERT= " + pnum.getString()); + return pnum; + } + + /** + * DEC2BIN + * Converts a decimal number to binary + */ + protected static Ptg calcDec2Bin(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "DEC2BIN"); + int dec = operands[0].getIntVal(); + int places = 0; + if (operands.length > 1) + places = operands[1].getIntVal(); + + if (dec < -512 || dec > 511 || places < 0) return new PtgErr(PtgErr.ERROR_NUM); + + String bString = Integer.toBinaryString(dec); + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + bString = bString.substring(Math.max(bString.length() - 10, 0)); + } else if (places > 0) { + if (bString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); + bString = ("0000000000" + bString); // maximum= 10 bits + bString = bString.substring(bString.length() - places); + } + PtgStr pstr = new PtgStr(bString); + if (DEBUG) Logger.logInfo("Result from DEC2BIN= " + pstr.getString()); + return pstr; + } + + /** + * DEC2HEX + * Converts a decimal number to hexadecimal + */ + protected static Ptg calcDec2Hex(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "DEC2HEX"); + long dec = PtgCalculator.getLongValue(operands[0]); + int places = 0; + if (operands.length > 1) + places = operands[1].getIntVal(); + if (dec < -549755813888L || dec > 549755813887L || places < 0) return new PtgErr(PtgErr.ERROR_NUM); + String hString = Long.toHexString(dec).toUpperCase(); + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + hString = hString.substring(Math.max(hString.length() - 10, 0)); + } else if (places > 0) { + if (hString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); + hString = ("0000000000" + hString); // maximum= 10 places + hString = hString.substring(hString.length() - places); + + } + PtgStr pstr = new PtgStr(hString); + if (DEBUG) Logger.logInfo("Result from DEC2HEX= " + pstr.getString()); + return pstr; + } + + /** + * DEC2OCT + * Converts a decimal number to octal + */ + protected static Ptg calcDec2Oct(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "DEC2OCT"); + long dec = PtgCalculator.getLongValue(operands[0]); + int places = 0; + if (operands.length > 1) + places = operands[1].getIntVal(); + if (dec < -536870912L || dec > 536870911L || places < 0) return new PtgErr(PtgErr.ERROR_NUM); + String oString = Long.toOctalString(dec); + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + oString = oString.substring(Math.max(oString.length() - 10, 0)); + } else if (places > 0) { + if (oString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); + oString = ("0000000000" + oString); // maximum= 10 places + oString = oString.substring(oString.length() - places); + + } + PtgStr pstr = new PtgStr(oString); + if (DEBUG) Logger.logInfo("Result from DEC2OCT= " + pstr.getString()); + return pstr; + } + + /** + * DELTA + * Tests whether two values are equal + */ + protected static Ptg calcDelta(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "DELTA"); + + double number1 = operands[0].getDoubleVal(); + double number2 = 0; + if (operands.length > 1) + number2 = operands[1].getDoubleVal(); + int result = 0; + if (number1 == number2) + result = 1; + + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) Logger.logInfo("Result from DELTA= " + pnum.getString()); + return pnum; + } + + + /** + * helper erf calc - seems to work *ALRIGHT* for values over 1 + * NOTE: not accurate to 9 digits for every case + * + * @param x + * @return + */ + private static double erf_try1(double x) { + double t = x; + double x2 = Math.pow(x, 2); + for (double n = 1000; n >= 0.5; n -= 0.5) { + t = x + n / t; + } + t = 1.0 / t; + double tt = Math.exp(-x2) / Math.sqrt(Math.PI); + return (1 - tt * t); + } + + /** + * ERF + * Returns the error function integrated between lower_limit and upper_limit. + * ERF(lower_limit,upper_limit) + *

                + * Lower_limit is the lower bound for integrating ERF. + * Upper_limit is the upper bound for integrating ERF. If omitted, ERF integrates between zero and lower_limit. + *

                + * With a single argument ERF returns the error function, defined as + * erf(x) = 2/sqrt(pi)* integral from 0 to x of exp(-t*t) dt. + * If two arguments are supplied, they are the lower and upper limits of the integral. + * NOTE: Accuracy is not always to 9 digits + * NOTE: Version with two parameters is NOT supported + */ + protected static Ptg calcErf(Ptg[] operands) throws FunctionNotSupportedException { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + try { + double lower_limit = operands[0].getDoubleVal(); + double upper_limit = Double.NaN; //lower_limit; + if (operands.length == 2) + upper_limit = operands[1].getDoubleVal(); + // If lower_limit is negative, ERF returns the #NUM! error value. + // If upper_limit is negative, ERF returns the #NUM! error value. // if (lower_limit < 0 /*|| upper_limitupper_limit < 0*/) return new PtgErr(PtgErr.ERROR_NUM); - boolean neg= (lower_limit < 0); - lower_limit= Math.abs(lower_limit); - - - double result; - double limit= lower_limit; + boolean neg = (lower_limit < 0); + lower_limit = Math.abs(lower_limit); + + + double result; + double limit = lower_limit; /* // try this: from "Computation of the error function erf in arbitrary precision with correct rounding" double r= 0; double r1= 0; double estimate= (2/Math.sqrt(Math.PI))*(limit - Math.pow(limit, 3)/3.0); - double convergence= Math.pow(2, estimate-15); + double convergence= Math.pow(2, estimate-15); for (int i= 0, n= 0; n < 100; i++, n++) { double factor= 2.0*n + 1.0; double z= Math.pow(limit, factor); @@ -1042,1285 +1032,1332 @@ protected static Ptg calcErf(Ptg[] operands) throws FunctionNotSupportedExcepti r= r-zzz; else r= r+zzz; - + if (Math.abs(r)-r1) < - + r1= Math.abs(r1); } result= r*(2.0/Math.sqrt(Math.PI));*/ - - if (limit < 0.005) { - /* A&S 7.1.1 - good to at least 6 digts ... but not for larger values ... sigh ...*/ - double r= 0; - for (int i= 0, n= 0; n < 12; i++, n++) { - double factor= 2.0*n + 1.0; - double z= Math.pow(limit, factor); - double zz= (MathFunctionCalculator.factorial(n)*factor); - double zzz= z/zz; - if ((i % 2)!=0) - r= r-zzz; - else - r= r+zzz; - } - result= r*(2.0/Math.sqrt(Math.PI)); - } else - result= erf_try1(limit); - - if (neg) - result*= -1; - - if (!Double.isNaN(upper_limit)) { // Erf(upper)-Erf(lower) - Ptg result2= calcErf(new Ptg[] {operands[1]}); - if (result2 instanceof PtgNumber) - result= ((PtgNumber)result2).getDoubleVal()-result; - else return new PtgErr(PtgErr.ERROR_VALUE); - } - - return new PtgNumber(result); - - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - static final double PI_SQRT = Math.sqrt(Math.PI); - static final double TSQPI = 2./PI_SQRT; - private static boolean firstCall = true; - /*** - * Calculate the error function erf. - * @param x the argument - * @return the value erf(x) - */ - public static double erf(final double x) { - //if (type==1) { - return erfAS(x); - //} - //return erfCody(x); - } - - /*** - * Calculate the remaining error function erfc. - * @param x the argument - * @return the value erfc(x) - */ - public static double erfc(final double x) { - //if (type==1) { - return erfcAS(x); - //} - //return erfcCody(x); - } - - /*** - * Internal helper method to calculate the error function at value x. - * This code is based on a Fortran implementation from - * W. J. Cody. - * Refactored by N.Wulff for Java. - * - * Cody vs AS algiorithm - * - * @param x the argument - * @return the approximation of erf(x) - */ - private static double erfCody(final double x) { - double result = 0; - double y = Math.abs(x); - if (firstCall) { - firstCall = false; - } - if (y<=THRESHOLD) { - result = x*calcLower(y); - } else { - result = calcUpper(y); - result = (0.5-result)+0.5; - if (x<0) { - result = -result; - } - } - return result; - } - - /*** - * Internal helper method to calculate the erfc functions. - * This code is based on a Fortran implementation from - * W. J. Cody. - * Refactored by N.Wulff for Java. - * - * @param x the argument - * @return the approximation erfc(x) - */ - private static double erfcCody(final double x) { - double result = 0; - double y = Math.abs(x); - if (firstCall) { - firstCall = false; - } - if (y<=THRESHOLD) { - result = x*calcLower(y); - result = 1-result; - } else { - result = calcUpper(y); - if (x<0) { - result = 2.0-result; - } - } - return result; - } - - /*** - * Internal helper method to calculate the erf/erfc functions. - * This code is based on a Fortran implementation from - * W. J. Cody. - * Refactored by N.Wulff for Java. - * - * @param y the value y=abs(x)<=THRESHOLD - * @return the series expansion - */ - private static double calcLower(final double y) { - double result; - double ySq; - double xNum; - double xDen; - ySq = 0.0; - if (y>X_SMALL) - ySq = y*y; - xNum = ERF_A[4]*ySq; - xDen = ySq; - for (int i = 0; i<3; i++) { - xNum = (xNum+ERF_A[i])*ySq; - xDen = (xDen+ERF_B[i])*ySq; - } - result = (xNum+ERF_A[3])/(xDen+ERF_B[3]); - return result; - } - - /*** - * Internal helper method to calculate the erf/erfc functions. - * This code is based on a Fortran implementation from - * W. J. Cody. - * Refactored by N.Wulff for Java. - * - * @param y the value y=abs(x)>THRESHOLD - * @return the series expansion - */ - private static double calcUpper(final double y) { - double result; - double ySq; - double xNum; - double xDen; - if (y<=4.0) { - xNum = ERF_C[8]*y; - xDen = y; - for (int i = 0; i<7; i++) { - xNum = (xNum+ERF_C[i])*y; - xDen = (xDen+ERF_D[i])*y; - } - result = (xNum+ERF_C[7])/(xDen+ERF_D[7]); - } else { - result = 0.0; - if (y>=X_HUGE) { - result = SQRPI/y; - } else { - ySq = 1.0/(y*y); - xNum = ERF_P[5]*ySq; - xDen = ySq; - for (int i = 0; i<4; i++) { - xNum = (xNum+ERF_P[i])*ySq; - xDen = (xDen+ERF_Q[i])*ySq; - } - result = ySq*(xNum+ERF_P[4])/(xDen+ERF_Q[4]); - result = (SQRPI-result)/y; - } - } - ySq = Math.round(y*16.0)/16.0; - double del = (y-ySq)*(y+ySq); - result = Math.exp(-ySq*ySq)*Math.exp(-del)*result; - return result; - } - - /*** - * Calculate the error function at value x. - * AS 7.1.5/7.1.26 - * @param x the argument - * @return the value erf(x) - */ - private static double erfAS(final double x) { - if (firstCall) { - firstCall = false; - } - if (x<0) { - return -erfAS(-x); - } - if (x<2) { - return erfSeries(x); - } - return erfRational(x); - } - - /*** - * Calculate the remaining erfc error function at value x. - * AS 7.1.5/7.1.26 - * @param x the argument - * @return the value erfc(x) - */ - private static double erfcAS(final double x) { - if (firstCall) { - firstCall = false; - } - return 1-erfAS(x); - } - - /*** - * Series expansion from A&S 7.1.5. - * - * @param x the argument - * @return erf(x) - */ - private static double erfSeries(final double x) { - final double eps = 1.E-8; // we want only ~1.E-7 - final int kmax = 50; // this can be reached with ~30-40 - double an, ak = x; - double erfo, erf = ak; - int k = 1; - do { - erfo = erf; - ak *= -x*x/k; - an = ak/(2.0*k+1.0); - erf += an; - } while (!hasConverged(erf, erfo, eps, ++k, kmax)); - return TSQPI*erf; - } - /** - * Indicate if an iterative algorithm has RELATIVE converged. - *


                - * Note: - * HasConverged throws an ArithmeticException if more than max calls - * have been made. Choose hasReacherAccuracy if this is not desired. - *
                - * @param xn the actual argument x[n] - * @param xo the older argument x[n-1] - * @param eps the accuracy to reach - * @param n the actual iteration counter - * @param max the maximal number of iterations - * @return flag indicating if accuracy is reached. - */ - public static boolean hasConverged(final double xn, final double xo, - final double eps, final int n, final int max) { - if (hasReachedAccuracy(xn, xo, eps)) { - return true; - } - if (n>= max) { - throw new ArithmeticException(); - } - return false; - } - /** - * Indicate if xn and xo have the relative/absolute accuracy epsilon. - * In case that the true value is less than one this is based - * on the absolute difference, otherwise on the relative difference: - *
                -         *     2*|x[n]-x[n-1]|/|x[n]+x[n-1]| < eps 
                -         * 
                - * @param xn the actual argument x[n] - * @param xo the older argument x[n-1] - * @param eps accuracy to reach - * @return flag indicating if accuracy is reached. - */ - public static boolean hasReachedAccuracy(final double xn, final double xo, - final double eps) { - double z=Math.abs(xn+xo)/2; - double error=Math.abs(xn-xo); - if (z>1) { - error /= z; + + if (limit < 0.005) { + /* A&S 7.1.1 - good to at least 6 digts ... but not for larger values ... sigh ...*/ + double r = 0; + for (int i = 0, n = 0; n < 12; i++, n++) { + double factor = 2.0 * n + 1.0; + double z = Math.pow(limit, factor); + double zz = (MathFunctionCalculator.factorial(n) * factor); + double zzz = z / zz; + if ((i % 2) != 0) + r = r - zzz; + else + r = r + zzz; + } + result = r * (2.0 / Math.sqrt(Math.PI)); + } else + result = erf_try1(limit); + + if (neg) + result *= -1; + + if (!Double.isNaN(upper_limit)) { // Erf(upper)-Erf(lower) + Ptg result2 = calcErf(new Ptg[]{operands[1]}); + if (result2 instanceof PtgNumber) + result = result2.getDoubleVal() - result; + else return new PtgErr(PtgErr.ERROR_VALUE); + } + + return new PtgNumber(result); + + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + + static final double PI_SQRT = Math.sqrt(Math.PI); + static final double TSQPI = 2. / PI_SQRT; + private static boolean firstCall = true; + + /*** + * Calculate the error function erf. + * @param x the argument + * @return the value erf(x) + */ + public static double erf(final double x) { + //if (type==1) { + return erfAS(x); + //} + //return erfCody(x); + } + + /*** + * Calculate the remaining error function erfc. + * @param x the argument + * @return the value erfc(x) + */ + public static double erfc(final double x) { + //if (type==1) { + return erfcAS(x); + //} + //return erfcCody(x); + } + + /*** + * Internal helper method to calculate the error function at value x. + * This code is based on a Fortran implementation from + * W. J. Cody. + * Refactored by N.Wulff for Java. + * + * Cody vs AS algiorithm + * + * @param x the argument + * @return the approximation of erf(x) + */ + private static double erfCody(final double x) { + double result = 0; + double y = Math.abs(x); + if (firstCall) { + firstCall = false; + } + if (y <= THRESHOLD) { + result = x * calcLower(y); + } else { + result = calcUpper(y); + result = (0.5 - result) + 0.5; + if (x < 0) { + result = -result; + } + } + return result; + } + + /*** + * Internal helper method to calculate the erfc functions. + * This code is based on a Fortran implementation from + * W. J. Cody. + * Refactored by N.Wulff for Java. + * + * @param x the argument + * @return the approximation erfc(x) + */ + private static double erfcCody(final double x) { + double result = 0; + double y = Math.abs(x); + if (firstCall) { + firstCall = false; + } + if (y <= THRESHOLD) { + result = x * calcLower(y); + result = 1 - result; + } else { + result = calcUpper(y); + if (x < 0) { + result = 2.0 - result; } - return error <= eps; - } - - /*** - * Rational approximation A&S 7.1.26 with accuracy 1.5E-7. - * - * @param x the argument - * @return erf(x) - */ - private static double erfRational(final double x) { - /* coefficients for A&S 7.1.26. */ - final double[] a = { .254829592, -.284496736, 1.421413741, - -1.453152027, 1.061405429 }; - /* constant for A&S 7.1.26 */ - final double p = .3275911; - double erf, r = 0, t = 1.0/(1+p*x); - for (int i = 4; i>=0; i--) { - r = a[i]+r*t; - } - erf = 1-t*r*Math.exp(-x*x); - return erf; - } - - // =========================================================================== - /*** Nominator coefficients for approximation to erf in first interval. */ - private static final double[] ERF_A = { 3.16112374387056560E00, - 1.13864154151050156E02, 3.77485237685302021E02, - 3.20937758913846947E03, 1.85777706184603153E-1 }; - /*** Denominator coefficients for approximation to erf in first interval. */ - private static final double[] ERF_B = { 2.36012909523441209E01, - 2.44024637934444173E02, 1.28261652607737228E03, - 2.84423683343917062E03 }; - // =========================================================================== - /*** Nominator coefficients for approximation to erfc in second interval. */ - private static final double[] ERF_C = { 5.64188496988670089E-1, - 8.88314979438837594E0, 6.61191906371416295E01, - 2.98635138197400131E02, 8.81952221241769090E02, - 1.71204761263407058E03, 2.05107837782607147E03, - 1.23033935479799725E03, 2.15311535474403846E-8 }; - /*** Denominator coefficients for approximation to erfc in second interval.*/ - private static final double[] ERF_D = { 1.57449261107098347E01, - 1.17693950891312499E02, 5.37181101862009858E02, - 1.62138957456669019E03, 3.29079923573345963E03, - 4.36261909014324716E03, 3.43936767414372164E03, - 1.23033935480374942E03 }; - // =========================================================================== - /*** Nominator coefficients for approximation to erfc in third interval. */ - private static final double[] ERF_P = { 3.05326634961232344E-1, - 3.60344899949804439E-1, 1.25781726111229246E-1, - 1.60837851487422766E-2, 6.58749161529837803E-4, - 1.63153871373020978E-2 }; - /*** Denominator coefficients for approximation to erfc in third interval. */ - private static final double[] ERF_Q = { 2.56852019228982242, - 1.87295284992346047, 5.27905102951428412E-1, - 6.05183413124413191E-2, 2.33520497626869185E-3 }; - // =========================================================================== - static final double THRESHOLD = 0.46875; - static final double SQRPI = 1./PI_SQRT; - static final double X_INF = Double.MAX_VALUE; - static final double X_MIN = Double.MIN_VALUE; - // private static final double X_NEG = -9.38241396824444; - static final double X_NEG = -Math.sqrt(Math.log(X_INF/2)); - static final double X_SMALL = getDEPS(); - static final double X_HUGE = 1.0/(2.0*Math.sqrt(X_SMALL)); - static final double X_MAX = Math.min(X_INF, (1.0/(PI_SQRT*X_MIN))); - // static final double X_BIG = 9.194E0; - static final double X_BIG = 26.543; - - private static final float FEPS_START = 2.E-6f; - /** double valued machine precision. */ - /** float valued machine precision. */ - //public static final float FEPS; - static double getDEPS() { - /** - * Calculate the machine accuracy, - * which is the smallest eps with - * 1<1+eps - */ - float feps = FEPS_START; - float fy = 1.0f + feps; - while (fy > 1.0f) { - feps /= 2.0f; - fy = 1.0f + feps; - } - //FEPS = feps; - double deps = feps * FEPS_START; - double dy = 1.0 + deps; - while (dy > 1.0) { - deps /= 2.0; - dy = 1.0 + deps; - } - //DEPS = deps; - //if (DEBUG) - - // Logger.logInfo(format("feps:%8.2E deps:%8.3G", FEPS, DEPS)); - return deps; - } + } + return result; + } + + /*** + * Internal helper method to calculate the erf/erfc functions. + * This code is based on a Fortran implementation from + * W. J. Cody. + * Refactored by N.Wulff for Java. + * + * @param y the value y=abs(x)<=THRESHOLD + * @return the series expansion + */ + private static double calcLower(final double y) { + double result; + double ySq; + double xNum; + double xDen; + ySq = 0.0; + if (y > X_SMALL) + ySq = y * y; + xNum = ERF_A[4] * ySq; + xDen = ySq; + for (int i = 0; i < 3; i++) { + xNum = (xNum + ERF_A[i]) * ySq; + xDen = (xDen + ERF_B[i]) * ySq; + } + result = (xNum + ERF_A[3]) / (xDen + ERF_B[3]); + return result; + } + + /*** + * Internal helper method to calculate the erf/erfc functions. + * This code is based on a Fortran implementation from + * W. J. Cody. + * Refactored by N.Wulff for Java. + * + * @param y the value y=abs(x)>THRESHOLD + * @return the series expansion + */ + private static double calcUpper(final double y) { + double result; + double ySq; + double xNum; + double xDen; + if (y <= 4.0) { + xNum = ERF_C[8] * y; + xDen = y; + for (int i = 0; i < 7; i++) { + xNum = (xNum + ERF_C[i]) * y; + xDen = (xDen + ERF_D[i]) * y; + } + result = (xNum + ERF_C[7]) / (xDen + ERF_D[7]); + } else { + result = 0.0; + if (y >= X_HUGE) { + result = SQRPI / y; + } else { + ySq = 1.0 / (y * y); + xNum = ERF_P[5] * ySq; + xDen = ySq; + for (int i = 0; i < 4; i++) { + xNum = (xNum + ERF_P[i]) * ySq; + xDen = (xDen + ERF_Q[i]) * ySq; + } + result = ySq * (xNum + ERF_P[4]) / (xDen + ERF_Q[4]); + result = (SQRPI - result) / y; + } + } + ySq = Math.round(y * 16.0) / 16.0; + double del = (y - ySq) * (y + ySq); + result = Math.exp(-ySq * ySq) * Math.exp(-del) * result; + return result; + } + + /*** + * Calculate the error function at value x. + * AS 7.1.5/7.1.26 + * @param x the argument + * @return the value erf(x) + */ + private static double erfAS(final double x) { + if (firstCall) { + firstCall = false; + } + if (x < 0) { + return -erfAS(-x); + } + if (x < 2) { + return erfSeries(x); + } + return erfRational(x); + } + + /*** + * Calculate the remaining erfc error function at value x. + * AS 7.1.5/7.1.26 + * @param x the argument + * @return the value erfc(x) + */ + private static double erfcAS(final double x) { + if (firstCall) { + firstCall = false; + } + return 1 - erfAS(x); + } + + /*** + * Series expansion from A&S 7.1.5. + * + * @param x the argument + * @return erf(x) + */ + private static double erfSeries(final double x) { + final double eps = 1.E-8; // we want only ~1.E-7 + final int kmax = 50; // this can be reached with ~30-40 + double an, ak = x; + double erfo, erf = ak; + int k = 1; + do { + erfo = erf; + ak *= -x * x / k; + an = ak / (2.0 * k + 1.0); + erf += an; + } while (!hasConverged(erf, erfo, eps, ++k, kmax)); + return TSQPI * erf; + } + + /** + * Indicate if an iterative algorithm has RELATIVE converged. + *
                + * Note: + * HasConverged throws an ArithmeticException if more than max calls + * have been made. Choose hasReacherAccuracy if this is not desired. + *
                + * + * @param xn the actual argument x[n] + * @param xo the older argument x[n-1] + * @param eps the accuracy to reach + * @param n the actual iteration counter + * @param max the maximal number of iterations + * @return flag indicating if accuracy is reached. + */ + public static boolean hasConverged(final double xn, final double xo, + final double eps, final int n, final int max) { + if (hasReachedAccuracy(xn, xo, eps)) { + return true; + } + if (n >= max) { + throw new ArithmeticException(); + } + return false; + } + + /** + * Indicate if xn and xo have the relative/absolute accuracy epsilon. + * In case that the true value is less than one this is based + * on the absolute difference, otherwise on the relative difference: + *
                +     *     2*|x[n]-x[n-1]|/|x[n]+x[n-1]| < eps
                +     * 
                + * + * @param xn the actual argument x[n] + * @param xo the older argument x[n-1] + * @param eps accuracy to reach + * @return flag indicating if accuracy is reached. + */ + public static boolean hasReachedAccuracy(final double xn, final double xo, + final double eps) { + double z = Math.abs(xn + xo) / 2; + double error = Math.abs(xn - xo); + if (z > 1) { + error /= z; + } + return error <= eps; + } + + /*** + * Rational approximation A&S 7.1.26 with accuracy 1.5E-7. + * + * @param x the argument + * @return erf(x) + */ + private static double erfRational(final double x) { + /* coefficients for A&S 7.1.26. */ + final double[] a = {.254829592, -.284496736, 1.421413741, + -1.453152027, 1.061405429}; + /* constant for A&S 7.1.26 */ + final double p = .3275911; + double erf, r = 0, t = 1.0 / (1 + p * x); + for (int i = 4; i >= 0; i--) { + r = a[i] + r * t; + } + erf = 1 - t * r * Math.exp(-x * x); + return erf; + } + + // =========================================================================== + /*** Nominator coefficients for approximation to erf in first interval. */ + private static final double[] ERF_A = {3.16112374387056560E00, + 1.13864154151050156E02, 3.77485237685302021E02, + 3.20937758913846947E03, 1.85777706184603153E-1}; + /*** Denominator coefficients for approximation to erf in first interval. */ + private static final double[] ERF_B = {2.36012909523441209E01, + 2.44024637934444173E02, 1.28261652607737228E03, + 2.84423683343917062E03}; + // =========================================================================== + /*** Nominator coefficients for approximation to erfc in second interval. */ + private static final double[] ERF_C = {5.64188496988670089E-1, + 8.88314979438837594E0, 6.61191906371416295E01, + 2.98635138197400131E02, 8.81952221241769090E02, + 1.71204761263407058E03, 2.05107837782607147E03, + 1.23033935479799725E03, 2.15311535474403846E-8}; + /*** Denominator coefficients for approximation to erfc in second interval.*/ + private static final double[] ERF_D = {1.57449261107098347E01, + 1.17693950891312499E02, 5.37181101862009858E02, + 1.62138957456669019E03, 3.29079923573345963E03, + 4.36261909014324716E03, 3.43936767414372164E03, + 1.23033935480374942E03}; + // =========================================================================== + /*** Nominator coefficients for approximation to erfc in third interval. */ + private static final double[] ERF_P = {3.05326634961232344E-1, + 3.60344899949804439E-1, 1.25781726111229246E-1, + 1.60837851487422766E-2, 6.58749161529837803E-4, + 1.63153871373020978E-2}; + /*** Denominator coefficients for approximation to erfc in third interval. */ + private static final double[] ERF_Q = {2.56852019228982242, + 1.87295284992346047, 5.27905102951428412E-1, + 6.05183413124413191E-2, 2.33520497626869185E-3}; + // =========================================================================== + static final double THRESHOLD = 0.46875; + static final double SQRPI = 1. / PI_SQRT; + static final double X_INF = Double.MAX_VALUE; + static final double X_MIN = Double.MIN_VALUE; + // private static final double X_NEG = -9.38241396824444; + static final double X_NEG = -Math.sqrt(Math.log(X_INF / 2)); + static final double X_SMALL = getDEPS(); + static final double X_HUGE = 1.0 / (2.0 * Math.sqrt(X_SMALL)); + static final double X_MAX = Math.min(X_INF, (1.0 / (PI_SQRT * X_MIN))); + // static final double X_BIG = 9.194E0; + static final double X_BIG = 26.543; + + private static final float FEPS_START = 2.E-6f; + /** double valued machine precision. */ + /** + * float valued machine precision. + */ + //public static final float FEPS; + static double getDEPS() { + /** + * Calculate the machine accuracy, + * which is the smallest eps with + * 1<1+eps + */ + float feps = FEPS_START; + float fy = 1.0f + feps; + while (fy > 1.0f) { + feps /= 2.0f; + fy = 1.0f + feps; + } + //FEPS = feps; + double deps = feps * FEPS_START; + double dy = 1.0 + deps; + while (dy > 1.0) { + deps /= 2.0; + dy = 1.0 + deps; + } + //DEPS = deps; + //if (DEBUG) + + // Logger.logInfo(format("feps:%8.2E deps:%8.3G", FEPS, DEPS)); + return deps; + } /* ERFC Returns the complementary error function */ -/** - GESTEP - Tests whether a number is greater than a threshold value - */ - protected static Ptg calcGEStep(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "GESTEP"); - - double number= operands[0].getDoubleVal(); - double step= 0; - if (operands.length > 1) - step= operands[1].getDoubleVal(); - int result= 0; - if (number >= step) - result= 1; - - PtgNumber pnum= new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from GESTEP= " + pnum.getString()); - return pnum; - } -/** - HEX2BIN - Converts a hexadecimal number to binary - */ - protected static Ptg calcHex2Bin(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "HEX2BIN"); - String hString= operands[0].getString().trim(); - // 10 digits (40 bits) at most - if (hString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - int places= 0; - if (operands.length > 1) - places= operands[1].getIntVal(); - - long dec; - String bString; - try { - dec= Long.parseLong(hString, 16); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 549755813888L) // 2^39 (= signed 40 bits) - dec-= 1099511627776L; // 2^40 (= signed 41 bits) - bString= Long.toBinaryString(dec); - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - if (dec < -512 /*0xFFFFFFFE00*/ || dec > 0x1FF || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - bString= bString.substring(Math.max(bString.length()-10, 0)); - } else if (places > 0) { - if (bString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - bString= ("0000000000" + bString); // maximum= 10 places - bString= bString.substring(bString.length()-places); - } - PtgStr pstr= new PtgStr(bString); - if (DEBUG) Logger.logInfo("Result from HEX2BIN= " + pstr.getString()); - return pstr; - } -/** - HEX2DEC - Converts a hexadecimal number to decimal - */ - protected static Ptg calcHex2Dec(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "HEX2DEC"); - String hString= operands[0].getString().trim(); - // 10 digits (40 bits) at most - if (hString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - long dec; - String oString; - try { - dec= Long.parseLong(hString, 16); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 549755813888L) // 2^39 (= signed 40 bits) - dec-= 1099511627776L; // 2^40 (= signed 41 bits) - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - PtgNumber pnum = new PtgNumber(dec); - if (DEBUG) Logger.logInfo("Result from HEX2DEC= " + pnum.getVal()); - return pnum; - } -/** - HEX2OCT - Converts a hexadecimal number to octal - */ - protected static Ptg calcHex2Oct(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "HEX2OCT"); - String hString= operands[0].getString().trim(); - // 10 digits (40 bits) at most - if (hString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - int places= 0; - if (operands.length > 1) - places= operands[1].getIntVal(); - - long dec; - String oString; - try { - dec= Long.parseLong(hString, 16); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 549755813888L) // 2^39 (= signed 40 bits) - dec-= 1099511627776L; // 2^40 (= signed 41 bits) - oString= Long.toOctalString(dec); - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - if (dec < -536870912L /*0xFFE0000000*/ || dec > 0x1FFFFFFF || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - oString= oString.substring(Math.max(oString.length()-10, 0)); - } else if (places > 0) { - if (oString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - oString= ("0000000000" + oString); // maximum= 10 places - oString= oString.substring(oString.length()-places); - } - PtgStr pstr= new PtgStr(oString); - if (DEBUG) Logger.logInfo("Result from HEX2OCT= " + pstr.getString()); - return pstr; - } -/** - * imParseComplexNumber - * - * used in the following Imaginary-based formulas to parse a complex number into its - * real and imaginary components. - * Throws a numberformat exception if the complex number is not in the format of: - * real + imaginary - * real - imaginary - * imaginary (real= 0) - * real (imaginary= 0) - * where imaginary coefficient n= ni or nj - * * - * */ -private static Complex imParseComplexNumber(String complexNumber) - throws NumberFormatException { - Complex c= new Complex(); - if (complexNumber.length() > 0) { - try { - int i= complexNumber.length(); - if (complexNumber.substring(i-1, i).equals("i") || - complexNumber.substring(i-1, i).equals("j")) { - c.suffix= complexNumber.substring(i-1, i); - i-=2; - while (i >= 0 && - !(complexNumber.substring(i, i+1).equals("+") || - complexNumber.substring(i, i+1).equals("-"))) - i--; - if (i < 0) { // case of "#i" or "#j" i.e. no real and no sign - complexNumber= "+" + complexNumber; - i++; - } - // get imaginary coefficient + sign - String s= complexNumber.substring(i, complexNumber.length()-1); - if (s.length()==1) { // only a sign; means that the coefficient==1 eg. real-j or real+i - s+= "1"; - } - c.imaginary= Double.parseDouble(s); - } - if (i > 0) - c.real= Double.parseDouble(complexNumber.substring(0, i)); - } - catch (Exception e) { throw new NumberFormatException(); } - } - return c; -} -/** - * imGetStr - * - * @param operands - * @return double formatted as an integer if no precision, otherwise rounds to 15 - */ - private static String imGetExcelStr(double d, int precision) { - String s; - if (d==(int)d) { - if ((int) d==1) return ""; - return String.valueOf((int) d); - } - // round to precision - default= 15 - double r= Math.pow(10, precision); - d*= r; - d= Math.round(d); - d/= r; - return String.valueOf(d); - } - private static String imGetExcelStr(double d) { - return imGetExcelStr(d, 15); - } -/** - IMABS - Returns the absolute value (modulus) of a complex number - */ - protected static Ptg calcImAbs(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "IMABS"); - String complexString= StringTool.allTrim(operands[0].getString()); - - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - - // Absolute of a complex number is: - // square root( real^2 + imaginary^2) - double result= Math.sqrt(Math.pow(c.real, 2) + Math.pow(c.imaginary, 2)); - - PtgNumber pnum= new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from IMABS= " + pnum.getString()); - return pnum; - } -/** - IMAGINARY - Returns the imaginary coefficient of a complex number -*/ - protected static Ptg calcImaginary(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "Imaginary"); - String complexString= StringTool.allTrim(operands[0].getString()); - - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - - PtgNumber pnum= new PtgNumber(c.imaginary); - if (DEBUG) Logger.logInfo("Result from IMAGINARY= " + pnum.getString()); - return pnum; - } -/** - IMARGUMENT - Returns the argument theta, an angle expressed in radians -*/ - protected static Ptg calcImArgument(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImArgument"); - String complexString= StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - - double result= Math.atan(c.imaginary/c.real); - - PtgNumber pnum= new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from IMARGUMENT= " + pnum.getString()); - return pnum; - } -/** - IMCONJUGATE - Returns the complex conjugate of a complex number - */ - protected static Ptg calcImConjugate(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImCongugate"); - String complexString= StringTool.allTrim(operands[0].getString()); - - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - - String congugate; - if (c.real!=0 && c.imaginary!=0) - congugate= imGetExcelStr(c.real) + (c.imaginary<0?"+":"-") + imGetExcelStr(Math.abs(c.imaginary)) + c.suffix; - else if (c.real==0) - congugate= imGetExcelStr(Math.abs(c.imaginary)) + c.suffix; - else - congugate= imGetExcelStr(c.real); - PtgStr pstr= new PtgStr(congugate); - if (DEBUG) Logger.logInfo("Result from IMCONGUGATE= " + pstr.getString()); - return pstr; - } -/** - IMCOS - Returns the cosine of a complex number -*/ - protected static Ptg calcImCos(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImCos"); - String complexString= StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - // cos(a + bi)= cos(a)cosh(b) - sin(a)sinh(b)i - double cosh= (Math.pow(Math.E, c.imaginary) + Math.pow(Math.E, c.imaginary*-1))/2; - double sinh= (Math.pow(Math.E, c.imaginary) - Math.pow(Math.E, c.imaginary*-1))/2; - double a= Math.cos(c.real)*cosh; - double b= Math.sin(c.real)*sinh; - - String imCos; - if (b < 0) - imCos= imGetExcelStr(a) + "+" + imGetExcelStr(Math.abs(b)) + c.suffix; - else - imCos= imGetExcelStr(a) + "-" + imGetExcelStr(b) + c.suffix; - - PtgStr pstr= new PtgStr(imCos); - if (DEBUG) Logger.logInfo("Result from IMCOS= " + pstr.getString()); - return pstr; - } -/** - IMDIV - Returns the quotient of two complex numbers, defined as (deep breath): - - (r1 + i1j)/(r2 + i2j) == ( r1r2 + i1i2 + (r2i1 - r1i2)i ) / (r2^2 + i2^2) - */ - protected static Ptg calcImDiv(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "IMDIV"); - String complexString1= StringTool.allTrim(operands[0].getString()); - String complexString2= StringTool.allTrim(operands[1].getString()); - - Complex c1, c2; - - try { - c1= imParseComplexNumber(complexString1); - c2= imParseComplexNumber(complexString2); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - - double divisor= Math.pow(c2.real,2) + Math.pow(c2.imaginary,2); - double a= c1.real * c2.real + c1.imaginary * c2.imaginary; - double b= c1.imaginary * c2.real - c1.real * c2.imaginary; - double c= a/divisor; - double d= b/divisor; - - String imDiv; - if (d > 0) - imDiv= imGetExcelStr(c) + "+" + imGetExcelStr(d) + "i"; - else - imDiv= imGetExcelStr(c) + "-" + imGetExcelStr(d) + "i"; - - PtgStr pstr= new PtgStr(imDiv); - if (DEBUG) Logger.logInfo("Result from IMDIV= " + pstr.getString()); - return pstr; - } -/** - IMEXP - Returns the exponential of a complex number - */ - protected static Ptg calcImExp(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImExp"); - String complexString= StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - // Exponential of a complex number x+yi = e^x(cos(y) + sin(y)i) - double e_x= Math.pow(Math.E, c.real); - double a= e_x*Math.cos(c.imaginary); - double b= e_x*Math.sin(c.imaginary); - - String imExp; - if (b > 0) - imExp= imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imExp= imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr= new PtgStr(imExp); - if (DEBUG) Logger.logInfo("Result from IMEXP= " + pstr.getString()); - return pstr; - } - -/** - IMLN - Returns the natural logarithm of a complex number -*/ - protected static Ptg calcImLn(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImLn"); - String complexString= StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - // Natural log of a complex number= - // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i - double a= Math.pow(c.real,2)+Math.pow(c.imaginary,2); - a= Math.sqrt(a); - a= Math.log(a); - double b= Math.atan(c.imaginary/c.real); - - String imLn; - if (b > 0) - imLn= imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imLn= imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr= new PtgStr(imLn); - if (DEBUG) Logger.logInfo("Result from IMLN= " + pstr.getString()); - return pstr; - } -/** - IMLOG10 - Returns the base-10 logarithm of a complex number - */ - protected static Ptg calcImLog10(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImLog10"); - String complexString= StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - // Natural log of a complex number= - // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i - double a= Math.pow(c.real,2)+Math.pow(c.imaginary,2); - a= Math.sqrt(a); - a= Math.log(a); - double b= Math.atan(c.imaginary/c.real); - // now, convert to base 10 log: - double logE= Math.log(Math.E)/Math.log(10); - a= a*logE; - b= b*logE; - - String imLog10; - if (b > 0) - imLog10= imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imLog10= imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr= new PtgStr(imLog10); - if (DEBUG) Logger.logInfo("Result from IMLOG10= " + pstr.getString()); - return pstr; - } -/** - IMLOG2 - Returns the base-2 logarithm of a complex number - */ - protected static Ptg calcImLog2(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImLog2"); - String complexString= StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - // Natural log of a complex number= - // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i - double a= Math.pow(c.real,2)+Math.pow(c.imaginary,2); - a= Math.sqrt(a); - a= Math.log(a); - double b= Math.atan(c.imaginary/c.real); - // now, convert to base 2 log: - double logE= Math.log(Math.E)/Math.log(2); - a= a*logE; - b= b*logE; - // TODO: Results only correct to 8th precision: WHY??? - String imLog2; - if (b > 0) - imLog2= imGetExcelStr(a, 8) + "+" + imGetExcelStr(b, 8) + c.suffix; - else - imLog2= imGetExcelStr(a, 8) + imGetExcelStr(b, 8) + c.suffix; - - PtgStr pstr= new PtgStr(imLog2); - if (DEBUG) Logger.logInfo("Result from IMLOG2= " + pstr.getString()); - return pstr; - } -/** - IMPOWER - Returns a complex number raised to any power -*/ - protected static Ptg calcImPower(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImPower"); - String complexString= StringTool.allTrim(operands[0].getString()); - double n= operands[1].getDoubleVal(); - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - - // A complex number (x + yi) raised to a power n is: - // sqrt(x^2 + y^2)*cos(n*atan(y/x)) + sqrt(x^2 + y^2)*sin(n*atan(y/x))i - double r= Math.pow(c.real,2)+Math.pow(c.imaginary,2); - r= Math.sqrt(r); - r= Math.pow(r, n); - double t= Math.atan(c.imaginary/c.real); - double a= r*Math.cos(n*t); - double b= r*Math.sin(n*t); - - String imPower; - if (b > 0) - imPower= imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imPower= imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr= new PtgStr(imPower); - if (DEBUG) Logger.logInfo("Result from IMPOWER= " + pstr.getString()); - return pstr; - } - -/** - IMPRODUCT - Returns the product of two complex numbers -*/ - protected static Ptg calcImProduct(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - - if (DEBUG) debugOperands(operands, "IMPRODUCT"); - Ptg[] ops= PtgCalculator.getAllComponents(operands); - String[] complexStrings= new String[ops.length]; - for (int i= 0; i < ops.length; i++) - complexStrings[i]= StringTool.allTrim(ops[i].getString()); - - Complex[] c= new Complex[complexStrings.length]; - for (int i= 0; i < complexStrings.length; i++) { - try { - c[i]= imParseComplexNumber(complexStrings[i]); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - } - - // basically, linear binomial multiplication over n terms - // (a + bi)(c + di) = (ac-bd) + (ad+bc)i for n terms - for (int i= 1; i < c.length; i++) { - double a= c[0].real; - double b= c[0].imaginary; - c[0].real= a*c[i].real - b*c[i].imaginary; - c[0].imaginary= a*c[i].imaginary + b*c[i].real; - } - - // Format Result - String imSum; - if (c[0].imaginary > 0) - imSum= imGetExcelStr(c[0].real) + "+" + imGetExcelStr(c[0].imaginary) + c[0].suffix; - else - imSum= imGetExcelStr(c[0].real) + imGetExcelStr(c[0].imaginary) + c[0].suffix; - - PtgStr pstr= new PtgStr(imSum); - if (DEBUG) Logger.logInfo("Result from IMSPRODUCT= " + pstr.getString()); - return pstr; - } -/** - IMREAL - Returns the real coefficient of a complex number - */ - protected static Ptg calcImReal(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "IMREAL"); - String complexString= StringTool.allTrim(operands[0].getString()); - - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - - PtgNumber pnum= new PtgNumber(c.real); - if (DEBUG) Logger.logInfo("Result from IMREAL= " + pnum.getString()); - return pnum; - } -/** - IMSIN - Returns the sine of a complex number - */ - protected static Ptg calcImSin(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImSin"); - String complexString= StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - // sin(a + bi)= sin(a)cosh(b) + cos(a)sinh(b)i (Excel doc is wrong!) - double cosh= (Math.pow(Math.E, c.imaginary) + Math.pow(Math.E, c.imaginary*-1))/2; - double sinh= (Math.pow(Math.E, c.imaginary) - Math.pow(Math.E, c.imaginary*-1))/2; - double a= Math.sin(c.real)*cosh; - double b= Math.cos(c.real)*sinh; - - String imSin; - if (b > 0) - imSin= imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imSin= imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr= new PtgStr(imSin); - if (DEBUG) Logger.logInfo("Result from IMSIN= " + pstr.getString()); - return pstr; - } -/** - IMSQRT - Returns the square root of a complex number - */ - protected static Ptg calcImSqrt(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImSqrt"); - String complexString= StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c= imParseComplexNumber(complexString); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - - // The square root of a complex number (x + yi) is: - // sqrt(sqrt(x^2 + y^2))*cos(atan(y/x)/2) + - // sqrt(sqrt(x^2 + y^2))*sin(atan(y/x)/2)i - double r= Math.pow(c.real,2)+Math.pow(c.imaginary,2); - r= Math.sqrt(r); - r= Math.sqrt(r); - double t= Math.atan(c.imaginary/c.real); - double a= r*Math.cos(t/2); - double b= r*Math.sin(t/2); - - String imSqrt; - if (b > 0) - imSqrt= imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imSqrt= imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr= new PtgStr(imSqrt); - if (DEBUG) Logger.logInfo("Result from IMSQRT= " + pstr.getString()); - return pstr; - } - - -/** - IMSUB - Returns the difference of two complex numbers - */ - protected static Ptg calcImSub(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "IMSUB"); - String complexString1= StringTool.allTrim(operands[0].getString()); - String complexString2= StringTool.allTrim(operands[1].getString()); - - Complex c1, c2; - try { - c1= imParseComplexNumber(complexString1); - c2= imParseComplexNumber(complexString2); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - - // basically, linear binomial subtraction: - // (a + bi) - (c + di)= (a-c) + (b-d)i - double a= c1.real - c2.real; - double b= c1.imaginary- c2.imaginary; - - String imSub; - if (b > 0) - imSub= imGetExcelStr(a) + "+" + imGetExcelStr(b) + c1.suffix; // should have the same suffix - else - imSub= imGetExcelStr(a) + imGetExcelStr(b) + c1.suffix; - - PtgStr pstr= new PtgStr(imSub); - if (DEBUG) Logger.logInfo("Result from IMSUB= " + pstr.getString()); - return pstr; - } - /** - IMSUM - Returns the sum of complex numbers - */ - protected static Ptg calcImSum(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - - if (DEBUG) debugOperands(operands, "IMSUM"); - Ptg[] ops= PtgCalculator.getAllComponents(operands); - String[] complexStrings= new String[ops.length]; - for (int i= 0; i < ops.length; i++) - complexStrings[i]= StringTool.allTrim(ops[i].getString()); - - Complex[] c= new Complex[complexStrings.length]; - for (int i= 0; i < complexStrings.length; i++) { - try { - c[i]= imParseComplexNumber(complexStrings[i]); - } - catch (Exception e) { return new PtgErr(PtgErr.ERROR_NUM); } - } - - // basically, linear binomial addition over n terms - // (a + bi)+(c + di) = (a+c) + (b+d)i for n terms - for (int i= 1; i < c.length; i++) { - c[0].real= c[0].real+c[i].real; - c[0].imaginary= c[0].imaginary + c[i].imaginary; - } - - // Format Result - String imSum; - if (c[0].imaginary > 0) - imSum= imGetExcelStr(c[0].real) + "+" + imGetExcelStr(c[0].imaginary) + c[0].suffix; - else - imSum= imGetExcelStr(c[0].real) + imGetExcelStr(c[0].imaginary) + c[0].suffix; - - PtgStr pstr= new PtgStr(imSum); - if (DEBUG) Logger.logInfo("Result from IMSUM= " + pstr.getString()); - return pstr; - } - -/** - OCT2BIN - Converts an octal number to binary - */ - protected static Ptg calcOct2Bin(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "OCT2BIN"); - long l= (long) operands[0].getDoubleVal(); // avoid sci notation - String oString= String.valueOf(l).trim(); - // 10 digits at most (=30 bits) - if (oString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - int places= 0; - if (operands.length > 1) - places= operands[1].getIntVal(); - - long dec; - String bString; - try { - dec= Long.parseLong(oString, 8); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 536870912L) // 2^29 (= 30 bits, signed) - dec-= 1073741824L; // 2^30 (= 31 bits, signed) - bString= Long.toBinaryString(dec); - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - if (dec < -512 /*7777777000*/ || dec > 0777 || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - bString= bString.substring(Math.max(bString.length()-10, 0)); - } else if (places > 0) { - if (bString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - bString= ("0000000000" + bString); // maximum= 10 places - bString= bString.substring(bString.length()-places); - } - PtgStr pstr= new PtgStr(bString); - if (DEBUG) Logger.logInfo("Result from OCT2BIN= " + pstr.getString()); - return pstr; - } -/** - OCT2DEC - Converts an octal number to decimal - */ - protected static Ptg calcOct2Dec(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "OCT2DEC"); - long l= (long) operands[0].getDoubleVal(); // avoid sci notation - String oString= String.valueOf(l).trim(); - // 10 digits at most (=30 bits) - if (oString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - long dec; - try { - dec= Long.parseLong(oString, 8); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 536870912L) // 2^29 (= 30 bits, signed) - dec-= 1073741824L; // 2^30 (= 31 bits, signed) - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - PtgNumber pnum = new PtgNumber(dec); - if (DEBUG) Logger.logInfo("Result from OCT2DEC= " + pnum.getVal()); - return pnum; - } -/** - OCT2HEX - Converts an octal number to hexadecimal - */ - protected static Ptg calcOct2Hex(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "OCT2HEX"); - long l= (long) operands[0].getDoubleVal(); // avoid sci notation - String oString= String.valueOf(l).trim(); - // 10 digits at most (=30 bits) - if (oString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - int places= 0; - if (operands.length > 1) - places= operands[1].getIntVal(); - - long dec; - String hString; - try { - dec= Long.parseLong(oString, 8); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 536870912L) // 2^29 (= 30 bits, signed) - dec-= 1073741824L; // 2^30 (= 31 bits, signed) - hString = Long.toHexString(dec).toUpperCase(); - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - hString= hString.substring(Math.max(hString.length()-10, 0)); - } else if (places > 0) { - if (hString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - hString= ("0000000000" + hString); // maximum= 10 bits - hString= hString.substring(hString.length()-places); - } - - PtgStr pstr= new PtgStr(hString); - if (DEBUG) Logger.logInfo("Result from OCT2HEX= " + pstr.getString()); - return pstr; - } -/* + + /** + * GESTEP + * Tests whether a number is greater than a threshold value + */ + protected static Ptg calcGEStep(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "GESTEP"); + + double number = operands[0].getDoubleVal(); + double step = 0; + if (operands.length > 1) + step = operands[1].getDoubleVal(); + int result = 0; + if (number >= step) + result = 1; + + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) Logger.logInfo("Result from GESTEP= " + pnum.getString()); + return pnum; + } + + /** + * HEX2BIN + * Converts a hexadecimal number to binary + */ + protected static Ptg calcHex2Bin(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "HEX2BIN"); + String hString = operands[0].getString().trim(); + // 10 digits (40 bits) at most + if (hString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); + int places = 0; + if (operands.length > 1) + places = operands[1].getIntVal(); + + long dec; + String bString; + try { + dec = Long.parseLong(hString, 16); + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 549755813888L) // 2^39 (= signed 40 bits) + dec -= 1099511627776L; // 2^40 (= signed 41 bits) + bString = Long.toBinaryString(dec); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + if (dec < -512 /*0xFFFFFFFE00*/ || dec > 0x1FF || places < 0) return new PtgErr(PtgErr.ERROR_NUM); + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + bString = bString.substring(Math.max(bString.length() - 10, 0)); + } else if (places > 0) { + if (bString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); + bString = ("0000000000" + bString); // maximum= 10 places + bString = bString.substring(bString.length() - places); + } + PtgStr pstr = new PtgStr(bString); + if (DEBUG) Logger.logInfo("Result from HEX2BIN= " + pstr.getString()); + return pstr; + } + + /** + * HEX2DEC + * Converts a hexadecimal number to decimal + */ + protected static Ptg calcHex2Dec(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "HEX2DEC"); + String hString = operands[0].getString().trim(); + // 10 digits (40 bits) at most + if (hString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); + long dec; + String oString; + try { + dec = Long.parseLong(hString, 16); + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 549755813888L) // 2^39 (= signed 40 bits) + dec -= 1099511627776L; // 2^40 (= signed 41 bits) + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + PtgNumber pnum = new PtgNumber(dec); + if (DEBUG) Logger.logInfo("Result from HEX2DEC= " + pnum.getVal()); + return pnum; + } + + /** + * HEX2OCT + * Converts a hexadecimal number to octal + */ + protected static Ptg calcHex2Oct(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "HEX2OCT"); + String hString = operands[0].getString().trim(); + // 10 digits (40 bits) at most + if (hString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); + int places = 0; + if (operands.length > 1) + places = operands[1].getIntVal(); + + long dec; + String oString; + try { + dec = Long.parseLong(hString, 16); + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 549755813888L) // 2^39 (= signed 40 bits) + dec -= 1099511627776L; // 2^40 (= signed 41 bits) + oString = Long.toOctalString(dec); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + if (dec < -536870912L /*0xFFE0000000*/ || dec > 0x1FFFFFFF || places < 0) return new PtgErr(PtgErr.ERROR_NUM); + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + oString = oString.substring(Math.max(oString.length() - 10, 0)); + } else if (places > 0) { + if (oString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); + oString = ("0000000000" + oString); // maximum= 10 places + oString = oString.substring(oString.length() - places); + } + PtgStr pstr = new PtgStr(oString); + if (DEBUG) Logger.logInfo("Result from HEX2OCT= " + pstr.getString()); + return pstr; + } + + /** + * imParseComplexNumber + *

                + * used in the following Imaginary-based formulas to parse a complex number into its + * real and imaginary components. + * Throws a numberformat exception if the complex number is not in the format of: + * real + imaginary + * real - imaginary + * imaginary (real= 0) + * real (imaginary= 0) + * where imaginary coefficient n= ni or nj + * * + */ + private static Complex imParseComplexNumber(String complexNumber) + throws NumberFormatException { + Complex c = new Complex(); + if (complexNumber.length() > 0) { + try { + int i = complexNumber.length(); + if (complexNumber.substring(i - 1, i).equals("i") || + complexNumber.substring(i - 1, i).equals("j")) { + c.suffix = complexNumber.substring(i - 1, i); + i -= 2; + while (i >= 0 && + !(complexNumber.substring(i, i + 1).equals("+") || + complexNumber.substring(i, i + 1).equals("-"))) + i--; + if (i < 0) { // case of "#i" or "#j" i.e. no real and no sign + complexNumber = "+" + complexNumber; + i++; + } + // get imaginary coefficient + sign + String s = complexNumber.substring(i, complexNumber.length() - 1); + if (s.length() == 1) { // only a sign; means that the coefficient==1 eg. real-j or real+i + s += "1"; + } + c.imaginary = Double.parseDouble(s); + } + if (i > 0) + c.real = Double.parseDouble(complexNumber.substring(0, i)); + } catch (Exception e) { + throw new NumberFormatException(); + } + } + return c; + } + + /** + * imGetStr + * + * @param operands + * @return double formatted as an integer if no precision, otherwise rounds to 15 + */ + private static String imGetExcelStr(double d, int precision) { + String s; + if (d == (int) d) { + if ((int) d == 1) return ""; + return String.valueOf((int) d); + } + // round to precision - default= 15 + double r = Math.pow(10, precision); + d *= r; + d = Math.round(d); + d /= r; + return String.valueOf(d); + } + + private static String imGetExcelStr(double d) { + return imGetExcelStr(d, 15); + } + + /** + * IMABS + * Returns the absolute value (modulus) of a complex number + */ + protected static Ptg calcImAbs(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "IMABS"); + String complexString = StringTool.allTrim(operands[0].getString()); + + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + // Absolute of a complex number is: + // square root( real^2 + imaginary^2) + double result = Math.sqrt(Math.pow(c.real, 2) + Math.pow(c.imaginary, 2)); + + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) Logger.logInfo("Result from IMABS= " + pnum.getString()); + return pnum; + } + + /** + * IMAGINARY + * Returns the imaginary coefficient of a complex number + */ + protected static Ptg calcImaginary(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "Imaginary"); + String complexString = StringTool.allTrim(operands[0].getString()); + + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + PtgNumber pnum = new PtgNumber(c.imaginary); + if (DEBUG) Logger.logInfo("Result from IMAGINARY= " + pnum.getString()); + return pnum; + } + + /** + * IMARGUMENT + * Returns the argument theta, an angle expressed in radians + */ + protected static Ptg calcImArgument(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImArgument"); + String complexString = StringTool.allTrim(operands[0].getString()); + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + double result = Math.atan(c.imaginary / c.real); + + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) Logger.logInfo("Result from IMARGUMENT= " + pnum.getString()); + return pnum; + } + + /** + * IMCONJUGATE + * Returns the complex conjugate of a complex number + */ + protected static Ptg calcImConjugate(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImCongugate"); + String complexString = StringTool.allTrim(operands[0].getString()); + + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + String congugate; + if (c.real != 0 && c.imaginary != 0) + congugate = imGetExcelStr(c.real) + (c.imaginary < 0 ? "+" : "-") + imGetExcelStr(Math.abs(c.imaginary)) + c.suffix; + else if (c.real == 0) + congugate = imGetExcelStr(Math.abs(c.imaginary)) + c.suffix; + else + congugate = imGetExcelStr(c.real); + PtgStr pstr = new PtgStr(congugate); + if (DEBUG) Logger.logInfo("Result from IMCONGUGATE= " + pstr.getString()); + return pstr; + } + + /** + * IMCOS + * Returns the cosine of a complex number + */ + protected static Ptg calcImCos(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImCos"); + String complexString = StringTool.allTrim(operands[0].getString()); + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + // cos(a + bi)= cos(a)cosh(b) - sin(a)sinh(b)i + double cosh = (Math.pow(Math.E, c.imaginary) + Math.pow(Math.E, c.imaginary * -1)) / 2; + double sinh = (Math.pow(Math.E, c.imaginary) - Math.pow(Math.E, c.imaginary * -1)) / 2; + double a = Math.cos(c.real) * cosh; + double b = Math.sin(c.real) * sinh; + + String imCos; + if (b < 0) + imCos = imGetExcelStr(a) + "+" + imGetExcelStr(Math.abs(b)) + c.suffix; + else + imCos = imGetExcelStr(a) + "-" + imGetExcelStr(b) + c.suffix; + + PtgStr pstr = new PtgStr(imCos); + if (DEBUG) Logger.logInfo("Result from IMCOS= " + pstr.getString()); + return pstr; + } + + /** + * IMDIV + * Returns the quotient of two complex numbers, defined as (deep breath): + *

                + * (r1 + i1j)/(r2 + i2j) == ( r1r2 + i1i2 + (r2i1 - r1i2)i ) / (r2^2 + i2^2) + */ + protected static Ptg calcImDiv(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "IMDIV"); + String complexString1 = StringTool.allTrim(operands[0].getString()); + String complexString2 = StringTool.allTrim(operands[1].getString()); + + Complex c1, c2; + + try { + c1 = imParseComplexNumber(complexString1); + c2 = imParseComplexNumber(complexString2); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + double divisor = Math.pow(c2.real, 2) + Math.pow(c2.imaginary, 2); + double a = c1.real * c2.real + c1.imaginary * c2.imaginary; + double b = c1.imaginary * c2.real - c1.real * c2.imaginary; + double c = a / divisor; + double d = b / divisor; + + String imDiv; + if (d > 0) + imDiv = imGetExcelStr(c) + "+" + imGetExcelStr(d) + "i"; + else + imDiv = imGetExcelStr(c) + "-" + imGetExcelStr(d) + "i"; + + PtgStr pstr = new PtgStr(imDiv); + if (DEBUG) Logger.logInfo("Result from IMDIV= " + pstr.getString()); + return pstr; + } + + /** + * IMEXP + * Returns the exponential of a complex number + */ + protected static Ptg calcImExp(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImExp"); + String complexString = StringTool.allTrim(operands[0].getString()); + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + // Exponential of a complex number x+yi = e^x(cos(y) + sin(y)i) + double e_x = Math.pow(Math.E, c.real); + double a = e_x * Math.cos(c.imaginary); + double b = e_x * Math.sin(c.imaginary); + + String imExp; + if (b > 0) + imExp = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; + else + imExp = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; + + PtgStr pstr = new PtgStr(imExp); + if (DEBUG) Logger.logInfo("Result from IMEXP= " + pstr.getString()); + return pstr; + } + + /** + * IMLN + * Returns the natural logarithm of a complex number + */ + protected static Ptg calcImLn(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImLn"); + String complexString = StringTool.allTrim(operands[0].getString()); + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + // Natural log of a complex number= + // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i + double a = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); + a = Math.sqrt(a); + a = Math.log(a); + double b = Math.atan(c.imaginary / c.real); + + String imLn; + if (b > 0) + imLn = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; + else + imLn = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; + + PtgStr pstr = new PtgStr(imLn); + if (DEBUG) Logger.logInfo("Result from IMLN= " + pstr.getString()); + return pstr; + } + + /** + * IMLOG10 + * Returns the base-10 logarithm of a complex number + */ + protected static Ptg calcImLog10(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImLog10"); + String complexString = StringTool.allTrim(operands[0].getString()); + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + // Natural log of a complex number= + // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i + double a = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); + a = Math.sqrt(a); + a = Math.log(a); + double b = Math.atan(c.imaginary / c.real); + // now, convert to base 10 log: + double logE = Math.log(Math.E) / Math.log(10); + a = a * logE; + b = b * logE; + + String imLog10; + if (b > 0) + imLog10 = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; + else + imLog10 = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; + + PtgStr pstr = new PtgStr(imLog10); + if (DEBUG) Logger.logInfo("Result from IMLOG10= " + pstr.getString()); + return pstr; + } + + /** + * IMLOG2 + * Returns the base-2 logarithm of a complex number + */ + protected static Ptg calcImLog2(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImLog2"); + String complexString = StringTool.allTrim(operands[0].getString()); + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + // Natural log of a complex number= + // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i + double a = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); + a = Math.sqrt(a); + a = Math.log(a); + double b = Math.atan(c.imaginary / c.real); + // now, convert to base 2 log: + double logE = Math.log(Math.E) / Math.log(2); + a = a * logE; + b = b * logE; + // TODO: Results only correct to 8th precision: WHY??? + String imLog2; + if (b > 0) + imLog2 = imGetExcelStr(a, 8) + "+" + imGetExcelStr(b, 8) + c.suffix; + else + imLog2 = imGetExcelStr(a, 8) + imGetExcelStr(b, 8) + c.suffix; + + PtgStr pstr = new PtgStr(imLog2); + if (DEBUG) Logger.logInfo("Result from IMLOG2= " + pstr.getString()); + return pstr; + } + + /** + * IMPOWER + * Returns a complex number raised to any power + */ + protected static Ptg calcImPower(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImPower"); + String complexString = StringTool.allTrim(operands[0].getString()); + double n = operands[1].getDoubleVal(); + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + // A complex number (x + yi) raised to a power n is: + // sqrt(x^2 + y^2)*cos(n*atan(y/x)) + sqrt(x^2 + y^2)*sin(n*atan(y/x))i + double r = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); + r = Math.sqrt(r); + r = Math.pow(r, n); + double t = Math.atan(c.imaginary / c.real); + double a = r * Math.cos(n * t); + double b = r * Math.sin(n * t); + + String imPower; + if (b > 0) + imPower = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; + else + imPower = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; + + PtgStr pstr = new PtgStr(imPower); + if (DEBUG) Logger.logInfo("Result from IMPOWER= " + pstr.getString()); + return pstr; + } + + /** + * IMPRODUCT + * Returns the product of two complex numbers + */ + protected static Ptg calcImProduct(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + + if (DEBUG) debugOperands(operands, "IMPRODUCT"); + Ptg[] ops = PtgCalculator.getAllComponents(operands); + String[] complexStrings = new String[ops.length]; + for (int i = 0; i < ops.length; i++) + complexStrings[i] = StringTool.allTrim(ops[i].getString()); + + Complex[] c = new Complex[complexStrings.length]; + for (int i = 0; i < complexStrings.length; i++) { + try { + c[i] = imParseComplexNumber(complexStrings[i]); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + } + + // basically, linear binomial multiplication over n terms + // (a + bi)(c + di) = (ac-bd) + (ad+bc)i for n terms + for (int i = 1; i < c.length; i++) { + double a = c[0].real; + double b = c[0].imaginary; + c[0].real = a * c[i].real - b * c[i].imaginary; + c[0].imaginary = a * c[i].imaginary + b * c[i].real; + } + + // Format Result + String imSum; + if (c[0].imaginary > 0) + imSum = imGetExcelStr(c[0].real) + "+" + imGetExcelStr(c[0].imaginary) + c[0].suffix; + else + imSum = imGetExcelStr(c[0].real) + imGetExcelStr(c[0].imaginary) + c[0].suffix; + + PtgStr pstr = new PtgStr(imSum); + if (DEBUG) Logger.logInfo("Result from IMSPRODUCT= " + pstr.getString()); + return pstr; + } + + /** + * IMREAL + * Returns the real coefficient of a complex number + */ + protected static Ptg calcImReal(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "IMREAL"); + String complexString = StringTool.allTrim(operands[0].getString()); + + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + PtgNumber pnum = new PtgNumber(c.real); + if (DEBUG) Logger.logInfo("Result from IMREAL= " + pnum.getString()); + return pnum; + } + + /** + * IMSIN + * Returns the sine of a complex number + */ + protected static Ptg calcImSin(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImSin"); + String complexString = StringTool.allTrim(operands[0].getString()); + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + // sin(a + bi)= sin(a)cosh(b) + cos(a)sinh(b)i (Excel doc is wrong!) + double cosh = (Math.pow(Math.E, c.imaginary) + Math.pow(Math.E, c.imaginary * -1)) / 2; + double sinh = (Math.pow(Math.E, c.imaginary) - Math.pow(Math.E, c.imaginary * -1)) / 2; + double a = Math.sin(c.real) * cosh; + double b = Math.cos(c.real) * sinh; + + String imSin; + if (b > 0) + imSin = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; + else + imSin = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; + + PtgStr pstr = new PtgStr(imSin); + if (DEBUG) Logger.logInfo("Result from IMSIN= " + pstr.getString()); + return pstr; + } + + /** + * IMSQRT + * Returns the square root of a complex number + */ + protected static Ptg calcImSqrt(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ImSqrt"); + String complexString = StringTool.allTrim(operands[0].getString()); + Complex c; + try { + c = imParseComplexNumber(complexString); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + // The square root of a complex number (x + yi) is: + // sqrt(sqrt(x^2 + y^2))*cos(atan(y/x)/2) + + // sqrt(sqrt(x^2 + y^2))*sin(atan(y/x)/2)i + double r = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); + r = Math.sqrt(r); + r = Math.sqrt(r); + double t = Math.atan(c.imaginary / c.real); + double a = r * Math.cos(t / 2); + double b = r * Math.sin(t / 2); + + String imSqrt; + if (b > 0) + imSqrt = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; + else + imSqrt = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; + + PtgStr pstr = new PtgStr(imSqrt); + if (DEBUG) Logger.logInfo("Result from IMSQRT= " + pstr.getString()); + return pstr; + } + + + /** + * IMSUB + * Returns the difference of two complex numbers + */ + protected static Ptg calcImSub(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "IMSUB"); + String complexString1 = StringTool.allTrim(operands[0].getString()); + String complexString2 = StringTool.allTrim(operands[1].getString()); + + Complex c1, c2; + try { + c1 = imParseComplexNumber(complexString1); + c2 = imParseComplexNumber(complexString2); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + // basically, linear binomial subtraction: + // (a + bi) - (c + di)= (a-c) + (b-d)i + double a = c1.real - c2.real; + double b = c1.imaginary - c2.imaginary; + + String imSub; + if (b > 0) + imSub = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c1.suffix; // should have the same suffix + else + imSub = imGetExcelStr(a) + imGetExcelStr(b) + c1.suffix; + + PtgStr pstr = new PtgStr(imSub); + if (DEBUG) Logger.logInfo("Result from IMSUB= " + pstr.getString()); + return pstr; + } + + /** + * IMSUM + * Returns the sum of complex numbers + */ + protected static Ptg calcImSum(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + + if (DEBUG) debugOperands(operands, "IMSUM"); + Ptg[] ops = PtgCalculator.getAllComponents(operands); + String[] complexStrings = new String[ops.length]; + for (int i = 0; i < ops.length; i++) + complexStrings[i] = StringTool.allTrim(ops[i].getString()); + + Complex[] c = new Complex[complexStrings.length]; + for (int i = 0; i < complexStrings.length; i++) { + try { + c[i] = imParseComplexNumber(complexStrings[i]); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + } + + // basically, linear binomial addition over n terms + // (a + bi)+(c + di) = (a+c) + (b+d)i for n terms + for (int i = 1; i < c.length; i++) { + c[0].real = c[0].real + c[i].real; + c[0].imaginary = c[0].imaginary + c[i].imaginary; + } + + // Format Result + String imSum; + if (c[0].imaginary > 0) + imSum = imGetExcelStr(c[0].real) + "+" + imGetExcelStr(c[0].imaginary) + c[0].suffix; + else + imSum = imGetExcelStr(c[0].real) + imGetExcelStr(c[0].imaginary) + c[0].suffix; + + PtgStr pstr = new PtgStr(imSum); + if (DEBUG) Logger.logInfo("Result from IMSUM= " + pstr.getString()); + return pstr; + } + + /** + * OCT2BIN + * Converts an octal number to binary + */ + protected static Ptg calcOct2Bin(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "OCT2BIN"); + long l = (long) operands[0].getDoubleVal(); // avoid sci notation + String oString = String.valueOf(l).trim(); + // 10 digits at most (=30 bits) + if (oString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); + int places = 0; + if (operands.length > 1) + places = operands[1].getIntVal(); + + long dec; + String bString; + try { + dec = Long.parseLong(oString, 8); + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 536870912L) // 2^29 (= 30 bits, signed) + dec -= 1073741824L; // 2^30 (= 31 bits, signed) + bString = Long.toBinaryString(dec); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + + if (dec < -512 /*7777777000*/ || dec > 0777 || places < 0) return new PtgErr(PtgErr.ERROR_NUM); + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + bString = bString.substring(Math.max(bString.length() - 10, 0)); + } else if (places > 0) { + if (bString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); + bString = ("0000000000" + bString); // maximum= 10 places + bString = bString.substring(bString.length() - places); + } + PtgStr pstr = new PtgStr(bString); + if (DEBUG) Logger.logInfo("Result from OCT2BIN= " + pstr.getString()); + return pstr; + } + + /** + * OCT2DEC + * Converts an octal number to decimal + */ + protected static Ptg calcOct2Dec(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "OCT2DEC"); + long l = (long) operands[0].getDoubleVal(); // avoid sci notation + String oString = String.valueOf(l).trim(); + // 10 digits at most (=30 bits) + if (oString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); + long dec; + try { + dec = Long.parseLong(oString, 8); + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 536870912L) // 2^29 (= 30 bits, signed) + dec -= 1073741824L; // 2^30 (= 31 bits, signed) + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + PtgNumber pnum = new PtgNumber(dec); + if (DEBUG) Logger.logInfo("Result from OCT2DEC= " + pnum.getVal()); + return pnum; + } + + /** + * OCT2HEX + * Converts an octal number to hexadecimal + */ + protected static Ptg calcOct2Hex(Ptg[] operands) { + if (operands.length < 1) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "OCT2HEX"); + long l = (long) operands[0].getDoubleVal(); // avoid sci notation + String oString = String.valueOf(l).trim(); + // 10 digits at most (=30 bits) + if (oString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); + int places = 0; + if (operands.length > 1) + places = operands[1].getIntVal(); + + long dec; + String hString; + try { + dec = Long.parseLong(oString, 8); + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 536870912L) // 2^29 (= 30 bits, signed) + dec -= 1073741824L; // 2^30 (= 31 bits, signed) + hString = Long.toHexString(dec).toUpperCase(); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NUM); + } + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + hString = hString.substring(Math.max(hString.length() - 10, 0)); + } else if (places > 0) { + if (hString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); + hString = ("0000000000" + hString); // maximum= 10 bits + hString = hString.substring(hString.length() - places); + } + + PtgStr pstr = new PtgStr(hString); + if (DEBUG) Logger.logInfo("Result from OCT2HEX= " + pstr.getString()); + return pstr; + } + + /* SQRTPI Returns the square root of (number * PI) -*/ - static void debugOperands(Ptg[] operands, String f) { - if (DEBUG) { - Logger.logInfo("Operands for " + f); - for (int i = 0; i < operands.length; i++) { - String s = operands[i].getString(); - if (!(operands[i] instanceof PtgMissArg)) { - String v = operands[i].getValue().toString(); - Logger.logInfo("\tOperand[" + i + "]=" + s + " " + v); - } else - Logger.logInfo("\tOperand[" + i + "]=" + s + " is Missing"); - } - } - } +*/ + static void debugOperands(Ptg[] operands, String f) { + if (DEBUG) { + Logger.logInfo("Operands for " + f); + for (int i = 0; i < operands.length; i++) { + String s = operands[i].getString(); + if (!(operands[i] instanceof PtgMissArg)) { + String v = operands[i].getValue().toString(); + Logger.logInfo("\tOperand[" + i + "]=" + s + " " + v); + } else + Logger.logInfo("\tOperand[" + i + "]=" + s + " is Missing"); + } + } + } } -class Complex { - public double real; - public double imaginary; - public String suffix; - Complex() { - this.suffix= "i"; - } - Complex(double r, double i) { - this.real= r; - this.imaginary= i; - this.suffix= "i"; - } + +class Complex { + public double real; + public double imaginary; + public String suffix; + + Complex() { + this.suffix = "i"; + } + + Complex(double r, double i) { + this.real = r; + this.imaginary = i; + this.suffix = "i"; + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.java index 532f688..06445c1 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,501 +22,501 @@ */ package io.starter.formats.XLS.formulas; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.Vector; import io.starter.OpenXLS.DateConverter; import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.toolkit.Logger; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Vector; + /* * FinancialCalculator is a collection of static methods that operate as the * Microsoft Excel function calls do. - * + * * All methods are called with an array of ptg's, which are then acted upon. A * Ptg of the type that makes sense (ie boolean, number) is returned after each * method. */ public class FinancialCalculator { - public static boolean DEBUG = false; - /* - * Basis - * - * 0, default= US (NASD) 30/360 - As with the European 30/360, with the - * additional provision that if the end date occurs on the 31st of a month - * it is moved to the 1st of the next month if the start date is earlier - * than the 30th. 1= Uses the exact number of elapsed days between the two - * dates, as well as the exact length of the year. 2= Uses the exact number - * of elapsed days between two dates but assumes the year only have 360 days - * 3- Uses the exact number of elapsed days between two dates but assumes - * the year always has 365 days 4= European 30/360 - Each month is assumed - * to have 30 days, such that the year has only 360 days. Start and end - * dates that occur on the 31st of a month become equal to the 30th of the - * same month. - * - * 30/360: If the accrual period ends on a 31st, do not change the date - * unless the period started on a 30th or 31st, in which case change the end - * date to 30th. In addition, if the accrual period ends on the last day of - * February, the month of February should not be extended to a 30 day month. - * 30/Actual: Method whereby interest is calculated based on a 30-day month - * and the assumed number of days in a year, i.e. the actual number of days - * in the accrual period multiplied by the number of interest payments in - * the year. Eg, a semi-annual bond (one paying two coupons per year) can - * display a period between coupons of 181 to 184 days. In this case, the - * number of days in a year will be 362 to 368 days. - * - * Euro: Method whereby interest is calculated based on a 30-day month (no - * exceptions, ie, February should always be extended to a 30 day month) and - * a 360-day year. - * - * Actual: Method whereby interest is calculated based on the actual number - * of accrued days (falling on a normal year) divided by 365, added to the - * actual number of accrued days (falling on a leap year) divided by 366. - * - * Actual/Actual: Is used for Treasury bonds and notes. This convention it - * refers to an interest accrual method that utilizes the actual number of - * days in a month and the actual number of days in a year. - * - * Actual/360: A day count fraction equal to actual days divided by 360 - * except in the - * United Kingdom and several countries where the denominator is 365 or - * actual days. It is used for bank deposits and in calculating rates pegged - * to some indices, such as LIBOR. 30/360 Rules: It is used for corporate - * bonds, U.S. Agency bonds and all mortgage backed securities. It assumes - * that all months have 30 days, and all years have 360 days. The number of - * days from M1/D1/Y1 to M2/D2/Y2 is computed according to the following - * procedure: If D1 is 31, change D1 to 30. If D2 is 31 and D1 is 30 or 31, - * then change D2 to 30. If M1 is 2, and D1 is 28 (in a non-leap year) or - * 29, then change D1 to 30. Then the number of days, N is: N = 360(Y2-Y1) + - * 30(M2-M1) + (D2-D1). - */ - static double yearFrac(int basis, long date0, long date1) { - double result; - // deep breath ... - GregorianCalendar fromDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(new Long(date0)); - GregorianCalendar toDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(new Long(date1)); - int y0 = fromDate.get(Calendar.YEAR); - int y1 = toDate.get(Calendar.YEAR); - int d0 = fromDate.get(Calendar.DAY_OF_MONTH); - int d1 = toDate.get(Calendar.DAY_OF_MONTH); - int m0 = fromDate.get(Calendar.MONTH); - int m1 = toDate.get(Calendar.MONTH); - double yearFrac = 1.0; - if (basis == 0) { // 30/360 US/NASD - if (d0 == 31) - d0 = 30; - if (d1 == 31 && d0 >= 30) - d1 = 30; - if (m0 == 1 && d0 >= 28) - d0 = 30; - yearFrac = (double) (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)) / 360.0; - } else if (basis == 1) { // Actual/Actual - int ndays = 0; - int i; // average # days between dates - for (i = y0; i <= y1; i++) { - ndays += isLeapYear(i) ? 366 : 365; - } - if (i!=y0) - yearFrac = (double) (date1 - date0) / ((double) ndays / (i - y0)); // yes I know it's redundant ... - else - yearFrac= date1-date0; - } else if (basis == 2) { // Actual/360 - yearFrac = (double) (date1 - date0) / 360.0; - } else if (basis == 3) { // Actual/365 - yearFrac = (double) (date1 - date0) / 365.0; - } else if (basis == 4) { // 30/360 EURO - // if (m0==1 && d0>=28) d0= 30; //??????????????????????????? - // if (m1==1 && d1>=28) d1= 30; //??????????????????????????? - yearFrac = (double) (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)) / 360.0; - } - return yearFrac; - } - - static double getDaysInYearFromBasis(int basis, long date0, long date1) { - double r= 0; - switch (basis) { - case 0: // 30/360 - case 2: // actual/360 - case 4: // 30/360 (EURO) - r= 360; - break; - case 1: // actual/actual - GregorianCalendar fromDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date0)); - int y0 = fromDate.get(Calendar.YEAR); - if (isLeapYear(y0)) - r= 365.4; - else - r= 365.25; + public static boolean DEBUG = false; + + /* + * Basis + * + * 0, default= US (NASD) 30/360 - As with the European 30/360, with the + * additional provision that if the end date occurs on the 31st of a month + * it is moved to the 1st of the next month if the start date is earlier + * than the 30th. 1= Uses the exact number of elapsed days between the two + * dates, as well as the exact length of the year. 2= Uses the exact number + * of elapsed days between two dates but assumes the year only have 360 days + * 3- Uses the exact number of elapsed days between two dates but assumes + * the year always has 365 days 4= European 30/360 - Each month is assumed + * to have 30 days, such that the year has only 360 days. Start and end + * dates that occur on the 31st of a month become equal to the 30th of the + * same month. + * + * 30/360: If the accrual period ends on a 31st, do not change the date + * unless the period started on a 30th or 31st, in which case change the end + * date to 30th. In addition, if the accrual period ends on the last day of + * February, the month of February should not be extended to a 30 day month. + * 30/Actual: Method whereby interest is calculated based on a 30-day month + * and the assumed number of days in a year, i.e. the actual number of days + * in the accrual period multiplied by the number of interest payments in + * the year. Eg, a semi-annual bond (one paying two coupons per year) can + * display a period between coupons of 181 to 184 days. In this case, the + * number of days in a year will be 362 to 368 days. + * + * Euro: Method whereby interest is calculated based on a 30-day month (no + * exceptions, ie, February should always be extended to a 30 day month) and + * a 360-day year. + * + * Actual: Method whereby interest is calculated based on the actual number + * of accrued days (falling on a normal year) divided by 365, added to the + * actual number of accrued days (falling on a leap year) divided by 366. + * + * Actual/Actual: Is used for Treasury bonds and notes. This convention it + * refers to an interest accrual method that utilizes the actual number of + * days in a month and the actual number of days in a year. + * + * Actual/360: A day count fraction equal to actual days divided by 360 + * except in the + * United Kingdom and several countries where the denominator is 365 or + * actual days. It is used for bank deposits and in calculating rates pegged + * to some indices, such as LIBOR. 30/360 Rules: It is used for corporate + * bonds, U.S. Agency bonds and all mortgage backed securities. It assumes + * that all months have 30 days, and all years have 360 days. The number of + * days from M1/D1/Y1 to M2/D2/Y2 is computed according to the following + * procedure: If D1 is 31, change D1 to 30. If D2 is 31 and D1 is 30 or 31, + * then change D2 to 30. If M1 is 2, and D1 is 28 (in a non-leap year) or + * 29, then change D1 to 30. Then the number of days, N is: N = 360(Y2-Y1) + + * 30(M2-M1) + (D2-D1). + */ + static double yearFrac(int basis, long date0, long date1) { + double result; + // deep breath ... + GregorianCalendar fromDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(new Long(date0)); + GregorianCalendar toDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(new Long(date1)); + int y0 = fromDate.get(Calendar.YEAR); + int y1 = toDate.get(Calendar.YEAR); + int d0 = fromDate.get(Calendar.DAY_OF_MONTH); + int d1 = toDate.get(Calendar.DAY_OF_MONTH); + int m0 = fromDate.get(Calendar.MONTH); + int m1 = toDate.get(Calendar.MONTH); + double yearFrac = 1.0; + if (basis == 0) { // 30/360 US/NASD + if (d0 == 31) + d0 = 30; + if (d1 == 31 && d0 >= 30) + d1 = 30; + if (m0 == 1 && d0 >= 28) + d0 = 30; + yearFrac = (double) (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)) / 360.0; + } else if (basis == 1) { // Actual/Actual + int ndays = 0; + int i; // average # days between dates + for (i = y0; i <= y1; i++) { + ndays += isLeapYear(i) ? 366 : 365; + } + if (i != y0) + yearFrac = (double) (date1 - date0) / ((double) ndays / (i - y0)); // yes I know it's redundant ... + else + yearFrac = date1 - date0; + } else if (basis == 2) { // Actual/360 + yearFrac = (double) (date1 - date0) / 360.0; + } else if (basis == 3) { // Actual/365 + yearFrac = (double) (date1 - date0) / 365.0; + } else if (basis == 4) { // 30/360 EURO + // if (m0==1 && d0>=28) d0= 30; //??????????????????????????? + // if (m1==1 && d1>=28) d1= 30; //??????????????????????????? + yearFrac = (double) (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)) / 360.0; + } + return yearFrac; + } + + static double getDaysInYearFromBasis(int basis, long date0, long date1) { + double r = 0; + switch (basis) { + case 0: // 30/360 + case 2: // actual/360 + case 4: // 30/360 (EURO) + r = 360; + break; + case 1: // actual/actual + GregorianCalendar fromDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date0)); + int y0 = fromDate.get(Calendar.YEAR); + if (isLeapYear(y0)) + r = 365.4; + else + r = 365.25; // r= (date1-date0)/yearFrac(basis, date0, date1); - break; - case 3: - r= 365; // actual/365 - break; - } - return r; - } - - static long getDaysFromBasis(int basis, long date0, long date1) { - if (basis == 0 || basis == 4) { // # months * 30 + extra days - GregorianCalendar fromDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date0)); - GregorianCalendar toDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date1)); - int y0 = fromDate.get(Calendar.YEAR); - int y1 = toDate.get(Calendar.YEAR); - int d0 = fromDate.get(Calendar.DAY_OF_MONTH); - int d1 = toDate.get(Calendar.DAY_OF_MONTH); - int m0 = fromDate.get(Calendar.MONTH); - int m1 = toDate.get(Calendar.MONTH); - if (basis == 0) { // 30/360 US/NASD - // TODO: PROBLEM: When date should be 2-28-02, get - // 2-2-02!!!!!!!!!!!!!!! - if (d0 == 31) - d0 = 30; - if (d1 == 31 && d0 >= 30) - d1 = 30; - // if (m0==1 && d0 >=28) d0= 30; // - // ?????????????????????????????????????????????????? - // if (m1 == 1 && d1 >= 28) - // d1 = 30; // ????????????????? - } else { // 30/360 EURO -- CORRECT FOR ALL DATES EXCEPT MONTH OF - // FEBRUARY (M0)!! - // if (m0==1 && d0>=28) d0= 30; // ????????????????????? - // if (m1==1 && d1>=28) d1= 30; // ????????????????????? - } - int result = (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)); - return result; - } - return date1 - date0; // actual (1, 2, 3) - } - - static int validateDay(int y, int m, int d) { - if (d > 28) { // m is 0-based - if (m == 1) // TODO: Get maximum for year - d = 28; - else if ((m == 3 || m == 5 || m == 8 || m == 10) && d == 31) - d = 30; - } - return d; - } - - /** - * ACCRINT - Returns the accrued interest for a security that pays periodic - * interest Analysis Pak function Issue is the security's issue date. - * First_interest is the security's first interest date. Settlement is the - * security's settlement date. The security settlement date is the date - * after the issue date when the security is traded to the buyer. Rate is - * the security's annual coupon rate. Par is the security's par value. If - * you omit par, ACCRINT uses $1,000. Frequency is the number of coupon - * payments per year. For annual payments, frequency = 1; for semiannual, - * frequency = 2; for quarterly, frequency = 4. Basis is the type of day - * count basis to use. - * - * Basis Day count basis 0 or omitted US (NASD) 30/360 1 Actual/actual 2 - * Actual/360 3 Actual/365 4 European 30/360 - * - * ACCRINT is calculated as follows: - * - * par X rate/frequency X Sum Ai/NLi over NC - * - * where: - * - * Ai = number of accrued days for the ith quasi-coupon period within odd - * period. NC = number of quasi-coupon periods that fit in odd period. If - * this number contains a fraction, raise it to the next whole number. NLi = - * normal length in days of the ith quasi-coupon period within odd period. - * - */ - // TODO: Works only for basis 0 & 4 - protected static Ptg calcAccrint(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ACCRINT."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ACCRINT"); - try { - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar fiDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - double rate = operands[3].getDoubleVal(); - double par = 1000; - if (!(operands[4] instanceof PtgMissArg)) - par = operands[4].getDoubleVal(); - int frequency = operands[5].getIntVal(); - int basis = 0; - if (operands.length > 6) - basis = operands[6].getIntVal(); - - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstInterestDate= (new Double(DateConverter.getXLSDateVal(fiDate))).longValue(); - long settlementDate= (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - - // TODO: if dates are not valid, return #VALUE! error - if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM); - if (rate <= 0 || par <= 0) return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (issueDate >= settlementDate) return new PtgErr(PtgErr.ERROR_NUM); - - // quasicoupon period: extend the series of equal payment periods to - // before or after the actual payment periods - // odd period= period between payments that differs from the usual - // equally spaced periods at which payments are made - // Ai= number of days which have accrued for the ith quasi-coupon period - // within the odd period - // equation: ACCRINT= ((par*rate)/frequency)* Sum over NC (Ai/NLi) - // where NC= # of quasi-coupon periods, rounded up - // Ai= # days in odd period i - // NLi=normal # days in odd period i - // For cases where Previous Coupon Date < Issue Date, NC=1, A= getDaysFromBasis and NLi= coupDays - - double result = (par * rate / frequency);//* Sum #Accrued days for - double sum = 0; - long E, PCD; - - // ACCRINT in EXCEL handles this case, so we should, too! - if (firstInterestDate>> S > FI!"); + break; + case 3: + r = 365; // actual/365 + break; + } + return r; + } + + static long getDaysFromBasis(int basis, long date0, long date1) { + if (basis == 0 || basis == 4) { // # months * 30 + extra days + GregorianCalendar fromDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date0)); + GregorianCalendar toDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date1)); + int y0 = fromDate.get(Calendar.YEAR); + int y1 = toDate.get(Calendar.YEAR); + int d0 = fromDate.get(Calendar.DAY_OF_MONTH); + int d1 = toDate.get(Calendar.DAY_OF_MONTH); + int m0 = fromDate.get(Calendar.MONTH); + int m1 = toDate.get(Calendar.MONTH); + if (basis == 0) { // 30/360 US/NASD + // TODO: PROBLEM: When date should be 2-28-02, get + // 2-2-02!!!!!!!!!!!!!!! + if (d0 == 31) + d0 = 30; + if (d1 == 31 && d0 >= 30) + d1 = 30; + // if (m0==1 && d0 >=28) d0= 30; // + // ?????????????????????????????????????????????????? + // if (m1 == 1 && d1 >= 28) + // d1 = 30; // ????????????????? + } else { // 30/360 EURO -- CORRECT FOR ALL DATES EXCEPT MONTH OF + // FEBRUARY (M0)!! + // if (m0==1 && d0>=28) d0= 30; // ????????????????????? + // if (m1==1 && d1>=28) d1= 30; // ????????????????????? + } + int result = (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)); + return result; + } + return date1 - date0; // actual (1, 2, 3) + } + + static int validateDay(int y, int m, int d) { + if (d > 28) { // m is 0-based + if (m == 1) // TODO: Get maximum for year + d = 28; + else if ((m == 3 || m == 5 || m == 8 || m == 10) && d == 31) + d = 30; + } + return d; + } + + /** + * ACCRINT - Returns the accrued interest for a security that pays periodic + * interest Analysis Pak function Issue is the security's issue date. + * First_interest is the security's first interest date. Settlement is the + * security's settlement date. The security settlement date is the date + * after the issue date when the security is traded to the buyer. Rate is + * the security's annual coupon rate. Par is the security's par value. If + * you omit par, ACCRINT uses $1,000. Frequency is the number of coupon + * payments per year. For annual payments, frequency = 1; for semiannual, + * frequency = 2; for quarterly, frequency = 4. Basis is the type of day + * count basis to use. + *

                + * Basis Day count basis 0 or omitted US (NASD) 30/360 1 Actual/actual 2 + * Actual/360 3 Actual/365 4 European 30/360 + *

                + * ACCRINT is calculated as follows: + *

                + * par X rate/frequency X Sum Ai/NLi over NC + *

                + * where: + *

                + * Ai = number of accrued days for the ith quasi-coupon period within odd + * period. NC = number of quasi-coupon periods that fit in odd period. If + * this number contains a fraction, raise it to the next whole number. NLi = + * normal length in days of the ith quasi-coupon period within odd period. + */ + // TODO: Works only for basis 0 & 4 + protected static Ptg calcAccrint(Ptg[] operands) throws + FunctionNotSupportedException { + if (true) { + String wn = "WARNING: this version of OpenXLS does not support the formula ACCRINT."; + Logger.logWarn(wn); + throw new FunctionNotSupportedException(wn); + } + if (operands.length < 6) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "ACCRINT"); + try { + GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar fiDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); + double rate = operands[3].getDoubleVal(); + double par = 1000; + if (!(operands[4] instanceof PtgMissArg)) + par = operands[4].getDoubleVal(); + int frequency = operands[5].getIntVal(); + int basis = 0; + if (operands.length > 6) + basis = operands[6].getIntVal(); + + long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); + long firstInterestDate = (new Double(DateConverter.getXLSDateVal(fiDate))).longValue(); + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + + // TODO: if dates are not valid, return #VALUE! error + if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM); + if (rate <= 0 || par <= 0) return new PtgErr(PtgErr.ERROR_NUM); + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (issueDate >= settlementDate) return new PtgErr(PtgErr.ERROR_NUM); + + // quasicoupon period: extend the series of equal payment periods to + // before or after the actual payment periods + // odd period= period between payments that differs from the usual + // equally spaced periods at which payments are made + // Ai= number of days which have accrued for the ith quasi-coupon period + // within the odd period + // equation: ACCRINT= ((par*rate)/frequency)* Sum over NC (Ai/NLi) + // where NC= # of quasi-coupon periods, rounded up + // Ai= # days in odd period i + // NLi=normal # days in odd period i + // For cases where Previous Coupon Date < Issue Date, NC=1, A= getDaysFromBasis and NLi= coupDays + + double result = (par * rate / frequency);//* Sum #Accrued days for + double sum = 0; + long E, PCD; + + // ACCRINT in EXCEL handles this case, so we should, too! + if (firstInterestDate < settlementDate) { + if (DEBUG) Logger.logInfo(">>> S > FI!"); /* Ptg[] ops = new Ptg[4]; ops[0] = new PtgNumber(settlementDate); ops[1] = new PtgNumber(firstInterestDate); ops[2] = new PtgInt(frequency); ops[3] = new PtgInt(basis); - + long x= PtgCalculator.getLongValue(calcCoupNCD(ops)); */ -// ????????????????????????????????? - E= 181; - PCD= issueDate; - } - else { - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(firstInterestDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - - - E= PtgCalculator.getLongValue(calcCoupDays(ops)); - PCD= PtgCalculator.getLongValue(calcCoupPCD(ops)); - } +// ????????????????????????????????? + E = 181; + PCD = issueDate; + } else { + Ptg[] ops = new Ptg[4]; + ops[0] = new PtgNumber(settlementDate); + ops[1] = new PtgNumber(firstInterestDate); + ops[2] = new PtgInt(frequency); + ops[3] = new PtgInt(basis); + + + E = PtgCalculator.getLongValue(calcCoupDays(ops)); + PCD = PtgCalculator.getLongValue(calcCoupPCD(ops)); + } // testing -if (PCD==0) { - PCD= issueDate + 1; -//Logger.logInfo(">>PCD==0"); -} - long A= 0; - if (basis==0 || basis==4) - // correct for basis 0, 4 - // INCORRECT for basis 1,2 and 3 - A= getDaysFromBasis(basis, issueDate, settlementDate); - else if (issueDate >= PCD) - // correct when issue >= PCD - A= getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate); - else { -// ??????????????????????????????????????????????? - A= getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate); -if (DEBUG) Logger.logInfo(">>I < PCD"); - } - - - result *= A/(double)E; - - PtgNumber pnum = new PtgNumber(result); - // TODO: Complete Accrint Alogorithm - if (DEBUG) Logger.logInfo("Result from Accrint= " + result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * ACCRINTM Returns the accrued interest for a security that pays interest - * at maturity Analysis Pak function - * - * Issue is the security's issue date. Maturity is the security's maturity - * date. Rate is the security's annual coupon rate. Par is the security's - * par value. If you omit par, ACCRINTM uses $1,000. Basis is the type of - * day count basis to use. 0 or omitted US (NASD) 30/360 1 Actual/actual 2 - * Actual/360 3 Actual/365 4 European 30/360 ACCRINTM = par X rate x A/D - * where: A = Number of accrued days counted according to a monthly basis. - * For interest at maturity items, the number of days from the issue date to - * the maturity date is used. D = Annual Year Basis. - * - */ - protected static Ptg calcAccrintm(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "ACCRINTM"); - try { - long issueDate, maturityDate; // dates are truncated to integers - double rate, par = 1000; - int basis = 0; - - // Issue Date - GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - issueDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue(); - // Maturity Date - dt = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - maturityDate = (new Double(DateConverter.getXLSDateVal(dt))) - .longValue(); - // Annual Coupon Rate - rate = operands[2].getDoubleVal(); - // Par value. If omitted, = 1000. - if (operands.length > 3 && (!(operands[3] instanceof PtgMissArg))) - par = operands[3].getDoubleVal(); - // Basis. If omitted, = 0 - if (operands.length > 4) - basis = operands[4].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (rate <= 0 || par <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (issueDate > maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - - double result = par * rate * yearFrac(basis, issueDate, maturityDate); - if (DEBUG) Logger.logInfo("Result from Accrintm= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * AMORDEGRC Returns the depreciation for each accounting period - */ - protected static Ptg calcAmordegrc(Ptg[] operands) { - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "AMORDEGRC"); - try { - double cost = operands[0].getDoubleVal(); - GregorianCalendar dP = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar fP = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - double salvage = operands[3].getDoubleVal(); - int period = operands[4].getIntVal(); - double rate = operands[5].getDoubleVal(); - int basis = operands[6].getIntVal(); - - long datePurchased = (new Double(DateConverter.getXLSDateVal(dP))).longValue(); - long firstPeriod = (new Double(DateConverter.getXLSDateVal(fP))).longValue(); - - // TODO: if dates are not valid, return #VALUE! error - if (rate <= 0) return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4 || basis==2) return new PtgErr(PtgErr.ERROR_NUM); - if (datePurchased > firstPeriod) return new PtgErr(PtgErr.ERROR_NUM); - - double coefficient; - double life = 1.0 / rate; - if (life < 3) - coefficient = 1; - else if (life < 5.0) // between 3 and 4 years - coefficient = 1.5; - else if (life <= 6.0) // between 5 and 6 years - coefficient = 2.0; - else - // more than 6 years - coefficient = 2.5; + if (PCD == 0) { + PCD = issueDate + 1; +//Logger.logInfo(">>PCD==0"); + } + long A = 0; + if (basis == 0 || basis == 4) + // correct for basis 0, 4 + // INCORRECT for basis 1,2 and 3 + A = getDaysFromBasis(basis, issueDate, settlementDate); + else if (issueDate >= PCD) + // correct when issue >= PCD + A = getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate); + else { +// ??????????????????????????????????????????????? + A = getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate); + if (DEBUG) Logger.logInfo(">>I < PCD"); + } - rate *= coefficient; - //cost-= Math.round(yearFrac(basis, datePurchased, - // firstPeriod)*rate*cost); - cost -= yearFrac(basis, datePurchased, firstPeriod) * rate * cost; - double Remainder = cost - salvage; - double A = 0; - if (Remainder > 0) { - for (int i = 0; i < period; i++) { - //A= Math.round(rate*cost); - A = rate * cost; - Remainder -= A; - if (Remainder < 0) { - /* - * if (period==i+1) { // A= Math.round(0.5*cost); - * Remainder=A; } - */ - if (i + 1 < period) - A = 0; - i = period; // exit loop - } - cost -= A; - } - } else - A = Math.round(rate * salvage); - double result = Math.max(Math.round(A), 0); - if (DEBUG) - Logger.logInfo("Result from AMORDEGRC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * AMORLINC Returns the depreciation for each accounting period - * - * - */ - protected static Ptg calcAmorlinc(Ptg[] operands) { - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "AMORLINC"); - try { - double cost = operands[0].getDoubleVal(); - GregorianCalendar dP = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar fP = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[2].getValue()); - double salvage = operands[3].getDoubleVal(); - int period = operands[4].getIntVal(); - double rate = operands[5].getDoubleVal(); - int basis = operands[6].getIntVal(); - - long datePurchased = (new Double(DateConverter.getXLSDateVal(dP))) - .longValue(); - long firstPeriod = (new Double(DateConverter.getXLSDateVal(fP))) - .longValue(); - - // TODO: if dates are not valid, return #VALUE! error - if (rate <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4 || basis==2) - return new PtgErr(PtgErr.ERROR_NUM); - if (datePurchased > firstPeriod) - return new PtgErr(PtgErr.ERROR_NUM); - - double A = 0; - double B = cost - salvage; - double C = yearFrac(basis, datePurchased, firstPeriod) * rate * cost; - double D = cost*rate; - long n = Math.round((cost - salvage - C) / D); - if (period == 0 || C == 0) - A= C; - else if (period < n) - A= D; - else if (period == n) - A= Math.min(D+(B-D*n-C), D); - else if (period == n + 1) - A= B-D*n-C; - else - A= 0; -/* + result *= A / (double) E; + + PtgNumber pnum = new PtgNumber(result); + // TODO: Complete Accrint Alogorithm + if (DEBUG) Logger.logInfo("Result from Accrint= " + result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * ACCRINTM Returns the accrued interest for a security that pays interest + * at maturity Analysis Pak function + *

                + * Issue is the security's issue date. Maturity is the security's maturity + * date. Rate is the security's annual coupon rate. Par is the security's + * par value. If you omit par, ACCRINTM uses $1,000. Basis is the type of + * day count basis to use. 0 or omitted US (NASD) 30/360 1 Actual/actual 2 + * Actual/360 3 Actual/365 4 European 30/360 ACCRINTM = par X rate x A/D + * where: A = Number of accrued days counted according to a monthly basis. + * For interest at maturity items, the number of days from the issue date to + * the maturity date is used. D = Annual Year Basis. + */ + protected static Ptg calcAccrintm(Ptg[] operands) { + if (operands.length < 3) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "ACCRINTM"); + try { + long issueDate, maturityDate; // dates are truncated to integers + double rate, par = 1000; + int basis = 0; + + // Issue Date + GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + issueDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue(); + // Maturity Date + dt = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + maturityDate = (new Double(DateConverter.getXLSDateVal(dt))) + .longValue(); + // Annual Coupon Rate + rate = operands[2].getDoubleVal(); + // Par value. If omitted, = 1000. + if (operands.length > 3 && (!(operands[3] instanceof PtgMissArg))) + par = operands[3].getDoubleVal(); + // Basis. If omitted, = 0 + if (operands.length > 4) + basis = operands[4].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (rate <= 0 || par <= 0) + return new PtgErr(PtgErr.ERROR_NUM); + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + if (issueDate > maturityDate) + return new PtgErr(PtgErr.ERROR_NUM); + + double result = par * rate * yearFrac(basis, issueDate, maturityDate); + if (DEBUG) Logger.logInfo("Result from Accrintm= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * AMORDEGRC Returns the depreciation for each accounting period + */ + protected static Ptg calcAmordegrc(Ptg[] operands) { + if (operands.length < 6) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "AMORDEGRC"); + try { + double cost = operands[0].getDoubleVal(); + GregorianCalendar dP = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + GregorianCalendar fP = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); + double salvage = operands[3].getDoubleVal(); + int period = operands[4].getIntVal(); + double rate = operands[5].getDoubleVal(); + int basis = operands[6].getIntVal(); + + long datePurchased = (new Double(DateConverter.getXLSDateVal(dP))).longValue(); + long firstPeriod = (new Double(DateConverter.getXLSDateVal(fP))).longValue(); + + // TODO: if dates are not valid, return #VALUE! error + if (rate <= 0) return new PtgErr(PtgErr.ERROR_NUM); + if (basis < 0 || basis > 4 || basis == 2) return new PtgErr(PtgErr.ERROR_NUM); + if (datePurchased > firstPeriod) return new PtgErr(PtgErr.ERROR_NUM); + + double coefficient; + double life = 1.0 / rate; + if (life < 3) + coefficient = 1; + else if (life < 5.0) // between 3 and 4 years + coefficient = 1.5; + else if (life <= 6.0) // between 5 and 6 years + coefficient = 2.0; + else + // more than 6 years + coefficient = 2.5; + + rate *= coefficient; + //cost-= Math.round(yearFrac(basis, datePurchased, + // firstPeriod)*rate*cost); + cost -= yearFrac(basis, datePurchased, firstPeriod) * rate * cost; + double Remainder = cost - salvage; + double A = 0; + if (Remainder > 0) { + for (int i = 0; i < period; i++) { + //A= Math.round(rate*cost); + A = rate * cost; + Remainder -= A; + if (Remainder < 0) { + /* + * if (period==i+1) { // A= Math.round(0.5*cost); + * Remainder=A; } + */ + if (i + 1 < period) + A = 0; + i = period; // exit loop + } + + cost -= A; + } + } else + A = Math.round(rate * salvage); + double result = Math.max(Math.round(A), 0); + if (DEBUG) + Logger.logInfo("Result from AMORDEGRC= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * AMORLINC Returns the depreciation for each accounting period + */ + protected static Ptg calcAmorlinc(Ptg[] operands) { + if (operands.length < 6) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "AMORLINC"); + try { + double cost = operands[0].getDoubleVal(); + GregorianCalendar dP = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + GregorianCalendar fP = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[2].getValue()); + double salvage = operands[3].getDoubleVal(); + int period = operands[4].getIntVal(); + double rate = operands[5].getDoubleVal(); + int basis = operands[6].getIntVal(); + + long datePurchased = (new Double(DateConverter.getXLSDateVal(dP))) + .longValue(); + long firstPeriod = (new Double(DateConverter.getXLSDateVal(fP))) + .longValue(); + + // TODO: if dates are not valid, return #VALUE! error + if (rate <= 0) + return new PtgErr(PtgErr.ERROR_NUM); + if (basis < 0 || basis > 4 || basis == 2) + return new PtgErr(PtgErr.ERROR_NUM); + if (datePurchased > firstPeriod) + return new PtgErr(PtgErr.ERROR_NUM); + + double A = 0; + double B = cost - salvage; + double C = yearFrac(basis, datePurchased, firstPeriod) * rate * cost; + double D = cost * rate; + long n = Math.round((cost - salvage - C) / D); + if (period == 0 || C == 0) + A = C; + else if (period < n) + A = D; + else if (period == n) + A = Math.min(D + (B - D * n - C), D); + else if (period == n + 1) + A = B - D * n - C; + else + A = 0; +/* cost -= yearFrac(basis, datePurchased, firstPeriod) * rate * cost; if (Remainder > 0) { for (int i = 0; i < period; i++) { @@ -536,9 +536,9 @@ else if (period == n + 1) } } else A = Math.round(rate * salvage); -*/ - double result = Math.max(Math.round(A), 0); -/* +*/ + double result = Math.max(Math.round(A), 0); +/* double A = cost * rate; double B = cost - salvage; //double yf= yearFrac(basis, datePurchased, firstPeriod); @@ -555,2110 +555,2110 @@ else if (period == n + 1) result = A - C; //B - A * n - C; else result = 0; -*/ if (DEBUG) - Logger.logInfo("Result from AMORLINC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPDAYBS -- - * - * Returns the number of days from the beginning of the coupon period to the - * settlement date Settlement is the security's settlement date. The - * security settlement date is the date after the issue date when the - * security is traded to the buyer. Maturity is the security's maturity - * date. The maturity date is the date when the security expires. Frequency - * is the number of coupon payments per year. For annual payments, frequency = - * 1; for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is - * the type of day count basis to use. (optional) - * - * - */ - protected static Ptg calcCoupDayBS(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "COUPDAYSBS"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) return new PtgErr(PtgErr.ERROR_NUM); - - long pcd = PtgCalculator.getLongValue(calcCoupPCD(operands)); - - double result = getDaysFromBasis(basis, pcd, settlementDate); - - // - - - if (DEBUG) - Logger.logInfo("Result from calcCoupDaysBS= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPDAYS Returns the number of days in the coupon period that contains - * the settlement date Settlement is the security's settlement date. The - * security settlement date is the date after the issue date when the - * security is traded to the buyer Maturity is the security's maturity date. - * The maturity date is the date when the security expires. Frequency is the - * number of coupon payments per year. For annual payments, frequency = 1; - * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the - * type of day count basis to use - * - */ - protected static Ptg calcCoupDays(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "COUPDAYS"); - try { - GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - long settlementDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue(); - dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - long maturityDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue(); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) return new PtgErr(PtgErr.ERROR_NUM); - - // VERY strange, but seems to be correct - double result; - if (basis==1) { // actual/actual - long pcd = PtgCalculator.getLongValue(calcCoupPCD(operands)); - long ncd= PtgCalculator.getLongValue(calcCoupNCD(operands)); - result = getDaysFromBasis(basis, pcd, ncd); - } - else if (basis==0 || basis==2 || basis==4) - result= 360.0/frequency; - else - result= 365.0/frequency; - if (DEBUG) - Logger.logInfo("Result from calcCoupDays=" + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPDAYSNC - Returns the number of days from the settlement date to the - * next coupon date - * - * Settlement is the security's settlement date. The security settlement - * date is the date after the issue date when the security is traded to the - * buyer Maturity is the security's maturity date. The maturity date is the - * date when the security expires. Frequency is the number of coupon - * payments per year. For annual payments, frequency = 1; for semiannual, - * frequency = 2; for quarterly, frequency = 4. Basis is the type of day - * count basis to use - * - */ - protected static Ptg calcCoupDaysNC(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "COUPDAYSNC"); - long settlementDate = new Long(operands[0].getValue().toString()).longValue(); - long maturityDate = new Long(operands[1].getValue().toString()).longValue(); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - - long ncd = PtgCalculator.getLongValue(calcCoupNCD(operands)); - - double result = getDaysFromBasis(basis, settlementDate, ncd); - if (DEBUG) - Logger.logInfo("Result from calcCoupDaysNC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * COUPNCD Returns the next coupon date after the settlement date Returns a - * number that represents the next coupon date after the settlement date. - * COUPNCD(settlement,maturity,frequency,basis) Important Dates should be - * entered by using the DATE function, or as results of other formulas or - * functions. For example, use DATE(2008,5,23) for the 23rd day of May, - * 2008. Problems can occur if dates are entered as text. Settlement is the - * security's settlement date. The security settlement date is the date - * after the issue date when the security is traded to the buyer. Maturity - * is the security's maturity date. The maturity date is the date when the - * security expires. Frequency is the number of coupon payments per year. - * For annual payments, frequency = 1; for semiannual, frequency = 2; for - * quarterly, frequency = 4. Basis is the type of day count basis to use. - * - */ - protected static Ptg calcCoupNCD(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "COUPNCD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - - GregorianCalendar resultDate; - int mm = mDate.get(Calendar.MONTH) + 1; // months are 0-based but calc - // needs 1-based - for now! - int sm = sDate.get(Calendar.MONTH) + 1; - int y = mDate.get(Calendar.YEAR); - int d = mDate.get(Calendar.DAY_OF_MONTH); - - if (frequency == 1) { // annual - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - y--; - } - y++; - } - if (frequency == 2) { // semi-annual - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - mm-=6; - if (mm<1) { - mm += 12; +*/ + if (DEBUG) + Logger.logInfo("Result from AMORLINC= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * COUPDAYBS -- + *

                + * Returns the number of days from the beginning of the coupon period to the + * settlement date Settlement is the security's settlement date. The + * security settlement date is the date after the issue date when the + * security is traded to the buyer. Maturity is the security's maturity + * date. The maturity date is the date when the security expires. Frequency + * is the number of coupon payments per year. For annual payments, frequency = + * 1; for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is + * the type of day count basis to use. (optional) + */ + protected static Ptg calcCoupDayBS(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "COUPDAYSBS"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); + int frequency = operands[2].getIntVal(); + int basis = 0; + if (operands.length > 3) + basis = operands[3].getIntVal(); + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM); + if (settlementDate > maturityDate) return new PtgErr(PtgErr.ERROR_NUM); + + long pcd = PtgCalculator.getLongValue(calcCoupPCD(operands)); + + double result = getDaysFromBasis(basis, pcd, settlementDate); + + // + + + if (DEBUG) + Logger.logInfo("Result from calcCoupDaysBS= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * COUPDAYS Returns the number of days in the coupon period that contains + * the settlement date Settlement is the security's settlement date. The + * security settlement date is the date after the issue date when the + * security is traded to the buyer Maturity is the security's maturity date. + * The maturity date is the date when the security expires. Frequency is the + * number of coupon payments per year. For annual payments, frequency = 1; + * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the + * type of day count basis to use + */ + protected static Ptg calcCoupDays(Ptg[] operands) { + if (operands.length < 3) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "COUPDAYS"); + try { + GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + long settlementDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue(); + dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + long maturityDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue(); + int frequency = operands[2].getIntVal(); + int basis = 0; + if (operands.length > 3) + basis = operands[3].getIntVal(); + // TODO: if dates are not valid, return #VALUE! error + if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM); + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (settlementDate > maturityDate) return new PtgErr(PtgErr.ERROR_NUM); + + // VERY strange, but seems to be correct + double result; + if (basis == 1) { // actual/actual + long pcd = PtgCalculator.getLongValue(calcCoupPCD(operands)); + long ncd = PtgCalculator.getLongValue(calcCoupNCD(operands)); + result = getDaysFromBasis(basis, pcd, ncd); + } else if (basis == 0 || basis == 2 || basis == 4) + result = 360.0 / frequency; + else + result = 365.0 / frequency; + if (DEBUG) + Logger.logInfo("Result from calcCoupDays=" + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * COUPDAYSNC - Returns the number of days from the settlement date to the + * next coupon date + *

                + * Settlement is the security's settlement date. The security settlement + * date is the date after the issue date when the security is traded to the + * buyer Maturity is the security's maturity date. The maturity date is the + * date when the security expires. Frequency is the number of coupon + * payments per year. For annual payments, frequency = 1; for semiannual, + * frequency = 2; for quarterly, frequency = 4. Basis is the type of day + * count basis to use + */ + protected static Ptg calcCoupDaysNC(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "COUPDAYSNC"); + long settlementDate = new Long(operands[0].getValue().toString()).longValue(); + long maturityDate = new Long(operands[1].getValue().toString()).longValue(); + int frequency = operands[2].getIntVal(); + int basis = 0; + if (operands.length > 3) + basis = operands[3].getIntVal(); + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + if (!(frequency == 1 || frequency == 2 || frequency == 4)) + return new PtgErr(PtgErr.ERROR_NUM); + if (settlementDate > maturityDate) + return new PtgErr(PtgErr.ERROR_NUM); + + long ncd = PtgCalculator.getLongValue(calcCoupNCD(operands)); + + double result = getDaysFromBasis(basis, settlementDate, ncd); + if (DEBUG) + Logger.logInfo("Result from calcCoupDaysNC= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * COUPNCD Returns the next coupon date after the settlement date Returns a + * number that represents the next coupon date after the settlement date. + * COUPNCD(settlement,maturity,frequency,basis) Important Dates should be + * entered by using the DATE function, or as results of other formulas or + * functions. For example, use DATE(2008,5,23) for the 23rd day of May, + * 2008. Problems can occur if dates are entered as text. Settlement is the + * security's settlement date. The security settlement date is the date + * after the issue date when the security is traded to the buyer. Maturity + * is the security's maturity date. The maturity date is the date when the + * security expires. Frequency is the number of coupon payments per year. + * For annual payments, frequency = 1; for semiannual, frequency = 2; for + * quarterly, frequency = 4. Basis is the type of day count basis to use. + */ + protected static Ptg calcCoupNCD(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "COUPNCD"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + int frequency = operands[2].getIntVal(); + int basis = 0; + if (operands.length > 3) + basis = operands[3].getIntVal(); + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + if (!(frequency == 1 || frequency == 2 || frequency == 4)) + return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) + return new PtgErr(PtgErr.ERROR_NUM); + + GregorianCalendar resultDate; + int mm = mDate.get(Calendar.MONTH) + 1; // months are 0-based but calc + // needs 1-based - for now! + int sm = sDate.get(Calendar.MONTH) + 1; + int y = mDate.get(Calendar.YEAR); + int d = mDate.get(Calendar.DAY_OF_MONTH); + + if (frequency == 1) { // annual + while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { y--; } - } - mm+=6; - } else if (frequency == 4) { // quarterly - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - mm-=3; - if (mm<1) { - mm += 12; - y--; + y++; + } + if (frequency == 2) { // semi-annual + while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { + mm -= 6; + if (mm < 1) { + mm += 12; + y--; + } } - } - mm+=3; - } - resultDate = new GregorianCalendar(y, mm - 1, d); - double date = DateConverter.getXLSDateVal(resultDate); - if (DEBUG) - Logger.logInfo("Result from calcCoupNCD= " - + date - + " " - + java.text.DateFormat.getDateInstance().format( - resultDate.getTime())); - int i = (int) date; - PtgInt pi = new PtgInt(i); - return pi; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPNUM Returns the number of coupons payable between the settlement date - * and maturity date Settlement is the security's settlement date. The - * security settlement date is the date after the issue date when the - * security is traded to the buyer Maturity is the security's maturity date. - * The maturity date is the date when the security expires. Frequency is the - * number of coupon payments per year. For annual payments, frequency = 1; - * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the - * type of day count basis to use - * - */ - protected static Ptg calcCoupNum(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "COUPNUM"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); + mm += 6; + } else if (frequency == 4) { // quarterly + while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { + mm -= 3; + if (mm < 1) { + mm += 12; + y--; + } + } + mm += 3; + } + resultDate = new GregorianCalendar(y, mm - 1, d); + double date = DateConverter.getXLSDateVal(resultDate); + if (DEBUG) + Logger.logInfo("Result from calcCoupNCD= " + + date + + " " + + java.text.DateFormat.getDateInstance().format( + resultDate.getTime())); + int i = (int) date; + PtgInt pi = new PtgInt(i); + return pi; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * COUPNUM Returns the number of coupons payable between the settlement date + * and maturity date Settlement is the security's settlement date. The + * security settlement date is the date after the issue date when the + * security is traded to the buyer Maturity is the security's maturity date. + * The maturity date is the date when the security expires. Frequency is the + * number of coupon payments per year. For annual payments, frequency = 1; + * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the + * type of day count basis to use + */ + protected static Ptg calcCoupNum(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "COUPNUM"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); + int frequency = operands[2].getIntVal(); + int basis = 0; + if (operands.length > 3) + basis = operands[3].getIntVal(); + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + if (!(frequency == 1 || frequency == 2 || frequency == 4)) + return new PtgErr(PtgErr.ERROR_NUM); + if (settlementDate > maturityDate) + return new PtgErr(PtgErr.ERROR_NUM); /* double result = getDaysFromBasis(basis, settlementDate, maturityDate) / (getDaysInYearFromBasis(basis, settlementDate, maturityDate) / frequency); result = Math.ceil(result); double delta= maturityDate-settlementDate; double result= delta/calcCoupDays(operands)); -*/ - double result= Math.ceil(yearFrac(basis, settlementDate, maturityDate)*frequency); - if (DEBUG) - Logger.logInfo("Result from calcCoupNUM= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPPCD Returns the previous coupon date before the settlement date - */ - protected static Ptg calcCoupPCD(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "COUPPCD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - - GregorianCalendar resultDate; - int mm = mDate.get(Calendar.MONTH) + 1; // months are 0-based but calc - // needs 1-based - for now! - int sm = sDate.get(Calendar.MONTH) + 1; - int y = mDate.get(Calendar.YEAR); - int d = mDate.get(Calendar.DAY_OF_MONTH); - - if (frequency == 1) { // annual - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - y--; - } +*/ + double result = Math.ceil(yearFrac(basis, settlementDate, maturityDate) * frequency); + if (DEBUG) + Logger.logInfo("Result from calcCoupNUM= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { } - if (frequency == 2) { // semi-annual - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - mm-=6; - if (mm<1) { - mm += 12; + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * COUPPCD Returns the previous coupon date before the settlement date + */ + protected static Ptg calcCoupPCD(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "COUPPCD"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + int frequency = operands[2].getIntVal(); + int basis = 0; + if (operands.length > 3) + basis = operands[3].getIntVal(); + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (!(frequency == 1 || frequency == 2 || frequency == 4)) + return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); + + GregorianCalendar resultDate; + int mm = mDate.get(Calendar.MONTH) + 1; // months are 0-based but calc + // needs 1-based - for now! + int sm = sDate.get(Calendar.MONTH) + 1; + int y = mDate.get(Calendar.YEAR); + int d = mDate.get(Calendar.DAY_OF_MONTH); + + if (frequency == 1) { // annual + while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { y--; } - } - } else if (frequency == 4) { // quarterly - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - mm-=3; - if (mm<1) { - mm += 12; - y--; + } + if (frequency == 2) { // semi-annual + while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { + mm -= 6; + if (mm < 1) { + mm += 12; + y--; + } + } + } else if (frequency == 4) { // quarterly + while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { + mm -= 3; + if (mm < 1) { + mm += 12; + y--; + } } } + resultDate = new GregorianCalendar(y, mm - 1, d); + double date = DateConverter.getXLSDateVal(resultDate); + if (DEBUG) + Logger.logInfo("Result from calcCoupPCD= " + + date + + " " + + java.text.DateFormat.getDateInstance().format( + resultDate.getTime())); + int i = (int) date; + PtgInt pi = new PtgInt(i); + return pi; + } catch (Exception e) { } - resultDate = new GregorianCalendar(y, mm - 1, d); - double date = DateConverter.getXLSDateVal(resultDate); - if (DEBUG) - Logger.logInfo("Result from calcCoupPCD= " - + date - + " " - + java.text.DateFormat.getDateInstance().format( - resultDate.getTime())); - int i = (int) date; - PtgInt pi = new PtgInt(i); - return pi; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * CUMIPMT Returns the cumulative interest paid between two periods - * CUMIPMT(rate,nper,pv,start_period,end_period,type) All parameters are - * require Rate is the interest rate. Nper is the total number of payment - * periods. Pv is the present value. Start_period is the first period in the - * calculation. Payment periods are numbered beginning with 1. End_period is - * the last period in the calculation. Type is the timing of the payment. - * - * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the - * beginning of the period - * - */ - protected static Ptg calcCumIPmt(Ptg[] operands) { - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - double rate = operands[0].getDoubleVal(); - double nper = operands[1].getDoubleVal(); - double pv = operands[2].getDoubleVal(); - int startperiod = operands[3].getIntVal(); - int endperiod = operands[4].getIntVal(); - int type = operands[5].getIntVal(); - - if (DEBUG) - debugOperands(operands, "CUMIPMT"); - if (rate <= 0 || pv <= 0 || nper <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (startperiod < 1 || endperiod < 1) - return new PtgErr(PtgErr.ERROR_NUM); - if (startperiod > endperiod) - return new PtgErr(PtgErr.ERROR_NUM); - if (type < 0 || type > 1) - return new PtgErr(PtgErr.ERROR_NUM); - - // CumIPMT= pmt*period - FV for start-1 - pmt - FV for end period and - // pmt - double A, B; - // PMT used in fv calc - double Rn = Math.pow(1 + rate, nper); - A = -pv * Rn * rate; - B = (Rn - 1) * (1 + rate * type); - double pmt = A / B; - - // WORKS on everything BUT type=1 AND startperiod=1 !!!!!! - double n = startperiod - 1 - type; - int period = endperiod - startperiod + 1; - - // FVa (StartPeriod) - A = Math.pow(1 + rate, n); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n) - 1) / rate; - double fva = -(pv * A + B); - // FVb (endPeriod) - A = Math.pow(1 + rate, endperiod - type); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, endperiod - type) - 1) / rate; - double fvb = -(pv * A + B); - - double result = fva - fvb - (pmt * period); //- (fva - fvb); - if (startperiod == 1 && type == 1) - result = (pmt * period) + pv; // I'm sure there's a good reason for - // this!?!?! - result *= -1; - if (DEBUG) - Logger.logInfo("Result from calcCumIPmt= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * CUMPRINC Returns the cumulative principal paid on a loan between - * start_period and end_period. Syntax - * - * CUMPRINC(rate,nper,pv,start_period,end_period,type) - * - * Rate is the interest rate. Nper is the total number of payment periods. - * Pv is the present value. Start_period is the first period in the - * calculation. Payment periods are numbered beginning with 1. End_period is - * the last period in the calculation. Type is the timing of the payment. - * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the - * beginning of the period - * - */ - protected static Ptg calcCumPrinc(Ptg[] operands) { - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - double rate = operands[0].getDoubleVal(); - double nper = operands[1].getDoubleVal(); - double pv = operands[2].getDoubleVal(); - int startperiod = operands[3].getIntVal(); - int endperiod = operands[4].getIntVal(); - int type = operands[5].getIntVal(); - - if (DEBUG) - debugOperands(operands, "CUMPRINC"); - // Cumprinc= FV for start-1 and pmt - FV for end period and pmt - double A, B; - // PMT used in fv calc - double Rn = Math.pow(1 + rate, nper); - A = -pv * Rn * rate; - B = (Rn - 1) * (1 + rate * type); - double pmt = A / B; - - // FVa (StartPeriod) - A = Math.pow(1 + rate, startperiod - type - 1); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, startperiod - type - 1) - 1) / rate; - double fva = -(pv * A + B); - // FVb (endPeriod) - A = Math.pow(1 + rate, endperiod - type); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, endperiod - type) - 1) / rate; - double fvb = -(pv * A + B); - - double result = fva - fvb; - if (startperiod == 1 && type == 1) - result = pv; // I'm sure there's a good reason for this!?!?! - if (DEBUG) - Logger.logInfo("Result from calcCUMPRINC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * DB Returns the depreciation of an asset for a specified period using the - * fixed-declining balance method - * - * DB(cost,salvage,life,period,month) - * - * Cost is the initial cost of the asset. - * - * Salvage is the value at the end of the depreciation (sometimes called the - * salvage value of the asset). - * - * Life is the number of periods over which the asset is being depreciated - * (sometimes called the useful life of the asset). - * - * Period is the period for which you want to calculate the depreciation. - * Period must use the same units as life. - * - * Month is the number of months in the first year. If month is omitted, it - * is assumed to be 12. - */ - - protected static Ptg calcDB(Ptg[] operands) { - if (operands.length < 4 || operands[0].getComponents() != null) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - double cost, salvage; - int life, period, month; - cost = new Double(String.valueOf(operands[0].getValue())).doubleValue(); - salvage = new Double(String.valueOf(operands[1].getValue())) - .doubleValue(); - life = Integer.valueOf(String.valueOf(operands[2].getValue())).intValue(); - period = Integer.valueOf(String.valueOf(operands[3].getValue())).intValue(); - if (operands.length > 4) { - if (operands[4] instanceof PtgMissArg) { - month = 12; - } else { - month = Integer.valueOf(String.valueOf(operands[4].getValue())) - .intValue(); - } - } else { - month = 12; - } - double salCost = salvage / cost; - // this section longhand due to some wierd calcs when calling lifdiv = - // 1/life; - double lifdiv = 1; - lifdiv /= life; - double rate = Math.pow(salCost, lifdiv); - rate = 1 - rate; - rate = rate * 1000; - rate = Math.round(rate); - rate /= 1000; - - double totalDepreciation = cost * rate * month / 12; - double result= totalDepreciation; - // 1st and last (i.e. period==life) are special cases - for (int i = 2; i < period || (i==period && period <= life); i++) { - result = (cost - totalDepreciation) * rate; - totalDepreciation+= (cost - totalDepreciation) * rate; - } - if (period>life) // last depreciation is special calc - result= (cost - totalDepreciation) * rate * (12-month)/12; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * DDB Returns the depreciation of an asset for a spcified period using the - * double-declining balance method or some other method you specify - * - * DDB(cost,salvage,life,period,factor) - * - * Cost is the initial cost of the asset. - * - * Salvage is the value at the end of the depreciation (sometimes called the - * salvage value of the asset). - * - * Life is the number of periods over which the asset is being depreciated - * (sometimes called the useful life of the asset). - * - * Period is the period for which you want to calculate the depreciation. - * Period must use the same units as life. - * - * Factor is the rate at which the balance declines. If factor is omitted, - * it is assumed to be 2 (the double-declining balance method). - * - * All five arguments must be positive numbers. - * - */ - protected static Ptg calcDDB(Ptg[] operands) { - if (operands.length < 4 || operands[0].getComponents() != null) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - double cost, salvage; - int life, period, factor; - cost = new Double(String.valueOf(operands[0].getValue())).doubleValue(); - salvage = new Double(String.valueOf(operands[1].getValue())) - .doubleValue(); - life = Integer.valueOf(String.valueOf(operands[2].getValue())).intValue(); - period = Integer.valueOf(String.valueOf(operands[3].getValue())).intValue(); - factor= 2; - if (operands.length > 4) { - if (!(operands[4] instanceof PtgMissArg)) - factor = Integer.valueOf(String.valueOf(operands[4].getValue())) - .intValue(); - } - double salCost = salvage / cost; - // this section longhand due to some wierd calcs when calling lifdiv = - // 1/life; - double facLife= factor/((double) life); - double totalDepreciation = 0; -/* original calc + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * CUMIPMT Returns the cumulative interest paid between two periods + * CUMIPMT(rate,nper,pv,start_period,end_period,type) All parameters are + * require Rate is the interest rate. Nper is the total number of payment + * periods. Pv is the present value. Start_period is the first period in the + * calculation. Payment periods are numbered beginning with 1. End_period is + * the last period in the calculation. Type is the timing of the payment. + *

                + * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the + * beginning of the period + */ + protected static Ptg calcCumIPmt(Ptg[] operands) { + if (operands.length < 6) { + return new PtgErr(PtgErr.ERROR_NULL); + } + double rate = operands[0].getDoubleVal(); + double nper = operands[1].getDoubleVal(); + double pv = operands[2].getDoubleVal(); + int startperiod = operands[3].getIntVal(); + int endperiod = operands[4].getIntVal(); + int type = operands[5].getIntVal(); + + if (DEBUG) + debugOperands(operands, "CUMIPMT"); + if (rate <= 0 || pv <= 0 || nper <= 0) + return new PtgErr(PtgErr.ERROR_NUM); + if (startperiod < 1 || endperiod < 1) + return new PtgErr(PtgErr.ERROR_NUM); + if (startperiod > endperiod) + return new PtgErr(PtgErr.ERROR_NUM); + if (type < 0 || type > 1) + return new PtgErr(PtgErr.ERROR_NUM); + + // CumIPMT= pmt*period - FV for start-1 - pmt - FV for end period and + // pmt + double A, B; + // PMT used in fv calc + double Rn = Math.pow(1 + rate, nper); + A = -pv * Rn * rate; + B = (Rn - 1) * (1 + rate * type); + double pmt = A / B; + + // WORKS on everything BUT type=1 AND startperiod=1 !!!!!! + double n = startperiod - 1 - type; + int period = endperiod - startperiod + 1; + + // FVa (StartPeriod) + A = Math.pow(1 + rate, n); + B = pmt * (1 + rate * type); + B *= (Math.pow(1 + rate, n) - 1) / rate; + double fva = -(pv * A + B); + // FVb (endPeriod) + A = Math.pow(1 + rate, endperiod - type); + B = pmt * (1 + rate * type); + B *= (Math.pow(1 + rate, endperiod - type) - 1) / rate; + double fvb = -(pv * A + B); + + double result = fva - fvb - (pmt * period); //- (fva - fvb); + if (startperiod == 1 && type == 1) + result = (pmt * period) + pv; // I'm sure there's a good reason for + // this!?!?! + result *= -1; + if (DEBUG) + Logger.logInfo("Result from calcCumIPmt= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * CUMPRINC Returns the cumulative principal paid on a loan between + * start_period and end_period. Syntax + *

                + * CUMPRINC(rate,nper,pv,start_period,end_period,type) + *

                + * Rate is the interest rate. Nper is the total number of payment periods. + * Pv is the present value. Start_period is the first period in the + * calculation. Payment periods are numbered beginning with 1. End_period is + * the last period in the calculation. Type is the timing of the payment. + * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the + * beginning of the period + */ + protected static Ptg calcCumPrinc(Ptg[] operands) { + if (operands.length < 6) { + return new PtgErr(PtgErr.ERROR_NULL); + } + double rate = operands[0].getDoubleVal(); + double nper = operands[1].getDoubleVal(); + double pv = operands[2].getDoubleVal(); + int startperiod = operands[3].getIntVal(); + int endperiod = operands[4].getIntVal(); + int type = operands[5].getIntVal(); + + if (DEBUG) + debugOperands(operands, "CUMPRINC"); + // Cumprinc= FV for start-1 and pmt - FV for end period and pmt + double A, B; + // PMT used in fv calc + double Rn = Math.pow(1 + rate, nper); + A = -pv * Rn * rate; + B = (Rn - 1) * (1 + rate * type); + double pmt = A / B; + + // FVa (StartPeriod) + A = Math.pow(1 + rate, startperiod - type - 1); + B = pmt * (1 + rate * type); + B *= (Math.pow(1 + rate, startperiod - type - 1) - 1) / rate; + double fva = -(pv * A + B); + // FVb (endPeriod) + A = Math.pow(1 + rate, endperiod - type); + B = pmt * (1 + rate * type); + B *= (Math.pow(1 + rate, endperiod - type) - 1) / rate; + double fvb = -(pv * A + B); + + double result = fva - fvb; + if (startperiod == 1 && type == 1) + result = pv; // I'm sure there's a good reason for this!?!?! + if (DEBUG) + Logger.logInfo("Result from calcCUMPRINC= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * DB Returns the depreciation of an asset for a specified period using the + * fixed-declining balance method + *

                + * DB(cost,salvage,life,period,month) + *

                + * Cost is the initial cost of the asset. + *

                + * Salvage is the value at the end of the depreciation (sometimes called the + * salvage value of the asset). + *

                + * Life is the number of periods over which the asset is being depreciated + * (sometimes called the useful life of the asset). + *

                + * Period is the period for which you want to calculate the depreciation. + * Period must use the same units as life. + *

                + * Month is the number of months in the first year. If month is omitted, it + * is assumed to be 12. + */ + + protected static Ptg calcDB(Ptg[] operands) { + if (operands.length < 4 || operands[0].getComponents() != null) { // not + // supported + // by + // function + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + double cost, salvage; + int life, period, month; + cost = new Double(String.valueOf(operands[0].getValue())).doubleValue(); + salvage = new Double(String.valueOf(operands[1].getValue())) + .doubleValue(); + life = Integer.valueOf(String.valueOf(operands[2].getValue())).intValue(); + period = Integer.valueOf(String.valueOf(operands[3].getValue())).intValue(); + if (operands.length > 4) { + if (operands[4] instanceof PtgMissArg) { + month = 12; + } else { + month = Integer.valueOf(String.valueOf(operands[4].getValue())) + .intValue(); + } + } else { + month = 12; + } + double salCost = salvage / cost; + // this section longhand due to some wierd calcs when calling lifdiv = + // 1/life; + double lifdiv = 1; + lifdiv /= life; + double rate = Math.pow(salCost, lifdiv); + rate = 1 - rate; + rate = rate * 1000; + rate = Math.round(rate); + rate /= 1000; + + double totalDepreciation = cost * rate * month / 12; + double result = totalDepreciation; + // 1st and last (i.e. period==life) are special cases + for (int i = 2; i < period || (i == period && period <= life); i++) { + result = (cost - totalDepreciation) * rate; + totalDepreciation += (cost - totalDepreciation) * rate; + } + if (period > life) // last depreciation is special calc + result = (cost - totalDepreciation) * rate * (12 - month) / 12; + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * DDB Returns the depreciation of an asset for a spcified period using the + * double-declining balance method or some other method you specify + *

                + * DDB(cost,salvage,life,period,factor) + *

                + * Cost is the initial cost of the asset. + *

                + * Salvage is the value at the end of the depreciation (sometimes called the + * salvage value of the asset). + *

                + * Life is the number of periods over which the asset is being depreciated + * (sometimes called the useful life of the asset). + *

                + * Period is the period for which you want to calculate the depreciation. + * Period must use the same units as life. + *

                + * Factor is the rate at which the balance declines. If factor is omitted, + * it is assumed to be 2 (the double-declining balance method). + *

                + * All five arguments must be positive numbers. + */ + protected static Ptg calcDDB(Ptg[] operands) { + if (operands.length < 4 || operands[0].getComponents() != null) { // not + // supported + // by + // function + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + double cost, salvage; + int life, period, factor; + cost = new Double(String.valueOf(operands[0].getValue())).doubleValue(); + salvage = new Double(String.valueOf(operands[1].getValue())) + .doubleValue(); + life = Integer.valueOf(String.valueOf(operands[2].getValue())).intValue(); + period = Integer.valueOf(String.valueOf(operands[3].getValue())).intValue(); + factor = 2; + if (operands.length > 4) { + if (!(operands[4] instanceof PtgMissArg)) + factor = Integer.valueOf(String.valueOf(operands[4].getValue())) + .intValue(); + } + double salCost = salvage / cost; + // this section longhand due to some wierd calcs when calling lifdiv = + // 1/life; + double facLife = factor / ((double) life); + double totalDepreciation = 0; +/* original calc for (int i = 1; i < period; i++) { //((cost-salvage) - total depreciation from prior periods) * (factor/life) totalDepreciation += (cost - salvage - totalDepreciation)*facLife; } // double result= (cost - salvage - totalDepreciation) * (facLife); - * + * */ - for (int i = 1; i < period; i++) { - totalDepreciation+= (cost-totalDepreciation)*facLife; - } - double result= 0.0; - if (cost-salvage-totalDepreciation > 0) - result = (cost-totalDepreciation)*facLife; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * DISC Returns the discount rate for a security - * - * Settlement is the security's settlement date. The security settlement - * date is the date after the issue date when the security is traded to the - * buyer. Maturity is the security's maturity date. The maturity date is the - * date when the security expires. Pr is the security's price per $100 face - * value. Redemption is the security's redemption value per $100 face value. - * Basis (optional) is the type of day count basis to use. - */ - protected static Ptg calcDISC(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcDISC"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - double pr = operands[2].getDoubleVal(); - double redemption = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - if (pr <= 0 || redemption <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - + for (int i = 1; i < period; i++) { + totalDepreciation += (cost - totalDepreciation) * facLife; + } + double result = 0.0; + if (cost - salvage - totalDepreciation > 0) + result = (cost - totalDepreciation) * facLife; + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * DISC Returns the discount rate for a security + *

                + * Settlement is the security's settlement date. The security settlement + * date is the date after the issue date when the security is traded to the + * buyer. Maturity is the security's maturity date. The maturity date is the + * date when the security expires. Pr is the security's price per $100 face + * value. Redemption is the security's redemption value per $100 face value. + * Basis (optional) is the type of day count basis to use. + */ + protected static Ptg calcDISC(Ptg[] operands) { + if (operands.length < 4) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcDISC"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); + double pr = operands[2].getDoubleVal(); + double redemption = operands[3].getDoubleVal(); + int basis = 0; + if (operands.length > 4) + basis = operands[4].getIntVal(); + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + if (settlementDate > maturityDate) + return new PtgErr(PtgErr.ERROR_NUM); + if (pr <= 0 || redemption <= 0) + return new PtgErr(PtgErr.ERROR_NUM); + + // double DSM = maturityDate - settlementDate; // double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); // double result = (redemption - pr) / redemption * (B / DSM); - double result = (redemption - pr) / (redemption * yearFrac(basis, settlementDate, maturityDate)); - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcDISC= " + result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * DOLLARDE Converts a dollar price, expressed as a fraction, into a dollar - * price, expressed as a decimal number Fractional_dollar is a number - * expressed as a fraction. Fraction is the integer to use in the - * denominator of the fraction. - * - */ - protected static Ptg calcDollarDE(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcDOLLARDE"); - double fractional_dollar = operands[0].getDoubleVal(); - int fraction = operands[1].getIntVal(); - if (fraction < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (fraction == 0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - - int n = String.valueOf(fraction).length(); - double x = Math.floor(fractional_dollar); - double y = (fractional_dollar - x) * Math.pow(10, n); - double result = x + y / fraction; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcDOLLARDE= " + result); - return pnum; - } - - /** - * DOLLARFR Converts a dollar price, expressed as a decimal number, into a - * dollar price, expressed as a fraction Decimal_dollar is a decimal number. - * Fraction is the integer to use in the denominator of a fraction - */ - protected static Ptg calcDollarFR(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcDOLLARFR"); - double decimal_dollar = operands[0].getDoubleVal(); - int fraction = operands[1].getIntVal(); - if (fraction < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (fraction == 0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - - int n = String.valueOf(fraction).length(); - - double x = Math.floor(decimal_dollar); - double y = (decimal_dollar - x); - double result = x + (y * fraction) / Math.pow(10, n); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcDOLLARFR= " + result); - return pnum; - } - - /** - * DURATION Returns the annual duration of a security with periodic interest - * payments - * DURATION(settlement,maturity,coupon,yld,frequency,basis) - * Settlement is the security's settlement date. Maturity is the security's - * maturity date. Coupon is the security's annual coupon rate. Yld is the - * security's annual yield. Frequency is the number of coupon payments per - * year. For annual payments, frequency = 1; for semiannual, frequency = 2; - * for quarterly, frequency = 4. Basis is the type of day count basis to use - */ - protected static Ptg calcDURATION(Ptg[] operands) { - if (operands.length < 5) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcDURATION"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double coupon = operands[2].getDoubleVal(); - double yld = operands[3].getDoubleVal(); - int frequency = operands[4].getIntVal(); - int basis = 0; - if (operands.length > 5) - basis = operands[5].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - if (coupon < 0 || yld < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - - Ptg[] ops = new Ptg[4]; - ops[0] = operands[0]; - ops[1] = operands[1]; - ops[2] = operands[4]; - ops[3] = new PtgInt(basis); - - /* - * Duration= (A + SumC)/(D+ SumB * 1/frequency - * - * where - * Y= 1+ yield/frequency - * R= 100*rate - * F= DSC/E - * A= (F*100)/Y^(n-1+F) - * SumC= Sum(1,n): R/(frequency*Y^(i-1+F)) * (i-1+F) - * D= 100/Y^(n-1+F) - * SumB= Sum(1,n): R/(frequency*Y^(i-1+F)) - * - */ - double n =calcCoupNum(ops).getDoubleVal(); - double DSC= calcCoupDaysNC(ops).getDoubleVal(); - double E= calcCoupDays(ops).getDoubleVal(); - double F= DSC/E; - double R= coupon*100; - double Y= 1+ yld/frequency; - double Yx= Math.pow(Y, n-1+F); - double SumA= 0; - for (int i=1; i<=n; i++) { - SumA+= R*(i-1+F)/(Math.pow(Y, i-1+F)*frequency); - } - double SumB= 0; - for (int i=1; i<=n; i++) { - SumB+= R/(Math.pow(Y, i-1+F)*frequency); - } - double C= 0.0, D= 0.0; - if (n > 1) { - C= ((n-1+F)*100)/Yx; - D= 100/Yx; - } - double result= (SumA+C)/((SumB+D) * frequency); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from calcDURATION= " + result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * EFFECT Returns the effective annual interest rate Nominal_rate is the - * nominal interest rate. Npery is the number of compounding periods per - * year - */ - protected static Ptg calcEffect(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcEFFECT"); - double nominal_rate = operands[0].getDoubleVal(); - int npery = operands[1].getIntVal(); - // TODO: If either argument is non-numeric, #VALUE! error - if (npery <= 0 || npery < 1) // funny guard!!!! - return new PtgErr(PtgErr.ERROR_NUM); - /* - * // KSC: TESTING for (int j= 1; j <= 10; j++) { double i; if ((j % 2) == - * 0) i= 1; else i= .5; npery= j; while (i <= 10) { nominal_rate= i / - * 100; - * - * - * double x= nominal_rate / npery; double result= Math.pow(1 + x, npery) - - * 1; Logger.logInfo("nominal_rate= " + nominal_rate + " npery= " + - * npery + " result= " + result); java.math.BigDecimal bd= new - * java.math.BigDecimal(i-Math.floor(i)).setScale(5, - * java.math.BigDecimal.ROUND_HALF_UP); if (((bd.doubleValue()*100) % 4) == - * 0) // if ((((i-Math.floor(i))*100) % 4)==0) i+= 0.3; else i+= 0.25; } } - */ - double x = nominal_rate / npery; - double result = Math.pow(1 + x, npery) - 1; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from calcEFFECT= " + result); - return pnum; - } - - /** - * FV Returns the future value of an investment FV(rate,nper,pmt,pv,type) - * - * - * Rate is the interest rate per period. Nper is the total number of payment - * periods in an annuity. Pmt is the payment made each period; it cannot - * change over the life of the annuity . Typically, pmt contains principal - * and interest but no other fees or taxes. If pmt is omitted, you must - * include the pv argument. Pv is the present value, or the lump-sum amount - * that a series of future payments is worth right now. If pv is omitted, it - * is assumed to be 0 (zero), and you must include the pmt argument. Type is - * the number 0 or 1 and indicates when payments are due. If type is - * omitted, it is assumed to be 0. - * - * - */ - protected static Ptg calcFV(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcFV"); - double rate = operands[0].getDoubleVal(); - int nper = operands[1].getIntVal(); - double pmt = operands[2].getDoubleVal(); - double pv = 0; - int type = 0; - if (operands.length > 3 && !(operands[3] instanceof PtgMissArg)) - pv = operands[3].getDoubleVal(); - if (operands.length > 4) - type = operands[4].getIntVal(); - - double A = Math.pow(1 + rate, nper); - double B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, nper) - 1) / rate; - double result = -(pv * A + B); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcFV= " + result); - return pnum; - - } - - /** - * FVSCHEDULE Returns the future value of an initial principal after - * applying a series of compound interest rates - */ - protected static Ptg calcFVSCHEDULE(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "calcFVSCHEDULE"); - double principal = operands[0].getDoubleVal(); - Ptg[] schedule = PtgCalculator.getAllComponents(operands[1]); - if (DEBUG) debugOperands(schedule, "calcFVSCHEDULE"); // AFTER converting - // references ... - double result = 1.0; - for (int i = 0; i < schedule.length; i++) { - result *= principal + schedule[i].getDoubleVal(); - } - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcFVSCHEDULE= " + result); - return pnum; - } - - /** - * INTRATE Returns the interest rate for a fully invested security - */ - protected static Ptg calcINTRATE(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcINTRATE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double investment = operands[2].getDoubleVal(); - double redemption = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (investment <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); + double result = (redemption - pr) / (redemption * yearFrac(basis, settlementDate, maturityDate)); + + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcDISC= " + result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * DOLLARDE Converts a dollar price, expressed as a fraction, into a dollar + * price, expressed as a decimal number Fractional_dollar is a number + * expressed as a fraction. Fraction is the integer to use in the + * denominator of the fraction. + */ + protected static Ptg calcDollarDE(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcDOLLARDE"); + double fractional_dollar = operands[0].getDoubleVal(); + int fraction = operands[1].getIntVal(); + if (fraction < 0) + return new PtgErr(PtgErr.ERROR_NUM); + if (fraction == 0) + return new PtgErr(PtgErr.ERROR_DIV_ZERO); + + int n = String.valueOf(fraction).length(); + double x = Math.floor(fractional_dollar); + double y = (fractional_dollar - x) * Math.pow(10, n); + double result = x + y / fraction; + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcDOLLARDE= " + result); + return pnum; + } + + /** + * DOLLARFR Converts a dollar price, expressed as a decimal number, into a + * dollar price, expressed as a fraction Decimal_dollar is a decimal number. + * Fraction is the integer to use in the denominator of a fraction + */ + protected static Ptg calcDollarFR(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcDOLLARFR"); + double decimal_dollar = operands[0].getDoubleVal(); + int fraction = operands[1].getIntVal(); + if (fraction < 0) + return new PtgErr(PtgErr.ERROR_NUM); + if (fraction == 0) + return new PtgErr(PtgErr.ERROR_DIV_ZERO); + + int n = String.valueOf(fraction).length(); + + double x = Math.floor(decimal_dollar); + double y = (decimal_dollar - x); + double result = x + (y * fraction) / Math.pow(10, n); + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcDOLLARFR= " + result); + return pnum; + } + + /** + * DURATION Returns the annual duration of a security with periodic interest + * payments + * DURATION(settlement,maturity,coupon,yld,frequency,basis) + * Settlement is the security's settlement date. Maturity is the security's + * maturity date. Coupon is the security's annual coupon rate. Yld is the + * security's annual yield. Frequency is the number of coupon payments per + * year. For annual payments, frequency = 1; for semiannual, frequency = 2; + * for quarterly, frequency = 4. Basis is the type of day count basis to use + */ + protected static Ptg calcDURATION(Ptg[] operands) { + if (operands.length < 5) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcDURATION"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + double coupon = operands[2].getDoubleVal(); + double yld = operands[3].getDoubleVal(); + int frequency = operands[4].getIntVal(); + int basis = 0; + if (operands.length > 5) + basis = operands[5].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) + return new PtgErr(PtgErr.ERROR_NUM); + if (coupon < 0 || yld < 0) + return new PtgErr(PtgErr.ERROR_NUM); + if (!(frequency == 1 || frequency == 2 || frequency == 4)) + return new PtgErr(PtgErr.ERROR_NUM); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); + + Ptg[] ops = new Ptg[4]; + ops[0] = operands[0]; + ops[1] = operands[1]; + ops[2] = operands[4]; + ops[3] = new PtgInt(basis); + + /* + * Duration= (A + SumC)/(D+ SumB * 1/frequency + * + * where + * Y= 1+ yield/frequency + * R= 100*rate + * F= DSC/E + * A= (F*100)/Y^(n-1+F) + * SumC= Sum(1,n): R/(frequency*Y^(i-1+F)) * (i-1+F) + * D= 100/Y^(n-1+F) + * SumB= Sum(1,n): R/(frequency*Y^(i-1+F)) + * + */ + double n = calcCoupNum(ops).getDoubleVal(); + double DSC = calcCoupDaysNC(ops).getDoubleVal(); + double E = calcCoupDays(ops).getDoubleVal(); + double F = DSC / E; + double R = coupon * 100; + double Y = 1 + yld / frequency; + double Yx = Math.pow(Y, n - 1 + F); + double SumA = 0; + for (int i = 1; i <= n; i++) { + SumA += R * (i - 1 + F) / (Math.pow(Y, i - 1 + F) * frequency); + } + double SumB = 0; + for (int i = 1; i <= n; i++) { + SumB += R / (Math.pow(Y, i - 1 + F) * frequency); + } + double C = 0.0, D = 0.0; + if (n > 1) { + C = ((n - 1 + F) * 100) / Yx; + D = 100 / Yx; + } + double result = (SumA + C) / ((SumB + D) * frequency); + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) Logger.logInfo("Result from calcDURATION= " + result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * EFFECT Returns the effective annual interest rate Nominal_rate is the + * nominal interest rate. Npery is the number of compounding periods per + * year + */ + protected static Ptg calcEffect(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcEFFECT"); + double nominal_rate = operands[0].getDoubleVal(); + int npery = operands[1].getIntVal(); + // TODO: If either argument is non-numeric, #VALUE! error + if (npery <= 0 || npery < 1) // funny guard!!!! + return new PtgErr(PtgErr.ERROR_NUM); + /* + * // KSC: TESTING for (int j= 1; j <= 10; j++) { double i; if ((j % 2) == + * 0) i= 1; else i= .5; npery= j; while (i <= 10) { nominal_rate= i / + * 100; + * + * + * double x= nominal_rate / npery; double result= Math.pow(1 + x, npery) - + * 1; Logger.logInfo("nominal_rate= " + nominal_rate + " npery= " + + * npery + " result= " + result); java.math.BigDecimal bd= new + * java.math.BigDecimal(i-Math.floor(i)).setScale(5, + * java.math.BigDecimal.ROUND_HALF_UP); if (((bd.doubleValue()*100) % 4) == + * 0) // if ((((i-Math.floor(i))*100) % 4)==0) i+= 0.3; else i+= 0.25; } } + */ + double x = nominal_rate / npery; + double result = Math.pow(1 + x, npery) - 1; + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) Logger.logInfo("Result from calcEFFECT= " + result); + return pnum; + } + + /** + * FV Returns the future value of an investment FV(rate,nper,pmt,pv,type) + *

                + *

                + * Rate is the interest rate per period. Nper is the total number of payment + * periods in an annuity. Pmt is the payment made each period; it cannot + * change over the life of the annuity . Typically, pmt contains principal + * and interest but no other fees or taxes. If pmt is omitted, you must + * include the pv argument. Pv is the present value, or the lump-sum amount + * that a series of future payments is worth right now. If pv is omitted, it + * is assumed to be 0 (zero), and you must include the pmt argument. Type is + * the number 0 or 1 and indicates when payments are due. If type is + * omitted, it is assumed to be 0. + */ + protected static Ptg calcFV(Ptg[] operands) { + if (operands.length < 3) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcFV"); + double rate = operands[0].getDoubleVal(); + int nper = operands[1].getIntVal(); + double pmt = operands[2].getDoubleVal(); + double pv = 0; + int type = 0; + if (operands.length > 3 && !(operands[3] instanceof PtgMissArg)) + pv = operands[3].getDoubleVal(); + if (operands.length > 4) + type = operands[4].getIntVal(); + + double A = Math.pow(1 + rate, nper); + double B = pmt * (1 + rate * type); + B *= (Math.pow(1 + rate, nper) - 1) / rate; + double result = -(pv * A + B); + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcFV= " + result); + return pnum; + + } + + /** + * FVSCHEDULE Returns the future value of an initial principal after + * applying a series of compound interest rates + */ + protected static Ptg calcFVSCHEDULE(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "calcFVSCHEDULE"); + double principal = operands[0].getDoubleVal(); + Ptg[] schedule = PtgCalculator.getAllComponents(operands[1]); + if (DEBUG) debugOperands(schedule, "calcFVSCHEDULE"); // AFTER converting + // references ... + double result = 1.0; + for (int i = 0; i < schedule.length; i++) { + result *= principal + schedule[i].getDoubleVal(); + } + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcFVSCHEDULE= " + result); + return pnum; + } + + /** + * INTRATE Returns the interest rate for a fully invested security + */ + protected static Ptg calcINTRATE(Ptg[] operands) { + if (operands.length < 4) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcINTRATE"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + double investment = operands[2].getDoubleVal(); + double redemption = operands[3].getDoubleVal(); + int basis = 0; + if (operands.length > 4) + basis = operands[4].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); + if (investment <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); // double delta = maturityDate - settlementDate; // double result = ((redemption - investment) / investment) * ((getDaysInYearFromBasis(basis, settlementDate, maturityDate) / delta)); - double result = ((redemption - investment) / investment)/yearFrac(basis, settlementDate, maturityDate); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcINTRATE= " + result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * IPMT Returns the interest payment for an investment for a given period. - * - * Syntax IPMT(rate,per,nper,pv,fv,type) - * - * Rate is the interest rate per period. Per is the period for which you - * want to find the interest and must be in the range 1 to nper. Nper is the - * total number of payment periods in an annuity. Pv is the present value, - * or the lump-sum amount that a series of future payments is worth right - * now. Fv is the future value, or a cash balance you want to attain after - * the last payment is made. If fv is omitted, it is assumed to be 0 (the - * future value of a loan, for example, is 0). Type is the number 0 or 1 and - * indicates when payments are due. If type is omitted, it is assumed to be - * 0. - */ - protected static Ptg calcIPMT(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcIPMT"); - double rate = operands[0].getDoubleVal(); - double per = operands[1].getDoubleVal(); - double nper = operands[2].getDoubleVal(); - double pv = operands[3].getDoubleVal(); - double fv = 0; - int type = 0; - if (operands.length > 4 && !(operands[4] instanceof PtgMissArg)) - fv = operands[4].getDoubleVal(); - if (operands.length > 5) - type = operands[5].getIntVal(); - - if (per < 0 || per > nper) - return new PtgErr(PtgErr.ERROR_NUM); - - // IPMT= pmt- (fv(b) - fv(a) i.e. = payement less the principal balance - // btwn two periods - - double n; - if (type == 0) - n = per; - else - n = per - 1; - // PMT - double Rn = Math.pow(1 + rate, nper); - double A = -fv * rate - pv * Rn * rate; - double B = (Rn - 1) * (1 + rate * type); - double pmt = A / B; - // FVa - A = Math.pow(1 + rate, n); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n) - 1) / rate; - double fva = -(pv * A + B); - // FVb - A = Math.pow(1 + rate, n - 1); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n - 1) - 1) / rate; - double fvb = -(pv * A + B); - - double result = pmt - (fvb - fva); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcIPMT= " + result); - return pnum; - } - - /** - * IRR Returns the internal rate of return for a series of cash flows - * Returns the internal rate of return for a series of cash flows - * represented by the numbers in values. These cash flows do not have to be - * even, as they would be for an annuity. However, the cash flows must occur - * at regular intervals, such as monthly or annually. The internal rate of - * return is the interest rate received for an investment consisting of - * payments (negative values) and income (positive values) that occur at - * regular periods. - * - * SEE: NPV - * - * Syntax IRR(values,guess) - * - * Values is an array or a reference to cells that contain numbers for which - * you want to calculate the internal rate of return. - * - * Values must contain at least one positive value and one negative value to - * calculate the internal rate of return. - * - * IRR uses the order of values to interpret the order of cash flows. Be - * sure to enter your payment and income values in the sequence you want. - * - * If an array or reference argument contains text, logical values, or empty - * cells, those values are ignored. Guess is a number that you guess is - * close to the result of IRR. - * - * Microsoft Excel uses an iterative technique for calculating IRR. Starting - * with guess, IRR cycles through the calculation until the result is - * accurate within 0.00001 percent. If IRR can't find a result that works - * after 20 tries, the #NUM! error value is returned. - * - * In most cases you do not need to provide guess for the IRR calculation. - * If guess is omitted, it is assumed to be 0.1 (10 percent). - * - * If IRR gives the #NUM! error value, or if the result is not close to what - * you expected, try again with a different value for guess - */ - protected static Ptg calcIRR(Ptg[] operands) { - if (operands.length < 1 || operands[0].getComponents() == null) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcIRR"); - double guess = .1; - if (operands.length > 1) - guess = operands[1].getDoubleVal(); - Ptg[] params = PtgCalculator.getAllComponents(operands); - if (DEBUG) - debugOperands(params, "calcIRR"); // AFTER converting references ... - int n = params.length; - - // examine values array, sum all outflows (= negative values) + inflows - // (= positive values) - double outflow = 0.0, inflow = 0.0; - // get outflow (- values) - for (int i = 0; i < n; i++) { - double val = params[i].getDoubleVal(); - if (val < 0) - outflow += Math.abs(val); - else - inflow += val; - } - if (outflow == 0.0 || inflow == 0.0) - return new PtgErr(PtgErr.ERROR_VALUE); // TODO: Excel doesn't specify which - // error to return - - // iterate over possible irr values; value is correct when - // outflow-pv <= tolerance, defined as .00001% - final double TOLERANCE = 0.0000001; - boolean bIsCorrect = false; - double xl, xh, fl, fh, f, trial = guess; - xl = 0; - xh = guess; - double delta = xh - xl; - fl = outflow - inflow; - fh = outflow; - for (int i = 0; i < n; i++) { - double val = params[i].getDoubleVal(); - if (val > 0) { - fh -= val / (Math.pow(1 + xh, i)); - } - } - for (int j = 0; j < 50 && !bIsCorrect; j++) { // maximum 20 tries - need - // more!!!! - trial = xl + delta * fl / (fl - fh); - f = outflow; - for (int i = 0; i < n; i++) { - double val = params[i].getDoubleVal(); - if (val > 0) { - f -= val / (Math.pow(1 + trial, i)); - } - } + double result = ((redemption - investment) / investment) / yearFrac(basis, settlementDate, maturityDate); + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcINTRATE= " + result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * IPMT Returns the interest payment for an investment for a given period. + *

                + * Syntax IPMT(rate,per,nper,pv,fv,type) + *

                + * Rate is the interest rate per period. Per is the period for which you + * want to find the interest and must be in the range 1 to nper. Nper is the + * total number of payment periods in an annuity. Pv is the present value, + * or the lump-sum amount that a series of future payments is worth right + * now. Fv is the future value, or a cash balance you want to attain after + * the last payment is made. If fv is omitted, it is assumed to be 0 (the + * future value of a loan, for example, is 0). Type is the number 0 or 1 and + * indicates when payments are due. If type is omitted, it is assumed to be + * 0. + */ + protected static Ptg calcIPMT(Ptg[] operands) { + if (operands.length < 4) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcIPMT"); + double rate = operands[0].getDoubleVal(); + double per = operands[1].getDoubleVal(); + double nper = operands[2].getDoubleVal(); + double pv = operands[3].getDoubleVal(); + double fv = 0; + int type = 0; + if (operands.length > 4 && !(operands[4] instanceof PtgMissArg)) + fv = operands[4].getDoubleVal(); + if (operands.length > 5) + type = operands[5].getIntVal(); + + if (per < 0 || per > nper) + return new PtgErr(PtgErr.ERROR_NUM); + + // IPMT= pmt- (fv(b) - fv(a) i.e. = payement less the principal balance + // btwn two periods + + double n; + if (type == 0) + n = per; + else + n = per - 1; + // PMT + double Rn = Math.pow(1 + rate, nper); + double A = -fv * rate - pv * Rn * rate; + double B = (Rn - 1) * (1 + rate * type); + double pmt = A / B; + // FVa + A = Math.pow(1 + rate, n); + B = pmt * (1 + rate * type); + B *= (Math.pow(1 + rate, n) - 1) / rate; + double fva = -(pv * A + B); + // FVb + A = Math.pow(1 + rate, n - 1); + B = pmt * (1 + rate * type); + B *= (Math.pow(1 + rate, n - 1) - 1) / rate; + double fvb = -(pv * A + B); + + double result = pmt - (fvb - fva); + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcIPMT= " + result); + return pnum; + } + + /** + * IRR Returns the internal rate of return for a series of cash flows + * Returns the internal rate of return for a series of cash flows + * represented by the numbers in values. These cash flows do not have to be + * even, as they would be for an annuity. However, the cash flows must occur + * at regular intervals, such as monthly or annually. The internal rate of + * return is the interest rate received for an investment consisting of + * payments (negative values) and income (positive values) that occur at + * regular periods. + *

                + * SEE: NPV + *

                + * Syntax IRR(values,guess) + *

                + * Values is an array or a reference to cells that contain numbers for which + * you want to calculate the internal rate of return. + *

                + * Values must contain at least one positive value and one negative value to + * calculate the internal rate of return. + *

                + * IRR uses the order of values to interpret the order of cash flows. Be + * sure to enter your payment and income values in the sequence you want. + *

                + * If an array or reference argument contains text, logical values, or empty + * cells, those values are ignored. Guess is a number that you guess is + * close to the result of IRR. + *

                + * Microsoft Excel uses an iterative technique for calculating IRR. Starting + * with guess, IRR cycles through the calculation until the result is + * accurate within 0.00001 percent. If IRR can't find a result that works + * after 20 tries, the #NUM! error value is returned. + *

                + * In most cases you do not need to provide guess for the IRR calculation. + * If guess is omitted, it is assumed to be 0.1 (10 percent). + *

                + * If IRR gives the #NUM! error value, or if the result is not close to what + * you expected, try again with a different value for guess + */ + protected static Ptg calcIRR(Ptg[] operands) { + if (operands.length < 1 || operands[0].getComponents() == null) { // not + // supported + // by + // function + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcIRR"); + double guess = .1; + if (operands.length > 1) + guess = operands[1].getDoubleVal(); + Ptg[] params = PtgCalculator.getAllComponents(operands); + if (DEBUG) + debugOperands(params, "calcIRR"); // AFTER converting references ... + int n = params.length; + + // examine values array, sum all outflows (= negative values) + inflows + // (= positive values) + double outflow = 0.0, inflow = 0.0; + // get outflow (- values) + for (int i = 0; i < n; i++) { + double val = params[i].getDoubleVal(); + if (val < 0) + outflow += Math.abs(val); + else + inflow += val; + } + if (outflow == 0.0 || inflow == 0.0) + return new PtgErr(PtgErr.ERROR_VALUE); // TODO: Excel doesn't specify which + // error to return + + // iterate over possible irr values; value is correct when + // outflow-pv <= tolerance, defined as .00001% + final double TOLERANCE = 0.0000001; + boolean bIsCorrect = false; + double xl, xh, fl, fh, f, trial = guess; + xl = 0; + xh = guess; + double delta = xh - xl; + fl = outflow - inflow; + fh = outflow; + for (int i = 0; i < n; i++) { + double val = params[i].getDoubleVal(); + if (val > 0) { + fh -= val / (Math.pow(1 + xh, i)); + } + } + for (int j = 0; j < 50 && !bIsCorrect; j++) { // maximum 20 tries - need + // more!!!! + trial = xl + delta * fl / (fl - fh); + f = outflow; + for (int i = 0; i < n; i++) { + double val = params[i].getDoubleVal(); + if (val > 0) { + f -= val / (Math.pow(1 + trial, i)); + } + } - if (f < 0) { - delta = xl - trial; - xl = trial; - fl = f; - } else { - delta = xh - trial; - xh = trial; - fh = f; - } - bIsCorrect = (Math.abs(delta) <= TOLERANCE); - delta = xh - xl; - } - if (!bIsCorrect) - return new PtgErr(PtgErr.ERROR_NUM); - - if (DEBUG) - Logger.logInfo("Result from calcIRR= " + trial); - PtgNumber pnum = new PtgNumber(trial); - return pnum; - } - - /** - * ISPMT Calculates the interest paid during a specific period of an - * investment. This function is provided for compatibility with Lotus 1-2-3. - * ISPMT(rate,per,nper,pv) Rate is the interest rate for the investment. Per - * is the period for which you want to find the interest, and must be - * between 1 and nper. Nper is the total number of payment periods for the - * investment. Pv is the present value of the investment. For a loan, pv is - * the loan amount. - */ - protected static Ptg calcISPMT(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcISPMT"); - double rate = operands[0].getDoubleVal(); - double per = operands[1].getDoubleVal(); - double nper = operands[2].getDoubleVal(); - double pv = operands[3].getDoubleVal(); - if (per < 0 || per > nper) - return new PtgErr(PtgErr.ERROR_NUM); - - double result = (-pv * rate * (nper - per)) / nper; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcISPMT= " + result); - return pnum; - } - - /** - * MDURATION Returns the Macauley modified duration for a security with an - * assumed par value of $100 - * DURATION(settlement,maturity,coupon,yld,frequency,basis) Settlement is - * the security's settlement date. Maturity is the security's maturity date. - * Coupon is the security's annual coupon rate. Yld is the security's annual - * yield. Frequency is the number of coupon payments per year. For annual - * payments, frequency = 1; for semiannual, frequency = 2; for quarterly, - * frequency = 4. Basis is the type of day count basis to use - */ - protected static Ptg calcMDURATION(Ptg[] operands) { - if (operands.length < 5) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcMDURATION"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double coupon = operands[2].getDoubleVal(); - double yld = operands[3].getDoubleVal(); - int frequency = operands[4].getIntVal(); - int basis = 0; - if (operands.length > 5) - basis = operands[5].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - if (coupon < 0 || yld < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - - double result = calcDURATION(operands).getDoubleVal(); - // above is regular duration calculation; to get modified duration: - result = result / (1 + yld / frequency); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcMDURATION= " + result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * MIRR Returns the internal rate of return where positive and negative cash - * flows are financed at different rates - * - * MIRR(values,finance_rate,reinvest_rate) - * - * Values is an array or a reference to cells that contain numbers. These - * numbers represent a series of payments (negative values) and income - * (positive values) occurring at regular periods. - * - * Values must contain at least one positive value and one negative value to - * calculate the modified internal rate of return. Otherwise, MIRR returns - * the #DIV/0! error value. - * - * If an array or reference argument contains text, logical values, or empty - * cells, those values are ignored; however, cells with the value zero are - * included. - * - * Finance_rate is the interest rate you pay on the money used in the cash - * flows. - * - * Reinvest_rate is the interest rate you receive on the cash flows as you - * reinvest them. - */ - protected static Ptg calcMIRR(Ptg[] operands) { - if (operands.length < 3) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcMIRR"); - double finance_rate = operands[1].getDoubleVal(); - double reinvest_rate = operands[2].getDoubleVal(); - Ptg[] params = PtgCalculator.getAllComponents(operands); - if (DEBUG) - debugOperands(params, "calcMIRR"); // AFTER converting references - // ... - - // Get + Values and - Values in separate Ptg arrays - Vector posVals = new Vector(); - Vector negVals = new Vector(); - Ptg[] positiveValues; - Ptg[] negativeValues; - int n = params.length - 2; // skip last 2 params (= rates) - for (int i = 0; i < n; i++) { - double val = params[i].getDoubleVal(); - if (val < 0) - negVals.addElement(params[i]); - else - posVals.addElement(params[i]); - } - positiveValues = new Ptg[posVals.size() + 1]; - negativeValues = new Ptg[negVals.size() + 1]; - System.arraycopy(posVals.toArray(), 0, positiveValues, 1, posVals - .size()); - System.arraycopy(negVals.toArray(), 0, negativeValues, 1, negVals - .size()); - - // add rate to Ptg array for call to calcNPV - positiveValues[0] = operands[2]; // reinvest rate - negativeValues[0] = operands[1]; // finance rate - - // Calculate MIRR from NPV values - double X = calcNPV(positiveValues).getDoubleVal(); - X = -1 * X * Math.pow(1 + reinvest_rate, posVals.size()); - double Y = calcNPV(negativeValues).getDoubleVal(); - Y = Y * (1 + finance_rate); - double result = Math.pow(X / Y, 1.0 / (n - 1)) - 1; - - if (DEBUG) - Logger.logInfo("Result from calcMIRR= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * NOMINAL Returns the annual nominal interest rate - */ - protected static Ptg calcNominal(Ptg[] operands) { - if (operands.length < 2) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcNominal"); - double effect = operands[0].getDoubleVal(); - int npery = operands[1].getIntVal(); - // TODO: if either is non-numeric, return #VALUE! - if (effect <= 0 || npery < 1) - return new PtgErr(PtgErr.ERROR_NUM); - - // solve for nominal_rate in: - // effect= (1 + nominal_rate/npery)^npery -1 - // nominal_rate= ((10^(log10(y)/n)) - 1)*npery - double y = effect + 1; - double log10y = Math.log(y) / Math.log(10); // base 10 log - double z = Math.pow(10, log10y / npery); - double result = (z - 1) * npery; - if (DEBUG) - Logger.logInfo("Result from calcNominal= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * NPER Returns the number of periods for an investment NPER(rate, pmt, pv, - * fv, type) - * - * Rate is the interest rate per period. Pmt is the payment made each - * period; it cannot change over the life of the annuity. Typically, pmt - * contains principal and interest but no other fees or taxes. Pv is the - * present value, or the lump-sum amount that a series of future payments is - * worth right now. Fv is the future value, or a cash balance you want to - * attain after the last payment is made. If fv is omitted, it is assumed to - * be 0 (the future value of a loan, for example, is 0). Type is the number - * 0 or 1 and indicates when payments are due. - */ - protected static Ptg calcNPER(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcNPER"); - double rate = operands[0].getDoubleVal(); - double pmt = operands[1].getDoubleVal(); - double pv = operands[2].getDoubleVal(); - double fv = 0; - if (operands.length > 3 && !(operands[3] instanceof PtgMissArg)) - fv = operands[3].getDoubleVal(); - int type = 0; - if (operands.length > 4) - type = operands[4].getIntVal(); - - double A = pmt * (1 + type * rate) - rate * fv; - double B = pmt * (1 + type * rate) + rate * pv; - double C = 1 + rate; - double result = Math.log(A / B) / Math.log(C); - if (DEBUG) - Logger.logInfo("Result from calcNPER= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * NPV(rate,value1,value2, ...) Calculates the net present value of an - * investment by using a discount rate and a series of future payments - * (negative values) and income (positive values). - * - * Rate is the rate of discount over the length of one period. Value1, - * value2, ... are 1 to 29 arguments representing the payments and income. - * Value1, value2, ... must be equally spaced in time and occur at the end - * of each period - * - * Returns the net present value of an investment based on a series of - * periodic cash flows and a discount rate = Sum(values / (1 + rate) ) - * - */ - protected static Ptg calcNPV(Ptg[] operands) { - if (operands.length < 2 || operands[0].getComponents() != null) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) debugOperands(operands, "calcNPV"); - Ptg[] params = PtgCalculator.getAllComponents(operands); - if (DEBUG) debugOperands(params, "calcNPV"); // AFTER converting references ... - double rate = params[0].getDoubleVal(); - int n = Math.min(params.length, 30); // at most 29 values - double result = 0; - for (int i = 1; i < n; i++) { - double valuei = params[i].getDoubleVal(); - // TODO: if valuei is an error, empty, etc., ignore - result += valuei / Math.pow(1 + rate, i); - } - if (DEBUG) Logger.logInfo("Result from calcNPV= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * ODDFPRICE Returns the price per $100 face value of a security with an odd - * first period - * ODDFPRICE(settlement,maturity,issue,first_coupon,rate,yld,redemption,frequency,basis) - * - */ - protected static Ptg calcODDFPRICE(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ODDFPRICE."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 8) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcODDFPRICE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); - double rate = operands[4].getDoubleVal(); - double yld = operands[5].getDoubleVal(); - double redemption = operands[6].getDoubleVal(); - int frequency = operands[7].getIntVal(); - int basis = 0; - if (operands.length > 8) - basis = operands[8].getIntVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - /*maturity > first_coupon > settlement > issue */ - if (issueDate >= settlementDate || settlementDate >= firstCouponDate || - firstCouponDate >= maturityDate) return new PtgErr(PtgErr.ERROR_NUM); - - - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(maturityDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double NCD= calcCoupNCD(ops).getDoubleVal(); // next coupon after 1st coupon date????? - double DFC= firstCouponDate-settlementDate; // # days from odd first coupon to next coupon date -double z= getDaysFromBasis(basis, settlementDate, firstCouponDate); - - double R = 100*rate/frequency; - double Y = 1 + yld/frequency; - - double result= 0.0; - if (DFC < E) { // odd short first coupon - double firstTerm= redemption/Math.pow(Y, N-1+DSC/E); - double secondTerm= (R*DFC/E)/Math.pow(Y, DSC/E); - double thirdTerm= 0.0; - for (int i= 2; i <=N; i++) { - thirdTerm+= R/Math.pow(Y, i-1+DSC/E); - } - double fourthTerm= R*A/E; - result= firstTerm + secondTerm + thirdTerm - fourthTerm; - } - else { // odd long first coupon - - } - - if (DEBUG) Logger.logInfo("Result from calcODDFPRICE= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /* - * ODDFYIELD Returns the yield of a security with an odd first period - * NOT COMPLETED!! - */ - protected static Ptg calcODDFYIELD(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ODDFYIELD."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 8) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcODDFYIELD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); - double rate = operands[4].getDoubleVal(); - double yld = operands[5].getDoubleVal(); - double redemption = operands[6].getDoubleVal(); - int frequency = operands[7].getIntVal(); - int basis = 0; - if (operands.length > 8) - basis = operands[8].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); - - /* - * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC - */ - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(maturityDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double DFC= getDaysFromBasis(basis, firstCouponDate, (long)A); // days from 1st odd coupon to 1st coupon - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double R = 100*rate/frequency; - double Y = 1 + yld/frequency; - - double result= 0.0; - - if (DEBUG) Logger.logInfo("Result from calcODDFYIELD= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - /* - * ODDLPRICE Returns the price per $100 face value of a security with an odd - * last period - * NOT COMPLETED!!!! - */ - protected static Ptg calcODDLPRICE(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ODDLPRICE."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 8) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "calcODDLPRICE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); - double rate = operands[4].getDoubleVal(); - double yld = operands[5].getDoubleVal(); - double redemption = operands[6].getDoubleVal(); - int frequency = operands[7].getIntVal(); - int basis = 0; - if (operands.length > 8) - basis = operands[8].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); - - /* - * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC - */ - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(maturityDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double DFC= getDaysFromBasis(basis, firstCouponDate, (long)A); // days from 1st odd coupon to 1st coupon - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double R = 100*rate/frequency; - double Y = 1 + yld/frequency; - - double result= 0.0; - - if (DEBUG) Logger.logInfo("Result from calcODDLPRICE= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - /* - * ODDLYIELD Returns the yield of a security with an odd last period - * NOT COMPLETED!! - */ - protected static Ptg calcODDLYIELD(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ODDLYIELD."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 8) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "calcODDLYIELD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); - double rate = operands[4].getDoubleVal(); - double yld = operands[5].getDoubleVal(); - double redemption = operands[6].getDoubleVal(); - int frequency = operands[7].getIntVal(); - int basis = 0; - if (operands.length > 8) - basis = operands[8].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); - - /* - * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC - */ - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(maturityDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double DFC= getDaysFromBasis(basis, firstCouponDate, (long)A); // days from 1st odd coupon to 1st coupon - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double R = 100*rate/frequency; - double Y = 1 + yld/frequency; - - double result= 0.0; - - if (DEBUG) Logger.logInfo("Result from calcODDLYIELD= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * PMT Returns the periodic payment for an annuity - * - * pmt= - (fv + (1+rate)**nper * pv) * rate / ( ( (1+rate)**nper - 1 ) * - * (1+rate*type) ) - * - * if fv or type are omitted they should be treated as 0 values. - */ - protected static Ptg calcPmt(Ptg[] operands) { - if (operands.length < 3) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - double rate, nper, pv, fv, type; - rate = new Double(String.valueOf(operands[0].getValue())).doubleValue(); - nper = new Double(String.valueOf(operands[1].getValue())).doubleValue(); - pv = new Double(String.valueOf(operands[2].getValue())).doubleValue(); - if (operands.length > 3) { - if (operands[3] instanceof PtgMissArg) { - fv = 0; - } else { - fv = new Double(String.valueOf(operands[3].getValue())) - .doubleValue(); - } - } else { - fv = 0; - } - if (operands.length > 4) { - if (operands[4] instanceof PtgMissArg) { - type = 0; - } else { - type = new Double(String.valueOf(operands[4].getValue())) - .doubleValue(); - } - } else { - type = 0; - } + if (f < 0) { + delta = xl - trial; + xl = trial; + fl = f; + } else { + delta = xh - trial; + xh = trial; + fh = f; + } + bIsCorrect = (Math.abs(delta) <= TOLERANCE); + delta = xh - xl; + } + if (!bIsCorrect) + return new PtgErr(PtgErr.ERROR_NUM); - //KSC: For some strange odd weird reason, original calculation was off - // even though this should be exactly the same thing. Go figure! - double Rn = Math.pow(1 + rate, nper); - double A = -fv * rate - pv * Rn * rate; - double B = (Rn - 1) * (1 + rate * type); - double result = A / B; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * PPMT Returns the payment on the principal for an investment for a given - * period PPMT(rate,per,nper,pv,fv,type) Rate is the interest rate per - * period. Per specifies the period and must be in the range 1 to nper. Nper - * is the total number of payment periods in an annuity. Pv is the present - * value the total amount that a series of future payments is worth now. - * Fv is the future value, or a cash balance you want to attain after the - * last payment is made. If fv is omitted, it is assumed to be 0 (zero), - * that is, the future value of a loan is 0. Type is the number 0 or 1 and - * indicates when payments are due - * - */ - protected static Ptg calcPPMT(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPPMT"); - double rate = operands[0].getDoubleVal(); - int per = operands[1].getIntVal(); - int nper = operands[2].getIntVal(); - double pv = operands[3].getDoubleVal(); - double fv = 0; - int type = 0; - if (operands.length > 4 && !(operands[4] instanceof PtgMissArg)) - fv = operands[4].getDoubleVal(); - if (operands.length > 5) - type = operands[5].getIntVal(); + if (DEBUG) + Logger.logInfo("Result from calcIRR= " + trial); + PtgNumber pnum = new PtgNumber(trial); + return pnum; + } + + /** + * ISPMT Calculates the interest paid during a specific period of an + * investment. This function is provided for compatibility with Lotus 1-2-3. + * ISPMT(rate,per,nper,pv) Rate is the interest rate for the investment. Per + * is the period for which you want to find the interest, and must be + * between 1 and nper. Nper is the total number of payment periods for the + * investment. Pv is the present value of the investment. For a loan, pv is + * the loan amount. + */ + protected static Ptg calcISPMT(Ptg[] operands) { + if (operands.length < 4) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcISPMT"); + double rate = operands[0].getDoubleVal(); + double per = operands[1].getDoubleVal(); + double nper = operands[2].getDoubleVal(); + double pv = operands[3].getDoubleVal(); + if (per < 0 || per > nper) + return new PtgErr(PtgErr.ERROR_NUM); + + double result = (-pv * rate * (nper - per)) / nper; + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcISPMT= " + result); + return pnum; + } + + /** + * MDURATION Returns the Macauley modified duration for a security with an + * assumed par value of $100 + * DURATION(settlement,maturity,coupon,yld,frequency,basis) Settlement is + * the security's settlement date. Maturity is the security's maturity date. + * Coupon is the security's annual coupon rate. Yld is the security's annual + * yield. Frequency is the number of coupon payments per year. For annual + * payments, frequency = 1; for semiannual, frequency = 2; for quarterly, + * frequency = 4. Basis is the type of day count basis to use + */ + protected static Ptg calcMDURATION(Ptg[] operands) { + if (operands.length < 5) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcMDURATION"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + double coupon = operands[2].getDoubleVal(); + double yld = operands[3].getDoubleVal(); + int frequency = operands[4].getIntVal(); + int basis = 0; + if (operands.length > 5) + basis = operands[5].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) + return new PtgErr(PtgErr.ERROR_NUM); + if (coupon < 0 || yld < 0) + return new PtgErr(PtgErr.ERROR_NUM); + if (!(frequency == 1 || frequency == 2 || frequency == 4)) + return new PtgErr(PtgErr.ERROR_NUM); + + double result = calcDURATION(operands).getDoubleVal(); + // above is regular duration calculation; to get modified duration: + result = result / (1 + yld / frequency); + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcMDURATION= " + result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * MIRR Returns the internal rate of return where positive and negative cash + * flows are financed at different rates + *

                + * MIRR(values,finance_rate,reinvest_rate) + *

                + * Values is an array or a reference to cells that contain numbers. These + * numbers represent a series of payments (negative values) and income + * (positive values) occurring at regular periods. + *

                + * Values must contain at least one positive value and one negative value to + * calculate the modified internal rate of return. Otherwise, MIRR returns + * the #DIV/0! error value. + *

                + * If an array or reference argument contains text, logical values, or empty + * cells, those values are ignored; however, cells with the value zero are + * included. + *

                + * Finance_rate is the interest rate you pay on the money used in the cash + * flows. + *

                + * Reinvest_rate is the interest rate you receive on the cash flows as you + * reinvest them. + */ + protected static Ptg calcMIRR(Ptg[] operands) { + if (operands.length < 3) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcMIRR"); + double finance_rate = operands[1].getDoubleVal(); + double reinvest_rate = operands[2].getDoubleVal(); + Ptg[] params = PtgCalculator.getAllComponents(operands); + if (DEBUG) + debugOperands(params, "calcMIRR"); // AFTER converting references + // ... + + // Get + Values and - Values in separate Ptg arrays + Vector posVals = new Vector(); + Vector negVals = new Vector(); + Ptg[] positiveValues; + Ptg[] negativeValues; + int n = params.length - 2; // skip last 2 params (= rates) + for (int i = 0; i < n; i++) { + double val = params[i].getDoubleVal(); + if (val < 0) + negVals.addElement(params[i]); + else + posVals.addElement(params[i]); + } + positiveValues = new Ptg[posVals.size() + 1]; + negativeValues = new Ptg[negVals.size() + 1]; + System.arraycopy(posVals.toArray(), 0, positiveValues, 1, posVals + .size()); + System.arraycopy(negVals.toArray(), 0, negativeValues, 1, negVals + .size()); + + // add rate to Ptg array for call to calcNPV + positiveValues[0] = operands[2]; // reinvest rate + negativeValues[0] = operands[1]; // finance rate + + // Calculate MIRR from NPV values + double X = calcNPV(positiveValues).getDoubleVal(); + X = -1 * X * Math.pow(1 + reinvest_rate, posVals.size()); + double Y = calcNPV(negativeValues).getDoubleVal(); + Y = Y * (1 + finance_rate); + double result = Math.pow(X / Y, 1.0 / (n - 1)) - 1; + + if (DEBUG) + Logger.logInfo("Result from calcMIRR= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * NOMINAL Returns the annual nominal interest rate + */ + protected static Ptg calcNominal(Ptg[] operands) { + if (operands.length < 2) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcNominal"); + double effect = operands[0].getDoubleVal(); + int npery = operands[1].getIntVal(); + // TODO: if either is non-numeric, return #VALUE! + if (effect <= 0 || npery < 1) + return new PtgErr(PtgErr.ERROR_NUM); + + // solve for nominal_rate in: + // effect= (1 + nominal_rate/npery)^npery -1 + // nominal_rate= ((10^(log10(y)/n)) - 1)*npery + double y = effect + 1; + double log10y = Math.log(y) / Math.log(10); // base 10 log + double z = Math.pow(10, log10y / npery); + double result = (z - 1) * npery; + if (DEBUG) + Logger.logInfo("Result from calcNominal= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * NPER Returns the number of periods for an investment NPER(rate, pmt, pv, + * fv, type) + *

                + * Rate is the interest rate per period. Pmt is the payment made each + * period; it cannot change over the life of the annuity. Typically, pmt + * contains principal and interest but no other fees or taxes. Pv is the + * present value, or the lump-sum amount that a series of future payments is + * worth right now. Fv is the future value, or a cash balance you want to + * attain after the last payment is made. If fv is omitted, it is assumed to + * be 0 (the future value of a loan, for example, is 0). Type is the number + * 0 or 1 and indicates when payments are due. + */ + protected static Ptg calcNPER(Ptg[] operands) { + if (operands.length < 3) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcNPER"); + double rate = operands[0].getDoubleVal(); + double pmt = operands[1].getDoubleVal(); + double pv = operands[2].getDoubleVal(); + double fv = 0; + if (operands.length > 3 && !(operands[3] instanceof PtgMissArg)) + fv = operands[3].getDoubleVal(); + int type = 0; + if (operands.length > 4) + type = operands[4].getIntVal(); + + double A = pmt * (1 + type * rate) - rate * fv; + double B = pmt * (1 + type * rate) + rate * pv; + double C = 1 + rate; + double result = Math.log(A / B) / Math.log(C); + if (DEBUG) + Logger.logInfo("Result from calcNPER= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * NPV(rate,value1,value2, ...) Calculates the net present value of an + * investment by using a discount rate and a series of future payments + * (negative values) and income (positive values). + *

                + * Rate is the rate of discount over the length of one period. Value1, + * value2, ... are 1 to 29 arguments representing the payments and income. + * Value1, value2, ... must be equally spaced in time and occur at the end + * of each period + *

                + * Returns the net present value of an investment based on a series of + * periodic cash flows and a discount rate = Sum(values / (1 + rate) ) + */ + protected static Ptg calcNPV(Ptg[] operands) { + if (operands.length < 2 || operands[0].getComponents() != null) { // not + // supported + // by + // function + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) debugOperands(operands, "calcNPV"); + Ptg[] params = PtgCalculator.getAllComponents(operands); + if (DEBUG) debugOperands(params, "calcNPV"); // AFTER converting references ... + double rate = params[0].getDoubleVal(); + int n = Math.min(params.length, 30); // at most 29 values + double result = 0; + for (int i = 1; i < n; i++) { + double valuei = params[i].getDoubleVal(); + // TODO: if valuei is an error, empty, etc., ignore + result += valuei / Math.pow(1 + rate, i); + } + if (DEBUG) Logger.logInfo("Result from calcNPV= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * ODDFPRICE Returns the price per $100 face value of a security with an odd + * first period + * ODDFPRICE(settlement,maturity,issue,first_coupon,rate,yld,redemption,frequency,basis) + */ + protected static Ptg calcODDFPRICE(Ptg[] operands) throws + FunctionNotSupportedException { + if (true) { + String wn = "WARNING: this version of OpenXLS does not support the formula ODDFPRICE."; + Logger.logWarn(wn); + throw new FunctionNotSupportedException(wn); + } + if (operands.length < 8) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcODDFPRICE"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); + GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); + double rate = operands[4].getDoubleVal(); + double yld = operands[5].getDoubleVal(); + double redemption = operands[6].getDoubleVal(); + int frequency = operands[7].getIntVal(); + int basis = 0; + if (operands.length > 8) + basis = operands[8].getIntVal(); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); + long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); + long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); + if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); + if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); + /*maturity > first_coupon > settlement > issue */ + if (issueDate >= settlementDate || settlementDate >= firstCouponDate || + firstCouponDate >= maturityDate) return new PtgErr(PtgErr.ERROR_NUM); + + + Ptg[] ops = new Ptg[4]; + ops[0] = new PtgNumber(settlementDate); + ops[1] = new PtgNumber(maturityDate); + ops[2] = new PtgInt(frequency); + ops[3] = new PtgInt(basis); + double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement + double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon + double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period + double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity + double NCD = calcCoupNCD(ops).getDoubleVal(); // next coupon after 1st coupon date????? + double DFC = firstCouponDate - settlementDate; // # days from odd first coupon to next coupon date + double z = getDaysFromBasis(basis, settlementDate, firstCouponDate); + + double R = 100 * rate / frequency; + double Y = 1 + yld / frequency; + + double result = 0.0; + if (DFC < E) { // odd short first coupon + double firstTerm = redemption / Math.pow(Y, N - 1 + DSC / E); + double secondTerm = (R * DFC / E) / Math.pow(Y, DSC / E); + double thirdTerm = 0.0; + for (int i = 2; i <= N; i++) { + thirdTerm += R / Math.pow(Y, i - 1 + DSC / E); + } + double fourthTerm = R * A / E; + result = firstTerm + secondTerm + thirdTerm - fourthTerm; + } else { // odd long first coupon + + } + + if (DEBUG) Logger.logInfo("Result from calcODDFPRICE= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /* + * ODDFYIELD Returns the yield of a security with an odd first period + * NOT COMPLETED!! + */ + protected static Ptg calcODDFYIELD(Ptg[] operands) throws + FunctionNotSupportedException { + if (true) { + String wn = "WARNING: this version of OpenXLS does not support the formula ODDFYIELD."; + Logger.logWarn(wn); + throw new FunctionNotSupportedException(wn); + } + if (operands.length < 8) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcODDFYIELD"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); + GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); + double rate = operands[4].getDoubleVal(); + double yld = operands[5].getDoubleVal(); + double redemption = operands[6].getDoubleVal(); + int frequency = operands[7].getIntVal(); + int basis = 0; + if (operands.length > 8) + basis = operands[8].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); + if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); + if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); + long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); + long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); + + /* + * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC + */ + Ptg[] ops = new Ptg[4]; + ops[0] = new PtgNumber(settlementDate); + ops[1] = new PtgNumber(maturityDate); + ops[2] = new PtgInt(frequency); + ops[3] = new PtgInt(basis); + double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement + double DFC = getDaysFromBasis(basis, firstCouponDate, (long) A); // days from 1st odd coupon to 1st coupon + double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon + double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period + double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity + double R = 100 * rate / frequency; + double Y = 1 + yld / frequency; + + double result = 0.0; + + if (DEBUG) Logger.logInfo("Result from calcODDFYIELD= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /* + * ODDLPRICE Returns the price per $100 face value of a security with an odd + * last period + * NOT COMPLETED!!!! + */ + protected static Ptg calcODDLPRICE(Ptg[] operands) throws + FunctionNotSupportedException { + if (true) { + String wn = "WARNING: this version of OpenXLS does not support the formula ODDLPRICE."; + Logger.logWarn(wn); + throw new FunctionNotSupportedException(wn); + } + if (operands.length < 8) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "calcODDLPRICE"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); + GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); + double rate = operands[4].getDoubleVal(); + double yld = operands[5].getDoubleVal(); + double redemption = operands[6].getDoubleVal(); + int frequency = operands[7].getIntVal(); + int basis = 0; + if (operands.length > 8) + basis = operands[8].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); + if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); + if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); + long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); + long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); + + /* + * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC + */ + Ptg[] ops = new Ptg[4]; + ops[0] = new PtgNumber(settlementDate); + ops[1] = new PtgNumber(maturityDate); + ops[2] = new PtgInt(frequency); + ops[3] = new PtgInt(basis); + double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement + double DFC = getDaysFromBasis(basis, firstCouponDate, (long) A); // days from 1st odd coupon to 1st coupon + double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon + double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period + double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity + double R = 100 * rate / frequency; + double Y = 1 + yld / frequency; + + double result = 0.0; + + if (DEBUG) Logger.logInfo("Result from calcODDLPRICE= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /* + * ODDLYIELD Returns the yield of a security with an odd last period + * NOT COMPLETED!! + */ + protected static Ptg calcODDLYIELD(Ptg[] operands) throws + FunctionNotSupportedException { + if (true) { + String wn = "WARNING: this version of OpenXLS does not support the formula ODDLYIELD."; + Logger.logWarn(wn); + throw new FunctionNotSupportedException(wn); + } + if (operands.length < 8) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "calcODDLYIELD"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); + GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); + double rate = operands[4].getDoubleVal(); + double yld = operands[5].getDoubleVal(); + double redemption = operands[6].getDoubleVal(); + int frequency = operands[7].getIntVal(); + int basis = 0; + if (operands.length > 8) + basis = operands[8].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); + if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); + if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); + long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); + long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); + + /* + * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC + */ + Ptg[] ops = new Ptg[4]; + ops[0] = new PtgNumber(settlementDate); + ops[1] = new PtgNumber(maturityDate); + ops[2] = new PtgInt(frequency); + ops[3] = new PtgInt(basis); + double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement + double DFC = getDaysFromBasis(basis, firstCouponDate, (long) A); // days from 1st odd coupon to 1st coupon + double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon + double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period + double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity + double R = 100 * rate / frequency; + double Y = 1 + yld / frequency; + + double result = 0.0; + + if (DEBUG) Logger.logInfo("Result from calcODDLYIELD= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * PMT Returns the periodic payment for an annuity + *

                + * pmt= - (fv + (1+rate)**nper * pv) * rate / ( ( (1+rate)**nper - 1 ) * + * (1+rate*type) ) + *

                + * if fv or type are omitted they should be treated as 0 values. + */ + protected static Ptg calcPmt(Ptg[] operands) { + if (operands.length < 3) { // not + // supported + // by + // function + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + double rate, nper, pv, fv, type; + rate = new Double(String.valueOf(operands[0].getValue())).doubleValue(); + nper = new Double(String.valueOf(operands[1].getValue())).doubleValue(); + pv = new Double(String.valueOf(operands[2].getValue())).doubleValue(); + if (operands.length > 3) { + if (operands[3] instanceof PtgMissArg) { + fv = 0; + } else { + fv = new Double(String.valueOf(operands[3].getValue())) + .doubleValue(); + } + } else { + fv = 0; + } + if (operands.length > 4) { + if (operands[4] instanceof PtgMissArg) { + type = 0; + } else { + type = new Double(String.valueOf(operands[4].getValue())) + .doubleValue(); + } + } else { + type = 0; + } + + //KSC: For some strange odd weird reason, original calculation was off + // even though this should be exactly the same thing. Go figure! + double Rn = Math.pow(1 + rate, nper); + double A = -fv * rate - pv * Rn * rate; + double B = (Rn - 1) * (1 + rate * type); + double result = A / B; + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * PPMT Returns the payment on the principal for an investment for a given + * period PPMT(rate,per,nper,pv,fv,type) Rate is the interest rate per + * period. Per specifies the period and must be in the range 1 to nper. Nper + * is the total number of payment periods in an annuity. Pv is the present + * value the total amount that a series of future payments is worth now. + * Fv is the future value, or a cash balance you want to attain after the + * last payment is made. If fv is omitted, it is assumed to be 0 (zero), + * that is, the future value of a loan is 0. Type is the number 0 or 1 and + * indicates when payments are due + */ + protected static Ptg calcPPMT(Ptg[] operands) { + if (operands.length < 4) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcPPMT"); + double rate = operands[0].getDoubleVal(); + int per = operands[1].getIntVal(); + int nper = operands[2].getIntVal(); + double pv = operands[3].getDoubleVal(); + double fv = 0; + int type = 0; + if (operands.length > 4 && !(operands[4] instanceof PtgMissArg)) + fv = operands[4].getDoubleVal(); + if (operands.length > 5) + type = operands[5].getIntVal(); + + double result; + // 1st, get payment for entire period + double Rn = Math.pow(1 + rate, nper); + double A = -fv * rate - pv * Rn * rate; + double B = (Rn - 1) * (1 + rate * type); + double pmt = A / B; + + double n; + if (type == 0) + n = per; + else + n = per - 1; + // FVa + A = Math.pow(1 + rate, n); + B = pmt * (1 + rate * type); + B *= (Math.pow(1 + rate, n) - 1) / rate; + double fva = -(pv * A + B); + // FVb + A = Math.pow(1 + rate, n - 1); + B = pmt * (1 + rate * type); + B *= (Math.pow(1 + rate, n - 1) - 1) / rate; + double fvb = -(pv * A + B); + + result = fvb - fva; + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcPPMT= " + result); + return pnum; + + } + + /** + * PRICE Returns the price per $100 face value of a security that pays + * periodic interest + * PRICE(settlement,maturity,rate,yld,redemption,frequency,basis) Settlement + * is the security's settlement date. The security settlement date is the + * date after the issue date when the security is traded to the buyer. + * Maturity is the security's maturity date. The maturity date is the date + * when the security expires. Rate is the security's annual coupon rate. Yld + * is the security's annual yield. Redemption is the security's redemption + * value per $100 face value. Frequency is the number of coupon payments per + * year. For annual payments, frequency = 1; for semiannual, frequency = 2; + * for quarterly, frequency = 4. Basis is the type of day count basis to + * use. + */ + protected static Ptg calcPRICE(Ptg[] operands) { + if (operands.length < 6) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcPRICE"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + double rate = operands[2].getDoubleVal(); + double yld = operands[3].getDoubleVal(); + double redemption = operands[4].getDoubleVal(); + int frequency = operands[5].getIntVal(); + int basis = 0; + if (operands.length > 6) + basis = operands[6].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) + return new PtgErr(PtgErr.ERROR_NUM); + if (yld < 0 || rate < 0) + return new PtgErr(PtgErr.ERROR_NUM); + if (redemption <= 0) + return new PtgErr(PtgErr.ERROR_NUM); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); + + /* + * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC + */ + Ptg[] ops = new Ptg[4]; + ops[0] = operands[0]; + ops[1] = operands[1]; + ops[2] = operands[5]; + ops[3] = new PtgInt(basis); + double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon + double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period in which settlementfalls + double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity + double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement + double R = rate / frequency; + double Y = 1 + yld / frequency; + double result = redemption / Math.pow(Y, N - 1 + DSC / E); + for (int i = 1; i <= N; i++) { + result += (R * 100) / Math.pow(Y, i - 1 + DSC / E); + } + result -= (100 * R * A / E); + + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcPRICE= " + result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * PRICEDISC Returns the price per $100 face value of a discounted security + *

                + * PRICEDISC(settlement,maturity,discount,redemption,frequency,basis) + * Settlement is the security's settlement date. Maturity is the security's + * maturity date. Discount is the security's discount rate. Redemption is + * the security's redemption value per $100 face value. Basis is the type of + * day count basis to use. + */ + protected static Ptg calcPRICEDISC(Ptg[] operands) { + if (operands.length < 4) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcPRICEDISC"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + double discount = operands[2].getDoubleVal(); + double redemption = operands[3].getDoubleVal(); + int basis = 0; + if (operands.length > 4) + basis = operands[4].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) + return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) + return new PtgErr(PtgErr.ERROR_NUM); + if (redemption <= 0) + return new PtgErr(PtgErr.ERROR_NUM); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); + + /* + * Ptg[] ops; if (operands.length > 6) ops = new Ptg[4]; else ops = new + * Ptg[3]; ops[0] = operands[0]; ops[1] = operands[1]; ops[2] = + * operands[5]; if (operands.length > 6) ops[3] = operands[6]; + */ + double result = redemption + - (discount * redemption * yearFrac(basis, settlementDate, + maturityDate)); + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcPRICEDISC= " + result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * PRICEMAT Returns the price per $100 face value of a security that pays + * interest at maturity + */ + protected static Ptg calcPRICEMAT(Ptg[] operands) { + if (operands.length < 4) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcPRICEMAT"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + GregorianCalendar iDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[2].getValue()); + double rate = operands[3].getDoubleVal(); + double yld = operands[4].getDoubleVal(); + int basis = 0; + if (operands.length > 5) + basis = operands[5].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); + if (rate < 0 || yld < 0) return new PtgErr(PtgErr.ERROR_NUM); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); + long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); + + double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); + double DSM = getDaysFromBasis(basis, settlementDate, maturityDate); + double DIM = getDaysFromBasis(basis, issueDate, maturityDate); + double A = getDaysFromBasis(basis, issueDate, settlementDate); + + double result = (100 + (DIM / B) * rate * 100) / (1 + ((DSM / B) * yld)); + result -= (A / B) * rate * 100; + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcPRICEMAT= " + result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * PV(rate,nper,pmt,fv,type) Returns the present value of an investment Rate + * is the interest rate per period. For example, if you obtain an automobile + * loan at a 10 percent annual interest rate and make monthly payments, your + * interest rate per month is 10%/12, or 0.83%. You would enter 10%/12, or + * 0.83%, or 0.0083, into the formula as the rate. Nper is the total number + * of payment periods in an annuity. For example, if you get a four-year car + * loan and make monthly payments, your loan has 4*12 (or 48) periods. You + * would enter 48 into the formula for nper. Pmt is the payment made each + * period and cannot change over the life of the annuity. Typically, pmt + * includes principal and interest but no other fees or taxes. For example, + * the monthly payments on a $10,000, four-year car loan at 12 percent are + * $263.33. You would enter -263.33 into the formula as the pmt. If pmt is + * omitted, you must include the fv argument. Fv is the future value, or a + * cash balance you want to attain after the last payment is made. If fv is + * omitted, it is assumed to be 0 (the future value of a loan, for example, + * is 0). For example, if you want to save $50,000 to pay for a special + * project in 18 years, then $50,000 is the future value. You could then + * make a conservative guess at an interest rate and determine how much you + * must save each month. If fv is omitted, you must include the pmt + * argument. Type is the number 0 or 1 and indicates when payments are due. + * 0 or omitted At the end of the period 1 At the beginning of the period + */ + protected static Ptg calcPV(Ptg[] operands) { + if (operands.length < 3) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcPV"); + double rate = operands[0].getDoubleVal(); + double nper = operands[1].getDoubleVal(); + double pmt = 0; + // TODO: No specified error trapping? + if (!(operands[2] instanceof PtgMissArg)) + pmt = operands[2].getDoubleVal(); + double fv = 0.0; + int type = 0; + if (operands.length > 3 && !(operands[3] instanceof PtgMissArg)) + fv = operands[3].getDoubleVal(); + if (operands.length > 4) + type = operands[4].getIntVal(); + + double A = Math.pow(1 + rate, nper); + double B = pmt * (1 + rate * type); + B *= (A - 1) / rate; + double result = (-fv - B) / A; + + //double testresults= (result * A + pmt*(1+rate*type) * (A-1)/rate + fv); + //testresults must==0 for Pv to be correct + + + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) + Logger.logInfo("Result from calcPV= " + result); + return pnum; + + } + + /** + * RATE Returns the interest rate per period of an annuity. RATE is + * calculated by iteration and can have zero or more solutions. If the + * successive results of RATE do not converge to within 0.0000001 after 20 + * iterations, RATE returns the #NUM! error value. + *

                + * RATE(nper,pmt,pv,fv,type,guess) + *

                + * Nper is the total number of payment periods in an annuity. Pmt is the + * payment made each period and cannot change over the life of the annuity. + * Typically, pmt includes principal and interest but no other fees or + * taxes. If pmt is omitted, you must include the fv argument. Pv is the + * present value the total amount that a series of future payments is + * worth now. Fv is the future value, or a cash balance you want to attain + * after the last payment is made. If fv is omitted, it is assumed to be 0 + * (the future value of a loan, for example, is 0). Type is the number 0 or + * 1 and indicates when payments are due. Guess is your guess for what the + * rate will be. + */ + protected static Ptg calcRate(Ptg[] operands) { + if (operands.length < 3) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcRate"); + double nper = operands[0].getDoubleVal(); + double pmt = operands[1].getDoubleVal(); + double pv = 0.0, fv = 0.0; + int type = 0; + double guess = 0.1; + if (!(operands[2] instanceof PtgMissArg)) + pv = operands[2].getDoubleVal(); + if (operands.length > 3) { + if (!(operands[3] instanceof PtgMissArg)) + fv = operands[3].getDoubleVal(); + } + if (operands.length > 4) { + if (!(operands[4] instanceof PtgMissArg)) + type = operands[4].getIntVal(); + } + if (operands.length > 5) + guess = operands[5].getDoubleVal(); + // validate params + if (type != 0 && type != 1) + return new PtgErr(PtgErr.ERROR_NUM); + if (pv == 0 && fv == 0) + return new PtgErr(PtgErr.ERROR_NUM); + + // iterate over possible Rate values; value is correct when + // f <= tolerance, defined as .00001% + final double TOLERANCE = 0.000000001; + boolean bIsCorrect = false; + double x0 = guess; + double x1 = x0; + double fx0; + double fprimex0; + // iterate, using Newton's approximation + for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 20 tries + // Calculate f(x) = (a+ f*g*h + c) + double R = Math.pow(1 + x0, nper); + double U = 1 / x0; + double a = pv * R; + double f = pmt * (1 + x0 * type); + double g = R - 1; + double h = U; + fx0 = a + f * g * U + fv; + // + // (a + f*g*h + c)' + // = a' + f'gh + fg'h + fgh' + double T = Math.pow(1 + x0, nper - 1); + double aprime = pv * nper * T; + double fprime = pmt * type; + double gprime = nper * T; + double hprime = -1 * Math.pow(x0, -2); + + fprimex0 = aprime + fprime * g * h + f * gprime * h + f * g * hprime; + // calculate x1, the next iteration + x1 = x0 - fx0 / fprimex0; + double delta = x1 - x0; + bIsCorrect = (Math.abs(delta) <= TOLERANCE); + x0 = x1; + } - double result; - // 1st, get payment for entire period - double Rn = Math.pow(1 + rate, nper); - double A = -fv * rate - pv * Rn * rate; - double B = (Rn - 1) * (1 + rate * type); - double pmt = A / B; - - double n; - if (type == 0) - n = per; - else - n = per - 1; - // FVa - A = Math.pow(1 + rate, n); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n) - 1) / rate; - double fva = -(pv * A + B); - // FVb - A = Math.pow(1 + rate, n - 1); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n - 1) - 1) / rate; - double fvb = -(pv * A + B); - - result = fvb - fva; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPPMT= " + result); - return pnum; - - } - - /** - * PRICE Returns the price per $100 face value of a security that pays - * periodic interest - * PRICE(settlement,maturity,rate,yld,redemption,frequency,basis) Settlement - * is the security's settlement date. The security settlement date is the - * date after the issue date when the security is traded to the buyer. - * Maturity is the security's maturity date. The maturity date is the date - * when the security expires. Rate is the security's annual coupon rate. Yld - * is the security's annual yield. Redemption is the security's redemption - * value per $100 face value. Frequency is the number of coupon payments per - * year. For annual payments, frequency = 1; for semiannual, frequency = 2; - * for quarterly, frequency = 4. Basis is the type of day count basis to - * use. - * - */ - protected static Ptg calcPRICE(Ptg[] operands) { - if (operands.length < 6) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPRICE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - double rate = operands[2].getDoubleVal(); - double yld = operands[3].getDoubleVal(); - double redemption = operands[4].getDoubleVal(); - int frequency = operands[5].getIntVal(); - int basis = 0; - if (operands.length > 6) - basis = operands[6].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - - /* - * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC - */ - Ptg[] ops = new Ptg[4]; - ops[0] = operands[0]; - ops[1] = operands[1]; - ops[2] = operands[5]; - ops[3] = new PtgInt(basis); - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period in which settlementfalls - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double R = rate / frequency; - double Y = 1 + yld / frequency; - double result = redemption / Math.pow(Y, N - 1 + DSC / E); - for (int i = 1; i <= N; i++) { - result += (R * 100) / Math.pow(Y, i - 1 + DSC / E); - } - result -= (100 * R * A / E); - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPRICE= " + result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * PRICEDISC Returns the price per $100 face value of a discounted security - * - * PRICEDISC(settlement,maturity,discount,redemption,frequency,basis) - * Settlement is the security's settlement date. Maturity is the security's - * maturity date. Discount is the security's discount rate. Redemption is - * the security's redemption value per $100 face value. Basis is the type of - * day count basis to use. - * - */ - protected static Ptg calcPRICEDISC(Ptg[] operands) { - if (operands.length < 4) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPRICEDISC"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double discount = operands[2].getDoubleVal(); - double redemption = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - - /* - * Ptg[] ops; if (operands.length > 6) ops = new Ptg[4]; else ops = new - * Ptg[3]; ops[0] = operands[0]; ops[1] = operands[1]; ops[2] = - * operands[5]; if (operands.length > 6) ops[3] = operands[6]; - */double result = redemption - - (discount * redemption * yearFrac(basis, settlementDate, - maturityDate)); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPRICEDISC= " + result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * PRICEMAT Returns the price per $100 face value of a security that pays - * interest at maturity - */ - protected static Ptg calcPRICEMAT(Ptg[] operands) { - if (operands.length < 4) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPRICEMAT"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[2].getValue()); - double rate = operands[3].getDoubleVal(); - double yld = operands[4].getDoubleVal(); - int basis = 0; - if (operands.length > 5) - basis = operands[5].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (rate < 0 || yld < 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - - double B= getDaysInYearFromBasis(basis,settlementDate, maturityDate); - double DSM= getDaysFromBasis(basis, settlementDate, maturityDate); - double DIM= getDaysFromBasis(basis, issueDate, maturityDate); - double A= getDaysFromBasis(basis, issueDate, settlementDate); - - double result = (100 + (DIM/B) * rate * 100) / (1 + ((DSM/B) * yld)); - result -= (A/B) * rate * 100; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPRICEMAT= " + result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * PV(rate,nper,pmt,fv,type) Returns the present value of an investment Rate - * is the interest rate per period. For example, if you obtain an automobile - * loan at a 10 percent annual interest rate and make monthly payments, your - * interest rate per month is 10%/12, or 0.83%. You would enter 10%/12, or - * 0.83%, or 0.0083, into the formula as the rate. Nper is the total number - * of payment periods in an annuity. For example, if you get a four-year car - * loan and make monthly payments, your loan has 4*12 (or 48) periods. You - * would enter 48 into the formula for nper. Pmt is the payment made each - * period and cannot change over the life of the annuity. Typically, pmt - * includes principal and interest but no other fees or taxes. For example, - * the monthly payments on a $10,000, four-year car loan at 12 percent are - * $263.33. You would enter -263.33 into the formula as the pmt. If pmt is - * omitted, you must include the fv argument. Fv is the future value, or a - * cash balance you want to attain after the last payment is made. If fv is - * omitted, it is assumed to be 0 (the future value of a loan, for example, - * is 0). For example, if you want to save $50,000 to pay for a special - * project in 18 years, then $50,000 is the future value. You could then - * make a conservative guess at an interest rate and determine how much you - * must save each month. If fv is omitted, you must include the pmt - * argument. Type is the number 0 or 1 and indicates when payments are due. - * 0 or omitted At the end of the period 1 At the beginning of the period - * - * - */ - protected static Ptg calcPV(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPV"); - double rate = operands[0].getDoubleVal(); - double nper = operands[1].getDoubleVal(); - double pmt = 0; - // TODO: No specified error trapping? - if (!(operands[2] instanceof PtgMissArg)) - pmt = operands[2].getDoubleVal(); - double fv = 0.0; - int type = 0; - if (operands.length > 3 && !(operands[3] instanceof PtgMissArg)) - fv = operands[3].getDoubleVal(); - if (operands.length > 4) - type = operands[4].getIntVal(); - - double A = Math.pow(1 + rate, nper); - double B = pmt * (1 + rate * type); - B *= (A - 1) / rate; - double result = (-fv - B) / A; - - //double testresults= (result * A + pmt*(1+rate*type) * (A-1)/rate + fv); - //testresults must==0 for Pv to be correct - - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPV= " + result); - return pnum; - - } - - /** - * RATE Returns the interest rate per period of an annuity. RATE is - * calculated by iteration and can have zero or more solutions. If the - * successive results of RATE do not converge to within 0.0000001 after 20 - * iterations, RATE returns the #NUM! error value. - * - * RATE(nper,pmt,pv,fv,type,guess) - * - * Nper is the total number of payment periods in an annuity. Pmt is the - * payment made each period and cannot change over the life of the annuity. - * Typically, pmt includes principal and interest but no other fees or - * taxes. If pmt is omitted, you must include the fv argument. Pv is the - * present value the total amount that a series of future payments is - * worth now. Fv is the future value, or a cash balance you want to attain - * after the last payment is made. If fv is omitted, it is assumed to be 0 - * (the future value of a loan, for example, is 0). Type is the number 0 or - * 1 and indicates when payments are due. Guess is your guess for what the - * rate will be. - */ - protected static Ptg calcRate(Ptg[] operands) { - if (operands.length < 3) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcRate"); - double nper = operands[0].getDoubleVal(); - double pmt = operands[1].getDoubleVal(); - double pv = 0.0, fv = 0.0; - int type = 0; - double guess = 0.1; - if (!(operands[2] instanceof PtgMissArg)) - pv = operands[2].getDoubleVal(); - if (operands.length > 3) { - if (!(operands[3] instanceof PtgMissArg)) - fv = operands[3].getDoubleVal(); - } - if (operands.length > 4) { - if (!(operands[4] instanceof PtgMissArg)) - type = operands[4].getIntVal(); - } - if (operands.length > 5) - guess = operands[5].getDoubleVal(); - // validate params - if (type != 0 && type != 1) - return new PtgErr(PtgErr.ERROR_NUM); - if (pv == 0 && fv == 0) - return new PtgErr(PtgErr.ERROR_NUM); - - // iterate over possible Rate values; value is correct when - // f <= tolerance, defined as .00001% - final double TOLERANCE = 0.000000001; - boolean bIsCorrect = false; - double x0= guess; - double x1= x0; - double fx0; - double fprimex0; - // iterate, using Newton's approximation - for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 20 tries - // Calculate f(x) = (a+ f*g*h + c) - double R= Math.pow(1 + x0, nper); - double U= 1/x0; - double a= pv*R; - double f= pmt*(1+x0*type); - double g= R-1; - double h= U; - fx0= a + f*g*U +fv; - // - // (a + f*g*h + c)' - // = a' + f'gh + fg'h + fgh' - double T= Math.pow(1 + x0, nper-1); - double aprime= pv * nper * T; - double fprime= pmt*type; - double gprime= nper * T; - double hprime= -1*Math.pow(x0, -2); - - fprimex0= aprime + fprime*g*h + f*gprime*h + f*g*hprime; - // calculate x1, the next iteration - x1= x0 - fx0/fprimex0; - double delta= x1-x0; - bIsCorrect = (Math.abs(delta) <= TOLERANCE); - x0= x1; - } - /* THIS CALCULATION IS OFF!!! // x= rate // f(x)= PV + PMT*((1-(1+x)^-NPER)/x)*(1+x)^TYPE + FV*(1+x)^-NPER = 0 @@ -2693,587 +2693,589 @@ protected static Ptg calcRate(Ptg[] operands) { trial -= delta; bIsCorrect = (Math.abs(delta) <= TOLERANCE); } -/**/ - if (!bIsCorrect) - return new PtgErr(PtgErr.ERROR_NUM); - - if (DEBUG) - Logger.logInfo("Result from calcRate= " + x1); - PtgNumber pnum = new PtgNumber(x1); - return pnum; - } - - /** - * RECEIVED Returns the amount received at maturity for a fully invested - * security - */ - protected static Ptg calcReceived(Ptg[] operands) { - if (operands.length < 4) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcRECEIVED"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double investment = operands[2].getDoubleVal(); - double rate = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); +/**/ + if (!bIsCorrect) + return new PtgErr(PtgErr.ERROR_NUM); + + if (DEBUG) + Logger.logInfo("Result from calcRate= " + x1); + PtgNumber pnum = new PtgNumber(x1); + return pnum; + } + + /** + * RECEIVED Returns the amount received at maturity for a fully invested + * security + */ + protected static Ptg calcReceived(Ptg[] operands) { + if (operands.length < 4) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcRECEIVED"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + double investment = operands[2].getDoubleVal(); + double rate = operands[3].getDoubleVal(); + int basis = 0; + if (operands.length > 4) + basis = operands[4].getIntVal(); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); // double DSM = maturityDate - settlementDate; // double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); // double result = investment / (1 - (rate * DSM / B)); - double result = investment/(1 - (rate *yearFrac(basis, settlementDate, maturityDate))); - if (DEBUG) - Logger.logInfo("Result from calcRECEIVED= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * SLN Returns the straight-line depreciation of an asset for one period - * SLN(cost,salvage,life) Cost is the initial cost of the asset. Salvage is - * the value at the end of the depreciation (sometimes called the salvage - * value of the asset). Life is the number of periods over which the asset - * is depreciated (sometimes called the useful life of the asset). - */ - protected static Ptg calcSLN(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - double cost = operands[0].getDoubleVal(); - double salvage = operands[1].getDoubleVal(); - double life = operands[2].getDoubleVal(); - if (DEBUG) - debugOperands(operands, "calcSLN"); - if (life == 0) - return new PtgErr(PtgErr.ERROR_NUM); - double result = (cost - salvage) / life; - if (DEBUG) - Logger.logInfo("Result from calcSLN= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * SYD Returns the sum-of-years' digits depreciation of an asset for a - * specified period SYD(cost,salvage,life,per) Cost is the initial cost of - * the asset. Salvage is the value at the end of the depreciation (sometimes - * called the salvage value of the asset). Life is the number of periods - * over which the asset is depreciated (sometimes called the useful life of - * the asset). Per is the period and must use the same units as life. - */ - protected static Ptg calcSYD(Ptg[] operands) { - if (operands.length < 4) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcSYD"); - double cost = operands[0].getDoubleVal(); - double salvage = operands[1].getDoubleVal(); - double life = operands[2].getDoubleVal(); - double per = operands[3].getDoubleVal(); - if (life == 0) - return new PtgErr(PtgErr.ERROR_NUM); - double A = (cost - salvage) * (life - per + 1) * 2; - double B = life * (life + 1); - double result = A / B; - if (DEBUG) - Logger.logInfo("Result from calcSYD= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * TBILLEQ Returns the bond-equivalent yield for a Treasury bill Settlement - * is the Treasury bill's settlement date. The security settlement date is - * the date after the issue date when the Treasury bill is traded to the - * buyer. Maturity is the Treasury bill's maturity date. The maturity date - * is the date when the Treasury bill expires. Discount is the Treasury - * bill's discount rate. - */ - protected static Ptg calcTBillEq(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcTBILLEQ"); - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double rate = operands[2].getDoubleVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - if (settlementDate >= maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - if (maturityDate - settlementDate > 365) - return new PtgErr(PtgErr.ERROR_NUM); - if (rate <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - double DSM = maturityDate - settlementDate; - double result; - if (DSM <= 182) { - result = (365 * rate) / (360 - rate * DSM); - } else { - double A= DSM/365; - double B= rate*DSM; - - double C = (2*A - 1)*B/(B - 360); - double D= Math.pow(A, 2)-C; - result = (-2*A + 2*Math.sqrt(D))/(2*A - 1); - } - if (DEBUG) - Logger.logInfo("Result from calcTBILLEQ= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * TBILLPRICE Returns the price per $100 face value for a Treasury bill - */ - protected static Ptg calcTBillPrice(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcTBILLPRICE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double rate = operands[2].getDoubleVal(); - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - - if (settlementDate >= maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - if (maturityDate - settlementDate > 365) - return new PtgErr(PtgErr.ERROR_NUM); - if (rate <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - double DSM = maturityDate - settlementDate; - double result = 100 * (1 - (rate * DSM / 360)); - if (DEBUG) - Logger.logInfo("Result from calcTBILLPRICE= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * TBILLYIELD Returns the yield for a Treasury bill - * - * VDB Returns the depreciation of an asset for a specified or partial - * period using a declining balance method - */ - protected static Ptg calcTBillYield(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcTBILLYIELD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double price = operands[2].getDoubleVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - if (settlementDate >= maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - if (maturityDate - settlementDate > 365) - return new PtgErr(PtgErr.ERROR_NUM); - if (price <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - double DSM = maturityDate - settlementDate; - double result = ((100 - price) / price) * (360 / DSM); - if (DEBUG) - Logger.logInfo("Result from calcTBILLYIELD= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - /** - * VDB - * Returns the depreciation of an asset for any period you specify, - * including partial periods, using the double-declining balance method or - * some other method you specify. VDB stands for variable declining balance. - * - * VDB(cost,salvage,life,start_period,end_period,factor,no_switch) - */ - protected static Ptg calcVDB(Ptg[] operands) { - if (operands.length < 5) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "calcVDB"); - double cost= operands[0].getDoubleVal(); - double salvage= operands[1].getDoubleVal(); - int life= operands[2].getIntVal(); - int start_period= operands[3].getIntVal(); - int end_period= operands[4].getIntVal(); - int factor= 2; - if (operands.length > 5 && !(operands[5] instanceof PtgMissArg)) - factor= operands[5].getIntVal(); - boolean bNoSwitch= false; - if (operands.length > 6) - bNoSwitch= PtgCalculator.getBooleanValue(operands[6]); - - if (cost <= 0 || salvage <= 0 || life <= 0 || - start_period < 0 || end_period < 0 || - factor < 0 || end_period > life) - return new PtgErr(PtgErr.ERROR_NUM); - - double result= 0.0; - - Ptg[] ops= new Ptg[5]; - ops[0]= new PtgNumber(cost); - ops[1]= new PtgNumber(salvage); - ops[2]= new PtgInt(life); - ops[4]= new PtgInt(factor); - if (bNoSwitch) { // just sum ddb - for (int i= start_period+1; i<=end_period; i++) { - ops[3]= new PtgInt(i); - result+= calcDDB(ops).getDoubleVal(); - } - } - else { // switch to straight-line depreciation when dep. > ddb calc - boolean bSwitch= false; - double A= 0.0; - int i= start_period + 1; - while (i <= end_period && !bSwitch) { - double sl= (cost-A-salvage)/(life-i+1); - ops[3]= new PtgInt(i); - double ddb= calcDDB(ops).getDoubleVal(); - if (sl <= ddb) { - A+= ddb; - i++; - } - else // straight-line depreciation is greater than ddb; switch - bSwitch= true; - } - result= A; - // use straight-line depreciation for rest of period - for(int j= i; j <=end_period; j++) { - result+= (cost - A - salvage)/(life-i+1); - } - } - - if (DEBUG) Logger.logInfo("Result from calcVDB= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * XIRR Returns the internal rate of return for a schedule of cash flows - * that is not necessarily periodic - */ - protected static Ptg calcXIRR(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcXIRR"); - double guess = .1; - if (operands.length > 2) - guess = operands[2].getDoubleVal(); - // convert references to Ptg[]s - Ptg[] values = PtgCalculator.getAllComponents(operands[0]); - Ptg[] dates = PtgCalculator.getAllComponents(operands[1]); - if (DEBUG) - debugOperands(values, "calcXIRR"); - if (DEBUG) - debugOperands(dates, "calcXIRR"); - /* - * 'Newton-Raphson method: ' Given PV a function of X, determine the - * value of X ' such that SUM[PV] = 0, using iteration. ' dPVdX = - * derivative of PV with respect to X ' NB: if X = 1 + Rate, dX/dRate = - * 1 ' ' Calculate PV and dPVdX using an arbitrary initial estimate of - * X. ' Change X by -PV/dPVdX and calculate again. ' Repeat until X - * changes by less than some arbitrary small amount. - * - * Let X = 1 + Rate - * Let EXPi = -YearFrax(i) - * PVi = Val(i) * X^(EXPi) - * dPV/dX = Val(i) * EXPi * X^(EXPi- 1) - * = Val(i) * X^(EXPi) *EXPi / X - * = PVi * EXPi / X - * dPVSum/dX = SUM[dPV/dX] - * dPVSum/dX = 1/X * SUM[PVi * EXPi] - * - */ - if (values.length != dates.length) - return new PtgErr(PtgErr.ERROR_NUM); - // validate dates - double date0 = dates[0].getDoubleVal(); - for (int i = 1; i < dates.length; i++) { - // TODO: if not valid date, return PtgErr.ERROR_VALUE - long val = PtgCalculator.getLongValue(dates[i]); - if (val < date0) - return new PtgErr(PtgErr.ERROR_NUM); - } - // validate values: sum all outflows (= negative values) + inflows (= - // positive values) - double outflow = 0.0, inflow = 0.0; - // get outflow (- values) - for (int i = 0; i < values.length; i++) { - double val = values[i].getDoubleVal(); - if (val < 0) - outflow += Math.abs(val); - else - inflow += val; - } - if (outflow == 0.0 || inflow == 0.0) - return new PtgErr(PtgErr.ERROR_NUM); - - int n = values.length; - - // iterate over possible irr values; value is correct when - // f <= tolerance, defined as .000001% - boolean bIsCorrect = false; - final double TOLERANCE = 0.00000001; - double trial = guess; - double x0, f0, f; - x0 = 1; //1+0 rate, x_0, lower bounds of guess - f0 = inflow - outflow; // =sum of values when rate is 0 - trial = 1 + guess; // x_1, upper bounds - // for secant method - // double f1= 0.0; - // double x1; - // double dx= xh-xl; - double delta = 0; - double fprime = 0; // derivative of f(x0) - for (int i = 0; i < n; i++) { - double val = values[i].getDoubleVal(); - double exp = (PtgCalculator.getLongValue(dates[i]) - date0) / 365; - // f+= val*(Math.pow(xh, -exp)); - fprime += val * -exp; // f' of x0 - } - for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 100 tries - // SECANT METHOD: trial= x0+dx*f0/(f0-f1); - // NEWTON'S: - f = 0.0; - for (int i = 0; i < n; i++) { - double val = values[i].getDoubleVal(); - double exp = (dates[i].getDoubleVal() - date0) / 365.0; - f += val * (Math.pow(trial, -exp)); - fprime += val * (Math.pow(trial, -exp)) * -exp; // derivative of - // f(trial) - } - fprime = fprime / trial; // final f' expression - delta = f / fprime; - if (trial - delta <= 0) - delta = trial / 2; - trial -= delta; - /* - * secant method if (f < 0) { delta= xl-trial; xl= trial; fl= f; } - * else { delta= xh-trial; xh= trial; fh= f; } dx= xh-xl; - */ - bIsCorrect = (Math.abs(delta) <= TOLERANCE); - } - if (!bIsCorrect) - return new PtgErr(PtgErr.ERROR_NUM); - - trial -= 1; - if (DEBUG) - Logger.logInfo("Result from calcXIRR= " + trial); - PtgNumber pnum = new PtgNumber(trial); - return pnum; - } - - /** - * XNPV Returns the net present value for a schedule of cash flows that is - * not necessarily periodic - * - */ - protected static Ptg calcXNPV(Ptg[] operands) { - if (operands.length < 2 || operands[1].getComponents() == null) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcXNPV"); - double rate = operands[0].getDoubleVal(); - // convert references to Ptg[]s - Ptg[] values = PtgCalculator.getAllComponents(operands[1]); - Ptg[] dates = PtgCalculator.getAllComponents(operands[2]); - if (DEBUG) - debugOperands(values, "calcXNPV"); - if (DEBUG) - debugOperands(dates, "calcXNPV"); - - if (values.length != dates.length) - return new PtgErr(PtgErr.ERROR_NUM); - // TODO: validate dates - double date0 = dates[0].getDoubleVal(); - for (int i = 1; i < dates.length; i++) { - // TODO: if not valid date, return PtgErr.ERROR_VALUE - long val = PtgCalculator.getLongValue(dates[i]); - if (val < date0) - return new PtgErr(PtgErr.ERROR_NUM); - } - // validate values: sum all outflows (= negative values) + inflows (= - // positive values) - double outflow = 0.0, inflow = 0.0; - // get outflow (- values) - for (int i = 0; i < values.length; i++) { - double val = values[i].getDoubleVal(); - if (val < 0) - outflow += Math.abs(val); - else - inflow += val; - } - if (outflow == 0.0 || inflow == 0.0) - return new PtgErr(PtgErr.ERROR_NUM); - - int n = values.length; - double result = 0.0; - for (int i = 0; i < n; i++) { - double val = values[i].getDoubleVal(); - double exp = (dates[i].getDoubleVal() - date0) / 365.0; - result += val / (Math.pow(1 + rate, exp)); - } - if (DEBUG) - Logger.logInfo("Result from calcXNPV= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * YIELD Returns the yield on a security that pays periodic interest - * - * YIELD(settlement,maturity,rate,pr,redemption,frequency,basis) Settlement - * is the security's settlement date. The security settlement date is the - * date after the issue date when the security is traded to the buyer. - * Maturity is the security's maturity date. The maturity date is the date - * when the security expires. Rate is the security's annual coupon rate. Pr - * is the security's price per $100 face value. Redemption is the security's - * redemption value per $100 face value. Frequency is the number of coupon - * payments per year. For annual payments, frequency = 1; for semiannual, - * frequency = 2; for quarterly, frequency = 4. Basis (optional) is the type - * of day count basis to use. - */ - protected static Ptg calcYIELD(Ptg[] operands) { - if (operands.length < 6) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcINTRATE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - double rate = operands[2].getDoubleVal(); - double pr = operands[3].getDoubleVal(); - double redemption = operands[4].getDoubleVal(); - int frequency = operands[5].getIntVal(); - int basis = 0; - if (operands.length > 6) - basis = operands[6].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (pr <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - Ptg[] ops; - ops = new Ptg[4]; - ops[0] = operands[0]; - ops[1] = operands[1]; - ops[2] = operands[5]; - ops[3] = new PtgInt(basis); - double result = 0.0; - /* - * A= coupDayBS E= coupDays n= coupNum DSC= coupDaysNC DSR= getDaysFromBasis - */ - double n = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period in which settlementfalls - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - - if (n <= 1) { - double DSR = getDaysFromBasis(basis, settlementDate, maturityDate); - double R = rate / frequency; - double P = pr / 100; - result = ((redemption / 100) + R) - (P + ((A / E) * R)); - result /= P + ((A / E) * R); - result *= (frequency * E) / DSR; - } else { - // for n values > 1, must employ an iterated approach to find yield using formula for price - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - result= yieldIteration(DSC, E, n, A, rate, frequency, redemption, pr); - if (result==-1) {// didn't find it - return new PtgErr(PtgErr.ERROR_NUM); - } - } + double result = investment / (1 - (rate * yearFrac(basis, settlementDate, maturityDate))); + if (DEBUG) + Logger.logInfo("Result from calcRECEIVED= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * SLN Returns the straight-line depreciation of an asset for one period + * SLN(cost,salvage,life) Cost is the initial cost of the asset. Salvage is + * the value at the end of the depreciation (sometimes called the salvage + * value of the asset). Life is the number of periods over which the asset + * is depreciated (sometimes called the useful life of the asset). + */ + protected static Ptg calcSLN(Ptg[] operands) { + if (operands.length < 3) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + double cost = operands[0].getDoubleVal(); + double salvage = operands[1].getDoubleVal(); + double life = operands[2].getDoubleVal(); + if (DEBUG) + debugOperands(operands, "calcSLN"); + if (life == 0) + return new PtgErr(PtgErr.ERROR_NUM); + double result = (cost - salvage) / life; + if (DEBUG) + Logger.logInfo("Result from calcSLN= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * SYD Returns the sum-of-years' digits depreciation of an asset for a + * specified period SYD(cost,salvage,life,per) Cost is the initial cost of + * the asset. Salvage is the value at the end of the depreciation (sometimes + * called the salvage value of the asset). Life is the number of periods + * over which the asset is depreciated (sometimes called the useful life of + * the asset). Per is the period and must use the same units as life. + */ + protected static Ptg calcSYD(Ptg[] operands) { + if (operands.length < 4) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcSYD"); + double cost = operands[0].getDoubleVal(); + double salvage = operands[1].getDoubleVal(); + double life = operands[2].getDoubleVal(); + double per = operands[3].getDoubleVal(); + if (life == 0) + return new PtgErr(PtgErr.ERROR_NUM); + double A = (cost - salvage) * (life - per + 1) * 2; + double B = life * (life + 1); + double result = A / B; + if (DEBUG) + Logger.logInfo("Result from calcSYD= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * TBILLEQ Returns the bond-equivalent yield for a Treasury bill Settlement + * is the Treasury bill's settlement date. The security settlement date is + * the date after the issue date when the Treasury bill is traded to the + * buyer. Maturity is the Treasury bill's maturity date. The maturity date + * is the date when the Treasury bill expires. Discount is the Treasury + * bill's discount rate. + */ + protected static Ptg calcTBillEq(Ptg[] operands) { + if (operands.length < 3) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcTBILLEQ"); + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + double rate = operands[2].getDoubleVal(); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); + if (settlementDate >= maturityDate) + return new PtgErr(PtgErr.ERROR_NUM); + if (maturityDate - settlementDate > 365) + return new PtgErr(PtgErr.ERROR_NUM); + if (rate <= 0) + return new PtgErr(PtgErr.ERROR_NUM); + + double DSM = maturityDate - settlementDate; + double result; + if (DSM <= 182) { + result = (365 * rate) / (360 - rate * DSM); + } else { + double A = DSM / 365; + double B = rate * DSM; + + double C = (2 * A - 1) * B / (B - 360); + double D = Math.pow(A, 2) - C; + result = (-2 * A + 2 * Math.sqrt(D)) / (2 * A - 1); + } + if (DEBUG) + Logger.logInfo("Result from calcTBILLEQ= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * TBILLPRICE Returns the price per $100 face value for a Treasury bill + */ + protected static Ptg calcTBillPrice(Ptg[] operands) { + if (operands.length < 3) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcTBILLPRICE"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + double rate = operands[2].getDoubleVal(); + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); + + if (settlementDate >= maturityDate) + return new PtgErr(PtgErr.ERROR_NUM); + if (maturityDate - settlementDate > 365) + return new PtgErr(PtgErr.ERROR_NUM); + if (rate <= 0) + return new PtgErr(PtgErr.ERROR_NUM); + + double DSM = maturityDate - settlementDate; + double result = 100 * (1 - (rate * DSM / 360)); + if (DEBUG) + Logger.logInfo("Result from calcTBILLPRICE= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * TBILLYIELD Returns the yield for a Treasury bill + *

                + * VDB Returns the depreciation of an asset for a specified or partial + * period using a declining balance method + */ + protected static Ptg calcTBillYield(Ptg[] operands) { + if (operands.length < 3) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcTBILLYIELD"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + double price = operands[2].getDoubleVal(); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); + if (settlementDate >= maturityDate) + return new PtgErr(PtgErr.ERROR_NUM); + if (maturityDate - settlementDate > 365) + return new PtgErr(PtgErr.ERROR_NUM); + if (price <= 0) + return new PtgErr(PtgErr.ERROR_NUM); + + double DSM = maturityDate - settlementDate; + double result = ((100 - price) / price) * (360 / DSM); + if (DEBUG) + Logger.logInfo("Result from calcTBILLYIELD= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * VDB + * Returns the depreciation of an asset for any period you specify, + * including partial periods, using the double-declining balance method or + * some other method you specify. VDB stands for variable declining balance. + *

                + * VDB(cost,salvage,life,start_period,end_period,factor,no_switch) + */ + protected static Ptg calcVDB(Ptg[] operands) { + if (operands.length < 5) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) debugOperands(operands, "calcVDB"); + double cost = operands[0].getDoubleVal(); + double salvage = operands[1].getDoubleVal(); + int life = operands[2].getIntVal(); + int start_period = operands[3].getIntVal(); + int end_period = operands[4].getIntVal(); + int factor = 2; + if (operands.length > 5 && !(operands[5] instanceof PtgMissArg)) + factor = operands[5].getIntVal(); + boolean bNoSwitch = false; + if (operands.length > 6) + bNoSwitch = PtgCalculator.getBooleanValue(operands[6]); + + if (cost <= 0 || salvage <= 0 || life <= 0 || + start_period < 0 || end_period < 0 || + factor < 0 || end_period > life) + return new PtgErr(PtgErr.ERROR_NUM); + + double result = 0.0; + + Ptg[] ops = new Ptg[5]; + ops[0] = new PtgNumber(cost); + ops[1] = new PtgNumber(salvage); + ops[2] = new PtgInt(life); + ops[4] = new PtgInt(factor); + if (bNoSwitch) { // just sum ddb + for (int i = start_period + 1; i <= end_period; i++) { + ops[3] = new PtgInt(i); + result += calcDDB(ops).getDoubleVal(); + } + } else { // switch to straight-line depreciation when dep. > ddb calc + boolean bSwitch = false; + double A = 0.0; + int i = start_period + 1; + while (i <= end_period && !bSwitch) { + double sl = (cost - A - salvage) / (life - i + 1); + ops[3] = new PtgInt(i); + double ddb = calcDDB(ops).getDoubleVal(); + if (sl <= ddb) { + A += ddb; + i++; + } else // straight-line depreciation is greater than ddb; switch + bSwitch = true; + } + result = A; + // use straight-line depreciation for rest of period + for (int j = i; j <= end_period; j++) { + result += (cost - A - salvage) / (life - i + 1); + } + } + + if (DEBUG) Logger.logInfo("Result from calcVDB= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * XIRR Returns the internal rate of return for a schedule of cash flows + * that is not necessarily periodic + */ + protected static Ptg calcXIRR(Ptg[] operands) { + if (operands.length < 2) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcXIRR"); + double guess = .1; + if (operands.length > 2) + guess = operands[2].getDoubleVal(); + // convert references to Ptg[]s + Ptg[] values = PtgCalculator.getAllComponents(operands[0]); + Ptg[] dates = PtgCalculator.getAllComponents(operands[1]); + if (DEBUG) + debugOperands(values, "calcXIRR"); + if (DEBUG) + debugOperands(dates, "calcXIRR"); + /* + * 'Newton-Raphson method: ' Given PV a function of X, determine the + * value of X ' such that SUM[PV] = 0, using iteration. ' dPVdX = + * derivative of PV with respect to X ' NB: if X = 1 + Rate, dX/dRate = + * 1 ' ' Calculate PV and dPVdX using an arbitrary initial estimate of + * X. ' Change X by -PV/dPVdX and calculate again. ' Repeat until X + * changes by less than some arbitrary small amount. + * + * Let X = 1 + Rate + * Let EXPi = -YearFrax(i) + * PVi = Val(i) * X^(EXPi) + * dPV/dX = Val(i) * EXPi * X^(EXPi- 1) + * = Val(i) * X^(EXPi) *EXPi / X + * = PVi * EXPi / X + * dPVSum/dX = SUM[dPV/dX] + * dPVSum/dX = 1/X * SUM[PVi * EXPi] + * + */ + if (values.length != dates.length) + return new PtgErr(PtgErr.ERROR_NUM); + // validate dates + double date0 = dates[0].getDoubleVal(); + for (int i = 1; i < dates.length; i++) { + // TODO: if not valid date, return PtgErr.ERROR_VALUE + long val = PtgCalculator.getLongValue(dates[i]); + if (val < date0) + return new PtgErr(PtgErr.ERROR_NUM); + } + // validate values: sum all outflows (= negative values) + inflows (= + // positive values) + double outflow = 0.0, inflow = 0.0; + // get outflow (- values) + for (int i = 0; i < values.length; i++) { + double val = values[i].getDoubleVal(); + if (val < 0) + outflow += Math.abs(val); + else + inflow += val; + } + if (outflow == 0.0 || inflow == 0.0) + return new PtgErr(PtgErr.ERROR_NUM); + + int n = values.length; + + // iterate over possible irr values; value is correct when + // f <= tolerance, defined as .000001% + boolean bIsCorrect = false; + final double TOLERANCE = 0.00000001; + double trial = guess; + double x0, f0, f; + x0 = 1; //1+0 rate, x_0, lower bounds of guess + f0 = inflow - outflow; // =sum of values when rate is 0 + trial = 1 + guess; // x_1, upper bounds + // for secant method + // double f1= 0.0; + // double x1; + // double dx= xh-xl; + double delta = 0; + double fprime = 0; // derivative of f(x0) + for (int i = 0; i < n; i++) { + double val = values[i].getDoubleVal(); + double exp = (PtgCalculator.getLongValue(dates[i]) - date0) / 365; + // f+= val*(Math.pow(xh, -exp)); + fprime += val * -exp; // f' of x0 + } + for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 100 tries + // SECANT METHOD: trial= x0+dx*f0/(f0-f1); + // NEWTON'S: + f = 0.0; + for (int i = 0; i < n; i++) { + double val = values[i].getDoubleVal(); + double exp = (dates[i].getDoubleVal() - date0) / 365.0; + f += val * (Math.pow(trial, -exp)); + fprime += val * (Math.pow(trial, -exp)) * -exp; // derivative of + // f(trial) + } + fprime = fprime / trial; // final f' expression + delta = f / fprime; + if (trial - delta <= 0) + delta = trial / 2; + trial -= delta; + /* + * secant method if (f < 0) { delta= xl-trial; xl= trial; fl= f; } + * else { delta= xh-trial; xh= trial; fh= f; } dx= xh-xl; + */ + bIsCorrect = (Math.abs(delta) <= TOLERANCE); + } + if (!bIsCorrect) + return new PtgErr(PtgErr.ERROR_NUM); + + trial -= 1; + if (DEBUG) + Logger.logInfo("Result from calcXIRR= " + trial); + PtgNumber pnum = new PtgNumber(trial); + return pnum; + } + + /** + * XNPV Returns the net present value for a schedule of cash flows that is + * not necessarily periodic + */ + protected static Ptg calcXNPV(Ptg[] operands) { + if (operands.length < 2 || operands[1].getComponents() == null) { + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcXNPV"); + double rate = operands[0].getDoubleVal(); + // convert references to Ptg[]s + Ptg[] values = PtgCalculator.getAllComponents(operands[1]); + Ptg[] dates = PtgCalculator.getAllComponents(operands[2]); + if (DEBUG) + debugOperands(values, "calcXNPV"); + if (DEBUG) + debugOperands(dates, "calcXNPV"); + + if (values.length != dates.length) + return new PtgErr(PtgErr.ERROR_NUM); + // TODO: validate dates + double date0 = dates[0].getDoubleVal(); + for (int i = 1; i < dates.length; i++) { + // TODO: if not valid date, return PtgErr.ERROR_VALUE + long val = PtgCalculator.getLongValue(dates[i]); + if (val < date0) + return new PtgErr(PtgErr.ERROR_NUM); + } + // validate values: sum all outflows (= negative values) + inflows (= + // positive values) + double outflow = 0.0, inflow = 0.0; + // get outflow (- values) + for (int i = 0; i < values.length; i++) { + double val = values[i].getDoubleVal(); + if (val < 0) + outflow += Math.abs(val); + else + inflow += val; + } + if (outflow == 0.0 || inflow == 0.0) + return new PtgErr(PtgErr.ERROR_NUM); + + int n = values.length; + double result = 0.0; + for (int i = 0; i < n; i++) { + double val = values[i].getDoubleVal(); + double exp = (dates[i].getDoubleVal() - date0) / 365.0; + result += val / (Math.pow(1 + rate, exp)); + } + if (DEBUG) + Logger.logInfo("Result from calcXNPV= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * YIELD Returns the yield on a security that pays periodic interest + *

                + * YIELD(settlement,maturity,rate,pr,redemption,frequency,basis) Settlement + * is the security's settlement date. The security settlement date is the + * date after the issue date when the security is traded to the buyer. + * Maturity is the security's maturity date. The maturity date is the date + * when the security expires. Rate is the security's annual coupon rate. Pr + * is the security's price per $100 face value. Redemption is the security's + * redemption value per $100 face value. Frequency is the number of coupon + * payments per year. For annual payments, frequency = 1; for semiannual, + * frequency = 2; for quarterly, frequency = 4. Basis (optional) is the type + * of day count basis to use. + */ + protected static Ptg calcYIELD(Ptg[] operands) { + if (operands.length < 6) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + if (DEBUG) + debugOperands(operands, "calcINTRATE"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); + double rate = operands[2].getDoubleVal(); + double pr = operands[3].getDoubleVal(); + double redemption = operands[4].getDoubleVal(); + int frequency = operands[5].getIntVal(); + int basis = 0; + if (operands.length > 6) + basis = operands[6].getIntVal(); + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); + if (rate < 0) return new PtgErr(PtgErr.ERROR_NUM); + if (pr <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); + Ptg[] ops; + ops = new Ptg[4]; + ops[0] = operands[0]; + ops[1] = operands[1]; + ops[2] = operands[5]; + ops[3] = new PtgInt(basis); + double result = 0.0; + /* + * A= coupDayBS E= coupDays n= coupNum DSC= coupDaysNC DSR= getDaysFromBasis + */ + double n = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity + double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period in which settlementfalls + double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement + + if (n <= 1) { + double DSR = getDaysFromBasis(basis, settlementDate, maturityDate); + double R = rate / frequency; + double P = pr / 100; + result = ((redemption / 100) + R) - (P + ((A / E) * R)); + result /= P + ((A / E) * R); + result *= (frequency * E) / DSR; + } else { + // for n values > 1, must employ an iterated approach to find yield using formula for price + double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon + result = yieldIteration(DSC, E, n, A, rate, frequency, redemption, pr); + if (result == -1) {// didn't find it + return new PtgErr(PtgErr.ERROR_NUM); + } + } - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from calcYIELD= " + result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } + PtgNumber pnum = new PtgNumber(result); + if (DEBUG) Logger.logInfo("Result from calcYIELD= " + result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } - private static double yieldIteration(double DSC, double E, double n, double A, double rate, int frequency, double redemption, double pr) { - /* Using Newton-Raphson, iterate over possible yield values; value is correct when + private static double yieldIteration(double DSC, double E, double n, double A, double rate, int frequency, double redemption, double pr) { + /* Using Newton-Raphson, iterate over possible yield values; value is correct when * f <= tolerance, defined as .000001% // 100 iterations, formula for PRICE // each trial= f(trial)/f'(trial) // f(trial)= price calculation // f'(trial): (deep breath) - * - * if f= price, and + * + * if f= price, and * G and F are given * C= redemption * B= 100*R * E= -(n-1+F) * Ei = -(i-1+F) * x= 1+trial/frequency - * + * * then, - * + * * ui(trial)= B*x^Ei - * f'(ui(trial))= -Ei*B*x^(Ei-1) + * f'(ui(trial))= -Ei*B*x^(Ei-1) * = Ei*B*x^Ei*1/x * = B*x^Ei*1/x*Ei * = ui(trial)*Ei/x * f'(sum(ui(trial)))= sum(f'(ui)) * = sum(ui(trial)*Ei/x * = 1/x*sum(ui(trial)*Ei) - * - * + * + * * f(trial)= C*x^E + sum(ui(trial)) - B*G * f'(trial)= f'(f(x)+sum(ui(trial)) * = f'(x) + f'(sum(ui(trial)) @@ -3281,172 +3283,173 @@ private static double yieldIteration(double DSC, double E, double n, double A, d * = (C*E*x^E)*(1/x) + (1/x)*sum(ui(trial)*Ei) * = (C*E*x^E)*(1/x) + (1/x)*sum(B*x^Ei*Ei) */ - double guess = .1; - boolean bIsCorrect = false; - final double TOLERANCE = 0.00000001; - double trial = guess; - double f; - double F= DSC/E; - double G= A/E; - double R= rate/frequency; - double Y; - double B= R*100; - double Exp= n-1+F; - - // N-R iteration - double fprime; // derivative of f(x0) - double delta = 0; - for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 100 tries - // f= f(trial) - Y= 1+ trial/frequency; - f= redemption/Math.pow(Y, Exp); - fprime= 0; - for (int i = 1; i <= n; i++) { - f+= B/Math.pow(Y, i - 1 + F); - fprime+= (B/Math.pow(Y, i - 1 + F))*(i-1+F); - } - f -= (B * G); - // pr-f =>0 - fprime/=Y; // final - fprime+= redemption/Math.pow(Y, Exp)*Exp*(1/Y); - - // N-R: use f/fprime as iterative factor - delta = (pr-f) / fprime; - if (trial < delta) - trial= delta-trial; - else - trial= trial-delta; + double guess = .1; + boolean bIsCorrect = false; + final double TOLERANCE = 0.00000001; + double trial = guess; + double f; + double F = DSC / E; + double G = A / E; + double R = rate / frequency; + double Y; + double B = R * 100; + double Exp = n - 1 + F; + + // N-R iteration + double fprime; // derivative of f(x0) + double delta = 0; + for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 100 tries + // f= f(trial) + Y = 1 + trial / frequency; + f = redemption / Math.pow(Y, Exp); + fprime = 0; + for (int i = 1; i <= n; i++) { + f += B / Math.pow(Y, i - 1 + F); + fprime += (B / Math.pow(Y, i - 1 + F)) * (i - 1 + F); + } + f -= (B * G); + // pr-f =>0 + fprime /= Y; // final + fprime += redemption / Math.pow(Y, Exp) * Exp * (1 / Y); + + // N-R: use f/fprime as iterative factor + delta = (pr - f) / fprime; + if (trial < delta) + trial = delta - trial; + else + trial = trial - delta; // while (trial - delta <= 0) delta= delta/2; // sanity check // trial-= delta; - bIsCorrect = (Math.abs(pr-f) <= TOLERANCE); - } - if (bIsCorrect) - return (trial); - else - return -1; -/* - MinYield = -1# - MaxYield = .Rate - If MaxYield = 0 Then MaxYield = 0.1 - Do While CalculatedPrice(BondInfo, MaxYield) > .Price - MaxYield = MaxYield * 2 - Loop - - - Yld = 0.5 * (MinYield + MaxYield) - For i = 1 To MaxIterations - Diff = CalculatedPrice(BondInfo, Yld) - .Price - If Abs(Diff) < Accuracy Then Exit For - 'if calculated price is greater, correct yield is greater - If Diff > 0 Then MinYield = Yld Else MaxYield = Yld - Yld = 0.5 * (MinYield + MaxYield) - Next i - End If - BondYield = Yld -*/ - } - - /** - * YIELDDISC Returns the annual yield for a discounted security. For - * example, a treasury bill - */ - protected static Ptg calcYieldDisc(Ptg[] operands) { - if (operands.length < 4) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcYIELDDISC"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double pr = operands[2].getDoubleVal(); - double redemption = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - - if (pr <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (maturityDate <= settlementDate) return new PtgErr(PtgErr.ERROR_NUM); + bIsCorrect = (Math.abs(pr - f) <= TOLERANCE); + } + if (bIsCorrect) + return (trial); + else + return -1; +/* + MinYield = -1# + MaxYield = .Rate + If MaxYield = 0 Then MaxYield = 0.1 + Do While CalculatedPrice(BondInfo, MaxYield) > .Price + MaxYield = MaxYield * 2 + Loop + + + Yld = 0.5 * (MinYield + MaxYield) + For i = 1 To MaxIterations + Diff = CalculatedPrice(BondInfo, Yld) - .Price + If Abs(Diff) < Accuracy Then Exit For + 'if calculated price is greater, correct yield is greater + If Diff > 0 Then MinYield = Yld Else MaxYield = Yld + Yld = 0.5 * (MinYield + MaxYield) + Next i + End If + BondYield = Yld +*/ + } + + /** + * YIELDDISC Returns the annual yield for a discounted security. For + * example, a treasury bill + */ + protected static Ptg calcYieldDisc(Ptg[] operands) { + if (operands.length < 4) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcYIELDDISC"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + double pr = operands[2].getDoubleVal(); + double redemption = operands[3].getDoubleVal(); + int basis = 0; + if (operands.length > 4) + basis = operands[4].getIntVal(); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); + + if (pr <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); + if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); + if (maturityDate <= settlementDate) return new PtgErr(PtgErr.ERROR_NUM); // double DSM = maturityDate - settlementDate; // double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); // double result = ((redemption - pr) / pr) * (B / DSM); - double result = ((redemption - pr) / pr)/yearFrac(basis, settlementDate, maturityDate); - if (DEBUG) - Logger.logInfo("Result from calcYIELDDISC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * YIELDMAT Returns the annual yield of a security that pays interest at - * maturity - * - */ - protected static Ptg calcYieldMat(Ptg[] operands) { - if (operands.length < 5) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcYIELDMAT"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[2].getValue()); - double rate = operands[3].getDoubleVal(); - double price = operands[4].getDoubleVal(); - int basis = 0; - if (operands.length > 5) - basis = operands[5].getIntVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))) - .longValue(); - - double B= getDaysInYearFromBasis(basis,settlementDate, maturityDate); - double DSM= getDaysFromBasis(basis, settlementDate, maturityDate); - double DIM= getDaysFromBasis(basis, issueDate, maturityDate); - double A= getDaysFromBasis(basis, issueDate, settlementDate); - - double result= (((1 + (DIM/B)*rate) - ((price/100) + (A/B)*rate))/((price/100) + (A/B)*rate)) * (B/DSM); - if (DEBUG) Logger.logInfo("Result from calcYIELDMAT= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - }catch (Exception e) { } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - static void debugOperands(Ptg[] operands, String f) { - if (DEBUG) { - Logger.logInfo("Operands for " + f); - for (int i = 0; i < operands.length; i++) { - String s = operands[i].getString(); - if (!(operands[i] instanceof PtgMissArg)) { - String v = operands[i].getValue().toString(); - Logger.logInfo("\tOperand[" + i + "]=" + s + " " + v); - } else - Logger.logInfo("\tOperand[" + i + "]=" + s + " is Missing"); - } - } - } + double result = ((redemption - pr) / pr) / yearFrac(basis, settlementDate, maturityDate); + if (DEBUG) + Logger.logInfo("Result from calcYIELDDISC= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * YIELDMAT Returns the annual yield of a security that pays interest at + * maturity + */ + protected static Ptg calcYieldMat(Ptg[] operands) { + if (operands.length < 5) { + PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); + return perr; + } + if (DEBUG) + debugOperands(operands, "calcYIELDMAT"); + try { + GregorianCalendar sDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[0].getValue()); + GregorianCalendar mDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[1].getValue()); + GregorianCalendar iDate = (GregorianCalendar) DateConverter + .getCalendarFromNumber(operands[2].getValue()); + double rate = operands[3].getDoubleVal(); + double price = operands[4].getDoubleVal(); + int basis = 0; + if (operands.length > 5) + basis = operands[5].getIntVal(); + + long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) + .longValue(); + long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) + .longValue(); + long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))) + .longValue(); + + double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); + double DSM = getDaysFromBasis(basis, settlementDate, maturityDate); + double DIM = getDaysFromBasis(basis, issueDate, maturityDate); + double A = getDaysFromBasis(basis, issueDate, settlementDate); + + double result = (((1 + (DIM / B) * rate) - ((price / 100) + (A / B) * rate)) / ((price / 100) + (A / B) * rate)) * (B / DSM); + if (DEBUG) Logger.logInfo("Result from calcYIELDMAT= " + result); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + static void debugOperands(Ptg[] operands, String f) { + if (DEBUG) { + Logger.logInfo("Operands for " + f); + for (int i = 0; i < operands.length; i++) { + String s = operands[i].getString(); + if (!(operands[i] instanceof PtgMissArg)) { + String v = operands[i].getValue().toString(); + Logger.logInfo("\tOperand[" + i + "]=" + s + " " + v); + } else + Logger.logInfo("\tOperand[" + i + "]=" + s + " is Missing"); + } + } + } - public static boolean isLeapYear(int year) { - return (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0)); - } + public static boolean isLeapYear(int year) { + return (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0)); + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.java index 30fc9e9..2529225 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,142 +22,146 @@ */ package io.starter.formats.XLS.formulas; -import java.util.Stack; - import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.toolkit.Logger; +import java.util.Stack; + -/** Formula Calculator. - - Translates an excel calc stack into a value. The stack is in a modified - reverse polish notation. For details please look into the Excel 97 reference for BIFF8. - - Formula Calculators do not exist per formula, rather it uses a factory pattern. - Put a formula in & calculate it. - - Actual calculation methods exist within the OperatorPtg's themselves, so this just handles - the grunt work of parsing and passing ptgs back and forth along with formatting the output. - +/** + * Formula Calculator. + *

                + * Translates an excel calc stack into a value. The stack is in a modified + * reverse polish notation. For details please look into the Excel 97 reference for BIFF8. + *

                + * Formula Calculators do not exist per formula, rather it uses a factory pattern. + * Put a formula in & calculate it. + *

                + * Actual calculation methods exist within the OperatorPtg's themselves, so this just handles + * the grunt work of parsing and passing ptgs back and forth along with formatting the output. + * * @see Formula + */ +public class FormulaCalculator { -*/ -public class FormulaCalculator -{ - /** - Calculates the value of calcStac This is handled by - running through the stack, adding operands to tempstack until - an operator PTG is found. At that point pass the relevant ptg's from - tempstack into the calculate method of the operator PTG. The operator - ptg should return a valid value PTG. - */ + * Calculates the value of calcStac This is handled by + * running through the stack, adding operands to tempstack until + * an operator PTG is found. At that point pass the relevant ptg's from + * tempstack into the calculate method of the operator PTG. The operator + * ptg should return a valid value PTG. + */ public static Object calculateFormula(Stack expression) - throws FunctionNotSupportedException{ + throws FunctionNotSupportedException { int sz = expression.size(); - Ptg[] stck = new Ptg[sz]; - stck = (Ptg[])expression.toArray(stck); - Stack calcStack = new Stack(); - for(int t=0;t. * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; - -import java.util.Stack; import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.*; -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.CompatibleVector; import io.starter.toolkit.Logger; import io.starter.toolkit.StringTool; -import io.starter.toolkit.CompatibleVector; -import io.starter.formats.XLS.XLSRecordFactory; -//import com.sun.org.apache.xerces.internal.impl.xs.identity.Selector.Matcher; -import java.util.Hashtable; -import java.util.Enumeration; -import java.util.Collections; import java.util.Locale; +import java.util.Stack; import java.util.Vector; -import java.util.regex.Pattern; +//import com.sun.org.apache.xerces.internal.impl.xs.identity.Selector.Matcher; -/** Formula Parser. - - Translates Excel-compatible Strings into Biff8/OpenXLS Compatible Formulas and vice-versa. - + +/** + * Formula Parser. + *

                + * Translates Excel-compatible Strings into Biff8/OpenXLS Compatible Formulas and vice-versa. + * * @see Formula + */ +public final class FormulaParser { -*/ -public final class FormulaParser{ - static int DEBUGLEVEL = -1; // KSC: handling unary operators necessitated, in the end, a complete rewrite ... + /** * getPtgsFromFormulaString * returns ordered stack of Ptgs parsed from formula string fmla - * - * @param Formula form formula record - * @param String fmla string rep. of current state of formula (either original "=F(Y)" or a recurrsed state e.g. "Y") - * - * @returns Stack ordered Ptgs that represent formula expression + * + * @param Formula form formula record + * @param String fmla string rep. of current state of formula (either original "=F(Y)" or a recurrsed state e.g. "Y") + * @returns Stack ordered Ptgs that represent formula expression + */ + public static Stack getPtgsFromFormulaString(XLSRecord form, String fmla) { + return getPtgsFromFormulaString(form, fmla, true); + } + + /** + * getPtgsFromFormulaString is the main entry point for parsing a string and creating a formula. + * The formula passed in at this point can either be an existing formula with an expression, or a + * templated formula with no expression. The string gets parsed and entered + * as the expression for the formula. + * + * @param Formula form formula record + * @param String fmla string rep. of current state of formula (either original "=F(Y)" or a recurrsed state e.g. "Y") + * @param boolean bIsCompleteExpression truth of "formula fmla represents a complete formula i.e. we are not currently in a recurrsed state" + * @returns Stack ordered Ptgs that represent formula expression */ - public static Stack getPtgsFromFormulaString(XLSRecord form, String fmla){ - return getPtgsFromFormulaString(form, fmla, true); - } - - /** - * getPtgsFromFormulaString is the main entry point for parsing a string and creating a formula. - * The formula passed in at this point can either be an existing formula with an expression, or a - * templated formula with no expression. The string gets parsed and entered - * as the expression for the formula. - * - * @param Formula form formula record - * @param String fmla string rep. of current state of formula (either original "=F(Y)" or a recurrsed state e.g. "Y") - * @param boolean bIsCompleteExpression truth of "formula fmla represents a complete formula i.e. we are not currently in a recurrsed state" - * @returns Stack ordered Ptgs that represent formula expression - */ /** * should handle all sorts of variations of formula strings such as: * =PV(C17,1+-(1*1)-9, 0, 1) @@ -85,331 +79,331 @@ public static Stack getPtgsFromFormulaString(XLSRecord form, String fmla){ * =SUM(IF(A1:A10=B1:B10, 1, 0)) * =IF(B4<=10,"10", if(b4<=100, "15", "20")) * ="STRING"&IF(A<>"",A,"N/A")&" - &IF(C<>"",C,"N/A")&" Result " - * + *

                * in basic essence, handles signatures such as * a op f(b op c, d, uop e ...) op g - * + *

                * where op is any binary operator, uop is a unary operator f is a formula - * ... - */ - protected static Stack getPtgsFromFormulaString(XLSRecord form, String fmla, boolean bMergeWithLast){ - Object[] operands= new Object[2]; - - fmla= fmla.trim(); - if (fmla.startsWith("=")) fmla= fmla.substring(1); - fmla= fmla.trim(); - - - if (fmla.equals("")) { // 20081120 KSC: Handle Missing Argument - Stack s= new Stack(); - s.add(new PtgMissArg()); - return s; - } - - if (fmla.startsWith("{")) { // must process array formula first, PtgArray expects full function string - PtgArray pa= new PtgArray(); - pa.setParentRec(form); - int endarray= getMatchOperator(fmla, 0, '{', '}'); - pa.setVal(fmla.substring(0, endarray+1)); - fmla= fmla.substring(endarray+1); - Stack s= new Stack(); - s.add(pa); - operands[0]= s; - bMergeWithLast= false; - } + * ... + */ + protected static Stack getPtgsFromFormulaString(XLSRecord form, String fmla, boolean bMergeWithLast) { + Object[] operands = new Object[2]; + + fmla = fmla.trim(); + if (fmla.startsWith("=")) fmla = fmla.substring(1); + fmla = fmla.trim(); + + + if (fmla.equals("")) { // 20081120 KSC: Handle Missing Argument + Stack s = new Stack(); + s.add(new PtgMissArg()); + return s; + } + + if (fmla.startsWith("{")) { // must process array formula first, PtgArray expects full function string + PtgArray pa = new PtgArray(); + pa.setParentRec(form); + int endarray = getMatchOperator(fmla, 0, '{', '}'); + pa.setVal(fmla.substring(0, endarray + 1)); + fmla = fmla.substring(endarray + 1); + Stack s = new Stack(); + s.add(pa); + operands[0] = s; + bMergeWithLast = false; + } // TODO: complex ranges?? - boolean inQuote= false; - boolean inRange= false; - boolean inOp= false; - String prefix= ""; - Stack ops= new Stack(); - String op= ""; - for (int i= 0; i < fmla.length(); i++) { - char c= fmla.charAt(i); - if (c=='"' || c=='\'') { // get to ending quote - if (inQuote) { - inQuote= (c!=prefix.trim().charAt(0)); // if start quote == end quote, inQuote is false - } else - inQuote= true; - if (inQuote) { - if (inOp) { - inOp= false; - if (!op.equals("")) ops.add(0, op); - op= ""; - if (operands[0]!=null && operands[1]!=null) - addOperands(form, /*functionStack, */operands, ops); - } - } - prefix+=c; - } else if (inQuote) { - prefix+=c; - } else if (c==':') { - if (i>0) - inRange= true; - prefix+=c; - } else if (c=='(') { // found a formula?? check out - // if the parenthesis is part of a complex range, keep going i.e. keep entire expression together - if (inRange) { - prefix+=c; - continue; - } - if (inOp) { - inOp= false; - if (!op.equals("")) ops.add(0, op); - op= ""; - if (!ops.isEmpty() && operands[0]!=null && operands[1]!=null) { - addOperands(form, /*functionStack, */operands, ops); - } - } - String funcName= ""; - for (int k= prefix.length()-1; k >= 0; k--) { - if (Character.isLetterOrDigit(prefix.charAt(k))|| Character.toString(prefix.charAt(k)).equals(".")) { - funcName= prefix.charAt(k) + funcName; - prefix= prefix.substring(0, k); - } - else - break; - } - // prefix= anything before function name - if (!prefix.trim().equals("")) { - if (operands[0]==null) - operands[0]= prefix.trim(); - else - operands[1]= prefix.trim(); - prefix= ""; - } - // function name should = part just before parents - if (!funcName.equals("")) { - Ptg funcPtg = null; - funcPtg=getFuncPtg(funcName, form); - - // do we have a valid function Ptg? - if (funcPtg!=null) { // yes, then handle function paramters i.e. evertyhing between the parentheses - int endparen= getMatchOperator(fmla, i, '(', ')'); - if (endparen < fmla.length()-1) { - if (fmla.charAt(endparen+1)==':') { // it's a VERY complex range :) - inRange= true; - prefix= funcName + fmla.substring(i, endparen+1); // keep function name together ... - i= endparen; - continue; - } - } - // things like: xyz + f(x) - if (!ops.isEmpty() && operands[0]!=null && operands[1]!=null) { - addOperands(form, /*functionStack, */operands, ops); - inOp= false; - } // have [xyz, xyz, OP] + [abc, def, ghi] - // parse function - Stack s= parseFunctionPtg(form, funcName, fmla.substring(i+1, endparen), funcPtg); - if (operands[0]==null) - operands[0]= s; - else - operands[1]= s; - //functionStack.addAll(parseFunctionPtg(form, funcName, fmla.substring(i+1, endparen), funcPtg)); - i= endparen; // inc. pointer to past processing point - } else // else, we have *something* in front of the parentheses ... - throw new FunctionNotSupportedException(funcName+ " is not a supported function"); - } else { // enclosing parens - // complexities occur for complex ranges and enclosing parens ... - int endparen = getMatchOperator(fmla, i, '(', ')'); - if (endparen==-1 || (endparen < fmla.length()-1 && fmla.charAt(endparen+1)==':')) { // it's a VERY complex range :) - inRange= true; - prefix= "("; - continue; - } - String f= fmla.substring(i+1, endparen); // the statement less the parenthesis - i= endparen; // skip parens ... - // see if the enclosed expression is a complex range - must parse as 1 unit, rather than parsing particular ptgs - if (FormulaParser.isComplexRange('('+f+')')) { - Stack s= new Stack(); - s.push(parseSinglePtg(form, '(' + f + ')', false)); - s.push(parseSinglePtg(form, ")", false)); - if (operands[0]==null) - operands[0]= s; - else - operands[1]= s; - } else { // embedded functions, keep parsing - Stack s= getPtgsFromFormulaString(form, f, true); // flag as a complete expression - s.push(new PtgParen()); // add ending parens to stack - if (operands[0]==null) - operands[0]= s; - else - operands[1]= s; - if (!ops.isEmpty()) { - addOperands(form, /*functionStack, */operands, ops); - } - } - } - } else { // see if we have found an operataor - if (!Character.isJavaIdentifierPart(c) && c!=' ' && c!='%') { - // if (inRange && !Character.isJavaIdentifierPart(c) && c!=',' && c!=' ') - if (inRange) { - if (c!=',' && c!=' ' && c!=')' && c!='!') { - inRange= false; - if (!prefix.trim().equals("")) { - if (operands[0]==null) - operands[0]= prefix.trim(); - else - operands[1]= prefix.trim(); - prefix= ""; - } - } else { - prefix+= c; - continue; - } - } - if (c!='!' && c!='#' && c!='.'){ // ignore ! - // FOUND AN OPERATOR - ready to add operands yet? - inOp= true; - if (!prefix.trim().equals("")) { - if (operands[0]==null) - operands[0]= prefix.trim(); - else - operands[1]= prefix.trim(); - prefix= ""; - } - if (!ops.isEmpty() && operands[0]!=null && operands[1]!=null) { - addOperands(form, /*functionStack, */operands, ops); - } - if (!op.equals("")) { - if (!(c=='=' || c=='>')) { - ops.add(0, op); - op= ""; - } - } - op+= c; // >,<,-,/, ,+ - continue; - } - } else if (inOp) { - inOp= false; - if (!ops.isEmpty() && operands[0]!=null && operands[1]!=null) { - addOperands(form, /*functionStack, */operands, ops); - } - if (!op.equals("")) - ops.add(0, op); - op= ""; - } - prefix+= c; - } - - } - if (!prefix.trim().equals("")) { // get any remaining elements - if (operands[0]==null) - operands[0]= prefix.trim(); - else - operands[1]= prefix.trim(); - prefix= ""; - } - - if (!op.equals("")) - ops.add(0, op); - addOperands(form, operands, ops); + boolean inQuote = false; + boolean inRange = false; + boolean inOp = false; + String prefix = ""; + Stack ops = new Stack(); + String op = ""; + for (int i = 0; i < fmla.length(); i++) { + char c = fmla.charAt(i); + if (c == '"' || c == '\'') { // get to ending quote + if (inQuote) { + inQuote = (c != prefix.trim().charAt(0)); // if start quote == end quote, inQuote is false + } else + inQuote = true; + if (inQuote) { + if (inOp) { + inOp = false; + if (!op.equals("")) ops.add(0, op); + op = ""; + if (operands[0] != null && operands[1] != null) + addOperands(form, /*functionStack, */operands, ops); + } + } + prefix += c; + } else if (inQuote) { + prefix += c; + } else if (c == ':') { + if (i > 0) + inRange = true; + prefix += c; + } else if (c == '(') { // found a formula?? check out + // if the parenthesis is part of a complex range, keep going i.e. keep entire expression together + if (inRange) { + prefix += c; + continue; + } + if (inOp) { + inOp = false; + if (!op.equals("")) ops.add(0, op); + op = ""; + if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { + addOperands(form, /*functionStack, */operands, ops); + } + } + String funcName = ""; + for (int k = prefix.length() - 1; k >= 0; k--) { + if (Character.isLetterOrDigit(prefix.charAt(k)) || Character.toString(prefix.charAt(k)).equals(".")) { + funcName = prefix.charAt(k) + funcName; + prefix = prefix.substring(0, k); + } else + break; + } + // prefix= anything before function name + if (!prefix.trim().equals("")) { + if (operands[0] == null) + operands[0] = prefix.trim(); + else + operands[1] = prefix.trim(); + prefix = ""; + } + // function name should = part just before parents + if (!funcName.equals("")) { + Ptg funcPtg = null; + funcPtg = getFuncPtg(funcName, form); + + // do we have a valid function Ptg? + if (funcPtg != null) { // yes, then handle function paramters i.e. evertyhing between the parentheses + int endparen = getMatchOperator(fmla, i, '(', ')'); + if (endparen < fmla.length() - 1) { + if (fmla.charAt(endparen + 1) == ':') { // it's a VERY complex range :) + inRange = true; + prefix = funcName + fmla.substring(i, endparen + 1); // keep function name together ... + i = endparen; + continue; + } + } + // things like: xyz + f(x) + if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { + addOperands(form, /*functionStack, */operands, ops); + inOp = false; + } // have [xyz, xyz, OP] + [abc, def, ghi] + // parse function + Stack s = parseFunctionPtg(form, funcName, fmla.substring(i + 1, endparen), funcPtg); + if (operands[0] == null) + operands[0] = s; + else + operands[1] = s; + //functionStack.addAll(parseFunctionPtg(form, funcName, fmla.substring(i+1, endparen), funcPtg)); + i = endparen; // inc. pointer to past processing point + } else // else, we have *something* in front of the parentheses ... + throw new FunctionNotSupportedException(funcName + " is not a supported function"); + } else { // enclosing parens + // complexities occur for complex ranges and enclosing parens ... + int endparen = getMatchOperator(fmla, i, '(', ')'); + if (endparen == -1 || (endparen < fmla.length() - 1 && fmla.charAt(endparen + 1) == ':')) { // it's a VERY complex range :) + inRange = true; + prefix = "("; + continue; + } + String f = fmla.substring(i + 1, endparen); // the statement less the parenthesis + i = endparen; // skip parens ... + // see if the enclosed expression is a complex range - must parse as 1 unit, rather than parsing particular ptgs + if (FormulaParser.isComplexRange('(' + f + ')')) { + Stack s = new Stack(); + s.push(parseSinglePtg(form, '(' + f + ')', false)); + s.push(parseSinglePtg(form, ")", false)); + if (operands[0] == null) + operands[0] = s; + else + operands[1] = s; + } else { // embedded functions, keep parsing + Stack s = getPtgsFromFormulaString(form, f, true); // flag as a complete expression + s.push(new PtgParen()); // add ending parens to stack + if (operands[0] == null) + operands[0] = s; + else + operands[1] = s; + if (!ops.isEmpty()) { + addOperands(form, /*functionStack, */operands, ops); + } + } + } + } else { // see if we have found an operataor + if (!Character.isJavaIdentifierPart(c) && c != ' ' && c != '%') { + // if (inRange && !Character.isJavaIdentifierPart(c) && c!=',' && c!=' ') + if (inRange) { + if (c != ',' && c != ' ' && c != ')' && c != '!') { + inRange = false; + if (!prefix.trim().equals("")) { + if (operands[0] == null) + operands[0] = prefix.trim(); + else + operands[1] = prefix.trim(); + prefix = ""; + } + } else { + prefix += c; + continue; + } + } + if (c != '!' && c != '#' && c != '.') { // ignore ! + // FOUND AN OPERATOR - ready to add operands yet? + inOp = true; + if (!prefix.trim().equals("")) { + if (operands[0] == null) + operands[0] = prefix.trim(); + else + operands[1] = prefix.trim(); + prefix = ""; + } + if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { + addOperands(form, /*functionStack, */operands, ops); + } + if (!op.equals("")) { + if (!(c == '=' || c == '>')) { + ops.add(0, op); + op = ""; + } + } + op += c; // >,<,-,/, ,+ + continue; + } + } else if (inOp) { + inOp = false; + if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { + addOperands(form, /*functionStack, */operands, ops); + } + if (!op.equals("")) + ops.add(0, op); + op = ""; + } + prefix += c; + } + + } + if (!prefix.trim().equals("")) { // get any remaining elements + if (operands[0] == null) + operands[0] = prefix.trim(); + else + operands[1] = prefix.trim(); + prefix = ""; + } + + if (!op.equals("")) + ops.add(0, op); + addOperands(form, operands, ops); //if (((Stack)operands[0]).isEmpty()) //return functionStack; - return (Stack) operands[0]; - - } - - /** - * Given two operands (objects) and operators (can be up to 2 if there is a unary operator present) - * organize and add to functionStack in reverse polish notation i.e. - * OPERAND, OPERAND, OP [...] - * - * @param form - * @param functionStack - * @param operands - * @param ops - */ - private static void addOperands(XLSRecord form, /*Stack functionStack, */Object[] operands, Stack ops) { - Ptg pOp= null; - if (!ops.isEmpty()) - pOp= parseSinglePtg(form, (String) ops.pop(), (operands[1]==null)); - - Stack s= new Stack(); - s.addAll(handleOperatorPrecedence(form, /*functionStack, */operands, pOp)); - operands[0]= s; - //functionStack.clear(); - if (!ops.isEmpty()) { - pOp= parseSinglePtg(form, (String) ops.pop(), true); - s= new Stack(); - s.addAll(handleOperatorPrecedence(form, /*functionStack, */operands, pOp)); - operands[0]= s; - } - - } - - private static Stack handleOperatorPrecedence(XLSRecord form, /*Stack functionStack, */Object[] operands, Ptg pOp) { - Stack functionStack= new Stack(); - if (operands[0] instanceof Stack) { - functionStack= (Stack) operands[0]; - operands[0]= null; - } - if (!functionStack.isEmpty() && pOp!=null) { - Ptg lastOp= (Ptg) functionStack.peek(); - if (lastOp!=null && lastOp.getIsOperator()) { - if (lastOp.getIsOperator()) { - functionStack.pop(); //= lastOp - int group1=rankPrecedence(pOp); - int group2=rankPrecedence(lastOp); - if (group2 >= group1) { - functionStack.push(lastOp); - } else { // current op has higher priority - if (operands[0]!=null) { - if (operands[0] instanceof String) - functionStack.push(parseSinglePtg(form, (String) operands[0], functionStack.isEmpty())); - else - functionStack.addAll((Stack) operands[0]); - } - if (operands[1]!=null) { - if (operands[1] instanceof String) - functionStack.push(parseSinglePtg(form, (String) operands[1], functionStack.isEmpty())); - else - functionStack.addAll((Stack) operands[1]); - } - operands[0]= null; - operands[1]= null; - functionStack.push(pOp); - pOp= lastOp; - } - } - } - } - if (operands[0]!=null) { - if (operands[0] instanceof String) - functionStack.push(parseSinglePtg(form, (String) operands[0], functionStack.isEmpty())); - else - functionStack.addAll((Stack) operands[0]); - } - if (operands[1]!=null) { - if (operands[1] instanceof String) - functionStack.push(parseSinglePtg(form, (String) operands[1], functionStack.isEmpty())); - else - functionStack.addAll((Stack) operands[1]); - } - operands[0]= null; - operands[1]= null; - if (pOp!=null) - functionStack.push(pOp); - return functionStack; - } - - - /** - * merge last stacks to ensure operator order is correct - * @param functionStack - * @return - */ - private static Stack mergeStacks(Stack prevStack, Stack curStack, boolean bIsCompleteExpression) { - if (prevStack.isEmpty()) - return curStack; - - Ptg lastOp= (Ptg)prevStack.peek(); - Ptg curOp= (curStack.isEmpty()?null:((Ptg)curStack.peek())); - int group1=rankPrecedence(lastOp); - int group2=rankPrecedence(curOp); - if (group1>=0 && (group1 < group2 || group2==-1)) { - lastOp= (Ptg) prevStack.pop(); - curStack.push(lastOp); + return (Stack) operands[0]; + + } + + /** + * Given two operands (objects) and operators (can be up to 2 if there is a unary operator present) + * organize and add to functionStack in reverse polish notation i.e. + * OPERAND, OPERAND, OP [...] + * + * @param form + * @param functionStack + * @param operands + * @param ops + */ + private static void addOperands(XLSRecord form, /*Stack functionStack, */Object[] operands, Stack ops) { + Ptg pOp = null; + if (!ops.isEmpty()) + pOp = parseSinglePtg(form, (String) ops.pop(), (operands[1] == null)); + + Stack s = new Stack(); + s.addAll(handleOperatorPrecedence(form, /*functionStack, */operands, pOp)); + operands[0] = s; + //functionStack.clear(); + if (!ops.isEmpty()) { + pOp = parseSinglePtg(form, (String) ops.pop(), true); + s = new Stack(); + s.addAll(handleOperatorPrecedence(form, /*functionStack, */operands, pOp)); + operands[0] = s; + } + + } + + private static Stack handleOperatorPrecedence(XLSRecord form, /*Stack functionStack, */Object[] operands, Ptg pOp) { + Stack functionStack = new Stack(); + if (operands[0] instanceof Stack) { + functionStack = (Stack) operands[0]; + operands[0] = null; + } + if (!functionStack.isEmpty() && pOp != null) { + Ptg lastOp = (Ptg) functionStack.peek(); + if (lastOp != null && lastOp.getIsOperator()) { + if (lastOp.getIsOperator()) { + functionStack.pop(); //= lastOp + int group1 = rankPrecedence(pOp); + int group2 = rankPrecedence(lastOp); + if (group2 >= group1) { + functionStack.push(lastOp); + } else { // current op has higher priority + if (operands[0] != null) { + if (operands[0] instanceof String) + functionStack.push(parseSinglePtg(form, (String) operands[0], functionStack.isEmpty())); + else + functionStack.addAll((Stack) operands[0]); + } + if (operands[1] != null) { + if (operands[1] instanceof String) + functionStack.push(parseSinglePtg(form, (String) operands[1], functionStack.isEmpty())); + else + functionStack.addAll((Stack) operands[1]); + } + operands[0] = null; + operands[1] = null; + functionStack.push(pOp); + pOp = lastOp; + } + } + } + } + if (operands[0] != null) { + if (operands[0] instanceof String) + functionStack.push(parseSinglePtg(form, (String) operands[0], functionStack.isEmpty())); + else + functionStack.addAll((Stack) operands[0]); + } + if (operands[1] != null) { + if (operands[1] instanceof String) + functionStack.push(parseSinglePtg(form, (String) operands[1], functionStack.isEmpty())); + else + functionStack.addAll((Stack) operands[1]); + } + operands[0] = null; + operands[1] = null; + if (pOp != null) + functionStack.push(pOp); + return functionStack; + } + + + /** + * merge last stacks to ensure operator order is correct + * + * @param functionStack + * @return + */ + private static Stack mergeStacks(Stack prevStack, Stack curStack, boolean bIsCompleteExpression) { + if (prevStack.isEmpty()) + return curStack; + + Ptg lastOp = (Ptg) prevStack.peek(); + Ptg curOp = (curStack.isEmpty() ? null : ((Ptg) curStack.peek())); + int group1 = rankPrecedence(lastOp); + int group2 = rankPrecedence(curOp); + if (group1 >= 0 && (group1 < group2 || group2 == -1)) { + lastOp = (Ptg) prevStack.pop(); + curStack.push(lastOp); /* while (curOp.getIsOperator()) { lastOp= curOp; curStack.push(curOp); @@ -423,443 +417,443 @@ private static Stack mergeStacks(Stack prevStack, Stack curStack, boolean bIsCom }else return curStack; } -*/ - } - prevStack.addAll(curStack); - return prevStack; - } - - - /** - * parse and add to Stack a valid Excel function represented by funcPtg and fmla string - * called from getPtgsFromFormulaString - * - * @param form formula record - * @para func function name f - * @param fmla function parameters in the form of (x, y, z) - * @param funcPtg function data for the formula represented by fmla - * @return Stack ordered parsed Stack of Ptgs - */ - private static Stack parseFunctionPtg(XLSRecord form, String func, String fmla, Ptg funcPtg) { - Stack returnStack = new Stack(); - fmla= fmla.trim(); - int nParens= 0; - boolean enclosing= false; - // change: only remove 1 set of parens: - if (fmla.length()>0 && fmla.charAt(0)=='(') { - if (getMatchOperator(fmla, 0, '(', ')')==fmla.length()-1) { // then strip enclosing parens - nParens++; - enclosing= true; - } - fmla= fmla.trim(); - } - - // NOTE: all memfuncs/complex ranges are enclosed by parentheses - // IF enclosed by parens, DO NOT split apart into operands: - int funcLen= 1; - if (enclosing) { - returnStack.addAll(FormulaParser.getPtgsFromFormulaString(form, fmla, true)); - } else { - CompatibleVector cv= splitFunctionOperands(fmla); - funcLen = cv.size(); - // loop through the operands to the function and recurse - for (int y=0;y, <=, >=, <, >) - int group1=rankPrecedence(opPtg); - int group2=rankPrecedence(lastOp); - - if (group1>=0 && (group1 < group2 || group2==-1)) { - while (opPtg.getIsOperator()) { - lastOp= opPtg; - destStack.push(opPtg); - if (!sourceStack.isEmpty()) { - opPtg = (Ptg) sourceStack.pop(); - // handle precedence - group1=rankPrecedence(opPtg); - group2=rankPrecedence(lastOp); - if (!(group1>=0 && (group1 < group2 || group2==-1))) - break; - }else - return destStack; - } - } - sourceStack.push(opPtg); - - // after sorting out operators, assemble two stacks into one - Stack nwstack = destStack; - destStack = new Stack(); - destStack.addAll(sourceStack); - destStack.addAll(nwstack); - - return destStack; - } - */ - - /** rank a Ptg Operator's precedence (lower - * @param curOp - * @return - */ - static int rankPrecedence(Ptg curOp) { - if (curOp==null) return -1; +*/ + } + prevStack.addAll(curStack); + return prevStack; + } + + + /** + * parse and add to Stack a valid Excel function represented by funcPtg and fmla string + * called from getPtgsFromFormulaString + * + * @param form formula record + * @param fmla function parameters in the form of (x, y, z) + * @param funcPtg function data for the formula represented by fmla + * @para func function name f + * @return Stack ordered parsed Stack of Ptgs + */ + private static Stack parseFunctionPtg(XLSRecord form, String func, String fmla, Ptg funcPtg) { + Stack returnStack = new Stack(); + fmla = fmla.trim(); + int nParens = 0; + boolean enclosing = false; + // change: only remove 1 set of parens: + if (fmla.length() > 0 && fmla.charAt(0) == '(') { + if (getMatchOperator(fmla, 0, '(', ')') == fmla.length() - 1) { // then strip enclosing parens + nParens++; + enclosing = true; + } + fmla = fmla.trim(); + } + + // NOTE: all memfuncs/complex ranges are enclosed by parentheses + // IF enclosed by parens, DO NOT split apart into operands: + int funcLen = 1; + if (enclosing) { + returnStack.addAll(FormulaParser.getPtgsFromFormulaString(form, fmla, true)); + } else { + CompatibleVector cv = splitFunctionOperands(fmla); + funcLen = cv.size(); + // loop through the operands to the function and recurse + for (int y = 0; y < cv.size(); y++) { + String s = (String) cv.elementAt(y); // flag as a complete expression + returnStack.addAll(FormulaParser.getPtgsFromFormulaString(form, s, true)); + } + } + + // Handle PtgFuncVar-specifics such as number of parameters and add-in PtgNameX record + if (funcPtg instanceof PtgFuncVar) { + if (((PtgFuncVar) funcPtg).getVal() == FunctionConstants.xlfADDIN) { + // if an add-in, must add PtgNameX to stack + PtgNameX pn = new PtgNameX(); + pn.setParentRec(form); + pn.setName(func); + returnStack.add(0, pn); // add to bottom of stack + funcLen++; + funcPtg.setParentRec(form); // nec. to resolve external name + } + ((PtgFuncVar) funcPtg).setNumParams((byte) funcLen); + } + returnStack.push(funcPtg); + return returnStack; + } + + /** + * combine two stacks of Ptgs, popping the operator of the sourceStack and + * adding it to the end of the destination stack to + * ensure it is in the correct order in the destination stack + * + * @param sourceStack + * @param destStack + * / + private static Stack addPtgStacks(Stack sourceStack, Stack destStack) { + Ptg opPtg = (Ptg) sourceStack.pop(); + Ptg lastOp= (destStack.isEmpty()?null:((Ptg)destStack.peek())); + + // handle precedence: unaries before ^(power) before *, / before +, - before &(concat), before comparisons (=, <>, <=, >=, <, >) + int group1=rankPrecedence(opPtg); + int group2=rankPrecedence(lastOp); + + if (group1>=0 && (group1 < group2 || group2==-1)) { + while (opPtg.getIsOperator()) { + lastOp= opPtg; + destStack.push(opPtg); + if (!sourceStack.isEmpty()) { + opPtg = (Ptg) sourceStack.pop(); + // handle precedence + group1=rankPrecedence(opPtg); + group2=rankPrecedence(lastOp); + if (!(group1>=0 && (group1 < group2 || group2==-1))) + break; + }else + return destStack; + } + } + sourceStack.push(opPtg); + + // after sorting out operators, assemble two stacks into one + Stack nwstack = destStack; + destStack = new Stack(); + destStack.addAll(sourceStack); + destStack.addAll(nwstack); + + return destStack; + } + */ + + /** + * rank a Ptg Operator's precedence (lower + * + * @param curOp + * @return + */ + static int rankPrecedence(Ptg curOp) { + if (curOp == null) return -1; // if (curOp==null || !curOp.getIsOperator()) return -1; - if (curOp instanceof PtgUMinus || curOp instanceof PtgUPlus) - return 7; - if (curOp instanceof PtgPercent) - return 6; - if (curOp instanceof PtgPower) - return 5; - if (curOp instanceof PtgMlt || curOp instanceof PtgDiv) - return 4; - else if (curOp instanceof PtgAdd || curOp instanceof PtgSub) - return 3; - else if (curOp instanceof PtgConcat) - return 2; - else if (curOp instanceof PtgEQ || curOp instanceof PtgNE || - curOp instanceof PtgLE || curOp instanceof PtgLT || - curOp instanceof PtgGE || curOp instanceof PtgGT) - return 1; + if (curOp instanceof PtgUMinus || curOp instanceof PtgUPlus) + return 7; + if (curOp instanceof PtgPercent) + return 6; + if (curOp instanceof PtgPower) + return 5; + if (curOp instanceof PtgMlt || curOp instanceof PtgDiv) + return 4; + else if (curOp instanceof PtgAdd || curOp instanceof PtgSub) + return 3; + else if (curOp instanceof PtgConcat) + return 2; + else if (curOp instanceof PtgEQ || curOp instanceof PtgNE || + curOp instanceof PtgLE || curOp instanceof PtgLT || + curOp instanceof PtgGE || curOp instanceof PtgGT) + return 1; // else if (curOp instanceof PtgParen) // return 0; - return -1; - } - - /* - * getMatchOperator takes a string and starting operator location. - * It then parses the string and determines which closing operator - * matches the opening parens specified by startParenLoc. Returns - * -1 if it cannot find a match. - */ - public static int getMatchOperator(String input, int startParenLoc, char matchOpenChar, char matchCloseChar){ - // 20081112 KSC: do a different way as it wasn't working for all cases - int openCnt= 0; - for (int i= startParenLoc; i < input.length(); i++) { - if (input.charAt(i)=='"' || input.charAt(i)=='\'') {// handle quoted strings within input (quoted strings may of course contain match chars ... - char endquote= input.charAt(i); - while (++i < input.length()) { - if (input.charAt(i)==endquote) { - break; - } - } - } - if (i== input.length()) return i-1; - if (input.charAt(i)==matchOpenChar) - openCnt++; - else if (input.charAt(i)==matchCloseChar) { - openCnt--; - if (openCnt==0) - return i; - } - } - - // no parens for you! - return -1; - } - - /** - * Looks up a function string and returns a funcPtg if it is found - * - * @param func function string without parents i.e. SUM or DB - * @returns Ptg valid funcPtg or null if not found - */ - // 20090210 KSC: add form so can set parent record for PtgFunc and PtgFuncVar - nec for self-referential formulas such as COLUMN - private static Ptg getFuncPtg(String func, XLSRecord form){ - Ptg funcPtg = null; - // if (true) { - if (Locale.JAPAN.equals(Locale.getDefault())) { - for (int y=0; yPtgMemFunc - } - if (bUnary) { - // unary ops +, - ... have a diff't Ptg than regular vers of the operator - if(ptgOpStr.equals("-")&&x==1&&ptgOpStr.length()>1)break; //negative number, NOT a unary - - for (int j= 0; j < XLSRecordFactory.ptgPrefixOperators.length; j++) { - if (ptgOpStr.startsWith(XLSRecordFactory.ptgPrefixOperators[j][0].toString())) { - ptgOpStr= XLSRecordFactory.ptgPrefixOperators[j][1].toString(); - } - } - } - return XLSRecordFactory.ptgOps[i][1]; - } - } - return s; - } - /** - * parseFinalLevel is where strings get converted into ptg's - * This method can handle multiple ptg's within a string, but cannot handle - * recursion. If you are having recursion problems look into getPtgsFromFormulaString above. - * - * This method should be called from the final level of parsing. - * There should not be additional sub-expressions at this point. - * Example (1+2) or (3,4,5) - * NOT ((1<2),3,4) or TAN(23); - * - * - * TODO: HANDLE these references: - * - * =SUM(table[[#This Row];['#Head3]:[Calced]]) - * - * table[['#Head3]:[Calced]] - * - * I assume this means a table of data, the Head3 table? and the calced column? - * - * - * - * - */ - private static Stack parseFinalLevel(XLSRecord form, String fmla, boolean bIsComplete){ - Stack returnStack= new Stack(); - CompatibleVector parseThings = new CompatibleVector(); - - // break it up into components first - Vector elements = new Vector(); - elements = splitString(fmla, bIsComplete); - - WorkBook bk= form.getWorkBook(); // nec. to determine if parsed element is a valid name handle name - - // convert each element into Ptg's - // each element at this point should be a named operand, or an unidentified operator - for (int x = 0;xPtgMemFunc + } + if (bUnary) { + // unary ops +, - ... have a diff't Ptg than regular vers of the operator + if (ptgOpStr.equals("-") && x == 1 && ptgOpStr.length() > 1) break; //negative number, NOT a unary - + for (int j = 0; j < XLSRecordFactory.ptgPrefixOperators.length; j++) { + if (ptgOpStr.startsWith(XLSRecordFactory.ptgPrefixOperators[j][0].toString())) { + ptgOpStr = XLSRecordFactory.ptgPrefixOperators[j][1].toString(); + } + } + } + return XLSRecordFactory.ptgOps[i][1]; + } + } + return s; + } + + /** + * parseFinalLevel is where strings get converted into ptg's + * This method can handle multiple ptg's within a string, but cannot handle + * recursion. If you are having recursion problems look into getPtgsFromFormulaString above. + *

                + * This method should be called from the final level of parsing. + * There should not be additional sub-expressions at this point. + * Example (1+2) or (3,4,5) + * NOT ((1<2),3,4) or TAN(23); + *

                + *

                + * TODO: HANDLE these references: + *

                + * =SUM(table[[#This Row];['#Head3]:[Calced]]) + *

                + * table[['#Head3]:[Calced]] + *

                + * I assume this means a table of data, the Head3 table? and the calced column? + */ + private static Stack parseFinalLevel(XLSRecord form, String fmla, boolean bIsComplete) { + Stack returnStack = new Stack(); + CompatibleVector parseThings = new CompatibleVector(); + + // break it up into components first + Vector elements = new Vector(); + elements = splitString(fmla, bIsComplete); + + WorkBook bk = form.getWorkBook(); // nec. to determine if parsed element is a valid name handle name + + // convert each element into Ptg's + // each element at this point should be a named operand, or an unidentified operator + for (int x = 0; x < elements.size(); x++) { + String val = (String) elements.elementAt(x); + String name = convertString(val, bk); + + Ptg pthing = null; + try { + pthing = XLSRecordFactory.getPtgRecord(name); + if (pthing == null && name.equals("PtgName")) + // TODO: MUST evaluate which type of PtgName is correct: understand usage! + if (form.getOpcode() == XLSConstants.FORMULA || form.getOpcode() == XLSConstants.ARRAY) + pthing = new PtgName(0x43); // assume this token to be of type Value (i.e PtgNameV) instead of Reference (PtgNameR) + else // DV needs ref-type name + pthing = new PtgName(0x23); // PtgNameR + } catch (InvalidRecordException e) { + Logger.logInfo("parsing formula string. Invalid Ptg: " + name + " error: " + e); + } + // if it is an operator we don't need to do anything with it! + if (pthing != null) { + pthing.setParentRec(form); + + if (!pthing.getIsOperator()) { + if (pthing.getIsReference()) { + // createPtgRefFromString will handle any type of string reference + // will return a PtgRefErr if cannot parse location + pthing = PtgRef.createPtgRefFromString(val, form); + } else if (pthing instanceof PtgStr) { + PtgStr pstr = (PtgStr) pthing; + val = StringTool.strip(val, '\"'); + pstr.setVal(val); + } else if (pthing instanceof PtgNumber) { + PtgNumber pnum = (PtgNumber) pthing; + if (val.indexOf("%") == -1) + pnum.setVal((new Double(val).doubleValue())); + else + pnum.setVal(val); + } else if (pthing instanceof PtgInt) { + PtgInt pint = (PtgInt) pthing; + pint.setVal((Integer.valueOf(val).intValue())); + } else if (pthing instanceof PtgBool) { + PtgBool pbool = (PtgBool) pthing; + pbool.setVal(Boolean.valueOf(val).booleanValue()); + } else if (pthing instanceof PtgArray) { + PtgArray parr = (PtgArray) pthing; + parr.setVal(val); + } else if (pthing instanceof PtgName) { // SHOULD really return PtgErr("#NAME!") as it's a missing Name instead of adding a new name + PtgName pname = (PtgName) pthing; + pname.setName(val); + } else if (pthing instanceof PtgNameX) { + PtgNameX pnameX = (PtgNameX) pthing; + pnameX.setName(val); + } else if (pthing instanceof PtgMissArg) { + ((PtgMissArg) pthing).init(new byte[]{22}); + } else if (pthing instanceof PtgErr) { + pthing = new PtgErr(PtgErr.convertStringToLookupByte(val)); + } + } + parseThings.add(pthing); + } else { + PtgMissArg pname = new PtgMissArg(); + } + + } + //reorder in polish notation and add to stack. + // 20081128 KSC: Do later as reordering will depend upon position of this segment in formula returnStack = reorderStack(parseThings); see getPtgsFromFormulaString + returnStack = convertToStack(parseThings); + return returnStack; + } /* private static Stack reorderStack(Stack sourceStack, boolean bIsComplete) { @@ -904,184 +898,186 @@ private static Stack reorderStack(Stack sourceStack, boolean bIsComplete) { return returnStack; } */ - - + + /** * convert list of parseThings without reordering + * * @param parseThings * @return */ private static Stack convertToStack(CompatibleVector parseThings) { - Stack returnStack = new Stack(); - Stack pOperators= new Stack(); - for (int x = 0;x and >; - badLocs.add(Integer.valueOf(y)); - } - if (end==0){ // means it didn't find an end quote - end= formStr.length()-1; - loop = false; - }else{ - pos = end; - loop= true; - } - }else{ - loop = false; - } - } - - - if(formStr.indexOf(",") == -1){ - locs.add(formStr); - }else{ - // Handle each parameter (delimited by ,) - // fill the badLocs vector with string locations we should disregard for comma proccesing - for (int i = 0; i-1) { // found instance of an operator - // if encounter a parenthesis, must determine if it is an expression limit OR - // if it is part of a complex range, in which case the expression must be kept together - if (ptgOpStr.equals("(")) { - int end = getMatchOperator(s, x, '(', ')'); - ret.add(s); // add entire - break; + /* + * Parses an internal string for a function, splitting out elements. + * for instance,(1<2), 3, tan(5); should return + * [(1<2)][3][tan(5)]. Currently just working off of commas, but this may change... + * + * One of the keys here is to not split on a comma from an internal function, for instance, + * "IF((1<2),MOD(45,6),0) should not split between 45 & 6! Note the badLocs vector that handles this. + */ + private static CompatibleVector splitFunctionOperands(String formStr) { + CompatibleVector locs = new CompatibleVector(); + // if there are no commas then we don't have to do all of this... + if (formStr.equals("")) return locs; // KSC: Handle no parameters by returning a null vector + + // first handle quoted strings 20081111 KSC + boolean loop = true; + int pos = 0; + CompatibleVector badLocs = new CompatibleVector(); + while (loop) { + char c = '"'; + int start = formStr.indexOf(c, pos); + if (start == -1) { // process single quotes as well + c = '\''; + start = formStr.indexOf(c, pos); + } + if (start != -1) { + int end = formStr.indexOf(c, start + 1); + end += 1; //include trailing quote + // check for being a part of a reference ... + if (end < formStr.length() && formStr.charAt(end) == '!') {// then it's part of a reference + end++; + while (end < formStr.length() && loop) { + c = formStr.charAt(end); + if (!(Character.isLetterOrDigit(c) || c == ':' || c == '$') || c == '-' || c == '+') + loop = false; + else + end++; + } + } + for (int y = start; y < end; y++) { + //make sure it is not a segment of a previous operand, like <> and >; + badLocs.add(Integer.valueOf(y)); + } + if (end == 0) { // means it didn't find an end quote + end = formStr.length() - 1; + loop = false; + } else { + pos = end; + loop = true; + } + } else { + loop = false; + } + } + + + if (formStr.indexOf(",") == -1) { + locs.add(formStr); + } else { + // Handle each parameter (delimited by ,) + // fill the badLocs vector with string locations we should disregard for comma proccesing + for (int i = 0; i < formStr.length(); i++) { + int openparen = formStr.indexOf("(", i); + if (openparen != -1) { + if (!badLocs.contains(Integer.valueOf(openparen))) { + int closeparen = getMatchOperator(formStr, openparen, '(', ')'); + if (closeparen == -1) closeparen = formStr.length(); + for (i = openparen; i < closeparen; i++) { + Integer in = Integer.valueOf(i); + badLocs.add(in); + } + } else { // open paren nested in quoted string + i = openparen + 1; + } + } else // 20081112 KSC + break; + } + // lets do the same for the array items + for (int i = 0; i < formStr.length(); i++) { + int openparen = formStr.indexOf("{", i); + if (openparen != -1) { + if (!badLocs.contains(Integer.valueOf(openparen))) { + int closeparen = getMatchOperator(formStr, openparen, '{', '}'); + if (closeparen == -1) closeparen = formStr.length(); + for (i = openparen; i < closeparen; i++) { + Integer in = Integer.valueOf(i); + badLocs.add(in); + } + } else { // open paren nested in quoted string + i = openparen + 1; + } + } else // 20081112 KSC + break; + } + // now check bad locations: + int placeholder = 0; + int holder = 0; + while (holder != -1) { + int i = formStr.indexOf(",", holder); + if (i != -1) { + Integer ing = Integer.valueOf(i); + if (!badLocs.contains(ing)) { + String s = formStr.substring(placeholder, i); + locs.add(s); + placeholder = i + 1; + } + holder = i + 1; + } else { + String s = formStr.substring(placeholder); + locs.add(s); + return locs; + } + } + } + return locs; + } + + /** + * parse a given string into known Ptg operators + * + * @param s + * @return + */ + private static CompatibleVector parsePtgOperators(String s, boolean bUnary) { + CompatibleVector ret = new CompatibleVector(); + s = StringTool.allTrim(s); + + for (int i = 0; i < XLSRecordFactory.ptgOps.length; i++) { + String ptgOpStr = XLSRecordFactory.ptgOps[i][0]; + if (s.startsWith("\"") || s.startsWith("'")) { + int end = s.substring(1).indexOf(s.charAt(0)); + end += 1; //include trailing quote + // TEST IF The quoted item is a sheet name + if (end < s.length() && s.charAt(end) == '!') {// then it's part of a reference + end++; + boolean loop = true; + while (end < s.length() && loop) { // if the quoted string is a sheet ref, get rest of reference + char c = s.charAt(end); + if (c == '#' && s.endsWith("#REF!")) { + end += 5; + loop = false; + } else if (!(Character.isLetterOrDigit(c) || c == ':' || c == '$') || c == '-' || c == '+') { + loop = false; + } else + end++; + } + } + ret.add(s.substring(0, end + 1)); + s = s.substring(end + 1); + bUnary = false; + if (!s.equals("")) + ret.addAll(parsePtgOperators(s, bUnary)); + break; + } + + int x = s.indexOf(ptgOpStr); + if (x > -1) { // found instance of an operator + // if encounter a parenthesis, must determine if it is an expression limit OR + // if it is part of a complex range, in which case the expression must be kept together + if (ptgOpStr.equals("(")) { + int end = getMatchOperator(s, x, '(', ')'); + ret.add(s); // add entire + break; /* String ss= s.substring(x, end+1); ret.add(ss.substring(x)); // add entire if (FormulaParser.isComplexRange(ss)) { @@ -1107,276 +1103,280 @@ private static CompatibleVector parsePtgOperators(String s, boolean bUnary) { if (nextChar==' ' || nextChar==',' || nextChar==':' || nextChar==')') continue; // keep complex range expression together } catch (Exception e) { ; } -*/ - } - if (ptgOpStr.equals(")")) // parens are there to keep expression together - continue; - if (x > 0) {// process prefix, if any - unary since it's the first operand - // exception here-- error range in the form of "Sheet!#REF! (eg) needs to be kept whole - if (!(XLSRecordFactory.ptgLookup[i][1].equals("PtgErr") && s.charAt(x-1)=='!')) { - ret.addAll(parsePtgOperators(s.substring(0, x), bUnary)); - bUnary= false; - } - else { // keep entire error reference together - ptgOpStr= s; - } - } - x= x+ ptgOpStr.length(); - if (bUnary) { - // unary ops +, - ... have a diff't Ptg than regular vers of the operator - if(ptgOpStr.equals("-")&&x==1&&ptgOpStr.length()>1)break; //negative number, NOT a unary - - for (int j= 0; j < XLSRecordFactory.ptgPrefixOperators.length; j++) { - if (ptgOpStr.startsWith(XLSRecordFactory.ptgPrefixOperators[j][0].toString())) { - ptgOpStr= XLSRecordFactory.ptgPrefixOperators[j][1].toString(); - } - } - } - ret.add(ptgOpStr); - if (x < s.length()) // process suffix, if any - ret.addAll(parsePtgOperators(s.substring(x), true)); - break; - } - } - if (ret.isEmpty()) - ret.add(s); - return ret; - } - +*/ + } + if (ptgOpStr.equals(")")) // parens are there to keep expression together + continue; + if (x > 0) {// process prefix, if any - unary since it's the first operand + // exception here-- error range in the form of "Sheet!#REF! (eg) needs to be kept whole + if (!(XLSRecordFactory.ptgLookup[i][1].equals("PtgErr") && s.charAt(x - 1) == '!')) { + ret.addAll(parsePtgOperators(s.substring(0, x), bUnary)); + bUnary = false; + } else { // keep entire error reference together + ptgOpStr = s; + } + } + x = x + ptgOpStr.length(); + if (bUnary) { + // unary ops +, - ... have a diff't Ptg than regular vers of the operator + if (ptgOpStr.equals("-") && x == 1 && ptgOpStr.length() > 1) break; //negative number, NOT a unary - + for (int j = 0; j < XLSRecordFactory.ptgPrefixOperators.length; j++) { + if (ptgOpStr.startsWith(XLSRecordFactory.ptgPrefixOperators[j][0].toString())) { + ptgOpStr = XLSRecordFactory.ptgPrefixOperators[j][1].toString(); + } + } + } + ret.add(ptgOpStr); + if (x < s.length()) // process suffix, if any + ret.addAll(parsePtgOperators(s.substring(x), true)); + break; + } + } + if (ret.isEmpty()) + ret.add(s); + return ret; + } + /* * Parses a string and returns an array based on contents - * Assumed to be 1 "final-level" operand i.e a range, complex range, a op b[...] + * Assumed to be 1 "final-level" operand i.e a range, complex range, a op b[...] */ - private static CompatibleVector splitString(String formStr, boolean bIsComplete){ - // Use a vector, and the collections methods to sort in natural order - CompatibleVector locs = new CompatibleVector(); - CompatibleVector retVect = new CompatibleVector(); - - // check for escaped string literals & add positions to vector if needed - formStr= StringTool.allTrim(formStr); - if (formStr.equals("")) { - retVect.add(formStr); - return retVect; - } - if (true) { - retVect.addAll(parsePtgOperators(formStr, bIsComplete)); // cleanString if not an array formula???? s= cleanString(s); - bIsComplete= false; - return retVect; - } - - // 20081207 KSC: redo completely to handle complex formula strings e.g. strings containing quoted commas, parens ... - // first, pre-process to parse quoted strings, parentheses and array formulas - boolean isArray= false; - boolean loop = true; - String s= ""; - boolean inRange= false; - char prevc= 0; - for (int i= 0; i < formStr.length(); i++) { - char c= formStr.charAt(i); - if (c=='"' || c=='\'') { + private static CompatibleVector splitString(String formStr, boolean bIsComplete) { + // Use a vector, and the collections methods to sort in natural order + CompatibleVector locs = new CompatibleVector(); + CompatibleVector retVect = new CompatibleVector(); + + // check for escaped string literals & add positions to vector if needed + formStr = StringTool.allTrim(formStr); + if (formStr.equals("")) { + retVect.add(formStr); + return retVect; + } + if (true) { + retVect.addAll(parsePtgOperators(formStr, bIsComplete)); // cleanString if not an array formula???? s= cleanString(s); + bIsComplete = false; + return retVect; + } + + // 20081207 KSC: redo completely to handle complex formula strings e.g. strings containing quoted commas, parens ... + // first, pre-process to parse quoted strings, parentheses and array formulas + boolean isArray = false; + boolean loop = true; + String s = ""; + boolean inRange = false; + char prevc = 0; + for (int i = 0; i < formStr.length(); i++) { + char c = formStr.charAt(i); + if (c == '"' || c == '\'') { /* if (!s.equals("")) { locs.add(s); s= ""; } -*/ - int end = formStr.indexOf(c, i+1); - end += 1; //include trailing quote - // TEST IF The quoted item is a sheet name - if (end < formStr.length() && formStr.charAt(end)=='!') {// then it's part of a reference - end++; - loop= true; - while (end < formStr.length() && loop) { // if the quoted string is a sheet ref, get rest of reference - c= formStr.charAt(end); - if (c=='#' && formStr.endsWith("#REF!")) { - end+=5; - loop= false; - } else if (!(Character.isLetterOrDigit(c) || c==':' || c=='$') || c=='-' || c=='+') { - loop= false; - } else - end++; - } - } - locs.add(s+formStr.substring(i, end)); - s+= formStr.substring(i, end); - i= end-1; - } else if (c=='(') { // may be a complex range if s=="" - if (!s.equals("") && !inRange) { +*/ + int end = formStr.indexOf(c, i + 1); + end += 1; //include trailing quote + // TEST IF The quoted item is a sheet name + if (end < formStr.length() && formStr.charAt(end) == '!') {// then it's part of a reference + end++; + loop = true; + while (end < formStr.length() && loop) { // if the quoted string is a sheet ref, get rest of reference + c = formStr.charAt(end); + if (c == '#' && formStr.endsWith("#REF!")) { + end += 5; + loop = false; + } else if (!(Character.isLetterOrDigit(c) || c == ':' || c == '$') || c == '-' || c == '+') { + loop = false; + } else + end++; + } + } + locs.add(s + formStr.substring(i, end)); + s += formStr.substring(i, end); + i = end - 1; + } else if (c == '(') { // may be a complex range if s=="" + if (!s.equals("") && !inRange) { // char prevc= s.charAt(s.length()-1); - if (!(prevc==' ' || prevc==':' || prevc==',' || prevc=='(')) { - locs.add(s); - s= ""; - } else { // DO NOT split apart complex ranges - they parse to PtgMemFuncs - //Logger.logInfo("FormulaParser.splitString: PtgMemFunc" + formStr); - s+= c; - inRange= true; - } - } - } else if (c==':') { - if (prevc==')' && locs.size()>0) // complex range in style of: F(x):Y(x) - s= (String) locs.get(locs.size()-1) + '(' + s; - inRange= true; - s+= c; - } else if (c=='{') { - if (!s.equals("")) { - locs.add(s); - s= ""; - } - int end = formStr.indexOf("}", i+1); - end += 1; //include trailing } - locs.add(formStr.substring(i, end)); - i= end-1; - } else - s+= c; - if (c!=' ') - prevc= c; - } - if (!s.equals("")) { - locs.add(s); - s= ""; - } - - // loop through the possible operator ptg's and get locations & length of them - for (int j= 0; j < locs.size(); j++) { - s= (String) locs.get(j); - if (s.startsWith("\"") || s.startsWith("'")) - retVect.add(s); // quoted strings - else { - if (s.startsWith("{")) // it's an array formula - isArray= true; // Do what?? else, cleanString?? - retVect.addAll(parsePtgOperators(s, bIsComplete)); // cleanString if not an array formula???? s= cleanString(s); - } - bIsComplete= false; // already parsed part of the formula string so cannot be unary :) - } - return retVect; + if (!(prevc == ' ' || prevc == ':' || prevc == ',' || prevc == '(')) { + locs.add(s); + s = ""; + } else { // DO NOT split apart complex ranges - they parse to PtgMemFuncs + //Logger.logInfo("FormulaParser.splitString: PtgMemFunc" + formStr); + s += c; + inRange = true; + } + } + } else if (c == ':') { + if (prevc == ')' && locs.size() > 0) // complex range in style of: F(x):Y(x) + s = (String) locs.get(locs.size() - 1) + '(' + s; + inRange = true; + s += c; + } else if (c == '{') { + if (!s.equals("")) { + locs.add(s); + s = ""; + } + int end = formStr.indexOf("}", i + 1); + end += 1; //include trailing } + locs.add(formStr.substring(i, end)); + i = end - 1; + } else + s += c; + if (c != ' ') + prevc = c; + } + if (!s.equals("")) { + locs.add(s); + s = ""; + } + + // loop through the possible operator ptg's and get locations & length of them + for (int j = 0; j < locs.size(); j++) { + s = (String) locs.get(j); + if (s.startsWith("\"") || s.startsWith("'")) + retVect.add(s); // quoted strings + else { + if (s.startsWith("{")) // it's an array formula + isArray = true; // Do what?? else, cleanString?? + retVect.addAll(parsePtgOperators(s, bIsComplete)); // cleanString if not an array formula???? s= cleanString(s); + } + bIsComplete = false; // already parsed part of the formula string so cannot be unary :) + } + return retVect; } - - /** - * helper method that turns operator & operand strings into the Ptg\ equivalent - * if there is no equivalant it leaves the string alone. - */ - private static String convertString(String ptg, WorkBook bk){ - // first check for operators - for(int i = 0;i= 0 && i.intValue()<=65535) // PtgInts are UNSIGNED + <=65535 - return "PtgInt"; - else - return "PtgNumber"; - }catch(NumberFormatException e){} - } - if (ptg.indexOf("%")== ptg.length()-1) { // see if it's a percentage - try { - Double d= new Double(ptg.substring(0, ptg.indexOf("%"))); - return "PtgNumber"; - } catch (NumberFormatException e) { } - } - // see if it is a Number - try{ - Double d = new Double(ptg); - return "PtgNumber"; - }catch(NumberFormatException e){} - - - // at this point it is probably some sort of ptgref - if (ptg.indexOf(":")!=-1 || ptg.indexOf(',')!= -1 || ptg.indexOf("!")!=-1){ - // ptgarea or ptgarea3d or ptgmemfunc - return "PtgArea"; // in ParseFinalLevel, PtgRef.createPtgRefFromString will handle all types of string refs - } - - - // maybe it is a garbage string, or a reference to a name (unsupported right now....) - // check if the last character is a number. If not, it sure isn't a reference, no? - // NO! Can have named ranges with numbers at the end -- better to try to parse it - try { - if (bk.getName(ptg)==null) {// it's not a named range - ExcelTools.getRowColFromString(ptg); // if passes it's a PtgRef - return "PtgRef"; - } else - return "PtgName"; - } catch (IllegalArgumentException e) { - return "PtgName"; - } - } - + + /** + * helper method that turns operator & operand strings into the Ptg\ equivalent + * if there is no equivalant it leaves the string alone. + */ + private static String convertString(String ptg, WorkBook bk) { + // first check for operators + for (int i = 0; i < XLSRecordFactory.ptgLookup.length; i++) { + if (ptg.equalsIgnoreCase(XLSRecordFactory.ptgLookup[i][0])) { + return ptg; + } + } + + // KSC: Added for missing arguments ("") + if (StringTool.allTrim(ptg).equals("")) + //return "PtgMissArg"; + //if (ptg.equals("")) + return "PtgAtr"; // a space + + // Now we need to figure out what type of operand it is + // see if it is a string, should be encased by "" + if (ptg.substring(0, 1).equalsIgnoreCase("\"")) { + return "PtgStr"; + } + // is it an array? + if (ptg.substring(0, 1).equalsIgnoreCase("{")) { + return "PtgArray"; + } + // see if it is an integer + if (ptg.indexOf(".") == -1) { + try { + Integer i = Integer.valueOf(ptg); + if (i.intValue() >= 0 && i.intValue() <= 65535) // PtgInts are UNSIGNED + <=65535 + return "PtgInt"; + else + return "PtgNumber"; + } catch (NumberFormatException e) { + } + } + if (ptg.indexOf("%") == ptg.length() - 1) { // see if it's a percentage + try { + Double d = new Double(ptg.substring(0, ptg.indexOf("%"))); + return "PtgNumber"; + } catch (NumberFormatException e) { + } + } + // see if it is a Number + try { + Double d = new Double(ptg); + return "PtgNumber"; + } catch (NumberFormatException e) { + } + + + // at this point it is probably some sort of ptgref + if (ptg.indexOf(":") != -1 || ptg.indexOf(',') != -1 || ptg.indexOf("!") != -1) { + // ptgarea or ptgarea3d or ptgmemfunc + return "PtgArea"; // in ParseFinalLevel, PtgRef.createPtgRefFromString will handle all types of string refs + } + + + // maybe it is a garbage string, or a reference to a name (unsupported right now....) + // check if the last character is a number. If not, it sure isn't a reference, no? + // NO! Can have named ranges with numbers at the end -- better to try to parse it + try { + if (bk.getName(ptg) == null) {// it's not a named range + ExcelTools.getRowColFromString(ptg); // if passes it's a PtgRef + return "PtgRef"; + } else + return "PtgName"; + } catch (IllegalArgumentException e) { + return "PtgName"; + } + } + /* * helper method that cleans out unneccesary parts of the formula string. */ - private static String cleanString(String dirtystring){ - String cleanstring = StringTool.allTrim(dirtystring); - cleanstring = StringTool.strip(cleanstring, "("); - cleanstring = StringTool.strip(cleanstring, ","); - return cleanstring; + private static String cleanString(String dirtystring) { + String cleanstring = StringTool.allTrim(dirtystring); + cleanstring = StringTool.strip(cleanstring, "("); + cleanstring = StringTool.strip(cleanstring, ","); + return cleanstring; } - protected static Stack getPtgsFromFormulaString(String fmla){ + protected static Stack getPtgsFromFormulaString(String fmla) { return null; } - + /** * parse a formula in string form and create a formula record from it * caluclate the new formula based on boolean setting calculate - * @param form Formula rec - * @param fmla String formula either =EXPRESSION or {=EXPRESSION} for array formulas - * @param calculate boolean truth of "calculate formula after setting" + * + * @param form Formula rec + * @param fmla String formula either =EXPRESSION or {=EXPRESSION} for array formulas + * @param calculate boolean truth of "calculate formula after setting" * @return Formula rec */ public static Formula setFormula(Formula form, String fmla, int[] rc) { - if (fmla.charAt(0)!='{') { - try { - Stack newptgs = FormulaParser.getPtgsFromFormulaString(form, fmla); - FormulaParser.adjustParameterIds(newptgs); // 20100614 KSC: adjust function parameter id's, if necessary, for Value, Array or Reference type - form.setExpression(newptgs); - } catch (FunctionNotSupportedException e) { // 200902 KSC: still add record if function is not found (using N/A in place of said function) - Logger.logErr("Adding new Formula at " + form.getSheet()+ "!" + ExcelTools.formatLocation(rc) + " failed: " + e.toString() + "."); - Stack newptgs = new Stack(); - newptgs.push(new PtgErr(PtgErr.ERROR_NA)); - form.setExpression(newptgs); - } - } else { // Handle Array Formulas - PtgExp pe= new PtgExp(); - pe.setParentRec(form); - // rowcol reference is from PARENT PtgExp not (necessarily) this formula's cell address - // [BugTracker 2683 + OOXML Array Formulas] - Object o= form.getSheet().getArrayFormulaParent(rc); - if (o!=null) // there is a parent array formula; use it's rowcol - rc= (int[]) o; - else { // no parent yet- add - String addr=ExcelTools.formatLocation(rc); - form.getSheet().addParentArrayRef(addr, addr); - } - pe.init(rc[0], rc[1]); - Stack e= new Stack(); e.push(pe); - FormulaParser.adjustParameterIds(e); // adjust function parameter id's, if necessary, for Value, Array or Reference type - form.setExpression(e); // add PtgExp to Formula Stack - Array a= new Array(); // Create new Array Record - a.setSheet(form.getSheet()); - a.setWorkBook(form.getWorkBook()); - a.init(fmla, rc[0], rc[1]); // init Array record from Formula String - form.addInternalRecord(a); // link array record to parent formula - } + if (fmla.charAt(0) != '{') { + try { + Stack newptgs = FormulaParser.getPtgsFromFormulaString(form, fmla); + FormulaParser.adjustParameterIds(newptgs); // 20100614 KSC: adjust function parameter id's, if necessary, for Value, Array or Reference type + form.setExpression(newptgs); + } catch (FunctionNotSupportedException e) { // 200902 KSC: still add record if function is not found (using N/A in place of said function) + Logger.logErr("Adding new Formula at " + form.getSheet() + "!" + ExcelTools.formatLocation(rc) + " failed: " + e.toString() + "."); + Stack newptgs = new Stack(); + newptgs.push(new PtgErr(PtgErr.ERROR_NA)); + form.setExpression(newptgs); + } + } else { // Handle Array Formulas + PtgExp pe = new PtgExp(); + pe.setParentRec(form); + // rowcol reference is from PARENT PtgExp not (necessarily) this formula's cell address + // [BugTracker 2683 + OOXML Array Formulas] + Object o = form.getSheet().getArrayFormulaParent(rc); + if (o != null) // there is a parent array formula; use it's rowcol + rc = (int[]) o; + else { // no parent yet- add + String addr = ExcelTools.formatLocation(rc); + form.getSheet().addParentArrayRef(addr, addr); + } + pe.init(rc[0], rc[1]); + Stack e = new Stack(); + e.push(pe); + FormulaParser.adjustParameterIds(e); // adjust function parameter id's, if necessary, for Value, Array or Reference type + form.setExpression(e); // add PtgExp to Formula Stack + Array a = new Array(); // Create new Array Record + a.setSheet(form.getSheet()); + a.setWorkBook(form.getWorkBook()); + a.init(fmla, rc[0], rc[1]); // init Array record from Formula String + form.addInternalRecord(a); // link array record to parent formula + } /* is this calc necessary? Object val = null; @@ -1388,41 +1388,41 @@ public static Formula setFormula(Formula form, String fmla, int[] rc) { } if(DEBUGLEVEL > 0)Logger.logInfo("FormulaParser.setFormula() string:" +fmla + " value: " + val); */ - + return form; } - - public static String getFormulaString(Formula form){ - Stack expression = form.getExpression(); - return FormulaParser.getExpressionString(expression); + + public static String getFormulaString(Formula form) { + Stack expression = form.getExpression(); + return FormulaParser.getExpressionString(expression); } - - public static String getExpressionString(Stack expression){ - StringBuffer retval = new StringBuffer(""); + + public static String getExpressionString(Stack expression) { + StringBuffer retval = new StringBuffer(); int sz = expression.size(); Ptg[] stck = new Ptg[sz]; - stck = (Ptg[])expression.toArray(stck); + stck = (Ptg[]) expression.toArray(stck); Stack newstck = new Stack(); - for(int t=0;t * Reference-type ptg's contain an ID which indicates the type @@ -1432,238 +1432,252 @@ public static String getExpressionString(Stack expression){ * ptg reference-type parameters are assigned a default id, * but this id may not be correct for all functions. */ - public static void adjustParameterIds(Stack expression){ - StringBuffer retval = new StringBuffer(""); + public static void adjustParameterIds(Stack expression) { + StringBuffer retval = new StringBuffer(); int sz = expression.size(); Ptg[] stck = new Ptg[sz]; - stck = (Ptg[])expression.toArray(stck); + stck = (Ptg[]) expression.toArray(stck); Stack newstck = new Stack(); - for(int t=0;t0){ - // 20060128 - KSC: handle parens - Ptg[] vx = new Ptg[1]; // parens are unary ops so only 1 var allowed - vx[0] = (Ptg)params.pop(); - p.setVars(vx); - params.push(p); // put paren (with var) back on stack - }else{ // this paren wraps other parens... - params.push(p); - } + while (!newstck.isEmpty()) { + Ptg p = (Ptg) newstck.pop(); + int x = 0, t = 0;// cargs = p.getNumParams(); + if (p.getIsControl()) { + // do the parens thing here... + if (p.getOpcode() == 0x15) { // its a parens... and there is a val + if (t > 0) { + // 20060128 - KSC: handle parens + Ptg[] vx = new Ptg[1]; // parens are unary ops so only 1 var allowed + vx[0] = (Ptg) params.pop(); + p.setVars(vx); + params.push(p); // put paren (with var) back on stack + } else { // this paren wraps other parens... + params.push(p); + } + } + } else if (p.getIsOperator() || p.getIsFunction()) { + if (p.getIsBinaryOperator()) t = 2; + if (p.getIsUnaryOperator()) t = 1; + if (p.getIsStandAloneOperator()) t = 0; + if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62) { + t = p.getNumParams(); + }// it's a ptgfuncvar + if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61) { + t = p.getNumParams(); + }// it's a ptgFunc + + if (t > params.size()) { + t = params.size(); } - } else if(p.getIsOperator()||p.getIsFunction()){ - if (p.getIsBinaryOperator()) t=2; - if (p.getIsUnaryOperator()) t=1; - if (p.getIsStandAloneOperator()) t=0; - if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62){t=p.getNumParams();}// it's a ptgfuncvar - if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61){t=p.getNumParams();}// it's a ptgFunc - - if (t > params.size()) { - t= params.size(); - } - Ptg[] vx = new Ptg[t]; - while (t > 0) - vx[--t] = (Ptg)params.pop();// get'em - p.setVars(vx); // set'em - // here is where we adjust the ptg's of the func or funcvar parameters - if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62) /* it's a ptgfuncvar*/ - ((PtgFuncVar) p).adjustParameterIds(); - else if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61)/* it's a ptgFunc */ - ((PtgFunc) p).adjustParameterIds(); - params.push(p);// push it back on the stack - }else if(p.getIsOperand()){ - params.push(p); - } + Ptg[] vx = new Ptg[t]; + while (t > 0) + vx[--t] = (Ptg) params.pop();// get'em + p.setVars(vx); // set'em + // here is where we adjust the ptg's of the func or funcvar parameters + if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62) /* it's a ptgfuncvar*/ + ((PtgFuncVar) p).adjustParameterIds(); + else if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61)/* it's a ptgFunc */ + ((PtgFunc) p).adjustParameterIds(); + params.push(p);// push it back on the stack + } else if (p.getIsOperand()) { + params.push(p); + } } } - /** set up the Formula chain - - A5 - 10 - * - 3 - EXP( - + - SUM( - =SUM(A5*10+EXP(3)) - =SUM(EXP(A5*103)) - - A3 add to vals - E5 add to vals - + pop last2 vals add to vals - ( check last -- if oper add oper, else add last, add to vals - - A1:A2 at this point we should see: (A3+E5) push to vals - SUM( check last -- if oper add oper, else add last, add to vals - + pop last2 add to vals - SUM( check last -- if oper add oper, else add last, add to vals - =SUM((A3+E5)+SUM(A1:A2)) - =SUM((A3+E5)+SUM(A1:A2)) - - A1 add to vals - A2 add to vals - + pop last2 vals add to vals? - ( check last -- if oper add oper, else add last, add to vals - A3 add to vals - A4 add to vals - + check last -- if paren pop last2 vals add to vals? - ( check last -- if oper add oper, else add last, add to vals - / pop last2 vals add to vals - - =(A1+A2)/(A3+A4) - =(A1+A2)/(A3+A4) - - - PtgFunc taking 3 vals again can only have 1 - We know IF has 3 vals - Do we need logic which knows how many vars a Ptg takes? (we sure do... -nick) Might help. - - ---> WRITE CODE TO SWITCH ON NUMBER OF PARAMS. Should Fix. - - - =IF(SUM(CONCATENATE(SUM((EXP(C2,D4,4))*2SUM(A5,C7,A2)A1:A5))=1),SUM(3),SUM(22)) - =IF(CONCATENATE(C2,(D4+EXP(4))*2,SUM(A5,C7,A2),SUM(A1:A5))=1,3,22) - - - */ - static void handlePtg(Stack newstck, Stack vals){ - Ptg p = (Ptg)newstck.pop(); + /** + * set up the Formula chain + *

                + * A5 + * 10 + *

                + * 3 + * EXP( + * + + * SUM( + * =SUM(A5*10+EXP(3)) + * =SUM(EXP(A5*103)) + *

                + * A3 add to vals + * E5 add to vals + * + pop last2 vals add to vals + * ( check last -- if oper add oper, else add last, add to vals + *

                + * A1:A2 at this point we should see: (A3+E5) push to vals + * SUM( check last -- if oper add oper, else add last, add to vals + * + pop last2 add to vals + * SUM( check last -- if oper add oper, else add last, add to vals + * =SUM((A3+E5)+SUM(A1:A2)) + * =SUM((A3+E5)+SUM(A1:A2)) + *

                + * A1 add to vals + * A2 add to vals + * + pop last2 vals add to vals? + * ( check last -- if oper add oper, else add last, add to vals + * A3 add to vals + * A4 add to vals + * + check last -- if paren pop last2 vals add to vals? + * ( check last -- if oper add oper, else add last, add to vals + * / pop last2 vals add to vals + *

                + * =(A1+A2)/(A3+A4) + * =(A1+A2)/(A3+A4) + *

                + *

                + * PtgFunc taking 3 vals again can only have 1 + * We know IF has 3 vals + * Do we need logic which knows how many vars a Ptg takes? (we sure do... -nick) Might help. + *

                + * ---> WRITE CODE TO SWITCH ON NUMBER OF PARAMS. Should Fix. + *

                + *

                + * =IF(SUM(CONCATENATE(SUM((EXP(C2,D4,4))*2SUM(A5,C7,A2)A1:A5))=1),SUM(3),SUM(22)) + * =IF(CONCATENATE(C2,(D4+EXP(4))*2,SUM(A5,C7,A2),SUM(A1:A5))=1,3,22) + */ + static void handlePtg(Stack newstck, Stack vals) { + Ptg p = (Ptg) newstck.pop(); int x = 0, t = 0;// cargs = p.getNumParams(); - if(p.getIsOperator()||p.getIsControl()||p.getIsFunction()){ + if (p.getIsOperator() || p.getIsControl() || p.getIsFunction()) { t = vals.size(); //this is faulty logic. We don't care what is there, the operator should tell us. // do the parens thing here... - if(p.getIsControl() /* !vals.isEmpty()*/){ - if(p.getOpcode() == 0x15) { // its a parens... and there is a val - if(t>0){ - // 20060128 - KSC: handle parens - Ptg[] vx = new Ptg[1]; // parens are unary ops so only 1 var allowed - vx[0] = (Ptg)vals.pop(); - p.setVars(vx); - vals.push(p); // put paren (with var) back on stack - }else{ // this paren wraps other parens... - vals.push(p); - } + if (p.getIsControl() /* !vals.isEmpty()*/) { + if (p.getOpcode() == 0x15) { // its a parens... and there is a val + if (t > 0) { + // 20060128 - KSC: handle parens + Ptg[] vx = new Ptg[1]; // parens are unary ops so only 1 var allowed + vx[0] = (Ptg) vals.pop(); + p.setVars(vx); + vals.push(p); // put paren (with var) back on stack + } else { // this paren wraps other parens... + vals.push(p); + } return; } } - if(t>0){ + if (t > 0) { // make sure we have the correct amount popped back in.. - if (p.getIsBinaryOperator()) t=2; - if (p.getIsUnaryOperator()) t=1; - if (p.getIsStandAloneOperator()) t=0; - if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62){t=p.getNumParams();}// it's a ptgfuncvar! - if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61){t=p.getNumParams();}// it's a ptgFunc - - if (t > vals.size()) { - - // is this a real error? throw an exception? - if(DEBUGLEVEL > 0) - Logger.logWarn("FormulaParser.handlePtg: number of parameters " + t + " is greater than available " + vals.size()); - t= vals.size(); - } + if (p.getIsBinaryOperator()) t = 2; + if (p.getIsUnaryOperator()) t = 1; + if (p.getIsStandAloneOperator()) t = 0; + if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62) { + t = p.getNumParams(); + }// it's a ptgfuncvar! + if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61) { + t = p.getNumParams(); + }// it's a ptgFunc + + if (t > vals.size()) { + + // is this a real error? throw an exception? + if (DEBUGLEVEL > 0) + Logger.logWarn("FormulaParser.handlePtg: number of parameters " + t + " is greater than available " + vals.size()); + t = vals.size(); + } Ptg[] vx = new Ptg[t]; - while (t > 0) - vx[--t] = (Ptg)vals.pop();// get'em + while (t > 0) + vx[--t] = (Ptg) vals.pop();// get'em p.setVars(vx); // set'em } vals.push(p);// push it back on the stack - }else if(p.getIsOperand()){ + } else if (p.getIsOperand()) { vals.push(p); - }else if (p instanceof PtgAtr){ - // this is probably just a space at this point, don't output error message - }else{ - if(DEBUGLEVEL > -1)Logger.logInfo("FormulaParser Error - Ptg Type: "+ p.getOpcode() + " " + p.getString()); + } else if (p instanceof PtgAtr) { + // this is probably just a space at this point, don't output error message + } else { + if (DEBUGLEVEL > -1) + Logger.logInfo("FormulaParser Error - Ptg Type: " + p.getOpcode() + " " + p.getString()); } } - + /** - * create a new formula record at row column rc using formula string formStr - * @param formStr String + * create a new formula record at row column rc using formula string formStr + * + * @param formStr String * @param st - * @param rc int[] - * @return new Formula record - * @throws Exception + * @param rc int[] + * @throws Exception + * @return new Formula record + */ + public static Formula getFormulaFromString(String formStr, Boundsheet st, int[] rc) + throws Exception { + Formula f = new Formula(); + if (st != null) { + f.setSheet(st); + f.setWorkBook(st.getWorkBook()); + } + f.setData(new byte[6]); // necessary for setRowCol + f.setRowCol(rc); // do before calculateFormula as array formulas use rowcol 20090817 KSC: [BugTracker 2683 + OOXML Array Formulas] + f = FormulaParser.setFormula(f, formStr, rc); + + return f; + } + + /** + * create a new formula record at row column rc using formula string formStr + * + * @param formStr String + * @param rc int[] + * @throws Exception + * @return new Formula record */ - public static Formula getFormulaFromString(String formStr, Boundsheet st, int[] rc) - throws Exception{ - Formula f = new Formula(); - if(st!=null){ - f.setSheet(st); - f.setWorkBook(st.getWorkBook()); - } - f.setData(new byte[6]); // necessary for setRowCol - f.setRowCol(rc); // do before calculateFormula as array formulas use rowcol 20090817 KSC: [BugTracker 2683 + OOXML Array Formulas] - f = FormulaParser.setFormula(f, formStr, rc); - - return f; - } - - /** - * create a new formula record at row column rc using formula string formStr - * @param formStr String - * @param rc int[] - * @return new Formula record - * @throws Exception - */ public static Formula setFormulaString(String formStr, int[] rc) - throws /* 20070212 KSC: FunctionNotSupported*/Exception{ - Formula f = new Formula(); - f = FormulaParser.setFormula(f, formStr, rc); - return f; + throws /* 20070212 KSC: FunctionNotSupported*/Exception { + Formula f = new Formula(); + f = FormulaParser.setFormula(f, formStr, rc); + return f; } - - + + /** * returns true of string s is in the form of a basic reference e.g. A1 + * * @param s * @return */ public static boolean isRef(String s) { - if (s==null) return false; - String simpleOne= "(([ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})"; - return s.matches(simpleOne); + if (s == null) return false; + String simpleOne = "(([ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})"; + return s.matches(simpleOne); } + /** * returns true if the stirng in question is in the form of a range + * * @param s * @return */ public static boolean isRange(String s) { - if (s==null) return false; - String one= "(([(]*[ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+[)]*){1})"; - String aRange= one + "(:"+one + ")?"; - String rangeop= "([ ]*[: ,][ ]*)"; - String rangeMatchString= aRange + rangeop + aRange + "(" + rangeop + aRange + ")*"; - String simpleOne= "(([ ]*[']?([a-zA-Z0-9 ]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})"; - String simpleRangeMatchString= "(" + simpleOne + "[ ]*[:][ ]*" + simpleOne + ")"; - return (s.matches(rangeMatchString)); + if (s == null) return false; + String one = "(([(]*[ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+[)]*){1})"; + String aRange = one + "(:" + one + ")?"; + String rangeop = "([ ]*[: ,][ ]*)"; + String rangeMatchString = aRange + rangeop + aRange + "(" + rangeop + aRange + ")*"; + String simpleOne = "(([ ]*[']?([a-zA-Z0-9 ]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})"; + String simpleRangeMatchString = "(" + simpleOne + "[ ]*[:][ ]*" + simpleOne + ")"; + return (s.matches(rangeMatchString)); } /** * returns true if the string represents a complex range (i.e. one containing multiple range values separated by one or more of: , : or space + * * @param s * @return */ public static boolean isComplexRange(String s) { - String one= "(([(]*[ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+[)]*){1})"; - String aRange= one + "(:"+one + ")?"; - String rangeop= "([ ]*[: ,][ ]*)"; - String rangeMatchString= aRange + rangeop + aRange + "(" + rangeop + aRange + ")*"; - String simpleOne= "(([ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})"; - String simpleRangeMatchString= "(" + simpleOne + "[ ]*[:][ ]*" + simpleOne + ")"; - return (isRange(s)) && !(s.matches(simpleRangeMatchString)); + String one = "(([(]*[ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+[)]*){1})"; + String aRange = one + "(:" + one + ")?"; + String rangeop = "([ ]*[: ,][ ]*)"; + String rangeMatchString = aRange + rangeop + aRange + "(" + rangeop + aRange + ")*"; + String simpleOne = "(([ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})"; + String simpleRangeMatchString = "(" + simpleOne + "[ ]*[:][ ]*" + simpleOne + ")"; + return (isRange(s)) && !(s.matches(simpleRangeMatchString)); } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.java b/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.java index 69edf3b..eb93771 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.java +++ b/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,1428 +23,1873 @@ package io.starter.formats.XLS.formulas; /** - * Function Constants for all Formula Types (PtgFunc, PtgFuncVar - regular and PtgFuncVar - AddIns) - * Modifications: - * all xlfXXX constants were originally in FunctionHandler - * getFunctionString was orignally in PtgFuncVar - * FUNCTION_STRINGS were originally in FunctionHandler - * getNumVars was originally in PtgFunc - * + * Function Constants for all Formula Types (PtgFunc, PtgFuncVar - regular and PtgFuncVar - AddIns) + * Modifications: + * all xlfXXX constants were originally in FunctionHandler + * getFunctionString was orignally in PtgFuncVar + * FUNCTION_STRINGS were originally in FunctionHandler + * getNumVars was originally in PtgFunc + * * @see */ public class FunctionConstants { -/* HOW TO USE: - * - * 1- If implementing a formula, MAKE SURE to put it in recArr AND getFunctionString list. If it is a ptgFunc, also input - * the number of args in getNumArgs. If it's an Add-in, add xlfXX constant to the end of the Excel function numbers list. - * - * All function ID's/number MUST exist in the xlfXXX constants list - * - * - */ - public static int FTYPE_PTGFUNC= 0; - public static int FTYPE_PTGFUNCVAR= 1; - public static int FTYPE_PTGFUNCVAR_ADDIN= 2; - - /**************************************** - * * - * Excel function numbers * - * * - ****************************************/ - - public static final int XLF_COUNT = 0; - public static final int XLF_IS = 1; - public static final int XLF_IS_NA = 2; - public static final int XLF_IS_ERROR = 3; - public static final int XLF_SUM = 4; - public static final int XLF_AVERAGE = 5; - public static final int XLF_MIN = 6; - public static final int XLF_MAX = 7; - public static final int XLF_ROW = 8; - public static final int xlfColumn = 9; - public static final int xlfNa = 10; - public static final int xlfNpv = 11; - public static final int xlfStdev = 12; - public static final int xlfDollar = 13; - public static final int xlfFixed = 14; - public static final int xlfSin = 15; - public static final int xlfCos = 16; - public static final int xlfTan = 17; - public static final int xlfAtan = 18; - public static final int xlfPi = 19; - public static final int xlfSqrt = 20; - public static final int xlfExp = 21; - public static final int xlfLn = 22; - public static final int xlfLog10 = 23; - public static final int xlfAbs = 24; - public static final int xlfInt = 25; - public static final int xlfSign = 26; - public static final int xlfRound = 27; - public static final int xlfLookup = 28; - public static final int xlfIndex = 29; - public static final int xlfRept = 30; - public static final int xlfMid = 31; - public static final int xlfLen = 32; - public static final int xlfValue = 33; - public static final int xlfTrue = 34; - public static final int xlfFalse = 35; - public static final int xlfAnd = 36; - public static final int xlfOr = 37; - public static final int xlfNot = 38; - public static final int xlfMod = 39; - public static final int xlfDcount = 40; - public static final int xlfDsum = 41; - public static final int xlfDaverage = 42; - public static final int xlfDmin = 43; - public static final int xlfDmax = 44; - public static final int xlfDstdev = 45; - public static final int xlfVar = 46; - public static final int xlfDvar = 47; - public static final int xlfText = 48; - public static final int xlfLinest = 49; - public static final int xlfTrend = 50; - public static final int xlfLogest = 51; - public static final int xlfGrowth = 52; - public static final int xlfGoto = 53; - public static final int xlfHalt = 54; - public static final int xlfPv = 56; - public static final int xlfFv = 57; - public static final int xlfNper = 58; - public static final int xlfPmt = 59; - public static final int xlfRate = 60; - public static final int xlfMirr = 61; - public static final int xlfIrr = 62; - public static final int xlfRand = 63; - public static final int xlfMatch = 64; - public static final int xlfDate = 65; - public static final int xlfTime = 66; - public static final int xlfDay = 67; - public static final int xlfMonth = 68; - public static final int xlfYear = 69; - public static final int xlfWeekday = 70; - public static final int xlfHour = 71; - public static final int xlfMinute = 72; - public static final int xlfSecond = 73; - public static final int xlfNow = 74; - public static final int xlfAreas = 75; - public static final int xlfRows = 76; - public static final int xlfColumns = 77; - public static final int xlfOffset = 78; - public static final int xlfAbsref = 79; - public static final int xlfRelref = 80; - public static final int xlfArgument = 81; - public static final int xlfSearch = 82; - public static final int xlfTranspose = 83; - public static final int xlfError = 84; - public static final int xlfStep = 85; - public static final int xlfType = 86; - public static final int xlfEcho = 87; - public static final int xlfSetName = 88; - public static final int xlfCaller = 89; - public static final int xlfDeref = 90; - public static final int xlfWindows = 91; - public static final int xlfSeries = 92; - public static final int xlfDocuments = 93; - public static final int xlfActiveCell = 94; - public static final int xlfSelection = 95; - public static final int xlfResult = 96; - public static final int xlfAtan2 = 97; - public static final int xlfAsin = 98; - public static final int xlfAcos = 99; - public static final int xlfChoose = 100; - public static final int xlfHlookup = 101; - public static final int xlfVlookup = 102; - public static final int xlfLinks = 103; - public static final int xlfInput = 104; - public static final int xlfIsref = 105; - public static final int xlfGetFormula = 106; - public static final int xlfGetName = 107; - public static final int xlfSetValue = 108; - public static final int xlfLog = 109; - public static final int xlfExec = 110; - public static final int xlfChar = 111; - public static final int xlfLower = 112; - public static final int xlfUpper = 113; - public static final int xlfProper = 114; - public static final int xlfLeft = 115; - public static final int xlfRight = 116; - public static final int xlfExact = 117; - public static final int xlfTrim = 118; - public static final int xlfReplace = 119; - public static final int xlfSubstitute = 120; - public static final int xlfCode = 121; - public static final int xlfNames = 122; - public static final int xlfDirectory = 123; - public static final int xlfFind = 124; - public static final int xlfCell = 125; - public static final int xlfIserr = 126; - public static final int xlfIstext = 127; - public static final int xlfIsnumber = 128; - public static final int xlfIsblank = 129; - public static final int xlfT = 130; - public static final int xlfN = 131; - public static final int xlfFopen = 132; - public static final int xlfFclose = 133; - public static final int xlfFsize = 134; - public static final int xlfFreadln = 135; - public static final int xlfFread = 136; - public static final int xlfFwriteln = 137; - public static final int xlfFwrite = 138; - public static final int xlfFpos = 139; - public static final int xlfDatevalue = 140; - public static final int xlfTimevalue = 141; - public static final int xlfSln = 142; - public static final int xlfSyd = 143; - public static final int xlfDdb = 144; - public static final int xlfGetDef = 145; - public static final int xlfReftext = 146; - public static final int xlfTextref = 147; - public static final int XLF_INDIRECT = 148; - public static final int xlfRegister = 149; - public static final int xlfCall = 150; - public static final int xlfAddBar = 151; - public static final int xlfAddMenu = 152; - public static final int xlfAddCommand = 153; - public static final int xlfEnableCommand = 154; - public static final int xlfCheckCommand = 155; - public static final int xlfRenameCommand = 156; - public static final int xlfShowBar = 157; - public static final int xlfDeleteMenu = 158; - public static final int xlfDeleteCommand = 159; - public static final int xlfGetChartItem = 160; - public static final int xlfDialogBox = 161; - public static final int xlfClean = 162; - public static final int xlfMdeterm = 163; - public static final int xlfMinverse = 164; - public static final int xlfMmult = 165; - public static final int xlfFiles = 166; - public static final int xlfIpmt = 167; - public static final int xlfPpmt = 168; - public static final int xlfCounta = 169; - public static final int xlfCancelKey = 170; - public static final int xlfInitiate = 175; - public static final int xlfRequest = 176; - public static final int xlfPoke = 177; - public static final int xlfExecute = 178; - public static final int xlfTerminate = 179; - public static final int xlfRestart = 180; - public static final int xlfHelp = 181; - public static final int xlfGetBar = 182; - public static final int xlfProduct = 183; - public static final int xlfFact = 184; - public static final int xlfGetCell = 185; - public static final int xlfGetWorkspace = 186; - public static final int xlfGetWindow = 187; - public static final int xlfGetDocument = 188; - public static final int xlfDproduct = 189; - public static final int xlfIsnontext = 190; - public static final int xlfGetNote = 191; - public static final int xlfNote = 192; - public static final int xlfStdevp = 193; - public static final int xlfVarp = 194; - public static final int xlfDstdevp = 195; - public static final int xlfDvarp = 196; - public static final int xlfTrunc = 197; - public static final int xlfIslogical = 198; - public static final int xlfDcounta = 199; - public static final int xlfDeleteBar = 200; - public static final int xlfUnregister = 201; - public static final int xlfUsdollar = 204; - public static final int xlfFindb = 205; - public static final int xlfSearchb = 206; - public static final int xlfReplaceb = 207; - public static final int xlfLeftb = 208; - public static final int xlfRightb = 209; - public static final int xlfMidb = 210; - public static final int xlfLenb = 211; - public static final int xlfRoundup = 212; - public static final int xlfRounddown = 213; - public static final int xlfAsc = 214; - public static final int xlfDbcs = 215; - public static final int xlfRank = 216; - public static final int xlfAddress = 219; - public static final int xlfDays360 = 220; - public static final int xlfToday = 221; - public static final int xlfVdb = 222; - public static final int xlfMedian = 227; - public static final int xlfSumproduct = 228; - public static final int xlfSinh = 229; - public static final int xlfCosh = 230; - public static final int xlfTanh = 231; - public static final int xlfAsinh = 232; - public static final int xlfAcosh = 233; - public static final int xlfAtanh = 234; - public static final int xlfDget = 235; - public static final int xlfCreateObject = 236; - public static final int xlfVolatile = 237; - public static final int xlfLastError = 238; - public static final int xlfCustomUndo = 239; - public static final int xlfCustomRepeat = 240; - public static final int xlfFormulaConvert = 241; - public static final int xlfGetLinkInfo = 242; - public static final int xlfTextBox = 243; - public static final int xlfInfo = 244; - public static final int xlfGroup = 245; - public static final int xlfGetObject = 246; - public static final int xlfDb = 247; - public static final int xlfPause = 248; - public static final int xlfResume = 251; - public static final int xlfFrequency = 252; - public static final int xlfAddToolbar = 253; - public static final int xlfDeleteToolbar = 254; - public static final int xlfADDIN = 255; // KSC: Added; Excel function ID for add-ins - public static final int xlfResetToolbar = 256; - public static final int xlfEvaluate = 257; - public static final int xlfGetToolbar = 258; - public static final int xlfGetTool = 259; - public static final int xlfSpellingCheck = 260; - public static final int xlfErrorType = 261; - public static final int xlfAppTitle = 262; - public static final int xlfWindowTitle = 263; - public static final int xlfSaveToolbar = 264; - public static final int xlfEnableTool = 265; - public static final int xlfPressTool = 266; - public static final int xlfRegisterId = 267; - public static final int xlfGetWorkbook = 268; - public static final int xlfAvedev = 269; - public static final int xlfBetadist = 270; - public static final int xlfGammaln = 271; - public static final int xlfBetainv = 272; - public static final int xlfBinomdist = 273; - public static final int xlfChidist = 274; - public static final int xlfChiinv = 275; - public static final int xlfCombin = 276; - public static final int xlfConfidence = 277; - public static final int xlfCritbinom = 278; - public static final int xlfEven = 279; - public static final int xlfExpondist = 280; - public static final int xlfFdist = 281; - public static final int xlfFinv = 282; - public static final int xlfFisher = 283; - public static final int xlfFisherinv = 284; - public static final int xlfFloor = 285; - public static final int xlfGammadist = 286; - public static final int xlfGammainv = 287; - public static final int xlfCeiling = 288; - public static final int xlfHypgeomdist = 289; - public static final int xlfLognormdist = 290; - public static final int xlfLoginv = 291; - public static final int xlfNegbinomdist = 292; - public static final int xlfNormdist = 293; - public static final int xlfNormsdist = 294; - public static final int xlfNorminv = 295; - public static final int xlfNormsinv = 296; - public static final int xlfStandardize = 297; - public static final int xlfOdd = 298; - public static final int xlfPermut = 299; - public static final int xlfPoisson = 300; - public static final int xlfTdist = 301; - public static final int xlfWeibull = 302; - public static final int xlfSumxmy2 = 303; - public static final int xlfSumx2my2 = 304; - public static final int xlfSumx2py2 = 305; - public static final int xlfChitest = 306; - public static final int xlfCorrel = 307; - public static final int xlfCovar = 308; - public static final int xlfForecast = 309; - public static final int xlfFtest = 310; - public static final int xlfIntercept = 311; - public static final int xlfPearson = 312; - public static final int xlfRsq = 313; - public static final int xlfSteyx = 314; - public static final int xlfSlope = 315; - public static final int xlfTtest = 316; - public static final int xlfProb = 317; - public static final int xlfDevsq = 318; - public static final int xlfGeomean = 319; - public static final int xlfHarmean = 320; - public static final int xlfSumsq = 321; - public static final int xlfKurt = 322; - public static final int xlfSkew = 323; - public static final int xlfZtest = 324; - public static final int xlfLarge = 325; - public static final int xlfSmall = 326; - public static final int xlfQuartile = 327; - public static final int xlfPercentile = 328; - public static final int xlfPercentrank = 329; - public static final int xlfMode = 330; - public static final int xlfTrimmean = 331; - public static final int xlfTinv = 332; - public static final int xlfMovieCommand = 334; - public static final int xlfGetMovie = 335; - public static final int xlfConcatenate = 336; - public static final int xlfPower = 337; - public static final int xlfPivotAddData = 338; - public static final int xlfGetPivotTable = 339; - public static final int xlfGetPivotField = 340; - public static final int xlfGetPivotItem = 341; - public static final int xlfRadians = 342; - public static final int xlfDegrees = 343; - public static final int xlfSubtotal = 344; - public static final int XLF_SUM_IF = 345; - public static final int xlfCountif = 346; - public static final int xlfCountblank = 347; - public static final int xlfScenarioGet = 348; - public static final int xlfOptionsListsGet = 349; - public static final int xlfIspmt = 350; - public static final int xlfDatedif = 351; - public static final int xlfDatestring = 352; - public static final int xlfNumberstring = 353; - public static final int xlfRoman = 354; - public static final int xlfOpenDialog = 355; - public static final int xlfSaveDialog = 356; - public static final int xlfViewGet = 357; - public static final int xlfGetPivotData = 358; - public static final int xlfHyperlink = 359; - public static final int xlfPhonetic = 360; - public static final int xlfAverageA = 361; - public static final int xlfMaxA = 362; - public static final int xlfMinA = 363; - public static final int xlfStDevPA = 364; - public static final int xlfVarPA = 365; - public static final int xlfStDevA = 366; - public static final int xlfVarA = 367; - // KSC: ADD-IN formulas - use any index; name must be present in FunctionConstants.addIns - // Financial Formulas - public static final int xlfAccrintm= 368; - public static final int xlfAccrint= 369; - public static final int xlfCoupDayBS= 370; - public static final int xlfCoupDays= 371; - public static final int xlfCumIPmt= 372; - public static final int xlfCumPrinc= 373; - public static final int xlfCoupNCD= 374; - public static final int xlfCoupDaysNC= 375; - public static final int xlfCoupPCD= 376; - public static final int xlfCoupNUM= 377; - public static final int xlfDollarDE= 378; - public static final int xlfDollarFR= 379; - public static final int xlfEffect= 380; - public static final int xlfINTRATE= 381; - public static final int xlfXIRR= 382; - public static final int xlfXNPV= 383; - public static final int xlfYIELD= 384; - public static final int xlfPRICE= 385; - public static final int xlfPRICEDISC= 386; - public static final int xlfPRICEMAT= 387; - public static final int xlfDURATION= 388; - public static final int xlfMDURATION= 389; - public static final int xlfTBillEq= 390; - public static final int xlfTBillPrice= 391; - public static final int xlfTBillYield= 392; - public static final int xlfYieldDisc= 393; - public static final int xlfYieldMat= 394; - public static final int xlfFVSchedule= 395; - public static final int xlfAmorlinc= 396; - public static final int xlfAmordegrc= 397; - public static final int xlfOddFPrice= 398; - public static final int xlfOddLPrice= 399; - public static final int xlfOddFYield= 400; - public static final int xlfOddLYield= 401; - public static final int xlfNOMINAL= 402; - public static final int xlfDISC= 403; - public static final int xlfRECEIVED= 404; - // Engineering Formulas - public static final int xlfBIN2DEC= 405; - public static final int xlfBIN2HEX= 406; - public static final int xlfBIN2OCT= 407; - public static final int xlfDEC2BIN= 408; - public static final int xlfDEC2HEX= 409; - public static final int xlfDEC2OCT= 410; - public static final int xlfHEX2BIN= 411; - public static final int xlfHEX2DEC= 412; - public static final int xlfHEX2OCT= 413; - public static final int xlfOCT2BIN= 414; - public static final int xlfOCT2DEC= 415; - public static final int xlfOCT2HEX= 416; - public static final int xlfCOMPLEX= 417; - public static final int xlfGESTEP= 418; - public static final int xlfDELTA= 419; - public static final int xlfIMAGINARY= 420; - public static final int xlfIMABS= 421; - public static final int xlfIMDIV= 422; - public static final int xlfIMCONJUGATE= 423; - public static final int xlfIMCOS= 424; - public static final int xlfIMSIN= 425; - public static final int xlfIMREAL= 426; - public static final int xlfIMEXP= 427; - public static final int xlfIMSUB= 428; - public static final int xlfIMSUM= 429; - public static final int xlfIMPRODUCT= 430; - public static final int xlfIMLN= 431; - public static final int xlfIMLOG10= 432; - public static final int xlfIMLOG2= 433; - public static final int xlfIMPOWER= 434; - public static final int xlfIMSQRT= 435; - public static final int xlfIMARGUMENT= 436; - public static final int xlfCONVERT= 437; - public static final int xlfERF= 460; - public static final int xlfERFC= 461; - // Math Add-in Formulas - public static final int xlfDOUBLEFACT= 438; - public static final int xlfGCD= 439; - public static final int xlfLCM= 440; - public static final int xlfMROUND= 441; - public static final int xlfMULTINOMIAL= 442; - public static final int xlfQUOTIENT= 443; - public static final int xlfRANDBETWEEN= 444; - public static final int xlfSERIESSUM= 445; - public static final int xlfSQRTPI= 446; - public static final int xlfSUMIFS= 456; - // Information Add-in Formulas - public static final int xlfISEVEN= 447; - public static final int xlfISODD= 448; - // Date/Time Add-in Formulas - public static final int xlfNETWORKDAYS= 449; - public static final int xlfEDATE= 450; - public static final int xlfEOMONTH= 451; - public static final int xlfWEEKNUM= 452; - public static final int xlfWORKDAY= 453; - public static final int xlfYEARFRAC= 459; - // Statistical - public static final int xlfAVERAGEIF= 454; - public static final int xlfAVERAGEIFS= 457; - public static final int xlfCOUNTIFS= 458; - // Logical - public static final int xlfIFERROR= 455; - public static final int MAXXLF= 462; + /* HOW TO USE: + * + * 1- If implementing a formula, MAKE SURE to put it in recArr AND getFunctionString list. If it is a ptgFunc, also input + * the number of args in getNumArgs. If it's an Add-in, add xlfXX constant to the end of the Excel function numbers list. + * + * All function ID's/number MUST exist in the xlfXXX constants list + * + * + */ + public static int FTYPE_PTGFUNC = 0; + public static int FTYPE_PTGFUNCVAR = 1; + public static int FTYPE_PTGFUNCVAR_ADDIN = 2; + + /**************************************** + * * + * Excel function numbers * + * * + ****************************************/ + + public static final int XLF_COUNT = 0; + public static final int XLF_IS = 1; + public static final int XLF_IS_NA = 2; + public static final int XLF_IS_ERROR = 3; + public static final int XLF_SUM = 4; + public static final int XLF_AVERAGE = 5; + public static final int XLF_MIN = 6; + public static final int XLF_MAX = 7; + public static final int XLF_ROW = 8; + public static final int xlfColumn = 9; + public static final int xlfNa = 10; + public static final int xlfNpv = 11; + public static final int xlfStdev = 12; + public static final int xlfDollar = 13; + public static final int xlfFixed = 14; + public static final int xlfSin = 15; + public static final int xlfCos = 16; + public static final int xlfTan = 17; + public static final int xlfAtan = 18; + public static final int xlfPi = 19; + public static final int xlfSqrt = 20; + public static final int xlfExp = 21; + public static final int xlfLn = 22; + public static final int xlfLog10 = 23; + public static final int xlfAbs = 24; + public static final int xlfInt = 25; + public static final int xlfSign = 26; + public static final int xlfRound = 27; + public static final int xlfLookup = 28; + public static final int xlfIndex = 29; + public static final int xlfRept = 30; + public static final int xlfMid = 31; + public static final int xlfLen = 32; + public static final int xlfValue = 33; + public static final int xlfTrue = 34; + public static final int xlfFalse = 35; + public static final int xlfAnd = 36; + public static final int xlfOr = 37; + public static final int xlfNot = 38; + public static final int xlfMod = 39; + public static final int xlfDcount = 40; + public static final int xlfDsum = 41; + public static final int xlfDaverage = 42; + public static final int xlfDmin = 43; + public static final int xlfDmax = 44; + public static final int xlfDstdev = 45; + public static final int xlfVar = 46; + public static final int xlfDvar = 47; + public static final int xlfText = 48; + public static final int xlfLinest = 49; + public static final int xlfTrend = 50; + public static final int xlfLogest = 51; + public static final int xlfGrowth = 52; + public static final int xlfGoto = 53; + public static final int xlfHalt = 54; + public static final int xlfPv = 56; + public static final int xlfFv = 57; + public static final int xlfNper = 58; + public static final int xlfPmt = 59; + public static final int xlfRate = 60; + public static final int xlfMirr = 61; + public static final int xlfIrr = 62; + public static final int xlfRand = 63; + public static final int xlfMatch = 64; + public static final int xlfDate = 65; + public static final int xlfTime = 66; + public static final int xlfDay = 67; + public static final int xlfMonth = 68; + public static final int xlfYear = 69; + public static final int xlfWeekday = 70; + public static final int xlfHour = 71; + public static final int xlfMinute = 72; + public static final int xlfSecond = 73; + public static final int xlfNow = 74; + public static final int xlfAreas = 75; + public static final int xlfRows = 76; + public static final int xlfColumns = 77; + public static final int xlfOffset = 78; + public static final int xlfAbsref = 79; + public static final int xlfRelref = 80; + public static final int xlfArgument = 81; + public static final int xlfSearch = 82; + public static final int xlfTranspose = 83; + public static final int xlfError = 84; + public static final int xlfStep = 85; + public static final int xlfType = 86; + public static final int xlfEcho = 87; + public static final int xlfSetName = 88; + public static final int xlfCaller = 89; + public static final int xlfDeref = 90; + public static final int xlfWindows = 91; + public static final int xlfSeries = 92; + public static final int xlfDocuments = 93; + public static final int xlfActiveCell = 94; + public static final int xlfSelection = 95; + public static final int xlfResult = 96; + public static final int xlfAtan2 = 97; + public static final int xlfAsin = 98; + public static final int xlfAcos = 99; + public static final int xlfChoose = 100; + public static final int xlfHlookup = 101; + public static final int xlfVlookup = 102; + public static final int xlfLinks = 103; + public static final int xlfInput = 104; + public static final int xlfIsref = 105; + public static final int xlfGetFormula = 106; + public static final int xlfGetName = 107; + public static final int xlfSetValue = 108; + public static final int xlfLog = 109; + public static final int xlfExec = 110; + public static final int xlfChar = 111; + public static final int xlfLower = 112; + public static final int xlfUpper = 113; + public static final int xlfProper = 114; + public static final int xlfLeft = 115; + public static final int xlfRight = 116; + public static final int xlfExact = 117; + public static final int xlfTrim = 118; + public static final int xlfReplace = 119; + public static final int xlfSubstitute = 120; + public static final int xlfCode = 121; + public static final int xlfNames = 122; + public static final int xlfDirectory = 123; + public static final int xlfFind = 124; + public static final int xlfCell = 125; + public static final int xlfIserr = 126; + public static final int xlfIstext = 127; + public static final int xlfIsnumber = 128; + public static final int xlfIsblank = 129; + public static final int xlfT = 130; + public static final int xlfN = 131; + public static final int xlfFopen = 132; + public static final int xlfFclose = 133; + public static final int xlfFsize = 134; + public static final int xlfFreadln = 135; + public static final int xlfFread = 136; + public static final int xlfFwriteln = 137; + public static final int xlfFwrite = 138; + public static final int xlfFpos = 139; + public static final int xlfDatevalue = 140; + public static final int xlfTimevalue = 141; + public static final int xlfSln = 142; + public static final int xlfSyd = 143; + public static final int xlfDdb = 144; + public static final int xlfGetDef = 145; + public static final int xlfReftext = 146; + public static final int xlfTextref = 147; + public static final int XLF_INDIRECT = 148; + public static final int xlfRegister = 149; + public static final int xlfCall = 150; + public static final int xlfAddBar = 151; + public static final int xlfAddMenu = 152; + public static final int xlfAddCommand = 153; + public static final int xlfEnableCommand = 154; + public static final int xlfCheckCommand = 155; + public static final int xlfRenameCommand = 156; + public static final int xlfShowBar = 157; + public static final int xlfDeleteMenu = 158; + public static final int xlfDeleteCommand = 159; + public static final int xlfGetChartItem = 160; + public static final int xlfDialogBox = 161; + public static final int xlfClean = 162; + public static final int xlfMdeterm = 163; + public static final int xlfMinverse = 164; + public static final int xlfMmult = 165; + public static final int xlfFiles = 166; + public static final int xlfIpmt = 167; + public static final int xlfPpmt = 168; + public static final int xlfCounta = 169; + public static final int xlfCancelKey = 170; + public static final int xlfInitiate = 175; + public static final int xlfRequest = 176; + public static final int xlfPoke = 177; + public static final int xlfExecute = 178; + public static final int xlfTerminate = 179; + public static final int xlfRestart = 180; + public static final int xlfHelp = 181; + public static final int xlfGetBar = 182; + public static final int xlfProduct = 183; + public static final int xlfFact = 184; + public static final int xlfGetCell = 185; + public static final int xlfGetWorkspace = 186; + public static final int xlfGetWindow = 187; + public static final int xlfGetDocument = 188; + public static final int xlfDproduct = 189; + public static final int xlfIsnontext = 190; + public static final int xlfGetNote = 191; + public static final int xlfNote = 192; + public static final int xlfStdevp = 193; + public static final int xlfVarp = 194; + public static final int xlfDstdevp = 195; + public static final int xlfDvarp = 196; + public static final int xlfTrunc = 197; + public static final int xlfIslogical = 198; + public static final int xlfDcounta = 199; + public static final int xlfDeleteBar = 200; + public static final int xlfUnregister = 201; + public static final int xlfUsdollar = 204; + public static final int xlfFindb = 205; + public static final int xlfSearchb = 206; + public static final int xlfReplaceb = 207; + public static final int xlfLeftb = 208; + public static final int xlfRightb = 209; + public static final int xlfMidb = 210; + public static final int xlfLenb = 211; + public static final int xlfRoundup = 212; + public static final int xlfRounddown = 213; + public static final int xlfAsc = 214; + public static final int xlfDbcs = 215; + public static final int xlfRank = 216; + public static final int xlfAddress = 219; + public static final int xlfDays360 = 220; + public static final int xlfToday = 221; + public static final int xlfVdb = 222; + public static final int xlfMedian = 227; + public static final int xlfSumproduct = 228; + public static final int xlfSinh = 229; + public static final int xlfCosh = 230; + public static final int xlfTanh = 231; + public static final int xlfAsinh = 232; + public static final int xlfAcosh = 233; + public static final int xlfAtanh = 234; + public static final int xlfDget = 235; + public static final int xlfCreateObject = 236; + public static final int xlfVolatile = 237; + public static final int xlfLastError = 238; + public static final int xlfCustomUndo = 239; + public static final int xlfCustomRepeat = 240; + public static final int xlfFormulaConvert = 241; + public static final int xlfGetLinkInfo = 242; + public static final int xlfTextBox = 243; + public static final int xlfInfo = 244; + public static final int xlfGroup = 245; + public static final int xlfGetObject = 246; + public static final int xlfDb = 247; + public static final int xlfPause = 248; + public static final int xlfResume = 251; + public static final int xlfFrequency = 252; + public static final int xlfAddToolbar = 253; + public static final int xlfDeleteToolbar = 254; + public static final int xlfADDIN = 255; // KSC: Added; Excel function ID for add-ins + public static final int xlfResetToolbar = 256; + public static final int xlfEvaluate = 257; + public static final int xlfGetToolbar = 258; + public static final int xlfGetTool = 259; + public static final int xlfSpellingCheck = 260; + public static final int xlfErrorType = 261; + public static final int xlfAppTitle = 262; + public static final int xlfWindowTitle = 263; + public static final int xlfSaveToolbar = 264; + public static final int xlfEnableTool = 265; + public static final int xlfPressTool = 266; + public static final int xlfRegisterId = 267; + public static final int xlfGetWorkbook = 268; + public static final int xlfAvedev = 269; + public static final int xlfBetadist = 270; + public static final int xlfGammaln = 271; + public static final int xlfBetainv = 272; + public static final int xlfBinomdist = 273; + public static final int xlfChidist = 274; + public static final int xlfChiinv = 275; + public static final int xlfCombin = 276; + public static final int xlfConfidence = 277; + public static final int xlfCritbinom = 278; + public static final int xlfEven = 279; + public static final int xlfExpondist = 280; + public static final int xlfFdist = 281; + public static final int xlfFinv = 282; + public static final int xlfFisher = 283; + public static final int xlfFisherinv = 284; + public static final int xlfFloor = 285; + public static final int xlfGammadist = 286; + public static final int xlfGammainv = 287; + public static final int xlfCeiling = 288; + public static final int xlfHypgeomdist = 289; + public static final int xlfLognormdist = 290; + public static final int xlfLoginv = 291; + public static final int xlfNegbinomdist = 292; + public static final int xlfNormdist = 293; + public static final int xlfNormsdist = 294; + public static final int xlfNorminv = 295; + public static final int xlfNormsinv = 296; + public static final int xlfStandardize = 297; + public static final int xlfOdd = 298; + public static final int xlfPermut = 299; + public static final int xlfPoisson = 300; + public static final int xlfTdist = 301; + public static final int xlfWeibull = 302; + public static final int xlfSumxmy2 = 303; + public static final int xlfSumx2my2 = 304; + public static final int xlfSumx2py2 = 305; + public static final int xlfChitest = 306; + public static final int xlfCorrel = 307; + public static final int xlfCovar = 308; + public static final int xlfForecast = 309; + public static final int xlfFtest = 310; + public static final int xlfIntercept = 311; + public static final int xlfPearson = 312; + public static final int xlfRsq = 313; + public static final int xlfSteyx = 314; + public static final int xlfSlope = 315; + public static final int xlfTtest = 316; + public static final int xlfProb = 317; + public static final int xlfDevsq = 318; + public static final int xlfGeomean = 319; + public static final int xlfHarmean = 320; + public static final int xlfSumsq = 321; + public static final int xlfKurt = 322; + public static final int xlfSkew = 323; + public static final int xlfZtest = 324; + public static final int xlfLarge = 325; + public static final int xlfSmall = 326; + public static final int xlfQuartile = 327; + public static final int xlfPercentile = 328; + public static final int xlfPercentrank = 329; + public static final int xlfMode = 330; + public static final int xlfTrimmean = 331; + public static final int xlfTinv = 332; + public static final int xlfMovieCommand = 334; + public static final int xlfGetMovie = 335; + public static final int xlfConcatenate = 336; + public static final int xlfPower = 337; + public static final int xlfPivotAddData = 338; + public static final int xlfGetPivotTable = 339; + public static final int xlfGetPivotField = 340; + public static final int xlfGetPivotItem = 341; + public static final int xlfRadians = 342; + public static final int xlfDegrees = 343; + public static final int xlfSubtotal = 344; + public static final int XLF_SUM_IF = 345; + public static final int xlfCountif = 346; + public static final int xlfCountblank = 347; + public static final int xlfScenarioGet = 348; + public static final int xlfOptionsListsGet = 349; + public static final int xlfIspmt = 350; + public static final int xlfDatedif = 351; + public static final int xlfDatestring = 352; + public static final int xlfNumberstring = 353; + public static final int xlfRoman = 354; + public static final int xlfOpenDialog = 355; + public static final int xlfSaveDialog = 356; + public static final int xlfViewGet = 357; + public static final int xlfGetPivotData = 358; + public static final int xlfHyperlink = 359; + public static final int xlfPhonetic = 360; + public static final int xlfAverageA = 361; + public static final int xlfMaxA = 362; + public static final int xlfMinA = 363; + public static final int xlfStDevPA = 364; + public static final int xlfVarPA = 365; + public static final int xlfStDevA = 366; + public static final int xlfVarA = 367; + // KSC: ADD-IN formulas - use any index; name must be present in FunctionConstants.addIns + // Financial Formulas + public static final int xlfAccrintm = 368; + public static final int xlfAccrint = 369; + public static final int xlfCoupDayBS = 370; + public static final int xlfCoupDays = 371; + public static final int xlfCumIPmt = 372; + public static final int xlfCumPrinc = 373; + public static final int xlfCoupNCD = 374; + public static final int xlfCoupDaysNC = 375; + public static final int xlfCoupPCD = 376; + public static final int xlfCoupNUM = 377; + public static final int xlfDollarDE = 378; + public static final int xlfDollarFR = 379; + public static final int xlfEffect = 380; + public static final int xlfINTRATE = 381; + public static final int xlfXIRR = 382; + public static final int xlfXNPV = 383; + public static final int xlfYIELD = 384; + public static final int xlfPRICE = 385; + public static final int xlfPRICEDISC = 386; + public static final int xlfPRICEMAT = 387; + public static final int xlfDURATION = 388; + public static final int xlfMDURATION = 389; + public static final int xlfTBillEq = 390; + public static final int xlfTBillPrice = 391; + public static final int xlfTBillYield = 392; + public static final int xlfYieldDisc = 393; + public static final int xlfYieldMat = 394; + public static final int xlfFVSchedule = 395; + public static final int xlfAmorlinc = 396; + public static final int xlfAmordegrc = 397; + public static final int xlfOddFPrice = 398; + public static final int xlfOddLPrice = 399; + public static final int xlfOddFYield = 400; + public static final int xlfOddLYield = 401; + public static final int xlfNOMINAL = 402; + public static final int xlfDISC = 403; + public static final int xlfRECEIVED = 404; + // Engineering Formulas + public static final int xlfBIN2DEC = 405; + public static final int xlfBIN2HEX = 406; + public static final int xlfBIN2OCT = 407; + public static final int xlfDEC2BIN = 408; + public static final int xlfDEC2HEX = 409; + public static final int xlfDEC2OCT = 410; + public static final int xlfHEX2BIN = 411; + public static final int xlfHEX2DEC = 412; + public static final int xlfHEX2OCT = 413; + public static final int xlfOCT2BIN = 414; + public static final int xlfOCT2DEC = 415; + public static final int xlfOCT2HEX = 416; + public static final int xlfCOMPLEX = 417; + public static final int xlfGESTEP = 418; + public static final int xlfDELTA = 419; + public static final int xlfIMAGINARY = 420; + public static final int xlfIMABS = 421; + public static final int xlfIMDIV = 422; + public static final int xlfIMCONJUGATE = 423; + public static final int xlfIMCOS = 424; + public static final int xlfIMSIN = 425; + public static final int xlfIMREAL = 426; + public static final int xlfIMEXP = 427; + public static final int xlfIMSUB = 428; + public static final int xlfIMSUM = 429; + public static final int xlfIMPRODUCT = 430; + public static final int xlfIMLN = 431; + public static final int xlfIMLOG10 = 432; + public static final int xlfIMLOG2 = 433; + public static final int xlfIMPOWER = 434; + public static final int xlfIMSQRT = 435; + public static final int xlfIMARGUMENT = 436; + public static final int xlfCONVERT = 437; + public static final int xlfERF = 460; + public static final int xlfERFC = 461; + // Math Add-in Formulas + public static final int xlfDOUBLEFACT = 438; + public static final int xlfGCD = 439; + public static final int xlfLCM = 440; + public static final int xlfMROUND = 441; + public static final int xlfMULTINOMIAL = 442; + public static final int xlfQUOTIENT = 443; + public static final int xlfRANDBETWEEN = 444; + public static final int xlfSERIESSUM = 445; + public static final int xlfSQRTPI = 446; + public static final int xlfSUMIFS = 456; + // Information Add-in Formulas + public static final int xlfISEVEN = 447; + public static final int xlfISODD = 448; + // Date/Time Add-in Formulas + public static final int xlfNETWORKDAYS = 449; + public static final int xlfEDATE = 450; + public static final int xlfEOMONTH = 451; + public static final int xlfWEEKNUM = 452; + public static final int xlfWORKDAY = 453; + public static final int xlfYEARFRAC = 459; + // Statistical + public static final int xlfAVERAGEIF = 454; + public static final int xlfAVERAGEIFS = 457; + public static final int xlfCOUNTIFS = 458; + // Logical + public static final int xlfIFERROR = 455; + public static final int MAXXLF = 462; + - - /** * Japanese Excel contains some different values and string output than US English Excel. - * + *

                * This recArr is checked if locale = japan... if null value is returned then the main list is checked */ public static String[][] jRecArr = { - {"YEN", String.valueOf(xlfDollar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"DOLLAR", String.valueOf(xlfUsdollar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"JIS", String.valueOf(xlfDbcs), String.valueOf(FTYPE_PTGFUNC)}, + {"YEN", String.valueOf(xlfDollar), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"DOLLAR", String.valueOf(xlfUsdollar), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"JIS", String.valueOf(xlfDbcs), String.valueOf(FTYPE_PTGFUNC)}, }; - + /** * Unimplemented records. This exists to allow writing of functions that are unsupported for calculation */ public static String[][] unimplRecArr = { - {"ASC", String.valueOf(xlfAsc), String.valueOf(FTYPE_PTGFUNC)}, - {"DBCS", String.valueOf(xlfDbcs), String.valueOf(FTYPE_PTGFUNC)}, - {"MDETERM", String.valueOf(xlfMdeterm), String.valueOf(FTYPE_PTGFUNC)}, - {"SEARCHB", String.valueOf(xlfSearchb), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TRANSPOSE", String.valueOf(xlfTranspose), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"BETAINV", String.valueOf(xlfBetainv), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"BETADIST", String.valueOf(xlfBetadist), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TIMEVALUE", String.valueOf(xlfTimevalue), String.valueOf(FTYPE_PTGFUNC)}, - {"MINVERSE", String.valueOf(xlfMinverse), String.valueOf(FTYPE_PTGFUNC)}, - {"MDETERM", String.valueOf(xlfMdeterm), String.valueOf(FTYPE_PTGFUNC)}, - {"GETPIVOTDATA", String.valueOf(xlfGetPivotData), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"HYPERLINK", String.valueOf(xlfHyperlink), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"PHONETIC", String.valueOf(xlfPhonetic), String.valueOf(FTYPE_PTGFUNC)}, - {"PERCENTILE", String.valueOf(xlfPercentile), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TRUNC", String.valueOf(xlfTrunc), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"PERCENTRANK",String.valueOf(xlfPercentrank), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"RIGHTB",String.valueOf(xlfRightb), String.valueOf(FTYPE_PTGFUNC)}, - {"REPLACEB",String.valueOf(xlfReplaceb), String.valueOf(FTYPE_PTGFUNC)}, - {"FINDB", String.valueOf(xlfFindb), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MIDB",String.valueOf(xlfMidb), String.valueOf(FTYPE_PTGFUNC)}, - {"ROWS", String.valueOf(xlfRows), String.valueOf(FTYPE_PTGFUNC)}, - {"COLUMNS", String.valueOf(xlfColumns), String.valueOf(FTYPE_PTGFUNC)}, - {"OFFSET", String.valueOf(xlfOffset), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ISTEXT",String.valueOf(xlfIstext), String.valueOf(FTYPE_PTGFUNC)}, - {"LOOKUP",String.valueOf(xlfLookup), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"EXPONDIST",String.valueOf(xlfExpondist), String.valueOf(FTYPE_PTGFUNC)}, - {"FDIST",String.valueOf(xlfFdist), String.valueOf(FTYPE_PTGFUNC)}, - {"FINV",String.valueOf(xlfFinv), String.valueOf(FTYPE_PTGFUNC)}, - {"FTEST",String.valueOf(xlfFtest), String.valueOf(FTYPE_PTGFUNC)}, - {"FISHER",String.valueOf(xlfFisher), String.valueOf(FTYPE_PTGFUNC)}, - {"FISHERINV",String.valueOf(xlfFisherinv), String.valueOf(FTYPE_PTGFUNC)}, - {"STANDARDIZE",String.valueOf(xlfStandardize), String.valueOf(FTYPE_PTGFUNC)}, - {"PERMUT",String.valueOf(xlfPermut), String.valueOf(FTYPE_PTGFUNC)}, - {"POISSON",String.valueOf(xlfPoisson), String.valueOf(FTYPE_PTGFUNC)}, - {"SUMXMY2",String.valueOf(xlfSumxmy2), String.valueOf(FTYPE_PTGFUNC)}, - {"SUMX2MY2",String.valueOf(xlfSumx2my2), String.valueOf(FTYPE_PTGFUNC)}, - {"SUMX2PY2",String.valueOf(xlfSumx2py2), String.valueOf(FTYPE_PTGFUNC)}, - {"ERFC", String.valueOf(xlfERFC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"CONFIDENCE", String.valueOf(xlfConfidence), String.valueOf(FTYPE_PTGFUNC)}, - {"CRITBINOM", String.valueOf(xlfCritbinom), String.valueOf(FTYPE_PTGFUNC)}, - {"DEVSQ", String.valueOf(xlfDevsq), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SERIESSUM",String.valueOf(xlfSERIESSUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"SUBTOTAL", String.valueOf(xlfSubtotal), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUMSQ", String.valueOf(xlfSumsq), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"CHIDIST", String.valueOf(xlfChidist), String.valueOf(FTYPE_PTGFUNC)}, - {"CHIINV", String.valueOf(xlfChiinv), String.valueOf(FTYPE_PTGFUNC)}, - {"CHITEST", String.valueOf(xlfChitest), String.valueOf(FTYPE_PTGFUNC)}, - {"GAMMADIST", String.valueOf(xlfGammadist), String.valueOf(FTYPE_PTGFUNC)}, - {"GAMMAINV", String.valueOf(xlfGammainv), String.valueOf(FTYPE_PTGFUNC)}, - {"GAMMALN", String.valueOf(xlfGammaln), String.valueOf(FTYPE_PTGFUNC)}, - {"GEOMEAN", String.valueOf(xlfGeomean), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"GROWTH", String.valueOf(xlfGrowth), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"HARMEAN", String.valueOf(xlfHarmean), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"HYPGEOMDIST", String.valueOf(xlfHypgeomdist), String.valueOf(FTYPE_PTGFUNC)}, - {"KURT", String.valueOf(xlfKurt), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"LOGEST", String.valueOf(xlfLogest), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"LOGINV",String.valueOf(xlfLoginv), String.valueOf(FTYPE_PTGFUNC)}, - {"LOGNORMDIST",String.valueOf(xlfLognormdist), String.valueOf(FTYPE_PTGFUNC)}, - {"NEGBINOMDIST",String.valueOf(xlfNegbinomdist), String.valueOf(FTYPE_PTGFUNC)}, - {"PROB",String.valueOf(xlfProb), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SKEW",String.valueOf(xlfSkew), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"STDEVPA",String.valueOf(xlfStDevPA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"STDEVP",String.valueOf(xlfStdevp), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"STDEVA",String.valueOf(xlfStDevA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TDIST",String.valueOf(xlfTdist), String.valueOf(FTYPE_PTGFUNC)}, - {"TINV",String.valueOf(xlfTinv), String.valueOf(FTYPE_PTGFUNC)}, - {"TTEST",String.valueOf(xlfTtest), String.valueOf(FTYPE_PTGFUNC)}, - {"VARA", String.valueOf(xlfVarA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"VARPA", String.valueOf(xlfVarPA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"WEIBULL",String.valueOf(xlfWeibull), String.valueOf(FTYPE_PTGFUNC)}, - {"ZTEST", String.valueOf(xlfZtest), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"ASC", String.valueOf(xlfAsc), String.valueOf(FTYPE_PTGFUNC)}, + {"DBCS", String.valueOf(xlfDbcs), String.valueOf(FTYPE_PTGFUNC)}, + {"MDETERM", String.valueOf(xlfMdeterm), String.valueOf(FTYPE_PTGFUNC)}, + {"SEARCHB", String.valueOf(xlfSearchb), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"TRANSPOSE", String.valueOf(xlfTranspose), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"BETAINV", String.valueOf(xlfBetainv), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"BETADIST", String.valueOf(xlfBetadist), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"TIMEVALUE", String.valueOf(xlfTimevalue), String.valueOf(FTYPE_PTGFUNC)}, + {"MINVERSE", String.valueOf(xlfMinverse), String.valueOf(FTYPE_PTGFUNC)}, + {"MDETERM", String.valueOf(xlfMdeterm), String.valueOf(FTYPE_PTGFUNC)}, + {"GETPIVOTDATA", String.valueOf(xlfGetPivotData), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"HYPERLINK", String.valueOf(xlfHyperlink), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"PHONETIC", String.valueOf(xlfPhonetic), String.valueOf(FTYPE_PTGFUNC)}, + {"PERCENTILE", String.valueOf(xlfPercentile), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"TRUNC", String.valueOf(xlfTrunc), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"PERCENTRANK", String.valueOf(xlfPercentrank), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"RIGHTB", String.valueOf(xlfRightb), String.valueOf(FTYPE_PTGFUNC)}, + {"REPLACEB", String.valueOf(xlfReplaceb), String.valueOf(FTYPE_PTGFUNC)}, + {"FINDB", String.valueOf(xlfFindb), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"MIDB", String.valueOf(xlfMidb), String.valueOf(FTYPE_PTGFUNC)}, + {"ROWS", String.valueOf(xlfRows), String.valueOf(FTYPE_PTGFUNC)}, + {"COLUMNS", String.valueOf(xlfColumns), String.valueOf(FTYPE_PTGFUNC)}, + {"OFFSET", String.valueOf(xlfOffset), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"ISTEXT", String.valueOf(xlfIstext), String.valueOf(FTYPE_PTGFUNC)}, + {"LOOKUP", String.valueOf(xlfLookup), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"EXPONDIST", String.valueOf(xlfExpondist), String.valueOf(FTYPE_PTGFUNC)}, + {"FDIST", String.valueOf(xlfFdist), String.valueOf(FTYPE_PTGFUNC)}, + {"FINV", String.valueOf(xlfFinv), String.valueOf(FTYPE_PTGFUNC)}, + {"FTEST", String.valueOf(xlfFtest), String.valueOf(FTYPE_PTGFUNC)}, + {"FISHER", String.valueOf(xlfFisher), String.valueOf(FTYPE_PTGFUNC)}, + {"FISHERINV", String.valueOf(xlfFisherinv), String.valueOf(FTYPE_PTGFUNC)}, + {"STANDARDIZE", String.valueOf(xlfStandardize), String.valueOf(FTYPE_PTGFUNC)}, + {"PERMUT", String.valueOf(xlfPermut), String.valueOf(FTYPE_PTGFUNC)}, + {"POISSON", String.valueOf(xlfPoisson), String.valueOf(FTYPE_PTGFUNC)}, + {"SUMXMY2", String.valueOf(xlfSumxmy2), String.valueOf(FTYPE_PTGFUNC)}, + {"SUMX2MY2", String.valueOf(xlfSumx2my2), String.valueOf(FTYPE_PTGFUNC)}, + {"SUMX2PY2", String.valueOf(xlfSumx2py2), String.valueOf(FTYPE_PTGFUNC)}, + {"ERFC", String.valueOf(xlfERFC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"CONFIDENCE", String.valueOf(xlfConfidence), String.valueOf(FTYPE_PTGFUNC)}, + {"CRITBINOM", String.valueOf(xlfCritbinom), String.valueOf(FTYPE_PTGFUNC)}, + {"DEVSQ", String.valueOf(xlfDevsq), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"SERIESSUM", String.valueOf(xlfSERIESSUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"SUBTOTAL", String.valueOf(xlfSubtotal), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"SUMSQ", String.valueOf(xlfSumsq), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"CHIDIST", String.valueOf(xlfChidist), String.valueOf(FTYPE_PTGFUNC)}, + {"CHIINV", String.valueOf(xlfChiinv), String.valueOf(FTYPE_PTGFUNC)}, + {"CHITEST", String.valueOf(xlfChitest), String.valueOf(FTYPE_PTGFUNC)}, + {"GAMMADIST", String.valueOf(xlfGammadist), String.valueOf(FTYPE_PTGFUNC)}, + {"GAMMAINV", String.valueOf(xlfGammainv), String.valueOf(FTYPE_PTGFUNC)}, + {"GAMMALN", String.valueOf(xlfGammaln), String.valueOf(FTYPE_PTGFUNC)}, + {"GEOMEAN", String.valueOf(xlfGeomean), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"GROWTH", String.valueOf(xlfGrowth), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"HARMEAN", String.valueOf(xlfHarmean), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"HYPGEOMDIST", String.valueOf(xlfHypgeomdist), String.valueOf(FTYPE_PTGFUNC)}, + {"KURT", String.valueOf(xlfKurt), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"LOGEST", String.valueOf(xlfLogest), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"LOGINV", String.valueOf(xlfLoginv), String.valueOf(FTYPE_PTGFUNC)}, + {"LOGNORMDIST", String.valueOf(xlfLognormdist), String.valueOf(FTYPE_PTGFUNC)}, + {"NEGBINOMDIST", String.valueOf(xlfNegbinomdist), String.valueOf(FTYPE_PTGFUNC)}, + {"PROB", String.valueOf(xlfProb), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"SKEW", String.valueOf(xlfSkew), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"STDEVPA", String.valueOf(xlfStDevPA), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"STDEVP", String.valueOf(xlfStdevp), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"STDEVA", String.valueOf(xlfStDevA), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"TDIST", String.valueOf(xlfTdist), String.valueOf(FTYPE_PTGFUNC)}, + {"TINV", String.valueOf(xlfTinv), String.valueOf(FTYPE_PTGFUNC)}, + {"TTEST", String.valueOf(xlfTtest), String.valueOf(FTYPE_PTGFUNC)}, + {"VARA", String.valueOf(xlfVarA), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"VARPA", String.valueOf(xlfVarPA), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"WEIBULL", String.valueOf(xlfWeibull), String.valueOf(FTYPE_PTGFUNC)}, + {"ZTEST", String.valueOf(xlfZtest), String.valueOf(FTYPE_PTGFUNCVAR)}, }; - - // Contains function name, id and type of ALL Formulas (PtgFuncs, PtgFuncVars and Add-in PtgFuncVars) - + + // Contains function name, id and type of ALL Formulas (PtgFuncs, PtgFuncVars and Add-in PtgFuncVars) + // fetch the pattern match from: http://office.microsoft.com/client/helpcategory.aspx?CategoryID=CH100645029990&lcid=1033&NS=EXCEL&Version=12&CTT=4 - public static String[][] recArr = { - {"Pi", String.valueOf(xlfPi), String.valueOf(FTYPE_PTGFUNC)}, - {"Round",String.valueOf(xlfRound), String.valueOf(FTYPE_PTGFUNC)}, - {"Rept",String.valueOf(xlfRept), String.valueOf(FTYPE_PTGFUNC)}, - {"Mid",String.valueOf(xlfMid), String.valueOf(FTYPE_PTGFUNC)}, - {"Mod",String.valueOf(xlfMod), String.valueOf(FTYPE_PTGFUNC)}, - {"MMult",String.valueOf(xlfMmult), String.valueOf(FTYPE_PTGFUNC)}, - {"Rand",String.valueOf(xlfRand), String.valueOf(FTYPE_PTGFUNC)}, - {"Date",String.valueOf(xlfDate), String.valueOf(FTYPE_PTGFUNC)}, - {"Time",String.valueOf(xlfTime), String.valueOf(FTYPE_PTGFUNC)}, - {"Day",String.valueOf(xlfDay), String.valueOf(FTYPE_PTGFUNC)}, - {"Now",String.valueOf(xlfNow), String.valueOf(FTYPE_PTGFUNC)}, - {"TAN", String.valueOf(xlfTan), String.valueOf(FTYPE_PTGFUNC)}, - {"Atan2",String.valueOf(xlfAtan2), String.valueOf(FTYPE_PTGFUNC)}, - {"Replace",String.valueOf(xlfReplace), String.valueOf(FTYPE_PTGFUNC)}, - {"Exact",String.valueOf(xlfExact), String.valueOf(FTYPE_PTGFUNC)}, - {"Trim",String.valueOf(xlfTrim), String.valueOf(FTYPE_PTGFUNC)}, - {"Text",String.valueOf(xlfText), String.valueOf(FTYPE_PTGFUNC)}, - {"Roundup",String.valueOf(xlfRoundup), String.valueOf(FTYPE_PTGFUNC)}, - {"RoundDown",String.valueOf(xlfRounddown), String.valueOf(FTYPE_PTGFUNC)}, - {"today",String.valueOf(xlfToday), String.valueOf(FTYPE_PTGFUNC)}, - {"Combin",String.valueOf(xlfCombin), String.valueOf(FTYPE_PTGFUNC)}, - {"Floor",String.valueOf(xlfFloor), String.valueOf(FTYPE_PTGFUNC)}, - {"Ceiling",String.valueOf(xlfCeiling), String.valueOf(FTYPE_PTGFUNC)}, - {"Power",String.valueOf(xlfPower), String.valueOf(FTYPE_PTGFUNC)}, - {"Hour", String.valueOf(xlfHour), String.valueOf(FTYPE_PTGFUNC)}, - {"Minute", String.valueOf(xlfMinute), String.valueOf(FTYPE_PTGFUNC)}, - {"Month", String.valueOf(xlfMonth), String.valueOf(FTYPE_PTGFUNC)}, - {"Year", String.valueOf(xlfYear), String.valueOf(FTYPE_PTGFUNC)}, - {"Second", String.valueOf(xlfSecond), String.valueOf(FTYPE_PTGFUNC)}, - {"Quartile", String.valueOf(xlfQuartile), String.valueOf(FTYPE_PTGFUNC)}, - {"Frequency", String.valueOf(xlfFrequency), String.valueOf(FTYPE_PTGFUNC)}, - {"Linest", String.valueOf(xlfLinest), String.valueOf(FTYPE_PTGFUNC)}, - {"Correl", String.valueOf(xlfCorrel), String.valueOf(FTYPE_PTGFUNC)}, - {"Slope", String.valueOf(xlfSlope), String.valueOf(FTYPE_PTGFUNC)}, - {"Intercept", String.valueOf(xlfIntercept), String.valueOf(FTYPE_PTGFUNC)}, - {"Pearson", String.valueOf(xlfPearson), String.valueOf(FTYPE_PTGFUNC)}, - {"Rsq", String.valueOf(xlfRsq), String.valueOf(FTYPE_PTGFUNC)}, - {"Steyx", String.valueOf(xlfSteyx), String.valueOf(FTYPE_PTGFUNC)}, - {"Forecast", String.valueOf(xlfForecast), String.valueOf(FTYPE_PTGFUNC)}, - {"Covar", String.valueOf(xlfCovar), String.valueOf(FTYPE_PTGFUNC)}, - {"IsNumber", String.valueOf(xlfIsnumber), String.valueOf(FTYPE_PTGFUNC)}, - {"DAVERAGE", String.valueOf(xlfDaverage), String.valueOf(FTYPE_PTGFUNC)}, - {"DCOUNT", String.valueOf(xlfDcount), String.valueOf(FTYPE_PTGFUNC)}, - {"DCOUNTA", String.valueOf(xlfDcounta), String.valueOf(FTYPE_PTGFUNC)}, - {"DGET", String.valueOf(xlfDget), String.valueOf(FTYPE_PTGFUNC)}, - {"DMIN", String.valueOf(xlfDmin), String.valueOf(FTYPE_PTGFUNC)}, - {"DMAX", String.valueOf(xlfDmax), String.valueOf(FTYPE_PTGFUNC)}, - {"DPRODUCT", String.valueOf(xlfDproduct), String.valueOf(FTYPE_PTGFUNC)}, - {"DSTDEVP", String.valueOf(xlfDstdevp), String.valueOf(FTYPE_PTGFUNC)}, - {"DSTDEV", String.valueOf(xlfDstdev), String.valueOf(FTYPE_PTGFUNC)}, - {"DSUM", String.valueOf(xlfDsum), String.valueOf(FTYPE_PTGFUNC)}, - {"DVAR", String.valueOf(xlfDvar), String.valueOf(FTYPE_PTGFUNC)}, - {"DVARP", String.valueOf(xlfDvarp), String.valueOf(FTYPE_PTGFUNC)}, - {"SQRT", String.valueOf(xlfSqrt), String.valueOf(FTYPE_PTGFUNC)}, - {"NA", String.valueOf(xlfNa), String.valueOf(FTYPE_PTGFUNC)}, - {"EXP", String.valueOf(xlfExp), String.valueOf(FTYPE_PTGFUNC)}, - {"MIRR", String.valueOf(xlfMirr), String.valueOf(FTYPE_PTGFUNC)}, - {"SLN", String.valueOf(xlfSln), String.valueOf(FTYPE_PTGFUNC)}, - {"SYD", String.valueOf(xlfSyd), String.valueOf(FTYPE_PTGFUNC)}, - {"ISPMT", String.valueOf(xlfIspmt), String.valueOf(FTYPE_PTGFUNC)}, - {"UPPER", String.valueOf(xlfUpper), String.valueOf(FTYPE_PTGFUNC)}, - {"LOWER", String.valueOf(xlfLower), String.valueOf(FTYPE_PTGFUNC)}, - {"LEN", String.valueOf(xlfLen), String.valueOf(FTYPE_PTGFUNC)}, - {"ISLOGICAL", String.valueOf(xlfIslogical), String.valueOf(FTYPE_PTGFUNC)}, - {"ISERROR", String.valueOf(XLF_IS_ERROR), String.valueOf(FTYPE_PTGFUNC)}, - {"ISNONTEXT", String.valueOf(xlfIsnontext), String.valueOf(FTYPE_PTGFUNC)}, - {"ISBLANK", String.valueOf(xlfIsblank), String.valueOf(FTYPE_PTGFUNC)}, - {"ISREF", String.valueOf(xlfIsref), String.valueOf(FTYPE_PTGFUNC)}, - {"SIN", String.valueOf(xlfSin), String.valueOf(FTYPE_PTGFUNC)}, - {"SINH", String.valueOf(xlfSinh), String.valueOf(FTYPE_PTGFUNC)}, - {"ASIN", String.valueOf(xlfAsin), String.valueOf(FTYPE_PTGFUNC)}, - {"ASINH", String.valueOf(xlfAsinh), String.valueOf(FTYPE_PTGFUNC)}, - {"COS", String.valueOf(xlfCos), String.valueOf(FTYPE_PTGFUNC)}, - {"COSH", String.valueOf(xlfCosh), String.valueOf(FTYPE_PTGFUNC)}, - {"ACOS", String.valueOf(xlfAcos), String.valueOf(FTYPE_PTGFUNC)}, - {"ACOSH", String.valueOf(xlfAcosh), String.valueOf(FTYPE_PTGFUNC)}, - {"ATAN", String.valueOf(xlfAtan), String.valueOf(FTYPE_PTGFUNC)}, - {"ATANH", String.valueOf(xlfAtanh), String.valueOf(FTYPE_PTGFUNC)}, - {"INT", String.valueOf(xlfInt), String.valueOf(FTYPE_PTGFUNC)}, - {"ABS", String.valueOf(xlfAbs), String.valueOf(FTYPE_PTGFUNC)}, - {"NOT", String.valueOf(xlfNot), String.valueOf(FTYPE_PTGFUNC)}, - {"DEGREES", String.valueOf(xlfDegrees), String.valueOf(FTYPE_PTGFUNC)}, - {"SIGN", String.valueOf(xlfSign), String.valueOf(FTYPE_PTGFUNC)}, - {"EVEN", String.valueOf(xlfEven), String.valueOf(FTYPE_PTGFUNC)}, - {"ODD", String.valueOf(xlfOdd), String.valueOf(FTYPE_PTGFUNC)}, - {"LN", String.valueOf(xlfLn), String.valueOf(FTYPE_PTGFUNC)}, - {"FACT", String.valueOf(xlfFact), String.valueOf(FTYPE_PTGFUNC)}, - {"RADIANS", String.valueOf(xlfRadians), String.valueOf(FTYPE_PTGFUNC)}, - {"PROPER", String.valueOf(xlfProper), String.valueOf(FTYPE_PTGFUNC)}, - {"CHAR", String.valueOf(xlfChar), String.valueOf(FTYPE_PTGFUNC)}, - {"ERROR.TYPE", String.valueOf(xlfErrorType), String.valueOf(FTYPE_PTGFUNC)}, - {"T", String.valueOf(xlfT), String.valueOf(FTYPE_PTGFUNC)}, - {"LOG10", String.valueOf(xlfLog10), String.valueOf(FTYPE_PTGFUNC)}, - {"VALUE", String.valueOf(xlfValue), String.valueOf(FTYPE_PTGFUNC)}, - {"CODE", String.valueOf(xlfCode), String.valueOf(FTYPE_PTGFUNC)}, - {"N", String.valueOf(xlfN), String.valueOf(FTYPE_PTGFUNC)}, - {"DATEVALUE", String.valueOf(xlfDatevalue), String.valueOf(FTYPE_PTGFUNC)}, - {"SMALL", String.valueOf(xlfSmall), String.valueOf(FTYPE_PTGFUNC)}, - {"LARGE", String.valueOf(xlfLarge), String.valueOf(FTYPE_PTGFUNC)}, - {"NORMDIST",String.valueOf(xlfNormdist), String.valueOf(FTYPE_PTGFUNC)}, - {"NORMSDIST",String.valueOf(xlfNormsdist), String.valueOf(FTYPE_PTGFUNC)}, - {"NORMSINV",String.valueOf(xlfNormsinv), String.valueOf(FTYPE_PTGFUNC)}, - {"NORMINV",String.valueOf(xlfNorminv), String.valueOf(FTYPE_PTGFUNC)}, - {"LENB", String.valueOf(xlfLenb), String.valueOf(FTYPE_PTGFUNC)}, - {"INFO",String.valueOf(xlfInfo), String.valueOf(FTYPE_PTGFUNC)}, - {"LEFTB",String.valueOf(xlfLeftb), String.valueOf(FTYPE_PTGFUNC)}, - {"TRUE",String.valueOf(xlfTrue), String.valueOf(FTYPE_PTGFUNC)}, - {"FALSE",String.valueOf(xlfFalse), String.valueOf(FTYPE_PTGFUNC)}, - // PtgFuncVars - {"COUNT", String.valueOf(XLF_COUNT), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"COUNTA", String.valueOf(xlfCounta), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"COUNTIF", String.valueOf(xlfCountif), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"COUNTBLANK", String.valueOf(xlfCountblank), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"IF", String.valueOf(XLF_IS), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ISNA", String.valueOf(XLF_IS_NA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ISERR", String.valueOf(xlfIserr), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUM", String.valueOf(XLF_SUM), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUMIF",String.valueOf(XLF_SUM_IF), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"AVERAGE", String.valueOf(XLF_AVERAGE), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MINA", String.valueOf(xlfMinA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MIN", String.valueOf(XLF_MIN), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MAXA", String.valueOf(xlfMaxA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MAX", String.valueOf(XLF_MAX), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ROW", String.valueOf(XLF_ROW), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"COLUMN", String.valueOf(xlfColumn), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"NPV", String.valueOf(xlfNpv), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"PMT", String.valueOf(xlfPmt), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"DB", String.valueOf(xlfDb), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"FIND", String.valueOf(xlfFind), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"DAYS360", String.valueOf(xlfDays360), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"LEFT",String.valueOf(xlfLeft), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"LOG",String.valueOf(xlfLog), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MEDIAN", String.valueOf(xlfMedian), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MODE", String.valueOf(xlfMode), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"RANK", String.valueOf(xlfRank), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"RIGHT",String.valueOf(xlfRight), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"STDEV", String.valueOf(xlfStdev), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"VAR", String.valueOf(xlfVar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"VARP", String.valueOf(xlfVarp), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TANH", String.valueOf(xlfTanh), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"VLOOKUP", String.valueOf(xlfVlookup), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"HLOOKUP", String.valueOf(xlfHlookup), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"CONCATENATE", String.valueOf(xlfConcatenate), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"INDEX", String.valueOf(xlfIndex), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MATCH", String.valueOf(xlfMatch), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"FIXED", String.valueOf(xlfFixed), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"AND", String.valueOf(xlfAnd), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"OR", String.valueOf(xlfOr), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"CHOOSE", String.valueOf(xlfChoose), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ADDRESS", String.valueOf(xlfAddress), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ROMAN", String.valueOf(xlfRoman), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"DOLLAR", String.valueOf(xlfDollar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"USDOLLAR", String.valueOf(xlfUsdollar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"AVEDEV", String.valueOf(xlfAvedev), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUBSTITUTE", String.valueOf(xlfSubstitute), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"PRODUCT", String.valueOf(xlfProduct), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SEARCH", String.valueOf(xlfSearch), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"AVERAGEA", String.valueOf(xlfAverageA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TREND",String.valueOf(xlfTrend), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUMPRODUCT",String.valueOf(xlfSumproduct), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"INDIRECT", String.valueOf(XLF_INDIRECT), String.valueOf(FTYPE_PTGFUNCVAR)}, - // Add-in Formulas - // Financial Formulas - {"ACCRINTM", String.valueOf(xlfAccrintm), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ACCRINT", String.valueOf(xlfAccrint), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPDAYBS", String.valueOf(xlfCoupDayBS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPDAYS", String.valueOf(xlfCoupDays), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PV", String.valueOf(xlfPv), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"FV", String.valueOf(xlfFv), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IPMT", String.valueOf(xlfIpmt), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"CUMIPMT", String.valueOf(xlfCumIPmt), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"CUMPRINC", String.valueOf(xlfCumPrinc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPNCD", String.valueOf(xlfCoupNCD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPDAYSNC", String.valueOf(xlfCoupDaysNC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPPCD", String.valueOf(xlfCoupPCD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPNUM", String.valueOf(xlfCoupNUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DOLLARDE", String.valueOf(xlfDollarDE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DOLLARFR", String.valueOf(xlfDollarFR), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"EFFECT", String.valueOf(xlfEffect), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"INTRATE", String.valueOf(xlfINTRATE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IRR", String.valueOf(xlfIrr), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"XIRR", String.valueOf(xlfXIRR), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"XNPV", String.valueOf(xlfXNPV), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"RATE", String.valueOf(xlfRate), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"YIELD", String.valueOf(xlfYIELD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PRICE", String.valueOf(xlfPRICE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PRICEDISC", String.valueOf(xlfPRICEDISC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DISC", String.valueOf(xlfDISC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PRICEMAT", String.valueOf(xlfPRICEMAT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DURATION", String.valueOf(xlfDURATION), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"MDURATION", String.valueOf(xlfMDURATION), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"NPER", String.valueOf(xlfNper), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"TBILLEQ", String.valueOf(xlfTBillEq), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"TBILLPRICE", String.valueOf(xlfTBillPrice), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"TBILLYIELD", String.valueOf(xlfTBillYield), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"YIELDDISC", String.valueOf(xlfYieldDisc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"YIELDMAT", String.valueOf(xlfYieldMat), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PPMT", String.valueOf(xlfPpmt), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"FVSCHEDULE", String.valueOf(xlfFVSchedule), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"AMORLINC", String.valueOf(xlfAmorlinc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"AMORDEGRC", String.valueOf(xlfAmordegrc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ODDFPRICE", String.valueOf(xlfOddFPrice), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ODDLPRICE", String.valueOf(xlfOddLPrice), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ODDFYIELD", String.valueOf(xlfOddFYield), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ODDLYIELD", String.valueOf(xlfOddLYield), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"NOMINAL", String.valueOf(xlfNOMINAL), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"VDB", String.valueOf(xlfVdb), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DDB", String.valueOf(xlfDdb), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"RECEIVED", String.valueOf(xlfRECEIVED), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Engineering Formulas - {"BIN2DEC", String.valueOf(xlfBIN2DEC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"BIN2HEX", String.valueOf(xlfBIN2HEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"BIN2OCT", String.valueOf(xlfBIN2OCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DEC2BIN", String.valueOf(xlfDEC2BIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DEC2HEX", String.valueOf(xlfDEC2HEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DEC2OCT", String.valueOf(xlfDEC2OCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"HEX2BIN", String.valueOf(xlfHEX2BIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"HEX2DEC", String.valueOf(xlfHEX2DEC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"HEX2OCT", String.valueOf(xlfHEX2OCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"OCT2BIN", String.valueOf(xlfOCT2BIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"OCT2DEC", String.valueOf(xlfOCT2DEC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"OCT2HEX", String.valueOf(xlfOCT2HEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COMPLEX", String.valueOf(xlfCOMPLEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"GESTEP", String.valueOf(xlfGESTEP), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DELTA", String.valueOf(xlfDELTA), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMAGINARY", String.valueOf(xlfIMAGINARY), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMREAL", String.valueOf(xlfIMREAL), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMARGUMENT", String.valueOf(xlfIMARGUMENT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMABS", String.valueOf(xlfIMABS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMDIV", String.valueOf(xlfIMDIV), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMCONJUGATE", String.valueOf(xlfIMCONJUGATE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMCOS", String.valueOf(xlfIMCOS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMSIN", String.valueOf(xlfIMSIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMEXP", String.valueOf(xlfIMEXP), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMPOWER", String.valueOf(xlfIMPOWER), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMSQRT", String.valueOf(xlfIMSQRT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMSUB", String.valueOf(xlfIMSUB), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMSUM", String.valueOf(xlfIMSUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMPRODUCT", String.valueOf(xlfIMPRODUCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMLN", String.valueOf(xlfIMLN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMLOG10", String.valueOf(xlfIMLOG10), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMLOG2", String.valueOf(xlfIMLOG2), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"CONVERT", String.valueOf(xlfCONVERT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ERF", String.valueOf(xlfERF), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Math Add-In Formulas - {"FACTDOUBLE", String.valueOf(xlfDOUBLEFACT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"GCD", String.valueOf(xlfGCD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"LCM", String.valueOf(xlfLCM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"MROUND", String.valueOf(xlfMROUND), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"MULTINOMIAL", String.valueOf(xlfMULTINOMIAL), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"QUOTIENT", String.valueOf(xlfQUOTIENT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"RANDBETWEEN", String.valueOf(xlfRANDBETWEEN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"SERIESSUM",String.valueOf(xlfSERIESSUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"SQRTPI", String.valueOf(xlfSQRTPI), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"SUMIFS", String.valueOf(xlfSUMIFS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Information Add-Ins - {"ISEVEN", String.valueOf(xlfISEVEN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ISODD", String.valueOf(xlfISODD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Date/Time Add-in Formulas - {"NETWORKDAYS", String.valueOf(xlfNETWORKDAYS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"EDATE", String.valueOf(xlfEDATE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"EOMONTH", String.valueOf(xlfEOMONTH), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"WEEKNUM", String.valueOf(xlfWEEKNUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"WEEKDAY", String.valueOf(xlfWeekday), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"WORKDAY", String.valueOf(xlfWORKDAY), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"YEARFRAC", String.valueOf(xlfYEARFRAC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Statistical - {"AVERAGEIF", String.valueOf(xlfAVERAGEIF), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"AVERAGEIFS", String.valueOf(xlfAVERAGEIFS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUNTIFS", String.valueOf(xlfCOUNTIFS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Logical - {"IFERROR", String.valueOf(xlfIFERROR), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - }; - + public static String[][] recArr = { + {"Pi", String.valueOf(xlfPi), String.valueOf(FTYPE_PTGFUNC)}, + {"Round", String.valueOf(xlfRound), String.valueOf(FTYPE_PTGFUNC)}, + {"Rept", String.valueOf(xlfRept), String.valueOf(FTYPE_PTGFUNC)}, + {"Mid", String.valueOf(xlfMid), String.valueOf(FTYPE_PTGFUNC)}, + {"Mod", String.valueOf(xlfMod), String.valueOf(FTYPE_PTGFUNC)}, + {"MMult", String.valueOf(xlfMmult), String.valueOf(FTYPE_PTGFUNC)}, + {"Rand", String.valueOf(xlfRand), String.valueOf(FTYPE_PTGFUNC)}, + {"Date", String.valueOf(xlfDate), String.valueOf(FTYPE_PTGFUNC)}, + {"Time", String.valueOf(xlfTime), String.valueOf(FTYPE_PTGFUNC)}, + {"Day", String.valueOf(xlfDay), String.valueOf(FTYPE_PTGFUNC)}, + {"Now", String.valueOf(xlfNow), String.valueOf(FTYPE_PTGFUNC)}, + {"TAN", String.valueOf(xlfTan), String.valueOf(FTYPE_PTGFUNC)}, + {"Atan2", String.valueOf(xlfAtan2), String.valueOf(FTYPE_PTGFUNC)}, + {"Replace", String.valueOf(xlfReplace), String.valueOf(FTYPE_PTGFUNC)}, + {"Exact", String.valueOf(xlfExact), String.valueOf(FTYPE_PTGFUNC)}, + {"Trim", String.valueOf(xlfTrim), String.valueOf(FTYPE_PTGFUNC)}, + {"Text", String.valueOf(xlfText), String.valueOf(FTYPE_PTGFUNC)}, + {"Roundup", String.valueOf(xlfRoundup), String.valueOf(FTYPE_PTGFUNC)}, + {"RoundDown", String.valueOf(xlfRounddown), String.valueOf(FTYPE_PTGFUNC)}, + {"today", String.valueOf(xlfToday), String.valueOf(FTYPE_PTGFUNC)}, + {"Combin", String.valueOf(xlfCombin), String.valueOf(FTYPE_PTGFUNC)}, + {"Floor", String.valueOf(xlfFloor), String.valueOf(FTYPE_PTGFUNC)}, + {"Ceiling", String.valueOf(xlfCeiling), String.valueOf(FTYPE_PTGFUNC)}, + {"Power", String.valueOf(xlfPower), String.valueOf(FTYPE_PTGFUNC)}, + {"Hour", String.valueOf(xlfHour), String.valueOf(FTYPE_PTGFUNC)}, + {"Minute", String.valueOf(xlfMinute), String.valueOf(FTYPE_PTGFUNC)}, + {"Month", String.valueOf(xlfMonth), String.valueOf(FTYPE_PTGFUNC)}, + {"Year", String.valueOf(xlfYear), String.valueOf(FTYPE_PTGFUNC)}, + {"Second", String.valueOf(xlfSecond), String.valueOf(FTYPE_PTGFUNC)}, + {"Quartile", String.valueOf(xlfQuartile), String.valueOf(FTYPE_PTGFUNC)}, + {"Frequency", String.valueOf(xlfFrequency), String.valueOf(FTYPE_PTGFUNC)}, + {"Linest", String.valueOf(xlfLinest), String.valueOf(FTYPE_PTGFUNC)}, + {"Correl", String.valueOf(xlfCorrel), String.valueOf(FTYPE_PTGFUNC)}, + {"Slope", String.valueOf(xlfSlope), String.valueOf(FTYPE_PTGFUNC)}, + {"Intercept", String.valueOf(xlfIntercept), String.valueOf(FTYPE_PTGFUNC)}, + {"Pearson", String.valueOf(xlfPearson), String.valueOf(FTYPE_PTGFUNC)}, + {"Rsq", String.valueOf(xlfRsq), String.valueOf(FTYPE_PTGFUNC)}, + {"Steyx", String.valueOf(xlfSteyx), String.valueOf(FTYPE_PTGFUNC)}, + {"Forecast", String.valueOf(xlfForecast), String.valueOf(FTYPE_PTGFUNC)}, + {"Covar", String.valueOf(xlfCovar), String.valueOf(FTYPE_PTGFUNC)}, + {"IsNumber", String.valueOf(xlfIsnumber), String.valueOf(FTYPE_PTGFUNC)}, + {"DAVERAGE", String.valueOf(xlfDaverage), String.valueOf(FTYPE_PTGFUNC)}, + {"DCOUNT", String.valueOf(xlfDcount), String.valueOf(FTYPE_PTGFUNC)}, + {"DCOUNTA", String.valueOf(xlfDcounta), String.valueOf(FTYPE_PTGFUNC)}, + {"DGET", String.valueOf(xlfDget), String.valueOf(FTYPE_PTGFUNC)}, + {"DMIN", String.valueOf(xlfDmin), String.valueOf(FTYPE_PTGFUNC)}, + {"DMAX", String.valueOf(xlfDmax), String.valueOf(FTYPE_PTGFUNC)}, + {"DPRODUCT", String.valueOf(xlfDproduct), String.valueOf(FTYPE_PTGFUNC)}, + {"DSTDEVP", String.valueOf(xlfDstdevp), String.valueOf(FTYPE_PTGFUNC)}, + {"DSTDEV", String.valueOf(xlfDstdev), String.valueOf(FTYPE_PTGFUNC)}, + {"DSUM", String.valueOf(xlfDsum), String.valueOf(FTYPE_PTGFUNC)}, + {"DVAR", String.valueOf(xlfDvar), String.valueOf(FTYPE_PTGFUNC)}, + {"DVARP", String.valueOf(xlfDvarp), String.valueOf(FTYPE_PTGFUNC)}, + {"SQRT", String.valueOf(xlfSqrt), String.valueOf(FTYPE_PTGFUNC)}, + {"NA", String.valueOf(xlfNa), String.valueOf(FTYPE_PTGFUNC)}, + {"EXP", String.valueOf(xlfExp), String.valueOf(FTYPE_PTGFUNC)}, + {"MIRR", String.valueOf(xlfMirr), String.valueOf(FTYPE_PTGFUNC)}, + {"SLN", String.valueOf(xlfSln), String.valueOf(FTYPE_PTGFUNC)}, + {"SYD", String.valueOf(xlfSyd), String.valueOf(FTYPE_PTGFUNC)}, + {"ISPMT", String.valueOf(xlfIspmt), String.valueOf(FTYPE_PTGFUNC)}, + {"UPPER", String.valueOf(xlfUpper), String.valueOf(FTYPE_PTGFUNC)}, + {"LOWER", String.valueOf(xlfLower), String.valueOf(FTYPE_PTGFUNC)}, + {"LEN", String.valueOf(xlfLen), String.valueOf(FTYPE_PTGFUNC)}, + {"ISLOGICAL", String.valueOf(xlfIslogical), String.valueOf(FTYPE_PTGFUNC)}, + {"ISERROR", String.valueOf(XLF_IS_ERROR), String.valueOf(FTYPE_PTGFUNC)}, + {"ISNONTEXT", String.valueOf(xlfIsnontext), String.valueOf(FTYPE_PTGFUNC)}, + {"ISBLANK", String.valueOf(xlfIsblank), String.valueOf(FTYPE_PTGFUNC)}, + {"ISREF", String.valueOf(xlfIsref), String.valueOf(FTYPE_PTGFUNC)}, + {"SIN", String.valueOf(xlfSin), String.valueOf(FTYPE_PTGFUNC)}, + {"SINH", String.valueOf(xlfSinh), String.valueOf(FTYPE_PTGFUNC)}, + {"ASIN", String.valueOf(xlfAsin), String.valueOf(FTYPE_PTGFUNC)}, + {"ASINH", String.valueOf(xlfAsinh), String.valueOf(FTYPE_PTGFUNC)}, + {"COS", String.valueOf(xlfCos), String.valueOf(FTYPE_PTGFUNC)}, + {"COSH", String.valueOf(xlfCosh), String.valueOf(FTYPE_PTGFUNC)}, + {"ACOS", String.valueOf(xlfAcos), String.valueOf(FTYPE_PTGFUNC)}, + {"ACOSH", String.valueOf(xlfAcosh), String.valueOf(FTYPE_PTGFUNC)}, + {"ATAN", String.valueOf(xlfAtan), String.valueOf(FTYPE_PTGFUNC)}, + {"ATANH", String.valueOf(xlfAtanh), String.valueOf(FTYPE_PTGFUNC)}, + {"INT", String.valueOf(xlfInt), String.valueOf(FTYPE_PTGFUNC)}, + {"ABS", String.valueOf(xlfAbs), String.valueOf(FTYPE_PTGFUNC)}, + {"NOT", String.valueOf(xlfNot), String.valueOf(FTYPE_PTGFUNC)}, + {"DEGREES", String.valueOf(xlfDegrees), String.valueOf(FTYPE_PTGFUNC)}, + {"SIGN", String.valueOf(xlfSign), String.valueOf(FTYPE_PTGFUNC)}, + {"EVEN", String.valueOf(xlfEven), String.valueOf(FTYPE_PTGFUNC)}, + {"ODD", String.valueOf(xlfOdd), String.valueOf(FTYPE_PTGFUNC)}, + {"LN", String.valueOf(xlfLn), String.valueOf(FTYPE_PTGFUNC)}, + {"FACT", String.valueOf(xlfFact), String.valueOf(FTYPE_PTGFUNC)}, + {"RADIANS", String.valueOf(xlfRadians), String.valueOf(FTYPE_PTGFUNC)}, + {"PROPER", String.valueOf(xlfProper), String.valueOf(FTYPE_PTGFUNC)}, + {"CHAR", String.valueOf(xlfChar), String.valueOf(FTYPE_PTGFUNC)}, + {"ERROR.TYPE", String.valueOf(xlfErrorType), String.valueOf(FTYPE_PTGFUNC)}, + {"T", String.valueOf(xlfT), String.valueOf(FTYPE_PTGFUNC)}, + {"LOG10", String.valueOf(xlfLog10), String.valueOf(FTYPE_PTGFUNC)}, + {"VALUE", String.valueOf(xlfValue), String.valueOf(FTYPE_PTGFUNC)}, + {"CODE", String.valueOf(xlfCode), String.valueOf(FTYPE_PTGFUNC)}, + {"N", String.valueOf(xlfN), String.valueOf(FTYPE_PTGFUNC)}, + {"DATEVALUE", String.valueOf(xlfDatevalue), String.valueOf(FTYPE_PTGFUNC)}, + {"SMALL", String.valueOf(xlfSmall), String.valueOf(FTYPE_PTGFUNC)}, + {"LARGE", String.valueOf(xlfLarge), String.valueOf(FTYPE_PTGFUNC)}, + {"NORMDIST", String.valueOf(xlfNormdist), String.valueOf(FTYPE_PTGFUNC)}, + {"NORMSDIST", String.valueOf(xlfNormsdist), String.valueOf(FTYPE_PTGFUNC)}, + {"NORMSINV", String.valueOf(xlfNormsinv), String.valueOf(FTYPE_PTGFUNC)}, + {"NORMINV", String.valueOf(xlfNorminv), String.valueOf(FTYPE_PTGFUNC)}, + {"LENB", String.valueOf(xlfLenb), String.valueOf(FTYPE_PTGFUNC)}, + {"INFO", String.valueOf(xlfInfo), String.valueOf(FTYPE_PTGFUNC)}, + {"LEFTB", String.valueOf(xlfLeftb), String.valueOf(FTYPE_PTGFUNC)}, + {"TRUE", String.valueOf(xlfTrue), String.valueOf(FTYPE_PTGFUNC)}, + {"FALSE", String.valueOf(xlfFalse), String.valueOf(FTYPE_PTGFUNC)}, + // PtgFuncVars + {"COUNT", String.valueOf(XLF_COUNT), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"COUNTA", String.valueOf(xlfCounta), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"COUNTIF", String.valueOf(xlfCountif), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"COUNTBLANK", String.valueOf(xlfCountblank), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"IF", String.valueOf(XLF_IS), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"ISNA", String.valueOf(XLF_IS_NA), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"ISERR", String.valueOf(xlfIserr), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"SUM", String.valueOf(XLF_SUM), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"SUMIF", String.valueOf(XLF_SUM_IF), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"AVERAGE", String.valueOf(XLF_AVERAGE), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"MINA", String.valueOf(xlfMinA), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"MIN", String.valueOf(XLF_MIN), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"MAXA", String.valueOf(xlfMaxA), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"MAX", String.valueOf(XLF_MAX), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"ROW", String.valueOf(XLF_ROW), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"COLUMN", String.valueOf(xlfColumn), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"NPV", String.valueOf(xlfNpv), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"PMT", String.valueOf(xlfPmt), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"DB", String.valueOf(xlfDb), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"FIND", String.valueOf(xlfFind), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"DAYS360", String.valueOf(xlfDays360), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"LEFT", String.valueOf(xlfLeft), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"LOG", String.valueOf(xlfLog), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"MEDIAN", String.valueOf(xlfMedian), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"MODE", String.valueOf(xlfMode), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"RANK", String.valueOf(xlfRank), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"RIGHT", String.valueOf(xlfRight), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"STDEV", String.valueOf(xlfStdev), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"VAR", String.valueOf(xlfVar), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"VARP", String.valueOf(xlfVarp), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"TANH", String.valueOf(xlfTanh), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"VLOOKUP", String.valueOf(xlfVlookup), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"HLOOKUP", String.valueOf(xlfHlookup), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"CONCATENATE", String.valueOf(xlfConcatenate), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"INDEX", String.valueOf(xlfIndex), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"MATCH", String.valueOf(xlfMatch), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"FIXED", String.valueOf(xlfFixed), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"AND", String.valueOf(xlfAnd), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"OR", String.valueOf(xlfOr), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"CHOOSE", String.valueOf(xlfChoose), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"ADDRESS", String.valueOf(xlfAddress), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"ROMAN", String.valueOf(xlfRoman), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"DOLLAR", String.valueOf(xlfDollar), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"USDOLLAR", String.valueOf(xlfUsdollar), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"AVEDEV", String.valueOf(xlfAvedev), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"SUBSTITUTE", String.valueOf(xlfSubstitute), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"PRODUCT", String.valueOf(xlfProduct), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"SEARCH", String.valueOf(xlfSearch), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"AVERAGEA", String.valueOf(xlfAverageA), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"TREND", String.valueOf(xlfTrend), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"SUMPRODUCT", String.valueOf(xlfSumproduct), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"INDIRECT", String.valueOf(XLF_INDIRECT), String.valueOf(FTYPE_PTGFUNCVAR)}, + // Add-in Formulas + // Financial Formulas + {"ACCRINTM", String.valueOf(xlfAccrintm), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"ACCRINT", String.valueOf(xlfAccrint), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"COUPDAYBS", String.valueOf(xlfCoupDayBS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"COUPDAYS", String.valueOf(xlfCoupDays), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"PV", String.valueOf(xlfPv), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"FV", String.valueOf(xlfFv), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IPMT", String.valueOf(xlfIpmt), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"CUMIPMT", String.valueOf(xlfCumIPmt), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"CUMPRINC", String.valueOf(xlfCumPrinc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"COUPNCD", String.valueOf(xlfCoupNCD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"COUPDAYSNC", String.valueOf(xlfCoupDaysNC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"COUPPCD", String.valueOf(xlfCoupPCD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"COUPNUM", String.valueOf(xlfCoupNUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"DOLLARDE", String.valueOf(xlfDollarDE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"DOLLARFR", String.valueOf(xlfDollarFR), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"EFFECT", String.valueOf(xlfEffect), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"INTRATE", String.valueOf(xlfINTRATE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IRR", String.valueOf(xlfIrr), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"XIRR", String.valueOf(xlfXIRR), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"XNPV", String.valueOf(xlfXNPV), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"RATE", String.valueOf(xlfRate), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"YIELD", String.valueOf(xlfYIELD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"PRICE", String.valueOf(xlfPRICE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"PRICEDISC", String.valueOf(xlfPRICEDISC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"DISC", String.valueOf(xlfDISC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"PRICEMAT", String.valueOf(xlfPRICEMAT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"DURATION", String.valueOf(xlfDURATION), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"MDURATION", String.valueOf(xlfMDURATION), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"NPER", String.valueOf(xlfNper), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"TBILLEQ", String.valueOf(xlfTBillEq), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"TBILLPRICE", String.valueOf(xlfTBillPrice), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"TBILLYIELD", String.valueOf(xlfTBillYield), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"YIELDDISC", String.valueOf(xlfYieldDisc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"YIELDMAT", String.valueOf(xlfYieldMat), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"PPMT", String.valueOf(xlfPpmt), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"FVSCHEDULE", String.valueOf(xlfFVSchedule), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"AMORLINC", String.valueOf(xlfAmorlinc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"AMORDEGRC", String.valueOf(xlfAmordegrc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"ODDFPRICE", String.valueOf(xlfOddFPrice), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"ODDLPRICE", String.valueOf(xlfOddLPrice), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"ODDFYIELD", String.valueOf(xlfOddFYield), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"ODDLYIELD", String.valueOf(xlfOddLYield), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"NOMINAL", String.valueOf(xlfNOMINAL), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"VDB", String.valueOf(xlfVdb), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"DDB", String.valueOf(xlfDdb), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"RECEIVED", String.valueOf(xlfRECEIVED), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + // Engineering Formulas + {"BIN2DEC", String.valueOf(xlfBIN2DEC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"BIN2HEX", String.valueOf(xlfBIN2HEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"BIN2OCT", String.valueOf(xlfBIN2OCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"DEC2BIN", String.valueOf(xlfDEC2BIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"DEC2HEX", String.valueOf(xlfDEC2HEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"DEC2OCT", String.valueOf(xlfDEC2OCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"HEX2BIN", String.valueOf(xlfHEX2BIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"HEX2DEC", String.valueOf(xlfHEX2DEC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"HEX2OCT", String.valueOf(xlfHEX2OCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"OCT2BIN", String.valueOf(xlfOCT2BIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"OCT2DEC", String.valueOf(xlfOCT2DEC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"OCT2HEX", String.valueOf(xlfOCT2HEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"COMPLEX", String.valueOf(xlfCOMPLEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"GESTEP", String.valueOf(xlfGESTEP), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"DELTA", String.valueOf(xlfDELTA), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMAGINARY", String.valueOf(xlfIMAGINARY), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMREAL", String.valueOf(xlfIMREAL), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMARGUMENT", String.valueOf(xlfIMARGUMENT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMABS", String.valueOf(xlfIMABS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMDIV", String.valueOf(xlfIMDIV), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMCONJUGATE", String.valueOf(xlfIMCONJUGATE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMCOS", String.valueOf(xlfIMCOS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMSIN", String.valueOf(xlfIMSIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMEXP", String.valueOf(xlfIMEXP), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMPOWER", String.valueOf(xlfIMPOWER), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMSQRT", String.valueOf(xlfIMSQRT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMSUB", String.valueOf(xlfIMSUB), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMSUM", String.valueOf(xlfIMSUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMPRODUCT", String.valueOf(xlfIMPRODUCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMLN", String.valueOf(xlfIMLN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMLOG10", String.valueOf(xlfIMLOG10), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"IMLOG2", String.valueOf(xlfIMLOG2), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"CONVERT", String.valueOf(xlfCONVERT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"ERF", String.valueOf(xlfERF), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + // Math Add-In Formulas + {"FACTDOUBLE", String.valueOf(xlfDOUBLEFACT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"GCD", String.valueOf(xlfGCD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"LCM", String.valueOf(xlfLCM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"MROUND", String.valueOf(xlfMROUND), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"MULTINOMIAL", String.valueOf(xlfMULTINOMIAL), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"QUOTIENT", String.valueOf(xlfQUOTIENT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"RANDBETWEEN", String.valueOf(xlfRANDBETWEEN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"SERIESSUM", String.valueOf(xlfSERIESSUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"SQRTPI", String.valueOf(xlfSQRTPI), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"SUMIFS", String.valueOf(xlfSUMIFS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + // Information Add-Ins + {"ISEVEN", String.valueOf(xlfISEVEN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"ISODD", String.valueOf(xlfISODD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + // Date/Time Add-in Formulas + {"NETWORKDAYS", String.valueOf(xlfNETWORKDAYS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"EDATE", String.valueOf(xlfEDATE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"EOMONTH", String.valueOf(xlfEOMONTH), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"WEEKNUM", String.valueOf(xlfWEEKNUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"WEEKDAY", String.valueOf(xlfWeekday), String.valueOf(FTYPE_PTGFUNCVAR)}, + {"WORKDAY", String.valueOf(xlfWORKDAY), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"YEARFRAC", String.valueOf(xlfYEARFRAC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + // Statistical + {"AVERAGEIF", String.valueOf(xlfAVERAGEIF), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"AVERAGEIFS", String.valueOf(xlfAVERAGEIFS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + {"COUNTIFS", String.valueOf(xlfCOUNTIFS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + // Logical + {"IFERROR", String.valueOf(xlfIFERROR), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, + }; + /** * Handles differences * in japanese locale xls - - * + * * @param iftb * @return */ public static String getJFunctionString(short iftb) { - switch(iftb) { - case xlfDollar: return "YEN("; - case xlfUsdollar: return "DOLLAR("; - case xlfDbcs: return "JIS("; + switch (iftb) { + case xlfDollar: + return "YEN("; + case xlfUsdollar: + return "DOLLAR("; + case xlfDbcs: + return "JIS("; } return null; } - - - - public static String getFunctionString(short iftb){ - switch(iftb){ - case xlfADDIN: return ""; - case XLF_COUNT: return "COUNT("; - case XLF_IS: return "IF("; - case XLF_IS_NA: return "ISNA("; - case XLF_IS_ERROR: return "ISERROR("; - case XLF_SUM: return "SUM("; - case XLF_AVERAGE: return "AVERAGE("; - case XLF_MIN: return "MIN("; - case XLF_MAX: return "MAX("; - case XLF_ROW: return "ROW("; - case xlfColumn: return "COLUMN("; - case xlfNa: return "NA("; - case xlfNpv: return "NPV("; - case xlfStdev: return "STDEV("; - case xlfDollar: return "DOLLAR("; - case xlfFixed: return "FIXED("; - case xlfSin: return "SIN("; - case xlfCos: return "COS("; - case xlfTan: return "TAN("; - case xlfAtan: return "ATAN("; - case xlfPi: return "PI("; - case xlfSqrt: return "SQRT("; - case xlfExp: return "EXP("; - case xlfLn: return "LN("; - case xlfLog10: return "LOG10("; - case xlfAbs: return "ABS("; - case xlfInt: return "INT("; - case xlfSign: return "SIGN("; - case xlfRound: return "ROUND("; - case xlfLookup: return "LOOKUP("; - case xlfIndex: return "INDEX("; - case xlfRept: return "REPT("; - case xlfMid: return "MID("; - case xlfLen: return "LEN("; - case xlfValue: return "VALUE("; - case xlfTrue: return "TRUE("; - case xlfFalse: return "FALSE("; - case xlfAnd: return "AND("; - case xlfOr: return "OR("; - case xlfNot: return "NOT("; - case xlfMod: return "MOD("; - case xlfDaverage: return "DAVERAGE("; - case xlfDcount: return "DCOUNT("; - case xlfDcounta: return "DCOUNTA("; - case xlfDget: return "DGET("; - case xlfDmax: return "DMAX("; - case xlfDmin: return "DMIN("; - case xlfDproduct: return "DPRODUCT("; - case xlfDstdev: return "DSTDEV("; - case xlfDstdevp: return "DSTDEVP("; - case xlfDsum: return "DSUM("; - case xlfDvar: return "DVAR("; - case xlfDvarp: return "DVARP("; - case xlfVar: return "VAR("; - case xlfText: return "TEXT("; - case xlfLinest: return "LINEST("; - case xlfTrend: return "TREND("; - case xlfLogest: return "LOGEST("; - case xlfGrowth: return "GROWTH("; - case xlfGoto: return "GOTO("; - case xlfHalt: return "HALT("; - case xlfPv: return "PV("; - case xlfFv: return "FV("; - case xlfNper: return "NPER("; - case xlfPmt: return "PMT("; - case xlfRate: return "RATE("; - case xlfMirr: return "MIRR("; - case xlfIrr: return "IRR("; - case xlfRand: return "RAND("; - case xlfMatch: return "MATCH("; - case xlfDate: return "DATE("; - case xlfTime: return "TIME("; - case xlfDay: return "DAY("; - case xlfMonth: return "MONTH("; - case xlfYear: return "YEAR("; - case xlfWeekday: return "WEEKDAY("; - case xlfHour: return "HOUR("; - case xlfMinute: return "MINUTE("; - case xlfSecond: return "SECOND("; - case xlfNow: return "NOW("; - case xlfAreas: return "AREAS("; - case xlfRows: return "ROWS("; - case xlfColumns: return "COLUMNS("; - case xlfOffset: return "OFFSET("; - case xlfAbsref: return "ABSREF("; - case xlfRelref: return "RELREF("; - case xlfArgument: return "ARGUMENT("; - case xlfSearch: return "SEARCH("; - case xlfTranspose: return "TRANSPOSE("; - case xlfError: return "ERROR("; - case xlfStep: return "STEP("; - case xlfType: return "TYPE("; - case xlfEcho: return "ECHO("; - case xlfSetName: return "SETNAME("; - case xlfCaller: return "CALLER("; - case xlfDeref: return "DEREF("; - case xlfWindows: return "WINDOWS("; - case xlfSeries: return "SERIES("; - case xlfDocuments: return "DOCUMENTS("; - case xlfActiveCell: return "ACTIVECELL("; - case xlfSelection: return "SELECTION("; - case xlfResult: return "RESULT("; - case xlfAtan2: return "ATAN2("; - case xlfAsin: return "ASIN("; - case xlfAcos: return "ACOS("; - case xlfChoose: return "CHOOSE("; - case xlfHlookup: return "HLOOKUP("; - case xlfVlookup: return "VLOOKUP("; - case xlfLinks: return "LINKS("; - case xlfInput: return "INPUT("; - case xlfIsref: return "ISREF("; - case xlfGetFormula: return "GETFORMULA("; - case xlfGetName: return "GETNAME("; - case xlfSetValue: return "SETVALUE("; - case xlfLog: return "LOG("; - case xlfExec: return "EXEC("; - case xlfChar: return "CHAR("; - case xlfLower: return "LOWER("; - case xlfUpper: return "UPPER("; - case xlfProper: return "PROPER("; - case xlfLeft: return "LEFT("; - case xlfRight: return "RIGHT("; - case xlfExact: return "EXACT("; - case xlfTrim: return "TRIM("; - case xlfReplace: return "REPLACE("; - case xlfSubstitute: return "SUBSTITUTE("; - case xlfCode: return "CODE("; - case xlfNames: return "NAMES("; - case xlfDirectory: return "DIRECTORY("; - case xlfFind: return "FIND("; - case xlfCell: return "CELL("; - case xlfIserr: return "ISERR("; - case xlfIstext: return "ISTEXT("; - case xlfIsnumber: return "ISNUMBER("; - case xlfIsblank: return "ISBLANK("; - case xlfT: return "T("; - case xlfN: return "N("; - case xlfFopen: return "FOPEN("; - case xlfFclose: return "FCLOSE("; - case xlfFsize: return "SIZE("; - case xlfFreadln: return "FREADLN("; - case xlfFread: return "FREAD("; - case xlfFwriteln: return "FWRITELN("; - case xlfFwrite: return "FWRITE("; - case xlfFpos: return "FPOS("; - case xlfDatevalue: return "DATEVALUE("; - case xlfTimevalue: return "TIMEVALUE("; - case xlfSln: return "SLN("; - case xlfSyd: return "SYD("; - case xlfDdb: return "DDB("; - case xlfGetDef: return "GETDEF("; - case xlfReftext: return "REFTEXT("; - case xlfTextref: return "TEXTREF("; - case XLF_INDIRECT: return "INDIRECT("; - case xlfRegister: return "REGISTER("; - case xlfCall: return "CALL("; - case xlfAddBar: return "ADDBAR("; - case xlfAddMenu: return "ADDMENU("; - case xlfAddCommand: return "ADDCOMMAND("; - case xlfEnableCommand: return "ENABLECOMMAND("; - case xlfCheckCommand: return "CHECKCOMMAND("; - case xlfRenameCommand: return "RENAMECOMMAND("; - case xlfShowBar: return "SHOWBAR("; - case xlfDeleteMenu: return "DELETEMENU("; - case xlfDeleteCommand: return "DELETECOMMAND("; - case xlfGetChartItem: return "CHARTITEM("; - case xlfDialogBox: return "DIALOGBOX("; - case xlfClean: return "CLEAN("; - case xlfMdeterm: return "MDETERM("; - case xlfMinverse: return "MINVERSE("; - case xlfMmult: return "MMULT("; - case xlfFiles: return "FILES("; - case xlfIpmt: return "IPMT("; - case xlfPpmt: return "PPMT("; - case xlfCounta: return "COUNTA("; - case xlfCancelKey: return "CANCELKEY("; - case xlfInitiate: return "INITIATE("; - case xlfRequest: return "REQUEST("; - case xlfPoke: return "POKE("; - case xlfExecute: return "EXECUTE("; - case xlfTerminate: return "TERMINATE("; - case xlfRestart: return "RESTART("; - case xlfHelp: return "HELP("; - case xlfGetBar: return "GETBAR("; - case xlfProduct: return "PRODUCT("; - case xlfFact: return "FACT("; - case xlfGetCell: return "GETCELL("; - case xlfGetWorkspace: return "GETWORKSPACE("; - case xlfGetWindow: return "GETWINDOW("; - case xlfGetDocument: return "GETDOCUMENT("; - case xlfIsnontext: return "ISNONTEXT("; - case xlfGetNote: return "GETNOTE("; - case xlfNote: return "NOTE("; - case xlfStdevp: return "STDEVP("; - case xlfVarp: return "VARP("; - case xlfTrunc: return "TRUNC("; - case xlfIslogical: return "ISLOGICAL("; - case xlfDeleteBar: return "DELETEBAR("; - case xlfUnregister: return "UNREGISTER("; - case xlfUsdollar: return "USDOLLAR("; - case xlfFindb: return "FINDB("; - case xlfSearchb: return "SEARCHB("; - case xlfReplaceb: return "REPLACEB("; - case xlfLeftb: return "LEFTB("; - case xlfRightb: return "RIGHTB("; - case xlfMidb: return "MIDB("; - case xlfLenb: return "LENB("; - case xlfRoundup: return "ROUNDUP("; - case xlfRounddown: return "ROUNDDOWN("; - case xlfAsc: return "ASC("; - case xlfDbcs: return "DBCS("; - case xlfRank: return "RANK("; - case xlfAddress: return "ADDRESS("; - case xlfDays360: return "DAYS360("; - case xlfToday: return "TODAY("; - case xlfVdb: return "VDB("; - case xlfMedian: return "MEDIAN("; - case xlfSumproduct: return "SUMPRODUCT("; - case xlfSinh: return "SINH("; - case xlfCosh: return "COSH("; - case xlfTanh: return "TANH("; - case xlfAsinh: return "ASINH("; - case xlfAcosh: return "ACOSH("; - case xlfAtanh: return "ATANH("; - case xlfCreateObject: return "CREATEOBJECT("; - case xlfVolatile: return "VOLATILE("; - case xlfLastError: return "LASTERROR("; - case xlfCustomUndo: return "CUSTOMUNDO("; - case xlfCustomRepeat: return "CUSTOMREPEAT("; - case xlfFormulaConvert: return "FORMULACONVERT("; - case xlfGetLinkInfo: return "GETLINKINFO("; - case xlfTextBox: return "TEXTBOX("; - case xlfInfo: return "INFO("; - case xlfGroup: return "GROUP("; - case xlfGetObject: return "GETOBJECT("; - case xlfDb: return "DB("; - case xlfPause: return "PAUSE("; - case xlfResume: return "RESUME("; - case xlfFrequency: return "FREQUENCY("; - case xlfAddToolbar: return "ADDTOOLBAR("; - case xlfDeleteToolbar: return "DELETETOOLBAR("; - case xlfResetToolbar: return "RESETTOOLBAR("; - case xlfEvaluate: return "EVALUATE("; - case xlfGetToolbar: return "GETTOOLBAR("; - case xlfGetTool: return "GETTOOL("; - case xlfSpellingCheck: return "SPELLINGCHECK("; - case xlfErrorType: return "ERROR.TYPE("; - case xlfAppTitle: return "APPTITLE("; - case xlfWindowTitle: return "WINDOWTITLE("; - case xlfSaveToolbar: return "SAVETOOLBAR("; - case xlfEnableTool: return "ENABLETOOL("; - case xlfPressTool: return "PRESSTOOL("; - case xlfRegisterId: return "REGISTERID("; - case xlfGetWorkbook: return "GETWORKBOOK("; - case xlfAvedev: return "AVEDEV("; - case xlfBetadist: return "BETADIST("; - case xlfGammaln: return "GAMMALN("; - case xlfBetainv: return "BETAINV("; - case xlfBinomdist: return "BINOMDIST("; - case xlfChidist: return "CHIDIST("; - case xlfChiinv: return "CHIINV("; - case xlfCombin: return "COMBIN("; - case xlfConfidence: return "CONFIDENCE("; - case xlfCritbinom: return "CRITBINOM("; - case xlfEven: return "EVEN("; - case xlfExpondist: return "EXPONDIST("; - case xlfFdist: return "FDIST("; - case xlfFinv: return "FINV("; - case xlfFisher: return "FISHER("; - case xlfFisherinv: return "FISHERINV("; - case xlfFloor: return "FLOOR("; - case xlfGammadist: return "GAMMADIST("; - case xlfGammainv: return "GAMMAINV("; - case xlfCeiling: return "CEILING("; - case xlfHypgeomdist: return "HYPGEOMDIST("; - case xlfLognormdist: return "LOGNORMDIST("; - case xlfLoginv: return "LOGINV("; - case xlfNegbinomdist: return "NEGBINOMDIST("; - case xlfNormdist: return "NORMDIST("; - case xlfNormsdist: return "NORMSDIST("; - case xlfNorminv: return "NORMINV("; - case xlfNormsinv: return "NORMSINV("; - case xlfStandardize: return "STANDARDIZE("; - case xlfOdd: return "ODD("; - case xlfPermut: return "PERMUT("; - case xlfPoisson: return "POISSON("; - case xlfTdist: return "TDIST("; - case xlfWeibull: return "WEIBULL("; - case xlfSumxmy2: return "SUMXMY2("; - case xlfSumx2my2: return "SUMX2MY2("; - case xlfSumx2py2: return "SUMX2PY2("; - case xlfChitest: return "CHITEST("; - case xlfCorrel: return "CORREL("; - case xlfCovar: return "COVAR("; - case xlfForecast: return "FORECAST("; - case xlfFtest: return "FTEST("; - case xlfIntercept: return "INTERCEPT("; - case xlfPearson: return "PEARSON("; - case xlfRsq: return "RSQ("; - case xlfSteyx: return "STEYX("; - case xlfSlope: return "SLOPE("; - case xlfTtest: return "TTEST("; - case xlfProb: return "PROB("; - case xlfDevsq: return "DEVSQ("; - case xlfGeomean: return "GEOMEAN("; - case xlfHarmean: return "HARMEAN("; - case xlfSumsq: return "SUMSQ("; - case xlfKurt: return "KURT("; - case xlfSkew: return "SKEW("; - case xlfZtest: return "ZTEST("; - case xlfLarge: return "LARGE("; - case xlfSmall: return "SMALL("; - case xlfQuartile: return "QUARTILE("; - case xlfPercentile: return "PERCENTILE("; - case xlfPercentrank: return "PERCENTRANK("; - case xlfMode: return "MODE("; - case xlfTrimmean: return "TRIMMEAN("; - case xlfTinv: return "TINV("; - case xlfMovieCommand: return "MOVIECOMMAND("; - case xlfGetMovie: return "GETMOVIE("; - case xlfConcatenate: return "CONCATENATE("; - case xlfPower: return "POWER("; - case xlfPivotAddData: return "PIVOTADDDATA("; - case xlfGetPivotTable: return "GETPIVOTTABLE("; - case xlfGetPivotField: return "GETPIVOTFIELD("; - case xlfGetPivotItem: return "GETPIVOTITEM("; - case xlfRadians: return "RADIANS("; - case xlfDegrees: return "DEGREES("; - case xlfSubtotal: return "SUBTOTAL("; - case XLF_SUM_IF: return "SUMIF("; - case xlfCountif: return "COUNTIF("; - case xlfCountblank: return "COUNTBLANK("; - case xlfScenarioGet: return "SCENARIOGET("; - case xlfOptionsListsGet: return "OPTIONSLISTSGET("; - case xlfIspmt: return "ISPMT("; - case xlfDatedif: return "DATEDIF("; - case xlfDatestring: return "DATESTRING("; - case xlfNumberstring: return "NUMBERSTRING("; - case xlfRoman: return "ROMAN("; - case xlfOpenDialog: return "OPENDIALOG("; - case xlfSaveDialog: return "SAVEDIALOG("; - case xlfViewGet: return "VIEWGET("; - case xlfGetPivotData: return "GETPIVOTDATA("; - case xlfHyperlink: return "HYPERLINK("; - case xlfPhonetic: return "PHONETIC("; - case xlfAverageA: return "AVERAGEA("; - case xlfMaxA: return "MAXA("; - case xlfMinA: return "MINA("; - case xlfStDevPA: return "STDEVPA("; - case xlfVarPA: return "VARPA("; - case xlfStDevA: return "STDEVA("; - case xlfVarA: return "VARA("; - // ADD-IN FORMULAS - // Financial Formulas AddIns - case xlfAccrintm: return "ACCRINTM("; - case xlfAccrint: return "ACCRINT("; - case xlfCoupDayBS: return "COUPDAYBS("; - case xlfCoupDays: return "COUPDAYS("; - case xlfCoupDaysNC: return "COUPDAYSNC("; - case xlfCumIPmt: return "CUMIPMT("; - case xlfCumPrinc: return "CUMPRINC("; - case xlfCoupNCD: return "COUPNCD("; - case xlfCoupPCD: return "COUPPCD("; - case xlfCoupNUM: return "COUPNUM("; - case xlfDollarDE: return "DOLLARDE("; - case xlfDollarFR: return "DOLLARFR("; - case xlfEffect: return "EFFECT("; - case xlfINTRATE: return "INTRATE("; - case xlfXIRR: return "XIRR("; - case xlfXNPV: return "XNPV("; - case xlfYIELD: return "YIELD("; - case xlfPRICE: return "PRICE("; - case xlfPRICEDISC: return "PRICEDISC("; - case xlfDISC: return "DISC("; - case xlfPRICEMAT: return "PRICEMAT("; - case xlfDURATION: return "DURATION("; - case xlfMDURATION: return "MDURATION("; - case xlfTBillEq: return "TBILLEQ("; - case xlfTBillPrice: return "TBILLPRICE("; - case xlfTBillYield: return "TBILLYIELD("; - case xlfYieldDisc: return "YIELDDISC("; - case xlfYieldMat: return "YIELDMAT("; - case xlfFVSchedule: return "FVSCHEDULE("; - case xlfAmorlinc: return "AMORLINC("; - case xlfAmordegrc: return "AMORDEGRC("; - case xlfOddFPrice: return "ODDFPRICE("; - case xlfOddFYield: return "ODDFYIELD("; - case xlfOddLPrice: return "ODDLPRICE("; - case xlfOddLYield: return "ODDLYIELD("; - case xlfNOMINAL: return "NOMINAL("; - case xlfRECEIVED: return "RECEIVED("; - // Engineering Formulas AddIns - case xlfBIN2DEC: return "BIN2DEC("; - case xlfBIN2HEX: return "BIN2HEX("; - case xlfBIN2OCT: return "BIN2OCT("; - case xlfDEC2BIN: return "DEC2BIN("; - case xlfDEC2HEX: return "DEC2HEX("; - case xlfDEC2OCT: return "DEC2OCT("; - case xlfHEX2BIN: return "HEX2BIN("; - case xlfHEX2DEC: return "HEX2DEC("; - case xlfHEX2OCT: return "HEX2OCT("; - case xlfOCT2BIN: return "OCT2BIN("; - case xlfOCT2DEC: return "OCT2DEC("; - case xlfOCT2HEX: return "OCT2HEX("; - case xlfCOMPLEX: return "COMPLEX("; - case xlfGESTEP: return "GESTEP("; - case xlfDELTA: return "DELTA("; - case xlfIMAGINARY: return "IMAGINARY("; - case xlfIMREAL: return "IMREAL("; - case xlfIMARGUMENT: return "IMARGUMENT("; - case xlfIMABS: return "IMABS("; - case xlfIMDIV: return "IMDIV("; - case xlfIMCONJUGATE: return "IMCONJUGATE("; - case xlfIMCOS: return "IMCOS("; - case xlfIMSIN: return "IMSIN("; - case xlfIMEXP: return "IMEXP("; - case xlfIMPOWER: return "IMPOWER("; - case xlfIMSQRT: return "IMSQRT("; - case xlfIMSUB: return "IMSUB("; - case xlfIMSUM: return "IMSUM("; - case xlfIMPRODUCT: return "IMPRODUCT("; - case xlfIMLN: return "IMLN("; - case xlfIMLOG10: return "IMLOG10("; - case xlfIMLOG2: return "IMLOG2("; - case xlfCONVERT: return "CONVERT("; - case xlfDOUBLEFACT: return "FACTDOUBLE("; - case xlfGCD: return "GCD("; - case xlfLCM: return "LCM("; - case xlfMROUND: return "MROUND("; - case xlfMULTINOMIAL: return "MULTINOMIAL("; - case xlfQUOTIENT: return "QUOTIENT("; - case xlfRANDBETWEEN: return "RANDBETWEEN("; - case xlfSERIESSUM: return "SERIESSUM("; - case xlfSQRTPI: return "SQRTPI("; - case xlfERF: return "ERF("; - // information Add-ins - case xlfISEVEN: return "ISEVEN("; - case xlfISODD: return "ISODD("; - // Date/Time Add-in Formulas - case xlfNETWORKDAYS: return "NETWORKDAYS("; - case xlfEDATE: return "EDATE("; - case xlfEOMONTH: return "EOMONTH("; - case xlfWEEKNUM: return "WEEKNUM("; - case xlfWORKDAY: return "WORKDAY("; - case xlfYEARFRAC: return "YEARFRAC("; - // Statistical - case xlfAVERAGEIF: return "AVERAGEIF("; - case xlfAVERAGEIFS: return "AVERAGEIFS("; - case xlfCOUNTIFS: return "COUNTIFS("; - // Logical - case xlfIFERROR: return "IFERROR("; - // Math - case xlfSUMIFS: return "SUMIFS("; + + + public static String getFunctionString(short iftb) { + switch (iftb) { + case xlfADDIN: + return ""; + case XLF_COUNT: + return "COUNT("; + case XLF_IS: + return "IF("; + case XLF_IS_NA: + return "ISNA("; + case XLF_IS_ERROR: + return "ISERROR("; + case XLF_SUM: + return "SUM("; + case XLF_AVERAGE: + return "AVERAGE("; + case XLF_MIN: + return "MIN("; + case XLF_MAX: + return "MAX("; + case XLF_ROW: + return "ROW("; + case xlfColumn: + return "COLUMN("; + case xlfNa: + return "NA("; + case xlfNpv: + return "NPV("; + case xlfStdev: + return "STDEV("; + case xlfDollar: + return "DOLLAR("; + case xlfFixed: + return "FIXED("; + case xlfSin: + return "SIN("; + case xlfCos: + return "COS("; + case xlfTan: + return "TAN("; + case xlfAtan: + return "ATAN("; + case xlfPi: + return "PI("; + case xlfSqrt: + return "SQRT("; + case xlfExp: + return "EXP("; + case xlfLn: + return "LN("; + case xlfLog10: + return "LOG10("; + case xlfAbs: + return "ABS("; + case xlfInt: + return "INT("; + case xlfSign: + return "SIGN("; + case xlfRound: + return "ROUND("; + case xlfLookup: + return "LOOKUP("; + case xlfIndex: + return "INDEX("; + case xlfRept: + return "REPT("; + case xlfMid: + return "MID("; + case xlfLen: + return "LEN("; + case xlfValue: + return "VALUE("; + case xlfTrue: + return "TRUE("; + case xlfFalse: + return "FALSE("; + case xlfAnd: + return "AND("; + case xlfOr: + return "OR("; + case xlfNot: + return "NOT("; + case xlfMod: + return "MOD("; + case xlfDaverage: + return "DAVERAGE("; + case xlfDcount: + return "DCOUNT("; + case xlfDcounta: + return "DCOUNTA("; + case xlfDget: + return "DGET("; + case xlfDmax: + return "DMAX("; + case xlfDmin: + return "DMIN("; + case xlfDproduct: + return "DPRODUCT("; + case xlfDstdev: + return "DSTDEV("; + case xlfDstdevp: + return "DSTDEVP("; + case xlfDsum: + return "DSUM("; + case xlfDvar: + return "DVAR("; + case xlfDvarp: + return "DVARP("; + case xlfVar: + return "VAR("; + case xlfText: + return "TEXT("; + case xlfLinest: + return "LINEST("; + case xlfTrend: + return "TREND("; + case xlfLogest: + return "LOGEST("; + case xlfGrowth: + return "GROWTH("; + case xlfGoto: + return "GOTO("; + case xlfHalt: + return "HALT("; + case xlfPv: + return "PV("; + case xlfFv: + return "FV("; + case xlfNper: + return "NPER("; + case xlfPmt: + return "PMT("; + case xlfRate: + return "RATE("; + case xlfMirr: + return "MIRR("; + case xlfIrr: + return "IRR("; + case xlfRand: + return "RAND("; + case xlfMatch: + return "MATCH("; + case xlfDate: + return "DATE("; + case xlfTime: + return "TIME("; + case xlfDay: + return "DAY("; + case xlfMonth: + return "MONTH("; + case xlfYear: + return "YEAR("; + case xlfWeekday: + return "WEEKDAY("; + case xlfHour: + return "HOUR("; + case xlfMinute: + return "MINUTE("; + case xlfSecond: + return "SECOND("; + case xlfNow: + return "NOW("; + case xlfAreas: + return "AREAS("; + case xlfRows: + return "ROWS("; + case xlfColumns: + return "COLUMNS("; + case xlfOffset: + return "OFFSET("; + case xlfAbsref: + return "ABSREF("; + case xlfRelref: + return "RELREF("; + case xlfArgument: + return "ARGUMENT("; + case xlfSearch: + return "SEARCH("; + case xlfTranspose: + return "TRANSPOSE("; + case xlfError: + return "ERROR("; + case xlfStep: + return "STEP("; + case xlfType: + return "TYPE("; + case xlfEcho: + return "ECHO("; + case xlfSetName: + return "SETNAME("; + case xlfCaller: + return "CALLER("; + case xlfDeref: + return "DEREF("; + case xlfWindows: + return "WINDOWS("; + case xlfSeries: + return "SERIES("; + case xlfDocuments: + return "DOCUMENTS("; + case xlfActiveCell: + return "ACTIVECELL("; + case xlfSelection: + return "SELECTION("; + case xlfResult: + return "RESULT("; + case xlfAtan2: + return "ATAN2("; + case xlfAsin: + return "ASIN("; + case xlfAcos: + return "ACOS("; + case xlfChoose: + return "CHOOSE("; + case xlfHlookup: + return "HLOOKUP("; + case xlfVlookup: + return "VLOOKUP("; + case xlfLinks: + return "LINKS("; + case xlfInput: + return "INPUT("; + case xlfIsref: + return "ISREF("; + case xlfGetFormula: + return "GETFORMULA("; + case xlfGetName: + return "GETNAME("; + case xlfSetValue: + return "SETVALUE("; + case xlfLog: + return "LOG("; + case xlfExec: + return "EXEC("; + case xlfChar: + return "CHAR("; + case xlfLower: + return "LOWER("; + case xlfUpper: + return "UPPER("; + case xlfProper: + return "PROPER("; + case xlfLeft: + return "LEFT("; + case xlfRight: + return "RIGHT("; + case xlfExact: + return "EXACT("; + case xlfTrim: + return "TRIM("; + case xlfReplace: + return "REPLACE("; + case xlfSubstitute: + return "SUBSTITUTE("; + case xlfCode: + return "CODE("; + case xlfNames: + return "NAMES("; + case xlfDirectory: + return "DIRECTORY("; + case xlfFind: + return "FIND("; + case xlfCell: + return "CELL("; + case xlfIserr: + return "ISERR("; + case xlfIstext: + return "ISTEXT("; + case xlfIsnumber: + return "ISNUMBER("; + case xlfIsblank: + return "ISBLANK("; + case xlfT: + return "T("; + case xlfN: + return "N("; + case xlfFopen: + return "FOPEN("; + case xlfFclose: + return "FCLOSE("; + case xlfFsize: + return "SIZE("; + case xlfFreadln: + return "FREADLN("; + case xlfFread: + return "FREAD("; + case xlfFwriteln: + return "FWRITELN("; + case xlfFwrite: + return "FWRITE("; + case xlfFpos: + return "FPOS("; + case xlfDatevalue: + return "DATEVALUE("; + case xlfTimevalue: + return "TIMEVALUE("; + case xlfSln: + return "SLN("; + case xlfSyd: + return "SYD("; + case xlfDdb: + return "DDB("; + case xlfGetDef: + return "GETDEF("; + case xlfReftext: + return "REFTEXT("; + case xlfTextref: + return "TEXTREF("; + case XLF_INDIRECT: + return "INDIRECT("; + case xlfRegister: + return "REGISTER("; + case xlfCall: + return "CALL("; + case xlfAddBar: + return "ADDBAR("; + case xlfAddMenu: + return "ADDMENU("; + case xlfAddCommand: + return "ADDCOMMAND("; + case xlfEnableCommand: + return "ENABLECOMMAND("; + case xlfCheckCommand: + return "CHECKCOMMAND("; + case xlfRenameCommand: + return "RENAMECOMMAND("; + case xlfShowBar: + return "SHOWBAR("; + case xlfDeleteMenu: + return "DELETEMENU("; + case xlfDeleteCommand: + return "DELETECOMMAND("; + case xlfGetChartItem: + return "CHARTITEM("; + case xlfDialogBox: + return "DIALOGBOX("; + case xlfClean: + return "CLEAN("; + case xlfMdeterm: + return "MDETERM("; + case xlfMinverse: + return "MINVERSE("; + case xlfMmult: + return "MMULT("; + case xlfFiles: + return "FILES("; + case xlfIpmt: + return "IPMT("; + case xlfPpmt: + return "PPMT("; + case xlfCounta: + return "COUNTA("; + case xlfCancelKey: + return "CANCELKEY("; + case xlfInitiate: + return "INITIATE("; + case xlfRequest: + return "REQUEST("; + case xlfPoke: + return "POKE("; + case xlfExecute: + return "EXECUTE("; + case xlfTerminate: + return "TERMINATE("; + case xlfRestart: + return "RESTART("; + case xlfHelp: + return "HELP("; + case xlfGetBar: + return "GETBAR("; + case xlfProduct: + return "PRODUCT("; + case xlfFact: + return "FACT("; + case xlfGetCell: + return "GETCELL("; + case xlfGetWorkspace: + return "GETWORKSPACE("; + case xlfGetWindow: + return "GETWINDOW("; + case xlfGetDocument: + return "GETDOCUMENT("; + case xlfIsnontext: + return "ISNONTEXT("; + case xlfGetNote: + return "GETNOTE("; + case xlfNote: + return "NOTE("; + case xlfStdevp: + return "STDEVP("; + case xlfVarp: + return "VARP("; + case xlfTrunc: + return "TRUNC("; + case xlfIslogical: + return "ISLOGICAL("; + case xlfDeleteBar: + return "DELETEBAR("; + case xlfUnregister: + return "UNREGISTER("; + case xlfUsdollar: + return "USDOLLAR("; + case xlfFindb: + return "FINDB("; + case xlfSearchb: + return "SEARCHB("; + case xlfReplaceb: + return "REPLACEB("; + case xlfLeftb: + return "LEFTB("; + case xlfRightb: + return "RIGHTB("; + case xlfMidb: + return "MIDB("; + case xlfLenb: + return "LENB("; + case xlfRoundup: + return "ROUNDUP("; + case xlfRounddown: + return "ROUNDDOWN("; + case xlfAsc: + return "ASC("; + case xlfDbcs: + return "DBCS("; + case xlfRank: + return "RANK("; + case xlfAddress: + return "ADDRESS("; + case xlfDays360: + return "DAYS360("; + case xlfToday: + return "TODAY("; + case xlfVdb: + return "VDB("; + case xlfMedian: + return "MEDIAN("; + case xlfSumproduct: + return "SUMPRODUCT("; + case xlfSinh: + return "SINH("; + case xlfCosh: + return "COSH("; + case xlfTanh: + return "TANH("; + case xlfAsinh: + return "ASINH("; + case xlfAcosh: + return "ACOSH("; + case xlfAtanh: + return "ATANH("; + case xlfCreateObject: + return "CREATEOBJECT("; + case xlfVolatile: + return "VOLATILE("; + case xlfLastError: + return "LASTERROR("; + case xlfCustomUndo: + return "CUSTOMUNDO("; + case xlfCustomRepeat: + return "CUSTOMREPEAT("; + case xlfFormulaConvert: + return "FORMULACONVERT("; + case xlfGetLinkInfo: + return "GETLINKINFO("; + case xlfTextBox: + return "TEXTBOX("; + case xlfInfo: + return "INFO("; + case xlfGroup: + return "GROUP("; + case xlfGetObject: + return "GETOBJECT("; + case xlfDb: + return "DB("; + case xlfPause: + return "PAUSE("; + case xlfResume: + return "RESUME("; + case xlfFrequency: + return "FREQUENCY("; + case xlfAddToolbar: + return "ADDTOOLBAR("; + case xlfDeleteToolbar: + return "DELETETOOLBAR("; + case xlfResetToolbar: + return "RESETTOOLBAR("; + case xlfEvaluate: + return "EVALUATE("; + case xlfGetToolbar: + return "GETTOOLBAR("; + case xlfGetTool: + return "GETTOOL("; + case xlfSpellingCheck: + return "SPELLINGCHECK("; + case xlfErrorType: + return "ERROR.TYPE("; + case xlfAppTitle: + return "APPTITLE("; + case xlfWindowTitle: + return "WINDOWTITLE("; + case xlfSaveToolbar: + return "SAVETOOLBAR("; + case xlfEnableTool: + return "ENABLETOOL("; + case xlfPressTool: + return "PRESSTOOL("; + case xlfRegisterId: + return "REGISTERID("; + case xlfGetWorkbook: + return "GETWORKBOOK("; + case xlfAvedev: + return "AVEDEV("; + case xlfBetadist: + return "BETADIST("; + case xlfGammaln: + return "GAMMALN("; + case xlfBetainv: + return "BETAINV("; + case xlfBinomdist: + return "BINOMDIST("; + case xlfChidist: + return "CHIDIST("; + case xlfChiinv: + return "CHIINV("; + case xlfCombin: + return "COMBIN("; + case xlfConfidence: + return "CONFIDENCE("; + case xlfCritbinom: + return "CRITBINOM("; + case xlfEven: + return "EVEN("; + case xlfExpondist: + return "EXPONDIST("; + case xlfFdist: + return "FDIST("; + case xlfFinv: + return "FINV("; + case xlfFisher: + return "FISHER("; + case xlfFisherinv: + return "FISHERINV("; + case xlfFloor: + return "FLOOR("; + case xlfGammadist: + return "GAMMADIST("; + case xlfGammainv: + return "GAMMAINV("; + case xlfCeiling: + return "CEILING("; + case xlfHypgeomdist: + return "HYPGEOMDIST("; + case xlfLognormdist: + return "LOGNORMDIST("; + case xlfLoginv: + return "LOGINV("; + case xlfNegbinomdist: + return "NEGBINOMDIST("; + case xlfNormdist: + return "NORMDIST("; + case xlfNormsdist: + return "NORMSDIST("; + case xlfNorminv: + return "NORMINV("; + case xlfNormsinv: + return "NORMSINV("; + case xlfStandardize: + return "STANDARDIZE("; + case xlfOdd: + return "ODD("; + case xlfPermut: + return "PERMUT("; + case xlfPoisson: + return "POISSON("; + case xlfTdist: + return "TDIST("; + case xlfWeibull: + return "WEIBULL("; + case xlfSumxmy2: + return "SUMXMY2("; + case xlfSumx2my2: + return "SUMX2MY2("; + case xlfSumx2py2: + return "SUMX2PY2("; + case xlfChitest: + return "CHITEST("; + case xlfCorrel: + return "CORREL("; + case xlfCovar: + return "COVAR("; + case xlfForecast: + return "FORECAST("; + case xlfFtest: + return "FTEST("; + case xlfIntercept: + return "INTERCEPT("; + case xlfPearson: + return "PEARSON("; + case xlfRsq: + return "RSQ("; + case xlfSteyx: + return "STEYX("; + case xlfSlope: + return "SLOPE("; + case xlfTtest: + return "TTEST("; + case xlfProb: + return "PROB("; + case xlfDevsq: + return "DEVSQ("; + case xlfGeomean: + return "GEOMEAN("; + case xlfHarmean: + return "HARMEAN("; + case xlfSumsq: + return "SUMSQ("; + case xlfKurt: + return "KURT("; + case xlfSkew: + return "SKEW("; + case xlfZtest: + return "ZTEST("; + case xlfLarge: + return "LARGE("; + case xlfSmall: + return "SMALL("; + case xlfQuartile: + return "QUARTILE("; + case xlfPercentile: + return "PERCENTILE("; + case xlfPercentrank: + return "PERCENTRANK("; + case xlfMode: + return "MODE("; + case xlfTrimmean: + return "TRIMMEAN("; + case xlfTinv: + return "TINV("; + case xlfMovieCommand: + return "MOVIECOMMAND("; + case xlfGetMovie: + return "GETMOVIE("; + case xlfConcatenate: + return "CONCATENATE("; + case xlfPower: + return "POWER("; + case xlfPivotAddData: + return "PIVOTADDDATA("; + case xlfGetPivotTable: + return "GETPIVOTTABLE("; + case xlfGetPivotField: + return "GETPIVOTFIELD("; + case xlfGetPivotItem: + return "GETPIVOTITEM("; + case xlfRadians: + return "RADIANS("; + case xlfDegrees: + return "DEGREES("; + case xlfSubtotal: + return "SUBTOTAL("; + case XLF_SUM_IF: + return "SUMIF("; + case xlfCountif: + return "COUNTIF("; + case xlfCountblank: + return "COUNTBLANK("; + case xlfScenarioGet: + return "SCENARIOGET("; + case xlfOptionsListsGet: + return "OPTIONSLISTSGET("; + case xlfIspmt: + return "ISPMT("; + case xlfDatedif: + return "DATEDIF("; + case xlfDatestring: + return "DATESTRING("; + case xlfNumberstring: + return "NUMBERSTRING("; + case xlfRoman: + return "ROMAN("; + case xlfOpenDialog: + return "OPENDIALOG("; + case xlfSaveDialog: + return "SAVEDIALOG("; + case xlfViewGet: + return "VIEWGET("; + case xlfGetPivotData: + return "GETPIVOTDATA("; + case xlfHyperlink: + return "HYPERLINK("; + case xlfPhonetic: + return "PHONETIC("; + case xlfAverageA: + return "AVERAGEA("; + case xlfMaxA: + return "MAXA("; + case xlfMinA: + return "MINA("; + case xlfStDevPA: + return "STDEVPA("; + case xlfVarPA: + return "VARPA("; + case xlfStDevA: + return "STDEVA("; + case xlfVarA: + return "VARA("; + // ADD-IN FORMULAS + // Financial Formulas AddIns + case xlfAccrintm: + return "ACCRINTM("; + case xlfAccrint: + return "ACCRINT("; + case xlfCoupDayBS: + return "COUPDAYBS("; + case xlfCoupDays: + return "COUPDAYS("; + case xlfCoupDaysNC: + return "COUPDAYSNC("; + case xlfCumIPmt: + return "CUMIPMT("; + case xlfCumPrinc: + return "CUMPRINC("; + case xlfCoupNCD: + return "COUPNCD("; + case xlfCoupPCD: + return "COUPPCD("; + case xlfCoupNUM: + return "COUPNUM("; + case xlfDollarDE: + return "DOLLARDE("; + case xlfDollarFR: + return "DOLLARFR("; + case xlfEffect: + return "EFFECT("; + case xlfINTRATE: + return "INTRATE("; + case xlfXIRR: + return "XIRR("; + case xlfXNPV: + return "XNPV("; + case xlfYIELD: + return "YIELD("; + case xlfPRICE: + return "PRICE("; + case xlfPRICEDISC: + return "PRICEDISC("; + case xlfDISC: + return "DISC("; + case xlfPRICEMAT: + return "PRICEMAT("; + case xlfDURATION: + return "DURATION("; + case xlfMDURATION: + return "MDURATION("; + case xlfTBillEq: + return "TBILLEQ("; + case xlfTBillPrice: + return "TBILLPRICE("; + case xlfTBillYield: + return "TBILLYIELD("; + case xlfYieldDisc: + return "YIELDDISC("; + case xlfYieldMat: + return "YIELDMAT("; + case xlfFVSchedule: + return "FVSCHEDULE("; + case xlfAmorlinc: + return "AMORLINC("; + case xlfAmordegrc: + return "AMORDEGRC("; + case xlfOddFPrice: + return "ODDFPRICE("; + case xlfOddFYield: + return "ODDFYIELD("; + case xlfOddLPrice: + return "ODDLPRICE("; + case xlfOddLYield: + return "ODDLYIELD("; + case xlfNOMINAL: + return "NOMINAL("; + case xlfRECEIVED: + return "RECEIVED("; + // Engineering Formulas AddIns + case xlfBIN2DEC: + return "BIN2DEC("; + case xlfBIN2HEX: + return "BIN2HEX("; + case xlfBIN2OCT: + return "BIN2OCT("; + case xlfDEC2BIN: + return "DEC2BIN("; + case xlfDEC2HEX: + return "DEC2HEX("; + case xlfDEC2OCT: + return "DEC2OCT("; + case xlfHEX2BIN: + return "HEX2BIN("; + case xlfHEX2DEC: + return "HEX2DEC("; + case xlfHEX2OCT: + return "HEX2OCT("; + case xlfOCT2BIN: + return "OCT2BIN("; + case xlfOCT2DEC: + return "OCT2DEC("; + case xlfOCT2HEX: + return "OCT2HEX("; + case xlfCOMPLEX: + return "COMPLEX("; + case xlfGESTEP: + return "GESTEP("; + case xlfDELTA: + return "DELTA("; + case xlfIMAGINARY: + return "IMAGINARY("; + case xlfIMREAL: + return "IMREAL("; + case xlfIMARGUMENT: + return "IMARGUMENT("; + case xlfIMABS: + return "IMABS("; + case xlfIMDIV: + return "IMDIV("; + case xlfIMCONJUGATE: + return "IMCONJUGATE("; + case xlfIMCOS: + return "IMCOS("; + case xlfIMSIN: + return "IMSIN("; + case xlfIMEXP: + return "IMEXP("; + case xlfIMPOWER: + return "IMPOWER("; + case xlfIMSQRT: + return "IMSQRT("; + case xlfIMSUB: + return "IMSUB("; + case xlfIMSUM: + return "IMSUM("; + case xlfIMPRODUCT: + return "IMPRODUCT("; + case xlfIMLN: + return "IMLN("; + case xlfIMLOG10: + return "IMLOG10("; + case xlfIMLOG2: + return "IMLOG2("; + case xlfCONVERT: + return "CONVERT("; + case xlfDOUBLEFACT: + return "FACTDOUBLE("; + case xlfGCD: + return "GCD("; + case xlfLCM: + return "LCM("; + case xlfMROUND: + return "MROUND("; + case xlfMULTINOMIAL: + return "MULTINOMIAL("; + case xlfQUOTIENT: + return "QUOTIENT("; + case xlfRANDBETWEEN: + return "RANDBETWEEN("; + case xlfSERIESSUM: + return "SERIESSUM("; + case xlfSQRTPI: + return "SQRTPI("; + case xlfERF: + return "ERF("; + // information Add-ins + case xlfISEVEN: + return "ISEVEN("; + case xlfISODD: + return "ISODD("; + // Date/Time Add-in Formulas + case xlfNETWORKDAYS: + return "NETWORKDAYS("; + case xlfEDATE: + return "EDATE("; + case xlfEOMONTH: + return "EOMONTH("; + case xlfWEEKNUM: + return "WEEKNUM("; + case xlfWORKDAY: + return "WORKDAY("; + case xlfYEARFRAC: + return "YEARFRAC("; + // Statistical + case xlfAVERAGEIF: + return "AVERAGEIF("; + case xlfAVERAGEIFS: + return "AVERAGEIFS("; + case xlfCOUNTIFS: + return "COUNTIFS("; + // Logical + case xlfIFERROR: + return "IFERROR("; + // Math + case xlfSUMIFS: + return "SUMIFS("; } - return ""; + return ""; } - + // Num Params for all PTGFUNCs - ptgfuncvar's have variable # args (hence the name ...) - public static int getNumParams(int iftab){ - if (iftab == xlfNa) return 0; // na - if (iftab == xlfPi) return 0; // Pi - if (iftab == xlfRound) return 2; // Round - if (iftab == xlfRept) return 2; // rept - if (iftab == xlfMid) return 3; // Mid - if (iftab == xlfMod) return 2; // Mod - if (iftab >= xlfDcount && iftab <= xlfDstdev) return 3; // Dxxx formulas - if (iftab == xlfDvar) return 3; // DVar - if (iftab == xlfRand) return 0; // Rand - if (iftab == xlfDate) return 3; // Date - if (iftab == xlfTime) return 3; // Time - if (iftab == xlfDay) return 1; // Day - if (iftab == xlfNow) return 0; // now - if (iftab == xlfAtan2) return 2; // Atan2 - if (iftab == xlfLog) return 2; // Log - if (iftab == xlfLeft) return 2; // Left - if (iftab == xlfRight) return 2; // Right - if (iftab == xlfTrim) return 1; // Trim - if (iftab == xlfText) return 2; // Text - if (iftab == xlfReplace) return 4; // Replace - if (iftab == xlfExact) return 2; // Exact - if (iftab == 165) return 2; //TODO: - if (iftab == xlfDproduct) return 3; // DProduct - if (iftab == xlfDstdevp) return 3; // DStdDevp - if (iftab == xlfDvarp) return 3; // DVarP - if (iftab == xlfDcounta) return 3; // DCountA - if (iftab == xlfRoundup) return 2; // Roundup - if (iftab == xlfRounddown) return 2; // Rounddown - if (iftab == xlfToday) return 0; // today - if (iftab == xlfDget) return 3; // DGet - if (iftab == xlfCombin) return 2; // Combin - if (iftab == xlfFloor) return 2; // Floor - if (iftab == xlfCeiling) return 2; // Ceiling - if (iftab == xlfPower) return 2; // Power - if (iftab == xlfCountif) return 2; // CountIf - if (iftab == xlfQuartile) return 2; - if (iftab == xlfFrequency) return 2; - if (iftab == xlfCorrel) return 2; - if (iftab == xlfCovar) return 2; - if (iftab == xlfSlope) return 2; - if (iftab == xlfIntercept) return 2; - if (iftab == xlfPearson) return 2; - if (iftab == xlfRsq) return 2; - if (iftab == xlfSteyx) return 2; - if (iftab == xlfCritbinom) return 3; - if (iftab == xlfForecast) return 3; - if (iftab == xlfTrend) return 2; - if (iftab == xlfIsnumber) return 1; - if (iftab == xlfMmult) return 2; - if (iftab == xlfHour) return 1; - if (iftab == xlfMinute) return 1; - if (iftab == xlfMonth) return 1; - if (iftab == xlfYear) return 1; - if (iftab == xlfSecond) return 1; - if (iftab == xlfSqrt) return 1; - if (iftab == xlfExp) return 1; - if (iftab == xlfMirr) return 3; - if (iftab == xlfSyd) return 4; - if (iftab == xlfSln) return 3; - if (iftab == xlfIspmt) return 4; - if (iftab == xlfBinomdist) return 4; - if (iftab == xlfChidist) return 2; - if (iftab == xlfChiinv) return 2; - if (iftab == xlfChitest) return 2; - if (iftab == xlfConfidence) return 3; - if (iftab == xlfFtest) return 2; - if (iftab == xlfSumx2my2) return 2; - if (iftab == xlfSumx2py2) return 2; - if (iftab == xlfSumxmy2) return 2; - if (iftab == xlfLookup) return 3; - if (iftab == xlfTrue) return 0; - if (iftab == xlfFalse) return 0; - if (iftab == xlfExpondist) return 3; - if (iftab == xlfFdist) return 3; - if (iftab == xlfFinv) return 3; - if (iftab == xlfLoginv) return 2; - if (iftab == xlfNegbinomdist) return 3; - if (iftab == xlfNormdist) return 4; - if (iftab == xlfNorminv) return 3; - if (iftab == xlfNormsinv) return 1; - if (iftab == xlfStandardize) return 3; - if (iftab == xlfPermut) return 2; - if (iftab == xlfPoisson) return 3; - if (iftab == xlfSumx2my2) return 2; - if (iftab == xlfSumx2py2) return 2; - if (iftab == xlfSumxmy2) return 2; - if (iftab == xlfTdist) return 2; - if (iftab == xlfLarge) return 2; - if (iftab == xlfSmall) return 2; + public static int getNumParams(int iftab) { + if (iftab == xlfNa) return 0; // na + if (iftab == xlfPi) return 0; // Pi + if (iftab == xlfRound) return 2; // Round + if (iftab == xlfRept) return 2; // rept + if (iftab == xlfMid) return 3; // Mid + if (iftab == xlfMod) return 2; // Mod + if (iftab >= xlfDcount && iftab <= xlfDstdev) return 3; // Dxxx formulas + if (iftab == xlfDvar) return 3; // DVar + if (iftab == xlfRand) return 0; // Rand + if (iftab == xlfDate) return 3; // Date + if (iftab == xlfTime) return 3; // Time + if (iftab == xlfDay) return 1; // Day + if (iftab == xlfNow) return 0; // now + if (iftab == xlfAtan2) return 2; // Atan2 + if (iftab == xlfLog) return 2; // Log + if (iftab == xlfLeft) return 2; // Left + if (iftab == xlfRight) return 2; // Right + if (iftab == xlfTrim) return 1; // Trim + if (iftab == xlfText) return 2; // Text + if (iftab == xlfReplace) return 4; // Replace + if (iftab == xlfExact) return 2; // Exact + if (iftab == 165) return 2; //TODO: + if (iftab == xlfDproduct) return 3; // DProduct + if (iftab == xlfDstdevp) return 3; // DStdDevp + if (iftab == xlfDvarp) return 3; // DVarP + if (iftab == xlfDcounta) return 3; // DCountA + if (iftab == xlfRoundup) return 2; // Roundup + if (iftab == xlfRounddown) return 2; // Rounddown + if (iftab == xlfToday) return 0; // today + if (iftab == xlfDget) return 3; // DGet + if (iftab == xlfCombin) return 2; // Combin + if (iftab == xlfFloor) return 2; // Floor + if (iftab == xlfCeiling) return 2; // Ceiling + if (iftab == xlfPower) return 2; // Power + if (iftab == xlfCountif) return 2; // CountIf + if (iftab == xlfQuartile) return 2; + if (iftab == xlfFrequency) return 2; + if (iftab == xlfCorrel) return 2; + if (iftab == xlfCovar) return 2; + if (iftab == xlfSlope) return 2; + if (iftab == xlfIntercept) return 2; + if (iftab == xlfPearson) return 2; + if (iftab == xlfRsq) return 2; + if (iftab == xlfSteyx) return 2; + if (iftab == xlfCritbinom) return 3; + if (iftab == xlfForecast) return 3; + if (iftab == xlfTrend) return 2; + if (iftab == xlfIsnumber) return 1; + if (iftab == xlfMmult) return 2; + if (iftab == xlfHour) return 1; + if (iftab == xlfMinute) return 1; + if (iftab == xlfMonth) return 1; + if (iftab == xlfYear) return 1; + if (iftab == xlfSecond) return 1; + if (iftab == xlfSqrt) return 1; + if (iftab == xlfExp) return 1; + if (iftab == xlfMirr) return 3; + if (iftab == xlfSyd) return 4; + if (iftab == xlfSln) return 3; + if (iftab == xlfIspmt) return 4; + if (iftab == xlfBinomdist) return 4; + if (iftab == xlfChidist) return 2; + if (iftab == xlfChiinv) return 2; + if (iftab == xlfChitest) return 2; + if (iftab == xlfConfidence) return 3; + if (iftab == xlfFtest) return 2; + if (iftab == xlfSumx2my2) return 2; + if (iftab == xlfSumx2py2) return 2; + if (iftab == xlfSumxmy2) return 2; + if (iftab == xlfLookup) return 3; + if (iftab == xlfTrue) return 0; + if (iftab == xlfFalse) return 0; + if (iftab == xlfExpondist) return 3; + if (iftab == xlfFdist) return 3; + if (iftab == xlfFinv) return 3; + if (iftab == xlfLoginv) return 2; + if (iftab == xlfNegbinomdist) return 3; + if (iftab == xlfNormdist) return 4; + if (iftab == xlfNorminv) return 3; + if (iftab == xlfNormsinv) return 1; + if (iftab == xlfStandardize) return 3; + if (iftab == xlfPermut) return 2; + if (iftab == xlfPoisson) return 3; + if (iftab == xlfSumx2my2) return 2; + if (iftab == xlfSumx2py2) return 2; + if (iftab == xlfSumxmy2) return 2; + if (iftab == xlfTdist) return 2; + if (iftab == xlfLarge) return 2; + if (iftab == xlfSmall) return 2; return 1; //if we are lucky - rest all should be 1 param! - } + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.java b/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.java index 405e9f2..0d6ec91 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.java +++ b/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,1183 +22,1182 @@ */ package io.starter.formats.XLS.formulas; -import java.util.Locale; - import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.formats.XLS.XLSRecord; -/** - * Function Handler takes an array of PTG's with a header PtgFunc or PtgFuncVar, calcuates - * those ptgs in the determined way, then return a relevant PtgValue - * - * Descriptions of these functions are available on the msdn site, - * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/office97/html/S88F9.asp - * -*/ +import java.util.Locale; +/** + * Function Handler takes an array of PTG's with a header PtgFunc or PtgFuncVar, calcuates + * those ptgs in the determined way, then return a relevant PtgValue + *

                + * Descriptions of these functions are available on the msdn site, + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/office97/html/S88F9.asp + */ -public class FunctionHandler -{ - -/* - Calculates the function and returns a relevant Ptg as a value - This is the main entry method. +public class FunctionHandler { -*/ -public static final Ptg calculateFunction(Ptg[] ptgs) throws FunctionNotSupportedException, CalculationException{ - Ptg funk; // the function identifier - Ptg[] operands; // the ptgs acted upon by the function - int funkId = 0; //what function are we calling? - - funk = ptgs[0]; - // if ptgs are missing parent_recs, populate from funk - XLSRecord bpar = funk.getParentRec(); - if(bpar!=null){ - for(int t=0;t. @@ -23,96 +23,130 @@ package io.starter.formats.XLS.formulas; -import java.util.Calendar; - import io.starter.OpenXLS.DateConverter; import io.starter.formats.XLS.*; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.util.Calendar; + public abstract class GenericPtg -implements Ptg, Cloneable { - - double doublePrecision= 0.00000001; // doubles/floats cannot be compared for exactness so use precision comparator + implements Ptg, Cloneable { + + double doublePrecision = 0.00000001; // doubles/floats cannot be compared for exactness so use precision comparator public static final long serialVersionUID = 666555444333222l; byte ptgId; byte[] record; - + Ptg[] vars = null; int lock_id = -1; private int locationLocked = Ptg.PTG_LOCATION_POLICY_UNLOCKED; - private BiffRec trackercell = null; - - - - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // This is, in theory, impossible - return null; - } - } + private BiffRec trackercell = null; - - /** a locking mechanism so that Ptgs are not endlessly - * re-calculated - * - * @return - */ - public int getLock(){ - return lock_id; - } - - /** a locking mechanism so that Ptgs are not endlessly - * re-calculated - * - * @return - */ - public void setLock(int x){ - lock_id = x; - } - -// determine behavior - public boolean getIsOperator(){return false;} - public boolean getIsBinaryOperator(){return false;} - public boolean getIsUnaryOperator(){return false;} - public boolean getIsStandAloneOperator(){return false;} - public boolean getIsPrimitiveOperator(){return false;} - public boolean getIsOperand(){return false;} - public boolean getIsFunction(){return false;} - public boolean getIsControl(){return false;} - public boolean getIsArray(){return false;} - public boolean getIsReference(){return false;} - - /** returns the Location Policy of the Ptg is locked - * used during automated BiffRec movement updates - * - * @return int - */ - public int getLocationPolicy(){ - return locationLocked; - } - - /** lock the Location of the Ptg so that it will not - * be updated during automated BiffRec movement updates - * - * @param b setting of the lock the location policy for this Ptg - */ - public void setLocationPolicy(int b){ - locationLocked = b; - } - - /** update the Ptg - * - */ - public void updateRecord(){ - - } - /** Returns the number of Params to pass to the Ptg - */ - public int getNumParams(){ - if(getIsPrimitiveOperator())return 2; + + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // This is, in theory, impossible + return null; + } + } + + + /** + * a locking mechanism so that Ptgs are not endlessly + * re-calculated + * + * @return + */ + public int getLock() { + return lock_id; + } + + /** + * a locking mechanism so that Ptgs are not endlessly + * re-calculated + * + * @return + */ + public void setLock(int x) { + lock_id = x; + } + + // determine behavior + public boolean getIsOperator() { + return false; + } + + public boolean getIsBinaryOperator() { + return false; + } + + public boolean getIsUnaryOperator() { + return false; + } + + public boolean getIsStandAloneOperator() { + return false; + } + + public boolean getIsPrimitiveOperator() { + return false; + } + + public boolean getIsOperand() { + return false; + } + + public boolean getIsFunction() { + return false; + } + + public boolean getIsControl() { + return false; + } + + public boolean getIsArray() { + return false; + } + + public boolean getIsReference() { + return false; + } + + /** + * returns the Location Policy of the Ptg is locked + * used during automated BiffRec movement updates + * + * @return int + */ + public int getLocationPolicy() { + return locationLocked; + } + + /** + * lock the Location of the Ptg so that it will not + * be updated during automated BiffRec movement updates + * + * @param b setting of the lock the location policy for this Ptg + */ + public void setLocationPolicy(int b) { + locationLocked = b; + } + + /** + * update the Ptg + */ + public void updateRecord() { + + } + + /** + * Returns the number of Params to pass to the Ptg + */ + public int getNumParams() { + if (getIsPrimitiveOperator()) return 2; return 0; } @@ -126,531 +160,562 @@ public int getNumParams(){ ################################################### EXPLANATION ###################################################*/ - - /** Operator Ptgs take other Ptgs as arguments - so we need to pass them in to get a meaningful - value. - */ - public void setVars(Ptg[] parr){ + + /** + * Operator Ptgs take other Ptgs as arguments + * so we need to pass them in to get a meaningful + * value. + */ + public void setVars(Ptg[] parr) { this.vars = parr; } - + /* Return all of the cells in this range as an array of Ptg's. This is used for range calculations. */ - public Ptg[] getComponents(){ - return null; + public Ptg[] getComponents() { + return null; } - - /** pass in arbitrary number of values (probably other Ptgs) - and return the resultant value. - - This effectively calculates the Expression. - */ - public Object evaluate(Object[] obj){ + + /** + * pass in arbitrary number of values (probably other Ptgs) + * and return the resultant value. + *

                + * This effectively calculates the Expression. + */ + public Object evaluate(Object[] obj) { // do something useful return this.getString(); } - - /** return the human-readable String representation of - this ptg -- if applicable - */ - public String getTextString(){ - - String strx = ""; - - try{ - strx = getString(); - }catch(Exception e){ - Logger.logErr("Function not supported: " + this.parent_rec.toString()); - } - - if(strx == null) - return ""; - + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + public String getTextString() { + + String strx = ""; + + try { + strx = getString(); + } catch (Exception e) { + Logger.logErr("Function not supported: " + this.parent_rec.toString()); + } + + if (strx == null) + return ""; + StringBuffer out = new StringBuffer(strx); - if(vars!=null){ + if (vars != null) { int numvars = vars.length; if (this.getIsPrimitiveOperator() && this.getIsUnaryOperator()) { - if(numvars > 0) - out.append( vars[0].getTextString()); - - } else if(this.getIsPrimitiveOperator()){ + if (numvars > 0) + out.append(vars[0].getTextString()); + + } else if (this.getIsPrimitiveOperator()) { out.setLength(0); - for(int x=0;x 0) // don't strip 1st paren if no params! 20060501 KSC - out.setLength(out.length()-1); // strip trailing comma - } + out.setLength(out.length() - 1); // strip trailing comma + } } - out.append( getString2()); + out.append(getString2()); return out.toString(); } - + /*text1 and 2 for this Ptg - */ - public String getString(){ + */ + public String getString() { return toString(); } - - /** return the human-readable String representation of - the "closing" portion of this Ptg - such as a closing parenthesis. - */ - - public String getString2(){ - if(this.getIsPrimitiveOperator())return ""; - if(this.getIsOperator())return ")"; + + /** + * return the human-readable String representation of + * the "closing" portion of this Ptg + * such as a closing parenthesis. + */ + + public String getString2() { + if (this.getIsPrimitiveOperator()) return ""; + if (this.getIsOperator()) return ")"; return ""; } - - public byte getOpcode(){return ptgId;} - - public void init(byte[] b){ - ptgId = b[0]; - record = b; - } - - /** - return a Ptg consisting of the calculated values - of the ptg's passed in. Returns null for any non-operand - ptg. - * @throws CalculationException - */ - public Ptg calculatePtg(Ptg[] parsething) throws FunctionNotSupportedException, CalculationException{ - return null; - + + public byte getOpcode() { + return ptgId; + } + + public void init(byte[] b) { + ptgId = b[0]; + record = b; } - - /** Gets the (return) value of this Ptg as an operand Ptg. + + /** + * return a Ptg consisting of the calculated values + * of the ptg's passed in. Returns null for any non-operand + * ptg. + * + * @throws CalculationException + */ + public Ptg calculatePtg(Ptg[] parsething) throws FunctionNotSupportedException, CalculationException { + return null; + + } + + /** + * Gets the (return) value of this Ptg as an operand Ptg. */ public Ptg getPtgVal() { - Object value = this.getValue(); - if (value instanceof Ptg) return (Ptg) value; - else if (value instanceof Boolean) - return new PtgBool( ((Boolean)value).booleanValue() ); - else if (value instanceof Integer) - return new PtgInt( ((Integer)value).intValue() ); - else if (value instanceof Number) - return new PtgNumber( ((Number)value).doubleValue() ); - else if (value instanceof String) - return new PtgStr( (String)value ); - else return new PtgErr( PtgErr.ERROR_VALUE ); - } - + Object value = this.getValue(); + if (value instanceof Ptg) return (Ptg) value; + else if (value instanceof Boolean) + return new PtgBool(((Boolean) value).booleanValue()); + else if (value instanceof Integer) + return new PtgInt(((Integer) value).intValue()); + else if (value instanceof Number) + return new PtgNumber(((Number) value).doubleValue()); + else if (value instanceof String) + return new PtgStr((String) value); + else return new PtgErr(PtgErr.ERROR_VALUE); + } + /** - returns the value of an operand ptg. - @return null for non-operand Ptg. - */ - public Object getValue(){ + * returns the value of an operand ptg. + * + * @return null for non-operand Ptg. + */ + public Object getValue() { return null; } - + /** * Gets the value of the ptg represented as an int. - * + *

                * This can result in loss of precision for floating point values. - * + *

                * overridden in PtgInt to natively return value. - * + * * @return integer representing the ptg, or NAN */ public int getIntVal() { try { return new Double(this.getValue().toString()).intValue(); - }catch(NumberFormatException e) { + } catch (NumberFormatException e) { // we should be throwing something better - if (!(this instanceof PtgErr)) // don't report an error if it's already an error - Logger.logErr("GetIntVal failed for formula: " + this.getParentRec().toString() + " " + e); + if (!(this instanceof PtgErr)) // don't report an error if it's already an error + Logger.logErr("GetIntVal failed for formula: " + this.getParentRec().toString() + " " + e); return 0; - /// RIIIIGHT! throw new FormulaCalculationException(); + /// RIIIIGHT! throw new FormulaCalculationException(); } } - + /** * Gets the value of the ptg represented as an double. - * + *

                * This can result in loss of precision for floating point values. - * + *

                * NAN will be returned for values that are not translateable to an double - * + *

                * overrideen in PtgNumber - * + * * @return integer representing the ptg, or NAN */ public double getDoubleVal() { Object pob = null; Double d = null; - try{ + try { pob = this.getValue(); - if (pob==null) { - Logger.logErr("Unable to calculate Formula at " + this.getLocation()); + if (pob == null) { + Logger.logErr("Unable to calculate Formula at " + this.getLocation()); return java.lang.Double.NaN; } d = (Double) pob; - }catch(ClassCastException e){ + } catch (ClassCastException e) { try { - Float f = (Float)pob; - d = new Double(f.doubleValue()); - }catch(ClassCastException e2){ - try { - Integer in = (Integer) pob; - d = new Double(in.doubleValue()); - }catch(Exception e3){ - if (pob == null || pob.toString().equals("")){ - d = new Double(0); - }else{ - try { - Double dd = new Double(pob.toString()); - return dd.doubleValue(); - }catch(Exception e4){// Logger.logWarn("Error in Ptg Calculator getting Double Value: " + e3); - return java.lang.Double.NaN; - } - } - } + Float f = (Float) pob; + d = new Double(f.doubleValue()); + } catch (ClassCastException e2) { + try { + Integer in = (Integer) pob; + d = new Double(in.doubleValue()); + } catch (Exception e3) { + if (pob == null || pob.toString().equals("")) { + d = new Double(0); + } else { + try { + Double dd = new Double(pob.toString()); + return dd.doubleValue(); + } catch (Exception e4) {// Logger.logWarn("Error in Ptg Calculator getting Double Value: " + e3); + return java.lang.Double.NaN; + } + } + } } - }catch(Throwable exp){ + } catch (Throwable exp) { Logger.logErr("Unexpected Exception in PtgCalculator.getDoubleValue()", exp); } return d.doubleValue(); } - - /** So, here you see we can get the static type from the record itself - then format the output record. Some shorthand techniques are shown. - - */ - public byte[] getRecord(){ - return record; + + /** + * So, here you see we can get the static type from the record itself + * then format the output record. Some shorthand techniques are shown. + */ + public byte[] getRecord() { + return record; } // these do nothing here... - public void setLocation(String s){;} - public String getLocation() throws FormulaNotFoundException{return null;} - public int[] getIntLocation() throws FormulaNotFoundException{return null;} - + public void setLocation(String s) { + } + + public String getLocation() throws FormulaNotFoundException { + return null; + } + + public int[] getIntLocation() throws FormulaNotFoundException { + return null; + } + // Parent Rec is the BiffRec record referenced by Operand Ptgs protected XLSRecord parent_rec; - public void setParentRec(XLSRecord f){parent_rec = f;} - public XLSRecord getParentRec(){return parent_rec;} - - /** Returns an array of doubles from number-type ptg's sent in. - This should only be referenced by sub-classes. - - Null values accessed are treated as 0. Within excel (empty cell values == 0) Tested! - Sometimes as well you can get empty string values, "". These are NOT EQUAL ("" != 0) - * + public void setParentRec(XLSRecord f) { + parent_rec = f; + } + + public XLSRecord getParentRec() { + return parent_rec; + } + + /** + * Returns an array of doubles from number-type ptg's sent in. + * This should only be referenced by sub-classes. + *

                + * Null values accessed are treated as 0. Within excel (empty cell values == 0) Tested! + * Sometimes as well you can get empty string values, "". These are NOT EQUAL ("" != 0) + * + * @param pthings + * @return + */ + protected static Object[] getValuesFromPtgs(Ptg[] pthings) { + Object[] obar = new Object[pthings.length]; + for (int t = 0; t < obar.length; t++) { + if (pthings[t] instanceof PtgErr) + return null; + if (pthings[t] instanceof PtgArray) { + obar[t] = pthings[t].getComponents(); // get all items in array as Ptgs + Object v = null; + try { + v = getValuesFromObjects((Object[]) obar[t]); // get value array from the ptgs + } catch (NumberFormatException e) { // string or non-numeric values + v = getStringValuesFromPtgs((Ptg[]) obar[t]); + } + obar[t] = v; + } else { + Object pval = pthings[t].getValue(); + if (pval instanceof PtgArray) { + obar[t] = ((PtgArray) pval).getComponents(); // get all items in array as Ptgs + Object v = null; + try { + v = getValuesFromObjects((Object[]) obar[t]); // get value array from the ptgs + } catch (NumberFormatException e) { // string or non-numeric values + v = getStringValuesFromPtgs((Ptg[]) obar[t]); + } + obar[t] = v; + } else if (pval instanceof Name) { // then get it's components ... + obar[t] = pthings[t].getComponents(); + Object v = null; + try { + v = getValuesFromPtgs((Ptg[]) obar[t]); // get value array from the ptgs + } catch (NumberFormatException e) { // string or non-numeric values + v = getStringValuesFromPtgs((Ptg[]) obar[t]); + } + obar[t] = v; + } else { // it's a single value + try { + obar[t] = new Double(getDoubleValueFromObject(pval)); + } catch (NumberFormatException e) { + if (pval instanceof CalculationException) + obar[t] = pval.toString(); + else + obar[t] = pval; + } + } + } + } + return obar; + } + + + /** + * Returns an array of doubles from number-type ptg's sent in. + * This should only be referenced by sub-classes. + *

                + * Null values accessed are treated as 0. Within excel (empty cell values == 0) Tested! + * Sometimes as well you can get empty string values, "". These are NOT EQUAL ("" != 0) + * * @param pthings * @return */ - protected static Object[] getValuesFromPtgs(Ptg[] pthings){ - Object[] obar = new Object[pthings.length]; - for(int t=0;t-1) { + if (s.indexOf("/") > -1) { try { Calendar c = DateConverter.convertStringToCalendar(s); - if (c!=null)ret = DateConverter.getXLSDateVal(c); - }catch(Exception e) {//guess not - }; + if (c != null) ret = DateConverter.getXLSDateVal(c); + } catch (Exception e) {//guess not + } } if (ret == 0.0) { - Double d = new Double(s); - ret = d.doubleValue(); + Double d = new Double(s); + ret = d.doubleValue(); } - } - return ret; - } - - /** returns an array of strings from ptg's sent in. - This should only be referenced by sub-classes. - */ - protected static String[] getStringValuesFromPtgs(Ptg[] pthings){ + } + return ret; + } + + /** + * returns an array of strings from ptg's sent in. + * This should only be referenced by sub-classes. + */ + protected static String[] getStringValuesFromPtgs(Ptg[] pthings) { String[] returnStr = new String[pthings.length]; - for (int i=0; i-1 || s.indexOf('&')>-1 || s.indexOf(',')>-1 || s.indexOf('(')>-1) ) { - if (s.indexOf("'")==-1) // normal case of no embedded ' s - return "'" +s + "'"; - return "\"" + s + "\""; - } - } catch (StringIndexOutOfBoundsException e) { } - return s; - } - + if (s == null || s.equals("")) return s; + try { + if (s.charAt(0) != '\'' && (s.indexOf(' ') > -1 || s.indexOf('&') > -1 || s.indexOf(',') > -1 || s.indexOf('(') > -1)) { + if (s.indexOf("'") == -1) // normal case of no embedded ' s + return "'" + s + "'"; + return "\"" + s + "\""; + } + } catch (StringIndexOutOfBoundsException e) { + } + return s; + } + /** * return cell address with $'s e.g. * cell AB12 ==> $AB$12 * cell Sheet1!C2=>Sheet1!$C$2 - * Does NOT handle ranges + * Does NOT handle ranges + * * @param s * @return */ public static String qualifyCellAddress(String s) { - String prefix= ""; - if (s.indexOf("$")==-1) { // it's not qualified yet - int i= s.indexOf("!"); - if (i>-1) { - prefix= s.substring(0, i+1); - s= s.substring(i+1); - } - s= "$" + s; - i= 1; - while (i < s.length() && !Character.isDigit(s.charAt(i++))) ; - i--; - if (i > 0 && i < s.length()) - s= s.substring(0, i) + "$" + s.substring(i); - } - return prefix + s; - } - + String prefix = ""; + if (s.indexOf("$") == -1) { // it's not qualified yet + int i = s.indexOf("!"); + if (i > -1) { + prefix = s.substring(0, i + 1); + s = s.substring(i + 1); + } + s = "$" + s; + i = 1; + while (i < s.length() && !Character.isDigit(s.charAt(i++))) ; + i--; + if (i > 0 && i < s.length()) + s = s.substring(0, i) + "$" + s.substring(i); + } + return prefix + s; + } + public static int getArrayLen(Object o) { - int len= 0; - if (o instanceof double[]) - len=((double[])o).length; - return len; + int len = 0; + if (o instanceof double[]) + len = ((double[]) o).length; + return len; } - + /** - * generic reading of a row byte pair with handling for Excel 2007 if necessary + * generic reading of a row byte pair with handling for Excel 2007 if necessary + * * @param b0 * @param b1 - * @return int row + * @return int row */ public int readRow(byte b0, byte b1) { - if ((parent_rec!=null && !parent_rec.getWorkBook().getIsExcel2007())) { - int rw= io.starter.toolkit.ByteTools.readInt(b0, b1, (byte)0, (byte)0); - if (rw>=MAXROWS_BIFF8-1 || rw < 0 || this instanceof PtgRefN) // PtgRefN's are ALWAYS relative and therefore never over 32xxx - rw= ByteTools.readShort(b0, b1); - return rw; - } - // issue when reading Excel2007 rw from bytes as limits exceed ... try to interpret as best one can - int rw= io.starter.toolkit.ByteTools.readInt(b0, b1, (byte)0, (byte)0); - if (rw==65535) { // have to assume that this means a wholeCol reference - rw= -1; - ((PtgRef)this).wholeCol= true; - } - return rw; - } - + if ((parent_rec != null && !parent_rec.getWorkBook().getIsExcel2007())) { + int rw = io.starter.toolkit.ByteTools.readInt(b0, b1, (byte) 0, (byte) 0); + if (rw >= MAXROWS_BIFF8 - 1 || rw < 0 || this instanceof PtgRefN) // PtgRefN's are ALWAYS relative and therefore never over 32xxx + rw = ByteTools.readShort(b0, b1); + return rw; + } + // issue when reading Excel2007 rw from bytes as limits exceed ... try to interpret as best one can + int rw = io.starter.toolkit.ByteTools.readInt(b0, b1, (byte) 0, (byte) 0); + if (rw == 65535) { // have to assume that this means a wholeCol reference + rw = -1; + ((PtgRef) this).wholeCol = true; + } + return rw; + } + /** * clear out object references in prep for closing workbook */ - public void close() { - parent_rec= null; - trackercell= null; - // vars?? - - } - - + public void close() { + parent_rec = null; + trackercell = null; + // vars?? + + } + + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/IlblListener.java b/src/main/java/io/starter/formats/XLS/formulas/IlblListener.java index c7c43ef..9a01f01 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/IlblListener.java +++ b/src/main/java/io/starter/formats/XLS/formulas/IlblListener.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,43 +24,38 @@ /** * An IlblListener is aware of changes to the Named range references. - * - * Notably, the PtgName recs... - * + *

                + * Notably, the PtgName recs... */ -public interface IlblListener{ - - +public interface IlblListener { + + /** * @return Returns the Ilbl. */ - public short getIlbl(); - + short getIlbl(); + /** * @param Ilbl The Ilbl to set. */ - public void setIlbl(short ixti); - + void setIlbl(short ixti); + /** * Add this to the ilbl listeners - - * */ - public void addListener(); - + void addListener(); + /** * Store the name string for matching missing references - - * + * * @param name */ - public void storeName(String name); - - /** - * get the name of the named range this refers to + void storeName(String name); - * + /** + * get the name of the named range this refers to + * * @return */ - public String getStoredName(); + String getStoredName(); } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.java index e26efb9..bd22fcb 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,17 +22,11 @@ */ package io.starter.formats.XLS.formulas; -import java.text.DecimalFormat; - -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.CellNotFoundException; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.formats.XLS.Labelsst; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.*; import io.starter.toolkit.Logger; +import java.text.DecimalFormat; + /* @@ -44,242 +38,241 @@ acted upon. A Ptg of the type that makes sense (ie boolean, number) is returned after each method. */ -public class InformationCalculator -{ - - - /** - * - CELL - Returns information about the formatting, location, or contents of a cell - The CELL function returns information about the formatting, location, or contents of a cell. +public class InformationCalculator { - CELL(info_type, [reference]) - info_type Required. A text value that specifies what type of cell information you want to return. The following list shows the possible values of the info_type argument and the corresponding results.info_type Returns - "address" Reference of the first cell in reference, as text. - "col" Column number of the cell in reference. - "color" The value 1 if the cell is formatted in color for negative values; otherwise returns 0 (zero). - "contents" Value of the upper-left cell in reference; not a formula. - "filename" Filename (including full path) of the file that contains reference, as text. Returns empty text ("") if the worksheet that contains reference has not yet been saved. - "format" Text value corresponding to the number format of the cell. The text values for the various formats are shown in the following table. Returns "-" at the end of the text value if the cell is formatted in color for negative values. Returns "()" at the end of the text value if the cell is formatted with parentheses for positive or all values. - "parentheses" The value 1 if the cell is formatted with parentheses for positive or all values; otherwise returns 0. - "prefix" Text value corresponding to the "label prefix" of the cell. Returns single quotation mark (') if the cell contains left-aligned text, double quotation mark (") if the cell contains right-aligned text, caret (^) if the cell contains centered text, backslash (\) if the cell contains fill-aligned text, and empty text ("") if the cell contains anything else. - "protect" The value 0 if the cell is not locked; otherwise returns 1 if the cell is locked. - "row" Row number of the cell in reference. - "type" Text value corresponding to the type of data in the cell. Returns "b" for blank if the cell is empty, "l" for label if the cell contains a text constant, and "v" for value if the cell contains anything else. - "width" Column width of the cell, rounded off to an integer. Each unit of column width is equal to the width of one character in the default font size. - reference Optional. The cell that you want information about. If omitted, the information specified in the info_type argument is returned for the last cell that was changed. If the reference argument is a range of cells, the CELL function returns the information for only the upper left cell of the range. - */ - protected static Ptg calcCell(Ptg[] operands) - throws FunctionNotSupportedException { - String type= operands[0].getValue().toString().toLowerCase(); - PtgRef ref= null; - BiffRec cell= null; - if (operands.length > 1) { - ref= (PtgRef) operands[1]; - try { - cell= ref.getParentRec().getWorkBook().getCell(ref.getLocationWithSheet()); - } catch (CellNotFoundException e) { - try { - String sh= null; - try { - sh= ref.getSheetName(); - } catch (WorkSheetNotFoundException we) {; } - if (sh==null) sh= ref.getParentRec().getSheet().getSheetName(); - cell= ref.getParentRec().getWorkBook().getWorkSheetByName(sh).addValue(null, ref.getLocation()); - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - // If ref param is omitted, the information specified in the info_type argument - // is returned for the last cell that was changed - } else if (!type.equals("filename"))// no ref was passed in and option is not "filename" - // We cannot determine which is the "last cell" they are referencing; - throw new FunctionNotSupportedException("Worsheet function CELL with no reference parameter is not supported"); - else // filename option can use any biffrec ... - cell= operands[0].getParentRec(); - - // at this point both ref (PtgRef) and r (BiffRec) should be valid - try { - if (type.equals("address")) { - PtgRef newref= ref; - newref.clearLocationCache(); - newref.fColRel= false; // make absolute - newref.fRwRel= false; - return new PtgStr(newref.getLocation()); - } else if (type.equals("col")) { - return new PtgNumber(ref.getIntLocation()[1]+1); - } else if (type.equals("color")) { // The value 1 if the cell is formatted in color for negative values; otherwise returns 0 (zero). - String s= cell.getFormatPattern(); - if (s.indexOf(";[Red")>-1) - return new PtgNumber(1); - return new PtgNumber(0); - } else if (type.equals("contents")) {// Value of the upper-left cell in reference; not a formula. - return new PtgStr(cell.getStringVal()); - } else if (type.equals("filename")) { - String f= cell.getWorkBook().getFileName(); - String sh= cell.getSheet().getSheetName(); - int i= f.lastIndexOf(java.io.File.separatorChar); - f= f.substring(0, i+1)+ "[" + f.substring(i+1); - f+= "]" + sh; - return new PtgStr(f); - } else if (type.equals("format")) { // Text value corresponding to the number format of the cell. The text values for the various formats are shown in the following table. Returns "-" at the end of the text value if the cell is formatted in color for negative values. Returns "()" at the end of the text value if the cell is formatted with parentheses for positive or all values. - String s= cell.getFormatPattern(); - String ret= "G"; // default? - if (s.equals("General") || - s.equals("# ?/?") || - s.equals("# ??/??")) { - ret= "G"; - } else if (s.equals("0")) { - ret= "F0"; - } else if (s.equals("#,##0")) { - ret= ",0"; - } else if (s.equals("0.00")) { - ret= "F2"; - } else if (s.equals("#,##0.00")) { - ret= ", 2"; - } else if (s.equals("$#,##0_);($#,##0)")) { - ret= "C0"; - } else if (s.equals("$#,##0_);[Red]($#,##0)")) { - ret= "C0-"; - } else if (s.equals("$#,##0.00_);($#,##0.00)")) { - ret= "C2"; - } else if (s.equals("$#,##0.00_);[Red]($#,##0.00)")) { - ret= "C2-"; - } else if (s.equals("0%")) { - ret= "P0"; - } else if (s.equals("0.00%")) { - ret= "P2"; - } else if (s.equals("0.00E+00")) { - ret= "S2"; + /** + * CELL + * Returns information about the formatting, location, or contents of a cell + * The CELL function returns information about the formatting, location, or contents of a cell. + *

                + * CELL(info_type, [reference]) + * info_type Required. A text value that specifies what type of cell information you want to return. The following list shows the possible values of the info_type argument and the corresponding results.info_type Returns + * "address" Reference of the first cell in reference, as text. + * "col" Column number of the cell in reference. + * "color" The value 1 if the cell is formatted in color for negative values; otherwise returns 0 (zero). + * "contents" Value of the upper-left cell in reference; not a formula. + * "filename" Filename (including full path) of the file that contains reference, as text. Returns empty text ("") if the worksheet that contains reference has not yet been saved. + * "format" Text value corresponding to the number format of the cell. The text values for the various formats are shown in the following table. Returns "-" at the end of the text value if the cell is formatted in color for negative values. Returns "()" at the end of the text value if the cell is formatted with parentheses for positive or all values. + * "parentheses" The value 1 if the cell is formatted with parentheses for positive or all values; otherwise returns 0. + * "prefix" Text value corresponding to the "label prefix" of the cell. Returns single quotation mark (') if the cell contains left-aligned text, double quotation mark (") if the cell contains right-aligned text, caret (^) if the cell contains centered text, backslash (\) if the cell contains fill-aligned text, and empty text ("") if the cell contains anything else. + * "protect" The value 0 if the cell is not locked; otherwise returns 1 if the cell is locked. + * "row" Row number of the cell in reference. + * "type" Text value corresponding to the type of data in the cell. Returns "b" for blank if the cell is empty, "l" for label if the cell contains a text constant, and "v" for value if the cell contains anything else. + * "width" Column width of the cell, rounded off to an integer. Each unit of column width is equal to the width of one character in the default font size. + *

                + * reference Optional. The cell that you want information about. If omitted, the information specified in the info_type argument is returned for the last cell that was changed. If the reference argument is a range of cells, the CELL function returns the information for only the upper left cell of the range. + */ + protected static Ptg calcCell(Ptg[] operands) + throws FunctionNotSupportedException { + String type = operands[0].getValue().toString().toLowerCase(); + PtgRef ref = null; + BiffRec cell = null; + if (operands.length > 1) { + ref = (PtgRef) operands[1]; + try { + cell = ref.getParentRec().getWorkBook().getCell(ref.getLocationWithSheet()); + } catch (CellNotFoundException e) { + try { + String sh = null; + try { + sh = ref.getSheetName(); + } catch (WorkSheetNotFoundException we) { + } + if (sh == null) sh = ref.getParentRec().getSheet().getSheetName(); + cell = ref.getParentRec().getWorkBook().getWorkSheetByName(sh).addValue(null, ref.getLocation()); + } catch (Exception ex) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + // If ref param is omitted, the information specified in the info_type argument + // is returned for the last cell that was changed + } else if (!type.equals("filename"))// no ref was passed in and option is not "filename" + // We cannot determine which is the "last cell" they are referencing; + throw new FunctionNotSupportedException("Worsheet function CELL with no reference parameter is not supported"); + else // filename option can use any biffrec ... + cell = operands[0].getParentRec(); + + // at this point both ref (PtgRef) and r (BiffRec) should be valid + try { + if (type.equals("address")) { + PtgRef newref = ref; + newref.clearLocationCache(); + newref.fColRel = false; // make absolute + newref.fRwRel = false; + return new PtgStr(newref.getLocation()); + } else if (type.equals("col")) { + return new PtgNumber(ref.getIntLocation()[1] + 1); + } else if (type.equals("color")) { // The value 1 if the cell is formatted in color for negative values; otherwise returns 0 (zero). + String s = cell.getFormatPattern(); + if (s.indexOf(";[Red") > -1) + return new PtgNumber(1); + return new PtgNumber(0); + } else if (type.equals("contents")) {// Value of the upper-left cell in reference; not a formula. + return new PtgStr(cell.getStringVal()); + } else if (type.equals("filename")) { + String f = cell.getWorkBook().getFileName(); + String sh = cell.getSheet().getSheetName(); + int i = f.lastIndexOf(java.io.File.separatorChar); + f = f.substring(0, i + 1) + "[" + f.substring(i + 1); + f += "]" + sh; + return new PtgStr(f); + } else if (type.equals("format")) { // Text value corresponding to the number format of the cell. The text values for the various formats are shown in the following table. Returns "-" at the end of the text value if the cell is formatted in color for negative values. Returns "()" at the end of the text value if the cell is formatted with parentheses for positive or all values. + String s = cell.getFormatPattern(); + String ret = "G"; // default? + if (s.equals("General") || + s.equals("# ?/?") || + s.equals("# ??/??")) { + ret = "G"; + } else if (s.equals("0")) { + ret = "F0"; + } else if (s.equals("#,##0")) { + ret = ",0"; + } else if (s.equals("0.00")) { + ret = "F2"; + } else if (s.equals("#,##0.00")) { + ret = ", 2"; + } else if (s.equals("$#,##0_);($#,##0)")) { + ret = "C0"; + } else if (s.equals("$#,##0_);[Red]($#,##0)")) { + ret = "C0-"; + } else if (s.equals("$#,##0.00_);($#,##0.00)")) { + ret = "C2"; + } else if (s.equals("$#,##0.00_);[Red]($#,##0.00)")) { + ret = "C2-"; + } else if (s.equals("0%")) { + ret = "P0"; + } else if (s.equals("0.00%")) { + ret = "P2"; + } else if (s.equals("0.00E+00")) { + ret = "S2"; // m/d/yy or m/d/yy h:mm or mm/dd/yy "D4" - } else if (s.equals("m/d/yy") || - s.equals("m/d/yy h:mm") || - s.equals("mm/dd/yy") || - s.equals("mm-dd-yy")) { // added last to accomodate Excel's regional short date setting (format #14) - ret= "D4"; - } else if (s.equals("d-mmm-yy") || - s.equals("dd-mmm-yy")) { - ret= "D1"; - } else if (s.equals("d-mmm") || - s.equals("dd-mmm")) { - ret= "D2"; - } else if (s.equals("mmm-yy")) { - ret= "D3"; - } else if (s.equals("mm/dd")) { - ret= "D5"; - } else if (s.equals("h:mm AM/PM")) { - ret= "D7"; - } else if (s.equals("h:mm:ss AM/PM")) { - ret= "D6"; - } else if (s.equals("h:mm")) { - ret= "D9"; - } else if (s.equals("h:mm:ss")) { - ret= "D8"; - } - return new PtgStr(ret); - } else if (type.equals("parentheses")) { - String s= cell.getFormatPattern(); - if (s.startsWith("(")) - return new PtgNumber(1); - return new PtgNumber(0); - } else if (type.equals("prefix")) { - // TODO: THIS IS NOT CORRECT - EITHER INFORM USER OR ?? + } else if (s.equals("m/d/yy") || + s.equals("m/d/yy h:mm") || + s.equals("mm/dd/yy") || + s.equals("mm-dd-yy")) { // added last to accomodate Excel's regional short date setting (format #14) + ret = "D4"; + } else if (s.equals("d-mmm-yy") || + s.equals("dd-mmm-yy")) { + ret = "D1"; + } else if (s.equals("d-mmm") || + s.equals("dd-mmm")) { + ret = "D2"; + } else if (s.equals("mmm-yy")) { + ret = "D3"; + } else if (s.equals("mm/dd")) { + ret = "D5"; + } else if (s.equals("h:mm AM/PM")) { + ret = "D7"; + } else if (s.equals("h:mm:ss AM/PM")) { + ret = "D6"; + } else if (s.equals("h:mm")) { + ret = "D9"; + } else if (s.equals("h:mm:ss")) { + ret = "D8"; + } + return new PtgStr(ret); + } else if (type.equals("parentheses")) { + String s = cell.getFormatPattern(); + if (s.startsWith("(")) + return new PtgNumber(1); + return new PtgNumber(0); + } else if (type.equals("prefix")) { + // TODO: THIS IS NOT CORRECT - EITHER INFORM USER OR ?? // DOESN'T APPEAR TO MATCH EXCEL - //Text value corresponding to the "label prefix" of the cell. - // Returns single quotation mark (') if the cell contains left-aligned text, double quotation mark (") if the cell contains right-aligned text, - // caret (^) if the cell contains centered text, backslash (\) if the cell contains fill-aligned text, and empty text ("") if the cell contains anything else. - int al= cell.getXfRec().getHorizontalAlignment(); - if(al==FormatConstants.ALIGN_LEFT) - return new PtgStr("'"); - if(al==FormatConstants.ALIGN_CENTER) - return new PtgStr("^"); - if(al==FormatConstants.ALIGN_RIGHT) - return new PtgStr("\""); - if(al==FormatConstants.ALIGN_FILL) - return new PtgStr("\\"); - return new PtgStr(""); - } else if (type.equals("protect")) { - if (cell.getXfRec().isLocked()) - return new PtgNumber(1); - return new PtgNumber(0); - } else if (type.equals("row")) { - return new PtgNumber(ref.getIntLocation()[0]+1); - } else if (type.equals("type")) { - //Text value corresponding to the type of data in the cell. - // Returns "b" for blank if the cell is empty, - //"l" for label if the cell contains a text constant, and - // "v" for value if the cell contains anything else. - if (((XLSRecord) cell).isBlank) - return new PtgStr("b"); - if (cell instanceof Labelsst) - return new PtgStr("l"); - return new PtgStr("v"); - } else if (type.equals("width")) { - int n= 0; - n= cell.getSheet().getColInfo(cell.getColNumber()).getColWidthInChars(); - return new PtgNumber(n); - } - } catch (Exception e) { - Logger.logWarn("CELL: unable to calculate: " + e.toString()); - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * ERROR.TYPE - Returns a number corresponding to an error type - If error_val is - ERROR.TYPE returns - - #NULL! 1 - #DIV/0! 2 - #VALUE! 3 - #REF! 4 - #NAME? 5 - #NUM! 6 - #N/A 7 - Anything else #N/A - */ - protected static Ptg calcErrorType(Ptg[] operands){ - Object o = operands[0].getValue(); - String s = o.toString(); - if (s.equalsIgnoreCase("#NULL!")) return new PtgInt(1); - if (s.equalsIgnoreCase("#DIV/0!")) return new PtgInt(2); - if (s.equalsIgnoreCase("#VALUE!")) return new PtgInt(3); - if (s.equalsIgnoreCase("#REF!")) return new PtgInt(4); - if (s.equalsIgnoreCase("#NAME?")) return new PtgInt(5); - if (s.equalsIgnoreCase("#NUM!")) return new PtgInt(6); - if (s.equalsIgnoreCase("#N/A")) return new PtgInt(7); - return new PtgErr(PtgErr.ERROR_NA); - } - - /** - INFO - Returns information about the current operating environment - INFO(type_text) - - NOTE: Several options are incomplete: - "osversion" -- only valid for Windows versions - "system" -- only valid for Windows and Mac - "release" -- incomplete - "origin" -- does not return R1C1 format - */ - protected static Ptg calcInfo(Ptg[] operands) { - // validate - if (operands==null || operands.length==0 || operands[0].getParentRec()==null) - return new PtgErr(PtgErr.ERROR_VALUE); - String type_text= operands[0].getString(); - String ret= ""; - if (type_text.equals("directory")) // Path of the current directory or folder - return new PtgStr(System.getProperty("user.dir").toLowerCase() + "\\"); - else if (type_text.equals("numfile")) // number of active worksheets in the current workbook + //Text value corresponding to the "label prefix" of the cell. + // Returns single quotation mark (') if the cell contains left-aligned text, double quotation mark (") if the cell contains right-aligned text, + // caret (^) if the cell contains centered text, backslash (\) if the cell contains fill-aligned text, and empty text ("") if the cell contains anything else. + int al = cell.getXfRec().getHorizontalAlignment(); + if (al == FormatConstants.ALIGN_LEFT) + return new PtgStr("'"); + if (al == FormatConstants.ALIGN_CENTER) + return new PtgStr("^"); + if (al == FormatConstants.ALIGN_RIGHT) + return new PtgStr("\""); + if (al == FormatConstants.ALIGN_FILL) + return new PtgStr("\\"); + return new PtgStr(""); + } else if (type.equals("protect")) { + if (cell.getXfRec().isLocked()) + return new PtgNumber(1); + return new PtgNumber(0); + } else if (type.equals("row")) { + return new PtgNumber(ref.getIntLocation()[0] + 1); + } else if (type.equals("type")) { + //Text value corresponding to the type of data in the cell. + // Returns "b" for blank if the cell is empty, + //"l" for label if the cell contains a text constant, and + // "v" for value if the cell contains anything else. + if (((XLSRecord) cell).isBlank) + return new PtgStr("b"); + if (cell instanceof Labelsst) + return new PtgStr("l"); + return new PtgStr("v"); + } else if (type.equals("width")) { + int n = 0; + n = cell.getSheet().getColInfo(cell.getColNumber()).getColWidthInChars(); + return new PtgNumber(n); + } + } catch (Exception e) { + Logger.logWarn("CELL: unable to calculate: " + e.toString()); + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * ERROR.TYPE + * Returns a number corresponding to an error type + * If error_val is + * ERROR.TYPE returns + *

                + * #NULL! 1 + * #DIV/0! 2 + * #VALUE! 3 + * #REF! 4 + * #NAME? 5 + * #NUM! 6 + * #N/A 7 + * Anything else #N/A + */ + protected static Ptg calcErrorType(Ptg[] operands) { + Object o = operands[0].getValue(); + String s = o.toString(); + if (s.equalsIgnoreCase("#NULL!")) return new PtgInt(1); + if (s.equalsIgnoreCase("#DIV/0!")) return new PtgInt(2); + if (s.equalsIgnoreCase("#VALUE!")) return new PtgInt(3); + if (s.equalsIgnoreCase("#REF!")) return new PtgInt(4); + if (s.equalsIgnoreCase("#NAME?")) return new PtgInt(5); + if (s.equalsIgnoreCase("#NUM!")) return new PtgInt(6); + if (s.equalsIgnoreCase("#N/A")) return new PtgInt(7); + return new PtgErr(PtgErr.ERROR_NA); + } + + /** + * INFO + * Returns information about the current operating environment + * INFO(type_text) + *

                + * NOTE: Several options are incomplete: + * "osversion" -- only valid for Windows versions + * "system" -- only valid for Windows and Mac + * "release" -- incomplete + * "origin" -- does not return R1C1 format + */ + protected static Ptg calcInfo(Ptg[] operands) { + // validate + if (operands == null || operands.length == 0 || operands[0].getParentRec() == null) + return new PtgErr(PtgErr.ERROR_VALUE); + String type_text = operands[0].getString(); + String ret = ""; + if (type_text.equals("directory")) // Path of the current directory or folder + return new PtgStr(System.getProperty("user.dir").toLowerCase() + "\\"); + else if (type_text.equals("numfile")) // number of active worksheets in the current workbook // TODO: what is correct definition of "Active Worksheets" - hidden state doesn't seem to affect" - return new PtgNumber(operands[0].getParentRec().getWorkBook().getNumWorkSheets()); - else if (type_text.equals("origin")) { /* Returns the absolute cell reference of the top and leftmost + return new PtgNumber(operands[0].getParentRec().getWorkBook().getNumWorkSheets()); + else if (type_text.equals("origin")) { /* Returns the absolute cell reference of the top and leftmost cell visible in the window, based on the current scrolling position, as text prepended with "$A:". This value is intended for for Lotus 1-2-3 release 3.x compatibility. @@ -289,368 +282,378 @@ else if (type_text.equals("origin")) { /* Returns the absolute cell reference o R1C1 reference style "$A:R9C4" */ // TODO: FINISH R1C1 reference style - String cell= operands[0].getParentRec().getSheet().getWindow2().getTopLeftCell(); - for (int i= cell.length()-1; i>=0; i--) { - if (!Character.isDigit(cell.charAt(i))) { - cell= cell.substring(0, i+1) + "$" + cell.substring(i+1); - break; - } - } - cell="$A:$" + cell; - return new PtgStr(cell); - } else if (type_text.equals("osversion")){ //Current operating system version, as text. - // see end of file for os info - String osversion = System.getProperty("os.version"); - String n= System.getProperty("os.name"); // Windows Vista - String os= ""; - // TODO: need a list of osversions to compare to! have know idea for mac, linux ... - if (n.startsWith("Windows")) { - double v= new Double(osversion); - os= "Windows (32-bit) "; - if (v>=5) - os+="NT "; - DecimalFormat df = new DecimalFormat("##.00"); - os+= df.format(v); - } // otherwise have NO idea as cannot find any info on net - else os += osversion; - return new PtgStr(os); - } else if (type_text.equals("recalc")) { //Current recalculation mode; returns "Automatic" or "Manual". - if (operands[0].getParentRec().getWorkBook().getRecalculationMode()==0) // manual - return new PtgStr("Manual"); - return new PtgStr("Automatic"); - }else if (type_text.equals("release")){ //Version of Microsoft Excel, as text. - // TODO: Finish! 97= 8.0, 2000= 9.0, 2002 (XP)= 10.0, 2003= 11.0, 2007= 12.0 - Logger.logWarn("Worksheet Function INFO(\"release\") is not supported"); - return new PtgStr(""); - }else if (type_text.equals("system")) // Name of the operating environment: Macintosh = "mac" Windows = "pcdos" - // TODO: linux? **************** - if (System.getProperty("os.name").indexOf("Windows")>=0) - return new PtgStr("pcdos"); - else - return new PtgStr("mac"); - // In previous versions of Microsoft Office Excel, the "memavail", "memused", and "totmem" type_text values, returned memory information. - // These type_text values are no longer supported and now return a #N/A error value. - else if (type_text.equals("memavail") || - type_text.equals("memused") || - type_text.equals("totmem")) - return new PtgErr(PtgErr.ERROR_NA); - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * ISBLANK - * ISBLANK determines if the cell referenced is blank, and returns - * a boolean ptg based off that - */ - protected static Ptg calcIsBlank(Ptg[] operands){ - Ptg[] allops = PtgCalculator.getAllComponents(operands); - for (int i=0;i= 0; i--) { + if (!Character.isDigit(cell.charAt(i))) { + cell = cell.substring(0, i + 1) + "$" + cell.substring(i + 1); + break; + } + } + cell = "$A:$" + cell; + return new PtgStr(cell); + } else if (type_text.equals("osversion")) { //Current operating system version, as text. + // see end of file for os info + String osversion = System.getProperty("os.version"); + String n = System.getProperty("os.name"); // Windows Vista + String os = ""; + // TODO: need a list of osversions to compare to! have know idea for mac, linux ... + if (n.startsWith("Windows")) { + double v = new Double(osversion); + os = "Windows (32-bit) "; + if (v >= 5) + os += "NT "; + DecimalFormat df = new DecimalFormat("##.00"); + os += df.format(v); + } // otherwise have NO idea as cannot find any info on net + else os += osversion; + return new PtgStr(os); + } else if (type_text.equals("recalc")) { //Current recalculation mode; returns "Automatic" or "Manual". + if (operands[0].getParentRec().getWorkBook().getRecalculationMode() == 0) // manual + return new PtgStr("Manual"); + return new PtgStr("Automatic"); + } else if (type_text.equals("release")) { //Version of Microsoft Excel, as text. + // TODO: Finish! 97= 8.0, 2000= 9.0, 2002 (XP)= 10.0, 2003= 11.0, 2007= 12.0 + Logger.logWarn("Worksheet Function INFO(\"release\") is not supported"); + return new PtgStr(""); + } else if (type_text.equals("system")) // Name of the operating environment: Macintosh = "mac" Windows = "pcdos" + // TODO: linux? **************** + if (System.getProperty("os.name").indexOf("Windows") >= 0) + return new PtgStr("pcdos"); + else + return new PtgStr("mac"); + // In previous versions of Microsoft Office Excel, the "memavail", "memused", and "totmem" type_text values, returned memory information. + // These type_text values are no longer supported and now return a #N/A error value. + else if (type_text.equals("memavail") || + type_text.equals("memused") || + type_text.equals("totmem")) + return new PtgErr(PtgErr.ERROR_NA); + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * ISBLANK + * ISBLANK determines if the cell referenced is blank, and returns + * a boolean ptg based off that + */ + protected static Ptg calcIsBlank(Ptg[] operands) { + Ptg[] allops = PtgCalculator.getAllComponents(operands); + for (int i = 0; i < allops.length; i++) { + // 20081120 KSC: blanks are handled differently now as Excel counts blank cells as 0's /*Object o = allops[i].getValue(); if (o != null) return new PtgBool(false); */ - if (!allops[i].isBlank()) - return new PtgBool(false); - } - return new PtgBool(true); - } - + if (!allops[i].isBlank()) + return new PtgBool(false); + } + return new PtgBool(true); + } + /** * ISERROR - Value refers to any error value - (#N/A, #VALUE!, #REF!, #DIV/0!, #NUM!, #NAME?, or #NULL!). - Usage@ ISERROR(value) - Return@ PtgBool - */ - protected static Ptg calcIserror(Ptg[] operands){ - if (operands[0] instanceof PtgErr){return new PtgBool(true);} - String[] errorstr = {"#N/A", "#VALUE!", "#REF!", "#DIV/0!", "#NUM!", "#NAME?", "#NULL!" }; - Object o = operands[0].getValue(); - String opval = o.toString(); - for (int i=0;i 1) return new PtgBool(false); - Object o = operands[0].getValue(); - if (o != null){ - try{ // KSC: mod for different number types + mod typo - if (o instanceof Integer) { - int s = ((Integer)o).intValue(); - if(s<0)return new PtgBool(false); - return new PtgBool((s%2==0)); - } else if (o instanceof Float) { - float s = ((Float)o).floatValue(); - if(s<0)return new PtgBool(false); - return new PtgBool((s%2==0)); - } else if (o instanceof Double) { - double s = ((Double)o).doubleValue(); - if(s<0)return new PtgBool(false); - return new PtgBool((s%2==0)); - } - }catch(Exception e){ } - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * ISLOGICAL - Returns TRUE if the value is a logical value - */ - protected static Ptg calcIsLogical(Ptg[] operands){ - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - Object o = operands[0].getValue(); - // unfortunately we need to know the difference between - // "true" and true, if it's a reference this can be difficult - try{ - Boolean b = (Boolean)o; - return new PtgBool(true); - }catch(ClassCastException e){}; - return new PtgBool(false); - } - - /** - ISNUMBER - Returns TRUE if the value is a number - */ - protected static Ptg calcIsNumber(Ptg[] operands){ - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - Object o = operands[0].getValue(); - try{ - Float f= (Float) o; - return new PtgBool(true); - }catch (ClassCastException e) { - try { - Double d= (Double) o; - return new PtgBool(true); - }catch(ClassCastException ee){ - try { - Integer ii= (Integer) o; - return new PtgBool(true); - }catch(ClassCastException eee){ }; - }; - } - return new PtgBool(false); - } + protected static Ptg calcIserr(Ptg[] operands) { + String[] errorstr = {"#VALUE!", "#REF!", "#DIV/0!", "#NUM!", "#NAME?", "#NULL!"}; + if (operands.length != 1) return new PtgErr(PtgErr.ERROR_VALUE); + Object o = operands[0].getValue(); + String opval = o.toString(); + for (int i = 0; i < errorstr.length; i++) { + if (opval.equalsIgnoreCase(errorstr[i])) { + return new PtgBool(true); + } + } + return new PtgBool(false); + } + + /** + * ISEVEN(number) + *

                + * Number is the value to test. If number is not an integer, it is truncated. + *

                + * Remarks + * If number is nonnumeric, ISEVEN returns the #VALUE! error value. + * Examples + * ISEVEN(-1) equals FALSE + * ISEVEN(2.5) equals TRUE + * ISEVEN(5) equals FALSE + *

                + * author: John + */ + protected static Ptg calcIsEven(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + Ptg[] allops = PtgCalculator.getAllComponents(operands); + if (allops.length > 1) return new PtgBool(false); + Object o = operands[0].getValue(); + if (o != null) { + try { // KSC: mod for different number types + mod typo + if (o instanceof Integer) { + int s = ((Integer) o).intValue(); + if (s < 0) return new PtgBool(false); + return new PtgBool((s % 2 == 0)); + } else if (o instanceof Float) { + float s = ((Float) o).floatValue(); + if (s < 0) return new PtgBool(false); + return new PtgBool((s % 2 == 0)); + } else if (o instanceof Double) { + double s = ((Double) o).doubleValue(); + if (s < 0) return new PtgBool(false); + return new PtgBool((s % 2 == 0)); + } + } catch (Exception e) { + } + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * ISLOGICAL + * Returns TRUE if the value is a logical value + */ + protected static Ptg calcIsLogical(Ptg[] operands) { + Ptg[] allops = PtgCalculator.getAllComponents(operands); + if (allops.length > 1) return new PtgBool(false); + Object o = operands[0].getValue(); + // unfortunately we need to know the difference between + // "true" and true, if it's a reference this can be difficult + try { + Boolean b = (Boolean) o; + return new PtgBool(true); + } catch (ClassCastException e) { + } + return new PtgBool(false); + } - /** - ISNONTEXT - Returns TRUE if the value is not text - */ - protected static Ptg calcIsNonText(Ptg[] operands){ - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - // blanks return true for this test - if (allops[0].isBlank()) - return new PtgBool(true); - Object o = operands[0].getValue(); - if (o != null){ - try{ - String s = (String)o; - return new PtgBool(false); - }catch(ClassCastException e){}; - } - return new PtgBool(true); - } + /** + * ISNUMBER + * Returns TRUE if the value is a number + */ + protected static Ptg calcIsNumber(Ptg[] operands) { + Ptg[] allops = PtgCalculator.getAllComponents(operands); + if (allops.length > 1) return new PtgBool(false); + Object o = operands[0].getValue(); + try { + Float f = (Float) o; + return new PtgBool(true); + } catch (ClassCastException e) { + try { + Double d = (Double) o; + return new PtgBool(true); + } catch (ClassCastException ee) { + try { + Integer ii = (Integer) o; + return new PtgBool(true); + } catch (ClassCastException eee) { + } + } + } + return new PtgBool(false); + } + + /** + * ISNONTEXT + * Returns TRUE if the value is not text + */ + protected static Ptg calcIsNonText(Ptg[] operands) { + Ptg[] allops = PtgCalculator.getAllComponents(operands); + if (allops.length > 1) return new PtgBool(false); + // blanks return true for this test + if (allops[0].isBlank()) + return new PtgBool(true); + Object o = operands[0].getValue(); + if (o != null) { + try { + String s = (String) o; + return new PtgBool(false); + } catch (ClassCastException e) { + } + } + return new PtgBool(true); + } /** * ISNA - Value refers to the #N/A - (value not available) error value. - - usage@ ISNA(value) - return@ PtgBool - */ - protected static Ptg calcIsna(Ptg[] operands){ + * Value refers to the #N/A + * (value not available) error value. + *

                + * usage@ ISNA(value) + * return@ PtgBool + */ + protected static Ptg calcIsna(Ptg[] operands) { if (operands.length != 1) return PtgCalculator.getError(); - if (operands[0] instanceof PtgErr){ - PtgErr per = (PtgErr) operands[0]; - if (per.getErrorType() == PtgErr.ERROR_NA){ - return new PtgBool(true); - } - }else if (operands[0].getIsReference()){ - Object o = operands[0].getValue(); - if (o.toString().equalsIgnoreCase(new PtgErr(PtgErr.ERROR_NA).toString())){ - return new PtgBool(true); - } + if (operands[0] instanceof PtgErr) { + PtgErr per = (PtgErr) operands[0]; + if (per.getErrorType() == PtgErr.ERROR_NA) { + return new PtgBool(true); + } + } else if (operands[0].getIsReference()) { + Object o = operands[0].getValue(); + if (o.toString().equalsIgnoreCase(new PtgErr(PtgErr.ERROR_NA).toString())) { + return new PtgBool(true); + } + } + return new PtgBool(false); + } + + /** + * NA + * Returns the error value #N/A + */ + protected static Ptg calcNa(Ptg[] operands) { + return new PtgErr(PtgErr.ERROR_NA); + } + + /** + * ISTEXT + * Returns TRUE if the value is text + */ + protected static Ptg calcIsText(Ptg[] operands) { + Ptg[] allops = PtgCalculator.getAllComponents(operands); + if (allops.length > 1) return new PtgBool(false); + Object o = operands[0].getValue(); + if (o != null) { + try { + String s = (String) o; + return new PtgBool(true); + } catch (ClassCastException e) { + } } return new PtgBool(false); } - /** - NA - Returns the error value #N/A - */ - protected static Ptg calcNa(Ptg[] operands){ - return new PtgErr(PtgErr.ERROR_NA); - } - /** - ISTEXT - Returns TRUE if the value is text - */ - protected static Ptg calcIsText(Ptg[] operands){ - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - Object o = operands[0].getValue(); - if (o != null){ - try{ - String s = (String)o; - return new PtgBool(true); - }catch(ClassCastException e){}; - } - return new PtgBool(false); - } - - -/** - ISODD - Returns TRUE if the number is odd - - author: John -*/ - protected static Ptg calcIsOdd(Ptg[] operands){ - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - Object o = operands[0].getValue(); - if (o != null){ - try{ // KSC: mod for different number types + mod typo - if (o instanceof Integer) { - int s = ((Integer)o).intValue(); - if(s<0)return new PtgBool(false); - return new PtgBool((s%2!=0)); - } else if (o instanceof Float) { - float s = ((Float)o).floatValue(); - if(s<0)return new PtgBool(false); - return new PtgBool((s%2!=0)); - } else if (o instanceof Double) { - double s = ((Double)o).doubleValue(); - if(s<0)return new PtgBool(false); - return new PtgBool((s%2!=0)); - } - } catch (Exception e) {} - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - /** - ISREF - Returns TRUE if the value is a reference - */ - protected static Ptg calcIsRef(Ptg[] operands){ - if (operands[0].getIsReference()){ - return new PtgBool(true); - } - return new PtgBool(false); - } - /** - N - Returns a value converted to a number. - - Syntax - - N(value) - - Value is the value you want converted. N converts values listed in the following table. - - If value is or refers to - N returns - - A number - That number - - A date, in one of the built-in date formats available in Microsoft Excel - The serial number of that date --- Note that to us, this is just a number, the date - format is just that, a format. - - TRUE - 1 - - Anything else - 0 - */ - protected static Ptg calcN(Ptg[] operands){ - Object o = operands[0].getValue(); - if (o instanceof Double || o instanceof Integer || o instanceof Float || o instanceof Long){ - Double d = new Double(o.toString()); - return new PtgNumber(d.doubleValue()); - } - if (o instanceof Boolean){ - Boolean b = (Boolean)o; - boolean bo = b.booleanValue(); - if (bo) return new PtgInt(1); - } - return new PtgInt(0); - } - - /** - TYPE - Returns a number indicating the data type of a value - Value can be any Microsoft Excel value, such as a number, text, logical value, and so on. - If value is TYPE returns - Number 1 - Text 2 - Logical value 4 - Error value 16 - Array 64 - */ - protected static Ptg calcType(Ptg[] operands) { - if (operands[0] instanceof PtgArray) - return new PtgNumber(64); // avoid value calc for arrays - else if (operands[0] instanceof PtgErr) - return new PtgNumber(16); - - // otherwise, test value of operand - Object value = operands[0].getValue(); - int type= 0; - if (value instanceof String) - type= 2; - else if (value instanceof Number) - type= 1; - else if (value instanceof Boolean) - type= 4; - return new PtgNumber(type); - } + /** + * ISODD + * Returns TRUE if the number is odd + *

                + * author: John + */ + protected static Ptg calcIsOdd(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + Ptg[] allops = PtgCalculator.getAllComponents(operands); + if (allops.length > 1) return new PtgBool(false); + Object o = operands[0].getValue(); + if (o != null) { + try { // KSC: mod for different number types + mod typo + if (o instanceof Integer) { + int s = ((Integer) o).intValue(); + if (s < 0) return new PtgBool(false); + return new PtgBool((s % 2 != 0)); + } else if (o instanceof Float) { + float s = ((Float) o).floatValue(); + if (s < 0) return new PtgBool(false); + return new PtgBool((s % 2 != 0)); + } else if (o instanceof Double) { + double s = ((Double) o).doubleValue(); + if (s < 0) return new PtgBool(false); + return new PtgBool((s % 2 != 0)); + } + } catch (Exception e) { + } + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + /** + * ISREF + * Returns TRUE if the value is a reference + */ + protected static Ptg calcIsRef(Ptg[] operands) { + if (operands[0].getIsReference()) { + return new PtgBool(true); + } + return new PtgBool(false); + } + + /** + * N + * Returns a value converted to a number. + *

                + * Syntax + *

                + * N(value) + *

                + * Value is the value you want converted. N converts values listed in the following table. + *

                + * If value is or refers to + * N returns + *

                + * A number + * That number + *

                + * A date, in one of the built-in date formats available in Microsoft Excel + * The serial number of that date --- Note that to us, this is just a number, the date + * format is just that, a format. + *

                + * TRUE + * 1 + *

                + * Anything else + * 0 + */ + protected static Ptg calcN(Ptg[] operands) { + Object o = operands[0].getValue(); + if (o instanceof Double || o instanceof Integer || o instanceof Float || o instanceof Long) { + Double d = new Double(o.toString()); + return new PtgNumber(d.doubleValue()); + } + if (o instanceof Boolean) { + Boolean b = (Boolean) o; + boolean bo = b.booleanValue(); + if (bo) return new PtgInt(1); + } + return new PtgInt(0); + } + + /** + * TYPE + * Returns a number indicating the data type of a value + * Value can be any Microsoft Excel value, such as a number, text, logical value, and so on. + * If value is TYPE returns + * Number 1 + * Text 2 + * Logical value 4 + * Error value 16 + * Array 64 + */ + protected static Ptg calcType(Ptg[] operands) { + if (operands[0] instanceof PtgArray) + return new PtgNumber(64); // avoid value calc for arrays + else if (operands[0] instanceof PtgErr) + return new PtgNumber(16); + + // otherwise, test value of operand + Object value = operands[0].getValue(); + int type = 0; + if (value instanceof String) + type = 2; + else if (value instanceof Number) + type = 1; + else if (value instanceof Boolean) + type = 4; + return new PtgNumber(type); + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/IxtiListener.java b/src/main/java/io/starter/formats/XLS/formulas/IxtiListener.java index 9dc328e..64f4f5e 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/IxtiListener.java +++ b/src/main/java/io/starter/formats/XLS/formulas/IxtiListener.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,29 +24,26 @@ /** * An IxtiListener is aware of changes to the Externsheet Ixti references. - * - * Notably, the PtgRef3D and PtgArea recs... - * + *

                + * Notably, the PtgRef3D and PtgArea recs... */ -public interface IxtiListener{ - - +public interface IxtiListener { + + /** * @return Returns the ixti. */ - public short getIxti(); - + short getIxti(); + /** * @param ixti The ixti to set. */ - public void setIxti(short ixti); - + void setIxti(short ixti); + /** * Add this to the ixti listeners - - * */ - public void addListener(); - - + void addListener(); + + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.java index 9cd7d30..496afa4 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,7 +23,6 @@ package io.starter.formats.XLS.formulas; - /* LogicalCalculator is a collection of static methods that operate as the Microsoft Excel function calls do. @@ -32,236 +31,231 @@ acted upon. A Ptg of the type that makes sense (ie boolean, number) is returned after each method. */ -public class LogicalCalculator -{ - /** - * AND +public class LogicalCalculator { + /** + * AND + *

                + * Returns TRUE if all its arguments are TRUE; + * returns FALSE if one or more arguments is FALSE. + *

                + * Syntax + * AND(logical1,logical2, ...) + *

                + * Logical1, logical2, ... are 1 to 30 conditions you want to test + * that can be either TRUE or FALSE. + *

                + * The arguments must evaluate to logical values such as TRUE or FALSE, + * or the arguments must be arrays or references that contain logical values. + * If an array or reference argument contains text or empty cells, + * those values are ignored. + * If the specified range contains no logical values, + * AND returns the #VALUE! error value. + */ + protected static Ptg calcAnd(Ptg[] operands) { + boolean b = true; + Ptg[] alloperands = PtgCalculator.getAllComponents(operands); + for (int i = 0; i < alloperands.length; i++) { + if (alloperands[i] instanceof PtgBool) { + PtgBool bo = (PtgBool) alloperands[i]; + Boolean bool = (Boolean) bo.getValue(); + if (bool.booleanValue() == false) { + return new PtgBool(false); + } + } else { + // probably a ref, hopefully to a bool + String s = String.valueOf(alloperands[i].getValue()); + if (s.equalsIgnoreCase("false")) return new PtgBool(false); + } + } + return new PtgBool(true); + } - Returns TRUE if all its arguments are TRUE; - returns FALSE if one or more arguments is FALSE. + /** + * IF + * Returns one value if a condition you specify evaluates to + * TRUE and another value if it evaluates to FALSE. + *

                + * Use IF to conduct conditional tests on values and formulas. + *

                + * Syntax 1 + *

                + * IF(logical_test,value_if_true,value_if_false) + *

                + * Logical_test is any value or expression that can be evaluated to TRUE or FALSE. + *

                + * Value_if_true is the value that is returned if logical_test is TRUE. If logical_test is TRUE and value_if_true is omitted, TRUE is returned. Value_if_true can be another formula. + *

                + * Value_if_false is the value that is returned if logical_test is FALSE. If logical_test is FALSE and value_if_false is omitted, FALSE is returned. Value_if_false can be another formula. + *

                + * Remarks + *

                + * Up to seven IF functions can be nested as value_if_true and value_if_false + * arguments to construct more elaborate tests. See the following last example. + * When the value_if_true and value_if_false arguments are evaluated, + * IF returns the value returned by those statements. + * If any of the arguments to IF are arrays, every element of the array + * is evaluated when the IF statement is carried out. If some of the + * value_if_true and value_if_false arguments are action-taking functions, + * all of the actions are taken. + */ + protected static Ptg calcIf(Ptg[] operands) { + // lets assume for now there are always 3 operands.. NOPE! sometimes the missarg gets + // lost for some reason, so we need to treat it like that. + if (operands.length < 2) + return new PtgErr(PtgErr.ERROR_VALUE); + Ptg determine = operands[0]; + Ptg iftrue; + if (operands.length > 1) + iftrue = operands[1]; // 20070212 KSC: if this is blank, return 0 (according to help!) + else { + // return strings + if (operands[0] instanceof PtgStr) + return operands[0]; + return (new PtgInt(0)); + } + Ptg iffalse; + if (operands.length > 2) { + iffalse = operands[2]; + } else { + iffalse = new PtgMissArg(); + } + if (iftrue instanceof PtgMissArg) { + iftrue = new PtgNumber(0); + } + if (iffalse instanceof PtgMissArg) { + iffalse = new PtgNumber(0); + } + if (!(determine instanceof PtgArray)) { + String strval = null; + if (!(determine instanceof PtgRef)) + strval = determine.toString(); + else { + try { + strval = determine.getValue().toString(); + } catch (Exception e) { // could be a formula not found error, etc. don't ignore + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + if (strval.equalsIgnoreCase("true")) { + return iftrue; + } else { + return iffalse; + } + } else { + try { // see what type of operands iftrue and iffalse arrays are + String retArry = ""; + Ptg[] p = determine.getComponents(); +// boolean trueIsArray= iftrue instanceof + boolean res = true; + boolean trueValueIsArray = (iftrue instanceof PtgArray); + boolean falseValueIsArray = (iffalse instanceof PtgArray); + for (int i = 0; i < p.length; i++) { + res = (p[i].toString().equalsIgnoreCase("true")); + if (res) { + if (trueValueIsArray) + retArry = retArry + ((PtgArray) iftrue).arrVals.get(i).toString() + ","; + else + retArry = retArry + iftrue + ","; + } else { // false + if (falseValueIsArray) + retArry = retArry + ((PtgArray) iffalse).arrVals.get(i).toString() + ","; + else + retArry = retArry + iffalse + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } catch (Exception e) { // this should hit if iftrue and iffalse are array types .... TODO: handle! + return iffalse; + } + } + } - Syntax - AND(logical1,logical2, ...) + /** + * Returns the logical function False + */ + protected static Ptg calcFalse(Ptg[] operands) { + return new PtgBool(false); + } - Logical1, logical2, ... are 1 to 30 conditions you want to test - that can be either TRUE or FALSE. + /** + * Returns the logical function true + */ + protected static Ptg calcTrue(Ptg[] operands) { + return new PtgBool(true); + } - The arguments must evaluate to logical values such as TRUE or FALSE, - or the arguments must be arrays or references that contain logical values. - If an array or reference argument contains text or empty cells, - those values are ignored. - If the specified range contains no logical values, - AND returns the #VALUE! error value. - */ - protected static Ptg calcAnd(Ptg[] operands){ - boolean b = true; - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); - for(int i=0; i1) - iftrue= operands[1]; // 20070212 KSC: if this is blank, return 0 (according to help!) - else{ - // return strings - if(operands[0] instanceof PtgStr) - return operands[0]; - return (new PtgInt(0)); - } - Ptg iffalse; - if (operands.length >2){ - iffalse = operands[2]; - }else{ - iffalse = new PtgMissArg(); - } - if (iftrue instanceof PtgMissArg){ - iftrue = new PtgNumber(0); - } - if (iffalse instanceof PtgMissArg){ - iffalse = new PtgNumber(0); - } - if (!(determine instanceof PtgArray)) { - String strval= null; - if (!(determine instanceof PtgRef)) - strval= determine.toString(); - else { - try { - strval= determine.getValue().toString(); - } catch (Exception e) { // could be a formula not found error, etc. don't ignore - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - if (strval.equalsIgnoreCase("true")){ - return iftrue; - }else{ - return iffalse; - } - } else { - try { // see what type of operands iftrue and iffalse arrays are - String retArry= ""; - Ptg[] p= ((PtgArray)determine).getComponents(); -// boolean trueIsArray= iftrue instanceof - boolean res= true; - boolean trueValueIsArray= (iftrue instanceof PtgArray); - boolean falseValueIsArray= (iffalse instanceof PtgArray); - for (int i= 0; i < p.length; i++) { - res= (p[i].toString().equalsIgnoreCase("true")); - if (res) { - if (trueValueIsArray) - retArry= retArry + ((PtgArray) iftrue).arrVals.get(i).toString() + ","; - else - retArry= retArry + iftrue + ","; - } - else { // false - if (falseValueIsArray) - retArry= retArry + ((PtgArray) iffalse).arrVals.get(i).toString() + ","; - else - retArry= retArry + iffalse + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } catch (Exception e) { // this should hit if iftrue and iffalse are array types .... TODO: handle! - return iffalse; - } - } - } - /** - * - * Returns the logical function False - */ - protected static Ptg calcFalse(Ptg[] operands){ - return new PtgBool(false); - } - - /** - * - * Returns the logical function true - */ - protected static Ptg calcTrue(Ptg[] operands){ - return new PtgBool(true); - } - - - /** - * - * Returns the opposite boolean - */ - protected static Ptg calcNot(Ptg[] operands){ - if (String.valueOf(operands[0].getValue()).equalsIgnoreCase("false")){ - return new PtgBool(true); - }else{ - return new PtgBool(false); - } - } - - /** - * - * Returns the opposite boolean - */ - protected static Ptg calcOr(Ptg[] operands){ - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); -// KSC: TESTING -//System.out.print("\tOR " + operands[0].toString() + " " + operands[1].toString() + "? "); - for (int i=0;i + * Value is the argument that is checked for an error. + *

                + * Value_if_error is the value to return if the formula evaluates to an error. The following error types are evaluated: #N/A, #VALUE!, #REF!, #DIV/0!, #NUM!, #NAME?, or #NULL!. + *

                + * Remarks + *

                + * If value or value_if_error is an empty cell, IFERROR treats it as an empty string value (""). + * If value is an array formula, IFERROR returns an array of results for each cell in the range specified in value. See the second example below. + * Example: Trapping division errors by using a regular formula + */ + protected static Ptg calcIferror(Ptg[] operands) { + if (operands == null || operands.length != 2) + return new PtgErr(PtgErr.ERROR_VALUE); + if (!(operands[0] instanceof PtgArray)) { + PtgBool ret = (PtgBool) InformationCalculator.calcIserror(operands); + if (ret.getBooleanValue()) // it's an error + return operands[1]; + else // it's not; return calculated results of 1st operand + return operands[0]; + } else { + Ptg[] components = operands[0].getComponents(); + String retArray = ""; + for (int i = 0; i < components.length; i++) { + Ptg[] test = new Ptg[1]; + test[0] = components[i]; + PtgBool ret = (PtgBool) InformationCalculator.calcIserror(test); + if (ret.getBooleanValue()) // it's an error + retArray = retArray + operands[1] + ","; + else // it's not; return calculated results of 1st operand + retArray = retArray + test[0] + ","; + } + retArray = "{" + retArray.substring(0, retArray.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArray); + return pa; + } + } - */ - protected static Ptg calcIferror(Ptg[] operands){ - if (operands==null || operands.length!=2) - return new PtgErr(PtgErr.ERROR_VALUE); - if (!(operands[0] instanceof PtgArray)) { - PtgBool ret= (PtgBool) InformationCalculator.calcIserror(operands); - if (ret.getBooleanValue()) // it's an error - return operands[1]; - else // it's not; return calculated results of 1st operand - return operands[0]; - } else { - Ptg[] components= operands[0].getComponents(); - String retArray= ""; - for (int i= 0; i < components.length; i++) { - Ptg[] test= new Ptg[1]; - test[0]= components[i]; - PtgBool ret= (PtgBool) InformationCalculator.calcIserror(test); - if (ret.getBooleanValue()) // it's an error - retArray= retArray + operands[1] + ","; - else // it's not; return calculated results of 1st operand - retArray= retArray + test[0] + ","; - } - retArray= "{" + retArray.substring(0, retArray.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArray); - return pa; - } - } - } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.java index 6cbc192..4d253f5 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,613 +22,614 @@ */ package io.starter.formats.XLS.formulas; -import java.util.ArrayList; - import io.starter.OpenXLS.ExcelTools; -import io.starter.toolkit.*; import io.starter.formats.XLS.*; +import io.starter.toolkit.Logger; + +import java.util.ArrayList; /** - LookupReferenceCalculator is a collection of static methods that operate - as the Microsoft Excel function calls do. + * LookupReferenceCalculator is a collection of static methods that operate + * as the Microsoft Excel function calls do. + *

                + * All methods are called with an array of ptg's, which are then + * acted upon. A Ptg of the type that makes sense (ie boolean, number) + * is returned after each method. + */ - All methods are called with an array of ptg's, which are then - acted upon. A Ptg of the type that makes sense (ie boolean, number) - is returned after each method. -*/ +public class LookupReferenceCalculator { -public class LookupReferenceCalculator -{ - - /** - ADDRESS - Creates a cell address as text, given specified row and column numbers. - - Syntax - ADDRESS(row_num,column_num,abs_num,a1,sheet_text) - Row_num is the row number to use in the cell reference. - Column_num is the column number to use in the cell reference. - Abs_num specifies the type of reference to return. - - Abs_num Returns this type of reference - 1 or omitted Absolute - - 2 Absolute row; relative column - - 3 Relative row; absolute column - - 4 Relative - - A1 is a logical value that specifies the A1 or R1C1 reference style. - If a1 is TRUE or omitted, ADDRESS returns an A1-style reference; - if FALSE, ADDRESS returns an R1C1-style reference. - - Sheet_text is text specifying the name of the worksheet to be - used as the external reference. If sheet_text is omitted, no sheet name is used. - - Examples - - ADDRESS(2,3) equals "$C$2" - - ADDRESS(2,3,2) equals "C$2" - - ADDRESS(2,3,2,FALSE) equals "R2C[3]" - - ADDRESS(2,3,1,FALSE,"[Book1]Sheet1") equals "[Book1]Sheet1!R2C3" - - ADDRESS(2,3,1,FALSE,"EXCEL SHEET") equals "'EXCEL SHEET'!R2C3" - - */ - protected static Ptg calcAddress(Ptg[] operands){ - if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); - // deal with floating point refs - String rx1 = operands[0].getValue().toString(); - if(rx1.indexOf(".")>-1) - rx1=rx1.substring(0, rx1.indexOf(".")); - int row = Integer.valueOf(rx1).intValue(); - // deal with floating point refs - String cx1 = operands[1].getValue().toString(); - if(cx1.indexOf(".")>-1) - cx1=cx1.substring(0, cx1.indexOf(".")); - - int col = Integer.valueOf(cx1).intValue(); - int abs_num = 1; - boolean ref_style = true; - String sheettext = ""; - if (operands.length > 2){ - if (operands[2].getValue() != null){ //checking for a ptgmissarg - abs_num = ((Integer)operands[2].getValue()).intValue(); - } - } - if (operands.length > 3){ - if (operands[3].getValue() != null){ //checking for a ptgmissarg - Boolean b = Boolean.valueOf(String.valueOf(operands[3].getValue())); - ref_style = b.booleanValue(); - } - } - if (operands.length > 4){ - if (operands[4].getValue() != null){ //checking for a ptgmissarg - sheettext = (String)operands[4].getValue() + "!"; - } - } - String loc = ""; - String colstr = ExcelTools.getAlphaVal(col-1); - if (ref_style){ - if (abs_num ==1){ - loc = "$" + colstr + "$" + row; - }else if(abs_num ==2){ - loc = colstr + "$" + row; - }else if(abs_num ==3){ - loc ="$" + colstr + row; - }else if(abs_num ==4){ - loc = colstr + row; - } - }else{ - if (abs_num ==1){ - loc = "R" + row + "C" + col; // this is transposed with abs_num 4. Error in Excel - }else if(abs_num ==2){ - loc = "R" + row + "C[" + col + "]"; - }else if(abs_num ==3){ - loc = "R[" + row + "]C" + col; - }else if(abs_num ==4){ - loc = "R[" + row + "]C[" + col + "]"; + /** + * ADDRESS + * Creates a cell address as text, given specified row and column numbers. + *

                + * Syntax + * ADDRESS(row_num,column_num,abs_num,a1,sheet_text) + * Row_num is the row number to use in the cell reference. + * Column_num is the column number to use in the cell reference. + * Abs_num specifies the type of reference to return. + *

                + * Abs_num Returns this type of reference + * 1 or omitted Absolute + *

                + * 2 Absolute row; relative column + *

                + * 3 Relative row; absolute column + *

                + * 4 Relative + *

                + * A1 is a logical value that specifies the A1 or R1C1 reference style. + * If a1 is TRUE or omitted, ADDRESS returns an A1-style reference; + * if FALSE, ADDRESS returns an R1C1-style reference. + *

                + * Sheet_text is text specifying the name of the worksheet to be + * used as the external reference. If sheet_text is omitted, no sheet name is used. + *

                + * Examples + *

                + * ADDRESS(2,3) equals "$C$2" + *

                + * ADDRESS(2,3,2) equals "C$2" + *

                + * ADDRESS(2,3,2,FALSE) equals "R2C[3]" + *

                + * ADDRESS(2,3,1,FALSE,"[Book1]Sheet1") equals "[Book1]Sheet1!R2C3" + *

                + * ADDRESS(2,3,1,FALSE,"EXCEL SHEET") equals "'EXCEL SHEET'!R2C3" + */ + protected static Ptg calcAddress(Ptg[] operands) { + if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); + // deal with floating point refs + String rx1 = operands[0].getValue().toString(); + if (rx1.indexOf(".") > -1) + rx1 = rx1.substring(0, rx1.indexOf(".")); + int row = Integer.valueOf(rx1).intValue(); + // deal with floating point refs + String cx1 = operands[1].getValue().toString(); + if (cx1.indexOf(".") > -1) + cx1 = cx1.substring(0, cx1.indexOf(".")); + + int col = Integer.valueOf(cx1).intValue(); + int abs_num = 1; + boolean ref_style = true; + String sheettext = ""; + if (operands.length > 2) { + if (operands[2].getValue() != null) { //checking for a ptgmissarg + abs_num = ((Integer) operands[2].getValue()).intValue(); + } + } + if (operands.length > 3) { + if (operands[3].getValue() != null) { //checking for a ptgmissarg + Boolean b = Boolean.valueOf(String.valueOf(operands[3].getValue())); + ref_style = b.booleanValue(); + } + } + if (operands.length > 4) { + if (operands[4].getValue() != null) { //checking for a ptgmissarg + sheettext = operands[4].getValue() + "!"; + } + } + String loc = ""; + String colstr = ExcelTools.getAlphaVal(col - 1); + if (ref_style) { + if (abs_num == 1) { + loc = "$" + colstr + "$" + row; + } else if (abs_num == 2) { + loc = colstr + "$" + row; + } else if (abs_num == 3) { + loc = "$" + colstr + row; + } else if (abs_num == 4) { + loc = colstr + row; + } + } else { + if (abs_num == 1) { + loc = "R" + row + "C" + col; // this is transposed with abs_num 4. Error in Excel + } else if (abs_num == 2) { + loc = "R" + row + "C[" + col + "]"; + } else if (abs_num == 3) { + loc = "R[" + row + "]C" + col; + } else if (abs_num == 4) { + loc = "R[" + row + "]C[" + col + "]"; + + } + } + loc = sheettext + loc; + return new PtgStr(loc); + + } - } - } - loc = sheettext + loc; - return new PtgStr(loc); - - } /** - AREAS - Returns the number of areas in a reference. An area is a range of contiguous cells or a single cell. - - Reference is a reference to a cell or range of cells and can refer to multiple areas. - If you want to specify several references as a single argument, then you must include extra sets of parentheses so that Microsoft Excel will not interpret the comma as a field separator. - - NOTE: this appears to be correct given Excel information but logic is not 100% known - e.g. =AREAS(B2:D4 D3) = 1 - =AREAS(B2:D4 E3) gives a #NULL! error - why? - =AREAS(B2:D4,E3) = 2 - */ - protected static Ptg calcAreas(Ptg[] operands){ - Ptg ref= operands[0]; - String s= ref.toString(); - String[] areas= s.split(",(?=([^'|\"]*'[^'|\"]*'|\")*[^'|\"]*$)"); - return new PtgNumber(areas.length); - } - - /** - CHOOSE - Chooses a value from a list of values - - Note, this function does not support one specific use-case. That is choosing a ptgref - and using that ptgref to complete a ptgarea. Example - =SUM(E6:CHOOSE(3,G4,G5,G6)) - =SUM(E6:G6) - - */ - protected static Ptg calcChoose(Ptg[] operands){ - if (operands.length<2) return new PtgErr(PtgErr.ERROR_VALUE); - Object o = operands[0].getValue(); - try { - Double dd = new Double(o.toString()); // this can be non-integer, so truncate it if so... - double e = dd.doubleValue(); - int i = (int)e; - if(i > operands.length+1 || i < 1)return new PtgErr(PtgErr.ERROR_REF); - o = operands[i].getValue(); - Double d = (Double)o; - return new PtgNumber(d.doubleValue()); - }catch(Exception ex){ - new PtgErr(PtgErr.ERROR_VALUE); - }; - return new PtgStr(o.toString()); - - } - - /** - COLUMN - Returns the column number of a reference - */ - protected static Ptg calcColumn(Ptg[] operands){ - if (operands[0] instanceof PtgFuncVar){ - // we need to return the col where the formula is. - PtgFuncVar pfunk = (PtgFuncVar)operands[0]; - try{ - int loc = pfunk.getParentRec().getColNumber(); - loc += 1; - return new PtgInt(loc); - }catch(Exception e){}; - }else{ - // It's ugly, but we are going to handle the four types of references seperately, as there is no good way - // to generically get this info - try{ - if(operands[0] instanceof PtgArea){ - PtgArea pa= (PtgArea)operands[0]; - int[] loc= pa.getIntLocation(); - return new PtgInt(loc[1]+1); - } else if (operands[0] instanceof PtgRef){ - PtgRef pref = (PtgRef)operands[0]; - int loc= pref.getIntLocation()[1]; - loc += 1; - return new PtgInt(loc); - } else if (operands[0] instanceof PtgName) { // table??? - String range= ((PtgName) operands[0]).getName().getLocation(); - int[] loc= ExcelTools.getRangeCoords(range); - return new PtgInt(loc[1]+1); - } - }catch(Exception e){}; - } - return new PtgInt(-1); - } - - /** - COLUMNS - Returns the number of columns in an array reference or array formula - */ - // TODO: Not finished yet! - protected static Ptg calcColumns(Ptg[] operands){ - // - if (operands[0] instanceof PtgFuncVar){ - // we need to return the col where the formula is. - PtgFuncVar pfunk = (PtgFuncVar)operands[0]; - try{ - int loc = pfunk.getParentRec().getColNumber(); - loc += 1; - return new PtgInt(loc); - }catch(Exception e){}; - }else{ - // It's ugly, but we are going to handle the four types of references seperately, as there is no good way - // to generically get this info - try{ - if(operands[0] instanceof PtgArea){ - PtgArea pa= (PtgArea) operands[0]; - int[] loc= pa.getIntLocation(); - int ncols= loc[3]-loc[1]+1; - return new PtgInt(ncols); - } else if (operands[0] instanceof PtgArray) { - PtgArray parr= (PtgArray) operands[0]; - return new PtgInt(parr.nc+1); - } - }catch(Exception e){}; - } - return new PtgInt(-1); - } - - /** HLOOKUP: Looks in the top row of an array and returns the value of the - * indicated cell. - */ - protected static Ptg calcHlookup(Ptg[] operands) - throws FunctionNotSupportedException{ - - if (operands.length < 3) return new PtgErr(PtgErr.ERROR_VALUE); - boolean sorted = true; - boolean isNumber = true; - Ptg lookup_value = operands[0]; - Ptg table_array = operands[1]; - PtgInt row_index_num = (PtgInt)operands[2]; - int rowNum = row_index_num.getVal() -1;// reduce 1 for ordinal base off firstcol - if (operands.length > 3){ - if (operands[3].getValue()!= null){ - Object o= operands[3].getValue(); - if (o instanceof Boolean) - sorted = ((Boolean) o).booleanValue(); - else if (o instanceof Integer) - sorted= (((Integer) o).intValue()!=0); - } - } - int[] retarea = {0,0}; - Boundsheet bs = lookup_value.getParentRec().getSheet(); - WorkBook bk = table_array.getParentRec().getWorkBook(); - PtgRef[] lookupComponents = null; - PtgRef[] valueComponents = null; - // first, get the lookup Column Vals - if(table_array instanceof PtgName){ - // Handle getting vals out of name - - }else if (table_array instanceof PtgArea || table_array instanceof PtgArea3d){ - try{ - PtgArea pa = (PtgArea)table_array; - int[] range = table_array.getIntLocation(); - - // TODO: check rc sanity here - int firstrow = range[0]; - lookupComponents = (PtgRef[])pa.getRowComponents(firstrow); - valueComponents = (PtgRef[])pa.getRowComponents(firstrow + rowNum); - }catch(/*20070209 KSC: FormulaNotFound*/Exception e){ - Logger.logWarn("Error in LookupReferenceCalculator: Cannot determine PtgArea location. " +e);} - - } - // error check - if (lookupComponents == null || lookupComponents.length == 0)return new PtgErr(PtgErr.ERROR_REF); - // lets check if we are dealing with strings or numbers.... - try{ - String val = lookupComponents[0].getValue().toString(); - Double d = new Double(val); - }catch(NumberFormatException e){ - isNumber=false; - } - - if (isNumber){ - double match_num; - try { - match_num = Double.parseDouble( - lookup_value.getValue().toString() ); - } catch (NumberFormatException e) { - return new PtgErr( PtgErr.ERROR_NA ); - } + * AREAS + * Returns the number of areas in a reference. An area is a range of contiguous cells or a single cell. + *

                + * Reference is a reference to a cell or range of cells and can refer to multiple areas. + * If you want to specify several references as a single argument, then you must include extra sets of parentheses so that Microsoft Excel will not interpret the comma as a field separator. + *

                + * NOTE: this appears to be correct given Excel information but logic is not 100% known + * e.g. =AREAS(B2:D4 D3) = 1 + * =AREAS(B2:D4 E3) gives a #NULL! error - why? + * =AREAS(B2:D4,E3) = 2 + */ + protected static Ptg calcAreas(Ptg[] operands) { + Ptg ref = operands[0]; + String s = ref.toString(); + String[] areas = s.split(",(?=([^'|\"]*'[^'|\"]*'|\")*[^'|\"]*$)"); + return new PtgNumber(areas.length); + } - for (int i = 0; i < lookupComponents.length; i++){ - double val; - try { - val = Double.parseDouble( - lookupComponents[i].getValue().toString() ); - } catch (NumberFormatException e) { - // Ignore entries in the table that aren't numbers. - continue; - } + /** + * CHOOSE + * Chooses a value from a list of values + *

                + * Note, this function does not support one specific use-case. That is choosing a ptgref + * and using that ptgref to complete a ptgarea. Example + * =SUM(E6:CHOOSE(3,G4,G5,G6)) + * =SUM(E6:G6) + */ + protected static Ptg calcChoose(Ptg[] operands) { + if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); + Object o = operands[0].getValue(); + try { + Double dd = new Double(o.toString()); // this can be non-integer, so truncate it if so... + double e = dd.doubleValue(); + int i = (int) e; + if (i > operands.length + 1 || i < 1) return new PtgErr(PtgErr.ERROR_REF); + o = operands[i].getValue(); + Double d = (Double) o; + return new PtgNumber(d.doubleValue()); + } catch (Exception ex) { + new PtgErr(PtgErr.ERROR_VALUE); + } + return new PtgStr(o.toString()); - if (val == match_num) { - return valueComponents[i].getPtgVal(); - } else if (sorted && val > match_num) { - if (i == 0) return new PtgErr( PtgErr.ERROR_NA ); - else return valueComponents[ i - 1 ].getPtgVal(); - } - } + } - if (sorted) return valueComponents[ lookupComponents.length - 1 ].getPtgVal(); - else return new PtgErr( PtgErr.ERROR_NA ); + /** + * COLUMN + * Returns the column number of a reference + */ + protected static Ptg calcColumn(Ptg[] operands) { + if (operands[0] instanceof PtgFuncVar) { + // we need to return the col where the formula is. + PtgFuncVar pfunk = (PtgFuncVar) operands[0]; + try { + int loc = pfunk.getParentRec().getColNumber(); + loc += 1; + return new PtgInt(loc); + } catch (Exception e) { + } + } else { + // It's ugly, but we are going to handle the four types of references seperately, as there is no good way + // to generically get this info + try { + if (operands[0] instanceof PtgArea) { + PtgArea pa = (PtgArea) operands[0]; + int[] loc = pa.getIntLocation(); + return new PtgInt(loc[1] + 1); + } else if (operands[0] instanceof PtgRef) { + PtgRef pref = (PtgRef) operands[0]; + int loc = pref.getIntLocation()[1]; + loc += 1; + return new PtgInt(loc); + } else if (operands[0] instanceof PtgName) { // table??? + String range = ((PtgName) operands[0]).getName().getLocation(); + int[] loc = ExcelTools.getRangeCoords(range); + return new PtgInt(loc[1] + 1); + } + } catch (Exception e) { + } + } + return new PtgInt(-1); + } - } else { - //TODO: need to handle as string - } + /** + * COLUMNS + * Returns the number of columns in an array reference or array formula + */ + // TODO: Not finished yet! + protected static Ptg calcColumns(Ptg[] operands) { + // + if (operands[0] instanceof PtgFuncVar) { + // we need to return the col where the formula is. + PtgFuncVar pfunk = (PtgFuncVar) operands[0]; + try { + int loc = pfunk.getParentRec().getColNumber(); + loc += 1; + return new PtgInt(loc); + } catch (Exception e) { + } + } else { + // It's ugly, but we are going to handle the four types of references seperately, as there is no good way + // to generically get this info + try { + if (operands[0] instanceof PtgArea) { + PtgArea pa = (PtgArea) operands[0]; + int[] loc = pa.getIntLocation(); + int ncols = loc[3] - loc[1] + 1; + return new PtgInt(ncols); + } else if (operands[0] instanceof PtgArray) { + PtgArray parr = (PtgArray) operands[0]; + return new PtgInt(parr.nc + 1); + } + } catch (Exception e) { + } + } + return new PtgInt(-1); + } - return new PtgErr(PtgErr.ERROR_NULL); - } + /** + * HLOOKUP: Looks in the top row of an array and returns the value of the + * indicated cell. + */ + protected static Ptg calcHlookup(Ptg[] operands) + throws FunctionNotSupportedException { -/** - HYPERLINK - Creates a shortcut or jump that opens a document - stored on a network server, an intranet, or the Internet - - Function just returns the "friendly name" of the link, - Excel doesn't appear to validate the url ... - */ - - protected static Ptg calcHyperlink(Ptg[] operands){ - try { - if (operands.length==2) - return new PtgStr(operands[1].getValue().toString()); - return new PtgStr(operands[0].getValue().toString()); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } + if (operands.length < 3) return new PtgErr(PtgErr.ERROR_VALUE); + boolean sorted = true; + boolean isNumber = true; + Ptg lookup_value = operands[0]; + Ptg table_array = operands[1]; + PtgInt row_index_num = (PtgInt) operands[2]; + int rowNum = row_index_num.getVal() - 1;// reduce 1 for ordinal base off firstcol + if (operands.length > 3) { + if (operands[3].getValue() != null) { + Object o = operands[3].getValue(); + if (o instanceof Boolean) + sorted = ((Boolean) o).booleanValue(); + else if (o instanceof Integer) + sorted = (((Integer) o).intValue() != 0); + } } - - /**INDEX - Returns a value or the reference to a value from within a table or range. - There are two forms of the INDEX function: the array form and the reference form. - - Array Form: - Returns the value of an element in a table or an array selected by the row and column number indexes. - Use the array form if the first argument to INDEX is an array constant. - INDEX(array,row_num,column_num) - Array is a range of cells or an array constant. - If array contains only one row or column, - the corresponding row_num or column_num argument is optional. - If array has more than one row and more than one column, and only row_num or column_num is used, - INDEX returns an array of the entire row or column in array. - Row_num selects the row in array from which to return a value. If row_num is omitted, column_num is required. - Column_num selects the column in array from which to return a value. If column_num is omitted, row_num is required. - - Reference Form: - Returns the reference of the cell at the intersection of a particular row and column. - If the reference is made up of nonadjacent selections, you can pick the selection to look in. - INDEX(reference,row_num,column_num,area_num) - Reference is a reference to one or more cell ranges. - If you are entering a nonadjacent range for the reference, enclose reference in parentheses. - If each area in reference contains only one row or column, the row_num or column_num argument, respectively, is optional. For example, for a single row reference, use INDEX(reference,,column_num). - Row_num is the number of the row in reference from which to return a reference. - Column_num is the number of the column in reference from which to return a reference. - Area_num selects a range in reference from which to return the intersection of row_num and column_num. The first area selected or entered is numbered 1, the second is 2, and so on. If area_num is omitted, INDEX uses area 1. - - Given a BiffRec Range, choose the cell within referenced by the - row and column operands. - - example: - =INDEX(G3:L8,6,4) - returns the value of the BiffRec at row 6, col 4 in the following table - which is 0.11 - - G H I J K L - 1 - 2 - 3 0.007 0.005 0.003 0.002 0.002 0.001 - 4 0.025 0.017 0.012 0.008 0.006 0.005 - 5 0.062 0.044 0.032 0.023 0.018 0.015 - 6 0.116 0.086 0.064 0.049 0.04 0.035 - 7 0.171 0.13 0.101 0.082 0.07 0.062 - 8 0.211 0.165 0.132 0.11 0.096 0.088 - - **/ - protected static Ptg calcIndex(Ptg[] operands){ - if (operands.length<1) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg o = operands[0]; - // only 1st operand is required; changed below (see doc snippet below) - /*If each area in reference contains only one row or column, - * the row_num or column_num argument, respectively, is optional. - * For example, for a single row reference, use INDEX(reference,,column_num). - */ - Object rowref = new Double(1); // defaults (1-based) - Object colref = new Double(1); - double areanum= 1; - int[] retarea= null; - String sht= null; - try { - if (operands.length>1) { - Ptg rowrefp = (Ptg)operands[1]; - rowref = rowrefp.getValue(); - } - if (operands.length>2) { - Ptg colrefp = (Ptg)operands[2]; - colref = colrefp.getValue(); - } - if (operands.length>3) { - areanum= operands[3].getDoubleVal(); - } - if (o instanceof PtgArea) { - retarea = ((PtgArea)o).getIntLocation(); - sht= ((PtgArea) o).getSheetName(); - } else if(o instanceof PtgName) { - //Ptg[] p=((PtgName) o).getName().getComponents(); //CellRangePtgs(); - //o= p[0]; - String r= ((PtgName) o).getName().getLocation(); - retarea = ExcelTools.getRangeRowCol(r); - sht= (r.indexOf("!")==-1)?null:r.substring(0, r.indexOf("!")); - } else if (o instanceof PtgMemFunc) { - Ptg[] ps= ((PtgMemFunc)o).getComponents(); - areanum--; - if (areanum >= 0 && areanum < ps.length) - o= ps[(int)areanum]; - else - o= ps[0]; - retarea = ((PtgArea)o).getIntLocation(); - sht= ((PtgArea) o).getSheetName(); - } else if (o instanceof PtgArray) { - Ptg[] ps= ((PtgArray) o).getComponents(); - areanum--; - if (areanum >= 0 && areanum < ps.length) - o= ps[(int)areanum]; - else - o= ps[0]; - retarea = ((PtgArea)o).getIntLocation(); - // TODO: Sheet! - } - - // now should have the correct area - if(retarea!=null){ - // really we can just use the first position to get a ref - // then the second just checks bounds + int[] retarea = {0, 0}; + Boundsheet bs = lookup_value.getParentRec().getSheet(); + WorkBook bk = table_array.getParentRec().getWorkBook(); + PtgRef[] lookupComponents = null; + PtgRef[] valueComponents = null; + // first, get the lookup Column Vals + if (table_array instanceof PtgName) { + // Handle getting vals out of name + + } else if (table_array instanceof PtgArea || table_array instanceof PtgArea3d) { + try { + PtgArea pa = (PtgArea) table_array; + int[] range = table_array.getIntLocation(); + + // TODO: check rc sanity here + int firstrow = range[0]; + lookupComponents = (PtgRef[]) pa.getRowComponents(firstrow); + valueComponents = (PtgRef[]) pa.getRowComponents(firstrow + rowNum); + } catch (/*20070209 KSC: FormulaNotFound*/Exception e) { + Logger.logWarn("Error in LookupReferenceCalculator: Cannot determine PtgArea location. " + e); + } + + } + // error check + if (lookupComponents == null || lookupComponents.length == 0) return new PtgErr(PtgErr.ERROR_REF); + // lets check if we are dealing with strings or numbers.... + try { + String val = lookupComponents[0].getValue().toString(); + Double d = new Double(val); + } catch (NumberFormatException e) { + isNumber = false; + } + + if (isNumber) { + double match_num; + try { + match_num = Double.parseDouble( + lookup_value.getValue().toString()); + } catch (NumberFormatException e) { + return new PtgErr(PtgErr.ERROR_NA); + } + + for (int i = 0; i < lookupComponents.length; i++) { + double val; + try { + val = Double.parseDouble( + lookupComponents[i].getValue().toString()); + } catch (NumberFormatException e) { + // Ignore entries in the table that aren't numbers. + continue; + } + + if (val == match_num) { + return valueComponents[i].getPtgVal(); + } else if (sorted && val > match_num) { + if (i == 0) return new PtgErr(PtgErr.ERROR_NA); + else return valueComponents[i - 1].getPtgVal(); + } + } + + if (sorted) return valueComponents[lookupComponents.length - 1].getPtgVal(); + else return new PtgErr(PtgErr.ERROR_NA); + + } else { + //TODO: need to handle as string + } + + return new PtgErr(PtgErr.ERROR_NULL); + } + + /** + * HYPERLINK + * Creates a shortcut or jump that opens a document + * stored on a network server, an intranet, or the Internet + *

                + * Function just returns the "friendly name" of the link, + * Excel doesn't appear to validate the url ... + */ + + protected static Ptg calcHyperlink(Ptg[] operands) { + try { + if (operands.length == 2) + return new PtgStr(operands[1].getValue().toString()); + return new PtgStr(operands[0].getValue().toString()); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + + /** + * INDEX + * Returns a value or the reference to a value from within a table or range. + * There are two forms of the INDEX function: the array form and the reference form. + *

                + * Array Form: + * Returns the value of an element in a table or an array selected by the row and column number indexes. + * Use the array form if the first argument to INDEX is an array constant. + * INDEX(array,row_num,column_num) + * Array is a range of cells or an array constant. + * If array contains only one row or column, + * the corresponding row_num or column_num argument is optional. + * If array has more than one row and more than one column, and only row_num or column_num is used, + * INDEX returns an array of the entire row or column in array. + * Row_num selects the row in array from which to return a value. If row_num is omitted, column_num is required. + * Column_num selects the column in array from which to return a value. If column_num is omitted, row_num is required. + *

                + * Reference Form: + * Returns the reference of the cell at the intersection of a particular row and column. + * If the reference is made up of nonadjacent selections, you can pick the selection to look in. + * INDEX(reference,row_num,column_num,area_num) + * Reference is a reference to one or more cell ranges. + * If you are entering a nonadjacent range for the reference, enclose reference in parentheses. + * If each area in reference contains only one row or column, the row_num or column_num argument, respectively, is optional. For example, for a single row reference, use INDEX(reference,,column_num). + * Row_num is the number of the row in reference from which to return a reference. + * Column_num is the number of the column in reference from which to return a reference. + * Area_num selects a range in reference from which to return the intersection of row_num and column_num. The first area selected or entered is numbered 1, the second is 2, and so on. If area_num is omitted, INDEX uses area 1. + *

                + * Given a BiffRec Range, choose the cell within referenced by the + * row and column operands. + *

                + * example: + * =INDEX(G3:L8,6,4) + * returns the value of the BiffRec at row 6, col 4 in the following table + * which is 0.11 + *

                + * G H I J K L + * 1 + * 2 + * 3 0.007 0.005 0.003 0.002 0.002 0.001 + * 4 0.025 0.017 0.012 0.008 0.006 0.005 + * 5 0.062 0.044 0.032 0.023 0.018 0.015 + * 6 0.116 0.086 0.064 0.049 0.04 0.035 + * 7 0.171 0.13 0.101 0.082 0.07 0.062 + * 8 0.211 0.165 0.132 0.11 0.096 0.088 + **/ + protected static Ptg calcIndex(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + Ptg o = operands[0]; + // only 1st operand is required; changed below (see doc snippet below) + /*If each area in reference contains only one row or column, + * the row_num or column_num argument, respectively, is optional. + * For example, for a single row reference, use INDEX(reference,,column_num). + */ + Object rowref = new Double(1); // defaults (1-based) + Object colref = new Double(1); + double areanum = 1; + int[] retarea = null; + String sht = null; + try { + if (operands.length > 1) { + Ptg rowrefp = operands[1]; + rowref = rowrefp.getValue(); + } + if (operands.length > 2) { + Ptg colrefp = operands[2]; + colref = colrefp.getValue(); + } + if (operands.length > 3) { + areanum = operands[3].getDoubleVal(); + } + if (o instanceof PtgArea) { + retarea = ((PtgArea) o).getIntLocation(); + sht = ((PtgArea) o).getSheetName(); + } else if (o instanceof PtgName) { + //Ptg[] p=((PtgName) o).getName().getComponents(); //CellRangePtgs(); + //o= p[0]; + String r = ((PtgName) o).getName().getLocation(); + retarea = ExcelTools.getRangeRowCol(r); + sht = (r.indexOf("!") == -1) ? null : r.substring(0, r.indexOf("!")); + } else if (o instanceof PtgMemFunc) { + Ptg[] ps = o.getComponents(); + areanum--; + if (areanum >= 0 && areanum < ps.length) + o = ps[(int) areanum]; + else + o = ps[0]; + retarea = ((PtgArea) o).getIntLocation(); + sht = ((PtgArea) o).getSheetName(); + } else if (o instanceof PtgArray) { + Ptg[] ps = o.getComponents(); + areanum--; + if (areanum >= 0 && areanum < ps.length) + o = ps[(int) areanum]; + else + o = ps[0]; + retarea = ((PtgArea) o).getIntLocation(); + // TODO: Sheet! + } + + // now should have the correct area + if (retarea != null) { + // really we can just use the first position to get a ref + // then the second just checks bounds // TODO: check rc sanity here - int rowoff = retarea[0]; - int coloff = retarea[1]; - int rowck = retarea[2]+1; - int colck = retarea[3]+1; - - int[] dims = new int[2]; - try{ - int rr; - if ((rowref instanceof Integer)) { - rr= ((Integer) rowref).intValue(); - } else { - String rw = rowref.toString(); - if(rw.indexOf(".")>-1)rw = rw.substring(0,rw.indexOf(".")); - // string non Integer Chars... - rr = Integer.parseInt(rw); - } - int cr; - if ((colref instanceof Integer)) { - cr= ((Integer) colref).intValue(); - } else { - String cl = colref.toString(); - if(cl.indexOf(".")>-1)cl = cl.substring(0,cl.indexOf(".")); - // string non Integer Chars... - cr = Integer.parseInt(cl); - } - - if((rr>rowck)||(cr>colck)){ + int rowoff = retarea[0]; + int coloff = retarea[1]; + int rowck = retarea[2] + 1; + int colck = retarea[3] + 1; + + int[] dims = new int[2]; + try { + int rr; + if ((rowref instanceof Integer)) { + rr = ((Integer) rowref).intValue(); + } else { + String rw = rowref.toString(); + if (rw.indexOf(".") > -1) rw = rw.substring(0, rw.indexOf(".")); + // string non Integer Chars... + rr = Integer.parseInt(rw); + } + int cr; + if ((colref instanceof Integer)) { + cr = ((Integer) colref).intValue(); + } else { + String cl = colref.toString(); + if (cl.indexOf(".") > -1) cl = cl.substring(0, cl.indexOf(".")); + // string non Integer Chars... + cr = Integer.parseInt(cl); + } + + if ((rr > rowck) || (cr > colck)) { return new PtgErr(PtgErr.ERROR_REF); - } - dims[0] = rr+rowoff-1; - dims[1] = cr+coloff-1; - - // here's a nice new ref... - PtgRef refp = new PtgRef(); - if (o instanceof PtgArea3d)refp = new PtgRef3d(); - refp.setParentRec(o.getParentRec()); - refp.setUseReferenceTracker(false); - if (sht!=null) refp.setSheetName(sht); - refp.setLocation(dims); - if (o instanceof PtgArea3d) { - ((PtgRef3d)refp).setLocation(((PtgArea3d)o).getSheetName() + "!" + ExcelTools.formatLocation(dims)); - }else { - - } - return refp; - - }catch(NumberFormatException e){ - //Logger.logWarn("could not calculate INDEX function: " + o.toString() + ":" + e); - return new PtgErr(PtgErr.ERROR_NULL); // ERR or #VALUE ?? - } - } - }catch(Exception e){ - Logger.logWarn("could not calculate INDEX function: " + o.toString() + ":" + e); - } + } + dims[0] = rr + rowoff - 1; + dims[1] = cr + coloff - 1; + + // here's a nice new ref... + PtgRef refp = new PtgRef(); + if (o instanceof PtgArea3d) refp = new PtgRef3d(); + refp.setParentRec(o.getParentRec()); + refp.setUseReferenceTracker(false); + if (sht != null) refp.setSheetName(sht); + refp.setLocation(dims); + if (o instanceof PtgArea3d) { + refp.setLocation(((PtgArea3d) o).getSheetName() + "!" + ExcelTools.formatLocation(dims)); + } else { + + } + return refp; + + } catch (NumberFormatException e) { + //Logger.logWarn("could not calculate INDEX function: " + o.toString() + ":" + e); + return new PtgErr(PtgErr.ERROR_NULL); // ERR or #VALUE ?? + } + } + } catch (Exception e) { + Logger.logWarn("could not calculate INDEX function: " + o.toString() + ":" + e); + } return new PtgErr(PtgErr.ERROR_NULL); - - } - - - - /** - INDIRECT - Returns a reference indicated by a text value - - INDIRECT(ref_text,a1) - - Ref_text is a reference to a cell that contains an A1-style - reference, an R1C1-style reference, a name defined as a reference, - or a reference to a cell as a text string. If ref_text is not a valid - cell reference, INDIRECT returns the #REF! error value. - - If ref_text refers to another workbook (an external reference), - the other workbook must be open. If the source workbook is not open, - INDIRECT returns the #REF! error value. - - A1 is a logical value that specifies what type of reference is contained in the cell ref_text. - - If a1 is TRUE or omitted, ref_text is interpreted as an A1-style reference. - If a1 is FALSE, ref_text is interpreted as an R1C1-style reference. - - */ - protected static Ptg calcIndirect(Ptg[] operands) - { - try{ - if(operands[0] instanceof PtgStr){ - PtgStr ps= (PtgStr)operands[0]; - - String locx = ps.toString(); - // detect range - if (!(FormulaParser.isRef(locx) || - FormulaParser.isRange(locx))) { // see if it's a named range - Name nmx = ps.getParentRec().getWorkBook().getName(locx); - if(nmx!=null){ // there is a named range - locx = nmx.getLocation(); - } else - return ps; //it's just a value - } - if ("".equals(locx)) - return new PtgInt(0); // that's what Excel does! - PtgArea3d refp = new PtgArea3d(false); - refp.setParentRec(ps.getParentRec()); - refp.setUseReferenceTracker(true); // very important!!! :) - refp.setLocation(locx); - return refp; - - } else if(operands[0] instanceof PtgRef){ + + } + + + /** + * INDIRECT + * Returns a reference indicated by a text value + *

                + * INDIRECT(ref_text,a1) + *

                + * Ref_text is a reference to a cell that contains an A1-style + * reference, an R1C1-style reference, a name defined as a reference, + * or a reference to a cell as a text string. If ref_text is not a valid + * cell reference, INDIRECT returns the #REF! error value. + *

                + * If ref_text refers to another workbook (an external reference), + * the other workbook must be open. If the source workbook is not open, + * INDIRECT returns the #REF! error value. + *

                + * A1 is a logical value that specifies what type of reference is contained in the cell ref_text. + *

                + * If a1 is TRUE or omitted, ref_text is interpreted as an A1-style reference. + * If a1 is FALSE, ref_text is interpreted as an R1C1-style reference. + */ + protected static Ptg calcIndirect(Ptg[] operands) { + try { + if (operands[0] instanceof PtgStr) { + PtgStr ps = (PtgStr) operands[0]; + + String locx = ps.toString(); + // detect range + if (!(FormulaParser.isRef(locx) || + FormulaParser.isRange(locx))) { // see if it's a named range + Name nmx = ps.getParentRec().getWorkBook().getName(locx); + if (nmx != null) { // there is a named range + locx = nmx.getLocation(); + } else + return ps; //it's just a value + } + if ("".equals(locx)) + return new PtgInt(0); // that's what Excel does! + PtgArea3d refp = new PtgArea3d(false); + refp.setParentRec(ps.getParentRec()); + refp.setUseReferenceTracker(true); // very important!!! :) + refp.setLocation(locx); + return refp; + + } else if (operands[0] instanceof PtgRef) { // check if the ptgRef value is a string representing a Named range - Object o = ((PtgRef)(operands[0])).getValue(); + Object o = operands[0].getValue(); PtgStr ps = new PtgStr(o.toString()); ps.setParentRec(operands[0].getParentRec()); operands = new Ptg[1]; operands[0] = ps; return calcIndirect(operands); - } else if(operands[0] instanceof PtgName){ - return calcIndirect(((PtgName)operands[0]).getComponents()); - } - }catch(Exception e){ - //Logger.logErr("INDIRECT: " + e.toString()); - } - return new PtgErr(PtgErr.ERROR_REF); // 's what Excel does ... - } - + } else if (operands[0] instanceof PtgName) { + return calcIndirect(operands[0].getComponents()); + } + } catch (Exception e) { + //Logger.logErr("INDIRECT: " + e.toString()); + } + return new PtgErr(PtgErr.ERROR_REF); // 's what Excel does ... + } + /** - LOOKUP - The LOOKUP function returns a value either from a one-row or one-column range - You can also use the LOOKUP function as an alternative to the IF function for elaborate tests or - tests that exceed the limit for nesting of functions. See the examples in the array form. - For the LOOKUP function to work correctly, the data being looked up must be sorted in - ascending order. If this is not possible, consider using the VLOOKUP, HLOOKUP, or MATCH functions. - - A vector is a range of only one row or one column. - The vector form of LOOKUP looks in a one-row or one-column range (known as a vector) for a value and returns a value from the same position in a second one-row or one-column range. Use this form of the LOOKUP function when you want to specify the range that contains the values that you want to match. The other form of LOOKUP automatically looks in the first column or row. - */ + * LOOKUP + * The LOOKUP function returns a value either from a one-row or one-column range + * You can also use the LOOKUP function as an alternative to the IF function for elaborate tests or + * tests that exceed the limit for nesting of functions. See the examples in the array form. + * For the LOOKUP function to work correctly, the data being looked up must be sorted in + * ascending order. If this is not possible, consider using the VLOOKUP, HLOOKUP, or MATCH functions. + *

                + * A vector is a range of only one row or one column. + * The vector form of LOOKUP looks in a one-row or one-column range (known as a vector) for a value and returns a value from the same position in a second one-row or one-column range. Use this form of the LOOKUP function when you want to specify the range that contains the values that you want to match. The other form of LOOKUP automatically looks in the first column or row. + */ public static Ptg calcLookup(Ptg[] operands) { - String lookup= operands[0].getValue().toString().toUpperCase(); - if (operands.length>2) { //normal version of lookup - Ptg[] vector= operands[1].getComponents(); - Ptg[] returnvector= operands[2].getComponents(); - if (returnvector==null) // happens when operands[2] is a PtgRef - return new PtgNumber(0); // this is what excel does - - //If the LOOKUP function can't find the lookup_value, the function matches the largest value in lookup_vector that is less than or equal to lookup_value. - //If lookup_value is smaller than the smallest value in lookup_vector, LOOKUP returns the #N/A error value - Object retval= null; - for (int i= 0; i < vector.length; i++) { - if (Calculator.compareCellValue(vector[i].getValue(), lookup, ">")) - break; - if (i < returnvector.length) - retval= returnvector[i].getValue(); - } - if (retval instanceof Number) - return new PtgNumber(((Number) retval).doubleValue()); - else if (retval instanceof Boolean) - return new PtgBool(((Boolean)retval).booleanValue()); - else if (retval==null) - return new PtgErr(PtgErr.ERROR_NA); - else // assume string - return new PtgStr(retval.toString()); - } else { //array form of lookup + String lookup = operands[0].getValue().toString().toUpperCase(); + if (operands.length > 2) { //normal version of lookup + Ptg[] vector = operands[1].getComponents(); + Ptg[] returnvector = operands[2].getComponents(); + if (returnvector == null) // happens when operands[2] is a PtgRef + return new PtgNumber(0); // this is what excel does + + //If the LOOKUP function can't find the lookup_value, the function matches the largest value in lookup_vector that is less than or equal to lookup_value. + //If lookup_value is smaller than the smallest value in lookup_vector, LOOKUP returns the #N/A error value + Object retval = null; + for (int i = 0; i < vector.length; i++) { + if (Calculator.compareCellValue(vector[i].getValue(), lookup, ">")) + break; + if (i < returnvector.length) + retval = returnvector[i].getValue(); + } + if (retval instanceof Number) + return new PtgNumber(((Number) retval).doubleValue()); + else if (retval instanceof Boolean) + return new PtgBool(((Boolean) retval).booleanValue()); + else if (retval == null) + return new PtgErr(PtgErr.ERROR_NA); + else // assume string + return new PtgStr(retval.toString()); + } else { //array form of lookup /* * The array form of LOOKUP looks in the first row or column of an array for the specified value * and returns a value from the same position in the last row or column of the array. @@ -640,188 +641,187 @@ else if (retval==null) With the HLOOKUP and VLOOKUP functions, you can index down or across, but LOOKUP always selects the last value in the row or column. */ - try { - Ptg[] array= ((PtgArray) operands[1]).getComponents(); - int nrs= ((PtgArray) operands[1]).getNumberOfRows(); - int ncs= ((PtgArray) operands[1]).getNumberOfColumns(); - //If array covers an area that is wider than it is tall (more columns than rows), LOOKUP searches for the value of lookup_value in the first row. - //If an array is square or is taller than it is wide (more rows than columns), LOOKUP searches in the first column. - Object retval= null; - boolean found= false; - boolean rowbased= (ncs>nrs); - ncs++; // make 1-based - int i= 0; - for (int j= 0; j < nrs && !found; j++) { - int start= i; - for (; i < start+ncs && !found; i++) { - if (Calculator.compareCellValue(array[i].getValue(), lookup, ">")) { - found= true; - break; - } - // returns a value from the same position in the last row or column of the array - if (rowbased){ - retval= array[i+ncs].getValue(); - }else{ - retval= array[i+1].getValue(); - i++; - } - } - } - if (retval instanceof Number) - return new PtgNumber(((Number) retval).doubleValue()); - else if (retval instanceof Boolean) - return new PtgBool(((Boolean)retval).booleanValue()); - else if (retval==null) - return new PtgErr(PtgErr.ERROR_NA); - else // assume string - return new PtgStr(retval.toString()); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NA); - } - } + try { + Ptg[] array = operands[1].getComponents(); + int nrs = ((PtgArray) operands[1]).getNumberOfRows(); + int ncs = ((PtgArray) operands[1]).getNumberOfColumns(); + //If array covers an area that is wider than it is tall (more columns than rows), LOOKUP searches for the value of lookup_value in the first row. + //If an array is square or is taller than it is wide (more rows than columns), LOOKUP searches in the first column. + Object retval = null; + boolean found = false; + boolean rowbased = (ncs > nrs); + ncs++; // make 1-based + int i = 0; + for (int j = 0; j < nrs && !found; j++) { + int start = i; + for (; i < start + ncs && !found; i++) { + if (Calculator.compareCellValue(array[i].getValue(), lookup, ">")) { + found = true; + break; + } + // returns a value from the same position in the last row or column of the array + if (rowbased) { + retval = array[i + ncs].getValue(); + } else { + retval = array[i + 1].getValue(); + i++; + } + } + } + if (retval instanceof Number) + return new PtgNumber(((Number) retval).doubleValue()); + else if (retval instanceof Boolean) + return new PtgBool(((Boolean) retval).booleanValue()); + else if (retval == null) + return new PtgErr(PtgErr.ERROR_NA); + else // assume string + return new PtgStr(retval.toString()); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_NA); + } + } } - - - - /** - MATCH - Looks up values in a reference or array - Returns the relative position of an item in an array that matches a specified value in a - specified order. Use MATCH instead of one of the LOOKUP functions when you need the position - of an item in a range instead of the item itself. - - MATCH(lookup_value,lookup_array,match_type) - - Lookup_value is the value you want to match in lookup_array. For example, when you look up someone's number - in a telephone book, you are using the person's name as the lookup value, but the telephone number is - the value you want. - Lookup_value can be a value (number, text, or logical value) or a cell reference to a number, - text, or logical value. - Lookup_array is a contiguous range of cells containing possible lookup values. Lookup_array must - be an array or an array reference. - Match_type is the number -1, 0, or 1. Match_type specifies how Microsoft Excel matches lookup_value - with values in lookup_array. - - If match_type is 1, MATCH finds the largest value that is less than or equal to lookup_value. - Lookup_array must be placed in ascending order: ...-2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE. - - If match_type is 0, MATCH finds the first value that is exactly equal to lookup_value. - Lookup_array can be in any order. - - If match_type is -1, MATCH finds the smallest value that is greater than or equal to lookup_value. - Lookup_array must be placed in descending order: TRUE, FALSE, Z-A, ...2, 1, 0, -1, -2, ..., and so on. + /** + * MATCH + * Looks up values in a reference or array + * Returns the relative position of an item in an array that matches a specified value in a + * specified order. Use MATCH instead of one of the LOOKUP functions when you need the position + * of an item in a range instead of the item itself. + *

                + * MATCH(lookup_value,lookup_array,match_type) + *

                + * Lookup_value is the value you want to match in lookup_array. For example, when you look up someone's number + * in a telephone book, you are using the person's name as the lookup value, but the telephone number is + * the value you want. + * Lookup_value can be a value (number, text, or logical value) or a cell reference to a number, + * text, or logical value. + *

                + * Lookup_array is a contiguous range of cells containing possible lookup values. Lookup_array must + * be an array or an array reference. + *

                + * Match_type is the number -1, 0, or 1. Match_type specifies how Microsoft Excel matches lookup_value + * with values in lookup_array. + *

                + * If match_type is 1, MATCH finds the largest value that is less than or equal to lookup_value. + * Lookup_array must be placed in ascending order: ...-2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE. + *

                + * If match_type is 0, MATCH finds the first value that is exactly equal to lookup_value. + * Lookup_array can be in any order. + *

                + * If match_type is -1, MATCH finds the smallest value that is greater than or equal to lookup_value. + * Lookup_array must be placed in descending order: TRUE, FALSE, Z-A, ...2, 1, 0, -1, -2, ..., and so on. + *

                + * If match_type is omitted, it is assumed to be 1. + *

                + * MATCH returns the position of the matched value within lookup_array, not the value itself. For example, MATCH("b",{"a","b","c"},0) returns 2, the relative position of "b" within the array {"a","b","c"}. + * MATCH does not distinguish between uppercase and lowercase letters when matching text values. + * If MATCH is unsuccessful in finding a match, it returns the #N/A error value. + * If match_type is 0 and lookup_value is text, lookup_value can contain the wildcard characters asterisk (*) and question mark (?). An asterisk matches any sequence of characters; a question mark matches any single character. + */ + public static Ptg calcMatch(Ptg[] operands) { + try { + Object lookupValue = operands[0].getValue(); // should be one value or a reference + Ptg lookupArray = operands[1]; // array or array reference (PtgArea) + Ptg[] values = null; + int matchType = 1; + if (operands.length > 2) { + Object o = operands[2].getValue(); + if (o instanceof Integer) + matchType = ((Integer) o).intValue(); + else + matchType = ((Double) o).intValue(); - If match_type is omitted, it is assumed to be 1. + } + // Step 1- get all the components of the lookupArray (Array or Array Reference + if (lookupArray instanceof PtgName) { + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(lookupArray.getParentRec()); + pa.setLocation(((PtgName) lookupArray).getName().getLocation()); + lookupArray = pa; + } + if (lookupArray instanceof PtgArea) { + PtgArea pa = (PtgArea) lookupArray; + values = pa.getComponents(); + } else if (lookupArray instanceof PtgMemFunc) { + PtgMemFunc pa = (PtgMemFunc) lookupArray; + values = pa.getComponents(); + } else if (lookupArray instanceof PtgArray) { + PtgArray pa = (PtgArray) lookupArray; + values = pa.getComponents(); + } else if (lookupArray instanceof PtgMystery) { + // PtgMystery is return from PtgMemFunc/MemArrays + ArrayList ptgs = new ArrayList(); + Ptg[] p = ((PtgMystery) lookupArray).vars; + for (int j = 0; j < p.length; j++) { + if (p[j] instanceof PtgArea) { + Ptg[] pa = p[j].getComponents(); + for (int k = 0; k < pa.length; k++) + ptgs.add(pa[k]); + } else + ptgs.add(p[j]); + } + values = new Ptg[ptgs.size()]; + ptgs.toArray(values); + } else if (lookupArray instanceof PtgStr) { + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(lookupArray.getParentRec()); + pa.setLocation(lookupArray.toString()); + values = pa.getComponents(); + } else { // testing! + Logger.logErr("match: unknown type of lookup array"); + } - MATCH returns the position of the matched value within lookup_array, not the value itself. For example, MATCH("b",{"a","b","c"},0) returns 2, the relative position of "b" within the array {"a","b","c"}. - MATCH does not distinguish between uppercase and lowercase letters when matching text values. - If MATCH is unsuccessful in finding a match, it returns the #N/A error value. - If match_type is 0 and lookup_value is text, lookup_value can contain the wildcard characters asterisk (*) and question mark (?). An asterisk matches any sequence of characters; a question mark matches any single character. - */ - public static Ptg calcMatch(Ptg[] operands) { - try { - Object lookupValue= operands[0].getValue(); // should be one value or a reference - Ptg lookupArray= operands[1]; // array or array reference (PtgArea) - Ptg[] values= null; - int matchType= 1; - if (operands.length>2) { - Object o= operands[2].getValue(); - if (o instanceof Integer) - matchType= ((Integer) o).intValue(); - else - matchType= ((Double) o).intValue(); - - } - // Step 1- get all the components of the lookupArray (Array or Array Reference - if(lookupArray instanceof PtgName){ - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(lookupArray.getParentRec()); - pa.setLocation( ((PtgName) lookupArray).getName().getLocation()); - lookupArray = pa; - } - if (lookupArray instanceof PtgArea){ - PtgArea pa = (PtgArea)lookupArray; - values = pa.getComponents(); - }else if (lookupArray instanceof PtgMemFunc) { - PtgMemFunc pa = (PtgMemFunc)lookupArray; - values= pa.getComponents(); - }else if (lookupArray instanceof PtgArray) { - PtgArray pa= (PtgArray) lookupArray; - values= pa.getComponents(); - } else if (lookupArray instanceof PtgMystery) { - // PtgMystery is return from PtgMemFunc/MemArrays - ArrayList ptgs= new ArrayList(); - Ptg[] p= ((PtgMystery) lookupArray).vars; - for (int j= 0; j < p.length; j++) { - if (p[j] instanceof PtgArea) { - Ptg[] pa= p[j].getComponents(); - for (int k= 0; k < pa.length; k++) - ptgs.add(pa[k]); - } else - ptgs.add(p[j]); - } - values= new Ptg[ptgs.size()]; - ptgs.toArray(values); - } else if (lookupArray instanceof PtgStr) { - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(lookupArray.getParentRec()); - pa.setLocation(lookupArray.toString()); - values= pa.getComponents(); - } else { // testing! - Logger.logErr("match: unknown type of lookup array"); - } - - // Step # 2- traverse thru value array to find lookupValue using matchType rules - // ALSO must ensure for matchType!=0 that array is in ascending or descending order - int retIndex= -1; - // TODO: matchType==0 Strings can match wildcards ... - for (int i= 1; i <= values.length; i++) { - Object v0= values[i-1].getValue(); - Object v1= null; - if (i < values.length) - v1= values[i].getValue(); - int mType= -2; // -1 means v0v1 - int match= -2; // test lookupValue against v0 (i-1) - if (v0 instanceof Integer) { - if (v1!=null) mType= (((Integer)v0).compareTo((Integer)v1)); - match= (((Integer)v0).compareTo((Integer)lookupValue)); - } else if (v0 instanceof Double) { - if (v1!=null) mType= (((Double)v0).compareTo((Double)v1)); - match= (((Double)v0).compareTo((Double)lookupValue)); - } else if (v0 instanceof Boolean) { - boolean bv0= ((Boolean) v0).booleanValue(); - // 1.6 only if (v1!=null) mType= (((Boolean) v0).compareTo((Boolean)v1)); - if (v1!=null) { - boolean bv1= ((Boolean) v1).booleanValue(); - mType= ((bv0==bv1)?0:((!bv0 && bv1)?-1:+1)); - } - // 1.6 only match= (((Boolean) v0).compareTo((Boolean)lookupValue)); - boolean bv1= ((Boolean)lookupValue).booleanValue(); - match= ((bv0==bv1)?0:((!bv0 && bv1)?-1:+1)); - }else if (v0 instanceof String) { - if (v1!=null) mType= (((String)v0).compareTo((String)v1)); - match= (((String)v0).compareTo((String)lookupValue)); - } - if (i < values.length) { // only check order - if ((matchType==1 && mType > 0)// not in ascending order - || - (matchType==-1 && mType < 0)) // not in descending order + // Step # 2- traverse thru value array to find lookupValue using matchType rules + // ALSO must ensure for matchType!=0 that array is in ascending or descending order + int retIndex = -1; + // TODO: matchType==0 Strings can match wildcards ... + for (int i = 1; i <= values.length; i++) { + Object v0 = values[i - 1].getValue(); + Object v1 = null; + if (i < values.length) + v1 = values[i].getValue(); + int mType = -2; // -1 means v0v1 + int match = -2; // test lookupValue against v0 (i-1) + if (v0 instanceof Integer) { + if (v1 != null) mType = (((Integer) v0).compareTo((Integer) v1)); + match = (((Integer) v0).compareTo((Integer) lookupValue)); + } else if (v0 instanceof Double) { + if (v1 != null) mType = (((Double) v0).compareTo((Double) v1)); + match = (((Double) v0).compareTo((Double) lookupValue)); + } else if (v0 instanceof Boolean) { + boolean bv0 = ((Boolean) v0).booleanValue(); + // 1.6 only if (v1!=null) mType= (((Boolean) v0).compareTo((Boolean)v1)); + if (v1 != null) { + boolean bv1 = ((Boolean) v1).booleanValue(); + mType = ((bv0 == bv1) ? 0 : ((!bv0 && bv1) ? -1 : +1)); + } + // 1.6 only match= (((Boolean) v0).compareTo((Boolean)lookupValue)); + boolean bv1 = ((Boolean) lookupValue).booleanValue(); + match = ((bv0 == bv1) ? 0 : ((!bv0 && bv1) ? -1 : +1)); + } else if (v0 instanceof String) { + if (v1 != null) mType = (((String) v0).compareTo((String) v1)); + match = (((String) v0).compareTo((String) lookupValue)); + } + if (i < values.length) { // only check order + if ((matchType == 1 && mType > 0)// not in ascending order + || + (matchType == -1 && mType < 0)) // not in descending order // DOCUMENTATION SEZ MUST BE IN DESCENDING ORDER FOR -1 BUT EXCEL ALLOWS IT IN CERTAIN CIRCUMSTANCES // ) return new PtgErr(PtgErr.ERROR_NA); - } - if (matchType==0 && match==0) { - retIndex= i; // 1-based - break; - } - if (matchType==1 && match <= 0) { - retIndex= i; // 1-based - } else if (matchType==-1 && match >= 0) { - retIndex= i; // 1-based - } - } + } + if (matchType == 0 && match == 0) { + retIndex = i; // 1-based + break; + } + if (matchType == 1 && match <= 0) { + retIndex = i; // 1-based + } else if (matchType == -1 && match >= 0) { + retIndex = i; // 1-based + } + } /* for (int i= 0; i < values.length; i++) { Object val=values[i].getValue(); @@ -843,485 +843,491 @@ else if (val instanceof String) } else { // default is 1 } } -*/ - if (retIndex>-1) - return new PtgInt(retIndex); - } catch (Exception e) {} +*/ + if (retIndex > -1) + return new PtgInt(retIndex); + } catch (Exception e) { + } return new PtgErr(PtgErr.ERROR_NA); - } - + } + /** * calcOffset - Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells. - The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and the number of columns to be returned. - - OFFSET(reference,rows,cols,height,width) - - Reference is the reference from which you want to base the offset. Reference must refer to a cell or range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value. - Rows is the number of rows, up or down, that you want the upper-left cell to refer to. Using 5 as the rows argument specifies that the upper-left cell in the reference is five rows below reference. Rows can be positive (which means below the starting reference) or negative (which means above the starting reference). - Cols is the number of columns, to the left or right, that you want the upper-left cell of the result to refer to. Using 5 as the cols argument specifies that the upper-left cell in the reference is five columns to the right of reference. Cols can be positive (which means to the right of the starting reference) or negative (which means to the left of the starting reference). - Height is the height, in number of rows, that you want the returned reference to be. Height must be a positive number. - Width is the width, in number of columns, that you want the returned reference to be. Width must be a positive number. + * Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells. + * The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and the number of columns to be returned. + *

                + * OFFSET(reference,rows,cols,height,width) + *

                + * Reference is the reference from which you want to base the offset. Reference must refer to a cell or range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value. + * Rows is the number of rows, up or down, that you want the upper-left cell to refer to. Using 5 as the rows argument specifies that the upper-left cell in the reference is five rows below reference. Rows can be positive (which means below the starting reference) or negative (which means above the starting reference). + * Cols is the number of columns, to the left or right, that you want the upper-left cell of the result to refer to. Using 5 as the cols argument specifies that the upper-left cell in the reference is five columns to the right of reference. Cols can be positive (which means to the right of the starting reference) or negative (which means to the left of the starting reference). + * Height is the height, in number of rows, that you want the returned reference to be. Height must be a positive number. + * Width is the width, in number of columns, that you want the returned reference to be. Width must be a positive number. */ protected static Ptg calcOffset(Ptg[] operands) { - Ptg ref= operands[0]; // Reference must refer to a cell or range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value - if (!(ref instanceof PtgRef)) - return new PtgErr(PtgErr.ERROR_VALUE); - int nrows= operands[1].getIntVal(); - int ncols= operands[2].getIntVal(); - int height= -1; // Height if present, how many rows to return - must be positive - if (operands.length>3) { - height= operands[3].getIntVal(); - if (height < 0) - return new PtgErr(PtgErr.ERROR_VALUE); - } - int width= -1; // Width if present, how many columns to return - must be positive - if (operands.length>4) { - width= operands[4].getIntVal(); - if (width < 0) - return new PtgErr(PtgErr.ERROR_VALUE); - } - int[] rc= ((PtgRef) ref).getIntLocation(); - rc[0]+=nrows; - rc[1]+=ncols; - if (rc.length > 3) { // it's an area - rc[2]+=nrows; - rc[3]+=ncols; - } - // A height/width of 1, 1= a single reference - // When we increase either the row height or column width in the offset function "=OFFSET(A1,2,0,1,1)" - // to more than 1, the reference is converted to a range. - // OK, this may be the wrong interpretation of the height and width, but, from research, this is what I've come up with: - // height and width are only really applicable for an initial single reference - // in Excel, a height and width value of more than 1 returns #VALUE! unless wrapped in SUM - // ???? - if (height==1 && width==1) { // it's a single reference - if (rc.length>3) { // truncate - int[] temp= new int[2]; - System.arraycopy(rc, 0, temp, 0, 2); - rc= temp; - } - } else if (!(height==-1 && width==-1)) { - if (rc.length<3) { // make a range - int[] temp= new int[4]; - System.arraycopy(rc, 0, temp, 0, 2); - rc= temp; - } - if (height > 0) { - rc[2]= rc[0] + height-1; // is this correct???? - } - if (width > 0) { - rc[3]= rc[1] + width-1; // " " - } - } - if (rc.length>3) { - // If rows and cols offset reference over the edge of the worksheet, OFFSET returns the #REF! error value. - if (rc[0] < 0 || rc[1] < 0 || rc[2] < 0 || rc[3] < 0) - return new PtgErr(PtgErr.ERROR_REF); - PtgArea pa= new PtgArea(false); - pa.setParentRec(ref.getParentRec()); - try { - String sh= ref.getLocation(); - int z= sh.indexOf('!'); - if (z>0) { - pa.setSheetName(sh.substring(0, z)); - } - } catch (Exception e) { ;} - pa.setLocation(rc); - return pa; - } else { // it's a single reference - // If rows and cols offset reference over the edge of the worksheet, OFFSET returns the #REF! error value. - if (rc[0] < 0 || rc[1] < 0) - return new PtgErr(PtgErr.ERROR_REF); - PtgRef pr= new PtgRef(); - pr.setParentRec(ref.getParentRec()); - pr.setLocation(rc); - return pr; - } + Ptg ref = operands[0]; // Reference must refer to a cell or range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value + if (!(ref instanceof PtgRef)) + return new PtgErr(PtgErr.ERROR_VALUE); + int nrows = operands[1].getIntVal(); + int ncols = operands[2].getIntVal(); + int height = -1; // Height if present, how many rows to return - must be positive + if (operands.length > 3) { + height = operands[3].getIntVal(); + if (height < 0) + return new PtgErr(PtgErr.ERROR_VALUE); + } + int width = -1; // Width if present, how many columns to return - must be positive + if (operands.length > 4) { + width = operands[4].getIntVal(); + if (width < 0) + return new PtgErr(PtgErr.ERROR_VALUE); + } + int[] rc = ((PtgRef) ref).getIntLocation(); + rc[0] += nrows; + rc[1] += ncols; + if (rc.length > 3) { // it's an area + rc[2] += nrows; + rc[3] += ncols; + } + // A height/width of 1, 1= a single reference + // When we increase either the row height or column width in the offset function "=OFFSET(A1,2,0,1,1)" + // to more than 1, the reference is converted to a range. + // OK, this may be the wrong interpretation of the height and width, but, from research, this is what I've come up with: + // height and width are only really applicable for an initial single reference + // in Excel, a height and width value of more than 1 returns #VALUE! unless wrapped in SUM + // ???? + if (height == 1 && width == 1) { // it's a single reference + if (rc.length > 3) { // truncate + int[] temp = new int[2]; + System.arraycopy(rc, 0, temp, 0, 2); + rc = temp; + } + } else if (!(height == -1 && width == -1)) { + if (rc.length < 3) { // make a range + int[] temp = new int[4]; + System.arraycopy(rc, 0, temp, 0, 2); + rc = temp; + } + if (height > 0) { + rc[2] = rc[0] + height - 1; // is this correct???? + } + if (width > 0) { + rc[3] = rc[1] + width - 1; // " " + } + } + if (rc.length > 3) { + // If rows and cols offset reference over the edge of the worksheet, OFFSET returns the #REF! error value. + if (rc[0] < 0 || rc[1] < 0 || rc[2] < 0 || rc[3] < 0) + return new PtgErr(PtgErr.ERROR_REF); + PtgArea pa = new PtgArea(false); + pa.setParentRec(ref.getParentRec()); + try { + String sh = ref.getLocation(); + int z = sh.indexOf('!'); + if (z > 0) { + pa.setSheetName(sh.substring(0, z)); + } + } catch (Exception e) { + } + pa.setLocation(rc); + return pa; + } else { // it's a single reference + // If rows and cols offset reference over the edge of the worksheet, OFFSET returns the #REF! error value. + if (rc[0] < 0 || rc[1] < 0) + return new PtgErr(PtgErr.ERROR_REF); + PtgRef pr = new PtgRef(); + pr.setParentRec(ref.getParentRec()); + pr.setLocation(rc); + return pr; + } } - - /** TRANSPOSE - Returns the transpose of an array - The TRANSPOSE function returns a vertical range of cells as a horizontal range, or vice versa. - The TRANSPOSE function must be entered as an array formula has columns and rows. - Use TRANSPOSE to shift the vertical and horizontal orientation of an array or range on a - worksheet. - - array Required. An array or range of cells on a worksheet that you want to transpose. The transpose of an array is created by using the first row of the array as the first column of the new array, the second row of the array as the second column of the new array, and so on. - - */ + + /** + * TRANSPOSE + * Returns the transpose of an array + * The TRANSPOSE function returns a vertical range of cells as a horizontal range, or vice versa. + * The TRANSPOSE function must be entered as an array formula has columns and rows. + * Use TRANSPOSE to shift the vertical and horizontal orientation of an array or range on a + * worksheet. + *

                + * array Required. An array or range of cells on a worksheet that you want to transpose. The transpose of an array is created by using the first row of the array as the first column of the new array, the second row of the array as the second column of the new array, and so on. + */ protected static Ptg calcTranspose(Ptg[] operands) { - String retArray= ""; - PtgArray ret= new PtgArray(); - if (!(operands[0] instanceof PtgArray)) { - Ptg[] arr= operands[0].getComponents(); - //it's a list of values, convert to row-based - for (int i= 0; i < arr.length; i++) { - retArray= retArray + arr[i].getValue().toString() + ";"; - } - retArray= "{" + retArray.substring(0, retArray.length()-1) + "}"; - ret.setVal(retArray); - } else { // transpose row/cols of an existing array - PtgArray pa= (PtgArray) operands[0]; - Ptg[] arr= pa.getComponents(); - int nc= pa.getNumberOfColumns()+1; - int nr= pa.getNumberOfRows()+1; - for (int i= 0; i < nc; i++) { - for (int j= 0; j < nc*nr; j+=nc) { - retArray= retArray+ arr[i+j].getValue().toString() + ","; - } - retArray= retArray.substring(0, retArray.length()-1) + ";"; - } - retArray= "{" + retArray.substring(0, retArray.length()-1) + "}"; - ret.setVal(retArray); - } - return ret; + String retArray = ""; + PtgArray ret = new PtgArray(); + if (!(operands[0] instanceof PtgArray)) { + Ptg[] arr = operands[0].getComponents(); + //it's a list of values, convert to row-based + for (int i = 0; i < arr.length; i++) { + retArray = retArray + arr[i].getValue().toString() + ";"; + } + retArray = "{" + retArray.substring(0, retArray.length() - 1) + "}"; + ret.setVal(retArray); + } else { // transpose row/cols of an existing array + PtgArray pa = (PtgArray) operands[0]; + Ptg[] arr = pa.getComponents(); + int nc = pa.getNumberOfColumns() + 1; + int nr = pa.getNumberOfRows() + 1; + for (int i = 0; i < nc; i++) { + for (int j = 0; j < nc * nr; j += nc) { + retArray = retArray + arr[i + j].getValue().toString() + ","; + } + retArray = retArray.substring(0, retArray.length() - 1) + ";"; + } + retArray = "{" + retArray.substring(0, retArray.length() - 1) + "}"; + ret.setVal(retArray); + } + return ret; } - + /** - * ROW - Returns the row number of a reference - - Note this is 1 based, ie Row1 = 1. - - */ - protected static Ptg calcRow(Ptg[] operands) - throws FunctionNotSupportedException{ - if (operands==null || operands.length!=1) return new PtgErr(PtgErr.ERROR_VALUE); - if (operands[0] instanceof PtgFuncVar){ + * ROW + * Returns the row number of a reference + *

                + * Note this is 1 based, ie Row1 = 1. + */ + protected static Ptg calcRow(Ptg[] operands) + throws FunctionNotSupportedException { + if (operands == null || operands.length != 1) return new PtgErr(PtgErr.ERROR_VALUE); + if (operands[0] instanceof PtgFuncVar) { // we need to return the col where the formula is. - PtgFuncVar pfunk = (PtgFuncVar)operands[0]; - try{ - int loc = pfunk.getParentRec().getRowNumber() +1; - return new PtgInt(loc); - }catch(Exception e){ + PtgFuncVar pfunk = (PtgFuncVar) operands[0]; + try { + int loc = pfunk.getParentRec().getRowNumber() + 1; + return new PtgInt(loc); + } catch (Exception e) { Logger.logErr("Error running calcRow " + e); - }; + } + } + try { // process as an array formula ... + boolean isArray = (operands[0].getParentRec() instanceof Array); + if (!isArray) { + if (operands[0] instanceof PtgRef) { + return new PtgInt((((PtgRef) operands[0]).getRowCol()[0]) + 1); + } else if (operands[0] instanceof PtgName) { // table??? + String range = ((PtgName) operands[0]).getName().getLocation(); + return new PtgInt(ExcelTools.getRowColFromString(range)[0] + 1); + } + return new PtgInt((operands[0].getIntLocation()[0]) + 1); + } else { + String retArry = ""; + Ptg[] comps = null; + if (operands[0] instanceof PtgRef) { + comps = operands[0].getComponents(); + } else if (operands[0] instanceof PtgName) { // table??? + comps = operands[0].getComponents(); + } + if (comps == null) { + return new PtgInt((((PtgRef) operands[0]).getRowCol()[0]) + 1); + } + for (int i = 0; i < comps.length; i++) { + try { + retArry = retArry + (((PtgRef) comps[i]).getIntLocation()[0] + 1) + ","; + } catch (Exception e) { + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } + } catch (Exception ex) { + return new PtgRefErr(); + } + } + + + /** + * ROWS + * Returns the number of rows in a reference + *

                + * ROWS(array) + *

                + * Array is an array, an array formula, or a reference to a range of cells for which you want the number of rows. + *

                + * =ROWS(C1:E4) Number of rows in the reference (4) + * =ROWS({1,2,3;4,5,6}) Number of rows in the array constant (2) + */ + protected static Ptg calcRows(Ptg[] operands) + throws FunctionNotSupportedException { + try { + int rsz = 0; + if (operands[0] instanceof PtgStr) { + String rangestr = operands[0].getValue().toString(); + String startx = rangestr.substring(0, rangestr.indexOf(":")); + String endx = rangestr.substring(rangestr.indexOf(":") + 1); + + int[] startints = ExcelTools.getRowColFromString(startx); + int[] endints = ExcelTools.getRowColFromString(endx); + rsz = endints[0] - startints[0]; + rsz++; // inclusive + } else if (operands[0] instanceof PtgName) { + int[] rc = ExcelTools.getRangeCoords(operands[0].getLocation()); + rsz = rc[2] - rc[0]; + rsz++; // inclusive + } else if (operands[0] instanceof PtgRef) { + int[] rc = ExcelTools.getRangeCoords(((PtgRef) operands[0]).getLocation()); + rsz = rc[2] - rc[0]; + rsz++; // inclusive + } else if (operands[0] instanceof PtgMemFunc) { + Ptg[] p = operands[0].getComponents(); + if (p != null && p.length > 0) { + int[] rc0 = p[0].getIntLocation(); + int[] rc1 = null; + if (p.length > 1) + rc1 = p[p.length - 1].getIntLocation(); + if (rc1 == null) + rsz = 0; + else + rsz = rc1[0] - rc0[0]; + rsz++; + } else + return new PtgErr(PtgErr.ERROR_VALUE); + } else + return new PtgErr(PtgErr.ERROR_VALUE); + return new PtgInt(rsz); + } catch (Exception e) { } - try{ // process as an array formula ... - boolean isArray= (operands[0].getParentRec() instanceof Array); - if (!isArray) { - if(operands[0] instanceof PtgRef){ - return new PtgInt((((PtgRef)operands[0]).getRowCol()[0])+1); - } else if (operands[0] instanceof PtgName) { // table??? - String range= ((PtgName) operands[0]).getName().getLocation(); - return new PtgInt(ExcelTools.getRowColFromString(range)[0]+1); - } - return new PtgInt((operands[0].getIntLocation()[0])+1); - } else { - String retArry= ""; - Ptg[] comps= null; - if(operands[0] instanceof PtgRef){ - comps= operands[0].getComponents(); - } else if (operands[0] instanceof PtgName) { // table??? - comps= ((PtgName) operands[0]).getComponents(); - } - if (comps==null) { - return new PtgInt((((PtgRef)operands[0]).getRowCol()[0])+1); - } - for (int i= 0; i < comps.length; i++) { - try { - retArry= retArry + (((PtgRef)comps[i]).getIntLocation()[0]+1) + ","; - } catch (Exception e) { ; } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } - }catch(Exception ex){ - return new PtgRefErr(); - } - } - - -/** - * ROWS - Returns the number of rows in a reference - - ROWS(array) - - Array is an array, an array formula, or a reference to a range of cells for which you want the number of rows. - - =ROWS(C1:E4) Number of rows in the reference (4) - =ROWS({1,2,3;4,5,6}) Number of rows in the array constant (2) - - */ - protected static Ptg calcRows(Ptg[] operands) - throws FunctionNotSupportedException{ - try { - int rsz= 0; - if(operands[0] instanceof PtgStr){ - String rangestr = operands[0].getValue().toString(); - String startx = rangestr.substring(0,rangestr.indexOf(":")); - String endx = rangestr.substring(rangestr.indexOf(":")+1); - - int[] startints = ExcelTools.getRowColFromString(startx); - int[] endints = ExcelTools.getRowColFromString(endx); - rsz = endints[0] - startints[0]; - rsz ++; // inclusive - }else if(operands[0] instanceof PtgName){ - int[] rc= ExcelTools.getRangeCoords(((PtgName)operands[0]).getLocation()); - rsz = rc[2] - rc[0]; - rsz ++; // inclusive - } else if (operands[0] instanceof PtgRef) { - int[] rc= ExcelTools.getRangeCoords(((PtgRef)operands[0]).getLocation()); - rsz = rc[2] - rc[0]; - rsz ++; // inclusive - } else if (operands[0] instanceof PtgMemFunc) { - Ptg[] p= ((PtgMemFunc) operands[0]).getComponents(); - if (p!=null && p.length>0) { - int[] rc0= p[0].getIntLocation(); - int[] rc1= null; - if (p.length>1) - rc1= p[p.length-1].getIntLocation(); - if (rc1==null) - rsz= 0; - else - rsz= rc1[0]-rc0[0]; - rsz++; - } else - return new PtgErr(PtgErr.ERROR_VALUE); - } else - return new PtgErr(PtgErr.ERROR_VALUE); - return new PtgInt(rsz); - }catch(Exception e){ } return new PtgErr(PtgErr.ERROR_VALUE); - } - - - /** VLOOKUP - Looks in the first column of an array and moves across the row to return the value of a cell - Searches for a value in the leftmost column of a table, and then returns a value in the same row - from a column you specify in the table. Use VLOOKUP instead of HLOOKUP when your comparison - values are located in a column to the left of the data you want to find. - - Syntax - - VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) - - Lookup_value is the value to be found in the first column of the array. Lookup_value can be a value, - a reference, or a text string. - - Table_array is the table of information in which data is looked up. Use a reference to a range or - a range name, such as Database or List. - - If range_lookup is TRUE, the values in the first column of table_array must be placed in ascending order: - ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE; otherwise VLOOKUP may not give the correct value. - If range_lookup is FALSE, table_array does not need to be sorted. - You can put the values in ascending order by choosing the Sort command from the Data menu and selecting Ascending. - The values in the first column of table_array can be text, numbers, or logical values. - Uppercase and lowercase text are equivalent. - Col_index_num is the column number in table_array from which the matching value must be returned. - A col_index_num of 1 returns the value in the first column in table_array; a col_index_num of 2 returns the value - in the second column in table_array, and so on. If col_index_num is less than 1, - VLOOKUP returns the #VALUE! error value; if col_index_num is greater than the number of columns in table_array, - VLOOKUP returns the #REF! error value. - - Range_lookup is a logical value that specifies whether you want VLOOKUP to find an exact match or an - approximate match. If TRUE or omitted, an approximate match is returned. In other words, - if an exact match is not found, the next largest value that is less than lookup_value is returned. - If FALSE, VLOOKUP will find an exact match. If one is not found, the error value #N/A is returned. - - Remarks - - If VLOOKUP can't find lookup_value, and range_lookup is TRUE, it uses the largest value that is less than or - equal to lookup_value. - If lookup_value is smaller than the smallest value in the first column of table_array, VLOOKUP returns the - #N/A error value. - If VLOOKUP can't find lookup_value, and range_lookup is FALSE, VLOOKUP returns the #N/A value. - - VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) - - On the preceding worksheet, where the range A4:C12 is named Range: - - VLOOKUP(1,Range,1,TRUE) equals 0.946 - - VLOOKUP(1,Range,2) equals 2.17 - - VLOOKUP(1,Range,3,TRUE) equals 100 - - VLOOKUP(.746,Range,3,FALSE) equals 200 - - VLOOKUP(0.1,Range,2,TRUE) equals #N/A, because 0.1 is less than the smallest value in column A - - VLOOKUP(2,Range,2,TRUE) equals 1.71 - - */ - protected static Ptg calcVlookup(Ptg[] operands) - throws FunctionNotSupportedException{ - - boolean rangeLookup = true; // truth of "approximate match"; must be sorted - boolean isNumber = true; - try { - Ptg lookup_value = operands[0]; - Ptg table_array = operands[1]; - - // can't assume that it's a PtgInt - //PtgInt col_index_num = (PtgInt)operands[2].getValue(); - //int colNum = col_index_num.getVal() -1;// reduce 1 for ordinal base off firstcol - Object o= operands[2].getValue(); - int colNum= 0; - if (o instanceof Double) - colNum= ((Double)o).intValue()-1; // reduce 1 for ordinal base off firstcol - else // assume int? - colNum= ((Integer)o).intValue()-1; // reduce 1 for ordinal base off firstcol - if (operands.length > 3){ - Object vx = operands[3].getValue(); - if (vx!= null){ + } + + + /** + * VLOOKUP + * Looks in the first column of an array and moves across the row to return the value of a cell + * Searches for a value in the leftmost column of a table, and then returns a value in the same row + * from a column you specify in the table. Use VLOOKUP instead of HLOOKUP when your comparison + * values are located in a column to the left of the data you want to find. + *

                + * Syntax + *

                + * VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) + *

                + * Lookup_value is the value to be found in the first column of the array. Lookup_value can be a value, + * a reference, or a text string. + *

                + * Table_array is the table of information in which data is looked up. Use a reference to a range or + * a range name, such as Database or List. + *

                + * If range_lookup is TRUE, the values in the first column of table_array must be placed in ascending order: + * ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE; otherwise VLOOKUP may not give the correct value. + * If range_lookup is FALSE, table_array does not need to be sorted. + * You can put the values in ascending order by choosing the Sort command from the Data menu and selecting Ascending. + * The values in the first column of table_array can be text, numbers, or logical values. + * Uppercase and lowercase text are equivalent. + * Col_index_num is the column number in table_array from which the matching value must be returned. + * A col_index_num of 1 returns the value in the first column in table_array; a col_index_num of 2 returns the value + * in the second column in table_array, and so on. If col_index_num is less than 1, + * VLOOKUP returns the #VALUE! error value; if col_index_num is greater than the number of columns in table_array, + * VLOOKUP returns the #REF! error value. + *

                + * Range_lookup is a logical value that specifies whether you want VLOOKUP to find an exact match or an + * approximate match. If TRUE or omitted, an approximate match is returned. In other words, + * if an exact match is not found, the next largest value that is less than lookup_value is returned. + * If FALSE, VLOOKUP will find an exact match. If one is not found, the error value #N/A is returned. + *

                + * Remarks + *

                + * If VLOOKUP can't find lookup_value, and range_lookup is TRUE, it uses the largest value that is less than or + * equal to lookup_value. + * If lookup_value is smaller than the smallest value in the first column of table_array, VLOOKUP returns the + * #N/A error value. + * If VLOOKUP can't find lookup_value, and range_lookup is FALSE, VLOOKUP returns the #N/A value. + *

                + * VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) + *

                + * On the preceding worksheet, where the range A4:C12 is named Range: + *

                + * VLOOKUP(1,Range,1,TRUE) equals 0.946 + *

                + * VLOOKUP(1,Range,2) equals 2.17 + *

                + * VLOOKUP(1,Range,3,TRUE) equals 100 + *

                + * VLOOKUP(.746,Range,3,FALSE) equals 200 + *

                + * VLOOKUP(0.1,Range,2,TRUE) equals #N/A, because 0.1 is less than the smallest value in column A + *

                + * VLOOKUP(2,Range,2,TRUE) equals 1.71 + */ + protected static Ptg calcVlookup(Ptg[] operands) + throws FunctionNotSupportedException { + + boolean rangeLookup = true; // truth of "approximate match"; must be sorted + boolean isNumber = true; + try { + Ptg lookup_value = operands[0]; + Ptg table_array = operands[1]; + + // can't assume that it's a PtgInt + //PtgInt col_index_num = (PtgInt)operands[2].getValue(); + //int colNum = col_index_num.getVal() -1;// reduce 1 for ordinal base off firstcol + Object o = operands[2].getValue(); + int colNum = 0; + if (o instanceof Double) + colNum = ((Double) o).intValue() - 1; // reduce 1 for ordinal base off firstcol + else // assume int? + colNum = ((Integer) o).intValue() - 1; // reduce 1 for ordinal base off firstcol + if (operands.length > 3) { + Object vx = operands[3].getValue(); + if (vx != null) { try { - Boolean sort = (Boolean)vx; - rangeLookup = sort.booleanValue(); - }catch(ClassCastException e){ - Integer bool = (Integer)vx; - if (bool.intValue()==0)rangeLookup = false; + Boolean sort = (Boolean) vx; + rangeLookup = sort.booleanValue(); + } catch (ClassCastException e) { + Integer bool = (Integer) vx; + if (bool.intValue() == 0) rangeLookup = false; } - } - } - - PtgRef[] lookupComponents = null; - PtgRef[] valueComponents = null; - // first, get the lookup Column Vals - if(table_array instanceof PtgName){ // 20090211 KSC: - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(table_array.getParentRec()); - - pa.setLocation(((PtgName) table_array).getName().getLocation()); - table_array= pa; - if (((PtgArea3d)table_array).isExternalRef()) { + } + } + + PtgRef[] lookupComponents = null; + PtgRef[] valueComponents = null; + // first, get the lookup Column Vals + if (table_array instanceof PtgName) { // 20090211 KSC: + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(table_array.getParentRec()); + + pa.setLocation(((PtgName) table_array).getName().getLocation()); + table_array = pa; + if (((PtgArea3d) table_array).isExternalRef()) { Logger.logWarn("LookupReferenceCalculator.calcVlookup External References are disallowed"); return new PtgErr(PtgErr.ERROR_REF); - } - } - if (table_array instanceof PtgArea){ - try{ - PtgArea pa = (PtgArea)table_array; - int[] range = table_array.getIntLocation(); - // TODO: check rc sanity here - int firstcol = range[1] ; - lookupComponents = (PtgRef[])pa.getColComponents(firstcol); - valueComponents = (PtgRef[])pa.getColComponents(firstcol + colNum); - }catch(/*20070209 KSC: FormulaNotFound*/Exception e){ - Logger.logWarn("LookupReferenceCalculator.calcVlookup cannot determine PtgArea location. " +e);} - - }else if (table_array instanceof PtgMemFunc) { // || table_array instanceof PtgMemArea){ - try{ - PtgMemFunc pa = (PtgMemFunc)table_array; - // int[] range = table_array.getIntLocation(); - int firstcol = -1; - - try{ - int[] rc1 = pa.getFirstloc().getIntLocation(); - - // TODO: check rc sanity here - firstcol = rc1[1]; - }catch(Exception e) { - Logger.logWarn("LookupReferenceCalculator.calcVlookup could not determine row col from PtgMemFunc."); - } - - lookupComponents = (PtgRef[])pa.getColComponents(firstcol); - valueComponents = (PtgRef[])pa.getColComponents(firstcol + colNum); - }catch(/*20070209 KSC: FormulaNotFound*/Exception e){ - Logger.logWarn("LookupReferenceCalculator.calcVlookup cannot determine PtgArea location. " +e);} - } - // error check - if (lookupComponents==null || lookupComponents.length==0) + } + } + if (table_array instanceof PtgArea) { + try { + PtgArea pa = (PtgArea) table_array; + int[] range = table_array.getIntLocation(); + // TODO: check rc sanity here + int firstcol = range[1]; + lookupComponents = (PtgRef[]) pa.getColComponents(firstcol); + valueComponents = (PtgRef[]) pa.getColComponents(firstcol + colNum); + } catch (/*20070209 KSC: FormulaNotFound*/Exception e) { + Logger.logWarn("LookupReferenceCalculator.calcVlookup cannot determine PtgArea location. " + e); + } + + } else if (table_array instanceof PtgMemFunc) { // || table_array instanceof PtgMemArea){ + try { + PtgMemFunc pa = (PtgMemFunc) table_array; + // int[] range = table_array.getIntLocation(); + int firstcol = -1; + + try { + int[] rc1 = pa.getFirstloc().getIntLocation(); + + // TODO: check rc sanity here + firstcol = rc1[1]; + } catch (Exception e) { + Logger.logWarn("LookupReferenceCalculator.calcVlookup could not determine row col from PtgMemFunc."); + } + + lookupComponents = (PtgRef[]) pa.getColComponents(firstcol); + valueComponents = (PtgRef[]) pa.getColComponents(firstcol + colNum); + } catch (/*20070209 KSC: FormulaNotFound*/Exception e) { + Logger.logWarn("LookupReferenceCalculator.calcVlookup cannot determine PtgArea location. " + e); + } + } + // error check + if (lookupComponents == null || lookupComponents.length == 0) return new PtgErr(PtgErr.ERROR_REF); - if (lookup_value==null || lookup_value.getValue()==null) // 20070221 KSC: Error trap getValue + if (lookup_value == null || lookup_value.getValue() == null) // 20070221 KSC: Error trap getValue return new PtgErr(PtgErr.ERROR_NULL); - // lets check if we are dealing with strings or numbers.... - try{ - String val = lookup_value.getValue().toString(); - if (val.length()==0) // 20090205 KSC + // lets check if we are dealing with strings or numbers.... + try { + String val = lookup_value.getValue().toString(); + if (val.length() == 0) // 20090205 KSC return new PtgErr(PtgErr.ERROR_NA); - Double d = new Double(val); - }catch(NumberFormatException e){ - isNumber=false; - } - - // TODO: - //if the value you supply for the lookup_value argument is smaller than the smallest value in the first column of the table_array argument, VLOOKUP returns the #N/A error value. - - if (isNumber) { - double match_num; - try { - match_num = Double.parseDouble( - lookup_value.getValue().toString() ); - } catch (NumberFormatException e) { + Double d = new Double(val); + } catch (NumberFormatException e) { + isNumber = false; + } + + // TODO: + //if the value you supply for the lookup_value argument is smaller than the smallest value in the first column of the table_array argument, VLOOKUP returns the #N/A error value. + + if (isNumber) { + double match_num; + try { + match_num = Double.parseDouble( + lookup_value.getValue().toString()); + } catch (NumberFormatException e) { return new PtgErr(PtgErr.ERROR_NA); - } - - for (int i = 0; i < lookupComponents.length; i++) { - double val; - - try { - val = Double.parseDouble( - lookupComponents[i].getValue().toString() ); - if (val==0) { // VLOOKUP does NOT treat blanks as 0's - if (lookupComponents[i].refCell[0]==null) { - continue; - } - } - } catch (NumberFormatException e) { - // Ignore entries in the table that aren't numbers. - continue; - } - - if (val == match_num) { - return valueComponents[i].getPtgVal(); - } else if (rangeLookup && val > match_num) { - if (i == 0) return new PtgErr( PtgErr.ERROR_NA ); - else return valueComponents[ i - 1 ].getPtgVal(); - } - } - - if (rangeLookup) return valueComponents[ lookupComponents.length - 1 ].getPtgVal(); - else return new PtgErr( PtgErr.ERROR_NA ); - } - - // It's a String - else { - if (rangeLookup){ // approximate match - String match_str = lookup_value.getValue().toString(); - int match_len = match_str.length(); - for (int i=0;i=match_len && val.substring(0, match_len).equalsIgnoreCase(match_str)) { // matches up to length, but not all, return previous - return valueComponents[i-1].getPtgVal(); - } else if (ExcelTools.getIntVal(val.substring(0, 1))>ExcelTools.getIntVal(match_str.substring(0, 1))){ - return valueComponents[i-1].getPtgVal(); - }else if (i == lookupComponents.length -1){// we reached the last one so use this - return valueComponents[i].getPtgVal(); - } - } catch (Exception e) { } // 20070209 KSC: ignore errors in lookup cells - } - } else{ // unsorted + } + + for (int i = 0; i < lookupComponents.length; i++) { + double val; + + try { + val = Double.parseDouble( + lookupComponents[i].getValue().toString()); + if (val == 0) { // VLOOKUP does NOT treat blanks as 0's + if (lookupComponents[i].refCell[0] == null) { + continue; + } + } + } catch (NumberFormatException e) { + // Ignore entries in the table that aren't numbers. + continue; + } + + if (val == match_num) { + return valueComponents[i].getPtgVal(); + } else if (rangeLookup && val > match_num) { + if (i == 0) return new PtgErr(PtgErr.ERROR_NA); + else return valueComponents[i - 1].getPtgVal(); + } + } + + if (rangeLookup) return valueComponents[lookupComponents.length - 1].getPtgVal(); + else return new PtgErr(PtgErr.ERROR_NA); + } + + // It's a String + else { + if (rangeLookup) { // approximate match + String match_str = lookup_value.getValue().toString(); + int match_len = match_str.length(); + for (int i = 0; i < lookupComponents.length; i++) { + try { + String val = lookupComponents[i].getValue().toString(); + if (val.equalsIgnoreCase(match_str)) {// we found it + return valueComponents[i].getPtgVal(); + } else if (val.length() >= match_len && val.substring(0, match_len).equalsIgnoreCase(match_str)) { // matches up to length, but not all, return previous + return valueComponents[i - 1].getPtgVal(); + } else if (ExcelTools.getIntVal(val.substring(0, 1)) > ExcelTools.getIntVal(match_str.substring(0, 1))) { + return valueComponents[i - 1].getPtgVal(); + } else if (i == lookupComponents.length - 1) {// we reached the last one so use this + return valueComponents[i].getPtgVal(); + } + } catch (Exception e) { + } // 20070209 KSC: ignore errors in lookup cells + } + } else { // unsorted String match_str = lookup_value.getValue().toString(); - for (int i=0;i. @@ -22,14 +22,11 @@ */ package io.starter.formats.XLS.formulas; +import io.starter.toolkit.Logger; + import java.math.BigDecimal; import java.util.ArrayList; import java.util.Random; -import java.util.Stack; - -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.toolkit.Logger; /* @@ -41,556 +38,569 @@ acted upon. A Ptg of the type that makes sense (ie boolean, number) is returned after each method. */ -public class MathFunctionCalculator -{ - +public class MathFunctionCalculator { + + + /** + * SUM + * Adds all the numbers in a range of cells. + * Ignores non-number fields + *

                + * Usage@ SUM(number1,number2, ...) + * Return@ PtgNumber + * + * @throws CalculationException + */ + protected static Ptg calcSum(Ptg[] operands) throws CalculationException { + if (operands.length < 1) return PtgCalculator.getNAError(); + double result = 0; + double[] dub = PtgCalculator.getDoubleValueArray(operands); + if (dub == null) return PtgCalculator.getNAError(); + for (int i = 0; i < dub.length; i++) { + result += dub[i]; + } + return new PtgNumber(result); + } -/** - * SUM - Adds all the numbers in a range of cells. - Ignores non-number fields + /** + * ABS + * Returns the absolute value of a number + */ + protected static Ptg calcAbs(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + dd = Math.abs(dd); + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + PtgNumber ptnum = new PtgNumber(dd); + return ptnum; - Usage@ SUM(number1,number2, ...) - Return@ PtgNumber - * @throws CalculationException -*/ -protected static Ptg calcSum(Ptg[] operands) throws CalculationException{ - if (operands.length<1)return PtgCalculator.getNAError(); - double result = 0; - double[] dub = PtgCalculator.getDoubleValueArray(operands); - if (dub==null) return PtgCalculator.getNAError(); - for (int i=0;i 0.0 ? 1.0 : -1.0)*getAcosh(Math.sqrt(1.0+(x*x)))); - bd.setScale(15, BigDecimal.ROUND_HALF_UP); + BigDecimal bd = new BigDecimal((x > 0.0 ? 1.0 : -1.0) * getAcosh(Math.sqrt(1.0 + (x * x)))); + bd.setScale(15, BigDecimal.ROUND_HALF_UP); // PtgNumber ptnum = new PtgNumber(dd); - PtgNumber ptnum = new PtgNumber(bd.doubleValue()); - return ptnum; -} + PtgNumber ptnum = new PtgNumber(bd.doubleValue()); + return ptnum; + } -/** -ATAN -Returns the arctangent of a number -*/ -protected static Ptg calcAtan(Ptg[] operands){ - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - dd = Math.atan(dd); - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - PtgNumber ptnum = new PtgNumber(dd); - return ptnum; -} + /** + * ATAN + * Returns the arctangent of a number + */ + protected static Ptg calcAtan(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + dd = Math.atan(dd); + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + PtgNumber ptnum = new PtgNumber(dd); + return ptnum; + } -/** -ATAN2 -Returns the arctangent from x- and y- coordinates - * @throws CalculationException -*/ -protected static Ptg calcAtan2(Ptg[] operands) throws CalculationException{ - if (operands.length != 2) return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA); - double res = Math.atan2(dd[0], dd[1]); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -ATANH -Returns the inverse hyperbolic tangent of a number -*/ -protected static Ptg calcAtanh(Ptg[] operands){ - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (dd > 1 || dd < -1) return PtgCalculator.getError(); - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - double res = (0.5*Math.log((1.0+dd)/(1.0-dd))); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} + /** + * ATAN2 + * Returns the arctangent from x- and y- coordinates + * + * @throws CalculationException + */ + protected static Ptg calcAtan2(Ptg[] operands) throws CalculationException { + if (operands.length != 2) return PtgCalculator.getNAError(); + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA); + double res = Math.atan2(dd[0], dd[1]); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } -/** -CEILING -Rounds a number to the nearest multiple of significance; -This takes 2 values, first the number to round, next the value of signifigance. -Ick. This is pretty intensive, so maybe a better way? - * @throws CalculationException -*/ -protected static Ptg calcCeiling(Ptg[] operands) throws CalculationException{ - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getNAError(); - double num = dd[0]; - double multiple = dd[1]; - double res = 0; - while (res < num){ - res += multiple; - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -COMBIN -Returns the number of combinations for a given number of objects - * @throws CalculationException -*/ -protected static Ptg calcCombin(Ptg[] operands) throws CalculationException{ - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getNAError(); - long num1 = Math.round(dd[0]); - long num2 = Math.round(dd[1]); - if (num1 < num2) return PtgCalculator.getError(); - long res1 = stepFactorial(num1, (int)num2); - long res2 = factorial(num2); - double res = res1/res2; - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -COS -Returns the cosine of a number -*/ -protected static Ptg calcCos(Ptg[] operands){ - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - double res = Math.cos(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -COSH -Returns the hyperbolic cosine of a number -*/ -protected static Ptg calcCosh(Ptg[] operands){ - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - double res = 0.5*(Math.exp(dd)+Math.exp(-dd)); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} + /** + * ATANH + * Returns the inverse hyperbolic tangent of a number + */ + protected static Ptg calcAtanh(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (dd > 1 || dd < -1) return PtgCalculator.getError(); + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + double res = (0.5 * Math.log((1.0 + dd) / (1.0 - dd))); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } -/** -DEGREES -Converts radians to degrees -*/ -protected static Ptg calcDegrees(Ptg[] operands){ - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - double res = Math.toDegrees(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} + /** + * CEILING + * Rounds a number to the nearest multiple of significance; + * This takes 2 values, first the number to round, next the value of signifigance. + * Ick. This is pretty intensive, so maybe a better way? + * + * @throws CalculationException + */ + protected static Ptg calcCeiling(Ptg[] operands) throws CalculationException { + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + if (dd.length != 2) return PtgCalculator.getNAError(); + double num = dd[0]; + double multiple = dd[1]; + double res = 0; + while (res < num) { + res += multiple; + } + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } -/** -EVEN -Rounds a number up to the nearest even integer -*/ -protected static Ptg calcEven(Ptg[] operands){ - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - long resnum = Math.round(dd); - double remainder = dd%2; - if (remainder !=0){ - resnum++; - } - PtgInt pint = new PtgInt((int)resnum); - return pint; -} -/** -EXP -Returns e raised to the power of number. -The constant e equals 2.71828182845904, -the base of the natural logarithm. - -Example -EXP(2) equals e2, or 7.389056 -*/ -protected static Ptg calcExp(Ptg[] operands){ - if (operands.length >1 || operands[0].getComponents() != null){ // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); + /** + * COMBIN + * Returns the number of combinations for a given number of objects + * + * @throws CalculationException + */ + protected static Ptg calcCombin(Ptg[] operands) throws CalculationException { + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + if (dd.length != 2) return PtgCalculator.getNAError(); + long num1 = Math.round(dd[0]); + long num2 = Math.round(dd[1]); + if (num1 < num2) return PtgCalculator.getError(); + long res1 = stepFactorial(num1, (int) num2); + long res2 = factorial(num2); + double res = res1 / res2; + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * COS + * Returns the cosine of a number + */ + protected static Ptg calcCos(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + double res = Math.cos(dd); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; } - double d = 2.718281828459045; - Ptg p = operands[0]; - try { - Double dub = new Double(String.valueOf(p.getValue())); + + /** + * COSH + * Returns the hyperbolic cosine of a number + */ + protected static Ptg calcCosh(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + double res = 0.5 * (Math.exp(dd) + Math.exp(-dd)); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * DEGREES + * Converts radians to degrees + */ + protected static Ptg calcDegrees(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + double res = Math.toDegrees(dd); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * EVEN + * Rounds a number up to the nearest even integer + */ + protected static Ptg calcEven(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + long resnum = Math.round(dd); + double remainder = dd % 2; + if (remainder != 0) { + resnum++; + } + PtgInt pint = new PtgInt((int) resnum); + return pint; + } + + /** + * EXP + * Returns e raised to the power of number. + * The constant e equals 2.71828182845904, + * the base of the natural logarithm. + *

                + * Example + * EXP(2) equals e2, or 7.389056 + */ + protected static Ptg calcExp(Ptg[] operands) { + if (operands.length > 1 || operands[0].getComponents() != null) { // not supported by function + return new PtgErr(PtgErr.ERROR_NULL); + } + double d = 2.718281828459045; + Ptg p = operands[0]; + try { + Double dub = new Double(String.valueOf(p.getValue())); // double result = Math.pow(d, dub.doubleValue()); - BigDecimal result = new BigDecimal(Math.pow(d, dub.doubleValue())); - result.setScale(15, BigDecimal.ROUND_HALF_UP); + BigDecimal result = new BigDecimal(Math.pow(d, dub.doubleValue())); + result.setScale(15, BigDecimal.ROUND_HALF_UP); // PtgNumber pnum = new PtgNumber(result); - PtgNumber pnum = new PtgNumber(result.doubleValue()); - return pnum; - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); + PtgNumber pnum = new PtgNumber(result.doubleValue()); + return pnum; + } catch (NumberFormatException e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } } -} -/** -FACT -Returns the factorial of a number -*/ -protected static Ptg calcFact(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - long dd= 0; - try { - dd= PtgCalculator.getLongValue(operands); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - long res = MathFunctionCalculator.factorial(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} + /** + * FACT + * Returns the factorial of a number + */ + protected static Ptg calcFact(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + long dd = 0; + try { + dd = PtgCalculator.getLongValue(operands); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + long res = MathFunctionCalculator.factorial(dd); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } -/** -FACTDOUBLE -Returns the double factorial of a number -Example -!!6 = 6*4*2 -!!7 = 7*5*3*1; -*/ -protected static Ptg calcFactDouble(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - long n =PtgCalculator.getLongValue(operands); - if (new Double(n).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - if (n < 0) - return new PtgErr(PtgErr.ERROR_NUM); - double res= 1; - long endPoint = (n%2==0?2:1); - for (long i= n; i >= endPoint; i-=2) { - res*= i; - } - if (n==0) - res= -1; // by convention ... + /** + * FACTDOUBLE + * Returns the double factorial of a number + * Example + * !!6 = 6*4*2 + * !!7 = 7*5*3*1; + */ + protected static Ptg calcFactDouble(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + long n = PtgCalculator.getLongValue(operands); + if (new Double(n).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + if (n < 0) + return new PtgErr(PtgErr.ERROR_NUM); + double res = 1; + long endPoint = (n % 2 == 0 ? 2 : 1); + for (long i = n; i >= endPoint; i -= 2) { + res *= i; + } + if (n == 0) + res = -1; // by convention ... // KSC: not quite right!!! replaced with above - //long res = MathFunctionCalculator.doubleFactorial(n); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -FLOOR -Rounds a number down, toward zero. Works just like Celing with two operands -See comment above for celing for more info. - * @throws CalculationException -*/ -protected static Ptg calcFloor(Ptg[] operands) throws CalculationException{ - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getError(); - double num = dd[0]; - double multiple = dd[1]; - double res = 0; - while (res < num){ - res += multiple; - } - // drop one from the celing code to get the floor... - res -= multiple; - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -GCD -Returns the greatest common divisor -TODO: Finish! -*/ -protected static Ptg calcGCD(Ptg[] operands) { - if (operands.length < 1)return PtgCalculator.getNAError(); - Ptg[] numbers= PtgCalculator.getAllComponents(operands); - long gcd= 0; - try { - long n1; - if ((n1=PtgCalculator.getLongValue(numbers[0])) < 0) - return new PtgErr(PtgErr.ERROR_NUM); - - gcd= n1; - for (int i= 1; i < numbers.length; i++) { - long n2; - if ((n2=PtgCalculator.getLongValue(numbers[i])) < 0) - return new PtgErr(PtgErr.ERROR_NUM); - - long bigger, smaller, r; - bigger= Math.max(n2, n1); - smaller= Math.min(n2, n1); - r= bigger%smaller; - while (r != 0) { - bigger= smaller; - smaller= r; - r= bigger%smaller; - } - gcd= Math.min(gcd, smaller); - } - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - - PtgNumber pnum = new PtgNumber(gcd); - return pnum; -} + //long res = MathFunctionCalculator.doubleFactorial(n); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } -/** -INT -Rounds a number down to the nearest integer -*/ -protected static Ptg calcInt(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - long res = Math.round(dd); - if (res > dd) res--; - PtgInt pint = new PtgInt((int)res); - return pint; -} + /** + * FLOOR + * Rounds a number down, toward zero. Works just like Celing with two operands + * See comment above for celing for more info. + * + * @throws CalculationException + */ + protected static Ptg calcFloor(Ptg[] operands) throws CalculationException { + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + if (dd.length != 2) return PtgCalculator.getError(); + double num = dd[0]; + double multiple = dd[1]; + double res = 0; + while (res < num) { + res += multiple; + } + // drop one from the celing code to get the floor... + res -= multiple; + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } -/** -LCM -Returns the least common multiple -*/ -protected static Ptg calcLCM(Ptg[] operands) { - if (operands.length < 1)return new PtgErr(PtgErr.ERROR_NA); - Ptg[] numbers= PtgCalculator.getAllComponents(operands); - // algorithm: - // LCM(a, b)= (a*b)/GCD(a, b) - long lcm= 0; - try { - Ptg[] ops= new Ptg[2]; - if ((lcm=PtgCalculator.getLongValue(numbers[0])) < 0) - return new PtgErr(PtgErr.ERROR_NUM); - - for (int i= 1; i < numbers.length; i++) { - long n2; - if ((n2=PtgCalculator.getLongValue(numbers[i])) < 0) - return new PtgErr(PtgErr.ERROR_NUM); - - ops[0]= new PtgNumber(lcm); - ops[1]= new PtgNumber(n2); - long gcd= PtgCalculator.getLongValue(calcGCD(ops)); - lcm= (lcm*n2)/gcd; - } - } - catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - - PtgNumber pnum = new PtgNumber(lcm); - return pnum; -} + /** + * GCD + * Returns the greatest common divisor + * TODO: Finish! + */ + protected static Ptg calcGCD(Ptg[] operands) { + if (operands.length < 1) return PtgCalculator.getNAError(); + Ptg[] numbers = PtgCalculator.getAllComponents(operands); + long gcd = 0; + try { + long n1; + if ((n1 = PtgCalculator.getLongValue(numbers[0])) < 0) + return new PtgErr(PtgErr.ERROR_NUM); + + gcd = n1; + for (int i = 1; i < numbers.length; i++) { + long n2; + if ((n2 = PtgCalculator.getLongValue(numbers[i])) < 0) + return new PtgErr(PtgErr.ERROR_NUM); + + long bigger, smaller, r; + bigger = Math.max(n2, n1); + smaller = Math.min(n2, n1); + r = bigger % smaller; + while (r != 0) { + bigger = smaller; + smaller = r; + r = bigger % smaller; + } + gcd = Math.min(gcd, smaller); + } + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + + PtgNumber pnum = new PtgNumber(gcd); + return pnum; + } -/** -LN -Returns the natural logarithm of a number -*/ -protected static Ptg calcLn(Ptg[] operands){ - if (operands.length > 1)return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.log(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -LOG -Returns the logarithm of a number to a specified base - * @throws CalculationException -*/ -protected static Ptg calcLog(Ptg[] operands) throws CalculationException{ - if (operands.length<1)return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length > 2) return PtgCalculator.getError(); - double num1; - double num2; - if (dd.length == 1){ - num1 = dd[0]; - num2 = 10; - }else{ - num1 = dd[0]; - num2 = dd[1]; - } - double res = Math.log(num1)/Math.log(num2); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -LOG10 -Returns the base-10 logarithm of a number -*/ -protected static Ptg calcLog10(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); + /** + * INT + * Rounds a number down to the nearest integer + */ + protected static Ptg calcInt(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + long res = Math.round(dd); + if (res > dd) res--; + PtgInt pint = new PtgInt((int) res); + return pint; + } + + /** + * LCM + * Returns the least common multiple + */ + protected static Ptg calcLCM(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_NA); + Ptg[] numbers = PtgCalculator.getAllComponents(operands); + // algorithm: + // LCM(a, b)= (a*b)/GCD(a, b) + long lcm = 0; + try { + Ptg[] ops = new Ptg[2]; + if ((lcm = PtgCalculator.getLongValue(numbers[0])) < 0) + return new PtgErr(PtgErr.ERROR_NUM); + + for (int i = 1; i < numbers.length; i++) { + long n2; + if ((n2 = PtgCalculator.getLongValue(numbers[i])) < 0) + return new PtgErr(PtgErr.ERROR_NUM); + + ops[0] = new PtgNumber(lcm); + ops[1] = new PtgNumber(n2); + long gcd = PtgCalculator.getLongValue(calcGCD(ops)); + lcm = (lcm * n2) / gcd; + } + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + + PtgNumber pnum = new PtgNumber(lcm); + return pnum; + } + + /** + * LN + * Returns the natural logarithm of a number + */ + protected static Ptg calcLn(Ptg[] operands) { + if (operands.length > 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr + double res = Math.log(dd); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * LOG + * Returns the logarithm of a number to a specified base + * + * @throws CalculationException + */ + protected static Ptg calcLog(Ptg[] operands) throws CalculationException { + if (operands.length < 1) return PtgCalculator.getNAError(); + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + if (dd.length > 2) return PtgCalculator.getError(); + double num1; + double num2; + if (dd.length == 1) { + num1 = dd[0]; + num2 = 10; + } else { + num1 = dd[0]; + num2 = dd[1]; + } + double res = Math.log(num1) / Math.log(num2); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * LOG10 + * Returns the base-10 logarithm of a number + */ + protected static Ptg calcLog10(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); // double res = Math.log(dd)/Math.log(10); - - BigDecimal res= new BigDecimal(Math.log10(dd)); - res.setScale(15, BigDecimal.ROUND_HALF_UP); - PtgNumber ptnum = new PtgNumber(res.doubleValue()); - return ptnum; -} + + BigDecimal res = new BigDecimal(Math.log10(dd)); + res.setScale(15, BigDecimal.ROUND_HALF_UP); + PtgNumber ptnum = new PtgNumber(res.doubleValue()); + return ptnum; + } /* MDETERM Returns the matrix determinant of an array @@ -598,682 +608,699 @@ protected static Ptg calcLog10(Ptg[] operands){ MINVERSE Returns the matrix inverse of an array */ -/** - * -MMULT -Returns the matrix product of two arrays: - The number of columns in array1 must be the same as the number of rows in array2, - and both arrays must contain only numbers. - MMULT returns the #VALUE! error when: - Any cells are empty or contain text. - The number of columns in array1 is different from the number of rows in array2. -*/ -protected static Ptg calcMMult(Ptg[] operands){ - if (operands.length!=2)return PtgCalculator.getNAError(); - try { - // error trap params: must be numeric arrays with no empty spaces - double[][] a1= PtgCalculator.getArray(operands[0]); - double[][] a2= PtgCalculator.getArray(operands[1]); - if (a1[0].length!=a2.length) - PtgCalculator.getValueError(); - double sum= 0.0; - for (int i= 0; i < a1[0].length; i++) { - sum+=a1[0][i]*a2[i][0]; - } - return new PtgNumber(sum); - } catch (Exception e) { + /** + * MMULT + * Returns the matrix product of two arrays: + * The number of columns in array1 must be the same as the number of rows in array2, + * and both arrays must contain only numbers. + * MMULT returns the #VALUE! error when: + * Any cells are empty or contain text. + * The number of columns in array1 is different from the number of rows in array2. + */ + protected static Ptg calcMMult(Ptg[] operands) { + if (operands.length != 2) return PtgCalculator.getNAError(); + try { + // error trap params: must be numeric arrays with no empty spaces + double[][] a1 = PtgCalculator.getArray(operands[0]); + double[][] a2 = PtgCalculator.getArray(operands[1]); + if (a1[0].length != a2.length) + PtgCalculator.getValueError(); + double sum = 0.0; + for (int i = 0; i < a1[0].length; i++) { + sum += a1[0][i] * a2[i][0]; + } + return new PtgNumber(sum); + } catch (Exception e) { // Logger.logErr("MMULT: error in operands " + e.toString()); - } - return PtgCalculator.getValueError(); -} -/** -MOD -Returns the remainder from division - * @throws CalculationException -*/ -protected static Ptg calcMod(Ptg[] operands) throws CalculationException{ - if (operands.length<1)return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getError(); - double num1 = dd[0]; - double num2 = dd[1]; - double res = num1%num2; - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -MROUND -Returns a number rounded to the desired multiple -*/ -protected static Ptg calcMRound(Ptg[] operands){ - if (operands.length != 2) return new PtgErr(PtgErr.ERROR_NA); - double m= 0.0, n= 0.0; - try { - n= operands[0].getDoubleVal(); - m= operands[1].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if ((n<0 && m>0) || (n>0 && m<0)) return new PtgErr(PtgErr.ERROR_NUM); - double result= Math.round(n/m) * m; - PtgNumber pnum= new PtgNumber(result); - return pnum; -} + } + return PtgCalculator.getValueError(); + } + + /** + * MOD + * Returns the remainder from division + * + * @throws CalculationException + */ + protected static Ptg calcMod(Ptg[] operands) throws CalculationException { + if (operands.length < 1) return PtgCalculator.getNAError(); + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + if (dd.length != 2) return PtgCalculator.getError(); + double num1 = dd[0]; + double num2 = dd[1]; + double res = num1 % num2; + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + /** + * MROUND + * Returns a number rounded to the desired multiple + */ + protected static Ptg calcMRound(Ptg[] operands) { + if (operands.length != 2) return new PtgErr(PtgErr.ERROR_NA); + double m = 0.0, n = 0.0; + try { + n = operands[0].getDoubleVal(); + m = operands[1].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if ((n < 0 && m > 0) || (n > 0 && m < 0)) return new PtgErr(PtgErr.ERROR_NUM); + double result = Math.round(n / m) * m; + PtgNumber pnum = new PtgNumber(result); + return pnum; + } -/** -MULTINOMIAL -Returns the multinomial of a set of numbers -*/ -protected static Ptg calcMultinomial(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_NA); - Ptg[] numbers= PtgCalculator.getAllComponents(operands); - long sum= 0; - double facts= 1; - for (int i= 0; i < numbers.length; i++) { - long n= PtgCalculator.getLongValue(operands[i]); - if (n < 1) return new PtgErr(PtgErr.ERROR_NUM); - sum+= n; - facts*=factorial(n); - } - double result= factorial(sum)/facts; - PtgNumber pnum= new PtgNumber(result); - return pnum; -} -/** -ODD -Rounds a number up to the nearest odd integer -*/ -protected static Ptg calcOdd(Ptg[] operands){ - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - long resnum = Math.round(dd); - // always round up!! - if (resnum < dd) resnum++; - double remainder = resnum%2; - if (remainder ==0){ - resnum++; - } - PtgInt pint = new PtgInt((int)resnum); - return pint; -} -/** -PI -Returns the value of Pi -*/ -protected static Ptg calcPi(Ptg[] operands){ - double pi = Math.PI; - PtgNumber pnum = new PtgNumber(pi); - return pnum; -} -/** -POWER -Returns the result of a number raised to a power - * @throws CalculationException -*/ -protected static Ptg calcPower(Ptg[] operands) throws CalculationException{ - if (operands.length<1)return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getError(); + /** + * MULTINOMIAL + * Returns the multinomial of a set of numbers + */ + protected static Ptg calcMultinomial(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_NA); + Ptg[] numbers = PtgCalculator.getAllComponents(operands); + long sum = 0; + double facts = 1; + for (int i = 0; i < numbers.length; i++) { + long n = PtgCalculator.getLongValue(operands[i]); + if (n < 1) return new PtgErr(PtgErr.ERROR_NUM); + sum += n; + facts *= factorial(n); + } + double result = factorial(sum) / facts; + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * ODD + * Rounds a number up to the nearest odd integer + */ + protected static Ptg calcOdd(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + long resnum = Math.round(dd); + // always round up!! + if (resnum < dd) resnum++; + double remainder = resnum % 2; + if (remainder == 0) { + resnum++; + } + PtgInt pint = new PtgInt((int) resnum); + return pint; + } + + /** + * PI + * Returns the value of Pi + */ + protected static Ptg calcPi(Ptg[] operands) { + double pi = Math.PI; + PtgNumber pnum = new PtgNumber(pi); + return pnum; + } + + /** + * POWER + * Returns the result of a number raised to a power + * + * @throws CalculationException + */ + protected static Ptg calcPower(Ptg[] operands) throws CalculationException { + if (operands.length < 1) return PtgCalculator.getNAError(); + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + if (dd.length != 2) return PtgCalculator.getError(); // double num1 = dd[0]; // double num2 = dd[1]; - BigDecimal num1= new BigDecimal(dd[0]); - num1.setScale(15, BigDecimal.ROUND_HALF_UP); - BigDecimal num2= new BigDecimal(dd[1]); - num2.setScale(15, BigDecimal.ROUND_HALF_UP); - BigDecimal res= new BigDecimal(Math.pow(num1.doubleValue(), num2.doubleValue())); - res.setScale(15, BigDecimal.ROUND_HALF_UP); - PtgNumber ptnum = new PtgNumber(res.doubleValue()); - return ptnum; -} + BigDecimal num1 = new BigDecimal(dd[0]); + num1.setScale(15, BigDecimal.ROUND_HALF_UP); + BigDecimal num2 = new BigDecimal(dd[1]); + num2.setScale(15, BigDecimal.ROUND_HALF_UP); + BigDecimal res = new BigDecimal(Math.pow(num1.doubleValue(), num2.doubleValue())); + res.setScale(15, BigDecimal.ROUND_HALF_UP); + PtgNumber ptnum = new PtgNumber(res.doubleValue()); + return ptnum; + } -/** -PRODUCT -Multiplies its arguments -NOTE: we gotta deal with ranges/refs/numbers here - * @throws CalculationException -*/ -protected static Ptg calcProduct(Ptg[] operands) throws CalculationException{ - if (operands.length<1)return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//propagate error - double result = dd[0]; - for (int i=1;i3999) return PtgCalculator.getError(); // can't write nums that high! - for (int z = 0; z < numbers.length; z++) { - while (i >= numbers[z]) { - roman += letters[z]; - i -= numbers[z]; - } - } - } - PtgStr pstr = new PtgStr(roman); - return pstr; -} -/** -ROUND -Rounds a number to a specified number of digits -This one is kind of nasty. 3 cases of rounding. If the rounding is a positive -integer this is the number of digits to round to. If negative, round up past the -decimal, if 0, give an integer - * @throws CalculationException -*/ -protected static Ptg calcRound(Ptg[] operands) throws CalculationException{ - if (operands.length<1)return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - //if (dd.length != 2) return PtgCalculator.getError(); - // we need to handle arrays sent in, just use the first and last elements. - double num = dd[0]; - double round = dd[dd.length-1]; - double res = 0; - if (round == 0){//return an int - res = Math.round(num); - }else if(round > 0){ //round the decimal that number of spaces - double tempnum = num * Math.pow(10, round); - tempnum = Math.round(tempnum); - res = tempnum/Math.pow(10, round); - }else{ //round up the decimal that numbe of places - round = round * -1; - double tempnum = num/Math.pow(10, round); - tempnum = Math.round(tempnum); - res = tempnum * Math.pow(10, round); - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -ROUNDDOWN -Rounds a number down, toward zero. Acts much like round above - * @throws CalculationException -*/ -protected static Ptg calcRoundDown(Ptg[] operands) throws CalculationException{ - if (operands.length<1)return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - //if (dd.length != 2) return PtgCalculator.getError(); - double num = dd[0]; - double round = dd[dd.length-1]; - double res = 0; - if (round == 0){//return an int - res = Math.round(num); - if (res > num) res--; - }else if(round > 0){ //round the decimal that number of spaces - double tempnum = num * Math.pow(10, round); - double tempnum2 = Math.round(tempnum); - if (tempnum2 > tempnum) tempnum2--; - res = tempnum2/Math.pow(10, round); - }else{ //round up the decimal that numbe of places - round = round * -1; - double tempnum = num/Math.pow(10, round); - double tempnum2 = Math.round(tempnum); - if (tempnum2 > tempnum) tempnum2--; - res = tempnum2 * Math.pow(10, round); - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} + /** + * ROMAN + * Converts an Arabic numeral to Roman, as text + * This one is a trip! + */ + protected static Ptg calcRoman(Ptg[] operands) { + int[] numbers = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; + String[] letters = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; + String roman = ""; + if (operands.length == 1) { + double dd = operands[0].getDoubleVal(); + if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr + return PtgCalculator.getError(); + + int i = (int) dd; + if (i < 0 || i > 3999) return PtgCalculator.getError(); // can't write nums that high! + for (int z = 0; z < numbers.length; z++) { + while (i >= numbers[z]) { + roman += letters[z]; + i -= numbers[z]; + } + } + } + PtgStr pstr = new PtgStr(roman); + return pstr; + } -/** -ROUNDUP -Rounds a number up, away from zero - * @throws CalculationException -*/ -protected static Ptg calcRoundUp(Ptg[] operands) throws CalculationException{ - if (operands.length<1)return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - //if (dd.length != 2) return PtgCalculator.getError(); - double num = dd[0]; - double round = dd[dd.length-1]; - double res = 0; - if (round == 0){//return an int - res = Math.round(num); - if (res < num) res++; - }else if(round > 0){ //round the decimal that number of spaces - double tempnum = num * Math.pow(10, round); - double tempnum2 = Math.round(tempnum); - if (tempnum2 < tempnum) tempnum2++; - res = tempnum2/Math.pow(10, round); - }else{ //round up the decimal that numbe of places - round = round * -1; - double tempnum = num/Math.pow(10, round); - double tempnum2 = Math.round(tempnum); - if (tempnum2 < tempnum) tempnum2++; - res = tempnum2 * Math.pow(10, round); - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} + /** + * ROUND + * Rounds a number to a specified number of digits + * This one is kind of nasty. 3 cases of rounding. If the rounding is a positive + * integer this is the number of digits to round to. If negative, round up past the + * decimal, if 0, give an integer + * + * @throws CalculationException + */ + protected static Ptg calcRound(Ptg[] operands) throws CalculationException { + if (operands.length < 1) return PtgCalculator.getNAError(); + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + //if (dd.length != 2) return PtgCalculator.getError(); + // we need to handle arrays sent in, just use the first and last elements. + double num = dd[0]; + double round = dd[dd.length - 1]; + double res = 0; + if (round == 0) {//return an int + res = Math.round(num); + } else if (round > 0) { //round the decimal that number of spaces + double tempnum = num * Math.pow(10, round); + tempnum = Math.round(tempnum); + res = tempnum / Math.pow(10, round); + } else { //round up the decimal that numbe of places + round = round * -1; + double tempnum = num / Math.pow(10, round); + tempnum = Math.round(tempnum); + res = tempnum * Math.pow(10, round); + } + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * ROUNDDOWN + * Rounds a number down, toward zero. Acts much like round above + * + * @throws CalculationException + */ + protected static Ptg calcRoundDown(Ptg[] operands) throws CalculationException { + if (operands.length < 1) return PtgCalculator.getNAError(); + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + //if (dd.length != 2) return PtgCalculator.getError(); + double num = dd[0]; + double round = dd[dd.length - 1]; + double res = 0; + if (round == 0) {//return an int + res = Math.round(num); + if (res > num) res--; + } else if (round > 0) { //round the decimal that number of spaces + double tempnum = num * Math.pow(10, round); + double tempnum2 = Math.round(tempnum); + if (tempnum2 > tempnum) tempnum2--; + res = tempnum2 / Math.pow(10, round); + } else { //round up the decimal that numbe of places + round = round * -1; + double tempnum = num / Math.pow(10, round); + double tempnum2 = Math.round(tempnum); + if (tempnum2 > tempnum) tempnum2--; + res = tempnum2 * Math.pow(10, round); + } + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * ROUNDUP + * Rounds a number up, away from zero + * + * @throws CalculationException + */ + protected static Ptg calcRoundUp(Ptg[] operands) throws CalculationException { + if (operands.length < 1) return PtgCalculator.getNAError(); + double[] dd = PtgCalculator.getDoubleValueArray(operands); + if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + //if (dd.length != 2) return PtgCalculator.getError(); + double num = dd[0]; + double round = dd[dd.length - 1]; + double res = 0; + if (round == 0) {//return an int + res = Math.round(num); + if (res < num) res++; + } else if (round > 0) { //round the decimal that number of spaces + double tempnum = num * Math.pow(10, round); + double tempnum2 = Math.round(tempnum); + if (tempnum2 < tempnum) tempnum2++; + res = tempnum2 / Math.pow(10, round); + } else { //round up the decimal that numbe of places + round = round * -1; + double tempnum = num / Math.pow(10, round); + double tempnum2 = Math.round(tempnum); + if (tempnum2 < tempnum) tempnum2++; + res = tempnum2 * Math.pow(10, round); + } + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } /* SERIESSUM Returns the sum of a power series based on the formula TODO: requires pack to run */ -/** -SIGN -Returns the sign of a number -return 1 if positive, -1 if negative, or 0 if 0; -*/ -protected static Ptg calcSign(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - int res = 0; - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - if (dd == 0)res = 0; - if (dd > 0)res = 1; - if (dd < 0)res = -1; - PtgInt pint = new PtgInt(res); - return pint; -} -/** -SIN -Returns the sine of the given angle -*/ -protected static Ptg calcSin(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.sin(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -SINH -Returns the hyperbolic sine of a number -*/ -protected static Ptg calcSinh(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = 0.5*(Math.exp(dd)-Math.exp(-dd)); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -SQRT -Returns a positive square root -*/ -protected static Ptg calcSqrt(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.sqrt(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} + /** + * SIGN + * Returns the sign of a number + * return 1 if positive, -1 if negative, or 0 if 0; + */ + protected static Ptg calcSign(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + int res = 0; + if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr + if (dd == 0) res = 0; + if (dd > 0) res = 1; + if (dd < 0) res = -1; + PtgInt pint = new PtgInt(res); + return pint; + } -/** -SQRTPI -Returns the square root of (number * PI) -*/ -protected static Ptg calcSqrtPi(Ptg[] operands){ - if (operands.length<1)return PtgCalculator.getNAError(); - double dd=0.0; - try { - dd =operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.sqrt(dd * Math.PI); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - -} + /** + * SIN + * Returns the sine of the given angle + */ + protected static Ptg calcSin(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr + double res = Math.sin(dd); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * SINH + * Returns the hyperbolic sine of a number + */ + protected static Ptg calcSinh(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr + double res = 0.5 * (Math.exp(dd) - Math.exp(-dd)); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * SQRT + * Returns a positive square root + */ + protected static Ptg calcSqrt(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr + double res = Math.sqrt(dd); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } + + /** + * SQRTPI + * Returns the square root of (number * PI) + */ + protected static Ptg calcSqrtPi(Ptg[] operands) { + if (operands.length < 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr + double res = Math.sqrt(dd * Math.PI); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + + } /* SUBTOTAL Returns a subtotal in a list or database */ - /** - SUMIF - Adds the cells specified by a given criteria - - You use the SUMIF function to sum the values in a range that meet criteria that you specify. - For example, suppose that in a column that contains numbers, you want to sum only the values that are larger than 5. You can use the following formula: - - =SUMIF(B2:B25,">5") - - you can apply the criteria to one range and sum the corresponding values in a different range. - For example, the formula =SUMIF(B2:B5, "John", C2:C5) sums only the values in the range C2:C5, where the corresponding cells in the range B2:B5 equal "John." - - range Required. The range of cells that you want evaluated by criteria. Cells in each range must be numbers or names, arrays, or references that contain numbers. Blank and text values are ignored. - criteria Required. The criteria in the form of a number, expression, a cell reference, text, or a function that defines which cells will be added. For example, criteria can be expressed as 32, ">32", B5, 32, "32", "apples", or TODAY(). - Important Any text criteria or any criteria that includes logical or mathematical symbols must be enclosed in double quotation marks ("). If the criteria is numeric, double quotation marks are not required. - sum_range Optional. The actual cells to add, if you want to add cells other than those specified in the range argument. If the sum_range argument is omitted, Excel adds the cells that are specified in the range argument - (the same cells to which the criteria is applied). - - The sum_range argument does not have to be the same size and shape as the range argument. - The actual cells that are added are determined by using theupper leftmost cell in the sum_range argument as the beginning cell, - and then including cells that correspond in size and shape to the range argument. - For example:If range is And sum_range is Then the actual cells are - A1:A5 B1:B5 B1:B5 - A1:A5 B1:B3 B1:B5 - A1:B4 C1:D4 C1:D4 - A1:B4 C1:C2 C1:D4 - - You can use the wildcard characters — the question mark (?) and asterisk (*) — as the criteria argument. - A question mark matches any single character; an asterisk matches any sequence of characters. - If you want to find an actual question mark or asterisk, type a tilde (~) preceding the character. - */ - protected static Ptg calcSumif(Ptg[] operands){ - try { - PtgArea range = Calculator.getRange(operands[0]); - PtgArea sum_range= null; - - try{ - Ptg criteria= operands[1]; - if (operands.length>2) { // see if has a sum_range; if not, source range is used for values as well as test - sum_range= Calculator.getRange(operands[2]); - } - // OK at this point should have criteria, range and, if necessary, sum_range - // algorithm: for each entry that meets the criterium in range, get the cell; - // if there is a sum_range, sum the values of those cells in the sum_range that correspond to the range - - // Parse the criteria string: can be a double, a comparison, a string with wildcards ... - // more info: You can use the wildcard characters — the question mark (?) and asterisk (*) — as the criteria argument. - // A question mark matches any single character; an asterisk matches any sequence of characters. - // If you want to find an actual question mark or asterisk, type a tilde (~) preceding the character. - String op= "="; // operator, default is = - String sCriteria= criteria.getString(); // criteria in string form - // strip operator, if any, and parse criteria - int j= Calculator.splitOperator(sCriteria); - if (j > 0) op= sCriteria.substring(0, j); // extract operator, if any - sCriteria= sCriteria.substring(j); - sCriteria= Calculator.translateWildcardsInCriteria(sCriteria); - - // stores the cells that pass the criteria expression and therefore will be summed up - ArrayList passesList= new ArrayList(); - - // test criteria for all cells in range, storing those cells (or sum_range cells) - // that pass in passesList - Ptg[] cells= range.getComponents(); - Ptg[] sumrangecells= null; - if (sum_range!=null) - sumrangecells= sum_range.getComponents(); - for (int i= 0; i < cells.length; i++) { - boolean passes= false; - try { - Object v= cells[i].getValue(); - passes= Calculator.compareCellValue(v, sCriteria, op); - } catch (Exception e) { // don't report error - // Logger.logErr("MathFunctionCalculator.calcSumif: error parsing " + e.toString()); // debugging only; take out when fully tested - } - if (passes) { - if (sumrangecells!=null) - passesList.add(sumrangecells[i]); - else - passesList.add(cells[i]); - } - } - - // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); - // Now we sum up the values of these cells and return - double ret= 0.0; - for (int i= 0; i < passesList.size(); i++) { - Ptg cell= (Ptg) passesList.get(i); - try { - ret+= cell.getDoubleVal(); - } catch (Exception e) { - Logger.logErr("MathFunctionCalculator.calcSumif: error obtaining cell value: " + e.toString()); // debugging only; take out when fully tested - ; // keep going - } - } - return new PtgNumber(ret); - }catch(Exception e){ - Logger.logWarn("could not calculate SUMIF function: " + e.toString()); - } - } catch (Exception e) {; } - return new PtgErr(PtgErr.ERROR_NULL); - } - - /** - * SUMIFS - * Adds the cells in a range (range: Two or more cells on a sheet. - * The cells in a range can be adjacent or nonadjacent.) that meet multiple criteria - * - * sum_range Required. One or more cells to sum, including numbers or names, ranges, or cell references (cell reference: The set of coordinates that a cell occupies on a worksheet. For example, the reference of the cell that appears at the intersection of column B and row 3 is B3.) that contain numbers. Blank and text values are ignored. - criteria_range1 Required. The first range in which to evaluate the associated criteria. - criteria1 Required. The criteria in the form of a number, expression, cell reference, or text that define which cells in the criteria_range1 argument will be added. For example, criteria can be expressed as 32, ">32", B4, "apples", or "32." - criteria_range2, criteria2, … Optional. Additional ranges and their associated criteria. Up to 127 range/criteria pairs are allowed. - - * @param operands - * @return - */ - protected static Ptg calcSumIfS(Ptg[] operands) { - try { - PtgArea sum_range= Calculator.getRange(operands[0]); - Ptg[] sumrangecells= sum_range.getComponents(); - String[] ops= new String[(operands.length-1)/2]; - String[] criteria= new String[(operands.length-1)/2]; - Ptg[][] criteria_cells= new Ptg[(operands.length-1)/2][]; - int j= 0; - for (int i= 1; i+1 < operands.length; i+=2) { - //criteria range - parse and get comprising cells - PtgArea cr= Calculator.getRange(operands[i]); - criteria_cells[j]= cr.getComponents(); - // each criteria_range must contain the same number of rows and columns as the sum_range - if (criteria_cells[j].length!=sumrangecells.length) - return new PtgErr(PtgErr.ERROR_VALUE); - // criteria for comparison, including operator - criteria[j]= operands[i+1].toString(); - // strip operator, if any, and parse criteria - ops[j]= "="; // operator, default is = - int k= Calculator.splitOperator(criteria[j]); - if (k > 0) ops[j]= criteria[j].substring(0, k); // extract operator, if any - criteria[j]= criteria[j].substring(k); - criteria[j]= Calculator.translateWildcardsInCriteria(criteria[j]); - j++; - } - - // test criteria for all cells in range, storing those corresponding sum_range cells - // that pass in passesList - // stores the cells that pass the criteria expression and therefore will be summed up - ArrayList passesList= new ArrayList(); - // for each set of criteria, test all cells in range and evaluate - // NOTE: this is an implicit AND evaluation - for (int i= 0; i < sumrangecells.length; i++) { - boolean passes= true; - for (int k= 0; k < criteria.length; k++) { - try { - Object v= criteria_cells[k][i].getValue(); - passes= Calculator.compareCellValue(v, criteria[k], ops[k]) && passes; - if (!passes) - break; // no need to continue - } catch (Exception e) { // don't report error - } - } - if (passes) { - passesList.add(sumrangecells[i]); - } - } - // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); - // Now we sum up the values of these cells and return - double ret= 0.0; - for (int i= 0; i < passesList.size(); i++) { - Ptg cell= (Ptg) passesList.get(i); - try { - ret+= cell.getDoubleVal(); - } catch (Exception e) { - Logger.logErr("MathFunctionCalculator.calcSumif: error obtaining cell value: " + e.toString()); // debugging only; take out when fully tested - ; // keep going - } - } - - return new PtgNumber(ret); - - } catch (Exception e) {; } - return new PtgErr(PtgErr.ERROR_NULL); - } -/** -SUMPRODUCT -Returns the sum of the products of corresponding array components -*/ -protected static Ptg calcSumproduct(Ptg[] operands){ - double res= 0; - int dim= 0; // all arrays must have same dimension see below - ArrayList arrays= new ArrayList(); - for (int i= 0; i < operands.length; i++) { - if (operands[i] instanceof PtgErr) return new PtgErr(PtgErr.ERROR_NA); // it's what excel does - Ptg[] a= operands[i].getComponents(); - if (a==null) { - arrays.add(operands[i]); - if (dim==0) { - dim= 1; - continue; - } else { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - if (dim==0) - dim= a.length; - else if (dim!=a.length) - return new PtgErr(PtgErr.ERROR_VALUE); - arrays.add(a); - } - for (int j= 0; j < dim; j++) { - double d= 1; - for (int i= 0; i < arrays.size(); i++) { - Object o= ((Ptg[]) arrays.get(i))[j].getValue(); - if (o instanceof Double) - d=d*((Double) o).doubleValue(); - else if (o instanceof Integer) - d=d*((Integer) o).intValue(); - else if (o instanceof Float) - d=d*((Float) o).floatValue(); - else - d= 0; // non-numeric values are treated as 0's - } - res+=d; - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} + /** + * SUMIF + * Adds the cells specified by a given criteria + *

                + * You use the SUMIF function to sum the values in a range that meet criteria that you specify. + * For example, suppose that in a column that contains numbers, you want to sum only the values that are larger than 5. You can use the following formula: + *

                + * =SUMIF(B2:B25,">5") + *

                + * you can apply the criteria to one range and sum the corresponding values in a different range. + * For example, the formula =SUMIF(B2:B5, "John", C2:C5) sums only the values in the range C2:C5, where the corresponding cells in the range B2:B5 equal "John." + *

                + * range Required. The range of cells that you want evaluated by criteria. Cells in each range must be numbers or names, arrays, or references that contain numbers. Blank and text values are ignored. + * criteria Required. The criteria in the form of a number, expression, a cell reference, text, or a function that defines which cells will be added. For example, criteria can be expressed as 32, ">32", B5, 32, "32", "apples", or TODAY(). + * Important Any text criteria or any criteria that includes logical or mathematical symbols must be enclosed in double quotation marks ("). If the criteria is numeric, double quotation marks are not required. + * sum_range Optional. The actual cells to add, if you want to add cells other than those specified in the range argument. If the sum_range argument is omitted, Excel adds the cells that are specified in the range argument + * (the same cells to which the criteria is applied). + *

                + * The sum_range argument does not have to be the same size and shape as the range argument. + * The actual cells that are added are determined by using theupper leftmost cell in the sum_range argument as the beginning cell, + * and then including cells that correspond in size and shape to the range argument. + * For example:If range is And sum_range is Then the actual cells are + * A1:A5 B1:B5 B1:B5 + * A1:A5 B1:B3 B1:B5 + * A1:B4 C1:D4 C1:D4 + * A1:B4 C1:C2 C1:D4 + *

                + * You can use the wildcard characters — the question mark (?) and asterisk (*) — as the criteria argument. + * A question mark matches any single character; an asterisk matches any sequence of characters. + * If you want to find an actual question mark or asterisk, type a tilde (~) preceding the character. + */ + protected static Ptg calcSumif(Ptg[] operands) { + try { + PtgArea range = Calculator.getRange(operands[0]); + PtgArea sum_range = null; + + try { + Ptg criteria = operands[1]; + if (operands.length > 2) { // see if has a sum_range; if not, source range is used for values as well as test + sum_range = Calculator.getRange(operands[2]); + } + // OK at this point should have criteria, range and, if necessary, sum_range + // algorithm: for each entry that meets the criterium in range, get the cell; + // if there is a sum_range, sum the values of those cells in the sum_range that correspond to the range + + // Parse the criteria string: can be a double, a comparison, a string with wildcards ... + // more info: You can use the wildcard characters — the question mark (?) and asterisk (*) — as the criteria argument. + // A question mark matches any single character; an asterisk matches any sequence of characters. + // If you want to find an actual question mark or asterisk, type a tilde (~) preceding the character. + String op = "="; // operator, default is = + String sCriteria = criteria.getString(); // criteria in string form + // strip operator, if any, and parse criteria + int j = Calculator.splitOperator(sCriteria); + if (j > 0) op = sCriteria.substring(0, j); // extract operator, if any + sCriteria = sCriteria.substring(j); + sCriteria = Calculator.translateWildcardsInCriteria(sCriteria); + + // stores the cells that pass the criteria expression and therefore will be summed up + ArrayList passesList = new ArrayList(); + + // test criteria for all cells in range, storing those cells (or sum_range cells) + // that pass in passesList + Ptg[] cells = range.getComponents(); + Ptg[] sumrangecells = null; + if (sum_range != null) + sumrangecells = sum_range.getComponents(); + for (int i = 0; i < cells.length; i++) { + boolean passes = false; + try { + Object v = cells[i].getValue(); + passes = Calculator.compareCellValue(v, sCriteria, op); + } catch (Exception e) { // don't report error + // Logger.logErr("MathFunctionCalculator.calcSumif: error parsing " + e.toString()); // debugging only; take out when fully tested + } + if (passes) { + if (sumrangecells != null) + passesList.add(sumrangecells[i]); + else + passesList.add(cells[i]); + } + } + + // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); + // Now we sum up the values of these cells and return + double ret = 0.0; + for (int i = 0; i < passesList.size(); i++) { + Ptg cell = (Ptg) passesList.get(i); + try { + ret += cell.getDoubleVal(); + } catch (Exception e) { + Logger.logErr("MathFunctionCalculator.calcSumif: error obtaining cell value: " + e.toString()); // debugging only; take out when fully tested + // keep going + } + } + return new PtgNumber(ret); + } catch (Exception e) { + Logger.logWarn("could not calculate SUMIF function: " + e.toString()); + } + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_NULL); + } + + /** + * SUMIFS + * Adds the cells in a range (range: Two or more cells on a sheet. + * The cells in a range can be adjacent or nonadjacent.) that meet multiple criteria + *

                + * sum_range Required. One or more cells to sum, including numbers or names, ranges, or cell references (cell reference: The set of coordinates that a cell occupies on a worksheet. For example, the reference of the cell that appears at the intersection of column B and row 3 is B3.) that contain numbers. Blank and text values are ignored. + * criteria_range1 Required. The first range in which to evaluate the associated criteria. + * criteria1 Required. The criteria in the form of a number, expression, cell reference, or text that define which cells in the criteria_range1 argument will be added. For example, criteria can be expressed as 32, ">32", B4, "apples", or "32." + * criteria_range2, criteria2, … Optional. Additional ranges and their associated criteria. Up to 127 range/criteria pairs are allowed. + * + * @param operands + * @return + */ + protected static Ptg calcSumIfS(Ptg[] operands) { + try { + PtgArea sum_range = Calculator.getRange(operands[0]); + Ptg[] sumrangecells = sum_range.getComponents(); + String[] ops = new String[(operands.length - 1) / 2]; + String[] criteria = new String[(operands.length - 1) / 2]; + Ptg[][] criteria_cells = new Ptg[(operands.length - 1) / 2][]; + int j = 0; + for (int i = 1; i + 1 < operands.length; i += 2) { + //criteria range - parse and get comprising cells + PtgArea cr = Calculator.getRange(operands[i]); + criteria_cells[j] = cr.getComponents(); + // each criteria_range must contain the same number of rows and columns as the sum_range + if (criteria_cells[j].length != sumrangecells.length) + return new PtgErr(PtgErr.ERROR_VALUE); + // criteria for comparison, including operator + criteria[j] = operands[i + 1].toString(); + // strip operator, if any, and parse criteria + ops[j] = "="; // operator, default is = + int k = Calculator.splitOperator(criteria[j]); + if (k > 0) ops[j] = criteria[j].substring(0, k); // extract operator, if any + criteria[j] = criteria[j].substring(k); + criteria[j] = Calculator.translateWildcardsInCriteria(criteria[j]); + j++; + } + + // test criteria for all cells in range, storing those corresponding sum_range cells + // that pass in passesList + // stores the cells that pass the criteria expression and therefore will be summed up + ArrayList passesList = new ArrayList(); + // for each set of criteria, test all cells in range and evaluate + // NOTE: this is an implicit AND evaluation + for (int i = 0; i < sumrangecells.length; i++) { + boolean passes = true; + for (int k = 0; k < criteria.length; k++) { + try { + Object v = criteria_cells[k][i].getValue(); + passes = Calculator.compareCellValue(v, criteria[k], ops[k]) && passes; + if (!passes) + break; // no need to continue + } catch (Exception e) { // don't report error + } + } + if (passes) { + passesList.add(sumrangecells[i]); + } + } + // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); + // Now we sum up the values of these cells and return + double ret = 0.0; + for (int i = 0; i < passesList.size(); i++) { + Ptg cell = (Ptg) passesList.get(i); + try { + ret += cell.getDoubleVal(); + } catch (Exception e) { + Logger.logErr("MathFunctionCalculator.calcSumif: error obtaining cell value: " + e.toString()); // debugging only; take out when fully tested + // keep going + } + } + + return new PtgNumber(ret); + + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_NULL); + } + + /** + * SUMPRODUCT + * Returns the sum of the products of corresponding array components + */ + protected static Ptg calcSumproduct(Ptg[] operands) { + double res = 0; + int dim = 0; // all arrays must have same dimension see below + ArrayList arrays = new ArrayList(); + for (int i = 0; i < operands.length; i++) { + if (operands[i] instanceof PtgErr) return new PtgErr(PtgErr.ERROR_NA); // it's what excel does + Ptg[] a = operands[i].getComponents(); + if (a == null) { + arrays.add(operands[i]); + if (dim == 0) { + dim = 1; + continue; + } else { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + if (dim == 0) + dim = a.length; + else if (dim != a.length) + return new PtgErr(PtgErr.ERROR_VALUE); + arrays.add(a); + } + for (int j = 0; j < dim; j++) { + double d = 1; + for (int i = 0; i < arrays.size(); i++) { + Object o = ((Ptg[]) arrays.get(i))[j].getValue(); + if (o instanceof Double) + d = d * ((Double) o).doubleValue(); + else if (o instanceof Integer) + d = d * ((Integer) o).intValue(); + else if (o instanceof Float) + d = d * ((Float) o).floatValue(); + else + d = 0; // non-numeric values are treated as 0's + } + res += d; + } + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } /* SUMSQ @@ -1289,117 +1316,119 @@ else if (o instanceof Float) Returns the sum of squares of differences of corresponding values in two arrays */ -/** -TAN -Returns the tangent of a number -*/ -protected static Ptg calcTan(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.tan(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; -} -/** -TANH -Returns the hyperbolic tangent of a number -*/ -protected static Ptg calcTanh(Ptg[] operands) { - if (operands.length != 1)return PtgCalculator.getNAError(); - double x= 0.0; - try { - x= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - double A= Math.pow(Math.E, x); - double B= Math.pow(Math.E, -x); - double result= (A-B)/(A+B); - PtgNumber pnum= new PtgNumber(result); - return pnum; -} + /** + * TAN + * Returns the tangent of a number + */ + protected static Ptg calcTan(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr + double res = Math.tan(dd); + PtgNumber ptnum = new PtgNumber(res); + return ptnum; + } -/** -TRUNC -Truncates a number to an integer -*/ -protected static Ptg calcTrunc(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getNAError(); - double dd= 0.0; - try { - dd= operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - int res = (int)dd; - PtgInt pint = new PtgInt(res); - return pint; -} -/* - * - * These are some helper methods for the more brutal of the math functions - */ + /** + * TANH + * Returns the hyperbolic tangent of a number + */ + protected static Ptg calcTanh(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double x = 0.0; + try { + x = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + double A = Math.pow(Math.E, x); + double B = Math.pow(Math.E, -x); + double result = (A - B) / (A + B); + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * TRUNC + * Truncates a number to an integer + */ + protected static Ptg calcTrunc(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getNAError(); + double dd = 0.0; + try { + dd = operands[0].getDoubleVal(); + } catch (NumberFormatException e) { + return PtgCalculator.getValueError(); + } + if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr + int res = (int) dd; + PtgInt pint = new PtgInt(res); + return pint; + } + + /* + * + * These are some helper methods for the more brutal of the math functions + */ //helper for asinh -private static double getAcosh(double x){ - return Math.log( x+(1.0+x)*Math.sqrt((x-1.0)/(x+1.0)) ); -} + private static double getAcosh(double x) { + return Math.log(x + (1.0 + x) * Math.sqrt((x - 1.0) / (x + 1.0))); + } -// factorial helper -public static long factorial(long n) { - long result; - if (n <= 1) { - result = 1; // 1! is 1 - } - // The recursive part - else { - result = n; - long partial = factorial(n-1); - result = result * partial; - } - return result; - } -/* - * Step factoral calculates a factorial of a number - * the number of steps specified - * - */ + // factorial helper + public static long factorial(long n) { + long result; + if (n <= 1) { + result = 1; // 1! is 1 + } + // The recursive part + else { + result = n; + long partial = factorial(n - 1); + result = result * partial; + } + return result; + } + + /* + * Step factoral calculates a factorial of a number + * the number of steps specified + * + */ //Combin helper, steps factorials -private static long stepFactorial(long n, int numsteps){ - long result = n; - if (n < numsteps) return -1; - while (numsteps > 1){ - long partial = n-1; - result = partial*result; - n--; - numsteps--; - - } - return result; -} + private static long stepFactorial(long n, int numsteps) { + long result = n; + if (n < numsteps) return -1; + while (numsteps > 1) { + long partial = n - 1; + result = partial * result; + n--; + numsteps--; + + } + return result; + } + + // double factorial helper + private static long doubleFactorial(long n) { + long result; + if (n <= 1) { + result = 1; // 1! is 1 + } else if (n == 2) { + result = 2; + } + // The recursive part + else { + result = n; + long partial = factorial(n - 2); + result = result * partial; + } + return result; + } -// double factorial helper -private static long doubleFactorial(long n){ - long result; - if (n <= 1) { - result = 1; // 1! is 1 - } - else if (n == 2){ - result = 2; - } - // The recursive part - else { - result = n; - long partial = factorial(n-2); - result = result * partial; - } - return result; - } - } diff --git a/src/main/java/io/starter/formats/XLS/formulas/Ptg.java b/src/main/java/io/starter/formats/XLS/formulas/Ptg.java index 7b21506..325f6a1 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/Ptg.java +++ b/src/main/java/io/starter/formats/XLS/formulas/Ptg.java @@ -2,316 +2,345 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; -import java.io.Serializable; - -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.FormulaNotFoundException; -import io.starter.formats.XLS.XLSConstants; -import io.starter.formats.XLS.XLSRecord; -import io.starter.formats.XLS.FunctionNotSupportedException; +import io.starter.formats.XLS.*; -/** +import java.io.Serializable; - Ptg is the interface all ptgs implement in order to be handled equally under the - eyes of the all seeing one, "he that shall not be named" A ptg is a unique segment - of a formula stack that indicates a value, a reference to a value, or an operation. - See the docs under Formula for more information. +/** + * Ptg is the interface all ptgs implement in order to be handled equally under the + * eyes of the all seeing one, "he that shall not be named" A ptg is a unique segment + * of a formula stack that indicates a value, a reference to a value, or an operation. + * See the docs under Formula for more information. + * * @see Ptg * @see Formula - -*/ + */ -public interface Ptg extends XLSConstants, Serializable{ +public interface Ptg extends XLSConstants, Serializable { /** * VALUE type Reference (Id=0x44) */ - public static short VALUE= 0; + short VALUE = 0; /** * REFERENCE type Reference (Id=0x24) */ - public static short REFERENCE= 1; + short REFERENCE = 1; /** * ARRAY type Reference (Id=0x64) */ - public static short ARRAY= 2; - - public static int PTG_LOCATION_POLICY_UNLOCKED = 0; - public static int PTG_LOCATION_POLICY_LOCKED = 1; - public static int PTG_LOCATION_POLICY_TRACK = 2; - - - public static int PTG_TYPE_SINGLE = 1; // single-byte record - public static int PTG_TYPE_ARRAY = 2; // array of bytes record - - //ptg lengths - public static int PTG_NUM_LENGTH = 9; - public static int PTG_ADD_LENGTH = 1; - public static int PTG_AREA_LENGTH = 9; - public static int PTG_AREA3D_LENGTH =11; - public static int PTG_AREAERR3D_LENGTH =11; - public static int PTG_ATR_LENGTH = 4; - public static int PTG_CONCAT_LENGTH = 1; - public static int PTG_DIV_LENGTH = 1; - public static int PTG_EQ_LENGTH = 1; - public static int PTG_EXP_LENGTH = 5; - public static int PTG_FUNC_LENGTH = 3; - public static int PTG_FUNCVAR_LENGTH = 4; - public static int PTG_GE_LENGTH = 1; - public static int PTG_GT_LENGTH = 1; - public static int PTG_INT_LENGTH = 3; - public static int PTG_ISECT_LENGTH = 1; - public static int PTG_LE_LENGTH = 1; - public static int PTG_LT_LENGTH = 1; - public static int PTG_MEMERR_LENGTH = 7; - public static int PTG_MEM_AREA_N_LENGTH = 7; - public static int PTG_MEM_AREA_NV_LENGTH = 7; - public static int PTG_MLT_LENGTH = 1; - public static int PTG_MYSTERY_LENGTH = 1; - public static int PTG_NE_LENGTH = 1; - public static int PTG_NAME_LENGTH = 5; - public static int PTG_NAMEX_LENGTH = 7; - public static int PTG_PAREN_LENGTH = 1; - public static int PTG_POWER_LENGTH = 1; - public static int PTG_RANGE_LENGTH = 1; - public static int PTG_REF_LENGTH = 5; - public static int PTG_REF3D_LENGTH = 7; - public static int PTG_REFERR_LENGTH = 5; - public static int PTG_REFERR3D_LENGTH = 7; - public static int PTG_ENDSHEET_LENGTH = 1; - public static int PTG_SUB_LENGTH = 1; - public static int PTG_UNION_LENGTH = 1; - public static int PTG_BOOL_LENGTH = 2; - public static int PTG_UPLUS_LENGTH = 1; - public static int PTG_UMINUS_LENGTH = 1; - public static int PTG_PERCENT_LENGTH =1; - - - //TODO: add all the opcodes here - public static byte PTG_INT = 0x1e; - - /** Creates a deep clone of this Ptg. - */ - public Object clone(); - - /** constructor must pass in 'parent' XLSRecord so that there - * is a handle for updating... - * - * @return - */ - void setParentRec(XLSRecord x); - - /** update the values of the Ptg - * - * - */ - void updateRecord(); - - /** return the length of the Ptg - */ - int getLength(); - - /** return the number of parameters to this Ptg - */ - int getNumParams(); - - public static final int CALCULATED = 0; - public static final int UNCALCULATED = -1; - - /** if the Ptg needs to keep a handle to a cell, this is it... - * tells the Ptg to get it on its own... - */ - public void updateAddressFromTrackerCell(); - - /** if the Ptg needs to keep a handle to a cell, this is it... - * tells the Ptg to get it on its own... - */ - public void initTrackerCell(); - - /** if the Ptg needs to keep a handle to a cell, this is it... + short ARRAY = 2; + + int PTG_LOCATION_POLICY_UNLOCKED = 0; + int PTG_LOCATION_POLICY_LOCKED = 1; + int PTG_LOCATION_POLICY_TRACK = 2; + + + int PTG_TYPE_SINGLE = 1; // single-byte record + int PTG_TYPE_ARRAY = 2; // array of bytes record + + //ptg lengths + int PTG_NUM_LENGTH = 9; + int PTG_ADD_LENGTH = 1; + int PTG_AREA_LENGTH = 9; + int PTG_AREA3D_LENGTH = 11; + int PTG_AREAERR3D_LENGTH = 11; + int PTG_ATR_LENGTH = 4; + int PTG_CONCAT_LENGTH = 1; + int PTG_DIV_LENGTH = 1; + int PTG_EQ_LENGTH = 1; + int PTG_EXP_LENGTH = 5; + int PTG_FUNC_LENGTH = 3; + int PTG_FUNCVAR_LENGTH = 4; + int PTG_GE_LENGTH = 1; + int PTG_GT_LENGTH = 1; + int PTG_INT_LENGTH = 3; + int PTG_ISECT_LENGTH = 1; + int PTG_LE_LENGTH = 1; + int PTG_LT_LENGTH = 1; + int PTG_MEMERR_LENGTH = 7; + int PTG_MEM_AREA_N_LENGTH = 7; + int PTG_MEM_AREA_NV_LENGTH = 7; + int PTG_MLT_LENGTH = 1; + int PTG_MYSTERY_LENGTH = 1; + int PTG_NE_LENGTH = 1; + int PTG_NAME_LENGTH = 5; + int PTG_NAMEX_LENGTH = 7; + int PTG_PAREN_LENGTH = 1; + int PTG_POWER_LENGTH = 1; + int PTG_RANGE_LENGTH = 1; + int PTG_REF_LENGTH = 5; + int PTG_REF3D_LENGTH = 7; + int PTG_REFERR_LENGTH = 5; + int PTG_REFERR3D_LENGTH = 7; + int PTG_ENDSHEET_LENGTH = 1; + int PTG_SUB_LENGTH = 1; + int PTG_UNION_LENGTH = 1; + int PTG_BOOL_LENGTH = 2; + int PTG_UPLUS_LENGTH = 1; + int PTG_UMINUS_LENGTH = 1; + int PTG_PERCENT_LENGTH = 1; + + + //TODO: add all the opcodes here + byte PTG_INT = 0x1e; + + /** + * Creates a deep clone of this Ptg. + */ + Object clone(); + + /** + * constructor must pass in 'parent' XLSRecord so that there + * is a handle for updating... + * + * @return + */ + void setParentRec(XLSRecord x); + + /** + * update the values of the Ptg + */ + void updateRecord(); + + /** + * return the length of the Ptg + */ + int getLength(); + + /** + * return the number of parameters to this Ptg + */ + int getNumParams(); + + int CALCULATED = 0; + int UNCALCULATED = -1; + + /** + * if the Ptg needs to keep a handle to a cell, this is it... + * tells the Ptg to get it on its own... + */ + void updateAddressFromTrackerCell(); + + /** + * if the Ptg needs to keep a handle to a cell, this is it... + * tells the Ptg to get it on its own... + */ + void initTrackerCell(); + + /** + * if the Ptg needs to keep a handle to a cell, this is it... + * * @return trackercell The trackercell to set. */ - public BiffRec getTrackercell(); - - /**if the Ptg needs to keep a handle to a cell, this is it... + BiffRec getTrackercell(); + + /** + * if the Ptg needs to keep a handle to a cell, this is it... + * * @param trackercell The trackercell to set. */ - public void setTrackercell(BiffRec trackercell) ; - - /** a locking mechanism so that Ptgs are not endlessly - * re-calculated - * - * @return - */ - int getLock(); - - /** a locking mechanism so that Ptgs are not endlessly - * re-calculated - * - * @return - */ - void setLock(int x); - - - /** determine the general Ptg type - */ - boolean getIsOperator(); - boolean getIsBinaryOperator(); - boolean getIsUnaryOperator(); - boolean getIsStandAloneOperator(); - boolean getIsOperand(); - boolean getIsControl(); - boolean getIsFunction(); - boolean getIsReference(); - - /** Operator Ptgs take other Ptgs as arguments - so we need to pass them in to get a meaningful - value. - */ - void setVars(Ptg[] parr); - - /** determines whether this operator is a 'primitive' such as +,-,=,<,>,!=,==,etc. - the upshot is that primitives go BETWEEN operands, and non-primitives - encapsulate - - ie: - - SUM(A1:A4) non-primitive - A1+A4 primitive - - */ - boolean getIsPrimitiveOperator(); - - /* - Determines whether the ptg represents multiple ptg's in reality. - ie ptgArea ia actually a collection of ptgRef's, so ptgArea.getIsArray returns 'true' - */ - boolean getIsArray(); - - /** return the human-readable String representation of - this ptg -- if applicable - */ - String getTextString(); - - /** pass in arbitrary number of values (probably other Ptgs) - and return the resultant value. - - This effectively calculates the Expression. - */ - Object evaluate(Object[] obj); - - /** - If a record consists of multiple sub records (ie PtgArea) return those - records, else return null; - */ - Ptg[] getComponents(); - /** - - @return byte[] containing the whole ptg, including identifying opcode - */ - byte[] getRecord(); - - /* - @return XLSRecord containing the whole ptg - */ - XLSRecord getParentRec(); - - /** returns whether the Location of the Ptg is locked - * used during automated BiffRec movement updates - * - * @return location policy - */ - int getLocationPolicy(); - - /** lock the Location of the Ptg so that it will not - * be updated during automated BiffRec movement updates - * - * @param b whether to lock the location of this Ptg - */ - void setLocationPolicy(int b); - - /** - setLocation moves a ptg that is a reference to a location, such as - a ptg range being modified - @param String location, such as A1:D4 - */ - void setLocation(String s); - - /** - When the ptg is a reference to a location this returns that location - @return Location - */ - String getLocation() throws FormulaNotFoundException; - - int[] getIntLocation() throws FormulaNotFoundException; - - /** return the human-readable String representation of - this ptg -- if applicable - */ - public String getString(); - - /** return the byte header for the Ptg - */ - public byte getOpcode(); - - /** return the human-readable String representation of - the "closing" portion of this Ptg - such as a closing parenthesis. - */ - public String getString2(); - - /** - return a Ptg consisting of the calculated values - of the ptg's passed in. Returns null for any non-operater - ptg. - * @throws CalculationException - */ - public Ptg calculatePtg(Ptg[] parsething) throws FunctionNotSupportedException, CalculationException; - - /** Gets the (return) value of this Ptg as an operand Ptg. - */ - public Ptg getPtgVal(); - - /** - returns the value of an operand ptg. - @return null for non-operand Ptg. - */ - public Object getValue(); - - /** + void setTrackercell(BiffRec trackercell); + + /** + * a locking mechanism so that Ptgs are not endlessly + * re-calculated + * + * @return + */ + int getLock(); + + /** + * a locking mechanism so that Ptgs are not endlessly + * re-calculated + * + * @return + */ + void setLock(int x); + + + /** + * determine the general Ptg type + */ + boolean getIsOperator(); + + boolean getIsBinaryOperator(); + + boolean getIsUnaryOperator(); + + boolean getIsStandAloneOperator(); + + boolean getIsOperand(); + + boolean getIsControl(); + + boolean getIsFunction(); + + boolean getIsReference(); + + /** + * Operator Ptgs take other Ptgs as arguments + * so we need to pass them in to get a meaningful + * value. + */ + void setVars(Ptg[] parr); + + /** + * determines whether this operator is a 'primitive' such as +,-,=,<,>,!=,==,etc. + * the upshot is that primitives go BETWEEN operands, and non-primitives + * encapsulate + *

                + * ie: + *

                + * SUM(A1:A4) non-primitive + * A1+A4 primitive + */ + boolean getIsPrimitiveOperator(); + + /* + Determines whether the ptg represents multiple ptg's in reality. + ie ptgArea ia actually a collection of ptgRef's, so ptgArea.getIsArray returns 'true' + */ + boolean getIsArray(); + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + String getTextString(); + + /** + * pass in arbitrary number of values (probably other Ptgs) + * and return the resultant value. + *

                + * This effectively calculates the Expression. + */ + Object evaluate(Object[] obj); + + /** + * If a record consists of multiple sub records (ie PtgArea) return those + * records, else return null; + */ + Ptg[] getComponents(); + + /** + * @return byte[] containing the whole ptg, including identifying opcode + */ + byte[] getRecord(); + + /* + @return XLSRecord containing the whole ptg + */ + XLSRecord getParentRec(); + + /** + * returns whether the Location of the Ptg is locked + * used during automated BiffRec movement updates + * + * @return location policy + */ + int getLocationPolicy(); + + /** + * lock the Location of the Ptg so that it will not + * be updated during automated BiffRec movement updates + * + * @param b whether to lock the location of this Ptg + */ + void setLocationPolicy(int b); + + /** + * setLocation moves a ptg that is a reference to a location, such as + * a ptg range being modified + * + * @param String location, such as A1:D4 + */ + void setLocation(String s); + + /** + * When the ptg is a reference to a location this returns that location + * + * @return Location + */ + String getLocation() throws FormulaNotFoundException; + + int[] getIntLocation() throws FormulaNotFoundException; + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + String getString(); + + /** + * return the byte header for the Ptg + */ + byte getOpcode(); + + /** + * return the human-readable String representation of + * the "closing" portion of this Ptg + * such as a closing parenthesis. + */ + String getString2(); + + /** + * return a Ptg consisting of the calculated values + * of the ptg's passed in. Returns null for any non-operater + * ptg. + * + * @throws CalculationException + */ + Ptg calculatePtg(Ptg[] parsething) throws FunctionNotSupportedException, CalculationException; + + /** + * Gets the (return) value of this Ptg as an operand Ptg. + */ + Ptg getPtgVal(); + + /** + * returns the value of an operand ptg. + * + * @return null for non-operand Ptg. + */ + Object getValue(); + + /** * Gets the value of the ptg represented as an int. - * + *

                * This can result in loss of precision for floating point values. - * + *

                * -1 will be returned for values that are not translateable to an integer - * + * * @return integer representing the ptg, or NAN */ - public int getIntVal(); - public double getDoubleVal(); - - public boolean isBlank(); // 20081112 KSC - public void close(); - + int getIntVal(); + + double getDoubleVal(); + + boolean isBlank(); // 20081112 KSC + + void close(); + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.java b/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.java index 4627f93..34f74f1 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,9 +24,6 @@ import java.lang.reflect.Array; -import io.starter.toolkit.Logger; - - /* Ptg that is an addition operand @@ -36,93 +33,104 @@ */ -public class PtgAdd extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -964400139336259946L; +public class PtgAdd extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -964400139336259946L; - public boolean getIsOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - - public PtgAdd(){ - ptgId = 0x3; - record = new byte[1]; - record[0] = 0x3; + public boolean getIsOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; } - /** return the human-readable String representation of - */ - public String getString(){ + + public PtgAdd() { + ptgId = 0x3; + record = new byte[1]; + record[0] = 0x3; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return "+"; } + public String toString() { return this.getString(); } - public int getLength(){ + + public int getLength() { return PTG_ADD_LENGTH; } /* Operator specific calculate method, this one adds two values. - - */ - public Ptg calculatePtg(Ptg[] form){ + + */ + public Ptg calculatePtg(Ptg[] form) { try { - Object[] o= super.getValuesFromPtgs(form); - if (o==null) return new PtgErr(PtgErr.ERROR_VALUE); - if (!o[0].getClass().isArray()) { - if (o.length != 2){ - return new PtgErr(PtgErr.ERROR_VALUE); - } - double o0= 0, o1= 0; - try { - o0= getDoubleValue(o[0], this.parent_rec); - o1= getDoubleValue(o[1], this.parent_rec); - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - double returnVal = o0 + o1; - PtgNumber n = new PtgNumber(returnVal); - return n; - } else { // handle array fomulas - String retArry= ""; - int nArrays= java.lang.reflect.Array.getLength(o); - if (nArrays!=2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals= java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i= 0; i < nArrays-1; i+=2) { - Object secondOp= null; - boolean comparitorIsArray= o[i+1].getClass().isArray(); - if (!comparitorIsArray) secondOp= o[i+1]; - for (int j= 0; j < nVals; j++) { - Object firstOp= Array.get(o[i],j); // first array index j - if (comparitorIsArray) - secondOp= Array.get(o[i+1],j); // second array index j - double o0= 0, o1= 0; - try { - o0= getDoubleValue(firstOp, this.parent_rec); - o1= getDoubleValue(secondOp, this.parent_rec); - } catch (NumberFormatException e) { - retArry= retArry + "#VALUE!" + ","; - continue; - } - double retVal= o0+o1; - retArry= retArry + retVal + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } + Object[] o = getValuesFromPtgs(form); + if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); + if (!o[0].getClass().isArray()) { + if (o.length != 2) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + double o0 = 0, o1 = 0; + try { + o0 = getDoubleValue(o[0], this.parent_rec); + o1 = getDoubleValue(o[1], this.parent_rec); + } catch (NumberFormatException e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + double returnVal = o0 + o1; + PtgNumber n = new PtgNumber(returnVal); + return n; + } else { // handle array fomulas + String retArry = ""; + int nArrays = java.lang.reflect.Array.getLength(o); + if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); + int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays + for (int i = 0; i < nArrays - 1; i += 2) { + Object secondOp = null; + boolean comparitorIsArray = o[i + 1].getClass().isArray(); + if (!comparitorIsArray) secondOp = o[i + 1]; + for (int j = 0; j < nVals; j++) { + Object firstOp = Array.get(o[i], j); // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j); // second array index j + double o0 = 0, o1 = 0; + try { + o0 = getDoubleValue(firstOp, this.parent_rec); + o1 = getDoubleValue(secondOp, this.parent_rec); + } catch (NumberFormatException e) { + retArry = retArry + "#VALUE!" + ","; + continue; + } + double retVal = o0 + o1; + retArry = retArry + retVal + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } - }catch(NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - }catch(Exception e) { // handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } + } catch (NumberFormatException e) { + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } catch (Exception e) { // handle error ala Excel + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgArea.java b/src/main/java/io/starter/formats/XLS/formulas/PtgArea.java index cbcf14b..0b4f977 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgArea.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgArea.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,73 +25,69 @@ import io.starter.OpenXLS.CellHandle; import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.*; - -import java.util.*; - -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.FormulaNotFoundException; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.XLSRecord; -import io.starter.formats.XLS.Formula; import io.starter.toolkit.FastAddVector; import io.starter.toolkit.Logger; +import java.util.Vector; -/** - - ptgArea is a reference to an area (rectangle) of cells. - Essentially it is a collection of two ptgRef's, so it will be - treated that way in the code... - -

                -    Offset      Size    Contents
                -    ----------------------------------------------------
                -	0			2 		Index to first row (065535) or offset of first row (method [B], -3276832767)
                -	2 			2 		Index to last row (065535) or offset of last row (method [B], -3276832767)
                -	4 			2 		Index to first column or offset of first column, with relative flags (see table above)
                -	6 			2 		Index to last column or offset of last column, with relative flags (see table above)
                -
                -    Only the low-order 14 bits specify the Col, the other bits specify
                -    relative vs absolute for both the col or the row.
                -
                -    Bits        Mask        Name    Contents
                -    -----------------------------------------------------
                -    15          8000h       fRwRel  =1 if row offset relative, 
                -                                    =0 if otherwise
                -    14          4000h       fColRel =1 if row offset relative,
                -                                    =0 if otherwise
                -    13-0        3FFFh       col     Ordinal column offset or number
                -    
                +/** + * ptgArea is a reference to an area (rectangle) of cells. + * Essentially it is a collection of two ptgRef's, so it will be + * treated that way in the code... + * + *
                + * Offset      Size    Contents
                + * ----------------------------------------------------
                + * 0			2 		Index to first row (065535) or offset of first row (method [B], -3276832767)
                + * 2 			2 		Index to last row (065535) or offset of last row (method [B], -3276832767)
                + * 4 			2 		Index to first column or offset of first column, with relative flags (see table above)
                + * 6 			2 		Index to last column or offset of last column, with relative flags (see table above)
                + *
                + * Only the low-order 14 bits specify the Col, the other bits specify
                + * relative vs absolute for both the col or the row.
                + *
                + * Bits        Mask        Name    Contents
                + * -----------------------------------------------------
                + * 15          8000h       fRwRel  =1 if row offset relative,
                + * =0 if otherwise
                + * 14          4000h       fColRel =1 if row offset relative,
                + * =0 if otherwise
                + * 13-0        3FFFh       col     Ordinal column offset or number
                + * 
                + * * @see Ptg * @see Formula - - */ -public class PtgArea extends PtgRef implements Ptg{ - +public class PtgArea extends PtgRef implements Ptg { + public static final long serialVersionUID = 666555444333222l; - public boolean getIsOperand(){return true;} - public boolean getIsReference(){return true;} + + public boolean getIsOperand() { + return true; + } + + public boolean getIsReference() { + return true; + } protected PtgRef firstPtg; protected PtgRef lastPtg; - + /* constructor, takes the array of the ptgRef, including the identifier so we do not need to figure it out again later... */ - public void init(byte[] b){ - locax=null; // cache reset + public void init(byte[] b) { + locax = null; // cache reset ptgId = b[0]; record = b; this.populateVals(); } - + /* Throw this data into two ptgref's */ - public void populateVals(){ + public void populateVals() { byte[] temp1 = new byte[5]; byte[] temp2 = new byte[5]; temp1[0] = 0x24; @@ -100,37 +96,38 @@ public void populateVals(){ System.arraycopy(record, 5, temp1, 3, 2); System.arraycopy(record, 3, temp2, 1, 2); System.arraycopy(record, 7, temp2, 3, 2); - try { + try { getSheetName(); // 20080212 KSC: } catch (WorkSheetNotFoundException we) { - Logger.logErr(we); - } - firstPtg = new PtgRef(temp1, parent_rec, false); // don't add to ref tracker as it's part of area - firstPtg.sheetname= sheetname; - - lastPtg = new PtgRef(temp2,parent_rec, false); // don't add to ref tracker as it's part of area - lastPtg.sheetname= sheetname; - setWholeRowCol(); - this.hashcode= getHashCode(); - } - - /** Returns all of the cells of this range as PtgRef's. - This includes empty cells, values, formulas, etc. - Note the setting of parent-rec requires finding the cell - the PtgRef refer's to. If that is null then the PtgRef - will exist, just with a null value. This could cause issues when - programatically populating cells. - */ -public Ptg[] getComponents(){ - Vector v = new Vector(); - try{ + Logger.logErr(we); + } + firstPtg = new PtgRef(temp1, parent_rec, false); // don't add to ref tracker as it's part of area + firstPtg.sheetname = sheetname; + + lastPtg = new PtgRef(temp2, parent_rec, false); // don't add to ref tracker as it's part of area + lastPtg.sheetname = sheetname; + setWholeRowCol(); + this.hashcode = getHashCode(); + } + + /** + * Returns all of the cells of this range as PtgRef's. + * This includes empty cells, values, formulas, etc. + * Note the setting of parent-rec requires finding the cell + * the PtgRef refer's to. If that is null then the PtgRef + * will exist, just with a null value. This could cause issues when + * programatically populating cells. + */ + public Ptg[] getComponents() { + Vector v = new Vector(); + try { // TODO: check rc sanity here - int startcol = -1; - int startrow = -1; - int endrow = -1; - int endcol = -1; - int[] startloc = null; - int[] endloc = null; + int startcol = -1; + int startrow = -1; + int endrow = -1; + int endcol = -1; + int[] startloc = null; + int[] endloc = null; /*if (this.wholeRow) { startcol= 0; @@ -141,364 +138,370 @@ public Ptg[] getComponents(){ startcol= endcol= firstPtg.col; endrow= this.getSheet().getMaxRow(); } */ - if(firstPtg!=null){ - startloc = firstPtg.getRealIntLocation(); - startcol = startloc[1]; - startrow = startloc[0]; - }else{ - startloc = ExcelTools.getRangeRowCol(locax); - startcol = startloc[1]; - startrow = startloc[0]; - } - - if(lastPtg!=null){ - endloc = lastPtg.getRealIntLocation(); - endcol = endloc[1]; - endrow = endloc[0]; - }else{ - endloc = ExcelTools.getRangeRowCol(locax); - endcol = endloc[3]; - endrow = endloc[2]; - } - - // usually don't need to set sheet on setlocation becuase uses parent_rec's sheet - // cases of named range or if location sheet does not = parent_rec sheet, set sheet explicitly - String sht= null; // usual case, don't need to set sheet - Boundsheet sh= parent_rec.getSheet(); - if (sh==null || (this.sheetname!=null && !this.sheetname.equals(sh.getSheetName()))) { - if (sh==null || !GenericPtg.qualifySheetname(this.sheetname).equals(GenericPtg.qualifySheetname(sh.getSheetName()))) - sht= this.sheetname + "!"; - } - // loop through the cols - for (;startcol<=endcol;startcol++){ - // loop through the rows inside - int rowholder = startrow; - for (;rowholder<=endrow;rowholder++){ - String displaycol = ExcelTools.getAlphaVal(startcol); - int displayrow = rowholder+1; - PtgRef pref; - if (sht==null) - pref = new PtgRef(displaycol+displayrow, parent_rec, false); - else - pref = new PtgRef(sht + displaycol+displayrow, parent_rec, false); - v.add(pref); + if (firstPtg != null) { + startloc = firstPtg.getRealIntLocation(); + startcol = startloc[1]; + startrow = startloc[0]; + } else { + startloc = ExcelTools.getRangeRowCol(locax); + startcol = startloc[1]; + startrow = startloc[0]; + } + + if (lastPtg != null) { + endloc = lastPtg.getRealIntLocation(); + endcol = endloc[1]; + endrow = endloc[0]; + } else { + endloc = ExcelTools.getRangeRowCol(locax); + endcol = endloc[3]; + endrow = endloc[2]; } + + // usually don't need to set sheet on setlocation becuase uses parent_rec's sheet + // cases of named range or if location sheet does not = parent_rec sheet, set sheet explicitly + String sht = null; // usual case, don't need to set sheet + Boundsheet sh = parent_rec.getSheet(); + if (sh == null || (this.sheetname != null && !this.sheetname.equals(sh.getSheetName()))) { + if (sh == null || !GenericPtg.qualifySheetname(this.sheetname).equals(GenericPtg.qualifySheetname(sh.getSheetName()))) + sht = this.sheetname + "!"; + } + // loop through the cols + for (; startcol <= endcol; startcol++) { + // loop through the rows inside + int rowholder = startrow; + for (; rowholder <= endrow; rowholder++) { + String displaycol = ExcelTools.getAlphaVal(startcol); + int displayrow = rowholder + 1; + PtgRef pref; + if (sht == null) + pref = new PtgRef(displaycol + displayrow, parent_rec, false); + else + pref = new PtgRef(sht + displaycol + displayrow, parent_rec, false); + v.add(pref); + } + } + } catch (Exception e) { + Logger.logErr("calculating formula range value failed.", e); } - }catch (Exception e){ - Logger.logErr("calculating formula range value failed.", e); + PtgRef[] pref = new PtgRef[v.size()]; + v.toArray(pref); + return pref; } - PtgRef[] pref = new PtgRef[v.size()]; - v.toArray(pref); - return pref; -} - - /** returns the row/col ints for the ref - * + + /** + * returns the row/col ints for the ref + *

                * Format is FirstRow,FirstCol,LastRow,LastCol - * - * @return - */ - public int[] getRowCol(){ - if(firstPtg==null){ - return null; - } - if(lastPtg==null && firstPtg!=null){ + * + * @return + */ + public int[] getRowCol() { + if (firstPtg == null) { + return null; + } + if (lastPtg == null && firstPtg != null) { int[] rc1 = firstPtg.getRowCol(); - int[] ret = {rc1[0],rc1[1],rc1[0],rc1[1]}; - return ret; - } + int[] ret = {rc1[0], rc1[1], rc1[0], rc1[1]}; + return ret; + } int[] rc1 = firstPtg.getRowCol(); int[] rc2 = lastPtg.getRowCol(); - int[] ret = {rc1[0],rc1[1],rc2[0],rc2[1]}; - return ret; - } - - /** returns whether this CellRange Contains a Cell - * + int[] ret = {rc1[0], rc1[1], rc2[0], rc2[1]}; + return ret; + } + + /** + * returns whether this CellRange Contains a Cell + * * @param the cell to test * @return whether the cell is in the range */ - public boolean contains(CellHandle ch){ + public boolean contains(CellHandle ch) { String chsheet = ch.getWorkSheetName(); String mysheet = ""; - if(this.getParentRec()!=null) { + if (this.getParentRec() != null) { BiffRec b = this.getParentRec(); - if (b.getSheet()!=null) { + if (b.getSheet() != null) { mysheet = b.getSheet().getSheetName(); } } - if(!chsheet.equalsIgnoreCase(mysheet)) - return false; + if (!chsheet.equalsIgnoreCase(mysheet)) + return false; String adr = ch.getCellAddress(); // FIX broken COLROW int[] rc = ExcelTools.getRowColFromString(adr); return contains(rc); - } - - /** check to see if the sheet and row/col are contained - * in this ref - * + } + + /** + * check to see if the sheet and row/col are contained + * in this ref + * * @param sheetname * @param rc * @return */ - public boolean contains(String sn, int[] rc){ - if(sheetname==null) { - try{ - sheetname = this.getSheetName(); - }catch(Exception e){ - ; - } + public boolean contains(String sn, int[] rc) { + if (sheetname == null) { + try { + sheetname = this.getSheetName(); + } catch (Exception e) { + } } - if(!sn.equalsIgnoreCase(sheetname)) - return false; + if (!sn.equalsIgnoreCase(sheetname)) + return false; return contains(rc); } - - /** returns whether this PtgArea Contains the specified row/col coordinate - * - * - [0] = firstrow - [1] = firstcol - [2] = lastrow - [3] = lastcol - * + + /** + * returns whether this PtgArea Contains the specified row/col coordinate + *

                + *

                + * [0] = firstrow + * [1] = firstcol + * [2] = lastrow + * [3] = lastcol + * * @param the rc coordinates to test * @return whether the coordinates are in the range */ public boolean contains(int[] rc) { int[] thisRange = this.getIntLocation(); // test the first rc - if(rc[0] < thisRange[0])return false; // row above the first ref row? - if(rc[0] > thisRange[2])return false; // row after the last ref row? - - if(rc[1] < thisRange[1])return false; // col before the first ref col? - if(rc[1] > thisRange[3])return false; // col after the last ref col? - - return true; + if (rc[0] < thisRange[0]) return false; // row above the first ref row? + if (rc[0] > thisRange[2]) return false; // row after the last ref row? + + if (rc[1] < thisRange[1]) return false; // col before the first ref col? + return rc[1] <= thisRange[3]; // col after the last ref col? } - - - // private byte[] PROTOTYPE_BYTES = {0x25, 0, 0, 0, 0, 0, 0, 0, 0}; - - /** return the human-readable String representation of - this ptg -- if applicable - */ - public String getString(){ - return this.getLocation(); + + + // private byte[] PROTOTYPE_BYTES = {0x25, 0, 0, 0, 0, 0, 0, 0, 0}; + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + public String getString() { + return this.getLocation(); } + /* * Creates a new PtgArea. The parent rec is needed * as getting a value goes to the boundsheet to determine * values. The parent rec *must* be on the same sheet * as the PtgArea referenced! */ - public PtgArea(String range, XLSRecord parent){ + public PtgArea(String range, XLSRecord parent) { this(range, parent, true); } - + /** * Creates a new PtgArea from 2 component ptgs, used by shared formula * to create ptgareas. ptg1 should be upperleft corner, ptg2 bottomright */ - public PtgArea(PtgRef ptg1, PtgRef ptg2, XLSRecord parent){ - this(); + public PtgArea(PtgRef ptg1, PtgRef ptg2, XLSRecord parent) { + this(); firstPtg = ptg1; lastPtg = ptg2; parent_rec = parent; - this.hashcode= getHashCode(); + this.hashcode = getHashCode(); this.updateRecord(); } - + /* * Creates a new PtgArea. The parent rec is needed * as getting a value goes to the boundsheet to determine * values. The parent rec *must* be on the same sheet * as the PtgArea referenced! - * + * * relativeRefs = true is excel default */ - public PtgArea(String range, XLSRecord parent, boolean relativeRefs){ + public PtgArea(String range, XLSRecord parent, boolean relativeRefs) { this(); int[] loc = ExcelTools.getRangeRowCol(range); int[] temp = new int[2]; temp[0] = loc[0]; temp[1] = loc[1]; - String res = ExcelTools.formatLocation(temp,relativeRefs,relativeRefs); + String res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs); firstPtg = new PtgRef(res, parent, false); temp[0] = loc[2]; temp[1] = loc[3]; - res = ExcelTools.formatLocation(temp,relativeRefs,relativeRefs); + res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs); lastPtg = new PtgRef(res, parent, false); setWholeRowCol(); parent_rec = parent; - this.hashcode= getHashCode(); - this.updateRecord(); + this.hashcode = getHashCode(); + this.updateRecord(); } /* - * Creates a new PtgArea using an int array as [r,c,r1,c1]. + * Creates a new PtgArea using an int array as [r,c,r1,c1]. * The parent rec is needed * as getting a value goes to the boundsheet to determine * values. The parent rec *must* be on the same sheet * as the PtgArea referenced! - * + * * relativeRefs = true is excel default */ public PtgArea(int[] loc, XLSRecord parent, boolean relativeRefs) { int[] temp = new int[2]; temp[0] = loc[0]; temp[1] = loc[1]; - String res = ExcelTools.formatLocation(temp,relativeRefs,relativeRefs); - firstPtg = new PtgRef(res, parent, false); + String res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs); + firstPtg = new PtgRef(res, parent, false); temp[0] = loc[2]; temp[1] = loc[3]; - res = ExcelTools.formatLocation(temp,relativeRefs,relativeRefs); + res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs); lastPtg = new PtgRef(res, parent, false); setWholeRowCol(); parent_rec = parent; - this.hashcode= getHashCode(); - this.updateRecord(); + this.hashcode = getHashCode(); + this.updateRecord(); } + /** * set the wholeRow and/or wholeCol flag for this PtgArea * for ranges such as: * $B:$B and $5:%9 */ public void setWholeRowCol() { - if (firstPtg.rw<=1 && lastPtg.wholeCol) // TODO: inconsistencies in 0-based or 1-based rows - this.wholeCol= true; - this.wholeRow= lastPtg.wholeRow; - if (this.wholeCol) - useReferenceTracker= false; + if (firstPtg.rw <= 1 && lastPtg.wholeCol) // TODO: inconsistencies in 0-based or 1-based rows + this.wholeCol = true; + this.wholeRow = lastPtg.wholeRow; + if (this.wholeCol) + useReferenceTracker = false; } + /* * Default constructor */ - public PtgArea(){ - record = new byte[9]; - ptgId = 0x25; - record[0] = 0x25; + public PtgArea() { + record = new byte[9]; + ptgId = 0x25; + record[0] = 0x25; } - + public PtgArea(boolean useReferenceTracker) { - this(); - this.useReferenceTracker= useReferenceTracker; + this(); + this.useReferenceTracker = useReferenceTracker; } - + /** * set the Ptg Id type to one of: * VALUE, REFERENCE or Array * 25H (tAreaR), 45H (tAreaV), 65H (tAreaA) - *
                The Ptg type is important for certain + *
                The Ptg type is important for certain * functions which require a specific type of operand */ - public void setPtgType(short type) { - switch (type) { - case VALUE: - ptgId= 0x45; - break; - case REFERENCE: - ptgId= 0x25; - break; - case Ptg.ARRAY: - ptgId= 0x65; - break; - } - record[0]= ptgId; - } - - - public String toString(){ - String ret = getString(); - - if( getParentRec() !=null) - if(ret.indexOf("!")<0) - try { // Catch WorkSheetNotFoundException to handle Unresolved External refs - getSheetName(); - if (sheetname!=null) - ret = sheetname + "!" + ret; - } catch (WorkSheetNotFoundException we) { - Logger.logErr(we); - } - return ret; - } - - public void setParentRec(XLSRecord rec){ - super.setParentRec(rec); - // 20080221 KSC: just set parent_rec super.setParentRec(rec); - if(firstPtg!=null) - firstPtg.setParentRec(parent_rec); - if(lastPtg!=null) - lastPtg.setParentRec(parent_rec); - } - - + public void setPtgType(short type) { + switch (type) { + case VALUE: + ptgId = 0x45; + break; + case REFERENCE: + ptgId = 0x25; + break; + case Ptg.ARRAY: + ptgId = 0x65; + break; + } + record[0] = ptgId; + } + + + public String toString() { + String ret = getString(); + + if (getParentRec() != null) + if (ret.indexOf("!") < 0) + try { // Catch WorkSheetNotFoundException to handle Unresolved External refs + getSheetName(); + if (sheetname != null) + ret = sheetname + "!" + ret; + } catch (WorkSheetNotFoundException we) { + Logger.logErr(we); + } + return ret; + } + + public void setParentRec(XLSRecord rec) { + super.setParentRec(rec); + // 20080221 KSC: just set parent_rec super.setParentRec(rec); + if (firstPtg != null) + firstPtg.setParentRec(parent_rec); + if (lastPtg != null) + lastPtg.setParentRec(parent_rec); + } + + /* Set the location of this PtgRef. This takes a location such as "a14:b15" */ - public void setLocation(String address){ - String s[]= ExcelTools.stripSheetNameFromRange(address); - setLocation(s); - this.hashcode= getHashCode(); - } - - /** - * set Ptg to parsed location - * @param loc String[] sheet1, range, sheet2, exref1, exref2 - */ - public void setLocation(String[] loc) { - locax=null; // cache reset - if(firstPtg==null){ - this.record = new byte[] {0x25, 0, 0, 0, 0, 0, 0, 0, 0}; - - if(this.getParentRec()!=null)this.populateVals(); - } else if (this.useReferenceTracker) - this.removeFromRefTracker(); + public void setLocation(String address) { + String[] s = ExcelTools.stripSheetNameFromRange(address); + setLocation(s); + this.hashcode = getHashCode(); + } + + /** + * set Ptg to parsed location + * + * @param loc String[] sheet1, range, sheet2, exref1, exref2 + */ + public void setLocation(String[] loc) { + locax = null; // cache reset + if (firstPtg == null) { + this.record = new byte[]{0x25, 0, 0, 0, 0, 0, 0, 0, 0}; + + if (this.getParentRec() != null) this.populateVals(); + } else if (this.useReferenceTracker) + this.removeFromRefTracker(); int i = loc[1].indexOf(":"); // handle single cell addresses as: A1:A1 - if (i==-1) { - loc[1]= loc[1] + ":" + loc[1]; - i = loc[1].indexOf(":"); + if (i == -1) { + loc[1] = loc[1] + ":" + loc[1]; + i = loc[1].indexOf(":"); } String firstloc = loc[1].substring(0, i); - String lastloc = loc[1].substring(i+1); - if (loc[0]!=null) - firstloc= loc[0]+"!" + firstloc; - if (loc[2]!=null) - lastloc= loc[2]+"!" + lastloc; - if (loc[3]!=null) // 20090325 KSC: store OOXML External References - firstloc= loc[3]+firstloc; - if (loc[4]!=null) // 20090325 KSC: store OOXML External References - lastloc= loc[4]+lastloc; - + String lastloc = loc[1].substring(i + 1); + if (loc[0] != null) + firstloc = loc[0] + "!" + firstloc; + if (loc[2] != null) + lastloc = loc[2] + "!" + lastloc; + if (loc[3] != null) // 20090325 KSC: store OOXML External References + firstloc = loc[3] + firstloc; + if (loc[4] != null) // 20090325 KSC: store OOXML External References + lastloc = loc[4] + lastloc; + // TODO: do we need to remove refs from tracker? - firstPtg.setParentRec(this.getParentRec()); - lastPtg.setParentRec(this.getParentRec()); + firstPtg.setParentRec(this.getParentRec()); + lastPtg.setParentRec(this.getParentRec()); - firstPtg.setUseReferenceTracker(false); + firstPtg.setUseReferenceTracker(false); lastPtg.setUseReferenceTracker(false); firstPtg.setLocation(firstloc); lastPtg.setLocation(lastloc); - setWholeRowCol(); - this.hashcode= getHashCode(); + setWholeRowCol(); + this.hashcode = getHashCode(); this.updateRecord(); - if (this.useReferenceTracker) {// check of boolean useReferenceTracker - if(!this.getIsWholeCol() && !this.getIsWholeRow()) - this.addToRefTracker(); - else - useReferenceTracker= false; + if (this.useReferenceTracker) {// check of boolean useReferenceTracker + if (!this.getIsWholeCol() && !this.getIsWholeRow()) + this.addToRefTracker(); + else + useReferenceTracker = false; } - } - - /** - returns the location of the ptg as an array of ints. - [0] = firstRow - [1] = firstCol - [2] = lastRow - [3] = lastCol - */ + } + + /** + * returns the location of the ptg as an array of ints. + * [0] = firstRow + * [1] = firstCol + * [2] = lastRow + * [3] = lastCol + */ public int[] getIntLocation() { int[] first = firstPtg.getIntLocation(); int[] last = lastPtg.getIntLocation(); @@ -506,118 +509,118 @@ public int[] getIntLocation() { System.arraycopy(first, 0, returning, 0, 2); System.arraycopy(last, 0, returning, 2, 2); return returning; - } + } - - /** Set the location of this PtgArea. This takes a location - such as {1,2,3,4} - */ - public void setLocation(int[] rowcol) { - locax=null; // cache reset - if(firstPtg==null){ - //this.record = new byte[] {0x25, 0, 0, 0, 0, 0, 0, 0, 0}; -- don't as can be called from PtgArea3d - if(this.getParentRec()!=null)this.populateVals(); - } else if (this.useReferenceTracker) - this.removeFromRefTracker(); - + /** + * Set the location of this PtgArea. This takes a location + * such as {1,2,3,4} + */ + public void setLocation(int[] rowcol) { + locax = null; // cache reset + if (firstPtg == null) { + //this.record = new byte[] {0x25, 0, 0, 0, 0, 0, 0, 0, 0}; -- don't as can be called from PtgArea3d + if (this.getParentRec() != null) this.populateVals(); + } else if (this.useReferenceTracker) + this.removeFromRefTracker(); + // TODO: do we need to remove refs from tracker? - firstPtg.setParentRec(this.getParentRec()); - firstPtg.setSheetName(sheetname); - lastPtg.setParentRec(this.getParentRec()); - lastPtg.setSheetName(sheetname); + firstPtg.setParentRec(this.getParentRec()); + firstPtg.setSheetName(sheetname); + lastPtg.setParentRec(this.getParentRec()); + lastPtg.setSheetName(sheetname); - firstPtg.setUseReferenceTracker(false); + firstPtg.setUseReferenceTracker(false); lastPtg.setUseReferenceTracker(false); firstPtg.setLocation(rowcol); - int[] rc= new int[2]; - rc[0]= rowcol[2]; - rc[1]= rowcol[3]; + int[] rc = new int[2]; + rc[0] = rowcol[2]; + rc[1] = rowcol[3]; lastPtg.setLocation(rc); - - this.hashcode= getHashCode(); + + this.hashcode = getHashCode(); this.updateRecord(); - if (this.useReferenceTracker) // check of boolean useReferenceTracker - this.addToRefTracker(); - } - + if (this.useReferenceTracker) // check of boolean useReferenceTracker + this.addToRefTracker(); + } + /* Returns the location of the Ptg as a string */ - public String getLocation(){ - String lc = getLocationHelper(); - locax = lc; - return lc; - } - - private String getLocationHelper(){ - //String loc= null; - if (firstPtg == null || lastPtg == null){ - this.populateVals(); - if (firstPtg == null || lastPtg == null) // we tried! - throw new AssertionError("PtgArea.getLocationHelper null ptgs"); + public String getLocation() { + String lc = getLocationHelper(); + locax = lc; + return lc; + } + + private String getLocationHelper() { + //String loc= null; + if (firstPtg == null || lastPtg == null) { + this.populateVals(); + if (firstPtg == null || lastPtg == null) // we tried! + throw new AssertionError("PtgArea.getLocationHelper null ptgs"); } String s = firstPtg.getLocation(); String y = lastPtg.getLocation(); - - String loc1[]= ExcelTools.stripSheetNameFromRange(s); // sheet, addr - String loc2[]= ExcelTools.stripSheetNameFromRange(y); // sheet, addr - - String sh1= loc1[0]; - String sh2= loc2[0]; - String addr1= loc1[1]; - String addr2= loc2[1]; - - if(!(this instanceof PtgArea3d)){ - //if (addr1.equals(addr2)) // this is proper but makes so many assertions fail, revert for now - //return addr2; + + String[] loc1 = ExcelTools.stripSheetNameFromRange(s); // sheet, addr + String[] loc2 = ExcelTools.stripSheetNameFromRange(y); // sheet, addr + + String sh1 = loc1[0]; + String sh2 = loc2[0]; + String addr1 = loc1[1]; + String addr2 = loc2[1]; + + if (!(this instanceof PtgArea3d)) { + //if (addr1.equals(addr2)) // this is proper but makes so many assertions fail, revert for now + //return addr2; return addr1 + ":" + addr2; } - if (sh1==null)sh1= sheetname; - if (sh1==null) // no sheetname avail - return addr1 + ":" + addr2; + if (sh1 == null) sh1 = sheetname; + if (sh1 == null) // no sheetname avail + return addr1 + ":" + addr2; // handle OOXML external references - if (externalLink1>0) - sh1= "[" + externalLink1 + "]" + sh1; - if (externalLink2>0 && sh2!=null) - sh2= "[" + externalLink2 + "]" + sh2; + if (externalLink1 > 0) + sh1 = "[" + externalLink1 + "]" + sh1; + if (externalLink2 > 0 && sh2 != null) + sh2 = "[" + externalLink2 + "]" + sh2; + + sh1 = qualifySheetname(sh1); - sh1= qualifySheetname(sh1); - // have sheetname if (sh1.equals(sh2)) { // range is in one sheet - if (!sh1.equals("")) { - if (!addr1.equals(addr2)) - return sh1 + "!" + addr1 + ":" + addr2; - else - return sh1 + "!" + addr1; - } else if (sheetname!=null) { // both sheets in sub-ptgs are null - sh1= sheetname; + if (!sh1.equals("")) { + if (!addr1.equals(addr2)) + return sh1 + "!" + addr1 + ":" + addr2; + else + return sh1 + "!" + addr1; + } else if (sheetname != null) { // both sheets in sub-ptgs are null + sh1 = sheetname; // 20090325 KSC: handle OOXML external references - if (externalLink1>0) - sh1= "[" + externalLink1 + "]" + sh1; - sh1= qualifySheetname(sh1); - if (!addr1.equals(addr2)) // 20081215 KSC: - return sh1 + "!" + addr1 + ":" + addr2; - return sh1 + "!" + addr1; - } - } else if (sh2==null) { // only 1 sheetnaame specified - if (!addr1.equals(addr2)) // 20081215 KSC: - return sh1 + "!" + addr1 + ":" + addr2; - return sh1 + "!" + addr1; + if (externalLink1 > 0) + sh1 = "[" + externalLink1 + "]" + sh1; + sh1 = qualifySheetname(sh1); + if (!addr1.equals(addr2)) // 20081215 KSC: + return sh1 + "!" + addr1 + ":" + addr2; + return sh1 + "!" + addr1; + } + } else if (sh2 == null) { // only 1 sheetnaame specified + if (!addr1.equals(addr2)) // 20081215 KSC: + return sh1 + "!" + addr1 + ":" + addr2; + return sh1 + "!" + addr1; } // otherwise, include both sheets in return string - sh2= qualifySheetname(sh2); + sh2 = qualifySheetname(sh2); return sh1 + ":" + sh2 + "!" + addr1 + ":" + addr2; - } - + } + /* Updates the record bytes so it can be pulled back out. - */ - public void updateRecord(){ - locax=null; // cache reset - int[] pols = {firstPtg.getLocationPolicy(), lastPtg.getLocationPolicy()}; + */ + public void updateRecord() { + locax = null; // cache reset + int[] pols = {firstPtg.getLocationPolicy(), lastPtg.getLocationPolicy()}; byte[] first = firstPtg.getRecord(); byte[] last = lastPtg.getRecord(); // the last record has an extra identifier on it. @@ -629,20 +632,20 @@ public void updateRecord(){ System.arraycopy(last, 3, newrecord, 7, 2); record = newrecord; // this.populateVals(); - if(parent_rec!=null) { - if(this.parent_rec instanceof Formula) - ((Formula)this.parent_rec).updateRecord(); - else if(this.parent_rec instanceof Name) - ((Name)this.parent_rec).updatePtgs(); + if (parent_rec != null) { + if (this.parent_rec instanceof Formula) + ((Formula) this.parent_rec).updateRecord(); + else if (this.parent_rec instanceof Name) + ((Name) this.parent_rec).updatePtgs(); } firstPtg.setLocationPolicy(pols[0]); - lastPtg.setLocationPolicy(pols[1]); + lastPtg.setLocationPolicy(pols[1]); } - - public int getLength(){ + + public int getLength() { return PTG_AREA_LENGTH; } - + /* returns the sum of all fields within this range ** this may need to be modified, as we might not always want sum's @@ -655,61 +658,61 @@ public int getLength(){ functions, or if the forced array class state is set, it is changed to array class. In all other cases (cell type formula without forced array class), value class is retained. */ - public Object getValue(){ + public Object getValue() { // 20080214 KSC: underlying cells may have changed ...if(refCell==null) refCell = this.getRefCells(); - Object returnval= new Double(0); + Object returnval = new Double(0); String retstr = null; - String array= ""; - boolean isArray= (this.parent_rec instanceof Array); - for(int t=0;t1) { // 20090817 KSC: [BugTracker 2683] - array= "{" + array.substring(0, array.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(array); - return pa; + if (isArray && array != null && array.length() > 1) { // 20090817 KSC: [BugTracker 2683] + array = "{" + array.substring(0, array.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(array); + return pa; } - if(retstr!=null) - return retstr; - - return returnval; //new Double(returnval); + if (retstr != null) + return retstr; + + return returnval; //new Double(returnval); } - - + + /* Returns all of the cells of this range as PtgRef's. This includes empty cells, values, formulas, etc. @@ -718,167 +721,170 @@ public Object getValue(){ will exist, just with a null value. This could cause issues when programatically populating cells. */ - public Ptg[] getColComponents(int colNum){ - if (colNum < 0) return null; - String lu = this.toString(); - Object p = parent_rec.getWorkBook().getRefTracker().getVlookups().get(lu); - - if(p!=null) { - PtgArea par = (PtgArea)p; - Ptg[] ret = (Ptg[])par.getParentRec().getWorkBook() - .getRefTracker().getLookupColCache().get(lu+":"+Integer.valueOf(colNum)); - if(ret!=null) - return ret; - } - - PtgRef[] v = null; - try{ + public Ptg[] getColComponents(int colNum) { + if (colNum < 0) return null; + String lu = this.toString(); + Object p = parent_rec.getWorkBook().getRefTracker().getVlookups().get(lu); + + if (p != null) { + PtgArea par = (PtgArea) p; + Ptg[] ret = (Ptg[]) par.getParentRec().getWorkBook() + .getRefTracker().getLookupColCache().get(lu + ":" + Integer.valueOf(colNum)); + if (ret != null) + return ret; + } + + PtgRef[] v = null; + try { // TODO: check rc sanity here - int[] startloc = firstPtg.getRealIntLocation(); - int startcol = colNum; // startloc[0]; - int startrow = startloc[0]; - int[] endloc = lastPtg.getRealIntLocation(); - int endrow = endloc[0]; - // error trap - if (endrow-1)sht = sht.substring(0,sht.indexOf("!")); - for (;rowholder<=endrow;rowholder++){ + if (sht.indexOf("!") > -1) sht = sht.substring(0, sht.indexOf("!")); + for (; rowholder <= endrow; rowholder++) { String displaycol = ExcelTools.getAlphaVal(startcol); - int displayrow = rowholder+1; - String loc = sht+ "!" + displaycol+displayrow; - - PtgRef pref = new PtgRef(loc, parent_rec, this.useReferenceTracker); + int displayrow = rowholder + 1; + String loc = sht + "!" + displaycol + displayrow; + + PtgRef pref = new PtgRef(loc, parent_rec, this.useReferenceTracker); - v[pos++] = pref; + v[pos++] = pref; } - }catch (Exception e){ - Logger.logErr("Getting column range in PtgArea failed.", e); - } - - // cache - parent_rec.getWorkBook().getRefTracker().getVlookups().put(this.toString(),this); - parent_rec.getWorkBook().getRefTracker().getLookupColCache().put(lu+":"+Integer.valueOf(colNum),v); - return v; - } + } catch (Exception e) { + Logger.logErr("Getting column range in PtgArea failed.", e); + } + + // cache + parent_rec.getWorkBook().getRefTracker().getVlookups().put(this.toString(), this); + parent_rec.getWorkBook().getRefTracker().getLookupColCache().put(lu + ":" + Integer.valueOf(colNum), v); + return v; + } + /** * return the ptg components for a certain column within a ptgArea() + * * @param rowNum * @return all Ptg's within colNum */ - public Ptg[] getRowComponents(int rowNum){ + public Ptg[] getRowComponents(int rowNum) { FastAddVector v = new FastAddVector(); - Ptg[] allComponents = this.getComponents(); - for(int i=0;ilastPtg (example in named ranges in tcr_formatted_2007.xlsm) + int numcols = endcol - startcol; + if (numcols < 0) + numcols = startcol - endcol; // 20090521 KSC: may have range switched so that firstPtg>lastPtg (example in named ranges in tcr_formatted_2007.xlsm) numcols++; - int numrows= endrow-startrow; - if (numrows<0) numrows= startrow-endrow; // 20090521 KSC: may have range switched so that firstPtg>lastPtg (example in named ranges in tcr_formatted_2007.xlsm) + int numrows = endrow - startrow; + if (numrows < 0) + numrows = startrow - endrow; // 20090521 KSC: may have range switched so that firstPtg>lastPtg (example in named ranges in tcr_formatted_2007.xlsm) numrows++; - int totcell= numcols*numrows; - if(totcell==0)totcell++; + int totcell = numcols * numrows; + if (totcell == 0) totcell++; if (totcell < 0) { - Logger.logErr("PtgArea.getRefCells. Error in Ptg locations: " + firstPtg.toString() + ":" + lastPtg.toString()); - totcell= 0; + Logger.logErr("PtgArea.getRefCells. Error in Ptg locations: " + firstPtg.toString() + ":" + lastPtg.toString()); + totcell = 0; } refCell = new BiffRec[totcell]; - int rowctr = 0; + int rowctr = 0; // 20090521 KSC: try to handle both cases i.e. ranges such that firstlast - if (startcol. @@ -22,325 +22,338 @@ */ package io.starter.formats.XLS.formulas; -import java.util.ArrayList; - +import io.starter.OpenXLS.CellHandle; +import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.*; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; -import io.starter.OpenXLS.CellHandle; -import io.starter.OpenXLS.ExcelTools; - -/** +import java.util.ArrayList; - ptgArea3d is a reference to an area (rectangle) of cells. - Essentially it is a collection of two ptgRef's, so it will be - treated that way in the code... - implies external sheet ref (rather than ptgarea) - -

                -    Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0           ixti        2       index into the Externsheet record
                -    2           rwFirst     2       The First row of the reference 
                -    4           rwLast      2       The Last row of the reference 
                -    6           grbitColFirst   2       (see following table)
                -    8           grbitColLast    2       (see following table)
                -
                -    Only the low-order 14 bits specify the Col, the other bits specify
                -    relative vs absolute for both the col or the row.
                -
                -    Bits        Mask        Name    Contents
                -    -----------------------------------------------------
                -    15          8000h       fRwRel  =1 if row offset relative, 
                -                                    =0 if otherwise
                -    14          4000h       fColRel =1 if row offset relative,
                -                                    =0 if otherwise
                -    13-0        3FFFh       col     Ordinal column offset or number
                -    
                - - For 3D references, the tokens contain a negative EXTERNSHEET index, indicating a reference into the own workbook. - The absolute value is the one-based index of the EXTERNSHEET record that contains the name of the first sheet. The - tokens additionally contain absolute indexes of the first and last referenced sheet. These indexes are independent of the - EXTERNSHEET record list. If the referenced sheets do not exist anymore, these indexes contain the value FFFFH (3D - reference to a deleted sheet), and an EXTERNSHEET record with the special name <04H> (own document) is used. - Each external reference contains the positive one-based index to an EXTERNSHEET record containing the URL of the - external document and the name of the sheet used. The sheet index fields of the tokens are not used. - - is the above correct?? Documentation sez different!!!!! +/** + * ptgArea3d is a reference to an area (rectangle) of cells. + * Essentially it is a collection of two ptgRef's, so it will be + * treated that way in the code... + * implies external sheet ref (rather than ptgarea) + * + *
                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0           ixti        2       index into the Externsheet record
                + * 2           rwFirst     2       The First row of the reference
                + * 4           rwLast      2       The Last row of the reference
                + * 6           grbitColFirst   2       (see following table)
                + * 8           grbitColLast    2       (see following table)
                + *
                + * Only the low-order 14 bits specify the Col, the other bits specify
                + * relative vs absolute for both the col or the row.
                + *
                + * Bits        Mask        Name    Contents
                + * -----------------------------------------------------
                + * 15          8000h       fRwRel  =1 if row offset relative,
                + * =0 if otherwise
                + * 14          4000h       fColRel =1 if row offset relative,
                + * =0 if otherwise
                + * 13-0        3FFFh       col     Ordinal column offset or number
                + * 
                + *

                + * For 3D references, the tokens contain a negative EXTERNSHEET index, indicating a reference into the own workbook. + * The absolute value is the one-based index of the EXTERNSHEET record that contains the name of the first sheet. The + * tokens additionally contain absolute indexes of the first and last referenced sheet. These indexes are independent of the + * EXTERNSHEET record list. If the referenced sheets do not exist anymore, these indexes contain the value FFFFH (3D + * reference to a deleted sheet), and an EXTERNSHEET record with the special name <04H> (own document) is used. + * Each external reference contains the positive one-based index to an EXTERNSHEET record containing the URL of the + * external document and the name of the sheet used. The sheet index fields of the tokens are not used. + *

                + * is the above correct?? Documentation sez different!!!!! + * * @see Ptg * @see Formula - - */ -public class PtgArea3d extends PtgArea implements Ptg, IxtiListener{ +public class PtgArea3d extends PtgArea implements Ptg, IxtiListener { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1176168076050592292L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1176168076050592292L; + + public boolean getIsOperand() { + return true; + } + + public boolean getIsReference() { + return true; + } - public boolean getIsOperand(){return true;} - public boolean getIsReference(){return true;} boolean quoted = false; public short ixti; - private boolean isExternalRef= false; // true if this ptg area is a reference in another workbook - private Ptg[] comps = null; - - /** return the human-readable String representation of - this ptg -- if applicable - */ - public String getString(){ - try{ - if (this.getIsWholeCol() || this.getIsWholeRow()) { // handle non-standard ranges i.e. $B:$C or $1:$3 - String s = firstPtg.getLocation(); - String y = lastPtg.getLocation(); - - String loc1[]= ExcelTools.stripSheetNameFromRange(s); // sheet, addr - String loc2[]= ExcelTools.stripSheetNameFromRange(y); // sheet, addr - if (this.getIsWholeCol()) { - int i= loc1[1].length(); - if (Character.isDigit(loc1[1].charAt(i-1))) - while (Character.isDigit(loc1[1].charAt(--i))) ; - loc1[1]= loc1[1].substring(0,i); - i= loc2[1].length(); - if (Character.isDigit(loc2[1].charAt(i-1))) - while (Character.isDigit(loc2[1].charAt(--i))) ; - loc2[1]= loc2[1].substring(0,i); - } else if (this.getIsWholeRow()) { - int i= 0; - while (!Character.isDigit(loc1[1].charAt(i++))) ; - loc1[1]= "$" + loc1[1].substring(i-1); - i= 0; - while (!Character.isDigit(loc2[1].charAt(i++))); - loc2[1]= "$" + loc2[1].substring(i-1); - } - sheetname= qualifySheetname(sheetname); - - return sheetname + "!" + loc1[1] + ":" + loc2[1]; - } - // otherwise, - return getLocation(); - }catch(Exception e){ - ;// is ok if new ptg... - return null; - } - } - - /** - * link to the externsheet to be automatically updated upon removals - */ + private boolean isExternalRef = false; // true if this ptg area is a reference in another workbook + private Ptg[] comps = null; + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + public String getString() { + try { + if (this.getIsWholeCol() || this.getIsWholeRow()) { // handle non-standard ranges i.e. $B:$C or $1:$3 + String s = firstPtg.getLocation(); + String y = lastPtg.getLocation(); + + String[] loc1 = ExcelTools.stripSheetNameFromRange(s); // sheet, addr + String[] loc2 = ExcelTools.stripSheetNameFromRange(y); // sheet, addr + if (this.getIsWholeCol()) { + int i = loc1[1].length(); + if (Character.isDigit(loc1[1].charAt(i - 1))) + while (Character.isDigit(loc1[1].charAt(--i))) ; + loc1[1] = loc1[1].substring(0, i); + i = loc2[1].length(); + if (Character.isDigit(loc2[1].charAt(i - 1))) + while (Character.isDigit(loc2[1].charAt(--i))) ; + loc2[1] = loc2[1].substring(0, i); + } else if (this.getIsWholeRow()) { + int i = 0; + while (!Character.isDigit(loc1[1].charAt(i++))) ; + loc1[1] = "$" + loc1[1].substring(i - 1); + i = 0; + while (!Character.isDigit(loc2[1].charAt(i++))) ; + loc2[1] = "$" + loc2[1].substring(i - 1); + } + sheetname = qualifySheetname(sheetname); + + return sheetname + "!" + loc1[1] + ":" + loc2[1]; + } + // otherwise, + return getLocation(); + } catch (Exception e) { + // is ok if new ptg... + return null; + } + } + + /** + * link to the externsheet to be automatically updated upon removals + */ public void addListener() { try { getParentRec().getWorkBook().getExternSheet().addPtgListener(this); - }catch(Exception e) { + } catch (Exception e) { // no need to output here. NullPointer occurs when a ref has an invalid ixti, such as when a sheet was removed Worksheet exception could never really happen. } } - - public String toString(){ - String ret = getString(); - return ret; - } - - /** - * create new PtgArea3d - */ - public PtgArea3d(){ - ptgId= 0x3b; - record= new byte[11]; - record[0]= ptgId; // "" + + public String toString() { + String ret = getString(); + return ret; + } + + /** + * create new PtgArea3d + */ + public PtgArea3d() { + ptgId = 0x3b; + record = new byte[11]; + record[0] = ptgId; // "" this.is3dRef = true; - } - - public PtgArea3d(boolean useReferenceTracker) { - this(); - this.useReferenceTracker= useReferenceTracker; - } - + } + + public PtgArea3d(boolean useReferenceTracker) { + this(); + this.useReferenceTracker = useReferenceTracker; + } + /** * set the Ptg Id type to one of: * VALUE, REFERENCE or Array * 25H (tAreaR), 45H (tAreaV), 65H (tAreaA) - *
                The Ptg type is important for certain + *
                The Ptg type is important for certain * functions which require a specific type of operand */ - public void setPtgType(short type) { - switch (type) { - case VALUE: - ptgId= 0x5B; - break; - case REFERENCE: - ptgId= 0x3B; - break; - case Ptg.ARRAY: - ptgId= 0x7B; - break; - } - record[0]= ptgId; - } - + public void setPtgType(short type) { + switch (type) { + case VALUE: + ptgId = 0x5B; + break; + case REFERENCE: + ptgId = 0x3B; + break; + case Ptg.ARRAY: + ptgId = 0x7B; + break; + } + record[0] = ptgId; + } + /** * @return Returns the ixti. */ - public short getIxti() { // only valid for 3d refs!!!! + public short getIxti() { // only valid for 3d refs!!!! return ixti; } - - + + + /** + * set the pointer into the Externsheet Rec. + * this is only valid for 3d refs + */ + public void setIxti(short ixf) { + if (ixti != ixf) { + ixti = ixf; + // this seems to be only one byte... + if (record != null) { + record[1] = (byte) ixf; + populateVals(); // add listener is done here + } + } + } + + /** + * return true if this PtgArea3d is an external reference + * i.e. defined in another, external workbook + * + * @return + */ + public boolean isExternalRef() { + return isExternalRef; + } + + /** + * return the first sheet referenced + * + * @return + */ + public Boundsheet getFirstSheet() { + if (parent_rec != null) { + WorkBook wb = parent_rec.getWorkBook(); + if (sheetname != null) + try { + return wb.getWorkSheetByName(sheetname); + } catch (WorkSheetNotFoundException e) { + // fall thru -- see sheet copy operations -- appears correct + } + if (wb != null && wb.getExternSheet() != null) { + Boundsheet[] bsa = wb.getExternSheet().getBoundSheets(this.ixti); + if (bsa != null) + return bsa[0]; + } + } + return null; + } + /** - * set the pointer into the Externsheet Rec. - * this is only valid for 3d refs + * return the last sheet referenced + * + * @return */ - public void setIxti(short ixf){ - if (ixti!=ixf) { - ixti = ixf; - // this seems to be only one byte... - if (record != null){ - record[1] = (byte)ixf; - populateVals(); // add listener is done here - } - } - } - - /** - * return true if this PtgArea3d is an external reference - * i.e. defined in another, external workbook - * @return - */ - public boolean isExternalRef() { - return isExternalRef; - } - - /** - * return the first sheet referenced - * @return - */ - public Boundsheet getFirstSheet() { - if (parent_rec!=null) { - WorkBook wb = parent_rec.getWorkBook(); - if (sheetname!=null) - try { - return wb.getWorkSheetByName(sheetname); - } catch (WorkSheetNotFoundException e) { - ; // fall thru -- see sheet copy operations -- appears correct - } - if(wb!=null && wb.getExternSheet()!=null) { - Boundsheet[] bsa = wb.getExternSheet().getBoundSheets(this.ixti); - if (bsa!=null) - return bsa[0]; - } - } - return null; - } - /** - * return the last sheet referenced - * @return - */ - public Boundsheet getLastSheet() { - if (parent_rec!=null) { - WorkBook wb = parent_rec.getWorkBook(); - if(wb!=null && wb.getExternSheet()!=null) { - Boundsheet[] bsa = wb.getExternSheet().getBoundSheets(this.ixti); - if (bsa!=null) { - if(bsa.length>1) - return bsa[bsa.length-1]; - return bsa[0]; - } - - } - } - return null; - } - - public void setParentRec(XLSRecord rec){ + public Boundsheet getLastSheet() { + if (parent_rec != null) { + WorkBook wb = parent_rec.getWorkBook(); + if (wb != null && wb.getExternSheet() != null) { + Boundsheet[] bsa = wb.getExternSheet().getBoundSheets(this.ixti); + if (bsa != null) { + if (bsa.length > 1) + return bsa[bsa.length - 1]; + return bsa[0]; + } + + } + } + return null; + } + + public void setParentRec(XLSRecord rec) { super.setParentRec(rec); - if(firstPtg!=null) + if (firstPtg != null) firstPtg.setParentRec(parent_rec); - if(lastPtg!=null) + if (lastPtg != null) lastPtg.setParentRec(parent_rec); } - + + /** + * return the first sheet referenced + * + * @return + */ + public Boundsheet getSheet() { + return getFirstSheet(); + } + /** - * return the first sheet referenced - * @return - */ - public Boundsheet getSheet() { - return getFirstSheet(); - } - - /** - * get the sheet name from the 1st 3d reference - */ - public String getSheetName() { - if (sheetname==null) { - if (parent_rec!=null) { - WorkBook wb = parent_rec.getWorkBook(); - if(wb!=null && wb.getExternSheet()!=null) { - String[] sheets = wb.getExternSheet().getBoundSheetNames(this.ixti); - if (sheets!=null && sheets.length > 0) { - sheetname= sheets[0]; - sheetname= qualifySheetname(sheetname); - } - } - - if(sheetname == null && parent_rec!=null && parent_rec.getSheet()!=null){ // try this: - sheetname = parent_rec.getSheet().getSheetName(); - sheetname= qualifySheetname(sheetname); - } - } - } - return sheetname; - } - /** - * return the name of the last sheet referenced if it's an external ref - * @return - */ - public String getLastSheetName() { - String sheetname= this.sheetname; // 20100217 KSC: default to 1st sheet - if (parent_rec!=null) { - WorkBook wb = parent_rec.getWorkBook(); - if(wb!=null && wb.getExternSheet()!=null) { - String[] sheets = wb.getExternSheet().getBoundSheetNames(this.ixti); - if (sheets!=null && sheets.length > 0) - sheetname= sheets[sheets.length-1]; - } - } - return sheetname; - } - - - /** get the worksheet that this ref is on - */ - public Boundsheet[] getSheets(WorkBook b){ - Boundsheet[] bsa= b.getExternSheet().getBoundSheets(this.ixti); - if (bsa[0]==null) // 20080303 KSC: Catch Unresolved External refs - Logger.logErr("PtgArea3d.getSheet: Unresolved External Worksheet"); - return bsa; - } - - - /** constructor, takes the array of the ptgRef, including - the identifier so we do not need to figure it out again later... - also takes the parent rec -- needed to init the sub-ptgs - * + * get the sheet name from the 1st 3d reference + */ + public String getSheetName() { + if (sheetname == null) { + if (parent_rec != null) { + WorkBook wb = parent_rec.getWorkBook(); + if (wb != null && wb.getExternSheet() != null) { + String[] sheets = wb.getExternSheet().getBoundSheetNames(this.ixti); + if (sheets != null && sheets.length > 0) { + sheetname = sheets[0]; + sheetname = qualifySheetname(sheetname); + } + } + + if (sheetname == null && parent_rec != null && parent_rec.getSheet() != null) { // try this: + sheetname = parent_rec.getSheet().getSheetName(); + sheetname = qualifySheetname(sheetname); + } + } + } + return sheetname; + } + + /** + * return the name of the last sheet referenced if it's an external ref + * + * @return + */ + public String getLastSheetName() { + String sheetname = this.sheetname; // 20100217 KSC: default to 1st sheet + if (parent_rec != null) { + WorkBook wb = parent_rec.getWorkBook(); + if (wb != null && wb.getExternSheet() != null) { + String[] sheets = wb.getExternSheet().getBoundSheetNames(this.ixti); + if (sheets != null && sheets.length > 0) + sheetname = sheets[sheets.length - 1]; + } + } + return sheetname; + } + + + /** + * get the worksheet that this ref is on + */ + public Boundsheet[] getSheets(WorkBook b) { + Boundsheet[] bsa = b.getExternSheet().getBoundSheets(this.ixti); + if (bsa[0] == null) // 20080303 KSC: Catch Unresolved External refs + Logger.logErr("PtgArea3d.getSheet: Unresolved External Worksheet"); + return bsa; + } + + + /** + * constructor, takes the array of the ptgRef, including + * the identifier so we do not need to figure it out again later... + * also takes the parent rec -- needed to init the sub-ptgs + * * @param b * @param parent */ - public void init(byte[] b,XLSRecord parent){ + public void init(byte[] b, XLSRecord parent) { ixti = ByteTools.readShort(b[1], b[2]); record = b; this.setParentRec(parent); populateVals(); } - + /** - Throw this data into two ptgref's - */ - public void populateVals(){ - byte[] temp1 = new byte[7]; // PtgRef3d is 7 bytes + * Throw this data into two ptgref's + */ + public void populateVals() { + byte[] temp1 = new byte[7]; // PtgRef3d is 7 bytes byte[] temp2 = new byte[7]; // Encoded Cell Range Address: // 0-2= first row @@ -351,169 +364,172 @@ public void populateVals(){ // 0-2= row index // 2-4= col index + relative flags try { - temp1[0] = 0x3a; - temp1[1] = record[1]; // ixti - temp1[2] = record[2]; // "" - temp1[3] = record[3]; // first row - temp1[4] = record[4]; // "" - temp1[5] = record[7]; // first col - temp1[6] = record[8]; // "" - - temp2[0] = 0x3a; - temp2[1] = record[1]; // ixti - temp2[2] = record[2]; // "" - temp2[3] = record[5]; // last row - temp2[4] = record[6]; // "" - temp2[5] = record[9]; // last col - temp2[6] = record[10]; // "" + temp1[0] = 0x3a; + temp1[1] = record[1]; // ixti + temp1[2] = record[2]; // "" + temp1[3] = record[3]; // first row + temp1[4] = record[4]; // "" + temp1[5] = record[7]; // first col + temp1[6] = record[8]; // "" + + temp2[0] = 0x3a; + temp2[1] = record[1]; // ixti + temp2[2] = record[2]; // "" + temp2[3] = record[5]; // last row + temp2[4] = record[6]; // "" + temp2[5] = record[9]; // last col + temp2[6] = record[10]; // "" } catch (Exception e) { //should never happen! - return; + return; } // pass in parent_rec so can properly set formulaRow/formulaCol firstPtg = new PtgRef3d(false); - + // the following method registers the Ptg with the ReferenceTracker firstPtg.setParentRec(parent_rec); firstPtg.setSheetName(this.getSheetName()); firstPtg.init(temp1); - + lastPtg = new PtgRef3d(false); lastPtg.setParentRec(parent_rec); lastPtg.setSheetName(this.getLastSheetName()); lastPtg.init(temp2); // flag if it's an external reference - isExternalRef= (((PtgRef3d)firstPtg).isExternalLink() || ((PtgRef3d)lastPtg).isExternalLink()); - + isExternalRef = (((PtgRef3d) firstPtg).isExternalLink() || ((PtgRef3d) lastPtg).isExternalLink()); + setWholeRowCol(); - - // take 1st Ptg as sample for relative state - this.fColRel= firstPtg.isColRel(); - this.fRwRel= firstPtg.isRowRel(); - //init sets formula row to 1st row for a shared formula; adjust here - if (parent_rec!=null && parent_rec instanceof Shrfmla) { - lastPtg.formulaRow= ((Shrfmla) parent_rec).getLastRow(); - lastPtg.formulaCol= ((Shrfmla) parent_rec).getLastCol(); + + // take 1st Ptg as sample for relative state + this.fColRel = firstPtg.isColRel(); + this.fRwRel = firstPtg.isRowRel(); + //init sets formula row to 1st row for a shared formula; adjust here + if (parent_rec != null && parent_rec instanceof Shrfmla) { + lastPtg.formulaRow = ((Shrfmla) parent_rec).getLastRow(); + lastPtg.formulaCol = ((Shrfmla) parent_rec).getLastCol(); + } + this.hashcode = super.getHashCode(); + } + + /** + * Set the location of this PtgRef. This takes a location + * such as "a14:b15" + *

                + * NOTE: the reference stays on the same sheet! + */ + public void setLocation(String address) { + String[] s = ExcelTools.stripSheetNameFromRange(address); + setLocation(s); + } + + + /** + * set Ptg Location to parsed location + * + * @param loc String[] sheet1, range, sheet2, exref1, exref2 + */ + public void setLocation(String[] s) { + try { + if (useReferenceTracker && locax != null) // if in tracker already, remove + this.removeFromRefTracker(); + } catch (Exception e) { + // will happen if this is not in tracker yet } - ((PtgArea)this).hashcode= super.getHashCode(); - } - - /** Set the location of this PtgRef. This takes a location - such as "a14:b15" - - NOTE: the reference stays on the same sheet! - */ - public void setLocation(String address){ - String[] s = ExcelTools.stripSheetNameFromRange(address); - setLocation(s); - } - - - /** - * set Ptg Location to parsed location - * @param loc String[] sheet1, range, sheet2, exref1, exref2 - */ - public void setLocation(String[] s) { - try{ - if (useReferenceTracker && locax!=null) // if in tracker already, remove - this.removeFromRefTracker(); - }catch(Exception e){ - ;// will happen if this is not in tracker yet + String sheetname2 = null; + String range = ""; + range = s[1]; + if (s[0] != null) { // has a sheet in the address + sheetname = s[0]; + sheetname2 = s[2]; + if (sheetname2 == null) + sheetname2 = sheetname; + // revised so can set ixti on error'd references + WorkBook b = null; + Externsheet xsht = null; + if (parent_rec != null) { + b = parent_rec.getWorkBook(); + if (b == null) + b = parent_rec.getSheet().getWorkBook(); + } + try { + xsht = b.getExternSheet(); + int boundnum = b.getWorkSheetByName(sheetname).getSheetNum(); + int boundnum2 = boundnum; // it could possibly be a 3d ref - check + if (!sheetname.equals(sheetname2) && sheetname2 != null) + boundnum2 = b.getWorkSheetByName(sheetname2).getSheetNum(); + this.setIxti((short) xsht.insertLocation(boundnum, boundnum2)); + } catch (WorkSheetNotFoundException e) { + try { + // try to link to external sheet, if possible + int boundnum = xsht.getXtiReference(s[0], s[0]); + if (boundnum == -1) { // can't resolve + this.setIxti((short) xsht.insertLocation(boundnum, boundnum)); + } else { + this.setIxti((short) boundnum); + this.isExternalRef = true; + } + } catch (Exception ex) { + } + } + } else if (parent_rec != null) { + sheetname = sheetname2 = parent_rec.getSheet().getSheetName(); // use parent rec's sheet } - String sheetname2= null; - String range= ""; - range= s[1]; - if(s[0]!=null){ // has a sheet in the address - sheetname= s[0]; - sheetname2= s[2]; - if (sheetname2==null) - sheetname2= sheetname; - // revised so can set ixti on error'd references - WorkBook b = null; - Externsheet xsht= null; - if(parent_rec!=null) { - b = parent_rec.getWorkBook(); - if (b==null) - b= parent_rec.getSheet().getWorkBook(); - } - try{ - xsht = b.getExternSheet(); - int boundnum = b.getWorkSheetByName(sheetname).getSheetNum(); - int boundnum2= boundnum; // it could possibly be a 3d ref - check - if (!sheetname.equals(sheetname2) && sheetname2!=null) - boundnum2= b.getWorkSheetByName(sheetname2).getSheetNum(); - this.setIxti((short)xsht.insertLocation(boundnum, boundnum2)); - } catch(WorkSheetNotFoundException e) { - try { - // try to link to external sheet, if possible - int boundnum = xsht.getXtiReference(s[0], s[0]); - if (boundnum==-1){ // can't resolve - this.setIxti((short)xsht.insertLocation(boundnum, boundnum)); - }else { - this.setIxti((short)boundnum); - this.isExternalRef= true; - } - } catch (Exception ex) {} - } - } else if (parent_rec!=null) { - sheetname= sheetname2= parent_rec.getSheet().getSheetName(); // use parent rec's sheet - } int i = range.indexOf(":"); - if(i<0) { - range = range+ ":" + range; + if (i < 0) { + range = range + ":" + range; i = range.indexOf(":"); } - - String firstcell = range.substring(0, i); - String lastcell = range.substring(i+1); - if (sheetname!=null) - firstcell= sheetname + "!" + firstcell; - if (sheetname2!=null) - lastcell= sheetname2 + "!" + lastcell; - if (s[3]!=null) // store OOXML External References - firstcell= s[3]+firstcell; - if (s[4]!=null) - lastcell= s[4]+lastcell; - - if (firstPtg == null){ - firstPtg=new PtgRef3d(false); - firstPtg.setParentRec(this.getParentRec()); + + String firstcell = range.substring(0, i); + String lastcell = range.substring(i + 1); + if (sheetname != null) + firstcell = sheetname + "!" + firstcell; + if (sheetname2 != null) + lastcell = sheetname2 + "!" + lastcell; + if (s[3] != null) // store OOXML External References + firstcell = s[3] + firstcell; + if (s[4] != null) + lastcell = s[4] + lastcell; + + if (firstPtg == null) { + firstPtg = new PtgRef3d(false); + firstPtg.setParentRec(this.getParentRec()); } - firstPtg.sheetname= sheetname; - ((PtgRef3d)firstPtg).setLocation(firstcell); - ((PtgRef3d)firstPtg).setIxti(this.ixti); - - if (lastPtg == null){ - lastPtg = new PtgRef3d(false); - lastPtg.setParentRec(this.getParentRec()); - } - lastPtg.sheetname= sheetname2; - ((PtgRef3d)lastPtg).setLocation(lastcell); - ((PtgRef3d)lastPtg).setIxti(this.ixti); - + firstPtg.sheetname = sheetname; + firstPtg.setLocation(firstcell); + ((PtgRef3d) firstPtg).setIxti(this.ixti); + + if (lastPtg == null) { + lastPtg = new PtgRef3d(false); + lastPtg.setParentRec(this.getParentRec()); + } + lastPtg.sheetname = sheetname2; + lastPtg.setLocation(lastcell); + ((PtgRef3d) lastPtg).setIxti(this.ixti); + this.setWholeRowCol(); - this.updateRecord(); + this.updateRecord(); // TODO: must deal with non-symmetrical absolute i.e. if first and last ptgs don't match - this.fRwRel= firstPtg.fRwRel; - this.fColRel= firstPtg.fColRel; - hashcode= getHashCode(); - if (useReferenceTracker) { - if(!this.getIsWholeCol() && !this.getIsWholeRow()) - this.addToRefTracker(); - else - useReferenceTracker= false; - } - } - - - /** - returns the location of the ptg as an array of shorts. - [0] = firstrow - [1] = firstcol - [2] = lastrow - [3] = lastcol - */ + this.fRwRel = firstPtg.fRwRel; + this.fColRel = firstPtg.fColRel; + hashcode = getHashCode(); + if (useReferenceTracker) { + if (!this.getIsWholeCol() && !this.getIsWholeRow()) + this.addToRefTracker(); + else + useReferenceTracker = false; + } + } + + + /** + * returns the location of the ptg as an array of shorts. + * [0] = firstrow + * [1] = firstcol + * [2] = lastrow + * [3] = lastcol + */ public int[] getIntLocation() { int[] first = firstPtg.getIntLocation(); int[] last = lastPtg.getIntLocation(); @@ -521,200 +537,201 @@ public int[] getIntLocation() { System.arraycopy(first, 0, returning, 0, 2); System.arraycopy(last, 0, returning, 2, 2); return returning; - } - - - /** returns whether this CellRange Contains a Cell - * + } + + + /** + * returns whether this CellRange Contains a Cell + * * @param the cell to test * @return whether the cell is in the range */ - public boolean contains(CellHandle ch){ + public boolean contains(CellHandle ch) { String chsheet = ch.getWorkSheetName(); - getSheetName(); - if(!chsheet.equalsIgnoreCase(sheetname))return false; + getSheetName(); + if (!chsheet.equalsIgnoreCase(sheetname)) return false; String adr = ch.getCellAddress(); // FIX broken COLROW int[] rc = ExcelTools.getRowColFromString(adr); return contains(rc); - } - + } + /** * Switches the two internal ptgref3ds to a new * sheet. - - * */ public void setReferencedSheet(Boundsheet b) { - ((PtgRef3d)firstPtg).setReferencedSheet(b); - ((PtgRef3d)lastPtg).setReferencedSheet(b); + ((PtgRef3d) firstPtg).setReferencedSheet(b); + ((PtgRef3d) lastPtg).setReferencedSheet(b); int boundnum = b.getSheetNum(); Externsheet xsht = b.getWorkBook().getExternSheet(true); //TODO: add handling for multi-sheet reference. Already handled in externsheet try { - this.sheetname= null; // 20100218 KSC: RESET + this.sheetname = null; // 20100218 KSC: RESET int xloc = xsht.insertLocation(boundnum, boundnum); - setIxti((short)xloc); - }catch(WorkSheetNotFoundException e) { + setIxti((short) xloc); + } catch (WorkSheetNotFoundException e) { Logger.logErr("Unable to set referenced sheet in PtgRef3d " + e); } } - - /** return all of the Ptg values represented in this array - * - * will have to reference the workbook cells as well as - * any upstream formulas... - * - * @return - */ - public Object[] getAllVals(){ - - return null; - } - + /** - * Updates the record bytes so it can be pulled back out. - */ - public void updateRecord(){ - comps = null; + * return all of the Ptg values represented in this array + *

                + * will have to reference the workbook cells as well as + * any upstream formulas... + * + * @return + */ + public Object[] getAllVals() { + + return null; + } + + /** + * Updates the record bytes so it can be pulled back out. + */ + public void updateRecord() { + comps = null; byte[] first = firstPtg.getRecord(); byte[] last = lastPtg.getRecord(); // KSC: this apparently is what excel wants: if (wholeRow) - first[5]= 0; + first[5] = 0; if (wholeCol) { - first[3]= 0; - first[4]= 0; + first[3] = 0; + first[4] = 0; } // the last record has an extra identifier on it. byte[] newrecord = new byte[PTG_AREA3D_LENGTH]; newrecord[0] = 0x3B; - System.arraycopy(first, 1, newrecord, 1, 2); + System.arraycopy(first, 1, newrecord, 1, 2); System.arraycopy(first, 3, newrecord, 3, 2); System.arraycopy(last, 3, newrecord, 5, 2); System.arraycopy(first, 5, newrecord, 7, 2); System.arraycopy(last, 5, newrecord, 9, 2); record = newrecord; - if(parent_rec!=null) { - if(this.parent_rec instanceof Formula) - ((Formula)this.parent_rec).updateRecord(); - else if(this.parent_rec instanceof Name) - ((Name)this.parent_rec).updatePtgs(); + if (parent_rec != null) { + if (this.parent_rec instanceof Formula) + ((Formula) this.parent_rec).updateRecord(); + else if (this.parent_rec instanceof Name) + ((Name) this.parent_rec).updatePtgs(); } } - - public int getLength(){ + + public int getLength() { return PTG_AREA3D_LENGTH; } - - - /** - Returns all of the cells of this range as PtgRef's. - This includes empty cells, values, formulas, etc. - Note the setting of parent-rec requires finding the cell - the PtgRef refer's to. If that is null then the PtgRef - will exist, just with a null value. This could cause issues when - programatically populating cells. - */ - public Ptg[] getComponents(){ - if(comps!=null) - return comps; - - ArrayList components = new ArrayList(); - try{ - // loop through the cols - String sht = ""; - if(this.toString().indexOf("!")>-1) { - sht = this.toString(); - sht = sht.substring(0,sht.indexOf("!")) + "!"; - } - int startrow=0, startcol=0, endrow=0, endcol=0; - if (!this.wholeCol && !this.wholeRow) { // normal case + + + /** + * Returns all of the cells of this range as PtgRef's. + * This includes empty cells, values, formulas, etc. + * Note the setting of parent-rec requires finding the cell + * the PtgRef refer's to. If that is null then the PtgRef + * will exist, just with a null value. This could cause issues when + * programatically populating cells. + */ + public Ptg[] getComponents() { + if (comps != null) + return comps; + + ArrayList components = new ArrayList(); + try { + // loop through the cols + String sht = ""; + if (this.toString().indexOf("!") > -1) { + sht = this.toString(); + sht = sht.substring(0, sht.indexOf("!")) + "!"; + } + int startrow = 0, startcol = 0, endrow = 0, endcol = 0; + if (!this.wholeCol && !this.wholeRow) { // normal case // TODO: check rc sanity here - int[] startloc= firstPtg.getRealIntLocation(); // Get Actual Coordinates - startcol = startloc[1]; - startrow = startloc[0]; - int[] endloc = lastPtg.getRealIntLocation(); // Get Actual Coordinates - endcol = endloc[1]; - endrow = endloc[0]; - } else if (this.wholeRow) { // like $1:$1 - startcol= 0; - try { - endcol= this.getSheet().getMaxCol(); - } catch (NullPointerException ne) { // can happens when Name record is being init'd and sheet records are not set yet - return null; - } - startrow= endrow= firstPtg.rw; - } else if (this.wholeCol) { // like $J:$J - startrow= 0; // Get Actual Coordinates - startcol= endcol= firstPtg.col; - try { - endrow= this.getSheet().getMaxRow(); - } catch (NullPointerException ne) { // can happens when Name record is being init'd and sheet records are not set yet - return null; - } - } - for (;startcol<=endcol;startcol++){ - // loop through the rows inside - int rowholder = startrow; - for (;rowholder<=endrow;rowholder++){ - String displaycol = ExcelTools.getAlphaVal(startcol); - int displayrow = rowholder+1; - String loc = sht + displaycol+displayrow; - - // cache these suckers! - Ptg pref = new PtgRef3d(false); - pref.setParentRec(parent_rec); // must set parentrec before setLocation - ((PtgRef3d)pref).setLocation(loc); - components.add(pref); - } - } - }catch (Exception e){ - Logger.logErr("calculating range value in PtgArea3d failed.", e); - } - PtgRef[] pref = new PtgRef[components.size()]; - components.toArray(pref); - comps = pref; - return comps; - } - - - - /** - * sets the column to be relative (relative is true) or absolute (relative is false) - *
                absolute references do not shift upon column inserts or deletes - *
                NOTE: DOES NOT handle asymmetrical ranges i.e. 1st ref is absolute, 2nd is relative - * @param boolean relative - */ - public void setColRel(boolean relative) { - this.fColRel= relative; - firstPtg.setColRel(relative); - lastPtg.setColRel(relative); - updateRecord(); - } - - /** - * sets the row to be relative (relative is true) or absolute (relative is false) - *
                absolute references do not shift upon row inserts or deletes - *
                NOTE: DOES NOT handle asymmetrical ranges i.e. 1st ref is absolute, 2nd is relative - * @param boolean relative - */ - public void setRowRel(boolean relative) { - if (this.fRwRel!=relative) { - this.fRwRel= relative; - firstPtg.setRowRel(relative); - lastPtg.setRowRel(relative); - updateRecord(); - } - } - + int[] startloc = firstPtg.getRealIntLocation(); // Get Actual Coordinates + startcol = startloc[1]; + startrow = startloc[0]; + int[] endloc = lastPtg.getRealIntLocation(); // Get Actual Coordinates + endcol = endloc[1]; + endrow = endloc[0]; + } else if (this.wholeRow) { // like $1:$1 + startcol = 0; + try { + endcol = this.getSheet().getMaxCol(); + } catch (NullPointerException ne) { // can happens when Name record is being init'd and sheet records are not set yet + return null; + } + startrow = endrow = firstPtg.rw; + } else if (this.wholeCol) { // like $J:$J + startrow = 0; // Get Actual Coordinates + startcol = endcol = firstPtg.col; + try { + endrow = this.getSheet().getMaxRow(); + } catch (NullPointerException ne) { // can happens when Name record is being init'd and sheet records are not set yet + return null; + } + } + for (; startcol <= endcol; startcol++) { + // loop through the rows inside + int rowholder = startrow; + for (; rowholder <= endrow; rowholder++) { + String displaycol = ExcelTools.getAlphaVal(startcol); + int displayrow = rowholder + 1; + String loc = sht + displaycol + displayrow; + + // cache these suckers! + Ptg pref = new PtgRef3d(false); + pref.setParentRec(parent_rec); // must set parentrec before setLocation + pref.setLocation(loc); + components.add(pref); + } + } + } catch (Exception e) { + Logger.logErr("calculating range value in PtgArea3d failed.", e); + } + PtgRef[] pref = new PtgRef[components.size()]; + components.toArray(pref); + comps = pref; + return comps; + } + + + /** + * sets the column to be relative (relative is true) or absolute (relative is false) + *
                absolute references do not shift upon column inserts or deletes + *
                NOTE: DOES NOT handle asymmetrical ranges i.e. 1st ref is absolute, 2nd is relative + * + * @param boolean relative + */ + public void setColRel(boolean relative) { + this.fColRel = relative; + firstPtg.setColRel(relative); + lastPtg.setColRel(relative); + updateRecord(); + } + + /** + * sets the row to be relative (relative is true) or absolute (relative is false) + *
                absolute references do not shift upon row inserts or deletes + *
                NOTE: DOES NOT handle asymmetrical ranges i.e. 1st ref is absolute, 2nd is relative + * + * @param boolean relative + */ + public void setRowRel(boolean relative) { + if (this.fRwRel != relative) { + this.fRwRel = relative; + firstPtg.setRowRel(relative); + lastPtg.setRowRel(relative); + updateRecord(); + } + } + public void close() { - super.close(); - if (comps!=null) - for (int i= 0; i < comps.length; i++) { - ((GenericPtg)comps[i]).close(); - comps[i]= null; - } - } + super.close(); + if (comps != null) + for (int i = 0; i < comps.length; i++) { + comps[i].close(); + comps[i] = null; + } + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.java b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.java index 3762483..cad5653 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -30,72 +30,75 @@ /** - An Erroneous BiffRec range spanning 3rd dimension of WorkSheets. - - identical to PtgArea3d - + * An Erroneous BiffRec range spanning 3rd dimension of WorkSheets. + *

                + * identical to PtgArea3d + * * @see Ptg * @see GenericPtgFunc **/ -public class PtgAreaErr3d extends PtgArea3d implements Ptg -{ - - // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -9091097082897614748L; - public boolean getIsRefErr() { return true; } +public class PtgAreaErr3d extends PtgArea3d implements Ptg { + + // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -9091097082897614748L; + + public boolean getIsRefErr() { + return true; + } + + public String getString() { + if (sheetname == null) + return "#REF!"; + return sheetname + "!#REF!"; + } + + public int getLength() { + return PTG_AREAERR3D_LENGTH; + } - public String getString(){ - if (sheetname==null) - return "#REF!"; - return sheetname+"!#REF!"; - } - - public int getLength(){ - return PTG_AREAERR3D_LENGTH; - } - - /* constructor, takes the array of the ptgRef, including - the identifier so we do not need to figure it out again later... - */ - public void init(byte[] b){ - record = b; + /* constructor, takes the array of the ptgRef, including + the identifier so we do not need to figure it out again later... + */ + public void init(byte[] b) { + record = b; ixti = ByteTools.readShort(record[1], record[2]); - if(ixti>0) this.sheetname= GenericPtg.qualifySheetname(this.getSheetName()); - - } - - public Object getValue(){ - if (sheetname==null) - return "#REF!"; - return sheetname+"!#REF!"; - } - - /** - * sets referenced sheet - * called from copy worksheet - * different from PtgArea3d as PtgAreaErr3d's have not set their firstPtg and lastPtg - */ + if (ixti > 0) this.sheetname = GenericPtg.qualifySheetname(this.getSheetName()); + + } + + public Object getValue() { + if (sheetname == null) + return "#REF!"; + return sheetname + "!#REF!"; + } + + /** + * sets referenced sheet + * called from copy worksheet + * different from PtgArea3d as PtgAreaErr3d's have not set their firstPtg and lastPtg + */ public void setReferencedSheet(Boundsheet b) { int boundnum = b.getSheetNum(); Externsheet xsht = b.getWorkBook().getExternSheet(true); //TODO: add handling for multi-sheet reference. Already handled in externsheet try { int xloc = xsht.insertLocation(boundnum, boundnum); - this.ixti = (short) xloc; - if(ixti>0) this.sheetname= GenericPtg.qualifySheetname(this.getSheetName()); - }catch(WorkSheetNotFoundException e) { + this.ixti = (short) xloc; + if (ixti > 0) this.sheetname = GenericPtg.qualifySheetname(this.getSheetName()); + } catch (WorkSheetNotFoundException e) { Logger.logErr("Unable to set referenced sheet in PtgRef3d " + e); } - } - public void setLocation(String[] s) { - sheetname= GenericPtg.qualifySheetname(s[0]); - } - - public int[] getRowCol(){ - return new int[] {-1, -1}; - } + } + + public void setLocation(String[] s) { + sheetname = GenericPtg.qualifySheetname(s[0]); + } + + public int[] getRowCol() { + return new int[]{-1, -1}; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.java b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.java index f857e46..9b9c480 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.java @@ -2,80 +2,82 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.FormulaNotFoundException; + import io.starter.formats.XLS.Shrfmla; import io.starter.formats.XLS.XLSConstants; import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.Logger; - /** + * ptgArea is a reference to an area (rectangle) of cells. + * Essentially it is a collection of two ptgRef's, so it will be + * treated that way in the code... + * + *

                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0           rwFirst     	2       The First row of the reference
                + * 2           rwLast     		2       The Last row of the reference
                + * 4           grbitColFirst   2       (see following table)
                + * 6           grbitColLast    2       (see following table)
                + *
                + * Only the low-order 14 bits specify the Col, the other bits specify
                + * relative vs absolute for both the col or the row.
                + *
                + * Bits        Mask        Name    Contents
                + * -----------------------------------------------------
                + * 15          8000h       fRwRel  =1 if row offset relative,
                + * =0 if otherwise
                + * 14          4000h       fColRel =1 if row offset relative,
                + * =0 if otherwise
                + * 13-0        3FFFh       col     Ordinal column offset or number
                + * 
                + * + * @see Ptg + * @see Formula + */ +public class PtgAreaN extends PtgArea { - ptgArea is a reference to an area (rectangle) of cells. - Essentially it is a collection of two ptgRef's, so it will be - treated that way in the code... - -
                -    Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0           rwFirst     	2       The First row of the reference 
                -    2           rwLast     		2       The Last row of the reference 
                -    4           grbitColFirst   2       (see following table)
                -    6           grbitColLast    2       (see following table)
                +    /**
                +     * serialVersionUID
                +     */
                +    private static final long serialVersionUID = -8433468704529379504L;
                 
                -    Only the low-order 14 bits specify the Col, the other bits specify
                -    relative vs absolute for both the col or the row.
                +    public boolean getIsOperand() {
                +        return true;
                +    }
                 
                -    Bits        Mask        Name    Contents
                -    -----------------------------------------------------
                -    15          8000h       fRwRel  =1 if row offset relative, 
                -                                    =0 if otherwise
                -    14          4000h       fColRel =1 if row offset relative,
                -                                    =0 if otherwise
                -    13-0        3FFFh       col     Ordinal column offset or number
                -    
                + public boolean getIsReference() { + return true; + } - * @see Ptg - * @see Formula + PtgRefN firstPtgN; + PtgRefN lastPtgN; + private PtgArea parea = null; - - */ -public class PtgAreaN extends PtgArea{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8433468704529379504L; - public boolean getIsOperand(){return true;} - public boolean getIsReference(){return true;} - PtgRefN firstPtgN; - PtgRefN lastPtgN; - private PtgArea parea= null; - /* + /* Throw this data into two ptgref's */ - public void populateVals(){ + public void populateVals() { byte[] temp1 = new byte[5]; byte[] temp2 = new byte[5]; temp1[0] = 0x24; @@ -109,52 +111,54 @@ public void populateVals(){ lastPtgN = new PtgRefN(false); lastPtgN.setParentRec(parent_rec); lastPtgN.init(temp2); - if (parent_rec!=null && parent_rec instanceof Shrfmla) { - // 20060301 KSC: init sets formula row to 1st row for a shared formula; adjust here - lastPtgN.setFormulaRow(((Shrfmla) parent_rec).getLastRow()); - lastPtgN.setFormulaCol(((Shrfmla) parent_rec).getLastCol()); + if (parent_rec != null && parent_rec instanceof Shrfmla) { + // 20060301 KSC: init sets formula row to 1st row for a shared formula; adjust here + lastPtgN.setFormulaRow(((Shrfmla) parent_rec).getLastRow()); + lastPtgN.setFormulaCol(((Shrfmla) parent_rec).getLastCol()); } // if (this.useReferenceTracker) // this.addToRefTracker(); } - - /** returns the row/col ints for the ref - * + + /** + * returns the row/col ints for the ref + * * @return */ - public int[] getRowCol(){ - if(firstPtgN==null){ + public int[] getRowCol() { + if (firstPtgN == null) { int[] rc1 = firstPtgN.getRowCol(); - int[] ret = {rc1[0],rc1[1],rc1[0],rc1[1]}; + int[] ret = {rc1[0], rc1[1], rc1[0], rc1[1]}; return ret; } int[] rc1 = firstPtgN.getRowCol(); int[] rc2 = lastPtgN.getRowCol(); - int[] ret = {rc1[0],rc1[1],rc2[0],rc2[1]}; + int[] ret = {rc1[0], rc1[1], rc2[0], rc2[1]}; return ret; } - - /** - * returns the uncoverted, actual row col - * @return - */ - public int[] getRealRowCol() { - return new int[] {firstPtgN.rw, firstPtgN.col, lastPtgN.rw, lastPtgN.col}; - } - - public PtgArea convertToPtgArea(io.starter.formats.XLS.XLSRecord r){ - PtgRef p1 = firstPtgN.convertToPtgRef(r); - PtgRef p2 = lastPtgN.convertToPtgRef(r); - PtgArea par = new PtgArea(p1, p2, r); - return par; + + /** + * returns the uncoverted, actual row col + * + * @return + */ + public int[] getRealRowCol() { + return new int[]{firstPtgN.rw, firstPtgN.col, lastPtgN.rw, lastPtgN.col}; + } + + public PtgArea convertToPtgArea(io.starter.formats.XLS.XLSRecord r) { + PtgRef p1 = firstPtgN.convertToPtgRef(r); + PtgRef p2 = lastPtgN.convertToPtgRef(r); + PtgArea par = new PtgArea(p1, p2, r); + return par; } - + /** * update record bytes */ // 20060223 KSC - public void updateRecord(){ + public void updateRecord() { byte[] first = firstPtgN.getRecord(); byte[] last = lastPtgN.getRecord(); // the last record has an extra identifier on it. @@ -166,128 +170,130 @@ public void updateRecord(){ System.arraycopy(last, 3, newrecord, 7, 2); record = newrecord; } - + /* Returns the location of the Ptg as a string */ - public String getLocation(){ - if (firstPtgN == null || lastPtgN == null){ - this.populateVals(); - if (firstPtgN == null || lastPtgN == null) // we tried - throw new AssertionError("PtgAreaN.getLocation null ptgs"); + public String getLocation() { + if (firstPtgN == null || lastPtgN == null) { + this.populateVals(); + if (firstPtgN == null || lastPtgN == null) // we tried + throw new AssertionError("PtgAreaN.getLocation null ptgs"); } String s = firstPtgN.getLocation(); String y = lastPtgN.getLocation(); - + return s + ":" + y; - } + } + /** * returns an array of the first and last addresses in the PtgAreaN - * */ // 20060223: KSC: customize from ptgArea public int[] getIntLocation() { int[] returning = new int[4]; - try { - int[] first= firstPtgN.getIntLocation(); + try { + int[] first = firstPtgN.getIntLocation(); int[] last = lastPtgN.getIntLocation(); - System.arraycopy(first, 0, returning, 0, 2); - System.arraycopy(last, 0, returning, 2, 2); - } - catch (Exception e) { ; } + System.arraycopy(first, 0, returning, 0, 2); + System.arraycopy(last, 0, returning, 2, 2); + } catch (Exception e) { + } return returning; - } - + } + /** - * * @return lastPtgN */ - public PtgRefN getLastPtgN() { - return lastPtgN; - } + public PtgRefN getLastPtgN() { + return lastPtgN; + } + /** - * * @return firstPtgN */ - public PtgRefN getFirstPtgN() { - return firstPtgN; - } - - /** - * custom RefTracker usage: uses entire range covered by all shared formulas - */ - public PtgArea getArea() { - Shrfmla sh= (Shrfmla) this.getParentRec(); - int[] i = new int[4]; + public PtgRefN getFirstPtgN() { + return firstPtgN; + } + + /** + * custom RefTracker usage: uses entire range covered by all shared formulas + */ + public PtgArea getArea() { + Shrfmla sh = (Shrfmla) this.getParentRec(); + int[] i = new int[4]; if (fRwRel) { i[0] = sh.getFirstRow() + firstPtgN.rw; - }else { + } else { i[0] = firstPtgN.rw; } if (fColRel) { i[1] = sh.getFirstCol() + firstPtgN.col; - }else { + } else { i[1] = firstPtgN.col; } if (fRwRel) { i[2] = sh.getLastRow() + lastPtgN.rw; - }else { + } else { i[2] = lastPtgN.rw; } if (fColRel) { i[3] = sh.getLastCol() + lastPtgN.col; - }else { + } else { i[3] = lastPtgN.col; } - - if (i[1]>=MAXCOLS_BIFF8 && - !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[1]-=MAXCOLS_BIFF8; - if (i[3]>=MAXCOLS_BIFF8 && - !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[3]-=MAXCOLS_BIFF8; - - PtgArea parea = new PtgArea(i, (XLSRecord)sh, true); - return parea; - } - /** - * add "true" area to reference tracker i.e. entire range referenced by all shared formula members - */ - public void addToRefTracker() { - int iParent= this.getParentRec().getOpcode(); - if (iParent==XLSConstants.SHRFMLA) { - // KSC: TESTING - local ptgarea gets finalized and messes up ref. tracker on multiple usages without close - //getArea(); - //parea.addToRefTracker(); - PtgArea parea= this.getArea(); // is finalized if local var --- but take out ptgarea finalize for now - parea.addToRefTracker(); - } - } - /** - * remove "true" area from reference tracker i.e. entire range referenced by all shared formula members - */ - public void removeFromRefTracker() { - int iParent= this.getParentRec().getOpcode(); - if (iParent==XLSConstants.SHRFMLA) { - PtgArea parea= this.getArea(); - parea.removeFromRefTracker(); - } - //if (parea!=null) { - // parea.removeFromRefTracker(); - //parea.close(); - //} - //parea= null; - } + + if (i[1] >= MAXCOLS_BIFF8 && + !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) + i[1] -= MAXCOLS_BIFF8; + if (i[3] >= MAXCOLS_BIFF8 && + !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) + i[3] -= MAXCOLS_BIFF8; + + PtgArea parea = new PtgArea(i, sh, true); + return parea; + } + + /** + * add "true" area to reference tracker i.e. entire range referenced by all shared formula members + */ + public void addToRefTracker() { + int iParent = this.getParentRec().getOpcode(); + if (iParent == XLSConstants.SHRFMLA) { + // KSC: TESTING - local ptgarea gets finalized and messes up ref. tracker on multiple usages without close + //getArea(); + //parea.addToRefTracker(); + PtgArea parea = this.getArea(); // is finalized if local var --- but take out ptgarea finalize for now + parea.addToRefTracker(); + } + } + + /** + * remove "true" area from reference tracker i.e. entire range referenced by all shared formula members + */ + public void removeFromRefTracker() { + int iParent = this.getParentRec().getOpcode(); + if (iParent == XLSConstants.SHRFMLA) { + PtgArea parea = this.getArea(); + parea.removeFromRefTracker(); + } + //if (parea!=null) { + // parea.removeFromRefTracker(); + //parea.close(); + //} + //parea= null; + } + public void close() { - removeFromRefTracker(); - if (parea!=null) - parea.close(); - parea= null; - if (firstPtgN!=null) - firstPtgN.close(); - firstPtgN= null; - if (lastPtgN!=null) - lastPtgN.close(); - lastPtgN= null; + removeFromRefTracker(); + if (parea != null) + parea.close(); + parea = null; + if (firstPtgN != null) + firstPtgN.close(); + firstPtgN = null; + if (lastPtgN != null) + lastPtgN.close(); + lastPtgN = null; } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java b/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java index 257b05f..899f40a 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,51 +29,51 @@ */ package io.starter.formats.XLS.formulas; -import java.io.UnsupportedEncodingException; - import io.starter.formats.XLS.ExpressionParser; import io.starter.toolkit.ByteTools; import io.starter.toolkit.CompatibleVector; import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; + /* * ARRAY CONSTANT followed by 7 reserved bytes. - * + * * The token value for ptgArray consists of the array * dimensions and the array values - * + * * ptgArray differs from most other operand tokens in that * the token value doesn't follow the token type. - * + * * Instead, the token value is appended to the saved parsed * expression, immediately following the last token. - * + * * Offset Name Size Contents * --------------------------------------------------------- * -- * 0 nc 1 number of columns -1 in array constant (0 = 256) * 1 nr 2 number of rows -1 in array constant * 3 rgval var the array vals (k+1)*(nr+1) length - * - * + * + * * The format of the token value is shown in the following * table. - * + * * The number of values in the array constant is equal to * the product of the array dimensions, (nc+1)*(nr+1_ - * + * * Each value is either an 8-byte IEEE fp numbr or a string. * The two formats for these values are shown in the * following tables. - * - * + * + * * IEEE FP Number * Offset Name Size Contents * --------------------------------------------------------- * -- * 0 grbit 1 =01h * 1 num 8 IEEE FP number - * + * * String * Offset Name Size Contents * --------------------------------------------------------- @@ -81,14 +81,14 @@ * 0 grbit 1 =02h * 1 cch 1 Length of the String * 2 rgch var the string. - * + * * If a formula contains more than one array constant, the * token values for the array constants are appended to the * saved * parsed expression in order: first the values for the * first array constant, * then the values for the second array constant, etc. - * + * * If a formula contains very long array constants, the * FORMULA, ARRAY, or NAME record contaniing the parsed * expression @@ -96,547 +96,556 @@ * individual array value is NEVER SPLIT between records, * but record boundaries are established between adjacent * array values. - * + * * The reference class ptgArray never appears in an Excel * formula, only the ptgArrayV and ptgArrayA classes are * used. - * - * + * + * * @see Ptg - * + * * @see Formula - * + * */ // 20090119-22 KSC: Many, many changes changes public class PtgArray extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4416140231168551393L; - int nc = -1; - int nr = -1; - byte[] rgval; - CompatibleVector arrVals = new CompatibleVector(); - boolean isIntermediary = false; // 20090824 - // KSC: - // true - // if - // this - // PtgArray - // is - // only - // part - // of - // a - // calcualtion - // process; - // if - // so, - // apparently - // can - // have - // more - // than - // 256 - // columns - // [BugTracker - // 2683] - - @Override - public boolean getIsOperand() { - return true; - } - - /** return the first 8 bytes of the ptgArray record - * this represents the id byte and 7 reserved bytes - * @return - */ - public byte[] getPreRecord() { - return record; - } - - /** - * these are the bytes appended to the formula token array, after all other ptg's - * @return - */ - public byte[] getPostRecord() { - return rgval; - } - - @Override - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - } - - private void populateVals() { - if (record.length > 8) { // means that array data has already been - // appeneded to end of record array; store - // in rgvals - rgval = new byte[record.length - 8]; - System.arraycopy(record, 8, rgval, 0, rgval.length); // save post - // array= - // nc, nr + - // array - // data - } - if (rgval != null) { - // clear out record array:0= id 1-7=reserved - byte[] b = new byte[8]; - b[0] = record[0]; - record = b; - this.parseArrayComponents(); - } // otherwise, it's just the initial input of the 1st 8 bytes - // record - see Formula - } - - /** - * given "extra info" at end of formula expression, parse array values - */ - public void parseArrayComponents() { - int nitems = 0; - arrVals.clear(); // 20090820 KSC: makes sense to! [BugTracker 2683] - if (!isIntermediary) // 20090824 KSC: sometimes an intermediary ptgarry - // can have more than 256 columns [BugTracker - // 2683] - nc = rgval[0] & 0xFF; // number of columns - nr = ByteTools.readShort(rgval[1], rgval[2]); // number of rows - try { - // (nc+1)*(nr+1) compoments - for (int i = 3; i < rgval.length;) { // 20090820 KSC: post array - // contains nc & nr so i - // should be initially 3 - // instead of 0 [BugTracker - // 2683] - if (rgval[i] == 0) { // empty value - i++; - i += 8; - arrVals.add(""); // TODO: Empty Constant should be null? - } else if (rgval[i] == 0x1) { // its a number - i++; - byte[] barr = new byte[8]; - System.arraycopy(rgval, i, barr, 0, 8); - double val = ByteTools.eightBytetoLEDouble(barr); - Double d = new Double(val); - arrVals.add(d); - i = i + 8; - } else if (rgval[i] == 0x2) { // its a string - int strLen = ByteTools - .readShort(rgval[i + 1], rgval[i + 2]); - i += 3; - int grbt = rgval[i++]; - byte[] barr = new byte[strLen]; - System.arraycopy(rgval, i, barr, 0, strLen); - String strVal = ""; - try { - if ((grbt & 0x1) == 0x1) { - strVal = new String(barr, UNICODEENCODING); - } else { - strVal = new String(barr, DEFAULTENCODING); - } - } catch (UnsupportedEncodingException e) { - Logger.logInfo("decoding formula string in array failed: " - + e); - } - arrVals.add(strVal); - i += strLen; - } else if (rgval[i] == 0x4) { // its a boolean - if (rgval[++i] == 0) - arrVals.add(Boolean.valueOf(false)); - else - arrVals.add(Boolean.valueOf(true)); - i = i + 8; - } else if (rgval[i] == 0x10) { // it's an error value - int errCode = rgval[++i]; - switch (errCode) { - case 0: - arrVals.add("#NULL!"); - break; - case 0x7: - arrVals.add("#DIV/0!"); - break; - case 0x0F: - arrVals.add("#VALUE!"); - break; - case 0x17: - arrVals.add("#REF!"); - break; - case 0x1D: - arrVals.add("#NAME!"); - break; - case 0x24: - arrVals.add("#NUM!"); - break; - case 0x2A: - arrVals.add("#N/A!"); - break; - } - i = i + 8; - } - nitems++; - if (nitems == ((nc + 1) * (nr + 1))) { // Finished with this - // array! - int length = i; - i = rgval.length; - // length may be less than rgval.length for cases of more - // than one array parameter - // see ExpressionParser.parseExpression - if (rgval.length != length) {// then truncate both record + - // rgval - byte[] tmp = new byte[length]; - System.arraycopy(rgval, 0, tmp, 0, length); - rgval = tmp; - } - } - } - } catch (Exception e) { - Logger.logErr("Error Processing Array Formula: " + e.toString()); - return; - } - } - - public int getVal() { - return -1; - } - - @Override - public Object getValue() { - // 20090820 KSC: value = entire array instead of 1st value; - // desired value is determined by cell position as compared - // to current formula; see Formula.calculate [BugTracker - // 2683] - // return elementAt(0).getValue(-1); // default: return 1st - // value - return getString(); - } - - /* - * returns the string value of the name - * - * @see io.starter.formats.XLS.formulas.Ptg#getValue() - */ - @Override - public String getString() { - Object retVal = null; - Ptg[] p = this.getComponents(); - String retstr = ""; - if (nc == 0 && nr == 0) { // if it's a single value, just return val - for (int i = 0; i < p.length; i++) { - if (i != 0) - retstr += ","; - retstr += p[i].getValue().toString(); - } - } else { - retstr = ""; - int loc = 0; - for (int x = 0; x < nr + 1; x++) { - if (x != 0) - retstr += ";"; - for (int i = 0; i < nc + 1; i++) { - if (i != 0) - retstr += ","; - retstr += p[loc++].getValue().toString(); - } - } - // retstr += "}"; - // retVal = retstr.substring(0,retstr.length()-1); - } - retVal = retstr; - return "{" + retVal + "}"; - } - - @Override - public String getTextString() { - return getString(); - } - - public void setVal(String arrStr) { - // remove the initial { and ending } - arrStr = arrStr.substring(1, arrStr.length() - 1); - if (arrStr.indexOf("{") != -1) { // SHOULDN'T -- see - // FormulaParser.getPtgsFromFormulaString - Logger.logErr("PtgArray.setVal: Multiple Arrays Encountered"); - } - - // parse all array strings into rows, cols - String[] rows = null; - String[][] cols = null; - // split rows - rows = arrStr.split(";"); - cols = new String[rows.length][]; - for (int i = 0; i < rows.length; i++) { - String[] s = rows[i].split(",", -1); // include empty strings - cols[i] = s; - } - byte[] databytes = new byte[11]; - databytes[0] = 0x60; // 20h=tArrayR, 40h=tArrayV, 60h=tArrayA - isIntermediary = false; // init value - if (cols[0].length >= 255) { // 20090824 KSC: apparently sometimes an - // intermediary calculations step can - // include > 256 array elements ... - isIntermediary = true; - nc = cols[0].length - 1; - } - databytes[8] = (byte) ((cols[0].length - 1) & 0xFF); // nc-1 // 20090819 - // KSC: placed - // in wrong pt - // of record: - // was [1] - // [BugTracker - // 2683] - // databytes[8] = (byte)((cols[0].length-1)); // nc-1 // - // 20090819 KSC: placed in wrong pt of record: was [1] - // [BugTracker 2683] - System.arraycopy(ByteTools - .shortToLEBytes((short) (rows.length - 1)), 0, databytes, 9, 2); // nr-1 - // // - // 20090819 - // KSC: - // placed - // in - // wrong - // pt - // of - // record: - // was - // 2,3 - // [BugTracker - // 2683] - // iterate the array and fill out the data section - for (int j = 0; j < rows.length; j++) { - for (int i = 0; i < cols[0].length; i++) { - byte[] valbytes = this.valuesIntoByteArray(cols[j][i]); - databytes = ByteTools.append(valbytes, databytes); - } - } - // populate primary values for rec - record = databytes; - this.init(databytes); - } - - /** - * Turns a vector of values into a byte array representation for the data section of this record - * @param compVect - * @return - */ - private byte[] valuesIntoByteArray(String constVal) { - byte[] databytes = new byte[0]; - byte[] thisElement = new byte[9]; - - try { // number? - Double d = new Double(constVal); - thisElement[0] = 0x1; // id for number value - byte[] b = ByteTools.toBEByteArray(d.doubleValue()); - System.arraycopy(b, 0, thisElement, 1, b.length); - databytes = ByteTools.append(thisElement, databytes); - } catch (NumberFormatException ee) { - try { - if (constVal.equalsIgnoreCase("true") - || constVal.equalsIgnoreCase("false")) { - Boolean bb = Boolean.valueOf(constVal); - thisElement[0] = 0x4; // id for boolean value - thisElement[1] = (byte) (bb.booleanValue() ? 1 : 0); - } else if (constVal == null || constVal.equals("")) { // emtpy - // or - // null - // value - thisElement[0] = 0x0; // id for empty value - } else if (constVal.charAt(0) == '#') { // it's an error value - thisElement[0] = 0x10; // id for error value - int errCode = 0; - if (constVal.equals("#NULL!")) - errCode = 0; - else if (constVal.equals("#DIV/0!")) - errCode = 0x7; - else if (constVal.equals("#VALUE!")) - errCode = 0x0F; - else if (constVal.equals("#REF!")) - errCode = 0x17; - else if (constVal.equals("#NAME!")) - errCode = 0x1D; - else if (constVal.equals("#NUM!")) - errCode = 0x24; - else if (constVal.equals("#N/A!") || constVal.equals("#N/A") - || constVal.equals("N/A")) - errCode = 0x2A; - thisElement[1] = (byte) errCode; - } else { // assume string - thisElement = new byte[3]; - try { - thisElement = new byte[4]; - thisElement[0] = 0x2; // id for string - byte[] b = constVal.getBytes(UNICODEENCODING); - System.arraycopy(ByteTools - .shortToLEBytes((short) b.length), 0, thisElement, 1, 2); - thisElement[3] = 1; // compressed= 0, uncompressed= 1 - // (16-bit chars) - thisElement = ByteTools.append(b, thisElement); - } catch (UnsupportedEncodingException z) { - Logger.logWarn("encoding formula array:" + z); - } - } - databytes = ByteTools.append(thisElement, databytes); - } catch (Exception ex) { - Logger.logWarn("PtgArray.valuesIntoByteArray: error parsing array element:" - + ex); - } - } - return databytes; - } - - /** - * Returns the second section of bytes for the PtgArray. - * These are the bytes that are split off the end of the - * formula - - public void getComponentBytes(){ - - } - //public void updateRecord(){ - //}*/ - - /** Override due to mystery extra byte - * occasionally found in ptgName recs. - * - */ - @Override - public int getLength() { - /* - * 20090820 KSC: really want record length not rgval length, - * which now is separate [BugTracker 2683] - * if (rgval!=null) - * return rgval.length; - */ - return 8; - } - - /* - * not used - * public int getLength(byte[] b){ - * int co = b[1]; - * int rw = ByteTools.readShort(b[2], b[3]); - * rw++; // appears that rows are not ordinal here... - * int numrecs = co*rw; - * int len = 4; - * int loc = 4; - * for (int i=0;i<=numrecs;i++){ - * if (b[len] == 0x1){ // its a number - * len += 9; - * }else{ - * len += b[len+1] + 2; - * } - * } - * length = len; - * return length; - * } - */ - - @Override - public String toString() { - return this.getString(); - } - - @Override - public Ptg[] getComponents() { - Ptg[] retVals = new Ptg[arrVals.size()]; - for (int i = 0; i < arrVals.size(); i++) { - Object o = arrVals.elementAt(i); - if (o instanceof Double) { - Double d = (Double) o; - PtgNumber pnum = new PtgNumber(d.doubleValue()); - retVals[i] = pnum; - } else if (o instanceof Boolean) { - PtgBool pb = new PtgBool(((Boolean) o).booleanValue()); - retVals[i] = pb; - } else { - if (FormulaParser.isRef((String) o) - || FormulaParser.isRange((String) o)) { // it's a range - PtgArea3d pa = new PtgArea3d(); - pa.setParentRec(this.getParentRec()); - pa.setUseReferenceTracker(true); - pa.setLocation((String) o); - Ptg[] pacomps = pa.getComponents(); - Ptg[] temp = new Ptg[retVals.length - 1 + pacomps.length]; - System.arraycopy(retVals, 0, temp, 0, retVals.length - 1); - System.arraycopy(pacomps, 0, temp, retVals.length - - 1, pacomps.length); - retVals = temp; - } else { - PtgStr pstr = new PtgStr((String) o); - retVals[i] = pstr; - } - } - } - return retVals; - } - - /** - * returns the 0-based number of rows in this array - * if nr>1 then the array is in the form of: - * a,b,c;d,e,f; .... where the semicolons delineate rows - * @return - */ - public int getNumberOfRows() { - return nr; - } - - /** - * returns the 0-based number of columns in this array - * number of columns is the amount of elements before the semicolon (if present) - * a,b,c;d,e,f; .... - * @return - */ - public int getNumberOfColumns() { - return nc; - } - - /** sets the array components values for this PtgArray - * returns the actual array components length - * @see ExpressionParser.parseExpression - */ - public int setArrVals(byte[] by) { - rgval = by; - if (rgval != null) { - // clear out record array:0= id 1-7=reserved - byte[] b = new byte[8]; - b[0] = record[0]; - record = b; - this.parseArrayComponents(); - } - return rgval.length; - - } - - public byte[] getArrVals() { - return rgval; - } - - /** - * returns a ptg at the specified location. Assumes that it is a one-dimensional - * array. If you need a multidimensional array please use the other elementAt(int,int)method - * @param loc - * @return - */ - public Ptg elementAt(int loc) { - Ptg[] p = this.getComponents(); - return p[loc]; - } - - public Ptg elementAt(int col, int row) { - Ptg[] p = this.getComponents(); - try { - int loc = 0; - for (int i = 0; i < row; i++) { - loc += (nc); // 20090816 KSC: why +1???? +1); [BugTracker 2683] - } - loc += col; - return elementAt(loc); - } catch (ArrayIndexOutOfBoundsException e) { - Logger.logErr("PtgArray.elementAt: error retrieving value at [" - + row + "," + col + "]: " + e); - } - return null; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4416140231168551393L; + int nc = -1; + int nr = -1; + byte[] rgval; + CompatibleVector arrVals = new CompatibleVector(); + boolean isIntermediary = false; // 20090824 + // KSC: + // true + // if + // this + // PtgArray + // is + // only + // part + // of + // a + // calcualtion + // process; + // if + // so, + // apparently + // can + // have + // more + // than + // 256 + // columns + // [BugTracker + // 2683] + + @Override + public boolean getIsOperand() { + return true; + } + + /** + * return the first 8 bytes of the ptgArray record + * this represents the id byte and 7 reserved bytes + * + * @return + */ + public byte[] getPreRecord() { + return record; + } + + /** + * these are the bytes appended to the formula token array, after all other ptg's + * + * @return + */ + public byte[] getPostRecord() { + return rgval; + } + + @Override + public void init(byte[] b) { + ptgId = b[0]; + record = b; + this.populateVals(); + } + + private void populateVals() { + if (record.length > 8) { // means that array data has already been + // appeneded to end of record array; store + // in rgvals + rgval = new byte[record.length - 8]; + System.arraycopy(record, 8, rgval, 0, rgval.length); // save post + // array= + // nc, nr + + // array + // data + } + if (rgval != null) { + // clear out record array:0= id 1-7=reserved + byte[] b = new byte[8]; + b[0] = record[0]; + record = b; + this.parseArrayComponents(); + } // otherwise, it's just the initial input of the 1st 8 bytes + // record - see Formula + } + + /** + * given "extra info" at end of formula expression, parse array values + */ + public void parseArrayComponents() { + int nitems = 0; + arrVals.clear(); // 20090820 KSC: makes sense to! [BugTracker 2683] + if (!isIntermediary) // 20090824 KSC: sometimes an intermediary ptgarry + // can have more than 256 columns [BugTracker + // 2683] + nc = rgval[0] & 0xFF; // number of columns + nr = ByteTools.readShort(rgval[1], rgval[2]); // number of rows + try { + // (nc+1)*(nr+1) compoments + for (int i = 3; i < rgval.length; ) { // 20090820 KSC: post array + // contains nc & nr so i + // should be initially 3 + // instead of 0 [BugTracker + // 2683] + if (rgval[i] == 0) { // empty value + i++; + i += 8; + arrVals.add(""); // TODO: Empty Constant should be null? + } else if (rgval[i] == 0x1) { // its a number + i++; + byte[] barr = new byte[8]; + System.arraycopy(rgval, i, barr, 0, 8); + double val = ByteTools.eightBytetoLEDouble(barr); + Double d = new Double(val); + arrVals.add(d); + i = i + 8; + } else if (rgval[i] == 0x2) { // its a string + int strLen = ByteTools + .readShort(rgval[i + 1], rgval[i + 2]); + i += 3; + int grbt = rgval[i++]; + byte[] barr = new byte[strLen]; + System.arraycopy(rgval, i, barr, 0, strLen); + String strVal = ""; + try { + if ((grbt & 0x1) == 0x1) { + strVal = new String(barr, UNICODEENCODING); + } else { + strVal = new String(barr, DEFAULTENCODING); + } + } catch (UnsupportedEncodingException e) { + Logger.logInfo("decoding formula string in array failed: " + + e); + } + arrVals.add(strVal); + i += strLen; + } else if (rgval[i] == 0x4) { // its a boolean + if (rgval[++i] == 0) + arrVals.add(Boolean.valueOf(false)); + else + arrVals.add(Boolean.valueOf(true)); + i = i + 8; + } else if (rgval[i] == 0x10) { // it's an error value + int errCode = rgval[++i]; + switch (errCode) { + case 0: + arrVals.add("#NULL!"); + break; + case 0x7: + arrVals.add("#DIV/0!"); + break; + case 0x0F: + arrVals.add("#VALUE!"); + break; + case 0x17: + arrVals.add("#REF!"); + break; + case 0x1D: + arrVals.add("#NAME!"); + break; + case 0x24: + arrVals.add("#NUM!"); + break; + case 0x2A: + arrVals.add("#N/A!"); + break; + } + i = i + 8; + } + nitems++; + if (nitems == ((nc + 1) * (nr + 1))) { // Finished with this + // array! + int length = i; + i = rgval.length; + // length may be less than rgval.length for cases of more + // than one array parameter + // see ExpressionParser.parseExpression + if (rgval.length != length) {// then truncate both record + + // rgval + byte[] tmp = new byte[length]; + System.arraycopy(rgval, 0, tmp, 0, length); + rgval = tmp; + } + } + } + } catch (Exception e) { + Logger.logErr("Error Processing Array Formula: " + e.toString()); + return; + } + } + + public int getVal() { + return -1; + } + + @Override + public Object getValue() { + // 20090820 KSC: value = entire array instead of 1st value; + // desired value is determined by cell position as compared + // to current formula; see Formula.calculate [BugTracker + // 2683] + // return elementAt(0).getValue(-1); // default: return 1st + // value + return getString(); + } + + /* + * returns the string value of the name + * + * @see io.starter.formats.XLS.formulas.Ptg#getValue() + */ + @Override + public String getString() { + Object retVal = null; + Ptg[] p = this.getComponents(); + String retstr = ""; + if (nc == 0 && nr == 0) { // if it's a single value, just return val + for (int i = 0; i < p.length; i++) { + if (i != 0) + retstr += ","; + retstr += p[i].getValue().toString(); + } + } else { + retstr = ""; + int loc = 0; + for (int x = 0; x < nr + 1; x++) { + if (x != 0) + retstr += ";"; + for (int i = 0; i < nc + 1; i++) { + if (i != 0) + retstr += ","; + retstr += p[loc++].getValue().toString(); + } + } + // retstr += "}"; + // retVal = retstr.substring(0,retstr.length()-1); + } + retVal = retstr; + return "{" + retVal + "}"; + } + + @Override + public String getTextString() { + return getString(); + } + + public void setVal(String arrStr) { + // remove the initial { and ending } + arrStr = arrStr.substring(1, arrStr.length() - 1); + if (arrStr.indexOf("{") != -1) { // SHOULDN'T -- see + // FormulaParser.getPtgsFromFormulaString + Logger.logErr("PtgArray.setVal: Multiple Arrays Encountered"); + } + + // parse all array strings into rows, cols + String[] rows = null; + String[][] cols = null; + // split rows + rows = arrStr.split(";"); + cols = new String[rows.length][]; + for (int i = 0; i < rows.length; i++) { + String[] s = rows[i].split(",", -1); // include empty strings + cols[i] = s; + } + byte[] databytes = new byte[11]; + databytes[0] = 0x60; // 20h=tArrayR, 40h=tArrayV, 60h=tArrayA + isIntermediary = false; // init value + if (cols[0].length >= 255) { // 20090824 KSC: apparently sometimes an + // intermediary calculations step can + // include > 256 array elements ... + isIntermediary = true; + nc = cols[0].length - 1; + } + databytes[8] = (byte) ((cols[0].length - 1) & 0xFF); // nc-1 // 20090819 + // KSC: placed + // in wrong pt + // of record: + // was [1] + // [BugTracker + // 2683] + // databytes[8] = (byte)((cols[0].length-1)); // nc-1 // + // 20090819 KSC: placed in wrong pt of record: was [1] + // [BugTracker 2683] + System.arraycopy(ByteTools + .shortToLEBytes((short) (rows.length - 1)), 0, databytes, 9, 2); // nr-1 + // // + // 20090819 + // KSC: + // placed + // in + // wrong + // pt + // of + // record: + // was + // 2,3 + // [BugTracker + // 2683] + // iterate the array and fill out the data section + for (int j = 0; j < rows.length; j++) { + for (int i = 0; i < cols[0].length; i++) { + byte[] valbytes = this.valuesIntoByteArray(cols[j][i]); + databytes = ByteTools.append(valbytes, databytes); + } + } + // populate primary values for rec + record = databytes; + this.init(databytes); + } + + /** + * Turns a vector of values into a byte array representation for the data section of this record + * + * @param compVect + * @return + */ + private byte[] valuesIntoByteArray(String constVal) { + byte[] databytes = new byte[0]; + byte[] thisElement = new byte[9]; + + try { // number? + Double d = new Double(constVal); + thisElement[0] = 0x1; // id for number value + byte[] b = ByteTools.toBEByteArray(d.doubleValue()); + System.arraycopy(b, 0, thisElement, 1, b.length); + databytes = ByteTools.append(thisElement, databytes); + } catch (NumberFormatException ee) { + try { + if (constVal.equalsIgnoreCase("true") + || constVal.equalsIgnoreCase("false")) { + Boolean bb = Boolean.valueOf(constVal); + thisElement[0] = 0x4; // id for boolean value + thisElement[1] = (byte) (bb.booleanValue() ? 1 : 0); + } else if (constVal == null || constVal.equals("")) { // emtpy + // or + // null + // value + thisElement[0] = 0x0; // id for empty value + } else if (constVal.charAt(0) == '#') { // it's an error value + thisElement[0] = 0x10; // id for error value + int errCode = 0; + if (constVal.equals("#NULL!")) + errCode = 0; + else if (constVal.equals("#DIV/0!")) + errCode = 0x7; + else if (constVal.equals("#VALUE!")) + errCode = 0x0F; + else if (constVal.equals("#REF!")) + errCode = 0x17; + else if (constVal.equals("#NAME!")) + errCode = 0x1D; + else if (constVal.equals("#NUM!")) + errCode = 0x24; + else if (constVal.equals("#N/A!") || constVal.equals("#N/A") + || constVal.equals("N/A")) + errCode = 0x2A; + thisElement[1] = (byte) errCode; + } else { // assume string + thisElement = new byte[3]; + try { + thisElement = new byte[4]; + thisElement[0] = 0x2; // id for string + byte[] b = constVal.getBytes(UNICODEENCODING); + System.arraycopy(ByteTools + .shortToLEBytes((short) b.length), 0, thisElement, 1, 2); + thisElement[3] = 1; // compressed= 0, uncompressed= 1 + // (16-bit chars) + thisElement = ByteTools.append(b, thisElement); + } catch (UnsupportedEncodingException z) { + Logger.logWarn("encoding formula array:" + z); + } + } + databytes = ByteTools.append(thisElement, databytes); + } catch (Exception ex) { + Logger.logWarn("PtgArray.valuesIntoByteArray: error parsing array element:" + + ex); + } + } + return databytes; + } + + /** + * Returns the second section of bytes for the PtgArray. + * These are the bytes that are split off the end of the + * formula + + public void getComponentBytes(){ + + } + //public void updateRecord(){ + //}*/ + + /** + * Override due to mystery extra byte + * occasionally found in ptgName recs. + */ + @Override + public int getLength() { + /* + * 20090820 KSC: really want record length not rgval length, + * which now is separate [BugTracker 2683] + * if (rgval!=null) + * return rgval.length; + */ + return 8; + } + + /* + * not used + * public int getLength(byte[] b){ + * int co = b[1]; + * int rw = ByteTools.readShort(b[2], b[3]); + * rw++; // appears that rows are not ordinal here... + * int numrecs = co*rw; + * int len = 4; + * int loc = 4; + * for (int i=0;i<=numrecs;i++){ + * if (b[len] == 0x1){ // its a number + * len += 9; + * }else{ + * len += b[len+1] + 2; + * } + * } + * length = len; + * return length; + * } + */ + + @Override + public String toString() { + return this.getString(); + } + + @Override + public Ptg[] getComponents() { + Ptg[] retVals = new Ptg[arrVals.size()]; + for (int i = 0; i < arrVals.size(); i++) { + Object o = arrVals.elementAt(i); + if (o instanceof Double) { + Double d = (Double) o; + PtgNumber pnum = new PtgNumber(d.doubleValue()); + retVals[i] = pnum; + } else if (o instanceof Boolean) { + PtgBool pb = new PtgBool(((Boolean) o).booleanValue()); + retVals[i] = pb; + } else { + if (FormulaParser.isRef((String) o) + || FormulaParser.isRange((String) o)) { // it's a range + PtgArea3d pa = new PtgArea3d(); + pa.setParentRec(this.getParentRec()); + pa.setUseReferenceTracker(true); + pa.setLocation((String) o); + Ptg[] pacomps = pa.getComponents(); + Ptg[] temp = new Ptg[retVals.length - 1 + pacomps.length]; + System.arraycopy(retVals, 0, temp, 0, retVals.length - 1); + System.arraycopy(pacomps, 0, temp, retVals.length + - 1, pacomps.length); + retVals = temp; + } else { + PtgStr pstr = new PtgStr((String) o); + retVals[i] = pstr; + } + } + } + return retVals; + } + + /** + * returns the 0-based number of rows in this array + * if nr>1 then the array is in the form of: + * a,b,c;d,e,f; .... where the semicolons delineate rows + * + * @return + */ + public int getNumberOfRows() { + return nr; + } + + /** + * returns the 0-based number of columns in this array + * number of columns is the amount of elements before the semicolon (if present) + * a,b,c;d,e,f; .... + * + * @return + */ + public int getNumberOfColumns() { + return nc; + } + + /** + * sets the array components values for this PtgArray + * returns the actual array components length + * + * @see ExpressionParser.parseExpression + */ + public int setArrVals(byte[] by) { + rgval = by; + if (rgval != null) { + // clear out record array:0= id 1-7=reserved + byte[] b = new byte[8]; + b[0] = record[0]; + record = b; + this.parseArrayComponents(); + } + return rgval.length; + + } + + public byte[] getArrVals() { + return rgval; + } + + /** + * returns a ptg at the specified location. Assumes that it is a one-dimensional + * array. If you need a multidimensional array please use the other elementAt(int,int)method + * + * @param loc + * @return + */ + public Ptg elementAt(int loc) { + Ptg[] p = this.getComponents(); + return p[loc]; + } + + public Ptg elementAt(int col, int row) { + Ptg[] p = this.getComponents(); + try { + int loc = 0; + for (int i = 0; i < row; i++) { + loc += (nc); // 20090816 KSC: why +1???? +1); [BugTracker 2683] + } + loc += col; + return elementAt(loc); + } catch (ArrayIndexOutOfBoundsException e) { + Logger.logErr("PtgArray.elementAt: error retrieving value at [" + + row + "," + col + "]: " + e); + } + return null; + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.java index 80b7cce..60c2f30 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,205 +22,226 @@ */ package io.starter.formats.XLS.formulas; +import io.starter.formats.XLS.Formula; +import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.toolkit.ByteTools; -import io.starter.formats.XLS.*; /** - Displays "special" attributes like spaces and "optimized SUMs" - - Offset Size Contents - 0 1 19H - 1 1 Attribute type flags: - 01H = This is a tAttrVolatile token (volatile function) - 02H = This is a tAttrIf token (IF function control) - 04H = This is a tAttrChoose token (CHOOSE function control) - 08H = This is a tAttrSkip token (skip part of token array) - 10H = This is a tAttrSum token (SUM function with one parameter) - 20H = This is a tAttrAssign token (assignment-style formula in a macro sheet) - 40H = This is a tAttrSpace token (spaces and carriage returns, BIFF3-BIFF8) - 41H = This is a tAttrSpaceVolatile token (BIFF3-BIFF8, see below) - 2 var. Additional information dependent on the attribute type - - tAttrSpace: - 0 1 19H - 1 1 40H (identifier for the tAttrSpace token), or - 41H (identifier for the tAttrSpaceVolatile token) - 2 1 Type and position of the inserted character(s): - 00H = Spaces before the next token (not allowed before tParen token) - 01H = Carriage returns before the next token (not allowed before tParen token) - 02H = Spaces before opening parenthesis (only allowed before tParen token) - 03H = Carriage returns before opening parenthesis (only allowed before tParen token) - 04H = Spaces before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) - 05H = Carriage returns before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) - 06H = Spaces following the equality sign (only in macro sheets) - 3 1 Number of inserted spaces or carriage returns - + * Displays "special" attributes like spaces and "optimized SUMs" + *

                + * Offset Size Contents + * 0 1 19H + * 1 1 Attribute type flags: + * 01H = This is a tAttrVolatile token (volatile function) + * 02H = This is a tAttrIf token (IF function control) + * 04H = This is a tAttrChoose token (CHOOSE function control) + * 08H = This is a tAttrSkip token (skip part of token array) + * 10H = This is a tAttrSum token (SUM function with one parameter) + * 20H = This is a tAttrAssign token (assignment-style formula in a macro sheet) + * 40H = This is a tAttrSpace token (spaces and carriage returns, BIFF3-BIFF8) + * 41H = This is a tAttrSpaceVolatile token (BIFF3-BIFF8, see below) + * 2 var. Additional information dependent on the attribute type + *

                + * tAttrSpace: + * 0 1 19H + * 1 1 40H (identifier for the tAttrSpace token), or + * 41H (identifier for the tAttrSpaceVolatile token) + * 2 1 Type and position of the inserted character(s): + * 00H = Spaces before the next token (not allowed before tParen token) + * 01H = Carriage returns before the next token (not allowed before tParen token) + * 02H = Spaces before opening parenthesis (only allowed before tParen token) + * 03H = Carriage returns before opening parenthesis (only allowed before tParen token) + * 04H = Spaces before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) + * 05H = Carriage returns before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) + * 06H = Spaces following the equality sign (only in macro sheets) + * 3 1 Number of inserted spaces or carriage returns + * * @see Ptg * @see Formula + */ +public class PtgAtr extends GenericPtg implements Ptg { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2825828785221803436L; + byte grbit = 0x0; + // int bitAttrSemi = 0; // changed to BitAttrVolatile + int bitAttrVolatile = 0; + int bitAttrIf = 0; + int bitAttrChoose = 0; + int bitAttrGoto = 0; // == bitAttrSkip + int bitAttrSum = 0; + int bitAttrAssign = 0; // changed from bitAttrBaxcel + int bitAttrSpace = 0; + int bitAttrSpaceVolatile = 0; // added + + public PtgAtr() { + } - -*/ -public class PtgAtr extends GenericPtg implements Ptg{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2825828785221803436L; - byte grbit = 0x0; -// int bitAttrSemi = 0; // changed to BitAttrVolatile - int bitAttrVolatile = 0; - int bitAttrIf = 0; - int bitAttrChoose = 0; - int bitAttrGoto = 0; // == bitAttrSkip - int bitAttrSum = 0; - int bitAttrAssign = 0; // changed from bitAttrBaxcel - int bitAttrSpace = 0; - int bitAttrSpaceVolatile= 0; // added - - public PtgAtr(){} public PtgAtr(byte type) { - record= new byte[4]; - record[0]= 0x19; - record[1]= type; + record = new byte[4]; + record[0] = 0x19; + record[1] = type; } - - public String getString(){ + + public String getString() { /* We may not want any text from this record.. */ this.init(); - if(bitAttrIf > 0) return ""; //"IF("; this is already taken care of by another ptg. - if(bitAttrSum > 0) return "SUM("; + if (bitAttrIf > 0) return ""; //"IF("; this is already taken care of by another ptg. + if (bitAttrSum > 0) return "SUM("; // if(bitAttrSemi > 0) return "SEMI("; - if (bitAttrVolatile > 0) return ""; - if (bitAttrAssign > 0) return " EQUALS "; - if(bitAttrChoose > 0) return "CHOOSE("; - if(bitAttrSpace > 0) return " "; - if(bitAttrGoto > 0) return ""; // this may be wrong, but as far as I can tell it is just internal for calc purposes. + if (bitAttrVolatile > 0) return ""; + if (bitAttrAssign > 0) return " EQUALS "; + if (bitAttrChoose > 0) return "CHOOSE("; + if (bitAttrSpace > 0) return " "; + if (bitAttrGoto > 0) + return ""; // this may be wrong, but as far as I can tell it is just internal for calc purposes. return "UNKNOWN("; - - + + } + public String toString() { return this.getString() + this.getString2(); } - /** return the human-readable String representation of - the "closing" portion of this Ptg - such as a closing parenthesis. - */ - public String getString2(){ - if(bitAttrIf > 0) return ""; //")"; this is already taken care of by another ptg. - if(bitAttrSum > 0) return ")"; + + /** + * return the human-readable String representation of + * the "closing" portion of this Ptg + * such as a closing parenthesis. + */ + public String getString2() { + if (bitAttrIf > 0) return ""; //")"; this is already taken care of by another ptg. + if (bitAttrSum > 0) return ")"; // if(bitAttrSemi > 0) return ")"; - if(bitAttrVolatile > 0) return ""; - if(bitAttrAssign > 0) return ")"; - if(bitAttrChoose > 0) return ")"; - if(bitAttrGoto > 0) return ""; // this may be wrong, but as far as I can tell it is just internal for calc purposes. + if (bitAttrVolatile > 0) return ""; + if (bitAttrAssign > 0) return ")"; + if (bitAttrChoose > 0) return ")"; + if (bitAttrGoto > 0) + return ""; // this may be wrong, but as far as I can tell it is just internal for calc purposes. return ""; - } - - public boolean getIsControl(){ + } + + public boolean getIsControl() { this.init(); - if(getIsPrimitiveOperator())return false; + if (getIsPrimitiveOperator()) return false; /* TODO: Rework bitAttrIf. It optimizes the calculation of if statements - * should not normally be a big deal, but saves the calculation of one of - * the result fields if needed.*/ - if(bitAttrIf > 0)return false; - if(bitAttrSum > 0)return true; + * should not normally be a big deal, but saves the calculation of one of + * the result fields if needed.*/ + if (bitAttrIf > 0) return false; + if (bitAttrSum > 0) return true; // if(bitAttrSemi > 0)return false; - if(bitAttrVolatile> 0)return false; - if(bitAttrAssign > 0)return true; - if(bitAttrGoto > 0)return false; - if(bitAttrChoose > 0)return false; + if (bitAttrVolatile > 0) return false; + if (bitAttrAssign > 0) return true; + if (bitAttrGoto > 0) return false; + if (bitAttrChoose > 0) return false; return false; } - - /** is the space special -- does it go between vars? - for now we say sure why not. - */ - public boolean getIsPrimitiveOperator(){ + + /** + * is the space special -- does it go between vars? + * for now we say sure why not. + */ + public boolean getIsPrimitiveOperator() { this.init(); - if(bitAttrSpace > 0)return true; - return false; + return bitAttrSpace > 0; + } + + public boolean getIsUnaryOperator() { + if (bitAttrIf > 0) return false; + return bitAttrChoose <= 0; } - - public boolean getIsUnaryOperator(){ - if(bitAttrIf > 0)return false; - if(bitAttrChoose > 0)return false; - return true; - } - - - public boolean getIsOperator(){ + + + public boolean getIsOperator() { return false; } - - public boolean getIsSpace(){ - if(bitAttrSpace > 0)return true; - return false; + + public boolean getIsSpace() { + return bitAttrSpace > 0; } - public boolean getIsOperand(){ - // - // if(bitAttrSpace > 0)return true; + + public boolean getIsOperand() { + // + // if(bitAttrSpace > 0)return true; // Old version? if(bitAttrSemi > 0)return true; // it just shows that this is a volatile function - if(bitAttrVolatile > 0)return true; // it just shows that this is a volatile function - - return false; + return bitAttrVolatile > 0; // it just shows that this is a volatile function } - + /* Sets the grbit for the record *///[25, 2, 10, 0] grbit= 2 - public void init(){ + public void init() { grbit = this.getRecord()[1]; /* john, the following syntax was not reliable, switched with syntax below.... bitAttrIf = ((grbit & 0x2) >> 4); */ // 20060501 KSC: Changed bitAttrVolatile operation + some names // if ((grbit & 0x1)== 0x1){bitAttrSemi = 1;} - if ((grbit & 0x1)== 0x1){bitAttrVolatile = 1;} // volatile= a function that needs to be recalculated always, such as NOW() - if ((grbit & 0x2)== 0x2){bitAttrIf = 1;} - if ((grbit & 0x4)== 0x4){bitAttrChoose = 1;} - if ((grbit & 0x8)== 0x8){bitAttrGoto = 1;} - if ((grbit & 0x10)== 0x10){bitAttrSum = 1;} - if ((grbit & 0x20)== 0x20){bitAttrAssign = 1;} // changed name from bitAttrBaxcel - if ((grbit & 0x40)== 0x40){bitAttrSpace = 1;} - if ((grbit & 0x41)== 0x41){bitAttrSpaceVolatile = 1;} - + if ((grbit & 0x1) == 0x1) { + bitAttrVolatile = 1; + } // volatile= a function that needs to be recalculated always, such as NOW() + if ((grbit & 0x2) == 0x2) { + bitAttrIf = 1; + } + if ((grbit & 0x4) == 0x4) { + bitAttrChoose = 1; + } + if ((grbit & 0x8) == 0x8) { + bitAttrGoto = 1; + } + if ((grbit & 0x10) == 0x10) { + bitAttrSum = 1; + } + if ((grbit & 0x20) == 0x20) { + bitAttrAssign = 1; + } // changed name from bitAttrBaxcel + if ((grbit & 0x40) == 0x40) { + bitAttrSpace = 1; + } + if ((grbit & 0x41) == 0x41) { + bitAttrSpaceVolatile = 1; + } + } - - /** return the human-readable String representation of - this ptg -- if applicable - - public String getString(){ - byte[] br = this.getRecord(); - byte[] db = new byte[br.length-1]; // strip opcode - System.arraycopy(br, 1, db, 0, db.length); - return ""; // new String(db); - } - */ - public int getLength(byte[] b){ - if ((b[0] & 0x4)== 0x4){ + + /** + * return the human-readable String representation of + * this ptg -- if applicable + *

                + * public String getString(){ + * byte[] br = this.getRecord(); + * byte[] db = new byte[br.length-1]; // strip opcode + * System.arraycopy(br, 1, db, 0, db.length); + * return ""; // new String(db); + * } + */ + public int getLength(byte[] b) { + if ((b[0] & 0x4) == 0x4) { int i = ByteTools.readShort(b[1], b[2]); i += 4; - return i; + return i; } return getLength(); } - - public int getLength(){ - return PTG_ATR_LENGTH; + + public int getLength() { + return PTG_ATR_LENGTH; } - - + + Ptg[] alloperands = null; // cached! + /* Calculate the value of this ptg. */ public Ptg calculatePtg(Ptg[] pthing) - throws FunctionNotSupportedException, CalculationException{ + throws FunctionNotSupportedException, CalculationException { Ptg returnPtg = null; - if (this.bitAttrSum > 0){ - returnPtg = MathFunctionCalculator.calcSum(pthing); + if (this.bitAttrSum > 0) { + returnPtg = MathFunctionCalculator.calcSum(pthing); } return returnPtg; } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgBool.java b/src/main/java/io/starter/formats/XLS/formulas/PtgBool.java index 1dcc655..9dca64f 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgBool.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgBool.java @@ -2,25 +2,26 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; + import io.starter.formats.XLS.ExpressionParser; /* @@ -28,75 +29,79 @@ the PtgID (0x1D) and a byte representing the boolean value (0 or 1); */ -public class PtgBool extends GenericPtg implements Ptg -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7270271326251770439L; +public class PtgBool extends GenericPtg implements Ptg { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7270271326251770439L; + + public boolean getIsOperator() { + return false; + } + + public boolean getIsOperand() { + return true; + } - public boolean getIsOperator(){return false;} - public boolean getIsOperand(){return true;} boolean val = false; - - /** return the human-readable String representation of the ptg - */ - public String getString(){ + + /** + * return the human-readable String representation of the ptg + */ + public String getString() { return String.valueOf(val); } - - public String toString(){ - return String.valueOf(val); + + public String toString() { + return String.valueOf(val); } - - public Object getValue(){ + + public Object getValue() { Boolean b = Boolean.valueOf(val); - return b; + return b; + } + + public void setVal(boolean boo) { + val = boo; + this.updateRecord(); } - public void setVal(boolean boo){ - val = boo; - this.updateRecord(); - } - public void init(byte[] rec){ + + public void init(byte[] rec) { this.record = rec; ptgId = rec[0]; - if (rec[1] == 0){ - val = false; - }else{ - val = true; - } + val = rec[1] != 0; } - - public PtgBool(){ + + public PtgBool() { record = new byte[2]; ptgId = ExpressionParser.ptgBool; record[0] = ptgId; } - - - public PtgBool(boolean b){ + + + public PtgBool(boolean b) { ptgId = ExpressionParser.ptgBool; val = b; this.updateRecord(); } - - public boolean getBooleanValue(){ - return val; + + public boolean getBooleanValue() { + return val; } - - public void updateRecord(){ + + public void updateRecord() { record = new byte[2]; record[0] = ptgId; - if (val){ + if (val) { record[1] = 1; - }else{ + } else { record[1] = 0; } } - - public int getLength(){ + + public int getLength() { return PTG_BOOL_LENGTH; } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.java index 7f641cf..0c0a7d7 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.java @@ -2,233 +2,232 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; + +import io.starter.OpenXLS.ExcelTools; import io.starter.toolkit.CompatibleVector; import io.starter.toolkit.FastAddVector; import io.starter.toolkit.Logger; import java.util.Enumeration; -import io.starter.OpenXLS.ExcelTools; /** - PtgCalculator handles some of the standard calls that all of the - calculator classes need. + * PtgCalculator handles some of the standard calls that all of the + * calculator classes need. + */ -*/ +public class PtgCalculator { -public class PtgCalculator -{ - - - /** getLongValue is for single-operand functions. - It returns NaN for calculations that have too many operands. - - * + /** + * getLongValue is for single-operand functions. + * It returns NaN for calculations that have too many operands. + * * @param operands * @return */ - protected static long getLongValue(Ptg[] operands){ - Ptg[] components= operands[0].getComponents(); - if(components!=null){ // check if too many operands TODO: check that ONE is ok?? - if(components.length >1){ - return (long)Double.NaN; - } - } - if (operands.length >1){ // not supported by function - Logger.logWarn("PtgCalculator getting Long Value for operand failed: - UNSUPPORTED BY FUNCTION"); - return (long)Double.NaN; - } - Double d = null; - try{ - d = new Double(operands[0].getDoubleVal()); - }catch(NumberFormatException e){ - Logger.logWarn("PtgCalculator getting Long Value for operand failed: " + e); - return (long)Double.NaN; - } - return d.longValue(); - } - - /* - * See getLongValue(operand[]) - * Does the same thing with a single operand - */ - protected static long getLongValue(Ptg operand){ - Ptg[] ptgArr = new Ptg[1]; - ptgArr[0] = operand; - return getLongValue(ptgArr); - } - - - // returns an array of longs from an array of ptg's - protected static long[] getLongValueArray(Ptg[] operands){ - Ptg[] alloperands = getAllComponents(operands); - long[] l = new long[alloperands.length]; - for (int i=0;i 1) { + return (long) Double.NaN; + } + } + if (operands.length > 1) { // not supported by function + Logger.logWarn("PtgCalculator getting Long Value for operand failed: - UNSUPPORTED BY FUNCTION"); + return (long) Double.NaN; + } + Double d = null; + try { + d = new Double(operands[0].getDoubleVal()); + } catch (NumberFormatException e) { + Logger.logWarn("PtgCalculator getting Long Value for operand failed: " + e); + return (long) Double.NaN; + } + return d.longValue(); + } - /** - getDoubleValue is for multi-operand functions. It returns NaN - for calculations that have to many operands. - - * - * - * @param operands - * @return - * @throws CircularReferenceException TODO - */ - protected static double[] getDoubleValueArray(Ptg[] operands) throws CalculationException - { + /* + * See getLongValue(operand[]) + * Does the same thing with a single operand + */ + protected static long getLongValue(Ptg operand) { + Ptg[] ptgArr = new Ptg[1]; + ptgArr[0] = operand; + return getLongValue(ptgArr); + } - Double d = null; - // we don't know the size ahead of time, so use a vector for now. - CompatibleVector cv = new CompatibleVector(); - - for (int i=0;i ASSUMPTIONS: - *
                1- accepts only 2-d references i.e. ranges are on 1 sheet - *
                2- assumes that range reference are in proper notation i.e A1:B6, NOT B6:A1 - * @param operand Ptg - * @return double[][] - */ - protected static double[][] getArray(Ptg operand) throws Exception{ - int nrows, ncols; - double[][] arr= null; - - if (operand instanceof PtgRef) { - int[] rc= ((PtgRef) operand).getIntLocation(); - String sheet= ((PtgRef)operand).getSheetName(); - io.starter.formats.XLS.WorkBook bk= operand.getParentRec().getWorkBook(); - nrows= rc[2]-rc[0]+1; - ncols= rc[3]-rc[1]+1; - arr= new double[nrows][ncols]; - for (int j= rc[1]; j <= rc[3]; j++) { - for (int i= rc[0]; i <= rc[2]; i++) { - String cell= ExcelTools.formatLocation(new int[] {i, j}); - arr[i-rc[0]][j-rc[1]]= bk.getCell(sheet, cell).getDblVal(); - } - } - - } else { // should be an array - String arrStr= ((PtgArray) operand).toString().substring(1); - arrStr= arrStr.substring(0, arrStr.length()-1); - String[] rows= arrStr.split(";"); - arr= new double[rows.length][]; - for (int i= 0; i < rows.length; i++) { - String[] s= rows[i].split(",", -1); // include empty strings - arr[i]= new double[s.length]; - for (int j= 0; j < s.length; j++) { - arr[i][j]= new Double(s[j]).doubleValue(); - } - } - } - return arr; - } - /* + } catch (Exception ee) { + Logger.logWarn("PtgCalculator getting Double value array failed: " + ee); + d = new Double(Double.NaN); + } + } + if (d != null) + darr[i] = d.doubleValue(); + } + i++; + } + return darr; + } + + protected static double[] getDoubleValueArray(Ptg operands) throws CalculationException { + Ptg[] ptgarr = new Ptg[1]; + ptgarr[0] = operands; + return getDoubleValueArray(ptgarr); + } + + /** + * return a 2-dimenional array of double values + * i.e. keep array structure of reference and array type parameters + *
                ASSUMPTIONS: + *
                1- accepts only 2-d references i.e. ranges are on 1 sheet + *
                2- assumes that range reference are in proper notation i.e A1:B6, NOT B6:A1 + * + * @param operand Ptg + * @return double[][] + */ + protected static double[][] getArray(Ptg operand) throws Exception { + int nrows, ncols; + double[][] arr = null; + + if (operand instanceof PtgRef) { + int[] rc = ((PtgRef) operand).getIntLocation(); + String sheet = ((PtgRef) operand).getSheetName(); + io.starter.formats.XLS.WorkBook bk = operand.getParentRec().getWorkBook(); + nrows = rc[2] - rc[0] + 1; + ncols = rc[3] - rc[1] + 1; + arr = new double[nrows][ncols]; + for (int j = rc[1]; j <= rc[3]; j++) { + for (int i = rc[0]; i <= rc[2]; i++) { + String cell = ExcelTools.formatLocation(new int[]{i, j}); + arr[i - rc[0]][j - rc[1]] = bk.getCell(sheet, cell).getDblVal(); + } + } + + } else { // should be an array + String arrStr = operand.toString().substring(1); + arrStr = arrStr.substring(0, arrStr.length() - 1); + String[] rows = arrStr.split(";"); + arr = new double[rows.length][]; + for (int i = 0; i < rows.length; i++) { + String[] s = rows[i].split(",", -1); // include empty strings + arr[i] = new double[s.length]; + for (int j = 0; j < s.length; j++) { + arr[i][j] = new Double(s[j]).doubleValue(); + } + } + } + return arr; + } + + /* * creates a generic error ptg */ - protected static Ptg getError(){ + protected static Ptg getError() { PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; + return perr; } - + /** * returns an #VALUE! error ptg + * * @return */ protected static Ptg getValueError() { return new PtgErr(PtgErr.ERROR_VALUE); } - + /** * returns an #NA! error ptg + * * @return */ protected static Ptg getNAError() { @@ -236,59 +235,59 @@ protected static Ptg getNAError() { } - /* - * Get all components recurses through the ptg's and returns an array + /* + * Get all components recurses through the ptg's and returns an array * of single ptg's for all the ptgs in the operands array. This means it * converts arrays to ref's, etc. */ - protected static Ptg[] getAllComponents(Ptg[] operands){ - - if(operands.length==1) { + protected static Ptg[] getAllComponents(Ptg[] operands) { + + if (operands.length == 1) { Ptg[] ret = operands[0].getComponents(); - if(ret == null) - return operands; + if (ret == null) + return operands; + } + + FastAddVector v = new FastAddVector(); + for (int i = 0; i < operands.length; i++) { + Ptg[] pthings = operands[i].getComponents(); // optimized -- do it once! -jm + if (pthings != null) { + for (int x = 0; x < pthings.length; x++) { + v.add(pthings[x]); + } + } else { + v.add(operands[i]); + } + } + Ptg[] res = new Ptg[v.size()]; + res = (Ptg[]) v.toArray(res); + return res; + } + + /* + * Get all components recurses through the ptg's and returns an array + * of single ptg's for all the ptgs in the operands array. This means it + * converts arrays to ref's, etc. + */ + protected static Ptg[] getAllComponents(Ptg operand) { + Ptg[] ptgArr = new Ptg[1]; + ptgArr[0] = operand; + return getAllComponents(ptgArr); + } + + /* + * Returns the boolean value of a PTG. If no boolean available then it returns false + * Add more types here if they are available/needed + */ + protected static boolean getBooleanValue(Ptg operand) { + if (operand instanceof PtgBool) { + PtgBool b = (PtgBool) operand; + return b.getBooleanValue(); + } else if (operand instanceof PtgInt) { + PtgInt i = (PtgInt) operand; + return i.getBooleanVal(); } - - FastAddVector v = new FastAddVector(); - for (int i=0;i. @@ -22,10 +22,6 @@ */ package io.starter.formats.XLS.formulas; -import io.starter.toolkit.Logger; - - - /* Ptg that is a Concatenation Operand. Appends the top operand to the second-to-top Operand @@ -35,73 +31,84 @@ */ -public class PtgConcat extends GenericPtg implements Ptg -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6671404163121438253L; +public class PtgConcat extends GenericPtg implements Ptg { - public boolean getIsOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} // 20060512 KSC: added - - public PtgConcat(){ - ptgId = 0x8; - record = new byte[1]; - record[0] = 0x8; - } - /** return the human-readable String representation of - */ - public String getString(){ + /** + * serialVersionUID + */ + private static final long serialVersionUID = 6671404163121438253L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } // 20060512 KSC: added + + public PtgConcat() { + ptgId = 0x8; + record = new byte[1]; + record[0] = 0x8; + } + + /** + * return the human-readable String representation of + */ + public String getString() { // return "CONCAT("; // 20060512 KSC: mod return "&"; } - - public String getString2(){ + + public String getString2() { // return ")"; - return ""; + return ""; } - - public int getLength(){ + + public int getLength() { return PTG_CONCAT_LENGTH; } - public String toString() { // KSC added + + public String toString() { // KSC added return this.getString(); } - - /** Operator specific calculate method, this Concatenates two values - */ - public Ptg calculatePtg(Ptg[] form){ - try { - Object[] o= super.getStringValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o==null || o.length != 2){ - //if (o!=null) - // Logger.logWarn("calculating formula failed, wrong number of values in PtgConcat"); - return new PtgErr(PtgErr.ERROR_VALUE); + + /** + * Operator specific calculate method, this Concatenates two values + */ + public Ptg calculatePtg(Ptg[] form) { + try { + Object[] o = getStringValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o == null || o.length != 2) { + //if (o!=null) + // Logger.logWarn("calculating formula failed, wrong number of values in PtgConcat"); + return new PtgErr(PtgErr.ERROR_VALUE); + } + if (!o[0].getClass().isArray()) { + String[] s = new String[2]; + try { // 20090216 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 + s[0] = String.valueOf(((Double) o[0]).intValue()); + } catch (Exception e) { + s[0] = o[0].toString(); + } + try { // 20090216 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 + s[1] = String.valueOf(((Double) o[1]).intValue()); + } catch (Exception e) { + s[1] = o[1].toString(); + } + + String returnVal = s[0] + s[1]; + PtgStr pstr = new PtgStr(returnVal); + return pstr; + } else { + return null; } - if (!o[0].getClass().isArray()) { - String[] s= new String[2]; - try { // 20090216 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 - s[0]= String.valueOf(((Double) o[0]).intValue()); - } catch (Exception e) { - s[0]= o[0].toString(); - } - try { // 20090216 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 - s[1]= String.valueOf(((Double) o[1]).intValue()); - } catch (Exception e) { - s[1]= o[1].toString(); - } - - String returnVal = s[0] + s[1]; - PtgStr pstr = new PtgStr(returnVal); - return pstr; - } else { - return null; - } - }catch(Exception e) { // handle error ala Excel + } catch (Exception e) { // handle error ala Excel PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); return perr; } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.java b/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.java index 3b1ea1c..aa668cf 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,9 +24,6 @@ import java.lang.reflect.Array; -import io.starter.toolkit.Logger; - - /* Ptg that is a division operand @@ -36,101 +33,110 @@ */ -public final class PtgDiv extends GenericPtg implements Ptg -{ +public final class PtgDiv extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4046772548262378126L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4046772548262378126L; - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - public PtgDiv(){ - ptgId = 0x6; - record = new byte[1]; - record[0] = 0x6; - } - - /** return the human-readable String representation of - */ - public String getString(){ + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public PtgDiv() { + ptgId = 0x6; + record = new byte[1]; + record[0] = 0x6; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return "/"; } + public String toString() { return this.getString(); } - - public int getLength(){ + + public int getLength() { return PTG_DIV_LENGTH; } - - /* Operator specific calculate method, this one divides two values. - - */ - public Ptg calculatePtg(Ptg[] form){ - try { - Object[] o= super.getValuesFromPtgs(form); - if (o==null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o.length != 2){ - //Logger.logInfo("calculating formula, wrong number of values in PtgDiv"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null - } - double o0= 0, o1= 0; - try { - o0= getDoubleValue(o[0], this.parent_rec); - o1= getDoubleValue(o[1], this.parent_rec); - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - if (o1==0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - double returnVal = (o0 / o1); - // create a container ptg for these. - PtgNumber n = new PtgNumber(returnVal); - return n; - } else { // handle array formulas - String retArry= ""; - int nArrays= java.lang.reflect.Array.getLength(o); - if (nArrays!=2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals= java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i= 0; i < nArrays-1; i+=2) { - Object secondOp= null; - boolean comparitorIsArray= o[i+1].getClass().isArray(); - if (!comparitorIsArray) secondOp= o[i+1]; - for (int j= 0; j < nVals; j++) { - Object firstOp= Array.get(o[i],j); // first array index j - if (comparitorIsArray) - secondOp= Array.get(o[i+1],j); // second array index j - double o0= 0, o1= 0; - try { - o0= getDoubleValue(firstOp, this.parent_rec); - o1= getDoubleValue(secondOp, this.parent_rec); - } catch (NumberFormatException e) { - retArry= retArry + "#VALUE!" + ","; - continue; - } - if (o1!=0) { - double retVal= o0/o1; - retArry= retArry + retVal + ","; - } else - retArry= retArry + "#DIV/0!" + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } - }catch(NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - }catch(Exception e) { // 20081125 KSC: handle error ala Excel + + /* Operator specific calculate method, this one divides two values. + + */ + public Ptg calculatePtg(Ptg[] form) { + try { + Object[] o = getValuesFromPtgs(form); + if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) + if (!o[0].getClass().isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o.length != 2) { + //Logger.logInfo("calculating formula, wrong number of values in PtgDiv"); + return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null + } + double o0 = 0, o1 = 0; + try { + o0 = getDoubleValue(o[0], this.parent_rec); + o1 = getDoubleValue(o[1], this.parent_rec); + } catch (NumberFormatException e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + if (o1 == 0) + return new PtgErr(PtgErr.ERROR_DIV_ZERO); + double returnVal = (o0 / o1); + // create a container ptg for these. + PtgNumber n = new PtgNumber(returnVal); + return n; + } else { // handle array formulas + String retArry = ""; + int nArrays = java.lang.reflect.Array.getLength(o); + if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); + int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays + for (int i = 0; i < nArrays - 1; i += 2) { + Object secondOp = null; + boolean comparitorIsArray = o[i + 1].getClass().isArray(); + if (!comparitorIsArray) secondOp = o[i + 1]; + for (int j = 0; j < nVals; j++) { + Object firstOp = Array.get(o[i], j); // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j); // second array index j + double o0 = 0, o1 = 0; + try { + o0 = getDoubleValue(firstOp, this.parent_rec); + o1 = getDoubleValue(secondOp, this.parent_rec); + } catch (NumberFormatException e) { + retArry = retArry + "#VALUE!" + ","; + continue; + } + if (o1 != 0) { + double retVal = o0 / o1; + retArry = retArry + retVal + ","; + } else + retArry = retArry + "#DIV/0!" + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } + } catch (NumberFormatException e) { + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } catch (Exception e) { // 20081125 KSC: handle error ala Excel PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); return perr; } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.java b/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.java index 0271a52..75f66a8 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,10 +22,9 @@ */ package io.starter.formats.XLS.formulas; -import java.lang.reflect.Array; - import io.starter.toolkit.Logger; +import java.lang.reflect.Array; /* @@ -39,124 +38,135 @@ */ -public class PtgEQ extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5446048862531696036L; - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - public PtgEQ(){ - ptgId = 0xB; - record = new byte[1]; - record[0] = 0xB; - } - - /** return the human-readable String representation of - */ - public String getString(){ +public class PtgEQ extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5446048862531696036L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public PtgEQ() { + ptgId = 0xB; + record = new byte[1]; + record[0] = 0xB; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return "="; } public String toString() { return this.getString(); } - - public int getLength(){ + + public int getLength() { return PTG_EQ_LENGTH; } - /* Operator specific calculate method, this one determines if the second-to-top - operand is equal to the top operand; Returns a PtgBool - - */ - public Ptg calculatePtg(Ptg[] form){ + + /* Operator specific calculate method, this one determines if the second-to-top + operand is equal to the top operand; Returns a PtgBool + + */ + public Ptg calculatePtg(Ptg[] form) { boolean res = false; - // there should always be only two ptg's in this, error if not. - if (form.length != 2){ - Logger.logInfo("calculating formula, wrong number of values in PtgEQ"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel - } - // check for null referenced values, a null reference is equal to the string ""; - Object[] o= super.getValuesFromPtgs(form); - if (o==null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (o[1].getClass().isArray() && !o[0].getClass().isArray()) { - Object tmp= o[0]; - o[0]= o[1]; - o[1]= tmp; - } - if (!o[0].getClass().isArray()) { - if (o.length != 2) return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; - // blank handling: - // determine if any of the operands are double - if true, - // then blank comparisons will be treated as 0's - boolean isDouble= false; - for (int i= 0; i < 2 && !isDouble; i++) { - //if (!form[i].isBlank()) - isDouble= ((o[i] instanceof Double)); - } - for(int i=0;i<2;i++){ - //if (form[i].isBlank()) { - if (o[i]!=null && o[i].toString().equals("")) { - if (isDouble) - o[i]= new Double(0.0); - else - o[i]=""; // in this case, empty cells are handled as blank, not zero - } - } - if (o[0]==o[1]) - res= true; - else if (o[0]==null || o[1]==null) - res= false; - else if (o[0] instanceof Double && o[1] instanceof Double) - res= (Math.abs((((Double)o[0]).doubleValue())-((Double)o[1]).doubleValue())). @@ -23,7 +23,6 @@ package io.starter.formats.XLS.formulas; - /* Undocumented PTG, the only thing we know is "ptg DELETED" from MEFF pg 446. I am going to treat @@ -33,35 +32,32 @@ * @see Formula */ -public class PtgEndSheet extends GenericPtg implements Ptg -{ - // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2395432053363123361L; +public class PtgEndSheet extends GenericPtg implements Ptg { + // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2395432053363123361L; - public boolean getIsOperand(){ return true;} - /** return the human-readable String representation of - */ - public String getString(){ + public boolean getIsOperand() { + return true; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return "End Sheet Error"; - } - - public Object getValue(){ - return "#REF!"; } - - public int getLength(){ + + public Object getValue() { + return "#REF!"; + } + + public int getLength() { return PTG_ENDSHEET_LENGTH; } - - - - - - - - + + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgErr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgErr.java index 8572efd..60d046e 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgErr.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgErr.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -38,109 +38,109 @@ * ------------------------------------------- * 0 err 1 An error value * - * + * */ public class PtgErr extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5201871987022621869L; - public static byte ERROR_NULL = 0x0; - public static byte ERROR_DIV_ZERO = 0x7; - public static byte ERROR_VALUE = 0xF; - public static byte ERROR_REF = 0x17; - public static byte ERROR_NAME = 0x1D; - public static byte ERROR_NUM = 0x24; - public static byte ERROR_NA = 0x2A; - - private boolean isCircularError = false; - - public boolean isCircularError() { - return isCircularError; - } - - public void setCircularError(boolean isCircularError) { - this.isCircularError = isCircularError; - } - - private String errorValue = null; - - @Override - public boolean getIsOperand() { - return true; - } - - public PtgErr() { - // default constructor - } - - public PtgErr(byte errorV) { - record = new byte[2]; - record[0] = 0x1C; - record[1] = errorV; - } - - public byte getErrorType() { - return record[1]; - } - - @Override - public Object getValue() { - return toString(); - } - - @Override - public void init(byte[] b) { - ptgId = b[0]; - record = b; - } - - @Override - public String toString() { - if (isCircularError()) - return "#CIR_ERR!"; - byte b = record[1]; - // duh, should have done a switch - if (b == ERROR_NULL) - errorValue = "#ERROR!"; - else if (b == ERROR_DIV_ZERO) - errorValue = "#DIV/0!"; - else if (b == ERROR_VALUE) - errorValue = "#VALUE!"; - else if (b == ERROR_REF) - errorValue = "#REF!"; - else if (b == ERROR_NAME) - errorValue = "#NAME?"; - else if (b == ERROR_NUM) - errorValue = "#NUM!"; - else if (b == ERROR_NA) - errorValue = "#N/A"; - return errorValue; - } - - public static byte convertStringToLookupByte(String errorString) { - if (errorString.equals("#ERROR!")) - return ERROR_NULL; - if (errorString.equals("#DIV/0!")) - return ERROR_DIV_ZERO; - if (errorString.equals("#REF!")) - return ERROR_VALUE; - if (errorString.equals("#ERROR!")) - return ERROR_REF; - if (errorString.equals("#NAME?")) - return ERROR_NAME; - if (errorString.equals("#NUM!")) - return ERROR_NUM; - if (errorString.equals("#N/A")) - return ERROR_NA; - return ERROR_NULL; - } - - @Override - public int getLength() { - return 2; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -5201871987022621869L; + public static byte ERROR_NULL = 0x0; + public static byte ERROR_DIV_ZERO = 0x7; + public static byte ERROR_VALUE = 0xF; + public static byte ERROR_REF = 0x17; + public static byte ERROR_NAME = 0x1D; + public static byte ERROR_NUM = 0x24; + public static byte ERROR_NA = 0x2A; + + private boolean isCircularError = false; + + public boolean isCircularError() { + return isCircularError; + } + + public void setCircularError(boolean isCircularError) { + this.isCircularError = isCircularError; + } + + private String errorValue = null; + + @Override + public boolean getIsOperand() { + return true; + } + + public PtgErr() { + // default constructor + } + + public PtgErr(byte errorV) { + record = new byte[2]; + record[0] = 0x1C; + record[1] = errorV; + } + + public byte getErrorType() { + return record[1]; + } + + @Override + public Object getValue() { + return toString(); + } + + @Override + public void init(byte[] b) { + ptgId = b[0]; + record = b; + } + + @Override + public String toString() { + if (isCircularError()) + return "#CIR_ERR!"; + byte b = record[1]; + // duh, should have done a switch + if (b == ERROR_NULL) + errorValue = "#ERROR!"; + else if (b == ERROR_DIV_ZERO) + errorValue = "#DIV/0!"; + else if (b == ERROR_VALUE) + errorValue = "#VALUE!"; + else if (b == ERROR_REF) + errorValue = "#REF!"; + else if (b == ERROR_NAME) + errorValue = "#NAME?"; + else if (b == ERROR_NUM) + errorValue = "#NUM!"; + else if (b == ERROR_NA) + errorValue = "#N/A"; + return errorValue; + } + + public static byte convertStringToLookupByte(String errorString) { + if (errorString.equals("#ERROR!")) + return ERROR_NULL; + if (errorString.equals("#DIV/0!")) + return ERROR_DIV_ZERO; + if (errorString.equals("#REF!")) + return ERROR_VALUE; + if (errorString.equals("#ERROR!")) + return ERROR_REF; + if (errorString.equals("#NAME?")) + return ERROR_NAME; + if (errorString.equals("#NUM!")) + return ERROR_NUM; + if (errorString.equals("#N/A")) + return ERROR_NA; + return ERROR_NULL; + } + + @Override + public int getLength() { + return 2; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgExp.java b/src/main/java/io/starter/formats/XLS/formulas/PtgExp.java index 6c4ee21..7354459 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgExp.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgExp.java @@ -2,274 +2,290 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; + import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.*; +import io.starter.toolkit.ByteTools; import java.util.Stack; -/** ptgExp indicates an Array Formula or Shared Formula - - When ptgExp occurs in a formula, it's the only token in the formula. - this indicates that the cell containing the formula - is part of an array or opartof a shared formula. - The actual formula is found in an array record. - - The value for ptgExp consists of the row and the column of the - upper-left corner of the array formula. - +/** + * ptgExp indicates an Array Formula or Shared Formula + *

                + * When ptgExp occurs in a formula, it's the only token in the formula. + * this indicates that the cell containing the formula + * is part of an array or opartof a shared formula. + * The actual formula is found in an array record. + *

                + * The value for ptgExp consists of the row and the column of the + * upper-left corner of the array formula. + * * @see Ptg * @see Formula * @see Array * @see Shrfmla -*/ -public class PtgExp extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2150560716287810448L; - int rwFirst; + */ +public class PtgExp extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2150560716287810448L; + int rwFirst; int colFirst; - - public boolean getIsControl(){return true;} - public boolean getIsStandAloneOperator(){return true;} - - public int getLength(){ + + public boolean getIsControl() { + return true; + } + + public boolean getIsStandAloneOperator() { + return true; + } + + public int getLength() { return PTG_EXP_LENGTH; } - - public int getRwFirst(){ - return rwFirst; + + public int getRwFirst() { + return rwFirst; + } + + public int getColFirst() { + return colFirst; } - public int getColFirst(){ - return colFirst; - } - - /** - * init from row, col - * @param row - * @param col - */ - public void init(int row, int col) { - byte[] r= ByteTools.shortToLEBytes((short) row); - byte[] c= ByteTools.shortToLEBytes((short) col); - record= new byte[] {0x1, r[0], r[1], c[0], c[1] }; - ptgId= record[0]; - this.populateVals(); + + /** + * init from row, col + * + * @param row + * @param col + */ + public void init(int row, int col) { + byte[] r = ByteTools.shortToLEBytes((short) row); + byte[] c = ByteTools.shortToLEBytes((short) col); + record = new byte[]{0x1, r[0], r[1], c[0], c[1]}; + ptgId = record[0]; + this.populateVals(); //this.addToReferenceTracker(); - } - public void init(byte[] b){ - ptgId = b[0]; - record = b; - this.populateVals(); + } + + public void init(byte[] b) { + ptgId = b[0]; + record = b; + this.populateVals(); //this.addToReferenceTracker(); - } + } - private void populateVals(){ - rwFirst =readRow(record[1], record[2]); - colFirst = ByteTools.readShort(record[3], record[4]); - } - - /** Returns the location this PtgExp points to. + private void populateVals() { + rwFirst = readRow(record[1], record[2]); + colFirst = ByteTools.readShort(record[3], record[4]); + } + + /** + * Returns the location this PtgExp points to. */ - public String getReferent(){ - return ExcelTools.formatLocation( new int[]{ rwFirst, colFirst } ); + public String getReferent() { + return ExcelTools.formatLocation(new int[]{rwFirst, colFirst}); } - + /** * Looks up into it's parent shared formula, and returns * the expression as if it were a regular formula. + * * @return converted Calculation Expression */ public Ptg[] getConvertedExpression() { - Formula f= (Formula) this.getParentRec(); - if ( f.isSharedFormula() ) { - Stack expression= f.shared.instantiate( f); - Ptg[] retPtg = new Ptg[expression.size()]; - for (int i=0;i0) - r= f.getInternalRecords().get(0); - else { // it's part of an array formula but not the parent - r= this.getParentRec().getSheet().getArrayFormula(getReferent()); - } - if (r instanceof Array) { - Array arr = (Array)r; - o = arr.getValue(this); - } else if (r instanceof StringRec) { - o= ((StringRec) r).getStringVal(); - } - } - return o; - } - public Ptg calculatePtg(Ptg[] parsething){ - Object o= null;; - Formula f= ((Formula) this.getParentRec()); - if ( f.isSharedFormula() ) { - o= FormulaCalculator.calculateFormula( f.shared.instantiate( f ) ); - }else{ - Object r= null; - if (f.getInternalRecords().size()>0) - r= f.getInternalRecords().get(0); - else { // it's part of an array formula but not the parent - r= this.getParentRec().getSheet().getArrayFormula(getReferent()); - } - if (r instanceof Array) { - Array arr = (Array)r; - o = arr.getValue(this); - } else if (r instanceof StringRec) { - o= ((StringRec) r).getStringVal(); - } else // should never happen - throw new UnsupportedOperationException ( - "Expected records parsing Formula were not present"); - } - Ptg p = null; - // conversion isn't necessary + } else { + Object r = null; + if (f.getInternalRecords().size() > 0) + r = f.getInternalRecords().get(0); + else { // it's part of an array formula but not the parent + r = this.getParentRec().getSheet().getArrayFormula(getReferent()); + } + if (r instanceof Array) { + Array arr = (Array) r; + o = arr.getValue(this); + } else if (r instanceof StringRec) { + o = ((StringRec) r).getStringVal(); + } + } + return o; + } + + public Ptg calculatePtg(Ptg[] parsething) { + Object o = null; + Formula f = ((Formula) this.getParentRec()); + if (f.isSharedFormula()) { + o = FormulaCalculator.calculateFormula(f.shared.instantiate(f)); + } else { + Object r = null; + if (f.getInternalRecords().size() > 0) + r = f.getInternalRecords().get(0); + else { // it's part of an array formula but not the parent + r = this.getParentRec().getSheet().getArrayFormula(getReferent()); + } + if (r instanceof Array) { + Array arr = (Array) r; + o = arr.getValue(this); + } else if (r instanceof StringRec) { + o = ((StringRec) r).getStringVal(); + } else // should never happen + throw new UnsupportedOperationException( + "Expected records parsing Formula were not present"); + } + Ptg p = null; + // conversion isn't necessary // try{ - if (o instanceof Integer) - return new PtgInt(((Integer)o).intValue()); + if (o instanceof Integer) + return new PtgInt(((Integer) o).intValue()); // Double d = new Double(o.toString()); - else if (o instanceof Double) - return new PtgNumber(((Double) o).doubleValue()); - //p = new PtgNumber(d.doubleValue()); + else if (o instanceof Double) + return new PtgNumber(((Double) o).doubleValue()); + //p = new PtgNumber(d.doubleValue()); // }catch(NumberFormatException e){ - if (o.toString().equalsIgnoreCase("true") || o.toString().equalsIgnoreCase("false")) - p= new PtgBool(o.toString().equalsIgnoreCase("true")); - else - p = new PtgStr(o.toString()); + if (o.toString().equalsIgnoreCase("true") || o.toString().equalsIgnoreCase("false")) + p = new PtgBool(o.toString().equalsIgnoreCase("true")); + else + p = new PtgStr(o.toString()); // } - return p; - } + return p; + } + + /** + * return the location of this PtgExp + * 20060302 KSC + */ + public String getLocation() throws FormulaNotFoundException { + String s = ""; + try { + s = this.getParentRec().getCellAddress(); + s = this.getParentRec().getSheet().getSheetName() + "!" + s; + } catch (Exception e) { + + } + return s; + } - /** return the location of this PtgExp - * 20060302 KSC - */ - public String getLocation() throws FormulaNotFoundException{ - String s= ""; - try { - s= this.getParentRec().getCellAddress(); - s= this.getParentRec().getSheet().getSheetName() + "!" + s; - } - catch (Exception e) { - - } - return s; - } - - /** return the human-readable String representation of the linked shared formula - */ - public String getString(){ - try{ - try { - // Object o= ((Formula) this.getParentRec()).getInternalRecords().get(0); PARENT REC of ARRAY or SHRFMLA is determined by referent (record) NOT necessarily same as actual Parent Rec - Boundsheet sht= this.getParentRec().getSheet(); + /** + * return the human-readable String representation of the linked shared formula + */ + public String getString() { + try { + try { + // Object o= ((Formula) this.getParentRec()).getInternalRecords().get(0); PARENT REC of ARRAY or SHRFMLA is determined by referent (record) NOT necessarily same as actual Parent Rec + Boundsheet sht = this.getParentRec().getSheet(); // Formula pr= (Formula) sht.getCell(this.getReferent()); - Formula f= ((Formula) this.getParentRec()); - Object o; - if ( f.isSharedFormula() ) { - o= FormulaParser.getExpressionString(f.shared.instantiate( f ) ); - if (o!=null && o.toString().startsWith("=")) - return o.toString().substring(1); - return o.toString(); - } - Formula pr= (Formula) sht.getCell(this.getReferent()); - o= pr.getInternalRecords().get(0); - if (o instanceof Array){ - Array a= (Array) o; - return a.getFormulaString(); - } else if (o instanceof StringRec) { - //if this is a shared formula the attached string is the RESULT, not the formula string itself - if(((Formula)this.getParentRec()).isSharedFormula()){ - throw new IndexOutOfBoundsException("parse it"); - } - StringRec s= (StringRec) o; - return s.getStringVal(); - } - } catch (IndexOutOfBoundsException e) { // subsequent formulas use same shared formula rec so find - throw new UnsupportedOperationException ( - "Shared formulas must be instantiated for calculation"); - } - }catch(Exception e) { - return "Array-Entered or Shared Formula"; - } - return "Array-Entered or Shared Formula"; - } - /** updateRecord from local rwFirst and colFirst values - * - * @see - */ - public void updateRecord(){ - System.arraycopy(ByteTools.shortToLEBytes((short) rwFirst), 0, record, 1, 2); - System.arraycopy(ByteTools.shortToLEBytes((short) colFirst), 0, record, 3, 2); - } + Formula f = ((Formula) this.getParentRec()); + Object o; + if (f.isSharedFormula()) { + o = FormulaParser.getExpressionString(f.shared.instantiate(f)); + if (o != null && o.toString().startsWith("=")) + return o.toString().substring(1); + return o.toString(); + } + Formula pr = (Formula) sht.getCell(this.getReferent()); + o = pr.getInternalRecords().get(0); + if (o instanceof Array) { + Array a = (Array) o; + return a.getFormulaString(); + } else if (o instanceof StringRec) { + //if this is a shared formula the attached string is the RESULT, not the formula string itself + if (((Formula) this.getParentRec()).isSharedFormula()) { + throw new IndexOutOfBoundsException("parse it"); + } + StringRec s = (StringRec) o; + return s.getStringVal(); + } + } catch (IndexOutOfBoundsException e) { // subsequent formulas use same shared formula rec so find + throw new UnsupportedOperationException( + "Shared formulas must be instantiated for calculation"); + } + } catch (Exception e) { + return "Array-Entered or Shared Formula"; + } + return "Array-Entered or Shared Formula"; + } + + /** + * updateRecord from local rwFirst and colFirst values + * + * @see + */ + public void updateRecord() { + System.arraycopy(ByteTools.shortToLEBytes((short) rwFirst), 0, record, 1, 2); + System.arraycopy(ByteTools.shortToLEBytes((short) colFirst), 0, record, 3, 2); + } + + /** + * setLocation vars from address string + * + * @param s String address + */ + public void setLocation(String s) { + int[] rc = ExcelTools.getRowColFromString(s); + rwFirst = rc[0]; + colFirst = rc[1]; + updateRecord(); + } + + public void setColFirst(int c) { + this.colFirst = c; + } + + public void setRowFirst(int r) { + this.rwFirst = r; + } + + public String toString() { + return "PtgExp: Parent Formula at [" + rwFirst + "," + colFirst + "]"; + } - /** setLocation vars from address string - * - * @param s String address - */ - public void setLocation(String s){ - int[] rc= ExcelTools.getRowColFromString(s); - rwFirst= rc[0]; - colFirst= rc[1]; - updateRecord(); - } - - public void setColFirst(int c) { - this.colFirst = c; - } - public void setRowFirst(int r) { - this.rwFirst = r; - } - - public String toString() { - return "PtgExp: Parent Formula at [" + rwFirst + "," + colFirst + "]"; - } - } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgFunc.java b/src/main/java/io/starter/formats/XLS/formulas/PtgFunc.java index e7acd72..5d45356 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgFunc.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgFunc.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,234 +22,239 @@ */ package io.starter.formats.XLS.formulas; -import java.util.Locale; - -import io.starter.toolkit.ByteTools; import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.formats.XLS.XLSRecord; +import io.starter.toolkit.ByteTools; + +import java.util.Locale; /** - PtgFunc is a fuction operator that refers to the header file in order to - use the correct function. PtgFunc is only used with fixed number of argument - functions - - Opcode = 21h - -

                -    Offset      Name        Size        Contents
                -    --------------------------------------------------------
                -    0           iftab       2           The index to the function table
                -                                        see GenericPtgFunc for details
                -    
                - + * PtgFunc is a fuction operator that refers to the header file in order to + * use the correct function. PtgFunc is only used with fixed number of argument + * functions + *

                + * Opcode = 21h + * + *

                + * Offset      Name        Size        Contents
                + * --------------------------------------------------------
                + * 0           iftab       2           The index to the function table
                + * see GenericPtgFunc for details
                + * 
                + * * @see Ptg * @see GenericPtgFunc **/ -public class PtgFunc extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4435263700288188538L; - /* 20060425 KSC: see FunctionConstants for Formula Consolidation details - public static String[][] recArr = { - {"Pi","19"}, - {"Round","27"}, - {"Rept","30"}, - {"Mid","31"}, - {"Mod","39"}, - {"Rand","63"}, - {"Date","65"}, - {"Time","66"}, - {"Day","67"}, - {"Now","74"}, - {"Atan2","97"}, - {"Log","109"}, - {"Left","115"}, - {"Right","116"}, - {"Replace","119"}, - {"Exact","117"}, - {"Trim","118"}, - {"Roundup","212"}, - {"MMult","165"}, - {"RoundDown","213"}, - {"today","221"}, - {"Combin","276"}, - {"Floor","285"}, - {"Celing","288"}, - {"Power","337"}, - {"Countif","246"}, - {"Hour", String.valueOf(FunctionHandler.xlfHour)}, - {"Minute", String.valueOf(FunctionHandler.xlfMinute)}, - {"Month", String.valueOf(FunctionHandler.xlfMonth)}, - {"Year", String.valueOf(FunctionHandler.xlfYear)}, - {"Second", String.valueOf(FunctionHandler.xlfSecond)}, - {"Weekday", String.valueOf(FunctionHandler.xlfWeekday)}, - {"Time", String.valueOf(FunctionHandler.xlfTime)}, - {"Now", String.valueOf(FunctionHandler.xlfNow)}, - {"Quartile", String.valueOf(FunctionHandler.xlfQuartile)}, - {"Frequency", String.valueOf(FunctionHandler.xlfFrequency)}, - {"Linest", String.valueOf(FunctionHandler.xlfLinest)}, - {"Correl", String.valueOf(FunctionHandler.xlfCorrel)}, - {"Slope", String.valueOf(FunctionHandler.xlfSlope)}, - {"Intercept", String.valueOf(FunctionHandler.xlfIntercept)}, - {"Pearson", String.valueOf(FunctionHandler.xlfPearson)}, - {"Rsq", String.valueOf(FunctionHandler.xlfRsq)}, - {"Steyx", String.valueOf(FunctionHandler.xlfSteyx)}, - {"Forecast", String.valueOf(FunctionHandler.xlfForecast)}, - {"Trend", String.valueOf(FunctionHandler.xlfTrend)}, - {"Covar", String.valueOf(FunctionHandler.xlfCovar)}, - {"IsNumber", String.valueOf(FunctionHandler.xlfIsnumber)}, - {"DAVERAGE", String.valueOf(FunctionHandler.xlfDaverage)}, - {"DCOUNT", String.valueOf(FunctionHandler.xlfDcount)}, - {"DCOUNTA", String.valueOf(FunctionHandler.xlfDcounta)}, - {"DGET", String.valueOf(FunctionHandler.xlfDget)}, - {"DMIN", String.valueOf(FunctionHandler.xlfDmin)}, - {"DMAX", String.valueOf(FunctionHandler.xlfDmax)}, - {"DPRODUCT", String.valueOf(FunctionHandler.xlfDproduct)}, - {"DSTDEVP", String.valueOf(FunctionHandler.xlfDstdevp)}, - {"DSTDEV", String.valueOf(FunctionHandler.xlfDstdev)}, - {"DSUM", String.valueOf(FunctionHandler.xlfDsum)}, - {"DVAR", String.valueOf(FunctionHandler.xlfDvar)}, - {"DVARP", String.valueOf(FunctionHandler.xlfDvarp)}, - {"SQRT", String.valueOf(FunctionHandler.xlfSqrt)}, - - }; - */ +public class PtgFunc extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4435263700288188538L; + /* 20060425 KSC: see FunctionConstants for Formula Consolidation details + public static String[][] recArr = { + {"Pi","19"}, + {"Round","27"}, + {"Rept","30"}, + {"Mid","31"}, + {"Mod","39"}, + {"Rand","63"}, + {"Date","65"}, + {"Time","66"}, + {"Day","67"}, + {"Now","74"}, + {"Atan2","97"}, + {"Log","109"}, + {"Left","115"}, + {"Right","116"}, + {"Replace","119"}, + {"Exact","117"}, + {"Trim","118"}, + {"Roundup","212"}, + {"MMult","165"}, + {"RoundDown","213"}, + {"today","221"}, + {"Combin","276"}, + {"Floor","285"}, + {"Celing","288"}, + {"Power","337"}, + {"Countif","246"}, + {"Hour", String.valueOf(FunctionHandler.xlfHour)}, + {"Minute", String.valueOf(FunctionHandler.xlfMinute)}, + {"Month", String.valueOf(FunctionHandler.xlfMonth)}, + {"Year", String.valueOf(FunctionHandler.xlfYear)}, + {"Second", String.valueOf(FunctionHandler.xlfSecond)}, + {"Weekday", String.valueOf(FunctionHandler.xlfWeekday)}, + {"Time", String.valueOf(FunctionHandler.xlfTime)}, + {"Now", String.valueOf(FunctionHandler.xlfNow)}, + {"Quartile", String.valueOf(FunctionHandler.xlfQuartile)}, + {"Frequency", String.valueOf(FunctionHandler.xlfFrequency)}, + {"Linest", String.valueOf(FunctionHandler.xlfLinest)}, + {"Correl", String.valueOf(FunctionHandler.xlfCorrel)}, + {"Slope", String.valueOf(FunctionHandler.xlfSlope)}, + {"Intercept", String.valueOf(FunctionHandler.xlfIntercept)}, + {"Pearson", String.valueOf(FunctionHandler.xlfPearson)}, + {"Rsq", String.valueOf(FunctionHandler.xlfRsq)}, + {"Steyx", String.valueOf(FunctionHandler.xlfSteyx)}, + {"Forecast", String.valueOf(FunctionHandler.xlfForecast)}, + {"Trend", String.valueOf(FunctionHandler.xlfTrend)}, + {"Covar", String.valueOf(FunctionHandler.xlfCovar)}, + {"IsNumber", String.valueOf(FunctionHandler.xlfIsnumber)}, + {"DAVERAGE", String.valueOf(FunctionHandler.xlfDaverage)}, + {"DCOUNT", String.valueOf(FunctionHandler.xlfDcount)}, + {"DCOUNTA", String.valueOf(FunctionHandler.xlfDcounta)}, + {"DGET", String.valueOf(FunctionHandler.xlfDget)}, + {"DMIN", String.valueOf(FunctionHandler.xlfDmin)}, + {"DMAX", String.valueOf(FunctionHandler.xlfDmax)}, + {"DPRODUCT", String.valueOf(FunctionHandler.xlfDproduct)}, + {"DSTDEVP", String.valueOf(FunctionHandler.xlfDstdevp)}, + {"DSTDEV", String.valueOf(FunctionHandler.xlfDstdev)}, + {"DSUM", String.valueOf(FunctionHandler.xlfDsum)}, + {"DVAR", String.valueOf(FunctionHandler.xlfDvar)}, + {"DVARP", String.valueOf(FunctionHandler.xlfDvarp)}, + {"SQRT", String.valueOf(FunctionHandler.xlfSqrt)}, + + }; + */ private int iftab = -1; - - + + public PtgFunc(int funcType, XLSRecord parentRec) { - this(funcType); - this.setParentRec(parentRec); + this(funcType); + this.setParentRec(parentRec); } - public PtgFunc(int funcType){ - byte[] recbyte = new byte[3]; + + public PtgFunc(int funcType) { + byte[] recbyte = new byte[3]; // recbyte[0]= 0x21; - recbyte[0]= 0x41; // 20060126 - KSC: Excel seems to need this code for PtgFunc - byte[] b = ByteTools.shortToLEBytes((short)funcType); - recbyte[1] = b[0]; - recbyte[2] = b[1]; - this.init(recbyte); + recbyte[0] = 0x41; // 20060126 - KSC: Excel seems to need this code for PtgFunc + byte[] b = ByteTools.shortToLEBytes((short) funcType); + recbyte[1] = b[0]; + recbyte[2] = b[1]; + this.init(recbyte); } - - public PtgFunc(){ - // placeholder + + public PtgFunc() { + // placeholder + } + + public boolean getIsFunction() { + return true; } - - public boolean getIsFunction(){return true;} - /** Returns the number of Params to pass to the Ptg + + /** + * Returns the number of Params to pass to the Ptg * Unfortunately this seems to vary depending on the formula. * fill in the non-1's as you get them. - * - */ - public int getNumParams(){ - // 20060425 KSC: Formula consolidation - see FunctionConstants for details - return FunctionConstants.getNumParams(iftab); + */ + public int getNumParams() { + // 20060425 KSC: Formula consolidation - see FunctionConstants for details + return FunctionConstants.getNumParams(iftab); } -/* - if (iftab == 10) return 0; // na - if (iftab == 19) return 0; // Pi - if (iftab == 27) return 2; // Round - if (iftab == 30) return 2; // rept - if (iftab == 31) return 3; // Mid - if (iftab == 39) return 2; // Mod - if (iftab >= 40 && iftab <= 45) return 3; // Dxxx formulas - if (iftab == 47) return 3; // DVar - if (iftab == 63) return 0; // Rand - if (iftab == 65) return 3; // Date - if (iftab == 66) return 3; // Time - if (iftab == 67) return 1; // Day - if (iftab == 74) return 0; // now - if (iftab == 97) return 2; // Atan2 - if (iftab == 109) return 2; // Log - if (iftab == 115) return 2; // Left - if (iftab == 116) return 2; // Right - if (iftab == 118) return 1; // Trim - if (iftab == 119) return 4; // Log - if (iftab == 117) return 2; // Exact - if (iftab == 165) return 2; //TODO: - if (iftab == 189) return 3; // DProduct - if (iftab == 195) return 3; // DStdDev - if (iftab == 196) return 3; // DVarP - if (iftab == 199) return 3; // DCountA - if (iftab == 212) return 2; // Roundup - if (iftab == 213) return 2; // Rounddown - if (iftab == 221) return 0; // today - if (iftab == 235) return 3; // DGet - if (iftab == 276) return 2; // Combin - if (iftab == 285) return 2; // Floor - if (iftab == 288) return 2; // Ceiling - if (iftab == 337) return 2; // Power - if (iftab == FunctionHandler.xlfTime) return 3; - if (iftab == FunctionHandler.xlfQuartile) return 2; - if (iftab == FunctionHandler.xlfFrequency) return 2; - if (iftab == FunctionHandler.xlfCorrel) return 2; - if (iftab == FunctionHandler.xlfCovar) return 2; - if (iftab == FunctionHandler.xlfSlope) return 2; - if (iftab == FunctionHandler.xlfIntercept) return 2; - if (iftab == FunctionHandler.xlfPearson) return 2; - if (iftab == FunctionHandler.xlfRsq) return 2; - if (iftab == FunctionHandler.xlfSteyx) return 2; - if (iftab == FunctionHandler.xlfForecast) return 2; - if (iftab == FunctionHandler.xlfTrend) return 2; - if (iftab == FunctionHandler.xlfIsnumber) return 1; - return 1; //if we are lucky + + /* + if (iftab == 10) return 0; // na + if (iftab == 19) return 0; // Pi + if (iftab == 27) return 2; // Round + if (iftab == 30) return 2; // rept + if (iftab == 31) return 3; // Mid + if (iftab == 39) return 2; // Mod + if (iftab >= 40 && iftab <= 45) return 3; // Dxxx formulas + if (iftab == 47) return 3; // DVar + if (iftab == 63) return 0; // Rand + if (iftab == 65) return 3; // Date + if (iftab == 66) return 3; // Time + if (iftab == 67) return 1; // Day + if (iftab == 74) return 0; // now + if (iftab == 97) return 2; // Atan2 + if (iftab == 109) return 2; // Log + if (iftab == 115) return 2; // Left + if (iftab == 116) return 2; // Right + if (iftab == 118) return 1; // Trim + if (iftab == 119) return 4; // Log + if (iftab == 117) return 2; // Exact + if (iftab == 165) return 2; //TODO: + if (iftab == 189) return 3; // DProduct + if (iftab == 195) return 3; // DStdDev + if (iftab == 196) return 3; // DVarP + if (iftab == 199) return 3; // DCountA + if (iftab == 212) return 2; // Roundup + if (iftab == 213) return 2; // Rounddown + if (iftab == 221) return 0; // today + if (iftab == 235) return 3; // DGet + if (iftab == 276) return 2; // Combin + if (iftab == 285) return 2; // Floor + if (iftab == 288) return 2; // Ceiling + if (iftab == 337) return 2; // Power + if (iftab == FunctionHandler.xlfTime) return 3; + if (iftab == FunctionHandler.xlfQuartile) return 2; + if (iftab == FunctionHandler.xlfFrequency) return 2; + if (iftab == FunctionHandler.xlfCorrel) return 2; + if (iftab == FunctionHandler.xlfCovar) return 2; + if (iftab == FunctionHandler.xlfSlope) return 2; + if (iftab == FunctionHandler.xlfIntercept) return 2; + if (iftab == FunctionHandler.xlfPearson) return 2; + if (iftab == FunctionHandler.xlfRsq) return 2; + if (iftab == FunctionHandler.xlfSteyx) return 2; + if (iftab == FunctionHandler.xlfForecast) return 2; + if (iftab == FunctionHandler.xlfTrend) return 2; + if (iftab == FunctionHandler.xlfIsnumber) return 1; + return 1; //if we are lucky + } + */ + public boolean getIsUnaryOperator() { + return true; } -*/ - public boolean getIsUnaryOperator(){return true;} - - - public String getString(){ - short iftb = (short)iftab; + + public String getString() { + short iftb = (short) iftab; String f = null; if (Locale.JAPAN.equals(Locale.getDefault())) { f = FunctionConstants.getJFunctionString(iftb); } - if(f==null) { - f = FunctionConstants.getFunctionString(iftb); + if (f == null) { + f = FunctionConstants.getFunctionString(iftb); } return f; } - - public String getString2(){ - return ")"; + + public String getString2() { + return ")"; } - - public void init(byte[] b){ - ptgId = b[0]; - record = b; + + public void init(byte[] b) { + ptgId = b[0]; + record = b; this.populateVals(); } - - private void populateVals(){ + + private void populateVals() { iftab = ByteTools.readShort(record[1], record[2]); } - - public int getVal(){ - return iftab; + + public int getVal() { + return iftab; } - - public void setVal(int i){ + + public void setVal(int i) { iftab = i; this.updateRecord(); } - - public void updateRecord(){ + + public void updateRecord() { byte[] tmp = new byte[1]; tmp[0] = record[0]; byte[] brow = ByteTools.cLongToLEBytes(iftab); - tmp = ByteTools.append(brow, tmp); + tmp = ByteTools.append(brow, tmp); record = tmp; } - - public int getLength(){ + + public int getLength() { return PTG_FUNC_LENGTH; } - - public Ptg calculatePtg(Ptg[] pthings) throws FunctionNotSupportedException, CalculationException{ - Ptg[] ptgarr = new Ptg[pthings.length +1]; + + public Ptg calculatePtg(Ptg[] pthings) throws FunctionNotSupportedException, CalculationException { + Ptg[] ptgarr = new Ptg[pthings.length + 1]; ptgarr[0] = this; // add this into the array so the functionHandler has a handle to the function System.arraycopy(pthings, 0, ptgarr, 1, pthings.length); @@ -262,23 +267,24 @@ public Ptg calculatePtg(Ptg[] pthings) throws FunctionNotSupportedException, Cal *
                Value, Reference or Array *
                This is necessary when functions are added via String *
                NOTE: This method is a stub; eventually ALL Functions which require - * specific types of parameters will be handled here + * specific types of parameters will be handled here + * * @see FormulaParser.adjustParameterIds */ public void adjustParameterIds() { - if (vars==null) return; // no parameters to worry about - /*TODO Eventually will have a list of Function Id's which require a certain type of parameter*/ - switch (iftab) { - case FunctionConstants.xlfRows: - for(int i=0; i. @@ -22,250 +22,258 @@ */ package io.starter.formats.XLS.formulas; -import java.util.Locale; - -import io.starter.toolkit.ByteTools; import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.formats.XLS.XLSRecord; +import io.starter.toolkit.ByteTools; + +import java.util.Locale; /** - PtgFunc is a fuction operator that refers to the header file in order to - use the correct function. - - PtgFuncVar is only used with a variable number of arguments. - - Opcode = 22h - -
                -    Offset      Bits    Name        Mask        Contents
                -    --------------------------------------------------------
                -    0           6-0     cargs       7Fh         The number of arguments to the function
                -                7       fPrompt     80h         =1, function prompts the user
                -    1           14-0    iftab       7FFFh       The index to the function table
                -                                                see GenericPtgFunc for details
                -                15      fCE         8000h       This function is a command equivalent
                -    
                - + * PtgFunc is a fuction operator that refers to the header file in order to + * use the correct function. + *

                + * PtgFuncVar is only used with a variable number of arguments. + *

                + * Opcode = 22h + * + *

                + * Offset      Bits    Name        Mask        Contents
                + * --------------------------------------------------------
                + * 0           6-0     cargs       7Fh         The number of arguments to the function
                + * 7       fPrompt     80h         =1, function prompts the user
                + * 1           14-0    iftab       7FFFh       The index to the function table
                + * see GenericPtgFunc for details
                + * 15      fCE         8000h       This function is a command equivalent
                + * 
                + * * @see Ptg * @see GenericPtgFunc **/ -public class PtgFuncVar extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1478629759437556620L; - public static int LENGTH = 3; +public class PtgFuncVar extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1478629759437556620L; + public static int LENGTH = 3; byte ptgId; String loc; byte cargs; boolean fprompt; short iftab; boolean fCE; - - - public PtgFuncVar(int funcType, int numArgs, XLSRecord parentRec){ - this(funcType, numArgs); - this.setParentRec(parentRec); - } - - public PtgFuncVar(int funcType, int numArgs){ - byte[] recbyte = new byte[4]; - // 20100609 KSC: there are three types of funcvars: - // 22H (tFuncVarR), 42H (tFuncVarV), 62H (tFuncVarA) - // tFUncVarR = reference return value (most common?) - // tFuncVarV = value type of return value (ROW, SUM) - // tFuncVarA = Array return type (TREND) - // TODO: figure out which other functions are type V or type A - switch (funcType) { - case FunctionConstants.XLF_ROW: // ROW - case FunctionConstants.xlfColumn: // COLUMN - case FunctionConstants.xlfIndex: // INDEX - case FunctionConstants.xlfVlookup: // VLOOKUP - case FunctionConstants.xlfSumproduct: // SUMPRODUCT - recbyte[0]= 0x42; - break; - default: // default= tFuncVarR - recbyte[0]= 0x22; - } - byte[] b = ByteTools.shortToLEBytes((short)funcType); - recbyte[1] = (byte) numArgs; - recbyte[2] = b[0]; - recbyte[3] = b[1]; - this.init(recbyte); - } - - public PtgFuncVar(){ - // placeholder - } - public boolean getIsFunction(){return true;} - - /** Returns the number of Params to pass to the Ptg - */ - public int getNumParams(){ + + public PtgFuncVar(int funcType, int numArgs, XLSRecord parentRec) { + this(funcType, numArgs); + this.setParentRec(parentRec); + } + + public PtgFuncVar(int funcType, int numArgs) { + byte[] recbyte = new byte[4]; + // 20100609 KSC: there are three types of funcvars: + // 22H (tFuncVarR), 42H (tFuncVarV), 62H (tFuncVarA) + // tFUncVarR = reference return value (most common?) + // tFuncVarV = value type of return value (ROW, SUM) + // tFuncVarA = Array return type (TREND) + // TODO: figure out which other functions are type V or type A + switch (funcType) { + case FunctionConstants.XLF_ROW: // ROW + case FunctionConstants.xlfColumn: // COLUMN + case FunctionConstants.xlfIndex: // INDEX + case FunctionConstants.xlfVlookup: // VLOOKUP + case FunctionConstants.xlfSumproduct: // SUMPRODUCT + recbyte[0] = 0x42; + break; + default: // default= tFuncVarR + recbyte[0] = 0x22; + } + byte[] b = ByteTools.shortToLEBytes((short) funcType); + recbyte[1] = (byte) numArgs; + recbyte[2] = b[0]; + recbyte[3] = b[1]; + this.init(recbyte); + } + + public PtgFuncVar() { + // placeholder + } + + public boolean getIsFunction() { + return true; + } + + /** + * Returns the number of Params to pass to the Ptg + */ + public int getNumParams() { return cargs; } - - /** set the number of parmeters in the FuncVar record - * @param byte nParams + + /** + * set the number of parmeters in the FuncVar record * + * @param byte nParams */ // 20060131 KSC: Added to set # params separately from init public void setNumParams(byte nParams) { - record[1]= nParams; - this.populateVals(); + record[1] = nParams; + this.populateVals(); } - + // should be handled by super? - public byte getOpcode(){return ptgId;} - - + public byte getOpcode() { + return ptgId; + } + + /** * GetString - is this toString, what is it returning? - * - * */ - public String getString(){ - if (iftab!=FunctionConstants.xlfADDIN) { + */ + public String getString() { + if (iftab != FunctionConstants.xlfADDIN) { String f = null; if (Locale.JAPAN.equals(Locale.getDefault())) { - f = FunctionConstants.getJFunctionString(iftab); + f = FunctionConstants.getJFunctionString(iftab); } - if (f==null)f= FunctionConstants.getFunctionString(iftab); + if (f == null) f = FunctionConstants.getFunctionString(iftab); return f; } - return getAddInFunctionString(); + return getAddInFunctionString(); } - + // KSC: added to handle string version of add-in formulas - private String getAddInFunctionString() { - if (vars!=null && vars[0] instanceof PtgNameX) { - return ((PtgNameX) vars[0]).toString() + "("; - } - return "("; + private String getAddInFunctionString() { + if (vars != null && vars[0] instanceof PtgNameX) { + return vars[0].toString() + "("; + } + return "("; } - public String getString2(){ - return ")"; + public String getString2() { + return ")"; } - - public void init(byte[] b){ + + public void init(byte[] b) { ptgId = 0x22; record = b; fprompt = false; fCE = false; this.populateVals(); } - + /** * Get the function ID for this PtgFuncVar + * * @return function Id */ public short getFunctionId() { return iftab; } - + /** - * parse all the values out of the byte array and + * parse all the values out of the byte array and * populate the classes values - */ - private void populateVals(){ - + */ + private void populateVals() { + cargs = record[1]; - if ((cargs & 0x80) == 0x80){ // is fprompt set? - fprompt = true; + if ((cargs & 0x80) == 0x80) { // is fprompt set? + fprompt = true; } cargs = (byte) (cargs & 0x7f); - iftab = (short) ByteTools.readShort(record[2], record[3]); - if ((iftab & 0x8000) == 0x8000){ // is fCE set? - fCE = true; + iftab = ByteTools.readShort(record[2], record[3]); + if ((iftab & 0x8000) == 0x8000) { // is fCE set? + fCE = true; } iftab = (short) (iftab & 0x7fff); // cut out the fCE - } - - - public int getVal(){ - return iftab; } - - + + + public int getVal() { + return iftab; + } + + // THis will have to be modified when we start modifying the record. - public byte[] getRecord(){ + public byte[] getRecord() { return record; } - - public int getLength(){ + + public int getLength() { return PTG_FUNCVAR_LENGTH; } - - public Ptg calculatePtg(Ptg[] pthings) throws FunctionNotSupportedException, CalculationException - { - Ptg[] ptgarr = new Ptg[pthings.length +1]; + + public Ptg calculatePtg(Ptg[] pthings) throws FunctionNotSupportedException, CalculationException { + Ptg[] ptgarr = new Ptg[pthings.length + 1]; ptgarr[0] = this; // add this into the array so the functionHandler has a handle to the function System.arraycopy(pthings, 0, ptgarr, 1, pthings.length); - Ptg resPtg = FunctionHandler.calculateFunction(ptgarr); - return resPtg; + Ptg resPtg = FunctionHandler.calculateFunction(ptgarr); + return resPtg; } - + /** * return String representation of function id for this funcvar */ - public String toString() { return "FUNCVAR " + iftab; } - + public String toString() { + return "FUNCVAR " + iftab; + } + /** * given this specific Func Var, ensure that it's parameters are of the correct Ptg type *
                Value, Reference or Array - *
                This is necessary when functions are added via String + *
                This is necessary when functions are added via String *
                NOTE: eventually all FuncVars which require a specific type of parameter will be handled here + * * @see FormulaParser.adjustParameterIds */ public void adjustParameterIds() { - if (vars==null) return; // no parameters to worry about - switch (iftab) { - case FunctionConstants.xlfVlookup: - setParameterType(0, PtgRef.VALUE); - setParameterType(1, PtgRef.REFERENCE); - setParameterType(2, PtgRef.VALUE); - setParameterType(3, PtgRef.VALUE); - break; - case FunctionConstants.xlfColumn: - case FunctionConstants.XLF_ROW: - setParameterType(0, PtgRef.REFERENCE); - break; - case FunctionConstants.xlfIndex: - setParameterType(0, PtgRef.REFERENCE); - setParameterType(1, PtgRef.VALUE); - break; - case FunctionConstants.XLF_SUM_IF: - setParameterType(0, PtgRef.REFERENCE); - break; - case FunctionConstants.xlfSumproduct: - setParameterType(0, PtgRef.ARRAY); - setParameterType(1, PtgRef.ARRAY); - break; - default: - break; - } + if (vars == null) return; // no parameters to worry about + switch (iftab) { + case FunctionConstants.xlfVlookup: + setParameterType(0, PtgRef.VALUE); + setParameterType(1, PtgRef.REFERENCE); + setParameterType(2, PtgRef.VALUE); + setParameterType(3, PtgRef.VALUE); + break; + case FunctionConstants.xlfColumn: + case FunctionConstants.XLF_ROW: + setParameterType(0, PtgRef.REFERENCE); + break; + case FunctionConstants.xlfIndex: + setParameterType(0, PtgRef.REFERENCE); + setParameterType(1, PtgRef.VALUE); + break; + case FunctionConstants.XLF_SUM_IF: + setParameterType(0, PtgRef.REFERENCE); + break; + case FunctionConstants.xlfSumproduct: + setParameterType(0, PtgRef.ARRAY); + setParameterType(1, PtgRef.ARRAY); + break; + default: + break; + } } - - + + /** * utility for adjustParameterIds to set the PtgRef-type or PtgName-type pareameter to the correct type * either PtgRef.REFERENCE, PtgRef.VALUE or PtgRef.ARRAY - * dependent upon the function they are used in + * dependent upon the function they are used in + * * @param n * @param type */ private void setParameterType(int n, short type) { - if (vars.length > n) { - if (vars[n] instanceof PtgArea) { - ((PtgArea) vars[n]).setPtgType(type); - } else if (vars[n] instanceof PtgRef) { - ((PtgRef) vars[n]).setPtgType(type); - } else if (vars[n] instanceof PtgName) { - ((PtgName) vars[n]).setPtgType(type); - } - } + if (vars.length > n) { + if (vars[n] instanceof PtgArea) { + ((PtgArea) vars[n]).setPtgType(type); + } else if (vars[n] instanceof PtgRef) { + ((PtgRef) vars[n]).setPtgType(type); + } else if (vars[n] instanceof PtgName) { + ((PtgName) vars[n]).setPtgType(type); + } + } } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgGE.java b/src/main/java/io/starter/formats/XLS/formulas/PtgGE.java index 87de975..58cb1ad 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgGE.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgGE.java @@ -2,31 +2,31 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; -import java.lang.reflect.Array; -import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.Formula; import io.starter.toolkit.Logger; +import java.lang.reflect.Array; + /* Ptg that is a Greater than or equal to operand @@ -40,79 +40,89 @@ */ -public class PtgGE extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5154662904662222256L; - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - public PtgGE(){ - ptgId = 0xC; - record = new byte[1]; - record[0] = 0xC; - } - - /** return the human-readable String representation of - */ - public String getString(){ +public class PtgGE extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5154662904662222256L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public PtgGE() { + ptgId = 0xC; + record = new byte[1]; + record[0] = 0xC; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return ">="; } + public String toString() { return this.getString(); } - public int getLength(){ + + public int getLength() { return PTG_GE_LENGTH; } - /* Operator specific calculate method, this one determines if the second-to-top - operand is less than the top operand; Returns a PtgBool - - */ - public Ptg calculatePtg(Ptg[] form){ - try{ - // 20090202 KSC: Handle array formulas - Object[] o= super.getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o==null || o.length != 2){ - Logger.logInfo("calculating formula failed, wrong number of values in PtgGE"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel - } - // blank handling: - // determine if any of the operands are double - if true, - // then blank comparisons will be treated as 0's - boolean isDouble= false; - for (int i= 0; i < 2 && !isDouble; i++) { - if (!form[i].isBlank()) - isDouble= ((o[i] instanceof Double)); - } - for(int i=0;i<2;i++){ - if (form[i].isBlank()) { - if (isDouble) - o[i]= new Double(0.0); - else - o[i]=""; // in this case, empty cells are handled as blank, not zero - } - } - - boolean res; - if (o[0] instanceof Double && o[1] instanceof Double) { - //if (dub[0].doubleValue() <= dub[1].doubleValue()){ - if (((Double)o[0]).doubleValue() >= ((Double)o[1]).doubleValue()){ - res = true; - }else{ - res = false; - } - } else { // string comparison?? - // This is what Excel does ... - if (Formula.isErrorValue(o[0].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); - if (Formula.isErrorValue(o[1].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); - // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res= (o[0].toString().compareTo(o[1].toString())>=0); + + /* Operator specific calculate method, this one determines if the second-to-top + operand is less than the top operand; Returns a PtgBool + + */ + public Ptg calculatePtg(Ptg[] form) { + try { + // 20090202 KSC: Handle array formulas + Object[] o = getValuesFromPtgs(form); + if (!o[0].getClass().isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o == null || o.length != 2) { + Logger.logInfo("calculating formula failed, wrong number of values in PtgGE"); + return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel + } + // blank handling: + // determine if any of the operands are double - if true, + // then blank comparisons will be treated as 0's + boolean isDouble = false; + for (int i = 0; i < 2 && !isDouble; i++) { + if (!form[i].isBlank()) + isDouble = ((o[i] instanceof Double)); + } + for (int i = 0; i < 2; i++) { + if (form[i].isBlank()) { + if (isDouble) + o[i] = new Double(0.0); + else + o[i] = ""; // in this case, empty cells are handled as blank, not zero + } + } + + boolean res; + if (o[0] instanceof Double && o[1] instanceof Double) { + //if (dub[0].doubleValue() <= dub[1].doubleValue()){ + res = ((Double) o[0]).doubleValue() >= ((Double) o[1]).doubleValue(); + } else { // string comparison?? + // This is what Excel does ... + if (Formula.isErrorValue(o[0].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + if (Formula.isErrorValue(o[1].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = (o[0].toString().compareTo(o[1].toString()) >= 0); /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); @@ -124,32 +134,34 @@ public Ptg calculatePtg(Ptg[] form){ } catch (ArrayIndexOutOfBoundsException e) { res= false; }*/ - } - - return new PtgBool(res); - } else { // handle array fomulas - boolean res= false; - String retArry= ""; - int nArrays= java.lang.reflect.Array.getLength(o); // TODO: Should always be 2 ???????????????????? - int nVals= java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i= 0; i < nArrays-1; i+=2) { - res= false; - Object secondOp= null; - boolean comparitorIsArray= o[i+1].getClass().isArray(); - if (!comparitorIsArray) secondOp= o[i+1]; - for (int j= 0; j < nVals; j++) { - Object firstOp= Array.get(o[i],j); // first array index j - if (comparitorIsArray) - secondOp= Array.get(o[i+1],j); // second array index j - - if (firstOp instanceof Double && secondOp instanceof Double) - res= ((Double)firstOp).compareTo((Double)secondOp)>=0; - else { // string comparison? - // This is what Excel does ... - if (Formula.isErrorValue(o[0].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); - if (Formula.isErrorValue(o[1].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); - // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res= (o[0].toString().compareTo(o[1].toString())>=0); + } + + return new PtgBool(res); + } else { // handle array fomulas + boolean res = false; + String retArry = ""; + int nArrays = java.lang.reflect.Array.getLength(o); // TODO: Should always be 2 ???????????????????? + int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays + for (int i = 0; i < nArrays - 1; i += 2) { + res = false; + Object secondOp = null; + boolean comparitorIsArray = o[i + 1].getClass().isArray(); + if (!comparitorIsArray) secondOp = o[i + 1]; + for (int j = 0; j < nVals; j++) { + Object firstOp = Array.get(o[i], j); // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j); // second array index j + + if (firstOp instanceof Double && secondOp instanceof Double) + res = ((Double) firstOp).compareTo((Double) secondOp) >= 0; + else { // string comparison? + // This is what Excel does ... + if (Formula.isErrorValue(o[0].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + if (Formula.isErrorValue(o[1].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = (o[0].toString().compareTo(o[1].toString()) >= 0); /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); @@ -161,15 +173,15 @@ public Ptg calculatePtg(Ptg[] form){ } catch (ArrayIndexOutOfBoundsException e) { res= false; }*/ - } - retArry= retArry + res + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } + } + retArry = retArry + res + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } /* }catch(NumberFormatException e){ 20090203 KSC: Handled above String[] s = getStringValuesFromPtgs(form); if (s==null || s.length<2) { // 20081203 KSC: Handle errors ala Excel @@ -192,9 +204,9 @@ public Ptg calculatePtg(Ptg[] form){ // Unfortuately <, >, and <> can all deal with strings as well... } */ - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); - } + } catch (Exception ex) { + return new PtgErr(PtgErr.ERROR_VALUE); + } } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgGT.java b/src/main/java/io/starter/formats/XLS/formulas/PtgGT.java index 14efe84..ed1f24b 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgGT.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgGT.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,12 +22,11 @@ */ package io.starter.formats.XLS.formulas; -import java.lang.reflect.Array; - -import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.Formula; import io.starter.toolkit.Logger; +import java.lang.reflect.Array; + /* Evaluates to TRUE if the second-to-top operand is greater than the top operand, otherwise FALSE @@ -38,82 +37,91 @@ */ -public class PtgGT extends GenericPtg implements Ptg -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1779961703481749272L; - - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - public PtgGT(){ - ptgId = 0xD; - record = new byte[1]; - record[0] = 0xD; - } - /** return the human-readable String representation of - */ - public String getString(){ +public class PtgGT extends GenericPtg implements Ptg { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1779961703481749272L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public PtgGT() { + ptgId = 0xD; + record = new byte[1]; + record[0] = 0xD; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return ">"; } + public String toString() { return this.getString(); } - public int getLength(){ + + public int getLength() { return PTG_GT_LENGTH; } - - /* Operator specific calculate method, this one determines if the second-to-top - operand is less than the top operand; Returns a PtgBool - - */ - public Ptg calculatePtg(Ptg[] form){ - try{ - // 20090202 KSC: Handle array formulas - Object[] o= super.getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o==null || o.length != 2){ - Logger.logInfo("calculating formula failesd, wrong number of values in PtgGT"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel - } - // blank handling: - // determine if any of the operands are double - if true, - // then blank comparisons will be treated as 0's - boolean isDouble= false; - for (int i= 0; i < 2 && !isDouble; i++) { - if (!form[i].isBlank()) - isDouble= ((o[i] instanceof Double)); - } - for(int i=0;i<2;i++){ - if (form[i].isBlank()) { - if (isDouble) - o[i]= new Double(0.0); - else - o[i]=""; // in this case, empty cells are handled as blank, not zero - } - } - - - boolean res; - if (o[0] instanceof Double && o[1] instanceof Double) { - //if (dub[0].doubleValue() <= dub[1].doubleValue()){ - if (((Double)o[0]).doubleValue() > ((Double)o[1]).doubleValue()){ - res = true; - }else{ - res = false; - } - } else { // string comparison?? - // This is what Excel does ... - if (Formula.isErrorValue(o[0].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); - if (Formula.isErrorValue(o[1].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); - // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res= (o[0].toString().compareTo(o[1].toString())>0); + + /* Operator specific calculate method, this one determines if the second-to-top + operand is less than the top operand; Returns a PtgBool + + */ + public Ptg calculatePtg(Ptg[] form) { + try { + // 20090202 KSC: Handle array formulas + Object[] o = getValuesFromPtgs(form); + if (!o[0].getClass().isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o == null || o.length != 2) { + Logger.logInfo("calculating formula failesd, wrong number of values in PtgGT"); + return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel + } + // blank handling: + // determine if any of the operands are double - if true, + // then blank comparisons will be treated as 0's + boolean isDouble = false; + for (int i = 0; i < 2 && !isDouble; i++) { + if (!form[i].isBlank()) + isDouble = ((o[i] instanceof Double)); + } + for (int i = 0; i < 2; i++) { + if (form[i].isBlank()) { + if (isDouble) + o[i] = new Double(0.0); + else + o[i] = ""; // in this case, empty cells are handled as blank, not zero + } + } + + + boolean res; + if (o[0] instanceof Double && o[1] instanceof Double) { + //if (dub[0].doubleValue() <= dub[1].doubleValue()){ + res = ((Double) o[0]).doubleValue() > ((Double) o[1]).doubleValue(); + } else { // string comparison?? + // This is what Excel does ... + if (Formula.isErrorValue(o[0].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + if (Formula.isErrorValue(o[1].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = (o[0].toString().compareTo(o[1].toString()) > 0); /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); @@ -125,32 +133,34 @@ public Ptg calculatePtg(Ptg[] form){ } catch (ArrayIndexOutOfBoundsException e) { res= false; }*/ - } - - PtgBool pboo = new PtgBool(res); - return pboo; - } else { // handle array fomulas - boolean res= false; - String retArry= ""; - int nArrays= java.lang.reflect.Array.getLength(o); // TODO: Should always be 2 ???????????????????? - int nVals= java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i= 0; i < nArrays-1; i+=2) { - res= false; - Object secondOp= null; - boolean comparitorIsArray= o[i+1].getClass().isArray(); - if (!comparitorIsArray) secondOp= o[i+1]; - for (int j= 0; j < nVals; j++) { - Object firstOp= Array.get(o[i],j); // first array index j - if (comparitorIsArray) - secondOp= Array.get(o[i+1],j); // second array index j - if (firstOp instanceof Double && secondOp instanceof Double) - res= ((Double)firstOp).compareTo((Double)secondOp)>0; - else { // string comparison? - // This is what Excel does ... - if (Formula.isErrorValue(o[0].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); - if (Formula.isErrorValue(o[1].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); - // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res= (o[0].toString().compareTo(o[1].toString())>0); + } + + PtgBool pboo = new PtgBool(res); + return pboo; + } else { // handle array fomulas + boolean res = false; + String retArry = ""; + int nArrays = java.lang.reflect.Array.getLength(o); // TODO: Should always be 2 ???????????????????? + int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays + for (int i = 0; i < nArrays - 1; i += 2) { + res = false; + Object secondOp = null; + boolean comparitorIsArray = o[i + 1].getClass().isArray(); + if (!comparitorIsArray) secondOp = o[i + 1]; + for (int j = 0; j < nVals; j++) { + Object firstOp = Array.get(o[i], j); // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j); // second array index j + if (firstOp instanceof Double && secondOp instanceof Double) + res = ((Double) firstOp).compareTo((Double) secondOp) > 0; + else { // string comparison? + // This is what Excel does ... + if (Formula.isErrorValue(o[0].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + if (Formula.isErrorValue(o[1].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = (o[0].toString().compareTo(o[1].toString()) > 0); /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); @@ -162,15 +172,15 @@ public Ptg calculatePtg(Ptg[] form){ } catch (ArrayIndexOutOfBoundsException e) { res= false; }*/ - } - retArry= retArry + res + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } + } + retArry = retArry + res + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } /*}catch(NumberFormatException e){ 20090203 KSC: ahndled above String[] s = getStringValuesFromPtgs(form); if (s==null || s.length<2) { // 20081203 KSC: Handle errors ala Excel @@ -190,10 +200,10 @@ public Ptg calculatePtg(Ptg[] form){ } return new PtgBool(true); */ - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - /*}*/ + } catch (Exception ex) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + /*}*/ } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgInt.java b/src/main/java/io/starter/formats/XLS/formulas/PtgInt.java index 3845d57..95d9fa6 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgInt.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgInt.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -37,84 +37,86 @@ */ -public class PtgInt extends GenericPtg implements Ptg -{ +public class PtgInt extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2129624418815329359L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2129624418815329359L; + + public boolean getIsOperand() { + return true; + } - public boolean getIsOperand(){return true;} int val; - - /** return the human-readable String representation of - */ - public String getString(){ + + /** + * return the human-readable String representation of + */ + public String getString() { return String.valueOf(val); } - - public PtgInt(){ + + public PtgInt() { } - + /* * constructer to create ptgint's on the fly, from formulas */ - public PtgInt(int i){ - val = i; - this.updateRecord(); + public PtgInt(int i) { + val = i; + this.updateRecord(); } - - public void init(byte[] b){ - ptgId = b[0]; - record = b; + + public void init(byte[] b) { + ptgId = b[0]; + record = b; this.populateVals(); } - + // 0 to 65535 - outside of these bounds must be a PtgNumber - private void populateVals(){ - byte b = 0; + private void populateVals() { + byte b = 0; int s = ByteTools.readInt(record[1], record[2], b, b); val = s; - } - - public int getVal(){ - return val; } - + + public int getVal() { + return val; + } + public int getIntVal() { return val; } - - public Object getValue(){ + + public Object getValue() { Integer i = Integer.valueOf(val); return i; } - - public void setVal(int i){ + + public void setVal(int i) { val = i; this.updateRecord(); } - - public boolean getBooleanVal(){ - if (val == 1)return true; - return false; + + public boolean getBooleanVal() { + return val == 1; } - - public void updateRecord(){ + + public void updateRecord() { byte[] tmp = new byte[1]; tmp[0] = PTG_INT; - byte[] brow = ByteTools.shortToLEBytes((short)val); - tmp = ByteTools.append(brow, tmp); + byte[] brow = ByteTools.shortToLEBytes((short) val); + tmp = ByteTools.append(brow, tmp); record = tmp; } - - public int getLength(){ + + public int getLength() { return PTG_INT_LENGTH; } - - public String toString(){ - return String.valueOf(this.getVal()); + + public String toString() { + return String.valueOf(this.getVal()); } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.java b/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.java index e7cb9d2..a33a9cf 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,9 +24,6 @@ import java.util.ArrayList; -import io.starter.toolkit.Logger; - - /* Computes the intersection of the two top operands. Essentially @@ -39,182 +36,191 @@ */ -public class PtgIsect extends GenericPtg implements Ptg -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2131759675781833457L; +public class PtgIsect extends GenericPtg implements Ptg { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2131759675781833457L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public PtgIsect() { + ptgId = 0xF; + record = new byte[1]; + record[0] = 0xF; + } - public boolean getIsOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - - public PtgIsect(){ - ptgId = 0xF; - record = new byte[1]; - record[0] = 0xF; - } - public String toString() { return this.getString(); } - /** return the human-readable String representation of - */ - public String getString(){ + + /** + * return the human-readable String representation of + */ + public String getString() { return " "; } - - public int getLength(){ + + public int getLength() { return PTG_ISECT_LENGTH; } - - /** Intersection = Where A and B are shared. - The ISECT operator (space) - Returns the intersected range of two ranges. If the resulting cell - range is empty, the formula will return the error code “#NULL!” (for instance A1:A2 B3). - A1:B2 B2:C3 ==> B2 - */ - public Ptg calculatePtg(Ptg[] form){ - if (form.length!=2) + + /** + * Intersection = Where A and B are shared. + * The ISECT operator (space) + * Returns the intersected range of two ranges. If the resulting cell + * range is empty, the formula will return the error code “#NULL!” (for instance A1:A2 B3). + * A1:B2 B2:C3 ==> B2 + */ + public Ptg calculatePtg(Ptg[] form) { + if (form.length != 2) return new PtgErr(PtgErr.ERROR_VALUE); - try { - String sourceSheet= null; - try { - sourceSheet= this.getParentRec().getSheet().getSheetName(); - } catch (NullPointerException ne) { - ; - } - ArrayList first= null; - ArrayList last= null; - for (int i= 0; i < 2; i++) { - Ptg p= form[i]; - ArrayList a= new ArrayList(); - if (p instanceof PtgArea) { - if (!((PtgArea) p).wholeCol || ((PtgArea) p).wholeRow) { - Ptg[] pc= ((PtgArea)p).getComponents(); - if (pc!=null) { - for (int j= 0; j < pc.length; j++) { - ((PtgRef)pc[j]).setSheetName(((PtgArea)p).getSheetName()); - a.add(pc[j]); - } - } - } else - a.add(p); // TODO: what????????? - } else if (p instanceof PtgRef3d) { - a.add(p); - } else if (p instanceof PtgRef) { - a.add(p); - } else if (p instanceof PtgName) { - Ptg[] pc= ((PtgName)p).getComponents(); - for (int j= 0; j < pc.length; j++) - a.add(pc[j]); - } else if (p instanceof PtgStr) { - String[] comps= (p.toString()).split(","); - for (int j= 0; j < comps.length; j++) { - if (comps[j].indexOf(":")==-1) { - if (!comps[j].equals("#REF!") && - !comps[j].equals("#NULL!")) { - PtgRef3d pr= new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(comps[j]); - a.add(pr); - } else { - PtgRefErr3d pr= new PtgRefErr3d(); - pr.setParentRec(this.getParentRec()); - a.add(pr); - } - } else { - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(comps[j]); - Ptg[] pcs= pa.getComponents(); - if (pcs!=null) { - for (int k= 0; k < pcs.length; k++) { - ((PtgRef)pcs[k]).setSheetName(pa.getSheetName()); - a.add(pcs[k]); - } - } - } - } - } else if (p instanceof PtgArray) { - // parse array components and create refs - Ptg[] pc= ((PtgArray)p).getComponents(); - if (pc!=null) { - for (int j= 0; j < pc.length; j++) { - String loc=((PtgStr) pc[j]).toString(); - if (loc.indexOf(":")==-1) { - if (loc.indexOf("!")==-1) { - PtgRef pr= new PtgRef(); - pr.setUseReferenceTracker(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } else { - PtgRef3d pr= new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } - } else { - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(loc); - Ptg[] pcs= pa.getComponents(); - for (int k= 0; k < pcs.length; k++) { - ((PtgRef)pcs[k]).setSheetName(pa.getSheetName()); - a.add(pcs[k]); - } - } - } - } - } else if (p instanceof PtgErr || p instanceof PtgRefErr || p instanceof PtgAreaErr3d) { - // DO WHAT??? - } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array - Ptg[] pc= ((GenericPtg) p).vars; - for (int j= 0; j < pc.length; j++) { - if (pc[j] instanceof PtgArea & !(pc[j] instanceof PtgAreaErr3d)) { - Ptg[] pa= pc[j].getComponents(); - for (int k= 0; k < pa.length; k++) - a.add(pa[k]); - } else - a.add(pc[j]); - } - } - if (first==null) - first= a; - else - last= a; - } - // now have components for both operands - // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars - GenericPtg retp= new PtgMystery(); - ArrayList retptgs= new ArrayList(); - for (int k= 0; k < first.size(); k++) { - PtgRef pr= (PtgRef) first.get(k); - int[] rc= pr.getIntLocation(); - for (int m= 0; m < last.size(); m++) { - PtgRef pc= (PtgRef) last.get(m); - int[] rc2= pc.getIntLocation(); - if (java.util.Arrays.equals(rc, rc2)) { - retptgs.add(pc); - last.remove(m); - m--; - } - } - } - Ptg[] ptgs= new Ptg[retptgs.size()]; - retptgs.toArray(ptgs); - retp.setVars(ptgs); - return retp; - }catch(NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - }catch(Exception e) { // handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - } + try { + String sourceSheet = null; + try { + sourceSheet = this.getParentRec().getSheet().getSheetName(); + } catch (NullPointerException ne) { + } + ArrayList first = null; + ArrayList last = null; + for (int i = 0; i < 2; i++) { + Ptg p = form[i]; + ArrayList a = new ArrayList(); + if (p instanceof PtgArea) { + if (!((PtgArea) p).wholeCol || ((PtgArea) p).wholeRow) { + Ptg[] pc = p.getComponents(); + if (pc != null) { + for (int j = 0; j < pc.length; j++) { + ((PtgRef) pc[j]).setSheetName(((PtgArea) p).getSheetName()); + a.add(pc[j]); + } + } + } else + a.add(p); // TODO: what????????? + } else if (p instanceof PtgRef3d) { + a.add(p); + } else if (p instanceof PtgRef) { + a.add(p); + } else if (p instanceof PtgName) { + Ptg[] pc = p.getComponents(); + for (int j = 0; j < pc.length; j++) + a.add(pc[j]); + } else if (p instanceof PtgStr) { + String[] comps = (p.toString()).split(","); + for (int j = 0; j < comps.length; j++) { + if (comps[j].indexOf(":") == -1) { + if (!comps[j].equals("#REF!") && + !comps[j].equals("#NULL!")) { + PtgRef3d pr = new PtgRef3d(false); + pr.setParentRec(this.getParentRec()); + pr.setLocation(comps[j]); + a.add(pr); + } else { + PtgRefErr3d pr = new PtgRefErr3d(); + pr.setParentRec(this.getParentRec()); + a.add(pr); + } + } else { + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(this.getParentRec()); + pa.setLocation(comps[j]); + Ptg[] pcs = pa.getComponents(); + if (pcs != null) { + for (int k = 0; k < pcs.length; k++) { + ((PtgRef) pcs[k]).setSheetName(pa.getSheetName()); + a.add(pcs[k]); + } + } + } + } + } else if (p instanceof PtgArray) { + // parse array components and create refs + Ptg[] pc = p.getComponents(); + if (pc != null) { + for (int j = 0; j < pc.length; j++) { + String loc = pc[j].toString(); + if (loc.indexOf(":") == -1) { + if (loc.indexOf("!") == -1) { + PtgRef pr = new PtgRef(); + pr.setUseReferenceTracker(false); + pr.setParentRec(this.getParentRec()); + pr.setLocation(loc); + a.add(pr); + } else { + PtgRef3d pr = new PtgRef3d(false); + pr.setParentRec(this.getParentRec()); + pr.setLocation(loc); + a.add(pr); + } + } else { + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(this.getParentRec()); + pa.setLocation(loc); + Ptg[] pcs = pa.getComponents(); + for (int k = 0; k < pcs.length; k++) { + ((PtgRef) pcs[k]).setSheetName(pa.getSheetName()); + a.add(pcs[k]); + } + } + } + } + } else if (p instanceof PtgErr || p instanceof PtgRefErr || p instanceof PtgAreaErr3d) { + // DO WHAT??? + } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array + Ptg[] pc = ((GenericPtg) p).vars; + for (int j = 0; j < pc.length; j++) { + if (pc[j] instanceof PtgArea & !(pc[j] instanceof PtgAreaErr3d)) { + Ptg[] pa = pc[j].getComponents(); + for (int k = 0; k < pa.length; k++) + a.add(pa[k]); + } else + a.add(pc[j]); + } + } + if (first == null) + first = a; + else + last = a; + } + // now have components for both operands + // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars + GenericPtg retp = new PtgMystery(); + ArrayList retptgs = new ArrayList(); + for (int k = 0; k < first.size(); k++) { + PtgRef pr = (PtgRef) first.get(k); + int[] rc = pr.getIntLocation(); + for (int m = 0; m < last.size(); m++) { + PtgRef pc = (PtgRef) last.get(m); + int[] rc2 = pc.getIntLocation(); + if (java.util.Arrays.equals(rc, rc2)) { + retptgs.add(pc); + last.remove(m); + m--; + } + } + } + Ptg[] ptgs = new Ptg[retptgs.size()]; + retptgs.toArray(ptgs); + retp.setVars(ptgs); + return retp; + } catch (NumberFormatException e) { + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } catch (Exception e) { // handle error ala Excel + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgLE.java b/src/main/java/io/starter/formats/XLS/formulas/PtgLE.java index 2f79599..ab15b59 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgLE.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgLE.java @@ -2,31 +2,31 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; -import java.lang.reflect.Array; -import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.Formula; import io.starter.toolkit.Logger; +import java.lang.reflect.Array; + /* Ptg that is a Less than or equal to operand @@ -40,76 +40,86 @@ */ -public class PtgLE extends GenericPtg implements Ptg -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4356555760240325388L; - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - public PtgLE(){ - ptgId = 0xA; - record = new byte[1]; - record[0] = 0xA; - } - /** return the human-readable String representation of - */ - public String getString(){ - return "<="; - } - public int getLength(){ - return PTG_LE_LENGTH; - } - /* Operator specific calculate method, this one determines if the second-to-top - operand is less than or equal to the top operand; Returns a PtgBool - - */ - public Ptg calculatePtg(Ptg[] form){ - try{ - // 20090202 KSC: Handle array formulas - Object[] o= super.getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o==null || o.length != 2){ - Logger.logWarn("calculating formula failed, wrong number of values in PtgLE"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel - } - // blank handling: - // determine if any of the operands are double - if true, - // then blank comparisons will be treated as 0's - boolean isDouble= false; - for (int i= 0; i < 2 && !isDouble; i++) { - if (!form[i].isBlank()) - isDouble= ((o[i] instanceof Double)); - } - for(int i=0;i<2;i++){ - if (form[i].isBlank()) { - if (isDouble) - o[i]= new Double(0.0); - else - o[i]=""; // in this case, empty cells are handled as blank, not zero - } - } - - boolean res; - if (o[0] instanceof Double && o[1] instanceof Double) { - //if (dub[0].doubleValue() <= dub[1].doubleValue()){ - if (((Double)o[0]).doubleValue() <= ((Double)o[1]).doubleValue()){ - res = true; - }else{ - res = false; - } - } else { // string comparison?? - // This is what Excel does - if (Formula.isErrorValue(o[0].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); - if (Formula.isErrorValue(o[1].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); - // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res= (o[0].toString().compareTo(o[1].toString())<=0); +public class PtgLE extends GenericPtg implements Ptg { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -4356555760240325388L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public PtgLE() { + ptgId = 0xA; + record = new byte[1]; + record[0] = 0xA; + } + + /** + * return the human-readable String representation of + */ + public String getString() { + return "<="; + } + + public int getLength() { + return PTG_LE_LENGTH; + } + + /* Operator specific calculate method, this one determines if the second-to-top + operand is less than or equal to the top operand; Returns a PtgBool + + */ + public Ptg calculatePtg(Ptg[] form) { + try { + // 20090202 KSC: Handle array formulas + Object[] o = getValuesFromPtgs(form); + if (!o[0].getClass().isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o == null || o.length != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgLE"); + return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel + } + // blank handling: + // determine if any of the operands are double - if true, + // then blank comparisons will be treated as 0's + boolean isDouble = false; + for (int i = 0; i < 2 && !isDouble; i++) { + if (!form[i].isBlank()) + isDouble = ((o[i] instanceof Double)); + } + for (int i = 0; i < 2; i++) { + if (form[i].isBlank()) { + if (isDouble) + o[i] = new Double(0.0); + else + o[i] = ""; // in this case, empty cells are handled as blank, not zero + } + } + + boolean res; + if (o[0] instanceof Double && o[1] instanceof Double) { + //if (dub[0].doubleValue() <= dub[1].doubleValue()){ + res = ((Double) o[0]).doubleValue() <= ((Double) o[1]).doubleValue(); + } else { // string comparison?? + // This is what Excel does + if (Formula.isErrorValue(o[0].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + if (Formula.isErrorValue(o[1].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = (o[0].toString().compareTo(o[1].toString()) <= 0); /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); @@ -121,32 +131,34 @@ public Ptg calculatePtg(Ptg[] form){ } catch (ArrayIndexOutOfBoundsException e) { res= false; }*/ - } - - PtgBool pboo = new PtgBool(res); - return pboo; - } else { // handle array fomulas - boolean res= false; - String retArry= ""; - int nArrays= java.lang.reflect.Array.getLength(o); // TODO: Should always be 2 ???????????????????? - int nVals= java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i= 0; i < nArrays-1; i+=2) { - res= false; - Object secondOp= null; - boolean comparitorIsArray= o[i+1].getClass().isArray(); - if (!comparitorIsArray) secondOp= o[i+1]; - for (int j= 0; j < nVals; j++) { - Object firstOp= Array.get(o[i],j); // first array index j - if (comparitorIsArray) - secondOp= Array.get(o[i+1],j); // second array index j - if (firstOp instanceof Double && secondOp instanceof Double) - res= ((Double)firstOp).compareTo((Double)secondOp)<=0; - else { // string comparison? - // This is what Excel does ... - if (Formula.isErrorValue(o[0].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); - if (Formula.isErrorValue(o[1].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); - // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res= (o[0].toString().compareTo(o[1].toString())<=0); + } + + PtgBool pboo = new PtgBool(res); + return pboo; + } else { // handle array fomulas + boolean res = false; + String retArry = ""; + int nArrays = java.lang.reflect.Array.getLength(o); // TODO: Should always be 2 ???????????????????? + int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays + for (int i = 0; i < nArrays - 1; i += 2) { + res = false; + Object secondOp = null; + boolean comparitorIsArray = o[i + 1].getClass().isArray(); + if (!comparitorIsArray) secondOp = o[i + 1]; + for (int j = 0; j < nVals; j++) { + Object firstOp = Array.get(o[i], j); // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j); // second array index j + if (firstOp instanceof Double && secondOp instanceof Double) + res = ((Double) firstOp).compareTo((Double) secondOp) <= 0; + else { // string comparison? + // This is what Excel does ... + if (Formula.isErrorValue(o[0].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + if (Formula.isErrorValue(o[1].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = (o[0].toString().compareTo(o[1].toString()) <= 0); /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); @@ -158,15 +170,15 @@ public Ptg calculatePtg(Ptg[] form){ } catch (ArrayIndexOutOfBoundsException e) { res= false; }*/ - } - retArry= retArry + res + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } + } + retArry = retArry + res + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } /*}catch(NumberFormatException e){ 20090203 KSC: Handled above String[] s = getStringValuesFromPtgs(form); if (s==null || s.length<2) { // 20081203 KSC: Handle errors ala Excel @@ -186,10 +198,10 @@ public Ptg calculatePtg(Ptg[] form){ // Unfortuately <, >, and <> can all deal with strings as well... }*/ - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - + } catch (Exception ex) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgLT.java b/src/main/java/io/starter/formats/XLS/formulas/PtgLT.java index 9764ab9..37992ad 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgLT.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgLT.java @@ -2,31 +2,31 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; -import java.lang.reflect.Array; -import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.Formula; import io.starter.toolkit.Logger; +import java.lang.reflect.Array; + /* Ptg that is a Less than operand @@ -39,79 +39,86 @@ */ -public class PtgLT extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2568203115024599915L; - - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - public PtgLT(){ - ptgId = 0x9; - record = new byte[1]; - record[0] = 0x9; - } - /** return the human-readable String representation of - */ - public String getString(){ +public class PtgLT extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2568203115024599915L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public PtgLT() { + ptgId = 0x9; + record = new byte[1]; + record[0] = 0x9; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return "<"; } - - public int getLength(){ + + public int getLength() { return PTG_LT_LENGTH; } - - /* Operator specific calculate method, this one determines if the second-to-top - operand is less than the top operand; Returns a PtgBool - - */ - public Ptg calculatePtg(Ptg[] form){ - try{ - // 20090202 KSC: Handle array formulas - Object[] o= super.getValuesFromPtgs(form); - if (o==null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o.length != 2){ - Logger.logWarn("calculating formula failed, wrong number of values in PtgLT"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; - } - // blank handling: - // determine if any of the operands are double - if true, - // then blank comparisons will be treated as 0's - boolean isDouble= false; - for (int i= 0; i < 2 && !isDouble; i++) { - if (!form[i].isBlank()) - isDouble= ((o[i] instanceof Double)); - } - for(int i=0;i<2;i++){ - if (form[i].isBlank()) { - if (isDouble) - o[i]= new Double(0.0); - else - o[i]=""; // in this case, empty cells are handled as blank, not zero - } - } - - boolean res; - //if (dub[0].doubleValue() < dub[1].doubleValue()){ - if (o[0] instanceof Double && o[1] instanceof Double) { - if (((Double)o[0]).doubleValue() < ((Double)o[1]).doubleValue()){ - res = true; - }else{ - res = false; - } - } else { // string comparison?? - // This is what Excel does ... - if (Formula.isErrorValue(o[0].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); - if (Formula.isErrorValue(o[1].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); - // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res= (o[0].toString().compareTo(o[1].toString())<0); + + /* Operator specific calculate method, this one determines if the second-to-top + operand is less than the top operand; Returns a PtgBool + + */ + public Ptg calculatePtg(Ptg[] form) { + try { + // 20090202 KSC: Handle array formulas + Object[] o = getValuesFromPtgs(form); + if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) + if (!o[0].getClass().isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o.length != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgLT"); + return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; + } + // blank handling: + // determine if any of the operands are double - if true, + // then blank comparisons will be treated as 0's + boolean isDouble = false; + for (int i = 0; i < 2 && !isDouble; i++) { + if (!form[i].isBlank()) + isDouble = ((o[i] instanceof Double)); + } + for (int i = 0; i < 2; i++) { + if (form[i].isBlank()) { + if (isDouble) + o[i] = new Double(0.0); + else + o[i] = ""; // in this case, empty cells are handled as blank, not zero + } + } + + boolean res; + //if (dub[0].doubleValue() < dub[1].doubleValue()){ + if (o[0] instanceof Double && o[1] instanceof Double) { + res = ((Double) o[0]).doubleValue() < ((Double) o[1]).doubleValue(); + } else { // string comparison?? + // This is what Excel does ... + if (Formula.isErrorValue(o[0].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + if (Formula.isErrorValue(o[1].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = (o[0].toString().compareTo(o[1].toString()) < 0); /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); @@ -123,34 +130,36 @@ public Ptg calculatePtg(Ptg[] form){ } catch (ArrayIndexOutOfBoundsException e) { res= false; }*/ - } - - PtgBool pboo = new PtgBool(res); - return pboo; - } else { // handle array fomulas - boolean res= false; - String retArry= ""; - int nArrays= java.lang.reflect.Array.getLength(o); - if (nArrays!=2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals= java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i= 0; i < nArrays-1; i+=2) { - res= false; - Object secondOp= null; - boolean comparitorIsArray= o[i+1].getClass().isArray(); - if (!comparitorIsArray) secondOp= o[i+1]; - for (int j= 0; j < nVals; j++) { - Object firstOp= Array.get(o[i],j); // first array index j - if (comparitorIsArray) - secondOp= Array.get(o[i+1],j); // second array index j - - if (firstOp instanceof Double && secondOp instanceof Double) - res= ((Double)firstOp).compareTo((Double)secondOp)<0; - else { // string comparison? - // This is what Excel does ... - if (Formula.isErrorValue(o[0].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); - if (Formula.isErrorValue(o[1].toString())) return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); - // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res= (o[0].toString().compareTo(o[1].toString())<0); + } + + PtgBool pboo = new PtgBool(res); + return pboo; + } else { // handle array fomulas + boolean res = false; + String retArry = ""; + int nArrays = java.lang.reflect.Array.getLength(o); + if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); + int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays + for (int i = 0; i < nArrays - 1; i += 2) { + res = false; + Object secondOp = null; + boolean comparitorIsArray = o[i + 1].getClass().isArray(); + if (!comparitorIsArray) secondOp = o[i + 1]; + for (int j = 0; j < nVals; j++) { + Object firstOp = Array.get(o[i], j); // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j); // second array index j + + if (firstOp instanceof Double && secondOp instanceof Double) + res = ((Double) firstOp).compareTo((Double) secondOp) < 0; + else { // string comparison? + // This is what Excel does ... + if (Formula.isErrorValue(o[0].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + if (Formula.isErrorValue(o[1].toString())) + return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = (o[0].toString().compareTo(o[1].toString()) < 0); /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); @@ -162,15 +171,15 @@ public Ptg calculatePtg(Ptg[] form){ } catch (ArrayIndexOutOfBoundsException e) { res= false; }*/ - } - retArry= retArry + res + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } + } + retArry = retArry + res + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } /*}catch(NumberFormatException e){ 20090203 KSC: Handled above try { // Unfortuately <, >, and <> can all deal with strings as well... @@ -184,11 +193,11 @@ public Ptg calculatePtg(Ptg[] form){ } return new PtgBool(true); */ - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - /*}*/ + } catch (Exception ex) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + /*}*/ } - - + + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java index fe17adc..9661128 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,100 +22,103 @@ */ package io.starter.formats.XLS.formulas; -import java.util.ArrayList; -import java.util.Stack; - import io.starter.formats.XLS.ExpressionParser; import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; -import io.starter.OpenXLS.ExcelTools; -/** - -PtgMemArea is an optimization of referenced areas. Sweet! -******************************************************************************** -NOTE: Below is from documentation but DOES NOT APPEAR to be what happens in actuality; -PtgMemArea token is followed by several ptg reference-types plus ptgunion(s), ends with a PtgParen. -The cce field is the length of all of these following tokens. -These following Ptgs are set and parsed in .setPostRecord +import java.util.ArrayList; +import java.util.Stack; +/** + * PtgMemArea is an optimization of referenced areas. Sweet! + * ******************************************************************************* + * NOTE: Below is from documentation but DOES NOT APPEAR to be what happens in actuality; + * PtgMemArea token is followed by several ptg reference-types plus ptgunion(s), ends with a PtgParen. + * The cce field is the length of all of these following tokens. + * These following Ptgs are set and parsed in .setPostRecord + *

                + *

                + *

                + * Like most optimizations it really sucks. It is also one of the few Ptg's that + * has a variable length. + *

                + * Format of length section + *

                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0           (reserved)     4       Whatever it may be
                + * 2           cce			   2	   length of the reference subexpression
                + *
                + * Format of reference Subexpression
                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0			cref		2			The number of rectangles to follow
                + * 2			rgref		var			An Array of rectangles
                + *
                + * Format of Rectangles
                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0           rwFirst     2       The First row of the reference
                + * 2           rwLast     2       The Last row of the reference
                + * 4           ColFirst    1       (see following table)
                + * 6           ColLast    1       (see following table)
                + * 
                + * + * @see Ptg + * @see Formula + */ +public class PtgMemArea extends GenericPtg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -6869393084367355874L; + int cce = 0; + Stack subexpression = null; + Ptg[] ptgs = null; + public boolean getIsOperand() { + return true; + } -Like most optimizations it really sucks. It is also one of the few Ptg's that -has a variable length. -Format of length section -
                -    Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0           (reserved)     4       Whatever it may be
                -    2           cce			   2	   length of the reference subexpression
                - 
                - Format of reference Subexpression
                -     Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0			cref		2			The number of rectangles to follow
                -    2			rgref		var			An Array of rectangles
                -    
                -Format of Rectangles
                -    Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0           rwFirst     2       The First row of the reference 
                -    2           rwLast     2       The Last row of the reference 
                -    4           ColFirst    1       (see following table)
                -    6           ColLast    1       (see following table)
                -
                + public void init(byte[] b) { + ptgId = b[0]; + record = b; + this.populateVals(); + } - * @see Ptg - * @see Formula + public int getnTokens() { + return cce; + } // KSC: - - */ -public class PtgMemArea extends GenericPtg{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6869393084367355874L; - int cce = 0; - Stack subexpression = null; - Ptg[] ptgs= null; - - public boolean getIsOperand(){return true;} - - - public void init(byte[] b){ - ptgId = b[0]; - record = b; - this.populateVals(); - } - - public int getnTokens() { return cce; } // KSC: + /** + * sets the bytes to describe the subexpression (set of ptgs) + * and parses the subexpression + * + * @param b + */ + public void setSubExpression(byte[] b) { + byte[] retbytes = new byte[7]; + System.arraycopy(record, 0, retbytes, 0, record.length); + retbytes = ByteTools.append(b, retbytes); + record = retbytes; + this.populateVals(); + // TODO: + } - /** - * sets the bytes to describe the subexpression (set of ptgs) - * and parses the subexpression - * @param b - */ - public void setSubExpression(byte[] b) { - byte[] retbytes = new byte[7]; - System.arraycopy(record, 0, retbytes, 0, record.length); - retbytes= ByteTools.append(b, retbytes); - record= retbytes; - this.populateVals(); - // TODO: - } + // really no need to keep postexpression as can regenerate easily ... + byte[] postExp = null; - // really no need to keep postexpression as can regenerate easily ... - byte[] postExp= null; - /** - * sets the PtgExtraMem structure which is appended to the end of the function array - * @param b - */ - public void setPostExpression(byte[] b, int expressionLen) { - int len= b.length-expressionLen; - postExp= new byte[len]; - System.arraycopy(b, expressionLen, postExp, 0, len); + /** + * sets the PtgExtraMem structure which is appended to the end of the function array + * + * @param b + */ + public void setPostExpression(byte[] b, int expressionLen) { + int len = b.length - expressionLen; + postExp = new byte[len]; + System.arraycopy(b, expressionLen, postExp, 0, len); /* * parsing PtgExtraMem - not really necessary * @@ -131,125 +134,129 @@ public void setPostExpression(byte[] b, int expressionLen) { ExcelTools.formatRangeRowCol(rc); } }*/ - } - - /** - * retrieves the PtgExtraMem structure that is located at the end of the function record - * @return - */ - public byte[] getPostRecord(){ - short cce= 0; - // first count # refs (excluding ptgUnion, range, etc ) - for (int i= 0; i < subexpression.size(); i++) { - Ptg p= (Ptg) subexpression.get(i); - if (p instanceof PtgRef) { - cce++; - } - } - - // input cce + cce*Ref8U (8 bytes) describing reference - byte[] b= ByteTools.shortToLEBytes(cce); - byte[] recbytes= new byte[(cce * 8)+2]; - recbytes[0]= b[0]; - recbytes[1]= b[1]; - int pos= 2; - for (int i= 0; i < subexpression.size() && pos+7 < recbytes.length; i++) { - Ptg p= (Ptg) subexpression.get(i); - if (p instanceof PtgRef) { - int[] rc= ((PtgRef)p).getRowCol(); - System.arraycopy(ByteTools.shortToLEBytes((short) rc[0]), 0, recbytes, pos, 2); // rw first - System.arraycopy(ByteTools.shortToLEBytes((short) rc[1]), 0, recbytes, pos+4, 2); // col first - if (rc.length==2) { // a single ref; repeat - System.arraycopy(ByteTools.shortToLEBytes((short) rc[0]), 0, recbytes, pos+2, 2); // rw last - System.arraycopy(ByteTools.shortToLEBytes((short) rc[1]), 0, recbytes, pos+6, 2); // col last - } else { // a range - System.arraycopy(ByteTools.shortToLEBytes((short) rc[2]), 0, recbytes, pos+2, 2); // rw last - System.arraycopy(ByteTools.shortToLEBytes((short) rc[3]), 0, recbytes, pos+6, 2); // col last - } - pos+=8; - } - } + } + + /** + * retrieves the PtgExtraMem structure that is located at the end of the function record + * + * @return + */ + public byte[] getPostRecord() { + short cce = 0; + // first count # refs (excluding ptgUnion, range, etc ) + for (int i = 0; i < subexpression.size(); i++) { + Ptg p = (Ptg) subexpression.get(i); + if (p instanceof PtgRef) { + cce++; + } + } + + // input cce + cce*Ref8U (8 bytes) describing reference + byte[] b = ByteTools.shortToLEBytes(cce); + byte[] recbytes = new byte[(cce * 8) + 2]; + recbytes[0] = b[0]; + recbytes[1] = b[1]; + int pos = 2; + for (int i = 0; i < subexpression.size() && pos + 7 < recbytes.length; i++) { + Ptg p = (Ptg) subexpression.get(i); + if (p instanceof PtgRef) { + int[] rc = ((PtgRef) p).getRowCol(); + System.arraycopy(ByteTools.shortToLEBytes((short) rc[0]), 0, recbytes, pos, 2); // rw first + System.arraycopy(ByteTools.shortToLEBytes((short) rc[1]), 0, recbytes, pos + 4, 2); // col first + if (rc.length == 2) { // a single ref; repeat + System.arraycopy(ByteTools.shortToLEBytes((short) rc[0]), 0, recbytes, pos + 2, 2); // rw last + System.arraycopy(ByteTools.shortToLEBytes((short) rc[1]), 0, recbytes, pos + 6, 2); // col last + } else { // a range + System.arraycopy(ByteTools.shortToLEBytes((short) rc[2]), 0, recbytes, pos + 2, 2); // rw last + System.arraycopy(ByteTools.shortToLEBytes((short) rc[3]), 0, recbytes, pos + 6, 2); // col last + } + pos += 8; + } + } /* KSC: TESTING if (!Arrays.equals(recbytes, postExp)) io.starter.toolkit.Logger.log("ISSUE!!!");*/ - return recbytes; - } - - ArrayList refsheets= new ArrayList(); - void populateVals(){ - // 1st byte = ID, next 4 are ignored - // cce= size of following sub-expressions - cce= ByteTools.readShort(record[5], record[6]); - // this is not really correct! - if (record.length> 7) { - byte[] subexp; - subexp = new byte[cce]; + return recbytes; + } + + ArrayList refsheets = new ArrayList(); + + void populateVals() { + // 1st byte = ID, next 4 are ignored + // cce= size of following sub-expressions + cce = ByteTools.readShort(record[5], record[6]); + // this is not really correct! + if (record.length > 7) { + byte[] subexp; + subexp = new byte[cce]; System.arraycopy(record, 7, subexp, 0, cce); subexpression = ExpressionParser.parseExpression(subexp, this.parent_rec); // subexpression stack in form of: REFERENCE, REFERENCE, OP [,REFERENCE, OP] ... // op can be one of: PtgUnion [,] PtgIsect [ ] or PtgRange [:] // calculate subexpression to obtain ptgs try { - Object o= FormulaCalculator.calculateFormula(this.subexpression); - ArrayList components= new ArrayList(); - if (o!=null && o instanceof Ptg[]) { - ptgs= (Ptg[]) o; - for (int i= 0; i < ptgs.length; i++) { - if (!refsheets.contains(((PtgRef)ptgs[i]).getSheetName())) - refsheets.add(((PtgRef)ptgs[i]).getSheetName()); - if (ptgs[i] instanceof PtgArea) { - Ptg[] p= ptgs[i].getComponents(); - for (int j= 0; j < p.length; j++) - components.add(p[j]); - } else - components.add(ptgs[i]); - } - } else { // often a single reference surrounded by parens - for (int i= 0; i < subexpression.size(); i++) { - try { - PtgRef pr= (PtgRef) subexpression.get(i); - if (!refsheets.contains(pr.getSheetName())) - refsheets.add(pr.getSheetName()); - if (pr instanceof PtgArea) { - Ptg[] pa= pr.getComponents(); - for (int j= 0; j < pa.length; j++) - components.add(pa[j]); - } else - components.add(pr); - } catch (Exception e) { - } - } - } ptgs= new Ptg[components.size()]; - components.toArray(ptgs); - } catch (Exception e){ - Logger.logErr("PtgMemArea init: " + e.toString()); + Object o = FormulaCalculator.calculateFormula(this.subexpression); + ArrayList components = new ArrayList(); + if (o != null && o instanceof Ptg[]) { + ptgs = (Ptg[]) o; + for (int i = 0; i < ptgs.length; i++) { + if (!refsheets.contains(((PtgRef) ptgs[i]).getSheetName())) + refsheets.add(((PtgRef) ptgs[i]).getSheetName()); + if (ptgs[i] instanceof PtgArea) { + Ptg[] p = ptgs[i].getComponents(); + for (int j = 0; j < p.length; j++) + components.add(p[j]); + } else + components.add(ptgs[i]); + } + } else { // often a single reference surrounded by parens + for (int i = 0; i < subexpression.size(); i++) { + try { + PtgRef pr = (PtgRef) subexpression.get(i); + if (!refsheets.contains(pr.getSheetName())) + refsheets.add(pr.getSheetName()); + if (pr instanceof PtgArea) { + Ptg[] pa = pr.getComponents(); + for (int j = 0; j < pa.length; j++) + components.add(pa[j]); + } else + components.add(pr); + } catch (Exception e) { + } + } + } + ptgs = new Ptg[components.size()]; + components.toArray(ptgs); + } catch (Exception e) { + Logger.logErr("PtgMemArea init: " + e.toString()); } - + //int z= subexpression.size(); // to get # of references (PtgRefs) = stack size/2 + 1 - } + } } /** * generate the bytes necessary to describe this PtgMemArea; * extra data described by getPostRecord is necessary for completion + * * @see getPostRecord */ - public byte[] getRecord(){ + public byte[] getRecord() { int len = 0; - for (int i=0;i + * NOTE: now obtaining component ptgs is done in populateValues as it is + * a more complex operation than simply gathering all referenced ptgs + */ + public Ptg[] getComponents() { /*if(comps!=null) // cache return comps; @@ -298,33 +307,33 @@ public Ptg[] getComponents(){ */ return ptgs; } - - /** - * traverse through expression to retrieve set of ranges - * either discontiguous union (,), intersected ( ) or regular range (:) - */ - public String toString(){ - return FormulaParser.getExpressionString(subexpression).substring(1); // avoid "=" + + /** + * traverse through expression to retrieve set of ranges + * either discontiguous union (,), intersected ( ) or regular range (:) + */ + public String toString() { + return FormulaParser.getExpressionString(subexpression).substring(1); // avoid "=" + } + + public Object getValue() { + try { + double[] dub; + try { + dub = PtgCalculator.getDoubleValueArray(ptgs); + } catch (CalculationException e) { + return null; + } + double result = 0.0; + for (int i = 0; i < dub.length; i++) { + result += dub[i]; + } + return new Double(result); + } catch (FunctionNotSupportedException e) { + Logger.logWarn("Function Unsupported error in PtgMemFunction: " + e); + return null; } - - public Object getValue(){ - try{ - double[] dub; - try { - dub = PtgCalculator.getDoubleValueArray(ptgs); - } catch (CalculationException e) { - return null; - } - double result= 0.0; - for (int i=0;i. @@ -22,119 +22,118 @@ */ package io.starter.formats.XLS.formulas; -import io.starter.toolkit.ByteTools; import io.starter.OpenXLS.ExcelTools; +import io.starter.toolkit.ByteTools; /** - -PtgMemArea is an optimization of referenced areas. Sweet! - -Like most optimizations it really sucks. It is also one of the few Ptg's that -has a variable length. - -Format of length section -
                -    Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0           (reserved)     4       Whatever it may be
                -    2           cce			   2	   length of the reference subexpression
                - 
                - Format of reference Subexpression
                -     Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0			cref		2			The number of rectangles to follow
                -    2			rgref		var			An Array of rectangles
                -    
                -Format of Rectangles
                -    Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0           rwFirst     2       The First row of the reference 
                -    2           rwLast     2       The Last row of the reference 
                -    4           ColFirst    1       (see following table)
                -    6           ColLast    1       (see following table)
                -
                - + * PtgMemArea is an optimization of referenced areas. Sweet! + *

                + * Like most optimizations it really sucks. It is also one of the few Ptg's that + * has a variable length. + *

                + * Format of length section + *

                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0           (reserved)     4       Whatever it may be
                + * 2           cce			   2	   length of the reference subexpression
                + *
                + * Format of reference Subexpression
                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0			cref		2			The number of rectangles to follow
                + * 2			rgref		var			An Array of rectangles
                + *
                + * Format of Rectangles
                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0           rwFirst     2       The First row of the reference
                + * 2           rwLast     2       The Last row of the reference
                + * 4           ColFirst    1       (see following table)
                + * 6           ColLast    1       (see following table)
                + * 
                + * * @see Ptg * @see Formula - - */ -public class PtgMemAreaA extends PtgMemArea{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5528547215693511069L; - int cce = 0; +public class PtgMemAreaA extends PtgMemArea { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5528547215693511069L; + int cce = 0; int cref = 0; MemArea[] areas; - - public void init(byte[] b){ - ptgId = b[0]; - record = b; - this.populateVals(); - } - - void populateVals(){ - cce = ByteTools.readInt(record[6], record[5]); - cref = ByteTools.readInt(record[8], record[7]); - areas = new MemArea[cref]; - int holder = 9; - for (int i = 0;i. @@ -23,7 +23,6 @@ package io.starter.formats.XLS.formulas; - /* Currently just a placeholder class @@ -32,25 +31,23 @@ */ -public class PtgMemAreaN extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7497529044241909689L; - +public class PtgMemAreaN extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 7497529044241909689L; - /** return the human-readable String representation of - */ - public String getString(){ + /** + * return the human-readable String representation of + */ + public String getString() { return "MEMAREAN"; - } - - - - public int getLength(){ + } + + + public int getLength() { return PTG_MEM_AREA_N_LENGTH; } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.java index c8a47e5..f85a56f 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,7 +23,6 @@ package io.starter.formats.XLS.formulas; - /* Currently just a placeholder class @@ -32,23 +31,23 @@ */ -public class PtgMemAreaNV extends PtgMemAreaA implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7912318247202256986L; +public class PtgMemAreaNV extends PtgMemAreaA implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7912318247202256986L; - /** return the human-readable String representation of - - public String getString(){ - return "MEMAREANV"; - } - */ - - - public int getLength(){ + /** + * return the human-readable String representation of + *

                + * public String getString(){ + * return "MEMAREANV"; + * } + */ + + + public int getLength() { return PTG_MEM_AREA_NV_LENGTH; } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.java index bdef1d8..f55444a 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,7 +23,6 @@ package io.starter.formats.XLS.formulas; - /* Currently just a placeholder class @@ -32,28 +31,22 @@ */ -public class PtgMemErr extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8754800881832127368L; +public class PtgMemErr extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8754800881832127368L; - /** return the human-readable String representation of - */ - public String getString(){ + /** + * return the human-readable String representation of + */ + public String getString() { return "MEMERR"; - } - - public int getLength(){ + } + + public int getLength() { return PTG_MEMERR_LENGTH; } - - - - - - - - + + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.java index e16cc2b..f652122 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,65 +22,73 @@ */ package io.starter.formats.XLS.formulas; +import io.starter.formats.XLS.Boundsheet; +import io.starter.formats.XLS.ExpressionParser; +import io.starter.formats.XLS.FunctionNotSupportedException; +import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.ByteTools; import io.starter.toolkit.FastAddVector; import io.starter.toolkit.Logger; -import io.starter.formats.XLS.*; -import java.util.Stack; import java.util.ArrayList; +import java.util.Stack; -import io.starter.formats.XLS.FunctionNotSupportedException; /** * PtgMemFunc refers to a reference subexpression that doesn't evaluate * to a constant reference. This is still somewhat unclear to me how it functions, * or why it exists for that matter. - * - * This token encapsulates a reference subexpression that results in a non-constant cell address, - * cell range address, or cell range list. For - * + *

                + * This token encapsulates a reference subexpression that results in a non-constant cell address, + * cell range address, or cell range list. For + *

                * A little update on it. Apparently this is used in situations where a record only contains one ptg, but needs to refer to a whole stack of them. * An example is in the Name record. For a built in name that has both row & col repeat regions, the name expression is a ptgmemfunc, but contains * 2 ptgArea3d's. * Also used in Ai's (Chart Series Range Refs) when non-contiguous range refs are required - * + *

                * PtgMemFunc basically represents a complex range and is used where only one PtgRef-type ptg is expected - * - * NOTE: that this represents a NON-CONSTANT expression while PtgMemArea represents a CONSTANT expression + *

                + * NOTE: that this represents a NON-CONSTANT expression while PtgMemArea represents a CONSTANT expression * - *

                - *	OFFSET		NAME		SIZE		CONTENTS
                - *-------------------------------------------------------
                - *	0			cce			2			The length of the reference subexpression
                + * 
                + * 	OFFSET		NAME		SIZE		CONTENTS
                + * -------------------------------------------------------
                + * 	0			cce			2			The length of the reference subexpression
                  */
                -public class PtgMemFunc extends GenericPtg{
                +public class PtgMemFunc extends GenericPtg {
                 
                     public static final long serialVersionUID = 666555444333222l;
                -    
                +
                     Stack subexpression = null; //
                -    
                -    Ptg[] ptgs= null;	// 20090905 KSC: can be PtgRef3d, PtgArea3d, PtgName  ...
                -    
                -    public boolean getIsOperand(){return true;}
                -	public boolean getIsReference(){return true;}	// 20100202 KSC
                -    
                +
                +    Ptg[] ptgs = null;    // 20090905 KSC: can be PtgRef3d, PtgArea3d, PtgName  ...
                +
                +    public boolean getIsOperand() {
                +        return true;
                +    }
                +
                +    public boolean getIsReference() {
                +        return true;
                +    }    // 20100202 KSC
                +
                     int cce;
                     byte[] subexp;
                -    
                -	public void init(byte[] b){
                -		ptgId   = b[0];
                -		record  = b;
                -		try {
                -			this.populateVals();
                -		} catch (Exception e) {
                -        	Logger.logErr("PtgMemFunc init: " + e.toString());
                -		}
                -	}
                -	
                -    ArrayList refsheets= new ArrayList();
                -	void populateVals() throws Exception{
                -		cce = ByteTools.readShort(record[1], record[2]);
                -		subexp = new byte[cce];
                +
                +    public void init(byte[] b) {
                +        ptgId = b[0];
                +        record = b;
                +        try {
                +            this.populateVals();
                +        } catch (Exception e) {
                +            Logger.logErr("PtgMemFunc init: " + e.toString());
                +        }
                +    }
                +
                +    ArrayList refsheets = new ArrayList();
                +
                +    void populateVals() throws Exception {
                +        cce = ByteTools.readShort(record[1], record[2]);
                +        subexp = new byte[cce];
                         System.arraycopy(record, 3, subexp, 0, cce);
                         // subexpression stack in form of:  REFERENCE, REFERENCE, OP [,REFERENCE, OP] ...
                         // op can be one of:  PtgUnion [,] PtgIsect [ ] or PtgRange [:]
                @@ -88,156 +96,159 @@ void populateVals() throws Exception{
                         // try parsing/calculating on-demand rather than upon init
                         //parseSubexpression(); 
                     }
                -	
                +
                     /**
                -     * update the record internally for ptgmemfunc        
                -*/
                -public byte[] getRecord(){
                -    int len = 0;
                -    for (int i=0;i components= new ArrayList();
                -        if (o!=null && o instanceof Ptg[]) {
                -        	// Firstly: take subexpression and remove reference-tracked elements; calcualted elements are ref-tracked below
                -        	for (int i= 0; i < subexpression.size(); i++) {
                -        		try {
                -        			((PtgRef) subexpression.get(i)).removeFromRefTracker();
                -        		} catch (Exception e) {
                -        		}
                -        	}    	
                -        	ptgs= (Ptg[]) o;
                -        	for (int i= 0; i < ptgs.length; i++) {
                -        		try {
                -	    			if (!refsheets.contains(((PtgRef)ptgs[i]).getSheetName()))
                -	    				refsheets.add(((PtgRef)ptgs[i]).getSheetName());
                -        			((PtgRef)ptgs[i]).addToRefTracker();
                -					if (ptgs[i] instanceof PtgArea & !(ptgs[i] instanceof PtgAreaErr3d)) {
                -	        			Ptg[] p= ptgs[i].getComponents();
                -	        			for (int j= 0; j < p.length; j++) 
                -	        				components.add(p[j]);
                -	        		} else
                -	        			components.add(ptgs[i]);
                -        		} catch (Exception e) {
                -                	Logger.logErr("PtgMemFunc init: " + e.toString());
                -        		}
                -        	}
                -        } else {	// often a single reference surrounded by parens
                -        	for (int i= 0; i < subexpression.size(); i++) {
                -        		try {
                -        			PtgRef pr= (PtgRef) subexpression.get(i);
                -	    			if (!refsheets.contains(pr.getSheetName()))
                -	    				refsheets.add(pr.getSheetName());
                -	        		if (pr instanceof PtgArea) {
                -	        			Ptg[] pa= pr.getComponents();
                -	        			for (int j= 0; j < pa.length; j++)
                -	        				components.add(pa[j]);
                -	        		} else
                -	        			components.add(pr);
                -        		} catch (Exception e) {
                -        		}
                -        	}
                -    	}
                -    	ptgs= new Ptg[components.size()];
                -    	components.toArray(ptgs); 
                -	}    
                -	
                -    /** Returns all of the cells of this range as PtgRef's.
                -        This includes empty cells, values, formulas, etc.
                -        Note the setting of parent-rec requires finding the cell
                -        the PtgRef refer's to.  If that is null then the PtgRef
                -        will exist, just with a null value.  This could cause issues when
                -        programatically populating cells.
                +        Object o = FormulaCalculator.calculateFormula(this.subexpression);
                +        ArrayList components = new ArrayList();
                +        if (o != null && o instanceof Ptg[]) {
                +            // Firstly: take subexpression and remove reference-tracked elements; calcualted elements are ref-tracked below
                +            for (int i = 0; i < subexpression.size(); i++) {
                +                try {
                +                    ((PtgRef) subexpression.get(i)).removeFromRefTracker();
                +                } catch (Exception e) {
                +                }
                +            }
                +            ptgs = (Ptg[]) o;
                +            for (int i = 0; i < ptgs.length; i++) {
                +                try {
                +                    if (!refsheets.contains(((PtgRef) ptgs[i]).getSheetName()))
                +                        refsheets.add(((PtgRef) ptgs[i]).getSheetName());
                +                    ((PtgRef) ptgs[i]).addToRefTracker();
                +                    if (ptgs[i] instanceof PtgArea & !(ptgs[i] instanceof PtgAreaErr3d)) {
                +                        Ptg[] p = ptgs[i].getComponents();
                +                        for (int j = 0; j < p.length; j++)
                +                            components.add(p[j]);
                +                    } else
                +                        components.add(ptgs[i]);
                +                } catch (Exception e) {
                +                    Logger.logErr("PtgMemFunc init: " + e.toString());
                +                }
                +            }
                +        } else {    // often a single reference surrounded by parens
                +            for (int i = 0; i < subexpression.size(); i++) {
                +                try {
                +                    PtgRef pr = (PtgRef) subexpression.get(i);
                +                    if (!refsheets.contains(pr.getSheetName()))
                +                        refsheets.add(pr.getSheetName());
                +                    if (pr instanceof PtgArea) {
                +                        Ptg[] pa = pr.getComponents();
                +                        for (int j = 0; j < pa.length; j++)
                +                            components.add(pa[j]);
                +                    } else
                +                        components.add(pr);
                +                } catch (Exception e) {
                +                }
                +            }
                +        }
                +        ptgs = new Ptg[components.size()];
                +        components.toArray(ptgs);
                +    }
                +
                +    /**
                +     * Returns all of the cells of this range as PtgRef's.
                +     * This includes empty cells, values, formulas, etc.
                +     * Note the setting of parent-rec requires finding the cell
                +     * the PtgRef refer's to.  If that is null then the PtgRef
                +     * will exist, just with a null value.  This could cause issues when
                +     * programatically populating cells.
                      */
                -    public Ptg[] getComponents(){
                -    	if (ptgs==null)
                -    		parseSubexpression();	// not parsed yet    		
                -    	return ptgs;
                -    }    
                -    
                +    public Ptg[] getComponents() {
                +        if (ptgs == null)
                +            parseSubexpression();    // not parsed yet
                +        return ptgs;
                +    }
                +
                     /**
                      * @return Returns the firstPtg.
                      */
                     public Ptg getFirstloc() {
                -    	if (ptgs==null)
                -    		parseSubexpression();	// not parsed yet    		
                -    	if (ptgs!=null)
                -    		return ptgs[0];
                -    	return null;
                +        if (ptgs == null)
                +            parseSubexpression();    // not parsed yet
                +        if (ptgs != null)
                +            return ptgs[0];
                +        return null;
                     }
                 
                     /**
                @@ -246,21 +257,25 @@ public Ptg getFirstloc() {
                      * should not be stored in the RT.
                      */
                     private boolean useReferenceTracker = true;
                +
                     public void setUseReferenceTracker(boolean b) {
                         useReferenceTracker = b;
                     }
                +
                     public boolean getUseReferenceTracker() {
                         return useReferenceTracker;
                     }
                +
                     /**
                      * @return Returns the lastPtg.
                      */
                     public Stack getSubExpression() {
                         return subexpression;
                     }
                -    
                +
                     /**
                -     * given a complex range, parse and set this PtgMemFunc's associated ptgs 
                +     * given a complex range, parse and set this PtgMemFunc's associated ptgs
                +     *
                      * @param String complexrange  String representing a complex range
                      */
                     // some possibilities
                @@ -292,330 +307,337 @@ public Stack getSubExpression() {
                 		Q48, Q48, ), ":"										'=-Q48:(Q48)
                      */
                     public void setLocation(String complexrange) {
                -    	byte[] newData=new byte[3];	// 1st 3 bytes= id + cce (length of following data) 
                -		String sheetName= "";
                -		WorkBook bk= null;
                -		ArrayList sheets= new ArrayList();
                -		try {
                -			bk= this.getParentRec().getWorkBook();
                -			for (int i= 0; i < bk.getSheetVect().size(); i++) {
                -				sheets.add(bk.getWorkSheetByNumber(i).getSheetName());
                -			}
                -			sheetName= this.getParentRec().getSheet().getSheetName()+ "!";
                -		} catch (Exception e) {
                -			//?
                -		}
                -		
                -		
                -		if (complexrange.startsWith("(") && complexrange.endsWith(")"))	// memfuncs are assumed to be wrapped in parens, no need to specify
                -			complexrange= complexrange.substring(0, complexrange.length()-1);
                -
                -		// KSC: TESTING: revert settng subsexpression here for now as tests fail ((;			
                -		//this.subexpression= new Stack();
                -		Stack refs= parseFmla(complexrange);	
                -		try {		
                -			// structure:
                -			// ref, ref, op, [op?] [, ref, op ...]
                -			String ref;
                -			while (refs.size()!=0) {
                -				while (refs.size() > 0) {
                -					if (refs.get(0) instanceof Character) { // it's an operator
                -						Character cOp= (Character)refs.get(0);
                -						if (cOp.charValue()==',') {
                -							PtgUnion pu= new PtgUnion();
                -							cce+=pu.getRecord().length;
                -							newData= ByteTools.append(pu.getRecord(), newData);
                -							//this.subexpression.add(pu);
                -						} else if (cOp.charValue()==' ') {
                -							PtgIsect pi= new PtgIsect();
                -							cce+=pi.getRecord().length;
                -							newData= ByteTools.append(pi.getRecord(), newData);
                -							//this.subexpression.add(pi);
                -						} else if (cOp.charValue()==':'){
                -							PtgRange pr= new PtgRange();
                -							cce+=pr.getRecord().length;
                -							newData= ByteTools.append(pr.getRecord(), newData);
                -							//this.subexpression.add(pr);
                -						} else if (cOp.charValue()==')'){
                -							PtgParen pp= new PtgParen();
                -							cce+=pp.getRecord().length;
                -							newData= ByteTools.append(pp.getRecord(), newData);
                -							//this.subexpression.add(pp);
                -						}
                -					} else {
                -						Object o= refs.get(0);
                -						if (o instanceof Ptg) {	// in the rare case of PtgMemFuncs which contain embedded formulas, Ptgs are already created (see parseFmla) 
                -							cce+=((Ptg)o).getRecord().length;
                -							newData= ByteTools.append(((Ptg)o).getRecord(), newData);							
                -							//this.subexpression.add(o);
                -						} else {
                -							ref= (String) o;
                -							boolean isName= (this.getParentRec().getWorkBook().getName(ref)!=null);
                -							Ptg p= null;
                -							if (isName) {
                -								p= new PtgName();				
                -								p.setParentRec(this.parent_rec);
                -								((PtgName)p).setName(ref);					
                -								cce+=p.getRecord().length;
                -								newData= ByteTools.append(p.getRecord(), newData);
                -							} else if (ref.indexOf(":")>0){ // TODO: handle in quote!!! 
                -								if (ref.indexOf("!")==-1) ref= sheetName + ref;
                -								p= new PtgArea3d(); 
                -								p.setParentRec(this.parent_rec);
                -								p.setLocation(ref);
                -								cce+=p.getRecord().length;
                -								newData= ByteTools.append(p.getRecord(), newData);
                -							} else {
                -								if (ref.indexOf("!")==-1) ref= sheetName + ref;
                -								p= new PtgRef3d();
                -								p.setParentRec(this.parent_rec);
                -								p.setLocation(ref);
                -			        			((PtgRef3d)p).setPtgType(PtgRef.REFERENCE);//important for charting/ptgmemfuncs in series/categories - will error on open otherwise
                -								cce+=p.getRecord().length;
                -								newData= ByteTools.append(p.getRecord(), newData);
                -							}
                -							//this.subexpression.add(p);
                -						}
                -					}
                -					refs.remove(0);
                -				}
                -			}
                -		} catch (Exception e) {	
                -			throw new IllegalArgumentException("PtgMemFunc Error Parsing Location " + complexrange + ":" + e.toString());
                -		}
                -		byte[] ix = ByteTools.shortToLEBytes((short) cce);
                -		System.arraycopy(ix, 0, newData, 1, 2);
                -		newData[0]= 41;	// ptgId 
                -		record= newData;
                -		try {
                -			// KSC: don't re-parse as already have all the ptgs ... also, rw/col bytes for Excel2007 exceed maximums so conversion can't be 100%
                -			// KSC: TESTING: revert for now tests fail ((;			
                -			populateVals();
                +        byte[] newData = new byte[3];    // 1st 3 bytes= id + cce (length of following data)
                +        String sheetName = "";
                +        WorkBook bk = null;
                +        ArrayList sheets = new ArrayList();
                +        try {
                +            bk = this.getParentRec().getWorkBook();
                +            for (int i = 0; i < bk.getSheetVect().size(); i++) {
                +                sheets.add(bk.getWorkSheetByNumber(i).getSheetName());
                +            }
                +            sheetName = this.getParentRec().getSheet().getSheetName() + "!";
                +        } catch (Exception e) {
                +            //?
                +        }
                +
                +
                +        if (complexrange.startsWith("(") && complexrange.endsWith(")"))    // memfuncs are assumed to be wrapped in parens, no need to specify
                +            complexrange = complexrange.substring(0, complexrange.length() - 1);
                +
                +        // KSC: TESTING: revert settng subsexpression here for now as tests fail ((;
                +        //this.subexpression= new Stack();
                +        Stack refs = parseFmla(complexrange);
                +        try {
                +            // structure:
                +            // ref, ref, op, [op?] [, ref, op ...]
                +            String ref;
                +            while (refs.size() != 0) {
                +                while (refs.size() > 0) {
                +                    if (refs.get(0) instanceof Character) { // it's an operator
                +                        Character cOp = (Character) refs.get(0);
                +                        if (cOp.charValue() == ',') {
                +                            PtgUnion pu = new PtgUnion();
                +                            cce += pu.getRecord().length;
                +                            newData = ByteTools.append(pu.getRecord(), newData);
                +                            //this.subexpression.add(pu);
                +                        } else if (cOp.charValue() == ' ') {
                +                            PtgIsect pi = new PtgIsect();
                +                            cce += pi.getRecord().length;
                +                            newData = ByteTools.append(pi.getRecord(), newData);
                +                            //this.subexpression.add(pi);
                +                        } else if (cOp.charValue() == ':') {
                +                            PtgRange pr = new PtgRange();
                +                            cce += pr.getRecord().length;
                +                            newData = ByteTools.append(pr.getRecord(), newData);
                +                            //this.subexpression.add(pr);
                +                        } else if (cOp.charValue() == ')') {
                +                            PtgParen pp = new PtgParen();
                +                            cce += pp.getRecord().length;
                +                            newData = ByteTools.append(pp.getRecord(), newData);
                +                            //this.subexpression.add(pp);
                +                        }
                +                    } else {
                +                        Object o = refs.get(0);
                +                        if (o instanceof Ptg) {    // in the rare case of PtgMemFuncs which contain embedded formulas, Ptgs are already created (see parseFmla)
                +                            cce += ((Ptg) o).getRecord().length;
                +                            newData = ByteTools.append(((Ptg) o).getRecord(), newData);
                +                            //this.subexpression.add(o);
                +                        } else {
                +                            ref = (String) o;
                +                            boolean isName = (this.getParentRec().getWorkBook().getName(ref) != null);
                +                            Ptg p = null;
                +                            if (isName) {
                +                                p = new PtgName();
                +                                p.setParentRec(this.parent_rec);
                +                                ((PtgName) p).setName(ref);
                +                                cce += p.getRecord().length;
                +                                newData = ByteTools.append(p.getRecord(), newData);
                +                            } else if (ref.indexOf(":") > 0) { // TODO: handle in quote!!!
                +                                if (ref.indexOf("!") == -1) ref = sheetName + ref;
                +                                p = new PtgArea3d();
                +                                p.setParentRec(this.parent_rec);
                +                                p.setLocation(ref);
                +                                cce += p.getRecord().length;
                +                                newData = ByteTools.append(p.getRecord(), newData);
                +                            } else {
                +                                if (ref.indexOf("!") == -1) ref = sheetName + ref;
                +                                p = new PtgRef3d();
                +                                p.setParentRec(this.parent_rec);
                +                                p.setLocation(ref);
                +                                ((PtgRef3d) p).setPtgType(PtgRef.REFERENCE);//important for charting/ptgmemfuncs in series/categories - will error on open otherwise
                +                                cce += p.getRecord().length;
                +                                newData = ByteTools.append(p.getRecord(), newData);
                +                            }
                +                            //this.subexpression.add(p);
                +                        }
                +                    }
                +                    refs.remove(0);
                +                }
                +            }
                +        } catch (Exception e) {
                +            throw new IllegalArgumentException("PtgMemFunc Error Parsing Location " + complexrange + ":" + e.toString());
                +        }
                +        byte[] ix = ByteTools.shortToLEBytes((short) cce);
                +        System.arraycopy(ix, 0, newData, 1, 2);
                +        newData[0] = 41;    // ptgId
                +        record = newData;
                +        try {
                +            // KSC: don't re-parse as already have all the ptgs ... also, rw/col bytes for Excel2007 exceed maximums so conversion can't be 100%
                +            // KSC: TESTING: revert for now tests fail ((;
                +            populateVals();
                 /*			cce = ByteTools.readShort(record[1], record[2]);
                 			subexp = new byte[cce];
                -	        System.arraycopy(record, 3, subexp, 0, cce);/**/			
                -        } catch (Exception e){
                -        	Logger.logErr("PtgMemFunc setLocation failed for: " + complexrange + " " + e.toString());
                +	        System.arraycopy(record, 3, subexp, 0, cce);/**/
                +        } catch (Exception e) {
                +            Logger.logErr("PtgMemFunc setLocation failed for: " + complexrange + " " + e.toString());
                         }
                 
                     }
                -    
                +
                     /**
                      * takes a range string which may contain operators:
                -     * union [,] isect [ ] range [:} or paren 
                +     * union [,] isect [ ] range [:} or paren
                      * plus range elements and/or named range
                      * parse and order each element correctly
                      * may be called recurrsively
                      * NOTE: may also be VERY complex, of type OFFSET(x,y,0):OFFSET(z,w,0)
                      * ALSO INDEX and INDIRECT ...
                +     *
                      * @param complexrange
                      * @return ordered stack containing parsed range elements
                      */
                     private Stack parseFmla(String complexrange) {
                -		Stack ops= new Stack();
                -		int lastOp= 0;
                -		boolean finishRange= false;
                -		Stack refs= new Stack();
                -		String ref= "";
                -		boolean inquote= false;
                -		String range= null;	// holds partial range 
                -		for (int i= 0; i < complexrange.length(); i++) {
                -			char c= complexrange.charAt(i);
                -			if (c=='\'') {
                -				inquote= !inquote;
                -				ref+= c;
                -			} else if (!inquote) {
                -				if (c==',' || c==' ' || c==')' || (c==':' && finishRange)) {	// it's an operand
                -					if (c==' ' && lastOp==' ')
                -						continue;	// skip 2nd space op (Isect)
                -					if (finishRange) { // add ref to rest of range
                -						refs.push(range + ref);
                -						if (!refs.isEmpty() && !ops.isEmpty()) 
                -							refs= handleOpearatorPreference(refs, ops);
                -						while (!ops.isEmpty()) 						
                -							refs.push(ops.pop());
                -						range= null;
                -						ref= "";
                -						finishRange= false;
                -						ops.push(new Character((char)c));
                -					} else if (refs.isEmpty()) {	// no operands yet - put in 1st					
                -							if (!ref.equals(""))
                -								refs.push(ref);
                -							ref= "";
                -							ops.push(new Character((char)c));
                -					} else {	// have all we need to process
                -						if (!ref.equals(""))
                -							refs.push(ref);
                -						while (!ops.isEmpty()) 						
                -							refs.push(ops.pop());
                -						ref= "";	// handle case of two spaces ... unfortunately
                -						ops.push(new Character((char)c));
                -					}
                -					lastOp= c;
                -				} else if (c==':') {
                -					if (this.getParentRec().getWorkBook().getName(ref)==null) { // it's a regular range
                -						// check if the ref is a sheet name in a 3d ref
                -						if (!ref.equals("")) { 
                -							range= ref + c;
                -							finishRange= true;
                -						} else { // happens in cases such as (opopop):ref:ref
                -							ops.push(new Character((char)c));
                -						}
                -						ref= "";
                -					} else {	// it's a named range
                -						refs.push(ref);
                -						ref= "";
                -						ops.push(new Character((char)c));
                -						finishRange= false;	// it's not a regular range
                -					}
                -				} else if (c=='(') {  
                -					int endparen = FormulaParser.getMatchOperator(complexrange, i, '(', ')');
                -					if (endparen==-1) 
                -						endparen= complexrange.length()-1;
                -					else if (!ref.equals("")) {
                -						// rare case of a PtgMemFunc containing a formula:
                -						String f= ref+"("+complexrange.substring(i+1, endparen+1);
                -						ref= "";
                -						refs= mergeStacks(refs, FormulaParser.getPtgsFromFormulaString(this.getParentRec(), f, true));
                -						i= endparen;
                -						if (!ops.isEmpty())
                -							refs= handleOpearatorPreference(refs, ops);
                -						while (!ops.isEmpty()) 						
                -							refs.push(ops.pop());
                -						continue;
                -					} 
                -					refs= mergeStacks(refs, parseFmla(complexrange.substring(i+1, endparen+1)));
                -					i= endparen;
                -					if (!ops.isEmpty())
                -						refs= handleOpearatorPreference(refs, ops);
                -					while (!ops.isEmpty()) 						
                -						refs.push(ops.pop());
                -				} else 
                -					ref+= c;
                -			} else 
                -				ref+= c;
                -		}
                -		// get any remaining
                -		if (finishRange) { // add ref to rest of range
                -			// range op has more precedence than others ...
                -			if (!ops.isEmpty() && ((Character) ops.peek()).charValue()==':' && !refs.isEmpty() &&
                -					refs.peek() instanceof Character) {
                -				while (refs.peek() instanceof Character) {	
                -					if (((Character)refs.peek()).charValue()!=':')
                -						ops.add(0, refs.pop());
                -					else
                -						break;							
                -				}
                -			}
                -			if (!ref.equals(""))
                -				refs.push(range + ref);
                -			else {
                -				refs.push(range.substring(0, range.length()-1));
                -				ops.push(':');
                -			}
                -		} else {
                -			if (!ref.equals(""))
                -				refs.push(ref);
                -		}
                -		while (!ops.isEmpty()) 							
                -			refs.push(ops.pop());							
                -		return refs;
                +        Stack ops = new Stack();
                +        int lastOp = 0;
                +        boolean finishRange = false;
                +        Stack refs = new Stack();
                +        String ref = "";
                +        boolean inquote = false;
                +        String range = null;    // holds partial range
                +        for (int i = 0; i < complexrange.length(); i++) {
                +            char c = complexrange.charAt(i);
                +            if (c == '\'') {
                +                inquote = !inquote;
                +                ref += c;
                +            } else if (!inquote) {
                +                if (c == ',' || c == ' ' || c == ')' || (c == ':' && finishRange)) {    // it's an operand
                +                    if (c == ' ' && lastOp == ' ')
                +                        continue;    // skip 2nd space op (Isect)
                +                    if (finishRange) { // add ref to rest of range
                +                        refs.push(range + ref);
                +                        if (!refs.isEmpty() && !ops.isEmpty())
                +                            refs = handleOpearatorPreference(refs, ops);
                +                        while (!ops.isEmpty())
                +                            refs.push(ops.pop());
                +                        range = null;
                +                        ref = "";
                +                        finishRange = false;
                +                        ops.push(new Character(c));
                +                    } else if (refs.isEmpty()) {    // no operands yet - put in 1st
                +                        if (!ref.equals(""))
                +                            refs.push(ref);
                +                        ref = "";
                +                        ops.push(new Character(c));
                +                    } else {    // have all we need to process
                +                        if (!ref.equals(""))
                +                            refs.push(ref);
                +                        while (!ops.isEmpty())
                +                            refs.push(ops.pop());
                +                        ref = "";    // handle case of two spaces ... unfortunately
                +                        ops.push(new Character(c));
                +                    }
                +                    lastOp = c;
                +                } else if (c == ':') {
                +                    if (this.getParentRec().getWorkBook().getName(ref) == null) { // it's a regular range
                +                        // check if the ref is a sheet name in a 3d ref
                +                        if (!ref.equals("")) {
                +                            range = ref + c;
                +                            finishRange = true;
                +                        } else { // happens in cases such as (opopop):ref:ref
                +                            ops.push(new Character(c));
                +                        }
                +                        ref = "";
                +                    } else {    // it's a named range
                +                        refs.push(ref);
                +                        ref = "";
                +                        ops.push(new Character(c));
                +                        finishRange = false;    // it's not a regular range
                +                    }
                +                } else if (c == '(') {
                +                    int endparen = FormulaParser.getMatchOperator(complexrange, i, '(', ')');
                +                    if (endparen == -1)
                +                        endparen = complexrange.length() - 1;
                +                    else if (!ref.equals("")) {
                +                        // rare case of a PtgMemFunc containing a formula:
                +                        String f = ref + "(" + complexrange.substring(i + 1, endparen + 1);
                +                        ref = "";
                +                        refs = mergeStacks(refs, FormulaParser.getPtgsFromFormulaString(this.getParentRec(), f, true));
                +                        i = endparen;
                +                        if (!ops.isEmpty())
                +                            refs = handleOpearatorPreference(refs, ops);
                +                        while (!ops.isEmpty())
                +                            refs.push(ops.pop());
                +                        continue;
                +                    }
                +                    refs = mergeStacks(refs, parseFmla(complexrange.substring(i + 1, endparen + 1)));
                +                    i = endparen;
                +                    if (!ops.isEmpty())
                +                        refs = handleOpearatorPreference(refs, ops);
                +                    while (!ops.isEmpty())
                +                        refs.push(ops.pop());
                +                } else
                +                    ref += c;
                +            } else
                +                ref += c;
                +        }
                +        // get any remaining
                +        if (finishRange) { // add ref to rest of range
                +            // range op has more precedence than others ...
                +            if (!ops.isEmpty() && ((Character) ops.peek()).charValue() == ':' && !refs.isEmpty() &&
                +                    refs.peek() instanceof Character) {
                +                while (refs.peek() instanceof Character) {
                +                    if (((Character) refs.peek()).charValue() != ':')
                +                        ops.add(0, refs.pop());
                +                    else
                +                        break;
                +                }
                +            }
                +            if (!ref.equals(""))
                +                refs.push(range + ref);
                +            else {
                +                refs.push(range.substring(0, range.length() - 1));
                +                ops.push(':');
                +            }
                +        } else {
                +            if (!ref.equals(""))
                +                refs.push(ref);
                +        }
                +        while (!ops.isEmpty())
                +            refs.push(ops.pop());
                +        return refs;
                     }
                -   
                -    
                -	/**
                -  	 *  handle precedence of complex range operators:  : before , before ' '
                -	 * @param sourceStack
                -	 * @param destStack
                -	 */
                -	private static Stack handleOpearatorPreference(Stack refs, Stack ops) {
                -		char lastOp= ((Character) ops.pop()).charValue();
                -		if (refs.peek() instanceof Character) {	
                -			char curOp= ((Character) refs.pop()).charValue();
                -	  		int group1=rankPrecedence(lastOp); 
                -	  		int group2=rankPrecedence(curOp);
                -			if (group2 >= group1) { 
                -				ops.push(new Character(lastOp));
                -				refs.push(new Character(curOp));
                -			}else {
                -				ops.push(new Character(curOp));
                -				refs.push(new Character(lastOp));
                -			}
                -			
                -		} else
                -			ops.push(new Character(lastOp));
                -		return refs;
                -	}
                -	
                -	/** rank a Ptg Operator's precedence (lower
                -	 * @param curOp
                -	 * @return
                -	 */
                -	static int rankPrecedence(char curOp) {
                -		if (curOp==0) return -1;
                -		if (curOp==')')
                -			return 6;
                -		if (curOp==':')
                -			return 5;
                -		if (curOp==',' || curOp==' ')	// same level????
                -			return 4;
                -  		return 0;	// ' '
                -	}
                -	
                -    
                -    /** 
                -     * when parenthesed sub-functions 
                +
                +
                +    /**
                +     * handle precedence of complex range operators:  : before , before ' '
                +     *
                +     * @param sourceStack
                +     * @param destStack
                +     */
                +    private static Stack handleOpearatorPreference(Stack refs, Stack ops) {
                +        char lastOp = ((Character) ops.pop()).charValue();
                +        if (refs.peek() instanceof Character) {
                +            char curOp = ((Character) refs.pop()).charValue();
                +            int group1 = rankPrecedence(lastOp);
                +            int group2 = rankPrecedence(curOp);
                +            if (group2 >= group1) {
                +                ops.push(new Character(lastOp));
                +                refs.push(new Character(curOp));
                +            } else {
                +                ops.push(new Character(curOp));
                +                refs.push(new Character(lastOp));
                +            }
                +
                +        } else
                +            ops.push(new Character(lastOp));
                +        return refs;
                +    }
                +
                +    /**
                +     * rank a Ptg Operator's precedence (lower
                +     *
                +     * @param curOp
                +     * @return
                +     */
                +    static int rankPrecedence(char curOp) {
                +        if (curOp == 0) return -1;
                +        if (curOp == ')')
                +            return 6;
                +        if (curOp == ':')
                +            return 5;
                +        if (curOp == ',' || curOp == ' ')    // same level????
                +            return 4;
                +        return 0;    // ' '
                +    }
                +
                +
                +    /**
                +     * when parenthesed sub-functions
                +     *
                      * @param first
                      * @param last
                      * @return
                      */
                     private Stack mergeStacks(Stack first, Stack last) {
                -    	first.addAll(last);
                -    	return first;    	
                +        first.addAll(last);
                +        return first;
                     }
                -        
                +
                     /**
                -     * traverse through expression to retrieve set of ranges 
                +     * traverse through expression to retrieve set of ranges
                      * either discontiguous union (,), intersected ( ) or regular range (:)
                      */
                -    public String toString(){
                -		return FormulaParser.getExpressionString(subexpression).substring(1);	// avoid "="
                +    public String toString() {
                +        return FormulaParser.getExpressionString(subexpression).substring(1);    // avoid "="
                     }
                -    
                +
                     /**
                      * return the boundsheet associated with this complex range
                -     * 
                NOTE: since complex ranges may contain more than one sheet, this is incomplete for those instanaces + *
                NOTE: since complex ranges may contain more than one sheet, this is incomplete for those instanaces + * * @param b * @return */ - public Boundsheet[] getSheets(WorkBook b) { - if (ptgs==null) - parseSubexpression(); - if (this.refsheets!=null || this.refsheets.size()!=0) { - try { - Boundsheet[] sheets= new Boundsheet[this.refsheets.size()]; - for (int i= 0; i < sheets.length; i++) - sheets[i]= b.getWorkSheetByName((String)refsheets.get(i)); - return sheets; - } catch (Exception e) { - ; // TODO: report error? - } - } - return null; - - } - public void close() { - if (ptgs!=null) { - for (int i= 0; i < ptgs.length; i++) { - if (ptgs[i] instanceof PtgRef) - ((PtgRef)ptgs[i]).close(); - else - ((GenericPtg)ptgs[i]).close(); - } - } - ptgs= null; - super.close(); - } + public Boundsheet[] getSheets(WorkBook b) { + if (ptgs == null) + parseSubexpression(); + if (this.refsheets != null || this.refsheets.size() != 0) { + try { + Boundsheet[] sheets = new Boundsheet[this.refsheets.size()]; + for (int i = 0; i < sheets.length; i++) + sheets[i] = b.getWorkSheetByName(refsheets.get(i)); + return sheets; + } catch (Exception e) { + // TODO: report error? + } + } + return null; + + } + + public void close() { + if (ptgs != null) { + for (int i = 0; i < ptgs.length; i++) { + if (ptgs[i] instanceof PtgRef) + ptgs[i].close(); + else + ptgs[i].close(); + } + } + ptgs = null; + super.close(); + } /* protected void finalize() { this.close(); }*/ diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.java index bb3162e..c3f88a0 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -33,34 +33,46 @@ */ -public class PtgMissArg extends GenericPtg implements Ptg -{ - +public class PtgMissArg extends GenericPtg implements Ptg { + private static final long serialVersionUID = 8995314621921283625L; - public boolean getIsOperand(){return true;} - public boolean getIsOperator(){return false;} - public boolean getIsBinaryOperator(){return false;} - public boolean getIsPrimitiveOperator(){return false;} - + public boolean getIsOperand() { + return true; + } + + public boolean getIsOperator() { + return false; + } + + public boolean getIsBinaryOperator() { + return false; + } + + public boolean getIsPrimitiveOperator() { + return false; + } + public PtgMissArg() { - this.init(new byte[] {22}); + this.init(new byte[]{22}); } - /** return the human-readable String representation of - */ - public String getString(){ + + /** + * return the human-readable String representation of + */ + public String getString() { return ""; } - - public String toString(){ - return ""; + + public String toString() { + return ""; } - - public int getLength(){ + + public int getLength() { return 1; } - - public Object getValue(){ - return null; + + public Object getValue() { + return null; } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.java index d21491d..60cbbc7 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,10 +22,10 @@ */ package io.starter.formats.XLS.formulas; -import java.lang.reflect.Array; - import io.starter.toolkit.Logger; +import java.lang.reflect.Array; + /* Ptg that is a multiplier operand @@ -35,104 +35,114 @@ */ -public class PtgMlt extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2670754297349356254L; - - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - /** return the human-readable String representation of - */ - public String getString(){ +public class PtgMlt extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2670754297349356254L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return "*"; - } + } + public String toString() { return this.getString(); } - public PtgMlt(){ - ptgId = 0x5; - record = new byte[1]; - record[0] = 0x5; - } - - public int getLength(){ + + public PtgMlt() { + ptgId = 0x5; + record = new byte[1]; + record[0] = 0x5; + } + + public int getLength() { return PTG_MLT_LENGTH; } - - /* Operator specific calculate method, this one multiplies two values. - - */ - public Ptg calculatePtg(Ptg[] form){ - // handle ref errs - if(form[0] instanceof PtgErr || form[0] instanceof PtgRefErr || form[0] instanceof PtgRefErr3d || form[0] instanceof PtgAreaErr3d) - return form[0]; - if(form[1] instanceof PtgErr || form[1] instanceof PtgRefErr || form[1] instanceof PtgRefErr3d || form[1] instanceof PtgAreaErr3d) - return form[1]; - - try { - // 20090202 KSC: Handle array formulas - Object[] o= super.getValuesFromPtgs(form); - if (o==null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o.length != 2){ - Logger.logWarn("calculating formula failed, wrong number of values in PtgMlt"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; - } - double o0= 0, o1= 0; - try { - o0= getDoubleValue(o[0], this.parent_rec); - o1= getDoubleValue(o[1], this.parent_rec); - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - double returnVal = o0 * o1; - // create a container ptg for these. - PtgNumber n = new PtgNumber(returnVal); - return n; - } else { // handle array fomulas - String retArry= ""; - int nArrays= java.lang.reflect.Array.getLength(o); - if (nArrays!=2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals= java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i= 0; i < nArrays-1; i+=2) { - Object secondOp= null; - boolean comparitorIsArray= o[i+1].getClass().isArray(); - if (!comparitorIsArray) secondOp= o[i+1]; - for (int j= 0; j < nVals; j++) { - Object firstOp= Array.get(o[i],j); // first array index j - if (comparitorIsArray) - secondOp= Array.get(o[i+1],j); // second array index j - double o0= 0, o1= 0; - try { - o0= getDoubleValue(firstOp, this.parent_rec); - o1= getDoubleValue(secondOp, this.parent_rec); - } catch (NumberFormatException e) { - retArry= retArry + "#VALUE!" + ","; - continue; - } - double retVal= o0*o1; - retArry= retArry + retVal + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } - }catch(NumberFormatException e) { + + /* Operator specific calculate method, this one multiplies two values. + + */ + public Ptg calculatePtg(Ptg[] form) { + // handle ref errs + if (form[0] instanceof PtgErr || form[0] instanceof PtgRefErr || form[0] instanceof PtgRefErr3d || form[0] instanceof PtgAreaErr3d) + return form[0]; + if (form[1] instanceof PtgErr || form[1] instanceof PtgRefErr || form[1] instanceof PtgRefErr3d || form[1] instanceof PtgAreaErr3d) + return form[1]; + + try { + // 20090202 KSC: Handle array formulas + Object[] o = getValuesFromPtgs(form); + if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) + if (!o[0].getClass().isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o.length != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgMlt"); + return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; + } + double o0 = 0, o1 = 0; + try { + o0 = getDoubleValue(o[0], this.parent_rec); + o1 = getDoubleValue(o[1], this.parent_rec); + } catch (NumberFormatException e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + double returnVal = o0 * o1; + // create a container ptg for these. + PtgNumber n = new PtgNumber(returnVal); + return n; + } else { // handle array fomulas + String retArry = ""; + int nArrays = java.lang.reflect.Array.getLength(o); + if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); + int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays + for (int i = 0; i < nArrays - 1; i += 2) { + Object secondOp = null; + boolean comparitorIsArray = o[i + 1].getClass().isArray(); + if (!comparitorIsArray) secondOp = o[i + 1]; + for (int j = 0; j < nVals; j++) { + Object firstOp = Array.get(o[i], j); // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j); // second array index j + double o0 = 0, o1 = 0; + try { + o0 = getDoubleValue(firstOp, this.parent_rec); + o1 = getDoubleValue(secondOp, this.parent_rec); + } catch (NumberFormatException e) { + retArry = retArry + "#VALUE!" + ","; + continue; + } + double retVal = o0 * o1; + retArry = retArry + retVal + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } + } catch (NumberFormatException e) { PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); return perr; - }catch(Exception e) { // 20081125 KSC: handle error ala Excel + } catch (Exception e) { // 20081125 KSC: handle error ala Excel PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); return perr; } } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMystery.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMystery.java index c63782b..83119f0 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMystery.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMystery.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,7 +23,6 @@ package io.starter.formats.XLS.formulas; - /* Unhandled or Unimplemented PTG, that is stored as a 1 byte ptg. Eases ability to reconstruct these formulas @@ -33,35 +32,35 @@ */ -public class PtgMystery extends GenericPtg implements Ptg -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5170319810844024569L; +public class PtgMystery extends GenericPtg implements Ptg { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5170319810844024569L; - /** return the human-readable String representation of - */ - public String getString(){ + /** + * return the human-readable String representation of + */ + public String getString() { return "!UNKNOWN!"; - } - - - public int getLength(){ + } + + + public int getLength() { return getRecord().length; } - + // KSC: Using PtgMystery as a Generic, lightweight Ptg - + /** - * Holder for PtgMemFunc/Area components - * functioning as a Generic, lightweight Ptg - * see PtgRange, PtgUnion, PtgIsect + * Holder for PtgMemFunc/Area components + * functioning as a Generic, lightweight Ptg + * see PtgRange, PtgUnion, PtgIsect */ - public Object getValue(){ + public Object getValue() { return vars; } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgNE.java b/src/main/java/io/starter/formats/XLS/formulas/PtgNE.java index 9df1b5b..1705fac 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgNE.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgNE.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,10 +22,9 @@ */ package io.starter.formats.XLS.formulas; -import java.lang.reflect.Array; - import io.starter.toolkit.Logger; +import java.lang.reflect.Array; /* @@ -40,109 +39,116 @@ */ -public class PtgNE extends GenericPtg implements Ptg -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6901661166166179786L; - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - public PtgNE(){ - ptgId = 0xE; - record = new byte[1]; - record[0] = 0xE; - } - - /** return the human-readable String representation of - */ - public String getString(){ +public class PtgNE extends GenericPtg implements Ptg { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 6901661166166179786L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public PtgNE() { + ptgId = 0xE; + record = new byte[1]; + record[0] = 0xE; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return "<>"; - } - - public int getLength(){ + } + + public int getLength() { return PTG_NE_LENGTH; } - /* Operator specific calculate method, this one determines if the second-to-top - operand is less than the top operand; Returns a PtgBool - - */ - public Ptg calculatePtg(Ptg[] form){ - try{ - // 20090202 KSC: Handle array formulas - Object[] o= super.getValuesFromPtgs(form); - boolean res; - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o==null || o.length != 2){ - Logger.logWarn("calculating formula failed, wrong number of values in PtgNE"); - return null; - } - // blank handling: - // determine if any of the operands are double - if true, - // then blank comparisons will be treated as 0's - boolean isDouble= false; - for (int i= 0; i < 2 && !isDouble; i++) { - //if (!form[i].isBlank()) - isDouble= ((o[i] instanceof Double)); - } - for(int i=0;i<2;i++){ - //if (form[i].isBlank()) { - if (o[i]!=null && o[i].toString().equals("")) { - if (isDouble) - o[i]= new Double(0.0); - else - o[i]=""; // in this case, empty cells are handled as blank, not zero - } - } - if (o[0] instanceof Double && o[1] instanceof Double) - res= (Math.abs((((Double)o[0]).doubleValue())-((Double)o[1]).doubleValue()))> doublePrecision; // compare equality to certain precision - else if (!o[0].toString().equalsIgnoreCase(o[1].toString())) - res= true; - else - res= false; - PtgBool pboo = new PtgBool(res); - return pboo; - } else { // handle array fomulas - String retArry= ""; - int nArrays= java.lang.reflect.Array.getLength(o); - if (nArrays!=2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals= java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i= 0; i < nArrays-1; i+=2) { - res= false; - Object secondOp= null; - boolean comparitorIsArray= o[i+1].getClass().isArray(); - if (!comparitorIsArray) secondOp= o[i+1]; - for (int j= 0; j < nVals; j++) { - Object firstOp= Array.get(o[i],j); // first array index j - if (comparitorIsArray) - secondOp= Array.get(o[i+1],j); // second array index j - - if (firstOp instanceof Double && secondOp instanceof Double) - res= (Math.abs((((Double)firstOp).doubleValue())-((Double)secondOp).doubleValue()))>doublePrecision; // compare to certain precision instead of equality - else - res= firstOp.toString().equalsIgnoreCase(secondOp.toString()); - retArry= retArry + res + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } + + /* Operator specific calculate method, this one determines if the second-to-top + operand is less than the top operand; Returns a PtgBool + + */ + public Ptg calculatePtg(Ptg[] form) { + try { + // 20090202 KSC: Handle array formulas + Object[] o = getValuesFromPtgs(form); + boolean res; + if (!o[0].getClass().isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o == null || o.length != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgNE"); + return null; + } + // blank handling: + // determine if any of the operands are double - if true, + // then blank comparisons will be treated as 0's + boolean isDouble = false; + for (int i = 0; i < 2 && !isDouble; i++) { + //if (!form[i].isBlank()) + isDouble = ((o[i] instanceof Double)); + } + for (int i = 0; i < 2; i++) { + //if (form[i].isBlank()) { + if (o[i] != null && o[i].toString().equals("")) { + if (isDouble) + o[i] = new Double(0.0); + else + o[i] = ""; // in this case, empty cells are handled as blank, not zero + } + } + if (o[0] instanceof Double && o[1] instanceof Double) + res = (Math.abs((((Double) o[0]).doubleValue()) - ((Double) o[1]).doubleValue())) > doublePrecision; // compare equality to certain precision + else res = !o[0].toString().equalsIgnoreCase(o[1].toString()); + PtgBool pboo = new PtgBool(res); + return pboo; + } else { // handle array fomulas + String retArry = ""; + int nArrays = java.lang.reflect.Array.getLength(o); + if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); + int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays + for (int i = 0; i < nArrays - 1; i += 2) { + res = false; + Object secondOp = null; + boolean comparitorIsArray = o[i + 1].getClass().isArray(); + if (!comparitorIsArray) secondOp = o[i + 1]; + for (int j = 0; j < nVals; j++) { + Object firstOp = Array.get(o[i], j); // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j); // second array index j + + if (firstOp instanceof Double && secondOp instanceof Double) + res = (Math.abs((((Double) firstOp).doubleValue()) - ((Double) secondOp).doubleValue())) > doublePrecision; // compare to certain precision instead of equality + else + res = firstOp.toString().equalsIgnoreCase(secondOp.toString()); + retArry = retArry + res + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } /*}catch(NumberFormatException e){ // shouldn't get here!! see new code above String[] s = getStringValuesFromPtgs(form); if (s[0].equalsIgnoreCase(s[1]))return new PtgBool(false); return new PtgBool(true); }*/ - }catch(Exception e) { // 20090212 KSC: handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } + } catch (Exception e) { // 20090212 KSC: handle error ala Excel + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgName.java b/src/main/java/io/starter/formats/XLS/formulas/PtgName.java index 481b292..a497a5b 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgName.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgName.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,230 +25,231 @@ import io.starter.formats.XLS.Formula; import io.starter.formats.XLS.FormulaNotFoundException; import io.starter.formats.XLS.Name; +import io.starter.formats.XLS.WorkBook; import io.starter.toolkit.ByteTools; +import io.starter.toolkit.FastAddVector; import io.starter.toolkit.Logger; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.*; /** - This PTG stores an index to a name. The ilbl field is a 1 based index to the table - of NAME records in the workbook - - OFFSET NAME sIZE CONTENTS - --------------------------------------------- - 0 ilbl 2 Index to the NAME table - 2 (reserved) 2 ` Must be 0; - + * This PTG stores an index to a name. The ilbl field is a 1 based index to the table + * of NAME records in the workbook + *

                + * OFFSET NAME sIZE CONTENTS + * --------------------------------------------- + * 0 ilbl 2 Index to the NAME table + * 2 (reserved) 2 ` Must be 0; + * * @see Ptg * @see Formula - -*/ -public class PtgName extends GenericPtg implements Ptg, IlblListener{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8047146848365098162L; - short ilbl; + */ +public class PtgName extends GenericPtg implements Ptg, IlblListener { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8047146848365098162L; + short ilbl; String name; - - public boolean getIsOperand(){ - - return true; + + public boolean getIsOperand() { + + return true; } + // lookup Name object in Workbook and return handle - public Name getName(){ + public Name getName() { WorkBook b = this.getParentRec().getWorkBook(); Name n = null; - try{ - n = b.getName(ilbl); - }catch(Exception ex){ - } - return n; + try { + n = b.getName(ilbl); + } catch (Exception ex) { + } + return n; } - - public void init(byte[] b){ - ptgId = b[0]; - record = b; + + public void init(byte[] b) { + ptgId = b[0]; + record = b; this.populateVals(); addToRefTracker(); } - - //default constructor - public PtgName(){ - ptgId = 0x23; // reference type is default - } - - /** + + //default constructor + public PtgName() { + ptgId = 0x23; // reference type is default + } + + /** * set the Ptg Id type to one of: * VALUE, REFERENCE or Array - *
                The Ptg type is important for certain + *
                The Ptg type is important for certain * functions which require a specific type of operand */ - public void setPtgType(short type) { + public void setPtgType(short type) { switch (type) { - case VALUE: - ptgId= 0x43; - break; - case REFERENCE: - ptgId= 0x23; - break; - case Ptg.ARRAY: - ptgId= 0x63; - break; + case VALUE: + ptgId = 0x43; + break; + case REFERENCE: + ptgId = 0x23; + break; + case Ptg.ARRAY: + ptgId = 0x63; + break; } - record[0]= ptgId; + record[0] = ptgId; } - // 20100218 KSC: - // constructor which sets a specific id - // to specify whether this PtgName is of value, ref or array type - // (PtgNameV, PtgNameR or PtgNameA) - public PtgName(int id){ - ptgId= (byte)id; + // 20100218 KSC: + // constructor which sets a specific id + // to specify whether this PtgName is of value, ref or array type + // (PtgNameV, PtgNameR or PtgNameA) + public PtgName(int id) { + ptgId = (byte) id; // 0x23= Ref // ptgId = 0x43; Value - } - - - /** add this reference to the ReferenceTracker... this - * is crucial if we are to update this Ptg when cells + } + + + /** + * add this reference to the ReferenceTracker... this + * is crucial if we are to update this Ptg when cells * are changed or added... - * - * */ - public void addToRefTracker(){ + public void addToRefTracker() { //Logger.logInfo("Adding :" + this.toString() + " to tracker"); - try{ - if(parent_rec!=null) + try { + if (parent_rec != null) parent_rec.getWorkBook().getRefTracker().addPtgNameReference(this); - }catch(Exception ex){ + } catch (Exception ex) { Logger.logErr("PtgRef.addToRefTracker() failed.", ex); } - } - - /** - * For creating a ptg name from formula parser - */ - public void setName(String name){ - record = new byte[5]; - record[0] = ptgId; - WorkBook b = this.getParentRec().getWorkBook(); - ilbl = (short)b.getNameNumber(name); + } + + /** + * For creating a ptg name from formula parser + */ + public void setName(String name) { + record = new byte[5]; + record[0] = ptgId; + WorkBook b = this.getParentRec().getWorkBook(); + ilbl = (short) b.getNameNumber(name); this.addListener(); - record[1] = (byte)ilbl; + record[1] = (byte) ilbl; } - - - private void populateVals(){ + + + private void populateVals() { ilbl = ByteTools.readShort(record[1], record[2]); } - - public int getVal(){ - return (int)ilbl; + + public int getVal() { + return (int) ilbl; } - - public short getIlbl(){ - return ilbl; + + public short getIlbl() { + return ilbl; } - + public void storeName(String nm) { name = nm; } - - - public void setIlbl(short i){ - if (ilbl!=i){ - ilbl = i; - this.updateRecord(); - } + + + public void setIlbl(short i) { + if (ilbl != i) { + ilbl = i; + this.updateRecord(); + } } + /* * * returns the string value of the name @see io.starter.formats.XLS.formulas.Ptg#getValue() */ - public Object getValue(){ - Name n = getName(); - try{ - Ptg[] p = n.getCellRangePtgs(); - if (p.length == 0){ - return new String("#NAME?"); - }else if (p.length == 1 || !(this.parent_rec instanceof io.starter.formats.XLS.Array)){ // usual case - return p[0].getValue(); - } else { // multiple values; create an array - String retarry= ""; - for (int i= 0; i < p.length; i++) { - retarry= retarry + p[i].getValue() + ","; - } - retarry= "{" + retarry.substring(0, retarry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retarry); - return pa; - } - }catch(Exception e){}; -// String s = n.getName(); - //return n; - return new String("#NAME?"); + public Object getValue() { + Name n = getName(); + try { + Ptg[] p = n.getCellRangePtgs(); + if (p.length == 0) { + return "#NAME?"; + } else if (p.length == 1 || !(this.parent_rec instanceof io.starter.formats.XLS.Array)) { // usual case + return p[0].getValue(); + } else { // multiple values; create an array + String retarry = ""; + for (int i = 0; i < p.length; i++) { + retarry = retarry + p[i].getValue() + ","; + } + retarry = "{" + retarry.substring(0, retarry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retarry); + return pa; + } + } catch (Exception e) { + } + // String s = n.getName(); + //return n; + return "#NAME?"; } - - public String getTextString(){ - Name n = getName(); - if(n==null) - return "#NAME!"; - return n.getName(); + + public String getTextString() { + Name n = getName(); + if (n == null) + return "#NAME!"; + return n.getName(); } - + public String getStoredName() { return name; } - - public void setVal(int i){ - ilbl = (short)i; + + public void setVal(int i) { + ilbl = (short) i; this.updateRecord(); } - - public void updateRecord(){ + + public void updateRecord() { byte[] brow = ByteTools.cLongToLEBytes(ilbl); record[1] = brow[0]; record[2] = brow[1]; - if(parent_rec!=null) { - if(parent_rec instanceof Formula) - ((Formula)parent_rec).updateRecord(); + if (parent_rec != null) { + if (parent_rec instanceof Formula) + ((Formula) parent_rec).updateRecord(); } } - - /** Override due to mystery extra byte - * occasionally found in ptgName recs. - * - */ - public int getLength(){ - if(record != null)return record.length; + + /** + * Override due to mystery extra byte + * occasionally found in ptgName recs. + */ + public int getLength() { + if (record != null) return record.length; return PTG_NAME_LENGTH; } - + public String toString() { - if (this.getName()!=null) - return this.getName().getName(); - return "[Null]"; + if (this.getName() != null) + return this.getName().getName(); + return "[Null]"; } - - public Ptg[] getComponents(){ - FastAddVector v = new FastAddVector(); - Ptg p= this.getName().getPtga(); - Ptg[] pcomps = p.getComponents(); - if (pcomps!= null){ - for (int x=0;x. @@ -23,9 +23,8 @@ package io.starter.formats.XLS.formulas; import io.starter.formats.XLS.Name; -import io.starter.toolkit.ByteTools; import io.starter.formats.XLS.WorkBook; -import io.starter.formats.XLS.Boundsheet; +import io.starter.toolkit.ByteTools; /* @@ -42,134 +41,136 @@ * @see Formula */ -public class PtgNameX extends PtgName implements Ptg, IxtiListener{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1240996941619495505L; - short ixti; - int ilbl; - +public class PtgNameX extends PtgName implements Ptg, IxtiListener { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1240996941619495505L; + short ixti; + int ilbl; + + + public boolean getIsOperand() { + + return true; + } + + //lookup Name object in Workbook and return handle + public Name getName() { + WorkBook b = this.getParentRec().getSheet().getWorkBook(); + // the externsheet reference is negative, there seems to be a problem + // off the docs. Just use a placeholder boundsheet, as the PtgRef3D internally will + // get the value correctly + //Externsheet x = b.getExternSheet(); + Name n = null; + + try { + n = b.getName(ilbl); + n.setSheet(this.getParentRec().getSheet()); + } catch (Exception e) { + // it's an AddInFormula... -jm + } + //Boundsheet[] bound = x.getBoundSheets(ixti); + return n; + } - public boolean getIsOperand(){ - - return true; - } - - //lookup Name object in Workbook and return handle - public Name getName(){ - WorkBook b = this.getParentRec().getSheet().getWorkBook(); - // the externsheet reference is negative, there seems to be a problem - // off the docs. Just use a placeholder boundsheet, as the PtgRef3D internally will - // get the value correctly - //Externsheet x = b.getExternSheet(); - Name n = null; - - try{ - n = b.getName(ilbl); - n.setSheet(this.getParentRec().getSheet()); - }catch(Exception e) { - ; // it's an AddInFormula... -jm - } - //Boundsheet[] bound = x.getBoundSheets(ixti); - return n; - } - public void addListener() { try { getParentRec().getWorkBook().getExternSheet().addPtgListener(this); - }catch(Exception e) { + } catch (Exception e) { // no need to output here. NullPointer occurs when a ref has an invalid ixti, such as when a sheet was removed Worksheet exception could never really happen. } } - - - public void init(byte[] b){ - ptgId = b[0]; - record = b; - this.populateVals(); - } - - private void populateVals(){ - ixti = ByteTools.readShort(record[1], record[2]); - ilbl = ByteTools.readShort(record[3], record[4]); - } - - public int getVal(){ - return ilbl; - } - - /* - * - * returns the string value of the name - @see io.starter.formats.XLS.formulas.Ptg#getValue() - */ - public Object getValue(){ - - WorkBook b = this.getParentRec().getSheet().getWorkBook(); - String externalname= null; - try { - externalname= b.getExternalName(ilbl); - } - catch (Exception e) {} - if (externalname!=null) - return externalname; - - Name n = getName(); - return n.getCalculatedValue(); - } - - public String toString() { - if(this.parent_rec.getSheet()!=null) - return (String)getValue(); - else - return "Uninitialized PtgNameX"; + + public void init(byte[] b) { + ptgId = b[0]; + record = b; + this.populateVals(); + } + + private void populateVals() { + ixti = ByteTools.readShort(record[1], record[2]); + + ilbl = ByteTools.readShort(record[3], record[4]); + } + + public int getVal() { + return ilbl; + } + + /* + * + * returns the string value of the name + @see io.starter.formats.XLS.formulas.Ptg#getValue() + */ + public Object getValue() { + + WorkBook b = this.getParentRec().getSheet().getWorkBook(); + String externalname = null; + try { + externalname = b.getExternalName(ilbl); + } catch (Exception e) { + } + if (externalname != null) + return externalname; + + Name n = getName(); + return n.getCalculatedValue(); + } + + public String toString() { + if (this.parent_rec.getSheet() != null) + return (String) getValue(); + else + return "Uninitialized PtgNameX"; + } + + public String getTextString() { + Object o = getValue(); + if (o == null) + return ""; + return o.toString(); + } + + + public int getLength() { + return PTG_NAMEX_LENGTH; } - - public String getTextString(){ - Object o = getValue(); - if(o==null) - return ""; - return o.toString(); - } - - public int getLength(){ - return PTG_NAMEX_LENGTH; - } - /** * @return Returns the ixti. */ public short getIxti() { return ixti; } + /** * @param ixti The ixti to set. */ public void setIxti(short ixti) { this.ixti = ixti; } - + // KSC: Added to handle External names (denoted by PtgNameX records in ExpressionParser) - /** - * For creating a ptg namex from formula parser - */ - public void setName(String name){ - ptgId = 0x39; // PtgNameX - record = new byte[PTG_NAMEX_LENGTH]; - record[0] = ptgId; - WorkBook b = this.getParentRec().getSheet().getWorkBook(); - ilbl= b.getExtenalNameNumber(name); - ixti = (short)b.getExternSheet().getVirtualReference(); - byte[] bb= ByteTools.shortToLEBytes(ixti); - record[1]= bb[0]; - record[2]= bb[1]; - byte[] bbb= ByteTools.cLongToLEBytes(ilbl); - record[3]= bbb[0]; - record[4]= bbb[1]; + + /** + * For creating a ptg namex from formula parser + */ + public void setName(String name) { + ptgId = 0x39; // PtgNameX + record = new byte[PTG_NAMEX_LENGTH]; + record[0] = ptgId; + WorkBook b = this.getParentRec().getSheet().getWorkBook(); + ilbl = b.getExtenalNameNumber(name); + ixti = (short) b.getExternSheet().getVirtualReference(); + byte[] bb = ByteTools.shortToLEBytes(ixti); + record[1] = bb[0]; + record[2] = bb[1]; + byte[] bbb = ByteTools.cLongToLEBytes(ilbl); + record[3] = bbb[0]; + record[4] = bbb[1]; } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.java b/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.java index f71e17f..e93da6f 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -37,99 +37,104 @@ */ -public class PtgNumber extends GenericPtg implements Ptg -{ +public class PtgNumber extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1650136303920724485L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1650136303920724485L; + + public boolean getIsOperand() { + return true; + } - public boolean getIsOperand(){return true;} double val; - boolean percentage= false; // 20081208 KSC: so can handle percentage values in String formulas - - /** return the human-readable String representation of - */ - public String getString(){ - if (!percentage) - return String.valueOf(val); - return String.valueOf(val*100)+"%"; + boolean percentage = false; // 20081208 KSC: so can handle percentage values in String formulas + + /** + * return the human-readable String representation of + */ + public String getString() { + if (!percentage) + return String.valueOf(val); + return val * 100 + "%"; } - - public Object getValue(){ + + public Object getValue() { Double d = new Double(val); return d; } - - public PtgNumber(){ - ptgId = 0x1F; - val = 0; - this.updateRecord(); + + public PtgNumber() { + ptgId = 0x1F; + val = 0; + this.updateRecord(); } - - public void init(byte[] b){ - ptgId = b[0]; - record = b; + + public void init(byte[] b) { + ptgId = b[0]; + record = b; this.populateVals(); } - - /** Constructer to create these on the fly, this is needed - for value storage in calculations of formulas. - */ - public PtgNumber(double d){ + + /** + * Constructer to create these on the fly, this is needed + * for value storage in calculations of formulas. + */ + public PtgNumber(double d) { ptgId = 0x1F; val = d; this.updateRecord(); } - - - private void populateVals(){ - byte[] barr = new byte[8]; - System.arraycopy(record, 1, barr, 0, 8); - val = ByteTools.eightBytetoLEDouble(barr); + + + private void populateVals() { + byte[] barr = new byte[8]; + System.arraycopy(record, 1, barr, 0, 8); + val = ByteTools.eightBytetoLEDouble(barr); } - - public double getVal(){ - return val; + + public double getVal() { + return val; } + /** - * override of GenericPtg.getDoubleVal(); + * override of GenericPtg.getDoubleVal(); */ public double getDoubleVal() { return val; } - - public void setVal(double i){ + + public void setVal(double i) { val = i; this.updateRecord(); } - + // 20081208 KSC: handle percentage values public void setVal(String s) { - s= s.trim(); - if (s.indexOf("%")==s.length()-1) { - percentage= true; - s= s.substring(0, s.indexOf("%")); - val= new Double(s).doubleValue()/100; - } else - val= new Double(s).doubleValue(); + s = s.trim(); + if (s.indexOf("%") == s.length() - 1) { + percentage = true; + s = s.substring(0, s.indexOf("%")); + val = new Double(s).doubleValue() / 100; + } else + val = new Double(s).doubleValue(); } - - public void updateRecord(){ + + public void updateRecord() { byte[] tmp = new byte[1]; tmp[0] = ptgId; byte[] brow = ByteTools.toBEByteArray(val); - tmp = ByteTools.append(brow, tmp); + tmp = ByteTools.append(brow, tmp); record = tmp; } - - public int getLength(){ + + public int getLength() { return PTG_NUM_LENGTH; } - - public String toString(){ - return getString(); + + public String toString() { + return getString(); } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgParen.java b/src/main/java/io/starter/formats/XLS/formulas/PtgParen.java index 10c4f61..91ee988 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgParen.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgParen.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,7 +23,6 @@ package io.starter.formats.XLS.formulas; - /* Indicate the placing of operatands in parenthesis. @@ -49,50 +48,57 @@ . */ -public class PtgParen extends GenericPtg implements Ptg -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8081397558698615537L; - public boolean getIsControl(){return true;} +public class PtgParen extends GenericPtg implements Ptg { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8081397558698615537L; + + public boolean getIsControl() { + return true; + } - /** return the human-readable String representation of - */ - public String getString(){ + /** + * return the human-readable String representation of + */ + public String getString() { // TODO: add logic to return proper paren 12/02 -jm - + return "("; - } - - /** return the human-readable String representation of - the "closing" portion of this Ptg - such as a closing parenthesis. - */ - public String getString2(){ + } + + /** + * return the human-readable String representation of + * the "closing" portion of this Ptg + * such as a closing parenthesis. + */ + public String getString2() { return ")"; } - - - /** Pass in the last 3 ptgs to evaluate - where to place the String parens. - - */ - public Object evaluate(Object[] b){ - return null; + + + /** + * Pass in the last 3 ptgs to evaluate + * where to place the String parens. + */ + public Object evaluate(Object[] b) { + return null; } - - public int getLength(){ + + public int getLength() { return PTG_PAREN_LENGTH; } - + // KSC: added //default constructor - public PtgParen(){ - ptgId = 0x15; - record = new byte[1]; - record[0] = ptgId; + public PtgParen() { + ptgId = 0x15; + record = new byte[1]; + record[0] = ptgId; + } + + public String toString() { + return ")"; } - public String toString() { return ")"; } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgPercent.java b/src/main/java/io/starter/formats/XLS/formulas/PtgPercent.java index ead8b40..ce0eed5 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgPercent.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgPercent.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -35,46 +35,55 @@ /** * */ -public class PtgPercent extends GenericPtg implements Ptg{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8559541841405018157L; +public class PtgPercent extends GenericPtg implements Ptg { - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsUnaryOperator(){return true;} - - /** return the human-readable String representation of - */ - public String getString(){ - return "%"; - } - - public int getLength(){ - return PTG_PERCENT_LENGTH; - } - - /* Operator specific calculate method, this one returns a single value sent to it. - - */ - public Ptg calculatePtg(Ptg[] form){ - // 20090202 KSC: Handle array formulas - Object[] o= super.getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o==null || o.length != 1){ - // there should always be only one ptg in this, error if not. - //if (form.length != 1){ - Logger.logWarn("calculating formula failed, wrong number of values in PtgPercent"); - return null; - } - } - // TODO: finish for Array formulas - double res = (((Double)o[0]).doubleValue())/100; - return new PtgNumber(res); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8559541841405018157L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsUnaryOperator() { + return true; + } + + /** + * return the human-readable String representation of + */ + public String getString() { + return "%"; + } + + public int getLength() { + return PTG_PERCENT_LENGTH; + } + + /* Operator specific calculate method, this one returns a single value sent to it. + + */ + public Ptg calculatePtg(Ptg[] form) { + // 20090202 KSC: Handle array formulas + Object[] o = getValuesFromPtgs(form); + if (!o[0].getClass().isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o == null || o.length != 1) { + // there should always be only one ptg in this, error if not. + //if (form.length != 1){ + Logger.logWarn("calculating formula failed, wrong number of values in PtgPercent"); + return null; + } + } + // TODO: finish for Array formulas + double res = (((Double) o[0]).doubleValue()) / 100; + return new PtgNumber(res); + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgPower.java b/src/main/java/io/starter/formats/XLS/formulas/PtgPower.java index 7a39bfc..895e368 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgPower.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgPower.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,7 +25,6 @@ import io.starter.toolkit.Logger; - /* Ptg that is a exponent operand Raises the second-to-top operand to the power of the @@ -36,54 +35,65 @@ */ -public class PtgPower extends GenericPtg implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4675566993519011450L; - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - public PtgPower(){ - ptgId = 0x7; - record = new byte[1]; - record[0] = 0x7; - } - /** return the human-readable String representation of - */ - public String getString(){ +public class PtgPower extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4675566993519011450L; + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public PtgPower() { + ptgId = 0x7; + record = new byte[1]; + record[0] = 0x7; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return "^"; - } + } - public int getLength(){ + public int getLength() { return PTG_POWER_LENGTH; } - /* Operator specific calculate method, this one raises the second-to-top - operand to the power of the top operand - - */ - public Ptg calculatePtg(Ptg[] form){ + + /* Operator specific calculate method, this one raises the second-to-top + operand to the power of the top operand + +*/ + public Ptg calculatePtg(Ptg[] form) { try { - // 20090202 KSC: Handle array formulas - Object[] o= super.getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o==null || o.length != 2){ - Logger.logWarn("calculating formula failed, wrong number of values in PtgPower"); - return null; - } - //double returnVal = Math.pow(dub[0].doubleValue(), dub[1].doubleValue()); - double returnVal = Math.pow(((Double)o[0]).doubleValue(),((Double)o[1]).doubleValue()); - // create a container ptg for these. - PtgNumber n = new PtgNumber(returnVal); - return n; - } - // TODO: FINISH ARRAY FORMULAS - return new PtgErr(PtgErr.ERROR_VALUE); - }catch(Exception e) { + // 20090202 KSC: Handle array formulas + Object[] o = getValuesFromPtgs(form); + if (!o[0].getClass().isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o == null || o.length != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgPower"); + return null; + } + //double returnVal = Math.pow(dub[0].doubleValue(), dub[1].doubleValue()); + double returnVal = Math.pow(((Double) o[0]).doubleValue(), ((Double) o[1]).doubleValue()); + // create a container ptg for these. + PtgNumber n = new PtgNumber(returnVal); + return n; + } + // TODO: FINISH ARRAY FORMULAS + return new PtgErr(PtgErr.ERROR_VALUE); + } catch (Exception e) { return new PtgErr(PtgErr.ERROR_VALUE); } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRange.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRange.java index b1f03ee..78d3060 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRange.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRange.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,11 +22,9 @@ */ package io.starter.formats.XLS.formulas; -import java.util.ArrayList; - import io.starter.OpenXLS.ExcelTools; -import io.starter.toolkit.Logger; +import java.util.ArrayList; /* @@ -39,223 +37,230 @@ */ -public class PtgRange extends GenericPtg implements Ptg -{ - +public class PtgRange extends GenericPtg implements Ptg { + private static final long serialVersionUID = 7181427387507157013L; - public boolean getIsOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} // 20091019 KSC + public boolean getIsOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } // 20091019 KSC /*? public boolean getIsOperand(){return true;} public boolean getIsControl(){return true;} */ - - public PtgRange(){ - ptgId = 0x11; - record = new byte[1]; - record[0] = 0x11; - } - /** return the human-readable String representation of - */ - public String getString(){ + + public PtgRange() { + ptgId = 0x11; + record = new byte[1]; + record[0] = 0x11; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return ":"; - } - - - - public int getLength(){ + } + + + public int getLength() { return PTG_RANGE_LENGTH; } - - /** - The RANGE operator (:) - Returns the minimal rectangular range that contains both parameters. - A1:B2:B2:C3 ==>A1:C3 - - NOTE: assumption is NO 3d refs i.e. all on same sheet ******* - */ - public Ptg calculatePtg(Ptg[] form){ - if (form.length!=2) + + /** + * The RANGE operator (:) + * Returns the minimal rectangular range that contains both parameters. + * A1:B2:B2:C3 ==>A1:C3 + *

                + * NOTE: assumption is NO 3d refs i.e. all on same sheet ******* + */ + public Ptg calculatePtg(Ptg[] form) { + if (form.length != 2) return new PtgErr(PtgErr.ERROR_VALUE); - try { - String sheet= null; - String sourceSheet= null; - try { - sourceSheet= this.getParentRec().getSheet().getSheetName(); - } catch (NullPointerException ne) { - ; - } - ArrayList first= null; - ArrayList last= null; - for (int i= 0; i < 2; i++) { - Ptg p= form[i]; - ArrayList a= new ArrayList(); - if (p instanceof PtgArea) { - a.add(p); - } else if (p instanceof PtgRef) { - a.add(p); - } else if (p instanceof PtgName) { - Ptg[] pc= ((PtgName)p).getComponents(); - for (int j= 0; j < pc.length; j++) - a.add(pc[j]); - } else if (p instanceof PtgStr) { - String[] comps= (p.toString()).split(","); - for (int j= 0; j < comps.length; j++) { - if (comps[j].indexOf(":")==-1) { - if (!comps[j].equals("#REF!") && - !comps[j].equals("#NULL!")) { - PtgRef3d pr= new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(comps[j]); - a.add(pr); - } else { - PtgRefErr3d pr= new PtgRefErr3d(); - pr.setParentRec(this.getParentRec()); - a.add(pr); - } - } else { - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(comps[j]); - Ptg[] pcs= pa.getComponents(); - if (pcs!=null) { - for (int k= 0; k < pcs.length; k++) { - ((PtgRef)pcs[k]).setSheetName(pa.getSheetName()); - a.add(pcs[k]); - } - } - } - } - } else if (p instanceof PtgArray) { - // parse array components and create refs - Ptg[] pc= ((PtgArray)p).getComponents(); - for (int j= 0; j < pc.length; j++) { - String loc=((PtgStr) pc[j]).toString(); - if (loc.indexOf(":")==-1) { - if (loc.indexOf("!")==-1) { - PtgRef pr= new PtgRef(); - pr.setUseReferenceTracker(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } else { - PtgRef3d pr= new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } - } else { - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(loc); - a.add(pa); - } - } - } else if (p instanceof PtgErr || p instanceof PtgRefErr || p instanceof PtgAreaErr3d) { - // DO WHAT??? - ; // ignore - } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array - Ptg[] pc= ((GenericPtg) p).vars; - for (int j= 0; j < pc.length; j++) { - if (pc[j] instanceof PtgArea & !(pc[j] instanceof PtgAreaErr3d)) { - Ptg[] pa= pc[j].getComponents(); - for (int k= 0; k < pa.length; k++) - a.add(pa[k]); - } else - a.add(pc[j]); - } - } - - if (first==null) - first= a; - else - last= a; - } - // now have components for both operands - // range op returns the range that encompasses all referenced ptgs - int[] rng= new int[] {Short.MAX_VALUE, Short.MAX_VALUE, 0, 0}; - for (int k= 0; k < first.size(); k++) { - PtgRef pr= (PtgRef) first.get(k); - if (sheet==null)sheet= pr.getSheetName(); // TODO: 3d ranges?????? - int[] rc= pr.getIntLocation(); - if (rc.length>2) { // it's a range - int numrows = (rc[2]- rc[0]) + 1; - int numcols = (rc[3] - rc[1]) + 1; - int numcells = numrows * numcols; - if (numcells < 0) - numcells *= -1; // handle swapped cells ie: "B1:A1" - int rowctr= rc[0]; - int cellctr = rc[1] - 1; - for (int i = 0; i < numcells; i++) { - if (cellctr == rc[3]) {// if its the end of the row,increment row. - cellctr = rc[1]- 1; - rowctr++; - } - ++cellctr; - int[] addr=new int[] {rowctr, cellctr}; - adjustRange(addr, rng); - } - } else { - adjustRange(rc, rng); - } - } - for (int k= 0; k < last.size(); k++) { - PtgRef pr= (PtgRef) last.get(k); - if (sheet==null)sheet= pr.getSheetName(); // TODO: 3d ranges?????? - int[] rc= pr.getIntLocation(); - if (rc.length>2) { // it's a range - if (rc.length>2) { // it's a range - int numrows = (rc[2]- rc[0]) + 1; - int numcols = (rc[3] - rc[1]) + 1; - int numcells = numrows * numcols; - if (numcells < 0) - numcells *= -1; // handle swapped cells ie: "B1:A1" - int rowctr= rc[0]; - int cellctr = rc[1] - 1; - for (int i = 0; i < numcells; i++) { - if (cellctr == rc[3]) {// if its the end of the row,increment row. - cellctr = rc[1]- 1; - rowctr++; - } - ++cellctr; - int[] addr=new int[] {rowctr, cellctr}; - adjustRange(addr, rng); - } - } - } else { - adjustRange(rc, rng); - } - } - // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars - GenericPtg retp= new PtgMystery(); - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - // TODO: 3d ranges???? - pa.setSheetName(sheet); - pa.setLocation(rng); - retp.setVars(new Ptg[] {pa}); - return retp; - }catch(NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - }catch(Exception e) { // handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } + try { + String sheet = null; + String sourceSheet = null; + try { + sourceSheet = this.getParentRec().getSheet().getSheetName(); + } catch (NullPointerException ne) { + } + ArrayList first = null; + ArrayList last = null; + for (int i = 0; i < 2; i++) { + Ptg p = form[i]; + ArrayList a = new ArrayList(); + if (p instanceof PtgArea) { + a.add(p); + } else if (p instanceof PtgRef) { + a.add(p); + } else if (p instanceof PtgName) { + Ptg[] pc = p.getComponents(); + for (int j = 0; j < pc.length; j++) + a.add(pc[j]); + } else if (p instanceof PtgStr) { + String[] comps = (p.toString()).split(","); + for (int j = 0; j < comps.length; j++) { + if (comps[j].indexOf(":") == -1) { + if (!comps[j].equals("#REF!") && + !comps[j].equals("#NULL!")) { + PtgRef3d pr = new PtgRef3d(false); + pr.setParentRec(this.getParentRec()); + pr.setLocation(comps[j]); + a.add(pr); + } else { + PtgRefErr3d pr = new PtgRefErr3d(); + pr.setParentRec(this.getParentRec()); + a.add(pr); + } + } else { + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(this.getParentRec()); + pa.setLocation(comps[j]); + Ptg[] pcs = pa.getComponents(); + if (pcs != null) { + for (int k = 0; k < pcs.length; k++) { + ((PtgRef) pcs[k]).setSheetName(pa.getSheetName()); + a.add(pcs[k]); + } + } + } + } + } else if (p instanceof PtgArray) { + // parse array components and create refs + Ptg[] pc = p.getComponents(); + for (int j = 0; j < pc.length; j++) { + String loc = pc[j].toString(); + if (loc.indexOf(":") == -1) { + if (loc.indexOf("!") == -1) { + PtgRef pr = new PtgRef(); + pr.setUseReferenceTracker(false); + pr.setParentRec(this.getParentRec()); + pr.setLocation(loc); + a.add(pr); + } else { + PtgRef3d pr = new PtgRef3d(false); + pr.setParentRec(this.getParentRec()); + pr.setLocation(loc); + a.add(pr); + } + } else { + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(this.getParentRec()); + pa.setLocation(loc); + a.add(pa); + } + } + } else if (p instanceof PtgErr || p instanceof PtgRefErr || p instanceof PtgAreaErr3d) { + // DO WHAT??? + // ignore + } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array + Ptg[] pc = ((GenericPtg) p).vars; + for (int j = 0; j < pc.length; j++) { + if (pc[j] instanceof PtgArea & !(pc[j] instanceof PtgAreaErr3d)) { + Ptg[] pa = pc[j].getComponents(); + for (int k = 0; k < pa.length; k++) + a.add(pa[k]); + } else + a.add(pc[j]); + } + } + + if (first == null) + first = a; + else + last = a; + } + // now have components for both operands + // range op returns the range that encompasses all referenced ptgs + int[] rng = new int[]{Short.MAX_VALUE, Short.MAX_VALUE, 0, 0}; + for (int k = 0; k < first.size(); k++) { + PtgRef pr = (PtgRef) first.get(k); + if (sheet == null) sheet = pr.getSheetName(); // TODO: 3d ranges?????? + int[] rc = pr.getIntLocation(); + if (rc.length > 2) { // it's a range + int numrows = (rc[2] - rc[0]) + 1; + int numcols = (rc[3] - rc[1]) + 1; + int numcells = numrows * numcols; + if (numcells < 0) + numcells *= -1; // handle swapped cells ie: "B1:A1" + int rowctr = rc[0]; + int cellctr = rc[1] - 1; + for (int i = 0; i < numcells; i++) { + if (cellctr == rc[3]) {// if its the end of the row,increment row. + cellctr = rc[1] - 1; + rowctr++; + } + ++cellctr; + int[] addr = new int[]{rowctr, cellctr}; + adjustRange(addr, rng); + } + } else { + adjustRange(rc, rng); + } + } + for (int k = 0; k < last.size(); k++) { + PtgRef pr = (PtgRef) last.get(k); + if (sheet == null) sheet = pr.getSheetName(); // TODO: 3d ranges?????? + int[] rc = pr.getIntLocation(); + if (rc.length > 2) { // it's a range + if (rc.length > 2) { // it's a range + int numrows = (rc[2] - rc[0]) + 1; + int numcols = (rc[3] - rc[1]) + 1; + int numcells = numrows * numcols; + if (numcells < 0) + numcells *= -1; // handle swapped cells ie: "B1:A1" + int rowctr = rc[0]; + int cellctr = rc[1] - 1; + for (int i = 0; i < numcells; i++) { + if (cellctr == rc[3]) {// if its the end of the row,increment row. + cellctr = rc[1] - 1; + rowctr++; + } + ++cellctr; + int[] addr = new int[]{rowctr, cellctr}; + adjustRange(addr, rng); + } + } + } else { + adjustRange(rc, rng); + } + } + // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars + GenericPtg retp = new PtgMystery(); + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(this.getParentRec()); + // TODO: 3d ranges???? + pa.setSheetName(sheet); + pa.setLocation(rng); + retp.setVars(new Ptg[]{pa}); + return retp; + } catch (NumberFormatException e) { + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } catch (Exception e) { // handle error ala Excel + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } } - + private void adjustRange(int[] rc, int[] rng) { - if (ExcelTools.isBeforeRange(rc, rng)) { - rng[0]= rc[0]; - rng[1]= rc[1]; - } - if (ExcelTools.isAfterRange(rc, rng)) { - rng[2]= rc[0]; - rng[3]= rc[1]; - } + if (ExcelTools.isBeforeRange(rc, rng)) { + rng[0] = rc[0]; + rng[1] = rc[1]; + } + if (ExcelTools.isAfterRange(rc, rng)) { + rng[2] = rc[0]; + rng[3] = rc[1]; + } } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRef.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRef.java index 286994d..2afa7b4 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRef.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRef.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,13 +23,6 @@ package io.starter.formats.XLS.formulas; import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.ExpressionParser; -import io.starter.formats.XLS.Formula; -import io.starter.formats.XLS.WorkBook; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.formats.XLS.XLSRecord; - import io.starter.formats.XLS.*; import io.starter.formats.cellformat.CellFormatFactory; import io.starter.toolkit.ByteTools; @@ -38,42 +31,40 @@ /** - ptgRef is a reference to a single cell. It contains row and - column information, plus a grbit to determine whether these - values are relative or absolute. This grbit is, stupidly, but expectedly, - encoded within the column value. -

                -    Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0           rw          2       the row
                -    2           grbitCol    2       (see following table)
                -
                -    Only the low-order 14 bits specify the Col, the other bits specify
                -    relative vs absolute for both the col or the row.
                -
                -    Bits        Mask        Name    Contents
                -    -----------------------------------------------------
                -    15          8000h       fRwRel  =1 if row offset relative, 
                -                                    =0 if otherwise
                -    14          4000h       fColRel =1 if col offset relative,
                -                                    =0 if otherwise
                -    13-0        3FFFh       col     Ordinal column offset or number
                -    
                - - + * ptgRef is a reference to a single cell. It contains row and + * column information, plus a grbit to determine whether these + * values are relative or absolute. This grbit is, stupidly, but expectedly, + * encoded within the column value. + *
                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0           rw          2       the row
                + * 2           grbitCol    2       (see following table)
                + *
                + * Only the low-order 14 bits specify the Col, the other bits specify
                + * relative vs absolute for both the col or the row.
                + *
                + * Bits        Mask        Name    Contents
                + * -----------------------------------------------------
                + * 15          8000h       fRwRel  =1 if row offset relative,
                + * =0 if otherwise
                + * 14          4000h       fColRel =1 if col offset relative,
                + * =0 if otherwise
                + * 13-0        3FFFh       col     Ordinal column offset or number
                + * 
                + * * @see WorkBook * @see Boundsheet * @see Dbcell * @see Row * @see Cell * @see XLSRecord - -*/ + */ public class PtgRef extends GenericPtg implements Ptg { /** - * - */ - private static final long serialVersionUID = -7776520933300730470L; + * + */ + private static final long serialVersionUID = -7776520933300730470L; protected int rw; // TODO: We actually are talking about 2 different notions of relativity: // 1- Relativity based on shared formula parent formula row/col @@ -89,118 +80,135 @@ public class PtgRef extends GenericPtg implements Ptg { protected int formulaCol; BiffRec[] refCell = new BiffRec[1]; protected String sheetname = null; - - protected int externalLink1= 0; - protected int externalLink2= 0; + + protected int externalLink1 = 0; + protected int externalLink2 = 0; protected boolean useReferenceTracker = true; - - String locax = null; -// String locstrax = null; - public long hashcode= -1L; - - + + String locax = null; + // String locstrax = null; + public long hashcode = -1L; + + public boolean equals(Object ob) { - return ob.hashCode()== this.hashCode(); + return ob.hashCode() == this.hashCode(); + } + + public boolean getIsWholeRow() { + return wholeRow; + } + + public void setIsWholeRow(boolean b) { + wholeRow = b; + } + + public boolean getIsWholeCol() { + return wholeCol; + } + + public void setIsWholeCol(boolean b) { + wholeCol = b; + } + + public boolean getIsRefErr() { + return false; + } + + public boolean wholeRow = false, wholeCol = false; // denotes a range which spans the entire row or column, a shorthand for checking end col or row # as this will vary between excel versions + + public boolean getIsOperand() { + return true; + } + + public boolean getIsReference() { + return true; + } + + public PtgRef(int[] rowcol, XLSRecord x, boolean useRefTracker) { + this(); + setParentRec(x); + this.useReferenceTracker = useRefTracker; + setLocation(rowcol); + updateRecord(); } - - public boolean getIsWholeRow() { return wholeRow; } - public void setIsWholeRow(boolean b) { wholeRow= b; } - public boolean getIsWholeCol() { return wholeCol; } - public void setIsWholeCol(boolean b) { wholeCol= b; } - public boolean getIsRefErr() { return false; } - - public boolean wholeRow= false, wholeCol= false; // denotes a range which spans the entire row or column, a shorthand for checking end col or row # as this will vary between excel versions - - public boolean getIsOperand(){return true;} - public boolean getIsReference(){return true;} - - public PtgRef(int[] rowcol, XLSRecord x, boolean useRefTracker){ - this(); - setParentRec(x); - this.useReferenceTracker= useRefTracker; - setLocation(rowcol); - updateRecord(); - } - - - + + /** - * - This constructor is for programmatic creation of Ptg's - in this case we do not have the ptgid, just the refereced location - */ - public PtgRef(String location, XLSRecord x, boolean utilizeRefTracker){ + * This constructor is for programmatic creation of Ptg's + * in this case we do not have the ptgid, just the refereced location + */ + public PtgRef(String location, XLSRecord x, boolean utilizeRefTracker) { this.setUseReferenceTracker(utilizeRefTracker); - ptgId =0x44; //0x24; defaulting to value operand + ptgId = 0x44; //0x24; defaulting to value operand record = new byte[5]; record[0] = ptgId; setParentRec(x); // MUST set before setLocation also sets formulaRow ... this.setLocation(location); this.setIsWholeRowCol(); - if(useReferenceTracker) - addToRefTracker(); + if (useReferenceTracker) + addToRefTracker(); } - - + + /** - * + * * @param id */ - + /** * set the Ptg Id type to one of: * VALUE, REFERENCE or Array - *
                The Ptg type is important for certain + *
                The Ptg type is important for certain * functions which require a specific type of operand */ - public void setPtgType(short type) { - switch (type) { - case VALUE: - ptgId= 0x44; - break; - case REFERENCE: - ptgId= 0x24; - break; - case Ptg.ARRAY: - ptgId= 0x64; - break; - } - record[0]= ptgId; + public void setPtgType(short type) { + switch (type) { + case VALUE: + ptgId = 0x44; + break; + case REFERENCE: + ptgId = 0x24; + break; + case Ptg.ARRAY: + ptgId = 0x64; + break; + } + record[0] = ptgId; } - + /** - This constructor is for programmatic creation of Ptg's - in this case we do not have the ptgid, just the refereced location - - this version sets the value of useReferenceTracker to avoid multiple entries due to area parent - */ - public PtgRef(byte[] bin,XLSRecord x, boolean utilizeRefTracker){ - this(); + * This constructor is for programmatic creation of Ptg's + * in this case we do not have the ptgid, just the refereced location + *

                + * this version sets the value of useReferenceTracker to avoid multiple entries due to area parent + */ + public PtgRef(byte[] bin, XLSRecord x, boolean utilizeRefTracker) { + this(); this.setUseReferenceTracker(utilizeRefTracker); - setParentRec(x); //MUST DO BEFORE INIT ... also sets formulaRow ... + setParentRec(x); //MUST DO BEFORE INIT ... also sets formulaRow ... init(bin); - if(useReferenceTracker) - addToRefTracker(); // TODO: check subreference issue (if it's not a 'real' ptg) + if (useReferenceTracker) + addToRefTracker(); // TODO: check subreference issue (if it's not a 'real' ptg) } - - /** default constructor - * + + /** + * default constructor */ - public PtgRef(){ - // 24H (tRefR), 44H (tRefV), 64H (tRefA) - ptgId =0x44; // default to value operand - record = new byte[5]; - record[0] = ptgId; + public PtgRef() { + // 24H (tRefR), 44H (tRefV), 64H (tRefA) + ptgId = 0x44; // default to value operand + record = new byte[5]; + record[0] = ptgId; } - - - public void init(byte[] b){ - ptgId = b[0]; - record = b; + + + public void init(byte[] b) { + ptgId = b[0]; + record = b; this.populateVals(); } - + /** * Ptgs upkeep their mapping in reference tracker, however, some ptgs * are components of other Ptgs, such as individual ptg cells in a PtgArea. These @@ -209,299 +217,298 @@ record = b; public void setUseReferenceTracker(boolean b) { useReferenceTracker = b; } + public boolean getUseReferenceTracker() { return useReferenceTracker; } - - /** parse all the values out of the byte array and - populate the classes values - */ - protected void populateVals(){ - rw= readRow(record[1], record[2]); - short column = (short) ByteTools.readShort(record[3], record[4]); - if ((column & 0x8000) == 0x8000){ // is the Row relative? - fRwRel = true; - }else{ fRwRel = false;} - if ((column & 0x4000) == 0x4000){ // is the Column relative? - fColRel = true; - }else{ fColRel = false;} + + /** + * parse all the values out of the byte array and + * populate the classes values + */ + protected void populateVals() { + rw = readRow(record[1], record[2]); + short column = ByteTools.readShort(record[3], record[4]); + // is the Row relative? + fRwRel = (column & 0x8000) == 0x8000; + // is the Column relative? + fColRel = (column & 0x4000) == 0x4000; col = (short) (column & 0x3fff); setRelativeRowCol(); // set formulaRow/Col for relative references if necessary this.setIsWholeRowCol(); - hashcode= getHashCode(); + hashcode = getHashCode(); } - + public boolean is3dRef() { return is3dRef; } - - /** return the human-readable String representation of - this ptg -- if applicable - */ - public String getString(){ - return this.getLocation(); - } + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + public String getString() { + return this.getLocation(); + } + /** * returns the String address of this ptg including sheet reference + * + * @return + */ + public String getLocationWithSheet() { + String ret = getString(); + + // AI PtgRefs do not have location info + if ((ret == null) && (parent_rec.getOpcode() == XLSRecord.AI)) + return parent_rec.toString(); + + if (ret == null) + return ""; + + if (ret.indexOf("!") > -1) + return ret; + + ret = sheetname + "!" + ret; + + return ret; + } + + + public String toString() { + return getString(); + } + - * + /** + * returns the row/col ints for the ref + * * @return */ - public String getLocationWithSheet(){ - String ret = getString(); - - // AI PtgRefs do not have location info - if((ret == null)&&(parent_rec.getOpcode() == XLSRecord.AI)) - return parent_rec.toString(); - - if(ret==null) - return ""; - - if(ret.indexOf("!")>-1) - return ret; - - ret = sheetname + "!" + ret; - - return ret; - } - - - public String toString(){ - return getString(); - } - - - /** returns the row/col ints for the ref - * - * @return - */ - public int[] getRowCol(){ - int[] ret = {this.rw,this.col}; - if (this.rw<0) {// if row truly references MAXROWS_BIFF8 comes out - - ret[0] = MAXROWS_BIFF8; - this.wholeCol= true; + public int[] getRowCol() { + int[] ret = {this.rw, this.col}; + if (this.rw < 0) {// if row truly references MAXROWS_BIFF8 comes out - + ret[0] = MAXROWS_BIFF8; + this.wholeCol = true; } - return ret; - } - - /** - * sets the sheetname for this - * @param sheetname - */ - public void setSheetName(String sheetname) { this.sheetname= sheetname;} - - + return ret; + } + /** - * Returns the location of the Ptg as a string (ie c4) + * sets the sheetname for this + * + * @param sheetname + */ + public void setSheetName(String sheetname) { + this.sheetname = sheetname; + } - * + + /** + * Returns the location of the Ptg as a string (ie c4) + * * @see io.starter.formats.XLS.formulas.GenericPtg#getLocation() */ - public String getLocation() { - if(locax !=null)//cache - return locax; + public String getLocation() { + if (locax != null)//cache + return locax; - int[] adjusted= this.getIntLocation(); + int[] adjusted = this.getIntLocation(); String s; if (this.wholeCol) { - s = (fColRel?"":"$") + ExcelTools.getAlphaVal(adjusted[1]); + s = (fColRel ? "" : "$") + ExcelTools.getAlphaVal(adjusted[1]); } else if (this.wholeRow) { - s = (fRwRel?"":"$") + String.valueOf(adjusted[0]+1); - } else{ - if(rw < 0 || col < 0) - return new PtgRefErr().toString(); - - s = (fColRel?"":"$") + ExcelTools.getAlphaVal(adjusted[1]) + - (fRwRel?"":"$") + String.valueOf(adjusted[0]+1); + s = (fRwRel ? "" : "$") + (adjusted[0] + 1); + } else { + if (rw < 0 || col < 0) + return new PtgRefErr().toString(); + + s = (fColRel ? "" : "$") + ExcelTools.getAlphaVal(adjusted[1]) + + (fRwRel ? "" : "$") + (adjusted[0] + 1); } - locax= s; + locax = s; return locax; } - + /** * Get the location of this ptgRef as an int array {row, col}. 0 based */ - public int[] getIntLocation(){ - - this.setIsWholeRowCol(); - int rowNew = rw; - int colNew = col; - try { - boolean isExcel2007= this.parent_rec.getWorkBook().getIsExcel2007(); - if (fRwRel){ // the row is a relative location - rowNew+=formulaRow; - } - if (fColRel){ // the column is a relative location - colNew+=formulaCol; - } - if (wholeRow) { - if (!isExcel2007) - colNew= MAXCOLS_BIFF8; - else - colNew= MAXCOLS; - } - if (wholeCol) { - if (isExcel2007) - rowNew= MAXROWS-1; - else - rowNew= MAXROWS_BIFF8-1; - } - - } catch (NullPointerException e) { - } - return new int[] {rowNew, colNew}; + public int[] getIntLocation() { + + this.setIsWholeRowCol(); + int rowNew = rw; + int colNew = col; + try { + boolean isExcel2007 = this.parent_rec.getWorkBook().getIsExcel2007(); + if (fRwRel) { // the row is a relative location + rowNew += formulaRow; + } + if (fColRel) { // the column is a relative location + colNew += formulaCol; + } + if (wholeRow) { + if (!isExcel2007) + colNew = MAXCOLS_BIFF8; + else + colNew = MAXCOLS; + } + if (wholeCol) { + if (isExcel2007) + rowNew = MAXROWS - 1; + else + rowNew = MAXROWS_BIFF8 - 1; + } + + } catch (NullPointerException e) { + } + return new int[]{rowNew, colNew}; } /** * Get the location of this ptgRef as an int array {row, col}. 0 based - NOTE: this version of getIntLocation returns the actual or real coordinates - This may be different from getIntLocation when rw designates MAXROWS - in these cases, - this method will return real max rows + * NOTE: this version of getIntLocation returns the actual or real coordinates + * This may be different from getIntLocation when rw designates MAXROWS - in these cases, + * this method will return real max rows */ - public int[] getRealIntLocation(){ + public int[] getRealIntLocation() { int rowNew = rw; int colNew = col; - if (fRwRel){ // the row is a relative location - rowNew += formulaRow; + if (fRwRel) { // the row is a relative location + rowNew += formulaRow; } - if (fColRel){ // the column is a relative location - colNew += formulaCol; + if (fColRel) { // the column is a relative location + colNew += formulaCol; } - + if (wholeCol || rowNew < 0) - try { - if (rowNew < 0) - wholeCol= true; - rowNew= this.getParentRec().getSheet().getMaxRow(); - } catch (Exception e) { - ; - } - - if (wholeRow || colNew >=MAXCOLS) - try { - colNew= this.getParentRec().getSheet().getMaxCol(); - } catch (Exception e) { - ; - } - int[] ret = {rowNew,colNew}; + try { + if (rowNew < 0) + wholeCol = true; + rowNew = this.getParentRec().getSheet().getMaxRow(); + } catch (Exception e) { + } + + if (wholeRow || colNew >= MAXCOLS) + try { + colNew = this.getParentRec().getSheet().getMaxCol(); + } catch (Exception e) { + } + int[] ret = {rowNew, colNew}; return ret; } - - /** - * Get the worksheet name this ptgref refers to - * @throws WorkSheetNotFoundException - */ - public String getSheetName() - throws WorkSheetNotFoundException { - - if(locax!=null){ // reference on different sheet than parent - if(locax.indexOf("!")>-1){ - sheetname = locax.substring(0,locax.indexOf("!")); - } - } - if (sheetname ==null && - parent_rec!=null){ - if(parent_rec.getSheet()!=null) { - sheetname= parent_rec.getSheet().getSheetName(); - } - } - - if(sheetname == null) { - return ""; // no sheetname - }else{ - - //handle external references (OOXML-specific) + + /** + * Get the worksheet name this ptgref refers to + * + * @throws WorkSheetNotFoundException + */ + public String getSheetName() + throws WorkSheetNotFoundException { + + if (locax != null) { // reference on different sheet than parent + if (locax.indexOf("!") > -1) { + sheetname = locax.substring(0, locax.indexOf("!")); + } + } + if (sheetname == null && + parent_rec != null) { + if (parent_rec.getSheet() != null) { + sheetname = parent_rec.getSheet().getSheetName(); + } + } + + if (sheetname == null) { + return ""; // no sheetname + } else { + + //handle external references (OOXML-specific) if (externalLink1 > 0) { - if (sheetname.charAt(0)=='\'') - sheetname= sheetname.substring(1, sheetname.length()-1); - sheetname= "[" + externalLink1 + "]" + sheetname; + if (sheetname.charAt(0) == '\'') + sheetname = sheetname.substring(1, sheetname.length() - 1); + sheetname = "[" + externalLink1 + "]" + sheetname; } - sheetname= qualifySheetname(sheetname); + sheetname = qualifySheetname(sheetname); } - return sheetname; + return sheetname; } - - /** - * Set the location of this PtgRef. This takes a location - such as "a14", also can take a absolute location, such as $A14 - */ - public void setLocation(String address){ - locax= null; - refCell = null; - if (record != null){ - String s[]= ExcelTools.stripSheetNameFromRange(address); - setLocation(s); - locax = s[1]; - }else{ - Logger.logWarn("PtgRef.setLocation() failed: NO record data: "+ address); - } - } - + /** - * Clears the location cache when needed + * Set the location of this PtgRef. This takes a location + * such as "a14", also can take a absolute location, such as $A14 + */ + public void setLocation(String address) { + locax = null; + refCell = null; + if (record != null) { + String[] s = ExcelTools.stripSheetNameFromRange(address); + setLocation(s); + locax = s[1]; + } else { + Logger.logWarn("PtgRef.setLocation() failed: NO record data: " + address); + } + } - * + /** + * Clears the location cache when needed */ public void clearLocationCache() { locax = null; } - + /** * Does this ref reference an entire row (ie $1); + * + * @return + */ + private boolean referencesEntireRow() { + boolean isExcel2007 = this.parent_rec.getWorkBook().getIsExcel2007(); + int colNew = col; + if (fColRel) { // the row is a relative location + colNew += formulaRow; + } + if (colNew < 0) { // have to assume that it's a wholeRow even if 2007 + return true; + } else if (colNew >= MAXCOLS_BIFF8 - 1 && !isExcel2007) { + return true; + } + if (this.cachedLocation != null && isExcel2007) { + return this.locationStringReferencesEntireRow(); + } + // This is unfortunately a bit of a hack due to biff 8 incompatibilies + return colNew == MAXCOLS_BIFF8 - 1 && isExcel2007; + + } + + /** + * Check if the cached string location referrs to a full row + * * @return */ - private boolean referencesEntireRow(){ - boolean isExcel2007= this.parent_rec.getWorkBook().getIsExcel2007(); - int colNew = col; - if (fColRel){ // the row is a relative location - colNew+=formulaRow; - } - if (colNew < 0){ // have to assume that it's a wholeRow even if 2007 - return true; - }else if (colNew>=MAXCOLS_BIFF8-1 && !isExcel2007) { - return true; - } - if (this.cachedLocation!=null && isExcel2007){ - return this.locationStringReferencesEntireRow(); - } - // This is unfortunately a bit of a hack due to biff 8 incompatibilies - if (colNew==MAXCOLS_BIFF8-1 && isExcel2007) { - return true; - } - return false; - + private boolean locationStringReferencesEntireRow() { + if (this.cachedLocation != null) { + int[] res = ExcelTools.getRowColFromString(cachedLocation); + return res[1] < 0; + } + return false; } - - /** - * Check if the cached string location referrs to a full row - * @return - */ - private boolean locationStringReferencesEntireRow() { - if(this.cachedLocation!=null){ - int[] res = ExcelTools.getRowColFromString(cachedLocation); - if(res[1]<0){ - return true; - } - } - return false; - } - - /** + + /** * Does this ref reference an entire col (ie $A); + * * @return - */ - private boolean referencesEntireCol(){ + */ + private boolean referencesEntireCol() { int rowNew = rw; - boolean isExcel2007= this.parent_rec.getWorkBook().getIsExcel2007(); - if (fRwRel){ // the row is a relative location - rowNew+=formulaRow; + boolean isExcel2007 = this.parent_rec.getWorkBook().getIsExcel2007(); + if (fRwRel) { // the row is a relative location + rowNew += formulaRow; } - if (rowNew<0) { - return true; - } else if (rowNew>=MAXROWS_BIFF8-1 && !isExcel2007) { - rowNew= -1; - return true; + if (rowNew < 0) { + return true; + } else if (rowNew >= MAXROWS_BIFF8 - 1 && !isExcel2007) { + rowNew = -1; + return true; } return false; } @@ -509,569 +516,590 @@ private boolean referencesEntireCol(){ /** * Inspects the record to determin if it references whole * rows or columns and sets the values as required. + * * @return */ - protected void setIsWholeRowCol(){ - this.wholeCol = referencesEntireCol(); - this.wholeRow = referencesEntireRow(); + protected void setIsWholeRowCol() { + this.wholeCol = referencesEntireCol(); + this.wholeRow = referencesEntireRow(); } - + /** - * set Ptg to parsed location - * @param loc String[] sheet1, range, sheet2, exref1, exref2 - */ - public void setLocation(String[] loc) { - if(useReferenceTracker) - removeFromRefTracker(); - locax= null; - sheetname= loc[0]; - String addr= loc[1]; - cachedLocation = addr; - fRwRel = true; - fColRel = true; - if (addr.indexOf("$") == -1){ // both row and col are relative refs, meaning moves/copies will change ref - // relative link - if (!addr.equals("#REF!") && !addr.equals("")) { - int[] res = ExcelTools.getRowColFromString(addr); - col = res[1]; - rw = res[0]; - } else { - col= -1; - rw= -1; - } - }else{ - // absolute reference - if (addr.substring(0,1).equalsIgnoreCase("$")){ - fColRel = false; - addr= addr.substring(1, addr.length()); - } - if (addr.indexOf("$") != -1){ - fRwRel = false; - addr = StringTool.strip(addr, "$"); - } - int[] res= null; - try { - res = ExcelTools.getRowColFromString(addr); - col = res[1]; - rw = res[0]; - if (col==-1 || rw==-1) { // if wholerow or wholecol, must be absolute - fColRel= false; - fRwRel= false; - } - }catch (IllegalArgumentException ie) { //is it a wholerow/wholecol issue? - if (Character.isDigit(addr.charAt(0))) { //assume wholecol ref - col= MAXCOLS_BIFF8-1; - rw= Integer.valueOf(addr).intValue()-1; - fColRel= false; - fRwRel= false; - } else { //wholerow ref? - rw= -1; - col= ExcelTools.getIntVal(addr); - fColRel= false; - fRwRel= false; - } - } - } - if (col==-1) - wholeRow= true; - if (rw==-1) - wholeCol= true; - this.setIsWholeRowCol(); - this.updateRecord(); - hashcode= getHashCode(); + * set Ptg to parsed location + * + * @param loc String[] sheet1, range, sheet2, exref1, exref2 + */ + public void setLocation(String[] loc) { + if (useReferenceTracker) + removeFromRefTracker(); + locax = null; + sheetname = loc[0]; + String addr = loc[1]; + cachedLocation = addr; + fRwRel = true; + fColRel = true; + if (addr.indexOf("$") == -1) { // both row and col are relative refs, meaning moves/copies will change ref + // relative link + if (!addr.equals("#REF!") && !addr.equals("")) { + int[] res = ExcelTools.getRowColFromString(addr); + col = res[1]; + rw = res[0]; + } else { + col = -1; + rw = -1; + } + } else { + // absolute reference + if (addr.substring(0, 1).equalsIgnoreCase("$")) { + fColRel = false; + addr = addr.substring(1); + } + if (addr.indexOf("$") != -1) { + fRwRel = false; + addr = StringTool.strip(addr, "$"); + } + int[] res = null; + try { + res = ExcelTools.getRowColFromString(addr); + col = res[1]; + rw = res[0]; + if (col == -1 || rw == -1) { // if wholerow or wholecol, must be absolute + fColRel = false; + fRwRel = false; + } + } catch (IllegalArgumentException ie) { //is it a wholerow/wholecol issue? + if (Character.isDigit(addr.charAt(0))) { //assume wholecol ref + col = MAXCOLS_BIFF8 - 1; + rw = Integer.valueOf(addr).intValue() - 1; + fColRel = false; + fRwRel = false; + } else { //wholerow ref? + rw = -1; + col = ExcelTools.getIntVal(addr); + fColRel = false; + fRwRel = false; + } + } + } + if (col == -1) + wholeRow = true; + if (rw == -1) + wholeCol = true; + this.setIsWholeRowCol(); + this.updateRecord(); + hashcode = getHashCode(); // trap OOXML external reference link, if any - if (loc[3]!=null) - externalLink1= Integer.valueOf(loc[3].substring(1, loc[3].length()-1)).intValue(); - if (loc[4]!=null) - externalLink2= Integer.valueOf(loc[4].substring(1, loc[4].length()-1)).intValue(); - if(useReferenceTracker) { - if(!getIsRefErr() && !this.getIsWholeCol() && !this.getIsWholeRow()) - addToRefTracker(); - } - } - - - /** Set the location of this PtgRef. This takes a location - such as {1,2} - */ - public void setLocation(int[] rowcol){ - locax = null; - cachedLocation = null; - if (record != null){ - if(useReferenceTracker) - removeFromRefTracker(); - rw = rowcol[0]; - col = rowcol[1]; - fRwRel = true; // default - fColRel = true; - this.updateRecord(); - hashcode= getHashCode(); - if(useReferenceTracker) - addToRefTracker(); - }else{ - Logger.logWarn("PtgRef.setLocation() failed: NO record data: "+ rowcol.toString()); - } - } - - /** - * given an address string, parse and assign to the appropriate PtgRef-type object - *
                #REF! 's return either PtgRefErr or PtgRefErr3d - *
                Ranges return either PtgArea or PtgArea3d - *
                Single addresses return either PtgRef or PtgRef3d - *
                NOTE: This method does not extract names embedded within the address string - * @param address - * @param parent parent record to assign the ptg to - * @return - */ - public static Ptg createPtgRefFromString(String address, XLSRecord parent) { - try { - String[] s= ExcelTools.stripSheetNameFromRange(address); - String sh1= s[0]; - String range= s[1]; - Ptg ptg; - if (range==null || range.equals("#REF!") || (sh1!=null && sh1.equals("#REF"))) { - if (sh1!=null) { - PtgRefErr3d pe3= new PtgRefErr3d(); - pe3.setParentRec(parent); - pe3.setLocation(s); - return pe3; - }else { - PtgRefErr pe= new PtgRefErr(); - pe.setParentRec(parent); - pe.setLocation(s); - return pe; - } - } - WorkBook bk= parent.getWorkBook(); - - - String sht= "((?:\\\\?+.)*?!)?+"; - String rangeMatch= "(.*(:).*){2,}?"; //matches 2 or more range ops (:'s) - String opMatch= "(.*([ ,]).*)+"; //matches union or isect op ( " " or ,) - String m= sht + "((" + opMatch + ")|(" + rangeMatch +"))"; - // is address a complex range?? - if (address.matches(m) || range.indexOf("(")>-1) { - //NOTE: this can be a MemFunc OR a MemArea -- - // PtgMemFunc= a NON-CONSTANT cell address, cell range address or cell range list - // Whenever one operand of the reference subexpression is a function, a defined name, a 3D - // reference, or an external reference (and no error occurs), a PtgMemFunc token is used. - // PtgMemArea= constant cell address, cell range address, or cell range list on the same sheet - PtgMemFunc pmf= new PtgMemFunc(); - pmf.setParentRec(parent); - pmf.setLocation(address); // TODO HANDLE FUNCTION MEMFUNCS ALA OFFSET(x,y,0):OFFSET(x,y,0) - ptg= pmf; - } else if (range.indexOf(":") > 0) { // it's a range, either PtgRef3d or PtgArea3d - String[] ops= StringTool.getTokensUsingDelim(range, ":"); - if ((bk.getName(ops[0])!=null || bk.getName(ops[1])!=null)) { - PtgMemFunc pmf= new PtgMemFunc(); - pmf.setParentRec(parent); - pmf.setLocation(address); - ptg= pmf; - } else if (sh1!=null) { - int[] rc= ExcelTools.getRowColFromString(ops[0]); // see if a wholerow/wholecol ref - if (!(ops[0].equals(ops[1])&& rc[0]!=-1 && rc[1]!=-1)) { - PtgArea3d pta= new PtgArea3d(); - pta.setParentRec(parent); - pta.setLocation(s); - ptg= pta; - } else { - ptg= new PtgRef3d(); - ((PtgRef3d)ptg).setPtgType(REFERENCE); - ptg.setParentRec(parent); - ((PtgRef)ptg).setUseReferenceTracker(false); - ((PtgRef3d)ptg).setLocation(s); - ((PtgRef)ptg).setUseReferenceTracker(true); - ((PtgRef3d)ptg).addToRefTracker(); - } - } else { - PtgArea pa= new PtgArea(); - pa.setParentRec(parent); - pa.setUseReferenceTracker(false); - pa.setLocation(s); - pa.setUseReferenceTracker(true); - pa.addToRefTracker(); - ptg= pa; - } - } else { // it's a single ref NOT a range e.g. Sheet1!A1 - if (sh1!=null) { - ptg= new PtgRef3d(); - ((PtgRef3d)ptg).setPtgType(REFERENCE); - ptg.setParentRec(parent); - ((PtgRef)ptg).setUseReferenceTracker(false); - ((PtgRef3d)ptg).setLocation(s); - ((PtgRef)ptg).setUseReferenceTracker(true); - ((PtgRef3d)ptg).addToRefTracker(); - } else { - PtgRef pr= new PtgRef(); - pr.setParentRec(parent); - pr.setUseReferenceTracker(false); - pr.setLocation(s); - pr.setUseReferenceTracker(true); - pr.addToRefTracker(); - ptg= pr; - } - } - return ptg; - } catch (Exception e) { // any error in parsing return a referr -- makes sense!!! - PtgRefErr3d pe3= new PtgRefErr3d(); - pe3.setParentRec(parent); - return pe3; - } - } - - /** - * set the location of this PtgRef - * @param rowcol int[] rowcol - * @param bRowRel true if row is relative (i.e. A1 not A$1) - * @param bColRel true if col is relative (i.e. A1 not $A1) - */ - public void setLocation(int[] rowcol, boolean bRowRel,boolean bColRel) { - locax = null; - cachedLocation = null; - if (record != null){ - if(useReferenceTracker)removeFromRefTracker(); - rw = rowcol[0]; - col = rowcol[1]; - fRwRel = bRowRel; - fColRel = bColRel; - this.updateRecord(); - if(useReferenceTracker) - addToRefTracker(); - }else{ - Logger.logWarn("PtgRef.setLocation() failed: NO record data: "+ rowcol.toString()); - } - } + if (loc[3] != null) + externalLink1 = Integer.valueOf(loc[3].substring(1, loc[3].length() - 1)).intValue(); + if (loc[4] != null) + externalLink2 = Integer.valueOf(loc[4].substring(1, loc[4].length() - 1)).intValue(); + if (useReferenceTracker) { + if (!getIsRefErr() && !this.getIsWholeCol() && !this.getIsWholeRow()) + addToRefTracker(); + } + } + /** - * Updates the record bytes so it can be pulled back out. - */ - public void updateRecord(){ - byte[] tmp = new byte[5]; - tmp[0] = record[0]; - byte[] brow = ByteTools.cLongToLEBytes(rw); - System.arraycopy(brow, 0, tmp, 1, 2); - if (fRwRel){ - col = (short)(0x8000 | col); - } - if (fColRel){ - col = (short)(0x4000 | col); - } - byte[] bcol = ByteTools.cLongToLEBytes(col); - if (col==-1){ // KSC: what excel expects - bcol[1]= 0; - } - System.arraycopy(bcol, 0, tmp, 3, 2); - - record = tmp; - if(parent_rec!=null) { - if(this.parent_rec instanceof Formula) - ((Formula)this.parent_rec).updateRecord(); - else if(this.parent_rec instanceof Name) - ((Name)this.parent_rec).updatePtgs(); - } - - col= (short) col & 0x3FFF; //get lower 14 bits which represent the actual column; + * Set the location of this PtgRef. This takes a location + * such as {1,2} + */ + public void setLocation(int[] rowcol) { + locax = null; + cachedLocation = null; + if (record != null) { + if (useReferenceTracker) + removeFromRefTracker(); + rw = rowcol[0]; + col = rowcol[1]; + fRwRel = true; // default + fColRel = true; + this.updateRecord(); + hashcode = getHashCode(); + if (useReferenceTracker) + addToRefTracker(); + } else { + Logger.logWarn("PtgRef.setLocation() failed: NO record data: " + rowcol.toString()); + } } - - public int getLength(){ + + /** + * given an address string, parse and assign to the appropriate PtgRef-type object + *
                #REF! 's return either PtgRefErr or PtgRefErr3d + *
                Ranges return either PtgArea or PtgArea3d + *
                Single addresses return either PtgRef or PtgRef3d + *
                NOTE: This method does not extract names embedded within the address string + * + * @param address + * @param parent parent record to assign the ptg to + * @return + */ + public static Ptg createPtgRefFromString(String address, XLSRecord parent) { + try { + String[] s = ExcelTools.stripSheetNameFromRange(address); + String sh1 = s[0]; + String range = s[1]; + Ptg ptg; + if (range == null || range.equals("#REF!") || (sh1 != null && sh1.equals("#REF"))) { + if (sh1 != null) { + PtgRefErr3d pe3 = new PtgRefErr3d(); + pe3.setParentRec(parent); + pe3.setLocation(s); + return pe3; + } else { + PtgRefErr pe = new PtgRefErr(); + pe.setParentRec(parent); + pe.setLocation(s); + return pe; + } + } + WorkBook bk = parent.getWorkBook(); + + + String sht = "((?:\\\\?+.)*?!)?+"; + String rangeMatch = "(.*(:).*){2,}?"; //matches 2 or more range ops (:'s) + String opMatch = "(.*([ ,]).*)+"; //matches union or isect op ( " " or ,) + String m = sht + "((" + opMatch + ")|(" + rangeMatch + "))"; + // is address a complex range?? + if (address.matches(m) || range.indexOf("(") > -1) { + //NOTE: this can be a MemFunc OR a MemArea -- + // PtgMemFunc= a NON-CONSTANT cell address, cell range address or cell range list + // Whenever one operand of the reference subexpression is a function, a defined name, a 3D + // reference, or an external reference (and no error occurs), a PtgMemFunc token is used. + // PtgMemArea= constant cell address, cell range address, or cell range list on the same sheet + PtgMemFunc pmf = new PtgMemFunc(); + pmf.setParentRec(parent); + pmf.setLocation(address); // TODO HANDLE FUNCTION MEMFUNCS ALA OFFSET(x,y,0):OFFSET(x,y,0) + ptg = pmf; + } else if (range.indexOf(":") > 0) { // it's a range, either PtgRef3d or PtgArea3d + String[] ops = StringTool.getTokensUsingDelim(range, ":"); + if ((bk.getName(ops[0]) != null || bk.getName(ops[1]) != null)) { + PtgMemFunc pmf = new PtgMemFunc(); + pmf.setParentRec(parent); + pmf.setLocation(address); + ptg = pmf; + } else if (sh1 != null) { + int[] rc = ExcelTools.getRowColFromString(ops[0]); // see if a wholerow/wholecol ref + if (!(ops[0].equals(ops[1]) && rc[0] != -1 && rc[1] != -1)) { + PtgArea3d pta = new PtgArea3d(); + pta.setParentRec(parent); + pta.setLocation(s); + ptg = pta; + } else { + ptg = new PtgRef3d(); + ((PtgRef3d) ptg).setPtgType(REFERENCE); + ptg.setParentRec(parent); + ((PtgRef) ptg).setUseReferenceTracker(false); + ((PtgRef3d) ptg).setLocation(s); + ((PtgRef) ptg).setUseReferenceTracker(true); + ((PtgRef3d) ptg).addToRefTracker(); + } + } else { + PtgArea pa = new PtgArea(); + pa.setParentRec(parent); + pa.setUseReferenceTracker(false); + pa.setLocation(s); + pa.setUseReferenceTracker(true); + pa.addToRefTracker(); + ptg = pa; + } + } else { // it's a single ref NOT a range e.g. Sheet1!A1 + if (sh1 != null) { + ptg = new PtgRef3d(); + ((PtgRef3d) ptg).setPtgType(REFERENCE); + ptg.setParentRec(parent); + ((PtgRef) ptg).setUseReferenceTracker(false); + ((PtgRef3d) ptg).setLocation(s); + ((PtgRef) ptg).setUseReferenceTracker(true); + ((PtgRef3d) ptg).addToRefTracker(); + } else { + PtgRef pr = new PtgRef(); + pr.setParentRec(parent); + pr.setUseReferenceTracker(false); + pr.setLocation(s); + pr.setUseReferenceTracker(true); + pr.addToRefTracker(); + ptg = pr; + } + } + return ptg; + } catch (Exception e) { // any error in parsing return a referr -- makes sense!!! + PtgRefErr3d pe3 = new PtgRefErr3d(); + pe3.setParentRec(parent); + return pe3; + } + } + + /** + * set the location of this PtgRef + * + * @param rowcol int[] rowcol + * @param bRowRel true if row is relative (i.e. A1 not A$1) + * @param bColRel true if col is relative (i.e. A1 not $A1) + */ + public void setLocation(int[] rowcol, boolean bRowRel, boolean bColRel) { + locax = null; + cachedLocation = null; + if (record != null) { + if (useReferenceTracker) removeFromRefTracker(); + rw = rowcol[0]; + col = rowcol[1]; + fRwRel = bRowRel; + fColRel = bColRel; + this.updateRecord(); + if (useReferenceTracker) + addToRefTracker(); + } else { + Logger.logWarn("PtgRef.setLocation() failed: NO record data: " + rowcol.toString()); + } + } + + /** + * Updates the record bytes so it can be pulled back out. + */ + public void updateRecord() { + byte[] tmp = new byte[5]; + tmp[0] = record[0]; + byte[] brow = ByteTools.cLongToLEBytes(rw); + System.arraycopy(brow, 0, tmp, 1, 2); + if (fRwRel) { + col = (short) (0x8000 | col); + } + if (fColRel) { + col = (short) (0x4000 | col); + } + byte[] bcol = ByteTools.cLongToLEBytes(col); + if (col == -1) { // KSC: what excel expects + bcol[1] = 0; + } + System.arraycopy(bcol, 0, tmp, 3, 2); + + record = tmp; + if (parent_rec != null) { + if (this.parent_rec instanceof Formula) + ((Formula) this.parent_rec).updateRecord(); + else if (this.parent_rec instanceof Name) + ((Name) this.parent_rec).updatePtgs(); + } + + col = (short) col & 0x3FFF; //get lower 14 bits which represent the actual column; + } + + public int getLength() { return PTG_REF_LENGTH; } - + /** * return truth of "reference is blank" + * * @return */ public boolean isBlank() { - getRefCells(); - return (refCell[0] == null||((XLSRecord)refCell[0]).isBlank);//getOpcode()==BLANK); + getRefCells(); + return (refCell[0] == null || ((XLSRecord) refCell[0]).isBlank);//getOpcode()==BLANK); } - + /** - returns the value of the cell refereced by the PtgRef - */ - public Object getValue(){ - getRefCells(); - Object retValue= null; - if (refCell[0] != null){ - if (refCell[0].getFormulaRec() != null){ - Formula f = (Formula)refCell[0].getFormulaRec(); - retValue =f.calculateFormula(); - return retValue; - }else{ - if (refCell[0].getDataType().equals("Float")){ - retValue =new Double(refCell[0].getDblVal()); - return retValue; - }else { - retValue = refCell[0].getInternalVal(); - return retValue; - } - } - } else{ - try { - if(!this.parent_rec.getSheet().getWindow2().getShowZeroValues()) - return null; - }catch(NullPointerException e) { - // assume zero, which the vast majority of cases are + * returns the value of the cell refereced by the PtgRef + */ + public Object getValue() { + getRefCells(); + Object retValue = null; + if (refCell[0] != null) { + if (refCell[0].getFormulaRec() != null) { + Formula f = refCell[0].getFormulaRec(); + retValue = f.calculateFormula(); + return retValue; + } else { + if (refCell[0].getDataType().equals("Float")) { + retValue = new Double(refCell[0].getDblVal()); + return retValue; + } else { + retValue = refCell[0].getInternalVal(); + return retValue; } - return Integer.valueOf(0); - } + } + } else { + try { + if (!this.parent_rec.getSheet().getWindow2().getShowZeroValues()) + return null; + } catch (NullPointerException e) { + // assume zero, which the vast majority of cases are + } + return Integer.valueOf(0); + } } /** * returns the value of the ptg formatted via the underlying cell's number format + * * @return String underlying cell value formatted via cell's format pattern */ public String getFormattedValue() { - getRefCells(); - Object retValue= null; - BiffRec cell = refCell[0]; - - if (cell != null){ - if (cell.getFormulaRec() != null){ - Formula f = (Formula)cell.getFormulaRec(); - retValue =f.calculateFormula(); - }else{ - if (cell.getDataType().equals("Float")){ - retValue =new Double(cell.getDblVal()); - }else { - retValue = cell.getInternalVal(); + getRefCells(); + Object retValue = null; + BiffRec cell = refCell[0]; + + if (cell != null) { + if (cell.getFormulaRec() != null) { + Formula f = cell.getFormulaRec(); + retValue = f.calculateFormula(); + } else { + if (cell.getDataType().equals("Float")) { + retValue = new Double(cell.getDblVal()); + } else { + retValue = cell.getInternalVal(); } - } - return CellFormatFactory.fromPatternString( - cell.getXfRec().getFormatPattern() ).format( retValue ); - } else{ + } + return CellFormatFactory.fromPatternString( + cell.getXfRec().getFormatPattern()).format(retValue); + } else { try { - if(!this.parent_rec.getSheet().getWindow2().getShowZeroValues()) + if (!this.parent_rec.getSheet().getWindow2().getShowZeroValues()) return ""; - }catch(NullPointerException e) { + } catch (NullPointerException e) { // assume zero, which the vast majority of cases are } return "0"; - } + } } - /** - * - * @return Returns the refCell. - */ - public BiffRec[] getRefCells() { - refCell = new BiffRec[1]; - try { - Boundsheet bs = null; - if((sheetname != null)&&(parent_rec!=null)){ - bs = this.parent_rec.getWorkBook().getWorkSheetByName(sheetname); - }else if(parent_rec!=null){ - bs = parent_rec.getSheet(); - } - refCell[0] = bs.getCell(rw,col); - }catch(Exception ex) {;} - return refCell; - } - - public boolean changeLocation(String newLoc, Formula f){ - locax= null; - Ptg ptg= null; - int z= -1; - try { - z= ExpressionParser.getExpressionLocByPtg(this, f.getExpression()); - ptg= (Ptg) f.getExpression().get(z); - } catch (Exception e) { - - } + + /** + * @return Returns the refCell. + */ + public BiffRec[] getRefCells() { + refCell = new BiffRec[1]; + try { + Boundsheet bs = null; + if ((sheetname != null) && (parent_rec != null)) { + bs = this.parent_rec.getWorkBook().getWorkSheetByName(sheetname); + } else if (parent_rec != null) { + bs = parent_rec.getSheet(); + } + refCell[0] = bs.getCell(rw, col); + } catch (Exception ex) { + } + return refCell; + } + + public boolean changeLocation(String newLoc, Formula f) { + locax = null; + Ptg ptg = null; + int z = -1; + try { + z = ExpressionParser.getExpressionLocByPtg(this, f.getExpression()); + ptg = (Ptg) f.getExpression().get(z); + } catch (Exception e) { + + } String unstripped = newLoc; - if(newLoc.indexOf("!")>-1)newLoc = newLoc.substring(newLoc.indexOf("!")+1); - if (unstripped.indexOf(":") > 0) { // then either PtgRef3d or PtgArea - if (unstripped.indexOf("!") > unstripped.indexOf(":")) { // than it's a PtgRef3d or PtgArea3d - if (unstripped.indexOf(":")!=unstripped.lastIndexOf(":")) { // it's a PtgArea3d ala Sheet1:Sheet3!A1:D1 - PtgArea3d pta3= new PtgArea3d(); - pta3.setLocation(unstripped); - ptg= pta3; - } else { // it's a PtgRef3d ala Sheet1!Sheet3:A1 - PtgRef3d prd= new PtgRef3d(); - prd.setParentRec(f); - prd.setLocation(unstripped); - ptg= prd; - } - // no sheet ref ... - } else { // it's a PtgArea3d (according to Excel's Ai recs !!) - PtgArea pta= new PtgArea(); - pta.setParentRec(f); - pta.setLocation(unstripped); - ptg= pta; - } - } else if (ptg!=null) { - // it's a single location - if (!unstripped.equals("")) { - ptg.setParentRec(f); - ptg.setLocation(unstripped); - }else { - ptg= new PtgRef3d(); - ptg.setParentRec(f); - } + if (newLoc.indexOf("!") > -1) newLoc = newLoc.substring(newLoc.indexOf("!") + 1); + if (unstripped.indexOf(":") > 0) { // then either PtgRef3d or PtgArea + if (unstripped.indexOf("!") > unstripped.indexOf(":")) { // than it's a PtgRef3d or PtgArea3d + if (unstripped.indexOf(":") != unstripped.lastIndexOf(":")) { // it's a PtgArea3d ala Sheet1:Sheet3!A1:D1 + PtgArea3d pta3 = new PtgArea3d(); + pta3.setLocation(unstripped); + ptg = pta3; + } else { // it's a PtgRef3d ala Sheet1!Sheet3:A1 + PtgRef3d prd = new PtgRef3d(); + prd.setParentRec(f); + prd.setLocation(unstripped); + ptg = prd; + } + // no sheet ref ... + } else { // it's a PtgArea3d (according to Excel's Ai recs !!) + PtgArea pta = new PtgArea(); + pta.setParentRec(f); + pta.setLocation(unstripped); + ptg = pta; + } + } else if (ptg != null) { + // it's a single location + if (!unstripped.equals("")) { + ptg.setParentRec(f); + ptg.setLocation(unstripped); + } else { + ptg = new PtgRef3d(); + ptg.setParentRec(f); + } } else { // ptg is null, create a new one - ptg= new PtgRef(); - ptg.setParentRec(f); - ptg.setLocation(unstripped); + ptg = new PtgRef(); + ptg.setParentRec(f); + ptg.setLocation(unstripped); } - if (z!=-1) - f.getExpression().set(z, ptg); // update expression with new Ptg - else - f.getExpression().add(ptg); - return true; - } - - public void setParentRec(XLSRecord f){ - parent_rec = f; - setRelativeRowCol(); // trap formulaRow, Col for relative PtgRefs + if (z != -1) + f.getExpression().set(z, ptg); // update expression with new Ptg + else + f.getExpression().add(ptg); + return true; + } + + public void setParentRec(XLSRecord f) { + parent_rec = f; + setRelativeRowCol(); // trap formulaRow, Col for relative PtgRefs } - /** removes this reference from the tracker... - * + /** + * removes this reference from the tracker... + *

                * used mostly when we've updated the ref and want * to re-register it. - * - * */ - public void removeFromRefTracker(){ - try{ - if(parent_rec!=null) { - parent_rec.getWorkBook().getRefTracker().removeCellRange(this); - if(parent_rec.getOpcode()==FORMULA)((Formula)parent_rec).setCachedValue(null); + public void removeFromRefTracker() { + try { + if (parent_rec != null) { + parent_rec.getWorkBook().getRefTracker().removeCellRange(this); + if (parent_rec.getOpcode() == FORMULA) ((Formula) parent_rec).setCachedValue(null); } - } catch(Exception ex){ - // no need to error here, sometimes this is called before its in Logger.logErr("PtgRef.removeFromRefTracker() failed.", ex); - } + } catch (Exception ex) { + // no need to error here, sometimes this is called before its in Logger.logErr("PtgRef.removeFromRefTracker() failed.", ex); + } } - - /** add this reference to the ReferenceTracker... this - * is crucial if we are to update this Ptg when cells + + /** + * add this reference to the ReferenceTracker... this + * is crucial if we are to update this Ptg when cells * are changed or added... */ - public void addToRefTracker(){ - //Logger.logInfo("Adding :" + this.toString() + " to tracker"); - try{ - if(parent_rec!=null) - parent_rec.getWorkBook().getRefTracker().addCellRange(this); - }catch(Exception ex){ - Logger.logErr("PtgRef.addToRefTracker() failed.", ex); - } - } - - /** - * update existing tracked ptg with new parent in reference tracker - * @param parent - */ - public void updateInRefTracker(XLSRecord parent) { - try { - if (parent != null) - parent.getWorkBook().getRefTracker().updateInRefTracker(this, parent); - } catch (Exception ex) { - Logger.logErr("updateInRefTracker() failed.", ex); - } - } + public void addToRefTracker() { + //Logger.logInfo("Adding :" + this.toString() + " to tracker"); + try { + if (parent_rec != null) + parent_rec.getWorkBook().getRefTracker().addCellRange(this); + } catch (Exception ex) { + Logger.logErr("PtgRef.addToRefTracker() failed.", ex); + } + } + + /** + * update existing tracked ptg with new parent in reference tracker + * + * @param parent + */ + public void updateInRefTracker(XLSRecord parent) { + try { + if (parent != null) + parent.getWorkBook().getRefTracker().updateInRefTracker(this, parent); + } catch (Exception ex) { + Logger.logErr("updateInRefTracker() failed.", ex); + } + } /** * set the formulaRow and formulaCol for relatively-referenced PtgRefs */ public void setRelativeRowCol() { - if (fRwRel || fColRel) { - short opc= 0; - if (parent_rec!=null) - opc= parent_rec.getOpcode(); - // protocol for shared formulas, conditional formatting, data validity and defined names only (type B cell addresses!) - if (opc==SHRFMLA || opc==DVAL) { - this.formulaRow= parent_rec.getRowNumber(); - this.formulaCol= parent_rec.getColNumber(); - } - } + if (fRwRel || fColRel) { + short opc = 0; + if (parent_rec != null) + opc = parent_rec.getOpcode(); + // protocol for shared formulas, conditional formatting, data validity and defined names only (type B cell addresses!) + if (opc == SHRFMLA || opc == DVAL) { + this.formulaRow = parent_rec.getRowNumber(); + this.formulaCol = parent_rec.getColNumber(); + } + } + } + + /** + * set this Ptg to an External Location - used when copying a sheet from another workbook + * + * @param f parent formula rec + */ + public void setExternalReference(String externalWorkbook) { + if (this instanceof PtgArea3d) { + PtgArea3d ptg = (PtgArea3d) this; + WorkBook b = parent_rec.getWorkBook(); + if (b == null) + b = parent_rec.getSheet().getWorkBook(); + short ixti = b.getExternSheet().addExternalSheetRef(externalWorkbook, ptg.getSheetName()); //20080714 KSC: May not reflect external reference! this.sheetname); + ptg.setIxti(ixti); + if (ptg.firstPtg != null) { // it's not a Ref3d + ptg.firstPtg.updateRecord(); + ptg.lastPtg.updateRecord(); + } + ptg.updateRecord(); + } else if (this instanceof PtgRef3d) { + WorkBook b = parent_rec.getWorkBook(); + PtgRef3d pr = (PtgRef3d) this; + if (b == null) + b = parent_rec.getSheet().getWorkBook(); + short ixti = b.getExternSheet().addExternalSheetRef(externalWorkbook, pr.getSheetName()); //20080714 KSC: May not reflect external reference! this.sheetname); + pr.setIxti(ixti); + } else { // TODO: convert to ref3d? + Logger.logWarn("PtgRef.setExternalReference: unable to convert ref"); + } } - /** - * set this Ptg to an External Location - used when copying a sheet from another workbook - * @param f parent formula rec - */ - public void setExternalReference(String externalWorkbook) { - if (this instanceof PtgArea3d) { - PtgArea3d ptg= (PtgArea3d) this; - WorkBook b = parent_rec.getWorkBook(); - if (b==null) - b= parent_rec.getSheet().getWorkBook(); - short ixti= b.getExternSheet().addExternalSheetRef(externalWorkbook, ptg.getSheetName()); //20080714 KSC: May not reflect external reference! this.sheetname); - ptg.setIxti(ixti); - if (ptg.firstPtg!=null) { // it's not a Ref3d - ptg.firstPtg.updateRecord(); - ptg.lastPtg.updateRecord(); - } - ptg.updateRecord(); - } else if (this instanceof PtgRef3d) { - WorkBook b = parent_rec.getWorkBook(); - PtgRef3d pr= (PtgRef3d) this; - if (b==null) - b= parent_rec.getSheet().getWorkBook(); - short ixti= b.getExternSheet().addExternalSheetRef(externalWorkbook, pr.getSheetName()); //20080714 KSC: May not reflect external reference! this.sheetname); - pr.setIxti(ixti); - } else { // TODO: convert to ref3d? - Logger.logWarn("PtgRef.setExternalReference: unable to convert ref"); - } - } - - public boolean isRowRel() { return fRwRel; } - public boolean isColRel() { return fColRel; } - /** - * sets the column to be relative (relative is true) or absolute (relative is false) - *
                absolute references do not shift upon column inserts or deletes - * @param boolean relative - */ - public void setColRel(boolean relative) { - if (fColRel!=relative) { - locax= null; - fColRel= relative; - updateRecord(); - } - } - - /** - * sets the row to be relative (relative is true) or absolute (relative is false) - *
                absolute references do not shift upon row inserts or deletes - * @param boolean relative - */ - public void setRowRel(boolean relative) { - if (fRwRel!=relative) { - locax= null; - fRwRel= relative; - updateRecord(); - } - } + + public boolean isRowRel() { + return fRwRel; + } + + public boolean isColRel() { + return fColRel; + } + + /** + * sets the column to be relative (relative is true) or absolute (relative is false) + *
                absolute references do not shift upon column inserts or deletes + * + * @param boolean relative + */ + public void setColRel(boolean relative) { + if (fColRel != relative) { + locax = null; + fColRel = relative; + updateRecord(); + } + } + + /** + * sets the row to be relative (relative is true) or absolute (relative is false) + *
                absolute references do not shift upon row inserts or deletes + * + * @param boolean relative + */ + public void setRowRel(boolean relative) { + if (fRwRel != relative) { + locax = null; + fRwRel = relative; + updateRecord(); + } + } + public void setArrayTypeRef() { - byte b =(byte) ((record[0] | 0x60) ); + byte b = (byte) ((record[0] | 0x60)); record[0] = b; } - - /** - * uniquely identifies a row/col - * to unencrypt: - * col= hashcode%maxcols - * row= hashcode/maxcols -1 - */ - protected long getHashCode() { - if (rw >= 0) - return col+ ((rw+1)*MAXCOLS); - else - return col+ ((MAXROWS-rw+1)*MAXCOLS); - } - public static long getHashCode(int row, int col) { - return col+ ((row+1)*MAXCOLS); - } - + + /** + * uniquely identifies a row/col + * to unencrypt: + * col= hashcode%maxcols + * row= hashcode/maxcols -1 + */ + protected long getHashCode() { + if (rw >= 0) + return col + ((rw + 1) * MAXCOLS); + else + return col + ((MAXROWS - rw + 1) * MAXCOLS); + } + + public static long getHashCode(int row, int col) { + return col + ((row + 1) * MAXCOLS); + } + /** * clear out object references in prep for closing workbook */ public void close() { - if (useReferenceTracker) removeFromRefTracker(); - useReferenceTracker= false; - super.close(); - if (refCell!=null && refCell.length > 0 && refCell[0]!=null) // clear out object references - ((XLSRecord) refCell[0]).close(); - refCell= null; + if (useReferenceTracker) removeFromRefTracker(); + useReferenceTracker = false; + super.close(); + if (refCell != null && refCell.length > 0 && refCell[0] != null) // clear out object references + ((XLSRecord) refCell[0]).close(); + refCell = null; } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.java index 9062ba0..89d87d6 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -30,58 +30,56 @@ /** - A BiffRec range spanning 3rd dimension of WorkSheets. - ` -

                -	offset  name        size    contents
                -	---    
                -	0       ixti       2        Index to Externsheet Sheet Record 
                -	2       row         2       The row
                -	4       grCol       2       The col, or the col offset (see next table)
                -    
                -	the low-order 8 bytes store the col numbers.  The 2 MSBs specify whether the row
                -	and col refs are relative or absolute.
                -    
                -	bits    mask        name    content
                -	---    
                -	15      8000h       fRwRel  =1 if the row is relative, 0 if absolute
                -	14      4000h       fColRel =1 if the col is relative, 0 if absolute
                -	13-8    3F00h       (reserved)
                -	7-0     00FFh       col     the col number or col offset (0-based)
                -    
                -	For 3D references, the tokens contain a negative EXTERNSHEET index, indicating a reference into the own workbook.
                -	The absolute value is the one-based index of the EXTERNSHEET record that contains the name of the first sheet. The
                -	tokens additionally contain absolute indexes of the first and last referenced sheet. These indexes are independent of the
                -	EXTERNSHEET record list. If the referenced sheets do not exist anymore, these indexes contain the value FFFFH (3D
                -	reference to a deleted sheet), and an EXTERNSHEET record with the special name <04H> (own document) is used.
                -	Each external reference contains the positive one-based index to an EXTERNSHEET record containing the URL of the
                -	external document and the name of the sheet used. The sheet index fields of the tokens are not used.
                -	
                + * A BiffRec range spanning 3rd dimension of WorkSheets.
                + * `
                + * 

                + * offset  name        size    contents
                + * ---
                + * 0       ixti       2        Index to Externsheet Sheet Record
                + * 2       row         2       The row
                + * 4       grCol       2       The col, or the col offset (see next table)
                + *
                + * the low-order 8 bytes store the col numbers.  The 2 MSBs specify whether the row
                + * and col refs are relative or absolute.
                + *
                + * bits    mask        name    content
                + * ---
                + * 15      8000h       fRwRel  =1 if the row is relative, 0 if absolute
                + * 14      4000h       fColRel =1 if the col is relative, 0 if absolute
                + * 13-8    3F00h       (reserved)
                + * 7-0     00FFh       col     the col number or col offset (0-based)
                + *
                + * For 3D references, the tokens contain a negative EXTERNSHEET index, indicating a reference into the own workbook.
                + * The absolute value is the one-based index of the EXTERNSHEET record that contains the name of the first sheet. The
                + * tokens additionally contain absolute indexes of the first and last referenced sheet. These indexes are independent of the
                + * EXTERNSHEET record list. If the referenced sheets do not exist anymore, these indexes contain the value FFFFH (3D
                + * reference to a deleted sheet), and an EXTERNSHEET record with the special name <04H> (own document) is used.
                + * Each external reference contains the positive one-based index to an EXTERNSHEET record containing the URL of the
                + * external document and the name of the sheet used. The sheet index fields of the tokens are not used.
                + *
                  * @see Ptg
                  * @see Formula
                + */
                +public class PtgRef3d extends PtgRef implements Ptg, IxtiListener {
                 
                -    
                -*/
                -public class PtgRef3d extends PtgRef implements Ptg, IxtiListener{
                -	
                     boolean quoted = false;
                     public short ixti;
                -    
                -	private static final long serialVersionUID = -441121385905948168L;
                 
                +    private static final long serialVersionUID = -441121385905948168L;
                +
                +
                +    public void setParentRec(XLSRecord r) {
                +        super.setParentRec(r);
                +    }
                 
                -	public void setParentRec(XLSRecord r){
                -		super.setParentRec(r);
                -	}
                -    
                     public void addListener() {
                         try {
                             getParentRec().getWorkBook().getExternSheet().addPtgListener(this);
                -        }catch(Exception e) {
                +        } catch (Exception e) {
                             // no need to output here.  NullPointer occurs when a ref has an invalid ixti, such as when a sheet was removed  Worksheet exception could never really happen.
                         }
                     }
                -    
                +
                 
                     /**
                      * @return Returns the ixti.
                @@ -89,228 +87,237 @@ public void addListener() {
                     public short getIxti() {    // only valid for 3d refs
                         return ixti;
                     }
                -    
                +
                     /**
                      * returns true if this PtgRef3d's ixti refers to an external sheet reference
                +     *
                      * @return
                      */
                     public boolean isExternalLink() {
                -    	try {
                -    		return (getParentRec().getWorkBook().getExternSheet().getIsExternalLink(ixti));
                -    	} catch (Exception e) {
                -    		return false;
                -    	}
                +        try {
                +            return (getParentRec().getWorkBook().getExternSheet().getIsExternalLink(ixti));
                +        } catch (Exception e) {
                +            return false;
                +        }
                +    }
                +
                +
                +    public int getLength() {
                +        return PTG_REF3D_LENGTH;
                     }
                -    
                -	
                -	public int getLength(){
                -		return PTG_REF3D_LENGTH;
                -	}
                -    
                -	public boolean getIsOperand(){return true;}
                -	public boolean getIsReference(){return true;}
                -	//true is relative, false is absolute
                +
                +    public boolean getIsOperand() {
                +        return true;
                +    }
                +
                +    public boolean getIsReference() {
                +        return true;
                +    }
                +    //true is relative, false is absolute
                 
                     /**
                -	  0x3A Reference class token: The reference address itself, independent of the cell contents.
                -	• 0x5A Value class token: A value (a constant, a function result, or one specific value from a dereferenced cell range).
                -	• 0x7A Array class token: An array of values (array of constant values, an array function result, or all values of a cell range).
                -	*/	
                -	public PtgRef3d(){
                -		record= new byte[PTG_REF3D_LENGTH];
                -		ptgId= 0x5A;  // id varies with type of token see above and setPtgType below  
                -		record[0]= ptgId; // ""
                +     * 0x3A Reference class token: The reference address itself, independent of the cell contents.
                +     * • 0x5A Value class token: A value (a constant, a function result, or one specific value from a dereferenced cell range).
                +     * • 0x7A Array class token: An array of values (array of constant values, an array function result, or all values of a cell range).
                +     */
                +    public PtgRef3d() {
                +        record = new byte[PTG_REF3D_LENGTH];
                +        ptgId = 0x5A;  // id varies with type of token see above and setPtgType below
                +        record[0] = ptgId; // ""
                         this.is3dRef = true;
                -	}
                -    
                +    }
                +
                     /**
                      * set the Ptg Id type to one of:
                      * VALUE, REFERENCE or Array
                -     * 
                The Ptg type is important for certain + *
                The Ptg type is important for certain * functions which require a specific type of operand */ - public void setPtgType(short type) { - switch (type) { - case VALUE: - ptgId= 0x5A; - break; - case REFERENCE: - ptgId= 0x3A; - break; - case Ptg.ARRAY: - ptgId= 0x7A; - break; - } - record[0]= ptgId; + public void setPtgType(short type) { + switch (type) { + case VALUE: + ptgId = 0x5A; + break; + case REFERENCE: + ptgId = 0x3A; + break; + case Ptg.ARRAY: + ptgId = 0x7A; + break; + } + record[0] = ptgId; } - - public PtgRef3d(boolean addToRefTracker){ + + public PtgRef3d(boolean addToRefTracker) { this.setUseReferenceTracker(addToRefTracker); - ptgId= 0x5A; // TODO: id varies with type of token see above - record[0]= ptgId; // "" + ptgId = 0x5A; // TODO: id varies with type of token see above + record[0] = ptgId; // "" this.is3dRef = true; - + } - - public PtgRef3d(String addr, short _ixti){ - this(); - setLocation(addr); + + public PtgRef3d(String addr, short _ixti) { + this(); + setLocation(addr); this.is3dRef = true; - } - - - - public void init(byte[] b){ - ptgId = b[0]; - record = b; - populateVals(); - } - - /** get the worksheet that this ref is on - for some reason this seems to be backwards in Ref3d - */ - public Boundsheet getSheet(WorkBook b) { - Boundsheet[] bsa = b.getExternSheet().getBoundSheets(ixti); - if (bsa!=null && bsa[0]==null) { // 20080303 KSC: catch error - // try harder... - if(parent_rec.getSheet()!=null){ - return parent_rec.getSheet(); // sheetless names belong to parent rec - }else { - if (b.getFactory().getDebugLevel()>1) // 20080925 KSC - Logger.logErr("PtgRef3d.getSheet: Unresolved External or Deleted Sheet Reference Found"); // [BUGTRACKER 1836] Claritas extenXLS22677.rec (Deleted Sheet/Named Range causes errant value in B3) - return null; //20080805 KSC: Don't just return the 1st sheet, may be wrong, deleted, etc! - } - } else if (bsa==null) - return null; - return bsa[0]; - } - - /** - * set Ptg to parsed location - * @param loc String[] sheet1, range, sheet2, exref1, exref2 - */ - public void setLocation(String[] s) { - if (useReferenceTracker && !getIsRefErr()) - this.getParentRec().getWorkBook().getRefTracker().removeCellRange(this); - sheetname= null; - if(s[0]!=null) { - sheetname= s[0]; + } + + + public void init(byte[] b) { + ptgId = b[0]; + record = b; + populateVals(); + } + + /** + * get the worksheet that this ref is on + * for some reason this seems to be backwards in Ref3d + */ + public Boundsheet getSheet(WorkBook b) { + Boundsheet[] bsa = b.getExternSheet().getBoundSheets(ixti); + if (bsa != null && bsa[0] == null) { // 20080303 KSC: catch error + // try harder... + if (parent_rec.getSheet() != null) { + return parent_rec.getSheet(); // sheetless names belong to parent rec + } else { + if (b.getFactory().getDebugLevel() > 1) // 20080925 KSC + Logger.logErr("PtgRef3d.getSheet: Unresolved External or Deleted Sheet Reference Found"); // [BUGTRACKER 1836] Claritas extenXLS22677.rec (Deleted Sheet/Named Range causes errant value in B3) + return null; //20080805 KSC: Don't just return the 1st sheet, may be wrong, deleted, etc! + } + } else if (bsa == null) + return null; + return bsa[0]; + } + + /** + * set Ptg to parsed location + * + * @param loc String[] sheet1, range, sheet2, exref1, exref2 + */ + public void setLocation(String[] s) { + if (useReferenceTracker && !getIsRefErr()) + this.getParentRec().getWorkBook().getRefTracker().removeCellRange(this); + sheetname = null; + if (s[0] != null) { + sheetname = s[0]; } else { - try { // if not provided, assume that parent rec sheet is correct - sheetname= this.getParentRec().getSheet().getSheetName(); - } catch (NullPointerException e) { ; } + try { // if not provided, assume that parent rec sheet is correct + sheetname = this.getParentRec().getSheet().getSheetName(); + } catch (NullPointerException e) { + } } - String loc= s[1]; - if (sheetname!=null) { - loc= sheetname + "!" + loc; // loc uses quoted vers of sheet - if (sheetname.indexOf("'")==0) { - sheetname = sheetname.substring(1, sheetname.length()-1); - quoted = true; - } - } - if (sheetname!=null) { - Externsheet xsht= null; - WorkBook b = parent_rec.getWorkBook(); - if (b==null) - b= parent_rec.getSheet().getWorkBook(); - try{ - - int boundnum = b.getWorkSheetByName(sheetname).getSheetNum(); - xsht = b.getExternSheet(); - try{ - int xloc = xsht.insertLocation(boundnum, boundnum); - setIxti((short)xloc); - }catch(Exception e){ - Logger.logWarn("PtgRef3d.setLocation could not update Externsheet:"+e.toString()); - } - }catch(WorkSheetNotFoundException e){ - try { - xsht = b.getExternSheet(); - int boundnum = xsht.getXtiReference(s[0], s[0]); - if (boundnum==-1){ // can't resolve - this.setIxti((short)xsht.insertLocation(boundnum, boundnum)); - }else { - this.setIxti((short)boundnum); - } - } catch (Exception ex) {} - } + String loc = s[1]; + if (sheetname != null) { + loc = sheetname + "!" + loc; // loc uses quoted vers of sheet + if (sheetname.indexOf("'") == 0) { + sheetname = sheetname.substring(1, sheetname.length() - 1); + quoted = true; + } + } + if (sheetname != null) { + Externsheet xsht = null; + WorkBook b = parent_rec.getWorkBook(); + if (b == null) + b = parent_rec.getSheet().getWorkBook(); + try { + + int boundnum = b.getWorkSheetByName(sheetname).getSheetNum(); + xsht = b.getExternSheet(); + try { + int xloc = xsht.insertLocation(boundnum, boundnum); + setIxti((short) xloc); + } catch (Exception e) { + Logger.logWarn("PtgRef3d.setLocation could not update Externsheet:" + e.toString()); + } + } catch (WorkSheetNotFoundException e) { + try { + xsht = b.getExternSheet(); + int boundnum = xsht.getXtiReference(s[0], s[0]); + if (boundnum == -1) { // can't resolve + this.setIxti((short) xsht.insertLocation(boundnum, boundnum)); + } else { + this.setIxti((short) boundnum); + } + } catch (Exception ex) { + } + } } super.setLocation(s); - } - - - /** - * Set Location can take either a local page address (ie A54) or - * a reference to a page and location(ie Sheet2!A22). It then changes - * the location reference of the Ptg. - * - * @see io.starter.formats.XLS.formulas.Ptg#setLocation(java.lang.String) - */ - public void setLocation(String address){ - String s[]= ExcelTools.stripSheetNameFromRange(address); - setLocation(s); - } - - /** - Throw this data into a ptgref's - */ - public void populateVals(){ - ixti = ByteTools.readShort(record[1], record[2]); - this.sheetname= this.getSheetName(); - - rw= readRow(record[3], record[4]); - short column = (short) ByteTools.readShort(record[5], record[6]); - if ((column & 0x8000) == 0x8000){ // is the Row relative? - fRwRel = true; - }else{ fRwRel = false;} - if ((column & 0x4000) == 0x4000){ // is the Column relative? - fColRel = true; - }else{ fColRel = false;} + } + + + /** + * Set Location can take either a local page address (ie A54) or + * a reference to a page and location(ie Sheet2!A22). It then changes + * the location reference of the Ptg. + * + * @see io.starter.formats.XLS.formulas.Ptg#setLocation(java.lang.String) + */ + public void setLocation(String address) { + String[] s = ExcelTools.stripSheetNameFromRange(address); + setLocation(s); + } + + /** + * Throw this data into a ptgref's + */ + public void populateVals() { + ixti = ByteTools.readShort(record[1], record[2]); + this.sheetname = this.getSheetName(); + + rw = readRow(record[3], record[4]); + short column = ByteTools.readShort(record[5], record[6]); + // is the Row relative? + fRwRel = (column & 0x8000) == 0x8000; + // is the Column relative? + fColRel = (column & 0x4000) == 0x4000; col = (short) (column & 0x3fff); setRelativeRowCol(); // set formulaRow/Col for relative references if necessary - this.getIntLocation(); // sets the wholeRow and/or wholeCol flag for certain refs - ((PtgRef)this).hashcode= super.getHashCode(); - } - - - /** Set the location of this PtgRef. This takes a location - such as "a14" - */ - public void setLocation(String address, short ix){ - ixti = ix; - String s[]= ExcelTools.stripSheetNameFromRange(address); - this.setLocation(s); - } - - public String toString(){ - String ret = ""; - try{ - ret = getLocation(); - if((ret.indexOf("!")==-1)&&(sheetname !=null)){ // prepend sheetname - if (sheetname.indexOf(' ')==-1 && sheetname.charAt(0)!='\'') // 20081211 KSC: Sheet names with spaces must have surrounding quotes - ret = sheetname + "!" + ret; - else - ret = "'" + sheetname + "'!" + ret; - } - }catch(Exception ex){ - Logger.logErr("PtgRef3d.toString() failed",ex); - } - return ret; - } - - public void setIxti(short ixf){ - if (ixti!=ixf) { - ixti = ixf; - // this seems to be only one byte... - if (record != null){ - record[1] = (byte)ixf; - } - updateRecord(); - } - } - + this.getIntLocation(); // sets the wholeRow and/or wholeCol flag for certain refs + this.hashcode = super.getHashCode(); + } + + + /** + * Set the location of this PtgRef. This takes a location + * such as "a14" + */ + public void setLocation(String address, short ix) { + ixti = ix; + String[] s = ExcelTools.stripSheetNameFromRange(address); + this.setLocation(s); + } + + public String toString() { + String ret = ""; + try { + ret = getLocation(); + if ((ret.indexOf("!") == -1) && (sheetname != null)) { // prepend sheetname + if (sheetname.indexOf(' ') == -1 && sheetname.charAt(0) != '\'') // 20081211 KSC: Sheet names with spaces must have surrounding quotes + ret = sheetname + "!" + ret; + else + ret = "'" + sheetname + "'!" + ret; + } + } catch (Exception ex) { + Logger.logErr("PtgRef3d.toString() failed", ex); + } + return ret; + } + + public void setIxti(short ixf) { + if (ixti != ixf) { + ixti = ixf; + // this seems to be only one byte... + if (record != null) { + record[1] = (byte) ixf; + } + updateRecord(); + } + } + /** * Change the sheet reference to the passed in boundsheet + * * @see io.starter.formats.XLS.formulas.PtgArea3d#setReferencedSheet(io.starter.formats.XLS.Boundsheet) */ public void setReferencedSheet(Boundsheet b) { @@ -318,132 +325,134 @@ public void setReferencedSheet(Boundsheet b) { Externsheet xsht = b.getWorkBook().getExternSheet(true); //TODO: add handling for multi-sheet reference. Already handled in externsheet try { - int xloc = xsht.insertLocation(boundnum, boundnum); - setIxti((short)xloc); - this.sheetname= null; // 20100218 KSC: RESET - this.getSheetName(); - locax= null; - }catch(WorkSheetNotFoundException e) { + int xloc = xsht.insertLocation(boundnum, boundnum); + setIxti((short) xloc); + this.sheetname = null; // 20100218 KSC: RESET + this.getSheetName(); + locax = null; + } catch (WorkSheetNotFoundException e) { Logger.logErr("Unable to set referenced sheet in PtgRef3d " + e); } } - /** - Returns the location of the Ptg as a string, including sheet name - */ - public String getLocation(){ - String ret= super.getLocation(); - if(ret.indexOf("!")==-1) { // doesn't have a sheet ref - // NOTE: Our tests error when PtgRefs have fully qualified range syntax - if (sheetname==null) - sheetname = this.getSheetName(); - if(this.sheetname!=null){ - if (sheetname.equals("#REF!")) - return sheetname + ret; - sheetname= qualifySheetname(sheetname); - return sheetname + "!" + ret; // PtgRef does not have ixti - } - } - return ret; - } - - /** Updates the record bytes so it can be pulled back out. - */ - public void updateRecord(){ - byte[] tmp= new byte[PTG_REF3D_LENGTH]; - tmp[0]= record[0]; - byte[] ix = ByteTools.shortToLEBytes(ixti); - System.arraycopy(ix, 0, tmp, 1, 2); + + /** + * Returns the location of the Ptg as a string, including sheet name + */ + public String getLocation() { + String ret = super.getLocation(); + if (ret.indexOf("!") == -1) { // doesn't have a sheet ref + // NOTE: Our tests error when PtgRefs have fully qualified range syntax + if (sheetname == null) + sheetname = this.getSheetName(); + if (this.sheetname != null) { + if (sheetname.equals("#REF!")) + return sheetname + ret; + sheetname = qualifySheetname(sheetname); + return sheetname + "!" + ret; // PtgRef does not have ixti + } + } + return ret; + } + + /** + * Updates the record bytes so it can be pulled back out. + */ + public void updateRecord() { + byte[] tmp = new byte[PTG_REF3D_LENGTH]; + tmp[0] = record[0]; + byte[] ix = ByteTools.shortToLEBytes(ixti); + System.arraycopy(ix, 0, tmp, 1, 2); byte[] brow = ByteTools.cLongToLEBytes(rw); System.arraycopy(brow, 0, tmp, 3, 2); - if (fRwRel){ - col = (short)(0x8000 | col); + if (fRwRel) { + col = (short) (0x8000 | col); + } + if (fColRel) { + col = (short) (0x4000 | col); } - if (fColRel){ - col = (short)(0x4000 | col); + byte[] bcol = ByteTools.cLongToLEBytes(col); + if (col == -1) { // KSC: what excel expects + bcol[1] = 0; } - byte[] bcol = ByteTools.cLongToLEBytes(col); - if (col==-1){ // KSC: what excel expects - bcol[1]= 0; - } - System.arraycopy(bcol, 0, tmp, 5, 2); - record = tmp; - if(parent_rec!=null) { - if(this.parent_rec instanceof Formula) - ((Formula)this.parent_rec).updateRecord(); - else if(this.parent_rec instanceof Name) - ((Name)this.parent_rec).updatePtgs(); + System.arraycopy(bcol, 0, tmp, 5, 2); + record = tmp; + if (parent_rec != null) { + if (this.parent_rec instanceof Formula) + ((Formula) this.parent_rec).updateRecord(); + else if (this.parent_rec instanceof Name) + ((Name) this.parent_rec).updatePtgs(); } - - col= (short) col & 0x3FFF; //get lower 14 bits which represent the actual column; - } - - - public Boundsheet getSheet(){ - if (parent_rec!=null) { - WorkBook wb = parent_rec.getWorkBook(); - if(wb!=null && wb.getExternSheet()!=null) { - Boundsheet[] bsa = wb.getExternSheet().getBoundSheets(this.ixti); - if (bsa==null || bsa[0]==null) {// 20080303 KSC: Catch Unresolved External refs - if (parent_rec instanceof Formula) - Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet in Formula " + parent_rec.getCellAddressWithSheet()); - else if (parent_rec instanceof Name) - Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet in Name " + ((Name)parent_rec).getName()); - else - Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet for " + parent_rec.getCellAddressWithSheet()); - return null; - } - return bsa[0]; - } - } - return null; + + col = (short) col & 0x3FFF; //get lower 14 bits which represent the actual column; } - + + + public Boundsheet getSheet() { + if (parent_rec != null) { + WorkBook wb = parent_rec.getWorkBook(); + if (wb != null && wb.getExternSheet() != null) { + Boundsheet[] bsa = wb.getExternSheet().getBoundSheets(this.ixti); + if (bsa == null || bsa[0] == null) {// 20080303 KSC: Catch Unresolved External refs + if (parent_rec instanceof Formula) + Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet in Formula " + parent_rec.getCellAddressWithSheet()); + else if (parent_rec instanceof Name) + Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet in Name " + ((Name) parent_rec).getName()); + else + Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet for " + parent_rec.getCellAddressWithSheet()); + return null; + } + return bsa[0]; + } + } + return null; + } + /** * return the sheet name for this 3d reference */ public String getSheetName() { - if (this.sheetname==null) { - if (parent_rec!=null) { - WorkBook wb = parent_rec.getWorkBook(); - if(wb!=null && wb.getExternSheet()!=null) { // 20080306 KSC: new way is to get sheet names rather than sheets as can be external refs - String[] sheets = wb.getExternSheet().getBoundSheetNames(this.ixti); - if (sheets!=null && sheets[0]!=null) - sheetname= sheets[0]; - } - } - } - return sheetname; + if (this.sheetname == null) { + if (parent_rec != null) { + WorkBook wb = parent_rec.getWorkBook(); + if (wb != null && wb.getExternSheet() != null) { // 20080306 KSC: new way is to get sheet names rather than sheets as can be external refs + String[] sheets = wb.getExternSheet().getBoundSheetNames(this.ixti); + if (sheets != null && sheets[0] != null) + sheetname = sheets[0]; + } + } + } + return sheetname; } - - - /** - * - * @return Returns the refCell. - */ - public BiffRec[] getRefCells() { - if(sheetname==null)sheetname = this.getSheetName(); - refCell = super.getRefCells(); - return refCell; - } - - /** - * PtgRef's have no sub-compnents - */ - public Ptg[] getComponents(){ - return null; // only one - } - - /** + + + /** + * @return Returns the refCell. + */ + public BiffRec[] getRefCells() { + if (sheetname == null) sheetname = this.getSheetName(); + refCell = super.getRefCells(); + return refCell; + } + + /** + * PtgRef's have no sub-compnents + */ + public Ptg[] getComponents() { + return null; // only one + } + + /** * return the ptg components for a certain column within a ptgArea() + * * @param colNum * @return all Ptg's within colNum */ - public Ptg[] getColComponents(int colNum){ + public Ptg[] getColComponents(int colNum) { FastAddVector v = new FastAddVector(); int[] x = this.getIntLocation(); - if(x[1] == colNum)v.add(this); + if (x[1] == colNum) v.add(this); PtgRef[] pref = new PtgRef[v.size()]; - v.toArray(pref); - return pref; + v.toArray(pref); + return pref; } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.java index 9373d43..d65e786 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,7 +23,6 @@ package io.starter.formats.XLS.formulas; - /* Specifies a cell reference that was changed to #REF! due to worksheet editing @@ -32,41 +31,49 @@ * @see Formula */ -public class PtgRefErr extends PtgRef implements Ptg -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2553420345077869256L; - public boolean getIsRefErr() { return true; } +public class PtgRefErr extends PtgRef implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2553420345077869256L; + + public boolean getIsRefErr() { + return true; + } + + public void init(byte[] b) { + record = b; + } + + // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... + public boolean getIsOperand() { + return true; + } - public void init(byte[] b){ - record = b; - } - // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... - public boolean getIsOperand(){ return true;} - /** return the human-readable String representation of - */ - public String getString(){ - return "#REF!"; //Invalid Reference Error"; - } - - public Object getValue(){ - return "#REF!"; + /** + * return the human-readable String representation of + */ + public String getString() { + return "#REF!"; //Invalid Reference Error"; } - - public int getLength(){ + + public Object getValue() { + return "#REF!"; + } + + public int getLength() { return PTG_REFERR_LENGTH; } - - public int[] getRowCol(){ - return new int[] {-1, -1}; - } - public String getLocation() { - return "#REF!"; + + public int[] getRowCol() { + return new int[]{-1, -1}; + } + + public String getLocation() { + return "#REF!"; + } + + public void setLocation(String[] s) { } - public void setLocation(String[] s) { - } - } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.java index 0e0033b..db2eb88 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,8 +23,6 @@ package io.starter.formats.XLS.formulas; import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - /* @@ -37,55 +35,62 @@ */ -public class PtgRefErr3d extends PtgRef3d implements Ptg{ - - +public class PtgRefErr3d extends PtgRef3d implements Ptg { + + private static final long serialVersionUID = 8691902605148033701L; - public boolean getIsRefErr() { return true; } + + public boolean getIsRefErr() { + return true; + } // IDs: 3C (R) 5C (V) 7C (A) public PtgRefErr3d() { - record = new byte[PTG_REFERR3D_LENGTH]; - record[0] = 0x3c; - // record[1]= index to REF entry in EXTERNSHEET - + record = new byte[PTG_REFERR3D_LENGTH]; + record[0] = 0x3c; + // record[1]= index to REF entry in EXTERNSHEET + + } + + public String getString() { + if (sheetname == null) + return "#REF!"; + return sheetname + "!#REF!"; } - public String getString(){ - if (sheetname==null) - return "#REF!"; - return sheetname+"!#REF!"; - } - public int getLength() { - return PTG_REFERR3D_LENGTH; + return PTG_REFERR3D_LENGTH; } - /* + + /* Throw this data into a ptgref's Ixti can reference sheets that don't exist, causing np error. As we don't perform any functions upon a PTGRef3D error, just swallow */ - public void populateVals(){ + public void populateVals() { ixti = ByteTools.readShort(record[1], record[2]); - if(ixti>0) this.sheetname= GenericPtg.qualifySheetname(this.getSheetName()); + if (ixti > 0) this.sheetname = GenericPtg.qualifySheetname(this.getSheetName()); } - - - public int[] getRowCol(){ - return new int[] {-1, -1}; - } - public Object getValue(){ - if (sheetname==null) - return "#REF!"; - return sheetname+"!#REF!"; - } - public String getLocation() { - if (sheetname==null) - return "#REF!"; - return sheetname+"!#REF!"; + + + public int[] getRowCol() { + return new int[]{-1, -1}; } - public void setLocation(String[] s) { - sheetname= GenericPtg.qualifySheetname(s[0]); - } - + + public Object getValue() { + if (sheetname == null) + return "#REF!"; + return sheetname + "!#REF!"; + } + + public String getLocation() { + if (sheetname == null) + return "#REF!"; + return sheetname + "!#REF!"; + } + + public void setLocation(String[] s) { + sheetname = GenericPtg.qualifySheetname(s[0]); + } + } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.java index 2caf4e9..7d1b9c6 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,340 +24,343 @@ import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.*; - -import io.starter.formats.XLS.Boundsheet; import io.starter.toolkit.Logger; -import io.starter.formats.XLS.XLSRecord; - /** - PtgRefN is a modified PtgRef that is for shared formulas. - Put here by M$ to make us miserable, - - it would have made much more sense to just use a PtgRef. -
                -    Offset      Name        Size    Contents
                -    ----------------------------------------------------
                -    0           rw          2       The row of the reference (so says the docs, but it is the row I think
                -    2           grbitCol    2       (see following table)
                -
                -    Only the low-order 14 bits specify the Col, the other bits specify
                -    relative vs absolute for both the col or the row.
                -
                -    Bits        Mask        Name    Contents
                -    -----------------------------------------------------
                -    15          8000h       fRwRel  =1 if row offset relative, 
                -                                    =0 if otherwise
                -    14          4000h       fColRel =1 if row offset relative,
                -                                    =0 if otherwise
                -    13-0        3FFFh       col     Ordinal column offset or number
                -    
                - - - This token contains the relative reference to a cell in the same sheet. - It stores relative components as signed offsets and is used in shared formulas, conditional formatting, and data validity. - + * PtgRefN is a modified PtgRef that is for shared formulas. + * Put here by M$ to make us miserable, + *

                + * it would have made much more sense to just use a PtgRef. + *

                + * Offset      Name        Size    Contents
                + * ----------------------------------------------------
                + * 0           rw          2       The row of the reference (so says the docs, but it is the row I think
                + * 2           grbitCol    2       (see following table)
                + *
                + * Only the low-order 14 bits specify the Col, the other bits specify
                + * relative vs absolute for both the col or the row.
                + *
                + * Bits        Mask        Name    Contents
                + * -----------------------------------------------------
                + * 15          8000h       fRwRel  =1 if row offset relative,
                + * =0 if otherwise
                + * 14          4000h       fColRel =1 if row offset relative,
                + * =0 if otherwise
                + * 13-0        3FFFh       col     Ordinal column offset or number
                + * 
                + *

                + *

                + * This token contains the relative reference to a cell in the same sheet. + * It stores relative components as signed offsets and is used in shared formulas, conditional formatting, and data validity. + * * @see WorkBook * @see Boundsheet * @see Dbcell * @see Row * @see Cell * @see XLSRecord - -*/ + */ public class PtgRefN extends PtgRef { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2652944516984815274L; - /* private int formulaRow; + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2652944516984815274L; + /* private int formulaRow; private int formulaCol;*/ - private int realRow; - private int realCol; - short column ; - private PtgArea parea= null; - - public boolean getIsReference(){return true;} - - public void init(byte[] b){ - ptgId = b[0]; - record = b; + private int realRow; + private int realCol; + short column; + private PtgArea parea = null; + + public boolean getIsReference() { + return true; + } + + public void init(byte[] b) { + ptgId = b[0]; + record = b; populateVals(); - hashcode= getHashCode(); // different from PtgRef calc + hashcode = getHashCode(); // different from PtgRef calc } - + public PtgRefN(boolean useReference) { this.setUseReferenceTracker(useReference); } + /** - Returns the location of the Ptg as a string (ie c4) - - TODO: look into this possible bug - - There is a problem here as the location will always be relative and incorrect. - this is deprecated and should be calling convertToPtgRef - */ - public String getLocation(){ + * Returns the location of the Ptg as a string (ie c4) + *

                + * TODO: look into this possible bug + *

                + * There is a problem here as the location will always be relative and incorrect. + * this is deprecated and should be calling convertToPtgRef + */ + public String getLocation() { //if (!populated){throw new FormulaNotFoundException("Cannot set location, no Formula Present");} realRow = rw; realCol = col; - if (fRwRel){ // the row is a relative location - realRow += (short) formulaRow; + if (fRwRel) { // the row is a relative location + realRow += (short) formulaRow; } - if (fColRel){ // the column is a relative location - realCol = (short) formulaCol;// - colNew; - if (realCol>=MAXCOLS)realCol-=MAXCOLS; + if (fColRel) { // the column is a relative location + realCol = (short) formulaCol;// - colNew; + if (realCol >= MAXCOLS) realCol -= MAXCOLS; } String s = ExcelTools.getAlphaVal(realCol); - String y = String.valueOf(realRow + 1); + String y = String.valueOf(realRow + 1); + + return (fColRel ? "" : "$") + s + (fRwRel ? "" : "$") + y; + } + + /* Set the location of this PtgRef. This takes a location + such as {1,2} + */ + public void setLocation(int[] rowcol) { + if (useReferenceTracker) this.removeFromRefTracker(); + if (record != null) { // 20090217 KSC: had some errors here, redid + if (fRwRel) + formulaRow = rowcol[0]; + else + rw = rowcol[0]; + if (fColRel) { + formulaCol = rowcol[1]; + } else { + col = rowcol[1]; + } + this.updateRecord(); + init(record); + } else { + Logger.logWarn("PtgRefN.setLocation() failed: NO record data: " + rowcol.toString()); + } + hashcode = getHashCode(); + if (useReferenceTracker) + this.addToRefTracker(); - return (fColRel?"":"$") + s + (fRwRel?"":"$") + y; } - /* Set the location of this PtgRef. This takes a location - such as {1,2} - */ - public void setLocation(int[] rowcol){ - if(useReferenceTracker) this.removeFromRefTracker(); - if (record != null){ // 20090217 KSC: had some errors here, redid - if (fRwRel) - formulaRow= rowcol[0]; - else - rw= rowcol[0]; - if (fColRel) { - formulaCol= rowcol[1]; - } else { - col = rowcol[1]; - } - this.updateRecord(); - init(record); - }else{ - Logger.logWarn("PtgRefN.setLocation() failed: NO record data: "+ rowcol.toString()); - } - hashcode= getHashCode(); - if(useReferenceTracker) - this.addToRefTracker(); - - } - - /** returns the row/col ints for the ref - * adjusted for the host cell - * - * @return - */ - public int[] getRowCol(){ + /** + * returns the row/col ints for the ref + * adjusted for the host cell + * + * @return + */ + public int[] getRowCol() { realRow = rw; realCol = col; - if (fRwRel){ // the row is a relative location - realRow += (short) formulaRow; + if (fRwRel) { // the row is a relative location + realRow += (short) formulaRow; } - if (fColRel){ // the column is a relative location - realCol = (short) formulaCol;// - colNew; - if (realCol>=MAXCOLS)realCol-=MAXCOLS; + if (fColRel) { // the column is a relative location + realCol = (short) formulaCol;// - colNew; + if (realCol >= MAXCOLS) realCol -= MAXCOLS; } - int[] ret = {realRow,realCol}; - return ret; - } - - /** - * this - * @return - */ - public int[] getRealRowCol() { - return new int[] {rw, col}; - } + int[] ret = {realRow, realCol}; + return ret; + } + + /** + * this + * + * @return + */ + public int[] getRealRowCol() { + return new int[]{rw, col}; + } + /* Set the location of this PtgRef. This takes a location such as "a14" TODO: check why this is overridden / reversed 12/02 -jm */ - public void setLocation(String address){ - if (record != null){ - // 20080215 KSC: replace address stripping - String s[]= ExcelTools.stripSheetNameFromRange(address); - address= s[1]; //stripped of sheet name, if any ... - - int[] res = ExcelTools.getRowColFromString(address); - // 20060301 KSC: Keep relativity - if (fRwRel) { - rw += formulaRow-res[0]; - if(rw<0) // handle row shifting issues - rw=0; - formulaRow = res[0]; - } else { - rw = res[0]; - } - if (fColRel) { - col += formulaCol-res[1]; - formulaCol = res[1]; - } else { - col = res[1]; - } - - updateRecord(); - init(record); - // 20090325 KSC: trap OOXML external reference link, if any - if (s[3]!=null) - externalLink1= Integer.valueOf(s[3].substring(1, s[3].length()-1)).intValue(); - if (s[4]!=null) - externalLink2= Integer.valueOf(s[4].substring(1, s[4].length()-1)).intValue(); - - }else{ - Logger.logWarn("PtgRefN.setLocation() failed: NO record data: "+ address.toString()); + public void setLocation(String address) { + if (record != null) { + // 20080215 KSC: replace address stripping + String[] s = ExcelTools.stripSheetNameFromRange(address); + address = s[1]; //stripped of sheet name, if any ... + + int[] res = ExcelTools.getRowColFromString(address); + // 20060301 KSC: Keep relativity + if (fRwRel) { + rw += formulaRow - res[0]; + if (rw < 0) // handle row shifting issues + rw = 0; + formulaRow = res[0]; + } else { + rw = res[0]; + } + if (fColRel) { + col += formulaCol - res[1]; + formulaCol = res[1]; + } else { + col = res[1]; + } + + updateRecord(); + init(record); + // 20090325 KSC: trap OOXML external reference link, if any + if (s[3] != null) + externalLink1 = Integer.valueOf(s[3].substring(1, s[3].length() - 1)).intValue(); + if (s[4] != null) + externalLink2 = Integer.valueOf(s[4].substring(1, s[4].length() - 1)).intValue(); + + } else { + Logger.logWarn("PtgRefN.setLocation() failed: NO record data: " + address); } } - + /** * Convert this PtgRefN to a PtgRef based on the offsets included in the PtgExp & * if this uses relative or absolute offsets + * * @param pxp * @return */ - public PtgRef convertToPtgRef(XLSRecord r/*PtgExp pxp*/){ - //XLSRecord r = (XLSRecord)pxp.getParentRec(); - int[] i = new int[2]; + public PtgRef convertToPtgRef(XLSRecord r/*PtgExp pxp*/) { + //XLSRecord r = (XLSRecord)pxp.getParentRec(); + int[] i = new int[2]; if (fRwRel) { i[0] = r.getRowNumber() + rw; - }else { + } else { i[0] = rw; } if (fColRel) { i[1] = r.getColNumber() + col; - }else { + } else { i[1] = col; } - - if (i[1]>=MAXCOLS_BIFF8 && - !r.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[1]-=MAXCOLS_BIFF8; - - PtgRef prf = new PtgRef(i, r, false); + + if (i[1] >= MAXCOLS_BIFF8 && + !r.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) + i[1] -= MAXCOLS_BIFF8; + + PtgRef prf = new PtgRef(i, r, false); // String s = ExcelTools.formatLocation(i, fRwRel, fColRel); // PtgRef prf = new PtgRef(s, r /*pxp.getParentRec()*/, false); //false); - - return prf; - } - - /** - * /* - * (try to) return int[] array containing the row/column - * referenced by this PtgRefN. - * - * - * - * @see io.starter.formats.XLS.formulas.PtgRef#getIntLocation() - * @returns int[] row/col absolute (non-offset) location - */ - public int[] getIntLocation(){ - - int rowNew = rw; - int colNew = col; - if (fRwRel){ // the row is a relative location - rowNew+= formulaRow; - } - if (fColRel){ // the column is a relative location - colNew += formulaCol; - } - if (colNew>=MAXCOLS)colNew-=MAXCOLS; // 20070205 KSC: Added 20080102 KSC: added = - - int[] returning = new int[2]; - returning[0] = rowNew; - returning[1] = colNew; - return returning; - } - - /** - * set formula row - * @param r int new row - */ - // 20060301 KSC: access to formula row/col - public void setFormulaRow(int r) { - formulaRow= r; - } - - /** - * set formula col - * @param c int new col - */ - // 20060301 KSC: access to formula row/col - public void setFormulaCol(int c) { - formulaCol= c; - } - - - /** - * custom RefTracker usage: uses entire range covered by all shared formulas - */ - public PtgArea getArea() { - Shrfmla sh= (Shrfmla) this.getParentRec(); - int[] i = new int[4]; - if (fRwRel) { - i[0] = sh.getFirstRow() + rw; - }else { - i[0] = rw; - } - if (fColRel) { - i[1] = sh.getFirstCol() + col; - }else { - i[1] = col; - } - if (fRwRel) { - i[2] = sh.getLastRow() + rw; - }else { - i[2] = rw; - } - if (fColRel) { - i[3] = sh.getLastCol() + col; - }else { - i[3] = col; - } - - if (i[1]>=MAXCOLS_BIFF8 && - !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[1]-=MAXCOLS_BIFF8; - if (i[3]>=MAXCOLS_BIFF8 && - !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[3]-=MAXCOLS_BIFF8; - - - PtgArea parea = new PtgArea(i, (XLSRecord)sh, true); - return parea; - } - - /** - * add "true" area to reference tracker i.e. entire range referenced by all shared formula members - */ - public void addToRefTracker() { - int iParent= this.getParentRec().getOpcode(); - if (iParent==XLSConstants.SHRFMLA) { - // KSC: TESTING - local ptgarea gets finalized and messes up ref. tracker on multiple usages without close + + return prf; + } + + /** + * /* + * (try to) return int[] array containing the row/column + * referenced by this PtgRefN. + * + * @returns int[] row/col absolute (non-offset) location + * @see io.starter.formats.XLS.formulas.PtgRef#getIntLocation() + */ + public int[] getIntLocation() { + + int rowNew = rw; + int colNew = col; + if (fRwRel) { // the row is a relative location + rowNew += formulaRow; + } + if (fColRel) { // the column is a relative location + colNew += formulaCol; + } + if (colNew >= MAXCOLS) colNew -= MAXCOLS; // 20070205 KSC: Added 20080102 KSC: added = + + int[] returning = new int[2]; + returning[0] = rowNew; + returning[1] = colNew; + return returning; + } + + /** + * set formula row + * + * @param r int new row + */ + // 20060301 KSC: access to formula row/col + public void setFormulaRow(int r) { + formulaRow = r; + } + + /** + * set formula col + * + * @param c int new col + */ + // 20060301 KSC: access to formula row/col + public void setFormulaCol(int c) { + formulaCol = c; + } + + + /** + * custom RefTracker usage: uses entire range covered by all shared formulas + */ + public PtgArea getArea() { + Shrfmla sh = (Shrfmla) this.getParentRec(); + int[] i = new int[4]; + if (fRwRel) { + i[0] = sh.getFirstRow() + rw; + } else { + i[0] = rw; + } + if (fColRel) { + i[1] = sh.getFirstCol() + col; + } else { + i[1] = col; + } + if (fRwRel) { + i[2] = sh.getLastRow() + rw; + } else { + i[2] = rw; + } + if (fColRel) { + i[3] = sh.getLastCol() + col; + } else { + i[3] = col; + } + + if (i[1] >= MAXCOLS_BIFF8 && + !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) + i[1] -= MAXCOLS_BIFF8; + if (i[3] >= MAXCOLS_BIFF8 && + !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) + i[3] -= MAXCOLS_BIFF8; + + + PtgArea parea = new PtgArea(i, sh, true); + return parea; + } + + /** + * add "true" area to reference tracker i.e. entire range referenced by all shared formula members + */ + public void addToRefTracker() { + int iParent = this.getParentRec().getOpcode(); + if (iParent == XLSConstants.SHRFMLA) { + // KSC: TESTING - local ptgarea gets finalized and messes up ref. tracker on multiple usages without close // getArea(); // parea.addToRefTracker(); - PtgArea parea= getArea(); // otherwise is finalized if local var --- but take out ptgarea finalize for now - parea.addToRefTracker(); - } - } - - /** - * remove "true" area from reference tracker i.e. entire range referenced by all shared formula members - */ - public void removeFromRefTracker(){ - int iParent= this.getParentRec().getOpcode(); - if (iParent==XLSConstants.SHRFMLA) { - PtgArea parea= getArea(); // otherwise is finalized if local var --- but take out ptgarea finalize for now - if (parea!=null) { - parea.removeFromRefTracker(); + PtgArea parea = getArea(); // otherwise is finalized if local var --- but take out ptgarea finalize for now + parea.addToRefTracker(); + } + } + + /** + * remove "true" area from reference tracker i.e. entire range referenced by all shared formula members + */ + public void removeFromRefTracker() { + int iParent = this.getParentRec().getOpcode(); + if (iParent == XLSConstants.SHRFMLA) { + PtgArea parea = getArea(); // otherwise is finalized if local var --- but take out ptgarea finalize for now + if (parea != null) { + parea.removeFromRefTracker(); // parea.close(); - } - // parea= null; - } - } - public void close() { - //removeFromRefTracker(); - if (parea!=null) - parea.close(); - parea= null; - } + } + // parea= null; + } + } + + public void close() { + //removeFromRefTracker(); + if (parea != null) + parea.close(); + parea = null; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgStr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgStr.java index 34cce79..b5d2aba 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgStr.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgStr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,149 +22,153 @@ */ package io.starter.formats.XLS.formulas; -import java.io.UnsupportedEncodingException; - import io.starter.toolkit.ByteTools; import io.starter.toolkit.Logger; +import java.io.UnsupportedEncodingException; + /** - PTG that stores a unicode string - - Offset Name Size Contents - ------------------------------------ - 0 cch 1 Length of the string - 1 rgch var The string - - ** I think the string includes a grbit itself, see UnicodeString. Internationalization issues - may exist here!!! - - -- Yes, it did include grbit, all handled now. - + * PTG that stores a unicode string + *

                + * Offset Name Size Contents + * ------------------------------------ + * 0 cch 1 Length of the string + * 1 rgch var The string + *

                + * * I think the string includes a grbit itself, see UnicodeString. Internationalization issues + * may exist here!!! + *

                + * -- Yes, it did include grbit, all handled now. + * * @see Ptg * @see Formula + */ +public class PtgStr extends GenericPtg implements Ptg { - -*/ -public class PtgStr extends GenericPtg implements Ptg -{ + /** + * serialVersionUID + */ + private static final long serialVersionUID = -1427051673654768400L; - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1427051673654768400L; + public boolean getIsOperand() { + return true; + } - public boolean getIsOperand(){return true;} short cch; byte grbit; boolean negativeCch = false; - + public String getString() { - String strVal = null; - try{ - if ((grbit & 0x1) == 0x1){ // hits on Japanese strings in formulas - byte[] barr = new byte[cch*2]; - System.arraycopy(record, 3, barr, 0, cch*2); - strVal = new String(barr, UNICODEENCODING); - }else{ - byte[] barr = new byte[cch]; - System.arraycopy(record, 3, barr, 0, cch); - strVal = new String(barr, DEFAULTENCODING); - } - }catch(Exception e){ - byte[] barr = new byte[cch]; - System.arraycopy(record, 3, barr, 0, cch); - strVal = new String(barr); - } - return strVal; + String strVal = null; + try { + if ((grbit & 0x1) == 0x1) { // hits on Japanese strings in formulas + byte[] barr = new byte[cch * 2]; + System.arraycopy(record, 3, barr, 0, cch * 2); + strVal = new String(barr, UNICODEENCODING); + } else { + byte[] barr = new byte[cch]; + System.arraycopy(record, 3, barr, 0, cch); + strVal = new String(barr, DEFAULTENCODING); + } + } catch (Exception e) { + byte[] barr = new byte[cch]; + System.arraycopy(record, 3, barr, 0, cch); + strVal = new String(barr); + } + return strVal; } public String toString() { - return getString(); + return getString(); } - - /** return the human-readable String representation of - */ - public String getTextString(){ + + /** + * return the human-readable String representation of + */ + public String getTextString() { try { - Double d = new Double( getString()); - }catch (NumberFormatException e) { + Double d = new Double(getString()); + } catch (NumberFormatException e) { } - return "\"" + getString() + "\""; + return "\"" + getString() + "\""; } - - public Object getValue(){ - return getString(); + + public Object getValue() { + return getString(); } - - public PtgStr(){ + + public PtgStr() { // default constructor } - - public PtgStr(String s){ + + public PtgStr(String s) { ptgId = 0x17; - setVal(s); + setVal(s); } - - public void init(byte[] b){ + + public void init(byte[] b) { grbit = b[2]; - cch = (short)(b[1] & 0xff); // this is the cch - ptgId = b[0]; - record = b; + cch = (short) (b[1] & 0xff); // this is the cch + ptgId = b[0]; + record = b; this.populateVals(); } - - /** Constructer to create these on the fly, this is needed - for value storage in calculations of formulas. - */ - private void populateVals(){ - // no longer does anything, no String value stored + /** + * Constructer to create these on the fly, this is needed + * for value storage in calculations of formulas. + */ + private void populateVals() { + // no longer does anything, no String value stored } - - public String getVal(){ - return getString(); + + public String getVal() { + return getString(); } - + private String tempstr = null; - public void setVal(String s){ + + public void setVal(String s) { tempstr = s; this.updateRecord(); } - - public void updateRecord(){ - String ts = tempstr; - if(ts == null) - return; - + + public void updateRecord() { + String ts = tempstr; + if (ts == null) + return; + if (ByteTools.isUnicode(ts)) { - grbit = (byte)(grbit | 0x1); + grbit = (byte) (grbit | 0x1); } - try{ + try { byte[] strbytes = null; - if ((grbit & 0x1) == 0x1){ - strbytes = ts.getBytes(UNICODEENCODING); - }else{ - strbytes = ts.getBytes(DEFAULTENCODING); + if ((grbit & 0x1) == 0x1) { + strbytes = ts.getBytes(UNICODEENCODING); + } else { + strbytes = ts.getBytes(DEFAULTENCODING); } - - short strbytelen = (short)strbytes.length; + + short strbytelen = (short) strbytes.length; cch = strbytelen; - if ((grbit & 0x1) == 0x1)cch = (short)(strbytelen/2); + if ((grbit & 0x1) == 0x1) cch = (short) (strbytelen / 2); //cch = (short)( getString().length() + 3); record = new byte[strbytelen + 3]; //record = new byte[(cch*times) + 3]; record[0] = 0x17; - record[1] = (byte)cch; + record[1] = (byte) cch; record[2] = grbit; System.arraycopy(strbytes, 0, record, 3, strbytelen); - - }catch(UnsupportedEncodingException e){Logger.logInfo("decoding formula string failed: " + e);} + + } catch (UnsupportedEncodingException e) { + Logger.logInfo("decoding formula string failed: " + e); + } } - - public int getLength(){ + + public int getLength() { return record.length; } - - + + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgSub.java b/src/main/java/io/starter/formats/XLS/formulas/PtgSub.java index 2c2cb05..4141816 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgSub.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgSub.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,112 +22,122 @@ */ package io.starter.formats.XLS.formulas; -import java.lang.reflect.Array; - import io.starter.toolkit.Logger; +import java.lang.reflect.Array; + /** - Ptg that indicates substitution (ie minus) - + * Ptg that indicates substitution (ie minus) + * * @see Ptg * @see Formula + */ +public class PtgSub extends GenericPtg implements Ptg { - -*/ -public class PtgSub extends GenericPtg implements Ptg -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3252464873846778499L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -3252464873846778499L; - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsBinaryOperator(){return true;} - - public PtgSub(){ - ptgId = 0x4; - record = new byte[1]; - record[0] = 0x4; - } - /** return the human-readable String representation of - */ - public String getString(){ + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsBinaryOperator() { + return true; + } + + public PtgSub() { + ptgId = 0x4; + record = new byte[1]; + record[0] = 0x4; + } + + /** + * return the human-readable String representation of + */ + public String getString() { return "-"; - } - public String toString() { return getString(); } - - public int getLength(){ + } + + public String toString() { + return getString(); + } + + public int getLength() { return PTG_SUB_LENGTH; } - - /** - * Operator specific calculate method, this one subtracts one value from another - */ - public Ptg calculatePtg(Ptg[] form){ - try { - Object[] o= super.getValuesFromPtgs(form); - if (o==null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (!o[0].getClass().isArray()) { - if (o.length != 2){ - Logger.logWarn("calculating formula failed, wrong number of values in PtgSub"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; - } - // blank handling: - if (form[0].isBlank()) o[0]=new Double(0); - if (form[1].isBlank()) o[1]=new Double(0); - // the following should only return #VALUE! if ??? - if (!(o[0] instanceof Double && o[1] instanceof Double)){ - if(this.parent_rec == null){ - return new PtgErr(PtgErr.ERROR_VALUE); - }else if(this.parent_rec.getSheet().getWindow2().getShowZeroValues()){ - return new PtgInt(0); - }else{ - return new PtgStr(""); - } - } - double returnVal = (((Double)o[0]).doubleValue() - ((Double)o[1]).doubleValue()); - PtgNumber n = new PtgNumber(returnVal); - return n; - } else { // handle array fomulas - String retArry= ""; - int nArrays= java.lang.reflect.Array.getLength(o); - if (nArrays!=2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals= java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i= 0; i < nArrays-1; i+=2) { - Object secondOp= null; - boolean comparitorIsArray= o[i+1].getClass().isArray(); - if (!comparitorIsArray) secondOp= o[i+1]; - for (int j= 0; j < nVals; j++) { - Object firstOp= Array.get(o[i],j); // first array index j - if (comparitorIsArray) - secondOp= Array.get(o[i+1],j); // second array index j - if (!(firstOp instanceof Double && secondOp instanceof Double)){ - if(this.parent_rec == null){ - return new PtgErr(PtgErr.ERROR_VALUE); - }else if(this.parent_rec.getSheet().getWindow2().getShowZeroValues()){ - return new PtgInt(0); - }else{ - return new PtgStr(""); - } - } // 20081203 KSC: handle error's ala Excel - double retVal= ((Double)firstOp).doubleValue()-((Double)secondOp).doubleValue(); - retArry= retArry + retVal + ","; - } - } - retArry= "{" + retArry.substring(0, retArry.length()-1) + "}"; - PtgArray pa= new PtgArray(); - pa.setVal(retArry); - return pa; - } - }catch(NumberFormatException e) { + + /** + * Operator specific calculate method, this one subtracts one value from another + */ + public Ptg calculatePtg(Ptg[] form) { + try { + Object[] o = getValuesFromPtgs(form); + if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) + if (!o[0].getClass().isArray()) { + if (o.length != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgSub"); + return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; + } + // blank handling: + if (form[0].isBlank()) o[0] = new Double(0); + if (form[1].isBlank()) o[1] = new Double(0); + // the following should only return #VALUE! if ??? + if (!(o[0] instanceof Double && o[1] instanceof Double)) { + if (this.parent_rec == null) { + return new PtgErr(PtgErr.ERROR_VALUE); + } else if (this.parent_rec.getSheet().getWindow2().getShowZeroValues()) { + return new PtgInt(0); + } else { + return new PtgStr(""); + } + } + double returnVal = (((Double) o[0]).doubleValue() - ((Double) o[1]).doubleValue()); + PtgNumber n = new PtgNumber(returnVal); + return n; + } else { // handle array fomulas + String retArry = ""; + int nArrays = java.lang.reflect.Array.getLength(o); + if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); + int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays + for (int i = 0; i < nArrays - 1; i += 2) { + Object secondOp = null; + boolean comparitorIsArray = o[i + 1].getClass().isArray(); + if (!comparitorIsArray) secondOp = o[i + 1]; + for (int j = 0; j < nVals; j++) { + Object firstOp = Array.get(o[i], j); // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j); // second array index j + if (!(firstOp instanceof Double && secondOp instanceof Double)) { + if (this.parent_rec == null) { + return new PtgErr(PtgErr.ERROR_VALUE); + } else if (this.parent_rec.getSheet().getWindow2().getShowZeroValues()) { + return new PtgInt(0); + } else { + return new PtgStr(""); + } + } // 20081203 KSC: handle error's ala Excel + double retVal = ((Double) firstOp).doubleValue() - ((Double) secondOp).doubleValue(); + retArry = retArry + retVal + ","; + } + } + retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; + PtgArray pa = new PtgArray(); + pa.setVal(retArry); + return pa; + } + } catch (NumberFormatException e) { PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); return perr; - }catch(Exception e) { // 20081125 KSC: handle error ala Excel - // Logger.logErr("PtgSub failed:" + e); - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + } catch (Exception e) { // 20081125 KSC: handle error ala Excel + // Logger.logErr("PtgSub failed:" + e); + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); return perr; } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.java b/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.java index 8b87ebb..04de666 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -35,57 +35,70 @@ /** * */ -public class PtgUMinus extends GenericPtg implements Ptg{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8448419489380791823L; - public PtgUMinus() { // 20060504 KSC: Added to fill record bytes upon creation - ptgId = 0x13; - record = new byte[1]; - record[0] = 0x13; - } - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsUnaryOperator(){return true;} - - /** return the human-readable String representation of - */ - public int getLength(){ - return PTG_UMINUS_LENGTH; - } - - /** Operator specific calculate method, this one returns a single value sent to it. - - */ - public Ptg calculatePtg(Ptg[] form){ - if (form.length != 1){ - Logger.logWarn("PtgMinus calculating formula failed, wrong number of values."); - return new PtgErr(PtgErr.ERROR_VALUE); - } - try { - Ptg p = form[0]; - Ptg ret= null; - if (p instanceof PtgInt) { - int val = p.getIntVal(); - val *=-1; - ret = new PtgInt(val); - } else { - double val = p.getDoubleVal(); - val *=-1; - ret = new PtgNumber(val); - } - return ret; - }catch(Exception e) { - Logger.logWarn("PtgMinus calculating formula failed, could not negate operand " + form[0].toString() + " : " + e.toString()); - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - public String getString(){ - return "-"; - } - public String toString() { - return "u-"; - } +public class PtgUMinus extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8448419489380791823L; + + public PtgUMinus() { // 20060504 KSC: Added to fill record bytes upon creation + ptgId = 0x13; + record = new byte[1]; + record[0] = 0x13; + } + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsUnaryOperator() { + return true; + } + + /** + * return the human-readable String representation of + */ + public int getLength() { + return PTG_UMINUS_LENGTH; + } + + /** + * Operator specific calculate method, this one returns a single value sent to it. + */ + public Ptg calculatePtg(Ptg[] form) { + if (form.length != 1) { + Logger.logWarn("PtgMinus calculating formula failed, wrong number of values."); + return new PtgErr(PtgErr.ERROR_VALUE); + } + try { + Ptg p = form[0]; + Ptg ret = null; + if (p instanceof PtgInt) { + int val = p.getIntVal(); + val *= -1; + ret = new PtgInt(val); + } else { + double val = p.getDoubleVal(); + val *= -1; + ret = new PtgNumber(val); + } + return ret; + } catch (Exception e) { + Logger.logWarn("PtgMinus calculating formula failed, could not negate operand " + form[0].toString() + " : " + e.toString()); + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + + public String getString() { + return "-"; + } + + public String toString() { + return "u-"; + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.java b/src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.java index 3f9da5d..6dd7ea5 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -35,42 +35,51 @@ /** * */ -public class PtgUPlus extends GenericPtg implements Ptg{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3514760881731524419L; +public class PtgUPlus extends GenericPtg implements Ptg { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -3514760881731524419L; - public PtgUPlus() { // 20060504 KSC: Added to fill record bytes upon creation - ptgId = 0x12; - record = new byte[1]; - record[0] = 0x12; - } - - public boolean getIsOperator(){return true;} - public boolean getIsPrimitiveOperator(){return true;} - public boolean getIsUnaryOperator(){return true;} - - /** return the human-readable String representation of - */ - public String getString(){ - return "+"; - } - - public int getLength(){ - return PTG_UPLUS_LENGTH; - } - - /* Operator specific calculate method, this one returns a single value sent to it. - - */ - public Ptg calculatePtg(Ptg[] form){ - // there should always be only one ptg in this, error if not. - if (form.length != 1){ - Logger.logWarn("calculating formula failed, wrong number of values in PtgUPlus"); - return null; - } - return form[0]; - } + public PtgUPlus() { // 20060504 KSC: Added to fill record bytes upon creation + ptgId = 0x12; + record = new byte[1]; + record[0] = 0x12; + } + + public boolean getIsOperator() { + return true; + } + + public boolean getIsPrimitiveOperator() { + return true; + } + + public boolean getIsUnaryOperator() { + return true; + } + + /** + * return the human-readable String representation of + */ + public String getString() { + return "+"; + } + + public int getLength() { + return PTG_UPLUS_LENGTH; + } + + /* Operator specific calculate method, this one returns a single value sent to it. + + */ + public Ptg calculatePtg(Ptg[] form) { + // there should always be only one ptg in this, error if not. + if (form.length != 1) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgUPlus"); + return null; + } + return form[0]; + } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.java b/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.java index 3abecda..a61f7fd 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,184 +24,180 @@ import java.util.ArrayList; -import io.starter.toolkit.Logger; - /* * Computes the Union of the two top operands. Supposedly this is * "Microsoft Excel's comma operator" Brought to you by the same people that * created those space saving RK's - * - * + * + * * "http://www.extentech.com">Extentech Inc. - * - * - * + * + * + * * @see Ptg - * + * * @see Formula */ public class PtgUnion extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8333035819099274707L; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8333035819099274707L; - public boolean getIsOperator() { - return true; - } + public boolean getIsOperator() { + return true; + } - public boolean getIsBinaryOperator() { - return true; - } + public boolean getIsBinaryOperator() { + return true; + } - public boolean getIsPrimitiveOperator() { - return true; - } + public boolean getIsPrimitiveOperator() { + return true; + } - public PtgUnion() { - ptgId = 0x10; - record = new byte[1]; - record[0] = 0x10; - } + public PtgUnion() { + ptgId = 0x10; + record = new byte[1]; + record[0] = 0x10; + } - /** - * return the human-readable String representation of - */ - public String getString() { - return ","; - } + /** + * return the human-readable String representation of + */ + public String getString() { + return ","; + } - public int getLength() { - return PTG_UNION_LENGTH; - } + public int getLength() { + return PTG_UNION_LENGTH; + } - /** - * Union = All items from A and B, shared and not shared. Range list - * operator, represented by the system's list separator sign (for example - * comma sign). Treats two ranges as one operator (A1:B2,B2:C3) B1:B3 - * ==>B1:B2, B2:B3 (A1:B2,B2:C3) ==>A1:B2, B2:C3 - */ - // just add together? seems that is the case - public Ptg calculatePtg(Ptg[] form){ - if (form.length!=2) + /** + * Union = All items from A and B, shared and not shared. Range list + * operator, represented by the system's list separator sign (for example + * comma sign). Treats two ranges as one operator (A1:B2,B2:C3) B1:B3 + * ==>B1:B2, B2:B3 (A1:B2,B2:C3) ==>A1:B2, B2:C3 + */ + // just add together? seems that is the case + public Ptg calculatePtg(Ptg[] form) { + if (form.length != 2) return new PtgErr(PtgErr.ERROR_VALUE); - try { - String sourceSheet= null; - try { - if (this.getParentRec().getSheet()!=null) // could be a Name rec ... - sourceSheet= this.getParentRec().getSheet().getSheetName(); - } catch (NullPointerException ne) { - ; - } - ArrayList first= null; - ArrayList last= null; - for (int i= 0; i < 2; i++) { - Ptg p= form[i]; - ArrayList a= new ArrayList(); - if (p instanceof PtgArea) { - a.add(p); - } else if (p instanceof PtgRef3d) { - a.add(p); - } else if (p instanceof PtgRef) { - a.add(p); - } else if (p instanceof PtgName) { - Ptg[] pc= ((PtgName)p).getComponents(); - for (int j= 0; j < pc.length; j++) - a.add(pc[j]); - } else if (p instanceof PtgStr) { - String[] comps= (p.toString()).split(","); - for (int j= 0; j < comps.length; j++) { - if (comps[j].indexOf(":")==-1) { - if (!comps[j].equals("#REF!") && - !comps[j].equals("#NULL!")) { - PtgRef3d pr= new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(comps[j]); - a.add(pr); - } else { - PtgRefErr3d pr= new PtgRefErr3d(); - pr.setParentRec(this.getParentRec()); - a.add(pr); - } - } else { - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(comps[j]); - Ptg[] pcs= pa.getComponents(); - if (pcs!=null) { - for (int k= 0; k < pcs.length; k++) { - ((PtgRef)pcs[k]).setSheetName(pa.getSheetName()); - a.add(pcs[k]); - } - } - } - } - } else if (p instanceof PtgArray) { - // parse array components and create refs - Ptg[] pc= ((PtgArray)p).getComponents(); - for (int j= 0; j < pc.length; j++) { - String loc=((PtgStr) pc[j]).toString(); - if (!loc.startsWith("#")) { // skip errors -- TODO: IS THIS CORRECT? - if (loc.indexOf(":")==-1) { - if (loc.indexOf("!")==-1) { - PtgRef pr= new PtgRef(); - pr.setUseReferenceTracker(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } else { - PtgRef3d pr= new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } - } else { - PtgArea3d pa= new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(loc); - a.add(pa); - } - } - } - } else if (p instanceof PtgErr || p instanceof PtgRefErr || p instanceof PtgAreaErr3d) { - // DO WHAT??? - ; - } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array - Ptg[] pc= ((GenericPtg) p).vars; - for (int j= 0; j < pc.length; j++) { - if (pc[j] instanceof PtgArea & !(pc[j] instanceof PtgAreaErr3d)) { - Ptg[] pa= pc[j].getComponents(); - for (int k= 0; k < pa.length; k++) - a.add(pa[k]); - } else - a.add(pc[j]); - } - } - if (first==null) - first= a; - else - last= a; - } - // now have components for both operands - SUM and package into 1 ptg for return - // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars - GenericPtg retp= new PtgMystery(); - ArrayList retptgs= new ArrayList(); - for (int k= 0; k < first.size(); k++) - retptgs.add((PtgRef) first.get(k)); - for (int k= 0; k < last.size(); k++) - retptgs.add((PtgRef) last.get(k)); - Ptg[] ptgs= new Ptg[retptgs.size()]; - retptgs.toArray(ptgs); - retp.setVars(ptgs); - return retp; - }catch(NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - }catch(Exception e) { // handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } + try { + String sourceSheet = null; + try { + if (this.getParentRec().getSheet() != null) // could be a Name rec ... + sourceSheet = this.getParentRec().getSheet().getSheetName(); + } catch (NullPointerException ne) { + } + ArrayList first = null; + ArrayList last = null; + for (int i = 0; i < 2; i++) { + Ptg p = form[i]; + ArrayList a = new ArrayList(); + if (p instanceof PtgArea) { + a.add(p); + } else if (p instanceof PtgRef3d) { + a.add(p); + } else if (p instanceof PtgRef) { + a.add(p); + } else if (p instanceof PtgName) { + Ptg[] pc = p.getComponents(); + for (int j = 0; j < pc.length; j++) + a.add(pc[j]); + } else if (p instanceof PtgStr) { + String[] comps = (p.toString()).split(","); + for (int j = 0; j < comps.length; j++) { + if (comps[j].indexOf(":") == -1) { + if (!comps[j].equals("#REF!") && + !comps[j].equals("#NULL!")) { + PtgRef3d pr = new PtgRef3d(false); + pr.setParentRec(this.getParentRec()); + pr.setLocation(comps[j]); + a.add(pr); + } else { + PtgRefErr3d pr = new PtgRefErr3d(); + pr.setParentRec(this.getParentRec()); + a.add(pr); + } + } else { + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(this.getParentRec()); + pa.setLocation(comps[j]); + Ptg[] pcs = pa.getComponents(); + if (pcs != null) { + for (int k = 0; k < pcs.length; k++) { + ((PtgRef) pcs[k]).setSheetName(pa.getSheetName()); + a.add(pcs[k]); + } + } + } + } + } else if (p instanceof PtgArray) { + // parse array components and create refs + Ptg[] pc = p.getComponents(); + for (int j = 0; j < pc.length; j++) { + String loc = pc[j].toString(); + if (!loc.startsWith("#")) { // skip errors -- TODO: IS THIS CORRECT? + if (loc.indexOf(":") == -1) { + if (loc.indexOf("!") == -1) { + PtgRef pr = new PtgRef(); + pr.setUseReferenceTracker(false); + pr.setParentRec(this.getParentRec()); + pr.setLocation(loc); + a.add(pr); + } else { + PtgRef3d pr = new PtgRef3d(false); + pr.setParentRec(this.getParentRec()); + pr.setLocation(loc); + a.add(pr); + } + } else { + PtgArea3d pa = new PtgArea3d(false); + pa.setParentRec(this.getParentRec()); + pa.setLocation(loc); + a.add(pa); + } + } + } + } else if (p instanceof PtgErr || p instanceof PtgRefErr || p instanceof PtgAreaErr3d) { + // DO WHAT??? + } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array + Ptg[] pc = ((GenericPtg) p).vars; + for (int j = 0; j < pc.length; j++) { + if (pc[j] instanceof PtgArea & !(pc[j] instanceof PtgAreaErr3d)) { + Ptg[] pa = pc[j].getComponents(); + for (int k = 0; k < pa.length; k++) + a.add(pa[k]); + } else + a.add(pc[j]); + } + } + if (first == null) + first = a; + else + last = a; + } + // now have components for both operands - SUM and package into 1 ptg for return + // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars + GenericPtg retp = new PtgMystery(); + ArrayList retptgs = new ArrayList(); + for (int k = 0; k < first.size(); k++) + retptgs.add(first.get(k)); + for (int k = 0; k < last.size(); k++) + retptgs.add(last.get(k)); + Ptg[] ptgs = new Ptg[retptgs.size()]; + retptgs.toArray(ptgs); + retp.setVars(ptgs); + return retp; + } catch (NumberFormatException e) { + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } catch (Exception e) { // handle error ala Excel + PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); + return perr; + } } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.java index c9f0d0f..6c54968 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.java @@ -2,34 +2,35 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.formats.XLS.formulas; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Vector; import io.starter.OpenXLS.ExcelTools; import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.toolkit.CompatibleVector; import io.starter.toolkit.Logger; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Vector; + /* StatisticalCalculator is a collection of static methods that operate @@ -39,294 +40,305 @@ acted upon. A Ptg of the type that makes sense (ie boolean, number) is returned after each method. */ -public class StatisticalCalculator -{ - - - /** AVERAGE - Returns the average (arithmetic mean) of the arguments. - Ignores non-numbers - This cannot recurse, due to averaging needs. - - Usage@ AVERAGE(number1,number2, ...) - Returns@ PtgNumber - */ - protected static Ptg calcAverage(Ptg[] operands){ +public class StatisticalCalculator { + + + /** + * AVERAGE + * Returns the average (arithmetic mean) of the arguments. + * Ignores non-numbers + * This cannot recurse, due to averaging needs. + *

                + * Usage@ AVERAGE(number1,number2, ...) + * Returns@ PtgNumber + */ + protected static Ptg calcAverage(Ptg[] operands) { Vector vect = new Vector(); - - for (int i=0;i32", "apples", or B4. - Average_range is the actual set of cells to average. If omitted, range is used. - Average_range does not have to be the same size and shape as range. The actual cells that are averaged are determined by using the top, left cell in average_range as the beginning cell, and then including cells that correspond in size and shape to range. + * Returns the average (arithmetic mean) of all the cells in a range that meet a given criteria. + *

                + * AVERAGEIF(range,criteria,average_range) + *

                + * Range is one or more cells to average, including numbers or names, arrays, or references that contain numbers. + * Criteria is the criteria in the form of a number, expression, cell reference, or text that defines which cells are averaged. For example, criteria can be expressed as 32, "32", ">32", "apples", or B4. + * Average_range is the actual set of cells to average. If omitted, range is used. + * Average_range does not have to be the same size and shape as range. The actual cells that are averaged are determined by using the top, left cell in average_range as the beginning cell, and then including cells that correspond in size and shape to range. + * * @param operands * @return */ protected static Ptg calcAverageIf(Ptg[] operands) { - if (operands.length < 2) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - // range used to test criteria - Ptg[] range= operands[0].getComponents(); - // TODO: if range is blank or a text value returns ERROR_DIV_ZERO - String criteria= operands[1].getString().trim(); - // Parse criteria into op + criteria - int i= Calculator.splitOperator(criteria); - String op= criteria.substring(0, i); // extract operand - criteria= criteria.substring(i); - criteria= Calculator.translateWildcardsInCriteria(criteria); - // Average_range, if present, is used for return values if range passes criteria - Ptg[] average_range= null; - boolean varyRow= false; - if (operands.length > 2) { - //The actual cells that are averaged are determined by using the top, left cell in average_range as the beginning cell, - // and then including cells that correspond in size and shape to range. - int rc[]= null; - average_range= new Ptg[range.length]; - average_range[0]= operands[2].getComponents()[0]; // start with top left of average range - String sheet= ""; - try { - rc= average_range[0].getIntLocation(); - if (range[0].getIntLocation()[0]!=range[range.length-1].getIntLocation()[0]) // determine if range is varied across row or column - varyRow= true; - sheet= ((PtgRef)average_range[0]).getSheetName() + "!"; - } catch (Exception e) { ; } - for (int j= 1; j < average_range.length; j++) { - if (varyRow) rc[0]++; - else rc[1]++; - average_range[j]= new PtgRef3d(); - average_range[j].setParentRec(range[0].getParentRec()); - average_range[j].setLocation(sheet + ExcelTools.formatLocation(rc)); - } - } - int nresults= 0; - double result= 0.0; - for (int j= 0; j < range.length; j++) { - Object val= range[j].getValue(); - // TODO: TRUE and FALSE values are ignored - // TODO: blank cells are treated as 0's - if (Calculator.compareCellValue(val, criteria, op)) { - try { - if (average_range!=null) { - val= average_range[j].getValue(); - if (val==null) // if a cell is empty it's ignored -- - continue; - } - result+=((Number) val).doubleValue(); - } catch (ClassCastException e) { ; } - nresults++; - } - } - - // If no cells in the range meet the criteria, AVERAGEIF returns the #DIV/0! error value - if (nresults==0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - // otherwise, average - return new PtgNumber(result/nresults); + if (operands.length < 2) + return new PtgErr(PtgErr.ERROR_DIV_ZERO); + // range used to test criteria + Ptg[] range = operands[0].getComponents(); + // TODO: if range is blank or a text value returns ERROR_DIV_ZERO + String criteria = operands[1].getString().trim(); + // Parse criteria into op + criteria + int i = Calculator.splitOperator(criteria); + String op = criteria.substring(0, i); // extract operand + criteria = criteria.substring(i); + criteria = Calculator.translateWildcardsInCriteria(criteria); + // Average_range, if present, is used for return values if range passes criteria + Ptg[] average_range = null; + boolean varyRow = false; + if (operands.length > 2) { + //The actual cells that are averaged are determined by using the top, left cell in average_range as the beginning cell, + // and then including cells that correspond in size and shape to range. + int[] rc = null; + average_range = new Ptg[range.length]; + average_range[0] = operands[2].getComponents()[0]; // start with top left of average range + String sheet = ""; + try { + rc = average_range[0].getIntLocation(); + if (range[0].getIntLocation()[0] != range[range.length - 1].getIntLocation()[0]) // determine if range is varied across row or column + varyRow = true; + sheet = ((PtgRef) average_range[0]).getSheetName() + "!"; + } catch (Exception e) { + } + for (int j = 1; j < average_range.length; j++) { + if (varyRow) rc[0]++; + else rc[1]++; + average_range[j] = new PtgRef3d(); + average_range[j].setParentRec(range[0].getParentRec()); + average_range[j].setLocation(sheet + ExcelTools.formatLocation(rc)); + } + } + int nresults = 0; + double result = 0.0; + for (int j = 0; j < range.length; j++) { + Object val = range[j].getValue(); + // TODO: TRUE and FALSE values are ignored + // TODO: blank cells are treated as 0's + if (Calculator.compareCellValue(val, criteria, op)) { + try { + if (average_range != null) { + val = average_range[j].getValue(); + if (val == null) // if a cell is empty it's ignored -- + continue; + } + result += ((Number) val).doubleValue(); + } catch (ClassCastException e) { + } + nresults++; + } + } + + // If no cells in the range meet the criteria, AVERAGEIF returns the #DIV/0! error value + if (nresults == 0) + return new PtgErr(PtgErr.ERROR_DIV_ZERO); + // otherwise, average + return new PtgNumber(result / nresults); } - + /** * AVERAGEIFS * Returns the average (arithmetic mean) of all cells that meet multiple criteria. * AVERAGEIFS(average_range,criteria_range1,criteria1,criteria_range2,criteria2…) - Average_range is one or more cells to average, including numbers or names, arrays, or references that contain numbers. - Criteria_range1, criteria_range2, … are 1 to 127 ranges in which to evaluate the associated criteria. - Criteria1, criteria2, … are 1 to 127 criteria in the form of a number, expression, cell reference, or text that define which cells will be averaged. For example, criteria can be expressed as 32, "32", ">32", "apples", or B4. + * Average_range is one or more cells to average, including numbers or names, arrays, or references that contain numbers. + * Criteria_range1, criteria_range2, … are 1 to 127 ranges in which to evaluate the associated criteria. + * Criteria1, criteria2, … are 1 to 127 criteria in the form of a number, expression, cell reference, or text that define which cells will be averaged. For example, criteria can be expressed as 32, "32", ">32", "apples", or B4. + * * @param operands * @return */ protected static Ptg calcAverageIfS(Ptg[] operands) { - try { - PtgArea average_range= Calculator.getRange(operands[0]); - Ptg[] averagerangecells= average_range.getComponents(); - if (averagerangecells.length==0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - String[] ops= new String[(operands.length-1)/2]; - String[] criteria= new String[(operands.length-1)/2]; - Ptg[][] criteria_cells= new Ptg[(operands.length-1)/2][]; - int j= 0; - for (int i= 1; i+1 < operands.length; i+=2) { - //criteria range - parse and get comprising cells - PtgArea cr= Calculator.getRange(operands[i]); - criteria_cells[j]= cr.getComponents(); - // each criteria_range must contain the same number of rows and columns as the sum_range - if (criteria_cells[j].length!=averagerangecells.length) - return new PtgErr(PtgErr.ERROR_VALUE); - // criteria for comparison, including operator - criteria[j]= operands[i+1].toString(); - // strip operator, if any, and parse criteria - ops[j]= "="; // operator, default is = - int k= Calculator.splitOperator(criteria[j]); - if (k > 0) ops[j]= criteria[j].substring(0, k); // extract operator, if any - criteria[j]= criteria[j].substring(k); - criteria[j]= Calculator.translateWildcardsInCriteria(criteria[j]); - j++; - } - - // test criteria for all cells in range, storing those corresponding average_range cells - // that pass in passesList - // stores the cells that pass the criteria expression and therefore will be averaged - ArrayList passesList= new ArrayList(); - // for each set of criteria, test all cells in range and evaluate - // NOTE: this is an implicit AND evaluation - for (int i= 0; i < averagerangecells.length; i++) { - boolean passes= true; - for (int k= 0; k < criteria.length; k++) { - try { - Object v= criteria_cells[k][i].getValue(); - // If cells in average_range cannot be translated into numbers, AVERAGEIFS returns the #DIV0! error value. - passes= Calculator.compareCellValue(v, criteria[k], ops[k]) && passes; - if (!passes) - break; // no need to continue - } catch (Exception e) { // don't report error - } - } - if (passes) { - passesList.add(averagerangecells[i]); - } - } - // If no cells in the range meet the criteria, AVERAGEIF returns the #DIV/0! error value - if (passesList.size()==0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - - // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); - // Now we sum up the values of these cells and return - double result= 0.0; - for (int i= 0; i < passesList.size(); i++) { - Ptg cell= (Ptg) passesList.get(i); - try { - result+= cell.getDoubleVal(); - } catch (Exception e) { - Logger.logErr("MathFunctionCalculator.calcAverageIfS: error obtaining cell value: " + e.toString()); // debugging only; take out when fully tested - ; // keep going - } - } - - // otherwise, average - return new PtgNumber(result/passesList.size()); - - } catch (Exception e) {; } - return new PtgErr(PtgErr.ERROR_NULL); - } - - /** - AVEDEV(number1,number2, ...) - Number1, number2, ... are 1 to 30 arguments for which you want - the average of the absolute deviations. You can also use a - single array or a reference to an array instead of arguments - separated by commas. - - The arguments must be either numbers or names, - arrays, or references that contain numbers. - - If an array or reference argument contains text, - logical values, or empty cells, those values are ignored; - however, cells with the value zero are included. - */ - protected static Ptg calcAveDev(Ptg[] operands){ - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - // Get the average for the mean - PtgNumber av = (PtgNumber)StatisticalCalculator.calcAverage(operands); - double average = -0.001; - try{ - Double dd = new Double(String.valueOf(av.getValue())); - average = dd.doubleValue(); - }catch(NumberFormatException e){}; - if (average == -0.001)return PtgCalculator.getError(); - - // work out the total deviation - double total = 0; - int count = 0; - Double d; - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); - for (int i=0;i 0) ops[j] = criteria[j].substring(0, k); // extract operator, if any + criteria[j] = criteria[j].substring(k); + criteria[j] = Calculator.translateWildcardsInCriteria(criteria[j]); + j++; + } + + // test criteria for all cells in range, storing those corresponding average_range cells + // that pass in passesList + // stores the cells that pass the criteria expression and therefore will be averaged + ArrayList passesList = new ArrayList(); + // for each set of criteria, test all cells in range and evaluate + // NOTE: this is an implicit AND evaluation + for (int i = 0; i < averagerangecells.length; i++) { + boolean passes = true; + for (int k = 0; k < criteria.length; k++) { + try { + Object v = criteria_cells[k][i].getValue(); + // If cells in average_range cannot be translated into numbers, AVERAGEIFS returns the #DIV0! error value. + passes = Calculator.compareCellValue(v, criteria[k], ops[k]) && passes; + if (!passes) + break; // no need to continue + } catch (Exception e) { // don't report error + } + } + if (passes) { + passesList.add(averagerangecells[i]); + } + } + // If no cells in the range meet the criteria, AVERAGEIF returns the #DIV/0! error value + if (passesList.size() == 0) + return new PtgErr(PtgErr.ERROR_DIV_ZERO); + + // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); + // Now we sum up the values of these cells and return + double result = 0.0; + for (int i = 0; i < passesList.size(); i++) { + Ptg cell = (Ptg) passesList.get(i); + try { + result += cell.getDoubleVal(); + } catch (Exception e) { + Logger.logErr("MathFunctionCalculator.calcAverageIfS: error obtaining cell value: " + e.toString()); // debugging only; take out when fully tested + // keep going + } + } + + // otherwise, average + return new PtgNumber(result / passesList.size()); + + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_NULL); + } + + /** + * AVEDEV(number1,number2, ...) + * Number1, number2, ... are 1 to 30 arguments for which you want + * the average of the absolute deviations. You can also use a + * single array or a reference to an array instead of arguments + * separated by commas. + *

                + * The arguments must be either numbers or names, + * arrays, or references that contain numbers. + *

                + * If an array or reference argument contains text, + * logical values, or empty cells, those values are ignored; + * however, cells with the value zero are included. + */ + protected static Ptg calcAveDev(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + // Get the average for the mean + PtgNumber av = (PtgNumber) StatisticalCalculator.calcAverage(operands); + double average = -0.001; + try { + Double dd = new Double(String.valueOf(av.getValue())); + average = dd.doubleValue(); + } catch (NumberFormatException e) { + } + if (average == -0.001) return PtgCalculator.getError(); + + // work out the total deviation + double total = 0; + int count = 0; + Double d; + Ptg[] alloperands = PtgCalculator.getAllComponents(operands); + for (int i = 0; i < alloperands.length; i++) { + Ptg resPtg = alloperands[i]; + try { // some fields may be text, so handle gracefully + if (resPtg.getValue() != null) { + d = new Double(String.valueOf(resPtg.getValue())); + double dub = d.doubleValue(); + dub = average - dub; + dub = Math.abs(dub); + total += dub; + count++; + } + } catch (NumberFormatException e) { + } + } + // work out the mean deviation + double mean = total / count; + PtgNumber pnum = new PtgNumber(mean); + return pnum; + } + + /** + * AVERAGEA + * Returns the average of its arguments, including numbers, text, + * and logical values + *

                + * The arguments must be numbers, names, arrays, or references. + * Array or reference arguments that contain text evaluate as 0 (zero). + * Empty text ("") evaluates as 0 (zero). + * Arguments that contain TRUE evaluate as 1; + * arguments that contain FALSE evaluate as 0 (zero). + */ + protected static Ptg calcAverageA(Ptg[] operands) { + Ptg[] alloperands = PtgCalculator.getAllComponents(operands); + double total = 0; + for (int i = 0; i < alloperands.length; i++) { + Ptg p = alloperands[i]; + try { + Object ov = p.getValue(); + if (ov != null) { + if (String.valueOf(ov) == "true") { + total++; + } else { + total += Double.parseDouble(String.valueOf(ov)); + } + } + } catch (NumberFormatException e) { + } + + } + double result = total / alloperands.length; + PtgNumber pnum = new PtgNumber(result); + return pnum; + } /* BETADIST Returns the cumulative beta probability density function @@ -349,265 +361,272 @@ protected static Ptg calcAverageA(Ptg[] operands){ CONFIDENCE Returns the confidence interval for a population mean */ - /** -CORREL - Returns the correlation coefficient between two data sets - * @throws CalculationException - */ - protected static Ptg calcCorrel(Ptg[] operands) throws CalculationException{ - // get the covariance - PtgNumber pnum = (PtgNumber)calcCovar(operands); - double covar = pnum.getVal(); - Ptg[] xPtg = new Ptg[1]; - xPtg[0] = operands[0]; - Ptg[] yPtg = new Ptg[1]; - yPtg[0] = operands[1]; - pnum = (PtgNumber)calcAverage(xPtg); - double xMean = pnum.getVal(); - pnum = (PtgNumber)calcAverage(yPtg); - double yMean = pnum.getVal(); - double[] xVals = PtgCalculator.getDoubleValueArray(xPtg); - double[] yVals = PtgCalculator.getDoubleValueArray(yPtg); - if (xVals==null || yVals==null) - return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double xstat = 0; - for (int i=0;i + * Usage@ COUNT(A1:A5,A9) + * Return@ PtgInt + * TODO: implement counting of dates! + */ + protected static Ptg calcCount(Ptg[] operands) { + int count = 0; + for (int i = 0; i < operands.length; i++) { + Ptg[] pref = operands[i].getComponents(); // optimized -- do it once!! -jm + if (pref != null) { // it is some sort of range + for (int z = 0; z < pref.length; z++) { + Object o = pref[z].getValue(); + if (o != null) { + try { + Double n = new Double(String.valueOf(o)); + count++; + } catch (NumberFormatException e) { + } + } + } + } else { // it's a single ptgref + Object o = operands[i].getValue(); + if (o != null) { + try { + Double n = new Double(String.valueOf(o)); + count++; + } catch (NumberFormatException e) { + } + } + } + } + PtgInt pint = new PtgInt(count); + return pint; + + } + + /** + * COUNTA + * Counts the number of non-blank cells within a range + */ + protected static Ptg calcCountA(Ptg[] operands) { + Ptg[] allops = PtgCalculator.getAllComponents(operands); + int count = 0; + for (int i = 0; i < allops.length; i++) { /* 20081120 KSC: blnaks are handled differently as Excel counts blank cells as 0's Object o = allops[i].getValue(); if (o != null) count++; */ - if (!allops[i].isBlank()) - count++; - } - return new PtgInt(count); - } - /** - COUNTBLANK - Counts the number of blank cells within a range -*/ - protected static Ptg calcCountBlank(Ptg[] operands){ - Ptg[] allops = PtgCalculator.getAllComponents(operands); - int count = 0; - for (int i=0;i32", B4, "apples", or "32". - criteria_range2, criteria2, ... Optional. Additional ranges and their associated criteria. Up to 127 range/criteria pairs are allowed. - - * @param operands - * @return - */ - protected static Ptg calcCountIfS(Ptg[] operands){ - try { - String[] ops= new String[operands.length/2]; - String[] criteria= new String[operands.length/2]; - Ptg[][] criteria_cells= new Ptg[operands.length/2][]; - for (int i= 0; i+1 < operands.length; i+=2) { - //criteria range - parse and get comprising cells - PtgArea cr= Calculator.getRange(operands[i]); - criteria_cells[i/2]= cr.getComponents(); - // each criteria_range must contain the same number of rows and columns as the criteriarange - if (i > 0 && criteria_cells[i/2].length!=criteria_cells[0].length) - return new PtgErr(PtgErr.ERROR_VALUE); - // criteria for comparison, including operator - criteria[i/2]= operands[i+1].toString(); - // strip operator, if any, and parse criteria - ops[i/2]= "="; // operator, default is = - int k= Calculator.splitOperator(criteria[i/2]); - if (k > 0) ops[i/2]= criteria[i/2].substring(0, k); // extract operator, if any - criteria[i/2]= criteria[i/2].substring(k); - criteria[i/2]= Calculator.translateWildcardsInCriteria(criteria[i/2]); - } - - // test criteria for all cells in range, counting each cell that passes - // for each set of criteria, test all cells in range and evaluate - int count= 0; - for (int i= 0; i < criteria_cells[0].length; i++) { - boolean passes= true; - for (int k= 0; k < criteria.length; k++) { - try { - Object v= criteria_cells[k][i].getValue(); - // the criteria argument is a reference to an empty cell, the COUNTIFS function treats the empty cell as a 0 value. - passes= Calculator.compareCellValue(v, criteria[k], ops[k]) && passes; - if (!passes) - break; // no need to continue - } catch (Exception e) { // don't report error - } - } - if (passes) - count++; - } - - return new PtgNumber(count); - - } catch (Exception e) {; } - return new PtgErr(PtgErr.ERROR_NULL); - } + } + } + PtgNumber pnum = new PtgNumber(count); + return pnum; + } + + /** + * COUNTIFS + * criteria_range1 Required. The first range in which to evaluate the associated criteria. + * criteria1 Required. The criteria in the form of a number, expression, cell reference, or text that define which cells will be counted. For example, criteria can be expressed as 32, ">32", B4, "apples", or "32". + * criteria_range2, criteria2, ... Optional. Additional ranges and their associated criteria. Up to 127 range/criteria pairs are allowed. + * + * @param operands + * @return + */ + protected static Ptg calcCountIfS(Ptg[] operands) { + try { + String[] ops = new String[operands.length / 2]; + String[] criteria = new String[operands.length / 2]; + Ptg[][] criteria_cells = new Ptg[operands.length / 2][]; + for (int i = 0; i + 1 < operands.length; i += 2) { + //criteria range - parse and get comprising cells + PtgArea cr = Calculator.getRange(operands[i]); + criteria_cells[i / 2] = cr.getComponents(); + // each criteria_range must contain the same number of rows and columns as the criteriarange + if (i > 0 && criteria_cells[i / 2].length != criteria_cells[0].length) + return new PtgErr(PtgErr.ERROR_VALUE); + // criteria for comparison, including operator + criteria[i / 2] = operands[i + 1].toString(); + // strip operator, if any, and parse criteria + ops[i / 2] = "="; // operator, default is = + int k = Calculator.splitOperator(criteria[i / 2]); + if (k > 0) ops[i / 2] = criteria[i / 2].substring(0, k); // extract operator, if any + criteria[i / 2] = criteria[i / 2].substring(k); + criteria[i / 2] = Calculator.translateWildcardsInCriteria(criteria[i / 2]); + } - /** - COVAR - Returns covariance, the average of the products of paired deviations - * @throws CalculationException - */ - protected static Ptg calcCovar(Ptg[] operands) throws CalculationException{ - Ptg[] xMeanPtg = new Ptg[1]; - xMeanPtg[0] = operands[0]; - Ptg[] yMeanPtg = new Ptg[1]; - yMeanPtg[0] = operands[1]; - PtgNumber pnum = (PtgNumber)calcAverage(xMeanPtg); - double xMean = pnum.getVal(); - pnum = (PtgNumber)calcAverage(yMeanPtg); - double yMean = pnum.getVal(); - double[] xVals = PtgCalculator.getDoubleValueArray(xMeanPtg); - double[] yVals = PtgCalculator.getDoubleValueArray(yMeanPtg); - if (xVals==null || yVals==null) - return new PtgErr(PtgErr.ERROR_NA);//propagate error - double xyMean = 0; - if (xVals.length == yVals.length){ - int addvals = 0; - for (int i=0;i binsArr[binsArr.length-1].doubleValue()){ - retvals[binsArr.length]++; - x = binsArr.length; - } - } - } - // keep the original locations, so we can put the end result array in the correct order. - // not used! double[] originalLocs = PtgCalculator.getDoubleValueArray(secondArr); - String ret = "{"; - for (int i=0;i binsArr[binsArr.length - 1].doubleValue()) { + retvals[binsArr.length]++; + x = binsArr.length; + } + } + } + // keep the original locations, so we can put the end result array in the correct order. + // not used! double[] originalLocs = PtgCalculator.getDoubleValueArray(secondArr); + String ret = "{"; + for (int i = 0; i < retvals.length; i++) { + ret += retvals[i] + ","; + } + ret = ret.substring(0, ret.length() - 1); // get rid of final comma + ret += "}"; + + PtgArray returnArr = new PtgArray(); + returnArr.setVal(ret); + return returnArr; + } /* FTEST Returns the result of an F-test @@ -721,83 +744,88 @@ Returns the natural logarithm of the gamma function, G(x) HYPGEOMDIST Returns the hypergeometric distribution */ - /** - INTERCEPT - Returns the intercept of the linear regression line - * @throws CalculationException - */ - protected static Ptg calcIntercept(Ptg[] operands) throws CalculationException{ - double[] yvals; - yvals = PtgCalculator.getDoubleValueArray(operands[0]); - - double[] xvals = PtgCalculator.getDoubleValueArray(operands[1]); - if (xvals==null || yvals==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double sumXVals = 0; - for (int i=0;i + * Returns the k-th largest value in a data set. You can use this function to select a value based on its relative standing. + * For example, you can use LARGE to return the highest, runner-up, or third-place score. + *

                + * LARGE(array,k) + *

                + * Array is the array or range of data for which you want to determine the k-th largest value. + * K is the position (from the largest) in the array or cell range of data to return. + *

                + * If array is empty, LARGE returns the #NUM! error value. + * If k ≤ 0 or if k is greater than the number of data points, LARGE returns the #NUM! error value. + * If n is the number of data points in a range, then LARGE(array,1) returns the largest value, and LARGE(array,n) returns the smallest value. + * + * @throws CalculationException + */ + protected static Ptg calcLarge(Ptg[] operands) throws CalculationException { + if (operands.length != 2) return new PtgErr(PtgErr.ERROR_VALUE); + Ptg rng = operands[0]; + Ptg[] array = PtgCalculator.getAllComponents(rng); + if (array.length == 0) return new PtgErr(PtgErr.ERROR_NUM); + int k = new Double(PtgCalculator.getDoubleValueArray(operands[1])[0]).intValue(); + if (k <= 0 || k > array.length) + return new PtgErr(PtgErr.ERROR_NUM); - Array is the array or range of data for which you want to determine the k-th largest value. - K is the position (from the largest) in the array or cell range of data to return. + CompatibleVector sortedValues = new CompatibleVector(); + for (int i = 0; i < array.length; i++) { + Ptg p = array[i]; + try { + Double d = new Double(String.valueOf(p.getValue())); + sortedValues.addOrderedDouble(d); + } catch (NumberFormatException e) { + } + } + // reverse array + Double[] dubRefs = new Double[sortedValues.size()]; + for (int i = 0; i < dubRefs.length; i++) { + dubRefs[i] = (Double) sortedValues.last(); + sortedValues.remove(sortedValues.size() - 1); + } - If array is empty, LARGE returns the #NUM! error value. - If k ≤ 0 or if k is greater than the number of data points, LARGE returns the #NUM! error value. - If n is the number of data points in a range, then LARGE(array,1) returns the largest value, and LARGE(array,n) returns the smallest value. - * @throws CalculationException - */ - protected static Ptg calcLarge(Ptg[] operands) throws CalculationException{ - if (operands.length!=2) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg rng= operands[0]; - Ptg[] array=PtgCalculator.getAllComponents(rng); - if (array.length==0) return new PtgErr(PtgErr.ERROR_NUM); - int k= new Double(PtgCalculator.getDoubleValueArray(operands[1])[0]).intValue(); - if (k<=0 || k > array.length) - return new PtgErr(PtgErr.ERROR_NUM); - - CompatibleVector sortedValues = new CompatibleVector(); - for (int i=0;i 2) { - if (!(operands[2] instanceof PtgMissArg)) - getIntercept = PtgCalculator.getBooleanValue(operands[2]); - } - boolean statistics= false; - if (operands.length > 3) { - if (!(operands[3] instanceof PtgMissArg)) - statistics = PtgCalculator.getBooleanValue(operands[3]); - } - - Ptg ps=calcSlope(operands); - if (ps instanceof PtgErr) return ps; - PtgNumber Pslope = (PtgNumber)calcSlope(operands); - double slope = Pslope.getVal(); // a1 val - PtgNumber Pintercept = (PtgNumber)calcIntercept(operands); - double intercept = Pintercept.getVal(); // b1 val - - if (operands.length > 3 && (operands[3] instanceof PtgBool || operands[3] instanceof PtgInt)){ - boolean b = PtgCalculator.getBooleanValue(operands[3]); - if (!b){ - String retstr = "{" + slope + "," + intercept + "},"; - retstr += "{" + slope + "," + intercept + "},"; - retstr += "{" + slope + "," + intercept + "},"; - retstr +="{" + slope + "," + intercept + "},"; - retstr += "{" + slope + "," + intercept + "}"; - PtgArray para = new PtgArray(); - para.setVal(retstr); - return para; - } - } - Ptg[] p = new Ptg[1]; - - // figure out the stdev of the slope - PtgNumber Psteyx = (PtgNumber)calcSteyx(operands); - double steyx = Psteyx.getVal(); // b3 val - - // calc the y error percentage - double yError = steyx*steyx; - p[0] = operands[1]; - PtgNumber vp = (PtgNumber)calcVarp(p); - double Sxx = vp.getVal()*ys.length; - yError = yError/Sxx; - yError = Math.sqrt(yError); // A2 val - - // calculate degrees of freedom - int degFreedom = ys.length-2; // b4 val - - // calculate standard error of intercept - double sumXsquared = 0; - double sumSquaredX = 0; - double sumXYsquared = 0; - for (int i=0;i 2) { + if (!(operands[2] instanceof PtgMissArg)) + getIntercept = PtgCalculator.getBooleanValue(operands[2]); + } + boolean statistics = false; + if (operands.length > 3) { + if (!(operands[3] instanceof PtgMissArg)) + statistics = PtgCalculator.getBooleanValue(operands[3]); + } + + Ptg ps = calcSlope(operands); + if (ps instanceof PtgErr) return ps; + PtgNumber Pslope = (PtgNumber) calcSlope(operands); + double slope = Pslope.getVal(); // a1 val + PtgNumber Pintercept = (PtgNumber) calcIntercept(operands); + double intercept = Pintercept.getVal(); // b1 val + + if (operands.length > 3 && (operands[3] instanceof PtgBool || operands[3] instanceof PtgInt)) { + boolean b = PtgCalculator.getBooleanValue(operands[3]); + if (!b) { + String retstr = "{" + slope + "," + intercept + "},"; + retstr += "{" + slope + "," + intercept + "},"; + retstr += "{" + slope + "," + intercept + "},"; + retstr += "{" + slope + "," + intercept + "},"; + retstr += "{" + slope + "," + intercept + "}"; + PtgArray para = new PtgArray(); + para.setVal(retstr); + return para; + } + } + Ptg[] p = new Ptg[1]; + + // figure out the stdev of the slope + PtgNumber Psteyx = (PtgNumber) calcSteyx(operands); + double steyx = Psteyx.getVal(); // b3 val + + // calc the y error percentage + double yError = steyx * steyx; + p[0] = operands[1]; + PtgNumber vp = (PtgNumber) calcVarp(p); + double Sxx = vp.getVal() * ys.length; + yError = yError / Sxx; + yError = Math.sqrt(yError); // A2 val + + // calculate degrees of freedom + int degFreedom = ys.length - 2; // b4 val + + // calculate standard error of intercept + double sumXsquared = 0; + double sumSquaredX = 0; + double sumXYsquared = 0; + for (int i = 0; i < xs.length; i++) { + sumSquaredX += (xs[i] * xs[i]); + sumXsquared += xs[i]; + sumXYsquared += (xs[i] * ys[i]); + } + sumXsquared *= sumXsquared; + sumXYsquared *= sumXYsquared; + double interceptError = 1 / (xs.length - (sumXsquared / sumSquaredX)); + interceptError = Math.sqrt(interceptError); + interceptError *= steyx; //b2val + + // calculate residual SS + // first create array of predicted values for the linear array + double[] predicted = new double[xs.length]; + double residualSS = 0; // b5value + for (int i = 0; i < xs.length; i++) { + predicted[i] = intercept + (xs[i] * slope); + double d = (predicted[i] - ys[i]); + residualSS += (d * d); + } + + // calculate regression SS + p[0] = operands[0]; + PtgNumber pnum = (PtgNumber) calcAverage(p); + double average = pnum.getVal(); + double regressionSS = 0; + for (int i = 0; i < xs.length; i++) { + double d = (predicted[i] - average); + regressionSS += (d * d);//A5 value + } + p = new Ptg[2]; + p[0] = operands[0]; + p[1] = operands[1]; + pnum = (PtgNumber) calcRsq(p); + double r2 = pnum.getVal(); // A3 + + // calculate the F value + double F = (regressionSS / 1) / (residualSS / degFreedom); // A4 + + // construct the string for creating ptgarray + String retstr = "{" + slope + "," + intercept + "},"; + retstr += "{" + yError + "," + interceptError + "},"; + retstr += "{" + r2 + "," + steyx + "},"; + retstr += "{" + F + "," + degFreedom + "},"; + retstr += "{" + regressionSS + "," + residualSS + "}"; + + PtgArray parr = new PtgArray(); + parr.setVal(retstr); + + return parr; + } /* LOGEST Returns the parameters of an exponential trend @@ -965,455 +994,468 @@ protected static Ptg calcLineSt(Ptg[] operands) throws CalculationException{ LOGNORMDIST Returns the cumulative lognormal distribution */ - - /** - * MAX - Returns the largest value in a set of values. - Ignores non-number fields - Recursively calls for ranges. - - Usage@ MAX(number1,number2, ...) - returns@ PtgNumber - */ - //untested - protected static Ptg calcMax(Ptg[] operands){ - double result = java.lang.Double.MIN_VALUE; // 20090129 KSC -1; - Double d = null; - for (int i=0;i result){result = d.doubleValue();} - }catch(NumberFormatException e){}; - }else{ - Ptg p = operands[i]; - try{ - Object ov = p.getValue(); - if (ov != null) - d = new Double(String.valueOf(ov)); - if (d.doubleValue() > result){result = d.doubleValue();} - }catch(NumberFormatException e){} - catch(NullPointerException e) { } - } - } - if (result==java.lang.Double.MIN_VALUE) // 20090129 KSC: - result= 0; //appears to be default in error situations - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - -/** -MAXA - Returns the maximum value in a list of arguments, including numbers, text, and logical values - - Arguments can be the following: numbers; names, arrays, or references that contain numbers; text representations of numbers; or logical values, such as TRUE and FALSE, in a reference. - Logical values and text representations of numbers that you type directly into the list of arguments are counted. - */ - protected static Ptg calcMaxA(Ptg[] operands){ - Ptg[] alloperands= PtgCalculator.getAllComponents(operands); - if (alloperands.length==0) - return new PtgNumber(0); - double max= Double.MIN_VALUE; - for (int i=0;i + * Usage@ MAX(number1,number2, ...) + * returns@ PtgNumber + */ + //untested + protected static Ptg calcMax(Ptg[] operands) { + double result = java.lang.Double.MIN_VALUE; // 20090129 KSC -1; + Double d = null; + for (int i = 0; i < operands.length; i++) { + Ptg[] pthings = operands[i].getComponents(); // optimized -- do it once!! -jm + if (pthings != null) { + Ptg resPtg = StatisticalCalculator.calcMax(pthings); + try { // some fields may be text, so handle gracefully + if (resPtg.getValue() != null) d = new Double(String.valueOf(resPtg.getValue())); + if (d.doubleValue() > result) { + result = d.doubleValue(); + } + } catch (NumberFormatException e) { + } + } else { + Ptg p = operands[i]; + try { + Object ov = p.getValue(); + if (ov != null) + d = new Double(String.valueOf(ov)); + if (d.doubleValue() > result) { + result = d.doubleValue(); + } + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + } + } + if (result == java.lang.Double.MIN_VALUE) // 20090129 KSC: + result = 0; //appears to be default in error situations + PtgNumber pnum = new PtgNumber(result); + return pnum; + } + + /** + * MAXA + * Returns the maximum value in a list of arguments, including numbers, text, and logical values + *

                + * Arguments can be the following: numbers; names, arrays, or references that contain numbers; text representations of numbers; or logical values, such as TRUE and FALSE, in a reference. + * Logical values and text representations of numbers that you type directly into the list of arguments are counted. + */ + protected static Ptg calcMaxA(Ptg[] operands) { + Ptg[] alloperands = PtgCalculator.getAllComponents(operands); + if (alloperands.length == 0) + return new PtgNumber(0); + double max = Double.MIN_VALUE; + for (int i = 0; i < alloperands.length; i++) { + Object o = alloperands[i].getValue(); + try { + double d = Double.MIN_VALUE; + if (o instanceof Number) + d = ((Number) o).doubleValue(); + else if (o instanceof Boolean) + d = (((Boolean) o).booleanValue() ? 1 : 0); + else + d = new Double(o.toString()).doubleValue(); + max = Math.max(max, d); + } catch (NumberFormatException e) { + // Arguments that are error values or text that cannot be translated into numbers cause errors. + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + return new PtgNumber(max); + } + + /** + * MEDIAN + * Returns the median of the given numbers + */ + protected static Ptg calcMedian(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + Ptg[] alloperands = PtgCalculator.getAllComponents(operands); + CompatibleVector t = new CompatibleVector(); + double retval = 0; + for (int i = 0; i < alloperands.length; i++) { + Ptg p = alloperands[i]; + try { + Double d = new Double(String.valueOf(p.getValue())); + t.addOrderedDouble(d); + } catch (NumberFormatException e) { + } + } + + try { + Double[] dub = new Double[t.size()]; + t.toArray(dub); + double dd = (double) t.size() % 2; + if ((double) t.size() % 2 == 0) { + int firstValLoc = ((t.size()) / 2) - 1; + int lastValLoc = firstValLoc + 1; + double firstVal = dub[firstValLoc].doubleValue(); + double lastVal = dub[lastValLoc].doubleValue(); + retval = (firstVal + lastVal) / 2; + } else { + // it's odd + int firstValLoc = ((t.size() - 1) / 2); + double firstVal = dub[firstValLoc].doubleValue(); + retval = firstVal; + } + PtgNumber pnum = new PtgNumber(retval); + return pnum; + } catch (ArrayIndexOutOfBoundsException e) { // 20090701 KSC: catch exception + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + + /** + * MIN + * Returns the smallest number in a set of values. + * Ignores non-number fields. Note that it also recursivly calls itself + * for things like PtgRange. + *

                + * Usage@ MIN(number1,number2, ...) + * returns PtgNumber + */ + protected static Ptg calcMin(Ptg[] operands) { + double result = java.lang.Double.MAX_VALUE; + Double d = null; + for (int i = 0; i < operands.length; i++) { + Ptg[] pthings = operands[i].getComponents(); // optimized -- do it once!! -jm + if (pthings != null) { + Ptg resPtg = StatisticalCalculator.calcMin(pthings); + try { // some fields may be text, so handle gracefully + if (resPtg instanceof PtgErr) return resPtg; // 20090205 KSC: propagate error + if (resPtg.getValue() != null) { + d = new Double(String.valueOf(resPtg.getValue())); + // 20090129 KSC; if (d.doubleValue() < result || result == -1){result = d.doubleValue();} // 20070215 KSC: only access d if not null! + if (d.doubleValue() < result) { + result = d.doubleValue(); + } // 20070215 KSC: only access d if not null! + } + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + }// 20070209 KSC + } else { + Ptg p = operands[i]; + try { + Object ov = p.getValue(); + if (ov != null) { + if (ov.toString().equals(new PtgErr(PtgErr.ERROR_NA).toString())) // 20090205 KSC: propagate error value + return new PtgErr(PtgErr.ERROR_NA); + d = new Double(String.valueOf(ov)); + // 20090129 KSC; result is defaulted to max + if (d.doubleValue() < result) { + result = d.doubleValue(); + } // 20070215 KSC: only access d if not null! + } + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + } + } + if (result == java.lang.Double.MAX_VALUE) // 20090129 KSC: + result = 0; //appears to be default in error situations + return new PtgNumber(result); // return pnum; -} - - /** - - MINA - Returns the smallest value in a list of arguments, including numbers, text, and logical values - - Arguments can be the following: numbers; names, arrays, or references that contain numbers; text representations of numbers; or logical values, such as TRUE and FALSE, in a reference. - Logical values and text representations of numbers that you type directly into the list of arguments are counted. - */ - protected static Ptg calcMinA(Ptg[] operands){ - Ptg[] alloperands= PtgCalculator.getAllComponents(operands); - if (alloperands.length==0) - return new PtgNumber(0); - double min= Double.MAX_VALUE; - for (int i=0;i biggest){ - biggest = size.doubleValue(); - Double newhigh = (Double)vals.elementAt(i); - retval = newhigh.doubleValue(); - } - } - PtgNumber pnum = new PtgNumber(retval); - return pnum; - } + } + + /** + * MINA + * Returns the smallest value in a list of arguments, including numbers, text, and logical values + *

                + * Arguments can be the following: numbers; names, arrays, or references that contain numbers; text representations of numbers; or logical values, such as TRUE and FALSE, in a reference. + * Logical values and text representations of numbers that you type directly into the list of arguments are counted. + */ + protected static Ptg calcMinA(Ptg[] operands) { + Ptg[] alloperands = PtgCalculator.getAllComponents(operands); + if (alloperands.length == 0) + return new PtgNumber(0); + double min = Double.MAX_VALUE; + for (int i = 0; i < alloperands.length; i++) { + Object o = alloperands[i].getValue(); + try { + double d = Double.MAX_VALUE; + if (o instanceof Number) + d = ((Number) o).doubleValue(); + else if (o instanceof Boolean) + d = (((Boolean) o).booleanValue() ? 1 : 0); + else + d = new Double(o.toString()).doubleValue(); + min = Math.min(min, d); + } catch (NumberFormatException e) { + // Arguments that are error values or text that cannot be translated into numbers cause errors. + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + return new PtgNumber(min); + } + + /** + * MODE + * Returns the most common value in a data set + */ + protected static Ptg calcMode(Ptg[] operands) { + Ptg[] alloperands = PtgCalculator.getAllComponents(operands); + Vector vals = new Vector(); + Vector occurences = new Vector(); + double retval = 0; + for (int i = 0; i < alloperands.length; i++) { + Ptg p = alloperands[i]; + try { + Double d = new Double(String.valueOf(p.getValue())); + if (vals.contains(d)) { + int loc = vals.indexOf(d); + Double nums = (Double) occurences.get(loc); + Double newnum = new Double(nums.doubleValue() + 1); + occurences.setElementAt(newnum, loc); + } else { + vals.add(d); + occurences.add(new Double(1)); + } + } catch (NumberFormatException e) { + } + } + double biggest = 0; + double numvalues = 0; + for (int i = 0; i < vals.size(); i++) { + Double size = (Double) occurences.elementAt(i); + if (size.doubleValue() > biggest) { + biggest = size.doubleValue(); + Double newhigh = (Double) vals.elementAt(i); + retval = newhigh.doubleValue(); + } + } + PtgNumber pnum = new PtgNumber(retval); + return pnum; + } /* NEGBINOMDIST Returns the negative binomial distribution */ - - /**NORMDIST - Returns the normal cumulative distribution - NORMDIST(x,mean,standard_dev,cumulative) - X is the value for which you want the distribution. - Mean is the arithmetic mean of the distribution. - Standard_dev is the standard deviation of the distribution. - Cumulative is a logical value that determines the form of the function. - If cumulative is TRUE, NORMDIST returns the cumulative distribution function; - if FALSE, it returns the probability mass function. - - ********************************************************************************* - IMPORTANT NOTE: when Cumulative=TRUE the results are not accurate to 9 siginfiicant digits in all cases - (When cumulative = TRUE, the formula is the integral from negative infinity to x of the given formula) - ********************************************************************************* - */ - protected static Ptg calcNormdist(Ptg[] operands){ - if (operands.length<4) return new PtgErr(PtgErr.ERROR_VALUE); - try { - //If mean or standard_dev is nonnumeric, NORMDIST returns the #VALUE! error value. - double x= operands[0].getDoubleVal(); - double mean= operands[1].getDoubleVal(); - // if standard_dev ≤ 0, NORMDIST returns the #NUM! error value. - double stddev= operands[2].getDoubleVal(); - if (stddev <= 0) return new PtgErr(PtgErr.ERROR_NUM); - boolean cumulative= PtgCalculator.getBooleanValue(operands[3]); - // If mean = 0, standard_dev = 1, and cumulative = TRUE, NORMDIST returns the standard normal distribution, NORMSDIST. - if (mean==0 && stddev==1.0 && cumulative) - return calcNormsdist(operands); - - if (!cumulative) { // return the probability mass function. *** definite excel algorithm - double a= Math.sqrt(2*Math.PI*Math.pow(stddev,2)); - a= 1.0/a; - double exp= Math.pow(x-mean, 2); - exp= exp/(2*Math.pow(stddev, 2)); - double b= Math.exp(-exp); - return new PtgNumber(a*b); - } else { - // When cumulative = TRUE, the formula is the integral from negative infinity to x of the given formula. - // = the cumulative distribution function - Ptg[] o= { new PtgNumber((x-mean)/(stddev*Math.sqrt(2))) }; - Ptg erf= EngineeringCalculator.calcErf(o); - double cdf= 0.5*(1+erf.getDoubleVal()); - return new PtgNumber(cdf); + + /** + * NORMDIST + * Returns the normal cumulative distribution + * NORMDIST(x,mean,standard_dev,cumulative) + * X is the value for which you want the distribution. + * Mean is the arithmetic mean of the distribution. + * Standard_dev is the standard deviation of the distribution. + * Cumulative is a logical value that determines the form of the function. + * If cumulative is TRUE, NORMDIST returns the cumulative distribution function; + * if FALSE, it returns the probability mass function. + *

                + * ******************************************************************************** + * IMPORTANT NOTE: when Cumulative=TRUE the results are not accurate to 9 siginfiicant digits in all cases + * (When cumulative = TRUE, the formula is the integral from negative infinity to x of the given formula) + * ******************************************************************************** + */ + protected static Ptg calcNormdist(Ptg[] operands) { + if (operands.length < 4) return new PtgErr(PtgErr.ERROR_VALUE); + try { + //If mean or standard_dev is nonnumeric, NORMDIST returns the #VALUE! error value. + double x = operands[0].getDoubleVal(); + double mean = operands[1].getDoubleVal(); + // if standard_dev ≤ 0, NORMDIST returns the #NUM! error value. + double stddev = operands[2].getDoubleVal(); + if (stddev <= 0) return new PtgErr(PtgErr.ERROR_NUM); + boolean cumulative = PtgCalculator.getBooleanValue(operands[3]); + // If mean = 0, standard_dev = 1, and cumulative = TRUE, NORMDIST returns the standard normal distribution, NORMSDIST. + if (mean == 0 && stddev == 1.0 && cumulative) + return calcNormsdist(operands); + + if (!cumulative) { // return the probability mass function. *** definite excel algorithm + double a = Math.sqrt(2 * Math.PI * Math.pow(stddev, 2)); + a = 1.0 / a; + double exp = Math.pow(x - mean, 2); + exp = exp / (2 * Math.pow(stddev, 2)); + double b = Math.exp(-exp); + return new PtgNumber(a * b); + } else { + // When cumulative = TRUE, the formula is the integral from negative infinity to x of the given formula. + // = the cumulative distribution function + Ptg[] o = {new PtgNumber((x - mean) / (stddev * Math.sqrt(2)))}; + Ptg erf = EngineeringCalculator.calcErf(o); + double cdf = 0.5 * (1 + erf.getDoubleVal()); + return new PtgNumber(cdf); /* // try this: Ptg[] o= { new PtgNumber((x-mean)/(stddev))}; return calcNormsdist(o); - */ - } - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - /** - The NORMSDIST function returns the result of the standard normal cumulative distribution function - for a particular value of the random variable X. The Excel function adheres to the - following mathematical approximation, P(x), of the following - standard normal cumulative distribution function (CDF): - - * P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - t = 1/(1+px) - p = 0.2316419 - b1 = 0.319381530 - b2 = -0.356563782 - b3 = 1.781477937 - b4 = -1.821255978 - b5 = 1.330274429 - - with the following parameters: - - abs(error(x))<7.5 * 10^-8 - - The NORMSDIST function returns the result of the standard normal - CDF for a standard normal random variable Z with a mean of 0 (zero) - and a standard deviation of 1. The CDF is found by taking the integral - of the following standard normal probability density function - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - - from negative infinity to the value (z) of the random variable in question. - The result of the integral gives the probability that Z will occur between the - values of negative infinity and z. - - NORMSDIST(z) must be evaluated by using an approximation procedure. - Earlier versions of Excel used the same procedure for all values of z. - For Excel 2003, two different approximations are used: - one for |z| less than or equal to five, and a second for |z| greater than five. - The two new procedures are each more accurate than the previous procedure - over the range that they are applied. In earlier versions of Excel, - accuracy deteriorates in the tails of the distribution yielding three significant - digits for z = 4 as reported in Knusel's paper. Also, in the neighborhood of z = 1.2, - NORMSDIST yields only six significant digits. However, in practice, this is likely - to be sufficient for most users. - - INFO ATP DEFINITION NORMDIST NOVEMBER 2006: - The NORMSDIST function returns the result of the standard normal cumulative distribution function for a particular value of the random variable X. - The Microsoft Excel function adheres to the following mathematical approximation, P(x), of the standard normal CDF - - P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - t = 1/(1+px) - p = 0.2316419 - b1 = 0.319381530 - b2 = -0.356563782 - b3 = 1.781477937 - b4 = -1.821255978 - b5 = 1.330274429 - - - with these parameters, abs(error(x))<7.5 * 10^-8. - - - In summary, if you use Excel 2002 and earlier, you should be satisfied with NORMSDIST. - However, if you must have highly accurate NORMSDIST(z) values for z far from 0 - (such as |z| greater than or equal to four), Excel 2003 might be required. - NORMSDIST(-4) = 0.0000316712; earlier versions would be accurate only as far as 0.0000317. - -from a forum: -Take into consideration that Z is related to x, xm(mean) and s(std.dev.) -through the expression Z = (x - xm) / s. -This means that as soon as you get Z, you can proceed and calculate the integral of the -CDF by using P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x). - -I wish you good code. - -Some other identities that express NORMSDIST in terms of other functions -that have no closed form are -NormSDist(x) = ErfC(-x/Sqrt(2))/2 = (1-Erf(-x/Sqrt(2)))/2 for x<=0 -NormSDist(x) = 1-ErfC(x/Sqrt(2))/2 = (1+Erf(x/Sqrt(2)))/2 for x>=0 -NormSDist(x) = (1–GammaDist(x^2/2,1/2,1,TRUE))/2 for x<=0 -NormSDist(x) = (1+GammaDist(x^2/2,1/2,1,TRUE))/2 for x>=0 -NormSDist(x) = ChiDist(x^2,1)/2 for x<=0 -NormSDist(x) = 1-ChiDist(x^2,1)/2 for x>=0 - - // for 2002: - The NORMSDIST function returns the result of the standard normal cumulative distribution - function for a particular value of the random variable X. The Excel function adheres to the - following mathematical approximation, P(x), of the following standard normal cumulative - distribution function (CDF) - - P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - t = 1/(1+px) - p = 0.2316419 - b1 = 0.319381530 - b2 = -0.356563782 - b3 = 1.781477937 - b4 = -1.821255978 - b5 = 1.330274429 - - - with the following parameters: - abs(error(x))<7.5 * 10^-8 - - The NORMSDIST function returns the result of the standard normal CDF for a standard - normal random variable Z with a mean of 0 (zero) and a standard deviation of 1. The CDF - is found by taking the integral of the following standard normal probability density - function - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - - from negative infinity to the value (z) of the random variable in question. The result - of the integral gives the probability that Z will occur between the values of negative - infinity and z. * - - from openoffice: - The wrong results in NORMSDIST are due to cancellation for small negative -values, where gauss() is near -0.5 -The problem can be solved in two ways: -(1) Use NORMSDIST(x)= 0.5*ERFC(-x/SQRT(2)). Unfortunaly ERFC is only an addin -function, see my issue 97091. -(2) Use NORMSDIST(x) - = 0.5+0.5*GetLowRegIGamma(0.5,0.5*x*x) for x>=0 - = 0.5*GetUpRegIGamma(0.5,0.5*x*x) for x<0 - - - From a forum: -For z less than 2, ERF = 2/SQRT (pi) * e^(-z^2) * z (1+ (2z^2)/3 + ((2z^2)^2)/15 + … -For z greater than 2, ERF = 1- (e^(-z^2))/(SQRT(pi)) * (1/z - 1/(2z^3) + 3/(4z^5) -….) -*/ - /** - NORMSDIST - Returns the standard normal cumulative distribution - - NORMSDIST(z) returns the probability that the observed value of a - standard normal random variable will be less than or equal to z. - A standard normal random variable has mean 0 and standard deviation 1 - (and also variance 1 because variance = standard deviation squared). - - NOTE: THIS FUNCTION IS ACCURATE AS COMPARED TO EXCEL VALUES ONLY UP TO 7 SIGNIFICANT DIGITS -*/ - protected static Ptg calcNormsdist(Ptg[] operands){ - if (operands.length<1) return new PtgErr(PtgErr.ERROR_VALUE); - try { - double result; - double x= operands[0].getDoubleVal(); - final double b1 = 0.319381530; - final double b2 = -0.356563782; - final double b3 = 1.781477937; - final double b4 = -1.821255978; - final double b5 = 1.330274429; - final double p = 0.2316419; - final double c = 0.39894228; - - // below is consistently correct to at least 7 decimals using a range of test values - if(x >= 0.0) { - double t = 1.0 / ( 1.0 + p * x ); - result=(1.0 - c * Math.exp( -x * x / 2.0 ) * t *( t *( t * ( t * ( t * b5 + b4 ) + b3 ) + b2 ) + b1 )); - } else { - double t = 1.0 / ( 1.0 - p * x ); - result= (c * Math.exp(-x * x / 2.0) * t * ( t *( t * ( t * ( t * b5 + b4 ) + b3 ) + b2 ) + b1 )); - } + */ + } + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + + /** + The NORMSDIST function returns the result of the standard normal cumulative distribution function + for a particular value of the random variable X. The Excel function adheres to the + following mathematical approximation, P(x), of the following + standard normal cumulative distribution function (CDF): + * P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where + + Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + t = 1/(1+px) + p = 0.2316419 + b1 = 0.319381530 + b2 = -0.356563782 + b3 = 1.781477937 + b4 = -1.821255978 + b5 = 1.330274429 + + with the following parameters: + + abs(error(x))<7.5 * 10^-8 + + The NORMSDIST function returns the result of the standard normal + CDF for a standard normal random variable Z with a mean of 0 (zero) + and a standard deviation of 1. The CDF is found by taking the integral + of the following standard normal probability density function + + Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + + from negative infinity to the value (z) of the random variable in question. + The result of the integral gives the probability that Z will occur between the + values of negative infinity and z. + + NORMSDIST(z) must be evaluated by using an approximation procedure. + Earlier versions of Excel used the same procedure for all values of z. + For Excel 2003, two different approximations are used: + one for |z| less than or equal to five, and a second for |z| greater than five. + The two new procedures are each more accurate than the previous procedure + over the range that they are applied. In earlier versions of Excel, + accuracy deteriorates in the tails of the distribution yielding three significant + digits for z = 4 as reported in Knusel's paper. Also, in the neighborhood of z = 1.2, + NORMSDIST yields only six significant digits. However, in practice, this is likely + to be sufficient for most users. + + INFO ATP DEFINITION NORMDIST NOVEMBER 2006: + The NORMSDIST function returns the result of the standard normal cumulative distribution function for a particular value of the random variable X. + The Microsoft Excel function adheres to the following mathematical approximation, P(x), of the standard normal CDF + + P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where + + Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + t = 1/(1+px) + p = 0.2316419 + b1 = 0.319381530 + b2 = -0.356563782 + b3 = 1.781477937 + b4 = -1.821255978 + b5 = 1.330274429 + + + with these parameters, abs(error(x))<7.5 * 10^-8. + + + In summary, if you use Excel 2002 and earlier, you should be satisfied with NORMSDIST. + However, if you must have highly accurate NORMSDIST(z) values for z far from 0 + (such as |z| greater than or equal to four), Excel 2003 might be required. + NORMSDIST(-4) = 0.0000316712; earlier versions would be accurate only as far as 0.0000317. + + from a forum: + Take into consideration that Z is related to x, xm(mean) and s(std.dev.) + through the expression Z = (x - xm) / s. + This means that as soon as you get Z, you can proceed and calculate the integral of the + CDF by using P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x). + + I wish you good code. + + Some other identities that express NORMSDIST in terms of other functions + that have no closed form are + NormSDist(x) = ErfC(-x/Sqrt(2))/2 = (1-Erf(-x/Sqrt(2)))/2 for x<=0 + NormSDist(x) = 1-ErfC(x/Sqrt(2))/2 = (1+Erf(x/Sqrt(2)))/2 for x>=0 + NormSDist(x) = (1–GammaDist(x^2/2,1/2,1,TRUE))/2 for x<=0 + NormSDist(x) = (1+GammaDist(x^2/2,1/2,1,TRUE))/2 for x>=0 + NormSDist(x) = ChiDist(x^2,1)/2 for x<=0 + NormSDist(x) = 1-ChiDist(x^2,1)/2 for x>=0 + + // for 2002: + The NORMSDIST function returns the result of the standard normal cumulative distribution + function for a particular value of the random variable X. The Excel function adheres to the + following mathematical approximation, P(x), of the following standard normal cumulative + distribution function (CDF) + + P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where + + Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + t = 1/(1+px) + p = 0.2316419 + b1 = 0.319381530 + b2 = -0.356563782 + b3 = 1.781477937 + b4 = -1.821255978 + b5 = 1.330274429 + + + with the following parameters: + abs(error(x))<7.5 * 10^-8 + + The NORMSDIST function returns the result of the standard normal CDF for a standard + normal random variable Z with a mean of 0 (zero) and a standard deviation of 1. The CDF + is found by taking the integral of the following standard normal probability density + function + + Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + + from negative infinity to the value (z) of the random variable in question. The result + of the integral gives the probability that Z will occur between the values of negative + infinity and z. * + + from openoffice: + The wrong results in NORMSDIST are due to cancellation for small negative + values, where gauss() is near -0.5 + The problem can be solved in two ways: + (1) Use NORMSDIST(x)= 0.5*ERFC(-x/SQRT(2)). Unfortunaly ERFC is only an addin + function, see my issue 97091. + (2) Use NORMSDIST(x) + = 0.5+0.5*GetLowRegIGamma(0.5,0.5*x*x) for x>=0 + = 0.5*GetUpRegIGamma(0.5,0.5*x*x) for x<0 + + + From a forum: + For z less than 2, ERF = 2/SQRT (pi) * e^(-z^2) * z (1+ (2z^2)/3 + ((2z^2)^2)/15 + … + For z greater than 2, ERF = 1- (e^(-z^2))/(SQRT(pi)) * (1/z - 1/(2z^3) + 3/(4z^5) -….) + */ + /** + * NORMSDIST + * Returns the standard normal cumulative distribution + *

                + * NORMSDIST(z) returns the probability that the observed value of a + * standard normal random variable will be less than or equal to z. + * A standard normal random variable has mean 0 and standard deviation 1 + * (and also variance 1 because variance = standard deviation squared). + *

                + * NOTE: THIS FUNCTION IS ACCURATE AS COMPARED TO EXCEL VALUES ONLY UP TO 7 SIGNIFICANT DIGITS + */ + protected static Ptg calcNormsdist(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + try { + double result; + double x = operands[0].getDoubleVal(); + final double b1 = 0.319381530; + final double b2 = -0.356563782; + final double b3 = 1.781477937; + final double b4 = -1.821255978; + final double b5 = 1.330274429; + final double p = 0.2316419; + final double c = 0.39894228; + + // below is consistently correct to at least 7 decimals using a range of test values + if (x >= 0.0) { + double t = 1.0 / (1.0 + p * x); + result = (1.0 - c * Math.exp(-x * x / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1)); + } else { + double t = 1.0 / (1.0 - p * x); + result = (c * Math.exp(-x * x / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1)); + } /* // try this one: double z= (1/(Math.sqrt(2*Math.PI))*Math.exp(-Math.pow(x, 2)/2.0)); @@ -1421,229 +1463,226 @@ protected static Ptg calcNormsdist(Ptg[] operands){ double e= EngineeringCalculator.calcErf(operands).getDoubleVal(); result = 1 -z*(b1*t+b2*Math.pow(t, 2)+b3*Math.pow(t, 3)+b4*Math.pow(t, 4)+b5*Math.pow(t, 5))+e; */ - BigDecimal bd= new BigDecimal(result); - bd.setScale(15, java.math.RoundingMode.HALF_UP); - return new PtgNumber(bd.doubleValue()); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - /** - NORMSINV - Returns the inverse of the standard normal cumulative distribution. The distribution has a mean of zero and a standard deviation of one. - - Syntax - NORMSINV(probability) - Probability is a probability corresponding to the normal distribution. - - If probability is nonnumeric, NORMSINV returns the #VALUE! error value. - If probability < 0 or if probability > 1, NORMSINV returns the #NUM! error value. - - Because the calculation of the NORMSINV function uses a systematic search - over the returned values of the NORMSDIST function, the accuracy of the - NORMSDIST function is critical. - - Also, the search must be sufficiently refined that it "homes in" on an appropriate - answer. To use the textbook Normal probability distribution table as an analogy, - entries in the table must be accurate. Also, the table must contain so many entries - that you can find the appropriate row of the table that yields a probability that is - correct to a specific number of decimal places. Instead, individual entries are computed - on demand as the search through the "table" - - However, the table must be accurate and the search must continue far enough - that it does not stop prematurely at an answer that has a corresponding probability - (or row of the table) that is too far from the value of p that you use in the call to - NORMSINV(p). Therefore, the NORMSINV function has been improved in the following ways: - - - The accuracy of the NORMSDIST function has been improved. - - The search process has been improved to increase refinement. - - The NORMSDIST function has been improved in Excel 2003 and in later versions of Excel. - Typically, inaccuracies in earlier versions of Excel occur for extremely small or extremely - large values of p in NORMSINV(p). The values in Excel 2003 and in later versions of Excel - are much more accurate. - - Accuracy of NORMSDIST has been improved in Excel 2003 and in later versions of Excel. - In earlier versions of Excel, a single computational procedure was used for all values - of z. Results were essentially accurate to 7 decimal places, more than sufficient for - most practical examples. - - Results in earlier versions of Excel -*** The accuracy of the NORMSINV function depends on two factors. Because the calculation of the NORMSINV - function uses a systematic search over the returned values of the NORMSDIST function, the accuracy of - the NORMSDIST function is critical. - - Also, the search must be sufficiently refined that it "homes in" on an appropriate answer. - To use the textbook Normal probability distribution table as an analogy, entries in the table - must be accurate. Also, the table must contain so many entries that you can find the - appropriate row of the table that yields a probability that is correct to a specific number - of decimal places. - -' This function is a replacement for the Microsoft Excel Worksheet function NORMSINV. -' It uses the algorithm of Peter J. Acklam to compute the inverse normal cumulative -' distribution. Refer to http://home.online.no/~pjacklam/notes/invnorm/index.html for -' a description of the algorithm. -' Adapted to VB by Christian d'Heureuse, http://www.source-code.biz. -Public Function NormSInv(ByVal p As Double) As Double - Const a1 = -39.6968302866538, a2 = 220.946098424521, a3 = -275.928510446969 - Const a4 = 138.357751867269, a5 = -30.6647980661472, a6 = 2.50662827745924 - Const b1 = -54.4760987982241, b2 = 161.585836858041, b3 = -155.698979859887 - Const b4 = 66.8013118877197, b5 = -13.2806815528857, c1 = -7.78489400243029E-03 - Const c2 = -0.322396458041136, c3 = -2.40075827716184, c4 = -2.54973253934373 - Const c5 = 4.37466414146497, c6 = 2.93816398269878, d1 = 7.78469570904146E-03 - Const d2 = 0.32246712907004, d3 = 2.445134137143, d4 = 3.75440866190742 - Const p_low = 0.02425, p_high = 1 - p_low - Dim q As Double, r As Double - If p < 0 Or p > 1 Then - Err.Raise vbObjectError, , "NormSInv: Argument out of range." - ElseIf p < p_low Then - q = Sqr(-2 * Log(p)) - NormSInv = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _ - ((((d1 * q + d2) * q + d3) * q + d4) * q + 1) - ElseIf p <= p_high Then - q = p - 0.5: r = q * q - NormSInv = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q / _ - (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1) - Else - q = Sqr(-2 * Log(1 - p)) - NormSInv = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _ - ((((d1 * q + d2) * q + d3) * q + d4) * q + 1) - End If - End Function - - NORMSINV= NORMINV(p; 0; 1) - */ - private static double expm1(double x) - { - final double DBL_EPSILON=0.0000001; - double y, a = Math.abs(x); - - if (a < DBL_EPSILON) return x; - if (a > 0.697) return Math.exp(x) - 1; /* negligible cancellation */ - - if (a > 1e-8) - y = Math.exp(x) - 1; - else /* Taylor expansion, more accurate in this range */ - y = (x / 2 + 1) * x; - - /* Newton step for solving log(1 + y) = x for y : */ - /* WARNING: does not work for y ~ -1: bug in 1.5.0 -- fixed??*/ - y -= (1 + y) * (Math.log(1+y) - x); - return y; - } - - private int R_Q_P01_check(int p, boolean log_p) { - if ((log_p && p > 0) || (!log_p && (p < 0 || p > 1)) ) - return 0; - return 1; - } - - private static double quartile(double p, double mu, double sigma) - { - boolean lower_tail = true; - boolean log_p = false; - double R_D__0= 0; - double R_D__1= 1; - double R_DT_0= 0; //((lower_tail) ? R_D__0 : R_D__1); /* 0 */ - double R_DT_1= 1; // ((lower_tail) ? R_D__1 : R_D__0) /* 1 */ - - double p_, q, r, val; - if (p==R_DT_0) return Double.NEGATIVE_INFINITY; - if (p==R_DT_1) return Double.POSITIVE_INFINITY; - //R_Q_P01_check(p); - - if(sigma < 0) return 0; - if(sigma == 0) return mu; - - p= (log_p ? (lower_tail ? Math.exp(p) : - expm1(p)) : (lower_tail ? (p) : (1 - (p)))); - p_ = (log_p ? (lower_tail ? Math.exp(p) : - expm1(p)) : (lower_tail ? (p) : (1 - (p))));/* real lower_tail prob. p */ - q = p_ - 0.5; - - if (Math.abs(q) <= .425) {/* 0.075 <= p <= 0.925 */ - r = .180625 - q * q; - val = - q * (((((((r * 2509.0809287301226727 + - 33430.575583588128105) * r + 67265.770927008700853) * r + - 45921.953931549871457) * r + 13731.693765509461125) * r + - 1971.5909503065514427) * r + 133.14166789178437745) * r + - 3.387132872796366608) - / (((((((r * 5226.495278852854561 + - 28729.085735721942674) * r + 39307.89580009271061) * r + - 21213.794301586595867) * r + 5394.1960214247511077) * r + - 687.1870074920579083) * r + 42.313330701600911252) * r + 1.); - } - else { /* closer than 0.075 from {0,1} boundary */ - - /* r = min(p, 1-p) < 0.075 */ - if (q > 0) - r = (log_p ? (lower_tail ? -expm1(p) : Math.exp(p)) : (lower_tail ? (1 - (p)) : (p)));/* 1-p */ - else - r = p_;/* = R_DT_Iv(p) ^= p */ - - r = Math.sqrt(-((log_p && ((lower_tail && q <= 0) || (!lower_tail && q > 0))) ? p : /* else */ Math.log(r))); - /* r = sqrt(-log(r)) <==> min(p, 1-p) = exp( - r^2 ) */ - - if (r <= 5.) { /* <==> min(p,1-p) >= exp(-25) ~= 1.3888e-11 */ - r += -1.6; - val = (((((((r * 7.7454501427834140764e-4 + - .0227238449892691845833) * r + .24178072517745061177) * - r + 1.27045825245236838258) * r + - 3.64784832476320460504) * r + 5.7694972214606914055) * - r + 4.6303378461565452959) * r + - 1.42343711074968357734) - / (((((((r * - 1.05075007164441684324e-9 + 5.475938084995344946e-4) * - r + .0151986665636164571966) * r + - .14810397642748007459) * r + .68976733498510000455) * - r + 1.6763848301838038494) * r + - 2.05319162663775882187) * r + 1.); - } - else { /* very close to 0 or 1 */ - r += -5.; - val = (((((((r * 2.01033439929228813265e-7 + - 2.71155556874348757815e-5) * r + - .0012426609473880784386) * r + .026532189526576123093) * - r + .29656057182850489123) * r + - 1.7848265399172913358) * r + 5.4637849111641143699) * - r + 6.6579046435011037772) - / (((((((r * - 2.04426310338993978564e-15 + 1.4215117583164458887e-7)* - r + 1.8463183175100546818e-5) * r + - 7.868691311456132591e-4) * r + .0148753612908506148525) - * r + .13692988092273580531) * r + - .59983220655588793769) * r + 1.); - } - - if(q < 0.0) - val = -val; - /* return (q >= 0.)? r : -r ;*/ - } - return mu + sigma * val; - } - public static Ptg calcNormInv(Ptg[] operands) { - try { - - double p= operands[0].getDoubleVal(); - if (p < 0 || p > 1) return new PtgErr(PtgErr.ERROR_NUM); - double mean= operands[1].getDoubleVal(); - double stddev= operands[2].getDoubleVal(); - if (stddev <= 0) return new PtgErr(PtgErr.ERROR_NUM); - // If mean = 0 and standard_dev = 1, NORMINV uses the standard normal inverse (see NORMSINV). - double result= quartile(p, mean, stddev); - return new PtgNumber(result); - } catch(Exception e) {;} - return new PtgErr(PtgErr.ERROR_VALUE); - } - + BigDecimal bd = new BigDecimal(result); + bd.setScale(15, java.math.RoundingMode.HALF_UP); + return new PtgNumber(bd.doubleValue()); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } - public static Ptg calcNormsInv(Ptg[] operands) - { - if (operands.length!=1) return PtgCalculator.getValueError(); - try { - double x= operands[0].getDoubleVal(); - if (x < 0 || x > 1) - return new PtgErr(PtgErr.ERROR_NUM); + /** + * NORMSINV + * Returns the inverse of the standard normal cumulative distribution. The distribution has a mean of zero and a standard deviation of one. + *

                + * Syntax + * NORMSINV(probability) + * Probability is a probability corresponding to the normal distribution. + *

                + * If probability is nonnumeric, NORMSINV returns the #VALUE! error value. + * If probability < 0 or if probability > 1, NORMSINV returns the #NUM! error value. + *

                + * Because the calculation of the NORMSINV function uses a systematic search + * over the returned values of the NORMSDIST function, the accuracy of the + * NORMSDIST function is critical. + *

                + * Also, the search must be sufficiently refined that it "homes in" on an appropriate + * answer. To use the textbook Normal probability distribution table as an analogy, + * entries in the table must be accurate. Also, the table must contain so many entries + * that you can find the appropriate row of the table that yields a probability that is + * correct to a specific number of decimal places. Instead, individual entries are computed + * on demand as the search through the "table" + *

                + * However, the table must be accurate and the search must continue far enough + * that it does not stop prematurely at an answer that has a corresponding probability + * (or row of the table) that is too far from the value of p that you use in the call to + * NORMSINV(p). Therefore, the NORMSINV function has been improved in the following ways: + *

                + * - The accuracy of the NORMSDIST function has been improved. + * - The search process has been improved to increase refinement. + *

                + * The NORMSDIST function has been improved in Excel 2003 and in later versions of Excel. + * Typically, inaccuracies in earlier versions of Excel occur for extremely small or extremely + * large values of p in NORMSINV(p). The values in Excel 2003 and in later versions of Excel + * are much more accurate. + *

                + * Accuracy of NORMSDIST has been improved in Excel 2003 and in later versions of Excel. + * In earlier versions of Excel, a single computational procedure was used for all values + * of z. Results were essentially accurate to 7 decimal places, more than sufficient for + * most practical examples. + *

                + * Results in earlier versions of Excel + * ** The accuracy of the NORMSINV function depends on two factors. Because the calculation of the NORMSINV + * function uses a systematic search over the returned values of the NORMSDIST function, the accuracy of + * the NORMSDIST function is critical. + *

                + * Also, the search must be sufficiently refined that it "homes in" on an appropriate answer. + * To use the textbook Normal probability distribution table as an analogy, entries in the table + * must be accurate. Also, the table must contain so many entries that you can find the + * appropriate row of the table that yields a probability that is correct to a specific number + * of decimal places. + *

                + * ' This function is a replacement for the Microsoft Excel Worksheet function NORMSINV. + * ' It uses the algorithm of Peter J. Acklam to compute the inverse normal cumulative + * ' distribution. Refer to http://home.online.no/~pjacklam/notes/invnorm/index.html for + * ' a description of the algorithm. + * ' Adapted to VB by Christian d'Heureuse, http://www.source-code.biz. + * Public Function NormSInv(ByVal p As Double) As Double + * Const a1 = -39.6968302866538, a2 = 220.946098424521, a3 = -275.928510446969 + * Const a4 = 138.357751867269, a5 = -30.6647980661472, a6 = 2.50662827745924 + * Const b1 = -54.4760987982241, b2 = 161.585836858041, b3 = -155.698979859887 + * Const b4 = 66.8013118877197, b5 = -13.2806815528857, c1 = -7.78489400243029E-03 + * Const c2 = -0.322396458041136, c3 = -2.40075827716184, c4 = -2.54973253934373 + * Const c5 = 4.37466414146497, c6 = 2.93816398269878, d1 = 7.78469570904146E-03 + * Const d2 = 0.32246712907004, d3 = 2.445134137143, d4 = 3.75440866190742 + * Const p_low = 0.02425, p_high = 1 - p_low + * Dim q As Double, r As Double + * If p < 0 Or p > 1 Then + * Err.Raise vbObjectError, , "NormSInv: Argument out of range." + * ElseIf p < p_low Then + * q = Sqr(-2 * Log(p)) + * NormSInv = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _ + * ((((d1 * q + d2) * q + d3) * q + d4) * q + 1) + * ElseIf p <= p_high Then + * q = p - 0.5: r = q * q + * NormSInv = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q / _ + * (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1) + * Else + * q = Sqr(-2 * Log(1 - p)) + * NormSInv = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _ + * ((((d1 * q + d2) * q + d3) * q + d4) * q + 1) + * End If + * End Function + *

                + * NORMSINV= NORMINV(p; 0; 1) + */ + private static double expm1(double x) { + final double DBL_EPSILON = 0.0000001; + double y, a = Math.abs(x); + + if (a < DBL_EPSILON) return x; + if (a > 0.697) return Math.exp(x) - 1; /* negligible cancellation */ + + if (a > 1e-8) + y = Math.exp(x) - 1; + else /* Taylor expansion, more accurate in this range */ + y = (x / 2 + 1) * x; + + /* Newton step for solving log(1 + y) = x for y : */ + /* WARNING: does not work for y ~ -1: bug in 1.5.0 -- fixed??*/ + y -= (1 + y) * (Math.log(1 + y) - x); + return y; + } + + private int R_Q_P01_check(int p, boolean log_p) { + if ((log_p && p > 0) || (!log_p && (p < 0 || p > 1))) + return 0; + return 1; + } + + private static double quartile(double p, double mu, double sigma) { + boolean lower_tail = true; + boolean log_p = false; + double R_D__0 = 0; + double R_D__1 = 1; + double R_DT_0 = 0; //((lower_tail) ? R_D__0 : R_D__1); /* 0 */ + double R_DT_1 = 1; // ((lower_tail) ? R_D__1 : R_D__0) /* 1 */ + + double p_, q, r, val; + if (p == R_DT_0) return Double.NEGATIVE_INFINITY; + if (p == R_DT_1) return Double.POSITIVE_INFINITY; + //R_Q_P01_check(p); + + if (sigma < 0) return 0; + if (sigma == 0) return mu; + + p = (log_p ? (lower_tail ? Math.exp(p) : -expm1(p)) : (lower_tail ? (p) : (1 - (p)))); + p_ = (log_p ? (lower_tail ? Math.exp(p) : -expm1(p)) : (lower_tail ? (p) : (1 - (p))));/* real lower_tail prob. p */ + q = p_ - 0.5; + + if (Math.abs(q) <= .425) {/* 0.075 <= p <= 0.925 */ + r = .180625 - q * q; + val = + q * (((((((r * 2509.0809287301226727 + + 33430.575583588128105) * r + 67265.770927008700853) * r + + 45921.953931549871457) * r + 13731.693765509461125) * r + + 1971.5909503065514427) * r + 133.14166789178437745) * r + + 3.387132872796366608) + / (((((((r * 5226.495278852854561 + + 28729.085735721942674) * r + 39307.89580009271061) * r + + 21213.794301586595867) * r + 5394.1960214247511077) * r + + 687.1870074920579083) * r + 42.313330701600911252) * r + 1.); + } else { /* closer than 0.075 from {0,1} boundary */ + + /* r = min(p, 1-p) < 0.075 */ + if (q > 0) + r = (log_p ? (lower_tail ? -expm1(p) : Math.exp(p)) : (lower_tail ? (1 - (p)) : (p)));/* 1-p */ + else + r = p_;/* = R_DT_Iv(p) ^= p */ + + r = Math.sqrt(-((log_p && ((lower_tail && q <= 0) || (!lower_tail && q > 0))) ? p : /* else */ Math.log(r))); + /* r = sqrt(-log(r)) <==> min(p, 1-p) = exp( - r^2 ) */ + + if (r <= 5.) { /* <==> min(p,1-p) >= exp(-25) ~= 1.3888e-11 */ + r += -1.6; + val = (((((((r * 7.7454501427834140764e-4 + + .0227238449892691845833) * r + .24178072517745061177) * + r + 1.27045825245236838258) * r + + 3.64784832476320460504) * r + 5.7694972214606914055) * + r + 4.6303378461565452959) * r + + 1.42343711074968357734) + / (((((((r * + 1.05075007164441684324e-9 + 5.475938084995344946e-4) * + r + .0151986665636164571966) * r + + .14810397642748007459) * r + .68976733498510000455) * + r + 1.6763848301838038494) * r + + 2.05319162663775882187) * r + 1.); + } else { /* very close to 0 or 1 */ + r += -5.; + val = (((((((r * 2.01033439929228813265e-7 + + 2.71155556874348757815e-5) * r + + .0012426609473880784386) * r + .026532189526576123093) * + r + .29656057182850489123) * r + + 1.7848265399172913358) * r + 5.4637849111641143699) * + r + 6.6579046435011037772) + / (((((((r * + 2.04426310338993978564e-15 + 1.4215117583164458887e-7) * + r + 1.8463183175100546818e-5) * r + + 7.868691311456132591e-4) * r + .0148753612908506148525) + * r + .13692988092273580531) * r + + .59983220655588793769) * r + 1.); + } + + if (q < 0.0) + val = -val; + /* return (q >= 0.)? r : -r ;*/ + } + return mu + sigma * val; + } + + public static Ptg calcNormInv(Ptg[] operands) { + try { + + double p = operands[0].getDoubleVal(); + if (p < 0 || p > 1) return new PtgErr(PtgErr.ERROR_NUM); + double mean = operands[1].getDoubleVal(); + double stddev = operands[2].getDoubleVal(); + if (stddev <= 0) return new PtgErr(PtgErr.ERROR_NUM); + // If mean = 0 and standard_dev = 1, NORMINV uses the standard normal inverse (see NORMSINV). + double result = quartile(p, mean, stddev); + return new PtgNumber(result); + } catch (Exception e) { + } + return new PtgErr(PtgErr.ERROR_VALUE); + } + + + public static Ptg calcNormsInv(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getValueError(); + try { + double x = operands[0].getDoubleVal(); + if (x < 0 || x > 1) + return new PtgErr(PtgErr.ERROR_NUM); /* * the algorithm is supposed to iterate over NORMSDIST values using Newton-Raphson's approximation * Newton-Raphson uses an iterative process to approach one root of a function (i.e the zero of the function or @@ -1662,66 +1701,67 @@ You may also consider operating the process on the function f(x) = x1/3, using a is the derivative of the standard normal distribution = the standard probability density function??? */ -/* below is not accurate enough - but N-R approximation is impossible ((:*/ - // Coefficients in rational approximations - double[] a = new double[]{-3.969683028665376e+01, 2.209460984245205e+02, - -2.759285104469687e+02, 1.383577518672690e+02, - -3.066479806614716e+01, 2.506628277459239e+00}; - - double[] b = new double[]{-5.447609879822406e+01, 1.615858368580409e+02, - -1.556989798598866e+02, 6.680131188771972e+01, - -1.328068155288572e+01 }; - - double[] c = new double[]{-7.784894002430293e-03, -3.223964580411365e-01, - -2.400758277161838e+00, -2.549732539343734e+00, - 4.374664141464968e+00, 2.938163982698783e+00}; - - double[] d = new double[]{7.784695709041462e-03, 3.224671290700398e-01, - 2.445134137142996e+00, 3.754408661907416e+00}; - - // Define break-points. - double plow = 0.02425; - double phigh = 1 - plow; - double result; - // Rational approximation for lower region: - if ( x < plow ) { - double q = Math.sqrt(-2*Math.log(x)); - BigDecimal r= new BigDecimal((((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / - ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1)); - r.setScale(15, java.math.RoundingMode.HALF_UP); - return new PtgNumber(r.doubleValue()); - } - - // Rational approximation for upper region: - if ( phigh < x ) { - double q = Math.sqrt(-2*Math.log(1-x)); - BigDecimal r= new BigDecimal(-(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / - ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1)); - r.setScale(15, java.math.RoundingMode.HALF_UP); - return new PtgNumber(r.doubleValue()); - } - - // Rational approximation for central region: - double q = x - 0.5; - double r = q*q; - BigDecimal rr= new BigDecimal((((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5])*q / - (((((b[0]*r+b[1])*r+b[2])*r+b[3])*r+b[4])*r+1)); - - rr.setScale(15, java.math.RoundingMode.HALF_UP); - return new PtgNumber(rr.doubleValue()); - } catch (Exception e) { - return PtgCalculator.getValueError(); - } - } - - /** -PEARSON - Returns the Pearson product moment correlation coefficient - * @throws CalculationException - */ - public static Ptg calcPearson(Ptg[] operands) throws CalculationException{ - return calcCorrel(operands); - } + /* below is not accurate enough - but N-R approximation is impossible ((:*/ + // Coefficients in rational approximations + double[] a = new double[]{-3.969683028665376e+01, 2.209460984245205e+02, + -2.759285104469687e+02, 1.383577518672690e+02, + -3.066479806614716e+01, 2.506628277459239e+00}; + + double[] b = new double[]{-5.447609879822406e+01, 1.615858368580409e+02, + -1.556989798598866e+02, 6.680131188771972e+01, + -1.328068155288572e+01}; + + double[] c = new double[]{-7.784894002430293e-03, -3.223964580411365e-01, + -2.400758277161838e+00, -2.549732539343734e+00, + 4.374664141464968e+00, 2.938163982698783e+00}; + + double[] d = new double[]{7.784695709041462e-03, 3.224671290700398e-01, + 2.445134137142996e+00, 3.754408661907416e+00}; + + // Define break-points. + double plow = 0.02425; + double phigh = 1 - plow; + double result; + // Rational approximation for lower region: + if (x < plow) { + double q = Math.sqrt(-2 * Math.log(x)); + BigDecimal r = new BigDecimal((((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / + ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)); + r.setScale(15, java.math.RoundingMode.HALF_UP); + return new PtgNumber(r.doubleValue()); + } + + // Rational approximation for upper region: + if (phigh < x) { + double q = Math.sqrt(-2 * Math.log(1 - x)); + BigDecimal r = new BigDecimal(-(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / + ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)); + r.setScale(15, java.math.RoundingMode.HALF_UP); + return new PtgNumber(r.doubleValue()); + } + + // Rational approximation for central region: + double q = x - 0.5; + double r = q * q; + BigDecimal rr = new BigDecimal((((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q / + (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1)); + + rr.setScale(15, java.math.RoundingMode.HALF_UP); + return new PtgNumber(rr.doubleValue()); + } catch (Exception e) { + return PtgCalculator.getValueError(); + } + } + + /** + * PEARSON + * Returns the Pearson product moment correlation coefficient + * + * @throws CalculationException + */ + public static Ptg calcPearson(Ptg[] operands) throws CalculationException { + return calcCorrel(operands); + } /* PERCENTILE Returns the k-th percentile of values in a range @@ -1738,249 +1778,258 @@ public static Ptg calcPearson(Ptg[] operands) throws CalculationException{ PROB Returns the probability that values in a range are between two limits */ - /** -QUARTILE - Returns the quartile of a data set - */ - protected static Ptg calcQuartile(Ptg[] operands){ - Ptg[] aveoperands = new Ptg[1]; - aveoperands[0] = operands[0]; - Ptg[] allVals =PtgCalculator.getAllComponents(aveoperands); - CompatibleVector t = new CompatibleVector(); - double retval = 0; - for (int i=0;i 4 || quart.intValue()<0){ - return new PtgErr(PtgErr.ERROR_NUM); - } - // find the kth smallest - float kk = (float)(quartile/4); - kk = (dub.length-1)*kk; - kk++; - // truncate k, but keep the remainder. - int k = -1; - float remainder = 0; - if (kk%1 != 0){ - remainder = kk%1; - String s = String.valueOf(kk); - String ss = s.substring(s.indexOf("."), s.length()); - ss = "0" +ss; - remainder = new Float(ss).floatValue(); - s = s.substring(0, s.indexOf(".")); - k = Integer.valueOf(String.valueOf(s)).intValue(); - }else{ - k = (int)kk/1; - } - if (k >= dub.length) - return new PtgErr(PtgErr.ERROR_VALUE); - double firstVal = dub[k-1].doubleValue(); - double secondVal = dub[k].doubleValue(); - double output = firstVal + (remainder*(secondVal-firstVal)); - PtgNumber pn = new PtgNumber(output); - return pn; - } - /** - RANK - Returns the rank of a number in a list of numbers - - RANK(number,ref,order) - Number is the number whose rank you want to find. - Ref is an array of, or a reference to, a list of numbers. Nonnumeric values in ref are ignored. - Order is a number specifying how to rank number. - * If order is 0 (zero) or omitted, Microsoft Excel ranks number as if ref were a list sorted in descending order. - * If order is any nonzero value, Microsoft Excel ranks number as if ref were a list sorted in ascending order. - */ - protected static Ptg calcRank(Ptg[] operands){ - // the number - if (operands.length<2) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg num = operands[0]; - Double theNum = null; - try{ - Object o = num.getValue(); - if(o.equals("")) - theNum = new Double(0.0d); - else - theNum = new Double(o.toString()); - }catch(NumberFormatException nfm){ - return new PtgErr(); - } - - //ascending or decending? - boolean ascending = true; - if (operands.length < 3){ - ascending = false; - }else if (operands[2] instanceof PtgMissArg){ - ascending = false; - }else{ - PtgInt order = (PtgInt)operands[2]; - int i = order.getVal(); - if (i==0)ascending = false; - } - Ptg[] aveoperands = new Ptg[1]; - aveoperands[0] = operands[1]; - Ptg[] refs =PtgCalculator.getAllComponents(aveoperands); - CompatibleVector retList = new CompatibleVector(); - double retval = 0; - for (int i=0;i 4 || quart.intValue() < 0) { + return new PtgErr(PtgErr.ERROR_NUM); + } + // find the kth smallest + float kk = quartile / 4; + kk = (dub.length - 1) * kk; + kk++; + // truncate k, but keep the remainder. + int k = -1; + float remainder = 0; + if (kk % 1 != 0) { + remainder = kk % 1; + String s = String.valueOf(kk); + String ss = s.substring(s.indexOf(".")); + ss = "0" + ss; + remainder = new Float(ss).floatValue(); + s = s.substring(0, s.indexOf(".")); + k = Integer.valueOf(s).intValue(); + } else { + k = (int) kk / 1; + } + if (k >= dub.length) + return new PtgErr(PtgErr.ERROR_VALUE); + double firstVal = dub[k - 1].doubleValue(); + double secondVal = dub[k].doubleValue(); + double output = firstVal + (remainder * (secondVal - firstVal)); + PtgNumber pn = new PtgNumber(output); + return pn; + } + + /** + * RANK + * Returns the rank of a number in a list of numbers + *

                + * RANK(number,ref,order) + * Number is the number whose rank you want to find. + * Ref is an array of, or a reference to, a list of numbers. Nonnumeric values in ref are ignored. + * Order is a number specifying how to rank number. + *

                + * If order is 0 (zero) or omitted, Microsoft Excel ranks number as if ref were a list sorted in descending order. + * If order is any nonzero value, Microsoft Excel ranks number as if ref were a list sorted in ascending order. + */ + protected static Ptg calcRank(Ptg[] operands) { + // the number + if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); + Ptg num = operands[0]; + Double theNum = null; + try { + Object o = num.getValue(); + if (o.equals("")) + theNum = new Double(0.0d); + else + theNum = new Double(o.toString()); + } catch (NumberFormatException nfm) { + return new PtgErr(); + } + + //ascending or decending? + boolean ascending = true; + if (operands.length < 3) { + ascending = false; + } else if (operands[2] instanceof PtgMissArg) { + ascending = false; + } else { + PtgInt order = (PtgInt) operands[2]; + int i = order.getVal(); + if (i == 0) ascending = false; + } + Ptg[] aveoperands = new Ptg[1]; + aveoperands[0] = operands[1]; + Ptg[] refs = PtgCalculator.getAllComponents(aveoperands); + CompatibleVector retList = new CompatibleVector(); + double retval = 0; + for (int i = 0; i < refs.length; i++) { + Ptg p = refs[i]; + try { + Double d = new Double(String.valueOf(p.getValue())); + retList.addOrderedDouble(d); + } catch (NumberFormatException e) { + } + } + Double[] dubRefs = new Double[retList.size()]; + if (ascending) { + retList.toArray(dubRefs); + } else { + for (int i = 0; i < dubRefs.length; i++) { + dubRefs[i] = (Double) retList.last(); + retList.remove(retList.size() - 1); + } + } + int res = -1; + for (int i = 0; i < dubRefs.length; i++) { + if (dubRefs[i].toString().equalsIgnoreCase(theNum.toString())) { + res = i + 1; + i = dubRefs.length; + } + } + if (res == -1) { + return new PtgErr(PtgErr.ERROR_NA); + } else { + return new PtgInt(res); + } + + } + + + /** + * RSQ + * Returns the square of the Pearson product moment correlatin coefficient + * + * @throws CalculationException + */ + protected static Ptg calcRsq(Ptg[] operands) throws CalculationException { + PtgNumber p = (PtgNumber) calcPearson(operands); + double d = p.getVal(); + d = (d * d); + return new PtgNumber(d); + } /* SKEW Returns the skewness of a distribution */ - - /** -SLOPE - Returns the slope of the linear regression line - * @throws CalculationException - */ - protected static Ptg calcSlope(Ptg[] operands) throws CalculationException{ - if (operands.length!=2) - return new PtgErr(PtgErr.ERROR_VALUE); - double[] yvals = PtgCalculator.getDoubleValueArray(operands[0]); - double[] xvals = PtgCalculator.getDoubleValueArray(operands[1]); - if (xvals==null || yvals==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double sumXVals = 0; - for (int i=0;i array.length) - return new PtgErr(PtgErr.ERROR_NUM); - - CompatibleVector sortedValues = new CompatibleVector(); - for (int i=0;i + * SMALL(array,k) + * Array is an array or range of numerical data for which you want to determine the k-th smallest value. + * K is the position (from the smallest) in the array or range of data to return. + * + * @throws CalculationException + */ + protected static Ptg calcSmall(Ptg[] operands) throws CalculationException { + if (operands.length != 2) return new PtgErr(PtgErr.ERROR_VALUE); + Ptg rng = operands[0]; + Ptg[] array = PtgCalculator.getAllComponents(rng); + if (array.length == 0) return new PtgErr(PtgErr.ERROR_NUM); + int k = new Double(PtgCalculator.getDoubleValueArray(operands[1])[0]).intValue(); + if (k <= 0 || k > array.length) + return new PtgErr(PtgErr.ERROR_NUM); + + CompatibleVector sortedValues = new CompatibleVector(); + for (int i = 0; i < array.length; i++) { + Ptg p = array[i]; + try { + Double d = new Double(String.valueOf(p.getValue())); + sortedValues.addOrderedDouble(d); + } catch (NumberFormatException e) { + } + } + try { + return new PtgNumber(((Double) sortedValues.get(k - 1)).doubleValue()); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } /* STANDARDIZE Returns a normalized value */ - /** -STDEV(number1,number2, ...) -Number1,number2, ... are 1 to 255 number arguments corresponding to a sample of a population. -You can also use a single array or a reference to an array instead of arguments separated by commas. - * @throws CalculationException - */ - protected static Ptg calcStdev(Ptg[] operands) throws CalculationException{ - double[] allVals = PtgCalculator.getDoubleValueArray(operands); - if (allVals==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double sqrDev = 0; - for (int i = 0;i + * Number1,number2, ... are 1 to 255 number arguments corresponding to a sample of a population. + * You can also use a single array or a reference to an array instead of arguments separated by commas. + * + * @throws CalculationException + */ + protected static Ptg calcStdev(Ptg[] operands) throws CalculationException { + double[] allVals = PtgCalculator.getDoubleValueArray(operands); + if (allVals == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + double sqrDev = 0; + for (int i = 0; i < allVals.length; i++) { + PtgNumber p = (PtgNumber) calcAverage(operands); + double ave = p.getVal(); + sqrDev += Math.pow((allVals[i] - ave), 2); + } + double retval = Math.sqrt(sqrDev / (allVals.length - 1)); + return new PtgNumber(retval); + } /* STDEVA Estimates standard deviation based on a sample, including numbers, text, and logical values @@ -1991,31 +2040,32 @@ protected static Ptg calcStdev(Ptg[] operands) throws CalculationException{ STDEVPA Calculates standard deviation based on the entire population, including numbers, text, and logical values */ - - /** -STEYX - Returns the standard error of the predicted y-value for each x in the regression - * @throws CalculationException - */ - public static Ptg calcSteyx(Ptg[] operands) throws CalculationException{ - Ptg[] arr = new Ptg[1]; - arr[0] = operands[0]; - PtgNumber pn = (PtgNumber)calcVarp(arr); - double yVarp = pn.getVal(); - arr[0] = operands[1]; - pn = (PtgNumber)calcVarp(arr); - double xVarp = pn.getVal(); - double[] y = PtgCalculator.getDoubleValueArray(operands[0]); - if (y==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - yVarp *= y.length; - xVarp *= y.length; - pn = (PtgNumber)calcSlope(operands); - double slope = pn.getVal(); - double retval = yVarp-((slope*slope)*xVarp); - retval = retval/(y.length-2); - retval = Math.sqrt(retval); - return new PtgNumber(retval); - } + + /** + * STEYX + * Returns the standard error of the predicted y-value for each x in the regression + * + * @throws CalculationException + */ + public static Ptg calcSteyx(Ptg[] operands) throws CalculationException { + Ptg[] arr = new Ptg[1]; + arr[0] = operands[0]; + PtgNumber pn = (PtgNumber) calcVarp(arr); + double yVarp = pn.getVal(); + arr[0] = operands[1]; + pn = (PtgNumber) calcVarp(arr); + double xVarp = pn.getVal(); + double[] y = PtgCalculator.getDoubleValueArray(operands[0]); + if (y == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error + yVarp *= y.length; + xVarp *= y.length; + pn = (PtgNumber) calcSlope(operands); + double slope = pn.getVal(); + double retval = yVarp - ((slope * slope) * xVarp); + retval = retval / (y.length - 2); + retval = Math.sqrt(retval); + return new PtgNumber(retval); + } /* TDIST Returns the Student's t-distribution @@ -2023,43 +2073,44 @@ public static Ptg calcSteyx(Ptg[] operands) throws CalculationException{ TINV Returns the inverse of the Student's t-distribution */ - - /** -TREND - Returns values along a linear trend - * @throws CalculationException - */ - protected static Ptg calcTrend(Ptg[] operands) throws CalculationException{ - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + + /** + * TREND + * Returns values along a linear trend + * + * @throws CalculationException + */ + protected static Ptg calcTrend(Ptg[] operands) throws CalculationException { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); // KSC: THIS FUNCTION DOES NOT WORK AS EXPECTED: TODO: FIX! - - if (true) return new PtgErr(PtgErr.ERROR_VALUE); + + if (true) return new PtgErr(PtgErr.ERROR_VALUE); // Ptg[] forecast = new Ptg[3]; - Ptg[] forecast= new Ptg[2]; - forecast[0] = operands[0]; - if (operands.length > 1) - forecast[1] = operands[1]; + Ptg[] forecast = new Ptg[2]; + forecast[0] = operands[0]; + if (operands.length > 1) + forecast[1] = operands[1]; // TODO: // else // If known_x's is omitted, it is assumed to be the array {1,2,3,...} that is the same size as known_y's. - Ptg[] newXs; - if (operands.length > 2) - newXs = PtgCalculator.getAllComponents(operands[2]); - else - newXs = PtgCalculator.getAllComponents(operands[1]); - - String retval = ""; - for (int i=0;i 2) + newXs = PtgCalculator.getAllComponents(operands[2]); + else + newXs = PtgCalculator.getAllComponents(operands[1]); + + String retval = ""; + for (int i = 0; i < newXs.length; i++) { + //forecast[0] = newXs[i]; + PtgNumber p = (PtgNumber) calcForecast(forecast); + double forcst = p.getVal(); + retval += "{" + forcst + "},"; + } + // get rid of trailing comma + retval = retval.substring(0, retval.length() - 1); + PtgArray pa = new PtgArray(); + pa.setVal(retval); + return pa; + } /* TRIMMEAN @@ -2068,44 +2119,48 @@ protected static Ptg calcTrend(Ptg[] operands) throws CalculationException{ TTEST Returns the probability associated with a Student's t-Test */ -/** - * VAR - Estimates variance based on a sample - * @throws CalculationException - */ - protected static Ptg calcVar(Ptg[] operands) throws CalculationException{ - double[] allVals = PtgCalculator.getDoubleValueArray(operands); - if (allVals==null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double sqrDev = 0; - for (int i = 0;i. @@ -22,306 +22,310 @@ */ package io.starter.formats.XLS.formulas; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; import io.starter.OpenXLS.DateConverter; import io.starter.OpenXLS.ExcelTools; import io.starter.OpenXLS.WorkBookHandle; -import io.starter.OpenXLS.FormatHandle; import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.Formula; import io.starter.formats.XLS.XLSConstants; import io.starter.formats.XLS.Xf; +import io.starter.toolkit.Logger; +import io.starter.toolkit.StringTool; import java.io.UnsupportedEncodingException; -import java.lang.Math; import java.text.DecimalFormat; import java.text.Format; import java.util.Date; /** - TextCalculator is a collection of static methods that operate - as the Microsoft Excel function calls do. + * TextCalculator is a collection of static methods that operate + * as the Microsoft Excel function calls do. + *

                + * All methods are called with an array of ptg's, which are then + * acted upon. A Ptg of the type that makes sense (ie boolean, number) + * is returned after each method. + */ +public class TextCalculator { - All methods are called with an array of ptg's, which are then - acted upon. A Ptg of the type that makes sense (ie boolean, number) - is returned after each method. -*/ -public class TextCalculator -{ - - /** - * ASC function - For Double-byte character set (DBCS) languages, changes full-width (double-byte) characters to half-width (single-byte) characters. - ASC(text) - Text is the text or a reference to a cell that contains the text you want to change. If text does not contain any full-width letters, text is not changed. - NOTE: in order to use this and other DBCS Methods in Excel, - the input language must be set to a DBCS language such as Japanese - Otherwise, the ASC function does nothing (apparently) - */ - protected static Ptg calcAsc(Ptg[] operands) { - if (operands==null || operands[0]==null) - return new PtgErr(PtgErr.ERROR_VALUE); - // determine if Excel's language is set up for DBCS; if not, returns normal string - io.starter.formats.XLS.WorkBook bk= operands[0].getParentRec().getWorkBook(); - if (bk.defaultLanguageIsDBCS()) { // otherwise just returns normal string - byte[] strbytes= getUnicodeBytesFromOp(operands[0]); - if (strbytes==null) - strbytes= (operands[0].getValue()).toString().getBytes(); - try { - return new PtgStr(new String(strbytes, XLSConstants.UNICODEENCODING)); - } catch (Exception e){ - } - } - return new PtgStr(operands[0].getValue().toString()); - } + /** + * ASC function + * For Double-byte character set (DBCS) languages, changes full-width (double-byte) characters to half-width (single-byte) characters. + * ASC(text) + * Text is the text or a reference to a cell that contains the text you want to change. If text does not contain any full-width letters, text is not changed. + * NOTE: in order to use this and other DBCS Methods in Excel, + * the input language must be set to a DBCS language such as Japanese + * Otherwise, the ASC function does nothing (apparently) + */ + protected static Ptg calcAsc(Ptg[] operands) { + if (operands == null || operands[0] == null) + return new PtgErr(PtgErr.ERROR_VALUE); + // determine if Excel's language is set up for DBCS; if not, returns normal string + io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); + if (bk.defaultLanguageIsDBCS()) { // otherwise just returns normal string + byte[] strbytes = getUnicodeBytesFromOp(operands[0]); + if (strbytes == null) + strbytes = (operands[0].getValue()).toString().getBytes(); + try { + return new PtgStr(new String(strbytes, XLSConstants.UNICODEENCODING)); + } catch (Exception e) { + } + } + return new PtgStr(operands[0].getValue().toString()); + } /*BAHTTEXT function Converts a number to Thai text and adds a suffix of "Baht." */ -/** - CHAR - Returns the character specified by the code number - */ - protected static Ptg calcChar(Ptg[] operands){ - Object o = operands[0].getValue(); - Byte s = new Byte(o.toString()); - if (s.intValue() > 255 || s.intValue() < 1)return PtgCalculator.getError(); - byte[] b = new byte[1]; - b[0] = s.byteValue(); - String str = ""; - try{ - str = new String(b, XLSConstants.DEFAULTENCODING); - }catch(UnsupportedEncodingException e){}; - return new PtgStr(str); - } - /** - CLEAN - Removes all nonprintable characters from text. Use CLEAN on text - imported from other applications that contains characters that may - not print with your operating system. For example, you can use - CLEAN to remove some low-level computer code that is frequently - at the beginning and end of data files and cannot be printed. - - Syntax - - CLEAN(text) - - Text is any worksheet information from which you want to remove nonprintable characters. - - The CLEAN function was designed to remove the first 32 nonprinting characters in the 7-bit ASCII code (values 0 through 31) from text. In the Unicode character set (Unicode: A character encoding standard developed by the Unicode Consortium. By using more than one byte to represent each character, Unicode enables almost all of the written languages in the world to be represented by using a single character set.), there are additional nonprinting characters (values 127, 129, 141, 143, 144, and 157). By itself, the CLEAN function does not remove these additional nonprinting characters. - */ - protected static Ptg calcClean(Ptg[] operands){ - String retString= ""; - try { - Object o= operands[0].getValue(); - String s = o.toString(); - for (int i= 0; i < s.length(); i++) { - int c= s.charAt(i); - if (c>=32) - retString+=(char) c; - } - }catch(Exception e){ ; } - return new PtgStr(retString); - } - - - /** - CODE - Returns a numeric code for the first character in a text string - */ - protected static Ptg calcCode(Ptg[] operands){ - Object o = operands[0].getValue(); - String s = o.toString(); - byte[] b = null; - try{ - b = s.getBytes(XLSConstants.DEFAULTENCODING); - }catch(UnsupportedEncodingException e){}; - Integer i = Integer.valueOf(b[0]); - return new PtgInt(i.intValue()); - } - - - /** - CONCATENATE - Joins several text items into one text item - */ - protected static Ptg calcConcatenate(Ptg[] operands){ - if (operands.length<1) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg[] allops = PtgCalculator.getAllComponents(operands); - String s = ""; - for (int i=0;i 1){ - pop = operands[1].getIntVal(); - } - double d = operands[0].getDoubleVal(); - d = d*Math.pow(10, pop); - d = Math.round(d); - d = d/Math.pow(10, pop); - String res = "$" + String.valueOf(d); - return new PtgStr(res); - } - - /** - EXACT - Checks to see if two text values are identical - */ - protected static Ptg calcExact(Ptg[] operands){ - if (operands.length != 2)return PtgCalculator.getError(); - String s1 = operands[0].getValue().toString(); - String s2 = operands[1].getValue().toString(); - if (s1.equals(s2))return new PtgBool(true); - return new PtgBool(false); - } - /** - FIND - Finds one text value within another (case-sensitive) - */ - protected static Ptg calcFind(Ptg[] operands){ - String instring = ""; - String wholestr = ""; - int start = 0; - if (operands.length<2) return new PtgErr(PtgErr.ERROR_VALUE); - if (operands.length == 3){ - start = operands[2].getIntVal()-1; - } - Object o = operands[0].getValue(); - Object oo = operands[1].getValue(); - - if (o == null || oo == null)return new PtgErr(PtgErr.ERROR_VALUE); - - instring = o.toString(); - wholestr = oo.toString(); - // note this uses a starting position to search for the string, - // but does not account for that starting position in respects to it's - // result. Pretty strange - int i = wholestr.indexOf(instring, start); - if (i != -1){ - i = wholestr.indexOf(instring); - i++; - return new PtgInt(i); - } - return new PtgErr(PtgErr.ERROR_VALUE); - - } - /** - * FINDB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, FINDB counts each character as 1. - NOTES: search is case sensitive and doesn't allow for wildcards - */ - protected static Ptg calcFindB(Ptg[] operands){ - if (operands==null || operands.length<2 || operands[0]==null) - return new PtgErr(PtgErr.ERROR_VALUE); - // determine if Excel's language is set up for DBCS; if not, returns normal string - io.starter.formats.XLS.WorkBook bk= operands[0].getParentRec().getWorkBook(); - if (!bk.defaultLanguageIsDBCS()) { // otherwise just use calcFind - return calcFind(operands); - } - int startnum= 0; - if (operands.length>2) - startnum= operands[2].getIntVal(); - byte[] strToFind= getUnicodeBytesFromOp(operands[0]); - byte[] str= getUnicodeBytesFromOp(operands[1]); - int index= -1; - if (strToFind==null || strToFind.length==0 || str==null || startnum<0 || str.length-1){ - res = res.substring(0,res.indexOf(".")); - return new PtgStr(res); - } - } - // pad w/zeros if need be. - if(res.indexOf(".")==-1&&pop>0) { - res = res + ".0"; - } - String mantissa = res.substring(res.indexOf(".")); - while (mantissa.length()<=pop) { - res += 0; - mantissa = res.substring(res.indexOf(".")); - } - if (nocommas || dub < 999.99){ - return new PtgStr(res); - } - - int e = res.indexOf("."); - String mant = res.substring(e); - String begin = res.substring(0, e); - int counter = 0; - int s = begin.length(); - // this adds the commas; - for (int v=0;v 255 || s.intValue() < 1) return PtgCalculator.getError(); + byte[] b = new byte[1]; + b[0] = s.byteValue(); + String str = ""; + try { + str = new String(b, XLSConstants.DEFAULTENCODING); + } catch (UnsupportedEncodingException e) { + } + return new PtgStr(str); + } + + /** + * CLEAN + * Removes all nonprintable characters from text. Use CLEAN on text + * imported from other applications that contains characters that may + * not print with your operating system. For example, you can use + * CLEAN to remove some low-level computer code that is frequently + * at the beginning and end of data files and cannot be printed. + *

                + * Syntax + *

                + * CLEAN(text) + *

                + * Text is any worksheet information from which you want to remove nonprintable characters. + *

                + * The CLEAN function was designed to remove the first 32 nonprinting characters in the 7-bit ASCII code (values 0 through 31) from text. In the Unicode character set (Unicode: A character encoding standard developed by the Unicode Consortium. By using more than one byte to represent each character, Unicode enables almost all of the written languages in the world to be represented by using a single character set.), there are additional nonprinting characters (values 127, 129, 141, 143, 144, and 157). By itself, the CLEAN function does not remove these additional nonprinting characters. + */ + protected static Ptg calcClean(Ptg[] operands) { + String retString = ""; + try { + Object o = operands[0].getValue(); + String s = o.toString(); + for (int i = 0; i < s.length(); i++) { + int c = s.charAt(i); + if (c >= 32) + retString += (char) c; + } + } catch (Exception e) { + } + return new PtgStr(retString); + } + + + /** + * CODE + * Returns a numeric code for the first character in a text string + */ + protected static Ptg calcCode(Ptg[] operands) { + Object o = operands[0].getValue(); + String s = o.toString(); + byte[] b = null; + try { + b = s.getBytes(XLSConstants.DEFAULTENCODING); + } catch (UnsupportedEncodingException e) { + } + Integer i = Integer.valueOf(b[0]); + return new PtgInt(i.intValue()); + } + + + /** + * CONCATENATE + * Joins several text items into one text item + */ + protected static Ptg calcConcatenate(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + Ptg[] allops = PtgCalculator.getAllComponents(operands); + String s = ""; + for (int i = 0; i < allops.length; i++) { + s += allops[i].getValue().toString(); + } + Ptg str = new PtgStr(s); + str.setParentRec(operands[0].getParentRec()); + return str; + } + + /** + * DOLLAR + * Converts a number to text, using currency format. Can + * have a separate operand to determine POP. + */ + protected static Ptg calcDollar(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + int pop = 0; + if (operands.length > 1) { + pop = operands[1].getIntVal(); + } + double d = operands[0].getDoubleVal(); + d = d * Math.pow(10, pop); + d = Math.round(d); + d = d / Math.pow(10, pop); + String res = "$" + d; + return new PtgStr(res); + } + + /** + * EXACT + * Checks to see if two text values are identical + */ + protected static Ptg calcExact(Ptg[] operands) { + if (operands.length != 2) return PtgCalculator.getError(); + String s1 = operands[0].getValue().toString(); + String s2 = operands[1].getValue().toString(); + if (s1.equals(s2)) return new PtgBool(true); + return new PtgBool(false); + } + + /** + * FIND + * Finds one text value within another (case-sensitive) + */ + protected static Ptg calcFind(Ptg[] operands) { + String instring = ""; + String wholestr = ""; + int start = 0; + if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); + if (operands.length == 3) { + start = operands[2].getIntVal() - 1; + } + Object o = operands[0].getValue(); + Object oo = operands[1].getValue(); + + if (o == null || oo == null) return new PtgErr(PtgErr.ERROR_VALUE); + + instring = o.toString(); + wholestr = oo.toString(); + // note this uses a starting position to search for the string, + // but does not account for that starting position in respects to it's + // result. Pretty strange + int i = wholestr.indexOf(instring, start); + if (i != -1) { + i = wholestr.indexOf(instring); + i++; + return new PtgInt(i); + } + return new PtgErr(PtgErr.ERROR_VALUE); + + } + + /** + * FINDB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, FINDB counts each character as 1. + * NOTES: search is case sensitive and doesn't allow for wildcards + */ + protected static Ptg calcFindB(Ptg[] operands) { + if (operands == null || operands.length < 2 || operands[0] == null) + return new PtgErr(PtgErr.ERROR_VALUE); + // determine if Excel's language is set up for DBCS; if not, returns normal string + io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); + if (!bk.defaultLanguageIsDBCS()) { // otherwise just use calcFind + return calcFind(operands); + } + int startnum = 0; + if (operands.length > 2) + startnum = operands[2].getIntVal(); + byte[] strToFind = getUnicodeBytesFromOp(operands[0]); + byte[] str = getUnicodeBytesFromOp(operands[1]); + int index = -1; + if (strToFind == null || strToFind.length == 0 || str == null || startnum < 0 || str.length < startnum) + return new PtgInt(startnum); + for (int i = startnum; i < str.length && index == -1; i++) { + if (strToFind[0] == str[i]) { + index = i; + for (int j = 0; j < strToFind.length && (i + j) < str.length && index == i; j++) { + if (strToFind[j] != str[i + j]) { + index = -1; // start over + break; + } + } + } + } + if (index == -1)// not found + new PtgErr(PtgErr.ERROR_VALUE); + return new PtgInt(index + 1); // return 1-based index of found bytes + } + + /** + * FIXED + * Formats a number as text with a fixed number of decimals + */ + protected static Ptg calcFixed(Ptg[] operands) { + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + boolean nocommas = false; + if (operands.length == 3) { + Boolean boo = (Boolean) operands[2].getValue(); + nocommas = boo.booleanValue(); + } + double dub = operands[0].getDoubleVal(); + if (dub == Double.NaN) dub = 0; + int pop = operands[1].getIntVal(); + dub = dub * Math.pow(10, pop); + dub = Math.round(dub); + dub = dub / Math.pow(10, pop); + String res = String.valueOf(dub); + if (pop == 0) { + if (res.indexOf(".") > -1) { + res = res.substring(0, res.indexOf(".")); + return new PtgStr(res); + } + } + // pad w/zeros if need be. + if (res.indexOf(".") == -1 && pop > 0) { + res = res + ".0"; + } + String mantissa = res.substring(res.indexOf(".")); + while (mantissa.length() <= pop) { + res += 0; + mantissa = res.substring(res.indexOf(".")); + } + if (nocommas || dub < 999.99) { + return new PtgStr(res); + } + + int e = res.indexOf("."); + String mant = res.substring(e); + String begin = res.substring(0, e); + int counter = 0; + int s = begin.length(); + // this adds the commas; + for (int v = 0; v < s; ) { + String ch = begin.substring((s - v) - 1, s - v); + mant = ch + mant; + v++; + if (counter == 2 && v != s) mant = "," + mant; + counter++; + if (counter == 3) counter = 0; + } + return new PtgStr(mant); + + } + + /** + * JIS function + * The function described in this Help topic converts half-width (single-byte) + * letters within a character string to full-width (double-byte) characters. + * The name of the function (and the characters that it converts) depends upon your language settings. + * For Japanese, this function changes half-width (single-byte) English letters or + * katakana within a character string to full-width (double-byte) characters. + * JIS(text) + * Text is the text or a reference to a cell that contains the text you want to change. If text does not contain any half-width English letters or katakana, text is not changed. + *

                + * TODO: STRING ENCODING IS NOT CORRECT ************** + */ /* * encoding info: Shift_JIS DBCS 16-bit Japanese encoding (Note that you must use an underscore character (_), not a hyphen (-) in the name in CFML attributes.) @@ -330,489 +334,497 @@ The name of the function (and the characters that it converts) depends upon your UCS-2 DBCS Two-byte Unicode encoding UTF-8 MBCS Multibyte Unicode encoding. ASCII is 7-bit; non-ASCII characters used in European and many Middle Eastern languages are two-byte; and most Asian characters are three-byte */ - protected static Ptg calcJIS(Ptg[] operands) { - if (operands==null || operands[0]==null) - return new PtgErr(PtgErr.ERROR_VALUE); - // determine if Excel's language is set up for DBCS; if not, returns normal string - io.starter.formats.XLS.WorkBook bk= operands[0].getParentRec().getWorkBook(); - if (bk.defaultLanguageIsDBCS()) { // otherwise just returns normal string - byte[] strbytes= getUnicodeBytesFromOp(operands[0]); - if (strbytes==null) - strbytes= (operands[0].getValue()).toString().getBytes(); - try { - return new PtgStr(new String(strbytes, "Shift_JIS")); - } catch (Exception e){ - } - } - return new PtgStr(operands[0].getValue().toString()); - } - - /** - LEFT - Returns the leftmost characters from a text value - */ - protected static Ptg calcLeft(Ptg[] operands){ - int numchars = 1; - if (operands.length<1) return new PtgErr(PtgErr.ERROR_VALUE); - if (operands[0] instanceof PtgErr) - return new PtgErr(PtgErr.ERROR_NA); // 'tis what excel does - if (operands.length == 2){ - if (operands[1] instanceof PtgErr) - return new PtgErr(PtgErr.ERROR_VALUE); - numchars = operands[1].getIntVal(); - } - Object o= operands[0].getValue(); - if (o==null) return new PtgStr(""); - String str = String.valueOf(o); - if (str==null || numchars > str.length()) - return new PtgStr(""); // 20081202 KSC: Don't error out if not enough chars ala Excel - String res = str.substring(0, numchars); - return new PtgStr(res); - } - /** - * LEFTB counts each double-byte character as 2 when you have enabled the editing of a - * language that supports DBCS and then set it as the default language. - * Otherwise, LEFTB counts each character as 1. - */ - protected static Ptg calcLeftB(Ptg[] operands) { - io.starter.formats.XLS.WorkBook bk= operands[0].getParentRec().getWorkBook(); - if (bk.defaultLanguageIsDBCS()) {// otherwise just returns normal string - int numchars = 1; - if (operands.length<1) return new PtgErr(PtgErr.ERROR_VALUE); - if (operands.length == 2){ - if (operands[1] instanceof PtgErr) - return new PtgErr(PtgErr.ERROR_VALUE); - try { - numchars = operands[1].getIntVal(); - byte[] b= new byte[numchars]; - System.arraycopy(getUnicodeBytesFromOp(operands[0]), 0, b, 0, numchars); - return new PtgStr(new String(b, XLSConstants.UNICODEENCODING)); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - } - return calcLeft(operands); - } - - /** - LEN - Returns the number of characters in a text string - */ - protected static Ptg calcLen(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getError(); - String s = String.valueOf(operands[0].getValue()); - return new PtgInt(s.length()); - } - - /** - * LENB counts each double-byte character as 2 when you have enabled the editing of - * a language that supports DBCS and then set it as the default language. - * Otherwise, LENB counts each character as 1. - */ - protected static Ptg calcLenB(Ptg[] operands){ - if (operands.length != 1)return PtgCalculator.getError(); - io.starter.formats.XLS.WorkBook bk= operands[0].getParentRec().getWorkBook(); - if (bk.defaultLanguageIsDBCS()) // otherwise just returns normal string - return new PtgInt(getUnicodeBytesFromOp(operands[0]).length); - String s = String.valueOf(operands[0].getValue()); - return new PtgInt(s.length()); - } - - /** - LOWER - Converts text to lowercase - */ - protected static Ptg calcLower(Ptg[] operands){ - if (operands.length > 1)return PtgCalculator.getError(); - String s = String.valueOf(operands[0].getValue()); - s = s.toLowerCase(); - return new PtgStr(s); - } - /** - MID - Returns a specific number of characters from a text string starting at the position you specify - */ - protected static Ptg calcMid(Ptg[] operands){ - String s = String.valueOf(operands[0].getValue()); - if (s==null || s.equals("")) return new PtgStr(""); // Don't error out if "" ala Excel - if(operands[1] instanceof PtgErr || operands[2] instanceof PtgErr)return new PtgErr(PtgErr.ERROR_VALUE); - int start = operands[1].getIntVal() - 1; - int len = operands[2].getIntVal(); - if(len<0){ - len = start + len; - } - if(s.length()s.length())return new PtgStr(s); - s = s.substring(0,len); - return new PtgStr(s); - } - /* - * MIDB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, MIDB counts each character as 1. - */ + protected static Ptg calcJIS(Ptg[] operands) { + if (operands == null || operands[0] == null) + return new PtgErr(PtgErr.ERROR_VALUE); + // determine if Excel's language is set up for DBCS; if not, returns normal string + io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); + if (bk.defaultLanguageIsDBCS()) { // otherwise just returns normal string + byte[] strbytes = getUnicodeBytesFromOp(operands[0]); + if (strbytes == null) + strbytes = (operands[0].getValue()).toString().getBytes(); + try { + return new PtgStr(new String(strbytes, "Shift_JIS")); + } catch (Exception e) { + } + } + return new PtgStr(operands[0].getValue().toString()); + } + + /** + * LEFT + * Returns the leftmost characters from a text value + */ + protected static Ptg calcLeft(Ptg[] operands) { + int numchars = 1; + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + if (operands[0] instanceof PtgErr) + return new PtgErr(PtgErr.ERROR_NA); // 'tis what excel does + if (operands.length == 2) { + if (operands[1] instanceof PtgErr) + return new PtgErr(PtgErr.ERROR_VALUE); + numchars = operands[1].getIntVal(); + } + Object o = operands[0].getValue(); + if (o == null) return new PtgStr(""); + String str = String.valueOf(o); + if (str == null || numchars > str.length()) + return new PtgStr(""); // 20081202 KSC: Don't error out if not enough chars ala Excel + String res = str.substring(0, numchars); + return new PtgStr(res); + } + + /** + * LEFTB counts each double-byte character as 2 when you have enabled the editing of a + * language that supports DBCS and then set it as the default language. + * Otherwise, LEFTB counts each character as 1. + */ + protected static Ptg calcLeftB(Ptg[] operands) { + io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); + if (bk.defaultLanguageIsDBCS()) {// otherwise just returns normal string + int numchars = 1; + if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); + if (operands.length == 2) { + if (operands[1] instanceof PtgErr) + return new PtgErr(PtgErr.ERROR_VALUE); + try { + numchars = operands[1].getIntVal(); + byte[] b = new byte[numchars]; + System.arraycopy(getUnicodeBytesFromOp(operands[0]), 0, b, 0, numchars); + return new PtgStr(new String(b, XLSConstants.UNICODEENCODING)); + } catch (Exception e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + } + return calcLeft(operands); + } + + /** + * LEN + * Returns the number of characters in a text string + */ + protected static Ptg calcLen(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getError(); + String s = String.valueOf(operands[0].getValue()); + return new PtgInt(s.length()); + } + + /** + * LENB counts each double-byte character as 2 when you have enabled the editing of + * a language that supports DBCS and then set it as the default language. + * Otherwise, LENB counts each character as 1. + */ + protected static Ptg calcLenB(Ptg[] operands) { + if (operands.length != 1) return PtgCalculator.getError(); + io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); + if (bk.defaultLanguageIsDBCS()) // otherwise just returns normal string + return new PtgInt(getUnicodeBytesFromOp(operands[0]).length); + String s = String.valueOf(operands[0].getValue()); + return new PtgInt(s.length()); + } + + /** + * LOWER + * Converts text to lowercase + */ + protected static Ptg calcLower(Ptg[] operands) { + if (operands.length > 1) return PtgCalculator.getError(); + String s = String.valueOf(operands[0].getValue()); + s = s.toLowerCase(); + return new PtgStr(s); + } + + /** + * MID + * Returns a specific number of characters from a text string starting at the position you specify + */ + protected static Ptg calcMid(Ptg[] operands) { + String s = String.valueOf(operands[0].getValue()); + if (s == null || s.equals("")) return new PtgStr(""); // Don't error out if "" ala Excel + if (operands[1] instanceof PtgErr || operands[2] instanceof PtgErr) return new PtgErr(PtgErr.ERROR_VALUE); + int start = operands[1].getIntVal() - 1; + int len = operands[2].getIntVal(); + if (len < 0) { + len = start + len; + } + if (s.length() < start) return new PtgStr(""); + if (start == -1) return new PtgErr(PtgErr.ERROR_VALUE); + s = s.substring(start); + if (len > s.length()) return new PtgStr(s); + s = s.substring(0, len); + return new PtgStr(s); + } + /* + * MIDB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, MIDB counts each character as 1. + */ /* * PHONETIC function Extracts the phonetic (furigana) characters from a text string. PHONETIC(reference) Reference is a text string or a reference to a single cell or a range of cells that contain a furigana text string. */ - /** - PROPER - Capitalizes the first letter in each word of a text value - */ - protected static Ptg calcProper(Ptg[] operands){ - String s = String.valueOf(operands[0].getValue()); - s = StringTool.proper(s); - return new PtgStr(s); - } - /** - REPLACE - Replaces characters within text - */ - protected static Ptg calcReplace(Ptg[] operands){ - String origstr = String.valueOf(operands[0].getValue()); - int start = operands[1].getIntVal(); - int repamount = operands[2].getIntVal(); - String repstr = String.valueOf(operands[3].getValue()); - String begin = origstr.substring(0, (start-1)); - String end = origstr.substring(start + repamount - 1); - String returnstr = begin + repstr + end; - return new PtgStr(returnstr); - - } - /* - * REPLACEB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, REPLACEB counts each character as 1. - */ - /** - REPT - Repeats text a given number of times - */ - protected static Ptg calcRept(Ptg[] operands){ - String origstr = String.valueOf(operands[0].getValue()); - int numtimes = operands[1].getIntVal(); - String retstr = ""; - for (int i=0;i origstr.length())numchars = origstr.length(); - if(numchars<0)return new PtgErr(PtgErr.ERROR_VALUE); - String res = origstr.substring(origstr.length() - numchars); - return new PtgStr(res); - } - /* - * RIGHTB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, RIGHTB counts each character as 1. - */ - - /** - SEARCH - Finds one text value within another (not case-sensitive) - */ - protected static Ptg calcSearch(Ptg[] operands){ - if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); - int start = 0; - if (operands.length == 3){ - start = operands[2].getIntVal()-1; - } - String search = operands[0].getValue().toString().toLowerCase(); - String orig = operands[1].getValue().toString().toLowerCase(); - String tmp = orig.substring(start).toLowerCase(); - int i = tmp.indexOf(search); - if (i == -1)return new PtgErr(PtgErr.ERROR_VALUE); - i = orig.indexOf(search); - i++; - return new PtgInt(i); - } - - /** - * SEARCHB counts each double-byte character as 2 when you have enabled the editing of a - * language that supports DBCS and then set it as the default language. - * Otherwise, SEARCHB counts each character as 1. - * - * TODO: THIS IS NOT COMPLETE - */ - protected static Ptg calcSearchB(Ptg[] operands) { - if (operands==null || operands.length<2 || operands[0]==null) - return new PtgErr(PtgErr.ERROR_VALUE); - // determine if Excel's language is set up for DBCS; if not, returns normal string - io.starter.formats.XLS.WorkBook bk= operands[0].getParentRec().getWorkBook(); - if (!bk.defaultLanguageIsDBCS()) { // otherwise just use calcFind - return calcSearch(operands); - } - int startnum= 0; - if (operands.length>2) - startnum= operands[2].getIntVal(); - byte[] strToFind= getUnicodeBytesFromOp(operands[0]); - byte[] str= getUnicodeBytesFromOp(operands[1]); - int index= -1; - if (strToFind==null || strToFind.length==0 || str==null || startnum<0 || str.length origstr.length()) numchars = origstr.length(); + if (numchars < 0) return new PtgErr(PtgErr.ERROR_VALUE); + String res = origstr.substring(origstr.length() - numchars); + return new PtgStr(res); + } + /* + * RIGHTB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, RIGHTB counts each character as 1. + */ + + /** + * SEARCH + * Finds one text value within another (not case-sensitive) + */ + protected static Ptg calcSearch(Ptg[] operands) { + if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); + int start = 0; + if (operands.length == 3) { + start = operands[2].getIntVal() - 1; + } + String search = operands[0].getValue().toString().toLowerCase(); + String orig = operands[1].getValue().toString().toLowerCase(); + String tmp = orig.substring(start).toLowerCase(); + int i = tmp.indexOf(search); + if (i == -1) return new PtgErr(PtgErr.ERROR_VALUE); + i = orig.indexOf(search); + i++; + return new PtgInt(i); + } + + /** + * SEARCHB counts each double-byte character as 2 when you have enabled the editing of a + * language that supports DBCS and then set it as the default language. + * Otherwise, SEARCHB counts each character as 1. + *

                + * TODO: THIS IS NOT COMPLETE + */ + protected static Ptg calcSearchB(Ptg[] operands) { + if (operands == null || operands.length < 2 || operands[0] == null) + return new PtgErr(PtgErr.ERROR_VALUE); + // determine if Excel's language is set up for DBCS; if not, returns normal string + io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); + if (!bk.defaultLanguageIsDBCS()) { // otherwise just use calcFind + return calcSearch(operands); + } + int startnum = 0; + if (operands.length > 2) + startnum = operands[2].getIntVal(); + byte[] strToFind = getUnicodeBytesFromOp(operands[0]); + byte[] str = getUnicodeBytesFromOp(operands[1]); + int index = -1; + if (strToFind == null || strToFind.length == 0 || str == null || startnum < 0 || str.length < startnum) + return new PtgInt(startnum); + + String search = operands[0].getValue().toString().toLowerCase(); + String orig = operands[1].getValue().toString().toLowerCase(); + String tmp = orig.substring(startnum).toLowerCase(); + index = tmp.indexOf(search); + + if (index == -1)// not found + new PtgErr(PtgErr.ERROR_VALUE); + else + index *= 2; // count the bytes as double + return new PtgInt(index + 1); // return 1-based index of found bytes + } + + /** + * SUBSTITUTE + * Substitutes new text for old text in a text string + */ + protected static Ptg calcSubstitute(Ptg[] operands) { + int whichreplace = 0; + if (operands.length < 3) return new PtgErr(PtgErr.ERROR_VALUE); + if (operands.length == 4) + whichreplace = operands[3].getIntVal() - 1; + String origstr = operands[0].getValue().toString(); + String srchstr = operands[1].getValue().toString(); + String repstr = operands[2].getValue().toString(); + String finalstr = StringTool.replaceText(origstr, srchstr, repstr, whichreplace, true); + return new PtgStr(finalstr); + } + + /** + * T + * According to documentation converts its arguments to text - + *

                + * not really though, it just returns value if they are text + */ + protected static Ptg calcT(Ptg[] operands) { + String res = ""; + try { + res = (String) operands[0].getValue(); + } catch (ClassCastException e) { + } + return new PtgStr(res); + + } + + /** + * TEXT + * Formats a number and converts it to text + *

                + * Converts a value to text in a specific number format. + *

                + * Syntax + *

                + * TEXT(value,format_text) + *

                + * Value is a numeric value, a formula that evaluates to a numeric value, or a reference to a cell containing a numeric value. + *

                + * Format_text is a number format in text form from in the Category box on the Number tab in the Format Cells dialog box. + *

                + * Remarks + *

                + * Format_text cannot contain an asterisk (*). + *

                + * Formatting a cell with an option on the Number tab (Cells command, Format menu) changes only the format, not the value. Using the TEXT function converts a value to formatted text, and the result is no longer calculated as a number. + *

                + * Salesperson Sales + * Buchanan 2800 + * Dodsworth 40% + *

                + * Formula Description (Result) + * =A2&" sold "&TEXT(B2, "$0.00")&" worth of units." Combines contents above into a phrase (Buchanan sold $2800.00 worth of units.) + * =A3&" sold "&TEXT(B3,"0%")&" of the total sales." Combines contents above into a phrase (Dodsworth sold 40% of the total sales.) + */ + protected static Ptg calcText(Ptg[] operands) { + if (operands.length != 2) + return new PtgErr(PtgErr.ERROR_VALUE); + String res = "#ERR!"; + try { + res = String.valueOf(operands[0].getValue()); + } catch (Exception e) { + res = operands[0].toString(); + } + + String fmt = operands[1].toString(); + Format fmtx = null; + // convert a string like "0" + // to a format pattern like: "##"; + for (int t = 0; t < FormatConstants.NUMERIC_FORMATS.length; t++) { + String fmx = FormatConstants.NUMERIC_FORMATS[t][0]; + if (fmx.equals(fmt)) { + fmt = FormatConstants.NUMERIC_FORMATS[t][2]; + fmtx = new DecimalFormat(fmt); + } + } + if (fmtx == null) { + for (int t = 0; t < FormatConstants.CURRENCY_FORMATS.length; t++) { String fmx = FormatConstants.CURRENCY_FORMATS[t][0]; - if(fmx.equals(fmt)){ + if (fmx.equals(fmt)) { fmt = FormatConstants.CURRENCY_FORMATS[t][2]; fmtx = new DecimalFormat(fmt); } } } - if(fmtx!=null){ - try { - if (res!=null && !res.equals("")) // 20090527 KSC: when cell=="", Excel treats as 0 - return new PtgStr(fmtx.format(new Float(res))); - else if (res!=null) - return new PtgStr(fmtx.format(0)); - }catch(Exception e) { + if (fmtx != null) { + try { + if (res != null && !res.equals("")) // 20090527 KSC: when cell=="", Excel treats as 0 + return new PtgStr(fmtx.format(new Float(res))); + else if (res != null) + return new PtgStr(fmtx.format(0)); + } catch (Exception e) { // Logger.logWarn("getting formatted string value for :" + res.toString() + " failed: " + e.toString()) ; - try { - return new PtgStr(res); // 20080211 KSC: Double.valueOf(ret.toString()); - }catch(NumberFormatException nbe) { - ; // who knew? - of course, functions don't have to return numbers! - } - } - } + try { + return new PtgStr(res); // 20080211 KSC: Double.valueOf(ret.toString()); + } catch (NumberFormatException nbe) { + // who knew? - of course, functions don't have to return numbers! + } + } + } for (int x = 0; x < FormatConstants.DATE_FORMATS.length; x++) { - String fmx = FormatConstants.DATE_FORMATS[x][0]; - if(fmx.equals(fmt)){ - fmt = FormatConstants.DATE_FORMATS[x][2]; - + String fmx = FormatConstants.DATE_FORMATS[x][0]; + if (fmx.equals(fmt)) { + fmt = FormatConstants.DATE_FORMATS[x][2]; + + try { + Date d; try { - Date d; - try { - d = DateConverter.getDateFromNumber(new Double(res)); - } catch (NumberFormatException e) { - d= DateConverter.getDate(res); // try to convert a string date - if (d==null) - // what excel does, if it's an empty date, it reverts to jan 0, 1900 - d= new Date("1/1/1990"); - } - //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); - try{ - //sdf = new SimpleDateFormat(fmt); - WorkBookHandle.simpledateformat.applyPattern(fmt); - }catch(Exception ex) { - Logger.logWarn("Simple Date Format could not parse: " + fmt + ". Returning default."); //not a valid date format - } - //return new PtgStr(sdf.format(d)); - return new PtgStr(WorkBookHandle.simpledateformat.format(d)); - }catch(Exception e) { - Logger.logErr("Unable to calcText formatting correctly for a date" + e); + d = DateConverter.getDateFromNumber(new Double(res)); + } catch (NumberFormatException e) { + d = DateConverter.getDate(res); // try to convert a string date + if (d == null) + // what excel does, if it's an empty date, it reverts to jan 0, 1900 + d = new Date("1/1/1990"); } + //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + try { + //sdf = new SimpleDateFormat(fmt); + WorkBookHandle.simpledateformat.applyPattern(fmt); + } catch (Exception ex) { + Logger.logWarn("Simple Date Format could not parse: " + fmt + ". Returning default."); //not a valid date format + } + //return new PtgStr(sdf.format(d)); + return new PtgStr(WorkBookHandle.simpledateformat.format(d)); + } catch (Exception e) { + Logger.logErr("Unable to calcText formatting correctly for a date" + e); } + } } - + // we've been unable to format, try based on the string try { - if (Xf.isDatePattern( fmt )) { - //fmtx = new SimpleDateFormat( fmt ); - WorkBookHandle.simpledateformat.applyPattern(fmt); - fmtx= WorkBookHandle.simpledateformat; - }else fmtx = new DecimalFormat( fmt ); - - if (res!=null && !res.equals("")) - return new PtgStr(fmtx.format(new Float(res))); - else if (res!=null) - return new PtgStr(fmtx.format(0)); - }catch(Exception e) { - //Logger.logWarn("getting formatted string value for :" + res.toString() + " failed: " + e.toString()) ; - try { - return new PtgStr(res); - }catch(NumberFormatException nbe) { - } - } - return new PtgStr(res); - } - - /** - TRIM - According to documentation Trim() removes leading and trailing spaces from the cell value. - - Actually it removes all spaces except for single spaces - between words. - */ - protected static Ptg calcTrim(Ptg[] operands){ - Object o = operands[0].getValue(); - String res; - if (o instanceof Double) { - res = ExcelTools.getNumberAsString(((Double) o).doubleValue()); - }else { - res = String.valueOf(o); - } - if (res==null || res.equals(new PtgErr(PtgErr.ERROR_NA).toString())) - return new PtgErr(PtgErr.ERROR_NA); - // first let's remove the beginning and trailing spaces. - if(res.length()>0) { - while(res.substring(0,1).equals(" ")){ - res = res.substring(1,res.length()); - } - while(res.substring(res.length()-1, res.length()).equals(" ")){ - res = res.substring(0, res.length()-1); - } - // now we need to remove double spaces - while(res.indexOf(" ") != -1){ - int i = res.indexOf(" "); - String prestring = res.substring(0, i); - String poststring = res.substring(i+1, res.length()); - res = prestring + poststring; - } - } - return new PtgStr(res); - - } - - /** - UPPER - Converts text to uppercase - */ - protected static Ptg calcUpper(Ptg[] operands){ - if (operands.length > 1)return PtgCalculator.getError(); - String s = String.valueOf(operands[0].getValue()); - s = s.toUpperCase(); - return new PtgStr(s); - } - /** - * - VALUE - Converts a text argument to a number - -*/ -protected static Ptg calcValue(Ptg[] operands){ - try { - String s = String.valueOf(operands[0].getValue()); - if (s.equals(""))s="0"; // Excel returns a zero for a blank value if VALUE is called upon it. - Double d = new Double(s); - return new PtgNumber(d.doubleValue()); - }catch(NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); + if (Xf.isDatePattern(fmt)) { + //fmtx = new SimpleDateFormat( fmt ); + WorkBookHandle.simpledateformat.applyPattern(fmt); + fmtx = WorkBookHandle.simpledateformat; + } else fmtx = new DecimalFormat(fmt); + + if (res != null && !res.equals("")) + return new PtgStr(fmtx.format(new Float(res))); + else if (res != null) + return new PtgStr(fmtx.format(0)); + } catch (Exception e) { + //Logger.logWarn("getting formatted string value for :" + res.toString() + " failed: " + e.toString()) ; + try { + return new PtgStr(res); + } catch (NumberFormatException nbe) { + } + } + return new PtgStr(res); } -} - /** helper method for all DBCS-related worksheet functions - * @param op - * @return - */ - private static byte[] getUnicodeBytesFromOp(Ptg op) { - byte[] strbytes= null; - if (op instanceof PtgRef) { - io.starter.formats.XLS.BiffRec rec= ((PtgRef)op).getRefCells()[0]; - if (rec instanceof io.starter.formats.XLS.Labelsst) - strbytes= ((io.starter.formats.XLS.Labelsst)rec).getUnsharedString().readStr(); - else if (rec instanceof Formula) { - strbytes= op.getValue().toString().getBytes(); - } else // DEBUGGING- Take out when done - Logger.logWarn("getUnicodeBytes: Unexpected rec encountered: " + op.getClass()); - } else if (op instanceof PtgStr) { - strbytes= new byte[((PtgStr) op).record.length-3]; - System.arraycopy(((PtgStr) op).record, 3, strbytes, 0, strbytes.length); - } else { - // DEBUGGING- Take out when done - Logger.logWarn("getUnicodeBytes: Unexpected operand encountered: " + op.getClass()); - } - return strbytes; - } + /** + * TRIM + * According to documentation Trim() removes leading and trailing spaces from the cell value. + *

                + * Actually it removes all spaces except for single spaces + * between words. + */ + protected static Ptg calcTrim(Ptg[] operands) { + Object o = operands[0].getValue(); + String res; + if (o instanceof Double) { + res = ExcelTools.getNumberAsString(((Double) o).doubleValue()); + } else { + res = String.valueOf(o); + } + if (res == null || res.equals(new PtgErr(PtgErr.ERROR_NA).toString())) + return new PtgErr(PtgErr.ERROR_NA); + // first let's remove the beginning and trailing spaces. + if (res.length() > 0) { + while (res.substring(0, 1).equals(" ")) { + res = res.substring(1); + } + while (res.substring(res.length() - 1).equals(" ")) { + res = res.substring(0, res.length() - 1); + } + // now we need to remove double spaces + while (res.indexOf(" ") != -1) { + int i = res.indexOf(" "); + String prestring = res.substring(0, i); + String poststring = res.substring(i + 1); + res = prestring + poststring; + } + } + return new PtgStr(res); + + } + + /** + * UPPER + * Converts text to uppercase + */ + protected static Ptg calcUpper(Ptg[] operands) { + if (operands.length > 1) return PtgCalculator.getError(); + String s = String.valueOf(operands[0].getValue()); + s = s.toUpperCase(); + return new PtgStr(s); + } + + /** + * VALUE + * Converts a text argument to a number + */ + protected static Ptg calcValue(Ptg[] operands) { + try { + String s = String.valueOf(operands[0].getValue()); + if (s.equals("")) s = "0"; // Excel returns a zero for a blank value if VALUE is called upon it. + Double d = new Double(s); + return new PtgNumber(d.doubleValue()); + } catch (NumberFormatException e) { + return new PtgErr(PtgErr.ERROR_VALUE); + } + } + + /** + * helper method for all DBCS-related worksheet functions + * + * @param op + * @return + */ + private static byte[] getUnicodeBytesFromOp(Ptg op) { + byte[] strbytes = null; + if (op instanceof PtgRef) { + io.starter.formats.XLS.BiffRec rec = ((PtgRef) op).getRefCells()[0]; + if (rec instanceof io.starter.formats.XLS.Labelsst) + strbytes = ((io.starter.formats.XLS.Labelsst) rec).getUnsharedString().readStr(); + else if (rec instanceof Formula) { + strbytes = op.getValue().toString().getBytes(); + } else // DEBUGGING- Take out when done + Logger.logWarn("getUnicodeBytes: Unexpected rec encountered: " + op.getClass()); + } else if (op instanceof PtgStr) { + strbytes = new byte[((PtgStr) op).record.length - 3]; + System.arraycopy(((PtgStr) op).record, 3, strbytes, 0, strbytes.length); + } else { + // DEBUGGING- Take out when done + Logger.logWarn("getUnicodeBytes: Unexpected operand encountered: " + op.getClass()); + } + return strbytes; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XML/UnicodeInputStream.java b/src/main/java/io/starter/formats/XML/UnicodeInputStream.java index bd63740..1bcb4c7 100644 --- a/src/main/java/io/starter/formats/XML/UnicodeInputStream.java +++ b/src/main/java/io/starter/formats/XML/UnicodeInputStream.java @@ -2,157 +2,157 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ -/** +/** * BOMInputstream.java - * - * + *

                + *

                * Oct 14, 2010 - * - * */ package io.starter.formats.XML; /** -Original pseudocode : Thomas Weidenfeller -Implementation tweaked: Aki Nieminen + Original pseudocode : Thomas Weidenfeller + Implementation tweaked: Aki Nieminen -http://www.unicode.org/unicode/faq/utf_bom.html -BOMs: - 00 00 FE FF = UTF-32, big-endian - FF FE 00 00 = UTF-32, little-endian - FE FF = UTF-16, big-endian - FF FE = UTF-16, little-endian - EF BB BF = UTF-8 + http://www.unicode.org/unicode/faq/utf_bom.html + BOMs: + 00 00 FE FF = UTF-32, big-endian + FF FE 00 00 = UTF-32, little-endian + FE FF = UTF-16, big-endian + FF FE = UTF-16, little-endian + EF BB BF = UTF-8 -Win2k Notepad: - Unicode format = UTF-16LE -***/ + Win2k Notepad: + Unicode format = UTF-16LE + ***/ -import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; /** -* This inputstream will recognize unicode BOM marks -* and will skip bytes if getEncoding() method is called -* before any of the read(...) methods. -* -* Usage pattern: - String enc = "ISO-8859-1"; // or NULL to use -systemdefault - FileInputStream fis = new FileInputStream(file); - UnicodeInputStream uin = new UnicodeInputStream(fis, -enc); - enc = uin.getEncoding(); // check for BOM mark and skip -bytes - InputStreamReader in; - if (enc == null) in = new InputStreamReader(uin); - else in = new InputStreamReader(uin, enc); -*/ + * This inputstream will recognize unicode BOM marks + * and will skip bytes if getEncoding() method is called + * before any of the read(...) methods. + * + * Usage pattern: + String enc = "ISO-8859-1"; // or NULL to use + systemdefault + FileInputStream fis = new FileInputStream(file); + UnicodeInputStream uin = new UnicodeInputStream(fis, + enc); + enc = uin.getEncoding(); // check for BOM mark and skip + bytes + InputStreamReader in; + if (enc == null) in = new InputStreamReader(uin); + else in = new InputStreamReader(uin, enc); + */ public class UnicodeInputStream extends InputStream { - PushbackInputStream internalIn; - boolean isInited = false; - String defaultEnc; - String encoding; - - private static final int BOM_SIZE = 4; - - public UnicodeInputStream(InputStream in, String defaultEnc) { - internalIn = new PushbackInputStream(in,BOM_SIZE); - this.defaultEnc = defaultEnc; - } - - public String getDefaultEncoding() { - return defaultEnc; - } - - public String getEncoding() { - if (!isInited) { - try { - init(); - } catch (IOException ex) { - throw new IllegalStateException("Init method failed." + ex); + PushbackInputStream internalIn; + boolean isInited = false; + String defaultEnc; + String encoding; + + private static final int BOM_SIZE = 4; + + public UnicodeInputStream(InputStream in, String defaultEnc) { + internalIn = new PushbackInputStream(in, BOM_SIZE); + this.defaultEnc = defaultEnc; + } + + public String getDefaultEncoding() { + return defaultEnc; + } + + public String getEncoding() { + if (!isInited) { + try { + init(); + } catch (IOException ex) { + throw new IllegalStateException("Init method failed." + ex); // (Throwable)ex); + } } - } - return encoding; - } - - /** - * Read-ahead four bytes and check for BOM marks. Extra -bytes are - * unread back to the stream, only BOM bytes are skipped. - */ - protected void init() throws IOException { - if (isInited) return; - - byte bom[] = new byte[BOM_SIZE]; - int n, unread; - n = internalIn.read(bom, 0, bom.length); - - if ( (bom[0] == (byte)0xEF) && (bom[1] == (byte)0xBB) -&& - (bom[2] == (byte)0xBF) ) { - encoding = "UTF-8"; - unread = n - 3; - } else if ( (bom[0] == (byte)0xFE) && (bom[1] == (byte) -0xFF) ) { - encoding = "UTF-16BE"; - unread = n - 2; - } else if ( (bom[0] == (byte)0xFF) && (bom[1] == (byte) -0xFE) ) { - encoding = "UTF-16LE"; - unread = n - 2; - } else if ( (bom[0] == (byte)0x00) && (bom[1] == (byte) -0x00) && - (bom[2] == (byte)0xFE) && (bom[3] == (byte) -0xFF)) { - encoding = "UTF-32BE"; - unread = n - 4; - } else if ( (bom[0] == (byte)0xFF) && (bom[1] == (byte) -0xFE) && - (bom[2] == (byte)0x00) && (bom[3] == (byte) -0x00)) { - encoding = "UTF-32LE"; - unread = n - 4; - } else { - // Unicode BOM mark not found, unread all bytes - encoding = defaultEnc; - unread = n; - } - // io.starter.toolkit.Logger.log("read=" + n + ", unread=" + unread); - - if (unread > 0) internalIn.unread(bom, (n - unread), -unread); - - isInited = true; - } - - public void close() throws IOException { - //init(); - isInited = true; - internalIn.close(); - } - - public int read() throws IOException { - //init(); - isInited = true; - return internalIn.read(); - } + return encoding; + } + + /** + * Read-ahead four bytes and check for BOM marks. Extra + bytes are + * unread back to the stream, only BOM bytes are skipped. + */ + protected void init() throws IOException { + if (isInited) return; + + byte[] bom = new byte[BOM_SIZE]; + int n, unread; + n = internalIn.read(bom, 0, bom.length); + + if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) + && + (bom[2] == (byte) 0xBF)) { + encoding = "UTF-8"; + unread = n - 3; + } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) + 0xFF)) { + encoding = "UTF-16BE"; + unread = n - 2; + } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) + 0xFE)) { + encoding = "UTF-16LE"; + unread = n - 2; + } else if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) + 0x00) && + (bom[2] == (byte) 0xFE) && (bom[3] == (byte) + 0xFF)) { + encoding = "UTF-32BE"; + unread = n - 4; + } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) + 0xFE) && + (bom[2] == (byte) 0x00) && (bom[3] == (byte) + 0x00)) { + encoding = "UTF-32LE"; + unread = n - 4; + } else { + // Unicode BOM mark not found, unread all bytes + encoding = defaultEnc; + unread = n; + } + // io.starter.toolkit.Logger.log("read=" + n + ", unread=" + unread); + + if (unread > 0) internalIn.unread(bom, (n - unread), + unread); + + isInited = true; + } + + public void close() throws IOException { + //init(); + isInited = true; + internalIn.close(); + } + + public int read() throws IOException { + //init(); + isInited = true; + return internalIn.read(); + } } diff --git a/src/main/java/io/starter/formats/cellformat/CellFormat.java b/src/main/java/io/starter/formats/cellformat/CellFormat.java index bfa9e5e..7966005 100644 --- a/src/main/java/io/starter/formats/cellformat/CellFormat.java +++ b/src/main/java/io/starter/formats/cellformat/CellFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,6 +25,7 @@ import io.starter.OpenXLS.Cell; public interface CellFormat { - public String format (Object input); - public String format (Cell cell); + String format(Object input); + + String format(Cell cell); } diff --git a/src/main/java/io/starter/formats/cellformat/CellFormatFactory.java b/src/main/java/io/starter/formats/cellformat/CellFormatFactory.java index d14fd40..b513b6c 100644 --- a/src/main/java/io/starter/formats/cellformat/CellFormatFactory.java +++ b/src/main/java/io/starter/formats/cellformat/CellFormatFactory.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,50 +25,50 @@ import io.starter.toolkit.StringTool; public class CellFormatFactory { - private CellFormatFactory() { - // this is a static-only class - throw new UnsupportedOperationException(); - } - - public static CellFormat fromPatternString (String pattern) { - if (null == pattern || "".equals( pattern ) - || "General".equalsIgnoreCase( pattern ) ) { - return new GeneralCellFormat(); + private CellFormatFactory() { + // this is a static-only class + throw new UnsupportedOperationException(); + } + + public static CellFormat fromPatternString(String pattern) { + if (null == pattern || "".equals(pattern) + || "General".equalsIgnoreCase(pattern)) { + return new GeneralCellFormat(); } - - String[] pats= pattern.split(";"); - - String tester= StringTool.convertPatternExtractBracketedExpression(pats[0]); + + String[] pats = pattern.split(";"); + + String tester = StringTool.convertPatternExtractBracketedExpression(pats[0]); if (tester.matches(".*(((y{1,4}|m{1,5}|d{1,4}|h{1,2}|s{1,2}).*)+).*")) { - String string; - if (pats.length > 3) { - string = StringTool.convertPatternFromExcelToStringFormatter( pats[ 3 ], false ); - } else { - string = "%s"; - } - - return new DateCellFormat( - StringTool.convertDatePatternFromExcelToStringFormatter( tester ), string ); + String string; + if (pats.length > 3) { + string = StringTool.convertPatternFromExcelToStringFormatter(pats[3], false); + } else { + string = "%s"; + } + + return new DateCellFormat( + StringTool.convertDatePatternFromExcelToStringFormatter(tester), string); } - - String positive, negative, zero, string; - positive = StringTool.convertPatternFromExcelToStringFormatter( pats[ 0 ], false ); - - negative = StringTool.convertPatternFromExcelToStringFormatter( - pats[ pats.length > 1 ? 1 : 0 ], true ); - - if (pats.length > 2) { - zero = StringTool.convertPatternFromExcelToStringFormatter( pats[ 2 ], false ); - } else { - zero = positive; - } - - if (pats.length > 3) { - string = StringTool.convertPatternFromExcelToStringFormatter( pats[ 3 ], false ); - } else { - string = "%s"; - } - - return new NumberCellFormat( positive, negative, zero, string ); - } + + String positive, negative, zero, string; + positive = StringTool.convertPatternFromExcelToStringFormatter(pats[0], false); + + negative = StringTool.convertPatternFromExcelToStringFormatter( + pats[pats.length > 1 ? 1 : 0], true); + + if (pats.length > 2) { + zero = StringTool.convertPatternFromExcelToStringFormatter(pats[2], false); + } else { + zero = positive; + } + + if (pats.length > 3) { + string = StringTool.convertPatternFromExcelToStringFormatter(pats[3], false); + } else { + string = "%s"; + } + + return new NumberCellFormat(positive, negative, zero, string); + } } diff --git a/src/main/java/io/starter/formats/cellformat/DateCellFormat.java b/src/main/java/io/starter/formats/cellformat/DateCellFormat.java index 700d2ae..b4c7b40 100644 --- a/src/main/java/io/starter/formats/cellformat/DateCellFormat.java +++ b/src/main/java/io/starter/formats/cellformat/DateCellFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,37 +22,37 @@ */ package io.starter.formats.cellformat; -import java.text.SimpleDateFormat; -import java.util.Calendar; - import io.starter.OpenXLS.Cell; import io.starter.OpenXLS.CellHandle; import io.starter.OpenXLS.DateConverter; +import java.text.SimpleDateFormat; +import java.util.Calendar; + public class DateCellFormat -extends SimpleDateFormat -implements CellFormat { - private static final long serialVersionUID = 1896075041723437260L; - - private final String text_format; - - DateCellFormat (String date, String text) { - super( date ); - this.text_format = text; - } - - public String format (Cell cell) { - // make sure to return the empty string for blank cells - // getting the calendar coerces to double and thus gets zero - if ( (cell instanceof CellHandle && ((CellHandle) cell).isBlank()) - || "".equals( cell.getVal() ) ) - return ""; - - if (cell.getCellType() == Cell.TYPE_STRING) { - return String.format( this.text_format, (String) cell.getVal() ); - } - - Calendar date = DateConverter.getCalendarFromCell( cell ); - return this.format( date.getTime() ); - } + extends SimpleDateFormat + implements CellFormat { + private static final long serialVersionUID = 1896075041723437260L; + + private final String text_format; + + DateCellFormat(String date, String text) { + super(date); + this.text_format = text; + } + + public String format(Cell cell) { + // make sure to return the empty string for blank cells + // getting the calendar coerces to double and thus gets zero + if ((cell instanceof CellHandle && ((CellHandle) cell).isBlank()) + || "".equals(cell.getVal())) + return ""; + + if (cell.getCellType() == Cell.TYPE_STRING) { + return String.format(this.text_format, (String) cell.getVal()); + } + + Calendar date = DateConverter.getCalendarFromCell(cell); + return this.format(date.getTime()); + } } diff --git a/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.java b/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.java index 4d264e4..550b994 100644 --- a/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.java +++ b/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,54 +22,55 @@ */ package io.starter.formats.cellformat; +import io.starter.OpenXLS.Cell; +import io.starter.OpenXLS.ExcelTools; + import java.text.FieldPosition; import java.text.Format; import java.text.ParsePosition; -import io.starter.OpenXLS.Cell; -import io.starter.OpenXLS.ExcelTools; - public class GeneralCellFormat -extends Format -implements CellFormat { - private static final long serialVersionUID = -3530672760714160988L; - - // make the constructor package-private - GeneralCellFormat() {} + extends Format + implements CellFormat { + private static final long serialVersionUID = -3530672760714160988L; + + // make the constructor package-private + GeneralCellFormat() { + } + + public StringBuffer format(Object obj, StringBuffer buffer, + FieldPosition pos) { + // try to parse strings as numbers + if (obj instanceof String) try { + obj = Double.valueOf((String) obj); + } catch (NumberFormatException ex) { + // this is OK, it just wasn't a number + } + + if (obj instanceof Number) { + Number num = (Number) obj; + if (num.longValue() == num.doubleValue()) { + // it's an integer + return buffer.append(num.longValue()); + } else { + // it's floating-point + return buffer.append( + ExcelTools.getNumberAsString(num.doubleValue())); + } + } - public StringBuffer format (Object obj, StringBuffer buffer, - FieldPosition pos) { - // try to parse strings as numbers - if (obj instanceof String) try { - obj = Double.valueOf( (String) obj ); - } catch (NumberFormatException ex) { - // this is OK, it just wasn't a number - } - - if (obj instanceof Number) { - Number num = (Number) obj; - if (num.longValue() == num.doubleValue()) { - // it's an integer - return buffer.append( String.valueOf( num.longValue() ) ); - } else { - // it's floating-point - return buffer.append( - ExcelTools.getNumberAsString( num.doubleValue() ) ); - } - } - - return buffer.append( obj.toString() ); - } + return buffer.append(obj.toString()); + } - public Object parseObject (String source, ParsePosition pos) { - throw new UnsupportedOperationException(); - } + public Object parseObject(String source, ParsePosition pos) { + throw new UnsupportedOperationException(); + } - public String format (Cell cell) { - return this.format( cell.getVal() ); - } + public String format(Cell cell) { + return this.format(cell.getVal()); + } - public String format (String val) { - return this.format( val ); - } + public String format(String val) { + return this.format(val); + } } diff --git a/src/main/java/io/starter/formats/cellformat/NumberCellFormat.java b/src/main/java/io/starter/formats/cellformat/NumberCellFormat.java index 5189e73..c2d7da6 100644 --- a/src/main/java/io/starter/formats/cellformat/NumberCellFormat.java +++ b/src/main/java/io/starter/formats/cellformat/NumberCellFormat.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,85 +22,84 @@ */ package io.starter.formats.cellformat; +import io.starter.OpenXLS.Cell; + import java.text.FieldPosition; import java.text.NumberFormat; import java.text.ParsePosition; -import io.starter.OpenXLS.Cell; - public class NumberCellFormat -extends NumberFormat -implements CellFormat { - private static final long serialVersionUID = -7191923168789058338L; + extends NumberFormat + implements CellFormat { + private static final long serialVersionUID = -7191923168789058338L; + + private final String positive, negative, zero, string; + + NumberCellFormat(String positive, String negative, String zero, String string) { + this.positive = positive; + this.negative = negative; + this.zero = zero; + this.string = string; + } + + public StringBuffer format(Object input, StringBuffer buffer, + FieldPosition pos) { + if (input instanceof String) { + // hack to make useless @ pattern work + if ("%s".equals(this.positive)) { + return buffer.append(input); + } + try { + Double d = new Double(input.toString()); + input = d; + } catch (NumberFormatException e) { + return buffer.append( + String.format(this.string, (String) input)); + } + } + if (input instanceof Number) { + String format; + double value = ((Number) input).doubleValue(); + + if (value > 0) format = this.positive; + else if (value < 0) { + format = this.negative; + value = Math.abs(value); + } else format = this.zero; + + // hack to make percentage formats work + if (format.contains("%%")) { + value *= 100; + } + + // hack to make useless @ pattern work + if ("%s".equals(format)) { + return buffer.append(input); + } + + return buffer.append( + String.format(format, Double.valueOf(value))); + } else { + throw new IllegalArgumentException("unsupported input type"); + } + } - private final String positive, negative, zero, string; - - NumberCellFormat (String positive, String negative, String zero, String string) { - this.positive = positive; - this.negative = negative; - this.zero = zero; - this.string = string; - } - - public StringBuffer format (Object input, StringBuffer buffer, - FieldPosition pos) { - if (input instanceof String) { - // hack to make useless @ pattern work - if ("%s".equals( this.positive )) { - return buffer.append( String.valueOf( input ) ); - } - try{ - Double d = new Double(input.toString()); - input = d; - }catch(NumberFormatException e){ - return buffer.append( - String.format( this.string, (String) input ) ); - } - } - if (input instanceof Number) { - String format; - double value = ((Number) input).doubleValue(); - - if (value > 0) format = this.positive; - else if (value < 0) { - format = this.negative; - value = Math.abs( value ); - } - else format = this.zero; - - // hack to make percentage formats work - if (format.contains( "%%" )) { - value *= 100; - } - - // hack to make useless @ pattern work - if ("%s".equals( format )) { - return buffer.append( String.valueOf( input ) ); - } - - return buffer.append( - String.format( format, Double.valueOf( value ) ) ); - } else { - throw new IllegalArgumentException( "unsupported input type" ); - } - } - - public StringBuffer format (double number, StringBuffer buffer, - FieldPosition pos) { - return buffer.append( this.format( Double.valueOf( number ) ) ); - } + public StringBuffer format(double number, StringBuffer buffer, + FieldPosition pos) { + return buffer.append(this.format(Double.valueOf(number))); + } - public StringBuffer format (long number, StringBuffer buffer, - FieldPosition pos) { - return buffer.append( this.format( Long.valueOf( number ) ) ); - } + public StringBuffer format(long number, StringBuffer buffer, + FieldPosition pos) { + return buffer.append(this.format(Long.valueOf(number))); + } - public Number parse (String source, ParsePosition parsePosition) { - throw new UnsupportedOperationException(); - } + public Number parse(String source, ParsePosition parsePosition) { + throw new UnsupportedOperationException(); + } - public String format (Cell cell) { - return this.format( cell.getVal() ); - } + public String format(Cell cell) { + return this.format(cell.getVal()); + } } diff --git a/src/main/java/io/starter/formats/escher/EscherRecord.java b/src/main/java/io/starter/formats/escher/EscherRecord.java index 3835ebc..05f113d 100644 --- a/src/main/java/io/starter/formats/escher/EscherRecord.java +++ b/src/main/java/io/starter/formats/escher/EscherRecord.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,93 +22,95 @@ */ package io.starter.formats.escher; +import io.starter.toolkit.ByteTools; + import java.io.Serializable; -import io.starter.toolkit.ByteTools; +abstract class EscherRecord implements Serializable { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7987132917889379656L; + byte[] header; + byte[] data; + int length; + int inst; + int fbt; + int version; + boolean isDirty = false; + + /** + * no param constructor for Serializable + */ + public void EscherRecord() { + } + + EscherRecord(int fbt, int inst, int version) { + this.fbt = fbt; + this.inst = inst; + this.version = version; + } + + public int getFbt() { + + return fbt; + } + + public int getInst() { + + return inst; + } + + public int getLength() { + return length; + } + + public void setFbt(int value) { + fbt = value; + } + + public void setInst(int value) { + inst = value; + } + + public void setLength(int value) { + length = value; + } + + protected abstract byte[] getData(); + + private byte[] getHeaderBytes() { + //TODO: Reverse the process of header decoding here + byte[] headerBytes = new byte[4]; + + headerBytes[0] = (byte) ((0xF & version) | (0xF0 & (inst << 4))); + headerBytes[1] = (byte) ((0x00000FF0 & inst) >> 4); + headerBytes[2] = (byte) ((0x000000FF & fbt)); + headerBytes[3] = (byte) ((0x0000FF00 & fbt) >> 8); + + + int version2 = (0x0F & headerBytes[0]); + int inst2 = ((0xFF & headerBytes[1]) >> 4) | ((0xF0 & headerBytes[0]) >> 4); + + byte[] lenBytes = ByteTools.cLongToLEBytes(length); + + byte[] retData = new byte[8]; + System.arraycopy(headerBytes, 0, retData, 0, 4); + System.arraycopy(lenBytes, 0, retData, headerBytes.length, 4); + + return retData; + } + + public byte[] toByteArray() { + byte[] dataBytes = getData(); //Have it in this sequence as some records adjust their header byte length from getData + byte[] headerBytes = getHeaderBytes(); + + byte[] retData = new byte[headerBytes.length + dataBytes.length]; + System.arraycopy(headerBytes, 0, retData, 0, headerBytes.length); + if (dataBytes.length > 0) + System.arraycopy(dataBytes, 0, retData, headerBytes.length, dataBytes.length); + return retData; -abstract class EscherRecord implements Serializable{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7987132917889379656L; - byte[] header; - byte[] data; - int length; - int inst; - int fbt; - int version; - boolean isDirty= false; - - /** - * no param constructor for Serializable - */ - public void EscherRecord() { - } - EscherRecord(int fbt,int inst,int version){ - this.fbt = fbt; - this.inst = inst; - this.version = version; - } - - public int getFbt(){ - - return fbt; - } - - public int getInst(){ - - return inst; - } - - public int getLength(){ - return length; - } - public void setFbt(int value){ - fbt=value; - } - - public void setInst(int value){ - inst=value; - } - - public void setLength(int value){ - length=value; - } - - protected abstract byte[] getData(); - - private byte[] getHeaderBytes(){ - //TODO: Reverse the process of header decoding here - byte[] headerBytes = new byte[4]; - - headerBytes[0] = (byte)( (0xF&version)| (0xF0&(inst<<4))); - headerBytes[1] = (byte)((0x00000FF0&inst)>>4); - headerBytes[2] = (byte)((0x000000FF&fbt)); - headerBytes[3] = (byte)((0x0000FF00&fbt)>>8); - - - int version2 = (0x0F&headerBytes[0]); - int inst2 = ((0xFF&headerBytes[1])>>4)|((0xF0&headerBytes[0])>>4); - - byte[] lenBytes = ByteTools.cLongToLEBytes(length); - - byte[] retData = new byte[8]; - System.arraycopy(headerBytes, 0, retData, 0,4); - System.arraycopy(lenBytes, 0, retData, headerBytes.length,4); - - return retData; - } - - public byte[] toByteArray(){ - byte[] dataBytes = getData(); //Have it in this sequence as some records adjust their header byte length from getData - byte[] headerBytes = getHeaderBytes(); - - byte[] retData = new byte[headerBytes.length+dataBytes.length]; - System.arraycopy(headerBytes, 0, retData, 0,headerBytes.length); - if(dataBytes.length>0) - System.arraycopy(dataBytes, 0, retData, headerBytes.length, dataBytes.length); - return retData; - - } + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtBSE.java b/src/main/java/io/starter/formats/escher/MsofbtBSE.java index 3c562b2..f8357b6 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtBSE.java +++ b/src/main/java/io/starter/formats/escher/MsofbtBSE.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -30,40 +30,41 @@ //0xF007 public class MsofbtBSE extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -9072016434347371029L; - private byte[] imageData; - private int imageType; - private int numShapes; - private int cRef= 1; // default reference count is 1; 0= hidden; > 1= multiple refs to same image 20071120 KSC - - - public MsofbtBSE(int fbt,int inst,int version){ - super(fbt,inst,version); - } - protected byte[] getData(){ - byte[] imgHeader = new byte[61]; // BSE header = 36 bytes, BLIP record header = 24 bytes, then imageData bytes follow - imgHeader[0] = (byte)imageType; //btWin32 - imgHeader[1] = (byte)imageType; //btMac - - // 20071004 KSC: Unknown type of record, does not follow below rules - // and will cause file error ... - // TODO: Research more - if (imageType==0) { - byte[] retData= new byte[36]; - retData[18]= (byte)-1; // tag - setLength(retData.length); - return retData; - } - //The digest of image data should be here. 2-17 - MD4Digest md4Digest = new MD4Digest(); - byte[] digest = md4Digest.getDigest(imageData); - System.arraycopy(digest, 0, imgHeader, 2, 16); - - imgHeader[18] = (byte)-1; //First tag byte is always -1 - imgHeader[19] = (byte)0; //Second tag byte is always 0 + /** + * serialVersionUID + */ + private static final long serialVersionUID = -9072016434347371029L; + private byte[] imageData; + private int imageType; + private int numShapes; + private int cRef = 1; // default reference count is 1; 0= hidden; > 1= multiple refs to same image 20071120 KSC + + + public MsofbtBSE(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + protected byte[] getData() { + byte[] imgHeader = new byte[61]; // BSE header = 36 bytes, BLIP record header = 24 bytes, then imageData bytes follow + imgHeader[0] = (byte) imageType; //btWin32 + imgHeader[1] = (byte) imageType; //btMac + + // 20071004 KSC: Unknown type of record, does not follow below rules + // and will cause file error ... + // TODO: Research more + if (imageType == 0) { + byte[] retData = new byte[36]; + retData[18] = (byte) -1; // tag + setLength(retData.length); + return retData; + } + //The digest of image data should be here. 2-17 + MD4Digest md4Digest = new MD4Digest(); + byte[] digest = md4Digest.getDigest(imageData); + System.arraycopy(digest, 0, imgHeader, 2, 16); + + imgHeader[18] = (byte) -1; //First tag byte is always -1 + imgHeader[19] = (byte) 0; //Second tag byte is always 0 /* 20071119 KSC: original code; this was wrong int mod = (imageData.length+25)%MAXROWS_BIFF8; @@ -94,46 +95,46 @@ protected byte[] getData(){ imgHeader[34]=(byte)1; //usage is numShapes imgHeader[35]=(byte)0; //cbName is always zero -*/ - // new code - // Size of data + 25 (for header stuff, I assume) (4 bytes) - int sz = imageData.length+25; - byte[] tempBytes = ByteTools.cLongToLEBytes(sz); - imgHeader[20] =tempBytes[0]; - imgHeader[21] =tempBytes[1]; - imgHeader[22] =tempBytes[2]; - imgHeader[23] =tempBytes[3]; - - // cRef (4 bytes) = reference count; 1 unless it's referenced more than once ... :) 0 if hidden - tempBytes = ByteTools.cLongToLEBytes(cRef); - imgHeader[24]=tempBytes[0]; - imgHeader[25]=tempBytes[1]; - imgHeader[26]=tempBytes[2]; - imgHeader[27]=tempBytes[3]; - - // foDelay is always zero = image bytes are not in delay stream - tempBytes = ByteTools.cLongToLEBytes(0); - imgHeader[28]=tempBytes[0]; - imgHeader[29]=tempBytes[1]; - imgHeader[30]=tempBytes[2]; - imgHeader[31]=tempBytes[3]; - - imgHeader[32]=(byte)0; //usage - should be 0=default usage - imgHeader[33]=(byte)0; //cbName is always zero = no name following this header - // bytes 34 and 35 are unused at this point and should be 0 - - // ******************************************************************************** - // BLIP RECORD follows BSE Header UNLESS file delayOffset is > 0 or cbNameLen > 0 - // ******************************************************************************** - - // BLIP RECORD for Metafile/PICT Blips (msobiEMF, msobiWMF, or msobiPICT): - /* The secondary, or data, UID - should always be set. */ - // BYTE m_rgbUid[16]; +*/ + // new code + // Size of data + 25 (for header stuff, I assume) (4 bytes) + int sz = imageData.length + 25; + byte[] tempBytes = ByteTools.cLongToLEBytes(sz); + imgHeader[20] = tempBytes[0]; + imgHeader[21] = tempBytes[1]; + imgHeader[22] = tempBytes[2]; + imgHeader[23] = tempBytes[3]; + + // cRef (4 bytes) = reference count; 1 unless it's referenced more than once ... :) 0 if hidden + tempBytes = ByteTools.cLongToLEBytes(cRef); + imgHeader[24] = tempBytes[0]; + imgHeader[25] = tempBytes[1]; + imgHeader[26] = tempBytes[2]; + imgHeader[27] = tempBytes[3]; + + // foDelay is always zero = image bytes are not in delay stream + tempBytes = ByteTools.cLongToLEBytes(0); + imgHeader[28] = tempBytes[0]; + imgHeader[29] = tempBytes[1]; + imgHeader[30] = tempBytes[2]; + imgHeader[31] = tempBytes[3]; + + imgHeader[32] = (byte) 0; //usage - should be 0=default usage + imgHeader[33] = (byte) 0; //cbName is always zero = no name following this header + // bytes 34 and 35 are unused at this point and should be 0 + + // ******************************************************************************** + // BLIP RECORD follows BSE Header UNLESS file delayOffset is > 0 or cbNameLen > 0 + // ******************************************************************************** + + // BLIP RECORD for Metafile/PICT Blips (msobiEMF, msobiWMF, or msobiPICT): + /* The secondary, or data, UID - should always be set. */ + // BYTE m_rgbUid[16]; /* The primary UID - this defaults to 0, in which case the primary ID is that of the internal data. NOTE!: The primary UID is only saved to disk if (blip_instance ^ blip_signature == 1). Blip_instance is MSOFBH.inst and blip_signature is one of the values defined in MSOBI */ - //BYTE m_rgbUidPrimary[16]; // optional based on the above check + //BYTE m_rgbUidPrimary[16]; // optional based on the above check /* Metafile Blip overhead = 34 bytes. m_cb gives the number of bytes required to store an uncompressed version of the file, m_cbSave @@ -151,8 +152,8 @@ drawing calls within the metafile (this may just be the bounding box BYTE m_fFilter; // always msofilterNone void *m_pvBits; // Compressed bits of metafile. */ - - // BLIP RECORD for Bitmap Blips (msobiJPEG, msobiPNG, or msobiDIB) : + + // BLIP RECORD for Bitmap Blips (msobiJPEG, msobiPNG, or msobiDIB) : /* * They have the same UID header as described in the Metafile Blip case. The data after the header is just a single BYTE "tag" * value and is followed by the compressed data of the bitmap in the relevant format (JFIF or PNG, bytes as would be stored in a file). @@ -174,86 +175,98 @@ drawing calls within the metafile (this may just be the bounding box BYTE m_bTag; void *m_pvBits; // raster bits of the blip. */ - - - // 20080226 KSC: instead of handling only two image types, handle them all :) - int inst1= MSODrawingConstants.msobiJPEG; - switch (imageType) { - case MSODrawingConstants.IMAGE_TYPE_GIF: - // TODO: ??? - break; - case MSODrawingConstants.IMAGE_TYPE_EMF: - inst1= MSODrawingConstants.msobiEMF; - break; - case MSODrawingConstants.IMAGE_TYPE_WMF: - inst1= MSODrawingConstants.msobiWMF; - break; - case MSODrawingConstants.IMAGE_TYPE_PICT: - inst1= MSODrawingConstants.msobiPICT; - break; - case MSODrawingConstants.IMAGE_TYPE_JPG: - inst1= MSODrawingConstants.msobiJPEG; - break; - case MSODrawingConstants.IMAGE_TYPE_PNG: - inst1= MSODrawingConstants.msobiPNG; - break; - case MSODrawingConstants.IMAGE_TYPE_DIB: - inst1= MSODrawingConstants.msobiDIB; - break; - } - //int inst1=(imageType==5)?MsodrawingConstants.msobiJPEG:MsodrawingConstants.msobiPNG; - int version1=0; - int fbt1=MSODrawingConstants.msofbtBlipFirst+imageType; - int len1=imageData.length+17; - imgHeader[36] = (byte)((0x0F&inst1)<<4 |(0x0F&version1)); - imgHeader[37] = (byte)((0xFF0&inst1)>>4); - imgHeader[38] = (byte)((0xFF&fbt1)); - imgHeader[39] = (byte)((0xFF00&fbt1)>>8); - - //length with size + 25; - tempBytes = ByteTools.cLongToLEBytes(len1); - imgHeader[40]=tempBytes[0]; - imgHeader[41]=tempBytes[1]; - imgHeader[42]=tempBytes[2]; - imgHeader[43]=tempBytes[3]; - - ///Here will again be the md4 digest of the image data 44-60 - System.arraycopy(digest, 0, imgHeader, 44, 16); - imgHeader[60] = (byte)0xff; //marker to indicate start of image data - int len= 61; - byte[] retData = new byte[len+imageData.length]; - - System.arraycopy(imgHeader, 0, retData, 0,len); - System.arraycopy(imageData, 0, retData,len,imageData.length); - - setLength(retData.length); - return retData; - } - public void setImageData(byte[] d){ - imageData = d; - } - - /** - * set the reference count for this image Data rec - * @param cRef - */ - public void setRefCount(int cRef) { this.cRef= cRef; } - /** - * returns the ref count for this image Data Rec - * @return - */ - public int getRefCount() { return this.cRef; } - public int getImageType() { - return imageType; - } - public void setImageType(int imageType) { - this.imageType = imageType; - } - public int getNumShapes() { - return numShapes; - } - public void setNumShapes(int numShapes) { - this.numShapes = numShapes; - } + + + // 20080226 KSC: instead of handling only two image types, handle them all :) + int inst1 = MSODrawingConstants.msobiJPEG; + switch (imageType) { + case MSODrawingConstants.IMAGE_TYPE_GIF: + // TODO: ??? + break; + case MSODrawingConstants.IMAGE_TYPE_EMF: + inst1 = MSODrawingConstants.msobiEMF; + break; + case MSODrawingConstants.IMAGE_TYPE_WMF: + inst1 = MSODrawingConstants.msobiWMF; + break; + case MSODrawingConstants.IMAGE_TYPE_PICT: + inst1 = MSODrawingConstants.msobiPICT; + break; + case MSODrawingConstants.IMAGE_TYPE_JPG: + inst1 = MSODrawingConstants.msobiJPEG; + break; + case MSODrawingConstants.IMAGE_TYPE_PNG: + inst1 = MSODrawingConstants.msobiPNG; + break; + case MSODrawingConstants.IMAGE_TYPE_DIB: + inst1 = MSODrawingConstants.msobiDIB; + break; + } + //int inst1=(imageType==5)?MsodrawingConstants.msobiJPEG:MsodrawingConstants.msobiPNG; + int version1 = 0; + int fbt1 = MSODrawingConstants.msofbtBlipFirst + imageType; + int len1 = imageData.length + 17; + imgHeader[36] = (byte) ((0x0F & inst1) << 4 | (0x0F & version1)); + imgHeader[37] = (byte) ((0xFF0 & inst1) >> 4); + imgHeader[38] = (byte) ((0xFF & fbt1)); + imgHeader[39] = (byte) ((0xFF00 & fbt1) >> 8); + + //length with size + 25; + tempBytes = ByteTools.cLongToLEBytes(len1); + imgHeader[40] = tempBytes[0]; + imgHeader[41] = tempBytes[1]; + imgHeader[42] = tempBytes[2]; + imgHeader[43] = tempBytes[3]; + + ///Here will again be the md4 digest of the image data 44-60 + System.arraycopy(digest, 0, imgHeader, 44, 16); + imgHeader[60] = (byte) 0xff; //marker to indicate start of image data + int len = 61; + byte[] retData = new byte[len + imageData.length]; + + System.arraycopy(imgHeader, 0, retData, 0, len); + System.arraycopy(imageData, 0, retData, len, imageData.length); + + setLength(retData.length); + return retData; + } + + public void setImageData(byte[] d) { + imageData = d; + } + + /** + * set the reference count for this image Data rec + * + * @param cRef + */ + public void setRefCount(int cRef) { + this.cRef = cRef; + } + + /** + * returns the ref count for this image Data Rec + * + * @return + */ + public int getRefCount() { + return this.cRef; + } + + public int getImageType() { + return imageType; + } + + public void setImageType(int imageType) { + this.imageType = imageType; + } + + public int getNumShapes() { + return numShapes; + } + + public void setNumShapes(int numShapes) { + this.numShapes = numShapes; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.java b/src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.java index 44a7fd5..fa8dd1e 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.java +++ b/src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,21 +22,21 @@ */ package io.starter.formats.escher; -import io.starter.toolkit.ByteTools; - //0xf001 public class MsofbtBstoreContainer extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8979132545323492655L; - public MsofbtBstoreContainer(int fbt,int inst,int version){ - super(fbt,inst,version); - } - protected byte[] getData(){ - - return new byte[0]; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8979132545323492655L; + + public MsofbtBstoreContainer(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + protected byte[] getData() { + + return new byte[0]; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.java b/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.java index f7defa5..6e5abed 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.java +++ b/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,58 +22,58 @@ */ package io.starter.formats.escher; -import java.io.*; - import io.starter.toolkit.ByteTools; +import java.io.ByteArrayOutputStream; + //0xf010 public class MsofbtClientAnchor extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7946989934447191433L; - // 20070914 KSC: this record doesn't store sheet index; 1st two bytes are a flag (seems always to be 2) - short flag= 2,leftColumnIndex,xOffsetL,topRowIndex,yOffsetT,rightColIndex,xOffsetR,bottomRowIndex,yOffsetB; - - public MsofbtClientAnchor(int fbt,int inst,int version){ - super(fbt,inst,version); - } - - public byte[] getData(){ - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try{ - bos.write(ByteTools.shortToLEBytes(flag)); - bos.write(ByteTools.shortToLEBytes(leftColumnIndex)); - bos.write(ByteTools.shortToLEBytes(xOffsetL)); - bos.write(ByteTools.shortToLEBytes(topRowIndex)); - bos.write(ByteTools.shortToLEBytes(yOffsetT)); - bos.write(ByteTools.shortToLEBytes(rightColIndex)); - bos.write(ByteTools.shortToLEBytes(xOffsetR)); - bos.write(ByteTools.shortToLEBytes(bottomRowIndex)); - bos.write(ByteTools.shortToLEBytes(yOffsetB)); - } - catch(Exception e){ - - } - this.setLength(bos.toByteArray().length); - return bos.toByteArray(); - - } - - public void setBounds(short[] bounds){ - if(bounds==null)return; - leftColumnIndex = bounds[0]; - xOffsetL = bounds[1]; - topRowIndex = bounds[2]; - yOffsetT = bounds[3]; - rightColIndex = bounds[4]; - xOffsetR = bounds[5]; - bottomRowIndex = bounds[6]; - yOffsetB = bounds[7]; - - } - public void setFlag(short flag){ - this.flag = flag; - } - + /** + * serialVersionUID + */ + private static final long serialVersionUID = 7946989934447191433L; + // 20070914 KSC: this record doesn't store sheet index; 1st two bytes are a flag (seems always to be 2) + short flag = 2, leftColumnIndex, xOffsetL, topRowIndex, yOffsetT, rightColIndex, xOffsetR, bottomRowIndex, yOffsetB; + + public MsofbtClientAnchor(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + public byte[] getData() { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + bos.write(ByteTools.shortToLEBytes(flag)); + bos.write(ByteTools.shortToLEBytes(leftColumnIndex)); + bos.write(ByteTools.shortToLEBytes(xOffsetL)); + bos.write(ByteTools.shortToLEBytes(topRowIndex)); + bos.write(ByteTools.shortToLEBytes(yOffsetT)); + bos.write(ByteTools.shortToLEBytes(rightColIndex)); + bos.write(ByteTools.shortToLEBytes(xOffsetR)); + bos.write(ByteTools.shortToLEBytes(bottomRowIndex)); + bos.write(ByteTools.shortToLEBytes(yOffsetB)); + } catch (Exception e) { + + } + this.setLength(bos.toByteArray().length); + return bos.toByteArray(); + + } + + public void setBounds(short[] bounds) { + if (bounds == null) return; + leftColumnIndex = bounds[0]; + xOffsetL = bounds[1]; + topRowIndex = bounds[2]; + yOffsetT = bounds[3]; + rightColIndex = bounds[4]; + xOffsetR = bounds[5]; + bottomRowIndex = bounds[6]; + yOffsetB = bounds[7]; + + } + + public void setFlag(short flag) { + this.flag = flag; + } + } diff --git a/src/main/java/io/starter/formats/escher/MsofbtClientData.java b/src/main/java/io/starter/formats/escher/MsofbtClientData.java index 5fde287..1aebc02 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtClientData.java +++ b/src/main/java/io/starter/formats/escher/MsofbtClientData.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,14 +25,16 @@ //0xf011 public class MsofbtClientData extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -937992933535504843L; - public MsofbtClientData(int fbt,int inst,int version){ - super(fbt,inst,version); - } - public byte[] getData(){ - return new byte[0]; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -937992933535504843L; + + public MsofbtClientData(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + public byte[] getData() { + return new byte[0]; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtDg.java b/src/main/java/io/starter/formats/escher/MsofbtDg.java index d07fe35..54133cd 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtDg.java +++ b/src/main/java/io/starter/formats/escher/MsofbtDg.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,34 +27,36 @@ //0xF008 public class MsofbtDg extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5218802290529676567L; - int csp,lastSPID; - - public MsofbtDg(int fbt,int inst,int version){ - super(fbt,inst,version); - } - protected byte[] getData(){ - byte[] cspBytes,spIdBytes; - - cspBytes = ByteTools.cLongToLEBytes(csp); // Number of shapes - spIdBytes = ByteTools.cLongToLEBytes(lastSPID); // last SPID - - byte[] retBytes = new byte[8]; - System.arraycopy(cspBytes, 0, retBytes, 0, 4); - System.arraycopy(spIdBytes, 0, retBytes, 4, 4); - - this.setLength(retBytes.length); - return retBytes; - } - - public void setNumShapes(int value){ - csp =value; - } - public void setLastSPID(int value){ - - lastSPID = value; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5218802290529676567L; + int csp, lastSPID; + + public MsofbtDg(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + protected byte[] getData() { + byte[] cspBytes, spIdBytes; + + cspBytes = ByteTools.cLongToLEBytes(csp); // Number of shapes + spIdBytes = ByteTools.cLongToLEBytes(lastSPID); // last SPID + + byte[] retBytes = new byte[8]; + System.arraycopy(cspBytes, 0, retBytes, 0, 4); + System.arraycopy(spIdBytes, 0, retBytes, 4, 4); + + this.setLength(retBytes.length); + return retBytes; + } + + public void setNumShapes(int value) { + csp = value; + } + + public void setLastSPID(int value) { + + lastSPID = value; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtDgContainer.java b/src/main/java/io/starter/formats/escher/MsofbtDgContainer.java index 7d7b6ba..d8fd643 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtDgContainer.java +++ b/src/main/java/io/starter/formats/escher/MsofbtDgContainer.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,18 +24,18 @@ //0xF002 public class MsofbtDgContainer extends EscherRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5481466773321268095L; - public MsofbtDgContainer(int fbt,int inst,int version){ - super(fbt,inst,version); - } - - protected byte[] getData(){ - return new byte[0]; - } - + /** + * serialVersionUID + */ + private static final long serialVersionUID = -5481466773321268095L; + + public MsofbtDgContainer(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + protected byte[] getData() { + return new byte[0]; + } + } diff --git a/src/main/java/io/starter/formats/escher/MsofbtDgg.java b/src/main/java/io/starter/formats/escher/MsofbtDgg.java index 1b00a43..687ed11 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtDgg.java +++ b/src/main/java/io/starter/formats/escher/MsofbtDgg.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,20 +26,20 @@ //0xF006 public class MsofbtDgg extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7933328640935994167L; - int spidMax=1024,numIdClusters,numShapes,numDrawings; + /** + * serialVersionUID + */ + private static final long serialVersionUID = -7933328640935994167L; + int spidMax = 1024, numIdClusters, numShapes, numDrawings; /* 20071115 KSC: Unused at this point IdClusters[] clusters = new IdClusters[1]; public class IdClusters{ public int dgid,cspidCur; } */ - - public MsofbtDgg(int fbt,int inst,int version){ - super(fbt,inst,version); + + public MsofbtDgg(int fbt, int inst, int version) { + super(fbt, inst, version); /* 20071115 KSC: THIS IS UNUSED RIGHT NOW //Data from experimental results @@ -48,33 +48,33 @@ public MsofbtDgg(int fbt,int inst,int version){ this.clusters[0].dgid = 1; this.clusters[0].cspidCur = 4; */ - } - - protected byte[] getData(){ - byte[] spidMaxBytes,cidclBytes,cspSavedBytes,cdgSavedBytes; + } + + protected byte[] getData() { + byte[] spidMaxBytes, cidclBytes, cspSavedBytes, cdgSavedBytes; // spidMaxBytes = ByteTools.cLongToLEBytes(spidMax+numShapes); 20071113 KSC: can't assume this - spidMaxBytes = ByteTools.cLongToLEBytes(spidMax); - numIdClusters= (int) (spidMax/1024) + ((spidMax%1024!=0)?1:0); // 20080903 KSC: # id clusters is based upon # shapes used - cidclBytes = ByteTools.cLongToLEBytes(numIdClusters); - cspSavedBytes = ByteTools.cLongToLEBytes(numShapes); - cdgSavedBytes = ByteTools.cLongToLEBytes(numDrawings); - - // new code - int lenOfFIDCL= numIdClusters-1; - byte[] retBytes = new byte[16 + lenOfFIDCL*8]; // HOLDS initial info plus FIDCL array of 8 bytes*numIdClusters - System.arraycopy(spidMaxBytes, 0, retBytes, 0, 4); - System.arraycopy(cidclBytes, 0, retBytes, 4, 4); - System.arraycopy(cspSavedBytes, 0, retBytes, 8, 4); - System.arraycopy(cdgSavedBytes, 0, retBytes, 12, 4); - int pos = 16; - - /* 20071120 KSC: new code */ - //20071115 KSC: try to interpret -- these changes seems correct in byte comparisons ... but unsure if it's correct for all cases - for (int i= 0; i < lenOfFIDCL; i++) { - System.arraycopy(ByteTools.cLongToLEBytes(1), 0, retBytes, pos, 4); // dgid owning the SPID's in this cluster - System.arraycopy(ByteTools.cLongToLEBytes(((i==0)?(numShapes):1)), 0, retBytes, pos+4, 4); // # SPID's used so far - pos+=8; - } + spidMaxBytes = ByteTools.cLongToLEBytes(spidMax); + numIdClusters = (spidMax / 1024) + ((spidMax % 1024 != 0) ? 1 : 0); // 20080903 KSC: # id clusters is based upon # shapes used + cidclBytes = ByteTools.cLongToLEBytes(numIdClusters); + cspSavedBytes = ByteTools.cLongToLEBytes(numShapes); + cdgSavedBytes = ByteTools.cLongToLEBytes(numDrawings); + + // new code + int lenOfFIDCL = numIdClusters - 1; + byte[] retBytes = new byte[16 + lenOfFIDCL * 8]; // HOLDS initial info plus FIDCL array of 8 bytes*numIdClusters + System.arraycopy(spidMaxBytes, 0, retBytes, 0, 4); + System.arraycopy(cidclBytes, 0, retBytes, 4, 4); + System.arraycopy(cspSavedBytes, 0, retBytes, 8, 4); + System.arraycopy(cdgSavedBytes, 0, retBytes, 12, 4); + int pos = 16; + + /* 20071120 KSC: new code */ + //20071115 KSC: try to interpret -- these changes seems correct in byte comparisons ... but unsure if it's correct for all cases + for (int i = 0; i < lenOfFIDCL; i++) { + System.arraycopy(ByteTools.cLongToLEBytes(1), 0, retBytes, pos, 4); // dgid owning the SPID's in this cluster + System.arraycopy(ByteTools.cLongToLEBytes(((i == 0) ? (numShapes) : 1)), 0, retBytes, pos + 4, 4); // # SPID's used so far + pos += 8; + } /* old code byte[] b1,b2; @@ -85,9 +85,9 @@ protected byte[] getData(){ System.arraycopy(b2, 0, retBytes, pos+4, 4); pos+=8; */ - this.setLength(retBytes.length); - return retBytes; - } + this.setLength(retBytes.length); + return retBytes; + } /* 20071115 KSC: THIS IS UNUSED RIGHT NOW @@ -95,37 +95,38 @@ public void setIdClusters(IdClusters[] c){ clusters = c; } */ - - public int getNumDrawings() { - return numDrawings; - } - public void setNumDrawings(int numDrawings) { - this.numDrawings = numDrawings; - } - - public int getNumIdClusters() { - return numIdClusters; - } + public int getNumDrawings() { + return numDrawings; + } + + public void setNumDrawings(int numDrawings) { + this.numDrawings = numDrawings; + } + + public int getNumIdClusters() { + return numIdClusters; + } /* 20080903 KSC: numIdClusters is soley a function of spidMax/1024 public void setNumIdClusters(int numIdClusters) { this.numIdClusters = numIdClusters; } -*/ +*/ - public int getNumShapes() { - return numShapes; - } + public int getNumShapes() { + return numShapes; + } - public void setNumShapes(int numShapes) { - this.numShapes = numShapes; - } - - // 20071113 KSC - /** - * set SpidMax - */ - public void setSpidMax(int spid) { - this.spidMax= spid; - } + public void setNumShapes(int numShapes) { + this.numShapes = numShapes; + } + + // 20071113 KSC + + /** + * set SpidMax + */ + public void setSpidMax(int spid) { + this.spidMax = spid; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtDggContainer.java b/src/main/java/io/starter/formats/escher/MsofbtDggContainer.java index 371b609..9ed1cb4 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtDggContainer.java +++ b/src/main/java/io/starter/formats/escher/MsofbtDggContainer.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,21 +22,21 @@ */ package io.starter.formats.escher; -import io.starter.toolkit.ByteTools; - //0xf000 public class MsofbtDggContainer extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2332486523666942739L; - public MsofbtDggContainer(int fbt,int inst,int version){ - super(fbt,inst,version); - } - protected byte[] getData(){ - - return new byte[0]; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -2332486523666942739L; + + public MsofbtDggContainer(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + protected byte[] getData() { + + return new byte[0]; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtOPT.java b/src/main/java/io/starter/formats/escher/MsofbtOPT.java index 319f9bc..9d5c71a 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtOPT.java +++ b/src/main/java/io/starter/formats/escher/MsofbtOPT.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,316 +22,326 @@ */ package io.starter.formats.escher; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Iterator; - import io.starter.OpenXLS.FormatHandle; import io.starter.formats.XLS.FormatConstants; import io.starter.formats.XLS.MSODrawingConstants; import io.starter.formats.XLS.XLSConstants; -import io.starter.formats.XLS.charts.Chart; -import io.starter.toolkit.*; +import io.starter.toolkit.ByteTools; +import io.starter.toolkit.Logger; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; //0xf00b + /** * shape properties table */ public class MsofbtOPT extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 465530579513265882L; - byte[] recordData = new byte[0]; - boolean bBackground, bActive, bPrint; - int imageIndex=-1; - java.awt.Color fillColor= null; - int fillType= 0; - String imageName="", shapeName="", alternateText=""; - int[] lineprops= null; // Line properties -- weight, color, style ... - static final int LINEPROPTS_STYLE= 0; - static final int LINEPROPTS_WEIGHT= 1; - static final int LINEPROPTS_COLOR= 2; - boolean hasTextId= false; // true if OPT contains msofbtlTxid - necessary to calculate container lengths correctly - see MSODrawing.updateRecord - LinkedHashMap props= new LinkedHashMap(); // parsed property table note: properties are ordered via property set; - - public MsofbtOPT(int fbt,int inst,int version){ - super(fbt,inst,version); - } - - public void setImageIndex(int value){ - imageIndex = value; - if (imageIndex>-1) - setProperty(MSODrawingConstants.msooptpib,true,false,value,null); - else // remove property - props.remove(Integer.valueOf(MSODrawingConstants.msooptpib)); - - } - - /** - * returns true if this OPT subrecord contains an msofbtlTxid entry- - * necessary to calculate container lengths correctly - see MSODrawing.updateRecord - * @return - */ - public boolean hasTextId() { return hasTextId; } - /** - * set the image name for this shape - * msooptpibName - * @param name - */ - public void setImageName(String name){ - try{ - imageName= name; - if (imageName==null || imageName.equals("")) { - // remove property - props.remove(Integer.valueOf(MSODrawingConstants.msooptpibName)); - return; - } - byte[] imageNameBytes = name.getBytes(XLSConstants.UNICODEENCODING); - byte[] newbytes = new byte[imageNameBytes.length]; - System.arraycopy(imageNameBytes, 0, newbytes, 0, imageNameBytes.length); - imageNameBytes = newbytes; - setProperty(MSODrawingConstants.msooptpibName,true,true,imageNameBytes.length,imageNameBytes); - }catch(Exception e){ - Logger.logErr("Msofbt.setImageName failed.", e); - } - } - - /** - * set the shape name atom in this OPT record - * @param name - */ - public void setShapeName(String name){ - try{ - shapeName= name; - if (shapeName==null || shapeName.equals("")) { - // remove property - props.remove(Integer.valueOf(MSODrawingConstants.msooptwzName)); - return; - } - byte[] shapeNameBytes = name.getBytes(XLSConstants.UNICODEENCODING); - byte[] newbytes = new byte[shapeNameBytes.length]; - System.arraycopy(shapeNameBytes, 0, newbytes, 0, shapeNameBytes.length); - shapeNameBytes = newbytes; - setProperty(MSODrawingConstants.msooptwzName,true,true,shapeNameBytes.length,shapeNameBytes); - }catch(Exception e){ - Logger.logErr("Msofbt.setShapeName failed.", e); - } - } - - /** - * generate the recordData from the stored props hashmap if anything has changed - */ - protected byte[] getData(){ - if (isDirty) { // regenerate recordData as contents have changed - byte[] tmp= new byte[inst*6]; // basic property table - byte[] complexData= new byte[0]; // extra complex data, if any, after basic property table - int pos= 0; - // try to extract properties in order - java.util.ArrayList keys= new java.util.ArrayList(props.keySet()); - // try in numerical order-- appears to MOSTLY be the case ... - Object[] k= keys.toArray(); - Arrays.sort(k); - - // write out properties in (numerical) order - for (int i= 0; i < k.length; i++){ - Integer propId= ((Integer) k[i]); - Object[] o= (Object[]) props.get(propId); - boolean isComplex= ((Boolean)o[0]).booleanValue(); - boolean isBid= ((Boolean)o[2]).booleanValue(); - int flag= 0; - if(isComplex)flag = flag | 0x80; - if(isBid) flag = flag | 0x40; - int dtx; - if (!isComplex) - dtx= ((Integer)o[1]).intValue(); // non-complex data is just an integer - else { - dtx= ((byte[])o[1]).length + 2; // stored data is a byte array; get length + 2 - complexData= ByteTools.append((byte[])o[1], complexData); - complexData= ByteTools.append(new byte[] {0, 0}, complexData); - } - // the basic part of the property table - tmp[pos++]= ((byte)( 0xFF & propId.intValue())); - tmp[pos++]= (byte)(flag | ( 0x3F00 & propId.intValue())>>8); - byte[] dtxBytes = ByteTools.cLongToLEBytes(dtx); - System.arraycopy(dtxBytes, 0, tmp, pos, 4); - pos+=4; - } - recordData= new byte[tmp.length + complexData.length]; - System.arraycopy(tmp, 0, recordData, 0, tmp.length); - // after the basic property table (PropID, IsBID, IsCOMPEX, dtx), store the complex data - System.arraycopy(complexData, 0, recordData, tmp.length, complexData.length); - isDirty= false; - } - this.setLength(recordData.length); - return recordData; - } - - public void setData(byte[] b) { - recordData= b; - props.clear(); - imageIndex=-1; - imageName=""; - shapeName=""; - alternateText=""; - parseData(); - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 465530579513265882L; + byte[] recordData = new byte[0]; + boolean bBackground, bActive, bPrint; + int imageIndex = -1; + java.awt.Color fillColor = null; + int fillType = 0; + String imageName = "", shapeName = "", alternateText = ""; + int[] lineprops = null; // Line properties -- weight, color, style ... + static final int LINEPROPTS_STYLE = 0; + static final int LINEPROPTS_WEIGHT = 1; + static final int LINEPROPTS_COLOR = 2; + boolean hasTextId = false; // true if OPT contains msofbtlTxid - necessary to calculate container lengths correctly - see MSODrawing.updateRecord + LinkedHashMap props = new LinkedHashMap(); // parsed property table note: properties are ordered via property set; - /** - * given property table bytes, parse into props hashmap - */ - private void parseData() { - /* - * First part of an OPT record is an array of FOPTEs (propertyId, fBid, fComplex, data) - * If fComplex is set, the actual data (Unicode strings, arrays, etc.) is stored AFTER the last FOPTE (sorted by property id???); - * the length of the complex data is stored in the data field. - * if fComplex is not set, the meaining of the data field is dependent upon the propertyId - * if fBid is set and fComplex is not set, the data = a BLIP id (= an index into the BLIP store) - * The number of FOPTES is the inst field read above - */ - int propertyId,fBid,fComplex; - //int n= inst; // number of properties to parse - int pos= 0; // pointer to current property in data/property table - if (inst==0 && recordData.length > 0) { // called from GelFrame ... - byte[] dat = new byte[8]; // read header - System.arraycopy(recordData, 0, dat, 0, 8); - version = (0xF&dat[0]); // 15 for containers, version for atoms - inst = ((0xFF&dat[1])<<4)|(0xF0&dat[0])>>4; - fbt = ((0xFF&dat[3])<<8)|(0xFF&dat[2]); // record type id==0xF00B - pos= 8; // skip header - } - for (int i= 0; i < inst; i++) { - propertyId = (0x3F&recordData[pos+1])<<8|(0xFF&recordData[pos]); // 14 bits - fBid = ((0x40&recordData[pos+1])>>6); // specifies whether the value in the dtx field is a BLIP identifier- only valid if fComplex= FALSE - fComplex = ((0x80&recordData[pos+1])>>7); // if complex property, value is length. Data is parsed after. - int dtx = ByteTools.readInt(recordData[pos+2],recordData[pos+3],recordData[pos+4],recordData[pos+5]); - // TODO: if property number is of type bool/long/msoarray/... parse accordingly .. - if (propertyId==MSODrawingConstants.msooptpib) // blip to display - imageIndex= dtx; - else if (propertyId==MSODrawingConstants.msooptFillType) { - fillType= dtx; - } else if (propertyId==MSODrawingConstants.msooptfillColor) - fillColor= setFillColor(dtx); - else if (propertyId==MSODrawingConstants.msooptfBackground) - bBackground= (dtx!=0); - else if (propertyId==MSODrawingConstants.msooptGroupShapeProperties) { - //bPrint= (dtx!=0); // NOT TRUE!! TODO: parse real GroupShapeProperties (many) - } else if (propertyId==MSODrawingConstants.msooptpictureActive) - bActive= (dtx!=0); - else if (propertyId==MSODrawingConstants.msooptlineWidth) { // appears that this controls display of line - if (lineprops==null) lineprops= new int[3]; - lineprops[LINEPROPTS_WEIGHT]= dtx; - } else if (propertyId==MSODrawingConstants.msooptlineColor) { // appears that this is always present, even if no line - if (lineprops==null) lineprops= new int[3]; - lineprops[LINEPROPTS_COLOR]= dtx; - } else if (propertyId==MSODrawingConstants.msooptLineStyle) { - if (lineprops==null) lineprops= new int[3]; - lineprops[LINEPROPTS_STYLE]= dtx; - } else if (propertyId==MSODrawingConstants.msofbtlTxid) { - hasTextId= true; - } // msooptFillWidth - props.put(Integer.valueOf(propertyId), new Object[] {Boolean.valueOf(fComplex!=0), Integer.valueOf(dtx), Boolean.valueOf(fBid!=0)}); - pos+=6; - } - - // now parse complex data after all "tightly packed" properties have been parsed. Order of data is original order - Iterator ii= props.keySet().iterator(); - while (ii.hasNext()) { - Integer propId=(Integer) ii.next(); - Object[] o= (Object[]) props.get(propId); // Object[]: 0= isComplex, 1= dtx (value or len of complex data -- filled in below), 2= isBid - if (((Boolean)o[0]).booleanValue()) { - int len=((Integer)o[1]).intValue(); - if (len >= 2) { - // apparently each record is delimited by a double byte 0 -- so decrement by 2 here and increment pos by 2 below - byte[] complexdata= new byte[len-2]; // retrieve complex data at end of record - System.arraycopy(recordData,pos,complexdata,0,complexdata.length); // get property data after main property table - props.put(propId, new Object[] {o[0], complexdata, o[2]}); //store complex data for later retrieval - if (propId.intValue()==MSODrawingConstants.msooptpibName) { // = image name - try{ - imageName = new String(complexdata, XLSConstants.UNICODEENCODING); - }catch(Exception e){imageName = "Unnamed"; - } - } else if (propId.intValue()==MSODrawingConstants.msooptwzName) { // = shape name - try{ - shapeName = new String(complexdata, XLSConstants.UNICODEENCODING); - }catch(Exception e){; - } - } else if (propId.intValue()==MSODrawingConstants.msooptwzDescription) { // = Alternate Text - try{ - alternateText = new String(complexdata, XLSConstants.UNICODEENCODING); - }catch(Exception e){; - } - } else if (propId.intValue()==MSODrawingConstants.msooptFillBlipName) { // = the comment, file name, or the full URL that is used as a fill - try{ - String fillName= new String(complexdata, XLSConstants.UNICODEENCODING); - }catch(Exception e){; - } - } - - pos+= complexdata.length + 2; - } - } - } - } - - /** - * - * @param propId msofbtopt property ide see Msoconstants - * @param isBid value is a BLIP id - only valid if isComplex is false - * @param isComplex if false, dtx is used; if true, complexBytes are used and dtx=length - * @param dtx if not iscomplex, the value of property id; if iscomplex, length of complex data following the property table - * @param complexBytes if iscomplex, holds value of complex property e.g. shape name - */ - public void setProperty(int propId, boolean isBid, boolean isComplex, int dtx, byte[] complexBytes){ - // a general order of common properties is (via property id): - /* - * 127 - * 267 - * 261 - * 262 - * 128 - * 133 - * 139 - * 191 - * 385 - * 447 - * 448 - * 459 - * 511 - */ - if (isComplex) // complexBytes shouldn't be null - props.put(Integer.valueOf(propId), new Object[] {Boolean.valueOf(isComplex), complexBytes, Boolean.valueOf(isBid)}); - else - props.put(Integer.valueOf(propId), new Object[] {Boolean.valueOf(isComplex), Integer.valueOf(dtx), Boolean.valueOf(isBid)}); - - this.inst= props.size(); - isDirty= true; // flag to regenerate recordData - } - public boolean hasBorder() { - if (lineprops!=null && lineprops[LINEPROPTS_WEIGHT] > 1) - return true; - return false; - } - public int getBorderLineWidth() { - if (lineprops!=null) - return lineprops[LINEPROPTS_WEIGHT]; - return -1; - } - public int getImageIndex() { - return imageIndex; - } - public String getImageName() { - return imageName; - } - public String getShapeName() { - return shapeName; - } - - /** - * Debug Output -- For Internal Use Only - */ - public String debugOutput() { - int propertyId; - StringBuffer log= new StringBuffer(); + public MsofbtOPT(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + public void setImageIndex(int value) { + imageIndex = value; + if (imageIndex > -1) + setProperty(MSODrawingConstants.msooptpib, true, false, value, null); + else // remove property + props.remove(Integer.valueOf(MSODrawingConstants.msooptpib)); + + } + + /** + * returns true if this OPT subrecord contains an msofbtlTxid entry- + * necessary to calculate container lengths correctly - see MSODrawing.updateRecord + * + * @return + */ + public boolean hasTextId() { + return hasTextId; + } + + /** + * set the image name for this shape + * msooptpibName + * + * @param name + */ + public void setImageName(String name) { + try { + imageName = name; + if (imageName == null || imageName.equals("")) { + // remove property + props.remove(Integer.valueOf(MSODrawingConstants.msooptpibName)); + return; + } + byte[] imageNameBytes = name.getBytes(XLSConstants.UNICODEENCODING); + byte[] newbytes = new byte[imageNameBytes.length]; + System.arraycopy(imageNameBytes, 0, newbytes, 0, imageNameBytes.length); + imageNameBytes = newbytes; + setProperty(MSODrawingConstants.msooptpibName, true, true, imageNameBytes.length, imageNameBytes); + } catch (Exception e) { + Logger.logErr("Msofbt.setImageName failed.", e); + } + } + + /** + * set the shape name atom in this OPT record + * + * @param name + */ + public void setShapeName(String name) { + try { + shapeName = name; + if (shapeName == null || shapeName.equals("")) { + // remove property + props.remove(Integer.valueOf(MSODrawingConstants.msooptwzName)); + return; + } + byte[] shapeNameBytes = name.getBytes(XLSConstants.UNICODEENCODING); + byte[] newbytes = new byte[shapeNameBytes.length]; + System.arraycopy(shapeNameBytes, 0, newbytes, 0, shapeNameBytes.length); + shapeNameBytes = newbytes; + setProperty(MSODrawingConstants.msooptwzName, true, true, shapeNameBytes.length, shapeNameBytes); + } catch (Exception e) { + Logger.logErr("Msofbt.setShapeName failed.", e); + } + } + + /** + * generate the recordData from the stored props hashmap if anything has changed + */ + protected byte[] getData() { + if (isDirty) { // regenerate recordData as contents have changed + byte[] tmp = new byte[inst * 6]; // basic property table + byte[] complexData = new byte[0]; // extra complex data, if any, after basic property table + int pos = 0; + // try to extract properties in order + java.util.ArrayList keys = new java.util.ArrayList(props.keySet()); + // try in numerical order-- appears to MOSTLY be the case ... + Object[] k = keys.toArray(); + Arrays.sort(k); + + // write out properties in (numerical) order + for (int i = 0; i < k.length; i++) { + Integer propId = ((Integer) k[i]); + Object[] o = (Object[]) props.get(propId); + boolean isComplex = ((Boolean) o[0]).booleanValue(); + boolean isBid = ((Boolean) o[2]).booleanValue(); + int flag = 0; + if (isComplex) flag = flag | 0x80; + if (isBid) flag = flag | 0x40; + int dtx; + if (!isComplex) + dtx = ((Integer) o[1]).intValue(); // non-complex data is just an integer + else { + dtx = ((byte[]) o[1]).length + 2; // stored data is a byte array; get length + 2 + complexData = ByteTools.append((byte[]) o[1], complexData); + complexData = ByteTools.append(new byte[]{0, 0}, complexData); + } + // the basic part of the property table + tmp[pos++] = ((byte) (0xFF & propId.intValue())); + tmp[pos++] = (byte) (flag | (0x3F00 & propId.intValue()) >> 8); + byte[] dtxBytes = ByteTools.cLongToLEBytes(dtx); + System.arraycopy(dtxBytes, 0, tmp, pos, 4); + pos += 4; + } + recordData = new byte[tmp.length + complexData.length]; + System.arraycopy(tmp, 0, recordData, 0, tmp.length); + // after the basic property table (PropID, IsBID, IsCOMPEX, dtx), store the complex data + System.arraycopy(complexData, 0, recordData, tmp.length, complexData.length); + isDirty = false; + } + this.setLength(recordData.length); + return recordData; + } + + public void setData(byte[] b) { + recordData = b; + props.clear(); + imageIndex = -1; + imageName = ""; + shapeName = ""; + alternateText = ""; + parseData(); + } + + /** + * given property table bytes, parse into props hashmap + */ + private void parseData() { + /* + * First part of an OPT record is an array of FOPTEs (propertyId, fBid, fComplex, data) + * If fComplex is set, the actual data (Unicode strings, arrays, etc.) is stored AFTER the last FOPTE (sorted by property id???); + * the length of the complex data is stored in the data field. + * if fComplex is not set, the meaining of the data field is dependent upon the propertyId + * if fBid is set and fComplex is not set, the data = a BLIP id (= an index into the BLIP store) + * The number of FOPTES is the inst field read above + */ + int propertyId, fBid, fComplex; + //int n= inst; // number of properties to parse + int pos = 0; // pointer to current property in data/property table + if (inst == 0 && recordData.length > 0) { // called from GelFrame ... + byte[] dat = new byte[8]; // read header + System.arraycopy(recordData, 0, dat, 0, 8); + version = (0xF & dat[0]); // 15 for containers, version for atoms + inst = ((0xFF & dat[1]) << 4) | (0xF0 & dat[0]) >> 4; + fbt = ((0xFF & dat[3]) << 8) | (0xFF & dat[2]); // record type id==0xF00B + pos = 8; // skip header + } + for (int i = 0; i < inst; i++) { + propertyId = (0x3F & recordData[pos + 1]) << 8 | (0xFF & recordData[pos]); // 14 bits + fBid = ((0x40 & recordData[pos + 1]) >> 6); // specifies whether the value in the dtx field is a BLIP identifier- only valid if fComplex= FALSE + fComplex = ((0x80 & recordData[pos + 1]) >> 7); // if complex property, value is length. Data is parsed after. + int dtx = ByteTools.readInt(recordData[pos + 2], recordData[pos + 3], recordData[pos + 4], recordData[pos + 5]); + // TODO: if property number is of type bool/long/msoarray/... parse accordingly .. + if (propertyId == MSODrawingConstants.msooptpib) // blip to display + imageIndex = dtx; + else if (propertyId == MSODrawingConstants.msooptFillType) { + fillType = dtx; + } else if (propertyId == MSODrawingConstants.msooptfillColor) + fillColor = setFillColor(dtx); + else if (propertyId == MSODrawingConstants.msooptfBackground) + bBackground = (dtx != 0); + else if (propertyId == MSODrawingConstants.msooptGroupShapeProperties) { + //bPrint= (dtx!=0); // NOT TRUE!! TODO: parse real GroupShapeProperties (many) + } else if (propertyId == MSODrawingConstants.msooptpictureActive) + bActive = (dtx != 0); + else if (propertyId == MSODrawingConstants.msooptlineWidth) { // appears that this controls display of line + if (lineprops == null) lineprops = new int[3]; + lineprops[LINEPROPTS_WEIGHT] = dtx; + } else if (propertyId == MSODrawingConstants.msooptlineColor) { // appears that this is always present, even if no line + if (lineprops == null) lineprops = new int[3]; + lineprops[LINEPROPTS_COLOR] = dtx; + } else if (propertyId == MSODrawingConstants.msooptLineStyle) { + if (lineprops == null) lineprops = new int[3]; + lineprops[LINEPROPTS_STYLE] = dtx; + } else if (propertyId == MSODrawingConstants.msofbtlTxid) { + hasTextId = true; + } // msooptFillWidth + props.put(Integer.valueOf(propertyId), new Object[]{Boolean.valueOf(fComplex != 0), Integer.valueOf(dtx), Boolean.valueOf(fBid != 0)}); + pos += 6; + } + + // now parse complex data after all "tightly packed" properties have been parsed. Order of data is original order + Iterator ii = props.keySet().iterator(); + while (ii.hasNext()) { + Integer propId = (Integer) ii.next(); + Object[] o = (Object[]) props.get(propId); // Object[]: 0= isComplex, 1= dtx (value or len of complex data -- filled in below), 2= isBid + if (((Boolean) o[0]).booleanValue()) { + int len = ((Integer) o[1]).intValue(); + if (len >= 2) { + // apparently each record is delimited by a double byte 0 -- so decrement by 2 here and increment pos by 2 below + byte[] complexdata = new byte[len - 2]; // retrieve complex data at end of record + System.arraycopy(recordData, pos, complexdata, 0, complexdata.length); // get property data after main property table + props.put(propId, new Object[]{o[0], complexdata, o[2]}); //store complex data for later retrieval + if (propId.intValue() == MSODrawingConstants.msooptpibName) { // = image name + try { + imageName = new String(complexdata, XLSConstants.UNICODEENCODING); + } catch (Exception e) { + imageName = "Unnamed"; + } + } else if (propId.intValue() == MSODrawingConstants.msooptwzName) { // = shape name + try { + shapeName = new String(complexdata, XLSConstants.UNICODEENCODING); + } catch (Exception e) { + } + } else if (propId.intValue() == MSODrawingConstants.msooptwzDescription) { // = Alternate Text + try { + alternateText = new String(complexdata, XLSConstants.UNICODEENCODING); + } catch (Exception e) { + } + } else if (propId.intValue() == MSODrawingConstants.msooptFillBlipName) { // = the comment, file name, or the full URL that is used as a fill + try { + String fillName = new String(complexdata, XLSConstants.UNICODEENCODING); + } catch (Exception e) { + } + } + + pos += complexdata.length + 2; + } + } + } + } + + /** + * @param propId msofbtopt property ide see Msoconstants + * @param isBid value is a BLIP id - only valid if isComplex is false + * @param isComplex if false, dtx is used; if true, complexBytes are used and dtx=length + * @param dtx if not iscomplex, the value of property id; if iscomplex, length of complex data following the property table + * @param complexBytes if iscomplex, holds value of complex property e.g. shape name + */ + public void setProperty(int propId, boolean isBid, boolean isComplex, int dtx, byte[] complexBytes) { + // a general order of common properties is (via property id): + /* + * 127 + * 267 + * 261 + * 262 + * 128 + * 133 + * 139 + * 191 + * 385 + * 447 + * 448 + * 459 + * 511 + */ + if (isComplex) // complexBytes shouldn't be null + props.put(Integer.valueOf(propId), new Object[]{Boolean.valueOf(isComplex), complexBytes, Boolean.valueOf(isBid)}); + else + props.put(Integer.valueOf(propId), new Object[]{Boolean.valueOf(isComplex), Integer.valueOf(dtx), Boolean.valueOf(isBid)}); + + this.inst = props.size(); + isDirty = true; // flag to regenerate recordData + } + + public boolean hasBorder() { + return lineprops != null && lineprops[LINEPROPTS_WEIGHT] > 1; + } + + public int getBorderLineWidth() { + if (lineprops != null) + return lineprops[LINEPROPTS_WEIGHT]; + return -1; + } + + public int getImageIndex() { + return imageIndex; + } + + public String getImageName() { + return imageName; + } + + public String getShapeName() { + return shapeName; + } + + /** + * Debug Output -- For Internal Use Only + */ + public String debugOutput() { + int propertyId; + StringBuffer log = new StringBuffer(); /* java.util.ArrayList keys= new java.util.ArrayList(props.keySet()); int n= keys.size(); for (int i= 0; i < n; i++) { @@ -353,186 +363,187 @@ public String debugOutput() { log.append("\t\t" + propertyId + "/" + fBid + "/" + fComplex + "/" + dtx); //pos+=6; } -*/ - int n= inst; // number of properties to parse - // pointer to current property in data/property table - int fBid= 0,fComplex= 0; - int end= recordData.length; - for (int pos = 0; pos < end; pos+=6) { - propertyId= (0x3F&recordData[pos+1])<<8|(0xFF&recordData[pos]); - fBid = ((0x40&recordData[pos+1])>>6); - fComplex = ((0x80&recordData[pos+1])>>7); // if complex property, value is length. Data is parsed after. - int dtx = ByteTools.readInt(recordData[pos+2],recordData[pos+3],recordData[pos+4],recordData[pos+5]); - if (fComplex!=0) - end-= dtx; - log.append("\t\t" + propertyId + "/" + fBid + "/" + fComplex + "/" + dtx); - } - return log.toString(); - } - - /** - * Interpret an OfficeArtCOLORREF (used in fillColor, lineColor and many other opts) - * @param clrStructure - *
                More information - * The OfficeArtCOLORREF structure specifies a color. The high 8 bits MAY be set to 0xFF, in which case the color MUST be ignored. - The color properties that are specified in the following table have a set of extended-color properties. The color property specifies the main color. - The colorExt and colorExtMod properties specify the extended colors that can be used to define the main color more precisely. - If neither extended-color property is set, the main color property contains the full color definition. - Otherwise, the colorExt property specifies the base color, and the colorExtMod property specifies a tint or shade modification that is applied to the colorExt property. - In this case, the main color property contains the flattened RGB color that is computed by applying the specified tint or shade modification to the specified base color. +*/ + int n = inst; // number of properties to parse + // pointer to current property in data/property table + int fBid = 0, fComplex = 0; + int end = recordData.length; + for (int pos = 0; pos < end; pos += 6) { + propertyId = (0x3F & recordData[pos + 1]) << 8 | (0xFF & recordData[pos]); + fBid = ((0x40 & recordData[pos + 1]) >> 6); + fComplex = ((0x80 & recordData[pos + 1]) >> 7); // if complex property, value is length. Data is parsed after. + int dtx = ByteTools.readInt(recordData[pos + 2], recordData[pos + 3], recordData[pos + 4], recordData[pos + 5]); + if (fComplex != 0) + end -= dtx; + log.append("\t\t" + propertyId + "/" + fBid + "/" + fComplex + "/" + dtx); + } + return log.toString(); + } + /** + * Interpret an OfficeArtCOLORREF (used in fillColor, lineColor and many other opts) + * + * @param clrStructure
                More information + * The OfficeArtCOLORREF structure specifies a color. The high 8 bits MAY be set to 0xFF, in which case the color MUST be ignored. + * The color properties that are specified in the following table have a set of extended-color properties. The color property specifies the main color. + * The colorExt and colorExtMod properties specify the extended colors that can be used to define the main color more precisely. + * If neither extended-color property is set, the main color property contains the full color definition. + * Otherwise, the colorExt property specifies the base color, and the colorExtMod property specifies a tint or shade modification that is applied to the colorExt property. + * In this case, the main color property contains the flattened RGB color that is computed by applying the specified tint or shade modification to the specified base color. + *

                + *

                + * A - unused1 (1 bit): A bit that is undefined and MUST be ignored. + *

                + * B - unused2 (1 bit): A bit that is undefined and MUST be ignored. + *

                + * C - unused3 (1 bit): A bit that is undefined and MUST be ignored. + *

                + * D - fSysIndex (1 bit): A bit that specifies whether the system color scheme will be used to determine the color. + * A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index into the system color table. Values less than 0x00F0 map directly to system colors. + * For more information, see [MSDN-GetSysColor] (below) + * The following table specifies values that have special meaning. + * Value Meaning + * 0x00F0 Use the fill color of the shape. + * 0x00F1 If the shape contains a line, use the line color of the shape. Otherwise, use the fill color. + * 0x00F2 Use the line color of the shape. + * 0x00F3 Use the shadow color of the shape. + * 0x00F4 Use the current, or last-used, color. + * 0x00F5 Use the fill background color of the shape. + * 0x00F6 Use the line background color of the shape. + * 0x00F7 If the shape contains a fill, use the fill color of the shape. Otherwise, use the line color. + * The following table specifies values that indicate special procedural properties that are used to modify the color components of another color. + * These values are combined with those in the preceding table or with a user-specified color. The first six values are mutually exclusive. + * Value Meaning + * 0x0100 Darken the color by the value that is specified in the blue field. A blue value of 0xFF specifies that the color is to be left unchanged, whereas a blue value of 0x00 specifies that the color is to be completely darkened. + * 0x0200 Lighten the color by the value that is specified in the blue field. A blue value of 0xFF specifies that the color is to be left unchanged, whereas a blue value of 0x00 specifies that the color is to be completely lightened. + * 0x0300 Add a gray level RGB value. The blue field contains the gray level to add: NewColor = SourceColor + gray + * 0x0400 Subtract a gray level RGB value. The blue field contains the gray level to subtract: NewColor = SourceColor - gray + * 0x0500 Reverse-subtract a gray level RGB value. The blue field contains the gray level from which to subtract: NewColor = gray - SourceColor + * 0x0600 If the color component being modified is less than the parameter contained in the blue field, set it to the minimum intensity. + * If the color component being modified is greater than or equal to the parameter, set it to the maximum intensity. + * 0x2000 After making other modifications, invert the color. + * 0x4000 After making other modifications, invert the color by toggling just the high bit of each color channel. + * 0x8000 Before making other modifications, convert the color to grayscale. + * E - fSchemeIndex (1 bit): A bit that specifies whether the current application-defined color scheme will be used to determine the color. + * A value of 0x1 specifies that red will be treated as an index into the current color scheme table. If this value is 0x1, green and blue MUST be 0x00. + * F - fSystemRGB (1 bit): A bit that specifies whether the color is a standard RGB color. The following table specifies the meaning of each value for this field. + * Value Meaning + * 0x0 The RGB color MAY use halftone dithering to display. + * 0x1 The color MUST be a solid color. + * G - fPaletteRGB (1 bit): A bit that specifies whether the current palette will be used to determine the color. + * A value of 0x1 specifies that red, green, and blue contain an RGB value that will be matched in the current color palette. This color MUST be solid. + * H - fPaletteIndex (1 bit): A bit that specifies whether the current palette will be used to determine the color. + * A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index into the current color palette. This color MAY<1> be dithered. + * If this value is 0x1, blue MUST be 0x00. + * blue (1 byte): An unsigned integer that specifies the intensity of the blue color channel. A value of 0x00 has the minimum blue intensity. A value of 0xFF has the maximum blue intensity. + * green (1 byte): An unsigned integer that specifies the intensity of the green color channel. A value of 0x00 has the minimum green intensity. A value of 0xFF has the maximum green intensity. + * red (1 byte): An unsigned integer that specifies the intensity of the red color channel. A value of 0x00 has the minimum red intensity. A value of 0xFF has the maximum red intensity. + *

                + * ... + *

                + * MSDN-GetSysColor + * Value Meaning + * COLOR_3DDKSHADOW 21 Dark shadow for three-dimensional display elements. + * COLOR_3DFACE 15 Face color for three-dimensional display elements and for dialog box backgrounds. + * COLOR_3DHIGHLIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) + * COLOR_3DHILIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) + * COLOR_3DLIGHT 22 Light color for three-dimensional display elements (for edges facing the light source.) + * COLOR_3DSHADOW 16 Shadow color for three-dimensional display elements (for edges facing away from the light source). + * COLOR_ACTIVEBORDER 10 Active window border. + * COLOR_ACTIVECAPTION 2 Active window title bar. Specifies the left side color in the color gradient of an active window's title bar if the gradient effect is enabled. + * COLOR_APPWORKSPACE 12 Background color of multiple document interface (MDI) applications. + * COLOR_BACKGROUND 1 Desktop. + * COLOR_BTNFACE 15 Face color for three-dimensional display elements and for dialog box backgrounds. + * COLOR_BTNHIGHLIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) + * COLOR_BTNHILIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) + * COLOR_BTNSHADOW 16 Shadow color for three-dimensional display elements (for edges facing away from the light source). + * COLOR_BTNTEXT 18 Text on push buttons. + * COLOR_CAPTIONTEXT 9 Text in caption, size box, and scroll bar arrow box. + * COLOR_DESKTOP 1 Desktop. + * COLOR_GRADIENTACTIVECAPTION 27 Right side color in the color gradient of an active window's title bar. + * COLOR_ACTIVECAPTION specifies the left side color. Use SPI_GETGRADIENTCAPTIONS with the SystemParametersInfo function to determine whether the gradient effect is enabled. + * COLOR_GRADIENTINACTIVECAPTION 28 Right side color in the color gradient of an inactive window's title bar. COLOR_INACTIVECAPTION specifies the left side color. + * COLOR_GRAYTEXT 17 Grayed (disabled) text. This color is set to 0 if the current display driver does not support a solid gray color. + * COLOR_HIGHLIGHT 13 Item(s) selected in a control. + * COLOR_HIGHLIGHTTEXT 14 Text of item(s) selected in a control. + * COLOR_HOTLIGHT 26 Color for a hyperlink or hot-tracked item. + * COLOR_INACTIVEBORDER11 Inactive window border. + * COLOR_INACTIVECAPTION3 Inactive window caption. + * Specifies the left side color in the color gradient of an inactive window's title bar if the gradient effect is enabled. + * COLOR_INACTIVECAPTIONTEXT19 Color of text in an inactive caption. + * COLOR_INFOBK 24 Background color for tooltip controls. + * COLOR_INFOTEXT 23 Text color for tooltip controls. + * COLOR_MENU 4 Menu background. + * COLOR_MENUHILIGHT 29 The color used to highlight menu items when the menu appears as a flat menu (see SystemParametersInfo). The highlighted menu item is outlined with COLOR_HIGHLIGHT. + * Windows 2000: This value is not supported. + * COLOR_MENUBAR 30 The background color for the menu bar when menus appear as flat menus (see SystemParametersInfo). However, COLOR_MENU continues to specify the background color of the menu popup. + * Windows 2000: This value is not supported. + * COLOR_MENUTEXT 7 Text in menus. + * COLOR_SCROLLBAR 0 Scroll bar gray area. + * COLOR_WINDOW 5 Window background. + * COLOR_WINDOWFRAME 6 Window frame. + * COLOR_WINDOWTEXT 8 Text in windows. + */ + private java.awt.Color setFillColor(int clrStructure) { + byte[] b = ByteTools.longToByteArray(clrStructure); + boolean bPaletteIndex, bSchemeIndex, bSysIndex; + short fillclr; - A - unused1 (1 bit): A bit that is undefined and MUST be ignored. + bPaletteIndex = (b[4] & 0x1) == 0x1; // specifies whether the current palette will be used to determine the color + bSchemeIndex = (b[4] & 0x8) == 0x8; // specifies whether the current application defined color scheme will be used to determine the color. + bSysIndex = (b[4] & 0x10) == 0x10; // specifies whether the system color scheme will be used to determine the color. - B - unused2 (1 bit): A bit that is undefined and MUST be ignored. + if (bPaletteIndex) { // // GREEN and RED are treated as an unsigned 16-bit index into the current color palette. This color MAY be dithered. BLUE MUST be 0x00. + } + if (bSchemeIndex) { // RED is an index into the current scheme color table. GREEN and BLUE MUST be 0x00. + fillclr = b[7]; // what does 80 mean?????? + if (fillclr > FormatHandle.COLORTABLE.length) + fillclr = FormatHandle.interpretSpecialColorIndex(fillclr); + fillColor = FormatHandle.COLORTABLE[fillclr]; + return fillColor; + } + if (bSysIndex) { // GREEN and RED will be treated as an unsigned 16-bit index into the system color table. Values less than 0x00F0 map directly to system colors. + fillclr = ByteTools.readShort(b[6], b[7]); + if ((fillclr == 0x00F0) // Use the fill color of the shape. + || (fillclr == 0x00F1) //If the shape contains a line, use the line color of the shape. Otherwise, use the fill color. + || (fillclr == 0x00F2) //Use the line color of the shape. + || (fillclr == 0x00F3) //Use the shadow color of the shape. + || (fillclr == 0x00F4) //Use the current, or last-used, color. + || (fillclr == 0x00F5) //Use the fill background color of the shape. + || (fillclr == 0x00F6) //Use the line background color of the shape. + || (fillclr == 0x00F7)) //If the shape contains a fill, use the fill color of the shape. Otherwise, use the line color. + fillclr = FormatConstants.COLOR_WHITE; + if (fillclr == 0x40) // default fg color + fillclr = FormatConstants.COLOR_WHITE; + else if (fillclr == 0x41) // default bg color + fillclr = FormatConstants.COLOR_WHITE; + else if (fillclr == 0x4D) { // default CHART fg color -- INDEX SPECIFIC! + fillColor = null; // flag to map via series (bar) color defaults + return fillColor; + } else if (fillclr == 0x4E) // default CHART fg color + fillclr = FormatConstants.COLOR_WHITE; + else if (fillclr == 0x4F) // chart neutral color == black + fillclr = FormatConstants.COLOR_BLACK; - C - unused3 (1 bit): A bit that is undefined and MUST be ignored. + if (fillclr < 0 || fillclr > FormatHandle.COLORTABLE.length) + fillclr = FormatConstants.COLOR_WHITE; + fillColor = FormatHandle.COLORTABLE[fillclr]; + return fillColor; + } - D - fSysIndex (1 bit): A bit that specifies whether the system color scheme will be used to determine the color. - A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index into the system color table. Values less than 0x00F0 map directly to system colors. - For more information, see [MSDN-GetSysColor] (below) - The following table specifies values that have special meaning. - Value Meaning - 0x00F0 Use the fill color of the shape. - 0x00F1 If the shape contains a line, use the line color of the shape. Otherwise, use the fill color. - 0x00F2 Use the line color of the shape. - 0x00F3 Use the shadow color of the shape. - 0x00F4 Use the current, or last-used, color. - 0x00F5 Use the fill background color of the shape. - 0x00F6 Use the line background color of the shape. - 0x00F7 If the shape contains a fill, use the fill color of the shape. Otherwise, use the line color. - The following table specifies values that indicate special procedural properties that are used to modify the color components of another color. - These values are combined with those in the preceding table or with a user-specified color. The first six values are mutually exclusive. - Value Meaning - 0x0100 Darken the color by the value that is specified in the blue field. A blue value of 0xFF specifies that the color is to be left unchanged, whereas a blue value of 0x00 specifies that the color is to be completely darkened. - 0x0200 Lighten the color by the value that is specified in the blue field. A blue value of 0xFF specifies that the color is to be left unchanged, whereas a blue value of 0x00 specifies that the color is to be completely lightened. - 0x0300 Add a gray level RGB value. The blue field contains the gray level to add: NewColor = SourceColor + gray - 0x0400 Subtract a gray level RGB value. The blue field contains the gray level to subtract: NewColor = SourceColor - gray - 0x0500 Reverse-subtract a gray level RGB value. The blue field contains the gray level from which to subtract: NewColor = gray - SourceColor - 0x0600 If the color component being modified is less than the parameter contained in the blue field, set it to the minimum intensity. - If the color component being modified is greater than or equal to the parameter, set it to the maximum intensity. - 0x2000 After making other modifications, invert the color. - 0x4000 After making other modifications, invert the color by toggling just the high bit of each color channel. - 0x8000 Before making other modifications, convert the color to grayscale. - E - fSchemeIndex (1 bit): A bit that specifies whether the current application-defined color scheme will be used to determine the color. - A value of 0x1 specifies that red will be treated as an index into the current color scheme table. If this value is 0x1, green and blue MUST be 0x00. - F - fSystemRGB (1 bit): A bit that specifies whether the color is a standard RGB color. The following table specifies the meaning of each value for this field. - Value Meaning - 0x0 The RGB color MAY use halftone dithering to display. - 0x1 The color MUST be a solid color. - G - fPaletteRGB (1 bit): A bit that specifies whether the current palette will be used to determine the color. - A value of 0x1 specifies that red, green, and blue contain an RGB value that will be matched in the current color palette. This color MUST be solid. - H - fPaletteIndex (1 bit): A bit that specifies whether the current palette will be used to determine the color. - A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index into the current color palette. This color MAY<1> be dithered. - If this value is 0x1, blue MUST be 0x00. - blue (1 byte): An unsigned integer that specifies the intensity of the blue color channel. A value of 0x00 has the minimum blue intensity. A value of 0xFF has the maximum blue intensity. - green (1 byte): An unsigned integer that specifies the intensity of the green color channel. A value of 0x00 has the minimum green intensity. A value of 0xFF has the maximum green intensity. - red (1 byte): An unsigned integer that specifies the intensity of the red color channel. A value of 0x00 has the minimum red intensity. A value of 0xFF has the maximum red intensity. - - ... - - MSDN-GetSysColor - Value Meaning - COLOR_3DDKSHADOW 21 Dark shadow for three-dimensional display elements. - COLOR_3DFACE 15 Face color for three-dimensional display elements and for dialog box backgrounds. - COLOR_3DHIGHLIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) - COLOR_3DHILIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) - COLOR_3DLIGHT 22 Light color for three-dimensional display elements (for edges facing the light source.) - COLOR_3DSHADOW 16 Shadow color for three-dimensional display elements (for edges facing away from the light source). - COLOR_ACTIVEBORDER 10 Active window border. - COLOR_ACTIVECAPTION 2 Active window title bar. Specifies the left side color in the color gradient of an active window's title bar if the gradient effect is enabled. - COLOR_APPWORKSPACE 12 Background color of multiple document interface (MDI) applications. - COLOR_BACKGROUND 1 Desktop. - COLOR_BTNFACE 15 Face color for three-dimensional display elements and for dialog box backgrounds. - COLOR_BTNHIGHLIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) - COLOR_BTNHILIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) - COLOR_BTNSHADOW 16 Shadow color for three-dimensional display elements (for edges facing away from the light source). - COLOR_BTNTEXT 18 Text on push buttons. - COLOR_CAPTIONTEXT 9 Text in caption, size box, and scroll bar arrow box. - COLOR_DESKTOP 1 Desktop. - COLOR_GRADIENTACTIVECAPTION 27 Right side color in the color gradient of an active window's title bar. - COLOR_ACTIVECAPTION specifies the left side color. Use SPI_GETGRADIENTCAPTIONS with the SystemParametersInfo function to determine whether the gradient effect is enabled. - COLOR_GRADIENTINACTIVECAPTION 28 Right side color in the color gradient of an inactive window's title bar. COLOR_INACTIVECAPTION specifies the left side color. - COLOR_GRAYTEXT 17 Grayed (disabled) text. This color is set to 0 if the current display driver does not support a solid gray color. - COLOR_HIGHLIGHT 13 Item(s) selected in a control. - COLOR_HIGHLIGHTTEXT 14 Text of item(s) selected in a control. - COLOR_HOTLIGHT 26 Color for a hyperlink or hot-tracked item. - COLOR_INACTIVEBORDER11 Inactive window border. - COLOR_INACTIVECAPTION3 Inactive window caption. - Specifies the left side color in the color gradient of an inactive window's title bar if the gradient effect is enabled. - COLOR_INACTIVECAPTIONTEXT19 Color of text in an inactive caption. - COLOR_INFOBK 24 Background color for tooltip controls. - COLOR_INFOTEXT 23 Text color for tooltip controls. - COLOR_MENU 4 Menu background. - COLOR_MENUHILIGHT 29 The color used to highlight menu items when the menu appears as a flat menu (see SystemParametersInfo). The highlighted menu item is outlined with COLOR_HIGHLIGHT. - Windows 2000: This value is not supported. - COLOR_MENUBAR 30 The background color for the menu bar when menus appear as flat menus (see SystemParametersInfo). However, COLOR_MENU continues to specify the background color of the menu popup. - Windows 2000: This value is not supported. - COLOR_MENUTEXT 7 Text in menus. - COLOR_SCROLLBAR 0 Scroll bar gray area. - COLOR_WINDOW 5 Window background. - COLOR_WINDOWFRAME 6 Window frame. - COLOR_WINDOWTEXT 8 Text in windows. - */ - private java.awt.Color setFillColor(int clrStructure) { - byte[] b= ByteTools.longToByteArray(clrStructure); - boolean bPaletteIndex, bSchemeIndex, bSysIndex; - short fillclr; - - bPaletteIndex= (b[4]&0x1)==0x1; // specifies whether the current palette will be used to determine the color - bSchemeIndex= (b[4]&0x8)==0x8; // specifies whether the current application defined color scheme will be used to determine the color. - bSysIndex= (b[4]&0x10)==0x10; // specifies whether the system color scheme will be used to determine the color. - - if (bPaletteIndex) { // // GREEN and RED are treated as an unsigned 16-bit index into the current color palette. This color MAY be dithered. BLUE MUST be 0x00. - } - if (bSchemeIndex) { // RED is an index into the current scheme color table. GREEN and BLUE MUST be 0x00. - fillclr= b[7]; // what does 80 mean?????? - if (fillclr > FormatHandle.COLORTABLE.length) - fillclr= FormatHandle.interpretSpecialColorIndex(fillclr); - fillColor= FormatHandle.COLORTABLE[fillclr]; - return fillColor; - } - if (bSysIndex) { // GREEN and RED will be treated as an unsigned 16-bit index into the system color table. Values less than 0x00F0 map directly to system colors. - fillclr= ByteTools.readShort(b[6], b[7]); - if ((fillclr==0x00F0) // Use the fill color of the shape. - || (fillclr==0x00F1) //If the shape contains a line, use the line color of the shape. Otherwise, use the fill color. - || (fillclr==0x00F2) //Use the line color of the shape. - || (fillclr==0x00F3) //Use the shadow color of the shape. - || (fillclr==0x00F4) //Use the current, or last-used, color. - || (fillclr==0x00F5) //Use the fill background color of the shape. - || (fillclr==0x00F6) //Use the line background color of the shape. - || (fillclr==0x00F7)) //If the shape contains a fill, use the fill color of the shape. Otherwise, use the line color. - fillclr= FormatConstants.COLOR_WHITE; - if (fillclr==0x40) // default fg color - fillclr= FormatConstants.COLOR_WHITE; - else if (fillclr==0x41) // default bg color - fillclr= FormatConstants.COLOR_WHITE; - else if (fillclr==0x4D) { // default CHART fg color -- INDEX SPECIFIC! - fillColor= null; // flag to map via series (bar) color defaults - return fillColor; - }else if (fillclr==0x4E) // default CHART fg color - fillclr= FormatConstants.COLOR_WHITE; - else if (fillclr==0x4F) // chart neutral color == black - fillclr= FormatConstants.COLOR_BLACK; + // otherwise, r, g and blue are color values 0-255 + int bl = (b[5] < 0 ? 255 + b[5] : b[5]); + int g = (b[6] < 0 ? 255 + b[6] : b[6]); + int r = (b[7] < 0 ? 255 + b[7] : b[7]); + fillColor = new java.awt.Color(r, g, bl); + return fillColor; + } - if (fillclr < 0 || fillclr > FormatHandle.COLORTABLE.length) - fillclr= FormatConstants.COLOR_WHITE; - fillColor= FormatHandle.COLORTABLE[fillclr]; - return fillColor; - } - - // otherwise, r, g and blue are color values 0-255 - int bl= (b[5]<0?255+b[5]:b[5]); - int g= (b[6]<0?255+b[6]:b[6]); - int r= (b[7]<0?255+b[7]:b[7]); - fillColor= new java.awt.Color(r, g, bl); - return fillColor; - } - - public java.awt.Color getFillColor() { - return fillColor; - } - public int getFillType() { - return fillType; - } + public java.awt.Color getFillColor() { + return fillColor; + } + + public int getFillType() { + return fillType; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtSp.java b/src/main/java/io/starter/formats/escher/MsofbtSp.java index 774f007..51762e4 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtSp.java +++ b/src/main/java/io/starter/formats/escher/MsofbtSp.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,34 +27,36 @@ //0xF00A public class MsofbtSp extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5355585244930369889L; - int id; - int grfPersistence; - - public MsofbtSp(int fbt,int inst,int version){ - super(fbt,inst,version); - } - - public byte[] getData(){ - byte[] idBytes,flagBytes; - idBytes = ByteTools.cLongToLEBytes(id); - flagBytes = ByteTools.cLongToLEBytes(grfPersistence); - byte[] retData = new byte[8]; - System.arraycopy(idBytes, 0, retData, 0, 4); - System.arraycopy(flagBytes, 0, retData, 4, 4); - - this.setLength(8); - return retData; - - } - public void setId(int value){ - - id= value; - } - public void setGrfPersistence(int value){ - grfPersistence = value; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -5355585244930369889L; + int id; + int grfPersistence; + + public MsofbtSp(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + public byte[] getData() { + byte[] idBytes, flagBytes; + idBytes = ByteTools.cLongToLEBytes(id); + flagBytes = ByteTools.cLongToLEBytes(grfPersistence); + byte[] retData = new byte[8]; + System.arraycopy(idBytes, 0, retData, 0, 4); + System.arraycopy(flagBytes, 0, retData, 4, 4); + + this.setLength(8); + return retData; + + } + + public void setId(int value) { + + id = value; + } + + public void setGrfPersistence(int value) { + grfPersistence = value; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtSpContainer.java b/src/main/java/io/starter/formats/escher/MsofbtSpContainer.java index 3c5be27..1bd5cfc 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtSpContainer.java +++ b/src/main/java/io/starter/formats/escher/MsofbtSpContainer.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,17 +24,17 @@ //0xf004 public class MsofbtSpContainer extends EscherRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3635215155760095395L; - public MsofbtSpContainer(int fbt,int inst,int version){ - super(fbt,inst,version); - } - - protected byte[] getData(){ - return new byte[0]; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 3635215155760095395L; + + public MsofbtSpContainer(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + protected byte[] getData() { + return new byte[0]; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtSpgr.java b/src/main/java/io/starter/formats/escher/MsofbtSpgr.java index 348548c..b42f861 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtSpgr.java +++ b/src/main/java/io/starter/formats/escher/MsofbtSpgr.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -28,43 +28,44 @@ /* * This record is present only in group shapes (not shapes in groups, shapes that are groups). - * The group shape record defines the coordinate system of the shape, which the anchors of the - * child shape are expressed in. All other information is stored in the shape records that follow. + * The group shape record defines the coordinate system of the shape, which the anchors of the + * child shape are expressed in. All other information is stored in the shape records that follow. */ public class MsofbtSpgr extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5591214948365806058L; - int left=0,top=0,right=0,bottom=0; - - public MsofbtSpgr(int fbt,int inst,int version){ - super(fbt,inst,version); - } - - protected byte[] getData(){ - byte[] leftBytes,topBytes,rightBytes,bottomBytes; - leftBytes = ByteTools.cLongToLEBytes(left); - topBytes = ByteTools.cLongToLEBytes(top); - rightBytes = ByteTools.cLongToLEBytes(right); - bottomBytes = ByteTools.cLongToLEBytes(bottom); - - byte[] retBytes = new byte[16]; - System.arraycopy(leftBytes, 0, retBytes, 0, 4); - System.arraycopy(topBytes, 0, retBytes, 4, 4); - System.arraycopy(rightBytes, 0, retBytes, 8, 4); - System.arraycopy(bottomBytes, 0, retBytes, 12, 4); - - this.setLength(retBytes.length); - - return retBytes; - } - public void setRect(int left,int top,int right,int bottom){ - this.left = left; - this.right = right; - this.bottom = bottom; - this.top = top; - - } - + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5591214948365806058L; + int left = 0, top = 0, right = 0, bottom = 0; + + public MsofbtSpgr(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + protected byte[] getData() { + byte[] leftBytes, topBytes, rightBytes, bottomBytes; + leftBytes = ByteTools.cLongToLEBytes(left); + topBytes = ByteTools.cLongToLEBytes(top); + rightBytes = ByteTools.cLongToLEBytes(right); + bottomBytes = ByteTools.cLongToLEBytes(bottom); + + byte[] retBytes = new byte[16]; + System.arraycopy(leftBytes, 0, retBytes, 0, 4); + System.arraycopy(topBytes, 0, retBytes, 4, 4); + System.arraycopy(rightBytes, 0, retBytes, 8, 4); + System.arraycopy(bottomBytes, 0, retBytes, 12, 4); + + this.setLength(retBytes.length); + + return retBytes; + } + + public void setRect(int left, int top, int right, int bottom) { + this.left = left; + this.right = right; + this.bottom = bottom; + this.top = top; + + } + } diff --git a/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.java b/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.java index 8066dcc..6ef4a7b 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.java +++ b/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -24,17 +24,17 @@ //0xf003 public class MsofbtSpgrContainer extends EscherRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8652169989794277582L; - public MsofbtSpgrContainer(int fbt,int inst,int version){ - super(fbt,inst,version); - } - - protected byte[] getData(){ - return new byte[0]; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 8652169989794277582L; + + public MsofbtSpgrContainer(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + protected byte[] getData() { + return new byte[0]; + } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.java b/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.java index a08285b..d80ee4f 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.java +++ b/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,59 +25,67 @@ import io.starter.toolkit.ByteTools; public class MsofbtSplitMenuColors extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5888748984363726576L; - //These values are from experimental records. - int fillColor=0x800000D,lineColor=0x800000C,shadowColor=0x8000017,_3dColor=0x100000f7; - - public MsofbtSplitMenuColors(int fbt,int inst,int version){ - super(fbt,inst,version); - } - // @Override - protected byte[] getData() { - byte[] fillColorBytes = ByteTools.cLongToLEBytes(fillColor); - byte[] lineColorBytes = ByteTools.cLongToLEBytes(lineColor); - byte[] shadowColorBytes = ByteTools.cLongToLEBytes(shadowColor); - byte[] _3dColorBytes = ByteTools.cLongToLEBytes(_3dColor); - - byte[] totalBytes = new byte[16]; - - System.arraycopy(fillColorBytes, 0, totalBytes, 0, 4); - System.arraycopy(lineColorBytes, 0, totalBytes, 4, 4); - System.arraycopy(shadowColorBytes, 0, totalBytes, 8, 4); - System.arraycopy(_3dColorBytes, 0, totalBytes, 12, 4); - - this.setLength(16); - this.setInst(4); - - return totalBytes; - } - - public int get3dColor() { - return _3dColor; - } - public void set3dColor(int color) { - _3dColor = color; - } - public int getFillColor() { - return fillColor; - } - public void setFillColor(int fillColor) { - this.fillColor = fillColor; - } - public int getLineColor() { - return lineColor; - } - public void setLineColor(int lineColor) { - this.lineColor = lineColor; - } - public int getShadowColor() { - return shadowColor; - } - public void setShadowColor(int shadowColor) { - this.shadowColor = shadowColor; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5888748984363726576L; + //These values are from experimental records. + int fillColor = 0x800000D, lineColor = 0x800000C, shadowColor = 0x8000017, _3dColor = 0x100000f7; + + public MsofbtSplitMenuColors(int fbt, int inst, int version) { + super(fbt, inst, version); + } + + // @Override + protected byte[] getData() { + byte[] fillColorBytes = ByteTools.cLongToLEBytes(fillColor); + byte[] lineColorBytes = ByteTools.cLongToLEBytes(lineColor); + byte[] shadowColorBytes = ByteTools.cLongToLEBytes(shadowColor); + byte[] _3dColorBytes = ByteTools.cLongToLEBytes(_3dColor); + + byte[] totalBytes = new byte[16]; + + System.arraycopy(fillColorBytes, 0, totalBytes, 0, 4); + System.arraycopy(lineColorBytes, 0, totalBytes, 4, 4); + System.arraycopy(shadowColorBytes, 0, totalBytes, 8, 4); + System.arraycopy(_3dColorBytes, 0, totalBytes, 12, 4); + + this.setLength(16); + this.setInst(4); + + return totalBytes; + } + + public int get3dColor() { + return _3dColor; + } + + public void set3dColor(int color) { + _3dColor = color; + } + + public int getFillColor() { + return fillColor; + } + + public void setFillColor(int fillColor) { + this.fillColor = fillColor; + } + + public int getLineColor() { + return lineColor; + } + + public void setLineColor(int lineColor) { + this.lineColor = lineColor; + } + + public int getShadowColor() { + return shadowColor; + } + + public void setShadowColor(int shadowColor) { + this.shadowColor = shadowColor; + } } diff --git a/src/main/java/io/starter/naming/InitialContextFactoryImpl.java b/src/main/java/io/starter/naming/InitialContextFactoryImpl.java index e8cc176..2693d5b 100644 --- a/src/main/java/io/starter/naming/InitialContextFactoryImpl.java +++ b/src/main/java/io/starter/naming/InitialContextFactoryImpl.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,21 +22,21 @@ */ package io.starter.naming; -import java.util.*; -import javax.naming.*; +import javax.naming.Context; +import javax.naming.NamingException; import javax.naming.spi.InitialContextFactory; +import java.util.Hashtable; + /** - * Read the details at: http://java.sun.com/j2se/1.3/docs/guide/jndi/spec/spi/jndispiTOC.fm.html - * - * + * Read the details at: http://java.sun.com/j2se/1.3/docs/guide/jndi/spec/spi/jndispiTOC.fm.html */ public class InitialContextFactoryImpl implements InitialContextFactory { - - - public Context getInitialContext(Hashtable env) - throws NamingException{ - InitialContextImpl contimple = new InitialContextImpl(); - - return contimple; - } + + + public Context getInitialContext(Hashtable env) + throws NamingException { + InitialContextImpl contimple = new InitialContextImpl(); + + return contimple; + } } diff --git a/src/main/java/io/starter/naming/InitialContextImpl.java b/src/main/java/io/starter/naming/InitialContextImpl.java index dbcca29..1368626 100644 --- a/src/main/java/io/starter/naming/InitialContextImpl.java +++ b/src/main/java/io/starter/naming/InitialContextImpl.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,203 +22,195 @@ */ package io.starter.naming; +import javax.naming.*; import java.util.Hashtable; -import javax.naming.Context; -import javax.naming.Name; -import javax.naming.NameParser; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; - /** * A basic JNDI Context which holds a flat lookup of names - * - * - * */ public class InitialContextImpl implements javax.naming.Context { - NameParser nameParser = new NameParserImpl(); - protected Hashtable env; - - // provide persistence between instantiations - public static String CONTEXT_ID = "io.starter.naming.InitialContextImpl_instance"; - public static String LOAD_CONTEXT = "io.starter.naming.load_context"; - - public InitialContextImpl() { - if (System.getProperties().get(CONTEXT_ID) != null) - this.env = (Hashtable) System.getProperties().get(CONTEXT_ID); - else { - String loadme = System.getProperty(LOAD_CONTEXT); - env = new Hashtable(); // 20070518 KSC: Moved so gets init even if no LOAD_CONTEXT - if (loadme != null) { - if (loadme.equals("true")) { - // env = new Hashtable(); KSC: See above - // this breaks properties - System.getProperties().put(CONTEXT_ID, env); - } - } - } - } - - // check return... -jm - public Object addToEnvironment(String propName, Object propVal) throws NamingException { - if (env.contains(propVal)) { - throw new NamingException("Object " + propName + " already exists in NamingContext."); - } else { - env.put(propName, propVal); - return propVal; - } - } - - // we use string to bind -- is that bad? - public void bind(Name name, Object obj) throws NamingException { - String str = name.toString(); - this.bind(str, obj); - } - - public void bind(String name, Object obj) throws NamingException { - try { - this.addToEnvironment(name, obj); - } catch (NamingException e) { - env.remove(obj); - env.put(name, obj); // override - } - } - - private boolean closed = false; - - public void close() throws NamingException { - closed = true; - } - - // ? - public Name composeName(Name name, Name prefix) throws NamingException { - NameImpl retval = new NameImpl(); - retval.addAll(prefix); - retval.addAll(name); - return retval; - } - - public String composeName(String name, String prefix) throws NamingException { - StringBuffer sb = new StringBuffer(); - sb.append(name); - sb.append(prefix); - return sb.toString(); - } - - public Hashtable getEnvironment() throws NamingException { - return env; - } - - public NameParser getNameParser(String name) throws NamingException { - this.nameParser.parse(name); - return this.nameParser; - } - - public NameParser getNameParser(Name name) throws NamingException { - return this.nameParser; - } - - public Object lookup(Name name) throws NamingException { - return env.get(name); - } - - public Object lookup(String name) throws NamingException { - return env.get(name); - } - - public Object lookupLink(Name name) throws NamingException { - return env.get(name); - } - - public Object lookupLink(String name) throws NamingException { - return env.get(name); - } - - public void rebind(Name name, Object obj) throws NamingException { - this.bind(name, obj); - } - - public void rebind(String name, Object obj) throws NamingException { - this.bind(name, obj); - } - - public Object removeFromEnvironment(String propName) throws NamingException { - return env.remove(propName); - } - - public void rename(String oldName, String newName) throws NamingException { - Object ob = env.get(oldName); - env.remove(oldName); - env.put(newName, ob); - } - - public void rename(Name oldName, Name newName) throws NamingException { - Object ob = env.get(oldName); - env.remove(oldName); - env.put(newName, ob); - } - - public void unbind(String name) throws NamingException { - try { - env.remove(env.get(name)); - } catch (Exception e) { - throw new NamingException(e.toString()); - } - } - - public void unbind(Name name) throws NamingException { - try { - env.remove(env.get(name)); - } catch (Exception e) { - throw new NamingException(e.toString()); - } - } - - // TODO: Implement the following mehods -jm 9/27/2004 - - public NamingEnumeration list(String name) throws NamingException { - return null; - } - - public NamingEnumeration list(Name name) throws NamingException { - return null; - } - - public NamingEnumeration listBindings(Name name) throws NamingException { - return null; - } - - public NamingEnumeration listBindings(String name) throws NamingException { - return null; - } - - public Context createSubcontext(Name name) throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - return null; - } - - public Context createSubcontext(String name) throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - return null; - } - - public void destroySubcontext(String name) throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - } - - public void destroySubcontext(Name name) throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - } - - public String getNameInNamespace() throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - return null; - } + NameParser nameParser = new NameParserImpl(); + protected Hashtable env; + + // provide persistence between instantiations + public static String CONTEXT_ID = "io.starter.naming.InitialContextImpl_instance"; + public static String LOAD_CONTEXT = "io.starter.naming.load_context"; + + public InitialContextImpl() { + if (System.getProperties().get(CONTEXT_ID) != null) + this.env = (Hashtable) System.getProperties().get(CONTEXT_ID); + else { + String loadme = System.getProperty(LOAD_CONTEXT); + env = new Hashtable(); // 20070518 KSC: Moved so gets init even if no LOAD_CONTEXT + if (loadme != null) { + if (loadme.equals("true")) { + // env = new Hashtable(); KSC: See above + // this breaks properties + System.getProperties().put(CONTEXT_ID, env); + } + } + } + } + + // check return... -jm + public Object addToEnvironment(String propName, Object propVal) throws NamingException { + if (env.contains(propVal)) { + throw new NamingException("Object " + propName + " already exists in NamingContext."); + } else { + env.put(propName, propVal); + return propVal; + } + } + + // we use string to bind -- is that bad? + public void bind(Name name, Object obj) throws NamingException { + String str = name.toString(); + this.bind(str, obj); + } + + public void bind(String name, Object obj) throws NamingException { + try { + this.addToEnvironment(name, obj); + } catch (NamingException e) { + env.remove(obj); + env.put(name, obj); // override + } + } + + private boolean closed = false; + + public void close() throws NamingException { + closed = true; + } + + // ? + public Name composeName(Name name, Name prefix) throws NamingException { + NameImpl retval = new NameImpl(); + retval.addAll(prefix); + retval.addAll(name); + return retval; + } + + public String composeName(String name, String prefix) throws NamingException { + StringBuffer sb = new StringBuffer(); + sb.append(name); + sb.append(prefix); + return sb.toString(); + } + + public Hashtable getEnvironment() throws NamingException { + return env; + } + + public NameParser getNameParser(String name) throws NamingException { + this.nameParser.parse(name); + return this.nameParser; + } + + public NameParser getNameParser(Name name) throws NamingException { + return this.nameParser; + } + + public Object lookup(Name name) throws NamingException { + return env.get(name); + } + + public Object lookup(String name) throws NamingException { + return env.get(name); + } + + public Object lookupLink(Name name) throws NamingException { + return env.get(name); + } + + public Object lookupLink(String name) throws NamingException { + return env.get(name); + } + + public void rebind(Name name, Object obj) throws NamingException { + this.bind(name, obj); + } + + public void rebind(String name, Object obj) throws NamingException { + this.bind(name, obj); + } + + public Object removeFromEnvironment(String propName) throws NamingException { + return env.remove(propName); + } + + public void rename(String oldName, String newName) throws NamingException { + Object ob = env.get(oldName); + env.remove(oldName); + env.put(newName, ob); + } + + public void rename(Name oldName, Name newName) throws NamingException { + Object ob = env.get(oldName); + env.remove(oldName); + env.put(newName, ob); + } + + public void unbind(String name) throws NamingException { + try { + env.remove(env.get(name)); + } catch (Exception e) { + throw new NamingException(e.toString()); + } + } + + public void unbind(Name name) throws NamingException { + try { + env.remove(env.get(name)); + } catch (Exception e) { + throw new NamingException(e.toString()); + } + } + + // TODO: Implement the following mehods -jm 9/27/2004 + + public NamingEnumeration list(String name) throws NamingException { + return null; + } + + public NamingEnumeration list(Name name) throws NamingException { + return null; + } + + public NamingEnumeration listBindings(Name name) throws NamingException { + return null; + } + + public NamingEnumeration listBindings(String name) throws NamingException { + return null; + } + + public Context createSubcontext(Name name) throws NamingException { + // This method is derived from interface javax.naming.Context + // to do: code goes here + return null; + } + + public Context createSubcontext(String name) throws NamingException { + // This method is derived from interface javax.naming.Context + // to do: code goes here + return null; + } + + public void destroySubcontext(String name) throws NamingException { + // This method is derived from interface javax.naming.Context + // to do: code goes here + } + + public void destroySubcontext(Name name) throws NamingException { + // This method is derived from interface javax.naming.Context + // to do: code goes here + } + + public String getNameInNamespace() throws NamingException { + // This method is derived from interface javax.naming.Context + // to do: code goes here + return null; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/naming/NameImpl.java b/src/main/java/io/starter/naming/NameImpl.java index 6abc0b5..648f7f0 100644 --- a/src/main/java/io/starter/naming/NameImpl.java +++ b/src/main/java/io/starter/naming/NameImpl.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,12 +22,11 @@ */ package io.starter.naming; -import java.util.Enumeration; +import io.starter.toolkit.CompatibleVector; import javax.naming.InvalidNameException; import javax.naming.Name; - -import io.starter.toolkit.CompatibleVector; +import java.util.Enumeration; /* Name add(int posn, String comp) @@ -59,174 +58,174 @@ boolean startsWith(Name n) */ public class NameImpl implements Name { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4387233472850688497L; - CompatibleVector vals = new CompatibleVector(); /* - * (non-Javadoc) - * - * - * * @see javax.naming.Name#clone() - */ - - @Override - public Object clone() { - NameImpl nimple = new NameImpl(); - CompatibleVector newvals = new CompatibleVector(); - newvals.addAll(vals); - nimple.vals = newvals; - return nimple; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#remove(int) - */ - public Object remove(int arg0) throws InvalidNameException { - return vals.remove(arg0); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#get(int) - */ - public String get(int arg0) { - return vals.get(arg0).toString(); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#getAll() - */ - public Enumeration getAll() { - return vals.elements(); - } - - /* - * Creates a name whose components consist of a prefix of the components of this - * name. - * - * @see javax.naming.Name#getPrefix(int) - */ - public Name getPrefix(int arg0) { - return null; - } - - /* - * Creates a name whose components consist of a suffix of the components in this - * name. - * - * @see javax.naming.Name#getSuffix(int) - */ - public Name getSuffix(int arg0) { - return null; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#add(java.lang.String) - */ - public Name add(String arg0) throws InvalidNameException { - return null; - } - - /* - * Adds the components of a name -- in order -- at a specified position within - * this name. - * - * @see javax.naming.Name#addAll(int, javax.naming.Name) - */ - public Name addAll(int arg0, Name arg1) throws InvalidNameException { - this.vals.addAll(arg0, ((NameImpl) arg1).getVals()); - return this; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#addAll(javax.naming.Name) - */ - public Name addAll(Name arg0) throws InvalidNameException { - this.vals.addAll(((NameImpl) arg0).getVals()); - return this; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#size() - */ - public int size() { - return vals.size(); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#isEmpty() - */ - public boolean isEmpty() { - return vals.size() > 0; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#compareTo(java.lang.Object) - */ - public int compareTo(Object arg0) { - return this.compareTo(arg0); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#endsWith(javax.naming.Name) - */ - public boolean endsWith(Name arg0) { - Object ob1 = arg0.get(arg0.size() - 1); - Object ob2 = this.get(this.size() - 1); - return ob1.equals(ob2); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#startsWith(javax.naming.Name) - */ - public boolean startsWith(Name arg0) { - Object ob1 = arg0.get(0); - Object ob2 = this.get(0); - return ob1.equals(ob2); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#add(int, java.lang.String) - */ - public Name add(int arg0, String arg1) throws InvalidNameException { - vals.set(arg0, arg1); - return this; - } - - /** - * @return - */ - CompatibleVector getVals() { - return vals; - } - - /** - * @param vector - */ - void setVals(CompatibleVector vector) { - vals = vector; - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 4387233472850688497L; + CompatibleVector vals = new CompatibleVector(); /* + * (non-Javadoc) + * + * + * * @see javax.naming.Name#clone() + */ + + @Override + public Object clone() { + NameImpl nimple = new NameImpl(); + CompatibleVector newvals = new CompatibleVector(); + newvals.addAll(vals); + nimple.vals = newvals; + return nimple; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#remove(int) + */ + public Object remove(int arg0) throws InvalidNameException { + return vals.remove(arg0); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#get(int) + */ + public String get(int arg0) { + return vals.get(arg0).toString(); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#getAll() + */ + public Enumeration getAll() { + return vals.elements(); + } + + /* + * Creates a name whose components consist of a prefix of the components of this + * name. + * + * @see javax.naming.Name#getPrefix(int) + */ + public Name getPrefix(int arg0) { + return null; + } + + /* + * Creates a name whose components consist of a suffix of the components in this + * name. + * + * @see javax.naming.Name#getSuffix(int) + */ + public Name getSuffix(int arg0) { + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#add(java.lang.String) + */ + public Name add(String arg0) throws InvalidNameException { + return null; + } + + /* + * Adds the components of a name -- in order -- at a specified position within + * this name. + * + * @see javax.naming.Name#addAll(int, javax.naming.Name) + */ + public Name addAll(int arg0, Name arg1) throws InvalidNameException { + this.vals.addAll(arg0, ((NameImpl) arg1).getVals()); + return this; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#addAll(javax.naming.Name) + */ + public Name addAll(Name arg0) throws InvalidNameException { + this.vals.addAll(((NameImpl) arg0).getVals()); + return this; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#size() + */ + public int size() { + return vals.size(); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#isEmpty() + */ + public boolean isEmpty() { + return vals.size() > 0; + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#compareTo(java.lang.Object) + */ + public int compareTo(Object arg0) { + return this.compareTo(arg0); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#endsWith(javax.naming.Name) + */ + public boolean endsWith(Name arg0) { + Object ob1 = arg0.get(arg0.size() - 1); + Object ob2 = this.get(this.size() - 1); + return ob1.equals(ob2); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#startsWith(javax.naming.Name) + */ + public boolean startsWith(Name arg0) { + Object ob1 = arg0.get(0); + Object ob2 = this.get(0); + return ob1.equals(ob2); + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#add(int, java.lang.String) + */ + public Name add(int arg0, String arg1) throws InvalidNameException { + vals.set(arg0, arg1); + return this; + } + + /** + * @return + */ + CompatibleVector getVals() { + return vals; + } + + /** + * @param vector + */ + void setVals(CompatibleVector vector) { + vals = vector; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/naming/NameParserImpl.java b/src/main/java/io/starter/naming/NameParserImpl.java index 017f7bc..d7bf55a 100644 --- a/src/main/java/io/starter/naming/NameParserImpl.java +++ b/src/main/java/io/starter/naming/NameParserImpl.java @@ -2,26 +2,29 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.naming; -import javax.naming.*; + +import javax.naming.Name; +import javax.naming.NameParser; +import javax.naming.NamingException; /* This class is used for parsing names from a hierarchical @@ -35,24 +38,24 @@ The equals() method, when used to compare two NameParsers, returns @see:CompoundName, Name */ -class NameParserImpl implements NameParser{ +class NameParserImpl implements NameParser { + + /* Parses a name into its components. + Parameters: + name - The non-null string name to parse. + Returns: + A non-null parsed form of the name using the naming convention of this parser. + Throws: + InvalidNameException - If name does not conform to syntax defined for the namespace. + NamingException - If a naming exception was encountered. + * @see javax.naming.NameParser#parse(java.lang.String) + */ + public Name parse(String arg0) throws NamingException { + Name nm = new NameImpl(); + + nm.add(arg0); // just plop it in for now... + + return nm; + } - /* Parses a name into its components. - Parameters: - name - The non-null string name to parse. - Returns: - A non-null parsed form of the name using the naming convention of this parser. - Throws: - InvalidNameException - If name does not conform to syntax defined for the namespace. - NamingException - If a naming exception was encountered. - * @see javax.naming.NameParser#parse(java.lang.String) - */ - public Name parse(String arg0) throws NamingException { - Name nm = new NameImpl(); - - nm.add(arg0); // just plop it in for now... - - return nm; - } - } \ No newline at end of file diff --git a/src/main/java/io/starter/naming/NamingEnumerationImpl.java b/src/main/java/io/starter/naming/NamingEnumerationImpl.java index 57aec8c..2b96d28 100644 --- a/src/main/java/io/starter/naming/NamingEnumerationImpl.java +++ b/src/main/java/io/starter/naming/NamingEnumerationImpl.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,50 +23,51 @@ package io.starter.naming; -import javax.naming.*; -import java.util.*; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import java.util.Enumeration; -public class NamingEnumerationImpl implements NamingEnumeration{ +public class NamingEnumerationImpl implements NamingEnumeration { - private Enumeration e = null; - - void setEnumeration(Enumeration ex){ - e = ex; - } + private Enumeration e = null; - /* (non-Javadoc) - * @see javax.naming.NamingEnumeration#close() - */ - public void close() throws NamingException { - e = null; - } + void setEnumeration(Enumeration ex) { + e = ex; + } - /* (non-Javadoc) - * @see javax.naming.NamingEnumeration#hasMore() - */ - public boolean hasMore() throws NamingException { - return e.hasMoreElements(); - } + /* (non-Javadoc) + * @see javax.naming.NamingEnumeration#close() + */ + public void close() throws NamingException { + e = null; + } - /* (non-Javadoc) - * @see javax.naming.NamingEnumeration#next() - */ - public Object next() throws NamingException { - return e.nextElement(); - } + /* (non-Javadoc) + * @see javax.naming.NamingEnumeration#hasMore() + */ + public boolean hasMore() throws NamingException { + return e.hasMoreElements(); + } - /* (non-Javadoc) - * @see java.util.Enumeration#hasMoreElements() - */ - public boolean hasMoreElements() { - return e.hasMoreElements(); - } + /* (non-Javadoc) + * @see javax.naming.NamingEnumeration#next() + */ + public Object next() throws NamingException { + return e.nextElement(); + } + + /* (non-Javadoc) + * @see java.util.Enumeration#hasMoreElements() + */ + public boolean hasMoreElements() { + return e.hasMoreElements(); + } + + /* (non-Javadoc) + * @see java.util.Enumeration#nextElement() + */ + public Object nextElement() { + return e.nextElement(); + } - /* (non-Javadoc) - * @see java.util.Enumeration#nextElement() - */ - public Object nextElement() { - return e.nextElement(); - } - } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/Base64.java b/src/main/java/io/starter/toolkit/Base64.java index 3531ede..da2a330 100644 --- a/src/main/java/io/starter/toolkit/Base64.java +++ b/src/main/java/io/starter/toolkit/Base64.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -29,32 +29,32 @@ * Change Log: *

                *
                  - *
                • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added - * some convenience methods for reading and writing to and from files.
                • - *
                • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems - * with other encodings (like EBCDIC).
                • - *
                • v2.0.1 - Fixed an error when decoding a single byte, that is, when the - * encoded data was a single byte.
                • - *
                • v2.0 - I got rid of methods that used booleans to set options. - * Now everything is more consolidated and cleaner. The code now detects - * when data that's being decoded is gzip-compressed and will decompress it - * automatically. Generally things are cleaner. You'll probably have to - * change some method calls that you were making to support the new - * options format (ints that you "OR" together).
                • - *
                • v1.5.1 - Fixed bug when decompressing and decoding to a - * byte[] using decode( String s, boolean gzipCompressed ). - * Added the ability to "suspend" encoding in the Output Stream so - * you can turn on and off the encoding if you need to embed base64 - * data in an otherwise "normal" stream (like an XML file).
                • - *
                • v1.5 - Output stream pases on flush() command but doesn't do anything itself. - * This helps when using GZIP streams. - * Added the ability to GZip-compress objects before encoding them.
                • - *
                • v1.4 - Added helper methods to read/write files.
                • - *
                • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
                • - *
                • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream - * where last buffer being read, if not completely full, was not returned.
                • - *
                • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
                • - *
                • v1.3.3 - Fixed I/O streams which were totally messed up.
                • + *
                • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files.
                • + *
                • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC).
                • + *
                • v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.
                • + *
                • v2.0 - I got rid of methods that used booleans to set options. + * Now everything is more consolidated and cleaner. The code now detects + * when data that's being decoded is gzip-compressed and will decompress it + * automatically. Generally things are cleaner. You'll probably have to + * change some method calls that you were making to support the new + * options format (ints that you "OR" together).
                • + *
                • v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).
                • + *
                • v1.5 - Output stream pases on flush() command but doesn't do anything itself. + * This helps when using GZIP streams. + * Added the ability to GZip-compress objects before encoding them.
                • + *
                • v1.4 - Added helper methods to read/write files.
                • + *
                • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
                • + *
                • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream + * where last buffer being read, if not completely full, was not returned.
                • + *
                • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
                • + *
                • v1.3.3 - Fixed I/O streams which were totally messed up.
                • *
                * *

                @@ -69,135 +69,154 @@ * @author rob@iharder.net * @version 2.1 */ -public class Base64 -{ - -/* ******** P U B L I C F I E L D S ******** */ - - - /** No options specified. Value is zero. */ +public class Base64 { + + /* ******** P U B L I C F I E L D S ******** */ + + + /** + * No options specified. Value is zero. + */ public final static int NO_OPTIONS = 0; - - /** Specify encoding. */ + + /** + * Specify encoding. + */ public final static int ENCODE = 1; - - - /** Specify decoding. */ + + + /** + * Specify decoding. + */ public final static int DECODE = 0; - - - /** Specify that data should be gzip-compressed. */ + + + /** + * Specify that data should be gzip-compressed. + */ public final static int GZIP = 2; - - - /** Don't break lines when encoding (violates strict Base64 specification) */ + + + /** + * Don't break lines when encoding (violates strict Base64 specification) + */ public final static int DONT_BREAK_LINES = 8; - - -/* ******** P R I V A T E F I E L D S ******** */ - - - /** Maximum line length (76) of Base64 output. */ + + + /* ******** P R I V A T E F I E L D S ******** */ + + + /** + * Maximum line length (76) of Base64 output. + */ private final static int MAX_LINE_LENGTH = 76; - - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte)'='; - - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte)'\n'; - - - /** Preferred encoding. */ + + + /** + * The equals sign (=) as a byte. + */ + private final static byte EQUALS_SIGN = (byte) '='; + + + /** + * The new line character (\n) as a byte. + */ + private final static byte NEW_LINE = (byte) '\n'; + + + /** + * Preferred encoding. + */ private final static String PREFERRED_ENCODING = "UTF-8"; - - - /** The 64 valid Base64 values. */ + + + /** + * The 64 valid Base64 values. + */ private final static byte[] ALPHABET; private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */ - { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', - (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' - }; - + { + (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', + (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', + (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', + (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', + (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', + (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' + }; + /** Determine which ALPHABET to use. */ - static - { + static { byte[] __bytes; - try - { - __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes( PREFERRED_ENCODING ); + try { + __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(PREFERRED_ENCODING); } // end try - catch (java.io.UnsupportedEncodingException use) - { + catch (java.io.UnsupportedEncodingException use) { __bytes = _NATIVE_ALPHABET; // Fall back to native encoding } // end catch ALPHABET = __bytes; } // end static - - - /** + + + /** * Translates a Base64 value to either its 6-bit reconstruction value * or a negative number indicating some other meaning. **/ private final static byte[] DECODABET = - { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9,-9,-9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9 // Decimal 123 - 126 - /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - + { + -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + // I think I end up not using the BAD_ENCODING indicator. //private final static byte BAD_ENCODING = -9; // Indicates error in encoding private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - /** Defeats instantiation. */ - private Base64(){} - - - -/* ******** E N C O D I N G M E T H O D S ******** */ - - + + /** + * Defeats instantiation. + */ + private Base64() { + } + + + + /* ******** E N C O D I N G M E T H O D S ******** */ + + /** * Encodes up to the first three bytes of array threeBytes * and returns a four-byte array in Base64 notation. @@ -207,23 +226,22 @@ private Base64(){} * numSigBytes. * Code can reuse a byte array by passing a four-byte array as b4. * - * @param b4 A reusable byte array to reduce array instantiation - * @param threeBytes the array to convert + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert * @param numSigBytes the number of significant bytes in your array * @return four byte array in Base64 notation. */ - private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes ) - { - encode3to4( threeBytes, 0, numSigBytes, b4, 0 ); + private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes) { + encode3to4(threeBytes, 0, numSigBytes, b4, 0); return b4; } // end encode3to4 - + /** * Encodes up to three bytes of the array source * and writes the resulting four Base64 bytes to destination. * The source and destination arrays can be manipulated - * anywhere along their length by specifying + * anywhere along their length by specifying * srcOffset and destOffset. * This method does not check to make sure your arrays * are large enough to accomodate srcOffset + 3 for @@ -232,62 +250,59 @@ private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes * The actual number of significant bytes in your array is * given by numSigBytes. * - * @param source the array to convert - * @param srcOffset the index where conversion begins + * @param source the array to convert + * @param srcOffset the index where conversion begins * @param numSigBytes the number of significant bytes in your array * @param destination the array to hold the conversion - * @param destOffset the index where output will be put + * @param destOffset the index where output will be put * @return the destination array */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset ) - { + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset) { // 1 2 3 // 01234567890123456789012345678901 Bit position // --------000000001111111122222222 Array position from threeBytes // --------| || || || | Six bit groups to index ALPHABET // >>18 >>12 >> 6 >> 0 Right shift necessary // 0x3f 0x3f 0x3f Additional AND - + // Create buffer with zero-padding if there are only one or two // significant bytes passed in the array. // We have to shift left 24 in order to flush out the 1's that appear // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) - | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) - | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); + int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - switch( numSigBytes ) - { + switch (numSigBytes) { case 3: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; return destination; - + case 2: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = EQUALS_SIGN; + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; return destination; - + case 1: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = EQUALS_SIGN; - destination[ destOffset + 3 ] = EQUALS_SIGN; + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; return destination; - + default: return destination; } // end switch } // end encode3to4 - - - + + /** * Serializes an object and returns the Base64-encoded * version of that serialized object. If the object @@ -298,11 +313,9 @@ private static byte[] encode3to4( * @param serializableObject The object to encode * @return The Base64-encoded object */ - public static String encodeObject( java.io.Serializable serializableObject ) - { - return encodeObject( serializableObject, NO_OPTIONS ); + public static String encodeObject(java.io.Serializable serializableObject) { + return encodeObject(serializableObject, NO_OPTIONS); } // end encodeObject - /** @@ -322,66 +335,70 @@ public static String encodeObject( java.io.Serializable serializableObject ) * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) * * @param serializableObject The object to encode - * @param options Specified options + * @param options Specified options * @return The Base64-encoded object * @see Base64#GZIP * @see Base64#DONT_BREAK_LINES */ - public static String encodeObject( java.io.Serializable serializableObject, int options ) - { + public static String encodeObject(java.io.Serializable serializableObject, int options) { // Streams - java.io.ByteArrayOutputStream baos = null; - java.io.OutputStream b64os = null; - java.io.ObjectOutputStream oos = null; - java.util.zip.GZIPOutputStream gzos = null; - + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.io.ObjectOutputStream oos = null; + java.util.zip.GZIPOutputStream gzos = null; + // Isolate options - int gzip = (options & GZIP); + int gzip = (options & GZIP); int dontBreakLines = (options & DONT_BREAK_LINES); - - try - { + + try { // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines ); - + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines); + // GZip? - if( gzip == GZIP ) - { - gzos = new java.util.zip.GZIPOutputStream( b64os ); - oos = new java.io.ObjectOutputStream( gzos ); + if (gzip == GZIP) { + gzos = new java.util.zip.GZIPOutputStream(b64os); + oos = new java.io.ObjectOutputStream(gzos); } // end if: gzip else - oos = new java.io.ObjectOutputStream( b64os ); - - oos.writeObject( serializableObject ); + oos = new java.io.ObjectOutputStream(b64os); + + oos.writeObject(serializableObject); } // end try - catch( java.io.IOException e ) - { + catch (java.io.IOException e) { e.printStackTrace(); return null; } // end catch - finally - { - try{ oos.close(); } catch( Exception e ){} - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} + finally { + try { + oos.close(); + } catch (Exception e) { + } + try { + gzos.close(); + } catch (Exception e) { + } + try { + b64os.close(); + } catch (Exception e) { + } + try { + baos.close(); + } catch (Exception e) { + } } // end finally - + // Return value according to relevant encoding. - try - { - return new String( baos.toByteArray(), PREFERRED_ENCODING ); + try { + return new String(baos.toByteArray(), PREFERRED_ENCODING); } // end try - catch (java.io.UnsupportedEncodingException uue) - { - return new String( baos.toByteArray() ); + catch (java.io.UnsupportedEncodingException uue) { + return new String(baos.toByteArray()); } // end catch - + } // end encode - - + /** * Encodes a byte array into Base64 notation. @@ -389,11 +406,9 @@ public static String encodeObject( java.io.Serializable serializableObject, int * * @param source The data to convert */ - public static String encodeBytes( byte[] source ) - { - return encodeBytes( source, 0, source.length, NO_OPTIONS ); + public static String encodeBytes(byte[] source) { + return encodeBytes(source, 0, source.length, NO_OPTIONS); } // end encodeBytes - /** @@ -409,32 +424,28 @@ public static String encodeBytes( byte[] source ) *

                * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) * - * - * @param source The data to convert + * @param source The data to convert * @param options Specified options * @see Base64#GZIP * @see Base64#DONT_BREAK_LINES */ - public static String encodeBytes( byte[] source, int options ) - { - return encodeBytes( source, 0, source.length, options ); + public static String encodeBytes(byte[] source, int options) { + return encodeBytes(source, 0, source.length, options); } // end encodeBytes - - + + /** * Encodes a byte array into Base64 notation. * Does not GZip-compress data. * * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert */ - public static String encodeBytes( byte[] source, int off, int len ) - { - return encodeBytes( source, off, len, NO_OPTIONS ); + public static String encodeBytes(byte[] source, int off, int len) { + return encodeBytes(source, off, len, NO_OPTIONS); } // end encodeBytes - - + /** * Encodes a byte array into Base64 notation. @@ -449,201 +460,189 @@ public static String encodeBytes( byte[] source, int off, int len ) *

                * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert * @param options Specified options * @see Base64#GZIP * @see Base64#DONT_BREAK_LINES */ - public static String encodeBytes( byte[] source, int off, int len, int options ) - { + public static String encodeBytes(byte[] source, int off, int len, int options) { // Isolate options - int dontBreakLines = ( options & DONT_BREAK_LINES ); - int gzip = ( options & GZIP ); - + int dontBreakLines = (options & DONT_BREAK_LINES); + int gzip = (options & GZIP); + // Compress? - if( gzip == GZIP ) - { - java.io.ByteArrayOutputStream baos = null; - java.util.zip.GZIPOutputStream gzos = null; - Base64.OutputStream b64os = null; - - - try - { + if (gzip == GZIP) { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + + try { // GZip -> Base64 -> ByteArray baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines ); - gzos = new java.util.zip.GZIPOutputStream( b64os ); - - gzos.write( source, off, len ); + b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines); + gzos = new java.util.zip.GZIPOutputStream(b64os); + + gzos.write(source, off, len); gzos.close(); } // end try - catch( java.io.IOException e ) - { + catch (java.io.IOException e) { e.printStackTrace(); return null; } // end catch - finally - { - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} + finally { + try { + gzos.close(); + } catch (Exception e) { + } + try { + b64os.close(); + } catch (Exception e) { + } + try { + baos.close(); + } catch (Exception e) { + } } // end finally // Return value according to relevant encoding. - try - { - return new String( baos.toByteArray(), PREFERRED_ENCODING ); + try { + return new String(baos.toByteArray(), PREFERRED_ENCODING); } // end try - catch (java.io.UnsupportedEncodingException uue) - { - return new String( baos.toByteArray() ); + catch (java.io.UnsupportedEncodingException uue) { + return new String(baos.toByteArray()); } // end catch } // end if: compress - + // Else, don't compress. Better not to use streams at all then. - else - { + else { // Convert option to boolean in way that code likes it. boolean breakLines = dontBreakLines == 0; - - int len43 = len * 4 / 3; - byte[] outBuff = new byte[ ( len43 ) // Main 4:3 - + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + + int len43 = len * 4 / 3; + byte[] outBuff = new byte[(len43) // Main 4:3 + + ((len % 3) > 0 ? 4 : 0) // Account for padding + + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines int d = 0; int e = 0; int len2 = len - 2; int lineLength = 0; - for( ; d < len2; d+=3, e+=4 ) - { - encode3to4( source, d+off, 3, outBuff, e ); + for (; d < len2; d += 3, e += 4) { + encode3to4(source, d + off, 3, outBuff, e); lineLength += 4; - if( breakLines && lineLength == MAX_LINE_LENGTH ) - { - outBuff[e+4] = NEW_LINE; + if (breakLines && lineLength == MAX_LINE_LENGTH) { + outBuff[e + 4] = NEW_LINE; e++; lineLength = 0; } // end if: end of line } // en dfor: each piece of array - if( d < len ) - { - encode3to4( source, d+off, len - d, outBuff, e ); + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e); e += 4; } // end if: some padding needed - + // Return value according to relevant encoding. - try - { - return new String( outBuff, 0, e, PREFERRED_ENCODING ); + try { + return new String(outBuff, 0, e, PREFERRED_ENCODING); } // end try - catch (java.io.UnsupportedEncodingException uue) - { - return new String( outBuff, 0, e ); + catch (java.io.UnsupportedEncodingException uue) { + return new String(outBuff, 0, e); } // end catch - + } // end else: don't compress - + } // end encodeBytes - - - - - -/* ******** D E C O D I N G M E T H O D S ******** */ - - + + + + + + /* ******** D E C O D I N G M E T H O D S ******** */ + + /** * Decodes four bytes from array source * and writes the resulting bytes (up to three of them) * to destination. * The source and destination arrays can be manipulated - * anywhere along their length by specifying + * anywhere along their length by specifying * srcOffset and destOffset. * This method does not check to make sure your arrays * are large enough to accomodate srcOffset + 4 for * the source array or destOffset + 3 for * the destination array. - * This method returns the actual number of bytes that + * This method returns the actual number of bytes that * were converted from the Base64 encoding. - * * - * @param source the array to convert - * @param srcOffset the index where conversion begins + * @param source the array to convert + * @param srcOffset the index where conversion begins * @param destination the array to hold the conversion - * @param destOffset the index where output will be put + * @param destOffset the index where output will be put * @return the number of decoded bytes converted */ - private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset ) - { + private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) { // Example: Dk== - if( source[ srcOffset + 2] == EQUALS_SIGN ) - { + if (source[srcOffset + 2] == EQUALS_SIGN) { // Two ways to do the same thing. Don't know which way I like best. //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); + + destination[destOffset] = (byte) (outBuff >>> 16); return 1; } - + // Example: DkL= - else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) - { + else if (source[srcOffset + 3] == EQUALS_SIGN) { // Two ways to do the same thing. Don't know which way I like best. //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); + + destination[destOffset] = (byte) (outBuff >>> 16); + destination[destOffset + 1] = (byte) (outBuff >>> 8); return 2; } - + // Example: DkLE - else - { - try{ - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) - | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); - - - destination[ destOffset ] = (byte)( outBuff >> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); - destination[ destOffset + 2 ] = (byte)( outBuff ); - - return 3; - }catch( Exception e){ - io.starter.toolkit.Logger.log(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); - io.starter.toolkit.Logger.log(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); - io.starter.toolkit.Logger.log(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); - io.starter.toolkit.Logger.log(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); + else { + try { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) + | ((DECODABET[source[srcOffset + 3]] & 0xFF)); + + + destination[destOffset] = (byte) (outBuff >> 16); + destination[destOffset + 1] = (byte) (outBuff >> 8); + destination[destOffset + 2] = (byte) (outBuff); + + return 3; + } catch (Exception e) { + io.starter.toolkit.Logger.log("" + source[srcOffset] + ": " + (DECODABET[source[srcOffset]])); + io.starter.toolkit.Logger.log("" + source[srcOffset + 1] + ": " + (DECODABET[source[srcOffset + 1]])); + io.starter.toolkit.Logger.log("" + source[srcOffset + 2] + ": " + (DECODABET[source[srcOffset + 2]])); + io.starter.toolkit.Logger.log("" + source[srcOffset + 3] + ": " + (DECODABET[source[srcOffset + 3]])); return -1; } //e nd catch } } // end decodeToBytes - - - - + + /** * Very low-level access to decoding ASCII characters in * the form of a byte array. Does not support automatically @@ -654,55 +653,48 @@ else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) * @param len The length of characters to decode * @return decoded data */ - public static byte[] decode( byte[] source, int off, int len ) - { - int len34 = len * 3 / 4; - byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output - int outBuffPosn = 0; - - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i = 0; - byte sbiCrop = 0; - byte sbiDecode = 0; - for( i = off; i < off+len; i++ ) - { - sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits - sbiDecode = DECODABET[ sbiCrop ]; - - if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better + public static byte[] decode(byte[] source, int off, int len) { + int len34 = len * 3 / 4; + byte[] outBuff = new byte[len34]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + int i = 0; + byte sbiCrop = 0; + byte sbiDecode = 0; + for (i = off; i < off + len; i++) { + sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits + sbiDecode = DECODABET[sbiCrop]; + + if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better { - if( sbiDecode >= EQUALS_SIGN_ENC ) - { - b4[ b4Posn++ ] = sbiCrop; - if( b4Posn > 3 ) - { - outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn ); + if (sbiDecode >= EQUALS_SIGN_ENC) { + b4[b4Posn++] = sbiCrop; + if (b4Posn > 3) { + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn); b4Posn = 0; - + // If that was the equals sign, break out of 'for' loop - if( sbiCrop == EQUALS_SIGN ) + if (sbiCrop == EQUALS_SIGN) break; } // end if: quartet built - + } // end if: equals sign or better - + } // end if: white space, equals sign or better - else - { - System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" ); + else { + System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)"); return null; } // end else: } // each input character - - byte[] out = new byte[ outBuffPosn ]; - System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); return out; } // end decode - - - - + + /** * Decodes data from Base64 notation, automatically * detecting gzip-compressed data and decompressing it. @@ -710,72 +702,70 @@ public static byte[] decode( byte[] source, int off, int len ) * @param s the string to decode * @return the decoded data */ - public static byte[] decode( String s ) - { + public static byte[] decode(String s) { byte[] bytes; - try - { - bytes = s.getBytes( PREFERRED_ENCODING ); + try { + bytes = s.getBytes(PREFERRED_ENCODING); } // end try - catch( java.io.UnsupportedEncodingException uee ) - { + catch (java.io.UnsupportedEncodingException uee) { bytes = s.getBytes(); } // end catch - // - + // + // Decode - bytes = decode( bytes, 0, bytes.length ); - - + bytes = decode(bytes, 0, bytes.length); + + // Check to see if it's gzip-compressed // GZIP Magic Two-Byte Number: 0x8b1f (35615) - if( bytes != null && bytes.length >= 4 ) - { - - int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) - { - java.io.ByteArrayInputStream bais = null; + if (bytes != null && bytes.length >= 4) { + + int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { + java.io.ByteArrayInputStream bais = null; java.util.zip.GZIPInputStream gzis = null; java.io.ByteArrayOutputStream baos = null; byte[] buffer = new byte[2048]; - int length = 0; + int length = 0; - try - { + try { baos = new java.io.ByteArrayOutputStream(); - bais = new java.io.ByteArrayInputStream( bytes ); - gzis = new java.util.zip.GZIPInputStream( bais ); + bais = new java.io.ByteArrayInputStream(bytes); + gzis = new java.util.zip.GZIPInputStream(bais); - while( ( length = gzis.read( buffer ) ) >= 0 ) - { - baos.write(buffer,0,length); + while ((length = gzis.read(buffer)) >= 0) { + baos.write(buffer, 0, length); } // end while: reading input // No error? Get new bytes. bytes = baos.toByteArray(); } // end try - catch( java.io.IOException e ) - { + catch (java.io.IOException e) { // Just return originally-decoded bytes } // end catch - finally - { - try{ baos.close(); } catch( Exception e ){} - try{ gzis.close(); } catch( Exception e ){} - try{ bais.close(); } catch( Exception e ){} + finally { + try { + baos.close(); + } catch (Exception e) { + } + try { + gzis.close(); + } catch (Exception e) { + } + try { + bais.close(); + } catch (Exception e) { + } } // end finally } // end if: gzipped } // end if: bytes.length >= 2 - + return bytes; } // end decode - - /** * Attempts to decode Base64 data and deserialize a Java * Object within. Returns null if there was an error. @@ -783,218 +773,205 @@ public static byte[] decode( String s ) * @param encodedObject The Base64 data to decode * @return The decoded and deserialized object */ - public static Object decodeToObject( String encodedObject ) - { + public static Object decodeToObject(String encodedObject) { // Decode and gunzip if necessary - byte[] objBytes = decode( encodedObject ); - - java.io.ByteArrayInputStream bais = null; - java.io.ObjectInputStream ois = null; + byte[] objBytes = decode(encodedObject); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; Object obj = null; - - try - { - bais = new java.io.ByteArrayInputStream( objBytes ); - ois = new java.io.ObjectInputStream( bais ); - + + try { + bais = new java.io.ByteArrayInputStream(objBytes); + ois = new java.io.ObjectInputStream(bais); + obj = ois.readObject(); } // end try - catch( java.io.IOException e ) - { + catch (java.io.IOException e) { e.printStackTrace(); obj = null; } // end catch - catch( java.lang.ClassNotFoundException e ) - { + catch (java.lang.ClassNotFoundException e) { e.printStackTrace(); obj = null; } // end catch - finally - { - try{ bais.close(); } catch( Exception e ){} - try{ ois.close(); } catch( Exception e ){} + finally { + try { + bais.close(); + } catch (Exception e) { + } + try { + ois.close(); + } catch (Exception e) { + } } // end finally - + return obj; } // end decodeObject - - - + + /** * Convenience method for encoding data to a file. * * @param dataToEncode byte array of data to encode in base64 form - * @param filename Filename for saving encoded data + * @param filename Filename for saving encoded data * @return true if successful, false otherwise - * */ - public static boolean encodeToFile( byte[] dataToEncode, String filename ) - { + public static boolean encodeToFile(byte[] dataToEncode, String filename) { boolean success = false; Base64.OutputStream bos = null; - try - { - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.ENCODE ); - bos.write( dataToEncode ); + try { + bos = new Base64.OutputStream( + new java.io.FileOutputStream(filename), Base64.ENCODE); + bos.write(dataToEncode); success = true; } // end try - catch( java.io.IOException e ) - { - + catch (java.io.IOException e) { + success = false; } // end catch: IOException - finally - { - try{ bos.close(); } catch( Exception e ){} + finally { + try { + bos.close(); + } catch (Exception e) { + } } // end finally - + return success; } // end encodeToFile - - + + /** * Convenience method for decoding data to a file. * * @param dataToDecode Base64-encoded data as a string - * @param filename Filename for saving decoded data + * @param filename Filename for saving decoded data * @return true if successful, false otherwise - * */ - public static boolean decodeToFile( String dataToDecode, String filename ) - { + public static boolean decodeToFile(String dataToDecode, String filename) { boolean success = false; Base64.OutputStream bos = null; - try - { - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.DECODE ); - bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); - success = true; + try { + bos = new Base64.OutputStream( + new java.io.FileOutputStream(filename), Base64.DECODE); + bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); + success = true; } // end try - catch( java.io.IOException e ) - { + catch (java.io.IOException e) { success = false; } // end catch: IOException - finally - { - try{ bos.close(); } catch( Exception e ){} + finally { + try { + bos.close(); + } catch (Exception e) { + } } // end finally - + return success; } // end decodeToFile - - - - + + /** * Convenience method for reading a base64-encoded * file and decoding it. * * @param filename Filename for reading encoded data * @return decoded byte array or null if unsuccessful - * */ - public static byte[] decodeFromFile( String filename ) - { + public static byte[] decodeFromFile(String filename) { byte[] decodedData = null; Base64.InputStream bis = null; - try - { + try { // Set up some useful variables - java.io.File file = new java.io.File( filename ); + java.io.File file = new java.io.File(filename); byte[] buffer = null; - int length = 0; + int length = 0; int numBytes = 0; - + // Check for size of file - if( file.length() > Integer.MAX_VALUE ) - { - System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." ); + if (file.length() > Integer.MAX_VALUE) { + System.err.println("File is too big for this convenience method (" + file.length() + " bytes)."); return null; } // end if: file too big for int index - buffer = new byte[ (int)file.length() ]; - + buffer = new byte[(int) file.length()]; + // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.DECODE ); - + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream(file)), Base64.DECODE); + // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) length += numBytes; - + // Save in a variable to return - decodedData = new byte[ length ]; - System.arraycopy( buffer, 0, decodedData, 0, length ); - + decodedData = new byte[length]; + System.arraycopy(buffer, 0, decodedData, 0, length); + } // end try - catch( java.io.IOException e ) - { - System.err.println( "Error decoding from file " + filename ); + catch (java.io.IOException e) { + System.err.println("Error decoding from file " + filename); } // end catch: IOException - finally - { - try{ bis.close(); } catch( Exception e) {} + finally { + try { + bis.close(); + } catch (Exception e) { + } } // end finally - + return decodedData; } // end decodeFromFile - - - + + /** * Convenience method for reading a binary file * and base64-encoding it. * * @param filename Filename for reading binary data * @return base64-encoded string or null if unsuccessful - * */ - public static String encodeFromFile( String filename ) - { + public static String encodeFromFile(String filename) { String encodedData = null; Base64.InputStream bis = null; - try - { + try { // Set up some useful variables - java.io.File file = new java.io.File( filename ); - byte[] buffer = new byte[ (int)(file.length() * 1.4) ]; - int length = 0; + java.io.File file = new java.io.File(filename); + byte[] buffer = new byte[(int) (file.length() * 1.4)]; + int length = 0; int numBytes = 0; - + // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.ENCODE ); - + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream(file)), Base64.ENCODE); + // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) length += numBytes; - + // Save in a variable to return - encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); - + encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING); + } // end try - catch( java.io.IOException e ) - { - System.err.println( "Error encoding from file " + filename ); + catch (java.io.IOException e) { + System.err.println("Error encoding from file " + filename); } // end catch: IOException - finally - { - try{ bis.close(); } catch( Exception e) {} + finally { + try { + bis.close(); + } catch (Exception e) { + } } // end finally - + return encodedData; - } // end encodeFromFile - - - - + } // end encodeFromFile + + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ - - - + + /** * A {@link Base64.InputStream} will read data from another * java.io.InputStream, given in the constructor, @@ -1002,28 +979,26 @@ public static String encodeFromFile( String filename ) * * @see Base64 */ - public static class InputStream extends java.io.FilterInputStream - { + public static class InputStream extends java.io.FilterInputStream { private boolean encode; // Encoding or decoding - private int position; // Current position in the buffer - private byte[] buffer; // Small buffer holding converted data - private int bufferLength; // Length of buffer (3 or 4) - private int numSigBytes; // Number of meaningful bytes in the buffer - private int lineLength; + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; private boolean breakLines; // Break lines at less than 80 characters - - + + /** * Constructs a {@link Base64.InputStream} in DECODE mode. * * @param in the java.io.InputStream from which to read data. */ - public InputStream( java.io.InputStream in ) - { - this( in, DECODE ); + public InputStream(java.io.InputStream in) { + this(in, DECODE); } // end constructor - - + + /** * Constructs a {@link Base64.InputStream} in * either ENCODE or DECODE mode. @@ -1037,146 +1012,129 @@ public InputStream( java.io.InputStream in ) *

                * Example: new Base64.InputStream( in, Base64.DECODE ) * - * - * @param in the java.io.InputStream from which to read data. + * @param in the java.io.InputStream from which to read data. * @param options Specified options * @see Base64#ENCODE * @see Base64#DECODE * @see Base64#DONT_BREAK_LINES */ - public InputStream( java.io.InputStream in, int options ) - { - super( in ); - this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; - this.encode = (options & ENCODE) == ENCODE; + public InputStream(java.io.InputStream in, int options) { + super(in); + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; this.bufferLength = encode ? 4 : 3; - this.buffer = new byte[ bufferLength ]; + this.buffer = new byte[bufferLength]; this.position = -1; this.lineLength = 0; } // end constructor - + /** * Reads enough of the input stream to convert * to/from Base64 and returns the next byte. * * @return next byte */ - public int read() throws java.io.IOException - { + public int read() throws java.io.IOException { // Do we need to get data? - if( position < 0 ) - { - if( encode ) - { + if (position < 0) { + if (encode) { byte[] b3 = new byte[3]; int numBinaryBytes = 0; - for( int i = 0; i < 3; i++ ) - { - try - { + for (int i = 0; i < 3; i++) { + try { int b = in.read(); - + // If end of stream, b is -1. - if( b >= 0 ) - { - b3[i] = (byte)b; + if (b >= 0) { + b3[i] = (byte) b; numBinaryBytes++; } // end if: not end of stream - + } // end try: read - catch( java.io.IOException e ) - { + catch (java.io.IOException e) { // Only a problem if we got no data at all. - if( i == 0 ) + if (i == 0) throw e; - + } // end catch } // end for: each needed input byte - - if( numBinaryBytes > 0 ) - { - encode3to4( b3, 0, numBinaryBytes, buffer, 0 ); + + if (numBinaryBytes > 0) { + encode3to4(b3, 0, numBinaryBytes, buffer, 0); position = 0; numSigBytes = 4; } // end if: got data - else - { + else { return -1; } // end else } // end if: encoding - + // Else decoding - else - { + else { byte[] b4 = new byte[4]; int i = 0; - for( i = 0; i < 4; i++ ) - { + for (i = 0; i < 4; i++) { // Read four "meaningful" bytes: int b = 0; - do{ b = in.read(); } - while( b >= 0 && DECODABET[ b & 0x7f ] <= WHITE_SPACE_ENC ); - - if( b < 0 ) + do { + b = in.read(); + } + while (b >= 0 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC); + + if (b < 0) break; // Reads a -1 if end of stream - - b4[i] = (byte)b; + + b4[i] = (byte) b; } // end for: each needed input byte - - if( i == 4 ) - { - numSigBytes = decode4to3( b4, 0, buffer, 0 ); + + if (i == 4) { + numSigBytes = decode4to3(b4, 0, buffer, 0); position = 0; } // end if: got four characters - else if( i == 0 ){ + else if (i == 0) { return -1; } // end else if: also padded correctly - else - { + else { // Must have broken out from above. - throw new java.io.IOException( "Improperly padded Base64 input." ); + throw new java.io.IOException("Improperly padded Base64 input."); } // end - + } // end else: decode } // end else: get data - + // Got data? - if( position >= 0 ) - { + if (position >= 0) { // End of relevant data? - if( /*!encode &&*/ position >= numSigBytes ) + if ( /*!encode &&*/ position >= numSigBytes) return -1; - - if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) - { + + if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { lineLength = 0; return '\n'; } // end if - else - { + else { lineLength++; // This isn't important when decoding - // but throwing an extra "if" seems - // just as wasteful. - - int b = buffer[ position++ ]; + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[position++]; - if( position >= bufferLength ) + if (position >= bufferLength) position = -1; return b & 0xFF; // This is how you "cast" a byte that's - // intended to be unsigned. + // intended to be unsigned. } // end else } // end if: position >= 0 - + // Else error - else - { + else { // When JDK1.4 is more accepted, use an assertion here. - throw new java.io.IOException( "Error in Base64 code reading stream." ); + throw new java.io.IOException("Error in Base64 code reading stream."); } // end else } // end read - - + + /** * Calls {@link #read()} repeatedly until the end of stream * is reached or len bytes are read. @@ -1184,42 +1142,39 @@ else if( i == 0 ){ * end of stream is encountered. * * @param dest array to hold values - * @param off offset for array - * @param len max number of bytes to read into array + * @param off offset for array + * @param len max number of bytes to read into array * @return bytes read into array or -1 if end of stream is encountered. */ - public int read( byte[] dest, int off, int len ) throws java.io.IOException - { + public int read(byte[] dest, int off, int len) throws java.io.IOException { int i; int b; - for( i = 0; i < len; i++ ) - { + for (i = 0; i < len; i++) { b = read(); - + //if( b < 0 && i == 0 ) // return -1; - - if( b >= 0 ) - dest[off + i] = (byte)b; - else if( i == 0 ) + + if (b >= 0) + dest[off + i] = (byte) b; + else if (i == 0) return -1; else break; // Out of 'for' loop } // end for: each byte read return i; } // end read - + } // end inner class InputStream - - - - - - + + + + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ - - - + + /** * A {@link Base64.OutputStream} will write data to another * java.io.OutputStream, given in the constructor, @@ -1227,28 +1182,26 @@ else if( i == 0 ) * * @see Base64 */ - public static class OutputStream extends java.io.FilterOutputStream - { + public static class OutputStream extends java.io.FilterOutputStream { private boolean encode; - private int position; - private byte[] buffer; - private int bufferLength; - private int lineLength; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; private boolean breakLines; - private byte[] b4; // Scratch used in a few places + private byte[] b4; // Scratch used in a few places private boolean suspendEncoding; - + /** * Constructs a {@link Base64.OutputStream} in ENCODE mode. * * @param out the java.io.OutputStream to which data will be written. */ - public OutputStream( java.io.OutputStream out ) - { - this( out, ENCODE ); + public OutputStream(java.io.OutputStream out) { + this(out, ENCODE); } // end constructor - - + + /** * Constructs a {@link Base64.OutputStream} in * either ENCODE or DECODE mode. @@ -1262,26 +1215,25 @@ public OutputStream( java.io.OutputStream out ) *

                * Example: new Base64.OutputStream( out, Base64.ENCODE ) * - * @param out the java.io.OutputStream to which data will be written. + * @param out the java.io.OutputStream to which data will be written. * @param options Specified options. * @see Base64#ENCODE * @see Base64#DECODE * @see Base64#DONT_BREAK_LINES */ - public OutputStream( java.io.OutputStream out, int options ) - { - super( out ); - this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; - this.encode = (options & ENCODE) == ENCODE; + public OutputStream(java.io.OutputStream out, int options) { + super(out); + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; this.bufferLength = encode ? 3 : 4; - this.buffer = new byte[ bufferLength ]; - this.position = 0; - this.lineLength = 0; + this.buffer = new byte[bufferLength]; + this.position = 0; + this.lineLength = 0; this.suspendEncoding = false; - this.b4 = new byte[4]; + this.b4 = new byte[4]; } // end constructor - - + + /** * Writes the byte to the output stream after * converting to/from Base64 notation. @@ -1293,27 +1245,23 @@ public OutputStream( java.io.OutputStream out, int options ) * * @param theByte the byte to write */ - public void write(int theByte) throws java.io.IOException - { + public void write(int theByte) throws java.io.IOException { // Encoding suspended? - if( suspendEncoding ) - { - super.out.write( theByte ); + if (suspendEncoding) { + super.out.write(theByte); return; } // end if: supsended - + // Encode? - if( encode ) - { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) // Enough to encode. + if (encode) { + buffer[position++] = (byte) theByte; + if (position >= bufferLength) // Enough to encode. { - out.write( encode3to4( b4, buffer, bufferLength ) ); + out.write(encode3to4(b4, buffer, bufferLength)); lineLength += 4; - if( breakLines && lineLength >= MAX_LINE_LENGTH ) - { - out.write( NEW_LINE ); + if (breakLines && lineLength >= MAX_LINE_LENGTH) { + out.write(NEW_LINE); lineLength = 0; } // end if: end of line @@ -1322,123 +1270,103 @@ public void write(int theByte) throws java.io.IOException } // end if: encoding // Else, Decoding - else - { + else { // Meaningful Base64 character? - if( DECODABET[ theByte & 0x7f ] > WHITE_SPACE_ENC ) - { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) // Enough to output. + if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) { + buffer[position++] = (byte) theByte; + if (position >= bufferLength) // Enough to output. { - int len = Base64.decode4to3( buffer, 0, b4, 0 ); - out.write( b4, 0, len ); + int len = Base64.decode4to3(buffer, 0, b4, 0); + out.write(b4, 0, len); //out.write( Base64.decode4to3( buffer ) ); position = 0; } // end if: enough to output } // end if: meaningful base64 character - else if( DECODABET[ theByte & 0x7f ] != WHITE_SPACE_ENC ) - { - throw new java.io.IOException( "Invalid character in Base64 data." ); + else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) { + throw new java.io.IOException("Invalid character in Base64 data."); } // end else: not white space either } // end else: decoding } // end write - - - + + /** - * Calls {@link #write(int)} repeatedly until len + * Calls {@link #write(int)} repeatedly until len * bytes are written. * * @param theBytes array from which to read bytes - * @param off offset for array - * @param len max number of bytes to read into array + * @param off offset for array + * @param len max number of bytes to read into array */ - public void write( byte[] theBytes, int off, int len ) throws java.io.IOException - { + public void write(byte[] theBytes, int off, int len) throws java.io.IOException { // Encoding suspended? - if( suspendEncoding ) - { - super.out.write( theBytes, off, len ); + if (suspendEncoding) { + super.out.write(theBytes, off, len); return; } // end if: supsended - - for( int i = 0; i < len; i++ ) - { - write( theBytes[ off + i ] ); + + for (int i = 0; i < len; i++) { + write(theBytes[off + i]); } // end for: each byte written - + } // end write - - - + + /** * Method added by PHIL. [Thanks, PHIL. -Rob] * This pads the buffer without closing the stream. */ - public void flushBase64() throws java.io.IOException - { - if( position > 0 ) - { - if( encode ) - { - out.write( encode3to4( b4, buffer, position ) ); + public void flushBase64() throws java.io.IOException { + if (position > 0) { + if (encode) { + out.write(encode3to4(b4, buffer, position)); position = 0; } // end if: encoding - else - { - throw new java.io.IOException( "Base64 input not properly padded." ); + else { + throw new java.io.IOException("Base64 input not properly padded."); } // end else: decoding } // end if: buffer partially full } // end flush - - /** - * Flushes and closes (I think, in the superclass) the stream. - * + + /** + * Flushes and closes (I think, in the superclass) the stream. */ - public void close() throws java.io.IOException - { + public void close() throws java.io.IOException { // 1. Ensure that pending characters are written flushBase64(); // 2. Actually close the stream // Base class both flushes and closes. super.close(); - + buffer = null; - out = null; + out = null; } // end close - - - + + /** * Suspends encoding of the stream. * May be helpful if you need to embed a piece of * base640-encoded data in a stream. - * */ - public void suspendEncoding() throws java.io.IOException - { + public void suspendEncoding() throws java.io.IOException { flushBase64(); this.suspendEncoding = true; } // end suspendEncoding - - + + /** * Resumes encoding of the stream. * May be helpful if you need to embed a piece of * base640-encoded data in a stream. - * */ - public void resumeEncoding() - { + public void resumeEncoding() { this.suspendEncoding = false; } // end resumeEncoding - - - + + } // end inner class OutputStream - - + + } // end class Base64 diff --git a/src/main/java/io/starter/toolkit/BrowserLauncher.java b/src/main/java/io/starter/toolkit/BrowserLauncher.java index fef2ec3..1d335a2 100644 --- a/src/main/java/io/starter/toolkit/BrowserLauncher.java +++ b/src/main/java/io/starter/toolkit/BrowserLauncher.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -27,86 +27,94 @@ * http://www.centerkey.com/java/browser/ */ -/** Launches the user's default browser to display a web page. +/** + * Launches the user's default browser to display a web page. + * * @author Dem Pilafian * @author Sam Hanes */ public class BrowserLauncher { - /** List of potential browsers on systems without a default mechanism. */ - public static final String[] browsers = { - "google-chrome", "firefox", "opera", "konqueror", "epiphany", - "seamonkey", "galeon", "kazehakase", "mozilla" }; - - /** The browser that was last successfully run. */ - private static String browser = null; - - /** Opens the specified web page in the user's default browser. - * @param url the URL of the page to be opened - * @throws Exception if an error occurred attempting to launch the browser. - * If the browser is successfully started but later fails for - * some reason, no exception will be thrown. - */ - public static void open (String url) - throws Exception { - // Attempt to use the Desktop class from JDK 1.6+ (even if on 1.5) - // This uses reflection to mimic the call: - // java.awt.Desktop.getDesktop().browse( java.net.URI.create(url) ); - try { - Class desktop = Class.forName("java.awt.Desktop"); - desktop.getDeclaredMethod ( - "browse", new Class[] {java.net.URI.class}) - .invoke( - desktop.getDeclaredMethod ( - "getDesktop", (Class[]) null) - .invoke (null, (Object[]) null), - new Object[] {java.net.URI.create(url)} ); - - // If that didn't throw an exception, we're done - return; - } catch (ClassNotFoundException e) { - // Intentionally empty, falls back to platform-dependent code - } catch (NoSuchMethodException e) { - // Intentionally empty, falls back to platform-dependent code - } catch (Exception e) { - throw new Exception ("failed to launch browser", e); - } - - String osName = System.getProperty("os.name"); - try { - // If this is OS X, use the FileManager class - if (osName.startsWith("Mac OS")) - Class.forName ("com.apple.eio.FileManager") - .getDeclaredMethod ("openURL", new Class[] {String.class}) - .invoke (null, new Object[] {url}); - - // If this is Windows, call the FileProtocolHandler via rundll - else if (osName.startsWith("Windows")) - Runtime.getRuntime().exec( - "rundll32 url.dll,FileProtocolHandler " + url); - - // Otherwise, assume this is a POSIX-like system and - // start trying possible browser commands - else { - // If we haven't found a browser yet, try some possible ones - if (browser == null) { - for (int idx = 0; idx < browsers.length; idx++) { - if (Runtime.getRuntime().exec( - new String[] {"which", browsers[idx]}) - .waitFor() == 0 ) { - browser = browsers[idx]; - } - } - - // If we couldn't find one, throw an exception - if (browser == null) - throw new Exception ("no browser found"); - } - - // Call the browser with the URL - Runtime.getRuntime().exec( new String[] {browser, url} ); - } - } catch (Exception e) { - throw new Exception ("failed to launch browser", e); - } - } + /** + * List of potential browsers on systems without a default mechanism. + */ + public static final String[] browsers = { + "google-chrome", "firefox", "opera", "konqueror", "epiphany", + "seamonkey", "galeon", "kazehakase", "mozilla"}; + + /** + * The browser that was last successfully run. + */ + private static String browser = null; + + /** + * Opens the specified web page in the user's default browser. + * + * @param url the URL of the page to be opened + * @throws Exception if an error occurred attempting to launch the browser. + * If the browser is successfully started but later fails for + * some reason, no exception will be thrown. + */ + public static void open(String url) + throws Exception { + // Attempt to use the Desktop class from JDK 1.6+ (even if on 1.5) + // This uses reflection to mimic the call: + // java.awt.Desktop.getDesktop().browse( java.net.URI.create(url) ); + try { + Class desktop = Class.forName("java.awt.Desktop"); + desktop.getDeclaredMethod( + "browse", new Class[]{java.net.URI.class}) + .invoke( + desktop.getDeclaredMethod( + "getDesktop", (Class[]) null) + .invoke(null, (Object[]) null), + java.net.URI.create(url)); + + // If that didn't throw an exception, we're done + return; + } catch (ClassNotFoundException e) { + // Intentionally empty, falls back to platform-dependent code + } catch (NoSuchMethodException e) { + // Intentionally empty, falls back to platform-dependent code + } catch (Exception e) { + throw new Exception("failed to launch browser", e); + } + + String osName = System.getProperty("os.name"); + try { + // If this is OS X, use the FileManager class + if (osName.startsWith("Mac OS")) + Class.forName("com.apple.eio.FileManager") + .getDeclaredMethod("openURL", new Class[]{String.class}) + .invoke(null, url); + + // If this is Windows, call the FileProtocolHandler via rundll + else if (osName.startsWith("Windows")) + Runtime.getRuntime().exec( + "rundll32 url.dll,FileProtocolHandler " + url); + + // Otherwise, assume this is a POSIX-like system and + // start trying possible browser commands + else { + // If we haven't found a browser yet, try some possible ones + if (browser == null) { + for (int idx = 0; idx < browsers.length; idx++) { + if (Runtime.getRuntime().exec( + new String[]{"which", browsers[idx]}) + .waitFor() == 0) { + browser = browsers[idx]; + } + } + + // If we couldn't find one, throw an exception + if (browser == null) + throw new Exception("no browser found"); + } + + // Call the browser with the URL + Runtime.getRuntime().exec(new String[]{browser, url}); + } + } catch (Exception e) { + throw new Exception("failed to launch browser", e); + } + } } diff --git a/src/main/java/io/starter/toolkit/ByteTools.java b/src/main/java/io/starter/toolkit/ByteTools.java index 94c0f1b..818e975 100644 --- a/src/main/java/io/starter/toolkit/ByteTools.java +++ b/src/main/java/io/starter/toolkit/ByteTools.java @@ -2,72 +2,65 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.toolkit; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.util.Collection; -import java.util.Enumeration; + +import java.io.*; import java.util.Iterator; import java.util.List; +/** + * Helper methods for working with byte arrays and XLS files. + */ + +public final class ByteTools implements Serializable { + + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 1220042103372057083L; + + /** + * Returns a string representation of the byte array. + * + * @param bt the byte array + * @param offset - offset into byte array + * @return the string representation + */ + public static String getByteDump(byte[] bt, int offset) { + return ByteTools.getByteDump(bt, offset, bt.length); + } -/** Helper methods for working with byte arrays and XLS files. - - -*/ - -public final class ByteTools implements Serializable -{ - - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1220042103372057083L; - - /**Returns a string representation of the byte array. - * - * @param bt the byte array - * @param offset - offset into byte array - * @return the string representation - */ - public static String getByteDump(byte[] bt, int offset){ - return ByteTools.getByteDump(bt, offset, bt.length); - } - - /**Returns a string representation of the byte array. - * - * @param bt the byte array - * @param offset - offset into byte array - * @param len - length of byte array segment to return - * @return the string representation - */ - public static String getByteDump(byte[] bt, int offset, int len){ - if(bt==null) - return ""; + /** + * Returns a string representation of the byte array. + * + * @param bt the byte array + * @param offset - offset into byte array + * @param len - length of byte array segment to return + * @return the string representation + */ + public static String getByteDump(byte[] bt, int offset, int len) { + if (bt == null) + return ""; StringBuffer buf = new StringBuffer(); int every4 = 0; int every16 = 0; @@ -75,10 +68,10 @@ public static String getByteDump(byte[] bt, int offset, int len){ String offst = (Integer.toHexString(offset)); // now calculate where the 4 byte words should be offset so display matches... //int remainder = offset%16; - while(offst.length()<4)offst = "0"+offst; + while (offst.length() < 4) offst = "0" + offst; buf.append(offst); buf.append(": "); - int origOffset= offset; + int origOffset = offset; offset += 16; for (int i = origOffset; i < /*origOffset+*/len; ++i) { buf.append(hexits.charAt((bt[i] >>> 4) & 0xf)); @@ -89,10 +82,10 @@ public static String getByteDump(byte[] bt, int offset, int len){ every4 = 0; buf.append(" "); every16++; - if (every16 == 4){ + if (every16 == 4) { buf.append("\r\n"); offst = (Integer.toHexString(offset)); - while(offst.length()<4)offst = "0"+offst; + while (offst.length() < 4) offst = "0" + offst; buf.append(offst); buf.append(": "); offset += 16; @@ -101,133 +94,139 @@ public static String getByteDump(byte[] bt, int offset, int len){ } } return buf + ""; - } -private static String hexits = "0123456789ABCDEF"; - -/**Returns a string representation of the byte array. - * - * @param bt the byte array - * @param pad whether to pad the strings so they align - * @return the string representation - */ -public static String getByteString(byte[] bt, boolean pad){ - if(bt.length==0)return "null"; - StringBuffer ret = new StringBuffer(); - for(int x=0;x0))ret.append("\r\n"); - //String bstr = Integer.toOctalString(bt[x]); // toBinaryString(bt[x]); // Byte.toString(bt[x]); - String bstr = Byte.toString(bt[x]); // toBinaryString(bt[x]); // Byte.toString(bt[x]); - if(pad)while(bstr.length()<4)bstr = " " + bstr; - ret.append(bstr); - ret.append(","); } - ret.setLength(ret.length()-1); - //ret.append(); - return ret.toString(); -} - /** Appends one byte array to another. - * If either input (but not both) is null, a clone of the other will be - * returned. This method is guaranteed to always return an array different - * from either of those passed in. - * - * @param src the array which will be appended to dest - * @param dest the array to which src will be appended - * @throws NullPointerException if both inputs are null + private static String hexits = "0123456789ABCDEF"; + + /** + * Returns a string representation of the byte array. + * + * @param bt the byte array + * @param pad whether to pad the strings so they align + * @return the string representation */ - public static byte[] append (byte[] src, byte[] dest){ - // Deal with null input correctly - if (src == null) return (byte[]) dest.clone(); - if (dest == null) return (byte[]) src.clone(); - + public static String getByteString(byte[] bt, boolean pad) { + if (bt.length == 0) return "null"; + StringBuffer ret = new StringBuffer(); + for (int x = 0; x < bt.length; x++) { + if (((x % 8) == 0) && (x > 0)) ret.append("\r\n"); + //String bstr = Integer.toOctalString(bt[x]); // toBinaryString(bt[x]); // Byte.toString(bt[x]); + String bstr = Byte.toString(bt[x]); // toBinaryString(bt[x]); // Byte.toString(bt[x]); + if (pad) while (bstr.length() < 4) bstr = " " + bstr; + ret.append(bstr); + ret.append(","); + } + ret.setLength(ret.length() - 1); + //ret.append(); + return ret.toString(); + } + + /** + * Appends one byte array to another. + * If either input (but not both) is null, a clone of the other will be + * returned. This method is guaranteed to always return an array different + * from either of those passed in. + * + * @param src the array which will be appended to dest + * @param dest the array to which src will be appended + * @throws NullPointerException if both inputs are null + */ + public static byte[] append(byte[] src, byte[] dest) { + // Deal with null input correctly + if (src == null) return dest.clone(); + if (dest == null) return src.clone(); + int srclen = src.length; int destlen = dest.length; - - byte[] ret = new byte[ srclen + destlen ]; - System.arraycopy( dest, 0, ret, 0, destlen ); - System.arraycopy( src, 0, ret, destlen, srclen ); - + + byte[] ret = new byte[srclen + destlen]; + System.arraycopy(dest, 0, ret, 0, destlen); + System.arraycopy(src, 0, ret, destlen, srclen); + return ret; } - - /** append one byte array to an empty array - of the proper size - usage: - newarray = bytetool.append(sourcearray, destinationarray, position to start copy at); - */ - public static byte[] append(byte[] src, byte[] dest, int pos){ + + /** + * append one byte array to an empty array + * of the proper size + * usage: + * newarray = bytetool.append(sourcearray, destinationarray, position to start copy at); + */ + public static byte[] append(byte[] src, byte[] dest, int pos) { int srclen = src.length; - if(dest == null)dest = new byte[srclen]; + if (dest == null) dest = new byte[srclen]; int destlen = dest.length; - if(destlen < srclen){ - Logger.logInfo("Your destination byte array is too small to copy into: srclen=" + String.valueOf(srclen) + ": destlen=" + String.valueOf(destlen) ); + if (destlen < srclen) { + Logger.logInfo("Your destination byte array is too small to copy into: srclen=" + srclen + ": destlen=" + destlen); srclen = destlen; } System.arraycopy(src, 0, dest, pos, srclen); return dest; } - - public static byte[] cLongToLEBytes(int i){ + + public static byte[] cLongToLEBytes(int i) { byte[] ret = new byte[4]; - ret[0] = (byte)(i & 0xff); - ret[1] = (byte)((i>>8) & 0xff); - ret[2] = (byte)((i>>16) & 0xff); - ret[3] = (byte)((i>>24) & 0xff); + ret[0] = (byte) (i & 0xff); + ret[1] = (byte) ((i >> 8) & 0xff); + ret[2] = (byte) ((i >> 16) & 0xff); + ret[3] = (byte) ((i >> 24) & 0xff); return ret; } - - /** C Longs are only 32 bits, Java Longs are 64. - This method converts a 32-bit 'C' long to a - pair of java shorts. - - Also performs 'little-endian' conversion. - */ - public static short[] cLongToLEShorts(int x){ + + /** + * C Longs are only 32 bits, Java Longs are 64. + * This method converts a 32-bit 'C' long to a + * pair of java shorts. + *

                + * Also performs 'little-endian' conversion. + */ + public static short[] cLongToLEShorts(int x) { short[] buf = new short[2]; short high = (short) (x >>> 16); - short low = (short) x; + short low = (short) x; buf[0] = low; buf[1] = high; // if(DEBUG)Logger.logInfo(Info( ("x=" + x + " high=" + high + " low=" + low ); return buf; } - public static byte[] doubleToLEByteArray(double d){ - byte[] bite=new byte[8]; // A long is 8 bytes + public static byte[] doubleToLEByteArray(double d) { + byte[] bite = new byte[8]; // A long is 8 bytes long l = Double.doubleToLongBits(d); - int i; - long t; - t=l; // variable t will be shifted right each time thru the loop. - for(i=bite.length-1;i>-1;i--) { //High order byte will be in b[0] + int i; + long t; + t = l; // variable t will be shifted right each time thru the loop. + for (i = bite.length - 1; i > -1; i--) { //High order byte will be in b[0] long irr = (t & 0xff); - bite[i]= Integer.valueOf((int)irr).byteValue(); //get the last 8 bits into the byte array. - t=t>>8; //Shifts the long 1 byte. Same as divide by 256 - } + bite[i] = Integer.valueOf((int) irr).byteValue(); //get the last 8 bits into the byte array. + t = t >> 8; //Shifts the long 1 byte. Same as divide by 256 + } byte[] ret = new byte[bite.length]; - for(int x=0;x-1;i--) { //High order byte will be in b[0] + int i; + long t; + t = l; // variable t will be shifted right each time thru the loop. + for (i = bite.length - 1; i > -1; i--) { //High order byte will be in b[0] long irr = (t & 0xff); - bite[i]= Integer.valueOf((int)irr).byteValue(); //get the last 8 bits into the byte array. - t=t>>8; //Shifts the long 1 byte. Same as divide by 256 - } + bite[i] = Integer.valueOf((int) irr).byteValue(); //get the last 8 bits into the byte array. + t = t >> 8; //Shifts the long 1 byte. Same as divide by 256 + } return bite; - } - - /** converts and bitswaps an eight bite byte array into an IEEE double. - */ - public static double eightBytetoLEDouble(byte[] bite){ + } + + /** + * converts and bitswaps an eight bite byte array into an IEEE double. + */ + public static double eightBytetoLEDouble(byte[] bite) { byte[] b = new byte[8]; b[0] = bite[7]; b[1] = bite[6]; @@ -238,19 +237,22 @@ public static double eightBytetoLEDouble(byte[] bite){ b[6] = bite[1]; b[7] = bite[0]; double d = 0; - java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(b); + java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(b); java.io.DataInputStream dis = new java.io.DataInputStream(bais); try { - Double dbl = new Double(dis.readDouble()); + Double dbl = new Double(dis.readDouble()); d = dbl.doubleValue(); - } catch (java.io.IOException e){ Logger.logInfo("io exception in byte to Double conversion" + e);} + } catch (java.io.IOException e) { + Logger.logInfo("io exception in byte to Double conversion" + e); + } return d; } - - - /** converts and bitswaps an eight bite byte array into an IEEE double. - */ - public static long eightBytetoLELong(byte[] bite){ + + + /** + * converts and bitswaps an eight bite byte array into an IEEE double. + */ + public static long eightBytetoLELong(byte[] bite) { byte[] b = new byte[8]; b[0] = bite[7]; b[1] = bite[6]; @@ -261,82 +263,86 @@ public static long eightBytetoLELong(byte[] bite){ b[6] = bite[1]; b[7] = bite[0]; long l = 0; - java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(b); + java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(b); java.io.DataInputStream dis = new java.io.DataInputStream(bais); try { - Long lg = new Long(dis.readLong()); + Long lg = new Long(dis.readLong()); l = lg.longValue(); - } catch (java.io.IOException e){ Logger.logInfo("io exception in byte to Double conversion" + e);} + } catch (java.io.IOException e) { + Logger.logInfo("io exception in byte to Double conversion" + e); + } return l; - } + } - /** - * Get an array of bytes from a collection of byte arrays - * - * Seems slow, why 2 iterations, should be faster way? - - */ - public static byte[] getBytes(List records){ + /** + * Get an array of bytes from a collection of byte arrays + *

                + * Seems slow, why 2 iterations, should be faster way? + */ + public static byte[] getBytes(List records) { Iterator e = records.iterator(); int buflen = 0; - while(e.hasNext()){ + while (e.hasNext()) { byte[] barr = (byte[]) e.next(); buflen += barr.length; } byte[] outbytes = new byte[buflen]; int pos = 0; - for(int i =0;i=0x7f) - return true; // deal with non-compressible Eastern Strings - i = i+1; - if (strbytes[i] != 0x0){ - return true; // there is a non-zero high-byte + } catch (UnsupportedEncodingException e) { + } + for (int i = 0; i < strbytes.length; i++) { + if (strbytes[i] >= 0x7f) + return true; // deal with non-compressible Eastern Strings + i = i + 1; + if (strbytes[i] != 0x0) { + return true; // there is a non-zero high-byte } } return false; - } - - public static byte[] longToByteArray(long l){ - byte[] bite=new byte[8]; // A long is 8 bytes - int i; - long t; - t=l; // variable t will be shifted right each time thru the loop. - for(i=bite.length-1;i>-1;i--) { //High order byte will be in b[0] + } + + public static byte[] longToByteArray(long l) { + byte[] bite = new byte[8]; // A long is 8 bytes + int i; + long t; + t = l; // variable t will be shifted right each time thru the loop. + for (i = bite.length - 1; i > -1; i--) { //High order byte will be in b[0] long irr = (t & 0xff); - bite[i]= Integer.valueOf((int)irr).byteValue(); //get the last 8 bits into the byte array. - t=t>>8; //Shifts the long 1 byte. Same as divide by 256 - } + bite[i] = Integer.valueOf((int) irr).byteValue(); //get the last 8 bits into the byte array. + t = t >> 8; //Shifts the long 1 byte. Same as divide by 256 + } return bite; - } - - - /** same as readInt, but takes 4 raw bytes instead of 2 shorts - */ - public static int readInt(byte[] bs){ - return readInt( readShort(bs[2], bs[3]), readShort(bs[0], bs[1])); - } - - /** same as readInt, but takes 4 raw bytes instead of 2 shorts - */ - public static int readInt(byte b1, byte b2, byte b3, byte b4){ + } + + + /** + * same as readInt, but takes 4 raw bytes instead of 2 shorts + */ + public static int readInt(byte[] bs) { + return readInt(readShort(bs[2], bs[3]), readShort(bs[0], bs[1])); + } + + /** + * same as readInt, but takes 4 raw bytes instead of 2 shorts + */ + public static int readInt(byte b1, byte b2, byte b3, byte b4) { return readInt(readShort(b3, b4), readShort(b1, b2)); } - - /** Reads a 4 byte int from a byte array at the specified position + + /** + * Reads a 4 byte int from a byte array at the specified position * and handles a little endian conversion */ - public static int readInt(byte[] b, int offset){ - return readInt(b[offset++],b[offset++],b[offset++],b[offset++]); - } - - - /** bit-flipping action converting a 'little-endian' - pair of shorts to a 'big-endian' long. - This is really a java int as it represents a C-language - long value which is only 32 bits, like the java int. - */ - public static int readInt(int low, int high) - { - if(low == 0x0 && high == 0x0)return 0; - low = low & 0xffff; - high = high & 0xffff; - return (int) (low << 16 | high); + public static int readInt(byte[] b, int offset) { + return readInt(b[offset++], b[offset++], b[offset++], b[offset++]); } - /** bit-flipping action converting a 'little-endian' - pair of bytes to a 'big-endian' short. Returns an int as - excel uses unsigned shorts which can exceed the boundary - of a java signed short - */ - public static int readUnsignedShort(byte low, byte high) - { - return readInt(low,high,(byte)0x0,(byte)0x0); + /** + * bit-flipping action converting a 'little-endian' + * pair of shorts to a 'big-endian' long. + * This is really a java int as it represents a C-language + * long value which is only 32 bits, like the java int. + */ + public static int readInt(int low, int high) { + if (low == 0x0 && high == 0x0) return 0; + low = low & 0xffff; + high = high & 0xffff; + return low << 16 | high; } - - /** bit-flipping action converting a 'little-endian' - pair of bytes to a 'big-endian' short. - - This will break if you pass it any values larger than a byte. Will - probably return a value, but I wouldn't trust it. Fix in R2 -Rab - - */ - public static short readShort(int low, int high) - { + + + /** + * bit-flipping action converting a 'little-endian' + * pair of bytes to a 'big-endian' short. Returns an int as + * excel uses unsigned shorts which can exceed the boundary + * of a java signed short + */ + public static int readUnsignedShort(byte low, byte high) { + return readInt(low, high, (byte) 0x0, (byte) 0x0); + } + + /** + * bit-flipping action converting a 'little-endian' + * pair of bytes to a 'big-endian' short. + *

                + * This will break if you pass it any values larger than a byte. Will + * probably return a value, but I wouldn't trust it. Fix in R2 -Rab + */ + public static short readShort(int low, int high) { // 2 bytes - low = low & 0xff; - high = high & 0xff; - return (short)(high << 8 | low); + low = low & 0xff; + high = high & 0xff; + return (short) (high << 8 | low); } - - + + /** bit-flipping action converting a 'little-endian' - pair of bytes to a 'big-endian' short. - - public static short readShort(byte low, byte high) - { - return (short)(high << 8 | low); - } - */ + pair of bytes to a 'big-endian' short. - /** take 16-bit short apart into two 8-bit bytes. - */ - public static byte[] shortToLEBytes(short x){ + public static short readShort(byte low, byte high) + { + return (short)(high << 8 | low); + } + */ + + /** + * take 16-bit short apart into two 8-bit bytes. + */ + public static byte[] shortToLEBytes(short x) { byte[] buf = new byte[2]; - buf[1] = (byte) (x >>> 8); - buf[0] = (byte) x;/* cast implies & 0xff */ + buf[1] = (byte) (x >>> 8); + buf[0] = (byte) x;/* cast implies & 0xff */ return buf; } - - public static byte[] toBEByteArray(double d){ - byte[] bite=new byte[8]; // A long is 8 bytes + + public static byte[] toBEByteArray(double d) { + byte[] bite = new byte[8]; // A long is 8 bytes long l = Double.doubleToLongBits(d); - int i; - long t; - t=l; // variable t will be shifted right each time thru the loop. - for(i=bite.length-1;i>-1;i--) { //High order byte will be in b[0] + int i; + long t; + t = l; // variable t will be shifted right each time thru the loop. + for (i = bite.length - 1; i > -1; i--) { //High order byte will be in b[0] long irr = (t & 0xff); - bite[i]= Integer.valueOf((int)irr).byteValue(); //get the last 8 bits into the byte array. - t=t>>8; //Shifts the long 1 byte. Same as divide by 256 - } + bite[i] = Integer.valueOf((int) irr).byteValue(); //get the last 8 bits into the byte array. + t = t >> 8; //Shifts the long 1 byte. Same as divide by 256 + } byte[] b = new byte[8]; b[0] = bite[7]; b[1] = bite[6]; @@ -477,114 +487,135 @@ public static byte[] toBEByteArray(double d){ b[5] = bite[2]; b[6] = bite[1]; b[7] = bite[0]; - return b; - } - // private boolean DEBUG = false; + return b; + } + // private boolean DEBUG = false; - /** write bytes to a file - */ - public static void writeToFile(byte[] b, String fname){ - try{ - FileOutputStream fos = new FileOutputStream(fname) ; + /** + * write bytes to a file + */ + public static void writeToFile(byte[] b, String fname) { + try { + FileOutputStream fos = new FileOutputStream(fname); fos.write(b); fos.flush(); fos.close(); - }catch(IOException e){Logger.logInfo("Error writing bytes to file in ByteTools: " + e);} - + } catch (IOException e) { + Logger.logInfo("Error writing bytes to file in ByteTools: " + e); + } + } - - /** C Longs are only 32 bits, Java Longs are 64. - This method converts a 32-bit 'C' long to a - byte array. - - Also performs 'little-endian' conversion. - */ - public byte[] cLongToLEBytesOLD(int i){ + + /** + * C Longs are only 32 bits, Java Longs are 64. + * This method converts a 32-bit 'C' long to a + * byte array. + *

                + * Also performs 'little-endian' conversion. + */ + public byte[] cLongToLEBytesOLD(int i) { //if(true)return Integer. short[] sbuf = cLongToLEShorts(i); byte[] b1 = shortToLEBytes(sbuf[0]); byte[] b2 = shortToLEBytes(sbuf[1]); - + byte[] bbuf = new byte[4]; bbuf[0] = b1[0]; bbuf[1] = b1[1]; bbuf[2] = b2[0]; bbuf[3] = b2[1]; - + // System.arraycopy(b1, 0, bbuf, 0, 2); // System.arraycopy(b2, 0, bbuf, 2, 2); return bbuf; } - - - // NICK -- let's put the following in a JUnit Test. -jm - - /** this is a good test for some of the above methods.. - byte[] bytes; - java.io.ByteArrayInputStream bis = new java.io.ByteArrayInputStream(bite); - java.io.DataInputStream dis = new java.io.DataInputStream(bis); - try { - long lon = dis.readLong(); - Logger.logInfo("pleeze stop here"); - } catch (java.io.IOException e){ Logger.logInfo("io exception in byte to long conversion" + e);} - */ + + + // NICK -- let's put the following in a JUnit Test. -jm + + /** this is a good test for some of the above methods.. + byte[] bytes; + java.io.ByteArrayInputStream bis = new java.io.ByteArrayInputStream(bite); + java.io.DataInputStream dis = new java.io.DataInputStream(bis); + try { + long lon = dis.readLong(); + Logger.logInfo("pleeze stop here"); + } catch (java.io.IOException e){ Logger.logInfo("io exception in byte to long conversion" + e);} + */ /** * generic method to update (set or clear) a short at bitNum + * * @param set * @param bitNum */ public static short updateGrBit(short grbit, boolean set, int bitNum) { - switch (bitNum) { - case 0: - if (set) grbit|= (0x1); else grbit&= (0xFFFE); - break; - case 1: - if (set) grbit|= (0x2); else grbit&= (0xFFFD); - break; - case 2: - if (set) grbit|= (0x4); else grbit&= (0xFFFB); - break; - case 3: - if (set) grbit|= (0x8); else grbit&= (0xFFF7); - break; - case 4: - if (set) grbit|= (0x10); else grbit&= (0xFFEF); - break; - case 5: - if (set) grbit|= (0x20); else grbit&= (0xFFDF); - break; - case 6: - if (set) grbit|= (0x40); else grbit&= (0xFFBF); - break; - case 7: - if (set) grbit|= (0x80); else grbit&= (0xFF7F); - break; - case 8: - if (set) grbit|= (0x100); else grbit&= (0xFEFF); - break; - case 9: - if (set) grbit|= (0x200); else grbit&= (0xFDFF); - break; - case 10: - if (set) grbit|= (0x400); else grbit&= (0xFBFF); - break; - case 11: - if (set) grbit|= (0x800); else grbit&= (0xF7FF); - break; - case 12: - if (set) grbit|= (0x1000); else grbit&= (0xEFFF); - break; - case 13: - if (set) grbit|= (0x2000); else grbit&= (0xDFFF); - break; - case 14: - if (set) grbit|= (0x4000); else grbit&= (0xBFFF); - break; - case 15: - if (set) grbit|= (0x8000); else grbit&= (0x7FFF); - break; - } - return grbit; + switch (bitNum) { + case 0: + if (set) grbit |= (0x1); + else grbit &= (0xFFFE); + break; + case 1: + if (set) grbit |= (0x2); + else grbit &= (0xFFFD); + break; + case 2: + if (set) grbit |= (0x4); + else grbit &= (0xFFFB); + break; + case 3: + if (set) grbit |= (0x8); + else grbit &= (0xFFF7); + break; + case 4: + if (set) grbit |= (0x10); + else grbit &= (0xFFEF); + break; + case 5: + if (set) grbit |= (0x20); + else grbit &= (0xFFDF); + break; + case 6: + if (set) grbit |= (0x40); + else grbit &= (0xFFBF); + break; + case 7: + if (set) grbit |= (0x80); + else grbit &= (0xFF7F); + break; + case 8: + if (set) grbit |= (0x100); + else grbit &= (0xFEFF); + break; + case 9: + if (set) grbit |= (0x200); + else grbit &= (0xFDFF); + break; + case 10: + if (set) grbit |= (0x400); + else grbit &= (0xFBFF); + break; + case 11: + if (set) grbit |= (0x800); + else grbit &= (0xF7FF); + break; + case 12: + if (set) grbit |= (0x1000); + else grbit &= (0xEFFF); + break; + case 13: + if (set) grbit |= (0x2000); + else grbit &= (0xDFFF); + break; + case 14: + if (set) grbit |= (0x4000); + else grbit &= (0xBFFF); + break; + case 15: + if (set) grbit |= (0x8000); + else grbit &= (0x7FFF); + break; + } + return grbit; } } diff --git a/src/main/java/io/starter/toolkit/CompatibleBigDecimal.java b/src/main/java/io/starter/toolkit/CompatibleBigDecimal.java index b828a11..9eb3919 100644 --- a/src/main/java/io/starter/toolkit/CompatibleBigDecimal.java +++ b/src/main/java/io/starter/toolkit/CompatibleBigDecimal.java @@ -2,90 +2,87 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.toolkit; -/** +/** * CompatibleBigDecimal.java - * - * + *

                + *

                * CompatibleBigDecimal deals with the java 1.4-1.5 transition error of BigDecimal.toString(). - * - * Prior to 1.5, BigDecimal.toString would not return Scientific Notation. JDK1.5 now allows Scientific Notation + *

                + * Prior to 1.5, BigDecimal.toString would not return Scientific Notation. JDK1.5 now allows Scientific Notation * to be returned in some cases for this method. A new method has been created, .toPlainString that mimics the - * behavior of the old .toString. As we do not know the runtime JDK, and returning correctly formatted numbers + * behavior of the old .toString. As we do not know the runtime JDK, and returning correctly formatted numbers * is critical to the functionality of OpenXLS, this class mimics 1.4 functionality for toString under either JDK. - * + *

                * A static method is used to increase performance for multiple calls. - * - * */ -import java.math.BigDecimal; import java.lang.reflect.Method; +import java.math.BigDecimal; public class CompatibleBigDecimal extends BigDecimal { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6816994951413033200L; - private static Method _methodToString = null; - + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -6816994951413033200L; + private static Method _methodToString = null; + // Create the static method we can access later. This could be .toPlainString or .toString. static { try { _methodToString = BigDecimal.class.getMethod("toPlainString", (Class[]) null); } catch (NoSuchMethodException e) { try { - _methodToString = BigDecimal.class.getMethod("toString", (Class[]) null); - }catch (NoSuchMethodException ex) { + _methodToString = BigDecimal.class.getMethod("toString", (Class[]) null); + } catch (NoSuchMethodException ex) { Logger.logWarn("Error creating toString method in CompatibleBigDecimal"); } - } + } } - + /** * Constructor **/ - public CompatibleBigDecimal(BigDecimal bd) - { - super(bd.unscaledValue(),bd.scale()); + public CompatibleBigDecimal(BigDecimal bd) { + super(bd.unscaledValue(), bd.scale()); } - + public CompatibleBigDecimal(String num) { super(num); } - + /** * Compatible toString functionality - * + * * @see java.math.BigDecimal#toString() */ public String toCompatibleString() { if (_methodToString != null) { - try { - return (String) _methodToString.invoke(this, (Object[]) null); - } catch (Exception e) { - Logger.logWarn("Error in calling CompatibleBigDecimal.toString"); - } + try { + return (String) _methodToString.invoke(this, (Object[]) null); + } catch (Exception e) { + Logger.logWarn("Error in calling CompatibleBigDecimal.toString"); + } } return null; } diff --git a/src/main/java/io/starter/toolkit/CompatibleVector.java b/src/main/java/io/starter/toolkit/CompatibleVector.java index 07f1a5f..2c46d7c 100644 --- a/src/main/java/io/starter/toolkit/CompatibleVector.java +++ b/src/main/java/io/starter/toolkit/CompatibleVector.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -29,275 +29,271 @@ /** * a Vector class designed to provide forwards compatibility for JDK1.1 * programs. - * - * - * */ public class CompatibleVector extends Vector { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6805047965683753637L; - private int change_offset = 0; - private int reindex_change_size = 1000; + /** + * serialVersionUID + */ + private static final long serialVersionUID = 6805047965683753637L; + private int change_offset = 0; + private int reindex_change_size = 1000; - /** - * reset the hints for all vector elements expense is linear to size but will - * increase accuracy of subsequent 'indexOf' calls. - */ - public void resetHints(boolean ignore_records) { - // ExcelTools.benchmark("Re-indexing CompatibleVector" + reindex_change_size++); - if (!ignore_records) { - for (int t = 0; t < this.size(); t++) { - try { - ((CompatibleVectorHints) this.get(t)).setRecordIndexHint(t); - } catch (Exception e) { - return; - } - } - } - change_offset = 0; - } + /** + * reset the hints for all vector elements expense is linear to size but will + * increase accuracy of subsequent 'indexOf' calls. + */ + public void resetHints(boolean ignore_records) { + // ExcelTools.benchmark("Re-indexing CompatibleVector" + reindex_change_size++); + if (!ignore_records) { + for (int t = 0; t < this.size(); t++) { + try { + ((CompatibleVectorHints) this.get(t)).setRecordIndexHint(t); + } catch (Exception e) { + return; + } + } + } + change_offset = 0; + } - public CompatibleVector() { - super(); - } + public CompatibleVector() { + super(); + } - public CompatibleVector(int i) { - super(i); - } + public CompatibleVector(int i) { + super(i); + } - @Override - public Iterator iterator() { - return new Itr(); - } + @Override + public Iterator iterator() { + return new Itr(); + } - int hits = 0, misses = 0; + int hits = 0, misses = 0; - /** - * if the object being checked implements index hints, the lookup can be - * performed much faster - * - * speed of lookups is affected by 'shuffling' positions of vector elements. - */ - public int indexOf(CompatibleVectorHints r) { - // return super.indexOf(r); + /** + * if the object being checked implements index hints, the lookup can be + * performed much faster + *

                + * speed of lookups is affected by 'shuffling' positions of vector elements. + */ + public int indexOf(CompatibleVectorHints r) { + // return super.indexOf(r); - int x = r.getRecordIndexHint(); - if ((x > 0) && (x < super.size())) - if (super.elementAt(x) != null) { - if (super.elementAt(x).equals(r)) { - // Logger.logInfo("hit/miss="+ hits++ + ":"+ misses); - // hits++; - return x; - } - } - x -= change_offset; - if ((x > 0) && (x < super.size())) - if (super.elementAt(x).equals(r)) { + int x = r.getRecordIndexHint(); + if ((x > 0) && (x < super.size())) + if (super.elementAt(x) != null) { + if (super.elementAt(x).equals(r)) { + // Logger.logInfo("hit/miss="+ hits++ + ":"+ misses); + // hits++; + return x; + } + } + x -= change_offset; + if ((x > 0) && (x < super.size())) + if (super.elementAt(x).equals(r)) { - // Logger.logInfo("hit/miss="+ hits++ + ":"+ misses); - // hits++; - return x; - } - int t = -1; - if (change_offset > reindex_change_size) - this.resetHints(false); - // if(x>0)t = super.indexOf(r,x); - if (x > 0) - t = super.indexOf(r); - if (t < 0) - t = super.indexOf(r); - r.setRecordIndexHint(t); - // Logger.logInfo("hit/miss="+ hits++ + ":"+ misses++); - return t; - /**/ - /* - * int idx = r.getRecordIndexHint(); int retval = -1; int recsz = this.size(); - * if(idx >= recsz)idx = recsz-1; if(idx < 0){ idx = this.indexOf((Object)r); - * r.setRecordIndexHint(idx); return idx; } if(this.get(idx).equals(r))return - * idx; else{ boolean found = false, checkhi = true, checklo = true; int hi - * =idx, lo = idx; // int hi = idx + change_offset, lo = idx + change_offset; - * //Logger.logInfo(change_offset); if(hi < 0)hi = recsz/2; if(lo > 0)lo = - * recsz/2; while(!found && (checkhi || checklo)){ if(++hi >= recsz)checkhi = - * false; if(--lo < 0)checklo = false; if(checkhi){ Object b = this.get(hi); - * if(b.equals(r)){ found = true; retval = hi; // Logger.logInfo(" hi:" + (idx - - * hi)); }else if(checklo){ Object c = this.get(lo); if(c.equals(r)){ found = - * true; retval = lo; // Logger.logInfo("lo:" + (idx - lo)); } } } } } - * Logger.logInfo(" hi:" + (idx - hi)); Logger.logInfo("lo:" + (idx - lo)); - * if((retval == -1)&&(change_offset > 0)){ this.resetHints(false); // - * Logger.logInfo("Loopy!! " + r); retval = this.indexOf(r); } - * r.setRecordIndexHint(retval); return retval; - */ - } + // Logger.logInfo("hit/miss="+ hits++ + ":"+ misses); + // hits++; + return x; + } + int t = -1; + if (change_offset > reindex_change_size) + this.resetHints(false); + // if(x>0)t = super.indexOf(r,x); + if (x > 0) + t = super.indexOf(r); + if (t < 0) + t = super.indexOf(r); + r.setRecordIndexHint(t); + // Logger.logInfo("hit/miss="+ hits++ + ":"+ misses++); + return t; + /**/ + /* + * int idx = r.getRecordIndexHint(); int retval = -1; int recsz = this.size(); + * if(idx >= recsz)idx = recsz-1; if(idx < 0){ idx = this.indexOf((Object)r); + * r.setRecordIndexHint(idx); return idx; } if(this.get(idx).equals(r))return + * idx; else{ boolean found = false, checkhi = true, checklo = true; int hi + * =idx, lo = idx; // int hi = idx + change_offset, lo = idx + change_offset; + * //Logger.logInfo(change_offset); if(hi < 0)hi = recsz/2; if(lo > 0)lo = + * recsz/2; while(!found && (checkhi || checklo)){ if(++hi >= recsz)checkhi = + * false; if(--lo < 0)checklo = false; if(checkhi){ Object b = this.get(hi); + * if(b.equals(r)){ found = true; retval = hi; // Logger.logInfo(" hi:" + (idx - + * hi)); }else if(checklo){ Object c = this.get(lo); if(c.equals(r)){ found = + * true; retval = lo; // Logger.logInfo("lo:" + (idx - lo)); } } } } } + * Logger.logInfo(" hi:" + (idx - hi)); Logger.logInfo("lo:" + (idx - lo)); + * if((retval == -1)&&(change_offset > 0)){ this.resetHints(false); // + * Logger.logInfo("Loopy!! " + r); retval = this.indexOf(r); } + * r.setRecordIndexHint(retval); return retval; + */ + } - /* */ - /** - * Index of element to be returned by subsequent call to next. - */ - int cursor = 0; + /* */ + /** + * Index of element to be returned by subsequent call to next. + */ + int cursor = 0; - /** - * Index of element returned by most recent call to next or previous. Reset to - * -1 if this element is deleted by a call to remove. - */ - int lastRet = -1; + /** + * Index of element returned by most recent call to next or previous. Reset to + * -1 if this element is deleted by a call to remove. + */ + int lastRet = -1; - /** - * overriding AbstractList so we can have concurrent mods... - * - */ - public Object next() { - // checkForComodification(); - try { - Object next = get(cursor); - lastRet = cursor++; - return next; - } catch (IndexOutOfBoundsException e) { - throw new NoSuchElementException(); - } - } + /** + * overriding AbstractList so we can have concurrent mods... + */ + public Object next() { + // checkForComodification(); + try { + Object next = get(cursor); + lastRet = cursor++; + return next; + } catch (IndexOutOfBoundsException e) { + throw new NoSuchElementException(); + } + } - @Override - public Object get(int idx) { - return super.elementAt(idx); - } + @Override + public Object get(int idx) { + return super.elementAt(idx); + } - /* - * If passed in a Double, it stores it as a Double in the vector in logical - * order (1,2,3) Returns true if all values can be represented by double and the - * element is inserted - */ - public boolean addOrderedDouble(Double obj) { - double d = obj.doubleValue(); - try { - for (int i = 0; i < super.size(); i++) { - Double dd = (Double) super.elementAt(i); - if (dd.doubleValue() > d) { - super.insertElementAt(obj, i); - return true; - } - } - } catch (Exception e) { - return false; - } - super.add(obj); - return true; - } + /* + * If passed in a Double, it stores it as a Double in the vector in logical + * order (1,2,3) Returns true if all values can be represented by double and the + * element is inserted + */ + public boolean addOrderedDouble(Double obj) { + double d = obj.doubleValue(); + try { + for (int i = 0; i < super.size(); i++) { + Double dd = (Double) super.elementAt(i); + if (dd.doubleValue() > d) { + super.insertElementAt(obj, i); + return true; + } + } + } catch (Exception e) { + return false; + } + super.add(obj); + return true; + } - /* - * Returns the last object in the collection - * - */ - public Object last() { - return super.elementAt(super.size() - 1); - } + /* + * Returns the last object in the collection + * + */ + public Object last() { + return super.elementAt(super.size() - 1); + } - public boolean add(CompatibleVectorHints obj) { - this.change_offset++; // - int idx = super.size(); - if (obj != null) - obj.setRecordIndexHint(idx); - try { - super.insertElementAt(obj, idx); - return true; - } catch (Exception e) { - return false; - } - } + public boolean add(CompatibleVectorHints obj) { + this.change_offset++; // + int idx = super.size(); + if (obj != null) + obj.setRecordIndexHint(idx); + try { + super.insertElementAt(obj, idx); + return true; + } catch (Exception e) { + return false; + } + } - public void add(int idx, CompatibleVectorHints obj) { - this.change_offset++;// - obj.setRecordIndexHint(idx); - super.insertElementAt(obj, idx); - } + public void add(int idx, CompatibleVectorHints obj) { + this.change_offset++;// + obj.setRecordIndexHint(idx); + super.insertElementAt(obj, idx); + } - public void addAll(CompatibleVector cv) { - for (int i = 0; i < cv.size(); i++) { - Object b = cv.get(i); - if (b instanceof CompatibleVectorHints) { - this.add((CompatibleVectorHints) b); - } else { - this.add(b); - } - } - } + public void addAll(CompatibleVector cv) { + for (int i = 0; i < cv.size(); i++) { + Object b = cv.get(i); + if (b instanceof CompatibleVectorHints) { + this.add((CompatibleVectorHints) b); + } else { + this.add(b); + } + } + } - @Override - public boolean remove(Object obj) { - if (super.remove(obj)) { - this.change_offset--; - return true; - } - return false; - } + @Override + public boolean remove(Object obj) { + if (super.remove(obj)) { + this.change_offset--; + return true; + } + return false; + } - @Override - public void clear() { - this.change_offset = 0; - super.removeAllElements(); - } + @Override + public void clear() { + this.change_offset = 0; + super.removeAllElements(); + } - @Override - public Object[] toArray() { - Object[] obj = new Object[super.size()]; - for (int i = 0; i < super.size(); i++) { - obj[i] = super.elementAt(i); - } - return obj; - } + @Override + public Object[] toArray() { + Object[] obj = new Object[super.size()]; + for (int i = 0; i < super.size(); i++) { + obj[i] = super.elementAt(i); + } + return obj; + } - @Override - public Object[] toArray(Object[] obj) { - for (int i = 0; i < super.size(); i++) { - try { - obj[i] = super.elementAt(i); - } catch (Exception e) { - Logger.logInfo("CompatibleVector.toArray() failed."); - } - } - return obj; - } + @Override + public Object[] toArray(Object[] obj) { + for (int i = 0; i < super.size(); i++) { + try { + obj[i] = super.elementAt(i); + } catch (Exception e) { + Logger.logInfo("CompatibleVector.toArray() failed."); + } + } + return obj; + } - private class Itr implements Iterator { - /** - * Index of element to be returned by subsequent call to next. - */ - int cursor = 0; + private class Itr implements Iterator { + /** + * Index of element to be returned by subsequent call to next. + */ + int cursor = 0; - /** - * Index of element returned by most recent call to next or previous. Reset to - * -1 if this element is deleted by a call to remove. - */ - int lastRet = -1; + /** + * Index of element returned by most recent call to next or previous. Reset to + * -1 if this element is deleted by a call to remove. + */ + int lastRet = -1; - /** - * The modCount value that the iterator believes that the backing List should - * have. If this expectation is violated, the iterator has detected concurrent - * modification. - */ - int expectedModCount = modCount; + /** + * The modCount value that the iterator believes that the backing List should + * have. If this expectation is violated, the iterator has detected concurrent + * modification. + */ + int expectedModCount = modCount; - public boolean hasNext() { - return cursor != size(); - } + public boolean hasNext() { + return cursor != size(); + } - public Object next() { - Object next = get(cursor); - lastRet = cursor++; - return next; - } + public Object next() { + Object next = get(cursor); + lastRet = cursor++; + return next; + } - public void remove() { - if (lastRet == -1) - throw new IllegalStateException(); + public void remove() { + if (lastRet == -1) + throw new IllegalStateException(); - CompatibleVector.this.remove(lastRet); - if (lastRet < cursor) - cursor--; - lastRet = -1; - expectedModCount = modCount; - } + CompatibleVector.this.remove(lastRet); + if (lastRet < cursor) + cursor--; + lastRet = -1; + expectedModCount = modCount; + } - } + } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/CompatibleVectorHints.java b/src/main/java/io/starter/toolkit/CompatibleVectorHints.java index d0fb59a..bbdfdda 100644 --- a/src/main/java/io/starter/toolkit/CompatibleVectorHints.java +++ b/src/main/java/io/starter/toolkit/CompatibleVectorHints.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,16 +22,17 @@ */ package io.starter.toolkit; -public interface CompatibleVectorHints -{ - /** provide a hint to the CompatibleVector - about this objects likely position. - */ - public int getRecordIndexHint(); - - - /** set index information about this - objects likely position. - */ - public void setRecordIndexHint(int i); +public interface CompatibleVectorHints { + /** + * provide a hint to the CompatibleVector + * about this objects likely position. + */ + int getRecordIndexHint(); + + + /** + * set index information about this + * objects likely position. + */ + void setRecordIndexHint(int i); } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.java b/src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.java index ef8d8ee..8d36e41 100644 --- a/src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.java +++ b/src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.java @@ -2,69 +2,66 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ -/** +/** * TempFileManager.java - * - * + *

                + *

                * Feb 27, 2012 - * - * */ package io.starter.toolkit; import java.io.File; import java.io.IOException; -import java.util.Properties; -/** +/** * */ -public class DefaultTempFileGeneratorImpl implements TempFileGenerator{ +public class DefaultTempFileGeneratorImpl implements TempFileGenerator { + + + public File createTempFile(String prefix, String extension) + throws IOException { + String tmpdir = System.getProperty("java.io.tmpdir"); + String lastchar = tmpdir.substring(tmpdir.length() - 1); + if (!lastchar.equals(File.separator)) { + tmpdir = tmpdir + File.separator; + } + tmpdir += "extentech/"; + File target = null; + try { + File tdir = new File(tmpdir); + if (!tdir.exists()) { + tdir.mkdirs(); + } + tdir.deleteOnExit(); + + target = File.createTempFile(prefix, extension, tdir); + } catch (Exception e) { + Logger.logInfo("Could not access temp dir: " + tmpdir);// could not create the temp folder fallback to unspecified temp file + target = File.createTempFile(prefix, extension); + } - - public File createTempFile(String prefix, String extension) - throws IOException{ - String tmpdir = System.getProperty("java.io.tmpdir"); - String lastchar = tmpdir.substring(tmpdir.length()-1, tmpdir.length()); - if (!lastchar.equals(File.separator)){ - tmpdir = tmpdir + File.separator; - } - tmpdir += "extentech/"; - File target = null; - try{ - File tdir = new File(tmpdir); - if(!tdir.exists()){ - tdir.mkdirs(); - } - tdir.deleteOnExit(); - - target = File.createTempFile(prefix, extension, tdir); - }catch(Exception e){ - Logger.logInfo("Could not access temp dir: " + tmpdir);// could not create the temp folder fallback to unspecified temp file - target = File.createTempFile(prefix, extension); - } - - target.deleteOnExit(); - // target.delete(); // triggers the deleteonexit - return target; - } + target.deleteOnExit(); + // target.delete(); // triggers the deleteonexit + return target; + } } diff --git a/src/main/java/io/starter/toolkit/DelimitedValuesParser.java b/src/main/java/io/starter/toolkit/DelimitedValuesParser.java index a26c403..620b96d 100644 --- a/src/main/java/io/starter/toolkit/DelimitedValuesParser.java +++ b/src/main/java/io/starter/toolkit/DelimitedValuesParser.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -25,70 +25,80 @@ import java.io.IOException; import java.io.Reader; -/** Stream parser for delimiter-separated values formats. +/** + * Stream parser for delimiter-separated values formats. * These include comma separated values (CSV) and tab separated values (TSV). */ public class DelimitedValuesParser { - /** Represents the type of a token. */ - public enum Token { VALUE, NEWLINE, EOF } - - private Reader source; - - /** The delimiter used to separate values. */ - private char delimiter = '\t'; - - /** Contains the current value, if any. */ - private StringBuilder value = new StringBuilder(); - - /** The last token returned. */ - private Token current = null; - - /** The next token to be returned. - * This is used when a single character ends a token and is itself a token. - */ - private Token next = null; - - public DelimitedValuesParser (Reader source) { - this.source = source; - } - - public Token next() - throws IOException { - // reset the value builder - value.setLength( 0 ); - - // if there's a token waiting, return it - if (next != null) { - current = next; - next = null; - return current; - } - - while (true) { - int read = source.read(); - if (read == -1) { - if (value.length() == 0) return current = Token.EOF; - else return current = Token.VALUE; - } - - if (read == delimiter) return current = Token.VALUE; - - if (read == '\n') { - if (value.length() > 0) { - if (value.charAt( value.length() - 1 ) == '\r') - value.setLength( value.length() - 1 ); - next = Token.NEWLINE; - return current = Token.VALUE; - } else { - return current = Token.NEWLINE; - } - } - - value.append( (char) read ); - } - } - - public String getValue() { - return value.length() > 0 ? value.toString() : null; - } + /** + * Represents the type of a token. + */ + public enum Token {VALUE, NEWLINE, EOF} + + private Reader source; + + /** + * The delimiter used to separate values. + */ + private char delimiter = '\t'; + + /** + * Contains the current value, if any. + */ + private StringBuilder value = new StringBuilder(); + + /** + * The last token returned. + */ + private Token current = null; + + /** + * The next token to be returned. + * This is used when a single character ends a token and is itself a token. + */ + private Token next = null; + + public DelimitedValuesParser(Reader source) { + this.source = source; + } + + public Token next() + throws IOException { + // reset the value builder + value.setLength(0); + + // if there's a token waiting, return it + if (next != null) { + current = next; + next = null; + return current; + } + + while (true) { + int read = source.read(); + if (read == -1) { + if (value.length() == 0) return current = Token.EOF; + else return current = Token.VALUE; + } + + if (read == delimiter) return current = Token.VALUE; + + if (read == '\n') { + if (value.length() > 0) { + if (value.charAt(value.length() - 1) == '\r') + value.setLength(value.length() - 1); + next = Token.NEWLINE; + return current = Token.VALUE; + } else { + return current = Token.NEWLINE; + } + } + + value.append((char) read); + } + } + + public String getValue() { + return value.length() > 0 ? value.toString() : null; + } } diff --git a/src/main/java/io/starter/toolkit/ExceptionListener.java b/src/main/java/io/starter/toolkit/ExceptionListener.java index 63fc4a4..790492c 100644 --- a/src/main/java/io/starter/toolkit/ExceptionListener.java +++ b/src/main/java/io/starter/toolkit/ExceptionListener.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,9 +23,8 @@ package io.starter.toolkit; +public interface ExceptionListener { -public interface ExceptionListener{ - - - public void exceptionThrown(Exception e); + + void exceptionThrown(Exception e); } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/ExtenClassLoader.java b/src/main/java/io/starter/toolkit/ExtenClassLoader.java index d463639..798b3dc 100644 --- a/src/main/java/io/starter/toolkit/ExtenClassLoader.java +++ b/src/main/java/io/starter/toolkit/ExtenClassLoader.java @@ -2,91 +2,91 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ - package io.starter.toolkit; +package io.starter.toolkit; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -public class ExtenClassLoader extends java.lang.ClassLoader -{ +public class ExtenClassLoader extends java.lang.ClassLoader { private String targetClassName; - public ExtenClassLoader(String target) { targetClassName = target; } - + public ExtenClassLoader(String target) { + targetClassName = target; + } + public ExtenClassLoader() { - } - + } + private String wd = ""; - public void setDirectory(String _wd){ - wd = _wd; + + public void setDirectory(String _wd) { + wd = _wd; } - - protected byte[] loadClassFromFile(String name) - { - byte[] classBytes = null; - try - { - File file = null; + + protected byte[] loadClassFromFile(String name) { + byte[] classBytes = null; + try { + File file = null; FileInputStream stream = null; - // name = name.substring(name.indexOf(wd)+wd.length()); // strip the working directory - name = this.wd + "/"+name; + // name = name.substring(name.indexOf(wd)+wd.length()); // strip the working directory + name = this.wd + "/" + name; name = StringTool.replaceChars(".", name, "/"); - file = new File(name + ".class"); - classBytes = new byte[(int)file.length()]; - stream = new FileInputStream(file); - stream.read(classBytes); + file = new File(name + ".class"); + classBytes = new byte[(int) file.length()]; + stream = new FileInputStream(file); + stream.read(classBytes); stream.close(); - } - catch(IOException io) { } - return classBytes; - } + } catch (IOException io) { + } + return classBytes; + } - public synchronized Class loadClass(String name) throws ClassNotFoundException - { + public synchronized Class loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } - - public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException - { - Class loadedClass; - byte[] bytes; - if( ! name.equals(targetClassName)) - { - try - { - loadedClass = super.findSystemClass(name); - return loadedClass; - } - catch (ClassNotFoundException e) { } - } - bytes = loadClassFromFile(name); - if (bytes == null) { throw new ClassNotFoundException(); } - loadedClass = defineClass(name, bytes, 0, bytes.length); - if (loadedClass == null) { + + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class loadedClass; + byte[] bytes; + if (!name.equals(targetClassName)) { + try { + loadedClass = super.findSystemClass(name); + return loadedClass; + } catch (ClassNotFoundException e) { + } + } + bytes = loadClassFromFile(name); + if (bytes == null) { + throw new ClassNotFoundException(); + } + loadedClass = defineClass(name, bytes, 0, bytes.length); + if (loadedClass == null) { Logger.logInfo("Class cannot be loaded: " + name); - throw new ClassFormatError(); - } - if (resolve) { resolveClass(loadedClass); } + throw new ClassFormatError(); + } + if (resolve) { + resolveClass(loadedClass); + } - return loadedClass; - } + return loadedClass; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/FastAddVector.java b/src/main/java/io/starter/toolkit/FastAddVector.java index 2c82bd7..697a52a 100644 --- a/src/main/java/io/starter/toolkit/FastAddVector.java +++ b/src/main/java/io/starter/toolkit/FastAddVector.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -28,151 +28,145 @@ /** * a Vector class designed to provide forwards compatibility for JDK1.1 * programs. - * - * + *

                + *

                * // add; toArray; iterator; insert; get; indexOf; remove // TreeList = 1260 * 7360; 3080; 160; 170; 3400; 170; // ArrayList = 220 1480; 1760; 6870; 50; * 1540; 7200; // LinkedList = 270 7360; 3350; 55860; 290720; 2910; 55200; - * - * */ public class FastAddVector extends SpecialArrayList implements java.io.Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5615615290731997512L; - - public FastAddVector() { - super(); - } - - public FastAddVector(int i) { - // super(i); - super(); - } - - @Override - public Iterator iterator() { - return new Itr(); - } - - int hits = 0, misses = 0; - - /* - * If passed in a Double, it stores it as a Double in the vector in logical - * order (1,2,3) Returns true if all values can be represented by double and the - * element is inserted - */ - public boolean addOrderedDouble(Double obj) { - double d = obj.doubleValue(); - try { - for (int i = 0; i < super.size(); i++) { - Double dd = (Double) super.get(i); - if (dd.doubleValue() > d) { - super.add(i, obj); - return true; - } - } - } catch (Exception e) { - return false; - } - super.add(obj); - return true; - } - - /* - * Returns the last object in the collection - * - */ - public Object last() { - return super.get(super.size() - 1); - } - - public void add(int idx, CompatibleVectorHints obj) { - obj.setRecordIndexHint(idx); - super.add(idx, obj); - } - - @Override - public boolean remove(Object obj) { - if (super.remove(obj)) - return true; - return false; - } - - @Override - public void clear() { - super.clear(); - } - - @Override - public Object[] toArray() { - if (true) - return elementData; - Object[] obj = new Object[super.size()]; - for (int i = 0; i < super.size(); i++) { - obj[i] = super.get(i); - } - return obj; - } - - public void removeAllElements() { - super.clear(); - } - - /** - * - * - * @param obar - */ - public void copyInto(Object[] obar) { - for (int x = 0; x < obar.length; x++) { - super.add(obar[x]); - } - } - - public void insertElementAt(Object ob, int i) { - super.add(i, ob); - } - - public Object lastElement() { - return super.get(super.size() - 1); - } - - public Enumeration elements() { - FastAddVectorEnumerator cve = new FastAddVectorEnumerator(this); - return cve; - } - - public Object elementAt(int t) { - return super.get(t); - } - - @Override - public Object[] toArray(Object[] obj) { - for (int i = 0; i < super.size(); i++) { - obj[i] = super.get(i); - } - return obj; - } - - final class FastAddVectorEnumerator implements Enumeration { - - private FastAddVector it = null; - int x = 0; - - FastAddVectorEnumerator(FastAddVector itx) { - it = itx; - } - - public Object nextElement() { - return it.elementAt(x++); - } - - public boolean hasMoreElements() { - return (x < it.size()); - } - - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = -5615615290731997512L; + + public FastAddVector() { + super(); + } + + public FastAddVector(int i) { + // super(i); + super(); + } + + @Override + public Iterator iterator() { + return new Itr(); + } + + int hits = 0, misses = 0; + + /* + * If passed in a Double, it stores it as a Double in the vector in logical + * order (1,2,3) Returns true if all values can be represented by double and the + * element is inserted + */ + public boolean addOrderedDouble(Double obj) { + double d = obj.doubleValue(); + try { + for (int i = 0; i < super.size(); i++) { + Double dd = (Double) super.get(i); + if (dd.doubleValue() > d) { + super.add(i, obj); + return true; + } + } + } catch (Exception e) { + return false; + } + super.add(obj); + return true; + } + + /* + * Returns the last object in the collection + * + */ + public Object last() { + return super.get(super.size() - 1); + } + + public void add(int idx, CompatibleVectorHints obj) { + obj.setRecordIndexHint(idx); + super.add(idx, obj); + } + + @Override + public boolean remove(Object obj) { + return super.remove(obj); + } + + @Override + public void clear() { + super.clear(); + } + + @Override + public Object[] toArray() { + if (true) + return elementData; + Object[] obj = new Object[super.size()]; + for (int i = 0; i < super.size(); i++) { + obj[i] = super.get(i); + } + return obj; + } + + public void removeAllElements() { + super.clear(); + } + + /** + * @param obar + */ + public void copyInto(Object[] obar) { + for (int x = 0; x < obar.length; x++) { + super.add(obar[x]); + } + } + + public void insertElementAt(Object ob, int i) { + super.add(i, ob); + } + + public Object lastElement() { + return super.get(super.size() - 1); + } + + public Enumeration elements() { + FastAddVectorEnumerator cve = new FastAddVectorEnumerator(this); + return cve; + } + + public Object elementAt(int t) { + return super.get(t); + } + + @Override + public Object[] toArray(Object[] obj) { + for (int i = 0; i < super.size(); i++) { + obj[i] = super.get(i); + } + return obj; + } + + final class FastAddVectorEnumerator implements Enumeration { + + private FastAddVector it = null; + int x = 0; + + FastAddVectorEnumerator(FastAddVector itx) { + it = itx; + } + + public Object nextElement() { + return it.elementAt(x++); + } + + public boolean hasMoreElements() { + return (x < it.size()); + } + + } } diff --git a/src/main/java/io/starter/toolkit/FastGetVector.java b/src/main/java/io/starter/toolkit/FastGetVector.java index f666fd8..232c0e3 100644 --- a/src/main/java/io/starter/toolkit/FastGetVector.java +++ b/src/main/java/io/starter/toolkit/FastGetVector.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,168 +22,170 @@ */ package io.starter.toolkit; -import java.util.*; - - -/** a Vector class designed to provide forwards compatibility - for JDK1.1 programs. - - -*/ -public class FastGetVector extends ArrayList -{ - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6701901995748359720L; - private int change_offset = 0; - private int reindex_change_size = 1000; - - /** reset the hints for all vector elements - expense is linear to size but will increase - accuracy of subsequent 'indexOf' calls. - */ - public void resetHints(boolean ignore_records){ - // ExcelTools.benchmark("Re-indexing CompatibleVector" + reindex_change_size++); - if(!ignore_records){ - for(int t = 0;td){ - super.add(i,obj); - return true; - } - } - }catch(Exception e){ - return false; - } - super.add(obj); - return true; - } - - /* - * Returns the last object in the collection - * - */ - public Object last(){ - return super.get(super.size()-1); - } - - - public void add(int idx, CompatibleVectorHints obj){ - this.change_offset++;// - obj.setRecordIndexHint(idx); - super.add( idx, obj); - } -/* - public void addAll(CompatibleVector cv){ - for(int i=0;i elements(){ - FastGetVectorEnumerator cve = new FastGetVectorEnumerator(this); - return cve; - } - - public Object elementAt(int t){ - return super.get(t); - } - - public Object[] toArray(Object[] obj){ - for(int i=0;i { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -6701901995748359720L; + private int change_offset = 0; + private int reindex_change_size = 1000; + + /** + * reset the hints for all vector elements + * expense is linear to size but will increase + * accuracy of subsequent 'indexOf' calls. + */ + public void resetHints(boolean ignore_records) { + // ExcelTools.benchmark("Re-indexing CompatibleVector" + reindex_change_size++); + if (!ignore_records) { + for (int t = 0; t < this.size(); t++) { + try { + ((CompatibleVectorHints) this.get(t)).setRecordIndexHint(t); + } catch (Exception e) { + return; + } + } + } + change_offset = 0; + } + + public FastGetVector() { + super(); + } + + public FastGetVector(int i) { + // super(i); + super(); + } + + int hits = 0, misses = 0; + + /* + * If passed in a Double, it stores it as a Double in the vector + * in logical order (1,2,3) + * Returns true if all values can be represented by double and the element is inserted + */ + public boolean addOrderedDouble(Double obj) { + double d = obj.doubleValue(); + try { + for (int i = 0; i < super.size(); i++) { + Double dd = (Double) super.get(i); + if (dd.doubleValue() > d) { + super.add(i, obj); + return true; + } + } + } catch (Exception e) { + return false; + } + super.add(obj); + return true; + } + + /* + * Returns the last object in the collection + * + */ + public Object last() { + return super.get(super.size() - 1); + } + + + public void add(int idx, CompatibleVectorHints obj) { + this.change_offset++;// + obj.setRecordIndexHint(idx); + super.add(idx, obj); + } + + /* + public void addAll(CompatibleVector cv){ + for(int i=0;i elements() { + FastGetVectorEnumerator cve = new FastGetVectorEnumerator(this); + return cve; + } + + public Object elementAt(int t) { + return super.get(t); + } + + public Object[] toArray(Object[] obj) { + for (int i = 0; i < super.size(); i++) { + obj[i] = super.get(i); + } + return obj; + } + } -final class FastGetVectorEnumerator implements Enumeration{ - - private FastGetVector it = null; - int x=0; - - FastGetVectorEnumerator(FastGetVector itx){ - it = itx; - } - - public Object nextElement(){ - return it.elementAt(x++); - } - - public boolean hasMoreElements(){ - return (x { + + private FastGetVector it = null; + int x = 0; + + FastGetVectorEnumerator(FastGetVector itx) { + it = itx; + } + + public Object nextElement() { + return it.elementAt(x++); + } + + public boolean hasMoreElements() { + return (x < it.size()); + } + } diff --git a/src/main/java/io/starter/toolkit/FileFilter.java b/src/main/java/io/starter/toolkit/FileFilter.java index 279cf2b..7de58a4 100644 --- a/src/main/java/io/starter/toolkit/FileFilter.java +++ b/src/main/java/io/starter/toolkit/FileFilter.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -30,31 +30,26 @@ import java.io.File; -/** - * - * +/** * @ */ public class FileFilter implements java.io.FileFilter { String fix = "*"; // the pattern to match - - /** - * - * + + /** + * */ public FileFilter(String fx) { this.fix = fx; } - /** - * + /** * @see java.io.FileFilter#accept(java.io.File) */ public boolean accept(File pathname) { String fn = pathname.getName(); - if(fn.endsWith(fix))return true; - return false; + return fn.endsWith(fix); } public static void main(String[] args) { diff --git a/src/main/java/io/starter/toolkit/GenericRecycleBin.java b/src/main/java/io/starter/toolkit/GenericRecycleBin.java index ccb4b4e..5a950ff 100644 --- a/src/main/java/io/starter/toolkit/GenericRecycleBin.java +++ b/src/main/java/io/starter/toolkit/GenericRecycleBin.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,209 +22,198 @@ */ package io.starter.toolkit; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Stack; -import java.util.Vector; +import java.util.*; /** * A recycling cache, items are checked at intervals - * - * - * */ public abstract class GenericRecycleBin extends java.lang.Thread - implements Map, io.starter.toolkit.RecycleBin { - protected Map map = new java.util.HashMap(); - protected Vector active = new Vector(); - protected Stack spares = new Stack(); - - /** - * add an item - */ - public void addItem(Recyclable r) throws RecycleBinFullException { - if ((MAXITEMS == -1) || (map.size() < MAXITEMS)) { - addItem(Integer.valueOf(map.size()), r); - } else { - throw new RecycleBinFullException(); - } - } - - /** - * returns number of items in cache - * - * - * @return - */ - public int getNumItems() { - return active.size(); - } - - public void addItem(Object key, Recyclable r) throws RecycleBinFullException { - // recycle(); - if ((MAXITEMS == -1) || (map.size() < MAXITEMS)) { - active.add(r); - map.put(key, r); - - } else { - throw new RecycleBinFullException(); - } - } - - /** - * iterate all active items and try to recycle - */ - public synchronized void recycle() { - Recyclable[] rs = new Recyclable[active.size()]; - active.copyInto(rs); - for (int t = 0; t < rs.length; t++) { - try { - Recyclable rb = rs[t]; - if (!rb.inUse()) { - // recycle it - rb.recycle(); - - // remove from active and lookup - active.remove(rb); - map.remove(rb); - - // put in spares - spares.push(rb); - - } - } catch (Exception ex) { - Logger.logErr("recycle failed", ex); - } - } - - } - - public void empty() { - map.clear(); - active.clear(); - } - - public synchronized List getAll() { - return active; - } - - /** - * returns a new or recycled item from the spares pool - * - * - * @see io.starter.toolkit.RecycleBin#getItem() - */ - public synchronized Recyclable getItem() throws RecycleBinFullException { - Recyclable active = null; - // spares contains the recycled - if (spares.size() > 0) { - active = spares.pop(); - addItem(active); - return active; - } - recycle(); - - // technically infinite loop until exception thrown - return getItem(); - } - - protected int MAXITEMS = -1; // no limit is default - - /** - * max number of items to be put in this bin. - * - */ - public void setMaxItems(int i) { - MAXITEMS = i; - } - - public int getMaxItems() { - return MAXITEMS; - } - - public int getSpareCount() { - return spares.size(); - } - - public GenericRecycleBin() { - } - - public void clear() { - map.clear(); - active.clear(); - } - - public boolean containsKey(Object key) { - return map.containsKey(key); - - } - - public boolean containsValue(Object value) { - return map.containsValue(value); - } - - public Set entrySet() { - return map.entrySet(); - } - - @Override - public boolean equals(Object o) { - return map.equals(o); - } - - public Object get(Object key) { - return map.get(key); - } - - @Override - public int hashCode() { - return map.hashCode(); - } - - public boolean isEmpty() { - return map.isEmpty(); - } - - public Set keySet() { - return map.keySet(); - } - - public Object put(Object arg0, Object arg1) { - active.add(arg1); - return map.put(arg0, arg1); - } - - public void putAll(Map arg0) { - active.addAll(arg0.entrySet()); - map.putAll(arg0); - } - - public Object remove(Object key) { - active.remove(map.get(key)); - return map.remove(key); - } - - public int size() { - return map.size(); - } - - @Override - public String toString() { - return map.toString(); - } - - public Collection values() { - return map.values(); - } - - public java.util.Map getMap() { - return map; - } - - public void setMap(java.util.HashMap _map) { - map = _map; - } + implements Map, io.starter.toolkit.RecycleBin { + protected Map map = new java.util.HashMap(); + protected Vector active = new Vector(); + protected Stack spares = new Stack(); + + /** + * add an item + */ + public void addItem(Recyclable r) throws RecycleBinFullException { + if ((MAXITEMS == -1) || (map.size() < MAXITEMS)) { + addItem(Integer.valueOf(map.size()), r); + } else { + throw new RecycleBinFullException(); + } + } + + /** + * returns number of items in cache + * + * @return + */ + public int getNumItems() { + return active.size(); + } + + public void addItem(Object key, Recyclable r) throws RecycleBinFullException { + // recycle(); + if ((MAXITEMS == -1) || (map.size() < MAXITEMS)) { + active.add(r); + map.put(key, r); + + } else { + throw new RecycleBinFullException(); + } + } + + /** + * iterate all active items and try to recycle + */ + public synchronized void recycle() { + Recyclable[] rs = new Recyclable[active.size()]; + active.copyInto(rs); + for (int t = 0; t < rs.length; t++) { + try { + Recyclable rb = rs[t]; + if (!rb.inUse()) { + // recycle it + rb.recycle(); + + // remove from active and lookup + active.remove(rb); + map.remove(rb); + + // put in spares + spares.push(rb); + + } + } catch (Exception ex) { + Logger.logErr("recycle failed", ex); + } + } + + } + + public void empty() { + map.clear(); + active.clear(); + } + + public synchronized List getAll() { + return active; + } + + /** + * returns a new or recycled item from the spares pool + * + * @see io.starter.toolkit.RecycleBin#getItem() + */ + public synchronized Recyclable getItem() throws RecycleBinFullException { + Recyclable active = null; + // spares contains the recycled + if (spares.size() > 0) { + active = spares.pop(); + addItem(active); + return active; + } + recycle(); + + // technically infinite loop until exception thrown + return getItem(); + } + + protected int MAXITEMS = -1; // no limit is default + + /** + * max number of items to be put in this bin. + */ + public void setMaxItems(int i) { + MAXITEMS = i; + } + + public int getMaxItems() { + return MAXITEMS; + } + + public int getSpareCount() { + return spares.size(); + } + + public GenericRecycleBin() { + } + + public void clear() { + map.clear(); + active.clear(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key); + + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public Set entrySet() { + return map.entrySet(); + } + + @Override + public boolean equals(Object o) { + return map.equals(o); + } + + public Object get(Object key) { + return map.get(key); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public Set keySet() { + return map.keySet(); + } + + public Object put(Object arg0, Object arg1) { + active.add(arg1); + return map.put(arg0, arg1); + } + + public void putAll(Map arg0) { + active.addAll(arg0.entrySet()); + map.putAll(arg0); + } + + public Object remove(Object key) { + active.remove(map.get(key)); + return map.remove(key); + } + + public int size() { + return map.size(); + } + + @Override + public String toString() { + return map.toString(); + } + + public Collection values() { + return map.values(); + } + + public java.util.Map getMap() { + return map; + } + + public void setMap(java.util.HashMap _map) { + map = _map; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/InFile.java b/src/main/java/io/starter/toolkit/InFile.java index 96ae218..0c9dfc3 100644 --- a/src/main/java/io/starter/toolkit/InFile.java +++ b/src/main/java/io/starter/toolkit/InFile.java @@ -2,34 +2,34 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.toolkit; + import java.io.*; -public class InFile extends DataInputStream -{ - +public class InFile extends DataInputStream { + StringBuffer sb = new StringBuffer(); public InFile(String filename) - throws FileNotFoundException { + throws FileNotFoundException { super(new BufferedInputStream(new FileInputStream(new File(filename)))); } @@ -40,42 +40,51 @@ public InFile(String filename) */ public InFile(File file) - throws FileNotFoundException { + throws FileNotFoundException { this(file.getPath()); } - + /** * Reads File from Disk - * + * * @param fname path to file - */ - public String readFile() - { - try {while (this.available() != 0){sb.append(this.readLine());}} - catch(FileNotFoundException e){ Logger.logInfo("FNF Exception in InFile: " + e);} - catch(IOException e){Logger.logInfo("IO Exception in InFile: " + e);} - return sb.toString(); - } - + */ + public String readFile() { + try { + while (this.available() != 0) { + sb.append(this.readLine()); + } + } catch (FileNotFoundException e) { + Logger.logInfo("FNF Exception in InFile: " + e); + } catch (IOException e) { + Logger.logInfo("IO Exception in InFile: " + e); + } + return sb.toString(); + } + - /** Gets a byte arrray from a file - * @param file File the file to get bytes from - * @return byte[] Returns byte[] array file contents - */ - public static byte[] getBytesFromFile(File file) throws IOException { - InputStream fis = new FileInputStream(file); - long length = file.length(); - byte[] ret = new byte[(int)length]; - int offset = 0; - int numRead = 0; - while ( (offset < ret.length)&&( (numRead=fis.read(ret, offset, ret.length-offset)) >= 0) ) { - offset += numRead; - } - if (offset < ret.length) {throw new IOException("Read file failed -- all bytes not retreived. " + file.getName());} - fis.close(); - return ret; + /** + * Gets a byte arrray from a file + * + * @param file File the file to get bytes from + * @return byte[] Returns byte[] array file contents + */ + public static byte[] getBytesFromFile(File file) throws IOException { + InputStream fis = new FileInputStream(file); + long length = file.length(); + byte[] ret = new byte[(int) length]; + int offset = 0; + int numRead = 0; + while ((offset < ret.length) && ((numRead = fis.read(ret, offset, ret.length - offset)) >= 0)) { + offset += numRead; + } + if (offset < ret.length) { + throw new IOException("Read file failed -- all bytes not retreived. " + file.getName()); + } + fis.close(); + return ret; + + } - } - } diff --git a/src/main/java/io/starter/toolkit/IndirectOutputStream.java b/src/main/java/io/starter/toolkit/IndirectOutputStream.java index d6a160d..d96a2f6 100644 --- a/src/main/java/io/starter/toolkit/IndirectOutputStream.java +++ b/src/main/java/io/starter/toolkit/IndirectOutputStream.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -26,149 +26,162 @@ import java.io.OutputStream; import java.util.concurrent.locks.ReentrantReadWriteLock; -/** An IndirectOutputStream forwards all requests unmodified to +/** + * An IndirectOutputStream forwards all requests unmodified to * another output stream which may be changed at runtime. By default an * IOException will be thrown if no sink is configured when a * request is received. The stream may be configured to drop such requests * instead. */ public class IndirectOutputStream -extends OutputStream { - private OutputStream sink; - private boolean discardOnNull; - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - /** Creates a new IndirectOutputStream with no sink which - * fails when no sink is present. - */ - public IndirectOutputStream () { - this( null ); - } - - /** Creates a new IndirectOutputStream with the given sink - * which fails when no sink is present. - * @param sink the initial sink - */ - public IndirectOutputStream (OutputStream sink) { - this( sink, false ); - } - - /** Creates a new IndirectOutputStream with the given sink - * and behavior when no sink is present. - * - * @param sink the initial sink - * @param discard whether to discard requests when no sink is present - */ - public IndirectOutputStream (OutputStream sink, boolean discard) { - this.sink = sink; - this.discardOnNull = discard; - } - - /** Gets the currently configured sink. - * @return the stream to which requests are currently being forwarded - * or null if no sink present - */ - public OutputStream getSink() { - // don't bother with a read lock, a single read is atomic - return sink; - } - - /** Sets the stream to which requests are forwarded. - * @param sink the stream to which requests should be forwarded - * or null to remove the current sink - */ - public void setSink (OutputStream sink) { - lock.writeLock().lock(); - try { - this.sink = sink; - } finally { - lock.writeLock().unlock(); - } - } - - /** Gets the current behavior when no wink is present. - * @return whether requests will be discarded when no sink is present - */ - public boolean discardOnNoSink() { - // don't bother with a read lock, a single read is atomic - return discardOnNull; - } - - /** Sets the behavior when no sink is present. - * @param discard whether to discard requests when no sink is present - */ - public void discardOnNoSink (boolean discard) { - // don't bother with a write lock, a single write is atomic - // as are all uses of this field - this.discardOnNull = discard; - } - - private boolean checkSink() - throws IOException { - if (null == sink) { - if (discardOnNull) return true; - else throw new IOException( "sink not connected" ); - } - return false; - } + extends OutputStream { + private OutputStream sink; + private boolean discardOnNull; + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - @Override - public synchronized void write (int b) - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.write(b); - } finally { - lock.readLock().unlock(); - } - } + /** + * Creates a new IndirectOutputStream with no sink which + * fails when no sink is present. + */ + public IndirectOutputStream() { + this(null); + } - @Override - public synchronized void write (byte[] b) - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.write(b); - } finally { - lock.readLock().unlock(); - } - } + /** + * Creates a new IndirectOutputStream with the given sink + * which fails when no sink is present. + * + * @param sink the initial sink + */ + public IndirectOutputStream(OutputStream sink) { + this(sink, false); + } - @Override - public synchronized void write (byte[] b, int off, int len) - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.write(b, off, len); - } finally { - lock.readLock().unlock(); - } - } + /** + * Creates a new IndirectOutputStream with the given sink + * and behavior when no sink is present. + * + * @param sink the initial sink + * @param discard whether to discard requests when no sink is present + */ + public IndirectOutputStream(OutputStream sink, boolean discard) { + this.sink = sink; + this.discardOnNull = discard; + } - @Override - public synchronized void flush () - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.flush(); - } finally { - lock.readLock().unlock(); - } - } + /** + * Gets the currently configured sink. + * + * @return the stream to which requests are currently being forwarded + * or null if no sink present + */ + public OutputStream getSink() { + // don't bother with a read lock, a single read is atomic + return sink; + } - @Override - public synchronized void close () - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.close(); - } finally { - lock.readLock().unlock(); - } - } + /** + * Sets the stream to which requests are forwarded. + * + * @param sink the stream to which requests should be forwarded + * or null to remove the current sink + */ + public void setSink(OutputStream sink) { + lock.writeLock().lock(); + try { + this.sink = sink; + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Gets the current behavior when no wink is present. + * + * @return whether requests will be discarded when no sink is present + */ + public boolean discardOnNoSink() { + // don't bother with a read lock, a single read is atomic + return discardOnNull; + } + + /** + * Sets the behavior when no sink is present. + * + * @param discard whether to discard requests when no sink is present + */ + public void discardOnNoSink(boolean discard) { + // don't bother with a write lock, a single write is atomic + // as are all uses of this field + this.discardOnNull = discard; + } + + private boolean checkSink() + throws IOException { + if (null == sink) { + if (discardOnNull) return true; + else throw new IOException("sink not connected"); + } + return false; + } + + @Override + public synchronized void write(int b) + throws IOException { + lock.readLock().lock(); + try { + if (this.checkSink()) return; + sink.write(b); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public synchronized void write(byte[] b) + throws IOException { + lock.readLock().lock(); + try { + if (this.checkSink()) return; + sink.write(b); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public synchronized void write(byte[] b, int off, int len) + throws IOException { + lock.readLock().lock(); + try { + if (this.checkSink()) return; + sink.write(b, off, len); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public synchronized void flush() + throws IOException { + lock.readLock().lock(); + try { + if (this.checkSink()) return; + sink.flush(); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public synchronized void close() + throws IOException { + lock.readLock().lock(); + try { + if (this.checkSink()) return; + sink.close(); + } finally { + lock.readLock().unlock(); + } + } } diff --git a/src/main/java/io/starter/toolkit/JFileWriter.java b/src/main/java/io/starter/toolkit/JFileWriter.java index e78de99..90c4a06 100644 --- a/src/main/java/io/starter/toolkit/JFileWriter.java +++ b/src/main/java/io/starter/toolkit/JFileWriter.java @@ -2,207 +2,234 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.toolkit; + import java.io.*; import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; -/** File utilities. - * +/** + * File utilities. */ public class JFileWriter { - - java.lang.String - path = "", - filename = "", - data = ""; + + java.lang.String + path = "", + filename = "", + data = ""; byte newLine = Character.LINE_SEPARATOR; - - public void setPath(String p){path = p;} - public void setFileName(String f){filename = f;} - public void setData(String d){data = d;} - - void printErr(String err){ - Logger.logInfo("Error in JFileWriter: " + err); - Logger.logWarn("Error in JFileWriter: " + err); - } - - - /** append text to the end of a text file - */ - public static final synchronized void appendToFile(String pth, String text){ - try{ - byte[] bbuf = text.getBytes("UTF-8"); + + public void setPath(String p) { + path = p; + } + + public void setFileName(String f) { + filename = f; + } + + public void setData(String d) { + data = d; + } + + void printErr(String err) { + Logger.logInfo("Error in JFileWriter: " + err); + Logger.logWarn("Error in JFileWriter: " + err); + } + + + /** + * append text to the end of a text file + */ + public static final synchronized void appendToFile(String pth, String text) { + try { + byte[] bbuf = text.getBytes(StandardCharsets.UTF_8); File outp = new File(pth); - - if(!outp.exists()){ + + if (!outp.exists()) { outp.mkdirs(); outp.delete(); outp = new java.io.File(pth); } - + RandomAccessFile outputFile = new RandomAccessFile(outp, "rw"); - outputFile.skipBytes((int)outputFile.length()); + outputFile.skipBytes((int) outputFile.length()); int strt = 0; - if(outp.exists()) strt = (int)outputFile.length(); - outputFile.write(bbuf,0,(int)bbuf.length); + if (outp.exists()) strt = (int) outputFile.length(); + outputFile.write(bbuf, 0, bbuf.length); outputFile.close(); - }catch(Exception e){Logger.logInfo("JFileWriter.appendToFile() IO Error : " + e.toString());} + } catch (Exception e) { + Logger.logInfo("JFileWriter.appendToFile() IO Error : " + e.toString()); + } } - /** write the inputstream contents to file - * - * - * @param is - * @param file - * @throws IOException - */ + + /** + * write the inputstream contents to file + * + * @param is + * @param file + * @throws IOException + */ public static void writeToFile(InputStream is, File file) throws IOException { - DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); - - // Transfer bytes from in to out + DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + + // Transfer bytes from in to out byte[] buf = new byte[1024]; int len; while ((len = is.read(buf)) > 0) { out.write(buf, 0, len); } - is.close(); - out.close(); - } - - public boolean writeIt() - { - try - { - path += filename; - StringReader SR = new StringReader(data); - File outputFile = new File(path); - FileWriter out = new FileWriter(outputFile); - int c; - if(outputFile.length() > 0){return false;} - while ((c = SR.read()) != -1)out.write(c); - out.flush(); - out.close(); - } - catch(IOException e){Logger.logInfo("JFileWriter IO Error : " + e.toString());} - return true; - } - - - public boolean writeIt(String data, String filename, String path) - { - try - { - path += filename; - StringReader SR = new StringReader(data); - File outputFile = new File(path); - FileWriter out = new FileWriter(outputFile); - int c; - if(outputFile.length() > 0){return false;} - while ((c = SR.read()) != -1){ - out.write(c);} - out.flush(); - out.close(); - } catch(IOException e){Logger.logInfo("JFileWriter IO Error : " + e.toString());} - return true; + is.close(); + out.close(); + } + + public boolean writeIt() { + try { + path += filename; + StringReader SR = new StringReader(data); + File outputFile = new File(path); + FileWriter out = new FileWriter(outputFile); + int c; + if (outputFile.length() > 0) { + return false; + } + while ((c = SR.read()) != -1) out.write(c); + out.flush(); + out.close(); + } catch (IOException e) { + Logger.logInfo("JFileWriter IO Error : " + e.toString()); + } + return true; + } + + + public boolean writeIt(String data, String filename, String path) { + try { + path += filename; + StringReader SR = new StringReader(data); + File outputFile = new File(path); + FileWriter out = new FileWriter(outputFile); + int c; + if (outputFile.length() > 0) { + return false; + } + while ((c = SR.read()) != -1) { + out.write(c); + } + out.flush(); + out.close(); + } catch (IOException e) { + Logger.logInfo("JFileWriter IO Error : " + e.toString()); + } + return true; } - public String readFile(String fname) - { + public String readFile(String fname) { StringBuffer addTxt = new StringBuffer(); - try { - BufferedReader d - = new BufferedReader(new FileReader(fname)); - - while(d.ready())addTxt.append(d.readLine()); - d.close(); - }catch(Exception e){ - printErr("problem reading file: " + e); - } + try { + BufferedReader d + = new BufferedReader(new FileReader(fname)); + + while (d.ready()) addTxt.append(d.readLine()); + d.close(); + } catch (Exception e) { + printErr("problem reading file: " + e); + } return addTxt.toString(); } - + public static void copyFile(String infile, String outfile) - throws FileNotFoundException, IOException{ - File fx = new File(infile); - copyFile(fx,outfile); - - // this.writeLine(outfile, this.readFile(infile)); + throws IOException { + File fx = new File(infile); + copyFile(fx, outfile); + + // this.writeLine(outfile, this.readFile(infile)); + } + + + /** + * Copy method, using FileChannel#transferTo + * NOTE: will overwrite existing files + * + * @param File source + * @param File target + * @throws IOException + */ + public static void copyFile(File source, String target) + throws IOException { + File fout = new File(target); + fout.mkdirs(); + fout.delete(); + fout = new File(target); + FileChannel in = new FileInputStream(source).getChannel(); + FileChannel out = new FileOutputStream(target).getChannel(); + in.transferTo(0, in.size(), out); + in.close(); + out.close(); } - - - /** Copy method, using FileChannel#transferTo - * NOTE: will overwrite existing files - * @param File source - * @param File target - * @throws IOException - * */ - public static void copyFile(File source, String target) - throws FileNotFoundException, IOException{ - File fout = new File(target); - fout.mkdirs(); - fout.delete(); - fout = new File(target); - FileChannel in = new FileInputStream(source).getChannel(); - FileChannel out = new FileOutputStream(target).getChannel(); - in.transferTo(0, in.size(), out); - in.close(); - out.close(); - } - - public void writeLine(String file, String line) - { + + public void writeLine(String file, String line) { String s; - try{ + try { File f = new File(file); // f.mkdirs(); FileWriter out = new FileWriter(f); DataInputStream inStream = new DataInputStream(new StringBufferInputStream(line)); - while((s = inStream.readLine()) != null){ + while ((s = inStream.readLine()) != null) { out.write(s); out.write(newLine); } out.close(); - } catch(FileNotFoundException e){ printErr(e.toString());} - catch(Exception e){printErr(e.toString());} + } catch (FileNotFoundException e) { + printErr(e.toString()); + } catch (Exception e) { + printErr(e.toString()); + } } - - public void writeLogToFile(String fname, javax.swing.JTextArea jta) - { - try { - OutFile n = new OutFile( fname ); + + public void writeLogToFile(String fname, javax.swing.JTextArea jta) { + try { + OutFile n = new OutFile(fname); String logText = jta.getText(); n.writeBytes(logText); jta.setText(""); n.close(); + } catch (FileNotFoundException e) { + printErr(e.toString()); + } catch (IOException e) { + printErr(e.toString()); } - catch(FileNotFoundException e){ printErr(e.toString());} - catch(IOException e){printErr(e.toString());} } - public String readLog(String logFname) - { + public String readLog(String logFname) { String addTxt = ""; - try {InFile n = new InFile( logFname ); - while (n.available() != 0){addTxt += n.readLine();}} - catch(FileNotFoundException e){ printErr(e.toString());} - catch(IOException e){printErr(e.toString());} + try { + InFile n = new InFile(logFname); + while (n.available() != 0) { + addTxt += n.readLine(); + } + } catch (FileNotFoundException e) { + printErr(e.toString()); + } catch (IOException e) { + printErr(e.toString()); + } return addTxt += "\r\n"; } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/LogOutputter.java b/src/main/java/io/starter/toolkit/LogOutputter.java index 704f5b9..719ba1b 100644 --- a/src/main/java/io/starter/toolkit/LogOutputter.java +++ b/src/main/java/io/starter/toolkit/LogOutputter.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -31,6 +31,6 @@ public interface LogOutputter { - void log(String string); + void log(String string); } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/Logger.java b/src/main/java/io/starter/toolkit/Logger.java index b3a4dc8..a94c2c8 100644 --- a/src/main/java/io/starter/toolkit/Logger.java +++ b/src/main/java/io/starter/toolkit/Logger.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,28 +29,16 @@ */ /** * Logger.java - * - * - * */ package io.starter.toolkit; -import java.io.BufferedWriter; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; +import java.io.*; import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.Date; /** System-wide Logging facility - * + * *
                * Logger can be used to output standardized messages to System.out and System.err, as well as * to pluggable Logger implementations. @@ -62,460 +50,460 @@ *
                  *  CustomLog mylogr = new CustomLog();
                  *  Properties props = System.getProperties();
                -    props.put("io.starter.toolkit.logger", mylogr );
                + props.put("io.starter.toolkit.logger", mylogr );
                  *  
                *
                * The default Logger settings can be controlled using System properties. *
                  * 	props.put("io.starter.toolkit.logger.dateformat", "MMM yyyy mm:ss" );
                  * 	props.put("io.starter.toolkit.logger.dateformat", "none" );
                - * 
                + *
                  * 
                - * + * * */ public class Logger extends PrintStream { - /** @deprecated Just use this. */ - @Deprecated - protected PrintStream ous = this; - - /** Copy of line.separator system property to save lookups. */ - private static final String endl = System - .getProperty("line.separator"); - - private static Logger targetLogger; - private static BufferedWriter targetWriter; - private StringBuffer lineBuffer = new StringBuffer(); - private static boolean autoFlush; - - public Logger(Logger target) { - this(); - this.init(target); - } - - public Logger(Logger target, String charset) - throws UnsupportedEncodingException { - this(charset); - this.init(target); - } - - public Logger(OutputStream target) { - this(target, false); - } - - public Logger(OutputStream target, boolean autoFlush) { - this(new OutputStreamWriter(target), autoFlush); - } - - public Logger(OutputStream target, String charset, boolean autoFlush) - throws UnsupportedEncodingException { - this(new OutputStreamWriter(target, charset), charset, autoFlush); - } - - public Logger(Writer target) { - this(target, false); - } - - public Logger(Writer target, boolean autoFlush) { - this(); - this.init(target, autoFlush); - } - - public Logger(Writer target, String charset, boolean autoFlush) - throws UnsupportedEncodingException { - this(charset); - this.init(target, autoFlush); - } - - private Logger() { - super(new IndirectOutputStream(), true); - ((IndirectOutputStream) out).setSink(new WriterOutputStream(this, - Charset.defaultCharset())); - } - - private Logger(String charset) throws UnsupportedEncodingException { - super(new IndirectOutputStream(), true, charset); - ((IndirectOutputStream) out) - .setSink(new WriterOutputStream(this, charset)); - } - - private void init(Logger target) { - targetLogger = target; - targetWriter = null; - autoFlush = false; // has no meaning for Logger target - } - - private void init(Writer target, boolean autoFlush) { - targetLogger = null; - targetWriter = new BufferedWriter(target); - // autoFlush = autoFlush; - } - - /** Installs this logger as the default logger and replaces the standard - * output and error streams. - */ - public void install() { - setLogger(this); - System.setOut(this); - System.setErr(this); - } - - /* ---------- Logger methods ---------- */ - - public static void log(String message) { - if (null != targetLogger) - Logger.log(message); - else - synchronized (targetWriter) { - try { - targetWriter.write(getLogDate()); - targetWriter.write(" "); - targetWriter.write(message); - targetWriter.newLine(); - if (autoFlush) - targetWriter.flush(); - } catch (IOException ex) { - // we're the logger, so we can't exactly log about it - // the interface doesn't support exceptions so just drop it - } - } - } - - public void log(String message, Exception ex, boolean trace) { - if (null != targetLogger) - targetLogger.log(message, ex, trace); - else - log(formatThrowable(message, ex, trace)); - } - - public void log(String message, Exception ex) { - if (null != targetLogger) - targetLogger.log(message, ex); - else - log(formatThrowable(message, ex, false)); - } - - /* ---------- PrintStream methods ---------- */ - - public void logLine() { - synchronized (lineBuffer) { - // if the line buffer ends with a newline, strip it - int length = lineBuffer.length(); - if (length >= endl.length() && endl.equals(lineBuffer - .substring(length - endl.length(), length))) - lineBuffer.setLength(length - endl.length()); - - // log and reset the line buffer but don't log empty lines - if (lineBuffer.length() > 0) { - log(lineBuffer.toString()); - lineBuffer.setLength(0); - } - } - } - - @Override - public Logger append(char value) { - lineBuffer.append(value); - return this; - } - - @Override - public Logger append(CharSequence value) { - lineBuffer.append(value); - return this; - } - - @Override - public Logger append(CharSequence value, int start, int end) { - lineBuffer.append(value, start, end); - return this; - } - - @Override - public void print(boolean b) { - lineBuffer.append(b); - } - - @Override - public void print(char c) { - lineBuffer.append(c); - } - - @Override - public void print(int i) { - lineBuffer.append(i); - } - - @Override - public void print(long l) { - lineBuffer.append(l); - } - - @Override - public void print(float f) { - lineBuffer.append(f); - } - - @Override - public void print(double d) { - lineBuffer.append(d); - } - - @Override - public void print(char[] s) { - lineBuffer.append(s); - } - - @Override - public void print(String s) { - synchronized (lineBuffer) { - lineBuffer.append(s); - if (s.endsWith(endl)) - this.println(); - } - } - - @Override - public void print(Object obj) { - lineBuffer.append(obj); - } - - @Override - public void println(boolean x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(char x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(int x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(long x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(float x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(double x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(char[] x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(String x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(Object x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println() { - synchronized (lineBuffer) { - // flush the input stream into the line buffer - super.flush(); - - // log the current line - logLine(); - } - } - - /* - * ---------- static convenience methods for logging - * ---------- - */ - - public static final String INFO_STRING = ""; - public static final String WARN_STRING = "WARNING: "; - public static final String ERROR_STRING = "ERROR: "; - - /** Gets the current system logger. - */ - public static Logger getLogger() { - Logger logger; - - try { - logger = (Logger) System.getProperties() - .get("io.starter.toolkit.logger"); - } catch (Exception ex) { - logger = null; - } - - if (null == logger) { - if (System.err instanceof Logger) - logger = (Logger) System.err; - else { - logger = new Logger(System.err, true); - } - setLogger(logger); - } - - return logger; - } - - /** Replaces the system logger. */ - public static void setLogger(Logger logger) { - System.getProperties().put("io.starter.toolkit.logger", logger); - } - - public static String formatThrowable(String message, Throwable ex, boolean trace) { - StringWriter writer = new StringWriter(); - writer.write(message); - - if (trace) { - writer.write(endl); - writer.write(endl); - - PrintWriter printer = new PrintWriter(writer); - ex.printStackTrace(printer); - printer.flush(); - } else { - writer.write(": "); - writer.write(ex.toString()); - } - - return writer.toString(); - } - - /** Logs a fatal error message to the system logger. - */ - public static void logErr(String message, Exception ex) { - getLogger().log(ERROR_STRING + message, ex); - } - - /** Logs a fatal error message to the system logger. - */ - public static void logErr(String message, Throwable ex) { - getLogger(); - Logger.log(formatThrowable(ERROR_STRING + message, ex, false)); - } - - /** Logs a fatal error message to the system logger. - */ - public static void logErr(String message) { - getLogger(); - Logger.log(ERROR_STRING + message); - } - - /** Logs a fatal error message to the system logger. - */ - public static void logErr(String message, Exception ex, boolean trace) { - getLogger().log(ERROR_STRING + message, ex, trace); - } - - /** Logs the string conversion of an object to the system logger. - */ - public static void log(Object object) { - logInfo(object.toString()); - } - - /** Logs a non-fatal warning to the system logger. - */ - public static void logWarn(String message) { - getLogger(); - Logger.log(WARN_STRING + message); - } - - /** Logs the string conversion of an exception to the system logger as a - * fatal error message. - */ - public static void logErr(Exception ex) { - logErr(ex.toString()); - } - - /** Logs an informational message to the system logger. - */ - public static void logInfo(String message) { - getLogger(); - Logger.log(INFO_STRING + message); - } - - /** Attempts to replace the standard output stream with a - * Logger instance that writes to the named file. If the - * operation fails a message will be logged to the system logger and the - * method will return without throwing an exception. - */ - public static void setOut(String filename) { - try { - java.io.File logfile = new java.io.File(filename); - FileOutputStream sysout = new FileOutputStream(logfile); - System.setOut(new Logger(sysout)); - } catch (Exception e) { - Logger.logErr("Setting System Output Stream in Logger failed: ", e); - } - } - - /** Attempts to replace the standard error stream with a - * Logger instance that writes to the named file. If the - * operation fails a message will be logged to the system logger and the - * method will return without throwing an exception. - */ - public static void setErr(String filename) { - try { - java.io.File logfile = new java.io.File(filename); - FileOutputStream sysout = new FileOutputStream(logfile); - System.setErr(new Logger(sysout)); - } catch (Exception e) { - Logger.logErr("Setting System Error Stream in Logger failed: ", e); - } - } - - /** The default time stamp format for {@link #getLogDate()}. */ - public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss:SSSS"; - - private static SimpleDateFormat dateFormat = new SimpleDateFormat( - DATE_FORMAT); - private static String dateSpec = DATE_FORMAT; - - /** Returns the current time in a configurable format. - * If the system property io.starter.toolkit.logger.dateformat - * exists and is a valid date format pattern it will be used. Otherwise the - * {@linkplain #DATE_FORMAT default format pattern} will be used. - */ - public static String getLogDate() { - String spec = System - .getProperty("io.starter.toolkit.logger.dateformat"); - if (null != spec) { - if ("none".equalsIgnoreCase(spec)) - return ""; - - if (!dateSpec.equals(spec)) { - try { - dateFormat.applyPattern(spec); - } catch (IllegalArgumentException e) { - dateFormat.applyPattern(DATE_FORMAT); - } - - dateSpec = spec; - } - } - - return dateFormat.format(new Date()); - } + /** @deprecated Just use this. */ + @Deprecated + protected PrintStream ous = this; + + /** Copy of line.separator system property to save lookups. */ + private static final String endl = System + .getProperty("line.separator"); + + private static Logger targetLogger; + private static BufferedWriter targetWriter; + private StringBuffer lineBuffer = new StringBuffer(); + private static boolean autoFlush; + + public Logger(Logger target) { + this(); + this.init(target); + } + + public Logger(Logger target, String charset) + throws UnsupportedEncodingException { + this(charset); + this.init(target); + } + + public Logger(OutputStream target) { + this(target, false); + } + + public Logger(OutputStream target, boolean autoFlush) { + this(new OutputStreamWriter(target), autoFlush); + } + + public Logger(OutputStream target, String charset, boolean autoFlush) + throws UnsupportedEncodingException { + this(new OutputStreamWriter(target, charset), charset, autoFlush); + } + + public Logger(Writer target) { + this(target, false); + } + + public Logger(Writer target, boolean autoFlush) { + this(); + this.init(target, autoFlush); + } + + public Logger(Writer target, String charset, boolean autoFlush) + throws UnsupportedEncodingException { + this(charset); + this.init(target, autoFlush); + } + + private Logger() { + super(new IndirectOutputStream(), true); + ((IndirectOutputStream) out).setSink(new WriterOutputStream(this, + Charset.defaultCharset())); + } + + private Logger(String charset) throws UnsupportedEncodingException { + super(new IndirectOutputStream(), true, charset); + ((IndirectOutputStream) out) + .setSink(new WriterOutputStream(this, charset)); + } + + private void init(Logger target) { + targetLogger = target; + targetWriter = null; + autoFlush = false; // has no meaning for Logger target + } + + private void init(Writer target, boolean autoFlush) { + targetLogger = null; + targetWriter = new BufferedWriter(target); + // autoFlush = autoFlush; + } + + /** Installs this logger as the default logger and replaces the standard + * output and error streams. + */ + public void install() { + setLogger(this); + System.setOut(this); + System.setErr(this); + } + + /* ---------- Logger methods ---------- */ + + public static void log(String message) { + if (null != targetLogger) + Logger.log(message); + else + synchronized (targetWriter) { + try { + targetWriter.write(getLogDate()); + targetWriter.write(" "); + targetWriter.write(message); + targetWriter.newLine(); + if (autoFlush) + targetWriter.flush(); + } catch (IOException ex) { + // we're the logger, so we can't exactly log about it + // the interface doesn't support exceptions so just drop it + } + } + } + + public void log(String message, Exception ex, boolean trace) { + if (null != targetLogger) + targetLogger.log(message, ex, trace); + else + log(formatThrowable(message, ex, trace)); + } + + public void log(String message, Exception ex) { + if (null != targetLogger) + targetLogger.log(message, ex); + else + log(formatThrowable(message, ex, false)); + } + + /* ---------- PrintStream methods ---------- */ + + public void logLine() { + synchronized (lineBuffer) { + // if the line buffer ends with a newline, strip it + int length = lineBuffer.length(); + if (length >= endl.length() && endl.equals(lineBuffer + .substring(length - endl.length(), length))) + lineBuffer.setLength(length - endl.length()); + + // log and reset the line buffer but don't log empty lines + if (lineBuffer.length() > 0) { + log(lineBuffer.toString()); + lineBuffer.setLength(0); + } + } + } + + @Override + public Logger append(char value) { + lineBuffer.append(value); + return this; + } + + @Override + public Logger append(CharSequence value) { + lineBuffer.append(value); + return this; + } + + @Override + public Logger append(CharSequence value, int start, int end) { + lineBuffer.append(value, start, end); + return this; + } + + @Override + public void print(boolean b) { + lineBuffer.append(b); + } + + @Override + public void print(char c) { + lineBuffer.append(c); + } + + @Override + public void print(int i) { + lineBuffer.append(i); + } + + @Override + public void print(long l) { + lineBuffer.append(l); + } + + @Override + public void print(float f) { + lineBuffer.append(f); + } + + @Override + public void print(double d) { + lineBuffer.append(d); + } + + @Override + public void print(char[] s) { + lineBuffer.append(s); + } + + @Override + public void print(String s) { + synchronized (lineBuffer) { + lineBuffer.append(s); + if (s.endsWith(endl)) + this.println(); + } + } + + @Override + public void print(Object obj) { + lineBuffer.append(obj); + } + + @Override + public void println(boolean x) { + lineBuffer.append(x); + this.println(); + } + + @Override + public void println(char x) { + lineBuffer.append(x); + this.println(); + } + + @Override + public void println(int x) { + lineBuffer.append(x); + this.println(); + } + + @Override + public void println(long x) { + lineBuffer.append(x); + this.println(); + } + + @Override + public void println(float x) { + lineBuffer.append(x); + this.println(); + } + + @Override + public void println(double x) { + lineBuffer.append(x); + this.println(); + } + + @Override + public void println(char[] x) { + lineBuffer.append(x); + this.println(); + } + + @Override + public void println(String x) { + lineBuffer.append(x); + this.println(); + } + + @Override + public void println(Object x) { + lineBuffer.append(x); + this.println(); + } + + @Override + public void println() { + synchronized (lineBuffer) { + // flush the input stream into the line buffer + super.flush(); + + // log the current line + logLine(); + } + } + + /* + * ---------- static convenience methods for logging + * ---------- + */ + + public static final String INFO_STRING = ""; + public static final String WARN_STRING = "WARNING: "; + public static final String ERROR_STRING = "ERROR: "; + + /** Gets the current system logger. + */ + public static Logger getLogger() { + Logger logger; + + try { + logger = (Logger) System.getProperties() + .get("io.starter.toolkit.logger"); + } catch (Exception ex) { + logger = null; + } + + if (null == logger) { + if (System.err instanceof Logger) + logger = (Logger) System.err; + else { + logger = new Logger(System.err, true); + } + setLogger(logger); + } + + return logger; + } + + /** Replaces the system logger. */ + public static void setLogger(Logger logger) { + System.getProperties().put("io.starter.toolkit.logger", logger); + } + + public static String formatThrowable(String message, Throwable ex, boolean trace) { + StringWriter writer = new StringWriter(); + writer.write(message); + + if (trace) { + writer.write(endl); + writer.write(endl); + + PrintWriter printer = new PrintWriter(writer); + ex.printStackTrace(printer); + printer.flush(); + } else { + writer.write(": "); + writer.write(ex.toString()); + } + + return writer.toString(); + } + + /** Logs a fatal error message to the system logger. + */ + public static void logErr(String message, Exception ex) { + getLogger().log(ERROR_STRING + message, ex); + } + + /** Logs a fatal error message to the system logger. + */ + public static void logErr(String message, Throwable ex) { + getLogger(); + Logger.log(formatThrowable(ERROR_STRING + message, ex, false)); + } + + /** Logs a fatal error message to the system logger. + */ + public static void logErr(String message) { + getLogger(); + Logger.log(ERROR_STRING + message); + } + + /** Logs a fatal error message to the system logger. + */ + public static void logErr(String message, Exception ex, boolean trace) { + getLogger().log(ERROR_STRING + message, ex, trace); + } + + /** Logs the string conversion of an object to the system logger. + */ + public static void log(Object object) { + logInfo(object.toString()); + } + + /** Logs a non-fatal warning to the system logger. + */ + public static void logWarn(String message) { + getLogger(); + Logger.log(WARN_STRING + message); + } + + /** Logs the string conversion of an exception to the system logger as a + * fatal error message. + */ + public static void logErr(Exception ex) { + logErr(ex.toString()); + } + + /** Logs an informational message to the system logger. + */ + public static void logInfo(String message) { + getLogger(); + Logger.log(INFO_STRING + message); + } + + /** Attempts to replace the standard output stream with a + * Logger instance that writes to the named file. If the + * operation fails a message will be logged to the system logger and the + * method will return without throwing an exception. + */ + public static void setOut(String filename) { + try { + java.io.File logfile = new java.io.File(filename); + FileOutputStream sysout = new FileOutputStream(logfile); + System.setOut(new Logger(sysout)); + } catch (Exception e) { + Logger.logErr("Setting System Output Stream in Logger failed: ", e); + } + } + + /** Attempts to replace the standard error stream with a + * Logger instance that writes to the named file. If the + * operation fails a message will be logged to the system logger and the + * method will return without throwing an exception. + */ + public static void setErr(String filename) { + try { + java.io.File logfile = new java.io.File(filename); + FileOutputStream sysout = new FileOutputStream(logfile); + System.setErr(new Logger(sysout)); + } catch (Exception e) { + Logger.logErr("Setting System Error Stream in Logger failed: ", e); + } + } + + /** The default time stamp format for {@link #getLogDate()}. */ + public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss:SSSS"; + + private static SimpleDateFormat dateFormat = new SimpleDateFormat( + DATE_FORMAT); + private static String dateSpec = DATE_FORMAT; + + /** Returns the current time in a configurable format. + * If the system property io.starter.toolkit.logger.dateformat + * exists and is a valid date format pattern it will be used. Otherwise the + * {@linkplain #DATE_FORMAT default format pattern} will be used. + */ + public static String getLogDate() { + String spec = System + .getProperty("io.starter.toolkit.logger.dateformat"); + if (null != spec) { + if ("none".equalsIgnoreCase(spec)) + return ""; + + if (!dateSpec.equals(spec)) { + try { + dateFormat.applyPattern(spec); + } catch (IllegalArgumentException e) { + dateFormat.applyPattern(DATE_FORMAT); + } + + dateSpec = spec; + } + } + + return dateFormat.format(new Date()); + } } diff --git a/src/main/java/io/starter/toolkit/MD4Digest.java b/src/main/java/io/starter/toolkit/MD4Digest.java index 67dfa5a..73e500f 100644 --- a/src/main/java/io/starter/toolkit/MD4Digest.java +++ b/src/main/java/io/starter/toolkit/MD4Digest.java @@ -2,25 +2,26 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.toolkit; + /** * implementation of MD4 as RFC 1320 by R. Rivest, MIT Laboratory for * Computer Science and RSA Data Security, Inc. @@ -30,76 +31,69 @@ */ public class MD4Digest { - - private int H1, H2, H3, H4; // IV's - private int[] X = new int[16]; - private int xOff; + + private int H1, H2, H3, H4; // IV's + private int[] X = new int[16]; + private int xOff; private static final int BYTE_LENGTH = 64; - private byte[] xBuf; - private int xBufOff; - private long byteCount; + private byte[] xBuf; + private int xBufOff; + private long byteCount; /** * Standard constructor */ - public MD4Digest() - { - xBuf = new byte[4]; - xBufOff = 0; + public MD4Digest() { + xBuf = new byte[4]; + xBufOff = 0; reset(); } - public byte[] getDigest(byte[] data){ - - this.update(data,0,data.length); - byte[] digest = new byte[16]; - this.doFinal(digest,0); - - return digest; - + public byte[] getDigest(byte[] data) { + + this.update(data, 0, data.length); + byte[] digest = new byte[16]; + this.doFinal(digest, 0); + + return digest; + } - + protected void processWord( - byte[] in, - int inOff) - { + byte[] in, + int inOff) { X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) - | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); - if (xOff == 16) - { + if (xOff == 16) { processBlock(); } } protected void processLength( - long bitLength) - { - if (xOff > 14) - { + long bitLength) { + if (xOff > 14) { processBlock(); } - X[14] = (int)(bitLength & 0xffffffff); - X[15] = (int)(bitLength >>> 32); + X[14] = (int) (bitLength & 0xffffffff); + X[15] = (int) (bitLength >>> 32); } private void unpackWord( - int word, - byte[] out, - int outOff) - { - out[outOff] = (byte)word; - out[outOff + 1] = (byte)(word >>> 8); - out[outOff + 2] = (byte)(word >>> 16); - out[outOff + 3] = (byte)(word >>> 24); + int word, + byte[] out, + int outOff) { + out[outOff] = (byte) word; + out[outOff + 1] = (byte) (word >>> 8); + out[outOff + 2] = (byte) (word >>> 16); + out[outOff + 3] = (byte) (word >>> 24); } public int doFinal( - byte[] out, - int outOff) - { + byte[] out, + int outOff) { finish(); unpackWord(H1, out, outOff); @@ -115,13 +109,11 @@ public int doFinal( /** * reset the chaining variables to the IV values. */ - public void reset() - { - byteCount = 0; + public void reset() { + byteCount = 0; xBufOff = 0; - for (int i = 0; i < xBuf.length; i++) - { + for (int i = 0; i < xBuf.length; i++) { xBuf[i] = 0; } @@ -132,8 +124,7 @@ public void reset() xOff = 0; - for (int i = 0; i != X.length; i++) - { + for (int i = 0; i != X.length; i++) { X[i] = 0; } } @@ -166,9 +157,8 @@ public void reset() * rotate int x left n bits. */ private int rotateLeft( - int x, - int n) - { + int x, + int n) { return (x << n) | (x >>> (32 - n)); } @@ -176,31 +166,27 @@ private int rotateLeft( * F, G, H and I are the basic MD4 functions. */ private int F( - int u, - int v, - int w) - { + int u, + int v, + int w) { return (u & v) | (~u & w); } private int G( - int u, - int v, - int w) - { + int u, + int v, + int w) { return (u & v) | (u & w) | (v & w); } private int H( - int u, - int v, - int w) - { + int u, + int v, + int w) { return u ^ v ^ w; } - protected void processBlock() - { + protected void processBlock() { int a = H1; int b = H2; int c = H3; @@ -209,16 +195,16 @@ protected void processBlock() // // Round 1 - F cycle, 16 times. // - a = rotateLeft(a + F(b, c, d) + X[ 0], S11); - d = rotateLeft(d + F(a, b, c) + X[ 1], S12); - c = rotateLeft(c + F(d, a, b) + X[ 2], S13); - b = rotateLeft(b + F(c, d, a) + X[ 3], S14); - a = rotateLeft(a + F(b, c, d) + X[ 4], S11); - d = rotateLeft(d + F(a, b, c) + X[ 5], S12); - c = rotateLeft(c + F(d, a, b) + X[ 6], S13); - b = rotateLeft(b + F(c, d, a) + X[ 7], S14); - a = rotateLeft(a + F(b, c, d) + X[ 8], S11); - d = rotateLeft(d + F(a, b, c) + X[ 9], S12); + a = rotateLeft(a + F(b, c, d) + X[0], S11); + d = rotateLeft(d + F(a, b, c) + X[1], S12); + c = rotateLeft(c + F(d, a, b) + X[2], S13); + b = rotateLeft(b + F(c, d, a) + X[3], S14); + a = rotateLeft(a + F(b, c, d) + X[4], S11); + d = rotateLeft(d + F(a, b, c) + X[5], S12); + c = rotateLeft(c + F(d, a, b) + X[6], S13); + b = rotateLeft(b + F(c, d, a) + X[7], S14); + a = rotateLeft(a + F(b, c, d) + X[8], S11); + d = rotateLeft(d + F(a, b, c) + X[9], S12); c = rotateLeft(c + F(d, a, b) + X[10], S13); b = rotateLeft(b + F(c, d, a) + X[11], S14); a = rotateLeft(a + F(b, c, d) + X[12], S11); @@ -229,41 +215,41 @@ protected void processBlock() // // Round 2 - G cycle, 16 times. // - a = rotateLeft(a + G(b, c, d) + X[ 0] + 0x5a827999, S21); - d = rotateLeft(d + G(a, b, c) + X[ 4] + 0x5a827999, S22); - c = rotateLeft(c + G(d, a, b) + X[ 8] + 0x5a827999, S23); + a = rotateLeft(a + G(b, c, d) + X[0] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[4] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[8] + 0x5a827999, S23); b = rotateLeft(b + G(c, d, a) + X[12] + 0x5a827999, S24); - a = rotateLeft(a + G(b, c, d) + X[ 1] + 0x5a827999, S21); - d = rotateLeft(d + G(a, b, c) + X[ 5] + 0x5a827999, S22); - c = rotateLeft(c + G(d, a, b) + X[ 9] + 0x5a827999, S23); + a = rotateLeft(a + G(b, c, d) + X[1] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[5] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[9] + 0x5a827999, S23); b = rotateLeft(b + G(c, d, a) + X[13] + 0x5a827999, S24); - a = rotateLeft(a + G(b, c, d) + X[ 2] + 0x5a827999, S21); - d = rotateLeft(d + G(a, b, c) + X[ 6] + 0x5a827999, S22); + a = rotateLeft(a + G(b, c, d) + X[2] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[6] + 0x5a827999, S22); c = rotateLeft(c + G(d, a, b) + X[10] + 0x5a827999, S23); b = rotateLeft(b + G(c, d, a) + X[14] + 0x5a827999, S24); - a = rotateLeft(a + G(b, c, d) + X[ 3] + 0x5a827999, S21); - d = rotateLeft(d + G(a, b, c) + X[ 7] + 0x5a827999, S22); + a = rotateLeft(a + G(b, c, d) + X[3] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[7] + 0x5a827999, S22); c = rotateLeft(c + G(d, a, b) + X[11] + 0x5a827999, S23); b = rotateLeft(b + G(c, d, a) + X[15] + 0x5a827999, S24); // // Round 3 - H cycle, 16 times. // - a = rotateLeft(a + H(b, c, d) + X[ 0] + 0x6ed9eba1, S31); - d = rotateLeft(d + H(a, b, c) + X[ 8] + 0x6ed9eba1, S32); - c = rotateLeft(c + H(d, a, b) + X[ 4] + 0x6ed9eba1, S33); + a = rotateLeft(a + H(b, c, d) + X[0] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[8] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[4] + 0x6ed9eba1, S33); b = rotateLeft(b + H(c, d, a) + X[12] + 0x6ed9eba1, S34); - a = rotateLeft(a + H(b, c, d) + X[ 2] + 0x6ed9eba1, S31); + a = rotateLeft(a + H(b, c, d) + X[2] + 0x6ed9eba1, S31); d = rotateLeft(d + H(a, b, c) + X[10] + 0x6ed9eba1, S32); - c = rotateLeft(c + H(d, a, b) + X[ 6] + 0x6ed9eba1, S33); + c = rotateLeft(c + H(d, a, b) + X[6] + 0x6ed9eba1, S33); b = rotateLeft(b + H(c, d, a) + X[14] + 0x6ed9eba1, S34); - a = rotateLeft(a + H(b, c, d) + X[ 1] + 0x6ed9eba1, S31); - d = rotateLeft(d + H(a, b, c) + X[ 9] + 0x6ed9eba1, S32); - c = rotateLeft(c + H(d, a, b) + X[ 5] + 0x6ed9eba1, S33); + a = rotateLeft(a + H(b, c, d) + X[1] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[9] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[5] + 0x6ed9eba1, S33); b = rotateLeft(b + H(c, d, a) + X[13] + 0x6ed9eba1, S34); - a = rotateLeft(a + H(b, c, d) + X[ 3] + 0x6ed9eba1, S31); + a = rotateLeft(a + H(b, c, d) + X[3] + 0x6ed9eba1, S31); d = rotateLeft(d + H(a, b, c) + X[11] + 0x6ed9eba1, S32); - c = rotateLeft(c + H(d, a, b) + X[ 7] + 0x6ed9eba1, S33); + c = rotateLeft(c + H(d, a, b) + X[7] + 0x6ed9eba1, S33); b = rotateLeft(b + H(c, d, a) + X[15] + 0x6ed9eba1, S34); H1 += a; @@ -275,22 +261,17 @@ protected void processBlock() // reset the offset and clean out the word buffer. // xOff = 0; - for (int i = 0; i != X.length; i++) - { + for (int i = 0; i != X.length; i++) { X[i] = 0; } } - - - - + + public void update( - byte in) - { + byte in) { xBuf[xBufOff++] = in; - if (xBufOff == xBuf.length) - { + if (xBufOff == xBuf.length) { processWord(xBuf, 0); xBufOff = 0; } @@ -299,15 +280,13 @@ public void update( } public void update( - byte[] in, - int inOff, - int len) - { + byte[] in, + int inOff, + int len) { // // fill the current word // - while ((xBufOff != 0) && (len > 0)) - { + while ((xBufOff != 0) && (len > 0)) { update(in[inOff]); inOff++; @@ -317,8 +296,7 @@ public void update( // // process whole words. // - while (len > xBuf.length) - { + while (len > xBuf.length) { processWord(in, inOff); inOff += xBuf.length; @@ -329,8 +307,7 @@ public void update( // // load in the remainder. // - while (len > 0) - { + while (len > 0) { update(in[inOff]); inOff++; @@ -338,18 +315,16 @@ public void update( } } - public void finish() - { - long bitLength = (byteCount << 3); + public void finish() { + long bitLength = (byteCount << 3); // // add the pad bytes. // - update((byte)128); + update((byte) 128); - while (xBufOff != 0) - { - update((byte)0); + while (xBufOff != 0) { + update((byte) 0); } processLength(bitLength); @@ -357,14 +332,11 @@ public void finish() processBlock(); } - - public int getByteLength() - { + public int getByteLength() { return BYTE_LENGTH; } - - + } diff --git a/src/main/java/io/starter/toolkit/Money.java b/src/main/java/io/starter/toolkit/Money.java index 129bbf4..6fc764e 100644 --- a/src/main/java/io/starter/toolkit/Money.java +++ b/src/main/java/io/starter/toolkit/Money.java @@ -2,1209 +2,1203 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.toolkit; -import java.text.*; // Java Text classes -import java.math.*; // Java Math classes -import java.io.*; // Java I/O + +import java.io.Serializable; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.ParseException; + /** + * Class Money * - Class Money - * -

                - The Money class represents a United States - monetary value, expressed in dollars and cents. Internally, - the - value is represented using Java's BigDecimal class. -

                - * -

                - Methods are provided to perform all the usual arithmetic - manipulations required when dealing with monetary data, - including add, subtract, multiply, and divide. -

                - * -

                - Rounding -

                -

                - Rounding does not occur during intermediate computations; - maximum precision - (and accuracy) is thus preserved throughout all computations. - Round-off to an integral cent occurs only when the - monetary value is externalized (when formatted for display as - a String or - converted to a long integer). One of several different - rounding modes - can be specified. The default rounding mode is to discard any - fractional - cent and truncate the monetary value to 2 decimal places. -

                + *

                + * The Money class represents a United States + * monetary value, expressed in dollars and cents. Internally, + * the + * value is represented using Java's BigDecimal class. + *

                * -

                - Currency Format -

                -

                - A Currency Format (an instance of DecimalFormat) is used to - control formatting of - monetary values for display as well as the parsing of strings - which represent - monetary values. - By default, the Currency Format for the current locale is - used. For the - United States, the default Currency Symbol is the Dollar Sign - ("$"). - Negative amounts are enclosed in parentheses. A Decimal Point - (".") - separates the dollars and cents, and a comma (",") separates - each group - of 3 consecutive digits in the dollar amount. -

                -

                - Examples: $1,234.56 ($1,234.56) -

                + *

                + * Methods are provided to perform all the usual arithmetic + * manipulations required when dealing with monetary data, + * including add, subtract, multiply, and divide. + *

                * -

                - Immutability -

                -

                - Money objects, like String objects, are immutable. - An operation on a Money object (such as add, subtract, etc.) - does not alter the object in any way. - Rather, a new Money object is returned whose state reflects - the result of the operation. - Thus, a statement like -

                -

                - money1.add(money2); -

                -

                - has no effect; it does not modify money1 in any way, and the - result is effectively discarded. - If the intent is to modify money1, then you should code -

                -

                - money1 = money1.add(money2); -

                -

                - which effectively replaces money1 with the result. -

                + *

                + * Rounding + *

                + *

                + * Rounding does not occur during intermediate computations; + * maximum precision + * (and accuracy) is thus preserved throughout all computations. + * Round-off to an integral cent occurs only when the + * monetary value is externalized (when formatted for display as + * a String or + * converted to a long integer). One of several different + * rounding modes + * can be specified. The default rounding mode is to discard any + * fractional + * cent and truncate the monetary value to 2 decimal places. + *

                * - @see BigDecimal + *

                + * Currency Format + *

                + *

                + * A Currency Format (an instance of DecimalFormat) is used to + * control formatting of + * monetary values for display as well as the parsing of strings + * which represent + * monetary values. + * By default, the Currency Format for the current locale is + * used. For the + * United States, the default Currency Symbol is the Dollar Sign + * ("$"). + * Negative amounts are enclosed in parentheses. A Decimal Point + * (".") + * separates the dollars and cents, and a comma (",") separates + * each group + * of 3 consecutive digits in the dollar amount. + *

                + *

                + * Examples: $1,234.56 ($1,234.56) + *

                * - @see DecimalFormat + *

                + * Immutability + *

                + *

                + * Money objects, like String objects, are immutable. + * An operation on a Money object (such as add, subtract, etc.) + * does not alter the object in any way. + * Rather, a new Money object is returned whose state reflects + * the result of the operation. + * Thus, a statement like + *

                + *

                + * money1.add(money2); + *

                + *

                + * has no effect; it does not modify money1 in any way, and the + * result is effectively discarded. + * If the intent is to modify money1, then you should code + *

                + *

                + * money1 = money1.add(money2); + *

                + *

                + * which effectively replaces money1 with the result. + *

                * + * @see BigDecimal + * @see DecimalFormat */ public class Money // Money Class -implements Cloneable, // Money objects can be cloned -Serializable // Money objects are serializable + implements Cloneable, // Money objects can be cloned + Serializable // Money objects are serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2055249101317798370L; - /** - The monetary value. - */ - protected BigDecimal value= null; // The monetary value - /** - The Rounding Mode. Specifies if and how the monetary value is - to be rounded off to an integral cent. - */ - protected int roundingMode= BigDecimal.ROUND_DOWN; // Rounding Mode - /** - The Currency Format, used for formatting and parsing a - monetary value. Refer to the Java API - documentation for the DecimalFormat class for information on - formats. - */ - protected DecimalFormat currencyFormat= - (DecimalFormat)NumberFormat.getCurrencyInstance(); - // Currency format - /** - The special monetary value of zero ($0.00). - */ - protected static final BigDecimal ZERO= new BigDecimal("0.00"); - // The value $0.00 - /** - * - Class InvalidScaleFactorException - * -

                - The InvalidScaleFactorException is thrown if an invalid scale - factor is specified (valid scale factors are 0, 1, and 2). - This is a non-checked exception and will be detected at - runtime only. -

                - * - */ - public static class InvalidScaleFactorException - extends RuntimeException // Non-checked exception - { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8038085965896123803L; - /** - * - Default constructor for a InvalidScaleFactorException object. - * - */ - public InvalidScaleFactorException() { - super(); // Invoke super class constructor. - } - /** - * - Constructor for a InvalidScaleFactorException object. - * -

                - @param info Descriptive information -

                - * - */ - public InvalidScaleFactorException(String info) // Descriptive info - { - super(info); // Invoke super class constructor. - } - } // Class InvalidScaleFactorException - /** - * - Class InvalidRoundingModeException - * -

                - The InvalidRoundingModeException is thrown if an invalid - Rounding - Mode is specified (all rounding modes except - ROUND_UNNECESSARY are valid). - This is a non-checked exception and will be detected at - runtime only. -

                - * - @see BigDecimal#ROUND_UP - @see BigDecimal#ROUND_DOWN - @see BigDecimal#ROUND_CEILING - @see BigDecimal#ROUND_FLOOR - @see BigDecimal#ROUND_HALF_UP - @see BigDecimal#ROUND_HALF_DOWN - @see BigDecimal#ROUND_HALF_EVEN - * - */ - public static class InvalidRoundingModeException - extends RuntimeException // Non-checked exception - { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5658836125641516151L; - /** - * - Default constructor for a InvalidRoundingModeException object. - * - */ - public InvalidRoundingModeException() { - super(); // Invoke super class constructor. - } // Default Constructor InvalidRoundingModeException() - /** - * - Constructor for a InvalidRoundingModeException object. - * -

                - @param info Descriptive information -

                - * - */ - public InvalidRoundingModeException(String info) // Descriptive info - { - super(info); // Invoke super class constructor. - } - } // Class InvalidRoundingModeException - /** - * - Default Constructor for a Money object; creates an object - whose value is $0.00. - * - */ - public Money() { - value= ZERO; // Initialize the monetary value - // to $0.00. - } // Default Constructor Money() - /** - * - Constructs a Money object from a double-precision, - floating-point value. - The Currency Format is set to the default format for the - current locale. - * -

                - The integral part of the value represents whole dollars, and - the fractional - part of the value represents fractional dollars (cents). As - an example, - the value 19.95 would represent $19.95. -

                - * -

                - @param amount The monetary amount, in dollars - and cents -

                - * - */ - public Money(double amount) // Monetary Value, dollars and cents - { - // In general, a double floating point value cannot represent a - // decimal value exactly, and - // therefore is only a very close approximation of the actual decimal - // value. Fortunately, - // the Double.toString() method is able to "recognize" an approximated - // decimal value, and - // will return the original (approximated) decimal value, rather than - // the literal floating - // point value. Here, we take advantage of this fact to obtain a - // string representation of - // the monetary value (without formatting, except for the decimal - // point), which we then use - // to create a BigDecimal object having the required value. - // Were we not to make this simplifying assumption, the only - // alternative would be to parse the - // string ourselves, which can be quite complicated, and would - // unnecessarily duplicate code - // already implemented by the format's parse() method. - // Convert the parsed value to a simple string (decimal point only) - // and use it to set the monetary value. - value= new BigDecimal(Double.toString(amount)); - } // Constructor Money(double amount) - /** - * - Constructs a Money object from a long integer value. The - specified - value represents whole dollars only (that is, cents are - implicity assumed - to be 00). For example, the integer 25 would represent a - monetary value of $25.00. - * -

                - @param amount The monetary amount, in whole - dollars (no cents) -

                - * - */ - public Money(long amount) // Monetary amount, whole dollars (no cents) - { - value= new BigDecimal(Long.toString(amount)); // Set monetary value. - } // Constructor Money(long amount) - /** - * - Constructs a Money object from a long integer value with a - specified scale factor. - The scale factor (0, 1, or 2) specifies the number of digits - to the right of an - implied decimal point. - * -

                - For example, the value 1995 would be interpreted as a - monetary value of - $1995.00, $199.50, and $19.95 for scale factors of 0, 1, or - 2, respectively. -

                - * -

                - @param amount The monetary amount, in dollars - and cents - * - @param scale Scale factor (must be 0, 1, or 2) -

                - * -

                - @throws InvalidScaleFactorException The scale factor - specified is not valid (must be 0, 1, or 2) -

                - * - */ - public Money(long amount, // Montetary amount - int scale) // Scale Factor (0, 1, 2) - throws InvalidScaleFactorException { - if (// If the Scale Factor is not - (scale < 0) // 0, 1, or - || (scale > 2) // 2, - ) - throw new InvalidScaleFactorException( - "Invalid scale factor: " + scale + " (must be 0, 1, or 2)"); - // Set the monetary value and scale as specified. - value= (new BigDecimal(Long.toString(amount))).movePointLeft(scale); - } // Constructor Money(long amount, int scale) - /** - * - Constructs a Money object from a string representation of a - monetary value. The - format of the string must be consistent with the Currency - Format; otherwise, - a ParseException is recognized. - * - Refer to the Java API documentation for the DecimalFormat - class for information on - Decimal Formats in general, and Currency Formats in - particular. - * -

                - @param string A String representing a monetary - value -

                - * -

                - @throws ParseException The string is inconsistent with - the Currency Format -

                - * - @see DecimalFormat - @see #setCurrencyFormat - * - */ - public Money(String string) // String representing a Monetary Value - throws ParseException // If string is inconsistent with - // Currency Format - { - // We make use of the format's parse() method, which parses the string - // according to the format and returns either a Double or a Long - // object representing the value. - Number number; - // Attempt to parse the string as a monetary value. May throw - // ParseException if the string is not consistent with the Currency - // Format. - number= currencyFormat.parse(string); - // In general, a double floating point value cannot represent a - // decimal value exactly, and - // therefore is only a very close approximation of the actual decimal - // value. Fortunately, - // the Double.toString() method is able to "recognize" an approximated - // decimal value, and - // will return the original (approximated) decimal value, rather than - // the literal floating - // point value. Here, we take advantage of this fact to obtain a - // string representation of - // the monetary value (without formatting, except for the decimal - // point), which we then use - // to create a BigDecimal object having the required value. - // Were we not to make this simplifying assumption, the only - // alternative would be to parse the - // string ourselves, which can be quite complicated, and would - // unnecessarily duplicate code - // already implemented by the format's parse() method. - // Convert the parsed value to a simple string (decimal point only) - // and use it to set the monetary value. - value= new BigDecimal(number.toString()); - } // Constructor Money(String) - /** - * - Constructs a Money object from a BigDecimal object. - * -

                - @param amount A BigDecimal value representing a - monetary amount, in dollars and - cents -

                - * - */ - public Money(BigDecimal amount) // A BigDecimal object representing a - // monetary amount - { - value= new BigDecimal(amount.toString()); // Set the monetary value. - } // Constructor Money(BigDecimal amount) - /** - * - Copy Constructor; constructs a Money object from another - Money object. - * -

                - @param amount A Money object -

                - * - */ - public Money(Money amount) // Money Object - { - roundingMode= amount.roundingMode; // Copy the Rounding Mode. - // Clone the Currency Format. - currencyFormat= (DecimalFormat)amount.currencyFormat.clone(); - value= amount.value; // Copy the Monetary Value. - } // Copy Constructor Money(Money money) - /** - * - Adds a specified monetary value to this monetary value. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @param money The monetary value to be added -

                - * -

                - @return A new Money object representing the - result -

                - * - */ - public Money add(Money money) // The Monetary Value to be added - { - Money result= new Money(this); // Create a new Money object for - // the result. - result.value= value.add(money.value); // Add the two monetary values. - return result; // Return the result. - } // Method Money.add() - /** - * - Subtracts a specified monetary value from this monetary value. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @param money The monetary value to be subtracted -

                - * -

                - @return A new Money object representing the - result -

                - * - */ - public Money subtract(Money money) // The monetary value to be - // subtracted - { - Money result= new Money(this); // Create a new Money object for - // the result. - result.value= value.subtract(money.value); // Subtract the specified - // monetary value. - return result; // Return the result. - } // Method Money.subtract() - /** - * - Multiplies this monetary value by a specified value. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @param mult The multiplier value -

                - * -

                - @return A new Money object representing the - result -

                - * - */ - public Money multiply(double mult) // The multiplier value - { - Money result= new Money(this); // Create a new Money object for - // the result. - // Multiply by the specified value. - result.value= value.multiply(new BigDecimal(mult)); - return result; // Return the result. - } // Method Money.multiply() - /** - * - Multiplies this monetary value by a specified value. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @param mult The multiplier value -

                - * -

                - @return A new Money object representing the - result -

                - * - */ - public Money multiply(long mult) // The multiplier value - { - Money result= new Money(this); // Create a new Money object for - // the result. - // Multiply by the specified value. - result.value= value.multiply(new BigDecimal(Long.toString(mult))); - return result; // Return the result. - } // Method Money.multiply() - /** - * - Divides this monetary value by a specified value. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @param div The divisor value -

                - * -

                - @return A new Money object representing the - result -

                - * - */ - public Money divide(double div) // The divisor value - { - Money result= new Money(this); // Create a new Money object for - // the result. - // Divide the monetary value by the specified value and round if - // necessary. - result.value= - value.divide(new BigDecimal(div), BigDecimal.ROUND_HALF_UP); - return result; // Return the result. - } // Method Money.divide() - /** - * - Divides this monetary value by a specified value. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @param div The divisor value -

                - * -

                - @return A new Money object representing the - result -

                - * - */ - public Money divide(long div) // The divisor value - { - Money result= new Money(this); // Create a new Money object for - // the result. - // Divide the monetary value by the specified value and round if - // necessary. - result.value= - value.divide( - new BigDecimal(Long.toString(div)), - BigDecimal.ROUND_HALF_UP); - return result; // Return the result. - } // Method Money.divide() - /** - * - Negates this monetary value. - * - Positive values become negative, and negative values become - positive. The effect is - the same as if the monetary value were multiplied by -1. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @return A new Money object representing the - result -

                - * - */ - public Money negate() { - Money result= new Money(this); // Create a new Money object for - // the result. - result.value= value.negate(); // Negate the monetary value. - return result; // Return the result. - } // Method Money.negate() - /** - * - Returns the absolute monetary value. - * - A positive value is returned, irrespective of whether the - monetary - value is positive or negative. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @return A new Money object representing the - result -

                - * - */ - public Money abs() { - Money result= new Money(this); // Create a new Money object for - // the result. - result.value= value.abs(); // Get the abolute monetary value. - return result; // Return the result. - } // Method Money.abs() - /** - * - Returns the monetary value as a long integer with 2 decimal - digits to the - right of an implicit decimal point. For example, if the - monetary value were $19.95, - a value of 1995 would be returned. Note that the monetary - value is - rounded, if necessary, according to the specified Rounding - Mode. - * -

                - @return The monetary value -

                - * - */ - public long toLong() { - // Round off the monetary value to 2 decimal places. - BigDecimal result= value.setScale(2, roundingMode); - result= result.movePointRight(2); // Move decimal point 2 places to - // the right to preserve cents. - return result.longValue(); // Return the result. - } // Method Money.toLong() - /** - * - Returns the monetary value as a double-precision, - floating-point value. - * -

                - Note: Exercise care when converting monetary values to - floating point - values, because floating-point arithmetic is not - well-suited for - use with monetary data. -

                - * -

                - @return The monetary value -

                - * - */ - public double toDouble() { - return value.doubleValue(); // Return the monetary value as a - // double floating point value. - } // Method Money.toDouble() - /** - * - Returns the monetary value as a BigDecimal object. - * -

                - @return The monetary value -

                - * - */ - public BigDecimal getValue() { - return value; // Return the monetary value. - } // Method Money.getValue() - /** - * - Returns the Rounding Mode. - * - Refer to Java's BigDecimal object for a description of the - possible - rounding modes. - * -

                - @return The Rounding Mode -

                - * - @see BigDecimal#ROUND_UP - @see BigDecimal#ROUND_DOWN - @see BigDecimal#ROUND_CEILING - @see BigDecimal#ROUND_FLOOR - @see BigDecimal#ROUND_HALF_UP - @see BigDecimal#ROUND_HALF_DOWN - @see BigDecimal#ROUND_HALF_EVEN - * - */ - public int getRoundingMode() { - return roundingMode; // Return the Rounding Mode. - } // Method Money.getRoundingMode() - /** - * - Sets the Rounding Mode. - * - Refer to the Java API documentation for the BigDecimal class - for a description of - the possible Rounding Modes. - * - The default Rounding Mode is - BigDecimal.ROUND_DOWN, which effectively discards any - fractional cent amount - and truncates the monetary value to 2 decimal places. - * - A Rounding Mode of BigDecimal.ROUND_UNNECESSARY is not valid - for use with - monetary data since certain operations result in a loss of - precision and - therefore require that a rounding mode be explicitly - specified. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @param int The Rounding Mode -

                - * -

                - @return A new Money object representing the - result -

                - * -

                - @throws InvalidRoundingModeException The Rounding Mode - specified is not valid for monetary data -

                - * - @see BigDecimal#ROUND_UP - @see BigDecimal#ROUND_DOWN - @see BigDecimal#ROUND_CEILING - @see BigDecimal#ROUND_FLOOR - @see BigDecimal#ROUND_HALF_UP - @see BigDecimal#ROUND_HALF_DOWN - @see BigDecimal#ROUND_HALF_EVEN - * - */ - public Money setRoundingMode(int mode) // Rounding Mode - throws InvalidRoundingModeException { - if (mode == BigDecimal.ROUND_UNNECESSARY) // If Rounding Mode is not - // valid, - throw new InvalidRoundingModeException( - "Rounding mode not valid for monetary data: " + mode); - Money result= new Money(this); // Create a new Money object for - // the result. - result.roundingMode= mode; // Set the Rounding Mode. - return result; // Return the result. - } // Method Money.setRoundingMode(int mode) - /** - * - Returns the Currency Format, used to format and parse - monetary values. - * - Refer to the Java API documentation for the DecimalFormat - class for information on - Decimal Formats in general, and Currency Formats in - particular. - * -

                - @return The Currency Format -

                - * - @see DecimalFormat - * - */ - public DecimalFormat getCurrencyFormat() { - return currencyFormat; // Return the Currency Format. - } // Method Money.getCurrencyFormat() - /** - * - Sets the Currency Format, used for formatting and parsing - monetary values. - * - Refer to the Java API documentation for the DecimalFormat - class for information on - Decimal Formats in general, and Currency Formats in particular. - * -

                - By default, the Currency Format for the current locale is - used. For the - United States, the default Currency Symbol is the Dollar Sign - ("$"). - Negative amounts are enclosed in parentheses. A Decimal Point - (".") - separates the dollars and cents, and a comma (",") separates - each group - of 3 consecutive digits in the dollar amount. -

                - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @param format The Currency Format -

                - * -

                - @return A new Money object representing the - result -

                - * - @see DecimalFormat - * - */ - public Money setCurrencyFormat(DecimalFormat format) // Currency Format - { - Money result= new Money(this); // Create a new Money object for - // the result. - result.currencyFormat= format; // Set the Currency Format. - return result; // Return the result. - } // Method Money.setCurrencyFormat() - /** - * - Returns a formatted string representation of the monetary - value. The format - of the string is determined by the Currency Format. - * - Refer to the Java API documentation for the DecimalFormat - class for information on - Decimal Formats in general, and Currency Formats in - particular. - * -

                - By default, the Currency Format for the current locale is - used. -

                - * -

                - @return A string representation of the - monetary value -

                - * - @see DecimalFormat - @see #setCurrencyFormat - * - */ - public String toString() { - // Round off the monetary value to 2 decimal places. - BigDecimal result= value.setScale(2, roundingMode); - return currencyFormat.format(result); // Format the result according - // to the Currency Format. - } - /** - * - Parses a string representation of a monetary value, returning - a new Money - object with the specified value. The format of the string - must be consistent - with the Currency Format; otherwise, a ParseException is - recognized. - * - Refer to the Java API documentation for the DecimalFormat - class for information on - Decimal Formats in general, and Currency Formats in - particular. - * -

                - The value of this object is not affected in any way. A new - object is returned reflecting the result of the operation. -

                - * -

                - @param string A String representing a monetary - value -

                - * -

                - @return A new Money object representating - the parsed monetary value -

                - * -

                - @throws ParseException The string is inconsistent with - the Currency Format -

                - * - @see DecimalFormat - @see #setCurrencyFormat - * - */ - public Money parse(String string) // String representing a Monetary - // Value - throws ParseException // If string is inconsistent with - // Currency Format - { - Money result= new Money(this); // Create a Money object for the - // result. - // We make use of the format's parse() method, which parses the string - // according to the format and returns either a Double or a Long - // object representing the value. - Number number; - // Attempt to parse the string as a monetary value. May throw a - // ParseException if the string is not consistent with the Currency - // Format. - number= currencyFormat.parse(string); - // In general, a double floating point value cannot represent a - // decimal value exactly, and - // therefore is only a very close approximation of the actual decimal - // value. Fortunately, - // the Double.toString() method is able to "recognize" an approximated - // decimal value, and - // will return the original (approximated) decimal value, rather than - // the literal floating - // point value. Here, we take advantage of this fact to obtain a - // string representation of - // the monetary value (without formatting, except for the decimal - // point), which we then use - // to create a BigDecimal object having the required value. - // Were we not to make this simplifying assumption, the only - // alternative would be to parse the - // string ourselves, which can be quite complicated, and would - // unnecessarily duplicate code - // already implemented by the format's parse() method. - // Convert the parsed value to a simple string (decimal point only) - // and use it to set the monetary value. - result.value= new BigDecimal(number.toString()); - return result; // Return the new Money object. - } // Method Money.parse() - /** - * - Returns an indication of whether or not this monetary value - is zero (equal to $0.00). - * -

                - @return true The monetary value is zero -
                - false The monetary value is not zero -

                - * - */ - public boolean isZero() { - return (value.compareTo(ZERO) == 0); // Return true if value is zero. - } // Method Money.isZero() - /** - * - Returns an indication of whether or not this monetary value - is negative (less than $0.00). - * -

                - @return true The monetary value is negative -
                - false The monetary value is not negative -

                - * - */ - public boolean isNegative() { - return (value.compareTo(ZERO) < 0); // Return true if value is less - // than zero. - } // Method Money.isNegative() - /** - * - Returns an indication of whether or not this monetary value - is positive (greater than or equal to $0.00). - * -

                - @return true The monetary value is positive -
                - false The monetary value is not positive -

                - * - */ - public boolean isPositive() { - return (value.compareTo(ZERO) >= 0); // Return true if value is - // greater than zero. - } // Method Money.isPositive() - /** - * - Returns an indication of whether or not this monetary value - is equal to - another monetary value. - * -

                - @param other A monetary value with which this - monetary value is to be compared -

                - * -

                - @return true This monetary values are equal -
                - false This monetary values are not equal -

                - * - */ - public boolean isEqual(Money other) // Monetary value for comparison - { - return (value.compareTo(other.value) == 0); // Return true if equal to - // the other amount. - } // Method Money.isEqual() - /** - * - Returns an indication of whether or not this monetary value - is less than - another monetary value. - * -

                - @param other A monetary value with which this - monetary value is to be compared -

                - * -

                - @return true This monetary value is less than - the specified monetary value -
                - false This monetary value is not less - than the specified monetary value -

                - * - */ - public boolean isLessThan(Money other) // Monetary value for - // comparison - { - return (value.compareTo(other.value) < 0); // Return true if less than - // the other amount. - } // Method Money.isLessThan() - /** - * - Returns an indication of whether or not this monetary value - is less than - or equal to another monetary value. - * -

                - @param other A monetary value with which this - monetary value is to be compared -

                - * -

                - @return true This monetary value is less than - or equal to the specified monetary - value -
                - false This monetary value is not less - than or equal to the specified - monetary value -

                - * - */ - public boolean isLessThanOrEqual(Money other) // Monetary value for - // comparison - { - // Return true if less than or equal to the other amount. - return (value.compareTo(other.value) <= 0); - } // Method Money.isLessThanOrEqual() - /** - * - Returns an indication of whether or not this monetary value - is greater than - another monetary value. - * -

                - @param other A monetary value with which this - monetary value is to be compared -

                - * -

                - @return true This monetary value is greater - than the specified monetary value -
                - false This monetary value is not greater - than the specified monetary value -

                - * - */ - public boolean isGreaterThan(Money other) // Monetary value for - // comparison - { - // Return true if greater than the other amount. - return (value.compareTo(other.value) > 0); - } // Method Money.isGreaterThan() - /** - * - Returns an indication of whether or not this monetary value - is greater than - or equal to another monetary value. - * -

                - @param other A monetary value with which this - monetary value is to be compared -

                - * -

                - @return true This monetary value is greater - than or equal to the specified - monetary value -
                - false This monetary value is not greater - than or equal to the specified - monetary value -

                - * - */ - public boolean isGreaterThanOrEqual(Money other) // Monetary value for - // comparison - { - // Return true if greater than or equal to the other amount. - return (value.compareTo(other.value) >= 0); - } // Method Money.isGreaterThanOrEqual() - /** - * - Compares this object with the specified object. The objects - are equal - if and only if the specified object is not null, is a Money - object, and has - the same monetary value as this object. - * -

                - @param object Some object -

                - * -

                - @return true The objects are equal -
                - false The objects are not equal -

                - * - */ - public boolean equals(Object object) // Object to compare - { - if (object == this) // If the object is this object, - return true; // the objects are equal by - // definition. - if (object == null) // If the object is null, - return false; // the objects are not equal by - // definition. - if (!(object instanceof Money)) // If the object is not an instance - // of Money, - return false; // the objects are not equal by - // definition. - // Return true if monetary values are the same. - return (value.compareTo(((Money)object).value) == 0); - } // Method Money.equals() - /** Returns a hashcode for this object. The hashcode is identical - to that for the BigDecimal - object that represents the monetary value. - -

                - @return The hashcode -

                - */ - public int hashCode() { - return value.hashCode(); // Return the hashcode for the - } - /**Clones a Money object. The new object is an exact copy of - this object, and inherits the object's monetary value and Currency Format. - -

                - @return Money The cloned object -

                - */ - public Object clone() { - Money result= new Money(this); // Create a copy of this Money object. - return result; // Return the cloned object. - } + /** + * serialVersionUID + */ + private static final long serialVersionUID = 2055249101317798370L; + /** + * The monetary value. + */ + protected BigDecimal value = null; // The monetary value + /** + * The Rounding Mode. Specifies if and how the monetary value is + * to be rounded off to an integral cent. + */ + protected int roundingMode = BigDecimal.ROUND_DOWN; // Rounding Mode + /** + * The Currency Format, used for formatting and parsing a + * monetary value. Refer to the Java API + * documentation for the DecimalFormat class for information on + * formats. + */ + protected DecimalFormat currencyFormat = + (DecimalFormat) NumberFormat.getCurrencyInstance(); + // Currency format + /** + * The special monetary value of zero ($0.00). + */ + protected static final BigDecimal ZERO = new BigDecimal("0.00"); + // The value $0.00 + + /** + * Class InvalidScaleFactorException + * + *

                + * The InvalidScaleFactorException is thrown if an invalid scale + * factor is specified (valid scale factors are 0, 1, and 2). + * This is a non-checked exception and will be detected at + * runtime only. + *

                + */ + public static class InvalidScaleFactorException + extends RuntimeException // Non-checked exception + { + /** + * serialVersionUID + */ + private static final long serialVersionUID = -8038085965896123803L; + + /** + * Default constructor for a InvalidScaleFactorException object. + */ + public InvalidScaleFactorException() { + super(); // Invoke super class constructor. + } + + /** + * Constructor for a InvalidScaleFactorException object. + * + *

                + * + * @param info Descriptive information + *

                + */ + public InvalidScaleFactorException(String info) // Descriptive info + { + super(info); // Invoke super class constructor. + } + } // Class InvalidScaleFactorException + + /** + * Class InvalidRoundingModeException + * + *

                + * The InvalidRoundingModeException is thrown if an invalid + * Rounding + * Mode is specified (all rounding modes except + * ROUND_UNNECESSARY are valid). + * This is a non-checked exception and will be detected at + * runtime only. + *

                + * + * @see BigDecimal#ROUND_UP + * @see BigDecimal#ROUND_DOWN + * @see BigDecimal#ROUND_CEILING + * @see BigDecimal#ROUND_FLOOR + * @see BigDecimal#ROUND_HALF_UP + * @see BigDecimal#ROUND_HALF_DOWN + * @see BigDecimal#ROUND_HALF_EVEN + */ + public static class InvalidRoundingModeException + extends RuntimeException // Non-checked exception + { + /** + * serialVersionUID + */ + private static final long serialVersionUID = 5658836125641516151L; + + /** + * Default constructor for a InvalidRoundingModeException object. + */ + public InvalidRoundingModeException() { + super(); // Invoke super class constructor. + } // Default Constructor InvalidRoundingModeException() + + /** + * Constructor for a InvalidRoundingModeException object. + * + *

                + * + * @param info Descriptive information + *

                + */ + public InvalidRoundingModeException(String info) // Descriptive info + { + super(info); // Invoke super class constructor. + } + } // Class InvalidRoundingModeException + + /** + * Default Constructor for a Money object; creates an object + * whose value is $0.00. + */ + public Money() { + value = ZERO; // Initialize the monetary value + // to $0.00. + } // Default Constructor Money() + + /** + * Constructs a Money object from a double-precision, + * floating-point value. + * The Currency Format is set to the default format for the + * current locale. + * + *

                + * The integral part of the value represents whole dollars, and + * the fractional + * part of the value represents fractional dollars (cents). As + * an example, + * the value 19.95 would represent $19.95. + *

                + * + *

                + * + * @param amount The monetary amount, in dollars + * and cents + *

                + */ + public Money(double amount) // Monetary Value, dollars and cents + { + // In general, a double floating point value cannot represent a + // decimal value exactly, and + // therefore is only a very close approximation of the actual decimal + // value. Fortunately, + // the Double.toString() method is able to "recognize" an approximated + // decimal value, and + // will return the original (approximated) decimal value, rather than + // the literal floating + // point value. Here, we take advantage of this fact to obtain a + // string representation of + // the monetary value (without formatting, except for the decimal + // point), which we then use + // to create a BigDecimal object having the required value. + // Were we not to make this simplifying assumption, the only + // alternative would be to parse the + // string ourselves, which can be quite complicated, and would + // unnecessarily duplicate code + // already implemented by the format's parse() method. + // Convert the parsed value to a simple string (decimal point only) + // and use it to set the monetary value. + value = new BigDecimal(Double.toString(amount)); + } // Constructor Money(double amount) + + /** + * Constructs a Money object from a long integer value. The + * specified + * value represents whole dollars only (that is, cents are + * implicity assumed + * to be 00). For example, the integer 25 would represent a + * monetary value of $25.00. + * + *

                + * + * @param amount The monetary amount, in whole + * dollars (no cents) + *

                + */ + public Money(long amount) // Monetary amount, whole dollars (no cents) + { + value = new BigDecimal(Long.toString(amount)); // Set monetary value. + } // Constructor Money(long amount) + + /** + * Constructs a Money object from a long integer value with a + * specified scale factor. + * The scale factor (0, 1, or 2) specifies the number of digits + * to the right of an + * implied decimal point. + * + *

                + * For example, the value 1995 would be interpreted as a + * monetary value of + * $1995.00, $199.50, and $19.95 for scale factors of 0, 1, or + * 2, respectively. + *

                + * + *

                + * + * @param amount The monetary amount, in dollars + * and cents + * @param scale Scale factor (must be 0, 1, or 2) + *

                + * + *

                + * @throws InvalidScaleFactorException The scale factor + * specified is not valid (must be 0, 1, or 2) + *

                + */ + public Money(long amount, // Montetary amount + int scale) // Scale Factor (0, 1, 2) + throws InvalidScaleFactorException { + if (// If the Scale Factor is not + (scale < 0) // 0, 1, or + || (scale > 2) // 2, + ) + throw new InvalidScaleFactorException( + "Invalid scale factor: " + scale + " (must be 0, 1, or 2)"); + // Set the monetary value and scale as specified. + value = (new BigDecimal(Long.toString(amount))).movePointLeft(scale); + } // Constructor Money(long amount, int scale) + + /** + * Constructs a Money object from a string representation of a + * monetary value. The + * format of the string must be consistent with the Currency + * Format; otherwise, + * a ParseException is recognized. + *

                + * Refer to the Java API documentation for the DecimalFormat + * class for information on + * Decimal Formats in general, and Currency Formats in + * particular. + * + *

                + * + * @param string A String representing a monetary + * value + *

                + * + *

                + * @throws ParseException The string is inconsistent with + * the Currency Format + *

                + * @see DecimalFormat + * @see #setCurrencyFormat + */ + public Money(String string) // String representing a Monetary Value + throws ParseException // If string is inconsistent with + // Currency Format + { + // We make use of the format's parse() method, which parses the string + // according to the format and returns either a Double or a Long + // object representing the value. + Number number; + // Attempt to parse the string as a monetary value. May throw + // ParseException if the string is not consistent with the Currency + // Format. + number = currencyFormat.parse(string); + // In general, a double floating point value cannot represent a + // decimal value exactly, and + // therefore is only a very close approximation of the actual decimal + // value. Fortunately, + // the Double.toString() method is able to "recognize" an approximated + // decimal value, and + // will return the original (approximated) decimal value, rather than + // the literal floating + // point value. Here, we take advantage of this fact to obtain a + // string representation of + // the monetary value (without formatting, except for the decimal + // point), which we then use + // to create a BigDecimal object having the required value. + // Were we not to make this simplifying assumption, the only + // alternative would be to parse the + // string ourselves, which can be quite complicated, and would + // unnecessarily duplicate code + // already implemented by the format's parse() method. + // Convert the parsed value to a simple string (decimal point only) + // and use it to set the monetary value. + value = new BigDecimal(number.toString()); + } // Constructor Money(String) + + /** + * Constructs a Money object from a BigDecimal object. + * + *

                + * + * @param amount A BigDecimal value representing a + * monetary amount, in dollars and + * cents + *

                + */ + public Money(BigDecimal amount) // A BigDecimal object representing a + // monetary amount + { + value = new BigDecimal(amount.toString()); // Set the monetary value. + } // Constructor Money(BigDecimal amount) + + /** + * Copy Constructor; constructs a Money object from another + * Money object. + * + *

                + * + * @param amount A Money object + *

                + */ + public Money(Money amount) // Money Object + { + roundingMode = amount.roundingMode; // Copy the Rounding Mode. + // Clone the Currency Format. + currencyFormat = (DecimalFormat) amount.currencyFormat.clone(); + value = amount.value; // Copy the Monetary Value. + } // Copy Constructor Money(Money money) + + /** + * Adds a specified monetary value to this monetary value. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @param money The monetary value to be added + *

                + * + *

                + * @return A new Money object representing the + * result + *

                + */ + public Money add(Money money) // The Monetary Value to be added + { + Money result = new Money(this); // Create a new Money object for + // the result. + result.value = value.add(money.value); // Add the two monetary values. + return result; // Return the result. + } // Method Money.add() + + /** + * Subtracts a specified monetary value from this monetary value. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @param money The monetary value to be subtracted + *

                + * + *

                + * @return A new Money object representing the + * result + *

                + */ + public Money subtract(Money money) // The monetary value to be + // subtracted + { + Money result = new Money(this); // Create a new Money object for + // the result. + result.value = value.subtract(money.value); // Subtract the specified + // monetary value. + return result; // Return the result. + } // Method Money.subtract() + + /** + * Multiplies this monetary value by a specified value. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @param mult The multiplier value + *

                + * + *

                + * @return A new Money object representing the + * result + *

                + */ + public Money multiply(double mult) // The multiplier value + { + Money result = new Money(this); // Create a new Money object for + // the result. + // Multiply by the specified value. + result.value = value.multiply(new BigDecimal(mult)); + return result; // Return the result. + } // Method Money.multiply() + + /** + * Multiplies this monetary value by a specified value. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @param mult The multiplier value + *

                + * + *

                + * @return A new Money object representing the + * result + *

                + */ + public Money multiply(long mult) // The multiplier value + { + Money result = new Money(this); // Create a new Money object for + // the result. + // Multiply by the specified value. + result.value = value.multiply(new BigDecimal(Long.toString(mult))); + return result; // Return the result. + } // Method Money.multiply() + + /** + * Divides this monetary value by a specified value. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @param div The divisor value + *

                + * + *

                + * @return A new Money object representing the + * result + *

                + */ + public Money divide(double div) // The divisor value + { + Money result = new Money(this); // Create a new Money object for + // the result. + // Divide the monetary value by the specified value and round if + // necessary. + result.value = + value.divide(new BigDecimal(div), BigDecimal.ROUND_HALF_UP); + return result; // Return the result. + } // Method Money.divide() + + /** + * Divides this monetary value by a specified value. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @param div The divisor value + *

                + * + *

                + * @return A new Money object representing the + * result + *

                + */ + public Money divide(long div) // The divisor value + { + Money result = new Money(this); // Create a new Money object for + // the result. + // Divide the monetary value by the specified value and round if + // necessary. + result.value = + value.divide( + new BigDecimal(Long.toString(div)), + BigDecimal.ROUND_HALF_UP); + return result; // Return the result. + } // Method Money.divide() + + /** + * Negates this monetary value. + *

                + * Positive values become negative, and negative values become + * positive. The effect is + * the same as if the monetary value were multiplied by -1. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @return A new Money object representing the + * result + *

                + */ + public Money negate() { + Money result = new Money(this); // Create a new Money object for + // the result. + result.value = value.negate(); // Negate the monetary value. + return result; // Return the result. + } // Method Money.negate() + + /** + * Returns the absolute monetary value. + *

                + * A positive value is returned, irrespective of whether the + * monetary + * value is positive or negative. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @return A new Money object representing the + * result + *

                + */ + public Money abs() { + Money result = new Money(this); // Create a new Money object for + // the result. + result.value = value.abs(); // Get the abolute monetary value. + return result; // Return the result. + } // Method Money.abs() + + /** + * Returns the monetary value as a long integer with 2 decimal + * digits to the + * right of an implicit decimal point. For example, if the + * monetary value were $19.95, + * a value of 1995 would be returned. Note that the monetary + * value is + * rounded, if necessary, according to the specified Rounding + * Mode. + * + *

                + * + * @return The monetary value + *

                + */ + public long toLong() { + // Round off the monetary value to 2 decimal places. + BigDecimal result = value.setScale(2, roundingMode); + result = result.movePointRight(2); // Move decimal point 2 places to + // the right to preserve cents. + return result.longValue(); // Return the result. + } // Method Money.toLong() + + /** + * Returns the monetary value as a double-precision, + * floating-point value. + * + *

                + * Note: Exercise care when converting monetary values to + * floating point + * values, because floating-point arithmetic is not + * well-suited for + * use with monetary data. + *

                + * + *

                + * + * @return The monetary value + *

                + */ + public double toDouble() { + return value.doubleValue(); // Return the monetary value as a + // double floating point value. + } // Method Money.toDouble() + + /** + * Returns the monetary value as a BigDecimal object. + * + *

                + * + * @return The monetary value + *

                + */ + public BigDecimal getValue() { + return value; // Return the monetary value. + } // Method Money.getValue() + + /** + * Returns the Rounding Mode. + *

                + * Refer to Java's BigDecimal object for a description of the + * possible + * rounding modes. + * + *

                + * + * @return The Rounding Mode + *

                + * @see BigDecimal#ROUND_UP + * @see BigDecimal#ROUND_DOWN + * @see BigDecimal#ROUND_CEILING + * @see BigDecimal#ROUND_FLOOR + * @see BigDecimal#ROUND_HALF_UP + * @see BigDecimal#ROUND_HALF_DOWN + * @see BigDecimal#ROUND_HALF_EVEN + */ + public int getRoundingMode() { + return roundingMode; // Return the Rounding Mode. + } // Method Money.getRoundingMode() + + /** + * Sets the Rounding Mode. + *

                + * Refer to the Java API documentation for the BigDecimal class + * for a description of + * the possible Rounding Modes. + *

                + * The default Rounding Mode is + * BigDecimal.ROUND_DOWN, which effectively discards any + * fractional cent amount + * and truncates the monetary value to 2 decimal places. + *

                + * A Rounding Mode of BigDecimal.ROUND_UNNECESSARY is not valid + * for use with + * monetary data since certain operations result in a loss of + * precision and + * therefore require that a rounding mode be explicitly + * specified. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @param int The Rounding Mode + *

                + * + *

                + * @return A new Money object representing the + * result + *

                + * + *

                + * @throws InvalidRoundingModeException The Rounding Mode + * specified is not valid for monetary data + *

                + * @see BigDecimal#ROUND_UP + * @see BigDecimal#ROUND_DOWN + * @see BigDecimal#ROUND_CEILING + * @see BigDecimal#ROUND_FLOOR + * @see BigDecimal#ROUND_HALF_UP + * @see BigDecimal#ROUND_HALF_DOWN + * @see BigDecimal#ROUND_HALF_EVEN + */ + public Money setRoundingMode(int mode) // Rounding Mode + throws InvalidRoundingModeException { + if (mode == BigDecimal.ROUND_UNNECESSARY) // If Rounding Mode is not + // valid, + throw new InvalidRoundingModeException( + "Rounding mode not valid for monetary data: " + mode); + Money result = new Money(this); // Create a new Money object for + // the result. + result.roundingMode = mode; // Set the Rounding Mode. + return result; // Return the result. + } // Method Money.setRoundingMode(int mode) + + /** + * Returns the Currency Format, used to format and parse + * monetary values. + *

                + * Refer to the Java API documentation for the DecimalFormat + * class for information on + * Decimal Formats in general, and Currency Formats in + * particular. + * + *

                + * + * @return The Currency Format + *

                + * @see DecimalFormat + */ + public DecimalFormat getCurrencyFormat() { + return currencyFormat; // Return the Currency Format. + } // Method Money.getCurrencyFormat() + + /** + * Sets the Currency Format, used for formatting and parsing + * monetary values. + *

                + * Refer to the Java API documentation for the DecimalFormat + * class for information on + * Decimal Formats in general, and Currency Formats in particular. + * + *

                + * By default, the Currency Format for the current locale is + * used. For the + * United States, the default Currency Symbol is the Dollar Sign + * ("$"). + * Negative amounts are enclosed in parentheses. A Decimal Point + * (".") + * separates the dollars and cents, and a comma (",") separates + * each group + * of 3 consecutive digits in the dollar amount. + *

                + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @param format The Currency Format + *

                + * + *

                + * @return A new Money object representing the + * result + *

                + * @see DecimalFormat + */ + public Money setCurrencyFormat(DecimalFormat format) // Currency Format + { + Money result = new Money(this); // Create a new Money object for + // the result. + result.currencyFormat = format; // Set the Currency Format. + return result; // Return the result. + } // Method Money.setCurrencyFormat() + + /** + * Returns a formatted string representation of the monetary + * value. The format + * of the string is determined by the Currency Format. + *

                + * Refer to the Java API documentation for the DecimalFormat + * class for information on + * Decimal Formats in general, and Currency Formats in + * particular. + * + *

                + * By default, the Currency Format for the current locale is + * used. + *

                + * + *

                + * + * @return A string representation of the + * monetary value + *

                + * @see DecimalFormat + * @see #setCurrencyFormat + */ + public String toString() { + // Round off the monetary value to 2 decimal places. + BigDecimal result = value.setScale(2, roundingMode); + return currencyFormat.format(result); // Format the result according + // to the Currency Format. + } + + /** + * Parses a string representation of a monetary value, returning + * a new Money + * object with the specified value. The format of the string + * must be consistent + * with the Currency Format; otherwise, a ParseException is + * recognized. + *

                + * Refer to the Java API documentation for the DecimalFormat + * class for information on + * Decimal Formats in general, and Currency Formats in + * particular. + * + *

                + * The value of this object is not affected in any way. A new + * object is returned reflecting the result of the operation. + *

                + * + *

                + * + * @param string A String representing a monetary + * value + *

                + * + *

                + * @return A new Money object representating + * the parsed monetary value + *

                + * + *

                + * @throws ParseException The string is inconsistent with + * the Currency Format + *

                + * @see DecimalFormat + * @see #setCurrencyFormat + */ + public Money parse(String string) // String representing a Monetary + // Value + throws ParseException // If string is inconsistent with + // Currency Format + { + Money result = new Money(this); // Create a Money object for the + // result. + // We make use of the format's parse() method, which parses the string + // according to the format and returns either a Double or a Long + // object representing the value. + Number number; + // Attempt to parse the string as a monetary value. May throw a + // ParseException if the string is not consistent with the Currency + // Format. + number = currencyFormat.parse(string); + // In general, a double floating point value cannot represent a + // decimal value exactly, and + // therefore is only a very close approximation of the actual decimal + // value. Fortunately, + // the Double.toString() method is able to "recognize" an approximated + // decimal value, and + // will return the original (approximated) decimal value, rather than + // the literal floating + // point value. Here, we take advantage of this fact to obtain a + // string representation of + // the monetary value (without formatting, except for the decimal + // point), which we then use + // to create a BigDecimal object having the required value. + // Were we not to make this simplifying assumption, the only + // alternative would be to parse the + // string ourselves, which can be quite complicated, and would + // unnecessarily duplicate code + // already implemented by the format's parse() method. + // Convert the parsed value to a simple string (decimal point only) + // and use it to set the monetary value. + result.value = new BigDecimal(number.toString()); + return result; // Return the new Money object. + } // Method Money.parse() + + /** + * Returns an indication of whether or not this monetary value + * is zero (equal to $0.00). + * + *

                + * + * @return true The monetary value is zero + *
                + * false The monetary value is not zero + *

                + */ + public boolean isZero() { + return (value.compareTo(ZERO) == 0); // Return true if value is zero. + } // Method Money.isZero() + + /** + * Returns an indication of whether or not this monetary value + * is negative (less than $0.00). + * + *

                + * + * @return true The monetary value is negative + *
                + * false The monetary value is not negative + *

                + */ + public boolean isNegative() { + return (value.compareTo(ZERO) < 0); // Return true if value is less + // than zero. + } // Method Money.isNegative() + + /** + * Returns an indication of whether or not this monetary value + * is positive (greater than or equal to $0.00). + * + *

                + * + * @return true The monetary value is positive + *
                + * false The monetary value is not positive + *

                + */ + public boolean isPositive() { + return (value.compareTo(ZERO) >= 0); // Return true if value is + // greater than zero. + } // Method Money.isPositive() + + /** + * Returns an indication of whether or not this monetary value + * is equal to + * another monetary value. + * + *

                + * + * @param other A monetary value with which this + * monetary value is to be compared + *

                + * + *

                + * @return true This monetary values are equal + *
                + * false This monetary values are not equal + *

                + */ + public boolean isEqual(Money other) // Monetary value for comparison + { + return (value.compareTo(other.value) == 0); // Return true if equal to + // the other amount. + } // Method Money.isEqual() + + /** + * Returns an indication of whether or not this monetary value + * is less than + * another monetary value. + * + *

                + * + * @param other A monetary value with which this + * monetary value is to be compared + *

                + * + *

                + * @return true This monetary value is less than + * the specified monetary value + *
                + * false This monetary value is not less + * than the specified monetary value + *

                + */ + public boolean isLessThan(Money other) // Monetary value for + // comparison + { + return (value.compareTo(other.value) < 0); // Return true if less than + // the other amount. + } // Method Money.isLessThan() + + /** + * Returns an indication of whether or not this monetary value + * is less than + * or equal to another monetary value. + * + *

                + * + * @param other A monetary value with which this + * monetary value is to be compared + *

                + * + *

                + * @return true This monetary value is less than + * or equal to the specified monetary + * value + *
                + * false This monetary value is not less + * than or equal to the specified + * monetary value + *

                + */ + public boolean isLessThanOrEqual(Money other) // Monetary value for + // comparison + { + // Return true if less than or equal to the other amount. + return (value.compareTo(other.value) <= 0); + } // Method Money.isLessThanOrEqual() + + /** + * Returns an indication of whether or not this monetary value + * is greater than + * another monetary value. + * + *

                + * + * @param other A monetary value with which this + * monetary value is to be compared + *

                + * + *

                + * @return true This monetary value is greater + * than the specified monetary value + *
                + * false This monetary value is not greater + * than the specified monetary value + *

                + */ + public boolean isGreaterThan(Money other) // Monetary value for + // comparison + { + // Return true if greater than the other amount. + return (value.compareTo(other.value) > 0); + } // Method Money.isGreaterThan() + + /** + * Returns an indication of whether or not this monetary value + * is greater than + * or equal to another monetary value. + * + *

                + * + * @param other A monetary value with which this + * monetary value is to be compared + *

                + * + *

                + * @return true This monetary value is greater + * than or equal to the specified + * monetary value + *
                + * false This monetary value is not greater + * than or equal to the specified + * monetary value + *

                + */ + public boolean isGreaterThanOrEqual(Money other) // Monetary value for + // comparison + { + // Return true if greater than or equal to the other amount. + return (value.compareTo(other.value) >= 0); + } // Method Money.isGreaterThanOrEqual() + + /** + * Compares this object with the specified object. The objects + * are equal + * if and only if the specified object is not null, is a Money + * object, and has + * the same monetary value as this object. + * + *

                + * + * @param object Some object + *

                + * + *

                + * @return true The objects are equal + *
                + * false The objects are not equal + *

                + */ + public boolean equals(Object object) // Object to compare + { + if (object == this) // If the object is this object, + return true; // the objects are equal by + // definition. + if (object == null) // If the object is null, + return false; // the objects are not equal by + // definition. + if (!(object instanceof Money)) // If the object is not an instance + // of Money, + return false; // the objects are not equal by + // definition. + // Return true if monetary values are the same. + return (value.compareTo(((Money) object).value) == 0); + } // Method Money.equals() + + /** + * Returns a hashcode for this object. The hashcode is identical + * to that for the BigDecimal + * object that represents the monetary value. + * + *

                + * + * @return The hashcode + *

                + */ + public int hashCode() { + return value.hashCode(); // Return the hashcode for the + } + + /** + * Clones a Money object. The new object is an exact copy of + * this object, and inherits the object's monetary value and Currency Format. + * + *

                + * + * @return Money The cloned object + *

                + */ + public Object clone() { + Money result = new Money(this); // Create a copy of this Money object. + return result; // Return the cloned object. + } } // Class Money diff --git a/src/main/java/io/starter/toolkit/OutFile.java b/src/main/java/io/starter/toolkit/OutFile.java index 4e92d50..b8cdaec 100644 --- a/src/main/java/io/starter/toolkit/OutFile.java +++ b/src/main/java/io/starter/toolkit/OutFile.java @@ -2,40 +2,40 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ package io.starter.toolkit; + import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -public class OutFile extends java.io.DataOutputStream -{ +public class OutFile extends java.io.DataOutputStream { public OutFile(String filename) - throws IOException { - super(new BufferedOutputStream(new FileOutputStream(filename))); + throws IOException { + super(new BufferedOutputStream(new FileOutputStream(filename))); } public OutFile(File file) - throws IOException { - this(file.getPath()); + throws IOException { + this(file.getPath()); } } diff --git a/src/main/java/io/starter/toolkit/ProgressListener.java b/src/main/java/io/starter/toolkit/ProgressListener.java index 98649b7..ca83783 100644 --- a/src/main/java/io/starter/toolkit/ProgressListener.java +++ b/src/main/java/io/starter/toolkit/ProgressListener.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,13 +23,18 @@ package io.starter.toolkit; -public interface ProgressListener -{ +public interface ProgressListener { void setMaxProgress(int i); + boolean updateProgress(); + void addTarget(ProgressNotifier t); + void removeTarget(ProgressNotifier t); + void clear(); + void setDisplayText(String t); - void setValue(int progress); + + void setValue(int progress); } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/ProgressNotifier.java b/src/main/java/io/starter/toolkit/ProgressNotifier.java index 18a3192..b16f54c 100644 --- a/src/main/java/io/starter/toolkit/ProgressNotifier.java +++ b/src/main/java/io/starter/toolkit/ProgressNotifier.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -23,14 +23,19 @@ package io.starter.toolkit; -public interface ProgressNotifier -{ +public interface ProgressNotifier { void register(ProgressListener j); + void fireProgressChanged(); + int getProgress(); + void setProgress(int progress); + String getProgressText(); + void setProgressText(String ptext); + boolean iscompleted(); - + } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/Recyclable.java b/src/main/java/io/starter/toolkit/Recyclable.java index e5cd58d..6a38f68 100644 --- a/src/main/java/io/starter/toolkit/Recyclable.java +++ b/src/main/java/io/starter/toolkit/Recyclable.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,21 +22,23 @@ */ package io.starter.toolkit; -public interface Recyclable -{ - /** remove all data and prepare for - re-use - */ - public Recyclable recycle(); - - /** returns whether this object - is in use - */ - public boolean inUse(); - - /** returns the number of times - this object has been reused - */ - public int getNumTimesReused(); - +public interface Recyclable { + /** + * remove all data and prepare for + * re-use + */ + Recyclable recycle(); + + /** + * returns whether this object + * is in use + */ + boolean inUse(); + + /** + * returns the number of times + * this object has been reused + */ + int getNumTimesReused(); + } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/RecycleBin.java b/src/main/java/io/starter/toolkit/RecycleBin.java index 6a562db..fbc85c6 100644 --- a/src/main/java/io/starter/toolkit/RecycleBin.java +++ b/src/main/java/io/starter/toolkit/RecycleBin.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,39 +22,44 @@ */ package io.starter.toolkit; -import java.util.*; - -public interface RecycleBin -{ - - /** add a Recyclable to the - bin - */ - public void addItem(Recyclable r) throws RecycleBinFullException; - - /** add a Recyclable to the - bin - */ - public void addItem(Object key, Recyclable r) throws RecycleBinFullException; - - /** get an unused Recyclable item from the bin - * @throws RecycleBinFullException - */ - public Recyclable getItem() throws RecycleBinFullException; - - /** get all of the items in this bin - */ - public List getAll(); - - /** empty the current contents of the bin - */ - public void empty(); - - /** set the maximum number of items for this - bin - */ - public void setMaxItems(int i); - - - +import java.util.List; + +public interface RecycleBin { + + /** + * add a Recyclable to the + * bin + */ + void addItem(Recyclable r) throws RecycleBinFullException; + + /** + * add a Recyclable to the + * bin + */ + void addItem(Object key, Recyclable r) throws RecycleBinFullException; + + /** + * get an unused Recyclable item from the bin + * + * @throws RecycleBinFullException + */ + Recyclable getItem() throws RecycleBinFullException; + + /** + * get all of the items in this bin + */ + List getAll(); + + /** + * empty the current contents of the bin + */ + void empty(); + + /** + * set the maximum number of items for this + * bin + */ + void setMaxItems(int i); + + } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/RecycleBinFullException.java b/src/main/java/io/starter/toolkit/RecycleBinFullException.java index c1d2f87..3c876ec 100644 --- a/src/main/java/io/starter/toolkit/RecycleBinFullException.java +++ b/src/main/java/io/starter/toolkit/RecycleBinFullException.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,26 +22,25 @@ */ package io.starter.toolkit; -public class RecycleBinFullException extends java.lang.Exception -{ - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3065931314209459102L; - String err = "Too many items in Cache."; - - public RecycleBinFullException(String errmsg){ +public class RecycleBinFullException extends java.lang.Exception { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = 3065931314209459102L; + String err = "Too many items in Cache."; + + public RecycleBinFullException(String errmsg) { super(); err = errmsg; } - - public RecycleBinFullException(){ + + public RecycleBinFullException() { super(); } - - public String toString(){ + + public String toString() { return err; } - + } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/ResourceLoader.java b/src/main/java/io/starter/toolkit/ResourceLoader.java index 8a36159..899d599 100644 --- a/src/main/java/io/starter/toolkit/ResourceLoader.java +++ b/src/main/java/io/starter/toolkit/ResourceLoader.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,13 +22,10 @@ */ package io.starter.toolkit; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; +import io.starter.OpenXLS.GetInfo; +import io.starter.naming.InitialContextImpl; + +import java.io.*; import java.lang.reflect.Method; import java.net.URL; import java.net.URLDecoder; @@ -39,529 +36,512 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; -import io.starter.OpenXLS.GetInfo; -import io.starter.naming.InitialContextImpl; - /** * Resource Loader which implements a basic JNDI Context and performs: - * + * *
              • Classloading mapped to variable names in properties files allows for easy * abstraction of implementation classes *
              • Configuration strings loaded from properties files *
              • Arbitrary resource binding - * - * */ public class ResourceLoader extends InitialContextImpl implements Serializable, javax.naming.Context { - /** - * - * - */ - private static final long serialVersionUID = 12345245254L; - private String resloc = ""; - private File propsfile = null; - public static boolean DEBUG = false; - private Properties resources = new Properties(); - - @Override - public String toString() { - return "Extentech ResourceLoader v." + ResourceLoader.getVersion(); - } - - public static String getVersion() { - return GetInfo.getVersion(); - } - - public Enumeration getKeys() { - if (!snagged) - return resources.keys(); - return env.keys(); - } - - public Object getObject(String key) { - if (!snagged) - return resources.get(key); - return env.get(key); - } - - private boolean snagged = false; - - /** - * put the properties file vals in the ResourceLoader - * - * - */ - private void snagVals() { - snagged = true; - Enumeration a = resources.keys(); - while (a.hasMoreElements()) { - String mystr = (String) a.nextElement(); - env.put(mystr, resources.get(mystr)); - } - } - - /** - * Constructor which takes a path to the properties file containing the initial - * ResourceLoader values. - * - * Uses the resources from the proper locale. - * - * @param s - */ - public ResourceLoader(String s) { - super(); - if (true) { - if (s.indexOf("resources/") == -1) - s = "resources/" + s; // StringTool.strip(s,".properties"); - } - Logger.logInfo("ResourceLoader INIT: " + s); - - resloc = s; - try { - try { - propsfile = new File(s + ".properties"); - FileInputStream fis = new FileInputStream(propsfile); - resources.load(fis); - } catch (Exception e) { - try { - // propsfile.mkdirs(); - - propsfile.createNewFile(); - FileInputStream fis = new FileInputStream(propsfile); - resources.load(fis); - } catch (Exception ex) { - Logger.logWarn("Could not init Resourceloader from: " + propsfile.getAbsolutePath()); - } - } - - // handle private values - boolean hidevals = false; - try { - if (resources.get("public") != null) - if (resources.get("visibility").equals("private")) - hidevals = true; - } catch (MissingResourceException mre) { // do not load properties into JNDI environment - // Logger.logInfo("ResourceLoader - getting resources failed: " + - // mre.toString()); - } - if (!hidevals) - this.snagVals(); - } catch (MissingResourceException mre) { - Logger.logErr("ResourceLoader getting resources failed: " + mre.toString()); - } - } - - public ResourceLoader() { - super(); - // empty constructor - } - - /** - * Returns a String from the properties file - * - * @param nm - * @return - */ - public String getResourceString(String nm) { - String str; - try { - str = resources.get(nm).toString(); - } catch (Exception mre) { - str = ""; - // Logger.logWarn("Resource string: " + nm + " not found in: " + this.resloc); - } - return str; - } - - /** - * Sets a String value in the properties file - * - * @param nm - * @return - */ - public void setResourceString(String nm, String v) { - try { - resources.setProperty(nm, v); - FileOutputStream fos = new FileOutputStream(propsfile); - resources.store(fos, null); - fos.flush(); - fos.close(); - } catch (Exception mre) { - Logger.logWarn("Resource string: " + nm + " could not be set to " + v + " in:" + this.resloc); - } - } - - /** - * Returns an Array of Objects which are class loaded based on a comma-delimited - * list of class names listed in the properties file. - */ - public Object[] getObjects(String propname) { - String objnames = getResourceString(propname); - if (objnames != null) { - Object[] obj = new Object[1]; - obj[0] = loadClass(objnames); - return obj; - } - return null; - } - - /** - * Load a Class by name - * - * @param className - * @return - */ - public static Object loadClass(String className) { - - ExtenClassLoader cl = new ExtenClassLoader(); - - // Make a new one of whatever type of Obj it is... - Object obj = null; - try { - Class c = cl.loadClass(className, true); - obj = c.newInstance(); - return obj; - } catch (ClassFormatError t) { - Logger.logErr(t.toString()); - return null; - } catch (ClassNotFoundException t) { - Logger.logErr(t); - return null; - } catch (ClassCastException t) { - Logger.logErr(t); - return null; - } catch (InstantiationException t) { - Logger.logErr(t); - return null; - } catch (IllegalAccessException t) { - Logger.logErr(t); - return null; - } - } - - /** - * Loads the named resource from the class path. - */ - public static byte[] getBytesFromJar(String name) throws IOException { - - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - InputStream source = classLoader.getResourceAsStream("io/starter/OpenXLS/templates/prototype.ser"); - - if (source == null) - return null; - - ByteArrayOutputStream sink = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int count = 0; - - while (count != -1) { - sink.write(buffer, 0, count); - count = source.read(buffer); - } - - source.close(); - return sink.toByteArray(); - } - - /** - * Returns the file system-specific path to a given resource in the classpath - * for the VM. - * - * @param resource - * @return - */ - public static String getFilePathForResource(String resource) { - URL u = new ResourceLoader().getClass().getResource(resource); - // 20070107 KSC: report error - if (u == null) { - Logger.logErr("ResourceLoader.getFilePathForResource: " + resource + " not found."); - return null; - } - if (DEBUG) - Logger.logInfo("ResourceLoader.getFilePathForResource() got:" + u.toString()); - String s = u.getFile(); - - if (DEBUG) - Logger.logInfo("ResourceLoader.getFilePathForResource Decoding:" + s); - s = ResourceLoader.Decode(s); - if (DEBUG) - Logger.logInfo("ResourceLoader.getFilePathForResource Decoded:" + s); - - int i = s.indexOf("!"); - if (i > -1) { // file is in a jar - String zipstring = s.substring(0, i); - - // cut off the internal zip file part & the file:/ - int begin = zipstring.indexOf(":"); - begin += 1; - zipstring = zipstring.substring(begin); - if (zipstring.indexOf(":") != -1) { // windoze box - if (zipstring.indexOf("/") == 0) { - zipstring = zipstring.substring(1); - } - } - if (DEBUG) - Logger.logInfo("Resourceloader.getFilePathForResource(): Successfully obtained " + zipstring); - return zipstring; - } else { // file is not in a jar - if (DEBUG) - Logger.logErr("ResourceLoader.getFilePathForResource(): File is not in jar:" + s); - return s; - } - } - - /** - * write file f to jar referenced by jarandresource ( ) and set - * path/name to resource - * - * @param jarandResource - * @param f - */ - public static void addFileToJar(String jarandResource, String f) { - String[] tmp = extractJarAndResourceName(jarandResource); - try { - ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tmp[0])); // open Archive for outptu - ZipInputStream fin = new ZipInputStream(new FileInputStream(f)); - out.putNextEntry(new ZipEntry(tmp[1])); - byte[] buf = new byte[fin.available()]; - fin.read(buf); - out.write(buf); - out.flush(); - out.closeEntry(); - out.close(); - } catch (Exception e) { - Logger.logErr("addFileToJar: Jar: " + tmp[0] + " File: " + tmp[1] + " : " + e.toString()); - } - } - - /** - * returns truth of "file is a jar/archive file" - * - * @param f - * @return - */ - public static boolean isJarFile(String f) { - f = f.toLowerCase(); - int i = f.indexOf(".war"); - if (i < 0) - i = f.indexOf(".jar"); - if (i < 0) - i = f.indexOf(".rar"); - if (i < 0) - i = f.indexOf(".zip"); - return (i > -1); - } - - /** - * separate and return the jar portion and resource portion of a jar and - * resource string: - * - * @param jarAndResource - * @return String[2] - */ - public static String[] extractJarAndResourceName(String jarAndResource) { - jarAndResource = jarAndResource.toLowerCase(); - int i = jarAndResource.indexOf(".war"); - if (i < 0) - i = jarAndResource.indexOf(".jar"); - if (i < 0) - i = jarAndResource.indexOf(".rar"); - if (i < 0) - i = jarAndResource.indexOf(".zip"); - return new String[] { jarAndResource.substring(0, i + 4), jarAndResource.substring(i + 5) }; - } - - /** - * Get the path to a directory by locating the jar file in the classpath - * containing the given resource name. - * - * @param resource - * @return - */ - public static String getWorkingDirectoryFromJar(String resource) { - String s; - // if jarloc property is set, use it to find working directory - if (System.getProperty("io.starter.OpenXLS.jarloc") != null) { - s = System.getProperty("io.starter.OpenXLS.jarloc") + "!"; - } else { - URL u = ResourceLoader.class.getResource(resource); - s = u.getFile(); - } - if (DEBUG) - Logger.logInfo("Resource: " + resource + " found in: " + s); - - // cut off the internal zip file part & the file:/ - int begin = -1; - begin = s.indexOf("file:"); - if (begin < 0) { - begin = s.indexOf(":"); - begin += 1; - } else - begin += 5; - s = s.substring(begin); - if (s.indexOf(":") != -1) { // windoze box - if (s.indexOf("/") == 0) - s = s.substring(1); - } - if (DEBUG) - Logger.logInfo("ResourceLoader() after stripping:" + s); - int i = s.indexOf("!"); - if (i > -1) { - String zipstring = s.substring(0, i); - i = zipstring.lastIndexOf("/"); - if (i == -1) { - i = zipstring.lastIndexOf("\\"); - } - zipstring = zipstring.substring(0, i); - if (DEBUG) - Logger.logInfo("ResourceLoader() returning zipstring Final Working Directory Setting: " + zipstring); - return zipstring; - } else { - if (DEBUG) - Logger.logInfo("ResourceLoader() returning Final Working Directory Setting: " + s); - return s; - } - } - - private static URLDecoder decodr = new URLDecoder(); - - /** - * Decode a URL String, if supported by the JDK version in use this method will - * utilize the - * - * @param s - * @return - */ - public static String Decode(String s) { - String[] tmpstr = { s, "ISO-8859-1" }; - // first attempt using encoding... - String ret = s; - ret = (String) ResourceLoader.executeIfSupported(decodr, tmpstr, "decode"); - if (ret == null) - try { - ret = URLDecoder.decode(s, "ISO-8859-1"); - } catch (Exception e) { - Logger.logErr("ResourceLoader.Decode resource failed: " + e.toString()); - } - return ret; - } - - /** - * Decode a URL String, if supported by the JDK version in use this method will - * utilize the non-deprecated method of decoding. - * - * @param s, - * string to decode - * @param encoding, - * the encoding type to use - * @return - */ - public static String Decode(String s, String encoding) { - String[] tmpstr = { s, "Encoding" }; - // first attempt using encoding... - String ret = s; - ret = (String) ResourceLoader.executeIfSupported(decodr, tmpstr, "decode"); - if (ret == null) - try { - ret = URLDecoder.decode(s); - } catch (Exception e) { - Logger.logErr("ResourceLoader.Decode resource failed: " + e.toString()); - } - return ret; - } - - /** - * Attempt to execute a Method on an Object - * - * @param ob - * the Object which contains the method you want to execute - * @param args - * an array of arguments to the Method, null if none - * @param methname - * the name of the Method you are executing - * @return the return value of the method if any - */ - public static Object executeIfSupported(Object ob, Object[] args, String methname) { - try { - Object retob = null; - Method[] mt = ob.getClass().getMethods(); - int t = 0; - for (; t < mt.length; t++) { - // Make JDK-specific call to method - // Logger.logInfo(mt[t].getName()); - int numparms = 0, numargs = 0; - if (args != null) - numargs = args.length; - if (mt[t].getParameterTypes() != null) - numparms = mt[t].getParameterTypes().length; - String nm = mt[t].getName(); - if ((nm.equals(methname)) && (numparms == numargs)) { - try { - Method mx = mt[t]; - retob = mx.invoke(ob, args); - break; - } catch (Exception e) { - ; - if (false) - Logger.logWarn("ResourceLoader.executeIfSupported() Method NOT supported: " + methname - + " in " + ob.getClass().getName() + " for arguments " - + StringTool.arrayToString(args)); - return null; - } - } - } - if (false) - if (t == mt.length) - Logger.logWarn("ResourceLoader.executeIfSupported() Method NOT found: " + methname + " in " - + ob.getClass().getName() + " for arguments " + StringTool.arrayToString(args)); - return retob; - } catch (NoSuchMethodError e) { - return null; - } - } - - /** - * Execute a Method on an Object - * - * @param ob - * the Object which contains the method you want to execute - * @param args - * an array of arguments to the Method, null if none - * @param methname - * the name of the Method you are executing - * @return the return value of the method if any - */ - public static Object execute(Object ob, Object[] args, String methname) throws Exception { - Class[] pc = new Class[args.length]; - for (int r = 0; r < args.length; r++) { - pc[r] = args[r].getClass(); - } - Method mt = null; - try { - mt = ob.getClass().getMethod(methname, pc); - } catch (NoSuchMethodException e) { - // deal with 'unwrapping' primitives - return executeIfSupported(ob, args, methname); - } - // Logger.logInfo("ResourceLoader.execute() Invoking:" + mt.getName() +" on " + - // ob.getClass().getName()); - try { - return mt.invoke(ob, args); - } catch (Exception e) { - Logger.logErr("ResourceLoader.execute " + methname + " on " + ob.getClass().getName() + " failed: " - + e.toString()); - e.printStackTrace(); - return null; - } - } - - /** - * Sets the debugging level for the ResourceLoader - * - * @param b - */ - public void setDebug(boolean b) { - DEBUG = b; - } + /** + * + */ + private static final long serialVersionUID = 12345245254L; + private String resloc = ""; + private File propsfile = null; + public static boolean DEBUG = false; + private Properties resources = new Properties(); + + @Override + public String toString() { + return "Extentech ResourceLoader v." + ResourceLoader.getVersion(); + } + + public static String getVersion() { + return GetInfo.getVersion(); + } + + public Enumeration getKeys() { + if (!snagged) + return resources.keys(); + return env.keys(); + } + + public Object getObject(String key) { + if (!snagged) + return resources.get(key); + return env.get(key); + } + + private boolean snagged = false; + + /** + * put the properties file vals in the ResourceLoader + */ + private void snagVals() { + snagged = true; + Enumeration a = resources.keys(); + while (a.hasMoreElements()) { + String mystr = (String) a.nextElement(); + env.put(mystr, resources.get(mystr)); + } + } + + /** + * Constructor which takes a path to the properties file containing the initial + * ResourceLoader values. + *

                + * Uses the resources from the proper locale. + * + * @param s + */ + public ResourceLoader(String s) { + super(); + if (true) { + if (s.indexOf("resources/") == -1) + s = "resources/" + s; // StringTool.strip(s,".properties"); + } + Logger.logInfo("ResourceLoader INIT: " + s); + + resloc = s; + try { + try { + propsfile = new File(s + ".properties"); + FileInputStream fis = new FileInputStream(propsfile); + resources.load(fis); + } catch (Exception e) { + try { + // propsfile.mkdirs(); + + propsfile.createNewFile(); + FileInputStream fis = new FileInputStream(propsfile); + resources.load(fis); + } catch (Exception ex) { + Logger.logWarn("Could not init Resourceloader from: " + propsfile.getAbsolutePath()); + } + } + + // handle private values + boolean hidevals = false; + try { + if (resources.get("public") != null) + if (resources.get("visibility").equals("private")) + hidevals = true; + } catch (MissingResourceException mre) { // do not load properties into JNDI environment + // Logger.logInfo("ResourceLoader - getting resources failed: " + + // mre.toString()); + } + if (!hidevals) + this.snagVals(); + } catch (MissingResourceException mre) { + Logger.logErr("ResourceLoader getting resources failed: " + mre.toString()); + } + } + + public ResourceLoader() { + super(); + // empty constructor + } + + /** + * Returns a String from the properties file + * + * @param nm + * @return + */ + public String getResourceString(String nm) { + String str; + try { + str = resources.get(nm).toString(); + } catch (Exception mre) { + str = ""; + // Logger.logWarn("Resource string: " + nm + " not found in: " + this.resloc); + } + return str; + } + + /** + * Sets a String value in the properties file + * + * @param nm + * @return + */ + public void setResourceString(String nm, String v) { + try { + resources.setProperty(nm, v); + FileOutputStream fos = new FileOutputStream(propsfile); + resources.store(fos, null); + fos.flush(); + fos.close(); + } catch (Exception mre) { + Logger.logWarn("Resource string: " + nm + " could not be set to " + v + " in:" + this.resloc); + } + } + + /** + * Returns an Array of Objects which are class loaded based on a comma-delimited + * list of class names listed in the properties file. + */ + public Object[] getObjects(String propname) { + String objnames = getResourceString(propname); + if (objnames != null) { + Object[] obj = new Object[1]; + obj[0] = loadClass(objnames); + return obj; + } + return null; + } + + /** + * Load a Class by name + * + * @param className + * @return + */ + public static Object loadClass(String className) { + + ExtenClassLoader cl = new ExtenClassLoader(); + + // Make a new one of whatever type of Obj it is... + Object obj = null; + try { + Class c = cl.loadClass(className, true); + obj = c.newInstance(); + return obj; + } catch (ClassFormatError t) { + Logger.logErr(t.toString()); + return null; + } catch (ClassNotFoundException t) { + Logger.logErr(t); + return null; + } catch (ClassCastException t) { + Logger.logErr(t); + return null; + } catch (InstantiationException t) { + Logger.logErr(t); + return null; + } catch (IllegalAccessException t) { + Logger.logErr(t); + return null; + } + } + + /** + * Loads the named resource from the class path. + */ + public static byte[] getBytesFromJar(String name) throws IOException { + + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream source = classLoader.getResourceAsStream("io/starter/OpenXLS/templates/prototype.ser"); + + if (source == null) + return null; + + ByteArrayOutputStream sink = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int count = 0; + + while (count != -1) { + sink.write(buffer, 0, count); + count = source.read(buffer); + } + + source.close(); + return sink.toByteArray(); + } + + /** + * Returns the file system-specific path to a given resource in the classpath + * for the VM. + * + * @param resource + * @return + */ + public static String getFilePathForResource(String resource) { + URL u = new ResourceLoader().getClass().getResource(resource); + // 20070107 KSC: report error + if (u == null) { + Logger.logErr("ResourceLoader.getFilePathForResource: " + resource + " not found."); + return null; + } + if (DEBUG) + Logger.logInfo("ResourceLoader.getFilePathForResource() got:" + u.toString()); + String s = u.getFile(); + + if (DEBUG) + Logger.logInfo("ResourceLoader.getFilePathForResource Decoding:" + s); + s = ResourceLoader.Decode(s); + if (DEBUG) + Logger.logInfo("ResourceLoader.getFilePathForResource Decoded:" + s); + + int i = s.indexOf("!"); + if (i > -1) { // file is in a jar + String zipstring = s.substring(0, i); + + // cut off the internal zip file part & the file:/ + int begin = zipstring.indexOf(":"); + begin += 1; + zipstring = zipstring.substring(begin); + if (zipstring.indexOf(":") != -1) { // windoze box + if (zipstring.indexOf("/") == 0) { + zipstring = zipstring.substring(1); + } + } + if (DEBUG) + Logger.logInfo("Resourceloader.getFilePathForResource(): Successfully obtained " + zipstring); + return zipstring; + } else { // file is not in a jar + if (DEBUG) + Logger.logErr("ResourceLoader.getFilePathForResource(): File is not in jar:" + s); + return s; + } + } + + /** + * write file f to jar referenced by jarandresource ( ) and set + * path/name to resource + * + * @param jarandResource + * @param f + */ + public static void addFileToJar(String jarandResource, String f) { + String[] tmp = extractJarAndResourceName(jarandResource); + try { + ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tmp[0])); // open Archive for outptu + ZipInputStream fin = new ZipInputStream(new FileInputStream(f)); + out.putNextEntry(new ZipEntry(tmp[1])); + byte[] buf = new byte[fin.available()]; + fin.read(buf); + out.write(buf); + out.flush(); + out.closeEntry(); + out.close(); + } catch (Exception e) { + Logger.logErr("addFileToJar: Jar: " + tmp[0] + " File: " + tmp[1] + " : " + e.toString()); + } + } + + /** + * returns truth of "file is a jar/archive file" + * + * @param f + * @return + */ + public static boolean isJarFile(String f) { + f = f.toLowerCase(); + int i = f.indexOf(".war"); + if (i < 0) + i = f.indexOf(".jar"); + if (i < 0) + i = f.indexOf(".rar"); + if (i < 0) + i = f.indexOf(".zip"); + return (i > -1); + } + + /** + * separate and return the jar portion and resource portion of a jar and + * resource string: + * + * @param jarAndResource + * @return String[2] + */ + public static String[] extractJarAndResourceName(String jarAndResource) { + jarAndResource = jarAndResource.toLowerCase(); + int i = jarAndResource.indexOf(".war"); + if (i < 0) + i = jarAndResource.indexOf(".jar"); + if (i < 0) + i = jarAndResource.indexOf(".rar"); + if (i < 0) + i = jarAndResource.indexOf(".zip"); + return new String[]{jarAndResource.substring(0, i + 4), jarAndResource.substring(i + 5)}; + } + + /** + * Get the path to a directory by locating the jar file in the classpath + * containing the given resource name. + * + * @param resource + * @return + */ + public static String getWorkingDirectoryFromJar(String resource) { + String s; + // if jarloc property is set, use it to find working directory + if (System.getProperty("io.starter.OpenXLS.jarloc") != null) { + s = System.getProperty("io.starter.OpenXLS.jarloc") + "!"; + } else { + URL u = ResourceLoader.class.getResource(resource); + s = u.getFile(); + } + if (DEBUG) + Logger.logInfo("Resource: " + resource + " found in: " + s); + + // cut off the internal zip file part & the file:/ + int begin = -1; + begin = s.indexOf("file:"); + if (begin < 0) { + begin = s.indexOf(":"); + begin += 1; + } else + begin += 5; + s = s.substring(begin); + if (s.indexOf(":") != -1) { // windoze box + if (s.indexOf("/") == 0) + s = s.substring(1); + } + if (DEBUG) + Logger.logInfo("ResourceLoader() after stripping:" + s); + int i = s.indexOf("!"); + if (i > -1) { + String zipstring = s.substring(0, i); + i = zipstring.lastIndexOf("/"); + if (i == -1) { + i = zipstring.lastIndexOf("\\"); + } + zipstring = zipstring.substring(0, i); + if (DEBUG) + Logger.logInfo("ResourceLoader() returning zipstring Final Working Directory Setting: " + zipstring); + return zipstring; + } else { + if (DEBUG) + Logger.logInfo("ResourceLoader() returning Final Working Directory Setting: " + s); + return s; + } + } + + private static URLDecoder decodr = new URLDecoder(); + + /** + * Decode a URL String, if supported by the JDK version in use this method will + * utilize the + * + * @param s + * @return + */ + public static String Decode(String s) { + String[] tmpstr = {s, "ISO-8859-1"}; + // first attempt using encoding... + String ret = s; + ret = (String) ResourceLoader.executeIfSupported(decodr, tmpstr, "decode"); + if (ret == null) + try { + ret = URLDecoder.decode(s, "ISO-8859-1"); + } catch (Exception e) { + Logger.logErr("ResourceLoader.Decode resource failed: " + e.toString()); + } + return ret; + } + + /** + * Decode a URL String, if supported by the JDK version in use this method will + * utilize the non-deprecated method of decoding. + * + * @param s, string to decode + * @param encoding, the encoding type to use + * @return + */ + public static String Decode(String s, String encoding) { + String[] tmpstr = {s, "Encoding"}; + // first attempt using encoding... + String ret = s; + ret = (String) ResourceLoader.executeIfSupported(decodr, tmpstr, "decode"); + if (ret == null) + try { + ret = URLDecoder.decode(s); + } catch (Exception e) { + Logger.logErr("ResourceLoader.Decode resource failed: " + e.toString()); + } + return ret; + } + + /** + * Attempt to execute a Method on an Object + * + * @param ob the Object which contains the method you want to execute + * @param args an array of arguments to the Method, null if none + * @param methname the name of the Method you are executing + * @return the return value of the method if any + */ + public static Object executeIfSupported(Object ob, Object[] args, String methname) { + try { + Object retob = null; + Method[] mt = ob.getClass().getMethods(); + int t = 0; + for (; t < mt.length; t++) { + // Make JDK-specific call to method + // Logger.logInfo(mt[t].getName()); + int numparms = 0, numargs = 0; + if (args != null) + numargs = args.length; + if (mt[t].getParameterTypes() != null) + numparms = mt[t].getParameterTypes().length; + String nm = mt[t].getName(); + if ((nm.equals(methname)) && (numparms == numargs)) { + try { + Method mx = mt[t]; + retob = mx.invoke(ob, args); + break; + } catch (Exception e) { + if (false) + Logger.logWarn("ResourceLoader.executeIfSupported() Method NOT supported: " + methname + + " in " + ob.getClass().getName() + " for arguments " + + StringTool.arrayToString(args)); + return null; + } + } + } + if (false) + if (t == mt.length) + Logger.logWarn("ResourceLoader.executeIfSupported() Method NOT found: " + methname + " in " + + ob.getClass().getName() + " for arguments " + StringTool.arrayToString(args)); + return retob; + } catch (NoSuchMethodError e) { + return null; + } + } + + /** + * Execute a Method on an Object + * + * @param ob the Object which contains the method you want to execute + * @param args an array of arguments to the Method, null if none + * @param methname the name of the Method you are executing + * @return the return value of the method if any + */ + public static Object execute(Object ob, Object[] args, String methname) throws Exception { + Class[] pc = new Class[args.length]; + for (int r = 0; r < args.length; r++) { + pc[r] = args[r].getClass(); + } + Method mt = null; + try { + mt = ob.getClass().getMethod(methname, pc); + } catch (NoSuchMethodException e) { + // deal with 'unwrapping' primitives + return executeIfSupported(ob, args, methname); + } + // Logger.logInfo("ResourceLoader.execute() Invoking:" + mt.getName() +" on " + + // ob.getClass().getName()); + try { + return mt.invoke(ob, args); + } catch (Exception e) { + Logger.logErr("ResourceLoader.execute " + methname + " on " + ob.getClass().getName() + " failed: " + + e.toString()); + e.printStackTrace(); + return null; + } + } + + /** + * Sets the debugging level for the ResourceLoader + * + * @param b + */ + public void setDebug(boolean b) { + DEBUG = b; + } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/SpecialArrayList.java b/src/main/java/io/starter/toolkit/SpecialArrayList.java index 93fc632..37ad6b4 100644 --- a/src/main/java/io/starter/toolkit/SpecialArrayList.java +++ b/src/main/java/io/starter/toolkit/SpecialArrayList.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,16 +22,7 @@ */ package io.starter.toolkit; -import java.util.AbstractList; -import java.util.Collection; -import java.util.Collections; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.RandomAccess; -import java.util.Vector; +import java.util.*; /** * Resizable-array implementation of the List interface. Implements all @@ -41,7 +32,7 @@ * the list. (This class is roughly equivalent to Vector, except that * it is unsynchronized.) *

                - * + *

                * The size, isEmpty, get, set, * iterator, and listIterator operations run in constant time. * The add operation runs in amortized constant time, that is, @@ -49,7 +40,7 @@ * linear time (roughly speaking). The constant factor is low compared to that * for the LinkedList implementation. *

                - * + *

                * Each ArrayList instance has a capacity. The capacity is the * size of the array used to store the elements in the list. It is always at * least as large as the list size. As elements are added to an ArrayList, its @@ -57,7 +48,7 @@ * specified beyond the fact that adding an element has constant amortized time * cost. *

                - * + *

                * An application can increase the capacity of an ArrayList instance * before adding a large number of elements using the ensureCapacity * operation. This may reduce the amount of incremental reallocation. @@ -74,12 +65,12 @@ * should be "wrapped" using the Collections.synchronizedList method. * This is best done at creation time, to prevent accidental unsynchronized * access to the list: - * + * *

                  *  List list = Collections.synchronizedList(new ArrayList(...));
                  * 
                *

                - * + *

                * The iterators returned by this class's iterator and * listIterator methods are fail-fast: if list is structurally * modified at any time after the iterator is created, in any way except through @@ -88,7 +79,7 @@ * modification, the iterator fails quickly and cleanly, rather than risking * arbitrary, non-deterministic behavior at an undetermined time in the future. *

                - * + *

                * Note that the fail-fast behavior of an iterator cannot be guaranteed as it * is, generally speaking, impossible to make any hard guarantees in the * presence of unsynchronized concurrent modification. Fail-fast iterators throw @@ -97,7 +88,7 @@ * correctness: the fail-fast behavior of iterators should be used only to * detect bugs. *

                - * + *

                * This class is a member of the * Java Collections * Framework. @@ -110,548 +101,518 @@ */ class SpecialArrayList extends AbstractList - implements List, RandomAccess, Cloneable, java.io.Serializable { - private static final long serialVersionUID = 8683452581122892189L; - - /** - * Returns an iterator over the elements in this list in proper sequence. - *

                - * - * This implementation returns a straightforward implementation of the iterator - * interface, relying on the backing list's size(), get(int), - * and remove(int) methods. - *

                - * - * Note that the iterator returned by this method will throw an - * UnsupportedOperationException in response to its remove - * method unless the list's remove(int) method is overridden. - *

                - * - * This implementation can be made to throw runtime exceptions in the face of - * concurrent modification, as described in the specification for the - * (protected) modCount field. - * - * @return an iterator over the elements in this list in proper sequence. - * - * @see #modCount - */ - @Override - public Iterator iterator() { - return new Itr(); - } - - /** - * The array buffer into which the elements of the ArrayList are stored. The - * capacity of the ArrayList is the length of this array buffer. - */ - Object elementData[]; - - /** - * The size of the ArrayList (the number of elements it contains). - * - * @serial - */ - private int size; - - /** - * Constructs an empty list with the specified initial capacity. - * - * @param initialCapacity - * the initial capacity of the list. - * @exception IllegalArgumentException - * if the specified initial capacity is negative - */ - public SpecialArrayList(int initialCapacity) { - super(); - if (initialCapacity < 0) - throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); - this.elementData = new Object[initialCapacity]; - } - - /** - * Constructs an empty list with an initial capacity of ten. - */ - public SpecialArrayList() { - this(10); - } - - /** - * Constructs a list containing the elements of the specified collection, in the - * order they are returned by the collection's iterator. The ArrayList - * instance has an initial capacity of 110% the size of the specified - * collection. - * - * @param c - * the collection whose elements are to be placed into this list. - * @throws NullPointerException - * if the specified collection is null. - */ - public SpecialArrayList(Collection c) { - size = c.size(); - // Allow 10% room for growth - elementData = new Object[(int) Math.min((size * 110L) / 100, Integer.MAX_VALUE)]; - c.toArray(elementData); - } - - /** - * Trims the capacity of this ArrayList instance to be the list's - * current size. An application can use this operation to minimize the storage - * of an ArrayList instance. - */ - public void trimToSize() { - modCount++; - int oldCapacity = elementData.length; - if (size < oldCapacity) { - Object oldData[] = elementData; - elementData = new Object[size]; - System.arraycopy(oldData, 0, elementData, 0, size); - } - } - - /** - * Increases the capacity of this ArrayList instance, if necessary, to - * ensure that it can hold at least the number of elements specified by the - * minimum capacity argument. - * - * @param minCapacity - * the desired minimum capacity. - */ - public void ensureCapacity(int minCapacity) { - modCount++; - int oldCapacity = elementData.length; - if (minCapacity > oldCapacity) { - Object oldData[] = elementData; - int newCapacity = (oldCapacity * 3) / 2 + 1; - if (newCapacity < minCapacity) - newCapacity = minCapacity; - elementData = new Object[newCapacity]; - System.arraycopy(oldData, 0, elementData, 0, size); - } - } - - /** - * Returns the number of elements in this list. - * - * @return the number of elements in this list. - */ - @Override - public int size() { - return size; - } - - /** - * Tests if this list has no elements. - * - * @return true if this list has no elements; false otherwise. - */ - @Override - public boolean isEmpty() { - return size == 0; - } - - /** - * Returns true if this list contains the specified element. - * - * @param elem - * element whose presence in this List is to be tested. - * @return true if the specified element is present; - * false otherwise. - */ - @Override - public boolean contains(Object elem) { - return indexOf(elem) >= 0; - } - - /** - * Searches for the first occurence of the given argument, testing for equality - * using the equals method. - * - * @param elem - * an object. - * @return the index of the first occurrence of the argument in this list; - * returns -1 if the object is not found. - * @see Object#equals(Object) - */ - @Override - public int indexOf(Object elem) { - if (elem == null) { - for (int i = 0; i < size; i++) - if (elementData[i] == null) - return i; - } else { - for (int i = 0; i < size; i++) - if (elem.equals(elementData[i])) - return i; - } - return -1; - } - - /** - * Returns the index of the last occurrence of the specified object in this - * list. - * - * @param elem - * the desired element. - * @return the index of the last occurrence of the specified object in this - * list; returns -1 if the object is not found. - */ - @Override - public int lastIndexOf(Object elem) { - if (elem == null) { - for (int i = size - 1; i >= 0; i--) - if (elementData[i] == null) - return i; - } else { - for (int i = size - 1; i >= 0; i--) - if (elem.equals(elementData[i])) - return i; - } - return -1; - } - - /** - * Returns a shallow copy of this ArrayList instance. (The elements - * themselves are not copied.) - * - * @return a clone of this ArrayList instance. - */ - @Override - public Object clone() { - try { - SpecialArrayList v = (SpecialArrayList) super.clone(); - v.elementData = new Object[size]; - System.arraycopy(elementData, 0, v.elementData, 0, size); - v.modCount = 0; - return v; - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - /** - * Returns an array containing all of the elements in this list in the correct - * order. - * - * @return an array containing all of the elements in this list in the correct - * order. - */ - @Override - public Object[] toArray() { - Object[] result = new Object[size]; - System.arraycopy(elementData, 0, result, 0, size); - return result; - } - - /** - * Returns an array containing all of the elements in this list in the correct - * order; the runtime type of the returned array is that of the specified array. - * If the list fits in the specified array, it is returned therein. Otherwise, a - * new array is allocated with the runtime type of the specified array and the - * size of this list. - *

                - * - * If the list fits in the specified array with room to spare (i.e., the array - * has more elements than the list), the element in the array immediately - * following the end of the collection is set to null. This is useful - * in determining the length of the list only if the caller knows that - * the list does not contain any null elements. - * - * @param a - * the array into which the elements of the list are to be stored, if - * it is big enough; otherwise, a new array of the same runtime type - * is allocated for this purpose. - * @return an array containing the elements of the list. - * @throws ArrayStoreException - * if the runtime type of a is not a supertype of the runtime type - * of every element in this list. - */ - @Override - public Object[] toArray(Object a[]) { - if (a.length < size) - a = (Object[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); - - System.arraycopy(elementData, 0, a, 0, size); - - if (a.length > size) - a[size] = null; - - return a; - } - - // Positional Access Operations - - /** - * Returns the element at the specified position in this list. - * - * @param index - * index of element to return. - * @return the element at the specified position in this list. - * @throws IndexOutOfBoundsException - * if index is out of range (index - * < 0 || index >= size()). - */ - @Override - public Object get(int index) { - RangeCheck(index); - return elementData[index]; - } - - /** - * Replaces the element at the specified position in this list with the - * specified element. - * - * @param index - * index of element to replace. - * @param element - * element to be stored at the specified position. - * @return the element previously at the specified position. - * @throws IndexOutOfBoundsException - * if index out of range - * (index < 0 || index >= size()). - */ - @Override - public Object set(int index, Object element) { - RangeCheck(index); - - Object oldValue = elementData[index]; - elementData[index] = element; - return oldValue; - } - - /** - * Appends the specified element to the end of this list. - * - * @param o - * element to be appended to this list. - * @return true (as per the general contract of Collection.add). - */ - @Override - public boolean add(Object o) { - ensureCapacity(size + 1); // Increments modCount!! - elementData[size++] = o; - return true; - } - - /** - * Inserts the specified element at the specified position in this list. Shifts - * the element currently at that position (if any) and any subsequent elements - * to the right (adds one to their indices). - * - * @param index - * index at which the specified element is to be inserted. - * @param element - * element to be inserted. - * @throws IndexOutOfBoundsException - * if index is out of range - * (index < 0 || index > size()). - */ - @Override - public void add(int index, Object element) { - if (index > size || index < 0) - throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); - - ensureCapacity(size + 1); // Increments modCount!! - System.arraycopy(elementData, index, elementData, index + 1, size - index); - elementData[index] = element; - size++; - } - - /** - * Removes the element at the specified position in this list. Shifts any - * subsequent elements to the left (subtracts one from their indices). - * - * @param index - * the index of the element to removed. - * @return the element that was removed from the list. - * @throws IndexOutOfBoundsException - * if index out of range (index - * < 0 || index >= size()). - */ - @Override - public Object remove(int index) { - RangeCheck(index); - - modCount++; - Object oldValue = elementData[index]; - - int numMoved = size - index - 1; - if (numMoved > 0) - System.arraycopy(elementData, index + 1, elementData, index, numMoved); - elementData[--size] = null; // Let gc do its work - - return oldValue; - } - - /** - * Removes all of the elements from this list. The list will be empty after this - * call returns. - */ - @Override - public void clear() { - modCount++; - - // Let gc do its work - for (int i = 0; i < size; i++) - elementData[i] = null; - - size = 0; - } - - /** - * Appends all of the elements in the specified Collection to the end of this - * list, in the order that they are returned by the specified Collection's - * Iterator. The behavior of this operation is undefined if the specified - * Collection is modified while the operation is in progress. (This implies that - * the behavior of this call is undefined if the specified Collection is this - * list, and this list is nonempty.) - * - * @param c - * the elements to be inserted into this list. - * @return true if this list changed as a result of the call. - * @throws NullPointerException - * if the specified collection is null. - */ - @Override - public boolean addAll(Collection c) { - Object[] a = c.toArray(); - // 20080124 KSC: replace with arraycopy - // this.elementData = a; - int numNew = a.length; - elementData = new Object[numNew]; - System.arraycopy(a, 0, elementData, 0, numNew); - // ensureCapacity(size + numNew); // Increments modCount - size += c.size(); - modCount += size; - modCount++; - return numNew != 0; - } - - /** - * Inserts all of the elements in the specified Collection into this list, - * starting at the specified position. Shifts the element currently at that - * position (if any) and any subsequent elements to the right (increases their - * indices). The new elements will appear in the list in the order that they are - * returned by the specified Collection's iterator. - * - * @param index - * index at which to insert first element from the specified - * collection. - * @param c - * elements to be inserted into this list. - * @return true if this list changed as a result of the call. - * @throws IndexOutOfBoundsException - * if index out of range (index - * < 0 || index > size()). - * @throws NullPointerException - * if the specified Collection is null. - */ - @Override - public boolean addAll(int index, Collection c) { - if (index > size || index < 0) - throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); - - Object[] a = c.toArray(); - int numNew = c.size(); // 20080121 KSC: a.length; - ensureCapacity(size + numNew); // Increments modCount - - int numMoved = size - index; - if (numMoved > 0) - System.arraycopy(elementData, index, elementData, index + numNew, numMoved); - - System.arraycopy(a, 0, elementData, index, numNew); - size += numNew; - return numNew != 0; - } - - /** - * Removes from this List all of the elements whose index is between fromIndex, - * inclusive and toIndex, exclusive. Shifts any succeeding elements to the left - * (reduces their index). This call shortens the list by - * (toIndex - fromIndex) elements. (If toIndex==fromIndex, - * this operation has no effect.) - * - * @param fromIndex - * index of first element to be removed. - * @param toIndex - * index after last element to be removed. - */ - @Override - protected void removeRange(int fromIndex, int toIndex) { - modCount++; - int numMoved = size - toIndex; - System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); - - // Let gc do its work - int newSize = size - (toIndex - fromIndex); - while (size != newSize) - elementData[--size] = null; - } - - /** - * Check if the given index is in range. If not, throw an appropriate runtime - * exception. This method does *not* check if the index is negative: It is - * always used immediately prior to an array access, which throws an - * ArrayIndexOutOfBoundsException if index is negative. - */ - private void RangeCheck(int index) { - if (index >= size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); - } - - class Itr implements Iterator { - /** - * Index of element to be returned by subsequent call to next. - */ - int cursor = 0; - - /** - * Index of element returned by most recent call to next or previous. Reset to - * -1 if this element is deleted by a call to remove. - */ - int lastRet = -1; - - /** - * The modCount value that the iterator believes that the backing List should - * have. If this expectation is violated, the iterator has detected concurrent - * modification. - */ - int expectedModCount = modCount; - - public boolean hasNext() { - return cursor != size; - } - - public Object next() { - // checkForComodification(); - try { - Object next = elementData[cursor]; - lastRet = cursor++; - return next; - } catch (IndexOutOfBoundsException e) { - // checkForComodification(); - throw new NoSuchElementException(); - } - } - - public void remove() { - if (lastRet == -1) - throw new IllegalStateException(); - // checkForComodification(); - - try { - SpecialArrayList.this.remove(lastRet); - if (lastRet < cursor) - cursor--; - lastRet = -1; - expectedModCount = modCount; - } catch (IndexOutOfBoundsException e) { - throw new ConcurrentModificationException(); - } - } - - final void checkForComodification() { - // NOT! - } - } + implements List, RandomAccess, Cloneable, java.io.Serializable { + private static final long serialVersionUID = 8683452581122892189L; + + /** + * Returns an iterator over the elements in this list in proper sequence. + *

                + *

                + * This implementation returns a straightforward implementation of the iterator + * interface, relying on the backing list's size(), get(int), + * and remove(int) methods. + *

                + *

                + * Note that the iterator returned by this method will throw an + * UnsupportedOperationException in response to its remove + * method unless the list's remove(int) method is overridden. + *

                + *

                + * This implementation can be made to throw runtime exceptions in the face of + * concurrent modification, as described in the specification for the + * (protected) modCount field. + * + * @return an iterator over the elements in this list in proper sequence. + * @see #modCount + */ + @Override + public Iterator iterator() { + return new Itr(); + } + + /** + * The array buffer into which the elements of the ArrayList are stored. The + * capacity of the ArrayList is the length of this array buffer. + */ + Object[] elementData; + + /** + * The size of the ArrayList (the number of elements it contains). + * + * @serial + */ + private int size; + + /** + * Constructs an empty list with the specified initial capacity. + * + * @param initialCapacity the initial capacity of the list. + * @throws IllegalArgumentException if the specified initial capacity is negative + */ + public SpecialArrayList(int initialCapacity) { + super(); + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); + this.elementData = new Object[initialCapacity]; + } + + /** + * Constructs an empty list with an initial capacity of ten. + */ + public SpecialArrayList() { + this(10); + } + + /** + * Constructs a list containing the elements of the specified collection, in the + * order they are returned by the collection's iterator. The ArrayList + * instance has an initial capacity of 110% the size of the specified + * collection. + * + * @param c the collection whose elements are to be placed into this list. + * @throws NullPointerException if the specified collection is null. + */ + public SpecialArrayList(Collection c) { + size = c.size(); + // Allow 10% room for growth + elementData = new Object[(int) Math.min((size * 110L) / 100, Integer.MAX_VALUE)]; + c.toArray(elementData); + } + + /** + * Trims the capacity of this ArrayList instance to be the list's + * current size. An application can use this operation to minimize the storage + * of an ArrayList instance. + */ + public void trimToSize() { + modCount++; + int oldCapacity = elementData.length; + if (size < oldCapacity) { + Object[] oldData = elementData; + elementData = new Object[size]; + System.arraycopy(oldData, 0, elementData, 0, size); + } + } + + /** + * Increases the capacity of this ArrayList instance, if necessary, to + * ensure that it can hold at least the number of elements specified by the + * minimum capacity argument. + * + * @param minCapacity the desired minimum capacity. + */ + public void ensureCapacity(int minCapacity) { + modCount++; + int oldCapacity = elementData.length; + if (minCapacity > oldCapacity) { + Object[] oldData = elementData; + int newCapacity = (oldCapacity * 3) / 2 + 1; + if (newCapacity < minCapacity) + newCapacity = minCapacity; + elementData = new Object[newCapacity]; + System.arraycopy(oldData, 0, elementData, 0, size); + } + } + + /** + * Returns the number of elements in this list. + * + * @return the number of elements in this list. + */ + @Override + public int size() { + return size; + } + + /** + * Tests if this list has no elements. + * + * @return true if this list has no elements; false otherwise. + */ + @Override + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns true if this list contains the specified element. + * + * @param elem element whose presence in this List is to be tested. + * @return true if the specified element is present; + * false otherwise. + */ + @Override + public boolean contains(Object elem) { + return indexOf(elem) >= 0; + } + + /** + * Searches for the first occurence of the given argument, testing for equality + * using the equals method. + * + * @param elem an object. + * @return the index of the first occurrence of the argument in this list; + * returns -1 if the object is not found. + * @see Object#equals(Object) + */ + @Override + public int indexOf(Object elem) { + if (elem == null) { + for (int i = 0; i < size; i++) + if (elementData[i] == null) + return i; + } else { + for (int i = 0; i < size; i++) + if (elem.equals(elementData[i])) + return i; + } + return -1; + } + + /** + * Returns the index of the last occurrence of the specified object in this + * list. + * + * @param elem the desired element. + * @return the index of the last occurrence of the specified object in this + * list; returns -1 if the object is not found. + */ + @Override + public int lastIndexOf(Object elem) { + if (elem == null) { + for (int i = size - 1; i >= 0; i--) + if (elementData[i] == null) + return i; + } else { + for (int i = size - 1; i >= 0; i--) + if (elem.equals(elementData[i])) + return i; + } + return -1; + } + + /** + * Returns a shallow copy of this ArrayList instance. (The elements + * themselves are not copied.) + * + * @return a clone of this ArrayList instance. + */ + @Override + public Object clone() { + try { + SpecialArrayList v = (SpecialArrayList) super.clone(); + v.elementData = new Object[size]; + System.arraycopy(elementData, 0, v.elementData, 0, size); + v.modCount = 0; + return v; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + } + + /** + * Returns an array containing all of the elements in this list in the correct + * order. + * + * @return an array containing all of the elements in this list in the correct + * order. + */ + @Override + public Object[] toArray() { + Object[] result = new Object[size]; + System.arraycopy(elementData, 0, result, 0, size); + return result; + } + + /** + * Returns an array containing all of the elements in this list in the correct + * order; the runtime type of the returned array is that of the specified array. + * If the list fits in the specified array, it is returned therein. Otherwise, a + * new array is allocated with the runtime type of the specified array and the + * size of this list. + *

                + *

                + * If the list fits in the specified array with room to spare (i.e., the array + * has more elements than the list), the element in the array immediately + * following the end of the collection is set to null. This is useful + * in determining the length of the list only if the caller knows that + * the list does not contain any null elements. + * + * @param a the array into which the elements of the list are to be stored, if + * it is big enough; otherwise, a new array of the same runtime type + * is allocated for this purpose. + * @return an array containing the elements of the list. + * @throws ArrayStoreException if the runtime type of a is not a supertype of the runtime type + * of every element in this list. + */ + @Override + public Object[] toArray(Object[] a) { + if (a.length < size) + a = (Object[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); + + System.arraycopy(elementData, 0, a, 0, size); + + if (a.length > size) + a[size] = null; + + return a; + } + + // Positional Access Operations + + /** + * Returns the element at the specified position in this list. + * + * @param index index of element to return. + * @return the element at the specified position in this list. + * @throws IndexOutOfBoundsException if index is out of range (index + * < 0 || index >= size()). + */ + @Override + public Object get(int index) { + RangeCheck(index); + return elementData[index]; + } + + /** + * Replaces the element at the specified position in this list with the + * specified element. + * + * @param index index of element to replace. + * @param element element to be stored at the specified position. + * @return the element previously at the specified position. + * @throws IndexOutOfBoundsException if index out of range + * (index < 0 || index >= size()). + */ + @Override + public Object set(int index, Object element) { + RangeCheck(index); + + Object oldValue = elementData[index]; + elementData[index] = element; + return oldValue; + } + + /** + * Appends the specified element to the end of this list. + * + * @param o element to be appended to this list. + * @return true (as per the general contract of Collection.add). + */ + @Override + public boolean add(Object o) { + ensureCapacity(size + 1); // Increments modCount!! + elementData[size++] = o; + return true; + } + + /** + * Inserts the specified element at the specified position in this list. Shifts + * the element currently at that position (if any) and any subsequent elements + * to the right (adds one to their indices). + * + * @param index index at which the specified element is to be inserted. + * @param element element to be inserted. + * @throws IndexOutOfBoundsException if index is out of range + * (index < 0 || index > size()). + */ + @Override + public void add(int index, Object element) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + + ensureCapacity(size + 1); // Increments modCount!! + System.arraycopy(elementData, index, elementData, index + 1, size - index); + elementData[index] = element; + size++; + } + + /** + * Removes the element at the specified position in this list. Shifts any + * subsequent elements to the left (subtracts one from their indices). + * + * @param index the index of the element to removed. + * @return the element that was removed from the list. + * @throws IndexOutOfBoundsException if index out of range (index + * < 0 || index >= size()). + */ + @Override + public Object remove(int index) { + RangeCheck(index); + + modCount++; + Object oldValue = elementData[index]; + + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index + 1, elementData, index, numMoved); + elementData[--size] = null; // Let gc do its work + + return oldValue; + } + + /** + * Removes all of the elements from this list. The list will be empty after this + * call returns. + */ + @Override + public void clear() { + modCount++; + + // Let gc do its work + for (int i = 0; i < size; i++) + elementData[i] = null; + + size = 0; + } + + /** + * Appends all of the elements in the specified Collection to the end of this + * list, in the order that they are returned by the specified Collection's + * Iterator. The behavior of this operation is undefined if the specified + * Collection is modified while the operation is in progress. (This implies that + * the behavior of this call is undefined if the specified Collection is this + * list, and this list is nonempty.) + * + * @param c the elements to be inserted into this list. + * @return true if this list changed as a result of the call. + * @throws NullPointerException if the specified collection is null. + */ + @Override + public boolean addAll(Collection c) { + Object[] a = c.toArray(); + // 20080124 KSC: replace with arraycopy + // this.elementData = a; + int numNew = a.length; + elementData = new Object[numNew]; + System.arraycopy(a, 0, elementData, 0, numNew); + // ensureCapacity(size + numNew); // Increments modCount + size += c.size(); + modCount += size; + modCount++; + return numNew != 0; + } + + /** + * Inserts all of the elements in the specified Collection into this list, + * starting at the specified position. Shifts the element currently at that + * position (if any) and any subsequent elements to the right (increases their + * indices). The new elements will appear in the list in the order that they are + * returned by the specified Collection's iterator. + * + * @param index index at which to insert first element from the specified + * collection. + * @param c elements to be inserted into this list. + * @return true if this list changed as a result of the call. + * @throws IndexOutOfBoundsException if index out of range (index + * < 0 || index > size()). + * @throws NullPointerException if the specified Collection is null. + */ + @Override + public boolean addAll(int index, Collection c) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + + Object[] a = c.toArray(); + int numNew = c.size(); // 20080121 KSC: a.length; + ensureCapacity(size + numNew); // Increments modCount + + int numMoved = size - index; + if (numMoved > 0) + System.arraycopy(elementData, index, elementData, index + numNew, numMoved); + + System.arraycopy(a, 0, elementData, index, numNew); + size += numNew; + return numNew != 0; + } + + /** + * Removes from this List all of the elements whose index is between fromIndex, + * inclusive and toIndex, exclusive. Shifts any succeeding elements to the left + * (reduces their index). This call shortens the list by + * (toIndex - fromIndex) elements. (If toIndex==fromIndex, + * this operation has no effect.) + * + * @param fromIndex index of first element to be removed. + * @param toIndex index after last element to be removed. + */ + @Override + protected void removeRange(int fromIndex, int toIndex) { + modCount++; + int numMoved = size - toIndex; + System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); + + // Let gc do its work + int newSize = size - (toIndex - fromIndex); + while (size != newSize) + elementData[--size] = null; + } + + /** + * Check if the given index is in range. If not, throw an appropriate runtime + * exception. This method does *not* check if the index is negative: It is + * always used immediately prior to an array access, which throws an + * ArrayIndexOutOfBoundsException if index is negative. + */ + private void RangeCheck(int index) { + if (index >= size) + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + } + + class Itr implements Iterator { + /** + * Index of element to be returned by subsequent call to next. + */ + int cursor = 0; + + /** + * Index of element returned by most recent call to next or previous. Reset to + * -1 if this element is deleted by a call to remove. + */ + int lastRet = -1; + + /** + * The modCount value that the iterator believes that the backing List should + * have. If this expectation is violated, the iterator has detected concurrent + * modification. + */ + int expectedModCount = modCount; + + public boolean hasNext() { + return cursor != size; + } + + public Object next() { + // checkForComodification(); + try { + Object next = elementData[cursor]; + lastRet = cursor++; + return next; + } catch (IndexOutOfBoundsException e) { + // checkForComodification(); + throw new NoSuchElementException(); + } + } + + public void remove() { + if (lastRet == -1) + throw new IllegalStateException(); + // checkForComodification(); + + try { + SpecialArrayList.this.remove(lastRet); + if (lastRet < cursor) + cursor--; + lastRet = -1; + expectedModCount = modCount; + } catch (IndexOutOfBoundsException e) { + throw new ConcurrentModificationException(); + } + } + + final void checkForComodification() { + // NOT! + } + } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/StringTool.java b/src/main/java/io/starter/toolkit/StringTool.java index dfb018e..b56e11c 100644 --- a/src/main/java/io/starter/toolkit/StringTool.java +++ b/src/main/java/io/starter/toolkit/StringTool.java @@ -2,9 +2,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -12,7 +12,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -20,7 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -29,1412 +29,1376 @@ */ package io.starter.toolkit; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.Reader; -import java.io.Serializable; -import java.io.StringWriter; +import java.io.*; import java.nio.CharBuffer; import java.util.StringTokenizer; /** * A collection of useful methods for manipulating Strings. - * */ public class StringTool implements Serializable { - // static final long serialVersionUID = - // -5757918511951798619l; - static final long serialVersionUID = -2761264230959133529l; - - /** - * replace illegal XML characters with their html counterparts - * - * ie: "&" is converted to "&" - * - * check out the w3 list of XML - * characters - * - * @param rep - * @return - */ - public static String convertXMLChars(String rep) { - return io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(rep) - .toString(); // 20110815 KSC: this method is more complete - } - - // test stuff - public static void main(String[] args) { - - String majorHTML = "Testing yes
                • item1
                • item2
                • item3

                newline
                newline2
                newline3 yes no 124<>something
                newline4bold"; - - Logger.logInfo(StringTool.stripHTML(majorHTML)); - - } - - /** - * strip out all most HTML tags - * - * @param rep - * @return a string stripped of all html tags - */ - public static String stripHTML(String rep) { - - // first convert newlines - rep = rep.replaceAll("<[B,b][R,r]?>", "\r\n"); - rep = rep.replaceAll("<[B,b][R,r]?/>", "\r\n"); - - rep = rep.replaceAll("<[L,l][I,i]?>", "\r\n\r\n"); - - StringBuffer ret = new StringBuffer(); - char[] cx = rep.toCharArray(); - boolean skipping = false; - for (int t = 0; t < cx.length; t++) { - char tt = cx[t]; - // begin match - if (tt == '<') { - skipping = true; - t++; - } else if (tt == '>') { - skipping = false; - } - if (!skipping && tt != '>') - ret.append(cx[t]); - - } - return ret.toString(); - } - - /** - * replace endoded text with normal text ie: "&" is converted to "&" - * - * check out the w3 list of XML - * characters - * - * @param rep - * @return - */ - public static String convertHTML(String rep) { - // if(true)return rep; - rep.replaceAll("&", "&"); - rep.replaceAll("'", "'"); - rep.replaceAll(""", "\""); - rep.replaceAll("<", "<"); - rep.replaceAll(">", ">"); - rep.replaceAll("©", ""); - return rep; - } - - /** - * If the string matches any part of the pattern, strip the pattern from the - * string. - */ - public static String stripMatch(String pattern, String matchstr) { - String upat = pattern.toUpperCase(); - String umat = matchstr.toUpperCase(); - String retval = ""; - if (umat.lastIndexOf(upat) > -1) { - int pos = umat.lastIndexOf(upat) + upat.length(); - retval = matchstr.substring(pos); - Logger.logInfo("foundpos: " + pos); - } - return retval; - } - - /** - * get the variable name for a "getXXXX" a field name per JavaBean java - * naming conventions. - * - * ie: Converts "getFirstName" to "firstName" - */ - public static String getVarNameFromGetMethod(String thismethod) { - int getidx = thismethod.indexOf("get"); - if (getidx < 0) - return ""; - String retval = thismethod.substring(getidx + 3); - retval = retval.substring(0, retval.length() - 2); - String upcase = retval.substring(0, 1); - upcase = upcase.toUpperCase(); - retval = retval.substring(1); - retval = upcase + retval; - return retval; - } - - /** - * converts java member naming convention to underscored DB-style naming - * convention - * - * ie: take upperCamelCase and turn into upper_camel_case - */ - public static String convertJavaStyletoDBConvention(String name) { - char[] chars = name.toCharArray(); - StringBuffer buf = new StringBuffer(); - for (int i = 0; i < chars.length; i++) { - // if there is a single upper-case letter, then it's a - // case-word - if (Character.isUpperCase(chars[i])) { - if ((i > 0) && (i + 1 < chars.length)) { - if (!Character.isUpperCase(chars[i])) - buf.append("_"); - } - buf.append(chars[i]); - } else { - buf.append(Character.toUpperCase(chars[i])); - } - } - return buf.toString(); - } - - /** - * converts java member naming convention to underscored DB-style naming - * convention - * - * ie: take upperCamelCase and turn into upper_camel_case - */ - public static String convertJavaStyletoFriendlyConvention(String name) { - if (name.equals("")) - return ""; - StringBuffer buf = new StringBuffer(); - char[] chars = name.toCharArray(); - buf.append(String.valueOf(chars[0]).toUpperCase()); - for (int i = 1; i < chars.length; i++) { - if (chars[i] == '_') { - chars[i++] = Character.toUpperCase(chars[i + 1]); - buf.append(" "); - buf.append(chars[i]); - } else { - buf.append(String.valueOf(chars[i]).toLowerCase()); - } - } - return buf.toString(); - } - - /** - * convert an Array to a String representation of its objects - * - * @param name - * @return - */ - public static String arrayToString(Object[] objs) { - StringBuffer ret = new StringBuffer("["); - for (int x = 0; x < objs.length; x++) { - ret.append(objs[x].toString()); - ret.append(", "); - } - ret.setLength(ret.length() - 1); - ret.append("]"); - return ret.toString(); - } - - /** - * Returns the given throwable's stack trace as a string. - * - * @param target - * the Throwable whose stack trace should be returned - * @return the stack trace of the given Throwable as a String - * @throws NullPointerException - * if target is null - */ - public static final String stackTraceToString(Throwable target) { - StringWriter trace = new StringWriter(); - PrintWriter writer = new PrintWriter(trace); - - target.printStackTrace(writer); - writer.flush(); - - return trace.toString(); - } - - /** - * converts strings to "proper" capitalization - * - * ie: take "mr. fraNK sMITH" and turn into "Mr. Frank Smith" - */ - public static String proper(String name) { - if (name.equals("")) - return ""; - StringBuffer buf = new StringBuffer(); - char[] chars = name.toCharArray(); - buf.append(String.valueOf(chars[0]).toUpperCase()); - for (int i = 1; i < chars.length; i++) { - if (chars[i] == ' ') { - buf.append(" "); - i++; - if (chars[i] != ' ') { - chars[i] = Character.toUpperCase(chars[i]); - buf.append(chars[i]); - } - } else { - buf.append(String.valueOf(chars[i]).toLowerCase()); - } - } - return buf.toString(); - } - - /** - * Each object in the array must support the .toString() method, as it will - * be used to render the object to its string representation. - */ - - public static String makeDelimitedList(Object[] list, String delimiter) { - StringBuffer listBuf = new StringBuffer(); - for (int i = 0; i < list.length; i++) { - if (i != 0) - listBuf.append(delimiter); - listBuf.append(list[i].toString()); - } - return listBuf.toString(); - } - - /** - * Returns an array of strings from a single string, similar to - * String.split() in JavaScript - */ - public static String[] splitString(String value, String delimeter) { - - StringTokenizer stoken = new StringTokenizer(value, delimeter); - String[] returnValue = new String[stoken.countTokens()]; - int i = 0; - while (stoken.hasMoreTokens()) { - returnValue[i] = stoken.nextToken(); - i++; - } - return returnValue; - } - - /** - * get compressed UNICODE string from uncompressed string - */ - public static String getCompressedUnicode(byte[] input) { - byte[] output = new byte[input.length / 2]; - int pos = 0; - for (int i = 0; i < input.length; i++) { - output[pos++] = input[i]; - i++; - } - return new String(output); - } - - public static String getLowerCaseFirstLetter(String thismember) { - String upcase = thismember.substring(0, 1); - upcase = upcase.toLowerCase(); - thismember = thismember.substring(1); - thismember = upcase + thismember; - return thismember; - } - - /** - * generate a "setXXXX" string from a field name per Extentech java naming - * conventions. - * - * ie: Converts "firstName" to "setFirstName" - */ - public static String getSetMethodNameFromVar(String thismember) { - return "set" + getUpperCaseFirstLetter(thismember); - } - - public static String getUpperCaseFirstLetter(String thismember) { - String upcase = thismember.substring(0, 1); - upcase = upcase.toUpperCase(); - thismember = thismember.substring(1); - thismember = upcase + thismember; - return thismember; - } - - /** - * generate a "getXXXX" string from a field name per Extentech java naming - * conventions. - * - * ie: Converts "firstName" to "getFirstName" - */ - public static String getGetMethodNameFromVar(String thismember) { - return "get" + getUpperCaseFirstLetter(thismember); - } - - /** - * converts underscored DB-style naming convention to java member naming - * convention - */ - public static String convertDBtoJavaStyleConvention(String name) { - int scoreloc = name.indexOf("_"); - StringBuffer buf = new StringBuffer(); - if (scoreloc < 0) - return name; - else { - char[] chars = name.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (chars[i] == '_') { - chars[i + 1] = Character.toUpperCase(chars[i + 1]); - } else { - buf.append(chars[i]); - } - } - } - return buf.toString(); - } - - /** - * converts underscored DB-style naming convention to java member naming - * convention - */ - public static String convertFilenameToJSPName(String name) { - StringBuffer buf = new StringBuffer(); - char[] chars = name.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (chars[i] == '_') { - chars[i + 1] = Character.toUpperCase(chars[i + 1]); - } else if (chars[i] == '-') { - chars[i + 1] = Character.toUpperCase(chars[i + 1]); - } else if (chars[i] == ' ') { - chars[i + 1] = Character.toUpperCase(chars[i + 1]); - } else { - buf.append(chars[i]); - } - } - return buf.toString(); - } - - /** - * Basically a String tokenizer - * - * @param instr - * @param token - * @return - */ - public static String[] getTokensUsingDelim(String instr, String token) { - if (instr.indexOf(token) < 0) { - String[] ret = new String[1]; - ret[0] = instr; - return ret; - } - CompatibleVector output = new CompatibleVector(); - new StringBuffer(); - int lastpos = 0, offset = 0; - int toklen = token.length(); - int pos = instr.indexOf(token); - // pos--; - while (pos > -1) { - if (lastpos > 0)// if the line starts with a token - offset = lastpos + toklen; - else - offset = 0; - String st = instr.substring(offset, pos); - output.add(st); - lastpos = pos; - pos = instr.indexOf(token, lastpos + 1); - } - if (lastpos < instr.length()) { - String st = instr.substring(lastpos + toklen); - output.add(st); - } - String[] retval = new String[output.size()]; - for (int i = 0; i < output.size(); i++) - retval[i] = (String) output.get(i); - return retval; - } - - // escaped slashes - String oneslash = String.valueOf((char) 0x005C); - String twoslash = oneslash + oneslash; - - /** - * - */ - public static String dbencode(String holder) { - return replaceText(holder, "'", "''", 0); - } - - /** - * Lose the whitespace at the end of strings... - * - * @param holder - * The String that you want stripped. - * @return Your stripped string. - */ - - public static String allTrim(String holder) { - holder = holder.trim(); - return rTrim(holder); - } - - /** - * strip trailing spaces - */ - public static String stripTrailingSpaces(String s) { - while (s.endsWith(" ")) - s = s.substring(0, s.length() - 1); - return s; - } - - /** - * Strips all occurences of a string from a given string. - * - * @param tostrip - * The String that you want stripped. - * @param stripchar - * The char you want stripped from the String. - * @return Your stripped string. - */ - - public static String strip(String tostrip, String stripstr) { - StringBuffer stripped = new StringBuffer(tostrip.length()); - while (tostrip.indexOf(stripstr) > -1) { - stripped.append(tostrip.substring(0, tostrip.indexOf(stripstr))); - tostrip = tostrip - .substring(tostrip.indexOf(stripstr) + stripstr.length()); - } - stripped.append(tostrip); - return (stripped.toString()); - } - - /** - * Strips all occurences of a character from a given string. - * - * @param tostrip - * The String that you want stripped. - * @param stripchar - * The char you want stripped from the String. - * @return Your stripped string. - */ - - public static String strip(String tostrip, char stripchar) { - StringBuffer stripped = new StringBuffer(tostrip.length()); - int i = 0; - char currentChar; - while (i < tostrip.length()) { - currentChar = tostrip.charAt(i); - if (currentChar == stripchar) { - i++; - } else { - stripped.append(currentChar); - i++; - } - } - return (stripped.toString()); - } - - /** - * Replaces an occurence of String B with String C within String A. This - * method is case sensitive. - * - * Example: String A = "I am a happy dog."; String A = - * stringtool.replaceText(A, "happy", "sad", 0); - * - * The result is A="I am a sad dog." - * - * @param originalText - * Original text - * @param replaceText - * Text to replace. - * @param replacementText - * Text to replace with. - * @param offset - * offset of replacement within original string. - * @return Processed text. - */ - public static String replaceText(String originalText, String replaceText, String replacementText, int offset, boolean skipmatch) { - if (!skipmatch) - return replaceText(originalText, replaceText, replacementText, offset); - - StringBuffer sb = new StringBuffer(); - if (originalText.indexOf(replaceText) < 0) { - return originalText; - } else { - int nextidx = 0, lastidx = 0, pos = 0; - int textlen = replaceText.length(); - int stringlen = originalText.length(); - - while (nextidx <= originalText.lastIndexOf(replaceText)) { - pos++; - nextidx = originalText.indexOf(replaceText, lastidx); - sb.append(originalText.substring(lastidx, nextidx)); - if (pos > offset) - sb.append(replacementText); - else - sb.append(replaceText); - nextidx += textlen; - if (textlen == 0) - break;// case of "" - lastidx = nextidx; - } - if (nextidx < stringlen) { - sb.append(originalText.substring(nextidx)); - } - return sb.toString(); - } - } - - /** - * Replaces an occurence of String B with String C within String A. This - * method is case sensitive. - * - * Example: String A = "I am a happy dog."; String A = - * stringtool.replaceText(A, "happy", "sad", 0); - * - * The result is A="I am a sad dog." - * - * @param originalText - * Original text - * @param replaceText - * Text to replace. - * @param replacementText - * Text to replace with. - * @param offset - * offset of replacement within original string. - * @return Processed text. - */ - public static String replaceText(String originalText, String replaceText, String replacementText, int offset) { - - int newlen = originalText.length() - replaceText.length() - + replacementText.length(); - - if (newlen < 1) - newlen = 0; - StringBuffer sb = new StringBuffer(newlen); - if (originalText.indexOf(replaceText) < 0) { - return originalText; - } else { - if (replaceText != null && replaceText.equals(replacementText)) - return originalText; // avoid infinite loops - int nextidx = 0, lastidx = 0; - int textlen = replaceText.length(); - int stringlen = originalText.length(); - while (nextidx <= originalText.lastIndexOf(replaceText)) { - nextidx = originalText.indexOf(replaceText, lastidx); - sb.append(originalText.substring(lastidx, nextidx + offset)); - sb.append(replacementText); - nextidx += textlen; - if (textlen == 0) - break; // case of "" - lastidx = nextidx; - } - if (nextidx < stringlen) { - sb.append(originalText.substring(nextidx)); - } - return sb.toString(); - } - } - - /** - * Trims whitespace from the right side of strings. - * - * @param originalText - * Text to trim. - * @return Trimmed text. - */ - public static String rTrim(String originalText) { - StringBuffer sb = new StringBuffer(originalText); - sb.reverse(); - String rstr = sb.toString(); - rstr.trim(); - sb = new StringBuffer(rstr); - sb.reverse(); - return sb.toString(); - } - - /** - * This method will retrieve the first instance of text between any two - * given patterns. This method is case sensitive. - * - * Example: - * - * String A = "I am a happy dog."; B = getTextBetweenDelims(A,"a","."); - * - * B is now equal to "m a happy dog". C declines comment. - * - * @param originalText - * Text to process. - * @param beginDelim - * Delimeter for beginning of retrieved section. - * @param endDelim - * Delimeter for end of retrieved section. - * @return Text between delims or "" if not found. - */ - public static String getTextBetweenNestedDelims(String originalText, String beginDelim, String endDelim) { - StringBuffer sb = new StringBuffer(originalText.length()); - // Check to see that both delimiters exist in the string - if ((originalText.indexOf(beginDelim) < 0) - || (originalText.lastIndexOf(endDelim) < 0)) { - return ""; - } else { - int begidx = originalText.indexOf(beginDelim) + beginDelim.length(); - int endidx = originalText.lastIndexOf(endDelim); - int holder = 0; - if (begidx < endidx) { - sb.append(originalText.substring(begidx, endidx)); - } else { - while ((begidx > endidx) && (endidx > -1)) { - holder = endidx; - endidx = originalText.lastIndexOf(endDelim, holder + 1); - } - if ((begidx < endidx) && (endidx > -1)) - sb.append(originalText.substring(begidx, endidx)); - } - return sb.toString(); - } - } - - /** - * This method will retrieve the first instance of text between any two - * given patterns. This method is case sensitive. - * - * Example: - * - * String A = "I am a happy dog."; B = getTextBetweenDelims(A,"a","."); - * - * B is now equal to "m a happy dog". C declines comment. - * - * @param originalText - * Text to process. - * @param beginDelim - * Delimeter for beginning of retrieved section. - * @param endDelim - * Delimeter for end of retrieved section. - * @return Text between delims or "" if not found. - */ - public static String getTextBetweenDelims(String originalText, String beginDelim, String endDelim) { - StringBuffer sb = new StringBuffer(originalText.length()); - // Check to see that both delimiters exist in the string - if ((originalText.indexOf(beginDelim) < 0) - || (originalText.indexOf(endDelim) < 0)) { - return ""; - } else { - int begidx = originalText.indexOf(beginDelim) + beginDelim.length(); - int endidx = originalText.indexOf(endDelim); - int holder = 0; - if (begidx < endidx) { - sb.append(originalText.substring(begidx, endidx)); - } else { - while ((begidx > endidx) && (endidx > -1)) { - holder = endidx; - endidx = originalText.indexOf(endDelim, holder + 1); - } - if ((begidx < endidx) && (endidx > -1)) - sb.append(originalText.substring(begidx, endidx)); - } - return sb.toString(); - } - } - - /** - * This method will replace any instance of given text within another - * string. This method is case sensitive. - * - * Example: - * - * String A = "I am a happy dog."; A = - * replaceSection(A,"happy","hippie cat", "dog"); - * - * A is now equal to "I am a hippie cat.". - * - * @param originalText - * Text to process. - * @param replaceBegin - * Beggining pattern of replaced section. - * @param replacementText - * Text to replace with. - * @param replaceEnd - * End pattern of replaced section. - * @return Processed text. - */ - - public static String replaceSection(String originalText, String replaceBegin, String replacementText, String replaceEnd) { - StringBuffer sb = new StringBuffer(originalText.length()); - if ((originalText.indexOf(replaceBegin) < 0) - || (originalText.indexOf(replaceEnd) < 0)) { - return originalText; - } else { - int begidx = originalText.indexOf(replaceBegin); - int endlen = replaceEnd.length(); - int endidx = originalText.indexOf(replaceEnd) + endlen; - int holder = 0; - if (begidx < endidx) - - { - sb.append(originalText.substring(0, begidx)); - sb.append(replacementText); - sb.append(originalText.substring(endidx)); - } else { - while ((begidx > endidx) && (endidx > -1)) { - holder = endidx; - endidx = originalText.indexOf(replaceEnd, holder + 1); - } - if ((begidx < endidx) && (endidx > -1)) { - sb.append(originalText.substring(0, begidx)); - sb.append(replacementText); - sb.append(originalText.substring(endidx + endlen)); - } - } - return sb.toString(); - } - } - - public static String StripChars(String theFilter, String theString) { - StringBuffer strOut = new StringBuffer(theString.length()); - char curChar; - for (int i = 0; i < theString.length(); i++) { - curChar = theString.charAt(i); - if (theFilter.indexOf(curChar) < 0) { // if it's not in the filter, - // send it thru - strOut.append(curChar); - } - } - return strOut.toString(); - } - - public static String UseOnlyChars(String theFilter, String theString) { - StringBuffer strOut = new StringBuffer(theString.length()); - char curChar; - for (int i = 0; i < theString.length(); i++) { - curChar = theString.charAt(i); - if (theFilter.indexOf(curChar) > -1) { // if it's in the filter, - // send it thru - strOut.append(curChar); - } - } - return strOut.toString(); - } - - public static String replaceChars(String theFilter, String theString, String replacement) { - StringBuffer strOut = new StringBuffer(theString.length()); - char curChar; - for (int i = 0; i < theString.length(); i++) { - curChar = theString.charAt(i); - if (theFilter.indexOf(curChar) < 0) { // if it's not in the filter, - // send it thru - strOut.append(curChar); - } else { - strOut.append(replacement); - } - } - return strOut.toString(); - } - - /** - * replace a section of text based on pattern match throughout string. - */ - public static String replaceText(String theString, String theFilter, String replacement) { - return replaceText(theString, theFilter, replacement, 0); - } - - public static boolean AllInRange(int x, int y, String theString) { - char curChar; - for (int i = 0; i < theString.length(); i++) { - curChar = theString.charAt(i); - if (curChar < x || curChar > y) { - return false; - } - } - return true; - } - - /** - * Replaces a specified token in a string with a value from the passed - * through array this is done in matching order from String to Arrray. - */ - public static String replaceTokenFromArray(String replace, String token, String[] vals) { - StringBuffer sb = new StringBuffer(); - StringTokenizer toke = new StringTokenizer(replace, token, false); - int i = 0; - // add the first element - while (toke.hasMoreTokens()) { - sb.append(toke.nextToken()); - if (i <= vals.length - 1) { - sb.append(vals[i]); - ++i; - } - } - return String.valueOf(sb); - } - - /** - * returns file path fPath qualified with an ending slash - * - * @param fPath - * @return fPath with an ending slash - */ - public static String qualifyFilePath(String fPath) { - StringTool.replaceChars("\\", fPath, "/"); - fPath = fPath.trim(); - if (!fPath.endsWith("/")) - fPath += "/"; - return fPath; - } - - /** - * splits a filepath into directory and filename - * - * @param filePath - * @return - */ - public static String[] splitFilepath(String filePath) { - String[] path = new String[2]; - filePath = StringTool.replaceText(filePath, "\\", "/"); - int lastpath = filePath.lastIndexOf("/"); - if (lastpath > -1) { // strip path and directory - path[0] = filePath.substring(0, lastpath + 1); // get directory - path[1] = filePath.substring(lastpath + 1); // strip directory from - // filename - } else - path[1] = filePath; - return path; - } - - /** - * strips the path portion from a filepath and returns the filename - * - * @param filePath - * @return - */ - public static String stripPath(String filePath) { - filePath = StringTool.replaceText(filePath, "\\", "/"); - int lastpath = filePath.lastIndexOf("/"); - if (lastpath > -1) - return filePath.substring(lastpath + 1); // strip directory from - // filename - - return filePath; - } - - /** - * strips the path portion from a filepath and returns it - * - * @param filePath - * @return - */ - public static String getPath(String filePath) { - filePath = StringTool.replaceText(filePath, "\\", "/"); - int lastpath = filePath.lastIndexOf("/"); - return filePath.substring(0, lastpath + 1); - } - - /** - * replaces the extension of a filepath with an new extension - * - * @param filepath - * Source FilePaht - * @param ext - * @return - */ - public static String replaceExtension(String filepath, String ext) { - int i = filepath.lastIndexOf("."); - String f = filepath; - if (i > 0) - f = filepath.substring(0, i) + ext; - else - f = filepath + ext; - return f; - } - - /** - * given a string, return the maximum of the width in pixels in the given - * the awt Font.
                - * NOTE: this method does not account for line feeds contained within - * strings - * - * @param f - * awt Font - * @param s - * String to compute - * @return double approximate width in pixels - */ - public static double getApproximateStringWidth(java.awt.Font f, String s) { - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() - .getFontMetrics(f); - /* - * width/height in pixels = (w/h field) * DPI of the display - * device / 72 - */ - double conversion = java.awt.Toolkit.getDefaultToolkit() - .getScreenResolution() / 72.0; - return fm.stringWidth(s) * conversion; // pixels * conversion - } - - /** - * given a string, return the maximum of the width in pixels in the given - * the awt Font Observing Line Breaks.
                - * @param f awt Font - * @param s String to compute - * @return double approximate width in pixels - */ - public static double getApproximateStringWidthLB(java.awt.Font f, String s) { - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() - .getFontMetrics(f); - /* - * width/height in pixels = (w/h field) * DPI of the display - * device / 72 - */ - double conversion = java.awt.Toolkit.getDefaultToolkit() - .getScreenResolution() / 72.0; - String[] ss = s.split("\n"); - double len = 0; - for (String st : ss) { - len = Math.max(len, fm.stringWidth(st) * conversion); - } - // return fm.stringWidth(s) * conversion; - return len; - } - - /** - * return the approximate witdth in width in pixels of the given character - * - * @param f - * awt Font - * @param c - * character - * @return double approximate width in pixels - */ - public static double getApproximateCharWidth(java.awt.Font f, Character c) { - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() - .getFontMetrics(f); - /* - * width/height in pixels = (w/h field) * DPI of the display - * device / 72 - */ - double conversion = java.awt.Toolkit.getDefaultToolkit() - .getScreenResolution() / 72.0; - return fm.charWidth(c) * conversion; - } - - /** - * return the approximate height it takes to display the given string in the - * given font in the given width - * - * @param f - * java.awt.Font - * @param s - * String - * @param w - * width in points - * @return - */ - public static double getApproximateHeight(java.awt.Font f, String s, double w) { - double len = StringTool.getApproximateStringWidth(f, s); - while (len > w) { - int lastSpace = -1; - int j = s.lastIndexOf("\n") + 1; - len = 0; - while (len < w && j < s.length()) { - len += StringTool.getApproximateCharWidth(f, s.charAt(j)); - if (s.charAt(j) == ' ') - lastSpace = j; - j++; - } - if (len < w) { - break; // got it - } - if (lastSpace == -1) { // no spaces to break apart - if (s.indexOf(' ') == -1) - break; - lastSpace = s.lastIndexOf(' '); // break at - } - s = s.substring(0, lastSpace) + "\n" + s.substring(lastSpace + 1); - } - int nl = s.split("\n").length; - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() - .getFontMetrics(f); - java.awt.font.LineMetrics lm = f - .getLineMetrics(s, fm.getFontRenderContext()); - // this calc appears to match Excel's ... - float l = lm.getLeading(); - // float h= lm.getHeight(); - // io.starter.toolkit.Logger.log("Font: " + - // f.toString()); - // io.starter.toolkit.Logger.log("l-i:" + - // fm.getLeading() + " l:" + l + " h-i:" + fm.getHeight() + - // " h:" + h + " a-i:" + fm.getAscent() + " a:" + - // lm.getAscent() + " d-i:" + fm.getDescent() + " d:" + - // lm.getDescent()); - float h = fm.getHeight(); // KSC: revert for now ... - l/3; // i don't - // know why but this seems to match Excel's - // the closest - return Math.ceil(h * (nl));// +1)); // KSC: added + 1 for testing - } - - /** - * converts an excel-style custom format to String.format custom format i.e. - %flags-width-precision-conversion - *

                NOTE: the pattern should be a single item in the excel-style format i.e.one of the terms in positive;negative;zero;text - * without semicolon. - *
                NOTE: the java-specific pattern returned will have the negative formatting (sign, parenthesis ...) and so, when used, - * the double value must be it's absolute value i.e String.format(pattern, Math.abs(d)) - *
                NOTE: date formats are handled separately. this only applies to number and currency formats -
                Excel-style: - 0 (zero) Digit placeholder. This code pads the value with zeros to fill the format. - # Digit placeholder. This code does not display extra zeros. - ? Digit placeholder. This code leaves a space for insignificant zeros but does not display them. - . (period) Decimal number. - % Percentage. Microsoft Excel multiplies by 100 and adds the % character. - , (comma) Thousands separator. A comma followed by a placeholder scales the number by a thousand. - E+ E- e+ e- Scientific notation. - Text Code Description - $ - + / ( ) : space These characters are displayed in the number. To display any other character, enclose the character in quotation marks or precede it with a backslash. - \character This code displays the character you specify. - "text" This code displays text. - * This code repeats the next character in the format to fill the column width. Note Only one asterisk per section of a format is allowed. - _ (underscore) This code skips the width of the next character. - This code is commonly used as "_)" (without the quotation marks) - to leave space for a closing parenthesis in a positive number format - when the negative number format includes parentheses. - This allows the values to line up at the decimal point. - @ Text placeholder. - * - *@param pattern String format pattern in Excel format - *@param isNegative true if the source is a negative number - * @return - */ - public static String convertPatternFromExcelToStringFormatter(String pattern, boolean isNegative) { - String curPattern = pattern; - String jpattern = ""; // return pattern - int w = 0; - int precision = 0; - String flags = ""; - char conversion = 'f'; // default - boolean inConversion = false; - boolean inPrecision = false; - boolean removeSign = false; // true if value is negative and pattern - // calls for parens or color change or ... - // i.e. don't display the negative sign - /* - * TODO: \ uXXX is Locale-specific to display? works - * manually ... - * TODO; finish fractional formats: ?/? - */ - for (int i = 0; i < curPattern.length(); i++) { - int c = curPattern.charAt(i); - switch (c) { - case '0': - w++; - if (!inConversion) { - jpattern += "%"; - inConversion = true; - } - if (inPrecision && conversion != 'E') - precision++; - break; - case '?': // don't really know what to do with this one! - break; - case '#': - if (!inConversion) { - jpattern += "%"; - inConversion = true; - } - // TODO: handle such as: ###0.00######### --- what's the - // format spec for that????? - // if (inPrecision) precision++; - break; - case ',': - flags += ","; - break; - case '.': - inPrecision = true; - break; - case 'E': - case 'e': - if (!inConversion) { - jpattern += "%"; - inConversion = true; - } - conversion = 'E'; - i++; // format is e+, E+, e- or E- - break; - case '[': // either color code or local-specific formatting - int j = ++i; - int k = j; - for (; i < curPattern.length(); i++) { // skip colors for now - c = curPattern.charAt(i); - if (c == '-') // got end of an extended char sequence - skip - // rest (Locale code ...) - k = i; - if (c == ']') - break; - } - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - if (k == j) // then it was a color string - removeSign = true; - else // it was a locale-specific string ... - jpattern += curPattern.substring(++j, k); - break; - case '"': // start of delimited text - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - for (i++; i < curPattern.length(); i++) { - c = curPattern.charAt(i); - if (c == '"') - break; - jpattern += (char) c; - - } - break; - // ignore - case '@': // text placeholder - jpattern += "%s"; - break; - case '*': // repeats the next char to fill -- IGNORE!!! - break; - case '(': // enclose negative #'s in parens - case ')': - // flags+="("; - if (isNegative) { - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - jpattern += (char) c; - removeSign = true; - } - break; - case '_': // skips the width of the next char - usually _) - to - // leave space for a closing parenthesis in a positive - // number format when the negative number format - // includes parentheses. This allows the values to line - // up at the decimal point. - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - i++; // skip next char -- true in all cases??? - break; - case '%': - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - jpattern += "%%"; - break; - case '\\': - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - int z; - if (i + 1 < curPattern.length() - && curPattern.charAt(i + 1) == 'u') - z = i + 6; - else - z = i + 1; - for (; i < z && i < curPattern.length(); i++) - jpattern += curPattern.charAt(i); - break; - default: // %, $, - space -- keep - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - jpattern += (char) c; - break; - } - } - if (inConversion) { - jpattern += flags + (w > 0 ? w : "") + "." + precision + conversion; - } - if (isNegative && !removeSign) - jpattern = "-" + jpattern; - // System.out.print("Original Pattern " + pattern + " new " - // + jpattern); - // patterns[z]= jpattern; - pattern = jpattern; - return pattern; - } - - public static String convertDatePatternFromExcelToStringFormatter(String pattern) { - String jpattern = ""; // return pattern - String dString = ""; // d string -- ddd ==> EEE and dddd ==> EEEE - String mString = ""; // m string -- either month (M, MM, MMM, MMMM) or - // minute - int prev = 0; - for (int i = 0; i < pattern.length(); i++) { - int c = pattern.charAt(i); - if (c != 'd' && !dString.equals("")) { - if (dString.length() <= 2) - jpattern += dString; - else if (dString.length() == 3) - jpattern += "EEE"; - else if (dString.length() == 4) - jpattern += "EEEE"; - dString = ""; - } else if (c != 'm' && !mString.equals("")) { - if (c == ':' || prev == 'h') { // it's time - jpattern += mString; - prev = c; - } else - jpattern += mString.toUpperCase(); - mString = ""; - } - - switch (c) { - case 'y': - jpattern += (char) c; - break; - case 'h': - jpattern += 'H'; // h in java is 1-24 excel h= 0-23 - prev = 'h'; - break; - case '\\': // found case of erroneous use of backslash, as in: - // mm\-dd\-yy ignore! - case '[': // no java equivalent of [h] [m] or [ss] == elapsed time - case ']': - break; - case 's': - jpattern += (char) c; - break; - case 'A': - if (pattern.substring(i, i + 5).equals("AM/PM")) { - jpattern += "a"; - i += 5; - for (int z = jpattern.length() - 2; z >= 0; z--) { - if (jpattern.charAt(z) == 'H') { - jpattern = jpattern.substring(0, z) + 'h' - + jpattern.substring(z + 1); - } - } - } - break; - case 'd': - dString += (char) c; - break; - case 'm': - mString += (char) c; - break; - default: - if (c != ':' && c != 'm') - prev = c; - jpattern += (char) c; - } - } - if (!mString.equals("")) { - if (prev == 'h') // it's time - jpattern += mString; - else - jpattern += mString.toUpperCase(); // remaining month string - } else if (!dString.equals("")) { - if (dString.length() <= 2) - jpattern += dString; - else if (dString.length() == 3) - jpattern += "EEE"; - else if (dString.length() == 4) - jpattern += "EEEE"; - dString = ""; - } - return jpattern; - } - - /** - * extract info, if any, from bracketed expressions within Excel custom number formats - * @param pattern String Excel number format - * @return String returned number format without the bracketed expression - */ - public static String convertPatternExtractBracketedExpression(String pattern) { - String[] s = pattern.split("\\["); - if (s.length > 1) { - pattern = ""; - for (int i = 0; i < s.length; i++) { - int zz = s[i].indexOf("]"); - if (zz != -1) { - String term = ""; - if (s[i].charAt(0) == '$') - term = s[i].substring(1, zz); // skip first $ - else - term = s[i].substring(0, zz); - if (term.indexOf("-") != -1) // extract character TODO: - // locale specifics - pattern += term.substring(0, term.indexOf("-")); - else - pattern += term; - } - pattern += s[i].substring(zz + 1); - } - } - return pattern; - } - - /** - * qualifies a pattern string to make valid for applying the pattern - * @param pattern - * @return - */ - public static String qualifyPatternString(String pattern) { - pattern = StringTool.strip(pattern, "*"); - pattern = StringTool.strip(pattern, "_("); // width placeholder - pattern = StringTool.strip(pattern, "_)"); // width placeholder - pattern = StringTool.strip(pattern, "_"); - pattern = pattern.replaceAll("\"", ""); - pattern = StringTool.strip(pattern, "?"); - // there are more bracketed expressions to deal with - // see - // http://office.microsoft.com/en-us/excel-help/creating-international-number-formats-HA001034635.aspx?redir=0 - // pattern = StringTool.strip(pattern, "[Red]"); // [Black] - // [h] [hhh] [=1] [=2] - // pattern = StringTool.strip(pattern, "Red]"); - // TODO: implement locale-specific entries: [$-409] [$-404] - // ... ******************** - // pattern= pattern.replaceAll("\\[.+?\\]", ""); - /* - * if (s.length > 1) { - * io.starter.toolkit.Logger.log(s[0]); - * java.util.regex.Pattern p = - * java.util.regex.Pattern.compile("\\[(.*?)\\]"); - * java.util.regex.Matcher m = p.matcher(pattern); - * - * while(m.find()) { - * io.starter.toolkit.Logger.log(m.group(1)); - * } - * } - */ - String[] s = pattern.split("\\["); - if (s.length > 1) { - pattern = ""; - for (int i = 0; i < s.length; i++) { - int zz = s[i].indexOf("]"); - if (zz != -1) { - String term = s[i].substring(1, zz); // skip first $ - if (term.indexOf("-") != -1) { // extract character TODO: - // locale specifics - pattern += term.substring(0, term.indexOf("-")); - } - } - pattern += s[i].substring(zz + 1); - } - } - return pattern; - } - - /** Reads from a Reader into a String. - * Blocking reads will be issued to the reader and the results will be - * concatenated into a string, which will be returned once the reader - * reports end-of-input. - * @param reader the Reader from which to read - * @return a string containing all characters read from the input - */ - public static String readString(Reader reader) throws IOException { - StringBuilder builder = new StringBuilder(); - CharBuffer buffer = CharBuffer.allocate(512); - - while (-1 != reader.read(buffer)) { - buffer.flip(); - builder.append(buffer); - buffer.clear(); - } - - return builder.toString(); - } + // static final long serialVersionUID = + // -5757918511951798619l; + static final long serialVersionUID = -2761264230959133529l; + + /** + * replace illegal XML characters with their html counterparts + *

                + * ie: "&" is converted to "&" + *

                + * check out the w3 list of XML + * characters + * + * @param rep + * @return + */ + public static String convertXMLChars(String rep) { + return io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(rep) + .toString(); // 20110815 KSC: this method is more complete + } + + // test stuff + public static void main(String[] args) { + + String majorHTML = "Testing yes

                • item1
                • item2
                • item3

                newline
                newline2
                newline3 yes no 124<>something
                newline4bold"; + + Logger.logInfo(StringTool.stripHTML(majorHTML)); + + } + + /** + * strip out all most HTML tags + * + * @param rep + * @return a string stripped of all html tags + */ + public static String stripHTML(String rep) { + + // first convert newlines + rep = rep.replaceAll("<[B,b][R,r]?>", "\r\n"); + rep = rep.replaceAll("<[B,b][R,r]?/>", "\r\n"); + + rep = rep.replaceAll("<[L,l][I,i]?>", "\r\n\r\n"); + + StringBuffer ret = new StringBuffer(); + char[] cx = rep.toCharArray(); + boolean skipping = false; + for (int t = 0; t < cx.length; t++) { + char tt = cx[t]; + // begin match + if (tt == '<') { + skipping = true; + t++; + } else if (tt == '>') { + skipping = false; + } + if (!skipping && tt != '>') + ret.append(cx[t]); + + } + return ret.toString(); + } + + /** + * replace endoded text with normal text ie: "&" is converted to "&" + *

                + * check out the w3 list of XML + * characters + * + * @param rep + * @return + */ + public static String convertHTML(String rep) { + // if(true)return rep; + rep.replaceAll("&", "&"); + rep.replaceAll("'", "'"); + rep.replaceAll(""", "\""); + rep.replaceAll("<", "<"); + rep.replaceAll(">", ">"); + rep.replaceAll("©", ""); + return rep; + } + + /** + * If the string matches any part of the pattern, strip the pattern from the + * string. + */ + public static String stripMatch(String pattern, String matchstr) { + String upat = pattern.toUpperCase(); + String umat = matchstr.toUpperCase(); + String retval = ""; + if (umat.lastIndexOf(upat) > -1) { + int pos = umat.lastIndexOf(upat) + upat.length(); + retval = matchstr.substring(pos); + Logger.logInfo("foundpos: " + pos); + } + return retval; + } + + /** + * get the variable name for a "getXXXX" a field name per JavaBean java + * naming conventions. + *

                + * ie: Converts "getFirstName" to "firstName" + */ + public static String getVarNameFromGetMethod(String thismethod) { + int getidx = thismethod.indexOf("get"); + if (getidx < 0) + return ""; + String retval = thismethod.substring(getidx + 3); + retval = retval.substring(0, retval.length() - 2); + String upcase = retval.substring(0, 1); + upcase = upcase.toUpperCase(); + retval = retval.substring(1); + retval = upcase + retval; + return retval; + } + + /** + * converts java member naming convention to underscored DB-style naming + * convention + *

                + * ie: take upperCamelCase and turn into upper_camel_case + */ + public static String convertJavaStyletoDBConvention(String name) { + char[] chars = name.toCharArray(); + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < chars.length; i++) { + // if there is a single upper-case letter, then it's a + // case-word + if (Character.isUpperCase(chars[i])) { + if ((i > 0) && (i + 1 < chars.length)) { + if (!Character.isUpperCase(chars[i])) + buf.append("_"); + } + buf.append(chars[i]); + } else { + buf.append(Character.toUpperCase(chars[i])); + } + } + return buf.toString(); + } + + /** + * converts java member naming convention to underscored DB-style naming + * convention + *

                + * ie: take upperCamelCase and turn into upper_camel_case + */ + public static String convertJavaStyletoFriendlyConvention(String name) { + if (name.equals("")) + return ""; + StringBuffer buf = new StringBuffer(); + char[] chars = name.toCharArray(); + buf.append(String.valueOf(chars[0]).toUpperCase()); + for (int i = 1; i < chars.length; i++) { + if (chars[i] == '_') { + chars[i++] = Character.toUpperCase(chars[i + 1]); + buf.append(" "); + buf.append(chars[i]); + } else { + buf.append(String.valueOf(chars[i]).toLowerCase()); + } + } + return buf.toString(); + } + + /** + * convert an Array to a String representation of its objects + * + * @param name + * @return + */ + public static String arrayToString(Object[] objs) { + StringBuffer ret = new StringBuffer("["); + for (int x = 0; x < objs.length; x++) { + ret.append(objs[x].toString()); + ret.append(", "); + } + ret.setLength(ret.length() - 1); + ret.append("]"); + return ret.toString(); + } + + /** + * Returns the given throwable's stack trace as a string. + * + * @param target the Throwable whose stack trace should be returned + * @return the stack trace of the given Throwable as a String + * @throws NullPointerException if target is null + */ + public static final String stackTraceToString(Throwable target) { + StringWriter trace = new StringWriter(); + PrintWriter writer = new PrintWriter(trace); + + target.printStackTrace(writer); + writer.flush(); + + return trace.toString(); + } + + /** + * converts strings to "proper" capitalization + *

                + * ie: take "mr. fraNK sMITH" and turn into "Mr. Frank Smith" + */ + public static String proper(String name) { + if (name.equals("")) + return ""; + StringBuffer buf = new StringBuffer(); + char[] chars = name.toCharArray(); + buf.append(String.valueOf(chars[0]).toUpperCase()); + for (int i = 1; i < chars.length; i++) { + if (chars[i] == ' ') { + buf.append(" "); + i++; + if (chars[i] != ' ') { + chars[i] = Character.toUpperCase(chars[i]); + buf.append(chars[i]); + } + } else { + buf.append(String.valueOf(chars[i]).toLowerCase()); + } + } + return buf.toString(); + } + + /** + * Each object in the array must support the .toString() method, as it will + * be used to render the object to its string representation. + */ + + public static String makeDelimitedList(Object[] list, String delimiter) { + StringBuffer listBuf = new StringBuffer(); + for (int i = 0; i < list.length; i++) { + if (i != 0) + listBuf.append(delimiter); + listBuf.append(list[i].toString()); + } + return listBuf.toString(); + } + + /** + * Returns an array of strings from a single string, similar to + * String.split() in JavaScript + */ + public static String[] splitString(String value, String delimeter) { + + StringTokenizer stoken = new StringTokenizer(value, delimeter); + String[] returnValue = new String[stoken.countTokens()]; + int i = 0; + while (stoken.hasMoreTokens()) { + returnValue[i] = stoken.nextToken(); + i++; + } + return returnValue; + } + + /** + * get compressed UNICODE string from uncompressed string + */ + public static String getCompressedUnicode(byte[] input) { + byte[] output = new byte[input.length / 2]; + int pos = 0; + for (int i = 0; i < input.length; i++) { + output[pos++] = input[i]; + i++; + } + return new String(output); + } + + public static String getLowerCaseFirstLetter(String thismember) { + String upcase = thismember.substring(0, 1); + upcase = upcase.toLowerCase(); + thismember = thismember.substring(1); + thismember = upcase + thismember; + return thismember; + } + + /** + * generate a "setXXXX" string from a field name per Extentech java naming + * conventions. + *

                + * ie: Converts "firstName" to "setFirstName" + */ + public static String getSetMethodNameFromVar(String thismember) { + return "set" + getUpperCaseFirstLetter(thismember); + } + + public static String getUpperCaseFirstLetter(String thismember) { + String upcase = thismember.substring(0, 1); + upcase = upcase.toUpperCase(); + thismember = thismember.substring(1); + thismember = upcase + thismember; + return thismember; + } + + /** + * generate a "getXXXX" string from a field name per Extentech java naming + * conventions. + *

                + * ie: Converts "firstName" to "getFirstName" + */ + public static String getGetMethodNameFromVar(String thismember) { + return "get" + getUpperCaseFirstLetter(thismember); + } + + /** + * converts underscored DB-style naming convention to java member naming + * convention + */ + public static String convertDBtoJavaStyleConvention(String name) { + int scoreloc = name.indexOf("_"); + StringBuffer buf = new StringBuffer(); + if (scoreloc < 0) + return name; + else { + char[] chars = name.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '_') { + chars[i + 1] = Character.toUpperCase(chars[i + 1]); + } else { + buf.append(chars[i]); + } + } + } + return buf.toString(); + } + + /** + * converts underscored DB-style naming convention to java member naming + * convention + */ + public static String convertFilenameToJSPName(String name) { + StringBuffer buf = new StringBuffer(); + char[] chars = name.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '_') { + chars[i + 1] = Character.toUpperCase(chars[i + 1]); + } else if (chars[i] == '-') { + chars[i + 1] = Character.toUpperCase(chars[i + 1]); + } else if (chars[i] == ' ') { + chars[i + 1] = Character.toUpperCase(chars[i + 1]); + } else { + buf.append(chars[i]); + } + } + return buf.toString(); + } + + /** + * Basically a String tokenizer + * + * @param instr + * @param token + * @return + */ + public static String[] getTokensUsingDelim(String instr, String token) { + if (instr.indexOf(token) < 0) { + String[] ret = new String[1]; + ret[0] = instr; + return ret; + } + CompatibleVector output = new CompatibleVector(); + new StringBuffer(); + int lastpos = 0, offset = 0; + int toklen = token.length(); + int pos = instr.indexOf(token); + // pos--; + while (pos > -1) { + if (lastpos > 0)// if the line starts with a token + offset = lastpos + toklen; + else + offset = 0; + String st = instr.substring(offset, pos); + output.add(st); + lastpos = pos; + pos = instr.indexOf(token, lastpos + 1); + } + if (lastpos < instr.length()) { + String st = instr.substring(lastpos + toklen); + output.add(st); + } + String[] retval = new String[output.size()]; + for (int i = 0; i < output.size(); i++) + retval[i] = (String) output.get(i); + return retval; + } + + // escaped slashes + String oneslash = String.valueOf((char) 0x005C); + String twoslash = oneslash + oneslash; + + /** + * + */ + public static String dbencode(String holder) { + return replaceText(holder, "'", "''", 0); + } + + /** + * Lose the whitespace at the end of strings... + * + * @param holder The String that you want stripped. + * @return Your stripped string. + */ + + public static String allTrim(String holder) { + holder = holder.trim(); + return rTrim(holder); + } + + /** + * strip trailing spaces + */ + public static String stripTrailingSpaces(String s) { + while (s.endsWith(" ")) + s = s.substring(0, s.length() - 1); + return s; + } + + /** + * Strips all occurences of a string from a given string. + * + * @param tostrip The String that you want stripped. + * @param stripchar The char you want stripped from the String. + * @return Your stripped string. + */ + + public static String strip(String tostrip, String stripstr) { + StringBuffer stripped = new StringBuffer(tostrip.length()); + while (tostrip.indexOf(stripstr) > -1) { + stripped.append(tostrip, 0, tostrip.indexOf(stripstr)); + tostrip = tostrip + .substring(tostrip.indexOf(stripstr) + stripstr.length()); + } + stripped.append(tostrip); + return (stripped.toString()); + } + + /** + * Strips all occurences of a character from a given string. + * + * @param tostrip The String that you want stripped. + * @param stripchar The char you want stripped from the String. + * @return Your stripped string. + */ + + public static String strip(String tostrip, char stripchar) { + StringBuffer stripped = new StringBuffer(tostrip.length()); + int i = 0; + char currentChar; + while (i < tostrip.length()) { + currentChar = tostrip.charAt(i); + if (currentChar == stripchar) { + i++; + } else { + stripped.append(currentChar); + i++; + } + } + return (stripped.toString()); + } + + /** + * Replaces an occurence of String B with String C within String A. This + * method is case sensitive. + *

                + * Example: String A = "I am a happy dog."; String A = + * stringtool.replaceText(A, "happy", "sad", 0); + *

                + * The result is A="I am a sad dog." + * + * @param originalText Original text + * @param replaceText Text to replace. + * @param replacementText Text to replace with. + * @param offset offset of replacement within original string. + * @return Processed text. + */ + public static String replaceText(String originalText, String replaceText, String replacementText, int offset, boolean skipmatch) { + if (!skipmatch) + return replaceText(originalText, replaceText, replacementText, offset); + + StringBuffer sb = new StringBuffer(); + if (originalText.indexOf(replaceText) < 0) { + return originalText; + } else { + int nextidx = 0, lastidx = 0, pos = 0; + int textlen = replaceText.length(); + int stringlen = originalText.length(); + + while (nextidx <= originalText.lastIndexOf(replaceText)) { + pos++; + nextidx = originalText.indexOf(replaceText, lastidx); + sb.append(originalText, lastidx, nextidx); + if (pos > offset) + sb.append(replacementText); + else + sb.append(replaceText); + nextidx += textlen; + if (textlen == 0) + break;// case of "" + lastidx = nextidx; + } + if (nextidx < stringlen) { + sb.append(originalText.substring(nextidx)); + } + return sb.toString(); + } + } + + /** + * Replaces an occurence of String B with String C within String A. This + * method is case sensitive. + *

                + * Example: String A = "I am a happy dog."; String A = + * stringtool.replaceText(A, "happy", "sad", 0); + *

                + * The result is A="I am a sad dog." + * + * @param originalText Original text + * @param replaceText Text to replace. + * @param replacementText Text to replace with. + * @param offset offset of replacement within original string. + * @return Processed text. + */ + public static String replaceText(String originalText, String replaceText, String replacementText, int offset) { + + int newlen = originalText.length() - replaceText.length() + + replacementText.length(); + + if (newlen < 1) + newlen = 0; + StringBuffer sb = new StringBuffer(newlen); + if (originalText.indexOf(replaceText) < 0) { + return originalText; + } else { + if (replaceText != null && replaceText.equals(replacementText)) + return originalText; // avoid infinite loops + int nextidx = 0, lastidx = 0; + int textlen = replaceText.length(); + int stringlen = originalText.length(); + while (nextidx <= originalText.lastIndexOf(replaceText)) { + nextidx = originalText.indexOf(replaceText, lastidx); + sb.append(originalText, lastidx, nextidx + offset); + sb.append(replacementText); + nextidx += textlen; + if (textlen == 0) + break; // case of "" + lastidx = nextidx; + } + if (nextidx < stringlen) { + sb.append(originalText.substring(nextidx)); + } + return sb.toString(); + } + } + + /** + * Trims whitespace from the right side of strings. + * + * @param originalText Text to trim. + * @return Trimmed text. + */ + public static String rTrim(String originalText) { + StringBuffer sb = new StringBuffer(originalText); + sb.reverse(); + String rstr = sb.toString(); + rstr.trim(); + sb = new StringBuffer(rstr); + sb.reverse(); + return sb.toString(); + } + + /** + * This method will retrieve the first instance of text between any two + * given patterns. This method is case sensitive. + *

                + * Example: + *

                + * String A = "I am a happy dog."; B = getTextBetweenDelims(A,"a","."); + *

                + * B is now equal to "m a happy dog". C declines comment. + * + * @param originalText Text to process. + * @param beginDelim Delimeter for beginning of retrieved section. + * @param endDelim Delimeter for end of retrieved section. + * @return Text between delims or "" if not found. + */ + public static String getTextBetweenNestedDelims(String originalText, String beginDelim, String endDelim) { + StringBuffer sb = new StringBuffer(originalText.length()); + // Check to see that both delimiters exist in the string + if ((originalText.indexOf(beginDelim) < 0) + || (originalText.lastIndexOf(endDelim) < 0)) { + return ""; + } else { + int begidx = originalText.indexOf(beginDelim) + beginDelim.length(); + int endidx = originalText.lastIndexOf(endDelim); + int holder = 0; + if (begidx < endidx) { + sb.append(originalText, begidx, endidx); + } else { + while ((begidx > endidx) && (endidx > -1)) { + holder = endidx; + endidx = originalText.lastIndexOf(endDelim, holder + 1); + } + if ((begidx < endidx) && (endidx > -1)) + sb.append(originalText, begidx, endidx); + } + return sb.toString(); + } + } + + /** + * This method will retrieve the first instance of text between any two + * given patterns. This method is case sensitive. + *

                + * Example: + *

                + * String A = "I am a happy dog."; B = getTextBetweenDelims(A,"a","."); + *

                + * B is now equal to "m a happy dog". C declines comment. + * + * @param originalText Text to process. + * @param beginDelim Delimeter for beginning of retrieved section. + * @param endDelim Delimeter for end of retrieved section. + * @return Text between delims or "" if not found. + */ + public static String getTextBetweenDelims(String originalText, String beginDelim, String endDelim) { + StringBuffer sb = new StringBuffer(originalText.length()); + // Check to see that both delimiters exist in the string + if ((originalText.indexOf(beginDelim) < 0) + || (originalText.indexOf(endDelim) < 0)) { + return ""; + } else { + int begidx = originalText.indexOf(beginDelim) + beginDelim.length(); + int endidx = originalText.indexOf(endDelim); + int holder = 0; + if (begidx < endidx) { + sb.append(originalText, begidx, endidx); + } else { + while ((begidx > endidx) && (endidx > -1)) { + holder = endidx; + endidx = originalText.indexOf(endDelim, holder + 1); + } + if ((begidx < endidx) && (endidx > -1)) + sb.append(originalText, begidx, endidx); + } + return sb.toString(); + } + } + + /** + * This method will replace any instance of given text within another + * string. This method is case sensitive. + *

                + * Example: + *

                + * String A = "I am a happy dog."; A = + * replaceSection(A,"happy","hippie cat", "dog"); + *

                + * A is now equal to "I am a hippie cat.". + * + * @param originalText Text to process. + * @param replaceBegin Beggining pattern of replaced section. + * @param replacementText Text to replace with. + * @param replaceEnd End pattern of replaced section. + * @return Processed text. + */ + + public static String replaceSection(String originalText, String replaceBegin, String replacementText, String replaceEnd) { + StringBuffer sb = new StringBuffer(originalText.length()); + if ((originalText.indexOf(replaceBegin) < 0) + || (originalText.indexOf(replaceEnd) < 0)) { + return originalText; + } else { + int begidx = originalText.indexOf(replaceBegin); + int endlen = replaceEnd.length(); + int endidx = originalText.indexOf(replaceEnd) + endlen; + int holder = 0; + if (begidx < endidx) { + sb.append(originalText, 0, begidx); + sb.append(replacementText); + sb.append(originalText.substring(endidx)); + } else { + while ((begidx > endidx) && (endidx > -1)) { + holder = endidx; + endidx = originalText.indexOf(replaceEnd, holder + 1); + } + if ((begidx < endidx) && (endidx > -1)) { + sb.append(originalText, 0, begidx); + sb.append(replacementText); + sb.append(originalText.substring(endidx + endlen)); + } + } + return sb.toString(); + } + } + + public static String StripChars(String theFilter, String theString) { + StringBuffer strOut = new StringBuffer(theString.length()); + char curChar; + for (int i = 0; i < theString.length(); i++) { + curChar = theString.charAt(i); + if (theFilter.indexOf(curChar) < 0) { // if it's not in the filter, + // send it thru + strOut.append(curChar); + } + } + return strOut.toString(); + } + + public static String UseOnlyChars(String theFilter, String theString) { + StringBuffer strOut = new StringBuffer(theString.length()); + char curChar; + for (int i = 0; i < theString.length(); i++) { + curChar = theString.charAt(i); + if (theFilter.indexOf(curChar) > -1) { // if it's in the filter, + // send it thru + strOut.append(curChar); + } + } + return strOut.toString(); + } + + public static String replaceChars(String theFilter, String theString, String replacement) { + StringBuffer strOut = new StringBuffer(theString.length()); + char curChar; + for (int i = 0; i < theString.length(); i++) { + curChar = theString.charAt(i); + if (theFilter.indexOf(curChar) < 0) { // if it's not in the filter, + // send it thru + strOut.append(curChar); + } else { + strOut.append(replacement); + } + } + return strOut.toString(); + } + + /** + * replace a section of text based on pattern match throughout string. + */ + public static String replaceText(String theString, String theFilter, String replacement) { + return replaceText(theString, theFilter, replacement, 0); + } + + public static boolean AllInRange(int x, int y, String theString) { + char curChar; + for (int i = 0; i < theString.length(); i++) { + curChar = theString.charAt(i); + if (curChar < x || curChar > y) { + return false; + } + } + return true; + } + + /** + * Replaces a specified token in a string with a value from the passed + * through array this is done in matching order from String to Arrray. + */ + public static String replaceTokenFromArray(String replace, String token, String[] vals) { + StringBuffer sb = new StringBuffer(); + StringTokenizer toke = new StringTokenizer(replace, token, false); + int i = 0; + // add the first element + while (toke.hasMoreTokens()) { + sb.append(toke.nextToken()); + if (i <= vals.length - 1) { + sb.append(vals[i]); + ++i; + } + } + return String.valueOf(sb); + } + + /** + * returns file path fPath qualified with an ending slash + * + * @param fPath + * @return fPath with an ending slash + */ + public static String qualifyFilePath(String fPath) { + StringTool.replaceChars("\\", fPath, "/"); + fPath = fPath.trim(); + if (!fPath.endsWith("/")) + fPath += "/"; + return fPath; + } + + /** + * splits a filepath into directory and filename + * + * @param filePath + * @return + */ + public static String[] splitFilepath(String filePath) { + String[] path = new String[2]; + filePath = StringTool.replaceText(filePath, "\\", "/"); + int lastpath = filePath.lastIndexOf("/"); + if (lastpath > -1) { // strip path and directory + path[0] = filePath.substring(0, lastpath + 1); // get directory + path[1] = filePath.substring(lastpath + 1); // strip directory from + // filename + } else + path[1] = filePath; + return path; + } + + /** + * strips the path portion from a filepath and returns the filename + * + * @param filePath + * @return + */ + public static String stripPath(String filePath) { + filePath = StringTool.replaceText(filePath, "\\", "/"); + int lastpath = filePath.lastIndexOf("/"); + if (lastpath > -1) + return filePath.substring(lastpath + 1); // strip directory from + // filename + + return filePath; + } + + /** + * strips the path portion from a filepath and returns it + * + * @param filePath + * @return + */ + public static String getPath(String filePath) { + filePath = StringTool.replaceText(filePath, "\\", "/"); + int lastpath = filePath.lastIndexOf("/"); + return filePath.substring(0, lastpath + 1); + } + + /** + * replaces the extension of a filepath with an new extension + * + * @param filepath Source FilePaht + * @param ext + * @return + */ + public static String replaceExtension(String filepath, String ext) { + int i = filepath.lastIndexOf("."); + String f = filepath; + if (i > 0) + f = filepath.substring(0, i) + ext; + else + f = filepath + ext; + return f; + } + + /** + * given a string, return the maximum of the width in pixels in the given + * the awt Font.
                + * NOTE: this method does not account for line feeds contained within + * strings + * + * @param f awt Font + * @param s String to compute + * @return double approximate width in pixels + */ + public static double getApproximateStringWidth(java.awt.Font f, String s) { + java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f); + /* + * width/height in pixels = (w/h field) * DPI of the display + * device / 72 + */ + double conversion = java.awt.Toolkit.getDefaultToolkit() + .getScreenResolution() / 72.0; + return fm.stringWidth(s) * conversion; // pixels * conversion + } + + /** + * given a string, return the maximum of the width in pixels in the given + * the awt Font Observing Line Breaks.
                + * + * @param f awt Font + * @param s String to compute + * @return double approximate width in pixels + */ + public static double getApproximateStringWidthLB(java.awt.Font f, String s) { + java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f); + /* + * width/height in pixels = (w/h field) * DPI of the display + * device / 72 + */ + double conversion = java.awt.Toolkit.getDefaultToolkit() + .getScreenResolution() / 72.0; + String[] ss = s.split("\n"); + double len = 0; + for (String st : ss) { + len = Math.max(len, fm.stringWidth(st) * conversion); + } + // return fm.stringWidth(s) * conversion; + return len; + } + + /** + * return the approximate witdth in width in pixels of the given character + * + * @param f awt Font + * @param c character + * @return double approximate width in pixels + */ + public static double getApproximateCharWidth(java.awt.Font f, Character c) { + java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f); + /* + * width/height in pixels = (w/h field) * DPI of the display + * device / 72 + */ + double conversion = java.awt.Toolkit.getDefaultToolkit() + .getScreenResolution() / 72.0; + return fm.charWidth(c) * conversion; + } + + /** + * return the approximate height it takes to display the given string in the + * given font in the given width + * + * @param f java.awt.Font + * @param s String + * @param w width in points + * @return + */ + public static double getApproximateHeight(java.awt.Font f, String s, double w) { + double len = StringTool.getApproximateStringWidth(f, s); + while (len > w) { + int lastSpace = -1; + int j = s.lastIndexOf("\n") + 1; + len = 0; + while (len < w && j < s.length()) { + len += StringTool.getApproximateCharWidth(f, s.charAt(j)); + if (s.charAt(j) == ' ') + lastSpace = j; + j++; + } + if (len < w) { + break; // got it + } + if (lastSpace == -1) { // no spaces to break apart + if (s.indexOf(' ') == -1) + break; + lastSpace = s.lastIndexOf(' '); // break at + } + s = s.substring(0, lastSpace) + "\n" + s.substring(lastSpace + 1); + } + int nl = s.split("\n").length; + java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f); + java.awt.font.LineMetrics lm = f + .getLineMetrics(s, fm.getFontRenderContext()); + // this calc appears to match Excel's ... + float l = lm.getLeading(); + // float h= lm.getHeight(); + // io.starter.toolkit.Logger.log("Font: " + + // f.toString()); + // io.starter.toolkit.Logger.log("l-i:" + + // fm.getLeading() + " l:" + l + " h-i:" + fm.getHeight() + + // " h:" + h + " a-i:" + fm.getAscent() + " a:" + + // lm.getAscent() + " d-i:" + fm.getDescent() + " d:" + + // lm.getDescent()); + float h = fm.getHeight(); // KSC: revert for now ... - l/3; // i don't + // know why but this seems to match Excel's + // the closest + return Math.ceil(h * (nl));// +1)); // KSC: added + 1 for testing + } + + /** + * converts an excel-style custom format to String.format custom format i.e. + * %flags-width-precision-conversion + *

                NOTE: the pattern should be a single item in the excel-style format i.e.one of the terms in positive;negative;zero;text + * without semicolon. + *
                NOTE: the java-specific pattern returned will have the negative formatting (sign, parenthesis ...) and so, when used, + * the double value must be it's absolute value i.e String.format(pattern, Math.abs(d)) + *
                NOTE: date formats are handled separately. this only applies to number and currency formats + *
                Excel-style: + * 0 (zero) Digit placeholder. This code pads the value with zeros to fill the format. + * # Digit placeholder. This code does not display extra zeros. + * ? Digit placeholder. This code leaves a space for insignificant zeros but does not display them. + * . (period) Decimal number. + * % Percentage. Microsoft Excel multiplies by 100 and adds the % character. + * , (comma) Thousands separator. A comma followed by a placeholder scales the number by a thousand. + * E+ E- e+ e- Scientific notation. + * Text Code Description + * $ - + / ( ) : space These characters are displayed in the number. To display any other character, enclose the character in quotation marks or precede it with a backslash. + * \character This code displays the character you specify. + * "text" This code displays text. + * This code repeats the next character in the format to fill the column width. Note Only one asterisk per section of a format is allowed. + * _ (underscore) This code skips the width of the next character. + * This code is commonly used as "_)" (without the quotation marks) + * to leave space for a closing parenthesis in a positive number format + * when the negative number format includes parentheses. + * This allows the values to line up at the decimal point. + * + * @param pattern String format pattern in Excel format + * @param isNegative true if the source is a negative number + * @return + * @ Text placeholder. + */ + public static String convertPatternFromExcelToStringFormatter(String pattern, boolean isNegative) { + String curPattern = pattern; + String jpattern = ""; // return pattern + int w = 0; + int precision = 0; + String flags = ""; + char conversion = 'f'; // default + boolean inConversion = false; + boolean inPrecision = false; + boolean removeSign = false; // true if value is negative and pattern + // calls for parens or color change or ... + // i.e. don't display the negative sign + /* + * TODO: \ uXXX is Locale-specific to display? works + * manually ... + * TODO; finish fractional formats: ?/? + */ + for (int i = 0; i < curPattern.length(); i++) { + int c = curPattern.charAt(i); + switch (c) { + case '0': + w++; + if (!inConversion) { + jpattern += "%"; + inConversion = true; + } + if (inPrecision && conversion != 'E') + precision++; + break; + case '?': // don't really know what to do with this one! + break; + case '#': + if (!inConversion) { + jpattern += "%"; + inConversion = true; + } + // TODO: handle such as: ###0.00######### --- what's the + // format spec for that????? + // if (inPrecision) precision++; + break; + case ',': + flags += ","; + break; + case '.': + inPrecision = true; + break; + case 'E': + case 'e': + if (!inConversion) { + jpattern += "%"; + inConversion = true; + } + conversion = 'E'; + i++; // format is e+, E+, e- or E- + break; + case '[': // either color code or local-specific formatting + int j = ++i; + int k = j; + for (; i < curPattern.length(); i++) { // skip colors for now + c = curPattern.charAt(i); + if (c == '-') // got end of an extended char sequence - skip + // rest (Locale code ...) + k = i; + if (c == ']') + break; + } + if (inConversion) { + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; + } + if (k == j) // then it was a color string + removeSign = true; + else // it was a locale-specific string ... + jpattern += curPattern.substring(++j, k); + break; + case '"': // start of delimited text + if (inConversion) { + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; + } + for (i++; i < curPattern.length(); i++) { + c = curPattern.charAt(i); + if (c == '"') + break; + jpattern += (char) c; + + } + break; + // ignore + case '@': // text placeholder + jpattern += "%s"; + break; + case '*': // repeats the next char to fill -- IGNORE!!! + break; + case '(': // enclose negative #'s in parens + case ')': + // flags+="("; + if (isNegative) { + if (inConversion) { + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; + } + jpattern += (char) c; + removeSign = true; + } + break; + case '_': // skips the width of the next char - usually _) - to + // leave space for a closing parenthesis in a positive + // number format when the negative number format + // includes parentheses. This allows the values to line + // up at the decimal point. + if (inConversion) { + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; + } + i++; // skip next char -- true in all cases??? + break; + case '%': + if (inConversion) { + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; + } + jpattern += "%%"; + break; + case '\\': + if (inConversion) { + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; + } + int z; + if (i + 1 < curPattern.length() + && curPattern.charAt(i + 1) == 'u') + z = i + 6; + else + z = i + 1; + for (; i < z && i < curPattern.length(); i++) + jpattern += curPattern.charAt(i); + break; + default: // %, $, - space -- keep + if (inConversion) { + inConversion = false; + inPrecision = false; + jpattern += flags + (w > 0 ? w : "") + "." + precision + + conversion; + } + jpattern += (char) c; + break; + } + } + if (inConversion) { + jpattern += flags + (w > 0 ? w : "") + "." + precision + conversion; + } + if (isNegative && !removeSign) + jpattern = "-" + jpattern; + // System.out.print("Original Pattern " + pattern + " new " + // + jpattern); + // patterns[z]= jpattern; + pattern = jpattern; + return pattern; + } + + public static String convertDatePatternFromExcelToStringFormatter(String pattern) { + String jpattern = ""; // return pattern + String dString = ""; // d string -- ddd ==> EEE and dddd ==> EEEE + String mString = ""; // m string -- either month (M, MM, MMM, MMMM) or + // minute + int prev = 0; + for (int i = 0; i < pattern.length(); i++) { + int c = pattern.charAt(i); + if (c != 'd' && !dString.equals("")) { + if (dString.length() <= 2) + jpattern += dString; + else if (dString.length() == 3) + jpattern += "EEE"; + else if (dString.length() == 4) + jpattern += "EEEE"; + dString = ""; + } else if (c != 'm' && !mString.equals("")) { + if (c == ':' || prev == 'h') { // it's time + jpattern += mString; + prev = c; + } else + jpattern += mString.toUpperCase(); + mString = ""; + } + + switch (c) { + case 'y': + jpattern += (char) c; + break; + case 'h': + jpattern += 'H'; // h in java is 1-24 excel h= 0-23 + prev = 'h'; + break; + case '\\': // found case of erroneous use of backslash, as in: + // mm\-dd\-yy ignore! + case '[': // no java equivalent of [h] [m] or [ss] == elapsed time + case ']': + break; + case 's': + jpattern += (char) c; + break; + case 'A': + if (pattern.substring(i, i + 5).equals("AM/PM")) { + jpattern += "a"; + i += 5; + for (int z = jpattern.length() - 2; z >= 0; z--) { + if (jpattern.charAt(z) == 'H') { + jpattern = jpattern.substring(0, z) + 'h' + + jpattern.substring(z + 1); + } + } + } + break; + case 'd': + dString += (char) c; + break; + case 'm': + mString += (char) c; + break; + default: + if (c != ':' && c != 'm') + prev = c; + jpattern += (char) c; + } + } + if (!mString.equals("")) { + if (prev == 'h') // it's time + jpattern += mString; + else + jpattern += mString.toUpperCase(); // remaining month string + } else if (!dString.equals("")) { + if (dString.length() <= 2) + jpattern += dString; + else if (dString.length() == 3) + jpattern += "EEE"; + else if (dString.length() == 4) + jpattern += "EEEE"; + dString = ""; + } + return jpattern; + } + + /** + * extract info, if any, from bracketed expressions within Excel custom number formats + * + * @param pattern String Excel number format + * @return String returned number format without the bracketed expression + */ + public static String convertPatternExtractBracketedExpression(String pattern) { + String[] s = pattern.split("\\["); + if (s.length > 1) { + pattern = ""; + for (int i = 0; i < s.length; i++) { + int zz = s[i].indexOf("]"); + if (zz != -1) { + String term = ""; + if (s[i].charAt(0) == '$') + term = s[i].substring(1, zz); // skip first $ + else + term = s[i].substring(0, zz); + if (term.indexOf("-") != -1) // extract character TODO: + // locale specifics + pattern += term.substring(0, term.indexOf("-")); + else + pattern += term; + } + pattern += s[i].substring(zz + 1); + } + } + return pattern; + } + + /** + * qualifies a pattern string to make valid for applying the pattern + * + * @param pattern + * @return + */ + public static String qualifyPatternString(String pattern) { + pattern = StringTool.strip(pattern, "*"); + pattern = StringTool.strip(pattern, "_("); // width placeholder + pattern = StringTool.strip(pattern, "_)"); // width placeholder + pattern = StringTool.strip(pattern, "_"); + pattern = pattern.replaceAll("\"", ""); + pattern = StringTool.strip(pattern, "?"); + // there are more bracketed expressions to deal with + // see + // http://office.microsoft.com/en-us/excel-help/creating-international-number-formats-HA001034635.aspx?redir=0 + // pattern = StringTool.strip(pattern, "[Red]"); // [Black] + // [h] [hhh] [=1] [=2] + // pattern = StringTool.strip(pattern, "Red]"); + // TODO: implement locale-specific entries: [$-409] [$-404] + // ... ******************** + // pattern= pattern.replaceAll("\\[.+?\\]", ""); + /* + * if (s.length > 1) { + * io.starter.toolkit.Logger.log(s[0]); + * java.util.regex.Pattern p = + * java.util.regex.Pattern.compile("\\[(.*?)\\]"); + * java.util.regex.Matcher m = p.matcher(pattern); + * + * while(m.find()) { + * io.starter.toolkit.Logger.log(m.group(1)); + * } + * } + */ + String[] s = pattern.split("\\["); + if (s.length > 1) { + pattern = ""; + for (int i = 0; i < s.length; i++) { + int zz = s[i].indexOf("]"); + if (zz != -1) { + String term = s[i].substring(1, zz); // skip first $ + if (term.indexOf("-") != -1) { // extract character TODO: + // locale specifics + pattern += term.substring(0, term.indexOf("-")); + } + } + pattern += s[i].substring(zz + 1); + } + } + return pattern; + } + + /** + * Reads from a Reader into a String. + * Blocking reads will be issued to the reader and the results will be + * concatenated into a string, which will be returned once the reader + * reports end-of-input. + * + * @param reader the Reader from which to read + * @return a string containing all characters read from the input + */ + public static String readString(Reader reader) throws IOException { + StringBuilder builder = new StringBuilder(); + CharBuffer buffer = CharBuffer.allocate(512); + + while (-1 != reader.read(buffer)) { + buffer.flip(); + builder.append(buffer); + buffer.clear(); + } + + return builder.toString(); + } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/TempFileGenerator.java b/src/main/java/io/starter/toolkit/TempFileGenerator.java index 6281534..8309f02 100644 --- a/src/main/java/io/starter/toolkit/TempFileGenerator.java +++ b/src/main/java/io/starter/toolkit/TempFileGenerator.java @@ -2,31 +2,29 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ -/** +/** * TempFileGenerator.java - * - * + *

                + *

                * Mar 14, 2012 - * - * */ package io.starter.toolkit; @@ -34,13 +32,12 @@ import java.io.IOException; - -/** +/** * */ public interface TempFileGenerator { - - public File createTempFile(String prefix, String extension) throws IOException; - - + + File createTempFile(String prefix, String extension) throws IOException; + + } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/TempFileManager.java b/src/main/java/io/starter/toolkit/TempFileManager.java index 4473ff8..ec49629 100644 --- a/src/main/java/io/starter/toolkit/TempFileManager.java +++ b/src/main/java/io/starter/toolkit/TempFileManager.java @@ -2,111 +2,105 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . * ---------- END COPYRIGHT NOTICE ---------- */ -/** +/** * TempFileManager.java - * - * + *

                + *

                * Feb 27, 2012 - * - * */ package io.starter.toolkit; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - import io.starter.OpenXLS.DocumentHandle; -/** +import java.io.*; + +/** * The TempFileManager allows for consolidated handling of all TempFiles used by OpenXLS. - * + * * TempFileManager is pluggable and allows you to implement a custom TempFileGenerator and install using * System properties. - * + * * ie: - * + * * System.setProperty(TempFileManager.TEMPFILE_MANAGER_CLASSNAME, "com.acme.CustomTempFileGenerator"); * WorkBookHandle bkx = new WorkBookHandle("test.xlsx"); // use custom tempfile generator - * + * * */ public class TempFileManager { - - public static String TEMPFILE_MANAGER_CLASSNAME = "io.starter.OpenXLS.tempfilemanager"; - - public static File createTempFile(String prefix, String extension) - throws IOException{ - String tmpfu = System.getProperty(TEMPFILE_MANAGER_CLASSNAME); - if(tmpfu != null){ - try { - TempFileGenerator tgen = (TempFileGenerator) Class.forName(tmpfu).newInstance(); - return tgen.createTempFile(prefix, extension); - } catch (Exception e) { - Logger.logErr("Could not load custom TempFileGenerator: " + tmpfu + ". Falling back to default TempFileGenerator."); - } - } - return new DefaultTempFileGeneratorImpl().createTempFile(prefix, extension); - } - - /** - * Write an InputStream to disk, and return as a file handle. - * - * @param input - * @param prefix - * @param extension - * @return - */ - public static File createTempFile(InputStream input, String prefix, String extension)throws IOException{ - File tmpfile = TempFileManager.createTempFile(prefix, extension); - JFileWriter.writeToFile(input,tmpfile); - return tmpfile; - } - /** - * Feb 27, 2012 - * @param string - * @param string2 - * @param dir - * @return - * @throws IOException - */ - public static File createTempFile(String prefix, String extension, File dir) throws IOException { - prefix = dir.getAbsolutePath() + prefix; - return createTempFile(prefix, extension); - } - - public static File writeToTempFile (String prefix, String extension, DocumentHandle doc) - throws IOException { - File file = createTempFile( prefix, extension ); - - BufferedOutputStream stream = - new BufferedOutputStream( new FileOutputStream( file ) ); - doc.write( stream, DocumentHandle.FORMAT_NATIVE ); - - stream.flush(); - stream.close(); - - return file; - } + public static String TEMPFILE_MANAGER_CLASSNAME = "io.starter.OpenXLS.tempfilemanager"; + + public static File createTempFile(String prefix, String extension) + throws IOException { + String tmpfu = System.getProperty(TEMPFILE_MANAGER_CLASSNAME); + if (tmpfu != null) { + try { + TempFileGenerator tgen = (TempFileGenerator) Class.forName(tmpfu).newInstance(); + return tgen.createTempFile(prefix, extension); + } catch (Exception e) { + Logger.logErr("Could not load custom TempFileGenerator: " + tmpfu + ". Falling back to default TempFileGenerator."); + } + } + return new DefaultTempFileGeneratorImpl().createTempFile(prefix, extension); + } + + /** + * Write an InputStream to disk, and return as a file handle. + * + * @param input + * @param prefix + * @param extension + * @return + */ + public static File createTempFile(InputStream input, String prefix, String extension) throws IOException { + File tmpfile = TempFileManager.createTempFile(prefix, extension); + JFileWriter.writeToFile(input, tmpfile); + return tmpfile; + } + + /** + * Feb 27, 2012 + * @param string + * @param string2 + * @param dir + * @return + * @throws IOException + */ + public static File createTempFile(String prefix, String extension, File dir) throws IOException { + prefix = dir.getAbsolutePath() + prefix; + return createTempFile(prefix, extension); + } + + public static File writeToTempFile(String prefix, String extension, DocumentHandle doc) + throws IOException { + File file = createTempFile(prefix, extension); + + BufferedOutputStream stream = + new BufferedOutputStream(new FileOutputStream(file)); + doc.write(stream, DocumentHandle.FORMAT_NATIVE); + + stream.flush(); + stream.close(); + + return file; + } } diff --git a/src/main/java/io/starter/toolkit/WriterOutputStream.java b/src/main/java/io/starter/toolkit/WriterOutputStream.java index 567c0b1..3c49d7c 100644 --- a/src/main/java/io/starter/toolkit/WriterOutputStream.java +++ b/src/main/java/io/starter/toolkit/WriterOutputStream.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -32,212 +32,221 @@ import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; -/** A WriterOutputStream is a bridge from byte streams to character +/** + * A WriterOutputStream is a bridge from byte streams to character * streams: bytes written to it are decoded into characters using a specified * {@linkplain Charset charset}. The charset that it uses may be specified by * name or given explicitly, or the system default charset may be used. The * decoded characters are written to a provided {@link Appendable}, which will * usually be a {@link Writer}. - * + * *

                The input is buffered so that writes don't need to be aligned to * character boundaries. Because conversion is only performed when the input * buffer is full (or when the stream is flushed) output also behaves as if it * were buffered. It is therefore usually unnecessary to externally buffer the * input or output.

                - * + * *

                In some charsets some or all characters are represented by multi-byte * sequences. If a byte sequence is encountered that is not valid in the input * charset or that cannot be mapped to a valid Unicode character it will be * replaced in the output with the value "\uFFFD". If more control * over the decoding process is required use {@link CharsetDecoder}.

                - * + * * @see InputStreamReader * @see OutputStreamWriter */ public class WriterOutputStream -extends OutputStream { - private static final int BUFFER_SIZE = 8192; - - private Appendable target; - private CharsetDecoder decoder; - private float bytesPerChar; - - private ByteBuffer inputBuffer; - private CharBuffer outputBuffer; - - /** Creates a WriterOutputStream with the default charset. - * @param target the sink for the decoded characters - */ - public WriterOutputStream (Appendable target) { - this( target, Charset.defaultCharset() ); - } - - /** Creates a WriterOutputStream with the named charset. - * @param target the sink for the decoded characters - * @param charset the character set with which to interpret the input bytes - */ - public WriterOutputStream (Appendable target, String charset) { - this( target, Charset.forName( charset ) ); - } - - /** Creates a WriterOutputStream with the given charset. - * @param target the sink for the decoded characters - * @param charset the character set with which to interpret the input bytes - */ - public WriterOutputStream (Appendable target, Charset charset) { - this.target = target; - - bytesPerChar = charset.newEncoder().maxBytesPerChar(); - decoder = charset.newDecoder(); - decoder.onMalformedInput( CodingErrorAction.REPLACE ); - decoder.onUnmappableCharacter( CodingErrorAction.REPLACE ); - - inputBuffer = ByteBuffer.allocate( - (int) Math.ceil( BUFFER_SIZE * bytesPerChar ) ); - outputBuffer = CharBuffer.allocate( BUFFER_SIZE ); - } - - @Override - public synchronized void write (byte[] buffer, int offset, int length) - throws IOException { - if (null == decoder) - throw new IOException( "this stream has been closed" ); - - // if the input buffer is too full decode it first - if (inputBuffer.remaining() < bytesPerChar) - this.decodeInputBuffer(); - - // Append the input to the buffer if it'll fit. If not and there are - // bytes left in the buffer fill it anyway so we don't lose them. - if (length <= inputBuffer.remaining() || inputBuffer.position() > 0) { - int fill = Math.min( inputBuffer.remaining(), length ); - inputBuffer.put( buffer, offset, fill ); - - // if we've buffered the entire input, we're done - if (fill == length) return; - - // otherwise, decode the input buffer - inputBuffer.flip(); - this.decode( inputBuffer ); - - fill -= inputBuffer.remaining(); - offset += fill; - length -= fill; - inputBuffer.clear(); - } - - // if the remaining input won't fit in the buffer decode it directly - if (length > inputBuffer.remaining()) { - ByteBuffer tempBuffer = ByteBuffer.wrap( buffer, offset, length ); - this.decode( tempBuffer ); - - // if any bytes are left over, put them in the input buffer - if (tempBuffer.hasRemaining()) - inputBuffer.put( tempBuffer ); - } - - // otherwise, just append it to the buffer - else inputBuffer.put( buffer, offset, length ); - } - - @Override - public synchronized void write (int b) - throws IOException { - if (null == decoder) - throw new IOException( "this stream has been closed" ); - - // if the buffer is full, decode it first - if (!inputBuffer.hasRemaining()) - this.decodeInputBuffer(); - - // append the input to the buffer - inputBuffer.put( (byte) b ); - } - - /** Flushes the input buffer through the decoder. - * If the input buffer ends with an incomplete character it will remain in - * the buffer. If the underlying character sink is a {@link Writer} its - * flush method will be called after all buffered input has - * been flushed. - */ - @Override - public void flush() - throws IOException { - synchronized (this) { - if (null == decoder) - throw new IOException( "this stream has been closed" ); - - this.decodeInputBuffer(); - } - - // flush the underlying Writer, if any - if (target instanceof Writer) - ((Writer) target).flush(); - } - - private void decodeInputBuffer() - throws IOException { - inputBuffer.flip(); - this.decode( inputBuffer ); - inputBuffer.compact(); - } - - private void decode (ByteBuffer bytes) - throws IOException { - CoderResult result; - - do { - outputBuffer.clear(); - result = decoder.decode( bytes, outputBuffer, false ); - - outputBuffer.flip(); - target.append( outputBuffer ); - } while (result == CoderResult.OVERFLOW); - } - - /** Closes the stream, flushing it first. - * If any partial characters remain in the input buffer the replacement - * value will be output in their place. If the underlying character sink is - * a {@link Writer} its close method will be called after all - * buffered input has been flushed. Once the stream has been closed further - * calls to write or flush will cause an - * IOException to be thrown. Closing a previously closed - * stream has no effect. - */ - @Override - public void close() - throws IOException { - synchronized (this) { - CoderResult result; - if (null == decoder) return; - - // flush the input buffer - inputBuffer.flip(); - do { - outputBuffer.clear(); - result = decoder.decode( inputBuffer, outputBuffer, true ); - - outputBuffer.flip(); - target.append( outputBuffer ); - } while (result == CoderResult.OVERFLOW); - - // flush the decoder - do { - outputBuffer.clear(); - result = decoder.flush( outputBuffer ); - - outputBuffer.flip(); - target.append( outputBuffer ); - } while (result == CoderResult.OVERFLOW); - - // release the buffers and decoder - inputBuffer = null; - outputBuffer = null; - decoder = null; - } - - // close the underlying Writer, if any - if (target instanceof Writer) - ((Writer) target).close(); - } + extends OutputStream { + private static final int BUFFER_SIZE = 8192; + + private Appendable target; + private CharsetDecoder decoder; + private float bytesPerChar; + + private ByteBuffer inputBuffer; + private CharBuffer outputBuffer; + + /** + * Creates a WriterOutputStream with the default charset. + * + * @param target the sink for the decoded characters + */ + public WriterOutputStream(Appendable target) { + this(target, Charset.defaultCharset()); + } + + /** + * Creates a WriterOutputStream with the named charset. + * + * @param target the sink for the decoded characters + * @param charset the character set with which to interpret the input bytes + */ + public WriterOutputStream(Appendable target, String charset) { + this(target, Charset.forName(charset)); + } + + /** + * Creates a WriterOutputStream with the given charset. + * + * @param target the sink for the decoded characters + * @param charset the character set with which to interpret the input bytes + */ + public WriterOutputStream(Appendable target, Charset charset) { + this.target = target; + + bytesPerChar = charset.newEncoder().maxBytesPerChar(); + decoder = charset.newDecoder(); + decoder.onMalformedInput(CodingErrorAction.REPLACE); + decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); + + inputBuffer = ByteBuffer.allocate( + (int) Math.ceil(BUFFER_SIZE * bytesPerChar)); + outputBuffer = CharBuffer.allocate(BUFFER_SIZE); + } + + @Override + public synchronized void write(byte[] buffer, int offset, int length) + throws IOException { + if (null == decoder) + throw new IOException("this stream has been closed"); + + // if the input buffer is too full decode it first + if (inputBuffer.remaining() < bytesPerChar) + this.decodeInputBuffer(); + + // Append the input to the buffer if it'll fit. If not and there are + // bytes left in the buffer fill it anyway so we don't lose them. + if (length <= inputBuffer.remaining() || inputBuffer.position() > 0) { + int fill = Math.min(inputBuffer.remaining(), length); + inputBuffer.put(buffer, offset, fill); + + // if we've buffered the entire input, we're done + if (fill == length) return; + + // otherwise, decode the input buffer + inputBuffer.flip(); + this.decode(inputBuffer); + + fill -= inputBuffer.remaining(); + offset += fill; + length -= fill; + inputBuffer.clear(); + } + + // if the remaining input won't fit in the buffer decode it directly + if (length > inputBuffer.remaining()) { + ByteBuffer tempBuffer = ByteBuffer.wrap(buffer, offset, length); + this.decode(tempBuffer); + + // if any bytes are left over, put them in the input buffer + if (tempBuffer.hasRemaining()) + inputBuffer.put(tempBuffer); + } + + // otherwise, just append it to the buffer + else inputBuffer.put(buffer, offset, length); + } + + @Override + public synchronized void write(int b) + throws IOException { + if (null == decoder) + throw new IOException("this stream has been closed"); + + // if the buffer is full, decode it first + if (!inputBuffer.hasRemaining()) + this.decodeInputBuffer(); + + // append the input to the buffer + inputBuffer.put((byte) b); + } + + /** + * Flushes the input buffer through the decoder. + * If the input buffer ends with an incomplete character it will remain in + * the buffer. If the underlying character sink is a {@link Writer} its + * flush method will be called after all buffered input has + * been flushed. + */ + @Override + public void flush() + throws IOException { + synchronized (this) { + if (null == decoder) + throw new IOException("this stream has been closed"); + + this.decodeInputBuffer(); + } + + // flush the underlying Writer, if any + if (target instanceof Writer) + ((Writer) target).flush(); + } + + private void decodeInputBuffer() + throws IOException { + inputBuffer.flip(); + this.decode(inputBuffer); + inputBuffer.compact(); + } + + private void decode(ByteBuffer bytes) + throws IOException { + CoderResult result; + + do { + outputBuffer.clear(); + result = decoder.decode(bytes, outputBuffer, false); + + outputBuffer.flip(); + target.append(outputBuffer); + } while (result == CoderResult.OVERFLOW); + } + + /** + * Closes the stream, flushing it first. + * If any partial characters remain in the input buffer the replacement + * value will be output in their place. If the underlying character sink is + * a {@link Writer} its close method will be called after all + * buffered input has been flushed. Once the stream has been closed further + * calls to write or flush will cause an + * IOException to be thrown. Closing a previously closed + * stream has no effect. + */ + @Override + public void close() + throws IOException { + synchronized (this) { + CoderResult result; + if (null == decoder) return; + + // flush the input buffer + inputBuffer.flip(); + do { + outputBuffer.clear(); + result = decoder.decode(inputBuffer, outputBuffer, true); + + outputBuffer.flip(); + target.append(outputBuffer); + } while (result == CoderResult.OVERFLOW); + + // flush the decoder + do { + outputBuffer.clear(); + result = decoder.flush(outputBuffer); + + outputBuffer.flip(); + target.append(outputBuffer); + } while (result == CoderResult.OVERFLOW); + + // release the buffers and decoder + inputBuffer = null; + outputBuffer = null; + decoder = null; + } + + // close the underlying Writer, if any + if (target instanceof Writer) + ((Writer) target).close(); + } } diff --git a/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.java b/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.java index 33db725..138b04f 100644 --- a/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.java +++ b/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,12 +22,7 @@ */ package io.starter.toolkit; -import java.util.AbstractList; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.ListIterator; -import java.util.NoSuchElementException; +import java.util.*; /* * Copyright 2004 The Apache Software Foundation @@ -55,14 +50,14 @@ * where elements are inserted and removed repeatedly from anywhere in the list. *

                * The following relative performance statistics are indicative of this class: - * + * *

                  *              get  add  insert  iterate  remove
                  * TreeList       3    5       1       2       1
                  * ArrayList      1    1      40       1      40
                  * LinkedList  5800    1     350       2     325
                  * 
                - * + * * ArrayList is a good general purpose list implementation. It is * faster than TreeList for most operations except inserting and * removing in the middle of the list. ArrayList also uses less @@ -71,872 +66,871 @@ * LinkedList is rarely a good choice of implementation. * TreeList is almost always a good replacement for it, although it * does use sligtly more memory. - * - * @since Commons Collections 3.1 - * @version $Revision: 1.2 $ $Date: 2010/01/26 14:53:49 $ * * @author Joerg Schmuecker * @author Stephen Colebourne + * @version $Revision: 1.2 $ $Date: 2010/01/26 14:53:49 $ + * @since Commons Collections 3.1 */ class XLSOptimizedTreeList extends AbstractList { - /** The root node in the AVL tree */ - private AVLNode root; - - /** The current size of the list */ - private int size; - - // ----------- - /** - * Constructs a new empty list. - */ - public XLSOptimizedTreeList() { - super(); - } - - /** - * Constructs a new empty list that copies the specified list. - * - * @param coll - * the collection to copy - * @throws NullPointerException - * if the collection is null - */ - public XLSOptimizedTreeList(Collection coll) { - super(); - addAll(coll); - } - - // ----------- - /** - * Gets the element at the specified index. - * - * @param index - * the index to retrieve - * @return the element at the specified index - */ - @Override - public Object get(int index) { - checkInterval(index, 0, size() - 1); - return root.get(index).getValue(); - } - - /** - * Gets the current size of the list. - * - * @return the current size - */ - @Override - public int size() { - return size; - } - - /** - * Gets an iterator over the list. - * - * @return an iterator over the list - */ - @Override - public Iterator iterator() { - // override to go 75% faster - return listIterator(0); - } - - /** - * Gets a ListIterator over the list. - * - * @return the new iterator - */ - @Override - public ListIterator listIterator() { - // override to go 75% faster - return listIterator(0); - } - - /** - * Gets a ListIterator over the list. - * - * @param fromIndex - * the index to start from - * @return the new iterator - */ - @Override - public ListIterator listIterator(int fromIndex) { - // override to go 75% faster - // cannot use EmptyIterator as iterator.add() must work - checkInterval(fromIndex, 0, size()); - return new TreeListIterator(this, fromIndex); - } - - /** - * Searches for the index of an object in the list. - * - * @return the index of the object, -1 if not found - */ - @Override - public int indexOf(Object object) { - // override to go 75% faster - if (root == null) { - return -1; - } - return root.indexOf(object, root.relativePosition); - } - - /** - * Searches for the presence of an object in the list. - * - * @return true if the object is found - */ - @Override - public boolean contains(Object object) { - return (indexOf(object) >= 0); - } - - /** - * Converts the list into an array. - * - * @return the list as an array - */ - @Override - public Object[] toArray() { - // override to go 20% faster - Object[] array = new Object[size()]; - if (root != null) { - root.toArray(array, root.relativePosition); - } - return array; - } - - // ----------- - /** - * Adds a new element to the list. - * - * @param index - * the index to add before - * @param obj - * the element to add - */ - @Override - public void add(int index, Object obj) { - modCount++; - checkInterval(index, 0, size()); - if (root == null) { - root = new AVLNode(index, obj, null, null); - } else { - root = root.insert(index, obj); - } - size++; - } - - /** - * Sets the element at the specified index. - * - * @param index - * the index to set - * @param obj - * the object to store at the specified index - * @return the previous object at that index - * @throws IndexOutOfBoundsException - * if the index is invalid - */ - @Override - public Object set(int index, Object obj) { - checkInterval(index, 0, size() - 1); - AVLNode node = root.get(index); - Object result = node.value; - node.setValue(obj); - return result; - } - - /** - * Removes the element at the specified index. - * - * @param index - * the index to remove - * @return the previous object at that index - */ - @Override - public Object remove(int index) { - modCount++; - checkInterval(index, 0, size() - 1); - Object result = get(index); - root = root.remove(index); - size--; - return result; - } - - /** - * Clears the list, removing all entries. - */ - @Override - public void clear() { - modCount++; - root = null; - size = 0; - } - - // ----------- - /** - * Checks whether the index is valid. - * - * @param index - * the index to check - * @param startIndex - * the first allowed index - * @param endIndex - * the last allowed index - * @throws IndexOutOfBoundsException - * if the index is invalid - */ - private void checkInterval(int index, int startIndex, int endIndex) { - if (index < startIndex || index > endIndex) { - throw new IndexOutOfBoundsException("Invalid index:" + index + ", size=" + size()); - } - } - - // ----------- - /** - * Implements an AVLNode which keeps the offset updated. - *

                - * This node contains the real work. TreeList is just there to implement - * {@link java.util.List}. The nodes don't know the index of the object they are - * holding. They do know however their position relative to their parent node. - * This allows to calculate the index of a node while traversing the tree. - *

                - * The Faedelung calculation stores a flag for both the left and right child to - * indicate if they are a child (false) or a link as in linked list (true). - */ - static class AVLNode { - /** The left child node or the predecessor if {@link #leftIsPrevious}. */ - private AVLNode left; - /** Flag indicating that left reference is not a subtree but the predecessor. */ - private boolean leftIsPrevious; - /** The right child node or the successor if {@link #rightIsNext}. */ - private AVLNode right; - /** Flag indicating that right reference is not a subtree but the successor. */ - private boolean rightIsNext; - /** How many levels of left/right are below this one. */ - private int height; - /** The relative position, root holds absolute position. */ - private int relativePosition; - /** The stored element. */ - private Object value; - - /** - * Constructs a new node with a relative position. - * - * @param relativePosition - * the relative position of the node - * @param obj - * the value for the ndoe - * @param rightFollower - * the node with the value following this one - * @param leftFollower - * the node with the value leading this one - */ - private AVLNode(int relativePosition, Object obj, AVLNode rightFollower, AVLNode leftFollower) { - this.relativePosition = relativePosition; - value = obj; - rightIsNext = true; - leftIsPrevious = true; - right = rightFollower; - left = leftFollower; - } - - /** - * Gets the value. - * - * @return the value of this node - */ - Object getValue() { - return value; - } - - /** - * Sets the value. - * - * @param obj - * the value to store - */ - void setValue(Object obj) { - this.value = obj; - } - - /** - * Locate the element with the given index relative to the offset of the parent - * of this node. - */ - AVLNode get(int index) { - int indexRelativeToMe = index - relativePosition; - - if (indexRelativeToMe == 0) { - return this; - } - - AVLNode nextNode = ((indexRelativeToMe < 0) ? getLeftSubTree() : getRightSubTree()); - if (nextNode == null) { - return null; - } - return nextNode.get(indexRelativeToMe); - } - - /** - * Locate the index that contains the specified object. - */ - int indexOf(Object object, int index) { - if (getLeftSubTree() != null) { - int result = left.indexOf(object, index + left.relativePosition); - if (result != -1) { - return result; - } - } - if (value == null ? value == object : value.equals(object)) { - return index; - } - if (getRightSubTree() != null) { - return right.indexOf(object, index + right.relativePosition); - } - return -1; - } - - /** - * Stores the node and its children into the array specified. - * - * @param array - * the array to be filled - * @param index - * the index of this node - */ - void toArray(Object[] array, int index) { - array[index] = value; - if (getLeftSubTree() != null) { - left.toArray(array, index + left.relativePosition); - } - if (getRightSubTree() != null) { - right.toArray(array, index + right.relativePosition); - } - } - - /** - * Gets the next node in the list after this one. - * - * @return the next node - */ - AVLNode next() { - if (rightIsNext || right == null) { - return right; - } - return right.min(); - } - - /** - * Gets the node in the list before this one. - * - * @return the previous node - */ - AVLNode previous() { - if (leftIsPrevious || left == null) { - return left; - } - return left.max(); - } - - /** - * Inserts a node at the position index. - * - * @param index - * is the index of the position relative to the position of the - * parent node. - * @param obj - * is the object to be stored in the position. - */ - AVLNode insert(int index, Object obj) { - int indexRelativeToMe = index - relativePosition; - - if (indexRelativeToMe <= 0) { - return insertOnLeft(indexRelativeToMe, obj); - } else { - return insertOnRight(indexRelativeToMe, obj); - } - } - - private AVLNode insertOnLeft(int indexRelativeToMe, Object obj) { - AVLNode ret = this; - - if (getLeftSubTree() == null) { - setLeft(new AVLNode(-1, obj, this, left), null); - } else { - setLeft(left.insert(indexRelativeToMe, obj), null); - } - - if (relativePosition >= 0) { - relativePosition++; - } - ret = balance(); - recalcHeight(); - return ret; - } - - private AVLNode insertOnRight(int indexRelativeToMe, Object obj) { - AVLNode ret = this; - - if (getRightSubTree() == null) { - setRight(new AVLNode(+1, obj, right, this), null); - } else { - setRight(right.insert(indexRelativeToMe, obj), null); - } - if (relativePosition < 0) { - relativePosition--; - } - ret = balance(); - recalcHeight(); - return ret; - } - - // ----------- - /** - * Gets the left node, returning null if its a faedelung. - */ - private AVLNode getLeftSubTree() { - return (leftIsPrevious ? null : left); - } - - /** - * Gets the right node, returning null if its a faedelung. - */ - private AVLNode getRightSubTree() { - return (rightIsNext ? null : right); - } - - /** - * Gets the rightmost child of this node. - * - * @return the rightmost child (greatest index) - */ - private AVLNode max() { - return (getRightSubTree() == null) ? this : right.max(); - } - - /** - * Gets the leftmost child of this node. - * - * @return the leftmost child (smallest index) - */ - private AVLNode min() { - return (getLeftSubTree() == null) ? this : left.min(); - } - - /** - * Removes the node at a given position. - * - * @param index - * is the index of the element to be removed relative to the position - * of the parent node of the current node. - */ - AVLNode remove(int index) { - int indexRelativeToMe = index - relativePosition; - - if (indexRelativeToMe == 0) { - return removeSelf(); - } - if (indexRelativeToMe > 0) { - setRight(right.remove(indexRelativeToMe), right.right); - if (relativePosition < 0) { - relativePosition++; - } - } else { - setLeft(left.remove(indexRelativeToMe), left.left); - if (relativePosition > 0) { - relativePosition--; - } - } - recalcHeight(); - return balance(); - } - - private AVLNode removeMax() { - if (getRightSubTree() == null) { - return removeSelf(); - } - setRight(right.removeMax(), right.right); - if (relativePosition < 0) { - relativePosition++; - } - recalcHeight(); - return balance(); - } - - private AVLNode removeMin() { - if (getLeftSubTree() == null) { - return removeSelf(); - } - setLeft(left.removeMin(), left.left); - if (relativePosition > 0) { - relativePosition--; - } - recalcHeight(); - return balance(); - } - - private AVLNode removeSelf() { - if (getRightSubTree() == null && getLeftSubTree() == null) - return null; - if (getRightSubTree() == null) { - if (relativePosition > 0) { - left.relativePosition += relativePosition + (relativePosition > 0 ? 0 : 1); - } - left.max().setRight(null, right); - return left; - } - if (getLeftSubTree() == null) { - right.relativePosition += relativePosition - (relativePosition < 0 ? 0 : 1); - right.min().setLeft(null, left); - return right; - } - - if (heightRightMinusLeft() > 0) { - AVLNode rightMin = right.min(); - value = rightMin.value; - if (leftIsPrevious) { - left = rightMin.left; - } - right = right.removeMin(); - if (relativePosition < 0) { - relativePosition++; - } - } else { - AVLNode leftMax = left.max(); - value = leftMax.value; - if (rightIsNext) { - right = leftMax.right; - } - left = left.removeMax(); - if (relativePosition > 0) { - relativePosition--; - } - } - recalcHeight(); - return this; - } - - // ----------- - /** - * Balances according to the AVL algorithm. - */ - private AVLNode balance() { - switch (heightRightMinusLeft()) { - case 1: - case 0: - case -1: - return this; - case -2: - if (left.heightRightMinusLeft() > 0) { - setLeft(left.rotateLeft(), null); - } - return rotateRight(); - case 2: - if (right.heightRightMinusLeft() < 0) { - setRight(right.rotateRight(), null); - } - return rotateLeft(); - default: - throw new RuntimeException("tree inconsistent!"); - } - } - - /** - * Gets the relative position. - */ - private int getOffset(AVLNode node) { - if (node == null) { - return 0; - } - return node.relativePosition; - } - - /** - * Sets the relative position. - */ - private int setOffset(AVLNode node, int newOffest) { - if (node == null) { - return 0; - } - int oldOffset = getOffset(node); - node.relativePosition = newOffest; - return oldOffset; - } - - /** - * Sets the height by calculation. - */ - private void recalcHeight() { - height = Math.max(getLeftSubTree() == null ? -1 : getLeftSubTree().height, - getRightSubTree() == null ? -1 : getRightSubTree().height) + 1; - } - - /** - * Returns the height of the node or -1 if the node is null. - */ - private int getHeight(AVLNode node) { - return (node == null ? -1 : node.height); - } - - /** - * Returns the height difference right - left - */ - private int heightRightMinusLeft() { - return getHeight(getRightSubTree()) - getHeight(getLeftSubTree()); - } - - private AVLNode rotateLeft() { - AVLNode newTop = right; // can't be faedelung! - AVLNode movedNode = getRightSubTree().getLeftSubTree(); - - int newTopPosition = relativePosition + getOffset(newTop); - int myNewPosition = -newTop.relativePosition; - int movedPosition = getOffset(newTop) + getOffset(movedNode); - - setRight(movedNode, newTop); - newTop.setLeft(this, null); - - setOffset(newTop, newTopPosition); - setOffset(this, myNewPosition); - setOffset(movedNode, movedPosition); - return newTop; - } - - private AVLNode rotateRight() { - AVLNode newTop = left; // can't be faedelung - AVLNode movedNode = getLeftSubTree().getRightSubTree(); - - int newTopPosition = relativePosition + getOffset(newTop); - int myNewPosition = -newTop.relativePosition; - int movedPosition = getOffset(newTop) + getOffset(movedNode); - - setLeft(movedNode, newTop); - newTop.setRight(this, null); - - setOffset(newTop, newTopPosition); - setOffset(this, myNewPosition); - setOffset(movedNode, movedPosition); - return newTop; - } - - private void setLeft(AVLNode node, AVLNode previous) { - leftIsPrevious = (node == null); - left = (leftIsPrevious ? previous : node); - recalcHeight(); - } - - private void setRight(AVLNode node, AVLNode next) { - rightIsNext = (node == null); - right = (rightIsNext ? next : node); - recalcHeight(); - } - - // private void checkFaedelung() { - // AVLNode maxNode = left.max(); - // if (!maxNode.rightIsFaedelung || maxNode.right != this) { - // throw new RuntimeException(maxNode + " should right-faedel to " + this); - // } - // AVLNode minNode = right.min(); - // if (!minNode.leftIsFaedelung || minNode.left != this) { - // throw new RuntimeException(maxNode + " should left-faedel to " + this); - // } - // } - // - // private int checkTreeDepth() { - // int hright = (getRightSubTree() == null ? -1 : - // getRightSubTree().checkTreeDepth()); - // // Logger.logInfo("checkTreeDepth"); - // // Logger.logInfo(this); - // // Logger.logInfo(" left: "); - // // Logger.logInfo(_left); - // // Logger.logInfo(" right: "); - // // Logger.logInfo(_right); - // - // int hleft = (left == null ? -1 : left.checkTreeDepth()); - // if (height != Math.max(hright, hleft) + 1) { - // throw new RuntimeException( - // "height should be max" + hleft + "," + hright + " but is " + height); - // } - // return height; - // } - // - // private int checkLeftSubNode() { - // if (getLeftSubTree() == null) { - // return 0; - // } - // int count = 1 + left.checkRightSubNode(); - // if (left.relativePosition != -count) { - // throw new RuntimeException(); - // } - // return count + left.checkLeftSubNode(); - // } - // - // private int checkRightSubNode() { - // AVLNode right = getRightSubTree(); - // if (right == null) { - // return 0; - // } - // int count = 1; - // count += right.checkLeftSubNode(); - // if (right.relativePosition != count) { - // throw new RuntimeException(); - // } - // return count + right.checkRightSubNode(); - // } - - /** - * Used for debugging. - */ - @Override - public String toString() { - return "AVLNode(" + relativePosition + "," + (left != null) + "," + value + "," - + (getRightSubTree() != null) + ", faedelung " + rightIsNext + " )"; - } - } - - /** - * A list iterator over the linked list. - */ - static class TreeListIterator implements ListIterator { - /** The parent list */ - protected final XLSOptimizedTreeList parent; - /** - * The node that will be returned by {@link #next()}. If this is equal to - * {@link AbstractLinkedList#header} then there are no more values to return. - */ - protected AVLNode next; - /** - * The index of {@link #next}. - */ - protected int nextIndex; - /** - * The last node that was returned by {@link #next()} or {@link #previous()}. - * Set to null if {@link #next()} or {@link #previous()} haven't - * been called, or if the node has been removed with {@link #remove()} or a new - * node added with {@link #add(Object)}. Should be accessed through - * {@link #getLastNodeReturned()} to enforce this behaviour. - */ - protected AVLNode current; - /** - * The index of {@link #current}. - */ - protected int currentIndex; - /** - * The modification count that the list is expected to have. If the list doesn't - * have this count, then a {@link java.util.ConcurrentModificationException} may - * be thrown by the operations. - */ - protected int expectedModCount; - - /** - * Create a ListIterator for a list. - * - * @param parent - * the parent list - * @param fromIndex - * the index to start at - */ - protected TreeListIterator(XLSOptimizedTreeList parent, int fromIndex) throws IndexOutOfBoundsException { - super(); - this.parent = parent; - this.expectedModCount = parent.modCount; - this.next = (parent.root == null ? null : parent.root.get(fromIndex)); - this.nextIndex = fromIndex; - } - - /** - * Checks the modification count of the list is the value that this object - * expects. - * - * @throws ConcurrentModificationException - * If the list's modification count isn't the value that was - * expected. - */ - protected void checkModCount() { - if (parent.modCount != expectedModCount) { - // How about Allow it??? - // throw new ConcurrentModificationException(); - } - } - - public boolean hasNext() { - return (nextIndex < parent.size()); - } - - public Object next() { - checkModCount(); - if (!hasNext()) { - throw new NoSuchElementException("No element at index " + nextIndex + "."); - } - if (next == null) { - next = parent.root.get(nextIndex); - } - Object value = next.getValue(); - current = next; - currentIndex = nextIndex++; - next = next.next(); - return value; - } - - public boolean hasPrevious() { - return (nextIndex > 0); - } - - public Object previous() { - checkModCount(); - if (!hasPrevious()) { - throw new NoSuchElementException("Already at start of list."); - } - if (next == null) { - next = parent.root.get(nextIndex - 1); - } else { - next = next.previous(); - } - Object value = next.getValue(); - current = next; - currentIndex = --nextIndex; - return value; - } - - public int nextIndex() { - return nextIndex; - } - - public int previousIndex() { - return nextIndex() - 1; - } - - public void remove() { - checkModCount(); - if (current == null) { - throw new IllegalStateException(); - } - parent.remove(currentIndex); - current = null; - currentIndex = -1; - nextIndex--; - expectedModCount++; - } - - public void set(Object obj) { - checkModCount(); - if (current == null) { - throw new IllegalStateException(); - } - current.setValue(obj); - } - - public void add(Object obj) { - checkModCount(); - parent.add(nextIndex, obj); - current = null; - currentIndex = -1; - nextIndex++; - expectedModCount++; - } - } + /** + * The root node in the AVL tree + */ + private AVLNode root; + + /** + * The current size of the list + */ + private int size; + + // ----------- + + /** + * Constructs a new empty list. + */ + public XLSOptimizedTreeList() { + super(); + } + + /** + * Constructs a new empty list that copies the specified list. + * + * @param coll the collection to copy + * @throws NullPointerException if the collection is null + */ + public XLSOptimizedTreeList(Collection coll) { + super(); + addAll(coll); + } + + // ----------- + + /** + * Gets the element at the specified index. + * + * @param index the index to retrieve + * @return the element at the specified index + */ + @Override + public Object get(int index) { + checkInterval(index, 0, size() - 1); + return root.get(index).getValue(); + } + + /** + * Gets the current size of the list. + * + * @return the current size + */ + @Override + public int size() { + return size; + } + + /** + * Gets an iterator over the list. + * + * @return an iterator over the list + */ + @Override + public Iterator iterator() { + // override to go 75% faster + return listIterator(0); + } + + /** + * Gets a ListIterator over the list. + * + * @return the new iterator + */ + @Override + public ListIterator listIterator() { + // override to go 75% faster + return listIterator(0); + } + + /** + * Gets a ListIterator over the list. + * + * @param fromIndex the index to start from + * @return the new iterator + */ + @Override + public ListIterator listIterator(int fromIndex) { + // override to go 75% faster + // cannot use EmptyIterator as iterator.add() must work + checkInterval(fromIndex, 0, size()); + return new TreeListIterator(this, fromIndex); + } + + /** + * Searches for the index of an object in the list. + * + * @return the index of the object, -1 if not found + */ + @Override + public int indexOf(Object object) { + // override to go 75% faster + if (root == null) { + return -1; + } + return root.indexOf(object, root.relativePosition); + } + + /** + * Searches for the presence of an object in the list. + * + * @return true if the object is found + */ + @Override + public boolean contains(Object object) { + return (indexOf(object) >= 0); + } + + /** + * Converts the list into an array. + * + * @return the list as an array + */ + @Override + public Object[] toArray() { + // override to go 20% faster + Object[] array = new Object[size()]; + if (root != null) { + root.toArray(array, root.relativePosition); + } + return array; + } + + // ----------- + + /** + * Adds a new element to the list. + * + * @param index the index to add before + * @param obj the element to add + */ + @Override + public void add(int index, Object obj) { + modCount++; + checkInterval(index, 0, size()); + if (root == null) { + root = new AVLNode(index, obj, null, null); + } else { + root = root.insert(index, obj); + } + size++; + } + + /** + * Sets the element at the specified index. + * + * @param index the index to set + * @param obj the object to store at the specified index + * @return the previous object at that index + * @throws IndexOutOfBoundsException if the index is invalid + */ + @Override + public Object set(int index, Object obj) { + checkInterval(index, 0, size() - 1); + AVLNode node = root.get(index); + Object result = node.value; + node.setValue(obj); + return result; + } + + /** + * Removes the element at the specified index. + * + * @param index the index to remove + * @return the previous object at that index + */ + @Override + public Object remove(int index) { + modCount++; + checkInterval(index, 0, size() - 1); + Object result = get(index); + root = root.remove(index); + size--; + return result; + } + + /** + * Clears the list, removing all entries. + */ + @Override + public void clear() { + modCount++; + root = null; + size = 0; + } + + // ----------- + + /** + * Checks whether the index is valid. + * + * @param index the index to check + * @param startIndex the first allowed index + * @param endIndex the last allowed index + * @throws IndexOutOfBoundsException if the index is invalid + */ + private void checkInterval(int index, int startIndex, int endIndex) { + if (index < startIndex || index > endIndex) { + throw new IndexOutOfBoundsException("Invalid index:" + index + ", size=" + size()); + } + } + + // ----------- + + /** + * Implements an AVLNode which keeps the offset updated. + *

                + * This node contains the real work. TreeList is just there to implement + * {@link java.util.List}. The nodes don't know the index of the object they are + * holding. They do know however their position relative to their parent node. + * This allows to calculate the index of a node while traversing the tree. + *

                + * The Faedelung calculation stores a flag for both the left and right child to + * indicate if they are a child (false) or a link as in linked list (true). + */ + static class AVLNode { + /** + * The left child node or the predecessor if {@link #leftIsPrevious}. + */ + private AVLNode left; + /** + * Flag indicating that left reference is not a subtree but the predecessor. + */ + private boolean leftIsPrevious; + /** + * The right child node or the successor if {@link #rightIsNext}. + */ + private AVLNode right; + /** + * Flag indicating that right reference is not a subtree but the successor. + */ + private boolean rightIsNext; + /** + * How many levels of left/right are below this one. + */ + private int height; + /** + * The relative position, root holds absolute position. + */ + private int relativePosition; + /** + * The stored element. + */ + private Object value; + + /** + * Constructs a new node with a relative position. + * + * @param relativePosition the relative position of the node + * @param obj the value for the ndoe + * @param rightFollower the node with the value following this one + * @param leftFollower the node with the value leading this one + */ + private AVLNode(int relativePosition, Object obj, AVLNode rightFollower, AVLNode leftFollower) { + this.relativePosition = relativePosition; + value = obj; + rightIsNext = true; + leftIsPrevious = true; + right = rightFollower; + left = leftFollower; + } + + /** + * Gets the value. + * + * @return the value of this node + */ + Object getValue() { + return value; + } + + /** + * Sets the value. + * + * @param obj the value to store + */ + void setValue(Object obj) { + this.value = obj; + } + + /** + * Locate the element with the given index relative to the offset of the parent + * of this node. + */ + AVLNode get(int index) { + int indexRelativeToMe = index - relativePosition; + + if (indexRelativeToMe == 0) { + return this; + } + + AVLNode nextNode = ((indexRelativeToMe < 0) ? getLeftSubTree() : getRightSubTree()); + if (nextNode == null) { + return null; + } + return nextNode.get(indexRelativeToMe); + } + + /** + * Locate the index that contains the specified object. + */ + int indexOf(Object object, int index) { + if (getLeftSubTree() != null) { + int result = left.indexOf(object, index + left.relativePosition); + if (result != -1) { + return result; + } + } + if (value == null ? value == object : value.equals(object)) { + return index; + } + if (getRightSubTree() != null) { + return right.indexOf(object, index + right.relativePosition); + } + return -1; + } + + /** + * Stores the node and its children into the array specified. + * + * @param array the array to be filled + * @param index the index of this node + */ + void toArray(Object[] array, int index) { + array[index] = value; + if (getLeftSubTree() != null) { + left.toArray(array, index + left.relativePosition); + } + if (getRightSubTree() != null) { + right.toArray(array, index + right.relativePosition); + } + } + + /** + * Gets the next node in the list after this one. + * + * @return the next node + */ + AVLNode next() { + if (rightIsNext || right == null) { + return right; + } + return right.min(); + } + + /** + * Gets the node in the list before this one. + * + * @return the previous node + */ + AVLNode previous() { + if (leftIsPrevious || left == null) { + return left; + } + return left.max(); + } + + /** + * Inserts a node at the position index. + * + * @param index is the index of the position relative to the position of the + * parent node. + * @param obj is the object to be stored in the position. + */ + AVLNode insert(int index, Object obj) { + int indexRelativeToMe = index - relativePosition; + + if (indexRelativeToMe <= 0) { + return insertOnLeft(indexRelativeToMe, obj); + } else { + return insertOnRight(indexRelativeToMe, obj); + } + } + + private AVLNode insertOnLeft(int indexRelativeToMe, Object obj) { + AVLNode ret = this; + + if (getLeftSubTree() == null) { + setLeft(new AVLNode(-1, obj, this, left), null); + } else { + setLeft(left.insert(indexRelativeToMe, obj), null); + } + + if (relativePosition >= 0) { + relativePosition++; + } + ret = balance(); + recalcHeight(); + return ret; + } + + private AVLNode insertOnRight(int indexRelativeToMe, Object obj) { + AVLNode ret = this; + + if (getRightSubTree() == null) { + setRight(new AVLNode(+1, obj, right, this), null); + } else { + setRight(right.insert(indexRelativeToMe, obj), null); + } + if (relativePosition < 0) { + relativePosition--; + } + ret = balance(); + recalcHeight(); + return ret; + } + + // ----------- + + /** + * Gets the left node, returning null if its a faedelung. + */ + private AVLNode getLeftSubTree() { + return (leftIsPrevious ? null : left); + } + + /** + * Gets the right node, returning null if its a faedelung. + */ + private AVLNode getRightSubTree() { + return (rightIsNext ? null : right); + } + + /** + * Gets the rightmost child of this node. + * + * @return the rightmost child (greatest index) + */ + private AVLNode max() { + return (getRightSubTree() == null) ? this : right.max(); + } + + /** + * Gets the leftmost child of this node. + * + * @return the leftmost child (smallest index) + */ + private AVLNode min() { + return (getLeftSubTree() == null) ? this : left.min(); + } + + /** + * Removes the node at a given position. + * + * @param index is the index of the element to be removed relative to the position + * of the parent node of the current node. + */ + AVLNode remove(int index) { + int indexRelativeToMe = index - relativePosition; + + if (indexRelativeToMe == 0) { + return removeSelf(); + } + if (indexRelativeToMe > 0) { + setRight(right.remove(indexRelativeToMe), right.right); + if (relativePosition < 0) { + relativePosition++; + } + } else { + setLeft(left.remove(indexRelativeToMe), left.left); + if (relativePosition > 0) { + relativePosition--; + } + } + recalcHeight(); + return balance(); + } + + private AVLNode removeMax() { + if (getRightSubTree() == null) { + return removeSelf(); + } + setRight(right.removeMax(), right.right); + if (relativePosition < 0) { + relativePosition++; + } + recalcHeight(); + return balance(); + } + + private AVLNode removeMin() { + if (getLeftSubTree() == null) { + return removeSelf(); + } + setLeft(left.removeMin(), left.left); + if (relativePosition > 0) { + relativePosition--; + } + recalcHeight(); + return balance(); + } + + private AVLNode removeSelf() { + if (getRightSubTree() == null && getLeftSubTree() == null) + return null; + if (getRightSubTree() == null) { + if (relativePosition > 0) { + left.relativePosition += relativePosition + (relativePosition > 0 ? 0 : 1); + } + left.max().setRight(null, right); + return left; + } + if (getLeftSubTree() == null) { + right.relativePosition += relativePosition - (relativePosition < 0 ? 0 : 1); + right.min().setLeft(null, left); + return right; + } + + if (heightRightMinusLeft() > 0) { + AVLNode rightMin = right.min(); + value = rightMin.value; + if (leftIsPrevious) { + left = rightMin.left; + } + right = right.removeMin(); + if (relativePosition < 0) { + relativePosition++; + } + } else { + AVLNode leftMax = left.max(); + value = leftMax.value; + if (rightIsNext) { + right = leftMax.right; + } + left = left.removeMax(); + if (relativePosition > 0) { + relativePosition--; + } + } + recalcHeight(); + return this; + } + + // ----------- + + /** + * Balances according to the AVL algorithm. + */ + private AVLNode balance() { + switch (heightRightMinusLeft()) { + case 1: + case 0: + case -1: + return this; + case -2: + if (left.heightRightMinusLeft() > 0) { + setLeft(left.rotateLeft(), null); + } + return rotateRight(); + case 2: + if (right.heightRightMinusLeft() < 0) { + setRight(right.rotateRight(), null); + } + return rotateLeft(); + default: + throw new RuntimeException("tree inconsistent!"); + } + } + + /** + * Gets the relative position. + */ + private int getOffset(AVLNode node) { + if (node == null) { + return 0; + } + return node.relativePosition; + } + + /** + * Sets the relative position. + */ + private int setOffset(AVLNode node, int newOffest) { + if (node == null) { + return 0; + } + int oldOffset = getOffset(node); + node.relativePosition = newOffest; + return oldOffset; + } + + /** + * Sets the height by calculation. + */ + private void recalcHeight() { + height = Math.max(getLeftSubTree() == null ? -1 : getLeftSubTree().height, + getRightSubTree() == null ? -1 : getRightSubTree().height) + 1; + } + + /** + * Returns the height of the node or -1 if the node is null. + */ + private int getHeight(AVLNode node) { + return (node == null ? -1 : node.height); + } + + /** + * Returns the height difference right - left + */ + private int heightRightMinusLeft() { + return getHeight(getRightSubTree()) - getHeight(getLeftSubTree()); + } + + private AVLNode rotateLeft() { + AVLNode newTop = right; // can't be faedelung! + AVLNode movedNode = getRightSubTree().getLeftSubTree(); + + int newTopPosition = relativePosition + getOffset(newTop); + int myNewPosition = -newTop.relativePosition; + int movedPosition = getOffset(newTop) + getOffset(movedNode); + + setRight(movedNode, newTop); + newTop.setLeft(this, null); + + setOffset(newTop, newTopPosition); + setOffset(this, myNewPosition); + setOffset(movedNode, movedPosition); + return newTop; + } + + private AVLNode rotateRight() { + AVLNode newTop = left; // can't be faedelung + AVLNode movedNode = getLeftSubTree().getRightSubTree(); + + int newTopPosition = relativePosition + getOffset(newTop); + int myNewPosition = -newTop.relativePosition; + int movedPosition = getOffset(newTop) + getOffset(movedNode); + + setLeft(movedNode, newTop); + newTop.setRight(this, null); + + setOffset(newTop, newTopPosition); + setOffset(this, myNewPosition); + setOffset(movedNode, movedPosition); + return newTop; + } + + private void setLeft(AVLNode node, AVLNode previous) { + leftIsPrevious = (node == null); + left = (leftIsPrevious ? previous : node); + recalcHeight(); + } + + private void setRight(AVLNode node, AVLNode next) { + rightIsNext = (node == null); + right = (rightIsNext ? next : node); + recalcHeight(); + } + + // private void checkFaedelung() { + // AVLNode maxNode = left.max(); + // if (!maxNode.rightIsFaedelung || maxNode.right != this) { + // throw new RuntimeException(maxNode + " should right-faedel to " + this); + // } + // AVLNode minNode = right.min(); + // if (!minNode.leftIsFaedelung || minNode.left != this) { + // throw new RuntimeException(maxNode + " should left-faedel to " + this); + // } + // } + // + // private int checkTreeDepth() { + // int hright = (getRightSubTree() == null ? -1 : + // getRightSubTree().checkTreeDepth()); + // // Logger.logInfo("checkTreeDepth"); + // // Logger.logInfo(this); + // // Logger.logInfo(" left: "); + // // Logger.logInfo(_left); + // // Logger.logInfo(" right: "); + // // Logger.logInfo(_right); + // + // int hleft = (left == null ? -1 : left.checkTreeDepth()); + // if (height != Math.max(hright, hleft) + 1) { + // throw new RuntimeException( + // "height should be max" + hleft + "," + hright + " but is " + height); + // } + // return height; + // } + // + // private int checkLeftSubNode() { + // if (getLeftSubTree() == null) { + // return 0; + // } + // int count = 1 + left.checkRightSubNode(); + // if (left.relativePosition != -count) { + // throw new RuntimeException(); + // } + // return count + left.checkLeftSubNode(); + // } + // + // private int checkRightSubNode() { + // AVLNode right = getRightSubTree(); + // if (right == null) { + // return 0; + // } + // int count = 1; + // count += right.checkLeftSubNode(); + // if (right.relativePosition != count) { + // throw new RuntimeException(); + // } + // return count + right.checkRightSubNode(); + // } + + /** + * Used for debugging. + */ + @Override + public String toString() { + return "AVLNode(" + relativePosition + "," + (left != null) + "," + value + "," + + (getRightSubTree() != null) + ", faedelung " + rightIsNext + " )"; + } + } + + /** + * A list iterator over the linked list. + */ + static class TreeListIterator implements ListIterator { + /** + * The parent list + */ + protected final XLSOptimizedTreeList parent; + /** + * The node that will be returned by {@link #next()}. If this is equal to + * {@link AbstractLinkedList#header} then there are no more values to return. + */ + protected AVLNode next; + /** + * The index of {@link #next}. + */ + protected int nextIndex; + /** + * The last node that was returned by {@link #next()} or {@link #previous()}. + * Set to null if {@link #next()} or {@link #previous()} haven't + * been called, or if the node has been removed with {@link #remove()} or a new + * node added with {@link #add(Object)}. Should be accessed through + * {@link #getLastNodeReturned()} to enforce this behaviour. + */ + protected AVLNode current; + /** + * The index of {@link #current}. + */ + protected int currentIndex; + /** + * The modification count that the list is expected to have. If the list doesn't + * have this count, then a {@link java.util.ConcurrentModificationException} may + * be thrown by the operations. + */ + protected int expectedModCount; + + /** + * Create a ListIterator for a list. + * + * @param parent the parent list + * @param fromIndex the index to start at + */ + protected TreeListIterator(XLSOptimizedTreeList parent, int fromIndex) throws IndexOutOfBoundsException { + super(); + this.parent = parent; + this.expectedModCount = parent.modCount; + this.next = (parent.root == null ? null : parent.root.get(fromIndex)); + this.nextIndex = fromIndex; + } + + /** + * Checks the modification count of the list is the value that this object + * expects. + * + * @throws ConcurrentModificationException If the list's modification count isn't the value that was + * expected. + */ + protected void checkModCount() { + if (parent.modCount != expectedModCount) { + // How about Allow it??? + // throw new ConcurrentModificationException(); + } + } + + public boolean hasNext() { + return (nextIndex < parent.size()); + } + + public Object next() { + checkModCount(); + if (!hasNext()) { + throw new NoSuchElementException("No element at index " + nextIndex + "."); + } + if (next == null) { + next = parent.root.get(nextIndex); + } + Object value = next.getValue(); + current = next; + currentIndex = nextIndex++; + next = next.next(); + return value; + } + + public boolean hasPrevious() { + return (nextIndex > 0); + } + + public Object previous() { + checkModCount(); + if (!hasPrevious()) { + throw new NoSuchElementException("Already at start of list."); + } + if (next == null) { + next = parent.root.get(nextIndex - 1); + } else { + next = next.previous(); + } + Object value = next.getValue(); + current = next; + currentIndex = --nextIndex; + return value; + } + + public int nextIndex() { + return nextIndex; + } + + public int previousIndex() { + return nextIndex() - 1; + } + + public void remove() { + checkModCount(); + if (current == null) { + throw new IllegalStateException(); + } + parent.remove(currentIndex); + current = null; + currentIndex = -1; + nextIndex--; + expectedModCount++; + } + + public void set(Object obj) { + checkModCount(); + if (current == null) { + throw new IllegalStateException(); + } + current.setValue(obj); + } + + public void add(Object obj) { + checkModCount(); + parent.add(nextIndex, obj); + current = null; + currentIndex = -1; + nextIndex++; + expectedModCount++; + } + } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/XSLConverterTool.java b/src/main/java/io/starter/toolkit/XSLConverterTool.java index da02d10..73a7ac2 100644 --- a/src/main/java/io/starter/toolkit/XSLConverterTool.java +++ b/src/main/java/io/starter/toolkit/XSLConverterTool.java @@ -2,19 +2,19 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with OpenXLS. If not, see * . @@ -22,309 +22,306 @@ */ /* * XSLConverterTool is a collection of methods that are of use in the xml/xsl conversion for sheetster. - * + * * These methods are called from XSL to populate various fields correctly - * + * * Created on Mar 17, 2006 * -*/ + */ package io.starter.toolkit; +import io.starter.OpenXLS.ExcelTools; + import java.text.NumberFormat; import java.util.Hashtable; -import io.starter.OpenXLS.ExcelTools; - public class XSLConverterTool { - private Hashtable styles = new Hashtable(); - private String lastCell = "A1"; - - /** - * Gets a date format pattern based off of an ifmt. This applies only to built - * in dates. - * - * @param ifmt - * from XF record - * @return date format pattern - */ - public String getDateFormatPattern(String ifmt) { - if (ifmt.equals("14")) { - return "m/d/yy"; - } else if (ifmt.equals("15")) { - return "d-mmm-yy"; - } else if (ifmt.equals("16")) { - return "d-mmm"; - } else if (ifmt.equals("17")) { - return "mmm-yy"; - } else if (ifmt.equals("22")) { - return "m/d/yy h:mm"; - } - return "m/d/yy"; - - } - - /** - * Gets a calendar format pattern based off of an ifmt. This applies only to - * built in dates. - * - * @param ifmt - * from XF record - * @return calendar format pattern - */ - public String getJsCalendarFormatPattern(String ifmt) { - if (ifmt.equals("14")) { - return "%m/%d/%Y"; - } else if (ifmt.equals("15")) { - return "%d-%b-%y"; - } else if (ifmt.equals("16")) { - return "%d-%b"; - } else if (ifmt.equals("17")) { - return "%m-%y"; - } else if (ifmt.equals("22")) { - return "%m/%d/%Y %h:%M"; - } else - return "%m/%d/%Y"; - } - - /** - * Returns a formatted currency string based off the local format and the string - * format passed in. TODO: implement formatting patterns - * - * @param fmt - * @return - */ - public String getCurrencyFormat(String fmt, String value) { - try { - NumberFormat nf = NumberFormat.getCurrencyInstance(); - Double d = new Double(value); - String retStr = nf.format(d); - return retStr; - } catch (NumberFormatException e) { - return value; - } - } - - /** - * Get a style based of a style ID. If the style does not yet exist, create a - * new one, and add it to the hashtable of styles - * - * @param styleId - * @return Style - */ - private Style getStyle(String styleId) { - Object o = styles.get(styleId); - if (o != null) { - return (Style) o; - } - Style thisStyle = new Style(styleId); - styles.put(styleId, thisStyle); - return thisStyle; - } - - /** - * returns a String populated with cell data for missing cells since the last - * cell read. Requires the first and last cells to exist for a row. - * - * @return html fragment - */ - public String getPreviousCellData(String sheet, String currCell, String colspan) { - StringBuffer returnString = new StringBuffer(); - int colSpan = 1; - if (colspan != null && colspan != "") { - colSpan = Integer.parseInt(colspan); - } - int[] newCell = ExcelTools.getRowColFromString(currCell); - int[] oldCell = ExcelTools.getRowColFromString(lastCell); - int newCol = newCell[1]; - int oldCol = oldCell[1] + 1; - for (; oldCol < newCol; oldCol++) { - String newAddress = ExcelTools.getAlphaVal(oldCol) + (oldCell[0] + 1); - returnString.append(getEmptyCellHTML(sheet, newAddress)); - } - newCell[1] = newCol + colSpan - 1; - currCell = ExcelTools.formatLocation(newCell); - lastCell = currCell; - return returnString.toString(); - } - - private String getEmptyCellHTML(String sheet, String address) { - return " "; - } - - /******************************************************************************************************* - *********************** DELEGATING METHODS *********************************************** - *******************************************************************************************************/ - public void setStyleColor(String styleId, String color) { - Style thisStyle = getStyle(styleId); - thisStyle.setColor(color); - } - - public String getStyleColor(String styleId) { - Style thisStyle = getStyle(styleId); - return thisStyle.getColor(); - } - - public void setIsDate(String styleId, String isDate) { - Style thisStyle = getStyle(styleId); - thisStyle.setIsDate(isDate); - } - - public String getIsDate(String styleId) { - Style thisStyle = getStyle(styleId); - String s = thisStyle.getIsDate(); - if (s == null) - return "0"; - return s; - } - - public void setIsCurrency(String styleId, String isCurrency) { - if (isCurrency.equals("")) - isCurrency = "0"; - Style thisStyle = getStyle(styleId); - thisStyle.setIsCurrency(isCurrency); - } - - public String getIsCurrency(String styleId) { - Style thisStyle = getStyle(styleId); - String s = thisStyle.getIsCurrency(); - if (s == null) - return "0"; - return s; - } - - public void setFormatId(String styleId, String formatId) { - Style thisStyle = getStyle(styleId); - thisStyle.setFormatId(formatId); - } - - public String getFormatId(String styleId) { - Style thisStyle = getStyle(styleId); - String s = thisStyle.getFormatId(); - if (s == null) - return "0"; - return s; - } - - public String getFormatPattern(String styleId) { - Style thisStyle = getStyle(styleId); - return thisStyle.getFormatPattern(); - } - - public void setFormatPattern(String styleId, String pattern) { - Style thisStyle = getStyle(styleId); - thisStyle.setFormatPattern(pattern); - } - - /** - * Style holds style information about a certain style in the xsl spreadsheet - * - */ - private class Style { - private String ID; - private String formatId; - private String color = ""; - private String test; - private String fontFamily; - private String fontSize; - private String fontWeight; - private String fontColor; - private String textAlign; - private String isDate; - private String isCurrency; - private String formatPattern; - - public String getFormatPattern() { - return formatPattern; - } - - public void setFormatPattern(String pattern) { - this.formatPattern = pattern; - } - - protected Style(String id) { - ID = id; - } - - public String getColor() { - return color; - } - - public void setColor(String color) { - this.color = color; - } - - public String getFontColor() { - return fontColor; - } - - public void setFontColor(String fontColor) { - this.fontColor = fontColor; - } - - public String getFontFamily() { - return fontFamily; - } - - public void setFontFamily(String fontFamily) { - this.fontFamily = fontFamily; - } - - public String getFontSize() { - return fontSize; - } - - public void setFontSize(String fontSize) { - this.fontSize = fontSize; - } - - public String getFontWeight() { - return fontWeight; - } - - public void setFontWeight(String fontWeight) { - this.fontWeight = fontWeight; - } - - public String getID() { - return ID; - } - - public void setID(String id) { - ID = id; - } - - public String getFormatId() { - return formatId; - } - - public void setFormatId(String formatId) { - this.formatId = formatId; - } - - public String getTextAlign() { - return textAlign; - } - - public void setTextAlign(String textAlign) { - this.textAlign = textAlign; - } - - public String getIsCurrency() { - return isCurrency; - } - - public void setIsCurrency(String isCurrency) { - this.isCurrency = isCurrency; - } - - public String getIsDate() { - return isDate; - } - - public void setIsDate(String isDate) { - this.isDate = isDate; - } - - } + private Hashtable styles = new Hashtable(); + private String lastCell = "A1"; + + /** + * Gets a date format pattern based off of an ifmt. This applies only to built + * in dates. + * + * @param ifmt from XF record + * @return date format pattern + */ + public String getDateFormatPattern(String ifmt) { + if (ifmt.equals("14")) { + return "m/d/yy"; + } else if (ifmt.equals("15")) { + return "d-mmm-yy"; + } else if (ifmt.equals("16")) { + return "d-mmm"; + } else if (ifmt.equals("17")) { + return "mmm-yy"; + } else if (ifmt.equals("22")) { + return "m/d/yy h:mm"; + } + return "m/d/yy"; + + } + + /** + * Gets a calendar format pattern based off of an ifmt. This applies only to + * built in dates. + * + * @param ifmt from XF record + * @return calendar format pattern + */ + public String getJsCalendarFormatPattern(String ifmt) { + if (ifmt.equals("14")) { + return "%m/%d/%Y"; + } else if (ifmt.equals("15")) { + return "%d-%b-%y"; + } else if (ifmt.equals("16")) { + return "%d-%b"; + } else if (ifmt.equals("17")) { + return "%m-%y"; + } else if (ifmt.equals("22")) { + return "%m/%d/%Y %h:%M"; + } else + return "%m/%d/%Y"; + } + + /** + * Returns a formatted currency string based off the local format and the string + * format passed in. TODO: implement formatting patterns + * + * @param fmt + * @return + */ + public String getCurrencyFormat(String fmt, String value) { + try { + NumberFormat nf = NumberFormat.getCurrencyInstance(); + Double d = new Double(value); + String retStr = nf.format(d); + return retStr; + } catch (NumberFormatException e) { + return value; + } + } + + /** + * Get a style based of a style ID. If the style does not yet exist, create a + * new one, and add it to the hashtable of styles + * + * @param styleId + * @return Style + */ + private Style getStyle(String styleId) { + Object o = styles.get(styleId); + if (o != null) { + return (Style) o; + } + Style thisStyle = new Style(styleId); + styles.put(styleId, thisStyle); + return thisStyle; + } + + /** + * returns a String populated with cell data for missing cells since the last + * cell read. Requires the first and last cells to exist for a row. + * + * @return html fragment + */ + public String getPreviousCellData(String sheet, String currCell, String colspan) { + StringBuffer returnString = new StringBuffer(); + int colSpan = 1; + if (colspan != null && colspan != "") { + colSpan = Integer.parseInt(colspan); + } + int[] newCell = ExcelTools.getRowColFromString(currCell); + int[] oldCell = ExcelTools.getRowColFromString(lastCell); + int newCol = newCell[1]; + int oldCol = oldCell[1] + 1; + for (; oldCol < newCol; oldCol++) { + String newAddress = ExcelTools.getAlphaVal(oldCol) + (oldCell[0] + 1); + returnString.append(getEmptyCellHTML(sheet, newAddress)); + } + newCell[1] = newCol + colSpan - 1; + currCell = ExcelTools.formatLocation(newCell); + lastCell = currCell; + return returnString.toString(); + } + + private String getEmptyCellHTML(String sheet, String address) { + return " "; + } + + /******************************************************************************************************* + *********************** DELEGATING METHODS *********************************************** + *******************************************************************************************************/ + public void setStyleColor(String styleId, String color) { + Style thisStyle = getStyle(styleId); + thisStyle.setColor(color); + } + + public String getStyleColor(String styleId) { + Style thisStyle = getStyle(styleId); + return thisStyle.getColor(); + } + + public void setIsDate(String styleId, String isDate) { + Style thisStyle = getStyle(styleId); + thisStyle.setIsDate(isDate); + } + + public String getIsDate(String styleId) { + Style thisStyle = getStyle(styleId); + String s = thisStyle.getIsDate(); + if (s == null) + return "0"; + return s; + } + + public void setIsCurrency(String styleId, String isCurrency) { + if (isCurrency.equals("")) + isCurrency = "0"; + Style thisStyle = getStyle(styleId); + thisStyle.setIsCurrency(isCurrency); + } + + public String getIsCurrency(String styleId) { + Style thisStyle = getStyle(styleId); + String s = thisStyle.getIsCurrency(); + if (s == null) + return "0"; + return s; + } + + public void setFormatId(String styleId, String formatId) { + Style thisStyle = getStyle(styleId); + thisStyle.setFormatId(formatId); + } + + public String getFormatId(String styleId) { + Style thisStyle = getStyle(styleId); + String s = thisStyle.getFormatId(); + if (s == null) + return "0"; + return s; + } + + public String getFormatPattern(String styleId) { + Style thisStyle = getStyle(styleId); + return thisStyle.getFormatPattern(); + } + + public void setFormatPattern(String styleId, String pattern) { + Style thisStyle = getStyle(styleId); + thisStyle.setFormatPattern(pattern); + } + + /** + * Style holds style information about a certain style in the xsl spreadsheet + */ + private class Style { + private String ID; + private String formatId; + private String color = ""; + private String test; + private String fontFamily; + private String fontSize; + private String fontWeight; + private String fontColor; + private String textAlign; + private String isDate; + private String isCurrency; + private String formatPattern; + + public String getFormatPattern() { + return formatPattern; + } + + public void setFormatPattern(String pattern) { + this.formatPattern = pattern; + } + + protected Style(String id) { + ID = id; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public String getFontColor() { + return fontColor; + } + + public void setFontColor(String fontColor) { + this.fontColor = fontColor; + } + + public String getFontFamily() { + return fontFamily; + } + + public void setFontFamily(String fontFamily) { + this.fontFamily = fontFamily; + } + + public String getFontSize() { + return fontSize; + } + + public void setFontSize(String fontSize) { + this.fontSize = fontSize; + } + + public String getFontWeight() { + return fontWeight; + } + + public void setFontWeight(String fontWeight) { + this.fontWeight = fontWeight; + } + + public String getID() { + return ID; + } + + public void setID(String id) { + ID = id; + } + + public String getFormatId() { + return formatId; + } + + public void setFormatId(String formatId) { + this.formatId = formatId; + } + + public String getTextAlign() { + return textAlign; + } + + public void setTextAlign(String textAlign) { + this.textAlign = textAlign; + } + + public String getIsCurrency() { + return isCurrency; + } + + public void setIsCurrency(String isCurrency) { + this.isCurrency = isCurrency; + } + + public String getIsDate() { + return isDate; + } + + public void setIsDate(String isDate) { + this.isDate = isDate; + } + + } } diff --git a/src/main/resources/io/starter/OpenXLS/build.properties b/src/main/resources/io/starter/OpenXLS/build.properties index 670ba41..9aae642 100644 --- a/src/main/resources/io/starter/OpenXLS/build.properties +++ b/src/main/resources/io/starter/OpenXLS/build.properties @@ -1,9 +1,6 @@ - # the version as declared in the POM version=${project.version} - # the time of the build, in maven-deploy-plugin format timestamp=${timestamp} - # the hash of the git commit the build was based on commit=${scmVersion} \ No newline at end of file diff --git a/src/test/java/io/starter/OpenXLS/FormulaTest.java b/src/test/java/io/starter/OpenXLS/FormulaTest.java index 3295c1d..324f0c5 100644 --- a/src/test/java/io/starter/OpenXLS/FormulaTest.java +++ b/src/test/java/io/starter/OpenXLS/FormulaTest.java @@ -4,9 +4,9 @@ * --------- BEGIN COPYRIGHT NOTICE --------- * Copyright 2002-2012 Extentech Inc. * Copyright 2013 Infoteria America Corp. - * + * * This file is part of OpenXLS. - * + * * OpenXLS is free software: you can redistribute it and/or * modify * it under the terms of the GNU Lesser General Public @@ -14,7 +14,7 @@ * published by the Free Software Foundation, either version * 3 of * the License, or (at your option) any later version. - * + * * OpenXLS is distributed in the hope that it will be * useful, * but WITHOUT ANY WARRANTY; without even the implied @@ -22,7 +22,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General * Public * License along with OpenXLS. If not, see @@ -30,17 +30,16 @@ * ---------- END COPYRIGHT NOTICE ---------- */ -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; - -import org.junit.Test; - import io.starter.formats.XLS.CellNotFoundException; import io.starter.formats.XLS.FormulaNotFoundException; import io.starter.formats.XLS.FunctionNotSupportedException; import io.starter.formats.XLS.WorkSheetNotFoundException; import io.starter.toolkit.Logger; +import org.junit.Test; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; /** * This Class Demonstrates the functionality of of OpenXLS Formula manipulation. @@ -48,29 +47,29 @@ public class FormulaTest { - @Test - public void testFormulaParse() { - testformula t = new testformula(); - t.testFormula(); - } - - @Test - public void testHandlerFunctions() { - testformula t = new testformula(); - t.testHandlerFunctions(); - } - - @Test - public void testMultiChange() { - testformula t = new testformula(); - t.testMultiChange(); - } - - @Test - public void changeSingleCellLoc() { - testformula t = new testformula(); - t.changeSingleCellLoc(); - } + @Test + public void testFormulaParse() { + testformula t = new testformula(); + t.testFormula(); + } + + @Test + public void testHandlerFunctions() { + testformula t = new testformula(); + t.testHandlerFunctions(); + } + + @Test + public void testMultiChange() { + testformula t = new testformula(); + t.testMultiChange(); + } + + @Test + public void changeSingleCellLoc() { + testformula t = new testformula(); + t.changeSingleCellLoc(); + } } @@ -78,302 +77,295 @@ public void changeSingleCellLoc() { * Test the manipulation of Formulas within a worksheet. */ class testformula { - WorkBookHandle book = null; - WorkSheetHandle sheet = null; - String sheetname = "Sheet1"; - String wd = System.getProperty("user.dir") - + "/docs/samples/Formulas/"; - String finpath = wd + "testFormula.xls"; - - /** - * thrash multiple changes to formula references and recalc - * ------------------------------------------------------------ - * - */ - public void testMultiChange() { - try { - Logger.logInfo("Testing multiple changes to formula references and recalc"); - WorkBookHandle wbx = new WorkBookHandle(); - WorkSheetHandle sheet1 = wbx.getWorkSheet(0); - sheet1.add(new Double(100.123), "A1"); - sheet1.add(new Double(200.123), "A2"); - CellHandle cx = sheet1.add("=sum(A1*A2)", "A3"); - Logger.logInfo(String.valueOf(cx)); - Logger.logInfo("start setting 100k vals"); - for (int t = 0; t < 100000; t++) { - sheet1.getCell("A1").setVal(Math.random() * 10000); - sheet1.getCell("A2").setVal(Math.random() * 10000); - Object calced = cx.getVal(); - Logger.logInfo(calced.toString()); - } - Logger.logInfo("done setting 100k vals"); - wbx.write(new FileOutputStream(new File( - wd + "testFormulas_out.xls")), WorkBookHandle.FORMAT_XLS); - - } catch (Exception ex) { - Logger.logErr("testFormulas.testMultiChange: " + ex.toString()); - } - } - - /** - * Demonstrates Dynamic Formula Calculation - * - * - */ - public void testCalculation() { - try { - this.openSheet(finpath, sheetname); - // c4 + d4 = f4 - CellHandle mycell1 = sheet.getCell("C4"); - CellHandle mycell2 = sheet.getCell("D4"); - CellHandle myformulacell = sheet.getCell("F4"); - - // output the calculated values - FormulaHandle form = myformulacell.getFormulaHandle(); - io.starter.toolkit.Logger.log(form.calculate().toString()); - - // change the values then recalc - mycell1.setVal(99); - mycell2.setVal(420); - io.starter.toolkit.Logger.log(form.calculate().toString()); - - testWrite("testCalculation_out.xls"); - } catch (CellNotFoundException e) { - io.starter.toolkit.Logger.log("cell not found" + e); - } catch (FormulaNotFoundException e) { - io.starter.toolkit.Logger.log("No formula to change" + e); - } catch (Exception e) { - Logger.logErr("TestFormulas failed.", e); - } - } - - /** - * Move a Cell Reference within a Formula - * - */ - public void changeSingleCellLoc() { - try { - this.openSheet(finpath, sheetname); - CellHandle mycell = sheet.getCell("A10"); - FormulaHandle form = mycell.getFormulaHandle(); - form.changeFormulaLocation("A3", "G10"); - testWrite("testChangeSingleCellLoc_out.xls"); - } catch (CellNotFoundException e) { - io.starter.toolkit.Logger.log("cell not found" + e); - } catch (FormulaNotFoundException e) { - io.starter.toolkit.Logger.log("No formula to change" + e); - } - } - - /** - * Move a Cell range reference within a Formula - * - */ - public void testHandlerFunctions() { - try { - this.openSheet(finpath, sheetname); - CellHandle mycell = sheet.getCell("E8"); - FormulaHandle myhandle = mycell.getFormulaHandle(); - boolean b = myhandle.changeFormulaLocation("A1:B2", "D1:D28"); - testWrite("testHandlerFunctions_out.xls"); - } catch (CellNotFoundException e) { - io.starter.toolkit.Logger.log("cell not found" + e); - } catch (FormulaNotFoundException e) { - io.starter.toolkit.Logger.log("No formula to change" + e); - } - } - - /** - * Add a cell to a Cell range reference within a Formula - * - */ - public void testCellHandlerFunctions() { - try { - this.openSheet(finpath, sheetname); - CellHandle mycell = sheet.getCell("E8"); - CellHandle secondcell = sheet.getCell("D19"); - FormulaHandle myhandle = mycell.getFormulaHandle(); - boolean b = myhandle.addCellToRange("A1:B2", secondcell); - testWrite("testCellHandlerFunctions_out.xls"); - } catch (CellNotFoundException e) { - io.starter.toolkit.Logger.log("cell not found" + e); - } catch (FormulaNotFoundException e) { - io.starter.toolkit.Logger.log("No formula to change" + e); - } - } - - /** - * Run tests - * - */ - public void testFormula() { - try { - String finpath = wd + "testFormula.xls"; - String sheetname = "Sheet1"; - this.openSheet(finpath, sheetname); - sheet.removeRow(2, true); - testWrite("testFormula_out.xls"); - } catch (Exception e) { - io.starter.toolkit.Logger - .log("Exception in testFORMULA.testFormulaSeries(): " + e); - } - } - - WorkSheetHandle sht = null; - - /** - * Demonstrates calculation of formulas - * - * Jan 19, 2010 - * - * @param fs - * @param sh - */ - void testFormulaCalc(String fs, String sh) { - WorkBookHandle book = new WorkBookHandle(fs); - sheetname = sh; - try { - sht = book.getWorkSheet(sheetname); - } catch (Exception e) { - Logger.logErr("TestFormulas failed.", e); - } - - FormulaHandle f = null; - Double i = null; - - /************************************ - * Formula Parse test - **************************************/ - if (sheetname.equalsIgnoreCase("Sheet1")) { - try { - - // one ref & ptgadd - sht.add(null, "A1"); - CellHandle c = sht.getCell("A1"); - c.setFormula("b1+5"); - f = c.getFormulaHandle(); - i = (Double) f.calculate(); - - // two refs & ptgadd - sht.add(null, "A2"); - c = sht.getCell("A2"); - c.setFormula("B1+ A1"); - f = c.getFormulaHandle(); - i = (Double) f.calculate(); - - // ptgsub - f.setFormula("B1 - 5"); - i = (Double) f.calculate(); - - // ptgmul - f.setFormula("D1 * F1"); - i = (Double) f.calculate(); - - // ptgdiv - f.setFormula("E1 / F1"); - i = (Double) f.calculate(); - - // ptgpower - f.setFormula("E1 ^ F1"); - i = (Double) f.calculate(); - - f.setFormula("E1 > F1"); - Boolean b = (Boolean) f.calculate(); - - f.setFormula("E1 >= F1"); - b = (Boolean) f.calculate(); - - f.setFormula("E1 < F1"); - b = (Boolean) f.calculate(); - - f.setFormula("E1 <= F1"); - b = (Boolean) f.calculate(); - - f.setFormula("Pi()"); - i = (Double) f.calculate(); - - f.setFormula("LOG(10,2)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("ROUND(32.443,1)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("MOD(45,6)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("DATE(1998,2,4)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("SUM(1998,2,4)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("IF(TRUE,1,0)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("ISERR(\"test\")"); - b = (Boolean) f.calculate(); - io.starter.toolkit.Logger.log(b.toString()); - - // many operand ptgfuncvar - f.setFormula("SUM(12,3,2,4,5,1)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - // test with a sub-calc - f.setFormula("IF((1<2),1,0)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("IF((1<2),MOD(45,6),1)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("IF((1<2),if((true),8,1),1)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("IF((SUM(23,2,3,4)<12),if((true),8,1),DATE(1998,2,4))"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - } catch (CellNotFoundException e) { - Logger.logErr("TestFormulas failed.", e); - } catch (FunctionNotSupportedException e) { - Logger.logErr("TestFormulas failed.", e); - } catch (Exception e) { - Logger.logErr("TestFormulas failed.", e); - } - testWrite("testCalcFormulas_out.xls"); - } - } - - public void openSheet(String finp, String sheetnm) { - book = new WorkBookHandle(finp); - try { - sheet = book.getWorkSheet(sheetnm); - } catch (WorkSheetNotFoundException e) { - io.starter.toolkit.Logger.log("couldn't find worksheet" + e); - } - - } - - public void testWrite(String fname) { - try { - java.io.File f = new java.io.File(wd + fname); - FileOutputStream fos = new FileOutputStream(f); - BufferedOutputStream bbout = new BufferedOutputStream(fos); - book.write(bbout); - bbout.flush(); - fos.close(); - } catch (java.io.IOException e) { - Logger.logInfo("IOException in Tester. " + e); - } - } + WorkBookHandle book = null; + WorkSheetHandle sheet = null; + String sheetname = "Sheet1"; + String wd = System.getProperty("user.dir") + + "/docs/samples/Formulas/"; + String finpath = wd + "testFormula.xls"; + + /** + * thrash multiple changes to formula references and recalc + * ------------------------------------------------------------ + */ + public void testMultiChange() { + try { + Logger.logInfo("Testing multiple changes to formula references and recalc"); + WorkBookHandle wbx = new WorkBookHandle(); + WorkSheetHandle sheet1 = wbx.getWorkSheet(0); + sheet1.add(new Double(100.123), "A1"); + sheet1.add(new Double(200.123), "A2"); + CellHandle cx = sheet1.add("=sum(A1*A2)", "A3"); + Logger.logInfo(String.valueOf(cx)); + Logger.logInfo("start setting 100k vals"); + for (int t = 0; t < 100000; t++) { + sheet1.getCell("A1").setVal(Math.random() * 10000); + sheet1.getCell("A2").setVal(Math.random() * 10000); + Object calced = cx.getVal(); + Logger.logInfo(calced.toString()); + } + Logger.logInfo("done setting 100k vals"); + wbx.write(new FileOutputStream(new File( + wd + "testFormulas_out.xls")), WorkBookHandle.FORMAT_XLS); + + } catch (Exception ex) { + Logger.logErr("testFormulas.testMultiChange: " + ex.toString()); + } + } + + /** + * Demonstrates Dynamic Formula Calculation + */ + public void testCalculation() { + try { + this.openSheet(finpath, sheetname); + // c4 + d4 = f4 + CellHandle mycell1 = sheet.getCell("C4"); + CellHandle mycell2 = sheet.getCell("D4"); + CellHandle myformulacell = sheet.getCell("F4"); + + // output the calculated values + FormulaHandle form = myformulacell.getFormulaHandle(); + io.starter.toolkit.Logger.log(form.calculate().toString()); + + // change the values then recalc + mycell1.setVal(99); + mycell2.setVal(420); + io.starter.toolkit.Logger.log(form.calculate().toString()); + + testWrite("testCalculation_out.xls"); + } catch (CellNotFoundException e) { + io.starter.toolkit.Logger.log("cell not found" + e); + } catch (FormulaNotFoundException e) { + io.starter.toolkit.Logger.log("No formula to change" + e); + } catch (Exception e) { + Logger.logErr("TestFormulas failed.", e); + } + } + + /** + * Move a Cell Reference within a Formula + */ + public void changeSingleCellLoc() { + try { + this.openSheet(finpath, sheetname); + CellHandle mycell = sheet.getCell("A10"); + FormulaHandle form = mycell.getFormulaHandle(); + form.changeFormulaLocation("A3", "G10"); + testWrite("testChangeSingleCellLoc_out.xls"); + } catch (CellNotFoundException e) { + io.starter.toolkit.Logger.log("cell not found" + e); + } catch (FormulaNotFoundException e) { + io.starter.toolkit.Logger.log("No formula to change" + e); + } + } + + /** + * Move a Cell range reference within a Formula + */ + public void testHandlerFunctions() { + try { + this.openSheet(finpath, sheetname); + CellHandle mycell = sheet.getCell("E8"); + FormulaHandle myhandle = mycell.getFormulaHandle(); + boolean b = myhandle.changeFormulaLocation("A1:B2", "D1:D28"); + testWrite("testHandlerFunctions_out.xls"); + } catch (CellNotFoundException e) { + io.starter.toolkit.Logger.log("cell not found" + e); + } catch (FormulaNotFoundException e) { + io.starter.toolkit.Logger.log("No formula to change" + e); + } + } + + /** + * Add a cell to a Cell range reference within a Formula + */ + public void testCellHandlerFunctions() { + try { + this.openSheet(finpath, sheetname); + CellHandle mycell = sheet.getCell("E8"); + CellHandle secondcell = sheet.getCell("D19"); + FormulaHandle myhandle = mycell.getFormulaHandle(); + boolean b = myhandle.addCellToRange("A1:B2", secondcell); + testWrite("testCellHandlerFunctions_out.xls"); + } catch (CellNotFoundException e) { + io.starter.toolkit.Logger.log("cell not found" + e); + } catch (FormulaNotFoundException e) { + io.starter.toolkit.Logger.log("No formula to change" + e); + } + } + + /** + * Run tests + */ + public void testFormula() { + try { + String finpath = wd + "testFormula.xls"; + String sheetname = "Sheet1"; + this.openSheet(finpath, sheetname); + sheet.removeRow(2, true); + testWrite("testFormula_out.xls"); + } catch (Exception e) { + io.starter.toolkit.Logger + .log("Exception in testFORMULA.testFormulaSeries(): " + e); + } + } + + WorkSheetHandle sht = null; + + /** + * Demonstrates calculation of formulas + *

                + * Jan 19, 2010 + * + * @param fs + * @param sh + */ + void testFormulaCalc(String fs, String sh) { + WorkBookHandle book = new WorkBookHandle(fs); + sheetname = sh; + try { + sht = book.getWorkSheet(sheetname); + } catch (Exception e) { + Logger.logErr("TestFormulas failed.", e); + } + + FormulaHandle f = null; + Double i = null; + + /************************************ + * Formula Parse test + **************************************/ + if (sheetname.equalsIgnoreCase("Sheet1")) { + try { + + // one ref & ptgadd + sht.add(null, "A1"); + CellHandle c = sht.getCell("A1"); + c.setFormula("b1+5"); + f = c.getFormulaHandle(); + i = (Double) f.calculate(); + + // two refs & ptgadd + sht.add(null, "A2"); + c = sht.getCell("A2"); + c.setFormula("B1+ A1"); + f = c.getFormulaHandle(); + i = (Double) f.calculate(); + + // ptgsub + f.setFormula("B1 - 5"); + i = (Double) f.calculate(); + + // ptgmul + f.setFormula("D1 * F1"); + i = (Double) f.calculate(); + + // ptgdiv + f.setFormula("E1 / F1"); + i = (Double) f.calculate(); + + // ptgpower + f.setFormula("E1 ^ F1"); + i = (Double) f.calculate(); + + f.setFormula("E1 > F1"); + Boolean b = (Boolean) f.calculate(); + + f.setFormula("E1 >= F1"); + b = (Boolean) f.calculate(); + + f.setFormula("E1 < F1"); + b = (Boolean) f.calculate(); + + f.setFormula("E1 <= F1"); + b = (Boolean) f.calculate(); + + f.setFormula("Pi()"); + i = (Double) f.calculate(); + + f.setFormula("LOG(10,2)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + f.setFormula("ROUND(32.443,1)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + f.setFormula("MOD(45,6)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + f.setFormula("DATE(1998,2,4)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + f.setFormula("SUM(1998,2,4)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + f.setFormula("IF(TRUE,1,0)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + f.setFormula("ISERR(\"test\")"); + b = (Boolean) f.calculate(); + io.starter.toolkit.Logger.log(b.toString()); + + // many operand ptgfuncvar + f.setFormula("SUM(12,3,2,4,5,1)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + // test with a sub-calc + f.setFormula("IF((1<2),1,0)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + f.setFormula("IF((1<2),MOD(45,6),1)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + f.setFormula("IF((1<2),if((true),8,1),1)"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + f.setFormula("IF((SUM(23,2,3,4)<12),if((true),8,1),DATE(1998,2,4))"); + i = (Double) f.calculate(); + io.starter.toolkit.Logger.log(i.toString()); + + } catch (CellNotFoundException e) { + Logger.logErr("TestFormulas failed.", e); + } catch (FunctionNotSupportedException e) { + Logger.logErr("TestFormulas failed.", e); + } catch (Exception e) { + Logger.logErr("TestFormulas failed.", e); + } + testWrite("testCalcFormulas_out.xls"); + } + } + + public void openSheet(String finp, String sheetnm) { + book = new WorkBookHandle(finp); + try { + sheet = book.getWorkSheet(sheetnm); + } catch (WorkSheetNotFoundException e) { + io.starter.toolkit.Logger.log("couldn't find worksheet" + e); + } + + } + + public void testWrite(String fname) { + try { + java.io.File f = new java.io.File(wd + fname); + FileOutputStream fos = new FileOutputStream(f); + BufferedOutputStream bbout = new BufferedOutputStream(fos); + book.write(bbout); + bbout.flush(); + fos.close(); + } catch (java.io.IOException e) { + Logger.logInfo("IOException in Tester. " + e); + } + } } \ No newline at end of file From 039b14f3ceea9fa87b2a3b5121cda9e345eb7bf9 Mon Sep 17 00:00:00 2001 From: John McMahon Date: Sun, 3 Nov 2019 11:22:59 -0800 Subject: [PATCH 3/3] feat(core) : kotlin PoC --- .idea/$PRODUCT_WORKSPACE_FILE$ | 19 + .idea/.gitignore | 2 + .idea/OpenXLS.iml | 47 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/modules.xml | 8 + docs/samples/Formulas/testFormulas_out.xls | Bin 13824 -> 13824 bytes openxls/.project | 42 - .../org.eclipse.wst.common.component | 9 - openxls/bin/.project | 42 - .../org.eclipse.wst.common.component | 9 - pom.xml | 53 +- ...oFilterHandle.java => AutoFilterHandle.kt} | 265 +- .../io/starter/OpenXLS/{Cell.java => Cell.kt} | 70 +- .../io/starter/OpenXLS/CellComparator.java | 131 - .../java/io/starter/OpenXLS/CellComparator.kt | 128 + .../java/io/starter/OpenXLS/CellHandle.java | 2901 -------- .../java/io/starter/OpenXLS/CellHandle.kt | 2867 ++++++++ .../java/io/starter/OpenXLS/CellRange.java | 1939 ------ src/main/java/io/starter/OpenXLS/CellRange.kt | 1986 ++++++ .../java/io/starter/OpenXLS/CellRangeRef.java | 335 - .../java/io/starter/OpenXLS/CellRangeRef.kt | 300 + .../java/io/starter/OpenXLS/ChartHandle.java | 3791 ----------- .../java/io/starter/OpenXLS/ChartHandle.kt | 3927 +++++++++++ ...SeriesHandle.java => ChartSeriesHandle.kt} | 442 +- .../java/io/starter/OpenXLS/ColHandle.java | 404 -- src/main/java/io/starter/OpenXLS/ColHandle.kt | 396 ++ .../io/starter/OpenXLS/CommentHandle.java | 281 - .../java/io/starter/OpenXLS/CommentHandle.kt | 256 + .../OpenXLS/ConditionalFormatHandle.java | 270 - .../OpenXLS/ConditionalFormatHandle.kt | 270 + .../OpenXLS/ConditionalFormatRule.java | 634 -- .../starter/OpenXLS/ConditionalFormatRule.kt | 590 ++ .../{DateConverter.java => DateConverter.kt} | 713 +- .../OpenXLS/{Document.java => Document.kt} | 82 +- .../io/starter/OpenXLS/DocumentHandle.java | 442 -- .../java/io/starter/OpenXLS/DocumentHandle.kt | 419 ++ .../DocumentObjectNotFoundException.kt} | 12 +- .../java/io/starter/OpenXLS/ExcelTools.java | 1166 ---- .../java/io/starter/OpenXLS/ExcelTools.kt | 1210 ++++ .../{FormatCache.java => FormatCache.kt} | 57 +- .../java/io/starter/OpenXLS/FormatHandle.java | 2836 -------- .../java/io/starter/OpenXLS/FormatHandle.kt | 3011 +++++++++ .../io/starter/OpenXLS/FormulaHandle.java | 548 -- .../java/io/starter/OpenXLS/FormulaHandle.kt | 569 ++ .../OpenXLS/{GetInfo.java => GetInfo.kt} | 54 +- .../OpenXLS/{Handle.java => Handle.kt} | 5 +- .../java/io/starter/OpenXLS/ImageHandle.java | 916 --- .../java/io/starter/OpenXLS/ImageHandle.kt | 890 +++ .../io/starter/OpenXLS/JSONConstants.java | 61 - .../java/io/starter/OpenXLS/JSONConstants.kt | 63 + .../java/io/starter/OpenXLS/NameHandle.java | 747 --- .../java/io/starter/OpenXLS/NameHandle.kt | 794 +++ .../io/starter/OpenXLS/PivotTableHandle.java | 574 -- .../io/starter/OpenXLS/PivotTableHandle.kt | 536 ++ .../OpenXLS/PrinterSettingsHandle.java | 728 -- .../starter/OpenXLS/PrinterSettingsHandle.kt | 703 ++ .../java/io/starter/OpenXLS/RowHandle.java | 556 -- src/main/java/io/starter/OpenXLS/RowHandle.kt | 539 ++ .../io/starter/OpenXLS/ValidationHandle.java | 636 -- .../io/starter/OpenXLS/ValidationHandle.kt | 622 ++ .../OpenXLS/{WorkBook.java => WorkBook.kt} | 415 +- .../io/starter/OpenXLS/WorkBookException.java | 62 - .../io/starter/OpenXLS/WorkBookException.kt | 63 + .../io/starter/OpenXLS/WorkBookHandle.java | 2188 ------ .../java/io/starter/OpenXLS/WorkBookHandle.kt | 2200 ++++++ .../OpenXLS/{WorkSheet.java => WorkSheet.kt} | 473 +- .../io/starter/OpenXLS/WorkSheetHandle.java | 2933 -------- .../io/starter/OpenXLS/WorkSheetHandle.kt | 2995 +++++++++ .../LEO/{BIGBLOCK.java => BIGBLOCK.kt} | 31 +- .../formats/LEO/{Block.java => Block.kt} | 110 +- ...ByteConsumer.java => BlockByteConsumer.kt} | 67 +- .../starter/formats/LEO/BlockByteReader.java | 987 --- .../io/starter/formats/LEO/BlockByteReader.kt | 967 +++ .../io/starter/formats/LEO/BlockFactory.java | 161 - .../io/starter/formats/LEO/BlockFactory.kt | 162 + .../io/starter/formats/LEO/BlockImpl.java | 356 - .../java/io/starter/formats/LEO/BlockImpl.kt | 312 + .../io/starter/formats/LEO/FileBuffer.java | 145 - .../java/io/starter/formats/LEO/FileBuffer.kt | 137 + ...Exception.java => InvalidFileException.kt} | 25 +- .../java/io/starter/formats/LEO/LEOFile.java | 1381 ---- .../java/io/starter/formats/LEO/LEOFile.kt | 1406 ++++ .../io/starter/formats/LEO/LEOHeader.java | 413 -- .../java/io/starter/formats/LEO/LEOHeader.kt | 398 ++ ...Exception.java => LEOIndexingException.kt} | 25 +- .../LEO/{RootStorage.java => RootStorage.kt} | 60 +- .../LEO/{SMALLBLOCK.java => SMALLBLOCK.kt} | 31 +- .../LEO/{Storable.java => Storable.kt} | 10 +- .../java/io/starter/formats/LEO/Storage.java | 765 --- .../java/io/starter/formats/LEO/Storage.kt | 735 ++ ...ption.java => StorageNotFoundException.kt} | 25 +- .../io/starter/formats/LEO/StorageTable.java | 624 -- .../io/starter/formats/LEO/StorageTable.kt | 626 ++ .../io/starter/formats/OOXML/Alignment.java | 120 - .../io/starter/formats/OOXML/Alignment.kt | 124 + .../io/starter/formats/OOXML/AutoFilter.java | 684 -- .../io/starter/formats/OOXML/AutoFilter.kt | 730 ++ .../java/io/starter/formats/OOXML/AvLst.java | 102 - .../java/io/starter/formats/OOXML/AvLst.kt | 108 + .../java/io/starter/formats/OOXML/BodyPr.java | 220 - .../java/io/starter/formats/OOXML/BodyPr.kt | 230 + .../java/io/starter/formats/OOXML/Border.java | 526 -- .../java/io/starter/formats/OOXML/Border.kt | 544 ++ .../java/io/starter/formats/OOXML/CNvPr.java | 191 - .../java/io/starter/formats/OOXML/CNvPr.kt | 174 + .../java/io/starter/formats/OOXML/CfRule.java | 261 - .../java/io/starter/formats/OOXML/CfRule.kt | 258 + .../java/io/starter/formats/OOXML/Color.java | 825 --- .../java/io/starter/formats/OOXML/Color.kt | 816 +++ .../io/starter/formats/OOXML/ColorChoice.java | 821 --- .../io/starter/formats/OOXML/ColorChoice.kt | 841 +++ .../java/io/starter/formats/OOXML/CxnSp.java | 387 -- .../java/io/starter/formats/OOXML/CxnSp.kt | 359 + .../java/io/starter/formats/OOXML/DLbls.java | 239 - .../java/io/starter/formats/OOXML/DLbls.kt | 246 + .../java/io/starter/formats/OOXML/DPt.java | 149 - src/main/java/io/starter/formats/OOXML/DPt.kt | 156 + .../java/io/starter/formats/OOXML/DefRPr.java | 218 - .../java/io/starter/formats/OOXML/DefRPr.kt | 227 + .../java/io/starter/formats/OOXML/Dxf.java | 375 -- src/main/java/io/starter/formats/OOXML/Dxf.kt | 322 + .../formats/OOXML/EffectPropsGroup.java | 233 - .../starter/formats/OOXML/EffectPropsGroup.kt | 243 + .../java/io/starter/formats/OOXML/Ext.java | 112 - src/main/java/io/starter/formats/OOXML/Ext.kt | 115 + .../java/io/starter/formats/OOXML/ExtLst.java | 116 - .../java/io/starter/formats/OOXML/ExtLst.kt | 117 + .../java/io/starter/formats/OOXML/Fill.java | 1186 ---- .../java/io/starter/formats/OOXML/Fill.kt | 1170 ++++ .../io/starter/formats/OOXML/FillGroup.java | 1363 ---- .../io/starter/formats/OOXML/FillGroup.kt | 1410 ++++ .../java/io/starter/formats/OOXML/Gd.java | 101 - src/main/java/io/starter/formats/OOXML/Gd.kt | 106 + .../io/starter/formats/OOXML/GeomGroup.java | 788 --- .../io/starter/formats/OOXML/GeomGroup.kt | 835 +++ .../io/starter/formats/OOXML/Graphic.java | 236 - .../java/io/starter/formats/OOXML/Graphic.kt | 221 + .../starter/formats/OOXML/GraphicFrame.java | 458 -- .../io/starter/formats/OOXML/GraphicFrame.kt | 433 ++ .../java/io/starter/formats/OOXML/GrpSp.java | 825 --- .../java/io/starter/formats/OOXML/GrpSp.kt | 827 +++ .../java/io/starter/formats/OOXML/Layout.java | 258 - .../java/io/starter/formats/OOXML/Layout.kt | 270 + .../java/io/starter/formats/OOXML/Legend.java | 266 - .../java/io/starter/formats/OOXML/Legend.kt | 275 + .../java/io/starter/formats/OOXML/Ln.java | 615 -- src/main/java/io/starter/formats/OOXML/Ln.kt | 630 ++ .../java/io/starter/formats/OOXML/Marker.java | 113 - .../java/io/starter/formats/OOXML/Marker.kt | 118 + .../java/io/starter/formats/OOXML/NumFmt.java | 145 - .../java/io/starter/formats/OOXML/NumFmt.kt | 148 + .../starter/formats/OOXML/OOXMLConstants.java | 372 -- .../starter/formats/OOXML/OOXMLConstants.kt | 219 + .../{OOXMLElement.java => OOXMLElement.kt} | 14 +- .../{OOXMLHandle.java => OOXMLHandle.kt} | 56 +- .../starter/formats/OOXML/ObjectChoice.java | 437 -- .../io/starter/formats/OOXML/ObjectChoice.kt | 454 ++ .../starter/formats/OOXML/OneCellAnchor.java | 344 - .../io/starter/formats/OOXML/OneCellAnchor.kt | 324 + src/main/java/io/starter/formats/OOXML/P.java | 475 -- src/main/java/io/starter/formats/OOXML/P.kt | 481 ++ .../java/io/starter/formats/OOXML/Pic.java | 504 -- src/main/java/io/starter/formats/OOXML/Pic.kt | 477 ++ .../formats/OOXML/PivotCacheDefinition.java | 140 - .../formats/OOXML/PivotCacheDefinition.kt | 129 + .../formats/OOXML/PivotTableDefinition.java | 717 -- .../formats/OOXML/PivotTableDefinition.kt | 746 +++ .../io/starter/formats/OOXML/SheetPr.java | 365 - .../java/io/starter/formats/OOXML/SheetPr.kt | 383 ++ .../io/starter/formats/OOXML/SheetView.java | 308 - .../io/starter/formats/OOXML/SheetView.kt | 321 + .../java/io/starter/formats/OOXML/Sp.java | 545 -- src/main/java/io/starter/formats/OOXML/Sp.kt | 531 ++ .../java/io/starter/formats/OOXML/SpPr.java | 340 - .../java/io/starter/formats/OOXML/SpPr.kt | 325 + .../java/io/starter/formats/OOXML/Ss_rPr.java | 315 - .../java/io/starter/formats/OOXML/Ss_rPr.kt | 322 + .../java/io/starter/formats/OOXML/StrRef.java | 191 - .../java/io/starter/formats/OOXML/StrRef.kt | 201 + .../java/io/starter/formats/OOXML/Style.java | 433 -- .../java/io/starter/formats/OOXML/Style.kt | 459 ++ .../java/io/starter/formats/OOXML/Text.java | 205 - .../java/io/starter/formats/OOXML/Text.kt | 203 + .../io/starter/formats/OOXML/TextRun.java | 505 -- .../java/io/starter/formats/OOXML/TextRun.kt | 523 ++ .../java/io/starter/formats/OOXML/Theme.java | 160 - .../java/io/starter/formats/OOXML/Theme.kt | 161 + .../java/io/starter/formats/OOXML/Title.java | 357 - .../java/io/starter/formats/OOXML/Title.kt | 358 + .../starter/formats/OOXML/TwoCellAnchor.java | 660 -- .../io/starter/formats/OOXML/TwoCellAnchor.kt | 630 ++ .../java/io/starter/formats/OOXML/TxBody.java | 103 - .../java/io/starter/formats/OOXML/TxBody.kt | 110 + .../java/io/starter/formats/OOXML/TxPr.java | 160 - .../java/io/starter/formats/OOXML/TxPr.kt | 159 + .../java/io/starter/formats/OOXML/Xfrm.java | 207 - .../java/io/starter/formats/OOXML/Xfrm.kt | 214 + .../java/io/starter/formats/XLS/Array.java | 338 - src/main/java/io/starter/formats/XLS/Array.kt | 329 + .../io/starter/formats/XLS/AutoFilter.java | 1159 ---- .../java/io/starter/formats/XLS/AutoFilter.kt | 1109 ++++ .../formats/XLS/{BiffRec.java => BiffRec.kt} | 336 +- .../java/io/starter/formats/XLS/Blank.java | 116 - src/main/java/io/starter/formats/XLS/Blank.kt | 117 + src/main/java/io/starter/formats/XLS/Bof.java | 124 - src/main/java/io/starter/formats/XLS/Bof.kt | 119 + .../formats/XLS/{Book.java => Book.kt} | 40 +- .../XLS/{BookBool.java => BookBool.kt} | 35 +- ...nManager.java => BookProtectionManager.kt} | 23 +- .../java/io/starter/formats/XLS/Boolerr.java | 170 - .../java/io/starter/formats/XLS/Boolerr.kt | 168 + .../{BottomMargin.java => BottomMargin.kt} | 44 +- .../io/starter/formats/XLS/Boundsheet.java | 5582 ---------------- .../java/io/starter/formats/XLS/Boundsheet.kt | 5911 +++++++++++++++++ .../io/starter/formats/XLS/ByteStreamer.java | 576 -- .../io/starter/formats/XLS/ByteStreamer.kt | 560 ++ .../XLS/{CalcMode.java => CalcMode.kt} | 67 +- ...mparator.java => CellAddressComparator.kt} | 53 +- .../starter/formats/XLS/CellAddressible.java | 130 - .../io/starter/formats/XLS/CellAddressible.kt | 106 + .../formats/XLS/CellNotFoundException.java | 41 - .../CellNotFoundException.kt} | 24 +- ....java => CellPositionConflictException.kt} | 32 +- .../formats/XLS/{CellRec.java => CellRec.kt} | 9 +- ...tion.java => CellTypeMismatchException.kt} | 33 +- src/main/java/io/starter/formats/XLS/Cf.java | 2387 ------- src/main/java/io/starter/formats/XLS/Cf.kt | 2317 +++++++ .../formats/XLS/{Cf12.java => Cf12.kt} | 17 +- ...ception.java => ChartNotFoundException.kt} | 34 +- .../java/io/starter/formats/XLS/Codename.java | 103 - .../java/io/starter/formats/XLS/Codename.kt | 107 + .../java/io/starter/formats/XLS/Colinfo.java | 386 -- .../java/io/starter/formats/XLS/Colinfo.kt | 386 ++ .../formats/XLS/{Color.java => Color.kt} | 43 +- ...eption.java => ColumnNotFoundException.kt} | 35 +- .../XLS/{ColumnRange.java => ColumnRange.kt} | 74 +- .../java/io/starter/formats/XLS/Condfmt.java | 533 -- .../java/io/starter/formats/XLS/Condfmt.kt | 522 ++ .../XLS/{Condfmt12.java => Condfmt12.kt} | 24 +- .../XLS/{Continue.java => Continue.kt} | 242 +- .../starter/formats/XLS/ContinueHandler.java | 757 --- .../io/starter/formats/XLS/ContinueHandler.kt | 765 +++ .../formats/XLS/{Country.java => Country.kt} | 30 +- src/main/java/io/starter/formats/XLS/Crn.java | 107 - src/main/java/io/starter/formats/XLS/Crn.kt | 117 + .../formats/XLS/CustomFormatHelper.java | 217 - .../starter/formats/XLS/CustomFormatHelper.kt | 217 + .../formats/XLS/{DConBin.java => DConBin.kt} | 157 +- .../XLS/{DConName.java => DConName.kt} | 119 +- .../formats/XLS/{DConRef.java => DConRef.kt} | 306 +- .../java/io/starter/formats/XLS/Dbcell.java | 345 - .../java/io/starter/formats/XLS/Dbcell.kt | 339 + .../XLS/{DefColWidth.java => DefColWidth.kt} | 45 +- ...aultRowHeight.java => DefaultRowHeight.kt} | 71 +- .../io/starter/formats/XLS/Dimensions.java | 191 - .../java/io/starter/formats/XLS/Dimensions.kt | 204 + .../formats/XLS/DiscontiguousRefStruct.java | 536 -- .../formats/XLS/DiscontiguousRefStruct.kt | 552 ++ .../starter/formats/XLS/{Dsf.java => Dsf.kt} | 32 +- src/main/java/io/starter/formats/XLS/Dv.java | 1369 ---- src/main/java/io/starter/formats/XLS/Dv.kt | 1384 ++++ .../java/io/starter/formats/XLS/Dval.java | 418 -- src/main/java/io/starter/formats/XLS/Dval.kt | 413 ++ .../starter/formats/XLS/{Eof.java => Eof.kt} | 18 +- .../starter/formats/XLS/ExpressionParser.java | 1199 ---- .../starter/formats/XLS/ExpressionParser.kt | 1214 ++++ .../io/starter/formats/XLS/Externname.java | 126 - .../java/io/starter/formats/XLS/Externname.kt | 134 + .../io/starter/formats/XLS/Externsheet.java | 685 -- .../io/starter/formats/XLS/Externsheet.kt | 702 ++ .../java/io/starter/formats/XLS/Extsst.java | 253 - .../java/io/starter/formats/XLS/Extsst.kt | 271 + .../XLS/{FeatHeadr.java => FeatHeadr.kt} | 205 +- .../java/io/starter/formats/XLS/FileLock.kt | 34 + .../java/io/starter/formats/XLS/Font.java | 908 --- src/main/java/io/starter/formats/XLS/Font.kt | 891 +++ .../XLS/{FontBasis.java => FontBasis.kt} | 54 +- .../io/starter/formats/XLS/Footerrec.java | 112 - .../java/io/starter/formats/XLS/Footerrec.kt | 117 + .../formats/XLS/{Format.java => Format.kt} | 132 +- .../starter/formats/XLS/FormatConstants.java | 1020 --- .../io/starter/formats/XLS/FormatConstants.kt | 542 ++ ...stantsImpl.java => FormatConstantsImpl.kt} | 29 +- .../java/io/starter/formats/XLS/Formula.java | 1259 ---- .../java/io/starter/formats/XLS/Formula.kt | 1257 ++++ ...endxn.java => FormulaNotFoundException.kt} | 24 +- .../XLS/FunctionNotSupportedException.java | 81 - .../XLS/FunctionNotSupportedException.kt | 83 + .../java/io/starter/formats/XLS/Guts.java | 116 - src/main/java/io/starter/formats/XLS/Guts.kt | 113 + .../formats/XLS/{HCenter.java => HCenter.kt} | 30 +- .../io/starter/formats/XLS/Headerrec.java | 112 - .../java/io/starter/formats/XLS/Headerrec.kt | 117 + .../java/io/starter/formats/XLS/Hlink.java | 624 -- src/main/java/io/starter/formats/XLS/Hlink.kt | 626 ++ ...ception.java => ImageNotFoundException.kt} | 35 +- .../java/io/starter/formats/XLS/Index.java | 353 - src/main/java/io/starter/formats/XLS/Index.kt | 367 + .../io/starter/formats/XLS/InterfaceHdr.kt | 34 + .../formats/XLS/InvalidRecordException.java | 40 - ...neChart.java => InvalidRecordException.kt} | 24 +- .../formats/XLS/{Isstinf.java => Isstinf.kt} | 39 +- .../java/io/starter/formats/XLS/Label.java | 110 - src/main/java/io/starter/formats/XLS/Label.kt | 115 + .../java/io/starter/formats/XLS/Labelsst.java | 262 - .../java/io/starter/formats/XLS/Labelsst.kt | 272 + .../XLS/{LeftMargin.java => LeftMargin.kt} | 44 +- .../io/starter/formats/XLS/MSODrawing.java | 2220 ------- .../java/io/starter/formats/XLS/MSODrawing.kt | 2321 +++++++ .../formats/XLS/MSODrawingConstants.java | 393 -- .../formats/XLS/MSODrawingConstants.kt | 394 ++ .../starter/formats/XLS/MSODrawingGroup.java | 842 --- .../io/starter/formats/XLS/MSODrawingGroup.kt | 845 +++ ...gSelection.java => MSODrawingSelection.kt} | 40 +- .../io/starter/formats/XLS/Mergedcells.java | 231 - .../io/starter/formats/XLS/Mergedcells.kt | 236 + .../starter/formats/XLS/{Mul.java => Mul.kt} | 22 +- .../java/io/starter/formats/XLS/Mulblank.java | 422 -- .../java/io/starter/formats/XLS/Mulblank.kt | 430 ++ .../formats/XLS/{Mulled.java => Mulled.kt} | 13 +- .../java/io/starter/formats/XLS/Mulrk.java | 229 - src/main/java/io/starter/formats/XLS/Mulrk.kt | 229 + .../java/io/starter/formats/XLS/Name.java | 1028 --- src/main/java/io/starter/formats/XLS/Name.kt | 1003 +++ ...xception.java => NameNotFoundException.kt} | 35 +- ...{NineteenOhFour.java => NineteenOhFour.kt} | 33 +- .../java/io/starter/formats/XLS/Note.java | 490 -- src/main/java/io/starter/formats/XLS/Note.kt | 515 ++ .../io/starter/formats/XLS/NumberRec.java | 193 - .../java/io/starter/formats/XLS/NumberRec.kt | 183 + .../io/starter/formats/XLS/OOXMLAdapter.java | 940 --- .../io/starter/formats/XLS/OOXMLAdapter.kt | 962 +++ .../io/starter/formats/XLS/OOXMLReader.java | 1131 ---- .../io/starter/formats/XLS/OOXMLReader.kt | 1182 ++++ .../io/starter/formats/XLS/OOXMLWriter.java | 1724 ----- .../io/starter/formats/XLS/OOXMLWriter.kt | 1756 +++++ src/main/java/io/starter/formats/XLS/Obj.java | 939 --- src/main/java/io/starter/formats/XLS/Obj.kt | 931 +++ .../XLS/{ObjProtect.java => ObjProtect.kt} | 81 +- .../java/io/starter/formats/XLS/Obproj.java | 42 - ...ormulaNotFoundException.java => Obproj.kt} | 28 +- .../starter/formats/XLS/{PLV.java => PLV.kt} | 20 +- .../formats/XLS/{Palette.java => Palette.kt} | 51 +- .../java/io/starter/formats/XLS/Pane.java | 205 - src/main/java/io/starter/formats/XLS/Pane.kt | 209 + .../java/io/starter/formats/XLS/Password.java | 176 - .../java/io/starter/formats/XLS/Password.kt | 176 + .../XLS/{Phonetic.java => Phonetic.kt} | 39 +- .../io/starter/formats/XLS/PivotCache.java | 308 - .../java/io/starter/formats/XLS/PivotCache.kt | 309 + ...otCacheRecord.java => PivotCacheRecord.kt} | 6 +- ...on.java => PivotTableNotFoundException.kt} | 35 +- .../starter/formats/XLS/{Pls.java => Pls.kt} | 35 +- .../XLS/{PrintGrid.java => PrintGrid.kt} | 30 +- .../XLS/{PrintRowCol.java => PrintRowCol.kt} | 30 +- .../XLS/{Prot4rev.java => Prot4rev.kt} | 46 +- .../formats/XLS/{Protect.java => Protect.kt} | 75 +- ...ctionManager.java => ProtectionManager.kt} | 76 +- .../XLS/{QsiSXTag.java => QsiSXTag.kt} | 173 +- .../starter/formats/XLS/{Ref.java => Ref.kt} | 44 +- .../starter/formats/XLS/ReferenceTracker.java | 1053 --- .../starter/formats/XLS/ReferenceTracker.kt | 1110 ++++ .../XLS/{RightMargin.java => RightMargin.kt} | 44 +- src/main/java/io/starter/formats/XLS/Rk.java | 678 -- src/main/java/io/starter/formats/XLS/Rk.kt | 663 ++ src/main/java/io/starter/formats/XLS/Row.java | 794 --- src/main/java/io/starter/formats/XLS/Row.kt | 794 +++ ...Exception.java => RowNotFoundException.kt} | 35 +- .../XLS/{InterfaceHdr.java => RrdHead.kt} | 14 +- .../formats/XLS/{FileLock.java => RrdInfo.kt} | 14 +- .../formats/XLS/{Rstring.java => Rstring.kt} | 17 +- .../java/io/starter/formats/XLS/SXBool.java | 83 - .../java/io/starter/formats/XLS/SXBool.kt | 88 + .../java/io/starter/formats/XLS/SXDBEx.java | 105 - .../java/io/starter/formats/XLS/SXDBEx.kt | 112 + .../io/starter/formats/XLS/SXFDBType.java | 144 - .../java/io/starter/formats/XLS/SXFDBType.kt | 153 + .../java/io/starter/formats/XLS/SXNum.java | 83 - src/main/java/io/starter/formats/XLS/SXNum.kt | 88 + .../java/io/starter/formats/XLS/SXString.java | 122 - .../java/io/starter/formats/XLS/SXString.kt | 130 + .../XLS/{ScenProtect.java => ScenProtect.kt} | 77 +- src/main/java/io/starter/formats/XLS/Scl.java | 142 - src/main/java/io/starter/formats/XLS/Scl.kt | 143 + .../XLS/{Selection.java => Selection.kt} | 84 +- .../java/io/starter/formats/XLS/Setup.java | 686 -- src/main/java/io/starter/formats/XLS/Setup.kt | 681 ++ .../formats/XLS/{Sheet.java => Sheet.kt} | 440 +- .../formats/XLS/SheetProtectionManager.java | 282 - .../formats/XLS/SheetProtectionManager.kt | 279 + .../java/io/starter/formats/XLS/Shrfmla.java | 445 -- .../java/io/starter/formats/XLS/Shrfmla.kt | 428 ++ src/main/java/io/starter/formats/XLS/Sst.java | 1712 ----- src/main/java/io/starter/formats/XLS/Sst.kt | 1719 +++++ .../XLS/{StringRec.java => StringRec.kt} | 73 +- .../formats/XLS/{Style.java => Style.kt} | 64 +- .../java/io/starter/formats/XLS/Supbook.java | 367 - .../java/io/starter/formats/XLS/Supbook.kt | 373 ++ .../java/io/starter/formats/XLS/SxAddl.java | 430 -- .../java/io/starter/formats/XLS/SxAddl.kt | 422 ++ .../formats/XLS/{SxDB.java => SxDB.kt} | 310 +- .../formats/XLS/{SxDBB.java => SxDBB.kt} | 110 +- .../formats/XLS/{SxDI.java => SxDI.kt} | 419 +- .../formats/XLS/{SxEX.java => SxEX.kt} | 134 +- .../formats/XLS/{SxFDB.java => SxFDB.kt} | 383 +- .../formats/XLS/{SxPI.java => SxPI.kt} | 153 +- .../io/starter/formats/XLS/SxStreamID.java | 229 - .../java/io/starter/formats/XLS/SxStreamID.kt | 231 + .../XLS/{SxVIEWEX9.java => SxVIEWEX9.kt} | 88 +- .../formats/XLS/{SxVS.java => SxVS.kt} | 68 +- .../formats/XLS/{SxVdEX.java => SxVdEX.kt} | 140 +- .../XLS/{Sxformat.java => Sxformat.kt} | 36 +- .../formats/XLS/{Sxivd.java => Sxivd.kt} | 86 +- .../formats/XLS/{Sxli.java => Sxli.kt} | 523 +- .../formats/XLS/{Sxvd.java => Sxvd.kt} | 464 +- .../java/io/starter/formats/XLS/Sxvi.java | 320 - src/main/java/io/starter/formats/XLS/Sxvi.kt | 328 + .../java/io/starter/formats/XLS/Sxview.java | 1330 ---- .../java/io/starter/formats/XLS/Sxview.kt | 1313 ++++ .../java/io/starter/formats/XLS/TabID.java | 134 - src/main/java/io/starter/formats/XLS/TabID.kt | 134 + .../XLS/{TableStyles.java => TableStyles.kt} | 97 +- .../XLS/{TopMargin.java => TopMargin.kt} | 44 +- src/main/java/io/starter/formats/XLS/Txo.java | 372 -- src/main/java/io/starter/formats/XLS/Txo.kt | 409 ++ ...XLSRecord.java => UnencryptedXLSRecord.kt} | 19 +- .../io/starter/formats/XLS/Unicodestring.java | 858 --- .../io/starter/formats/XLS/Unicodestring.kt | 845 +++ .../starter/formats/XLS/Usersviewbegin.java | 160 - .../io/starter/formats/XLS/Usersviewbegin.kt | 156 + .../{Usersviewend.java => Usersviewend.kt} | 38 +- .../formats/XLS/{RrdInfo.java => UsrExcl.kt} | 18 +- .../formats/XLS/{VCenter.java => VCenter.kt} | 30 +- ...nException.java => ValidationException.kt} | 41 +- .../java/io/starter/formats/XLS/Window1.java | 150 - .../java/io/starter/formats/XLS/Window1.kt | 161 + .../java/io/starter/formats/XLS/Window2.java | 260 - .../java/io/starter/formats/XLS/Window2.kt | 252 + .../java/io/starter/formats/XLS/WorkBook.java | 3946 ----------- .../java/io/starter/formats/XLS/WorkBook.kt | 4040 +++++++++++ .../formats/XLS/WorkBookAssembler.java | 248 - .../starter/formats/XLS/WorkBookAssembler.kt | 252 + .../formats/XLS/WorkBookException.java | 73 - .../starter/formats/XLS/WorkBookException.kt | 70 + .../starter/formats/XLS/WorkBookFactory.java | 1170 ---- .../io/starter/formats/XLS/WorkBookFactory.kt | 1078 +++ ...ion.java => WorkSheetNotFoundException.kt} | 39 +- .../io/starter/formats/XLS/Writeaccess.java | 75 - .../io/starter/formats/XLS/Writeaccess.kt | 77 + .../formats/XLS/{WsBool.java => WsBool.kt} | 49 +- .../{XLSCellRecord.java => XLSCellRecord.kt} | 25 +- .../io/starter/formats/XLS/XLSConstants.java | 321 - .../io/starter/formats/XLS/XLSConstants.kt | 322 + .../io/starter/formats/XLS/XLSRecord.java | 1121 ---- .../java/io/starter/formats/XLS/XLSRecord.kt | 1042 +++ .../starter/formats/XLS/XLSRecordFactory.java | 996 --- .../starter/formats/XLS/XLSRecordFactory.kt | 548 ++ .../{XLSSubstream.java => XLSSubstream.kt} | 28 +- src/main/java/io/starter/formats/XLS/Xct.java | 62 - src/main/java/io/starter/formats/XLS/Xct.kt | 66 + src/main/java/io/starter/formats/XLS/Xf.java | 1494 ----- src/main/java/io/starter/formats/XLS/Xf.kt | 1445 ++++ .../io/starter/formats/XLS/charts/Ai.java | 817 --- .../java/io/starter/formats/XLS/charts/Ai.kt | 811 +++ .../XLS/charts/{Fbi.java => AlRuns.kt} | 22 +- .../io/starter/formats/XLS/charts/Area.java | 134 - .../io/starter/formats/XLS/charts/Area.kt | 138 + .../formats/XLS/charts/Area3DChart.java | 85 - .../starter/formats/XLS/charts/Area3DChart.kt | 84 + .../starter/formats/XLS/charts/AreaChart.java | 296 - .../starter/formats/XLS/charts/AreaChart.kt | 294 + .../formats/XLS/charts/AreaFormat.java | 301 - .../starter/formats/XLS/charts/AreaFormat.kt | 317 + .../formats/XLS/charts/AttachedLabel.java | 214 - .../formats/XLS/charts/AttachedLabel.kt | 224 + .../io/starter/formats/XLS/charts/Axcent.java | 137 - .../io/starter/formats/XLS/charts/Axcent.kt | 127 + .../XLS/charts/{Axesused.java => Axesused.kt} | 25 +- .../io/starter/formats/XLS/charts/Axis.java | 2332 ------- .../io/starter/formats/XLS/charts/Axis.kt | 2379 +++++++ .../formats/XLS/charts/AxisLineFormat.java | 85 - .../formats/XLS/charts/AxisLineFormat.kt | 86 + .../formats/XLS/charts/AxisParent.java | 360 - .../starter/formats/XLS/charts/AxisParent.kt | 370 ++ .../io/starter/formats/XLS/charts/Bar.java | 232 - .../java/io/starter/formats/XLS/charts/Bar.kt | 236 + .../formats/XLS/charts/Bar3DChart.java | 93 - .../starter/formats/XLS/charts/Bar3DChart.kt | 92 + .../starter/formats/XLS/charts/BarChart.java | 329 - .../io/starter/formats/XLS/charts/BarChart.kt | 323 + .../XLS/charts/{Begin.java => Begin.kt} | 39 +- .../XLS/charts/{Boppop.java => Boppop.kt} | 347 +- .../{BoppopCustom.java => BoppopCustom.kt} | 21 +- .../formats/XLS/charts/BubbleChart.java | 228 - .../starter/formats/XLS/charts/BubbleChart.kt | 224 + .../XLS/charts/{CatLab.java => CatLab.kt} | 119 +- .../formats/XLS/charts/CatserRange.java | 248 - .../starter/formats/XLS/charts/CatserRange.kt | 256 + .../io/starter/formats/XLS/charts/Chart.java | 1932 ------ .../io/starter/formats/XLS/charts/Chart.kt | 2034 ++++++ ...hart3DBarShape.java => Chart3DBarShape.kt} | 63 +- .../starter/formats/XLS/charts/ChartAxes.java | 1043 --- .../starter/formats/XLS/charts/ChartAxes.kt | 1042 +++ .../formats/XLS/charts/ChartConstants.java | 65 - .../formats/XLS/charts/ChartConstants.kt | 67 + .../formats/XLS/charts/ChartFormat.java | 940 --- .../starter/formats/XLS/charts/ChartFormat.kt | 959 +++ .../formats/XLS/charts/ChartFormatLink.java | 46 - .../formats/XLS/charts/ChartFormatLink.kt | 51 + .../{ChartFrtInfo.java => ChartFrtInfo.kt} | 54 +- .../starter/formats/XLS/charts/ChartLine.java | 179 - .../starter/formats/XLS/charts/ChartLine.kt | 185 + .../{ChartObject.java => ChartObject.kt} | 30 +- .../formats/XLS/charts/ChartSeries.java | 991 --- .../starter/formats/XLS/charts/ChartSeries.kt | 1013 +++ .../starter/formats/XLS/charts/ChartType.java | 1485 ----- .../starter/formats/XLS/charts/ChartType.kt | 1480 +++++ .../formats/XLS/charts/Col3DChart.java | 92 - .../starter/formats/XLS/charts/Col3DChart.kt | 91 + .../starter/formats/XLS/charts/ColChart.java | 170 - .../io/starter/formats/XLS/charts/ColChart.kt | 169 + .../formats/XLS/charts/ConeBarChart.java | 38 - .../formats/XLS/charts/ConeBarChart.kt | 36 + .../starter/formats/XLS/charts/ConeChart.kt | 37 + .../{CrtLayout12.java => CrtLayout12.kt} | 91 +- .../{CrtLayout12A.java => CrtLayout12A.kt} | 111 +- .../formats/XLS/charts/CylinderBarChart.java | 38 - .../formats/XLS/charts/CylinderBarChart.kt | 36 + .../formats/XLS/charts/CylinderChart.java | 38 - .../formats/XLS/charts/CylinderChart.kt | 36 + .../io/starter/formats/XLS/charts/Dat.java | 109 - .../java/io/starter/formats/XLS/charts/Dat.kt | 119 + .../formats/XLS/charts/DataFormat.java | 679 -- .../starter/formats/XLS/charts/DataFormat.kt | 670 ++ .../charts/{DataLabExt.java => DataLabExt.kt} | 46 +- ...ExtContents.java => DataLabExtContents.kt} | 122 +- .../{DefaultText.java => DefaultText.kt} | 76 +- .../formats/XLS/charts/DoughnutChart.java | 249 - .../formats/XLS/charts/DoughnutChart.kt | 256 + .../XLS/charts/{Dropbar.java => Dropbar.kt} | 111 +- .../formats/XLS/charts/{End.java => End.kt} | 39 +- .../XLS/charts/{EndBlock.java => EndBlock.kt} | 63 +- .../charts/{EndObject.java => EndObject.kt} | 46 +- .../formats/XLS/charts/{Picf.java => Fbi.kt} | 21 +- .../XLS/charts/{Fontx.java => Fontx.kt} | 86 +- .../io/starter/formats/XLS/charts/Frame.java | 192 - .../io/starter/formats/XLS/charts/Frame.kt | 194 + .../{FrtFontList.java => FrtFontList.kt} | 52 +- .../formats/XLS/charts/FrtWrapper.java | 114 - .../starter/formats/XLS/charts/FrtWrapper.kt | 111 + .../XLS/charts/{GelFrame.java => GelFrame.kt} | 46 +- .../XLS/charts/GenericChartObject.java | 158 - .../formats/XLS/charts/GenericChartObject.kt | 147 + .../formats/XLS/charts/{Ifmt.java => Ifmt.kt} | 26 +- .../io/starter/formats/XLS/charts/Legend.java | 604 -- .../io/starter/formats/XLS/charts/Legend.kt | 645 ++ .../io/starter/formats/XLS/charts/Legendxn.kt | 40 + .../io/starter/formats/XLS/charts/Line.java | 135 - .../io/starter/formats/XLS/charts/Line.kt | 144 + .../formats/XLS/charts/Line3DChart.java | 86 - .../starter/formats/XLS/charts/Line3DChart.kt | 84 + .../starter/formats/XLS/charts/LineChart.java | 220 - .../starter/formats/XLS/charts/LineChart.kt | 217 + .../formats/XLS/charts/LineFormat.java | 280 - .../starter/formats/XLS/charts/LineFormat.kt | 294 + .../formats/XLS/charts/MarkerFormat.java | 211 - .../formats/XLS/charts/MarkerFormat.kt | 230 + .../formats/XLS/charts/OOXMLChart.java | 433 -- .../starter/formats/XLS/charts/OOXMLChart.kt | 418 ++ .../formats/XLS/charts/ObjectLink.java | 104 - .../starter/formats/XLS/charts/ObjectLink.kt | 103 + .../formats/XLS/charts/OfPieChart.java | 77 - .../starter/formats/XLS/charts/OfPieChart.kt | 76 + .../XLS/charts/{Units.java => Picf.kt} | 21 +- .../io/starter/formats/XLS/charts/Pie.java | 214 - .../java/io/starter/formats/XLS/charts/Pie.kt | 219 + .../formats/XLS/charts/Pie3dChart.java | 202 - .../starter/formats/XLS/charts/Pie3dChart.kt | 202 + .../starter/formats/XLS/charts/PieChart.java | 269 - .../io/starter/formats/XLS/charts/PieChart.kt | 276 + .../starter/formats/XLS/charts/PieFormat.java | 81 - .../starter/formats/XLS/charts/PieFormat.kt | 85 + ...{PivotChartBits.java => PivotChartBits.kt} | 49 +- ...{PivotChartLink.java => PivotChartLink.kt} | 46 +- .../starter/formats/XLS/charts/PlotArea.java | 37 - .../io/starter/formats/XLS/charts/PlotArea.kt | 40 + .../formats/XLS/charts/PlotGrowth.java | 37 - .../starter/formats/XLS/charts/PlotGrowth.kt | 40 + .../formats/XLS/charts/{Pos.java => Pos.kt} | 358 +- .../formats/XLS/charts/PyramidBarChart.java | 38 - .../charts/PyramidBarChart.kt} | 20 +- .../formats/XLS/charts/PyramidChart.java | 38 - .../formats/XLS/charts/PyramidChart.kt | 36 + .../io/starter/formats/XLS/charts/Radar.java | 103 - .../io/starter/formats/XLS/charts/Radar.kt | 110 + .../starter/formats/XLS/charts/RadarArea.java | 96 - .../starter/formats/XLS/charts/RadarArea.kt | 105 + ...{RadarAreaChart.java => RadarAreaChart.kt} | 43 +- .../formats/XLS/charts/RadarChart.java | 214 - .../starter/formats/XLS/charts/RadarChart.kt | 209 + .../starter/formats/XLS/charts/SbaseRef.java | 37 - .../io/starter/formats/XLS/charts/SbaseRef.kt | 40 + .../starter/formats/XLS/charts/Scatter.java | 145 - .../io/starter/formats/XLS/charts/Scatter.kt | 152 + .../formats/XLS/charts/ScatterChart.java | 332 - .../formats/XLS/charts/ScatterChart.kt | 336 + .../charts/{SerParent.java => SerParent.kt} | 21 +- .../XLS/charts/{SerToCrt.java => SerToCrt.kt} | 34 +- .../formats/XLS/charts/SerauxErrBar.java | 37 - .../formats/XLS/charts/SerauxErrBar.kt | 40 + .../formats/XLS/charts/SerauxTrend.java | 37 - .../starter/formats/XLS/charts/SerauxTrend.kt | 40 + .../io/starter/formats/XLS/charts/Serfmt.java | 155 - .../io/starter/formats/XLS/charts/Serfmt.kt | 156 + .../io/starter/formats/XLS/charts/Series.java | 1266 ---- .../io/starter/formats/XLS/charts/Series.kt | 1268 ++++ .../formats/XLS/charts/SeriesList.java | 86 - .../starter/formats/XLS/charts/SeriesList.kt | 88 + .../formats/XLS/charts/SeriesText.java | 120 - .../starter/formats/XLS/charts/SeriesText.kt | 131 + .../XLS/charts/{ShtProps.java => ShtProps.kt} | 27 +- .../XLS/charts/{SiIndex.java => SiIndex.kt} | 36 +- .../formats/XLS/charts/StackedAreaChart.java | 145 - .../formats/XLS/charts/StackedAreaChart.kt | 146 + .../formats/XLS/charts/StackedColumn.java | 127 - .../formats/XLS/charts/StackedColumn.kt | 132 + .../charts/{StartBlock.java => StartBlock.kt} | 76 +- .../{StartObject.java => StartObject.kt} | 44 +- .../charts/{StockChart.java => StockChart.kt} | 47 +- .../starter/formats/XLS/charts/Surface.java | 154 - .../io/starter/formats/XLS/charts/Surface.kt | 152 + ...{Surface3DChart.java => Surface3DChart.kt} | 41 +- .../{SurfaceChart.java => SurfaceChart.kt} | 47 +- .../charts/{SxViewLink.java => SxViewLink.kt} | 46 +- .../starter/formats/XLS/charts/TextDisp.java | 617 -- .../io/starter/formats/XLS/charts/TextDisp.kt | 640 ++ .../io/starter/formats/XLS/charts/ThreeD.java | 440 -- .../io/starter/formats/XLS/charts/ThreeD.kt | 452 ++ .../io/starter/formats/XLS/charts/Tick.java | 217 - .../io/starter/formats/XLS/charts/Tick.kt | 212 + .../XLS/charts/{AlRuns.java => Units.kt} | 22 +- .../formats/XLS/charts/ValueRange.java | 565 -- .../starter/formats/XLS/charts/ValueRange.kt | 571 ++ .../io/starter/formats/XLS/charts/YMult.java | 215 - .../io/starter/formats/XLS/charts/YMult.kt | 210 + .../XLS/formulas/CalculationException.java | 181 - .../XLS/formulas/CalculationException.kt | 153 + .../formats/XLS/formulas/Calculator.java | 227 - .../formats/XLS/formulas/Calculator.kt | 239 + ...ion.java => CircularReferenceException.kt} | 13 +- ...lculator.java => DDEExternalCalculator.kt} | 8 +- .../XLS/formulas/DatabaseCalculator.java | 1197 ---- .../XLS/formulas/DatabaseCalculator.kt | 1264 ++++ .../XLS/formulas/DateTimeCalculator.java | 633 -- .../XLS/formulas/DateTimeCalculator.kt | 664 ++ .../XLS/formulas/EngineeringCalculator.java | 2363 ------- .../XLS/formulas/EngineeringCalculator.kt | 2290 +++++++ .../XLS/formulas/FinancialCalculator.java | 3455 ---------- .../XLS/formulas/FinancialCalculator.kt | 3757 +++++++++++ .../XLS/formulas/FormulaCalculator.java | 167 - .../formats/XLS/formulas/FormulaCalculator.kt | 171 + .../formats/XLS/formulas/FormulaParser.java | 1683 ----- .../formats/XLS/formulas/FormulaParser.kt | 1756 +++++ .../XLS/formulas/FunctionConstants.java | 1895 ------ .../formats/XLS/formulas/FunctionConstants.kt | 1116 ++++ .../formats/XLS/formulas/FunctionHandler.java | 1789 ----- .../formats/XLS/formulas/FunctionHandler.kt | 1253 ++++ .../formats/XLS/formulas/GenericPtg.java | 721 -- .../formats/XLS/formulas/GenericPtg.kt | 697 ++ .../{IlblListener.java => IlblListener.kt} | 29 +- .../XLS/formulas/InformationCalculator.java | 722 -- .../XLS/formulas/InformationCalculator.kt | 732 ++ .../{IxtiListener.java => IxtiListener.kt} | 13 +- ...alCalculator.java => LogicalCalculator.kt} | 248 +- .../formulas/LookupReferenceCalculator.java | 1333 ---- .../XLS/formulas/LookupReferenceCalculator.kt | 1424 ++++ .../XLS/formulas/MathFunctionCalculator.java | 1434 ---- .../XLS/formulas/MathFunctionCalculator.kt | 1454 ++++ .../formats/XLS/formulas/{Ptg.java => Ptg.kt} | 350 +- .../starter/formats/XLS/formulas/PtgAdd.java | 136 - .../io/starter/formats/XLS/formulas/PtgAdd.kt | 137 + .../starter/formats/XLS/formulas/PtgArea.java | 910 --- .../starter/formats/XLS/formulas/PtgArea.kt | 929 +++ .../formats/XLS/formulas/PtgArea3d.java | 737 -- .../starter/formats/XLS/formulas/PtgArea3d.kt | 780 +++ .../formats/XLS/formulas/PtgAreaErr3d.java | 104 - .../formats/XLS/formulas/PtgAreaErr3d.kt | 101 + .../formats/XLS/formulas/PtgAreaN.java | 299 - .../starter/formats/XLS/formulas/PtgAreaN.kt | 297 + .../formats/XLS/formulas/PtgArray.java | 651 -- .../starter/formats/XLS/formulas/PtgArray.kt | 623 ++ .../starter/formats/XLS/formulas/PtgAtr.java | 248 - .../io/starter/formats/XLS/formulas/PtgAtr.kt | 250 + .../starter/formats/XLS/formulas/PtgBool.java | 107 - .../starter/formats/XLS/formulas/PtgBool.kt | 101 + .../formats/XLS/formulas/PtgCalculator.java | 293 - .../formats/XLS/formulas/PtgCalculator.kt | 293 + .../formats/XLS/formulas/PtgConcat.java | 117 - .../starter/formats/XLS/formulas/PtgConcat.kt | 113 + .../starter/formats/XLS/formulas/PtgDiv.java | 144 - .../io/starter/formats/XLS/formulas/PtgDiv.kt | 146 + .../starter/formats/XLS/formulas/PtgEQ.java | 172 - .../io/starter/formats/XLS/formulas/PtgEQ.kt | 177 + .../{PtgEndSheet.java => PtgEndSheet.kt} | 39 +- .../starter/formats/XLS/formulas/PtgErr.java | 146 - .../io/starter/formats/XLS/formulas/PtgErr.kt | 130 + .../starter/formats/XLS/formulas/PtgExp.java | 291 - .../io/starter/formats/XLS/formulas/PtgExp.kt | 293 + .../XLS/formulas/{PtgFunc.java => PtgFunc.kt} | 187 +- .../formats/XLS/formulas/PtgFuncVar.java | 279 - .../formats/XLS/formulas/PtgFuncVar.kt | 265 + .../XLS/formulas/{PtgGE.java => PtgGE.kt} | 171 +- .../XLS/formulas/{PtgGT.java => PtgGT.kt} | 176 +- .../starter/formats/XLS/formulas/PtgInt.java | 122 - .../io/starter/formats/XLS/formulas/PtgInt.kt | 113 + .../formats/XLS/formulas/PtgIsect.java | 226 - .../starter/formats/XLS/formulas/PtgIsect.kt | 225 + .../XLS/formulas/{PtgLE.java => PtgLE.kt} | 172 +- .../starter/formats/XLS/formulas/PtgLT.java | 203 - .../io/starter/formats/XLS/formulas/PtgLT.kt | 207 + .../formats/XLS/formulas/PtgMemArea.java | 339 - .../formats/XLS/formulas/PtgMemArea.kt | 356 + .../{PtgMemAreaA.java => PtgMemAreaA.kt} | 132 +- .../{PtgMemAreaN.java => PtgMemAreaN.kt} | 27 +- .../{PtgMemAreaNV.java => PtgMemAreaNV.kt} | 25 +- .../formulas/{PtgMemErr.java => PtgMemErr.kt} | 27 +- .../formats/XLS/formulas/PtgMemFunc.java | 649 -- .../formats/XLS/formulas/PtgMemFunc.kt | 754 +++ .../{PtgMissArg.java => PtgMissArg.kt} | 59 +- .../starter/formats/XLS/formulas/PtgMlt.java | 148 - .../io/starter/formats/XLS/formulas/PtgMlt.kt | 150 + .../{PtgMystery.java => PtgMystery.kt} | 34 +- .../starter/formats/XLS/formulas/PtgNE.java | 154 - .../io/starter/formats/XLS/formulas/PtgNE.kt | 158 + .../starter/formats/XLS/formulas/PtgName.java | 294 - .../starter/formats/XLS/formulas/PtgName.kt | 291 + .../formats/XLS/formulas/PtgNameX.java | 177 - .../starter/formats/XLS/formulas/PtgNameX.kt | 172 + .../formats/XLS/formulas/PtgNumber.java | 140 - .../starter/formats/XLS/formulas/PtgNumber.kt | 132 + .../formulas/{PtgParen.java => PtgParen.kt} | 78 +- .../{PtgPercent.java => PtgPercent.kt} | 60 +- .../formulas/{PtgPower.java => PtgPower.kt} | 82 +- .../formats/XLS/formulas/PtgRange.java | 266 - .../starter/formats/XLS/formulas/PtgRange.kt | 264 + .../starter/formats/XLS/formulas/PtgRef.java | 1105 --- .../io/starter/formats/XLS/formulas/PtgRef.kt | 1099 +++ .../formats/XLS/formulas/PtgRef3d.java | 458 -- .../starter/formats/XLS/formulas/PtgRef3d.kt | 475 ++ .../formulas/{PtgRefErr.java => PtgRefErr.kt} | 61 +- .../{PtgRefErr3d.java => PtgRefErr3d.kt} | 74 +- .../starter/formats/XLS/formulas/PtgRefN.java | 366 - .../starter/formats/XLS/formulas/PtgRefN.kt | 420 ++ .../starter/formats/XLS/formulas/PtgStr.java | 174 - .../io/starter/formats/XLS/formulas/PtgStr.kt | 180 + .../starter/formats/XLS/formulas/PtgSub.java | 144 - .../io/starter/formats/XLS/formulas/PtgSub.kt | 143 + .../formats/XLS/formulas/PtgUMinus.java | 104 - .../starter/formats/XLS/formulas/PtgUMinus.kt | 103 + .../formulas/{PtgUPlus.java => PtgUPlus.kt} | 62 +- .../formats/XLS/formulas/PtgUnion.java | 203 - .../starter/formats/XLS/formulas/PtgUnion.kt | 201 + .../XLS/formulas/StatisticalCalculator.java | 2176 ------ .../XLS/formulas/StatisticalCalculator.kt | 2233 +++++++ .../formats/XLS/formulas/TextCalculator.java | 830 --- .../formats/XLS/formulas/TextCalculator.kt | 873 +++ .../formats/XML/UnicodeInputStream.java | 158 - .../starter/formats/XML/UnicodeInputStream.kt | 160 + .../{CellFormat.java => CellFormat.kt} | 10 +- .../formats/cellformat/CellFormatFactory.java | 74 - .../formats/cellformat/CellFormatFactory.kt | 80 + ...{DateCellFormat.java => DateCellFormat.kt} | 43 +- .../formats/cellformat/GeneralCellFormat.java | 76 - .../formats/cellformat/GeneralCellFormat.kt | 76 + .../formats/cellformat/NumberCellFormat.java | 105 - .../formats/cellformat/NumberCellFormat.kt | 100 + .../starter/formats/escher/EscherRecord.java | 116 - .../io/starter/formats/escher/EscherRecord.kt | 84 + .../io/starter/formats/escher/MsofbtBSE.java | 272 - .../io/starter/formats/escher/MsofbtBSE.kt | 232 + .../formats/escher/MsofbtBstoreContainer.kt | 41 + .../formats/escher/MsofbtClientAnchor.java | 79 - .../formats/escher/MsofbtClientAnchor.kt | 87 + ...fbtClientData.java => MsofbtClientData.kt} | 21 +- .../io/starter/formats/escher/MsofbtDg.java | 62 - .../io/starter/formats/escher/MsofbtDg.kt | 63 + .../formats/escher/MsofbtDgContainer.java | 41 - .../formats/escher/MsofbtDgContainer.kt | 40 + .../io/starter/formats/escher/MsofbtDgg.java | 132 - .../io/starter/formats/escher/MsofbtDgg.kt | 117 + .../formats/escher/MsofbtDggContainer.kt | 41 + .../io/starter/formats/escher/MsofbtOPT.java | 549 -- .../io/starter/formats/escher/MsofbtOPT.kt | 578 ++ .../io/starter/formats/escher/MsofbtSp.java | 62 - .../io/starter/formats/escher/MsofbtSp.kt | 62 + .../formats/escher/MsofbtSpContainer.kt | 39 + .../io/starter/formats/escher/MsofbtSpgr.java | 71 - .../io/starter/formats/escher/MsofbtSpgr.kt | 76 + .../formats/escher/MsofbtSpgrContainer.java | 40 - .../formats/escher/MsofbtSpgrContainer.kt | 39 + .../formats/escher/MsofbtSplitMenuColors.java | 91 - .../formats/escher/MsofbtSplitMenuColors.kt | 61 + ...Impl.java => InitialContextFactoryImpl.kt} | 19 +- .../io/starter/naming/InitialContextImpl.java | 216 - .../io/starter/naming/InitialContextImpl.kt | 251 + src/main/java/io/starter/naming/NameImpl.java | 231 - src/main/java/io/starter/naming/NameImpl.kt | 230 + ...{NameParserImpl.java => NameParserImpl.kt} | 37 +- ...tionImpl.java => NamingEnumerationImpl.kt} | 39 +- src/main/java/io/starter/toolkit/Base64.java | 1372 ---- src/main/java/io/starter/toolkit/Base64.kt | 1380 ++++ ...rowserLauncher.java => BrowserLauncher.kt} | 72 +- .../java/io/starter/toolkit/ByteTools.java | 622 -- src/main/java/io/starter/toolkit/ByteTools.kt | 651 ++ ...igDecimal.java => CompatibleBigDecimal.kt} | 85 +- ...patibleVector.java => CompatibleVector.kt} | 262 +- ...torHints.java => CompatibleVectorHints.kt} | 9 +- ...l.java => DefaultTempFileGeneratorImpl.kt} | 48 +- ...esParser.java => DelimitedValuesParser.kt} | 71 +- ...tionListener.java => ExceptionListener.kt} | 6 +- .../io/starter/toolkit/ExtenClassLoader.java | 92 - .../io/starter/toolkit/ExtenClassLoader.kt | 98 + .../io/starter/toolkit/FastAddVector.java | 172 - .../java/io/starter/toolkit/FastAddVector.kt | 166 + .../io/starter/toolkit/FastGetVector.java | 191 - .../java/io/starter/toolkit/FastGetVector.kt | 190 + .../{FileFilter.java => FileFilter.kt} | 33 +- .../io/starter/toolkit/GenericRecycleBin.java | 219 - .../io/starter/toolkit/GenericRecycleBin.kt | 214 + src/main/java/io/starter/toolkit/InFile.java | 90 - src/main/java/io/starter/toolkit/InFile.kt | 90 + .../starter/toolkit/IndirectOutputStream.java | 187 - .../starter/toolkit/IndirectOutputStream.kt | 173 + .../java/io/starter/toolkit/JFileWriter.java | 235 - .../java/io/starter/toolkit/JFileWriter.kt | 246 + .../{LogOutputter.java => LogOutputter.kt} | 6 +- src/main/java/io/starter/toolkit/Logger.java | 509 -- src/main/java/io/starter/toolkit/Logger.kt | 479 ++ .../java/io/starter/toolkit/MD4Digest.java | 347 - src/main/java/io/starter/toolkit/MD4Digest.kt | 355 + .../starter/toolkit/{Money.java => Money.kt} | 1097 +-- src/main/java/io/starter/toolkit/OutFile.java | 41 - src/main/java/io/starter/toolkit/OutFile.kt | 37 + .../io/starter/toolkit/ProgressListener.java | 40 - .../ProgressListener.kt} | 20 +- .../io/starter/toolkit/ProgressNotifier.java | 41 - .../ProgressNotifier.kt} | 21 +- .../{Recyclable.java => Recyclable.kt} | 21 +- .../{RecycleBin.java => RecycleBin.kt} | 36 +- .../toolkit/RecycleBinFullException.java | 46 - .../RecycleBinFullException.kt} | 28 +- .../io/starter/toolkit/ResourceLoader.java | 547 -- .../java/io/starter/toolkit/ResourceLoader.kt | 559 ++ ...cialArrayList.java => SpecialArrayList.kt} | 475 +- .../java/io/starter/toolkit/StringTool.java | 1404 ---- .../java/io/starter/toolkit/StringTool.kt | 1454 ++++ ...ileGenerator.java => TempFileGenerator.kt} | 17 +- ...empFileManager.java => TempFileManager.kt} | 69 +- .../starter/toolkit/WriterOutputStream.java | 252 - .../io/starter/toolkit/WriterOutputStream.kt | 247 + .../starter/toolkit/XLSOptimizedTreeList.java | 936 --- .../starter/toolkit/XLSOptimizedTreeList.kt | 899 +++ .../io/starter/toolkit/XSLConverterTool.java | 327 - .../io/starter/toolkit/XSLConverterTool.kt | 228 + .../java/io/starter/OpenXLS/FormulaTest.java | 371 -- .../java/io/starter/OpenXLS/FormulaTest.kt | 379 ++ 866 files changed, 177109 insertions(+), 177004 deletions(-) create mode 100644 .idea/$PRODUCT_WORKSPACE_FILE$ create mode 100644 .idea/.gitignore create mode 100644 .idea/OpenXLS.iml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/modules.xml delete mode 100644 openxls/.project delete mode 100644 openxls/.settings/org.eclipse.wst.common.component delete mode 100644 openxls/bin/.project delete mode 100644 openxls/bin/.settings/org.eclipse.wst.common.component rename src/main/java/io/starter/OpenXLS/{AutoFilterHandle.java => AutoFilterHandle.kt} (60%) rename src/main/java/io/starter/OpenXLS/{Cell.java => Cell.kt} (73%) delete mode 100644 src/main/java/io/starter/OpenXLS/CellComparator.java create mode 100644 src/main/java/io/starter/OpenXLS/CellComparator.kt delete mode 100644 src/main/java/io/starter/OpenXLS/CellHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/CellHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/CellRange.java create mode 100644 src/main/java/io/starter/OpenXLS/CellRange.kt delete mode 100644 src/main/java/io/starter/OpenXLS/CellRangeRef.java create mode 100644 src/main/java/io/starter/OpenXLS/CellRangeRef.kt delete mode 100644 src/main/java/io/starter/OpenXLS/ChartHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/ChartHandle.kt rename src/main/java/io/starter/OpenXLS/{ChartSeriesHandle.java => ChartSeriesHandle.kt} (56%) delete mode 100644 src/main/java/io/starter/OpenXLS/ColHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/ColHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/CommentHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/CommentHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/ConditionalFormatRule.java create mode 100644 src/main/java/io/starter/OpenXLS/ConditionalFormatRule.kt rename src/main/java/io/starter/OpenXLS/{DateConverter.java => DateConverter.kt} (50%) rename src/main/java/io/starter/OpenXLS/{Document.java => Document.kt} (77%) delete mode 100644 src/main/java/io/starter/OpenXLS/DocumentHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/DocumentHandle.kt rename src/main/java/io/starter/{formats/XLS/RrdHead.java => OpenXLS/DocumentObjectNotFoundException.kt} (79%) delete mode 100644 src/main/java/io/starter/OpenXLS/ExcelTools.java create mode 100644 src/main/java/io/starter/OpenXLS/ExcelTools.kt rename src/main/java/io/starter/OpenXLS/{FormatCache.java => FormatCache.kt} (54%) delete mode 100644 src/main/java/io/starter/OpenXLS/FormatHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/FormatHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/FormulaHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/FormulaHandle.kt rename src/main/java/io/starter/OpenXLS/{GetInfo.java => GetInfo.kt} (54%) rename src/main/java/io/starter/OpenXLS/{Handle.java => Handle.kt} (92%) delete mode 100644 src/main/java/io/starter/OpenXLS/ImageHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/ImageHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/JSONConstants.java create mode 100644 src/main/java/io/starter/OpenXLS/JSONConstants.kt delete mode 100644 src/main/java/io/starter/OpenXLS/NameHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/NameHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/PivotTableHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/PivotTableHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/RowHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/RowHandle.kt delete mode 100644 src/main/java/io/starter/OpenXLS/ValidationHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/ValidationHandle.kt rename src/main/java/io/starter/OpenXLS/{WorkBook.java => WorkBook.kt} (69%) delete mode 100644 src/main/java/io/starter/OpenXLS/WorkBookException.java create mode 100644 src/main/java/io/starter/OpenXLS/WorkBookException.kt delete mode 100644 src/main/java/io/starter/OpenXLS/WorkBookHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/WorkBookHandle.kt rename src/main/java/io/starter/OpenXLS/{WorkSheet.java => WorkSheet.kt} (75%) delete mode 100644 src/main/java/io/starter/OpenXLS/WorkSheetHandle.java create mode 100644 src/main/java/io/starter/OpenXLS/WorkSheetHandle.kt rename src/main/java/io/starter/formats/LEO/{BIGBLOCK.java => BIGBLOCK.kt} (79%) rename src/main/java/io/starter/formats/LEO/{Block.java => Block.kt} (69%) rename src/main/java/io/starter/formats/LEO/{BlockByteConsumer.java => BlockByteConsumer.kt} (83%) delete mode 100644 src/main/java/io/starter/formats/LEO/BlockByteReader.java create mode 100644 src/main/java/io/starter/formats/LEO/BlockByteReader.kt delete mode 100644 src/main/java/io/starter/formats/LEO/BlockFactory.java create mode 100644 src/main/java/io/starter/formats/LEO/BlockFactory.kt delete mode 100644 src/main/java/io/starter/formats/LEO/BlockImpl.java create mode 100644 src/main/java/io/starter/formats/LEO/BlockImpl.kt delete mode 100644 src/main/java/io/starter/formats/LEO/FileBuffer.java create mode 100644 src/main/java/io/starter/formats/LEO/FileBuffer.kt rename src/main/java/io/starter/formats/LEO/{InvalidFileException.java => InvalidFileException.kt} (69%) delete mode 100644 src/main/java/io/starter/formats/LEO/LEOFile.java create mode 100644 src/main/java/io/starter/formats/LEO/LEOFile.kt delete mode 100644 src/main/java/io/starter/formats/LEO/LEOHeader.java create mode 100644 src/main/java/io/starter/formats/LEO/LEOHeader.kt rename src/main/java/io/starter/formats/LEO/{LEOIndexingException.java => LEOIndexingException.kt} (69%) rename src/main/java/io/starter/formats/LEO/{RootStorage.java => RootStorage.kt} (59%) rename src/main/java/io/starter/formats/LEO/{SMALLBLOCK.java => SMALLBLOCK.kt} (78%) rename src/main/java/io/starter/formats/LEO/{Storable.java => Storable.kt} (86%) delete mode 100644 src/main/java/io/starter/formats/LEO/Storage.java create mode 100644 src/main/java/io/starter/formats/LEO/Storage.kt rename src/main/java/io/starter/formats/LEO/{StorageNotFoundException.java => StorageNotFoundException.kt} (65%) delete mode 100644 src/main/java/io/starter/formats/LEO/StorageTable.java create mode 100644 src/main/java/io/starter/formats/LEO/StorageTable.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Alignment.java create mode 100644 src/main/java/io/starter/formats/OOXML/Alignment.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/AutoFilter.java create mode 100644 src/main/java/io/starter/formats/OOXML/AutoFilter.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/AvLst.java create mode 100644 src/main/java/io/starter/formats/OOXML/AvLst.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/BodyPr.java create mode 100644 src/main/java/io/starter/formats/OOXML/BodyPr.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Border.java create mode 100644 src/main/java/io/starter/formats/OOXML/Border.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/CNvPr.java create mode 100644 src/main/java/io/starter/formats/OOXML/CNvPr.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/CfRule.java create mode 100644 src/main/java/io/starter/formats/OOXML/CfRule.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Color.java create mode 100644 src/main/java/io/starter/formats/OOXML/Color.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/ColorChoice.java create mode 100644 src/main/java/io/starter/formats/OOXML/ColorChoice.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/CxnSp.java create mode 100644 src/main/java/io/starter/formats/OOXML/CxnSp.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/DLbls.java create mode 100644 src/main/java/io/starter/formats/OOXML/DLbls.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/DPt.java create mode 100644 src/main/java/io/starter/formats/OOXML/DPt.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/DefRPr.java create mode 100644 src/main/java/io/starter/formats/OOXML/DefRPr.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Dxf.java create mode 100644 src/main/java/io/starter/formats/OOXML/Dxf.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/EffectPropsGroup.java create mode 100644 src/main/java/io/starter/formats/OOXML/EffectPropsGroup.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Ext.java create mode 100644 src/main/java/io/starter/formats/OOXML/Ext.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/ExtLst.java create mode 100644 src/main/java/io/starter/formats/OOXML/ExtLst.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Fill.java create mode 100644 src/main/java/io/starter/formats/OOXML/Fill.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/FillGroup.java create mode 100644 src/main/java/io/starter/formats/OOXML/FillGroup.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Gd.java create mode 100644 src/main/java/io/starter/formats/OOXML/Gd.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/GeomGroup.java create mode 100644 src/main/java/io/starter/formats/OOXML/GeomGroup.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Graphic.java create mode 100644 src/main/java/io/starter/formats/OOXML/Graphic.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/GraphicFrame.java create mode 100644 src/main/java/io/starter/formats/OOXML/GraphicFrame.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/GrpSp.java create mode 100644 src/main/java/io/starter/formats/OOXML/GrpSp.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Layout.java create mode 100644 src/main/java/io/starter/formats/OOXML/Layout.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Legend.java create mode 100644 src/main/java/io/starter/formats/OOXML/Legend.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Ln.java create mode 100644 src/main/java/io/starter/formats/OOXML/Ln.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Marker.java create mode 100644 src/main/java/io/starter/formats/OOXML/Marker.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/NumFmt.java create mode 100644 src/main/java/io/starter/formats/OOXML/NumFmt.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/OOXMLConstants.java create mode 100644 src/main/java/io/starter/formats/OOXML/OOXMLConstants.kt rename src/main/java/io/starter/formats/OOXML/{OOXMLElement.java => OOXMLElement.kt} (73%) rename src/main/java/io/starter/formats/OOXML/{OOXMLHandle.java => OOXMLHandle.kt} (60%) delete mode 100644 src/main/java/io/starter/formats/OOXML/ObjectChoice.java create mode 100644 src/main/java/io/starter/formats/OOXML/ObjectChoice.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/OneCellAnchor.java create mode 100644 src/main/java/io/starter/formats/OOXML/OneCellAnchor.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/P.java create mode 100644 src/main/java/io/starter/formats/OOXML/P.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Pic.java create mode 100644 src/main/java/io/starter/formats/OOXML/Pic.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.java create mode 100644 src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/PivotTableDefinition.java create mode 100644 src/main/java/io/starter/formats/OOXML/PivotTableDefinition.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/SheetPr.java create mode 100644 src/main/java/io/starter/formats/OOXML/SheetPr.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/SheetView.java create mode 100644 src/main/java/io/starter/formats/OOXML/SheetView.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Sp.java create mode 100644 src/main/java/io/starter/formats/OOXML/Sp.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/SpPr.java create mode 100644 src/main/java/io/starter/formats/OOXML/SpPr.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Ss_rPr.java create mode 100644 src/main/java/io/starter/formats/OOXML/Ss_rPr.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/StrRef.java create mode 100644 src/main/java/io/starter/formats/OOXML/StrRef.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Style.java create mode 100644 src/main/java/io/starter/formats/OOXML/Style.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Text.java create mode 100644 src/main/java/io/starter/formats/OOXML/Text.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/TextRun.java create mode 100644 src/main/java/io/starter/formats/OOXML/TextRun.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Theme.java create mode 100644 src/main/java/io/starter/formats/OOXML/Theme.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Title.java create mode 100644 src/main/java/io/starter/formats/OOXML/Title.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/TwoCellAnchor.java create mode 100644 src/main/java/io/starter/formats/OOXML/TwoCellAnchor.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/TxBody.java create mode 100644 src/main/java/io/starter/formats/OOXML/TxBody.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/TxPr.java create mode 100644 src/main/java/io/starter/formats/OOXML/TxPr.kt delete mode 100644 src/main/java/io/starter/formats/OOXML/Xfrm.java create mode 100644 src/main/java/io/starter/formats/OOXML/Xfrm.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Array.java create mode 100644 src/main/java/io/starter/formats/XLS/Array.kt delete mode 100644 src/main/java/io/starter/formats/XLS/AutoFilter.java create mode 100644 src/main/java/io/starter/formats/XLS/AutoFilter.kt rename src/main/java/io/starter/formats/XLS/{BiffRec.java => BiffRec.kt} (64%) delete mode 100644 src/main/java/io/starter/formats/XLS/Blank.java create mode 100644 src/main/java/io/starter/formats/XLS/Blank.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Bof.java create mode 100644 src/main/java/io/starter/formats/XLS/Bof.kt rename src/main/java/io/starter/formats/XLS/{Book.java => Book.kt} (73%) rename src/main/java/io/starter/formats/XLS/{BookBool.java => BookBool.kt} (62%) rename src/main/java/io/starter/formats/XLS/{BookProtectionManager.java => BookProtectionManager.kt} (70%) delete mode 100644 src/main/java/io/starter/formats/XLS/Boolerr.java create mode 100644 src/main/java/io/starter/formats/XLS/Boolerr.kt rename src/main/java/io/starter/formats/XLS/{BottomMargin.java => BottomMargin.kt} (53%) delete mode 100644 src/main/java/io/starter/formats/XLS/Boundsheet.java create mode 100644 src/main/java/io/starter/formats/XLS/Boundsheet.kt delete mode 100644 src/main/java/io/starter/formats/XLS/ByteStreamer.java create mode 100644 src/main/java/io/starter/formats/XLS/ByteStreamer.kt rename src/main/java/io/starter/formats/XLS/{CalcMode.java => CalcMode.kt} (61%) rename src/main/java/io/starter/formats/XLS/{CellAddressComparator.java => CellAddressComparator.kt} (54%) delete mode 100644 src/main/java/io/starter/formats/XLS/CellAddressible.java create mode 100644 src/main/java/io/starter/formats/XLS/CellAddressible.kt delete mode 100644 src/main/java/io/starter/formats/XLS/CellNotFoundException.java rename src/main/java/io/starter/formats/{escher/MsofbtBstoreContainer.java => XLS/CellNotFoundException.kt} (66%) rename src/main/java/io/starter/formats/XLS/{CellPositionConflictException.java => CellPositionConflictException.kt} (71%) rename src/main/java/io/starter/formats/XLS/{CellRec.java => CellRec.kt} (84%) rename src/main/java/io/starter/formats/XLS/{CellTypeMismatchException.java => CellTypeMismatchException.kt} (69%) delete mode 100644 src/main/java/io/starter/formats/XLS/Cf.java create mode 100644 src/main/java/io/starter/formats/XLS/Cf.kt rename src/main/java/io/starter/formats/XLS/{Cf12.java => Cf12.kt} (83%) rename src/main/java/io/starter/formats/XLS/{ChartNotFoundException.java => ChartNotFoundException.kt} (66%) delete mode 100644 src/main/java/io/starter/formats/XLS/Codename.java create mode 100644 src/main/java/io/starter/formats/XLS/Codename.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Colinfo.java create mode 100644 src/main/java/io/starter/formats/XLS/Colinfo.kt rename src/main/java/io/starter/formats/XLS/{Color.java => Color.kt} (59%) rename src/main/java/io/starter/formats/XLS/{ColumnNotFoundException.java => ColumnNotFoundException.kt} (64%) rename src/main/java/io/starter/formats/XLS/{ColumnRange.java => ColumnRange.kt} (50%) delete mode 100644 src/main/java/io/starter/formats/XLS/Condfmt.java create mode 100644 src/main/java/io/starter/formats/XLS/Condfmt.kt rename src/main/java/io/starter/formats/XLS/{Condfmt12.java => Condfmt12.kt} (81%) rename src/main/java/io/starter/formats/XLS/{Continue.java => Continue.kt} (52%) delete mode 100644 src/main/java/io/starter/formats/XLS/ContinueHandler.java create mode 100644 src/main/java/io/starter/formats/XLS/ContinueHandler.kt rename src/main/java/io/starter/formats/XLS/{Country.java => Country.kt} (72%) delete mode 100644 src/main/java/io/starter/formats/XLS/Crn.java create mode 100644 src/main/java/io/starter/formats/XLS/Crn.kt delete mode 100644 src/main/java/io/starter/formats/XLS/CustomFormatHelper.java create mode 100644 src/main/java/io/starter/formats/XLS/CustomFormatHelper.kt rename src/main/java/io/starter/formats/XLS/{DConBin.java => DConBin.kt} (57%) rename src/main/java/io/starter/formats/XLS/{DConName.java => DConName.kt} (61%) rename src/main/java/io/starter/formats/XLS/{DConRef.java => DConRef.kt} (52%) delete mode 100644 src/main/java/io/starter/formats/XLS/Dbcell.java create mode 100644 src/main/java/io/starter/formats/XLS/Dbcell.kt rename src/main/java/io/starter/formats/XLS/{DefColWidth.java => DefColWidth.kt} (54%) rename src/main/java/io/starter/formats/XLS/{DefaultRowHeight.java => DefaultRowHeight.kt} (56%) delete mode 100644 src/main/java/io/starter/formats/XLS/Dimensions.java create mode 100644 src/main/java/io/starter/formats/XLS/Dimensions.kt delete mode 100644 src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.java create mode 100644 src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.kt rename src/main/java/io/starter/formats/XLS/{Dsf.java => Dsf.kt} (68%) delete mode 100644 src/main/java/io/starter/formats/XLS/Dv.java create mode 100644 src/main/java/io/starter/formats/XLS/Dv.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Dval.java create mode 100644 src/main/java/io/starter/formats/XLS/Dval.kt rename src/main/java/io/starter/formats/XLS/{Eof.java => Eof.kt} (74%) delete mode 100644 src/main/java/io/starter/formats/XLS/ExpressionParser.java create mode 100644 src/main/java/io/starter/formats/XLS/ExpressionParser.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Externname.java create mode 100644 src/main/java/io/starter/formats/XLS/Externname.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Externsheet.java create mode 100644 src/main/java/io/starter/formats/XLS/Externsheet.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Extsst.java create mode 100644 src/main/java/io/starter/formats/XLS/Extsst.kt rename src/main/java/io/starter/formats/XLS/{FeatHeadr.java => FeatHeadr.kt} (56%) create mode 100644 src/main/java/io/starter/formats/XLS/FileLock.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Font.java create mode 100644 src/main/java/io/starter/formats/XLS/Font.kt rename src/main/java/io/starter/formats/XLS/{FontBasis.java => FontBasis.kt} (65%) delete mode 100644 src/main/java/io/starter/formats/XLS/Footerrec.java create mode 100644 src/main/java/io/starter/formats/XLS/Footerrec.kt rename src/main/java/io/starter/formats/XLS/{Format.java => Format.kt} (58%) delete mode 100644 src/main/java/io/starter/formats/XLS/FormatConstants.java create mode 100644 src/main/java/io/starter/formats/XLS/FormatConstants.kt rename src/main/java/io/starter/formats/XLS/{FormatConstantsImpl.java => FormatConstantsImpl.kt} (64%) delete mode 100644 src/main/java/io/starter/formats/XLS/Formula.java create mode 100644 src/main/java/io/starter/formats/XLS/Formula.kt rename src/main/java/io/starter/formats/XLS/{charts/Legendxn.java => FormulaNotFoundException.kt} (70%) delete mode 100644 src/main/java/io/starter/formats/XLS/FunctionNotSupportedException.java create mode 100644 src/main/java/io/starter/formats/XLS/FunctionNotSupportedException.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Guts.java create mode 100644 src/main/java/io/starter/formats/XLS/Guts.kt rename src/main/java/io/starter/formats/XLS/{HCenter.java => HCenter.kt} (62%) delete mode 100644 src/main/java/io/starter/formats/XLS/Headerrec.java create mode 100644 src/main/java/io/starter/formats/XLS/Headerrec.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Hlink.java create mode 100644 src/main/java/io/starter/formats/XLS/Hlink.kt rename src/main/java/io/starter/formats/XLS/{ImageNotFoundException.java => ImageNotFoundException.kt} (62%) delete mode 100644 src/main/java/io/starter/formats/XLS/Index.java create mode 100644 src/main/java/io/starter/formats/XLS/Index.kt create mode 100644 src/main/java/io/starter/formats/XLS/InterfaceHdr.kt delete mode 100644 src/main/java/io/starter/formats/XLS/InvalidRecordException.java rename src/main/java/io/starter/formats/XLS/{charts/ConeChart.java => InvalidRecordException.kt} (66%) rename src/main/java/io/starter/formats/XLS/{Isstinf.java => Isstinf.kt} (66%) delete mode 100644 src/main/java/io/starter/formats/XLS/Label.java create mode 100644 src/main/java/io/starter/formats/XLS/Label.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Labelsst.java create mode 100644 src/main/java/io/starter/formats/XLS/Labelsst.kt rename src/main/java/io/starter/formats/XLS/{LeftMargin.java => LeftMargin.kt} (53%) delete mode 100644 src/main/java/io/starter/formats/XLS/MSODrawing.java create mode 100644 src/main/java/io/starter/formats/XLS/MSODrawing.kt delete mode 100644 src/main/java/io/starter/formats/XLS/MSODrawingConstants.java create mode 100644 src/main/java/io/starter/formats/XLS/MSODrawingConstants.kt delete mode 100644 src/main/java/io/starter/formats/XLS/MSODrawingGroup.java create mode 100644 src/main/java/io/starter/formats/XLS/MSODrawingGroup.kt rename src/main/java/io/starter/formats/XLS/{MSODrawingSelection.java => MSODrawingSelection.kt} (64%) delete mode 100644 src/main/java/io/starter/formats/XLS/Mergedcells.java create mode 100644 src/main/java/io/starter/formats/XLS/Mergedcells.kt rename src/main/java/io/starter/formats/XLS/{Mul.java => Mul.kt} (86%) delete mode 100644 src/main/java/io/starter/formats/XLS/Mulblank.java create mode 100644 src/main/java/io/starter/formats/XLS/Mulblank.kt rename src/main/java/io/starter/formats/XLS/{Mulled.java => Mulled.kt} (84%) delete mode 100644 src/main/java/io/starter/formats/XLS/Mulrk.java create mode 100644 src/main/java/io/starter/formats/XLS/Mulrk.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Name.java create mode 100644 src/main/java/io/starter/formats/XLS/Name.kt rename src/main/java/io/starter/formats/XLS/{NameNotFoundException.java => NameNotFoundException.kt} (65%) rename src/main/java/io/starter/formats/XLS/{NineteenOhFour.java => NineteenOhFour.kt} (60%) delete mode 100644 src/main/java/io/starter/formats/XLS/Note.java create mode 100644 src/main/java/io/starter/formats/XLS/Note.kt delete mode 100644 src/main/java/io/starter/formats/XLS/NumberRec.java create mode 100644 src/main/java/io/starter/formats/XLS/NumberRec.kt delete mode 100644 src/main/java/io/starter/formats/XLS/OOXMLAdapter.java create mode 100644 src/main/java/io/starter/formats/XLS/OOXMLAdapter.kt delete mode 100644 src/main/java/io/starter/formats/XLS/OOXMLReader.java create mode 100644 src/main/java/io/starter/formats/XLS/OOXMLReader.kt delete mode 100644 src/main/java/io/starter/formats/XLS/OOXMLWriter.java create mode 100644 src/main/java/io/starter/formats/XLS/OOXMLWriter.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Obj.java create mode 100644 src/main/java/io/starter/formats/XLS/Obj.kt rename src/main/java/io/starter/formats/XLS/{ObjProtect.java => ObjProtect.kt} (50%) delete mode 100644 src/main/java/io/starter/formats/XLS/Obproj.java rename src/main/java/io/starter/formats/XLS/{FormulaNotFoundException.java => Obproj.kt} (64%) rename src/main/java/io/starter/formats/XLS/{PLV.java => PLV.kt} (83%) rename src/main/java/io/starter/formats/XLS/{Palette.java => Palette.kt} (52%) delete mode 100644 src/main/java/io/starter/formats/XLS/Pane.java create mode 100644 src/main/java/io/starter/formats/XLS/Pane.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Password.java create mode 100644 src/main/java/io/starter/formats/XLS/Password.kt rename src/main/java/io/starter/formats/XLS/{Phonetic.java => Phonetic.kt} (76%) delete mode 100644 src/main/java/io/starter/formats/XLS/PivotCache.java create mode 100644 src/main/java/io/starter/formats/XLS/PivotCache.kt rename src/main/java/io/starter/formats/XLS/{PivotCacheRecord.java => PivotCacheRecord.kt} (88%) rename src/main/java/io/starter/formats/XLS/{PivotTableNotFoundException.java => PivotTableNotFoundException.kt} (62%) rename src/main/java/io/starter/formats/XLS/{Pls.java => Pls.kt} (95%) rename src/main/java/io/starter/formats/XLS/{PrintGrid.java => PrintGrid.kt} (61%) rename src/main/java/io/starter/formats/XLS/{PrintRowCol.java => PrintRowCol.kt} (61%) rename src/main/java/io/starter/formats/XLS/{Prot4rev.java => Prot4rev.kt} (65%) rename src/main/java/io/starter/formats/XLS/{Protect.java => Protect.kt} (54%) rename src/main/java/io/starter/formats/XLS/{ProtectionManager.java => ProtectionManager.kt} (61%) rename src/main/java/io/starter/formats/XLS/{QsiSXTag.java => QsiSXTag.kt} (64%) rename src/main/java/io/starter/formats/XLS/{Ref.java => Ref.kt} (75%) delete mode 100644 src/main/java/io/starter/formats/XLS/ReferenceTracker.java create mode 100644 src/main/java/io/starter/formats/XLS/ReferenceTracker.kt rename src/main/java/io/starter/formats/XLS/{RightMargin.java => RightMargin.kt} (53%) delete mode 100644 src/main/java/io/starter/formats/XLS/Rk.java create mode 100644 src/main/java/io/starter/formats/XLS/Rk.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Row.java create mode 100644 src/main/java/io/starter/formats/XLS/Row.kt rename src/main/java/io/starter/formats/XLS/{RowNotFoundException.java => RowNotFoundException.kt} (65%) rename src/main/java/io/starter/formats/XLS/{InterfaceHdr.java => RrdHead.kt} (78%) rename src/main/java/io/starter/formats/XLS/{FileLock.java => RrdInfo.kt} (78%) rename src/main/java/io/starter/formats/XLS/{Rstring.java => Rstring.kt} (77%) delete mode 100644 src/main/java/io/starter/formats/XLS/SXBool.java create mode 100644 src/main/java/io/starter/formats/XLS/SXBool.kt delete mode 100644 src/main/java/io/starter/formats/XLS/SXDBEx.java create mode 100644 src/main/java/io/starter/formats/XLS/SXDBEx.kt delete mode 100644 src/main/java/io/starter/formats/XLS/SXFDBType.java create mode 100644 src/main/java/io/starter/formats/XLS/SXFDBType.kt delete mode 100644 src/main/java/io/starter/formats/XLS/SXNum.java create mode 100644 src/main/java/io/starter/formats/XLS/SXNum.kt delete mode 100644 src/main/java/io/starter/formats/XLS/SXString.java create mode 100644 src/main/java/io/starter/formats/XLS/SXString.kt rename src/main/java/io/starter/formats/XLS/{ScenProtect.java => ScenProtect.kt} (50%) delete mode 100644 src/main/java/io/starter/formats/XLS/Scl.java create mode 100644 src/main/java/io/starter/formats/XLS/Scl.kt rename src/main/java/io/starter/formats/XLS/{Selection.java => Selection.kt} (51%) delete mode 100644 src/main/java/io/starter/formats/XLS/Setup.java create mode 100644 src/main/java/io/starter/formats/XLS/Setup.kt rename src/main/java/io/starter/formats/XLS/{Sheet.java => Sheet.kt} (61%) delete mode 100644 src/main/java/io/starter/formats/XLS/SheetProtectionManager.java create mode 100644 src/main/java/io/starter/formats/XLS/SheetProtectionManager.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Shrfmla.java create mode 100644 src/main/java/io/starter/formats/XLS/Shrfmla.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Sst.java create mode 100644 src/main/java/io/starter/formats/XLS/Sst.kt rename src/main/java/io/starter/formats/XLS/{StringRec.java => StringRec.kt} (63%) rename src/main/java/io/starter/formats/XLS/{Style.java => Style.kt} (66%) delete mode 100644 src/main/java/io/starter/formats/XLS/Supbook.java create mode 100644 src/main/java/io/starter/formats/XLS/Supbook.kt delete mode 100644 src/main/java/io/starter/formats/XLS/SxAddl.java create mode 100644 src/main/java/io/starter/formats/XLS/SxAddl.kt rename src/main/java/io/starter/formats/XLS/{SxDB.java => SxDB.kt} (53%) rename src/main/java/io/starter/formats/XLS/{SxDBB.java => SxDBB.kt} (61%) rename src/main/java/io/starter/formats/XLS/{SxDI.java => SxDI.kt} (53%) rename src/main/java/io/starter/formats/XLS/{SxEX.java => SxEX.kt} (88%) rename src/main/java/io/starter/formats/XLS/{SxFDB.java => SxFDB.kt} (54%) rename src/main/java/io/starter/formats/XLS/{SxPI.java => SxPI.kt} (53%) delete mode 100644 src/main/java/io/starter/formats/XLS/SxStreamID.java create mode 100644 src/main/java/io/starter/formats/XLS/SxStreamID.kt rename src/main/java/io/starter/formats/XLS/{SxVIEWEX9.java => SxVIEWEX9.kt} (73%) rename src/main/java/io/starter/formats/XLS/{SxVS.java => SxVS.kt} (66%) rename src/main/java/io/starter/formats/XLS/{SxVdEX.java => SxVdEX.kt} (83%) rename src/main/java/io/starter/formats/XLS/{Sxformat.java => Sxformat.kt} (72%) rename src/main/java/io/starter/formats/XLS/{Sxivd.java => Sxivd.kt} (66%) rename src/main/java/io/starter/formats/XLS/{Sxli.java => Sxli.kt} (53%) rename src/main/java/io/starter/formats/XLS/{Sxvd.java => Sxvd.kt} (57%) delete mode 100644 src/main/java/io/starter/formats/XLS/Sxvi.java create mode 100644 src/main/java/io/starter/formats/XLS/Sxvi.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Sxview.java create mode 100644 src/main/java/io/starter/formats/XLS/Sxview.kt delete mode 100644 src/main/java/io/starter/formats/XLS/TabID.java create mode 100644 src/main/java/io/starter/formats/XLS/TabID.kt rename src/main/java/io/starter/formats/XLS/{TableStyles.java => TableStyles.kt} (59%) rename src/main/java/io/starter/formats/XLS/{TopMargin.java => TopMargin.kt} (53%) delete mode 100644 src/main/java/io/starter/formats/XLS/Txo.java create mode 100644 src/main/java/io/starter/formats/XLS/Txo.kt rename src/main/java/io/starter/formats/XLS/{UnencryptedXLSRecord.java => UnencryptedXLSRecord.kt} (76%) delete mode 100644 src/main/java/io/starter/formats/XLS/Unicodestring.java create mode 100644 src/main/java/io/starter/formats/XLS/Unicodestring.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Usersviewbegin.java create mode 100644 src/main/java/io/starter/formats/XLS/Usersviewbegin.kt rename src/main/java/io/starter/formats/XLS/{Usersviewend.java => Usersviewend.kt} (64%) rename src/main/java/io/starter/formats/XLS/{RrdInfo.java => UsrExcl.kt} (81%) rename src/main/java/io/starter/formats/XLS/{VCenter.java => VCenter.kt} (62%) rename src/main/java/io/starter/formats/XLS/{ValidationException.java => ValidationException.kt} (63%) delete mode 100644 src/main/java/io/starter/formats/XLS/Window1.java create mode 100644 src/main/java/io/starter/formats/XLS/Window1.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Window2.java create mode 100644 src/main/java/io/starter/formats/XLS/Window2.kt delete mode 100644 src/main/java/io/starter/formats/XLS/WorkBook.java create mode 100644 src/main/java/io/starter/formats/XLS/WorkBook.kt delete mode 100644 src/main/java/io/starter/formats/XLS/WorkBookAssembler.java create mode 100644 src/main/java/io/starter/formats/XLS/WorkBookAssembler.kt delete mode 100644 src/main/java/io/starter/formats/XLS/WorkBookException.java create mode 100644 src/main/java/io/starter/formats/XLS/WorkBookException.kt delete mode 100644 src/main/java/io/starter/formats/XLS/WorkBookFactory.java create mode 100644 src/main/java/io/starter/formats/XLS/WorkBookFactory.kt rename src/main/java/io/starter/formats/XLS/{WorkSheetNotFoundException.java => WorkSheetNotFoundException.kt} (65%) delete mode 100644 src/main/java/io/starter/formats/XLS/Writeaccess.java create mode 100644 src/main/java/io/starter/formats/XLS/Writeaccess.kt rename src/main/java/io/starter/formats/XLS/{WsBool.java => WsBool.kt} (82%) rename src/main/java/io/starter/formats/XLS/{XLSCellRecord.java => XLSCellRecord.kt} (65%) delete mode 100644 src/main/java/io/starter/formats/XLS/XLSConstants.java create mode 100644 src/main/java/io/starter/formats/XLS/XLSConstants.kt delete mode 100644 src/main/java/io/starter/formats/XLS/XLSRecord.java create mode 100644 src/main/java/io/starter/formats/XLS/XLSRecord.kt delete mode 100644 src/main/java/io/starter/formats/XLS/XLSRecordFactory.java create mode 100644 src/main/java/io/starter/formats/XLS/XLSRecordFactory.kt rename src/main/java/io/starter/formats/XLS/{XLSSubstream.java => XLSSubstream.kt} (69%) delete mode 100644 src/main/java/io/starter/formats/XLS/Xct.java create mode 100644 src/main/java/io/starter/formats/XLS/Xct.kt delete mode 100644 src/main/java/io/starter/formats/XLS/Xf.java create mode 100644 src/main/java/io/starter/formats/XLS/Xf.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Ai.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Ai.kt rename src/main/java/io/starter/formats/XLS/charts/{Fbi.java => AlRuns.kt} (71%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Area.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Area.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Area3DChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Area3DChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/AreaChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/AreaChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/AreaFormat.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/AreaFormat.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/AttachedLabel.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Axcent.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Axcent.kt rename src/main/java/io/starter/formats/XLS/charts/{Axesused.java => Axesused.kt} (70%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Axis.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Axis.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/AxisParent.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/AxisParent.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Bar.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Bar.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Bar3DChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Bar3DChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/BarChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/BarChart.kt rename src/main/java/io/starter/formats/XLS/charts/{Begin.java => Begin.kt} (62%) rename src/main/java/io/starter/formats/XLS/charts/{Boppop.java => Boppop.kt} (65%) rename src/main/java/io/starter/formats/XLS/charts/{BoppopCustom.java => BoppopCustom.kt} (69%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/BubbleChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/BubbleChart.kt rename src/main/java/io/starter/formats/XLS/charts/{CatLab.java => CatLab.kt} (61%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/CatserRange.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/CatserRange.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Chart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Chart.kt rename src/main/java/io/starter/formats/XLS/charts/{Chart3DBarShape.java => Chart3DBarShape.kt} (72%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartAxes.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartAxes.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartConstants.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartConstants.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartFormat.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartFormat.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.kt rename src/main/java/io/starter/formats/XLS/charts/{ChartFrtInfo.java => ChartFrtInfo.kt} (64%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartLine.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartLine.kt rename src/main/java/io/starter/formats/XLS/charts/{ChartObject.java => ChartObject.kt} (79%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartSeries.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartSeries.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartType.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ChartType.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Col3DChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Col3DChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ColChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ColChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ConeBarChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ConeBarChart.kt create mode 100644 src/main/java/io/starter/formats/XLS/charts/ConeChart.kt rename src/main/java/io/starter/formats/XLS/charts/{CrtLayout12.java => CrtLayout12.kt} (67%) rename src/main/java/io/starter/formats/XLS/charts/{CrtLayout12A.java => CrtLayout12A.kt} (68%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/CylinderChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/CylinderChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Dat.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Dat.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/DataFormat.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/DataFormat.kt rename src/main/java/io/starter/formats/XLS/charts/{DataLabExt.java => DataLabExt.kt} (61%) rename src/main/java/io/starter/formats/XLS/charts/{DataLabExtContents.java => DataLabExtContents.kt} (59%) rename src/main/java/io/starter/formats/XLS/charts/{DefaultText.java => DefaultText.kt} (60%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/DoughnutChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/DoughnutChart.kt rename src/main/java/io/starter/formats/XLS/charts/{Dropbar.java => Dropbar.kt} (55%) rename src/main/java/io/starter/formats/XLS/charts/{End.java => End.kt} (62%) rename src/main/java/io/starter/formats/XLS/charts/{EndBlock.java => EndBlock.kt} (54%) rename src/main/java/io/starter/formats/XLS/charts/{EndObject.java => EndObject.kt} (63%) rename src/main/java/io/starter/formats/XLS/charts/{Picf.java => Fbi.kt} (72%) rename src/main/java/io/starter/formats/XLS/charts/{Fontx.java => Fontx.kt} (60%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Frame.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Frame.kt rename src/main/java/io/starter/formats/XLS/charts/{FrtFontList.java => FrtFontList.kt} (74%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/FrtWrapper.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/FrtWrapper.kt rename src/main/java/io/starter/formats/XLS/charts/{GelFrame.java => GelFrame.kt} (51%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/GenericChartObject.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/GenericChartObject.kt rename src/main/java/io/starter/formats/XLS/charts/{Ifmt.java => Ifmt.kt} (64%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Legend.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Legend.kt create mode 100644 src/main/java/io/starter/formats/XLS/charts/Legendxn.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Line.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Line.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Line3DChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Line3DChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/LineChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/LineChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/LineFormat.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/LineFormat.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/MarkerFormat.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/MarkerFormat.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/OOXMLChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ObjectLink.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ObjectLink.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/OfPieChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/OfPieChart.kt rename src/main/java/io/starter/formats/XLS/charts/{Units.java => Picf.kt} (71%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Pie.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Pie.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Pie3dChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Pie3dChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/PieChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/PieChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/PieFormat.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/PieFormat.kt rename src/main/java/io/starter/formats/XLS/charts/{PivotChartBits.java => PivotChartBits.kt} (66%) rename src/main/java/io/starter/formats/XLS/charts/{PivotChartLink.java => PivotChartLink.kt} (63%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/PlotArea.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/PlotArea.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/PlotGrowth.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/PlotGrowth.kt rename src/main/java/io/starter/formats/XLS/charts/{Pos.java => Pos.kt} (61%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.java rename src/main/java/io/starter/formats/{escher/MsofbtSpContainer.java => XLS/charts/PyramidBarChart.kt} (68%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/PyramidChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/PyramidChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Radar.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Radar.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/RadarArea.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/RadarArea.kt rename src/main/java/io/starter/formats/XLS/charts/{RadarAreaChart.java => RadarAreaChart.kt} (54%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/RadarChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/RadarChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/SbaseRef.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/SbaseRef.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Scatter.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Scatter.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ScatterChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ScatterChart.kt rename src/main/java/io/starter/formats/XLS/charts/{SerParent.java => SerParent.kt} (70%) rename src/main/java/io/starter/formats/XLS/charts/{SerToCrt.java => SerToCrt.kt} (61%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/SerauxTrend.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/SerauxTrend.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Serfmt.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Serfmt.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Series.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Series.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/SeriesList.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/SeriesList.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/SeriesText.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/SeriesText.kt rename src/main/java/io/starter/formats/XLS/charts/{ShtProps.java => ShtProps.kt} (77%) rename src/main/java/io/starter/formats/XLS/charts/{SiIndex.java => SiIndex.kt} (82%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/StackedColumn.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/StackedColumn.kt rename src/main/java/io/starter/formats/XLS/charts/{StartBlock.java => StartBlock.kt} (79%) rename src/main/java/io/starter/formats/XLS/charts/{StartObject.java => StartObject.kt} (66%) rename src/main/java/io/starter/formats/XLS/charts/{StockChart.java => StockChart.kt} (53%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Surface.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Surface.kt rename src/main/java/io/starter/formats/XLS/charts/{Surface3DChart.java => Surface3DChart.kt} (53%) rename src/main/java/io/starter/formats/XLS/charts/{SurfaceChart.java => SurfaceChart.kt} (50%) rename src/main/java/io/starter/formats/XLS/charts/{SxViewLink.java => SxViewLink.kt} (64%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/TextDisp.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/TextDisp.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ThreeD.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ThreeD.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/Tick.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/Tick.kt rename src/main/java/io/starter/formats/XLS/charts/{AlRuns.java => Units.kt} (71%) delete mode 100644 src/main/java/io/starter/formats/XLS/charts/ValueRange.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/ValueRange.kt delete mode 100644 src/main/java/io/starter/formats/XLS/charts/YMult.java create mode 100644 src/main/java/io/starter/formats/XLS/charts/YMult.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/CalculationException.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/CalculationException.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/Calculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/Calculator.kt rename src/main/java/io/starter/formats/XLS/formulas/{CircularReferenceException.java => CircularReferenceException.kt} (76%) rename src/main/java/io/starter/formats/XLS/formulas/{DDEExternalCalculator.java => DDEExternalCalculator.kt} (90%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/FormulaParser.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/FormulaParser.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/GenericPtg.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/GenericPtg.kt rename src/main/java/io/starter/formats/XLS/formulas/{IlblListener.java => IlblListener.kt} (82%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.kt rename src/main/java/io/starter/formats/XLS/formulas/{IxtiListener.java => IxtiListener.kt} (85%) rename src/main/java/io/starter/formats/XLS/formulas/{LogicalCalculator.java => LogicalCalculator.kt} (50%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/MathFunctionCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/MathFunctionCalculator.kt rename src/main/java/io/starter/formats/XLS/formulas/{Ptg.java => Ptg.kt} (58%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgAdd.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgAdd.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgArea.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgArea.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgArea3d.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgArea3d.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgArray.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgArray.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgAtr.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgAtr.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgBool.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgBool.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgConcat.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgConcat.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgDiv.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgDiv.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgEQ.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgEQ.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgEndSheet.java => PtgEndSheet.kt} (63%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgErr.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgErr.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgExp.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgExp.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgFunc.java => PtgFunc.kt} (67%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgFuncVar.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgFuncVar.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgGE.java => PtgGE.kt} (51%) rename src/main/java/io/starter/formats/XLS/formulas/{PtgGT.java => PtgGT.kt} (50%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgInt.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgInt.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgIsect.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgIsect.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgLE.java => PtgLE.kt} (51%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgLT.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgLT.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgMemAreaA.java => PtgMemAreaA.kt} (51%) rename src/main/java/io/starter/formats/XLS/formulas/{PtgMemAreaN.java => PtgMemAreaN.kt} (71%) rename src/main/java/io/starter/formats/XLS/formulas/{PtgMemAreaNV.java => PtgMemAreaNV.kt} (75%) rename src/main/java/io/starter/formats/XLS/formulas/{PtgMemErr.java => PtgMemErr.kt} (72%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgMissArg.java => PtgMissArg.kt} (56%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgMlt.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgMlt.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgMystery.java => PtgMystery.kt} (73%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgNE.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgNE.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgName.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgName.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgNameX.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgNameX.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgNumber.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgNumber.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgParen.java => PtgParen.kt} (61%) rename src/main/java/io/starter/formats/XLS/formulas/{PtgPercent.java => PtgPercent.kt} (61%) rename src/main/java/io/starter/formats/XLS/formulas/{PtgPower.java => PtgPower.kt} (54%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgRange.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgRange.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgRef.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgRef.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgRefErr.java => PtgRefErr.kt} (57%) rename src/main/java/io/starter/formats/XLS/formulas/{PtgRefErr3d.java => PtgRefErr3d.kt} (50%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgRefN.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgRefN.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgStr.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgStr.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgSub.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgSub.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.kt rename src/main/java/io/starter/formats/XLS/formulas/{PtgUPlus.java => PtgUPlus.kt} (58%) delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgUnion.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/PtgUnion.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.kt delete mode 100644 src/main/java/io/starter/formats/XLS/formulas/TextCalculator.java create mode 100644 src/main/java/io/starter/formats/XLS/formulas/TextCalculator.kt delete mode 100644 src/main/java/io/starter/formats/XML/UnicodeInputStream.java create mode 100644 src/main/java/io/starter/formats/XML/UnicodeInputStream.kt rename src/main/java/io/starter/formats/cellformat/{CellFormat.java => CellFormat.kt} (81%) delete mode 100644 src/main/java/io/starter/formats/cellformat/CellFormatFactory.java create mode 100644 src/main/java/io/starter/formats/cellformat/CellFormatFactory.kt rename src/main/java/io/starter/formats/cellformat/{DateCellFormat.java => DateCellFormat.kt} (51%) delete mode 100644 src/main/java/io/starter/formats/cellformat/GeneralCellFormat.java create mode 100644 src/main/java/io/starter/formats/cellformat/GeneralCellFormat.kt delete mode 100644 src/main/java/io/starter/formats/cellformat/NumberCellFormat.java create mode 100644 src/main/java/io/starter/formats/cellformat/NumberCellFormat.kt delete mode 100644 src/main/java/io/starter/formats/escher/EscherRecord.java create mode 100644 src/main/java/io/starter/formats/escher/EscherRecord.kt delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtBSE.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtBSE.kt create mode 100644 src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.kt delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtClientAnchor.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtClientAnchor.kt rename src/main/java/io/starter/formats/escher/{MsofbtClientData.java => MsofbtClientData.kt} (69%) delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtDg.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtDg.kt delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtDgContainer.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtDgContainer.kt delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtDgg.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtDgg.kt create mode 100644 src/main/java/io/starter/formats/escher/MsofbtDggContainer.kt delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtOPT.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtOPT.kt delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtSp.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtSp.kt create mode 100644 src/main/java/io/starter/formats/escher/MsofbtSpContainer.kt delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtSpgr.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtSpgr.kt delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.kt delete mode 100644 src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.java create mode 100644 src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.kt rename src/main/java/io/starter/naming/{InitialContextFactoryImpl.java => InitialContextFactoryImpl.kt} (67%) delete mode 100644 src/main/java/io/starter/naming/InitialContextImpl.java create mode 100644 src/main/java/io/starter/naming/InitialContextImpl.kt delete mode 100644 src/main/java/io/starter/naming/NameImpl.java create mode 100644 src/main/java/io/starter/naming/NameImpl.kt rename src/main/java/io/starter/naming/{NameParserImpl.java => NameParserImpl.kt} (73%) rename src/main/java/io/starter/naming/{NamingEnumerationImpl.java => NamingEnumerationImpl.kt} (61%) delete mode 100644 src/main/java/io/starter/toolkit/Base64.java create mode 100644 src/main/java/io/starter/toolkit/Base64.kt rename src/main/java/io/starter/toolkit/{BrowserLauncher.java => BrowserLauncher.kt} (57%) delete mode 100644 src/main/java/io/starter/toolkit/ByteTools.java create mode 100644 src/main/java/io/starter/toolkit/ByteTools.kt rename src/main/java/io/starter/toolkit/{CompatibleBigDecimal.java => CompatibleBigDecimal.kt} (57%) rename src/main/java/io/starter/toolkit/{CompatibleVector.java => CompatibleVector.kt} (52%) rename src/main/java/io/starter/toolkit/{CompatibleVectorHints.java => CompatibleVectorHints.kt} (85%) rename src/main/java/io/starter/toolkit/{DefaultTempFileGeneratorImpl.java => DefaultTempFileGeneratorImpl.kt} (53%) rename src/main/java/io/starter/toolkit/{DelimitedValuesParser.java => DelimitedValuesParser.kt} (54%) rename src/main/java/io/starter/toolkit/{ExceptionListener.java => ExceptionListener.kt} (86%) delete mode 100644 src/main/java/io/starter/toolkit/ExtenClassLoader.java create mode 100644 src/main/java/io/starter/toolkit/ExtenClassLoader.kt delete mode 100644 src/main/java/io/starter/toolkit/FastAddVector.java create mode 100644 src/main/java/io/starter/toolkit/FastAddVector.kt delete mode 100644 src/main/java/io/starter/toolkit/FastGetVector.java create mode 100644 src/main/java/io/starter/toolkit/FastGetVector.kt rename src/main/java/io/starter/toolkit/{FileFilter.java => FileFilter.kt} (68%) delete mode 100644 src/main/java/io/starter/toolkit/GenericRecycleBin.java create mode 100644 src/main/java/io/starter/toolkit/GenericRecycleBin.kt delete mode 100644 src/main/java/io/starter/toolkit/InFile.java create mode 100644 src/main/java/io/starter/toolkit/InFile.kt delete mode 100644 src/main/java/io/starter/toolkit/IndirectOutputStream.java create mode 100644 src/main/java/io/starter/toolkit/IndirectOutputStream.kt delete mode 100644 src/main/java/io/starter/toolkit/JFileWriter.java create mode 100644 src/main/java/io/starter/toolkit/JFileWriter.kt rename src/main/java/io/starter/toolkit/{LogOutputter.java => LogOutputter.kt} (87%) delete mode 100644 src/main/java/io/starter/toolkit/Logger.java create mode 100644 src/main/java/io/starter/toolkit/Logger.kt delete mode 100644 src/main/java/io/starter/toolkit/MD4Digest.java create mode 100644 src/main/java/io/starter/toolkit/MD4Digest.kt rename src/main/java/io/starter/toolkit/{Money.java => Money.kt} (56%) delete mode 100644 src/main/java/io/starter/toolkit/OutFile.java create mode 100644 src/main/java/io/starter/toolkit/OutFile.kt delete mode 100644 src/main/java/io/starter/toolkit/ProgressListener.java rename src/main/java/io/starter/{OpenXLS/DocumentObjectNotFoundException.java => toolkit/ProgressListener.kt} (72%) delete mode 100644 src/main/java/io/starter/toolkit/ProgressNotifier.java rename src/main/java/io/starter/{formats/XLS/UsrExcl.java => toolkit/ProgressNotifier.kt} (78%) rename src/main/java/io/starter/toolkit/{Recyclable.java => Recyclable.kt} (86%) rename src/main/java/io/starter/toolkit/{RecycleBin.java => RecycleBin.kt} (76%) delete mode 100644 src/main/java/io/starter/toolkit/RecycleBinFullException.java rename src/main/java/io/starter/{formats/escher/MsofbtDggContainer.java => toolkit/RecycleBinFullException.kt} (65%) delete mode 100644 src/main/java/io/starter/toolkit/ResourceLoader.java create mode 100644 src/main/java/io/starter/toolkit/ResourceLoader.kt rename src/main/java/io/starter/toolkit/{SpecialArrayList.java => SpecialArrayList.kt} (62%) delete mode 100644 src/main/java/io/starter/toolkit/StringTool.java create mode 100644 src/main/java/io/starter/toolkit/StringTool.kt rename src/main/java/io/starter/toolkit/{TempFileGenerator.java => TempFileGenerator.kt} (78%) rename src/main/java/io/starter/toolkit/{TempFileManager.java => TempFileManager.kt} (51%) delete mode 100644 src/main/java/io/starter/toolkit/WriterOutputStream.java create mode 100644 src/main/java/io/starter/toolkit/WriterOutputStream.kt delete mode 100644 src/main/java/io/starter/toolkit/XLSOptimizedTreeList.java create mode 100644 src/main/java/io/starter/toolkit/XLSOptimizedTreeList.kt delete mode 100644 src/main/java/io/starter/toolkit/XSLConverterTool.java create mode 100644 src/main/java/io/starter/toolkit/XSLConverterTool.kt delete mode 100644 src/test/java/io/starter/OpenXLS/FormulaTest.java create mode 100644 src/test/java/io/starter/OpenXLS/FormulaTest.kt diff --git a/.idea/$PRODUCT_WORKSPACE_FILE$ b/.idea/$PRODUCT_WORKSPACE_FILE$ new file mode 100644 index 0000000..ada569f --- /dev/null +++ b/.idea/$PRODUCT_WORKSPACE_FILE$ @@ -0,0 +1,19 @@ + + + + + + + Kotlin SDK + + + + + + + + \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..5c98b42 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/OpenXLS.iml b/.idea/OpenXLS.iml new file mode 100644 index 0000000..4162f5a --- /dev/null +++ b/.idea/OpenXLS.iml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..493b04d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/docs/samples/Formulas/testFormulas_out.xls b/docs/samples/Formulas/testFormulas_out.xls index 41b74641a66ffae2ee3c20a51cf2e1afa072571c..36db83085d1d7b2ee5cc47351108f773a08e4318 100644 GIT binary patch delta 56 zcmZq3X~@~IfkXSLRqthk`1KCVOneNC3=9nX3@Zy4-8bC6&w-6Wm4OK?l0KuK>$*(c J=Gz>qx&RGg5jp?> delta 56 zcmZq3X~@~IfkWGiQNxb+;64XtCO!s61_lOx1`pO$^Hj}^4r~mn3`}4V_o>}0!s{9~ J-{w%&1pto_4g3HA diff --git a/openxls/.project b/openxls/.project deleted file mode 100644 index f96bc55..0000000 --- a/openxls/.project +++ /dev/null @@ -1,42 +0,0 @@ - - - openxls_experimental - - - - - - org.eclipse.wst.jsdt.core.javascriptValidator - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.wst.common.project.facet.core.builder - - - - - org.eclipse.wst.validation.validationbuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - org.eclipse.jem.workbench.JavaEMFNature - org.eclipse.wst.common.modulecore.ModuleCoreNature - org.eclipse.wst.common.project.facet.core.nature - org.eclipse.jdt.core.javanature - org.eclipse.wst.jsdt.core.jsNature - - diff --git a/openxls/.settings/org.eclipse.wst.common.component b/openxls/.settings/org.eclipse.wst.common.component deleted file mode 100644 index 737d6ff..0000000 --- a/openxls/.settings/org.eclipse.wst.common.component +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/openxls/bin/.project b/openxls/bin/.project deleted file mode 100644 index f96bc55..0000000 --- a/openxls/bin/.project +++ /dev/null @@ -1,42 +0,0 @@ - - - openxls_experimental - - - - - - org.eclipse.wst.jsdt.core.javascriptValidator - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.wst.common.project.facet.core.builder - - - - - org.eclipse.wst.validation.validationbuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - org.eclipse.jem.workbench.JavaEMFNature - org.eclipse.wst.common.modulecore.ModuleCoreNature - org.eclipse.wst.common.project.facet.core.nature - org.eclipse.jdt.core.javanature - org.eclipse.wst.jsdt.core.jsNature - - diff --git a/openxls/bin/.settings/org.eclipse.wst.common.component b/openxls/bin/.settings/org.eclipse.wst.common.component deleted file mode 100644 index 737d6ff..0000000 --- a/openxls/bin/.settings/org.eclipse.wst.common.component +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/pom.xml b/pom.xml index a6437fe..e3b051c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,18 @@ io.starter OpenXLS - 11.0-SNAPSHOT + 11.0.0-SNAPSHOT jar + + scm:git:https://github.com/StarterInc/OpenXLS.git + scm:git:ssh://git@github.com/StarterInc/OpenXLS.git + https://github.com/StarterInc/OpenXLS + OpenXLS-11.0.0 + + OpenXLS Java Spreadsheet SDK - https://sourceforge.net/projects/openxls/ + https://github.com/StarterInc/OpenXLS/ 2002 @@ -20,6 +27,7 @@ 20180813 4.10 nothing-to-exclude + 1.3.50 @@ -35,12 +43,6 @@ http://starter.io - - https://bitbucket.org/openxls/openxls - scm:git:ssh://git@bitbucket.org/openxls/openxls.git - master - - - // dimensions - // + - // series - // data - // ... - this.mychart.getLegend() - .resetPos(chartMetrics.get("y"), chartMetrics - .get("h"), chartMetrics - .get("canvash"), legendCells.length); - } catch (Exception e) { - } - setDimensionsRecord(); - // now add series - boolean hasBubbles = ((bubbleSizeRanges != null - && bubbleSizeRanges.length == valueRanges.length)); - for (int i = valueRanges.length - 1; i >= 0; i--) { - try { - if (!hasBubbles) // usual case - this.addSeriesRange(legendCells[i], categoryRange, valueRanges[i], null, nChart); - else - this.addSeriesRange(legendCells[i], categoryRange, valueRanges[i], bubbleSizeRanges[i], nChart); - } catch (Exception e) { - Logger.logErr("Error adding series: " + e.toString()); - } - } - } - - /** - * Appends a series one row below the last series in the chart. - *

                - * This can be utilized when programmatically adding rows of data that should be - * reflected in the chart.
                - * Legend cell will be incremented by one row if a reference. Category range - * will stay the same. - * - *
                - * In order for this method to work properly the chart must have row-based - * series. If your chart utilizes column-based series, then you need to append a - * category. - * - * @return ChartSeriesHandle representing newly added series - * @see ChartHandle.appendRowCategoryToChart - */ - public ChartSeriesHandle appendRowSeriesToChart() { - return appendRowSeriesToChart(0); // do for default chart (0) - } - - /** - * Appends a series one row below the last series in the chart for the desired - * chart - *

                - * This can be utilized when programmatically adding rows of data that should be - * reflected in the chart.
                - * Legend cell will be incremented by one row if a reference. Category range - * will stay the same. - * - *
                - * In order for this method to work properly the chart must have row-based - * series. If your chart utilizes column-based series, then you need to append a - * category. - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return ChartSeriesHandle representing newly added series - * @see ChartHandle.appendRowCategoryToChart - */ - public ChartSeriesHandle appendRowSeriesToChart(int nChart) { - ChartSeriesHandle[] handles = this.getAllChartSeriesHandles(nChart); - ChartSeriesHandle theHandle = handles[handles.length - 1]; - String legendRef = theHandle.getSeriesLegendReference(); - if (legendRef != null && !legendRef.equals("")) { - String sheetnm = legendRef.substring(0, legendRef.indexOf("!")); - legendRef = legendRef - .substring(legendRef.indexOf("!") + 1); - int[] rc = ExcelTools.getRowColFromString(legendRef); - rc[0] = rc[0] + 1; - legendRef = sheetnm + "!" + ExcelTools.formatLocation(rc); - } else if (legendRef == null) { - legendRef = theHandle.getSeriesLegend(); - } else { - legendRef = ""; - } - String categoryRange = theHandle.getCategoryRange(); - String seriesRange = theHandle.getSeriesRange(); - String sheetnm = seriesRange.substring(0, seriesRange.indexOf("!")); - seriesRange = seriesRange - .substring(seriesRange.indexOf("!") + 1); - int[] rc = ExcelTools.getRangeRowCol(seriesRange); - // fiddle it, since exceltools doesn't translate back/forth - int[] newRc = new int[4]; - newRc[0] = rc[1]; - newRc[1] = rc[0] + 1; - newRc[2] = rc[3]; - newRc[3] = rc[2] + 1; - seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); - try { - return this - .addSeriesRange(legendRef, "", categoryRange, seriesRange, "", nChart); - } catch (CellNotFoundException e) { - Logger.logErr("ChartHandle.appendRowSeriesToChart: Unable to append series to chart: " - + e); - } - return null; - } - - /** - * Append a row of categories to the bottom of the chart.
                - * Expands all Series to include the new bottom row.
                - * To be utilized when expanding a chart to encompass more data that has a - * col-based series. - * - * @see ChartHandle.appendRowSeriesToChart - */ - public void appendRowCategoryToChart() { - appendRowCategoryToChart(0); // default chart - } - - /** - * Append a row of categories to the bottom of the chart.
                - * Expands all Series to include the new bottom row.
                - * To be utilized when expanding a chart to encompass more data that has a - * col-based series. - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @see ChartHandle.appendRowSeriesToChart - */ - public void appendRowCategoryToChart(int nChart) { - ChartSeriesHandle[] handles = this.getAllChartSeriesHandles(nChart); - - for (int i = 0; i < handles.length; i++) { - ChartSeriesHandle theHandle = handles[i]; - // update the series - String seriesRange = theHandle.getSeriesRange(); - String[] s = ExcelTools.stripSheetNameFromRange(seriesRange); - String sheetnm = s[0]; - seriesRange = s[1]; - // String sheetnm = seriesRange.substring(0, - // seriesRange.indexOf("!")); - // seriesRange = seriesRange.substring( - // seriesRange.indexOf("!")+1, - // seriesRange.length()); - // Strip 2nd sheet ref, if any 20080213 KSC - // int n= seriesRange.indexOf('!'); - // int m= seriesRange.indexOf(':'); - // seriesRange= seriesRange.substring(0, m+1) + - // seriesRange.substring(n+1); - - int[] rc = ExcelTools.getRangeRowCol(seriesRange); - int[] newRc = new int[4]; - newRc[0] = rc[1]; - newRc[1] = rc[0]; - newRc[2] = rc[3]; - newRc[3] = rc[2] + 1; - seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); - theHandle.setSeriesRange(seriesRange); - // update the category - seriesRange = theHandle.getCategoryRange(); - s = ExcelTools.stripSheetNameFromRange(seriesRange); - sheetnm = s[0]; - seriesRange = s[1]; - /* - * sheetnm = seriesRange.substring(0, - * seriesRange.indexOf("!")); seriesRange = - * seriesRange.substring( seriesRange.indexOf("!")+1, - * seriesRange.length()); // - * Strip 2nd sheet ref, if any 20080213 KSC n= - * seriesRange.indexOf('!'); m= - * seriesRange.indexOf(':'); seriesRange= - * seriesRange.substring(0, m+1) + - * seriesRange.substring(n+1); - */ - rc = ExcelTools.getRangeRowCol(seriesRange); - newRc = new int[4]; - newRc[0] = rc[1]; - newRc[1] = rc[0]; - newRc[2] = rc[3]; - newRc[3] = rc[2] + 1; - seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc); - theHandle.setCategoryRange(seriesRange); - } - } - - /* - * NOT IMPLEMENTED YET adjust chart cell references upon row - * insertion or - * deletion NOTE: Assumes we're on the correct sheet NOT - * COMPLETELY IMPLEMENTED - * YEt - * - * @param rownum - * - * @param shiftamt +1= insert row, -1= delete row - * - * public void adjustCellRefs(int rownum, int shiftamt) { - * Vector v = - * mychart.getAllSeries(); boolean bSeriesRows= false; - * boolean bMod= false; for - * (int i=0;i 0) { // - * insert row i.e. - * shift ai location down if ((loc.length==2 && - * loc[0]>=rownum) || - * (loc[0]>=rownum || loc[2]>=rownum)) { - * adjustAiLocation(ai, - * p[0].getIntLocation(), shiftamt); bMod= true; } } else { - * if ((loc.length==2 - * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) - * { // remove it - * this.removeSeries(i); bMod= true; continue; } } } - * catch(Exception e) { - * - * } } catch (Exception e) { - * - * } try { // CATEGORY Ai ai= s.getCategoryValueAi(); Ptg[] - * p=ai.getCellRangePtgs(); try { int[] loc= - * p[0].getIntLocation(); if (shiftamt - * > 0) { // insert row i.e. shift ai location down if - * ((loc.length==2 && - * loc[0]>=rownum) || (loc[0]>=rownum || loc[2]>=rownum)) { - * adjustAiLocation(ai, - * p[0].getIntLocation(), shiftamt); bMod= true; } } else { - * if ((loc.length==2 - * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) - * { // remove it ???? - * this.removeSeries(i); bMod= true; } } } catch(Exception - * e) { - * - * } } catch (Exception e) { - * - * } try { // LEGEND Ai ai= s.getLegendAi(); Ptg[] - * p=ai.getCellRangePtgs(); - * int[] loc= p[0].getIntLocation(); if (shiftamt > 0) { // - * insert row i.e. - * shift ai location down if ((loc.length==2 && - * loc[0]>=rownum) || - * (loc[0]>=rownum || loc[2]>=rownum)) { - * adjustAiLocation(ai, - * p[0].getIntLocation(), shiftamt); bMod= true; } } else { - * if ((loc.length==2 - * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) - * { // remove it - * this.removeSeries(i); bMod= true; } } } catch(Exception - * e) { - * - * } } if (bMod)// one or more series elements were modified - * setDimensionsRecord(); // update dimensions i.e. Data - * Range - * - * } - */ - - /* - * doesn't appear to be used right now move the ai location - * (row) according to - * shift amount - * - * @param ai - * - * @param loc - * - * @param shift - * - * private void adjustAiLocation(Ai ai, int[] loc, int - * shift) { String oldloc= - * ExcelTools.formatLocation(loc); if (loc.length>2) // get - * 2nd part of range - * oldloc += ":" + ExcelTools.formatLocation(new - * int[]{loc[2], loc[3]}); oldloc= - * this.getSheet().getSheetName() + "!" + oldloc; if - * (loc.length==2)// single - * cell loc[0]+=shift; else { // range loc[0]+=shift; - * loc[2]+=shift; } String - * newloc= ExcelTools.formatLocation(loc); if (loc.length>2) - * // get 2nd part of - * range newloc += ":" + ExcelTools.formatLocation(new - * int[]{loc[2], loc[3]}); - * ai.changeAiLocation(oldloc, newloc); } - * - */ - - /** - * Get the Chart's bytes - *

                - * This is an internal method that is not useful to the end user. - */ - public byte[] getChartBytes() { - return mychart.getChartBytes(); - } - - public byte[] getSerialBytes() { - return mychart.getSerialBytes(); - } - - /** - * get the chart-type-specific options in XML form - * - * @return String XML - */ - private String getChartOptionsXML() { - return mychart.getChartOptionsXML(0); // 0 for default chart - } - - private String t(int n) { - String tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t"; - return (tabs.substring(0, n)); - } - - /** - * given a XmlPullParser positioned at the chart element, parse all chart - * elements to create desired chart - * - * @param sht WorkSheetHandle - * @param xpp XmlPullParser - */ - public void parseXML(WorkSheetHandle sht, XmlPullParser xpp, HashMap maps) { - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ChartFonts")) { - for (int x = 0; x < xpp.getAttributeCount(); x++) { - this.setChartFont(xpp.getAttributeName(x), xpp - .getAttributeValue(x)); - } - } else if (tnm.equals("ChartFontRec")) { - String fName = ""; - int fontId = 0, fSize = 0, fWeight = 0, fColor = 0, - fUnderline = 0; - boolean bIsBold = false; - for (int x = 0; x < xpp.getAttributeCount(); x++) { - String attr = xpp.getAttributeName(x); - String val = xpp.getAttributeValue(x); - if (attr.equals("name")) { - fName = val; - } else if (attr.equals("id")) { - fontId = Integer.parseInt(val); - } else if (attr.equals("size")) { - fSize = Font.PointsToFontHeight(Double - .parseDouble(val)); - } else if (attr.equals("color")) { - fColor = FormatHandle - .HexStringToColorInt(val, FormatHandle.colorFONT); - if (fColor == 0) - fColor = 32767; // necessary? - } else if (attr.equals("weight")) { - fWeight = Integer.parseInt(val); - } else if (attr.equals("bold")) { - bIsBold = true; - } else if (attr.equals("underline")) { - fUnderline = Integer.parseInt(val); - } - } - while (this.getWorkBook().getNumFonts() < fontId - 1) { - this.getWorkBook().insertFont(new Font("Arial", - FormatConstants.PLAIN, 10)); - } - if (this.getWorkBook().getNumFonts() < fontId) { - Font f = new Font(fName, fWeight, fSize); - f.setColor(fColor); - f.setBold(bIsBold); - f.setUnderlineStyle((byte) fUnderline); - this.getWorkBook().insertFont(f); - } else { // TODO: this will screw up linked fonts, - // perhaps, so what to do? - Font f = this.getWorkBook().getFont(fontId); - f.setFontWeight(fWeight); - f.setFontName(fName); - f.setFontHeight(fSize); - f.setColor(fColor); - f.setBold(bIsBold); - f.setUnderlineStyle((byte) fUnderline); - } - } else if (tnm.equals("FormatChartArea")) { // TODO: - // something! - // ChartBorder - // ChartProperties - } else if (tnm.equals("Series")) { // series --> - // Legend Range Category shape typex typey - String legend = "", series = "", category = "", - bubble = ""; - String dataTypeX = "", dataTypeY = ""; - String shape = ""; - for (int x = 0; x < xpp.getAttributeCount(); x++) { - if (xpp.getAttributeName(x) - .equalsIgnoreCase("Legend")) - legend = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Range")) - series = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Category")) - category = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Bubbles")) - bubble = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("TypeX")) - dataTypeX = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("TypeY")) - dataTypeY = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Shape")) - shape = xpp.getAttributeValue(x); - } - // 20070709 KSC: can't add until all cells are added - // ch.addSeriesRange(legend, category, series); - String[] s = {legend, series, category, bubble, - dataTypeX, dataTypeY, shape}; - HashMap map = (HashMap) maps - .get("chartseries"); - map.put(s, this); - } else if (tnm.equals("ChartOptions")) { // handle - // chart-type-specific - // options such - // as legend - // options - // 20070716 KSC: handle chart-type-specific options in a - // very generic way ... - for (int x = 0; x < xpp.getAttributeCount(); x++) { - this.setChartOption(xpp.getAttributeName(x), xpp - .getAttributeValue(x)); - } - } else if (tnm.equals("ThreeD")) { // handle three-d options - // handle threeD record options in a very generic way - // ... - this.make3D(); // default chart - TODO; if mutliple - // charts, handle - for (int x = 0; x < xpp.getAttributeCount(); x++) { // now - // add - // threed - // rec - // options - this.setChartOption(xpp.getAttributeName(x), xpp - .getAttributeValue(x)); - } - } else if (tnm.endsWith("Axis")) { // handle axis specs - // (Label + options ...) - // 20070720 KSC: handle Axis record options ... - int type = 0; - String axis = xpp.getName(); - if (axis.equalsIgnoreCase("XAxis")) - type = XAXIS; - else if (axis.equalsIgnoreCase("YAxis")) - type = YAXIS; - else if (axis.equalsIgnoreCase("ZAxis")) - type = ZAXIS; - if (xpp.getAttributeCount() > 0) { // then has axis - // options - for (int x = 0; x < xpp.getAttributeCount(); x++) { - this.setAxisOption(type, xpp - .getAttributeName(x), xpp - .getAttributeValue(x)); - } - } else { // no axis options means no axis present; - // remove - this.removeAxis(type); - } - } else if (tnm.equals("Series")) { // handle series data - // Legend Range Category - String legend = "", series = "", category = "", - bubble = ""; - String dataTypeX = "", dataTypeY = ""; - String shape = ""; - for (int x = 0; x < xpp.getAttributeCount(); x++) { - if (xpp.getAttributeName(x) - .equalsIgnoreCase("Legend")) - legend = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Range")) - series = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Category")) - category = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Bubbles")) - bubble = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("TypeX")) - dataTypeX = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("TypeY")) - dataTypeY = xpp.getAttributeValue(x); - else if (xpp.getAttributeName(x) - .equalsIgnoreCase("Shape")) - shape = xpp.getAttributeValue(x); - } - // 20070709 KSC: can't add until all cells are added - // ch.addSeriesRange(legend, category, series); - String[] s = {legend, series, category, bubble, - dataTypeX, dataTypeY, shape}; - HashMap map = (HashMap) maps - .get("chartseries"); - map.put(s, this); - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals("Chart")) - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logWarn("ChartHandle.parseXML <" + xpp.getName() + ">: " - + e.toString()); - // TODO: propogate Exception??? - } - } - - /** - * returns an XML representation of this chart - * - * @return String XML - */ - public String getXML() { - StringBuffer sb = new StringBuffer(t(1) + "\n"); - - // Chart Fonts - sb.append(t(2) + "" + this.getChartFontRecsXML()); - sb.append("\n" + t(2) + "\n"); - sb.append(t(2) + "\n"); - // Format Chart Area - sb.append(t(2) + "\n"); - sb.append(t(3) + "\n"); // KSC: TODO: BORDER - sb.append(t(3) + "\n"); // TODO: - // Properties - sb.append(t(2) + "\n"); - // Source Data - sb.append(t(2) + "\n"); - ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); - for (int i = 0; i < series.length; i++) { - sb.append(t(3) + "\n"); - } - sb.append(t(2) + "\n"); - // Chart Options - sb.append(t(2) + "\n"); - // Axis Options - sb.append(t(2) + "\n"); - sb.append(t(3) + "\n"); - sb.append(t(3) + "\n"); - sb.append(t(3) + "\n"); - sb.append(t(2) + "\n"); - // ThreeD rec opts - if (this.isThreeD()) { - sb.append(t(2) + "\n"); - } - - sb.append(t(1) + "\n"); - return sb.toString(); - } - - /******************************************************************** - * OOXML Generation Methods - **********************************************************************/ - /** - * Generates OOXML (chartML) for this chart object. - * - *
                - * NOTE: necessary root chartSpace element + namespaces are not set here - * - * @param int rId -reference ID for this chart - * @return String representing the OOXML describing this Chart - */ - public String getOOXML(int rId) { - // TODO: finish 3d options- floor, sideWall, backWall - // TODO: finish axis options - // TODO: printSettings - - // generate OOXML (chartML) - StringBuffer cooxml = new StringBuffer(); - try { - mychart.getChartSeries().resetSeriesNumber(); // reset series idx - // retrieve pertinent chart data - // axes id's TODO: HANDLE MULTIPLE AXES per chart ... - String catAxisId = Integer - .toString((int) (Math.random() * 1000000)); - String valAxisId = Integer - .toString((int) (Math.random() * 1000000)); - String serAxisId = Integer - .toString((int) (Math.random() * 1000000)); - OOXMLChart thischart; - if ((mychart instanceof OOXMLChart)) - thischart = (OOXMLChart) mychart; - else { // XLS->XLSX - thischart = new OOXMLChart(mychart, wbh); - mychart = thischart; - thischart.getChartSeries().setParentChart(thischart); - } - thischart.wbh = this.wbh; - - cooxml.append(thischart.getOOXML(catAxisId, valAxisId, serAxisId)); - - // TODO: - ArrayList chartEmbeds = thischart.getChartEmbeds(); - if (chartEmbeds != null) { - int j = 0; - for (int i = 0; i < chartEmbeds.size(); i++) { - if (((String[]) chartEmbeds.get(i))[0] - .equals("userShape")) { - j++; - cooxml.append(""); - } - } - } - } catch (Exception e) { - Logger.logErr("ChartHandle.getOOXML: error generating OOXML. Chart not created: " - + e.toString()); - } - return cooxml.toString(); - } - - /** - * generates the OOXML specific for DrawingML, specifying offsets and - * identifying the chart object.
                - * this Drawing ML (OOXML) is distinct from Chart ML (OOXML) which actually - * defines the chart object including series, categories and axes
                - * This is an internal method that is not useful to the end user. - * - * @param int id - the reference id for this chart - * @return String OOXML - */ - public String getChartDrawingOOXML(int id) { - TwoCellAnchor t = new TwoCellAnchor( - ((OOXMLChart) mychart).getEditMovement()); - t.setAsChart(id, OOXMLAdapter.stripNonAscii(this.getOOXMLName()) - .toString(), TwoCellAnchor.convertBoundsFromBIFF8(this - .getSheet(), mychart.getBounds())); // adjust BIFF8 - // bounds to - // OOXML units - return t.getOOXML(); - } - - /******************************************************************************** - * Parsing OOXML Methods - **********************************************************************/ - /** - * defines this chart object based on a Chart ML (OOXML) input Stream (root - * element=c:chartSpace)
                - * This is an internal method that is not useful to the end user. - * - * @param inputStream ii - representing chart OOXML - */ - public void parseOOXML(java.io.InputStream ii) { - // overlay in title, after layout - // varyColors val= "0" -- after grouping and before ser - - // series colors by theme: - /* - * accent1 - 6 - */ - /** - * chartSpace: chart (Chart) §5.7.2.27 clrMapOvr (Color Map Override) §5.7.2.30 - * date1904 (1904 Date System) §5.7.2.38 externalData (External Data - * Relationship) §5.7.2.63 extLst (Chart Extensibility) §5.7.2.64 lang (Editing - * Language) §5.7.2.87 pivotSource (Pivot Source) §5.7.2.145 printSettings - * (Print Settings) §5.7.2.149 protection (Protection) §5.7.2.150 roundedCorners - * (Rounded Corners) §5.7.2.160 spPr (Shape Properties) §5.7.2.198 style (Style) - * §5.7.2.203 txPr (Text Properties) §5.7.2.217 userShapes (Reference to Chart - * Drawing Part) §5.7.2.222 - */ - try { - OOXMLChart thischart = (OOXMLChart) mychart; - int drawingOrder = 0; // drawing order of the chart (0=default, 1-9 - // for multiple charts in 1) - boolean hasPivotTableSource = false; - - // remove any undesired formatting from default chart: - this.setTitle(""); // clear any previously set - mychart.getAxes().setPlotAreaBgColor(FormatConstants.COLOR_WHITE); - mychart.getAxes().setPlotAreaBorder(-1, -1); // remove plot area - // border - - java.util.Stack lastTag = new java.util.Stack(); // keep - // track - // of - // element - // hierarchy - - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); // main entry= chartSpace, - // children: lang, chart - lastTag.push(tnm); // keep track of element hierarchy - if (tnm.equals("chart")) { // beginning of DrawingML for a - // single image or chart; - // children: title, - // plotArea, legend, - /** - * 5 - * 6 - * 7 - * 8 - * 9 - * 10 - * 11 - * 12 - * - * plotArea contains layout, , serAx, valAx, catAx, dateAx, spPr, - * dTable 13 - * 14 - * - * 15 - * 16 - * 17 - * - * 18 - */ - } else if (tnm.equals("lang")) { - thischart.lang = xpp.getAttributeValue(0); - } else if (tnm.equals("roundedCorners")) { - thischart.roundedCorners = xpp.getAttributeValue(0) - .equals("1"); - } else if (tnm.equals("pivotSource")) { // has a pivot table - hasPivotTableSource = true; - } else if (tnm.equals("view3D")) { - ThreeD.parseOOXML(xpp, lastTag, thischart); - } else if (tnm.equals("layout")) { - thischart.plotAreaLayout = (Layout) Layout - .parseOOXML(xpp, lastTag).cloneElement(); - } else if (tnm.equals("legend")) { - thischart.showLegend(true, false); - thischart.ooxmlLegend = (io.starter.formats.OOXML.Legend) io.starter.formats.OOXML.Legend - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement(); - thischart.ooxmlLegend - .fill2003Legend(thischart.getLegend()); - // Parse actual CHART TYPE element (barChart, pieChart, - // etc.) - } else if (tnm - .equals(OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.LINECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.PIECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.AREACHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SCATTERCHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.RADARCHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SURFACECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.DOUGHNUTCHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BUBBLECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.OFPIECHART]) - || tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.STOCKCHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.BARCHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.LINECHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.PIECHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.AREACHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SCATTERCHART]) - || tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART])) { // specific - // chart - // type- - ChartType - .parseOOXML(xpp, this.wbh, mychart, drawingOrder++); - lastTag.pop(); - } else if (tnm.equals("title")) { - thischart.setOOXMLTitle((Title) Title - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement(), this.wbh); - this.setTitle(thischart.getOOXMLTitle().getTitle()); - } else if (tnm.equals("spPr")) { // shape properties -- can - // be for plot area or - // chart space - String parent = lastTag.get(lastTag.size() - 2); - if (parent.equals("plotArea")) { - thischart.setSpPr(0, (SpPr) SpPr - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement()); - } else if (parent.equals("chartSpace")) { - thischart.setSpPr(1, (SpPr) SpPr - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement()); - } - } else if (tnm.equals("txPr")) { // text formatting - thischart.setTxPr((TxPr) TxPr - .parseOOXML(xpp, lastTag, this.wbh) - .cloneElement()); - } else if (tnm.equals("catAx")) { // child of plotArea - mychart.getAxes() - .parseOOXML(XAXIS, xpp, tnm, lastTag, this.wbh); - } else if (tnm.equals("valAx")) { // child of plotArea - if (mychart.getAxes().hasAxis(ChartConstants.XAXIS)) // usual, - // have - // a - // catAx - // then - // a - // valAx - mychart.getAxes() - .parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); - else if (mychart.getAxes() - .hasAxis(ChartConstants.YAXIS)) // for bubble - // charts, has - // two valAxes - // and no - // catAx - mychart.getAxes() - .parseOOXML(ChartConstants.XVALAXIS, xpp, tnm, lastTag, this.wbh); - else // 2nd val axis is Y axis - mychart.getAxes() - .parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.wbh); - } else if (tnm.equals("serAx")) { // series axis - 3d charts - mychart.getAxes() - .parseOOXML(ZAXIS, xpp, tnm, lastTag, this.wbh); - } else if (tnm.equals("dateAx")) { // TODO: not finished: - // figure out! - // ?? - } - - } else if (eventType == XmlPullParser.END_TAG) { - lastTag.pop(); - String endTag = xpp.getName(); - if (endTag.equals("chartSpace")) { - setDimensionsRecord(); - break; // done processing - } - } - if (xpp.getEventType() != XmlPullParser.END_DOCUMENT) - eventType = xpp.next(); - else - eventType = XmlPullParser.END_DOCUMENT; - } - } catch (Exception e) { - Logger.logErr("ChartHandle.parseChartOOXML: " + e.toString()); - } - } - - /** - * Specifies how to resize or move this Chart upon edit
                - * This is an internal method that is not useful to the end user.
                - * Excel 7/OOXML specific - * - * @param editMovement String OOXML-specific edit movement setting - */ - public void setEditMovement(String editMovement) { - ((OOXMLChart) mychart).setEditMovement(editMovement); - } - - /** - * return the Excel 7/OOXML-specific name for this chart - * - * @return String OOXML name - */ - private String getOOXMLName() { - return ((OOXMLChart) mychart).getOOXMLName(); - } - - /** - * set the Excel 7/OOXML-specific name for this chart - * - * @param String name - */ - public void setOOXMLName(String name) { - ((OOXMLChart) mychart).setOOXMLName(name); - } - - /** - * returns the drawingml file name which defines the userShape (if any)
                - * a userShape is a drawing or shape ontop of a chart associated with this chart - * - * @return - */ - public ArrayList getChartEmbeds() { - return ((OOXMLChart) mychart).getChartEmbeds(); - } - - /** - * sets external information linked to or "embedded" in this OOXML chart; can be - * a chart user shape, an image ...
                - * NOTE: a userShape is a drawingml file name which defines the userShape (if - * any)
                - * a userShape is a drawing or shape ontop of a chart - * - * @param String[] embedType, filename e.g. {"userShape", "userShape file name"} - */ - public void addChartEmbed(String[] ce) { - ((OOXMLChart) mychart).addChartEmbed(ce); - } - - /** - * set the chart DIMENSIONS record based on the series ranges in the chart - * APPEARS THAT for charts, the DIMENSIONS record merely notes the range of - * values: 0, #points in series, 0, #series - */ - protected void setDimensionsRecord() { - ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); - int nSeries = series.length; - int nPoints = 0; - for (int i = 0; i < series.length; i++) { - try { - int[] coords = ExcelTools - .getRangeCoords(series[i].getSeriesRange()); - if (coords[3] > coords[1]) - nPoints = Math.max(nPoints, coords[3] - coords[1] + 1); // c1-c0 - else - nPoints = Math.max(nPoints, coords[2] - coords[0] + 1); // r1-r0 - } catch (Exception e) { - } - } - mychart.setDimensionsRecord(0, nPoints, 0, nSeries); - } - - /** - * Method for setting Chart-Type-specific options in a generic fashion e.g. - * charthandle.setChartOption("Stacked", "true"); - *

                - * Note: since most Chart Type Options are interdependent, there are several - * makeXX methods that set the desired group of options e.g. makeStacked(); use - * setChartOption with care - *

                - * Note that not all Chart Types will have every option available - *

                - * Possible Options: - *

                - * "Stacked" - true or false - set Chart Series to be Stacked
                - * "Cluster" - true or false - set Clustered for Column and Bar Chart Types
                - * "PercentageDisplay" - true or false - Each Category is broken down as a - * percentge
                - * "Percentage" - Distance of pie slice from center of pie as % for Pie Charts - * (0 for all others)
                - * "donutSize" - Donut size for Donut Charts Only
                - * "Overlap" - Space between bars (default= 0%)
                - * "Gap" - Space between categories (%) (default=50%)
                - * "SmoothedLine" - true or false - the Line series has a smoothed line
                - * "AnRot" - Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for others - * (3D option)
                - * "AnElev" - Elevation Angle (-90 to 90 degrees) (15 is default) (3D option) - *
                - * "ThreeDScaling" - true or false - 3d effect
                - * "TwoDWalls" - true if 2D walls (3D option)
                - * "PcDist" - Distance from eye to chart (0 to 100) (30 is default) (3D option) - *
                - * "ThreeDBubbles" - true or false - Draw bubbles with a 3d effect
                - * "ShowLdrLines" - true or false - Show Pie and Donut charts Leader Lines
                - * "MarkerFormat" - "0" thru "9" for various marker options @see - * ChartHandle.setMarkerFormat
                - * "ShowLabel" - true or false - show Series/Data Label
                - * "ShowCatLabel" - true or false - show Category Label
                - * "ShowLabelPct" - true or false - show percentage labels for Pie charts
                - * "ShowBubbleSizes" - true or false - show bubble sizes for Bubble charts - *

                - * NOTE: all values must be in String form - * - * @see ChartHandle.getXML - */ - public void setChartOption(String op, String val) { - mychart.setChartOption(op, val); - } - - /** - * Method for setting Chart-Type-specific options in a generic fashion e.g. - * charthandle.setChartOption("Stacked", "true"); - * - * @param op - * @param val - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - */ - private void setChartOption(String op, String val, int nChart) { - mychart.setChartOption(op, val, nChart); - } - - /** - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return true if Chart has 3D effects, false otherwise - */ - public boolean isThreeD(int nChart) { - return mychart.isThreeD(nChart); - } - - /** - * @return true if Chart has 3D effects, false otherwise - */ - public boolean isThreeD() { - return mychart.isThreeD(0); // default chart - } - - /** - * @return boolean true if Chart contains Stacked Series, false otherwise - */ - public boolean isStacked() { - return mychart.isStacked(0); // default chart - } - - /** - * @return boolean true if Chart is of type 100% Stacked, false otherwise - */ - public boolean is100PercentStacked() { - return mychart.is100PercentStacked(0); // default chart - } - - /** - * @return boolean true if Chart contains Clustered Bars or Columns, false - * otherwise - */ - public boolean isClustered() { - return mychart.isClustered(0); // default chart - } - - /** - * @return String ThreeD options in XML form - */ - public String getThreeDXML() { - return mychart.getThreeDXML(0); // 0 for default chart - } - - /** - * Make chart 3D if not already - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @return ThreeD rec - */ - ThreeD initThreeD(int nChart) { - return mychart.initThreeD(nChart, this.getChartType(nChart)); - } - - /** - * @param int Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) - * @return String XML representation of the desired Axis - */ - private String getAxisOptionsXML(int Axis) { - return mychart.getAxes().getAxisOptionsXML(Axis); - } - - /** - * Returns the Axis Label Placement or position as an int - *

                - * One of:
                - * Axis.INVISIBLE - axis is hidden
                - * Axis.LOW - low end of plot area
                - * Axis.HIGH - high end of plot area
                - * Axis.NEXTTO- next to axis (default) - * - * @param int Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) - * @return int - one of the Axis Label placement constants above - */ - public int getAxisPlacement(int Axis) { - return mychart.getAxes().getAxisPlacement(Axis); - } - - /** - * Sets the Axis labels position or placement to the desired value (these match - * Excel placement options) - *

                - * Possible options:
                - * Axis.INVISIBLE - hides the axis
                - * Axis.LOW - low end of plot area
                - * Axis.HIGH - high end of plot area
                - * Axis.NEXTTO- next to axis (default) - * - * @param int Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) - * @param Placement - int one of the Axis placement constants listed above - */ - public void setAxisPlacement(int Axis, int Placement) { - mychart.getAxes().setAxisPlacement(Axis, Placement); - mychart.setDirtyFlag(true); - } - - /* - * returns the desired axis If bCreateIfNecessary, will - * creates if doesn't exist - * - * @return Axis Object / private Axis getAxis(int axisType, - * boolean - * bCreateIfNecessary) { return mychart.getAxis(axisType, - * bCreateIfNecessary); } - */ - - /** - * removes the desired Axis from the Chart - * - * @param int axisType - one of the Axis constants (XAXIS, YAXIS or ZAXIS) - */ - public void removeAxis(int axisType) { - mychart.getAxes().removeAxis(axisType); - mychart.setDirtyFlag(true); - } - - /** - * returns Chart-specific Font Records in XML form - * - * @return String Chart Font information in XML format - */ - public String getChartFontRecsXML() { - return mychart.getChartFontRecsXML(); - } - - /** - * Return non-axis Chart font ids in XML form - * - * @return String Font information in XML format - */ - public String getChartFontsXML() { - return mychart.getChartFontsXML(); - } - - /** - * Set non-axis chart font id for title, default, etc
                - * For Internal Use Only - * - * @param String type - font type - * @param String val - font id - */ - public void setChartFont(String type, String val) { - mychart.setChartFont(type, val); - } - - /** - * @return the WorkBook Object attached to this Chart - */ - public io.starter.formats.XLS.WorkBook getWorkBook() { - return mychart.getWorkBook(); - } - - public WorkBookHandle getWorkBookHandle() { - return this.wbh; - } - - public WorkSheetHandle getWorkSheetHandle() { - try { - return this.wbh.getWorkSheet(mychart.getSheet().getSheetNum()); - } catch (WorkSheetNotFoundException e) { - // this should be impossible - throw new RuntimeException(e); - } - } - - /** - * sets the coordinates or bounds (position, width and height) of this chart in - * pixels - * - * @param short[4] bounds - left or x value, top or y value, width, height - * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT - */ - // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and - // Coords are very - // distinct - public void setBounds(short[] bounds) { - mychart.setCoords(bounds); - } - - /** - * returns the coordinates or bounds (position, width and height) of this chart - * in pixels - * - * @return short[4] bounds - left or x value, top or y value, width, height - * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT - */ - // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and - // Coords are very - // distinct - public short[] getBounds() { - return mychart.getCoords(); - } - - /** - * sets the coordinates (position, width and height) for this chart in Excel - * size units - * - * @return short[4] pixel coords - left or x value, top or y value, width, - * height - * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT - */ - public void setCoords(short[] coords) { - mychart.setCoords(coords); - } - - /** - * returns the coordinates (position, width and height) of this chart in Excel - * size units - * - * @return short[4] pixel coords - left or x value, top or y value, width, - * height - * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT - */ - public short[] getCoords() { - mychart.getMetrics(this.wbh); - return mychart.getCoords(); - } - - /** - * get the bounds of the chart using coordinates relative to row/cols and their - * offsets - * - * @return short[8] bounds - COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, - * ROW1, ROWOFFSET1 - */ - public short[] getRelativeBounds() { - return mychart.getBounds(); - } - - /** - * returns the offset within the column in pixels - * - * @return - */ - public short getColOffset() { - return mychart.getColOffset(); - } - - /** - * sets the bounds of the chart using coordinates relative to row/cols and their - * offsets - * - * @param short[8] bounds - COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, - * ROWOFFSET1 - */ - public void setRelativeBounds(short[] bounds) { - mychart.setBounds(bounds); - } - - // 20070802 KSC: Debug uility to write out chartrecs - /* - * For internal debugging use only - * - * public void writeChartRecs(String fName) { - * mychart.writeChartRecs(fName); } - * - * /** set DataLabels option for this chart - * - * @param String type - see below - * - * @param boolean bShowLegendKey - true if show legend key, - * false otherwise - *

                possible String type values:
                Series
                Category - *
                Value - *
                Percentage (Only for Pie Charts)
                Bubble (Only for - * Bubble Charts) - *
                X Value (Only for Bubble Charts)
                Y Value (Only - * for Bubble Charts) - *
                CandP - * - *

                NOTE: not 100% implemented at this time - */ - public void setDataLabel(String/* [] */ type, boolean bShowLegendKey) { - /* - * for now, only 1 option is valid - multiple legend - * settings e.g. Category and - * Value are not figured out yet for (int i= 0; i < - * type.length; i++) - * mychart.setChartOption("DataLabel", type[i]); - */ - if (!bShowLegendKey) - mychart.setChartOption("DataLabel", type); - else - mychart.setChartOption("DataLabelWithLegendKey", type); - } - - /** - * shows or removes the Data Table for this chart - * - * @param boolean bShow - true if show data table - */ - public void showDataTable(boolean bShow) { - mychart.showDataTable(bShow); - } - - /** - * shows or hides the Chart legend key - * - * @param booean bShow - true if show legend, false to hide - * @param boolean vertical - true if show vertically, false for horizontal - */ - public void showLegend(boolean bShow, boolean vertical) { - mychart.showLegend(bShow, vertical); - } - - /** - * returns true if Chart has a Data Legend Key showing - * - * @return true if Chart has a Data Legend Key showing - */ - public boolean hasDataLegend() { - return mychart.hasDataLegend(); - } - - public void removeLegend() { - mychart.removeLegend(); - } - - // 20070905 KSC: Group chart options for ease of setting - // almost all charts have these specific ChartTypes: - - /** - * makes this Chart Stacked
                - * sets the group of options necessary to create a stacked chart
                - * For Chart Types:
                - * BAR, COL, LINE, AREA, PYRAMID, PYRAMIDBAR, CYLINDER, CYLINDERBAR, CONE, - * CONEBAR - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void makeStacked(int nChart) { // bar, col, line, area, pyramid col + - // bar, cone col + bar, cylinder col - // + bar - int chartType = this.getChartType(nChart); - this.setChartOption("Stacked", "true", nChart); - switch (chartType) { - case ChartConstants.BARCHART: - case ChartConstants.COLCHART: - this.setChartOption("Overlap", "-100", nChart); - break; - case ChartConstants.CYLINDERCHART: - case ChartConstants.CYLINDERBARCHART: - case ChartConstants.CONECHART: - case ChartConstants.CONEBARCHART: - case ChartConstants.PYRAMIDCHART: - case ChartConstants.PYRAMIDBARCHART: - this.setChartOption("Overlap", "-100", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("Cluster", "false"); - break; - case ChartConstants.LINECHART: - this.setChartOption("Percentage", "0", nChart); - break; - case ChartConstants.AREACHART: - this.setChartOption("Overlap", "-100", nChart); - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - break; - } - } - - /** - * makes this Chart 100% Stacked
                - * sets the group of options necessary to create a 100% stacked chart
                - * For Chart Types:
                - * BAR, COL, LINE, PYRAMID, PYRAMIDBAR, CYLINDER, CYLINDERBAR, CONE, CONEBAR - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void make100PercentStacked(int nChart) { // bar, col, line, pyramid - // col + bar, cone col + - // bar, cylinder col + - // bar - int chartType = this.getChartType(nChart); - this.setChartOption("Stacked", "true", nChart); - this.setChartOption("PercentageDisplay", "true", nChart); - switch (chartType) { - case ChartConstants.COLCHART: // + pyramid - case ChartConstants.BARCHART: - this.setChartOption("Overlap", "-100", nChart); - break; - case ChartConstants.CYLINDERCHART: - case ChartConstants.CYLINDERBARCHART: - case ChartConstants.CONECHART: - case ChartConstants.CONEBARCHART: - case ChartConstants.PYRAMIDCHART: - case ChartConstants.PYRAMIDBARCHART: - this.setChartOption("Overlap", "-100", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("Cluster", "false"); - break; - case ChartConstants.LINECHART: - break; - } - } - - /** - * makes this Chart Stacked with a 3D Effect
                - * sets the group of options necessary to create a Stacked 3D chart
                - * For Chart Types:
                - * BAR, COL, AREA - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - */ - public void makeStacked3D(int nChart) { // bar, col, area - int chartType = this.getChartType(nChart); - this.setChartOption("Stacked", "true", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - switch (chartType) { - case ChartConstants.COLCHART: - case ChartConstants.BARCHART: - this.setChartOption("Overlap", "-100", nChart); - break; - case ChartConstants.AREACHART: - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - break; - } - } - - /** - * makes this Chart 100% Stacked with a 3D Effect
                - * sets the group of options necessary to create a 100% Stacked 3D chart
                - * For Chart Types:
                - * BAR, COL, AREA - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void make100PercentStacked3D(int nChart) { // bar, col, area - int chartType = this.getChartType(nChart); - this.setChartOption("Stacked", "true", nChart); - this.setChartOption("PercentageDisplay", "true", nChart); - switch (chartType) { - case ChartConstants.COLCHART: // + pyramid - case ChartConstants.BARCHART: - this.setChartOption("Overlap", "-100", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - break; - case ChartConstants.AREACHART: - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - break; - } - } - - /** - * makes the default Chart hava a 3D effect
                - * sets the group of options necessary to create a 3D chart
                - * For Chart Types:
                - * BARCHART, COLCHART, LINECHART, PIECHART, AREACHART, BUBBLECHART, - * PYRAMIDCHART, CYLINDERCHART, CONECHART - * - * @deprecated use setChartType(chartType, 0, is3d, isStacked, - * is100PercentStacked) instead - */ - @Deprecated - public void make3D() { // bar, col, line, pie, area, bubble, pyramid, cone, - // cylinder - make3D(0); - } - - /** - * makes the desired Chart hava a 3D effect
                - * where nChart 0= default, 1-9=multiple charts in one
                - * sets the group of options necessary to create a 3D chart
                - * For Chart Types:
                - * BARCHART, COLCHART, LINECHART, PIECHART, AREACHART, BUBBLECHART, - * PYRAMIDCHART, CYLINDERCHART, CONECHART - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated use setChartType(chartType, nChart, is3d, isStacked, - * is100%Stacked) instead - */ - @Deprecated - public void make3D(int nChart) { // bar, col, line, pie, area, bubble, - // pyramid, cone, cylinder - int chartType = this.getChartType(nChart); - ThreeD td = null; - switch (chartType) { - case ChartConstants.COLCHART: - case ChartConstants.BARCHART: - td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - break; - case ChartConstants.CYLINDERCHART: - case ChartConstants.CONECHART: - case ChartConstants.PYRAMIDCHART: - td = this.initThreeD(nChart); - td.setChartOption("Cluster", "false"); - break; - case ChartConstants.AREACHART: - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - td.setChartOption("Perspective", "true"); - break; - case ChartConstants.PIECHART: - case ChartConstants.LINECHART: - this.initThreeD(nChart); // just create a threeD rec w/ no extra - // options - break; - case ChartConstants.BUBBLECHART: - this.setChartOption("Percentage", "25", nChart); - this.setChartOption("SmoothedLine", "true", nChart); - this.setChartOption("ThreeDBubbles", "true", nChart); - td = this.initThreeD(nChart); // 20081228 KSC - break; - } - } - - // more specialized option sets - - /** - * makes this Chart Clusted with a 3D effect
                - * sets the group of options necessary to create a Clusted 3D chart
                - * For Chart Types:
                - * BAR, COL - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void makeClustered3D(int nChart) { // only for Column and Bar (?) - int chartType = this.getChartType(nChart); - switch (chartType) { - case ChartConstants.BARCHART: - case ChartConstants.COLCHART: - ThreeD td = this.initThreeD(nChart); - td.setChartOption("AnRot", "20"); - td.setChartOption("Cluster", "true"); - td.setChartOption("ThreeDScaling", "true"); - td.setChartOption("TwoDWalls", "true"); - break; - } - } - - /** - * makes this chart's wedges exploded i.e. separated
                - * For Chart Types:
                - * PIECHART, DOUGHNUTCHART - * - * @deprecated - */ - @Deprecated - public void makeExploded() { // pie, donut - int chartType = this.getChartType(); - switch (chartType) { - case ChartConstants.DOUGHNUTCHART: - this.setChartOption("SmoothedLine", "true"); - case ChartConstants.PIECHART: - this.setChartOption("ShowLdrLines", "true"); - this.setChartOption("Percentage", "25"); - break; - // ShowLdrLines="true" Percentage="25"/> - // exploded donut: ShowLdrLines="true" Donut="50" - // Percentage="25" - // SmoothedLine="true"/> - } - } - - /** - * makes this chart's wedges exploded 3D i.e. separated with a 3D effect
                - * For Chart Types:
                - * PIECHART, DOUGHNUTCHART - * - * @param nChart number and drawing order of the desired chart (default= 0 max=9 - * where 1-9 indicate an overlay chart) - * @deprecated - */ - @Deprecated - public void makeExploded3D(int nChart) { // pie - // ShowLdrLines="true" Percentage="25" - // AnRot="236" - int chartType = this.getChartType(nChart); - switch (chartType) { - case ChartConstants.DOUGHNUTCHART: - case ChartConstants.PIECHART: - this.setChartOption("ShowLdrLines", "true", nChart); - this.setChartOption("Percentage", "25", nChart); - ThreeD td = this.initThreeD(nChart); - td.setChartOption("AnRot", "236"); - break; - } - } - - /* - * NOT IMPLEMENTED YET TODO: IMPLEMENT Make this Chart have - * smoothed lines - * (Scatter only) - * - * public void makeSmoothedLines() { // scatter - * //Percentage="25" - * SmoothedLine="true } - * - * public void makeWireFrame() { // surface // NO ColorFill, - * only // - * Percentage="25" SmoothedLine="true" // AnRot="20" - * Perspective="true" - * ThreeDScaling="true" TwoDWalls="true"/> // all else - * should be default for - * surface charts } public void makeContour() { // surface - * -- for wireframe - * surface, no ColorFill //ColorFill="true" Percentage="25" - * SmoothedLine="true"/> // AnElev="90" pcDist="0" - * Perspective="true" - * ThreeDScaling="true" TwoDWalls="true" } - */ - - /** - * set the marker format style for this chart
                - * one of:
                - * 0 = no marker
                - * 1 = square
                - * 2 = diamond
                - * 3 = triangle
                - * 4 = X
                - * 5 = star
                - * 6 = Dow-Jones
                - * 7 = standard deviation
                - * 8 = circle
                - * 9 = plus sign
                - * For Chart Types:
                - * LINE, SCATTER - * - * @param int imf - marker format constant from list above - */ - public void setMarkerFormat(int imf) { // line, scatter ... - this.setChartOption("MarkerFormat", String.valueOf(imf)); - } - - /** - * returs the JSON representation of this chart, based upon - * Dojo-charting-specifics - * - * @return String JSON representation of the chart - */ - public String getJSON() { - JSONObject theChart = new JSONObject(); - try { - JSONObject titles = new JSONObject(); - int type = this.getChartType(); // necessary for parsing AXIS - // options: horizontal charts - // "switch" axes ... - - // titles/labels - titles.put("title", this.getTitle()); - titles.put("XAxis", (type != ChartConstants.BARCHART) - ? (this.getXAxisLabel()) - : this.getYAxisLabel()); // bar - // axes - // are - // reversed - // ... - titles.put("YAxis", (type != ChartConstants.BARCHART) - ? (this.getYAxisLabel()) - : this.getXAxisLabel()); - try { - titles.put("ZAxis", this.getZAxisLabel()); - } catch (Exception e) { - Logger.logWarn("ChartHandle.getJSON failed getting zaxislable:" - + e.toString()); - } - theChart.put("titles", titles); - - // Chart dimensions (width, height) - short[] coords = mychart.getCoords(); - theChart.put("width", coords[ChartHandle.WIDTH]); - theChart.put("height", coords[ChartHandle.HEIGHT]); - theChart.put("row", mychart.getRow0()); // TODO: may not be - // necessary, see usage ... - theChart.put("col", mychart.getCol0()); - // Plot Area Background color - int plotAreabg = this.getPlotAreaBgColor(); - if (plotAreabg == 0x4D || plotAreabg == 0x4E) - plotAreabg = FormatConstants.COLOR_WHITE; - theChart.put("fill", FormatConstants.SVGCOLORSTRINGS[plotAreabg]); - - Double[] jMinMax = new Double[3]; - JSONObject chartObjectJSON = this.mychart.getChartObject() - .getJSON(this.mychart.getChartSeries(), this.wbh, jMinMax); - double yMax = 1.0, yMin = 0.0; - int nSeries = 0; - try { // it's possible to not have any series defined ... - theChart.put("Series", chartObjectJSON.getJSONArray("Series")); - // 20080416 KSC: Save SeriesJSON for later comparisons - mychart.setSeriesJSON(chartObjectJSON.getJSONArray("Series")); - theChart.put("SeriesFills", chartObjectJSON - .getJSONArray("SeriesFills")); // 20090729 KSC: capture - // bar - // colors or fills - } catch (Exception e) { - } - theChart.put("type", chartObjectJSON.getJSONObject("type")); - yMin = jMinMax[0].doubleValue(); - yMax = jMinMax[1].doubleValue(); - nSeries = jMinMax[2].intValue(); - - // Axes + Category Labels + Grid Lines - try { - // inputJSONObject(theChart, this.getAxis(YAXIS, - // false).getJSON(this.wbh, type, - // yMax, yMin, nSeries)); - theChart.put("y", mychart.getAxes() - .getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries) - .getJSONObject("y")); - theChart.put("back_grid", mychart.getAxes() - .getJSON(YAXIS, this.wbh, type, yMax, yMin, nSeries) - .getJSONObject("back_grid")); - } catch (Exception e) { - } - try { - // inputJSONObject(theChart, this.getAxis(XAXIS, - // false).getJSON(this.wbh, type, - // yMax, yMin, nSeries)); - theChart.put("x", mychart.getAxes() - .getJSON(XAXIS, this.wbh, type, yMax, yMin, nSeries) - .getJSONObject("x")); - theChart.put("back_grid", mychart.getAxes() - .getJSON(ChartConstants.YAXIS, this.wbh, type, yMax, yMin, nSeries) - .getJSONObject("back_grid")); - } catch (Exception e) { - } - // TODO: 3d Charts (z axis) - - /* - * /* Chart Fonts sb.append(t(2) + "" + - * this.getChartFontRecsXML()); sb.append("\n" + t(2) + - * "\n"); - * sb.append(t(2) + "\n"); - */ - /* - * Format Chart Area - */ - /* TODO: read in legend settings */ - - // Chart Legend - if (this.hasDataLegend()) { - short s = this.mychart.getLegend().getLegendPosition(); - String[] legends = this.mychart.getLegends(-1); // -1 is flag - // for all - // rather than - // for a - // specific - // chart - String l = ""; - for (int i = 0; i < legends.length; i++) - l += legends[i] + ","; - if (l.length() > 0) - l = l.substring(0, l.length() - 1); - theChart.put("legend", new JSONObject( - "{position:" + s + ",labels:[" + l + "]}")); - } - - } catch (JSONException e) { - Logger.logErr("Error getting Chart JSON: " + e); - } - return theChart.toString(); - } - - /** - * utility to add a JSON object
                - * This is an internal method that is not useful to the end user. - * - * @param source - * @param input - */ - protected void inputJSONObject(JSONObject source, JSONObject input) { - if (source != null) { - try { - for (int j = 0; j < input.names().length(); j++) { - source.put(input.names().getString(j), input - .get(input.names().getString(j))); - } - } catch (JSONException e) { - Logger.logErr("Error inputting JSON Object: " + e); - } - } - } - - /** - * retrieves the saved Series JSON for comparisons
                - * This is an internal method that is not useful to the end user. - * - * @return JSONArray - */ - public JSONArray getSeriesJSON() { - return mychart.getSeriesJSON(); - } - - /** - * sets the saved Series JSON
                - * This is an internal method that is not useful to the end user. - * - * @param JSONArray s - - * @throws JSONException - */ - public void setSeriesJSON(JSONArray s) throws JSONException { - mychart.setSeriesJSON(s); - } - - /** - * retrieves current series and axis scale info in JSONObject form used upon - * chart updating
                - * This is an internal method that is not useful to the end user. - * - * @return JSONObject series and axis info - */ - public JSONObject getCurrentSeries() { - JSONObject retJSON = new JSONObject(); - // Retrieve series data + yMin yMax, nSeries - Double[] jMinMax = new Double[3]; - try { - // Series Data - // 20080516 KSC: See above JSONObject chartObjectJSON= - // ((GenericChartObject)this.mychart.getChartObject()).getJSON( - // this.getAllChartSeriesHandles(), this.getCategories()[0], - // this.wbh, minMax); - JSONObject chartObjectJSON = this.mychart.getChartObject() - .getJSON(this.mychart.getChartSeries(), this.wbh, jMinMax); - - try { - retJSON.put("Series", chartObjectJSON.getJSONArray("Series")); - } catch (Exception e) { - Logger.logWarn("ChartHandle.getCurrentSeries problem:" - + e.toString()); - } - - // Retrieve Axis Scale info - double yMax = 0.0, yMin = 0.0; - int nSeries = 0; - yMin = jMinMax[0].doubleValue(); - yMax = jMinMax[1].doubleValue(); - nSeries = jMinMax[2].intValue(); - - int type = this.getChartType(); // necessary for parsing AXIS - // options: horizontal charts - // "switch" axes ... - // Axes + Category Labels + Grid Lines - /* - * KSC: TAKE OUT JSON STUFF FOR NOW; WILL REFACTOR LATER try - * { - * inputJSONObject(retJSON, - * mychart.getAxes().getMinMaxJSON(YAXIS, this.wbh, - * type, yMax, yMin, nSeries)); } catch (Exception e) { } - * try { - * inputJSONObject(retJSON, - * mychart.getAxes().getMinMaxJSON(XAXIS, this.wbh, - * type, yMax, yMin, nSeries)); } catch (Exception e) { } - */ - // TODO: 3d Charts (z axis) - } catch (JSONException e) { - Logger.logErr("ChartHandle.getCurrentSeries: Error getting Series JSON: " - + e); - } - return retJSON; - } - - /** - * returns a JSON representation of all Series Data (Legend, Categogies, Series - * Values) for the chart
                - * This is an internal method that is not useful to the end user. - * - * @return String JSON representation - */ - public String getAllSeriesDataJSON() { - JSONArray s = new JSONArray(); - ChartSeriesHandle[] series = this.getAllChartSeriesHandles(); - try { - for (int i = 0; i < series.length; i++) { - JSONObject ser = new JSONObject(); - ser.put("l", series[i].getSeriesLegendReference()); - ser.put("v", series[i].getSeriesRange()); - ser.put("b", series[i].getBubbleSizes()); - if (i == 0) - ser.put("c", series[i].getCategoryRange()); // 1 per chart - s.put(ser); - } - } catch (JSONException e) { - Logger.logErr("ChartHandle.getAllSeriesDataJSON: " + e); - } - return s.toString(); - } - - /** - * Take current Chart object and return the SVG code necessary to define it. - */ - /** - * TODO: Less Common Charts: STOCK RADAR SURFACE COLUMN- 3D, CONE, CYLINDER, - * PYRAMID BAR- 3D, CONE, CYLINDER, PYRAMID 3D PIE 3D LINE 3D AREA - *

                - * LINE CHART APPEARS THAT STARTS AND ENDS A BIT TOO EARLY ***************** Z - * Axis - *

                - * CHART OPTIONS: STACKED CLUSTERED - */ - public String getSVG() { - return getSVG(1); - } - - /** - * /** Take current Chart object and return the SVG code necessary to define it, - * scaled to the desired percentage e.g. 0.75= 75% - * - * @param scale double scale factor - * @return String SVG - */ - public String getSVG(double scale) { - HashMap chartMetrics = mychart.getMetrics(wbh); // build - // or - // retrieve - // Chart - // Metrics - // --> - // dimensions - // + - // series - // data - // ... - - StringBuffer svg = new StringBuffer(); - // required header - // svg.append("\r\n"); // referneces - // the DTD - // svg.append("\r\n"); - - // Define SVG Canvas: - svg.append("\r\n"); - svg.append(""); // scale - // chart - // -default - // scale=1 - // == 100% - // JavaScript hooks - svg.append(getJavaScript()); - - // Data Legend Box -- do before drawing plot area as legends - // box may change plot - // area coordinates - String legendSVG = getLegendSVG(chartMetrics); // but have to append it - // after because should - // overlap the - // plotarea - - String bgclr = this.mychart.getPlotAreaBgColor(); - // setup gradients - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - - // PLOT AREA BG + RECT - // rectangle around entire chart canvas - if (!(mychart instanceof OOXMLChart) - || !((OOXMLChart) mychart).roundedCorners) - svg.append("\r\n"); - else // OOXML rounded corners - svg.append("\r\n"); - - // actual plot area - svg.append("\r\n"); - - // AXES, IF PRESENT - DO BEFORE ACTUAL SERIES DATA SO DRAWS - // CORRECTLY + ADJUST - // CHART DIMENSIONS - svg.append(mychart.getAxes().getSVG(XAXIS, chartMetrics, mychart - .getChartSeries().getCategories())); - svg.append(mychart.getAxes().getSVG(YAXIS, chartMetrics, mychart - .getChartSeries().getCategories())); - // TODO: Z Axis - - // After Axes and gridlines (if present), - // ACTUAL bar/series/area/etc. svg generated from series and - // scale data - svg.append(this.mychart.getChartObject().getSVG(chartMetrics, mychart - .getAxes().getMetrics(), mychart.getChartSeries())); - - svg.append(legendSVG); // append legend SVG obtained above - - // CHART TITLE - - if (mychart.getTitleTd() != null) - svg.append(mychart.getTitleTd().getSVG(chartMetrics)); - - svg.append(""); - svg.append(""); - /* - * //KSC: TESTING: REMOVE WHEN DONE if - * (WorkBookFactory.PID==WorkBookFactory.E360) { // save svg - * for testing - * purposes try { java.io.File f= new java.io.File( - * "c:/eclipse/workspace/testfiles/io.starter.OpenXLS/output/charts/FromSheetster.svg" - * ); java.io.FileOutputStream fos= new - * java.io.FileOutputStream(f); - * fos.write(svg.toString().getBytes()); fos.flush(); - * fos.close(); } catch - * (Exception e) {} } - */ - return svg.toString(); - } - - private String getLegendSVG(HashMap chartMetrics) { - try { - return mychart.getLegend().getSVG(chartMetrics, mychart - .getChartObject(), mychart.getChartSeries()); - } catch (NullPointerException ne) { // no legend?? - return null; - } - } - - /** - * returns the svg for javascript for highlight and restore - * - * @return - */ - protected String getJavaScript() { - StringBuffer svg = new StringBuffer(); - svg.append(""); - return svg.toString(); - } - - /** - * debugging utility remove when done - */ - public void WriteMainChartRecs(String fName) { - mychart.getChartObject().WriteMainChartRecs(fName); - } - - public Axis getAxis(int type) { - // TODO Auto-generated method stub - return null; - } -} diff --git a/src/main/java/io/starter/OpenXLS/ChartHandle.kt b/src/main/java/io/starter/OpenXLS/ChartHandle.kt new file mode 100644 index 0000000..ea72834 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/ChartHandle.kt @@ -0,0 +1,3927 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.OOXML.* +import io.starter.formats.XLS.* +import io.starter.formats.XLS.charts.* +import io.starter.toolkit.Logger +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserFactory + +import java.util.ArrayList +import java.util.EnumSet +import java.util.HashMap +import java.util.Vector + +/** + * Chart Handle allows for manipulation of Charts within a WorkBook.

                + * Allows for run-time modification of Chart titles, labels, categories, and + * data Cells.

                + * Modification of Chart data cells allows you to completely modify the data + * shown on the Chart.

                + *

                + * To create a new chart and add to a worksheet, use: + * ChartHandle.createNewChart(sheet, charttype, chartoptions).

                + *

                + * To Obtain an array of existing chart handles, use

                + * WorkBookHandle.getCharts()

                + * or WorkBookHandle.getCharts(chart title) + * + * [Starter Inc.](http://starter.io) + * + * @see WorkBookHandle + */ +class ChartHandle +/** + * Constructor which creates a new ChartHandle from an existing Chart Object + * + * @param Chart c - the source Chart object + * @param WorkBookHandle wb - the parent WorkBookHandle + */ +(private var mychart: Chart?, wb: WorkBookHandle) : ChartConstants { + + var workBookHandle: WorkBookHandle + protected set + + /** + * Returns the title of the Chart + * + * @return String title of the Chart + */ + /** + * Sets the title of the Chart + * + * @param String title - Chart title + */ + var title: String? + get() = mychart!!.title + set(title) { + this.mychart!!.title = title + } + + /** + * returns the data range used by the chart + * + * @return + */ + val dataRangeJSON: String + get() = this.mychart!!.chartSeries.dataRangeJSON.toString() + + val encompassingDataRange: IntArray? + get() = getEncompassingDataRange(this.mychart!!.chartSeries + .dataRangeJSON) + + /** + * returns the ordinal id associated with the underlying Chart Object + * + * @return int chart id + * @see WorkBookHandle.getChartById + */ + val id: Int + get() = this.mychart!!.id + + /** */ + /** + * Returns an ordered array of strings representing all the series ranges in the + * Chart.

                + * Each series can only represent one bar, line or wedge of data. + * + * @return String[] each item being a Cell Range representing one bar, line or + * wedge of data in the Chart + * @see ChartHandle.getCategories + */ + // -1 is flag for all rather than for a + // specific chart + val series: Array + get() = mychart!!.getSeries(-1) + + /** + * Returns an ordered array of strings representing all the category ranges in + * the chart.

                + * This vector corresponds to the getSeries() method so will often contain + * duplicates, as while the series data changes frequently, category data is the + * same throughout the chart. + * + * @return String[] each item being a Cell Range representing the Category Data + * @see ChartHandle.getSeries + */ + // -1 is flag for all rather than for a + // specific chart + val categories: Array + get() = getCategories(-1) + + /** + * Returns an array of ChartSeriesHandle Objects, one for each bar, line or + * wedge of data. + * + * @return ChartSeriesHandle[] Array of ChartSeriesHandle Objects representing + * Chart Series Data (Series and Categories) + * @see ChartSeriesHandle + */ + // get ALL + val allChartSeriesHandles: Array + get() = getAllChartSeriesHandles(-1) + + /** + * returns the Y axis Title + * + * @return String title + */ + /** + * Sets the Y axis Title + * + * @param String yTitle - new Y Axis title + */ + var yAxisLabel: String + get() = mychart!!.axes!!.getTitle(YAXIS) + set(yTitle) = mychart!!.axes!!.setTitle(YAXIS, yTitle) + + /** + * returns the X axis Title + * + * @return String title + */ + /** + * Sets the XAxisTitle + * + * @param String xTitle - new X Axis title + */ + var xAxisLabel: String + get() = mychart!!.axes!!.getTitle(XAXIS) + set(xTitle) = mychart!!.axes!!.setTitle(XAXIS, xTitle) + + /** + * returns the Z axis Title, if any + * + * @return String Title + */ + /** + * set the Z AxisTitle + * + * @param String zTitle - new Z Axis Title + */ + var zAxisLabel: String + get() = mychart!!.axes!!.getTitle(ZAXIS) + set(zTitle) = mychart!!.axes!!.setTitle(ZAXIS, zTitle) + + /** + * Returns true if the Y Axis (Value axis) is set to automatic scale + * + * + * The default setting for charts is known as Automatic Scaling

                + * When data changes, the chart automatically adjusts the scale (minimum, + * maximum values plus major and minor tick units) as necessary + * + * @return boolean true if Automatic Scaling is turned on + * @see getAxisAutomaticScale + */ + /** + * Sets the automatic scale option on or off for the Y Axis (Value axis) + * + * + * Automatic Scaling will automatically set the scale maximum, minimum and tick + * units upon data changes, and is the default chart setting + * + * @param b + * @see setAxisAutomaticScale + */ + var axisAutomaticScale: Boolean + get() = mychart!!.axisAutomaticScale + set(b) { + mychart!!.axes!!.axisAutomaticScale = b + mychart!!.setDirtyFlag(true) + } + + /** + * Returns the minimum value of the Y Axis (Value Axis) scale + * + * @return int Miminum Scale value for Y axis + * @see getAxisMinScale + */ + // -1 is flag for all + // rather than for a + // specific chart + val axisMinScale: Double + get() { + val minmax = mychart!!.getMinMax(this.workBookHandle) + return mychart!!.axes!!.getMinMax(minmax[0], minmax[1])[0] + } + + /** + * Returns the maximum value of the Y Axis (Value Axis) scale + * + * @return int Maximum Scale value for Y axis + * @see getAxisMaxScale + */ + val axisMaxScale: Double + get() { + val minmax = mychart!!.getMinMax(this.workBookHandle) + return mychart!!.axes!!.getMinMax(minmax[0], minmax[1])[1] + } + + /** + * Returns the major tick unit of the Y Axis (Value Axis) + * + * @return int major tick unit + * @see getAxisMajorUnit + */ + val axisMajorUnit: Int + get() { + val minmax = mychart!!.getMinMax(this.workBookHandle) + return mychart!!.axes!!.getMinMax(minmax[0], minmax[1])[2].toInt() + } + + /** + * Returns the minor tick unit of the Y Axis (Value Axis) + * + * @return int minor tick unit + * @see getAxisMinorUnit + */ + val axisMinorUnit: Int + get() { + val minmax = mychart!!.getMinMax(this.workBookHandle) + return mychart!!.axes!!.getMinMax(minmax[0], minmax[1])[1].toInt() + } + + /** + * returns the Font associated with the Chart Title + * + * @return io.starter.Formats.XLS.Font + * @see Font + */ + /** + * set the font for the Chart Title + * + * @param io.starter.Formats.XLS.Font f - desired font for the Chart Title + * @see Font + */ + // font doesn't exist yet, add to streamer + // flag to insert + var titleFont: Font? + get() = mychart!!.titleFont + set(f) { + var idx = workBookHandle.workBook!!.getFontIdx(f) + if (idx == -1) { + f.idx = -1 + idx = workBookHandle.workBook!!.insertFont(f) + 1 + } + mychart!!.setTitleFont(idx) + } + + /** + * return the Font associated with the Chart Axes + * + * @return io.starter.Formats.XLS.Font for Chart Axes or null if no axes + * @see Font + */ + /** + * set the font for all axes on the Chart + * + * @param io.starter.Formats.XLS.Font f - desired font for the Chart axes + * @see Font + */ + // font doesn't exist yet, add to streamer + // flag to insert + var axisFont: Font? + get() { + var f: Font? = null + f = mychart!!.axes!!.getTitleFont(XAXIS) + if (f != null) + return f + f = mychart!!.axes!!.getTitleFont(YAXIS) + if (f != null) + return f + f = mychart!!.axes!!.getTitleFont(ZAXIS) + return f + } + set(f) { + var idx = workBookHandle.workBook!!.getFontIdx(f) + if (idx == -1) { + f.idx = -1 + idx = workBookHandle.workBook!!.insertFont(f) + 1 + } + mychart!!.axes!!.setTitleFont(XAXIS, idx) + mychart!!.axes!!.setTitleFont(YAXIS, idx) + mychart!!.axes!!.setTitleFont(ZAXIS, idx) + mychart!!.setDirtyFlag(true) + } + + /** + * returns the underlhying Sheet Object this Chart is attached to

                + * For Internal Use + * + * @return Boundsheet + */ + val sheet: Boundsheet? + get() = mychart!!.sheet + + /** + * return the background color of this chart's Plot Area as an int + * + * @return int background color constant + * @see FormatHandle.COLOR_* constants + */ + /** + * sets the Plot Area background color + * + * @param int bg - color constant + * @see FormatHandle.COLOR_* constants + */ + var plotAreaBgColor: Int + get() { + val bg = mychart!!.plotAreaBgColor + return FormatHandle.HexStringToColorInt(bg, 0.toShort()) + } + set(bg) = mychart!!.setPlotAreaBgColor(bg) + + val plotAreaBgColorStr: String + get() = mychart!!.plotAreaBgColor + + /** + * Return an int corresponding to this ChartHandle's Chart Type for the default + * chart

                + * To see possible Chart Types, view the public static int's in ChartHandle. + * + * @return int chart type + * @see ChartHandle static Chart Type Constants + * + * @see ChartHandle.setChartType + */ + /** + * Sets the Chart type to the specified basic type (no 3d, no stacked ...)

                + * To see possible Chart Types, view the public static int's in ChartHandle. + *

                + * Possible Chart Types:

                + * BARCHART

                + * COLCHART

                + * LINECHART

                + * PIECHART

                + * AREACHART

                + * SCATTERCHART

                + * RADARCHART

                + * SURFACECHART

                + * DOUGHNUTCHART

                + * BUBBLECHART

                + * RADARAREACHART

                + * PYRAMIDCHART

                + * CYLINDERCHART

                + * CONECHART

                + * PYRAMIDBARCHART

                + * CYLINDERBARCHART

                + * CONEBAR + * + * @param int chartType - representing the chart type + */ + // no + // specific + // options + var chartType: Int + get() = mychart!!.chartType + set(chartType) = mychart!!.setChartType(chartType, 0, EnumSet.noneOf(ChartOptions::class.java)) + + /* + * NOT IMPLEMENTED YET adjust chart cell references upon row + * insertion or + * deletion NOTE: Assumes we're on the correct sheet NOT + * COMPLETELY IMPLEMENTED + * YEt + * + * @param rownum + * + * @param shiftamt +1= insert row, -1= delete row + * + * public void adjustCellRefs(int rownum, int shiftamt) { + * Vector v = + * mychart.getAllSeries(); boolean bSeriesRows= false; + * boolean bMod= false; for + * (int i=0;i 0) { // + * insert row i.e. + * shift ai location down if ((loc.length==2 && + * loc[0]>=rownum) || + * (loc[0]>=rownum || loc[2]>=rownum)) { + * adjustAiLocation(ai, + * p[0].getIntLocation(), shiftamt); bMod= true; } } else { + * if ((loc.length==2 + * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) + * { // remove it + * this.removeSeries(i); bMod= true; continue; } } } + * catch(Exception e) { + * + * } } catch (Exception e) { + * + * } try { // CATEGORY Ai ai= s.getCategoryValueAi(); Ptg[] + * p=ai.getCellRangePtgs(); try { int[] loc= + * p[0].getIntLocation(); if (shiftamt + * > 0) { // insert row i.e. shift ai location down if + * ((loc.length==2 && + * loc[0]>=rownum) || (loc[0]>=rownum || loc[2]>=rownum)) { + * adjustAiLocation(ai, + * p[0].getIntLocation(), shiftamt); bMod= true; } } else { + * if ((loc.length==2 + * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) + * { // remove it ???? + * this.removeSeries(i); bMod= true; } } } catch(Exception + * e) { + * + * } } catch (Exception e) { + * + * } try { // LEGEND Ai ai= s.getLegendAi(); Ptg[] + * p=ai.getCellRangePtgs(); + * int[] loc= p[0].getIntLocation(); if (shiftamt > 0) { // + * insert row i.e. + * shift ai location down if ((loc.length==2 && + * loc[0]>=rownum) || + * (loc[0]>=rownum || loc[2]>=rownum)) { + * adjustAiLocation(ai, + * p[0].getIntLocation(), shiftamt); bMod= true; } } else { + * if ((loc.length==2 + * && loc[0]==rownum) || (loc[0]>=rownum && loc[2]<=rownum)) + * { // remove it + * this.removeSeries(i); bMod= true; } } } catch(Exception + * e) { + * + * } } if (bMod)// one or more series elements were modified + * setDimensionsRecord(); // update dimensions i.e. Data + * Range + * + * } + */ + + /* + * doesn't appear to be used right now move the ai location + * (row) according to + * shift amount + * + * @param ai + * + * @param loc + * + * @param shift + * + * private void adjustAiLocation(Ai ai, int[] loc, int + * shift) { String oldloc= + * ExcelTools.formatLocation(loc); if (loc.length>2) // get + * 2nd part of range + * oldloc += ":" + ExcelTools.formatLocation(new + * int[]{loc[2], loc[3]}); oldloc= + * this.getSheet().getSheetName() + "!" + oldloc; if + * (loc.length==2)// single + * cell loc[0]+=shift; else { // range loc[0]+=shift; + * loc[2]+=shift; } String + * newloc= ExcelTools.formatLocation(loc); if (loc.length>2) + * // get 2nd part of + * range newloc += ":" + ExcelTools.formatLocation(new + * int[]{loc[2], loc[3]}); + * ai.changeAiLocation(oldloc, newloc); } + * + */ + + /** + * Get the Chart's bytes + * + * + * This is an internal method that is not useful to the end user. + */ + val chartBytes: ByteArray? + get() = mychart!!.chartBytes + + val serialBytes: ByteArray? + get() = mychart!!.serialBytes + + /** + * get the chart-type-specific options in XML form + * + * @return String XML + */ + private// 0 for default chart + val chartOptionsXML: String + get() = mychart!!.getChartOptionsXML(0) + + /** + * returns an XML representation of this chart + * + * @return String XML + */ + // Chart Name (=Title) + // Type + // Plot Area Background color 20080429 KSC + // Position + // Chart Fonts + // Format Chart Area + // KSC: TODO: BORDER + // TODO: + // Properties + // Source Data + // controls shape of complex datapoints such as pyramid, + // cylinder, cone + + // stacked 3d bars + // Chart Options + // Axis Options + // ThreeD rec opts + val xml: String + get() { + val sb = StringBuffer(t(1) + "\n") + sb.append(t(2) + "" + this.chartFontRecsXML) + sb.append("\n" + t(2) + "\n") + sb.append(t(2) + "\n") + sb.append(t(2) + "\n") + sb.append(t(3) + "\n") + sb.append(t(3) + "\n") + sb.append(t(2) + "\n") + sb.append(t(2) + "\n") + val series = this.allChartSeriesHandles + for (i in series.indices) { + sb.append(t(3) + "\n") + } + sb.append(t(2) + "\n") + sb.append(t(2) + "\n") + sb.append(t(2) + "\n") + sb.append(t(3) + "\n") + sb.append(t(3) + "\n") + sb.append(t(3) + "\n") + sb.append(t(2) + "\n") + if (this.isThreeD) { + sb.append(t(2) + "\n") + } + + sb.append(t(1) + "\n") + return sb.toString() + } + + /** + * return the Excel 7/OOXML-specific name for this chart + * + * @return String OOXML name + */ + /** + * set the Excel 7/OOXML-specific name for this chart + * + * @param String name + */ + private var ooxmlName: String? + get() = (mychart as OOXMLChart).ooxmlName + set(name) { + (mychart as OOXMLChart).ooxmlName = name + } + + /** + * returns the drawingml file name which defines the userShape (if any)

                + * a userShape is a drawing or shape ontop of a chart associated with this chart + * + * @return + */ + val chartEmbeds: ArrayList<*>? + get() = (mychart as OOXMLChart).chartEmbeds + + /** + * @return true if Chart has 3D effects, false otherwise + */ + // default chart + val isThreeD: Boolean + get() = mychart!!.isThreeD(0) + + /** + * @return boolean true if Chart contains Stacked Series, false otherwise + */ + // default chart + val isStacked: Boolean + get() = mychart!!.isStacked(0) + + /** + * @return boolean true if Chart is of type 100% Stacked, false otherwise + */ + // default chart + val is100PercentStacked: Boolean + get() = mychart!!.is100PercentStacked(0) + + /** + * @return boolean true if Chart contains Clustered Bars or Columns, false + * otherwise + */ + // default chart + val isClustered: Boolean + get() = mychart!!.isClustered(0) + + /** + * @return String ThreeD options in XML form + */ + // 0 for default chart + val threeDXML: String + get() = mychart!!.getThreeDXML(0) + + /** + * returns Chart-specific Font Records in XML form + * + * @return String Chart Font information in XML format + */ + val chartFontRecsXML: String + get() = mychart!!.chartFontRecsXML + + /** + * Return non-axis Chart font ids in XML form + * + * @return String Font information in XML format + */ + val chartFontsXML: String + get() = mychart!!.chartFontsXML + + /** + * @return the WorkBook Object attached to this Chart + */ + val workBook: io.starter.formats.XLS.WorkBook? + get() = mychart!!.workBook + + // this should be impossible + val workSheetHandle: WorkSheetHandle + get() { + try { + return this.workBookHandle.getWorkSheet(mychart!!.sheet!!.sheetNum) + } catch (e: WorkSheetNotFoundException) { + throw RuntimeException(e) + } + + } + + /** + * returns the coordinates or bounds (position, width and height) of this chart + * in pixels + * + * @return short[4] bounds - left or x value, top or y value, width, height + * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT + */ + // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and + // Coords are very + // distinct + /** + * sets the coordinates or bounds (position, width and height) of this chart in + * pixels + * + * @param short[4] bounds - left or x value, top or y value, width, height + * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT + */ + // NOTE: THIS SHOULD BE RENAMED TO setCoords as Bounds and + // Coords are very + // distinct + var bounds: ShortArray + get() = mychart!!.coords + set(bounds) { + mychart!!.coords = bounds + } + + /** + * returns the coordinates (position, width and height) of this chart in Excel + * size units + * + * @return short[4] pixel coords - left or x value, top or y value, width, + * height + * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT + */ + /** + * sets the coordinates (position, width and height) for this chart in Excel + * size units + * + * @return short[4] pixel coords - left or x value, top or y value, width, + * height + * @see ChartHandle.X, ChartHandle.Y, ChartHandle.WIDTH, ChartHandle.HEIGHT + */ + var coords: ShortArray + get() { + mychart!!.getMetrics(this.workBookHandle) + return mychart!!.coords + } + set(coords) { + mychart!!.coords = coords + } + + /** + * get the bounds of the chart using coordinates relative to row/cols and their + * offsets + * + * @return short[8] bounds - COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, + * ROW1, ROWOFFSET1 + */ + /** + * sets the bounds of the chart using coordinates relative to row/cols and their + * offsets + * + * @param short[8] bounds - COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, + * ROWOFFSET1 + */ + var relativeBounds: ShortArray? + get() = mychart!!.bounds + set(bounds) { + mychart!!.bounds = bounds + } + + /** + * returns the offset within the column in pixels + * + * @return + */ + val colOffset: Short + get() = mychart!!.colOffset + +/** + * returs the JSON representation of this chart, based upon + * Dojo-charting-specifics + * + * @return String JSON representation of the chart + */ +// necessary for parsing AXIS +// options: horizontal charts +// "switch" axes ... +// titles/labels +// bar +// axes +// are +// reversed +// ... +// Chart dimensions (width, height) +// TODO: may not be +// necessary, see usage ... +// Plot Area Background color +// it's possible to not have any series defined ... +// 20080416 KSC: Save SeriesJSON for later comparisons +// 20090729 KSC: capture +// bar +// colors or fills +// Axes + Category Labels + Grid Lines +// inputJSONObject(theChart, this.getAxis(YAXIS, +// false).getJSON(this.wbh, type, +// yMax, yMin, nSeries)); +// inputJSONObject(theChart, this.getAxis(XAXIS, +// false).getJSON(this.wbh, type, +// yMax, yMin, nSeries)); +// TODO: 3d Charts (z axis) +/* + * /* Chart Fonts sb.append(t(2) + "" + + * this.getChartFontRecsXML()); sb.append("\n" + t(2) + + * "\n"); + * sb.append(t(2) + "\n"); + *//* + * Format Chart Area + *//* TODO: read in legend settings */// Chart Legend + // -1 is flag + // for all + // rather than + // for a + // specific + // chart + val json:String +get() { +val theChart = JSONObject() +try +{ +val titles = JSONObject() +val type = this.chartType +titles.put("title", this.title) +titles.put("XAxis", if ((type != ChartConstants.BARCHART)) +(this.xAxisLabel) +else +this.yAxisLabel) +titles.put("YAxis", if ((type != ChartConstants.BARCHART)) +(this.yAxisLabel) +else +this.xAxisLabel) +try +{ +titles.put("ZAxis", this.zAxisLabel) +} +catch (e:Exception) { +Logger.logWarn(("ChartHandle.getJSON failed getting zaxislable:" + e.toString())) +} + +theChart.put("titles", titles) +val coords = mychart!!.coords +theChart.put("width", coords[ChartHandle.WIDTH].toInt()) +theChart.put("height", coords[ChartHandle.HEIGHT].toInt()) +theChart.put("row", mychart!!.row0) +theChart.put("col", mychart!!.col0) +var plotAreabg = this.plotAreaBgColor +if (plotAreabg == 0x4D || plotAreabg == 0x4E) +plotAreabg = FormatConstants.COLOR_WHITE +theChart.put("fill", FormatConstants.SVGCOLORSTRINGS[plotAreabg]) + +val jMinMax = arrayOfNulls(3) +val chartObjectJSON = this.mychart!!.chartObject +.getJSON(this.mychart!!.chartSeries, this.workBookHandle, jMinMax) +var yMax = 1.0 +var yMin = 0.0 +var nSeries = 0 +try +{ +theChart.put("Series", chartObjectJSON.getJSONArray("Series")) +mychart!!.seriesJSON = chartObjectJSON.getJSONArray("Series") +theChart.put("SeriesFills", chartObjectJSON +.getJSONArray("SeriesFills")) +} +catch (e:Exception) {} + +theChart.put("type", chartObjectJSON.getJSONObject("type")) +yMin = jMinMax[0].toDouble() +yMax = jMinMax[1].toDouble() +nSeries = jMinMax[2].toInt() +try +{ +theChart.put("y", mychart!!.axes!! +.getJSON(YAXIS, this.workBookHandle, type, yMax, yMin, nSeries) +.getJSONObject("y")) +theChart.put("back_grid", mychart!!.axes!! +.getJSON(YAXIS, this.workBookHandle, type, yMax, yMin, nSeries) +.getJSONObject("back_grid")) +} +catch (e:Exception) {} + +try +{ +theChart.put("x", mychart!!.axes!! +.getJSON(XAXIS, this.workBookHandle, type, yMax, yMin, nSeries) +.getJSONObject("x")) +theChart.put("back_grid", mychart!!.axes!! +.getJSON(ChartConstants.YAXIS, this.workBookHandle, type, yMax, yMin, nSeries) +.getJSONObject("back_grid")) +} +catch (e:Exception) {} + +if (this.hasDataLegend()) +{ +val s = this.mychart!!.legend!!.legendPosition +val legends = this.mychart!!.getLegends(-1) +var l = "" +for (i in legends.indices) +l += legends[i] + "," +if (l.length > 0) +l = l.substring(0, l.length - 1) +theChart.put("legend", JSONObject( +"{position:" + s + ",labels:[" + l + "]}")) +} + +} +catch (e:JSONException) { +Logger.logErr("Error getting Chart JSON: " + e) +} + +return theChart.toString() +} + +/** + * retrieves the saved Series JSON for comparisons

                + * This is an internal method that is not useful to the end user. + * + * @return JSONArray + */ + /** + * sets the saved Series JSON

                + * This is an internal method that is not useful to the end user. + * + * @param JSONArray s - + * @throws JSONException + */ + var seriesJSON:JSONArray? +get() { +return mychart!!.seriesJSON +} +@Throws(JSONException::class) +set(s) { +mychart!!.seriesJSON = s +} + +/** + * retrieves current series and axis scale info in JSONObject form used upon + * chart updating

                + * This is an internal method that is not useful to the end user. + * + * @return JSONObject series and axis info + */ + // Retrieve series data + yMin yMax, nSeries + // Series Data + // 20080516 KSC: See above JSONObject chartObjectJSON= + // ((GenericChartObject)this.mychart.getChartObject()).getJSON( + // this.getAllChartSeriesHandles(), this.getCategories()[0], + // this.wbh, minMax); + // Retrieve Axis Scale info + // necessary for parsing AXIS + // options: horizontal charts + // "switch" axes ... + // Axes + Category Labels + Grid Lines + /* + * KSC: TAKE OUT JSON STUFF FOR NOW; WILL REFACTOR LATER try + * { + * inputJSONObject(retJSON, + * mychart.getAxes().getMinMaxJSON(YAXIS, this.wbh, + * type, yMax, yMin, nSeries)); } catch (Exception e) { } + * try { + * inputJSONObject(retJSON, + * mychart.getAxes().getMinMaxJSON(XAXIS, this.wbh, + * type, yMax, yMin, nSeries)); } catch (Exception e) { } + */// TODO: 3d Charts (z axis) + val currentSeries:JSONObject +get() { +val retJSON = JSONObject() +val jMinMax = arrayOfNulls(3) +try +{ +val chartObjectJSON = this.mychart!!.chartObject +.getJSON(this.mychart!!.chartSeries, this.workBookHandle, jMinMax) + +try +{ +retJSON.put("Series", chartObjectJSON.getJSONArray("Series")) +} +catch (e:Exception) { +Logger.logWarn(("ChartHandle.getCurrentSeries problem:" + e.toString())) +} + +var yMax = 0.0 +var yMin = 0.0 +var nSeries = 0 +yMin = jMinMax[0].toDouble() +yMax = jMinMax[1].toDouble() +nSeries = jMinMax[2].toInt() + +val type = this.chartType +} +catch (e:JSONException) { +Logger.logErr(("ChartHandle.getCurrentSeries: Error getting Series JSON: " + e)) +} + +return retJSON +} + +/** + * returns a JSON representation of all Series Data (Legend, Categogies, Series + * Values) for the chart

                + * This is an internal method that is not useful to the end user. + * + * @return String JSON representation + */ + // 1 per chart + val allSeriesDataJSON:String +get() { +val s = JSONArray() +val series = this.allChartSeriesHandles +try +{ +for (i in series.indices) +{ +val ser = JSONObject() +ser.put("l", series[i].seriesLegendReference) +ser.put("v", series[i].seriesRange) +ser.put("b", series[i].bubbleSizes) +if (i == 0) +ser.put("c", series[i].categoryRange) +s.put(ser) +} +} +catch (e:JSONException) { +Logger.logErr("ChartHandle.getAllSeriesDataJSON: " + e) +} + +return s.toString() +} + +/** + * Take current Chart object and return the SVG code necessary to define it. + */ + /** + * TODO: Less Common Charts: STOCK RADAR SURFACE COLUMN- 3D, CONE, CYLINDER, + * PYRAMID BAR- 3D, CONE, CYLINDER, PYRAMID 3D PIE 3D LINE 3D AREA + * + * + * LINE CHART APPEARS THAT STARTS AND ENDS A BIT TOO EARLY ***************** Z + * Axis + * + * + * CHART OPTIONS: STACKED CLUSTERED + */ + val svg:String +get() { +return getSVG(1.0) +} + +/** + * returns the svg for javascript for highlight and restore + * + * @return + */ + protected// rgb('+ + // red + // +','+ + // green+','+blue+')');"); + // svg.append("evt.target.setAttributeNS(null,'stroke-color','white');"); + // rgb('+ + // red + // +','+ + // green+','+blue+')');"); + // svg.append("try{parent.parent.uiWindowing.getActiveSheet().book.handleMouseClick(evt);}catch(x){;}"); + val javaScript:String +get() { +val svg = StringBuffer() +svg.append("") +return svg.toString() +} + +init{ +workBookHandle = wb +if (mychart!!.workBook == null) + // TODO: WHY IS THIS NULL???? + mychart!!.workBook = wb.workBook +}// super(); + +/** + * returns the string representation of this ChartHandle + */ + public override fun toString():String { +return mychart!!.title +} + +/** + * Returns an ordered array of strings representing all the category ranges in + * the chart.

                + * This vector corresponds to the getSeries() method so will often contain + * duplicates, as while the series data changes frequently, category data is the + * same throughout the chart. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return String[] each item being a Cell Range representing the Category Data + * @see ChartHandle.getSeries + */ + private fun getCategories(nChart:Int):Array { +return mychart!!.getCategories(nChart) +} + +/** + * Returns an array of ChartSeriesHandle Objects for the desired chart, one for + * each bar, line or wedge of data.

                + * A chart number of 0 means the default chart, 1-9 indicate series for overlay + * charts

                + * NOTE: using this method returns the series for the desired chart ONLY

                + * You MUST use the corresponding removeSeries(index, nChart) when removing + * series to properly match the series index. Otherwise a mismatch will occur. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle[] Array of ChartSeriesHandle Objects representing + * Chart Series Data (Series and Categories) + * @see ChartSeriesHandle + */ + fun getAllChartSeriesHandles(nChart:Int):Array { +val v = mychart!!.getAllSeries(nChart) +val csh = arrayOfNulls(v.size) +for (i in v.indices) +{ +val s = v.get(i) as Series +csh[i] = ChartSeriesHandle(s, this.workBookHandle) +} +return csh +} + +/** + * Get the ChartSeriesHandle representing Chart Series Data (Series and + * Categories) for the specified Series range + * + * @param String seriesRange - For example, "Sheet1!A12:A21" + * @return ChartSeriesHandle + * @see ChartSeriesHandle + */ + fun getChartSeriesHandle(seriesRange:String):ChartSeriesHandle? { +val series = this.allChartSeriesHandles +for (i in series.indices) +{ +val sr = series[i].seriesRange +if (seriesRange.equals(sr, ignoreCase = true)) +{ +return series[i] +} +} +return null +} + +/** + * Get the ChartSeriesHandle representing Chart Series Data (Series and + * Categories) for the specified Series index + * + * @param int idx - the index (0 based) of the series + * @return ChartSeriesHandle + * @see ChartSeriesHandle + */ + fun getChartSeriesHandle(idx:Int):ChartSeriesHandle? { +val series = this.allChartSeriesHandles +if (series.size >= idx) +return series[idx] +return null +} + +/** + * Get the ChartSeriesHandle representing Chart Series Data (Series and + * Categories) for the Series specified by label (legend) + * + * @param String legend - label for the desired series + * @return ChartSeriesHandle + * @see ChartSeriesHandle + */ + fun getChartSeriesHandleByName(legend:String):ChartSeriesHandle { +val s = mychart!!.getSeries(legend, -1) // -1 is flag for all rather + // than for a specific chart + return ChartSeriesHandle(s, this.workBookHandle) +} + +/** + * sets or removes the axis title + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param ttl String new title or null to remove + */ + fun setAxisTitle(axisType:Int, ttl:String) { +mychart!!.axes!!.setTitle(axisType, ttl) +} + +/** + * Sets the automatic scale option on or off for the desired Value axis + * + * + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * + * Automatic Scaling automatically sets the scale maximum, minimum and tick + * units upon data changes, and is the default setting for charts + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @param boolean b - true if set Automatic scaling on, false otherwise + * @see setAxisAutomaticScale + */ + fun setAxisAutomaticScale(axisType:Int, b:Boolean) { +mychart!!.axes!!.setAxisAutomaticScale(axisType, b) +mychart!!.setDirtyFlag(true) +} + +/** + * Returns the minimum scale value of the the desired Value axis + * + * + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return int Miminum Scale value for the desired axis + * @see getAxisMinScale + */ + fun getAxisMinScale(axisType:Int):Double { +val minmax = mychart!!.getMinMax(this.workBookHandle) +return mychart!!.axes!!.getMinMax(minmax[0], minmax[1], axisType)[0] +} + +/** + * Returns the maximum scale value of the desired Value axis + * + * + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return int - Maximum Scale value for the desired axis + * @see getAxisMaxScale + */ + fun getAxisMaxScale(axisType:Int):Double { +val minmax = mychart!!.getMinMax(this.workBookHandle) // -1 is flag for all + // rather than for a + // specific chart + return mychart!!.axes!!.getMinMax(minmax[0], minmax[1], axisType)[1] +} + +/** + * Returns the major tick unit of the desired Value axis + * + * + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return int major tick unit + * @see getAxisMajorUnit + */ + fun getAxisMajorUnit(axisType:Int):Int { +return mychart!!.axes!!.getAxisMajorUnit(axisType) +} + +/** + * Returns the minor tick unit of the desired Value axis + * + * + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return int - minor tick unit of the desired axis + * @see getAxisMinorUnit + */ + fun getAxisMinorUnit(axisType:Int):Int { +return mychart!!.axes!!.getAxisMinorUnit(axisType) +} + +/** + * Sets the maximum scale value of the desired Value axis + * + * + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * + * Note: The default scale setting for charts is known as Automatic Scaling

                + * When data changes, the chart automatically adjusts the scale as necessary + *

                + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @param int MaxValue - desired maximum value of the desired axis + * @see setAxisMax + */ + fun setAxisMax(axisType:Int, MaxValue:Int) { +mychart!!.axes!!.setAxisMax(axisType, MaxValue) +mychart!!.setDirtyFlag(true) +} + +/** + * Sets the minimum scale value of the desired Value axis + * + * + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * + * Note: The default setting for charts is known as Automatic Scaling

                + * When data values change, the chart automatically adjusts the scale as + * necessary

                + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @param int MinValue - the desired Minimum scale value + * @see setAxisMin + */ + fun setAxisMin(axisType:Int, MinValue:Int) { +mychart!!.axes!!.setAxisMin(axisType, MinValue) +mychart!!.setDirtyFlag(true) +} + +/** + * Returns true if the desired Value axis is set to automatic scale + * + * + * The Value axis contains numbers rather than labels, and is normally the Y + * axis, but Scatter and Bubble charts may have a value axis on the X Axis as + * well + * + * + * Note: The default setting for charts is known as Automatic Scaling

                + * When data changes, the chart automatically adjusts the scale as necessary + *

                + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, + * ChartHandle.ZAXIS + * @return boolean true if Automatic Scaling is turned on + * @see getAxisAutomaticScale + */ + fun getAxisAutomaticScale(axisType:Int):Boolean { +return mychart!!.axes!!.getAxisAutomaticScale(axisType) +} + +/** + * Sets the maximum value of the Y Axis (Value Axis) Scale + * + * + * Note: The default scale setting for charts is known as Automatic Scaling

                + * When data changes, the chart automatically adjusts the scale as necessary + *

                + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int MaxValue - the desired maximum scale value + * @see ChartHandle.setAxisMax + */ + fun setAxisMax(MaxValue:Int) { +mychart!!.axes!!.setAxisMax(MaxValue) +mychart!!.setDirtyFlag(true) +} + +/** + * Sets the minimum value of the Y Axis (Value Axis) Scale + * + * + * Note: The default setting for charts is known as Automatic Scaling

                + * When data changes, the chart automatically adjusts the scale as necessary + *

                + * Setting the scale manually (either Minimum or Maximum Value) removes + * Automatic Scaling + * + * @param int MinValue - the desired minimum scale value + * @see ChartHandle.setAxisMin + */ + fun setAxisMin(MinValue:Int) { +mychart!!.axes!!.setAxisMin(MinValue) +mychart!!.setDirtyFlag(true) +} + +/** + * sets an option for this axis + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param op option name; one of: CatCross, LabelCross, Marks, CrossBetween, + * CrossMax, MajorGridLines, AddArea, AreaFg, AreaBg or Linked Text + * Display options: Label, ShowKey, ShowValue, ShowLabelPct, ShowPct, + * ShowCatLabel, ShowBubbleSizes, TextRotation, Font + * @param val option value + */ + fun setAxisOption(axisType:Int, op:String, `val`:String) { +mychart!!.axes!!.setChartOption(axisType, op, `val`) + +} + +/** + * set the font for the chart title + * + * @param String name - font name + * @param int height - font height in 1/20 point units + * @param boolean bold - true if bold + * @param boolean italic - true if italic + * @param boolean underline - true if underlined + */ + fun setTitleFont(name:String, height:Int, bold:Boolean, italic:Boolean, underline:Boolean) { +val f = Font(name, 200, height) +f.bold = bold +f.italic = italic +f.underlined = underline +var idx = workBookHandle.workBook!!.getFontIdx(f) +if (idx == -1) +{ // font doesn't exist yet, add to streamer +f.idx = -1 // flag to insert +idx = workBookHandle.workBook!!.insertFont(f) + 1 +} +mychart!!.setTitleFont(idx) +} + +/** + * set the font for all axes on the chart + * + * @param String name - font name + * @param int height - font height in 1/20 point units + * @param boolean bold - true if bold + * @param boolean italic - true if italic + * @param boolean underline - true if underlined + */ + fun setAxisFont(name:String, height:Int, bold:Boolean, italic:Boolean, underline:Boolean) { +val f = Font(name, 200, height) +f.bold = bold +f.italic = italic +f.underlined = underline +var idx = workBookHandle.workBook!!.getFontIdx(f) +if (idx == -1) +{ // font doesn't exist yet, add to streamer +f.idx = -1 // flag to insert +idx = workBookHandle.workBook!!.insertFont(f) + 1 +} +mychart!!.axes!!.setTitleFont(XAXIS, idx) +mychart!!.axes!!.setTitleFont(YAXIS, idx) +mychart!!.axes!!.setTitleFont(ZAXIS, idx) +mychart!!.setDirtyFlag(true) +} + +/** + * resets all fonts in the chart to the default font of the workbook + */ + fun resetFonts() { +mychart!!.resetFonts() +} + +/** + * Change the value of a Chart object.

                + * **NOTE: THIS HAS NOT BEEN 100% IMPLEMENTED YET**

                + * You can use this method to change: + * + *

                + * - the Title of the Chart

                + * - the Text Labels of Categories and Values (X and Y) + * + *

                + * eg: + * + *

                + * To change the value of the Chart title

                + * chart.changeObjectValue("Template Chart Title", "Widget Sales By Quarter"); + * + *

                + * To change the text label of the categories

                + * chart.changeObjectValue("Category X", "Fiscal Year"); + * + *

                + * To change the text label of the values

                + * chart.changeObjectValue("Value Y", "Sales in US$"); + * + * @param String originalval - One of: "Template Chart Title", "Category X" or + * "Value Y" + * @param Sring newval - the new setting + * @return whether the change was successful + */ + fun changeTextValue(originalval:String, newval:String):Boolean { + /* + * KSC: TODO: Refactor *** for(int + * x=0;x
                + * Use these chart options when creating new charts

                + * A chart may have multiple chart options e.g. 3D Exploded pie chart + * + * @see ChartHandle.createNewChart + */ + // need: hasMarkers **** + enum class ChartOptions { +CLUSTERED, +/** + * bar, col charts only + */ + STACKED, +PERCENTSTACKED, +/** + * 100% stacked + */ + THREED, +/** + * 3d Effect + */ + EXPLODED, +/** + * Pie, Donut + */ + HASLINES, +/** + * Scatter, Line + */ + SMOOTHLINES, +/** + * Scatter, Line, Radar + */ + WIREFRAME, +/** + * Surface + */ + DROPLINES, +/** + * line, area, stock charts + */ + UPDOWNBARS, +/** + * line, stock + */ + SERLINES, +/** + * bar, ofpie + */ + HILOWLINES, +/** + * line, stock charts + */ + FILLED /** radar */ +} + +/** + * Sets the Chart type to the specified type (no 3d, no stacked ...)

                + * To see possible Chart Types, view the public static int's in ChartHandle. + *

                + * Possible Chart Types:

                + * BARCHART

                + * COLCHART

                + * LINECHART

                + * PIECHART

                + * AREACHART

                + * SCATTERCHART

                + * RADARCHART

                + * SURFACECHART

                + * DOUGHNUTCHART

                + * BUBBLECHART

                + * RADARAREACHART

                + * PYRAMIDCHART

                + * CYLINDERCHART

                + * CONECHART

                + * PYRAMIDBARCHART

                + * CYLINDERBARCHART

                + * CONEBARCHART + * + * @param int chartType - representing the chart type + * @param nChart - 0 (default) or 1-9 for complex overlay charts + * @param EnumSet 0 or more chart options (Such as Stacked, Exploded ...) + * @see ChartHandle.ChartOptions + */ + fun setChartType(chartType:Int, nChart:Int, options:EnumSet) { +mychart!!.setChartType(chartType, nChart, options) +} + +/** + * Sets the basic chart type (no 3d, stacked...) for multiple or overlay Charts. + *

                + * You can specify the drawing order of the Chart, where 0 is the default chart, + * and 1-9 are overlay charts.

                + * The default chart (chart 0) is always present; however, using this method, + * you can create a new overlay chart (up to 9 maximum).

                + * NOTE: The chart number must be **unique** and **in order**

                + * If the desired chart number is not present in the chart, a new overlay chart + * will be created.

                + * **To set explicit chart options, @see setChartType(chartType, nChart, is3d, + * isStacked, is100PercentStacked)**

                + *

                + * To see possible Chart Types, view the public static int's in ChartHandle. + *

                + * Possible Chart Types:

                + * BARCHART

                + * COLCHART

                + * LINECHART

                + * PIECHART

                + * AREACHART

                + * SCATTERCHART

                + * RADARCHART

                + * SURFACECHART

                + * DOUGHNUTCHART

                + * BUBBLECHART

                + * RADARAREACHART

                + * PYRAMIDCHART

                + * CYLINDERCHART

                + * CONECHART

                + * PYRAMIDBARCHART

                + * CYLINDERBARCHART

                + * CONEBARCHART + * + * @param int chartType - representing the chart type + * @param chartType + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + fun setChartType(chartType:Int, nChart:Int) { +mychart!!.setChartType(chartType, nChart, EnumSet +.noneOf(ChartOptions::class.java)) // no specific options +} + +/** + * Return an int corresponding to this ChartHandle's Chart Type for the + * specified chart

                + * To see possible Chart Types, view the public static int's in ChartHandle. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return int chart type + * @see ChartHandle static Chart Type Constants + * + * @see ChartHandle.setChartType + */ + fun getChartType(nChart:Int):Int { +return mychart!!.getChartType(nChart) +} + +/** + * Sets the location lock on the Cell Reference at the specified location + * + * Used to prevent updating of the Cell Reference when Cells are moved. + * + * @param location + * of the Cell Reference to be locked/unlocked + * @param lock + * status setting + * @return boolean whether the Cell Reference was found and modified + */ + /* + * TODO: NEEDED?? public boolean setLocationLocked(String + * loc, boolean l){ int x + * = Ptg.PTG_LOCATION_POLICY_UNLOCKED; if(l)x = + * Ptg.PTG_LOCATION_POLICY_LOCKED; + * return setLocationPolicy(loc, x); } + */ + + /** + * Change the Cell Range referenced by one of the Series (a bar, line or wedge + * of data) in the Chart.

                + *

                + * For Example, if the data values for one of the Series in the Chart are + * obtained from the range Sheet1!A1:A10 and we want to add 5 more values to + * that Series, use: + * + *

                + * boolean changedOK = + * charthandle.changeSeriesRange("Sheet1!A1:A10","Sheet1!A1:A15");

                + *

                + * Please keep in mind this is only the data range; it does not include labels + * that may have been automatically created when you chose the chart range.

                + *

                + * To illustrate this, if A1 = "label" A2 = "data" A3 = "data", and we want to + * add 2 more data points, we would use: changeSeriesRange("Sheet1!A2:A3", + * "Sheet1!A2:A5");

                + *

                + * Series are always expressed as one single line of data. If your chart + * encompasses a range of rows and columns you will need to modify each of the + * series in the chart handle. To determine the series that already exist in + * your chart, utilize the String[] getSeries() method. + * + * @param String originalrange - the original Series (bar, line or wedge of data) + * to alter + * @param String newrange -the new data range + * @return whether the change was successful + */ + fun changeSeriesRange(originalrange:String, newrange:String):Boolean { +return mychart!!.changeSeriesRange(originalrange, newrange) +} + +/** + * Change the Cell Range representing the Categories in the Chart.

                + * Categories usually appear on the X Axis and are textual, not numeric

                + * For example: the Category values in the Chart are obtained from the range + * Sheet1!A1:A10 and we want to add 5 more categories to the chart:

                + * boolean changedOK = + * chart.changeCategoryRange("Sheet1!A1:A10","Sheet1!A1:A15");

                + * Note that Category Range is the same for each Series (bar, line or wedge of + * data)

                + * i.e. there is only one Category Range for the Chart, but there may be many + * Series Ranges + * + * @param String originalrange - Original Category Range + * @param String newrange - New Category Range + * @return true if the change was successful + */ + fun changeCategoryRange(originalrange:String, newrange:String):Boolean { +return mychart!!.changeCategoryRange(originalrange, newrange) +} + +/** + * Changes or adds a Series to the chart via Series Index. Each bar, line or + * wedge in a chart represents a Series.

                + * If the Series index is greater than the number of series already present in + * the chart, the series will be added to the end.

                + * Otherwise the Series at the index position will be altered.

                + * This method allows altering of every aspect of the Series: Data (Series) + * Range, Legend Cell Address, Category Range and/or Bubble Range. + * + * @param int index - the series index. If greater than the number of series + * already present in the chart, the series will be added to the end + * @param String legendCell - String representation of Legend Cell Address + * @param String categoryRange - String representation of Category Range (should be + * same for all series) + * @param String seriesRange - String representation of the Series Data Range for + * this series + * @param String bubbleRange - String representation of Bubble Range (representing + * bubble sizes), if bubble chart. null if not + * @return a ChartSeriesHandle representing the new or altered Series + * @throws CellNotFoundException + */ + @Throws(CellNotFoundException::class) + fun setSeries(index:Int, legendCell:String?, categoryRange:String, seriesRange:String, bubbleRange:String):ChartSeriesHandle { +var legendText:String? = "" +try +{ +var ICell:CellHandle? = null +if (legendCell != null && legendCell != "") +{ + // 20070707 KSC: allow addition of new cell ranges for + // legendCell (see + // OpenXLS.handleChartElement) + try +{ +ICell = workBookHandle.getCell(legendCell!!) +} +catch (c:CellNotFoundException) { +val shtpos = legendCell!!.indexOf("!") +if (shtpos > 0) +{ +val sheetstr = legendCell!!.substring(0, shtpos) +val sht = workBookHandle.getWorkSheet(sheetstr) +val celstr = legendCell!!.substring(shtpos + 1) +ICell = sht!!.add("", celstr) +} +} + +legendText = ICell!!.stringVal +} +return setSeries(index, legendCell, legendText, categoryRange, seriesRange, bubbleRange) +} +catch (e:WorkSheetNotFoundException) { +throw CellNotFoundException( +("Error locating cell for adding series range: " + legendCell!!)) +} + +} + +/** + * Changes or adds a Series to the desired Chart (either default or overlay) via + * Series Index. Each bar, line or wedge in a chart represents a Series.

                + * If the Series index is greater than the number of series already present in + * the chart, the series will be added to the end.

                + * Otherwise the Series at the index position will be altered.

                + * This method allows altering of every aspect of the Series: Data (Series) + * Range, Legend Text, Legend Cell Address, Category Range and/or Bubble Range. + * + * @param int index - the series index. If greater than the number of series + * already present in the chart, the series will be added to the end + * @param String legendCell - String representation of Legend Cell Address + * @param String legendText - String Legend text + * @param String categoryRange - String representation of Category Range (should be + * same for all series) + * @param String seriesRange - String representation of the Series Data Range for + * this series + * @param String bubbleRange - String representation of Bubble Range (representing + * bubble sizes), if bubble chart. null if not + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return a ChartSeriesHandle representing the new or altered Series + * @throws CellNotFoundException + */ + @Throws(CellNotFoundException::class) +@JvmOverloads fun setSeries(index:Int, legendCell:String?, legendText:String, categoryRange:String, seriesRange:String, bubbleRange:String, nChart:Int = 0):ChartSeriesHandle { + // if (index < mychart.getAllSeries(nChart).size() && index + // >= 0) { + try +{ +val s = mychart!!.getAllSeries(nChart).get(index) as Series +val csh = ChartSeriesHandle(s, this.workBookHandle) +csh.setSeries(legendCell, categoryRange, seriesRange, bubbleRange) +setDimensionsRecord() +return csh +} +catch (ae:ArrayIndexOutOfBoundsException) { // not found - add +return addSeriesRange(legendCell, legendText, categoryRange, seriesRange, bubbleRange, nChart) +} + +} + +/** + * Adds a new Series to the chart. Each bar, line or wedge in a chart represents + * a Series. + * + * @param String legendAddress - The cell address defining the legend for the + * series + * @param Srring legendText - Text of the legend + * @param String categoryRange - Cell Range defining the category (normally will be + * the same range for all series) + * @param String seriesRange - Cell range defining the data points of the series + * @param String bubbleRange - Cell range defining the bubble sizes for this series + * (bubble charts only) + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle representing the new series + * @throws CellNotFoundException + */ + @Throws(CellNotFoundException::class) +private fun addSeriesRange(legendAddress:String?, legendText:String, categoryRange:String, seriesRange:String, bubbleRange:String?, nChart:Int = 0):ChartSeriesHandle { +var s:Series? = null +if (bubbleRange == null || bubbleRange == "") +s = mychart!! +.addSeries(seriesRange, categoryRange, "", legendAddress, legendText, nChart) +else +s = mychart!! +.addSeries(seriesRange, categoryRange, bubbleRange, legendAddress, legendText, nChart) +if (nChart > 0) +{ // must update SeriesList record for overlay charts + // TODO: FINISH + } +setDimensionsRecord() +return ChartSeriesHandle(s, workBookHandle) +} + +/** + * Adds a new Series to the chart. Each bar, line or wedge in a chart represents + * a Series.

                + * An Example of adding multiple series to a chart: + * + * + * ChartHandle.addSeriesRange("Sheet1!A3", "Sheet1!B1:E1", "Sheet1:B3:E3", + * null);

                + * ChartHandle.addSeriesRange("Sheet1!A4", "Sheet1!B1:E1", "Sheet1:B4:E4", + * null);

                + * ChartHandle.addSeriesRange("Sheet1!A5", "Sheet1!B1:E1", "Sheet1:B5:E5", + * null);

                + * etc... + * + * + * Note that the category does not change, it is usually constant through + * series.

                + * Also note that the example above is for a non-bubble-type chart. + * + * @param String legendCell - Cell reference for the legend cell (e.g. Sheet1!A1) + * @param String categoryRange - Category Cell range (e.g. Sheet1!B1:B1); + * @param String seriesRange - Series Data range (e.g. Sheet1!B3:E3); + * @param String bubbleRange - Cell Range representing Bubble sizes (e.g. + * Sheet1!A2:A5); or null if chart is not of type Bubble. + * @return ChartSeriesHandle referencing the newly added series + */ + @Throws(CellNotFoundException::class) + fun addSeriesRange(legendCell:String, categoryRange:String, seriesRange:String, bubbleRange:String):ChartSeriesHandle { +return this +.addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0) // target + // default + // chart + } + +/** + * Adds a new Series to the chart. Each bar, line or wedge in a chart represents + * a Series.

                + * An Example of adding multiple series to a chart: + * + * + * ChartHandle.addSeriesRange("Sheet1!A3", "Sheet1!B1:E1", "Sheet1:B3:E3", + * null);

                + * ChartHandle.addSeriesRange("Sheet1!A4", "Sheet1!B1:E1", "Sheet1:B4:E4", + * null);

                + * ChartHandle.addSeriesRange("Sheet1!A5", "Sheet1!B1:E1", "Sheet1:B5:E5", + * null);

                + * etc... + * + * + * Note that the category does not change, it is usually constant through + * series.

                + * Also note that the example above is for a non-bubble-type chart. + * + * @param String legendCell - Cell reference for the legend cell (e.g. Sheet1!A1) + * @param String categoryRange - Category Cell range (e.g. Sheet1!B1:B1); + * @param String seriesRange - Series Data range (e.g. Sheet1!B3:E3); + * @param String bubbleRange - Cell Range representing Bubble sizes (e.g. + * Sheet1!A2:A5); or null if chart is not of type Bubble. + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle referencing the newly added series + */ + @Throws(CellNotFoundException::class) + fun addSeriesRange(legendCell:String?, categoryRange:String, seriesRange:String, bubbleRange:String?, nChart:Int):ChartSeriesHandle { +var legendText:String? = "" +var legendAddr = "" +try +{ +var ICell:CellHandle? = null +if (legendCell != null && legendCell != "") +{ +try +{ +ICell = workBookHandle.getCell(legendCell!!) +if (legendCell!!.indexOf("!") == -1) +legendAddr = (ICell!!.workSheetName + "!" ++ ICell!!.cellAddress) +else +legendAddr = legendCell +} +catch (c:CellNotFoundException) { +val shtpos = legendCell!!.indexOf("!") +if (shtpos > 0) +{ +val sheetstr = legendCell!!.substring(0, shtpos) +val sht = workBookHandle.getWorkSheet(sheetstr) +val celstr = legendCell!!.substring(shtpos + 1) +ICell = sht!!.add("", celstr) // TODO: Why is this being + // added? + legendAddr = celstr +} +} + +if (ICell != null) +legendText = ICell!!.stringVal +else +legendText = legendCell +} +var s:Series? = null +if (bubbleRange == null) +s = mychart!! +.addSeries(seriesRange, categoryRange, "", legendAddr, legendText, nChart) +else +s = mychart!! +.addSeries(seriesRange, categoryRange, bubbleRange, legendAddr, legendText, nChart) +setDimensionsRecord() // update chart DIMENSIONS record upon update + // of series + return ChartSeriesHandle(s, workBookHandle) +} +catch (e:WorkSheetNotFoundException) { +throw CellNotFoundException( +("Error locating cell for adding series range: " + legendCell!!)) +} + +} + +/** + * Adds a new Series to the chart via CellHandles and CellRange Objects. Each + * bar, line or wedge in a chart represents a Series. + * + * @param CellHandle legendCell - references the legend cell for this series + * @param CellRange categoryRange - The CellRange referencing the category (should be + * the same for all Series) + * @param CelLRange seriesRange - The CellRange referencing the data points for one + * bar, line or wedge in the chart + * @param CellRange bubbleRange -The CellRange referencing bubble sizes for this + * series, or null if chart is not of type BUBBLE + * @return ChartSeriesHandle referencing the newly added series + * @see ChartHandle.addSeriesRange + */ + fun addSeriesRange(legendCell:CellHandle, categoryRange:CellRange, seriesRange:CellRange, bubbleRange:CellRange):ChartSeriesHandle { +return this +.addSeriesRange(legendCell, categoryRange, seriesRange, bubbleRange, 0) // 0=default + // chart + } + +/** + * Adds a new Series to the chart via CellHandles and CellRange Objects. Each + * bar, line or wedge in a chart represents a Series.

                + * This method can update the default chart (nChart==0) or overlay charts + * (nChart 1-9) + * + * @param CellHandle legendCell - references the legend cell for this series + * @param CellRange categoryRange - The CellRange referencing the category (should be + * the same for all Series) + * @param CelLRange seriesRange - The CellRange referencing the data points for one + * bar, line or wedge in the chart + * @param CellRange bubbleRange -The CellRange referencing bubble sizes for this + * series, or null if chart is not of type BUBBLE + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle referencing the newly added series + * @see ChartHandle.addSeriesRange + */ + fun addSeriesRange(legendCell:CellHandle, categoryRange:CellRange, seriesRange:CellRange, bubbleRange:CellRange?, nChart:Int):ChartSeriesHandle { +var s:Series? = null +if (bubbleRange == null) +s = mychart!!.addSeries(seriesRange.toString(), categoryRange +.toString(), "", (legendCell.workSheetName + "!" ++ legendCell.cellAddress), legendCell +.stringVal, nChart) +else +s = mychart!!.addSeries(seriesRange.toString(), categoryRange +.toString(), bubbleRange!! +.toString(), (legendCell.workSheetName + "!" ++ legendCell.cellAddress), legendCell +.stringVal, nChart) +setDimensionsRecord() // 20080417 KSC: update chart DIMENSIONS record + // upon update of series + return ChartSeriesHandle(s, workBookHandle) +} + +/** + * remove the Series (bar, line or wedge) at the desired index + * + * @param int index - series index (valid values: 0 to + * getAllChartSeriesHandles().length-1) + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @see getAllChartSeriesHandles + */ + @JvmOverloads fun removeSeries(index:Int, nChart:Int = -1) { +val seriesperchart = mychart!!.getAllSeries(nChart) +val seriestodelete = seriesperchart.get(index) as Series // series to + // delete + mychart!!.removeSeries(seriestodelete) +setDimensionsRecord() +} + +/** + * updates (replaces) every Chart Series (bar, line or wedge on the Chart) with + * the data from the array of values, legends, bubble sizes (optional) and + * category range.

                + * NOTE: all arrays must be the same size (the exception is the bubleSizeRanges + * array, which may be null) + * + * + * NOTE: String arrays come in reverse order from plugins, so this method adds + * series LIFO i.e. reversed + * + * @param String[] valueRanges - Array of Cell Ranges representing the Values or Data + * points for each series (bar, line or wedge) on the Chart + * @param String[] legendCells - Array of Cell Addresses representing the legends for + * each Series + * @param String[] bubbleSizeRanges - Array of Cell ranges representing the bubble + * sizes for the Chart, or null if chart is not of type BUBBLE + * @param String categoryRange - The Cell Range representing the categories (X + * Axis) for the entire Chart + */ + fun addAllSeries(valueRanges:Array, legendCells:Array, bubbleSizeRanges:Array, categoryRange:String) { +addAllSeries(valueRanges, legendCells, bubbleSizeRanges, categoryRange, 0) // do + // for + // default + // chart + } + +/** + * updates (replaces) every Chart Series (bar, line or wedge on the Chart) with + * the data from the array of values, legends, bubble sizes (optional) and + * category range For the desired chart (0=default 1-9=overlay charts)

                + * NOTE: all arrays must be the same size (the exception is the bubleSizeRanges + * array, which may be null) + * + * + * NOTE: String arrays come in reverse order from plugins, so this method adds + * series LIFO i.e. reversed + * + * @param String[] valueRanges - Array of Cell Ranges representing the Values or Data + * points for each series (bar, line or wedge) on the Chart + * @param String[] legendCells - Array of Cell Addresses representing the legends for + * each Series + * @param String[] bubbleSizeRanges - Array of Cell ranges representing the bubble + * sizes for the Chart, or null if chart is not of type BUBBLE + * @param String categoryRange - The Cell Range representing the categories (X + * Axis) for the entire Chart + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + private fun addAllSeries(valueRanges:Array, legendCells:Array, bubbleSizeRanges:Array?, categoryRange:String, nChart:Int) { + // first, remove all existing series + val v = mychart!!.allSeries +for (i in v.indices) +{ +mychart!!.removeSeries(v.get(i) as Series) +} +try +{ +val chartMetrics = mychart!!.getMetrics(workBookHandle) // build + // or + // retrieve + // Chart + // Metrics + // --> + // dimensions + // + + // series + // data + // ... + this.mychart!!.legend!! +.resetPos(chartMetrics.get("y"), chartMetrics +.get("h"), chartMetrics +.get("canvash"), legendCells.size) +} +catch (e:Exception) {} + +setDimensionsRecord() + // now add series + val hasBubbles = (((bubbleSizeRanges != null && bubbleSizeRanges!!.size == valueRanges.size))) +for (i in valueRanges.indices.reversed()) +{ +try +{ +if (!hasBubbles) + // usual case + this.addSeriesRange(legendCells[i], categoryRange, valueRanges[i], null, nChart) +else +this.addSeriesRange(legendCells[i], categoryRange, valueRanges[i], bubbleSizeRanges!![i], nChart) +} +catch (e:Exception) { +Logger.logErr("Error adding series: " + e.toString()) +} + +} +} + +/** + * Appends a series one row below the last series in the chart for the desired + * chart + * + * + * This can be utilized when programmatically adding rows of data that should be + * reflected in the chart.

                + * Legend cell will be incremented by one row if a reference. Category range + * will stay the same. + * + *

                + * In order for this method to work properly the chart must have row-based + * series. If your chart utilizes column-based series, then you need to append a + * category. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ChartSeriesHandle representing newly added series + * @see ChartHandle.appendRowCategoryToChart + */ + @JvmOverloads fun appendRowSeriesToChart(nChart:Int = 0):ChartSeriesHandle? { +val handles = this.getAllChartSeriesHandles(nChart) +val theHandle = handles[handles.size - 1] +var legendRef = theHandle.seriesLegendReference +if (legendRef != null && legendRef != "") +{ +val sheetnm = legendRef!!.substring(0, legendRef!!.indexOf("!")) +legendRef = legendRef!! +.substring(legendRef!!.indexOf("!") + 1) +val rc = ExcelTools.getRowColFromString(legendRef!!) +rc[0] = rc[0] + 1 +legendRef = sheetnm + "!" + ExcelTools.formatLocation(rc) +} +else if (legendRef == null) +{ +legendRef = theHandle.seriesLegend +} +else +{ +legendRef = "" +} +val categoryRange = theHandle.categoryRange +var seriesRange = theHandle.seriesRange +val sheetnm = seriesRange.substring(0, seriesRange.indexOf("!")) +seriesRange = seriesRange +.substring(seriesRange.indexOf("!") + 1) +val rc = ExcelTools.getRangeRowCol(seriesRange) + // fiddle it, since exceltools doesn't translate back/forth + val newRc = IntArray(4) +newRc[0] = rc[1] +newRc[1] = rc[0] + 1 +newRc[2] = rc[3] +newRc[3] = rc[2] + 1 +seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc) +try +{ +return this +.addSeriesRange(legendRef, "", categoryRange, seriesRange, "", nChart) +} +catch (e:CellNotFoundException) { +Logger.logErr(("ChartHandle.appendRowSeriesToChart: Unable to append series to chart: " + e)) +} + +return null +} + +/** + * Append a row of categories to the bottom of the chart.

                + * Expands all Series to include the new bottom row.

                + * To be utilized when expanding a chart to encompass more data that has a + * col-based series. + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @see ChartHandle.appendRowSeriesToChart + */ + @JvmOverloads fun appendRowCategoryToChart(nChart:Int = 0) { +val handles = this.getAllChartSeriesHandles(nChart) + +for (i in handles.indices) +{ +val theHandle = handles[i] + // update the series + var seriesRange = theHandle.seriesRange +var s = ExcelTools.stripSheetNameFromRange(seriesRange) +var sheetnm = s[0] +seriesRange = s[1] + // String sheetnm = seriesRange.substring(0, + // seriesRange.indexOf("!")); + // seriesRange = seriesRange.substring( + // seriesRange.indexOf("!")+1, + // seriesRange.length()); + // Strip 2nd sheet ref, if any 20080213 KSC + // int n= seriesRange.indexOf('!'); + // int m= seriesRange.indexOf(':'); + // seriesRange= seriesRange.substring(0, m+1) + + // seriesRange.substring(n+1); + + var rc = ExcelTools.getRangeRowCol(seriesRange) +var newRc = IntArray(4) +newRc[0] = rc[1] +newRc[1] = rc[0] +newRc[2] = rc[3] +newRc[3] = rc[2] + 1 +seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc) +theHandle.seriesRange = seriesRange + // update the category + seriesRange = theHandle.categoryRange +s = ExcelTools.stripSheetNameFromRange(seriesRange) +sheetnm = s[0] +seriesRange = s[1] + /* + * sheetnm = seriesRange.substring(0, + * seriesRange.indexOf("!")); seriesRange = + * seriesRange.substring( seriesRange.indexOf("!")+1, + * seriesRange.length()); // + * Strip 2nd sheet ref, if any 20080213 KSC n= + * seriesRange.indexOf('!'); m= + * seriesRange.indexOf(':'); seriesRange= + * seriesRange.substring(0, m+1) + + * seriesRange.substring(n+1); + */ + rc = ExcelTools.getRangeRowCol(seriesRange) +newRc = IntArray(4) +newRc[0] = rc[1] +newRc[1] = rc[0] +newRc[2] = rc[3] +newRc[3] = rc[2] + 1 +seriesRange = sheetnm + "!" + ExcelTools.formatRange(newRc) +theHandle.categoryRange = seriesRange +} +} + +private fun t(n:Int):String { +val tabs = "\t\t\t\t\t\t\t\t\t\t\t\t\t" +return (tabs.substring(0, n)) +} + +/** + * given a XmlPullParser positioned at the chart element, parse all chart + * elements to create desired chart + * + * @param sht WorkSheetHandle + * @param xpp XmlPullParser + */ + fun parseXML(sht:WorkSheetHandle, xpp:XmlPullParser, maps:HashMap<*, *>) { +try +{ +var eventType = xpp.getEventType() +while (eventType != XmlPullParser.END_DOCUMENT) +{ +if (eventType == XmlPullParser.START_TAG) +{ +val tnm = xpp.getName() +if (tnm == "ChartFonts") +{ +for (x in 0 until xpp.getAttributeCount()) +{ +this.setChartFont(xpp.getAttributeName(x), xpp +.getAttributeValue(x)) +} +} +else if (tnm == "ChartFontRec") +{ +var fName = "" +var fontId = 0 +var fSize = 0 +var fWeight = 0 +var fColor = 0 +var fUnderline = 0 +var bIsBold = false +for (x in 0 until xpp.getAttributeCount()) +{ +val attr = xpp.getAttributeName(x) +val `val` = xpp.getAttributeValue(x) +if (attr == "name") +{ +fName = `val` +} +else if (attr == "id") +{ +fontId = Integer.parseInt(`val`) +} +else if (attr == "size") +{ +fSize = Font.PointsToFontHeight(java.lang.Double +.parseDouble(`val`)) +} +else if (attr == "color") +{ +fColor = FormatHandle +.HexStringToColorInt(`val`, FormatHandle.colorFONT) +if (fColor == 0) +fColor = 32767 // necessary? +} +else if (attr == "weight") +{ +fWeight = Integer.parseInt(`val`) +} +else if (attr == "bold") +{ +bIsBold = true +} +else if (attr == "underline") +{ +fUnderline = Integer.parseInt(`val`) +} +} +while (this.workBook!!.numFonts < fontId - 1) +{ +this.workBook!!.insertFont(Font("Arial", +FormatConstants.PLAIN, 10)) +} +if (this.workBook!!.numFonts < fontId) +{ +val f = Font(fName, fWeight, fSize) +f.color = fColor +f.bold = bIsBold +f.setUnderlineStyle(fUnderline.toByte()) +this.workBook!!.insertFont(f) +} +else +{ // TODO: this will screw up linked fonts, + // perhaps, so what to do? + val f = this.workBook!!.getFont(fontId) +f.fontWeight = fWeight +f.setFontName(fName) +f.fontHeight = fSize +f.color = fColor +f.bold = bIsBold +f.setUnderlineStyle(fUnderline.toByte()) +} +} +else if (tnm == "FormatChartArea") +{ // TODO: + // something! + // ChartBorder + // ChartProperties + } +else if (tnm == "Series") +{ // series --> + // Legend Range Category shape typex typey + var legend = "" +var series = "" +var category = "" +var bubble = "" +var dataTypeX = "" +var dataTypeY = "" +var shape = "" +for (x in 0 until xpp.getAttributeCount()) +{ +if (xpp.getAttributeName(x) +.equals("Legend", ignoreCase = true)) +legend = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("Range", ignoreCase = true)) +series = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("Category", ignoreCase = true)) +category = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("Bubbles", ignoreCase = true)) +bubble = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("TypeX", ignoreCase = true)) +dataTypeX = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("TypeY", ignoreCase = true)) +dataTypeY = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("Shape", ignoreCase = true)) +shape = xpp.getAttributeValue(x) +} + // 20070709 KSC: can't add until all cells are added + // ch.addSeriesRange(legend, category, series); + val s = arrayOf(legend, series, category, bubble, dataTypeX, dataTypeY, shape) +val map = maps +.get("chartseries") as HashMap, ChartHandle> +map.put(s, this) +} +else if (tnm == "ChartOptions") +{ // handle + // chart-type-specific + // options such + // as legend + // options + // 20070716 KSC: handle chart-type-specific options in a + // very generic way ... + for (x in 0 until xpp.getAttributeCount()) +{ +this.setChartOption(xpp.getAttributeName(x), xpp +.getAttributeValue(x)) +} +} +else if (tnm == "ThreeD") +{ // handle three-d options + // handle threeD record options in a very generic way + // ... + this.make3D() // default chart - TODO; if mutliple + // charts, handle + for (x in 0 until xpp.getAttributeCount()) +{ // now + // add + // threed + // rec + // options + this.setChartOption(xpp.getAttributeName(x), xpp +.getAttributeValue(x)) +} +} +else if (tnm.endsWith("Axis")) +{ // handle axis specs + // (Label + options ...) + // 20070720 KSC: handle Axis record options ... + var type = 0 +val axis = xpp.getName() +if (axis.equals("XAxis", ignoreCase = true)) +type = XAXIS +else if (axis.equals("YAxis", ignoreCase = true)) +type = YAXIS +else if (axis.equals("ZAxis", ignoreCase = true)) +type = ZAXIS +if (xpp.getAttributeCount() > 0) +{ // then has axis + // options + for (x in 0 until xpp.getAttributeCount()) +{ +this.setAxisOption(type, xpp +.getAttributeName(x), xpp +.getAttributeValue(x)) +} +} +else +{ // no axis options means no axis present; + // remove + this.removeAxis(type) +} +} +else if (tnm == "Series") +{ // handle series data + // Legend Range Category + var legend = "" +var series = "" +var category = "" +var bubble = "" +var dataTypeX = "" +var dataTypeY = "" +var shape = "" +for (x in 0 until xpp.getAttributeCount()) +{ +if (xpp.getAttributeName(x) +.equals("Legend", ignoreCase = true)) +legend = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("Range", ignoreCase = true)) +series = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("Category", ignoreCase = true)) +category = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("Bubbles", ignoreCase = true)) +bubble = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("TypeX", ignoreCase = true)) +dataTypeX = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("TypeY", ignoreCase = true)) +dataTypeY = xpp.getAttributeValue(x) +else if (xpp.getAttributeName(x) +.equals("Shape", ignoreCase = true)) +shape = xpp.getAttributeValue(x) +} + // 20070709 KSC: can't add until all cells are added + // ch.addSeriesRange(legend, category, series); + val s = arrayOf(legend, series, category, bubble, dataTypeX, dataTypeY, shape) +val map = maps +.get("chartseries") as HashMap, ChartHandle> +map.put(s, this) +} +} +else if (eventType == XmlPullParser.END_TAG) +{ +if (xpp.getName() == "Chart") +break +} +eventType = xpp.next() +} +} +catch (e:Exception) { +Logger.logWarn(("ChartHandle.parseXML <" + xpp.getName() + ">: " ++ e.toString())) + // TODO: propogate Exception??? + } + +} + +/******************************************************************** + * OOXML Generation Methods + */ + /** + * Generates OOXML (chartML) for this chart object. + * + *

                + * NOTE: necessary root chartSpace element + namespaces are not set here + * + * @param int rId -reference ID for this chart + * @return String representing the OOXML describing this Chart + */ + fun getOOXML(rId:Int):String { + // TODO: finish 3d options- floor, sideWall, backWall + // TODO: finish axis options + // TODO: printSettings + + // generate OOXML (chartML) + val cooxml = StringBuffer() +try +{ +mychart!!.chartSeries.resetSeriesNumber() // reset series idx + // retrieve pertinent chart data + // axes id's TODO: HANDLE MULTIPLE AXES per chart ... + val catAxisId = Integer +.toString((Math.random() * 1000000).toInt()) +val valAxisId = Integer +.toString((Math.random() * 1000000).toInt()) +val serAxisId = Integer +.toString((Math.random() * 1000000).toInt()) +val thischart:OOXMLChart +if ((mychart is OOXMLChart)) +thischart = mychart as OOXMLChart? +else +{ // XLS->XLSX +thischart = OOXMLChart(mychart!!, workBookHandle) +mychart = thischart +thischart.chartSeries.setParentChart(thischart) +} +thischart.wbh = this.workBookHandle + +cooxml.append(thischart.getOOXML(catAxisId, valAxisId, serAxisId)) + + // TODO: + val chartEmbeds = thischart.chartEmbeds +if (chartEmbeds != null) +{ +var j = 0 +for (i in chartEmbeds!!.indices) +{ +if ((chartEmbeds!!.get(i) as Array)[0] == "userShape") +{ +j++ +cooxml.append("") +} +} +} +} +catch (e:Exception) { +Logger.logErr(("ChartHandle.getOOXML: error generating OOXML. Chart not created: " + e.toString())) +} + +return cooxml.toString() +} + +/** + * generates the OOXML specific for DrawingML, specifying offsets and + * identifying the chart object.

                + * this Drawing ML (OOXML) is distinct from Chart ML (OOXML) which actually + * defines the chart object including series, categories and axes

                + * This is an internal method that is not useful to the end user. + * + * @param int id - the reference id for this chart + * @return String OOXML + */ + fun getChartDrawingOOXML(id:Int):String { +val t = TwoCellAnchor( +(mychart as OOXMLChart).editMovement) +t.setAsChart(id, OOXMLAdapter.stripNonAscii(this.ooxmlName) +.toString(), TwoCellAnchor.convertBoundsFromBIFF8(this +.sheet, mychart!!.bounds!!)) // adjust BIFF8 + // bounds to + // OOXML units + return t.ooxml +} + +/******************************************************************************** + * Parsing OOXML Methods + */ + /** + * defines this chart object based on a Chart ML (OOXML) input Stream (root + * element=c:chartSpace)

                + * This is an internal method that is not useful to the end user. + * + * @param inputStream ii - representing chart OOXML + */ + fun parseOOXML(ii:java.io.InputStream) { + // overlay in title, after layout + // varyColors val= "0" -- after grouping and before ser + + // series colors by theme: + /* + * accent1 - 6 + */ + /** + * chartSpace: chart (Chart) §5.7.2.27 clrMapOvr (Color Map Override) §5.7.2.30 + * date1904 (1904 Date System) §5.7.2.38 externalData (External Data + * Relationship) §5.7.2.63 extLst (Chart Extensibility) §5.7.2.64 lang (Editing + * Language) §5.7.2.87 pivotSource (Pivot Source) §5.7.2.145 printSettings + * (Print Settings) §5.7.2.149 protection (Protection) §5.7.2.150 roundedCorners + * (Rounded Corners) §5.7.2.160 spPr (Shape Properties) §5.7.2.198 style (Style) + * §5.7.2.203 txPr (Text Properties) §5.7.2.217 userShapes (Reference to Chart + * Drawing Part) §5.7.2.222 + */ + try +{ +val thischart = mychart as OOXMLChart? +var drawingOrder = 0 // drawing order of the chart (0=default, 1-9 + // for multiple charts in 1) + var hasPivotTableSource = false + + // remove any undesired formatting from default chart: + this.title = "" // clear any previously set +mychart!!.axes!!.setPlotAreaBgColor(FormatConstants.COLOR_WHITE) +mychart!!.axes!!.setPlotAreaBorder(-1, -1) // remove plot area + // border + + val lastTag = java.util.Stack() // keep + // track + // of + // element + // hierarchy + + val factory = XmlPullParserFactory.newInstance() +factory.setNamespaceAware(true) +val xpp = factory.newPullParser() + +xpp.setInput(ii, null) // using XML 1.0 specification +var eventType = xpp.getEventType() +while (eventType != XmlPullParser.END_DOCUMENT) +{ +if (eventType == XmlPullParser.START_TAG) +{ +val tnm = xpp.getName() // main entry= chartSpace, + // children: lang, chart + lastTag.push(tnm) // keep track of element hierarchy +if (tnm == "chart") +{ // beginning of DrawingML for a + // single image or chart; + // children: title, + // plotArea, legend, + /** + * 5 + * 6 + * 7 + * 8 + * 9 + * 10 + * 11 + * 12 + * + * plotArea contains layout, , serAx, valAx, catAx, dateAx, spPr, + * dTable 13 + * 14 + * + * 15 + * 16 + * 17 + * + * 18 + */ + } +else if (tnm == "lang") +{ +thischart!!.lang = xpp.getAttributeValue(0) +} +else if (tnm == "roundedCorners") +{ +thischart!!.roundedCorners = xpp.getAttributeValue(0) == "1" +} +else if (tnm == "pivotSource") +{ // has a pivot table +hasPivotTableSource = true +} +else if (tnm == "view3D") +{ +ThreeD.parseOOXML(xpp, lastTag, thischart!!) +} +else if (tnm == "layout") +{ +thischart!!.plotAreaLayout = Layout +.parseOOXML(xpp, lastTag).cloneElement() as Layout +} +else if (tnm == "legend") +{ +thischart!!.showLegend(true, false) +thischart!!.ooxmlLegend = io.starter.formats.OOXML.Legend +.parseOOXML(xpp, lastTag, this.workBookHandle) +.cloneElement() as io.starter.formats.OOXML.Legend +thischart!!.ooxmlLegend!! +.fill2003Legend(thischart!!.legend) + // Parse actual CHART TYPE element (barChart, pieChart, + // etc.) + } +else if ((tnm == OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.LINECHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.PIECHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.AREACHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.SCATTERCHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.RADARCHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.SURFACECHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.DOUGHNUTCHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.BUBBLECHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.OFPIECHART] +|| tnm == OOXMLConstants.twoDchartTypes[ChartHandle.STOCKCHART] +|| tnm == OOXMLConstants.threeDchartTypes[ChartHandle.BARCHART] +|| tnm == OOXMLConstants.threeDchartTypes[ChartHandle.LINECHART] +|| tnm == OOXMLConstants.threeDchartTypes[ChartHandle.PIECHART] +|| tnm == OOXMLConstants.threeDchartTypes[ChartHandle.AREACHART] +|| tnm == OOXMLConstants.threeDchartTypes[ChartHandle.SCATTERCHART] +|| tnm == OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART])) +{ // specific + // chart + // type- + ChartType +.parseOOXML(xpp, this.workBookHandle, mychart, drawingOrder++) +lastTag.pop() +} +else if (tnm == "title") +{ +thischart!!.setOOXMLTitle(Title +.parseOOXML(xpp, lastTag, this.workBookHandle) +.cloneElement() as Title, this.workBookHandle) +this.title = thischart!!.ooxmlTitle!!.title +} +else if (tnm == "spPr") +{ // shape properties -- can + // be for plot area or + // chart space + val parent = lastTag.get(lastTag.size - 2) +if (parent == "plotArea") +{ +thischart!!.setSpPr(0, SpPr +.parseOOXML(xpp, lastTag, this.workBookHandle) +.cloneElement() as SpPr) +} +else if (parent == "chartSpace") +{ +thischart!!.setSpPr(1, SpPr +.parseOOXML(xpp, lastTag, this.workBookHandle) +.cloneElement() as SpPr) +} +} +else if (tnm == "txPr") +{ // text formatting +thischart!!.txPr = TxPr +.parseOOXML(xpp, lastTag, this.workBookHandle) +.cloneElement() as TxPr +} +else if (tnm == "catAx") +{ // child of plotArea +mychart!!.axes!! +.parseOOXML(XAXIS, xpp, tnm, lastTag, this.workBookHandle) +} +else if (tnm == "valAx") +{ // child of plotArea +if (mychart!!.axes!!.hasAxis(ChartConstants.XAXIS)) + // usual, + // have + // a + // catAx + // then + // a + // valAx + mychart!!.axes!! +.parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.workBookHandle) +else if (mychart!!.axes!! +.hasAxis(ChartConstants.YAXIS)) + // for bubble + // charts, has + // two valAxes + // and no + // catAx + mychart!!.axes!! +.parseOOXML(ChartConstants.XVALAXIS, xpp, tnm, lastTag, this.workBookHandle) +else + // 2nd val axis is Y axis + mychart!!.axes!! +.parseOOXML(ChartConstants.YAXIS, xpp, tnm, lastTag, this.workBookHandle) +} +else if (tnm == "serAx") +{ // series axis - 3d charts +mychart!!.axes!! +.parseOOXML(ZAXIS, xpp, tnm, lastTag, this.workBookHandle) +} +else if (tnm == "dateAx") +{ // TODO: not finished: + // figure out! + // ?? + } + +} +else if (eventType == XmlPullParser.END_TAG) +{ +lastTag.pop() +val endTag = xpp.getName() +if (endTag == "chartSpace") +{ +setDimensionsRecord() +break // done processing +} +} +if (xpp.getEventType() != XmlPullParser.END_DOCUMENT) +eventType = xpp.next() +else +eventType = XmlPullParser.END_DOCUMENT +} +} +catch (e:Exception) { +Logger.logErr("ChartHandle.parseChartOOXML: " + e.toString()) +} + +} + +/** + * Specifies how to resize or move this Chart upon edit

                + * This is an internal method that is not useful to the end user.

                + * Excel 7/OOXML specific + * + * @param editMovement String OOXML-specific edit movement setting + */ + fun setEditMovement(editMovement:String) { +(mychart as OOXMLChart).editMovement = editMovement +} + +/** + * sets external information linked to or "embedded" in this OOXML chart; can be + * a chart user shape, an image ...

                + * NOTE: a userShape is a drawingml file name which defines the userShape (if + * any)

                + * a userShape is a drawing or shape ontop of a chart + * + * @param String[] embedType, filename e.g. {"userShape", "userShape file name"} + */ + fun addChartEmbed(ce:Array) { +(mychart as OOXMLChart).addChartEmbed(ce) +} + +/** + * set the chart DIMENSIONS record based on the series ranges in the chart + * APPEARS THAT for charts, the DIMENSIONS record merely notes the range of + * values: 0, #points in series, 0, #series + */ + protected fun setDimensionsRecord() { +val series = this.allChartSeriesHandles +val nSeries = series.size +var nPoints = 0 +for (i in series.indices) +{ +try +{ +val coords = ExcelTools +.getRangeCoords(series[i].seriesRange) +if (coords[3] > coords[1]) +nPoints = Math.max(nPoints, coords[3] - coords[1] + 1) // c1-c0 +else +nPoints = Math.max(nPoints, coords[2] - coords[0] + 1) // r1-r0 +} +catch (e:Exception) {} + +} +mychart!!.setDimensionsRecord(0, nPoints, 0, nSeries) +} + +/** + * Method for setting Chart-Type-specific options in a generic fashion e.g. + * charthandle.setChartOption("Stacked", "true"); + * + * + * Note: since most Chart Type Options are interdependent, there are several + * makeXX methods that set the desired group of options e.g. makeStacked(); use + * setChartOption with care + * + * + * Note that not all Chart Types will have every option available + * + * + * Possible Options: + * + * + * "Stacked" - true or false - set Chart Series to be Stacked

                + * "Cluster" - true or false - set Clustered for Column and Bar Chart Types

                + * "PercentageDisplay" - true or false - Each Category is broken down as a + * percentge

                + * "Percentage" - Distance of pie slice from center of pie as % for Pie Charts + * (0 for all others)

                + * "donutSize" - Donut size for Donut Charts Only

                + * "Overlap" - Space between bars (default= 0%)

                + * "Gap" - Space between categories (%) (default=50%)

                + * "SmoothedLine" - true or false - the Line series has a smoothed line

                + * "AnRot" - Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for others + * (3D option)

                + * "AnElev" - Elevation Angle (-90 to 90 degrees) (15 is default) (3D option) + *

                + * "ThreeDScaling" - true or false - 3d effect

                + * "TwoDWalls" - true if 2D walls (3D option)

                + * "PcDist" - Distance from eye to chart (0 to 100) (30 is default) (3D option) + *

                + * "ThreeDBubbles" - true or false - Draw bubbles with a 3d effect

                + * "ShowLdrLines" - true or false - Show Pie and Donut charts Leader Lines

                + * "MarkerFormat" - "0" thru "9" for various marker options @see + * ChartHandle.setMarkerFormat

                + * "ShowLabel" - true or false - show Series/Data Label

                + * "ShowCatLabel" - true or false - show Category Label

                + * "ShowLabelPct" - true or false - show percentage labels for Pie charts

                + * "ShowBubbleSizes" - true or false - show bubble sizes for Bubble charts + * + * + * NOTE: all values must be in String form + * + * @see ChartHandle.getXML + */ + fun setChartOption(op:String, `val`:String) { +mychart!!.setChartOption(op, `val`) +} + +/** + * Method for setting Chart-Type-specific options in a generic fashion e.g. + * charthandle.setChartOption("Stacked", "true"); + * + * @param op + * @param val + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + private fun setChartOption(op:String, `val`:String, nChart:Int) { +mychart!!.setChartOption(op, `val`, nChart) +} + +/** + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return true if Chart has 3D effects, false otherwise + */ + fun isThreeD(nChart:Int):Boolean { +return mychart!!.isThreeD(nChart) +} + +/** + * Make chart 3D if not already + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + * @return ThreeD rec + */ + internal fun initThreeD(nChart:Int):ThreeD { +return mychart!!.initThreeD(nChart, this.getChartType(nChart)) +} + +/** + * @param int Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) + * @return String XML representation of the desired Axis + */ + private fun getAxisOptionsXML(Axis:Int):String { +return mychart!!.axes!!.getAxisOptionsXML(Axis) +} + +/** + * Returns the Axis Label Placement or position as an int + * + * + * One of:

                + * Axis.INVISIBLE - axis is hidden

                + * Axis.LOW - low end of plot area

                + * Axis.HIGH - high end of plot area

                + * Axis.NEXTTO- next to axis (default) + * + * @param int Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) + * @return int - one of the Axis Label placement constants above + */ + fun getAxisPlacement(Axis:Int):Int { +return mychart!!.axes!!.getAxisPlacement(Axis) +} + +/** + * Sets the Axis labels position or placement to the desired value (these match + * Excel placement options) + * + * + * Possible options:

                + * Axis.INVISIBLE - hides the axis

                + * Axis.LOW - low end of plot area

                + * Axis.HIGH - high end of plot area

                + * Axis.NEXTTO- next to axis (default) + * + * @param int Axis - one of the Axis constants (XAXIS, YAXIS or ZAXIS) + * @param Placement - int one of the Axis placement constants listed above + */ + fun setAxisPlacement(Axis:Int, Placement:Int) { +mychart!!.axes!!.setAxisPlacement(Axis, Placement) +mychart!!.setDirtyFlag(true) +} + + /* + * returns the desired axis If bCreateIfNecessary, will + * creates if doesn't exist + * + * @return Axis Object / private Axis getAxis(int axisType, + * boolean + * bCreateIfNecessary) { return mychart.getAxis(axisType, + * bCreateIfNecessary); } + */ + + /** + * removes the desired Axis from the Chart + * + * @param int axisType - one of the Axis constants (XAXIS, YAXIS or ZAXIS) + */ + fun removeAxis(axisType:Int) { +mychart!!.axes!!.removeAxis(axisType) +mychart!!.setDirtyFlag(true) +} + +/** + * Set non-axis chart font id for title, default, etc

                + * For Internal Use Only + * + * @param String type - font type + * @param String val - font id + */ + fun setChartFont(type:String, `val`:String) { +mychart!!.setChartFont(type, `val`) +} + + // 20070802 KSC: Debug uility to write out chartrecs + /* + * For internal debugging use only + * + * public void writeChartRecs(String fName) { + * mychart.writeChartRecs(fName); } + * + * /** set DataLabels option for this chart + * + * @param String type - see below + * + * @param boolean bShowLegendKey - true if show legend key, + * false otherwise + *

                possible String type values:
                Series
                Category + *
                Value + *
                Percentage (Only for Pie Charts)
                Bubble (Only for + * Bubble Charts) + *
                X Value (Only for Bubble Charts)
                Y Value (Only + * for Bubble Charts) + *
                CandP + * + *

                NOTE: not 100% implemented at this time + */ + fun setDataLabel(/* [] */ type:String, bShowLegendKey:Boolean) { + /* + * for now, only 1 option is valid - multiple legend + * settings e.g. Category and + * Value are not figured out yet for (int i= 0; i < + * type.length; i++) + * mychart.setChartOption("DataLabel", type[i]); + */ + if (!bShowLegendKey) +mychart!!.setChartOption("DataLabel", type) +else +mychart!!.setChartOption("DataLabelWithLegendKey", type) +} + +/** + * shows or removes the Data Table for this chart + * + * @param boolean bShow - true if show data table + */ + fun showDataTable(bShow:Boolean) { +mychart!!.showDataTable(bShow) +} + +/** + * shows or hides the Chart legend key + * + * @param booean bShow - true if show legend, false to hide + * @param boolean vertical - true if show vertically, false for horizontal + */ + fun showLegend(bShow:Boolean, vertical:Boolean) { +mychart!!.showLegend(bShow, vertical) +} + +/** + * returns true if Chart has a Data Legend Key showing + * + * @return true if Chart has a Data Legend Key showing + */ + fun hasDataLegend():Boolean { +return mychart!!.hasDataLegend() +} + + fun removeLegend() { +mychart!!.removeLegend() +} + + // 20070905 KSC: Group chart options for ease of setting + // almost all charts have these specific ChartTypes: + + /** + * makes this Chart Stacked

                + * sets the group of options necessary to create a stacked chart

                + * For Chart Types:

                + * BAR, COL, LINE, AREA, PYRAMID, PYRAMIDBAR, CYLINDER, CYLINDERBAR, CONE, + * CONEBAR + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + @Deprecated("") + fun makeStacked(nChart:Int) { // bar, col, line, area, pyramid col + + // bar, cone col + bar, cylinder col + // + bar + val chartType = this.getChartType(nChart) +this.setChartOption("Stacked", "true", nChart) +when (chartType) { +ChartConstants.BARCHART, ChartConstants.COLCHART -> this.setChartOption("Overlap", "-100", nChart) +ChartConstants.CYLINDERCHART, ChartConstants.CYLINDERBARCHART, ChartConstants.CONECHART, ChartConstants.CONEBARCHART, ChartConstants.PYRAMIDCHART, ChartConstants.PYRAMIDBARCHART -> { +this.setChartOption("Overlap", "-100", nChart) +val td = this.initThreeD(nChart) +td.setChartOption("Cluster", "false") +} +ChartConstants.LINECHART -> this.setChartOption("Percentage", "0", nChart) +ChartConstants.AREACHART -> { +this.setChartOption("Overlap", "-100", nChart) +this.setChartOption("Percentage", "25", nChart) +this.setChartOption("SmoothedLine", "true", nChart) +} +} +} + +/** + * makes this Chart 100% Stacked

                + * sets the group of options necessary to create a 100% stacked chart

                + * For Chart Types:

                + * BAR, COL, LINE, PYRAMID, PYRAMIDBAR, CYLINDER, CYLINDERBAR, CONE, CONEBAR + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + @Deprecated("") + fun make100PercentStacked(nChart:Int) { // bar, col, line, pyramid + // col + bar, cone col + + // bar, cylinder col + + // bar + val chartType = this.getChartType(nChart) +this.setChartOption("Stacked", "true", nChart) +this.setChartOption("PercentageDisplay", "true", nChart) +when (chartType) { +ChartConstants.COLCHART // + pyramid +, ChartConstants.BARCHART -> this.setChartOption("Overlap", "-100", nChart) +ChartConstants.CYLINDERCHART, ChartConstants.CYLINDERBARCHART, ChartConstants.CONECHART, ChartConstants.CONEBARCHART, ChartConstants.PYRAMIDCHART, ChartConstants.PYRAMIDBARCHART -> { +this.setChartOption("Overlap", "-100", nChart) +val td = this.initThreeD(nChart) +td.setChartOption("Cluster", "false") +} +ChartConstants.LINECHART -> {} +} +} + +/** + * makes this Chart Stacked with a 3D Effect

                + * sets the group of options necessary to create a Stacked 3D chart

                + * For Chart Types:

                + * BAR, COL, AREA + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + fun makeStacked3D(nChart:Int) { // bar, col, area +val chartType = this.getChartType(nChart) +this.setChartOption("Stacked", "true", nChart) +val td = this.initThreeD(nChart) +td.setChartOption("AnRot", "20") +td.setChartOption("ThreeDScaling", "true") +td.setChartOption("TwoDWalls", "true") +when (chartType) { +ChartConstants.COLCHART, ChartConstants.BARCHART -> this.setChartOption("Overlap", "-100", nChart) +ChartConstants.AREACHART -> { +this.setChartOption("Percentage", "25", nChart) +this.setChartOption("SmoothedLine", "true", nChart) +} +} +} + +/** + * makes this Chart 100% Stacked with a 3D Effect

                + * sets the group of options necessary to create a 100% Stacked 3D chart

                + * For Chart Types:

                + * BAR, COL, AREA + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + @Deprecated("") + fun make100PercentStacked3D(nChart:Int) { // bar, col, area +val chartType = this.getChartType(nChart) +this.setChartOption("Stacked", "true", nChart) +this.setChartOption("PercentageDisplay", "true", nChart) +when (chartType) { +ChartConstants.COLCHART // + pyramid +, ChartConstants.BARCHART -> { +this.setChartOption("Overlap", "-100", nChart) +val td = this.initThreeD(nChart) +td.setChartOption("AnRot", "20") +td.setChartOption("ThreeDScaling", "true") +td.setChartOption("TwoDWalls", "true") +} +ChartConstants.AREACHART -> { +this.setChartOption("Percentage", "25", nChart) +this.setChartOption("SmoothedLine", "true", nChart) +} +} +} + +/** + * makes the desired Chart hava a 3D effect

                + * where nChart 0= default, 1-9=multiple charts in one

                + * sets the group of options necessary to create a 3D chart

                + * For Chart Types:

                + * BARCHART, COLCHART, LINECHART, PIECHART, AREACHART, BUBBLECHART, + * PYRAMIDCHART, CYLINDERCHART, CONECHART + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + @Deprecated("use setChartType(chartType, nChart, is3d, isStacked,\n"+ +" is100%Stacked) instead") +@JvmOverloads fun make3D(nChart:Int = 0) { // bar, col, line, pie, area, bubble, + // pyramid, cone, cylinder + val chartType = this.getChartType(nChart) +var td:ThreeD? = null +when (chartType) { +ChartConstants.COLCHART, ChartConstants.BARCHART -> { +td = this.initThreeD(nChart) +td!!.setChartOption("AnRot", "20") +td!!.setChartOption("ThreeDScaling", "true") +td!!.setChartOption("TwoDWalls", "true") +} +ChartConstants.CYLINDERCHART, ChartConstants.CONECHART, ChartConstants.PYRAMIDCHART -> { +td = this.initThreeD(nChart) +td!!.setChartOption("Cluster", "false") +} +ChartConstants.AREACHART -> { +this.setChartOption("Percentage", "25", nChart) +this.setChartOption("SmoothedLine", "true", nChart) +td = this.initThreeD(nChart) +td!!.setChartOption("AnRot", "20") +td!!.setChartOption("ThreeDScaling", "true") +td!!.setChartOption("TwoDWalls", "true") +td!!.setChartOption("Perspective", "true") +} +ChartConstants.PIECHART, ChartConstants.LINECHART -> this.initThreeD(nChart) // just create a threeD rec w/ no extra +ChartConstants.BUBBLECHART -> { +this.setChartOption("Percentage", "25", nChart) +this.setChartOption("SmoothedLine", "true", nChart) +this.setChartOption("ThreeDBubbles", "true", nChart) +td = this.initThreeD(nChart) // 20081228 KSC +} +}// options +} + + // more specialized option sets + + /** + * makes this Chart Clusted with a 3D effect

                + * sets the group of options necessary to create a Clusted 3D chart

                + * For Chart Types:

                + * BAR, COL + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + @Deprecated("") + fun makeClustered3D(nChart:Int) { // only for Column and Bar (?) +val chartType = this.getChartType(nChart) +when (chartType) { +ChartConstants.BARCHART, ChartConstants.COLCHART -> { +val td = this.initThreeD(nChart) +td.setChartOption("AnRot", "20") +td.setChartOption("Cluster", "true") +td.setChartOption("ThreeDScaling", "true") +td.setChartOption("TwoDWalls", "true") +} +} +} + +/** + * makes this chart's wedges exploded i.e. separated

                + * For Chart Types:

                + * PIECHART, DOUGHNUTCHART + * + */ + @Deprecated("") + fun makeExploded() { // pie, donut +val chartType = this.chartType +when (chartType) { +ChartConstants.DOUGHNUTCHART -> { +this.setChartOption("SmoothedLine", "true") +this.setChartOption("ShowLdrLines", "true") +this.setChartOption("Percentage", "25") +} +ChartConstants.PIECHART -> { +this.setChartOption("ShowLdrLines", "true") +this.setChartOption("Percentage", "25") +} +}// ShowLdrLines="true" Percentage="25"/> + // exploded donut: ShowLdrLines="true" Donut="50" + // Percentage="25" + // SmoothedLine="true"/> +} + +/** + * makes this chart's wedges exploded 3D i.e. separated with a 3D effect

                + * For Chart Types:

                + * PIECHART, DOUGHNUTCHART + * + * @param nChart number and drawing order of the desired chart (default= 0 max=9 + * where 1-9 indicate an overlay chart) + */ + @Deprecated("") + fun makeExploded3D(nChart:Int) { // pie + // ShowLdrLines="true" Percentage="25" + // AnRot="236" + val chartType = this.getChartType(nChart) +when (chartType) { +ChartConstants.DOUGHNUTCHART, ChartConstants.PIECHART -> { +this.setChartOption("ShowLdrLines", "true", nChart) +this.setChartOption("Percentage", "25", nChart) +val td = this.initThreeD(nChart) +td.setChartOption("AnRot", "236") +} +} +} + + /* + * NOT IMPLEMENTED YET TODO: IMPLEMENT Make this Chart have + * smoothed lines + * (Scatter only) + * + * public void makeSmoothedLines() { // scatter + * //Percentage="25" + * SmoothedLine="true } + * + * public void makeWireFrame() { // surface // NO ColorFill, + * only // + * Percentage="25" SmoothedLine="true" // AnRot="20" + * Perspective="true" + * ThreeDScaling="true" TwoDWalls="true"/> // all else + * should be default for + * surface charts } public void makeContour() { // surface + * -- for wireframe + * surface, no ColorFill //ColorFill="true" Percentage="25" + * SmoothedLine="true"/> // AnElev="90" pcDist="0" + * Perspective="true" + * ThreeDScaling="true" TwoDWalls="true" } + */ + + /** + * set the marker format style for this chart

                + * one of:

                + * 0 = no marker

                + * 1 = square

                + * 2 = diamond

                + * 3 = triangle

                + * 4 = X

                + * 5 = star

                + * 6 = Dow-Jones

                + * 7 = standard deviation

                + * 8 = circle

                + * 9 = plus sign

                + * For Chart Types:

                + * LINE, SCATTER + * + * @param int imf - marker format constant from list above + */ + fun setMarkerFormat(imf:Int) { // line, scatter ... +this.setChartOption("MarkerFormat", (imf).toString()) +} + +/** + * utility to add a JSON object

                + * This is an internal method that is not useful to the end user. + * + * @param source + * @param input + */ + protected fun inputJSONObject(source:JSONObject?, input:JSONObject) { +if (source != null) +{ +try +{ +for (j in 0 until input.names().length()) +{ +source!!.put(input.names().getString(j), input +.get(input.names().getString(j))) +} +} +catch (e:JSONException) { +Logger.logErr("Error inputting JSON Object: " + e) +} + +} +} + +/** + * /** Take current Chart object and return the SVG code necessary to define it, + * scaled to the desired percentage e.g. 0.75= 75% + * + * @param scale double scale factor + * @return String SVG + */ + fun getSVG(scale:Double):String { +val chartMetrics = mychart!!.getMetrics(workBookHandle) // build + // or + // retrieve + // Chart + // Metrics + // --> + // dimensions + // + + // series + // data + // ... + + val svg = StringBuffer() + // required header + // svg.append("\r\n"); // referneces + // the DTD + // svg.append("\r\n"); + + // Define SVG Canvas: + svg.append(("\r\n")) +svg.append(("")) // scale + // chart + // -default + // scale=1 + // == 100% + // JavaScript hooks + svg.append(javaScript) + + // Data Legend Box -- do before drawing plot area as legends + // box may change plot + // area coordinates + val legendSVG = getLegendSVG(chartMetrics) // but have to append it + // after because should + // overlap the + // plotarea + + val bgclr = this.mychart!!.plotAreaBgColor + // setup gradients + svg.append("") +svg.append("") +svg.append(("")) +svg.append(("")) +svg.append("") +svg.append("") + + // PLOT AREA BG + RECT + // rectangle around entire chart canvas + if ((!(mychart is OOXMLChart) || !(mychart as OOXMLChart).roundedCorners)) +svg.append(("\r\n")) +else + // OOXML rounded corners + svg.append(("\r\n")) + + // actual plot area + svg.append(("\r\n")) + + // AXES, IF PRESENT - DO BEFORE ACTUAL SERIES DATA SO DRAWS + // CORRECTLY + ADJUST + // CHART DIMENSIONS + svg.append(mychart!!.axes!!.getSVG(XAXIS, chartMetrics, mychart!! +.chartSeries.getCategories())) +svg.append(mychart!!.axes!!.getSVG(YAXIS, chartMetrics, mychart!! +.chartSeries.getCategories())) + // TODO: Z Axis + + // After Axes and gridlines (if present), + // ACTUAL bar/series/area/etc. svg generated from series and + // scale data + svg.append(this.mychart!!.chartObject.getSVG(chartMetrics, mychart!! +.axes!!.metrics, mychart!!.chartSeries)) + +svg.append(legendSVG) // append legend SVG obtained above + + // CHART TITLE + + if (mychart!!.titleTd != null) +svg.append(mychart!!.titleTd!!.getSVG(chartMetrics)) + +svg.append("") +svg.append("") + /* + * //KSC: TESTING: REMOVE WHEN DONE if + * (WorkBookFactory.PID==WorkBookFactory.E360) { // save svg + * for testing + * purposes try { java.io.File f= new java.io.File( + * "c:/eclipse/workspace/testfiles/io.starter.OpenXLS/output/charts/FromSheetster.svg" + * ); java.io.FileOutputStream fos= new + * java.io.FileOutputStream(f); + * fos.write(svg.toString().getBytes()); fos.flush(); + * fos.close(); } catch + * (Exception e) {} } + */ + return svg.toString() +} + +private fun getLegendSVG(chartMetrics:HashMap):String? { +try +{ +return mychart!!.legend!!.getSVG(chartMetrics, mychart!! +.chartObject, mychart!!.chartSeries) +} +catch (ne:NullPointerException) { // no legend?? +return null +} + +} + +/** + * debugging utility remove when done + */ + fun WriteMainChartRecs(fName:String) { +mychart!!.chartObject.WriteMainChartRecs(fName) +} + + fun getAxis(type:Int):Axis? { + // TODO Auto-generated method stub + return null +} + +companion object { + // 20080114 KSC: delegate so visible + val BARCHART = ChartConstants.BARCHART + val COLCHART = ChartConstants.COLCHART + val LINECHART = ChartConstants.LINECHART + val PIECHART = ChartConstants.PIECHART + val AREACHART = ChartConstants.AREACHART + val SCATTERCHART = ChartConstants.SCATTERCHART + val RADARCHART = ChartConstants.RADARCHART + val SURFACECHART = ChartConstants.SURFACECHART + val DOUGHNUTCHART = ChartConstants.DOUGHNUTCHART + val BUBBLECHART = ChartConstants.BUBBLECHART + val OFPIECHART = ChartConstants.OFPIECHART + val PYRAMIDCHART = ChartConstants.PYRAMIDCHART + val CYLINDERCHART = ChartConstants.CYLINDERCHART + val CONECHART = ChartConstants.CONECHART + val PYRAMIDBARCHART = ChartConstants.PYRAMIDBARCHART + val CYLINDERBARCHART = ChartConstants.CYLINDERBARCHART + val CONEBARCHART = ChartConstants.CONEBARCHART + val RADARAREACHART = ChartConstants.RADARAREACHART + val STOCKCHART = ChartConstants.STOCKCHART + + // legacy + val BAR = BARCHART + val COL = COLCHART + val LINE = LINECHART + val PIE = PIECHART + val AREA = AREACHART + val SCATTER = SCATTERCHART + val RADAR = RADARCHART + val SURFACE = SURFACECHART + val DOUGHNUT = DOUGHNUTCHART + val BUBBLE = BUBBLECHART + val RADARAREA = RADARAREACHART + val PYRAMID = PYRAMIDCHART + val CYLINDER = CYLINDERCHART + val CONE = CONECHART + val PYRAMIDBAR = PYRAMIDBARCHART + val CYLINDERBAR = CYLINDERBARCHART + val CONEBAR = CONEBARCHART + // axis types + val XAXIS = ChartConstants.XAXIS + val YAXIS = ChartConstants.YAXIS + val ZAXIS = ChartConstants.ZAXIS + val XVALAXIS = ChartConstants.XVALAXIS // an + // X + // axis + // type + // but + // VAL + // records + // coordinates + val X = 0 + val Y = 1 + val WIDTH = 2 + val HEIGHT = 3 + +/** + * returns the encompassing range for this chart, or null if the chart data is + * too complex to represent + * + * @param jsonDataRange + * @return + */ + fun getEncompassingDataRange(jsonDataRange:JSONObject):IntArray? { +try +{ +val catrange = jsonDataRange.get("c").toString() +val sheet = catrange.substring(0, catrange.indexOf('!')) +val retVals = ExcelTools.getRangeRowCol(catrange) +val nSeries = jsonDataRange.getJSONArray("Series").length() +for (i in 0 until nSeries) +{ +val series = jsonDataRange +.getJSONArray("Series").get(i) as JSONObject +val serrange = series.get("v").toString() +if (!serrange.startsWith(sheet)) +continue +var locs = ExcelTools.getRangeRowCol(serrange) +try +{ +if (locs[0] < retVals[0]) +retVals[0] = locs[0] +if (locs[1] < retVals[1]) +retVals[1] = locs[1] +if (locs[2] > retVals[2]) +retVals[2] = locs[2] +if (locs[3] > retVals[3]) +retVals[3] = locs[3] + +val legendrange = series.get("l").toString() +locs = ExcelTools.getRowColFromString(legendrange) +if (locs[0] < retVals[0]) +retVals[0] = locs[0] +if (locs[1] < retVals[1]) +retVals[1] = locs[1] +if (locs[0] > retVals[2]) +retVals[2] = locs[0] +if (locs[1] > retVals[3]) +retVals[3] = locs[1] + +if (series.has("b")) +{ +val bubblerange = series.get("b").toString() +locs = ExcelTools.getRangeRowCol(serrange) +if (locs[0] < retVals[0]) +retVals[0] = locs[0] +if (locs[1] < retVals[1]) +retVals[1] = locs[1] +if (locs[2] > retVals[2]) +retVals[2] = locs[2] +if (locs[3] > retVals[3]) +retVals[3] = locs[3] +} +} +catch (e:Exception) { + // just continue + } + +} +return retVals +} +catch (e:Exception) {} + +return null + /* + * while (ptgs.hasNext()) { PtgRef pr= (PtgRef) ptgs.next(); + * // PtgRef pr= + * (PtgRef)refs[i]; int[] locs = pr.getIntLocation(); for + * (int x=0;x<2;x++) { + * if((locs[x]retValues[x]))retValues[x]=locs[x]; } i++; } + */ + } + +/** + * Static method to create a new chart on WorkSheet sheet of type chartType with + * chart Options options

                + * After creating, you can set the chart title via ChartHandle.setTitle

                + * and Position via ChartHandle.setRelativeBounds (row/col-based) or + * ChartHandle.setCoords (pixel-based)

                + * as well as several other customizations possible + * + * @param book WorkBookHandle + * @param sheet WorkSheetHandle + * @param chartType one of:

                + * BARCHART

                + * COLCHART

                + * LINECHART

                + * PIECHART

                + * AREACHART

                + * SCATTERCHART

                + * RADARCHART

                + * SURFACECHART

                + * DOUGHNUTCHART

                + * BUBBLECHART

                + * RADARAREACHART

                + * PYRAMIDCHART

                + * CYLINDERCHART

                + * CONECHART

                + * PYRAMIDBARCHART

                + * CYLINDERBARCHART

                + * CONEBARCHART + * @param options EnumSet + * @return + * @see ChartHandle.ChartOptions + * + * @see setChartType + */ + fun createNewChart(sheet:WorkSheetHandle, chartType:Int, options:EnumSet):ChartHandle { + // Create Initial Basic Chart + val cht = sheet.workBook!!.createChart("", sheet) + // Change Chart Type with Desired Options: + cht!!.setChartType(chartType, 0, options) +return cht +} +} +}/** + * Changes or adds a Series to the chart via Series Index. Each bar, line or + * wedge in a chart represents a Series.

                + * If the Series index is greater than the number of series already present in + * the chart, the series will be added to the end.

                + * Otherwise the Series at the index position will be altered.

                + * This method allows altering of every aspect of the Series: Data (Series) + * Range, Legend Text, Legend Cell Address, Category Range and/or Bubble Range. + * + * @param int index - the series index. If greater than the number of series + * already present in the chart, the series will be added to the end + * @param String legendCell - String representation of Legend Cell Address + * @param String legendText - String Legend text + * @param String categoryRange - String representation of Category Range (should be + * same for all series) + * @param String seriesRange - String representation of the Series Data Range for + * this series + * @param String bubbleRange - String representation of Bubble Range (representing + * bubble sizes), if bubble chart. null if not + * @return a ChartSeriesHandle representing the new or altered Series + * @throws CellNotFoundException + */// for + // default + // chart +/** + * Adds a new Series to the chart. Each bar, line or wedge in a chart represents + * a Series. + * + * @param String legendAddress - The cell address defining the legend for the + * series + * @param Srring legendText - Text of the legend + * @param String categoryRange - Cell Range defining the category (normally will be + * the same range for all series) + * @param String seriesRange - Cell range defining the data points of the series + * @param String bubbleRange - Cell range defining the bubble sizes for this series + * (bubble charts only) + * @return ChartSeriesHandle representing the new series + * @throws CellNotFoundException + */// for + // default + // chart +/** + * remove the Series (bar, line or wedge) at the desired index + * + * @param int index - series index (valid values: 0 to + * getAllChartSeriesHandles().length-1) + * @see getAllChartSeriesHandles + */// -1 flag for all series +/** + * Appends a series one row below the last series in the chart. + * + * + * This can be utilized when programmatically adding rows of data that should be + * reflected in the chart.

                + * Legend cell will be incremented by one row if a reference. Category range + * will stay the same. + * + *

                + * In order for this method to work properly the chart must have row-based + * series. If your chart utilizes column-based series, then you need to append a + * category. + * + * @return ChartSeriesHandle representing newly added series + * @see ChartHandle.appendRowCategoryToChart + */// do for default chart (0) +/** + * Append a row of categories to the bottom of the chart.

                + * Expands all Series to include the new bottom row.

                + * To be utilized when expanding a chart to encompass more data that has a + * col-based series. + * + * @see ChartHandle.appendRowSeriesToChart + */// default chart +/** + * makes the default Chart hava a 3D effect

                + * sets the group of options necessary to create a 3D chart

                + * For Chart Types:

                + * BARCHART, COLCHART, LINECHART, PIECHART, AREACHART, BUBBLECHART, + * PYRAMIDCHART, CYLINDERCHART, CONECHART + * + */// bar, col, line, pie, area, bubble, pyramid, cone, + // cylinder diff --git a/src/main/java/io/starter/OpenXLS/ChartSeriesHandle.java b/src/main/java/io/starter/OpenXLS/ChartSeriesHandle.kt similarity index 56% rename from src/main/java/io/starter/OpenXLS/ChartSeriesHandle.java rename to src/main/java/io/starter/OpenXLS/ChartSeriesHandle.kt index 10e9d02..0d211de 100644 --- a/src/main/java/io/starter/OpenXLS/ChartSeriesHandle.java +++ b/src/main/java/io/starter/OpenXLS/ChartSeriesHandle.kt @@ -20,27 +20,27 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.OpenXLS; +package io.starter.OpenXLS //IMPORT OOXML-specific items -import io.starter.formats.XLS.charts.Ai; -import io.starter.formats.XLS.charts.Series; +import io.starter.formats.XLS.charts.Ai +import io.starter.formats.XLS.charts.Series /** * Chart Series Handle allows for manipulation of Chart Series within a Chart Handle. - *
                + *

                * Charts are typically made up of two data elements, series data and category data. * When Microsoft Excel creates a chart, it assigns either the worksheet rows as series data (and the * columns as category data), or the worksheet columns as the series data (and the rows as categories). - *
                + *

                * Excel tries to minimize the number of series by default, unless both rows and columns are equal, in which case * the default is to have the rows as series. - *
                + *

                * Typically there will be one series object per row (or column if that is the series) of data. In most cases, the categories * will be the same for every series. - *
                + *

                * As there are series for every row of data, when performing actions such as adding a column to a chart that has it's series * arranged by row, it will be necessary to modify every series within the chart. ChartSeriesHandle is available to * make this process easier. @@ -48,227 +48,255 @@ * @see ChartHandle */ -public class ChartSeriesHandle { - private Series myseries; - private WorkBookHandle wbh; +class ChartSeriesHandle +/** + * Constructor, used internally. For public use get series from a ChartHandle + * + * @param Series series + * @param WorkBook wbk - WorkBook the Chart is attached to + */ +(private val myseries: Series, private val wbh: WorkBookHandle) { /** - * Constructor, used internally. For public use get series from a ChartHandle + * returns the Cell Range String representing the Data in this Series Object. * - * @param Series series - * @param WorkBook wbk - WorkBook the Chart is attached to + * @return String Cell Range representing Series Data e.g. Sheet1!A1:A12 */ - public ChartSeriesHandle(Series series, WorkBookHandle wbk) { - myseries = series; - wbh = wbk; - } - /** - * returns the Cell Range String representing the Data in this Series Object. + * sets the data for this Series to be obtained from a new Cell Range. + *

                + * Note that if you set the size of this series to a different amount of data than other Series + * in the chart you could have unexpected results. * - * @return String Cell Range representing Series Data e.g. Sheet1!A1:A12 + * @param String seriesRange - a Cell Range representing Series Data e.g. Sheet1!A1:A12 */ - public String getSeriesRange() { - return myseries.getSeriesValueAi().toString(); - } + // 20080215 KSC: Added + // "" + // 20070711 KSC: update value (series) count for series + var seriesRange: String + get() = myseries.seriesValueAi!!.toString() + set(seriesRange) { + myseries.parentChart!!.setMetricsDirty() + val ai = myseries.seriesValueAi + ai!!.parentChart = myseries.parentChart + ai.setSheet(myseries.parentChart!!.sheet) + if (ai.workBook == null) ai.workBook = wbh.workBook + ai.changeAiLocation(ai.toString(), seriesRange) + try { + val coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(seriesRange) + myseries.valueCount = coords[4] + myseries.parentChart!!.setMetricsDirty() + } catch (e: Exception) { + + } + + } /** * returns the Cell Range String representing the Categories (usually the X Axis) for the Chart - *
                Note that Category typically stays constant for all Series in the Chart + *

                Note that Category typically stays constant for all Series in the Chart * * @return String Category Cell Range e.g. Sheet1!A1:C1 */ - public String getCategoryRange() { - return myseries.getCategoryValueAi().toString(); - } + /** + * sets the Category from a new Cell Range. + * + * + * IMPORTANT! In most cases, the category should be set to the same Cell Range for all Series in the chart. + *

                This method is available for complex charts, but in most cases the ideal way to handle this call is through + *

                chartHandle.changeCategoryRange. + * + * @param String categoryRange - new Category Cell Range + */ + // 20070711 KSC: update Category Count for this series + var categoryRange: String + get() = myseries.categoryValueAi!!.toString() + set(categoryRange) { + myseries.parentChart!!.setMetricsDirty() + val ai = myseries.categoryValueAi + if (ai!!.workBook == null) ai.workBook = wbh.workBook + ai.changeAiLocation(ai.toString(), categoryRange) + try { + val coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(categoryRange) + myseries.categoryCount = coords[4] + myseries.parentChart!!.setMetricsDirty() + } catch (e: Exception) { + + } + + } /** * returns the Cell Range String representing Bubble Size for this Series (Bubble Charts only) * * @return String Bubble Sizes Cell Range e.g. Sheet1!A1:C1 */ - public String getBubbleSizes() { - return myseries.getBubbleValueAi().toString(); - } + val bubbleSizes: String + get() = myseries.bubbleValueAi!!.toString() /** - * returns true if this chart has Bubble Sizes + * returns the Legend text for this Series * - * @return + * @return String Legend text for this Series + * @see getSeriesLegendReference */ - public boolean hasBubbleSizes() { - return myseries.hasBubbleSizes(); - } - /** - * sets the data for this Series to be obtained from a new Cell Range. - *
                - * Note that if you set the size of this series to a different amount of data than other Series - * in the chart you could have unexpected results. + * Set the Legend text for this Series + *

                note: series legend will then not be linked to a particular cell. if you want to change the legend + * reference, use setSeriesLegendRef * - * @param String seriesRange - a Cell Range representing Series Data e.g. Sheet1!A1:A12 + * @param String legend - Legend Text + * @see setSeriesLegendRef */ - public void setSeriesRange(String seriesRange) { - myseries.getParentChart().setMetricsDirty(); - Ai ai = myseries.getSeriesValueAi(); - ai.setParentChart(myseries.getParentChart()); // 20080215 KSC: Added - ai.setSheet(myseries.getParentChart().getSheet());// "" - if (ai.getWorkBook() == null) ai.setWorkBook(wbh.getWorkBook()); - ai.changeAiLocation(ai.toString(), seriesRange); - try { // 20070711 KSC: update value (series) count for series - int[] coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(seriesRange); - myseries.setValueCount(coords[4]); - myseries.getParentChart().setMetricsDirty(); - } catch (Exception e) { - + var seriesLegend: String + get() = myseries.legendText + set(legend) { + myseries.setLegend(legend, this.wbh) + myseries.parentChart!!.setMetricsDirty() } - } /** - * sets the Category from a new Cell Range. - *

                - * IMPORTANT! In most cases, the category should be set to the same Cell Range for all Series in the chart. - *
                This method is available for complex charts, but in most cases the ideal way to handle this call is through - *
                chartHandle.changeCategoryRange. + * returns the Legend Cell Reference, if any * - * @param String categoryRange - new Category Cell Range + * @return String Cell Address representing the Legend for this Series */ - public void setCategoryRange(String categoryRange) { - myseries.getParentChart().setMetricsDirty(); - Ai ai = myseries.getCategoryValueAi(); - if (ai.getWorkBook() == null) ai.setWorkBook(wbh.getWorkBook()); - ai.changeAiLocation(ai.toString(), categoryRange); - try { // 20070711 KSC: update Category Count for this series - int[] coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(categoryRange); - myseries.setCategoryCount(coords[4]); - myseries.getParentChart().setMetricsDirty(); - } catch (Exception e) { - + val seriesLegendReference: String? + get() { + val ai = myseries.legendAi + return ai?.definition } - } /** - * set the Cell Range for the Bubbles in this Seeries (Bubble Chart Only) + * gets the Chart Category Data Type * - * @param String bubbleSizes - Cell Range for Bubble Sizes + * @return int Data Type of the Category */ - public void setBubbleRange(String bubbleSizes) { - if (bubbleSizes != null && !bubbleSizes.equals("")) { - myseries.getParentChart().setMetricsDirty(); - Ai ai = myseries.getBubbleValueAi(); - if (ai.getWorkBook() == null) ai.setWorkBook(wbh.getWorkBook()); - ai.changeAiLocation(ai.toString(), bubbleSizes); - ai.setRt(2); - try { // also update Bubble Count for this series - int[] coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(bubbleSizes); - myseries.setBubbleCount(coords[4]); - myseries.getParentChart().setMetricsDirty(); - } catch (Exception e) { - - } + /** + * sets the Chart Category Data Type + * + * @param int i - Category Data Type + */ + var categoryDataType: Int + get() = myseries.categoryDataType + set(i) { + myseries.categoryDataType = i + myseries.parentChart!!.setMetricsDirty() } - } /** - * Set the Legend text for this Series - *
                note: series legend will then not be linked to a particular cell. if you want to change the legend - * reference, use setSeriesLegendRef + * gets the Series Data Type * - * @param String legend - Legend Text - * @see setSeriesLegendRef + * @return int Data Type of this Series */ - public void setSeriesLegend(String legend) { - myseries.setLegend(legend, this.wbh); - myseries.getParentChart().setMetricsDirty(); - } - /** - * returns the Legend text for this Series + * sets the Series Data Type * - * @return String Legend text for this Series - * @see getSeriesLegendReference + * @param int i - Series Data Type */ - public String getSeriesLegend() { - return myseries.getLegendText(); - } + var seriesDataType: Int + get() = myseries.valueDataType + set(i) { + myseries.valueDataType = i + } /** - * returns the Legend Cell Reference, if any + * returns a constant that represents the bar shape for this Series + *

                This is an internal method that is not useful to the end user. * - * @return String Cell Address representing the Legend for this Series + * @return int DataPoint shape for this Series (3d Bar, Pyramid ...) */ - public String getSeriesLegendReference() { - Ai ai = myseries.getLegendAi(); - if (ai != null) return ai.getDefinition(); - return null; - } - /** - * sets Cell Address for the Series Legend + * sets the constant that represents the bar shape for this Series + *

                This is an internal method that is not useful to the end user. * - * @param String legendCell - Cell Address for Legend + * @param int shape - DataPoint shape for this Series (3d Bar, Pyramid ...) */ - public void setSeriesLegendRef(String legendCell) { - myseries.setLegendRef(legendCell); - myseries.getParentChart().setMetricsDirty(); - } + var shape: Int + get() = myseries.shape + set(shape) { + myseries.shape = shape + myseries.parentChart!!.setMetricsDirty() + } /** - * gets the Chart Category Data Type + * returns the Series (bar or line) color + *

                NOTE: for Pie charts, use getPieChartSliceColor + * + * @return int color constant representing the Series color + * @see FormatHandle.COLOR_* constants + * + * @see getSeriesColorStr * - * @return int Data Type of the Category */ - public int getCategoryDataType() { - return myseries.getCategoryDataType(); - } - /** - * gets the Series Data Type + * sets the color for this Series (bar or line) + *

                NOTE: for Pie Charts, use setPieChartSliceColor * - * @return int Data Type of this Series + * @param int clr - color constant + * @see setPieChartSliceColor + * + * @see FormatHandle.COLOR_* constants */ - public int getSeriesDataType() { - return myseries.getValueDataType(); - } + var seriesColor: Int + @Deprecated("") + get() = FormatHandle.HexStringToColorInt(myseries.seriesColor, FormatHandle.colorBACKGROUND) + set(clr) { + myseries.setColor(clr) + myseries.parentChart!!.setMetricsDirty() + } /** - * sets the Chart Category Data Type + * returns the Series (bar or line) color hex string + *

                NOTE: for Pie charts, use getPieChartSliceColor * - * @param int i - Category Data Type + * @return int color constant representing the Series color + * @see FormatHandle.COLOR_* constants + * + * @see getPieChartSliceColor */ - public void setCategoryDataType(int i) { - myseries.setCategoryDataType(i); - myseries.getParentChart().setMetricsDirty(); - } + val seriesColorStr: String + get() = myseries.seriesColor /** - * sets the Series Data Type + * returns true if this chart has Bubble Sizes * - * @param int i - Series Data Type + * @return */ - public void setSeriesDataType(int i) { - myseries.setValueDataType(i); + fun hasBubbleSizes(): Boolean { + return myseries.hasBubbleSizes() } /** - * returns a constant that represents the bar shape for this Series - *
                This is an internal method that is not useful to the end user. + * set the Cell Range for the Bubbles in this Seeries (Bubble Chart Only) * - * @return int DataPoint shape for this Series (3d Bar, Pyramid ...) + * @param String bubbleSizes - Cell Range for Bubble Sizes */ - public int getShape() { - return myseries.getShape(); + fun setBubbleRange(bubbleSizes: String?) { + if (bubbleSizes != null && bubbleSizes != "") { + myseries.parentChart!!.setMetricsDirty() + val ai = myseries.bubbleValueAi + if (ai!!.workBook == null) ai.workBook = wbh.workBook + ai.changeAiLocation(ai.toString(), bubbleSizes) + ai.setRt(2) + try { // also update Bubble Count for this series + val coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(bubbleSizes) + myseries.bubbleCount = coords[4] + myseries.parentChart!!.setMetricsDirty() + } catch (e: Exception) { + + } + + } } /** - * sets the constant that represents the bar shape for this Series - *
                This is an internal method that is not useful to the end user. + * sets Cell Address for the Series Legend * - * @param int shape - DataPoint shape for this Series (3d Bar, Pyramid ...) + * @param String legendCell - Cell Address for Legend */ - public void setShape(int shape) { - myseries.setShape(shape); - myseries.getParentChart().setMetricsDirty(); + fun setSeriesLegendRef(legendCell: String) { + myseries.legendRef = legendCell + myseries.parentChart!!.setMetricsDirty() } /** @@ -278,15 +306,15 @@ public void setShape(int shape) { * @param String legendRef - Cell Address representing the Legend for this Series * @param String series - Cell Range representing the Data Points for this Series * @param String cat - Cell Range representing the Category for this Series - * (NOTE: The Category Cell Range is typically the same for every Series in the Chart) + * (NOTE: The Category Cell Range is typically the same for every Series in the Chart) * @param String bubble - Cell Range representing the Bubble Sizes for this Series (Bubble Chart only) or null if none */ - public void setSeries(String legendRef, String series, String cat, String bubble) { - this.setSeriesLegendRef(legendRef); - this.setSeriesRange(series); - this.setCategoryRange(cat); - this.setBubbleRange(bubble); - myseries.getParentChart().setMetricsDirty(); + fun setSeries(legendRef: String, series: String, cat: String, bubble: String) { + this.setSeriesLegendRef(legendRef) + this.seriesRange = series + this.categoryRange = cat + this.setBubbleRange(bubble) + myseries.parentChart!!.setMetricsDirty() } /** @@ -294,38 +322,26 @@ public void setSeries(String legendRef, String series, String cat, String bubble * * @param int seriesNumber - series index * @param int clr - color constant - * @see setSeriesColor(int clr) - * @deprecated use setSeriesColor(int clr) instead + * @see setSeriesColor */ - public void setSeriesColor(int seriesNumber, int clr) { - myseries.setColor(clr); - myseries.getParentChart().setMetricsDirty(); + @Deprecated("use setSeriesColor(int clr) instead") + fun setSeriesColor(seriesNumber: Int, clr: Int) { + myseries.setColor(clr) + myseries.parentChart!!.setMetricsDirty() } /** * sets the color for this Series (bar or line) - *
                NOTE: for Pie Charts, use setPieChartSliceColor - * - * @param int clr - color constant - * @see setPieChartSliceColor - * @see FormatHandle.COLOR_* constants - */ - public void setSeriesColor(int clr) { - myseries.setColor(clr); - myseries.getParentChart().setMetricsDirty(); - } - - /** - * sets the color for this Series (bar or line) - *
                NOTE: for Pie Charts, use setPieChartSliceColor + *

                NOTE: for Pie Charts, use setPieChartSliceColor * * @param String color hex string * @see setPieChartSliceColor + * * @see FormatHandle.COLOR_* constants */ - public void setSeriesColor(String clr) { - myseries.setColor(clr); - myseries.getParentChart().setMetricsDirty(); + fun setSeriesColor(clr: String) { + myseries.setColor(clr) + myseries.parentChart!!.setMetricsDirty() } /** @@ -335,34 +351,9 @@ public void setSeriesColor(String clr) { * @param int slice - 0-based slice or point number * @see FormatHandle.COLOR_* constants */ - public void setPieChartSliceColor(int clr, int slice) { - myseries.setPieSliceColor(clr, slice); - myseries.getParentChart().setMetricsDirty(); - } - - /** - * returns the Series (bar or line) color - *
                NOTE: for Pie charts, use getPieChartSliceColor - * - * @return int color constant representing the Series color - * @see FormatHandle.COLOR_* constants - * @see getSeriesColorStr - * @deprecated - */ - public int getSeriesColor() { - return FormatHandle.HexStringToColorInt(myseries.getSeriesColor(), FormatHandle.colorBACKGROUND); - } - - /** - * returns the Series (bar or line) color hex string - *
                NOTE: for Pie charts, use getPieChartSliceColor - * - * @return int color constant representing the Series color - * @see FormatHandle.COLOR_* constants - * @see getPieChartSliceColor - */ - public String getSeriesColorStr() { - return myseries.getSeriesColor(); + fun setPieChartSliceColor(clr: Int, slice: Int) { + myseries.setPieSliceColor(clr, slice) + myseries.parentChart!!.setMetricsDirty() } /** @@ -371,10 +362,11 @@ public String getSeriesColorStr() { * @param int slice - 0-based slice or wedge index * @return int color constant repressenting the color for the desired pie slice * @see FormatHandle.COLOR_* constants - * @deprecated See getPieChartSliceColorStr + * */ - public int getPieChartSliceColor(int slice) { - return FormatHandle.HexStringToColorInt(myseries.getPieSliceColor(slice), FormatHandle.colorBACKGROUND); + @Deprecated("See getPieChartSliceColorStr") + fun getPieChartSliceColor(slice: Int): Int { + return FormatHandle.HexStringToColorInt(myseries.getPieSliceColor(slice)!!, FormatHandle.colorBACKGROUND) } /** @@ -383,15 +375,15 @@ public int getPieChartSliceColor(int slice) { * @param int slice - 0-based slice or wedge index * @return String color hex string representing the color for the desired pie slice */ - public String getPieChartSliceColorStr(int slice) { - return myseries.getPieSliceColor(slice); + fun getPieChartSliceColorStr(slice: Int): String? { + return myseries.getPieSliceColor(slice) } - + /* - * returns the OOXML (Open Office XML) Shape Properties Object for this Series + * returns the OOXML (Open Office XML) Shape Properties Object for this Series * @return SpPr OOXML Shape Properties Object * @see SpPr - * + * public SpPr getSpPr() { return myseries.getSpPr(); } */ @@ -403,12 +395,12 @@ public String getPieChartSliceColorStr(int slice) { public void setSpPr(SpPr sp) { myseries.setSpPr(sp); }*/ - + /* - * returns the OOXML (Open Office XML) Marker Properties Object for this Series + * returns the OOXML (Open Office XML) Marker Properties Object for this Series * @return Marker -OOXML Marker Properties Object * @see Marker - * + * public Marker getMarker() { return myseries.getMarker(); } */ /* @@ -419,14 +411,14 @@ public void setSpPr(SpPr sp) { public void setMarker(Marker m) { myseries.setMarker(m); }*/ - + /* - * returns the OOXML (Open Office XML) Data Label Properties Object for this Series + * returns the OOXML (Open Office XML) Data Label Properties Object for this Series * @return DLbls - OOXML Data Label Properties Object - * + * public DLbls getDLbls() { return myseries.getDLbls(); } */ - + /* * sets the OOXML (Open Office XML) Data Label Properties Object for this Series * @param Dlbls d- OOXML Data Label Properties Object @@ -435,7 +427,7 @@ public void setMarker(Marker m) { public void setDLbls(DLbls d) { myseries.setDLbls(d); }*/ - + /* * returns an array of OOXML (Open Office XML) Data Point Properties Objects defining this Series * @return DPt[] - Array of OOXML Data Point Properties Objects diff --git a/src/main/java/io/starter/OpenXLS/ColHandle.java b/src/main/java/io/starter/OpenXLS/ColHandle.java deleted file mode 100644 index 78c1398..0000000 --- a/src/main/java/io/starter/OpenXLS/ColHandle.java +++ /dev/null @@ -1,404 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.*; -import io.starter.toolkit.StringTool; - -import java.util.ArrayList; -import java.util.List; - - -/** - * The ColHandle provides access to an Worksheet Column and its Cells. - *
                - * Use the ColHandle to work with individual Columns in an XLS file. - *
                - * With a ColHandle you can: - *

                - * get a handle to the Cells in a column
                - * set the default formatting for a column
                - *
                - *

                - * Note: for a discussion of Column widths see: - * http://support.microsoft.com/?kbid=214123 - * - * @see WorkBookHandle - * @see WorkSheetHandle - * @see FormulaHandle - */ -public class ColHandle { - - // TODO: read 1st font in file to set DEFAULT_ZERO_CHAR_WIDTH ... eventually ... - public static final double DEFAULT_ZERO_CHAR_WIDTH = 7.0; // width of '0' char in default font + conversion 1.3 - public static final int COL_UNITS_TO_PIXELS = (int) (256 / DEFAULT_ZERO_CHAR_WIDTH); // = 36.57 - public static final int DEFAULT_COLWIDTH = Colinfo.DEFAULT_COLWIDTH; - - private Colinfo myCol; - private FormatHandle formatter; - private WorkBook wbh; - private WorkSheetHandle mySheet; - - /** - * creates a new ColHandle from a Colinfo Object and reference to a worksheet (WorkSheetHandle Object) - * - * @param c - * @param sheet - */ - protected ColHandle(Colinfo c, WorkSheetHandle sheet) { - myCol = c; - wbh = sheet.getWorkBook(); - mySheet = sheet; - } - - private int lastsz = 0; // the last checked col width - - /** - * resizes this column to fit the width of all displayed, non-wrapped text. - *
                NOTE: as the Excel autofit implementation is undocumented, this is an approximation - */ - public void autoFit() { - // KSC: make more betta :) - double w = 0; - CellHandle[] cxt = this.getCells(); - for (int t = 0; t < cxt.length; t++) { - String s = cxt[t].getFormattedStringVal(); //StringVal(); - FormatHandle fh = cxt[t].getFormatHandle(); - Font ef = fh.getFont(); - int style = java.awt.Font.PLAIN; - if (ef.getBold()) style |= java.awt.Font.BOLD; - if (ef.getItalic()) style |= java.awt.Font.ITALIC; - int h = (int) ef.getFontHeightInPoints(); - java.awt.Font f = new java.awt.Font(ef.getFontName(), style, h); - double newW = 0; - if (!cxt[t].getFormatHandle().getWrapText()) // normal case, no wrap - newW = StringTool.getApproximateStringWidth(f, s); - else // wrap - use current column width????? - newW = this.getWidth() / COL_UNITS_TO_PIXELS; - w = Math.max(w, newW); - /* - int strlen = cstr.length(); - int csz = strlen *= cxt[t].getFontSize(); - int factor= 28; // KSC: was 50 + added factor to guard below - if((csz*factor)>lastsz) - this.setWidth(csz*factor); - */ - } - if (w == 0) - return; // keep original width ... that's what Excel does for blank columns ... - // convert pixels to excel column units basically OpenXLS.COLUNITSTOPIXELS in double form - this.setWidth((int) Math.floor((w / DEFAULT_ZERO_CHAR_WIDTH) * 256.0)); - } - - - /** - * sets the width of this Column in Characters or Excel units. - *
                - * The default Excel column width is set to 8.43 Characters, - * based on the default font and font size, - *
                - * NOTE: The last Cell in the column having its width - * set will be the resulting width of the column - * - * @param int i - desired Column width in Characters (Excel units) - */ - public void setWidthInChars(int newWidth) { - /* if an image falls upon this column, - * adjust image width so that it does not change - */ - ArrayList iAdjust = new ArrayList(); - ImageHandle[] images = myCol.getSheet().getImages(); - if (images != null) { - // for each image that falls over this column, trap index + original width -- to be reset after setting col width - for (int z = 0; z < images.length; z++) { - ImageHandle ih = images[z]; - int c0 = ih.getCol(); - int c1 = ih.getCol1(); - int col = myCol.getColFirst(); // should only be one, right? - if (col >= c0 && col <= c1) { - int w = ih.getWidth(); - iAdjust.add(new int[]{z, w}); - } - } - } - - myCol.setColWidthInChars(newWidth); - for (int z = 0; z < iAdjust.size(); z++) { - ImageHandle ih = images[iAdjust.get(z)[0]]; - ih.setWidth(iAdjust.get(z)[1]); - } - } - - /** - * sets the width of this Column in internal units, described as follows: - *
                - * default width of the columns in 1/256 of the width of the zero character, - * using default font. - *
                The Default Excel Column, whose width in Characters or Excel Units, is 8.43, has a width in these units of 2300. - *

                NOTE: - * The last Cell in the column having its width - * set will be the resulting width of the column - * - * @param int i - desired Column width in internal units - */ - public void setWidth(int newWidth) { - /* if an image falls upon this column, - * adjust image width so that it does not change - */ - ArrayList iAdjust = new ArrayList(); - ImageHandle[] images = myCol.getSheet().getImages(); - if (images != null) { - // for each image that falls over this column, trap index + original width -- to be reset after setting col width - for (int z = 0; z < images.length; z++) { - ImageHandle ih = images[z]; - int c0 = ih.getCol(); - int c1 = ih.getCol1(); - int col = myCol.getColFirst(); // should only be one, right? - if (col >= c0 && col <= c1) { - int w = ih.getWidth(); - iAdjust.add(new int[]{z, w}); - } - } - } - lastsz = newWidth; - myCol.setColWidth(newWidth); - // now adjust any of the images that we noted above - for (int z = 0; z < iAdjust.size(); z++) { - ImageHandle ih = images[iAdjust.get(z)[0]]; - ih.setWidth(iAdjust.get(z)[1]); - } - } - - /** - * returns the width of this Column in internal units - * defined as follows: - *
                - * default width of the columns in 1/256 of the width of the zero character, - * using default font. - *
                The Default Excel Column, whose width in Excel Units or Characters is 8.43, has a width in these units of 2300. - * - * @return int Column width in internal units - */ - public int getWidth() { - return myCol.getColWidth(); - } - - - /** - * returns the width of this Column in Characters or regular Excel units - *
                NOTE: this value is a calculated value that should be close but still is an approximation of Excel units - * - * @return int Column width in Excel units - */ - public int getWidthInChars() { - return myCol.getColWidthInChars(); - } - - /** - * static utility method to return the Column width of an existing column - * in the units as follows: - *
                - * default width of the columns in 1/256 of the width of the zero character, - * using default font. - *
                For Arial 10 point, the default width of the zero character = 7 - *
                The Default Excel Column, whose width in Characters or Excel Units is 8.43, has a width in these units of 2300. - * - * @param Boundsheet sheet - source Worksheet - * @param int col - 0-based Column number - * @return int - Column width in internal units - */ - public static int getWidth(Boundsheet sheet, int col) { - int w = Colinfo.DEFAULT_COLWIDTH; - try { - Colinfo c = sheet.getColInfo(col); - if (c != null) - w = c.getColWidth(); - } catch (Exception e) { // exception if no col defined - } - return w; - } - - - /** - * sets the format id (an index to a Format record) for this Column - *
                This sets the default formatting for the Column - * such that any cell that does not specifically set it's own formatting - * will display this Column formatting - * - * @param int i - ID representing the Format to set this Column - * @see FormatHandle - */ - public void setFormatId(int i) { - myCol.setIxfe(i); - } - - /** - * returns the format ID (the index to the format record) for this Column - *
                The Column format is the default formatting for each cell contained - * within the column - * - * @return int formatId - the index of the format record for this Column - * @see FormatHandle - */ - public int getFormatId() { - return myCol.getIxfe(); - } - - /** - * returns the FormatHandle (a Format Object describing visual properties) for this Column - *
                NOTE: The Column format record describes the default formatting for each cell contained - * within the column - * - * @return FormatHandle - a Format object to apply to this Col - */ - public FormatHandle getFormatHandle() { - if (this.formatter == null) this.setFormatHandle(); - return this.formatter; - } - - /** - * sets the FormatHandle (a Format Object describing visual properties) for this Column - *
                NOTE: The Column format record describes the default formatting for each cell contained - * within the column - */ - private void setFormatHandle() { - if (formatter != null) return; - formatter = new FormatHandle(wbh, this.getFormatId()); - formatter.setColHandle(this); - } - - /** - * returns the first Column referenced by this column handle - *
                NOTE: A Column handle may in some circumstances refer to a range of columns - * - * @return int first column number referenced by this Column handle - */ - public int getColFirst() { - return myCol.getColFirst(); - } - - /** - * returns the last Column referenced by this column handle - *
                NOTE: A Column handle may in some circumstances refer to a range of columns - * - * @return int last column number referenced by this Column handle - */ - public int getColLast() { - return myCol.getColLast(); - } - - - /** - * returns the array of Cells in this Column - * - * @return CellHandle array - */ - public CellHandle[] getCells() { - List mycells; - try { - mycells = this.mySheet.getBoundsheet().getCellsByCol(this.getColFirst()); - } catch (CellNotFoundException e) { - return new CellHandle[0]; - } - CellHandle[] ch = new CellHandle[mycells.size()]; - for (int t = 0; t < ch.length; t++) { - ch[t] = new CellHandle((BiffRec) mycells.get(t), null); - ch[t].setWorkSheetHandle(null); - } - return ch; - } - - /** - * determines if this Column passes through i.e. contains a - * horizontal merge range - * - * @return true if this Column is part of any merge (horizontally merged cells) - */ - public boolean containsMergeRange() { - RowHandle[] r = mySheet.getRows(); - for (int i = 0; i < r.length; i++) { - BiffRec b; - try { - b = r[i].myRow.getCell((short) this.getColFirst()); - if (b != null && b.getMergeRange() != null) return true; - } catch (CellNotFoundException e) { - } - - } - return false; - } - - /** - * sets whether to collapse this Column - * - * @param boolean b - true to collapse this Column - */ - public void setCollapsed(boolean b) { - this.myCol.setCollapsed(b); - } - - /** - * sets whether to hide or show this Column - * - * @param boolean b - true to hide this Column, false to show - */ - public void setHidden(boolean b) { - this.myCol.setHidden(b); - } - - /** - * Set the Outline level (depth) of this Column - * - * @param int x - outline level - */ - public void setOutlineLevel(int x) { - this.myCol.setOutlineLevel(x); - } - - /** - * Returns the Outline level (depth) of this Column - * - * @return int outline level - */ - public int getOutlineLevel() { - return myCol.getOutlineLevel(); - } - - /** - * returns true if this Column is collapsed - * - * @return true if ths Column is collapsed, false otherwise - */ - public boolean isCollapsed() { - return myCol.isCollapsed(); - } - - /** - * returns true if this Column is hidden - * - * @return true if this Column is hidden, false if not - */ - public boolean isHidden() { - return myCol.isHidden(); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/ColHandle.kt b/src/main/java/io/starter/OpenXLS/ColHandle.kt new file mode 100644 index 0000000..72c9c2c --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/ColHandle.kt @@ -0,0 +1,396 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.* +import io.starter.toolkit.StringTool + +import java.util.ArrayList + + +/** + * The ColHandle provides access to an Worksheet Column and its Cells. + *

                + * Use the ColHandle to work with individual Columns in an XLS file. + *

                + * With a ColHandle you can: + *

                + * get a handle to the Cells in a column

                + * set the default formatting for a column

                + *

                + * + * + * Note: for a discussion of Column widths see: + * http://support.microsoft.com/?kbid=214123 + * + * @see WorkBookHandle + * + * @see WorkSheetHandle + * + * @see FormulaHandle + */ +class ColHandle +/** + * creates a new ColHandle from a Colinfo Object and reference to a worksheet (WorkSheetHandle Object) + * + * @param c + * @param sheet + */ +(private val myCol: Colinfo, private val mySheet: WorkSheetHandle) { + private var formatter: FormatHandle? = null + private val wbh: WorkBook? + + private var lastsz = 0 // the last checked col width + + /** + * returns the width of this Column in internal units + * defined as follows: + *

                + * default width of the columns in 1/256 of the width of the zero character, + * using default font. + *

                The Default Excel Column, whose width in Excel Units or Characters is 8.43, has a width in these units of 2300. + * + * @return int Column width in internal units + */ + /** + * sets the width of this Column in internal units, described as follows: + *

                + * default width of the columns in 1/256 of the width of the zero character, + * using default font. + *

                The Default Excel Column, whose width in Characters or Excel Units, is 8.43, has a width in these units of 2300. + * + * NOTE: + * The last Cell in the column having its width + * set will be the resulting width of the column + * + * @param int i - desired Column width in internal units + */ + /* if an image falls upon this column, + * adjust image width so that it does not change + */// for each image that falls over this column, trap index + original width -- to be reset after setting col width + // should only be one, right? + // now adjust any of the images that we noted above + var width: Int + get() = myCol.colWidth + set(newWidth) { + val iAdjust = ArrayList() + val images = myCol.sheet!!.images + if (images != null) { + for (z in images.indices) { + val ih = images[z] + val c0 = ih.col + val c1 = ih.col1 + val col = myCol.colFirst + if (col >= c0 && col <= c1) { + val w = ih.width.toInt() + iAdjust.add(intArrayOf(z, w)) + } + } + } + lastsz = newWidth + myCol.colWidth = newWidth + for (z in iAdjust.indices) { + val ih = images!![iAdjust[z][0]] + ih.setWidth(iAdjust[z][1]) + } + } + + + /** + * returns the width of this Column in Characters or regular Excel units + *

                NOTE: this value is a calculated value that should be close but still is an approximation of Excel units + * + * @return int Column width in Excel units + */ + /** + * sets the width of this Column in Characters or Excel units. + *

                + * The default Excel column width is set to 8.43 Characters, + * based on the default font and font size, + *

                + * NOTE: The last Cell in the column having its width + * set will be the resulting width of the column + * + * @param int i - desired Column width in Characters (Excel units) + */ + /* if an image falls upon this column, + * adjust image width so that it does not change + */// for each image that falls over this column, trap index + original width -- to be reset after setting col width + // should only be one, right? + var widthInChars: Int + get() = myCol.colWidthInChars + set(newWidth) { + val iAdjust = ArrayList() + val images = myCol.sheet!!.images + if (images != null) { + for (z in images.indices) { + val ih = images[z] + val c0 = ih.col + val c1 = ih.col1 + val col = myCol.colFirst + if (col >= c0 && col <= c1) { + val w = ih.width.toInt() + iAdjust.add(intArrayOf(z, w)) + } + } + } + + myCol.setColWidthInChars(newWidth.toDouble()) + for (z in iAdjust.indices) { + val ih = images!![iAdjust[z][0]] + ih.setWidth(iAdjust[z][1]) + } + } + + /** + * returns the format ID (the index to the format record) for this Column + *

                The Column format is the default formatting for each cell contained + * within the column + * + * @return int formatId - the index of the format record for this Column + * @see FormatHandle + */ + /** + * sets the format id (an index to a Format record) for this Column + *

                This sets the default formatting for the Column + * such that any cell that does not specifically set it's own formatting + * will display this Column formatting + * + * @param int i - ID representing the Format to set this Column + * @see FormatHandle + */ + var formatId: Int + get() = myCol.ixfe + set(i) { + myCol.ixfe = i + } + + /** + * returns the FormatHandle (a Format Object describing visual properties) for this Column + *

                NOTE: The Column format record describes the default formatting for each cell contained + * within the column + * + * @return FormatHandle - a Format object to apply to this Col + */ + val formatHandle: FormatHandle? + get() { + if (this.formatter == null) this.setFormatHandle() + return this.formatter + } + + /** + * returns the first Column referenced by this column handle + *

                NOTE: A Column handle may in some circumstances refer to a range of columns + * + * @return int first column number referenced by this Column handle + */ + val colFirst: Int + get() = myCol.colFirst + + /** + * returns the last Column referenced by this column handle + *

                NOTE: A Column handle may in some circumstances refer to a range of columns + * + * @return int last column number referenced by this Column handle + */ + val colLast: Int + get() = myCol.colLast + + + /** + * returns the array of Cells in this Column + * + * @return CellHandle array + */ + val cells: Array + get() { + val mycells: List<*> + try { + mycells = this.mySheet.boundsheet!!.getCellsByCol(this.colFirst) + } catch (e: CellNotFoundException) { + return arrayOfNulls(0) + } + + val ch = arrayOfNulls(mycells.size) + for (t in ch.indices) { + ch[t] = CellHandle(mycells[t] as BiffRec, null) + ch[t].workSheetHandle = null + } + return ch + } + + /** + * Returns the Outline level (depth) of this Column + * + * @return int outline level + */ + /** + * Set the Outline level (depth) of this Column + * + * @param int x - outline level + */ + var outlineLevel: Int + get() = myCol.outlineLevel + set(x) { + this.myCol.outlineLevel = x + } + + /** + * returns true if this Column is collapsed + * + * @return true if ths Column is collapsed, false otherwise + */ + /** + * sets whether to collapse this Column + * + * @param boolean b - true to collapse this Column + */ + var isCollapsed: Boolean + get() = myCol.isCollapsed + set(b) { + this.myCol.isCollapsed = b + } + + /** + * returns true if this Column is hidden + * + * @return true if this Column is hidden, false if not + */ + /** + * sets whether to hide or show this Column + * + * @param boolean b - true to hide this Column, false to show + */ + var isHidden: Boolean + get() = myCol.isHidden + set(b) { + this.myCol.isHidden = b + } + + init { + wbh = mySheet.workBook + } + + /** + * resizes this column to fit the width of all displayed, non-wrapped text. + *

                NOTE: as the Excel autofit implementation is undocumented, this is an approximation + */ + fun autoFit() { + // KSC: make more betta :) + var w = 0.0 + val cxt = this.cells + for (t in cxt.indices) { + val s = cxt[t].formattedStringVal //StringVal(); + val fh = cxt[t].formatHandle + val ef = fh!!.font + var style = java.awt.Font.PLAIN + if (ef!!.bold) style = style or java.awt.Font.BOLD + if (ef.italic) style = style or java.awt.Font.ITALIC + val h = ef.fontHeightInPoints.toInt() + val f = java.awt.Font(ef.fontName, style, h) + var newW = 0.0 + if (!cxt[t].formatHandle!!.wrapText) + // normal case, no wrap + newW = StringTool.getApproximateStringWidth(f, s) + else + // wrap - use current column width????? + newW = (this.width / COL_UNITS_TO_PIXELS).toDouble() + w = Math.max(w, newW) + /* + int strlen = cstr.length(); + int csz = strlen *= cxt[t].getFontSize(); + int factor= 28; // KSC: was 50 + added factor to guard below + if((csz*factor)>lastsz) + this.setWidth(csz*factor); + */ + } + if (w == 0.0) + return // keep original width ... that's what Excel does for blank columns ... + // convert pixels to excel column units basically OpenXLS.COLUNITSTOPIXELS in double form + this.width = Math.floor(w / DEFAULT_ZERO_CHAR_WIDTH * 256.0).toInt() + } + + /** + * sets the FormatHandle (a Format Object describing visual properties) for this Column + *

                NOTE: The Column format record describes the default formatting for each cell contained + * within the column + */ + private fun setFormatHandle() { + if (formatter != null) return + formatter = FormatHandle(wbh!!, this.formatId) + formatter!!.setColHandle(this) + } + + /** + * determines if this Column passes through i.e. contains a + * horizontal merge range + * + * @return true if this Column is part of any merge (horizontally merged cells) + */ + fun containsMergeRange(): Boolean { + val r = mySheet.rows + for (i in r.indices) { + val b: BiffRec? + try { + b = r[i].myRow.getCell(this.colFirst.toShort()) + if (b != null && b.mergeRange != null) return true + } catch (e: CellNotFoundException) { + } + + } + return false + } + + companion object { + + // TODO: read 1st font in file to set DEFAULT_ZERO_CHAR_WIDTH ... eventually ... + val DEFAULT_ZERO_CHAR_WIDTH = 7.0 // width of '0' char in default font + conversion 1.3 + val COL_UNITS_TO_PIXELS = (256 / DEFAULT_ZERO_CHAR_WIDTH).toInt() // = 36.57 + val DEFAULT_COLWIDTH = Colinfo.DEFAULT_COLWIDTH + + /** + * static utility method to return the Column width of an existing column + * in the units as follows: + *

                + * default width of the columns in 1/256 of the width of the zero character, + * using default font. + *

                For Arial 10 point, the default width of the zero character = 7 + *

                The Default Excel Column, whose width in Characters or Excel Units is 8.43, has a width in these units of 2300. + * + * @param Boundsheet sheet - source Worksheet + * @param int col - 0-based Column number + * @return int - Column width in internal units + */ + fun getWidth(sheet: Boundsheet, col: Int): Int { + var w = Colinfo.DEFAULT_COLWIDTH + try { + val c = sheet.getColInfo(col) + if (c != null) + w = c.colWidth + } catch (e: Exception) { // exception if no col defined + } + + return w + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/CommentHandle.java b/src/main/java/io/starter/OpenXLS/CommentHandle.java deleted file mode 100644 index 49d84b3..0000000 --- a/src/main/java/io/starter/OpenXLS/CommentHandle.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.Note; -import io.starter.toolkit.Logger; - -/** - *
                - * CommentHandle allows for manipulation of the Note or Comment feature of Excel
                - *
                - * In order to create CommentHandles programatically use the methods in WorkSheetHandle or CellHandle
                - *
                - * 
                - */ -public class CommentHandle implements Handle { - - private Note note; - - /** - * Creates a new CommentHandle object - *
                For internal use only - * - * @param n - */ - protected CommentHandle(Note n) { - this.note = n; - } - - /** - * Returns the text of the Note (Comment) - * - * @return String value or text of Note - */ - public String getCommentText() { - if (note != null) - return note.getText(); - return null; - } - - /** - * Sets the text of the Note (Comment). - *

                The text may contain embedded formatting information as follows: - *
                < font specifics>text segment< font specifics for next segment>text segment... - *
                where font specifics can be one or more of (all are optional): - *

                  b - bold - *
                  i - italic - *
                  s - strikethru - *
                  u - underlined - *
                  f="" - font name surrounded by quotes e.g. "Tahoma" - *
                  sz="" - font size in points surrounded by quotes e.g. "10" - *
                  Each option must be delimited by ;'s - *
                  For Example: - *
                    "<f=\"Tahoma\";b;sz=\"16\">Note: <f=\"Cambria\";sz=\"12\">This is an important point"
                  - * To reset to the default font, input an empty format: <> e.g.: - *
                    "<b;i;sz=\"8\">Note:<>This is an important comment"
                  - * - * @param text - String text of Note - */ - public void setCommentText(String text) { - if (note != null) { - try { - note.setText(text); - } catch (IllegalArgumentException e) { - Logger.logErr(e.toString()); - } - } - } - - /** - * returns the author of this Note (Comment) if set - * - * @return String author - */ - public String getAuthor() { - if (note != null) - return note.getAuthor(); - return null; - } - - /** - * sets the author of this Note (Comment) - * - * @param author - */ - public void setAuthor(String author) { - if (note != null) note.setAuthor(author); - } - - /** - * Removes or deletes this Note (Comment) from the worksheet - */ - public void remove() { - note.getSheet().removeNote(note); - note = null; - } - - /** - * Sets this Note (Comment) to always show, even when the attached cell loses focus - */ - public void show() { - if (note != null) note.setHidden(false); - } - - /** - * Sets this Note (Comment) to be hidden until the attached cell has focus - *

                  - * This is the default state of note records - */ - public void hide() { - if (note != null) note.setHidden(true); - } - - /** - * Returns true if this Note (Comment) is hidden until focus - * - * @return - */ - public boolean getIsHidden() { - if (note != null) - return note.getHidden(); - return false; - } - - /** - * Sets this Note (Comment) to be attached to a cell at [row, col] - * - * @param row int row number (0-based) - * @param col int column number (0-based) - */ - public void setRowCol(int row, int col) { - if (note != null) - note.setRowCol(row, col); - } - - /** - * Returns the address this Note (Comment) is attached to - * - * @return String Cell Address - */ - public String getAddress() { - if (note != null) - return note.getCellAddressWithSheet(); - return null; - } - - /** - * return the String representation of this CommentHandle - */ - public String toString() { - if (note != null) - return note.toString(); - return "Not initialized"; - } - - /** - * return the Row number (0-based) this Note is attached to - * - * @return 0-based row number - */ - public int getRowNum() { - if (note != null) - return note.getRowNumber(); - return -1; - } - - /** - * return the Column this note is attached to - * - * @return Column number as an integer e.g. A=0, B=1 ... - */ - public int getColNum() { - if (note != null) - return note.getColNumber(); - return -1; - } - - /** - * Sets the width and height of the bounding text box of the note - *
                  Units are in pixels - *
                  NOTE: the height algorithm w.r.t. varying row heights is not 100% - * - * @param width short desired text box width in pixels - * @param height short desired text box height in pixels - */ - public void setTextBoxSize(int width, int height) { - if (note != null) { - note.setTextBoxWidth((short) width); - note.setTextBoxHeight((short) height); - } - } - - /** - * returns the bounds (size and position) of the Text Box for this Note - *
                  bounds are relative and based upon rows, columns and offsets within - *
                  bounds are as follows: - *
                  bounds[0]= column # of top left position (0-based) of the shape - *
                  bounds[1]= x offset within the top-left column (0-1023) - *
                  bounds[2]= row # for top left corner - *
                  bounds[3]= y offset within the top-left corner (0-1023) - *
                  bounds[4]= column # of the bottom right corner of the shape - *
                  bounds[5]= x offset within the cell for the bottom-right corner (0-1023) - *
                  bounds[6]= row # for bottom-right corner of the shape - *
                  bounds[7]= y offset within the cell for the bottom-right corner (0-1023) - * - * @return - */ - public short[] getTextBoxBounds() { - if (note != null) - return note.getTextBoxBounds(); - return null; - } - - /** - * Sets the bounds (size and position) of the Text Box for this Note - *
                  bounds are relative and based upon rows, columns and offsets within - *
                  bounds are as follows: - *
                  bounds[0]= column # of top left position (0-based) of the shape - *
                  bounds[1]= x offset within the top-left column (0-1023) - *
                  bounds[2]= row # for top left corner - *
                  bounds[3]= y offset within the top-left corner (0-1023) - *
                  bounds[4]= column # of the bottom right corner of the shape - *
                  bounds[5]= x offset within the cell for the bottom-right corner (0-1023) - *
                  bounds[6]= row # for bottom-right corner of the shape - *
                  bounds[7]= y offset within the cell for the bottom-right corner (0-1023) - */ - public void setTextBoxBounds(short[] bounds) { - if (note != null) - note.setTextBoxBounds(bounds); - } - - - /** - * Returns the internal note record for this CommentHandle. - *

                  - * Be aware that this note record should not be modified directly, and that this - * method is only for internal application use. - * - * @return Name record - */ - public Note getInternalNoteRec() { - return note; - } - - /** - * Returns the OOXML representation of this Note object - * - * @param authId 0-based author index for the author linked to this Note - * @return String OOMXL representation - */ - public String getOOXML(int authId) { - StringBuffer ooxml = new StringBuffer(); - // TODO: Handle FORMATS - ooxml.append(""); - ooxml.append(note.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - -} - diff --git a/src/main/java/io/starter/OpenXLS/CommentHandle.kt b/src/main/java/io/starter/OpenXLS/CommentHandle.kt new file mode 100644 index 0000000..020e9e6 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/CommentHandle.kt @@ -0,0 +1,256 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.Note +import io.starter.toolkit.Logger + +/** + *

                  + * CommentHandle allows for manipulation of the Note or Comment feature of Excel
                  + *
                  + * In order to create CommentHandles programatically use the methods in WorkSheetHandle or CellHandle
                  + *
                  +
                  * + */ +class CommentHandle +/** + * Creates a new CommentHandle object + *

                  For internal use only + * + * @param n + */ +(n: Note) : Handle { + + /** + * Returns the internal note record for this CommentHandle. + * + * + * Be aware that this note record should not be modified directly, and that this + * method is only for internal application use. + * + * @return Name record + */ + var internalNoteRec: Note? = null + private set + + /** + * Returns the text of the Note (Comment) + * + * @return String value or text of Note + */ + /** + * Sets the text of the Note (Comment). + * + * The text may contain embedded formatting information as follows: + *

                  < font specifics>text segment< font specifics for next segment>text segment... + *

                  where font specifics can be one or more of (all are optional): + * b - bold + *

                  i - italic + *

                  s - strikethru + *

                  u - underlined + *

                  f="" - font name surrounded by quotes e.g. "Tahoma" + *

                  sz="" - font size in points surrounded by quotes e.g. "10" + *

                  Each option must be delimited by ;'s + *

                  For Example: + *

                  "<f=\"Tahoma\";b;sz=\"16\">Note: <f=\"Cambria\";sz=\"12\">This is an important point" + * To reset to the default font, input an empty format: <> e.g.: + *

                  "<b;i;sz=\"8\">Note:<>This is an important comment" + * + * @param text - String text of Note + */ + var commentText: String? + get() = if (internalNoteRec != null) internalNoteRec!!.text else null + set(text) { + if (internalNoteRec != null) { + try { + internalNoteRec!!.text = text + } catch (e: IllegalArgumentException) { + Logger.logErr(e.toString()) + } + + } + } + + /** + * returns the author of this Note (Comment) if set + * + * @return String author + */ + /** + * sets the author of this Note (Comment) + * + * @param author + */ + var author: String? + get() = if (internalNoteRec != null) internalNoteRec!!.author else null + set(author) { + if (internalNoteRec != null) internalNoteRec!!.author = author + } + + /** + * Returns true if this Note (Comment) is hidden until focus + * + * @return + */ + val isHidden: Boolean + get() = if (internalNoteRec != null) internalNoteRec!!.hidden else false + + /** + * Returns the address this Note (Comment) is attached to + * + * @return String Cell Address + */ + val address: String? + get() = if (internalNoteRec != null) internalNoteRec!!.cellAddressWithSheet else null + + /** + * return the Row number (0-based) this Note is attached to + * + * @return 0-based row number + */ + val rowNum: Int + get() = if (internalNoteRec != null) internalNoteRec!!.rowNumber else -1 + + /** + * return the Column this note is attached to + * + * @return Column number as an integer e.g. A=0, B=1 ... + */ + val colNum: Int + get() = if (internalNoteRec != null) internalNoteRec!!.colNumber.toInt() else -1 + + /** + * returns the bounds (size and position) of the Text Box for this Note + *

                  bounds are relative and based upon rows, columns and offsets within + *

                  bounds are as follows: + *

                  bounds[0]= column # of top left position (0-based) of the shape + *

                  bounds[1]= x offset within the top-left column (0-1023) + *

                  bounds[2]= row # for top left corner + *

                  bounds[3]= y offset within the top-left corner (0-1023) + *

                  bounds[4]= column # of the bottom right corner of the shape + *

                  bounds[5]= x offset within the cell for the bottom-right corner (0-1023) + *

                  bounds[6]= row # for bottom-right corner of the shape + *

                  bounds[7]= y offset within the cell for the bottom-right corner (0-1023) + * + * @return + */ + /** + * Sets the bounds (size and position) of the Text Box for this Note + *

                  bounds are relative and based upon rows, columns and offsets within + *

                  bounds are as follows: + *

                  bounds[0]= column # of top left position (0-based) of the shape + *

                  bounds[1]= x offset within the top-left column (0-1023) + *

                  bounds[2]= row # for top left corner + *

                  bounds[3]= y offset within the top-left corner (0-1023) + *

                  bounds[4]= column # of the bottom right corner of the shape + *

                  bounds[5]= x offset within the cell for the bottom-right corner (0-1023) + *

                  bounds[6]= row # for bottom-right corner of the shape + *

                  bounds[7]= y offset within the cell for the bottom-right corner (0-1023) + */ + var textBoxBounds: ShortArray? + get() = if (internalNoteRec != null) internalNoteRec!!.textBoxBounds else null + set(bounds) { + if (internalNoteRec != null) + internalNoteRec!!.textBoxBounds = bounds + } + + init { + this.internalNoteRec = n + } + + /** + * Removes or deletes this Note (Comment) from the worksheet + */ + fun remove() { + internalNoteRec!!.sheet!!.removeNote(internalNoteRec!!) + internalNoteRec = null + } + + /** + * Sets this Note (Comment) to always show, even when the attached cell loses focus + */ + fun show() { + if (internalNoteRec != null) internalNoteRec!!.hidden = false + } + + /** + * Sets this Note (Comment) to be hidden until the attached cell has focus + * + * + * This is the default state of note records + */ + fun hide() { + if (internalNoteRec != null) internalNoteRec!!.hidden = true + } + + /** + * Sets this Note (Comment) to be attached to a cell at [row, col] + * + * @param row int row number (0-based) + * @param col int column number (0-based) + */ + fun setRowCol(row: Int, col: Int) { + if (internalNoteRec != null) + internalNoteRec!!.setRowCol(row, col) + } + + /** + * return the String representation of this CommentHandle + */ + override fun toString(): String { + return if (internalNoteRec != null) internalNoteRec!!.toString() else "Not initialized" + } + + /** + * Sets the width and height of the bounding text box of the note + *

                  Units are in pixels + *

                  NOTE: the height algorithm w.r.t. varying row heights is not 100% + * + * @param width short desired text box width in pixels + * @param height short desired text box height in pixels + */ + fun setTextBoxSize(width: Int, height: Int) { + if (internalNoteRec != null) { + internalNoteRec!!.setTextBoxWidth(width.toShort()) + internalNoteRec!!.setTextBoxHeight(height.toShort()) + } + } + + /** + * Returns the OOXML representation of this Note object + * + * @param authId 0-based author index for the author linked to this Note + * @return String OOMXL representation + */ + fun getOOXML(authId: Int): String { + val ooxml = StringBuffer() + // TODO: Handle FORMATS + ooxml.append("") + ooxml.append(internalNoteRec!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + +} + diff --git a/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.java b/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.java deleted file mode 100644 index c6f8888..0000000 --- a/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.Cf; -import io.starter.formats.XLS.Condfmt; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; - -/** - *
                  - * ConditionalFormatHandle allows for manipulation of the ConditionalFormat cells in Excel
                  - *
                  - * Using the ConditionalFormatHandle, the affected range of ConditionalFormats can be modified,
                  - * along with the formatting applied to the cells when the condition is true.
                  - *
                  - * Each ConditionalFormatHandle represents a range of cells and can have a number
                  - * of formatting rules and formats (ConditionalFormatRule) applied.
                  - *
                  - * The ConditionalFormatHandle affected range can either be a contiguous range, or a series of cells and ranges.
                  - *
                  - * Each ConditionalFormatRule contains one rule and corresponding format data.
                  - *
                  - *
                  - * Many of these calls are very self-explanatory and can be found in the api.
                  - * 
                  - */ -public class ConditionalFormatHandle implements Handle { - - - private Condfmt cndfmt = null; - private WorkSheetHandle worksheet; - - - /** - * evaluates the criteria for this Conditional Format - *
                  if the criteria involves a comparison i.e. equals, less than, etc., it uses the - * value from the passed in referenced cell to compare with - *

                  - * If there are multiple rules in the ConditionalFormat then the first rule that passes will be returned. - *

                  - * If no valid rules pass then a null result is given - * - * @param CellHandle refcell - the cell to obtain a value from in order for evaluation to occur - * @return the ConditionalFormatRule that passes. - * @see io.starter.formats.XLS.Cf#evaluate(io.starter.formats.XLS.formulas.Ptg) - */ - public ConditionalFormatRule evaluate(CellHandle refcell) { - ConditionalFormatRule[] rules = this.getRules(); - for (int i = 0; i < rules.length; i++) { - if (rules[i].evaluate(refcell)) return rules[i]; - } - return null; - } - - - /** - * Get all the rules assocated with this conditional format record - * - * @return - */ - public ConditionalFormatRule[] getRules() { - ArrayList cfs = this.cndfmt.getRules(); - ConditionalFormatRule[] rules = new ConditionalFormatRule[cfs.size()]; - for (int i = 0; i < cfs.size(); i++) { - ConditionalFormatRule cfr = new ConditionalFormatRule((Cf) cfs.get(i)); - rules[i] = cfr; - } - return rules; - } - - - /** - * For internal use only. Creates a ConditionalFormat Handle based of the Condfmt passed in. - * - * @param workBookHandle - * @param Condfmt - */ - protected ConditionalFormatHandle(Condfmt c, WorkSheetHandle workSheetHandle) { - this.cndfmt = c; - worksheet = workSheetHandle; - } - - /** - * get the WorkSheetHandle for this ConditionalFormat - *

                  - * ConditionalFormats are bound to a specific worksheet and cannot be - * applied to multiple worksheets - * - * @return the WorkSheetHandle for this ConditionalFormat - */ - public WorkSheetHandle getWorkSheetHandle() { - return worksheet; - } - - - /** - * Return the range of data this ConditionalFormatHandle refers to as a string - * This location is the largest bounding rectangle that all cells utilized in this conditional - * format can be contained in. - * - * @return Encompassing range in the format "A2:B12" - */ - public String getEncompassingRange() { - int[] rowcols = cndfmt.getEncompassingRange(); - return ExcelTools.formatRangeRowCol(rowcols); - } - - - /** - * Return a string representing all ranges that this conditional format handle can affect - * - * @return range in the format "A2:B3"; - */ - public String[] getAllAffectedRanges() { - return cndfmt.getAllRanges(); - } - - - /** - * Determine if the conditional format contains/affects the cell handle passed in - * - * @param cellHandle - * @return - */ - public boolean contains(CellHandle cellHandle) { - return this.cndfmt.contains(cellHandle.getIntLocation()); - } - - - /** - * Set the range this ConditionalFormatHandle refers to. - * Pass in a range string, sans worksheet. - *

                  - * This range will overwrite all other ranges this ConditionalFormatHandle refers to. - *

                  - * In order to handle multiple ranges, use the addRange(String range) method - * - * @param range = standard excel range without worksheet information ("A1" or "A1:A10") - */ - public void setRange(String range) { - this.cndfmt.resetRange(range); - } - - /** - * Determines if the ConditionalFormatHandle contains the cell address passed in - * - * @param cellAddress a cell address in the format "A1" - * @return if the ce - */ - public boolean contains(String celladdy) { - return this.cndfmt.contains(ExcelTools.getRowColFromString(celladdy)); - } - - - /** - * Return an xml representation of the ConditionalFormatHandle - * - * @return - */ - // TODO: more than one cf rule??? - public String getXML() { - ConditionalFormatRule[] rules = this.getRules(); - StringBuffer xml = new StringBuffer(); - xml.append(""); - if (rules[0].getFirstCondition() != null) { - xml.append(""); - xml.append(rules[0].getFirstCondition()); - xml.append(""); - } - if (rules[0].getSecondCondition() != null) { - xml.append(""); - xml.append(rules[0].getSecondCondition()); - xml.append(""); - } - xml.append(""); - return xml.toString(); - } - - /** - * return a string representation of this Conditional Format - *

                  - * This method is still incomplete as it only returns data for one rule, and only refers to one range - */ - // TODO: more than one cf rule??? - public String toString() { - ConditionalFormatRule[] rules = this.getRules(); - String ret = this.getEncompassingRange() + ": " + rules[0].getType() + " " + rules[0].getOperator(); // range, type + operator - if (rules[0].getFormula1() != null) // formulas - ret += " " + rules[0].getFormula1().getFormulaString().substring(1); - if (rules[0].getFormula2() != null) - ret += " and " + rules[0].getFormula2().getFormulaString().substring(1); - // todo: add formats to this - return ret; - } - - /** - * @return Returns the cndfmt. - */ - protected Condfmt getCndfmt() { - return cndfmt; - } - - /** - * @param cndfmt The cndfmt to set. - */ - protected void setCndfmt(Condfmt cndfmt) { - this.cndfmt = cndfmt; - } - - - /** - * Add a cell to this conditional format record - * - * @param cellHandle - */ - public void addCell(CellHandle cellHandle) { - if (this.contains(cellHandle)) return; - cndfmt.addLocation(cellHandle.getCellAddress()); - } - - /** - * returns the formatting for each rule of this Contditional Format Handle - * - * @return FormatHandle[] - */ - public FormatHandle[] getFormats() { - FormatHandle[] fmx = new FormatHandle[this.getCndfmt().getRules().size()]; - if (cndfmt.getFormatHandle() == null) { - //cfm.initCells(this); // added! - int cfxe = cndfmt.getCfxe(); - FormatHandle fz = new FormatHandle(cndfmt, worksheet.wbh, cfxe, null); - } - for (int t = 0; t < fmx.length; t++) { - cndfmt.getFormatHandle().updateFromCF((Cf) this.getCndfmt().getRules().get(t), worksheet.wbh); - fmx[t] = cndfmt.getFormatHandle(); - } - return fmx; - } - -} diff --git a/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.kt b/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.kt new file mode 100644 index 0000000..e88c2b9 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/ConditionalFormatHandle.kt @@ -0,0 +1,270 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.Cf +import io.starter.formats.XLS.Condfmt +import io.starter.toolkit.Logger + +import java.util.ArrayList + +/** + *

                  + * ConditionalFormatHandle allows for manipulation of the ConditionalFormat cells in Excel
                  + *
                  + * Using the ConditionalFormatHandle, the affected range of ConditionalFormats can be modified,
                  + * along with the formatting applied to the cells when the condition is true.
                  + *
                  + * Each ConditionalFormatHandle represents a range of cells and can have a number
                  + * of formatting rules and formats (ConditionalFormatRule) applied.
                  + *
                  + * The ConditionalFormatHandle affected range can either be a contiguous range, or a series of cells and ranges.
                  + *
                  + * Each ConditionalFormatRule contains one rule and corresponding format data.
                  + *
                  + *
                  + * Many of these calls are very self-explanatory and can be found in the api.
                  +
                  * + */ +class ConditionalFormatHandle +/** + * For internal use only. Creates a ConditionalFormat Handle based of the Condfmt passed in. + * + * @param workBookHandle + * @param Condfmt + */ +(c: Condfmt, + /** + * get the WorkSheetHandle for this ConditionalFormat + * + * + * ConditionalFormats are bound to a specific worksheet and cannot be + * applied to multiple worksheets + * + * @return the WorkSheetHandle for this ConditionalFormat + */ + val workSheetHandle: WorkSheetHandle) : Handle { + + + /** + * @return Returns the cndfmt. + */ + /** + * @param cndfmt The cndfmt to set. + */ + var cndfmt: Condfmt? = null + protected set + + + /** + * Get all the rules assocated with this conditional format record + * + * @return + */ + val rules: Array + get() { + val cfs = this.cndfmt!!.rules + val rules = arrayOfNulls(cfs.size) + for (i in cfs.indices) { + val cfr = ConditionalFormatRule(cfs[i] as Cf) + rules[i] = cfr + } + return rules + } + + + /** + * Return the range of data this ConditionalFormatHandle refers to as a string + * This location is the largest bounding rectangle that all cells utilized in this conditional + * format can be contained in. + * + * @return Encompassing range in the format "A2:B12" + */ + val encompassingRange: String + get() { + val rowcols = cndfmt!!.encompassingRange + return ExcelTools.formatRangeRowCol(rowcols) + } + + + /** + * Return a string representing all ranges that this conditional format handle can affect + * + * @return range in the format "A2:B3"; + */ + val allAffectedRanges: Array + get() = cndfmt!!.allRanges + + + /** + * Return an xml representation of the ConditionalFormatHandle + * + * @return + */ + // TODO: more than one cf rule??? + val xml: String + get() { + val rules = this.rules + val xml = StringBuffer() + xml.append("") + if (rules[0].firstCondition != null) { + xml.append("") + xml.append(rules[0].firstCondition) + xml.append("") + } + if (rules[0].secondCondition != null) { + xml.append("") + xml.append(rules[0].secondCondition) + xml.append("") + } + xml.append("") + return xml.toString() + } + + /** + * returns the formatting for each rule of this Contditional Format Handle + * + * @return FormatHandle[] + */ + //cfm.initCells(this); // added! + val formats: Array + get() { + val fmx = arrayOfNulls(this.cndfmt!!.rules.size) + if (cndfmt!!.formatHandle == null) { + val cfxe = cndfmt!!.cfxe + val fz = FormatHandle(cndfmt!!, workSheetHandle.workBook!!, cfxe, null) + } + for (t in fmx.indices) { + cndfmt!!.formatHandle!!.updateFromCF(this.cndfmt!!.rules[t] as Cf, workSheetHandle.workBook) + fmx[t] = cndfmt!!.formatHandle + } + return fmx + } + + + /** + * evaluates the criteria for this Conditional Format + *

                  if the criteria involves a comparison i.e. equals, less than, etc., it uses the + * value from the passed in referenced cell to compare with + * + * + * If there are multiple rules in the ConditionalFormat then the first rule that passes will be returned. + * + * + * If no valid rules pass then a null result is given + * + * @param CellHandle refcell - the cell to obtain a value from in order for evaluation to occur + * @return the ConditionalFormatRule that passes. + * @see io.starter.formats.XLS.Cf.evaluate + */ + fun evaluate(refcell: CellHandle): ConditionalFormatRule? { + val rules = this.rules + for (i in rules.indices) { + if (rules[i].evaluate(refcell)) return rules[i] + } + return null + } + + + init { + this.cndfmt = c + } + + + /** + * Determine if the conditional format contains/affects the cell handle passed in + * + * @param cellHandle + * @return + */ + operator fun contains(cellHandle: CellHandle): Boolean { + return this.cndfmt!!.contains(cellHandle.intLocation) + } + + + /** + * Set the range this ConditionalFormatHandle refers to. + * Pass in a range string, sans worksheet. + * + * + * This range will overwrite all other ranges this ConditionalFormatHandle refers to. + * + * + * In order to handle multiple ranges, use the addRange(String range) method + * + * @param range = standard excel range without worksheet information ("A1" or "A1:A10") + */ + fun setRange(range: String) { + this.cndfmt!!.resetRange(range) + } + + /** + * Determines if the ConditionalFormatHandle contains the cell address passed in + * + * @param cellAddress a cell address in the format "A1" + * @return if the ce + */ + operator fun contains(celladdy: String): Boolean { + return this.cndfmt!!.contains(ExcelTools.getRowColFromString(celladdy)) + } + + /** + * return a string representation of this Conditional Format + * + * + * This method is still incomplete as it only returns data for one rule, and only refers to one range + */ + // TODO: more than one cf rule??? + override fun toString(): String { + val rules = this.rules + var ret = this.encompassingRange + ": " + rules[0].type + " " + rules[0].operator // range, type + operator + if (rules[0].formula1 != null) + // formulas + ret += " " + rules[0].formula1!!.formulaString.substring(1) + if (rules[0].formula2 != null) + ret += " and " + rules[0].formula2!!.formulaString.substring(1) + // todo: add formats to this + return ret + } + + + /** + * Add a cell to this conditional format record + * + * @param cellHandle + */ + fun addCell(cellHandle: CellHandle) { + if (this.contains(cellHandle)) return + cndfmt!!.addLocation(cellHandle.cellAddress) + } + +} diff --git a/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.java b/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.java deleted file mode 100644 index 4752dd1..0000000 --- a/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.java +++ /dev/null @@ -1,634 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.Cf; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.Formula; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.formats.XLS.formulas.PtgRef; - -import java.awt.*; - -/** - * ConditionalFormatRule defines a single rule for manipulation of the - * ConditionalFormat cells in Excel - *

                  - * Each ConditionalFormatRule contains one rule and corresponding format data. - * - * @see ConditionalFormatHandle - * @see Handle - */ -public class ConditionalFormatRule implements Handle { - - Cf currentCf = null; - // static shorts for setting ConditionalFormat type - public static final byte VALUE_ANY = 0x0; - public static final byte VALUE_INTEGER = 0x1; - public static final byte VALUE_DECIMAL = 0x2; - public static final byte VALUE_USER_DEFINED_LIST = 0x3; - public static final byte VALUE_DATE = 0x4; - public static final byte VALUE_TIME = 0x5; - public static final byte VALUE_TEXT_LENGTH = 0x6; - public static final byte VALUE_FORMULA = 0x7; - - // static shorts for setting action on error - public static byte ERROR_STOP = 0x0; - public static byte ERROR_WARN = 0x1; - public static byte ERROR_INFO = 0x2; - - // static shorts for setting conditions on ConditionalFormat - public static final byte CONDITION_BETWEEN = 0x0; - public static final byte CONDITION_NOT_BETWEEN = 0x1; - public static final byte CONDITION_EQUAL = 0x2; - public static final byte CONDITION_NOT_EQUAL = 0x3; - public static final byte CONDITION_GREATER_THAN = 0x4; - public static final byte CONDITION_LESS_THAN = 0x5; - public static final byte CONDITION_GREATER_OR_EQUAL = 0x6; - public static final byte CONDITION_LESS_OR_EQUAL = 0x7; - - public static String[] OPERATORS = {"nocomparison", "between", "notBetween", "equal", "notEqual", "greaterThan", - "lessThan", "greaterOrEqual", "lessOrEqual", "beginsWith", "endsWith", "containsText", "notContains"}; - - /** - * Get the byte representing the condition type string passed in. Options are' - * "between", "notBetween", "equal", "notEqual", "greaterThan", "lessThan", - * "greaterOrEqual", "lessOrEqual" - * - * @return - */ - public static byte getConditionNumber(String conditionType) { - for (int i = 0; i < OPERATORS.length; i++) { - if (conditionType.equalsIgnoreCase(OPERATORS[i])) - return (byte) i; - } - return -1; - } - - public static String[] VALUE_TYPE = {"any", "integer", "decimal", "userDefinedList", "date", "time", "textLength", - "formula"}; - - /** - * Get the byte representing the value type string passed in. Options are' - * "any", "integer", "decimal", "userDefinedList", "date", "time", "textLength", - * "formula" - * - * @return - */ - public static byte getValueNumber(String valueType) { - for (int i = 0; i < VALUE_TYPE.length; i++) { - if (valueType.equalsIgnoreCase(VALUE_TYPE[i])) - return (byte) i; - } - return -1; - } - - /** - * Create a conditional format rule from a Cf record. - * - * @param theCf - */ - protected ConditionalFormatRule(Cf theCf) { - currentCf = theCf; - } - - /** - * evaluates the criteria for this Conditional Format Rule
                  - * if the criteria involves a comparison i.e. equals, less than, etc., it uses - * the value from the passed in referenced cell to compare with - * - * @param Ptg refcell - the Ptg location to obtain cell value from - * @return boolean true if evaluation of criteria passes - * @see io.starter.formats.XLS.Cf#evaluate(io.starter.formats.XLS.formulas.Ptg) - */ - public boolean evaluate(CellHandle refcell) { - Ptg/* Ref */ pr = PtgRef.createPtgRefFromString(refcell.getCellAddress(), null); - return currentCf.evaluate(pr); - } - - /** - * Get the type operator of this ConditionalFormat as a byte. - *

                  - * These bytes map to the CONDITION_* static values in ConditionalFormatHandle - * - * @return - */ - public byte getTypeOperator() { - return (byte) currentCf.getOperator(); - } - - /** - * set the type operator of this ConditionalFormat as a byte. - *

                  - * These bytes map to the CONDITION_* static values in ConditionalFormatHandle - */ - public void setTypeOperator(byte typOperator) { - currentCf.setOperator(typOperator); - } - - /** - * Get the second condition of the ConditionalFormat as a string representation - * - * @return - */ - public String getSecondCondition() { - if (currentCf != null && currentCf.getFormula2() != null) - return currentCf.getFormula2().getFormulaString(); - return null; - } - - /** - * Set the second condition of the ConditionalFormat utilizing a string. This - * value must conform to the Value Type of this ConditionalFormat or unexpected - * results may occur. For example, entering a string representation of a date - * here will not work if your ConditionalFormat is an integer... - *

                  - * String passed in should be a vaild XLS formula. Does not need to include the - * "=" - *

                  - * Types of conditions Integer values Decimal values User defined list Date Time - * Text length Formula - *

                  - * Be sure that your ConditionalFormat type (getConditionalFormatType()) matches - * the type of data. - * - * @return - */ - public void setSecondCondition(Object secondCond) { - String setval = secondCond.toString(); - if (secondCond instanceof java.util.Date) { - double d = DateConverter.getXLSDateVal((java.util.Date) secondCond); - setval = d + ""; - } - currentCf.setCondition2(setval); - } - - /** - * retrieves the border colors for the current Conditional Format - * - * @return java.awt.Color array of Color objects for each border side (Top, - * Left, Bottom, Right) - * @see io.starter.formats.XLS.Cf#getBorderColors() - */ - public Color[] getBorderColors() { - return currentCf.getBorderColors(); - } - - /** - * returns the bottom border line color for the current Conditional Format - * - * @return int bottom border line color constant - * @see io.starter.formats.XLS.Cf#getBorderLineColorBottom() - * @see FormatHandle.COLOR_* constants - */ - public int getBorderLineColorBottom() { - return currentCf.getBorderLineColorBottom(); - } - - /** - * returns the left border line color for the current Conditional Format - * - * @return int left border line color constant - * @see io.starter.formats.XLS.Cf#getBorderLineColorLeft() - * @see FormatHandle.COLOR_* constants - */ - public int getBorderLineColorLeft() { - return currentCf.getBorderLineColorLeft(); - } - - /** - * returns the right border line color for the current Conditional Format - * - * @return int right border line color constant - * @see io.starter.formats.XLS.Cf#getBorderLineColorRight() - * @see FormatHandle.COLOR_* constants - */ - public int getBorderLineColorRight() { - return currentCf.getBorderLineColorRight(); - } - - /** - * returns the top border line color for the current Conditional Format - * - * @return int top border line color constant - * @see io.starter.formats.XLS.Cf#getBorderLineColorTop() - * @see FormatHandle.COLOR_* constants - */ - public int getBorderLineColorTop() { - return currentCf.getBorderLineColorTop(); - } - - /** - * returns the bottom border line style for the current Conditional Format - * - * @return int bottom border line style constant - * @see io.starter.formats.XLS.Cf#getBorderLineStylesBottom() - * @see FormatHandle.BORDER* line style constants - */ - public int getBorderLineStylesBottom() { - return currentCf.getBorderLineStylesBottom(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderLineStylesLeft() - */ - public int getBorderLineStylesLeft() { - return currentCf.getBorderLineStylesLeft(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderLineStylesRight() - */ - public int getBorderLineStylesRight() { - return currentCf.getBorderLineStylesRight(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderLineStylesTop() - */ - public int getBorderLineStylesTop() { - return currentCf.getBorderLineStylesTop(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderSizes() - */ - public int[] getBorderSizes() { - return currentCf.getBorderSizes(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getBorderStyles() - */ - public int[] getBorderStyles() { - return currentCf.getBorderStyles(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFont() - */ - public Font getFont() { - return currentCf.getFont(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontColorIndex() - */ - public int getFontColorIndex() { - return currentCf.getFontColorIndex(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontEscapement() - */ - public int getFontEscapement() { - return currentCf.getFontEscapement(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontHeight() - */ - public int getFontHeight() { - return currentCf.getFontHeight(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontOptsCancellation() - */ - public int getFontOptsCancellation() { - return currentCf.getFontOptsCancellation(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontOptsPosture() - */ - public int getFontOptsPosture() { - return currentCf.getFontOptsPosture(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontUnderlineStyle() - */ - public int getFontUnderlineStyle() { - return currentCf.getFontUnderlineStyle(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFontWeight() - */ - public int getFontWeight() { - return currentCf.getFontWeight(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getForegroundColor() - */ - public int getForegroundColor() { - return currentCf.getForegroundColor(); - } - - /** - * @return - * @see io.starter.formats.XLS.XLSRecord#getFormatPattern() - */ - public String getFormatPattern() { - return currentCf.getFormatPattern(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFormula1() - */ - public Formula getFormula1() { - return currentCf.getFormula1(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getFormula2() - */ - public Formula getFormula2() { - return currentCf.getFormula2(); - } - - /** - * returns the operator for this Conditional Format Rule
                  - * e.g. "bewteen", "greater than" ... - * - * @return - */ - public String getOperator() { - int op = currentCf.getOperator(); - if (op >= 0 && op < OPERATORS.length) - return OPERATORS[op]; - return "unknown operator: " + op; - } - - /** - * returns the type of this Conditional Format
                  - * e.g. "Cell value is" or "Formula value is" - * - * @return String Conditional Format Type - */ - public String getType() { - return currentCf.getTypeString(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getPatternFillColor() - */ - public int getPatternFillColor() { - return currentCf.getPatternFillColor(); - } - - /** - * returns the pattern color, if any, as an HTML color String. Includes custom - * OOXML colors. - * - * @return String HTML Color String - */ - public String getPatternFgColor() { - return currentCf.getPatternFgColor(); - } - - /** - * returns the pattern color, if any, as an HTML color String. Includes custom - * OOXML colors. - * - * @return String HTML Color String - */ - public String getPatternBgColor() { - return currentCf.getPatternBgColor(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getPatternFillColorBack() - */ - public int getPatternFillColorBack() { - return currentCf.getPatternFillColorBack(); - } - - /** - * @return - * @see io.starter.formats.XLS.Cf#getPatternFillStyle() - */ - public int getPatternFillStyle() { - return currentCf.getPatternFillStyle(); - } - - /** - * @param borderLineColorBottom - * @see io.starter.formats.XLS.Cf#setBorderLineColorBottom(int) - */ - public void setBorderLineColorBottom(int borderLineColorBottom) { - currentCf.setBorderLineColorBottom(borderLineColorBottom); - } - - /** - * @param borderLineColorLeft - * @see io.starter.formats.XLS.Cf#setBorderLineColorLeft(int) - */ - public void setBorderLineColorLeft(int borderLineColorLeft) { - currentCf.setBorderLineColorLeft(borderLineColorLeft); - } - - /** - * @param borderLineColorTop - * @see io.starter.formats.XLS.Cf#setBorderLineColorTop(int) - */ - public void setBorderLineColorTop(int borderLineColorTop) { - currentCf.setBorderLineColorTop(borderLineColorTop); - } - - /** - * @param borderLineStylesBottom - * @see io.starter.formats.XLS.Cf#setBorderLineStylesBottom(int) - */ - public void setBorderLineStylesBottom(int borderLineStylesBottom) { - currentCf.setBorderLineStylesBottom(borderLineStylesBottom); - } - - /** - * @param borderLineStylesLeft - * @see io.starter.formats.XLS.Cf#setBorderLineStylesLeft(int) - */ - public void setBorderLineStylesLeft(int borderLineStylesLeft) { - currentCf.setBorderLineStylesLeft(borderLineStylesLeft); - } - - /** - * @param borderLineStylesRight - * @see io.starter.formats.XLS.Cf#setBorderLineStylesRight(int) - */ - public void setBorderLineStylesRight(int borderLineStylesRight) { - currentCf.setBorderLineStylesRight(borderLineStylesRight); - } - - /** - * @param borderLineStylesTop - * @see io.starter.formats.XLS.Cf#setBorderLineStylesTop(int) - */ - public void setBorderLineStylesTop(int borderLineStylesTop) { - currentCf.setBorderLineStylesTop(borderLineStylesTop); - } - - /** - * @param fontColorIndex - * @see io.starter.formats.XLS.Cf#setFontColorIndex(int) - */ - public void setFontColorIndex(int fontColorIndex) { - currentCf.setFontColorIndex(fontColorIndex); - } - - /** - * @param fontEscapementFlag - * @see io.starter.formats.XLS.Cf#setFontEscapement(int) - */ - public void setFontEscapement(int fontEscapementFlag) { - currentCf.setFontEscapement(fontEscapementFlag); - } - - /** - * @param fontHeight - * @see io.starter.formats.XLS.Cf#setFontHeight(int) - */ - public void setFontHeight(int fontHeight) { - currentCf.setFontHeight(fontHeight); - } - - /** - * @param fontOptsCancellation - * @see io.starter.formats.XLS.Cf#setFontOptsCancellation(int) - */ - public void setFontOptsCancellation(int fontOptsCancellation) { - currentCf.setFontStriken((fontOptsCancellation == Cf.FONT_OPTIONS_CANCELLATION_ON)); - } - - /** - * @param fontOptsPosture - * @see io.starter.formats.XLS.Cf#setFontOptsPosture(int) - */ - public void setFontOptsPosture(int fontOptsPosture) { - currentCf.setFontOptsPosture(fontOptsPosture); - } - - /** - * @param fontUnderlineStyle - * @see io.starter.formats.XLS.Cf#setFontUnderlineStyle(int) - */ - public void setFontUnderlineStyle(int fontUnderlineStyle) { - currentCf.setFontUnderlineStyle(fontUnderlineStyle); - } - - /** - * @param fontWeight - * @see io.starter.formats.XLS.Cf#setFontWeight(int) - */ - public void setFontWeight(int fontWeight) { - currentCf.setFontWeight(fontWeight); - } - - /** - * @param patternFillColor - * @see io.starter.formats.XLS.Cf#setPatternFillColor(int) - */ - public void setPatternFillColor(int patternFillColor) { - currentCf.setPatternFillColor(patternFillColor, null); - } - - /** - * @param patternFillColorBack - * @see io.starter.formats.XLS.Cf#setPatternFillColorBack(int) - */ - public void setPatternFillColorBack(int patternFillColorBack) { - currentCf.setPatternFillColorBack(patternFillColorBack); - } - - /** - * @param patternFillStyle - * @see io.starter.formats.XLS.Cf#setPatternFillStyle(int) - */ - public void setPatternFillStyle(int patternFillStyle) { - currentCf.setPatternFillStyle(patternFillStyle); - } - - /** - * Get the first condition of the ConditionalFormat as a string representation - * - * @return - */ - public String getFirstCondition() { - return currentCf.getFormula1().getFormulaString(); - } - - /** - * Set the first condition of the ConditionalFormat - *

                  - * This value must conform to the Value Type of this ConditionalFormat or - * unexpected results may occur. For example, entering a string representation - * of a date here will not work if your ConditionalFormat is an integer... - *

                  - * A java.util.Date object can also be passed in. This value will be translated - * into an integer as excel stores dates. If you need to manipulate/retrieve - * this value later utilize the DateConverter tool to transform the value - *

                  - * String passed in should be a vaild XLS formula. Does not need to include the - * "=" - *

                  - * Types of conditions Integer values Decimal values User defined list Date Time - * Text length Formula - *

                  - * Be sure that your ConditionalFormat type (getConditionalFormatType()) matches - * the type of data. - * - * @param firstCond = the first condition for the ConditionalFormat - */ - public void setFirstCondition(Object firstCond) { - String setval = firstCond.toString(); - if (firstCond instanceof java.util.Date) { - double d = DateConverter.getXLSDateVal((java.util.Date) firstCond); - setval = d + ""; - } - currentCf.setCondition1(setval); - } - - public int getConditionalFormatType() { - return 0; - } -} diff --git a/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.kt b/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.kt new file mode 100644 index 0000000..0bf612a --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/ConditionalFormatRule.kt @@ -0,0 +1,590 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.Cf +import io.starter.formats.XLS.Font +import io.starter.formats.XLS.Formula +import io.starter.formats.XLS.formulas.Ptg +import io.starter.formats.XLS.formulas.PtgRef + +import java.awt.* + +/** + * ConditionalFormatRule defines a single rule for manipulation of the + * ConditionalFormat cells in Excel + * + * + * Each ConditionalFormatRule contains one rule and corresponding format data. + * + * @see ConditionalFormatHandle + * + * @see Handle + */ +class ConditionalFormatRule +/** + * Create a conditional format rule from a Cf record. + * + * @param theCf + */ +(theCf: Cf) : Handle { + + internal var currentCf: Cf? = null + + /** + * Get the type operator of this ConditionalFormat as a byte. + * + * + * These bytes map to the CONDITION_* static values in ConditionalFormatHandle + * + * @return + */ + /** + * set the type operator of this ConditionalFormat as a byte. + * + * + * These bytes map to the CONDITION_* static values in ConditionalFormatHandle + */ + var typeOperator: Byte + get() = currentCf!!.operator.toByte() + set(typOperator) = currentCf!!.setOperator(typOperator.toInt()) + + /** + * Get the second condition of the ConditionalFormat as a string representation + * + * @return + */ + val secondCondition: String? + get() = if (currentCf != null && currentCf!!.formula2 != null) currentCf!!.formula2!!.formulaString else null + + /** + * retrieves the border colors for the current Conditional Format + * + * @return java.awt.Color array of Color objects for each border side (Top, + * Left, Bottom, Right) + * @see io.starter.formats.XLS.Cf.getBorderColors + */ + val borderColors: Array? + get() = currentCf!!.borderColors + + /** + * returns the bottom border line color for the current Conditional Format + * + * @return int bottom border line color constant + * @see io.starter.formats.XLS.Cf.getBorderLineColorBottom + * @see FormatHandle.COLOR_* constants + */ + /** + * @param borderLineColorBottom + * @see io.starter.formats.XLS.Cf.setBorderLineColorBottom + */ + var borderLineColorBottom: Int + get() = currentCf!!.borderLineColorBottom + set(borderLineColorBottom) { + currentCf!!.borderLineColorBottom = borderLineColorBottom + } + + /** + * returns the left border line color for the current Conditional Format + * + * @return int left border line color constant + * @see io.starter.formats.XLS.Cf.getBorderLineColorLeft + * @see FormatHandle.COLOR_* constants + */ + /** + * @param borderLineColorLeft + * @see io.starter.formats.XLS.Cf.setBorderLineColorLeft + */ + var borderLineColorLeft: Int + get() = currentCf!!.borderLineColorLeft + set(borderLineColorLeft) { + currentCf!!.borderLineColorLeft = borderLineColorLeft + } + + /** + * returns the right border line color for the current Conditional Format + * + * @return int right border line color constant + * @see io.starter.formats.XLS.Cf.getBorderLineColorRight + * @see FormatHandle.COLOR_* constants + */ + val borderLineColorRight: Int + get() = currentCf!!.borderLineColorRight + + /** + * returns the top border line color for the current Conditional Format + * + * @return int top border line color constant + * @see io.starter.formats.XLS.Cf.getBorderLineColorTop + * @see FormatHandle.COLOR_* constants + */ + /** + * @param borderLineColorTop + * @see io.starter.formats.XLS.Cf.setBorderLineColorTop + */ + var borderLineColorTop: Int + get() = currentCf!!.borderLineColorTop + set(borderLineColorTop) { + currentCf!!.borderLineColorTop = borderLineColorTop + } + + /** + * returns the bottom border line style for the current Conditional Format + * + * @return int bottom border line style constant + * @see io.starter.formats.XLS.Cf.getBorderLineStylesBottom + * @see FormatHandle.BORDER* line style constants + */ + /** + * @param borderLineStylesBottom + * @see io.starter.formats.XLS.Cf.setBorderLineStylesBottom + */ + var borderLineStylesBottom: Int + get() = currentCf!!.borderLineStylesBottom + set(borderLineStylesBottom) { + currentCf!!.borderLineStylesBottom = borderLineStylesBottom + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getBorderLineStylesLeft + */ + /** + * @param borderLineStylesLeft + * @see io.starter.formats.XLS.Cf.setBorderLineStylesLeft + */ + var borderLineStylesLeft: Int + get() = currentCf!!.borderLineStylesLeft + set(borderLineStylesLeft) { + currentCf!!.borderLineStylesLeft = borderLineStylesLeft + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getBorderLineStylesRight + */ + /** + * @param borderLineStylesRight + * @see io.starter.formats.XLS.Cf.setBorderLineStylesRight + */ + var borderLineStylesRight: Int + get() = currentCf!!.borderLineStylesRight + set(borderLineStylesRight) { + currentCf!!.borderLineStylesRight = borderLineStylesRight + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getBorderLineStylesTop + */ + /** + * @param borderLineStylesTop + * @see io.starter.formats.XLS.Cf.setBorderLineStylesTop + */ + var borderLineStylesTop: Int + get() = currentCf!!.borderLineStylesTop + set(borderLineStylesTop) { + currentCf!!.borderLineStylesTop = borderLineStylesTop + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getBorderSizes + */ + val borderSizes: IntArray? + get() = currentCf!!.borderSizes + + /** + * @return + * @see io.starter.formats.XLS.Cf.getBorderStyles + */ + val borderStyles: IntArray? + get() = currentCf!!.borderStyles + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFont + */ + val font: Font? + get() = currentCf!!.font + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFontColorIndex + */ + /** + * @param fontColorIndex + * @see io.starter.formats.XLS.Cf.setFontColorIndex + */ + var fontColorIndex: Int + get() = currentCf!!.fontColorIndex + set(fontColorIndex) { + currentCf!!.fontColorIndex = fontColorIndex + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFontEscapement + */ + /** + * @param fontEscapementFlag + * @see io.starter.formats.XLS.Cf.setFontEscapement + */ + var fontEscapement: Int + get() = currentCf!!.fontEscapement + set(fontEscapementFlag) { + currentCf!!.fontEscapement = fontEscapementFlag + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFontHeight + */ + /** + * @param fontHeight + * @see io.starter.formats.XLS.Cf.setFontHeight + */ + var fontHeight: Int + get() = currentCf!!.fontHeight + set(fontHeight) { + currentCf!!.fontHeight = fontHeight + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFontOptsCancellation + */ + /** + * @param fontOptsCancellation + * @see io.starter.formats.XLS.Cf.setFontOptsCancellation + */ + var fontOptsCancellation: Int + get() = currentCf!!.fontOptsCancellation + set(fontOptsCancellation) { + currentCf!!.fontStriken = fontOptsCancellation == Cf.FONT_OPTIONS_CANCELLATION_ON + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFontOptsPosture + */ + /** + * @param fontOptsPosture + * @see io.starter.formats.XLS.Cf.setFontOptsPosture + */ + var fontOptsPosture: Int + get() = currentCf!!.fontOptsPosture + set(fontOptsPosture) { + currentCf!!.fontOptsPosture = fontOptsPosture + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFontUnderlineStyle + */ + /** + * @param fontUnderlineStyle + * @see io.starter.formats.XLS.Cf.setFontUnderlineStyle + */ + var fontUnderlineStyle: Int + get() = currentCf!!.fontUnderlineStyle + set(fontUnderlineStyle) { + currentCf!!.fontUnderlineStyle = fontUnderlineStyle + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFontWeight + */ + /** + * @param fontWeight + * @see io.starter.formats.XLS.Cf.setFontWeight + */ + var fontWeight: Int + get() = currentCf!!.fontWeight + set(fontWeight) { + currentCf!!.fontWeight = fontWeight + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getForegroundColor + */ + val foregroundColor: Int + get() = currentCf!!.foregroundColor + + /** + * @return + * @see io.starter.formats.XLS.XLSRecord.getFormatPattern + */ + val formatPattern: String? + get() = currentCf!!.formatPattern + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFormula1 + */ + val formula1: Formula? + get() = currentCf!!.formula1 + + /** + * @return + * @see io.starter.formats.XLS.Cf.getFormula2 + */ + val formula2: Formula? + get() = currentCf!!.formula2 + + /** + * returns the operator for this Conditional Format Rule

                  + * e.g. "bewteen", "greater than" ... + * + * @return + */ + val operator: String + get() { + val op = currentCf!!.operator.toInt() + return if (op >= 0 && op < OPERATORS.size) OPERATORS[op] else "unknown operator: $op" + } + + /** + * returns the type of this Conditional Format

                  + * e.g. "Cell value is" or "Formula value is" + * + * @return String Conditional Format Type + */ + val type: String + get() = currentCf!!.typeString + + /** + * @return + * @see io.starter.formats.XLS.Cf.getPatternFillColor + */ + /** + * @param patternFillColor + * @see io.starter.formats.XLS.Cf.setPatternFillColor + */ + var patternFillColor: Int + get() = currentCf!!.patternFillColor + set(patternFillColor) = currentCf!!.setPatternFillColor(patternFillColor, null) + + /** + * returns the pattern color, if any, as an HTML color String. Includes custom + * OOXML colors. + * + * @return String HTML Color String + */ + val patternFgColor: String? + get() = currentCf!!.patternFgColor + + /** + * returns the pattern color, if any, as an HTML color String. Includes custom + * OOXML colors. + * + * @return String HTML Color String + */ + val patternBgColor: String? + get() = currentCf!!.patternBgColor + + /** + * @return + * @see io.starter.formats.XLS.Cf.getPatternFillColorBack + */ + /** + * @param patternFillColorBack + * @see io.starter.formats.XLS.Cf.setPatternFillColorBack + */ + var patternFillColorBack: Int + get() = currentCf!!.patternFillColorBack + set(patternFillColorBack) { + currentCf!!.patternFillColorBack = patternFillColorBack + } + + /** + * @return + * @see io.starter.formats.XLS.Cf.getPatternFillStyle + */ + /** + * @param patternFillStyle + * @see io.starter.formats.XLS.Cf.setPatternFillStyle + */ + var patternFillStyle: Int + get() = currentCf!!.patternFillStyle + set(patternFillStyle) { + currentCf!!.patternFillStyle = patternFillStyle + } + + /** + * Get the first condition of the ConditionalFormat as a string representation + * + * @return + */ + val firstCondition: String + get() = currentCf!!.formula1!!.formulaString + + val conditionalFormatType: Int + get() = 0 + + init { + currentCf = theCf + } + + /** + * evaluates the criteria for this Conditional Format Rule

                  + * if the criteria involves a comparison i.e. equals, less than, etc., it uses + * the value from the passed in referenced cell to compare with + * + * @param Ptg refcell - the Ptg location to obtain cell value from + * @return boolean true if evaluation of criteria passes + * @see io.starter.formats.XLS.Cf.evaluate + */ + fun evaluate(refcell: CellHandle): Boolean { + val /* Ref */ pr = PtgRef.createPtgRefFromString(refcell.cellAddress, null) + return currentCf!!.evaluate(pr) + } + + /** + * Set the second condition of the ConditionalFormat utilizing a string. This + * value must conform to the Value Type of this ConditionalFormat or unexpected + * results may occur. For example, entering a string representation of a date + * here will not work if your ConditionalFormat is an integer... + * + * + * String passed in should be a vaild XLS formula. Does not need to include the + * "=" + * + * + * Types of conditions Integer values Decimal values User defined list Date Time + * Text length Formula + * + * + * Be sure that your ConditionalFormat type (getConditionalFormatType()) matches + * the type of data. + * + * @return + */ + fun setSecondCondition(secondCond: Any) { + var setval = secondCond.toString() + if (secondCond is java.util.Date) { + val d = DateConverter.getXLSDateVal(secondCond) + setval = d.toString() + "" + } + currentCf!!.setCondition2(setval) + } + + /** + * Set the first condition of the ConditionalFormat + * + * + * This value must conform to the Value Type of this ConditionalFormat or + * unexpected results may occur. For example, entering a string representation + * of a date here will not work if your ConditionalFormat is an integer... + * + * + * A java.util.Date object can also be passed in. This value will be translated + * into an integer as excel stores dates. If you need to manipulate/retrieve + * this value later utilize the DateConverter tool to transform the value + * + * + * String passed in should be a vaild XLS formula. Does not need to include the + * "=" + * + * + * Types of conditions Integer values Decimal values User defined list Date Time + * Text length Formula + * + * + * Be sure that your ConditionalFormat type (getConditionalFormatType()) matches + * the type of data. + * + * @param firstCond = the first condition for the ConditionalFormat + */ + fun setFirstCondition(firstCond: Any) { + var setval = firstCond.toString() + if (firstCond is java.util.Date) { + val d = DateConverter.getXLSDateVal(firstCond) + setval = d.toString() + "" + } + currentCf!!.setCondition1(setval) + } + + companion object { + // static shorts for setting ConditionalFormat type + val VALUE_ANY: Byte = 0x0 + val VALUE_INTEGER: Byte = 0x1 + val VALUE_DECIMAL: Byte = 0x2 + val VALUE_USER_DEFINED_LIST: Byte = 0x3 + val VALUE_DATE: Byte = 0x4 + val VALUE_TIME: Byte = 0x5 + val VALUE_TEXT_LENGTH: Byte = 0x6 + val VALUE_FORMULA: Byte = 0x7 + + // static shorts for setting action on error + var ERROR_STOP: Byte = 0x0 + var ERROR_WARN: Byte = 0x1 + var ERROR_INFO: Byte = 0x2 + + // static shorts for setting conditions on ConditionalFormat + val CONDITION_BETWEEN: Byte = 0x0 + val CONDITION_NOT_BETWEEN: Byte = 0x1 + val CONDITION_EQUAL: Byte = 0x2 + val CONDITION_NOT_EQUAL: Byte = 0x3 + val CONDITION_GREATER_THAN: Byte = 0x4 + val CONDITION_LESS_THAN: Byte = 0x5 + val CONDITION_GREATER_OR_EQUAL: Byte = 0x6 + val CONDITION_LESS_OR_EQUAL: Byte = 0x7 + + var OPERATORS = arrayOf("nocomparison", "between", "notBetween", "equal", "notEqual", "greaterThan", "lessThan", "greaterOrEqual", "lessOrEqual", "beginsWith", "endsWith", "containsText", "notContains") + + /** + * Get the byte representing the condition type string passed in. Options are' + * "between", "notBetween", "equal", "notEqual", "greaterThan", "lessThan", + * "greaterOrEqual", "lessOrEqual" + * + * @return + */ + fun getConditionNumber(conditionType: String): Byte { + for (i in OPERATORS.indices) { + if (conditionType.equals(OPERATORS[i], ignoreCase = true)) + return i.toByte() + } + return -1 + } + + var VALUE_TYPE = arrayOf("any", "integer", "decimal", "userDefinedList", "date", "time", "textLength", "formula") + + /** + * Get the byte representing the value type string passed in. Options are' + * "any", "integer", "decimal", "userDefinedList", "date", "time", "textLength", + * "formula" + * + * @return + */ + fun getValueNumber(valueType: String): Byte { + for (i in VALUE_TYPE.indices) { + if (valueType.equals(VALUE_TYPE[i], ignoreCase = true)) + return i.toByte() + } + return -1 + } + } +} diff --git a/src/main/java/io/starter/OpenXLS/DateConverter.java b/src/main/java/io/starter/OpenXLS/DateConverter.kt similarity index 50% rename from src/main/java/io/starter/OpenXLS/DateConverter.java rename to src/main/java/io/starter/OpenXLS/DateConverter.kt index 7c06154..fc335e8 100644 --- a/src/main/java/io/starter/OpenXLS/DateConverter.java +++ b/src/main/java/io/starter/OpenXLS/DateConverter.kt @@ -20,81 +20,93 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.OpenXLS; +package io.starter.OpenXLS //import java.text.SimpleDateFormat; -import io.starter.toolkit.Logger; +import io.starter.toolkit.Logger -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.StringTokenizer; +import java.util.Calendar +import java.util.Date +import java.util.GregorianCalendar +import java.util.StringTokenizer /** * Provides methods for conversion to and from Excel serial date values. - *

                  + * + * * Excel stores dates as the number of days since midnight on January 1, 1900. * Times are represented as fractional days. For example, 6:00 AM on February 2, * 1900 is represented as 33.25. Excel incorrectly treats 1900 as a leap year, * so serial dates after February 28, 1900 are one higher than they otherwise * should be and the value 60 is unmapped. It also interprets the value 0 as * January 0, 1900. - *

                  + * + * * Excel does not support negative serial date values, so it cannot handle dates * prior to 1900. It also does not currently accept date values with a year of * 10000 or greater. OpenXLS does not currently support negative date values, * but this feature is planned. If you wish to restrict the output to the subset * of values supported by Excel, you may enable input validation by calling - * {@link #setValidate}. - *

                  + * [.setValidate]. + * + * * Due to the inherent inaccuracy of floating-point types, values from this * class can only be guaranteed to equal values generated by Excel to eight * decimal places. This provides accuracy to the unit milliseconds, the maximum * precision of Java's date classes. Accuracy outside the range supported by * Excel is not guaranteed and will degrade as the values get farther from zero. */ -public class DateConverter { +object DateConverter { /** * The number of milliseconds in a day. */ - private static final int MILLIS_DAY = 86400000; + private val MILLIS_DAY = 86400000 /** * The extra day caused by the 1900 leap year bug. */ - private static final int EXTRA_DAY = 60; + private val EXTRA_DAY = 60 /** * Calendar used for date calculation. */ - private static Calendar calendar = Calendar.getInstance(); + private var calendar = Calendar.getInstance() /** * Whether to validate input dates for Excel compatibility. */ - private static boolean validate = false; + /** + * Returns whether input validation is on. + */ + /** + * Sets whether to perform input validation. + */ + var validate = false /** * The set of supported serial date encoding schemes. */ - public enum DateFormat { + enum class DateFormat private constructor(val epochDelta: Int, val lowerLimit: Double, val upperLimit: Double) { /** * 1900 epoch with negative value support as used in OOXML. - *

                  - * Lower limit: -9999/01/01 00:00:00, value -4 346 018
                  - * Epoch date: 1899/12/30 00:00:00, value 0
                  - * Upper limit: 9999/12/31 23:59:59, value 2 958 465.999 988 4
                  + * + * + * Lower limit: -9999/01/01 00:00:00, value -4 346 018

                  + * Epoch date: 1899/12/30 00:00:00, value 0

                  + * Upper limit: 9999/12/31 23:59:59, value 2 958 465.999 988 4

                  */ OOXML_1900(25569, -4346018, 2958465.9999884), /** * 1900 epoch without negative value support as used in BIFF8. - *

                  - * Epoch date: 1899/12/31 00:00:00, value 0
                  - * Lower limit: 1900/01/01 00:00:00, value 1
                  - * Upper limit: 9999/12/31 23:59:59, value 2 958 465.999 988 4
                  - *

                  + * + * + * Epoch date: 1899/12/31 00:00:00, value 0

                  + * Lower limit: 1900/01/01 00:00:00, value 1

                  + * Upper limit: 9999/12/31 23:59:59, value 2 958 465.999 988 4

                  + * + * * In this system 1900 is (incorrectly) considered a leap year. Serial * dates after 1900/02/28 are one higher than they otherwise should be * and the value 60 is unmapped. @@ -103,40 +115,20 @@ public enum DateFormat { /** * 1904 epoch without negative value support as used by Excel for Mac. - *

                  - * Epoch date: 1904/01/01 00:00:00, value 0
                  - * Lower limit: 1904/01/01 00:00:00, value 0
                  - * Upper limit: 9999/12/31 23:59:59, value 2 957 003.999 988 4
                  + * + * + * Epoch date: 1904/01/01 00:00:00, value 0

                  + * Lower limit: 1904/01/01 00:00:00, value 0

                  + * Upper limit: 9999/12/31 23:59:59, value 2 957 003.999 988 4

                  */ - LEGACY_1904(24107, 0, 2957003.9999884); - - private final int epoch_delta; - private final double limit_lower, limit_upper; - - DateFormat(int delta, double min, double max) { - epoch_delta = delta; - limit_lower = min; - limit_upper = max; - } - - protected int getEpochDelta() { - return epoch_delta; - } - - public double getLowerLimit() { - return limit_lower; - } - - public double getUpperLimit() { - return limit_upper; - } + LEGACY_1904(24107, 0, 2957003.9999884) } /** * Gets a clone of the calendar used for date calculation. */ - public static Calendar getCalendar() { - return (Calendar) calendar.clone(); + fun getCalendar(): Calendar { + return calendar.clone() as Calendar } /** @@ -145,69 +137,44 @@ public static Calendar getCalendar() { * * @param cal the calendar that should be used for date calculations */ - public static void setCalendar(Calendar cal) { - calendar = cal; - } - - /** - * Returns whether input validation is on. - */ - public static boolean getValidate() { - return validate; - } - - /** - * Sets whether to perform input validation. - */ - public static void setValidate(boolean validate) { - DateConverter.validate = validate; + fun setCalendar(cal: Calendar) { + calendar = cal } /** * returns whether this method will work with your input string */ - public static boolean isParseableDateString(String str) { + fun isParseableDateString(str: String): Boolean { + var str = str // fix problem with timestamps tagged on end if (str.indexOf(" ") > 0) { - str = str.substring(0, str.indexOf(" ")); + str = str.substring(0, str.indexOf(" ")) } try { - java.sql.Date d1 = java.sql.Date.valueOf(str); - getXLSDateVal(d1); - return true; - } catch (IllegalArgumentException e) { - return false; + val d1 = java.sql.Date.valueOf(str) + getXLSDateVal(d1) + return true + } catch (e: IllegalArgumentException) { + return false } + } /** * attempt to interpret a date string into a date * returns null if cannot be converted to date */ - public static Date getDate(String str) { + fun getDate(str: String): Date? { try { - java.sql.Timestamp d1 = java.sql.Timestamp.valueOf(str); - Calendar cal = (Calendar) calendar.clone(); - cal.setTime(d1); - return cal.getTime(); - } catch (Exception e) { + val d1 = java.sql.Timestamp.valueOf(str) + val cal = calendar.clone() as Calendar + cal.time = d1 + return cal.time + } catch (e: Exception) { } - return null; - } + return null - /** - * Converts the value of the given Calendar to an Excel serial date. - * The date will be returned in the - * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. - * - * @param cal the Calendar to convert - * @return the Excel serial date representing the given calendar's value in - * the calendar's time zone - * @deprecated Use {@link #getXLSDateVal(Calendar, DateFormat)} instead. - */ - public static double getXLSDateVal(Calendar cal) { - return getXLSDateVal(cal, DateFormat.LEGACY_1900); } /** @@ -218,32 +185,32 @@ public static double getXLSDateVal(Calendar cal) { * @return the Excel serial date representing the given calendar's value in * the calendar's time zone */ - public static double getXLSDateVal(Calendar cal, DateFormat format) { + @JvmOverloads + fun getXLSDateVal(cal: Calendar, format: DateFormat = DateFormat.LEGACY_1900): Double { // Get the UTC milliseconds since the epoch - long millis = cal.getTimeInMillis(); + var millis = cal.timeInMillis // Add the GMT offset and daylight savings offset for the time zone - millis += cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET); + millis += (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)).toLong() // Convert from milliseconds to days - double days = (double) millis / MILLIS_DAY; + var days = millis.toDouble() / MILLIS_DAY // Switch from UNIX epoch to the Excel epoch - days += format.getEpochDelta(); + days += format.epochDelta.toDouble() // If the date is after February 28, 1900 add one day. // This compensates for Excel incorrectly treating 1900 as a leap year if (format == DateFormat.LEGACY_1900 && days >= EXTRA_DAY) - days += 1; + days += 1.0 // Perform validation - if (validate && (days < format.getLowerLimit() - || days > format.getUpperLimit())) - throw new IllegalArgumentException( - "the given date is not supported by Excel"); + if (validate && (days < format.lowerLimit || days > format.upperLimit)) + throw IllegalArgumentException( + "the given date is not supported by Excel") // Return the Excel serial date value - return days; + return days } /** @@ -253,37 +220,11 @@ public static double getXLSDateVal(Calendar cal, DateFormat format) { * @param format the serial date format to use * @return the Excel serial date value corresponding to the given date */ - public static double getXLSDateVal(Date date, DateFormat format) { - Calendar cal = (Calendar) calendar.clone(); - cal.setTime(date); - return getXLSDateVal(cal, format); - } - - /** - * Converts the given Date to an Excel serial date in the default time zone. - * The date will be returned in the - * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. - * - * @param date the date to be converted - * @return the Excel serial date value corresponding to the given date - * @deprecated Use {@link #getXLSDateVal(Date, DateFormat)} instead. - */ - public static double getXLSDateVal(Date date) { - return getXLSDateVal(date, DateFormat.LEGACY_1900); - } - - /** - * Parses the the given Excel serial date and returns a Calendar. - * The date will be interpreted in the - * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. - * - * @param date the Excel serial date to be interpreted - * @return a Calendar representing the given Excel date interpreted in the - * default time zone - * @deprecated Use {@link #getCalendarFromNumber(double, DateFormat)} instead. - */ - public static Calendar getCalendarFromNumber(double date) { - return getCalendarFromNumber(date, DateFormat.LEGACY_1900); + @JvmOverloads + fun getXLSDateVal(date: Date, format: DateFormat = DateFormat.LEGACY_1900): Double { + val cal = calendar.clone() as Calendar + cal.time = date + return getXLSDateVal(cal, format) } /** @@ -294,50 +235,52 @@ public static Calendar getCalendarFromNumber(double date) { * @return a Calendar representing the given Excel date interpreted in the * default time zone */ - public static Calendar getCalendarFromNumber(double date, DateFormat format) { - Calendar cal = getCalendar(); - double days = date; + @JvmOverloads + fun getCalendarFromNumber(date: Double, format: DateFormat = DateFormat.LEGACY_1900): Calendar { + val cal = getCalendar() + var days = date // For the legacy 1900 epoch, if the date is after 1900/02/28 subtract // one day. This compensates for 1900 being considered a leap year. // Not matching the non-existent February 29 causes it to become // March 1. This behavior matches that of Calendar for the same input. if (format == DateFormat.LEGACY_1900 && days > EXTRA_DAY) - days -= 1; + days -= 1.0 // Switch from the Excel epoch to the UNIX epoch - days -= format.getEpochDelta(); + days -= format.epochDelta.toDouble() // Convert from days to milliseconds - long millis = Math.round(days * MILLIS_DAY); + val millis = Math.round(days * MILLIS_DAY) // Set the calendar's approximate time so zone offsets are correct // The offsets can still be wrong for certain border cases. - cal.setTimeInMillis(millis); + cal.timeInMillis = millis // Adjust for time zone and daylight saving time offsets - long offset = 0; - for (int count = 0; offset != (offset = cal.get(Calendar.ZONE_OFFSET) - + cal.get(Calendar.DST_OFFSET)) - && count < 3; count++) - cal.setTimeInMillis(millis - offset); + var offset: Long = 0 + var count = 0 + while (offset != (offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)).toLong()) && count < 3) { + cal.timeInMillis = millis - offset + count++ + } - return cal; + return cal } /** * Parses the the given Excel serial date and returns a Date. * The date will be interpreted in the - * {@linkplain DateFormat#LEGACY_1900 legacy 1900} format. + * [legacy 1900][DateFormat.LEGACY_1900] format. * * @param date the Excel serial date to be interpreted * @param format the date format with which to interpret the serial date * @return a Calendar representing the given Excel date interpreted in the * default time zone - * @deprecated Use {@link #getCalendarFromNumber(double, DateFormat)} instead. */ - public static Date getDateFromNumber(double date) { - return getCalendarFromNumber(date).getTime(); + @Deprecated("Use {@link #getCalendarFromNumber(double, DateFormat)} instead.") + fun getDateFromNumber(date: Double): Date { + return getCalendarFromNumber(date).time } /** @@ -347,14 +290,13 @@ public static Date getDateFromNumber(double date) { * @return a Date representing the given Excel date interpreted in the * default time zone * @throws ClassCastException if the passed object is not a Number - * @deprecated Use {@link #getDateFromNumber(double)} instead. */ - public static Date getDateFromNumber(Object number) { - if (number instanceof Number) - return getDateFromNumber(((Number) number).doubleValue()); - + @Deprecated("Use {@link #getDateFromNumber(double)} instead.") + fun getDateFromNumber(number: Any): Date { + return if (number is Number) + getDateFromNumber(number.toDouble()) else - throw new ClassCastException("passed object was not a number"); + throw ClassCastException("passed object was not a number") } /** @@ -364,10 +306,10 @@ public static Date getDateFromNumber(Object number) { * @return a Date representing the given Excel date interpreted in the * default time zone * @throws ClassCastException if the passed object is not a Number - * @deprecated Identical to {@link #getDateFromNumber(Object)}. */ - public static Date getNonLocalizedDateFromNumber(Object number) { - return getDateFromNumber(number); + @Deprecated("Identical to {@link #getDateFromNumber(Object)}.") + fun getNonLocalizedDateFromNumber(number: Any): Date { + return getDateFromNumber(number) } /** @@ -377,19 +319,17 @@ public static Date getNonLocalizedDateFromNumber(Object number) { * @return a Calendar representing the given Excel date interpreted in the * default time zone * @throws ClassCastException if the passed object is not a Number - * @deprecated Use {@link #getCalendarFromNumber(double)} instead. */ - public static Calendar getCalendarFromNumber(Object number) { - if (number == null) - throw new ClassCastException("object cannot be converted to a date"); - if (!(number instanceof Number)) - number = new Double(number.toString()); - - if (number instanceof Number) - return getCalendarFromNumber(((Number) number).doubleValue()); - + @Deprecated("Use {@link #getCalendarFromNumber(double)} instead.") + fun getCalendarFromNumber(number: Any?): Calendar { + var number: Any? = number ?: throw ClassCastException("object cannot be converted to a date") + if (number !is Number) + number = Double(number!!.toString()) + + return if (number is Number) + getCalendarFromNumber(number.toDouble()) else - throw new ClassCastException("passed object was not a number"); + throw ClassCastException("passed object was not a number") } /** @@ -399,10 +339,10 @@ public static Calendar getCalendarFromNumber(Object number) { * @return a Date representing the given Excel date interpreted in the * default time zone * @throws ClassCastException if the passed object is not a Number - * @deprecated Identical to {@link #getDateFromNumber(Object)}. */ - public static Calendar getNonLocalizedCalendarFromNumber(Object number) { - return getCalendarFromNumber(number); + @Deprecated("Identical to {@link #getDateFromNumber(Object)}.") + fun getNonLocalizedCalendarFromNumber(number: Any): Calendar { + return getCalendarFromNumber(number) } /** @@ -412,63 +352,65 @@ public static Calendar getNonLocalizedCalendarFromNumber(Object number) { * @return a Date representing the given Excel date interpreted in the * default time zone */ - public static Date getDateFromCell(CellHandle cell) { - return getCalendarFromCell(cell).getTime(); + fun getDateFromCell(cell: CellHandle): Date { + return getCalendarFromCell(cell).time } /** * returns a Java Calendar from a CellHandle containing an Excel-formatted * Date - *

                  + * + * * The Excel date format does not map 100% accurately to Java dates, due to * the limitation of the precision of the Excel floating-point value record. * Due to this, OpenXLS dates may be too precise, this method will round * the java.util.Date returned to the precision entered. Rounding is handled * by the ROUND_HALF_UP method. - *

                  + * + * * Pass in a static Calendar precision, options are Calendar.HOUR * Calendar.MINUTE Calendar.SECOND Calendar.MILLISECOND * * @return Calendar - A GregorianCalendar value of the Cell */ - public static Calendar getCalendarFromCellWithPrecision(Cell cell, - int roundingCalendarField) { - Calendar tmp = getCalendarFromCell(cell); - GregorianCalendar newCalendar = new GregorianCalendar(); - newCalendar.set(Calendar.YEAR, tmp.get(Calendar.YEAR)); - newCalendar.set(Calendar.MONTH, tmp.get(Calendar.MONTH)); - newCalendar.set(Calendar.DAY_OF_MONTH, tmp.get(Calendar.DAY_OF_MONTH)); - newCalendar.set(Calendar.HOUR_OF_DAY, tmp.get(Calendar.HOUR_OF_DAY)); + fun getCalendarFromCellWithPrecision(cell: Cell, + roundingCalendarField: Int): Calendar { + val tmp = getCalendarFromCell(cell) + val newCalendar = GregorianCalendar() + newCalendar.set(Calendar.YEAR, tmp.get(Calendar.YEAR)) + newCalendar.set(Calendar.MONTH, tmp.get(Calendar.MONTH)) + newCalendar.set(Calendar.DAY_OF_MONTH, tmp.get(Calendar.DAY_OF_MONTH)) + newCalendar.set(Calendar.HOUR_OF_DAY, tmp.get(Calendar.HOUR_OF_DAY)) if (roundingCalendarField == Calendar.HOUR) { if (tmp.get(Calendar.MINUTE) > 29) { newCalendar.set(Calendar.HOUR_OF_DAY, - tmp.get(Calendar.HOUR_OF_DAY) + 1); + tmp.get(Calendar.HOUR_OF_DAY) + 1) } - newCalendar.set(Calendar.MINUTE, 0); - newCalendar.set(Calendar.SECOND, 0); - newCalendar.set(Calendar.MILLISECOND, 0); - return newCalendar; + newCalendar.set(Calendar.MINUTE, 0) + newCalendar.set(Calendar.SECOND, 0) + newCalendar.set(Calendar.MILLISECOND, 0) + return newCalendar } - newCalendar.set(Calendar.MINUTE, tmp.get(Calendar.MINUTE)); + newCalendar.set(Calendar.MINUTE, tmp.get(Calendar.MINUTE)) if (roundingCalendarField == Calendar.MINUTE) { if (tmp.get(Calendar.SECOND) > 29) { - newCalendar.set(Calendar.MINUTE, tmp.get(Calendar.MINUTE) + 1); + newCalendar.set(Calendar.MINUTE, tmp.get(Calendar.MINUTE) + 1) } - newCalendar.set(Calendar.SECOND, 0); - newCalendar.set(Calendar.MILLISECOND, 0); - return newCalendar; + newCalendar.set(Calendar.SECOND, 0) + newCalendar.set(Calendar.MILLISECOND, 0) + return newCalendar } - newCalendar.set(Calendar.SECOND, tmp.get(Calendar.SECOND)); + newCalendar.set(Calendar.SECOND, tmp.get(Calendar.SECOND)) if (roundingCalendarField == Calendar.SECOND) { if (tmp.get(Calendar.MILLISECOND) > 499) { - newCalendar.set(Calendar.SECOND, tmp.get(Calendar.SECOND) + 1); + newCalendar.set(Calendar.SECOND, tmp.get(Calendar.SECOND) + 1) } - newCalendar.set(Calendar.MILLISECOND, 0); - return newCalendar; + newCalendar.set(Calendar.MILLISECOND, 0) + return newCalendar } // round the milliseconds - newCalendar.set(Calendar.MILLISECOND, tmp.get(Calendar.MILLISECOND)); - return newCalendar; + newCalendar.set(Calendar.MILLISECOND, tmp.get(Calendar.MILLISECOND)) + return newCalendar } @@ -479,24 +421,22 @@ public static Calendar getCalendarFromCellWithPrecision(Cell cell, * @return a Calendar representing the given Excel date interpreted in the * default time zone */ - @SuppressWarnings("deprecation") - public static Calendar getCalendarFromCell(Cell cell) { - double value; - DateFormat format = DateFormat.LEGACY_1900; + fun getCalendarFromCell(cell: Cell): Calendar { + val value: Double + var format = DateFormat.LEGACY_1900 - if (cell instanceof CellHandle) { - CellHandle realCell = (CellHandle) cell; - value = realCell.getDoubleVal(); + if (cell is CellHandle) { + value = cell.doubleVal - WorkBook book = realCell.getWorkBook(); + val book = cell.workBook if (null != book) { - format = book.getDateFormat(); + format = book.dateFormat } } else { - value = Double.parseDouble(cell.getVal().toString()); + value = java.lang.Double.parseDouble(cell.`val`.toString()) } - return getCalendarFromNumber(value, format); + return getCalendarFromNumber(value, format) } /** @@ -506,11 +446,11 @@ public static Calendar getCalendarFromCell(Cell cell) { * @param possibleDate the string to check for date formats * @return whether the given string matches a known date format */ - public static boolean isDatePattern(String possibleDate) { + fun isDatePattern(possibleDate: String): Boolean { if (possibleDate.indexOf("/") == -1) - return false; - StringTokenizer st = new StringTokenizer(possibleDate, "/"); - return st.countTokens() == 3; + return false + val st = StringTokenizer(possibleDate, "/") + return st.countTokens() == 3 } /** @@ -520,152 +460,175 @@ public static boolean isDatePattern(String possibleDate) { * @param dateStr * @return null if not a valid date */ - public static Calendar convertStringToCalendar(String dateStr) { + fun convertStringToCalendar(dateStr: String): Calendar? { if (!isDatePattern(dateStr)) - return null; - int m, d, y; - StringTokenizer st = new StringTokenizer(dateStr, "/"); + return null + val m: Int + val d: Int + var y: Int + val st = StringTokenizer(dateStr, "/") try { - m = Integer.valueOf(st.nextToken()).intValue() - 1; - d = Integer.valueOf(st.nextToken()).intValue(); - String s = st.nextToken(); - int h, mn, sc; - h = mn = sc = 0; + m = Integer.valueOf(st.nextToken()).toInt() - 1 + d = Integer.valueOf(st.nextToken()).toInt() + var s = st.nextToken() + var h: Int + var mn: Int + var sc: Int + sc = 0 + mn = sc + h = mn if (s.indexOf(" ") > -1) { - int i = s.indexOf(" "); - String time = s.substring(i + 1); - s = s.substring(0, i); // rest of date - String[] timetokens = time.split(":"); - if (timetokens.length > 0) - h = Integer.valueOf(timetokens[0]).intValue(); - if (timetokens.length > 1) - mn = Integer.valueOf(timetokens[1]).intValue(); - if (timetokens.length > 2) - sc = Integer.valueOf(timetokens[2]).intValue(); + val i = s.indexOf(" ") + val time = s.substring(i + 1) + s = s.substring(0, i) // rest of date + val timetokens = time.split(":".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + if (timetokens.size > 0) + h = Integer.valueOf(timetokens[0]).toInt() + if (timetokens.size > 1) + mn = Integer.valueOf(timetokens[1]).toInt() + if (timetokens.size > 2) + sc = Integer.valueOf(timetokens[2]).toInt() } - y = Integer.valueOf(s).intValue(); + y = Integer.valueOf(s).toInt() if (y < 100) - y += 2000; - GregorianCalendar cdr = new GregorianCalendar(y, m, d, h, mn, sc); - return cdr; - } catch (Exception e) { - return null; + y += 2000 + return GregorianCalendar(y, m, d, h, mn, sc) + } catch (e: Exception) { + return null } + } /** * Returns the value of the cell as a date formatted as a String date * representation. The format is determined by inspecting the excel format. - *

                  + * + * * Currently supported formats in this method are: mm/dd/yy dd-mmm-yy dd-mmm * mmm-yy mm/dd/yy hh:mm - *

                  + * + * * If the cell's date forrmat pattern falls outside of this range, the * default output will be in the following format mm/dd/yyyy * * @return String, the value of a cell formatted as a date - * @deprecated The date format handling in this method is wildly incorrect. - * It is retained only to provide compatibility with legacy - * OpenXLS XML files. */ - public static String getFormattedDateVal(CellHandle cell) { + @Deprecated("The date format handling in this method is wildly incorrect.\n" + + " It is retained only to provide compatibility with legacy\n" + + " OpenXLS XML files.") + fun getFormattedDateVal(cell: CellHandle): String { // assemble the formatting for the javascript. - if (!cell.isDate()) return "Not a Date"; - if (Double.isNaN(cell.getDoubleVal())) - return ""; // 20060623 KSC - Date cal = DateConverter.getDateFromCell(cell); - FormatHandle f = cell.getFormatHandle(); - int pat = f.getFormatPatternId(); + if (!cell.isDate) return "Not a Date" + if (java.lang.Double.isNaN(cell.doubleVal)) + return "" // 20060623 KSC + val cal = DateConverter.getDateFromCell(cell) + val f = cell.formatHandle + val pat = f!!.formatPatternId // SimpleDateFormat sdf = new SimpleDateFormat(); KSC: reuse - switch (pat) { + when (pat) { - case 0xe: - WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy"); - return WorkBookHandle.simpledateformat.format(cal); - - case 0xf: - WorkBookHandle.simpledateformat.applyPattern("dd-MMM-yy"); - return WorkBookHandle.simpledateformat.format(cal); + 0xe -> { + WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy") + return WorkBookHandle.simpledateformat.format(cal) + } - case 0x10: - WorkBookHandle.simpledateformat.applyPattern("dd-MMM"); - return WorkBookHandle.simpledateformat.format(cal); + 0xf -> { + WorkBookHandle.simpledateformat.applyPattern("dd-MMM-yy") + return WorkBookHandle.simpledateformat.format(cal) + } - case 0x11: - WorkBookHandle.simpledateformat.applyPattern("MMM-yy"); - return WorkBookHandle.simpledateformat.format(cal); + 0x10 -> { + WorkBookHandle.simpledateformat.applyPattern("dd-MMM") + return WorkBookHandle.simpledateformat.format(cal) + } - case 0x16: - WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy HH:mm"); - return WorkBookHandle.simpledateformat.format(cal); + 0x11 -> { + WorkBookHandle.simpledateformat.applyPattern("MMM-yy") + return WorkBookHandle.simpledateformat.format(cal) + } - default: - WorkBookHandle.simpledateformat.applyPattern("MM/dd/yyyy"); - return WorkBookHandle.simpledateformat.format(cal); + 0x16 -> { + WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy HH:mm") + return WorkBookHandle.simpledateformat.format(cal) + } + else -> { + WorkBookHandle.simpledateformat.applyPattern("MM/dd/yyyy") + return WorkBookHandle.simpledateformat.format(cal) + } } } - /** - * @deprecated The date format handling in this method is wildly incorrect. - * It is retained only to provide compatibility with legacy - * OpenXLS XML files. - */ - public static Date parseDate(String s, int pat) { - if (s.equals("")) - return null; + + @Deprecated("The date format handling in this method is wildly incorrect.\n" + + " It is retained only to provide compatibility with legacy\n" + + " OpenXLS XML files.") + fun parseDate(s: String, pat: Int): Date? { + if (s == "") + return null //SimpleDateFormat sdf = new SimpleDateFormat(); try { - switch (pat) { - case 0xe: - WorkBookHandle.simpledateformat.applyPattern("dd/MM/yy"); - return WorkBookHandle.simpledateformat.parse(s); - - case 0xf: - WorkBookHandle.simpledateformat.applyPattern("dd-MMM-yy"); - return WorkBookHandle.simpledateformat.parse(s); + when (pat) { + 0xe -> { + WorkBookHandle.simpledateformat.applyPattern("dd/MM/yy") + return WorkBookHandle.simpledateformat.parse(s) + } - case 0x10: - WorkBookHandle.simpledateformat.applyPattern("dd-MMM"); - return WorkBookHandle.simpledateformat.parse(s); + 0xf -> { + WorkBookHandle.simpledateformat.applyPattern("dd-MMM-yy") + return WorkBookHandle.simpledateformat.parse(s) + } - case 0x11: - WorkBookHandle.simpledateformat.applyPattern("MMM-yy"); - return WorkBookHandle.simpledateformat.parse(s); + 0x10 -> { + WorkBookHandle.simpledateformat.applyPattern("dd-MMM") + return WorkBookHandle.simpledateformat.parse(s) + } - case 0x16: - WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy HH:mm"); - return WorkBookHandle.simpledateformat.parse(s); + 0x11 -> { + WorkBookHandle.simpledateformat.applyPattern("MMM-yy") + return WorkBookHandle.simpledateformat.parse(s) + } - default: - WorkBookHandle.simpledateformat.applyPattern("dd/MM/yyyy"); - return WorkBookHandle.simpledateformat.parse(s); + 0x16 -> { + WorkBookHandle.simpledateformat.applyPattern("MM/dd/yy HH:mm") + return WorkBookHandle.simpledateformat.parse(s) + } + else -> { + WorkBookHandle.simpledateformat.applyPattern("dd/MM/yyyy") + return WorkBookHandle.simpledateformat.parse(s) + } } - } catch (Exception e) { + } catch (e: Exception) { Logger.logWarn("Failed to parse date " + s + " format pattern: " - + pat); - return Calendar.getInstance().getTime(); + + pat) + return Calendar.getInstance().time } + } /** * DATEVALUE * Returns the serial number of the date represented by date_text. Use DATEVALUE to convert a date represented by text to a serial number. - *

                  + * + * * Syntax * DATEVALUE(date_text) - *

                  + * + * * Date_text is text that represents a date in a Microsoft Excel date format. For example, "1/30/2008" or "30-Jan-2008" are text strings * within quotation marks that represent dates. Using the default date system in Excel for Windows, * date_text must represent a date from January 1, 1900, to December 31, 9999. Using the default date system in Excel for the Macintosh, * date_text must represent a date from January 1, 1904, to December 31, 9999. DATEVALUE returns the #VALUE! error value if date_text is out of this range. - *

                  + * + * * If the year portion of date_text is omitted, DATEVALUE uses the current year from your computer's built-in clock. Time information in date_text is ignored. - *

                  + * + * * Remarks - *

                  + * + * * Excel stores dates as sequential serial numbers so they can be used in calculations. By default, January 1, 1900 is serial number 1, and January 1, 2008 is serial number 39448 because it is 39,448 days after January 1, 1900. Excel for the Macintosh uses a different date system as its default. * Most functions automatically convert date values to serial numbers. * @@ -673,86 +636,62 @@ public static Date parseDate(String s, int pat) { * @return */ - public static Double calcDateValue(String dateString) { - String[] formats = {"MM/dd/yyyy HH:mm:ss", - "MM/dd/yy HH:mm:ss", - "MM/dd/yy", - "MM/dd/yyyy", - "MM/d/yyyy HH:mm:ss", - "MM/d/yy HH:mm:ss", - "yy-M-d hh:mm:ss a", - "yy-M-d HH:mm:ss", - "yy-M-d hh:mm a", - "yy-M-d HH:mm", - "dd-M-yyyy hh:mm:ss a", // 10 - "dd-M-yyyy HH:mm:ss", - "dd-M-yyyy hh:mm a", - "dd-M-yyyy HH:mm", - "dd-MMM-yy hh:mm:ss a", - "dd-MMM-yy HH:mm:ss", - "dd-MMM-yy hh:mm a", - "dd-MMM-yy HH:mm", - "yyMMdd", - "MM/d/yy", - "MM/d/yyyy", // 20 - "yyyy/MM/dd", - "d-MMM-yy", - "d-M-yy", - "d-M-yyyy", - "dd-MMM-yy", - "dd-MMM-yyyy", - "dd-M-yyyy", - "dd-MM-yy", - "dd-MM-yyyy", - "d-MMM-yyyy", // 30 - "d-M-yyyy", - "d-MMM-yyyy", // 32 really is d-MMM but inorder to match, must append year - "d/MMM/yyyy", // 33 really is d/MMM but in order to match, must append year - "M-yy", - "MMM-yy", - "M/d/yyyy", - "M d, yyyy", - "yyyy-MM-dd", - "yy-M-d", - "yyyy-MM-dd", - "MMyy", - "yyMM", - "yyyyMMddHHmm", - "yyyyMMddHHmmss", - "yyMMddHHmmss", - "MMDDHHmm", - "MMMM dd, yyyy", - "E, MMM d, yyyy", - "E MMM dd, yyyy", - "EE, MMM dd, yyyy", - "E, MMMM d, yyyy", - "E, MMMM dd, yyyy", - "EE, MMMM dd, yyyy", - "hh:mm:ss a", - "HH:mm:ss", - "hh:mm a", - "HH:mm", - }; - for (int i = 0; i < formats.length; i++) { + fun calcDateValue(dateString: String): Double? { + val formats = arrayOf("MM/dd/yyyy HH:mm:ss", "MM/dd/yy HH:mm:ss", "MM/dd/yy", "MM/dd/yyyy", "MM/d/yyyy HH:mm:ss", "MM/d/yy HH:mm:ss", "yy-M-d hh:mm:ss a", "yy-M-d HH:mm:ss", "yy-M-d hh:mm a", "yy-M-d HH:mm", "dd-M-yyyy hh:mm:ss a", // 10 + "dd-M-yyyy HH:mm:ss", "dd-M-yyyy hh:mm a", "dd-M-yyyy HH:mm", "dd-MMM-yy hh:mm:ss a", "dd-MMM-yy HH:mm:ss", "dd-MMM-yy hh:mm a", "dd-MMM-yy HH:mm", "yyMMdd", "MM/d/yy", "MM/d/yyyy", // 20 + "yyyy/MM/dd", "d-MMM-yy", "d-M-yy", "d-M-yyyy", "dd-MMM-yy", "dd-MMM-yyyy", "dd-M-yyyy", "dd-MM-yy", "dd-MM-yyyy", "d-MMM-yyyy", // 30 + "d-M-yyyy", "d-MMM-yyyy", // 32 really is d-MMM but inorder to match, must append year + "d/MMM/yyyy", // 33 really is d/MMM but in order to match, must append year + "M-yy", "MMM-yy", "M/d/yyyy", "M d, yyyy", "yyyy-MM-dd", "yy-M-d", "yyyy-MM-dd", "MMyy", "yyMM", "yyyyMMddHHmm", "yyyyMMddHHmmss", "yyMMddHHmmss", "MMDDHHmm", "MMMM dd, yyyy", "E, MMM d, yyyy", "E MMM dd, yyyy", "EE, MMM dd, yyyy", "E, MMMM d, yyyy", "E, MMMM dd, yyyy", "EE, MMMM dd, yyyy", "hh:mm:ss a", "HH:mm:ss", "hh:mm a", "HH:mm") + for (i in formats.indices) { try { - String ds = dateString; + var ds = dateString if (i == 32 || i == 33) { // then must add year to date - GregorianCalendar calendar = new GregorianCalendar(); - int curyear = calendar.get(Calendar.YEAR); - if (i == 32) ds = dateString + "-" + curyear; - else ds = dateString + "/" + curyear; + val calendar = GregorianCalendar() + val curyear = calendar.get(Calendar.YEAR) + if (i == 32) + ds = "$dateString-$curyear" + else + ds = "$dateString/$curyear" } //SimpleDateFormat format= new SimpleDateFormat(formats[i], Locale.ENGLISH); // 20090701 KSC: apparently need Locale -- why now though?? //format.setLenient(false); - WorkBookHandle.simpledateformat.applyLocalizedPattern(formats[i]); - WorkBookHandle.simpledateformat.setLenient(false); + WorkBookHandle.simpledateformat.applyLocalizedPattern(formats[i]) + WorkBookHandle.simpledateformat.isLenient = false //Date d= format.parse(ds); - Date d = WorkBookHandle.simpledateformat.parse(ds); - return DateConverter.getXLSDateVal(d); - } catch (Exception e) { + val d = WorkBookHandle.simpledateformat.parse(ds) + return DateConverter.getXLSDateVal(d) + } catch (e: Exception) { } + } - return null; + return null } -} \ No newline at end of file +} +/** + * Converts the value of the given Calendar to an Excel serial date. + * The date will be returned in the + * [legacy 1900][DateFormat.LEGACY_1900] format. + * + * @param cal the Calendar to convert + * @return the Excel serial date representing the given calendar's value in + * the calendar's time zone + */ +/** + * Converts the given Date to an Excel serial date in the default time zone. + * The date will be returned in the + * [legacy 1900][DateFormat.LEGACY_1900] format. + * + * @param date the date to be converted + * @return the Excel serial date value corresponding to the given date + */ +/** + * Parses the the given Excel serial date and returns a Calendar. + * The date will be interpreted in the + * [legacy 1900][DateFormat.LEGACY_1900] format. + * + * @param date the Excel serial date to be interpreted + * @return a Calendar representing the given Excel date interpreted in the + * default time zone + */ \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/Document.java b/src/main/java/io/starter/OpenXLS/Document.kt similarity index 77% rename from src/main/java/io/starter/OpenXLS/Document.java rename to src/main/java/io/starter/OpenXLS/Document.kt index ae5a459..498954c 100644 --- a/src/main/java/io/starter/OpenXLS/Document.java +++ b/src/main/java/io/starter/OpenXLS/Document.kt @@ -27,38 +27,26 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.OpenXLS; +package io.starter.OpenXLS -import io.starter.formats.XLS.XLSConstants; +import io.starter.formats.XLS.XLSConstants -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; +import java.io.File +import java.io.IOException +import java.io.OutputStream /** * An interface representing an OpenXLS document. * This interface provides no functionality beyond the abstract - * {@link DocumentHandle} class. Use that type instead. + * [DocumentHandle] class. Use that type instead. */ -public interface Document { - int DEBUG_LOW = XLSConstants.DEBUG_LOW; - int DEBUG_MEDIUM = XLSConstants.DEBUG_MEDIUM; - int DEBUG_HIGH = XLSConstants.DEBUG_HIGH; +interface Document { /** - * get a non-Excel property - * - * @return Returns the properties. - */ - Object getProperty(String name); - - /** - * add non-Excel property + * Returns the name of this WorkBook * - * @param properties The properties to set. + * @return String name of WorkBook */ - void addProperty(String name, Object val); - /** The Session for the WorkBook instance * * @return public BookSession getSession(); @@ -66,35 +54,45 @@ public interface Document { /** * Sets the internal name of this WorkBookHandle. - *

                  + * + * * Overrides the default for 'getName()' which returns * the file name source of this WorkBook by default. * * @param WorkBook Name */ - void setName(String nm); + var name: String /** - * Set the Debugging level. Higher values output more - * debugging info during execution. + * get a non-Excel property * - * @parameter int Debug level. higher=more verbose + * @return Returns the properties. */ - void setDebugLevel(int l); + fun getProperty(name: String): Any /** - * Returns the name of this WorkBook + * add non-Excel property * - * @return String name of WorkBook + * @param properties The properties to set. */ - String getName(); + fun addProperty(name: String, `val`: Any) + + /** + * Set the Debugging level. Higher values output more + * debugging info during execution. + * + * @parameter int Debug level. higher=more verbose + */ + fun setDebugLevel(l: Int) /** * Clears all values in a template WorkBook. - *

                  + * + * * Use this method to 'reset' the values of your * WorkBook in memory to defaults. - *

                  + * + * * For example, if you load a Servlet with a * single WorkBookHandle instance, then modify * values and stream to a Client system, yo @@ -102,7 +100,7 @@ public interface Document { * is completed to remove the modified values * and set them back to a default. */ - void reset(); + fun reset() /** * Writes the document to the given stream in the requested format. @@ -112,7 +110,8 @@ public interface Document { * @throws IllegalArgumentException if the given type code is invalid * @throws IOException if an error occurs while writing to the stream */ - void write(OutputStream dest, int format) throws IOException; + @Throws(IOException::class) + fun write(dest: OutputStream, format: Int) /** * Writes the document to the given stream in its native format. @@ -120,7 +119,8 @@ public interface Document { * @param dest the stream to which the document should be written * @throws IOException if an error occurs while writing to the stream */ - void write(OutputStream dest) throws IOException; + @Throws(IOException::class) + fun write(dest: OutputStream) /** * Writes the document to the given file in the requested format. @@ -130,7 +130,8 @@ public interface Document { * @throws IllegalArgumentException if the given type code is invalid * @throws IOException if an error occurs while writing to the file */ - void write(File file, int format) throws IOException; + @Throws(IOException::class) + fun write(file: File, format: Int) /** * Writes the document to the given file in its native format. @@ -138,5 +139,12 @@ public interface Document { * @param file the path to which the document should be written * @throws IOException if an error occurs while writing to the stream */ - void write(File file) throws IOException; + @Throws(IOException::class) + fun write(file: File) + + companion object { + val DEBUG_LOW = XLSConstants.DEBUG_LOW + val DEBUG_MEDIUM = XLSConstants.DEBUG_MEDIUM + val DEBUG_HIGH = XLSConstants.DEBUG_HIGH + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/DocumentHandle.java b/src/main/java/io/starter/OpenXLS/DocumentHandle.java deleted file mode 100644 index 0eca8fe..0000000 --- a/src/main/java/io/starter/OpenXLS/DocumentHandle.java +++ /dev/null @@ -1,442 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.LEO.LEOFile; -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.toolkit.Logger; -import io.starter.toolkit.TempFileManager; - -import java.io.*; -import java.net.URL; -import java.net.URLConnection; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Functionality common to all document types. - */ -public abstract class DocumentHandle implements Document, Handle, Closeable { - - public static String workingdir = System.getProperty("user.dir") - + "/tmp"; - - /** - * Format constant for the most appropriate format for this document. - * If the document was read in from a file, this is usually the format that - * was read in. - */ - public static final int FORMAT_NATIVE = 0; - - /** - * The level of debugging output requested by the user. - * Higher values should produce more output. - */ - // TODO: should the debug level be static? - protected int DEBUGLEVEL = 0; - - /** - * The user-visible display name or title of this document. - */ - protected String name = null; - - /** - * The file associated with this document. - * This will generally be the file the document was parsed from, if any. - */ - protected File file; - - /** - * Store for workbook properties. - */ - private Map props = new HashMap(); - - /** - * Handling for a streaming worksheet based workbook - **/ - private boolean streamingSheets = false; - - /** - * default constructor - */ - public DocumentHandle() { - super(); - - } - - /** - * Apr 5, 2011 - * - * @param urlx - */ - public DocumentHandle(InputStream urlx) { - super(); - // TODO Auto-generated constructor stub - Logger.logErr("DocumentHandle InputStream Constructor Not Implemented"); - } - - public DocumentHandle(File input) { - // TODO Auto-generated constructor stub - } - - /** - * Retrieves a property in the workbook property store. - * This is not an Excel-compatible feature. - * - * @param name the name of the property to retrieve - * @return the value of the requested property or null if it doesn't exist - */ - @Override - public Object getProperty(String name) { - return props.get(name); - } - - /** - * Sets the value of a property in the workbook property store. - * This is not an Excel-compatible feature. - * - * @param name the name of the property which should be updated - * @param value the value to which the property should be set - */ - @Override - public void addProperty(String name, Object val) { - props.put(name, val); - } - - /** - * Retrieves a Map containing the workbook properties store. - * This is not an Excel-compatible feature. - * - * @return an immutable Map containing the current workbook properties - */ - public Map getProperties() { - return Collections.unmodifiableMap(props); - } - - /** - * Replaces the workbook properties with the values in a given Map. - * This is not an Excel-compatible feature. - * - * @param properties the values that will replace the existing properties - */ - public void setProperties(Map properties) { - props = new HashMap(); - props.putAll(properties); - } - - /** - * Gets the OpenXLS version number. - */ - public static String getVersion() { - return GetInfo.getVersion(); - } - - /** - * Sets the user-visible descriptive name or title of this document. - * Some formats will persist this setting in the document itself. - */ - @Override - public void setName(String nm) { - name = nm; - } - - /** - * Handling for streaming sheets. Currently this is in development and unsupported - * - * @param streamSheets - */ - public void setStreamingSheets(boolean streamSheets) { - this.streamingSheets = streamSheets; - } - - /** - * Sets the file name associated with this document. - * - * @deprecated Use {@link #setFile(File)} instead. - */ - @Deprecated - public void setFileName(String name) { - file = new File(name).getAbsoluteFile(); - } - - /** - * Sets the file associated with this document. - */ - public void setFile(File file) { - this.file = file; - } - - /** - * Gets the file associated with this document. - * For documents read in from a file, this defaults to that file. If no - * file is associated with this document, for example if the document was - * parsed from a stream, this may return null. - */ - public File getFile() { - return file; - } - - /** - * Looks for magic numbers in the given input data and attempts to parse - * it with an appropriate DocumentHandle subclass. Detection - * is performed on a best-effort basis and is not guaranteed to be accurate. - * - * @throws IOException if an error occurs while reading from the stream - * @throws WorkBookException if parsing fails - */ - public static DocumentHandle getInstance(InputStream input) throws IOException { - BufferedInputStream bufferedStream = new BufferedInputStream(input); - // read in that start of the file for checking magic numbers - byte[] headerBytes; - int count; - // make sure the file is long enough to get magic numbers - bufferedStream.mark(1028); - headerBytes = new byte[512]; - count = bufferedStream.read(headerBytes); - bufferedStream.reset(); - - // if it starts with the LEO magic number check the header - if (LEOFile.checkIsLEO(headerBytes, count)) { - LEOFile leo = new LEOFile(bufferedStream); - - if (leo.hasWorkBook()) - return new WorkBookHandle(leo); - else - throw new WorkBookException( - "input is LEO but no supported format detected", -1); - } - - String headerString; - headerString = new String(headerBytes, 0, count, StandardCharsets.UTF_8); - - // if it's a ZIP archive, try parsing as OOXML - if (headerString.startsWith("PK")) { - return new WorkBookHandle(bufferedStream); - } - - if ((headerString.indexOf(",") > -1) - && (headerString.indexOf(",") > -1)) { - // init a blank workbook - WorkBookHandle book = new WorkBookHandle(); - - // map CSV into workbook - try { - WorkSheetHandle sheet = book.getWorkSheet(0); - sheet.readCSV(new BufferedReader( - new InputStreamReader(bufferedStream))); - return book; - } catch (Exception e) { - throw new WorkBookException( - "Error encountered importing CSV: " + e.toString(), - WorkBookException.ILLEGAL_INIT_ERROR); - } - } else { - throw new WorkBookException("unknown file format", -1); - } - } - - /** - * Gets the file name associated with this document. - * For documents read in from a file, this defaults to that file. If no - * file is associated with this document, for example if the document was - * parsed from a stream, this may return null. - * - * @deprecated Use {@link #getFile()} instead. - */ - @Deprecated - public String getFileName() { - return file != null ? file.getPath() : "New Document.doc"; - } - - /** - * Sets the debugging output level. - * Higher values will produce more output. Output at higher values will - * generally only be of use to OpenXLS developers. Increased output incurs - * a performance penalty, so it is recommended this be left at zero unless - * you are reporting a bug. - */ - @Override - public void setDebugLevel(int level) { - DEBUGLEVEL = level; - } - - public int getDebugLevel() { - return DEBUGLEVEL; - } - - /** - * Downloads the resource at the given URL to a temporary file. - * - * @param u the URL representing the resource to be downloaded - * @return the path to a temporary file containing the downloaded resource - * or null if an error occurred - * @deprecated The download should be handled outside OpenXLS. - * There is no specific replacement for this method. - */ - @Deprecated - protected static File getFileFromURL(URL u) { - try { - File fx = TempFileManager.createTempFile("upload-" - + System.currentTimeMillis(), ".tmp"); - - URLConnection uc = u.openConnection(); - String contentType = uc.getContentType(); - int contentLength = uc.getContentLength(); - if (contentType.startsWith("text/") || contentLength == -1) { - throw new IOException("This is not a binary file."); - } - InputStream raw = uc.getInputStream(); - InputStream in = new BufferedInputStream(raw); - byte[] data = new byte[contentLength]; - int bytesRead = 0; - int offset = 0; - while (offset < contentLength) { - bytesRead = in.read(data, offset, data.length - offset); - if (bytesRead == -1) - break; - offset += bytesRead; - } - in.close(); - - if (offset != contentLength) { - throw new IOException("Only read " + offset - + " bytes; Expected " + contentLength + " bytes"); - } - - // String filename = - // u.getFile().substring(filename.lastIndexOf('/') + 1); - FileOutputStream out = new FileOutputStream(fx); - out.write(data); - out.flush(); - out.close(); - return fx; - } catch (Exception e) { - Logger.logErr("Could not load WorkBook from URL: " + e.toString()); - return null; - } - } - - /** - * Gets the user-visible descriptive name or title of this document. - */ - @Override - public String getName() { - if (name != null) - return name; - else - return "Untitled Document"; - } - - /** - * Resets the document state to what it was when it was loaded. - * - * @throws UnsupportedOperationException if there is not sufficient data - * available to perform the reversion - */ - @Override - public abstract void reset(); - - /** - * Gets the constant representing this document's native format. - */ - public abstract int getFormat(); - - /** - * Gets the file name extension for this document's native format. - */ - public abstract String getFileExtension(); - - /** - * Writes the document to the given stream in the requested format. - * - * @param dest the stream to which the document should be written - * @param format the constant representing the desired output format - * @throws IllegalArgumentException if the given type code is invalid - * @throws IOException if an error occurs while writing to the stream - */ - @Override - public abstract void write(OutputStream dest, int format) throws IOException; - - /** - * Writes the document to the given stream in its native format. - * - * @param dest the stream to which the document should be written - * @throws IOException if an error occurs while writing to the stream - */ - @Override - public void write(OutputStream dest) throws IOException { - this.write(dest, FORMAT_NATIVE); - } - - /** - * Writes the document to the given file in the requested format. - * - * @param file the path to which the document should be written - * @param format the constant representing the desired output format - * @throws IllegalArgumentException if the given type code is invalid - * @throws IOException if an error occurs while writing to the file - */ - @Override - public void write(File file, int format) throws IOException { - if (format > WorkBookHandle.FORMAT_XLS && this.file != null) - OOXMLAdapter.refreshPassThroughFiles((WorkBookHandle) this); - - if (file.exists()) - file.delete(); // try this - OutputStream stream = new BufferedOutputStream( - new FileOutputStream(file)); - this.write(stream, format); - this.file = file; // necesary for OOXML re-write ... - stream.flush(); - stream.close(); - } - - /** - * Writes the document to the given file in its native format. - * - * @param file the path to which the document should be written - * @throws IOException if an error occurs while writing to the stream - */ - @Override - public void write(File file) throws IOException { - this.write(file, FORMAT_NATIVE); - } - - /** - * Returns a string representation of the object. - * This is currently equivalent to {@link #getName()}. - */ - @Override - public String toString() { - return getName(); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/DocumentHandle.kt b/src/main/java/io/starter/OpenXLS/DocumentHandle.kt new file mode 100644 index 0000000..063ccfb --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/DocumentHandle.kt @@ -0,0 +1,419 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.LEO.LEOFile +import io.starter.formats.XLS.OOXMLAdapter +import io.starter.toolkit.Logger +import io.starter.toolkit.TempFileManager + +import java.io.* +import java.net.URL +import java.net.URLConnection +import java.nio.charset.StandardCharsets +import java.util.Collections +import java.util.HashMap + +/** + * Functionality common to all document types. + */ +abstract class DocumentHandle : Document, Handle, Closeable { + + /** + * The level of debugging output requested by the user. + * Higher values should produce more output. + */ + // TODO: should the debug level be static? + protected var DEBUGLEVEL = 0 + + /** + * The user-visible display name or title of this document. + */ + protected var name: String? = null + + /** + * The file associated with this document. + * This will generally be the file the document was parsed from, if any. + */ + /** + * Gets the file associated with this document. + * For documents read in from a file, this defaults to that file. If no + * file is associated with this document, for example if the document was + * parsed from a stream, this may return `null`. + */ + /** + * Sets the file associated with this document. + */ + var file: File? = null + + /** + * Store for workbook properties. + */ + private var props: MutableMap = HashMap() + + /** + * Handling for a streaming worksheet based workbook + */ + private var streamingSheets = false + + /** + * Retrieves a Map containing the workbook properties store. + * This is not an Excel-compatible feature. + * + * @return an immutable Map containing the current workbook properties + */ + /** + * Replaces the workbook properties with the values in a given Map. + * This is not an Excel-compatible feature. + * + * @param properties the values that will replace the existing properties + */ + var properties: Map + get() = Collections.unmodifiableMap(props) + set(properties) { + props = HashMap() + props.putAll(properties) + } + + /** + * Gets the file name associated with this document. + * For documents read in from a file, this defaults to that file. If no + * file is associated with this document, for example if the document was + * parsed from a stream, this may return `null`. + * + */ + /** + * Sets the file name associated with this document. + * + */ + var fileName: String + @Deprecated("Use {@link #getFile()} instead.") + get() = if (file != null) file!!.path else "New Document.doc" + @Deprecated("Use {@link #setFile(File)} instead.") + set(name) { + file = File(name).absoluteFile + } + + /** + * Gets the constant representing this document's native format. + */ + abstract val format: Int + + /** + * Gets the file name extension for this document's native format. + */ + abstract val fileExtension: String + + /** + * default constructor + */ + constructor() : super() { + + } + + /** + * Apr 5, 2011 + * + * @param urlx + */ + constructor(urlx: InputStream) : super() { + // TODO Auto-generated constructor stub + Logger.logErr("DocumentHandle InputStream Constructor Not Implemented") + } + + constructor(input: File) { + // TODO Auto-generated constructor stub + } + + /** + * Retrieves a property in the workbook property store. + * This is not an Excel-compatible feature. + * + * @param name the name of the property to retrieve + * @return the value of the requested property or null if it doesn't exist + */ + override fun getProperty(name: String): Any { + return props[name] + } + + /** + * Sets the value of a property in the workbook property store. + * This is not an Excel-compatible feature. + * + * @param name the name of the property which should be updated + * @param value the value to which the property should be set + */ + override fun addProperty(name: String, `val`: Any) { + props[name] = `val` + } + + /** + * Sets the user-visible descriptive name or title of this document. + * Some formats will persist this setting in the document itself. + */ + override fun setName(nm: String) { + name = nm + } + + /** + * Handling for streaming sheets. Currently this is in development and unsupported + * + * @param streamSheets + */ + fun setStreamingSheets(streamSheets: Boolean) { + this.streamingSheets = streamSheets + } + + /** + * Sets the debugging output level. + * Higher values will produce more output. Output at higher values will + * generally only be of use to OpenXLS developers. Increased output incurs + * a performance penalty, so it is recommended this be left at zero unless + * you are reporting a bug. + */ + override fun setDebugLevel(level: Int) { + DEBUGLEVEL = level + } + + fun getDebugLevel(): Int { + return DEBUGLEVEL + } + + /** + * Gets the user-visible descriptive name or title of this document. + */ + override fun getName(): String { + return if (name != null) + name + else + "Untitled Document" + } + + /** + * Resets the document state to what it was when it was loaded. + * + * @throws UnsupportedOperationException if there is not sufficient data + * available to perform the reversion + */ + abstract override fun reset() + + /** + * Writes the document to the given stream in the requested format. + * + * @param dest the stream to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the stream + */ + @Throws(IOException::class) + abstract override fun write(dest: OutputStream, format: Int) + + /** + * Writes the document to the given stream in its native format. + * + * @param dest the stream to which the document should be written + * @throws IOException if an error occurs while writing to the stream + */ + @Throws(IOException::class) + override fun write(dest: OutputStream) { + this.write(dest, FORMAT_NATIVE) + } + + /** + * Writes the document to the given file in the requested format. + * + * @param file the path to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the file + */ + @Throws(IOException::class) + override fun write(file: File, format: Int) { + if (format > WorkBookHandle.FORMAT_XLS && this.file != null) + OOXMLAdapter.refreshPassThroughFiles(this as WorkBookHandle) + + if (file.exists()) + file.delete() // try this + val stream = BufferedOutputStream( + FileOutputStream(file)) + this.write(stream, format) + this.file = file // necesary for OOXML re-write ... + stream.flush() + stream.close() + } + + /** + * Writes the document to the given file in its native format. + * + * @param file the path to which the document should be written + * @throws IOException if an error occurs while writing to the stream + */ + @Throws(IOException::class) + override fun write(file: File) { + this.write(file, FORMAT_NATIVE) + } + + /** + * Returns a string representation of the object. + * This is currently equivalent to [.getName]. + */ + override fun toString(): String { + return getName() + } + + companion object { + + var workingdir = System.getProperty("user.dir") + "/tmp" + + /** + * Format constant for the most appropriate format for this document. + * If the document was read in from a file, this is usually the format that + * was read in. + */ + val FORMAT_NATIVE = 0 + + /** + * Gets the OpenXLS version number. + */ + val version: String + get() = GetInfo.getVersion() + + /** + * Looks for magic numbers in the given input data and attempts to parse + * it with an appropriate `DocumentHandle` subclass. Detection + * is performed on a best-effort basis and is not guaranteed to be accurate. + * + * @throws IOException if an error occurs while reading from the stream + * @throws WorkBookException if parsing fails + */ + @Throws(IOException::class) + fun getInstance(input: InputStream): DocumentHandle { + val bufferedStream = BufferedInputStream(input) + // read in that start of the file for checking magic numbers + val headerBytes: ByteArray + val count: Int + // make sure the file is long enough to get magic numbers + bufferedStream.mark(1028) + headerBytes = ByteArray(512) + count = bufferedStream.read(headerBytes) + bufferedStream.reset() + + // if it starts with the LEO magic number check the header + if (LEOFile.checkIsLEO(headerBytes, count)) { + val leo = LEOFile(bufferedStream) + + return if (leo.hasWorkBook()) + WorkBookHandle(leo) + else + throw WorkBookException( + "input is LEO but no supported format detected", -1) + } + + val headerString: String + headerString = String(headerBytes, 0, count, StandardCharsets.UTF_8) + + // if it's a ZIP archive, try parsing as OOXML + if (headerString.startsWith("PK")) { + return WorkBookHandle(bufferedStream) + } + + if (headerString.indexOf(",") > -1 && headerString.indexOf(",") > -1) { + // init a blank workbook + val book = WorkBookHandle() + + // map CSV into workbook + try { + val sheet = book.getWorkSheet(0) + sheet.readCSV(BufferedReader( + InputStreamReader(bufferedStream))) + return book + } catch (e: Exception) { + throw WorkBookException( + "Error encountered importing CSV: $e", + WorkBookException.ILLEGAL_INIT_ERROR) + } + + } else { + throw WorkBookException("unknown file format", -1) + } + } + + /** + * Downloads the resource at the given URL to a temporary file. + * + * @param u the URL representing the resource to be downloaded + * @return the path to a temporary file containing the downloaded resource + * or `null` if an error occurred + */ + @Deprecated("The download should be handled outside OpenXLS.\n" + + " There is no specific replacement for this method.") + protected fun getFileFromURL(u: URL): File? { + try { + val fx = TempFileManager.createTempFile("upload-" + System.currentTimeMillis(), ".tmp") + + val uc = u.openConnection() + val contentType = uc.contentType + val contentLength = uc.contentLength + if (contentType.startsWith("text/") || contentLength == -1) { + throw IOException("This is not a binary file.") + } + val raw = uc.getInputStream() + val `in` = BufferedInputStream(raw) + val data = ByteArray(contentLength) + var bytesRead = 0 + var offset = 0 + while (offset < contentLength) { + bytesRead = `in`.read(data, offset, data.size - offset) + if (bytesRead == -1) + break + offset += bytesRead + } + `in`.close() + + if (offset != contentLength) { + throw IOException("Only read " + offset + + " bytes; Expected " + contentLength + " bytes") + } + + // String filename = + // u.getFile().substring(filename.lastIndexOf('/') + 1); + val out = FileOutputStream(fx) + out.write(data) + out.flush() + out.close() + return fx + } catch (e: Exception) { + Logger.logErr("Could not load WorkBook from URL: $e") + return null + } + + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/RrdHead.java b/src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.kt similarity index 79% rename from src/main/java/io/starter/formats/XLS/RrdHead.java rename to src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.kt index f49b4d3..1ed7d92 100644 --- a/src/main/java/io/starter/formats/XLS/RrdHead.java +++ b/src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.kt @@ -20,13 +20,13 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.OpenXLS -public class RrdHead extends UnencryptedXLSRecord { +class DocumentObjectNotFoundException(string: String) : Exception(string) { + companion object { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6986702491495765959L; + + private val serialVersionUID = 6605511680058750453L + } } diff --git a/src/main/java/io/starter/OpenXLS/ExcelTools.java b/src/main/java/io/starter/OpenXLS/ExcelTools.java deleted file mode 100644 index c742c8c..0000000 --- a/src/main/java/io/starter/OpenXLS/ExcelTools.java +++ /dev/null @@ -1,1166 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.toolkit.CompatibleBigDecimal; -import io.starter.toolkit.Logger; -import io.starter.toolkit.ResourceLoader; -import io.starter.toolkit.StringTool; - -import java.util.ArrayList; -import java.util.Date; -import java.util.IllegalFormatConversionException; -import java.util.StringTokenizer; - -//import java.text.SimpleDateFormat; - - -/** - * OpenXLS helper methods.
                  - * Contains helpful methods to ease use of the OpenXLS toolkit.
                  - *

                  - * "http://starter.io">Starter Inc. - * - * @see ByteTools - */ - -public class ExcelTools implements java.io.Serializable { - - private static final long serialVersionUID = 7622857355626065370L; - - /** - * Formats a double in the standard OpenXLS (General) format. Up to - * 99999999999 is expressed in standard notation. Above that is formatted in - * scientific notation - *

                  - * In addition, Excel precision of 9 digits is maintained - *

                  - *

                  - * returns a number formatted in Excel's General format, (assuming a wide - * enough column width - see below) example: - * formatNumericNotation(1234567890123) returns "1.23457E+12" - *

                  - * Information on NOTATION_STANDARD_EXCEL (i.e. Excel's General Format): // - * Excel will show as many decimal places that the text item has room for, - * it won't use a thousands separator, and if the // number can't fit, Excel - * uses a scientific number format. // RULES: // 1- Assuming the column is - * wide enough numbers will only be displayed in the scientific format when - * they contain more than 10 digits. // 2- If you enter a number into a cell - * and thre is not enough room to display all the digits // then the number - * will either be displayed in scientific format or will not be displayed at - * all, meaning that ##### will appear. // The exact precision of the - * scientific format will depend on the width of the actual cell. - * - * @param fpnum - * @return String formatted number - */ - public static String getNumberAsString(double fpnum) { - // Ensure precision and number of digits ala Excel - // double issues - use BigDecimal - java.math.BigDecimal bd = new java.math.BigDecimal(fpnum); - int scale = bd.scale(); - if ((Math.abs(fpnum) > 0.000000001) && scale > 9) { - bd = bd.setScale(9, java.math.RoundingMode.HALF_UP); - } else if (scale > 9) - bd = new java.math.BigDecimal(fpnum, new java.math.MathContext(5, java.math.RoundingMode.HALF_UP)); - bd = bd.stripTrailingZeros(); - String s = bd.toPlainString(); - int len = s.length(); - // If larger than 11 characters, truncate string - if (len > 11 && fpnum > 0 || len > 12) { // must deal with exponents and such as well - if (scale == 0) { - s = new java.math.BigDecimal(bd.toString(), new java.math.MathContext(6, java.math.RoundingMode.HALF_UP)).toString(); - } else if (bd.toString().indexOf("E") == -1) { - s = new java.math.BigDecimal(bd.toString(), new java.math.MathContext(10, java.math.RoundingMode.HALF_UP)).toString(); - while (s.length() > 0 && s.charAt(s.length() - 1) == '0') - s = s.substring(0, s.length() - 1); - if (s.endsWith(".")) - s = s.substring(0, s.length() - 1); - } else { // 5 + E+XX + sign - s = new java.math.BigDecimal(bd.toString(), new java.math.MathContext(5, java.math.RoundingMode.HALF_UP)).toString(); - } - } - return s; - } - - /** - * static version of getFormattedStringVal; given an object value, a - * valid Excel format pattern, return the formatted string value. - * - * @param Object o - * @param String pattern if General or "" returns string value - * @param boolean isInteger if General pattern, attempt to use integer value (rather than double) - */ - public static String getFormattedStringVal(Object o, String pattern/*, boolean isInteger*/) { - if (o == null) o = ""; - - boolean isInteger = false; - isInteger = o instanceof Integer || (o instanceof Double && ((Double) o).intValue() == ((Double) o).doubleValue()); - - if (pattern == null || pattern.equals("") || pattern.equalsIgnoreCase("GENERAL")) { - if (isInteger) - return String.valueOf(Double.valueOf(o.toString()).intValue()); - else { // general double numbers have default precision ... - try { - double d = new Double(o.toString()); - return ExcelTools.getNumberAsString(Double.valueOf(o.toString())); // handles default precision - } catch (NumberFormatException e) { - } - return o.toString(); - } - } else if (pattern.equals("000-00-0000")) { // special case for SSN format ... sigh ... - try { - new Double(o.toString()); // if it can't be converted to a number, return original string (tis what excel does) - String s = o.toString(); - while (s.length() < 9) // tis what excel does ... - s = '0' + s; - return s.substring(0, 3) + "-" + s.substring(3, 5) + "-" + s.substring(5); - } catch (Exception e) { - return o.toString(); - } - } - - - /** try to determine if the format is numeric (+currency) or date */ - boolean isNumeric = false, isDate = false, isString = false; - - /** excel formats can have up to 4 parts: ;;; */ - String[] pats = pattern.split(";"); // assign the correct pattern according to double or string value - - String tester = StringTool.convertPatternExtractBracketedExpression(pats[0]); - if (tester.matches(".*(((y{1,4}|m{1,5}|d{1,4}|h{1,2}|s{1,2}).*)+).*")) { - isDate = true; - pats[0] = tester; // ignore locale and other info for dates ... - } - if (!isDate) { - int idx = pats.length - 1; // default with string - try { - double d = new Double(o.toString()); - isNumeric = true; - if (d > 0) // 1st expression is for + numbers - idx = 0; - else if (pats.length > 1 && d < 0) // 2nd is for - numbers - idx = 1; - else if (pats.length > 2 && d == 0) // 3rd for 0 - idx = 2; - pattern = StringTool.convertPatternFromExcelToStringFormatter(pats[idx], d < 0); // get correct format for String.format formatter - } catch (NumberFormatException e) { // 4th for text (non-numeric) - if (pats.length > 3) - idx = 3; - isString = true; - pattern = StringTool.convertPatternFromExcelToStringFormatter(pats[idx], false); // get correct format for String.format formatter - } - } else { - pattern = pats[0]; - pattern = StringTool.convertDatePatternFromExcelToStringFormatter(pattern); // get correct format for SimpleDateFormat - } - - - if (isString) { // use string portion of format, if any - try { - return String.format(pattern, o); - } catch (IllegalFormatConversionException e) { - return o.toString(); - } - } - if (isNumeric) { - try { - double d = new Double(o.toString()); - if (!Double.isNaN(d)) { - d = Math.abs(d); // negative number intricacies have been handled in convertPattern method - // ugly, but has to be done ... - if (pattern.indexOf("%%") != -1) // convert to percent - d *= 100; - // special case of "@" -- integers converted to doubles format incorrectly ... - if (pattern.equals("%s")) { - return o.toString(); - } - return String.format(pattern, d); - } - } catch (Exception e) { - } - return o.toString(); - } - if (isDate) { - try { - WorkBookHandle.simpledateformat.applyPattern(pattern); - } catch (Exception ex) { - return o.toString(); - } - try { - return WorkBookHandle.simpledateformat.format(DateConverter.getCalendarFromNumber(o).getTime()); -/*// KSC: TESTING -Date d= DateConverter.getCalendarFromNumber(o).getTime(); -return WorkBookHandle.simpledateformat.format(d);*/ - } catch (NumberFormatException e) { - try { - return WorkBookHandle.simpledateformat.format(new Date(o.toString()).getTime()); - } catch (IllegalArgumentException i) { - if (o instanceof Number) - Logger.logWarn("Unable to format date in " + pattern); - } - } catch (IllegalArgumentException e) { - if (o instanceof Number) - Logger.logWarn("Unable to format date in " + pattern); - } - } - // otherwise - return o.toString(); - } - - - /** - * A FAIL FAST implementation for finding whether a cell string address - * falls within a set of row/col range coordinates. - *

                  - * Sep 21, 2010 - * - * @param rng the range you want to test - * @param rowFirst in the target range - * @param rowLast in the target range - * @param colFirst in the target range - * @param colLast in the target range - * @return - */ - public static boolean isInRange(String rng, int rowFirst, int rowLast, - int colFirst, int colLast) { - int[] sh = io.starter.OpenXLS.ExcelTools.getRowColFromString(rng); - - // the guantlet - if (sh[1] < colFirst) - return false; - if (sh[1] > colLast) - return false; - if (sh[0] < rowFirst) - return false; - return sh[0] <= rowLast;// passes! - - } - - /** - * returns true if range intersects with range2 - * - * @param rng - * @param rc - * @return - */ - public static boolean intersects(String rng, int[] rc) { - int[] rc2 = ExcelTools.getRangeCoords(rng); - return (rc[0] >= rc2[0]) && (rc[2] <= rc2[2]) && (rc[1] >= rc2[1]) - && (rc[3] <= rc2[3]); - } - - /** - * returns true if address is before the range coordinates defined by rc - * - * @param rc row col of address - * @param rng int[] coordinates as: row0, col0, row1, col1 - * @return true if address is before the range coordinates - */ - public static boolean isBeforeRange(int[] rc, int[] rng) { - return rc[0] < rng[0] || (rc[0] == rng[0] && rc[1] < rng[1]); - } - - /** - * returns true if address is before the range coordinates defined by rc - * - * @param rc row col of address - * @param rng int[] coordinates as: row0, col0, row1, col1 - * @return true if address is before the range coordinates - */ - public static boolean isAfterRange(int[] rc, int[] rng) { - return rc[0] > rng[2] || (rc[0] == rng[2] && rc[1] > rng[3]); - } - - /** - * Takes an input Object and attempts to convert to numeric Objects of the - * highest precision possible. - *

                  - * This method is useful for avoiding the Excel warnings - * "Number Stored As Text" when storing string data that contains numbers. - *

                  - * NOTE: this method is useful for ensuring that Formula references contain - * true numeric values as not all String numbers are properly interpreted in - * Formula engines, and can silently fail. - *

                  - * For this reason, always use numeric, non-string values to calculated - * cells. - * - * @param input - * @return - */ - public static Object getObject(Object in) { - // do not record -- only called from other methods - if (!(in instanceof String)) { - return in; - } - - String input = String.valueOf(in); - Object ret = input; // default is the original string - - try { - ret = new Double(input); - return ret; - } catch (NumberFormatException ex) { - try { - ret = new Float(input); - return ret; - } catch (NumberFormatException ex2) { - try { - ret = Integer.valueOf(input); - return ret; - } catch (NumberFormatException ex3) { - // ret Is set outside of loop incase no match is ever found. - } - } - } - - // list of formatting chars to check for - String[][] fmtlist = {{"$", ","}, {",", ","}, {"%", ","}}; - - // strip the formatting - for (int t = 0; t < fmtlist.length; t++) { - if (input.indexOf(fmtlist[t][0]) > -1) { // contains! - String converted = StringTool.replaceText(input, fmtlist[t][0], - ""); // strip first token (ie: '$') - converted = StringTool - .replaceText(converted, fmtlist[t][1], ""); // strip - // second - // token - // (ie: ',') - try { - ret = new Double(converted); - return ret; - } catch (NumberFormatException ex) { - try { - ret = new Float(converted); - return ret; - } catch (NumberFormatException ex2) { - try { - ret = Integer.valueOf(converted); - return ret; - } catch (NumberFormatException ex3) { - // ret Is set outside of loop incase no match is - // ever found. - } - } - } - } - - } - - return ret; - } - - /** - * convert twips to pixels - *

                  - *

                  - * In addition to a calculated size unit derived from the average size of - * the default characters 0-9, Excel uses the 'twips' measurement which is - * defined as: - *

                  - * 1 twip = 1/20 point or 20 twips = 1 point 1 twip = 1/567 centimeter or - * 567 twips = 1 centimeter 1 twip = 1/1440 inch or 1440 twips = 1 inch - *

                  - *

                  - * 1 pixel = 0.75 points 1 pixel * 1.3333 = 1 point 1 twip * 20 = 1 point - * - * @param pixels - * @return twips - */ - public static final float getPixels(float twips) { - float points = twips / 20; - float pixels = points * 1.3333333f; // good enuff precision - return pixels; - } - - /** - * convert pixels to twips - *

                  - *

                  - * In addition to a calculated size unit derived from the average size of - * the default characters 0-9, Excel uses the 'twips' measurement which is - * defined as: - *

                  - * 1 pixel = 0.75 points 1 pixel * 1.3333 = 1 point 1 twip * 20 = 1 point - * - * @param pixels - * @return twips - */ - public static final float getTwips(float pixels) { - float points = pixels * .75f; - float twips = points * 20; - return twips; - } - - /** - * get recordy byte def as a String - *

                  - * public static String getRecordByteDef(XLSRecord rec){ byte[] b = - * rec.read(); StringBuffer sb = new StringBuffer("byte[] rbytes = {"); - * for(int t = 0;t - * Byte thisb = new Byte(b[t]); - *

                  - * sb.append(thisb.toString() + ", "); } sb.append("};"); return - * sb.toString(); } - */ - - public static String getLogDate() { - return String.valueOf(new Date(System.currentTimeMillis())); - } - - /** - * tracks minimal info container for counters -> start time, last time, - * start mem, last mem - * - * @param info - * @param perfobj - */ - public static void benchmark(String info, Object perfobj) { - Runtime rt = Runtime.getRuntime(); - long[] p = null; - long lasttime = 0l, lastmem = 0l; - if (System.getProperties().get(perfobj.toString()) != null) { - p = (long[]) System.getProperties().get(perfobj.toString()); - lasttime = p[1]; - lastmem = p[3]; - p[1] = System.currentTimeMillis(); - p[3] = rt.freeMemory(); - double elapsedsec = p[1] - lasttime; - double usedmem = lastmem - p[3]; // - lastmem; - if (usedmem < 0) - usedmem *= -1; - Logger.logInfo(getLogDate() + " " + info); - Logger.logInfo(" time: " + elapsedsec + " millis"); - Logger.logInfo(" mem: " + usedmem + " bytes."); - } else { - p = new long[4]; - p[0] = System.currentTimeMillis(); - p[1] = System.currentTimeMillis(); - p[2] = rt.freeMemory(); - p[3] = rt.freeMemory(); - lasttime = p[1]; - lastmem = p[3]; - System.getProperties().put(perfobj.toString(), p); - } - - } - - /** - * get the bytes from a Vector of objects public byte[] - * getBytesFrom(CompatibleVector objs){ - *

                  - * for(int t = 0;t - * } - *

                  - *

                  - * } - */ - - static char[] alpharr = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', - 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', - 'X', 'Y', 'Z'}; - /* - * NOT USED ANYMORE -- see getAlphaVal -- OK to remove?? - */ - public static final String[] ALPHASDELETE = {"A", "B", "C", "D", "E", "F", - "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", - "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB", "AC", "AD", "AE", - "AF", "AG", "AH", "AI", "AJ", "AK", "AL", "AM", "AN", "AO", "AP", - "AQ", "AR", "AS", "AT", "AU", "AV", "AW", "AX", "AY", "AZ", "BA", - "BB", "BC", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BK", "BL", - "BM", "BN", "BO", "BP", "BQ", "BR", "BS", "BT", "BU", "BV", "BW", - "BX", "BY", "BZ", "CA", "CB", "CC", "CD", "CE", "CF", "CG", "CH", - "CI", "CJ", "CK", "CL", "CM", "CN", "CO", "CP", "CQ", "CR", "CS", - "CT", "CU", "CV", "CW", "CX", "CY", "CZ", "DA", "DB", "DC", "DD", - "DE", "DF", "DG", "DH", "DI", "DJ", "DK", "DL", "DM", "DN", "DO", - "DP", "DQ", "DR", "DS", "DT", "DU", "DV", "DW", "DX", "DY", "DZ", - "EA", "EB", "EC", "ED", "EE", "EF", "EG", "EH", "EI", "EJ", "EK", - "EL", "EM", "EN", "EO", "EP", "EQ", "ER", "ES", "ET", "EU", "EV", - "EW", "EX", "EY", "EZ", "FA", "FB", "FC", "FD", "FE", "FF", "FG", - "FH", "FI", "FJ", "FK", "FL", "FM", "FN", "FO", "FP", "FQ", "FR", - "FS", "FT", "FU", "FV", "FW", "FX", "FY", "FZ", "GA", "GB", "GC", - "GD", "GE", "GF", "GG", "GH", "GI", "GJ", "GK", "GL", "GM", "GN", - "GO", "GP", "GQ", "GR", "GS", "GT", "GU", "GV", "GW", "GX", "GY", - "GZ", "HA", "HB", "HC", "HD", "HE", "HF", "HG", "HH", "HI", "HJ", - "HK", "HL", "HM", "HN", "HO", "HP", "HQ", "HR", "HS", "HT", "HU", - "HV", "HW", "HX", "HY", "HZ", "IA", "IB", "IC", "ID", "IE", "IF", - "IG", "IH", "II", "IJ", "IK", "IL", "IM", "IN", "IO", "IP", "IQ", - "IR", "IS", "IT", "IU", "IV", "IW", "IX", "IY", "IZ",}; - - /** - * get the Excel-style Column alphabetical representation of an integer - * (0-based). - *

                  - * for example: 0 = A 26= AA 701= ZZ 702= AAA 16383= XFD (max) - */ - public static String getAlphaVal(int i) { - String ret = ""; - int leftover = 0; - if (i > 701) { // has 3rd digit - int z = (i / 676) - 1; // -1 to account for 0-based - if ((i % 676) < 26) { // then "leftover" is actually 2nd digit - z--; - leftover = 676; - } - ret = String.valueOf(ExcelTools.alpharr[z]); - i = i % 676; - i += leftover; - } - if (i > 25) { // has 2nd digit - int z = (i / 26) - 1; // -1 to account for 0-based - ret = ret + ExcelTools.alpharr[z]; - i = i % 26; - } - - // bbennett: this raises AIOOB if i = -1. In this situation we don't - // care about alpha because it will just be in the message of the - // exception that flags cell as non existent. - ret += i < 0 ? Integer.toString(i) : String.valueOf(ExcelTools.alpharr[(i)]); - - return ret; - } - - /** - * get the int value of the Excel-style Column alpha representation. - * - * @param String column name - * @return int the 0-based column number - */ - public static int getIntVal(String c) { - c = c.toUpperCase(); - if (c.length() > 3) // max col value= XFD in Excel 2007 - return -1; - int i = c.length() - 1; - int ret = 0; - while (i >= 0) { // process least to most-sigificant dig - int z = 0; - char cc = c.charAt(i); - while (z < alpharr.length && cc != alpharr[z++]) - ; - z *= Math.pow(26, c.length() - i - 1); // 1-based col for computing - ret += z; - i--; - } - // make 0-based - ret--; - return ret; - } - - /** - * Parses an Excel cell address into row and column integers. - * - * @param address the address to parse, either A1 or R1C1 - * @return int[2]: [0] row index, [1] column index - * @throws IllegalArgumentException if the argument is not a valid address - */ - public static int[] getRowColFromString(String address) { - - if (address.indexOf("$") > -1) { - address = StringTool.strip(address, "$"); - } - if (address.indexOf("!") > -1) { - address = address.substring(address.indexOf("!") + 1); - } - if (address.indexOf(":") > -1) - return getRangeRowCol(address); - - char[] adrchars = address.toCharArray(); - int row = 0, col = 0; - int charpos = -1, numpos = -1; - boolean r1c1 = false; - for (int i = 0; i < adrchars.length; i++) { - if (Character.isDigit(adrchars[i])) { - if (numpos == -1) // its a number - numpos = i; - } else if (charpos == -1) { - charpos = i; - if (numpos >= 0) { // we have already set number and we now have - // a nondigit - R1C1 style - r1c1 = true; - // break, it's all over! - break; - } - } - } - - if (r1c1) { // it's a single cell ref - try { - // there's an R and a C, not adjacent - if (address.toUpperCase().indexOf("R") == 0) { // startwith R - String rx = address.substring(1, address.toUpperCase() - .indexOf("C")); - String cx = address.substring(address.toUpperCase() - .indexOf("C") + 1); - row = Integer.parseInt(rx); - col = Integer.parseInt(cx); - } - } catch (NumberFormatException e) { - throw new IllegalArgumentException("illegal R1C1 address '" - + address + "'"); - } - } else { - row = 0; // -1 below - col = -1; - if (charpos == 0 && numpos > 0) { - String colval = address.substring(0, numpos); - col = getIntVal(colval); - if (col < 0) - throw new IllegalArgumentException("illegal column value '" - + colval + "' in address '" + address + "'"); - } - if (numpos >= 0) { - row = Integer.parseInt(address.substring(numpos)); - if (row < 1) - throw new IllegalArgumentException( - "row may not be negative in address '" + address - + "'"); - } else { // it's a wholecol ref - col = getIntVal(address); - if (col < 0) - throw new IllegalArgumentException("illegal column value '" - + address + "' in address '" + address + "'"); - } - } - - int[] ret = {row - 1, col}; - return ret; - } - - /** - * Parses an Excel cell range and returns the addresses as an int array. The - * range may not be qualified with sheet names. Strip them with - * {@link #stripSheetNameFromRange} before calling this method. If the - * argument is a single cell address it will be returned for both bounds. - * - * @param range the range to parse - * @return int[4]: [0] first row, [1] first column, [2] second row, [3] - * second column - * @throws IllegalArgumentException if the addresses are invalid - */ - public static int[] getRangeRowCol(String range) { - int colon = range.indexOf(":"); - - String firstloc; - String lastloc; - - if (colon > -1) { - firstloc = range.substring(0, colon); - lastloc = range.substring(colon + 1); - } else { - firstloc = range; - lastloc = range; - } - - int[] result = new int[4]; - int[] temp; - - temp = getRowColFromString(firstloc); - System.arraycopy(temp, 0, result, 0, 2); - - temp = getRowColFromString(lastloc); - System.arraycopy(temp, 0, result, 2, 2); - - return result; - } - - /** - * Takes an int array representing a row and column and formats it as a cell - * address. - *

                  - * The index is zero-based. - *

                  - * [0][0] is "A1" [1][1] is "B2" [2][2] is "C3" - * - * @param int[] the numeric range to convert - * @return String the string representation of the range - */ - public static String formatLocation(int[] rowCol) { - StringBuffer sb = new StringBuffer(getAlphaVal(rowCol[1])); - sb.append((rowCol[0] + 1)); - - // handle ranges - if (rowCol.length > 3) { - // 20090807: KSC: only a range if 1st is != 2nd cell :) - if (rowCol[0] == rowCol[2] && rowCol[1] == rowCol[3]) // it's a single address - return sb.toString(); - sb.append(":"); - sb.append(getAlphaVal(rowCol[3])); - sb.append((rowCol[2] + 1)); - } - - return sb.toString(); - } - - /** - * Takes an int array representing a row and column and formats it as a cell - * address, taking into account relative or absolute refs - *

                  - * The index is zero-based. - *

                  - * [0][0] is "A1", $A1, A$1 or $A$1 depending upon bRelRow or bRelCol [1][1] - * is "B2", $B1, B$1 or B$1 depending upon bRelRow or bRelCol [2][2] is - * "C3", $C1, C$1 or $C$1 depending upon bRelRow or bRelCol - * - * @param int[] the numeric range to convert - * @param bRelRow if true, no "$"s are added, relative row reference - * @param bRelCol if true, no "$"s are added, relative col reference - * @return String the string representation of the range - */ - public static String formatLocation(int[] s, boolean bRelRow, - boolean bRelCol) { - StringBuffer sb = new StringBuffer((bRelCol ? "" : "$")); - if (s[1] > -1) // account for WholeRow/WholeCol references - sb.append(getAlphaVal(s[1])); - - if (s[0] > -1) // account for WholeRow/WholeCol references - sb.append((bRelRow ? "" : "$") + (s[0] + 1)); - // 20090906 KSC: handle ranges - if (s.length > 3) { - if (s[0] == s[2] && s[1] == s[3]) // it's a single address - return sb.toString(); - sb.append(":"); - sb.append((bRelCol ? "" : "$")); - sb.append(getAlphaVal(s[3])); - sb.append((bRelRow ? "" : "$") + (s[2] + 1)); - } - - return sb.toString(); - } - - /** - * Takes an array of four shorts and formats it as a cell range. - *

                  - * IE [0][3][1][4] would be "A2:B3" - * - * @param int[] the numeric range to convert - * @return String the string representation of the range - */ - public static String formatRange(int[] s) { - if (s.length != 4) - return "incorrect array size in ExcelTools.formatLocation"; - int[] temp = new int[2]; - temp[0] = s[1]; - temp[1] = s[0]; - String firstcell = formatLocation(temp); - temp[0] = s[3]; - temp[1] = s[2]; - String lastcell = formatLocation(temp); - return firstcell + ":" + lastcell; - } - - /** - * format a range as a string, range in format of [r][c][r1][c1] - * - * @param s - * @return String representation of the integers as a range, ie A1:B4 - */ - public static String formatRangeRowCol(int[] s) { - if (s.length != 4) - return "incorrect array size in ExcelTools.formatLocation"; - int[] temp = new int[2]; - temp[0] = s[0]; - temp[1] = s[1]; - String firstcell = formatLocation(temp); - temp[0] = s[2]; - temp[1] = s[3]; - String lastcell = formatLocation(temp); - return firstcell + ":" + lastcell; - } - - /** - * format a range as a string, range in format of [r][c][r1][c1] including - * relative address state - * - * @param s - * @param bRelAddresses contains relative row and col state for each rcr1c1 - * @return String representation of the integers as a range, ie A1:B4 - */ - public static String formatRangeRowCol(int[] s, boolean[] bRelAddresses) { - if (s.length != 4) - return "incorrect array size in ExcelTools.formatLocation"; - int[] temp = new int[2]; - temp[0] = s[0]; - temp[1] = s[1]; - String firstcell = formatLocation(temp, bRelAddresses[0], - bRelAddresses[1]); - temp[0] = s[2]; - temp[1] = s[3]; - String lastcell = formatLocation(temp, bRelAddresses[2], - bRelAddresses[3]); - // unfortunately, no formatLocation can do this. This is - // formattingRANGERowCol - // if (firstcell.equals(lastcell)) return firstcell; // 20090309 KSC: - return firstcell + ":" + lastcell; - } - - /** - * Transforms a string to an array of ints for evaluation purposes. For - * example, acdc == [0][2][3][2] - */ - public static int[] transformStringToIntVals(String trans) { - int[] intarr = new int[trans.length()]; - for (int i = 0; i < trans.length(); i++) { - char c = trans.charAt(i); - for (int x = 0; x < alpharr.length; x++) { - if (String.valueOf(c).equalsIgnoreCase( - String.valueOf(alpharr[x]))) { - intarr[i] = x; - } - } - } - return intarr; - } - - /** - * Formats a string representation of a numeric value as a string in the - * specified notation: - * - * @param int
                  - * NOTATION_STANDARD = 0,
                  - * NOTATION_SCIENTIFIC = 1,
                  - * NOTATION_SCIENTIFIC_EXCEL = 2,
                  - * EXTENXLS_NOTATION = 3 - *

                  - *

                  - * example: formatNumericNotation(1.23456E5, 0) returns a "123456" - * example: formatNumericNotation(123456, 1) returns "1.23456E5" - * example: formatNumericNotation(123456, 2) returns "1.23456E+5" - * example: formatNumericNotation(123456, 3) returns "1.23456E+5" - */ - public static String formatNumericNotation(String num, int notationType) { - // if (notationType > 2)return null; - boolean negative = false; - if (num.substring(0, 1).equals("-")) { - negative = true; - num = num.substring(1); - } - String preString, postString, fullString = ""; - switch (notationType) { - case 0: // NOTATION_STANDARD - int i = num.indexOf("E"); - if (i == -1) { // just return - if (num.substring(num.length() - 2).equals(".0")) { - num = num.substring(0, num.length() - 2); - } - if (negative) { - return "-" + num; - } - return num; - } - preString = num.substring(0, i); - CompatibleBigDecimal outNumD = new CompatibleBigDecimal(preString); - String exp = ""; - if (num.indexOf("+") == -1) { - exp = num.substring(i + 1); - } else { - exp = num.substring(i + 2); - } - int expNum = Integer.valueOf(exp).intValue(); - outNumD = new CompatibleBigDecimal(outNumD.movePointRight(expNum)); - // outNumD = outNumD.multiply(new CompatibleBigDecimal(Math.pow(10, - // expNum))); - // Logger.logInfo(String.valueOf(outNumD)); - // outNum = Math.r - - // check if we should be returning a whole number or a decimal - int moveLen = num.indexOf("E") - num.indexOf(".") - 1; - if (expNum >= moveLen) { - if (negative) { - return "-" - + Math.round(outNumD.doubleValue()); - } else { - return String.valueOf(Math.round(outNumD.doubleValue())); - } - } - Object[] args = new Object[0]; - // args[0] = outNumD; - Object res = ResourceLoader.executeIfSupported(outNumD, args, - "toPlainString"); - if (res != null) { - fullString = res.toString(); - } else { - fullString = outNumD.toCompatibleString(); - } - break; - case 1: // NOTATION_SCIENTIFIC - if (num.indexOf("E") != -1 && num.indexOf("+") == -1) { - fullString = num; - } else if (num.indexOf("+") != -1) { - preString = num.substring(0, num.indexOf("+")); - postString = num.substring(num.indexOf("+") + 1); - return preString + postString; - } else if (num.indexOf(".") != -1) { - int pos = num.indexOf("."); - preString = num.substring(0, 1) + "." - + num.substring(1, num.indexOf(".")); - CompatibleBigDecimal d = new CompatibleBigDecimal(num); - if (d.doubleValue() < 1 && d.doubleValue() != 0) { - // it is a very small value, ie 1.0E-10 - int counter = 0; - while (d.doubleValue() < 1) { - d = new CompatibleBigDecimal(d.movePointRight(1)); - counter++; - } - String retStr = d.toCompatibleString() + "E-" + counter; - return retStr; - } - postString = num.substring(num.indexOf(".") + 1); - fullString = preString + postString; - fullString = fullString + "E" + (pos - 1); - } else { - preString = num.substring(0, 1) + "."; - if (num.length() > 1) { - preString += num.substring(1); - } else { - preString += "0"; - } - fullString = preString + "E" + (num.length() - 1); - } - break; - case 2: // NOTATION_SCIENTIFIC_EXCEL - if (num.indexOf("E") != -1 && num.indexOf("+") != -1) { - fullString = num; - } else if (num.indexOf("E") != -1) { - preString = num.substring(0, num.indexOf("E") + 1); - postString = "+" - + num.substring(num.indexOf("E") + 1); - fullString = preString + postString; - } else if (num.indexOf(".") != -1) { - int pos = num.indexOf("."); - CompatibleBigDecimal d = new CompatibleBigDecimal(num); - if (d.doubleValue() < 1 && d.doubleValue() != 0) { - // it is a very small value, ie 1.0E-10 - int counter = 0; - while (d.doubleValue() < 1) { - d = new CompatibleBigDecimal(d.movePointRight(1)); - counter++; - } - String retStr = d.toCompatibleString() + "E-" + counter; - return retStr; - } - preString = num.substring(0, 1) + "." - + num.substring(1, num.indexOf(".")); - postString = num.substring(num.indexOf(".") + 1); - fullString = preString + postString; - fullString = fullString + "E+" + (pos - 1); - } else { - preString = num.substring(0, 1) + "."; - if (num.length() > 1) { - preString += num.substring(1); - } else { - preString += "0"; - } - fullString = preString + "E+" + (num.length() - 1); - } - break; - default: - return num; - } - if (negative) - fullString = "-" + fullString; - return fullString; - } - - /** - * Return an array of cell handles specified from the string passed in. - *

                  - * Note that a CellHandle cannot exist for an empty cell, so the cells - * retrieved in this manner will be blank cells, not empty cells. - * - * @param cellstr - a comma delimited String representing cells and cell ranges, - * example "A1,A5,A6,B1:B5" would return cells A1, A5, A6, B1, - * B2, B3, B4, B5 - * @param sheet the worksheet containing the cells. - * @return CellHandle[] - */ - public static CellHandle[] getCellHandlesFromSheet(String strRange, - WorkSheetHandle sheet) { - CellHandle[] retCells; - StringTokenizer cellTokenizer = new StringTokenizer(strRange, ","); - ArrayList cells = new ArrayList(); - do { - String element = (String) cellTokenizer.nextElement(); - if (element.indexOf(":") != -1) { - CellRange aRange = new CellRange(sheet.getSheetName() + "!" - + strRange, sheet.wbh, true); - cells.addAll(aRange.getCellList()); - } else { - CellHandle aCell = null; - try { - aCell = sheet.getCell(element); - } catch (Exception ce) { - aCell = sheet.add(null, element); - } - if (aCell != null) - cells.add(aCell); - } - } while (cellTokenizer.hasMoreElements()); - retCells = new CellHandle[cells.size()]; - retCells = cells.toArray(retCells); - return retCells; - } - - /** - * Strip sheet name(s) from range string can be Sheet1!AB:Sheet!BC or - * Sheet!AB:BC or AB:BC or Sheet1:Sheet2!A1:A2 - * - * @param address or range String - * @return 1st sheetname - *

                  - * Ok, this is a strange method. It returns a string array of the - * following format 0 - sheetname1 1 - cell address or range (what - * if there are 2?) 2 - sheetname2 3 - external link 1 ?? some ooxml - * record 4 - external link 2 - */ - public static String[] stripSheetNameFromRange(String address) { - String sheetname = null, sheetname2 = null; - int m = address.indexOf('!'); - if (m > -1) { - if (address.substring(0, m).indexOf(":") == -1) - sheetname = address.substring(0, m); - else { - int z = address.indexOf(":"); - sheetname = address.substring(0, z); - sheetname2 = address.substring(z + 1, m); - } - } - address = address.substring(m + 1); - int n = address.indexOf('!'); // see if 2nd sheet name exists - if (n > -1 && !address.equals("#REF!")) { - m = address.indexOf(':'); - sheetname2 = address.substring(m + 1, n); - m = address.indexOf(':'); - address = address.substring(0, m + 1) + address.substring(n + 1); - } - // 20090323 KSC: handle external references (OOXML-Specific format of - // [#]SheetName!Ref where # denotes ExternalLink workbook - String exLink1 = null, exLink2 = null; - if (sheetname != null && sheetname.indexOf('[') >= 0) { // External - // OOXML - // reference - exLink1 = sheetname.substring(sheetname.indexOf('[')); - exLink1 = exLink1.substring(0, exLink1.indexOf(']') + 1); - sheetname = StringTool.replaceText(sheetname, exLink1, ""); - if (sheetname.equals("")) - sheetname = null; // possible to have address in form of = - // [#]!Name or range - } - if (sheetname2 != null && sheetname2.indexOf('[') >= 0) { // External - // OOXML - // reference - exLink2 = sheetname2.substring(sheetname2.indexOf('[')); - exLink2 = exLink2.substring(0, exLink2.indexOf(']') + 1); - sheetname2 = StringTool.replaceText(sheetname2, exLink2, ""); - if (sheetname2.equals("")) - sheetname2 = null; // possible to have address in form of = - // [#]!Name or range - } - // return new String[]{sheetname, address, sheetname2}; - return new String[]{sheetname, address, sheetname2, exLink1, exLink2}; // 20090323 - // KSC: - // add - // any - // external - // link - // info - } - - /** - * return the first and last coords of a range in int form + the number of - * cells in the range range is in the format of Sheet - */ - public static int[] getRangeCoords(String range) { - int numrows = 0; - int numcols = 0; - int numcells = 0; - int[] coords = new int[5]; - String temprange = range; - // figure out the sheet bounds using the range string - temprange = ExcelTools.stripSheetNameFromRange(temprange)[1]; - String startcell = "", endcell = ""; - int lastcolon = temprange.lastIndexOf(":"); - endcell = temprange.substring(lastcolon + 1); - if (lastcolon == -1) // no range - startcell = endcell; - else - startcell = temprange.substring(0, lastcolon); - startcell = StringTool.strip(startcell, "$"); - endcell = StringTool.strip(endcell, "$"); - - // get the first cell's coordinates - int charct = startcell.length(); - while (charct > 0) { - if (!Character.isDigit(startcell.charAt(--charct))) { - charct++; - break; - } - } - String firstcellrowstr = startcell.substring(charct); - int firstcellrow = -1; - try { - firstcellrow = Integer.parseInt(firstcellrowstr); - } catch (NumberFormatException e) { // could be a whole-col-style ref - } - String firstcellcolstr = startcell.substring(0, charct).trim(); - int firstcellcol = ExcelTools.getIntVal(firstcellcolstr); - // get the last cell's coordinates - charct = endcell.length(); - while (charct > 0) { - if (!Character.isDigit(endcell.charAt(--charct))) { - charct++; - break; - } - } - String lastcellrowstr = endcell.substring(charct); - int lastcellrow = -1; - try { - lastcellrow = Integer.parseInt(lastcellrowstr); - } catch (NumberFormatException e) { // could be a whole-col-style ref - } - String lastcellcolstr = endcell.substring(0, charct); - int lastcellcol = ExcelTools.getIntVal(lastcellcolstr); - numrows = (lastcellrow - firstcellrow) + 1; - numcols = (lastcellcol - firstcellcol) + 1; - /* - * if(numrows == 0)numrows =1; if(numcols == 0)numcols =1; - */ - numcells = numrows * numcols; - if (numcells < 0) - numcells *= -1; // handle swapped cells ie: "B1:A1" - - coords[0] = firstcellrow; - coords[1] = firstcellcol; - coords[2] = lastcellrow; - coords[3] = lastcellcol; - coords[4] = numcells; - // Trap errors in range - // if (firstcellrow < 0 || lastcellrow < 0 || firstcellcol < 0 || - // lastcellcol < 0) - // Logger.logErr("ExcelTools.getRangeCoords: Error in Range " + range); - return coords; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/ExcelTools.kt b/src/main/java/io/starter/OpenXLS/ExcelTools.kt new file mode 100644 index 0000000..945a4f2 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/ExcelTools.kt @@ -0,0 +1,1210 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.toolkit.CompatibleBigDecimal +import io.starter.toolkit.Logger +import io.starter.toolkit.ResourceLoader +import io.starter.toolkit.StringTool + +import java.util.ArrayList +import java.util.Date +import java.util.IllegalFormatConversionException +import java.util.StringTokenizer + +//import java.text.SimpleDateFormat; + + +/** + * OpenXLS helper methods.

                  + * Contains helpful methods to ease use of the OpenXLS toolkit.

                  + * + * + * "http://starter.io">Starter Inc. + * + * @see ByteTools + */ + +class ExcelTools : java.io.Serializable { + companion object { + + private const val serialVersionUID = 7622857355626065370L + + /** + * Formats a double in the standard OpenXLS (General) format. Up to + * 99999999999 is expressed in standard notation. Above that is formatted in + * scientific notation + * + * + * In addition, Excel precision of 9 digits is maintained + * + * + * + * + * returns a number formatted in Excel's General format, (assuming a wide + * enough column width - see below) example: + * formatNumericNotation(1234567890123) returns "1.23457E+12" + * + * + * Information on NOTATION_STANDARD_EXCEL (i.e. Excel's General Format): // + * Excel will show as many decimal places that the text item has room for, + * it won't use a thousands separator, and if the // number can't fit, Excel + * uses a scientific number format. // RULES: // 1- Assuming the column is + * wide enough numbers will only be displayed in the scientific format when + * they contain more than 10 digits. // 2- If you enter a number into a cell + * and thre is not enough room to display all the digits // then the number + * will either be displayed in scientific format or will not be displayed at + * all, meaning that ##### will appear. // The exact precision of the + * scientific format will depend on the width of the actual cell. + * + * @param fpnum + * @return String formatted number + */ + fun getNumberAsString(fpnum: Double): String { + // Ensure precision and number of digits ala Excel + // double issues - use BigDecimal + var bd = java.math.BigDecimal(fpnum) + val scale = bd.scale() + if (Math.abs(fpnum) > 0.000000001 && scale > 9) { + bd = bd.setScale(9, java.math.RoundingMode.HALF_UP) + } else if (scale > 9) + bd = java.math.BigDecimal(fpnum, java.math.MathContext(5, java.math.RoundingMode.HALF_UP)) + bd = bd.stripTrailingZeros() + var s = bd.toPlainString() + val len = s.length + // If larger than 11 characters, truncate string + if (len > 11 && fpnum > 0 || len > 12) { // must deal with exponents and such as well + if (scale == 0) { + s = java.math.BigDecimal(bd.toString(), java.math.MathContext(6, java.math.RoundingMode.HALF_UP)).toString() + } else if (bd.toString().indexOf("E") == -1) { + s = java.math.BigDecimal(bd.toString(), java.math.MathContext(10, java.math.RoundingMode.HALF_UP)).toString() + while (s.length > 0 && s[s.length - 1] == '0') + s = s.substring(0, s.length - 1) + if (s.endsWith(".")) + s = s.substring(0, s.length - 1) + } else { // 5 + E+XX + sign + s = java.math.BigDecimal(bd.toString(), java.math.MathContext(5, java.math.RoundingMode.HALF_UP)).toString() + } + } + return s + } + + /** + * static version of getFormattedStringVal; given an object value, a + * valid Excel format pattern, return the formatted string value. + * + * @param Object o + * @param String pattern if General or "" returns string value + * @param boolean isInteger if General pattern, attempt to use integer value (rather than double) + */ + fun getFormattedStringVal(o: Any?, pattern: String?/*, boolean isInteger*/): String { + var o = o + var pattern = pattern + if (o == null) o = "" + + var isInteger = false + isInteger = o is Int || o is Double && o.toInt().toDouble() == o.toDouble() + + if (pattern == null || pattern == "" || pattern.equals("GENERAL", ignoreCase = true)) { + if (isInteger) + return java.lang.Double.valueOf(o.toString()).toInt().toString() + else { // general double numbers have default precision ... + try { + val d = Double(o.toString()) + return ExcelTools.getNumberAsString(java.lang.Double.valueOf(o.toString())) // handles default precision + } catch (e: NumberFormatException) { + } + + return o.toString() + } + } else if (pattern == "000-00-0000") { // special case for SSN format ... sigh ... + try { + Double(o.toString()) // if it can't be converted to a number, return original string (tis what excel does) + var s = o.toString() + while (s.length < 9) + // tis what excel does ... + s = '0' + s + return s.substring(0, 3) + "-" + s.substring(3, 5) + "-" + s.substring(5) + } catch (e: Exception) { + return o.toString() + } + + } + + + /** try to determine if the format is numeric (+currency) or date */ + var isNumeric = false + var isDate = false + var isString = false + + /** excel formats can have up to 4 parts: ;;; */ + val pats = pattern.split(";".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() // assign the correct pattern according to double or string value + + val tester = StringTool.convertPatternExtractBracketedExpression(pats[0]) + if (tester.matches(".*(((y{1,4}|m{1,5}|d{1,4}|h{1,2}|s{1,2}).*)+).*".toRegex())) { + isDate = true + pats[0] = tester // ignore locale and other info for dates ... + } + if (!isDate) { + var idx = pats.size - 1 // default with string + try { + val d = Double(o.toString()) + isNumeric = true + if (d > 0) + // 1st expression is for + numbers + idx = 0 + else if (pats.size > 1 && d < 0) + // 2nd is for - numbers + idx = 1 + else if (pats.size > 2 && d == 0.0) + // 3rd for 0 + idx = 2 + pattern = StringTool.convertPatternFromExcelToStringFormatter(pats[idx], d < 0) // get correct format for String.format formatter + } catch (e: NumberFormatException) { // 4th for text (non-numeric) + if (pats.size > 3) + idx = 3 + isString = true + pattern = StringTool.convertPatternFromExcelToStringFormatter(pats[idx], false) // get correct format for String.format formatter + } + + } else { + pattern = pats[0] + pattern = StringTool.convertDatePatternFromExcelToStringFormatter(pattern!!) // get correct format for SimpleDateFormat + } + + + if (isString) { // use string portion of format, if any + try { + return String.format(pattern!!, o) + } catch (e: IllegalFormatConversionException) { + return o.toString() + } + + } + if (isNumeric) { + try { + var d = Double(o.toString()) + if (!java.lang.Double.isNaN(d)) { + d = Math.abs(d) // negative number intricacies have been handled in convertPattern method + // ugly, but has to be done ... + if (pattern!!.indexOf("%%") != -1) + // convert to percent + d *= 100.0 + // special case of "@" -- integers converted to doubles format incorrectly ... + return if (pattern == "%s") { + o.toString() + } else String.format(pattern, d) + } + } catch (e: Exception) { + } + + return o.toString() + } + if (isDate) { + try { + WorkBookHandle.simpledateformat.applyPattern(pattern!!) + } catch (ex: Exception) { + return o.toString() + } + + try { + return WorkBookHandle.simpledateformat.format(DateConverter.getCalendarFromNumber(o).time) + /*// KSC: TESTING +Date d= DateConverter.getCalendarFromNumber(o).getTime(); +return WorkBookHandle.simpledateformat.format(d);*/ + } catch (e: NumberFormatException) { + try { + return WorkBookHandle.simpledateformat.format(Date(o.toString()).time) + } catch (i: IllegalArgumentException) { + if (o is Number) + Logger.logWarn("Unable to format date in $pattern") + } + + } catch (e: IllegalArgumentException) { + if (o is Number) + Logger.logWarn("Unable to format date in $pattern") + } + + } + // otherwise + return o.toString() + } + + + /** + * A FAIL FAST implementation for finding whether a cell string address + * falls within a set of row/col range coordinates. + * + * + * Sep 21, 2010 + * + * @param rng the range you want to test + * @param rowFirst in the target range + * @param rowLast in the target range + * @param colFirst in the target range + * @param colLast in the target range + * @return + */ + fun isInRange(rng: String, rowFirst: Int, rowLast: Int, + colFirst: Int, colLast: Int): Boolean { + val sh = io.starter.OpenXLS.ExcelTools.getRowColFromString(rng) + + // the guantlet + if (sh[1] < colFirst) + return false + if (sh[1] > colLast) + return false + return if (sh[0] < rowFirst) false else sh[0] <= rowLast +// passes! + + } + + /** + * returns true if range intersects with range2 + * + * @param rng + * @param rc + * @return + */ + fun intersects(rng: String, rc: IntArray): Boolean { + val rc2 = ExcelTools.getRangeCoords(rng) + return (rc[0] >= rc2[0] && rc[2] <= rc2[2] && rc[1] >= rc2[1] + && rc[3] <= rc2[3]) + } + + /** + * returns true if address is before the range coordinates defined by rc + * + * @param rc row col of address + * @param rng int[] coordinates as: row0, col0, row1, col1 + * @return true if address is before the range coordinates + */ + fun isBeforeRange(rc: IntArray, rng: IntArray): Boolean { + return rc[0] < rng[0] || rc[0] == rng[0] && rc[1] < rng[1] + } + + /** + * returns true if address is before the range coordinates defined by rc + * + * @param rc row col of address + * @param rng int[] coordinates as: row0, col0, row1, col1 + * @return true if address is before the range coordinates + */ + fun isAfterRange(rc: IntArray, rng: IntArray): Boolean { + return rc[0] > rng[2] || rc[0] == rng[2] && rc[1] > rng[3] + } + + /** + * Takes an input Object and attempts to convert to numeric Objects of the + * highest precision possible. + * + * + * This method is useful for avoiding the Excel warnings + * "Number Stored As Text" when storing string data that contains numbers. + * + * + * NOTE: this method is useful for ensuring that Formula references contain + * true numeric values as not all String numbers are properly interpreted in + * Formula engines, and can silently fail. + * + * + * For this reason, always use numeric, non-string values to calculated + * cells. + * + * @param input + * @return + */ + fun getObject(`in`: Any): Any { + // do not record -- only called from other methods + if (`in` !is String) { + return `in` + } + + val input = `in`.toString() + var ret: Any = input // default is the original string + + try { + ret = Double(input) + return ret + } catch (ex: NumberFormatException) { + try { + ret = Float(input) + return ret + } catch (ex2: NumberFormatException) { + try { + ret = Integer.valueOf(input) + return ret + } catch (ex3: NumberFormatException) { + // ret Is set outside of loop incase no match is ever found. + } + + } + + } + + // list of formatting chars to check for + val fmtlist = arrayOf(arrayOf("$", ","), arrayOf(",", ","), arrayOf("%", ",")) + + // strip the formatting + for (t in fmtlist.indices) { + if (input.indexOf(fmtlist[t][0]) > -1) { // contains! + var converted = StringTool.replaceText(input, fmtlist[t][0], + "") // strip first token (ie: '$') + converted = StringTool + .replaceText(converted, fmtlist[t][1], "") // strip + // second + // token + // (ie: ',') + try { + ret = Double(converted) + return ret + } catch (ex: NumberFormatException) { + try { + ret = Float(converted) + return ret + } catch (ex2: NumberFormatException) { + try { + ret = Integer.valueOf(converted) + return ret + } catch (ex3: NumberFormatException) { + // ret Is set outside of loop incase no match is + // ever found. + } + + } + + } + + } + + } + + return ret + } + + /** + * convert twips to pixels + * + * + * + * + * In addition to a calculated size unit derived from the average size of + * the default characters 0-9, Excel uses the 'twips' measurement which is + * defined as: + * + * + * 1 twip = 1/20 point or 20 twips = 1 point 1 twip = 1/567 centimeter or + * 567 twips = 1 centimeter 1 twip = 1/1440 inch or 1440 twips = 1 inch + * + * + * + * + * 1 pixel = 0.75 points 1 pixel * 1.3333 = 1 point 1 twip * 20 = 1 point + * + * @param pixels + * @return twips + */ + fun getPixels(twips: Float): Float { + val points = twips / 20 + return points * 1.3333333f + } + + /** + * convert pixels to twips + * + * + * + * + * In addition to a calculated size unit derived from the average size of + * the default characters 0-9, Excel uses the 'twips' measurement which is + * defined as: + * + * + * 1 pixel = 0.75 points 1 pixel * 1.3333 = 1 point 1 twip * 20 = 1 point + * + * @param pixels + * @return twips + */ + fun getTwips(pixels: Float): Float { + val points = pixels * .75f + return points * 20 + } + + /** + * get recordy byte def as a String + * + * + * public static String getRecordByteDef(XLSRecord rec){ byte[] b = + * rec.read(); StringBuffer sb = new StringBuffer("byte[] rbytes = {"); + * for(int t = 0;t;t++){ + * + * + * Byte thisb = new Byte(b[t]); + * + * + * sb.append(thisb.toString() + ", "); } sb.append("};"); return + * sb.toString(); } + */ + + val logDate: String + get() = Date(System.currentTimeMillis()).toString() + + /** + * tracks minimal info container for counters -> start time, last time, + * start mem, last mem + * + * @param info + * @param perfobj + */ + fun benchmark(info: String, perfobj: Any) { + val rt = Runtime.getRuntime() + var p: LongArray? = null + var lasttime = 0L + var lastmem = 0L + if (System.getProperties()[perfobj.toString()] != null) { + p = System.getProperties()[perfobj.toString()] as LongArray + lasttime = p[1] + lastmem = p[3] + p[1] = System.currentTimeMillis() + p[3] = rt.freeMemory() + val elapsedsec = (p[1] - lasttime).toDouble() + var usedmem = (lastmem - p[3]).toDouble() // - lastmem; + if (usedmem < 0) + usedmem *= -1.0 + Logger.logInfo("$logDate $info") + Logger.logInfo(" time: $elapsedsec millis") + Logger.logInfo(" mem: $usedmem bytes.") + } else { + p = LongArray(4) + p[0] = System.currentTimeMillis() + p[1] = System.currentTimeMillis() + p[2] = rt.freeMemory() + p[3] = rt.freeMemory() + lasttime = p[1] + lastmem = p[3] + System.getProperties()[perfobj.toString()] = p + } + + } + + /** + * get the bytes from a Vector of objects public byte[] + * getBytesFrom(CompatibleVector objs){ + * + * + * for(int t = 0;t();t++){ + * + * + * } + * + * + * + * + * } + */ + + internal var alpharr = charArrayOf('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z') + /* + * NOT USED ANYMORE -- see getAlphaVal -- OK to remove?? + */ + val ALPHASDELETE = arrayOf("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB", "AC", "AD", "AE", "AF", "AG", "AH", "AI", "AJ", "AK", "AL", "AM", "AN", "AO", "AP", "AQ", "AR", "AS", "AT", "AU", "AV", "AW", "AX", "AY", "AZ", "BA", "BB", "BC", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BK", "BL", "BM", "BN", "BO", "BP", "BQ", "BR", "BS", "BT", "BU", "BV", "BW", "BX", "BY", "BZ", "CA", "CB", "CC", "CD", "CE", "CF", "CG", "CH", "CI", "CJ", "CK", "CL", "CM", "CN", "CO", "CP", "CQ", "CR", "CS", "CT", "CU", "CV", "CW", "CX", "CY", "CZ", "DA", "DB", "DC", "DD", "DE", "DF", "DG", "DH", "DI", "DJ", "DK", "DL", "DM", "DN", "DO", "DP", "DQ", "DR", "DS", "DT", "DU", "DV", "DW", "DX", "DY", "DZ", "EA", "EB", "EC", "ED", "EE", "EF", "EG", "EH", "EI", "EJ", "EK", "EL", "EM", "EN", "EO", "EP", "EQ", "ER", "ES", "ET", "EU", "EV", "EW", "EX", "EY", "EZ", "FA", "FB", "FC", "FD", "FE", "FF", "FG", "FH", "FI", "FJ", "FK", "FL", "FM", "FN", "FO", "FP", "FQ", "FR", "FS", "FT", "FU", "FV", "FW", "FX", "FY", "FZ", "GA", "GB", "GC", "GD", "GE", "GF", "GG", "GH", "GI", "GJ", "GK", "GL", "GM", "GN", "GO", "GP", "GQ", "GR", "GS", "GT", "GU", "GV", "GW", "GX", "GY", "GZ", "HA", "HB", "HC", "HD", "HE", "HF", "HG", "HH", "HI", "HJ", "HK", "HL", "HM", "HN", "HO", "HP", "HQ", "HR", "HS", "HT", "HU", "HV", "HW", "HX", "HY", "HZ", "IA", "IB", "IC", "ID", "IE", "IF", "IG", "IH", "II", "IJ", "IK", "IL", "IM", "IN", "IO", "IP", "IQ", "IR", "IS", "IT", "IU", "IV", "IW", "IX", "IY", "IZ") + + /** + * get the Excel-style Column alphabetical representation of an integer + * (0-based). + * + * + * for example: 0 = A 26= AA 701= ZZ 702= AAA 16383= XFD (max) + */ + fun getAlphaVal(i: Int): String { + var i = i + var ret = "" + var leftover = 0 + if (i > 701) { // has 3rd digit + var z = i / 676 - 1 // -1 to account for 0-based + if (i % 676 < 26) { // then "leftover" is actually 2nd digit + z-- + leftover = 676 + } + ret = ExcelTools.alpharr[z].toString() + i = i % 676 + i += leftover + } + if (i > 25) { // has 2nd digit + val z = i / 26 - 1 // -1 to account for 0-based + ret = ret + ExcelTools.alpharr[z] + i = i % 26 + } + + // bbennett: this raises AIOOB if i = -1. In this situation we don't + // care about alpha because it will just be in the message of the + // exception that flags cell as non existent. + ret += if (i < 0) Integer.toString(i) else ExcelTools.alpharr[i].toString() + + return ret + } + + /** + * get the int value of the Excel-style Column alpha representation. + * + * @param String column name + * @return int the 0-based column number + */ + fun getIntVal(c: String): Int { + var c = c + c = c.toUpperCase() + if (c.length > 3) + // max col value= XFD in Excel 2007 + return -1 + var i = c.length - 1 + var ret = 0 + while (i >= 0) { // process least to most-sigificant dig + var z = 0 + val cc = c[i] + while (z < alpharr.size && cc != alpharr[z++]) + ; + z *= Math.pow(26.0, (c.length - i - 1).toDouble()).toInt() // 1-based col for computing + ret += z + i-- + } + // make 0-based + ret-- + return ret + } + + /** + * Parses an Excel cell address into row and column integers. + * + * @param address the address to parse, either A1 or R1C1 + * @return int[2]: [0] row index, [1] column index + * @throws IllegalArgumentException if the argument is not a valid address + */ + fun getRowColFromString(address: String): IntArray { + var address = address + + if (address.indexOf("$") > -1) { + address = StringTool.strip(address, "$") + } + if (address.indexOf("!") > -1) { + address = address.substring(address.indexOf("!") + 1) + } + if (address.indexOf(":") > -1) + return getRangeRowCol(address) + + val adrchars = address.toCharArray() + var row = 0 + var col = 0 + var charpos = -1 + var numpos = -1 + var r1c1 = false + for (i in adrchars.indices) { + if (Character.isDigit(adrchars[i])) { + if (numpos == -1) + // its a number + numpos = i + } else if (charpos == -1) { + charpos = i + if (numpos >= 0) { // we have already set number and we now have + // a nondigit - R1C1 style + r1c1 = true + // break, it's all over! + break + } + } + } + + if (r1c1) { // it's a single cell ref + try { + // there's an R and a C, not adjacent + if (address.toUpperCase().indexOf("R") == 0) { // startwith R + val rx = address.substring(1, address.toUpperCase() + .indexOf("C")) + val cx = address.substring(address.toUpperCase() + .indexOf("C") + 1) + row = Integer.parseInt(rx) + col = Integer.parseInt(cx) + } + } catch (e: NumberFormatException) { + throw IllegalArgumentException("illegal R1C1 address '" + + address + "'") + } + + } else { + row = 0 // -1 below + col = -1 + if (charpos == 0 && numpos > 0) { + val colval = address.substring(0, numpos) + col = getIntVal(colval) + if (col < 0) + throw IllegalArgumentException("illegal column value '" + + colval + "' in address '" + address + "'") + } + if (numpos >= 0) { + row = Integer.parseInt(address.substring(numpos)) + if (row < 1) + throw IllegalArgumentException( + "row may not be negative in address '" + address + + "'") + } else { // it's a wholecol ref + col = getIntVal(address) + if (col < 0) + throw IllegalArgumentException("illegal column value '" + + address + "' in address '" + address + "'") + } + } + + return intArrayOf(row - 1, col) + } + + /** + * Parses an Excel cell range and returns the addresses as an int array. The + * range may not be qualified with sheet names. Strip them with + * [.stripSheetNameFromRange] before calling this method. If the + * argument is a single cell address it will be returned for both bounds. + * + * @param range the range to parse + * @return int[4]: [0] first row, [1] first column, [2] second row, [3] + * second column + * @throws IllegalArgumentException if the addresses are invalid + */ + fun getRangeRowCol(range: String): IntArray { + val colon = range.indexOf(":") + + val firstloc: String + val lastloc: String + + if (colon > -1) { + firstloc = range.substring(0, colon) + lastloc = range.substring(colon + 1) + } else { + firstloc = range + lastloc = range + } + + val result = IntArray(4) + var temp: IntArray + + temp = getRowColFromString(firstloc) + System.arraycopy(temp, 0, result, 0, 2) + + temp = getRowColFromString(lastloc) + System.arraycopy(temp, 0, result, 2, 2) + + return result + } + + /** + * Takes an int array representing a row and column and formats it as a cell + * address. + * + * + * The index is zero-based. + * + * + * [0][0] is "A1" [1][1] is "B2" [2][2] is "C3" + * + * @param int[] the numeric range to convert + * @return String the string representation of the range + */ + fun formatLocation(rowCol: IntArray): String { + val sb = StringBuffer(getAlphaVal(rowCol[1])) + sb.append(rowCol[0] + 1) + + // handle ranges + if (rowCol.size > 3) { + // 20090807: KSC: only a range if 1st is != 2nd cell :) + if (rowCol[0] == rowCol[2] && rowCol[1] == rowCol[3]) + // it's a single address + return sb.toString() + sb.append(":") + sb.append(getAlphaVal(rowCol[3])) + sb.append(rowCol[2] + 1) + } + + return sb.toString() + } + + /** + * Takes an int array representing a row and column and formats it as a cell + * address, taking into account relative or absolute refs + * + * + * The index is zero-based. + * + * + * [0][0] is "A1", $A1, A$1 or $A$1 depending upon bRelRow or bRelCol [1][1] + * is "B2", $B1, B$1 or B$1 depending upon bRelRow or bRelCol [2][2] is + * "C3", $C1, C$1 or $C$1 depending upon bRelRow or bRelCol + * + * @param int[] the numeric range to convert + * @param bRelRow if true, no "$"s are added, relative row reference + * @param bRelCol if true, no "$"s are added, relative col reference + * @return String the string representation of the range + */ + fun formatLocation(s: IntArray, bRelRow: Boolean, + bRelCol: Boolean): String { + val sb = StringBuffer(if (bRelCol) "" else "$") + if (s[1] > -1) + // account for WholeRow/WholeCol references + sb.append(getAlphaVal(s[1])) + + if (s[0] > -1) + // account for WholeRow/WholeCol references + sb.append((if (bRelRow) "" else "$") + (s[0] + 1)) + // 20090906 KSC: handle ranges + if (s.size > 3) { + if (s[0] == s[2] && s[1] == s[3]) + // it's a single address + return sb.toString() + sb.append(":") + sb.append(if (bRelCol) "" else "$") + sb.append(getAlphaVal(s[3])) + sb.append((if (bRelRow) "" else "$") + (s[2] + 1)) + } + + return sb.toString() + } + + /** + * Takes an array of four shorts and formats it as a cell range. + * + * + * IE [0][3][1][4] would be "A2:B3" + * + * @param int[] the numeric range to convert + * @return String the string representation of the range + */ + fun formatRange(s: IntArray): String { + if (s.size != 4) + return "incorrect array size in ExcelTools.formatLocation" + val temp = IntArray(2) + temp[0] = s[1] + temp[1] = s[0] + val firstcell = formatLocation(temp) + temp[0] = s[3] + temp[1] = s[2] + val lastcell = formatLocation(temp) + return "$firstcell:$lastcell" + } + + /** + * format a range as a string, range in format of [r][c][r1][c1] + * + * @param s + * @return String representation of the integers as a range, ie A1:B4 + */ + fun formatRangeRowCol(s: IntArray): String { + if (s.size != 4) + return "incorrect array size in ExcelTools.formatLocation" + val temp = IntArray(2) + temp[0] = s[0] + temp[1] = s[1] + val firstcell = formatLocation(temp) + temp[0] = s[2] + temp[1] = s[3] + val lastcell = formatLocation(temp) + return "$firstcell:$lastcell" + } + + /** + * format a range as a string, range in format of [r][c][r1][c1] including + * relative address state + * + * @param s + * @param bRelAddresses contains relative row and col state for each rcr1c1 + * @return String representation of the integers as a range, ie A1:B4 + */ + fun formatRangeRowCol(s: IntArray, bRelAddresses: BooleanArray): String { + if (s.size != 4) + return "incorrect array size in ExcelTools.formatLocation" + val temp = IntArray(2) + temp[0] = s[0] + temp[1] = s[1] + val firstcell = formatLocation(temp, bRelAddresses[0], + bRelAddresses[1]) + temp[0] = s[2] + temp[1] = s[3] + val lastcell = formatLocation(temp, bRelAddresses[2], + bRelAddresses[3]) + // unfortunately, no formatLocation can do this. This is + // formattingRANGERowCol + // if (firstcell.equals(lastcell)) return firstcell; // 20090309 KSC: + return "$firstcell:$lastcell" + } + + /** + * Transforms a string to an array of ints for evaluation purposes. For + * example, acdc == [0][2][3][2] + */ + fun transformStringToIntVals(trans: String): IntArray { + val intarr = IntArray(trans.length) + for (i in 0 until trans.length) { + val c = trans[i] + for (x in alpharr.indices) { + if (c.toString().equals( + alpharr[x].toString(), ignoreCase = true)) { + intarr[i] = x + } + } + } + return intarr + } + + /** + * Formats a string representation of a numeric value as a string in the + * specified notation: + * + * @param int

                  + * NOTATION_STANDARD = 0,

                  + * NOTATION_SCIENTIFIC = 1,

                  + * NOTATION_SCIENTIFIC_EXCEL = 2,

                  + * EXTENXLS_NOTATION = 3 + * + * + * + * + * example: formatNumericNotation(1.23456E5, 0) returns a "123456" + * example: formatNumericNotation(123456, 1) returns "1.23456E5" + * example: formatNumericNotation(123456, 2) returns "1.23456E+5" + * example: formatNumericNotation(123456, 3) returns "1.23456E+5" + */ + fun formatNumericNotation(num: String, notationType: Int): String { + var num = num + // if (notationType > 2)return null; + var negative = false + if (num.substring(0, 1) == "-") { + negative = true + num = num.substring(1) + } + var preString: String + val postString: String + var fullString: String? = "" + when (notationType) { + 0 // NOTATION_STANDARD + -> { + val i = num.indexOf("E") + if (i == -1) { // just return + if (num.substring(num.length - 2) == ".0") { + num = num.substring(0, num.length - 2) + } + return if (negative) { + "-$num" + } else num + } + preString = num.substring(0, i) + var outNumD = CompatibleBigDecimal(preString) + var exp = "" + if (num.indexOf("+") == -1) { + exp = num.substring(i + 1) + } else { + exp = num.substring(i + 2) + } + val expNum = Integer.valueOf(exp).toInt() + outNumD = CompatibleBigDecimal(outNumD.movePointRight(expNum)) + // outNumD = outNumD.multiply(new CompatibleBigDecimal(Math.pow(10, + // expNum))); + // Logger.logInfo(String.valueOf(outNumD)); + // outNum = Math.r + + // check if we should be returning a whole number or a decimal + val moveLen = num.indexOf("E") - num.indexOf(".") - 1 + if (expNum >= moveLen) { + return if (negative) { + "-" + Math.round(outNumD.toDouble()) + } else { + Math.round(outNumD.toDouble()).toString() + } + } + val args = arrayOfNulls(0) + // args[0] = outNumD; + val res = ResourceLoader.executeIfSupported(outNumD, args, + "toPlainString") + if (res != null) { + fullString = res.toString() + } else { + fullString = outNumD.toCompatibleString() + } + } + 1 // NOTATION_SCIENTIFIC + -> if (num.indexOf("E") != -1 && num.indexOf("+") == -1) { + fullString = num + } else if (num.indexOf("+") != -1) { + preString = num.substring(0, num.indexOf("+")) + postString = num.substring(num.indexOf("+") + 1) + return preString + postString + } else if (num.indexOf(".") != -1) { + val pos = num.indexOf(".") + preString = (num.substring(0, 1) + "." + + num.substring(1, num.indexOf("."))) + var d = CompatibleBigDecimal(num) + if (d.toDouble() < 1 && d.toDouble() != 0.0) { + // it is a very small value, ie 1.0E-10 + var counter = 0 + while (d.toDouble() < 1) { + d = CompatibleBigDecimal(d.movePointRight(1)) + counter++ + } + return d.toCompatibleString() + "E-" + counter + } + postString = num.substring(num.indexOf(".") + 1) + fullString = preString + postString + fullString = fullString + "E" + (pos - 1) + } else { + preString = num.substring(0, 1) + "." + if (num.length > 1) { + preString += num.substring(1) + } else { + preString += "0" + } + fullString = preString + "E" + (num.length - 1) + } + 2 // NOTATION_SCIENTIFIC_EXCEL + -> if (num.indexOf("E") != -1 && num.indexOf("+") != -1) { + fullString = num + } else if (num.indexOf("E") != -1) { + preString = num.substring(0, num.indexOf("E") + 1) + postString = "+" + num.substring(num.indexOf("E") + 1) + fullString = preString + postString + } else if (num.indexOf(".") != -1) { + val pos = num.indexOf(".") + var d = CompatibleBigDecimal(num) + if (d.toDouble() < 1 && d.toDouble() != 0.0) { + // it is a very small value, ie 1.0E-10 + var counter = 0 + while (d.toDouble() < 1) { + d = CompatibleBigDecimal(d.movePointRight(1)) + counter++ + } + return d.toCompatibleString() + "E-" + counter + } + preString = (num.substring(0, 1) + "." + + num.substring(1, num.indexOf("."))) + postString = num.substring(num.indexOf(".") + 1) + fullString = preString + postString + fullString = fullString + "E+" + (pos - 1) + } else { + preString = num.substring(0, 1) + "." + if (num.length > 1) { + preString += num.substring(1) + } else { + preString += "0" + } + fullString = preString + "E+" + (num.length - 1) + } + else -> return num + } + if (negative) + fullString = "-" + fullString!! + return fullString + } + + /** + * Return an array of cell handles specified from the string passed in. + * + * + * Note that a CellHandle cannot exist for an empty cell, so the cells + * retrieved in this manner will be blank cells, not empty cells. + * + * @param cellstr - a comma delimited String representing cells and cell ranges, + * example "A1,A5,A6,B1:B5" would return cells A1, A5, A6, B1, + * B2, B3, B4, B5 + * @param sheet the worksheet containing the cells. + * @return CellHandle[] + */ + fun getCellHandlesFromSheet(strRange: String, + sheet: WorkSheetHandle): Array { + var retCells: Array + val cellTokenizer = StringTokenizer(strRange, ",") + val cells = ArrayList() + do { + val element = cellTokenizer.nextElement() as String + if (element.indexOf(":") != -1) { + val aRange = CellRange(sheet.sheetName + "!" + + strRange, sheet.workBook, true) + cells.addAll(aRange.cellList) + } else { + var aCell: CellHandle? = null + try { + aCell = sheet.getCell(element) + } catch (ce: Exception) { + aCell = sheet.add(null, element) + } + + if (aCell != null) + cells.add(aCell) + } + } while (cellTokenizer.hasMoreElements()) + retCells = arrayOfNulls(cells.size) + retCells = cells.toTypedArray() + return retCells + } + + /** + * Strip sheet name(s) from range string can be Sheet1!AB:Sheet!BC or + * Sheet!AB:BC or AB:BC or Sheet1:Sheet2!A1:A2 + * + * @param address or range String + * @return 1st sheetname + * + * + * Ok, this is a strange method. It returns a string array of the + * following format 0 - sheetname1 1 - cell address or range (what + * if there are 2?) 2 - sheetname2 3 - external link 1 ?? some ooxml + * record 4 - external link 2 + */ + fun stripSheetNameFromRange(address: String): Array { + var address = address + var sheetname: String? = null + var sheetname2: String? = null + var m = address.indexOf('!') + if (m > -1) { + if (address.substring(0, m).indexOf(":") == -1) + sheetname = address.substring(0, m) + else { + val z = address.indexOf(":") + sheetname = address.substring(0, z) + sheetname2 = address.substring(z + 1, m) + } + } + address = address.substring(m + 1) + val n = address.indexOf('!') // see if 2nd sheet name exists + if (n > -1 && address != "#REF!") { + m = address.indexOf(':') + sheetname2 = address.substring(m + 1, n) + m = address.indexOf(':') + address = address.substring(0, m + 1) + address.substring(n + 1) + } + // 20090323 KSC: handle external references (OOXML-Specific format of + // [#]SheetName!Ref where # denotes ExternalLink workbook + var exLink1: String? = null + var exLink2: String? = null + if (sheetname != null && sheetname.indexOf('[') >= 0) { // External + // OOXML + // reference + exLink1 = sheetname.substring(sheetname.indexOf('[')) + exLink1 = exLink1!!.substring(0, exLink1.indexOf(']') + 1) + sheetname = StringTool.replaceText(sheetname, exLink1, "") + if (sheetname == "") + sheetname = null // possible to have address in form of = + // [#]!Name or range + } + if (sheetname2 != null && sheetname2.indexOf('[') >= 0) { // External + // OOXML + // reference + exLink2 = sheetname2.substring(sheetname2.indexOf('[')) + exLink2 = exLink2!!.substring(0, exLink2.indexOf(']') + 1) + sheetname2 = StringTool.replaceText(sheetname2, exLink2, "") + if (sheetname2 == "") + sheetname2 = null // possible to have address in form of = + // [#]!Name or range + } + // return new String[]{sheetname, address, sheetname2}; + return arrayOf(sheetname, address, sheetname2, exLink1, exLink2) // 20090323 + // KSC: + // add + // any + // external + // link + // info + } + + /** + * return the first and last coords of a range in int form + the number of + * cells in the range range is in the format of Sheet + */ + fun getRangeCoords(range: String): IntArray { + var numrows = 0 + var numcols = 0 + var numcells = 0 + val coords = IntArray(5) + var temprange = range + // figure out the sheet bounds using the range string + temprange = ExcelTools.stripSheetNameFromRange(temprange)[1] + var startcell = "" + var endcell = "" + val lastcolon = temprange.lastIndexOf(":") + endcell = temprange.substring(lastcolon + 1) + if (lastcolon == -1) + // no range + startcell = endcell + else + startcell = temprange.substring(0, lastcolon) + startcell = StringTool.strip(startcell, "$") + endcell = StringTool.strip(endcell, "$") + + // get the first cell's coordinates + var charct = startcell.length + while (charct > 0) { + if (!Character.isDigit(startcell[--charct])) { + charct++ + break + } + } + val firstcellrowstr = startcell.substring(charct) + var firstcellrow = -1 + try { + firstcellrow = Integer.parseInt(firstcellrowstr) + } catch (e: NumberFormatException) { // could be a whole-col-style ref + } + + val firstcellcolstr = startcell.substring(0, charct).trim({ it <= ' ' }) + val firstcellcol = ExcelTools.getIntVal(firstcellcolstr) + // get the last cell's coordinates + charct = endcell.length + while (charct > 0) { + if (!Character.isDigit(endcell[--charct])) { + charct++ + break + } + } + val lastcellrowstr = endcell.substring(charct) + var lastcellrow = -1 + try { + lastcellrow = Integer.parseInt(lastcellrowstr) + } catch (e: NumberFormatException) { // could be a whole-col-style ref + } + + val lastcellcolstr = endcell.substring(0, charct) + val lastcellcol = ExcelTools.getIntVal(lastcellcolstr) + numrows = lastcellrow - firstcellrow + 1 + numcols = lastcellcol - firstcellcol + 1 + /* + * if(numrows == 0)numrows =1; if(numcols == 0)numcols =1; + */ + numcells = numrows * numcols + if (numcells < 0) + numcells *= -1 // handle swapped cells ie: "B1:A1" + + coords[0] = firstcellrow + coords[1] = firstcellcol + coords[2] = lastcellrow + coords[3] = lastcellcol + coords[4] = numcells + // Trap errors in range + // if (firstcellrow < 0 || lastcellrow < 0 || firstcellcol < 0 || + // lastcellcol < 0) + // Logger.logErr("ExcelTools.getRangeCoords: Error in Range " + range); + return coords + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/FormatCache.java b/src/main/java/io/starter/OpenXLS/FormatCache.kt similarity index 54% rename from src/main/java/io/starter/OpenXLS/FormatCache.java rename to src/main/java/io/starter/OpenXLS/FormatCache.kt index 09943e3..6675d11 100644 --- a/src/main/java/io/starter/OpenXLS/FormatCache.java +++ b/src/main/java/io/starter/OpenXLS/FormatCache.kt @@ -23,68 +23,63 @@ /** * FormatCache.java */ -package io.starter.OpenXLS; +package io.starter.OpenXLS -import io.starter.toolkit.Logger; - -import java.util.Iterator; -import java.util.Map; +import io.starter.toolkit.Logger /** Handles the caching of the Formats - - This class is no longer in use nor needed * - *@deprecated + * This class is no longer in use nor needed + * */ // is this a valid class anymore? -public class FormatCache { +@Deprecated("") +class FormatCache { - Map mpx = new java.util.HashMap(); + internal var mpx: Map<*, *> = java.util.HashMap() /** Consolidate all identical formats to avoid too many formats errors * - * @deprecated */ - public void pack() { - Iterator itx = mpx.keySet().iterator(); + @Deprecated("") + fun pack() { + val itx = mpx.keys.iterator() while (itx.hasNext()) { - Object oby = mpx.get(itx.next()); - FormatHandle thisfmt = (FormatHandle) oby; - thisfmt = this.get(thisfmt); + val oby = mpx.get(itx.next()) + var thisfmt = oby as FormatHandle + thisfmt = this[thisfmt] } } /** - * @deprecated * @return */ - public FormatHandle get(FormatHandle fmx) { - String fmt = fmx.toString(); + @Deprecated(" ") + operator fun get(fmx: FormatHandle): FormatHandle { + val fmt = fmx.toString() if (!mpx.containsKey(fmt)) { - Logger.logErr("missing in cache: FH " + fmt); + Logger.logErr("missing in cache: FH $fmt") } - FormatHandle ret = (FormatHandle) mpx.get(fmt); - return ret; + return mpx.get(fmt) as FormatHandle } /** - * @deprecated * @return */ - public Object get(String f) { - Object myo = mpx.get(f); - return myo; + @Deprecated(" ") + operator fun get(f: String): Any { + return mpx.get(f) } /** - * @deprecated * @return */ - public int getInt(String f) { - int findex = -1; - if (mpx.containsKey(f)) findex = ((Integer) mpx.get(f)).intValue(); - return findex; + @Deprecated(" ") + fun getInt(f: String): Int { + var findex = -1 + if (mpx.containsKey(f)) findex = (mpx.get(f) as Int).toInt() + return findex } } diff --git a/src/main/java/io/starter/OpenXLS/FormatHandle.java b/src/main/java/io/starter/OpenXLS/FormatHandle.java deleted file mode 100644 index bf3df68..0000000 --- a/src/main/java/io/starter/OpenXLS/FormatHandle.java +++ /dev/null @@ -1,2836 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.OOXML.Fill; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.*; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.json.JSONException; -import org.json.JSONObject; - -import java.awt.Color; -import java.util.List; -import java.util.*; - -/** - * Provides methods for querying and changing cell formatting information. Cell - * formating includes fonts, borders, text alignment, background colors, cell - * protection (locking), etc. - *

                  - * The mutator methods of a FormatHandle object directly and immediately change - * the formatting of the cell(s) to which it is applied. Under no circumstances - * will a FormatHandle alter the formatting of cells to which it is not applied. - * The list of cells to which a FormatHandle applies may be queried with the - * {@link #getCells} method. Additional cells may be added with {@link #addCell} - * and related methods. - *

                  - * A FormatHandle may also be obtained for a row or a column. In such a case the - * FormatHandle sets the default formats for the row or column. The default - * format for a row or column is the format which appears when no other formats - * for the cell are specified, such as for newly created cells. - */ -public class FormatHandle implements Handle, FormatConstants { - - private CompatibleVector mycells = new CompatibleVector(); // all the Cells sharing this format e.g. CellRange - private Xf myxf; - private int xfe; - // see Xf.usedCount instead private boolean canModify = false; - private ColHandle mycol = null; - private RowHandle myrow = null; - private boolean writeImmediate = false; - boolean underlined = false; - - private io.starter.formats.XLS.WorkBook wkbook; - private io.starter.OpenXLS.WorkBook wbh; - - public static final Map numericFormatMap; - public static final Map currencyFormatMap; - public static final Map dateFormatMap; - - static { - Map formats = new HashMap(); - for (String[] formatArr : FormatConstants.NUMERIC_FORMATS) { - if (formatArr.length == 3) { - formats.put(formatArr[0].toLowerCase(), formatArr[2]); - } - } - numericFormatMap = Collections.unmodifiableMap(formats); - - formats = new HashMap(); - for (String[] formatArr : FormatConstants.CURRENCY_FORMATS) { - if (formatArr.length == 3) { - formats.put(formatArr[0].toLowerCase(), formatArr[2]); - } - } - currencyFormatMap = Collections.unmodifiableMap(formats); - - formats = new HashMap(); - for (String[] formatArr : FormatConstants.DATE_FORMATS) { - if (formatArr.length == 3) { - formats.put(formatArr[0].toLowerCase(), formatArr[2]); - } else { - // only 2 elements in date format string array: pattern and hex id - formats.put(formatArr[0].toLowerCase(), formatArr[0]); - } - } - dateFormatMap = Collections.unmodifiableMap(formats); - } - - /** - * Nullary constructor for use in bean context. This is not part of the - * public API and should not be called. - */ - public FormatHandle() { - } - - /** - * Constructs a FormatHandle for the given WorkBook and format record. - * This is not part of the public API and should not be called. - */ - public FormatHandle(io.starter.OpenXLS.WorkBook book, Xf xfr) { - myxf = xfr; - xfe = myxf.getIdx(); - wbh = book; - if (book != null) { - wkbook = book.getWorkBook(); - } else { - if (xfr.getWorkBook() != null) { - wkbook = xfr.getWorkBook(); - } else { - Logger.logErr("FormatHandle constructed with null WorkBook."); - } - } - } - - /** - * Constructs a FormatHandle for the given WorkBook's default format. - */ - public FormatHandle(io.starter.OpenXLS.WorkBook book) { - this(book, book.getWorkBook().getDefaultIxfe()); - } - - /** - * Constructs a FormatHandle for the given format ID and WorkBook. - * This is useful for creating a FormatHandle with the same parameters as - * a cell that does not refer to the cell. For example: - *

                  -     * CellHandle cell = <get cell here>;
                  -     * FormatHandle format = new FormatHandle(
                  -     *         cell.getWorkBook(), cell.getFormatId() );
                  -     * 
                  - * - * @param book the WorkBook from which the format should be retrieved - * @param xfnum the ID of the format - */ - public FormatHandle(io.starter.formats.XLS.WorkBook book, int xfnum) { - wkbook = book; - if (xfnum > -1 && xfnum < wkbook.getNumXfs()) { - myxf = wkbook.getXf(xfnum); - xfe = myxf.getIdx(); - } - if (myxf == null) { // add new xf if necessary - myxf = duplicateXf(null); - } - myxf.getFont(); // will set to default (0th) font if not already set - } - - /** Constructs a FormatHandle for the given format ID and WorkBook. - * This is useful for creating a FormatHandle with the same parameters as - * a cell that does not refer to the cell. For example: - *
                  -     * CellHandle cell = <get cell here>;
                  -     * FormatHandle format = new FormatHandle(
                  -     *         cell.getWorkBook(), cell.getFormatId() );
                  -     * 
                  - * - * @param book - * the WorkBook from which the format should be retrieved - * @param xfnum - * the ID of the format public FormatHandle(WorkBook book, int - * xfnum ,int x) { this(book.getWorkBook(),xfnum); wbh = book; } - */ - - /** - * Constructs a FormatHandle for the given format index and WorkBook. - * This is not part of the public API and should not be called. - */ - /* - * This constructor is just used from XML parsing, due to some dedupe - * errors. possibly errors in .xlsx too? - */ - protected FormatHandle(io.starter.OpenXLS.WorkBook book, int xfnum, - boolean dedupe) { - this(book, xfnum); - writeImmediate = dedupe; - } - - /** - * Constructs a dummy FormatHandle for the given conditional format. This - * is not part of the public API and should not be called. - *

                  - * This unique flavor of FormatHandle is only used to display the formatting - * values of a Conditional format record. - *

                  - * Creates a dummy Xf to store values, otherwise has no effect on the - * WorkBook record stream which are manipulated through CellHandle. - * - * @param book containing the conditional formats - * @param the index to the conditional format in the book collection - */ - protected FormatHandle(Condfmt cx, io.starter.OpenXLS.WorkBook book, - int xfnum, CellHandle c) { - cx.setFormatHandle(this); - xfe = xfnum; - wbh = book; - wkbook = book.getWorkBook(); - if (c == null) { - // ok, this is a horrible hack, as its only correct if the top left - // cell of the range has the same background format - // as the cell a user is hitting. Lame, but i've been handed this at - // the last moment and am patching things. weak effort guys. - try { - int[] rc = cx.getEncompassingRange(); - c = new CellHandle(cx.getSheet().getCell(rc[0], rc[1]), - book); - } catch (Exception e) { - } - } - myxf = duplicateXf(book.getWorkBook().getXf(c.getFormatId())); - - // set the format from the cf - List lx = cx.getRules(); - Iterator itx = lx.iterator(); - while (itx.hasNext()) { - Cf format = (Cf) itx.next(); - this.updateFromCF(format, book); - } - } - - /** - * updates this format handle via a Cf rule - * - * @param cf Cf rule - * @param book workbook - */ - protected void updateFromCF(Cf cf, io.starter.OpenXLS.WorkBook book) { - // border colors - Color[] clr = cf.getBorderColors(); - if (clr != null) - setBorderColors(clr); - - // line style - int[] xs = cf.getBorderStyles(); - if (xs != null) - this.setBorderLineStyle(xs); - - /* - * // cf.getBorderSizes() int[] b = cf.getBorderSizes(); if(b!=null) - * setBorderLineStyle(b); - */ - // cf.getFont() - Font f = cf.getFont(); - if (f != null) - setFont(f); - else - this.setFontHeight(180); // why???????? - - if (cf.getFontItalic()) - this.setItalic(true); - - if (cf.getFontStriken()) - this.setStricken(true); - - int fsup = cf.getFontEscapement(); - // super/sub (0 = none, 1 = super, 2 = sub) - if (fsup > -1) - this.setScript(fsup); - - // handle underlines - int us = cf.getFontUnderlineStyle(); - - if (us > -1) { - this.setUnderlineStyle(us); - this.setUnderlined(true); - } - - // number cf - if (cf.getFormatPattern() != null) - setFormatPattern(cf.getFormatPattern()); - - if (cf.getFill() != null) { - this.setFill(cf.getFill()); - } else { - int fill = cf.getPatternFillStyle(); // Now -1 is a valid entry: - if (fill > -1) { - /* If the fill style is solid: When solid is specified, the - foreground color (fgColor) is the only color rendered, - even when a background color (bgColor) is also - specified. */ - int bg = cf.getPatternFillColorBack(); - int fg = cf.getPatternFillColor(); - this.setFill(fill, fg, bg); - } else { - int fg = cf.getForegroundColor(); - if (fg > -1) - setForegroundColor(fg); - } - } - } - - /** - * Creates a FormatHandle for the given cell. This is not part of the - * public API and should not be called. Customers should use - * {@link CellHandle#getFormatHandle()} instead. - */ - public FormatHandle(CellHandle c) { - wkbook = c.getCell().getWorkBook(); - if (c.getCell().getXfRec() != null) { - myxf = c.getCell().getXfRec(); - xfe = myxf.getIdx(); // update the pointer - 20071010 KSC - } else { // ?? create new - // 20090512 KSC: Shigeo NPE error formaterror646694, create new - // rather than outputting warning - // Logger.logWarn("No XF for cell " + c.toString()); - myxf = wkbook.getXf(c.getCell().getIxfe()); - xfe = myxf.getIdx(); - } - // 20101201 KSC: only add to cache when adding xf's addToCache(); - } - - /** - * overrides the equals method to perform equality based on format - * properties ------------------------------------------------------------ - * - * @param Object another - the FormatHandle to compare with this FormatHandle - * @return true if this FormatHandle equals another - */ - public boolean equals(Object another) { - return another.toString().equals(toString()); - } - - /** - * Locks the cell attached to this FormatHandle for editing (makes - * read-only) lock cell and make read-only - * - * @param boolean locked - true if cells should be locked for this - * FormatHandle - */ - public void setLocked(boolean locked) { - Xf xf = cloneXf(myxf); - xf.setLocked(locked); - updateXf(xf); - } - - /** - * sets the cell attached to this FormatHandle to hide or show formula - * strings; - * - * @param boolean b- true if formulas should be hidden for this FormatHandle - */ - public void setFormulaHidden(boolean b) { - Xf xf = cloneXf(myxf); - xf.setFormulaHidden(b); - updateXf(xf); - } - - /** - * returns whether this FormatHandle is set to hide formula strings - * - * @return true if the formula strings are hidden, false otherwise - */ - public boolean isFormulaHidden() { - return myxf.isFormulaHidden(); - } - - /** - * returns whether this Format Handle specifies that cells are locked for - * changing - * - * @return true if cells are locked - */ - public boolean isLocked() { - return myxf.isLocked(); - } - - /** - * provides a mapping between Excel formats and Java formats
                  - * see: http://java.sun.com/docs/books/tutorial/i18n/format - * /decimalFormat.html - *

                  - * Note there are slight Excel-specific differences in the format strings - * returned. Several numeric and currency formats in excel have different - * formatting for postive and negative numbers. In these cases, the java - * format string is split by semicolons and may contain text [Red] which is - * to specify the negative number should be displayed in red. Remove this - * from the string before passing into the Format class; - * - *

                  -     *         G  Era designator  Text  AD
                  -     *         y  Year  Year  1996; 96
                  -     *         M  Month in year  Month  July; Jul; 07
                  -     *         w  Week in year  Number  27
                  -     *         W  Week in month  Number  2
                  -     *         D  Day in year  Number  189
                  -     *         d  Day in month  Number  10
                  -     *         F  Day of week in month  Number  2
                  -     *         E  Day in week  Text  Tuesday; Tue
                  -     *         a  Am/pm marker  Text  PM
                  -     *         H  Hour in day (0-23)  Number  0
                  -     *         k  Hour in day (1-24)  Number  24
                  -     *         K  Hour in am/pm (0-11)  Number  0
                  -     *         h  Hour in am/pm (1-12)  Number  12
                  -     *         m  Minute in hour  Number  30
                  -     *         s  Second in minute  Number  55
                  -     *         S  Millisecond  Number  978
                  -     *         z  Time zone  General time zone  Pacific Standard Time; PST; GMT-08:00
                  -     *         Z  Time zone  RFC 822 time zone  -0800
                  -     * 
                  - * - * @return String the formatting pattern for the cell - */ - public String getJavaFormatString() { - String pat = getFormatPattern(); - if (pat == null) return null; - - // toLowerCase is a simplistic way to implement the case insensitivity - // of the pattern tokens. It could cause issues with string literals. - Object patty = convertFormatString(pat.toLowerCase()); - if (patty != null) - patty = StringTool.qualifyPatternString(patty.toString()); - if (myxf.isDatePattern()) { - if (patty != null) - return (String) patty; - else - return "M/d/yy h:mm"; - } - if (patty != null) - return (String) patty; - /* - * If we reached here, we don't have a mapping for this particular - * format. Send a warning to the system then make sure the pattern we - * are sending back is valid. Many excel patterns have 4 patterns - * separated by semicolons. We only can pass 2 into the formatter - * (positive and negative). This usually works out to be the first two - * patterns in the string. - */ - pat = StringTool.qualifyPatternString(pat); - int firstParens = pat.indexOf(";"); - if (firstParens != -1) { - int secondParens = pat.indexOf(";", firstParens + 1); - if (secondParens != -1) { - pat = pat.substring(0, secondParens); - } else { // yet another hackaround -jm - pat = pat.substring(firstParens + 2, pat.length() - 1); - } - } - return pat; - } - - /** - * converts an Excel-style format string to a Java Format string. - * - * @param String pattern - Excel Format String - * @return String that can be used with the Java Format classes. - * @see getJavaFormatString - */ - public static String convertFormatString(String pattern) { - String ret = numericFormatMap.get(pattern); - if (ret != null) - return ret; - ret = currencyFormatMap.get(pattern); - if (ret != null) - return ret; - ret = dateFormatMap.get(pattern); - return ret; - } - - /* - * FIXME: Border Issues (marker) - * - * The methods to set individual border line styles do not follow a - * consistent naming convention with the rest of the border methods. - * - * There are no methods for getting or setting the inside borders. I don't - * know whether this is a problem or a design choice. It depends on whether - * the inside borders are stored in the format record or as separate cell - * formats. - * - * There are no methods to get the diagonal borders. There is a method to - * set the diagonal border line style, but it affects both diagonals. - */ - - /** - * sets the border color for all borders (top, left, bottom and right) from - * a Color array - *

                  - * NOTE: this setting will affect every cell which refers to this - * FormatHandle - * - * @param java .awt.Color array - 4-element array of desired border colors - * [T, L, B, R] - */ - public void setBorderColors(Color[] bordercolors) { - Xf xf = cloneXf(myxf); - if (bordercolors[0] != null) - xf.setTopBorderColor(getColorInt(bordercolors[0])); - if (bordercolors[1] != null) - xf.setLeftBorderColor(getColorInt(bordercolors[1])); - if (bordercolors[2] != null) - xf.setBottomBorderColor(getColorInt(bordercolors[2])); - if (bordercolors[3] != null) - xf.setRightBorderColor(getColorInt(bordercolors[3])); - updateXf(xf); - } - - /** - * remove borders for this format - */ - public void removeBorders() { - Xf xf = cloneXf(myxf); - xf.removeBorders(); - updateXf(xf); - } - - /** - * sets the border color for all borders (top, left, bottom and right) from - * an int array containing color constants - * - *

                  - * NOTE: this setting will affect every cell which refers to this - * FormatHandle - * - * @param int[] bordercolors - 4-element array of desired border color - * constants [T, L, B, R] - * @see FormatHandle.COLOR_* constants - */ - public void setBorderColors(int[] bordercolors) { - Xf xf = cloneXf(myxf); - xf.setTopBorderColor(bordercolors[0]); - xf.setLeftBorderColor(bordercolors[1]); - xf.setBottomBorderColor(bordercolors[2]); - xf.setRightBorderColor(bordercolors[3]); - updateXf(xf); - - } - - /** - * set the border color for all borders (top, left, bottom, and right) to - * one color via color constant - * - *

                  - * NOTE: this setting will affect every cell which refers to this - * FormatHandle - * - * @param int x - color constant which represents the color to set all - * border sides - * @see FormatHandle.COLOR_* constants - */ - public void setBorderColor(int x) { - Xf xf = cloneXf(myxf); - xf.setRightBorderColor((short) x); - xf.setLeftBorderColor((short) x); - xf.setTopBorderColor((short) x); - xf.setBottomBorderColor((short) x); - updateXf(xf); - - } - - /** - * set the border color for all borders (top, left, bottom, and right) to - * one java.awt.Color - *

                  - * NOTE: this setting will affect every cell which refers to this - * FormatHandle - * - * @param Color col - color to set all border sides - */ - public void setBorderColor(Color col) { - Xf xf = cloneXf(myxf); - short x = (short) getColorInt(col); - xf.setRightBorderColor(x); - xf.setLeftBorderColor(x); - xf.setTopBorderColor(x); - xf.setBottomBorderColor(x); - updateXf(xf); - - } - - /** - * Set the top border color - * - * @param color constant - */ - public void setBorderRightColor(int x) { - Xf xf = cloneXf(myxf); - xf.setRightBorderColor((short) x); - updateXf(xf); - } - - /** - * Set the top border color - * - * @param color constant - */ - public void setBorderRightColor(Color x) { - Xf xf = cloneXf(myxf); - xf.setRightBorderColor((short) getColorInt(x)); - updateXf(xf); - } - - /** - * Get the Right border color - * - * @return color constant - */ - public Color getBorderRightColor() { - if (myxf.getRightBorderLineStyle() == 0) - return null; - int x = myxf.getRightBorderColor(); - if (x < this.getWorkBook().colorTable.length) - return this.getWorkBook().getColorTable()[x]; - else - return this.getWorkBook().getColorTable()[0]; // black i'm afraid - } - - /** - * Set the top border color - * - * @param color constant - */ - public void setBorderLeftColor(int x) { - Xf xf = cloneXf(myxf); - xf.setLeftBorderColor((short) x); - updateXf(xf); - } - - /** - * Set the top border color - * - * @param color constant - */ - public void setBorderLeftColor(Color x) { - Xf xf = cloneXf(myxf); - xf.setLeftBorderColor((short) getColorInt(x)); - updateXf(xf); - } - - /** - * returns Border Colors of Cell ie: top, left, bottom, right - *

                  - * returns null or 1 color for each of 4 sides - *

                  - * 1,1,1,1 represents a border all around the cell 1,1,0,0 represents on the - * top left edge of the cell - * - * @return int array representing Cell borders - */ - public Color[] getBorderColors() { - Color[] colors = new Color[4]; - colors[0] = getBorderTopColor(); - colors[1] = getBorderLeftColor(); - colors[2] = getBorderBottomColor(); - colors[3] = getBorderRightColor(); - return colors; - } - - /** - * Get the Left border color - * - * @return color constant - */ - public Color getBorderLeftColor() { - if (myxf.getLeftBorderLineStyle() == 0) - return null; - return this.getWorkBook().getColorTable()[myxf.getLeftBorderColor()]; - } - - /** - * Set the top border color - * - * @param color constant - */ - public void setBorderTopColor(int x) { - Xf xf = cloneXf(myxf); - xf.setTopBorderColor(x); - updateXf(xf); - } - - /** - * Get the Top border color - * - * @return color constant - */ - public Color getBorderTopColor() { - if (myxf.getTopBorderLineStyle() == 0) - return null; - int xt = myxf.getTopBorderColor(); - if (xt > this.getWorkBook().getColorTable().length) // guards - xt = 0; - return this.getWorkBook().getColorTable()[xt]; - } - - /** - * Set the top border color - * - * @param color constant - */ - public void setBorderTopColor(Color x) { - Xf xf = cloneXf(myxf); - xf.setTopBorderColor(getColorInt(x)); - updateXf(xf); - } - - /** - * Set the top border color - * - * @param color constant - */ - public void setBorderBottomColor(int x) { - Xf xf = cloneXf(myxf); - xf.setBottomBorderColor(x); - updateXf(xf); - } - - /** - * Set the top border color - * - * @param color constant - */ - public void setBorderBottomColor(Color x) { - Xf xf = cloneXf(myxf); - xf.setBottomBorderColor(getColorInt(x)); - updateXf(xf); - } - - /** - * Returns true if the value should be red due to a combination of a format - * pattern and a negative number - * - * @return - */ - public boolean isRedWhenNegative() { - String pattern = myxf.getFormatPattern(); - return pattern.indexOf("Red") > -1; - } - - /** - * Get the Right border color - * - * @return color constant - */ - public Color getBorderBottomColor() { - if (myxf.getBottomBorderLineStyle() == 0) - return null; - int x = myxf.getBottomBorderColor(); - if (x < this.getWorkBook().getColorTable().length) - return this.getWorkBook().getColorTable()[x]; - else - return this.getWorkBook().getColorTable()[0]; // black i'm afraid - - } - - /** - * Set the border line style - * - * @param line style constant - */ - public void setTopBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setTopBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * Get the border line style - * - * @return line style constant - */ - public int getTopBorderLineStyle() { - return myxf.getTopBorderLineStyle(); - } - - /** - * Set the border line style - * - * @param line style constant - */ - public void setBottomBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setBottomBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * Get the border line style - * - * @return line style constant - */ - public int getBottomBorderLineStyle() { - return myxf.getBottomBorderLineStyle(); - } - - /** - * Set the border line style - * - * @param line style constant - */ - public void setLeftBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setLeftBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * Get the border line style - * - * @return line style constant - */ - public int getLeftBorderLineStyle() { - return myxf.getLeftBorderLineStyle(); - } - - /** - * Set the border line style - * - * @param line style constant - */ - public void setRightBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setRightBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * Get the border line style - * - * @return line style constant - */ - public int getRightBorderLineStyle() { - return myxf.getRightBorderLineStyle(); - } - - /** - * Sets the border line style using static BORDER_ shorts within - * FormatHandle - * - * @param line style constant - */ - public void setBorderLineStyle(int x) { - Xf xf = cloneXf(myxf); - xf.setBorderLineStyle((short) x); - updateXf(xf); - } - - /** - * set border line styles via array of ints representing border styles - * order= left, right, top, bottom, [diagonal] - * - * @param b int[] - */ - public void setBorderLineStyle(int[] b) { - myxf.setAllBorderLineStyles(b); - } - - /** - * Set the border line style - * - * @param line style constant - */ - public void setBorderLineStyle(short x) { - Xf xf = cloneXf(myxf); - xf.setBorderLineStyle(x); - updateXf(xf); - } - - /** - * return the 5 border lines styles (l, r, t, b, diag) - * - * @return - */ - public int[] getAllBorderLineStyles() { - int[] ret = new int[5]; - ret[0] = myxf.getLeftBorderLineStyle(); - ret[1] = myxf.getRightBorderLineStyle(); - ret[2] = myxf.getTopBorderLineStyle(); - ret[3] = myxf.getBottomBorderLineStyle(); - ret[4] = myxf.getDiagBorderLineStyle(); - return ret; - } - - /** - * return the 5 border line colors (l, r, t, b, diag) - * - * @return - */ - public int[] getAllBorderColors() { - int[] ret = new int[5]; - ret[0] = myxf.getLeftBorderColor(); - ret[1] = myxf.getRightBorderColor(); - ret[2] = myxf.getTopBorderColor(); - ret[3] = myxf.getBottomBorderColor(); - ret[4] = myxf.getDiagBorderColor(); - return ret; - } - - /** - * Set the border line style - * - * @param line style constant - */ - public void setBorderDiagonal(int x) { - Xf xf = cloneXf(myxf); - xf.setBorderDiag(x); - updateXf(xf); - } - - /** - * Set a column handle on this format handle, so all changes applied to this - * format will be applied to the entire column - * - * @param c - */ - public void setColHandle(ColHandle c) { - mycol = c; - } - - /** - * Set a row handle on this format handle, so all changes applied to this - * format will be applied to the entire row - * - * @param c - */ - public void setRowHandle(RowHandle c) { - myrow = c; - } - - /** - * Create a copy of this FormatHandle with its own Xf - * - * @return the copied FormatHandle - */ - public Object clone() { - FormatHandle ret = null; - if (wbh == null) { // who knew??? - wkbook = myxf.getWorkBook(); // Changed to myxf since myfont is no - // longer - ret = new FormatHandle(); - ret.myxf = myxf; - ret.xfe = myxf.getIdx(); - ret.wkbook = wkbook; - } else { - ret = new FormatHandle(wbh, myxf); // no need to duplicate it - just - // use all formatting of - // original xf - } - - return ret; - } - - public String toString() { - return myxf.toString(); - } - - public FormatHandle(io.starter.OpenXLS.WorkBook book, String fontname, - int fontstyle, int fontsize) { - this(book); - setFont(fontname, fontstyle, fontsize); - } - - /** - * Jan 27, 2011 - * - * @param workBook - * @param i - */ - public FormatHandle(io.starter.OpenXLS.WorkBook workBook, int i) { - this(workBook.getWorkBook(), i); - wbh = workBook; - } - - /** - * /** Set the weight of the font in 1/20 point units 100-1000 range. 400 is - * normal, 700 is bold. - * - * @param wt - */ - public void setFontWeight(int wt) { - Font f = cloneFont(myxf.getFont()); - f.setFontWeight(wt); - updateFont(f); - } - - /** - * Get the font weight the weight of the font is in 1/20 point units - * - * @return - */ - public int getFontWeight() { - return myxf.getFont().getFontWeight(); - } - - /** - * Set the Font for this Format. - *

                  - * As adding a new Font and format increases the file size, try using this - * once for each distinct font used in the file, then use 'setFormatId' to - * share this font with other Cells. - *

                  - * Roughly matches the functionality of the java.awt.Font class. Currently - * the style parameter is only useful for bold/normal weights. Italics, - * underlines, etc must be modified elsewhere. - *

                  - * Note that in order to maintain java.awt.Font compatibility for - * bold/normal styles, defaults for weight/style have been mapped to 0 = - * normal (excel 200 weight) and 1 = bold (excel 700 weight) - * - * @param String font name - * @param int font style - * @param int font size - */ - public void setFont(Font f) { - setXFToFont(f); - } - - /** - * Set new font to XF, handling duplication and caching ... - * - * @param Font f - */ - private void setXFToFont(Font f) { - int fti = addFontIfNecessary(f); - if (myxf == null) { // shouldn't!! - myxf = duplicateXf(null); - myxf.setFont(fti); - } else if (myxf.getIfnt() != fti) { // if not using font already, - // duplicate xf and set to new font - Xf xf = cloneXf(myxf); - xf.setFont(fti); - updateXf(xf); - } - - } - - /** - * Sets this format handle to a font - * - * @param fn font name e.g. 'Arial' - * @param stl font style either Font.PLAIN or Font.BOLD - * @param sz font size or height in 1/20 point units - */ - public void setFont(String fn, int stl, double sz) { - sz *= 20; - if (stl == 0) - stl = 200; - if (stl == 1) - stl = 700; - Font f = new Font(fn, stl, (int) sz); - setXFToFont(f); - } - - /** - * add font to record streamer if cant find exact font already in there - * - * @param f - * @return - */ - private int addFontIfNecessary(Font f) { - if (wkbook == null) { - Logger.logErr("AddFontIfNecessary: workbook is null"); - return -1; - } - int fti = wkbook.getFontIdx(f); - // don't use the built-ins. - if (fti == 3) - fti = 0; // use initial default font instead of last ... - if (fti == -1) { // font doesn't exist yet, add to streamer - f.setIdx(-1); // flag to insert - fti = wkbook.insertFont(f) + 1; - } else - f.setIdx(fti); - return fti; - } - - /** - * adds a font to the global font store only if exact font is not already - * present - * - * @param f Font - * @param bk WorkBookHandle - */ - public static int addFont(Font f, WorkBookHandle bk) { - if (bk == null) { - Logger.logErr("addFont: workbook is null"); - } - int fti = bk.getWorkBook().getFontIdx(f); - // if (fti > 3) {// don't use the built-ins. - if (fti == 3) - fti = 0; // use initial default font instead of last ... 20070827 - // KSC - if (fti == -1) { // font doesn't exist yet, add to streamer - f.setIdx(-1); // flag to insert - fti = bk.getWorkBook().insertFont(f) + 1; - } else - f.setIdx(fti); - return fti; - } - - /** - * Adds a font internally to the workbook - * - * @param f - * - * private void addFont(Font f) { - * - * } - */ - - /** - * Apply this Format to a Range of Cells - * - * @param CellRange to apply the format to - */ - public void addCellRange(CellRange cr) { - CellHandle[] crcells = cr.getCells(); - for (int t = 0; t < crcells.length; t++) { - addCell(crcells[t]); - } - } - - /** - * Apply this Format to a Range of Cells - * - * @param CellHandle array to apply the format to - */ - public void addCellArray(CellHandle[] crcells) { - for (int t = 0; t < crcells.length; t++) { - addCell(crcells[t]); - } - } - - /** - * add a Cell to this FormatHandle thus applying the Format to the Cell - * - * @param CellHandle to apply the format to - */ - public void addCell(CellHandle c) { - c.setFormatHandle(this); - } - - /** - * Add a List of Cells to this FormatHandle - * - * @param cx - */ - public void addCells(List cx) { - Iterator itx = cx.iterator(); - while (itx.hasNext()) { - addCell((BiffRec) itx.next()); - mycells.add(itx.next()); - } - } - - void addCell(BiffRec c) { - if (myxf != null) { - c.setXFRecord(myxf.getIdx()); - } else { - Logger.logWarn("FormatHandle.addCell() - You MUST call setFont() to initialize the FormatHandle's font before adding Cells."); - } - mycells.add(c); - } - - /** - * Applies the format to a cell without establishing a relationship. The - * format represented by this FormatHandle will be applied to - * the cell but it will not be updated with any future changes. If you want - * that behavior use {@link #addCell(CellHandle) addCell} instead. - */ - public void stamp(CellHandle cell) { - cell.setFormatId(xfe); - } - - /** - * Applies the format to a cell range without establishing a relationship. - * The format represented by this FormatHandle will be applied - * to the cells but they will not be updated with any future changes. If you - * want that behavior use {@link #addCell(CellHandle) addCell} instead. - */ - public void stamp(CellRange range) { - try { - range.setFormatID(xfe); - } catch (Exception e) { - // This can't actually happen - } - } - - /** - * set the Background Pattern for this Format - * - * @param int Excel color constant - */ - public void setBackgroundPattern(int t) { - Xf xf = cloneXf(myxf); - // 20080103 KSC: handle solid (=filled) backgrounds, in which Excel - // switches fg and bg colors (!!!) - if (t != FormatConstants.PATTERN_FILLED) - xf.setPattern(t); - else { - int bg = xf.getBackgroundColor(); - xf.setBackgroundSolid(); - xf.setForeColor(bg, null); - } - updateXf(xf); - } - - /** - * returns whether this Format is formatted as a Date - * - * @return boolean true if this Format is formatted as a Date - */ - public boolean isDate() { - if (myxf == null) - return false; - return myxf.isDatePattern(); - } - - /** - * returns whether this Format is formatted as a Currency - * - * @return boolean true if this Format is formatted as a currency - */ - public boolean isCurrency() { - if (myxf == null) - return false; - return myxf.isCurrencyPattern(); - } - - /** - * set the underline style for this font - * - * @param int u underline style one of the Font.STYLE_UNDERLINE constants - */ - public void setUnderlineStyle(int u) { - Font f = cloneFont(myxf.getFont()); - f.setUnderlineStyle((byte) u); - updateFont(f); - } - - /** - * super/sub (0 = none, 1 = super, 2 = sub) - * - * @param int script type for Format Font - */ - public void setScript(int ss) { - if (ss > 2) - ss = 2; // deal with invalid numbers - if (ss < 0) - ss = 0; // deal with invalid numbers - myxf.getFont().setScript(ss); - } - - /** - * set the Font Color for this Format via indexed color constant - * - * @param int Excel color constant - */ - public void setFontColor(int t) { - Font f = cloneFont(myxf.getFont()); - f.setColor(t); - updateFont(f); - } - - /** - * set the Font Color for this Format - * - * @param AWT Color color constant - */ - public void setFontColor(Color colr) { - Font f = cloneFont(myxf.getFont()); - f.setColor(colr); - updateFont(f); - } - - /** - * sets the Font color for this Format via web Hex String - * - * @param clr - */ - public void setFontColor(String clr) { - Font f = cloneFont(myxf.getFont()); - f.setColor(clr); - updateFont(f); - } - - /** - * Get the Font foreground (text) color as a java.awt.Color - * - * @return - */ - public Color getFontColor() { - return myxf.getFont().getColorAsColor(); - } - - - public String getFontColorAsHex() { - return myxf.getFont().getColorAsHex(); - } - - - /** - * set the Foreground Color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @param int Excel color constant - */ - public void setForegroundColor(int t) { - Xf xf = cloneXf(myxf); - xf.setForeColor(t, null); - updateXf(xf); - } - - /** - * set the foreground Color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @param AWT Color constant - */ - public void setForegroundColor(Color colr) { - Xf xf = cloneXf(myxf); - int clrz = getColorInt(colr); - xf.setForeColor(clrz, colr); - updateXf(xf); - } - - /** - * set the background color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @param t Excel color constant - */ - public void setBackgroundColor(int t) { - Xf xf = cloneXf(myxf); - if (xf.getFillPattern() == Xf.PATTERN_SOLID) - xf.setForeColor(t, null); - else - xf.setBackColor(t, null); - - updateXf(xf); - } - - /** - * set the Cell Background Color for this Format - *

                  - * NOTE: Foreground color = the CELL BACKGROUND color color for all patterns - * and Background color= the PATTERN color for all patterns != Solid For - * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background - * Color=64 (white). - * - * @param awt Color color constant - */ - public void setCellBackgroundColor(Color colr) { - int clrz = getColorInt(colr); - setCellBackgroundColor(clrz); - - } - - /** - * makes the cell a solid pattern background if no pattern was already - * present NOTE: Foreground color = the CELL BACKGROUND color color for all - * patterns and Background color= the PATTERN color for all patterns != - * Solid For PATTERN_SOLID, Foreground color=CELL BACKGROUND color and - * Background Color=64 (white). - * - * @param int Excel color constant - */ - public void setCellBackgroundColor(int t) { - Xf xf = cloneXf(myxf); - - if (xf.getFillPattern() == 0) - xf.setBackgroundSolid(); - - if (xf.getFillPattern() == Xf.PATTERN_SOLID) - xf.setForeColor(t, null); - else - xf.setBackColor(t, null); - updateXf(xf); - } - - /** - * sets this fill pattern from an existing OOXML (2007v) fill element - * - * @param f - */ - protected void setFill(Fill f) { - Xf xf = cloneXf(myxf); - xf.setFill(f); - updateXf(xf); - } - - /** - * sets the fill for this format handle if fill==Xf.PATTERN_SOLID then fg is - * the PATTERN color i.e the CELL BG COLOR - * - * @param fillpattern - * @param fg - * @param bg - */ - public void setFill(int fillpattern, int fg, int bg) { - Xf xf = cloneXf(myxf); - - xf.setPattern(fillpattern); - - /** - * If the fill style is solid: When solid is specified, the foreground - * color (fgColor) is the only color rendered, even when a background - * color (bgColor) is also specified. - */ - if (xf.getFillPattern() == Xf.PATTERN_SOLID) { // is reversed - xf.setForeColor(bg, null); - xf.setBackColor(64, null); - } else { - /** - * or cell fills with patterns specified, then the cell fill color - * is specified by the bgColor element - */ - xf.setForeColor(fg, null); - xf.setBackColor(bg, null); - } - updateXf(xf); - } - - /** - * Get the Pattern Background Color for this Format Pattern - * - * @return the Excel color constant - */ - public int getBackgroundColor() { - return myxf.getBackgroundColor(); - } - - /** - * get the Pattern Background Color for this Format Pattern as a hex string - * - * @return Hex Color String - */ - public String getBackgroundColorAsHex() { - return myxf.getBackgroundColorHEX(); - } - - /** - * get the Pattern Background Color for this Format Pattern as an awt.Color - * - * @return background Color - */ - public java.awt.Color getBackgroundColorAsColor() { - return HexStringToColor(this.getBackgroundColorAsHex()); - } - - - /** - * returns the foreground color setting regardless of format pattern (which - * can switch fg and bg) - * - * @return - */ - public int getTrueForegroundColor() { - return myxf.getForegroundColor(); // 20080814 KSC: getForegroundColor() does the swapping so use base method - } - - /** - * get the Pattern Background Color for this Formatted Cell - *

                  - * This method handles display of conditional formats for the cell - *

                  - * checks for conditional format, then applies it if conditions are true. - * - * @return the Excel color constant - */ - public int getCellBackgroundColor() { - int fp = getFillPattern(); - - if (fp == Xf.PATTERN_SOLID) - return myxf.getForegroundColor(); // this.getForegroundColor() does - // the swapping so use base - // method - else - return myxf.getBackgroundColor(); - } - - /** - * get the Pattern Background Color for this Format as a Hex Color String - * - * @return Hex Color String - */ - public String getCellBackgroundColorAsHex() { - int fp = getFillPattern(); - - if (fp == Xf.PATTERN_SOLID) - return myxf.getForegroundColorHEX(); // this.getForegroundColor() does - else - return myxf.getBackgroundColorHEX(); - } - - /** - * get the Pattern Background Color for this Format as an awt.Color - * - * @return cell background color - */ - public java.awt.Color getCellBackgroundColorAsColor() { - return HexStringToColor(this.getCellBackgroundColorAsHex()); - } - - /** - * get the Background Color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @return the Excel color constant - */ - public int getForegroundColor() { - // if it's SOLID pattern, fg/bg are swapped - if (getFillPattern() == Xf.PATTERN_SOLID) - return myxf.getBackgroundColor(); - - return myxf.getForegroundColor(); - } - - /** - * get the Background Color for this Format as a Hex Color String NOTE: - * Foreground color = the CELL BACKGROUND color color for all patterns and - * Background color= the PATTERN color for all patterns != Solid For - * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background - * Color=64 (white). - * - * @return Hex Color String - */ - public String getForegroundColorAsHex() { - if (getFillPattern() == Xf.PATTERN_SOLID) // if it's SOLID pattern, - return myxf.getBackgroundColorHEX(); - - return myxf.getForegroundColorHEX(); - } - - /** - * get the Background Color for this Format as a Color NOTE: - * Foreground color = the CELL BACKGROUND color color for all patterns and - * Background color= the PATTERN color for all patterns != Solid For - * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background - * Color=64 (white). - * - * @return Hex Color String - */ - public Color getForegroundColorAsColor() { - return HexStringToColor(this.getForegroundColorAsHex()); - } - - /** - * set the Background Color for this Format NOTE: Foreground color = the - * CELL BACKGROUND color color for all patterns and Background color= the - * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground - * color=CELL BACKGROUND color and Background Color=64 (white). - * - * @param awt Color color constant - */ - public void setBackgroundColor(Color colr) { - Xf xf = cloneXf(myxf); - int clrz = getColorInt(colr); - xf.setBackColor(clrz, colr); - updateXf(xf); - } - - /** - * Set the format handle to use standard bold text - * - * @param boolean isBold - */ - public void setBold(boolean isBold) { - Font f = cloneFont(myxf.getFont()); - f.setBold(isBold); - updateFont(f); - } - - /** - * Get if this format is bold or not - * - * @return boolean whether the cell font format is bold - */ - public boolean getIsBold() { - return myxf.getFont().getIsBold(); - } - - /** - * Return an int representing the underline style - *

                  - * These map to the STYLE_UNDERLINE static integers * - * - * @return int underline style - */ - public int getUnderlineStyle() { - return myxf.getFont().getUnderlineStyle(); - } - - /** - * Get the font height in points - * - * @return font height - */ - public double getFontHeightInPoints() { - return getFont().getFontHeightInPoints(); - } - - /** - * Returns the Font's height in 1/20th point increment - * - * @return font height - */ - public int getFontHeight() { - return getFont().getFontHeight(); - } - - /** - * Set the Font's height in 1/20th point increment - * - * @param new font height - */ - public void setFontHeight(int fontHeight) { - Font f = cloneFont(myxf.getFont()); - f.setFontHeight(fontHeight); - updateFont(f); - } - - /** - * Returns the Font's name - * - * @return font name - */ - public String getFontName() { - return getFont().getFontName(); - } - - /** - * Set the Font's name - *

                  - * To be valid, this font name must be available on the client system. - * - * @param font name - */ - public void setFontName(String fontName) { - Font f = cloneFont(myxf.getFont()); - f.setFontName(fontName); - updateFont(f); - } - - /** - * Determine if the format handle refers to a font stricken out - * - * @return boolean representing if the FormatHandle is striking out a cell. - */ - public boolean getStricken() { - if (myxf.getFont() == null) - return false; - return myxf.getFont().getStricken(); - } - - /** - * Set if the format handle is stricken out - * - * @param isStricken boolean representing if the formatted cell should be stricken - * out. - */ - public void setStricken(boolean isStricken) { - Font f = cloneFont(myxf.getFont()); - f.setStricken(isStricken); - updateFont(f); - } - - /** - * Get if the font is italic - * - * @return boolean representing if the formatted cell is italic. - */ - public boolean getItalic() { - if (myxf.getFont() == null) - return false; - return myxf.getFont().getItalic(); - } - - /** - * Set if the font is italic - * - * @param isItalic boolean representing if the formatted cell should be italic. - */ - public void setItalic(boolean isItalic) { - Font f = cloneFont(myxf.getFont()); - f.setItalic(isItalic); - updateFont(f); - } - - /** - * Get if the font is underlined - * - * @return boolean representing if the formatted cell is underlined. - */ - public boolean getUnderlined() { - if (myxf.getFont() == null) - return underlined; - return myxf.getFont().getUnderlined(); - } - - /** - * Set underline attribute on the font - * - * @param isUnderlined boolean representing if the formatted cell should be - * underlined - */ - public void setUnderlined(boolean isUnderlined) { - Font f = cloneFont(myxf.getFont()); - f.setUnderlined(isUnderlined); - updateFont(f); - - } - - /** - * Get if the font is bold - * - * @return boolean representing if the formatted cell is bold - */ - public boolean getBold() { - return myxf.getFont().getBold(); - } - - /** - * returns the existing font record for this Format - *

                  - * Font is an internal record and should not be accessed by end users - * - * @return the XLS Font record associated with this Format - */ - public io.starter.formats.XLS.Font getFont() { - // should this be a protected method? - if (myxf != null) // shouldn't! - return myxf.getFont(); - return null; - } - - /** - * Sets the number format pattern for this format. All Excel built-in number - * formats are supported. Custom formats will not be applied by OpenXLS - * (e.g. {@link CellHandle#getFormattedStringVal}) but they will be written - * correctly to the output file. For more information on number format - * patterns see Microsoft - * KB264372. - * - * @param pat the Excel number format pattern to apply - * @see FormatConstantsImpl#getBuiltinFormats - */ - public void setFormatPattern(String pat) { - Xf xf = cloneXf(myxf); - xf.setFormatPattern(pat); - updateXf(xf); - } - - public void setPattern(int pat) { - Xf xf = cloneXf(myxf); - xf.setPattern(pat); - updateXf(xf); - } - - /** - * The format ID allows setting of the format for a Cell without adding it - * to the Format's Cell Collection. - *

                  - * Use to decrease memory requirements when dealing with large collections - * of cells. - *

                  - * Usage: - *

                  - * mycell.setFormatId(myformat.getFormatId()); - * - * @return the format ID for this Format - */ - public int getFormatId() { - return xfe; - } - - /** - * Gets the number format pattern for this format, if set. For more - * information on number format patterns see Microsoft KB264372. - * - * @return the Excel number format pattern for this cell or - * null if none is applied - */ - public String getFormatPattern() { - return myxf.getFormatPattern(); - } - - /** - * get the fill pattern for this format - */ - public int getFillPattern() { - return myxf.getFillPattern(); - } - - /** - * returns the index of the Color within the Colortable - * - * @param the index of the color in the colortable - * @return the color - */ - public static Color getColor(int col) { - if (col > -1 && col < FormatHandle.COLORTABLE.length) - return FormatHandle.COLORTABLE[col]; - return FormatHandle.COLORTABLE[0]; - } - - /** - * returns the index of the Color within the Colortable - * - * @param col the color - * @return the index of the color in the colortable - */ - public static int getColorInt(Color col) { - for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) { - if (col.equals(FormatHandle.COLORTABLE[i])) { - return i; - } - } - int R = col.getRed(); - int G = col.getGreen(); - int B = col.getBlue(); - int colorMatch = -1; - double colorDiff = Integer.MAX_VALUE; - for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) { - double curDif = - Math.pow(R - FormatHandle.COLORTABLE[i].getRed(), 2) - + Math.pow(G - FormatHandle.COLORTABLE[i].getGreen(), 2) - + Math.pow(B - FormatHandle.COLORTABLE[i].getBlue(), 2); - if (curDif < colorDiff) { - colorDiff = curDif; - colorMatch = i; - } - } - return colorMatch; - } - - /** - * Sets the internal format to the FormatHandle. For internal use only, not - * supported. - */ - public void setXf(Xf xrec) { - myxf = xrec; - } - - /** - * Set the horizontal alignment for this FormatHandle - * - * @param align - an int representing the alignment. Please review the - * FormatHandle.ALIGN*** static int's - */ - public void setHorizontalAlignment(int align) { - Xf xf = cloneXf(myxf); - xf.setHorizontalAlignment(align); - updateXf(xf); - } - - /** - * Returns an int representing the current horizontal alignment in this - * FormatHandle. These values are mapped to the FormatHandle.ALIGN*** static - * int's - */ - public int getHorizontalAlignment() { - return myxf.getHorizontalAlignment(); - } - - /** - * set indent (1= 3 spaces) - * - * @param indent - */ - public void setIndent(int indent) { - Xf xf = cloneXf(myxf); - xf.setIndent(indent); - updateXf(xf); - } - - /** - * increase or decrease the precision of this numeric or curerncy format pattern - *
                  If the format pattern is "General", converts to a basic number pattern - *
                  If the precision is already 0 and !increase, this method does nothing - * - * @param increase true if increase the precsion (number of decimals to display) - */ - public void adjustPrecision(boolean increase) { - // TODO: if decimal is contained within quotes ... - String pat = getFormatPattern(); - if (pat.equals("General") && increase) { - pat = "0.0"; // the most basic numeric pattern - this.setFormatPattern(pat); - return; - } - - try { - // split pattern and deal with positive, negative, zero and text separately - // for each, find decimal place and increment/decrement; if not found, find last digit placeholder - String[] pats = pat.split(";"); - String newPat = ""; - for (int i = 0; i < pats.length; i++) { - if (i > 0) newPat += ';'; - int z = pats[i].indexOf('.'); // position of decimal - boolean foundit = false; - if (z != -1) { // found decimal place - z++; - for (; z < pats[i].length(); z++) { - char c = pats[i].charAt(z); - if ((c == '0' || c == '#' || c == '?')) // numeric placeholders - foundit = true; - else if (foundit && !(c == '0' || c == '#' || c == '?')) // numeric placeholders. if hit last one, either inc or dec - break; - } - if (increase) - newPat += new StringBuffer(pats[i]).insert(z, "0").toString(); //pats[i].substring(0, z) + "0" + pats[i].substring(z+1); - else { - if (pats[i].charAt(z - 2) != '.') { - newPat += new StringBuffer(pats[i]).deleteCharAt(z - 1).toString(); // .pats[i].substring(0, z-1) + pats[i].substring(z+1); - } else { - newPat += new StringBuffer(pats[i]).delete(z - 2, z).toString(); - } - } - - } else if (increase) { // no decimal yet. If decrease, ignore. if increase, add - z = pats[i].length() - 1; - for (; z >= 0; z--) { - char c = pats[i].charAt(z); - if ((c == '0' || c == '#' || c == '?')) { // found last numeric placeholder - foundit = true; - break; - } - } - if (foundit) // if had ANY numeric placeholders - newPat += new StringBuffer(pats[i]).insert(z + 1, ".0").toString(); //pats[i].substring(0, z) + ".0" + pats[i].substring(z+1); - else - newPat += pats[i]; // keep original - } else // if decrease and no decimal found, leave alone - newPat += pats[i]; // leave alone - } - - //io.starter.toolkit.Logger.log("Old Style" + pat + ". New Style: " + newPat + ". Increase?" + (increase?"yes":"no")); // KSC: TESETING: TAKE OUT WHEN DONE - this.setFormatPattern(newPat); - } catch (Exception e) { - Logger.logErr("Error setting style"); // KSC: TESETING: TAKE OUT WHEN DONE - } - } - - /** - * return indent (1 = 3 spaces) - * - * @return - */ - public int getIndent() { - return myxf.getIndent(); - } - - /** - * sets the Right to Left Text Direction or reading order of this style - * - * @param rtl possible values: - *
                  0=Context Dependent - *
                  1=Left-to-Right - *
                  2=Right-to-Let - * @param rtl possible values:
                  - * 0=Context Dependent
                  - * 1=Left-to-Right
                  - * 2=Right-to-Let - */ - public void setRightToLeftReadingOrder(int rtl) { - Xf xf = cloneXf(myxf); - xf.setRightToLeftReadingOrder(rtl); - updateXf(xf); - } - - /** - * returns true if this style is set to Right-to-Left text direction - * (reading order) - * - * @return - */ - public int getRightToLetReadingOrder() { - return myxf.getRightToLeftReadingOrder(); - } - - /** - * Set the Vertical alignment for this FormatHandle - * - * @param align - an int representing the alignment. Please review the - * FormatHandle.ALIGN*** static int's - */ - public void setVerticalAlignment(int align) { - Xf xf = cloneXf(myxf); - xf.setVerticalAlignment(align); - updateXf(xf); - } - - /** - * Returns an int representing the current Vertical alignment in this - * FormatHandle. These values are mapped to the FormatHandle.ALIGN*** static - * int's - */ - public int getVerticalAlignment() { - return myxf.getVerticalAlignment(); - } - - /** - * DEPRECATED and non functional. Not neccesary as this occurs automatically - *

                  - * Consolidates this Format with other identical formats in workbook - *

                  - *

                  - * There is a limit to the number of distinct formats in an Excel workbook. - *

                  - * This method allows you to share Formats between identically formatted - * cells. - * - * @return - * @deprecated - */ - public FormatHandle pack() { - return this; // wkbook.cache.get(this); - } - - /** - * Set the workbook for this FormatHandle - * - * @param bk - */ - public void setWorkBook(io.starter.formats.XLS.WorkBook bk) { - wkbook = bk; - } - - /** - * Set the cell wrapping behavior for this FormatHandle. Default is false - */ - public void setWrapText(boolean wrapit) { - Xf xf = cloneXf(myxf); - xf.setWrapText(wrapit); - updateXf(xf); - } - - /** - * Get the cell wrapping behavior for this FormatHandle. Default is false - */ - public boolean getWrapText() { - return myxf.getWrapText(); - } - - - /** - * Set the rotation of the cell in degrees. Values 0-90 represent rotation - * up, 0-90degrees. Values 91-180 represent rotation down, 0-90 degrees. - * Value 255 is vertical - * - * @param align - an int representing the rotation. - */ - public void setCellRotation(int align) { - Xf xf = cloneXf(myxf); - xf.setRotation(align); - updateXf(xf); - } - - /** - * Get the rotation of the cell. Value 0 means no rotation (horizontal - * text). Values 1-90 mean rotation up (couter-clockwise) by 1-90 degrees. - * Values 91-180 mean rotation down (clockwise) by 1-90 degrees. Value 255 - * means vertical text. - */ - public int getCellRotation() { - return myxf.getRotation(); - } - - /** - * Get a JSON representation of the format - * - * @param cr - * @return - */ - public String getJSON(int XFNum) { - return getJSONObject(XFNum).toString(); - } - - /** - * Get a JSON representation of the format - *

                  - * font height is represented as HTML pt size - * - * @param cr - * @return - */ - public JSONObject getJSONObject(int XFNum) { - Font myf = getFont(); - JSONObject theStyle = new JSONObject(); - try { - theStyle.put("style", XFNum); - // handle the font - JSONObject theFont = new JSONObject(); - theFont.put("name", myf.getFontName()); - - // round out the font size... - long sz = Math.round(myf.getFontHeight() / 22.0); - - theFont.put("size", sz); // adjust smaller - theFont.put("color", colorToHexString(myf.getColorAsColor())); - theFont.put("weight", myf.getFontWeight()); - if (getIsBold()) { - theFont.put("bold", "1"); - } - if (this.getUnderlined()) { - theFont.put("underline", "1"); - } else { - theFont.put("underline", "0"); - } - - if (getItalic()) { - theFont.put("italic", "1"); - } - - theStyle.put("font", theFont); - - // - JSONObject border = new JSONObject(); - if (getRightBorderLineStyle() != 0) { - JSONObject rBorder = new JSONObject(); - rBorder.put("style", - BORDER_STYLES_JSON[getRightBorderLineStyle()]); - rBorder.put("color", colorToHexString(getBorderRightColor())); - border.put("right", rBorder); - } - if (getBottomBorderLineStyle() != 0) { - JSONObject bBorder = new JSONObject(); - bBorder.put("style", - BORDER_STYLES_JSON[getBottomBorderLineStyle()]); - bBorder.put("color", colorToHexString(getBorderBottomColor())); - border.put("bottom", bBorder); - } - if (getLeftBorderLineStyle() != 0) { - JSONObject lBorder = new JSONObject(); - lBorder.put("style", - BORDER_STYLES_JSON[getLeftBorderLineStyle()]); - lBorder.put("color", colorToHexString(getBorderLeftColor())); - border.put("left", lBorder); - } - if (getTopBorderLineStyle() != 0) { - JSONObject tBorder = new JSONObject(); - tBorder.put("style", - BORDER_STYLES_JSON[getTopBorderLineStyle()]); - tBorder.put("color", colorToHexString(getBorderTopColor())); - border.put("top", tBorder); - } - theStyle.put("borders", border); - - // - JSONObject alignment = new JSONObject(); - alignment.put("horizontal", - HORIZONTAL_ALIGNMENTS[getHorizontalAlignment()]); - alignment.put("vertical", - VERTICAL_ALIGNMENTS[getVerticalAlignment()]); - if (getWrapText()) { - alignment.put("wrap", "1"); - } - theStyle.put("alignment", alignment); - - if (getIndent() != 0) { - theStyle.put("indent", getIndent()); - } - // colors + background patterns - if (getFillPattern() >= 0) { // KSC: added >= 0 as some conditional formats have patternFillStyle==0 even though they have a pattern block and - JSONObject interior = new JSONObject(); - // weird black/white case - if (myxf.getForegroundColor() == 65) { - interior.put("color", "#FFFFFF"); - } else { - // KSC: use color string if it exists; create if doesn't - interior.put("color", myxf.getForegroundColorHEX()); - } - interior.put("pattern", myxf.getFillPattern()); - interior.put("fg", myxf.getForegroundColor()); // Excel-2003 Color Table index - interior.put("patterncolor", myxf.getBackgroundColorHEX()); - interior.put("bg", myxf.getBackgroundColor()); // Excel-2003 Color Table index - theStyle.put("interior", interior); - } - - if (myxf.getIfmt() != 0) { // only input user defined formats ... - JSONObject nFormat = new JSONObject(); - String fmtpat = getFormatPattern(); - try { - if (!fmtpat.equals("General")) { - nFormat.put("format", fmtpat); // convertXMLChars? - nFormat.put("formatid", myxf.getIfmt()); - if (isDate()) - nFormat.put("isdate", "1"); - if (isCurrency()) - nFormat.put("iscurrency", "1"); - if (isRedWhenNegative()) - nFormat.put("isrednegative", "1"); - } - - } catch (Exception e) { // it's possible that getFormatPattern - // returns null - } - theStyle.put("numberformat", nFormat); - } - // - JSONObject protection = new JSONObject(); - try { - if (this.myxf.isLocked()) - protection.put("Protected", true); - if (this.myxf.isFormulaHidden()) - protection.put("HideFormula", true); - } catch (Exception e) { - } - theStyle.put("protection", protection); - - } catch (JSONException e) { - Logger.logErr("Error getting cellRange JSON: " + e); - } - return theStyle; - } - - /** - * Returns an XML fragment representing the FormatHandle - */ - public String getXML(int XFNum) { - return getXML(XFNum, false); - } - - /** - * Returns an XML fragment representing the FormatHandle - * - * @param convertToUnicodeFont if true, font family will be changed to ArialUnicodeMS - * (standard unicode) for non-ascii fonts - */ - public String getXML(int XFNum, boolean convertToUnicodeFont) { - Font myf = getFont(); - // "); - return sb.toString(); - } - - /** - * Gets the Excel format ID for this format's number format pattern. - * - * @return the Excel format identifier number for the number format pattern - */ - public int getFormatPatternId() { - return myxf.getIfmt(); - } - - /** - * Sets the number format pattern based on the format ID number. This method - * is recommended for advanced users only. In most cases you should use - * {@link #setFormatPattern(String)} instead. - * - * @param fmt the format ID number for the desired number format pattern - */ - public void setFormatPatternId(int fmt) { - myxf.setFormat((short) fmt); - } - - /** - * Convert a java.awt.Color to a hex string. - * - * @return String representation of a Color - */ - public static String colorToHexString(Color c) { - int r = c.getRed(); - int g = c.getGreen(); - int b = c.getBlue(); - String rh = Integer.toHexString(r); - if (rh.length() < 2) { - rh = "0" + rh; - } - String gh = Integer.toHexString(g); - if (gh.length() < 2) { - gh = "0" + gh; - } - String bh = Integer.toHexString(b); - if (bh.length() < 2) { - bh = "0" + bh; - } - return ("#" + rh + gh + bh).toUpperCase(); - } - - - public static short colorFONT = 0; - public static short colorBACKGROUND = 1; - public static short colorFOREGROUND = 2; - public static short colorBORDER = 3; - - /** - * convert hex string RGB to Excel colortable int format if an exact match - * is not find, does color-matching to try and obtain closest match - * - * @param s - * @param colorType - * @return - */ - public static int HexStringToColorInt(String s, short colorType) { - if (s.length() > 7) - s = "#" + s.substring(s.length() - 6); - if (s.indexOf("#") == -1) - s = "#" + s; - if (s.length() == 7) { - String rs = s.substring(1, 3); - int r = Integer.parseInt(rs, 16); - String gs = s.substring(3, 5); - int g = Integer.parseInt(gs, 16); - String bs = s.substring(5, 7); - int b = Integer.parseInt(bs, 16); - // Handle exceptions for black, white and color indexes 9 (see - // FormatConstants for more info) - - if (r == 255 && r == g && r == b) { - if (colorType == colorFONT) - return 9; - } - - Color c = new Color(r, g, b); - return getColorInt(c); - } - return 0; - } - - /** - * convert hex string RGB to Excel colortable int format if no exact match - * is found returns -1 - * - * @param s HTML color string (#XXXXXX) format or (#FFXXXXXX - OOXML-style - * format) - * @param colorType - * @return index into color table or -1 if not found - */ - public static int HexStringToColorIntExact(String s, short colorType) { - if (s.length() > 7) - s = "#" + s.substring(s.length() - 6); - if (s.indexOf("#") == -1) - s = "#" + s; - if (s.length() == 7) { - String rs = s.substring(1, 3); - int r = Integer.parseInt(rs, 16); - String gs = s.substring(3, 5); - int g = Integer.parseInt(gs, 16); - String bs = s.substring(5, 7); - int b = Integer.parseInt(bs, 16); - - // Handle exceptions for black, white and color indexes 9 (see - // FormatConstants for more info) - if (r == 255 && r == g && r == b) { - if (colorType == colorFONT) - return 9; - } - - Color c = new Color(r, g, b); - for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) { - if (c.equals(FormatHandle.COLORTABLE[i])) { - return i; - } - } - } - return -1; // match NOT FOUND - } - - /** - * convert hex string RGB to a Color - * - * @param s web-style hex color string, including #, or OOXML-style color string, FFXXXXXX - * @return Color - * @see Color - */ - public static Color HexStringToColor(String s) { - if (s.length() > 7) { // transform OOXML-style color strings to Web-style - s = "#" + s.substring(s.length() - 6); - } - Color c = null; - if (s.length() == 7) { - String rs = s.substring(1, 3); - int r = Integer.parseInt(rs, 16); - String gs = s.substring(3, 5); - int g = Integer.parseInt(gs, 16); - String bs = s.substring(5, 7); - int b = Integer.parseInt(bs, 16); - c = new Color(r, g, b); - } else - c = new Color(0, 0, 0); // default to black? - return c; - } - - /** - * interpret color table special entries - * - * @param clr color index in range of 0x41-0x4F (charts) 0x40 ... - * @return index into color table - */ - public static short interpretSpecialColorIndex(int clr) { - switch (clr) { - case 0x0041: // Default background color. This is the window background - // color in the sheet display and is the default - // background color for a cell. - case 0x004E: // Default chart background color. This is the window - // background color in the chart display. - case 0x0050: // WHAT IS THIS ONE???????? - return FormatConstants.COLOR_WHITE; - case 0x0040: // Default foreground color - case 0x004F: // Chart neutral color which is black, an RGB value of - // (0,0,0). - case 0x004D: // Default chart foreground color. This is the window text - // color in the chart display. - case 0x0051: // ToolTip text color. This is the automatic font color for - // comments. - case 0x7FFF: // Font automatic color. This is the window text color - return FormatConstants.COLOR_BLACK; - default: // 67(=0x43) ??? - return FormatConstants.COLOR_WHITE; - } - - /* - * switch (icvFore) { case 0x40: // default fg color return - * FormatConstants.COLOR_WHITE; case 0x41: // default bg color return - * FormatConstants.COLOR_WHITE; case 0x4D: // default CHART fg color -- - * INDEX SPECIFIC! return -1; // flag to map via series (bar) color - * defaults case 0x4E: // default CHART fg color return icvFore; case - * 0x4F: // chart neutral color == black return - * FormatConstants.COLOR_BLACK; } - * - * switch (icvBack) { case 0x40: // default fg color return - * FormatConstants.COLOR_WHITE; case 0x41: // default bg color return - * FormatConstants.COLOR_WHITE; case 0x4D: // default CHART fg color -- - * INDEX SPECIFIC! return -1; // flag to map via series (bar) color - * defaults case 0x4E: // default CHART bg color //return - * FormatConstants.COLOR_WHITE; // is this correct? return icvBack; case - * 0x4F: // chart neutral color == black return - * FormatConstants.COLOR_BLACK; } - */ - } - - public static int BorderStringToInt(String s) { - for (int i = 0; i < FormatConstants.BORDER_NAMES.length; i++) { - if (FormatConstants.BORDER_NAMES[i].equals(s)) - return i; - } - return 0; - } - - // 20060412 KSC: added for access - public io.starter.formats.XLS.WorkBook getWorkBook() { - return wkbook; - } - - /** - * return truth of "this Xf rec is a style xf" - * - * @return - */ - public boolean isStyleXf() { - return myxf.isStyleXf(); - } - - /** - * creates a new font based on an existing one, adds to workbook recs - */ - private Font createNewFont(Font f) { - f.setIdx(-1); // flag to insert anew (see Font.setWorkBook) - wkbook.insertFont(f); - f.setWorkBook(wkbook); - return f; - } - - /** - * create a new font based on existing font - does not add to workbook recs - * - * @param src font - * @return cloned font - */ - private Font cloneFont(Font src) { - Font f = new Font(); - f.setOpcode(io.starter.formats.XLS.XLSConstants.FONT); - f.setData(src.getBytes()); // use default font as basis of new font - f.setIdx(-2); // avoid adding to fonts array when call setW.b. below - f.setWorkBook(this.getWorkBook()); - f.init(); - return f; - } - - /** - * update the font for this FormatHandle, including updating the xf if - * necessary - * - * @param f - */ - private void updateFont(Font f) { - int idx = wkbook.getFontIdx(f); - if (idx == -1) { // can't find it so add new - f = createNewFont(f); - } else - f = wkbook.getFont(idx); - if (f.getIdx() != myxf.getIfnt()) { // then updated the font, must create new xf to link to - Xf xf = cloneXf(myxf); - xf.setFont(f.getIdx()); - updateXf(xf); - } - } - - /** - * Create or Duplicate Xf rec so can alter (pattern, font, colrs ...) - * - * @param xf xf to base off of, or null (will create new) - * @return new Xf - */ - private Xf duplicateXf(Xf xf) { - int fidx = 0; - if (xf != null) - fidx = xf.getFont().getIdx(); - xf = Xf.updateXf(xf, fidx, wkbook); // clones/creates new based upon - // original - xfe = xf.getIdx(); // update the pointer -// not used anymore canModify = true; // if duplicated, it's new and unlinked (thus far) - return xf; - } - - /** - * adds all formatting represented by the sourceXf to this workbook, if not - * already present
                  - * This is used internally for transferring formats from one workbook to - * another - * - * @param xf - sourceXf - * @return ixfe of added Xf - */ - public int addXf(Xf sourceXf) { - // must handle font first in order to create xf below - // check to see if the font needs to be added in current workbook - int fidx = addFontIfNecessary(sourceXf.getFont()); - - /** XF **/ - Xf localXf = FormatHandle.cloneXf(sourceXf, wkbook.getFont(fidx), - wkbook); // clone xf so modifcations don't affect original - - /** NUMBER FORMAT **/ - String fmt = sourceXf.getFormatPattern(); // number format pattern - if (fmt != null) - localXf.setFormatPattern(fmt); // adds new format pattern if not // found - - // now check out to see if this particular xf pattern exists; if not, add - updateXf(localXf); - return xfe; - } - - /** - * if existing format matches, reuse. otherwise, create new Xf record and - * add to cache - * - * @param xf - */ - private void updateXf(Xf xf) { - if (!myxf.toString().equals(xf.toString())) { - if (myxf.getUseCount() <= 1 && xfe > 15) { // used only by one cell, OK to modify - if (writeImmediate || wkbook.getFormatCache().get(xf.toString()) == null) { - // myxf hasn't been used yet; modify bytes and re-init *** - byte[] xfbytes = xf.getBytes(); - myxf.setData(xfbytes); - if (xf.fill != null) - myxf.fill = (io.starter.formats.OOXML.Fill) xf.fill.cloneElement(); - myxf.init(); - myxf.setFont(myxf.getIfnt()); // set font as well .. - wkbook.updateFormatCache(myxf); // ensure new xf signature - // is stored - } else { - if (myxf.getUseCount() > 0) - myxf.decUseCoount(); // flag original xf that 1 less record is referencing it - myxf = (Xf) wkbook.getFormatCache().get(xf.toString()); - xfe = myxf.getIdx(); // update the pointer - if (xfe == -1) // hasn't been added to wb yet - should this ever happen??? - myxf = duplicateXf(xf); // create a duplicate and leave original - else - myxf.incUseCount(); - } - } else { // cannot modify original - either find matching or create new - if (myxf.getUseCount() > 0) - myxf.decUseCoount(); // flag original xf that 1 less record is referencing it - if (wkbook.getFormatCache().get(xf.toString()) == null) { // doesn't exist yet - myxf = duplicateXf(xf); // create a duplicate and leave original - } else { - myxf = (Xf) (wkbook.getFormatCache().get(xf.toString())); - xfe = myxf.getIdx(); // update the pointer - if (xfe == -1) // hasn't been added to the record store yet // - should ever happen??? - myxf = duplicateXf(xf); // create a duplicate and leave original - else - myxf.incUseCount(); - } - } - - for (int i = 0; i < mycells.size(); i++) { - ((BiffRec) mycells.get(i)).setXFRecord(xfe); // make sure all linked cells are updated as well - } - if (mycol != null) - mycol.setFormatId(xfe); - if (myrow != null) - myrow.setFormatId(xfe); - } - } - - /** - * create a duplicate xf rec based on existing ... - * - * @param xf - * @return cloned xf - */ - private Xf cloneXf(Xf xf) { - Xf clone = new Xf(xf.getFont().getIdx(), wkbook); - byte[] data = xf.getBytesAt(0, xf.getLength() - 4); - clone.setData(data); - if (xf.fill != null) - clone.fill = (io.starter.formats.OOXML.Fill) xf.fill.cloneElement(); - clone.init(); - return clone; - } - - /** - * static version of cloneXf - * - * @param xf - * @param wkbook - * @return - */ - public static Xf cloneXf(Xf xf, io.starter.formats.XLS.WorkBook wkbook) { - Xf clone = new Xf(xf.getFont().getIdx(), wkbook); - byte[] data = xf.getBytesAt(0, xf.getLength() - 4); - clone.setData(data); - clone.init(); - return clone; - } - - /** - * static version of cloneXf - * - * @param xf - * @param wkbook - * @return - */ - public static Xf cloneXf(Xf xf, Font f, - io.starter.formats.XLS.WorkBook wkbook) { - Xf clone = new Xf(f, wkbook); - byte[] data = xf.getBytesAt(0, xf.getLength() - 4); - clone.setData(data); - clone.setFont(f.getIdx()); // font idx is overwritten by xf data; must reset - clone.init(); - return clone; - } - - /** - * set the pointer to the XFE or Conditional format - * - * @param xfe - */ - public void setFormatId(int x) { - this.xfe = x; - } - - /** - * clear out object references - */ - public void close() { - mycells.clear(); - mycells = new CompatibleVector(); // all the Cells sharing this format - myxf = null; - mycol = null; - myrow = null; - wkbook = null; - wbh = null; - } - - protected void finalize() throws Throwable { - try { - close(); // close open files - } finally { - super.finalize(); - } - } - - /** - * For internal usage only, return the internal XF record that - * represents this FormatHandle - * - * @return the myxf - */ - protected Xf getXf() { - return myxf; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/FormatHandle.kt b/src/main/java/io/starter/OpenXLS/FormatHandle.kt new file mode 100644 index 0000000..a2f200e --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/FormatHandle.kt @@ -0,0 +1,3011 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.OOXML.Fill +import io.starter.formats.XLS.Font +import io.starter.formats.XLS.* +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.json.JSONException +import org.json.JSONObject + +import java.awt.Color +import java.util.* + +/** + * Provides methods for querying and changing cell formatting information. Cell + * formating includes fonts, borders, text alignment, background colors, cell + * protection (locking), etc. + * + * + * The mutator methods of a FormatHandle object directly and immediately change + * the formatting of the cell(s) to which it is applied. Under no circumstances + * will a FormatHandle alter the formatting of cells to which it is not applied. + * The list of cells to which a FormatHandle applies may be queried with the + * [.getCells] method. Additional cells may be added with [.addCell] + * and related methods. + * + * + * A FormatHandle may also be obtained for a row or a column. In such a case the + * FormatHandle sets the default formats for the row or column. The default + * format for a row or column is the format which appears when no other formats + * for the cell are specified, such as for newly created cells. + */ +class FormatHandle : Handle, FormatConstants { + + private var mycells = CompatibleVector() // all the Cells sharing this format e.g. CellRange + /** + * For internal usage only, return the internal XF record that + * represents this FormatHandle + * + * @return the myxf + */ + /** + * Sets the internal format to the FormatHandle. For internal use only, not + * supported. + */ + var xf: Xf? = null + /** + * The format ID allows setting of the format for a Cell without adding it + * to the Format's Cell Collection. + * + * + * Use to decrease memory requirements when dealing with large collections + * of cells. + * + * + * Usage: + * + * + * mycell.setFormatId(myformat.getFormatId()); + * + * @return the format ID for this Format + */ + /** + * set the pointer to the XFE or Conditional format + * + * @param xfe + */ + var formatId: Int = 0 + // see Xf.usedCount instead private boolean canModify = false; + private var mycol: ColHandle? = null + private var myrow: RowHandle? = null + private val writeImmediate = false + internal var underlined = false + + // 20060412 KSC: added for access + /** + * Set the workbook for this FormatHandle + * + * @param bk + */ + var workBook: io.starter.formats.XLS.WorkBook? = null + private var wbh: io.starter.OpenXLS.WorkBook? = null + + /** + * returns whether this FormatHandle is set to hide formula strings + * + * @return true if the formula strings are hidden, false otherwise + */ + /** + * sets the cell attached to this FormatHandle to hide or show formula + * strings; + * + * @param boolean b- true if formulas should be hidden for this FormatHandle + */ + var isFormulaHidden: Boolean + get() = xf!!.isFormulaHidden + set(b) { + val xf = cloneXf(this.xf!!) + xf.isFormulaHidden = b + updateXf(xf) + } + + /** + * returns whether this Format Handle specifies that cells are locked for + * changing + * + * @return true if cells are locked + */ + /** + * Locks the cell attached to this FormatHandle for editing (makes + * read-only) lock cell and make read-only + * + * @param boolean locked - true if cells should be locked for this + * FormatHandle + */ + var isLocked: Boolean + get() = xf!!.isLocked + set(locked) { + val xf = cloneXf(this.xf!!) + xf.isLocked = locked + updateXf(xf) + } + + /** + * provides a mapping between Excel formats and Java formats

                  + * see: [http://java.sun.com/docs/books/tutorial/i18n/format + * /decimalFormat.html](tutorial) + * + * + * Note there are slight Excel-specific differences in the format strings + * returned. Several numeric and currency formats in excel have different + * formatting for postive and negative numbers. In these cases, the java + * format string is split by semicolons and may contain text [Red] which is + * to specify the negative number should be displayed in red. Remove this + * from the string before passing into the Format class; + * + *

                  +     * G  Era designator  Text  AD
                  +     * y  Year  Year  1996; 96
                  +     * M  Month in year  Month  July; Jul; 07
                  +     * w  Week in year  Number  27
                  +     * W  Week in month  Number  2
                  +     * D  Day in year  Number  189
                  +     * d  Day in month  Number  10
                  +     * F  Day of week in month  Number  2
                  +     * E  Day in week  Text  Tuesday; Tue
                  +     * a  Am/pm marker  Text  PM
                  +     * H  Hour in day (0-23)  Number  0
                  +     * k  Hour in day (1-24)  Number  24
                  +     * K  Hour in am/pm (0-11)  Number  0
                  +     * h  Hour in am/pm (1-12)  Number  12
                  +     * m  Minute in hour  Number  30
                  +     * s  Second in minute  Number  55
                  +     * S  Millisecond  Number  978
                  +     * z  Time zone  General time zone  Pacific Standard Time; PST; GMT-08:00
                  +     * Z  Time zone  RFC 822 time zone  -0800
                  +    
                  * + * + * @return String the formatting pattern for the cell + */ + // toLowerCase is a simplistic way to implement the case insensitivity + // of the pattern tokens. It could cause issues with string literals. + /* + * If we reached here, we don't have a mapping for this particular + * format. Send a warning to the system then make sure the pattern we + * are sending back is valid. Many excel patterns have 4 patterns + * separated by semicolons. We only can pass 2 into the formatter + * (positive and negative). This usually works out to be the first two + * patterns in the string. + */// yet another hackaround -jm + val javaFormatString: String? + get() { + var pat = formatPattern ?: return null + var patty: Any? = convertFormatString(pat!!.toLowerCase()) + if (patty != null) + patty = StringTool.qualifyPatternString(patty.toString()) + if (xf!!.isDatePattern) { + return if (patty != null) + patty as String? + else + "M/d/yy h:mm" + } + if (patty != null) + return patty as String? + pat = StringTool.qualifyPatternString(pat) + val firstParens = pat!!.indexOf(";") + if (firstParens != -1) { + val secondParens = pat!!.indexOf(";", firstParens + 1) + if (secondParens != -1) { + pat = pat!!.substring(0, secondParens) + } else { + pat = pat!!.substring(firstParens + 2, pat!!.length - 1) + } + } + return pat + } + + /** + * Get the Right border color + * + * @return color constant + */ + // black i'm afraid + val borderRightColor: Color? + get() { + if (xf!!.rightBorderLineStyle.toInt() == 0) + return null + val x = xf!!.rightBorderColor.toInt() + return if (x < this.workBook!!.colorTable.size) + this.workBook!!.colorTable[x] + else + this.workBook!!.colorTable[0] + } + + /** + * returns Border Colors of Cell ie: top, left, bottom, right + * + * + * returns null or 1 color for each of 4 sides + * + * + * 1,1,1,1 represents a border all around the cell 1,1,0,0 represents on the + * top left edge of the cell + * + * @return int array representing Cell borders + */ + /* + * FIXME: Border Issues (marker) + * + * The methods to set individual border line styles do not follow a + * consistent naming convention with the rest of the border methods. + * + * There are no methods for getting or setting the inside borders. I don't + * know whether this is a problem or a design choice. It depends on whether + * the inside borders are stored in the format record or as separate cell + * formats. + * + * There are no methods to get the diagonal borders. There is a method to + * set the diagonal border line style, but it affects both diagonals. + */ + + /** + * sets the border color for all borders (top, left, bottom and right) from + * a Color array + * + * + * NOTE: this setting will affect every cell which refers to this + * FormatHandle + * + * @param java .awt.Color array - 4-element array of desired border colors + * [T, L, B, R] + */ + var borderColors: Array + get() { + val colors = arrayOfNulls(4) + colors[0] = borderTopColor + colors[1] = borderLeftColor + colors[2] = borderBottomColor + colors[3] = borderRightColor + return colors + } + set(bordercolors) { + val xf = cloneXf(this.xf!!) + if (bordercolors[0] != null) + xf.topBorderColor = getColorInt(bordercolors[0]) + if (bordercolors[1] != null) + xf.leftBorderColor = getColorInt(bordercolors[1]) + if (bordercolors[2] != null) + xf.bottomBorderColor = getColorInt(bordercolors[2]) + if (bordercolors[3] != null) + xf.setRightBorderColor(getColorInt(bordercolors[3])) + updateXf(xf) + } + + /** + * Get the Left border color + * + * @return color constant + */ + val borderLeftColor: Color? + get() = if (xf!!.leftBorderLineStyle.toInt() == 0) null else this.workBook!!.colorTable[xf!!.leftBorderColor] + + /** + * Get the Top border color + * + * @return color constant + */ + // guards + val borderTopColor: Color? + get() { + if (xf!!.topBorderLineStyle.toInt() == 0) + return null + var xt = xf!!.topBorderColor + if (xt > this.workBook!!.colorTable.size) + xt = 0 + return this.workBook!!.colorTable[xt] + } + + /** + * Returns true if the value should be red due to a combination of a format + * pattern and a negative number + * + * @return + */ + val isRedWhenNegative: Boolean + get() { + val pattern = xf!!.formatPattern + return pattern!!.indexOf("Red") > -1 + } + + /** + * Get the Right border color + * + * @return color constant + */ + // black i'm afraid + val borderBottomColor: Color? + get() { + if (xf!!.bottomBorderLineStyle.toInt() == 0) + return null + val x = xf!!.bottomBorderColor + return if (x < this.workBook!!.colorTable.size) + this.workBook!!.colorTable[x] + else + this.workBook!!.colorTable[0] + + } + + /** + * Get the border line style + * + * @return line style constant + */ + /** + * Set the border line style + * + * @param line style constant + */ + var topBorderLineStyle: Int + get() = xf!!.topBorderLineStyle.toInt() + set(x) { + val xf = cloneXf(this.xf!!) + xf.topBorderLineStyle = x.toShort() + updateXf(xf) + } + + /** + * Get the border line style + * + * @return line style constant + */ + /** + * Set the border line style + * + * @param line style constant + */ + var bottomBorderLineStyle: Int + get() = xf!!.bottomBorderLineStyle.toInt() + set(x) { + val xf = cloneXf(this.xf!!) + xf.bottomBorderLineStyle = x.toShort() + updateXf(xf) + } + + /** + * Get the border line style + * + * @return line style constant + */ + /** + * Set the border line style + * + * @param line style constant + */ + var leftBorderLineStyle: Int + get() = xf!!.leftBorderLineStyle.toInt() + set(x) { + val xf = cloneXf(this.xf!!) + xf.leftBorderLineStyle = x.toShort() + updateXf(xf) + } + + /** + * Get the border line style + * + * @return line style constant + */ + /** + * Set the border line style + * + * @param line style constant + */ + var rightBorderLineStyle: Int + get() = xf!!.rightBorderLineStyle.toInt() + set(x) { + val xf = cloneXf(this.xf!!) + xf.rightBorderLineStyle = x.toShort() + updateXf(xf) + } + + /** + * return the 5 border lines styles (l, r, t, b, diag) + * + * @return + */ + val allBorderLineStyles: IntArray + get() { + val ret = IntArray(5) + ret[0] = xf!!.leftBorderLineStyle.toInt() + ret[1] = xf!!.rightBorderLineStyle.toInt() + ret[2] = xf!!.topBorderLineStyle.toInt() + ret[3] = xf!!.bottomBorderLineStyle.toInt() + ret[4] = xf!!.diagBorderLineStyle.toInt() + return ret + } + + /** + * return the 5 border line colors (l, r, t, b, diag) + * + * @return + */ + val allBorderColors: IntArray + get() { + val ret = IntArray(5) + ret[0] = xf!!.leftBorderColor + ret[1] = xf!!.rightBorderColor.toInt() + ret[2] = xf!!.topBorderColor + ret[3] = xf!!.bottomBorderColor + ret[4] = xf!!.diagBorderColor.toInt() + return ret + } + +/** + * Get the font weight the weight of the font is in 1/20 point units + * + * @return + */ +/** + * /** Set the weight of the font in 1/20 point units 100-1000 range. 400 is + * normal, 700 is bold. + * + * @param wt +*/ +var fontWeight:Int +get() { +return xf!!.font!!.fontWeight +} +set(wt) { +val f = cloneFont(xf!!.font!!) +f.fontWeight = wt +updateFont(f) +} + +/** + * returns whether this Format is formatted as a Date + * + * @return boolean true if this Format is formatted as a Date +*/ +val isDate:Boolean +get() { +if (xf == null) +return false +return xf!!.isDatePattern +} + +/** + * returns whether this Format is formatted as a Currency + * + * @return boolean true if this Format is formatted as a currency +*/ +val isCurrency:Boolean +get() { +if (xf == null) +return false +return xf!!.isCurrencyPattern +} + +/** + * Get the Font foreground (text) color as a java.awt.Color + * + * @return +*/ +val fontColor:Color +get() { +return xf!!.font!!.colorAsColor +} + + +val fontColorAsHex:String +get() { +return xf!!.font!!.colorAsHex +} + +/** + * Get the Pattern Background Color for this Format Pattern + * + * @return the Excel color constant +*/ +/** + * set the background color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @param t Excel color constant +*/ +var backgroundColor:Int +get() { +return xf!!.backgroundColor.toInt() +} +set(t) { +val xf = cloneXf(this.xf!!) +if (xf.fillPattern == Xf.PATTERN_SOLID) +xf.setForeColor(t, null) +else +xf.setBackColor(t, null) + +updateXf(xf) +} + +/** + * get the Pattern Background Color for this Format Pattern as a hex string + * + * @return Hex Color String +*/ +val backgroundColorAsHex:String? +get() { +return xf!!.backgroundColorHEX +} + +/** + * get the Pattern Background Color for this Format Pattern as an awt.Color + * + * @return background Color +*/ +val backgroundColorAsColor:java.awt.Color +get() { +return HexStringToColor(this.backgroundColorAsHex!!) +} + + +/** + * returns the foreground color setting regardless of format pattern (which + * can switch fg and bg) + * + * @return +*/ +// 20080814 KSC: getForegroundColor() does the swapping so use base method +val trueForegroundColor:Int +get() { +return xf!!.foregroundColor.toInt() +} + +/** + * get the Pattern Background Color for this Formatted Cell + * + * + * This method handles display of conditional formats for the cell + * + * + * checks for conditional format, then applies it if conditions are true. + * + * @return the Excel color constant +*/ +// this.getForegroundColor() does +// the swapping so use base +// method +val cellBackgroundColor:Int +get() { +val fp = fillPattern + +if (fp == Xf.PATTERN_SOLID) +return xf!!.foregroundColor.toInt() +else +return xf!!.backgroundColor.toInt() +} + +/** + * get the Pattern Background Color for this Format as a Hex Color String + * + * @return Hex Color String +*/ +// this.getForegroundColor() does +val cellBackgroundColorAsHex:String? +get() { +val fp = fillPattern + +if (fp == Xf.PATTERN_SOLID) +return xf!!.foregroundColorHEX +else +return xf!!.backgroundColorHEX +} + +/** + * get the Pattern Background Color for this Format as an awt.Color + * + * @return cell background color +*/ +val cellBackgroundColorAsColor:java.awt.Color +get() { +return HexStringToColor(this.cellBackgroundColorAsHex!!) +} + +/** + * get the Background Color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @return the Excel color constant +*/ +/** + * set the Foreground Color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @param int Excel color constant +*/ +// if it's SOLID pattern, fg/bg are swapped +var foregroundColor:Int +get() { +if (fillPattern == Xf.PATTERN_SOLID) +return xf!!.backgroundColor.toInt() + +return xf!!.foregroundColor.toInt() +} +set(t) { +val xf = cloneXf(this.xf!!) +xf.setForeColor(t, null) +updateXf(xf) +} + +/** + * get the Background Color for this Format as a Hex Color String NOTE: + * Foreground color = the CELL BACKGROUND color color for all patterns and + * Background color= the PATTERN color for all patterns != Solid For + * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background + * Color=64 (white). + * + * @return Hex Color String +*/ +// if it's SOLID pattern, +val foregroundColorAsHex:String? +get() { +if (fillPattern == Xf.PATTERN_SOLID) +return xf!!.backgroundColorHEX + +return xf!!.foregroundColorHEX +} + +/** + * get the Background Color for this Format as a Color NOTE: + * Foreground color = the CELL BACKGROUND color color for all patterns and + * Background color= the PATTERN color for all patterns != Solid For + * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background + * Color=64 (white). + * + * @return Hex Color String +*/ +val foregroundColorAsColor:Color +get() { +return HexStringToColor(this.foregroundColorAsHex!!) +} + +/** + * Get if this format is bold or not + * + * @return boolean whether the cell font format is bold +*/ +val isBold:Boolean +get() { +return xf!!.font!!.isBold +} + +/** + * Return an int representing the underline style + * + * + * These map to the STYLE_UNDERLINE static integers * + * + * @return int underline style +*/ +/** + * set the underline style for this font + * + * @param int u underline style one of the Font.STYLE_UNDERLINE constants +*/ +var underlineStyle:Int +get() { +return xf!!.font!!.underlineStyle +} +set(u) { +val f = cloneFont(xf!!.font!!) +f.setUnderlineStyle(u.toByte()) +updateFont(f) +} + +/** + * Get the font height in points + * + * @return font height +*/ +val fontHeightInPoints:Double +get() { +return font!!.fontHeightInPoints +} + +/** + * Returns the Font's height in 1/20th point increment + * + * @return font height +*/ +/** + * Set the Font's height in 1/20th point increment + * + * @param new font height +*/ +var fontHeight:Int +get() { +return font!!.fontHeight +} +set(fontHeight) { +val f = cloneFont(xf!!.font!!) +f.fontHeight = fontHeight +updateFont(f) +} + +/** + * Returns the Font's name + * + * @return font name +*/ +/** + * Set the Font's name + * + * + * To be valid, this font name must be available on the client system. + * + * @param font name +*/ +var fontName:String? +get() { +return font!!.getFontName() +} +set(fontName) { +val f = cloneFont(xf!!.font!!) +f.setFontName(fontName) +updateFont(f) +} + +/** + * Determine if the format handle refers to a font stricken out + * + * @return boolean representing if the FormatHandle is striking out a cell. +*/ +/** + * Set if the format handle is stricken out + * + * @param isStricken boolean representing if the formatted cell should be stricken + * out. +*/ +var stricken:Boolean +get() { +if (xf!!.font == null) +return false +return xf!!.font!!.stricken +} +set(isStricken) { +val f = cloneFont(xf!!.font!!) +f.stricken = isStricken +updateFont(f) +} + +/** + * Get if the font is italic + * + * @return boolean representing if the formatted cell is italic. +*/ +/** + * Set if the font is italic + * + * @param isItalic boolean representing if the formatted cell should be italic. +*/ +var italic:Boolean +get() { +if (xf!!.font == null) +return false +return xf!!.font!!.italic +} +set(isItalic) { +val f = cloneFont(xf!!.font!!) +f.italic = isItalic +updateFont(f) +} + +/** + * Get if the font is bold + * + * @return boolean representing if the formatted cell is bold +*/ +/** + * Set the format handle to use standard bold text + * + * @param boolean isBold +*/ +var bold:Boolean +get() { +return xf!!.font!!.bold +} +set(isBold) { +val f = cloneFont(xf!!.font!!) +f.bold = isBold +updateFont(f) +} + +/** + * returns the existing font record for this Format + * + * + * Font is an internal record and should not be accessed by end users + * + * @return the XLS Font record associated with this Format +*/ +/** + * Set the Font for this Format. + * + * + * As adding a new Font and format increases the file size, try using this + * once for each distinct font used in the file, then use 'setFormatId' to + * share this font with other Cells. + * + * + * Roughly matches the functionality of the java.awt.Font class. Currently + * the style parameter is only useful for bold/normal weights. Italics, + * underlines, etc must be modified elsewhere. + * + * + * Note that in order to maintain java.awt.Font compatibility for + * bold/normal styles, defaults for weight/style have been mapped to 0 = + * normal (excel 200 weight) and 1 = bold (excel 700 weight) + * + * @param String font name + * @param int font style + * @param int font size +*/ +// should this be a protected method? +// shouldn't! +var font:io.starter.formats.XLS.Font? +get() { +if (xf != null) +return xf!!.font +return null +} +set(f) { +setXFToFont(f) +} + +/** + * Gets the number format pattern for this format, if set. For more + * information on number format patterns see [Microsoft KB264372](http://support.microsoft.com/kb/264372). + * + * @return the Excel number format pattern for this cell or + * `null` if none is applied +*/ +/** + * Sets the number format pattern for this format. All Excel built-in number + * formats are supported. Custom formats will not be applied by OpenXLS + * (e.g. [CellHandle.getFormattedStringVal]) but they will be written + * correctly to the output file. For more information on number format + * patterns see [Microsoft + * KB264372](http://support.microsoft.com/kb/264372). + * + * @param pat the Excel number format pattern to apply + * @see FormatConstantsImpl.getBuiltinFormats +*/ +var formatPattern:String? +get() { +return xf!!.formatPattern +} +set(pat) { +val xf = cloneXf(this.xf!!) +xf.formatPattern = pat +updateXf(xf) +} + +/** + * get the fill pattern for this format +*/ +val fillPattern:Int +get() { +return xf!!.fillPattern +} + +/** + * Returns an int representing the current horizontal alignment in this + * FormatHandle. These values are mapped to the FormatHandle.ALIGN*** static + * int's +*/ +/** + * Set the horizontal alignment for this FormatHandle + * + * @param align - an int representing the alignment. Please review the + * FormatHandle.ALIGN*** static int's +*/ +var horizontalAlignment:Int +get() { +return xf!!.horizontalAlignment +} +set(align) { +val xf = cloneXf(this.xf!!) +xf.horizontalAlignment = align +updateXf(xf) +} + +/** + * return indent (1 = 3 spaces) + * + * @return +*/ +/** + * set indent (1= 3 spaces) + * + * @param indent +*/ +var indent:Int +get() { +return xf!!.indent +} +set(indent) { +val xf = cloneXf(this.xf!!) +xf.indent = indent +updateXf(xf) +} + +/** + * returns true if this style is set to Right-to-Left text direction + * (reading order) + * + * @return +*/ +val rightToLetReadingOrder:Int +get() { +return xf!!.rightToLeftReadingOrder +} + +/** + * Returns an int representing the current Vertical alignment in this + * FormatHandle. These values are mapped to the FormatHandle.ALIGN*** static + * int's +*/ +/** + * Set the Vertical alignment for this FormatHandle + * + * @param align - an int representing the alignment. Please review the + * FormatHandle.ALIGN*** static int's +*/ +var verticalAlignment:Int +get() { +return xf!!.verticalAlignment +} +set(align) { +val xf = cloneXf(this.xf!!) +xf.verticalAlignment = align +updateXf(xf) +} + +/** + * Get the cell wrapping behavior for this FormatHandle. Default is false +*/ +/** + * Set the cell wrapping behavior for this FormatHandle. Default is false +*/ +var wrapText:Boolean +get() { +return xf!!.wrapText +} +set(wrapit) { +val xf = cloneXf(this.xf!!) +xf.wrapText = wrapit +updateXf(xf) +} + +/** + * Get the rotation of the cell. Value 0 means no rotation (horizontal + * text). Values 1-90 mean rotation up (couter-clockwise) by 1-90 degrees. + * Values 91-180 mean rotation down (clockwise) by 1-90 degrees. Value 255 + * means vertical text. +*/ +/** + * Set the rotation of the cell in degrees. Values 0-90 represent rotation + * up, 0-90degrees. Values 91-180 represent rotation down, 0-90 degrees. + * Value 255 is vertical + * + * @param align - an int representing the rotation. +*/ +var cellRotation:Int +get() { +return xf!!.rotation +} +set(align) { +val xf = cloneXf(this.xf!!) +xf.rotation = align +updateXf(xf) +} + +/** + * Gets the Excel format ID for this format's number format pattern. + * + * @return the Excel format identifier number for the number format pattern +*/ +/** + * Sets the number format pattern based on the format ID number. This method + * is recommended for advanced users only. In most cases you should use + * [.setFormatPattern] instead. + * + * @param fmt the format ID number for the desired number format pattern +*/ +var formatPatternId:Int +get() { +return xf!!.ifmt.toInt() +} +set(fmt) { +xf!!.setFormat(fmt.toShort()) +} + +/** + * return truth of "this Xf rec is a style xf" + * + * @return +*/ +val isStyleXf:Boolean +get() { +return xf!!.isStyleXf +} + +/** + * Nullary constructor for use in bean context. **This is not part of the + * public API and should not be called.** +*/ +constructor() {} + +/** + * Constructs a FormatHandle for the given WorkBook and format record. + * **This is not part of the public API and should not be called.** +*/ +constructor(book:io.starter.OpenXLS.WorkBook?, xfr:Xf) { +xf = xfr +formatId = xf!!.idx +wbh = book +if (book != null) +{ +workBook = book!!.workBook +} +else +{ +if (xfr.workBook != null) +{ +workBook = xfr.workBook +} +else +{ +Logger.logErr("FormatHandle constructed with null WorkBook.") +} +} +} + +/** + * Constructs a FormatHandle for the given WorkBook's default format. +*/ +constructor(book:io.starter.OpenXLS.WorkBook) : this(book, book.workBook.defaultIxfe) {} + +/** + * Constructs a FormatHandle for the given format ID and WorkBook. + * This is useful for creating a FormatHandle with the same parameters as + * a cell that does not refer to the cell. For example: + *
                  + * CellHandle cell = <get cell here>;
                  + * FormatHandle format = new FormatHandle(
                  + * cell.getWorkBook(), cell.getFormatId() );
                  +
                  * + * + * @param book the WorkBook from which the format should be retrieved + * @param xfnum the ID of the format +*/ +constructor(book:io.starter.formats.XLS.WorkBook, xfnum:Int) { +workBook = book +if (xfnum > -1 && xfnum < workBook!!.numXfs) +{ +xf = workBook!!.getXf(xfnum) +formatId = xf!!.idx +} +if (xf == null) +{ // add new xf if necessary +xf = duplicateXf(null) +} +xf!!.font // will set to default (0th) font if not already set +} + +/** Constructs a FormatHandle for the given format ID and WorkBook. + * This is useful for creating a FormatHandle with the same parameters as + * a cell that does not refer to the cell. For example: + *
                  + * CellHandle cell = <get cell here>;
                  + * FormatHandle format = new FormatHandle(
                  + * cell.getWorkBook(), cell.getFormatId() );
                  +
                  * + * + * @param book + * the WorkBook from which the format should be retrieved + * @param xfnum + * the ID of the format public FormatHandle(WorkBook book, int + * xfnum ,int x) { this(book.getWorkBook(),xfnum); wbh = book; } +*/ + +/** + * Constructs a FormatHandle for the given format index and WorkBook. + * **This is not part of the public API and should not be called.** +*/ +/* + * This constructor is just used from XML parsing, due to some dedupe + * errors. possibly errors in .xlsx too? +*/ +protected constructor(book:io.starter.OpenXLS.WorkBook, xfnum:Int, +dedupe:Boolean) : this(book, xfnum) { +writeImmediate = dedupe +} + +/** + * Constructs a dummy FormatHandle for the given conditional format. **This + * is not part of the public API and should not be called.** + * + * + * This unique flavor of FormatHandle is only used to display the formatting + * values of a Conditional format record. + * + * + * Creates a dummy Xf to store values, otherwise has no effect on the + * WorkBook record stream which are manipulated through CellHandle. + * + * @param book containing the conditional formats + * @param the index to the conditional format in the book collection +*/ +constructor(cx:Condfmt, book:io.starter.OpenXLS.WorkBook, +xfnum:Int, c:CellHandle?) { +var c = c +cx.formatHandle = this +formatId = xfnum +wbh = book +workBook = book.workBook +if (c == null) +{ +// ok, this is a horrible hack, as its only correct if the top left +// cell of the range has the same background format +// as the cell a user is hitting. Lame, but i've been handed this at +// the last moment and am patching things. weak effort guys. +try +{ +val rc = cx.encompassingRange +c = CellHandle(cx.sheet!!.getCell(rc[0], rc[1]), +book) +} +catch (e:Exception) {} + +} +xf = duplicateXf(book.workBook.getXf(c!!.formatId)) + +// set the format from the cf +val lx = cx.rules +val itx = lx.iterator() +while (itx.hasNext()) +{ +val format = itx.next() as Cf +this.updateFromCF(format, book) +} +} + +/** + * updates this format handle via a Cf rule + * + * @param cf Cf rule + * @param book workbook +*/ +fun updateFromCF(cf:Cf, book:io.starter.OpenXLS.WorkBook) { +// border colors +val clr = cf.borderColors +if (clr != null) +borderColors = clr + +// line style +val xs = cf.borderStyles +if (xs != null) +this.setBorderLineStyle(xs) + +/* + * // cf.getBorderSizes() int[] b = cf.getBorderSizes(); if(b!=null) + * setBorderLineStyle(b); +*/ +// cf.getFont() +val f = cf.getFont() +if (f != null) +font = f +else +this.fontHeight = 180 // why???????? + +if (cf.fontItalic) +this.italic = true + +if (cf.fontStriken) +this.stricken = true + +val fsup = cf.fontEscapement +// super/sub (0 = none, 1 = super, 2 = sub) +if (fsup > -1) +this.setScript(fsup) + +// handle underlines +val us = cf.getFontUnderlineStyle() + +if (us > -1) +{ +this.underlineStyle = us +this.setUnderlined(true) +} + +// number cf +if (cf.formatPattern != null) +formatPattern = cf.formatPattern + +if (cf.fill != null) +{ +this.setFill(cf.fill) +} +else +{ +val fill = cf.getPatternFillStyle() // Now -1 is a valid entry: +if (fill > -1) +{ +/* If the fill style is solid: When solid is specified, the +foreground color (fgColor) is the only color rendered, +even when a background color (bgColor) is also +specified. */ +val bg = cf.getPatternFillColorBack() +val fg = cf.getPatternFillColor() +this.setFill(fill, fg, bg) +} +else +{ +val fg = cf.foregroundColor +if (fg > -1) +foregroundColor = fg +} +} +} + +/** + * Creates a FormatHandle for the given cell. **This is not part of the + * public API and should not be called.** Customers should use + * [CellHandle.getFormatHandle] instead. +*/ +constructor(c:CellHandle) { +workBook = c.cell!!.workBook +if (c.cell!!.xfRec != null) +{ +xf = c.cell!!.xfRec +formatId = xf!!.idx // update the pointer - 20071010 KSC +} +else +{ // ?? create new +// 20090512 KSC: Shigeo NPE error formaterror646694, create new +// rather than outputting warning +// Logger.logWarn("No XF for cell " + c.toString()); +xf = workBook!!.getXf(c.cell!!.ixfe) +formatId = xf!!.idx +} +// 20101201 KSC: only add to cache when adding xf's addToCache(); +} + +/** + * overrides the equals method to perform equality based on format + * properties ------------------------------------------------------------ + * + * @param Object another - the FormatHandle to compare with this FormatHandle + * @return true if this FormatHandle equals another +*/ +public override fun equals(another:Any?):Boolean { +return another!!.toString() == toString() +} + +/** + * remove borders for this format +*/ +fun removeBorders() { +val xf = cloneXf(this.xf!!) +xf.removeBorders() +updateXf(xf) +} + +/** + * sets the border color for all borders (top, left, bottom and right) from + * an int array containing color constants + * + * + * + * NOTE: this setting will affect every cell which refers to this + * FormatHandle + * + * @param int[] bordercolors - 4-element array of desired border color + * constants [T, L, B, R] + * @see FormatHandle.COLOR_* constants +*/ +fun setBorderColors(bordercolors:IntArray) { +val xf = cloneXf(this.xf!!) +xf.topBorderColor = bordercolors[0] +xf.leftBorderColor = bordercolors[1] +xf.bottomBorderColor = bordercolors[2] +xf.setRightBorderColor(bordercolors[3]) +updateXf(xf) + +} + +/** + * set the border color for all borders (top, left, bottom, and right) to + * one color via color constant + * + * + * + * NOTE: this setting will affect every cell which refers to this + * FormatHandle + * + * @param int x - color constant which represents the color to set all + * border sides + * @see FormatHandle.COLOR_* constants +*/ +fun setBorderColor(x:Int) { +val xf = cloneXf(this.xf!!) +xf.setRightBorderColor(x.toShort().toInt()) +xf.setLeftBorderColor(x.toShort()) +xf.topBorderColor = x.toShort() +xf.bottomBorderColor = x.toShort() +updateXf(xf) + +} + +/** + * set the border color for all borders (top, left, bottom, and right) to + * one java.awt.Color + * + * + * NOTE: this setting will affect every cell which refers to this + * FormatHandle + * + * @param Color col - color to set all border sides +*/ +fun setBorderColor(col:Color) { +val xf = cloneXf(this.xf!!) +val x = getColorInt(col).toShort() +xf.setRightBorderColor(x.toInt()) +xf.setLeftBorderColor(x) +xf.topBorderColor = x +xf.bottomBorderColor = x +updateXf(xf) + +} + +/** + * Set the top border color + * + * @param color constant +*/ +fun setBorderRightColor(x:Int) { +val xf = cloneXf(this.xf!!) +xf.setRightBorderColor(x.toShort().toInt()) +updateXf(xf) +} + +/** + * Set the top border color + * + * @param color constant +*/ +fun setBorderRightColor(x:Color) { +val xf = cloneXf(this.xf!!) +xf.setRightBorderColor(getColorInt(x).toShort().toInt()) +updateXf(xf) +} + +/** + * Set the top border color + * + * @param color constant +*/ +fun setBorderLeftColor(x:Int) { +val xf = cloneXf(this.xf!!) +xf.setLeftBorderColor(x.toShort()) +updateXf(xf) +} + +/** + * Set the top border color + * + * @param color constant +*/ +fun setBorderLeftColor(x:Color) { +val xf = cloneXf(this.xf!!) +xf.setLeftBorderColor(getColorInt(x).toShort()) +updateXf(xf) +} + +/** + * Set the top border color + * + * @param color constant +*/ +fun setBorderTopColor(x:Int) { +val xf = cloneXf(this.xf!!) +xf.topBorderColor = x +updateXf(xf) +} + +/** + * Set the top border color + * + * @param color constant +*/ +fun setBorderTopColor(x:Color) { +val xf = cloneXf(this.xf!!) +xf.topBorderColor = getColorInt(x) +updateXf(xf) +} + +/** + * Set the top border color + * + * @param color constant +*/ +fun setBorderBottomColor(x:Int) { +val xf = cloneXf(this.xf!!) +xf.bottomBorderColor = x +updateXf(xf) +} + +/** + * Set the top border color + * + * @param color constant +*/ +fun setBorderBottomColor(x:Color) { +val xf = cloneXf(this.xf!!) +xf.bottomBorderColor = getColorInt(x) +updateXf(xf) +} + +/** + * Sets the border line style using static BORDER_ shorts within + * FormatHandle + * + * @param line style constant +*/ +fun setBorderLineStyle(x:Int) { +val xf = cloneXf(this.xf!!) +xf.setBorderLineStyle(x.toShort()) +updateXf(xf) +} + +/** + * set border line styles via array of ints representing border styles + * order= left, right, top, bottom, [diagonal] + * + * @param b int[] +*/ +fun setBorderLineStyle(b:IntArray) { +xf!!.setAllBorderLineStyles(b) +} + +/** + * Set the border line style + * + * @param line style constant +*/ +fun setBorderLineStyle(x:Short) { +val xf = cloneXf(this.xf!!) +xf.setBorderLineStyle(x) +updateXf(xf) +} + +/** + * Set the border line style + * + * @param line style constant +*/ +fun setBorderDiagonal(x:Int) { +val xf = cloneXf(this.xf!!) +xf.setBorderDiag(x) +updateXf(xf) +} + +/** + * Set a column handle on this format handle, so all changes applied to this + * format will be applied to the entire column + * + * @param c +*/ +fun setColHandle(c:ColHandle) { +mycol = c +} + +/** + * Set a row handle on this format handle, so all changes applied to this + * format will be applied to the entire row + * + * @param c +*/ +fun setRowHandle(c:RowHandle) { +myrow = c +} + +/** + * Create a copy of this FormatHandle with its own Xf + * + * @return the copied FormatHandle +*/ +fun clone():Any { +var ret:FormatHandle? = null +if (wbh == null) +{ // who knew??? +workBook = xf!!.workBook // Changed to myxf since myfont is no +// longer +ret = FormatHandle() +ret!!.xf = xf +ret!!.formatId = xf!!.idx +ret!!.workBook = workBook +} +else +{ +ret = FormatHandle(wbh, xf) // no need to duplicate it - just +// use all formatting of +// original xf +} + +return ret +} + +public override fun toString():String { +return xf!!.toString() +} + +constructor(book:io.starter.OpenXLS.WorkBook, fontname:String, +fontstyle:Int, fontsize:Int) : this(book) { +setFont(fontname, fontstyle, fontsize.toDouble()) +} + +/** + * Jan 27, 2011 + * + * @param workBook + * @param i +*/ +constructor(workBook:io.starter.OpenXLS.WorkBook, i:Int) : this(workBook.workBook, i) { +wbh = workBook +} + +/** + * Set new font to XF, handling duplication and caching ... + * + * @param Font f +*/ +private fun setXFToFont(f:Font) { +val fti = addFontIfNecessary(f) +if (xf == null) +{ // shouldn't!! +xf = duplicateXf(null) +xf!!.setFont(fti) +} +else if (xf!!.ifnt.toInt() != fti) +{ // if not using font already, +// duplicate xf and set to new font +val xf = cloneXf(this.xf!!) +xf.setFont(fti) +updateXf(xf) +} + +} + +/** + * Sets this format handle to a font + * + * @param fn font name e.g. 'Arial' + * @param stl font style either Font.PLAIN or Font.BOLD + * @param sz font size or height in 1/20 point units +*/ +fun setFont(fn:String, stl:Int, sz:Double) { +var stl = stl +var sz = sz +sz *= 20.0 +if (stl == 0) +stl = 200 +if (stl == 1) +stl = 700 +val f = Font(fn, stl, sz.toInt()) +setXFToFont(f) +} + +/** + * add font to record streamer if cant find exact font already in there + * + * @param f + * @return +*/ +private fun addFontIfNecessary(f:Font?):Int { +if (workBook == null) +{ +Logger.logErr("AddFontIfNecessary: workbook is null") +return -1 +} +var fti = workBook!!.getFontIdx(f) +// don't use the built-ins. +if (fti == 3) +fti = 0 // use initial default font instead of last ... +if (fti == -1) +{ // font doesn't exist yet, add to streamer +f!!.idx = -1 // flag to insert +fti = workBook!!.insertFont(f) + 1 +} +else +f!!.idx = fti +return fti +} + +/** + * Adds a font internally to the workbook + * + * @param f + * + * private void addFont(Font f) { + * + * } +*/ + +/** + * Apply this Format to a Range of Cells + * + * @param CellRange to apply the format to +*/ +fun addCellRange(cr:CellRange) { +val crcells = cr.getCells() +for (t in crcells!!.indices) +{ +addCell(crcells!![t]) +} +} + +/** + * Apply this Format to a Range of Cells + * + * @param CellHandle array to apply the format to +*/ +fun addCellArray(crcells:Array) { +for (t in crcells.indices) +{ +addCell(crcells[t]) +} +} + +/** + * add a Cell to this FormatHandle thus applying the Format to the Cell + * + * @param CellHandle to apply the format to +*/ +fun addCell(c:CellHandle) { +c.formatHandle = this +} + +/** + * Add a List of Cells to this FormatHandle + * + * @param cx +*/ +fun addCells(cx:List<*>) { +val itx = cx.iterator() +while (itx.hasNext()) +{ +addCell(itx.next() as BiffRec) +mycells.add(itx.next()) +} +} + +internal fun addCell(c:BiffRec) { +if (xf != null) +{ +c.setXFRecord(xf!!.idx) +} +else +{ +Logger.logWarn("FormatHandle.addCell() - You MUST call setFont() to initialize the FormatHandle's font before adding Cells.") +} +mycells.add(c) +} + +/** + * Applies the format to a cell without establishing a relationship. The + * format represented by this `FormatHandle` will be applied to + * the cell but it will not be updated with any future changes. If you want + * that behavior use [addCell][.addCell] instead. +*/ +fun stamp(cell:CellHandle) { +cell.formatId = formatId +} + +/** + * Applies the format to a cell range without establishing a relationship. + * The format represented by this `FormatHandle` will be applied + * to the cells but they will not be updated with any future changes. If you + * want that behavior use [addCell][.addCell] instead. +*/ +fun stamp(range:CellRange) { +try +{ +range.setFormatID(formatId) +} +catch (e:Exception) { +// This can't actually happen +} + +} + +/** + * set the Background Pattern for this Format + * + * @param int Excel color constant +*/ +fun setBackgroundPattern(t:Int) { +val xf = cloneXf(this.xf!!) +// 20080103 KSC: handle solid (=filled) backgrounds, in which Excel +// switches fg and bg colors (!!!) +if (t != FormatConstants.PATTERN_FILLED) +xf.setPattern(t) +else +{ +val bg = xf.backgroundColor.toInt() +xf.setBackgroundSolid() +xf.setForeColor(bg, null) +} +updateXf(xf) +} + +/** + * super/sub (0 = none, 1 = super, 2 = sub) + * + * @param int script type for Format Font +*/ +fun setScript(ss:Int) { +var ss = ss +if (ss > 2) +ss = 2 // deal with invalid numbers +if (ss < 0) +ss = 0 // deal with invalid numbers +xf!!.font!!.script = ss +} + +/** + * set the Font Color for this Format via indexed color constant + * + * @param int Excel color constant +*/ +fun setFontColor(t:Int) { +val f = cloneFont(xf!!.font!!) +f.color = t +updateFont(f) +} + +/** + * set the Font Color for this Format + * + * @param AWT Color color constant +*/ +fun setFontColor(colr:Color) { +val f = cloneFont(xf!!.font!!) +f.setColor(colr) +updateFont(f) +} + +/** + * sets the Font color for this Format via web Hex String + * + * @param clr +*/ +fun setFontColor(clr:String) { +val f = cloneFont(xf!!.font!!) +f.setColor(clr) +updateFont(f) +} + +/** + * set the foreground Color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @param AWT Color constant +*/ +fun setForegroundColor(colr:Color) { +val xf = cloneXf(this.xf!!) +val clrz = getColorInt(colr) +xf.setForeColor(clrz, colr) +updateXf(xf) +} + +/** + * set the Cell Background Color for this Format + * + * + * NOTE: Foreground color = the CELL BACKGROUND color color for all patterns + * and Background color= the PATTERN color for all patterns != Solid For + * PATTERN_SOLID, Foreground color=CELL BACKGROUND color and Background + * Color=64 (white). + * + * @param awt Color color constant +*/ +fun setCellBackgroundColor(colr:Color) { +val clrz = getColorInt(colr) +setCellBackgroundColor(clrz) + +} + +/** + * makes the cell a solid pattern background if no pattern was already + * present NOTE: Foreground color = the CELL BACKGROUND color color for all + * patterns and Background color= the PATTERN color for all patterns != + * Solid For PATTERN_SOLID, Foreground color=CELL BACKGROUND color and + * Background Color=64 (white). + * + * @param int Excel color constant +*/ +fun setCellBackgroundColor(t:Int) { +val xf = cloneXf(this.xf!!) + +if (xf.fillPattern == 0) +xf.setBackgroundSolid() + +if (xf.fillPattern == Xf.PATTERN_SOLID) +xf.setForeColor(t, null) +else +xf.setBackColor(t, null) +updateXf(xf) +} + +/** + * sets this fill pattern from an existing OOXML (2007v) fill element + * + * @param f +*/ +protected fun setFill(f:Fill?) { +val xf = cloneXf(this.xf!!) +xf.setFill(f!!) +updateXf(xf) +} + +/** + * sets the fill for this format handle if fill==Xf.PATTERN_SOLID then fg is + * the PATTERN color i.e the CELL BG COLOR + * + * @param fillpattern + * @param fg + * @param bg +*/ +fun setFill(fillpattern:Int, fg:Int, bg:Int) { +val xf = cloneXf(this.xf!!) + +xf.setPattern(fillpattern) + +/** + * If the fill style is solid: When solid is specified, the foreground + * color (fgColor) is the only color rendered, even when a background + * color (bgColor) is also specified. +*/ +if (xf.fillPattern == Xf.PATTERN_SOLID) +{ // is reversed +xf.setForeColor(bg, null) +xf.setBackColor(64, null) +} +else +{ +/** + * or cell fills with patterns specified, then the cell fill color + * is specified by the bgColor element +*/ +xf.setForeColor(fg, null) +xf.setBackColor(bg, null) +} +updateXf(xf) +} + +/** + * set the Background Color for this Format NOTE: Foreground color = the + * CELL BACKGROUND color color for all patterns and Background color= the + * PATTERN color for all patterns != Solid For PATTERN_SOLID, Foreground + * color=CELL BACKGROUND color and Background Color=64 (white). + * + * @param awt Color color constant +*/ +fun setBackgroundColor(colr:Color) { +val xf = cloneXf(this.xf!!) +val clrz = getColorInt(colr) +xf.setBackColor(clrz, colr) +updateXf(xf) +} + +/** + * Get if the font is underlined + * + * @return boolean representing if the formatted cell is underlined. +*/ +fun getUnderlined():Boolean { +if (xf!!.font == null) +return underlined +return xf!!.font!!.underlined +} + +/** + * Set underline attribute on the font + * + * @param isUnderlined boolean representing if the formatted cell should be + * underlined +*/ +fun setUnderlined(isUnderlined:Boolean) { +val f = cloneFont(xf!!.font!!) +f.underlined = isUnderlined +updateFont(f) + +} + +fun setPattern(pat:Int) { +val xf = cloneXf(this.xf!!) +xf.setPattern(pat) +updateXf(xf) +} + +/** + * increase or decrease the precision of this numeric or curerncy format pattern + *

                  If the format pattern is "General", converts to a basic number pattern + *

                  If the precision is already 0 and !increase, this method does nothing + * + * @param increase true if increase the precsion (number of decimals to display) +*/ +fun adjustPrecision(increase:Boolean) { +// TODO: if decimal is contained within quotes ... +var pat = formatPattern +if (pat == "General" && increase) +{ +pat = "0.0" // the most basic numeric pattern +this.formatPattern = pat +return +} + +try +{ +// split pattern and deal with positive, negative, zero and text separately +// for each, find decimal place and increment/decrement; if not found, find last digit placeholder +val pats = pat!!.split((";").toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() +var newPat = "" +for (i in pats.indices) +{ +if (i > 0) newPat += ';'.toString() +var z = pats[i].indexOf('.') // position of decimal +var foundit = false +if (z != -1) +{ // found decimal place +z++ +while (z < pats[i].length) +{ +val c = pats[i].get(z) +if ((c == '0' || c == '#' || c == '?')) +// numeric placeholders +foundit = true +else if (foundit && !(c == '0' || c == '#' || c == '?')) +// numeric placeholders. if hit last one, either inc or dec +break +z++ +} +if (increase) +newPat += StringBuffer(pats[i]).insert(z, "0").toString() //pats[i].substring(0, z) + "0" + pats[i].substring(z+1); +else +{ +if (pats[i].get(z - 2) != '.') +{ +newPat += StringBuffer(pats[i]).deleteCharAt(z - 1).toString() // .pats[i].substring(0, z-1) + pats[i].substring(z+1); +} +else +{ +newPat += StringBuffer(pats[i]).delete(z - 2, z).toString() +} +} + +} +else if (increase) +{ // no decimal yet. If decrease, ignore. if increase, add +z = pats[i].length - 1 +while (z >= 0) +{ +val c = pats[i].get(z) +if ((c == '0' || c == '#' || c == '?')) +{ // found last numeric placeholder +foundit = true +break +} +z-- +} +if (foundit) +// if had ANY numeric placeholders +newPat += StringBuffer(pats[i]).insert(z + 1, ".0").toString() //pats[i].substring(0, z) + ".0" + pats[i].substring(z+1); +else +newPat += pats[i] // keep original +} +else +// if decrease and no decimal found, leave alone +newPat += pats[i] // leave alone +} + +//io.starter.toolkit.Logger.log("Old Style" + pat + ". New Style: " + newPat + ". Increase?" + (increase?"yes":"no")); // KSC: TESETING: TAKE OUT WHEN DONE +this.formatPattern = newPat +} +catch (e:Exception) { +Logger.logErr("Error setting style") // KSC: TESETING: TAKE OUT WHEN DONE +} + +} + +/** + * sets the Right to Left Text Direction or reading order of this style + * + * @param rtl possible values: + *

                  0=Context Dependent + *

                  1=Left-to-Right + *

                  2=Right-to-Let + * @param rtl possible values:

                  + * 0=Context Dependent

                  + * 1=Left-to-Right

                  + * 2=Right-to-Let +*/ +fun setRightToLeftReadingOrder(rtl:Int) { +val xf = cloneXf(this.xf!!) +xf.rightToLeftReadingOrder = rtl +updateXf(xf) +} + +/** + * DEPRECATED and non functional. Not neccesary as this occurs automatically + * + * + * Consolidates this Format with other identical formats in workbook + * + * + * + * + * There is a limit to the number of distinct formats in an Excel workbook. + * + * + * This method allows you to share Formats between identically formatted + * cells. + * + * @return +*/ +@Deprecated("") +fun pack():FormatHandle { +return this // wkbook.cache.get(this); +} + +/** + * Get a JSON representation of the format + * + * @param cr + * @return +*/ +fun getJSON(XFNum:Int):String { +return getJSONObject(XFNum).toString() +} + +/** + * Get a JSON representation of the format + * + * + * font height is represented as HTML pt size + * + * @param cr + * @return +*/ +fun getJSONObject(XFNum:Int):JSONObject { +val myf = font +val theStyle = JSONObject() +try +{ +theStyle.put("style", XFNum) +// handle the font +val theFont = JSONObject() +theFont.put("name", myf!!.getFontName()) + +// round out the font size... +val sz = Math.round(myf!!.fontHeight / 22.0) + +theFont.put("size", sz) // adjust smaller +theFont.put("color", colorToHexString(myf!!.colorAsColor)) +theFont.put("weight", myf!!.fontWeight) +if (isBold) +{ +theFont.put("bold", "1") +} +if (this.getUnderlined()) +{ +theFont.put("underline", "1") +} +else +{ +theFont.put("underline", "0") +} + +if (italic) +{ +theFont.put("italic", "1") +} + +theStyle.put("font", theFont) + +// +val border = JSONObject() +if (rightBorderLineStyle != 0) +{ +val rBorder = JSONObject() +rBorder.put("style", +FormatConstants.BORDER_STYLES_JSON[rightBorderLineStyle]) +rBorder.put("color", colorToHexString(borderRightColor!!)) +border.put("right", rBorder) +} +if (bottomBorderLineStyle != 0) +{ +val bBorder = JSONObject() +bBorder.put("style", +FormatConstants.BORDER_STYLES_JSON[bottomBorderLineStyle]) +bBorder.put("color", colorToHexString(borderBottomColor!!)) +border.put("bottom", bBorder) +} +if (leftBorderLineStyle != 0) +{ +val lBorder = JSONObject() +lBorder.put("style", +FormatConstants.BORDER_STYLES_JSON[leftBorderLineStyle]) +lBorder.put("color", colorToHexString(borderLeftColor!!)) +border.put("left", lBorder) +} +if (topBorderLineStyle != 0) +{ +val tBorder = JSONObject() +tBorder.put("style", +FormatConstants.BORDER_STYLES_JSON[topBorderLineStyle]) +tBorder.put("color", colorToHexString(borderTopColor!!)) +border.put("top", tBorder) +} +theStyle.put("borders", border) + +// +val alignment = JSONObject() +alignment.put("horizontal", +FormatConstants.HORIZONTAL_ALIGNMENTS[horizontalAlignment]) +alignment.put("vertical", +FormatConstants.VERTICAL_ALIGNMENTS[verticalAlignment]) +if (wrapText) +{ +alignment.put("wrap", "1") +} +theStyle.put("alignment", alignment) + +if (indent != 0) +{ +theStyle.put("indent", indent) +} +// colors + background patterns +if (fillPattern >= 0) +{ // KSC: added >= 0 as some conditional formats have patternFillStyle==0 even though they have a pattern block and +val interior = JSONObject() +// weird black/white case +if (xf!!.foregroundColor.toInt() == 65) +{ +interior.put("color", "#FFFFFF") +} +else +{ +// KSC: use color string if it exists; create if doesn't +interior.put("color", xf!!.foregroundColorHEX) +} +interior.put("pattern", xf!!.fillPattern) +interior.put("fg", xf!!.foregroundColor.toInt()) // Excel-2003 Color Table index +interior.put("patterncolor", xf!!.backgroundColorHEX) +interior.put("bg", xf!!.backgroundColor.toInt()) // Excel-2003 Color Table index +theStyle.put("interior", interior) +} + +if (xf!!.ifmt.toInt() != 0) +{ // only input user defined formats ... +val nFormat = JSONObject() +val fmtpat = formatPattern +try +{ +if (fmtpat != "General") +{ +nFormat.put("format", fmtpat) // convertXMLChars? +nFormat.put("formatid", xf!!.ifmt.toInt()) +if (isDate) +nFormat.put("isdate", "1") +if (isCurrency) +nFormat.put("iscurrency", "1") +if (isRedWhenNegative) +nFormat.put("isrednegative", "1") +} + +} +catch (e:Exception) { // it's possible that getFormatPattern +// returns null +} + +theStyle.put("numberformat", nFormat) +} +// +val protection = JSONObject() +try +{ +if (this.xf!!.isLocked) +protection.put("Protected", true) +if (this.xf!!.isFormulaHidden) +protection.put("HideFormula", true) +} +catch (e:Exception) {} + +theStyle.put("protection", protection) + +} +catch (e:JSONException) { +Logger.logErr("Error getting cellRange JSON: " + e) +} + +return theStyle +} + +/** + * Returns an XML fragment representing the FormatHandle + * + * @param convertToUnicodeFont if true, font family will be changed to ArialUnicodeMS + * (standard unicode) for non-ascii fonts +*/ +@JvmOverloads fun getXML(XFNum:Int, convertToUnicodeFont:Boolean = false):String { +val myf = font +// ") +return sb.toString() +} + +/** + * creates a new font based on an existing one, adds to workbook recs +*/ +private fun createNewFont(f:Font):Font { +f.idx = -1 // flag to insert anew (see Font.setWorkBook) +workBook!!.insertFont(f) +f.workBook = workBook +return f +} + +/** + * create a new font based on existing font - does not add to workbook recs + * + * @param src font + * @return cloned font +*/ +private fun cloneFont(src:Font):Font { +val f = Font() +f.opcode = io.starter.formats.XLS.XLSConstants.FONT +f.setData(src.bytes) // use default font as basis of new font +f.idx = -2 // avoid adding to fonts array when call setW.b. below +f.workBook = this.workBook +f.init() +return f +} + +/** + * update the font for this FormatHandle, including updating the xf if + * necessary + * + * @param f +*/ +private fun updateFont(f:Font) { +var f = f +val idx = workBook!!.getFontIdx(f) +if (idx == -1) +{ // can't find it so add new +f = createNewFont(f) +} +else +f = workBook!!.getFont(idx) +if (f.idx != xf!!.ifnt.toInt()) +{ // then updated the font, must create new xf to link to +val xf = cloneXf(this.xf!!) +xf.setFont(f.idx) +updateXf(xf) +} +} + +/** + * Create or Duplicate Xf rec so can alter (pattern, font, colrs ...) + * + * @param xf xf to base off of, or null (will create new) + * @return new Xf +*/ +private fun duplicateXf(xf:Xf?):Xf { +var xf = xf +var fidx = 0 +if (xf != null) +fidx = xf!!.font!!.idx +xf = Xf.updateXf(xf, fidx, workBook) // clones/creates new based upon +// original +formatId = xf!!.idx // update the pointer +// not used anymore canModify = true; // if duplicated, it's new and unlinked (thus far) +return xf +} + +/** + * adds all formatting represented by the sourceXf to this workbook, if not + * already present

                  + * This is used internally for transferring formats from one workbook to + * another + * + * @param xf - sourceXf + * @return ixfe of added Xf +*/ +fun addXf(sourceXf:Xf):Int { +// must handle font first in order to create xf below +// check to see if the font needs to be added in current workbook +val fidx = addFontIfNecessary(sourceXf.font) + +/** XF */ +val localXf = FormatHandle.cloneXf(sourceXf, workBook!!.getFont(fidx), +workBook) // clone xf so modifcations don't affect original + +/** NUMBER FORMAT */ +val fmt = sourceXf.formatPattern // number format pattern +if (fmt != null) +localXf.formatPattern = fmt // adds new format pattern if not // found + +// now check out to see if this particular xf pattern exists; if not, add +updateXf(localXf) +return formatId +} + +/** + * if existing format matches, reuse. otherwise, create new Xf record and + * add to cache + * + * @param xf +*/ +private fun updateXf(xf:Xf) { +if (this.xf!!.toString() != xf.toString()) +{ +if (this.xf!!.useCount <= 1 && formatId > 15) +{ // used only by one cell, OK to modify +if (writeImmediate || workBook!!.formatCache.get(xf.toString()) == null) +{ +// myxf hasn't been used yet; modify bytes and re-init *** +val xfbytes = xf.bytes +this.xf!!.setData(xfbytes) +if (xf.fill != null) +this.xf!!.fill = xf.fill!!.cloneElement() as io.starter.formats.OOXML.Fill +this.xf!!.init() +this.xf!!.setFont(this.xf!!.ifnt.toInt()) // set font as well .. +workBook!!.updateFormatCache(this.xf!!) // ensure new xf signature +// is stored +} +else +{ +if (this.xf!!.useCount > 0) +this.xf!!.decUseCoount() // flag original xf that 1 less record is referencing it +this.xf = workBook!!.formatCache.get(xf.toString()) as Xf +formatId = this.xf!!.idx // update the pointer +if (formatId == -1) +// hasn't been added to wb yet - should this ever happen??? +this.xf = duplicateXf(xf) // create a duplicate and leave original +else +this.xf!!.incUseCount() +} +} +else +{ // cannot modify original - either find matching or create new +if (this.xf!!.useCount > 0) +this.xf!!.decUseCoount() // flag original xf that 1 less record is referencing it +if (workBook!!.formatCache.get(xf.toString()) == null) +{ // doesn't exist yet +this.xf = duplicateXf(xf) // create a duplicate and leave original +} +else +{ +this.xf = (workBook!!.formatCache.get(xf.toString())) as Xf +formatId = this.xf!!.idx // update the pointer +if (formatId == -1) +// hasn't been added to the record store yet // - should ever happen??? +this.xf = duplicateXf(xf) // create a duplicate and leave original +else +this.xf!!.incUseCount() +} +} + +for (i in mycells.indices) +{ +(mycells.get(i) as BiffRec).setXFRecord(formatId) // make sure all linked cells are updated as well +} +if (mycol != null) +mycol!!.formatId = formatId +if (myrow != null) +myrow!!.formatId = formatId +} +} + +/** + * create a duplicate xf rec based on existing ... + * + * @param xf + * @return cloned xf +*/ +private fun cloneXf(xf:Xf):Xf { +val clone = Xf(xf.font!!.idx, workBook) +val data = xf.getBytesAt(0, xf.length - 4) +clone.setData(data) +if (xf.fill != null) +clone.fill = xf.fill!!.cloneElement() as io.starter.formats.OOXML.Fill +clone.init() +return clone +} + +/** + * clear out object references +*/ +fun close() { +mycells.clear() +mycells = CompatibleVector() // all the Cells sharing this format +xf = null +mycol = null +myrow = null +workBook = null +wbh = null +} + +@Throws(Throwable::class) +protected fun finalize() { +try +{ +close() // close open files +} + +finally +{ +super.finalize() +} +} + +companion object { + +val numericFormatMap:Map +val currencyFormatMap:Map +val dateFormatMap:Map + +init{ +val formats = HashMap() +for (formatArr in FormatConstants.NUMERIC_FORMATS) +{ +if (formatArr.size == 3) +{ +formats.put(formatArr[0].toLowerCase(), formatArr[2]) +} +} +numericFormatMap = Collections.unmodifiableMap(formats) + +formats = HashMap() +for (formatArr in FormatConstants.CURRENCY_FORMATS) +{ +if (formatArr.size == 3) +{ +formats.put(formatArr[0].toLowerCase(), formatArr[2]) +} +} +currencyFormatMap = Collections.unmodifiableMap(formats) + +formats = HashMap() +for (formatArr in FormatConstants.DATE_FORMATS) +{ +if (formatArr.size == 3) +{ +formats.put(formatArr[0].toLowerCase(), formatArr[2]) +} +else +{ +// only 2 elements in date format string array: pattern and hex id +formats.put(formatArr[0].toLowerCase(), formatArr[0]) +} +} +dateFormatMap = Collections.unmodifiableMap(formats) +} + +/** + * converts an Excel-style format string to a Java Format string. + * + * @param String pattern - Excel Format String + * @return String that can be used with the Java Format classes. + * @see getJavaFormatString +*/ +fun convertFormatString(pattern:String):String? { +var ret:String? = numericFormatMap.get(pattern) +if (ret != null) +return ret +ret = currencyFormatMap.get(pattern) +if (ret != null) +return ret +ret = dateFormatMap.get(pattern) +return ret +} + +/** + * adds a font to the global font store only if exact font is not already + * present + * + * @param f Font + * @param bk WorkBookHandle +*/ +fun addFont(f:Font, bk:WorkBookHandle?):Int { +if (bk == null) +{ +Logger.logErr("addFont: workbook is null") +} +var fti = bk!!.workBook!!.getFontIdx(f) +// if (fti > 3) {// don't use the built-ins. +if (fti == 3) +fti = 0 // use initial default font instead of last ... 20070827 +// KSC +if (fti == -1) +{ // font doesn't exist yet, add to streamer +f.idx = -1 // flag to insert +fti = bk!!.workBook!!.insertFont(f) + 1 +} +else +f.idx = fti +return fti +} + +/** + * returns the index of the Color within the Colortable + * + * @param the index of the color in the colortable + * @return the color +*/ +fun getColor(col:Int):Color { +if (col > -1 && col < FormatHandle.COLORTABLE.size) +return FormatHandle.COLORTABLE[col] +return FormatHandle.COLORTABLE[0] +} + +/** + * returns the index of the Color within the Colortable + * + * @param col the color + * @return the index of the color in the colortable +*/ +fun getColorInt(col:Color):Int { +for (i in FormatHandle.COLORTABLE.indices) +{ +if (col == FormatHandle.COLORTABLE[i]) +{ +return i +} +} +val R = col.getRed() +val G = col.getGreen() +val B = col.getBlue() +var colorMatch = -1 +var colorDiff = Integer.MAX_VALUE.toDouble() +for (i in FormatHandle.COLORTABLE.indices) +{ +val curDif = (Math.pow((R - FormatHandle.COLORTABLE[i].getRed()).toDouble(), 2.0) ++ Math.pow((G - FormatHandle.COLORTABLE[i].getGreen()).toDouble(), 2.0) ++ Math.pow((B - FormatHandle.COLORTABLE[i].getBlue()).toDouble(), 2.0)) +if (curDif < colorDiff) +{ +colorDiff = curDif +colorMatch = i +} +} +return colorMatch +} + +/** + * Convert a java.awt.Color to a hex string. + * + * @return String representation of a Color +*/ +fun colorToHexString(c:Color):String { +val r = c.getRed() +val g = c.getGreen() +val b = c.getBlue() +var rh = Integer.toHexString(r) +if (rh.length < 2) +{ +rh = "0" + rh +} +var gh = Integer.toHexString(g) +if (gh.length < 2) +{ +gh = "0" + gh +} +var bh = Integer.toHexString(b) +if (bh.length < 2) +{ +bh = "0" + bh +} +return ("#" + rh + gh + bh).toUpperCase() +} + + +var colorFONT:Short = 0 +var colorBACKGROUND:Short = 1 +var colorFOREGROUND:Short = 2 +var colorBORDER:Short = 3 + +/** + * convert hex string RGB to Excel colortable int format if an exact match + * is not find, does color-matching to try and obtain closest match + * + * @param s + * @param colorType + * @return +*/ +fun HexStringToColorInt(s:String, colorType:Short):Int { +var s = s +if (s.length > 7) +s = "#" + s.substring(s.length - 6) +if (s.indexOf("#") == -1) +s = "#" + s +if (s.length == 7) +{ +val rs = s.substring(1, 3) +val r = Integer.parseInt(rs, 16) +val gs = s.substring(3, 5) +val g = Integer.parseInt(gs, 16) +val bs = s.substring(5, 7) +val b = Integer.parseInt(bs, 16) +// Handle exceptions for black, white and color indexes 9 (see +// FormatConstants for more info) + +if (r == 255 && r == g && r == b) +{ +if (colorType == colorFONT) +return 9 +} + +val c = Color(r, g, b) +return getColorInt(c) +} +return 0 +} + +/** + * convert hex string RGB to Excel colortable int format if no exact match + * is found returns -1 + * + * @param s HTML color string (#XXXXXX) format or (#FFXXXXXX - OOXML-style + * format) + * @param colorType + * @return index into color table or -1 if not found +*/ +fun HexStringToColorIntExact(s:String, colorType:Short):Int { +var s = s +if (s.length > 7) +s = "#" + s.substring(s.length - 6) +if (s.indexOf("#") == -1) +s = "#" + s +if (s.length == 7) +{ +val rs = s.substring(1, 3) +val r = Integer.parseInt(rs, 16) +val gs = s.substring(3, 5) +val g = Integer.parseInt(gs, 16) +val bs = s.substring(5, 7) +val b = Integer.parseInt(bs, 16) + +// Handle exceptions for black, white and color indexes 9 (see +// FormatConstants for more info) +if (r == 255 && r == g && r == b) +{ +if (colorType == colorFONT) +return 9 +} + +val c = Color(r, g, b) +for (i in FormatHandle.COLORTABLE.indices) +{ +if (c == FormatHandle.COLORTABLE[i]) +{ +return i +} +} +} +return -1 // match NOT FOUND +} + +/** + * convert hex string RGB to a Color + * + * @param s web-style hex color string, including #, or OOXML-style color string, FFXXXXXX + * @return Color + * @see Color +*/ +fun HexStringToColor(s:String):Color { +var s = s +if (s.length > 7) +{ // transform OOXML-style color strings to Web-style +s = "#" + s.substring(s.length - 6) +} +var c:Color? = null +if (s.length == 7) +{ +val rs = s.substring(1, 3) +val r = Integer.parseInt(rs, 16) +val gs = s.substring(3, 5) +val g = Integer.parseInt(gs, 16) +val bs = s.substring(5, 7) +val b = Integer.parseInt(bs, 16) +c = Color(r, g, b) +} +else +c = Color(0, 0, 0) // default to black? +return c +} + +/** + * interpret color table special entries + * + * @param clr color index in range of 0x41-0x4F (charts) 0x40 ... + * @return index into color table +*/ +fun interpretSpecialColorIndex(clr:Int):Short { +when (clr) { +0x0041 // Default background color. This is the window background +, +// color in the sheet display and is the default +// background color for a cell. +0x004E // Default chart background color. This is the window +, +// background color in the chart display. +0x0050 // WHAT IS THIS ONE???????? +-> return FormatConstants.COLOR_WHITE.toShort() +0x0040 // Default foreground color +, 0x004F // Chart neutral color which is black, an RGB value of +, +// (0,0,0). +0x004D // Default chart foreground color. This is the window text +, +// color in the chart display. +0x0051 // ToolTip text color. This is the automatic font color for +, +// comments. +0x7FFF // Font automatic color. This is the window text color +-> return FormatConstants.COLOR_BLACK.toShort() +else // 67(=0x43) ??? +-> return FormatConstants.COLOR_WHITE.toShort() +} + +/* + * switch (icvFore) { case 0x40: // default fg color return + * FormatConstants.COLOR_WHITE; case 0x41: // default bg color return + * FormatConstants.COLOR_WHITE; case 0x4D: // default CHART fg color -- + * INDEX SPECIFIC! return -1; // flag to map via series (bar) color + * defaults case 0x4E: // default CHART fg color return icvFore; case + * 0x4F: // chart neutral color == black return + * FormatConstants.COLOR_BLACK; } + * + * switch (icvBack) { case 0x40: // default fg color return + * FormatConstants.COLOR_WHITE; case 0x41: // default bg color return + * FormatConstants.COLOR_WHITE; case 0x4D: // default CHART fg color -- + * INDEX SPECIFIC! return -1; // flag to map via series (bar) color + * defaults case 0x4E: // default CHART bg color //return + * FormatConstants.COLOR_WHITE; // is this correct? return icvBack; case + * 0x4F: // chart neutral color == black return + * FormatConstants.COLOR_BLACK; } +*/ +} + +fun BorderStringToInt(s:String):Int { +for (i in FormatConstants.BORDER_NAMES.indices) +{ +if (FormatConstants.BORDER_NAMES[i] == s) +return i +} +return 0 +} + +/** + * static version of cloneXf + * + * @param xf + * @param wkbook + * @return +*/ +fun cloneXf(xf:Xf, wkbook:io.starter.formats.XLS.WorkBook):Xf { +val clone = Xf(xf.font!!.idx, wkbook) +val data = xf.getBytesAt(0, xf.length - 4) +clone.setData(data) +clone.init() +return clone +} + +/** + * static version of cloneXf + * + * @param xf + * @param wkbook + * @return +*/ +fun cloneXf(xf:Xf, f:Font, +wkbook:io.starter.formats.XLS.WorkBook):Xf { +val clone = Xf(f, wkbook) +val data = xf.getBytesAt(0, xf.length - 4) +clone.setData(data) +clone.setFont(f.idx) // font idx is overwritten by xf data; must reset +clone.init() +return clone +} +} +}/** + * Returns an XML fragment representing the FormatHandle + */ \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/FormulaHandle.java b/src/main/java/io/starter/OpenXLS/FormulaHandle.java deleted file mode 100644 index 9d33486..0000000 --- a/src/main/java/io/starter/OpenXLS/FormulaHandle.java +++ /dev/null @@ -1,548 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.*; -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.Logger; - -import java.util.Iterator; -import java.util.List; - - -/** - * Formula Handle allows for manipulation of Formulas within a WorkBook. - * - * @see WorkBookHandle - * @see WorkSheetHandle - * @see CellHandle - */ -public class FormulaHandle { - public static String[][] getSupportedFunctions() { - return FunctionConstants.recArr; - } - - private WorkBook bk; - - /** - * Sets the location lock on the Cell Reference at the - * specified location - *

                  - * Used to prevent updating of the Cell Reference when - * Cells are moved. - * - * @param location of the Cell Reference to be locked/unlocked - * @param lock status setting - * @return boolean whether the Cell Reference was found and modified - */ - public boolean setLocationLocked(String loc, boolean l) { - int x = Ptg.PTG_LOCATION_POLICY_UNLOCKED; - if (l) x = Ptg.PTG_LOCATION_POLICY_LOCKED; - return form.setLocationPolicy(loc, x); - } - - private Formula form; - - /** - * Sets the location lock on the Cell Reference at the - * specified location - *

                  - * Used to prevent updating of the Cell Reference when - * Cells are moved. - * - * @param location of the Cell Reference to be locked/unlocked - * @param lock status setting - * @return boolean whether the Cell Reference was found and modified - */ - public boolean setLocationPolicy(String loc, int l) { - return form.setLocationPolicy(loc, l); - } - - /** - * Create a new FormulaHandle from an Excel Formula - * - * @param Formula - the formula to create a handle for. - */ - protected FormulaHandle(Formula f, WorkBook book) { - this.bk = book; - form = f; - } - - - /** - * Returns the cell Address of the formula - */ - public String getCellAddress() { - return form.getCellAddress(); - } - - /** - * Returns the Human-Readable Formula String - * - * @return String the Formula in Human-readable format - */ - public String getFormulaString() { - return form.getFormulaString(); - } - - /** - * If the Formula evaluates to a String, return - * the value as a String. - * - * @return String - value of the Formula if stored as a String. - */ - public String getStringVal() - throws FunctionNotSupportedException { - //this.form.init(); - return form.getStringVal(); - } - - /** - * Converts a cell value to a form suitable for the public API. - * Currently this converts cached errors ({@link CalculationException}s) - * to the corresponding error string. - */ - static Object sanitizeValue(Object val) { - if (val instanceof CalculationException) - return ((CalculationException) val).getName(); - return val; - } - - /** - * Return the value of the Formula - * - * @return Object - value of the Formula - */ - public Object getVal() - throws FunctionNotSupportedException { - return sanitizeValue(form.calculateFormula()); - } - - /** Return the cached value of the Formula. - * - * This method returns the value as cached by OpenXLS or Excel of the formula. Please note - * that cases could exist where a cached value does not exist. In this case getCachedVal will not try and calculate - * the formula, it will return null. - - @return Object - cached value of the Formula as a String or a Double dependent on data type. - - public Object getCachedVal() { - return form.getCachedVal(); - } */ - - - /** - * Calculate the value of the formula and return it as an object - *

                  - * Calling calculate will ignore the WorkBook formula calculation flags - * and forces calculation of the entire formula stack - */ - public Object calculate() - throws FunctionNotSupportedException { - form.clearCachedValue(); - - return sanitizeValue(form.calculate()); - } - - - /** - * Sets the formula to a string passed in excel formula format. - * - * @param formulaString - String formatted as an excel formula, like Sum(A3+4) - */ - - public void setFormula(String formulaString) - throws FunctionNotSupportedException { - form = FormulaParser.setFormula(form, formulaString, new int[]{form.getRowNumber(), form.getColNumber()}); - } - - /** - * If the Formula evaluates to a String, there - * will be a Stringrec attached to the Formula - * which contains the latest value. - * - * @return boolean whether this Formula evaluates to a String - */ - public boolean evaluatesToString() { - return (form.calculateFormula() instanceof String); - } - - /** - * If the Formula evaluates to a float, return - * the value as an float. - *

                  - * If the workbook level flag CALCULATE_EXPLICIT is set - * then the cached value of the formula (if available) will be returned, - * otherwise the latest calculated value will be returned - * - * @return float - value of the Formula if available as a float. If the - * value cannot be returned as a float NaN will be returned. - */ - public float getFloatVal() - throws FunctionNotSupportedException { - return form.getFloatVal(); - } - - /** - * If the Formula evaluates to a double, return - * the value as an double. - *

                  - * If the workbook level flag CALCULATE_EXPLICIT is set - * then the cached value of the formula (if available) will be returned, - * otherwise the latest calculated value will be returned - * - * @return double - value of the Formula if available as a double. If the - * value cannot be returned as a double NaN will be returned. - */ - public double getDoubleVal() - throws FunctionNotSupportedException { - return form.getDblVal(); - } - - /** - * If the Formula evaluates to an int, return - * the value as an int. - *

                  - * If the workbook level flag CALCULATE_EXPLICIT is set - * then the cached value of the formula (if available) will be returned, - * otherwise the latest calculated value will be returned - * - * @return int - value of the Formula if available as a int. If the value returned can not be - * represented by an int or is a float/double with a non-zero mantissa a runtime NumberFormatException - * will be thrown - */ - public int getIntVal() - throws FunctionNotSupportedException { - return form.getIntVal(); - } - - /** - * get CellRange strings referenced by this formula - * - * @return - * @throws FormulaNotFoundException - */ - public String[] getRanges() - throws FormulaNotFoundException { - Ptg[] locptgs = form.getCellRangePtgs(); - String[] ret = new String[locptgs.length]; - for (int x = 0; x < locptgs.length; x++) { - // need sheetname along with address; to ensure, must use explicit method: -// ret[x]=locptgs[x].getTextString(); - try { - ret[x] = ((PtgRef) locptgs[x]).getLocationWithSheet(); - } catch (Exception e) { - if (locptgs[x] instanceof PtgName) {//avoid NumberFormatExceptions on parsing missing Named Ranges - ret[x] = locptgs[x].getLocation(); - } else - ret[x] = locptgs[x].getTextString(); - } - } - return ret; - } - - /** - * Initialize CellRanges referenced by this formula - * - * @return - * @throws FormulaNotFoundException - */ - public CellRange[] getCellRanges() - throws FormulaNotFoundException { - String[] crstrs = this.getRanges(); - CellRange[] crs = new CellRange[crstrs.length]; - for (int x = 0; x < crs.length; x++) { - crs[x] = new CellRange(crstrs[x], bk, true); - try { - crs[x].init(); - } catch (Exception e) { - // - } - } - return crs; - } - - /** - * Takes a string as a current formula location, and changes - * that pointer in the formula to the new string that is sent. - * This can take single cells"A5" and cell ranges,"A3:d4" - * Returns true if the cell range specified in formulaLoc exists & can be changed - * else false. This also cannot change a cell pointer to a cell range or vice - * versa. - * - * @param String - range of Cells within Formula to modify - * @param String - new range of Cells within Formula - */ - public boolean changeFormulaLocation(String formulaLoc, String newaddr) - throws FormulaNotFoundException { - List dx = form.getPtgsByLocation(formulaLoc); - Iterator lx = dx.iterator(); - while (lx.hasNext()) { - try { - Ptg thisptg = (Ptg) lx.next(); - ReferenceTracker.updateAddressPerPolicy(thisptg, newaddr); - form.setCachedValue(null); // flag to recalculate - return true; - } catch (Exception e) { - Logger.logInfo("updating Formula reference failed:" + e.toString()); - return false; - } - } - return true; - } - - /** - * Changes a range in a formula to expand until it includes the - * cell address from CellHandle. - *

                  - * Example: - *

                  - * CellHandle cell = new Cellhandle("D4") Formula = SUM(A1:B2) - * addCellToRange("A1:B2",cell); would change the formula to look like"SUM(A1:D4)" - *

                  - * Returns false if formula does not contain the formulaLoc range. - * - * @param String - the Cell Range as a String to add the Cell to - * @param CellHandle - the CellHandle to add to the range - */ - public boolean addCellToRange(String formulaLoc, CellHandle handle) - throws FormulaNotFoundException { - List dx = form.getPtgsByLocation(formulaLoc); - Iterator lx = dx.iterator(); - boolean b = false; - while (lx.hasNext()) { - Ptg ptg = (Ptg) lx.next(); - if (ptg == null) return false; - int[] formulaaddr = ExcelTools.getRangeRowCol(formulaLoc); - String handleaddr = handle.getCellAddress(); - int[] celladdr = ExcelTools.getRowColFromString(handleaddr); - - // check existing range and set new range vals if the new Cell is outside - if (celladdr[0] > formulaaddr[2]) formulaaddr[2] = celladdr[0]; - if (celladdr[0] < formulaaddr[0]) formulaaddr[0] = celladdr[0]; - if (celladdr[1] > formulaaddr[3]) formulaaddr[3] = celladdr[1]; - if (celladdr[1] < formulaaddr[1]) formulaaddr[1] = celladdr[1]; - String newaddr = ExcelTools.formatRange(formulaaddr); - b = this.changeFormulaLocation(formulaLoc, newaddr); - - } - return b; - } - - /** - * Copy the formula references with offsets - * - * @param int[row,col] offsets to move the references - * @return - */ - public static void moveCellRefs(FormulaHandle fmh, int[] offsets) throws FormulaNotFoundException { - // get the current offsets from the FMH references - String[] celladdys = fmh.getRanges(); - - // iterate - for (int x = 0; x < celladdys.length; x++) { - String[] s = ExcelTools.stripSheetNameFromRange(celladdys[x]); - String sh = s[0]; // sheet portion of address,if any - String range = s[1]; // range or single address - int rangeIdx = range.indexOf(":"); - String secondAddress = null; - if (rangeIdx > -1) { // separate out addresses within a range - secondAddress = range.substring(rangeIdx + 1); - range = range.substring(0, rangeIdx); - } - int[] orig = ExcelTools.getRowColFromString(range); - boolean relCol = !(range.startsWith("$")); // 20100603 KSC: handle relative refs - boolean relRow = !(range.length() > 0 && range.substring(1).indexOf('$') > -1); - if (relRow) // only move if relative ref - orig[0] += offsets[0]; //row - if (relCol)// only move if relative ref - orig[1] += offsets[1]; //col - String newAddress = ExcelTools.formatLocation(orig, relRow, relCol); - if (orig[0] < 0 || orig[1] < 0) newAddress = "#REF!"; - if (secondAddress != null) { - orig = ExcelTools.getRowColFromString(secondAddress); - relCol = !(secondAddress.startsWith("$")); // handle relative refs - relRow = !(secondAddress.length() > 0 && secondAddress.substring(1).indexOf('$') > -1); - if (orig[0] >= 0) { - if (relRow) // only move if relative ref - orig[0] += offsets[0]; //row - } - if (orig[1] >= 0) { //if not wholerow/wholecol ref - if (relCol)// only move if relative ref - orig[1] += offsets[1]; //col - } - String newAddress1 = ExcelTools.formatLocation(orig, relRow, relCol); - newAddress = newAddress + ":" + newAddress1; - } - if (sh != null) // TODO: handle refs with multiple sheets - newAddress = sh + "!" + newAddress; - if (!fmh.changeFormulaLocation(celladdys[x], newAddress)) { - Logger.logErr("Could not change Formula Reference: " + celladdys[x] + " to: " + newAddress); - } - } - return; - } - - - public String toString() { - return this.form.getCellAddress() + ":" + this.form.getFormulaString(); - } - - /** - * return truth of "this formula is shared" - * - * @return boolean - */ - public boolean isSharedFormula() { - return this.form.isSharedFormula(); - } - - public boolean isArrayFormula() { - return form.isArrayFormula(); - } - // 20090120 KSC: should be detected auto public void setIsArrayFormula(boolean b) { form.setIsArrayFormula(b); } - - /** - * returns the low-level formula rec for this Formulahandle - * - * @return - */ - public Formula getFormulaRec() { - return form; - } - - /** - * Utility method to determine if the calculation works out to an error value. - *

                  - * The excel values that will cause this to be true are - * #VALUE!, #N/A, #REF!, #DIV/0!, #NUM!, #NAME?, #NULL! - * - * @return - */ - public boolean isErrorValue() { - return (form.calculateFormula() instanceof CalculationException); - } - - /** - * return the "Calculate Always" setting for this formula - * used for formulas that always need calculating such as TODAY - * - * @return - */ - public boolean getCalcAlways() { - return form.getCalcAlways(); - } - - /** - * set the "Calculate Always setting for this formula - * used for formulas that always need calculating such as TODAY - * - * @param fAlwaysCalc - */ - public void setCalcAlways(boolean fAlwaysCalc) { - form.setCalcAlways(fAlwaysCalc); - } - - /** - * generate the OOXML necessary to describe this formula - * OOXML element - * - * @return - */ - // TODO: Deal with External References ... dataTables - // common possible attributes: - // aca= always calculate array, bx=name to assign formula to, ca=calculate cell, r1=data table cell1, - // t=formula type shared, array, dataTable or normal=default - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - // must have type of formula result - Object val; - try { - val = this.getVal(); - if (val == null) {// means cache was cleared (in all cases???) MUST recalc - if (this.form.getWorkBook().getCalcMode() != WorkBookHandle.CALCULATE_EXPLICIT) { - this.calculate(); - val = this.getVal(); - } else { - val = new CalculationException(CalculationException.VALUE); - } - } else if (val instanceof String && ((String) val).startsWith("#")) - val = new CalculationException(CalculationException.getErrorCode((String) val)); - } catch (Exception e) { - val = new CalculationException(CalculationException.VALUE); - } - if (val == null) { - Logger.logErr("FormulaHandle.getOOXML: unexpected null encountered when calculating formula: " + this.getCellAddress()); - } - // Handle attributes for special cached values - if (val instanceof String) { - ooxml.append(" t=\"str\""); - val = OOXMLAdapter.stripNonAscii((String) val); // TODO: how can we strip non-ascii? What about Japanese cell text? ans: has to be XML-compliant is all ... - } else if (val instanceof Boolean) { - ooxml.append(" t=\"b\""); - if (((Boolean) val).booleanValue()) val = "1"; - else val = "0"; - } else if (val instanceof Double) { - ooxml.append(" t=\"n\""); - } else if (val instanceof CalculationException) { - ooxml.append(" t=\"e\""); - } - - String fs = "="; - try { - fs = this.getFormulaString(); - } catch (Exception e) { - Logger.logErr("FormulaHandle.getOOXML: error obtaining formula string: " + e.toString()); - } - fs = OOXMLAdapter.stripNonAscii(fs).toString(); // handle non-standard xml chars -- ummm what about Japanese? -- it's all ok - if (!this.isArrayFormula()) { - ooxml.append(">"); // only output value info - } - } - if (this.isSharedFormula()) { - // TODO: FINISH 00XML SHARED FORMULAS - // TODO: need si= shared formula index; when referencing (after shared formula is defined) don't need to include "fs" just the si= **** - try { - // 20091022 KSC: Shared Formulas do not work 2003->2007 - //ooxml.append(" t=\"shared\" ref=\"" + this.getFormulaRec().getSharedFormula().getCellRange() + "\""); - } catch (Exception e) { - } - } - if (this.getCalcAlways()) ooxml.append(" ca=\"1\""); - if (fs != null) // can happen if not a parent array formula - ooxml.append(">" + fs + ""); - ooxml.append("" + val + ""); - return ooxml.toString(); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/FormulaHandle.kt b/src/main/java/io/starter/OpenXLS/FormulaHandle.kt new file mode 100644 index 0000000..a8d656a --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/FormulaHandle.kt @@ -0,0 +1,569 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.* +import io.starter.formats.XLS.formulas.* +import io.starter.toolkit.Logger + + +/** + * Formula Handle allows for manipulation of Formulas within a WorkBook. + * + * @see WorkBookHandle + * + * @see WorkSheetHandle + * + * @see CellHandle + */ +class FormulaHandle +/** + * Create a new FormulaHandle from an Excel Formula + * + * @param Formula - the formula to create a handle for. + */ +(f: Formula, private val bk: WorkBook) { + + // 20090120 KSC: should be detected auto public void setIsArrayFormula(boolean b) { form.setIsArrayFormula(b); } + + /** + * returns the low-level formula rec for this Formulahandle + * + * @return + */ + var formulaRec: Formula? = null + private set + + + /** + * Returns the cell Address of the formula + */ + val cellAddress: String + get() = formulaRec!!.cellAddress + + /** + * Returns the Human-Readable Formula String + * + * @return String the Formula in Human-readable format + */ + val formulaString: String + get() = formulaRec!!.formulaString + + /** + * If the Formula evaluates to a String, return + * the value as a String. + * + * @return String - value of the Formula if stored as a String. + */ + //this.form.init(); + val stringVal: String? + @Throws(FunctionNotSupportedException::class) + get() = formulaRec!!.stringVal + + /** + * Return the value of the Formula + * + * @return Object - value of the Formula + */ + val `val`: Any? + @Throws(FunctionNotSupportedException::class) + get() = sanitizeValue(formulaRec!!.calculateFormula()) + + /** + * If the Formula evaluates to a float, return + * the value as an float. + * + * + * If the workbook level flag CALCULATE_EXPLICIT is set + * then the cached value of the formula (if available) will be returned, + * otherwise the latest calculated value will be returned + * + * @return float - value of the Formula if available as a float. If the + * value cannot be returned as a float NaN will be returned. + */ + val floatVal: Float + @Throws(FunctionNotSupportedException::class) + get() = formulaRec!!.floatVal + + /** + * If the Formula evaluates to a double, return + * the value as an double. + * + * + * If the workbook level flag CALCULATE_EXPLICIT is set + * then the cached value of the formula (if available) will be returned, + * otherwise the latest calculated value will be returned + * + * @return double - value of the Formula if available as a double. If the + * value cannot be returned as a double NaN will be returned. + */ + val doubleVal: Double + @Throws(FunctionNotSupportedException::class) + get() = formulaRec!!.dblVal + + /** + * If the Formula evaluates to an int, return + * the value as an int. + * + * + * If the workbook level flag CALCULATE_EXPLICIT is set + * then the cached value of the formula (if available) will be returned, + * otherwise the latest calculated value will be returned + * + * @return int - value of the Formula if available as a int. If the value returned can not be + * represented by an int or is a float/double with a non-zero mantissa a runtime NumberFormatException + * will be thrown + */ + val intVal: Int + @Throws(FunctionNotSupportedException::class) + get() = formulaRec!!.intVal + + /** + * get CellRange strings referenced by this formula + * + * @return + * @throws FormulaNotFoundException + */ + // need sheetname along with address; to ensure, must use explicit method: + // ret[x]=locptgs[x].getTextString(); + //avoid NumberFormatExceptions on parsing missing Named Ranges + val ranges: Array + @Throws(FormulaNotFoundException::class) + get() { + val locptgs = formulaRec!!.cellRangePtgs + val ret = arrayOfNulls(locptgs.size) + for (x in locptgs.indices) { + try { + ret[x] = (locptgs[x] as PtgRef).locationWithSheet + } catch (e: Exception) { + if (locptgs[x] is PtgName) { + ret[x] = locptgs[x].location + } else + ret[x] = locptgs[x].textString + } + + } + return ret + } + + /** + * Initialize CellRanges referenced by this formula + * + * @return + * @throws FormulaNotFoundException + */ + // + val cellRanges: Array + @Throws(FormulaNotFoundException::class) + get() { + val crstrs = this.ranges + val crs = arrayOfNulls(crstrs.size) + for (x in crs.indices) { + crs[x] = CellRange(crstrs[x], bk, true) + try { + crs[x].init() + } catch (e: Exception) { + } + + } + return crs + } + + /** + * return truth of "this formula is shared" + * + * @return boolean + */ + val isSharedFormula: Boolean + get() = this.formulaRec!!.isSharedFormula + + val isArrayFormula: Boolean + get() = formulaRec!!.isArrayFormula + + /** + * Utility method to determine if the calculation works out to an error value. + * + * + * The excel values that will cause this to be true are + * #VALUE!, #N/A, #REF!, #DIV/0!, #NUM!, #NAME?, #NULL! + * + * @return + */ + val isErrorValue: Boolean + get() = formulaRec!!.calculateFormula() is CalculationException + + /** + * return the "Calculate Always" setting for this formula + * used for formulas that always need calculating such as TODAY + * + * @return + */ + /** + * set the "Calculate Always setting for this formula + * used for formulas that always need calculating such as TODAY + * + * @param fAlwaysCalc + */ + var calcAlways: Boolean + get() = formulaRec!!.calcAlways + set(fAlwaysCalc) { + formulaRec!!.calcAlways = fAlwaysCalc + } + + /** + * generate the OOXML necessary to describe this formula + * OOXML element + * + * @return + */ + // TODO: Deal with External References ... dataTables + // common possible attributes: + // aca= always calculate array, bx=name to assign formula to, ca=calculate cell, r1=data table cell1, + // t=formula type shared, array, dataTable or normal=default + // must have type of formula result + // means cache was cleared (in all cases???) MUST recalc + // Handle attributes for special cached values + // TODO: how can we strip non-ascii? What about Japanese cell text? ans: has to be XML-compliant is all ... + // handle non-standard xml chars -- ummm what about Japanese? -- it's all ok + // ignore = + // array formulas + // it's the parent + // remove "{= }" + // it's part of a multi-cell array formula therefore DO NOT add array info here + // only output value info + // TODO: FINISH 00XML SHARED FORMULAS + // TODO: need si= shared formula index; when referencing (after shared formula is defined) don't need to include "fs" just the si= **** + // 20091022 KSC: Shared Formulas do not work 2003->2007 + //ooxml.append(" t=\"shared\" ref=\"" + this.getFormulaRec().getSharedFormula().getCellRange() + "\""); + // can happen if not a parent array formula + val ooxml: String + get() { + val ooxml = StringBuffer() + var `val`: Any? + try { + `val` = this.`val` + if (`val` == null) { + if (this.formulaRec!!.workBook!!.calcMode != WorkBookHandle.CALCULATE_EXPLICIT) { + this.calculate() + `val` = this.`val` + } else { + `val` = CalculationException(CalculationException.VALUE) + } + } else if (`val` is String && `val`.startsWith("#")) + `val` = CalculationException(CalculationException.getErrorCode(`val` as String?)) + } catch (e: Exception) { + `val` = CalculationException(CalculationException.VALUE) + } + + if (`val` == null) { + Logger.logErr("FormulaHandle.getOOXML: unexpected null encountered when calculating formula: " + this.cellAddress) + } + if (`val` is String) { + ooxml.append(" t=\"str\"") + `val` = OOXMLAdapter.stripNonAscii(`val` as String?) + } else if (`val` is Boolean) { + ooxml.append(" t=\"b\"") + if (`val`.booleanValue()) + `val` = "1" + else + `val` = "0" + } else if (`val` is Double) { + ooxml.append(" t=\"n\"") + } else if (`val` is CalculationException) { + ooxml.append(" t=\"e\"") + } + + var fs: String? = "=" + try { + fs = this.formulaString + } catch (e: Exception) { + Logger.logErr("FormulaHandle.getOOXML: error obtaining formula string: $e") + } + + fs = OOXMLAdapter.stripNonAscii(fs).toString() + if (!this.isArrayFormula) { + ooxml.append(">") + } + } + if (this.isSharedFormula) { + try { + } catch (e: Exception) { + } + + } + if (this.calcAlways) ooxml.append(" ca=\"1\"") + if (fs != null) + ooxml.append(">$fs") + ooxml.append("$`val`") + return ooxml.toString() + } + + /** + * Sets the location lock on the Cell Reference at the + * specified location + * + * + * Used to prevent updating of the Cell Reference when + * Cells are moved. + * + * @param location of the Cell Reference to be locked/unlocked + * @param lock status setting + * @return boolean whether the Cell Reference was found and modified + */ + fun setLocationLocked(loc: String, l: Boolean): Boolean { + var x = Ptg.PTG_LOCATION_POLICY_UNLOCKED + if (l) x = Ptg.PTG_LOCATION_POLICY_LOCKED + return formulaRec!!.setLocationPolicy(loc, x) + } + + /** + * Sets the location lock on the Cell Reference at the + * specified location + * + * + * Used to prevent updating of the Cell Reference when + * Cells are moved. + * + * @param location of the Cell Reference to be locked/unlocked + * @param lock status setting + * @return boolean whether the Cell Reference was found and modified + */ + fun setLocationPolicy(loc: String, l: Int): Boolean { + return formulaRec!!.setLocationPolicy(loc, l) + } + + init { + formulaRec = f + } + + /** Return the cached value of the Formula. + * + * This method returns the value as cached by OpenXLS or Excel of the formula. Please note + * that cases could exist where a cached value does not exist. In this case getCachedVal will not try and calculate + * the formula, it will return null. + * + * @return Object - cached value of the Formula as a String or a Double dependent on data type. + * + * public Object getCachedVal() { + * return form.getCachedVal(); + * } + */ + + + /** + * Calculate the value of the formula and return it as an object + * + * + * Calling calculate will ignore the WorkBook formula calculation flags + * and forces calculation of the entire formula stack + */ + @Throws(FunctionNotSupportedException::class) + fun calculate(): Any? { + formulaRec!!.clearCachedValue() + + return sanitizeValue(formulaRec!!.calculate()) + } + + + /** + * Sets the formula to a string passed in excel formula format. + * + * @param formulaString - String formatted as an excel formula, like Sum(A3+4) + */ + + @Throws(FunctionNotSupportedException::class) + fun setFormula(formulaString: String) { + formulaRec = FormulaParser.setFormula(formulaRec, formulaString, intArrayOf(formulaRec!!.rowNumber, formulaRec!!.colNumber.toInt())) + } + + /** + * If the Formula evaluates to a String, there + * will be a Stringrec attached to the Formula + * which contains the latest value. + * + * @return boolean whether this Formula evaluates to a String + */ + fun evaluatesToString(): Boolean { + return formulaRec!!.calculateFormula() is String + } + + /** + * Takes a string as a current formula location, and changes + * that pointer in the formula to the new string that is sent. + * This can take single cells"A5" and cell ranges,"A3:d4" + * Returns true if the cell range specified in formulaLoc exists & can be changed + * else false. This also cannot change a cell pointer to a cell range or vice + * versa. + * + * @param String - range of Cells within Formula to modify + * @param String - new range of Cells within Formula + */ + @Throws(FormulaNotFoundException::class) + fun changeFormulaLocation(formulaLoc: String, newaddr: String): Boolean { + val dx = formulaRec!!.getPtgsByLocation(formulaLoc) + val lx = dx.iterator() + while (lx.hasNext()) { + try { + val thisptg = lx.next() as Ptg + ReferenceTracker.updateAddressPerPolicy(thisptg, newaddr) + formulaRec!!.setCachedValue(null) // flag to recalculate + return true + } catch (e: Exception) { + Logger.logInfo("updating Formula reference failed:$e") + return false + } + + } + return true + } + + /** + * Changes a range in a formula to expand until it includes the + * cell address from CellHandle. + * + * + * Example: + * + * + * CellHandle cell = new Cellhandle("D4") Formula = SUM(A1:B2) + * addCellToRange("A1:B2",cell); would change the formula to look like"SUM(A1:D4)" + * + * + * Returns false if formula does not contain the formulaLoc range. + * + * @param String - the Cell Range as a String to add the Cell to + * @param CellHandle - the CellHandle to add to the range + */ + @Throws(FormulaNotFoundException::class) + fun addCellToRange(formulaLoc: String, handle: CellHandle): Boolean { + val dx = formulaRec!!.getPtgsByLocation(formulaLoc) + val lx = dx.iterator() + var b = false + while (lx.hasNext()) { + val ptg = lx.next() as Ptg ?: return false + val formulaaddr = ExcelTools.getRangeRowCol(formulaLoc) + val handleaddr = handle.cellAddress + val celladdr = ExcelTools.getRowColFromString(handleaddr) + + // check existing range and set new range vals if the new Cell is outside + if (celladdr[0] > formulaaddr[2]) formulaaddr[2] = celladdr[0] + if (celladdr[0] < formulaaddr[0]) formulaaddr[0] = celladdr[0] + if (celladdr[1] > formulaaddr[3]) formulaaddr[3] = celladdr[1] + if (celladdr[1] < formulaaddr[1]) formulaaddr[1] = celladdr[1] + val newaddr = ExcelTools.formatRange(formulaaddr) + b = this.changeFormulaLocation(formulaLoc, newaddr) + + } + return b + } + + + override fun toString(): String { + return this.formulaRec!!.cellAddress + ":" + this.formulaRec!!.formulaString + } + + companion object { + val supportedFunctions: Array> + get() = FunctionConstants.recArr + + /** + * Converts a cell value to a form suitable for the public API. + * Currently this converts cached errors ([CalculationException]s) + * to the corresponding error string. + */ + internal fun sanitizeValue(`val`: Any?): Any? { + return if (`val` is CalculationException) `val`.name else `val` + } + + /** + * Copy the formula references with offsets + * + * @param int[row,col] offsets to move the references + * @return + */ + @Throws(FormulaNotFoundException::class) + fun moveCellRefs(fmh: FormulaHandle, offsets: IntArray) { + // get the current offsets from the FMH references + val celladdys = fmh.ranges + + // iterate + for (x in celladdys.indices) { + val s = ExcelTools.stripSheetNameFromRange(celladdys[x]) + val sh = s[0] // sheet portion of address,if any + var range = s[1] // range or single address + val rangeIdx = range.indexOf(":") + var secondAddress: String? = null + if (rangeIdx > -1) { // separate out addresses within a range + secondAddress = range.substring(rangeIdx + 1) + range = range.substring(0, rangeIdx) + } + var orig = ExcelTools.getRowColFromString(range) + var relCol = !range.startsWith("$") // 20100603 KSC: handle relative refs + var relRow = !(range.length > 0 && range.substring(1).indexOf('$') > -1) + if (relRow) + // only move if relative ref + orig[0] += offsets[0] //row + if (relCol) + // only move if relative ref + orig[1] += offsets[1] //col + var newAddress = ExcelTools.formatLocation(orig, relRow, relCol) + if (orig[0] < 0 || orig[1] < 0) newAddress = "#REF!" + if (secondAddress != null) { + orig = ExcelTools.getRowColFromString(secondAddress) + relCol = !secondAddress.startsWith("$") // handle relative refs + relRow = !(secondAddress.length > 0 && secondAddress.substring(1).indexOf('$') > -1) + if (orig[0] >= 0) { + if (relRow) + // only move if relative ref + orig[0] += offsets[0] //row + } + if (orig[1] >= 0) { //if not wholerow/wholecol ref + if (relCol) + // only move if relative ref + orig[1] += offsets[1] //col + } + val newAddress1 = ExcelTools.formatLocation(orig, relRow, relCol) + newAddress = "$newAddress:$newAddress1" + } + if (sh != null) + // TODO: handle refs with multiple sheets + newAddress = "$sh!$newAddress" + if (!fmh.changeFormulaLocation(celladdys[x], newAddress)) { + Logger.logErr("Could not change Formula Reference: " + celladdys[x] + " to: " + newAddress) + } + } + return + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/GetInfo.java b/src/main/java/io/starter/OpenXLS/GetInfo.kt similarity index 54% rename from src/main/java/io/starter/OpenXLS/GetInfo.java rename to src/main/java/io/starter/OpenXLS/GetInfo.kt index cffa8ed..65d351a 100644 --- a/src/main/java/io/starter/OpenXLS/GetInfo.java +++ b/src/main/java/io/starter/OpenXLS/GetInfo.kt @@ -20,62 +20,64 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.OpenXLS; +package io.starter.OpenXLS -import java.io.IOException; -import java.util.Properties; +import java.io.IOException +import java.util.Properties /** * Prints various bits of information about this build. */ -public class GetInfo { +object GetInfo { - private static Properties buildProps; + private var buildProps: Properties? = null - private static final void loadBuildProps() { + private var version: String? = null + + private fun loadBuildProps() { if (null == buildProps) { - buildProps = new Properties(); + buildProps = Properties() try { - buildProps.load(GetInfo.class.getResourceAsStream( - "build.properties")); - } catch (IOException caught) { + buildProps!!.load(GetInfo::class.java!!.getResourceAsStream( + "build.properties")) + } catch (caught: IOException) { // don't care, it'll just be empty } + } } - private static String version; - /** * Return the version number of this installation of OpenXLS. */ - public static final String getVersion() { + fun getVersion(): String { if (null == version) { - loadBuildProps(); - version = buildProps.getProperty("version", "unknown"); + loadBuildProps() + version = buildProps!!.getProperty("version", "unknown") - if (version.endsWith("-SNAPSHOT")) { - version = version.substring(0, version.length() - 9); + if (version!!.endsWith("-SNAPSHOT")) { + version = version!!.substring(0, version!!.length - 9) - String timestamp = buildProps.getProperty("timestamp"); - String commit = buildProps.getProperty("commit"); - if ("UNKNOWN".equals(commit)) commit = null; + val timestamp = buildProps!!.getProperty("timestamp") + var commit: String? = buildProps!!.getProperty("commit") + if ("UNKNOWN" == commit) commit = null if (null != timestamp) - version += "-" + timestamp; + version += "-$timestamp" else if (null != commit) - version += "-unknown"; + version += "-unknown" if (null != commit) - version += "-" + commit.substring(0, 10); + version += "-" + commit.substring(0, 10) } } - return version; + return version } - public static void main(String[] args) { + @JvmStatic + fun main(args: Array) { // Product name and version - io.starter.toolkit.Logger.log("This is OpenXLS " + getVersion()); + io.starter.toolkit.Logger.log("This is OpenXLS " + getVersion()) } } diff --git a/src/main/java/io/starter/OpenXLS/Handle.java b/src/main/java/io/starter/OpenXLS/Handle.kt similarity index 92% rename from src/main/java/io/starter/OpenXLS/Handle.java rename to src/main/java/io/starter/OpenXLS/Handle.kt index 2582bf3..7fffc8e 100644 --- a/src/main/java/io/starter/OpenXLS/Handle.java +++ b/src/main/java/io/starter/OpenXLS/Handle.kt @@ -20,12 +20,11 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.OpenXLS; +package io.starter.OpenXLS /** * A type that represents a component of a document. * This interface serves to distinguish such types from helper and other * non-content classes. */ -public interface Handle { -} +interface Handle diff --git a/src/main/java/io/starter/OpenXLS/ImageHandle.java b/src/main/java/io/starter/OpenXLS/ImageHandle.java deleted file mode 100644 index eaa4311..0000000 --- a/src/main/java/io/starter/OpenXLS/ImageHandle.java +++ /dev/null @@ -1,916 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.OOXML.SpPr; -import io.starter.formats.OOXML.TwoCellAnchor; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.MSODrawing; -import io.starter.formats.XLS.MSODrawingConstants; -import io.starter.toolkit.Logger; -import org.json.JSONException; -import org.json.JSONObject; - -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; -import java.awt.image.BufferedImage; -import java.io.*; -import java.util.Iterator; - -//OOXML-specific structures - - -/** - * The ImageHandle provides access to an Image embedded in a spreadsheet.
                  - *
                  - * Use the ImageHandle to work with images in spreadsheet.
                  - *

                  - * With an ImageHandle you can: - *

                  - *

                  - * insert images into your spreadsheet - * set the position of the image - * set the width and height of the image - * write spreadsheet image files to any outputstream - * - *
                  - *
                  - *
                  - * - * @see io.starter.OpenXLS.WorkBookHandle - * @see io.starter.OpenXLS.WorkSheetHandle - */ -public class ImageHandle implements Serializable { - /** - * returns width divided by height for the aspect ratio - * - * @return the aspect ratio for the image - */ - public double getAspectRatio() { - double height, width; - double aspectRatio = 0; - height = getHeight() / 122.27; //(convert to in) - width = getWidth() / 57.06; //(convert to in) - aspectRatio = width / height; - return aspectRatio; - } - - /** - * - */ - private static final long serialVersionUID = 3177017738178634238L; - private byte[] imageBytes; - private Boundsheet mysheet; - - // coordinates - public static final int X = 0; - public static final int Y = 1; - public static final int WIDTH = 2; - public static final int HEIGHT = 3; - - private String imageName = " ", shapeName = ""; - private short height, width; - private short x, y; - private int image_type = -1; - private int DEBUGLEVEL = 0; // eventually will set! - - // OOXML-specific - private SpPr imagesp; - private String editMovement = null; - - private MSODrawing thisMsodrawing; //20070924 KSC: link to actual msodrawing rec that describes this image - - public void setMsgdrawing(MSODrawing rec) { - thisMsodrawing = rec; - } - - public MSODrawing getMsodrawing() { - return thisMsodrawing; - } - - - /** - * Returns the image type - * - * @return - */ - public String getType() { - switch (image_type) { - case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_GIF: - return "gif"; - case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_JPG: - return "jpeg"; - case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_PNG: - return "png"; - case io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_EMF: - return "emf"; - default: - return "undefined"; - } - } - - /** - * Returns the image mime type - * - * @return - */ - public String getMimeType() { - return "image/" + getImageType(); - } - - /** - * Constructor which takes image file bytes and inserts into - * specific sheet - * - * @param imageBytes - * @param sheet - */ - public ImageHandle(InputStream imagebytestream, WorkSheetHandle _sheet) { - this(imagebytestream, _sheet.getMysheet()); - } - - /** - * Constructor which takes image file bytes and associates it with the - * specified boundsheet - * - * @param imagebytestream - * @param bs - */ - public ImageHandle(InputStream imagebytestream, Boundsheet bs) { - mysheet = bs; - try { - imageBytes = new byte[imagebytestream.available()]; - imagebytestream.read(imageBytes); - } catch (Exception ex) { - System.err.print("Failed to create new ImageHandle in sheet" + bs.getSheetName() + " from InputStream:" + ex.toString()); - } - initialize(); - } - - /** - * Constructor which takes image file bytes and inserts into - * specific sheet - * - * @param imageBytes - * @param sheet - */ - public ImageHandle(byte[] _imageBytes, Boundsheet sheet) { - mysheet = sheet; - imageBytes = _imageBytes; - - initialize(); - - } - - /** - * Override equals so that Sheet cannot contain dupes. - * - * @see java.lang.Object#equals(java.lang.Object) - */ - public boolean equals(Object another) { - return another.toString().equals(this.toString()); - } - - private void initialize() { - // after instantiating the image you should have - // access to the width and height bounds - String imageFormat = ""; - try { - ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes); - - imageFormat = getImageFormat(bis); - if (imageFormat == null) { - // 20080128 KSC: Occurs when image format= EMF ????? We cannot interpret, will most likely crash file - if (DEBUGLEVEL > WorkBook.DEBUG_LOW) - Logger.logErr("ImageHandle.initialize: Unrecognized Image Format"); - return; - } - if (!(imageFormat.equalsIgnoreCase("jpeg") || imageFormat.equalsIgnoreCase("png"))) { - bis.reset(); - imageBytes = convertData(bis); - image_type = MSODrawingConstants.IMAGE_TYPE_PNG; - } else { - if (imageFormat.equalsIgnoreCase("jpeg")) image_type = MSODrawingConstants.IMAGE_TYPE_JPG; - else this.image_type = MSODrawingConstants.IMAGE_TYPE_PNG; - - } - - try { - BufferedImage bi = ImageIO.read(new ByteArrayInputStream(imageBytes)); - width = (short) bi.getWidth(); - height = (short) bi.getHeight(); - bi = null; - } catch (IOException ex) { - Logger.logWarn("Java ImageIO could not decode image bytes for " + imageName + ":" + ex.toString()); - if (false) { // 20081028 KSC: don't overwrite original image bytes - String imgname = "failed_image_read_" + this.imageName + "." + imageFormat; - FileOutputStream outimg = new FileOutputStream(imgname); - outimg.write(imageBytes); - outimg.flush(); - outimg.close(); - - } - } - // 20070924 KSC: bounds[2] = width; - // "" bounds[3] = height; - this.imageName = "UnnamedImage"; - } catch (Exception e) { - Logger.logWarn("Problem creating ImageHandle:" + e.toString() + " Please see BugTrack article: http://extentech.com/uimodules/docs/docs_detail.jsp?meme_id=1431&showall=true"); - if (false) { // debug image parse probs - String imgname = "failed_image_read_" + this.imageName + "." + imageFormat; - try { - FileOutputStream outimg = new FileOutputStream(imgname); - outimg.write(imageBytes); - outimg.flush(); - outimg.close(); - } catch (Exception ex) { - } - } - } - } - - /** - * update the underlying image record - */ - public void update() throws Exception { - if (this.thisMsodrawing == null) - throw new Exception("ImageHandle.Update: Image Not initialzed"); - this.thisMsodrawing.updateRecord(); //this.thisMsodrawing.getSPID()); - this.mysheet.getWorkBook().updateMsodrawingHeaderRec(this.mysheet); - } - - /** - * returns true if this drawing is active i.e. not deleted - *
                  Note this is experimental - * - * @return - */ - public boolean isActive() { - if (this.thisMsodrawing != null) - return this.thisMsodrawing.isActive(); - return true; // default - } - - /** - * converts image data into byte array used by - *

                  - * Jan 22, 2010 - * - * @param imagebytestream - * @return - */ - public byte[] convertData(InputStream imagebytestream) { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - - BufferedImage bi = ImageIO.read(imagebytestream); - - ImageIO.write(bi, "png", bos); - - return bos.toByteArray(); - } catch (Exception e) { - Logger.logErr("ImageHandle.convertData: " + e.toString()); - return null; - } - - } - - /** - * returns the format name of the image data - *

                  - * Jan 22, 2010 - * - * @param imagebytestream - * @return - */ - public String getImageFormat(InputStream imagebytestream) { - - try { - // Create an image input stream on the image - ImageInputStream iis = ImageIO.createImageInputStream(imagebytestream); - - // Find all image readers that recognize the image format - Iterator iter = ImageIO.getImageReaders(iis); - if (!iter.hasNext()) { - - return null; - } - - // Use the first reader - ImageReader reader = (ImageReader) iter.next(); - - // Close stream - iis.close(); - - // Return the format name - return reader.getFormatName(); - } catch (IOException e) { - } - // The image could not be read - return null; - } - - - - /* - * 20070924 KSC: image bounds are set via column/row # + offsets within the respective cell - * added many position methods that access msodrawing for actual positions - */ - /* position methods */ - - /** - * return the image bounds - * images bounds are as follows: - * bounds[0]= column # of top left position (0-based) of the shape - * bounds[1]= x offset within the top-left column (0-1023) - * bounds[2]= row # for top left corner - * bounds[3]= y offset within the top-left corner (0-1023) - * bounds[4]= column # of the bottom right corner of the shape - * bounds[5]= x offset within the cell for the bottom-right corner (0-1023) - * bounds[6]= row # for bottom-right corner of the shape - * bounds[7]= y offset within the cell for the bottom-right corner (0-1023) - */ - public short[] getBounds() { - return thisMsodrawing.getBounds(); - } - - // short is not big enough to handle MAXROWS... correct? -jm - - /** - * sets the image bounds - * images bounds are as follows: - * bounds[0]= column # of top left position (0-based) of the shape - * bounds[1]= x offset within the top-left column (0-1023) - * bounds[2]= row # for top left corner - * bounds[3]= y offset within the top-left corner (0-1023) - * bounds[4]= column # of the bottom right corner of the shape - * bounds[5]= x offset within the cell for the bottom-right corner (0-1023) - * bounds[6]= row # for bottom-right corner of the shape - * bounds[7]= y offset within the cell for the bottom-right corner (0-1023) - */ - public void setBounds(short[] bounds) { - thisMsodrawing.setBounds(bounds); - } - - /** - * return the image bounds in x, y, width, height format in pixels - * - * @return - */ - public short[] getCoords() { - if (thisMsodrawing != null) - return thisMsodrawing.getCoords(); - else - return new short[]{x, y, width, height}; - } - - /** - * set the image x, w, width and height in pixels - * - * @param x - * @param y - * @param w - * @param h - */ - public void setCoords(int x, int y, int w, int h) { - if (thisMsodrawing != null) - thisMsodrawing.setCoords(new short[]{(short) x, (short) y, (short) w, (short) h}); - else {// save for later - this.x = (short) x; - this.y = (short) y; - this.width = (short) w; - this.height = (short) h; - } - } - - /** - * set the image upper x coordinate in pixels - * - * @param x - */ - public void setX(int x) { - if (thisMsodrawing != null) - thisMsodrawing.setX((short) Math.round(x / 6.4)); // 20090506 KSC: convert pixels to excel units - else // save for later - this.x = (short) x; - } - - /** - * set the image upper y coordinate in pixels - * - * @param y - */ - public void setY(int y) { - if (thisMsodrawing != null) - thisMsodrawing.setY((short) Math.round(y * 0.60)); //convert pixels to points - else // save for later - this.y = (short) y; - } - - /** - * return the topmost row of the image - * - * @return - */ - public int getRow() { - return thisMsodrawing.getRow0(); - } - - /** - * return the lower row of the image - * - * @return - */ - public int getRow1() { - return thisMsodrawing.getRow1(); - } - - /** - * set the topmost row of the image - * - * @param row - */ - public void setRow(int row) { - thisMsodrawing.setRow(row); - } - - /** - * set the lower row of the image - * - * @param row - */ - public void setRow1(int row) { - thisMsodrawing.setRow1(row); - } - - /** - * return the leftmost column of the image - */ - public int getCol() { - return thisMsodrawing.getCol(); - } - - /** - * return the rightmost column of the image - */ - public int getCol1() { - return thisMsodrawing.getCol1(); - } - - public int getOriginalWidth() { - return thisMsodrawing.getOriginalWidth(); - } - - /** - * return the width of the image in pixels - * - * @return - */ - public short getWidth() { - return (short) Math.round(thisMsodrawing.getWidth() * 6.4); // 20090506 KSC: Convert excel units to pixels - } - - /** - * set the width of the image in pixels - * - * @param w - */ - public void setWidth(int w) { - thisMsodrawing.setWidth((short) Math.round(w / 6.4)); // 20090506 KSC: convert pixels to excel units - } - - /** - * return the height of the image in pixels - * - * @return - */ - public short getHeight() { - return (short) Math.round(thisMsodrawing.getHeight() / 0.60); // 20090506 KSC: Convert points to pixels - } - - /** - * set the height of the image in pixels - * - * @param h - */ - public void setHeight(int h) { - thisMsodrawing.setHeight((short) Math.round(h * 0.60)); // 20090506 KSC: convert pixels to points - } - - /** - * return the upper x coordinate of the image in pixels - * - * @return - */ - public short getX() { - return (short) Math.round(thisMsodrawing.getX() * 6.4); // 20090506 KSC: convert excel units to pixels - } - - /** - * return the upper y coordinate of the image in pixels - * - * @return - */ - public short getY() { - return (short) Math.round(thisMsodrawing.getY() * 0.60); // 20090506 KSC: convert points to pixels - } - - /** - * get the position of the top left corner of the image in the sheet - */ - - public short[] getRowAndOffset() { - return thisMsodrawing.getRowAndOffset(); - } - - /** - * get the position of the top left corner of the image in the sheet - */ - public short[] getColAndOffset() { - return thisMsodrawing.getColAndOffset(); - } - - /** - * Internal method that converts the image bounds appropriate for saving - * by Excel in the MsofbtOPT record. - */ - public int getImageIndex() { - return thisMsodrawing.getImageIndex(); - } - - /** - * write the image bytes to an outputstream such as a file - * - * @param out - */ - //Modified by Bikash - public void write(OutputStream out) throws IOException { - // TODO: write the image bytes out - out.write(this.imageBytes); - } - - /** - * Need to figure out a unique identifier for these -- if there - * is one in the BIFF8 information, that would be best - * - * @see java.lang.Object#toString() - */ - public String toString() { - return getName(); // 20071026 KSC: imageName; - } - - - /** - * removes this Image from the WorkBook. - * - * @return whether the removal was a success - */ - public boolean remove() { - this.mysheet.removeImage(this); - // blow out the image rec - this.thisMsodrawing.remove(true); - return true; - } - - /** - * @return Returns the image type. - */ - public int getImageType() { - return image_type; - } - - /** - * @param image_type The image type to set. - */ - public void setImageType(int type) { - image_type = type; - } - - /** - * returns the WorkSheet this image is contained in - * - * @return Returns the sheet. - */ - public Boundsheet getSheet() { - return mysheet; - } - - /** - * @return Returns the name (either explicitly set name or image name). - */ - public String getName() { // 20071025 KSC: use Explicitly set name = shape name, if present. Otherwise, use imageName - if (!shapeName.equals("")) - return shapeName; - return this.imageName; - } - - /** - * return the imageName (= original file name, I believe) - * - * @return - */ - public String getImageName() { - return this.imageName; - } - - /** - * returns the explicitly set shape name (set by entering text in the named range field in Excel) - * - * @return the shape name - */ - public String getShapeName() { - return shapeName; - } - - /** - * @param name The name to set. - */ - public void setName(String name) { - this.imageName = name; - // NOTE: updating mso code causes corruption in certain Infoteria files- must look at - if (this.thisMsodrawing != null && !this.thisMsodrawing.getName().equals(name)) { - this.thisMsodrawing.setImageName(name); // update is done in setImageName - this.thisMsodrawing.getWorkBook().updateMsodrawingHeaderRec(this.getSheet()); // must update header if change mso's - Claritas image insert regression bug (testImages.testInsertImageCorruption) - } - } - - /** - * allow setting of image name as seen in the Named Range Box - * - * @param name - */ - public void setShapeName(String name) { - if (name != null) - shapeName = name; - else - shapeName = ""; - // only set name/update record if names have changed - if (this.thisMsodrawing != null && !shapeName.equals(this.thisMsodrawing.getShapeName())) { - this.thisMsodrawing.setShapeName(shapeName); // update is done in setShapeName - this.thisMsodrawing.getWorkBook().updateMsodrawingHeaderRec(this.getSheet()); // 20100202 KSC: must update header if change mso's - Claritas image insert regression bug (testImages.testInsertImageCorruption) - } - } - - /** - * @return Returns the imageBytes. - */ - public byte[] getImageBytes() { - return imageBytes; - } - - /** - * sets the underlying image bytes to the bytes from a new image - *

                  - * essentially swapping the image bytes for another, while retaining - * all other image properties (borders, etc.) - * - * @param imageBytes The imageBytes to set. - */ - public void setImageBytes(byte[] imageBytes) { - this.imageBytes = imageBytes; - mysheet.getWorkBook().getMSODrawingGroup().setImageBytes(this.imageBytes, mysheet, this.thisMsodrawing, this.getName()); - } - - /** - * Get a JSON representation of the format - * - * @param cr - * @return - */ - public JSONObject getJSON() { - JSONObject ch = new JSONObject(); - try { - ch.put("name", this.getImageName()); - short[] coords = this.getCoords(); - - // short[] coords = { x, y, width, height }; - - ch.put("x", coords[ImageHandle.X]); - ch.put("y", coords[ImageHandle.Y]); - - ch.put("width", coords[ImageHandle.WIDTH]); - // ch.put("width", width); // for some reason COORDS wrong for width - - ch.put("height", coords[ImageHandle.HEIGHT]); - ch.put("type", this.getType()); - - } catch (JSONException e) { - Logger.logErr("Error getting imageHandle JSON: " + e); - } - return ch; - } - - /** - * set ImageHandle position based on another - * - * @param im source ImageHandle - * @return - */ - public void position(ImageHandle im) { - /* one way, just set x and y and keep original w and h - // set x and y, keep original width and height - short[] origcoords= im.getCoords(); - short[] coords= this.getCoords(); - coords[0]= origcoords[0]; - coords[1]= origcoords[1]; - this.setCoords(coords[0], coords[1], coords[2], coords[3]); - */ - /* other way, set with all original coordinates */ - this.setBounds(im.getBounds()); - } - - /** - * return the XML representation of this image - * - * @return String - */ - public String getXML(int rId) { - StringBuffer sb = new StringBuffer(); - short[] bounds = this.getBounds(); - final int EMU = 1270; // 1 pt= 1270 EMUs -- for consistency with OOXML - - sb.append(""); - sb.append("\r\n"); - // top left coords - sb.append(""); - sb.append("" + bounds[0] + ""); - sb.append("" + bounds[1] * EMU + ""); - sb.append("" + bounds[2] + ""); - sb.append("" + bounds[3] * EMU + ""); - sb.append(""); - sb.append("\r\n"); - // bottom right coords - sb.append(""); - sb.append("" + bounds[4] + ""); - sb.append("" + bounds[5] * EMU + ""); - sb.append("" + bounds[6] + ""); - sb.append("" + bounds[7] * EMU + ""); - sb.append(""); - sb.append("\r\n"); - // Picture details - req. child elements= nvPicPr (non-visual picture properties), blipFill (links to image), spPr (shape properties) - sb.append(""); - sb.append("\r\n"); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append("\r\n"); - // Picture relationship Id and relationship to the package - sb.append(""); - sb.append(""); - // //If the picture is cropped, these details are stored in the element - sb.append(""); - sb.append(""); - sb.append("\r\n"); - // shape properties - sb.append(""/* bwMode=\"auto\">"*/); - sb.append(""); - int x = this.getX() * EMU, y = this.getY() * EMU, cx = this.getWidth() * EMU, cy = this.getHeight() * EMU; - sb.append(""); // offsets= location - sb.append(""); // extents= size of bounding box enclosing pic in EMUs - sb.append(""); - sb.append(""); // preset geometry, appears necessary for images ... - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append("\r\n"); - sb.append(""); - sb.append("\r\n"); - sb.append(""); - return sb.toString(); - } - - /** - * return the (00)XML (or DrawingML) representation of this image - * - * @param rId relationship id for image file - * @return String - */ - public String getOOXML(int rId) { - TwoCellAnchor t = new TwoCellAnchor(this.editMovement); - t.setAsImage(rId, this.getImageName(), this.getShapeName(), this.getMsodrawing().getSPID(), this.getSpPr()); - t.setBounds(TwoCellAnchor.convertBoundsFromBIFF8(this.getSheet(), this.getBounds())); // adjust BIFF8 bounds to OOXML units - return t.getOOXML(); - // missing in - // - // - // - - -/* - StringBuffer sb= new StringBuffer(); - int[] bounds= twoCellAnchor.convertBoundsFromBIFF8(this.getSheet(), this.getBounds()); // adjust BIFF8 bounds to OOXML units - final int EMU= 1270; // 1 pt= 1270 EMUs - - // 20081008 KSC: Added namespaces for Excel7 Use ********************** - // TODO: create a twoCellAnchor from this ImageHandle and use twoCellAnchor.getOOXML - sb.append("\r\n"); - // top left coords - sb.append(""); - sb.append("" + bounds[0] + ""); // 1-based column - sb.append("" + bounds[1]+ ""); - sb.append("" + bounds[2] + ""); - sb.append("" + bounds[3] + ""); - sb.append(""); sb.append("\r\n"); - // bottom right coords - sb.append(""); - sb.append("" + bounds[4] + ""); // 1-based column - sb.append("" + bounds[5] + ""); - sb.append("" + bounds[6] + ""); - sb.append("" + bounds[7] + ""); - sb.append(""); sb.append("\r\n"); - // Picture details - req. child elements= nvPicPr (non-visual picture properties), blipFill (links to image), spPr (shape properties) - sb.append(""); sb.append("\r\n"); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append(""); sb.append("\r\n"); - // Picture relationship Id and relationship to the package - sb.append(""); - sb.append(""); - sb.append(""); //If the picture is cropped, these details are stored in the element - sb.append(""); - sb.append(""); sb.append("\r\n"); - // shape properties - if (imagesp!=null) - sb.append(imagesp.getOOXML()); - else { // default basic - sb.append(""); - sb.append(""); - int x=this.getX()*EMU, y=this.getY()*EMU, cx= this.getWidth()*EMU, cy= this.getHeight()*EMU; - sb.append(""); // offsets= location - sb.append(""); // extents= size of bounding box enclosing pic in EMUs - sb.append(""); - sb.append(""); // preset geometry, appears necessary for images ... - sb.append(""); - sb.append(""); - sb.append(""); sb.append("\r\n"); - } - sb.append(""); sb.append("\r\n"); - sb.append(""); sb.append("\r\n"); - sb.append(""); - return sb.toString(); -*/ - } - - /** - * return the OOXML shape property for this image - * - * @return - */ - public SpPr getSpPr() { - return imagesp; - } - - /** - * define the OOXML shape property for this image from an existing spPr element - */ - public void setSpPr(SpPr sp) { - imagesp = sp; -// imagesp.setNS("xdr"); - } - - /** - * specify how to resize or move upon edit OOXML specific - * - * @param editMovement - */ - public void setEditMovement(String editMovement) { - this.editMovement = editMovement; - } - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/ImageHandle.kt b/src/main/java/io/starter/OpenXLS/ImageHandle.kt new file mode 100644 index 0000000..4ebea5f --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/ImageHandle.kt @@ -0,0 +1,890 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.OOXML.SpPr +import io.starter.formats.OOXML.TwoCellAnchor +import io.starter.formats.XLS.Boundsheet +import io.starter.formats.XLS.MSODrawing +import io.starter.formats.XLS.MSODrawingConstants +import io.starter.toolkit.Logger +import org.json.JSONException +import org.json.JSONObject + +import javax.imageio.ImageIO +import javax.imageio.ImageReader +import javax.imageio.stream.ImageInputStream +import java.awt.image.BufferedImage +import java.io.* + +//OOXML-specific structures + + +/** + * The ImageHandle provides access to an Image embedded in a spreadsheet.

                  + *

                  + * Use the ImageHandle to work with images in spreadsheet.

                  + *



                  + * With an ImageHandle you can: + *



                  + *

                  + * insert images into your spreadsheet + * set the position of the image + * set the width and height of the image + * write spreadsheet image files to any outputstream + * +
                  * + *

                  + *

                  + * + * @see io.starter.OpenXLS.WorkBookHandle + * + * @see io.starter.OpenXLS.WorkSheetHandle + */ +class ImageHandle : Serializable { + /** + * returns width divided by height for the aspect ratio + * + * @return the aspect ratio for the image + */ + //(convert to in) + //(convert to in) + val aspectRatio: Double + get() { + val height: Double + val width: Double + var aspectRatio = 0.0 + height = getHeight() / 122.27 + width = getWidth() / 57.06 + aspectRatio = width / height + return aspectRatio + } + private var imageBytes: ByteArray? = null + /** + * returns the WorkSheet this image is contained in + * + * @return Returns the sheet. + */ + var sheet: Boundsheet? = null + private set + + /** + * return the imageName (= original file name, I believe) + * + * @return + */ + var imageName = " " + private set + /** + * returns the explicitly set shape name (set by entering text in the named range field in Excel) + * + * @return the shape name + */ + /** + * allow setting of image name as seen in the Named Range Box + * + * @param name + */ + // only set name/update record if names have changed + // update is done in setShapeName + // 20100202 KSC: must update header if change mso's - Claritas image insert regression bug (testImages.testInsertImageCorruption) + var shapeName: String? = "" + set(name) { + if (name != null) + field = name + else + field = "" + if (this.msodrawing != null && this.shapeName != this.msodrawing!!.shapeName) { + this.msodrawing!!.shapeName = this.shapeName!! + this.msodrawing!!.workBook!!.updateMsodrawingHeaderRec(this.sheet) + } + } + private var height: Short = 0 + private var width: Short = 0 + private var x: Short = 0 + private var y: Short = 0 + /** + * @return Returns the image type. + */ + /** + * @param image_type The image type to set. + */ + var imageType = -1 + private val DEBUGLEVEL = 0 // eventually will set! + + // OOXML-specific + /** + * return the OOXML shape property for this image + * + * @return + */ + /** + * define the OOXML shape property for this image from an existing spPr element + */ + // imagesp.setNS("xdr"); + var spPr: SpPr? = null + private var editMovement: String? = null + + var msodrawing: MSODrawing? = null + private set //20070924 KSC: link to actual msodrawing rec that describes this image + + + /** + * Returns the image type + * + * @return + */ + val type: String + get() { + when (imageType) { + io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_GIF -> return "gif" + io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_JPG -> return "jpeg" + io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_PNG -> return "png" + io.starter.formats.XLS.MSODrawingConstants.IMAGE_TYPE_EMF -> return "emf" + else -> return "undefined" + } + } + + /** + * Returns the image mime type + * + * @return + */ + val mimeType: String + get() = "image/$imageType" + + /** + * returns true if this drawing is active i.e. not deleted + *

                  Note this is experimental + * + * @return + */ + // default + val isActive: Boolean + get() = if (this.msodrawing != null) this.msodrawing!!.isActive else true + + + /* + * 20070924 KSC: image bounds are set via column/row # + offsets within the respective cell + * added many position methods that access msodrawing for actual positions + */ + /* position methods */ + + /** + * return the image bounds + * images bounds are as follows: + * bounds[0]= column # of top left position (0-based) of the shape + * bounds[1]= x offset within the top-left column (0-1023) + * bounds[2]= row # for top left corner + * bounds[3]= y offset within the top-left corner (0-1023) + * bounds[4]= column # of the bottom right corner of the shape + * bounds[5]= x offset within the cell for the bottom-right corner (0-1023) + * bounds[6]= row # for bottom-right corner of the shape + * bounds[7]= y offset within the cell for the bottom-right corner (0-1023) + */ + // short is not big enough to handle MAXROWS... correct? -jm + + /** + * sets the image bounds + * images bounds are as follows: + * bounds[0]= column # of top left position (0-based) of the shape + * bounds[1]= x offset within the top-left column (0-1023) + * bounds[2]= row # for top left corner + * bounds[3]= y offset within the top-left corner (0-1023) + * bounds[4]= column # of the bottom right corner of the shape + * bounds[5]= x offset within the cell for the bottom-right corner (0-1023) + * bounds[6]= row # for bottom-right corner of the shape + * bounds[7]= y offset within the cell for the bottom-right corner (0-1023) + */ + var bounds: ShortArray? + get() = msodrawing!!.bounds + set(bounds) { + msodrawing!!.bounds = bounds!! + } + + /** + * return the image bounds in x, y, width, height format in pixels + * + * @return + */ + val coords: ShortArray + get() = if (msodrawing != null) + msodrawing!!.coords + else + shortArrayOf(x, y, width, height) + + /** + * return the topmost row of the image + * + * @return + */ + /** + * set the topmost row of the image + * + * @param row + */ + var row: Int + get() = msodrawing!!.row0 + set(row) = msodrawing!!.setRow(row) + + /** + * return the lower row of the image + * + * @return + */ + /** + * set the lower row of the image + * + * @param row + */ + var row1: Int + get() = msodrawing!!.row1 + set(row) { + msodrawing!!.row1 = row + } + + /** + * return the leftmost column of the image + */ + val col: Int + get() = msodrawing!!.col + + /** + * return the rightmost column of the image + */ + val col1: Int + get() = msodrawing!!.col1 + + val originalWidth: Int + get() = msodrawing!!.originalWidth.toInt() + + /** + * get the position of the top left corner of the image in the sheet + */ + + val rowAndOffset: ShortArray + get() = msodrawing!!.rowAndOffset + + /** + * get the position of the top left corner of the image in the sheet + */ + val colAndOffset: ShortArray + get() = msodrawing!!.colAndOffset + + /** + * Internal method that converts the image bounds appropriate for saving + * by Excel in the MsofbtOPT record. + */ + val imageIndex: Int + get() = msodrawing!!.imageIndex + + /** + * @return Returns the name (either explicitly set name or image name). + */ + /** + * @param name The name to set. + */ + // 20071025 KSC: use Explicitly set name = shape name, if present. Otherwise, use imageName + // NOTE: updating mso code causes corruption in certain Infoteria files- must look at + // update is done in setImageName + // must update header if change mso's - Claritas image insert regression bug (testImages.testInsertImageCorruption) + var name: String + get() = if (this.shapeName != "") this.shapeName else this.imageName + set(name) { + this.imageName = name + if (this.msodrawing != null && this.msodrawing!!.name != name) { + this.msodrawing!!.setImageName(name) + this.msodrawing!!.workBook!!.updateMsodrawingHeaderRec(this.sheet) + } + } + + /** + * Get a JSON representation of the format + * + * @param cr + * @return + */ + // short[] coords = { x, y, width, height }; + // ch.put("width", width); // for some reason COORDS wrong for width + val json: JSONObject + get() { + val ch = JSONObject() + try { + ch.put("name", this.imageName) + val coords = this.coords + + ch.put("x", coords[ImageHandle.X].toInt()) + ch.put("y", coords[ImageHandle.Y].toInt()) + + ch.put("width", coords[ImageHandle.WIDTH].toInt()) + + ch.put("height", coords[ImageHandle.HEIGHT].toInt()) + ch.put("type", this.type) + + } catch (e: JSONException) { + Logger.logErr("Error getting imageHandle JSON: $e") + } + + return ch + } + + fun setMsgdrawing(rec: MSODrawing) { + msodrawing = rec + } + + /** + * Constructor which takes image file bytes and inserts into + * specific sheet + * + * @param imageBytes + * @param sheet + */ + constructor(imagebytestream: InputStream, _sheet: WorkSheetHandle) : this(imagebytestream, _sheet.mysheet) {} + + /** + * Constructor which takes image file bytes and associates it with the + * specified boundsheet + * + * @param imagebytestream + * @param bs + */ + constructor(imagebytestream: InputStream, bs: Boundsheet?) { + sheet = bs + try { + imageBytes = ByteArray(imagebytestream.available()) + imagebytestream.read(imageBytes!!) + } catch (ex: Exception) { + System.err.print("Failed to create new ImageHandle in sheet" + bs!!.sheetName + " from InputStream:" + ex.toString()) + } + + initialize() + } + + /** + * Constructor which takes image file bytes and inserts into + * specific sheet + * + * @param imageBytes + * @param sheet + */ + constructor(_imageBytes: ByteArray, sheet: Boundsheet) { + this.sheet = sheet + imageBytes = _imageBytes + + initialize() + + } + + /** + * Override equals so that Sheet cannot contain dupes. + * + * @see java.lang.Object.equals + */ + override fun equals(another: Any?): Boolean { + return another!!.toString() == this.toString() + } + + private fun initialize() { + // after instantiating the image you should have + // access to the width and height bounds + var imageFormat: String? = "" + try { + val bis = ByteArrayInputStream(imageBytes!!) + + imageFormat = getImageFormat(bis) + if (imageFormat == null) { + // 20080128 KSC: Occurs when image format= EMF ????? We cannot interpret, will most likely crash file + if (DEBUGLEVEL > WorkBook.DEBUG_LOW) + Logger.logErr("ImageHandle.initialize: Unrecognized Image Format") + return + } + if (!(imageFormat.equals("jpeg", ignoreCase = true) || imageFormat.equals("png", ignoreCase = true))) { + bis.reset() + imageBytes = convertData(bis) + imageType = MSODrawingConstants.IMAGE_TYPE_PNG + } else { + if (imageFormat.equals("jpeg", ignoreCase = true)) + imageType = MSODrawingConstants.IMAGE_TYPE_JPG + else + this.imageType = MSODrawingConstants.IMAGE_TYPE_PNG + + } + + try { + var bi: BufferedImage? = ImageIO.read(ByteArrayInputStream(imageBytes!!)) + width = bi!!.width.toShort() + height = bi.height.toShort() + bi = null + } catch (ex: IOException) { + Logger.logWarn("Java ImageIO could not decode image bytes for $imageName:$ex") + if (false) { // 20081028 KSC: don't overwrite original image bytes + val imgname = "failed_image_read_" + this.imageName + "." + imageFormat + val outimg = FileOutputStream(imgname) + outimg.write(imageBytes!!) + outimg.flush() + outimg.close() + + } + } + + // 20070924 KSC: bounds[2] = width; + // "" bounds[3] = height; + this.imageName = "UnnamedImage" + } catch (e: Exception) { + Logger.logWarn("Problem creating ImageHandle:$e Please see BugTrack article: http://extentech.com/uimodules/docs/docs_detail.jsp?meme_id=1431&showall=true") + if (false) { // debug image parse probs + val imgname = "failed_image_read_" + this.imageName + "." + imageFormat + try { + val outimg = FileOutputStream(imgname) + outimg.write(imageBytes!!) + outimg.flush() + outimg.close() + } catch (ex: Exception) { + } + + } + } + + } + + /** + * update the underlying image record + */ + @Throws(Exception::class) + fun update() { + if (this.msodrawing == null) + throw Exception("ImageHandle.Update: Image Not initialzed") + this.msodrawing!!.updateRecord() //this.thisMsodrawing.getSPID()); + this.sheet!!.workBook!!.updateMsodrawingHeaderRec(this.sheet) + } + + /** + * converts image data into byte array used by + * + * + * Jan 22, 2010 + * + * @param imagebytestream + * @return + */ + fun convertData(imagebytestream: InputStream): ByteArray? { + try { + val bos = ByteArrayOutputStream() + + val bi = ImageIO.read(imagebytestream) + + ImageIO.write(bi, "png", bos) + + return bos.toByteArray() + } catch (e: Exception) { + Logger.logErr("ImageHandle.convertData: $e") + return null + } + + } + + /** + * returns the format name of the image data + * + * + * Jan 22, 2010 + * + * @param imagebytestream + * @return + */ + fun getImageFormat(imagebytestream: InputStream): String? { + + try { + // Create an image input stream on the image + val iis = ImageIO.createImageInputStream(imagebytestream) + + // Find all image readers that recognize the image format + val iter = ImageIO.getImageReaders(iis) + if (!iter.hasNext()) { + + return null + } + + // Use the first reader + val reader = iter.next() as ImageReader + + // Close stream + iis.close() + + // Return the format name + return reader.formatName + } catch (e: IOException) { + } + + // The image could not be read + return null + } + + /** + * set the image x, w, width and height in pixels + * + * @param x + * @param y + * @param w + * @param h + */ + fun setCoords(x: Int, y: Int, w: Int, h: Int) { + if (msodrawing != null) + msodrawing!!.coords = shortArrayOf(x.toShort(), y.toShort(), w.toShort(), h.toShort()) + else {// save for later + this.x = x.toShort() + this.y = y.toShort() + this.width = w.toShort() + this.height = h.toShort() + } + } + + /** + * set the image upper x coordinate in pixels + * + * @param x + */ + fun setX(x: Int) { + if (msodrawing != null) + msodrawing!!.setX(Math.round(x / 6.4).toShort().toInt()) // 20090506 KSC: convert pixels to excel units + else + // save for later + this.x = x.toShort() + } + + /** + * set the image upper y coordinate in pixels + * + * @param y + */ + fun setY(y: Int) { + if (msodrawing != null) + msodrawing!!.setY(Math.round(y * 0.60).toShort().toInt()) //convert pixels to points + else + // save for later + this.y = y.toShort() + } + + /** + * return the width of the image in pixels + * + * @return + */ + fun getWidth(): Short { + return Math.round(msodrawing!!.width * 6.4).toShort() // 20090506 KSC: Convert excel units to pixels + } + + /** + * set the width of the image in pixels + * + * @param w + */ + fun setWidth(w: Int) { + msodrawing!!.setWidth(Math.round(w / 6.4).toShort().toInt()) // 20090506 KSC: convert pixels to excel units + } + + /** + * return the height of the image in pixels + * + * @return + */ + fun getHeight(): Short { + return Math.round(msodrawing!!.height / 0.60).toShort() // 20090506 KSC: Convert points to pixels + } + + /** + * set the height of the image in pixels + * + * @param h + */ + fun setHeight(h: Int) { + msodrawing!!.setHeight(Math.round(h * 0.60).toShort().toInt()) // 20090506 KSC: convert pixels to points + } + + /** + * return the upper x coordinate of the image in pixels + * + * @return + */ + fun getX(): Short { + return Math.round(msodrawing!!.x * 6.4).toShort() // 20090506 KSC: convert excel units to pixels + } + + /** + * return the upper y coordinate of the image in pixels + * + * @return + */ + fun getY(): Short { + return Math.round(msodrawing!!.y * 0.60).toShort() // 20090506 KSC: convert points to pixels + } + + /** + * write the image bytes to an outputstream such as a file + * + * @param out + */ + //Modified by Bikash + @Throws(IOException::class) + fun write(out: OutputStream) { + // TODO: write the image bytes out + out.write(this.imageBytes!!) + } + + /** + * Need to figure out a unique identifier for these -- if there + * is one in the BIFF8 information, that would be best + * + * @see java.lang.Object.toString + */ + override fun toString(): String { + return name // 20071026 KSC: imageName; + } + + + /** + * removes this Image from the WorkBook. + * + * @return whether the removal was a success + */ + fun remove(): Boolean { + this.sheet!!.removeImage(this) + // blow out the image rec + this.msodrawing!!.remove(true) + return true + } + + /** + * @return Returns the imageBytes. + */ + fun getImageBytes(): ByteArray? { + return imageBytes + } + + /** + * sets the underlying image bytes to the bytes from a new image + * + * + * essentially swapping the image bytes for another, while retaining + * all other image properties (borders, etc.) + * + * @param imageBytes The imageBytes to set. + */ + fun setImageBytes(imageBytes: ByteArray) { + this.imageBytes = imageBytes + sheet!!.workBook!!.msoDrawingGroup!!.setImageBytes(this.imageBytes, sheet, this.msodrawing!!, this.name) + } + + /** + * set ImageHandle position based on another + * + * @param im source ImageHandle + * @return + */ + fun position(im: ImageHandle) { + /* one way, just set x and y and keep original w and h + // set x and y, keep original width and height + short[] origcoords= im.getCoords(); + short[] coords= this.getCoords(); + coords[0]= origcoords[0]; + coords[1]= origcoords[1]; + this.setCoords(coords[0], coords[1], coords[2], coords[3]); + */ + /* other way, set with all original coordinates */ + this.bounds = im.bounds + } + + /** + * return the XML representation of this image + * + * @return String + */ + fun getXML(rId: Int): String { + val sb = StringBuffer() + val bounds = this.bounds + val EMU = 1270 // 1 pt= 1270 EMUs -- for consistency with OOXML + + sb.append("") + sb.append("\r\n") + // top left coords + sb.append("") + sb.append("" + bounds!![0] + "") + sb.append("" + bounds[1] * EMU + "") + sb.append("" + bounds[2] + "") + sb.append("" + bounds[3] * EMU + "") + sb.append("") + sb.append("\r\n") + // bottom right coords + sb.append("") + sb.append("" + bounds[4] + "") + sb.append("" + bounds[5] * EMU + "") + sb.append("" + bounds[6] + "") + sb.append("" + bounds[7] * EMU + "") + sb.append("") + sb.append("\r\n") + // Picture details - req. child elements= nvPicPr (non-visual picture properties), blipFill (links to image), spPr (shape properties) + sb.append("") + sb.append("\r\n") + sb.append("") + sb.append("") + sb.append("") + sb.append("") + sb.append("") + sb.append("") + sb.append("\r\n") + // Picture relationship Id and relationship to the package + sb.append("") + sb.append("") + // //If the picture is cropped, these details are stored in the element + sb.append("") + sb.append("") + sb.append("\r\n") + // shape properties + sb.append(""/* bwMode=\"auto\">"*/) + sb.append("") + val x = this.getX() * EMU + val y = this.getY() * EMU + val cx = this.getWidth() * EMU + val cy = this.getHeight() * EMU + sb.append("") // offsets= location + sb.append("") // extents= size of bounding box enclosing pic in EMUs + sb.append("") + sb.append("") // preset geometry, appears necessary for images ... + sb.append("") + sb.append("") + sb.append("") + sb.append("") + sb.append("") + sb.append("\r\n") + sb.append("") + sb.append("\r\n") + sb.append("") + return sb.toString() + } + + /** + * return the (00)XML (or DrawingML) representation of this image + * + * @param rId relationship id for image file + * @return String + */ + fun getOOXML(rId: Int): String { + val t = TwoCellAnchor(this.editMovement) + t.setAsImage(rId, this.imageName, this.shapeName, this.msodrawing!!.spid, this.spPr) + t.bounds = TwoCellAnchor.convertBoundsFromBIFF8(this.sheet, this.bounds!!) // adjust BIFF8 bounds to OOXML units + return t.ooxml + // missing in + // + // + // + + + /* + StringBuffer sb= new StringBuffer(); + int[] bounds= twoCellAnchor.convertBoundsFromBIFF8(this.getSheet(), this.getBounds()); // adjust BIFF8 bounds to OOXML units + final int EMU= 1270; // 1 pt= 1270 EMUs + + // 20081008 KSC: Added namespaces for Excel7 Use ********************** + // TODO: create a twoCellAnchor from this ImageHandle and use twoCellAnchor.getOOXML + sb.append("\r\n"); + // top left coords + sb.append(""); + sb.append("" + bounds[0] + ""); // 1-based column + sb.append("" + bounds[1]+ ""); + sb.append("" + bounds[2] + ""); + sb.append("" + bounds[3] + ""); + sb.append(""); sb.append("\r\n"); + // bottom right coords + sb.append(""); + sb.append("" + bounds[4] + ""); // 1-based column + sb.append("" + bounds[5] + ""); + sb.append("" + bounds[6] + ""); + sb.append("" + bounds[7] + ""); + sb.append(""); sb.append("\r\n"); + // Picture details - req. child elements= nvPicPr (non-visual picture properties), blipFill (links to image), spPr (shape properties) + sb.append(""); sb.append("\r\n"); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); sb.append("\r\n"); + // Picture relationship Id and relationship to the package + sb.append(""); + sb.append(""); + sb.append(""); //If the picture is cropped, these details are stored in the element + sb.append(""); + sb.append(""); sb.append("\r\n"); + // shape properties + if (imagesp!=null) + sb.append(imagesp.getOOXML()); + else { // default basic + sb.append(""); + sb.append(""); + int x=this.getX()*EMU, y=this.getY()*EMU, cx= this.getWidth()*EMU, cy= this.getHeight()*EMU; + sb.append(""); // offsets= location + sb.append(""); // extents= size of bounding box enclosing pic in EMUs + sb.append(""); + sb.append(""); // preset geometry, appears necessary for images ... + sb.append(""); + sb.append(""); + sb.append(""); sb.append("\r\n"); + } + sb.append(""); sb.append("\r\n"); + sb.append(""); sb.append("\r\n"); + sb.append(""); + return sb.toString(); +*/ + } + + /** + * specify how to resize or move upon edit OOXML specific + * + * @param editMovement + */ + fun setEditMovement(editMovement: String) { + this.editMovement = editMovement + } + + companion object { + + /** + * + */ + private const val serialVersionUID = 3177017738178634238L + + // coordinates + val X = 0 + val Y = 1 + val WIDTH = 2 + val HEIGHT = 3 + } + + +} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/JSONConstants.java b/src/main/java/io/starter/OpenXLS/JSONConstants.java deleted file mode 100644 index 4826078..0000000 --- a/src/main/java/io/starter/OpenXLS/JSONConstants.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -/** - * Constants used in the generation of JSON output. - */ -interface JSONConstants { - String JSON_CELL_VALUE = "v"; - String JSON_CELL_FORMATTED_VALUE = "fv"; - String JSON_CELL_FORMULA = "fm"; - String JSON_CELL = "Cell"; - String JSON_CELLS = "cs"; - String JSON_DATETIME = "DateTime"; - String JSON_DATEVALUE = "DateValue"; - String JSON_DOUBLE = "Double"; - String JSON_RANGE = "Range"; - String JSON_DATA = "d"; - String JSON_FLOAT = "Float"; - String JSON_INTEGER = "Integer"; - String JSON_LOCATION = "loc"; - String JSON_ROW = "Row"; - String JSON_ROW_BORDER_TOP = "BdrT"; - String JSON_ROW_BORDER_BOTTOM = "BdrB"; - String JSON_HEIGHT = "h"; - String JSON_STRING = "String"; - String JSON_STYLEID = "sid"; - String JSON_TYPE = "t"; - String JSON_FORMULA_HIDDEN = "fhd"; - String JSON_LOCKED = "lck"; - String JSON_HIDDEN = "Hidden"; - String JSON_VALIDATION_MESSAGE = "vm"; - String JSON_MERGEACROSS = "MergeAcross"; - String JSON_MERGEDOWN = "MergeDown"; - String JSON_MERGEPARENT = "MergeParent"; - String JSON_MERGECHILD = "MergeChild"; - String JSON_HREF = "HRef"; - String JSON_WORD_WRAP = "wrap"; - String JSON_RED_FORMAT = "negRed"; - String JSON_TEXT_ALIGN = "txtAlign"; -} diff --git a/src/main/java/io/starter/OpenXLS/JSONConstants.kt b/src/main/java/io/starter/OpenXLS/JSONConstants.kt new file mode 100644 index 0000000..b3a6dd5 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/JSONConstants.kt @@ -0,0 +1,63 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +/** + * Constants used in the generation of JSON output. + */ +internal interface JSONConstants { + companion object { + val JSON_CELL_VALUE = "v" + val JSON_CELL_FORMATTED_VALUE = "fv" + val JSON_CELL_FORMULA = "fm" + val JSON_CELL = "Cell" + val JSON_CELLS = "cs" + val JSON_DATETIME = "DateTime" + val JSON_DATEVALUE = "DateValue" + val JSON_DOUBLE = "Double" + val JSON_RANGE = "Range" + val JSON_DATA = "d" + val JSON_FLOAT = "Float" + val JSON_INTEGER = "Integer" + val JSON_LOCATION = "loc" + val JSON_ROW = "Row" + val JSON_ROW_BORDER_TOP = "BdrT" + val JSON_ROW_BORDER_BOTTOM = "BdrB" + val JSON_HEIGHT = "h" + val JSON_STRING = "String" + val JSON_STYLEID = "sid" + val JSON_TYPE = "t" + val JSON_FORMULA_HIDDEN = "fhd" + val JSON_LOCKED = "lck" + val JSON_HIDDEN = "Hidden" + val JSON_VALIDATION_MESSAGE = "vm" + val JSON_MERGEACROSS = "MergeAcross" + val JSON_MERGEDOWN = "MergeDown" + val JSON_MERGEPARENT = "MergeParent" + val JSON_MERGECHILD = "MergeChild" + val JSON_HREF = "HRef" + val JSON_WORD_WRAP = "wrap" + val JSON_RED_FORMAT = "negRed" + val JSON_TEXT_ALIGN = "txtAlign" + } +} diff --git a/src/main/java/io/starter/OpenXLS/NameHandle.java b/src/main/java/io/starter/OpenXLS/NameHandle.java deleted file mode 100644 index 63d3ed8..0000000 --- a/src/main/java/io/starter/OpenXLS/NameHandle.java +++ /dev/null @@ -1,747 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.*; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.json.JSONArray; -import org.json.JSONObject; - -import static io.starter.OpenXLS.JSONConstants.JSON_CELL; -import static io.starter.OpenXLS.JSONConstants.JSON_CELLS; - -/** - * The NameHandle provides access to a Named Range and its Cells.
                  - *
                  - * Use the NameHandle to work with individual Named Ranges in an XLS file.
                  - *

                  - * With a NameHandle you can: - *

                  - *
                  - * get a handle to the Cells in a Name
                  - * set the default formatting for a Name - *
                  - *
                  - *
                  - * - * @see WorkBookHandle - * @see WorkSheetHandle - * @see FormulaHandle - */ -public class NameHandle { - - private Name myName; - private WorkBook mybook; - private int DEBUGLEVEL = -1; - private boolean createblanks = false; - private CellRange initialRange = null; - - /** - * Creates a new Named Range from a CellRange - * - * @param Name of the Range, the CellRange referenced by the Name - */ - public NameHandle(String namestr, CellRange cr) { - mybook = cr.getWorkBook(); - initialRange = cr; // cache - myName = new Name(mybook.getWorkBook(), namestr); - this.setName(namestr); - this.setLocation(cr.toString()); - } - - /** - * Create a NameHandle from an internal Name record - * - * @param c - * @param myb - */ - protected NameHandle(Name c, WorkBookHandle myb) { - myName = c; - mybook = myb; - } - - /** - * Returns a handle to the object (either workbook or sheet) that is scoped - * to the name record - *

                  - * Default scope is a WorkBookHandle, else the WorkSheetHandle is returned. - * - * @return the scope of the name - */ - public Handle getScope() throws WorkSheetNotFoundException { - int itab = myName.getItab(); - if (itab == 0) return mybook; - WorkSheetHandle sheet = mybook.getWorkSheet(itab - 1); - return sheet; - } - - /** - * Set the scope of this name to that of the handle passed in. - *

                  - * This can either be a WorkbookHandle or a WorksheetHandle - *

                  - * note: this will only be functional for the workbook that the name is contained in, - * you cannot change the scope to a different Document with this method. - * - * @param scope Workbookhandle or WorksheetHandle - */ - public void setScope(Handle scope) { - int newitab = 0; - if (scope instanceof WorkSheetHandle) { - newitab = (((WorkSheetHandle) scope).getSheetNum() + 1); - } - try { - myName.setNewScope(newitab); - } catch (WorkSheetNotFoundException e) { - // this really shouldnt happen unless you are passing a scope in from a different workbook - Logger.logErr("ERROR: setting new scope on name: " + e); - } - } - - /** - * Create a new named range in the workbook - * note that this does not set the actual range, just the workbook and name, - * follow up with setLocation - * - * @param namestr - * @param myb - * @deprecated - */ - public NameHandle(String namestr, WorkBookHandle myb) { - mybook = myb; - myName = new Name(mybook.getWorkBook(), namestr); - this.setName(namestr); - } - - /** - * Create a new named range in the workbook - * - * @param name name that should be used to reference this named range - * @param location rangeDef Range of the cells for this named range, in excel syntax including sheet name, ie "Sheet1!A1:D1" - * @param book WorkBookHandle to insert this named range into - */ - public NameHandle(String name, String location, WorkBookHandle book) { - mybook = book; - myName = new Name(mybook.getWorkBook(), name); - this.setName(name); - this.setLocation(location); - mybook.getWorkBook().associateDereferencedNames(myName); - } - - - /** - * Return an XML representation of this name record - */ - public String getXML() { - StringBuffer retXML = new StringBuffer(); - String nmv = getExpressionString(); - if (nmv.indexOf("=!") > -1) { // add the sheetname - Boundsheet bs = myName.getSheet(); - if (bs != null) { - nmv = bs.getSheetName() + "!" + nmv; - } else { // TODO: why no sheet defined for name??? - nmv = StringTool.replaceChars("!", nmv, ""); - } - } - // it's possible that name expression can have quotes and other non-compliant characters - nmv = io.starter.toolkit.StringTool.convertXMLChars(nmv); - - String nmx = getName(); - nmx = io.starter.toolkit.StringTool.convertXMLChars(nmx); - - if (nmx.length() == 1) { // deal with snafu character names - if (Character.isLetterOrDigit(nmx.charAt(0))) { - Logger.logInfo("NameHandle getting XML for name: " + nmx); - } else { - nmx = "#NAME"; - } - } - - if (nmv.startsWith("=")) - nmv = nmv.substring(1); - retXML.append(" "); - return retXML.toString(); - } - - - /** - * @return String XML rep of all the cells referenced by this range - */ - public String getExpandedXML() { - String nmv = getExpressionString(); - if (nmv.indexOf("=!") > -1) { // add the sheetname - Boundsheet bs = myName.getSheet(); - if (bs != null) { - nmv = bs.getSheetName() + "!" + nmv; - } else { // TODO: why no sheet defined for name??? - nmv = StringTool.replaceChars("!", nmv, ""); - } - } - String nmx = getName(); - nmx = io.starter.toolkit.StringTool.convertXMLChars(nmx); - - if (nmx.length() == 1) { // deal with snafu character names - if (Character.isLetterOrDigit(nmx.charAt(0))) { - Logger.logInfo("NameHandle getting XML for name: " + nmx); - } else { - nmx = "#NAME"; - } - } - - if (nmv.startsWith("=")) - nmv = nmv.substring(1); - StringBuffer retXML = new StringBuffer(); - retXML.append("\t\n"); - try { - CellHandle[] cells = getCells(); - - for (int i = 0; i < cells.length; i++) { - retXML.append("\t\t" + cells[i].getXML() + "\n"); - } - } catch (CellNotFoundException ex) { - Logger.logErr("NameHandle.getExpandedXML failed: ", ex); - } - retXML.append("\n"); - - return retXML.toString(); - } - - /** - * sets the default format id for the Name's Cells - * - * @param int Format Id for all Cells in Name - */ - public void setFormatId(int i) { - // myName.setXFRecord(i); - // TODO: why doesn't this work with myName? - try { - CellHandle[] cs = this.getCells(); - for (int t = 0; t < cs.length; t++) { - cs[t].setFormatId(i); - } - } catch (CellNotFoundException ex) { - Logger.logErr("NameHandle.setFormatId failed: ", ex); - } - } - - /** - * deletes a row of cells in this named range, shifts subsequent - * rows up. - * - * @param the column to use as unique index - */ - public void deleteRow(int idxcol) throws Exception { - CellRange[] rngs = getCellRanges(); - if (rngs.length > 1) { - throw new WorkBookException("NamedRange.updateRow Object array failed: too many CellRanges.", WorkBookException.RUNTIME_ERROR); - } else if (rngs.length == 0) { - throw new WorkBookException("NamedRange.updateRow Object array failed: zero CellRanges", WorkBookException.RUNTIME_ERROR); - } - - // - WorkSheetHandle shtx = rngs[0].getSheet(); - int x = rngs[0].firstcellrow; - - - RowHandle[] rx = rngs[0].getRows(); - boolean found = false; - // iterate, find, update - for (int t = 0; t < rx.length; t++) { - - } - } - - /** - * update a row of cells in this named range - * - * @param an array of Objects to update existing - * @param the column to use as unique index - */ - public void updateRow(Object[] objarr, int idxcol) throws Exception { - CellRange[] rngs = getCellRanges(); - if (rngs.length > 1) { - throw new WorkBookException("NamedRange.updateRow Object array failed: too many CellRanges.", WorkBookException.RUNTIME_ERROR); - } else if (rngs.length == 0) { - throw new WorkBookException("NamedRange.updateRow Object array failed: zero CellRanges", WorkBookException.RUNTIME_ERROR); - } - - // - WorkSheetHandle shtx = rngs[0].getSheet(); - int x = rngs[0].firstcellrow; - - - RowHandle[] rx = rngs[0].getRows(); - boolean found = false; - // iterate, find, update - for (int t = 0; t < rx.length; t++) { - - CellHandle[] cx = rx[t].getCells(); - if (cx.length > idxcol) { - if (cx[idxcol].getStringVal().equalsIgnoreCase(objarr[idxcol].toString())) { - found = true; - for (int z = 0; z < cx.length; z++) { - cx[z].setVal(objarr[z]); - } - } - } - } - - if (!found) - this.addRow(objarr); - } - - - /** - * add a row of cells to this named range - * - * @param an array of Objects to insert at last rown - */ - public void addRow(Object[] objarr) throws Exception { - CellRange[] rngs = getCellRanges(); - if (rngs.length > 1) { - throw new WorkBookException("NamedRange.add Object array failed: too many CellRanges.", WorkBookException.RUNTIME_ERROR); - } else if (rngs.length == 0) { - throw new WorkBookException("NamedRange.add Object array failed: zero CellRanges", WorkBookException.RUNTIME_ERROR); - } - - // - WorkSheetHandle shtx = rngs[0].getSheet(); - - int x = rngs[0].firstcellrow; - CellHandle[] cxx = shtx.insertRow(x, objarr, true); - - //for(int t=0;t 1) { - throw new WorkBookException("NamedRange.addCell failed -- more than one cell range defined in this NameHandle, cannot determine where to add cell.", WorkBookException.RUNTIME_ERROR); - } - rngs[0].addCellToRange(cx); - setLocation(rngs[0].getRange()); - } - - /** - * set the referenced cells for the named range - *

                  - * this reference should be in the standard excel syntax including sheetname, - * for instance "Sheet1!A1:Z255" - */ - public void setLocation(String strloc) { - - // if there is no sheetname, and no sheet, then this is an unusable name and cannot be added - if ((strloc.indexOf("!") == -1) && (strloc.indexOf("=") == -1)) { - if (this.get2DSheetName() == null) { - this.remove(); - throw new IllegalArgumentException("Named Range References must include a Sheet name."); - } else - strloc = this.get2DSheetName() + "!" + strloc; - } - try { - myName.setLocation(strloc); - } catch (FunctionNotSupportedException e) { - Logger.logWarn("NameHandle.setLocation :" + strloc + " failed: " + e.toString()); - } - } - - /** - * get the referenced named cell range as string in standard excel syntax including sheetname, - *

                  - * for instance "Sheet1!A1:Z255" - */ - public String getLocation() { - try { - String loc = myName.getLocation(); - return loc; - } catch (Exception e) { - Logger.logErr("Error getting named range location" + e); - return null; - } - } - - - /** - * set the name String for the range definition - * - * @param String definition name - */ - public void setName(String newname) { - myName.setName(newname); - } - - /** - * returns the name String for the range definition - * - * @return String definition name - */ - public String getName() { - return myName.getName(); - } - - public String toString() { - return getName(); - } - - /** - * returns the name's formula String for the range definition - * - * @return String the expression string - */ - public String getExpressionString() { - try { - return myName.getExpressionString(); - } catch (Exception e) { - if (DEBUGLEVEL > -1) Logger.logWarn("Could not parse expression string for name: " + this.getName()); - } - return "#ERR"; - } - - /** - * removes this Named Range from the WorkBook. - * - * @return whether the removal was a success - */ - public boolean remove() { - boolean success = false; - try { - success = this.myName.getWorkBook().removeName(myName); - } catch (Exception e) { - return false; - } - return success; - } - - /** - * set whether the CellRanges referenced by the NameHandle - * will add blank records to the WorkBook for any missing Cells - * contained within the range. - * - * @param b set whether to create blank records for missing Cells - */ - public void setCreateBlanks(boolean b) { - createblanks = b; - } - - /** - * gets the array of Cells in this Name - *

                  - * NOTE: this method variation also returns the Sheetname for the name record if not null. - *

                  - * Thus this method is limited to use with 2D ranges. - * - * @param fragment whether to enclose result in NameHandle tag - * @return Cell[] all Cells defined in this Name - */ - public String getCellRangeXML(boolean fragment) { - StringBuffer sbx = new StringBuffer(); - if (!fragment) - sbx.append(""); - sbx.append(""); - sbx.append(getCellRangeXML()); - sbx.append(""); - return sbx.toString(); - } - - /** - * gets the array of Cells in this Name - * - * @return Cell[] all Cells defined in this Name - */ - public String getCellRangeXML() { - StringBuffer sbx = new StringBuffer(); - try { - CellHandle[] celx = this.getCells(); - RowHandle rowhold = null; - for (int x = 0; x < celx.length; x++) { - RowHandle rx = celx[x].getRow(); - if (x == 0) { - rowhold = rx; - sbx.append(""); - sbx.append(celx[x].getXML()); // ignores merged ranges here - } else if (rowhold.getRowNumber() == rx.getRowNumber()) { - sbx.append(celx[x].getXML()); // ignores merged ranges here - } else { - sbx.append(""); - rowhold = rx; - sbx.append(""); - sbx.append(celx[x].getXML()); // ignores merged ranges here - } - } - sbx.append(""); - } catch (CellNotFoundException ex) { - Logger.logErr("NameHandle.getCellRangeXML failed: ", ex); - } - // sbx.append(""); - return sbx.toString(); - } - - /** - * gets the array of Cells in this Name - * - * @return Cell[] all Cells defined in this Name - */ - public CellHandle[] getCells() - throws CellNotFoundException { - if (false) - return null; - // first get the ptgArea3d from the parsed expression - try { - CompatibleVector cellhandles = new CompatibleVector(); - CellRange[] rngz = this.getCellRanges(); - if (rngz != null) { - for (int t = 0; t < rngz.length; t++) { - try { - CellHandle[] cells = rngz[t].getCells(); - // for(int b = (cells.length-1);b>=0;b--)cellhandles.add(cells[b]); - for (int b = 0; b < cells.length; b++) - if (cells[b] != null) - cellhandles.add(cells[b]); - } catch (Exception ex) { - Logger.logWarn("Could not get cells for range: " + rngz[t]); - } - } - } else { - return null; - } - CellHandle[] ret = new CellHandle[cellhandles.size()]; - return (CellHandle[]) cellhandles.toArray(ret); - } catch (Exception e) { - if (e instanceof CellNotFoundException) - throw (CellNotFoundException) e; - throw new CellNotFoundException(e.toString()); - } - } - - - /** - * Get an Array of CellRanges, one per referenced WorkSheet. - *

                  - * If this method throws CellNotFoundExceptions, then you are - * addressing a sparsely populated CellRange. - *

                  - * Use 'setCreateBlanks(true)' to populate these Cells and avoid - * this error. - * - * @return - * @throws Exception - */ - public CellRange[] getCellRanges() - throws Exception { - if (initialRange != null) { - CellRange[] ret = {initialRange}; - return ret; - } - // 20100217 KSC: try a better way (that can handle 3D refs and complex cell ranges) - String loc = myName.getLocation(); // may contain one or more ranges, separated by ","'s if complex - WorkSheetHandle[] sheets = this.getReferencedSheets(); - // handle commas within quoted sheet names (TestExtenXLSEngine.TestQuotedSheetsWithCommansInNRs) - // NOTE that sheetnames must be properly qualified for the split to work below - String[] nranges = loc.split(",(?=([^'|\"]*'[^'|\"]*'|\")*[^'|\"]*$)"); - // below cannot handle commas embedded within quotes -// String[] nranges= StringTool.splitString(loc, ","); // TODO: can be another delimeter? - CellRange[] ranges = new CellRange[nranges.length]; - for (int i = 0; i < nranges.length; i++) { - String r = nranges[i]; - if (r.indexOf("!") == -1) { // no sheetname - r = sheets[i] + "!" + r; - } - ranges[i] = new CellRange(r, mybook, createblanks); - ranges[i].setParent(this.myName); - } - - return ranges; - } - - /** - * Get WorkSheetHandles for all of the Boundsheets referenced in - * this NameHandle. - * - * @return an array of WorkSheetHandles referenced in this Name - */ - public WorkSheetHandle[] getReferencedSheets() - throws WorkSheetNotFoundException { - //first get the ptgArea3d from the parsed expression - Boundsheet[] bs = myName.getBoundSheets(); - if (bs == null) - throw new WorkSheetNotFoundException("Worksheet for Named Range: " + this.toString() + ":" + this.myName.getExpressionString()); - if (bs[0] == null) - throw new WorkSheetNotFoundException("Worksheet for Named Range: " + this.toString() + ":" + this.myName.getExpressionString()); - WorkSheetHandle[] ret = new WorkSheetHandle[bs.length]; - for (int x = 0; x < ret.length; x++) { - ret[x] = mybook.getWorkSheet(bs[x].toString()); - } - return ret; - } - - /** - * return the calculated value of this Name - * if it contains a parsed Expression (Formula) - * - * @return - * @throws FunctionNotSupportedException - */ - public Object getCalculatedValue() - throws FunctionNotSupportedException { - return myName.getCalculatedValue(); - } - - /** - * Sets the location lock on the Cell Reference at the - * specified location - *

                  - * Used to prevent updating of the Cell Reference when - * Cells are moved. - * - * @param location of the Cell Reference to be locked/unlocked - * @param lock status setting - * @return boolean whether the Cell Reference was found and modified - */ - public boolean setLocationLocked(String loc, boolean l) { - int x = Ptg.PTG_LOCATION_POLICY_UNLOCKED; - if (l) x = Ptg.PTG_LOCATION_POLICY_LOCKED; - return myName.setLocationPolicy(loc, x); - } - - /** - * Return a JSON object representing this name Handle. - * name:'nameOfRange' - * cellrange:'Sheet1!A1:B1' - * - * @return - */ - public String getJSON() { - return getJSON(false); - } - - - /** - * Return a JSON object representing this name Handle. - * name:'nameOfRange' - * cellrange:'Sheet1!A1:B1' - * cells:celldata - * - * @param whether to return cell data - * @return - */ - public String getJSON(boolean celldata) { - JSONObject theNameHandle = new JSONObject(); - try { - theNameHandle.put("name", this.getName()); - theNameHandle.put("cellrange", myName.getLocation()); - - if (celldata) { - StringBuffer ret = new StringBuffer(); - CellHandle[] cx1 = getCells(); - int p = cx1[0].getRowNum(); - for (int x = 0; x < cx1.length; x++) { - if (cx1[x] != null) { - if (cx1[x].getRowNum() != p) { - ret.append("\r\n"); - p = cx1[x].getRowNum(); - } - ret.append("'"); - try { - ret.append(cx1[x].getStringVal()); - } catch (Exception ex) { // handles empties - // - } - if (x != cx1.length - 1) - ret.append("',"); - else - ret.append("'"); - } - } - - theNameHandle.put("celldata", ret.toString()); - } - } catch (Exception e) { - Logger.logErr("Error creating JSON name handle: " + e); - } - return theNameHandle.toString(); - } - - /** - * Returns a JSON object in the same format as {@link CellRange#getJSON}. - */ - public JSONObject getJSONCellRange() { - JSONObject theRange = new JSONObject(); - JSONArray cells = new JSONArray(); - try { - CellHandle[] chandles = getCells(); - for (int i = 0; i < chandles.length; i++) { - CellHandle thisCell = chandles[i]; - JSONObject result = new JSONObject(); - - result.put(JSON_CELL, thisCell.getJSONObject()); - cells.put(result); - } - theRange.put(JSON_CELLS, cells); - } catch (Exception e) { - Logger.logErr("Error getting NamedRange JSON: " + e); - } - return theRange; - } - - /** - * return the sheetname for a 2D named range - *

                  - * NOTE: Does not work for 3D ranges - * - * @return the Sheet name if this is a 2d range - */ - public String get2DSheetName() { - try { - return this.myName.getBoundSheets()[0].getSheetName(); - } catch (Exception e) { - try { - return this.myName.getSheet().getSheetName(); - } catch (Exception ex) { - return null; - } - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/NameHandle.kt b/src/main/java/io/starter/OpenXLS/NameHandle.kt new file mode 100644 index 0000000..b7190da --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/NameHandle.kt @@ -0,0 +1,794 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.* +import io.starter.formats.XLS.formulas.Ptg +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.json.JSONArray +import org.json.JSONObject + +import io.starter.OpenXLS.JSONConstants.JSON_CELL +import io.starter.OpenXLS.JSONConstants.JSON_CELLS + +/** + * The NameHandle provides access to a Named Range and its Cells.

                  + *

                  + * Use the NameHandle to work with individual Named Ranges in an XLS file.

                  + *



                  + * With a NameHandle you can: + *



                  + *

                  + * get a handle to the Cells in a Name

                  + * set the default formatting for a Name +
                  * + *

                  + *

                  + * + * @see WorkBookHandle + * + * @see WorkSheetHandle + * + * @see FormulaHandle + */ +class NameHandle { + + private var myName: Name? = null + private var mybook: WorkBook? = null + private val DEBUGLEVEL = -1 + private var createblanks = false + private var initialRange: CellRange? = null + + /** + * Returns a handle to the object (either workbook or sheet) that is scoped + * to the name record + * + * + * Default scope is a WorkBookHandle, else the WorkSheetHandle is returned. + * + * @return the scope of the name + */ + /** + * Set the scope of this name to that of the handle passed in. + * + * + * This can either be a WorkbookHandle or a WorksheetHandle + * + * + * note: this will only be functional for the workbook that the name is contained in, + * you cannot change the scope to a different Document with this method. + * + * @param scope Workbookhandle or WorksheetHandle + */ + // this really shouldnt happen unless you are passing a scope in from a different workbook + var scope: Handle? + @Throws(WorkSheetNotFoundException::class) + get() { + val itab = myName!!.itab.toInt() + return if (itab == 0) mybook else mybook!!.getWorkSheet(itab - 1) + } + set(scope) { + var newitab = 0 + if (scope is WorkSheetHandle) { + newitab = scope.sheetNum + 1 + } + try { + myName!!.setNewScope(newitab) + } catch (e: WorkSheetNotFoundException) { + Logger.logErr("ERROR: setting new scope on name: $e") + } + + } + + + /** + * Return an XML representation of this name record + */ + // add the sheetname + // TODO: why no sheet defined for name??? + // it's possible that name expression can have quotes and other non-compliant characters + // deal with snafu character names + // if not workbook-scoped, add sheet scope for later retrieval + val xml: String + get() { + val retXML = StringBuffer() + var nmv = expressionString + if (nmv.indexOf("=!") > -1) { + val bs = myName!!.sheet + if (bs != null) { + nmv = bs.sheetName + "!" + nmv + } else { + nmv = StringTool.replaceChars("!", nmv, "") + } + } + nmv = io.starter.toolkit.StringTool.convertXMLChars(nmv) + + var nmx = name + nmx = io.starter.toolkit.StringTool.convertXMLChars(nmx) + + if (nmx.length == 1) { + if (Character.isLetterOrDigit(nmx[0])) { + Logger.logInfo("NameHandle getting XML for name: $nmx") + } else { + nmx = "#NAME" + } + } + + if (nmv.startsWith("=")) + nmv = nmv.substring(1) + retXML.append(" ") + return retXML.toString() + } + + + /** + * @return String XML rep of all the cells referenced by this range + */ + // add the sheetname + // TODO: why no sheet defined for name??? + // deal with snafu character names + val expandedXML: String + get() { + var nmv = expressionString + if (nmv.indexOf("=!") > -1) { + val bs = myName!!.sheet + if (bs != null) { + nmv = bs.sheetName + "!" + nmv + } else { + nmv = StringTool.replaceChars("!", nmv, "") + } + } + var nmx = name + nmx = io.starter.toolkit.StringTool.convertXMLChars(nmx) + + if (nmx.length == 1) { + if (Character.isLetterOrDigit(nmx[0])) { + Logger.logInfo("NameHandle getting XML for name: $nmx") + } else { + nmx = "#NAME" + } + } + + if (nmv.startsWith("=")) + nmv = nmv.substring(1) + val retXML = StringBuffer() + retXML.append("\t\n") + try { + val cells = cells + + for (i in cells!!.indices) { + retXML.append("\t\t" + cells[i].xml + "\n") + } + } catch (ex: CellNotFoundException) { + Logger.logErr("NameHandle.getExpandedXML failed: ", ex) + } + + retXML.append("\n") + + return retXML.toString() + } + + /** + * get the referenced named cell range as string in standard excel syntax including sheetname, + * + * + * for instance "Sheet1!A1:Z255" + */ + /** + * set the referenced cells for the named range + * + * + * this reference should be in the standard excel syntax including sheetname, + * for instance "Sheet1!A1:Z255" + */ + // if there is no sheetname, and no sheet, then this is an unusable name and cannot be added + var location: String? + get() { + try { + return myName!!.location + } catch (e: Exception) { + Logger.logErr("Error getting named range location$e") + return null + } + + } + set(strloc) { + var strloc = strloc + if (strloc.indexOf("!") == -1 && strloc.indexOf("=") == -1) { + if (this .2 DSheetName == null) + { + this.remove() + throw IllegalArgumentException("Named Range References must include a Sheet name.") + } + else + strloc = this.2DSheetName+"!"+strloc + } + try { + myName!!.location = strloc + } catch (e: FunctionNotSupportedException) { + Logger.logWarn("NameHandle.setLocation :$strloc failed: $e") + } + + } + + /** + * returns the name String for the range definition + * + * @return String definition name + */ + /** + * set the name String for the range definition + * + * @param String definition name + */ + var name: String + get() = myName!!.name + set(newname) { + myName!!.name = newname + } + + /** + * returns the name's formula String for the range definition + * + * @return String the expression string + */ + val expressionString: String + get() { + try { + return myName!!.expressionString + } catch (e: Exception) { + if (DEBUGLEVEL > -1) Logger.logWarn("Could not parse expression string for name: " + this.name) + } + + return "#ERR" + } + + /** + * gets the array of Cells in this Name + * + * @return Cell[] all Cells defined in this Name + */ + // ignores merged ranges here + // ignores merged ranges here + // ignores merged ranges here + // sbx.append(""); + val cellRangeXML: String + get() { + val sbx = StringBuffer() + try { + val celx = this.cells + var rowhold: RowHandle? = null + for (x in celx!!.indices) { + val rx = celx[x].row + if (x == 0) { + rowhold = rx + sbx.append("") + sbx.append(celx[x].xml) + } else if (rowhold!!.rowNumber == rx.rowNumber) { + sbx.append(celx[x].xml) + } else { + sbx.append("") + rowhold = rx + sbx.append("") + sbx.append(celx[x].xml) + } + } + sbx.append("") + } catch (ex: CellNotFoundException) { + Logger.logErr("NameHandle.getCellRangeXML failed: ", ex) + } + + return sbx.toString() + } + + /** + * gets the array of Cells in this Name + * + * @return Cell[] all Cells defined in this Name + */ + // first get the ptgArea3d from the parsed expression + // for(int b = (cells.length-1);b>=0;b--)cellhandles.add(cells[b]); + val cells: Array? + @Throws(CellNotFoundException::class) + get() { + if (false) + return null + try { + val cellhandles = CompatibleVector() + val rngz = this.cellRanges + if (rngz != null) { + for (t in rngz.indices) { + try { + val cells = rngz[t].getCells() + for (b in cells!!.indices) + if (cells[b] != null) + cellhandles.add(cells[b]) + } catch (ex: Exception) { + Logger.logWarn("Could not get cells for range: " + rngz[t]) + } + + } + } else { + return null + } + val ret = arrayOfNulls(cellhandles.size) + return cellhandles.toTypedArray() as Array + } catch (e: Exception) { + if (e is CellNotFoundException) + throw e + throw CellNotFoundException(e.toString()) + } + + } + + + /** + * Get an Array of CellRanges, one per referenced WorkSheet. + * + * + * If this method throws CellNotFoundExceptions, then you are + * addressing a sparsely populated CellRange. + * + * + * Use 'setCreateBlanks(true)' to populate these Cells and avoid + * this error. + * + * @return + * @throws Exception + */ + // 20100217 KSC: try a better way (that can handle 3D refs and complex cell ranges) + // may contain one or more ranges, separated by ","'s if complex + // handle commas within quoted sheet names (TestExtenXLSEngine.TestQuotedSheetsWithCommansInNRs) + // NOTE that sheetnames must be properly qualified for the split to work below + // below cannot handle commas embedded within quotes + // String[] nranges= StringTool.splitString(loc, ","); // TODO: can be another delimeter? + // no sheetname + val cellRanges: Array + @Throws(Exception::class) + get() { + if (initialRange != null) { + return arrayOf(initialRange) + } + val loc = myName!!.location + val sheets = this.referencedSheets + val nranges = loc!!.split(",(?=([^'|\"]*'[^'|\"]*'|\")*[^'|\"]*$)".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + val ranges = arrayOfNulls(nranges.size) + for (i in nranges.indices) { + var r = nranges[i] + if (r.indexOf("!") == -1) { + r = sheets[i].toString() + "!" + r + } + ranges[i] = CellRange(r, mybook, createblanks) + ranges[i].setParent(this.myName) + } + + return ranges + } + + /** + * Get WorkSheetHandles for all of the Boundsheets referenced in + * this NameHandle. + * + * @return an array of WorkSheetHandles referenced in this Name + */ + //first get the ptgArea3d from the parsed expression + val referencedSheets: Array + @Throws(WorkSheetNotFoundException::class) + get() { + val bs = myName!!.boundSheets + ?: throw WorkSheetNotFoundException("Worksheet for Named Range: " + this.toString() + ":" + this.myName!!.expressionString) + if (bs[0] == null) + throw WorkSheetNotFoundException("Worksheet for Named Range: " + this.toString() + ":" + this.myName!!.expressionString) + val ret = arrayOfNulls(bs.size) + for (x in ret.indices) { + ret[x] = mybook!!.getWorkSheet(bs[x].toString()) + } + return ret + } + + /** + * return the calculated value of this Name + * if it contains a parsed Expression (Formula) + * + * @return + * @throws FunctionNotSupportedException + */ + val calculatedValue: Any + @Throws(FunctionNotSupportedException::class) + get() = myName!!.calculatedValue + + /** + * Return a JSON object representing this name Handle. + * name:'nameOfRange' + * cellrange:'Sheet1!A1:B1' + * + * @return + */ + val json: String + get() = getJSON(false) + + /** + * Returns a JSON object in the same format as [CellRange.getJSON]. + */ + val jsonCellRange: JSONObject + get() { + val theRange = JSONObject() + val cells = JSONArray() + try { + val chandles = cells + for (i in chandles.indices) { + val thisCell = chandles[i] + val result = JSONObject() + + result.put(JSON_CELL, thisCell.jsonObject) + cells.put(result) + } + theRange.put(JSON_CELLS, cells) + } catch (e: Exception) { + Logger.logErr("Error getting NamedRange JSON: $e") + } + + return theRange + } + + /** + * return the sheetname for a 2D named range + * + * + * NOTE: Does not work for 3D ranges + * + * @return the Sheet name if this is a 2d range + */ + val 2DSheetName:String? + get() + { + try { + return this.myName!!.boundSheets!![0].sheetName + } catch (e: Exception) { + try { + return this.myName!!.sheet!!.sheetName + } catch (ex: Exception) { + return null + } + + } + + } + + /** + * Creates a new Named Range from a CellRange + * + * @param Name of the Range, the CellRange referenced by the Name + */ + constructor(namestr: String, cr: CellRange) { + mybook = cr.workBook + initialRange = cr // cache + myName = Name(mybook!!.workBook, namestr) + this.name = namestr + this.location = cr.toString() + } + + /** + * Create a NameHandle from an internal Name record + * + * @param c + * @param myb + */ + constructor(c: Name, myb: WorkBookHandle) { + myName = c + mybook = myb + } + + /** + * Create a new named range in the workbook + * note that this does not set the actual range, just the workbook and name, + * follow up with setLocation + * + * @param namestr + * @param myb + */ + @Deprecated("") + constructor(namestr: String, myb: WorkBookHandle) { + mybook = myb + myName = Name(mybook!!.workBook, namestr) + this.name = namestr + } + + /** + * Create a new named range in the workbook + * + * @param name name that should be used to reference this named range + * @param location rangeDef Range of the cells for this named range, in excel syntax including sheet name, ie "Sheet1!A1:D1" + * @param book WorkBookHandle to insert this named range into + */ + constructor(name: String, location: String, book: WorkBookHandle) { + mybook = book + myName = Name(mybook!!.workBook, name) + this.name = name + this.location = location + mybook!!.workBook.associateDereferencedNames(myName!!) + } + + /** + * sets the default format id for the Name's Cells + * + * @param int Format Id for all Cells in Name + */ + fun setFormatId(i: Int) { + // myName.setXFRecord(i); + // TODO: why doesn't this work with myName? + try { + val cs = this.cells + for (t in cs!!.indices) { + cs[t].formatId = i + } + } catch (ex: CellNotFoundException) { + Logger.logErr("NameHandle.setFormatId failed: ", ex) + } + + } + + /** + * deletes a row of cells in this named range, shifts subsequent + * rows up. + * + * @param the column to use as unique index + */ + @Throws(Exception::class) + fun deleteRow(idxcol: Int) { + val rngs = cellRanges + if (rngs.size > 1) { + throw WorkBookException("NamedRange.updateRow Object array failed: too many CellRanges.", WorkBookException.RUNTIME_ERROR) + } else if (rngs.size == 0) { + throw WorkBookException("NamedRange.updateRow Object array failed: zero CellRanges", WorkBookException.RUNTIME_ERROR) + } + + // + val shtx = rngs[0].sheet + val x = rngs[0].firstcellrow + + + val rx = rngs[0].rows + val found = false + // iterate, find, update + for (t in rx.indices) { + + } + } + + /** + * update a row of cells in this named range + * + * @param an array of Objects to update existing + * @param the column to use as unique index + */ + @Throws(Exception::class) + fun updateRow(objarr: Array, idxcol: Int) { + val rngs = cellRanges + if (rngs.size > 1) { + throw WorkBookException("NamedRange.updateRow Object array failed: too many CellRanges.", WorkBookException.RUNTIME_ERROR) + } else if (rngs.size == 0) { + throw WorkBookException("NamedRange.updateRow Object array failed: zero CellRanges", WorkBookException.RUNTIME_ERROR) + } + + // + val shtx = rngs[0].sheet + val x = rngs[0].firstcellrow + + + val rx = rngs[0].rows + var found = false + // iterate, find, update + for (t in rx.indices) { + + val cx = rx[t].cells + if (cx.size > idxcol) { + if (cx[idxcol].stringVal!!.equals(objarr[idxcol].toString(), ignoreCase = true)) { + found = true + for (z in cx.indices) { + cx[z].`val` = objarr[z] + } + } + } + } + + if (!found) + this.addRow(objarr) + } + + + /** + * add a row of cells to this named range + * + * @param an array of Objects to insert at last rown + */ + @Throws(Exception::class) + fun addRow(objarr: Array) { + val rngs = cellRanges + if (rngs.size > 1) { + throw WorkBookException("NamedRange.add Object array failed: too many CellRanges.", WorkBookException.RUNTIME_ERROR) + } else if (rngs.size == 0) { + throw WorkBookException("NamedRange.add Object array failed: zero CellRanges", WorkBookException.RUNTIME_ERROR) + } + + // + val shtx = rngs[0].sheet + + val x = rngs[0].firstcellrow + val cxx = shtx!!.insertRow(x, objarr, true) + + //for(int t=0;t 1) { + throw WorkBookException("NamedRange.addCell failed -- more than one cell range defined in this NameHandle, cannot determine where to add cell.", WorkBookException.RUNTIME_ERROR) + } + rngs[0].addCellToRange(cx) + location = rngs[0].getRange() + } + + override fun toString(): String { + return name + } + + /** + * removes this Named Range from the WorkBook. + * + * @return whether the removal was a success + */ + fun remove(): Boolean { + var success = false + try { + success = this.myName!!.workBook!!.removeName(myName) + } catch (e: Exception) { + return false + } + + return success + } + + /** + * set whether the CellRanges referenced by the NameHandle + * will add blank records to the WorkBook for any missing Cells + * contained within the range. + * + * @param b set whether to create blank records for missing Cells + */ + fun setCreateBlanks(b: Boolean) { + createblanks = b + } + + /** + * gets the array of Cells in this Name + * + * + * NOTE: this method variation also returns the Sheetname for the name record if not null. + * + * + * Thus this method is limited to use with 2D ranges. + * + * @param fragment whether to enclose result in NameHandle tag + * @return Cell[] all Cells defined in this Name + */ + fun getCellRangeXML(fragment: Boolean): String { + val sbx = StringBuffer() + if (!fragment) + sbx.append("") + sbx.append("") + sbx.append(cellRangeXML) + sbx.append("") + return sbx.toString() + } + + /** + * Sets the location lock on the Cell Reference at the + * specified location + * + * + * Used to prevent updating of the Cell Reference when + * Cells are moved. + * + * @param location of the Cell Reference to be locked/unlocked + * @param lock status setting + * @return boolean whether the Cell Reference was found and modified + */ + fun setLocationLocked(loc: String, l: Boolean): Boolean { + var x = Ptg.PTG_LOCATION_POLICY_UNLOCKED + if (l) x = Ptg.PTG_LOCATION_POLICY_LOCKED + return myName!!.setLocationPolicy(loc, x) + } + + + /** + * Return a JSON object representing this name Handle. + * name:'nameOfRange' + * cellrange:'Sheet1!A1:B1' + * cells:celldata + * + * @param whether to return cell data + * @return + */ + fun getJSON(celldata: Boolean): String { + val theNameHandle = JSONObject() + try { + theNameHandle.put("name", this.name) + theNameHandle.put("cellrange", myName!!.location) + + if (celldata) { + val ret = StringBuffer() + val cx1 = cells + var p = cx1!![0].rowNum + for (x in cx1.indices) { + if (cx1[x] != null) { + if (cx1[x].rowNum != p) { + ret.append("\r\n") + p = cx1[x].rowNum + } + ret.append("'") + try { + ret.append(cx1[x].stringVal) + } catch (ex: Exception) { // handles empties + // + } + + if (x != cx1.size - 1) + ret.append("',") + else + ret.append("'") + } + } + + theNameHandle.put("celldata", ret.toString()) + } + } catch (e: Exception) { + Logger.logErr("Error creating JSON name handle: $e") + } + + return theNameHandle.toString() + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/PivotTableHandle.java b/src/main/java/io/starter/OpenXLS/PivotTableHandle.java deleted file mode 100644 index 37f4bf1..0000000 --- a/src/main/java/io/starter/OpenXLS/PivotTableHandle.java +++ /dev/null @@ -1,574 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - - -import io.starter.formats.XLS.ColumnNotFoundException; -import io.starter.formats.XLS.SxStreamID; -import io.starter.formats.XLS.Sxview; -import io.starter.toolkit.Logger; -import org.json.JSONObject; - - -/** - * PivotTable Handle allows for manipulation of PivotTables within a WorkBook. - * - * @see WorkBookHandle - * @see WorkSheetHandle - * @see CellHandle - */ -/** - * - */ -/** - * - */ - -/** - * - */ -public class PivotTableHandle { - - public static int AUTO_FORMAT_Report1 = 1; - public static int AUTO_FORMAT_Report2 = 2; - public static int AUTO_FORMAT_Report3 = 3; - public static int AUTO_FORMAT_Report4 = 4; - public static int AUTO_FORMAT_Report5 = 5; - public static int AUTO_FORMAT_Report6 = 6; - public static int AUTO_FORMAT_Report7 = 7; - public static int AUTO_FORMAT_Report8 = 8; - public static int AUTO_FORMAT_Report9 = 9; - public static int AUTO_FORMAT_Report10 = 10; - public static int AUTO_FORMAT_Table1 = 11; - public static int AUTO_FORMAT_Table2 = 12; - public static int AUTO_FORMAT_Table3 = 13; - public static int AUTO_FORMAT_Table4 = 14; - public static int AUTO_FORMAT_Table5 = 15; - public static int AUTO_FORMAT_Table6 = 16; - public static int AUTO_FORMAT_Table7 = 17; - public static int AUTO_FORMAT_Table8 = 18; - public static int AUTO_FORMAT_Table9 = 19; - public static int AUTO_FORMAT_Table10 = 20; - public static int AUTO_FORMAT_Classic = 30; - private Sxview pt; - private WorkBookHandle book; - private WorkSheetHandle worksheet; - - /** - * Create a new PivotTableHandle from an Excel PivotTable - * - * @param PivotTable - * - the PivotTable to create a handle for. - */ - public PivotTableHandle(Sxview f, WorkBookHandle bk) { - pt = f; - book = bk; - //SxStreamID sxid= bk.getWorkBook().getPivotStrean(pt.getICache()); - //cellRange= sxid.getCellRange(); - } - - public WorkSheetHandle getWorkSheetHandle() { - return worksheet; - } - - /** - * @return Returns the cellRange. - */ - public CellRange getDataSourceRange() { - SxStreamID sxid = book.getWorkBook().getPivotStream(pt.getICache()); - return sxid.getCellRange(); - } - - /** - * Sets the Pivot Table Range to represent the Data to analyse - *
                  NOTE: any existing data will be replaced - * @param cellRange The cellRange to set. - */ - public void setSourceDataRange(CellRange cellRange) { - SxStreamID sxid = book.getWorkBook().getPivotStream(pt.getICache()); - sxid.setCellRange(cellRange); - try { - pt.setNPivotFields((short) cellRange.getCols().length); - } catch (ColumnNotFoundException e) { - - } - - } - - /** - * Sets the Pivot Table Range to represent the Data to analyse - *
                  NOTE: any existing data will be replaced - *
                  If the cell range does not contain sheet information, the sheet that the pivot table is located will be used - * @param cellRange - */ - public void setSourceDataRange(String range) { - int[] rc = ExcelTools.getRangeCoords(range); - if (range.indexOf("!") == -1) - range = this.getWorkSheetHandle() + "!" + range; - SxStreamID sxid = book.getWorkBook().getPivotStream(pt.getICache()); - sxid.setCellRange(range); - - pt.setNPivotFields((short) ((rc[3] - rc[1]) + 1)); - } - - /** - * Sets the Pivot Table data source from a named range - * @param namedrange Named Range - */ - public void setSource(String namedrange) { -// TODO: finish; update DCONNAME - } - - /** - * get the Name of the PivotTable - * - * @return String - value of the PivotTable if stored as a String. - */ - public String getTableName() { - return pt.getTableName(); - } - - /** - * set the Name of the PivotTable - * - * @param String - * - value of the PivotTable if stored as a String. - */ - public void setTableName(String tx) { - pt.setTableName(tx); - } - - /** - * returns the name of the data field - * @return - */ - public String getDataName() { - return pt.getDataName(); - } - - /** - * sets the name of the data field for this pivot table - * @param name - */ - public void setDataName(String name) { - pt.setDataName(name); - } - - /** - * returns whether a given row is contained in this PivotTable - * - * @param int the row number - * @return boolean whether the row is in the table - */ - public boolean containsRow(int x) { - return (x <= pt.getRwLast()) && (x >= pt.getRwFirst()); - } - - /** - * return a more friendly - * - * @see java.lang.Object#toString() - */ - public String toString() { - return this.getTableName(); - } - - /** - * returns whether a given col is contained in this PivotTable - * - * @param int the column number - * @return boolean whether the col is in the table - */ - public boolean containsCol(int x) { - return (x <= pt.getColLast()) && (x >= pt.getColFirst()); - } - - /** - * Takes a string as a current PivotTable location, and changes that pointer - * in the PivotTable to the new string that is sent. This can take single - * cells "A5" and cell ranges, "A3:d4" Returns true if the cell range - * specified in PivotTableLoc exists & can be changed else false. This also - * cannot change a cell pointer to a cell range or vice versa. - * - * @param String - * - range of Cells within PivotTable to modify - * @param String - * - new range of Cells within PivotTable - * - * - * public boolean changePivotTableLocation(String PivotTableLoc, - * String newLoc) throws PivotTableNotFoundException{ - * - * Logger.logInfo("Changing: " + PivotTableLoc + " to: " + - * newLoc); - * - * return false; - * - * } - */ - - /** - * set whether to display row grand totals - * - * @param boolean whether to display row grand totals - */ - public void setRowsHaveGrandTotals(boolean b) { - pt.setFRwGrand(b); - } - - /** - * get whether table displays row grand totals - * - * @return boolean whether table displays row grand total - */ - public boolean getRowsHaveGrandTotals() { - return pt.getFRwGrand(); - } - - /** - * set whether to display a column grand total - * - * @param boolean whether to display column grand total - */ - public void setColsHaveGrandTotals(boolean b) { - pt.setColGrand(b); - } - - /** - * get whether table displays a column grand total - * - * @return boolean whether table displays column grand total - */ - public boolean getColsHaveGrandTotals() { - return pt.getFColGrand(); - } - - /** - * set the auto format for the Table - * - *
                  -     *         The valid formats are:
                  -     *
                  -     *             PivotTableHandle.AUTO_FORMAT_Report1
                  -     *             PivotTableHandle.AUTO_FORMAT_Report2
                  -     *             PivotTableHandle.AUTO_FORMAT_Report3
                  -     *             PivotTableHandle.AUTO_FORMAT_Report4
                  -     *             PivotTableHandle.AUTO_FORMAT_Report5
                  -     *             PivotTableHandle.AUTO_FORMAT_Report6
                  -     *             PivotTableHandle.AUTO_FORMAT_Report7
                  -     *             PivotTableHandle.AUTO_FORMAT_Report8
                  -     *             PivotTableHandle.AUTO_FORMAT_Report9
                  -     *             PivotTableHandle.AUTO_FORMAT_Report10
                  -     *             PivotTableHandle.AUTO_FORMAT_Table1
                  -     *             PivotTableHandle.AUTO_FORMAT_Table2
                  -     *             PivotTableHandle.AUTO_FORMAT_Table3
                  -     *             PivotTableHandle.AUTO_FORMAT_Table4
                  -     *             PivotTableHandle.AUTO_FORMAT_Table5
                  -     *             PivotTableHandle.AUTO_FORMAT_Table6
                  -     *             PivotTableHandle.AUTO_FORMAT_Table7
                  -     *             PivotTableHandle.AUTO_FORMAT_Table8
                  -     *             PivotTableHandle.AUTO_FORMAT_Table9
                  -     *             PivotTableHandle.AUTO_FORMAT_Table10
                  -     * 
                  - * - * @param int the auto format Id for the table - */ - public void setAutoFormatId(int b) { - pt.setItblAutoFmt((short) b); - } - - /** - * get the auto format for the Table - * - * @return int the auto format Id for the table - */ - public int getAutoFormatId() { - return pt.getItblAutoFmt(); - } - - /** - * set whether to auto format the Table - * - * @param boolean whether to auto format the table - */ - public void setUsesAutoFormat(boolean b) { - pt.setFAutoFormat(b); - } - - /** - * get whether table has auto format applied - * - * @param boolean whether table has auto format applied - */ - public boolean getUsesAutoFormat() { - return pt.getFAutoFormat(); - } - - /** - * set Width/Height Autoformat is applied - * - * @param boolean whether to apply the Width/Height Autoformat - */ - public void setAutoWidthHeight(boolean b) { - pt.setFWH(b); - } - - /** - * get whether Width/Height Autoformat is applied - * - * @return boolean whether the Width/Height Autoformat is applied - */ - public boolean getAutoWidthHeight() { - return pt.getFWH(); - } - - /** - * set whether Font Autoformat is applied - * - * @param boolean whether to apply the Font Autoformat - */ - public void setAutoFont(boolean b) { - pt.setFFont(b); - } - - /** - * get whether Font Autoformat is applied - * - * @return boolean whether the Font Autoformat is applied - */ - public boolean getAutoFont() { - return pt.getFFont(); - } - - public void removeArtifacts() { - int[] coords = {pt.getRwFirst() - 2, pt.getColFirst(), pt.getRwLast(), - pt.getColLast()}; - try { - CellRange newr = new CellRange(worksheet, coords, true); - CellHandle[] ch = newr.getCells(); - for (int r = 0; r < ch.length; r++) { - if (ch[r] != null) - ch[r].remove(true); - } - } catch (Exception e) { - Logger.logWarn("could not remove artifacts in PivotTableHandle: " - + e); - } - } - - /** - * set whether Alignment Autoformat is applied - * - * @param boolean whether to apply the Alignment Autoformat - */ - public void setAutoAlign(boolean b) { - pt.setFAlign(b); - } - - /** - * get whether Alignment Autoformat is applied - * - * @return boolean whether the Alignment Autoformat is applied - */ - public boolean getAutoAlign() { - return pt.getFAlign(); - } - - /** - * set whether Border Autoformat is applied - * - * @param boolean whether to apply the Border Autoformat - */ - public void setAutoBorder(boolean b) { - pt.setFBorder(b); - } - - /** - * get whether Border Autoformat is applied - * - * @return boolean whether the Border Autoformat is applied - */ - public boolean getAutoBorder() { - return pt.getFBorder(); - } - - /** - * set whether Pattern Autoformat is applied - * - * @param boolean whether to apply the Pattern Autoformat - */ - public void setAutoPattern(boolean b) { - pt.setFPattern(b); - } - - /** - * get whether Pattern Autoformat is applied - * - * @return boolean whether the Pattern Autoformat is applied - */ - public boolean getAutoPattern() { - return pt.getFPattern(); - } - - /** - * set whether Number Autoformat is applied - * - * @param boolean whether to apply the Number Autoformat - */ - public void setAutoNumber(boolean b) { - pt.setFNumber(b); - } - - /** - * get whether Number Autoformat is applied - * - * @return boolean whether the Number Autoformat is applied - */ - public boolean getAutoNumber() { - return pt.getFNumber(); - } - - /** - * set the first row in the PivotTable - */ - public void setRowFirst(int s) { - // s--; // these are zero-based rows - pt.setRwFirst((short) s); - } - - /** - * get the first row in the PivotTable - */ - public int getRowFirst() { - return (int) pt.getRwFirst() + 1; - } - - /** - * set the last row in the PivotTable - */ - public void setRowLast(int s) { - s--; - pt.setRwLast((short) s); - } - - /** - * get the last row in the PivotTable - */ - public int getRowLast() { - return (int) pt.getRwLast() + 1; - } - - /** - * set the first Column in the PivotTable - */ - public void setColFirst(int s) { - pt.setColFirst((short) s); - } - - /** - * get the first Column in the PivotTable - */ - public int getColFirst() { - return (int) pt.getColFirst(); - } - - /** - * set the last Column in the PivotTable - */ - public void setColLast(int s) { - pt.setColLast((short) s); - } - - /** - * get the last Column in the PivotTable - */ - public int getColLast() { - return (int) pt.getColLast(); - } - - /** - * set the first header row - */ - public void setRowFirstHead(int s) { - s--; - pt.setRwFirstHead((short) s); - } - - /** - * get the first header row - */ - public int getRowFirstHead() { - return (int) pt.getRwFirstHead() + 1; - } - - /** - * set the first Row containing data - */ - public void setRowFirstData(int s) { - s--; // zero-based rows - pt.setRwFirstData((short) s); - } - - /** - * get the first Row containing data - */ - public int getRowFirstData() { - return (int) pt.getRwFirstData() + 1; - } - - /** - * set the first Column containing data - */ - public void setColFirstData(int s) { - pt.setColFirstData((short) s); - } - - /** - * get the first Column containing data - */ - public int getColFirstData() { - return (int) pt.getColFirstData(); - } - - /** - * returns the JSON representation of this PivotTable - * - * @return JSON Pivot Table representation - */ - public String getJSON() { - // copy all methods to this sucker - JSONObject thePivot = new JSONObject(); - - try { - thePivot.put("title", this.getTableName()); -// thePivot.put("cellrange", this.getCellRange().getRange()); - - } catch (Exception e) { - throw new WorkBookException("PivotTableHandle.getJSON failed:" + e, - WorkBookException.RUNTIME_ERROR); - } - return thePivot.toString(); - } - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/PivotTableHandle.kt b/src/main/java/io/starter/OpenXLS/PivotTableHandle.kt new file mode 100644 index 0000000..24c754d --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/PivotTableHandle.kt @@ -0,0 +1,536 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + + +import io.starter.formats.XLS.ColumnNotFoundException +import io.starter.formats.XLS.SxStreamID +import io.starter.formats.XLS.Sxview +import io.starter.toolkit.Logger +import org.json.JSONObject + + +/** + * PivotTable Handle allows for manipulation of PivotTables within a WorkBook. + * + * @see WorkBookHandle + * + * @see WorkSheetHandle + * + * @see CellHandle + */ +/** + * + */ +/** + * + */ + +/** + * + */ +class PivotTableHandle +/** + * Create a new PivotTableHandle from an Excel PivotTable + * + * @param PivotTable + * - the PivotTable to create a handle for. + */ +(private val pt: Sxview, private val book: WorkBookHandle)//SxStreamID sxid= bk.getWorkBook().getPivotStrean(pt.getICache()); +//cellRange= sxid.getCellRange(); +{ + val workSheetHandle: WorkSheetHandle? = null + + /** + * @return Returns the cellRange. + */ + val dataSourceRange: CellRange? + get() { + val sxid = book.workBook!!.getPivotStream(pt.iCache.toInt()) + return sxid!!.cellRange + } + + /** + * get the Name of the PivotTable + * + * @return String - value of the PivotTable if stored as a String. + */ + /** + * set the Name of the PivotTable + * + * @param String + * - value of the PivotTable if stored as a String. + */ + var tableName: String? + get() = pt.tableName + set(tx) { + pt.tableName = tx + } + + /** + * returns the name of the data field + * @return + */ + /** + * sets the name of the data field for this pivot table + * @param name + */ + var dataName: String? + get() = pt.dataName + set(name) { + pt.dataName = name + } + + /** + * get whether table displays row grand totals + * + * @return boolean whether table displays row grand total + */ + /** + * Takes a string as a current PivotTable location, and changes that pointer + * in the PivotTable to the new string that is sent. This can take single + * cells "A5" and cell ranges, "A3:d4" Returns true if the cell range + * specified in PivotTableLoc exists & can be changed else false. This also + * cannot change a cell pointer to a cell range or vice versa. + * + * @param String + * - range of Cells within PivotTable to modify + * @param String + * - new range of Cells within PivotTable + * + * + * public boolean changePivotTableLocation(String PivotTableLoc, + * String newLoc) throws PivotTableNotFoundException{ + * + * Logger.logInfo("Changing: " + PivotTableLoc + " to: " + + * newLoc); + * + * return false; + * + * } + */ + + /** + * set whether to display row grand totals + * + * @param boolean whether to display row grand totals + */ + var rowsHaveGrandTotals: Boolean + get() = pt.fRwGrand + set(b) { + pt.fRwGrand = b + } + + /** + * get whether table displays a column grand total + * + * @return boolean whether table displays column grand total + */ + /** + * set whether to display a column grand total + * + * @param boolean whether to display column grand total + */ + var colsHaveGrandTotals: Boolean + get() = pt.fColGrand + set(b) = pt.setColGrand(b) + + /** + * get the auto format for the Table + * + * @return int the auto format Id for the table + */ + /** + * set the auto format for the Table + * + *
                  +     * The valid formats are:
                  +     *
                  +     * PivotTableHandle.AUTO_FORMAT_Report1
                  +     * PivotTableHandle.AUTO_FORMAT_Report2
                  +     * PivotTableHandle.AUTO_FORMAT_Report3
                  +     * PivotTableHandle.AUTO_FORMAT_Report4
                  +     * PivotTableHandle.AUTO_FORMAT_Report5
                  +     * PivotTableHandle.AUTO_FORMAT_Report6
                  +     * PivotTableHandle.AUTO_FORMAT_Report7
                  +     * PivotTableHandle.AUTO_FORMAT_Report8
                  +     * PivotTableHandle.AUTO_FORMAT_Report9
                  +     * PivotTableHandle.AUTO_FORMAT_Report10
                  +     * PivotTableHandle.AUTO_FORMAT_Table1
                  +     * PivotTableHandle.AUTO_FORMAT_Table2
                  +     * PivotTableHandle.AUTO_FORMAT_Table3
                  +     * PivotTableHandle.AUTO_FORMAT_Table4
                  +     * PivotTableHandle.AUTO_FORMAT_Table5
                  +     * PivotTableHandle.AUTO_FORMAT_Table6
                  +     * PivotTableHandle.AUTO_FORMAT_Table7
                  +     * PivotTableHandle.AUTO_FORMAT_Table8
                  +     * PivotTableHandle.AUTO_FORMAT_Table9
                  +     * PivotTableHandle.AUTO_FORMAT_Table10
                  +    
                  * + * + * @param int the auto format Id for the table + */ + var autoFormatId: Int + get() = pt.itblAutoFmt.toInt() + set(b) { + pt.itblAutoFmt = b.toShort() + } + + /** + * get whether table has auto format applied + * + * @param boolean whether table has auto format applied + */ + /** + * set whether to auto format the Table + * + * @param boolean whether to auto format the table + */ + var usesAutoFormat: Boolean + get() = pt.fAutoFormat + set(b) { + pt.fAutoFormat = b + } + + /** + * get whether Width/Height Autoformat is applied + * + * @return boolean whether the Width/Height Autoformat is applied + */ + /** + * set Width/Height Autoformat is applied + * + * @param boolean whether to apply the Width/Height Autoformat + */ + var autoWidthHeight: Boolean + get() = pt.fwh + set(b) { + pt.fwh = b + } + + /** + * get whether Font Autoformat is applied + * + * @return boolean whether the Font Autoformat is applied + */ + /** + * set whether Font Autoformat is applied + * + * @param boolean whether to apply the Font Autoformat + */ + var autoFont: Boolean + get() = pt.fFont + set(b) { + pt.fFont = b + } + + /** + * get whether Alignment Autoformat is applied + * + * @return boolean whether the Alignment Autoformat is applied + */ + /** + * set whether Alignment Autoformat is applied + * + * @param boolean whether to apply the Alignment Autoformat + */ + var autoAlign: Boolean + get() = pt.fAlign + set(b) { + pt.fAlign = b + } + + /** + * get whether Border Autoformat is applied + * + * @return boolean whether the Border Autoformat is applied + */ + /** + * set whether Border Autoformat is applied + * + * @param boolean whether to apply the Border Autoformat + */ + var autoBorder: Boolean + get() = pt.fBorder + set(b) { + pt.fBorder = b + } + + /** + * get whether Pattern Autoformat is applied + * + * @return boolean whether the Pattern Autoformat is applied + */ + /** + * set whether Pattern Autoformat is applied + * + * @param boolean whether to apply the Pattern Autoformat + */ + var autoPattern: Boolean + get() = pt.fPattern + set(b) { + pt.fPattern = b + } + + /** + * get whether Number Autoformat is applied + * + * @return boolean whether the Number Autoformat is applied + */ + /** + * set whether Number Autoformat is applied + * + * @param boolean whether to apply the Number Autoformat + */ + var autoNumber: Boolean + get() = pt.fNumber + set(b) { + pt.fNumber = b + } + + /** + * get the first row in the PivotTable + */ + /** + * set the first row in the PivotTable + */ + // s--; // these are zero-based rows + var rowFirst: Int + get() = pt.rwFirst.toInt() + 1 + set(s) { + pt.rwFirst = s.toShort() + } + + /** + * get the last row in the PivotTable + */ + /** + * set the last row in the PivotTable + */ + var rowLast: Int + get() = pt.rwLast.toInt() + 1 + set(s) { + var s = s + s-- + pt.rwLast = s.toShort() + } + + /** + * get the first Column in the PivotTable + */ + /** + * set the first Column in the PivotTable + */ + var colFirst: Int + get() = pt.colFirst.toInt() + set(s) { + pt.colFirst = s.toShort() + } + + /** + * get the last Column in the PivotTable + */ + /** + * set the last Column in the PivotTable + */ + var colLast: Int + get() = pt.colLast.toInt() + set(s) { + pt.colLast = s.toShort() + } + + /** + * get the first header row + */ + /** + * set the first header row + */ + var rowFirstHead: Int + get() = pt.rwFirstHead.toInt() + 1 + set(s) { + var s = s + s-- + pt.rwFirstHead = s.toShort() + } + + /** + * get the first Row containing data + */ + /** + * set the first Row containing data + */ + // zero-based rows + var rowFirstData: Int + get() = pt.rwFirstData.toInt() + 1 + set(s) { + var s = s + s-- + pt.rwFirstData = s.toShort() + } + + /** + * get the first Column containing data + */ + /** + * set the first Column containing data + */ + var colFirstData: Int + get() = pt.colFirstData.toInt() + set(s) { + pt.colFirstData = s.toShort() + } + + /** + * returns the JSON representation of this PivotTable + * + * @return JSON Pivot Table representation + */ + // copy all methods to this sucker + // thePivot.put("cellrange", this.getCellRange().getRange()); + val json: String + get() { + val thePivot = JSONObject() + + try { + thePivot.put("title", this.tableName) + + } catch (e: Exception) { + throw WorkBookException("PivotTableHandle.getJSON failed:$e", + WorkBookException.RUNTIME_ERROR) + } + + return thePivot.toString() + } + + /** + * Sets the Pivot Table Range to represent the Data to analyse + *

                  NOTE: any existing data will be replaced + * @param cellRange The cellRange to set. + */ + fun setSourceDataRange(cellRange: CellRange) { + val sxid = book.workBook!!.getPivotStream(pt.iCache.toInt()) + sxid!!.cellRange = cellRange + try { + pt.nPivotFields = cellRange.cols.size.toShort() + } catch (e: ColumnNotFoundException) { + + } + + } + + /** + * Sets the Pivot Table Range to represent the Data to analyse + *

                  NOTE: any existing data will be replaced + *

                  If the cell range does not contain sheet information, the sheet that the pivot table is located will be used + * @param cellRange + */ + fun setSourceDataRange(range: String) { + var range = range + val rc = ExcelTools.getRangeCoords(range) + if (range.indexOf("!") == -1) + range = this.workSheetHandle.toString() + "!" + range + val sxid = book.workBook!!.getPivotStream(pt.iCache.toInt()) + sxid!!.setCellRange(range) + + pt.nPivotFields = (rc[3] - rc[1] + 1).toShort() + } + + /** + * Sets the Pivot Table data source from a named range + * @param namedrange Named Range + */ + fun setSource(namedrange: String) { + // TODO: finish; update DCONNAME + } + + /** + * returns whether a given row is contained in this PivotTable + * + * @param int the row number + * @return boolean whether the row is in the table + */ + fun containsRow(x: Int): Boolean { + return x <= pt.rwLast && x >= pt.rwFirst + } + + /** + * return a more friendly + * + * @see java.lang.Object.toString + */ + override fun toString(): String? { + return this.tableName + } + + /** + * returns whether a given col is contained in this PivotTable + * + * @param int the column number + * @return boolean whether the col is in the table + */ + fun containsCol(x: Int): Boolean { + return x <= pt.colLast && x >= pt.colFirst + } + + fun removeArtifacts() { + val coords = intArrayOf(pt.rwFirst - 2, pt.colFirst.toInt(), pt.rwLast.toInt(), pt.colLast.toInt()) + try { + val newr = CellRange(workSheetHandle!!, coords, true) + val ch = newr.getCells() + for (r in ch!!.indices) { + if (ch[r] != null) + ch[r].remove(true) + } + } catch (e: Exception) { + Logger.logWarn("could not remove artifacts in PivotTableHandle: $e") + } + + } + + companion object { + + var AUTO_FORMAT_Report1 = 1 + var AUTO_FORMAT_Report2 = 2 + var AUTO_FORMAT_Report3 = 3 + var AUTO_FORMAT_Report4 = 4 + var AUTO_FORMAT_Report5 = 5 + var AUTO_FORMAT_Report6 = 6 + var AUTO_FORMAT_Report7 = 7 + var AUTO_FORMAT_Report8 = 8 + var AUTO_FORMAT_Report9 = 9 + var AUTO_FORMAT_Report10 = 10 + var AUTO_FORMAT_Table1 = 11 + var AUTO_FORMAT_Table2 = 12 + var AUTO_FORMAT_Table3 = 13 + var AUTO_FORMAT_Table4 = 14 + var AUTO_FORMAT_Table5 = 15 + var AUTO_FORMAT_Table6 = 16 + var AUTO_FORMAT_Table7 = 17 + var AUTO_FORMAT_Table8 = 18 + var AUTO_FORMAT_Table9 = 19 + var AUTO_FORMAT_Table10 = 20 + var AUTO_FORMAT_Classic = 30 + } + + +} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.java b/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.java deleted file mode 100644 index 074d7b8..0000000 --- a/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.java +++ /dev/null @@ -1,728 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.*; - -import java.util.Iterator; - -/** - * The PrinterSettingsHandle gives you control over the printer settings for a Sheet such as whether to print in landscape or portrait mode. - *

                  - * The PrinterSettingsHandle provides fine-grained control over printing settings - * in Excel. - *

                  - * NOTE: you can only view the effects of these methods in an open Excel file when - * you use the "Print Setup" command. - *

                  - * OpenXLS does not currently support directly sending - * spreadsheet data to a printer. - *

                  - *

                  - * Example Usage: - *

                  - * ...
                  - * PrinterSettingsHandle printersetup = sheet.getPrinterSettings();
                  - * // Paper Size
                  - * printersetup.setPaperSize(PrinterSettingsHandle.PAPER_SIZE_LEDGER_17x11);
                  - * // Scaling
                  - * printersetup.setScale(125);
                  - * //	resolution
                  - * printersetup.setResolution(300);
                  - * ...
                  - * 
                  - */ -/* order of records in Page Settings Block: - * HORIZONTALPAGEBREAKS - VERTICALPAGEBREAKS - HEADER - FOOTER - HCENTER - VCENTER - LEFTMARGIN - RIGHTMARGIN - TOPMARGIN - BOTTOMMARGIN - PLS - SETUP - BITMAP - * - */ -public class PrinterSettingsHandle implements Handle { - - - // paper size ints - public static final int PAPER_SIZE_UNDEFINED = 0; - public static final int PAPER_SIZE_LETTER_8_5x11 = 1; // Letter 81/2" x 11" - public static final int PAPER_SIZE_LETTER_SMALL = 2; // Letter small 81/2" x 11" - public static final int PAPER_SIZE_TABLOID_11x17 = 3; // Tabloid 11" x 17" - public static final int PAPER_SIZE_LEDGER_17x11 = 4; // Ledger 17" x 11" - public static final int PAPER_SIZE_LEGAL_8_5x14 = 5; // Legal 81/2" x 14" - public static final int PAPER_SIZE_STATEMENT_5_5x8_5 = 6; // Statement 51/2" x 81/2" - public static final int PAPER_SIZE_LETTER_EXTRA_9_5Ax12 = 50; // Letter Extra 91/2" x 12 - public static final int PAPER_SIZE_LEGAL_EXTRA_9_5Ax15 = 51; // Legal Extra 91/2" x 15" - public static final int PAPER_SIZE_TABLOID_EXTRA_1111_16Ax18 = 52; // Tabloid Extra 1111/16" x 18" - public static final int PAPER_SIZE_A4_EXTRA_235MM_X_322MM = 53; // A4 Extra 235mm x 322mm - public static final int PAPER_SIZE_LETTER_TRANSVERSE_8_5Ax11 = 54; // Letter Transverse 81/2" x 11" - public static final int PAPER_SIZE_EXECUTIVE_7_QUARTER_X_10_5 = 7; // Executive 71/4" x 101/2" - public static final int PAPER_SIZE_TRANSVERSE_210MM_X_297MM = 55; // A4 Transverse 210mm x 297mm - public static final int PAPER_SIZE_A3_297MM_X_420MM = 8; // 8 A3 297mm x 420mm - public static final int PAPER_SIZE_LETTER_EXTRA_TRANSV_9_5_X_12 = 56; // 56 Letter Extra Transv. 91/2" x 12" - public static final int PAPER_SIZE_A4_210MM_X_297MM = 9; // A4 210mm x 297mm - public static final int PAPER_SIZE_SUPER_A_A4_227MM_X_356MM = 57; // Super A/A4 227mm x 356mm - public static final int PAPER_SIZE_A4_SMALL_210MM_X_297MM = 10; // A4 small 210mm x 297mm - public static final int PAPER_SIZE_SUPER_B_A3_305MM_X_487MM = 58; // Super B/A3 305mm x 487mm - public static final int PAPER_SIZE_A5_148MM_X_210MM = 11; // A5 148mm x 210mm - public static final int PAPER_SIZE_LETTER_PLUS = 59; // Letter Plus - public static final int PAPER_SIZE_2_X_1211_16 = 81; // 2" x 1211/16" - public static final int PAPER_SIZE_B4_JIS_257MM_X_364MM = 12; // B4 (JIS) 257mm x 364mm - public static final int PAPER_SIZE_A4_PLUS_210MM_X_330MM = 60; // A4 Plus 210mm x 330mm - public static final int PAPER_SIZE_B5_JIS_182MM_X_257MM = 13; // B5 (JIS) 182mm x 257mm - public static final int PAPER_SIZE_A5_TRANSVERSE_148MM_X_210MM = 61; // A5 Transverse 148mm x 210mm - public static final int PAPER_SIZE_FOLIO_8_5_X_13 = 14; // Folio 81/2" x 13" - public static final int PAPER_SIZE_B5_JIS_TRANSVERSE_182MM_X_257MM = 62; // B5 (JIS) Transverse 182mm x 257mm - public static final int PAPER_SIZE_QUATRO_215MM_X_275MM = 15; // Quarto 215mm x 275mm - public static final int PAPER_SIZE_A3_EXTRA_322MM_X_445MM = 63; // A3 Extra 322mm x 445mm - public static final int PAPER_SIZE_10Ax14_10_X_14 = 16; // 10x14 10" x 14" - public static final int PAPER_SIZE_A5_EXTRA_174MM_X_235 = 64; // A5 Extra 174mm x 235mm - public static final int PAPER_SIZE_11Ax17_11_X_17 = 17; // 11x17 11" x 17" - public static final int PAPER_SIZE_B5_ISO_EXTRA_201MM_X_276MM = 65; // B5 (ISO) Extra 201mm x 276mm - public static final int PAPER_SIZE_NOTE_8_5_X_11 = 18; // Note 81/2" x 11" - public static final int PAPER_SIZE_A2_420MM_X_594MM = 66; // A2 420mm x 594mm - public static final int PAPER_SIZE_ENVELOPE_9_3_78_X_8_78 = 19; // Envelope #9 37/8" x 87/8" - public static final int PAPER_SIZE_A3_TRANSVERSE_297MM_X_420MM = 67; // A3 Transverse 297mm x 420mm - public static final int PAPER_SIZE_ENVELOPE_10_4_18_X_9_5 = 20; // Envelope #10 41/8" x 91/2" - public static final int PAPER_SIZE_EXTRA_TRANSVERSE_322MM_X_445MM = 68; // A3 Extra Transverse 322mm x 445mm - public static final int PAPER_SIZE_ENVELOPE_11_4_5_X_10_38 = 21; // Envelope #11 41/2" x 103/8" - public static final int PAPER_SIZE_DBL_JAP_POSTCARD_200MM_X_148MM = 69; // Dbl. Japanese Postcard 200mm x 148mm - public static final int PAPER_SIZE_ENVELOPE_12_4_34_X_11 = 22; // Envelope #12 43/4" x 11" - public static final int PAPER_SIZE_A6_105MM_X_148MM = 70; // A6 105mm x 148mm - public static final int PAPER_SIZE_ENVELOPE_14_5_X_11_5 = 23; // Envelope #14 5" x 111/2" - public static final int PAPER_SIZE_C_17_X_22_72 = 24; // C 17" x 22" 72 - public static final int PAPER_SIZE_D_22_X_34_73 = 25; // D 22" x 34" 73 - public static final int PAPER_SIZE_E_34_X_44_74 = 26; // E 34" x 44" 74 - public static final int PAPER_SIZE_DL_ENVELOPE_110MM_X_110MM_X_220MM = 27; // Envelope DL 110mm x 220mm - public static final int PAPER_SIZE_LETTER_ROTATED_11_X_8_5 = 75; // Letter Rotated 11" x 81/2" - public static final int PAPER_SIZE_ENVELOPE_C5_162MM_X_229M = 28; // Envelope C5 162mm x 229mm - public static final int PAPER_SIZE_A3_ROTATED_420MM_X_297MM = 76; // A3 Rotated 420mm x 297mm - public static final int PAPER_SIZE_ENVELOPE_C3_324MM_X_458MM = 29; // Envelope C3 324mm x 458mm - public static final int PAPER_SIZE_A4_ROTATED_297MM_X_210MM = 77; // A4 Rotated 297mm x 210mm - public static final int PAPER_SIZE_ENVELOPE_C4_229MM_X_324MM = 30; // Envelope C4 229mm x 324mm - public static final int PAPER_SIZE_A5_ROTATED_210MM_X_148MM = 78; // A5 Rotated 210mm x 148mm - public static final int PAPER_SIZE_ENVELOPE_C6_115MM_X_162MM = 31; // Envelope C6 114mm x 162mm - public static final int PAPER_SIZE_ENVELOPE_C6_C5_114MM_X_229MM = 32; // Envelope C6/C5 114mm x 229mm - public static final int PAPER_SIZE_B4_ISO_250MM_X_353MM = 33; // B4 (ISO) 250mm x 353mm - public static final int PAPER_SIZE_B5_ISO_176MM_X_250MM = 34; // B5 (ISO) 176mm x 250mm - public static final int PAPER_SIZE_DBL_JAP_POSTCARD_ROT_148MM_X_200MM = 82; // Dbl. Jap. Postcard Rot. 148mm x 200mm - public static final int PAPER_SIZE_B6_ISO_125MM_X_176MM = 35; // B6 (ISO) 125mm x 176mm - public static final int PAPER_SIZE_ENVELOPE_ITALY_10MM_X_230MM = 36; // Envelope Italy 110mm x 230mm 84 - public static final int PAPER_SIZE_ENVELOPE_MONARCH_3_7_8_X_7_5 = 37; // Envelope Monarch 37/8" x 71/2" 85 - public static final int PAPER_SIZE_6_3_4_ENVELOPE_3_5_8_X_6_5 = 38; // 63/4 Envelope 35/8" x 61/2" 86 - public static final int PAPER_SIZE_US_STANDARD_FANFOLD_147_8_X_11 = 39; // US Standard Fanfold 147/8" x 11" 87 - public static final int PAPER_SIZE_GERMAN_STD_FANFOLD_8_5_X_12 = 40; // German Std. Fanfold 81/2" x 12" - public static final int PAPER_SIZE_GERMAN_LEGAL_FANFOLD_8_5_X_13 = 41; // German Legal Fanfold 81/2" x 13" - // public static final int PAPER_SIZE_B4_ISO_250MM_X_353MM = 42 ; // B4 (ISO) 250mm x 353mm - public static final int PAPER_SIZE_JAP_POSTCARD_100M_X_148MM = 43; // Japanese Postcard 100mm x 148mm - public static final int PAPER_SIZE_9_X_11 = 44; // 9x11 9" x 11" - public static final int PAPER_SIZE_10_X_11 = 45; // 10x11 10" x 11" - public static final int PAPER_SIZE_15_X_11 = 46; // 15x11 15" x 11" - public static final int PAPER_SIZE_ENVELOPE_INVITE_220MM_X_220MM = 47; // Envelope Invite 220mm x 220mm - public static final int PAPER_SIZE_B4_JIS_ROTATED_364MM_X_257MM = 79; // B4 (JIS) Rotated 364mm x 257mm - public static final int PAPER_SIZE_B5_JIS_ROTATED_257MMX_X_182MM = 80; // B5 (JIS) Rotated 257mm x 182mm - public static final int PAPER_SIZE_JAP_POSTCARD_ROT_148MM_X_100MM = 81; // Japanese Postcard Rot. 148mm x 100mm - public static final int PAPER_SIZE_A6_ROTATED_148MM_X_105MM = 83; // A6 Rotated 148mm x 105mm - public static final int PAPER_SIZE_B6_JIS_128MM_X_182MM = 88; // B6 (JIS) 128mm x 182mm - public static final int PAPER_SIZE_B6_JIS_ROT_182MM_X_128MM = 89; // B6 (JIS) Rotated 182mm x 128mm - public static final int PAPER_SIZE_12_X_11 = 90; // 12x11 12" x 11" - - /** - * default constructor - */ - public PrinterSettingsHandle(Boundsheet sheet) { - this.sheet = sheet; - - Iterator iter = sheet.getPrintRecs().iterator(); - while (iter.hasNext()) { - BiffRec record = (BiffRec) iter.next(); - - if (record instanceof Setup) - printerSettings = (Setup) record; - else if (record instanceof HCenter) - hCenter = (HCenter) record; - else if (record instanceof VCenter) - vCenter = (VCenter) record; - else if (record instanceof LeftMargin) - leftMargin = (LeftMargin) record; // missing in default set of records - else if (record instanceof RightMargin) - rightMargin = (RightMargin) record; // missing in default set of records - else if (record instanceof TopMargin) - topMargin = (TopMargin) record; // missing in default set of records - else if (record instanceof BottomMargin) - bottomMargin = (BottomMargin) record; // missing in default set of records - else if (record instanceof PrintGrid) - grid = (PrintGrid) record; - else if (record instanceof PrintRowCol) - headers = (PrintRowCol) record; - else if (record instanceof WsBool) - wsBool = (WsBool) record; - } - // Actually, the below comment is incorrect: do NOT do this unconditionally - //printerSettings.setNoPrintData(false); // there IS printer setup data - } - - Boundsheet sheet; - - private Setup printerSettings; - private HCenter hCenter; - private VCenter vCenter; - private LeftMargin leftMargin; - private RightMargin rightMargin; - private TopMargin topMargin; - private BottomMargin bottomMargin; - private PrintGrid grid; - private PrintRowCol headers; - private WsBool wsBool; - - // the following are unimplemented printer setting recs: - // HORIZONTALPAGEBREAKS; - // VERTICALPAGEBREAKS; - - - /** - * get the number of copies to print - * - * @return the number of copies - */ - public short getCopies() { - return printerSettings.getCopies(); - } - - - /** - * get the footer margin size in inches - * - * @return the footer margin in inches - */ - public double getFooterMargin() { - return printerSettings.getFooterMargin(); - } - - /** - * get the header margin size in inches - * - * @return the header margin in inches - */ - public double getHeaderMargin() { - return printerSettings.getHeaderMargin(); - } - - /** - * Gets the left print margin. - */ - public double getLeftMargin() { - if (leftMargin == null) { - leftMargin = new LeftMargin(); - this.sheet.addMarginRecord(leftMargin); - } - return leftMargin.getMargin(); - } - - /** - * Gets the right print margin. - */ - public double getRightMargin() { - if (rightMargin == null) { - rightMargin = new RightMargin(); - this.sheet.addMarginRecord(rightMargin); - } - return rightMargin.getMargin(); - } - - /** - * Gets the top print margin. - */ - public double getTopMargin() { - if (topMargin == null) { - topMargin = new TopMargin(); - this.sheet.addMarginRecord(topMargin); - } - return topMargin.getMargin(); - } - - /** - * Gets the bottom print margin. - */ - public double getBottomMargin() { - if (bottomMargin == null) { - bottomMargin = new BottomMargin(); - this.sheet.addMarginRecord(bottomMargin); - } - return bottomMargin.getMargin(); - } - - /** - * get the landscape orientation - * - * @return whether the print orientation is set to landscape - */ - public boolean getLandscape() { - return printerSettings.getLandscape(); - } - - /** - * get the left-to-right print orientation - * - * @return whether the print orientation is set to left-to-right - */ - public boolean getLeftToRight() { - return printerSettings.getLeftToRight(); - } - - /** - * get whether printing is in black and white - * - * @return black and white - */ - public boolean getNoColor() { - return printerSettings.getNoColor(); - } - - /** - * get whether to ignore orientation - * - * @return ignore orientation - */ - public boolean getNoOrient() { - return printerSettings.getNoOrient(); - } - - /** - * get whether printer data is missing - * - * @return whether the printer data is missing - */ - public boolean getNoPrintData() { - return printerSettings.getNoPrintData(); - } - - /** - * get the page to start printing from - * - * @return the page to start printing from - */ - public short getPageStart() { - return printerSettings.getPageStart(); - } - - /** - * Returns the paper size setting for the printer setup based on the - * following table: - * - * @return paper size - */ - public short getPaperSize() { - return printerSettings.getPaperSize(); - } - - /** - * @return whether to print Notes. - */ - public boolean getPrintNotes() { - return printerSettings.getPrintNotes(); - } - - - /** - * get the print resolution - * - * @return the printer resolution in DPI - */ - public short getResolution() { - return printerSettings.getResolution(); - } - - /** - * get the scale of the printer output in whole percentages - *

                  - * ie: 25 = 25% - * - * @return the scale of printer output - */ - public short getScale() { - return printerSettings.getScale(); - } - - /** - * use custom start page for auto numbering - * - * @return Returns whether to use a custom start page - */ - public boolean getUsePage() { - return printerSettings.getUsePage(); - } - - /** - * get the vertical print resolution - * - * @return the vertical printer resolution in DPI - */ - public short getVerticalResolution() { - return printerSettings.getVerticalResolution(); - } - - /** - * Whether the sheet should be centered horizontally. - */ - public boolean isHCenter() { - return hCenter.isHCenter(); - } - - /** - * Whether the sheet should be centered vertically. - */ - public boolean isVCenter() { - return vCenter.isVCenter(); - } - - /** - * Whether the grid lines will be printed. - */ - public boolean isPrintGridLines() { - return grid.isPrintGrid(); - } - - /** - * Whether the row and column headers will be printed. - */ - public boolean isPrintRowColHeaders() { - return headers.isPrintHeaders(); - } - - /** - * Gets whether the sheet will be printed fit to some number of pages. - */ - public boolean isFitToPage() { - return wsBool.isFitToPage(); - } - - /** - * set for draft quality output - * - * @param whether to use draft quality - */ - public void setDraft(boolean b) { - printerSettings.setDraft(b); - } - - /** - * Set the output to print onto this number of pages high - *

                  - * ie: setFitHeight(10) will stretch the print out to fit 10 - * pages high - * - * @param number of pages to fit to height - */ - public void setFitHeight(int numpages) { - printerSettings.setFitHeight((short) numpages); - } - - /** - * Set the output to print onto this number of pages wide - *

                  - * ie: setFitWidth(10) will stretch the print out to fit 10 - * pages wide - * - * @param number of pages to fit to width - */ - public void setFitWidth(int numpages) { - printerSettings.setFitWidth((short) numpages); - } - - /** - * Sets whether the sheet will be printed fit to some number of pages. - */ - public void setFitToPage(boolean value) { - wsBool.setFitToPage(value); - } - - /** - * sets the footer margin in inches - * - * @param footer margin - */ - public void setFooterMargin(double f) { - printerSettings.setFooterMargin(f); - } - - /** - * Sets whether the page should be centered horizontally. - */ - public void setHCenter(boolean center) { - hCenter.setHCenter(center); - } - - /** - * sets the Header margin in inches - * - * @param header margin - */ - public void setHeaderMargin(double h) { - printerSettings.setHeaderMargin(h); - } - - /** - * Sets the sheet's left print margin. - */ - public void setLeftMargin(double value) { - if (leftMargin == null) { - leftMargin = new LeftMargin(); - this.sheet.addMarginRecord(leftMargin); - } - leftMargin.setMargin(value); - } - - /** - * Sets the sheet's right print margin. - */ - public void setRightMargin(double value) { - if (rightMargin == null) { - rightMargin = new RightMargin(); - this.sheet.addMarginRecord(rightMargin); - } - rightMargin.setMargin(value); - } - - /** - * Sets the sheet's top print margin. - */ - public void setTopMargin(double value) { - if (topMargin == null) { - topMargin = new TopMargin(); - this.sheet.addMarginRecord(topMargin); - } - topMargin.setMargin(value); - } - - /** - * Sets the sheet's bottom print margin. - */ - public void setBottomMargin(double value) { - if (bottomMargin == null) { - bottomMargin = new BottomMargin(); - this.sheet.addMarginRecord(bottomMargin); - } - bottomMargin.setMargin(value); - } - - /** - * set the print orientation to landscape or portrait - * - * @param landscape - */ - public void setLandscape(boolean b) { - printerSettings.setNoOrient(false); // use the orientation setting - printerSettings.setLandscape(b); - } - - /** - * set the print orientation to left-to-right printing - * - * @param leftToRight - */ - public void setLeftToRight(boolean b) { - printerSettings.setLeftToRight(b); - } - - /** - * sets the output to black and white - * - * @param noColor - */ - public void setNoColor(boolean b) { - printerSettings.setNoColor(b); - } - - /** - * set the default page to start printing from - * - * @param p - */ - public void setPageStart(short p) { - printerSettings.setPageStart(p); - } - - /** - * sets the whether to print cell notes - * - * @param printNotes whether to print Notes. - */ - public void setPrintNotes(boolean b) { - printerSettings.setPrintNotes(b); - } - - /** - * Set the output printer resolution - * - * @param resolution The resolution to set in DPI. - */ - public void setResolution(int r) { - printerSettings.setResolution((short) r); - } - - /** - * scale the printer output in whole percentages - *

                  - * ie: 25 = 25% - * - * @param scale The scale to set. - */ - public void setScale(int scale) { - printerSettings.setScale((short) scale); - } - - /** - * @param usePage whether to use custom Page to start printing from. - */ - public void setUsePage(boolean usePage) { - printerSettings.setUsePage(usePage); - } - - /** - * @param verticalResolution The vertical Resolution in DPI - */ - public void setVerticalResolution(short verticalResolution) { - printerSettings.setVerticalResolution(verticalResolution); - } - - /** - * @param copies The number of copies to print - */ - public void setCopies(int copies) { - printerSettings.setCopies((short) copies); - } - - /** - * sets the paper size based on the paper size table - * - * @param the paper size index - */ - public void setPaperSize(int p) { - printerSettings.setPaperSize((short) p); - } - - /** - * get the draft quality setting - * - * @return draft quality - */ - public boolean getDraft() { - return printerSettings.getDraft(); - } - - /** - * get the number of pages to fit the printout to height - * - * @return fit to height - */ - public short getFitHeight() { - return printerSettings.getFitHeight(); - } - - /** - * get the number of pages to fit the printout to width - * - * @return fit to width - */ - public short getFitWidth() { - return printerSettings.getFitWidth(); - } - - /** - * Sets whether the sheet should be centered vertically. - */ - public void setVCenter(boolean center) { - vCenter.setVCenter(center); - } - - /** - * Sets whether to print the grid lines. - */ - public void setPrintGrid(boolean print) { - grid.setPrintGrid(print); - } - - /** - * Sets whether to print the row and column headers. - */ - public void setPrintRowColHeaders(boolean print) { - headers.setPrintHeaders(print); - } - - /** - * Gets the range specifying the titles printed on each page. - */ - public String getTitles() { - Name range = sheet.getName("Built-in: PRINT_TITLES"); - if (range == null) return null; - return range.getExpressionString(); - } - - /** - * Sets the range specifying the titles printed on each page. - * The reference for the row(s) to repeat e.g. $1:$1 for row 1 - * For Columns, type the reference to the column or columns that - * you want to set as a title e.g. $A:$B for columns A and B - */ - // note: MUST be in $ROW:$ROW or $COL:$COL format, for both - // can be $R:$R, $C:$C for both - public void setTitles(String range) { - Name name = sheet.getName("Built-in: PRINT_TITLES"); - if (name == null) try { - name = new Name(sheet.getWorkBook(), "Print_Titles"); - name.setBuiltIn((byte) 0x07); //do before setNewScope as it blows out itab - name.setNewScope(sheet.getSheetNum() + 1); - } catch (WorkSheetNotFoundException e) { - // This shouldn't be possible. - throw new Error("sheet not found re-scoping name"); - } - // pre-process range to ensure in proper format, ensure all absolute ($) refs + - // handle wholerow-wholecol refs + complex ranges - if (range == null) return; // TODO: Do what?? remove?? - String[] ranges = range.split(","); - range = ""; - for (int i = 0; i < ranges.length; i++) { - if (i > 0) // concatenate terms into one ptgmemfunc-style expression - range += ","; - String r = ""; - int[] rc = ExcelTools.getRangeCoords(ranges[i]); - if (rc[0] == rc[2]) // varies by column - r = "$" + ExcelTools.getAlphaVal(rc[1]) + ":$" + ExcelTools.getAlphaVal(rc[3]); - if (rc[1] == rc[3]) {// varies by row - r = "$" + rc[0] + ":$" + rc[2]; - } - range += sheet.getSheetName() + "!" + r; - } - name.setLocation(range); - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.kt b/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.kt new file mode 100644 index 0000000..a342bf1 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/PrinterSettingsHandle.kt @@ -0,0 +1,703 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.* + +/** + * The PrinterSettingsHandle gives you control over the printer settings for a Sheet such as whether to print in landscape or portrait mode. + *



                  + * The PrinterSettingsHandle provides fine-grained control over printing settings + * in Excel. + *



                  + * NOTE: you can only view the effects of these methods in an open Excel file when + * you use the "Print Setup" command. + *



                  + * OpenXLS does not currently support directly sending + * spreadsheet data to a printer. + * + * + *



                  ** + * Example Usage:** + *

                  + * ...
                  + * PrinterSettingsHandle printersetup = sheet.getPrinterSettings();
                  + * // Paper Size
                  + * printersetup.setPaperSize(PrinterSettingsHandle.PAPER_SIZE_LEDGER_17x11);
                  + * // Scaling
                  + * printersetup.setScale(125);
                  + * //	resolution
                  + * printersetup.setResolution(300);
                  + * ...
                  +
                  * + */ +/* order of records in Page Settings Block: + * HORIZONTALPAGEBREAKS + VERTICALPAGEBREAKS + HEADER + FOOTER + HCENTER + VCENTER + LEFTMARGIN + RIGHTMARGIN + TOPMARGIN + BOTTOMMARGIN + PLS + SETUP + BITMAP + * + */ +class PrinterSettingsHandle +/** + * default constructor + */ +(internal var sheet: Boundsheet) : Handle { + + private var printerSettings: Setup? = null + private var hCenter: HCenter? = null + private var vCenter: VCenter? = null + private var leftMargin: LeftMargin? = null + private var rightMargin: RightMargin? = null + private var topMargin: TopMargin? = null + private var bottomMargin: BottomMargin? = null + private var grid: PrintGrid? = null + private var headers: PrintRowCol? = null + private var wsBool: WsBool? = null + + // the following are unimplemented printer setting recs: + // HORIZONTALPAGEBREAKS; + // VERTICALPAGEBREAKS; + + + /** + * get the number of copies to print + * + * @return the number of copies + */ + val copies: Short + get() = printerSettings!!.copies + + + /** + * get the footer margin size in inches + * + * @return the footer margin in inches + */ + /** + * sets the footer margin in inches + * + * @param footer margin + */ + var footerMargin: Double + get() = printerSettings!!.footerMargin + set(f) { + printerSettings!!.footerMargin = f + } + + /** + * get the header margin size in inches + * + * @return the header margin in inches + */ + /** + * sets the Header margin in inches + * + * @param header margin + */ + var headerMargin: Double + get() = printerSettings!!.headerMargin + set(h) { + printerSettings!!.headerMargin = h + } + + /** + * get the landscape orientation + * + * @return whether the print orientation is set to landscape + */ + /** + * set the print orientation to landscape or portrait + * + * @param landscape + */ + // use the orientation setting + var landscape: Boolean + get() = printerSettings!!.landscape + set(b) { + printerSettings!!.noOrient = false + printerSettings!!.landscape = b + } + + /** + * get the left-to-right print orientation + * + * @return whether the print orientation is set to left-to-right + */ + /** + * set the print orientation to left-to-right printing + * + * @param leftToRight + */ + var leftToRight: Boolean + get() = printerSettings!!.leftToRight + set(b) { + printerSettings!!.leftToRight = b + } + + /** + * get whether printing is in black and white + * + * @return black and white + */ + /** + * sets the output to black and white + * + * @param noColor + */ + var noColor: Boolean + get() = printerSettings!!.noColor + set(b) { + printerSettings!!.noColor = b + } + + /** + * get whether to ignore orientation + * + * @return ignore orientation + */ + val noOrient: Boolean + get() = printerSettings!!.noOrient + + /** + * get whether printer data is missing + * + * @return whether the printer data is missing + */ + val noPrintData: Boolean + get() = printerSettings!!.noPrintData + + /** + * get the page to start printing from + * + * @return the page to start printing from + */ + /** + * set the default page to start printing from + * + * @param p + */ + var pageStart: Short + get() = printerSettings!!.pageStart + set(p) { + printerSettings!!.pageStart = p + } + + /** + * Returns the paper size setting for the printer setup based on the + * following table: + * + * @return paper size + */ + val paperSize: Short + get() = printerSettings!!.paperSize + + /** + * @return whether to print Notes. + */ + /** + * sets the whether to print cell notes + * + * @param printNotes whether to print Notes. + */ + var printNotes: Boolean + get() = printerSettings!!.printNotes + set(b) { + printerSettings!!.printNotes = b + } + + + /** + * get the print resolution + * + * @return the printer resolution in DPI + */ + val resolution: Short + get() = printerSettings!!.resolution + + /** + * get the scale of the printer output in whole percentages + * + * + * ie: 25 = 25% + * + * @return the scale of printer output + */ + val scale: Short + get() = printerSettings!!.scale + + /** + * use custom start page for auto numbering + * + * @return Returns whether to use a custom start page + */ + /** + * @param usePage whether to use custom Page to start printing from. + */ + var usePage: Boolean + get() = printerSettings!!.usePage + set(usePage) { + printerSettings!!.usePage = usePage + } + + /** + * get the vertical print resolution + * + * @return the vertical printer resolution in DPI + */ + /** + * @param verticalResolution The vertical Resolution in DPI + */ + var verticalResolution: Short + get() = printerSettings!!.verticalResolution + set(verticalResolution) { + printerSettings!!.verticalResolution = verticalResolution + } + + /** + * Whether the sheet should be centered horizontally. + */ + /** + * Sets whether the page should be centered horizontally. + */ + var isHCenter: Boolean + get() = hCenter!!.isHCenter + set(center) { + hCenter!!.isHCenter = center + } + + /** + * Whether the sheet should be centered vertically. + */ + /** + * Sets whether the sheet should be centered vertically. + */ + var isVCenter: Boolean + get() = vCenter!!.isVCenter + set(center) { + vCenter!!.isVCenter = center + } + + /** + * Whether the grid lines will be printed. + */ + val isPrintGridLines: Boolean + get() = grid!!.isPrintGrid + + /** + * Whether the row and column headers will be printed. + */ + /** + * Sets whether to print the row and column headers. + */ + var isPrintRowColHeaders: Boolean + get() = headers!!.isPrintHeaders + set(print) { + headers!!.isPrintHeaders = print + } + + /** + * Gets whether the sheet will be printed fit to some number of pages. + */ + /** + * Sets whether the sheet will be printed fit to some number of pages. + */ + var isFitToPage: Boolean + get() = wsBool!!.isFitToPage + set(value) { + wsBool!!.isFitToPage = value + } + + /** + * get the draft quality setting + * + * @return draft quality + */ + /** + * set for draft quality output + * + * @param whether to use draft quality + */ + var draft: Boolean + get() = printerSettings!!.draft + set(b) { + printerSettings!!.draft = b + } + + /** + * get the number of pages to fit the printout to height + * + * @return fit to height + */ + val fitHeight: Short + get() = printerSettings!!.fitHeight + + /** + * get the number of pages to fit the printout to width + * + * @return fit to width + */ + val fitWidth: Short + get() = printerSettings!!.fitWidth + + /** + * Gets the range specifying the titles printed on each page. + */ + /** + * Sets the range specifying the titles printed on each page. + * The reference for the row(s) to repeat e.g. $1:$1 for row 1 + * For Columns, type the reference to the column or columns that + * you want to set as a title e.g. $A:$B for columns A and B + */ + // note: MUST be in $ROW:$ROW or $COL:$COL format, for both + // can be $R:$R, $C:$C for both + //do before setNewScope as it blows out itab + // This shouldn't be possible. + // pre-process range to ensure in proper format, ensure all absolute ($) refs + + // handle wholerow-wholecol refs + complex ranges + // TODO: Do what?? remove?? + // concatenate terms into one ptgmemfunc-style expression + // varies by column + // varies by row + var titles: String? + get() { + val range = sheet.getName("Built-in: PRINT_TITLES") ?: return null + return range.expressionString + } + set(range) { + var range = range + var name = sheet.getName("Built-in: PRINT_TITLES") + if (name == null) + try { + name = Name(sheet.workBook, "Print_Titles") + name.setBuiltIn(0x07.toByte()) + name.setNewScope(sheet.sheetNum + 1) + } catch (e: WorkSheetNotFoundException) { + throw Error("sheet not found re-scoping name") + } + + if (range == null) return + val ranges = range.split(",".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + range = "" + for (i in ranges.indices) { + if (i > 0) + range += "," + var r = "" + val rc = ExcelTools.getRangeCoords(ranges[i]) + if (rc[0] == rc[2]) + r = "$" + ExcelTools.getAlphaVal(rc[1]) + ":$" + ExcelTools.getAlphaVal(rc[3]) + if (rc[1] == rc[3]) { + r = "$" + rc[0] + ":$" + rc[2] + } + range += sheet.sheetName + "!" + r + } + name.location = range + } + + init { + + val iter = sheet.printRecs.iterator() + while (iter.hasNext()) { + val record = iter.next() as BiffRec + + if (record is Setup) + printerSettings = record + else if (record is HCenter) + hCenter = record + else if (record is VCenter) + vCenter = record + else if (record is LeftMargin) + leftMargin = record // missing in default set of records + else if (record is RightMargin) + rightMargin = record // missing in default set of records + else if (record is TopMargin) + topMargin = record // missing in default set of records + else if (record is BottomMargin) + bottomMargin = record // missing in default set of records + else if (record is PrintGrid) + grid = record + else if (record is PrintRowCol) + headers = record + else if (record is WsBool) + wsBool = record + } + // Actually, the below comment is incorrect: do NOT do this unconditionally + //printerSettings.setNoPrintData(false); // there IS printer setup data + } + + /** + * Gets the left print margin. + */ + fun getLeftMargin(): Double { + if (leftMargin == null) { + leftMargin = LeftMargin() + this.sheet.addMarginRecord(leftMargin!!) + } + return leftMargin!!.margin + } + + /** + * Gets the right print margin. + */ + fun getRightMargin(): Double { + if (rightMargin == null) { + rightMargin = RightMargin() + this.sheet.addMarginRecord(rightMargin!!) + } + return rightMargin!!.margin + } + + /** + * Gets the top print margin. + */ + fun getTopMargin(): Double { + if (topMargin == null) { + topMargin = TopMargin() + this.sheet.addMarginRecord(topMargin!!) + } + return topMargin!!.margin + } + + /** + * Gets the bottom print margin. + */ + fun getBottomMargin(): Double { + if (bottomMargin == null) { + bottomMargin = BottomMargin() + this.sheet.addMarginRecord(bottomMargin!!) + } + return bottomMargin!!.margin + } + + /** + * Set the output to print onto this number of pages high + * + * + * ie: setFitHeight(10) will stretch the print out to fit 10 + * pages high + * + * @param number of pages to fit to height + */ + fun setFitHeight(numpages: Int) { + printerSettings!!.fitHeight = numpages.toShort() + } + + /** + * Set the output to print onto this number of pages wide + * + * + * ie: setFitWidth(10) will stretch the print out to fit 10 + * pages wide + * + * @param number of pages to fit to width + */ + fun setFitWidth(numpages: Int) { + printerSettings!!.fitWidth = numpages.toShort() + } + + /** + * Sets the sheet's left print margin. + */ + fun setLeftMargin(value: Double) { + if (leftMargin == null) { + leftMargin = LeftMargin() + this.sheet.addMarginRecord(leftMargin!!) + } + leftMargin!!.margin = value + } + + /** + * Sets the sheet's right print margin. + */ + fun setRightMargin(value: Double) { + if (rightMargin == null) { + rightMargin = RightMargin() + this.sheet.addMarginRecord(rightMargin!!) + } + rightMargin!!.margin = value + } + + /** + * Sets the sheet's top print margin. + */ + fun setTopMargin(value: Double) { + if (topMargin == null) { + topMargin = TopMargin() + this.sheet.addMarginRecord(topMargin!!) + } + topMargin!!.margin = value + } + + /** + * Sets the sheet's bottom print margin. + */ + fun setBottomMargin(value: Double) { + if (bottomMargin == null) { + bottomMargin = BottomMargin() + this.sheet.addMarginRecord(bottomMargin!!) + } + bottomMargin!!.margin = value + } + + /** + * Set the output printer resolution + * + * @param resolution The resolution to set in DPI. + */ + fun setResolution(r: Int) { + printerSettings!!.resolution = r.toShort() + } + + /** + * scale the printer output in whole percentages + * + * + * ie: 25 = 25% + * + * @param scale The scale to set. + */ + fun setScale(scale: Int) { + printerSettings!!.scale = scale.toShort() + } + + /** + * @param copies The number of copies to print + */ + fun setCopies(copies: Int) { + printerSettings!!.copies = copies.toShort() + } + + /** + * sets the paper size based on the paper size table + * + * @param the paper size index + */ + fun setPaperSize(p: Int) { + printerSettings!!.paperSize = p.toShort() + } + + /** + * Sets whether to print the grid lines. + */ + fun setPrintGrid(print: Boolean) { + grid!!.isPrintGrid = print + } + + companion object { + + + // paper size ints + val PAPER_SIZE_UNDEFINED = 0 + val PAPER_SIZE_LETTER_8_5x11 = 1 // Letter 81/2" x 11" + val PAPER_SIZE_LETTER_SMALL = 2 // Letter small 81/2" x 11" + val PAPER_SIZE_TABLOID_11x17 = 3 // Tabloid 11" x 17" + val PAPER_SIZE_LEDGER_17x11 = 4 // Ledger 17" x 11" + val PAPER_SIZE_LEGAL_8_5x14 = 5 // Legal 81/2" x 14" + val PAPER_SIZE_STATEMENT_5_5x8_5 = 6 // Statement 51/2" x 81/2" + val PAPER_SIZE_LETTER_EXTRA_9_5Ax12 = 50 // Letter Extra 91/2" x 12 + val PAPER_SIZE_LEGAL_EXTRA_9_5Ax15 = 51 // Legal Extra 91/2" x 15" + val PAPER_SIZE_TABLOID_EXTRA_1111_16Ax18 = 52 // Tabloid Extra 1111/16" x 18" + val PAPER_SIZE_A4_EXTRA_235MM_X_322MM = 53 // A4 Extra 235mm x 322mm + val PAPER_SIZE_LETTER_TRANSVERSE_8_5Ax11 = 54 // Letter Transverse 81/2" x 11" + val PAPER_SIZE_EXECUTIVE_7_QUARTER_X_10_5 = 7 // Executive 71/4" x 101/2" + val PAPER_SIZE_TRANSVERSE_210MM_X_297MM = 55 // A4 Transverse 210mm x 297mm + val PAPER_SIZE_A3_297MM_X_420MM = 8 // 8 A3 297mm x 420mm + val PAPER_SIZE_LETTER_EXTRA_TRANSV_9_5_X_12 = 56 // 56 Letter Extra Transv. 91/2" x 12" + val PAPER_SIZE_A4_210MM_X_297MM = 9 // A4 210mm x 297mm + val PAPER_SIZE_SUPER_A_A4_227MM_X_356MM = 57 // Super A/A4 227mm x 356mm + val PAPER_SIZE_A4_SMALL_210MM_X_297MM = 10 // A4 small 210mm x 297mm + val PAPER_SIZE_SUPER_B_A3_305MM_X_487MM = 58 // Super B/A3 305mm x 487mm + val PAPER_SIZE_A5_148MM_X_210MM = 11 // A5 148mm x 210mm + val PAPER_SIZE_LETTER_PLUS = 59 // Letter Plus + val PAPER_SIZE_2_X_1211_16 = 81 // 2" x 1211/16" + val PAPER_SIZE_B4_JIS_257MM_X_364MM = 12 // B4 (JIS) 257mm x 364mm + val PAPER_SIZE_A4_PLUS_210MM_X_330MM = 60 // A4 Plus 210mm x 330mm + val PAPER_SIZE_B5_JIS_182MM_X_257MM = 13 // B5 (JIS) 182mm x 257mm + val PAPER_SIZE_A5_TRANSVERSE_148MM_X_210MM = 61 // A5 Transverse 148mm x 210mm + val PAPER_SIZE_FOLIO_8_5_X_13 = 14 // Folio 81/2" x 13" + val PAPER_SIZE_B5_JIS_TRANSVERSE_182MM_X_257MM = 62 // B5 (JIS) Transverse 182mm x 257mm + val PAPER_SIZE_QUATRO_215MM_X_275MM = 15 // Quarto 215mm x 275mm + val PAPER_SIZE_A3_EXTRA_322MM_X_445MM = 63 // A3 Extra 322mm x 445mm + val PAPER_SIZE_10Ax14_10_X_14 = 16 // 10x14 10" x 14" + val PAPER_SIZE_A5_EXTRA_174MM_X_235 = 64 // A5 Extra 174mm x 235mm + val PAPER_SIZE_11Ax17_11_X_17 = 17 // 11x17 11" x 17" + val PAPER_SIZE_B5_ISO_EXTRA_201MM_X_276MM = 65 // B5 (ISO) Extra 201mm x 276mm + val PAPER_SIZE_NOTE_8_5_X_11 = 18 // Note 81/2" x 11" + val PAPER_SIZE_A2_420MM_X_594MM = 66 // A2 420mm x 594mm + val PAPER_SIZE_ENVELOPE_9_3_78_X_8_78 = 19 // Envelope #9 37/8" x 87/8" + val PAPER_SIZE_A3_TRANSVERSE_297MM_X_420MM = 67 // A3 Transverse 297mm x 420mm + val PAPER_SIZE_ENVELOPE_10_4_18_X_9_5 = 20 // Envelope #10 41/8" x 91/2" + val PAPER_SIZE_EXTRA_TRANSVERSE_322MM_X_445MM = 68 // A3 Extra Transverse 322mm x 445mm + val PAPER_SIZE_ENVELOPE_11_4_5_X_10_38 = 21 // Envelope #11 41/2" x 103/8" + val PAPER_SIZE_DBL_JAP_POSTCARD_200MM_X_148MM = 69 // Dbl. Japanese Postcard 200mm x 148mm + val PAPER_SIZE_ENVELOPE_12_4_34_X_11 = 22 // Envelope #12 43/4" x 11" + val PAPER_SIZE_A6_105MM_X_148MM = 70 // A6 105mm x 148mm + val PAPER_SIZE_ENVELOPE_14_5_X_11_5 = 23 // Envelope #14 5" x 111/2" + val PAPER_SIZE_C_17_X_22_72 = 24 // C 17" x 22" 72 + val PAPER_SIZE_D_22_X_34_73 = 25 // D 22" x 34" 73 + val PAPER_SIZE_E_34_X_44_74 = 26 // E 34" x 44" 74 + val PAPER_SIZE_DL_ENVELOPE_110MM_X_110MM_X_220MM = 27 // Envelope DL 110mm x 220mm + val PAPER_SIZE_LETTER_ROTATED_11_X_8_5 = 75 // Letter Rotated 11" x 81/2" + val PAPER_SIZE_ENVELOPE_C5_162MM_X_229M = 28 // Envelope C5 162mm x 229mm + val PAPER_SIZE_A3_ROTATED_420MM_X_297MM = 76 // A3 Rotated 420mm x 297mm + val PAPER_SIZE_ENVELOPE_C3_324MM_X_458MM = 29 // Envelope C3 324mm x 458mm + val PAPER_SIZE_A4_ROTATED_297MM_X_210MM = 77 // A4 Rotated 297mm x 210mm + val PAPER_SIZE_ENVELOPE_C4_229MM_X_324MM = 30 // Envelope C4 229mm x 324mm + val PAPER_SIZE_A5_ROTATED_210MM_X_148MM = 78 // A5 Rotated 210mm x 148mm + val PAPER_SIZE_ENVELOPE_C6_115MM_X_162MM = 31 // Envelope C6 114mm x 162mm + val PAPER_SIZE_ENVELOPE_C6_C5_114MM_X_229MM = 32 // Envelope C6/C5 114mm x 229mm + val PAPER_SIZE_B4_ISO_250MM_X_353MM = 33 // B4 (ISO) 250mm x 353mm + val PAPER_SIZE_B5_ISO_176MM_X_250MM = 34 // B5 (ISO) 176mm x 250mm + val PAPER_SIZE_DBL_JAP_POSTCARD_ROT_148MM_X_200MM = 82 // Dbl. Jap. Postcard Rot. 148mm x 200mm + val PAPER_SIZE_B6_ISO_125MM_X_176MM = 35 // B6 (ISO) 125mm x 176mm + val PAPER_SIZE_ENVELOPE_ITALY_10MM_X_230MM = 36 // Envelope Italy 110mm x 230mm 84 + val PAPER_SIZE_ENVELOPE_MONARCH_3_7_8_X_7_5 = 37 // Envelope Monarch 37/8" x 71/2" 85 + val PAPER_SIZE_6_3_4_ENVELOPE_3_5_8_X_6_5 = 38 // 63/4 Envelope 35/8" x 61/2" 86 + val PAPER_SIZE_US_STANDARD_FANFOLD_147_8_X_11 = 39 // US Standard Fanfold 147/8" x 11" 87 + val PAPER_SIZE_GERMAN_STD_FANFOLD_8_5_X_12 = 40 // German Std. Fanfold 81/2" x 12" + val PAPER_SIZE_GERMAN_LEGAL_FANFOLD_8_5_X_13 = 41 // German Legal Fanfold 81/2" x 13" + // public static final int PAPER_SIZE_B4_ISO_250MM_X_353MM = 42 ; // B4 (ISO) 250mm x 353mm + val PAPER_SIZE_JAP_POSTCARD_100M_X_148MM = 43 // Japanese Postcard 100mm x 148mm + val PAPER_SIZE_9_X_11 = 44 // 9x11 9" x 11" + val PAPER_SIZE_10_X_11 = 45 // 10x11 10" x 11" + val PAPER_SIZE_15_X_11 = 46 // 15x11 15" x 11" + val PAPER_SIZE_ENVELOPE_INVITE_220MM_X_220MM = 47 // Envelope Invite 220mm x 220mm + val PAPER_SIZE_B4_JIS_ROTATED_364MM_X_257MM = 79 // B4 (JIS) Rotated 364mm x 257mm + val PAPER_SIZE_B5_JIS_ROTATED_257MMX_X_182MM = 80 // B5 (JIS) Rotated 257mm x 182mm + val PAPER_SIZE_JAP_POSTCARD_ROT_148MM_X_100MM = 81 // Japanese Postcard Rot. 148mm x 100mm + val PAPER_SIZE_A6_ROTATED_148MM_X_105MM = 83 // A6 Rotated 148mm x 105mm + val PAPER_SIZE_B6_JIS_128MM_X_182MM = 88 // B6 (JIS) 128mm x 182mm + val PAPER_SIZE_B6_JIS_ROT_182MM_X_128MM = 89 // B6 (JIS) Rotated 182mm x 128mm + val PAPER_SIZE_12_X_11 = 90 // 12x11 12" x 11" + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/RowHandle.java b/src/main/java/io/starter/OpenXLS/RowHandle.java deleted file mode 100644 index 474c89b..0000000 --- a/src/main/java/io/starter/OpenXLS/RowHandle.java +++ /dev/null @@ -1,556 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.*; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.awt.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; - -import static io.starter.OpenXLS.JSONConstants.*; - - -/** - * The RowHandle provides access to a Worksheet Row and its Cells. - *
                  - * Use the RowHandle to work with individual Rows in an XLS file. - *
                  - * With a RowHandle you can: - *
                  - * get a handle to the Cells in a row
                  - * set the default formatting for a Row
                  - *
                  - * - * @see WorkBookHandle - * @see WorkSheetHandle - * @see FormulaHandle - */ -public class RowHandle { - // FYI: do not change lightly -- these match Excel 2007 almost exactly - public static int ROW_HEIGHT_DIVISOR = 17; - - public Row myRow; - private FormatHandle formatter; - private WorkBook wbh; - private WorkSheetHandle wsh; - - protected RowHandle(Row c, WorkSheetHandle ws) { - myRow = c; - wbh = ws.getWorkBook(); - wsh = ws; - } - - /** - * Return the row height of an existing row. - *

                  - * These values are returned in twips, 1/20th of a character. - * - * @return int Height of Row in twips - */ - public int getHeight() { - return myRow.getRowHeight(); - } - - /** - * returns the row height in Excel units, which depends upon the default font - *
                  in Arial 10 pt, standard row height is 12.75 points - * - * @return int row height in Excel units - */ - public int getHeightInChars() { - return myRow.getRowHeight() / 20; - } - - - /** - * Return the row height of an existing row. - *

                  - * These values are returned in twips, 1/20th of a character. - * - * @param sheet - * @param row - * @return - */ - public static int getHeight(Boundsheet sheet, int row) { - int h = 255; - try { - Row r = sheet.getRowByNumber(row); - if (r != null) - h = r.getRowHeight(); - } catch (Exception e) { // exception if no row defined - h = 255; // default - } - return h; - } - - /** - * sets the row height in Excel units. - * - * @param double i - row height value in Excel units - */ - public void setHeightInChars(int newHeight) { - this.setHeight(newHeight * 20); // 20090506 KSC: apparently it's in twips ?? 1/20 of a point - } - - /** - * sets the row height to auto fit - *
                  When the row height is set manually, autofit is automatically turned off - */ - public void setRowHeightAutoFit() { - // this.myRow.setUnsynched(false); // firstly, set so excel - Collection ct = myRow.getCells(); - Iterator it = ct.iterator(); - double h = 0; - int dpi = Toolkit.getDefaultToolkit().getScreenResolution(); // 96 is "small", 120 dpi is "lg" - // 1 point= 1/72 of an inch - // 1 twip= 1 twip= 1/20 of a point - // this should be a pretty good pixels/twips conversion factor. - double factorTwip = (double) dpi / 72 / 20; // .06 is "normal" - // factorZero is width of 0 char in default font. If assume Arial 10 pt, it is 6 + 1= 7 - double factorZero = 7; //java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f).charWidth('0') + 1; - - while (it.hasNext()) { - XLSRecord cellrec = (XLSRecord) it.next(); - try { - double newH = 255; // default row height - try { - Font ef = cellrec.getXfRec().getFont(); - int style = java.awt.Font.PLAIN; - if (ef.getBold()) - style |= java.awt.Font.BOLD; - if (ef.getItalic()) - style |= java.awt.Font.ITALIC; - java.awt.Font f = new java.awt.Font(ef.getFontName(), style, (int) ef.getFontHeightInPoints()); - String s = cellrec.getStringVal(); - if (!cellrec.getXfRec().getWrapText()) // normal case, no wrap - newH = StringTool.getApproximateHeight(f, s, Double.MAX_VALUE); - else { // wrap to column width - // convert column width to pixels - // factorZero is usually 7 // double factorZero= java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f).charWidth('0') + 1; - double cw = ColHandle.getWidth(this.wsh.getBoundsheet(), cellrec.getColNumber()) / 256.0; - newH = StringTool.getApproximateHeight(f, s, cw * factorZero); - } -// this doesn't work correctly newH/=factorTwip; // pixels * twips/pixels == twips) - newH *= 20; // this is better ... - } catch (Exception e) { - Logger.logErr("RowHandle.setRowHeightAutoFit: " + e.toString()); - } - - h = Math.max(h, newH); - } catch (Exception e) { - } - } - if (h > 0) - this.myRow.setRowHeight((int) Math.ceil(h)); - } - - /** - * Sets the row height in twips (1/20th of a point) - * - * @param newHeight - */ - public void setHeight(int newHeight) { - /* 20080604 KSC: if an image falls upon this column, - * adjust image width so that it does not change - */ - ArrayList iAdjust = new ArrayList(); - ImageHandle[] images = myRow.getSheet().getImages(); - if (images != null) { - // for each image that falls over this row, trap index + original width -- to be reset after setting row height - for (int z = 0; z < images.length; z++) { - ImageHandle ih = images[z]; - int r0 = ih.getRow(); - int r1 = ih.getRow1(); - int row = myRow.getRowNumber(); - if (row >= r0 && row <= r1) { - int h = ih.getHeight(); - iAdjust.add(new int[]{z, h}); - } - } - } - myRow.setRowHeight(newHeight); - for (int z = 0; z < iAdjust.size(); z++) { - ImageHandle ih = images[iAdjust.get(z)[0]]; - ih.setHeight(iAdjust.get(z)[1]); - } - - } - - /** - * Determines if the row passes through - * a vertical merge range - * - * @return - */ - public boolean containsVerticalMergeRange() { - CellHandle[] c = this.getCells(); - for (int i = 0; i < c.length; i++) { - if (c[i].getMergedCellRange() != null) { - CellRange cr = c[i].getMergedCellRange(); - try { - if (cr.getRows().length > 1) return true; - } catch (Exception e) { - } - } - } - return false; - } - - /** - * sets the default format id for the Row's Cells - * - * @param int Format Id for all Cells in Row - */ - public void setFormatId(int i) { - myRow.setIxfe(i); - } - - /** - * Gets the FormatHandle for this Row. - * - * @return FormatHandle - a Format object to apply to this Row - */ - public FormatHandle getFormatHandle() { - if (this.formatter == null) this.setFormatHandle(); - return this.formatter; - } - - - /** - * Set up the format handle for this row - */ - private void setFormatHandle() { - if (formatter != null) return; - formatter = new FormatHandle(wbh, this.getFormatId()); - formatter.setRowHandle(this); - } - - /** - * gets the current default row format id. May be overwritten by contained cells - * - * @return format id of row - */ - public int getFormatId() { - if (myRow.getExplicitFormatSet()) return myRow.getIxfe(); - return this.getWorkBook().getWorkBook().getDefaultIxfe(); - } - - /** - * Returns the array of Cells in this Row - * - * @param cache cellhandles flag - * @return Cell[] all Cells in this Row - */ - public CellHandle[] getCells(boolean cached) { - Collection ct = myRow.getCells(); - Iterator it = ct.iterator(); - CellHandle[] ch = new CellHandle[ct.size()]; - int t = 0; - Mulblank aMul = null; - short c = -1; - while (it.hasNext()) { - BiffRec rc = (BiffRec) it.next(); - try { // use cache of Cellhandles! - if (rc.getOpcode() != XLSConstants.MULBLANK) { - ch[t] = this.wsh.getCell(rc.getRowNumber(), rc.getColNumber(), cached); - } else { - // handle Mulblanks: ref a range of cells; to get correct cell address, - // traverse thru range and set cellhandle ref to correct column - if (rc == aMul) { - c++; - } else { - aMul = (Mulblank) rc; - c = (short) aMul.getColFirst(); - } - ch[t] = this.wsh.getCell(rc.getRowNumber(), c, cached); - } - } catch (CellNotFoundException cnfe) { - rc.setXFRecord(); - ch[t] = new CellHandle(rc, null); - ch[t].setWorkSheetHandle(null); //TODO: implement if causing grief -jm - if (rc.getOpcode() == XLSConstants.MULBLANK) { - // handle Mulblanks: ref a range of cells; to get correct cell address, - // traverse thru range and set cellhandle ref to correct column - if (rc == aMul) { - c++; - } else { - aMul = (Mulblank) rc; - c = (short) aMul.getColFirst(); - } - ch[t].setBlankRef(c); // for Mulblank use only -sets correct column reference for multiple blank cells ... - } - - } - t++; - } - return ch; - } - - /** - * Returns the array of Cells in this Row - * - * @return Cell[] all Cells in this Row - */ - public CellHandle[] getCells() { - return getCells(false); // don't use cache - } - - - /** - * Get the JSON object for this row. - * - * @return - */ - public String getJSON() { - return getJSON(255).toString(); - } - - public JSONObject getJSON(int maxcols) { - JSONObject theRange = new JSONObject(); - JSONArray cells = new JSONArray(); - try { - theRange.put(JSON_ROW, getRowNumber()); - - theRange.put(JSON_ROW_BORDER_TOP, getHasAnyThickTopBorder()); - theRange.put(JSON_ROW_BORDER_BOTTOM, getHasAnyBottomBorder()); - if (getFormatId() != getWorkBook().getWorkBook().getDefaultIxfe()) theRange.put("xf", getFormatId()); - theRange.put(JSON_HEIGHT, (getHeight() / ROW_HEIGHT_DIVISOR) + 5); // the default is TOO SMALL! - CellHandle[] chandles = getCells(false); - for (int i = 0; i < chandles.length; i++) { - CellHandle thisCell = chandles[i]; - if (!thisCell.isDefaultCell()) { - // do NOT use cached formula vals - if (thisCell.getCell().getOpcode() == XLSRecord.FORMULA) { - try { - FormulaHandle fh = thisCell.getFormulaHandle(); - fh.getFormulaRec().setCachedValue(null); - } catch (Exception ex) { - } - } - - if (thisCell.getColNum() >= maxcols) { - i = chandles.length; - } else if (thisCell.getCell().getOpcode() == XLSRecord.MULBLANK) { - Mulblank mb = (Mulblank) thisCell.getCell(); - ArrayList columns = mb.getColReferences(); - for (int x = 0; x < columns.size(); x++) { - thisCell.setBlankRef(columns.get(x)); - thisCell.getCell().setCol(columns.get(x).shortValue()); - JSONObject result = new JSONObject(); - thisCell.getCellAddress(); - Object v = ""; - try { - v = thisCell.getJSONObject(); - } catch (Exception exz) { - Logger.logErr("Error getting Row cell value " + thisCell.getCellAddress() + " JSON: " + exz); - v = "ERROR FETCHING VALUE for:" + thisCell.getCellAddress(); - } - if (v != null) { - result.put(JSON_CELL, v); - cells.put(result); - } - } - } else { - JSONObject result = new JSONObject(); - Object v = "ERROR FETCHING VALUE for:" + thisCell.getCellAddress(); - try { - v = thisCell.getJSONObject(); - } catch (Exception exz) { - Logger.logErr("Error getting Row cell value " + thisCell.getCellAddress() + " JSON: " + exz); - } - if (v != null) { - result.put(JSON_CELL, v); - cells.put(result); - } - } - } - } - theRange.put(JSON_CELLS, cells); - } catch (JSONException e) { - Logger.logErr("Error getting Row JSON: " + e); - } - return theRange; - } - - /** - * Returns the String representation of this Row - */ - public String toString() { - return myRow.toString(); - } - - /** - * Returns the row number of this RowHandle - */ - public int getRowNumber() { - return myRow.getRowNumber(); - } - - /** - * Set whether the row is collapsed. - * Will hide the current row, and all contiguous rows - * with the same outline level. - * - * @param b - */ - public void setCollapsed(boolean b) { - myRow.setCollapsed(b); - } - - /** - * Set whether the row is hidden - * - * @param b - */ - public void setHidden(boolean b) { - myRow.setHidden(b); - } - - /** - * Set the Outline level (depth) of the row - * - * @param x - */ - public void setOutlineLevel(int x) { - myRow.setOutlineLevel(x); - } - - /** - * Returns the Outline level (depth) of the row - * - * @return - */ - public int getOutlineLevel() { - return myRow.getOutlineLevel(); - } - - /** - * Returns whether the row is collapsed - * - * @return - */ - public boolean isCollapsed() { - return myRow.isCollapsed(); - } - - /** - * Returns whether the row is hidden - * - * @return - */ - public boolean isHidden() { - return myRow.isHidden(); - } - - - /** - * true if row height has been altered from default - * i.e. set manually - * - * @return - */ - public boolean isAlteredHeight() { - return myRow.isAlteredHeight(); - } - - public void setBackgroundColor(java.awt.Color colr) { - setFormatHandle(); - formatter.setCellBackgroundColor(colr); - } - - /** - * returns true if there is a Thick Top border set on the row - */ - public boolean getHasThickTopBorder() { - return myRow.getHasThickTopBorder(); - } - - /** - * returns true if there is a Thick Bottom border set on the row - */ - public boolean getHasThickBottomBorder() { - return myRow.getHasThickBottomBorder(); - } - - /** - * returns true if there is a thick top or thick or medium bottom border on previoous row - *

                  - * Not useful for public API - */ - public boolean getHasAnyThickTopBorder() { - return myRow.getHasAnyThickTopBorder(); - } - - /** - * Additional space below the row. This flag is set, if the - * lower border of at least one cell in this row or if the upper - * border of at least one cell in the row below is formatted with - * a medium or thick line style. Thin line styles are not taken - * into account. - *

                  - * Usage of this method is primarily for UI applications, and is not - * needed for standard OpenXLS functionality - */ - public boolean getHasAnyBottomBorder() { - return myRow.getHasAnyBottomBorder(); - } - - /** - * sets this row to have a thick top border - */ - public void setHasThickTopBorder(boolean hasBorder) { - myRow.setHasThickTopBorder(hasBorder); - } - - /** - * sets this row to have a thick bottom border - */ - public void setHasThickBottomBorder(boolean hasBorder) { - myRow.setHasThickBottomBorder(hasBorder); - } - - /** - * return the min/max columns defined for this row - * - * @return - */ - public int[] getColDimensions() { - return myRow.getColDimensions(); - } - - public WorkBook getWorkBook() { - return this.wbh; - } - - public WorkSheetHandle getWorkSheetHandle() { - return this.wsh; - } -} diff --git a/src/main/java/io/starter/OpenXLS/RowHandle.kt b/src/main/java/io/starter/OpenXLS/RowHandle.kt new file mode 100644 index 0000000..45f3100 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/RowHandle.kt @@ -0,0 +1,539 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.Font +import io.starter.formats.XLS.* +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +import java.awt.* +import java.util.ArrayList + +import io.starter.OpenXLS.JSONConstants.* + + +/** + * The RowHandle provides access to a Worksheet Row and its Cells. + *

                  + * Use the RowHandle to work with individual Rows in an XLS file. + *

                  + * With a RowHandle you can: + *

                  + * get a handle to the Cells in a row

                  + * set the default formatting for a Row

                  + *

                  + * + * @see WorkBookHandle + * + * @see WorkSheetHandle + * + * @see FormulaHandle + */ +class RowHandle(var myRow: Row, val workSheetHandle: WorkSheetHandle) { + private var formatter: FormatHandle? = null + val workBook: WorkBook? + + /** + * Return the row height of an existing row. + * + * + * These values are returned in twips, 1/20th of a character. + * + * @return int Height of Row in twips + */ + /** + * Sets the row height in twips (1/20th of a point) + * + * @param newHeight + */ + /* 20080604 KSC: if an image falls upon this column, + * adjust image width so that it does not change + */// for each image that falls over this row, trap index + original width -- to be reset after setting row height + var height: Int + get() = myRow.rowHeight + set(newHeight) { + val iAdjust = ArrayList() + val images = myRow.sheet!!.images + if (images != null) { + for (z in images.indices) { + val ih = images[z] + val r0 = ih.row + val r1 = ih.row1 + val row = myRow.rowNumber + if (row >= r0 && row <= r1) { + val h = ih.height.toInt() + iAdjust.add(intArrayOf(z, h)) + } + } + } + myRow.rowHeight = newHeight + for (z in iAdjust.indices) { + val ih = images!![iAdjust[z][0]] + ih.setHeight(iAdjust[z][1]) + } + + } + + /** + * returns the row height in Excel units, which depends upon the default font + *

                  in Arial 10 pt, standard row height is 12.75 points + * + * @return int row height in Excel units + */ + /** + * sets the row height in Excel units. + * + * @param double i - row height value in Excel units + */ + // 20090506 KSC: apparently it's in twips ?? 1/20 of a point + var heightInChars: Int + get() = myRow.rowHeight / 20 + set(newHeight) { + this.height = newHeight * 20 + } + + /** + * Gets the FormatHandle for this Row. + * + * @return FormatHandle - a Format object to apply to this Row + */ + val formatHandle: FormatHandle? + get() { + if (this.formatter == null) this.setFormatHandle() + return this.formatter + } + + /** + * gets the current default row format id. May be overwritten by contained cells + * + * @return format id of row + */ + /** + * sets the default format id for the Row's Cells + * + * @param int Format Id for all Cells in Row + */ + var formatId: Int + get() = if (myRow.explicitFormatSet) myRow.ixfe else this.workBook!!.workBook.defaultIxfe + set(i) { + myRow.ixfe = i + } + + /** + * Returns the array of Cells in this Row + * + * @return Cell[] all Cells in this Row + */ + // don't use cache + val cells: Array + get() = getCells(false) + + + /** + * Get the JSON object for this row. + * + * @return + */ + val json: String + get() = getJSON(255).toString() + + /** + * Returns the row number of this RowHandle + */ + val rowNumber: Int + get() = myRow.rowNumber + + /** + * Returns the Outline level (depth) of the row + * + * @return + */ + /** + * Set the Outline level (depth) of the row + * + * @param x + */ + var outlineLevel: Int + get() = myRow.outlineLevel + set(x) { + myRow.outlineLevel = x + } + + /** + * Returns whether the row is collapsed + * + * @return + */ + /** + * Set whether the row is collapsed. + * Will hide the current row, and all contiguous rows + * with the same outline level. + * + * @param b + */ + var isCollapsed: Boolean + get() = myRow.isCollapsed + set(b) { + myRow.isCollapsed = b + } + + /** + * Returns whether the row is hidden + * + * @return + */ + /** + * Set whether the row is hidden + * + * @param b + */ + var isHidden: Boolean + get() = myRow.isHidden + set(b) { + myRow.isHidden = b + } + + + /** + * true if row height has been altered from default + * i.e. set manually + * + * @return + */ + val isAlteredHeight: Boolean + get() = myRow.isAlteredHeight + + /** + * returns true if there is a Thick Top border set on the row + */ + /** + * sets this row to have a thick top border + */ + var hasThickTopBorder: Boolean + get() = myRow.hasThickTopBorder + set(hasBorder) { + myRow.hasThickTopBorder = hasBorder + } + + /** + * returns true if there is a Thick Bottom border set on the row + */ + /** + * sets this row to have a thick bottom border + */ + var hasThickBottomBorder: Boolean + get() = myRow.hasThickBottomBorder + set(hasBorder) { + myRow.hasThickBottomBorder = hasBorder + } + + /** + * returns true if there is a thick top or thick or medium bottom border on previoous row + * + * + * Not useful for public API + */ + val hasAnyThickTopBorder: Boolean + get() = myRow.hasAnyThickTopBorder + + /** + * Additional space below the row. This flag is set, if the + * lower border of at least one cell in this row or if the upper + * border of at least one cell in the row below is formatted with + * a medium or thick line style. Thin line styles are not taken + * into account. + * + * + * Usage of this method is primarily for UI applications, and is not + * needed for standard OpenXLS functionality + */ + val hasAnyBottomBorder: Boolean + get() = myRow.hasAnyBottomBorder + + /** + * return the min/max columns defined for this row + * + * @return + */ + val colDimensions: IntArray + get() = myRow.colDimensions + + init { + workBook = workSheetHandle.workBook + } + + /** + * sets the row height to auto fit + *

                  When the row height is set manually, autofit is automatically turned off + */ + fun setRowHeightAutoFit() { + // this.myRow.setUnsynched(false); // firstly, set so excel + val ct = myRow.cells + val it = ct.iterator() + var h = 0.0 + val dpi = Toolkit.getDefaultToolkit().screenResolution // 96 is "small", 120 dpi is "lg" + // 1 point= 1/72 of an inch + // 1 twip= 1 twip= 1/20 of a point + // this should be a pretty good pixels/twips conversion factor. + val factorTwip = dpi.toDouble() / 72.0 / 20.0 // .06 is "normal" + // factorZero is width of 0 char in default font. If assume Arial 10 pt, it is 6 + 1= 7 + val factorZero = 7.0 //java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f).charWidth('0') + 1; + + while (it.hasNext()) { + val cellrec = it.next() as XLSRecord + try { + var newH = 255.0 // default row height + try { + val ef = cellrec.xfRec!!.font + var style = java.awt.Font.PLAIN + if (ef!!.bold) + style = style or java.awt.Font.BOLD + if (ef.italic) + style = style or java.awt.Font.ITALIC + val f = java.awt.Font(ef.fontName, style, ef.fontHeightInPoints.toInt()) + val s = cellrec.stringVal + if (!cellrec.xfRec!!.wrapText) + // normal case, no wrap + newH = StringTool.getApproximateHeight(f, s, java.lang.Double.MAX_VALUE) + else { // wrap to column width + // convert column width to pixels + // factorZero is usually 7 // double factorZero= java.awt.Toolkit.getDefaultToolkit().getFontMetrics(f).charWidth('0') + 1; + val cw = ColHandle.getWidth(this.workSheetHandle.boundsheet, cellrec.colNumber.toInt()) / 256.0 + newH = StringTool.getApproximateHeight(f, s, cw * factorZero) + } + // this doesn't work correctly newH/=factorTwip; // pixels * twips/pixels == twips) + newH *= 20.0 // this is better ... + } catch (e: Exception) { + Logger.logErr("RowHandle.setRowHeightAutoFit: $e") + } + + h = Math.max(h, newH) + } catch (e: Exception) { + } + + } + if (h > 0) + this.myRow.rowHeight = Math.ceil(h).toInt() + } + + /** + * Determines if the row passes through + * a vertical merge range + * + * @return + */ + fun containsVerticalMergeRange(): Boolean { + val c = this.cells + for (i in c.indices) { + if (c[i].mergedCellRange != null) { + val cr = c[i].mergedCellRange + try { + if (cr!!.rows.size > 1) return true + } catch (e: Exception) { + } + + } + } + return false + } + + + /** + * Set up the format handle for this row + */ + private fun setFormatHandle() { + if (formatter != null) return + formatter = FormatHandle(workBook!!, this.formatId) + formatter!!.setRowHandle(this) + } + + /** + * Returns the array of Cells in this Row + * + * @param cache cellhandles flag + * @return Cell[] all Cells in this Row + */ + fun getCells(cached: Boolean): Array { + val ct = myRow.cells + val it = ct.iterator() + val ch = arrayOfNulls(ct.size) + var t = 0 + var aMul: Mulblank? = null + var c: Short = -1 + while (it.hasNext()) { + val rc = it.next() as BiffRec + try { // use cache of Cellhandles! + if (rc.opcode != XLSConstants.MULBLANK) { + ch[t] = this.workSheetHandle.getCell(rc.rowNumber, rc.colNumber.toInt(), cached) + } else { + // handle Mulblanks: ref a range of cells; to get correct cell address, + // traverse thru range and set cellhandle ref to correct column + if (rc === aMul) { + c++ + } else { + aMul = rc as Mulblank + c = aMul.colFirst.toShort() + } + ch[t] = this.workSheetHandle.getCell(rc.rowNumber, c.toInt(), cached) + } + } catch (cnfe: CellNotFoundException) { + rc.setXFRecord() + ch[t] = CellHandle(rc, null) + ch[t].workSheetHandle = null //TODO: implement if causing grief -jm + if (rc.opcode == XLSConstants.MULBLANK) { + // handle Mulblanks: ref a range of cells; to get correct cell address, + // traverse thru range and set cellhandle ref to correct column + if (rc === aMul) { + c++ + } else { + aMul = rc as Mulblank + c = aMul.colFirst.toShort() + } + ch[t].setBlankRef(c.toInt()) // for Mulblank use only -sets correct column reference for multiple blank cells ... + } + + } + + t++ + } + return ch + } + + fun getJSON(maxcols: Int): JSONObject { + val theRange = JSONObject() + val cells = JSONArray() + try { + theRange.put(JSON_ROW, rowNumber) + + theRange.put(JSON_ROW_BORDER_TOP, hasAnyThickTopBorder) + theRange.put(JSON_ROW_BORDER_BOTTOM, hasAnyBottomBorder) + if (formatId != workBook!!.workBook.defaultIxfe) theRange.put("xf", formatId) + theRange.put(JSON_HEIGHT, height / ROW_HEIGHT_DIVISOR + 5) // the default is TOO SMALL! + val chandles = getCells(false) + var i = 0 + while (i < chandles.size) { + val thisCell = chandles[i] + if (!thisCell.isDefaultCell) { + // do NOT use cached formula vals + if (thisCell.cell!!.opcode == XLSRecord.FORMULA) { + try { + val fh = thisCell.formulaHandle + fh.formulaRec!!.setCachedValue(null) + } catch (ex: Exception) { + } + + } + + if (thisCell.colNum >= maxcols) { + i = chandles.size + } else if (thisCell.cell!!.opcode == XLSRecord.MULBLANK) { + val mb = thisCell.cell as Mulblank + val columns = mb.colReferences + for (x in columns.indices) { + thisCell.setBlankRef(columns[x]) + thisCell.cell!!.setCol(columns[x].toShort()) + val result = JSONObject() + thisCell.cellAddress + var v: Any? = "" + try { + v = thisCell.jsonObject + } catch (exz: Exception) { + Logger.logErr("Error getting Row cell value " + thisCell.cellAddress + " JSON: " + exz) + v = "ERROR FETCHING VALUE for:" + thisCell.cellAddress + } + + if (v != null) { + result.put(JSON_CELL, v) + cells.put(result) + } + } + } else { + val result = JSONObject() + var v: Any? = "ERROR FETCHING VALUE for:" + thisCell.cellAddress + try { + v = thisCell.jsonObject + } catch (exz: Exception) { + Logger.logErr("Error getting Row cell value " + thisCell.cellAddress + " JSON: " + exz) + } + + if (v != null) { + result.put(JSON_CELL, v) + cells.put(result) + } + } + } + i++ + } + theRange.put(JSON_CELLS, cells) + } catch (e: JSONException) { + Logger.logErr("Error getting Row JSON: $e") + } + + return theRange + } + + /** + * Returns the String representation of this Row + */ + override fun toString(): String { + return myRow.toString() + } + + fun setBackgroundColor(colr: java.awt.Color) { + setFormatHandle() + formatter!!.setCellBackgroundColor(colr) + } + + companion object { + // FYI: do not change lightly -- these match Excel 2007 almost exactly + var ROW_HEIGHT_DIVISOR = 17 + + + /** + * Return the row height of an existing row. + * + * + * These values are returned in twips, 1/20th of a character. + * + * @param sheet + * @param row + * @return + */ + fun getHeight(sheet: Boundsheet, row: Int): Int { + var h = 255 + try { + val r = sheet.getRowByNumber(row) + if (r != null) + h = r.rowHeight + } catch (e: Exception) { // exception if no row defined + h = 255 // default + } + + return h + } + } +} diff --git a/src/main/java/io/starter/OpenXLS/ValidationHandle.java b/src/main/java/io/starter/OpenXLS/ValidationHandle.java deleted file mode 100644 index a164cce..0000000 --- a/src/main/java/io/starter/OpenXLS/ValidationHandle.java +++ /dev/null @@ -1,636 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.Dv; -import io.starter.formats.XLS.ValidationException; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; - -/** - * ValidationHandle allows for manipulation of the validation cells in Excel - *

                  - * Using the ValidationHandle, the affected range of validations can be - * modified, along with many of the error messages and actual validation upon - * the cells. Many of these calls are very self-explanatory and can be found in - * the api. - *

                  - * Some common use cases: - *
                    - *
                  1. Setting/changing the validation data type. - *
                    - * // Change validation to only allow a formula ValidationHandle validator =
                    - * theSheet.getValidationHandle("A1");
                    - * validator.setValidationType(ValidationHandle.VALUE_FORMULA); // can use any VALUE static byte here.
                    - * 
                    - *
                  2. - *
                  3. - * Setting/changing the validation condition. This requires setting the - * condition type, and a first and second condition. Also make the page error on - * invalid entry and set the error text. - *
                    - * // Validate cell is an int between the current values of cell D1 and cell D2.
                    - * ValidationHandle validator = theSheet.getValidationHandle("A1");
                    - * validator.setValidationType(ValidationHandle.VALUE_INTEGER);
                    - * validator.setTypeOperator(ValidationHandle.CONDITION_BETWEEN)// any CONDITION
                    - * static byte validator.setFirstCondition("D1"); // any valid excel formula,
                    - * omitting the '=' validator.setSecondCondition("D2");
                    - * validator.setErrorBoxText
                    - * ("The value is not between the values of D1 and D2");
                    - * validator.setShowErrorMessage(true);
                    - * 
                    - *
                  4. - * Change the range the validation is applied to, for instance if one is - * inseting a number of new rows and wants to grow the range A1:D1 to A1:Z1. - *
                    - * ValidationHandle validator = theSheet.getValidationHandle("A1");
                    - * validator.setRange("A1:Z1");
                    - * 
                    - *
                  5. - *
                  - * "http://starter.io">Starter Inc. - */ - -public class ValidationHandle implements Handle { - - private Dv myDv; - - // static shorts for setting validation type - public static final byte VALUE_ANY = 0x0; - public static final byte VALUE_INTEGER = 0x1; - public static final byte VALUE_DECIMAL = 0x2; - public static final byte VALUE_USER_DEFINED_LIST = 0x3; - public static final byte VALUE_DATE = 0x4; - public static final byte VALUE_TIME = 0x5; - public static final byte VALUE_TEXT_LENGTH = 0x6; - public static final byte VALUE_FORMULA = 0x7; - - // static shorts for setting action on error - public static byte ERROR_STOP = 0x0; - public static byte ERROR_WARN = 0x1; - public static byte ERROR_INFO = 0x2; - - // static shorts for setting conditions on validation - public static final byte CONDITION_BETWEEN = 0x0; - public static final byte CONDITION_NOT_BETWEEN = 0x1; - public static final byte CONDITION_EQUAL = 0x2; - public static final byte CONDITION_NOT_EQUAL = 0x3; - public static final byte CONDITION_GREATER_THAN = 0x4; - public static final byte CONDITION_LESS_THAN = 0x5; - public static final byte CONDITION_GREATER_OR_EQUAL = 0x6; - public static final byte CONDITION_LESS_OR_EQUAL = 0x7; - - // static shorts for setting IME modes - public static short IME_MODE_NO_CONTROL = 0x0; // No control for IME. - // (default) - public static short IME_MODE_ON = 0x1; // IME is on. - public static short IME_MODE_OFF = 0x2; // IME is off. - public static short IME_MODE_DISABLE = 0x3;// IME is disabled. - public static short IME_MODE_HIRAGANA = 0x4;// IME is in hiragana input - // mode. - public static short IME_MODE_KATAKANA = 0x5;// IME is in full-width katakana - // input mode. - public static short IME_MODE_KATALANA_HALF = 0x6;// IME is in half-width - // katakana input mode. - public static short IME_MODE_FULL_WIDTH_ALPHA = 0x7;// IME is in full-width - // alphanumeric input - // mode - public static short IME_MODE_HALF_WIDTH_ALPHA = 0x8;// IME is in half-width - // alphanumeric input - // mode. - public static short IME_MODE_FULL_WIDTH_HANKUL = 0x9;// IME is in full-width - // Hankul input mode - public static short IME_MODE_HALF_WIDTH_HANKUL = 0x10;// IME is in - // half-width Hankul - // input mode. - - public static String[] CONDITIONS = {"between", "notBetween", "equal", - "notEqual", "greaterThan", "lessThan", "greaterOrEqual", - "lessOrEqual"}; - - /** - * Get the byte representing the condition type string passed in. Options - * are' "between", "notBetween", "equal", "notEqual", "greaterThan", - * "lessThan", "greaterOrEqual", "lessOrEqual" - * - * @return - */ - public static byte getConditionNumber(String conditionType) { - for (int i = 0; i < CONDITIONS.length; i++) { - if (conditionType.equalsIgnoreCase(CONDITIONS[i])) - return (byte) i; - } - return -1; - } - - public static String[] VALUE_TYPE = {"any", "integer", "decimal", - "userDefinedList", "date", "time", "textLength", "formula"}; - - /** - * Get the byte representing the value type string passed in. Options are' - * "any", "integer", "decimal", "userDefinedList", "date", "time", - * "textLength", "formula" - * - * @return - */ - public static byte getValueNumber(String valueType) { - for (int i = 0; i < VALUE_TYPE.length; i++) { - if (valueType.equalsIgnoreCase(VALUE_TYPE[i])) - return (byte) i; - } - return -1; - } - - /** - * Create a new Validation for the input cell range. - * - * Validations are specific to worksheets. - * - * - * @param cellRange - * = cell or range of cells, example "A1", "A1:A10" - * - * protected ValidationHandle(String cellRange, WorkSheetHandle - * wsh) { - * - * } - */ - /** - * For internal use only. Creates a Validation Handle based of the Dv passed - * in. - * - * @param dv - */ - public ValidationHandle(Dv dv) { - myDv = dv; - } - - /** - * Return the range of data this ValidationHandle refers to as a string Will - * not contain worksheet identifier, as ValidationHandles are specific to a - * worksheet. If the Validation effects multiple ranges they are separated - * by a space - * - * @return ptgRef.toString() - */ - public String getRange() { - String[] s = myDv.getRanges(); - String out = ""; - for (int i = 0; i < s.length; i++) { - if (i > 0) - out += " "; - out += s[i]; - } - return out; - } - - /** - * Determine if the value passed in is valid for this validation - * - * @param value - * @return - */ - public boolean isValid(Object value) throws ValidationException { - return myDv.isValid(value); - } - - /** - * Determine if the value passed in is valid for this validation - * - * @param value - * @return - */ - public boolean isValid(Object value, boolean throwException) - throws RuntimeException { - if (throwException) { - try { - return myDv.isValid(value); - } catch (Exception e) { - try { - throw e; - } catch (Exception ex) { - Logger.logErr("Error getting isValid " + ex.toString()); - } - } - } - try { - return myDv.isValid(value); - } catch (Exception e) { - return false; - } - } - - /** - * Set the range this ValidationHandle refers to. Pass in a range string, - * sans worksheet. - *

                  - * This range will overwrite all other ranges this ValidationHandle refers - * to. - *

                  - * In order to handle multiple ranges, use the addRange(String range) method - * - * @param range = standard excel range without worksheet information ("A1" or - * "A1:A10") - */ - public void setRange(String range) { - myDv.setRange(range); - } - - /** - * Adds an additional range to the existing ranges in this validationhandle - * - * @param range - */ - public void addRange(String range) { - myDv.addRange(range); - } - - /** - * Get the text from the error box. - * - * @return - */ - public String getErrorBoxText() { - return myDv.getErrorBoxText(); - } - - /** - * Set the text for the error box - * - * @param textError - */ - public void setErrorBoxText(String textError) { - myDv.setErrorBoxText(textError); - } - - /** - * Return the text in the prompt box - * - * @return - */ - public String getPromptBoxText() { - return myDv.getPromptBoxText(); - } - - /** - * Set the text for the prompt box - * - * @param text - */ - public void setPromptBoxText(String text) { - myDv.setPromptBoxText(text); - } - - /** - * Set the title for the error box - * - * @param textError - */ - public void setErrorBoxTitle(String textError) { - myDv.setErrorBoxTitle(textError); - } - - /** - * Get the title from the error box - * - * @return - */ - public String getErrorBoxTitle() { - return myDv.getErrorBoxTitle(); - } - - /** - * Return the title in the prompt box - * - * @return - */ - public String getPromptBoxTitle() { - return myDv.getPromptBoxTitle(); - } - - /** - * Set the title for the prompt box - * - * @param text - */ - public void setPromptBoxTitle(String text) { - myDv.setPromptBoxTitle(text); - } - - /** - * Return a byte representing the error style for this ValidationHandle - *

                  - * These map to the static final ints ERROR_* in ValidationHandle - * - * @return - */ - public byte getErrorStyle() { - return myDv.getErrorStyle(); - } - - /** - * Set the error style for this ValidationHandle record - *

                  - * These map to the static final ints ERROR_* from ValidationHandle - * - * @return - */ - public void setErrorStyle(byte errstyle) { - myDv.setErrorStyle(errstyle); - } - - /** - * Get the IME mode for this validation - * - * @return - */ - public short getIMEMode() { - return myDv.getIMEMode(); - } - - /** - * set the IME mode for this validation - * - * @return - */ - public void setIMEMode(short mode) { - myDv.setIMEMode(mode); - } - - /** - * Allow blank cells in the validation area? - * - * @return - */ - public boolean isAllowBlank() { - return myDv.isAllowBlank(); - } - - /** - * Allow blank cells in the validation area? - * - * @return - */ - public void setAllowBlank(boolean allowBlank) { - myDv.setAllowBlank(allowBlank); - } - - /** - * Get the first condition of the validation as a string representation - * - * @return - */ - public String getFirstCondition() { - return myDv.getFirstCond(); - } - - /** - * Set the first condition of the validation - *

                  - * This value must conform to the Value Type of this validation or - * unexpected results may occur. For example, entering a string - * representation of a date here will not work if your validation is an - * integer... - *

                  - * A java.util.Date object can also be passed in. This value will be - * translated into an integer as excel stores dates. If you need to - * manipulate/retrieve this value later utilize the DateConverter tool to - * transform the value - *

                  - * String passed in should be a vaild XLS formula. Does not need to include - * the "=" - *

                  - * Types of conditions Integer values Decimal values User defined list Date - * Time Text length Formula - *

                  - * Be sure that your validation type (getValidationType()) matches the type - * of data. - * - * @param firstCond = the first condition for the validation - */ - public void setFirstCondition(Object firstCond) { - String setval = firstCond.toString(); - if (firstCond instanceof java.util.Date) { - double d = DateConverter.getXLSDateVal((java.util.Date) firstCond); - setval = d + ""; - } - myDv.setFirstCond(setval); - } - - /** - * Get the second condition of the validation as a string representation - * - * @return - */ - public String getSecondCondition() { - return myDv.getSecondCond(); - } - - /** - * Set the first condition of the validation utilizing a string. This value - * must conform to the Value Type of this validation or unexpected results - * may occur. For example, entering a string representation of a date here - * will not work if your validation is an integer... - *

                  - * String passed in should be a vaild XLS formula. Does not need to include - * the "=" - *

                  - * Types of conditions Integer values Decimal values User defined list Date - * Time Text length Formula - *

                  - * Be sure that your validation type (getValidationType()) matches the type - * of data. - * - * @return - */ - public void setSecondCondition(Object secondCond) { - String setval = secondCond.toString(); - if (secondCond instanceof java.util.Date) { - double d = DateConverter.getXLSDateVal((java.util.Date) secondCond); - setval = d + ""; - } - myDv.setSecondCond(setval); - } - - /** - * Show error box if invalid values entered? - * - * @return - */ - public boolean isShowErrorMsg() { - return myDv.isShowErrorMsg(); - } - - /** - * Set show error box if invalid values entered? - * - * @return - */ - public void setShowErrorMsg(boolean showErrMsg) { - myDv.setShowErrMsg(showErrMsg); - } - - /** - * Show prompt box if cell selected? - * - * @return - */ - public boolean getShowInputMsg() { - return myDv.getShowInputMsg(); - } - - /** - * Set show prompt box if cell selected? - * - * @param showInputMsg - */ - public void setShowInputMsg(boolean showInputMsg) { - myDv.setShowInputMsg(showInputMsg); - } - - /** - * In list type validity the string list is explicitly given in the formula - * - * @return boolean - */ - public boolean isStrLookup() { - return myDv.isStrLookup(); - } - - /** - * In list type validity the string list is explicitly given in the formula - * - * @param strLookup - */ - public void setStrLookup(boolean strLookup) { - myDv.setStrLookup(strLookup); - } - - /** - * Suppress the drop down arrow in list type validity - * - * @return boolean - */ - public boolean isSuppressCombo() { - return myDv.isSuppressCombo(); - } - - /** - * Suppress the drop down arrow in list type validity - */ - public void setSuppressCombo(boolean suppressCombo) { - myDv.setSuppressCombo(suppressCombo); - } - - /** - * Get the type operator of this validation as a byte. - *

                  - * These bytes map to the CONDITION_* static values in ValidationHandle - * - * @return - */ - public byte getTypeOperator() { - return myDv.getTypeOperator(); - } - - /** - * set the type operator of this validation as a byte. - *

                  - * These bytes map to the CONDITION_* static values in ValidationHandle - */ - public void setTypeOperator(byte typOperator) { - myDv.setTypeOperator(typOperator); - } - - /** - * Get the validation type of this ValidationHandle as a byte - *

                  - * These bytes map to the VALUE_* static values in ValidationHandle - * - * @return - */ - public byte getValidationType() { - return myDv.getValType(); - } - - /** - * Set the validation type of this ValidationHandle as a byte - *

                  - * These bytes map to the VALUE_* static values in ValidationHandle - */ - public void setValidationType(byte valtype) { - myDv.setValType(valtype); - } - - /** - * Determines if the ValidationHandle contains the cell address passed in - * - * @param range - * @return - */ - public boolean isInRange(String celladdy) { - return myDv.isInRange(celladdy); - } - - /** - * Return an xml representation of the ValidationHandle - * - * @return - */ - public String getXML() { - StringBuffer xml = new StringBuffer(); - xml.append(""); - if (this.getFirstCondition() != null) { - xml.append(""); - xml.append(StringTool.convertXMLChars(this.getFirstCondition())); - xml.append(""); - } - if (this.getSecondCondition() != null) { - xml.append(""); - xml.append(StringTool.convertXMLChars(this.getSecondCondition())); - xml.append(""); - } - xml.append(""); - return xml.toString(); - } - -} diff --git a/src/main/java/io/starter/OpenXLS/ValidationHandle.kt b/src/main/java/io/starter/OpenXLS/ValidationHandle.kt new file mode 100644 index 0000000..35e9ee1 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/ValidationHandle.kt @@ -0,0 +1,622 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.Dv +import io.starter.formats.XLS.ValidationException +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool + +/** + * ValidationHandle allows for manipulation of the validation cells in Excel + * + * + * Using the ValidationHandle, the affected range of validations can be + * modified, along with many of the error messages and actual validation upon + * the cells. Many of these calls are very self-explanatory and can be found in + * the api. + * + * Some common use cases: + * + * 1. Setting/changing the validation data type. + *

                  + * // Change validation to only allow a formula ValidationHandle validator =
                  + * theSheet.getValidationHandle("A1");
                  + * validator.setValidationType(ValidationHandle.VALUE_FORMULA); // can use any VALUE static byte here.
                  +
                  * + * + * 1. + * Setting/changing the validation condition. This requires setting the + * condition type, and a first and second condition. Also make the page error on + * invalid entry and set the error text. + *
                  + * // Validate cell is an int between the current values of cell D1 and cell D2.
                  + * ValidationHandle validator = theSheet.getValidationHandle("A1");
                  + * validator.setValidationType(ValidationHandle.VALUE_INTEGER);
                  + * validator.setTypeOperator(ValidationHandle.CONDITION_BETWEEN)// any CONDITION
                  + * static byte validator.setFirstCondition("D1"); // any valid excel formula,
                  + * omitting the '=' validator.setSecondCondition("D2");
                  + * validator.setErrorBoxText
                  + * ("The value is not between the values of D1 and D2");
                  + * validator.setShowErrorMessage(true);
                  +
                  * + * 1. + * Change the range the validation is applied to, for instance if one is + * inseting a number of new rows and wants to grow the range A1:D1 to A1:Z1. + *
                  + * ValidationHandle validator = theSheet.getValidationHandle("A1");
                  + * validator.setRange("A1:Z1");
                  +
                  * + * + * + * "http://starter.io">Starter Inc. + */ + +class ValidationHandle +/** + * Create a new Validation for the input cell range. + * + * Validations are specific to worksheets. + * + * + * @param cellRange + * = cell or range of cells, example "A1", "A1:A10" + * + * protected ValidationHandle(String cellRange, WorkSheetHandle + * wsh) { + * + * } + */ +/** + * For internal use only. Creates a Validation Handle based of the Dv passed + * in. + * + * @param dv + */ +(private val myDv: Dv) : Handle { + + /** + * Return the range of data this ValidationHandle refers to as a string Will + * not contain worksheet identifier, as ValidationHandles are specific to a + * worksheet. If the Validation effects multiple ranges they are separated + * by a space + * + * @return ptgRef.toString() + */ + /** + * Set the range this ValidationHandle refers to. Pass in a range string, + * sans worksheet. + * + * + * This range will overwrite all other ranges this ValidationHandle refers + * to. + * + * + * In order to handle multiple ranges, use the addRange(String range) method + * + * @param range = standard excel range without worksheet information ("A1" or + * "A1:A10") + */ + var range: String + get() { + val s = myDv.ranges + var out = "" + for (i in s.indices) { + if (i > 0) + out += " " + out += s[i] + } + return out + } + set(range) = myDv.setRange(range) + + /** + * Get the text from the error box. + * + * @return + */ + /** + * Set the text for the error box + * + * @param textError + */ + var errorBoxText: String + get() = myDv.errorBoxText + set(textError) { + myDv.errorBoxText = textError + } + + /** + * Return the text in the prompt box + * + * @return + */ + /** + * Set the text for the prompt box + * + * @param text + */ + var promptBoxText: String + get() = myDv.promptBoxText + set(text) { + myDv.promptBoxText = text + } + + /** + * Get the title from the error box + * + * @return + */ + /** + * Set the title for the error box + * + * @param textError + */ + var errorBoxTitle: String + get() = myDv.errorBoxTitle + set(textError) { + myDv.errorBoxTitle = textError + } + + /** + * Return the title in the prompt box + * + * @return + */ + /** + * Set the title for the prompt box + * + * @param text + */ + var promptBoxTitle: String + get() = myDv.promptBoxTitle + set(text) { + myDv.promptBoxTitle = text + } + + /** + * Return a byte representing the error style for this ValidationHandle + * + * + * These map to the static final ints ERROR_* in ValidationHandle + * + * @return + */ + /** + * Set the error style for this ValidationHandle record + * + * + * These map to the static final ints ERROR_* from ValidationHandle + * + * @return + */ + var errorStyle: Byte + get() = myDv.errorStyle + set(errstyle) { + myDv.errorStyle = errstyle + } + + /** + * Get the IME mode for this validation + * + * @return + */ + /** + * set the IME mode for this validation + * + * @return + */ + var imeMode: Short + get() = myDv.imeMode + set(mode) { + myDv.imeMode = mode + } + + /** + * Allow blank cells in the validation area? + * + * @return + */ + /** + * Allow blank cells in the validation area? + * + * @return + */ + var isAllowBlank: Boolean + get() = myDv.isAllowBlank + set(allowBlank) { + myDv.isAllowBlank = allowBlank + } + + /** + * Get the first condition of the validation as a string representation + * + * @return + */ + val firstCondition: String? + get() = myDv.firstCond + + /** + * Get the second condition of the validation as a string representation + * + * @return + */ + val secondCondition: String? + get() = myDv.secondCond + + /** + * Show error box if invalid values entered? + * + * @return + */ + /** + * Set show error box if invalid values entered? + * + * @return + */ + var isShowErrorMsg: Boolean + get() = myDv.isShowErrorMsg + set(showErrMsg) = myDv.setShowErrMsg(showErrMsg) + + /** + * Show prompt box if cell selected? + * + * @return + */ + /** + * Set show prompt box if cell selected? + * + * @param showInputMsg + */ + var showInputMsg: Boolean + get() = myDv.showInputMsg + set(showInputMsg) { + myDv.showInputMsg = showInputMsg + } + + /** + * In list type validity the string list is explicitly given in the formula + * + * @return boolean + */ + /** + * In list type validity the string list is explicitly given in the formula + * + * @param strLookup + */ + var isStrLookup: Boolean + get() = myDv.isStrLookup + set(strLookup) { + myDv.isStrLookup = strLookup + } + + /** + * Suppress the drop down arrow in list type validity + * + * @return boolean + */ + /** + * Suppress the drop down arrow in list type validity + */ + var isSuppressCombo: Boolean + get() = myDv.isSuppressCombo + set(suppressCombo) { + myDv.isSuppressCombo = suppressCombo + } + + /** + * Get the type operator of this validation as a byte. + * + * + * These bytes map to the CONDITION_* static values in ValidationHandle + * + * @return + */ + /** + * set the type operator of this validation as a byte. + * + * + * These bytes map to the CONDITION_* static values in ValidationHandle + */ + var typeOperator: Byte + get() = myDv.typeOperator + set(typOperator) { + myDv.typeOperator = typOperator + } + + /** + * Get the validation type of this ValidationHandle as a byte + * + * + * These bytes map to the VALUE_* static values in ValidationHandle + * + * @return + */ + /** + * Set the validation type of this ValidationHandle as a byte + * + * + * These bytes map to the VALUE_* static values in ValidationHandle + */ + var validationType: Byte + get() = myDv.valType + set(valtype) { + myDv.valType = valtype + } + + /** + * Return an xml representation of the ValidationHandle + * + * @return + */ + val xml: String + get() { + val xml = StringBuffer() + xml.append("") + if (this.firstCondition != null) { + xml.append("") + xml.append(StringTool.convertXMLChars(this.firstCondition)) + xml.append("") + } + if (this.secondCondition != null) { + xml.append("") + xml.append(StringTool.convertXMLChars(this.secondCondition)) + xml.append("") + } + xml.append("") + return xml.toString() + } + + /** + * Determine if the value passed in is valid for this validation + * + * @param value + * @return + */ + @Throws(ValidationException::class) + fun isValid(value: Any): Boolean { + return myDv.isValid(value) + } + + /** + * Determine if the value passed in is valid for this validation + * + * @param value + * @return + */ + @Throws(RuntimeException::class) + fun isValid(value: Any, throwException: Boolean): Boolean { + if (throwException) { + try { + return myDv.isValid(value) + } catch (e: Exception) { + try { + throw e + } catch (ex: Exception) { + Logger.logErr("Error getting isValid $ex") + } + + } + + } + try { + return myDv.isValid(value) + } catch (e: Exception) { + return false + } + + } + + /** + * Adds an additional range to the existing ranges in this validationhandle + * + * @param range + */ + fun addRange(range: String) { + myDv.addRange(range) + } + + /** + * Set the first condition of the validation + * + * + * This value must conform to the Value Type of this validation or + * unexpected results may occur. For example, entering a string + * representation of a date here will not work if your validation is an + * integer... + * + * + * A java.util.Date object can also be passed in. This value will be + * translated into an integer as excel stores dates. If you need to + * manipulate/retrieve this value later utilize the DateConverter tool to + * transform the value + * + * + * String passed in should be a vaild XLS formula. Does not need to include + * the "=" + * + * + * Types of conditions Integer values Decimal values User defined list Date + * Time Text length Formula + * + * + * Be sure that your validation type (getValidationType()) matches the type + * of data. + * + * @param firstCond = the first condition for the validation + */ + fun setFirstCondition(firstCond: Any) { + var setval = firstCond.toString() + if (firstCond is java.util.Date) { + val d = DateConverter.getXLSDateVal(firstCond) + setval = d.toString() + "" + } + myDv.firstCond = setval + } + + /** + * Set the first condition of the validation utilizing a string. This value + * must conform to the Value Type of this validation or unexpected results + * may occur. For example, entering a string representation of a date here + * will not work if your validation is an integer... + * + * + * String passed in should be a vaild XLS formula. Does not need to include + * the "=" + * + * + * Types of conditions Integer values Decimal values User defined list Date + * Time Text length Formula + * + * + * Be sure that your validation type (getValidationType()) matches the type + * of data. + * + * @return + */ + fun setSecondCondition(secondCond: Any) { + var setval = secondCond.toString() + if (secondCond is java.util.Date) { + val d = DateConverter.getXLSDateVal(secondCond) + setval = d.toString() + "" + } + myDv.secondCond = setval + } + + /** + * Determines if the ValidationHandle contains the cell address passed in + * + * @param range + * @return + */ + fun isInRange(celladdy: String): Boolean { + return myDv.isInRange(celladdy) + } + + companion object { + + // static shorts for setting validation type + val VALUE_ANY: Byte = 0x0 + val VALUE_INTEGER: Byte = 0x1 + val VALUE_DECIMAL: Byte = 0x2 + val VALUE_USER_DEFINED_LIST: Byte = 0x3 + val VALUE_DATE: Byte = 0x4 + val VALUE_TIME: Byte = 0x5 + val VALUE_TEXT_LENGTH: Byte = 0x6 + val VALUE_FORMULA: Byte = 0x7 + + // static shorts for setting action on error + var ERROR_STOP: Byte = 0x0 + var ERROR_WARN: Byte = 0x1 + var ERROR_INFO: Byte = 0x2 + + // static shorts for setting conditions on validation + val CONDITION_BETWEEN: Byte = 0x0 + val CONDITION_NOT_BETWEEN: Byte = 0x1 + val CONDITION_EQUAL: Byte = 0x2 + val CONDITION_NOT_EQUAL: Byte = 0x3 + val CONDITION_GREATER_THAN: Byte = 0x4 + val CONDITION_LESS_THAN: Byte = 0x5 + val CONDITION_GREATER_OR_EQUAL: Byte = 0x6 + val CONDITION_LESS_OR_EQUAL: Byte = 0x7 + + // static shorts for setting IME modes + var IME_MODE_NO_CONTROL: Short = 0x0 // No control for IME. + // (default) + var IME_MODE_ON: Short = 0x1 // IME is on. + var IME_MODE_OFF: Short = 0x2 // IME is off. + var IME_MODE_DISABLE: Short = 0x3// IME is disabled. + var IME_MODE_HIRAGANA: Short = 0x4// IME is in hiragana input + // mode. + var IME_MODE_KATAKANA: Short = 0x5// IME is in full-width katakana + // input mode. + var IME_MODE_KATALANA_HALF: Short = 0x6// IME is in half-width + // katakana input mode. + var IME_MODE_FULL_WIDTH_ALPHA: Short = 0x7// IME is in full-width + // alphanumeric input + // mode + var IME_MODE_HALF_WIDTH_ALPHA: Short = 0x8// IME is in half-width + // alphanumeric input + // mode. + var IME_MODE_FULL_WIDTH_HANKUL: Short = 0x9// IME is in full-width + // Hankul input mode + var IME_MODE_HALF_WIDTH_HANKUL: Short = 0x10// IME is in + // half-width Hankul + // input mode. + + var CONDITIONS = arrayOf("between", "notBetween", "equal", "notEqual", "greaterThan", "lessThan", "greaterOrEqual", "lessOrEqual") + + /** + * Get the byte representing the condition type string passed in. Options + * are' "between", "notBetween", "equal", "notEqual", "greaterThan", + * "lessThan", "greaterOrEqual", "lessOrEqual" + * + * @return + */ + fun getConditionNumber(conditionType: String): Byte { + for (i in CONDITIONS.indices) { + if (conditionType.equals(CONDITIONS[i], ignoreCase = true)) + return i.toByte() + } + return -1 + } + + var VALUE_TYPE = arrayOf("any", "integer", "decimal", "userDefinedList", "date", "time", "textLength", "formula") + + /** + * Get the byte representing the value type string passed in. Options are' + * "any", "integer", "decimal", "userDefinedList", "date", "time", + * "textLength", "formula" + * + * @return + */ + fun getValueNumber(valueType: String): Byte { + for (i in VALUE_TYPE.indices) { + if (valueType.equals(VALUE_TYPE[i], ignoreCase = true)) + return i.toByte() + } + return -1 + } + } + +} diff --git a/src/main/java/io/starter/OpenXLS/WorkBook.java b/src/main/java/io/starter/OpenXLS/WorkBook.kt similarity index 69% rename from src/main/java/io/starter/OpenXLS/WorkBook.java rename to src/main/java/io/starter/OpenXLS/WorkBook.kt index 53d6d29..8b2409d 100644 --- a/src/main/java/io/starter/OpenXLS/WorkBook.java +++ b/src/main/java/io/starter/OpenXLS/WorkBook.kt @@ -27,234 +27,246 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.OpenXLS; +package io.starter.OpenXLS -import io.starter.formats.XLS.*; +import io.starter.formats.XLS.* -import java.io.OutputStream; +import java.io.OutputStream /** * An interface representing an OpenXLS WorkBook. */ -public interface WorkBook extends Handle, Document { - - int CALCULATE_ALWAYS = XLSConstants.CALCULATE_ALWAYS; - int CALCULATE_EXPLICIT = XLSConstants.CALCULATE_EXPLICIT; - int CALCULATE_AUTO = XLSConstants.CALCULATE_AUTO; - String CALC_MODE_PROP = XLSConstants.CALC_MODE_PROP; - String REFTRACK_PROP = XLSConstants.REFTRACK_PROP; - String USETEMPFILE_PROP = XLSConstants.USETEMPFILE_PROP; - String DEFAULTENCODING = XLSConstants.DEFAULTENCODING; - String UNICODEENCODING = XLSConstants.UNICODEENCODING; - String VALIDATEWORKBOOK = XLSConstants.VALIDATEWORKBOOK; - - // public static final int FORMULA_CALC_AUTO = 0; - - int STRING_ENCODING_AUTO = XLSConstants.STRING_ENCODING_AUTO; - int STRING_ENCODING_UNICODE = XLSConstants.STRING_ENCODING_UNICODE; - int STRING_ENCODING_COMPRESSED = XLSConstants.STRING_ENCODING_COMPRESSED; - int ALLOWDUPES = XLSConstants.ALLOWDUPES; - int SHAREDUPES = XLSConstants.SHAREDUPES; +interface WorkBook : Handle, Document { /** * Explicit calcing of formulas * * @param mode */ - void setFormulaCalculationMode(int mode); - - int getFormulaCalculationMode(); + var formulaCalculationMode: Int /** - * get a non-Excel property + * The Session for the WorkBook instance * - * @return Returns the properties. + * @return public BookSession getSession(); */ - @Override - Object getProperty(String name); + + val colorTable: Array /** - * add non-Excel property - * - * @param properties The properties to set. + * Returns all of the Cells contained in the WorkBook */ - @Override - void addProperty(String name, Object val); + val cells: Array /** - * The Session for the WorkBook instance + * Returns an Array of all the FormatHandles present in the workbook * - * @return public BookSession getSession(); + * @return all existing FormatHandles in the workbook */ - - java.awt.Color[] getColorTable(); + val formats: Array /** - * Returns all of the Cells contained in the WorkBook + * Gets the date format used by this book. */ - CellHandle[] getCells(); + val dateFormat: DateConverter.DateFormat /** - * Returns the Cell at the specified Location + * get an array of handles to all PivotTables in the WorkBook * - * @param address - * @return + * @return PivotTable[] all of the WorkBooks PivotTables */ - CellHandle getCell(String address) throws CellNotFoundException, WorkSheetNotFoundException; + val pivotTables: Array /** - * Returns an Array of all the FormatHandles present in the workbook + * Returns all Chart Handles contained in the WorkBook * - * @return all existing FormatHandles in the workbook + * @return ChartHandle[] an array of all Charts in the WorkBook */ - FormatHandle[] getFormats(); + val charts: Array /** - * Gets the date format used by this book. + * Returns all Named Range Handles + * + * @return NameHandle[] all of the Named ranges in the WorkBook */ - DateConverter.DateFormat getDateFormat(); + val namedRanges: Array /** - * get a handle to a PivotTable in the WorkBook + * Returns the name of this WorkBook * - * @param String name of the PivotTable - * @return PivotTable the PivotTable + * @return String name of WorkBook */ - PivotTableHandle getPivotTable(String ptname) throws PivotTableNotFoundException; - /** - * get an array of handles to all PivotTables in the WorkBook + * Sets the internal name of this WorkBookHandle. * - * @return PivotTable[] all of the WorkBooks PivotTables + * + * Overrides the default for 'getName()' which returns + * the file name source of this WorkBook by default. + * + * @param WorkBook Name */ - PivotTableHandle[] getPivotTables() throws PivotTableNotFoundException; + override var name: String /** - * set the workbook to protected mode - *

                  - * Note: the password cannot be decrypted or changed - * in Excel -- protection can only be set/removed using - * OpenXLS + * Returns the number of Cells in this WorkBook * - * @param int Default Column width + * @return int number of Cells */ - void setProtected(boolean b); + val numCells: Int /** - * set Default row height - *

                  - * Note: only affects undefined Rows containing Cells + * Returns a byte Array containing the + * valid file containing this WorkBook + * and associated Storages (such as VB files + * and PivotTables.) * - * @param int Default Row Height + * + * This is the actual file data and that can be + * read from and written to FileOutputStreams and + * ServletOutputStreams. + * + * @return byte[] the XLS File's bytes */ - void setDefaultRowHeight(int t); + val bytes: ByteArray + + /** Initialize a Vector of the CellRanges existing in this WorkBook + * specifically the Ranges referenced in Formulas, Charts, and + * Named Ranges. + * + * This is necessary to allow for automatic updating of references + * when adding/removing/moving Cells within these ranges, as well + * as shifting references to Cells in Formulas when Formula records + * are moved. + * + */ + // public abstract void initCellRanges(boolean + // createblanks); /** - * set Default col width - *

                  - * Note: only affects undefined Columns containing Cells + * Returns an array of handles to all + * of the WorkSheets in the Workbook. * - * @param int Default Column width + * @return WorkSheetHandle[] Array of all WorkSheets in WorkBook */ - void setDefaultColWidth(int t); + val workSheets: Array /** - * Sets the internal name of this WorkBookHandle. - *

                  - * Overrides the default for 'getName()' which returns - * the file name source of this WorkBook by default. + * Returns a low-level WorkBook. * - * @param WorkBook Name + * + * NOTE: The WorkBook class is NOT a part of the + * published API. Any of the methods and/or + * variables on a WorkBook object are subject + * to change without notice in new versions of OpenXLS. */ - @Override - void setName(String nm); + val workBook: io.starter.formats.XLS.WorkBook /** - * Set the Debugging level. Higher values output more - * debugging info during execution. + * Returns a WorkBookHandle containing an empty + * version of this WorkBook. * - * @parameter int Debug level. higher=more verbose + * + * Use in conjunction with addSheetFromWorkBook() to create + * new output WorkBooks containing various sheets from a master + * template. + * + * + * ie: + * WorkBookHandle emptytemplate = this.getNoSheetWorkBook(); + * emptytemplate.addSheetFromWorkBook(this, "Sheet1", "TargetSheet"); + * + * @return WorkBookHandle - the empty WorkBookHandle duplicate + * @see addSheetFromWorkBook */ - @Override - void setDebugLevel(int l); + val noSheetWorkBook: WorkBookHandle /** - * Returns a Named Range Handle + * get a non-Excel property * - * @return NameHandle a Named range in the WorkBook + * @return Returns the properties. */ - NameHandle getNamedRange(String rangename) throws CellNotFoundException; + override fun getProperty(name: String): Any /** - * Returns a Chart Handle + * add non-Excel property * - * @return ChartHandle a Chart in the WorkBook + * @param properties The properties to set. */ - ChartHandle getChart(String chartname) throws ChartNotFoundException; + override fun addProperty(name: String, `val`: Any) /** - * Returns all Chart Handles contained in the WorkBook + * Returns the Cell at the specified Location * - * @return ChartHandle[] an array of all Charts in the WorkBook + * @param address + * @return */ - ChartHandle[] getCharts(); + @Throws(CellNotFoundException::class, WorkSheetNotFoundException::class) + fun getCell(address: String): CellHandle /** - * Returns all Named Range Handles + * get a handle to a PivotTable in the WorkBook * - * @return NameHandle[] all of the Named ranges in the WorkBook + * @param String name of the PivotTable + * @return PivotTable the PivotTable */ - NameHandle[] getNamedRanges(); + @Throws(PivotTableNotFoundException::class) + fun getPivotTable(ptname: String): PivotTableHandle /** - * Returns the name of this WorkBook + * set the workbook to protected mode * - * @return String name of WorkBook + * + * Note: the password cannot be decrypted or changed + * in Excel -- protection can only be set/removed using + * OpenXLS + * + * @param int Default Column width */ - @Override - String getName(); + fun setProtected(b: Boolean) /** - * Returns the number of Cells in this WorkBook + * set Default row height * - * @return int number of Cells + * + * Note: only affects undefined Rows containing Cells + * + * @param int Default Row Height */ - int getNumCells(); + fun setDefaultRowHeight(t: Int) /** - * Returns a byte Array containing the - * valid file containing this WorkBook - * and associated Storages (such as VB files - * and PivotTables.) - *

                  - * This is the actual file data and that can be - * read from and written to FileOutputStreams and - * ServletOutputStreams. + * set Default col width * - * @return byte[] the XLS File's bytes + * + * Note: only affects undefined Columns containing Cells + * + * @param int Default Column width */ - byte[] getBytes(); + fun setDefaultColWidth(t: Int) - /** Initialize a Vector of the CellRanges existing in this WorkBook - * specifically the Ranges referenced in Formulas, Charts, and - * Named Ranges. + /** + * Set the Debugging level. Higher values output more + * debugging info during execution. * - * This is necessary to allow for automatic updating of references - * when adding/removing/moving Cells within these ranges, as well - * as shifting references to Cells in Formulas when Formula records - * are moved. + * @parameter int Debug level. higher=more verbose + */ + override fun setDebugLevel(l: Int) + + /** + * Returns a Named Range Handle * + * @return NameHandle a Named range in the WorkBook */ - // public abstract void initCellRanges(boolean - // createblanks); + @Throws(CellNotFoundException::class) + fun getNamedRange(rangename: String): NameHandle /** - * Returns an array of handles to all - * of the WorkSheets in the Workbook. + * Returns a Chart Handle * - * @return WorkSheetHandle[] Array of all WorkSheets in WorkBook + * @return ChartHandle a Chart in the WorkBook */ - WorkSheetHandle[] getWorkSheets(); + @Throws(ChartNotFoundException::class) + fun getChart(chartname: String): ChartHandle /** * returns the handle to a WorkSheet by name. @@ -262,9 +274,10 @@ public interface WorkBook extends Handle, Document { * @param index of worksheet (ie: 0) * @return WorkSheetHandle the WorkSheet * @throws WorkSheetNotFoundException if the specified WorkSheet is - * not found in the WorkBook. + * not found in the WorkBook. */ - WorkSheetHandle getWorkSheet(int i) throws WorkSheetNotFoundException; + @Throws(WorkSheetNotFoundException::class) + fun getWorkSheet(i: Int): WorkSheetHandle /** * returns the handle to a WorkSheet by name. @@ -272,26 +285,19 @@ public interface WorkBook extends Handle, Document { * @param String name of worksheet (ie: "Sheet1") * @return WorkSheetHandle the WorkSheet * @throws WorkSheetNotFoundException if the specified WorkSheet is - * not found in the WorkBook. - */ - WorkSheetHandle getWorkSheet(String handstr) throws WorkSheetNotFoundException; - - /** - * Returns a low-level WorkBook. - *

                  - * NOTE: The WorkBook class is NOT a part of the - * published API. Any of the methods and/or - * variables on a WorkBook object are subject - * to change without notice in new versions of OpenXLS. + * not found in the WorkBook. */ - io.starter.formats.XLS.WorkBook getWorkBook(); + @Throws(WorkSheetNotFoundException::class) + fun getWorkSheet(handstr: String): WorkSheetHandle /** * Clears all values in a template WorkBook. - *

                  + * + * * Use this method to 'reset' the values of your * WorkBook in memory to defaults. - *

                  + * + * * For example, if you load a Servlet with a * single WorkBookHandle instance, then modify * values and stream to a Client system, yo @@ -299,30 +305,36 @@ public interface WorkBook extends Handle, Document { * is completed to remove the modified values * and set them back to a default. */ - @Override - void reset(); + override fun reset() /** * Set Encoding mode of new Strings added to file. - *

                  + * + * * OpenXLS has 3 modes for handling the internal encoding of * String data that is added to the file. - *

                  + * + * * OpenXLS can save space in the file if it knows that all characters * in your String data can be represented with a single byte (Compressed.) - *

                  + * + * * If your String contains characters which need 2 bytes to represent (such * as Eastern-language characters) then it needs to be stored in an uncompressed * Unicode format. - *

                  + * + * * OpenXLS can either automatically detect the mode for each String, or you * can set it explicitly. The auto mode is the most flexible but requires processing * overhead. - *

                  + * + * * Default mode is WorkBookHandle.STRING_ENCODING_AUTO. - *

                  + * + * * Valid Modes Are: - *

                  + * + * * WorkBookHandle.STRING_ENCODING_AUTO Use if you are adding mixed Unicode and non-unicode * Strings and can accept the performance hit * -slowest String adds @@ -336,40 +348,47 @@ public interface WorkBook extends Handle, Document { * * @param int String Encoding Mode */ - void setStringEncodingMode(int mode); + fun setStringEncodingMode(mode: Int) /** * Set Duplicate String Handling Mode. - *

                  + * + * * The Duplicate String Mode determines the behavior of * the String table when inserting new Strings. - *

                  + * + * * The String table shares a single entry for multiple * Cells containing the same string. When multiple Cells * have the same value, they share the same underlying string. - *

                  + * + * * Changing the value of any one of the Cells will change * the value for any Cells sharing that reference. - *

                  + * + * * For this reason, you need to determine * the handling of new strings added to the sheet that * are duplicates of strings already in the table. - *

                  + * + * * If you will be changing the values of these * new Cells, you will need to set the Duplicate * String Mode to ALLOWDUPES. If the string table * encounters a duplicate entry being added, it * will insert a duplicate that can then be subsequently * changed without affecting the other duplicate Cells. - *

                  + * + * * Valid Modes Are: - *

                  + * + * * WorkBookHandle.ALLOWDUPES - faster, smaller file sizes, dupe Cells share changes * WorkBookHandle.SHAREDUPES - slower inserts, changing Cells has no effect on dupe Cells * * @param int Duplicate String Handling Mode */ - void setDupeStringMode(int mode); + fun setDupeStringMode(mode: Int) /** * Copies an existing Chart to another WorkSheet @@ -377,7 +396,8 @@ public interface WorkBook extends Handle, Document { * @param chartname * @param sheetname */ - void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException; + @Throws(ChartNotFoundException::class, WorkSheetNotFoundException::class) + fun copyChartToSheet(chartname: String, sheetname: String) /** * Copies an existing Chart to another WorkSheet @@ -385,7 +405,8 @@ public interface WorkBook extends Handle, Document { * @param chart * @param sheet */ - void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) throws ChartNotFoundException, WorkSheetNotFoundException; + @Throws(ChartNotFoundException::class, WorkSheetNotFoundException::class) + fun copyChartToSheet(chart: ChartHandle, sheet: WorkSheetHandle) /** * Copy (duplicate) a worksheet in the workbook and add it to the end of the workbook with a new name @@ -394,12 +415,14 @@ public interface WorkBook extends Handle, Document { * @param String the Name of the new (destination) worksheet; * @return the new WorkSheetHandle */ - WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName) throws WorkSheetNotFoundException; + @Throws(WorkSheetNotFoundException::class) + fun copyWorkSheet(SourceSheetName: String, NewSheetName: String): WorkSheetHandle /** * Iterate through the formulas in this WorkBook and call the * calculate method on each. - *

                  + * + * * May be more expensive than calling update on individual FormulaHandles * depending on extent of data changes to your WorkBook, or calling update * on only the 'top-level' formula in a calculation. When a formula references @@ -409,32 +432,17 @@ public interface WorkBook extends Handle, Document { * * @throws FunctionNotSupportedException */ - void calculateFormulas() throws FunctionNotSupportedException; + @Throws(FunctionNotSupportedException::class) + fun calculateFormulas() /** * Removes all of the WorkSheets from this WorkBook. - *

                  + * + * * Bytes streamed from this WorkBook will create invalid * Spreadsheet files unless a WorkSheet(s) are added to it. */ - void removeAllWorkSheets(); - - /** - * Returns a WorkBookHandle containing an empty - * version of this WorkBook. - *

                  - * Use in conjunction with addSheetFromWorkBook() to create - * new output WorkBooks containing various sheets from a master - * template. - *

                  - * ie: - * WorkBookHandle emptytemplate = this.getNoSheetWorkBook(); - * emptytemplate.addSheetFromWorkBook(this, "Sheet1", "TargetSheet"); - * - * @return WorkBookHandle - the empty WorkBookHandle duplicate - * @see addSheetFromWorkBook - */ - WorkBookHandle getNoSheetWorkBook(); + fun removeAllWorkSheets() /** * Inserts a worksheet from a Source WorkBook. @@ -444,7 +452,8 @@ public interface WorkBook extends Handle, Document { * @param destSheetName - the name of the new sheet in this workbook * @throws WorkSheetNotFoundException */ - boolean addSheetFromWorkBook(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException; + @Throws(WorkSheetNotFoundException::class) + fun addSheetFromWorkBook(sourceBook: WorkBookHandle, sourceSheetName: String, destSheetName: String): Boolean /** * Inserts a new worksheet and places it at the end of the workbook @@ -452,7 +461,7 @@ public interface WorkBook extends Handle, Document { * @param WorkSheetHandle the source WorkSheetHandle; * @param String the Name of the new (destination) worksheet; */ - WorkSheetHandle addWorkSheet(WorkSheetHandle sht, String NewSheetName); + fun addWorkSheet(sht: WorkSheetHandle, NewSheetName: String): WorkSheetHandle /** * Creates a new worksheet and places it at the end of the workbook @@ -460,15 +469,35 @@ public interface WorkBook extends Handle, Document { * @param String the Name of the newly created worksheet * @return the new WorkSheetHandle */ - WorkSheetHandle createWorkSheet(String name); + fun createWorkSheet(name: String): WorkSheetHandle /** * Returns the name of this Sheet. * - * @see java.lang.Object#toString() + * @see java.lang.Object.toString */ - @Override - String toString(); + override fun toString(): String + + fun writeBytes(bbout: OutputStream): StringBuffer + + companion object { + + val CALCULATE_ALWAYS = XLSConstants.CALCULATE_ALWAYS + val CALCULATE_EXPLICIT = XLSConstants.CALCULATE_EXPLICIT + val CALCULATE_AUTO = XLSConstants.CALCULATE_AUTO + val CALC_MODE_PROP = XLSConstants.CALC_MODE_PROP + val REFTRACK_PROP = XLSConstants.REFTRACK_PROP + val USETEMPFILE_PROP = XLSConstants.USETEMPFILE_PROP + val DEFAULTENCODING = XLSConstants.DEFAULTENCODING + val UNICODEENCODING = XLSConstants.UNICODEENCODING + val VALIDATEWORKBOOK = XLSConstants.VALIDATEWORKBOOK + + // public static final int FORMULA_CALC_AUTO = 0; - StringBuffer writeBytes(OutputStream bbout); + val STRING_ENCODING_AUTO = XLSConstants.STRING_ENCODING_AUTO + val STRING_ENCODING_UNICODE = XLSConstants.STRING_ENCODING_UNICODE + val STRING_ENCODING_COMPRESSED = XLSConstants.STRING_ENCODING_COMPRESSED + val ALLOWDUPES = XLSConstants.ALLOWDUPES + val SHAREDUPES = XLSConstants.SHAREDUPES + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/WorkBookException.java b/src/main/java/io/starter/OpenXLS/WorkBookException.java deleted file mode 100644 index ca56690..0000000 --- a/src/main/java/io/starter/OpenXLS/WorkBookException.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - - -/** - * WorkBookInstantiationException is thrown when a workbook cannot be parsed for a particular reason. - *

                  - * Error codes can be retrieved with getErrorCode, which map to the static error ints - */ -public class WorkBookException extends io.starter.formats.XLS.WorkBookException { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5313787084750169461L; - public final static int DOUBLE_STREAM_FILE = 0; - public final static int NOT_BIFF8_FILE = 1; - public final static int LICENSING_FAILED = 2; - public final static int UNSPECIFIED_INIT_ERROR = 3; - public final static int RUNTIME_ERROR = 4; - public final static int SMALLBLOCK_FILE = 5; - public final static int WRITING_ERROR = 6; - public final static int DECRYPTION_ERROR = 7; - public final static int DECRYPTION_INCORRECT_PASSWORD = 8; - public static final int ENCRYPTION_ERROR = 9; - public final static int DECRYPTION_INCORRECT_FORMAT = 10; - public final static int ILLEGAL_INIT_ERROR = 11; - public final static int READ_ONLY_EXCEPTION = 12; - public final static int SHEETPROTECT_INCORRECT_PASSWORD = 13; - - public WorkBookException(String n, int x) { - super(n, x); - } - - public WorkBookException(String string, int x, - Exception e) { - super(string, x, e); - } - - -} diff --git a/src/main/java/io/starter/OpenXLS/WorkBookException.kt b/src/main/java/io/starter/OpenXLS/WorkBookException.kt new file mode 100644 index 0000000..d75d445 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/WorkBookException.kt @@ -0,0 +1,63 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + + +/** + * WorkBookInstantiationException is thrown when a workbook cannot be parsed for a particular reason. + * + * + * Error codes can be retrieved with getErrorCode, which map to the static error ints + */ +class WorkBookException : io.starter.formats.XLS.WorkBookException { + + constructor(n: String, x: Int) : super(n, x) {} + + constructor(string: String, x: Int, + e: Exception) : super(string, x, e) { + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -5313787084750169461L + val DOUBLE_STREAM_FILE = 0 + val NOT_BIFF8_FILE = 1 + val LICENSING_FAILED = 2 + val UNSPECIFIED_INIT_ERROR = 3 + val RUNTIME_ERROR = 4 + val SMALLBLOCK_FILE = 5 + val WRITING_ERROR = 6 + val DECRYPTION_ERROR = 7 + val DECRYPTION_INCORRECT_PASSWORD = 8 + val ENCRYPTION_ERROR = 9 + val DECRYPTION_INCORRECT_FORMAT = 10 + val ILLEGAL_INIT_ERROR = 11 + val READ_ONLY_EXCEPTION = 12 + val SHEETPROTECT_INCORRECT_PASSWORD = 13 + } + + +} diff --git a/src/main/java/io/starter/OpenXLS/WorkBookHandle.java b/src/main/java/io/starter/OpenXLS/WorkBookHandle.java deleted file mode 100644 index b215547..0000000 --- a/src/main/java/io/starter/OpenXLS/WorkBookHandle.java +++ /dev/null @@ -1,2188 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.LEO.BlockByteReader; -import io.starter.formats.LEO.InvalidFileException; -import io.starter.formats.LEO.LEOFile; -import io.starter.formats.XLS.*; -import io.starter.formats.XLS.charts.Chart; -import io.starter.formats.XLS.charts.OOXMLChart; -import io.starter.toolkit.*; - -import java.io.*; -import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.*; - -/** - * The WorkBookHandle provides a handle to the XLS file and includes convenience - * methods for working with the WorkSheets and Cell values within the XLS file. - * For example:
                  - * - * WorkBookHandle book = new WorkBookHandle("testxls.xls");
                  - * WorkSheetHandle sheet = book.getWorkSheet("Sheet1");
                  - * CellHandle cell = sheet.getCell("B22");
                  - *
                  - * - *

                  - * By default, OpenXLS will lock open WorkBook files. To close the file after - * parsing and work with a temporary file instead, use the following setting: - *
                  - * - * System.getProperties().put(WorkBookHandle.USETEMPFILE, "true"); - *
                  - * If you enable this mode you will need to periodically clean up the generated - * temporary files in your working directory. All OpenXLS temporary file names - * begin with "ExtenXLS_". - */ -public class WorkBookHandle extends DocumentHandle implements WorkBook, Handle { - /** - * A Writer to which a record dump should be written on input. This is used by - * the dumping code in WorkBookFactory. - */ - public static Writer dump_input = null; - - protected io.starter.formats.XLS.WorkBook mybook; - protected LEOFile myLEOFile; - protected WorkBookFactory myfactory = null; - - protected ProgressListener plist; - - /** - * Format constant for BIFF8 (Excel '97-2007). - */ - public static final int FORMAT_XLS = 100; - - /** - * Format constant for normal OOXML (Excel 2007). - */ - public static final int FORMAT_XLSX = 101; - - /** - * Format constant for macro-enabled OOXML (Excel 2007). - */ - public static final int FORMAT_XLSM = 102; - - /** - * Format constant for OOXML template (Excel 2007). - */ - public static final int FORMAT_XLTX = 103; - - /** - * Format constant for macro-enabled OOXML template (Excel 2007). - */ - public static final int FORMAT_XLTM = 104; - - private static byte[] protobook; - private static byte[] protochart; - private static byte[] protosheet; - public static java.text.SimpleDateFormat simpledateformat = new java.text.SimpleDateFormat(); // static - // to - // reuse - - /** - * How many recursion levels to allow formulas to be calculated before throwing - * a circular reference error - */ - public static int RECURSION_LEVELS_ALLOWED = 107; - - /** - * This is - **/ - public static String CONVERTMULBLANKS = "deprecated"; - - protected static byte[] getPrototypeBook() throws IOException { - if (protobook == null) - - protobook = ResourceLoader - .getBytesFromJar("/io/starter/OpenXLS/templates/prototysspe.ser"); - - return protobook; - } - - protected static byte[] getPrototypeSheet() { - if (protosheet == null) - try { - WorkBookHandle bookhandle = new WorkBookHandle(); - io.starter.formats.XLS.WorkBook book = bookhandle.getWorkBook(); - Boundsheet sheet = book.getWorkSheetByNumber(0); - protosheet = sheet.getSheetBytes(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - return protosheet; - } - - protected static byte[] getPrototypeChart() { - if (protochart == null) { - try { - byte[] bookbytes = ResourceLoader - .getBytesFromJar("/io/starter/OpenXLS/templates/prototypechart.ser"); - WorkBookHandle chartBook = new WorkBookHandle(bookbytes); - ChartHandle ch = chartBook.getCharts()[0]; - protochart = ch.getSerialBytes(); - return protochart; - } catch (IOException e) { - Logger.logErr("Unable to get default chart bytes"); - } - } - return protochart; - } - - /** - * Gets the internal Factory object. - *

                  - * WARNING: This method is not part of the public API. Its use - * is not supported and behavior is subject to change. - *

                  - */ - public WorkBookFactory getFactory() { - return myfactory; - } - - /** - * Gets the internal LEOFile object. - *

                  - * WARNING: This method is not part of the public API. Its use - * is not supported and behavior is subject to change. - *

                  - */ - public LEOFile getLEOFile() { - return myLEOFile; - } - - /** - * Searches all Cells in the workbook for the string occurrence and replaces - * with the replacement text. - * - * @return the number of replacements that were made - */ - public int searchAndReplace(String searchfor, String replacewith) { - CellHandle[] cx = getCells(); - int foundcount = 0; - for (int t = 0; t < cx.length; t++) { - if (!(cx[t].getCell() instanceof Formula)) { - // find the string - if (!cx[t].isNumber()) { - String v = cx[t].getStringVal(); - if (v.indexOf(searchfor) > -1) { - cx[t].setVal(StringTool - .replaceText(v, searchfor, replacewith)); - foundcount++; - } - } - } - } - return foundcount; - } - - /** - * Returns all strings that are in the SharedStringTable for this workbook. The - * SST contains all standard string records in cells, but may not include such - * things as strings that are contained within formulas. This is useful for such - * things as full text indexing of workbooks - * - * @return Strings in the workbook. - */ - public String[] getAllStrings() { - return this.mybook.getAllStrings(); - } - - /** - * Get either the default color table, or the color table from the custom - * palatte(if exists) from the WorkBook - * - * @return - */ - @Override - public java.awt.Color[] getColorTable() { - return this.getWorkBook().getColorTable(); - } - - /** - * Returns whether this book uses the 1904 date format. - * - * @deprecated Use {@link #getDateFormat()} instead. - */ - @Deprecated - public boolean is1904() { - return mybook.is1904(); - } - - /** - * Gets the date format used by this book. - */ - @Override - public DateConverter.DateFormat getDateFormat() { - return mybook.getDateFormat(); - } - - /** - * Returns the lowest version of Excel compatible with the input file. - * - * @return an Excel version string - */ - public String getXLSVersionString() { - return mybook.getXLSVersionString(); - } - - /** - * Return useful statistics about this workbook. - * - * @param use html line breaks - * @return a string contatining various statistics. - */ - public String getStats(boolean usehtml) { - return mybook.getStats(usehtml); - } - - /** - * Return useful statistics about this workbook. - * - * @return a string contatining various statistics. - */ - public String getStats() { - return mybook.getStats(); - } - - /** - * Returns the Cell at the specified Location - * - * @param address - * @return - */ - @Override - public CellHandle getCell(String address) throws CellNotFoundException, WorkSheetNotFoundException { - int shtpos = address.indexOf("!"); - if (shtpos < 0) - throw new CellNotFoundException(address - + " not found. You need to specify a location in the format: Sheet1!A1"); - String sheetstr = address.substring(0, shtpos); - WorkSheetHandle sht = this.getWorkSheet(sheetstr); - String celstr = address.substring(shtpos + 1); - return sht.getCell(celstr); - } - - /** - * Returns an Array of the CellRanges existing in this WorkBook specifically the - * Ranges referenced in Formulas, Charts, and Named Ranges. - *

                  - * This is necessary to allow for automatic updating of references when - * adding/removing/moving Cells within these ranges, as well as shifting - * references to Cells in Formulas when Formula records are moved. - * - * @return all existing Cell Range references used in Formulas, Charts, and - * Names - */ - public CellRange[] getCellRanges() { - return this.mybook.getRefTracker().getCellRanges(); - } - - /** - * get a handle to a PivotTable in the WorkBook - * - * @param String name of the PivotTable - * @return PivotTable the PivotTable - */ - @Override - public PivotTableHandle getPivotTable(String ptname) throws PivotTableNotFoundException { - Sxview st = mybook.getPivotTableView(ptname); - if (st == null) - throw new PivotTableNotFoundException(ptname); - return new PivotTableHandle(st, this); - } - - /** - * get an array of handles to all PivotTables in the WorkBook - * - * @return PivotTable[] all of the WorkBooks PivotTables - */ - @Override - public PivotTableHandle[] getPivotTables() throws PivotTableNotFoundException { - Sxview[] sxv = mybook.getAllPivotTableViews(); - if (sxv == null || sxv.length == 0) - throw new PivotTableNotFoundException( - "There are no PivotTables defined in: " + this.getName()); - PivotTableHandle[] pth = new PivotTableHandle[sxv.length]; - for (int t = 0; t < pth.length; t++) { - pth[t] = new PivotTableHandle(sxv[t], this); - } - return pth; - } - - /** - * Set the calculation mode for the workbook. - *

                  - * CALCULATE_AUTO is the default for new workbooks. Calling Cell.getVal() will - * calculate formulas if they exist within the cell. - *

                  - * CALCULATE_EXPLICIT will return present, cached value of the cell. Formula - * calculation will ONLY occur when explicitly called through the Formula - * Handle.calculate() method. - *

                  - * CALCULATE_ALWAYS will ignore the cache and force a recalc every time a cell - * value is requested. - *

                  - *

                  - * WorkBookHandle.CALCULATE_AUTO WorkBookHandle.CALCULATE_ALWAYS - * WorkBookHandle.CALCULATE_EXPLICIT - * - * @param CalcMode Calculation mode to use in workbook. - */ - @Override - public void setFormulaCalculationMode(int CalcMode) { - mybook.setCalcMode(CalcMode); - } - - /** - * Get the calculation mode for the workbook. - *

                  - * CALCULATE_ALWAYS is the default for new workbooks. Calling Cell.getVal() will - * calculate formulas if they exist within the cell. - *

                  - * CALCULATE_EXPLICIT will return present value of the cell. Formula calculation - * will only occur when explicitly called through the Formula Handle - *

                  - * WorkBookHandle.CALCULATE_ALWAYS -- recalc every time the cell value is - * requested (no cacheing) WorkBookHandle.CALCULATE_EXPLICIT -- recalc only when - * FormulaHandle.calculate() called WorkBookHandle.CALCULATE_AUTO -- only recac - * when changes - * - * @param CalcMode Calculation mode to use in workbook. - */ - @Override - public int getFormulaCalculationMode() { - return mybook.getCalcMode(); - } - - /** - * set the workbook to protected mode - *

                  - * Note: the password cannot be decrypted or changed in Excel -- protection can - * only be set/removed using OpenXLS - * - * @param boolean whether to protect the book - */ - @Override - public void setProtected(boolean protect) { - // TODO: Check that this behavior is correct - // This is what the old implementation did - - BookProtectionManager protector = mybook.getProtectionManager(); - - // Excel default... no kidding! - if (protect) - protector.setPassword("VelvetSweatshop"); - else - protector.setPassword(null); - - protector.setProtected(protect); - } - - /** - * set Default row height in twips (=1/20 of a point) - *

                  - * Note: only affects undefined Rows containing Cells - * - * @param int Default Row Height - */ - // should be a double as Excel units are 1/20 of what is - // stored in - // defaultrowheight - // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 - // (approx) - // should expect users to use Excel units and target method - // do the 20* - // conversion - @Override - public void setDefaultRowHeight(int t) { - mybook.setDefaultRowHeight(t); - } - - /** - * Set the default column width across all worksheets
                  - * This setting is a worksheet level setting, so will be applied to all existing - * worksheets. individual worksheets can also be set using - * WorkSheetHandle.setDefaultColWidth - *

                  - * This setting is roughly the width of the character '0' The default width of a - * column is 8. - */ - @Override - public void setDefaultColWidth(int t) { - mybook.setDefaultColWidth(t); - } - - /** - * Returns a Formula Handle - * - * @return FormulaHandle a formula handle in the WorkBook - */ - public FormulaHandle getFormulaHandle(String celladdress) throws FormulaNotFoundException { - Formula formula = mybook.getFormula(celladdress); - return new FormulaHandle(formula, this); - } - - /** - * Returns all ImageHandles in the workbook - * - * @return - */ - public ImageHandle[] getImages() { - List ret = new Vector(); - for (int t = 0; t < this.getNumWorkSheets(); t++) { - try { - ImageHandle[] r = this.getWorkSheet(t).getImages(); - for (int x = 0; x < r.length; x++) - ret.add(r[x]); - } catch (Exception ex) { - } - } - ImageHandle[] retx = new ImageHandle[ret.size()]; - ret.toArray(retx); - return retx; - } - - /** - * Returns an ImageHandle for manipulating images in the WorkBook - * - * @param imagename - * @return - */ - public ImageHandle getImage(String imagename) throws ImageNotFoundException { - for (int t = 0; t < this.getNumWorkSheets(); t++) { - try { - ImageHandle[] r = this.getWorkSheet(t).getImages(); - for (int x = 0; x < r.length; x++) - if (r[x].getName().equals(imagename)) - return r[x]; - } catch (Exception ex) { - } - } - throw new ImageNotFoundException( - "Image not found: " + imagename + " in " + this.toString()); - } - - /** - * Returns a Named Range Handle - * - * @return NameHandle a Named range in the WorkBook - */ - @Override - public NameHandle getNamedRange(String rangename) throws CellNotFoundException { - Name nand = mybook.getName(rangename.toUpperCase()); // case-insensitive - if (nand == null) - throw new CellNotFoundException(rangename); - return new NameHandle(nand, this); - } - - /** - * Returns a Named Range Handle if it exists in the specified scope. - *

                  - * This can be used to distinguish between multiple named ranges with the same - * name but differing scopes - * - * @return NameHandle a Named range in the WorkBook that exists in the scope - */ - public NameHandle getNamedRangeInScope(String rangename) throws CellNotFoundException { - Name nand = mybook.getScopedName(rangename); - if (nand == null) - throw new CellNotFoundException(rangename); - return new NameHandle(nand, this); - } - - /** - * Create a named range in the workbook - *

                  - * Note that the named range designation can conform to excel specs, that is, - * boolean values, references, or string variables can be set. Remember to - * utilize the sheet name when setting referential names. - *

                  - *

                  - * NameHandle nh = createNamedRange("cellRange", "Sheet1!A1:B3"); NameHandle nh - * = createNamedRange("trueRange", "=true"); - * - * @param name The name that should be used to reference this named range - * @param rangeDef Range of the cells for this named range, in excel syntax including - * sheet name, ie "Sheet1!A1:D1" - * @return NameHandle for modifying the named range - */ - public NameHandle createNamedRange(String name, String rangeDef) { - NameHandle nh = new NameHandle(name, rangeDef, this); - return nh; - } - - /** - * Returns a Chart Handle - * - * @return ChartHandle a Chart in the WorkBook - */ - // KSC: NOTE: this methodology needs work as a book may - // contain charts in - // different sheets containing the same name - // TODO: rethink - @Override - public ChartHandle getChart(String chartname) throws ChartNotFoundException { - return new ChartHandle(mybook.getChart(chartname), this); - } - - /** - * Returns all Chart Handles contained in the WorkBook - * - * @return ChartHandle[] an array of all Charts in the WorkBook - */ - @Override - public ChartHandle[] getCharts() { - AbstractList cv = mybook.getChartVect(); - ChartHandle[] cht = new ChartHandle[cv.size()]; - for (int x = 0; x < cv.size(); x++) { - cht[x] = new ChartHandle((Chart) cv.get(x), this); - } - return cht; - } - - /** - * retrieve a ChartHandle via id - * - * @param id - * @return - * @throws ChartNotFoundException - */ - public ChartHandle getChartById(int id) throws ChartNotFoundException { - AbstractList cv = mybook.getChartVect(); - Chart cht = null; - for (int x = 0; x < cv.size(); x++) { - cht = (Chart) cv.get(x); - if (cht.getId() == id) - return new ChartHandle(cht, this); - } - throw new ChartNotFoundException("Id " + id); - } - - /** - * Returns all Named Range Handles - * - * @return NameHandle[] all of the Named ranges in the WorkBook - */ - @Override - public NameHandle[] getNamedRanges() { - Name[] nand = mybook.getNames(); - NameHandle[] nands = new NameHandle[nand.length]; - for (int x = 0; x < nand.length; x++) { - nands[x] = new NameHandle(nand[x], this); - } - return nands; - } - - /** - * Returns all Named Range Handles scoped to WorkBook. - *

                  - * Note this will not include worksheet scoped named ranges - * - * @return NameHandle[] all of the Named ranges that are scoped to WorkBook - */ - public NameHandle[] getNamedRangesInScope() { - Name[] nand = mybook.getWorkbookScopedNames(); - NameHandle[] nands = new NameHandle[nand.length]; - for (int x = 0; x < nand.length; x++) { - nands[x] = new NameHandle(nand[x], this); - } - return nands; - } - - /** - * Returns the name of this WorkBook - * - * @return String name of WorkBook - */ - @Override - public String getName() { - if (name != null) - return name; - return "New Spreadsheet"; - } - - /** - * Returns an array containing all cells in the WorkBook - * - * @return CellHandle array of all book cells - */ - @Override - public CellHandle[] getCells() { - BiffRec[] allcz = this.mybook.getCells(); - CellHandle[] ret = new CellHandle[allcz.length]; - Mulblank aMul = null; - short c = -1; - for (int t = 0; t < ret.length; t++) { - ret[t] = new CellHandle(allcz[t], this); - if (allcz[t].getOpcode() == XLSConstants.MULBLANK) { - // handle Mulblanks: ref a range of cells; to get correct - // cell address, - // traverse thru range and set cellhandle ref to correct - // column - if (allcz[t] == aMul) { - c++; - } else { - aMul = (Mulblank) allcz[t]; - c = (short) aMul.getColFirst(); - } - ret[t].setBlankRef(c); // for Mulblank use only -sets correct - // column reference for multiple blank - // cells - // ... - } - } - return ret; - } - - /** - * Returns the number of Cells in this WorkBook - * - * @return int number of Cells - */ - @Override - public int getNumCells() { - return mybook.getNumCells(); - } - - /** - * Returns whether the sheet selection tabs should be shown. - */ - public boolean showSheetTabs() { - return mybook.showSheetTabs(); - } - - /** - * Sets whether the sheet selection tabs should be shown. - */ - public void setShowSheetTabs(boolean show) { - mybook.setShowSheetTabs(show); - } - - /** - * Gets the spreadsheet as a byte array in BIFF8 (Excel '97-2003) format. - * - * @deprecated Writing the spreadsheet to a byte array uses a great deal of - * memory and generally provides no benefit over streaming output. - * Use the {@link #write} family of methods instead. If you need a - * byte array use {@link ByteArrayOutputStream}. - */ - @Override - @Deprecated - public byte[] getBytes() { - try { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - writeBytes(bout); - - return bout.toByteArray(); - } catch (Exception e1) { - Logger.logErr("Getting Spreadsheet bytes failed.", e1); - return null; - } - } - - /** - * Writes the document to the given path. If the filename ends with ".xlsx" or - * ".xlsm", the workbook will be written as OOXML (XLSX). Otherwise it will be - * written as BIFF8 (XLS). For OOXML, if the file has a VBA project the file - * extension must be ".xlsm". It will be changed if necessary. - * - * @param path the path to which the document should be written - * @deprecated The filename-based format choosing is counter-intuitive and - * failure-prone. Use {@link #write(OutputStream, int)} instead. - */ - @Deprecated - public void write(String path) { - String ext = path.toLowerCase(); - write(path, ext.endsWith(".xlsx") || ext.endsWith(".xlsm")); - } - - /** - * Writes the document to the given file in either XLS or XLSX. For OOXML, if - * the file has a VBA project the file extension must be ".xlsm". It will be - * changed if necessary. - * - * @param path the path to which the document should be written - * @param ooxml If true, write as OOXML (XLSX). Otherwise, write as - * BIFF8 (XLS). - * @deprecated The boolean format parameter is not flexible enough to represent - * all supported formats. Use {@link #write(File, int)} instead. - */ - @Deprecated - public void write(String path, boolean ooxml) { - int format; - if (ooxml) { - if (this.getIsExcel2007()) - format = this.getFormat(path); - else - format = FORMAT_XLSX; - - if (!OOXMLAdapter.hasMacros(this)) - path = StringTool.replaceExtension(path, ".xlsx"); - else // it's a macro-enabled workbook - path = StringTool.replaceExtension(path, ".xlsm"); - } else - format = FORMAT_XLS; - - try { - this.write(new File(path), format); - } catch (Exception e) { - throw new WorkBookException("error writing workbook", - WorkBookException.WRITING_ERROR, e); - } - } - - /** - * Writes the document to the given stream in either XLS or XLSX format. - * - * @param dest the stream to which the document should be written - * @param ooxml If true, write as OOXML (XLSX). Otherwise, write as - * BIFF8 (XLS). - * @deprecated The boolean format parameter is not flexible enough to represent - * all supported formats. Use {@link #write(OutputStream, int)} - * instead. - */ - @Deprecated - public void write(OutputStream dest, boolean ooxml) { - int format; - if (ooxml) { - if (this.getIsExcel2007()) - format = this.getFormat(); - else - format = FORMAT_XLSX; - } else - format = FORMAT_XLS; - - try { - if (format > WorkBookHandle.FORMAT_XLS && this.file != null) { - OOXMLAdapter.refreshPassThroughFiles(this); - } - this.write(dest, format); - } catch (Exception e) { - throw new WorkBookException("error writing workbook", - WorkBookException.WRITING_ERROR, e); - } - } - - /** - * Gets the constant representing this document's native format. - */ - @Override - public int getFormat() { - String name = this.getFileName().toLowerCase(); - - if (this.getIsExcel2007()) { - if (OOXMLAdapter.hasMacros(this)) - return name.endsWith(".xltm") ? FORMAT_XLTM : FORMAT_XLSM; - else - return name.endsWith(".xltx") ? FORMAT_XLTX : FORMAT_XLSX; - } else - return FORMAT_XLS; - } - - /** - * Gets the constant representing this document's desired format - */ - public int getFormat(String path) { - if (path == null) - return getFormat(); - if (this.getIsExcel2007()) { - if (OOXMLAdapter.hasMacros(this)) - return path.endsWith(".xltm") ? FORMAT_XLTM : FORMAT_XLSM; - else - return path.endsWith(".xltx") ? FORMAT_XLTX : FORMAT_XLSX; - } else - return FORMAT_XLS; - } - - @Override - public String getFileExtension() { - switch (this.getFormat()) { - case FORMAT_XLSX: - return ".xlsx"; - case FORMAT_XLSM: - return ".xlsm"; - case FORMAT_XLTX: - return ".xltx"; - case FORMAT_XLTM: - return ".xltm"; - case FORMAT_XLS: - return ".xls"; - default: - return ""; - } - } - - /** - * Writes the document to the given stream in the requested format. - *

                  - * format choices: - *

                  - * WorkBookHandle.FORMAT_XLS for 2003 and previous versions
                  - * WorkBookHandle.FORMAT_XLSX for non-macro-enabled 2007 version
                  - * WorkBookHandle.FORMAT_XLSM for macro-enabled 2007 version
                  - * WorkBookHandle.FORMAT_XLTM for macro-enabled 2007 templates.
                  - * WorkBookHandle.FORMAT_XLTX for 2007 templates, - *

                  - * IMPORTANT NOTE: if the resulting filename contains the .XLSM extension - *
                  - * the WorkBook MUST be written in FORMAT_XLSM; otherwise open errors - * will occur - *

                  - * NOTE: If the format is FORMAT_XLSX and the filename contains macros - *
                  - * the file will be written as Macro-Enabled i.e. in FORMAT_XLSM. In these - * cases,
                  - * the filename must contain the .XLSM extension - * - * @param dest the stream to which the document should be written - * @param format the constant representing the desired output format - * @throws IllegalArgumentException if the given type code is invalid - * @throws IOException if an error occurs while writing to the stream - */ - @Override - public void write(OutputStream dest, int format) throws IOException { - if (format == FORMAT_NATIVE) - format = this.getFormat(); - - switch (format) { - case FORMAT_XLSX: - case FORMAT_XLSM: - case FORMAT_XLTX: - case FORMAT_XLTM: - try { - if (this.file != null) - OOXMLAdapter.refreshPassThroughFiles(this); - - OOXMLWriter adapter = new OOXMLWriter(); - adapter.setFormat(format); - adapter.getOOXML(this, dest); - } catch (IOException e) { - throw e; - } catch (Exception e) { - // TODO: OOXMLAdapter only throws IOException, change its - // throws - throw new WorkBookException("error writing workbook", - WorkBookException.WRITING_ERROR, e); - } - break; - - case FORMAT_XLS: - try { - this.mybook.getStreamer().writeOut(dest); - } catch (io.starter.formats.XLS.WorkBookException e) { - Throwable cause = e.getCause(); - if (cause instanceof IOException) - throw (IOException) cause; - throw e; - } - break; - - default: - throw new IllegalArgumentException("unknown output format"); - } - } - - /** - * Writes the document to the given stream in the requested OOXML format. - * - * @param dest the stream to which the document should be written - * @param format the constant representing the desired output format - * @throws IllegalArgumentException if the given type code is invalid - * @throws IOException if an error occurs while writing to the stream - * @deprecated This method is like {@link #write(OutputStream, int)} except it - * only supports OOXML formats. Use that instead. - */ - @Deprecated - public void writeXLSXBytes(OutputStream dest, int format) throws Exception { - this.write(dest, format); - } - - /** - * Writes the document to the given stream in the default OOXML format. - * - * @param dest the stream to which the document should be written - * @throws IOException if an error occurs while writing to the stream - * @deprecated Use {@link #write(OutputStream, int}) instead. - */ - @Deprecated - public void writeXLSXBytes(OutputStream dest) throws Exception { - this.write(dest, true); - } - - /** - * Returns whether the underlying spreadsheet is in Excel 2007 format by - * default. - *

                  - * Even if this method returns true, it is still possible to write out the file - * as a BIFF8 (Excel 97-2003) file, but unsupported features will be dropped, - * and some files could experience corruption. - * - * @return whether the underlying spreadsheet is Excel 2007 format - */ - public boolean getIsExcel2007() { - return this.mybook.getIsExcel2007(); - } - - /** - * Sets whether this Workbook is in Excel 2007 format. Excel 2007 format - * contains larger maximum column and row contraints, for example.
                  - * Even if the workbook is set to Excel 2007 format, it is still possible to - * write out the file as a BIFF8 (Excel 97-2003) file, but unsupported features - * will be dropped, and some files could experience corruption. - * - * @param isExcel2007 - */ - public void setIsExcel2007(boolean isExcel2007) { - this.mybook.setIsExcel2007(isExcel2007); - } - - /** - * Writes the document to the given stream in BIFF8 (XLS) format. - * - *

                  - * To output a debugging StringBuffer, you must first set the - * autolockdown setting:
                  - * - * props.put("io.starter.OpenXLS.autocreatelockdown","true"); - * - *

                  - * - * @param dest the stream to which the document should be written - * @return for debugging: a StringBuffer containing an output of the record - * bytes streamed - * @deprecated Use {@link #write(OutputStream, int)} instead. - */ - @Override - @Deprecated - public StringBuffer writeBytes(OutputStream dest) { - StringBuffer sb = this.mybook.getStreamer().writeOut(dest); - return sb; - } - - /** - * Default constructor creates a new, empty Spreadsheet with - *

                  - * 3 WorkSheets: "Sheet1","Sheet2",and "Sheet3". - */ - public WorkBookHandle() { - // Xf.DEFAULTIXFE= 15; // reset to default in cases of - // having previously read - // Excel2007 template which may have set defaultXF - // differently - this.initDefault(); - } - - /** - * Constructor creates a new, empty Spreadsheet with 3 worksheets: "Sheet1", - * "Sheet2" and "Sheet3"
                  - * This version allows flagging the workbook as Excel 2007 format.
                  - * Excel 2007 format contains larger maximum column and row contraints, for - * example.
                  - * Even if the workbook is set to Excel 2007 format, it is still possible to - * write out the file as a BIFF8 (Excel 97-2003) file, but unsupported features - * will be dropped, and some files could experience corruption. - * - * @param boolean Excel2007 - true if set to Excel 2007 version - */ - public WorkBookHandle(boolean Excel2007) { - this.initDefault(); - this.setIsExcel2007(Excel2007); - } - - /** - * another handle to the useful ability to load a book from the prorotype bytes - */ - protected void initDefault() { - try { - byte[] b = getPrototypeBook(); - if (b == null) { - throw new io.starter.formats.XLS.WorkBookException( - "Unable to load prototype workbook.", - WorkBookException.LICENSING_FAILED); - } - ByteBuffer bbf = ByteBuffer.wrap(b); - bbf.order(ByteOrder.LITTLE_ENDIAN); - myLEOFile = new LEOFile(bbf); - } catch (Exception e) { - throw new InvalidFileException( - "WorkBook could not be instantiated: " + e.toString()); - } - this.initFromLeoFile(myLEOFile); - } - - /** - * constructor which takes an InputStream containing the bytes of a valid XLS - * file. - * - * @param InputStream contains the valid BIFF8 bytes for reading - */ - public WorkBookHandle(InputStream inx) { - this.initFromStream(inx); - } - - /** - * Initialization of this workbook handle from a leoFile; - */ - private void initFromLeoFile(LEOFile leo) { - this.myLEOFile = leo; - try { - BlockByteReader bar = myLEOFile.getXLSBlockBytes(); - this.initBytes(bar); - this.setIsExcel2007(false); - myLEOFile.clearAfterInit(); - } catch (Exception e) { - if (e instanceof io.starter.formats.XLS.WorkBookException) - throw (io.starter.formats.XLS.WorkBookException) e; - throw new io.starter.formats.XLS.WorkBookException( - "ERROR: instantiating WorkBookHandle failed: " + e, - WorkBookException.UNSPECIFIED_INIT_ERROR, e); - } - } - - /** - * Initialize this workbook from a stream, unfortunately our byte backer - * requires a file, so create a tempfile and init from that - */ - protected void initFromStream(InputStream input) { - try { - File target = TempFileManager.createTempFile("WBP", ".tmp"); - - JFileWriter.writeToFile(input, target); - this.initFromFile(target.getAbsoluteFile()); - if (this.myLEOFile != null)// it would be if XLSX or XLSM ... - // 20090323 KSC - this.myLEOFile.closefb(); - // this.myLEOFile.close(); // close now flushes buffers + - // storages ... - input.close(); - - File fdel = new File(target.toString()); - if (!fdel.delete()) { - if (this.DEBUGLEVEL > DEBUG_LOW) - Logger.logWarn("Could not delete tempfile: " - + target.toString()); - } - } catch (IOException ex) { - Logger.logErr("Initializing WorkBookHandle failed.", ex); - } - } - - /** - * Create a new WorkBookHandle from the byte array passed in. Byte array passed - * in must contain a valid xls or xlsx workbook file - * - * @param byte[] byte array containing the valid XLS or XLSX file for reading - */ - public WorkBookHandle(byte[] barray) { - initializeFromByteArray(barray); - } - - /** - * Protected method that handles WorkBookHandle(byte[]) constructor - * - * @param barray - */ - protected void initializeFromByteArray(byte[] barray) { - // check first bytes to see if this is a zipfile (OOXML) - if ((char) barray[0] == 'P' && (char) barray[1] == 'K') { - try { - // added "." fixes Baxter Open Bug [BugTracker 2909] - File ftmp = TempFileManager.createTempFile("WBP", ".tmp"); - FileOutputStream fous = new FileOutputStream(ftmp); - fous.write(barray); - fous.flush(); - fous.close(); - this.initFromFile(ftmp); - return; - } catch (Exception e) { - Logger.logErr("Could not parse XLSX from bytes." - + e.toString()); - return; - } - } - - ByteBuffer bbf = ByteBuffer.wrap(barray); - bbf.order(ByteOrder.LITTLE_ENDIAN); - myLEOFile = new LEOFile(bbf); - if (myLEOFile.hasWorkBook()) { - try { - BlockByteReader bar = myLEOFile.getXLSBlockBytes(); - this.initBytes(bar); - } catch (Throwable e) { - if (e instanceof OutOfMemoryError) - throw (Error) e; - if (e instanceof WorkBookException) - throw (WorkBookException) e; - String errstr = "Instantiating WorkBookHandle failed: " - + e.toString(); - throw new io.starter.formats.XLS.WorkBookException(errstr, - WorkBookException.UNSPECIFIED_INIT_ERROR); - } - } else { - Logger.logWarn("Initializing WorkBookHandle failed: byte array does not contain a supported Excel WorkBook."); - throw new InvalidFileException( - "byte array does not contian a supported Excel WorkBook."); - } - } - - /** - * Fetches a workbook from a URL - *

                  - * If you need to authenticate your URL connection first then use the - * InputStream constructor - * - * @param urlx - * @return - * @throws Exception - */ - public WorkBookHandle(URL url) { - /* - * OK, both this method and the (inputstream) constructor - * set a temp file, is - * this not possible to do without hitting the disk? TODO: - * look into fix - */ - this(getFileFromURL(url)); - } - - /** - * Constructor which takes the XLS file name( - * - * @param String filePath the name of the XLS file to read - */ - public WorkBookHandle(String filePath) { - this(filePath, 0); - } - - /** - * constructor which takes the XLS file name and has an optional debug setting - * to assist with output. Setting this value will cause verbose logging and is - * discouraged unless required for support. - * - * @param String filePath the name of the XLS file to read - * @param Debug level - */ - public WorkBookHandle(String filePath, int debug) { - this.setDebugLevel(debug); - File f = new File(filePath); - this.initFromFile(f); - this.file = f; // XXX KSC: Save for potential re-input of pass-through - // ooxml files - - } - - /** - * constructor which takes the XLS file - * - * @param File the XLS file to read - * @param Debug level - */ - public WorkBookHandle(File fx) { - this.initFromFile(fx); - } - - protected void initWorkBookFactory() { - myfactory = new WorkBookFactory(); - } - - /** - * initialize from an XLSX/OOXML workbook. - */ - private boolean initXLSX(String fname) { - // do before parseNBind so can set myfactory & fname - // set state vars for this workbookhandle - this.initWorkBookFactory(); - - myfactory.setDebugLevel(this.DEBUGLEVEL); - myfactory.setFileName(this.name); - - if (plist != null) - myfactory.register(plist); // register progress notifier - try { - // iterate sheets,inputting cell values, named ranges and - // formula strings - OOXMLReader oe = new OOXMLReader(); - WorkBookHandle bk = new WorkBookHandle(); - bk.removeAllWorkSheets(); - myfactory.setDebugLevel(this.DEBUGLEVEL); - bk.DEBUGLEVEL = this.DEBUGLEVEL; - oe.parseNBind(bk, fname); - this.sheethandles = bk.sheethandles; - this.mybook = bk.mybook; - } catch (Exception e) { - throw new WorkBookException( - "WorkBookHandle OOXML Read failed: " + e.toString(), - WorkBookException.UNSPECIFIED_INIT_ERROR, e); - } - - mybook.setIsExcel2007(true); - return true; - } - - /** - * do all initialization with a filename - * - * @param fname - */ - protected void initFromFile(File fx) { - String fname = fx.getPath(); - String finch = ""; - - // handle csv import - FileReader fincheck; - try { - fincheck = new FileReader(fx); - if (fx.length() > 100) { - char[] cbuf = new char[100]; - fincheck.read(cbuf); - finch = new String(cbuf); - } - fincheck.close(); - - } catch (FileNotFoundException e) { - Logger.logErr("WorkBookHandle: Cannot open file " + fname + ": " - + e); - } catch (Exception e1) { - Logger.logErr("Invalid XLSX/OOXML File."); - } - this.name = fname; // 20081231 KSC: set here - if (finch.toUpperCase().startsWith("PK")) { // it's a zip file... give - // XLSX parsing a shot - if (this.file != null) - OOXMLAdapter.refreshPassThroughFiles(this); - if (initXLSX(fname)) - return; - } - try { - myLEOFile = new LEOFile(fx, this.DEBUGLEVEL); - } catch (InvalidFileException ifx) { - if ((finch.indexOf(",") > -1) && (finch.indexOf(",") > -1)) { - // init a blank workbook - this.initDefault(); - - // map CSV into workbook - try { - WorkSheetHandle sheet = getWorkSheet(0); - sheet.readCSV(new BufferedReader(new FileReader(fx))); - return; - } catch (Exception e) { - throw new WorkBookException( - "Error encountered importing CSV: " + e.toString(), - WorkBookException.ILLEGAL_INIT_ERROR); - } - } else { - throw ifx; - } - - } - if (myLEOFile.hasWorkBook()) { - this.initFromLeoFile(myLEOFile); - } else { - // total failure to load - Logger.logErr("Initializing WorkBookHandle failed: " + fname - + " does not contain a supported Excel WorkBook."); - throw new InvalidFileException( - fname + " does not contian a supported Excel WorkBook."); - } - } - - /** - * Constructor which takes a ProgressListener which monitors the progress of - * creating a new Excel file. - * - * @param ProgressListener object which is monitoring progress of WorkBook read - */ - public WorkBookHandle(ProgressListener pn) { - this.plist = pn; - try { - byte[] b = getPrototypeBook(); - ByteBuffer bbf = ByteBuffer.wrap(b); - bbf.order(ByteOrder.LITTLE_ENDIAN); - myLEOFile = new LEOFile(bbf); - } catch (Exception e) { - throw new InvalidFileException( - "WorkBook could not be instantiated: " + e.toString()); - } - this.initFromLeoFile(myLEOFile); - } - - /** - * Constructor which takes the XLS file name - *

                  - * and a ProgressListener which monitors the progress of reading the Excel file. - * - * @param String fname the name of the XLS file to read - * @param ProgressListener object which is monitoring progress of WorkBook read - */ - public WorkBookHandle(String fname, ProgressListener pn) { - this.plist = pn; - this.initFromFile(new File(fname)); - } - - /** - * For internal creation of a workbook handle from - * - * @param leo - */ - protected WorkBookHandle(LEOFile leo) { - this.initFromLeoFile(leo); - } - - /** - * init the new WorkBookHandle - */ - protected synchronized void initBytes(BlockByteReader blockByteReader) { - this.initWorkBookFactory(); - - if (plist != null) - myfactory.register(plist); // register progress notifier - myfactory.setDebugLevel(this.DEBUGLEVEL); - - mybook = (io.starter.formats.XLS.WorkBook) myfactory - .getWorkBook(blockByteReader, myLEOFile); - - if (dump_input != null) { - try { - dump_input.flush(); - dump_input.close(); - dump_input = null; - } catch (Exception e) { - } - } - this.postLoad(); - } - - /** - * Handles tasks that need to occur after workbook has been loaded - */ - void postLoad() { - initHlinks(); - initMerges(); - mybook.initializeNames(); // must initialize name expressions AFTER - // loading sheet records - mybook.mergeMSODrawingRecords(); - mybook.initializeIndirectFormulas(); - initPivotCache(); // if any - } - - void initMerges() { - AbstractList mergelookup = mybook.getMergecelllookup(); - for (int t = 0; t < mergelookup.size(); t++) { - Mergedcells mc = (Mergedcells) mergelookup.get(t); - mc.initCells(this); - } - } - - void initHlinks() { - AbstractList hlinklookup = mybook.getHlinklookup(); - for (int t = 0; t < hlinklookup.size(); t++) { - Hlink hl = (Hlink) hlinklookup.get(t); - hl.initCells(this); - } - } - - /** - * reads in the pivot cache storage and parses the pivot cache records
                  - * pivot cache(s) are used by pivot tables as data source storage - */ - void initPivotCache() { - if (myLEOFile.hasPivotCache()) { - PivotCache pc = new PivotCache(); // grab any pivot caches - try { - pc.init(myLEOFile.getDirectoryArray(), this); - mybook.setPivotCache(pc); - } catch (Exception e) { - - } - } - } - - /** - * Closes the WorkBook and releases resources. - */ - @Override - public void close() { - try { - if (myLEOFile != null) - myLEOFile.shutdown(); - myLEOFile = null; - } catch (Exception e) { - if (DEBUGLEVEL > 3) - Logger.logWarn("Closing Document: " + toString() + " failed: " - + e.toString()); - } - if (mybook != null) - mybook.close(); // clear out object refs to release memory - mybook = null; - myfactory = null; - name = null; - sheethandles = null; - // Runtime.getRuntime().gc(); - } - - @Override - protected void finalize() throws Throwable { - close(); - } - - @Override - public void reset() { - initFromFile(new File(myLEOFile.getFileName())); - } - - /** - * Returns an array of handles to all of the WorkSheets in the Workbook. - * - * @return WorkSheetHandle[] Array of all WorkSheets in WorkBook - */ - @Override - public WorkSheetHandle[] getWorkSheets() { - try { - if (myfactory != null) { - int numsheets = mybook.getNumWorkSheets(); - if (numsheets == 0) - throw new WorkSheetNotFoundException( - "WorkBook has No Sheets."); - WorkSheetHandle[] sheets = new WorkSheetHandle[numsheets]; - for (int i = 0; i < numsheets; i++) { - Boundsheet bs = mybook.getWorkSheetByNumber(i); - bs.setWorkBook(this.mybook); - sheets[i] = new WorkSheetHandle(bs, this); - } - return sheets; - } - return null; - } catch (WorkSheetNotFoundException a) { - Logger.logWarn("getWorkSheets() failed: " + a); - return null; - } - } - - /** - * returns the handle to a WorkSheet by number. - *

                  - * Sheet 0 is the first Sheet. - * - * @param index of worksheet (ie: 0) - * @return WorkSheetHandle the WorkSheet - * @throws WorkSheetNotFoundException if the specified WorkSheet is not found in the WorkBook. - */ - @Override - public WorkSheetHandle getWorkSheet(int sheetnum) throws WorkSheetNotFoundException { - Boundsheet st = mybook.getWorkSheetByNumber(sheetnum); - if (sheethandles.get(st.getSheetName()) != null) - return sheethandles.get(st.getSheetName()); - else { - WorkSheetHandle shth = new WorkSheetHandle(st, this); - sheethandles.put(st.getSheetName(), shth); - return shth; - } - } - - Hashtable sheethandles = new Hashtable(); - - /** - * returns the handle to a WorkSheet by name. - * - * @param String name of worksheet (ie: "Sheet1") - * @return WorkSheetHandle the WorkSheet - * @throws WorkSheetNotFoundException if the specified WorkSheet is not found in the WorkBook. - */ - @Override - public WorkSheetHandle getWorkSheet(String handstr) throws WorkSheetNotFoundException { - if (sheethandles.get(handstr) != null) { - if (mybook.getWorkSheetByName(handstr) != null) - return sheethandles.get(handstr); - else - throw new WorkSheetNotFoundException(handstr + " not found"); - } - if (myfactory != null) { - Boundsheet bs = mybook.getWorkSheetByName(handstr); - if (bs != null) { - bs.setWorkBook(this.mybook); - WorkSheetHandle ret = new WorkSheetHandle(bs, this); - sheethandles.put(handstr, ret); - return ret; - } else { - throw new WorkSheetNotFoundException(handstr); - } - } - throw new WorkSheetNotFoundException( - "Cannot find WorkSheet " + handstr); - } - - /** - * returns the active or selected worksheet tab - * - * @return WorkSheetHandle - * @throws WorkSheetNotFoundException - */ - public WorkSheetHandle getActiveSheet() throws WorkSheetNotFoundException { - return this.getWorkSheet(this.getWorkBook().getSelectedSheetNum()); - } - - /** - * Returns a low-level WorkBook. - *

                  - * NOTE: The WorkBook class is NOT a part of the published API. Any of the - * methods and/or variables on a WorkBook object are subject to change without - * notice in new versions of OpenXLS. - */ - @Override - public io.starter.formats.XLS.WorkBook getWorkBook() { - return this.mybook; - } - - /** - * Set Encoding mode of new Strings added to file. - *

                  - * OpenXLS has 3 modes for handling the internal encoding of String data that is - * added to the file. - *

                  - * OpenXLS can save space in the file if it knows that all characters in your - * String data can be represented with a single byte (Compressed.) - *

                  - * If your String contains characters which need 2 bytes to represent (such as - * Eastern-language characters) then it needs to be stored in an uncompressed - * Unicode format. - *

                  - * OpenXLS can either automatically detect the mode for each String, or you can - * set it explicitly. The auto mode is the most flexible but requires processing - * overhead. - *

                  - * Default mode is WorkBookHandle.STRING_ENCODING_AUTO. - *

                  - * Valid Modes Are: - *

                  - * WorkBookHandle.STRING_ENCODING_AUTO Use if you are adding mixed Unicode and - * non-unicode Strings and can accept the performance hit -slowest String adds - * -optimal file size for mixed Strings WorkBookHandle.STRING_ENCODING_UNICODE - * Use if all of your new Strings are Unicode - faster than AUTO -faster than - * AUTO -largest file size WorkBookHandle.STRING_ENCODING_COMPRESSED Use if all - * of your new Strings are non-Unicode and can have high-bytes compressed - * -faster than AUTO -smallest file size - * - * @param int String Encoding Mode - */ - @Override - public void setStringEncodingMode(int mode) { - mybook.setStringEncodingMode(mode); - } - - /** - * Set Duplicate String Handling Mode. - * - *

                  -     * The Duplicate String Mode determines the behavior of
                  -     * the String table when inserting new Strings.
                  -     *
                  -     * The String table shares a single entry for multiple
                  -     * Cells containing the same string.  When multiple Cells
                  -     * have the same value, they share the same underlying string.
                  -     *
                  -     * Changing the value of any one of the Cells will change
                  -     * the value for any Cells sharing that reference.
                  -     *
                  -     * For this reason, you need to determine
                  -     * the handling of new strings added to the sheet that
                  -     * are duplicates of strings already in the table.
                  -     *
                  -     * If you will be changing the values of these
                  -     * new Cells, you will need to set the Duplicate
                  -     * String Mode to ALLOWDUPES.  If the string table
                  -     * encounters a duplicate entry being added, it
                  -     * will insert a duplicate that can then be subsequently
                  -     * changed without affecting the other duplicate Cells.
                  -     *
                  -     * Valid Modes Are:
                  -     *
                  -     * WorkBookHandle.ALLOWDUPES - faster String inserts, larger file sizes,  changing Cells has no effect on dupe Cells
                  -     *
                  -     * WorkBookHandle.SHAREDUPES - slower inserts, dupe smaller file sizes, Cells share changes
                  -     * 
                  - * - * @param int Duplicate String Handling Mode - */ - @Override - public void setDupeStringMode(int mode) { - mybook.setDupeStringMode(mode); - } - - /** - * Copies an existing Chart to another WorkSheet - * - * @param chartname - * @param sheetname - */ - @Override - public void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException { - mybook.copyChartToSheet(chartname, sheetname); - } - - /** - * Copies an existing Chart to another WorkSheet - * - * @param chart - * @param sheet - */ - @Override - public void copyChartToSheet(ChartHandle chart, WorkSheetHandle sheet) throws ChartNotFoundException, WorkSheetNotFoundException { - mybook.copyChartToSheet(chart.getTitle(), sheet.getSheetName()); - } - - /** - * Copy (duplicate) a worksheet in the workbook and add it to the end of the - * workbook with a new name - * - * @param String the Name of the source worksheet; - * @param String the Name of the new (destination) worksheet; - * @return the new WorkSheetHandle - */ - @Override - public WorkSheetHandle copyWorkSheet(String SourceSheetName, String NewSheetName) throws WorkSheetNotFoundException { - try { - mybook.copyWorkSheet(SourceSheetName, NewSheetName); - } catch (Exception e) { - throw new WorkBookException("Failed to copy WorkSheet: " - + SourceSheetName + ": " + e.toString(), - WorkBookException.RUNTIME_ERROR); - } - mybook.getRefTracker().clearPtgLocationCaches(NewSheetName); - // update the merged cells (requires a WBH, that's why it's - // here) - WorkSheetHandle wsh = this.getWorkSheet(NewSheetName); - if (wsh != null) { - List mc = wsh.getMysheet().getMergedCellsRecs(); - for (int i = 0; i < mc.size(); i++) { - Mergedcells mrg = (Mergedcells) mc.get(i); - if (mrg != null) - mrg.initCells(this); - } - // now conditional formats - /* - * mc = wsh.getMysheet().getConditionalFormats(); for (int - * i=0;i - * You generally need not call this method. Dirty formulas will automatically be - * recalculated when their values are queried. This method is only useful for - * forcing calculation to occur at a certain time. In the case of functions such - * as NOW() whose value is volatile the formula will still be recalculated every - * time it is queried. - * - * @throws FunctionNotSupportedException if an unsupported function is used by any formula in the workbook - * @see #markFormulasDirty() - */ - public void recalc() { - int calcmode = mybook.getCalcMode(); - mybook.setCalcMode(CALCULATE_AUTO); // ensure referenced functions are - // calcualted as necesary! - Formula[] formulas = mybook.getFormulas(); - for (int idx = 0; idx < formulas.length; idx++) { - try { - formulas[idx].clearCachedValue(); - formulas[idx].calculate(); - } catch (FunctionNotSupportedException fe) { - Logger.logErr("WorkBookHandle.recalc: Error calculating Formula " - + fe.toString()); - } - } - // KSC: Clear out lookup caches! - this.getWorkBook().getRefTracker().clearLookupCaches(); - mybook.setCalcMode(calcmode); // reset - } - - /** - * Removes all of the WorkSheets from this WorkBook. - *

                  - * Bytes streamed from this WorkBook will create invalid Spreadsheet files - * unless a WorkSheet(s) are added to it. - *

                  - * NOTE: A WorkBook with no sheets is *invalid* and will not open in Excel. You - * must add sheets to this WorkBook for it to be valid. - */ - @Override - public void removeAllWorkSheets() { - - try { - Object ob = this.mybook.getTabID().getTabIDs().get(0); - this.mybook.getTabID().getTabIDs().removeAllElements(); - this.mybook.getTabID().getTabIDs().add(ob); - this.mybook.getTabID().updateRecord(); - } catch (Exception ex) { - } - WorkSheetHandle[] ws = this.getWorkSheets(); - try { - for (int x = 0; x < ws.length; x++) { - try { - ws[x].remove(); - } catch (WorkBookException e) { - } // ignore the invalid WorkBook problem - } - } catch (Exception e) { - // in case sheets already gone... - } - this.sheethandles.clear(); - this.mybook.closeSheets(); // replaced below with this - - /* - * WHY ARE WE DOING THIS??? // init new book // save records - * then reset to avoid - * ByteStreamer.stream records expansion Object[] recs= - * this.getWorkBook().getStreamer().getBiffRecords(); - * - * // keep Excel 2007 status boolean isExcel2007= - * this.getIsExcel2007(); - * WorkBookHandle ret = new WorkBookHandle(this.getBytes()); - * ret.setIsExcel2007(isExcel2007); - * - * this.getWorkBook().getStreamer().setBiffRecords(Arrays. - * asList(recs)); - * this.mybook = ret.getWorkBook(); / - **/ - - } - - /** - * Returns a WorkBookHandle containing an empty version of this WorkBook. - *

                  - * Use in conjunction with addSheetFromWorkBook() to create new output WorkBooks - * containing various sheets from a master template. - *

                  - * ie: WorkBookHandle emptytemplate = this.getNoSheetWorkBook(); - * emptytemplate.addSheetFromWorkBook(this, "Sheet1", "TargetSheet"); - * - * @return WorkBookHandle - the empty WorkBookHandle duplicate - * @see addSheetFromWorkBook - */ - @Override - public WorkBookHandle getNoSheetWorkBook() { - // to avoid ByteStreamer.stream records expansion - Object[] recs = this.getWorkBook().getStreamer().getBiffRecords(); - byte[] gb = this.getBytes(); - WorkBookHandle ret = new WorkBookHandle(gb); - this.getWorkBook().getStreamer().setBiffRecords(Arrays.asList(recs)); - ret.removeAllWorkSheets(); - return ret; - } - - /** - * Inserts a worksheet from a Source WorkBook. - * - * @param sourceBook - the WorkBook containing the sheet to copy - * @param sourceSheetName - the name of the sheet to copy - * @param destSheetName - the name of the new sheet in this workbook - * @throws WorkSheetNotFoundException - * @deprecated - use addWorkSheet(WorkSheetHandle sht, String NewSheetName){ - */ - @Override - @Deprecated - public boolean addSheetFromWorkBook(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException { - return this.addWorkSheet(sourceBook - .getWorkSheet(sourceSheetName), destSheetName) != null; - } - - /** - * Inserts a worksheet from a Source WorkBook. Brings all string data and - * formatting information from the source workbook. - *

                  - * Be aware this is programmatically creating a large amount of new formatting - * information in the destination workbook. A higher performance option will - * usually be using getNoSheetWorkbook and addSheetFromWorkBook. - * - * @param sourceBook - the WorkBook containing the sheet to copy - * @param sourceSheetName - the name of the sheet to copy - * @param destSheetName - the name of the new sheet in this workbook - * @throws WorkSheetNotFoundException - * @deprecated - use addWorkSheet(WorkSheetHandle sht, String NewSheetName){ - */ - @Deprecated - public boolean addSheetFromWorkBookWithFormatting(WorkBookHandle sourceBook, String sourceSheetName, String destSheetName) throws WorkSheetNotFoundException { - return this.addWorkSheet(sourceBook - .getWorkSheet(sourceSheetName), destSheetName) != null; - } - - /** - * Inserts a WorkSheetHandle from a separate WorkBookhandle into the current - * WorkBookHandle. - *

                  - * copies charts, images, formats from source workbook - *

                  - * Worksheet will be the same name as in the source workbook. To add a custom - * named worksheet use the addWorkSheet(WorkSheetHandle, String sheetname) - * method - * - * @param WorkSheetHandle the source WorkSheetHandle; - */ - public WorkSheetHandle addWorkSheet(WorkSheetHandle sourceSheet) { - return this.addWorkSheet(sourceSheet, sourceSheet.getSheetName()); - } - - /** - * Inserts a WorkSheetHandle from a separate WorkBookhandle into the current - * WorkBookHandle. - *

                  - * copies charts, images, formats from source workbook - * - * @param WorkSheetHandle the source WorkSheetHandle; - * @param String the Name of the new (destination) worksheet; - */ - @Override - public WorkSheetHandle addWorkSheet(WorkSheetHandle sourceSheet, String NewSheetName) { - sourceSheet.getSheet().populateForTransfer(); // copy all formatting + - // images for this sheet - List chts = sourceSheet.getSheet().getCharts(); - for (int i = 0; i < chts.size(); i++) { - Chart cxi = (Chart) chts.get(i); - cxi.populateForTransfer(); - } - byte[] bao = sourceSheet.getSerialBytes(); - try { - mybook.addBoundsheet(bao, sourceSheet - .getSheetName(), NewSheetName, StringTool - .stripPath(sourceSheet.getWorkBook() - .getName()), true); - WorkSheetHandle wsh = this.getWorkSheet(NewSheetName); - if (wsh != null) { - List mc = wsh.getMysheet().getMergedCellsRecs(); - for (int i = 0; i < mc.size(); i++) { - Mergedcells mrg = (Mergedcells) mc.get(i); - if (mrg != null) - mrg.initCells(this); - } - } - - return wsh; - } catch (Exception e) { - throw new WorkBookException( - "Failed to copy WorkSheet: " + e.toString(), - WorkBookException.RUNTIME_ERROR); - } - } - - /** - * Utility method to copy a format handle from a separate WorkBookHandle to this - * WorkBookHandle - * - * @param externalFormat - FormatHandle from an external WorkBookHandle - * @return - */ - public FormatHandle transferExternalFormatHandle(FormatHandle externalFormat) { - Xf xf = externalFormat.getXf(); - FormatHandle newHandle = new FormatHandle(this); - newHandle.addXf(xf); - return newHandle; - } - - /** - * Creates a new Chart and places it at the end of the workbook - * - * @param String the Name of the newly created Chart - * @return the new ChartHandle - */ - public ChartHandle createChart(String name, WorkSheetHandle wsh) { - if (wsh == null) { - // this is a sheetless chart - TODO: - } - /* - * a chart needs a supbook, externsheet, & MSO object in the - * book stream. I - * think this is due to the fact that the referenced series - * are usually stored - * in the fashon 'Sheet1!A4:B6' The sheet1 reference - * requires a supbook, though - * the reference is internal. - */ - - try { - ObjectInputStream ois = new ObjectInputStream( - new ByteArrayInputStream(getPrototypeChart())); - Chart newchart = (Chart) ois.readObject(); - newchart.setWorkBook(this.getWorkBook()); - if (this.getIsExcel2007()) - newchart = new OOXMLChart(newchart, this); - mybook.addPreChart(); - mybook.addChart(newchart, name, wsh.getSheet()); - /* - * add font recs if nec: for the default chart: default - * chart text fonts are # 5 - * & 6 title # 7 axis # 8 - */ - ChartHandle bs = new ChartHandle(newchart, this); - int nfonts = mybook.getNumFonts(); - while (nfonts < 8) { // ensure - Font f = new Font("Arial", Font.PLAIN, 200); - mybook.insertFont(f); - nfonts++; - } - Font f = mybook.getFont(8); // axis title font - if (f.toString() - .equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { - // it's default text font -- change to default axis title - // font - f = new Font("Arial", Font.BOLD, 240); - bs.setAxisFont(f); - } - f = mybook.getFont(7); // chart title font - if (f.toString() - .equals("Arial,400,200 java.awt.Color[r=0,g=0,b=0] font style:[falsefalsefalsefalse00]")) { - // it's default text font -- change to default title font - f = new Font("Arial", Font.BOLD, 360); - bs.setTitleFont(f); - } - bs.removeSeries(0); // remove the "dummied" series - bs.setAxisTitle(ChartHandle.XAXIS, null); // remove default axis - // titles, if any - bs.setAxisTitle(ChartHandle.YAXIS, null); // "" - return bs; - } catch (Exception e) { - Logger.logErr("Creating New Chart: " + name + " failed: " + e); - return null; - } - } - - /** - * delete an existing chart of the workbook - * - * @param chartname - */ - public void deleteChart(String chartname, WorkSheetHandle wsh) throws ChartNotFoundException { - try { - mybook.deleteChart(chartname, wsh.getSheet()); - } catch (ChartNotFoundException e) { - throw new ChartNotFoundException( - "Removing Chart: " + chartname + " failed: " + e); - } catch (Exception e) { - Logger.logErr("Removing Chart: " + chartname + " failed: " + e); - } - } - - /** - * Returns the number of Sheets in this WorkBook - * - * @return int number of Sheets - */ - public int getNumWorkSheets() { - return mybook.getNumWorkSheets(); - } - - /** - * Creates a new worksheet and places it at the specified position. The new - * sheet will be inserted before the sheet currently at the given index. If the - * given index is higher than the last index currently in use, the sheet will be - * added to the end of the workbook and will receive an index one higher than - * that of the current final sheet. If the given index is negative it will be - * interpreted as 0. - * - * @param name the name of the newly created worksheet - * @param sheetpos the index at which the sheet should be inserted - * @return the new WorkSheetHandle - */ - public WorkSheetHandle createWorkSheet(String name, int sheetpos) { - if (sheetpos > this.getNumWorkSheets()) - sheetpos = this.getNumWorkSheets(); - if (sheetpos < 0) - sheetpos = 0; - - WorkSheetHandle s = this.createWorkSheet(name); - s.setTabIndex(sheetpos); - return s; - } - - /** - * Creates a new worksheet and places it at the end of the workbook. - * - * @param name the name of the newly created worksheet - * @return the new WorkSheetHandle - */ - @Override - public WorkSheetHandle createWorkSheet(String name) { - try { - this.getWorkSheet(name); - throw new WorkBookException( - "Attempting to add worksheet with duplicate name. " + name - + " already exists in " + this.toString(), - WorkBookException.RUNTIME_ERROR); - } catch (WorkSheetNotFoundException ex) { - // good! - } - - Boundsheet bo = null; - try { - ObjectInputStream ois = new ObjectInputStream( - new ByteArrayInputStream(getPrototypeSheet())); - bo = (Boundsheet) ois.readObject(); - mybook.addBoundsheet(bo, null, name, null, false); - try { - WorkSheetHandle bs = this.getWorkSheet(name); - if (this.mybook.getNumWorkSheets() == 1) { - bs.setSelected(true); // it's the only sheet so select! - } else { - bs.setSelected(false); - } - return bs; - } catch (WorkSheetNotFoundException e) { - Logger.logWarn("Creating New Sheet: " + name + " failed: " + e); - return null; - } - } catch (Exception e) { - Logger.logWarn("Error loading prototype sheet: " + e); - return null; - } - } - - /** - * Returns an array of all FormatHandles in the workbook - */ - @Override - public FormatHandle[] getFormats() { - List l = this.mybook.getXfrecs(); - FormatHandle[] formats = new FormatHandle[l.size()]; - Iterator its = l.iterator(); - int i = 0; - while (its.hasNext()) { - Xf x = (Xf) its.next(); - // passing (this) with the format handle breaks the - // relationship to the font. - // if you need to pass it in we will have to handle it - // differently - try { - formats[i] = new FormatHandle(); - formats[i].setWorkBook(this.getWorkBook()); - formats[i].setXf(x); - } catch (Exception ex) { - } - i++; - } - return formats; - } - - /** - * Returns an array of all Conditional Formats in the workbook - *

                  - * these are formats referenced and used by the conditionally formatted ranges - * in the workbook. - * - * @return - */ - public FormatHandle[] getConditionalFormats() { - // the idea is to create a fake IXFE for use by - // sheetster to find formats - // int cfxe = this.getWorkBook().getNumFormats() + 50000; // - // there would have to - // be 50k styles on the sheet to conflict here.... - // int cfxe = this.getWorkBook().getNumXfs() + 50000; // - // there would have to be - // 50k styles on the sheet to conflict here.... - - List retl = new Vector(); - AbstractList v = this.mybook.getSheetVect(); - - Iterator its = v.iterator(); - - while (its.hasNext()) { - Boundsheet shtx = (Boundsheet) its.next(); - List fmtlist = shtx.getConditionalFormats(); - Iterator ixa = fmtlist.iterator(); - while (ixa.hasNext()) { - Condfmt cfm = (Condfmt) ixa.next(); - // cfm.initCells(this); // added! - int cfxe = cfm.getCfxe(); - FormatHandle fz = new FormatHandle(cfm, this, cfxe, null); - - fz.setFormatId(cfxe); - retl.add(fz); - // cfm.setCfxe(cfxe); - // cxfe++; - } - } - FormatHandle[] formats = new FormatHandle[retl.size()]; - for (int t = 0; t < formats.length; t++) - formats[t] = retl.get(t); - return formats; - } - - /** - * Set the recursion levels allowed for formulas calculated in this workbook - * before a circular reference is thrown. - *

                  - * Default setting is 250 levels of recursion - * - * @param recursion_allowed - */ - public static void setFormulaRecursionLevels(int recursion_allowed) { - RECURSION_LEVELS_ALLOWED = recursion_allowed; - } - - public String getWorkingDirectory() { - // TODO Auto-generated method stub - return null; - } - - public void initSharedFormulas() { - // TODO Auto-generated method stub - - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/WorkBookHandle.kt b/src/main/java/io/starter/OpenXLS/WorkBookHandle.kt new file mode 100644 index 0000000..7de06aa --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/WorkBookHandle.kt @@ -0,0 +1,2200 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.LEO.BlockByteReader +import io.starter.formats.LEO.InvalidFileException +import io.starter.formats.LEO.LEOFile +import io.starter.formats.XLS.* +import io.starter.formats.XLS.charts.Chart +import io.starter.formats.XLS.charts.OOXMLChart +import io.starter.toolkit.* + +import java.io.* +import java.net.URL +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.* + +/** + * The WorkBookHandle provides a handle to the XLS file and includes convenience + * methods for working with the WorkSheets and Cell values within the XLS file. + * For example:

                  + * ` + * WorkBookHandle book = new WorkBookHandle("testxls.xls");

                  + * WorkSheetHandle sheet = book.getWorkSheet("Sheet1");

                  + * CellHandle cell = sheet.getCell("B22");

                  +` * + * + * + * + * By default, OpenXLS will lock open WorkBook files. To close the file after + * parsing and work with a temporary file instead, use the following setting: + *

                  + * ` + * System.getProperties().put(WorkBookHandle.USETEMPFILE, "true"); +` *

                  + * If you enable this mode you will need to periodically clean up the generated + * temporary files in your working directory. All OpenXLS temporary file names + * begin with "ExtenXLS_". + */ +class WorkBookHandle : DocumentHandle, WorkBook, Handle { + + /** + * Returns a low-level WorkBook. + * + * + * NOTE: The WorkBook class is NOT a part of the published API. Any of the + * methods and/or variables on a WorkBook object are subject to change without + * notice in new versions of OpenXLS. + */ + override var workBook: io.starter.formats.XLS.WorkBook? = null + protected set + /** + * Gets the internal LEOFile object. + * + * + * **WARNING:** This method is not part of the public API. Its use + * is not supported and behavior is subject to change. + * + */ + var leoFile: LEOFile? = null + protected set + /** + * Gets the internal Factory object. + * + * + * **WARNING:** This method is not part of the public API. Its use + * is not supported and behavior is subject to change. + * + */ + var factory: WorkBookFactory? = null + protected set + + protected var plist: ProgressListener? = null + + /** + * Returns all strings that are in the SharedStringTable for this workbook. The + * SST contains all standard string records in cells, but may not include such + * things as strings that are contained within formulas. This is useful for such + * things as full text indexing of workbooks + * + * @return Strings in the workbook. + */ + val allStrings: Array + get() = this.workBook!!.allStrings + + /** + * Get either the default color table, or the color table from the custom + * palatte(if exists) from the WorkBook + * + * @return + */ + override val colorTable: Array + get() = this.workBook!!.colorTable + + /** + * Returns whether this book uses the 1904 date format. + * + */ + val is1904: Boolean + @Deprecated("Use {@link #getDateFormat()} instead.") + get() = workBook!!.is1904 + + /** + * Gets the date format used by this book. + */ + override val dateFormat: DateConverter.DateFormat + get() = workBook!!.dateFormat + + /** + * Returns the lowest version of Excel compatible with the input file. + * + * @return an Excel version string + */ + val xlsVersionString: String + get() = workBook!!.xlsVersionString + + /** + * Return useful statistics about this workbook. + * + * @return a string contatining various statistics. + */ + val stats: String + get() = workBook!!.stats + + /** + * Returns an Array of the CellRanges existing in this WorkBook specifically the + * Ranges referenced in Formulas, Charts, and Named Ranges. + * + * + * This is necessary to allow for automatic updating of references when + * adding/removing/moving Cells within these ranges, as well as shifting + * references to Cells in Formulas when Formula records are moved. + * + * @return all existing Cell Range references used in Formulas, Charts, and + * Names + */ + val cellRanges: Array + get() = this.workBook!!.refTracker!!.cellRanges + + /** + * get an array of handles to all PivotTables in the WorkBook + * + * @return PivotTable[] all of the WorkBooks PivotTables + */ + override val pivotTables: Array + @Throws(PivotTableNotFoundException::class) + get() { + val sxv = workBook!!.allPivotTableViews + if (sxv == null || sxv.size == 0) + throw PivotTableNotFoundException( + "There are no PivotTables defined in: " + this.name) + val pth = arrayOfNulls(sxv.size) + for (t in pth.indices) { + pth[t] = PivotTableHandle(sxv[t], this) + } + return pth + } + + /** + * Get the calculation mode for the workbook. + * + * + * CALCULATE_ALWAYS is the default for new workbooks. Calling Cell.getVal() will + * calculate formulas if they exist within the cell. + * + * + * CALCULATE_EXPLICIT will return present value of the cell. Formula calculation + * will only occur when explicitly called through the Formula Handle + * + * + * WorkBookHandle.CALCULATE_ALWAYS -- recalc every time the cell value is + * requested (no cacheing) WorkBookHandle.CALCULATE_EXPLICIT -- recalc only when + * FormulaHandle.calculate() called WorkBookHandle.CALCULATE_AUTO -- only recac + * when changes + * + * @param CalcMode Calculation mode to use in workbook. + */ + /** + * Set the calculation mode for the workbook. + * + * + * CALCULATE_AUTO is the default for new workbooks. Calling Cell.getVal() will + * calculate formulas if they exist within the cell. + * + * + * CALCULATE_EXPLICIT will return present, cached value of the cell. Formula + * calculation will ONLY occur when explicitly called through the Formula + * Handle.calculate() method. + * + * + * CALCULATE_ALWAYS will ignore the cache and force a recalc every time a cell + * value is requested. + * + * + * + * + * WorkBookHandle.CALCULATE_AUTO WorkBookHandle.CALCULATE_ALWAYS + * WorkBookHandle.CALCULATE_EXPLICIT + * + * @param CalcMode Calculation mode to use in workbook. + */ + override var formulaCalculationMode: Int + get() = workBook!!.calcMode + set(CalcMode) { + workBook!!.calcMode = CalcMode + } + + /** + * Returns all ImageHandles in the workbook + * + * @return + */ + val images: Array + get() { + val ret = Vector() + for (t in 0 until this.numWorkSheets) { + try { + val r = this.getWorkSheet(t).images + for (x in r!!.indices) + ret.add(r!![x]) + } catch (ex: Exception) { + } + + } + val retx = arrayOfNulls(ret.size) + ret.toTypedArray() + return retx + } + + /** + * Returns all Chart Handles contained in the WorkBook + * + * @return ChartHandle[] an array of all Charts in the WorkBook + */ + override val charts: Array + get() { + val cv = workBook!!.chartVect + val cht = arrayOfNulls(cv.size) + for (x in cv.indices) { + cht[x] = ChartHandle(cv[x] as Chart, this) + } + return cht + } + + /** + * Returns all Named Range Handles + * + * @return NameHandle[] all of the Named ranges in the WorkBook + */ + override val namedRanges: Array + get() { + val nand = workBook!!.names + val nands = arrayOfNulls(nand.size) + for (x in nand.indices) { + nands[x] = NameHandle(nand[x], this) + } + return nands + } + + /** + * Returns all Named Range Handles scoped to WorkBook. + * + * + * Note this will not include worksheet scoped named ranges + * + * @return NameHandle[] all of the Named ranges that are scoped to WorkBook + */ + val namedRangesInScope: Array + get() { + val nand = workBook!!.workbookScopedNames + val nands = arrayOfNulls(nand.size) + for (x in nand.indices) { + nands[x] = NameHandle(nand[x], this) + } + return nands + } + + /** + * Returns the name of this WorkBook + * + * @return String name of WorkBook + */ + override var name: String + get() = if (name != null) name else "New Spreadsheet" + set + + /** + * Returns an array containing all cells in the WorkBook + * + * @return CellHandle array of all book cells + */ + override// handle Mulblanks: ref a range of cells; to get correct + // cell address, + // traverse thru range and set cellhandle ref to correct + // column + // for Mulblank use only -sets correct + // column reference for multiple blank + // cells + // ... + val cells: Array + get() { + val allcz = this.workBook!!.cells + val ret = arrayOfNulls(allcz.size) + var aMul: Mulblank? = null + var c: Short = -1 + for (t in ret.indices) { + ret[t] = CellHandle(allcz[t], this) + if (allcz[t].opcode == XLSConstants.MULBLANK) { + if (allcz[t] === aMul) { + c++ + } else { + aMul = allcz[t] as Mulblank + c = aMul.colFirst.toShort() + } + ret[t].setBlankRef(c.toInt()) + } + } + return ret + } + + /** + * Returns the number of Cells in this WorkBook + * + * @return int number of Cells + */ + override val numCells: Int + get() = workBook!!.numCells + + /** + * Gets the spreadsheet as a byte array in BIFF8 (Excel '97-2003) format. + * + */ + override val bytes: ByteArray? + @Deprecated("Writing the spreadsheet to a byte array uses a great deal of\n" + + " memory and generally provides no benefit over streaming output.\n" + + " Use the {@link #write} family of methods instead. If you need a\n" + + " byte array use {@link ByteArrayOutputStream}.") + get() { + try { + val bout = ByteArrayOutputStream() + writeBytes(bout) + + return bout.toByteArray() + } catch (e1: Exception) { + Logger.logErr("Getting Spreadsheet bytes failed.", e1) + return null + } + + } + + /** + * Gets the constant representing this document's native format. + */ + override val format: Int + get() { + val name = this.fileName.toLowerCase() + + return if (this.isExcel2007) { + if (OOXMLAdapter.hasMacros(this)) + if (name.endsWith(".xltm")) FORMAT_XLTM else FORMAT_XLSM + else + if (name.endsWith(".xltx")) FORMAT_XLTX else FORMAT_XLSX + } else + FORMAT_XLS + } + + override val fileExtension: String + get() { + when (this.format) { + FORMAT_XLSX -> return ".xlsx" + FORMAT_XLSM -> return ".xlsm" + FORMAT_XLTX -> return ".xltx" + FORMAT_XLTM -> return ".xltm" + FORMAT_XLS -> return ".xls" + else -> return "" + } + } + + /** + * Returns whether the underlying spreadsheet is in Excel 2007 format by + * default. + * + * + * Even if this method returns true, it is still possible to write out the file + * as a BIFF8 (Excel 97-2003) file, but unsupported features will be dropped, + * and some files could experience corruption. + * + * @return whether the underlying spreadsheet is Excel 2007 format + */ + /** + * Sets whether this Workbook is in Excel 2007 format. Excel 2007 format + * contains larger maximum column and row contraints, for example.

                  + * Even if the workbook is set to Excel 2007 format, it is still possible to + * write out the file as a BIFF8 (Excel 97-2003) file, but unsupported features + * will be dropped, and some files could experience corruption. + * + * @param isExcel2007 + */ + var isExcel2007: Boolean + get() = this.workBook!!.isExcel2007 + set(isExcel2007) { + this.workBook!!.isExcel2007 = isExcel2007 + } + + /** + * Returns an array of handles to all of the WorkSheets in the Workbook. + * + * @return WorkSheetHandle[] Array of all WorkSheets in WorkBook + */ + override val workSheets: Array? + get() { + try { + if (factory != null) { + val numsheets = workBook!!.numWorkSheets + if (numsheets == 0) + throw WorkSheetNotFoundException( + "WorkBook has No Sheets.") + val sheets = arrayOfNulls(numsheets) + for (i in 0 until numsheets) { + val bs = workBook!!.getWorkSheetByNumber(i) + bs.workBook = this.workBook + sheets[i] = WorkSheetHandle(bs, this) + } + return sheets + } + return null + } catch (a: WorkSheetNotFoundException) { + Logger.logWarn("getWorkSheets() failed: $a") + return null + } + + } + + internal var sheethandles: Hashtable? = Hashtable() + + /** + * returns the active or selected worksheet tab + * + * @return WorkSheetHandle + * @throws WorkSheetNotFoundException + */ + val activeSheet: WorkSheetHandle + @Throws(WorkSheetNotFoundException::class) + get() = this.getWorkSheet(this.workBook!!.selectedSheetNum) + + /** + * Returns a WorkBookHandle containing an empty version of this WorkBook. + * + * + * Use in conjunction with addSheetFromWorkBook() to create new output WorkBooks + * containing various sheets from a master template. + * + * + * ie: WorkBookHandle emptytemplate = this.getNoSheetWorkBook(); + * emptytemplate.addSheetFromWorkBook(this, "Sheet1", "TargetSheet"); + * + * @return WorkBookHandle - the empty WorkBookHandle duplicate + * @see addSheetFromWorkBook + */ + override// to avoid ByteStreamer.stream records expansion + val noSheetWorkBook: WorkBookHandle + get() { + val recs = this.workBook!!.streamer.biffRecords + val gb = this.bytes + val ret = WorkBookHandle(gb) + this.workBook!!.streamer.setBiffRecords(Arrays.asList(*recs)) + ret.removeAllWorkSheets() + return ret + } + + /** + * Returns the number of Sheets in this WorkBook + * + * @return int number of Sheets + */ + val numWorkSheets: Int + get() = workBook!!.numWorkSheets + + /** + * Returns an array of all FormatHandles in the workbook + */ + override// passing (this) with the format handle breaks the + // relationship to the font. + // if you need to pass it in we will have to handle it + // differently + val formats: Array + get() { + val l = this.workBook!!.xfrecs + val formats = arrayOfNulls(l.size) + val its = l.iterator() + var i = 0 + while (its.hasNext()) { + val x = its.next() as Xf + try { + formats[i] = FormatHandle() + formats[i].workBook = this.workBook + formats[i].xf = x + } catch (ex: Exception) { + } + + i++ + } + return formats + } + + /** + * Returns an array of all Conditional Formats in the workbook + * + * + * these are formats referenced and used by the conditionally formatted ranges + * in the workbook. + * + * @return + */ + // the idea is to create a fake IXFE for use by + // sheetster to find formats + // int cfxe = this.getWorkBook().getNumFormats() + 50000; // + // there would have to + // be 50k styles on the sheet to conflict here.... + // int cfxe = this.getWorkBook().getNumXfs() + 50000; // + // there would have to be + // 50k styles on the sheet to conflict here.... + // cfm.initCells(this); // added! + // cfm.setCfxe(cfxe); + // cxfe++; + val conditionalFormats: Array + get() { + + val retl = Vector() + val v = this.workBook!!.sheetVect + + val its = v.iterator() + + while (its.hasNext()) { + val shtx = its.next() as Boundsheet + val fmtlist = shtx.conditionalFormats + val ixa = fmtlist!!.iterator() + while (ixa.hasNext()) { + val cfm = ixa.next() as Condfmt + val cfxe = cfm.cfxe + val fz = FormatHandle(cfm, this, cfxe, null) + + fz.formatId = cfxe + retl.add(fz) + } + } + val formats = arrayOfNulls(retl.size) + for (t in formats.indices) + formats[t] = retl[t] + return formats + } + + // TODO Auto-generated method stub + val workingDirectory: String? + get() = null + + /** + * Searches all Cells in the workbook for the string occurrence and replaces + * with the replacement text. + * + * @return the number of replacements that were made + */ + fun searchAndReplace(searchfor: String, replacewith: String): Int { + val cx = cells + var foundcount = 0 + for (t in cx.indices) { + if (cx[t].cell !is Formula) { + // find the string + if (!cx[t].isNumber) { + val v = cx[t].stringVal + if (v!!.indexOf(searchfor) > -1) { + cx[t].`val` = StringTool + .replaceText(v, searchfor, replacewith) + foundcount++ + } + } + } + } + return foundcount + } + + /** + * Return useful statistics about this workbook. + * + * @param use html line breaks + * @return a string contatining various statistics. + */ + fun getStats(usehtml: Boolean): String { + return workBook!!.getStats(usehtml) + } + + /** + * Returns the Cell at the specified Location + * + * @param address + * @return + */ + @Throws(CellNotFoundException::class, WorkSheetNotFoundException::class) + override fun getCell(address: String): CellHandle { + val shtpos = address.indexOf("!") + if (shtpos < 0) + throw CellNotFoundException("$address not found. You need to specify a location in the format: Sheet1!A1") + val sheetstr = address.substring(0, shtpos) + val sht = this.getWorkSheet(sheetstr) + val celstr = address.substring(shtpos + 1) + return sht!!.getCell(celstr) + } + + /** + * get a handle to a PivotTable in the WorkBook + * + * @param String name of the PivotTable + * @return PivotTable the PivotTable + */ + @Throws(PivotTableNotFoundException::class) + override fun getPivotTable(ptname: String): PivotTableHandle { + val st = workBook!!.getPivotTableView(ptname) ?: throw PivotTableNotFoundException(ptname) + return PivotTableHandle(st, this) + } + + /** + * set the workbook to protected mode + * + * + * Note: the password cannot be decrypted or changed in Excel -- protection can + * only be set/removed using OpenXLS + * + * @param boolean whether to protect the book + */ + override fun setProtected(protect: Boolean) { + // TODO: Check that this behavior is correct + // This is what the old implementation did + + val protector = workBook!!.protectionManager + + // Excel default... no kidding! + if (protect) + protector.password = "VelvetSweatshop" + else + protector.password = null + + protector.protected = protect + } + + /** + * set Default row height in twips (=1/20 of a point) + * + * + * Note: only affects undefined Rows containing Cells + * + * @param int Default Row Height + */ + // should be a double as Excel units are 1/20 of what is + // stored in + // defaultrowheight + // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 + // (approx) + // should expect users to use Excel units and target method + // do the 20* + // conversion + override fun setDefaultRowHeight(t: Int) { + workBook!!.setDefaultRowHeight(t) + } + + /** + * Set the default column width across all worksheets

                  + * This setting is a worksheet level setting, so will be applied to all existing + * worksheets. individual worksheets can also be set using + * WorkSheetHandle.setDefaultColWidth + * + * + * This setting is roughly the width of the character '0' The default width of a + * column is 8. + */ + override fun setDefaultColWidth(t: Int) { + workBook!!.setDefaultColWidth(t) + } + + /** + * Returns a Formula Handle + * + * @return FormulaHandle a formula handle in the WorkBook + */ + @Throws(FormulaNotFoundException::class) + fun getFormulaHandle(celladdress: String): FormulaHandle { + val formula = workBook!!.getFormula(celladdress) + return FormulaHandle(formula, this) + } + + /** + * Returns an ImageHandle for manipulating images in the WorkBook + * + * @param imagename + * @return + */ + @Throws(ImageNotFoundException::class) + fun getImage(imagename: String): ImageHandle { + for (t in 0 until this.numWorkSheets) { + try { + val r = this.getWorkSheet(t).images + for (x in r!!.indices) + if (r!![x].name == imagename) + return r!![x] + } catch (ex: Exception) { + } + + } + throw ImageNotFoundException( + "Image not found: $imagename in $this") + } + + /** + * Returns a Named Range Handle + * + * @return NameHandle a Named range in the WorkBook + */ + @Throws(CellNotFoundException::class) + override fun getNamedRange(rangename: String): NameHandle { + val nand = workBook!!.getName(rangename.toUpperCase()) + ?: throw CellNotFoundException(rangename) // case-insensitive + return NameHandle(nand, this) + } + + /** + * Returns a Named Range Handle if it exists in the specified scope. + * + * + * This can be used to distinguish between multiple named ranges with the same + * name but differing scopes + * + * @return NameHandle a Named range in the WorkBook that exists in the scope + */ + @Throws(CellNotFoundException::class) + fun getNamedRangeInScope(rangename: String): NameHandle { + val nand = workBook!!.getScopedName(rangename) ?: throw CellNotFoundException(rangename) + return NameHandle(nand, this) + } + + /** + * Create a named range in the workbook + * + * + * Note that the named range designation can conform to excel specs, that is, + * boolean values, references, or string variables can be set. Remember to + * utilize the sheet name when setting referential names. + * + * + * + * + * NameHandle nh = createNamedRange("cellRange", "Sheet1!A1:B3"); NameHandle nh + * = createNamedRange("trueRange", "=true"); + * + * @param name The name that should be used to reference this named range + * @param rangeDef Range of the cells for this named range, in excel syntax including + * sheet name, ie "Sheet1!A1:D1" + * @return NameHandle for modifying the named range + */ + fun createNamedRange(name: String, rangeDef: String): NameHandle { + return NameHandle(name, rangeDef, this) + } + + /** + * Returns a Chart Handle + * + * @return ChartHandle a Chart in the WorkBook + */ + // KSC: NOTE: this methodology needs work as a book may + // contain charts in + // different sheets containing the same name + // TODO: rethink + @Throws(ChartNotFoundException::class) + override fun getChart(chartname: String): ChartHandle { + return ChartHandle(workBook!!.getChart(chartname), this) + } + + /** + * retrieve a ChartHandle via id + * + * @param id + * @return + * @throws ChartNotFoundException + */ + @Throws(ChartNotFoundException::class) + fun getChartById(id: Int): ChartHandle { + val cv = workBook!!.chartVect + var cht: Chart? = null + for (x in cv.indices) { + cht = cv[x] as Chart + if (cht.id == id) + return ChartHandle(cht, this) + } + throw ChartNotFoundException("Id $id") + } + + /** + * Returns whether the sheet selection tabs should be shown. + */ + fun showSheetTabs(): Boolean { + return workBook!!.showSheetTabs() + } + + /** + * Sets whether the sheet selection tabs should be shown. + */ + fun setShowSheetTabs(show: Boolean) { + workBook!!.setShowSheetTabs(show) + } + + /** + * Writes the document to the given path. If the filename ends with ".xlsx" or + * ".xlsm", the workbook will be written as OOXML (XLSX). Otherwise it will be + * written as BIFF8 (XLS). For OOXML, if the file has a VBA project the file + * extension must be ".xlsm". It will be changed if necessary. + * + * @param path the path to which the document should be written + */ + @Deprecated("The filename-based format choosing is counter-intuitive and\n" + + " failure-prone. Use {@link #write(OutputStream, int)} instead.") + fun write(path: String) { + val ext = path.toLowerCase() + write(path, ext.endsWith(".xlsx") || ext.endsWith(".xlsm")) + } + + /** + * Writes the document to the given file in either XLS or XLSX. For OOXML, if + * the file has a VBA project the file extension must be ".xlsm". It will be + * changed if necessary. + * + * @param path the path to which the document should be written + * @param ooxml If `true`, write as OOXML (XLSX). Otherwise, write as + * BIFF8 (XLS). + */ + @Deprecated("The boolean format parameter is not flexible enough to represent\n" + + " all supported formats. Use {@link #write(File, int)} instead.") + fun write(path: String, ooxml: Boolean) { + var path = path + val format: Int + if (ooxml) { + if (this.isExcel2007) + format = this.getFormat(path) + else + format = FORMAT_XLSX + + if (!OOXMLAdapter.hasMacros(this)) + path = StringTool.replaceExtension(path, ".xlsx") + else + // it's a macro-enabled workbook + path = StringTool.replaceExtension(path, ".xlsm") + } else + format = FORMAT_XLS + + try { + this.write(File(path), format) + } catch (e: Exception) { + throw WorkBookException("error writing workbook", + WorkBookException.WRITING_ERROR, e) + } + + } + + /** + * Writes the document to the given stream in either XLS or XLSX format. + * + * @param dest the stream to which the document should be written + * @param ooxml If `true`, write as OOXML (XLSX). Otherwise, write as + * BIFF8 (XLS). + */ + @Deprecated("The boolean format parameter is not flexible enough to represent\n" + + " all supported formats. Use {@link #write(OutputStream, int)}\n" + + " instead.") + fun write(dest: OutputStream, ooxml: Boolean) { + val format: Int + if (ooxml) { + if (this.isExcel2007) + format = this.format + else + format = FORMAT_XLSX + } else + format = FORMAT_XLS + + try { + if (format > WorkBookHandle.FORMAT_XLS && this.file != null) { + OOXMLAdapter.refreshPassThroughFiles(this) + } + this.write(dest, format) + } catch (e: Exception) { + throw WorkBookException("error writing workbook", + WorkBookException.WRITING_ERROR, e) + } + + } + + /** + * Gets the constant representing this document's desired format + */ + fun getFormat(path: String?): Int { + if (path == null) + return format + return if (this.isExcel2007) { + if (OOXMLAdapter.hasMacros(this)) + if (path.endsWith(".xltm")) FORMAT_XLTM else FORMAT_XLSM + else + if (path.endsWith(".xltx")) FORMAT_XLTX else FORMAT_XLSX + } else + FORMAT_XLS + } + + /** + * Writes the document to the given stream in the requested format. + * + * + * format choices: + * + * + * WorkBookHandle.FORMAT_XLS for 2003 and previous versions

                  + * WorkBookHandle.FORMAT_XLSX for non-macro-enabled 2007 version

                  + * WorkBookHandle.FORMAT_XLSM for macro-enabled 2007 version

                  + * WorkBookHandle.FORMAT_XLTM for macro-enabled 2007 templates.

                  + * WorkBookHandle.FORMAT_XLTX for 2007 templates, + * + * + * **IMPORTANT NOTE:** if the resulting filename contains the .XLSM extension + *

                  + * the WorkBook **MUST** be written in FORMAT_XLSM; otherwise open errors + * will occur + * + * + * **NOTE:** If the format is FORMAT_XLSX and the filename contains macros + *

                  + * the file will be written as Macro-Enabled i.e. in FORMAT_XLSM. In these + * cases,

                  + * the filename must contain the .XLSM extension + * + * @param dest the stream to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the stream + */ + @Throws(IOException::class) + override fun write(dest: OutputStream, format: Int) { + var format = format + if (format == DocumentHandle.FORMAT_NATIVE) + format = this.format + + when (format) { + FORMAT_XLSX, FORMAT_XLSM, FORMAT_XLTX, FORMAT_XLTM -> try { + if (this.file != null) + OOXMLAdapter.refreshPassThroughFiles(this) + + val adapter = OOXMLWriter() + adapter.format = format + adapter.getOOXML(this, dest) + } catch (e: IOException) { + throw e + } catch (e: Exception) { + // TODO: OOXMLAdapter only throws IOException, change its + // throws + throw WorkBookException("error writing workbook", + WorkBookException.WRITING_ERROR, e) + } + + FORMAT_XLS -> try { + this.workBook!!.streamer.writeOut(dest) + } catch (e: io.starter.formats.XLS.WorkBookException) { + val cause = e.cause + if (cause is IOException) + throw cause + throw e + } + + else -> throw IllegalArgumentException("unknown output format") + } + } + + /** + * Writes the document to the given stream in the requested OOXML format. + * + * @param dest the stream to which the document should be written + * @param format the constant representing the desired output format + * @throws IllegalArgumentException if the given type code is invalid + * @throws IOException if an error occurs while writing to the stream + */ + @Deprecated("This method is like {@link #write(OutputStream, int)} except it\n" + + " only supports OOXML formats. Use that instead.") + @Throws(Exception::class) + fun writeXLSXBytes(dest: OutputStream, format: Int) { + this.write(dest, format) + } + + /** + * Writes the document to the given stream in the default OOXML format. + * + * @param dest the stream to which the document should be written + * @throws IOException if an error occurs while writing to the stream + */ + @Deprecated("Use {@link #write(OutputStream, int}) instead.") + @Throws(Exception::class) + fun writeXLSXBytes(dest: OutputStream) { + this.write(dest, true) + } + + /** + * Writes the document to the given stream in BIFF8 (XLS) format. + * + * + * + * To output a debugging `StringBuffer`, you must first set the + * autolockdown setting:

                  + * ` + * props.put("io.starter.OpenXLS.autocreatelockdown","true"); + ` * + * + * + * @param dest the stream to which the document should be written + * @return for debugging: a StringBuffer containing an output of the record + * bytes streamed + */ + @Deprecated("Use {@link #write(OutputStream, int)} instead.") + override fun writeBytes(dest: OutputStream): StringBuffer? { + return workBook!!.streamer.writeOut(dest) + } + + /** + * Default constructor creates a new, empty Spreadsheet with + * + * + * 3 WorkSheets: "Sheet1","Sheet2",and "Sheet3". + */ + constructor() { + // Xf.DEFAULTIXFE= 15; // reset to default in cases of + // having previously read + // Excel2007 template which may have set defaultXF + // differently + this.initDefault() + } + + /** + * Constructor creates a new, empty Spreadsheet with 3 worksheets: "Sheet1", + * "Sheet2" and "Sheet3"

                  + * This version allows flagging the workbook as Excel 2007 format.

                  + * Excel 2007 format contains larger maximum column and row contraints, for + * example.

                  + * Even if the workbook is set to Excel 2007 format, it is still possible to + * write out the file as a BIFF8 (Excel 97-2003) file, but unsupported features + * will be dropped, and some files could experience corruption. + * + * @param boolean Excel2007 - true if set to Excel 2007 version + */ + constructor(Excel2007: Boolean) { + this.initDefault() + this.isExcel2007 = Excel2007 + } + + /** + * another handle to the useful ability to load a book from the prorotype bytes + */ + protected fun initDefault() { + try { + val b = prototypeBook ?: throw io.starter.formats.XLS.WorkBookException( + "Unable to load prototype workbook.", + WorkBookException.LICENSING_FAILED) + val bbf = ByteBuffer.wrap(b) + bbf.order(ByteOrder.LITTLE_ENDIAN) + leoFile = LEOFile(bbf) + } catch (e: Exception) { + throw InvalidFileException( + "WorkBook could not be instantiated: $e") + } + + this.initFromLeoFile(leoFile) + } + + /** + * constructor which takes an InputStream containing the bytes of a valid XLS + * file. + * + * @param InputStream contains the valid BIFF8 bytes for reading + */ + constructor(inx: InputStream) { + this.initFromStream(inx) + } + + /** + * Initialization of this workbook handle from a leoFile; + */ + private fun initFromLeoFile(leo: LEOFile) { + this.leoFile = leo + try { + val bar = leoFile!!.xlsBlockBytes + this.initBytes(bar) + this.isExcel2007 = false + leoFile!!.clearAfterInit() + } catch (e: Exception) { + if (e is io.starter.formats.XLS.WorkBookException) + throw e + throw io.starter.formats.XLS.WorkBookException( + "ERROR: instantiating WorkBookHandle failed: $e", + WorkBookException.UNSPECIFIED_INIT_ERROR, e) + } + + } + + /** + * Initialize this workbook from a stream, unfortunately our byte backer + * requires a file, so create a tempfile and init from that + */ + protected fun initFromStream(input: InputStream) { + try { + val target = TempFileManager.createTempFile("WBP", ".tmp") + + JFileWriter.writeToFile(input, target) + this.initFromFile(target.absoluteFile) + if (this.leoFile != null) + // it would be if XLSX or XLSM ... + // 20090323 KSC + this.leoFile!!.closefb() + // this.myLEOFile.close(); // close now flushes buffers + + // storages ... + input.close() + + val fdel = File(target.toString()) + if (!fdel.delete()) { + if (this.DEBUGLEVEL > Document.DEBUG_LOW) + Logger.logWarn("Could not delete tempfile: $target") + } + } catch (ex: IOException) { + Logger.logErr("Initializing WorkBookHandle failed.", ex) + } + + } + + /** + * Create a new WorkBookHandle from the byte array passed in. Byte array passed + * in must contain a valid xls or xlsx workbook file + * + * @param byte[] byte array containing the valid XLS or XLSX file for reading + */ + constructor(barray: ByteArray) { + initializeFromByteArray(barray) + } + + /** + * Protected method that handles WorkBookHandle(byte[]) constructor + * + * @param barray + */ + protected fun initializeFromByteArray(barray: ByteArray) { + // check first bytes to see if this is a zipfile (OOXML) + if (barray[0].toChar() == 'P' && barray[1].toChar() == 'K') { + try { + // added "." fixes Baxter Open Bug [BugTracker 2909] + val ftmp = TempFileManager.createTempFile("WBP", ".tmp") + val fous = FileOutputStream(ftmp) + fous.write(barray) + fous.flush() + fous.close() + this.initFromFile(ftmp) + return + } catch (e: Exception) { + Logger.logErr("Could not parse XLSX from bytes.$e") + return + } + + } + + val bbf = ByteBuffer.wrap(barray) + bbf.order(ByteOrder.LITTLE_ENDIAN) + leoFile = LEOFile(bbf) + if (leoFile!!.hasWorkBook()) { + try { + val bar = leoFile!!.xlsBlockBytes + this.initBytes(bar) + } catch (e: Throwable) { + if (e is OutOfMemoryError) + throw e + if (e is WorkBookException) + throw e + val errstr = "Instantiating WorkBookHandle failed: $e" + throw io.starter.formats.XLS.WorkBookException(errstr, + WorkBookException.UNSPECIFIED_INIT_ERROR) + } + + } else { + Logger.logWarn("Initializing WorkBookHandle failed: byte array does not contain a supported Excel WorkBook.") + throw InvalidFileException( + "byte array does not contian a supported Excel WorkBook.") + } + } + + /** + * Fetches a workbook from a URL + * + * + * If you need to authenticate your URL connection first then use the + * InputStream constructor + * + * @param urlx + * @return + * @throws Exception + */ + constructor(url: URL) : this(DocumentHandle.getFileFromURL(url)) {}/* + * OK, both this method and the (inputstream) constructor + * set a temp file, is + * this not possible to do without hitting the disk? TODO: + * look into fix + */ + + /** + * constructor which takes the XLS file name and has an optional debug setting + * to assist with output. Setting this value will cause verbose logging and is + * discouraged unless required for support. + * + * @param String filePath the name of the XLS file to read + * @param Debug level + */ + @JvmOverloads + constructor(filePath: String, debug: Int = 0) { + this.debugLevel = debug + val f = File(filePath) + this.initFromFile(f) + this.file = f // XXX KSC: Save for potential re-input of pass-through + // ooxml files + + } + + /** + * constructor which takes the XLS file + * + * @param File the XLS file to read + * @param Debug level + */ + constructor(fx: File?) { + this.initFromFile(fx!!) + } + + protected fun initWorkBookFactory() { + factory = WorkBookFactory() + } + + /** + * initialize from an XLSX/OOXML workbook. + */ + private fun initXLSX(fname: String): Boolean { + // do before parseNBind so can set myfactory & fname + // set state vars for this workbookhandle + this.initWorkBookFactory() + + factory!!.debugLevel = this.DEBUGLEVEL + factory!!.fileName = this.name + + if (plist != null) + factory!!.register(plist!!) // register progress notifier + try { + // iterate sheets,inputting cell values, named ranges and + // formula strings + val oe = OOXMLReader() + val bk = WorkBookHandle() + bk.removeAllWorkSheets() + factory!!.debugLevel = this.DEBUGLEVEL + bk.DEBUGLEVEL = this.DEBUGLEVEL + oe.parseNBind(bk, fname) + this.sheethandles = bk.sheethandles + this.workBook = bk.workBook + } catch (e: Exception) { + throw WorkBookException( + "WorkBookHandle OOXML Read failed: $e", + WorkBookException.UNSPECIFIED_INIT_ERROR, e) + } + + workBook!!.isExcel2007 = true + return true + } + + /** + * do all initialization with a filename + * + * @param fname + */ + protected fun initFromFile(fx: File) { + val fname = fx.path + var finch = "" + + // handle csv import + val fincheck: FileReader + try { + fincheck = FileReader(fx) + if (fx.length() > 100) { + val cbuf = CharArray(100) + fincheck.read(cbuf) + finch = String(cbuf) + } + fincheck.close() + + } catch (e: FileNotFoundException) { + Logger.logErr("WorkBookHandle: Cannot open file " + fname + ": " + + e) + } catch (e1: Exception) { + Logger.logErr("Invalid XLSX/OOXML File.") + } + + this.name = fname // 20081231 KSC: set here + if (finch.toUpperCase().startsWith("PK")) { // it's a zip file... give + // XLSX parsing a shot + if (this.file != null) + OOXMLAdapter.refreshPassThroughFiles(this) + if (initXLSX(fname)) + return + } + try { + leoFile = LEOFile(fx, this.DEBUGLEVEL) + } catch (ifx: InvalidFileException) { + if (finch.indexOf(",") > -1 && finch.indexOf(",") > -1) { + // init a blank workbook + this.initDefault() + + // map CSV into workbook + try { + val sheet = getWorkSheet(0) + sheet.readCSV(BufferedReader(FileReader(fx))) + return + } catch (e: Exception) { + throw WorkBookException( + "Error encountered importing CSV: $e", + WorkBookException.ILLEGAL_INIT_ERROR) + } + + } else { + throw ifx + } + + } + + if (leoFile!!.hasWorkBook()) { + this.initFromLeoFile(leoFile) + } else { + // total failure to load + Logger.logErr("Initializing WorkBookHandle failed: " + fname + + " does not contain a supported Excel WorkBook.") + throw InvalidFileException( + "$fname does not contian a supported Excel WorkBook.") + } + } + + /** + * Constructor which takes a ProgressListener which monitors the progress of + * creating a new Excel file. + * + * @param ProgressListener object which is monitoring progress of WorkBook read + */ + constructor(pn: ProgressListener) { + this.plist = pn + try { + val b = prototypeBook + val bbf = ByteBuffer.wrap(b) + bbf.order(ByteOrder.LITTLE_ENDIAN) + leoFile = LEOFile(bbf) + } catch (e: Exception) { + throw InvalidFileException( + "WorkBook could not be instantiated: $e") + } + + this.initFromLeoFile(leoFile) + } + + /** + * Constructor which takes the XLS file name + * + * + * and a ProgressListener which monitors the progress of reading the Excel file. + * + * @param String fname the name of the XLS file to read + * @param ProgressListener object which is monitoring progress of WorkBook read + */ + constructor(fname: String, pn: ProgressListener) { + this.plist = pn + this.initFromFile(File(fname)) + } + + /** + * For internal creation of a workbook handle from + * + * @param leo + */ + constructor(leo: LEOFile) { + this.initFromLeoFile(leo) + } + + /** + * init the new WorkBookHandle + */ + @Synchronized + protected fun initBytes(blockByteReader: BlockByteReader) { + this.initWorkBookFactory() + + if (plist != null) + factory!!.register(plist!!) // register progress notifier + factory!!.debugLevel = this.DEBUGLEVEL + + workBook = factory!! + .getWorkBook(blockByteReader, leoFile) as io.starter.formats.XLS.WorkBook + + if (dump_input != null) { + try { + dump_input!!.flush() + dump_input!!.close() + dump_input = null + } catch (e: Exception) { + } + + } + this.postLoad() + } + + /** + * Handles tasks that need to occur after workbook has been loaded + */ + internal fun postLoad() { + initHlinks() + initMerges() + workBook!!.initializeNames() // must initialize name expressions AFTER + // loading sheet records + workBook!!.mergeMSODrawingRecords() + workBook!!.initializeIndirectFormulas() + initPivotCache() // if any + } + + internal fun initMerges() { + val mergelookup = workBook!!.mergecelllookup + for (t in mergelookup.indices) { + val mc = mergelookup[t] as Mergedcells + mc.initCells(this) + } + } + + internal fun initHlinks() { + val hlinklookup = workBook!!.hlinklookup + for (t in hlinklookup.indices) { + val hl = hlinklookup[t] as Hlink + hl.initCells(this) + } + } + + /** + * reads in the pivot cache storage and parses the pivot cache records

                  + * pivot cache(s) are used by pivot tables as data source storage + */ + internal fun initPivotCache() { + if (leoFile!!.hasPivotCache()) { + val pc = PivotCache() // grab any pivot caches + try { + pc.init(leoFile!!.directoryArray!!, this) + workBook!!.pivotCache = pc + } catch (e: Exception) { + + } + + } + } + + /** + * Closes the WorkBook and releases resources. + */ + override fun close() { + try { + if (leoFile != null) + leoFile!!.shutdown() + leoFile = null + } catch (e: Exception) { + if (DEBUGLEVEL > 3) + Logger.logWarn("Closing Document: " + toString() + " failed: " + + e.toString()) + } + + if (workBook != null) + workBook!!.close() // clear out object refs to release memory + workBook = null + factory = null + name = null + sheethandles = null + // Runtime.getRuntime().gc(); + } + + @Throws(Throwable::class) + protected fun finalize() { + close() + } + + override fun reset() { + initFromFile(File(leoFile!!.fileName)) + } + + /** + * returns the handle to a WorkSheet by number. + * + * + * Sheet 0 is the first Sheet. + * + * @param index of worksheet (ie: 0) + * @return WorkSheetHandle the WorkSheet + * @throws WorkSheetNotFoundException if the specified WorkSheet is not found in the WorkBook. + */ + @Throws(WorkSheetNotFoundException::class) + override fun getWorkSheet(sheetnum: Int): WorkSheetHandle { + val st = workBook!!.getWorkSheetByNumber(sheetnum) + if (sheethandles!![st.sheetName] != null) + return sheethandles!![st.sheetName] + else { + val shth = WorkSheetHandle(st, this) + sheethandles!![st.sheetName] = shth + return shth + } + } + + /** + * returns the handle to a WorkSheet by name. + * + * @param String name of worksheet (ie: "Sheet1") + * @return WorkSheetHandle the WorkSheet + * @throws WorkSheetNotFoundException if the specified WorkSheet is not found in the WorkBook. + */ + @Throws(WorkSheetNotFoundException::class) + override fun getWorkSheet(handstr: String): WorkSheetHandle? { + if (sheethandles!![handstr] != null) { + return if (workBook!!.getWorkSheetByName(handstr) != null) + sheethandles!![handstr] + else + throw WorkSheetNotFoundException("$handstr not found") + } + if (factory != null) { + val bs = workBook!!.getWorkSheetByName(handstr) + if (bs != null) { + bs.workBook = this.workBook + val ret = WorkSheetHandle(bs, this) + sheethandles!![handstr] = ret + return ret + } else { + throw WorkSheetNotFoundException(handstr) + } + } + throw WorkSheetNotFoundException( + "Cannot find WorkSheet $handstr") + } + + /** + * Set Encoding mode of new Strings added to file. + * + * + * OpenXLS has 3 modes for handling the internal encoding of String data that is + * added to the file. + * + * + * OpenXLS can save space in the file if it knows that all characters in your + * String data can be represented with a single byte (Compressed.) + * + * + * If your String contains characters which need 2 bytes to represent (such as + * Eastern-language characters) then it needs to be stored in an uncompressed + * Unicode format. + * + * + * OpenXLS can either automatically detect the mode for each String, or you can + * set it explicitly. The auto mode is the most flexible but requires processing + * overhead. + * + * + * Default mode is WorkBookHandle.STRING_ENCODING_AUTO. + * + * + * Valid Modes Are: + * + * + * WorkBookHandle.STRING_ENCODING_AUTO Use if you are adding mixed Unicode and + * non-unicode Strings and can accept the performance hit -slowest String adds + * -optimal file size for mixed Strings WorkBookHandle.STRING_ENCODING_UNICODE + * Use if all of your new Strings are Unicode - faster than AUTO -faster than + * AUTO -largest file size WorkBookHandle.STRING_ENCODING_COMPRESSED Use if all + * of your new Strings are non-Unicode and can have high-bytes compressed + * -faster than AUTO -smallest file size + * + * @param int String Encoding Mode + */ + override fun setStringEncodingMode(mode: Int) { + workBook!!.setStringEncodingMode(mode) + } + + /** + * Set Duplicate String Handling Mode. + * + *

                  +     * The Duplicate String Mode determines the behavior of
                  +     * the String table when inserting new Strings.
                  +     *
                  +     * The String table shares a single entry for multiple
                  +     * Cells containing the same string.  When multiple Cells
                  +     * have the same value, they share the same underlying string.
                  +     *
                  +     * Changing the value of any one of the Cells will change
                  +     * the value for any Cells sharing that reference.
                  +     *
                  +     * For this reason, you need to determine
                  +     * the handling of new strings added to the sheet that
                  +     * are duplicates of strings already in the table.
                  +     *
                  +     * If you will be changing the values of these
                  +     * new Cells, you will need to set the Duplicate
                  +     * String Mode to ALLOWDUPES.  If the string table
                  +     * encounters a duplicate entry being added, it
                  +     * will insert a duplicate that can then be subsequently
                  +     * changed without affecting the other duplicate Cells.
                  +     *
                  +     * Valid Modes Are:
                  +     *
                  +     * WorkBookHandle.ALLOWDUPES - faster String inserts, larger file sizes,  changing Cells has no effect on dupe Cells
                  +     *
                  +     * WorkBookHandle.SHAREDUPES - slower inserts, dupe smaller file sizes, Cells share changes
                  +    
                  * + * + * @param int Duplicate String Handling Mode + */ + override fun setDupeStringMode(mode: Int) { + workBook!!.setDupeStringMode(mode) + } + + /** + * Copies an existing Chart to another WorkSheet + * + * @param chartname + * @param sheetname + */ + @Throws(ChartNotFoundException::class, WorkSheetNotFoundException::class) + override fun copyChartToSheet(chartname: String, sheetname: String) { + workBook!!.copyChartToSheet(chartname, sheetname) + } + + /** + * Copies an existing Chart to another WorkSheet + * + * @param chart + * @param sheet + */ + @Throws(ChartNotFoundException::class, WorkSheetNotFoundException::class) + override fun copyChartToSheet(chart: ChartHandle, sheet: WorkSheetHandle) { + workBook!!.copyChartToSheet(chart.title, sheet.sheetName) + } + + /** + * Copy (duplicate) a worksheet in the workbook and add it to the end of the + * workbook with a new name + * + * @param String the Name of the source worksheet; + * @param String the Name of the new (destination) worksheet; + * @return the new WorkSheetHandle + */ + @Throws(WorkSheetNotFoundException::class) + override fun copyWorkSheet(SourceSheetName: String, NewSheetName: String): WorkSheetHandle? { + try { + workBook!!.copyWorkSheet(SourceSheetName, NewSheetName) + } catch (e: Exception) { + throw WorkBookException("Failed to copy WorkSheet: " + + SourceSheetName + ": " + e.toString(), + WorkBookException.RUNTIME_ERROR) + } + + workBook!!.refTracker!!.clearPtgLocationCaches(NewSheetName) + // update the merged cells (requires a WBH, that's why it's + // here) + val wsh = this.getWorkSheet(NewSheetName) + if (wsh != null) { + val mc = wsh.mysheet!!.mergedCellsRecs + for (i in mc.indices) { + val mrg = mc[i] as Mergedcells + mrg?.initCells(this) + } + // now conditional formats + /* + * mc = wsh.getMysheet().getConditionalFormats(); for (int + * i=0;i this.numWorkSheets) + sheetpos = this.numWorkSheets + if (sheetpos < 0) + sheetpos = 0 + + val s = this.createWorkSheet(name) + s!!.tabIndex = sheetpos + return s + } + + /** + * Creates a new worksheet and places it at the end of the workbook. + * + * @param name the name of the newly created worksheet + * @return the new WorkSheetHandle + */ + override fun createWorkSheet(name: String): WorkSheetHandle? { + try { + this.getWorkSheet(name) + throw WorkBookException( + "Attempting to add worksheet with duplicate name. " + name + + " already exists in " + this.toString(), + WorkBookException.RUNTIME_ERROR) + } catch (ex: WorkSheetNotFoundException) { + // good! + } + + var bo: Boundsheet? = null + try { + val ois = ObjectInputStream( + ByteArrayInputStream(prototypeSheet!!)) + bo = ois.readObject() as Boundsheet + workBook!!.addBoundsheet(bo, null, name, null, false) + try { + val bs = this.getWorkSheet(name) + if (this.workBook!!.numWorkSheets == 1) { + bs!!.selected = true // it's the only sheet so select! + } else { + bs!!.selected = false + } + return bs + } catch (e: WorkSheetNotFoundException) { + Logger.logWarn("Creating New Sheet: $name failed: $e") + return null + } + + } catch (e: Exception) { + Logger.logWarn("Error loading prototype sheet: $e") + return null + } + + } + + fun initSharedFormulas() { + // TODO Auto-generated method stub + + } + + companion object { + /** + * A Writer to which a record dump should be written on input. This is used by + * the dumping code in WorkBookFactory. + */ + var dump_input: Writer? = null + + /** + * Format constant for BIFF8 (Excel '97-2007). + */ + val FORMAT_XLS = 100 + + /** + * Format constant for normal OOXML (Excel 2007). + */ + val FORMAT_XLSX = 101 + + /** + * Format constant for macro-enabled OOXML (Excel 2007). + */ + val FORMAT_XLSM = 102 + + /** + * Format constant for OOXML template (Excel 2007). + */ + val FORMAT_XLTX = 103 + + /** + * Format constant for macro-enabled OOXML template (Excel 2007). + */ + val FORMAT_XLTM = 104 + + private var protobook: ByteArray? = null + private var protochart: ByteArray? = null + private var protosheet: ByteArray? = null + var simpledateformat = java.text.SimpleDateFormat() // static + // to + // reuse + + /** + * How many recursion levels to allow formulas to be calculated before throwing + * a circular reference error + */ + var RECURSION_LEVELS_ALLOWED = 107 + + /** + * This is + */ + var CONVERTMULBLANKS = "deprecated" + + protected val prototypeBook: ByteArray? + @Throws(IOException::class) + get() { + if (protobook == null) + + protobook = ResourceLoader + .getBytesFromJar("/io/starter/OpenXLS/templates/prototysspe.ser") + + return protobook + } + + protected val prototypeSheet: ByteArray? + get() { + if (protosheet == null) + try { + val bookhandle = WorkBookHandle() + val book = bookhandle.workBook + val sheet = book!!.getWorkSheetByNumber(0) + protosheet = sheet.sheetBytes + } catch (e: Exception) { + throw RuntimeException(e) + } + + return protosheet + } + + protected val prototypeChart: ByteArray? + get() { + if (protochart == null) { + try { + val bookbytes = ResourceLoader + .getBytesFromJar("/io/starter/OpenXLS/templates/prototypechart.ser") + val chartBook = WorkBookHandle(bookbytes) + val ch = chartBook.charts[0] + protochart = ch.serialBytes + return protochart + } catch (e: IOException) { + Logger.logErr("Unable to get default chart bytes") + } + + } + return protochart + } + + /** + * Set the recursion levels allowed for formulas calculated in this workbook + * before a circular reference is thrown. + * + * + * Default setting is 250 levels of recursion + * + * @param recursion_allowed + */ + fun setFormulaRecursionLevels(recursion_allowed: Int) { + RECURSION_LEVELS_ALLOWED = recursion_allowed + } + } +} +/** + * Constructor which takes the XLS file name( + * + * @param String filePath the name of the XLS file to read + */ \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/WorkSheet.java b/src/main/java/io/starter/OpenXLS/WorkSheet.kt similarity index 75% rename from src/main/java/io/starter/OpenXLS/WorkSheet.java rename to src/main/java/io/starter/OpenXLS/WorkSheet.kt index 9abb6f2..22cf8a9 100644 --- a/src/main/java/io/starter/OpenXLS/WorkSheet.java +++ b/src/main/java/io/starter/OpenXLS/WorkSheet.kt @@ -20,210 +20,285 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.OpenXLS; +package io.starter.OpenXLS -import io.starter.formats.XLS.*; - -import java.util.List; +import io.starter.formats.XLS.* /** * An interface representing an OpenXLS WorkSheet * * @see WorkSheetHandle */ -public interface WorkSheet extends Handle { - - WorkBook getWorkBook(); +interface WorkSheet : Handle { - Mergedcells getMergedCellsRec(); + val workBook: WorkBook - void addChart(byte[] serialchart, String name); + val mergedCellsRec: Mergedcells /** * Get the first row on the Worksheet * * @return the Minimum Row Number on the Worksheet */ - int getFirstRow(); + val firstRow: Int /** * Get the first column on the Worksheet * * @return the Minimum Column Number on the Worksheet */ - int getFirstCol(); + val firstCol: Int /** * Get the last row on the Worksheet * * @return the Maximum Row Number on the Worksheet */ - int getLastRow(); + val lastRow: Int /** * Get the last column on the Worksheet * * @return the Maximum Column Number on the Worksheet */ - int getLastCol(); - - /* set protection on sheet - - @param boolean whether to protect the sheet - */ - void setProtected(boolean b); + val lastCol: Int /** - * set whether this sheet is VERY hidden opening the file. - *

                  - * VERY hidden means users will not be able to unhide - * the sheet without using VB code. + * get whether this sheet is selected upon opening the file. * - * @param boolean b hidden state + * @return boolean b selected state */ - void setVeryHidden(boolean b); - /** - * get whether this sheet is selected upon opening the file. + * set whether this sheet is selected upon + * opening the file. * - * @return boolean b selected state + * @param boolean b selected value */ - boolean getSelected(); + var selected: Boolean /** * get whether this sheet is hidden from the user opening the file. * * @return boolean b hidden state */ - boolean getHidden(); - /** * set whether this sheet is hidden from the user opening the file. - *

                  + * + * * if the sheet is selected, the API will set the first * visible sheet to selected as you cannot have your selected sheet * be hidden. - *

                  + * + * * to override this behavior, set your desired sheet to selected * after calling this method. * * @param boolean b hidden state */ - void setHidden(boolean b); - - /** - * set this WorkSheet as the first visible tab on the left - */ - void setFirstVisibleTab(); + var hidden: Boolean /** * get the tab display order of this Worksheet - *

                  + * + * * this is a zero based index with zero representing * the left-most WorkSheet tab. * * @return int idx the index of the sheet tab */ - int getTabIndex(); - /** * set the tab display order of this Worksheet - *

                  + * + * * this is a zero based index with zero representing * the left-most WorkSheet tab. * * @param int idx the new index of the sheet tab */ - void setTabIndex(int idx); + var tabIndex: Int /** - * set whether this sheet is selected upon - * opening the file. + * returns all of the Columns in this WorkSheet * - * @param boolean b selected value + * @return ColHandle[] Columns */ - void setSelected(boolean b); + val columns: Array /** - * returns the Column at the index position + * returns a Vector of Column names * - * @return ColHandle the Column + * @return CompatibleVector column names */ - ColHandle getCol(int clnum) throws ColumnNotFoundException; + val colNames: List<*> /** - * returns the Column at the named position + * returns a Vector of Row numbers * - * @return ColHandle the Column + * @return CompatibleVector row numbers */ - ColHandle getCol(String name) - throws ColumnNotFoundException; + val rowNums: List<*> /** - * returns all of the Columns in this WorkSheet + * get an array of all RowHandles for + * this WorkSheet * - * @return ColHandle[] Columns + * @return RowHandle[] all Rows on this WorkSheet */ - ColHandle[] getColumns(); + val rows: Array /** - * returns a Vector of Column names + * Returns the number of rows in this WorkSheet * - * @return CompatibleVector column names + * @return int Number of Rows on this WorkSheet */ - List getColNames(); + val numRows: Int /** - * returns a Vector of Row numbers + * Returns the number of Columns in this WorkSheet * - * @return CompatibleVector row numbers + * @return int Number of Cols on this WorkSheet */ - List getRowNums(); + val numCols: Int /** - * get an a RowHandle for - * this WorkSheet by number + * Returns the low-level OpenXLS Sheet. * - * @param int row number to return - * @return RowHandle a Row on this WorkSheet + * @return io.starter.formats.XLS.Sheet */ - RowHandle getRow(int t) throws RowNotFoundException; + val sheet: Sheet /** - * get an array of all RowHandles for - * this WorkSheet + * Returns the name of the Sheet. * - * @return RowHandle[] all Rows on this WorkSheet + * @return String Sheet Name */ - RowHandle[] getRows(); + /** + * Set the name of the Worksheet. This method will change + * the name on the Worksheet's tab as displayed in the + * WorkBook, as well as all programmatic and internal + * references to the name. + * + * + * This change takes effect immediately, so all attempts + * to reference the Worksheet by its previous name will + * fail. + * + * @param String the new name for the Worksheet + */ + var sheetName: String /** - * Returns the number of rows in this WorkSheet + * Returns the Serialized bytes for this WorkSheet. * - * @return int Number of Rows on this WorkSheet + * + * The output of this method can be used to insert + * a copy of this WorkSheet into another WorkBook + * using the WorkBook.addWorkSheet(byte[] serialsheet, String NewSheetName) + * method. + * + * @return byte[] the WorkSheet's Serialized bytes + * @see WorkBook.addWorkSheet */ - int getNumRows(); + val serialBytes: ByteArray /** - * Returns the number of Columns in this WorkSheet + * Returns all CellHandles defined on this WorkSheet. * - * @return int Number of Cols on this WorkSheet + * @return Cell[] - the array of Cells in the Sheet + */ + val cells: Array + + /** + * Get the text for the Footer printed at the bottom + * of the Worksheet + * + * @return String footer text + */ + /** + * Set the text for the Footer printed at the bottom + * of the Worksheet + * + * @param String footer text + */ + var footerText: String + + /** + * Get the text for the Header printed at the top + * of the Worksheet + * + * @return String header text + */ + /** + * Set the text for the Header printed at the top + * of the Worksheet + * + * @param String header text + */ + var headerText: String + + fun addChart(serialchart: ByteArray, name: String) + + /* set protection on sheet + + @param boolean whether to protect the sheet + */ + fun setProtected(b: Boolean) + + /** + * set whether this sheet is VERY hidden opening the file. + * + * + * VERY hidden means users will not be able to unhide + * the sheet without using VB code. + * + * @param boolean b hidden state + */ + fun setVeryHidden(b: Boolean) + + /** + * set this WorkSheet as the first visible tab on the left + */ + fun setFirstVisibleTab() + + /** + * returns the Column at the index position + * + * @return ColHandle the Column + */ + @Throws(ColumnNotFoundException::class) + fun getCol(clnum: Int): ColHandle + + /** + * returns the Column at the named position + * + * @return ColHandle the Column */ - int getNumCols(); + @Throws(ColumnNotFoundException::class) + fun getCol(name: String): ColHandle + + /** + * get an a RowHandle for + * this WorkSheet by number + * + * @param int row number to return + * @return RowHandle a Row on this WorkSheet + */ + @Throws(RowNotFoundException::class) + fun getRow(t: Int): RowHandle /** * Remove a Cell from this WorkSheet. * * @param CellHandle to remove */ - void removeCell(CellHandle celldel); + fun removeCell(celldel: CellHandle) /** * Remove a Cell from this WorkSheet. * * @param String celladdr - the Address of the Cell to remove */ - void removeCell(String celladdr); + fun removeCell(celladdr: String) /** * Remove a Row and all associated Cells from @@ -231,7 +306,8 @@ ColHandle getCol(String name) * * @param int rownum - the number of the row to remove */ - void removeRow(int rownum) throws RowNotFoundException; + @Throws(RowNotFoundException::class) + fun removeRow(rownum: Int) /** * Remove a Row and all associated Cells from @@ -241,8 +317,8 @@ ColHandle getCol(String name) * @param int rownum - the number of the row to remove * @param boolean shiftrows - true will shift all lower rows up one. */ - void removeRow(int rownum, boolean shiftrows) - throws RowNotFoundException; + @Throws(RowNotFoundException::class) + fun removeRow(rownum: Int, shiftrows: Boolean) /** * Remove a Column and all associated Cells from @@ -250,48 +326,22 @@ void removeRow(int rownum, boolean shiftrows) * * @param String colstr - the name of the column to remove */ - void removeCol(String colstr) - throws ColumnNotFoundException; + @Throws(ColumnNotFoundException::class) + fun removeCol(colstr: String) /** * Remove a Column and all associated Cells from * this WorkSheet. - *

                  + * + * * Optionally shift all cols to the right * of this column to the left by one. * * @param String colstr - the name of the column to remove * @param boolean shiftcols - true will shift following cols */ - void removeCol(String colstr, boolean shiftcols) - throws ColumnNotFoundException; - - /** - * Returns the low-level OpenXLS Sheet. - * - * @return io.starter.formats.XLS.Sheet - */ - Sheet getSheet(); - - /** - * Returns the name of the Sheet. - * - * @return String Sheet Name - */ - String getSheetName(); - - /** - * Returns the Serialized bytes for this WorkSheet. - *

                  - * The output of this method can be used to insert - * a copy of this WorkSheet into another WorkBook - * using the WorkBook.addWorkSheet(byte[] serialsheet, String NewSheetName) - * method. - * - * @return byte[] the WorkSheet's Serialized bytes - * @see WorkBook.addWorkSheet(byte[] serialsheet, String NewSheetName) - */ - byte[] getSerialBytes(); + @Throws(ColumnNotFoundException::class) + fun removeCol(colstr: String, shiftcols: Boolean) /** * Set the Object value of the Cell at the given address. @@ -299,10 +349,10 @@ void removeCol(String colstr, boolean shiftcols) * @param String Cell Address (ie: "D14") * @param Object new Cell Object value * @throws CellNotFoundException is thrown if there is - * no existing Cell at the specified address. + * no existing Cell at the specified address. */ - void setVal(String address, Object val) - throws CellNotFoundException, CellTypeMismatchException; + @Throws(CellNotFoundException::class, CellTypeMismatchException::class) + fun setVal(address: String, `val`: Any) /** * Set the double value of the Cell at the given address @@ -312,10 +362,10 @@ void setVal(String address, Object val) * @param address * @param d * @throws io.starter.formats.XLS.CellNotFoundException is - * thrown if there is no existing Cell at the specified address. + * thrown if there is no existing Cell at the specified address. */ - void setVal(String address, double d) - throws CellNotFoundException, CellTypeMismatchException; + @Throws(CellNotFoundException::class, CellTypeMismatchException::class) + fun setVal(address: String, d: Double) /** * Set the String value of the Cell at the given address @@ -324,22 +374,8 @@ void setVal(String address, double d) * @param String new Cell String value * @throws CellNotFoundException is thrown if there is no existing Cell at the specified address. */ - void setVal(String address, String s) - throws CellNotFoundException, CellTypeMismatchException; - - /** - * Set the name of the Worksheet. This method will change - * the name on the Worksheet's tab as displayed in the - * WorkBook, as well as all programmatic and internal - * references to the name. - *

                  - * This change takes effect immediately, so all attempts - * to reference the Worksheet by its previous name will - * fail. - * - * @param String the new name for the Worksheet - */ - void setSheetName(String name); + @Throws(CellNotFoundException::class, CellTypeMismatchException::class) + fun setVal(address: String, s: String) /** * Set the int value of the Cell at the given address @@ -348,25 +384,28 @@ void setVal(String address, String s) * @param int new Cell int value * @throws io.starter.formats.XLS.CellNotFoundException is thrown if there is no existing Cell at the specified address. */ - void setVal(String address, int i) - throws CellNotFoundException, CellTypeMismatchException; + @Throws(CellNotFoundException::class, CellTypeMismatchException::class) + fun setVal(address: String, i: Int) /** * Get the Object value of a Cell. - *

                  + * + * * Numeric Cell values will return as type Long, Integer, or Double. * String Cell values will return as type String. * * @return the value of the Cell as an Object. * @throws CellNotFoundException is thrown if there is - * no existing Cell at the specified address. + * no existing Cell at the specified address. */ - Object getVal(String address) throws CellNotFoundException; + @Throws(CellNotFoundException::class) + fun getVal(address: String): Any /** * Insert a blank row into the worksheet. * Shift all rows below the cell down one. - *

                  + * + * * Adding new cells to non-existent rows will * automatically create new rows in the file, * This method is only necessary to "move" existing cells @@ -374,50 +413,51 @@ void setVal(String address, int i) * * @param rownum the rownumber to insert */ - void insertRow(int rownum); + fun insertRow(rownum: Int) /** * Insert a blank row into the worksheet. * Shift all rows below the cell down one. - *

                  + * + * * Adding new cells to non-existent rows will * automatically create new rows in the file, - *

                  + * + * * After calling this method, setVal() can be used on the * newly created cells to update with new values. * * @param rownum the rownumber to insert * @param whether to shift down existing Cells */ - void insertRow(int rownum, boolean shiftrows); - - int ROW_KEEP = 0; - - int ROW_INSERT = 1; + fun insertRow(rownum: Int, shiftrows: Boolean) /** * Insert a blank row into the worksheet. * Shift all rows below the cell down one. - *

                  + * + * * Adding new cells to non-existent rows will * automatically create new rows in the file, - *

                  + * + * * After calling this method, setVal() can be used on the * newly created cells to update with new values. * * @param rownum the rownumber to insert * @param whether to shift down existing Cells */ - void insertRow( - int rownum, - int copyrow, - int flag, - boolean shiftrows); + fun insertRow( + rownum: Int, + copyrow: Int, + flag: Int, + shiftrows: Boolean) /** * Insert a blank column into the worksheet. * Shift all columns to the right of the cell over one. - *

                  + * + * * Adding new cells to non-existent columns will * automatically create new Columns in the file, * This method is only necessary to "move" existing cells @@ -425,30 +465,37 @@ void insertRow( * * @param colstr the Column string to insert */ - void insertCol(String colnum); + fun insertCol(colnum: String) /** * When adding a new Cell to the sheet, OpenXLS can * automatically copy the formatting from the Cell directly * above the inserted Cell. - *

                  + * + * * ie: if set to true, newly added Cell D19 would take its formatting * from Cell D18. - *

                  + * + * * Default is false - *

                  - *

                  - *

                  + * + * + * + * + * + * * boolean copy the formats from the prior Cell */ - void setCopyFormatsFromPriorWhenAdding(boolean f); + fun setCopyFormatsFromPriorWhenAdding(f: Boolean) /** * Add a Cell with the specified value to a WorkSheet. - *

                  + * + * * This method determines the Cell type based on * type-compatibility of the value. - *

                  + * + * * In other words, if the Object cannot be converted * safely to a Numeric Object type, then it is treated * as a String and a new String value is added to the @@ -458,14 +505,16 @@ void insertRow( * @param int row the row of the new Cell * @param int col the column of the new Cell */ - CellHandle add(Object obj, int row, int col); + fun add(obj: Any, row: Int, col: Int): CellHandle /** * Add a Cell with the specified value to a WorkSheet. - *

                  + * + * * This method determines the Cell type based on * type-compatibility of the value. - *

                  + * + * * In other words, if the Object cannot be converted * safely to a Numeric Object type, then it is treated * as a String and a new String value is added to the @@ -474,14 +523,16 @@ void insertRow( * @param obj the value of the new Cell * @param address the address of the new Cell */ - CellHandle add(Object obj, String address); + fun add(obj: Any, address: String): CellHandle /** * Add a java.sql.Date Cell to a WorkSheet. - *

                  + * + * * You must specify a formatting pattern for the * new date, or null for the default ("m/d/yy h:mm".) - *

                  + * + * * valid date format patterns * "m/d/y" * "d-mmm-yy" @@ -500,22 +551,15 @@ void insertRow( * @param address the address of the new java.sql.Date Cell * @param formatting pattern the address of the new java.sql.Date Cell */ - CellHandle add( - java.util.Date dt, - String address, - String fmt); + fun add( + dt: java.util.Date, + address: String, + fmt: String): CellHandle /** * Remove this WorkSheet from the WorkBook */ - void remove(); - - /** - * Returns all CellHandles defined on this WorkSheet. - * - * @return Cell[] - the array of Cells in the Sheet - */ - CellHandle[] getCells(); + fun remove() /** * Returns a FormulaHandle for working with @@ -524,8 +568,8 @@ CellHandle add( * @param addr the address of the Cell * @throws FormulaNotFoundException is thrown if there is no existing formula at the specified address. */ - FormulaHandle getFormula(String addr) - throws FormulaNotFoundException, CellNotFoundException; + @Throws(FormulaNotFoundException::class, CellNotFoundException::class) + fun getFormula(addr: String): FormulaHandle /** * Returns a CellHandle for working with @@ -534,8 +578,8 @@ FormulaHandle getFormula(String addr) * @param addr the address of the Cell * @throws CellNotFoundException is thrown if there is no existing Cell at the specified address. */ - CellHandle getCell(String addr) - throws CellNotFoundException; + @Throws(CellNotFoundException::class) + fun getCell(addr: String): CellHandle /** * Returns a CellHandle for working with @@ -545,8 +589,8 @@ CellHandle getCell(String addr) * @param int Col the integer col of the Cell * @throws CellNotFoundException is thrown if there is no existing Cell at the specified address. */ - CellHandle getCell(int row, int col) - throws CellNotFoundException; + @Throws(CellNotFoundException::class) + fun getCell(row: Int, col: Int): CellHandle /** * Move a cell on this WorkSheet. @@ -554,45 +598,20 @@ CellHandle getCell(int row, int col) * @param CellHandle c - the cell to be moved * @param String celladdr - the destination address of the cell */ - void moveCell(CellHandle c, String addr) - throws CellPositionConflictException; + @Throws(CellPositionConflictException::class) + fun moveCell(c: CellHandle, addr: String) /** - * Get the text for the Footer printed at the bottom - * of the Worksheet + * Returns the name of this Sheet. * - * @return String footer text + * @see java.lang.Object.toString */ - String getFooterText(); + override fun toString(): String - /** - * Get the text for the Header printed at the top - * of the Worksheet - * - * @return String header text - */ - String getHeaderText(); + companion object { - /** - * Set the text for the Header printed at the top - * of the Worksheet - * - * @param String header text - */ - void setHeaderText(String t); - - /** - * Set the text for the Footer printed at the bottom - * of the Worksheet - * - * @param String footer text - */ - void setFooterText(String t); + val ROW_KEEP = 0 - /** - * Returns the name of this Sheet. - * - * @see java.lang.Object#toString() - */ - String toString(); + val ROW_INSERT = 1 + } } \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/WorkSheetHandle.java b/src/main/java/io/starter/OpenXLS/WorkSheetHandle.java deleted file mode 100644 index 56ccad1..0000000 --- a/src/main/java/io/starter/OpenXLS/WorkSheetHandle.java +++ /dev/null @@ -1,2933 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.OpenXLS; - -import io.starter.formats.XLS.WorkBook; -import io.starter.formats.XLS.*; -import io.starter.formats.XLS.charts.Chart; -import io.starter.formats.XLS.formulas.GenericPtg; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.formats.XLS.formulas.PtgRef; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; - -import java.io.*; -import java.sql.Timestamp; -import java.util.*; - -/** - * The WorkSheetHandle provides a handle to a Worksheet within an XLS file
                  - * and includes convenience methods for working with the Cell values within the - * sheet.
                  - *
                  - * for example:
                  - *
                  - *

                  WorkBookHandle book = new WorkBookHandle("testxls.xls");
                  - * WorkSheetHandle sheet1 = book.getWorkSheet("Sheet1");
                  - * CellHandle cell = sheet1.getCell("B22");
                  - * - *
                  - * to add a cell:
                  - *
                  - * CellHandle cell = sheet1.add("Hello World","C22");
                  - * - *
                  - * to add a numeric cell:
                  - *
                  - * CellHandle cell = sheet1.add(Integer.valueOf(120),"C23");
                  - * - *
                  - * to add a formula cell:
                  - *
                  - * CellHandle cell = sheet1.add("=PI()","C24");
                  - * - *

                  - *
                  - * - * @see WorkSheet - * @see WorkBookHandle - * @see CellHandle - */ -public class WorkSheetHandle implements Handle { - - private Boundsheet mysheet; - private WorkBook mybook; - WorkBookHandle wbh; - private int DEBUGLEVEL = 0; - private Hashtable dateFormats = new Hashtable(); - private boolean cache = true; // 20080917 KSC: set var for caching, default to true [BugTracker 1862] - // public Map cellhandles = new HashMap(); - - public void addChart(byte[] serialchart, String name, short[] coords) { - mysheet.addChart(serialchart, name, coords); - } - - /** - * Get the first row on the Worksheet - * - * @return the Minimum Row Number on the Worksheet - */ - public int getFirstRow() { - return mysheet.getMinRow(); - } - - /** - * Get the first column on the Worksheet - * - * @return the Minimum Column Number on the Worksheet - */ - public int getFirstCol() { - return mysheet.getMinCol(); - } - - /** - * Get the last row on the Worksheet - * - * @return the Maximum Row Number on the Worksheet - */ - public int getLastRow() { - return mysheet.getMaxRow(); - } - - /** - * Get the last column on the Worksheet - * - * @return the Maximum Column Number on the Worksheet - */ - public int getLastCol() { - return mysheet.getMaxCol(); - } - - /** - * Sets whether the worksheet is protected. If protect is - * true, the worksheet will be protected and the password will be - * set to password. If it's false, the worksheet will - * be unprotected and the password will be removed. - * - * @param protect whether the worksheet should be protected - * @param password the password to set if protect is true. ignored when - * protect is false. - * @throws WorkBookException never. This used to be thrown when unprotecting if the password - * was incorrect. - */ - public void setProtected(boolean protect, String password) throws WorkBookException { - SheetProtectionManager protector = mysheet.getProtectionManager(); - - // we need to check if this password can be used to unprotect... - // otherwise it is totally insecure... - String oldpass = protector.getPassword(); - - Password pss = new Password(); - pss.setPassword(password); - String passcheck = pss.getPasswordHashString(); - - if (oldpass != null) { - if (!oldpass.equals(passcheck) && oldpass != "0000") { - throw new WorkBookException("Incorrect Password Attempt to Unprotect Worksheet.", - WorkBookException.SHEETPROTECT_INCORRECT_PASSWORD); - } - } - protector.setProtected(protect); - protector.setPassword(protect ? password : null); - } - - /** - * Sets whether the worksheet is protected. - * - * @param protect whether worksheet protection should be enabled - */ - public void setProtected(boolean protect) { - mysheet.getProtectionManager().setProtected(protect); - } - - /** - * Sets the password used to unlock the sheet when it is protected. - * - * @param password the clear text of the password to be applied or null to remove the - * existing password - */ - public void setProtectionPassword(String password) { - mysheet.getProtectionManager().setPassword(password); - } - - /** - * Sets the password used to unlock the sheet when it is protected. This method - * is useful in combination with {@link #getHashedProtectionPassword} to copy - * the password from one worksheet to another. - * - * @param hash the hash of the protection password to be applied or null to - * remove the existing password - */ - public void setProtectionPasswordHashed(String hash) { - mysheet.getProtectionManager().setPasswordHashed(hash); - } - - /** - * Gets the hash of the sheet protection password. This method returns the - * hashed password as stored in the file. It has been passed through a one-way - * hash function. It is therefore not possible to recover the actual password. - * You can, however, use {@link #setProtectionPasswordHashed} to apply the same - * password to another worksheet. - * - * @return the password hash or "0000" if the sheet doesn't have a password - */ - public String getHashedProtectionPassword() { - return mysheet.getProtectionManager().getPassword(); - } - - /** - * Returns whether the sheet is protected. Note that this is separate from - * whether the sheet has a protection password. It can be protected without a - * password or have a password but not be protected. - * - * @return whether protection is enabled for the sheet - */ - public boolean getProtected() { - return mysheet.getProtectionManager().getProtected(); - } - - /** - * Checks whether the given password matches the protection password. - * - * @param guess the password to be checked against the stored hash - * @return whether the given password matches the stored hash - */ - public boolean checkProtectionPassword(String guess) { - return mysheet.getProtectionManager().checkPassword(guess); - } - - /** - * Sets the worksheet enhanced protection option - * - * @param int protectionOption - * @see WorkBookHandle.iprot options - */ - public void setEnhancedProtection(int protectionOption, boolean set) { - mysheet.getProtectionManager().setProtected(protectionOption, set); - } - - /** - * returns true if the indicated Enhanced Protection Setting is turned on - * - * @param protectionOption - * @return boolean true if the indicated Enhanced Protection Setting is turned - * on - * @see WorkBookHandle.iprot options - */ - public boolean getEnhancedProtection(int protectionOption) { - return mysheet.getProtectionManager().getProtected(protectionOption); - } - - /** - * set whether this sheet is VERY hidden opening the file. - *

                  - * VERY hidden means users will not be able to unhide the sheet without using VB - * code. - * - * @param boolean b hidden state - */ - public void setVeryHidden(boolean b) { - int h = 0; - if (b) - h = Boundsheet.VERY_HIDDEN; - mysheet.setHidden(h); - int t = mysheet.getSheetNum(); - try { // set the next sheet selected... - Boundsheet s2 = mybook.getWorkSheetByNumber(t + 1); - s2.setSelected(true); - } catch (WorkSheetNotFoundException e) { - } - } - - /** - * get whether this sheet is selected upon opening the file. - * - * @return boolean b selected state - */ - public boolean getSelected() { - return mysheet.selected(); - } - - /** - * get whether this sheet is hidden from the user opening the file. - * - * @return boolean b hidden state - */ - public boolean getHidden() { - return mysheet.getHidden(); - } - - /** - * return the 'veryhidden' state of the sheet - * - * @return - */ - public boolean getVeryHidden() { - return mysheet.getVeryHidden(); - } - - /** - * set whether this sheet is hidden from the user opening the file. - *

                  - * if the sheet is selected, the API will set the first visible sheet to - * selected as you cannot have your selected sheet be hidden. - *

                  - * to override this behavior, set your desired sheet to selected after calling - * this method. - * - * @param boolean b hidden state - */ - public void setHidden(boolean b) { - int h = 0; - if (b) - h = Boundsheet.HIDDEN; - mysheet.setHidden(h); - if (mysheet.getSheetNum() == 0) { - try { - Boundsheet s2 = mybook.getWorkSheetByNumber(mysheet.getSheetNum() + 1); - mybook.setFirstVisibleSheet(s2); - } catch (WorkSheetNotFoundException e) { - } - } - if (mysheet.selected()) { - try { // set the next sheet selected... - int x = 1; - Boundsheet s2 = mybook.getWorkSheetByNumber(mysheet.getSheetNum() + x); - while (s2.getHidden()) - s2 = mybook.getWorkSheetByNumber(mysheet.getSheetNum() + x++); - s2.setSelected(true); - } catch (WorkSheetNotFoundException e) { - } - } - } - - /** - * set this WorkSheet as the first visible tab on the left - */ - public void setFirstVisibleTab() { - mysheet.getWorkBook().setFirstVisibleSheet(mysheet); - } - - /** - * get the tab display order of this Worksheet - *

                  - * this is a zero based index with zero representing the left-most WorkSheet - * tab. - * - * @return int idx the index of the sheet tab - */ - public int getTabIndex() { - return mysheet.getSheetNum(); - } - - /** - * set the tab display order of this Worksheet - *

                  - * this is a zero based index with zero representing the left-most WorkSheet - * tab. - * - * @param int idx the new index of the sheet tab - */ - public void setTabIndex(int idx) { - mysheet.getWorkBook().changeWorkSheetOrder(mysheet, idx); - } - - /** - * set whether this sheet is selected upon opening the file. - * - * @param boolean b selected value - */ - public void setSelected(boolean b) { - mysheet.setSelected(b); - } - - /** - * returns the ColHandle for the column at index position the column index is - * zero based ie: column A = 0 - * - * @return ColHandle the Column - */ - public ColHandle getCol(int clnum) throws ColumnNotFoundException { - Colinfo ci = mysheet.getColInfo(clnum); - ColHandle mycol; - if (ci == null || !ci.isSingleCol()) { - try { - if (ci == null) { - ci = mysheet.createColinfo(clnum, clnum); - } else { - ci = mysheet.createColinfo(clnum, clnum, ci); - } - mycol = new ColHandle(ci, this); - - } catch (Exception e) { - throw new ColumnNotFoundException("Unable to getCol for col number " + clnum + " " + e.toString()); - } - } else { - mycol = new ColHandle(ci, this); // usual case - } - return mycol; - } - - /** - * adds the column (col1st, colLast) and returns the new ColHandle - * - * @param c1st - * @param clast - * @return - * @deprecated use addCol(int) - */ - @Deprecated - public ColHandle addCol(int c1st, int clast) { - Colinfo ci = mysheet.createColinfo(c1st, clast); - ColHandle mycol = new ColHandle(ci, this); - return mycol; - } - - /** - * adds the column (col1st, colLast) and returns the new ColHandle - * - * @param colNum, zero based number of the column - * @return ColHandle - */ - public ColHandle addCol(int colNum) { - Colinfo ci = mysheet.createColinfo(colNum, colNum); - ColHandle mycol = new ColHandle(ci, this); - return mycol; - } - - /** - * returns the Column at the named position - * - * @return ColHandle the Column - */ - public ColHandle getCol(String name) throws ColumnNotFoundException { - return this.getCol(ExcelTools.getIntVal(name)); - } - - /** - * returns all of the Columns in this WorkSheet - * - * @return ColHandle[] Columns - */ - public ColHandle[] getColumns() { - List columns = new CompatibleVector(); - - for (Colinfo c : mysheet.getColinfos()) { - try { - int start = c.getColFirst(); - int end = c.getColLast(); - for (int i = start; i <= end; i++) { - try { - columns.add(this.getCol(i)); - } catch (ColumnNotFoundException e) { - } - } - } catch (Exception ex) { - } - } - - return (ColHandle[]) columns.toArray(new ColHandle[columns.size()]); - } - - /** - * returns a List of Column names - * - * @return List column names - */ - public List getColNames() { - return mysheet.getColNames(); - } - - /** - * returns a List of Row numbers - * - * @return List of row numbers - */ - public List getRowNums() { - return mysheet.getRowNums(); - } - - /** - * returns the RowHandle for the row at index position - *

                  - * the row index is zero based ie: Excel row 1 = 0 - * - * @param int row number to return - * @return RowHandle a Row on this WorkSheet - */ - public RowHandle getRow(int t) throws RowNotFoundException { - Row x = mysheet.getRowByNumber(t); - if (x == null) - throw new RowNotFoundException("Row " + t + " not found in :" + this.getSheetName()); - return new RowHandle(x, this); - } - - /** - * get an array of all RowHandles for this WorkSheet - * - * @return RowHandle[] all Rows on this WorkSheet - */ - public RowHandle[] getRows() { - Row[] rs = mysheet.getRows(); - RowHandle[] ret = new RowHandle[rs.length]; - for (int t = 0; t < rs.length; t++) { - ret[t] = new RowHandle(rs[t], this); - } - return ret; - } - - /** - * get an array of BIFFREC Rows - * - * @return RowHandle[] all Rows on this WorkSheet - */ - public Map getRowMap() { - return mysheet.getRowMap(); - } - - /** - * Returns whether a Cell exists in the WorkSheet. - * - * @param String the address of the Cell to check for - * @return boolean whether the Cell exists - */ - boolean hasCell(String addr) { - try { - this.getCell(addr); - return true; - } catch (CellNotFoundException e) { - return false; - } - } - - /** - * Returns the number of rows in this WorkSheet - * - * @return int Number of Rows on this WorkSheet - */ - public int getNumRows() { - return this.mysheet.getNumRows(); - } - - /** - * Returns the number of Columns in this WorkSheet - * - * @return int Number of Cols on this WorkSheet - */ - public int getNumCols() { - return this.mysheet.getNumCols(); - } - - /** - * Remove a Cell from this WorkSheet. - * - * @param CellHandle to remove - */ - public void removeCell(CellHandle celldel) { - mysheet.removeCell(celldel.getCell()); - } - - /** - * removes an Image from the Spreadsheet - *

                  - *

                  - * Jan 22, 2010 - * - * @param img - */ - public void removeImage(ImageHandle img) { - mysheet.removeImage(img); - img.remove(); - } - - /** - * Remove a Cell from this WorkSheet. - * - * @param String celladdr - the Address of the Cell to remove - */ - public void removeCell(String celladdr) { - mysheet.removeCell(celladdr.toUpperCase()); - } - - /** - * Remove a Row and all associated Cells from this WorkSheet. - * - * @param int - * rownum - the number of the row to remove not used public void - * removeRow(int rownum) throws RowNotFoundException{ - * mysheet.removeRow(rownum); } - */ - - /** - * Remove a Row and all associated Cells from this WorkSheet. Optionally shift - * all rows below target row up one. - * - * @param int rownum - the number of the row to remove - * @param boolean shiftrows - true will shift all lower rows up one. - */ - public void removeRow(int rownum, boolean shiftrows) throws RowNotFoundException { - if (shiftrows) - removeRow(rownum); - else - removeRow(rownum, WorkSheetHandle.ROW_DELETE_NO_REFERENCE_UPDATE); - } - - /** - * Remove a Row and all associated Cells from this WorkSheet. - * - * @param int rownum - the number of the row to remove uses default row deletion - * rules regarding updating references - */ - public void removeRow(int rownum) throws RowNotFoundException { - removeRow(rownum, WorkSheetHandle.ROW_DELETE); - } - - /** - * Remove all cells and formatting from a row within this WorkSheet. Has no - * other affect upon the workbook - * - * @param int rownum - the number of the row contents to remove - */ - public void removeRowContents(int rownum) throws RowNotFoundException { - mysheet.removeRowContents(rownum); - } - - /** - * Remove a Row and all associated Cells from this WorkSheet. - * - * @param int rownum - the number of the row to remove - * @param int flag - controls whether row deletions updates references as well - * ... - */ - public void removeRow(int rownum, int flag) throws RowNotFoundException { - - /* TODO: deal with merges! */ - mysheet.removeRows(rownum, 1, true); - - // Delete chart series IF SERIES ARE ROW-BASED -- do before updateReferences - List charts = this.mysheet.getCharts(); - for (int i = 0; i < charts.size(); i++) { - String sht = GenericPtg.qualifySheetname(this.getSheetName()); - Chart c = (Chart) charts.get(i); - HashMap seriesmap = c.getSeriesPtgs(); - Iterator ii = seriesmap.keySet().iterator(); - while (ii.hasNext()) { - io.starter.formats.XLS.charts.Series s = (io.starter.formats.XLS.charts.Series) ii.next(); - Ptg[] ptgs = (Ptg[]) seriesmap.get(s); - PtgRef pr; - String cursheet; - int[] rc; - for (int j = 0; j < ptgs.length; j++) { - try { - pr = (PtgRef) ptgs[j]; - cursheet = pr.getSheetName(); - rc = pr.getIntLocation(); - if (rc[1] != rc[3] && sht.equalsIgnoreCase(cursheet)) { // series are in rows, if existing - // series fall within deleted row - if ((rc[0]) == rownum - 1) { - c.removeSeries(j); - break; // got it - } - } else - break; // isn't row-based so split - } catch (Exception e) { - continue; // shouldn't happen! - } - } - } - // also shift chart up if necessary [BugTracker 2858] - int row = c.getRow0(); - // only move images whose top is >= rnum - int rnum = rownum + 1; - if (row > rnum) { - int h = c.getHeight(); - // move down 1 row - c.setRow(row - 1); - c.setHeight(h); - } - } - - if (flag != WorkSheetHandle.ROW_DELETE_NO_REFERENCE_UPDATE) - ReferenceTracker.updateReferences(rownum, -1, this.mysheet, true); - - // Adjust image row so that height remains constant - int rnum = rownum + 1; - ImageHandle[] images = this.getImages(); - for (int i = 0; i < images.length; i++) { - ImageHandle ih = images[i]; - int row = ih.getRow(); - // only move images whose top is >= rnum - if (row > rnum) { - short h = ih.getHeight(); - // move down 1 row - ih.setRow(row - 1); - ih.setHeight(h); - } - } - } - - /** - * Removes columns and all their associated cells from the sheet. This method - * does not shift the subsequent columns left, for that use {@link #removeCols}. - * - * @param first the zero-based index of the first column to be removed - * @param count the number of columns to remove. - */ - public void clearCols(int first, int count) { - this.removeCols(first, count, false); - } - - /** - * Removes columns from the sheet and shifts the following columns left. - * - * @param first the zero-based index of the first column to be removed - * @param count the number of columns to remove. - */ - public void removeCols(int first, int count) { - this.removeCols(first, count, true); - } - - private void removeCols(int first, int count, boolean shift) { - if (first < 0) - throw new IllegalArgumentException("column index must be zero or greater"); - if (count < 1) - throw new IllegalArgumentException("count must be at least one"); - mysheet.removeCols(first, count, shift); - } - - /** - * Removes a column and all associated cells from this sheet. This does not - * shift subsequent columns. - * - * @param colstr the name of the column to remove - * @deprecated Use {@link #clearCols} instead. - */ - @Deprecated - public void removeCol(String colstr) throws ColumnNotFoundException { - this.removeCols(ExcelTools.getIntVal(colstr), 1, false); - } - - /** - * Remove a column and all associated cells from this sheet. Optionally shift - * all subsequent columns left to fill the gap. - * - * @param colstr the name of the column to remove - * @param shiftcols whether to shift subsequent columns - * @deprecated Use {@link #removeCols} or {@link #clearCols} instead. - */ - @Deprecated - public void removeCol(String colstr, boolean shiftcols) throws ColumnNotFoundException { - this.removeCols(ExcelTools.getIntVal(colstr), 1, shiftcols); - } - - /** - * Returns the index of the Sheet. - * - * @return String Sheet Name - */ - public int getSheetNum() { - return mysheet.getSheetNum(); - } - - /** - * Returns all Named Range Handles scoped to this Worksheet. - *

                  - * Note this will not include workbook scoped named ranges - * - * @return NameHandle[] all of the Named ranges that are scoped to the present - * worksheet - */ - public NameHandle[] getNamedRangesInScope() { - Name[] nand = mysheet.getSheetScopedNames(); - NameHandle[] nands = new NameHandle[nand.length]; - for (int x = 0; x < nand.length; x++) { - nands[x] = new NameHandle(nand[x], this.wbh); - } - return nands; - } - - /** - * Returns a Named Range Handle if it exists in the specified scope. - *

                  - * This can be used to distinguish between multiple named ranges with the same - * name but differing scopes - * - * @return NameHandle a Named range in the Worksheet that exists in the scope - */ - public NameHandle getNamedRangeInScope(String rangename) throws CellNotFoundException { - Name nand = mysheet.getScopedName(rangename); - if (nand == null) - throw new CellNotFoundException(rangename); - return new NameHandle(nand, this.wbh); - } - - /** - * Returns the name of the Sheet. - * - * @return String Sheet Name - */ - public String getSheetName() { - return mysheet.getSheetName(); - } - - /** - * return the sheetname properly qualified or quoted used when the sheetname - * contains spaces, commas or parentheses - * - * @return - */ - public String getQualifiedSheetName() { - return GenericPtg.qualifySheetname(mysheet.getSheetName()); - } - - /** - * Returns the underlying low-level Boundsheet object. - * - * @return Boundsheet sheet - */ - protected Boundsheet getSheet() { - return mysheet; - } - - /** - * Returns the Serialized bytes for this WorkSheet. - *

                  - * The output of this method can be used to insert a copy of this WorkSheet into - * another WorkBook using the WorkBookHandle.addWorkSheet(byte[] serialsheet, - * String NewSheetName) method. - * - * @return byte[] the WorkSheet's Serialized bytes - * @see WorkBookHandle.addWorkSheet(byte[] serialsheet, String NewSheetName) - */ - public byte[] getSerialBytes() { - mysheet.setLocalRecs(); - ObjectOutputStream obs = null; - byte[] b = null; - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - BufferedOutputStream bufo = new BufferedOutputStream(baos); - obs = new ObjectOutputStream(bufo); - obs.writeObject(mysheet); - bufo.flush(); - b = baos.toByteArray(); - } catch (Throwable e) { - Logger.logWarn("Serializing Sheet: " + this.toString() + " failed: " + e); - } - return b; - // return mysheet.getSheetBytes(); - } - - /** - * write this sheet as tabbed text output:
                  - * All rows and all characters in each cell are saved. Columns of data are - * separated by tab characters, and each row of data ends in a carriage return. - * If a cell contains a comma, the cell contents are enclosed in double - * quotation marks. All formatting, graphics, objects, and other worksheet - * contents are lost. The euro symbol will be converted to a question mark. If - * cells display formulas instead of formula values, the formulas are saved as - * text. - */ - public void writeAsTabbedText(OutputStream dest) throws IOException { - this.mysheet.writeAsTabbedText(dest); - } - - /** - * Constructor which takes a WorkBook and sheetname as parameters. - * - * @param sht The name of the WorkSheet - * @param mybk The WorkBook - */ - protected WorkSheetHandle(Boundsheet sht, WorkBookHandle b) { - this.wbh = b; - this.mysheet = sht; - this.mybook = sht.getWorkBook(); - // 20080624 KSC: add flag for shift formula rules upon row insertion/deletion - String shiftRule = (String) System.getProperties().get("io.starter.OpenXLS.WorkSheetHandle.shiftInclusive"); - if (shiftRule != null && shiftRule.equalsIgnoreCase("true")) { - mysheet.setShiftRule(shiftRule.equalsIgnoreCase("true")); - } - // 20080917 KSC: set cache setting via system property [BugTracker 1862] - if (System.getProperty("io.starter.OpenXLS.cacheCellHandles") != null) - cache = Boolean.valueOf((System.getProperty("io.starter.OpenXLS.cacheCellHandles"))).booleanValue(); - } - - /** - * @return setting on whether to use cache or not - */ - public boolean getUseCache() { - return cache; - } - - /** - * Set the Object value of the Cell at the given address. - * - * @param String Cell Address (ie: "D14") - * @param Object new Cell Object value - * @throws CellNotFoundException is thrown if there is no existing Cell at the specified - * address. - */ - public void setVal(String address, Object val) throws CellNotFoundException, CellTypeMismatchException { - CellHandle c = this.getCell(address); - c.setVal(val); - } - - /** - * Set the double value of the Cell at the given address - * - * @param String Cell Address (ie: "D14") - * @param double new Cell double value - * @param address - * @param d - * @throws io.starter.formats.XLS.CellNotFoundException is thrown if there is no existing Cell at the specified - * address. - */ - public void setVal(String address, double d) throws CellNotFoundException, CellTypeMismatchException { - CellHandle c = this.getCell(address); - c.setVal(d); - } - - /** - * Set the String value of the Cell at the given address - * - * @param String Cell Address (ie: "D14") - * @param String new Cell String value - * @throws CellNotFoundException is thrown if there is no existing Cell at the specified - * address. - */ - public void setVal(String address, String s) throws CellNotFoundException, CellTypeMismatchException { - CellHandle c = this.getCell(address); - c.setVal(s); - } - - /** - * Set the name of the Worksheet. This method will change the name on the - * Worksheet's tab as displayed in the WorkBook, as well as all programmatic and - * internal references to the name. - *

                  - * This change takes effect immediately, so all attempts to reference the - * Worksheet by its previous name will fail. - * - * @param String the new name for the Worksheet - */ - public void setSheetName(String name) { - wbh.sheethandles.remove(this.getSheetName()); // keep sheethandles (name->wsh) updated - mysheet.setSheetName(name); - wbh.sheethandles.put(name, this); - } - - /** - * Set the int value of the Cell at the given address - * - * @param String Cell Address (ie: "D14") - * @param int new Cell int value - * @throws io.starter.formats.XLS.CellNotFoundException is thrown if there is no existing Cell at the specified - * address. - */ - public void setVal(String address, int i) throws CellNotFoundException, CellTypeMismatchException { - CellHandle c = this.getCell(address); - c.setVal(i); - } - - /** - * Get the Object value of a Cell. - *

                  - * Numeric Cell values will return as type Long, Integer, or Double. String Cell - * values will return as type String. - * - * @return the value of the Cell as an Object. - * @throws CellNotFoundException is thrown if there is no existing Cell at the specified - * address. - */ - public Object getVal(String address) throws CellNotFoundException { - CellHandle c = this.getCell(address); - return c.getVal(); - } - - /** - * Insert a row of Objects into the worksheet. Automatically shifts all rows - * below the cell down one. - *

                  - * Method takes an array of Objects to insert into the rows. - *

                  - * Object array must match columns in number starting with column A. - *

                  - * For emptly cells, put a null Object reference in your array. - *

                  - * example: Object[] newCellHandles = { null, // col A "Hello", // col B - * Integer.valueOf(120), // col C "=sum(A1+B1)", // col D null, // col E null, - * // col F "World" // col G }; - *

                  - * CellHandle ret = sheet.insertRow(newCellHandles, 1, true); if(ret !=null) - * Logger.log("It worked"); - * - * @param an array of Objects to insert into the new row - * @param rownum the rownumber to insert - * @param whether to shift down existing Cells - */ - public CellHandle[] insertRow(int row1, Object[] data) { - return insertRow(row1, data, true); - } - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - *

                  - * Adding new cells to non-existent rows will automatically create new rows in - * the file, This method is only necessary to "move" existing cells by inserting - * empty rows. - * - * @param rownum the rownumber to insert - */ - public boolean insertRow(int rownum) { - return insertRow(rownum, (Row) null, ROW_INSERT, true); - } - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - *

                  - * Adding new cells to non-existent rows will automatically create new rows in - * the file, This method is only necessary to "move" existing cells by inserting - * empty rows. - *

                  - * Same as insertRow(rownum) except with addition of flag - * - * @param rownum the rownumber to insert - * @param flag row insertion rule - */ - public void insertRow(int rownum, int flag) { - insertRow(rownum, (Row) null, flag, true); - } - - private CompatibleVector addedrows = new CompatibleVector(); - private boolean range_init = true; - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - *

                  - * This method differs from insertRow in that it can be used to repeatedly - * insert rows at the same row index. - *

                  - * Adding new cells to non-existent rows will automatically create new rows in - * the file, - *

                  - * After calling this method, setVal() can be used on the newly created cells to - * update with new values. - * - * @param rownum the rownumber to insert - * @param whether to shift down existing Cells - * @return whether the insert was successful - */ - public boolean insertRowAt(int rownum, boolean shiftrows) { - addedrows.remove(Integer.valueOf(rownum)); - return insertRow(rownum, mysheet.getRowByNumber(rownum), rownum, shiftrows); - } - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - *

                  - * Adding new cells to non-existent rows will automatically create new rows in - * the file, - *

                  - * After calling this method, setVal() can be used on the newly created cells to - * update with new values. - * - * @param rownum the rownumber to insert (NOTE: rownum is 0-based) - * @param whether to shift down existing Cells - * @return whether the insert was successful - */ - public boolean insertRow(int rownum, boolean shiftrows) { - Row myr = null; - try { - myr = mysheet.getRowByNumber(rownum); - } catch (Exception e) { - } - if (myr != null || shiftrows) { - return insertRow(rownum, myr, ROW_INSERT_MULTI, shiftrows); - } else { - // essentially a high performance row insert for the bottom of the workbook, - // used frequently in streaming workbook insertion - Row newRow = mysheet.insertRow(rownum, 0, ROW_INSERT_MULTI, shiftrows); - return true; - } - - } - - // insert handling flags - /** - * Insert row multiple times allowed, also copies formulas to inserted row - */ - public static final int ROW_INSERT_MULTI = 0; - /** - * Excel standard row insertion behavior - */ - public static final int ROW_INSERT = 3; - /** - * Insert row one time, multiple calls ignored - */ - public static final int ROW_INSERT_ONCE = 1; - /** - * Insert row but do not update any cell references affected by insert - */ - public static final int ROW_INSERT_NO_REFERENCE_UPDATE = 2; - - // 20080619 KSC: Add flag constants for Delete Row - public static final int ROW_DELETE = 1; - public static final int ROW_DELETE_NO_REFERENCE_UPDATE = 2; - - /** - * enhanced protection settings: Edit Object - */ - public final static short ALLOWOBJECTS = FeatHeadr.ALLOWOBJECTS; - /** - * enhanced protection settings: Edit scenario - */ - public static final short ALLOWSCENARIOS = FeatHeadr.ALLOWSCENARIOS; - /** - * enhanced protection settings: Format cells - */ - public static final short ALLOWFORMATCELLS = FeatHeadr.ALLOWFORMATCELLS; - /** - * enhanced protection settings: Format columns - */ - public static final short ALLOWFORMATCOLUMNS = FeatHeadr.ALLOWFORMATCOLUMNS; - /** - * enhanced protection settings: Format rows - */ - public static final short ALLOWFORMATROWS = FeatHeadr.ALLOWFORMATROWS; - /** - * enhanced protection settings: Insert columns - */ - public static final short ALLOWINSERTCOLUMNS = FeatHeadr.ALLOWINSERTCOLUMNS; - /** - * enhanced protection settings: Insert rows - */ - public static final short ALLOWINSERTROWS = FeatHeadr.ALLOWINSERTROWS; - /** - * enhanced protection settings: Insert hyperlinks - */ - public static final short ALLOWINSERTHYPERLINKS = FeatHeadr.ALLOWINSERTHYPERLINKS; - /** - * enhanced protection settings: Delete columns - */ - public static final short ALLOWDELETECOLUMNS = FeatHeadr.ALLOWDELETECOLUMNS; - /** - * enhanced protection settings: Delete rows - */ - public static final short ALLOWDELETEROWS = FeatHeadr.ALLOWDELETEROWS; - /** - * enhanced protection settings: Select locked cells - */ - public static final short ALLOWSELLOCKEDCELLS = FeatHeadr.ALLOWSELLOCKEDCELLS; - /** - * enhanced protection settings: Sort - */ - public static final short ALLOWSORT = FeatHeadr.ALLOWSORT; - /** - * enhanced protection settings: Use Autofilter - */ - public static final short ALLOWAUTOFILTER = FeatHeadr.ALLOWAUTOFILTER; - /** - * enhanced protection settings: Use PivotTable reports - */ - public static final short ALLOWPIVOTTABLES = FeatHeadr.ALLOWPIVOTTABLES; - /** - * enhanced protection settings: Select unlocked cells - */ - public static final short ALLOWSELUNLOCKEDCELLS = FeatHeadr.ALLOWSELUNLOCKEDCELLS; - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - *

                  - * Adding new cells to non-existent rows will automatically create new rows in - * the file, - *

                  - * After calling this method, setVal() can be used on the newly created cells to - * update with new values. - * - * @param rownum the rownumber to insert - * @param whether to shift down existing Cells - */ - public boolean insertRow(int rownum, RowHandle copyRow, int flag, boolean shiftrows) { - return this.insertRow(rownum, copyRow.myRow, flag, shiftrows); - } - - /** - * Insert a row of Objects into the worksheet. Shift all rows below the cell - * down one. - *

                  - * Method takes an array of Objects to insert into the rows. - *

                  - * Object array must match columns in number starting with column A. - *

                  - * For emptly cells, put a null Object reference in your array. - *

                  - * example: Object[] newCellHandles = { null, // col A "Hello", // col B - * Integer.valueOf(120), // col C "=sum(A1+B1)", // col D null, // col E null, - * // col F "World" // col G }; - *

                  - * boolean okay = sheet.insertRow(newCellHandles, 1, true); - * if(okay)Logger.log("It worked"); - * - * @param an array of Objects to insert into the new row - * @param rownum the rownumber to insert - * @param whether to shift down existing Cells - */ - public CellHandle[] insertRow(int rownum, Object[] data, boolean shiftrows) { - CellHandle[] retc = new CellHandle[data.length]; - try { - insertRow(rownum, shiftrows); - for (int t = 0; t < data.length; t++) { - if (data[t] != null) - retc[t] = add(data[t], rownum, t); - } - } catch (Exception ex) { - throw new WorkBookException(ex.toString(), WorkBookException.RUNTIME_ERROR); - } - return retc; - } - - /** - * Insert a blank row into the worksheet. Shift all rows below the cell down - * one. - *

                  - * Adding new cells to non-existent rows will automatically create new rows in - * the file, - *

                  - * After calling this method, setVal() can be used on the newly created cells to - * update with new values. - * - * @param rownum the rownumber to insert (0-based) - * @param copyrow the row to copy formats and formulas from - * @param flag determines handling tracking of inserted rows and only allow - * insertion once - * @param whether to shift down existing Cells - */ - private boolean insertRow(int rownum, Row copyRow, int flag, boolean shiftrows) { - int offset = 1; - return shiftRow(rownum, copyRow, flag, shiftrows, offset); - } - - /** - * replacement method for delete row that handles references better - * - * @param rownum - * @param flag - * @param shiftrows - * @return - */ - boolean deleteRow(int rownum, int flag, boolean shiftrows) { - int offset = -1; - return shiftRow(rownum, null, flag, shiftrows, offset); - } - - /** - * insert/delete agnostic row copy/insert/delete and formula shifter - *

                  - * TODO: Better comments - * - * @param rownum - * @param copyRow - * @param flag - * @param shiftrows - * @param offset - * @return - */ - private boolean shiftRow(int rownum, Row copyRow, int flag, boolean shiftrows, int offset) { - - // If the copyrow is null, such as an insert row on an empty row, create that - // row, otherwise - // we end up using different logic for row insertion, which makes no sense. - if (copyRow == null) { - // insert a blank - this.add(null, "A" + rownum + 1); - copyRow = mysheet.getRowByNumber(rownum); - - } - // handle tracking of inserted rows -- if flag is false rows can be inserted - // multiple times at the same index - if (flag == WorkSheetHandle.ROW_INSERT_ONCE) { // not inserted - if (this.addedrows.contains(Integer.valueOf(rownum))) - return false; // can't add an existing row! - } - - // sheetster ui means insert - // 'on top of' row, shift down - if (offset == 0) - offset = 1; - - // shiftrefs BEFORE inserting new row - if (shiftrows && flag != WorkSheetHandle.ROW_INSERT_NO_REFERENCE_UPDATE) { - int refUpdateStart = rownum; - // OpenXLS default behavior is to update one row too high. If we are using - // ROW_INSERT, update per excel standard, - // see TestInsertRows.testUpdateFormulaSettings() for testing - if (flag == WorkSheetHandle.ROW_INSERT) - refUpdateStart++; - ReferenceTracker.updateReferences(refUpdateStart, offset, this.mysheet, true); // shift or expand/contract - // ALL affected references - // including named ranges - } - - int firstcol = copyRow.getColDimensions()[0]; - Row newRow = mysheet.insertRow(rownum, firstcol, flag, shiftrows); // shifts rows down and inserts a new row, - // also shifts shared formula refs (see note - // below) - - // *************************************************************************************************************************************************************/ - // Named Range, Formula and AI references: - // ALL references to the inserted row# (rownum) and rows beyond are shifted in - // ReferenceTracker.updateReferences. - // This method uses the ReferenceTracker collection for the specific sheet in - // question to iterate through the stored references, - // shifting them as the shifting rules allow. - // In addition, all formulas in the copyrow will be duplicated in the newly - // inserted row; - // These formula references are shifted in ReferenceTracker.adjustFormulaRefs - // (see below) - // The only references that are NOT shifted in the schema described above are - // SharedFormula references (specifically, PtgRefN & PtgAreaA), - // which are NOT contained within the ReferenceTracker collection. - // These references are shifted "by hand" in - // ReferenceTracker.moveSharedFormulas, called upon - // insertRow->->Boundsheet.shiftCellRow - // *************************************************************************************************************************************************************/ - - if (shiftrows) - addedrows.add(Integer.valueOf(rownum)); - - // TODO: Why so much logic in here, move this to Boundsheet? - - if ((shiftrows) && (copyRow != null)) { - // Handle shifting reference rules for the newrow only and it's copyrow (NOTE: - // copyrow may have been shifted via insertRow above although it's references - // have not yet been shifted) - int refMovementDiff = (copyRow.getRowNumber() - rownum); // number of rows to shift - int refMovementRow = rownum; // start row for shifting operation - if (refMovementDiff < 0) - refMovementRow += refMovementDiff; // since copyrow < rownum, shifting should be done BEFORE insert row - newRow.setRowHeight(copyRow.getRowHeight()); - - // Now iterate through all cells in the original row and copy formats and - // formulas - Object[] copyRowCells = copyRow.getCellArray(); - Map newmerges = new Hashtable(); - CellRange newmerge = null; - CellHandle newCellHandle = null; - Mulblank aMul = null; // KSC: Mulblank handling - short c = -1; // "" - String sheetname = GenericPtg.qualifySheetname(this.toString()); - for (int i = 0; i < copyRowCells.length; i++) { - BiffRec copyRowCell = (BiffRec) copyRowCells[i]; - if (copyRowCell.getOpcode() == XLSConstants.MULBLANK) { - if (copyRowCell == aMul) - c++; // ref next blank in range - nec. for ixfe (FormatId) see below - else { - aMul = (Mulblank) copyRowCell; - c = (short) aMul.getColFirst(); - } - aMul.setCurrentCell(c); - } - CellHandle copyCellHandle = new CellHandle(copyRowCell, this.wbh); - copyCellHandle.setWorkSheetHandle(this); - // this.cellhandles.put(copyCellHandle.getCellAddress(), copyCellHandle); - int colnum = copyCellHandle.getColNum(); - - // insert an empty copy of the cell OR, if it's a formula cell, copy formula and - // adjust it's cell references appropriate for new cell position - if (copyRowCell.getOpcode() == XLSRecord.FORMULA - && flag != WorkSheetHandle.ROW_INSERT_NO_REFERENCE_UPDATE - && flag != WorkSheetHandle.ROW_INSERT) { - try { - // copy copyrow's formula and then shift it's references relative to copycell's - // original row and references - newCellHandle = add(copyCellHandle.getFormulaHandle().getFormulaString(), rownum, colnum); - // streaming parser uses fast cell adds which returns null, populate here - if (newCellHandle == null) { - try { - newCellHandle = this.getCell(rownum, colnum); - } catch (CellNotFoundException e) { - // should be impossible } - } - } - // because the original formula has been shifted by one, unshift this sucker... - ReferenceTracker.adjustFormulaRefs(newCellHandle, refMovementRow, refMovementDiff * -1, true); - newCellHandle.getFormulaHandle().getFormulaRec().clearCachedValue(); - } catch (Exception e) { - if (DEBUGLEVEL > 0) - Logger.logWarn("WorkSheetHandle.shiftRow() could not adjust formula references in formula: " - + copyCellHandle + " while inserting new row." + e.toString()); - } - } else { - newCellHandle = add(null, rownum, colnum); - // streaming parser uses fast cell adds which returns null, populate here - if (newCellHandle == null) { - try { - newCellHandle = this.getCell(rownum, colnum); - } catch (CellNotFoundException e) { - // should be impossible } - } - } - } - newCellHandle.setFormatId(copyCellHandle.getFormatId()); - - // handle merged cells -- assemble the newmerges collection for below - CellRange oby = copyCellHandle.getMergedCellRange(); - if (oby != null) { // we have a merge - int[] fr = {rownum, oby.firstcellcol}; - int[] lr = {rownum, oby.lastcellcol}; - String newrng = sheetname + "!" + ExcelTools.formatLocation(fr) + ":" - + ExcelTools.formatLocation(lr); - newrng = GenericPtg.qualifySheetname(newrng); - if (DEBUGLEVEL > 10) - Logger.logInfo("WorksheetHandle.insertRow() created new Merge Range: " + newrng); - // check if we've already created... - if (newmerges.get(newrng) == null) { - newmerge = new CellRange(newrng, this.wbh, true); - if (DEBUGLEVEL > 10) - Logger.logInfo("WorksheetHandle.insertRow() created new Merge Range: " + newrng); - newmerges.put(newmerge.toString(), newmerge); - } - } - - } - // now update the new merge ranges... - Collection xl = newmerges.values(); - if (xl != null) { - Iterator itx = xl.iterator(); - while (itx.hasNext()) { - itx.next().mergeCells(true); - } - } - } - // Handle Image Movement - ImageHandle[] images = mysheet.getImages(); - if (images != null) { - for (int i = 0; i < images.length; i++) { - ImageHandle ih = images[i]; - int row = ih.getRow(); - // only move images whose top is >= copyRow - if (row >= rownum) { - // move down 1 row - short h = ih.getHeight(); - ih.setRow(row + 1); - ih.setHeight(h); - } - } - } - // Insert chart series IF SERIES ARE ROW-BASED - List charts = this.mysheet.getCharts(); - for (int i = 0; i < charts.size(); i++) { - Chart c = (Chart) charts.get(i); - ReferenceTracker.insertChartSeries(c, GenericPtg.qualifySheetname(this.getSheetName()), rownum); - // also shift charts down [BugTracker 2858] - int row = c.getRow0(); - // only move charts whose top is >= copyRow - if (row >= rownum) { - // move down 1 row - int h = c.getHeight(); - c.setRow(row + offset); - c.setHeight(h); - } - } - return true; - } - - /** - * returns an array of FormatHandles for the ConditionalFormats applied to this - * cell - * - * @return an array of FormatHandles, one for each of the Conditional Formatting - * rules - */ - public ConditionalFormatHandle[] getConditionalFormatHandles() { - ConditionalFormatHandle[] cfx = new ConditionalFormatHandle[this.mysheet.getConditionalFormats().size()]; - for (int i = 0; i < cfx.length; i++) { - Condfmt cfmt = (Condfmt) this.mysheet.getConditionalFormats().get(i); - cfx[i] = new ConditionalFormatHandle(cfmt, this); - } - return cfx; - } - - /** - * Returns the WorkBookHandle for this Sheet - * - * @return - */ - public WorkBookHandle getWorkBook() { - return this.wbh; - } - - /** - * Get a handle to all of the images in this worksheet - * - * @return - */ - public ImageHandle getImage(String name) throws ImageNotFoundException { - int idz = mysheet.getImageVect().indexOf(name); - if (idz > 0) - return (ImageHandle) mysheet.getImageVect().get(idz); - throw new ImageNotFoundException("Could not find " + name + " in " + this.getSheetName()); - } - - /** - * Get a handle to all of the images in this worksheet - * - * @return - */ - public ImageHandle[] getImages() { - return this.mysheet.getImages(); - } - - /** - * returns the actual amount of images contained in the sheet and is determined - * by imageMap - * - * @return - */ - public int getNumImages() { - return this.mysheet.imageMap.size(); - } - - /** - * write out all of the images in the Sheet to a directory - * - * @param imageoutput directory - */ - public void extractImagesToDirectory(String outdir) { - ImageHandle[] extracted = getImages(); - - // extract and output images - for (int tx = 0; tx < extracted.length; tx++) { - String n = extracted[tx].getName(); - if (n.equals("")) - n = "image" + extracted[tx].getMsodrawing().getImageIndex(); - String imgname = n + "." + extracted[tx].getType(); - if (DEBUGLEVEL > 0) - Logger.logInfo("Successfully extracted: " + outdir + imgname); - try { - FileOutputStream outimg = new FileOutputStream(outdir + imgname); - extracted[tx].write(outimg); - outimg.flush(); - outimg.close(); - } catch (Exception ex) { - Logger.logErr("Could not extract images from: " + this); - } - } - } - - /** - * retrieves all charts for this sheet and writes them (in SVG form) to outpdir - *
                  - * Filename is in form of: _Chart<#>.svg - * - * @param outdir String output folder - */ - public void extractChartToDirectory(String outdir) { - ArrayList charts = (ArrayList) this.mysheet.getCharts(); - String sheetname = this.getSheetName(); - for (int i = 0; i < charts.size(); i++) { - ChartHandle ch = new ChartHandle((Chart) charts.get(i), this.getWorkBook()); - String fname = sheetname + "_Chart" + ch.getId() + ".svg"; - try { - FileOutputStream chartout = new FileOutputStream(outdir + fname); - chartout.write(ch.getSVG(1.0).getBytes()); // scaled as necessary in XSL - } catch (Exception ex) { - Logger.logErr("extractChartToDirectory: Could not extract charts from: " + this + ":" + ex.toString()); - } - - } - } - - /** - * insert an image into this worksheet - * - * @param im -- the ImageHandle to insert - * @see ImageHandle - */ - public void insertImage(ImageHandle im) { - this.mysheet.insertImage(im); - } - - /** - * Inserts empty columns and shifts the following columns to the right. This - * method is used to shift existing columns right to make room for a new column. - * To create a column in the file so its size or style can be set just add a - * blank cell to its first row with {@link #add}. - * - * @param first the zero-based index of the first column to insert - * @param count the number of columns to insert - */ - public void insertCols(int first, int count) { - if (first < 0) - throw new IllegalArgumentException("column index must be zero or greater"); - if (count < 1) - throw new IllegalArgumentException("count must be at least one"); - mysheet.insertCols(first, count); - } - - /** - * Inserts an empty column and shifts the following columns to the right. - * - * @param colnum the zero-based index of the column to be inserted - * @deprecated Use {@link #insertCols} instead. - */ - @Deprecated - public void insertCol(int colnum) { - this.insertCols(colnum, 1); - } - - /** - * Inserts an empty column and shifts the following columns to the right. - * - * @param colnum the address of the column to be inserted - * @deprecated Use {@link #insertCols} instead. - */ - @Deprecated - public void insertCol(String colnum) { - this.insertCols(ExcelTools.getIntVal(colnum), 1); - } - - /** - * When adding a new Cell to the sheet, OpenXLS can automatically copy the - * formatting from the Cell directly above the inserted Cell. - *

                  - * ie: if set to true, newly added Cell D19 would take its formatting from Cell - * D18. - *

                  - * Default is false - *

                  - *

                  - *

                  - * boolean copy the formats from the prior Cell - */ - public void setCopyFormatsFromPriorWhenAdding(boolean f) { - mysheet.setCopyPriorCellFormats(f); - } - - /** - * Add a Cell with the specified value to a WorkSheet. - *

                  - * This method determines the Cell type based on type-compatibility of the - * value. - *

                  - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * @param obj the value of the new Cell - * @param int row the row of the new Cell - * @param int col the column of the new Cell - */ - public CellHandle add(Object obj, int row, int col) { - return add(obj, row, col, this.getWorkBook().getWorkBook().getDefaultIxfe()); - } - - /** - * Add a Cell with the specified value to a WorkSheet. - *

                  - * This method determines the Cell type based on type-compatibility of the - * value. - *

                  - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - *

                  - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param obj the value of the new Cell - * @param int row the row of the new Cell - * @param int col the column of the new Cell - */ - public CellHandle[] addValidated(Object obj, int row, int col) throws ValidationException { - return addValidated(obj, row, col, this.getWorkBook().getWorkBook().getDefaultIxfe()); - } - - /** - * Add a Cell with the specified value to a WorkSheet. - *

                  - * This method determines the Cell type based on type-compatibility of the - * value. - *

                  - * Further, this method allows passing in a format id - *

                  - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - *

                  - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param obj the value of the new Cell - * @param int row the row of the new Cell - * @param int col the column of the new Cell - * @param int the format id to apply to this cell - */ - public CellHandle[] addValidated(Object obj, int row, int col, int formatId) throws ValidationException { - int[] rc = {row, col}; - ValidationHandle vh = this.getValidationHandle(ExcelTools.formatLocation(rc)); - if (vh != null) { - vh.isValid(obj); - } - return this.addValidated(obj, ExcelTools.formatLocation(rc)); - } - - /** - * Add a Cell with the specified value to a WorkSheet. - *

                  - * This method determines the Cell type based on type-compatibility of the - * value. - *

                  - * Further, this method allows passing in a format id - *

                  - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * @param obj the value of the new Cell - * @param int row the row of the new Cell - * @param int col the column of the new Cell - * @param int the format id to apply to this cell - */ - public CellHandle add(Object obj, int row, int col, int formatId) { - int[] rc = {row, col}; - - if (obj instanceof java.util.Date) { - String address = ExcelTools.formatLocation(rc); - this.add((java.util.Date) obj, address, null); - } else { - BiffRec reca = mysheet.addValue(obj, rc, formatId); - - if (DEBUGLEVEL > 1) - if (reca != null) - Logger.logInfo("WorkSheetHandle.add() " + reca.toString() + " Successfully Added."); - else - return null; - } - - if (!mysheet.fastCellAdds) { - try { - CellHandle c = this.getCell(row, col); - if (this.wbh.getFormulaCalculationMode() != wbh.CALCULATE_EXPLICIT) - c.clearAffectedCells(); // blow out cache - return c; - } catch (CellNotFoundException e) { - Logger.logInfo("Adding Cell to row failed row:" + row + " col: " + col + " failed."); - return null; - } - } else { - return null; - } - } - - /** - * Fast-adds a Cell with the specified value to a WorkSheet. - *

                  - * This method determines the Cell type based on type-compatibility of the - * value. - *

                  - * Further, this method allows passing in a format id - *

                  - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * @param obj the value of the new Cell - * @param int row the row of the new Cell - * @param int col the column of the new Cell - * @param int the format id to apply to this cell - */ - public void fastAdd(Object obj, int row, int col, int formatId) { - int[] rc = {row, col}; - - // use default format - if (formatId == -1) - formatId = 0; - - if (obj instanceof java.util.Date) { - String address = ExcelTools.formatLocation(rc); - this.add((java.util.Date) obj, address, null); - } else { - BiffRec reca = mysheet.addValue(obj, rc, formatId); - if (this.wbh.getFormulaCalculationMode() != wbh.CALCULATE_EXPLICIT) { - ReferenceTracker rt = this.wbh.getWorkBook().getRefTracker(); - rt.clearAffectedFormulaCells(reca); - } - if (DEBUGLEVEL > 1) - if (reca != null) - Logger.logInfo("WorkSheetHandle.add() " + reca.toString() + " Successfully Added."); - } - } - - /** - * Toggle fast cell add mode. - *

                  - * Set to true to turn off checking for existing cells, conditional formats and - * merged ranges in order to accelerate adding new cells - * - * @param fastadds whether to disable checking for existing cells and - */ - public void setFastCellAdds(boolean fastadds) { - this.mysheet.setFastCellAdds(fastadds); - } - - /** - * Get the current fast add cell mode for this worksheet - */ - public boolean getFastCellAdds() { - return this.mysheet.fastCellAdds; - } - - /** - * Add a Cell with the specified value to a WorkSheet, optionally attempting to - * convert numeric values to appropriate number cells with appropriate number - * formatting applied. - *

                  - * This would allow a value entered such as "$1.00" to be converted to a numeric - * 1.00d value with a $0.00 format pattern applied. - *

                  - * Note that there is overhead to this method, as the value added needs to be - * parsed, for higher performance do not use the autodetectNumberAndPattern = - * true setting, and instead pass numeric values and format patterns. - *

                  - * If the value is non numeric, then it is simply added. - * - * @param obj the value of the new Cell - * @param address the address of the new Cell - * @param autoDetectValue whether to attempt to store as a number with a format pattern. - */ - public CellHandle add(Object obj, String address, boolean autoDetectValue) { - if (obj == null) - mysheet.addValue(obj, address); // to add a blank cell - else if (obj instanceof Date) - this.add((Date) obj, address, null); - else - mysheet.addValue(obj, address, autoDetectValue);// - - // fast-adds optimzation -- do not return a CellHandle - if (this.mysheet.fastCellAdds) - return null; - - try { - return this.getCell(address); - } catch (CellNotFoundException e) { - Logger.logInfo("Adding Cell: " + address + " failed"); - return null; - } - } - - /** - * Add a Cell with the specified value to a WorkSheet. - *

                  - * This method determines the Cell type based on type-compatibility of the - * value. - *

                  - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - * - * @param obj the value of the new Cell - * @param address the address of the new Cell - */ - public CellHandle add(Object obj, String address) { - if (obj == null) - mysheet.addValue(obj, address); // to add a blank cell - else if (obj instanceof Date) - this.add((Date) obj, address, null); - else - mysheet.addValue(obj, address);// - - // fast-adds optimzation -- do not return a CellHandle - if (!mysheet.fastCellAdds) { - try { - CellHandle c = this.getCell(address); - if (this.wbh.getFormulaCalculationMode() != wbh.CALCULATE_EXPLICIT) - c.clearAffectedCells(); // blow out cache - return c; - } catch (CellNotFoundException e) { - Logger.logInfo("Adding Cell: " + address + " failed"); - return null; - } - } else { - return null; - } - } - - /** - * Add a Cell with the specified value to a WorkSheet. - *

                  - * This method determines the Cell type based on type-compatibility of the - * value. - *

                  - * In other words, if the Object cannot be converted safely to a Numeric Object - * type, then it is treated as a String and a new String value is added to the - * WorkSheet at the Cell address specified. - *

                  - *

                  - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param obj the value of the new Cell - * @param address the address of the new Cell - */ - public CellHandle[] addValidated(Object obj, String address) throws ValidationException { - ValidationHandle vh = this.getValidationHandle(address); - if (vh != null) { - vh.isValid(obj); - } - CellHandle ch = this.add(obj, address); - List cxrs = ch.calculateAffectedCellsOnSheet(); - - // return the cellhandles - CellHandle[] cxrx = new CellHandle[cxrs.size() + 1]; - cxrx[0] = ch; - for (int t = 1; t < cxrx.length; t++) { - cxrx[t] = (CellHandle) cxrs.get(t - 1); - cxrx[t].setWorkSheetHandle(this); - // this.cellhandles.put(cxrx[t].getCellAddress(), cxrx[t]); - } - - return cxrx; - } - - /** - * Add a java.sql.Timestamp Cell to a WorkSheet. - *

                  - * Will create a default format of: - *

                  - * "m/d/yyyy h:mm:ss" - *

                  - * if none is specified. - * - * @param dt the value of the new java.sql.Timestamp Cell - * @param address the address of the new java.sql.Date Cell - * @param formatting pattern the address of the new java.sql.Date Cell - */ - public CellHandle add(Timestamp dt, String address, String fmt) { - if (fmt == null) - fmt = "m/d/yyyy h:mm:ss"; - Date dx = new Date(dt.getTime()); - return this.add(dx, address, fmt); - } - - /** - * Add a java.sql.Timestamp Cell to a WorkSheet. - *

                  - * Will create a default format of: - *

                  - * "m/d/yyyy h:mm:ss" - *

                  - * if none is specified. - *

                  - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param dt the value of the new java.sql.Timestamp Cell - * @param address the address of the new java.sql.Date Cell - * @param formatting pattern the address of the new java.sql.Date Cell - */ - public CellHandle[] addValidated(Timestamp dt, String address, String fmt) throws ValidationException { - if (fmt == null) - fmt = "m/d/yyyy h:mm:ss"; - Date dx = new Date(dt.getTime()); - return this.addValidated(dx, address, fmt); - } - - /** - * Add a java.sql.Date Cell to a WorkSheet. - *

                  - * You must specify a formatting pattern for the new date, or null for the - * default ("m/d/yy h:mm".) - *

                  - * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" - * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" - * - * @param dt the value of the new java.sql.Date Cell - * @param row to add the date - * @param col to add the date - * @param formatting pattern the address of the new java.sql.Date Cell - */ - public CellHandle add(java.util.Date dt, String address, String fmt) { - int[] rc = ExcelTools.getRowColFromString(address); - return add(dt, rc[0], rc[1], fmt); - } - - /** - * Add a java.sql.Date Cell to a WorkSheet. - *

                  - * You must specify a formatting pattern for the new date, or null for the - * default ("m/d/yy h:mm".) - *

                  - * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" - * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" - * - * @param dt the value of the new java.sql.Date Cell - * @param row to add the date - * @param col to add the date - * @param formatting pattern the address of the new java.sql.Date Cell - */ - public CellHandle[] addValidated(java.util.Date dt, String address, String fmt) throws ValidationException { - int[] rc = ExcelTools.getRowColFromString(address); - return addValidated(dt, rc[0], rc[1], fmt); - } - - /** - * Add a java.sql.Date Cell to a WorkSheet. - *

                  - * You must specify a formatting pattern for the new date, or null for the - * default ("m/d/yy h:mm".) - *

                  - * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" - * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" - * - * @param dt the value of the new java.sql.Date Cell - * @param address the address of the new java.sql.Date Cell - * @param formatting pattern the address of the new java.sql.Date Cell - */ - public CellHandle add(java.util.Date dt, int row, int col, String fmt) { - double x = DateConverter.getXLSDateVal(dt, this.mybook.getDateFormat()); - CellHandle thisCell = this.add(new Double(x), row, col); - - // first handle fast adds - if (thisCell == null && this.mysheet.fastCellAdds) { - try { - thisCell = getCell(row, col); - } catch (CellNotFoundException exp) { - Logger.logWarn("adding date to WorkSheet failed: " + this.getSheetName() + ":" + row + ":" + col); - return null; - } - } - - // 20060419 KSC: Use format from cell, if any - if (fmt == null) { - fmt = thisCell.getFormatPattern(); - if (fmt == null || fmt.equals("General")) - fmt = "m/d/yy h:mm"; - } - if (dateFormats.get(fmt) == null) { - FormatHandle fh = thisCell.getFormatHandle(); - fh.setFormatPattern(fmt); - dateFormats.put(fmt, Integer.valueOf(thisCell.getFormatId())); - } else { - Integer in = dateFormats.get(fmt); - thisCell.setFormatId(in.intValue()); - } - // Logger.logInfo("Date added: " + thisCell.getFormattedStringVal()); - return thisCell; - } - - /** - * Add a java.sql.Date Cell to a WorkSheet. - *

                  - * You must specify a formatting pattern for the new date, or null for the - * default ("m/d/yy h:mm".) - *

                  - * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" - * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" - *

                  - * If a validation record for the cell exists the validation is checked for a - * correct value, if the value does not pass the validation a - * ValidationException will be thrown - * - * @param dt the value of the new java.sql.Date Cell - * @param address the address of the new java.sql.Date Cell - * @param formatting pattern the address of the new java.sql.Date Cell - */ - public CellHandle[] addValidated(java.util.Date dt, int row, int col, String fmt) throws ValidationException { - int[] rc = {row, col}; - ValidationHandle vh = this.getValidationHandle(ExcelTools.formatLocation(rc)); - if (vh != null) { - vh.isValid(dt); - } - CellHandle ch = this.add(dt, row, col, fmt); - List cxrs = ch.calculateAffectedCellsOnSheet(); - - // FIXME: Use List.toArray instead of for loop - // return the cellhandles - CellHandle[] cxrx = new CellHandle[cxrs.size() + 1]; - cxrx[0] = ch; - for (int t = 1; t < cxrx.length; t++) { - cxrx[t] = (CellHandle) cxrs.get(t - 1); - } - - return cxrx; - - } - - /** - * Remove this WorkSheet from the WorkBook - *

                  - * NOTE: will throw a WorkBookException if the last sheet is removed. This - * results in an invalid output file. - */ - public void remove() { - mybook.removeWorkSheet(this.mysheet); - wbh.sheethandles.remove(this.getSheetName()); - } - - /** - * Create a CellRange object from an OpenXLS string range passed in such as - * "A1:F6" - * - * @param rangeName "A1:F6" - * @return - * @throws CellNotFoundException if the range cannot be created - */ - public CellRange getCellRange(String rangeName) throws CellNotFoundException { - CellRange cr = new CellRange(this.getSheetName() + "!" + rangeName, this.getWorkBook()); - return cr; - } - - /** - * Returns all CellHandles defined on this WorkSheet. - * - * @return CellHandle[] - the array of Cells in the Sheet - */ - public CellHandle[] getCells() { - BiffRec[] cells = mysheet.getCells(); - CellHandle[] retval = new CellHandle[cells.length]; - Mulblank aMul = null; - short c = -1; - for (int i = 0; i < retval.length; i++) { - try { - if (cells[i].getOpcode() != XLSConstants.MULBLANK) { - retval[i] = getCell(cells[i].getRowNumber(), cells[i].getColNumber()); - } else { // Handle MULBLANKS proper column number - if (cells[i] == aMul) { - c++; - } else { - aMul = (Mulblank) cells[i]; - c = (short) aMul.getColFirst(); - } - retval[i] = getCell(cells[i].getRowNumber(), c); - } - } catch (CellNotFoundException cnfe) { - // try harder - retval[i] = new CellHandle(cells[i], this.wbh); - retval[i].setWorkSheetHandle(this); - if (cells[i].getOpcode() == XLSConstants.MULBLANK) { - // handle Mulblanks: ref a range of cells; to get correct cell address, - // traverse thru range and set cellhandle ref to correct column - if (cells[i] == aMul) { - c++; - } else { - aMul = (Mulblank) cells[i]; - c = (short) aMul.getColFirst(); - } - retval[i].setBlankRef(c); // for Mulblank use only -sets correct column reference for multiple blank - // cells ... - } - } - } - return retval; - } - - /** - * Returns a FormulaHandle for working with the ranges of a formula on a - * WorkSheet. - * - * @param addr the address of the Cell - * @throws FormulaNotFoundException is thrown if there is no existing formula at the specified - * address. - */ - public FormulaHandle getFormula(String addr) throws FormulaNotFoundException, CellNotFoundException { - CellHandle c = this.getCell(addr); - return c.getFormulaHandle(); - } - - /** - * Returns a CellHandle for working with the value of a Cell on a WorkSheet. - * - * @param addr the address of the Cell - * @throws CellNotFoundException is thrown if there is no existing Cell at the specified - * address. - */ - public CellHandle getCell(String addr) throws CellNotFoundException { - CellHandle ret = null; // - - BiffRec c = mysheet.getCell(addr.toUpperCase()); - if (c == null) { - String sn = ""; - try { - sn = this.getSheetName(); - sn += "!"; - } catch (Exception e) { - } - if (addr == null) - addr = "undefined cell address"; - throw new CellNotFoundException(sn + addr); - } - ret = new CellHandle(c, this.wbh); - ret.setWorkSheetHandle(this); - return ret; - } - - /** - * Returns a CellHandle for working with the value of a Cell on a WorkSheet. - *

                  - * returns a new CellHandle with each call - *

                  - * use caching method getCell(int row, int col, boolean cache) to control - * caching of CellHandles. - * - * @param int Row the integer row of the Cell - * @param int Col the integer col of the Cell - * @throws CellNotFoundException is thrown if there is no existing Cell at the specified - * address. - */ - public CellHandle getCell(int row, int col) throws CellNotFoundException { - return getCell(row, col, false); - } - - /** - * Returns a CellHandle for working with the value of a Cell on a WorkSheet. - * - * @param int Row the integer row of the Cell - * @param int Col the integer col of the Cell - * @param boolean whether to cache or return a new CellHandle each call - * @throws CellNotFoundException is thrown if there is no existing Cell at the specified - * address. - */ - public CellHandle getCell(int row, int col, boolean cache) throws CellNotFoundException { - CellHandle ret = null; - if (cache) { - int[] rc = {row, col}; - String address = ExcelTools.formatLocation(rc); - // ret = (CellHandle)cellhandles.get(address); - if (ret != null) // caching! - return ret; - else { - ret = new CellHandle(this.mysheet.getCell(row, col), this.wbh); - ret.setWorkSheetHandle(this); - // cellhandles.put(address,ret); - return ret; - } - } - ret = new CellHandle(this.mysheet.getCell(row, col), this.wbh); - ret.setWorkSheetHandle(this); - return ret; - } - - /** - * Move a cell on this WorkSheet. - * - * @param CellHandle c - the cell to be moved - * @param String celladdr - the destination address of the cell - */ - public void moveCell(CellHandle c, String addr) throws CellPositionConflictException { - this.mysheet.moveCell(c.getCellAddress(), addr); - // c.moveTo(addr); < redundant call to above. 070104 -jm - } - - /** - * Get the text for the Footer printed at the bottom of the Worksheet - * - * @return String footer text - */ - public String getFooterText() { - return mysheet.getFooter().getFooterText(); - } - - /** - * Get the text for the Header printed at the top of the Worksheet - * - * @return String header text - */ - public String getHeaderText() { - return mysheet.getHeader().getHeaderText(); - } - - /** - * Get the print area set for this WorkSheetHandle. - *

                  - * If no print area is set return null; - */ - public String getPrintArea() { - return mysheet.getPrintArea(); - } - - /** - * Get the Print Titles set for this WorkSheetHandle. - *

                  - * If no Print Titles are set, this returns null; - */ - public String getPrintTitles() { - return mysheet.getPrintTitles(); - } - - /** - * Get the printer settings handle for this WorkSheetHandle. - */ - public PrinterSettingsHandle getPrinterSettings() { - return mysheet.getPrinterSetupHandle(); - } - - /** - * Sets the print area for the worksheet - *

                  - *

                  - * sets the printarea as a CellRange - * - * @param printarea - */ - public void setPrintArea(CellRange printarea) { - mysheet.setPrintArea(printarea.getRange()); - } - - /** - * Set the text for the Header printed at the top of the Worksheet - * - * @param String header text - */ - public void setHeaderText(String t) { - mysheet.getHeader().setHeaderText(t); - } - - /** - * Set the text for the Footer printed at the bottom of the Worksheet - * - * @param String footer text - */ - public void setFooterText(String t) { - mysheet.getFooter().setFooterText(t); - } - - /** - * Set the default column width of the worksheet - *

                  - *
                  - *

                  - * This setting is roughly the width of the character '0' The default width of a - * column is 8. - */ - public void setDefaultColWidth(int t) { - mysheet.setDefaultColumnWidth(t); - } - - public int getDefaultColWidth() { - return (int) mysheet.getDefaultColumnWidth(); - } - - /** - * Returns the name of this Sheet. - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return mysheet.toString(); - } - - /** - * FOR internal Use Only! - * - * @return Returns the low-level sheet record. - */ - public Boundsheet getMysheet() { - return mysheet; - } - - /** - * Calculates all formulas that reference the cell address passed in. - *

                  - * Please note that these cells have already been calculated, so in order to get - * their values without re-calculating them Extentech suggests setting the book - * level non-calculation flag, ie - * book.setFormulaCalculationMode(WorkBookHandle.CALCULATE_EXPLICIT) or - * FormulaHandle.getCachedVal() - * - * @return List of of calculated cells - */ - public List calculateAffectedCells(String CellAddress) { - CellHandle c = null; - try { - c = this.getCell(CellAddress); - } catch (CellNotFoundException e) { - return null; - } - return c.calculateAffectedCells(); - } - - /** - * Set whether to show calculated formula results in the output sheet. - * - * @return boolean whether to show calculated formula results - */ - public boolean getShowFormulaResults() { - return this.mysheet.getWindow2().getShowFormulaResults(); - } - - public void setShowFormulaResults(boolean b) { - this.mysheet.getWindow2().setShowFormulaResults(b); - } - - /** - * Get whether to show gridlines in the output sheet. - * - * @param boolean whether to show gridlines - */ - public boolean getShowGridlines() { - return this.mysheet.getWindow2().getShowGridlines(); - } - - /** - * Set whether to show gridlines in the output sheet. - * - * @return boolean whether to show gridlines - */ - public void setShowGridlines(boolean b) { - this.mysheet.getWindow2().setShowGridlines(b); - } - - /** - * Get whether to show sheet headers in the output sheet. - * - * @return boolean whether to show sheet headers - */ - public boolean getShowSheetHeaders() { - return this.mysheet.getWindow2().getShowSheetHeaders(); - } - - /** - * Set whether to show sheet headers in the output sheet. - * - * @param boolean whether to show sheet headers - */ - public void setShowSheetHeaders(boolean b) { - this.mysheet.getWindow2().setShowSheetHeaders(b); - } - - /** - * Get whether to show zero values in the output sheet. - * - * @return boolean whether to show zero values - */ - public boolean getShowZeroValues() { - return this.mysheet.getWindow2().getShowZeroValues(); - } - - /** - * Set whether to show zero values in the output sheet. - * - * @return boolean whether to show zero values - */ - public void setShowZeroValues(boolean b) { - this.mysheet.getWindow2().setShowZeroValues(b); - } - - /** - * Get whether to show outline symbols in the output sheet. - * - * @return boolean whether to outline symbols - */ - public boolean getShowOutlineSymbols() { - return this.mysheet.getWindow2().getShowOutlineSymbols(); - } - - /** - * Set whether to show outline symbols in the output sheet. - * - * @param boolean whether to show outline symbols - */ - public void setShowOutlineSymbols(boolean b) { - this.mysheet.getWindow2().setShowOutlineSymbols(b); - } - - /** - * Get whether to show normal view or page break preview view in the output - * sheet. - * - * @return boolean whether to show normal view or page break preview view - */ - public boolean getShowInNormalView() { - return this.mysheet.getWindow2().getShowInNormalView(); - } - - /** - * Set whether to show normal view or page break preview view in the output - * sheet. - * - * @param boolean whether to show normal view or page break preview view - */ - public void setShowInNormalView(boolean b) { - this.mysheet.getWindow2().setShowInNormalView(!b); // opposite of expected behavior - } - - /** - * Get whether there are freeze panes in the output sheet. - * - * @return boolean whether there are freeze panes - */ - public boolean hasFrozenPanes() { - return this.mysheet.getWindow2().getFreezePanes(); - } - - /** - * Set whether there are freeze panes in the output sheet. - * - * @param boolean whether there are freeze panes - */ - public void setHasFrozenPanes(boolean b) { - this.mysheet.getWindow2().setFreezePanes(b); - if (!b && this.mysheet.getPane() != null) { - this.mysheet.removePane(); // remove pane rec if unfreezing ... can also convert to plain splits, but a bit - // more complicated ... - } - } - - /** - * sets the zoom for the sheet - * - * @param the zoom as a float percentage (.25 = 25%) - */ - public void setZoom(float zm) { - this.mysheet.getScl().setZoom(zm); - } - - /** - * if this sheet has freeze panes, return the address of the top left cell - * otherwise, return null - * - * @return - */ - public String getTopLeftCell() { - if (this.mysheet.getPane() != null) { - return this.mysheet.getPane().getTopLeftCell(); - } - return null; - } - - /** - * gets the zoom for the sheet - * - * @return the zoom as a float percentage (.25 = 25%) - */ - public float getZoom() { - return this.mysheet.getScl().getZoom(); - } - - /** - * freezes the rows starting at the specified row and creating a scrollable - * sheet below this row - * - * @param row the row to start the freeze - */ - public void freezeRow(int row) { - if (this.mysheet.getPane() == null) - this.mysheet.setPane(null); // will add new - this.mysheet.getPane().setFrozenRow(row); - } - - /** - * freezes the cols starting at the specified column and creating a scrollable - * sheet to the right - * - * @param col the col to start the freeze - */ - public void freezeCol(int col) { - if (this.mysheet.getPane() == null) - this.mysheet.setPane(null); // will add new - this.mysheet.getPane().setFrozenColumn(col); - } - - /** - * splits the worksheet at column col for nCols - *

                  - * Note: unfreezes panes if frozen - * - * @param col col start col to split - * @param splitpos position of the horizontal split - */ - public void splitCol(int col, int splitpos) { - if (this.mysheet.getPane() == null) - this.mysheet.setPane(null); // will add new - this.mysheet.getPane().setSplitColumn(col, splitpos); - } - - /** - * splits the worksheet at row for nRows - *

                  - * Note: unfreezes panes if frozen - * - * @param row start row to split - * @param splitpos position of the vertical split - */ - public void splitRow(int row, int splitpos) { - if (this.mysheet.getPane() == null) - this.mysheet.setPane(null); // will add new - this.mysheet.getPane().setSplitRow(row, splitpos); - } - - /** - * Gets the row number (0 based) that the sheet split is located on. If the - * sheet is not split returns -1 - * - * @return - */ - public int getSplitRowLocation() { - if (this.mysheet.getPane() == null) - return -1; - return this.mysheet.getPane().getVisibleRow(); - } - - /** - * gets the column number (0-based)that the sheet split is locaated on; if the - * sheet is not split, returns -1 - * - * @return 0-based index of split column, if any - */ - public int getSplitColLocation() { - if (this.mysheet.getPane() == null) - return -1; - return this.mysheet.getPane().getVisibleCol(); - } - - /** - * Gets the twips split location returns -1 - * - * @return - */ - public int getSplitLocation() { - if (this.mysheet.getPane() == null) - return -1; - return this.mysheet.getPane().getRowSplitLoc(); - } - - /** - * Get whether to use manual grid color in the output sheet. - * - * @return boolean whether to use manual grid color - */ - public boolean getManualGridLineColor() { - return this.mysheet.getWindow2().getManualGridLineColor(); - } - - /** - * Set whether to use manual grid color in the output sheet. - * - * @param boolean whether to use manual grid color - */ - public void setManualGridLineColor(boolean b) { - this.mysheet.getWindow2().setManualGridLineColor(b); - } - - public CompatibleVector getAddedrows() { - return addedrows; - } - - /** - * Create a Conditional Format handle for a cell/range - * - * @param cellAddress without sheetname. Can also be a range, such as A1:B5 - * @param qualifier = maps to CONDITION_* bytes in ConditionalFormatHandle - * @param value1 = the error message - * @param value2 = the error title - * @param format = the initial format string to use with the condition - * @param firstCondition = formula string - * @param secondCondition = 2nd formula string (optional) - * @return - */ - public ConditionalFormatHandle createConditionalFormatHandle(String cellAddress, String operator, String value1, - String value2, String format, String firstCondition, String secondCondition) { - - if (cellAddress != null && cellAddress.indexOf("!") == -1) - cellAddress = this.getSheetName() + "!" + cellAddress; - Condfmt cfm = this.mysheet.createCondfmt(cellAddress, this.wbh); - - Cf cfr = this.mysheet.createCf(cfm); - cfr.setOperator(operator); - - // only place this is done.. - cfr.setCondition1(value1); - cfr.setCondition2(value2); - - Cf.setStylePropsFromString(format, cfr); - - ConditionalFormatHandle cfh = new ConditionalFormatHandle(cfm, this); - // done above in createCondfmt this.mysheet.addConditionalFormat(cfm); - return cfh; - } - - /** - * Creates a new annotation (Note or Comment) to the worksheeet, attached to a - * specific cell - * - * @param address -- address to attach - * @param txt -- text of note - * @param author -- name of author - * @return NoteHandle - handle which allows access to the Note object - * @see CommentHandle - */ - public CommentHandle createNote(String address, String txt, String author) { - Note n = this.getMysheet().createNote(address, txt, author); - return new CommentHandle(n); - } - - /** - * Creates a new annotation (Note or Comment) to the worksheeet, attached to a - * specific cell
                  - * The note or comment is a Unicode string, thus it can contain formatting - * information - * - * @param address -- address to attach - * @param txt -- Unicode string of note with Formatting - * @param author -- name of author - * @return NoteHandle - handle which allows access to the Note object - * @see CommentHandle - */ - public CommentHandle createNote(String address, Unicodestring txt, String author) { - Note n = this.getMysheet().createNote(address, txt, author); - return new CommentHandle(n); - } - - /** - * returns an array of all CommentHandles that exist in the sheet - * - * @return - */ - public CommentHandle[] getCommentHandles() { - ArrayList notes = getMysheet().getNotes(); - CommentHandle[] nHandles = new CommentHandle[notes.size()]; - for (int i = 0; i < nHandles.length; i++) { - nHandles[i] = new CommentHandle((Note) notes.get(i)); - } - return nHandles; - } - - /** - * creates a new, blank PivotTable and adds it to the worksheet. - * - * @param name pivot table name - * @param range source range for the pivot table. If no sheet is specified, the - * current sheet will be used. - * @param sId Stream or cachid Id -- links back to SxStream set of records - * @return PivotTableHandle - */ - public PivotTableHandle createPivotTable(String name, String range, int sId) { - Sxview sx = getMysheet().addPivotTable(range, this.wbh, sId, name); - PivotTableHandle pth = new PivotTableHandle(sx, this.getWorkBook()); - pth.setSourceDataRange(range); - return pth; - } - - /** - * Get a validation handle for the cell address passed in. If the validation is - * for a range, the handle returned will modify the entire range, not just the - * cell address passed in. - *

                  - * Returns null if a validation does not exist at the specified location - * - * @param cell address String - */ - public ValidationHandle getValidationHandle(String cellAddress) { - if (this.mysheet.getDvalRec() != null) { - Dv d = this.mysheet.getDvalRec().getDv(cellAddress); - if (d == null) - return null; - return new ValidationHandle(d); - } - return null; - } - - /** - * Create a validation handle for a cell/range - * - * @param cellAddress without sheetname. Can also be a range, such as A1:B5 - * @param valueType = maps to VALUE_* bytes in ValidationHandle - * @param condition = maps to CONDITION_* bytes in ValidationHandle - * @param errorBoxText = the error message - * @param errorBoxTitle = the error title - * @param promptBoxText = the prompt (hover) message - * @param promptBoxTitle = the prompt (hover) title - * @param firstCondition = formula string, seeValidationHandle.setFirstCondition - * @param secondCondition = seeValidationHandle.setSecondCondition, this can be left null - * for validations that do not require a second argument. - * @return - */ - public ValidationHandle createValidationHandle(String cellAddress, byte valueType, byte condition, - String errorBoxText, String errorBoxTitle, String promptBoxText, String promptBoxTitle, - String firstCondition, String secondCondition) { - Dv d = this.mysheet.createDv(cellAddress); - d.setValType(valueType); - /* - * // KSC: APPARENTLY NOT NEEDED if - * (valueType==ValidationHandle.VALUE_USER_DEFINED_LIST) { // ensure Mso - * Drop-downs are defined int objId = - * this.mysheet.insertDropDownBox(d.getColNumber()); //TODO: verify that drop - * down lists are SHARED **** - * this.mysheet.getDvalRec().setObjectIdentifier(objId); } - */ - d.setTypeOperator(condition); - d.setErrorBoxText(errorBoxText); - d.setErrorBoxTitle(errorBoxTitle); - d.setPromptBoxText(promptBoxText); - d.setPromptBoxTitle(promptBoxTitle); - if (firstCondition != null) - d.setFirstCond(firstCondition); - if (secondCondition != null) - d.setSecondCond(secondCondition); - ValidationHandle vh = new ValidationHandle(d); - return vh; - } - - /** - * Return all validation handles that refer to this worksheet - * - * @return array of all validationhandles valid for this worksheet - */ - public ValidationHandle[] getAllValidationHandles() { - if (mysheet.getDvRecs() == null) - return new ValidationHandle[0]; - ValidationHandle[] vh = new ValidationHandle[mysheet.getDvRecs().size()]; - List dvrecs = mysheet.getDvRecs(); - for (int i = 0; i < vh.length; i++) { - vh[i] = new ValidationHandle((Dv) dvrecs.get(i)); - } - return vh; - } - - /** - * return true if sheet contains data validations - * - * @return boolean - */ - public boolean hasDataValidations() { - return (mysheet.getDvRecs() != null); - } - - /** - * Returns a list of all AutoFilterHandles on this sheet
                  - * An AutoFilterHandle allows access and manipulation of AutoFilters on the - * sheet - * - * @return array of AutoFilterHandles if any exist on sheet, null otherwise - */ - public AutoFilterHandle[] getAutoFilterHandles() { - if (mysheet.getAutoFilters() == null) - return null; - AutoFilterHandle[] af = new AutoFilterHandle[mysheet.getAutoFilters().size()]; - List afs = mysheet.getAutoFilters(); - for (int i = 0; i < afs.size(); i++) { - af[i] = new AutoFilterHandle((AutoFilter) afs.get(i)); - } - return af; - } - - /** - * Adds a new AutoFilter for the specified column (0-based) in this sheet
                  - * returns a handle to the new AutoFilter - * - * @param int column - column number to add an AutoFilter to - * @return AutoFilterHandle - */ - public AutoFilterHandle addAutoFilter(int column) { - AutoFilter af = mysheet.addAutoFilter(column); - return new AutoFilterHandle(af); - } - - /** - * Removes all AutoFilters from this sheet
                  - * As a consequence, all previously hidden rows are shown or unhidden - */ - public void removeAutoFilters() { - mysheet.removeAutoFilter(); - } - - /** - * Updates the Row filter (hidden status) for each row on the sheet by - * evaluating all AutoFilter conditions - *

                  - * NOTE: This method must be called after Autofilter updates or additions - * in order to see the results of the AutoFilter(s) - *

                  - * NOTE: this evaluation is NOT done automatically due to performance - * considerations, and is designed to be called after all additions and updating - * is completed (as evaluation may be time-consuming) - */ - public void evaluateAutoFilters() { - mysheet.evaluateAutoFilters(); - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - if (mysheet != null) - mysheet.close(); - addedrows.clear(); - addedrows = new CompatibleVector(); - mysheet = null; - mybook = null; - wbh = null; - dateFormats.clear(); - dateFormats = null; - } - - protected Boundsheet getBoundsheet() { - return this.mysheet; - } - - /** - * Imports the given CSV data into this worksheet. All rows in the input will be - * inserted sequentially before any rows which already exist in this worksheet. - *

                  - * To change the value delimiter set the system property - * "{@code io.starter.OpenXLS.csvdelimiter}". - */ - public void readCSV(BufferedReader input) throws IOException { - int rws = 0; - String field_delimiter = System.getProperty("io.starter.OpenXLS.csvdelimiter", ","); - - String thisLine = ""; - while ((thisLine = input.readLine()) != null) { // while loop begins here - String[] vals = StringTool.getTokensUsingDelim(thisLine, field_delimiter); - Object[] data = new Object[vals.length]; - for (int t = 0; t < vals.length; t++) { - vals[t] = StringTool.strip(vals[t], '"'); - try { - int i = Integer.parseInt(vals[t]); - data[t] = Integer.valueOf(i); - - double d = Double.parseDouble(vals[t]); - data[t] = new Double(d); - - } catch (NumberFormatException ax) { - // it's a string! - data[t] = vals[t]; - } - } - - insertRow(rws++, data, true); - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/WorkSheetHandle.kt b/src/main/java/io/starter/OpenXLS/WorkSheetHandle.kt new file mode 100644 index 0000000..ab05265 --- /dev/null +++ b/src/main/java/io/starter/OpenXLS/WorkSheetHandle.kt @@ -0,0 +1,2995 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.OpenXLS + +import io.starter.formats.XLS.WorkBook +import io.starter.formats.XLS.* +import io.starter.formats.XLS.charts.Chart +import io.starter.formats.XLS.formulas.GenericPtg +import io.starter.formats.XLS.formulas.Ptg +import io.starter.formats.XLS.formulas.PtgRef +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool + +import java.io.* +import java.sql.Timestamp +import java.util.* + +/** + * The WorkSheetHandle provides a handle to a Worksheet within an XLS file

                  + * and includes convenience methods for working with the Cell values within the + * sheet.

                  + *

                  + * for example:

                  + *

                  + *

                  WorkBookHandle book = new WorkBookHandle("testxls.xls");

                  + * WorkSheetHandle sheet1 = book.getWorkSheet("Sheet1");

                  + * CellHandle cell = sheet1.getCell("B22");

                  + * + *

                  + * to add a cell:

                  + *

                  + * CellHandle cell = sheet1.add("Hello World","C22");

                  + * + *

                  + * to add a numeric cell:

                  + *

                  + * CellHandle cell = sheet1.add(Integer.valueOf(120),"C23");

                  + * + *

                  + * to add a formula cell:

                  + *

                  + * CellHandle cell = sheet1.add("=PI()","C24");

                  + * +
                  *

                  + *

                  + * + * @see WorkSheet + * + * @see WorkBookHandle + * + * @see CellHandle + */ +class WorkSheetHandle +/** + * Constructor which takes a WorkBook and sheetname as parameters. + * + * @param sht The name of the WorkSheet + * @param mybk The WorkBook + */ +(sht: Boundsheet, b: WorkBookHandle) : Handle { + + /** + * Returns the underlying low-level Boundsheet object. + * + * @return Boundsheet sheet + */ + var sheet: Boundsheet? = null + private set + private var mybook: WorkBook? = null + /** + * Returns the WorkBookHandle for this Sheet + * + * @return + */ + var workBook: WorkBookHandle? = null + internal set + private val DEBUGLEVEL = 0 + private var dateFormats: Hashtable? = Hashtable() + /** + * @return setting on whether to use cache or not + */ + var useCache = true + private set // 20080917 KSC: set var for caching, default to true [BugTracker 1862] + + /** + * Get the first row on the Worksheet + * + * @return the Minimum Row Number on the Worksheet + */ + val firstRow: Int + get() = sheet!!.minRow + + /** + * Get the first column on the Worksheet + * + * @return the Minimum Column Number on the Worksheet + */ + val firstCol: Int + get() = sheet!!.minCol + + /** + * Get the last row on the Worksheet + * + * @return the Maximum Row Number on the Worksheet + */ + val lastRow: Int + get() = sheet!!.maxRow + + /** + * Get the last column on the Worksheet + * + * @return the Maximum Column Number on the Worksheet + */ + val lastCol: Int + get() = sheet!!.maxCol + + /** + * Gets the hash of the sheet protection password. This method returns the + * hashed password as stored in the file. It has been passed through a one-way + * hash function. It is therefore not possible to recover the actual password. + * You can, however, use [.setProtectionPasswordHashed] to apply the same + * password to another worksheet. + * + * @return the password hash or "0000" if the sheet doesn't have a password + */ + val hashedProtectionPassword: String + get() = sheet!!.protectionManager.password + + /** + * Returns whether the sheet is protected. Note that this is separate from + * whether the sheet has a protection password. It can be protected without a + * password or have a password but not be protected. + * + * @return whether protection is enabled for the sheet + */ + /** + * Sets whether the worksheet is protected. + * + * @param protect whether worksheet protection should be enabled + */ + var protected: Boolean + get() = sheet!!.protectionManager.protected + set(protect) { + sheet!!.protectionManager.protected = protect + } + + /** + * get whether this sheet is selected upon opening the file. + * + * @return boolean b selected state + */ + /** + * set whether this sheet is selected upon opening the file. + * + * @param boolean b selected value + */ + var selected: Boolean + get() = sheet!!.selected() + set(b) = sheet!!.setSelected(b) + + /** + * get whether this sheet is hidden from the user opening the file. + * + * @return boolean b hidden state + */ + /** + * set whether this sheet is hidden from the user opening the file. + * + * + * if the sheet is selected, the API will set the first visible sheet to + * selected as you cannot have your selected sheet be hidden. + * + * + * to override this behavior, set your desired sheet to selected after calling + * this method. + * + * @param boolean b hidden state + */ + // set the next sheet selected... + var hidden: Boolean + get() = sheet!!.hidden + set(b) { + var h = 0 + if (b) + h = Boundsheet.HIDDEN.toInt() + sheet!!.setHidden(h) + if (sheet!!.sheetNum == 0) { + try { + val s2 = mybook!!.getWorkSheetByNumber(sheet!!.sheetNum + 1) + mybook!!.setFirstVisibleSheet(s2) + } catch (e: WorkSheetNotFoundException) { + } + + } + if (sheet!!.selected()) { + try { + var x = 1 + var s2 = mybook!!.getWorkSheetByNumber(sheet!!.sheetNum + x) + while (s2.hidden) + s2 = mybook!!.getWorkSheetByNumber(sheet!!.sheetNum + x++) + s2.setSelected(true) + } catch (e: WorkSheetNotFoundException) { + } + + } + } + + /** + * return the 'veryhidden' state of the sheet + * + * @return + */ + /** + * set whether this sheet is VERY hidden opening the file. + * + * + * VERY hidden means users will not be able to unhide the sheet without using VB + * code. + * + * @param boolean b hidden state + */ + // set the next sheet selected... + var veryHidden: Boolean + get() = sheet!!.veryHidden + set(b) { + var h = 0 + if (b) + h = Boundsheet.VERY_HIDDEN.toInt() + sheet!!.setHidden(h) + val t = sheet!!.sheetNum + try { + val s2 = mybook!!.getWorkSheetByNumber(t + 1) + s2.setSelected(true) + } catch (e: WorkSheetNotFoundException) { + } + + } + + /** + * get the tab display order of this Worksheet + * + * + * this is a zero based index with zero representing the left-most WorkSheet + * tab. + * + * @return int idx the index of the sheet tab + */ + /** + * set the tab display order of this Worksheet + * + * + * this is a zero based index with zero representing the left-most WorkSheet + * tab. + * + * @param int idx the new index of the sheet tab + */ + var tabIndex: Int + get() = sheet!!.sheetNum + set(idx) = sheet!!.workBook!!.changeWorkSheetOrder(sheet, idx) + + /** + * returns all of the Columns in this WorkSheet + * + * @return ColHandle[] Columns + */ + val columns: Array + get() { + val columns = CompatibleVector() + + for (c in sheet!!.colinfos) { + try { + val start = c.colFirst + val end = c.colLast + for (i in start..end) { + try { + columns.add(this.getCol(i)) + } catch (e: ColumnNotFoundException) { + } + + } + } catch (ex: Exception) { + } + + } + + return columns.toTypedArray() as Array + } + + /** + * returns a List of Column names + * + * @return List column names + */ + val colNames: List<*> + get() = sheet!!.colNames + + /** + * returns a List of Row numbers + * + * @return List of row numbers + */ + val rowNums: List<*> + get() = sheet!!.rowNums + + /** + * get an array of all RowHandles for this WorkSheet + * + * @return RowHandle[] all Rows on this WorkSheet + */ + val rows: Array + get() { + val rs = sheet!!.rows + val ret = arrayOfNulls(rs.size) + for (t in rs.indices) { + ret[t] = RowHandle(rs[t], this) + } + return ret + } + + /** + * get an array of BIFFREC Rows + * + * @return RowHandle[] all Rows on this WorkSheet + */ + val rowMap: Map<*, *> + get() = sheet!!.rowMap + + /** + * Returns the number of rows in this WorkSheet + * + * @return int Number of Rows on this WorkSheet + */ + val numRows: Int + get() = this.sheet!!.numRows + + /** + * Returns the number of Columns in this WorkSheet + * + * @return int Number of Cols on this WorkSheet + */ + val numCols: Int + get() = this.sheet!!.numCols + + /** + * Returns the index of the Sheet. + * + * @return String Sheet Name + */ + val sheetNum: Int + get() = sheet!!.sheetNum + + /** + * Returns all Named Range Handles scoped to this Worksheet. + * + * + * Note this will not include workbook scoped named ranges + * + * @return NameHandle[] all of the Named ranges that are scoped to the present + * worksheet + */ + val namedRangesInScope: Array + get() { + val nand = sheet!!.sheetScopedNames + val nands = arrayOfNulls(nand.size) + for (x in nand.indices) { + nands[x] = NameHandle(nand[x], this.workBook) + } + return nands + } + + /** + * Returns the name of the Sheet. + * + * @return String Sheet Name + */ + /** + * Set the name of the Worksheet. This method will change the name on the + * Worksheet's tab as displayed in the WorkBook, as well as all programmatic and + * internal references to the name. + * + * + * This change takes effect immediately, so all attempts to reference the + * Worksheet by its previous name will fail. + * + * @param String the new name for the Worksheet + */ + // keep sheethandles (name->wsh) updated + var sheetName: String + get() = sheet!!.sheetName + set(name) { + workBook!!.sheethandles!!.remove(this.sheetName) + sheet!!.sheetName = name + workBook!!.sheethandles!![name] = this + } + + /** + * return the sheetname properly qualified or quoted used when the sheetname + * contains spaces, commas or parentheses + * + * @return + */ + val qualifiedSheetName: String? + get() = GenericPtg.qualifySheetname(sheet!!.sheetName) + + /** + * Returns the Serialized bytes for this WorkSheet. + * + * + * The output of this method can be used to insert a copy of this WorkSheet into + * another WorkBook using the WorkBookHandle.addWorkSheet(byte[] serialsheet, + * String NewSheetName) method. + * + * @return byte[] the WorkSheet's Serialized bytes + * @see WorkBookHandle.addWorkSheet + */ + // return mysheet.getSheetBytes(); + val serialBytes: ByteArray? + get() { + sheet!!.setLocalRecs() + var obs: ObjectOutputStream? = null + var b: ByteArray? = null + try { + val baos = ByteArrayOutputStream() + val bufo = BufferedOutputStream(baos) + obs = ObjectOutputStream(bufo) + obs.writeObject(sheet) + bufo.flush() + b = baos.toByteArray() + } catch (e: Throwable) { + Logger.logWarn("Serializing Sheet: $this failed: $e") + } + + return b + } + + var addedrows = CompatibleVector() + private set + private val range_init = true + + /** + * returns an array of FormatHandles for the ConditionalFormats applied to this + * cell + * + * @return an array of FormatHandles, one for each of the Conditional Formatting + * rules + */ + val conditionalFormatHandles: Array + get() { + val cfx = arrayOfNulls(this.sheet!!.conditionalFormats!!.size) + for (i in cfx.indices) { + val cfmt = this.sheet!!.conditionalFormats!![i] as Condfmt + cfx[i] = ConditionalFormatHandle(cfmt, this) + } + return cfx + } + + /** + * Get a handle to all of the images in this worksheet + * + * @return + */ + val images: Array? + get() = this.sheet!!.images + + /** + * returns the actual amount of images contained in the sheet and is determined + * by imageMap + * + * @return + */ + val numImages: Int + get() = this.sheet!!.imageMap!!.size + + /** + * Get the current fast add cell mode for this worksheet + */ + /** + * Toggle fast cell add mode. + * + * + * Set to true to turn off checking for existing cells, conditional formats and + * merged ranges in order to accelerate adding new cells + * + * @param fastadds whether to disable checking for existing cells and + */ + var fastCellAdds: Boolean + get() = this.sheet!!.fastCellAdds + set(fastadds) = this.sheet!!.setFastCellAdds(fastadds) + + /** + * Returns all CellHandles defined on this WorkSheet. + * + * @return CellHandle[] - the array of Cells in the Sheet + */ + // Handle MULBLANKS proper column number + // try harder + // handle Mulblanks: ref a range of cells; to get correct cell address, + // traverse thru range and set cellhandle ref to correct column + // for Mulblank use only -sets correct column reference for multiple blank + // cells ... + val cells: Array + get() { + val cells = sheet!!.cells + val retval = arrayOfNulls(cells.size) + var aMul: Mulblank? = null + var c: Short = -1 + for (i in retval.indices) { + try { + if (cells[i].opcode != XLSConstants.MULBLANK) { + retval[i] = getCell(cells[i].rowNumber, cells[i].colNumber.toInt()) + } else { + if (cells[i] === aMul) { + c++ + } else { + aMul = cells[i] as Mulblank + c = aMul.colFirst.toShort() + } + retval[i] = getCell(cells[i].rowNumber, c.toInt()) + } + } catch (cnfe: CellNotFoundException) { + retval[i] = CellHandle(cells[i], this.workBook) + retval[i].workSheetHandle = this + if (cells[i].opcode == XLSConstants.MULBLANK) { + if (cells[i] === aMul) { + c++ + } else { + aMul = cells[i] as Mulblank + c = aMul.colFirst.toShort() + } + retval[i].setBlankRef(c.toInt()) + } + } + + } + return retval + } + + /** + * Get the text for the Footer printed at the bottom of the Worksheet + * + * @return String footer text + */ + /** + * Set the text for the Footer printed at the bottom of the Worksheet + * + * @param String footer text + */ + var footerText: String? + get() = sheet!!.footer!!.footerText + set(t) { + sheet!!.footer!!.footerText = t + } + + /** + * Get the text for the Header printed at the top of the Worksheet + * + * @return String header text + */ + /** + * Set the text for the Header printed at the top of the Worksheet + * + * @param String header text + */ + var headerText: String? + get() = sheet!!.header!!.headerText + set(t) { + sheet!!.header!!.headerText = t + } + + /** + * Get the print area set for this WorkSheetHandle. + * + * + * If no print area is set return null; + */ + val printArea: String? + get() = sheet!!.printArea + + /** + * Get the Print Titles set for this WorkSheetHandle. + * + * + * If no Print Titles are set, this returns null; + */ + val printTitles: String? + get() = sheet!!.printTitles + + /** + * Get the printer settings handle for this WorkSheetHandle. + */ + val printerSettings: PrinterSettingsHandle + get() = sheet!!.printerSetupHandle + + /** + * Set the default column width of the worksheet + * + * + *

                  + * + * + * This setting is roughly the width of the character '0' The default width of a + * column is 8. + */ + var defaultColWidth: Int + get() = sheet!!.defaultColumnWidth.toInt() + set(t) { + sheet!!.defaultColumnWidth = t + } + + /** + * Set whether to show calculated formula results in the output sheet. + * + * @return boolean whether to show calculated formula results + */ + var showFormulaResults: Boolean + get() = this.sheet!!.window2!!.showFormulaResults + set(b) { + this.sheet!!.window2!!.showFormulaResults = b + } + + /** + * Get whether to show gridlines in the output sheet. + * + * @param boolean whether to show gridlines + */ + /** + * Set whether to show gridlines in the output sheet. + * + * @return boolean whether to show gridlines + */ + var showGridlines: Boolean + get() = this.sheet!!.window2!!.showGridlines + set(b) { + this.sheet!!.window2!!.showGridlines = b + } + + /** + * Get whether to show sheet headers in the output sheet. + * + * @return boolean whether to show sheet headers + */ + /** + * Set whether to show sheet headers in the output sheet. + * + * @param boolean whether to show sheet headers + */ + var showSheetHeaders: Boolean + get() = this.sheet!!.window2!!.showSheetHeaders + set(b) { + this.sheet!!.window2!!.showSheetHeaders = b + } + + /** + * Get whether to show zero values in the output sheet. + * + * @return boolean whether to show zero values + */ + /** + * Set whether to show zero values in the output sheet. + * + * @return boolean whether to show zero values + */ + var showZeroValues: Boolean + get() = this.sheet!!.window2!!.showZeroValues + set(b) { + this.sheet!!.window2!!.showZeroValues = b + } + + /** + * Get whether to show outline symbols in the output sheet. + * + * @return boolean whether to outline symbols + */ + /** + * Set whether to show outline symbols in the output sheet. + * + * @param boolean whether to show outline symbols + */ + var showOutlineSymbols: Boolean + get() = this.sheet!!.window2!!.showOutlineSymbols + set(b) { + this.sheet!!.window2!!.showOutlineSymbols = b + } + + /** + * Get whether to show normal view or page break preview view in the output + * sheet. + * + * @return boolean whether to show normal view or page break preview view + */ + /** + * Set whether to show normal view or page break preview view in the output + * sheet. + * + * @param boolean whether to show normal view or page break preview view + */ + // opposite of expected behavior + var showInNormalView: Boolean + get() = this.sheet!!.window2!!.showInNormalView + set(b) { + this.sheet!!.window2!!.showInNormalView = !b + } + + /** + * if this sheet has freeze panes, return the address of the top left cell + * otherwise, return null + * + * @return + */ + val topLeftCell: String? + get() = if (this.sheet!!.pane != null) { + this.sheet!!.pane!!.topLeftCell + } else null + + /** + * gets the zoom for the sheet + * + * @return the zoom as a float percentage (.25 = 25%) + */ + /** + * sets the zoom for the sheet + * + * @param the zoom as a float percentage (.25 = 25%) + */ + var zoom: Float + get() = this.sheet!!.scl.zoom + set(zm) { + this.sheet!!.scl.zoom = zm + } + + /** + * Gets the row number (0 based) that the sheet split is located on. If the + * sheet is not split returns -1 + * + * @return + */ + val splitRowLocation: Int + get() = if (this.sheet!!.pane == null) -1 else this.sheet!!.pane!!.visibleRow + + /** + * gets the column number (0-based)that the sheet split is locaated on; if the + * sheet is not split, returns -1 + * + * @return 0-based index of split column, if any + */ + val splitColLocation: Int + get() = if (this.sheet!!.pane == null) -1 else this.sheet!!.pane!!.visibleCol + + /** + * Gets the twips split location returns -1 + * + * @return + */ + val splitLocation: Int + get() = if (this.sheet!!.pane == null) -1 else this.sheet!!.pane!!.rowSplitLoc + + /** + * Get whether to use manual grid color in the output sheet. + * + * @return boolean whether to use manual grid color + */ + /** + * Set whether to use manual grid color in the output sheet. + * + * @param boolean whether to use manual grid color + */ + var manualGridLineColor: Boolean + get() = this.sheet!!.window2!!.manualGridLineColor + set(b) { + this.sheet!!.window2!!.manualGridLineColor = b + } + + /** + * returns an array of all CommentHandles that exist in the sheet + * + * @return + */ + val commentHandles: Array + get() { + val notes = getMysheet()!!.notes + val nHandles = arrayOfNulls(notes.size) + for (i in nHandles.indices) { + nHandles[i] = CommentHandle(notes[i] as Note) + } + return nHandles + } + + /** + * Return all validation handles that refer to this worksheet + * + * @return array of all validationhandles valid for this worksheet + */ + val allValidationHandles: Array + get() { + if (sheet!!.dvRecs == null) + return arrayOfNulls(0) + val vh = arrayOfNulls(sheet!!.dvRecs!!.size) + val dvrecs = sheet!!.dvRecs + for (i in vh.indices) { + vh[i] = ValidationHandle(dvrecs!![i] as Dv) + } + return vh + } + + /** + * Returns a list of all AutoFilterHandles on this sheet

                  + * An AutoFilterHandle allows access and manipulation of AutoFilters on the + * sheet + * + * @return array of AutoFilterHandles if any exist on sheet, null otherwise + */ + val autoFilterHandles: Array? + get() { + if (sheet!!.autoFilters == null) + return null + val af = arrayOfNulls(sheet!!.autoFilters.size) + val afs = sheet!!.autoFilters + for (i in afs.indices) { + af[i] = AutoFilterHandle(afs[i] as AutoFilter) + } + return af + } + // public Map cellhandles = new HashMap(); + + fun addChart(serialchart: ByteArray, name: String, coords: ShortArray) { + sheet!!.addChart(serialchart, name, coords) + } + + /** + * Sets whether the worksheet is protected. If `protect` is + * `true`, the worksheet will be protected and the password will be + * set to `password`. If it's `false`, the worksheet will + * be unprotected and the password will be removed. + * + * @param protect whether the worksheet should be protected + * @param password the password to set if protect is `true`. ignored when + * protect is `false`. + * @throws WorkBookException never. This used to be thrown when unprotecting if the password + * was incorrect. + */ + @Throws(WorkBookException::class) + fun setProtected(protect: Boolean, password: String) { + val protector = sheet!!.protectionManager + + // we need to check if this password can be used to unprotect... + // otherwise it is totally insecure... + val oldpass = protector.password + + val pss = Password() + pss.setPassword(password) + val passcheck = pss.passwordHashString + + if (oldpass != null) { + if (oldpass != passcheck && oldpass !== "0000") { + throw WorkBookException("Incorrect Password Attempt to Unprotect Worksheet.", + WorkBookException.SHEETPROTECT_INCORRECT_PASSWORD) + } + } + protector.protected = protect + protector.password = if (protect) password else null + } + + /** + * Sets the password used to unlock the sheet when it is protected. + * + * @param password the clear text of the password to be applied or null to remove the + * existing password + */ + fun setProtectionPassword(password: String) { + sheet!!.protectionManager.password = password + } + + /** + * Sets the password used to unlock the sheet when it is protected. This method + * is useful in combination with [.getHashedProtectionPassword] to copy + * the password from one worksheet to another. + * + * @param hash the hash of the protection password to be applied or null to + * remove the existing password + */ + fun setProtectionPasswordHashed(hash: String) { + sheet!!.protectionManager.setPasswordHashed(hash) + } + + /** + * Checks whether the given password matches the protection password. + * + * @param guess the password to be checked against the stored hash + * @return whether the given password matches the stored hash + */ + fun checkProtectionPassword(guess: String): Boolean { + return sheet!!.protectionManager.checkPassword(guess) + } + + /** + * Sets the worksheet enhanced protection option + * + * @param int protectionOption + * @see WorkBookHandle.iprot options + */ + fun setEnhancedProtection(protectionOption: Int, set: Boolean) { + sheet!!.protectionManager.setProtected(protectionOption, set) + } + + /** + * returns true if the indicated Enhanced Protection Setting is turned on + * + * @param protectionOption + * @return boolean true if the indicated Enhanced Protection Setting is turned + * on + * @see WorkBookHandle.iprot options + */ + fun getEnhancedProtection(protectionOption: Int): Boolean { + return sheet!!.protectionManager.getProtected(protectionOption) + } + + /** + * set this WorkSheet as the first visible tab on the left + */ + fun setFirstVisibleTab() { + sheet!!.workBook!!.setFirstVisibleSheet(sheet!!) + } + + /** + * returns the ColHandle for the column at index position the column index is + * zero based ie: column A = 0 + * + * @return ColHandle the Column + */ + @Throws(ColumnNotFoundException::class) + fun getCol(clnum: Int): ColHandle { + var ci = sheet!!.getColInfo(clnum) + val mycol: ColHandle + if (ci == null || !ci.isSingleCol) { + try { + if (ci == null) { + ci = sheet!!.createColinfo(clnum, clnum) + } else { + ci = sheet!!.createColinfo(clnum, clnum, ci) + } + mycol = ColHandle(ci, this) + + } catch (e: Exception) { + throw ColumnNotFoundException("Unable to getCol for col number $clnum $e") + } + + } else { + mycol = ColHandle(ci, this) // usual case + } + return mycol + } + + /** + * adds the column (col1st, colLast) and returns the new ColHandle + * + * @param c1st + * @param clast + * @return + */ + @Deprecated("use addCol(int)") + fun addCol(c1st: Int, clast: Int): ColHandle { + val ci = sheet!!.createColinfo(c1st, clast) + return ColHandle(ci, this) + } + + /** + * adds the column (col1st, colLast) and returns the new ColHandle + * + * @param colNum, zero based number of the column + * @return ColHandle + */ + fun addCol(colNum: Int): ColHandle { + val ci = sheet!!.createColinfo(colNum, colNum) + return ColHandle(ci, this) + } + + /** + * returns the Column at the named position + * + * @return ColHandle the Column + */ + @Throws(ColumnNotFoundException::class) + fun getCol(name: String): ColHandle { + return this.getCol(ExcelTools.getIntVal(name)) + } + + /** + * returns the RowHandle for the row at index position + * + * + * the row index is zero based ie: Excel row 1 = 0 + * + * @param int row number to return + * @return RowHandle a Row on this WorkSheet + */ + @Throws(RowNotFoundException::class) + fun getRow(t: Int): RowHandle { + val x = sheet!!.getRowByNumber(t) ?: throw RowNotFoundException("Row " + t + " not found in :" + this.sheetName) + return RowHandle(x, this) + } + + /** + * Returns whether a Cell exists in the WorkSheet. + * + * @param String the address of the Cell to check for + * @return boolean whether the Cell exists + */ + internal fun hasCell(addr: String): Boolean { + try { + this.getCell(addr) + return true + } catch (e: CellNotFoundException) { + return false + } + + } + + /** + * Remove a Cell from this WorkSheet. + * + * @param CellHandle to remove + */ + fun removeCell(celldel: CellHandle) { + sheet!!.removeCell(celldel.cell!!) + } + + /** + * removes an Image from the Spreadsheet + * + * + * + * + * Jan 22, 2010 + * + * @param img + */ + fun removeImage(img: ImageHandle) { + sheet!!.removeImage(img) + img.remove() + } + + /** + * Remove a Cell from this WorkSheet. + * + * @param String celladdr - the Address of the Cell to remove + */ + fun removeCell(celladdr: String) { + sheet!!.removeCell(celladdr.toUpperCase()) + } + + /** + * Remove a Row and all associated Cells from this WorkSheet. + * + * @param int + * rownum - the number of the row to remove not used public void + * removeRow(int rownum) throws RowNotFoundException{ + * mysheet.removeRow(rownum); } + */ + + /** + * Remove a Row and all associated Cells from this WorkSheet. Optionally shift + * all rows below target row up one. + * + * @param int rownum - the number of the row to remove + * @param boolean shiftrows - true will shift all lower rows up one. + */ + @Throws(RowNotFoundException::class) + fun removeRow(rownum: Int, shiftrows: Boolean) { + if (shiftrows) + removeRow(rownum) + else + removeRow(rownum, WorkSheetHandle.ROW_DELETE_NO_REFERENCE_UPDATE) + } + + /** + * Remove all cells and formatting from a row within this WorkSheet. Has no + * other affect upon the workbook + * + * @param int rownum - the number of the row contents to remove + */ + @Throws(RowNotFoundException::class) + fun removeRowContents(rownum: Int) { + sheet!!.removeRowContents(rownum) + } + + /** + * Remove a Row and all associated Cells from this WorkSheet. + * + * @param int rownum - the number of the row to remove + * @param int flag - controls whether row deletions updates references as well + * ... + */ + @Throws(RowNotFoundException::class) + @JvmOverloads + fun removeRow(rownum: Int, flag: Int = WorkSheetHandle.ROW_DELETE) { + + /* TODO: deal with merges! */ + sheet!!.removeRows(rownum, 1, true) + + // Delete chart series IF SERIES ARE ROW-BASED -- do before updateReferences + val charts = this.sheet!!.charts + for (i in charts.indices) { + val sht = GenericPtg.qualifySheetname(this.sheetName) + val c = charts[i] as Chart + val seriesmap = c.seriesPtgs + val ii = seriesmap.keys.iterator() + while (ii.hasNext()) { + val s = ii.next() as io.starter.formats.XLS.charts.Series + val ptgs = seriesmap[s] as Array + var pr: PtgRef + var cursheet: String? + var rc: IntArray? + for (j in ptgs.indices) { + try { + pr = ptgs[j] as PtgRef + cursheet = pr.sheetName + rc = pr.intLocation + if (rc!![1] != rc[3] && sht!!.equals(cursheet!!, ignoreCase = true)) { // series are in rows, if existing + // series fall within deleted row + if (rc[0] == rownum - 1) { + c.removeSeries(j) + break // got it + } + } else + break // isn't row-based so split + } catch (e: Exception) { + continue // shouldn't happen! + } + + } + } + // also shift chart up if necessary [BugTracker 2858] + val row = c.row0 + // only move images whose top is >= rnum + val rnum = rownum + 1 + if (row > rnum) { + val h = c.height + // move down 1 row + c.setRow(row - 1) + c.height = h + } + } + + if (flag != WorkSheetHandle.ROW_DELETE_NO_REFERENCE_UPDATE) + ReferenceTracker.updateReferences(rownum, -1, this.sheet!!, true) + + // Adjust image row so that height remains constant + val rnum = rownum + 1 + val images = this.images + for (i in images!!.indices) { + val ih = images[i] + val row = ih.row + // only move images whose top is >= rnum + if (row > rnum) { + val h = ih.height + // move down 1 row + ih.row = row - 1 + ih.setHeight(h.toInt()) + } + } + } + + /** + * Removes columns and all their associated cells from the sheet. This method + * does not shift the subsequent columns left, for that use [.removeCols]. + * + * @param first the zero-based index of the first column to be removed + * @param count the number of columns to remove. + */ + fun clearCols(first: Int, count: Int) { + this.removeCols(first, count, false) + } + + /** + * Removes columns from the sheet and shifts the following columns left. + * + * @param first the zero-based index of the first column to be removed + * @param count the number of columns to remove. + */ + fun removeCols(first: Int, count: Int) { + this.removeCols(first, count, true) + } + + private fun removeCols(first: Int, count: Int, shift: Boolean) { + if (first < 0) + throw IllegalArgumentException("column index must be zero or greater") + if (count < 1) + throw IllegalArgumentException("count must be at least one") + sheet!!.removeCols(first, count, shift) + } + + /** + * Removes a column and all associated cells from this sheet. This does not + * shift subsequent columns. + * + * @param colstr the name of the column to remove + */ + @Deprecated("Use {@link #clearCols} instead.") + @Throws(ColumnNotFoundException::class) + fun removeCol(colstr: String) { + this.removeCols(ExcelTools.getIntVal(colstr), 1, false) + } + + /** + * Remove a column and all associated cells from this sheet. Optionally shift + * all subsequent columns left to fill the gap. + * + * @param colstr the name of the column to remove + * @param shiftcols whether to shift subsequent columns + */ + @Deprecated("Use {@link #removeCols} or {@link #clearCols} instead.") + @Throws(ColumnNotFoundException::class) + fun removeCol(colstr: String, shiftcols: Boolean) { + this.removeCols(ExcelTools.getIntVal(colstr), 1, shiftcols) + } + + /** + * Returns a Named Range Handle if it exists in the specified scope. + * + * + * This can be used to distinguish between multiple named ranges with the same + * name but differing scopes + * + * @return NameHandle a Named range in the Worksheet that exists in the scope + */ + @Throws(CellNotFoundException::class) + fun getNamedRangeInScope(rangename: String): NameHandle { + val nand = sheet!!.getScopedName(rangename) ?: throw CellNotFoundException(rangename) + return NameHandle(nand, this.workBook) + } + + /** + * write this sheet as tabbed text output:

                  + * All rows and all characters in each cell are saved. Columns of data are + * separated by tab characters, and each row of data ends in a carriage return. + * If a cell contains a comma, the cell contents are enclosed in double + * quotation marks. All formatting, graphics, objects, and other worksheet + * contents are lost. The euro symbol will be converted to a question mark. If + * cells display formulas instead of formula values, the formulas are saved as + * text. + */ + @Throws(IOException::class) + fun writeAsTabbedText(dest: OutputStream) { + this.sheet!!.writeAsTabbedText(dest) + } + + init { + this.workBook = b + this.sheet = sht + this.mybook = sht.workBook + // 20080624 KSC: add flag for shift formula rules upon row insertion/deletion + val shiftRule = System.getProperties()["io.starter.OpenXLS.WorkSheetHandle.shiftInclusive"] as String + if (shiftRule != null && shiftRule.equals("true", ignoreCase = true)) { + sheet!!.setShiftRule(shiftRule.equals("true", ignoreCase = true)) + } + // 20080917 KSC: set cache setting via system property [BugTracker 1862] + if (System.getProperty("io.starter.OpenXLS.cacheCellHandles") != null) + useCache = java.lang.Boolean.valueOf(System.getProperty("io.starter.OpenXLS.cacheCellHandles")).booleanValue() + } + + /** + * Set the Object value of the Cell at the given address. + * + * @param String Cell Address (ie: "D14") + * @param Object new Cell Object value + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + @Throws(CellNotFoundException::class, CellTypeMismatchException::class) + fun setVal(address: String, `val`: Any) { + val c = this.getCell(address) + c.`val` = `val` + } + + /** + * Set the double value of the Cell at the given address + * + * @param String Cell Address (ie: "D14") + * @param double new Cell double value + * @param address + * @param d + * @throws io.starter.formats.XLS.CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + @Throws(CellNotFoundException::class, CellTypeMismatchException::class) + fun setVal(address: String, d: Double) { + val c = this.getCell(address) + c.setVal(d) + } + + /** + * Set the String value of the Cell at the given address + * + * @param String Cell Address (ie: "D14") + * @param String new Cell String value + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + @Throws(CellNotFoundException::class, CellTypeMismatchException::class) + fun setVal(address: String, s: String) { + val c = this.getCell(address) + c.`val` = s + } + + /** + * Set the int value of the Cell at the given address + * + * @param String Cell Address (ie: "D14") + * @param int new Cell int value + * @throws io.starter.formats.XLS.CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + @Throws(CellNotFoundException::class, CellTypeMismatchException::class) + fun setVal(address: String, i: Int) { + val c = this.getCell(address) + c.setVal(i) + } + + /** + * Get the Object value of a Cell. + * + * + * Numeric Cell values will return as type Long, Integer, or Double. String Cell + * values will return as type String. + * + * @return the value of the Cell as an Object. + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + @Throws(CellNotFoundException::class) + fun getVal(address: String): Any? { + val c = this.getCell(address) + return c.`val` + } + + /** + * Insert a row of Objects into the worksheet. Automatically shifts all rows + * below the cell down one. + * + * + * Method takes an array of Objects to insert into the rows. + * + * + * Object array must match columns in number starting with column A. + * + * + * For emptly cells, put a null Object reference in your array. + * + * + * example: Object[] newCellHandles = { null, // col A "Hello", // col B + * Integer.valueOf(120), // col C "=sum(A1+B1)", // col D null, // col E null, + * // col F "World" // col G }; + * + * + * CellHandle ret = sheet.insertRow(newCellHandles, 1, true); if(ret !=null) + * Logger.log("It worked"); + * + * @param an array of Objects to insert into the new row + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + */ + fun insertRow(row1: Int, data: Array): Array { + return insertRow(row1, data, true) + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + * + * + * Adding new cells to non-existent rows will automatically create new rows in + * the file, This method is only necessary to "move" existing cells by inserting + * empty rows. + * + * @param rownum the rownumber to insert + */ + fun insertRow(rownum: Int): Boolean { + return insertRow(rownum, null as Row?, ROW_INSERT, true) + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + * + * + * Adding new cells to non-existent rows will automatically create new rows in + * the file, This method is only necessary to "move" existing cells by inserting + * empty rows. + * + * + * Same as insertRow(rownum) except with addition of flag + * + * @param rownum the rownumber to insert + * @param flag row insertion rule + */ + fun insertRow(rownum: Int, flag: Int) { + insertRow(rownum, null as Row?, flag, true) + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + * + * + * This method differs from insertRow in that it can be used to repeatedly + * insert rows at the same row index. + * + * + * Adding new cells to non-existent rows will automatically create new rows in + * the file, + * + * + * After calling this method, setVal() can be used on the newly created cells to + * update with new values. + * + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + * @return whether the insert was successful + */ + fun insertRowAt(rownum: Int, shiftrows: Boolean): Boolean { + addedrows.remove(Integer.valueOf(rownum)) + return insertRow(rownum, sheet!!.getRowByNumber(rownum), rownum, shiftrows) + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + * + * + * Adding new cells to non-existent rows will automatically create new rows in + * the file, + * + * + * After calling this method, setVal() can be used on the newly created cells to + * update with new values. + * + * @param rownum the rownumber to insert (NOTE: rownum is 0-based) + * @param whether to shift down existing Cells + * @return whether the insert was successful + */ + fun insertRow(rownum: Int, shiftrows: Boolean): Boolean { + var myr: Row? = null + try { + myr = sheet!!.getRowByNumber(rownum) + } catch (e: Exception) { + } + + if (myr != null || shiftrows) { + return insertRow(rownum, myr, ROW_INSERT_MULTI, shiftrows) + } else { + // essentially a high performance row insert for the bottom of the workbook, + // used frequently in streaming workbook insertion + val newRow = sheet!!.insertRow(rownum, 0, ROW_INSERT_MULTI, shiftrows) + return true + } + + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + * + * + * Adding new cells to non-existent rows will automatically create new rows in + * the file, + * + * + * After calling this method, setVal() can be used on the newly created cells to + * update with new values. + * + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + */ + fun insertRow(rownum: Int, copyRow: RowHandle, flag: Int, shiftrows: Boolean): Boolean { + return this.insertRow(rownum, copyRow.myRow, flag, shiftrows) + } + + /** + * Insert a row of Objects into the worksheet. Shift all rows below the cell + * down one. + * + * + * Method takes an array of Objects to insert into the rows. + * + * + * Object array must match columns in number starting with column A. + * + * + * For emptly cells, put a null Object reference in your array. + * + * + * example: Object[] newCellHandles = { null, // col A "Hello", // col B + * Integer.valueOf(120), // col C "=sum(A1+B1)", // col D null, // col E null, + * // col F "World" // col G }; + * + * + * boolean okay = sheet.insertRow(newCellHandles, 1, true); + * if(okay)Logger.log("It worked"); + * + * @param an array of Objects to insert into the new row + * @param rownum the rownumber to insert + * @param whether to shift down existing Cells + */ + fun insertRow(rownum: Int, data: Array, shiftrows: Boolean): Array { + val retc = arrayOfNulls(data.size) + try { + insertRow(rownum, shiftrows) + for (t in data.indices) { + if (data[t] != null) + retc[t] = add(data[t], rownum, t) + } + } catch (ex: Exception) { + throw WorkBookException(ex.toString(), WorkBookException.RUNTIME_ERROR) + } + + return retc + } + + /** + * Insert a blank row into the worksheet. Shift all rows below the cell down + * one. + * + * + * Adding new cells to non-existent rows will automatically create new rows in + * the file, + * + * + * After calling this method, setVal() can be used on the newly created cells to + * update with new values. + * + * @param rownum the rownumber to insert (0-based) + * @param copyrow the row to copy formats and formulas from + * @param flag determines handling tracking of inserted rows and only allow + * insertion once + * @param whether to shift down existing Cells + */ + private fun insertRow(rownum: Int, copyRow: Row?, flag: Int, shiftrows: Boolean): Boolean { + val offset = 1 + return shiftRow(rownum, copyRow, flag, shiftrows, offset) + } + + /** + * replacement method for delete row that handles references better + * + * @param rownum + * @param flag + * @param shiftrows + * @return + */ + internal fun deleteRow(rownum: Int, flag: Int, shiftrows: Boolean): Boolean { + val offset = -1 + return shiftRow(rownum, null, flag, shiftrows, offset) + } + + /** + * insert/delete agnostic row copy/insert/delete and formula shifter + * + * + * TODO: Better comments + * + * @param rownum + * @param copyRow + * @param flag + * @param shiftrows + * @param offset + * @return + */ + private fun shiftRow(rownum: Int, copyRow: Row?, flag: Int, shiftrows: Boolean, offset: Int): Boolean { + var copyRow = copyRow + var offset = offset + + // If the copyrow is null, such as an insert row on an empty row, create that + // row, otherwise + // we end up using different logic for row insertion, which makes no sense. + if (copyRow == null) { + // insert a blank + this.add(null, "A" + rownum + 1) + copyRow = sheet!!.getRowByNumber(rownum) + + } + // handle tracking of inserted rows -- if flag is false rows can be inserted + // multiple times at the same index + if (flag == WorkSheetHandle.ROW_INSERT_ONCE) { // not inserted + if (this.addedrows.contains(Integer.valueOf(rownum))) + return false // can't add an existing row! + } + + // sheetster ui means insert + // 'on top of' row, shift down + if (offset == 0) + offset = 1 + + // shiftrefs BEFORE inserting new row + if (shiftrows && flag != WorkSheetHandle.ROW_INSERT_NO_REFERENCE_UPDATE) { + var refUpdateStart = rownum + // OpenXLS default behavior is to update one row too high. If we are using + // ROW_INSERT, update per excel standard, + // see TestInsertRows.testUpdateFormulaSettings() for testing + if (flag == WorkSheetHandle.ROW_INSERT) + refUpdateStart++ + ReferenceTracker.updateReferences(refUpdateStart, offset, this.sheet!!, true) // shift or expand/contract + // ALL affected references + // including named ranges + } + + val firstcol = copyRow!!.colDimensions[0] + val newRow = sheet!!.insertRow(rownum, firstcol, flag, shiftrows) // shifts rows down and inserts a new row, + // also shifts shared formula refs (see note + // below) + + // *************************************************************************************************************************************************************/ + // Named Range, Formula and AI references: + // ALL references to the inserted row# (rownum) and rows beyond are shifted in + // ReferenceTracker.updateReferences. + // This method uses the ReferenceTracker collection for the specific sheet in + // question to iterate through the stored references, + // shifting them as the shifting rules allow. + // In addition, all formulas in the copyrow will be duplicated in the newly + // inserted row; + // These formula references are shifted in ReferenceTracker.adjustFormulaRefs + // (see below) + // The only references that are NOT shifted in the schema described above are + // SharedFormula references (specifically, PtgRefN & PtgAreaA), + // which are NOT contained within the ReferenceTracker collection. + // These references are shifted "by hand" in + // ReferenceTracker.moveSharedFormulas, called upon + // insertRow->->Boundsheet.shiftCellRow + // *************************************************************************************************************************************************************/ + + if (shiftrows) + addedrows.add(Integer.valueOf(rownum)) + + // TODO: Why so much logic in here, move this to Boundsheet? + + if (shiftrows && copyRow != null) { + // Handle shifting reference rules for the newrow only and it's copyrow (NOTE: + // copyrow may have been shifted via insertRow above although it's references + // have not yet been shifted) + val refMovementDiff = copyRow.rowNumber - rownum // number of rows to shift + var refMovementRow = rownum // start row for shifting operation + if (refMovementDiff < 0) + refMovementRow += refMovementDiff // since copyrow < rownum, shifting should be done BEFORE insert row + newRow!!.rowHeight = copyRow.rowHeight + + // Now iterate through all cells in the original row and copy formats and + // formulas + val copyRowCells = copyRow.cellArray + val newmerges = Hashtable() + var newmerge: CellRange? = null + var newCellHandle: CellHandle? = null + var aMul: Mulblank? = null // KSC: Mulblank handling + var c: Short = -1 // "" + val sheetname = GenericPtg.qualifySheetname(this.toString()) + for (i in copyRowCells.indices) { + val copyRowCell = copyRowCells[i] as BiffRec + if (copyRowCell.opcode == XLSConstants.MULBLANK) { + if (copyRowCell === aMul) + c++ // ref next blank in range - nec. for ixfe (FormatId) see below + else { + aMul = copyRowCell as Mulblank + c = aMul.colFirst.toShort() + } + aMul.setCurrentCell(c) + } + val copyCellHandle = CellHandle(copyRowCell, this.workBook) + copyCellHandle.workSheetHandle = this + // this.cellhandles.put(copyCellHandle.getCellAddress(), copyCellHandle); + val colnum = copyCellHandle.colNum + + // insert an empty copy of the cell OR, if it's a formula cell, copy formula and + // adjust it's cell references appropriate for new cell position + if (copyRowCell.opcode == XLSRecord.FORMULA + && flag != WorkSheetHandle.ROW_INSERT_NO_REFERENCE_UPDATE + && flag != WorkSheetHandle.ROW_INSERT) { + try { + // copy copyrow's formula and then shift it's references relative to copycell's + // original row and references + newCellHandle = add(copyCellHandle.formulaHandle.formulaString, rownum, colnum) + // streaming parser uses fast cell adds which returns null, populate here + if (newCellHandle == null) { + try { + newCellHandle = this.getCell(rownum, colnum) + } catch (e: CellNotFoundException) { + // should be impossible } + } + + } + // because the original formula has been shifted by one, unshift this sucker... + ReferenceTracker.adjustFormulaRefs(newCellHandle!!, refMovementRow, refMovementDiff * -1, true) + newCellHandle.formulaHandle.formulaRec!!.clearCachedValue() + } catch (e: Exception) { + if (DEBUGLEVEL > 0) + Logger.logWarn("WorkSheetHandle.shiftRow() could not adjust formula references in formula: " + + copyCellHandle + " while inserting new row." + e.toString()) + } + + } else { + newCellHandle = add(null, rownum, colnum) + // streaming parser uses fast cell adds which returns null, populate here + if (newCellHandle == null) { + try { + newCellHandle = this.getCell(rownum, colnum) + } catch (e: CellNotFoundException) { + // should be impossible } + } + + } + } + newCellHandle!!.formatId = copyCellHandle.formatId + + // handle merged cells -- assemble the newmerges collection for below + val oby = copyCellHandle.mergedCellRange + if (oby != null) { // we have a merge + val fr = intArrayOf(rownum, oby.firstcellcol) + val lr = intArrayOf(rownum, oby.lastcellcol) + var newrng: String? = (sheetname + "!" + ExcelTools.formatLocation(fr) + ":" + + ExcelTools.formatLocation(lr)) + newrng = GenericPtg.qualifySheetname(newrng) + if (DEBUGLEVEL > 10) + Logger.logInfo("WorksheetHandle.insertRow() created new Merge Range: " + newrng!!) + // check if we've already created... + if (newmerges[newrng] == null) { + newmerge = CellRange(newrng, this.workBook, true) + if (DEBUGLEVEL > 10) + Logger.logInfo("WorksheetHandle.insertRow() created new Merge Range: " + newrng!!) + newmerges[newmerge.toString()] = newmerge + } + } + + } + // now update the new merge ranges... + val xl = newmerges.values + if (xl != null) { + val itx = xl.iterator() + while (itx.hasNext()) { + itx.next().mergeCells(true) + } + } + } + // Handle Image Movement + val images = sheet!!.images + if (images != null) { + for (i in images.indices) { + val ih = images[i] + val row = ih.row + // only move images whose top is >= copyRow + if (row >= rownum) { + // move down 1 row + val h = ih.height + ih.row = row + 1 + ih.setHeight(h.toInt()) + } + } + } + // Insert chart series IF SERIES ARE ROW-BASED + val charts = this.sheet!!.charts + for (i in charts.indices) { + val c = charts[i] as Chart + ReferenceTracker.insertChartSeries(c, GenericPtg.qualifySheetname(this.sheetName), rownum) + // also shift charts down [BugTracker 2858] + val row = c.row0 + // only move charts whose top is >= copyRow + if (row >= rownum) { + // move down 1 row + val h = c.height + c.setRow(row + offset) + c.height = h + } + } + return true + } + + /** + * Get a handle to all of the images in this worksheet + * + * @return + */ + @Throws(ImageNotFoundException::class) + fun getImage(name: String): ImageHandle { + val idz = sheet!!.imageVect.indexOf(name) + if (idz > 0) + return sheet!!.imageVect[idz] as ImageHandle + throw ImageNotFoundException("Could not find " + name + " in " + this.sheetName) + } + + /** + * write out all of the images in the Sheet to a directory + * + * @param imageoutput directory + */ + fun extractImagesToDirectory(outdir: String) { + val extracted = images + + // extract and output images + for (tx in extracted!!.indices) { + var n = extracted[tx].name + if (n == "") + n = "image" + extracted[tx].msodrawing!!.imageIndex + val imgname = n + "." + extracted[tx].type + if (DEBUGLEVEL > 0) + Logger.logInfo("Successfully extracted: $outdir$imgname") + try { + val outimg = FileOutputStream(outdir + imgname) + extracted[tx].write(outimg) + outimg.flush() + outimg.close() + } catch (ex: Exception) { + Logger.logErr("Could not extract images from: $this") + } + + } + } + + /** + * retrieves all charts for this sheet and writes them (in SVG form) to outpdir + *

                  + * Filename is in form of: _Chart<#>.svg + * + * @param outdir String output folder + */ + fun extractChartToDirectory(outdir: String) { + val charts = this.sheet!!.charts as ArrayList<*> + val sheetname = this.sheetName + for (i in charts.indices) { + val ch = ChartHandle(charts[i] as Chart, this.workBook) + val fname = sheetname + "_Chart" + ch.id + ".svg" + try { + val chartout = FileOutputStream(outdir + fname) + chartout.write(ch.getSVG(1.0).toByteArray()) // scaled as necessary in XSL + } catch (ex: Exception) { + Logger.logErr("extractChartToDirectory: Could not extract charts from: $this:$ex") + } + + } + } + + /** + * insert an image into this worksheet + * + * @param im -- the ImageHandle to insert + * @see ImageHandle + */ + fun insertImage(im: ImageHandle) { + this.sheet!!.insertImage(im) + } + + /** + * Inserts empty columns and shifts the following columns to the right. This + * method is used to shift existing columns right to make room for a new column. + * To create a column in the file so its size or style can be set just add a + * blank cell to its first row with [.add]. + * + * @param first the zero-based index of the first column to insert + * @param count the number of columns to insert + */ + fun insertCols(first: Int, count: Int) { + if (first < 0) + throw IllegalArgumentException("column index must be zero or greater") + if (count < 1) + throw IllegalArgumentException("count must be at least one") + sheet!!.insertCols(first, count) + } + + /** + * Inserts an empty column and shifts the following columns to the right. + * + * @param colnum the zero-based index of the column to be inserted + */ + @Deprecated("Use {@link #insertCols} instead.") + fun insertCol(colnum: Int) { + this.insertCols(colnum, 1) + } + + /** + * Inserts an empty column and shifts the following columns to the right. + * + * @param colnum the address of the column to be inserted + */ + @Deprecated("Use {@link #insertCols} instead.") + fun insertCol(colnum: String) { + this.insertCols(ExcelTools.getIntVal(colnum), 1) + } + + /** + * When adding a new Cell to the sheet, OpenXLS can automatically copy the + * formatting from the Cell directly above the inserted Cell. + * + * + * ie: if set to true, newly added Cell D19 would take its formatting from Cell + * D18. + * + * + * Default is false + * + * + * + * + * + * + * boolean copy the formats from the prior Cell + */ + fun setCopyFormatsFromPriorWhenAdding(f: Boolean) { + sheet!!.setCopyPriorCellFormats(f) + } + + /** + * Add a Cell with the specified value to a WorkSheet. + * + * + * This method determines the Cell type based on type-compatibility of the + * value. + * + * + * Further, this method allows passing in a format id + * + * + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + * @param int the format id to apply to this cell + */ + @Throws(ValidationException::class) + @JvmOverloads + fun addValidated(obj: Any, row: Int, col: Int, formatId: Int = this.workBook!!.workBook!!.defaultIxfe): Array { + val rc = intArrayOf(row, col) + val vh = this.getValidationHandle(ExcelTools.formatLocation(rc)) + vh?.isValid(obj) + return this.addValidated(obj, ExcelTools.formatLocation(rc)) + } + + /** + * Add a Cell with the specified value to a WorkSheet. + * + * + * This method determines the Cell type based on type-compatibility of the + * value. + * + * + * Further, this method allows passing in a format id + * + * + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + * @param int the format id to apply to this cell + */ + @JvmOverloads + fun add(obj: Any?, row: Int, col: Int, formatId: Int = this.workBook!!.workBook!!.defaultIxfe): CellHandle? { + val rc = intArrayOf(row, col) + + if (obj is java.util.Date) { + val address = ExcelTools.formatLocation(rc) + this.add(obj as java.util.Date?, address, null) + } else { + val reca = sheet!!.addValue(obj, rc, formatId) + + if (DEBUGLEVEL > 1) + if (reca != null) + Logger.logInfo("WorkSheetHandle.add() $reca Successfully Added.") + else + return null + } + + return if (!sheet!!.fastCellAdds) { + try { + val c = this.getCell(row, col) + if (this.workBook!!.formulaCalculationMode != workBook!!.CALCULATE_EXPLICIT) + c.clearAffectedCells() // blow out cache + c + } catch (e: CellNotFoundException) { + Logger.logInfo("Adding Cell to row failed row:$row col: $col failed.") + null + } + + } else { + null + } + } + + /** + * Fast-adds a Cell with the specified value to a WorkSheet. + * + * + * This method determines the Cell type based on type-compatibility of the + * value. + * + * + * Further, this method allows passing in a format id + * + * + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + * @param int the format id to apply to this cell + */ + fun fastAdd(obj: Any, row: Int, col: Int, formatId: Int) { + var formatId = formatId + val rc = intArrayOf(row, col) + + // use default format + if (formatId == -1) + formatId = 0 + + if (obj is java.util.Date) { + val address = ExcelTools.formatLocation(rc) + this.add(obj, address, null) + } else { + val reca = sheet!!.addValue(obj, rc, formatId) + if (this.workBook!!.formulaCalculationMode != workBook!!.CALCULATE_EXPLICIT) { + val rt = this.workBook!!.workBook!!.refTracker + rt!!.clearAffectedFormulaCells(reca) + } + if (DEBUGLEVEL > 1) + if (reca != null) + Logger.logInfo("WorkSheetHandle.add() $reca Successfully Added.") + } + } + + /** + * Add a Cell with the specified value to a WorkSheet, optionally attempting to + * convert numeric values to appropriate number cells with appropriate number + * formatting applied. + * + * + * This would allow a value entered such as "$1.00" to be converted to a numeric + * 1.00d value with a $0.00 format pattern applied. + * + * + * Note that there is overhead to this method, as the value added needs to be + * parsed, for higher performance do not use the autodetectNumberAndPattern = + * true setting, and instead pass numeric values and format patterns. + * + * + * If the value is non numeric, then it is simply added. + * + * @param obj the value of the new Cell + * @param address the address of the new Cell + * @param autoDetectValue whether to attempt to store as a number with a format pattern. + */ + fun add(obj: Any?, address: String, autoDetectValue: Boolean): CellHandle? { + if (obj == null) + sheet!!.addValue(obj, address) // to add a blank cell + else if (obj is Date) + this.add(obj as Date?, address, null) + else + sheet!!.addValue(obj, address, autoDetectValue)// + + // fast-adds optimzation -- do not return a CellHandle + if (this.sheet!!.fastCellAdds) + return null + + try { + return this.getCell(address) + } catch (e: CellNotFoundException) { + Logger.logInfo("Adding Cell: $address failed") + return null + } + + } + + /** + * Add a Cell with the specified value to a WorkSheet. + * + * + * This method determines the Cell type based on type-compatibility of the + * value. + * + * + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param address the address of the new Cell + */ + fun add(obj: Any?, address: String): CellHandle? { + if (obj == null) + sheet!!.addValue(obj, address) // to add a blank cell + else if (obj is Date) + this.add(obj as Date?, address, null) + else + sheet!!.addValue(obj, address)// + + // fast-adds optimzation -- do not return a CellHandle + return if (!sheet!!.fastCellAdds) { + try { + val c = this.getCell(address) + if (this.workBook!!.formulaCalculationMode != workBook!!.CALCULATE_EXPLICIT) + c.clearAffectedCells() // blow out cache + c + } catch (e: CellNotFoundException) { + Logger.logInfo("Adding Cell: $address failed") + null + } + + } else { + null + } + } + + /** + * Add a Cell with the specified value to a WorkSheet. + * + * + * This method determines the Cell type based on type-compatibility of the + * value. + * + * + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * + * + * + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param obj the value of the new Cell + * @param address the address of the new Cell + */ + @Throws(ValidationException::class) + fun addValidated(obj: Any, address: String): Array { + val vh = this.getValidationHandle(address) + vh?.isValid(obj) + val ch = this.add(obj, address) + val cxrs = ch!!.calculateAffectedCellsOnSheet() + + // return the cellhandles + val cxrx = arrayOfNulls(cxrs.size + 1) + cxrx[0] = ch + for (t in 1 until cxrx.size) { + cxrx[t] = cxrs[t - 1] as CellHandle + cxrx[t].workSheetHandle = this + // this.cellhandles.put(cxrx[t].getCellAddress(), cxrx[t]); + } + + return cxrx + } + + /** + * Add a java.sql.Timestamp Cell to a WorkSheet. + * + * + * Will create a default format of: + * + * + * "m/d/yyyy h:mm:ss" + * + * + * if none is specified. + * + * @param dt the value of the new java.sql.Timestamp Cell + * @param address the address of the new java.sql.Date Cell + * @param formatting pattern the address of the new java.sql.Date Cell + */ + fun add(dt: Timestamp, address: String, fmt: String?): CellHandle? { + var fmt = fmt + if (fmt == null) + fmt = "m/d/yyyy h:mm:ss" + val dx = Date(dt.time) + return this.add(dx, address, fmt) + } + + /** + * Add a java.sql.Timestamp Cell to a WorkSheet. + * + * + * Will create a default format of: + * + * + * "m/d/yyyy h:mm:ss" + * + * + * if none is specified. + * + * + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param dt the value of the new java.sql.Timestamp Cell + * @param address the address of the new java.sql.Date Cell + * @param formatting pattern the address of the new java.sql.Date Cell + */ + @Throws(ValidationException::class) + fun addValidated(dt: Timestamp, address: String, fmt: String?): Array { + var fmt = fmt + if (fmt == null) + fmt = "m/d/yyyy h:mm:ss" + val dx = Date(dt.time) + return this.addValidated(dx, address, fmt) + } + + /** + * Add a java.sql.Date Cell to a WorkSheet. + * + * + * You must specify a formatting pattern for the new date, or null for the + * default ("m/d/yy h:mm".) + * + * + * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" + * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" + * + * @param dt the value of the new java.sql.Date Cell + * @param row to add the date + * @param col to add the date + * @param formatting pattern the address of the new java.sql.Date Cell + */ + fun add(dt: java.util.Date, address: String, fmt: String?): CellHandle? { + val rc = ExcelTools.getRowColFromString(address) + return add(dt, rc[0], rc[1], fmt) + } + + /** + * Add a java.sql.Date Cell to a WorkSheet. + * + * + * You must specify a formatting pattern for the new date, or null for the + * default ("m/d/yy h:mm".) + * + * + * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" + * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" + * + * @param dt the value of the new java.sql.Date Cell + * @param row to add the date + * @param col to add the date + * @param formatting pattern the address of the new java.sql.Date Cell + */ + @Throws(ValidationException::class) + fun addValidated(dt: java.util.Date, address: String, fmt: String): Array { + val rc = ExcelTools.getRowColFromString(address) + return addValidated(dt, rc[0], rc[1], fmt) + } + + /** + * Add a java.sql.Date Cell to a WorkSheet. + * + * + * You must specify a formatting pattern for the new date, or null for the + * default ("m/d/yy h:mm".) + * + * + * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" + * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" + * + * @param dt the value of the new java.sql.Date Cell + * @param address the address of the new java.sql.Date Cell + * @param formatting pattern the address of the new java.sql.Date Cell + */ + fun add(dt: java.util.Date, row: Int, col: Int, fmt: String?): CellHandle? { + var fmt = fmt + val x = DateConverter.getXLSDateVal(dt, this.mybook!!.dateFormat) + var thisCell = this.add(x, row, col) + + // first handle fast adds + if (thisCell == null && this.sheet!!.fastCellAdds) { + try { + thisCell = getCell(row, col) + } catch (exp: CellNotFoundException) { + Logger.logWarn("adding date to WorkSheet failed: " + this.sheetName + ":" + row + ":" + col) + return null + } + + } + + // 20060419 KSC: Use format from cell, if any + if (fmt == null) { + fmt = thisCell!!.formatPattern + if (fmt == null || fmt == "General") + fmt = "m/d/yy h:mm" + } + if (dateFormats!![fmt] == null) { + val fh = thisCell!!.formatHandle + fh!!.formatPattern = fmt + dateFormats!![fmt] = Integer.valueOf(thisCell.formatId)!! + } else { + val `in` = dateFormats!![fmt] + thisCell!!.formatId = `in`!!.toInt() + } + // Logger.logInfo("Date added: " + thisCell.getFormattedStringVal()); + return thisCell + } + + /** + * Add a java.sql.Date Cell to a WorkSheet. + * + * + * You must specify a formatting pattern for the new date, or null for the + * default ("m/d/yy h:mm".) + * + * + * valid date format patterns "m/d/y" "d-mmm-yy" "d-mmm" "mmm-yy" "h:mm AM/PM" + * "h:mm:ss AM/PM" "h:mm" "h:mm:ss" "m/d/yy h:mm" "mm:ss" "[h]:mm:ss" "mm:ss.0" + * + * + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param dt the value of the new java.sql.Date Cell + * @param address the address of the new java.sql.Date Cell + * @param formatting pattern the address of the new java.sql.Date Cell + */ + @Throws(ValidationException::class) + fun addValidated(dt: java.util.Date, row: Int, col: Int, fmt: String): Array { + val rc = intArrayOf(row, col) + val vh = this.getValidationHandle(ExcelTools.formatLocation(rc)) + vh?.isValid(dt) + val ch = this.add(dt, row, col, fmt) + val cxrs = ch!!.calculateAffectedCellsOnSheet() + + // FIXME: Use List.toArray instead of for loop + // return the cellhandles + val cxrx = arrayOfNulls(cxrs.size + 1) + cxrx[0] = ch + for (t in 1 until cxrx.size) { + cxrx[t] = cxrs[t - 1] as CellHandle + } + + return cxrx + + } + + /** + * Remove this WorkSheet from the WorkBook + * + * + * NOTE: will throw a WorkBookException if the last sheet is removed. This + * results in an invalid output file. + */ + fun remove() { + mybook!!.removeWorkSheet(this.sheet!!) + workBook!!.sheethandles!!.remove(this.sheetName) + } + + /** + * Create a CellRange object from an OpenXLS string range passed in such as + * "A1:F6" + * + * @param rangeName "A1:F6" + * @return + * @throws CellNotFoundException if the range cannot be created + */ + @Throws(CellNotFoundException::class) + fun getCellRange(rangeName: String): CellRange { + return CellRange("$sheetName!$rangeName", workBook) + } + + /** + * Returns a FormulaHandle for working with the ranges of a formula on a + * WorkSheet. + * + * @param addr the address of the Cell + * @throws FormulaNotFoundException is thrown if there is no existing formula at the specified + * address. + */ + @Throws(FormulaNotFoundException::class, CellNotFoundException::class) + fun getFormula(addr: String): FormulaHandle { + val c = this.getCell(addr) + return c.formulaHandle + } + + /** + * Returns a CellHandle for working with the value of a Cell on a WorkSheet. + * + * @param addr the address of the Cell + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + @Throws(CellNotFoundException::class) + fun getCell(addr: String): CellHandle { + var addr = addr + var ret: CellHandle? = null // + + val c = sheet!!.getCell(addr.toUpperCase()) + if (c == null) { + var sn = "" + try { + sn = this.sheetName + sn += "!" + } catch (e: Exception) { + } + + if (addr == null) + addr = "undefined cell address" + throw CellNotFoundException(sn + addr) + } + ret = CellHandle(c, this.workBook) + ret.workSheetHandle = this + return ret + } + + /** + * Returns a CellHandle for working with the value of a Cell on a WorkSheet. + * + * @param int Row the integer row of the Cell + * @param int Col the integer col of the Cell + * @param boolean whether to cache or return a new CellHandle each call + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ + @Throws(CellNotFoundException::class) + @JvmOverloads + fun getCell(row: Int, col: Int, cache: Boolean = false): CellHandle { + var ret: CellHandle? = null + if (cache) { + val rc = intArrayOf(row, col) + val address = ExcelTools.formatLocation(rc) + // ret = (CellHandle)cellhandles.get(address); + if (ret != null) + // caching! + return ret + else { + ret = CellHandle(this.sheet!!.getCell(row, col), this.workBook) + ret.workSheetHandle = this + // cellhandles.put(address,ret); + return ret + } + } + ret = CellHandle(this.sheet!!.getCell(row, col), this.workBook) + ret.workSheetHandle = this + return ret + } + + /** + * Move a cell on this WorkSheet. + * + * @param CellHandle c - the cell to be moved + * @param String celladdr - the destination address of the cell + */ + @Throws(CellPositionConflictException::class) + fun moveCell(c: CellHandle, addr: String) { + this.sheet!!.moveCell(c.cellAddress, addr) + // c.moveTo(addr); < redundant call to above. 070104 -jm + } + + /** + * Sets the print area for the worksheet + * + * + * + * + * sets the printarea as a CellRange + * + * @param printarea + */ + fun setPrintArea(printarea: CellRange) { + sheet!!.printArea = printarea.getRange() + } + + /** + * Returns the name of this Sheet. + * + * @see java.lang.Object.toString + */ + override fun toString(): String { + return sheet!!.toString() + } + + /** + * FOR internal Use Only! + * + * @return Returns the low-level sheet record. + */ + fun getMysheet(): Boundsheet? { + return sheet + } + + /** + * Calculates all formulas that reference the cell address passed in. + * + * + * Please note that these cells have already been calculated, so in order to get + * their values without re-calculating them Extentech suggests setting the book + * level non-calculation flag, ie + * book.setFormulaCalculationMode(WorkBookHandle.CALCULATE_EXPLICIT) or + * FormulaHandle.getCachedVal() + * + * @return List of of calculated cells + */ + fun calculateAffectedCells(CellAddress: String): List<*>? { + var c: CellHandle? = null + try { + c = this.getCell(CellAddress) + } catch (e: CellNotFoundException) { + return null + } + + return c.calculateAffectedCells() + } + + /** + * Get whether there are freeze panes in the output sheet. + * + * @return boolean whether there are freeze panes + */ + fun hasFrozenPanes(): Boolean { + return this.sheet!!.window2!!.freezePanes + } + + /** + * Set whether there are freeze panes in the output sheet. + * + * @param boolean whether there are freeze panes + */ + fun setHasFrozenPanes(b: Boolean) { + this.sheet!!.window2!!.freezePanes = b + if (!b && this.sheet!!.pane != null) { + this.sheet!!.removePane() // remove pane rec if unfreezing ... can also convert to plain splits, but a bit + // more complicated ... + } + } + + /** + * freezes the rows starting at the specified row and creating a scrollable + * sheet below this row + * + * @param row the row to start the freeze + */ + fun freezeRow(row: Int) { + if (this.sheet!!.pane == null) + this.sheet!!.pane = null // will add new + this.sheet!!.pane!!.setFrozenRow(row) + } + + /** + * freezes the cols starting at the specified column and creating a scrollable + * sheet to the right + * + * @param col the col to start the freeze + */ + fun freezeCol(col: Int) { + if (this.sheet!!.pane == null) + this.sheet!!.pane = null // will add new + this.sheet!!.pane!!.setFrozenColumn(col) + } + + /** + * splits the worksheet at column col for nCols + * + * + * Note: unfreezes panes if frozen + * + * @param col col start col to split + * @param splitpos position of the horizontal split + */ + fun splitCol(col: Int, splitpos: Int) { + if (this.sheet!!.pane == null) + this.sheet!!.pane = null // will add new + this.sheet!!.pane!!.setSplitColumn(col, splitpos) + } + + /** + * splits the worksheet at row for nRows + * + * + * Note: unfreezes panes if frozen + * + * @param row start row to split + * @param splitpos position of the vertical split + */ + fun splitRow(row: Int, splitpos: Int) { + if (this.sheet!!.pane == null) + this.sheet!!.pane = null // will add new + this.sheet!!.pane!!.setSplitRow(row, splitpos) + } + + /** + * Create a Conditional Format handle for a cell/range + * + * @param cellAddress without sheetname. Can also be a range, such as A1:B5 + * @param qualifier = maps to CONDITION_* bytes in ConditionalFormatHandle + * @param value1 = the error message + * @param value2 = the error title + * @param format = the initial format string to use with the condition + * @param firstCondition = formula string + * @param secondCondition = 2nd formula string (optional) + * @return + */ + fun createConditionalFormatHandle(cellAddress: String?, operator: String, value1: String, + value2: String, format: String, firstCondition: String, secondCondition: String): ConditionalFormatHandle { + var cellAddress = cellAddress + + if (cellAddress != null && cellAddress.indexOf("!") == -1) + cellAddress = this.sheetName + "!" + cellAddress + val cfm = this.sheet!!.createCondfmt(cellAddress, this.workBook) + + val cfr = this.sheet!!.createCf(cfm) + cfr.setOperator(operator) + + // only place this is done.. + cfr.setCondition1(value1) + cfr.setCondition2(value2) + + Cf.setStylePropsFromString(format, cfr) + +// done above in createCondfmt this.mysheet.addConditionalFormat(cfm); + return ConditionalFormatHandle(cfm, this) + } + + /** + * Creates a new annotation (Note or Comment) to the worksheeet, attached to a + * specific cell + * + * @param address -- address to attach + * @param txt -- text of note + * @param author -- name of author + * @return NoteHandle - handle which allows access to the Note object + * @see CommentHandle + */ + fun createNote(address: String, txt: String, author: String): CommentHandle { + val n = this.getMysheet()!!.createNote(address, txt, author) + return CommentHandle(n) + } + + /** + * Creates a new annotation (Note or Comment) to the worksheeet, attached to a + * specific cell

                  + * The note or comment is a Unicode string, thus it can contain formatting + * information + * + * @param address -- address to attach + * @param txt -- Unicode string of note with Formatting + * @param author -- name of author + * @return NoteHandle - handle which allows access to the Note object + * @see CommentHandle + */ + fun createNote(address: String, txt: Unicodestring, author: String): CommentHandle { + val n = this.getMysheet()!!.createNote(address, txt, author) + return CommentHandle(n) + } + + /** + * creates a new, blank PivotTable and adds it to the worksheet. + * + * @param name pivot table name + * @param range source range for the pivot table. If no sheet is specified, the + * current sheet will be used. + * @param sId Stream or cachid Id -- links back to SxStream set of records + * @return PivotTableHandle + */ + fun createPivotTable(name: String, range: String, sId: Int): PivotTableHandle { + val sx = getMysheet()!!.addPivotTable(range, this.workBook, sId, name) + val pth = PivotTableHandle(sx, this.workBook) + pth.setSourceDataRange(range) + return pth + } + + /** + * Get a validation handle for the cell address passed in. If the validation is + * for a range, the handle returned will modify the entire range, not just the + * cell address passed in. + * + * + * Returns null if a validation does not exist at the specified location + * + * @param cell address String + */ + fun getValidationHandle(cellAddress: String): ValidationHandle? { + if (this.sheet!!.dvalRec != null) { + val d = this.sheet!!.dvalRec!!.getDv(cellAddress) ?: return null + return ValidationHandle(d) + } + return null + } + + /** + * Create a validation handle for a cell/range + * + * @param cellAddress without sheetname. Can also be a range, such as A1:B5 + * @param valueType = maps to VALUE_* bytes in ValidationHandle + * @param condition = maps to CONDITION_* bytes in ValidationHandle + * @param errorBoxText = the error message + * @param errorBoxTitle = the error title + * @param promptBoxText = the prompt (hover) message + * @param promptBoxTitle = the prompt (hover) title + * @param firstCondition = formula string, seeValidationHandle.setFirstCondition + * @param secondCondition = seeValidationHandle.setSecondCondition, this can be left null + * for validations that do not require a second argument. + * @return + */ + fun createValidationHandle(cellAddress: String, valueType: Byte, condition: Byte, + errorBoxText: String, errorBoxTitle: String, promptBoxText: String, promptBoxTitle: String, + firstCondition: String?, secondCondition: String?): ValidationHandle { + val d = this.sheet!!.createDv(cellAddress) + d.valType = valueType + /* + * // KSC: APPARENTLY NOT NEEDED if + * (valueType==ValidationHandle.VALUE_USER_DEFINED_LIST) { // ensure Mso + * Drop-downs are defined int objId = + * this.mysheet.insertDropDownBox(d.getColNumber()); //TODO: verify that drop + * down lists are SHARED **** + * this.mysheet.getDvalRec().setObjectIdentifier(objId); } + */ + d.typeOperator = condition + d.errorBoxText = errorBoxText + d.errorBoxTitle = errorBoxTitle + d.promptBoxText = promptBoxText + d.promptBoxTitle = promptBoxTitle + if (firstCondition != null) + d.firstCond = firstCondition + if (secondCondition != null) + d.secondCond = secondCondition + return ValidationHandle(d) + } + + /** + * return true if sheet contains data validations + * + * @return boolean + */ + fun hasDataValidations(): Boolean { + return sheet!!.dvRecs != null + } + + /** + * Adds a new AutoFilter for the specified column (0-based) in this sheet

                  + * returns a handle to the new AutoFilter + * + * @param int column - column number to add an AutoFilter to + * @return AutoFilterHandle + */ + fun addAutoFilter(column: Int): AutoFilterHandle { + val af = sheet!!.addAutoFilter(column) + return AutoFilterHandle(af) + } + + /** + * Removes all AutoFilters from this sheet

                  + * As a consequence, all previously hidden rows are shown or unhidden + */ + fun removeAutoFilters() { + sheet!!.removeAutoFilter() + } + + /** + * Updates the Row filter (hidden status) for each row on the sheet by + * evaluating all AutoFilter conditions + * + * + * NOTE: This method **must** be called after Autofilter updates or additions + * in order to see the results of the AutoFilter(s) + * + * + * NOTE: this evaluation is NOT done automatically due to performance + * considerations, and is designed to be called after all additions and updating + * is completed (as evaluation may be time-consuming) + */ + fun evaluateAutoFilters() { + sheet!!.evaluateAutoFilters() + } + + /** + * clear out object references in prep for closing workbook + */ + fun close() { + if (sheet != null) + sheet!!.close() + addedrows.clear() + addedrows = CompatibleVector() + sheet = null + mybook = null + workBook = null + dateFormats!!.clear() + dateFormats = null + } + + fun getBoundsheet(): Boundsheet? { + return this.sheet + } + + /** + * Imports the given CSV data into this worksheet. All rows in the input will be + * inserted sequentially before any rows which already exist in this worksheet. + * + * + * To change the value delimiter set the system property + * "`io.starter.OpenXLS.csvdelimiter`". + */ + @Throws(IOException::class) + fun readCSV(input: BufferedReader) { + var rws = 0 + val field_delimiter = System.getProperty("io.starter.OpenXLS.csvdelimiter", ",") + + var thisLine = "" + while ((thisLine = input.readLine()) != null) { // while loop begins here + val vals = StringTool.getTokensUsingDelim(thisLine, field_delimiter) + val data = arrayOfNulls(vals.size) + for (t in vals.indices) { + vals[t] = StringTool.strip(vals[t], '"') + try { + val i = Integer.parseInt(vals[t]) + data[t] = Integer.valueOf(i) + + val d = java.lang.Double.parseDouble(vals[t]) + data[t] = d + + } catch (ax: NumberFormatException) { + // it's a string! + data[t] = vals[t] + } + + } + + insertRow(rws++, data, true) + } + } + + companion object { + + // insert handling flags + /** + * Insert row multiple times allowed, also copies formulas to inserted row + */ + val ROW_INSERT_MULTI = 0 + /** + * Excel standard row insertion behavior + */ + val ROW_INSERT = 3 + /** + * Insert row one time, multiple calls ignored + */ + val ROW_INSERT_ONCE = 1 + /** + * Insert row but do not update any cell references affected by insert + */ + val ROW_INSERT_NO_REFERENCE_UPDATE = 2 + + // 20080619 KSC: Add flag constants for Delete Row + val ROW_DELETE = 1 + val ROW_DELETE_NO_REFERENCE_UPDATE = 2 + + /** + * enhanced protection settings: Edit Object + */ + val ALLOWOBJECTS = FeatHeadr.ALLOWOBJECTS + /** + * enhanced protection settings: Edit scenario + */ + val ALLOWSCENARIOS = FeatHeadr.ALLOWSCENARIOS + /** + * enhanced protection settings: Format cells + */ + val ALLOWFORMATCELLS = FeatHeadr.ALLOWFORMATCELLS + /** + * enhanced protection settings: Format columns + */ + val ALLOWFORMATCOLUMNS = FeatHeadr.ALLOWFORMATCOLUMNS + /** + * enhanced protection settings: Format rows + */ + val ALLOWFORMATROWS = FeatHeadr.ALLOWFORMATROWS + /** + * enhanced protection settings: Insert columns + */ + val ALLOWINSERTCOLUMNS = FeatHeadr.ALLOWINSERTCOLUMNS + /** + * enhanced protection settings: Insert rows + */ + val ALLOWINSERTROWS = FeatHeadr.ALLOWINSERTROWS + /** + * enhanced protection settings: Insert hyperlinks + */ + val ALLOWINSERTHYPERLINKS = FeatHeadr.ALLOWINSERTHYPERLINKS + /** + * enhanced protection settings: Delete columns + */ + val ALLOWDELETECOLUMNS = FeatHeadr.ALLOWDELETECOLUMNS + /** + * enhanced protection settings: Delete rows + */ + val ALLOWDELETEROWS = FeatHeadr.ALLOWDELETEROWS + /** + * enhanced protection settings: Select locked cells + */ + val ALLOWSELLOCKEDCELLS = FeatHeadr.ALLOWSELLOCKEDCELLS + /** + * enhanced protection settings: Sort + */ + val ALLOWSORT = FeatHeadr.ALLOWSORT + /** + * enhanced protection settings: Use Autofilter + */ + val ALLOWAUTOFILTER = FeatHeadr.ALLOWAUTOFILTER + /** + * enhanced protection settings: Use PivotTable reports + */ + val ALLOWPIVOTTABLES = FeatHeadr.ALLOWPIVOTTABLES + /** + * enhanced protection settings: Select unlocked cells + */ + val ALLOWSELUNLOCKEDCELLS = FeatHeadr.ALLOWSELUNLOCKEDCELLS + } + +} +/** + * Remove a Row and all associated Cells from this WorkSheet. + * + * @param int rownum - the number of the row to remove uses default row deletion + * rules regarding updating references + */ +/** + * Add a Cell with the specified value to a WorkSheet. + * + * + * This method determines the Cell type based on type-compatibility of the + * value. + * + * + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + */ +/** + * Add a Cell with the specified value to a WorkSheet. + * + * + * This method determines the Cell type based on type-compatibility of the + * value. + * + * + * In other words, if the Object cannot be converted safely to a Numeric Object + * type, then it is treated as a String and a new String value is added to the + * WorkSheet at the Cell address specified. + * + * + * If a validation record for the cell exists the validation is checked for a + * correct value, if the value does not pass the validation a + * ValidationException will be thrown + * + * @param obj the value of the new Cell + * @param int row the row of the new Cell + * @param int col the column of the new Cell + */ +/** + * Returns a CellHandle for working with the value of a Cell on a WorkSheet. + * + * + * returns a new CellHandle with each call + * + * + * use caching method getCell(int row, int col, boolean cache) to control + * caching of CellHandles. + * + * @param int Row the integer row of the Cell + * @param int Col the integer col of the Cell + * @throws CellNotFoundException is thrown if there is no existing Cell at the specified + * address. + */ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/BIGBLOCK.java b/src/main/java/io/starter/formats/LEO/BIGBLOCK.kt similarity index 79% rename from src/main/java/io/starter/formats/LEO/BIGBLOCK.java rename to src/main/java/io/starter/formats/LEO/BIGBLOCK.kt index 47d1470..15b4b1a 100644 --- a/src/main/java/io/starter/formats/LEO/BIGBLOCK.java +++ b/src/main/java/io/starter/formats/LEO/BIGBLOCK.kt @@ -20,36 +20,41 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.LEO; +package io.starter.formats.LEO /** * LEO File BIGBLOCK Information Record. - *

                  + * + * * These blocks of data contain information related to the * LEO file format data blocks. - *

                  + * + * * depending on the size and complexity of the file, * these records may all be contained in the 'header' * block of the LEO Stream File. - *

                  + * + * * In files over that size, one or more BIGBLOCK records * are inserted directly in the midst of substream records. */ -public class BIGBLOCK extends BlockImpl { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 839197117033095054L; - public final static int SIZE = 512; +class BIGBLOCK : BlockImpl() { /** * returns the int representing the block type */ - public int getBlockType() { - return BIG; + override val blockType: Int + get() = Block.BIG + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 839197117033095054L + val SIZE = 512 } diff --git a/src/main/java/io/starter/formats/LEO/Block.java b/src/main/java/io/starter/formats/LEO/Block.kt similarity index 69% rename from src/main/java/io/starter/formats/LEO/Block.java rename to src/main/java/io/starter/formats/LEO/Block.kt index 90dbc12..dcbef29 100644 --- a/src/main/java/io/starter/formats/LEO/Block.java +++ b/src/main/java/io/starter/formats/LEO/Block.kt @@ -20,150 +20,142 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.LEO; +package io.starter.formats.LEO -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.List; +import java.io.OutputStream +import java.nio.ByteBuffer /** * the basic unit of data in a LEO file. Can either be BIG or SMALL. */ -public interface Block extends java.util.Iterator { - - int SMALL = 0; - int BIG = 1; +interface Block : Iterator<*> { /** * @return */ - boolean isXBAT(); - - /** - * @param b - */ - void setIsExtraSector(boolean b); - - /** - * link to the vector of blocks for the storage - */ - void setBlockVector(List v); + val isXBAT: Boolean /** * get the size of the Block data in bytes * * @return block data size */ - int getBlockSize(); + val blockSize: Int /** * get the index of this Block in the storage * Vector */ - int getBlockIndex(); - - /** - * link the next Block in the chain - */ - void setNextBlock(Block b); + val blockIndex: Int /** * set the storage for this Block */ - void setStorage(Storage s); - /** * set the storage for this Block */ - Storage getStorage(); + var storage: Storage /** * returns whether this block has been * added to the output stream */ - boolean getStreamed(); - /** * sets whether this block has been * added to the output stream */ - void setStreamed(boolean b); + var streamed: Boolean /** * returns the int representing the block type */ - int getBlockType(); + val blockType: Int /** * returns true if this is a Block Depot block * that needs to be ignored when reading byte storages */ - boolean getIsSpecialBlock(); + val isSpecialBlock: Boolean /** * returns true if this is a Block Depot block * that needs to be ignored when reading byte storages */ - boolean getIsDepotBlock(); - /** * set to true if this is a Block Depot block */ - void setIsDepotBlock(boolean b); + var isDepotBlock: Boolean /** - * init the Block Data + * returns whether this Block has been read yet... */ - void init(ByteBuffer d, int origidx, int origp); - /** * set whether this Block has been read yet... */ - void setInitialized(boolean b); + var initialized: Boolean /** - * returns whether this Block has been read yet... + * get the data bytes on this Block */ - boolean getInitialized(); + val byteBuffer: ByteBuffer /** - * set the data bytes on this Block + * get the data bytes on this Block */ - void setBytes(ByteBuffer b); + val bytes: ByteArray /** - * get the data bytes on this Block + * return the original BB position in the file */ - ByteBuffer getByteBuffer(); + val originalPos: Int /** - * return the byte Array for this BLOCK + * return the original BB position in the file */ - byte[] getBytes(int start, int end); + /** + * set the original BB position in the file + */ + var originalIdx: Int /** - * get the data bytes on this Block + * @param b */ - byte[] getBytes(); + fun setIsExtraSector(b: Boolean) /** - * write the data bytes on this Block to out + * link to the vector of blocks for the storage */ - void writeBytes(OutputStream out); + fun setBlockVector(v: List<*>) /** - * return the original BB position in the file + * link the next Block in the chain */ - int getOriginalPos(); + fun setNextBlock(b: Block) /** - * return the original BB position in the file + * init the Block Data */ - int getOriginalIdx(); + fun init(d: ByteBuffer, origidx: Int, origp: Int) /** - * set the original BB position in the file + * set the data bytes on this Block + */ + fun setBytes(b: ByteBuffer) + + /** + * return the byte Array for this BLOCK */ - void setOriginalIdx(int x); + fun getBytes(start: Int, end: Int): ByteArray + + /** + * write the data bytes on this Block to out + */ + fun writeBytes(out: OutputStream) + + companion object { + + val SMALL = 0 + val BIG = 1 + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/BlockByteConsumer.java b/src/main/java/io/starter/formats/LEO/BlockByteConsumer.kt similarity index 83% rename from src/main/java/io/starter/formats/LEO/BlockByteConsumer.java rename to src/main/java/io/starter/formats/LEO/BlockByteConsumer.kt index 61d3913..a49e098 100644 --- a/src/main/java/io/starter/formats/LEO/BlockByteConsumer.java +++ b/src/main/java/io/starter/formats/LEO/BlockByteConsumer.kt @@ -20,32 +20,44 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.LEO; +package io.starter.formats.LEO /** * The BlockByteConsumer interface describes an Object * which reads its data from a scattered collection of Blocks. - *

                  + * + * * By tracking the blocks containing the record's data, */ -public interface BlockByteConsumer { +interface BlockByteConsumer { + /** + * Get the relative position within the data + * underlying the block vector represented by + * the BlockByteReader. + * + * @return relative position + */ /** * Set the relative position within the data * underlying the block vector represented by * the BlockByteReader. - *

                  + * + * * In other words, this is the relative position * used by the BlockByteReader to offset the Consumer's * read position within the collection of Data Blocks. - *

                  + * + * * This may be an offset relative to the data in a file, * or within a Storage contained in a file. - *

                  + * + * * The Workbook Storage for example will contain a non-contiguous * collection of Blocks containing data from any number of * positions in a file. - *

                  + * + * * This collection forms a contiguous span of bytes comprising * an XLS Workbook. The XLSRecords within this span of bytes will * set their relative position within this 'virtual' array. Thus @@ -56,76 +68,61 @@ public interface BlockByteConsumer { * * @param pos */ - void setOffset(int pos); + var offset: Int /** - * Get the relative position within the data - * underlying the block vector represented by - * the BlockByteReader. + * Returns the index of the first block * - * @return relative position + * @return */ - int getOffset(); - /** Get the blocks containing this Consumer's data * * @return */ -// KSC: NOT USED Block[] getBlocks(); + // KSC: NOT USED Block[] getBlocks(); /** Set the blocks containing this Consumer's data * * @param myblocks */ -// KSC: NOT USED void setBlocks(Block[] myblocks); + // KSC: NOT USED void setBlocks(Block[] myblocks); /** * Sets the index of the first block * * @return */ - void setFirstBlock(int i); - - /** - * Sets the index of the last block - * - * @return - */ - void setLastBlock(int i); + var firstBlock: Int /** - * Returns the index of the first block + * Returns the index of the last block * * @return */ - int getFirstBlock(); - /** - * Returns the index of the last block + * Sets the index of the last block * * @return */ - int getLastBlock(); + var lastBlock: Int /** * Returns the length of the record. * * @return */ - int getLength(); + val length: Int /** - * Set the BlockByteReader for this Consumer + * Get the BlockByteReader for this Consumer * * @param db */ - void setByteReader(BlockByteReader db); - /** - * Get the BlockByteReader for this Consumer + * Set the BlockByteReader for this Consumer * * @param db */ - BlockByteReader getByteReader(); + var byteReader: BlockByteReader } diff --git a/src/main/java/io/starter/formats/LEO/BlockByteReader.java b/src/main/java/io/starter/formats/LEO/BlockByteReader.java deleted file mode 100644 index db413ab..0000000 --- a/src/main/java/io/starter/formats/LEO/BlockByteReader.java +++ /dev/null @@ -1,987 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.LEO; - -import io.starter.formats.XLS.WorkBookException; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.ByteArrayOutputStream; -import java.io.Serializable; -import java.nio.*; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.List; - -/** - * Provide a translation layer between the block vector and a byte array. - *

                  - *

                  - * A record (Storage, XLSRecord) can retrieve bytes from Scattered blocks - * transparently. - *

                  - * The BlockByteReader Allocates a ByteBuffer containing only byte references - * contained within Blocs assigned to the implementation class. - *

                  - * A Class using this reader will either subclass the reader and manage its - * blocks, or interact with a shared reader. - *

                  - * In the case of a shared reader, - */ -public class BlockByteReader implements Serializable { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4845306509411520019L; - private boolean applyRelativePosition = true; - - protected BlockByteReader() { - // empty constructor... - } - - private List blockmap = new ArrayList(); - private boolean ro = false; - private int length = -1; - transient ByteBuffer backingByteBuffer = ByteBuffer.allocate(0); - - public BlockByteReader(List blokz, int len) { - this.blockmap = blokz; - this.length = len; - } - - public boolean isReadOnly() { - return ro; - } - - /** - * Allows for getting of header bytes without setting blocks on a rec - * - * @param startpos - * @return - */ - public byte[] getHeaderBytes(int startpos) { - try { - int SIZE = BIGBLOCK.SIZE; // normal case - if (this.length < StorageTable.BIGSTORAGE_SIZE) { - SIZE = SMALLBLOCK.SIZE; - } - int block = startpos / SIZE; - int check = startpos % SIZE; - // handle EOF that falls right on boundary - if (((check + 4) > SIZE) && (blockmap.size() - 1 == block)) { - // Last EOF falls within 4 bytes of 512 boundary... junkrec - byte[] junk = {0x0, 0x0, 0x0, 0x0}; - return junk; - } else if ((check + 4) > SIZE) { // SPANNER! - Block bx = (Block) this.blockmap.get(block); - int l1 = ((SIZE * (block + 1)) - startpos); - int s2 = startpos % SIZE; - byte[] b1 = bx.getBytes(s2, s2 + l1); - bx = (Block) this.blockmap.get(block + 1); - l1 = 4 - l1; - byte[] b2 = bx.getBytes(0, l1); - return ByteTools.append(b2, b1); - } - - Block bx = (Block) this.blockmap.get(block); - startpos -= (block * SIZE); - return bx.getBytes(startpos, startpos + 4); - } catch (RuntimeException e) { - throw new WorkBookException("Smallblock based workbooks are unsupported in OpenXLS: see http://extentech.com/uimodules/docs/docs_detail.jsp?showall=true&meme_id=195", WorkBookException.SMALLBLOCK_FILE); - } - } - - - /* Return the byte from the blocks at the proper locations... - * - * @see java.nio.ByteBuffer#get() - */ - public byte get(BlockByteConsumer rec, int startpos) { - byte ret = this.get(rec, startpos, 1)[0]; - return ret; - } - - /* Return the bytes from the blocks at the proper locations... - * - * as opposed to when we are traversing the entire collection of bytes as in WorkBookFactory.parse() - * - * @ see java.nio.ByteBuffer # get() - */ - public byte[] get(BlockByteConsumer rec, int startpos, int len) { - rec.setByteReader(this); - // we only want to add the offset when - // we are fetching data from 'within' a record, ie: rkdata.get(i) - int recoffy = rec.getOffset(); - if (this.getApplyRelativePosition()) { - - startpos += 4; // add the offset - } - startpos += recoffy; - // reality checks - if (false) // ((startpos + len) > getLength()) - Logger.logWarn( - "WARNING: BlockByteReader.get(rec," - + startpos - + "," - + rec.getLength() - + ") error. Attempt to read past end of Block buffer."); - - // return the bytes from the rec - return getRecBytes(rec, startpos, len); - } - - /* Handles the spanning of Record Bytes over Block boundaries - * then returns requested bytes. - * - */ - private byte[] getRecBytes( - BlockByteConsumer rec, - int startpos, - int len) { - if ((startpos < 0) || (startpos > (startpos + len))) - throw new RuntimeException( - "ERROR: BBR.getRecBytes(" - + rec.getClass().getName() - + "," - + startpos - + "," - + (startpos + len) - + ") failed - OUT OF BOUNDS."); - // get the block byte boundaries - int[] pos = this.getReadPositions(startpos, len); - int numblocks = pos.length / 3; - int blkdef = 0; - // backingByteBuffer = blokx.getByteBuffer(); - // Temporarily use BAOS... - ByteArrayOutputStream out = new ByteArrayOutputStream(); - for (int t = 0; t < numblocks; t++) { - try { // inlining byte read - Block b1 = (Block) this.blockmap.get(pos[blkdef++]); - out.write(b1.getBytes(pos[blkdef++], pos[blkdef++])); - if (false) - Logger.logInfo( - "INFO: BBR.getRecBytes() " - + rec.getClass().getName() - + " ACCESSING DATA for block:" - + b1.getBlockIndex() - + ":" - + pos[0] - + "-" - + pos[1]); - } catch (Exception a) { - Logger.logWarn( - "ERROR: BBR.getRecBytes streaming " + rec.toString() + " bytes for block failed: " + a); - } - } - return out.toByteArray(); - } - - - /** - * returns the lengths of the two byte - *

                  -     * ie: start 10 len 8
                  -     * blk0 = 10-18
                  -     * 0,10,18
                  -     *
                  -     * ie: start 514 len 1
                  -     * blk1 = 2-3
                  -     * 1,2,3
                  -     *
                  -     * ie: start 480 len 1124
                  -     *
                  -     *  blk0 = 480-512
                  -     *  blk1 = 512-1024
                  -     *  blk2 = 1024-1536
                  -     *  blk3 = 1536-1604
                  -     *
                  -     * [0, 480, 512,1, 0, 512, 2, 0, 512, 3, 0, 68]
                  -     *
                  -     * 8139, 693
                  -     * 
                  - * - * @param startpos - * @return - */ - public static int[] getReadPositions(int startpos, int len, boolean BIGBLOCKSTORAGE) { - // Logger.logInfo("BBR.getReadPositions()"+startpos+":"+endpos); - - // 20100323 KSC: handle small blocks - int SIZE = BIGBLOCK.SIZE; // normal case - if (!BIGBLOCKSTORAGE) - SIZE = SMALLBLOCK.SIZE; - - - int firstblock = startpos / SIZE; - int lastblock = (startpos + len) / SIZE; - int numblocks = lastblock - firstblock; - numblocks++; - int origlen = len; - - int ct = startpos / SIZE; - - - int pos1 = startpos; - int[] ret = new int[numblocks * 3]; - int t = 0; - // for each block, create 2 byte positions - while (len > 0) { - if (t >= ret.length) { - Logger.logWarn("BlockByteReader.getReadPositions() wrong guess on NumBlocks."); - numblocks++; - int[] retz = new int[numblocks * 3]; - System.arraycopy(ret, 0, retz, 0, ret.length); - ret = retz; - } - ret[t++] = firstblock++; - int check = pos1 % SIZE; //leftover - check += len; - if (check > SIZE) { // SPANNER! - pos1 = startpos - ((SIZE) * ct); - if (pos1 < 0) pos1 = 0; - // int s1 = pos1- ((SIZE)*(ct)); - int s2 = pos1 % SIZE; - if (s2 < 0) { - s2 = 0; - pos1 = 0; - } - ret[t++] = s2; - ret[t++] = SIZE; - } else { - pos1 = startpos - ((SIZE) * ct); - int strt = startpos - ((SIZE) * ct); - if (strt < 0) { - ret[t++] = 0; - ret[t++] = len; - } else { - ret[t++] = strt; - ret[t++] = (startpos + origlen) - ((SIZE) * ct); - } - - } - ct++; - int ctdn = ret[t - 1] - pos1; - len -= (ctdn); - pos1 = 0;//startpos; - } - return ret; - } - - /** - * Gets the list of blocks needed to read the given sequence. - */ - public int[] getReadPositions(int startpos, int len) { - return getReadPositions(startpos, len, - (this.length >= StorageTable.BIGSTORAGE_SIZE)); - } - - /** - * Gets the mapping from stream offsets to file offsets over the given - * range. - *

                  - * This returns an array of integers arranged in pairs. The first value of - * each pair is an offset from start and the second is the - * corresponding offset in the source file. - */ - public int[] getFileOffsets(int start, int size) { - int[] smap = this.getReadPositions(start, size); - int[] fmap = new int[smap.length / 3 * 2]; - - int offset = 0; - int fidx = 0; - Block block = null; - Block prev; - for (int sidx = 0; sidx < smap.length; sidx += 3) { - prev = block; - block = (Block) this.blockmap.get(smap[sidx]); - - if (prev == null || block.getOriginalPos() + smap[sidx + 1] != - prev.getOriginalPos() + smap[sidx - 1]) { - fmap[fidx++] = offset; - fmap[fidx++] = block.getOriginalPos() + smap[sidx + 1]; - } - - offset += smap[sidx + 2] - smap[sidx + 1]; - } - - int[] ret = new int[fidx]; - System.arraycopy(fmap, 0, ret, 0, fidx); - return ret; - } - - public String getFileOffsetString(int start, int size) { - String ret = ""; - int[] map = this.getFileOffsets(start, size); - - for (int idx = 0; idx < map.length; idx += 2) { - ret += (idx != 0 ? " " : "") - + Integer.toHexString(map[idx + 0]).toUpperCase() + ":" - + Integer.toHexString(map[idx + 1]).toUpperCase(); - } - - return ret; - } - - /**Assign blocks to the recs - * - * @param rec - * @param startpos - * @return - */ -/* Not used - public int setBlocks(BlockByteConsumer rec, int startpos) { - if (rec.getBlocks() != null) - return rec.getFirstBlock(); - - int reclen = rec.getLength(); - - int numblocks = -1, firstblock = -1, lastblock = -1; - - // Handle assignment of blocks - // span blocks on either side of the block boundary - if (this.getApplyRelativePosition()) { - firstblock = rec.getOffset(); - firstblock /= BIGBLOCK.SIZE; - lastblock = reclen + rec.getOffset(); - lastblock /= BIGBLOCK.SIZE; - } else { - firstblock = startpos; - firstblock /= BIGBLOCK.SIZE; - lastblock = (reclen + startpos); - lastblock /= BIGBLOCK.SIZE; - } - - numblocks = lastblock - (firstblock - 1); - - if (numblocks < 1) - numblocks = 1; - - Block[] blks = new Block[numblocks]; - for (int t = 0; t < numblocks; t++) { - try { // inlining byte read - int getblk = firstblock + t; - //Logger.logInfo("GETTING BLOCK: "+ getblk); - blks[t] = (Block) this.getBlockmap().get(getblk); - } catch (Exception a) { - Logger.logWarn( - "ERROR: Bytes for block:" - + blks[t] - + " failed: " - + a - + " Output Corrupted."); - } - } - rec.setFirstBlock(firstblock); - rec.setLastBlock(lastblock); - rec.setBlocks(blks); - return firstblock; - } -*/ - - /** - * For whatever reason, get the blockmap - * - * @return the map of blocks - */ - public List getBlockmap() { - return blockmap; - } - - /** - * Set the map of Blocks contained in this reader - * - * @param list the map of blocks - */ - public void setBlockmap(AbstractList list) { - blockmap = list; - } - - /** - * @return - */ - public int getLength() { - return length; - } - - /** - * @param i - */ - public void setLength(int i) { - length = i; - } - - /** - * @param arg0 - * @return - */ - public static ByteBuffer allocate(int arg0) { - return ByteBuffer.allocate(arg0); - } - - /** - * @param arg0 - * @return - */ - public static ByteBuffer allocateDirect(int arg0) { - return ByteBuffer.allocateDirect(arg0); - } - - /** - * @param arg0 - * @return - */ - public final static ByteBuffer wrap(byte[] arg0) { - return ByteBuffer.wrap(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @param arg2 - * @return - */ - public final static ByteBuffer wrap(byte[] arg0, int arg1, int arg2) { - return ByteBuffer.wrap(arg0, arg1, arg2); - } - - /** - * @return - */ - public byte[] array() { - return backingByteBuffer.array(); - } - - /** - * @return - */ - public int arrayOffset() { - return backingByteBuffer.arrayOffset(); - } - - /** - * @return - */ - public CharBuffer asCharBuffer() { - return backingByteBuffer.asCharBuffer(); - } - - /** - * @return - */ - public DoubleBuffer asDoubleBuffer() { - return backingByteBuffer.asDoubleBuffer(); - } - - /** - * @return - */ - public FloatBuffer asFloatBuffer() { - return backingByteBuffer.asFloatBuffer(); - } - - /** - * @return - */ - public IntBuffer asIntBuffer() { - return backingByteBuffer.asIntBuffer(); - } - - /** - * @return - */ - public LongBuffer asLongBuffer() { - return backingByteBuffer.asLongBuffer(); - } - - /** - * @return - */ - public ByteBuffer asReadOnlyBuffer() { - return backingByteBuffer.asReadOnlyBuffer(); - } - - /** - * @return - */ - public ShortBuffer asShortBuffer() { - return backingByteBuffer.asShortBuffer(); - } - - /** - * @return - */ - public int capacity() { - return backingByteBuffer.capacity(); - } - - /** - * @return - */ - public Buffer clear() { - return backingByteBuffer.clear(); - } - - /** - * @return - */ - public ByteBuffer compact() { - return backingByteBuffer.compact(); - } - - /** - * @param arg0 - * @return public int compareTo(Object arg0) { - return backingByteBuffer.compareTo(arg0); - }*/ - - /** - * @return - */ - public ByteBuffer duplicate() { - return backingByteBuffer.duplicate(); - } - - /* (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - public boolean equals(Object arg0) { - return backingByteBuffer.equals(arg0); - } - - /** - * @return - */ - public Buffer flip() { - return backingByteBuffer.flip(); - } - - /** - * @return - */ - public byte get() { - return backingByteBuffer.get(); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer get(byte[] arg0) { - return backingByteBuffer.get(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @param arg2 - * @return - */ - public ByteBuffer get(byte[] arg0, int arg1, int arg2) { - return backingByteBuffer.get(arg0, arg1, arg2); - } - - /** - * @return - */ - public char getChar() { - return backingByteBuffer.getChar(); - } - - /** - * @param arg0 - * @return - */ - public char getChar(int arg0) { - return backingByteBuffer.getChar(arg0); - } - - /** - * @return - */ - public double getDouble() { - return backingByteBuffer.getDouble(); - } - - /** - * @param arg0 - * @return - */ - public double getDouble(int arg0) { - return backingByteBuffer.getDouble(arg0); - } - - /** - * @return - */ - public float getFloat() { - return backingByteBuffer.getFloat(); - } - - /** - * @param arg0 - * @return - */ - public float getFloat(int arg0) { - return backingByteBuffer.getFloat(arg0); - } - - /** - * @return - */ - public int getInt() { - return backingByteBuffer.getInt(); - } - - /** - * @param arg0 - * @return - */ - public int getInt(int arg0) { - return backingByteBuffer.getInt(arg0); - } - - /** - * @return - */ - public long getLong() { - return backingByteBuffer.getLong(); - } - - /** - * @param arg0 - * @return - */ - public long getLong(int arg0) { - return backingByteBuffer.getLong(arg0); - } - - /** - * @return - */ - public short getShort() { - return backingByteBuffer.getShort(); - } - - /** - * @param arg0 - * @return - */ - public short getShort(int arg0) { - return backingByteBuffer.getShort(arg0); - } - - /** - * @return - */ - public boolean hasArray() { - return backingByteBuffer.hasArray(); - } - - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - public int hashCode() { - return backingByteBuffer.hashCode(); - } - - /** - * @return - */ - public boolean hasRemaining() { - return backingByteBuffer.hasRemaining(); - } - - /** - * @return - */ - public boolean isDirect() { - return backingByteBuffer.isDirect(); - } - - /** - * @return - */ - public int limit() { - return backingByteBuffer.limit(); - } - - /** - * @param arg0 - * @return - */ - public Buffer limit(int arg0) { - return backingByteBuffer.limit(arg0); - } - - /** - * @return - */ - public Buffer mark() { - return backingByteBuffer.mark(); - } - - /** - * @return - */ - public ByteOrder order() { - return backingByteBuffer.order(); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer order(ByteOrder arg0) { - return backingByteBuffer.order(arg0); - } - - /** - * @return - */ - public int position() { - return backingByteBuffer.position(); - } - - /** - * @param arg0 - * @return - */ - public Buffer position(int arg0) { - return backingByteBuffer.position(arg0); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer put(byte arg0) { - return backingByteBuffer.put(arg0); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer put(byte[] arg0) { - return backingByteBuffer.put(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @param arg2 - * @return - */ - public ByteBuffer put(byte[] arg0, int arg1, int arg2) { - return backingByteBuffer.put(arg0, arg1, arg2); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer put(int arg0, byte arg1) { - return backingByteBuffer.put(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer put(ByteBuffer arg0) { - return backingByteBuffer.put(arg0); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putChar(char arg0) { - return backingByteBuffer.putChar(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putChar(int arg0, char arg1) { - return backingByteBuffer.putChar(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putDouble(double arg0) { - return backingByteBuffer.putDouble(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putDouble(int arg0, double arg1) { - return backingByteBuffer.putDouble(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putFloat(float arg0) { - return backingByteBuffer.putFloat(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putFloat(int arg0, float arg1) { - return backingByteBuffer.putFloat(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putInt(int arg0) { - return backingByteBuffer.putInt(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putInt(int arg0, int arg1) { - return backingByteBuffer.putInt(arg0, arg1); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putLong(int arg0, long arg1) { - return backingByteBuffer.putLong(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putLong(long arg0) { - return backingByteBuffer.putLong(arg0); - } - - /** - * @param arg0 - * @param arg1 - * @return - */ - public ByteBuffer putShort(int arg0, short arg1) { - return backingByteBuffer.putShort(arg0, arg1); - } - - /** - * @param arg0 - * @return - */ - public ByteBuffer putShort(short arg0) { - return backingByteBuffer.putShort(arg0); - } - - /** - * @return - */ - public int remaining() { - return backingByteBuffer.remaining(); - } - - /** - * @return - */ - public Buffer reset() { - return backingByteBuffer.reset(); - } - - /** - * @return - */ - public Buffer rewind() { - return backingByteBuffer.rewind(); - } - - /** - * @return - */ - public ByteBuffer slice() { - return backingByteBuffer.slice(); - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - public String toString() { - return backingByteBuffer.toString(); - } - - /** - * @return - */ - public ByteBuffer getBackingByteBuffer() { - return backingByteBuffer; - } - - /** - * @param buffer - */ - public void setBackingByteBuffer(ByteBuffer buffer) { - backingByteBuffer = buffer; - } - - /** - * @return - */ - public boolean getApplyRelativePosition() { - return applyRelativePosition; - } - - /** - * Only add the offset when we are fetching - * data from 'within' a record, ie: rkdata.get(i) - * as opposed to when we are traversing the entire collection - * of bytes as in WorkBookFactory.parse()... - *

                  - * * @param b - */ - public void setApplyRelativePosition(boolean b) { - applyRelativePosition = b; - } - -} diff --git a/src/main/java/io/starter/formats/LEO/BlockByteReader.kt b/src/main/java/io/starter/formats/LEO/BlockByteReader.kt new file mode 100644 index 0000000..b63a9a1 --- /dev/null +++ b/src/main/java/io/starter/formats/LEO/BlockByteReader.kt @@ -0,0 +1,967 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.LEO + +import io.starter.formats.XLS.WorkBookException +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.ByteArrayOutputStream +import java.io.Serializable +import java.nio.* +import java.util.AbstractList +import java.util.ArrayList + +/** + * Provide a translation layer between the block vector and a byte array. + * + * + * + * + * A record (Storage, XLSRecord) can retrieve bytes from Scattered blocks + * transparently. + * + * + * The BlockByteReader Allocates a ByteBuffer containing only byte references + * contained within Blocs assigned to the implementation class. + * + * + * A Class using this reader will either subclass the reader and manage its + * blocks, or interact with a shared reader. + * + * + * In the case of a shared reader, + */ +open class BlockByteReader : Serializable { + /** + * @return + */ + /** + * Only add the offset when we are fetching + * data from 'within' a record, ie: rkdata.get(i) + * as opposed to when we are traversing the entire collection + * of bytes as in WorkBookFactory.parse()... + * + * + * * @param b + */ + var applyRelativePosition = true + + /**Assign blocks to the recs + * + * @param rec + * @param startpos + * @return + */ + /* Not used + public int setBlocks(BlockByteConsumer rec, int startpos) { + if (rec.getBlocks() != null) + return rec.getFirstBlock(); + + int reclen = rec.getLength(); + + int numblocks = -1, firstblock = -1, lastblock = -1; + + // Handle assignment of blocks + // span blocks on either side of the block boundary + if (this.getApplyRelativePosition()) { + firstblock = rec.getOffset(); + firstblock /= BIGBLOCK.SIZE; + lastblock = reclen + rec.getOffset(); + lastblock /= BIGBLOCK.SIZE; + } else { + firstblock = startpos; + firstblock /= BIGBLOCK.SIZE; + lastblock = (reclen + startpos); + lastblock /= BIGBLOCK.SIZE; + } + + numblocks = lastblock - (firstblock - 1); + + if (numblocks < 1) + numblocks = 1; + + Block[] blks = new Block[numblocks]; + for (int t = 0; t < numblocks; t++) { + try { // inlining byte read + int getblk = firstblock + t; + //Logger.logInfo("GETTING BLOCK: "+ getblk); + blks[t] = (Block) this.getBlockmap().get(getblk); + } catch (Exception a) { + Logger.logWarn( + "ERROR: Bytes for block:" + + blks[t] + + " failed: " + + a + + " Output Corrupted."); + } + } + rec.setFirstBlock(firstblock); + rec.setLastBlock(lastblock); + rec.setBlocks(blks); + return firstblock; + } +*/ + + /** + * For whatever reason, get the blockmap + * + * @return the map of blocks + */ + var blockmap: List<*> = ArrayList() + private set + val isReadOnly = false + /** + * @return + */ + /** + * @param i + */ + var length = -1 + /** + * @return + */ + /** + * @param buffer + */ + @Transient + var backingByteBuffer = ByteBuffer.allocate(0) + + /** + * @return + */ + val char: Char + get() = backingByteBuffer.char + + /** + * @return + */ + val double: Double + get() = backingByteBuffer.double + + /** + * @return + */ + val float: Float + get() = backingByteBuffer.float + + /** + * @return + */ + val int: Int + get() = backingByteBuffer.int + + /** + * @return + */ + val long: Long + get() = backingByteBuffer.long + + /** + * @return + */ + val short: Short + get() = backingByteBuffer.short + + /** + * @return + */ + val isDirect: Boolean + get() = backingByteBuffer.isDirect + + protected constructor() { + // empty constructor... + } + + constructor(blokz: List<*>, len: Int) { + this.blockmap = blokz + this.length = len + } + + /** + * Allows for getting of header bytes without setting blocks on a rec + * + * @param startpos + * @return + */ + fun getHeaderBytes(startpos: Int): ByteArray { + var startpos = startpos + try { + var SIZE = BIGBLOCK.SIZE // normal case + if (this.length < StorageTable.BIGSTORAGE_SIZE) { + SIZE = SMALLBLOCK.SIZE + } + val block = startpos / SIZE + val check = startpos % SIZE + // handle EOF that falls right on boundary + if (check + 4 > SIZE && blockmap.size - 1 == block) { + // Last EOF falls within 4 bytes of 512 boundary... junkrec + return byteArrayOf(0x0, 0x0, 0x0, 0x0) + } else if (check + 4 > SIZE) { // SPANNER! + var bx = this.blockmap[block] as Block + var l1 = SIZE * (block + 1) - startpos + val s2 = startpos % SIZE + val b1 = bx.getBytes(s2, s2 + l1) + bx = this.blockmap[block + 1] as Block + l1 = 4 - l1 + val b2 = bx.getBytes(0, l1) + return ByteTools.append(b2, b1) + } + + val bx = this.blockmap[block] as Block + startpos -= block * SIZE + return bx.getBytes(startpos, startpos + 4) + } catch (e: RuntimeException) { + throw WorkBookException("Smallblock based workbooks are unsupported in OpenXLS: see http://extentech.com/uimodules/docs/docs_detail.jsp?showall=true&meme_id=195", WorkBookException.SMALLBLOCK_FILE) + } + + } + + + /* Return the byte from the blocks at the proper locations... + * + * @see java.nio.ByteBuffer#get() + */ + operator fun get(rec: BlockByteConsumer, startpos: Int): Byte { + return get(rec, startpos, 1)[0] + } + + /* Return the bytes from the blocks at the proper locations... + * + * as opposed to when we are traversing the entire collection of bytes as in WorkBookFactory.parse() + * + * @ see java.nio.ByteBuffer # get() + */ + operator fun get(rec: BlockByteConsumer, startpos: Int, len: Int): ByteArray { + var startpos = startpos + rec.byteReader = this + // we only want to add the offset when + // we are fetching data from 'within' a record, ie: rkdata.get(i) + val recoffy = rec.offset + if (this.applyRelativePosition) { + + startpos += 4 // add the offset + } + startpos += recoffy + // reality checks + if (false) + // ((startpos + len) > getLength()) + Logger.logWarn( + "WARNING: BlockByteReader.get(rec," + + startpos + + "," + + rec.length + + ") error. Attempt to read past end of Block buffer.") + + // return the bytes from the rec + return getRecBytes(rec, startpos, len) + } + + /* Handles the spanning of Record Bytes over Block boundaries + * then returns requested bytes. + * + */ + private fun getRecBytes( + rec: BlockByteConsumer, + startpos: Int, + len: Int): ByteArray { + if (startpos < 0 || startpos > startpos + len) + throw RuntimeException( + "ERROR: BBR.getRecBytes(" + + rec.javaClass.getName() + + "," + + startpos + + "," + + (startpos + len) + + ") failed - OUT OF BOUNDS.") + // get the block byte boundaries + val pos = this.getReadPositions(startpos, len) + val numblocks = pos.size / 3 + var blkdef = 0 + // backingByteBuffer = blokx.getByteBuffer(); + // Temporarily use BAOS... + val out = ByteArrayOutputStream() + for (t in 0 until numblocks) { + try { // inlining byte read + val b1 = this.blockmap[pos[blkdef++]] as Block + out.write(b1.getBytes(pos[blkdef++], pos[blkdef++])) + if (false) + Logger.logInfo( + "INFO: BBR.getRecBytes() " + + rec.javaClass.getName() + + " ACCESSING DATA for block:" + + b1.blockIndex + + ":" + + pos[0] + + "-" + + pos[1]) + } catch (a: Exception) { + Logger.logWarn( + "ERROR: BBR.getRecBytes streaming $rec bytes for block failed: $a") + } + + } + return out.toByteArray() + } + + /** + * Gets the list of blocks needed to read the given sequence. + */ + fun getReadPositions(startpos: Int, len: Int): IntArray { + return getReadPositions(startpos, len, + this.length >= StorageTable.BIGSTORAGE_SIZE) + } + + /** + * Gets the mapping from stream offsets to file offsets over the given + * range. + * + * + * This returns an array of integers arranged in pairs. The first value of + * each pair is an offset from `start` and the second is the + * corresponding offset in the source file. + */ + fun getFileOffsets(start: Int, size: Int): IntArray { + val smap = this.getReadPositions(start, size) + val fmap = IntArray(smap.size / 3 * 2) + + var offset = 0 + var fidx = 0 + var block: Block? = null + var prev: Block? + var sidx = 0 + while (sidx < smap.size) { + prev = block + block = this.blockmap[smap[sidx]] as Block + + if (prev == null || block.originalPos + smap[sidx + 1] != prev.originalPos + smap[sidx - 1]) { + fmap[fidx++] = offset + fmap[fidx++] = block.originalPos + smap[sidx + 1] + } + + offset += smap[sidx + 2] - smap[sidx + 1] + sidx += 3 + } + + val ret = IntArray(fidx) + System.arraycopy(fmap, 0, ret, 0, fidx) + return ret + } + + fun getFileOffsetString(start: Int, size: Int): String { + var ret = "" + val map = this.getFileOffsets(start, size) + + var idx = 0 + while (idx < map.size) { + ret += ((if (idx != 0) " " else "") + + Integer.toHexString(map[idx + 0]).toUpperCase() + ":" + + Integer.toHexString(map[idx + 1]).toUpperCase()) + idx += 2 + } + + return ret + } + + /** + * Set the map of Blocks contained in this reader + * + * @param list the map of blocks + */ + fun setBlockmap(list: AbstractList<*>) { + blockmap = list + } + + /** + * @return + */ + fun array(): ByteArray { + return backingByteBuffer.array() + } + + /** + * @return + */ + fun arrayOffset(): Int { + return backingByteBuffer.arrayOffset() + } + + /** + * @return + */ + fun asCharBuffer(): CharBuffer { + return backingByteBuffer.asCharBuffer() + } + + /** + * @return + */ + fun asDoubleBuffer(): DoubleBuffer { + return backingByteBuffer.asDoubleBuffer() + } + + /** + * @return + */ + fun asFloatBuffer(): FloatBuffer { + return backingByteBuffer.asFloatBuffer() + } + + /** + * @return + */ + fun asIntBuffer(): IntBuffer { + return backingByteBuffer.asIntBuffer() + } + + /** + * @return + */ + fun asLongBuffer(): LongBuffer { + return backingByteBuffer.asLongBuffer() + } + + /** + * @return + */ + fun asReadOnlyBuffer(): ByteBuffer { + return backingByteBuffer.asReadOnlyBuffer() + } + + /** + * @return + */ + fun asShortBuffer(): ShortBuffer { + return backingByteBuffer.asShortBuffer() + } + + /** + * @return + */ + fun capacity(): Int { + return backingByteBuffer.capacity() + } + + /** + * @return + */ + fun clear(): Buffer { + return backingByteBuffer.clear() + } + + /** + * @return + */ + fun compact(): ByteBuffer { + return backingByteBuffer.compact() + } + + /** + * @param arg0 + * @return public int compareTo(Object arg0) { + * return backingByteBuffer.compareTo(arg0); + * } + */ + + /** + * @return + */ + fun duplicate(): ByteBuffer { + return backingByteBuffer.duplicate() + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + override fun equals(arg0: Any?): Boolean { + return backingByteBuffer == arg0 + } + + /** + * @return + */ + fun flip(): Buffer { + return backingByteBuffer.flip() + } + + /** + * @return + */ + fun get(): Byte { + return backingByteBuffer.get() + } + + /** + * @param arg0 + * @return + */ + operator fun get(arg0: ByteArray): ByteBuffer { + return backingByteBuffer.get(arg0) + } + + /** + * @param arg0 + * @param arg1 + * @param arg2 + * @return + */ + operator fun get(arg0: ByteArray, arg1: Int, arg2: Int): ByteBuffer { + return backingByteBuffer.get(arg0, arg1, arg2) + } + + /** + * @param arg0 + * @return + */ + fun getChar(arg0: Int): Char { + return backingByteBuffer.getChar(arg0) + } + + /** + * @param arg0 + * @return + */ + fun getDouble(arg0: Int): Double { + return backingByteBuffer.getDouble(arg0) + } + + /** + * @param arg0 + * @return + */ + fun getFloat(arg0: Int): Float { + return backingByteBuffer.getFloat(arg0) + } + + /** + * @param arg0 + * @return + */ + fun getInt(arg0: Int): Int { + return backingByteBuffer.getInt(arg0) + } + + /** + * @param arg0 + * @return + */ + fun getLong(arg0: Int): Long { + return backingByteBuffer.getLong(arg0) + } + + /** + * @param arg0 + * @return + */ + fun getShort(arg0: Int): Short { + return backingByteBuffer.getShort(arg0) + } + + /** + * @return + */ + fun hasArray(): Boolean { + return backingByteBuffer.hasArray() + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + override fun hashCode(): Int { + return backingByteBuffer.hashCode() + } + + /** + * @return + */ + fun hasRemaining(): Boolean { + return backingByteBuffer.hasRemaining() + } + + /** + * @return + */ + fun limit(): Int { + return backingByteBuffer.limit() + } + + /** + * @param arg0 + * @return + */ + fun limit(arg0: Int): Buffer { + return backingByteBuffer.limit(arg0) + } + + /** + * @return + */ + fun mark(): Buffer { + return backingByteBuffer.mark() + } + + /** + * @return + */ + fun order(): ByteOrder { + return backingByteBuffer.order() + } + + /** + * @param arg0 + * @return + */ + fun order(arg0: ByteOrder): ByteBuffer { + return backingByteBuffer.order(arg0) + } + + /** + * @return + */ + fun position(): Int { + return backingByteBuffer.position() + } + + /** + * @param arg0 + * @return + */ + fun position(arg0: Int): Buffer { + return backingByteBuffer.position(arg0) + } + + /** + * @param arg0 + * @return + */ + fun put(arg0: Byte): ByteBuffer { + return backingByteBuffer.put(arg0) + } + + /** + * @param arg0 + * @return + */ + fun put(arg0: ByteArray): ByteBuffer { + return backingByteBuffer.put(arg0) + } + + /** + * @param arg0 + * @param arg1 + * @param arg2 + * @return + */ + fun put(arg0: ByteArray, arg1: Int, arg2: Int): ByteBuffer { + return backingByteBuffer.put(arg0, arg1, arg2) + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + fun put(arg0: Int, arg1: Byte): ByteBuffer { + return backingByteBuffer.put(arg0, arg1) + } + + /** + * @param arg0 + * @return + */ + fun put(arg0: ByteBuffer): ByteBuffer { + return backingByteBuffer.put(arg0) + } + + /** + * @param arg0 + * @return + */ + fun putChar(arg0: Char): ByteBuffer { + return backingByteBuffer.putChar(arg0) + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + fun putChar(arg0: Int, arg1: Char): ByteBuffer { + return backingByteBuffer.putChar(arg0, arg1) + } + + /** + * @param arg0 + * @return + */ + fun putDouble(arg0: Double): ByteBuffer { + return backingByteBuffer.putDouble(arg0) + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + fun putDouble(arg0: Int, arg1: Double): ByteBuffer { + return backingByteBuffer.putDouble(arg0, arg1) + } + + /** + * @param arg0 + * @return + */ + fun putFloat(arg0: Float): ByteBuffer { + return backingByteBuffer.putFloat(arg0) + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + fun putFloat(arg0: Int, arg1: Float): ByteBuffer { + return backingByteBuffer.putFloat(arg0, arg1) + } + + /** + * @param arg0 + * @return + */ + fun putInt(arg0: Int): ByteBuffer { + return backingByteBuffer.putInt(arg0) + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + fun putInt(arg0: Int, arg1: Int): ByteBuffer { + return backingByteBuffer.putInt(arg0, arg1) + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + fun putLong(arg0: Int, arg1: Long): ByteBuffer { + return backingByteBuffer.putLong(arg0, arg1) + } + + /** + * @param arg0 + * @return + */ + fun putLong(arg0: Long): ByteBuffer { + return backingByteBuffer.putLong(arg0) + } + + /** + * @param arg0 + * @param arg1 + * @return + */ + fun putShort(arg0: Int, arg1: Short): ByteBuffer { + return backingByteBuffer.putShort(arg0, arg1) + } + + /** + * @param arg0 + * @return + */ + fun putShort(arg0: Short): ByteBuffer { + return backingByteBuffer.putShort(arg0) + } + + /** + * @return + */ + fun remaining(): Int { + return backingByteBuffer.remaining() + } + + /** + * @return + */ + fun reset(): Buffer { + return backingByteBuffer.reset() + } + + /** + * @return + */ + fun rewind(): Buffer { + return backingByteBuffer.rewind() + } + + /** + * @return + */ + fun slice(): ByteBuffer { + return backingByteBuffer.slice() + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + override fun toString(): String { + return backingByteBuffer.toString() + } + + companion object { + + /** + * serialVersionUID + */ + private const val serialVersionUID = 4845306509411520019L + + + /** + * returns the lengths of the two byte + *

                  +         * ie: start 10 len 8
                  +         * blk0 = 10-18
                  +         * 0,10,18
                  +         *
                  +         * ie: start 514 len 1
                  +         * blk1 = 2-3
                  +         * 1,2,3
                  +         *
                  +         * ie: start 480 len 1124
                  +         *
                  +         * blk0 = 480-512
                  +         * blk1 = 512-1024
                  +         * blk2 = 1024-1536
                  +         * blk3 = 1536-1604
                  +         *
                  +         * [0, 480, 512,1, 0, 512, 2, 0, 512, 3, 0, 68]
                  +         *
                  +         * 8139, 693
                  +        
                  * + * + * @param startpos + * @return + */ + fun getReadPositions(startpos: Int, len: Int, BIGBLOCKSTORAGE: Boolean): IntArray { + var len = len + // Logger.logInfo("BBR.getReadPositions()"+startpos+":"+endpos); + + // 20100323 KSC: handle small blocks + var SIZE = BIGBLOCK.SIZE // normal case + if (!BIGBLOCKSTORAGE) + SIZE = SMALLBLOCK.SIZE + + + var firstblock = startpos / SIZE + val lastblock = (startpos + len) / SIZE + var numblocks = lastblock - firstblock + numblocks++ + val origlen = len + + var ct = startpos / SIZE + + + var pos1 = startpos + var ret = IntArray(numblocks * 3) + var t = 0 + // for each block, create 2 byte positions + while (len > 0) { + if (t >= ret.size) { + Logger.logWarn("BlockByteReader.getReadPositions() wrong guess on NumBlocks.") + numblocks++ + val retz = IntArray(numblocks * 3) + System.arraycopy(ret, 0, retz, 0, ret.size) + ret = retz + } + ret[t++] = firstblock++ + var check = pos1 % SIZE //leftover + check += len + if (check > SIZE) { // SPANNER! + pos1 = startpos - SIZE * ct + if (pos1 < 0) pos1 = 0 + // int s1 = pos1- ((SIZE)*(ct)); + var s2 = pos1 % SIZE + if (s2 < 0) { + s2 = 0 + pos1 = 0 + } + ret[t++] = s2 + ret[t++] = SIZE + } else { + pos1 = startpos - SIZE * ct + val strt = startpos - SIZE * ct + if (strt < 0) { + ret[t++] = 0 + ret[t++] = len + } else { + ret[t++] = strt + ret[t++] = startpos + origlen - SIZE * ct + } + + } + ct++ + val ctdn = ret[t - 1] - pos1 + len -= ctdn + pos1 = 0//startpos; + } + return ret + } + + /** + * @param arg0 + * @return + */ + fun allocate(arg0: Int): ByteBuffer { + return ByteBuffer.allocate(arg0) + } + + /** + * @param arg0 + * @return + */ + fun allocateDirect(arg0: Int): ByteBuffer { + return ByteBuffer.allocateDirect(arg0) + } + + /** + * @param arg0 + * @return + */ + fun wrap(arg0: ByteArray): ByteBuffer { + return ByteBuffer.wrap(arg0) + } + + /** + * @param arg0 + * @param arg1 + * @param arg2 + * @return + */ + fun wrap(arg0: ByteArray, arg1: Int, arg2: Int): ByteBuffer { + return ByteBuffer.wrap(arg0, arg1, arg2) + } + } + +} diff --git a/src/main/java/io/starter/formats/LEO/BlockFactory.java b/src/main/java/io/starter/formats/LEO/BlockFactory.java deleted file mode 100644 index 1d6122d..0000000 --- a/src/main/java/io/starter/formats/LEO/BlockFactory.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.LEO; - -import io.starter.toolkit.ByteTools; - -import java.io.ByteArrayInputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - - -/** - * Dutifully produces LEO file Blocks. Complains not. - */ -public class BlockFactory { - /** - * get a new, empty Block - */ - public final static Block getPrototypeBlock(int type) { - Block retblock = null; - ByteBuffer dta = null; - switch (type) { - case Block.BIG: - dta = ByteBuffer.allocate(BIGBLOCK.SIZE); - retblock = new BIGBLOCK(); - break; - case Block.SMALL: - dta = ByteBuffer.allocate(SMALLBLOCK.SIZE); - retblock = new SMALLBLOCK(); - } - dta.order(ByteOrder.LITTLE_ENDIAN); - //for (int x = 0; x < dta.limit(); x++) - // dta.put((byte) - 1); - retblock.setBytes(dta); - return retblock; - } - - /** - * transform the byte array into Block records. - */ - public final static Block[] getBlocksFromOutputStream(OutputStream bbuf, int blen, int type) { - int SIZE = 0; - switch (type) { - case Block.BIG: - SIZE = 512; - break; - case Block.SMALL: - SIZE = 64; - } - int sz = LEOFile.getSizeInBlocks(blen, SIZE); - - if (bbuf == null) - return null; - // int len = (blen-3) / SIZE; - int len = (blen) / SIZE; - int pos = 0, sizeDiff = 0, size = 0; - - if ((len * SIZE) < blen) { - len++; - sizeDiff = (len * SIZE) - blen; - } - Block[] blockarr = new Block[len]; - ByteArrayInputStream ins = null; - - // KSC: made a bit simpler upon padding situations ... - // get ALL blockVect (512 byte chunks of file) - for (int i = 0; i < len; i++) { - Block bbd = getPrototypeBlock(type); - byte[] bb = bbd.getBytes(); - int filepos = i * SIZE; - if (type == Block.SMALL) // smallblocks don't need file offset as they are allocated differently than bigblocks - filepos = 0; - - size = SIZE; - // make simpler: - if (blen - pos < size) - size = (blen - pos); - ins.read(bb, pos, size); - bbd.init(ByteBuffer.wrap(bb), i, filepos); - pos += SIZE; - blockarr[i] = bbd; - } - return blockarr; - } - - /** - * transform the byte array into Block records. - */ - public final static Block[] getBlocksFromByteArray(byte[] bbuf, int type) { - int SIZE = 0; - switch (type) { - case Block.BIG: - SIZE = 512; - break; - case Block.SMALL: - SIZE = 64; - } - if (bbuf == null) - return null; -// int sz = LEOFile.getSizeInBlocks(bbuf.length,SIZE); - - int len = (bbuf.length) / SIZE; - int pos = 0, sizeDiff = 0, size = 0; - - if ((len * SIZE) < bbuf.length) { - len++; // PAD - this most usually hits when called from buildSSAT - sizeDiff = (len * SIZE) - bbuf.length; - byte[] bb = new byte[sizeDiff]; - bbuf = ByteTools.append(bb, bbuf); - } - Block[] blockarr = new Block[len]; - - int start = 1; // for BigBlocks, skip 1st block, for small - if (type == Block.SMALL) - start = 0; - // get ALL blockVect (512 byte chunks of file) - for (int i = 0; i < len; i++) { - Block bbd = getPrototypeBlock(type); - byte[] bb = bbd.getBytes(); - -/* int filepos = (i + start) * SIZE; - if (type==Block.SMALL) // smallblocks don't need file offset as they are allocated differently than bigblocks - filepos= 0; -*/ - size = SIZE; - // make it simpler: - if (bbuf.length - pos < size) - size = (bbuf.length - pos); // account for leftovers (Block padding) - /*if (i + start == len) { - size -= sizeDiff; // account for leftovers (Block padding) - }*/ - // ExcelTools.benchmark("BlockFactory initting new Block@: " + pos + " sz: " +size + " len:" + bbuf.length); - System.arraycopy(bbuf, pos, bb, 0, size); - bbd.init(ByteBuffer.wrap(bb), i, 0); // THIS IS A BYTEARRAY ALL OFFSETS ARE 0 filepos); - pos += size; - blockarr[i] = bbd; - } - return blockarr; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/BlockFactory.kt b/src/main/java/io/starter/formats/LEO/BlockFactory.kt new file mode 100644 index 0000000..a0b38d6 --- /dev/null +++ b/src/main/java/io/starter/formats/LEO/BlockFactory.kt @@ -0,0 +1,162 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.LEO + +import io.starter.toolkit.ByteTools + +import java.io.ByteArrayInputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.nio.ByteOrder + + +/** + * Dutifully produces LEO file Blocks. Complains not. + */ +object BlockFactory { + /** + * get a new, empty Block + */ + fun getPrototypeBlock(type: Int): Block { + var retblock: Block? = null + var dta: ByteBuffer? = null + when (type) { + Block.BIG -> { + dta = ByteBuffer.allocate(BIGBLOCK.SIZE) + retblock = BIGBLOCK() + } + Block.SMALL -> { + dta = ByteBuffer.allocate(SMALLBLOCK.SIZE) + retblock = SMALLBLOCK() + } + } + dta!!.order(ByteOrder.LITTLE_ENDIAN) + //for (int x = 0; x < dta.limit(); x++) + // dta.put((byte) - 1); + retblock!!.setBytes(dta) + return retblock + } + + /** + * transform the byte array into Block records. + */ + fun getBlocksFromOutputStream(bbuf: OutputStream?, blen: Int, type: Int): Array? { + var SIZE = 0 + when (type) { + Block.BIG -> SIZE = 512 + Block.SMALL -> SIZE = 64 + } + val sz = LEOFile.getSizeInBlocks(blen, SIZE) + + if (bbuf == null) + return null + // int len = (blen-3) / SIZE; + var len = blen / SIZE + var pos = 0 + var sizeDiff = 0 + var size = 0 + + if (len * SIZE < blen) { + len++ + sizeDiff = len * SIZE - blen + } + val blockarr = arrayOfNulls(len) + val ins: ByteArrayInputStream? = null + + // KSC: made a bit simpler upon padding situations ... + // get ALL blockVect (512 byte chunks of file) + for (i in 0 until len) { + val bbd = getPrototypeBlock(type) + val bb = bbd.bytes + var filepos = i * SIZE + if (type == Block.SMALL) + // smallblocks don't need file offset as they are allocated differently than bigblocks + filepos = 0 + + size = SIZE + // make simpler: + if (blen - pos < size) + size = blen - pos + ins!!.read(bb, pos, size) + bbd.init(ByteBuffer.wrap(bb), i, filepos) + pos += SIZE + blockarr[i] = bbd + } + return blockarr + } + + /** + * transform the byte array into Block records. + */ + fun getBlocksFromByteArray(bbuf: ByteArray?, type: Int): Array? { + var bbuf = bbuf + var SIZE = 0 + when (type) { + Block.BIG -> SIZE = 512 + Block.SMALL -> SIZE = 64 + } + if (bbuf == null) + return null + // int sz = LEOFile.getSizeInBlocks(bbuf.length,SIZE); + + var len = bbuf.size / SIZE + var pos = 0 + var sizeDiff = 0 + var size = 0 + + if (len * SIZE < bbuf.size) { + len++ // PAD - this most usually hits when called from buildSSAT + sizeDiff = len * SIZE - bbuf.size + val bb = ByteArray(sizeDiff) + bbuf = ByteTools.append(bb, bbuf) + } + val blockarr = arrayOfNulls(len) + + var start = 1 // for BigBlocks, skip 1st block, for small + if (type == Block.SMALL) + start = 0 + // get ALL blockVect (512 byte chunks of file) + for (i in 0 until len) { + val bbd = getPrototypeBlock(type) + val bb = bbd.bytes + + /* int filepos = (i + start) * SIZE; + if (type==Block.SMALL) // smallblocks don't need file offset as they are allocated differently than bigblocks + filepos= 0; +*/ + size = SIZE + // make it simpler: + if (bbuf!!.size - pos < size) + size = bbuf.size - pos // account for leftovers (Block padding) + /*if (i + start == len) { + size -= sizeDiff; // account for leftovers (Block padding) + }*/ + // ExcelTools.benchmark("BlockFactory initting new Block@: " + pos + " sz: " +size + " len:" + bbuf.length); + System.arraycopy(bbuf, pos, bb, 0, size) + bbd.init(ByteBuffer.wrap(bb), i, 0) // THIS IS A BYTEARRAY ALL OFFSETS ARE 0 filepos); + pos += size + blockarr[i] = bbd + } + return blockarr + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/BlockImpl.java b/src/main/java/io/starter/formats/LEO/BlockImpl.java deleted file mode 100644 index 52724fe..0000000 --- a/src/main/java/io/starter/formats/LEO/BlockImpl.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.LEO; - -import io.starter.toolkit.CompatibleVectorHints; -import io.starter.toolkit.Logger; - -import java.io.OutputStream; -import java.io.Serializable; -import java.nio.ByteBuffer; -import java.util.List; - - -/** - * LEO File Block Information Record - *

                  - * These blocks of data contain information related to the - * LEO file format data blocks. - */ -public abstract class BlockImpl - implements io.starter.formats.LEO.Block, CompatibleVectorHints, Serializable { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4833713921208834278L; - /*allows the block to be populated with a byte array - rather than just a bytebuffer, easing debugging - **/ - boolean DEBUG = false; - - /** - * methods from CompatibleVectorHints - */ - transient int recordIdx = -1, lastidx = -1; - transient ByteBuffer data = null; // new byte[SIZE]; - private List blockvec = null; - - // implement iterator for use in - // chaining blocks - public Block nextblock = null; - private boolean isXBAT = false; - - int originalidx, originalpos; - boolean isBBDepotBlock = false; - boolean isSBDepotBlock = false; - boolean isSpecialBlock = false; - private boolean initialized = false; - private boolean streamed = false; - public Storage mystore = null; - - - public void close() { - if (blockvec != null) { - blockvec.clear(); - blockvec = null; - } - if (nextblock != null && nextblock != this) { - nextblock = null; - } - mystore = null; - if (data != null) { - data.clear(); - data = null; - } - } - - /** - * return the ByteBuffer for this BLOCK - */ - public ByteBuffer getByteBuffer() { - return data; - } - - /** - * Write the entire bytes directly to out - * - * @see io.starter.formats.LEO.Block#writeBytes(java.io.OutputStream) - */ - public void writeBytes(OutputStream out) { - try { - out.write(getBytes()); - } catch (Exception exp) { - Logger.logErr("BlockImpl.writeBytes failed.", exp); - } - } - - - //byte[] delbytes = null; - - /** - * return the byte Array for this BLOCK - */ - public byte[] getBytes(int start, int end) { - //if (delbytes == null) delbytes = getBytes(); - int SIZE = end - start; - if ((end) > this.getBlockSize()) - throw new RuntimeException("WARNING: BlockImpl.getBytes(): read position > block size:" + SIZE + start); - - byte[] ret = new byte[SIZE]; - int capcheck = data.capacity(); - // TODO: track why this is occurring - if (capcheck <= SIZE) - originalpos = 0; - // CAN HAPPEN ON OUT-OF-SPEC FILES whom have last block size < 512 - if (capcheck < SIZE + originalpos) - SIZE = capcheck - originalpos; - try { - start += originalpos; - data.position(start); - data.get(ret, 0, SIZE); - } catch (Exception e) { - Logger.logWarn("BlockImpl.getBytes() start: " + start + " size: " + SIZE + ": " + e); - } - return ret; - } - - public int getBlockSize() { - if (this.getBlockType() == BIG) - return BIGBLOCK.SIZE; - else if (this.getBlockType() == SMALL) - return SMALLBLOCK.SIZE; - else - return 0; - } - - /** - * return the byte Array for this BLOCK - */ - public byte[] getBytes() { - int SIZE = 0; - if (this.getBlockType() == BIG) - SIZE = BIGBLOCK.SIZE; - else if (this.getBlockType() == SMALL) - SIZE = SMALLBLOCK.SIZE; - byte[] ret = new byte[SIZE]; - - int capcheck = data.capacity(); - if (capcheck <= originalpos) // why is this hitting???????? - originalpos = 0; - if (capcheck < SIZE + originalpos) - SIZE = capcheck - originalpos; // CAN HAPPEN ON OUT-OF-SPEC FILES whom have last block size < 512 - try { - data.position(originalpos); - data.get(ret, 0, SIZE); - } catch (Exception e) { - Logger.logWarn("BlockImpl.getBytes(0," + SIZE + "): " + e); - } - return ret; - } - - /** - * set the data bytes on this Block - */ - public void setBytes(ByteBuffer b) { - data = b; - //if(DEBUG) { - // Logger.logInfo("Debugging turned on in BlockImpl.setBytes"); - //delbytes = getBytes(); - //} - } - - /** - * provide a hint to the CompatibleVector - * about this objects likely position. - */ - public int getRecordIndexHint() { - return recordIdx; - } - - /** - * set index information about this - * objects likely position. - */ - public void setRecordIndexHint(int i) { - recordIdx = i; - } - - /** - * link to the vector of blocks for the storage - */ - public void setBlockVector(List v) { - blockvec = v; - } - - /** - * get the index of this Block in the storage - * Vector - */ - public int getBlockIndex() { - if (blockvec == null) - return this.recordIdx; - return blockvec.indexOf(this); - } - - /** - * set the original BB position in the file - */ - public void setOriginalIdx(int x) { - originalidx = x; - } - - public void setNextBlock(Block b) { - nextblock = b; -/* if (LEOFile.DEBUG) - Logger.logInfo( - "INFO: BlockImpl setNextBlock(): " + b.toString());*/ - } - - public boolean hasNext() { - return nextblock != null; - } - - public Object next() { - return nextblock; - } - - public void remove() { - this.mystore.removeBlock(this); - nextblock = null; - } - - /** - * set the storage for this Block - */ - public void setStorage(Storage s) { - mystore = s; - } - - /** - * get the storage for this Block - */ - public Storage getStorage() { - return mystore; - } - - /** - * return the original position of this BIGBLOCK - */ - public int getOriginalIdx() { - return originalidx; - } - - /** returns the original BB pos - - public int getOriginalPos(){return this.originalpos;} - */ - /** - * returns whether this block has been - * added to the output stream - */ - public boolean getStreamed() { - return streamed; - } - - /** - * sets whether this block has been - * added to the output stream - */ - public void setStreamed(boolean b) { - streamed = b; - } - - /** - * set whether this Block has been read yet... - */ - public void setInitialized(boolean b) { - initialized = b; - } - - /** - * returns whether this Block has been read yet... - */ - public boolean getInitialized() { - return initialized; - } - - /** - * returns true if this is a Block Depot block - * that needs to be ignored when reading byte storages - */ - public boolean getIsSpecialBlock() { - return isSpecialBlock; - } - - /** - * returns true if this is a Block Depot block - * that needs to be ignored when reading byte storages - */ - public boolean getIsDepotBlock() { - return isBBDepotBlock; - } - - /** - * set to true if this is a Block Depot block - */ - public void setIsDepotBlock(boolean b) { - isSpecialBlock = b; - isBBDepotBlock = b; - } - - /** - * init the BIGBLOCK Data - */ - public void init(ByteBuffer d, int origidx, int origp) { - originalidx = origidx; - originalpos = origp; - this.setBytes(d); - } - - /** - * return the original position of this - * BIGBLOCK record in the array of BIGBLOCKS - * that make up the file. - */ - public int getOriginalPos() { - return originalpos; - } - - /** - * @return - */ - public boolean isXBAT() { - return isXBAT; - } - - /** - * true if ths block is represents an extra DIFAT sector - * - * @param b - */ - public void setIsExtraSector(boolean b) { - isXBAT = b; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/BlockImpl.kt b/src/main/java/io/starter/formats/LEO/BlockImpl.kt new file mode 100644 index 0000000..8b71472 --- /dev/null +++ b/src/main/java/io/starter/formats/LEO/BlockImpl.kt @@ -0,0 +1,312 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.LEO + +import io.starter.toolkit.CompatibleVectorHints +import io.starter.toolkit.Logger + +import java.io.OutputStream +import java.io.Serializable +import java.nio.ByteBuffer + + +/** + * LEO File Block Information Record + * + * + * These blocks of data contain information related to the + * LEO file format data blocks. + */ +abstract class BlockImpl : io.starter.formats.LEO.Block, CompatibleVectorHints, Serializable { + /*allows the block to be populated with a byte array + rather than just a bytebuffer, easing debugging + **/ + internal var DEBUG = false + + /** + * methods from CompatibleVectorHints + */ + /** + * provide a hint to the CompatibleVector + * about this objects likely position. + */ + /** + * set index information about this + * objects likely position. + */ + @Transient + override var recordIndexHint = -1 + @Transient + internal var lastidx = -1 + /** + * return the ByteBuffer for this BLOCK + */ + @Transient + override var byteBuffer: ByteBuffer? = null + internal set // new byte[SIZE]; + private var blockvec: MutableList<*>? = null + + // implement iterator for use in + // chaining blocks + var nextblock: Block? = null + /** + * @return + */ + override var isXBAT = false + private set + + /** + * return the original position of this BIGBLOCK + */ + /** + * set the original BB position in the file + */ + override var originalIdx: Int = 0 + /** + * return the original position of this + * BIGBLOCK record in the array of BIGBLOCKS + * that make up the file. + */ + override var originalPos: Int = 0 + internal set + internal var isBBDepotBlock = false + internal var isSBDepotBlock = false + /** + * returns true if this is a Block Depot block + * that needs to be ignored when reading byte storages + */ + override var isSpecialBlock = false + internal set + /** + * returns whether this Block has been read yet... + */ + /** + * set whether this Block has been read yet... + */ + override var initialized = false + /** returns the original BB pos + * + * public int getOriginalPos(){return this.originalpos;} + */ + /** + * returns whether this block has been + * added to the output stream + */ + /** + * sets whether this block has been + * added to the output stream + */ + override var streamed = false + /** + * get the storage for this Block + */ + /** + * set the storage for this Block + */ + override var storage: Storage? = null + + override val blockSize: Int + get() = if (this.blockType == Block.BIG) + BIGBLOCK.SIZE + else if (this.blockType == Block.SMALL) + SMALLBLOCK.SIZE + else + 0 + + /** + * return the byte Array for this BLOCK + */ + override// why is this hitting???????? + // CAN HAPPEN ON OUT-OF-SPEC FILES whom have last block size < 512 + val bytes: ByteArray + get() { + var SIZE = 0 + if (this.blockType == Block.BIG) + SIZE = BIGBLOCK.SIZE + else if (this.blockType == Block.SMALL) + SIZE = SMALLBLOCK.SIZE + val ret = ByteArray(SIZE) + + val capcheck = byteBuffer!!.capacity() + if (capcheck <= originalPos) + originalPos = 0 + if (capcheck < SIZE + originalPos) + SIZE = capcheck - originalPos + try { + byteBuffer!!.position(originalPos) + byteBuffer!!.get(ret, 0, SIZE) + } catch (e: Exception) { + Logger.logWarn("BlockImpl.getBytes(0,$SIZE): $e") + } + + return ret + } + + /** + * get the index of this Block in the storage + * Vector + */ + override val blockIndex: Int + get() = if (blockvec == null) this.recordIndexHint else blockvec!!.indexOf(this) + + /** + * returns true if this is a Block Depot block + * that needs to be ignored when reading byte storages + */ + /** + * set to true if this is a Block Depot block + */ + override var isDepotBlock: Boolean + get() = isBBDepotBlock + set(b) { + isSpecialBlock = b + isBBDepotBlock = b + } + + + fun close() { + if (blockvec != null) { + blockvec!!.clear() + blockvec = null + } + if (nextblock != null && nextblock !== this) { + nextblock = null + } + storage = null + if (byteBuffer != null) { + byteBuffer!!.clear() + byteBuffer = null + } + } + + /** + * Write the entire bytes directly to out + * + * @see io.starter.formats.LEO.Block.writeBytes + */ + override fun writeBytes(out: OutputStream) { + try { + out.write(bytes) + } catch (exp: Exception) { + Logger.logErr("BlockImpl.writeBytes failed.", exp) + } + + } + + + //byte[] delbytes = null; + + /** + * return the byte Array for this BLOCK + */ + override fun getBytes(start: Int, end: Int): ByteArray { + var start = start + //if (delbytes == null) delbytes = getBytes(); + var SIZE = end - start + if (end > this.blockSize) + throw RuntimeException("WARNING: BlockImpl.getBytes(): read position > block size:$SIZE$start") + + val ret = ByteArray(SIZE) + val capcheck = byteBuffer!!.capacity() + // TODO: track why this is occurring + if (capcheck <= SIZE) + originalPos = 0 + // CAN HAPPEN ON OUT-OF-SPEC FILES whom have last block size < 512 + if (capcheck < SIZE + originalPos) + SIZE = capcheck - originalPos + try { + start += originalPos + byteBuffer!!.position(start) + byteBuffer!!.get(ret, 0, SIZE) + } catch (e: Exception) { + Logger.logWarn("BlockImpl.getBytes() start: $start size: $SIZE: $e") + } + + return ret + } + + /** + * set the data bytes on this Block + */ + override fun setBytes(b: ByteBuffer) { + byteBuffer = b + //if(DEBUG) { + // Logger.logInfo("Debugging turned on in BlockImpl.setBytes"); + //delbytes = getBytes(); + //} + } + + /** + * link to the vector of blocks for the storage + */ + override fun setBlockVector(v: MutableList<*>) { + blockvec = v + } + + override fun setNextBlock(b: Block) { + nextblock = b + /* if (LEOFile.DEBUG) + Logger.logInfo( + "INFO: BlockImpl setNextBlock(): " + b.toString());*/ + } + + override fun hasNext(): Boolean { + return nextblock != null + } + + override fun next(): Any? { + return nextblock + } + + override fun remove() { + this.storage!!.removeBlock(this) + nextblock = null + } + + /** + * init the BIGBLOCK Data + */ + override fun init(d: ByteBuffer, origidx: Int, origp: Int) { + originalIdx = origidx + originalPos = origp + this.setBytes(d) + } + + /** + * true if ths block is represents an extra DIFAT sector + * + * @param b + */ + override fun setIsExtraSector(b: Boolean) { + isXBAT = b + } + + companion object { + + /** + * serialVersionUID + */ + private const val serialVersionUID = 4833713921208834278L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/FileBuffer.java b/src/main/java/io/starter/formats/LEO/FileBuffer.java deleted file mode 100644 index e7413d2..0000000 --- a/src/main/java/io/starter/formats/LEO/FileBuffer.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -/* - * Created on Apr 25, 2005 - * - * TODO To change the template for this generated file go to - * Window - Preferences - Java - Code Style - Code Templates - */ -package io.starter.formats.LEO; - -import io.starter.toolkit.TempFileManager; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; - -/** - * TODO To change the template for this generated type comment go to - * Window - Preferences - Java - Code Style - Code Templates - */ -public class FileBuffer { - - private transient ByteBuffer buffer = null; - private File tempfile = null; - FileChannel channel = null; - FileInputStream input = null; - - public void close() - throws IOException { - input.close(); - channel.close(); - if (tempfile != null) { - tempfile.deleteOnExit(); - tempfile.delete(); - } - tempfile = null; - } - - /** - * - */ - public FileBuffer() { - super(); - // TODO Auto-generated constructor stub - } - - public static FileBuffer readFile(String fpath) { - try { - File fx0 = new File(fpath); - return readFile(fx0); - } catch (Throwable e) { - throw new InvalidFileException("LEO FileBuffer.readFile() failed: " + e.toString()); - } - } - - public static FileBuffer readFile(File fx0) { - try { - FileBuffer fb = new FileBuffer(); - fb.input = new FileInputStream(fx0); - fb.channel = fb.input.getChannel(); - int fileLength = (int) fb.channel.size(); - // MappedByteBuffer - fb.buffer = - fb.channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength); - fb.buffer.order(ByteOrder.LITTLE_ENDIAN); - return fb; - } catch (Throwable e) { - throw new InvalidFileException("LEO FileBuffer.readFile() failed: " + e.toString()); - } - } - - //TODO: reimplement temp files and deal with cleanup -jm - public static FileBuffer readFileUsingTemp(String fpath) { - return readFileUsingTemp(new File(fpath)); - } - - public static FileBuffer readFileUsingTemp(File fx0) { - try { - FileBuffer fb = new FileBuffer(); - // create Temp file and populate - fb.tempfile = TempFileManager.createTempFile("LEOFile_", ".tmp"); - fb.tempfile.delete(); - - FileInputStream input0 = new FileInputStream(fx0); - FileChannel channel0 = input0.getChannel(); - FileOutputStream output0 = new FileOutputStream(fb.tempfile); - FileChannel channel1 = output0.getChannel(); - channel0.transferTo(0, fx0.length(), channel1); - - channel0.close(); - channel1.close(); - input0.close(); - output0.close(); - - fb.input = new FileInputStream(fb.tempfile); - fb.channel = fb.input.getChannel(); - int fileLength = (int) fb.channel.size(); - // MappedByteBuffer - fb.buffer = - fb.channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength); - fb.buffer.order(ByteOrder.LITTLE_ENDIAN); - return fb; - } catch (Throwable e) { - throw new InvalidFileException("LEO FileBuffer.readFile() failed: " + e.toString()); - } - } - - /** - * @return Returns the buffer. - */ - public ByteBuffer getBuffer() { - return buffer; - } - - /** - * @param buffer The buffer to set. - */ - public void setBuffer(ByteBuffer b) { - this.buffer = b; - } -} diff --git a/src/main/java/io/starter/formats/LEO/FileBuffer.kt b/src/main/java/io/starter/formats/LEO/FileBuffer.kt new file mode 100644 index 0000000..c3476b5 --- /dev/null +++ b/src/main/java/io/starter/formats/LEO/FileBuffer.kt @@ -0,0 +1,137 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +/* + * Created on Apr 25, 2005 + * + * TODO To change the template for this generated file go to + * Window - Preferences - Java - Code Style - Code Templates + */ +package io.starter.formats.LEO + +import io.starter.toolkit.TempFileManager + +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.channels.FileChannel + +/** + * TODO To change the template for this generated type comment go to + * Window - Preferences - Java - Code Style - Code Templates + */ +/** + * + */ +class FileBuffer { + + /** + * @return Returns the buffer. + */ + /** + * @param buffer The buffer to set. + */ + @Transient + var buffer: ByteBuffer? = null + private var tempfile: File? = null + internal var channel: FileChannel? = null + internal var input: FileInputStream? = null + + @Throws(IOException::class) + fun close() { + input!!.close() + channel!!.close() + if (tempfile != null) { + tempfile!!.deleteOnExit() + tempfile!!.delete() + } + tempfile = null + } + + companion object { + + fun readFile(fpath: String): FileBuffer { + try { + val fx0 = File(fpath) + return readFile(fx0) + } catch (e: Throwable) { + throw InvalidFileException("LEO FileBuffer.readFile() failed: $e") + } + + } + + fun readFile(fx0: File): FileBuffer { + try { + val fb = FileBuffer() + fb.input = FileInputStream(fx0) + fb.channel = fb.input!!.channel + val fileLength = fb.channel!!.size().toInt() + // MappedByteBuffer + fb.buffer = fb.channel!!.map(FileChannel.MapMode.READ_ONLY, 0, fileLength.toLong()) + fb.buffer!!.order(ByteOrder.LITTLE_ENDIAN) + return fb + } catch (e: Throwable) { + throw InvalidFileException("LEO FileBuffer.readFile() failed: $e") + } + + } + + //TODO: reimplement temp files and deal with cleanup -jm + fun readFileUsingTemp(fpath: String): FileBuffer { + return readFileUsingTemp(File(fpath)) + } + + fun readFileUsingTemp(fx0: File): FileBuffer { + try { + val fb = FileBuffer() + // create Temp file and populate + fb.tempfile = TempFileManager.createTempFile("LEOFile_", ".tmp") + fb.tempfile!!.delete() + + val input0 = FileInputStream(fx0) + val channel0 = input0.channel + val output0 = FileOutputStream(fb.tempfile!!) + val channel1 = output0.channel + channel0.transferTo(0, fx0.length(), channel1) + + channel0.close() + channel1.close() + input0.close() + output0.close() + + fb.input = FileInputStream(fb.tempfile!!) + fb.channel = fb.input!!.channel + val fileLength = fb.channel!!.size().toInt() + // MappedByteBuffer + fb.buffer = fb.channel!!.map(FileChannel.MapMode.READ_ONLY, 0, fileLength.toLong()) + fb.buffer!!.order(ByteOrder.LITTLE_ENDIAN) + return fb + } catch (e: Throwable) { + throw InvalidFileException("LEO FileBuffer.readFile() failed: $e") + } + + } + } +}// TODO Auto-generated constructor stub diff --git a/src/main/java/io/starter/formats/LEO/InvalidFileException.java b/src/main/java/io/starter/formats/LEO/InvalidFileException.kt similarity index 69% rename from src/main/java/io/starter/formats/LEO/InvalidFileException.java rename to src/main/java/io/starter/formats/LEO/InvalidFileException.kt index 016809c..9a3bdf3 100644 --- a/src/main/java/io/starter/formats/LEO/InvalidFileException.java +++ b/src/main/java/io/starter/formats/LEO/InvalidFileException.kt @@ -20,25 +20,28 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.LEO; +package io.starter.formats.LEO /** * This Exception is thrown whenever a LEOFile cannot be parsed or contains invalid data. */ -public class InvalidFileException extends RuntimeException { +class InvalidFileException(er: String) : RuntimeException() { + private val err = "Not a valid OLE File." - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2547604121903676738L; - private String err = "Not a valid OLE File."; + init { + this.err = er + } - public InvalidFileException(String er) { - this.err = er; + override fun toString(): String { + return "InvalidFileException: $err" } - public String toString() { - return "InvalidFileException: " + err; + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 2547604121903676738L } } diff --git a/src/main/java/io/starter/formats/LEO/LEOFile.java b/src/main/java/io/starter/formats/LEO/LEOFile.java deleted file mode 100644 index bf7fabb..0000000 --- a/src/main/java/io/starter/formats/LEO/LEOFile.java +++ /dev/null @@ -1,1381 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.LEO; - -import io.starter.formats.XLS.WorkBookException; -import io.starter.toolkit.*; - -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.*; - -/** - * LEOFile is an archive format compatible with other popular archive formats such as OLE. - *

                  - * It contains multiple files or "Storages" which can - * subsequently contain data in popular desktop application formats. - */ -public class LEOFile implements Serializable { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2760792940329331096L; - public final static int MAXDIFATLEN = 109; // maximum - // DIFATLEN - // = - // 109; - // if - // more - // sectors - // are - // needed - // goes - // into - // extraDIFAT - public final static int IDXBLOCKSIZE = 128; // number - // of - // indexes - // that - // can - // be - // stored - // in - // 1 - // block - public final static boolean DEBUG = false; - public int DEBUGLEVEL = 0; - public static int actualOutput = 0; - private List bigBlocks; - private boolean readok = false; - private LEOHeader header = null; - private StorageTable directories; - private FileBuffer fb = null; - String fileName = "New Spreadsheet"; - byte[] encryptionStorageOverage = null; - boolean encryptedXLSX = false; - - // TODO: fix temp file issues and implement shutdown cleanup - // -- currently no way to delete -jm - - public LEOHeader getHeader() { - return header; - } - - /** - * Close the underlying IO streams for this LEOFile. - *

                  - * Also deletes the temp file. - * - * @throws Exception - */ - public void close() throws IOException { - if (fb != null) - fb.close(); - // KSC: close out other object refs - fb = null; - // header.getBytes().clear(); - header = null;// new LEOHeader(); - if (directories != null) { - directories.close(); - directories = null; - } - if (bigBlocks != null) { - for (int i = 0; i < bigBlocks.size(); i++) { - BlockImpl b = (BlockImpl) bigBlocks.get(i); - b.close(); - } - bigBlocks.clear(); - } - bigBlocks = null; - // FAT= null; - } - - /** - * just closes the filebuffer withut clearing out buffers and storage tables - */ - public void closefb() throws IOException { - if (fb != null) - fb.close(); - fb = null; - } - - public void shutdown() { - try { - this.close(); - } catch (Exception e) { - if (DEBUGLEVEL > 0) - Logger.logWarn("could not close workbook cleanly." + e); - } - } - - /** - * a new LEO file containing LEO archive entries - * - * @param String a file path containing a valid LEOfile - */ - public LEOFile(String fname) { - if (fname.indexOf(".ser") > -1) { - this.initFromPrototype(fname); - return; - } - this.fileName = fname; - fb = LEOFile.readFile(fname); - this.initWrapper(fb.getBuffer()); - } - - /** - * Create a leo file from a prototype string/path - *

                  - * PROTOTYPE_LEO_ENCRYPTED - * - * @param fname - */ - private void initFromPrototype(String fname) { - try { - byte[] b = ResourceLoader.getBytesFromJar(fname); - if (b == null) { - throw new io.starter.formats.XLS.WorkBookException( - "Required Class files not on the CLASSPATH. Check location of .jar file and/or jarloc System property.", - WorkBookException.LICENSING_FAILED); - } - ByteBuffer bbf = ByteBuffer.wrap(b); - bbf.order(ByteOrder.LITTLE_ENDIAN); - this.initWrapper(bbf); - } catch (Exception e) { - throw new InvalidFileException( - "WorkBook could not be instantiated: " + e.toString()); - } - } - - /** - * Instantiate a Leo file from an encrypted document. We currently have a hack - * in place for encrypted documents due to their having truncated bigblocks that our - * interface does not correctly handle. - * - * @param a file containing a valid LEOfile (XLS BIFF8) - * @param whether to use a temp file - * @param if the file is encrypted xlsx format - */ - public LEOFile(File fpath, boolean usetempfile, boolean encryptedXLSX) { - this.encryptedXLSX = encryptedXLSX; - this.fileName = fpath.getAbsolutePath(); - fb = LEOFile.readFile(fpath, usetempfile); - this.initWrapper(fb.getBuffer()); - } - - /** - * Checks whether the given byte array starts with the LEO magic number. - */ - public static boolean checkIsLEO(byte[] data, int count) { - if (count < LEOHeader.majick.length) - return false; - for (int idx = 0; idx < LEOHeader.majick.length; idx++) - if (data[idx] != LEOHeader.majick[idx]) - return false; - return true; - } - - /** - * a new LEO file containing LEO archive entries - * - * @param a file containing a valid LEOfile (XLS BIFF8) - * @param whether to use a temp file - */ - public LEOFile(File fpath, boolean usetempfile) { - this.fileName = fpath.getAbsolutePath(); - fb = LEOFile.readFile(fpath, usetempfile); - this.initWrapper(fb.getBuffer()); - } - - /** - * a new LEO file containing LEO archive entries - * - * @param a file containing a valid LEOfile (XLS BIFF8) - */ - public LEOFile(File fpath, int DEBUGLEVEL) { - this.fileName = fpath.getAbsolutePath(); - this.DEBUGLEVEL = DEBUGLEVEL; - fb = LEOFile.readFile(fpath); - this.initWrapper(fb.getBuffer()); - } - - /** - * a new LEO file containing LEO archive entries - * - * @param byte[] a byte array containing a valid LEOfile - */ - public LEOFile(ByteBuffer bytebuff) { - this.initWrapper(bytebuff); - } - - /** - * This is just removing some duplicate code from our constructors - *

                  - * We should add some exception handling in here! - */ - public void initWrapper(ByteBuffer bytebuff) { - int[] FAT = this.init(bytebuff); - if (FAT != null) { - directories.initDirectories(bigBlocks, FAT); - // KSC: TESTING: XLS-97 - if (DEBUG || DEBUGLEVEL > 200) - directories.DEBUG(); - FAT = null; - readok = true; - } else - readok = false; - } - - public void clearAfterInit() { - this.bigBlocks.clear(); - } - - /** - * Reads in an encrypted LEO stream. May be able to remove this and use the standard - * constructor, we shall see. - * - * @param encryptedFile - */ - public void readEncryptedFile(File encryptedFile) { - fb = readFile(encryptedFile); - this.initWrapper(fb.getBuffer()); - } - - public String getFileName() { - return fileName; - } - - /** - * return whether the LEOFile contains a valid workbook - */ - public boolean hasWorkBook() { - Storage book; - try { - book = directories.getDirectoryByName("Workbook"); - } catch (StorageNotFoundException e) { - try { - book = directories.getDirectoryByName("Book"); - } catch (StorageNotFoundException e1) { - return false; - } - } - return true; - } - - /** - * return whether the LEOFile contains a valid doc - */ - public boolean hasDoc() { - if (readok) { - try { - Storage doc = this.directories - .getDirectoryByName("WordDocument"); - } catch (StorageNotFoundException e) { - return false; - } - return true; - } - return false; - } - - /** - * return whether the LEOFile contains the _SX_DB_CUR Pivot Table Cache storage (required for Pivot Table) - * - * @return - */ - public boolean hasPivotCache() { - if (readok) { - try { - Storage doc = this.directories.getDirectoryByName("_SX_DB_CUR"); - } catch (StorageNotFoundException e) { - return false; - } - return true; - } - return false; - } - - /** - * - */ - public LEOFile() { - super(); - } - - /** - * Create a LEOFile from an input stream. Unfortunately because of our byte backer - * we need to write to a temporary file in order to do this - * - * @param stream - */ - public LEOFile(InputStream stream) throws IOException { - File target = TempFileManager.createTempFile("ExtenXLS_temp", ".leo"); - JFileWriter.writeToFile(stream, target); - - this.fileName = target.getAbsolutePath(); - fb = LEOFile.readFile(target); - this.initWrapper(fb.getBuffer()); - target.deleteOnExit(); - target.delete(); - } - - public LEOFile(File fx) { - // TODO Auto-generated constructor stub - } - - /** - * calculate number of FAT blocks necessary to describe compound file - */ - final static int getNumFATSectors(int storageTotal) { - int nFAT = storageTotal * 4; - nFAT /= BIGBLOCK.SIZE; - float realnum = ((float) (storageTotal * 4) / BIGBLOCK.SIZE); - if ((realnum - nFAT) > 0 || (nFAT > MAXDIFATLEN)) { - nFAT++; - } - return nFAT; - } - - /** - * Creates all the necessary headers and associated storages/structures that, - * along with the actual workbook data, makes up the document - *

                  - * Basic Structure - * 1st sector or block= Header + 1st 109 sector chains (==FAT===array of sector numbers) - * Location of FAT==DIFAT - * Header denotes sector of "Root Directory" which in turn denotes sectors of all other directories - * (Workbook is but one of several directories) - * Each directory denotes the start block of any associated data into the FAT (sector chain index) - * (if directory is >0 an <4096 in size, it's associated data is stored in miniStorage - *

                  - *

                  - *

                  - *

                  - *

                  - *

                  - *

                  - *

                  - * Ok, so this is a little odd, it looks as if it collects all of the storages, and handles - * all of the preprocessing necessary to create FAT etc. - * returns a list of all the storages, presumably to be handled by ByteStreamer. - *

                  - *

                  - * A little more information: - * A list of storages is generated. These storages do not include - * a) the workbook storage(!) which is meant to be the first record written after receiving the byte array - * b) individual miniFAT storages, these are all combined into one container storage that is not represented in the header - * c) the LeoHeader, which is written to the output stream already - *

                  - *

                  - * It seems as if this method needs several things - * 1) better header comments on its functions - * 2) break out logic to private methods to keep overall functions understandable. - * 3) Handle file formats other than XLS (ie doc, encrypted xls, ppt, etc) - * 4) be clear about its usage to outputStream. Why only the 1 storage written, maybe none would be better? - * 5) probably throw an exception - *

                  - * 6) WHAT IS WITH THE STORAGES THAT ARE NOT STORAGES? -- these are non-directory storages used to write out the bytes contained within in the correct order - * for the document. These non-directory storages include the FAT and the minFAT blocks and miniStream - * - * @param outputstream to write out - * @param book to write - * @param book bytes size - * @return storages to write out in streamer - */ - public synchronized List writeBytes(OutputStream out, int workbook_byte_size) { - /***** storages to be written - rebuild from saved storages and recreated workbook storage*/ - AbstractList storages = new Vector(); - // header variables - int numFATSectors = 0; // num FAT indexes - int rootstart = 0; // block position where root sector is located - int numExtraDIFATSectors = 0; // num extra sectors/blocks - int extraDIFATStart = -2; // extra sector start (-2 for none) - int numMiniFATSectors = 0; // num short sectors/miniFAT indexes - int miniFATStart = -2; // start block position of short sector container - // blocks (-2 for none) - int sbidxpos = -2; // start position of short sector idx chain (-2 for - // none) - int sbsz = 0; // size of short sector container - int[] DIFAT; // holds the sector id chains for the 1st 109 sector index - // blocks (DIFAT) - int[] extraDIFAT; // holds the sector id chains for any sector index - // blocks (DIFAT) > 109 - int numblocks = 1; // header is 1st - - // workbook directory - Storage book = null; - boolean isEncrypted = false; - try { - book = directories.getDirectoryByName("Workbook"); - } catch (StorageNotFoundException e3) { - try { - book = directories.getDirectoryByName("EncryptedPackage"); - isEncrypted = !true; - } catch (StorageNotFoundException e1) { - // this is an error state, ideally we would be throwing a - // (non-runtime) exception? - throw new InvalidFileException( - "Input LEO file not valid for output"); - } - } - - // root directory - RootStorage rootStore = null; - try { - rootStore = directories.getRootStorage(); - } catch (StorageNotFoundException e2) { - // this is an error state, ideally we would be throwing a - // (non-runtime) exception? - throw new InvalidFileException( - "Input LEO file not valid for output"); - } - - // get existing directories + store all except workbook + - // root (which are rebuilt) - Enumeration e = directories.getAllDirectories().elements(); - while (e.hasMoreElements()) { - Storage thisStore = (Storage) e.nextElement(); - if (thisStore != book && thisStore != rootStore) { // && - // thisStore!=encryptionInfo? - storages.add(thisStore); - if (thisStore.getBlockType() == Block.SMALL) { // count number - // of miniStream - // blocks - thisStore.miniStreamStorage = true; // TODO: investigate why - // this setting gets - // lost! - numMiniFATSectors += thisStore.getBlockVect().size(); - } - } - } - - // if have miniStream sectors, rebuild miniFAT index - // + convert mini blocks to one or more big blocks (=miniFAT - // container) - if (numMiniFATSectors > 0) { - Storage[] sbs = buildMiniFAT(storages, numMiniFATSectors); // returns - // two - // non-directory - // storages: - // miniStream - // Storage - // + - // miniFAT - // index - storages.add(sbs[0]); - numMiniFATSectors = sbs[1].getBlockVect().size(); // trap number of - // miniStream - // block indexes - storages.add(sbs[1]); - } - - // we need to count up the total block array - // count number of blocks: - // add blocks necessary for root storage - numblocks += Math.ceil(directories.directoryVector.size() / 4); // each - // 4 - // directories==1 - // block - // (512 - // byte - // sector) - // count Storage Blocks (except root and workbook) - for (int t = 0; t < storages.size(); t++) { - Storage nstr = (Storage) storages.get(t); // saved stores containt - // thier original blocks - // + padding block - // signifying end of - // sector/block - if (!nstr.miniStreamStorage) - numblocks += nstr.getSizeInBlocks(); - } - - // take current workbook byte size and calculate # bigblocks - book.miniStreamStorage = false; // if had any initially, now it's big - // blocks - - int workbook_idx_block_size = LEOFile - .getSizeInBlocks(workbook_byte_size, BIGBLOCK.SIZE); - workbook_idx_block_size = Math - .max(workbook_idx_block_size, getMinBlocks() - 1);// ensure - // minimum # - // blocks - numblocks += workbook_idx_block_size + 1;// to account for end of block - // sector - if (isEncrypted) { // Encrypted workbooks are already set to correct - // size - numblocks--; - workbook_idx_block_size--; - } - // Given amount of blocks to write, calculate id chain - // necessary to describe - numFATSectors = LEOFile.getNumFATSectors(numblocks); // get # of secids - // necessary to - // describe - // blocks - - // now include the FAT itself - // total number of Sectors(blocks)= number of Sectors + FAT - // chain block(s) - numFATSectors = getNumFATSectors(numFATSectors + numblocks); - int[] FAT = new int[IDXBLOCKSIZE * numFATSectors]; - for (int i = 0; i < FAT.length; i++) - FAT[i] = -1; // create byte array of -1's 128 bytes - total number - // of sector ids - - // allocate FAT array index - // important positions that will be recorded in the header - // or FAT/miniFAT - DIFAT = new int[Math.min(numFATSectors, MAXDIFATLEN)]; // 1 up to the - // 1st 109 - // sector index - // blocks used - // to describe - // this file - extraDIFAT = new int[Math.max(numFATSectors - MAXDIFATLEN, 0)]; // if - // necessary, - // sector - // index - // blocks - // in - // excess - // of - // 109 - - /************************ now start to lay out block positions of all directories' associated data ************************/ - /************************ note: order of directories in storages is order that they will be written out - * this order is indexed in the FAT via the block indexes used for each sector - * upon return, storages contains all the directories, their associated data, the FAT ... - * in the calling method, the workbook records (==associated data) is written first because - * the workbook storage is first in the FAT. - Eventually, want to change this so all other storages and the FAT are written first, then - the calling method only needs to write the workbook records. - **************************************************************************************************************************/ - // first start block index with workbook blocks (they will - // be written out in ByteStreamer before all other blocks - // except the header) - int blockpos = 0; // initial sector position - book.setStartBlock(blockpos); - for (int t = 0; t < workbook_idx_block_size; t++) { - FAT[blockpos++] = blockpos; - } - FAT[blockpos++] = -2; // end of sector flag - if (!isEncrypted) { - // this is a questionable line. It is setting the storage - // length to that of the padded bigblock. - // causes errors in encrypted files - book.setActualFileSize((workbook_idx_block_size + 1) - * BIGBLOCK.SIZE); - if (DEBUG) - Logger.logInfo("Workbook actual bytes: " - + book.getActualFileSize()); - } - - // now rest of "static" stores (summary, document summary, - // comp obj ...) - for (int t = 0; t < storages.size(); t++) { - Storage nstr = (Storage) storages.get(t); - if (!nstr.miniStreamStorage) { // miniStream blocks are handled - // separately - if (nstr.getName().equals("miniStream")) { // miniStream (short - // sector container) - miniFATStart = blockpos; // this goes in rootstore - // startblock to denote start of - // short sector container - sbsz = nstr.getActualFileSize(); // actual size of short - // sector (miniFAT) - // container - stored in - // root storage - } else if (nstr.getName().equals("miniFAT")) { // miniFAT (short - // sector) index - sbidxpos = blockpos; - } - nstr.setStartBlock(-2); // default, not linked to any blocks - // 20100325 KSC: since original blocks are kept for - // non-workbook storage, - // and since size is not under our control, keep original - // size - otherwise can error - // nstr.setActualFileSize(0); // default, 0 bytes - // record index of block position for each block of the - // storage - Block[] blks = nstr.getBlocks(); - if (blks != null) { - nstr.setStartBlock(blockpos); // start of block chain - for (int i = 0; i < blks.length - 1; i++) { // -1 to trap - // last block - // with special - // end-of-block - // flag (-2) - FAT[blockpos++] = blockpos; // start of this store's - // blocks as indexed in - // block index - } - FAT[blockpos++] = -2; // end of sector/block flag - // nstr.setActualFileSize((blks.length)*512); // set file - // size -- see above why we can't do this at this time - } - } - } - - // after other storages, add root store: - // must rebuild after info from above as root storage is - // built from from all other storages - rootStore.setStartBlock(miniFATStart); // root store start points to - // miniFAT container, or -2 if - // none - rootStore.setActualFileSize(sbsz); // 0 or miniFAT container size - // handle rootstorage blocks = data of all other directory - // storages - rootStore.setBytes(directories.rebuildRootStore()); // now rebuild - // rootstore from - // all other - // storages - rootstart = blockpos; - for (int i = 0; i < rootStore.getBlockVect().size() - 1; i++) { - FAT[blockpos++] = blockpos; - } - FAT[blockpos++] = -2; // end of sector/block flag - // add rootstore to list of blocks writing out - storages.add(rootStore); - - // mark position of FAT itself within blocks (==DIFAT) - // the 1st MAXDIFATLEN DIFAT indexes goes into header - for (int i = 0; i < DIFAT.length; i++) { - DIFAT[i] = blockpos + 1; // each pos is decremented in method below, - // so increment here - FAT[blockpos++] = -3; // mark loc of FAT with special value -3 - } - - // if any FAT sectors > MAX, goes into extra blocks - if (numFATSectors > MAXDIFATLEN) { // handle extra blocks - need extra - // blocks to store the indexes - for (int n = MAXDIFATLEN; n < numFATSectors; n++) { - extraDIFAT[n - MAXDIFATLEN] = blockpos + 1; - FAT[blockpos++] = -3; - } - for (int i = 0; i < (int) Math.ceil((numFATSectors - MAXDIFATLEN) - / (IDXBLOCKSIZE * 1.0)); i++) - FAT[blockpos++] = -4; // flag for MSAT or XBB - } - - // now that all the blocks referenced by storages such as - // workbook and document summary, - // plus blocks referenced by the root storage, - // transform the blockindex to blocks & input a phantom or - // special store so can write out - // have been indexed, store the index blocks themselves - Storage idxstore = new Storage(); - idxstore.setName("IDXStorage"); - Block[] FATSectors = getIDXBlocks(FAT); // convert each int to 4-bytes - // in sector/block format --> - // FAT Sectors - idxstore.setBlocks(FATSectors); // input the FAT Sectors to a - // non-directory store for later writing - storages.add(idxstore); // output FAT Sectors after all other blocks - - if (numFATSectors > MAXDIFATLEN) { - // now build and add the extra sectors necessary to store - // the FAT - extraDIFATStart = blockpos - (int) Math - .ceil((numFATSectors - MAXDIFATLEN) / (IDXBLOCKSIZE * 1.0)); - Storage xbbstore = buildExtraDIFAT(extraDIFAT, extraDIFATStart); // create - // block(s) - // from - // the - // extraDIFAT - numExtraDIFATSectors = xbbstore.getBlockVect().size(); - storages.add(xbbstore); - } - - // now that blocks have been accounted for and indexed and - // storages and all their blocks - // (except for workbook blocks) are stored, we can build and - // output the header block - // build header block: - - header = LEOHeader.getPrototype(DIFAT); // input secIdChain to header - // loc 76 - // start sector of short sector/miniFAT index chain or -2 if - // n/a - header.setMiniFATStart(sbidxpos); - // the block # where the storages begin - header.setRootStorageStart(rootstart); - // number of block indexes needed to describe 1st 109 - // sectors/blocks (= DIFAT) - header.setNumFATSectors(numFATSectors); - // number of block indexes needed to describe short - // sectors/miniFAT sectors if any - header.setNumMiniFATSectors(numMiniFATSectors); - // number of block indexes necessary to describe extra - // sectors if any - header.setNumExtraDIFATSectors(numExtraDIFATSectors); - // start of extra sector block index or -2 if n/a - header.setExtraDIFATStart(extraDIFATStart); - - // setup the header info... - if (!header.init()) - throw new RuntimeException("LEO File Header Not Initialized"); // invalid - // WorkBook - // File - - // return the storages - return storages; - } - - /** - * build Extra Storage Sector index (extra DIFAT) - * - * @param difatidx - indexes of blocks > 13952 (= 109 blocks described by DIFAT * 128 indexes in each block) - * @return - */ - private Storage buildExtraDIFAT(int[] difatidx, int xbbpos) { - // handle Extra blocks beyond the 109*128 blocks described - // by the regular DIFAT (109 limit) - if (difatidx != null) { - ArrayList outblocks = new ArrayList(); - byte[] xbytes = new byte[difatidx.length * 4]; - // convert the int difatidx to bytes - for (int i = 0; i < difatidx.length; i++) { - byte[] idx = ByteTools.cLongToLEBytes(difatidx[i] - 1); - System.arraycopy(idx, 0, xbytes, i * 4, 4); - } - int counter = 0; - for (int i = 0; i < xbytes.length; ) { - ByteBuffer bl = BlockFactory.getPrototypeBlock(Block.BIG) - .getByteBuffer(); - int len = xbytes.length - i; - - // handle eob - if (len > BIGBLOCK.SIZE - 4) - len = BIGBLOCK.SIZE - 4; - bl.position(0); - bl.put(xbytes, i, len); - - // handle eob - counter++; - if (len == BIGBLOCK.SIZE - 4) - bl.putInt(xbbpos + counter); - - BIGBLOCK xbbBlock = new BIGBLOCK(); - xbbBlock.init(bl, 0, 0); - outblocks.add(xbbBlock); - - i += BIGBLOCK.SIZE - 4; - } - // create new storage so can be added to end - Storage xbbstore = new Storage(); - xbbstore.setName("XBBStore"); - Block[] xblx = new Block[outblocks.size()]; - outblocks.toArray(xblx); - xbbstore.setBlocks(xblx); - return xbbstore; - } - return null; - } - - /** - * Gather up all existing smallblocks from the existing storages - * and create contiguous bigblocks from it; since smallblocks are - * only 128 bytes long, 4 smallblocks fit into 1 bigblock - * while building the miniFAT Sectors,also build the miniFAT index - * which corresponds to it - * - * @param storages existing Storages (except workbook and root, which are handled separately) - * @param numsbs total number of miniFAT blocks - * @return Storage[] - two storage containers: 1 contains smallblocks, 2 contains smallblock index - */ - private Storage[] buildMiniFAT(List storages, int numsbs) { - // once more, now gathering the sbidx's (miniFAT index) - int[] sbidx = new int[(int) Math.ceil(numsbs / (IDXBLOCKSIZE * 1.0)) - * IDXBLOCKSIZE]; - byte[] smallblocks = new byte[0]; // sum up all short sectors, will be - // subsequently converted to big - // blocks - for (int i = 0; i < sbidx.length; i++) - sbidx[i] = -1; // init sbidx index - int z = 0; - for (int i = 0; i < storages.size(); i++) { - Storage thisStore = (Storage) storages.get(i); - if (thisStore.getBlockType() == Block.SMALL) { - // since these are static blocks we can copy original data - thisStore.setStartBlock(z); // set start position of miniFATs as - // may have changed - // keep length as that hasn't changed - for (int j = 0; j < thisStore.getBlockVect().size() - 1; j++) - sbidx[z++] = z; - sbidx[z++] = -2; // end of sector - smallblocks = ByteTools - .append(thisStore.getBytes(), smallblocks); - // 20100407 KSC: this screws up subsequent usages of book - // after writing ... see TestCreateNewName NPE - // thisStore.setBlocks(new Block[0]); - } - } - - // concatenate all miniFAT sectors to build the miniFAT or - // short sector container - // i.e. miniFAT blocks are concatenated and stored in big - // block(s): - Block[] smallBlocksToBig = BlockFactory - .getBlocksFromByteArray(smallblocks, Block.BIG); - Storage miniFATContainer = new Storage(); - miniFATContainer.setName("miniStream"); - miniFATContainer.setBlocks(smallBlocksToBig); - miniFATContainer.setActualFileSize(smallblocks.length); - smallblocks = null; // no need anymore - Storage miniFAT = new Storage(); - miniFAT.setName("miniFAT"); - Block[] sbIDX = getIDXBlocks(sbidx); // convert each int to 4-bytes in - // sector/block format --> BBDIX - miniFAT.setBlocks(sbIDX); - return new Storage[]{miniFATContainer, miniFAT}; - } - - /** - * create the idx from the actual locations - * of the Blocks in the outblock array - */ - final static void initSmallBlockIndex(int[] newidx, Block b) { - while (b.hasNext() && !(b == b.next())) { - int origps = b.getBlockIndex(); - if (origps < 0) { - Logger.logWarn("WARNING: LEOFile Block Not In MINIFAT vector: " - + b.getOriginalIdx()); - } else { - int newp = ((Block) b.next()).getBlockIndex(); - if (false) - Logger.logInfo("INFO: LEOFile Initializing block index: " - + origps + " val: " + newp + " idxlen: " - + newidx.length); - newidx[origps] = newp; - } - b = (Block) b.next(); - } - if (b.getBlockIndex() >= 0) - newidx[b.getBlockIndex()] = -2; // end of storage - } - - /** create the idx from the actual locations - of the Blocks in the outblock array - * - final static int initWBBlockIndex(int[] newidx, int sz) { - int start = 0; - for(int r=newidx.length-1;r>=0;r--) { - if(newidx[r]!=-1) { - start = r+1; - break; - } - } - for(int i=0;i0) - newidx[(start+sz-1)] = -2; // end of storage - else - newidx[(start+sz-1)] = -2; // end of storage - return start; - } - */ - - /** - * returns an empty new FAT which - * accounts for the size of its own blocks - */ - final static int[] getEmptyDIFAT(int totblocks, int numFATSectors) { - - // allocate space in idx for FAT recs, StorageTable and for - // final -2 - int[] bbdi = new int[numFATSectors + totblocks + 1]; - for (int x = 0; x < bbdi.length; x++) - bbdi[x] = (byte) -1; - return bbdi; - } - - /** - * Create the array of BB Idx blocks - */ - final static Block[] getIDXBlocks(int[] bbdidx) { - // step through and create index recs for all Blocks - byte[] b = new byte[bbdidx.length * 4]; - int bv = 0; - - // can we wrap t in an nio buffered and read? - - for (int t = 0; t < b.length/* 20100304 KSC: why? - 4 */; ) { - byte[] bs = ByteTools.cLongToLEBytes(bbdidx[bv++]); - // for(int z=0;z<4;z++) - b[t++] = bs[0]; - b[t++] = bs[1]; - b[t++] = bs[2]; - b[t++] = bs[3]; - } - return BlockFactory.getBlocksFromByteArray(b, Block.BIG); - } - - /** - * get a handle to a Storage within this LEO file - */ - public Storage getStorageByName(String s) throws StorageNotFoundException { - return directories.getDirectoryByName(s); - } - - /** - * get all Directories in this LEO file - */ - public Storage[] getAllDirectories() { - CompatibleVector v = directories.getAllDirectories(); - Storage[] s = new Storage[v.size()]; - s = (Storage[]) v.toArray(s); - return s; - } - - /** - * return the Directory Array for this LEOFile - *
                  holds all directories (storages and streams) in correct order - *
                  Directories in turn can reference streams of data - * - * @return - */ - public StorageTable getDirectoryArray() { - return directories; - } - - /** Simply setting the byte size on the book storage - * - private void checkIfSmallBlockOutput(int blen) throws StorageNotFoundException { - Storage book; - try { - book = storageTable.getStorageByName("Workbook"); - }catch(StorageNotFoundException e) { - book = storageTable.getStorageByName("EncryptedPackage"); - } - - if (blen >= StorageTable.BIGSTORAGE_SIZE) { - if (book.getBlockType() == Block.SMALL) { - Logger.logWarn( - "WARNING: Modifying SmallBlock Workbook: "+ this.getFileName() + - ". Modifying SmallBlock-type Workbooks " + - " can cause corrupt output files. See: " + - "http://extentech.com/uimodules/docs/docs_detail.jsp?meme_id=195"); - throw new WorkBookException("SmallBlock File Detected reading: "+this.getFileName(), WorkBookException.SMALLBLOCK_FILE); - // this.convertSBtoBB(book); - } - book.setActualFileSize(blen); - } else - book.setActualFileSize(StorageTable.BIGSTORAGE_SIZE); // set at the minimum - } - */ - - /** - * get the bytes of the DOC file. - */ - public BlockByteReader getDocBlockBytes() { - // get the bytes for the WordDocument substream only - Storage doc; - try { - doc = directories.getDirectoryByName("WordDocument"); - } catch (StorageNotFoundException e) { - throw new InvalidFileException( - "InvalidFileException: Not Word '97 or later version. Unsupported file format."); - } - return doc.getBlockReader(); - } - - /** - * get the bytes of the XLS file. - */ - public BlockByteReader getXLSBlockBytes() { - // get the bytes for the Workbook substream only - Storage book; - try { - book = directories.getDirectoryByName("Workbook"); - } catch (StorageNotFoundException e) { - try { - book = directories.getDirectoryByName("Book"); - } catch (StorageNotFoundException e1) { - Logger.logInfo("Not Excel '97 (BIFF8) or later version. Unsupported file format."); - throw new InvalidFileException( - "InvalidFileException: Not Excel '97 (BIFF8) or later version. Unsupported file format."); - } - } - return book.getBlockReader(); - } - - /** - * read LEO file information from header. - */ - public synchronized int[] init(ByteBuffer bbuf) { - int pos = 0; - CompatibleVector FATSectors = new CompatibleVector(); // one or more - // sectors which - // hold the FAT - // (File - // Allocation - // Table or - // indexes into - // the sectors) - - bigBlocks = new ArrayList(); - int len = bbuf.limit() / BIGBLOCK.SIZE; - // get ALL BIGBLOCKS (512 byte chunks of file) - if (DEBUG) - Logger.logInfo("\nINIT: Total Number of bigblocks: " + len); - for (int i = 0; i < len; i++) { - BIGBLOCK bbd = new BIGBLOCK(); - bbd.init(bbuf, i, pos); - pos += BIGBLOCK.SIZE; - this.bigBlocks.add(bbd); - } - - // Encrypted workbooks can have random overages. - // not ideal, but store this value in LEO and get from the - // storage if its named 'EncryptedPackage' - int encryptionStorageOverageLen = (bbuf.limit() % BIGBLOCK.SIZE); - if (encryptionStorageOverageLen > 0) { - int filepos = len * BIGBLOCK.SIZE; - if (this.encryptedXLSX) { - bbuf.position(filepos); - encryptionStorageOverage = new byte[encryptionStorageOverageLen]; - bbuf.get(encryptionStorageOverage, 0, encryptionStorageOverage.length); - } else { - BIGBLOCK bbd = new BIGBLOCK(); - bbd.init(bbuf, len, pos); // filepos); - pos += encryptionStorageOverageLen; - this.bigBlocks.add(bbd); - } - } - - /***** Read in the file header */ - // header holds directory start sector and - // FAT/miniFAT/extraFAT or Sector Chain index info - header = new LEOHeader(); // read the LEO file header rec - - // is a valid WorkBook File? - if (!header.init(bbuf)) - throw new InvalidFileException( - this.getFileName() + " is not a valid OLE File."); - - if (DEBUG) { - Logger.logInfo("Header: "); - Logger.logInfo("numbFATSectors: " + header.getNumFATSectors()); - Logger.logInfo("numMiniFATSectors: " - + header.getNumMiniFATSectors()); - Logger.logInfo("numbExtraDIFATSectors: " - + header.getNumExtraDIFATSectors()); - Logger.logInfo("rootstart: " + header.getRootStartPos()); - Logger.logInfo("miniFATStart: " + header.getMiniFATStart()); - } - BIGBLOCK headerblock = (BIGBLOCK) bigBlocks.get(0); - headerblock.setInitialized(true); - - /***** Read in the FAT sectors - the sectors or blocks used for the FAT (==Sector Chain Index or File Allocation Table) */ - FATSectors = getFATSectors(); - - /***** turn the FAT Sectors into the FAT or Sector Index Chain */ - byte[] blx = LEOFile.getBytes(FATSectors); - int[] FAT = null; - FAT = LEOFile.readFAT(blx); - blx = null; // done - - if (DEBUG) - Logger.logInfo("FAT:\n" + Arrays.toString(FAT)); - - // if (DEBUG) - // StorageTable.writeitout(FATSectors, "FAT.dat"); - - /***** Read the Directory blocks */ - directories = new StorageTable(); - directories.init(bbuf, header, bigBlocks, FAT); - return FAT; - } - - /** - * create a FileBuffer from a file, use system property to determine - * whether to use a temp file. - * - * @param file containing XLS bytes - * @param fpath - * @return - */ - public final static FileBuffer readFile(File fpath) { - boolean usetempfile = false; - String tmpfu = (String) System.getProperties() - .get("io.starter.formats.LEO.usetempfile"); - - if (tmpfu != null) - usetempfile = tmpfu.equalsIgnoreCase("true"); - - return readFile(fpath, usetempfile); - } - - /** - * create a FileBuffer from a file, use boolean parameter to determine - * whether to use a temp file. - * - * @param file containing XLS bytes - * @param whether to use a tempfile - * @return - */ - public final static FileBuffer readFile(File fpath, boolean usetempfile) { - if (usetempfile) - return FileBuffer.readFileUsingTemp(fpath); - return FileBuffer.readFile(fpath); - } - - /** - * read in a WorkBook ByteBuffer from a file path. - *

                  - * from here on out, we're reading pointers to the bytes on disk - *

                  - * access data directly on disk through the ByteBuffer. - * - *
                  By default, OpenXLS will lock open WorkBook files, to close the file after parsing and work with a - * temporary file instead, use the following setting: - *

                  - * System.getProperties().put("io.starter.formats.LEO.usetempfile", "true"); - *

                  - * IMPORTANT NOTE: You will need to clean up temp files occasionally in your user directory (temp filenames will begin - * with "ExtenXLS_".) - *

                  - **/ - public final static FileBuffer readFile(String fpath) { - return LEOFile.readFile(new File(fpath)); - } - - /** - * returns the table of int locations - * in the file for the BIGBLOCK linked list. - */ - public final static int[] readFAT(List vect) { - byte[] data = getBytes(vect); - return readFAT(data); - } - - /** - * returns the table of int locations - * in the file for the BIGBLOCK linked list. - */ - private final static int[] readFAT(byte[] data) { - int[] bbs = new int[data.length / 4]; - int pos = 0; - for (int i = 0; i < data.length; ) { - bbs[pos++] = ByteTools - .readInt(data[i++], data[i++], data[i++], data[i++]); - } - data = null; - return bbs; - } - - public final static byte[] getBytes(Block[] outblocks) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - getBytes(outblocks, out); - return out.toByteArray(); - } - - public final static OutputStream getByteStream(Block[] outblocks) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - getBytes(outblocks, out); - return out; - } - - /** - * Call this method for each storage, root storage first... - * - * @param outblocks - * @param out - */ - public final static void getBytes(Block[] outblocks, OutputStream out) { - List blxt = new ArrayList(); - for (int tx = 0; tx < outblocks.length; tx++) - blxt.add(outblocks[tx]); - getBytes(blxt, out); - } - - /** - * Call this method for each storage, root storage first... - * - * @param outblocks - * @param out - */ - public final static void getBytes(List outblocks, OutputStream out) { - Iterator it = outblocks.iterator(); - int i = 0; - while (it.hasNext()) { - Block boc = (Block) it.next(); - try { - if (boc != null) { - // if (LEOFile.DEBUG)Logger.logInfo("INFO: - // LEOFile.getBytes() getting bytes from Block: "+ i++ + ":" - // + boc.getBlockIndex()); - out.write(boc.getBytes()); - i++; // count how many blocks written - } - } catch (IOException a) { - Logger.logWarn("ERROR: gettting bytes from blocks failed: " - + a); - } - - } - } - - /** - * return the bytes for the blocks in a vector - */ - public final static byte[] getBytes(List bbvect) { - if (bbvect == null) - return new byte[]{}; - Block[] b = new Block[bbvect.size()]; - b = (Block[]) bbvect.toArray(b); - return LEOFile.getBytes(b); - } - - /** - * return the bytes for the blocks in a vector - */ - public final static OutputStream getByteStream(List bbvect) { - if (bbvect == null) - return new ByteArrayOutputStream(); - Block[] b = new Block[bbvect.size()]; - b = (Block[]) bbvect.toArray(b); - return LEOFile.getByteStream(b); - } - - /** - * get the number of Block records - * that this Storage needs to store its byte array - */ - public final static int getSizeInBlocks(int sz, int blocksize) { - int size = sz / blocksize; - float realnum = ((float) (sz) / blocksize); - if ((realnum - size) > 0) { - size++; - } - return size; - } - - /** - * Get the minimum blocks that this file can encompass - * - * @return - */ - public int getMinBlocks() { - return (header.getMinStreamSize() / BIGBLOCK.SIZE); - } - - /** - * @return Returns the encryptionStorageOverage. - */ - public byte[] getEncryptionStorageOverage() { - if (encryptionStorageOverage == null) - encryptionStorageOverage = new byte[0]; - return encryptionStorageOverage; - } - - /** - * reads in the FAT sectors from the compound file using info from the LEOFile header - * - * @return FATSectors == one or more sectors used to define the Sector Chain Index (==File Allocation Table == FAT) - */ - private CompatibleVector getFATSectors() { - CompatibleVector FATSectors = new CompatibleVector(); - int[] DIFAT = header.getDIFAT(); // the Index for the FAT(which is the - // File Allocation Table or Sector - // Chain Array), id's of 1st 109 - // sectors or blocks - if (DEBUG) - Logger.logInfo("FAT Blocks:\n" + Arrays.toString(DIFAT)); - // **** read in the FAT index ****// - // **** First get the inital blocks before the extra DIFAT - // sectors ****// - int FATidx = 0; - int blockidx = 0; - int bsz = bigBlocks.size() - 1; - for (; blockidx < Math.min(DIFAT.length, MAXDIFATLEN); blockidx++) { - FATidx = DIFAT[blockidx]; - if (FATidx <= bsz) { - BIGBLOCK bbd = (BIGBLOCK) bigBlocks.get(FATidx); - // each FAT entry is a 1-based index - // if (DEBUG)Logger.logInfo("INFO: LEOFile Got A FAT index - // at: "+ String.valueOf(FATidx)); - bbd.setIsDepotBlock(true); - FATSectors.add(bbd); - } else { - // Usually caused by CHECK ByteStreamer.writeOut() - // dlen/filler handling - Logger.logErr("LEOFile.init failed. FAT Index Attempting to fetch Block past end of blocks."); - throw new InvalidFileException( - "Input file truncated. LEOFile.init failed. FAT Index Attempting to fetch Block past end of blocks."); - } - } - - // ******* read in any Exrra DIFAT sectors *******// - int numExtraDIFATSectors = header.getNumExtraDIFATSectors(); - int extraDIFATStart = header.getExtraDIFATStart() + 1; - int chainIndex = extraDIFATStart; - for (int x = 0; x < numExtraDIFATSectors; x++) { - BIGBLOCK xind = (BIGBLOCK) bigBlocks.get(chainIndex); - ByteBuffer extraSectorBytes = ByteBuffer.wrap(xind.getBytes()); - extraSectorBytes.position(0); - extraSectorBytes.order(ByteOrder.LITTLE_ENDIAN); - int numExtraSectorElements = Math - .min((header.getNumFATSectors() - blockidx), IDXBLOCKSIZE); - for (int i = 0; i < numExtraSectorElements; i++) { - if (i < (IDXBLOCKSIZE - 1)) { // -1 because must always have - // room for end of sector mark - int bbloc = extraSectorBytes.getInt() + 1; - if (bbloc <= bsz) { - BIGBLOCK bbd = (BIGBLOCK) bigBlocks.get(bbloc); // fails - // at - // 41k - // recs - // jpm - // test - bbd.setIsDepotBlock(true); - bbd.setIsExtraSector(true); - FATSectors.add(bbd); - blockidx++; - } else { - Logger.logErr("LEOFile.init failed. Attempting to fetch Invalid Extra Sector Block."); - throw new InvalidFileException( - "LEOFile.init failed. Attempting to fetch Invalid Extra Sector Block."); - } - } else { - // the index to the next extra DIFAT sector is the last int - // in the chain - chainIndex = extraSectorBytes.getInt() + 1; - } - } - } - return FATSectors; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/LEOFile.kt b/src/main/java/io/starter/formats/LEO/LEOFile.kt new file mode 100644 index 0000000..ec2d882 --- /dev/null +++ b/src/main/java/io/starter/formats/LEO/LEOFile.kt @@ -0,0 +1,1406 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.LEO + +import io.starter.formats.XLS.WorkBookException +import io.starter.toolkit.* + +import java.io.* +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.* + +/** + * LEOFile is an archive format compatible with other popular archive formats such as OLE. + * + * + * It contains multiple files or "Storages" which can + * subsequently contain data in popular desktop application formats. + */ +class LEOFile : Serializable { + var DEBUGLEVEL = 0 + private var bigBlocks: MutableList<*>? = null + private var readok = false + // TODO: fix temp file issues and implement shutdown cleanup + // -- currently no way to delete -jm + + var header: LEOHeader? = null + private set + /** + * return the Directory Array for this LEOFile + *

                  holds all directories (storages and streams) in correct order + *

                  Directories in turn can reference streams of data + * + * @return + */ + var directoryArray: StorageTable? = null + private set + private var fb: FileBuffer? = null + var fileName = "New Spreadsheet" + internal set + internal var encryptionStorageOverage: ByteArray? = null + internal var encryptedXLSX = false + + /** + * get all Directories in this LEO file + */ + val allDirectories: Array + get() { + val v = directoryArray!!.allDirectories + var s = arrayOfNulls(v.size) + s = v.toTypedArray() as Array + return s + } + + /** Simply setting the byte size on the book storage + * + * private void checkIfSmallBlockOutput(int blen) throws StorageNotFoundException { + * Storage book; + * try { + * book = storageTable.getStorageByName("Workbook"); + * }catch(StorageNotFoundException e) { + * book = storageTable.getStorageByName("EncryptedPackage"); + * } + * + * if (blen >= StorageTable.BIGSTORAGE_SIZE) { + * if (book.getBlockType() == Block.SMALL) { + * Logger.logWarn( + * "WARNING: Modifying SmallBlock Workbook: "+ this.getFileName() + + * ". Modifying SmallBlock-type Workbooks " + + * " can cause corrupt output files. See: " + + * "http://extentech.com/uimodules/docs/docs_detail.jsp?meme_id=195"); + * throw new WorkBookException("SmallBlock File Detected reading: "+this.getFileName(), WorkBookException.SMALLBLOCK_FILE); + * // this.convertSBtoBB(book); + * } + * book.setActualFileSize(blen); + * } else + * book.setActualFileSize(StorageTable.BIGSTORAGE_SIZE); // set at the minimum + * } + */ + + /** + * get the bytes of the DOC file. + */ + // get the bytes for the WordDocument substream only + val docBlockBytes: BlockByteReader + get() { + val doc: Storage? + try { + doc = directoryArray!!.getDirectoryByName("WordDocument") + } catch (e: StorageNotFoundException) { + throw InvalidFileException( + "InvalidFileException: Not Word '97 or later version. Unsupported file format.") + } + + return doc!!.blockReader + } + + /** + * get the bytes of the XLS file. + */ + // get the bytes for the Workbook substream only + val xlsBlockBytes: BlockByteReader + get() { + var book: Storage? + try { + book = directoryArray!!.getDirectoryByName("Workbook") + } catch (e: StorageNotFoundException) { + try { + book = directoryArray!!.getDirectoryByName("Book") + } catch (e1: StorageNotFoundException) { + Logger.logInfo("Not Excel '97 (BIFF8) or later version. Unsupported file format.") + throw InvalidFileException( + "InvalidFileException: Not Excel '97 (BIFF8) or later version. Unsupported file format.") + } + + } + + return book!!.blockReader + } + + /** + * Get the minimum blocks that this file can encompass + * + * @return + */ + val minBlocks: Int + get() = header!!.minStreamSize / BIGBLOCK.SIZE + + /** + * reads in the FAT sectors from the compound file using info from the LEOFile header + * + * @return FATSectors == one or more sectors used to define the Sector Chain Index (==File Allocation Table == FAT) + */ + private// the Index for the FAT(which is the + // File Allocation Table or Sector + // Chain Array), id's of 1st 109 + // sectors or blocks + // **** read in the FAT index ****// + // **** First get the inital blocks before the extra DIFAT + // sectors ****// + // each FAT entry is a 1-based index + // if (DEBUG)Logger.logInfo("INFO: LEOFile Got A FAT index + // at: "+ String.valueOf(FATidx)); + // Usually caused by CHECK ByteStreamer.writeOut() + // dlen/filler handling + // ******* read in any Exrra DIFAT sectors *******// + // -1 because must always have + // room for end of sector mark + // fails + // at + // 41k + // recs + // jpm + // test + // the index to the next extra DIFAT sector is the last int + // in the chain + val fatSectors: CompatibleVector + get() { + val FATSectors = CompatibleVector() + val DIFAT = header!!.difat + if (DEBUG) + Logger.logInfo("FAT Blocks:\n" + Arrays.toString(DIFAT)) + var FATidx = 0 + var blockidx = 0 + val bsz = bigBlocks!!.size - 1 + while (blockidx < Math.min(DIFAT.size, MAXDIFATLEN)) { + FATidx = DIFAT[blockidx] + if (FATidx <= bsz) { + val bbd = bigBlocks!![FATidx] as BIGBLOCK + bbd.isDepotBlock = true + FATSectors.add(bbd) + } else { + Logger.logErr("LEOFile.init failed. FAT Index Attempting to fetch Block past end of blocks.") + throw InvalidFileException( + "Input file truncated. LEOFile.init failed. FAT Index Attempting to fetch Block past end of blocks.") + } + blockidx++ + } + val numExtraDIFATSectors = header!!.numExtraDIFATSectors + val extraDIFATStart = header!!.extraDIFATStart + 1 + var chainIndex = extraDIFATStart + for (x in 0 until numExtraDIFATSectors) { + val xind = bigBlocks!![chainIndex] as BIGBLOCK + val extraSectorBytes = ByteBuffer.wrap(xind.bytes) + extraSectorBytes.position(0) + extraSectorBytes.order(ByteOrder.LITTLE_ENDIAN) + val numExtraSectorElements = Math + .min(header!!.numFATSectors - blockidx, IDXBLOCKSIZE) + for (i in 0 until numExtraSectorElements) { + if (i < IDXBLOCKSIZE - 1) { + val bbloc = extraSectorBytes.int + 1 + if (bbloc <= bsz) { + val bbd = bigBlocks!![bbloc] as BIGBLOCK + bbd.isDepotBlock = true + bbd.setIsExtraSector(true) + FATSectors.add(bbd) + blockidx++ + } else { + Logger.logErr("LEOFile.init failed. Attempting to fetch Invalid Extra Sector Block.") + throw InvalidFileException( + "LEOFile.init failed. Attempting to fetch Invalid Extra Sector Block.") + } + } else { + chainIndex = extraSectorBytes.int + 1 + } + } + } + return FATSectors + } + + /** + * Close the underlying IO streams for this LEOFile. + * + * + * Also deletes the temp file. + * + * @throws Exception + */ + @Throws(IOException::class) + fun close() { + if (fb != null) + fb!!.close() + // KSC: close out other object refs + fb = null + // header.getBytes().clear(); + header = null// new LEOHeader(); + if (directoryArray != null) { + directoryArray!!.close() + directoryArray = null + } + if (bigBlocks != null) { + for (i in bigBlocks!!.indices) { + val b = bigBlocks!![i] as BlockImpl + b.close() + } + bigBlocks!!.clear() + } + bigBlocks = null + // FAT= null; + } + + /** + * just closes the filebuffer withut clearing out buffers and storage tables + */ + @Throws(IOException::class) + fun closefb() { + if (fb != null) + fb!!.close() + fb = null + } + + fun shutdown() { + try { + this.close() + } catch (e: Exception) { + if (DEBUGLEVEL > 0) + Logger.logWarn("could not close workbook cleanly.$e") + } + + } + + /** + * a new LEO file containing LEO archive entries + * + * @param String a file path containing a valid LEOfile + */ + constructor(fname: String) { + if (fname.indexOf(".ser") > -1) { + this.initFromPrototype(fname) + return + } + this.fileName = fname + fb = LEOFile.readFile(fname) + this.initWrapper(fb!!.buffer) + } + + /** + * Create a leo file from a prototype string/path + * + * + * PROTOTYPE_LEO_ENCRYPTED + * + * @param fname + */ + private fun initFromPrototype(fname: String) { + try { + val b = ResourceLoader.getBytesFromJar(fname) ?: throw io.starter.formats.XLS.WorkBookException( + "Required Class files not on the CLASSPATH. Check location of .jar file and/or jarloc System property.", + WorkBookException.LICENSING_FAILED) + val bbf = ByteBuffer.wrap(b) + bbf.order(ByteOrder.LITTLE_ENDIAN) + this.initWrapper(bbf) + } catch (e: Exception) { + throw InvalidFileException( + "WorkBook could not be instantiated: $e") + } + + } + + /** + * Instantiate a Leo file from an encrypted document. We currently have a hack + * in place for encrypted documents due to their having truncated bigblocks that our + * interface does not correctly handle. + * + * @param a file containing a valid LEOfile (XLS BIFF8) + * @param whether to use a temp file + * @param if the file is encrypted xlsx format + */ + constructor(fpath: File, usetempfile: Boolean, encryptedXLSX: Boolean) { + this.encryptedXLSX = encryptedXLSX + this.fileName = fpath.absolutePath + fb = LEOFile.readFile(fpath, usetempfile) + this.initWrapper(fb!!.buffer) + } + + /** + * a new LEO file containing LEO archive entries + * + * @param a file containing a valid LEOfile (XLS BIFF8) + * @param whether to use a temp file + */ + constructor(fpath: File, usetempfile: Boolean) { + this.fileName = fpath.absolutePath + fb = LEOFile.readFile(fpath, usetempfile) + this.initWrapper(fb!!.buffer) + } + + /** + * a new LEO file containing LEO archive entries + * + * @param a file containing a valid LEOfile (XLS BIFF8) + */ + constructor(fpath: File, DEBUGLEVEL: Int) { + this.fileName = fpath.absolutePath + this.DEBUGLEVEL = DEBUGLEVEL + fb = LEOFile.readFile(fpath) + this.initWrapper(fb!!.buffer) + } + + /** + * a new LEO file containing LEO archive entries + * + * @param byte[] a byte array containing a valid LEOfile + */ + constructor(bytebuff: ByteBuffer) { + this.initWrapper(bytebuff) + } + + /** + * This is just removing some duplicate code from our constructors + * + * + * We should add some exception handling in here! + */ + fun initWrapper(bytebuff: ByteBuffer?) { + var FAT = this.init(bytebuff!!) + if (FAT != null) { + directoryArray!!.initDirectories(bigBlocks, FAT) + // KSC: TESTING: XLS-97 + if (DEBUG || DEBUGLEVEL > 200) + directoryArray!!.DEBUG() + FAT = null + readok = true + } else + readok = false + } + + fun clearAfterInit() { + this.bigBlocks!!.clear() + } + + /** + * Reads in an encrypted LEO stream. May be able to remove this and use the standard + * constructor, we shall see. + * + * @param encryptedFile + */ + fun readEncryptedFile(encryptedFile: File) { + fb = readFile(encryptedFile) + this.initWrapper(fb!!.buffer) + } + + /** + * return whether the LEOFile contains a valid workbook + */ + fun hasWorkBook(): Boolean { + var book: Storage? + try { + book = directoryArray!!.getDirectoryByName("Workbook") + } catch (e: StorageNotFoundException) { + try { + book = directoryArray!!.getDirectoryByName("Book") + } catch (e1: StorageNotFoundException) { + return false + } + + } + + return true + } + + /** + * return whether the LEOFile contains a valid doc + */ + fun hasDoc(): Boolean { + if (readok) { + try { + val doc = this.directoryArray!! + .getDirectoryByName("WordDocument") + } catch (e: StorageNotFoundException) { + return false + } + + return true + } + return false + } + + /** + * return whether the LEOFile contains the _SX_DB_CUR Pivot Table Cache storage (required for Pivot Table) + * + * @return + */ + fun hasPivotCache(): Boolean { + if (readok) { + try { + val doc = this.directoryArray!!.getDirectoryByName("_SX_DB_CUR") + } catch (e: StorageNotFoundException) { + return false + } + + return true + } + return false + } + + /** + * + */ + constructor() : super() {} + + /** + * Create a LEOFile from an input stream. Unfortunately because of our byte backer + * we need to write to a temporary file in order to do this + * + * @param stream + */ + @Throws(IOException::class) + constructor(stream: InputStream) { + val target = TempFileManager.createTempFile("ExtenXLS_temp", ".leo") + JFileWriter.writeToFile(stream, target) + + this.fileName = target.absolutePath + fb = LEOFile.readFile(target) + this.initWrapper(fb!!.buffer) + target.deleteOnExit() + target.delete() + } + + constructor(fx: File) { + // TODO Auto-generated constructor stub + } + + /** + * Creates all the necessary headers and associated storages/structures that, + * along with the actual workbook data, makes up the document + * + * + * Basic Structure + * 1st sector or block= Header + 1st 109 sector chains (==FAT===array of sector numbers) + * Location of FAT==DIFAT + * Header denotes sector of "Root Directory" which in turn denotes sectors of all other directories + * (Workbook is but one of several directories) + * Each directory denotes the start block of any associated data into the FAT (sector chain index) + * (if directory is >0 an <4096 in size, it's associated data is stored in miniStorage + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Ok, so this is a little odd, it looks as if it collects all of the storages, and handles + * all of the preprocessing necessary to create FAT etc. + * returns a list of all the storages, presumably to be handled by ByteStreamer. + * + * + * + * + * A little more information: + * A list of storages is generated. These storages do not include + * a) the workbook storage(!) which is meant to be the first record written after receiving the byte array + * b) individual miniFAT storages, these are all combined into one container storage that is not represented in the header + * c) the LeoHeader, which is written to the output stream already + * + * + * + * + * It seems as if this method needs several things + * 1) better header comments on its functions + * 2) break out logic to private methods to keep overall functions understandable. + * 3) Handle file formats other than XLS (ie doc, encrypted xls, ppt, etc) + * 4) be clear about its usage to outputStream. Why only the 1 storage written, maybe none would be better? + * 5) probably throw an exception + * + * + * 6) WHAT IS WITH THE STORAGES THAT ARE NOT STORAGES? -- these are non-directory storages used to write out the bytes contained within in the correct order + * for the document. These non-directory storages include the FAT and the minFAT blocks and miniStream + * + * @param outputstream to write out + * @param book to write + * @param book bytes size + * @return storages to write out in streamer + */ + @Synchronized + fun writeBytes(out: OutputStream, workbook_byte_size: Int): List<*> { + /***** storages to be written - rebuild from saved storages and recreated workbook storage */ + val storages = Vector() + // header variables + var numFATSectors = 0 // num FAT indexes + var rootstart = 0 // block position where root sector is located + var numExtraDIFATSectors = 0 // num extra sectors/blocks + var extraDIFATStart = -2 // extra sector start (-2 for none) + var numMiniFATSectors = 0 // num short sectors/miniFAT indexes + var miniFATStart = -2 // start block position of short sector container + // blocks (-2 for none) + var sbidxpos = -2 // start position of short sector idx chain (-2 for + // none) + var sbsz = 0 // size of short sector container + val DIFAT: IntArray // holds the sector id chains for the 1st 109 sector index + // blocks (DIFAT) + val extraDIFAT: IntArray // holds the sector id chains for any sector index + // blocks (DIFAT) > 109 + var numblocks = 1 // header is 1st + + // workbook directory + var book: Storage? = null + var isEncrypted = false + try { + book = directoryArray!!.getDirectoryByName("Workbook") + } catch (e3: StorageNotFoundException) { + try { + book = directoryArray!!.getDirectoryByName("EncryptedPackage") + isEncrypted = !true + } catch (e1: StorageNotFoundException) { + // this is an error state, ideally we would be throwing a + // (non-runtime) exception? + throw InvalidFileException( + "Input LEO file not valid for output") + } + + } + + // root directory + var rootStore: RootStorage? = null + try { + rootStore = directoryArray!!.rootStorage + } catch (e2: StorageNotFoundException) { + // this is an error state, ideally we would be throwing a + // (non-runtime) exception? + throw InvalidFileException( + "Input LEO file not valid for output") + } + + // get existing directories + store all except workbook + + // root (which are rebuilt) + val e = directoryArray!!.allDirectories.elements() + while (e.hasMoreElements()) { + val thisStore = e.nextElement() as Storage + if (thisStore !== book && thisStore !== rootStore) { // && + // thisStore!=encryptionInfo? + storages.add(thisStore) + if (thisStore.blockType == Block.SMALL) { // count number + // of miniStream + // blocks + thisStore.miniStreamStorage = true // TODO: investigate why + // this setting gets + // lost! + numMiniFATSectors += thisStore.blockVect!!.size + } + } + } + + // if have miniStream sectors, rebuild miniFAT index + // + convert mini blocks to one or more big blocks (=miniFAT + // container) + if (numMiniFATSectors > 0) { + val sbs = buildMiniFAT(storages, numMiniFATSectors) // returns + // two + // non-directory + // storages: + // miniStream + // Storage + // + + // miniFAT + // index + storages.add(sbs[0]) + numMiniFATSectors = sbs[1].blockVect!!.size // trap number of + // miniStream + // block indexes + storages.add(sbs[1]) + } + + // we need to count up the total block array + // count number of blocks: + // add blocks necessary for root storage + numblocks += Math.ceil((directoryArray!!.allDirectories.size / 4).toDouble()).toInt() // each + // 4 + // directories==1 + // block + // (512 + // byte + // sector) + // count Storage Blocks (except root and workbook) + for (t in storages.indices) { + val nstr = storages.get(t) as Storage // saved stores containt + // thier original blocks + // + padding block + // signifying end of + // sector/block + if (!nstr.miniStreamStorage) + numblocks += nstr.sizeInBlocks + } + + // take current workbook byte size and calculate # bigblocks + book!!.miniStreamStorage = false // if had any initially, now it's big + // blocks + + var workbook_idx_block_size = LEOFile + .getSizeInBlocks(workbook_byte_size, BIGBLOCK.SIZE) + workbook_idx_block_size = Math + .max(workbook_idx_block_size, minBlocks - 1)// ensure + // minimum # + // blocks + numblocks += workbook_idx_block_size + 1// to account for end of block + // sector + if (isEncrypted) { // Encrypted workbooks are already set to correct + // size + numblocks-- + workbook_idx_block_size-- + } + // Given amount of blocks to write, calculate id chain + // necessary to describe + numFATSectors = LEOFile.getNumFATSectors(numblocks) // get # of secids + // necessary to + // describe + // blocks + + // now include the FAT itself + // total number of Sectors(blocks)= number of Sectors + FAT + // chain block(s) + numFATSectors = getNumFATSectors(numFATSectors + numblocks) + val FAT = IntArray(IDXBLOCKSIZE * numFATSectors) + for (i in FAT.indices) + FAT[i] = -1 // create byte array of -1's 128 bytes - total number + // of sector ids + + // allocate FAT array index + // important positions that will be recorded in the header + // or FAT/miniFAT + DIFAT = IntArray(Math.min(numFATSectors, MAXDIFATLEN)) // 1 up to the + // 1st 109 + // sector index + // blocks used + // to describe + // this file + extraDIFAT = IntArray(Math.max(numFATSectors - MAXDIFATLEN, 0)) // if + // necessary, + // sector + // index + // blocks + // in + // excess + // of + // 109 + + /************************ now start to lay out block positions of all directories' associated data */ + /************************ note: order of directories in storages is order that they will be written out + * this order is indexed in the FAT via the block indexes used for each sector + * upon return, storages contains all the directories, their associated data, the FAT ... + * in the calling method, the workbook records (==associated data) is written first because + * the workbook storage is first in the FAT. + * Eventually, want to change this so all other storages and the FAT are written first, then + * the calling method only needs to write the workbook records. + */ + // first start block index with workbook blocks (they will + // be written out in ByteStreamer before all other blocks + // except the header) + var blockpos = 0 // initial sector position + book.startBlock = blockpos + for (t in 0 until workbook_idx_block_size) { + FAT[blockpos++] = blockpos + } + FAT[blockpos++] = -2 // end of sector flag + if (!isEncrypted) { + // this is a questionable line. It is setting the storage + // length to that of the padded bigblock. + // causes errors in encrypted files + book.actualFileSize = (workbook_idx_block_size + 1) * BIGBLOCK.SIZE + if (DEBUG) + Logger.logInfo("Workbook actual bytes: " + book.actualFileSize) + } + + // now rest of "static" stores (summary, document summary, + // comp obj ...) + for (t in storages.indices) { + val nstr = storages.get(t) as Storage + if (!nstr.miniStreamStorage) { // miniStream blocks are handled + // separately + if (nstr.getName() == "miniStream") { // miniStream (short + // sector container) + miniFATStart = blockpos // this goes in rootstore + // startblock to denote start of + // short sector container + sbsz = nstr.actualFileSize // actual size of short + // sector (miniFAT) + // container - stored in + // root storage + } else if (nstr.getName() == "miniFAT") { // miniFAT (short + // sector) index + sbidxpos = blockpos + } + nstr.startBlock = -2 // default, not linked to any blocks + // 20100325 KSC: since original blocks are kept for + // non-workbook storage, + // and since size is not under our control, keep original + // size - otherwise can error + // nstr.setActualFileSize(0); // default, 0 bytes + // record index of block position for each block of the + // storage + val blks = nstr.blocks + if (blks != null) { + nstr.startBlock = blockpos // start of block chain + for (i in 0 until blks.size - 1) { // -1 to trap + // last block + // with special + // end-of-block + // flag (-2) + FAT[blockpos++] = blockpos // start of this store's + // blocks as indexed in + // block index + } + FAT[blockpos++] = -2 // end of sector/block flag + // nstr.setActualFileSize((blks.length)*512); // set file + // size -- see above why we can't do this at this time + } + } + } + + // after other storages, add root store: + // must rebuild after info from above as root storage is + // built from from all other storages + rootStore!!.startBlock = miniFATStart // root store start points to + // miniFAT container, or -2 if + // none + rootStore.actualFileSize = sbsz // 0 or miniFAT container size + // handle rootstorage blocks = data of all other directory + // storages + rootStore.bytes = directoryArray!!.rebuildRootStore() // now rebuild + // rootstore from + // all other + // storages + rootstart = blockpos + for (i in 0 until rootStore.blockVect!!.size - 1) { + FAT[blockpos++] = blockpos + } + FAT[blockpos++] = -2 // end of sector/block flag + // add rootstore to list of blocks writing out + storages.add(rootStore) + + // mark position of FAT itself within blocks (==DIFAT) + // the 1st MAXDIFATLEN DIFAT indexes goes into header + for (i in DIFAT.indices) { + DIFAT[i] = blockpos + 1 // each pos is decremented in method below, + // so increment here + FAT[blockpos++] = -3 // mark loc of FAT with special value -3 + } + + // if any FAT sectors > MAX, goes into extra blocks + if (numFATSectors > MAXDIFATLEN) { // handle extra blocks - need extra + // blocks to store the indexes + for (n in MAXDIFATLEN until numFATSectors) { + extraDIFAT[n - MAXDIFATLEN] = blockpos + 1 + FAT[blockpos++] = -3 + } + for (i in 0 until Math.ceil((numFATSectors - MAXDIFATLEN) / (IDXBLOCKSIZE * 1.0)).toInt()) + FAT[blockpos++] = -4 // flag for MSAT or XBB + } + + // now that all the blocks referenced by storages such as + // workbook and document summary, + // plus blocks referenced by the root storage, + // transform the blockindex to blocks & input a phantom or + // special store so can write out + // have been indexed, store the index blocks themselves + val idxstore = Storage() + idxstore.setName("IDXStorage") + val FATSectors = getIDXBlocks(FAT) // convert each int to 4-bytes + // in sector/block format --> + // FAT Sectors + idxstore.blocks = FATSectors // input the FAT Sectors to a + // non-directory store for later writing + storages.add(idxstore) // output FAT Sectors after all other blocks + + if (numFATSectors > MAXDIFATLEN) { + // now build and add the extra sectors necessary to store + // the FAT + extraDIFATStart = blockpos - Math + .ceil((numFATSectors - MAXDIFATLEN) / (IDXBLOCKSIZE * 1.0)).toInt() + val xbbstore = buildExtraDIFAT(extraDIFAT, extraDIFATStart) // create + // block(s) + // from + // the + // extraDIFAT + numExtraDIFATSectors = xbbstore!!.blockVect!!.size + storages.add(xbbstore) + } + + // now that blocks have been accounted for and indexed and + // storages and all their blocks + // (except for workbook blocks) are stored, we can build and + // output the header block + // build header block: + + header = LEOHeader.getPrototype(DIFAT) // input secIdChain to header + // loc 76 + // start sector of short sector/miniFAT index chain or -2 if + // n/a + header!!.miniFATStart = sbidxpos + // the block # where the storages begin + header!!.setRootStorageStart(rootstart) + // number of block indexes needed to describe 1st 109 + // sectors/blocks (= DIFAT) + header!!.numFATSectors = numFATSectors + // number of block indexes needed to describe short + // sectors/miniFAT sectors if any + header!!.numMiniFATSectors = numMiniFATSectors + // number of block indexes necessary to describe extra + // sectors if any + header!!.numExtraDIFATSectors = numExtraDIFATSectors + // start of extra sector block index or -2 if n/a + header!!.extraDIFATStart = extraDIFATStart + + // setup the header info... + if (!header!!.init()) + throw RuntimeException("LEO File Header Not Initialized") // invalid + // WorkBook + // File + + // return the storages + return storages + } + + /** + * build Extra Storage Sector index (extra DIFAT) + * + * @param difatidx - indexes of blocks > 13952 (= 109 blocks described by DIFAT * 128 indexes in each block) + * @return + */ + private fun buildExtraDIFAT(difatidx: IntArray?, xbbpos: Int): Storage? { + // handle Extra blocks beyond the 109*128 blocks described + // by the regular DIFAT (109 limit) + if (difatidx != null) { + val outblocks = ArrayList() + val xbytes = ByteArray(difatidx.size * 4) + // convert the int difatidx to bytes + for (i in difatidx.indices) { + val idx = ByteTools.cLongToLEBytes(difatidx[i] - 1) + System.arraycopy(idx, 0, xbytes, i * 4, 4) + } + var counter = 0 + var i = 0 + while (i < xbytes.size) { + val bl = BlockFactory.getPrototypeBlock(Block.BIG) + .byteBuffer + var len = xbytes.size - i + + // handle eob + if (len > BIGBLOCK.SIZE - 4) + len = BIGBLOCK.SIZE - 4 + bl.position(0) + bl.put(xbytes, i, len) + + // handle eob + counter++ + if (len == BIGBLOCK.SIZE - 4) + bl.putInt(xbbpos + counter) + + val xbbBlock = BIGBLOCK() + xbbBlock.init(bl, 0, 0) + outblocks.add(xbbBlock) + + i += BIGBLOCK.SIZE - 4 + } + // create new storage so can be added to end + val xbbstore = Storage() + xbbstore.setName("XBBStore") + val xblx = arrayOfNulls(outblocks.size) + outblocks.toTypedArray() + xbbstore.blocks = xblx + return xbbstore + } + return null + } + + /** + * Gather up all existing smallblocks from the existing storages + * and create contiguous bigblocks from it; since smallblocks are + * only 128 bytes long, 4 smallblocks fit into 1 bigblock + * while building the miniFAT Sectors,also build the miniFAT index + * which corresponds to it + * + * @param storages existing Storages (except workbook and root, which are handled separately) + * @param numsbs total number of miniFAT blocks + * @return Storage[] - two storage containers: 1 contains smallblocks, 2 contains smallblock index + */ + private fun buildMiniFAT(storages: List<*>, numsbs: Int): Array { + // once more, now gathering the sbidx's (miniFAT index) + val sbidx = IntArray(Math.ceil(numsbs / (IDXBLOCKSIZE * 1.0)).toInt() * IDXBLOCKSIZE) + var smallblocks: ByteArray? = ByteArray(0) // sum up all short sectors, will be + // subsequently converted to big + // blocks + for (i in sbidx.indices) + sbidx[i] = -1 // init sbidx index + var z = 0 + for (i in storages.indices) { + val thisStore = storages[i] as Storage + if (thisStore.blockType == Block.SMALL) { + // since these are static blocks we can copy original data + thisStore.startBlock = z // set start position of miniFATs as + // may have changed + // keep length as that hasn't changed + for (j in 0 until thisStore.blockVect!!.size - 1) + sbidx[z++] = z + sbidx[z++] = -2 // end of sector + smallblocks = ByteTools + .append(thisStore.bytes, smallblocks) + // 20100407 KSC: this screws up subsequent usages of book + // after writing ... see TestCreateNewName NPE + // thisStore.setBlocks(new Block[0]); + } + } + + // concatenate all miniFAT sectors to build the miniFAT or + // short sector container + // i.e. miniFAT blocks are concatenated and stored in big + // block(s): + val smallBlocksToBig = BlockFactory + .getBlocksFromByteArray(smallblocks, Block.BIG) + val miniFATContainer = Storage() + miniFATContainer.setName("miniStream") + miniFATContainer.blocks = smallBlocksToBig + miniFATContainer.actualFileSize = smallblocks!!.size + smallblocks = null // no need anymore + val miniFAT = Storage() + miniFAT.setName("miniFAT") + val sbIDX = getIDXBlocks(sbidx) // convert each int to 4-bytes in + // sector/block format --> BBDIX + miniFAT.blocks = sbIDX + return arrayOf(miniFATContainer, miniFAT) + } + + /** + * get a handle to a Storage within this LEO file + */ + @Throws(StorageNotFoundException::class) + fun getStorageByName(s: String): Storage? { + return directoryArray!!.getDirectoryByName(s) + } + + /** + * read LEO file information from header. + */ + @Synchronized + fun init(bbuf: ByteBuffer): IntArray? { + var pos = 0 + var FATSectors = CompatibleVector() // one or more + // sectors which + // hold the FAT + // (File + // Allocation + // Table or + // indexes into + // the sectors) + + bigBlocks = ArrayList() + val len = bbuf.limit() / BIGBLOCK.SIZE + // get ALL BIGBLOCKS (512 byte chunks of file) + if (DEBUG) + Logger.logInfo("\nINIT: Total Number of bigblocks: $len") + for (i in 0 until len) { + val bbd = BIGBLOCK() + bbd.init(bbuf, i, pos) + pos += BIGBLOCK.SIZE + this.bigBlocks!!.add(bbd) + } + + // Encrypted workbooks can have random overages. + // not ideal, but store this value in LEO and get from the + // storage if its named 'EncryptedPackage' + val encryptionStorageOverageLen = bbuf.limit() % BIGBLOCK.SIZE + if (encryptionStorageOverageLen > 0) { + val filepos = len * BIGBLOCK.SIZE + if (this.encryptedXLSX) { + bbuf.position(filepos) + encryptionStorageOverage = ByteArray(encryptionStorageOverageLen) + bbuf.get(encryptionStorageOverage!!, 0, encryptionStorageOverage!!.size) + } else { + val bbd = BIGBLOCK() + bbd.init(bbuf, len, pos) // filepos); + pos += encryptionStorageOverageLen + this.bigBlocks!!.add(bbd) + } + } + + /***** Read in the file header */ + // header holds directory start sector and + // FAT/miniFAT/extraFAT or Sector Chain index info + header = LEOHeader() // read the LEO file header rec + + // is a valid WorkBook File? + if (!header!!.init(bbuf)) + throw InvalidFileException( + this.fileName + " is not a valid OLE File.") + + if (DEBUG) { + Logger.logInfo("Header: ") + Logger.logInfo("numbFATSectors: " + header!!.numFATSectors) + Logger.logInfo("numMiniFATSectors: " + header!!.numMiniFATSectors) + Logger.logInfo("numbExtraDIFATSectors: " + header!!.numExtraDIFATSectors) + Logger.logInfo("rootstart: " + header!!.rootStartPos) + Logger.logInfo("miniFATStart: " + header!!.miniFATStart) + } + val headerblock = bigBlocks!![0] as BIGBLOCK + headerblock.initialized = true + + /***** Read in the FAT sectors - the sectors or blocks used for the FAT (==Sector Chain Index or File Allocation Table) */ + FATSectors = fatSectors + + /***** turn the FAT Sectors into the FAT or Sector Index Chain */ + var blx: ByteArray? = LEOFile.getBytes(FATSectors) + var FAT: IntArray? = null + FAT = LEOFile.readFAT(blx!!) + blx = null // done + + if (DEBUG) + Logger.logInfo("FAT:\n" + Arrays.toString(FAT)) + + // if (DEBUG) + // StorageTable.writeitout(FATSectors, "FAT.dat"); + + /***** Read the Directory blocks */ + directoryArray = StorageTable() + directoryArray!!.init(bbuf, header, bigBlocks, FAT) + return FAT + } + + /** + * @return Returns the encryptionStorageOverage. + */ + fun getEncryptionStorageOverage(): ByteArray { + if (encryptionStorageOverage == null) + encryptionStorageOverage = ByteArray(0) + return encryptionStorageOverage + } + + companion object { + + /** + * serialVersionUID + */ + private const val serialVersionUID = 2760792940329331096L + val MAXDIFATLEN = 109 // maximum + // DIFATLEN + // = + // 109; + // if + // more + // sectors + // are + // needed + // goes + // into + // extraDIFAT + val IDXBLOCKSIZE = 128 // number + // of + // indexes + // that + // can + // be + // stored + // in + // 1 + // block + val DEBUG = false + var actualOutput = 0 + + /** + * Checks whether the given byte array starts with the LEO magic number. + */ + fun checkIsLEO(data: ByteArray, count: Int): Boolean { + if (count < LEOHeader.majick.size) + return false + for (idx in LEOHeader.majick.indices) + if (data[idx] != LEOHeader.majick[idx]) + return false + return true + } + + /** + * calculate number of FAT blocks necessary to describe compound file + */ + internal fun getNumFATSectors(storageTotal: Int): Int { + var nFAT = storageTotal * 4 + nFAT /= BIGBLOCK.SIZE + val realnum = (storageTotal * 4).toFloat() / BIGBLOCK.SIZE + if (realnum - nFAT > 0 || nFAT > MAXDIFATLEN) { + nFAT++ + } + return nFAT + } + + /** + * create the idx from the actual locations + * of the Blocks in the outblock array + */ + internal fun initSmallBlockIndex(newidx: IntArray, b: Block) { + var b = b + while (b.hasNext() && !(b === b.next())) { + val origps = b.blockIndex + if (origps < 0) { + Logger.logWarn("WARNING: LEOFile Block Not In MINIFAT vector: " + b.originalIdx) + } else { + val newp = (b.next() as Block).blockIndex + if (false) + Logger.logInfo("INFO: LEOFile Initializing block index: " + + origps + " val: " + newp + " idxlen: " + + newidx.size) + newidx[origps] = newp + } + b = b.next() as Block + } + if (b.blockIndex >= 0) + newidx[b.blockIndex] = -2 // end of storage + } + + /** create the idx from the actual locations + * of the Blocks in the outblock array + * + * final static int initWBBlockIndex(int[] newidx, int sz) { + * int start = 0; + * for(int r=newidx.length-1;r>=0;r--) { + * if(newidx[r]!=-1) { + * start = r+1; + * break; + * } + * } + * for(int i=0;i;i++) { + * newidx[(start+i)] = start+(i+1); + * } + * if(start>0) + * newidx[(start+sz-1)] = -2; // end of storage + * else + * newidx[(start+sz-1)] = -2; // end of storage + * return start; + * } + */ + + /** + * returns an empty new FAT which + * accounts for the size of its own blocks + */ + internal fun getEmptyDIFAT(totblocks: Int, numFATSectors: Int): IntArray { + + // allocate space in idx for FAT recs, StorageTable and for + // final -2 + val bbdi = IntArray(numFATSectors + totblocks + 1) + for (x in bbdi.indices) + bbdi[x] = (-1).toByte().toInt() + return bbdi + } + + /** + * Create the array of BB Idx blocks + */ + internal fun getIDXBlocks(bbdidx: IntArray): Array? { + // step through and create index recs for all Blocks + val b = ByteArray(bbdidx.size * 4) + var bv = 0 + + // can we wrap t in an nio buffered and read? + + var t = 0 + while (t < b.size/* 20100304 KSC: why? - 4 */) { + val bs = ByteTools.cLongToLEBytes(bbdidx[bv++]) + // for(int z=0;z<4;z++) + b[t++] = bs[0] + b[t++] = bs[1] + b[t++] = bs[2] + b[t++] = bs[3] + } + return BlockFactory.getBlocksFromByteArray(b, Block.BIG) + } + + /** + * create a FileBuffer from a file, use system property to determine + * whether to use a temp file. + * + * @param file containing XLS bytes + * @param fpath + * @return + */ + fun readFile(fpath: File): FileBuffer { + var usetempfile = false + val tmpfu = System.getProperties()["io.starter.formats.LEO.usetempfile"] as String + + if (tmpfu != null) + usetempfile = tmpfu.equals("true", ignoreCase = true) + + return readFile(fpath, usetempfile) + } + + /** + * create a FileBuffer from a file, use boolean parameter to determine + * whether to use a temp file. + * + * @param file containing XLS bytes + * @param whether to use a tempfile + * @return + */ + fun readFile(fpath: File, usetempfile: Boolean): FileBuffer { + return if (usetempfile) FileBuffer.readFileUsingTemp(fpath) else FileBuffer.readFile(fpath) + } + + /** + * read in a WorkBook ByteBuffer from a file path. + * + * + * from here on out, we're reading pointers to the bytes on disk + * + * + * access data directly on disk through the ByteBuffer. + * + *

                  By default, OpenXLS will lock open WorkBook files, to close the file after parsing and work with a + * temporary file instead, use the following setting: + *



                  + * System.getProperties().put("io.starter.formats.LEO.usetempfile", "true"); + *



                  + * IMPORTANT NOTE: You will need to clean up temp files occasionally in your user directory (temp filenames will begin + * with "ExtenXLS_".) + *



                  + */ + fun readFile(fpath: String): FileBuffer { + return LEOFile.readFile(File(fpath)) + } + + /** + * returns the table of int locations + * in the file for the BIGBLOCK linked list. + */ + fun readFAT(vect: List<*>): IntArray { + val data = getBytes(vect) + return readFAT(data) + } + + /** + * returns the table of int locations + * in the file for the BIGBLOCK linked list. + */ + private fun readFAT(data: ByteArray): IntArray { + var data = data + val bbs = IntArray(data.size / 4) + var pos = 0 + var i = 0 + while (i < data.size) { + bbs[pos++] = ByteTools + .readInt(data[i++], data[i++], data[i++], data[i++]) + } + data = null + return bbs + } + + fun getBytes(outblocks: Array): ByteArray { + val out = ByteArrayOutputStream() + getBytes(outblocks, out) + return out.toByteArray() + } + + fun getByteStream(outblocks: Array): OutputStream { + val out = ByteArrayOutputStream() + getBytes(outblocks, out) + return out + } + + /** + * Call this method for each storage, root storage first... + * + * @param outblocks + * @param out + */ + fun getBytes(outblocks: Array, out: OutputStream) { + val blxt = ArrayList() + for (tx in outblocks.indices) + blxt.add(outblocks[tx]) + getBytes(blxt, out) + } + + /** + * Call this method for each storage, root storage first... + * + * @param outblocks + * @param out + */ + fun getBytes(outblocks: List<*>, out: OutputStream) { + val it = outblocks.iterator() + var i = 0 + while (it.hasNext()) { + val boc = it.next() as Block + try { + if (boc != null) { + // if (LEOFile.DEBUG)Logger.logInfo("INFO: + // LEOFile.getBytes() getting bytes from Block: "+ i++ + ":" + // + boc.getBlockIndex()); + out.write(boc.bytes) + i++ // count how many blocks written + } + } catch (a: IOException) { + Logger.logWarn("ERROR: gettting bytes from blocks failed: $a") + } + + } + } + + /** + * return the bytes for the blocks in a vector + */ + fun getBytes(bbvect: List<*>?): ByteArray { + if (bbvect == null) + return byteArrayOf() + var b = arrayOfNulls(bbvect.size) + b = bbvect.toTypedArray() as Array + return LEOFile.getBytes(b) + } + + /** + * return the bytes for the blocks in a vector + */ + fun getByteStream(bbvect: List<*>?): OutputStream { + if (bbvect == null) + return ByteArrayOutputStream() + var b = arrayOfNulls(bbvect.size) + b = bbvect.toTypedArray() as Array + return LEOFile.getByteStream(b) + } + + /** + * get the number of Block records + * that this Storage needs to store its byte array + */ + fun getSizeInBlocks(sz: Int, blocksize: Int): Int { + var size = sz / blocksize + val realnum = sz.toFloat() / blocksize + if (realnum - size > 0) { + size++ + } + return size + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/LEOHeader.java b/src/main/java/io/starter/formats/LEO/LEOHeader.java deleted file mode 100644 index c659218..0000000 --- a/src/main/java/io/starter/formats/LEO/LEOHeader.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.LEO; - -// NIO based API requires JDK1.3+ - -import io.starter.toolkit.Logger; - -import java.io.Serializable; -import java.nio.ByteBuffer; - - -/** - * Header record containing information on the Storage records in the LEOFile. - *

                  - * Header (block 1) -- 512 (0x200) bytes - * Field Description Offset Length Default value or const - * FILETYPE Magic number identifying this as a LEO filesystem. 0x0000 Long 0xE11AB1A1E011CFD0 - * UK1 Unknown constant 0x0008 Integer 0 - * UK2 Unknown Constant 0x000C Integer 0 - * UK3 Unknown Constant 0x0014 Integer 0 - * UK4 Unknown Constant (revision?) 0x0018 Short 0x003B - * UK5 Unknown Constant (version?) 0x001A Short 0x0003 - * UK6 Unknown Constant 0x001C Short -2 - * LOG_2_BIG_BLOCK_SIZE Log, base 2, of the big block size 0x001E Short 9 (2 ^ 9 = 512 bytes) - * LOG_2_SMALL_BLOCK_SIZE Log, base 2, of the miniFAT size 0x0020 Integer 6 (2 ^ 6 = 64 bytes) - * UK7 Unknown Constant 0x0024 Integer 0 - * UK8 Unknown Constant 0x0028 Integer 0 - * FAT_COUNT Number of elements in the FAT array 0x002C Integer required - * PROPERTIES_START Block index of the first block of the property table 0x0030 Integer required - * UK9 Unknown Constant 0x0034 Integer 0 - * UK10 Unknown Constant 0x0038 Integer 0x00001000 - * MINIFAT_START Block index of first big block containing the mini FATallocation table (MINIFAT) 0x003C Integer -2 - * UK11 Unknown Constant 0x0040 Integer 1 - * extraDIFAT Block index of the first block in the Extended Block Allocation Table 0x0044 Integer -2 - * extraDIFATCount Number of elements in the Extended File Allocation Table (to be added to the FAT) 0x0048 Integer 0 - * FAT_ARRAY Array of block indicies constituting the File Allocation Table (FAT) 0x004C, 0x0050, 0x0054 ... 0x01FC Integer[ ] -1 for unused elements, at least first element must be filled. - * N/A Header block data not otherwise described in this table N/A - */ -public class LEOHeader implements Serializable { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -422489164065975273L; - public final static int HEADER_SIZE = 0x200; - private transient ByteBuffer data; - private static int DIFATPOSITION = 76; // DIFAT start position WITHIN HEADER (DIFAT=where to find the FAT i.e the index to the FAT) - private int numFATSectors = -1; // FAT= array of Sector numbers - private int rootstart = -1; // Root Directory sector start - private int miniFATStart = -2; // miniFAT stores sectors for storages < 4096 - private int extraDIFATStart = -1; // if more than 109 FAT sectors are needed this stores start position of remaining DIFAT - private int numExtraDIFATSectors = -1; // usually 109 FAT sectors is enough; if > -1 more DIFAT is stored in other sectors - private int numMiniFATSectors = 0; - private int minStreamSize = 4096; // = 8 blocks minimum for a stream - public final static byte[] majick = - { - (byte) 0xd0, - (byte) 0xcf, - (byte) 0x11, - (byte) 0xe0, - (byte) 0xa1, - (byte) 0xb1, - (byte) 0x1a, - (byte) 0xe1, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00, - (byte) 0x00}; - - /** - * initialize from existing data - */ - protected boolean init() { - return this.init(this.data); - } - - /** - * structure of typical header: - * // 24, 25= 62, 0 (rev #) = 0x3E - * // 26, 27= 3, 0 (vers #) = 0x03 - * // 28, 29= -2, -1 (little endian) - * // 30, 31= 9, 0 (size of sector (9= 512, 7= 128)) - * // 32, 33= 6, 0 (size of short sector (6=64 bytes)) - * // 34->43= 0 (unused) - * // 44->47= Total # sectors in SAT *** - * // 48->51= Sector id of 1st sector - * // 52->55= 0 (unused) - * // 56->59= Minimum size of a standard stream (usually 4096) - * // 60->63= SecID of first sector in short-SAT - * // 64->67= Total # sectors in Short-SAT *** - * // 68->71= SecID of 1st sector of MSAT or -2 if no additional sectors are needed - * // 72->75= Total # sectors used for the MSAT - * // 76->436= MSAT (1st 109 sectors) - */ - private static void displayHeader(ByteBuffer data) { - for (int i = 0; i < data.capacity(); i++) { - io.starter.toolkit.Logger.log("[" + i + "] " + data.get(i)); - } - } - - - /** - * initialize the leo doc header - */ - protected boolean init(ByteBuffer dta) { - this.data = dta; - int pos; - - if (dta.limit() < 1) - throw new InvalidFileException("Empty input file."); - - try { - // sanity check -- is it a valid WorkBook? - for (int t = 0; t < 16; t++) { - if (dta.get(t) != majick[t]) { - throw new InvalidFileException("File is not valid OLE format: Bad Majick."); - } - } - } catch (Exception e) { - throw new InvalidFileException("File is not valid OLE format:" + dta.limit()); - } - - // get the rootstart 0x30 - pos = 0x30; - data.position(pos); // secId of 1st storage pos (==rootstart)[48] - rootstart = data.getInt(); - rootstart++; - rootstart *= BIGBLOCK.SIZE; - - // has DIFAT Extra sectors? -- extended data blocks for large XLS files - pos = 0x44; // [68] - data.position(pos); - extraDIFATStart = data.getInt(); // sector id of 1st sector of extra DIFAT sectors or -2 of no extra sectors used - numExtraDIFATSectors = data.getInt(); // total # sectors used for the DIFAT -- if # sectors or blocks > 109, more sectors are used - - // get minimum size of standard stream - pos = 56; - data.position(pos); - minStreamSize = data.getInt(); - - // starting at 0x2c (44) THE numFATSectors - pos = 0x2C; - data.position(pos); - numFATSectors = data.getInt(); // total # blocks used in the FAT chain (each block can hold 128 sector ids) - - - // 0x3c (60) THE miniFAT start - pos = 60; - data.position(pos); - miniFATStart = data.getInt(); // secId of 1st sector of the miniFAT sector chain or -2 if non-existant - - // pos 0x40= # miniFAT sectors [64] - pos = 0x40; - data.position(pos); - numMiniFATSectors = data.getInt(); - return true; - } - - /** - * get the number of the Extended DIFAT sectors - */ - public int getNumExtraDIFATSectors() { - return numExtraDIFATSectors; - } - - /** - * get the position of the extra DIFAT sector start - */ - public int getExtraDIFATStart() { - return this.extraDIFATStart; - } - - /** - * get the position of the Root Storage/1st Directory - */ - public int getRootStartPos() { - return rootstart; - } - - /** - * get the position of the miniFAT Start Block - */ - public int getMiniFATStart() { - return miniFATStart; - } - - /** - * get the FAT Sectors (sectors which hold the FAT or the chain that references all sectors in the file) - */ - public int[] getDIFAT() { - int numblks = Math.min(this.numFATSectors, 109); // more than 109 goes into DIFAT - int[] FAT = new int[numblks]; - int pos = DIFATPOSITION; // START OF DIFAT (indexes the FAT) - data.position(pos); // start of the 1st 109 secIds (4 bytes each==436 bytes) - for (int i = 0; i < numblks; i++) { - FAT[i] = data.getInt(); - FAT[i]++; - } - return FAT; - } - - /** - * get the Header bytes - */ - public ByteBuffer getBytes() { - return this.data; - } - - /** - * get a BIGBLOCK containing this Header's data - * ie: create a new header byte block. - */ - static final LEOHeader getPrototype(int[] FAT) { - - LEOHeader retval = new LEOHeader(); - - // get an empty BIGBLOCK - BIGBLOCK retblock = - (BIGBLOCK) BlockFactory.getPrototypeBlock(Block.BIG); - retval.setData(retblock.data); - retval.initMajickBytes(); - retval.initConstants(); - - // recreate the FAT and indexes to ... - retval.setFAT(FAT); - retval.setMiniFATStart(-2); // we don't rebuild miniFATs at this time ... - retval.setRootStorageStart(1); - retval.setExtraDIFATStart(-2); - retval.setNumExtraDIFATSectors(0); - return retval; - } - - void initConstants() { - int pos = 0x08; - data.position(pos); - data.putInt(0); - - pos = 0xc; - data.position(pos); - data.putInt(0); - - pos = 0x10; - data.position(pos); - data.putInt(0); - - pos = 0x14; - data.position(pos); - data.putInt(0); - - pos = 0x18; - data.position(pos); - data.putShort((short) 0x3e); - - pos = 0x1a; - data.position(pos); - data.putShort((short) 0x3); - - pos = 0x1c; - data.position(pos); - data.putShort((short) -2); - - pos = 0x1e; - data.position(pos); - data.putShort((short) 9); - - pos = 0x20; - data.position(pos); - data.putInt(6); - - pos = 0x24; - data.position(pos); - data.putInt(0); - - pos = 0x28; - data.position(pos); - data.putInt(0); - - pos = 0x34; - data.position(pos); - data.putInt(0); - - pos = 0x38; - data.position(pos); - data.putInt(0x00001000); - - } - - void initMajickBytes() { - // create the majick number - this.data.position(0); - data.put(majick); - } - - void setData(ByteBuffer dta) { - this.data = dta; - } - - /** - * Set the FAT or sector chain index - * - * @param FAT - */ - void setFAT(int[] FAT) { - int pos = DIFATPOSITION; - data.position(pos); - for (int i = 0; i < FAT.length; i++) { - if ((FAT.length * 4) >= (this.data.limit() - 4)) { - // TODO: Create extra DIFAT sectors FAT.length too big... - Logger.logWarn("WARNING: LEOHeader.setFAT() creating Extra FAT Sectors Not Implemented. Output file too large."); - } else { - data.putInt((FAT[i] - 1)); // todo: why decrement here? necessitates an increment in LEOFile.writeBytes - } - } - // fill rest with -1's === Empty Sector denotation - for (int i = data.position(); i < 512; i += 4) - data.putInt(-1); - - setNumFATSectors(FAT.length); - } - - void setRootStorageStart(int i) { - int pos = 0x30; - data.position(pos); - data.putInt(i); - } - - void setMiniFATStart(int i) { - int pos = 0x3c; - this.miniFATStart = i; - data.position(pos); - data.putInt(i); - miniFATStart = i; - } - - void setExtraDIFATStart(int i) { - int pos = 0x44; - data.position(pos); - data.putInt(i); - extraDIFATStart = i; - } - - void setNumExtraDIFATSectors(int i) { - int pos = 0x48; - data.position(pos); - data.putInt(i); - numExtraDIFATSectors = i; - } - - void setNumFATSectors(int i) { - int pos = 0x2c; - data.position(pos); - data.putInt(i); - numFATSectors = i; - } - - int getNumFATSectors() { - return numFATSectors; - } - - /** - * set the number of miniFAT sectors (each sector=64 bytes) - */ - void setNumMiniFATSectors(int i) { - int pos = 0x40; - data.position(pos); - data.putInt(i); - numMiniFATSectors = i; - } - - /** - * return the number of miniFAT sectors - * - * @return - */ - int getNumMiniFATSectors() { - return numMiniFATSectors; - } - - /** - * return the minimum stream size - * usually 4096 or 8 blocks - * - * @return - */ - int getMinStreamSize() { - return minStreamSize; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/LEOHeader.kt b/src/main/java/io/starter/formats/LEO/LEOHeader.kt new file mode 100644 index 0000000..a6e1129 --- /dev/null +++ b/src/main/java/io/starter/formats/LEO/LEOHeader.kt @@ -0,0 +1,398 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.LEO + +// NIO based API requires JDK1.3+ + +import io.starter.toolkit.Logger + +import java.io.Serializable +import java.nio.ByteBuffer + + +/** + * Header record containing information on the Storage records in the LEOFile. + * + * + * Header (block 1) -- 512 (0x200) bytes + * Field Description Offset Length Default value or const + * FILETYPE Magic number identifying this as a LEO filesystem. 0x0000 Long 0xE11AB1A1E011CFD0 + * UK1 Unknown constant 0x0008 Integer 0 + * UK2 Unknown Constant 0x000C Integer 0 + * UK3 Unknown Constant 0x0014 Integer 0 + * UK4 Unknown Constant (revision?) 0x0018 Short 0x003B + * UK5 Unknown Constant (version?) 0x001A Short 0x0003 + * UK6 Unknown Constant 0x001C Short -2 + * LOG_2_BIG_BLOCK_SIZE Log, base 2, of the big block size 0x001E Short 9 (2 ^ 9 = 512 bytes) + * LOG_2_SMALL_BLOCK_SIZE Log, base 2, of the miniFAT size 0x0020 Integer 6 (2 ^ 6 = 64 bytes) + * UK7 Unknown Constant 0x0024 Integer 0 + * UK8 Unknown Constant 0x0028 Integer 0 + * FAT_COUNT Number of elements in the FAT array 0x002C Integer required + * PROPERTIES_START Block index of the first block of the property table 0x0030 Integer required + * UK9 Unknown Constant 0x0034 Integer 0 + * UK10 Unknown Constant 0x0038 Integer 0x00001000 + * MINIFAT_START Block index of first big block containing the mini FATallocation table (MINIFAT) 0x003C Integer -2 + * UK11 Unknown Constant 0x0040 Integer 1 + * extraDIFAT Block index of the first block in the Extended Block Allocation Table 0x0044 Integer -2 + * extraDIFATCount Number of elements in the Extended File Allocation Table (to be added to the FAT) 0x0048 Integer 0 + * FAT_ARRAY Array of block indicies constituting the File Allocation Table (FAT) 0x004C, 0x0050, 0x0054 ... 0x01FC Integer[ ] -1 for unused elements, at least first element must be filled. + * N/A Header block data not otherwise described in this table N/A + */ +class LEOHeader : Serializable { + /** + * get the Header bytes + */ + @Transient + var bytes: ByteBuffer? = null + private set + private var numFATSectors = -1 // FAT= array of Sector numbers + /** + * get the position of the Root Storage/1st Directory + */ + var rootStartPos = -1 + private set // Root Directory sector start + private var miniFATStart = -2 // miniFAT stores sectors for storages < 4096 + private var extraDIFATStart = -1 // if more than 109 FAT sectors are needed this stores start position of remaining DIFAT + private var numExtraDIFATSectors = -1 // usually 109 FAT sectors is enough; if > -1 more DIFAT is stored in other sectors + private var numMiniFATSectors = 0 + /** + * return the minimum stream size + * usually 4096 or 8 blocks + * + * @return + */ + internal var minStreamSize = 4096 + private set // = 8 blocks minimum for a stream + + /** + * get the FAT Sectors (sectors which hold the FAT or the chain that references all sectors in the file) + */ + // more than 109 goes into DIFAT + // START OF DIFAT (indexes the FAT) + // start of the 1st 109 secIds (4 bytes each==436 bytes) + val difat: IntArray + get() { + val numblks = Math.min(this.numFATSectors, 109) + val FAT = IntArray(numblks) + val pos = DIFATPOSITION + bytes!!.position(pos) + for (i in 0 until numblks) { + FAT[i] = bytes!!.int + FAT[i]++ + } + return FAT + } + + /** + * initialize from existing data + */ + fun init(): Boolean { + return this.init(this.bytes!!) + } + + + /** + * initialize the leo doc header + */ + fun init(dta: ByteBuffer): Boolean { + this.bytes = dta + var pos: Int + + if (dta.limit() < 1) + throw InvalidFileException("Empty input file.") + + try { + // sanity check -- is it a valid WorkBook? + for (t in 0..15) { + if (dta.get(t) != majick[t]) { + throw InvalidFileException("File is not valid OLE format: Bad Majick.") + } + } + } catch (e: Exception) { + throw InvalidFileException("File is not valid OLE format:" + dta.limit()) + } + + // get the rootstart 0x30 + pos = 0x30 + bytes!!.position(pos) // secId of 1st storage pos (==rootstart)[48] + rootStartPos = bytes!!.int + rootStartPos++ + rootStartPos *= BIGBLOCK.SIZE + + // has DIFAT Extra sectors? -- extended data blocks for large XLS files + pos = 0x44 // [68] + bytes!!.position(pos) + extraDIFATStart = bytes!!.int // sector id of 1st sector of extra DIFAT sectors or -2 of no extra sectors used + numExtraDIFATSectors = bytes!!.int // total # sectors used for the DIFAT -- if # sectors or blocks > 109, more sectors are used + + // get minimum size of standard stream + pos = 56 + bytes!!.position(pos) + minStreamSize = bytes!!.int + + // starting at 0x2c (44) THE numFATSectors + pos = 0x2C + bytes!!.position(pos) + numFATSectors = bytes!!.int // total # blocks used in the FAT chain (each block can hold 128 sector ids) + + + // 0x3c (60) THE miniFAT start + pos = 60 + bytes!!.position(pos) + miniFATStart = bytes!!.int // secId of 1st sector of the miniFAT sector chain or -2 if non-existant + + // pos 0x40= # miniFAT sectors [64] + pos = 0x40 + bytes!!.position(pos) + numMiniFATSectors = bytes!!.int + return true + } + + /** + * get the number of the Extended DIFAT sectors + */ + fun getNumExtraDIFATSectors(): Int { + return numExtraDIFATSectors + } + + /** + * get the position of the extra DIFAT sector start + */ + fun getExtraDIFATStart(): Int { + return this.extraDIFATStart + } + + /** + * get the position of the miniFAT Start Block + */ + fun getMiniFATStart(): Int { + return miniFATStart + } + + internal fun initConstants() { + var pos = 0x08 + bytes!!.position(pos) + bytes!!.putInt(0) + + pos = 0xc + bytes!!.position(pos) + bytes!!.putInt(0) + + pos = 0x10 + bytes!!.position(pos) + bytes!!.putInt(0) + + pos = 0x14 + bytes!!.position(pos) + bytes!!.putInt(0) + + pos = 0x18 + bytes!!.position(pos) + bytes!!.putShort(0x3e.toShort()) + + pos = 0x1a + bytes!!.position(pos) + bytes!!.putShort(0x3.toShort()) + + pos = 0x1c + bytes!!.position(pos) + bytes!!.putShort((-2).toShort()) + + pos = 0x1e + bytes!!.position(pos) + bytes!!.putShort(9.toShort()) + + pos = 0x20 + bytes!!.position(pos) + bytes!!.putInt(6) + + pos = 0x24 + bytes!!.position(pos) + bytes!!.putInt(0) + + pos = 0x28 + bytes!!.position(pos) + bytes!!.putInt(0) + + pos = 0x34 + bytes!!.position(pos) + bytes!!.putInt(0) + + pos = 0x38 + bytes!!.position(pos) + bytes!!.putInt(0x00001000) + + } + + internal fun initMajickBytes() { + // create the majick number + this.bytes!!.position(0) + bytes!!.put(majick) + } + + internal fun setData(dta: ByteBuffer?) { + this.bytes = dta + } + + /** + * Set the FAT or sector chain index + * + * @param FAT + */ + internal fun setFAT(FAT: IntArray) { + val pos = DIFATPOSITION + bytes!!.position(pos) + for (i in FAT.indices) { + if (FAT.size * 4 >= this.bytes!!.limit() - 4) { + // TODO: Create extra DIFAT sectors FAT.length too big... + Logger.logWarn("WARNING: LEOHeader.setFAT() creating Extra FAT Sectors Not Implemented. Output file too large.") + } else { + bytes!!.putInt(FAT[i] - 1) // todo: why decrement here? necessitates an increment in LEOFile.writeBytes + } + } + // fill rest with -1's === Empty Sector denotation + var i = bytes!!.position() + while (i < 512) { + bytes!!.putInt(-1) + i += 4 + } + + setNumFATSectors(FAT.size) + } + + internal fun setRootStorageStart(i: Int) { + val pos = 0x30 + bytes!!.position(pos) + bytes!!.putInt(i) + } + + internal fun setMiniFATStart(i: Int) { + val pos = 0x3c + this.miniFATStart = i + bytes!!.position(pos) + bytes!!.putInt(i) + miniFATStart = i + } + + internal fun setExtraDIFATStart(i: Int) { + val pos = 0x44 + bytes!!.position(pos) + bytes!!.putInt(i) + extraDIFATStart = i + } + + internal fun setNumExtraDIFATSectors(i: Int) { + val pos = 0x48 + bytes!!.position(pos) + bytes!!.putInt(i) + numExtraDIFATSectors = i + } + + internal fun setNumFATSectors(i: Int) { + val pos = 0x2c + bytes!!.position(pos) + bytes!!.putInt(i) + numFATSectors = i + } + + internal fun getNumFATSectors(): Int { + return numFATSectors + } + + /** + * set the number of miniFAT sectors (each sector=64 bytes) + */ + internal fun setNumMiniFATSectors(i: Int) { + val pos = 0x40 + bytes!!.position(pos) + bytes!!.putInt(i) + numMiniFATSectors = i + } + + /** + * return the number of miniFAT sectors + * + * @return + */ + internal fun getNumMiniFATSectors(): Int { + return numMiniFATSectors + } + + companion object { + + /** + * serialVersionUID + */ + private const val serialVersionUID = -422489164065975273L + val HEADER_SIZE = 0x200 + private val DIFATPOSITION = 76 // DIFAT start position WITHIN HEADER (DIFAT=where to find the FAT i.e the index to the FAT) + val majick = byteArrayOf(0xd0.toByte(), 0xcf.toByte(), 0x11.toByte(), 0xe0.toByte(), 0xa1.toByte(), 0xb1.toByte(), 0x1a.toByte(), 0xe1.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte(), 0x00.toByte()) + + /** + * structure of typical header: + * // 24, 25= 62, 0 (rev #) = 0x3E + * // 26, 27= 3, 0 (vers #) = 0x03 + * // 28, 29= -2, -1 (little endian) + * // 30, 31= 9, 0 (size of sector (9= 512, 7= 128)) + * // 32, 33= 6, 0 (size of short sector (6=64 bytes)) + * // 34->43= 0 (unused) + * // 44->47= Total # sectors in SAT *** + * // 48->51= Sector id of 1st sector + * // 52->55= 0 (unused) + * // 56->59= Minimum size of a standard stream (usually 4096) + * // 60->63= SecID of first sector in short-SAT + * // 64->67= Total # sectors in Short-SAT *** + * // 68->71= SecID of 1st sector of MSAT or -2 if no additional sectors are needed + * // 72->75= Total # sectors used for the MSAT + * // 76->436= MSAT (1st 109 sectors) + */ + private fun displayHeader(data: ByteBuffer) { + for (i in 0 until data.capacity()) { + io.starter.toolkit.Logger.log("[" + i + "] " + data.get(i)) + } + } + + /** + * get a BIGBLOCK containing this Header's data + * ie: create a new header byte block. + */ + internal fun getPrototype(FAT: IntArray): LEOHeader { + + val retval = LEOHeader() + + // get an empty BIGBLOCK + val retblock = BlockFactory.getPrototypeBlock(Block.BIG) as BIGBLOCK + retval.setData(retblock.byteBuffer) + retval.initMajickBytes() + retval.initConstants() + + // recreate the FAT and indexes to ... + retval.setFAT(FAT) + retval.setMiniFATStart(-2) // we don't rebuild miniFATs at this time ... + retval.setRootStorageStart(1) + retval.setExtraDIFATStart(-2) + retval.setNumExtraDIFATSectors(0) + return retval + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/LEOIndexingException.java b/src/main/java/io/starter/formats/LEO/LEOIndexingException.kt similarity index 69% rename from src/main/java/io/starter/formats/LEO/LEOIndexingException.java rename to src/main/java/io/starter/formats/LEO/LEOIndexingException.kt index 41ebc6b..0747981 100644 --- a/src/main/java/io/starter/formats/LEO/LEOIndexingException.java +++ b/src/main/java/io/starter/formats/LEO/LEOIndexingException.kt @@ -20,26 +20,29 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.LEO; +package io.starter.formats.LEO /** * This exception is thrown when an invalid indexing scheme occurs, currently only happens in * miniFAT indexing */ -public class LEOIndexingException extends RuntimeException { +class LEOIndexingException(er: String) : RuntimeException() { + private val err = "Error in FAT indexing" - /** - * serialVersionUID - */ - private static final long serialVersionUID = -795980000366851485L; - private String err = "Error in FAT indexing"; + init { + this.err = er + } - public LEOIndexingException(String er) { - this.err = er; + override fun toString(): String { + return "InvalidFileException: $err" } - public String toString() { - return "InvalidFileException: " + err; + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -795980000366851485L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/RootStorage.java b/src/main/java/io/starter/formats/LEO/RootStorage.kt similarity index 59% rename from src/main/java/io/starter/formats/LEO/RootStorage.java rename to src/main/java/io/starter/formats/LEO/RootStorage.kt index e519dda..5438b93 100644 --- a/src/main/java/io/starter/formats/LEO/RootStorage.java +++ b/src/main/java/io/starter/formats/LEO/RootStorage.kt @@ -20,53 +20,53 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.LEO; +package io.starter.formats.LEO -import io.starter.toolkit.Logger; +import io.starter.toolkit.Logger -import java.nio.ByteBuffer; -import java.util.ArrayList; +import java.nio.ByteBuffer +import java.util.ArrayList /** * The Root Storage == The Root Directory is the 1st Directory in the Directory Stream * This Directory is the root for all objects; it also stores the size and starting sector of the miniStream - *

                  + * + * * The Root Directory entry behaves as both a stream and a storage object. It's name MUST = "Root Entry" */ -public class RootStorage extends io.starter.formats.LEO.Storage { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6568586717509723981L; - int DEBUGLEVEL = 0; +class RootStorage internal constructor(b: ByteBuffer) : io.starter.formats.LEO.Storage(b) { + internal var DEBUGLEVEL = 0 - RootStorage(ByteBuffer b) { - super(b); - } - /** * set the underlying byte array for this * Storage - *

                  + * + * * This appears to be a special case for RootStorage, as it seems to convert from a * smallblock based storage to a bigblock based storage. works.... I guess? */ - public void setBytes(byte[] b) { - Block[] bs = BlockFactory.getBlocksFromByteArray(b, Block.BIG); - if (super.myblocks != null) { - myblocks.clear(); - lastblock = null; - } else { - myblocks = new ArrayList(bs.length); + override var bytes: ByteArray + get() { + if (DEBUGLEVEL > 5) Logger.logInfo("Getting Root Storage Bytes....") + return super.bytes + } + set(b) { + val bs = BlockFactory.getBlocksFromByteArray(b, Block.BIG) + if (super.myblocks != null) { + myblocks!!.clear() + lastblock = null + } else { + myblocks = ArrayList(bs!!.size) + } + for (d in bs!!.indices) + this.addBlock(bs[d]) } - for (int d = 0; d < bs.length; d++) - this.addBlock(bs[d]); - } - - public byte[] getBytes() { - if (DEBUGLEVEL > 5) Logger.logInfo("Getting Root Storage Bytes...."); - return super.getBytes(); + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -6568586717509723981L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/SMALLBLOCK.java b/src/main/java/io/starter/formats/LEO/SMALLBLOCK.kt similarity index 78% rename from src/main/java/io/starter/formats/LEO/SMALLBLOCK.java rename to src/main/java/io/starter/formats/LEO/SMALLBLOCK.kt index b0467d5..d54d7af 100644 --- a/src/main/java/io/starter/formats/LEO/SMALLBLOCK.java +++ b/src/main/java/io/starter/formats/LEO/SMALLBLOCK.kt @@ -20,35 +20,40 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.LEO; +package io.starter.formats.LEO /** * LEO File SMALLBLOCK Information Record - *

                  + * + * * These blocks of data contain information related to the * LEO file format data blocks. - *

                  + * + * * depending on the size and complexity of the file, * these records may all be contained in the 'header' * block of the LEO Stream File. - *

                  + * + * * In files over that size, one or more SMALLBLOCK records * are inserted directly in the midst of substream records. */ -public class SMALLBLOCK extends BlockImpl { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7432771150988897281L; - public final static int SIZE = 64; +class SMALLBLOCK : BlockImpl() { /** * returns the int representing the block type */ - public final int getBlockType() { - return SMALL; + override val blockType: Int + get() = Block.SMALL + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -7432771150988897281L + val SIZE = 64 } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/Storable.java b/src/main/java/io/starter/formats/LEO/Storable.kt similarity index 86% rename from src/main/java/io/starter/formats/LEO/Storable.java rename to src/main/java/io/starter/formats/LEO/Storable.kt index e29f334..319099d 100644 --- a/src/main/java/io/starter/formats/LEO/Storable.java +++ b/src/main/java/io/starter/formats/LEO/Storable.kt @@ -20,16 +20,16 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.LEO; +package io.starter.formats.LEO -import java.util.Vector; +import java.util.Vector /** * Create a Storage interface/Generic implementation * to represent a file or index of files within a LEO filesystem. */ -public interface Storable { +interface Storable { /** causes the raw file data to be read in as a byte array */ @@ -38,12 +38,12 @@ public interface Storable { * associate this storage with its Block * table entries and data */ - void init(Vector dta, int[] tab); + fun init(dta: Vector<*>, tab: IntArray) /** * sets whether this Storage's Data blocks are contained * in the Small or Big Block arrays. */ - void setBlockType(int type); + fun setBlockType(type: Int) } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/Storage.java b/src/main/java/io/starter/formats/LEO/Storage.java deleted file mode 100644 index fd44742..0000000 --- a/src/main/java/io/starter/formats/LEO/Storage.java +++ /dev/null @@ -1,765 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.LEO; - -import io.starter.formats.XLS.XLSConstants; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - - -/** - * Defines a 'file' in the LEO filesystem. contains pointers - * to Block storages as well as the storage type etc. - *

                  - *

                  - * Header (128 bytes): - * Directory Entry Name (64 bytes): This field MUST contain a Unicode string for the storage or stream name encoded in UTF-16. The name MUST be terminated with a UTF-16 terminating null character. Thus storage and stream names are limited to 32 UTF-16 code points, including the terminating null character. When locating an object in the compound file except for the root storage, the directory entry name is compared using a special case-insensitive upper-case mapping, described in Red-Black Tree. The following characters are illegal and MUST NOT be part of the name: '/', '\', ':', '!'. - * Directory Entry Name Length (2 bytes): This field MUST match the length of the Directory Entry Name Unicode string in bytes. The length MUST be a multiple of 2, and include the terminating null character in the count. This length MUST NOT exceed 64, the maximum size of the Directory Entry Name field. - * Object Type (1 byte): This field MUST be 0x00, 0x01, 0x02, or 0x05, depending on the actual type of object. All other values are not valid. - * 0= Unknown or unallocated - * 1= Storage Object - * 2- Stream Object - * 5= Root Storage Object - * Color Flag (1 byte): This field MUST be 0x00 (red) or 0x01 (black). All other values are not valid. - * Left Sibling ID (4 bytes): This field contains the Stream ID of the left sibling. If there is no left sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF). - * Right Sibling ID (4 bytes): This field contains the Stream ID of the right sibling. If there is no right sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF). - * Child ID (4 bytes): This field contains the Stream ID of a child object. If there is no child object, then the field MUST be set to NOSTREAM (0xFFFFFFFF). - * CLSID (16 bytes): This field contains an object classGUID, if this entry is a storage or root storage. If there is no object class GUID set on this object, then the field MUST be set to all zeroes. In a stream object, this field MUST be set to all zeroes. If not NULL, the object class GUID can be used as a parameter to launch applications. - * State Bits (4 bytes): This field contains the user-defined flags if this entry is a storage object or root storage object. If there are no state bits set on the object, then this field MUST be set to all zeroes. - * Creation Time (8 bytes): This field contains the creation time for a storage object. The Windows FILETIME structure is used to represent this field in UTC. If there is no creation time set on the object, this field MUST be all zeroes. For a root storage object, this field MUST be all zeroes, and the creation time is retrieved or set on the compound file itself. - * Modified Time (8 bytes): This field contains the modification time for a storage object. The Windows FILETIME structure is used to represent this field in UTC. If there is no modified time set on the object, this field MUST be all zeroes. For a root storage object, this field MUST be all zeroes, and the modified time is retrieved or set on the compound file itself. - * Starting Sector Location (4 bytes): This field contains the first sector location if this is a stream object. For a root storage object, this field MUST contain the first sector of the mini st ream, if the mini stream exists. - * Stream Size (8 bytes): This 64-bit integer field contains the size of the user-defined data, if this is a stream object. For a root storage object, this field contains the size of the mini stream. - *

                  - * ?? - * offset type value const? function - * 00: stream $pps_rawname ! name of the pps - * 40: word $pps_sizeofname ! size of $pps_rawname - * 42: byte $pps_type ! type of pps (1=storage|2=stream|5=root) - * 43: byte $pps_uk0 ! ? - * 44: long $pps_prev ! previous pps - * 48: long $pps_next ! next pps - * 4c: long $pps_dir ! directory pps - * 50: stream 00 09 02 00 . ? - * 54: long 0 . ? - * 58: long c0 . ? - * 5c: stream 00 00 00 46 . ? - * 60: long 0 . ? - * 64: long $pps_ts1s ! timestamp 1 : "seconds" creation time - * 68: long $pps_ts1d ! timestamp 1 : "days" - * 6c: long $pps_ts2s ! timestamp 2 : "seconds" modified time - * 70: long $pps_ts2d ! timestamp 2 : "days" - * 74: long $pps_sb ! starting block of property - * 78: long $pps_size ! size of property - * 7c: long . ? - */ -public class Storage extends BlockByteReader { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2065921767253066667L; - // make an enum in > 1.4 - static byte TYPE_INVALID = 0; - static byte TYPE_DIRECTORY = 1; - static byte TYPE_STREAM = 2; - static byte TYPE_LOCKBYTES = 3; - static byte TYPE_PROPERTY = 4; - static byte TYPE_ROOT = 5; - - byte DIR_COLOR_RED = 0; - byte DIR_COLOR_BLACK = 1; - - - private transient ByteBuffer headerData = ByteBuffer.allocate(128); - - // properties of this storage file. - String name = ""; - int nameSize = -1; - - public byte storageType = -1; - public byte directoryColor = -1; - public int prevStorageID = -1; - public int nextStorageID = -1; - public int childStorageID = -1; - - public int sz = -1; - public boolean miniStreamStorage = false; - private boolean isSpecial = false; - - protected List myblocks; - private int startBlock = 0; - private int SIZE = -1; - private int blockType = -1; - private boolean initialized = false; - AbstractList idxs = new CompatibleVector(); - - Block lastblock = null; - - public void setBlocks(Block[] blks) { - myblocks = new ArrayList(); - for (int t = 0; t < blks.length; t++) - this.addBlock(blks[t]); - } - - /** - * returns a new BlockByteReader - * - * @return - */ - public BlockByteReader getBlockReader() { - BlockByteReader ret = - new BlockByteReader(myblocks, this.getActualFileSize()); - return ret; - } - - public List getBlockVect() { - return myblocks; - } - - - public void setIsSpecial(boolean b) { - this.isSpecial = b; - } - - public boolean getIsSpecial() { - return this.isSpecial; - } - - public boolean getInitialized() { - return this.initialized; - } - - public void setInitialized(boolean b) { - this.initialized = b; - } - - - /** - * remove a block from this Storage's headerData - */ - void removeBlock(Block b) { - myblocks.remove(b); - } - - /** - * sets whether this Storage's headerData blocks are contained - * in the Small or Big Block arrays. - */ - public void setBlockType(int type) { - this.blockType = type; - switch (type) { - case Block.BIG: - SIZE = BIGBLOCK.SIZE; - break; - case Block.SMALL: - SIZE = SMALLBLOCK.SIZE; - break; - } - } - - /** - * returns whether this Storage's headerData blocks are contained - * in the Small or Big Block arrays. - */ - public int getBlockType() { - return this.blockType; - } - - public int getStorageType() { - return storageType; - } - - public int getDirectoryColor() { - return directoryColor; - } - - /** - * set the value of the prevProp variable - */ - public void setDirectoryColor(int o) { - int pos = 0x43; - headerData.position(pos); - headerData.put((byte) o); - this.directoryColor = (byte) o; - } - - - public String getName() { - return name; - } - - public String toString() { - return getName() + " n:" + nextStorageID + " p:" + prevStorageID + " c:" + childStorageID + " sz:" + sz; - } - - /** - * sets the storage name - * - * @param nm - */ - public void setName(String nm) { - try { - byte[] b = nm.getBytes(XLSConstants.UNICODEENCODING); - int pos = 0; // unicode name bytes - headerData.position(pos); - headerData.put(b); - pos = 0x40; // short name size - headerData.position(pos); - headerData.putShort((short) (b.length + 2)); - } catch (UnsupportedEncodingException e) { - } - name = nm; - } - - /**/ - protected Storage() { - // empty constructor... - } - - /** - * create a new Storage record with a byte - * array containing its record headerData. - */ - protected Storage(ByteBuffer buff) { - headerData = buff; - int pos = 0x40; - headerData.position(pos); - nameSize = headerData.getShort(); - if (nameSize > 0) { - // get the name - pos = 0; - byte[] namebuf = new byte[nameSize]; - try { - for (int i = 0; i < nameSize; i++) { - namebuf[i] = headerData.get(i); - } - } catch (Exception e) { - } - try { - name = new String(namebuf, XLSConstants.UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logWarn( - "Storage error decoding storage name " + e); - } - name = name.substring(0, name.length() - 1); - // if this line fails, your header BBD index is wrong... - - } else { - // empty storage - } - pos = 0x42; - headerData.position(pos); - storageType = headerData.get(); - directoryColor = headerData.get(); - prevStorageID = headerData.getInt(); - nextStorageID = headerData.getInt(); - childStorageID = headerData.getInt(); - - sz = this.getActualFileSize(); - if (sz > 0 && sz < BIGBLOCK.SIZE) - miniStreamStorage = true; - if (LEOFile.DEBUG) - Logger.logInfo("Storage: " + name + " storageType: " + storageType + " directoryColor:" + directoryColor + - " prevSID:" + prevStorageID + " nextSID:" + nextStorageID + " childSID:" + childStorageID + " sz:" + sz); - } - - /** - * get the value of the prevProp variable - */ - public int getPrevStorageID() { - return this.prevStorageID; - } - - /** - * set the value of the prevProp variable - */ - public void setPrevStorageID(int o) { - int pos = 0x44; - headerData.position(pos); - headerData.putInt(o); - this.prevStorageID = o; - } - - /** - * get the value of the nextProp variable - */ - public int getNextStorageID() { - return this.nextStorageID; - } - - /** - * set the value of the nextProp variable - */ - public void setNextStorageID(int o) { - int pos = 0x48; - headerData.position(pos); - headerData.putInt(o); - this.nextStorageID = o; - } - - /** - * get the value of the child storage id - */ - public int getChildStorageID() { - return this.childStorageID; - } - - /** - * set the value of the child storage id - */ - public void setChildStorageID(int o) { - int pos = 0x4C; - headerData.position(pos); - headerData.putInt(o); - this.childStorageID = o; - } - - /** - * get the position of this Storage in the file bytes - */ - public int getFilePos() { - int ret = (this.getStartBlock() + 1) * SIZE; - return ret; - } - - /** - * return the existing header headerData for this storage - */ - public ByteBuffer getHeaderData() { - return headerData; - } - - /** - * return the underlying byte array for this - * Storage - */ - public byte[] getBytes() { - return LEOFile.getBytes(myblocks); - } - - /** - * return the underlying byte array for this - * Storage - */ - public OutputStream getByteStream() { - return LEOFile.getByteStream(myblocks); - } - - /** - * return the underlying byte array for this - * Storage - */ - public void writeBytes(OutputStream out) { - Iterator itx = myblocks.iterator(); - while (itx.hasNext()) { - ((Block) itx.next()).writeBytes(out); - } - } - - /** - * set the underlying byte array for this - * Storage - */ - public void writeBytes(OutputStream out, int blen) { - Block[] bs = BlockFactory.getBlocksFromOutputStream(out, blen, Block.BIG); - if (myblocks != null) { - myblocks.clear(); - lastblock = null; - } else { - myblocks = new ArrayList(bs.length); - } - for (int d = 0; d < bs.length; d++) - this.addBlock(bs[d]); - } - - /** - * set the underlying byte array for this - * Storage - */ - public void setOutputBytes(OutputStream b, int blen) { - Block[] bs = BlockFactory.getBlocksFromOutputStream(b, blen, Block.BIG); - if (myblocks != null) { - myblocks.clear(); - lastblock = null; - } else { - myblocks = new ArrayList(bs.length); - } - for (int d = 0; d < bs.length; d++) - this.addBlock(bs[d]); - } - - /** - * set the underlying byte array for this - * Storage - */ - public void setBytes(byte[] b) { - if (this.miniStreamStorage) { - this.setMiniFATSectorBytes(b); - return; - } - Block[] bs = BlockFactory.getBlocksFromByteArray(b, Block.BIG); - if (myblocks != null) { - myblocks.clear(); - lastblock = null; - } else { - myblocks = new ArrayList(bs.length); - } - for (int d = 0; d < bs.length; d++) - this.addBlock(bs[d]); - } - - /** - * Sets bytes on a miniFAT storage - * - * @param b - */ - private void setMiniFATSectorBytes(byte[] b) { - Block[] bs = BlockFactory.getBlocksFromByteArray(b, Block.SMALL); - if (myblocks != null) { - myblocks.clear(); - lastblock = null; - } else { - myblocks = new ArrayList(bs.length); - } - for (int d = 0; d < bs.length; d++) - this.addBlock(bs[d]); - } - - /** - * sets bytes for this storage; length of newbytes determines - * whether the storage is miniFAT or regular - *
                  Incldes padding of bytes to ensure blocks are a factor of required block size - * - * @param newbytes - */ - public void setBytesWithOverage(byte[] newbytes) { - int actuallen = newbytes.length; - myblocks = new ArrayList(); // clear out - if (newbytes.length < StorageTable.BIGSTORAGE_SIZE) { // usual case - int overage = newbytes.length % 128; - if (overage > 0) { - byte[] b = new byte[128 - overage]; - newbytes = ByteTools.append(b, newbytes); - } - Block[] smallblocks = BlockFactory.getBlocksFromByteArray(newbytes, Block.SMALL); - for (int i = 0; i < smallblocks.length; i++) - this.addBlock(smallblocks[i]); - this.setBlockType(Block.SMALL); - } else { - int overage = newbytes.length % BIGBLOCK.SIZE; - if (overage > 0) { - byte[] b = new byte[BIGBLOCK.SIZE - overage]; - newbytes = ByteTools.append(b, newbytes); - } - Block[] blocks = BlockFactory.getBlocksFromByteArray(newbytes, Block.BIG); - for (int i = 0; i < blocks.length; i++) - this.addBlock(blocks[i]); - this.setBlockType(Block.BIG); - } - this.setActualFileSize(actuallen); - } - - /** - * Associate this storage with it's data - * (obtained by walking the miniFAT sector index to reference blocks in the miniStream - */ - public void initFromMiniStream(List miniStream, int[] miniFAT) throws LEOIndexingException { - if (this.getStartBlock() < 0) - return; - - if (miniFAT == null) { // error: trying to access smallblocks but no smallblock container found - if (LEOFile.DEBUG) - Logger.logWarn("initMiniFAT: no miniFAT container found"); - return; - } - myblocks = new ArrayList(); - boolean endloop = false; - Block thisBlock = null; - - int idx = this.getStartBlock(); - while (idx >= 0) { - switch (idx) { - case -1: // unused sector, shouldn't get to here - case -2: // end of sector marker, exit - endloop = true; - break; - - default: - if (idx >= miniStream.size()) { - Logger.logWarn("MiniStream Error initting Storage: " + this.getName()); - } else { - thisBlock = (Block) miniStream.get(idx); // miniFAT is 0-based (no header sector at position 0 as in regular FAT) - this.addBlock(thisBlock); - } - } - if (endloop) - break; - idx = miniFAT[idx]; // otherwise, walk the sector id chain - } - if (LEOFile.DEBUG) { - if ((int) Math.ceil(this.getActualFileSize() / 64.0) != myblocks.size()) - Logger.logErr("Number of miniStream Sectors does not equal storage size. Expected: " + (int) Math.ceil(this.getActualFileSize() / 64.0) + ". Is: " + myblocks.size()); - } - this.setInitialized(true); - } - - /** - * associate this storage with its blocks of data - * (obtained by walking down the FAT sector index chain to obtain blocks from the dta block store) - *

                  - * for some bizarre reason, at idx 30208 the extraDIFAT jumps 5420 to 35628 - */ - public void init(List dta, int[] FAT, boolean keepStartBlock) { - myblocks = new ArrayList(); - boolean endloop = false; - if (getStartBlock() < 0) - return; - Block thisbb = null; - int nextIdx = 0; //, lastIdx = 0, specialOffset = 1; - - // ksc: for root block and miniFAT cont., we add start block to block list - if (keepStartBlock) { - // for root storages, add rootstart block - thisbb = (Block) dta.get(startBlock + 1); - this.addBlock(thisbb); //; - } - for (int i = startBlock; i < FAT.length; ) { - nextIdx = FAT[i]; - - switch (nextIdx) { - - case -4: // extraDIFAT sector - Logger.logInfo( - "INFO: Storage.init() encountered extra DIFAT sector."); - break; - - case -3: // special block = DIFAT - defines the FAT - if (this.getActualFileSize() > 0) { - if (LEOFile.DEBUG) - Logger.logWarn( - "WARNING: Storage.init() Special block containing headerData."); - this.setIsSpecial(true); - - thisbb = (Block) dta.get(i++); - if (!thisbb.getIsSpecialBlock()) { - this.addBlock(thisbb); //; - } - nextIdx = i; - } else { - endloop = true; - } - break; - - case -1: // unused - endloop = true; - break; // ksc - - case -2: // end of Storage - keep end block - if (i + 1 < dta.size()) { - // get the "padding" block for later retrieval - thisbb = (Block) dta.get(i + 1); - if (thisbb == null) - break; - this.addBlock(thisbb); // - //} - } - endloop = true; - break; - - default: // normal block - if (dta.size() > nextIdx) - thisbb = (Block) dta.get(nextIdx); - if (thisbb == null) - break; - if (nextIdx != i + 1) { - //the next is a jumper, pickup the orphan - if (LEOFile.DEBUG) - Logger.logInfo( - "INFO: Storage init: jumper skipping: " - + i); - Block skipbb = (Block) dta.get(i + 1); - - this.addBlock(skipbb); // - } else if (!thisbb.getIsSpecialBlock()) { // just skip as probably a bbdix in the midst of the secid chain - this.addBlock(thisbb); // - - } - } - i = nextIdx; - if (endloop) - break; - } - - if (LEOFile.DEBUG) { - int sz = this.getActualFileSize(); - if (sz != 0) { - if (Math.ceil(sz / 512.0) != myblocks.size()) - Logger.logWarn("Storage.init: Number of blocks do not equal storage size"); - } - } - this.setInitialized(true); - } - - /** - * adds a block of data to this storage - * - * @param b - */ - public void addBlock(Block b) { - if (lastblock != null) - lastblock.setNextBlock(b); - - if (b.getInitialized()) { - if (LEOFile.DEBUG) Logger.logWarn("ERROR: " + this.toString() + " - Block is already initialized."); - return; - } - b.setStorage(this); - b.setInitialized(true); - if (myblocks == null) - myblocks = new ArrayList(); - myblocks.add(b); - lastblock = b; - } - - /** - * set the storage type - */ - public void setStorageType(int i) { - int pos = 0x42; - headerData.position(pos); - headerData.put((byte) i); - storageType = (byte) i; - } - - /** - * set the size of the Storage headerData - */ - public void setActualFileSize(int i) { - int pos = 0x78; - this.sz = i; - headerData.position(pos); - headerData.putInt(i); - } - - /** - * get the size of the Storage headerData - */ - public int getActualFileSize() { - int pos = 0x78; - headerData.position(pos); - this.sz = headerData.getInt(); - return this.sz; - } - - /** - * set the starting block in the Block table - * array for this Storage's headerData. - */ - public void setStartBlock(int i) { - this.startBlock = i; - int pos = 0x74; - this.headerData.position(pos); - this.headerData.putInt(i); - } - - /** - * get the starting block for the Storage headerData - */ - public int getStartBlock() { - int pos = 0x74; - headerData.position(pos); - startBlock = headerData.getInt(); - return startBlock; - } - - /** - * return this Storage's existing headerData Blocks. - */ - Block[] getBlocks() { - if (myblocks.size() < 1) { - return this.initBigBlocks(); - } - Block[] blox = new Block[this.myblocks.size()]; - blox = (Block[]) myblocks.toArray(blox); - return blox; - } - - /** - * return this Storage's headerData in new Blocks - */ - private Block[] initBigBlocks() { - // byte[] bb = this.getBytes(); - if (this.getLength() > 0) { - Block[] blks = BlockFactory.getBlocksFromByteArray(this.getBytes(), Block.BIG); - int t = 0; - myblocks.clear(); - for (; t < blks.length; t++) { - if (t + 1 < blks.length) - blks[t].setNextBlock(blks[t + 1]); - this.addBlock(blks[t]); - } - return blks; - } - return null; - } - - /** - * get the number of Block records - * that this Storage needs to store its - * byte array - */ - public int getSizeInBlocks() { - return myblocks.size(); - } - - /** - * Track BB Index info - */ - public void addIdx(int x) { - idxs.add(Integer.valueOf(x)); - } - - public boolean equals(Object other) { - return other.toString().equals(this.toString()); - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - if (myblocks != null) - myblocks.clear(); - if (idxs != null) - idxs.clear(); - lastblock = null; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/Storage.kt b/src/main/java/io/starter/formats/LEO/Storage.kt new file mode 100644 index 0000000..bd5c03f --- /dev/null +++ b/src/main/java/io/starter/formats/LEO/Storage.kt @@ -0,0 +1,735 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.LEO + +import io.starter.formats.XLS.XLSConstants +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + +import java.io.OutputStream +import java.io.UnsupportedEncodingException +import java.nio.ByteBuffer +import java.util.AbstractList +import java.util.ArrayList + + +/** + * Defines a 'file' in the LEO filesystem. contains pointers + * to Block storages as well as the storage type etc. + * + * + * + * + * Header (128 bytes): + * Directory Entry Name (64 bytes): This field MUST contain a Unicode string for the storage or stream name encoded in UTF-16. The name MUST be terminated with a UTF-16 terminating null character. Thus storage and stream names are limited to 32 UTF-16 code points, including the terminating null character. When locating an object in the compound file except for the root storage, the directory entry name is compared using a special case-insensitive upper-case mapping, described in Red-Black Tree. The following characters are illegal and MUST NOT be part of the name: '/', '\', ':', '!'. + * Directory Entry Name Length (2 bytes): This field MUST match the length of the Directory Entry Name Unicode string in bytes. The length MUST be a multiple of 2, and include the terminating null character in the count. This length MUST NOT exceed 64, the maximum size of the Directory Entry Name field. + * Object Type (1 byte): This field MUST be 0x00, 0x01, 0x02, or 0x05, depending on the actual type of object. All other values are not valid. + * 0= Unknown or unallocated + * 1= Storage Object + * 2- Stream Object + * 5= Root Storage Object + * Color Flag (1 byte): This field MUST be 0x00 (red) or 0x01 (black). All other values are not valid. + * Left Sibling ID (4 bytes): This field contains the Stream ID of the left sibling. If there is no left sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF). + * Right Sibling ID (4 bytes): This field contains the Stream ID of the right sibling. If there is no right sibling, the field MUST be set to NOSTREAM (0xFFFFFFFF). + * Child ID (4 bytes): This field contains the Stream ID of a child object. If there is no child object, then the field MUST be set to NOSTREAM (0xFFFFFFFF). + * CLSID (16 bytes): This field contains an object classGUID, if this entry is a storage or root storage. If there is no object class GUID set on this object, then the field MUST be set to all zeroes. In a stream object, this field MUST be set to all zeroes. If not NULL, the object class GUID can be used as a parameter to launch applications. + * State Bits (4 bytes): This field contains the user-defined flags if this entry is a storage object or root storage object. If there are no state bits set on the object, then this field MUST be set to all zeroes. + * Creation Time (8 bytes): This field contains the creation time for a storage object. The Windows FILETIME structure is used to represent this field in UTC. If there is no creation time set on the object, this field MUST be all zeroes. For a root storage object, this field MUST be all zeroes, and the creation time is retrieved or set on the compound file itself. + * Modified Time (8 bytes): This field contains the modification time for a storage object. The Windows FILETIME structure is used to represent this field in UTC. If there is no modified time set on the object, this field MUST be all zeroes. For a root storage object, this field MUST be all zeroes, and the modified time is retrieved or set on the compound file itself. + * Starting Sector Location (4 bytes): This field contains the first sector location if this is a stream object. For a root storage object, this field MUST contain the first sector of the mini st ream, if the mini stream exists. + * Stream Size (8 bytes): This 64-bit integer field contains the size of the user-defined data, if this is a stream object. For a root storage object, this field contains the size of the mini stream. + * + * + * ?? + * offset type value const? function + * 00: stream $pps_rawname ! name of the pps + * 40: word $pps_sizeofname ! size of $pps_rawname + * 42: byte $pps_type ! type of pps (1=storage|2=stream|5=root) + * 43: byte $pps_uk0 ! ? + * 44: long $pps_prev ! previous pps + * 48: long $pps_next ! next pps + * 4c: long $pps_dir ! directory pps + * 50: stream 00 09 02 00 . ? + * 54: long 0 . ? + * 58: long c0 . ? + * 5c: stream 00 00 00 46 . ? + * 60: long 0 . ? + * 64: long $pps_ts1s ! timestamp 1 : "seconds" creation time + * 68: long $pps_ts1d ! timestamp 1 : "days" + * 6c: long $pps_ts2s ! timestamp 2 : "seconds" modified time + * 70: long $pps_ts2d ! timestamp 2 : "days" + * 74: long $pps_sb ! starting block of property + * 78: long $pps_size ! size of property + * 7c: long . ? + */ +open class Storage : BlockByteReader { + + internal var DIR_COLOR_RED: Byte = 0 + internal var DIR_COLOR_BLACK: Byte = 1 + + + /** + * return the existing header headerData for this storage + */ + @Transient + var headerData = ByteBuffer.allocate(128) + + // properties of this storage file. + internal var name = "" + internal var nameSize = -1 + + var storageType: Byte = -1 + var directoryColor: Byte = -1 + var prevStorageID = -1 + var nextStorageID = -1 + var childStorageID = -1 + + var sz = -1 + var miniStreamStorage = false + var isSpecial = false + + var myblocks: MutableList<*>? = null + private var startBlock = 0 + private var SIZE = -1 + /** + * returns whether this Storage's headerData blocks are contained + * in the Small or Big Block arrays. + */ + /** + * sets whether this Storage's headerData blocks are contained + * in the Small or Big Block arrays. + */ + var blockType = -1 + set(type) { + field = type + when (type) { + Block.BIG -> SIZE = BIGBLOCK.SIZE + Block.SMALL -> SIZE = SMALLBLOCK.SIZE + } + } + var initialized = false + internal var idxs: AbstractList<*>? = CompatibleVector() + + internal var lastblock: Block? = null + + /** + * returns a new BlockByteReader + * + * @return + */ + val blockReader: BlockByteReader + get() = BlockByteReader(myblocks, actualFileSize) + + val blockVect: List<*>? + get() = myblocks + + /** + * get the position of this Storage in the file bytes + */ + val filePos: Int + get() = (getStartBlock() + 1) * SIZE + + /** + * return the underlying byte array for this + * Storage + */ + /** + * set the underlying byte array for this + * Storage + */ + open var bytes: ByteArray + get() = LEOFile.getBytes(myblocks) + set(b) { + if (this.miniStreamStorage) { + this.setMiniFATSectorBytes(b) + return + } + val bs = BlockFactory.getBlocksFromByteArray(b, Block.BIG) + if (myblocks != null) { + myblocks!!.clear() + lastblock = null + } else { + myblocks = ArrayList(bs!!.size) + } + for (d in bs!!.indices) + this.addBlock(bs[d]) + } + + /** + * return the underlying byte array for this + * Storage + */ + val byteStream: OutputStream + get() = LEOFile.getByteStream(myblocks) + + /** + * get the size of the Storage headerData + */ + /** + * set the size of the Storage headerData + */ + var actualFileSize: Int + get() { + val pos = 0x78 + headerData.position(pos) + this.sz = headerData.int + return this.sz + } + set(i) { + val pos = 0x78 + this.sz = i + headerData.position(pos) + headerData.putInt(i) + } + + /** + * return this Storage's existing headerData Blocks. + */ + internal var blocks: Array? + get() { + if (myblocks!!.size < 1) { + return this.initBigBlocks() + } + var blox = arrayOfNulls(this.myblocks!!.size) + blox = myblocks!!.toTypedArray() as Array + return blox + } + set(blks) { + myblocks = ArrayList() + for (t in blks.indices) + this.addBlock(blks[t]) + } + + /** + * get the number of Block records + * that this Storage needs to store its + * byte array + */ + val sizeInBlocks: Int + get() = myblocks!!.size + + + /** + * remove a block from this Storage's headerData + */ + internal fun removeBlock(b: Block) { + myblocks!!.remove(b) + } + + fun getStorageType(): Int { + return storageType.toInt() + } + + fun getDirectoryColor(): Int { + return directoryColor.toInt() + } + + /** + * set the value of the prevProp variable + */ + fun setDirectoryColor(o: Int) { + val pos = 0x43 + headerData.position(pos) + headerData.put(o.toByte()) + this.directoryColor = o.toByte() + } + + + fun getName(): String { + return name + } + + override fun toString(): String { + return getName() + " n:" + nextStorageID + " p:" + prevStorageID + " c:" + childStorageID + " sz:" + sz + } + + /** + * sets the storage name + * + * @param nm + */ + fun setName(nm: String) { + try { + val b = nm.toByteArray(charset(XLSConstants.UNICODEENCODING)) + var pos = 0 // unicode name bytes + headerData.position(pos) + headerData.put(b) + pos = 0x40 // short name size + headerData.position(pos) + headerData.putShort((b.size + 2).toShort()) + } catch (e: UnsupportedEncodingException) { + } + + name = nm + } + + /**/ + constructor() { + // empty constructor... + } + + /** + * create a new Storage record with a byte + * array containing its record headerData. + */ + constructor(buff: ByteBuffer) { + headerData = buff + var pos = 0x40 + headerData.position(pos) + nameSize = headerData.short.toInt() + if (nameSize > 0) { + // get the name + pos = 0 + val namebuf = ByteArray(nameSize) + try { + for (i in 0 until nameSize) { + namebuf[i] = headerData.get(i) + } + } catch (e: Exception) { + } + + try { + name = String(namebuf, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logWarn( + "Storage error decoding storage name $e") + } + + name = name.substring(0, name.length - 1) + // if this line fails, your header BBD index is wrong... + + } else { + // empty storage + } + pos = 0x42 + headerData.position(pos) + storageType = headerData.get() + directoryColor = headerData.get() + prevStorageID = headerData.int + nextStorageID = headerData.int + childStorageID = headerData.int + + sz = this.actualFileSize + if (sz > 0 && sz < BIGBLOCK.SIZE) + miniStreamStorage = true + if (LEOFile.DEBUG) + Logger.logInfo("Storage: " + name + " storageType: " + storageType + " directoryColor:" + directoryColor + + " prevSID:" + prevStorageID + " nextSID:" + nextStorageID + " childSID:" + childStorageID + " sz:" + sz) + } + + /** + * get the value of the prevProp variable + */ + fun getPrevStorageID(): Int { + return this.prevStorageID + } + + /** + * set the value of the prevProp variable + */ + fun setPrevStorageID(o: Int) { + val pos = 0x44 + headerData.position(pos) + headerData.putInt(o) + this.prevStorageID = o + } + + /** + * get the value of the nextProp variable + */ + fun getNextStorageID(): Int { + return this.nextStorageID + } + + /** + * set the value of the nextProp variable + */ + fun setNextStorageID(o: Int) { + val pos = 0x48 + headerData.position(pos) + headerData.putInt(o) + this.nextStorageID = o + } + + /** + * get the value of the child storage id + */ + fun getChildStorageID(): Int { + return this.childStorageID + } + + /** + * set the value of the child storage id + */ + fun setChildStorageID(o: Int) { + val pos = 0x4C + headerData.position(pos) + headerData.putInt(o) + this.childStorageID = o + } + + /** + * return the underlying byte array for this + * Storage + */ + fun writeBytes(out: OutputStream) { + val itx = myblocks!!.iterator() + while (itx.hasNext()) { + (itx.next() as Block).writeBytes(out) + } + } + + /** + * set the underlying byte array for this + * Storage + */ + fun writeBytes(out: OutputStream, blen: Int) { + val bs = BlockFactory.getBlocksFromOutputStream(out, blen, Block.BIG) + if (myblocks != null) { + myblocks!!.clear() + lastblock = null + } else { + myblocks = ArrayList(bs!!.size) + } + for (d in bs!!.indices) + this.addBlock(bs[d]) + } + + /** + * set the underlying byte array for this + * Storage + */ + fun setOutputBytes(b: OutputStream, blen: Int) { + val bs = BlockFactory.getBlocksFromOutputStream(b, blen, Block.BIG) + if (myblocks != null) { + myblocks!!.clear() + lastblock = null + } else { + myblocks = ArrayList(bs!!.size) + } + for (d in bs!!.indices) + this.addBlock(bs[d]) + } + + /** + * Sets bytes on a miniFAT storage + * + * @param b + */ + private fun setMiniFATSectorBytes(b: ByteArray) { + val bs = BlockFactory.getBlocksFromByteArray(b, Block.SMALL) + if (myblocks != null) { + myblocks!!.clear() + lastblock = null + } else { + myblocks = ArrayList(bs!!.size) + } + for (d in bs!!.indices) + this.addBlock(bs[d]) + } + + /** + * sets bytes for this storage; length of newbytes determines + * whether the storage is miniFAT or regular + *

                  Incldes padding of bytes to ensure blocks are a factor of required block size + * + * @param newbytes + */ + fun setBytesWithOverage(newbytes: ByteArray) { + var newbytes = newbytes + val actuallen = newbytes.size + myblocks = ArrayList() // clear out + if (newbytes.size < StorageTable.BIGSTORAGE_SIZE) { // usual case + val overage = newbytes.size % 128 + if (overage > 0) { + val b = ByteArray(128 - overage) + newbytes = ByteTools.append(b, newbytes) + } + val smallblocks = BlockFactory.getBlocksFromByteArray(newbytes, Block.SMALL) + for (i in smallblocks!!.indices) + this.addBlock(smallblocks[i]) + this.blockType = Block.SMALL + } else { + val overage = newbytes.size % BIGBLOCK.SIZE + if (overage > 0) { + val b = ByteArray(BIGBLOCK.SIZE - overage) + newbytes = ByteTools.append(b, newbytes) + } + val blocks = BlockFactory.getBlocksFromByteArray(newbytes, Block.BIG) + for (i in blocks!!.indices) + this.addBlock(blocks[i]) + this.blockType = Block.BIG + } + this.actualFileSize = actuallen + } + + /** + * Associate this storage with it's data + * (obtained by walking the miniFAT sector index to reference blocks in the miniStream + */ + @Throws(LEOIndexingException::class) + fun initFromMiniStream(miniStream: List<*>, miniFAT: IntArray?) { + if (this.getStartBlock() < 0) + return + + if (miniFAT == null) { // error: trying to access smallblocks but no smallblock container found + if (LEOFile.DEBUG) + Logger.logWarn("initMiniFAT: no miniFAT container found") + return + } + myblocks = ArrayList() + var endloop = false + var thisBlock: Block? = null + + var idx = this.getStartBlock() + while (idx >= 0) { + when (idx) { + -1 // unused sector, shouldn't get to here + , -2 // end of sector marker, exit + -> endloop = true + + else -> if (idx >= miniStream.size) { + Logger.logWarn("MiniStream Error initting Storage: " + this.getName()) + } else { + thisBlock = miniStream[idx] as Block // miniFAT is 0-based (no header sector at position 0 as in regular FAT) + this.addBlock(thisBlock) + } + } + if (endloop) + break + idx = miniFAT[idx] // otherwise, walk the sector id chain + } + if (LEOFile.DEBUG) { + if (Math.ceil(this.actualFileSize / 64.0).toInt() != myblocks!!.size) + Logger.logErr("Number of miniStream Sectors does not equal storage size. Expected: " + Math.ceil(this.actualFileSize / 64.0).toInt() + ". Is: " + myblocks!!.size) + } + this.initialized = true + } + + /** + * associate this storage with its blocks of data + * (obtained by walking down the FAT sector index chain to obtain blocks from the dta block store) + * + * + * for some bizarre reason, at idx 30208 the extraDIFAT jumps 5420 to 35628 + */ + fun init(dta: List<*>, FAT: IntArray, keepStartBlock: Boolean) { + myblocks = ArrayList() + var endloop = false + if (getStartBlock() < 0) + return + var thisbb: Block? = null + var nextIdx = 0 //, lastIdx = 0, specialOffset = 1; + + // ksc: for root block and miniFAT cont., we add start block to block list + if (keepStartBlock) { + // for root storages, add rootstart block + thisbb = dta[startBlock + 1] as Block + this.addBlock(thisbb) //; + } + var i = startBlock + while (i < FAT.size) { + nextIdx = FAT[i] + + when (nextIdx) { + + -4 // extraDIFAT sector + -> Logger.logInfo( + "INFO: Storage.init() encountered extra DIFAT sector.") + + -3 // special block = DIFAT - defines the FAT + -> if (this.actualFileSize > 0) { + if (LEOFile.DEBUG) + Logger.logWarn( + "WARNING: Storage.init() Special block containing headerData.") + this.isSpecial = true + + thisbb = dta[i++] as Block + if (!thisbb.isSpecialBlock) { + this.addBlock(thisbb) //; + } + nextIdx = i + } else { + endloop = true + } + + -1 // unused + -> endloop = true + + -2 // end of Storage - keep end block + -> { + if (i + 1 < dta.size) { + // get the "padding" block for later retrieval + thisbb = dta[i + 1] as Block + if (thisbb == null) + break + this.addBlock(thisbb) // + //} + } + endloop = true + } + + else // normal block + -> { + if (dta.size > nextIdx) + thisbb = dta[nextIdx] as Block + if (thisbb == null) + break + if (nextIdx != i + 1) { + //the next is a jumper, pickup the orphan + if (LEOFile.DEBUG) + Logger.logInfo( + "INFO: Storage init: jumper skipping: $i") + val skipbb = dta[i + 1] as Block + + this.addBlock(skipbb) // + } else if (!thisbb.isSpecialBlock) { // just skip as probably a bbdix in the midst of the secid chain + this.addBlock(thisbb) // + + } + } + }// ksc + i = nextIdx + if (endloop) + break + } + + if (LEOFile.DEBUG) { + val sz = this.actualFileSize + if (sz != 0) { + if (Math.ceil(sz / 512.0) != myblocks!!.size.toDouble()) + Logger.logWarn("Storage.init: Number of blocks do not equal storage size") + } + } + this.initialized = true + } + + /** + * adds a block of data to this storage + * + * @param b + */ + fun addBlock(b: Block?) { + if (lastblock != null) + lastblock!!.setNextBlock(b) + + if (b!!.initialized) { + if (LEOFile.DEBUG) Logger.logWarn("ERROR: $this - Block is already initialized.") + return + } + b.storage = this + b.initialized = true + if (myblocks == null) + myblocks = ArrayList() + myblocks!!.add(b) + lastblock = b + } + + /** + * set the storage type + */ + fun setStorageType(i: Int) { + val pos = 0x42 + headerData.position(pos) + headerData.put(i.toByte()) + storageType = i.toByte() + } + + /** + * set the starting block in the Block table + * array for this Storage's headerData. + */ + fun setStartBlock(i: Int) { + this.startBlock = i + val pos = 0x74 + this.headerData.position(pos) + this.headerData.putInt(i) + } + + /** + * get the starting block for the Storage headerData + */ + fun getStartBlock(): Int { + val pos = 0x74 + headerData.position(pos) + startBlock = headerData.int + return startBlock + } + + /** + * return this Storage's headerData in new Blocks + */ + private fun initBigBlocks(): Array? { + // byte[] bb = this.getBytes(); + if (this.length > 0) { + val blks = BlockFactory.getBlocksFromByteArray(this.bytes, Block.BIG) + var t = 0 + myblocks!!.clear() + while (t < blks!!.size) { + if (t + 1 < blks.size) + blks[t].setNextBlock(blks[t + 1]) + this.addBlock(blks[t]) + t++ + } + return blks + } + return null + } + + /** + * Track BB Index info + */ + fun addIdx(x: Int) { + idxs!!.add(Integer.valueOf(x)) + } + + override fun equals(other: Any?): Boolean { + return other!!.toString() == this.toString() + } + + /** + * clear out object references in prep for closing workbook + */ + fun close() { + if (myblocks != null) + myblocks!!.clear() + if (idxs != null) + idxs!!.clear() + lastblock = null + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -2065921767253066667L + // make an enum in > 1.4 + internal var TYPE_INVALID: Byte = 0 + internal var TYPE_DIRECTORY: Byte = 1 + internal var TYPE_STREAM: Byte = 2 + internal var TYPE_LOCKBYTES: Byte = 3 + internal var TYPE_PROPERTY: Byte = 4 + internal var TYPE_ROOT: Byte = 5 + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/StorageNotFoundException.java b/src/main/java/io/starter/formats/LEO/StorageNotFoundException.kt similarity index 65% rename from src/main/java/io/starter/formats/LEO/StorageNotFoundException.java rename to src/main/java/io/starter/formats/LEO/StorageNotFoundException.kt index db7370c..4fe5b8a 100644 --- a/src/main/java/io/starter/formats/LEO/StorageNotFoundException.java +++ b/src/main/java/io/starter/formats/LEO/StorageNotFoundException.kt @@ -20,27 +20,24 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.LEO; +package io.starter.formats.LEO /** * An exception to be thrown when a storage is not found in a LEO operation* */ -public class StorageNotFoundException extends Exception { +class StorageNotFoundException : Exception { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4253743894652780395L; + constructor(cause: Throwable) : super(cause) {} - public StorageNotFoundException(Throwable cause) { - super(cause); - } + constructor(message: String, cause: Throwable) : super(message, cause) {} - public StorageNotFoundException(String message, Throwable cause) { - super(message, cause); - } + constructor(message: String) : super(message) {} + + companion object { - public StorageNotFoundException(String message) { - super(message); + /** + * serialVersionUID + */ + private val serialVersionUID = 4253743894652780395L } } diff --git a/src/main/java/io/starter/formats/LEO/StorageTable.java b/src/main/java/io/starter/formats/LEO/StorageTable.java deleted file mode 100644 index b3be8b3..0000000 --- a/src/main/java/io/starter/formats/LEO/StorageTable.java +++ /dev/null @@ -1,624 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.LEO; - -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.WorkBookException; -import io.starter.formats.XLS.XLSRecordFactory; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.Serializable; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.*; - - -/** - * The directory system for an LEO file - * - * Extentech Inc. - */ -public class StorageTable implements Serializable { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3399830613453524580L; - public final static int TABLE_SIZE = 0x200; - public final static int DIRECTORY_SIZE = 0x80; - public final static int BIGSTORAGE_SIZE = 4096; // default, should read from LEOHeader - - private LEOHeader myheader; - - CompatibleVector directoryVector = new CompatibleVector(); // all directories in the LEO file - - // Directory collection - Hashtable directoryHashtable = new Hashtable(100, 0.9f); - - private int dupct = 0; - - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - myheader = null; - - Iterator ii = directoryHashtable.keySet().iterator(); - while (ii.hasNext()) { - Storage s = (Storage) directoryHashtable.get(ii.next()); - s.close(); - } - directoryHashtable = new Hashtable(100, 0.9f); - - - for (int i = 0; i < directoryVector.size(); i++) { - Storage s = (Storage) directoryVector.get(i); - s.close(); - } - directoryVector.clear(); - } - - /** - * initialize the directory entry array - */ - public void init(ByteBuffer dta, LEOHeader h, List blockvect, int[] FAT) { - this.myheader = h; - byte[] data = LEOFile.getBytes(this.initDirectoryStream(blockvect, FAT)); - int psbsize = data.length; - int numRecs = psbsize / DIRECTORY_SIZE; - if (LEOFile.DEBUG) { - Logger.logInfo("Number of Directories: " + numRecs); - Logger.logInfo("Directories: " + Arrays.toString(data)); - } - - int pos = 0; - for (int i = 0; i < numRecs; i++) { - ByteBuffer b = ByteBuffer.allocate(DIRECTORY_SIZE); - b.order(ByteOrder.LITTLE_ENDIAN); - b.put(data, pos, DIRECTORY_SIZE); - pos += DIRECTORY_SIZE; - Storage rec = null; - try { - rec = new Storage(b); - } catch (Exception ex) { - throw new WorkBookException("StorageTable.init failed:" + ex.toString(), WorkBookException.UNSPECIFIED_INIT_ERROR); - } - if (i == 0) { - rec = new RootStorage(b); - if (!rec.getName().equals("Root Entry")) - rec.setName("Root Entry"); // can happen upon a mac-sourced file - } - this.addStorage(rec, -1); - } - } - - /** - * return the number of existing directories - * - * @return - */ - public int getNumDirectories() { - return directoryVector.size(); - } - - /** - * Create a new Storage and add to the directory array - *
                  NOTE: add any associated data separately - * - * @param name Storage name - must be unique - * @param type Storage Type - 1= storage 2= stream 5= root 0=unknown or unallocated - * @param insertIdx id where to insert, or -1 to insert at end - * @return - */ - public Storage createStorage(String name, int type, int insertIdx) { - Storage s = null; - try { - ByteBuffer b = ByteBuffer.allocate(DIRECTORY_SIZE); - b.order(ByteOrder.LITTLE_ENDIAN); - b.put(new byte[DIRECTORY_SIZE]); - s = new Storage(b); - s.setName(name); - s.setStorageType(type); - s.setBlocks(new Block[0]); // init empty storages with 0 blocksW - s.setPrevStorageID(-1); - s.setNextStorageID(-1); - s.setChildStorageID(-1); - addStorage(s, insertIdx); - } catch (Exception ex) { - throw new WorkBookException("Storage.createStorage failed:" + ex.toString(), WorkBookException.UNSPECIFIED_INIT_ERROR); - } - return s; - } - - /** - * init the directories and gather their associated data (if present) - * including those directories whose data is gathered from the miniStream - */ - int[] miniFAT = null; // index into the miniStream - - public void initDirectories(List blockvect, int[] FAT) { - Enumeration rcs = directoryVector.elements(); - List miniStream = null; - int totrecsize = 0; - - while (rcs.hasMoreElements()) { - Storage rec = (Storage) rcs.nextElement(); - String name = rec.getName(); - int recsize = rec.getActualFileSize(); -// if (LEOFile.DEBUG) -// Logger.logInfo("Initializing Directory: " + name + ". Start=" + rec.getStartBlock() + ". Size=" + recsize); - totrecsize += recsize; - if (name.equals("Root Entry")) { - // also sets miniFAT ... ugly, I know ... - miniStream = this.initMiniStream(blockvect, FAT); // grab the mini stream (short sector container) (if any), indexed by miniFAT - if (LEOFile.DEBUG && miniFAT != null) - Logger.logInfo("miniFAT: " + Arrays.toString(miniFAT)); - } - // this storage has it's data in the miniStream - else if ((recsize > 0) && (recsize < BIGSTORAGE_SIZE)) { - rec.setBlockType(Block.SMALL); - this.initStorage(rec, miniStream, miniFAT, Block.SMALL); // the miniStream is indexed by the miniFAT - // Regular Sector file storage - } else if ((recsize >= BIGSTORAGE_SIZE)) { - this.initStorage(rec, blockvect, FAT, Block.BIG); - - // a storage-less directory - } else if (recsize == 0) { - rec.setBlocks(new Block[0]); // init empty storages with 0 blocksW - } else { - if (LEOFile.DEBUG) - Logger.logWarn("Storage has no Block Type."); - } - } - if (LEOFile.DEBUG) - Logger.logInfo( - "Total Size used by Directories : " + totrecsize); - - } - - /** - * // whenever a stream is shorter than a specific length - * // it is stored as a short-stream or ministream. ministreamentries do not directly - * // use sectors to store their data, but are all embedded in a specific - * // internal control stream. - * // first used sector is obtained from the root store ==> miniFAT chain - * // it's secID chain is contained in the miniFAT - * // The data used by all of the short-sectors container stream are concatenated - * // in order of the secID chain. There is no header, so the first - * // mini sector (secId= 0) is always located at position 0 in the mini Stream - * // The miniFAT is the same as the FAT except the secID chains refer - * // to miniSectors (64 bytes) rather than regular sectors or blocks (512 bytes) - */ - public List initMiniStream(List blockvect, int[] FAT) { - int pos = myheader.getMiniFATStart(); - if (pos == -2) - return null; // no miniStream sectors - - List miniFATSectors = getMiniFAT(pos, blockvect, FAT); - if (miniFATSectors.size() > 0) { - try { - Iterator sbz = miniFATSectors.iterator(); - while (sbz.hasNext()) { - BIGBLOCK sbb = (BIGBLOCK) sbz.next(); - sbb.setIsDepotBlock(true); - } - - miniFAT = LEOFile.readFAT(miniFATSectors); - - RootStorage rootStore; - try { - rootStore = (RootStorage) this.getDirectoryByName("Root Entry"); - } catch (StorageNotFoundException e) { - throw new InvalidFileException("Error parsing OLE File. OLE FileSystem Out of Spec: No Root Entry."); - } - // capture the short-stream container stream - int miniStreamStart = rootStore.getStartBlock(); - int miniStreamSize = rootStore.getActualFileSize(); - - Storage miniStream = new Storage(); - miniStream.setStartBlock(miniStreamStart); - miniStream.setActualFileSize(miniStreamSize); - miniStream.setName("miniStream"); - // obtain the miniStream from the regular bigblock store - this.initStorage(miniStream, blockvect, FAT, Block.BIG); - // now that we have the entire miniStream , break it up into mini Sector-sized blocks - // NOTE: only miniStreamSize bytes are usable - ignore rest - byte[] b = new byte[miniStreamSize]; - System.arraycopy(miniStream.getBytes(), 0, b, 0, miniStreamSize); - Block[] miniStreamBlocks = BlockFactory.getBlocksFromByteArray(b, Block.SMALL); - ArrayList miniStreamBlockList = new ArrayList(); - for (int i = 0; i < miniStreamBlocks.length; i++) { // should equal sbbsize - miniStreamBlockList.add(miniStreamBlocks[i]); - } - return miniStreamBlockList; - } catch (LEOIndexingException e) { - Logger.logWarn("initSBStorages: Error obtaining sbdIdx"); - } - } - return null; - } - - /** - * extract the miniFAT sector index from the blockvect - * - * @param pos - * @param blockvect - * @param FAT - * @return - */ - private List getMiniFAT(int pos, List blockvect, int[] FAT) { - Storage miniFATContainer = null; - miniFATContainer = new Storage(); - miniFATContainer.setBlockType(Block.BIG); - miniFATContainer.setStartBlock(pos); - miniFATContainer.setStorageType(5); // set as root type to distinguish from regular storages - if (LEOFile.DEBUG) - Logger.logInfo("StorageTable.getMiniFAT() Initializing miniFAT Container."); - miniFATContainer.init(blockvect, FAT, true); - // miniFAT.setName("SBidx"); - // miniFAT index - return miniFATContainer.getBlockVect(); - } - - /** - * add a Storage to the directory array - * - * @param Storage storage to insert - * @param insertIdx -1 if add at end, otherwise insert at spot - */ - void addStorage(Storage rec, int insertIdx) { - String nm = rec.getName(); - if (directoryHashtable.get(nm) != null && !nm.equals("")) { -/* KSC: with 2012 code changes, this breaks output: - * if (LEOFile.DEBUG) - Logger.logInfo( - "INFO: StorageTable.addStorage() Dupe Storage Name: " + nm); - nm = nm + "|^" + dupct++; - rec.setName(nm); - Does not appear necessary -*/ - } - directoryHashtable.put(rec.getName(), rec); - if (insertIdx == -1) - directoryVector.add(rec); - else { - directoryVector.add(insertIdx, rec); - for (int i = 0; i < directoryVector.size(); i++) { // adjust prev, next, child ids if necessary - Storage s = (Storage) directoryVector.get(i); - if (s.getChildStorageID() >= insertIdx) - s.setChildStorageID(s.getChildStorageID() + 1); - if (s.getPrevStorageID() >= insertIdx) - s.setPrevStorageID(s.getPrevStorageID() + 1); - if (s.getNextStorageID() >= insertIdx) - s.setNextStorageID(s.getNextStorageID() + 1); - - } - } - - if (false && LEOFile.DEBUG) { - Logger.logInfo( - /*"INFO: StorageTable.addStorage() Storage size: " - +*/ rec.getName() - + " Size: " - + rec.getActualFileSize() - + " Start Block: " - + rec.getStartBlock()); - } - } - - /* remove a Storage - */ - void removeStorage(Storage st) { - this.directoryHashtable.remove(st); - this.directoryVector.remove(st); - } - - /** - * init a Storage - */ - void initStorage(Storage rec, List sourceblocks, int[] idx, int blocktype) { - String name = rec.getName(); - int recsize = rec.getActualFileSize(); - if (LEOFile.DEBUG) - Logger.logInfo( - "Initializing Storage: " + name + " Retrieving Data." + - " Size: " - + recsize - + " type: " - + blocktype - + " startidx: " - + rec.getStartBlock() + (rec.getBlockType() == Block.SMALL ? " MiniFAT" : "")); - rec.setBlockType(blocktype); - if (("Root Entry").equals(name)) // ksc: shouldn't! - return; - - - if (rec.getBlockType() == Block.BIG) - rec.init(sourceblocks, idx, false); - else if (rec.getBlockType() == Block.SMALL) - rec.initFromMiniStream(sourceblocks, idx); - - if (LEOFile.DEBUG) { - if (rec.getBytes() != null) { - if (name == null) - name = "noname.dat"; - if (name.charAt(0) == '') - name = name.substring(1); - if (name.charAt(0) == '') - name = name.substring(1); - // if(blocktype == Block.BIG) - StorageTable.writeitout(rec.getBlockVect(), name + ".stor"); - } - } - rec.idxs = null; - } - - /** - * get the directory BLOCKS or Sectors - * contains the header info for all of the directories - */ - List initDirectoryStream(List blockvect, int[] FAT) { - //getDirectoryBlocks() - Storage directories = null; - directories = new Storage(); - int pstart = (this.myheader.getRootStartPos() / BIGBLOCK.SIZE) - 1; - directories.setStartBlock(pstart); - directories.setBlockType(Block.BIG); - directories.setStorageType(5); // set to root directory - directories.init(blockvect, FAT, true); // get additional directory stores, if any -// directories.setName("StorageTable"); -// if (LEOFile.DEBUG) -// StorageTable.writeitout(directories.getBlockVect(), -// "directoryStorage.dat"); - return directories.getBlockVect(); - - - } - - /** - * generate new RootStorage bytes from - * all of the Storage directories - */ - byte[] rebuildRootStore() { - /* - * Free (unused) directory entries are marked with Object Type 0x0 (unknown or unallocated). - * The entire directory entry should consist of all zeroes except for the child, right sibling, - * and left sibling pointers, which should be initialized to NOSTREAM (0xFFFFFFFF). - */ - while ((directoryVector.size() % 4) != 0) { // add "null" storages to ensure multiples of 4 (128*4=512==minimum size) - this.createStorage("", 0, -1); - } - Enumeration e = directoryVector.elements(); - byte[] bytebuff = new byte[directoryVector.size() * DIRECTORY_SIZE]; - int pos = 0; - while (e.hasMoreElements()) { - Storage s = (Storage) e.nextElement(); - ByteBuffer buff = - this.getDirectoryHeaderBytes(s); - System.arraycopy(buff.array(), 0, bytebuff, pos, DIRECTORY_SIZE); - pos += DIRECTORY_SIZE; - } - return bytebuff; - } - - /** - * get the Hashtable of Storages within the LEO file - */ - Hashtable getStorageHash() { - return directoryHashtable; - } - - /** - * get the CompatibleVector of all Directories within the LEO file - */ - CompatibleVector getAllDirectories() { - return directoryVector; - } - - /** - * get the Root Storage Record - */ - RootStorage getRootStorage() throws StorageNotFoundException { - return (RootStorage) this.getDirectoryByName("Root Entry"); - } - - /** - * get the directory by name. throws StorageNotFoundException if not found. - */ - public Storage getDirectoryByName(String name) throws StorageNotFoundException { - if (directoryHashtable.get(name) != null) - return (Storage) directoryHashtable.get(name); - throw new StorageNotFoundException("Storage: " + name + " not located"); - } - - /** - * returns the Stream ID of the named directory, or -1 if not found - * - * @param name - * @return - * @throws StorageNotFoundException - */ - public int getDirectoryStreamID(String name) throws StorageNotFoundException { - for (int i = 0; i < directoryVector.size(); i++) { - Storage s = (Storage) directoryVector.get(i); - if (s.getName().equals(name)) - return i; - } - return -1; - } - - /** - * get the child directory (storage or stream) of the named storage - * - * @param name - * @throws StorageNotFoundException - * @return Storage or null - */ - public Storage getChild(String name) throws StorageNotFoundException { - Storage s = getDirectoryByName(name); - if (s != null) { - int child = s.getChildStorageID(); - if (child > -1) - return (Storage) directoryVector.get(child); - } - return null; - } - - /** - * get the next directory (storage or stream) of the named storage - * - * @param name - * @throws StorageNotFoundException - * @return Storage or null - */ - public Storage getNext(String name) throws StorageNotFoundException { - Storage s = getDirectoryByName(name); - if (s != null) { - int next = s.getNextStorageID(); - if (next > -1) - return (Storage) directoryVector.get(next); - } - return null; - } - - /** - * get the previous directory (storage or stream) of the named storage - * - * @param name - * @throws StorageNotFoundException - * @return Storage or null - */ - public Storage getPrevious(String name) throws StorageNotFoundException { - Storage s = getDirectoryByName(name); - if (s != null) { - int prev = s.getNextStorageID(); - if (prev > -1) - return (Storage) directoryVector.get(prev); - } - return null; - } - - /** - * get the header record for a Directory - */ - ByteBuffer getDirectoryHeaderBytes(Storage thisStorage) { - // create a new byte buffer - ByteBuffer buff = null; - buff = thisStorage.getHeaderData(); - // 20100304 KSC: don't reset anything, just return - return buff; - } - - /** - * write out the directory bytes for debugging - */ - public final static void writeitout(List blocks, String name) { - try { - FileOutputStream fos = - new FileOutputStream(new File(System.getProperty("user.dir") + "\\storages\\" + name)); - fos.write(LEOFile.getBytes(blocks)); - } catch (IOException a) { - } - } - - public void DEBUG() { - io.starter.toolkit.Logger.log("DIRECTORY CONTENTS:"); - for (int i = 0; i < directoryVector.size(); i++) { - Storage s = (Storage) directoryVector.get(i); - String n = s.getName(); - Logger.logInfo("Storage: " + n + " storageType: " + s.getStorageType() + " directoryColor:" + s.getDirectoryColor() + - " prevSID:" + s.getPrevStorageID() + " nextSID:" + s.getNextStorageID() + " childSID:" + s.getChildStorageID() + " sz:" + s.getActualFileSize()); - // special storages - if (n.equals("Root Entry")) { - Logger.logInfo("Root Header: " + Arrays.toString(s.getHeaderData().array())); - - /*********************************** - // KSC: TESTING for XLS-97: - !!! Set creation time and modified time on root storage to 0 - int p = 100; - s.getHeaderData().position(p); - Long tsCreated= s.getHeaderData().getLong(); - Long tsModified= s.getHeaderData().getLong(); - */ - if (s.myblocks != null) { - int zz = 0; - if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) - io.starter.toolkit.Logger.log("BLOCK 1:\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK) s.myblocks.get(zz)).getBytes())); - else - io.starter.toolkit.Logger.log("BLOCK 1:\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK) s.myblocks.get(zz)).getBytes())); - } - } else if (n.equals("Workbook")) { - //skip - } else if (n.equals("\1CompObj")) { - BlockByteReader bytes = s.getBlockReader(); - int len = bytes.getLength(); - BiffRec rec = new io.starter.formats.XLS.XLSRecord(); // 4 bytes are header ... - rec.setByteReader(bytes); - rec.setLength(len); - int slen = ByteTools.readInt(rec.getBytesAt(24, 4)); // actually position 28 - if (slen >= 0) { - String ss = new String(rec.getBytesAt(28, slen)); // AnsiUserType= a display name of the linked object or embedded object. - io.starter.toolkit.Logger.log("\tOLE Object:" + ss); - } - // AnsiClipboardFormat (variable) -// io.starter.toolkit.Logger.log("\t" + Arrays.toString(rec.getData())); - } else if (n.startsWith("000")) { // pivot cache - if (s.myblocks != null) { - for (int zz = 0; zz < s.myblocks.size(); zz++) - if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) - io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK) s.myblocks.get(zz)).getBytes())); - else - io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK) s.myblocks.get(zz)).getBytes())); - } - BlockByteReader bytes = s.getBlockReader(); - int len = bytes.getLength(); - for (int z = 0; z <= len - 4; ) { - byte[] headerbytes = bytes.getHeaderBytes(z); - short opcode = ByteTools.readShort(headerbytes[0], headerbytes[1]); - int reclen = ByteTools.readShort(headerbytes[2], headerbytes[3]); - BiffRec rec = XLSRecordFactory.getBiffRecord(opcode); - rec.setByteReader(bytes); - rec.setOffset(z); - rec.setLength((short) reclen); - rec.init(); - io.starter.toolkit.Logger.log("\t\t" + rec.toString()); - z += reclen + 4; - } - } else { - if (s.myblocks != null) { - for (int zz = 0; zz < s.myblocks.size(); zz++) - if ((s.myblocks.get(zz) instanceof io.starter.formats.LEO.BIGBLOCK)) - io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.BIGBLOCK) s.myblocks.get(zz)).getBytes())); - else - io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString(((io.starter.formats.LEO.SMALLBLOCK) s.myblocks.get(zz)).getBytes())); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/LEO/StorageTable.kt b/src/main/java/io/starter/formats/LEO/StorageTable.kt new file mode 100644 index 0000000..5fab58a --- /dev/null +++ b/src/main/java/io/starter/formats/LEO/StorageTable.kt @@ -0,0 +1,626 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.LEO + +import io.starter.formats.XLS.BiffRec +import io.starter.formats.XLS.WorkBookException +import io.starter.formats.XLS.XLSRecordFactory +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.Serializable +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.util.* + + +/** + * The directory system for an LEO file + * + * [Extentech Inc.](http://www.extentech.com) + */ +class StorageTable : Serializable { + + private var myheader: LEOHeader? = null + + /** + * get the CompatibleVector of all Directories within the LEO file + */ + internal var allDirectories = CompatibleVector() // all directories in the LEO file + + // Directory collection + /** + * get the Hashtable of Storages within the LEO file + */ + internal var storageHash = Hashtable(100, 0.9f) + + private val dupct = 0 + + /** + * return the number of existing directories + * + * @return + */ + val numDirectories: Int + get() = allDirectories.size + + /** + * init the directories and gather their associated data (if present) + * including those directories whose data is gathered from the miniStream + */ + internal var miniFAT: IntArray? = null // index into the miniStream + + /** + * get the Root Storage Record + */ + internal val rootStorage: RootStorage + @Throws(StorageNotFoundException::class) + get() = this.getDirectoryByName("Root Entry") as RootStorage? + + + /** + * clear out object references in prep for closing workbook + */ + fun close() { + myheader = null + + val ii = storageHash.keys.iterator() + while (ii.hasNext()) { + val s = storageHash.get(ii.next()) as Storage + s.close() + } + storageHash = Hashtable(100, 0.9f) + + + for (i in allDirectories.indices) { + val s = allDirectories[i] as Storage + s.close() + } + allDirectories.clear() + } + + /** + * initialize the directory entry array + */ + fun init(dta: ByteBuffer, h: LEOHeader, blockvect: List<*>, FAT: IntArray) { + this.myheader = h + val data = LEOFile.getBytes(this.initDirectoryStream(blockvect, FAT)) + val psbsize = data.size + val numRecs = psbsize / DIRECTORY_SIZE + if (LEOFile.DEBUG) { + Logger.logInfo("Number of Directories: $numRecs") + Logger.logInfo("Directories: " + Arrays.toString(data)) + } + + var pos = 0 + for (i in 0 until numRecs) { + val b = ByteBuffer.allocate(DIRECTORY_SIZE) + b.order(ByteOrder.LITTLE_ENDIAN) + b.put(data, pos, DIRECTORY_SIZE) + pos += DIRECTORY_SIZE + var rec: Storage? = null + try { + rec = Storage(b) + } catch (ex: Exception) { + throw WorkBookException("StorageTable.init failed:$ex", WorkBookException.UNSPECIFIED_INIT_ERROR) + } + + if (i == 0) { + rec = RootStorage(b) + if (rec.getName() != "Root Entry") + rec.setName("Root Entry") // can happen upon a mac-sourced file + } + this.addStorage(rec, -1) + } + } + + /** + * Create a new Storage and add to the directory array + *

                  NOTE: add any associated data separately + * + * @param name Storage name - must be unique + * @param type Storage Type - 1= storage 2= stream 5= root 0=unknown or unallocated + * @param insertIdx id where to insert, or -1 to insert at end + * @return + */ + fun createStorage(name: String, type: Int, insertIdx: Int): Storage { + var s: Storage? = null + try { + val b = ByteBuffer.allocate(DIRECTORY_SIZE) + b.order(ByteOrder.LITTLE_ENDIAN) + b.put(ByteArray(DIRECTORY_SIZE)) + s = Storage(b) + s.setName(name) + s.setStorageType(type) + s.blocks = arrayOfNulls(0) // init empty storages with 0 blocksW + s.setPrevStorageID(-1) + s.setNextStorageID(-1) + s.setChildStorageID(-1) + addStorage(s, insertIdx) + } catch (ex: Exception) { + throw WorkBookException("Storage.createStorage failed:$ex", WorkBookException.UNSPECIFIED_INIT_ERROR) + } + + return s + } + + fun initDirectories(blockvect: List<*>, FAT: IntArray) { + val rcs = allDirectories.elements() + var miniStream: List<*>? = null + var totrecsize = 0 + + while (rcs.hasMoreElements()) { + val rec = rcs.nextElement() as Storage + val name = rec.getName() + val recsize = rec.actualFileSize + // if (LEOFile.DEBUG) + // Logger.logInfo("Initializing Directory: " + name + ". Start=" + rec.getStartBlock() + ". Size=" + recsize); + totrecsize += recsize + if (name == "Root Entry") { + // also sets miniFAT ... ugly, I know ... + miniStream = this.initMiniStream(blockvect, FAT) // grab the mini stream (short sector container) (if any), indexed by miniFAT + if (LEOFile.DEBUG && miniFAT != null) + Logger.logInfo("miniFAT: " + Arrays.toString(miniFAT)) + } else if (recsize > 0 && recsize < BIGSTORAGE_SIZE) { + rec.blockType = Block.SMALL + this.initStorage(rec, miniStream, miniFAT, Block.SMALL) // the miniStream is indexed by the miniFAT + // Regular Sector file storage + } else if (recsize >= BIGSTORAGE_SIZE) { + this.initStorage(rec, blockvect, FAT, Block.BIG) + + // a storage-less directory + } else if (recsize == 0) { + rec.blocks = arrayOfNulls(0) // init empty storages with 0 blocksW + } else { + if (LEOFile.DEBUG) + Logger.logWarn("Storage has no Block Type.") + }// this storage has it's data in the miniStream + } + if (LEOFile.DEBUG) + Logger.logInfo( + "Total Size used by Directories : $totrecsize") + + } + + /** + * // whenever a stream is shorter than a specific length + * // it is stored as a short-stream or ministream. ministreamentries do not directly + * // use sectors to store their data, but are all embedded in a specific + * // internal control stream. + * // first used sector is obtained from the root store ==> miniFAT chain + * // it's secID chain is contained in the miniFAT + * // The data used by all of the short-sectors container stream are concatenated + * // in order of the secID chain. There is no header, so the first + * // mini sector (secId= 0) is always located at position 0 in the mini Stream + * // The miniFAT is the same as the FAT except the secID chains refer + * // to miniSectors (64 bytes) rather than regular sectors or blocks (512 bytes) + */ + fun initMiniStream(blockvect: List<*>, FAT: IntArray): List<*>? { + val pos = myheader!!.miniFATStart + if (pos == -2) + return null // no miniStream sectors + + val miniFATSectors = getMiniFAT(pos, blockvect, FAT) + if (miniFATSectors!!.size > 0) { + try { + val sbz = miniFATSectors.iterator() + while (sbz.hasNext()) { + val sbb = sbz.next() as BIGBLOCK + sbb.isDepotBlock = true + } + + miniFAT = LEOFile.readFAT(miniFATSectors) + + val rootStore: RootStorage + try { + rootStore = this.getDirectoryByName("Root Entry") as RootStorage? + } catch (e: StorageNotFoundException) { + throw InvalidFileException("Error parsing OLE File. OLE FileSystem Out of Spec: No Root Entry.") + } + + // capture the short-stream container stream + val miniStreamStart = rootStore.startBlock + val miniStreamSize = rootStore.actualFileSize + + val miniStream = Storage() + miniStream.startBlock = miniStreamStart + miniStream.actualFileSize = miniStreamSize + miniStream.setName("miniStream") + // obtain the miniStream from the regular bigblock store + this.initStorage(miniStream, blockvect, FAT, Block.BIG) + // now that we have the entire miniStream , break it up into mini Sector-sized blocks + // NOTE: only miniStreamSize bytes are usable - ignore rest + val b = ByteArray(miniStreamSize) + System.arraycopy(miniStream.bytes, 0, b, 0, miniStreamSize) + val miniStreamBlocks = BlockFactory.getBlocksFromByteArray(b, Block.SMALL) + val miniStreamBlockList = ArrayList() + for (i in miniStreamBlocks!!.indices) { // should equal sbbsize + miniStreamBlockList.add(miniStreamBlocks[i]) + } + return miniStreamBlockList + } catch (e: LEOIndexingException) { + Logger.logWarn("initSBStorages: Error obtaining sbdIdx") + } + + } + return null + } + + /** + * extract the miniFAT sector index from the blockvect + * + * @param pos + * @param blockvect + * @param FAT + * @return + */ + private fun getMiniFAT(pos: Int, blockvect: List<*>, FAT: IntArray): List<*>? { + var miniFATContainer: Storage? = null + miniFATContainer = Storage() + miniFATContainer.blockType = Block.BIG + miniFATContainer.startBlock = pos + miniFATContainer.setStorageType(5) // set as root type to distinguish from regular storages + if (LEOFile.DEBUG) + Logger.logInfo("StorageTable.getMiniFAT() Initializing miniFAT Container.") + miniFATContainer.init(blockvect, FAT, true) + // miniFAT.setName("SBidx"); + // miniFAT index + return miniFATContainer.blockVect + } + + /** + * add a Storage to the directory array + * + * @param Storage storage to insert + * @param insertIdx -1 if add at end, otherwise insert at spot + */ + internal fun addStorage(rec: Storage, insertIdx: Int) { + val nm = rec.getName() + if (storageHash.get(nm) != null && nm != "") { + /* KSC: with 2012 code changes, this breaks output: + * if (LEOFile.DEBUG) + Logger.logInfo( + "INFO: StorageTable.addStorage() Dupe Storage Name: " + nm); + nm = nm + "|^" + dupct++; + rec.setName(nm); + Does not appear necessary +*/ + } + storageHash.put(rec.getName(), rec) + if (insertIdx == -1) + allDirectories.add(rec) + else { + allDirectories.add(insertIdx, rec) + for (i in allDirectories.indices) { // adjust prev, next, child ids if necessary + val s = allDirectories[i] as Storage + if (s.getChildStorageID() >= insertIdx) + s.setChildStorageID(s.getChildStorageID() + 1) + if (s.getPrevStorageID() >= insertIdx) + s.setPrevStorageID(s.getPrevStorageID() + 1) + if (s.getNextStorageID() >= insertIdx) + s.setNextStorageID(s.getNextStorageID() + 1) + + } + } + + if (false && LEOFile.DEBUG) { + Logger.logInfo( + /*"INFO: StorageTable.addStorage() Storage size: " + +*/ rec.getName() + + " Size: " + + rec.actualFileSize + + " Start Block: " + + rec.startBlock) + } + } + + /* remove a Storage + */ + internal fun removeStorage(st: Storage) { + this.storageHash.remove(st) + this.allDirectories.remove(st) + } + + /** + * init a Storage + */ + internal fun initStorage(rec: Storage, sourceblocks: List<*>?, idx: IntArray?, blocktype: Int) { + var name: String? = rec.getName() + val recsize = rec.actualFileSize + if (LEOFile.DEBUG) + Logger.logInfo( + "Initializing Storage: " + name + " Retrieving Data." + + " Size: " + + recsize + + " type: " + + blocktype + + " startidx: " + + rec.startBlock + if (rec.blockType == Block.SMALL) " MiniFAT" else "") + rec.blockType = blocktype + if ("Root Entry" == name) + // ksc: shouldn't! + return + + + if (rec.blockType == Block.BIG) + rec.init(sourceblocks, idx, false) + else if (rec.blockType == Block.SMALL) + rec.initFromMiniStream(sourceblocks, idx) + + if (LEOFile.DEBUG) { + if (rec.bytes != null) { + if (name == null) + name = "noname.dat" + if (name[0] == '') + name = name.substring(1) + if (name[0] == '') + name = name.substring(1) + // if(blocktype == Block.BIG) + StorageTable.writeitout(rec.blockVect, "$name.stor") + } + } + rec.idxs = null + } + + /** + * get the directory BLOCKS or Sectors + * contains the header info for all of the directories + */ + internal fun initDirectoryStream(blockvect: List<*>, FAT: IntArray): List<*>? { + //getDirectoryBlocks() + var directories: Storage? = null + directories = Storage() + val pstart = this.myheader!!.rootStartPos / BIGBLOCK.SIZE - 1 + directories.startBlock = pstart + directories.blockType = Block.BIG + directories.setStorageType(5) // set to root directory + directories.init(blockvect, FAT, true) // get additional directory stores, if any + // directories.setName("StorageTable"); + // if (LEOFile.DEBUG) + // StorageTable.writeitout(directories.getBlockVect(), + // "directoryStorage.dat"); + return directories.blockVect + + + } + + /** + * generate new RootStorage bytes from + * all of the Storage directories + */ + internal fun rebuildRootStore(): ByteArray { + /* + * Free (unused) directory entries are marked with Object Type 0x0 (unknown or unallocated). + * The entire directory entry should consist of all zeroes except for the child, right sibling, + * and left sibling pointers, which should be initialized to NOSTREAM (0xFFFFFFFF). + */ + while (allDirectories.size % 4 != 0) { // add "null" storages to ensure multiples of 4 (128*4=512==minimum size) + this.createStorage("", 0, -1) + } + val e = allDirectories.elements() + val bytebuff = ByteArray(allDirectories.size * DIRECTORY_SIZE) + var pos = 0 + while (e.hasMoreElements()) { + val s = e.nextElement() as Storage + val buff = this.getDirectoryHeaderBytes(s) + System.arraycopy(buff!!.array(), 0, bytebuff, pos, DIRECTORY_SIZE) + pos += DIRECTORY_SIZE + } + return bytebuff + } + + /** + * get the directory by name. throws StorageNotFoundException if not found. + */ + @Throws(StorageNotFoundException::class) + fun getDirectoryByName(name: String): Storage? { + if (storageHash.get(name) != null) + return storageHash.get(name) + throw StorageNotFoundException("Storage: $name not located") + } + + /** + * returns the Stream ID of the named directory, or -1 if not found + * + * @param name + * @return + * @throws StorageNotFoundException + */ + @Throws(StorageNotFoundException::class) + fun getDirectoryStreamID(name: String): Int { + for (i in allDirectories.indices) { + val s = allDirectories[i] as Storage + if (s.getName() == name) + return i + } + return -1 + } + + /** + * get the child directory (storage or stream) of the named storage + * + * @param name + * @throws StorageNotFoundException + * @return Storage or null + */ + @Throws(StorageNotFoundException::class) + fun getChild(name: String): Storage? { + val s = getDirectoryByName(name) + if (s != null) { + val child = s.getChildStorageID() + if (child > -1) + return allDirectories[child] as Storage + } + return null + } + + /** + * get the next directory (storage or stream) of the named storage + * + * @param name + * @throws StorageNotFoundException + * @return Storage or null + */ + @Throws(StorageNotFoundException::class) + fun getNext(name: String): Storage? { + val s = getDirectoryByName(name) + if (s != null) { + val next = s.getNextStorageID() + if (next > -1) + return allDirectories[next] as Storage + } + return null + } + + /** + * get the previous directory (storage or stream) of the named storage + * + * @param name + * @throws StorageNotFoundException + * @return Storage or null + */ + @Throws(StorageNotFoundException::class) + fun getPrevious(name: String): Storage? { + val s = getDirectoryByName(name) + if (s != null) { + val prev = s.getNextStorageID() + if (prev > -1) + return allDirectories[prev] as Storage + } + return null + } + + /** + * get the header record for a Directory + */ + internal fun getDirectoryHeaderBytes(thisStorage: Storage): ByteBuffer? { + // create a new byte buffer + var buff: ByteBuffer? = null + buff = thisStorage.headerData + // 20100304 KSC: don't reset anything, just return + return buff + } + + fun DEBUG() { + io.starter.toolkit.Logger.log("DIRECTORY CONTENTS:") + for (i in allDirectories.indices) { + val s = allDirectories[i] as Storage + val n = s.getName() + Logger.logInfo("Storage: " + n + " storageType: " + s.getStorageType() + " directoryColor:" + s.getDirectoryColor() + + " prevSID:" + s.getPrevStorageID() + " nextSID:" + s.getNextStorageID() + " childSID:" + s.getChildStorageID() + " sz:" + s.actualFileSize) + // special storages + if (n == "Root Entry") { + Logger.logInfo("Root Header: " + Arrays.toString(s.headerData.array())) + + /*********************************** + * // KSC: TESTING for XLS-97: + * !!! Set creation time and modified time on root storage to 0 + * int p = 100; + * s.getHeaderData().position(p); + * Long tsCreated= s.getHeaderData().getLong(); + * Long tsModified= s.getHeaderData().getLong(); + */ + if (s.myblocks != null) { + val zz = 0 + if (s.myblocks!![zz] is io.starter.formats.LEO.BIGBLOCK) + io.starter.toolkit.Logger.log("BLOCK 1:\t" + zz + "-" + Arrays.toString((s.myblocks!![zz] as io.starter.formats.LEO.BIGBLOCK).bytes)) + else + io.starter.toolkit.Logger.log("BLOCK 1:\t" + zz + "-" + Arrays.toString((s.myblocks!![zz] as io.starter.formats.LEO.SMALLBLOCK).bytes)) + } + } else if (n == "Workbook") { + //skip + } else if (n == "\u0001CompObj") { + val bytes = s.blockReader + val len = bytes.length + val rec = io.starter.formats.XLS.XLSRecord() // 4 bytes are header ... + rec.byteReader = bytes + rec.length = len + val slen = ByteTools.readInt(rec.getBytesAt(24, 4)) // actually position 28 + if (slen >= 0) { + val ss = String(rec.getBytesAt(28, slen)) // AnsiUserType= a display name of the linked object or embedded object. + io.starter.toolkit.Logger.log("\tOLE Object:$ss") + } + // AnsiClipboardFormat (variable) + // io.starter.toolkit.Logger.log("\t" + Arrays.toString(rec.getData())); + } else if (n.startsWith("000")) { // pivot cache + if (s.myblocks != null) { + for (zz in s.myblocks!!.indices) + if (s.myblocks!![zz] is io.starter.formats.LEO.BIGBLOCK) + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString((s.myblocks!![zz] as io.starter.formats.LEO.BIGBLOCK).bytes)) + else + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString((s.myblocks!![zz] as io.starter.formats.LEO.SMALLBLOCK).bytes)) + } + val bytes = s.blockReader + val len = bytes.length + var z = 0 + while (z <= len - 4) { + val headerbytes = bytes.getHeaderBytes(z) + val opcode = ByteTools.readShort(headerbytes[0].toInt(), headerbytes[1].toInt()) + val reclen = ByteTools.readShort(headerbytes[2].toInt(), headerbytes[3].toInt()).toInt() + val rec = XLSRecordFactory.getBiffRecord(opcode) + rec.byteReader = bytes + rec.offset = z + rec.length = reclen.toShort() + rec.init() + io.starter.toolkit.Logger.log("\t\t" + rec.toString()) + z += reclen + 4 + } + } else { + if (s.myblocks != null) { + for (zz in s.myblocks!!.indices) + if (s.myblocks!![zz] is io.starter.formats.LEO.BIGBLOCK) + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString((s.myblocks!![zz] as io.starter.formats.LEO.BIGBLOCK).bytes)) + else + io.starter.toolkit.Logger.log("\t" + zz + "-" + Arrays.toString((s.myblocks!![zz] as io.starter.formats.LEO.SMALLBLOCK).bytes)) + } + } + } + } + + companion object { + + /** + * serialVersionUID + */ + private const val serialVersionUID = 3399830613453524580L + val TABLE_SIZE = 0x200 + val DIRECTORY_SIZE = 0x80 + val BIGSTORAGE_SIZE = 4096 // default, should read from LEOHeader + + /** + * write out the directory bytes for debugging + */ + fun writeitout(blocks: List<*>?, name: String) { + try { + val fos = FileOutputStream(File(System.getProperty("user.dir") + "\\storages\\" + name)) + fos.write(LEOFile.getBytes(blocks)) + } catch (a: IOException) { + } + + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/Alignment.java b/src/main/java/io/starter/formats/OOXML/Alignment.java deleted file mode 100644 index 9ac22a6..0000000 --- a/src/main/java/io/starter/formats/OOXML/Alignment.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; - - -/** - * alignment (Alignment) OOMXL element - *

                  - * Formatting information pertaining to text alignment in cells. There are a variety of choices for how text is - * aligned both horizontally and vertically, as well as indentation settings, and so on. - *

                  - * parent: (styles.xml) xf, dxf - * children: none - */ -public class Alignment implements OOXMLElement { - - private static final long serialVersionUID = 995367747930839216L; - private HashMap attrs = null; - - public Alignment(HashMap attrs) { - this.attrs = attrs; - } - - public Alignment(Alignment a) { - this.attrs = a.attrs; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("alignment")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("alignment")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("alignment.parseOOXML: " + e.toString()); - } - Alignment a = new Alignment(attrs); - return a; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Alignment(this); - } - - /** - * @param type horizontal vertical - * @return - */ - public String getAlignment(String type) { - // attributes: - // horizontal: center, centerContinuous, fill, general, justify, left, right, distributed - // indent: int value - // justifyLastLine: bool - // readingOrder: 0=Context Dependent, 1=Left-to-Right, 2=Right-to-Left - // relativeIndent: # - // shrinkToFit: bool - // textRotation: degrees from 0-180 - // vertical: bottom, centered, distributed, justify, top - // wrapText - true/false - if (attrs != null) { - return attrs.get(type); - } - return null; - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Alignment.kt b/src/main/java/io/starter/formats/OOXML/Alignment.kt new file mode 100644 index 0000000..5432d38 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Alignment.kt @@ -0,0 +1,124 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap + + +/** + * alignment (Alignment) OOMXL element + * + * + * Formatting information pertaining to text alignment in cells. There are a variety of choices for how text is + * aligned both horizontally and vertically, as well as indentation settings, and so on. + * + * + * parent: (styles.xml) xf, dxf + * children: none + */ +class Alignment : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(a: Alignment) { + this.attrs = a.attrs + } + + override fun cloneElement(): OOXMLElement { + return Alignment(this) + } + + /** + * @param type horizontal vertical + * @return + */ + fun getAlignment(type: String): String? { + // attributes: + // horizontal: center, centerContinuous, fill, general, justify, left, right, distributed + // indent: int value + // justifyLastLine: bool + // readingOrder: 0=Context Dependent, 1=Left-to-Right, 2=Right-to-Left + // relativeIndent: # + // shrinkToFit: bool + // textRotation: degrees from 0-180 + // vertical: bottom, centered, distributed, justify, top + // wrapText - true/false + return if (attrs != null) { + attrs!![type] + } else null + } + + companion object { + + private val serialVersionUID = 995367747930839216L + + fun parseOOXML(xpp: XmlPullParser): OOXMLElement { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "alignment") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "alignment") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("alignment.parseOOXML: $e") + } + + return Alignment(attrs) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/AutoFilter.java b/src/main/java/io/starter/formats/OOXML/AutoFilter.java deleted file mode 100644 index 51e7026..0000000 --- a/src/main/java/io/starter/formats/OOXML/AutoFilter.java +++ /dev/null @@ -1,684 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; - -/** - * autoFilter (AutoFilter Settings) - * AutoFilter temporarily hides rows based on a filter criteria, which is applied column by column to a table of data - * in the worksheet. This collection expresses AutoFilter settings. - *

                  - * parent: worksheet, table, filter, customSheetView - * children: filterColumn (0+), sortState - * attributes: ref - */ -// TODO: finish sortState -// TODO: finish filterColumn children filters->filter, dataGroupItem -public class AutoFilter implements OOXMLElement { - - private static final long serialVersionUID = 7111401348177004218L; - private String ref = null; - private ArrayList filterColumns = null; - - public AutoFilter(String ref, ArrayList f) { - this.ref = ref; - this.filterColumns = f; - } - - public AutoFilter(AutoFilter a) { - this.ref = a.ref; - this.filterColumns = a.filterColumns; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp) { - String ref = null; - ArrayList f = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("autoFilter")) { // get ref attribute - if (xpp.getAttributeCount() == 1) ref = xpp.getAttributeValue(0); - } else if (tnm.equals("sortState")) { - } else if (tnm.equals("filterColumn")) { - if (f == null) f = new ArrayList(); - f.add(FilterColumn.parseOOXML(xpp)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("autoFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("autoFilter.parseOOXML: " + e.toString()); - } - AutoFilter a = new AutoFilter(ref, f); - return a; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (filterColumns != null) { - for (int i = 0; i < filterColumns.size(); i++) - ooxml.append(filterColumns.get(i).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new AutoFilter(this); - } -} - -/** - * filterColumn (AutoFilter Column) - * The filterColumn collection identifies a particular column in the AutoFilter range and specifies filter information - * that has been applied to this column. If a column in the AutoFilter range has no criteria specified, then there is - * no corresponding filterColumn collection expressed for that column - *

                  - * parent: autoFilter - * children: CHOICE OF: colorFilter, customFilters, dynamicFilter, filters, iconFilter, top10 - * attributes: colId REQ, hiddenButton, showButton - */ -class FilterColumn implements OOXMLElement { - - private static final long serialVersionUID = 5005589034415840928L; - private HashMap attrs = null; - private Object filter = null; // CHOICE of filter - - public FilterColumn(HashMap attrs, Object filter) { - this.attrs = attrs; - this.filter = filter; - } - - public FilterColumn(FilterColumn f) { - this.attrs = f.attrs; - this.filter = f.filter; - } - - - public static FilterColumn parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - Object filter = null; // CHOICE of filter - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("filterColumn")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("colorFilter")) { - filter = ColorFilter.parseOOXML(xpp); - } else if (tnm.equals("customFilters")) { - } else if (tnm.equals("dynamicFilter")) { - } else if (tnm.equals("filters")) { - } else if (tnm.equals("iconFilter")) { - } else if (tnm.equals("top10")) { - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("filterColumn")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("filterColumn.parseOOXML: " + e.toString()); - } - FilterColumn f = new FilterColumn(attrs, filter); - return f; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (filter instanceof ColorFilter) ooxml.append(((ColorFilter) filter).getOOXML()); - if (filter instanceof CustomFilters) ooxml.append(((CustomFilters) filter).getOOXML()); - if (filter instanceof DynamicFilter) ooxml.append(((DynamicFilter) filter).getOOXML()); - if (filter instanceof Filters) ooxml.append(((Filters) filter).getOOXML()); - if (filter instanceof IconFilter) ooxml.append(((IconFilter) filter).getOOXML()); - if (filter instanceof Top10) ooxml.append(((Top10) filter).getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FilterColumn(this); - } -} - -/** - * colorFilter (Color Filter Criteria) - * This element specifies the color to filter by and whether to use the cell's fill or font color in the filter criteria. If - * the cell's font or fill color does not match the color specified in the criteria, the rows corresponding to those cells - * are hidden from view. - *

                  - * parent: filterColumn - * children: none - */ -class ColorFilter implements OOXMLElement { - - private static final long serialVersionUID = 7077951504723033275L; - private HashMap attrs = null; - - public ColorFilter(HashMap attrs) { - this.attrs = attrs; - } - - public ColorFilter(ColorFilter c) { - this.attrs = c.attrs; - } - - public static ColorFilter parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("colorFilter")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("colorFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("colorFilter.parseOOXML: " + e.toString()); - } - ColorFilter oe = new ColorFilter(attrs); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ColorFilter(this); - } -} - -/** - * dynamicFilter (Dynamic Filter) - * This collection specifies dynamic filter criteria. These criteria are considered dynamic because they can change, - * either with the data itself (e.g., "above average") or with the current system date (e.g., show values for "today"). - * For any cells whose values do not meet the specified criteria, the corresponding rows shall be hidden from view - * when the filter is applied. - *

                  - * parent: filterColumn - * children: none - */ -class DynamicFilter implements OOXMLElement { - - private static final long serialVersionUID = -473171074711686551L; - private HashMap attrs = null; - - public DynamicFilter(HashMap attrs) { - this.attrs = attrs; - } - - public DynamicFilter(DynamicFilter d) { - this.attrs = d.attrs; - } - - - public static DynamicFilter parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("dynamicFilter")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("dynamicFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("dynamicFilter.parseOOXML: " + e.toString()); - } - DynamicFilter d = new DynamicFilter(attrs); - return d; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new DynamicFilter(this); - } -} - -/** - * iconFilter (Icon Filter) - *

                  - * This element specifies the icon set and particular icon within that set to filter by. For any cells whose icon does - * not match the specified criteria, the corresponding rows shall be hidden from view when the filter is applied. - *

                  - * parent: filterColumn - * children: none - */ -class IconFilter implements OOXMLElement { - - private static final long serialVersionUID = -5897037678209125965L; - private HashMap attrs = null; - - public IconFilter(HashMap attrs) { - this.attrs = attrs; - } - - public IconFilter(IconFilter i) { - this.attrs = i.attrs; - } - - - public static IconFilter parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("iconFilter")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("iconFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("iconFilter.parseOOXML: " + e.toString()); - } - IconFilter i = new IconFilter(attrs); - return i; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new IconFilter(this); - } -} - -/** - * customFilters (Custom Filters) - * When there is more than one custom filter criteria to apply (an 'and' or 'or' joining two criteria), then this - * element groups the customFilter elements together. - */ -class CustomFilters implements OOXMLElement { - - private static final long serialVersionUID = -2491942158519963335L; - private boolean and = false; - private CustomFilter[] custfilter = null; - - public CustomFilters(boolean and, CustomFilter[] custfilter) { - this.and = and; - this.custfilter = custfilter; - } - - public CustomFilters(CustomFilters c) { - this.and = c.and; - this.custfilter = c.custfilter; - } - - - public static CustomFilters parseOOXML(XmlPullParser xpp) { - boolean and = false; - CustomFilter[] custfilter = new CustomFilter[2]; - int idx = 0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("customFilters")) { // get attributes - if (xpp.getAttributeCount() == 1) and = (xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("customFilter")) { // 1-2 - custfilter[idx++] = CustomFilter.parseOOXML(xpp); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("customFilters")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("customFilters.parseOOXML: " + e.toString()); - } - CustomFilters c = new CustomFilters(and, custfilter); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (custfilter != null) { // shouln't be! - if (custfilter[0] != null)// shouldn't be! - ooxml.append(custfilter[0].getOOXML()); - if (custfilter[1] != null) ooxml.append(custfilter[1].getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CustomFilters(this); - } -} - -/** - * customFilter (Custom Filter Criteria) - * A custom AutoFilter specifies an operator and a value. There can be at most two customFilters specified, and in - * that case the parent element specifies whether the two conditions are joined by 'and' or 'or'. For any cells - * whose values do not meet the specified criteria, the corresponding rows shall be hidden from view when the - * filter is applied. - *

                  - * parent: customFilters - * children: none - */ -class CustomFilter implements OOXMLElement { - - private static final long serialVersionUID = 7995078604042667255L; - private HashMap attrs = null; - - public CustomFilter(HashMap attrs) { - this.attrs = attrs; - } - - public CustomFilter(CustomFilter c) { - this.attrs = c.attrs; - } - - public static CustomFilter parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("customFilter")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("customFilter")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("customFilter.parseOOXML: " + e.toString()); - } - CustomFilter c = new CustomFilter(attrs); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CustomFilter(this); - } -} - -/** - * top10 (Top 10) - * This element specifies the top N (percent or number of items) to filter by. - *

                  - * parent: filterColumn - * children: none - */ -class Top10 implements OOXMLElement { - - private static final long serialVersionUID = 77735498689922082L; - private HashMap attrs = null; - - public Top10(HashMap attrs) { - this.attrs = attrs; - } - - public Top10(Top10 t) { - this.attrs = t.attrs; - } - - - public static Top10 parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("top10")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("top10")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("top10.parseOOXML: " + e.toString()); - } - Top10 t = new Top10(attrs); - return t; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Top10(this); - } -} - - -/** - * filters (Filter Criteria) - * When multiple values are chosen to filter by, or when a group of date values are chosen to filter by, this element - * groups those criteria together. - *

                  - * parent: filterColumn - * children: filter (0+), dateGroupItem (0+) - */ -// TODO: finish children -class Filters implements OOXMLElement { - - private static final long serialVersionUID = 921424089049938924L; - private HashMap attrs = null; - - public Filters(HashMap attrs) { - this.attrs = attrs; - } - - public Filters(Filters f) { - this.attrs = f.attrs; - } - - public static Filters parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("filters")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("filter")) { - } else if (tnm.equals("dateGroupItem")) { - //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("filters")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("filters.parseOOXML: " + e.toString()); - } - Filters oe = new Filters(attrs); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - // TODO: one or more filter elements - // TODO: one or more dateGroupItem elements - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Filters(this); - } -} - - diff --git a/src/main/java/io/starter/formats/OOXML/AutoFilter.kt b/src/main/java/io/starter/formats/OOXML/AutoFilter.kt new file mode 100644 index 0000000..a4c57cc --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/AutoFilter.kt @@ -0,0 +1,730 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.HashMap + +/** + * autoFilter (AutoFilter Settings) + * AutoFilter temporarily hides rows based on a filter criteria, which is applied column by column to a table of data + * in the worksheet. This collection expresses AutoFilter settings. + * + * + * parent: worksheet, table, filter, customSheetView + * children: filterColumn (0+), sortState + * attributes: ref + */ +// TODO: finish sortState +// TODO: finish filterColumn children filters->filter, dataGroupItem +class AutoFilter : OOXMLElement { + private var ref: String? = null + private var filterColumns: ArrayList? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (filterColumns != null) { + for (i in filterColumns!!.indices) + ooxml.append(filterColumns!![i].ooxml) + } + ooxml.append("") + return ooxml.toString() + } + + constructor(ref: String, f: ArrayList) { + this.ref = ref + this.filterColumns = f + } + + constructor(a: AutoFilter) { + this.ref = a.ref + this.filterColumns = a.filterColumns + } + + override fun cloneElement(): OOXMLElement { + return AutoFilter(this) + } + + companion object { + + private val serialVersionUID = 7111401348177004218L + + + fun parseOOXML(xpp: XmlPullParser): OOXMLElement { + var ref: String? = null + var f: ArrayList? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "autoFilter") { // get ref attribute + if (xpp.attributeCount == 1) ref = xpp.getAttributeValue(0) + } else if (tnm == "sortState") { + } else if (tnm == "filterColumn") { + if (f == null) f = ArrayList() + f.add(FilterColumn.parseOOXML(xpp)) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "autoFilter") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("autoFilter.parseOOXML: $e") + } + + return AutoFilter(ref, f) + } + } +} + +/** + * filterColumn (AutoFilter Column) + * The filterColumn collection identifies a particular column in the AutoFilter range and specifies filter information + * that has been applied to this column. If a column in the AutoFilter range has no criteria specified, then there is + * no corresponding filterColumn collection expressed for that column + * + * + * parent: autoFilter + * children: CHOICE OF: colorFilter, customFilters, dynamicFilter, filters, iconFilter, top10 + * attributes: colId REQ, hiddenButton, showButton + */ +internal class FilterColumn : OOXMLElement { + private var attrs: HashMap? = null + private var filter: Any? = null // CHOICE of filter + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (filter is ColorFilter) ooxml.append((filter as ColorFilter).ooxml) + if (filter is CustomFilters) ooxml.append((filter as CustomFilters).ooxml) + if (filter is DynamicFilter) ooxml.append((filter as DynamicFilter).ooxml) + if (filter is Filters) ooxml.append((filter as Filters).ooxml) + if (filter is IconFilter) ooxml.append((filter as IconFilter).ooxml) + if (filter is Top10) ooxml.append((filter as Top10).ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap, filter: Any) { + this.attrs = attrs + this.filter = filter + } + + constructor(f: FilterColumn) { + this.attrs = f.attrs + this.filter = f.filter + } + + override fun cloneElement(): OOXMLElement { + return FilterColumn(this) + } + + companion object { + + private val serialVersionUID = 5005589034415840928L + + + fun parseOOXML(xpp: XmlPullParser): FilterColumn { + val attrs = HashMap() + var filter: Any? = null // CHOICE of filter + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "filterColumn") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "colorFilter") { + filter = ColorFilter.parseOOXML(xpp) + } else if (tnm == "customFilters") { + } else if (tnm == "dynamicFilter") { + } else if (tnm == "filters") { + } else if (tnm == "iconFilter") { + } else if (tnm == "top10") { + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "filterColumn") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("filterColumn.parseOOXML: $e") + } + + return FilterColumn(attrs, filter) + } + } +} + +/** + * colorFilter (Color Filter Criteria) + * This element specifies the color to filter by and whether to use the cell's fill or font color in the filter criteria. If + * the cell's font or fill color does not match the color specified in the criteria, the rows corresponding to those cells + * are hidden from view. + * + * + * parent: filterColumn + * children: none + */ +internal class ColorFilter : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(c: ColorFilter) { + this.attrs = c.attrs + } + + override fun cloneElement(): OOXMLElement { + return ColorFilter(this) + } + + companion object { + + private val serialVersionUID = 7077951504723033275L + + fun parseOOXML(xpp: XmlPullParser): ColorFilter { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "colorFilter") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "colorFilter") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("colorFilter.parseOOXML: $e") + } + + return ColorFilter(attrs) + } + } +} + +/** + * dynamicFilter (Dynamic Filter) + * This collection specifies dynamic filter criteria. These criteria are considered dynamic because they can change, + * either with the data itself (e.g., "above average") or with the current system date (e.g., show values for "today"). + * For any cells whose values do not meet the specified criteria, the corresponding rows shall be hidden from view + * when the filter is applied. + * + * + * parent: filterColumn + * children: none + */ +internal class DynamicFilter : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(d: DynamicFilter) { + this.attrs = d.attrs + } + + override fun cloneElement(): OOXMLElement { + return DynamicFilter(this) + } + + companion object { + + private val serialVersionUID = -473171074711686551L + + + fun parseOOXML(xpp: XmlPullParser): DynamicFilter { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "dynamicFilter") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "dynamicFilter") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("dynamicFilter.parseOOXML: $e") + } + + return DynamicFilter(attrs) + } + } +} + +/** + * iconFilter (Icon Filter) + * + * + * This element specifies the icon set and particular icon within that set to filter by. For any cells whose icon does + * not match the specified criteria, the corresponding rows shall be hidden from view when the filter is applied. + * + * + * parent: filterColumn + * children: none + */ +internal class IconFilter : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(i: IconFilter) { + this.attrs = i.attrs + } + + override fun cloneElement(): OOXMLElement { + return IconFilter(this) + } + + companion object { + + private val serialVersionUID = -5897037678209125965L + + + fun parseOOXML(xpp: XmlPullParser): IconFilter { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "iconFilter") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "iconFilter") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("iconFilter.parseOOXML: $e") + } + + return IconFilter(attrs) + } + } +} + +/** + * customFilters (Custom Filters) + * When there is more than one custom filter criteria to apply (an 'and' or 'or' joining two criteria), then this + * element groups the customFilter elements together. + */ +internal class CustomFilters : OOXMLElement { + private var and = false + private var custfilter: Array? = null + + override// shouln't be! + // shouldn't be! + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (custfilter != null) { + if (custfilter!![0] != null) + ooxml.append(custfilter!![0].ooxml) + if (custfilter!![1] != null) ooxml.append(custfilter!![1].ooxml) + } + ooxml.append("") + return ooxml.toString() + } + + constructor(and: Boolean, custfilter: Array) { + this.and = and + this.custfilter = custfilter + } + + constructor(c: CustomFilters) { + this.and = c.and + this.custfilter = c.custfilter + } + + override fun cloneElement(): OOXMLElement { + return CustomFilters(this) + } + + companion object { + + private val serialVersionUID = -2491942158519963335L + + + fun parseOOXML(xpp: XmlPullParser): CustomFilters { + var and = false + val custfilter = arrayOfNulls(2) + var idx = 0 + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "customFilters") { // get attributes + if (xpp.attributeCount == 1) and = xpp.getAttributeValue(0) == "1" + } else if (tnm == "customFilter") { // 1-2 + custfilter[idx++] = CustomFilter.parseOOXML(xpp) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "customFilters") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("customFilters.parseOOXML: $e") + } + + return CustomFilters(and, custfilter) + } + } +} + +/** + * customFilter (Custom Filter Criteria) + * A custom AutoFilter specifies an operator and a value. There can be at most two customFilters specified, and in + * that case the parent element specifies whether the two conditions are joined by 'and' or 'or'. For any cells + * whose values do not meet the specified criteria, the corresponding rows shall be hidden from view when the + * filter is applied. + * + * + * parent: customFilters + * children: none + */ +internal class CustomFilter : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(c: CustomFilter) { + this.attrs = c.attrs + } + + override fun cloneElement(): OOXMLElement { + return CustomFilter(this) + } + + companion object { + + private val serialVersionUID = 7995078604042667255L + + fun parseOOXML(xpp: XmlPullParser): CustomFilter { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "customFilter") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "customFilter") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("customFilter.parseOOXML: $e") + } + + return CustomFilter(attrs) + } + } +} + +/** + * top10 (Top 10) + * This element specifies the top N (percent or number of items) to filter by. + * + * + * parent: filterColumn + * children: none + */ +internal class Top10 : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(t: Top10) { + this.attrs = t.attrs + } + + override fun cloneElement(): OOXMLElement { + return Top10(this) + } + + companion object { + + private val serialVersionUID = 77735498689922082L + + + fun parseOOXML(xpp: XmlPullParser): Top10 { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "top10") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "top10") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("top10.parseOOXML: $e") + } + + return Top10(attrs) + } + } +} + + +/** + * filters (Filter Criteria) + * When multiple values are chosen to filter by, or when a group of date values are chosen to filter by, this element + * groups those criteria together. + * + * + * parent: filterColumn + * children: filter (0+), dateGroupItem (0+) + */ +// TODO: finish children +internal class Filters : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + // TODO: one or more filter elements + // TODO: one or more dateGroupItem elements + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(f: Filters) { + this.attrs = f.attrs + } + + override fun cloneElement(): OOXMLElement { + return Filters(this) + } + + companion object { + + private val serialVersionUID = 921424089049938924L + + fun parseOOXML(xpp: XmlPullParser): Filters { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "filters") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "filter") { + } else if (tnm == "dateGroupItem") { + //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "filters") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("filters.parseOOXML: $e") + } + + return Filters(attrs) + } + } +} + + diff --git a/src/main/java/io/starter/formats/OOXML/AvLst.java b/src/main/java/io/starter/formats/OOXML/AvLst.java deleted file mode 100644 index 6311bbf..0000000 --- a/src/main/java/io/starter/formats/OOXML/AvLst.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.Stack; - - -/** - * avLst (List of Shape Adjust Values) - *

                  - * This element specifies the adjust values that will be applied to the specified shape. An adjust value is simply a - * guide that has a value based formula specified. That is, no calculation takes place for an adjust value guide. - * Instead, this guide specifies a parameter value that is used for calculations within the shape guides. - *

                  - * parent: prstGeom, prstTxWarp, custGeom - * children: gd (shape guide) (0 or more) - */ -public class AvLst implements OOXMLElement { - - private static final long serialVersionUID = 4823524943145191780L; - private ArrayList gds = null; - - public AvLst() { - - } - - public AvLst(ArrayList gds) { - this.gds = gds; - } - - public AvLst(AvLst av) { - this.gds = av.gds; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - ArrayList gds = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gd")) { - lastTag.push(tnm); - if (gds == null) gds = new ArrayList(); - gds.add((Gd) Gd.parseOOXML(xpp, lastTag)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("avLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("avLst.parseOOXML: " + e.toString()); - } - AvLst av = new AvLst(gds); - return av; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (gds != null) { - for (int i = 0; i < gds.size(); i++) - ooxml.append(((Gd) gds.get(i)).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new AvLst(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/AvLst.kt b/src/main/java/io/starter/formats/OOXML/AvLst.kt new file mode 100644 index 0000000..b0de64f --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/AvLst.kt @@ -0,0 +1,108 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.Stack + + +/** + * avLst (List of Shape Adjust Values) + * + * + * This element specifies the adjust values that will be applied to the specified shape. An adjust value is simply a + * guide that has a value based formula specified. That is, no calculation takes place for an adjust value guide. + * Instead, this guide specifies a parameter value that is used for calculations within the shape guides. + * + * + * parent: prstGeom, prstTxWarp, custGeom + * children: gd (shape guide) (0 or more) + */ +class AvLst : OOXMLElement { + private var gds: ArrayList<*>? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (gds != null) { + for (i in gds!!.indices) + ooxml.append((gds!![i] as Gd).ooxml) + } + ooxml.append("") + return ooxml.toString() + } + + constructor() { + + } + + constructor(gds: ArrayList<*>) { + this.gds = gds + } + + constructor(av: AvLst) { + this.gds = av.gds + } + + override fun cloneElement(): OOXMLElement { + return AvLst(this) + } + + companion object { + + private val serialVersionUID = 4823524943145191780L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + var gds: ArrayList? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "gd") { + lastTag.push(tnm) + if (gds == null) gds = ArrayList() + gds.add(Gd.parseOOXML(xpp, lastTag) as Gd) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "avLst") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("avLst.parseOOXML: $e") + } + + return AvLst(gds) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/BodyPr.java b/src/main/java/io/starter/formats/OOXML/BodyPr.java deleted file mode 100644 index c03ec45..0000000 --- a/src/main/java/io/starter/formats/OOXML/BodyPr.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * bodyPr (Body Properties) - *

                  - * This element defines the body properties for the text body within a shape - *

                  - * parents: many, including txBody and txPr - * attributes: many - * children: flatTx, noAutoFit, normAutoFit, prstTxWarp, scene3d, sp3d, spAutoFit - */ -// TODO: Handle CHILDREN *********************************** -public class BodyPr implements OOXMLElement { - - private static final long serialVersionUID = 3693893834015788452L; - private HashMap attrs = new HashMap(); - private PrstTxWarp txwarp = null; - private boolean spAutoFit = false; - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - PrstTxWarp txwarp = null; - boolean spAutoFit = false; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bodyPr")) { // body text properties - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - // TODO: handle flatTx element - // TODO: handle scene3d element - // TODO: handle sp3d element - } else if (tnm.equals("spAutoFit")) { // TODO: should be a choice of autofit options - spAutoFit = true; // no attributes or children - } else if (tnm.equals("prstTxWarp")) { - lastTag.push(tnm); - txwarp = PrstTxWarp.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("bodyPr")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("bodyPr.parseOOXML: " + e.toString()); - } - BodyPr bpr = new BodyPr(attrs, txwarp, spAutoFit); - return bpr; - } - - public BodyPr() { - } - - public BodyPr(HashMap attrs, PrstTxWarp txwarp, boolean spAutoFit) { - this.attrs = attrs; - this.txwarp = txwarp; - this.spAutoFit = spAutoFit; - } - - public BodyPr(BodyPr tpr) { - this.attrs = tpr.attrs; - this.txwarp = tpr.txwarp; - this.spAutoFit = tpr.spAutoFit; - } - - /** - * defines the body properties for the text body within a shape - * - * @param hrot - * @param vert Determines if the text within the given text body should be displayed vertically. If this attribute is omitted, then a value of horz, or no vertical text is implied. - * vert Determines if all of the text is vertical orientation (each line is 90 degrees rotated clockwise, so it goes from top to bottom; each next line is to the left from the previous one). - * vert270 Determines if all of the text is vertical orientation (each line is 270 degrees rotated clockwise, so it goes from bottom to top; each next line is to the right from the previous one). - * wordArtVert Determines if all of the text is vertical ("one letter on top of another"). - * wordArtVertRtl Specifies that vertical WordArt should be shown from right to left rather than left to right. - * eaVert A special version of vertical text, where some fonts are displayed as if rotated by 90 degrees while some fonts (mostly East Asian) are displayed vertical. - */ - public BodyPr(int hrot, String vert) { - attrs = new HashMap(); - attrs.put("rot", String.valueOf(hrot)); - if (vert != null) - attrs.put("vert", vert); - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (txwarp != null) ooxml.append(txwarp.getOOXML()); - if (spAutoFit) ooxml.append(""); // TODO: Should be a choice of autofit options - // scene3d - // text3d choice - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BodyPr(this); - } - -} - - -/** - * prstTxWarp (Preset Text Warp) - *

                  - * This element specifies when a preset geometric shape should be used to transform a piece of text. This - * operation is known formally as a text warp. The generating application should be able to render all preset - * geometries enumerated in the ST_TextShapeType list. - *

                  - * parent: bodyPr - * children: avLst - */ -class PrstTxWarp implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2627323317407321668L; - private String prst = null; - private AvLst av = null; - - public PrstTxWarp() { - } - - public PrstTxWarp(String prst, AvLst av) { - this.prst = prst; - this.av = av; - } - - public PrstTxWarp(PrstTxWarp p) { - this.prst = p.prst; - this.av = p.av; - } - - public static PrstTxWarp parseOOXML(XmlPullParser xpp, Stack lastTag) { - String prst = null; - AvLst av = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstTxWarp")) { // prst is only attribute and is required - prst = xpp.getAttributeValue(0); - } else if (tnm.equals("avLst")) { - lastTag.push(tnm); - av = (AvLst) AvLst.parseOOXML(xpp, lastTag); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("prstTxWarp")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("prstTxWarp.parseOOXML: " + e.toString()); - } - PrstTxWarp p = new PrstTxWarp(prst, av); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (av != null) ooxml.append(av.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PrstTxWarp(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/BodyPr.kt b/src/main/java/io/starter/formats/OOXML/BodyPr.kt new file mode 100644 index 0000000..19ad615 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/BodyPr.kt @@ -0,0 +1,230 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * bodyPr (Body Properties) + * + * + * This element defines the body properties for the text body within a shape + * + * + * parents: many, including txBody and txPr + * attributes: many + * children: flatTx, noAutoFit, normAutoFit, prstTxWarp, scene3d, sp3d, spAutoFit + */ +// TODO: Handle CHILDREN *********************************** +class BodyPr : OOXMLElement { + private var attrs = HashMap() + private var txwarp: PrstTxWarp? = null + private var spAutoFit = false + + override// attributes + // TODO: Should be a choice of autofit options + // scene3d + // text3d choice + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (txwarp != null) ooxml.append(txwarp!!.ooxml) + if (spAutoFit) ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + constructor() {} + + constructor(attrs: HashMap, txwarp: PrstTxWarp, spAutoFit: Boolean) { + this.attrs = attrs + this.txwarp = txwarp + this.spAutoFit = spAutoFit + } + + constructor(tpr: BodyPr) { + this.attrs = tpr.attrs + this.txwarp = tpr.txwarp + this.spAutoFit = tpr.spAutoFit + } + + /** + * defines the body properties for the text body within a shape + * + * @param hrot + * @param vert Determines if the text within the given text body should be displayed vertically. If this attribute is omitted, then a value of horz, or no vertical text is implied. + * vert Determines if all of the text is vertical orientation (each line is 90 degrees rotated clockwise, so it goes from top to bottom; each next line is to the left from the previous one). + * vert270 Determines if all of the text is vertical orientation (each line is 270 degrees rotated clockwise, so it goes from bottom to top; each next line is to the right from the previous one). + * wordArtVert Determines if all of the text is vertical ("one letter on top of another"). + * wordArtVertRtl Specifies that vertical WordArt should be shown from right to left rather than left to right. + * eaVert A special version of vertical text, where some fonts are displayed as if rotated by 90 degrees while some fonts (mostly East Asian) are displayed vertical. + */ + constructor(hrot: Int, vert: String?) { + attrs = HashMap() + attrs["rot"] = hrot.toString() + if (vert != null) + attrs["vert"] = vert + } + + override fun cloneElement(): OOXMLElement { + return BodyPr(this) + } + + companion object { + + private val serialVersionUID = 3693893834015788452L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + val attrs = HashMap() + var txwarp: PrstTxWarp? = null + var spAutoFit = false + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "bodyPr") { // body text properties + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + // TODO: handle flatTx element + // TODO: handle scene3d element + // TODO: handle sp3d element + } else if (tnm == "spAutoFit") { // TODO: should be a choice of autofit options + spAutoFit = true // no attributes or children + } else if (tnm == "prstTxWarp") { + lastTag.push(tnm) + txwarp = PrstTxWarp.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "bodyPr") { + lastTag.pop() // pop this tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("bodyPr.parseOOXML: $e") + } + + return BodyPr(attrs, txwarp, spAutoFit) + } + } + +} + + +/** + * prstTxWarp (Preset Text Warp) + * + * + * This element specifies when a preset geometric shape should be used to transform a piece of text. This + * operation is known formally as a text warp. The generating application should be able to render all preset + * geometries enumerated in the ST_TextShapeType list. + * + * + * parent: bodyPr + * children: avLst + */ +internal class PrstTxWarp : OOXMLElement { + private var prst: String? = null + private var av: AvLst? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (av != null) ooxml.append(av!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor() {} + + constructor(prst: String, av: AvLst) { + this.prst = prst + this.av = av + } + + constructor(p: PrstTxWarp) { + this.prst = p.prst + this.av = p.av + } + + override fun cloneElement(): OOXMLElement { + return PrstTxWarp(this) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -2627323317407321668L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): PrstTxWarp { + var prst: String? = null + var av: AvLst? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "prstTxWarp") { // prst is only attribute and is required + prst = xpp.getAttributeValue(0) + } else if (tnm == "avLst") { + lastTag.push(tnm) + av = AvLst.parseOOXML(xpp, lastTag) as AvLst + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "prstTxWarp") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("prstTxWarp.parseOOXML: $e") + } + + return PrstTxWarp(prst, av) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Border.java b/src/main/java/io/starter/formats/OOXML/Border.java deleted file mode 100644 index 46cf5ba..0000000 --- a/src/main/java/io/starter/formats/OOXML/Border.java +++ /dev/null @@ -1,526 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - - -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Xf; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; - -/** - * border OOXML element - *

                  - * parent: styleSheet/borders element in styles.xml - * children: SEQ: left, right, top, bottom, diagonal, vertical, horizontal - */ -public class Border implements OOXMLElement { - - private static final long serialVersionUID = 4340789910636828223L; - private HashMap attrs = null; - private HashMap borderElements = null; - - public Border() { - } - - /** - * @param styles int array {top, left, top, bottom, right, [diagonal]} - * @param colors int array {top, left, top, bottom, right, [diagonal]} - */ - public Border(HashMap attrs, HashMap borderElements) { - this.attrs = attrs; - this.borderElements = borderElements; - } - - public Border(Border b) { - this.attrs = b.attrs; - this.borderElements = b.borderElements; - } - - /** - * set borders - * - * @param bk - * @param styles t, l, b, r - * @param colors - */ - public Border(WorkBookHandle bk, int[] styles, int[] colors) { - this.borderElements = new HashMap(); - String[] borderElements = {"top", "left", "bottom", "right"}; - for (int i = 0; i < 4; i++) { - if (styles[i] > 0) { - String style = OOXMLConstants.borderStyle[styles[i]]; - this.borderElements.put(borderElements[i], new BorderElement(style, colors[i], borderElements[i], bk)); - } - } - // diagonal? vertical? horizontal? - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - HashMap borderElements = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("border")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("left")) { - borderElements.put("left", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("right")) { - borderElements.put("right", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("top")) { - borderElements.put("top", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("bottom")) { - borderElements.put("bottom", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("diagonal")) { - borderElements.put("diagonal", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("vertical")) { - borderElements.put("vertical", BorderElement.parseOOXML(xpp, bk)); - } else if (tnm.equals("horizontal")) { - borderElements.put("horizontal", BorderElement.parseOOXML(xpp, bk)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("border")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("border.parseOOXML: " + e.toString()); - } - Border b = new Border(attrs, borderElements); - return b; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (borderElements.get("left") != null) ooxml.append(borderElements.get("left").getOOXML()); - if (borderElements.get("right") != null) ooxml.append(borderElements.get("right").getOOXML()); - if (borderElements.get("top") != null) ooxml.append(borderElements.get("top").getOOXML()); - if (borderElements.get("bottom") != null) ooxml.append(borderElements.get("bottom").getOOXML()); - if (borderElements.get("diagonal") != null) ooxml.append(borderElements.get("diagonal").getOOXML()); - if (borderElements.get("vertical") != null) ooxml.append(borderElements.get("vertical").getOOXML()); - if (borderElements.get("horizontal") != null) ooxml.append(borderElements.get("horizontal").getOOXML()); - - ooxml.append(""); - return ooxml.toString(); - } - - /** - * returns an array representing the border sizes - *
                  top, left, bottom, right, diag - * - * @return int[5] representing border sizes - */ - public int[] getBorderSizes() { - int[] sizes = new int[5]; - if (borderElements.get("top") != null) sizes[0] = borderElements.get("top").getBorderSize(); - if (borderElements.get("left") != null) sizes[1] = borderElements.get("left").getBorderSize(); - if (borderElements.get("bottom") != null) sizes[2] = borderElements.get("bottom").getBorderSize(); - if (borderElements.get("right") != null) sizes[3] = borderElements.get("right").getBorderSize(); - if (borderElements.get("diagonal") != null) sizes[4] = borderElements.get("diagonal").getBorderSize(); - return sizes; - } - - /** - * returns an array representing the border styles - * translated from OOXML String value to 2003-int value - *
                  top, left, bottom, right, diag - * - * @return int[5] - */ - public int[] getBorderStyles() { - int[] styles = new int[5]; - if (borderElements.get("top") != null) styles[0] = borderElements.get("top").getBorderStyle(); - if (borderElements.get("left") != null) styles[1] = borderElements.get("left").getBorderStyle(); - if (borderElements.get("bottom") != null) styles[2] = borderElements.get("bottom").getBorderStyle(); - if (borderElements.get("right") != null) styles[3] = borderElements.get("right").getBorderStyle(); - if (borderElements.get("diagonal") != null) styles[4] = borderElements.get("diagonal").getBorderStyle(); - return styles; - } - - /** - * returns an array representing the border colors as rgb string - *
                  top, left, bottom, right, diag - * - * @return String[6] - */ - public String[] getBorderColors() { - try { - String[] clrs = new String[5]; - if (borderElements.get("top") != null) clrs[0] = borderElements.get("top").getBorderColor(); - if (borderElements.get("left") != null) clrs[1] = borderElements.get("left").getBorderColor(); - if (borderElements.get("bottom") != null) clrs[2] = borderElements.get("bottom").getBorderColor(); - if (borderElements.get("right") != null) clrs[3] = borderElements.get("right").getBorderColor(); - if (borderElements.get("diagonal") != null) clrs[4] = borderElements.get("diagonal").getBorderColor(); - return clrs; - } catch (NullPointerException e) { - return new String[5]; - } - } - - /** - * returns an array representing the border colors as rgb string - *
                  top, left, bottom, right, diag - * - * @return String[6] - */ - public int[] getBorderColorInts() { - try { - int[] clrs = new int[5]; - if (borderElements.get("top") != null) clrs[0] = borderElements.get("top").getBorderColorInt(); - if (borderElements.get("left") != null) clrs[1] = borderElements.get("left").getBorderColorInt(); - if (borderElements.get("bottom") != null) clrs[2] = borderElements.get("bottom").getBorderColorInt(); - if (borderElements.get("right") != null) clrs[3] = borderElements.get("right").getBorderColorInt(); - if (borderElements.get("diagonal") != null) clrs[4] = borderElements.get("diagonal").getBorderColorInt(); - return clrs; - } catch (NullPointerException e) { - return new int[5]; - } - } - - public OOXMLElement cloneElement() { - return new Border(this); - } - - public String toString() { - if (borderElements != null) - return borderElements.toString(); - return ""; - } - - /** - * return an OOXML representation of this border based on this FormatHandle object - */ - public static String getOOXML(Xf xf) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - int[] lineStyles = new int[5]; - lineStyles[0] = xf.getLeftBorderLineStyle(); - lineStyles[1] = xf.getRightBorderLineStyle(); - lineStyles[2] = xf.getTopBorderLineStyle(); - lineStyles[3] = xf.getBottomBorderLineStyle(); - lineStyles[4] = xf.getDiagBorderLineStyle(); - - int[] colors = new int[5]; - colors[0] = xf.getLeftBorderColor(); - colors[1] = xf.getRightBorderColor(); - colors[2] = xf.getTopBorderColor(); - colors[3] = xf.getBottomBorderColor(); - colors[4] = xf.getDiagBorderColor(); - - if (lineStyles[0] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - - if (lineStyles[1] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - - if (lineStyles[2] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - - if (lineStyles[3] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - - if (lineStyles[4] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - ooxml.append(""); - return ooxml.toString(); - } - - /** - * return the OOMXL to define a border based on the below specifications: - * - * @param styles int array {top, left, top, bottom, right, [diagonal]} - * @param colors int array {top, left, top, bottom, right, [diagonal]} - */ - public static String getOOXML(int[] styles, int[] colors) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - // left - if (styles[0] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - // right - if (styles[1] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - // top - if (styles[2] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - // bottom - if (styles[3] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - // diagonal - if (styles[4] > 0) { - ooxml.append(" 0) - ooxml.append(">"); - else ooxml.append("/>"); - } - ooxml.append(""); - return ooxml.toString(); - } -} - -/** - * one of: - * left, right, top, bottom, diagonal, vertical, horizontal - *

                  - * parent: border - * children: color - */ -class BorderElement implements OOXMLElement { - - private static final long serialVersionUID = -8040551653089261574L; - private String style; - private Color color; - private String borderElement; - - public BorderElement(String style, Color c, String borderElement) { - this.style = style; - this.color = c; - this.borderElement = borderElement; - } - - public BorderElement(BorderElement b) { - this.style = b.style; - this.color = b.color; - this.borderElement = b.borderElement; - } - - /** - * @return the border size for this border element, translated from OOXML string to 2003-style int - */ - public int getBorderSize() { - int st = getBorderStyle(); - if (st <= 4) - return st + 1; - // otherwise, interpret style --> size??? - if (st == 7) // hair - return 1; - if (st == 6 || st == 8 || st == 0xC) - return 3; - return 2; - } - - /** - * return the border style for this border element, translated from OOXML string to 2003-style int - * - * @return - */ - public int getBorderStyle() { - if (style == null || style.equals("none")) - return -1; - else if (style.equals("thin")) - return 1; - else if (style.equals("medium")) - return 2; - else if (style.equals("dashed")) - return 3; - else if (style.equals("dotted")) - return 4; - else if (style.equals("thick")) - return 5; - else if (style.equals("double")) - return 6; - else if (style.equals("hair")) - return 7; - else if (style.equals("mediumDashed")) - return 8; - else if (style.equals("dashDot")) - return 9; - else if (style.equals("mediumDashDot")) - return 0xA; - else if (style.equals("dashDotDot")) - return 0xB; - else if (style.equals("mediumDashDotDot")) - return 0xC; - else if (style.equals("slantDashDot")) - return 0xD; - return -1; - } - - /** - * return the rgb color string for this border element - * - * @return - */ - public String getBorderColor() { - if (color != null) - return this.color.getColorAsOOXMLRBG(); - return null; - } - - public int getBorderColorInt() { - if (color != null) - return this.color.getColorInt(); - return 0; - } - - /** - * constructor from string representation of a border element - * - * @param style "thin", "thick" ... - * @param val rgb color value - * @param borderElement "left", "right", "top", "bottom", "diagonal" - */ - public BorderElement(String style, String val, String borderElement, WorkBookHandle bk) { - this.style = style; - if (style != null && val != null) - this.color = new Color("color", false, Color.COLORTYPERGB, val, 0.0, (short) 0, bk.getWorkBook().getTheme()); - this.borderElement = borderElement; - } - - /** - * create a new border element - * - * @param style "thin", "thick" ... - * @param val color int - * @param borderElement "left", "right", "top", "bottom", "diagonal" - */ - public BorderElement(String style, int val, String borderElement, WorkBookHandle bk) { - this.style = style; - if (style != null && val != -1) - this.color = new Color("color", false, Color.COLORTYPEINDEXED, String.valueOf(val), 0.0, (short) 0, bk.getWorkBook().getTheme()); - this.borderElement = borderElement; - } - - public static BorderElement parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - String style = null; - Color c = null; - String borderElement = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("color")) { - c = (Color) Color.parseOOXML(xpp, (short) 0, bk); - } else { // one of the border elements - if (xpp.getAttributeCount() > 0) // style - style = xpp.getAttributeValue(0); - borderElement = tnm; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals(borderElement)) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("borderElement.parseOOXML: " + e.toString()); - } - BorderElement b = new BorderElement(style, c, borderElement); - return b; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append("<" + borderElement); - if (style != null) { - ooxml.append(" style=\"" + style + "\">"); - if (color != null) ooxml.append(color.getOOXML()); - ooxml.append(""); - } else - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BorderElement(this); - } - - public String toString() { - return ((style != null) ? style : "") + - " c:" + ((color != null) ? color.toString() : ""); - - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Border.kt b/src/main/java/io/starter/formats/OOXML/Border.kt new file mode 100644 index 0000000..d768159 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Border.kt @@ -0,0 +1,544 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + + +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Xf +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap + +/** + * border OOXML element + * + * + * parent: styleSheet/borders element in styles.xml + * children: SEQ: left, right, top, bottom, diagonal, vertical, horizontal + */ +class Border : OOXMLElement { + private var attrs: HashMap? = null + private var borderElements: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (borderElements!!["left"] != null) ooxml.append(borderElements!!["left"].ooxml) + if (borderElements!!["right"] != null) ooxml.append(borderElements!!["right"].ooxml) + if (borderElements!!["top"] != null) ooxml.append(borderElements!!["top"].ooxml) + if (borderElements!!["bottom"] != null) ooxml.append(borderElements!!["bottom"].ooxml) + if (borderElements!!["diagonal"] != null) ooxml.append(borderElements!!["diagonal"].ooxml) + if (borderElements!!["vertical"] != null) ooxml.append(borderElements!!["vertical"].ooxml) + if (borderElements!!["horizontal"] != null) ooxml.append(borderElements!!["horizontal"].ooxml) + + ooxml.append("") + return ooxml.toString() + } + + /** + * returns an array representing the border sizes + *

                  top, left, bottom, right, diag + * + * @return int[5] representing border sizes + */ + val borderSizes: IntArray + get() { + val sizes = IntArray(5) + if (borderElements!!["top"] != null) sizes[0] = borderElements!!["top"].borderSize + if (borderElements!!["left"] != null) sizes[1] = borderElements!!["left"].borderSize + if (borderElements!!["bottom"] != null) sizes[2] = borderElements!!["bottom"].borderSize + if (borderElements!!["right"] != null) sizes[3] = borderElements!!["right"].borderSize + if (borderElements!!["diagonal"] != null) sizes[4] = borderElements!!["diagonal"].borderSize + return sizes + } + + /** + * returns an array representing the border styles + * translated from OOXML String value to 2003-int value + *

                  top, left, bottom, right, diag + * + * @return int[5] + */ + val borderStyles: IntArray + get() { + val styles = IntArray(5) + if (borderElements!!["top"] != null) styles[0] = borderElements!!["top"].borderStyle + if (borderElements!!["left"] != null) styles[1] = borderElements!!["left"].borderStyle + if (borderElements!!["bottom"] != null) styles[2] = borderElements!!["bottom"].borderStyle + if (borderElements!!["right"] != null) styles[3] = borderElements!!["right"].borderStyle + if (borderElements!!["diagonal"] != null) styles[4] = borderElements!!["diagonal"].borderStyle + return styles + } + + /** + * returns an array representing the border colors as rgb string + *

                  top, left, bottom, right, diag + * + * @return String[6] + */ + val borderColors: Array + get() { + try { + val clrs = arrayOfNulls(5) + if (borderElements!!["top"] != null) clrs[0] = borderElements!!["top"].borderColor + if (borderElements!!["left"] != null) clrs[1] = borderElements!!["left"].borderColor + if (borderElements!!["bottom"] != null) clrs[2] = borderElements!!["bottom"].borderColor + if (borderElements!!["right"] != null) clrs[3] = borderElements!!["right"].borderColor + if (borderElements!!["diagonal"] != null) clrs[4] = borderElements!!["diagonal"].borderColor + return clrs + } catch (e: NullPointerException) { + return arrayOfNulls(5) + } + + } + + /** + * returns an array representing the border colors as rgb string + *

                  top, left, bottom, right, diag + * + * @return String[6] + */ + val borderColorInts: IntArray + get() { + try { + val clrs = IntArray(5) + if (borderElements!!["top"] != null) clrs[0] = borderElements!!["top"].borderColorInt + if (borderElements!!["left"] != null) clrs[1] = borderElements!!["left"].borderColorInt + if (borderElements!!["bottom"] != null) clrs[2] = borderElements!!["bottom"].borderColorInt + if (borderElements!!["right"] != null) clrs[3] = borderElements!!["right"].borderColorInt + if (borderElements!!["diagonal"] != null) clrs[4] = borderElements!!["diagonal"].borderColorInt + return clrs + } catch (e: NullPointerException) { + return IntArray(5) + } + + } + + constructor() {} + + /** + * @param styles int array {top, left, top, bottom, right, [diagonal]} + * @param colors int array {top, left, top, bottom, right, [diagonal]} + */ + constructor(attrs: HashMap, borderElements: HashMap) { + this.attrs = attrs + this.borderElements = borderElements + } + + constructor(b: Border) { + this.attrs = b.attrs + this.borderElements = b.borderElements + } + + /** + * set borders + * + * @param bk + * @param styles t, l, b, r + * @param colors + */ + constructor(bk: WorkBookHandle, styles: IntArray, colors: IntArray) { + this.borderElements = HashMap() + val borderElements = arrayOf("top", "left", "bottom", "right") + for (i in 0..3) { + if (styles[i] > 0) { + val style = OOXMLConstants.borderStyle[styles[i]] + this.borderElements!![borderElements[i]] = BorderElement(style, colors[i], borderElements[i], bk) + } + } + // diagonal? vertical? horizontal? + } + + override fun cloneElement(): OOXMLElement { + return Border(this) + } + + override fun toString(): String { + return if (borderElements != null) borderElements!!.toString() else "" + } + + companion object { + + private val serialVersionUID = 4340789910636828223L + + fun parseOOXML(xpp: XmlPullParser, bk: WorkBookHandle): OOXMLElement { + val attrs = HashMap() + val borderElements = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "border") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "left") { + borderElements["left"] = BorderElement.parseOOXML(xpp, bk) + } else if (tnm == "right") { + borderElements["right"] = BorderElement.parseOOXML(xpp, bk) + } else if (tnm == "top") { + borderElements["top"] = BorderElement.parseOOXML(xpp, bk) + } else if (tnm == "bottom") { + borderElements["bottom"] = BorderElement.parseOOXML(xpp, bk) + } else if (tnm == "diagonal") { + borderElements["diagonal"] = BorderElement.parseOOXML(xpp, bk) + } else if (tnm == "vertical") { + borderElements["vertical"] = BorderElement.parseOOXML(xpp, bk) + } else if (tnm == "horizontal") { + borderElements["horizontal"] = BorderElement.parseOOXML(xpp, bk) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "border") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("border.parseOOXML: $e") + } + + return Border(attrs, borderElements) + } + + /** + * return an OOXML representation of this border based on this FormatHandle object + */ + fun getOOXML(xf: Xf): String { + val ooxml = StringBuffer() + ooxml.append("") + val lineStyles = IntArray(5) + lineStyles[0] = xf.leftBorderLineStyle.toInt() + lineStyles[1] = xf.rightBorderLineStyle.toInt() + lineStyles[2] = xf.topBorderLineStyle.toInt() + lineStyles[3] = xf.bottomBorderLineStyle.toInt() + lineStyles[4] = xf.diagBorderLineStyle.toInt() + + val colors = IntArray(5) + colors[0] = xf.leftBorderColor + colors[1] = xf.rightBorderColor.toInt() + colors[2] = xf.topBorderColor + colors[3] = xf.bottomBorderColor + colors[4] = xf.diagBorderColor.toInt() + + if (lineStyles[0] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + + if (lineStyles[1] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + + if (lineStyles[2] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + + if (lineStyles[3] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + + if (lineStyles[4] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + ooxml.append("") + return ooxml.toString() + } + + /** + * return the OOMXL to define a border based on the below specifications: + * + * @param styles int array {top, left, top, bottom, right, [diagonal]} + * @param colors int array {top, left, top, bottom, right, [diagonal]} + */ + fun getOOXML(styles: IntArray, colors: IntArray): String { + val ooxml = StringBuffer() + ooxml.append("") + // left + if (styles[0] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + // right + if (styles[1] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + // top + if (styles[2] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + // bottom + if (styles[3] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + // diagonal + if (styles[4] > 0) { + ooxml.append(" 0) + ooxml.append(">") + else + ooxml.append("/>") + } + ooxml.append("") + return ooxml.toString() + } + } +} + +/** + * one of: + * left, right, top, bottom, diagonal, vertical, horizontal + * + * + * parent: border + * children: color + */ +internal class BorderElement : OOXMLElement { + private var style: String? = null + private var color: Color? = null + private var borderElement: String? = null + + /** + * @return the border size for this border element, translated from OOXML string to 2003-style int + */ + // otherwise, interpret style --> size??? + // hair + val borderSize: Int + get() { + val st = borderStyle + if (st <= 4) + return st + 1 + if (st == 7) + return 1 + return if (st == 6 || st == 8 || st == 0xC) 3 else 2 + } + + /** + * return the border style for this border element, translated from OOXML string to 2003-style int + * + * @return + */ + val borderStyle: Int + get() { + if (style == null || style == "none") + return -1 + else if (style == "thin") + return 1 + else if (style == "medium") + return 2 + else if (style == "dashed") + return 3 + else if (style == "dotted") + return 4 + else if (style == "thick") + return 5 + else if (style == "double") + return 6 + else if (style == "hair") + return 7 + else if (style == "mediumDashed") + return 8 + else if (style == "dashDot") + return 9 + else if (style == "mediumDashDot") + return 0xA + else if (style == "dashDotDot") + return 0xB + else if (style == "mediumDashDotDot") + return 0xC + else if (style == "slantDashDot") + return 0xD + return -1 + } + + /** + * return the rgb color string for this border element + * + * @return + */ + val borderColor: String? + get() = if (color != null) this.color!!.colorAsOOXMLRBG else null + + val borderColorInt: Int + get() = if (color != null) this.color!!.colorInt else 0 + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("<" + borderElement!!) + if (style != null) { + ooxml.append(" style=\"$style\">") + if (color != null) ooxml.append(color!!.ooxml) + ooxml.append("") + } else + ooxml.append("/>") + return ooxml.toString() + } + + constructor(style: String, c: Color, borderElement: String) { + this.style = style + this.color = c + this.borderElement = borderElement + } + + constructor(b: BorderElement) { + this.style = b.style + this.color = b.color + this.borderElement = b.borderElement + } + + /** + * constructor from string representation of a border element + * + * @param style "thin", "thick" ... + * @param val rgb color value + * @param borderElement "left", "right", "top", "bottom", "diagonal" + */ + constructor(style: String?, `val`: String?, borderElement: String, bk: WorkBookHandle) { + this.style = style + if (style != null && `val` != null) + this.color = Color("color", false, Color.COLORTYPERGB, `val`, 0.0, 0.toShort(), bk.workBook!!.theme) + this.borderElement = borderElement + } + + /** + * create a new border element + * + * @param style "thin", "thick" ... + * @param val color int + * @param borderElement "left", "right", "top", "bottom", "diagonal" + */ + constructor(style: String?, `val`: Int, borderElement: String, bk: WorkBookHandle) { + this.style = style + if (style != null && `val` != -1) + this.color = Color("color", false, Color.COLORTYPEINDEXED, `val`.toString(), 0.0, 0.toShort(), bk.workBook!!.theme) + this.borderElement = borderElement + } + + override fun cloneElement(): OOXMLElement { + return BorderElement(this) + } + + override fun toString(): String { + return (if (style != null) style else "") + + " c:" + if (color != null) color!!.toString() else "" + + } + + companion object { + + private val serialVersionUID = -8040551653089261574L + + fun parseOOXML(xpp: XmlPullParser, bk: WorkBookHandle): BorderElement { + var style: String? = null + var c: Color? = null + var borderElement: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "color") { + c = Color.parseOOXML(xpp, 0.toShort(), bk) as Color + } else { // one of the border elements + if (xpp.attributeCount > 0) + // style + style = xpp.getAttributeValue(0) + borderElement = tnm + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == borderElement) { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("borderElement.parseOOXML: $e") + } + + return BorderElement(style, c, borderElement) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/CNvPr.java b/src/main/java/io/starter/formats/OOXML/CNvPr.java deleted file mode 100644 index 9eff840..0000000 --- a/src/main/java/io/starter/formats/OOXML/CNvPr.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * cNvPr (Non-Visual Drawing Properties) - *

                  - * OOXML/DrawingML element specifies non-visual canvas properties. This allows for additional information that does not affect - * the appearance of the picture to be stored. - *

                  - * attributes: descr, hidden, id REQ, name REQ, - * parents: nvSpPr, nvPicPr ... - * children: hlinkClick, hlinkHover - */ -// TODO: Handle Child elements hlinkClick, hlinkHover -public class CNvPr implements OOXMLElement { - - private static final long serialVersionUID = -3382139449400844949L; - //private hlinkClick hc; - //private hlinkHover hh; - private String descr = null, name = null; - private boolean hidden = false; - private int id = -1; - - public CNvPr() { - } - - public CNvPr(/*hlinkClick hc, hlinkHover hh, */int id, String name, String descr, boolean hidden) { - //this.hc= hc; - //this.hh= hh; - this.id = id; - this.name = name; - this.descr = descr; - this.hidden = hidden; - } - - public CNvPr(CNvPr cnv) { - //this.hc= cnv.hc; - //this.hh= cnv.hh; - this.id = cnv.id; - this.name = cnv.name; - this.descr = cnv.descr; - this.hidden = cnv.hidden; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - //hlinkClick hc= null; - //hlinkHover hh= null; - String descr = null, name = null; - boolean hidden = false; - int id = -1; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - String val = xpp.getAttributeValue(i); - if (nm.equals("id")) { - id = Integer.valueOf(val).intValue(); - } else if (nm.equals("name")) { - name = val; - } else if (nm.equals("descr")) { - descr = val; - } else if (nm.equals("hidden")) { - hidden = val.equals("1"); - } - } - } else if (tnm.equals("hlinkClick")) { - // hc= (hlinkClick) hlinkClick.parseOOXML(xpp).clone(); - } else if (tnm.equals("hlinkHover")) { - // hh= (hlinkHover) hlinkHover.parseOOXML(xpp).clone(); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("cNvPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cNvPr.parseOOXML: " + e.toString()); - } - CNvPr cnv = new CNvPr(/*hc, hh, */id, name, descr, hidden); - return cnv; - } - - public String getOOXML() { - StringBuffer tooxml = new StringBuffer(); - tooxml.append(""); - return tooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvPr(this); - } - - /** - * get name attribute - * - * @return - */ - public String getName() { - return name; - } - - /** - * set name attribute - * - * @param name - */ - public void setName(String name) { - this.name = name; - } - - /** - * get descr attribute - * - * @return - */ - - public String getDescr() { - return descr; - } - - /** - * set description attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setDescr(String descr) { - this.descr = descr; - } - - /** - * set the id for this element - * - * @param id - */ - public void setId(int id) { - this.id = id; - } - - /** - * return the id for this element - * - * @return - */ - public int getId() { - return this.id; - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/CNvPr.kt b/src/main/java/io/starter/formats/OOXML/CNvPr.kt new file mode 100644 index 0000000..cf8fbc0 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/CNvPr.kt @@ -0,0 +1,174 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.formats.XLS.OOXMLAdapter +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * cNvPr (Non-Visual Drawing Properties) + * + * + * OOXML/DrawingML element specifies non-visual canvas properties. This allows for additional information that does not affect + * the appearance of the picture to be stored. + * + * + * attributes: descr, hidden, id REQ, name REQ, + * parents: nvSpPr, nvPicPr ... + * children: hlinkClick, hlinkHover + */ +// TODO: Handle Child elements hlinkClick, hlinkHover +class CNvPr : OOXMLElement { + //private hlinkClick hc; + //private hlinkHover hh; + /** + * get descr attribute + * + * @return + */ + + /** + * set description attribute + * sometimes associated with shape name + * + * @param descr + */ + var descr: String? = null + /** + * get name attribute + * + * @return + */ + /** + * set name attribute + * + * @param name + */ + var name: String? = null + private var hidden = false + /** + * return the id for this element + * + * @return + */ + /** + * set the id for this element + * + * @param id + */ + var id = -1 + + override// TODO: HANDLE if (hc!=null) tooxml.append(hc.getOOXML()); + // TODO: HANDLE if (hh!=null) tooxml.append(hh.getOOXML()); + val ooxml: String + get() { + val tooxml = StringBuffer() + tooxml.append("") + return tooxml.toString() + } + + constructor() {} + + constructor(/*hlinkClick hc, hlinkHover hh, */id: Int, name: String, descr: String, hidden: Boolean) { + //this.hc= hc; + //this.hh= hh; + this.id = id + this.name = name + this.descr = descr + this.hidden = hidden + } + + constructor(cnv: CNvPr) { + //this.hc= cnv.hc; + //this.hh= cnv.hh; + this.id = cnv.id + this.name = cnv.name + this.descr = cnv.descr + this.hidden = cnv.hidden + } + + override fun cloneElement(): OOXMLElement { + return CNvPr(this) + } + + companion object { + + private val serialVersionUID = -3382139449400844949L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + //hlinkClick hc= null; + //hlinkHover hh= null; + var descr: String? = null + var name: String? = null + var hidden = false + var id = -1 + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cNvPr") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + val `val` = xpp.getAttributeValue(i) + if (nm == "id") { + id = Integer.valueOf(`val`).toInt() + } else if (nm == "name") { + name = `val` + } else if (nm == "descr") { + descr = `val` + } else if (nm == "hidden") { + hidden = `val` == "1" + } + } + } else if (tnm == "hlinkClick") { + // hc= (hlinkClick) hlinkClick.parseOOXML(xpp).clone(); + } else if (tnm == "hlinkHover") { + // hh= (hlinkHover) hlinkHover.parseOOXML(xpp).clone(); + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "cNvPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("cNvPr.parseOOXML: $e") + } + + return CNvPr(/*hc, hh, */id, name, descr, hidden) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/CfRule.java b/src/main/java/io/starter/formats/OOXML/CfRule.java deleted file mode 100644 index 22a539d..0000000 --- a/src/main/java/io/starter/formats/OOXML/CfRule.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; - -/** - * cfRule (Conditional Formatting Rule) - * This collection represents a description of a conditional formatting rule. - *
                  - * NOTE: now is used merely to parse the cfRule OOXML element - * the data is stored in a BIFF-8 Cf object - *

                  - * parent: conditionalFormatting - * children: SEQ: formula (0-3), colorScale, dataBar, iconSet - * attributes: type, dxfId, priority (REQ), stopIfTrue, aboveAverage, - * percent, bottom, operator, text, timePeriod, rank, stdDev, equalAverage - */ -//TODO: Finish children colorScale, dataBar, iconSet -public class CfRule implements OOXMLElement { - - private static final long serialVersionUID = 8509907308100079138L; - private HashMap attrs; - private ArrayList formulas; - - public CfRule(HashMap attrs, ArrayList formulas) { - this.attrs = attrs; - this.formulas = formulas; - } - - public CfRule(CfRule cf) { - this.attrs = cf.attrs; - this.formulas = cf.formulas; - } - - /** - * generate a cfRule based on OOXML input stream - * - * @param xpp - * @return - */ - // TODO: finish children colorScale, dataBar, iconSet - public static CfRule parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - ArrayList formulas = new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cfRule")) { // get attributes: priority, type, operator, dxfId - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("formula")) { - formulas.add(OOXMLAdapter.getNextText(xpp)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("cfRule")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cfRule.parseOOXML: " + e.toString()); - } - CfRule cf = new CfRule(attrs, formulas); - return cf; - } - - /** - * generate OOXML for this cfRule - * NOW Cf is parsed to obtain OOXML and CfRule is not retained - * - * @return - * @deprecated - */ - public String getOOXML(int priority) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (formulas != null) { - for (int j = 0; j < formulas.size(); j++) { - ooxml.append("" + OOXMLAdapter.stripNonAsciiRetainQuote(formulas.get(j)) + ""); - } - } - // TODO: finish children dataBar, colorScale, iconSet - ooxml.append(""); - return ooxml.toString(); - } - - /** - * get the dxfId (the incremental style associated with this conditional formatting rule) - * - * @return - */ - public int getDxfId() { - if (this.attrs != null) - try { - return Integer.valueOf(this.attrs.get("dxfId")).intValue(); - } catch (Exception e) { - // it's possible to not specify a dxfId - } - return -1; - } - - /** - * set the dxfId (the incremental style associated with this conditional formatting rule) - * - * @param dxfId - * @deprecated - */ - public void setDxfId(int dxfId) { - if (this.attrs == null) this.attrs = new HashMap(); - attrs.put("dxfId", Integer.valueOf(dxfId).toString()); - } - - public OOXMLElement cloneElement() { - return new CfRule(this); - } - - public String getOOXML() { - // TODO Auto-generated method stub - return null; - } - - /** - * get methods - */ - public String getOperator() { - // valid only when type=="cellIs" - if (attrs != null) - return attrs.get("operator"); - return null; - } - - public String getType() { - String type = null; - if (attrs != null) - type = attrs.get("type"); - if (type == null) - type = "cellIs"; - return type; - } - - /** - * returns the text to test in a containsText type of condition - *
                  Only valid for containsText conditions - * - * @return - */ - public String getContainsText() { - if (attrs != null) - return attrs.get("text"); - return null; - } - - public String getFormula1() { - if (formulas != null && formulas.size() > 0) - return formulas.get(0); - return null; - } - - public String getFormula2() { - if (formulas != null && formulas.size() > 1) - return formulas.get(1); - return null; - } -} - -/** - * dataBar (Data Bar) - * Describes a data bar conditional formatting rule. - * [Example: - * In this example a data bar conditional format is expressed, which spreads across all cell values in the cell range, - * and whose color is blue. - * - * - * - * - * - * end example] - * The length of the data bar for any cell can be calculated as follows: - * Data bar length = minLength + (cell value - minimum value in the range) / 1 (maximum value in the range - - * minimum value in the range) * (maxLength - minLength), - * where min and max length are a fixed percentage of the column width (by default, 10% and 90% respectively.) - * The minimum difference in length (or increment amount) is 1 pixel. - *

                  - * parent: cfRule - * children: SEQ: cfvo (2), color (1) - * attributes: minLength (def=10), maxLength (def=90), showValue (def=true) - *

                  - * colorScale (Color Scale) - * Describes a graduated color scale in this conditional formatting rule. - *

                  - * parent: cfRule - * children: SEQ: cfvo (2+), color (2+) - *

                  - * cfvo (Conditional Format Value Object) - * Describes the values of the interpolation points in a gradient scale. - *

                  - * parents: colorScale, dataBar, iconSet - * children: extLst - * attributes: type (REQ), val, gte (def=true) - */ - - -/** - * colorScale (Color Scale) - * Describes a graduated color scale in this conditional formatting rule. - * - * parent: cfRule - * children: SEQ: cfvo (2+), color (2+) - */ - -/** - * cfvo (Conditional Format Value Object) - * Describes the values of the interpolation points in a gradient scale. - * - * parents: colorScale, dataBar, iconSet - * children: extLst - * attributes: type (REQ), val, gte (def=true) - * - */ - diff --git a/src/main/java/io/starter/formats/OOXML/CfRule.kt b/src/main/java/io/starter/formats/OOXML/CfRule.kt new file mode 100644 index 0000000..d6f704d --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/CfRule.kt @@ -0,0 +1,258 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.formats.XLS.OOXMLAdapter +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.HashMap + +/** + * cfRule (Conditional Formatting Rule) + * This collection represents a description of a conditional formatting rule. + *

                  + * NOTE: now is used merely to parse the cfRule OOXML element + * the data is stored in a BIFF-8 Cf object + * + * + * parent: conditionalFormatting + * children: SEQ: formula (0-3), colorScale, dataBar, iconSet + * attributes: type, dxfId, priority (REQ), stopIfTrue, aboveAverage, + * percent, bottom, operator, text, timePeriod, rank, stdDev, equalAverage + */ +//TODO: Finish children colorScale, dataBar, iconSet +class CfRule : OOXMLElement { + private var attrs: HashMap? = null + private var formulas: ArrayList? = null + + /** + * get the dxfId (the incremental style associated with this conditional formatting rule) + * + * @return + */ + /** + * set the dxfId (the incremental style associated with this conditional formatting rule) + * + * @param dxfId + */ + // it's possible to not specify a dxfId + var dxfId: Int + get() { + if (this.attrs != null) + try { + return Integer.valueOf(this.attrs!!["dxfId"]).toInt() + } catch (e: Exception) { + } + + return -1 + } + @Deprecated("") + set(dxfId) { + if (this.attrs == null) this.attrs = HashMap() + attrs!!["dxfId"] = Integer.valueOf(dxfId)!!.toString() + } + + override// TODO Auto-generated method stub + val ooxml: String? + get() = null + + /** + * get methods + */ + // valid only when type=="cellIs" + val operator: String? + get() = if (attrs != null) attrs!!["operator"] else null + + val type: String + get() { + var type: String? = null + if (attrs != null) + type = attrs!!["type"] + if (type == null) + type = "cellIs" + return type + } + + /** + * returns the text to test in a containsText type of condition + *

                  Only valid for containsText conditions + * + * @return + */ + val containsText: String? + get() = if (attrs != null) attrs!!["text"] else null + + val formula1: String? + get() = if (formulas != null && formulas!!.size > 0) formulas!![0] else null + + val formula2: String? + get() = if (formulas != null && formulas!!.size > 1) formulas!![1] else null + + constructor(attrs: HashMap, formulas: ArrayList) { + this.attrs = attrs + this.formulas = formulas + } + + constructor(cf: CfRule) { + this.attrs = cf.attrs + this.formulas = cf.formulas + } + + /** + * generate OOXML for this cfRule + * NOW Cf is parsed to obtain OOXML and CfRule is not retained + * + * @return + */ + @Deprecated("") + fun getOOXML(priority: Int): String { + val ooxml = StringBuffer() + ooxml.append("") + if (formulas != null) { + for (j in formulas!!.indices) { + ooxml.append("" + OOXMLAdapter.stripNonAsciiRetainQuote(formulas!![j]) + "") + } + } + // TODO: finish children dataBar, colorScale, iconSet + ooxml.append("") + return ooxml.toString() + } + + override fun cloneElement(): OOXMLElement { + return CfRule(this) + } + + companion object { + + private val serialVersionUID = 8509907308100079138L + + /** + * generate a cfRule based on OOXML input stream + * + * @param xpp + * @return + */ + // TODO: finish children colorScale, dataBar, iconSet + fun parseOOXML(xpp: XmlPullParser): CfRule { + val attrs = HashMap() + val formulas = ArrayList() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cfRule") { // get attributes: priority, type, operator, dxfId + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "formula") { + formulas.add(OOXMLAdapter.getNextText(xpp)) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "cfRule") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("cfRule.parseOOXML: $e") + } + + return CfRule(attrs, formulas) + } + } +} + +/** + * dataBar (Data Bar) + * Describes a data bar conditional formatting rule. + * [Example: + * In this example a data bar conditional format is expressed, which spreads across all cell values in the cell range, + * and whose color is blue. + * + * + * + * + * + * end example] + * The length of the data bar for any cell can be calculated as follows: + * Data bar length = minLength + (cell value - minimum value in the range) / 1 (maximum value in the range - + * minimum value in the range) * (maxLength - minLength), + * where min and max length are a fixed percentage of the column width (by default, 10% and 90% respectively.) + * The minimum difference in length (or increment amount) is 1 pixel. + * + * + * parent: cfRule + * children: SEQ: cfvo (2), color (1) + * attributes: minLength (def=10), maxLength (def=90), showValue (def=true) + * + * + * colorScale (Color Scale) + * Describes a graduated color scale in this conditional formatting rule. + * + * + * parent: cfRule + * children: SEQ: cfvo (2+), color (2+) + * + * + * cfvo (Conditional Format Value Object) + * Describes the values of the interpolation points in a gradient scale. + * + * + * parents: colorScale, dataBar, iconSet + * children: extLst + * attributes: type (REQ), val, gte (def=true) + */ + + +/** + * colorScale (Color Scale) + * Describes a graduated color scale in this conditional formatting rule. + * + * parent: cfRule + * children: SEQ: cfvo (2+), color (2+) + */ + +/** + * cfvo (Conditional Format Value Object) + * Describes the values of the interpolation points in a gradient scale. + * + * parents: colorScale, dataBar, iconSet + * children: extLst + * attributes: type (REQ), val, gte (def=true) + * + */ + diff --git a/src/main/java/io/starter/formats/OOXML/Color.java b/src/main/java/io/starter/formats/OOXML/Color.java deleted file mode 100644 index 4686783..0000000 --- a/src/main/java/io/starter/formats/OOXML/Color.java +++ /dev/null @@ -1,825 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -/** - * color (Data Bar Color) - * One of the colors associated with the data bar or color scale. Also font ... - * Note: the auto attribute is not used in the context of data bars - *

                  - * NOTE: both color, fgColor and bgColor use same CT_COLOR schema: - * - * - * - * - * - * - * - *

                  - * fgColor (Foreground Color) - * Foreground color of the cell fill pattern. Cell fill patterns operate with two colors: a background color and a - * foreground color. These combine together to make a patterned cell fill. - *

                  - *

                  - * bgColor (Background Color) - * Background color of the cell fill pattern. Cell fill patterns operate with two colors: a background color and a - * foreground color. These combine together to make a patterned cell fill. - */ -public class Color implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2546003092245407502L; - private boolean auto = false; - public static int COLORTYPEINDEXED = 0; - public static int COLORTYPERGB = 1; - public static int COLORTYPETHEME = 2; - private int colortype = -1; - private String colorval = null; // value of colortype - private double tint = 0.0; - private String element = null; - private int colorint = -1; // parsed color (tint + value) translated to OpenXLS color int value - private String colorstr = null; // parsed color (tint + value) translated to HTML color string - private Theme theme = null; - - public Color() { - } - - public Color(String element, boolean auto, int colortype, String colorval, double tint, short type, Theme t) { - this.element = element; - this.auto = auto; - this.colortype = colortype; - this.colorval = colorval; - this.tint = tint; - this.theme = t; - this.parseColor(type); - } - - public Color(Color c) { - this.element = c.element; - this.auto = c.auto; - this.colortype = c.colortype; - this.colorval = c.colorval; - this.tint = c.tint; - this.colorint = c.colorint; - this.colorstr = c.colorstr; - this.theme = c.theme; - } - - - /** - * creates a new Color object based upon a java.awt.Color - * - * @param clr Color objeect - * @param element "color", "fgColor" or "bgColor" - */ - public Color(java.awt.Color c, String element, Theme t) { - this.colortype = COLORTYPERGB; - this.colorval = "FF" + FormatHandle.colorToHexString(c).substring(1); - this.element = element; - this.theme = t; // ok if it's null - this.parseColor((short) 0); - } - - /** - * creates a new Color object based upon a web-compliant Hex Color string - * - * @param clr - * @param element "color", "fgColor" or "bgColor" - */ - public Color(String clr, String element, Theme t) { - this.colortype = COLORTYPERGB; - if (clr.startsWith("#")) - this.colorval = "FF" + clr.substring(1); - else if (clr.length() == 6) - this.colorval = "FF" + clr; - else - this.colorval = clr; - this.element = element; - this.theme = t; // ok if it's null - this.parseColor((short) 0); - } - - /** - * parses a color element - * root= color, fgColor or bgColor - * - * @param xpp - * @return - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, short type, WorkBookHandle bk) { - String element = null; - boolean auto = false; - int colortype = -1; - String colorval = null; - double tint = 0.0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("color") || - tnm.equals("fgColor") || - tnm.equals("bgColor")) { // get attributes - element = tnm; // save element name - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - String val = xpp.getAttributeValue(i); - if (n.equals("auto")) { - auto = true; - } else if (n.equals("indexed")) { - colortype = Color.COLORTYPEINDEXED; - colorval = val; - } else if (n.equals("rgb")) { - colortype = COLORTYPERGB; - colorval = val; - } else if (n.equals("theme")) { - colortype = COLORTYPETHEME; - colorval = val; - } else if (n.equals("tint")) { - tint = new Double(val).doubleValue(); - } - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals(element)) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("color.parseOOXML: " + e.toString()); - } - Color c = new Color(element, auto, colortype, colorval, tint, type, bk.getWorkBook().getTheme()); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append("<" + this.element); - if (this.auto) ooxml.append(" auto=\"1\""); - else if (this.colortype == COLORTYPERGB)// rgb - ooxml.append(" rgb=\"" + colorstr + "\""); - else if (this.colortype == COLORTYPEINDEXED) - ooxml.append(" indexed=\"" + colorval + "\""); - else if (this.colortype == COLORTYPETHEME) // theme - ooxml.append(" theme=\"" + colorval + "\""); - if (tint != 0) - ooxml.append(" tint=\"" + tint + "\""); - ooxml.append("/>"); - return ooxml.toString(); - } - - public static String getOOXML(String element, int colortype, int colorval, String colorstr, double tint) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append("<" + element); - if (colortype == COLORTYPERGB) // rgb - ooxml.append(" rgb=\"" + colorstr + "\""); - else if (colortype == COLORTYPEINDEXED) // indexed - ooxml.append(" indexed=\"" + colorval + "\""); - else if (colortype == COLORTYPETHEME) // theme - ooxml.append(" theme=\"" + colorval + "\""); - if (tint != 0) - ooxml.append(" tint=\"" + tint + "\""); - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Color(this); - } - - public String toString() { - String ret = ""; - if (colortype == COLORTYPERGB) // rgb - ret = " rgb=" + colorstr; - else if (colortype == COLORTYPEINDEXED) // indexed - ret = " indexed=" + colorval; - else if (colortype == COLORTYPETHEME) // theme - ret = " theme=" + colorval; - if (tint != 0) - ret = " tint=" + tint; - return ret; - } - - /** - * return the translated color int for this OOXML color - * - * @return - */ - public int getColorInt() { - return this.colorint; - } - - /** - * return the color type for this OOXML color - * (indexed= 0, rgb= 1, theme= 2) - * - * @return - */ - public int getColorType() { - return this.colortype; - } - - /** - * return the translated HTML color string for this OOXML color - * - * @return - */ - public String getColorAsOOXMLRBG() { - return this.colorstr; - } - - public java.awt.Color getColorAsColor() { - return FormatHandle.HexStringToColor(this.getColorAsOOXMLRBG()); - } - - /** - * manually set color int - * automatically defaults to indexed color, and looks up color in COLORTABLE - * - * @param clr - */ - public void setColorInt(int clr) { - this.colorint = clr; - this.colortype = 0; // indexed - // reset other vars as well - if (clr > -1) - this.colorstr = FormatHandle.colorToHexString(FormatHandle.COLORTABLE[clr]).substring(1); - else - this.colorstr = null; - this.tint = 0.0; - this.auto = false; - } - - /** - * sets the color via java.awt.Color - * - * @param c - */ - public void setColor(java.awt.Color c) { - this.colortype = COLORTYPERGB; - this.colorval = "FF" + FormatHandle.colorToHexString(c).substring(1); - this.parseColor((short) 0); - this.tint = 0.0; - this.auto = false; - } - - - /** - * sets the color via a web-compliant Hex Color String - * - * @param clr - */ - public void setColor(String clr) { - this.colortype = COLORTYPERGB; - if (clr.startsWith("#")) - this.colorval = "FF" + clr.substring(1); - else if (clr.length() == 6) - this.colorval = "FF" + clr; - else - this.colorval = clr; - this.parseColor((short) 0); - } - - /** - * static version of parseColor --takes a color value of OOXML type type - *
                  COLORTYPERBG, COLORTYPEINDEXED or COLORTYPETHEME - *
                  and returns the - * - * @param val String OOXML color value, value depends upon colortype - * @param colortype int val's colortype, one of: Color.COLORTYPERGB, Color.COLORTYPEINDEXED, Color.COLORTYPETHEME - * @param type 0= font, 1= fill - * @return String HEX-style color string - */ - public static String parseColor(String val, int colortype, short type, Theme t) { - Color c = new Color("", false, colortype, val, 0, type, t); - return c.colorstr; - } - - /** - * static version of parseColor --takes a color value of OOXML type type - *
                  COLORTYPERBG, COLORTYPEINDEXED or COLORTYPETHEME - *
                  and returns the indexed color int it represents - * - * @param val String OOXML color value, value depends upon colortype - * @param colortype int val's colortype, one of: Color.COLORTYPERGB, Color.COLORTYPEINDEXED, Color.COLORTYPETHEME - * @param type 0= font, 1= fill - * @return int - */ - public static int parseColorInt(String val, int colortype, short type, Theme t) { - Color c = new Color("", false, colortype, val, 0, type, t); -// c.parseColor(type); - return c.colorint; - } - - /** - * simple utility to parse correct colors (int + html string) from OOXML style color - * - * @param type 0= font, -1= fill - */ - private void parseColor(short type) { - try { - if (this.colortype == COLORTYPERGB) { // rgb - color string - this.colorint = FormatHandle.HexStringToColorInt(this.colorval, type); // find best match - this.colorstr = this.colorval; - } else if (this.colortype == COLORTYPEINDEXED) { // indexed (corresponds to either our color int or a custom set of indexed colors - this.colorint = Integer.valueOf(this.colorval).intValue(); - if (this.colorint == 64 && type == FormatHandle.colorFONT) // means system foreground: Default foreground color. This is the window text color in the sheet display. - this.colorstr = FormatHandle.colorToHexString(FormatHandle.getColor(0)); -// this.colorint= 0; // black - else - this.colorstr = FormatHandle.colorToHexString(FormatHandle.getColor(this.colorint)); - } else if (this.colortype == COLORTYPETHEME) { // theme - Object[] o = Color.parseThemeColor(this.colorval, this.tint, type, this.theme); - this.colorint = (Integer) o[0]; - this.colorstr = (String) o[1]; - } - } catch (Exception e) { - Logger.logWarn("color.parseColor: " + colortype + ":" + colorval + ": " + e.toString()); - } - } - - /** - * interprets theme colorval and tint - * TODO: read in theme colors from theme1.xml - * - * @param colorval String theme colorval (see SchemeClr and Theme for details) - * EITHER an Index into the collection, referencing a particular or value expressed in the Theme part. - * OR the actual clrScheme string - * @param tint double tint to apply to the base color (0 for none) - * @param type short 0= font, 1= fill - * @return - */ - public static Object[] parseThemeColor(String colorval, double tint, short type, Theme t) { - /** - * TODO: read this in from THEME!!! - */ - final String[][] themeColors = { - {"dk1", "000000"}, - {"lt1", "FFFFFF"}, - {"dk2", "1F497D"}, - {"lt2", "EEECE1"}, - {"accent1", "4F81BD"}, - {"accent1", "C0504D"}, - {"accent3", "9BBB59"}, - {"accent4", "8064A2"}, - {"accent5", "4BACC6"}, - {"accent6", "F79646"}, - {"hlink", "0000FF"}, - {"folHlink", "800080"}, - }; - - // TODO: read theme colors from file - Object[] o = new Object[2]; - int i = 0; - String clr = ""; - try { - i = Integer.valueOf(colorval).intValue(); - clr = t.genericThemeClrs[i]; - } catch (Exception e) { // OpenXLS-created xlsx files will not have theme entries - for (i = 0; i < themeColors.length; i++) - if (themeColors[i][0].equals(colorval)) { - clr = themeColors[i][1]; - break; - } - } - if (Math.abs(tint) > .005) { - java.awt.Color c = applyTint(tint, clr); - o[0] = FormatHandle.getColorInt(c); - o[1] = FormatHandle.colorToHexString(c).substring(1); // avoid # - } else { - o[0] = FormatHandle.HexStringToColorInt("#" + clr, type); - o[1] = "#" + clr; - } - return o; - } - - - /** - * If tint is supplied, then it is applied to the RGB value of the color to determine the final - * color applied. - * The tint value is stored as a double from -1.0 .. 1.0, where -1.0 means 100% darken and - * 1.0 means 100% lighten. Also, 0.0 means no change. - * In loading the RGB value, it is converted to HLS where HLS values are (0..HLSMAX), where - * HLSMAX is currently 255. - */ - private static java.awt.Color applyTint(double tint, String clr) { - final int HLSMAX = 255; - int r = Integer.parseInt(clr.substring(0, 2), 16); - int g = Integer.parseInt(clr.substring(2, 4), 16); - int b = Integer.parseInt(clr.substring(4, 6), 16); - - HSLColor hsl = new HSLColor(); - hsl.initHSLbyRGB(r, g, b); - double l = hsl.getLuminence(); - if (tint < 0) //darken - l = l * (1 + tint); - else // lighten - l = l * (1 - tint) + (HLSMAX - HLSMAX * (1.0 - tint)); - l = Math.round(l); - hsl.initRGBbyHSL(hsl.getHue(), hsl.getSaturation(), new Double(l).intValue()); - return new java.awt.Color(hsl.getRed(), hsl.getGreen(), hsl.getBlue()); - - } -} - - - - -/* HSL stands for hue, saturation, lightness - * Both HSL and HSV describe colors as points in a cylinder - * whose central axis ranges from black at the bottom to white at the top - * with neutral colors between them, - * where angle around the axis corresponds to hue, - * distance from the axis corresponds to saturation, - * and distance along the axis corresponds to lightness, value, or brightness.*/ - -/* - * Conversion from RGB to HSL - - Let r, g, b E [0,1] be the red, green, and blue coordinates, respectively, of a color in RGB space. - Let max be the greatest of r, g, and b, and min the least. - - hue angle h: - - h= 0 if max=min (for grays) - = 60 deg x (g-b)/(max-min) + 360 deg) mod 360 deg if max=r - = 60 deg x (b-r)/(max-min) + 120 deg) if max=g - = 60 deg x (r-g)/(max-min) + 240 deg) if max=b - - lightness l= (max-min)/2 - - Saturation s - - s= 0 if max=min - = (max-min)/2l if l <= 1/2 - = (max-min)/(2-2l) if l > 1/2 - - The value of h is generally normalized to lie between 0 and 360, and h = 0 is used when max = min (that is, for grays) - though the hue has no geometric meaning there, where the saturation s is zero. - Similarly, the choice of 0 as the value for s when l is equal to 0 or 1 is arbitrary. - - Conversion from HSL to RGB - - Given a color defined by (h, s, l) values in HSL space, with h in the range [0, 360), indicating the angle, - in degrees of the hue, and with s and l in the range [0, 1], representing the saturation and lightness, respectively, - a corresponding (r, g, b) triplet in RGB space, with r, g, and b also in range [0, 1], and corresponding to red, green, and blue, - respectively, can be computed as follows: - - First, if s = 0, then the resulting color is achromatic, or gray. In this special case, r, g, and b all equal l. - Note that the value of h is ignored, and may be undefined in this situation. - - The following procedure can be used, even when s is zero: - - q= l x (1+s) if l < 1/2 - = l + s - (l x s) if l >= 1/2 - - p= 2 x l - q - - hk= h / 360 (h normalized in the range 0-1) - - tr= hk + 1/3 - - tg= hk - - tb= hk - 1/3 - - if tc - - */ -class HSLColor { - private final static int HSLMAX = 255; - private final static int RGBMAX = 255; - private final static int UNDEFINED = 170; - - private int pHue; - private int pSat; - private int pLum; - private int pRed; - private int pGreen; - private int pBlue; - - public void initHSLbyRGB(int R, int G, int B) { - // sets Hue, Sat, Lum - int cMax; - int cMin; - int RDelta; - int GDelta; - int BDelta; - int cMinus; - int cPlus; - - pRed = R; - pGreen = G; - pBlue = B; - - //Set Max & MinColor Values - cMax = iMax(iMax(R, G), B); - cMin = iMin(iMin(R, G), B); - - cMinus = cMax - cMin; - cPlus = cMax + cMin; - - // Calculate luminescence (lightness) - pLum = ((cPlus * HSLMAX) + RGBMAX) / (2 * RGBMAX); - - if (cMax == cMin) { - // greyscale - pSat = 0; - pHue = UNDEFINED; - } else { - // Calculate color saturation - if (pLum <= (HSLMAX / 2)) { - pSat = (int) (((cMinus * HSLMAX) + 0.5) / cPlus); - } else { - pSat = (int) (((cMinus * HSLMAX) + 0.5) / (2 * RGBMAX - cPlus)); - } - - //Calculate hue - RDelta = (int) ((((cMax - R) * (HSLMAX / 6)) + 0.5) / cMinus); - GDelta = (int) ((((cMax - G) * (HSLMAX / 6)) + 0.5) / cMinus); - BDelta = (int) ((((cMax - B) * (HSLMAX / 6)) + 0.5) / cMinus); - - if (cMax == R) { - pHue = BDelta - GDelta; - } else if (cMax == G) { - pHue = (HSLMAX / 3) + RDelta - BDelta; - } else if (cMax == B) { - pHue = ((2 * HSLMAX) / 3) + GDelta - RDelta; - } - - if (pHue < 0) { - pHue = pHue + HSLMAX; - } - } - } - - public void initRGBbyHSL(int H, int S, int L) { - int Magic1; - int Magic2; - - pHue = H; - pLum = L; - pSat = S; - - if (S == 0) { //Greyscale - pRed = (L * RGBMAX) / HSLMAX; //luminescence: set to range - pGreen = pRed; - pBlue = pRed; - } else { - if (L <= HSLMAX / 2) { - Magic2 = (L * (HSLMAX + S) + (HSLMAX / 2)) / (HSLMAX); - } else { - Magic2 = L + S - ((L * S) + (HSLMAX / 2)) / HSLMAX; - } - Magic1 = 2 * L - Magic2; - - //get R, G, B; change units from HSLMAX range to RGBMAX range - pRed = (hueToRGB(Magic1, Magic2, H + (HSLMAX / 3)) * RGBMAX + (HSLMAX / 2)) / HSLMAX; - if (pRed > RGBMAX) { - pRed = RGBMAX; - } - - pGreen = (hueToRGB(Magic1, Magic2, H) * RGBMAX + (HSLMAX / 2)) / HSLMAX; - if (pGreen > RGBMAX) { - pGreen = RGBMAX; - } - - pBlue = (hueToRGB(Magic1, Magic2, H - (HSLMAX / 3)) * RGBMAX + (HSLMAX / 2)) / HSLMAX; - if (pBlue > RGBMAX) { - pBlue = RGBMAX; - } - } - } - - private int hueToRGB(int mag1, int mag2, int Hue) { - // check the range - if (Hue < 0) { - Hue = Hue + HSLMAX; - } else if (Hue > HSLMAX) { - Hue = Hue - HSLMAX; - } - - if (Hue < (HSLMAX / 6)) - return (mag1 + (((mag2 - mag1) * Hue + (HSLMAX / 12)) / (HSLMAX / 6))); - - if (Hue < (HSLMAX / 2)) - return mag2; - - if (Hue < (HSLMAX * 2 / 3)) - return (mag1 + (((mag2 - mag1) * ((HSLMAX * 2 / 3) - Hue) + (HSLMAX / 12)) / (HSLMAX / 6))); - - return mag1; - } - - private int iMax(int a, int b) { - if (a > b) return a; - else return b; - } - - private int iMin(int a, int b) { - if (a < b) return a; - else return b; - } - - - public void greyscale() { - initRGBbyHSL(UNDEFINED, 0, pLum); - } - - - // -- - - public int getHue() { - return pHue; - } - - public void setHue(int iToValue) { - while (iToValue < 0) { - iToValue = HSLMAX + iToValue; - } - while (iToValue > HSLMAX) { - iToValue = iToValue - HSLMAX; - } - - initRGBbyHSL(iToValue, pSat, pLum); - } - - // -- - - public int getSaturation() { - return pSat; - } - - public void setSaturation(int iToValue) { - if (iToValue < 0) { - iToValue = 0; - } else if (iToValue > HSLMAX) { - iToValue = HSLMAX; - } - - initRGBbyHSL(pHue, iToValue, pLum); - } - - // -- - - public int getLuminence() { - return pLum; - } - - public void setLuminence(int iToValue) { - if (iToValue < 0) { - iToValue = 0; - } else if (iToValue > HSLMAX) { - iToValue = HSLMAX; - } - - initRGBbyHSL(pHue, pSat, iToValue); - } - - // -- - - public int getRed() { - return pRed; - } - - public void setRed(int iNewValue) { - initHSLbyRGB(iNewValue, pGreen, pBlue); - } - - // -- - - public int getGreen() { - return pGreen; - } - - public void setGreen(int iNewValue) { - initHSLbyRGB(pRed, iNewValue, pBlue); - } - - // -- - - public int getBlue() { - return pBlue; - } - - public void setBlue(int iNewValue) { - initHSLbyRGB(pRed, pGreen, iNewValue); - } - - // -- - - public void reverseColor() { - setHue(pHue + (HSLMAX / 2)); - } - - // -- - - public void reverseLight() { - setLuminence(HSLMAX - pLum); - } - - // -- - - public void brighten(float fPercent) { - int L; - - if (fPercent == 0) { - return; - } - - L = (int) (pLum * fPercent); - if (L < 0) L = 0; - if (L > HSLMAX) L = HSLMAX; - - setLuminence(L); - } - - // -- - // -- - - public void blend(int R, int G, int B, float fPercent) { - if (fPercent >= 1) { - initHSLbyRGB(R, G, B); - return; - } - if (fPercent <= 0) - return; - - int newR = (int) ((R * fPercent) + (pRed * (1.0 - fPercent))); - int newG = (int) ((G * fPercent) + (pGreen * (1.0 - fPercent))); - int newB = (int) ((B * fPercent) + (pBlue * (1.0 - fPercent))); - - initHSLbyRGB(newR, newG, newB); - } -} - - /* - Color.getHSBColor(hue, saturation, brightness - * The hue parameter is a decimal number between 0.0 and 1.0 which indicates the hue of the color. You'll have to experiment with the hue number to find out what color it represents. - * The saturation is a decimal number between 0.0 and 1.0 which indicates how deep the color should be. Supplying a "1" will make the color as deep as possible, and to the other extreme, - * supplying a "0," will take all the color out of the mixture and make it a shade of gray. - * The brightness is also a decimal number between 0.0 and 1.0 which obviously indicates how bright the color should be. - * A 1 will make the color as light as possible and a 0 will make it very dark. - */ - - /* - int maxC = Math.max(Math.max(r,g),b); - int minC = Math.min(Math.min(r,g),b); - int l = (((maxC + minC)*HLSMAX) + RGBMAX)/(2*RGBMAX); - int delta= maxC - minC; - int sum= maxC + minC; - if (delta != 0) { - if (l <= (HLSMAX/2)) - s = ( (delta*HLSMAX) + (sum/2) ) / sum; - else - s = ( (delta*HLSMAX) + ((2*RGBMAX - sum)/2) ) / (2*RGBMAX - sum); - - if (r == maxC) - h = ((g - b) * HLSMAX) / (6 * delta); - else if (g == maxC) - h = ( HLSMAX/3) + ((b - r) * HLSMAX) / (6 * delta); - else if (b == maxC) - h = (2*HLSMAX/3) + ((r - g) * HLSMAX) / (6 * delta); - - if (h < 0) - h += HLSMAX; - else if (h >= HLSMAX) - h -= HLSMAX; - } else { - h = 0; - s = 0; - } - - - */ - - \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/Color.kt b/src/main/java/io/starter/formats/OOXML/Color.kt new file mode 100644 index 0000000..8e3e328 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Color.kt @@ -0,0 +1,816 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +/** + * color (Data Bar Color) + * One of the colors associated with the data bar or color scale. Also font ... + * Note: the auto attribute is not used in the context of data bars + * + * + * NOTE: both color, fgColor and bgColor use same CT_COLOR schema: + * + * + * + * + * + * + * + * + * + * fgColor (Foreground Color) + * Foreground color of the cell fill pattern. Cell fill patterns operate with two colors: a background color and a + * foreground color. These combine together to make a patterned cell fill. + * + * + * + * + * bgColor (Background Color) + * Background color of the cell fill pattern. Cell fill patterns operate with two colors: a background color and a + * foreground color. These combine together to make a patterned cell fill. + */ +class Color : OOXMLElement { + private var auto = false + /** + * return the color type for this OOXML color + * (indexed= 0, rgb= 1, theme= 2) + * + * @return + */ + var colorType = -1 + private set + private var colorval: String? = null // value of colortype + private var tint = 0.0 + private var element: String? = null + private var colorint = -1 // parsed color (tint + value) translated to OpenXLS color int value + /** + * return the translated HTML color string for this OOXML color + * + * @return + */ + var colorAsOOXMLRBG: String? = null + private set // parsed color (tint + value) translated to HTML color string + private var theme: Theme? = null + + override// rgb + // theme + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("<" + this.element!!) + if (this.auto) + ooxml.append(" auto=\"1\"") + else if (this.colorType == COLORTYPERGB) + ooxml.append(" rgb=\"$colorAsOOXMLRBG\"") + else if (this.colorType == COLORTYPEINDEXED) + ooxml.append(" indexed=\"$colorval\"") + else if (this.colorType == COLORTYPETHEME) + ooxml.append(" theme=\"$colorval\"") + if (tint != 0.0) + ooxml.append(" tint=\"$tint\"") + ooxml.append("/>") + return ooxml.toString() + } + + /** + * return the translated color int for this OOXML color + * + * @return + */ + /** + * manually set color int + * automatically defaults to indexed color, and looks up color in COLORTABLE + * + * @param clr + */ + // indexed + // reset other vars as well + var colorInt: Int + get() = this.colorint + set(clr) { + this.colorint = clr + this.colorType = 0 + if (clr > -1) + this.colorAsOOXMLRBG = FormatHandle.colorToHexString(FormatHandle.COLORTABLE[clr]).substring(1) + else + this.colorAsOOXMLRBG = null + this.tint = 0.0 + this.auto = false + } + + val colorAsColor: java.awt.Color + get() = FormatHandle.HexStringToColor(this.colorAsOOXMLRBG!!) + + constructor() {} + + constructor(element: String, auto: Boolean, colortype: Int, colorval: String, tint: Double, type: Short, t: Theme) { + this.element = element + this.auto = auto + this.colorType = colortype + this.colorval = colorval + this.tint = tint + this.theme = t + this.parseColor(type) + } + + constructor(c: Color) { + this.element = c.element + this.auto = c.auto + this.colorType = c.colorType + this.colorval = c.colorval + this.tint = c.tint + this.colorint = c.colorint + this.colorAsOOXMLRBG = c.colorAsOOXMLRBG + this.theme = c.theme + } + + + /** + * creates a new Color object based upon a java.awt.Color + * + * @param clr Color objeect + * @param element "color", "fgColor" or "bgColor" + */ + constructor(c: java.awt.Color, element: String, t: Theme) { + this.colorType = COLORTYPERGB + this.colorval = "FF" + FormatHandle.colorToHexString(c).substring(1) + this.element = element + this.theme = t // ok if it's null + this.parseColor(0.toShort()) + } + + /** + * creates a new Color object based upon a web-compliant Hex Color string + * + * @param clr + * @param element "color", "fgColor" or "bgColor" + */ + constructor(clr: String, element: String, t: Theme) { + this.colorType = COLORTYPERGB + if (clr.startsWith("#")) + this.colorval = "FF" + clr.substring(1) + else if (clr.length == 6) + this.colorval = "FF$clr" + else + this.colorval = clr + this.element = element + this.theme = t // ok if it's null + this.parseColor(0.toShort()) + } + + override fun cloneElement(): OOXMLElement { + return Color(this) + } + + override fun toString(): String { + var ret = "" + if (colorType == COLORTYPERGB) + // rgb + ret = " rgb=" + colorAsOOXMLRBG!! + else if (colorType == COLORTYPEINDEXED) + // indexed + ret = " indexed=" + colorval!! + else if (colorType == COLORTYPETHEME) + // theme + ret = " theme=" + colorval!! + if (tint != 0.0) + ret = " tint=$tint" + return ret + } + + /** + * sets the color via java.awt.Color + * + * @param c + */ + fun setColor(c: java.awt.Color) { + this.colorType = COLORTYPERGB + this.colorval = "FF" + FormatHandle.colorToHexString(c).substring(1) + this.parseColor(0.toShort()) + this.tint = 0.0 + this.auto = false + } + + + /** + * sets the color via a web-compliant Hex Color String + * + * @param clr + */ + fun setColor(clr: String) { + this.colorType = COLORTYPERGB + if (clr.startsWith("#")) + this.colorval = "FF" + clr.substring(1) + else if (clr.length == 6) + this.colorval = "FF$clr" + else + this.colorval = clr + this.parseColor(0.toShort()) + } + + /** + * simple utility to parse correct colors (int + html string) from OOXML style color + * + * @param type 0= font, -1= fill + */ + private fun parseColor(type: Short) { + try { + if (this.colorType == COLORTYPERGB) { // rgb - color string + this.colorint = FormatHandle.HexStringToColorInt(this.colorval!!, type) // find best match + this.colorAsOOXMLRBG = this.colorval + } else if (this.colorType == COLORTYPEINDEXED) { // indexed (corresponds to either our color int or a custom set of indexed colors + this.colorint = Integer.valueOf(this.colorval!!).toInt() + if (this.colorint == 64 && type == FormatHandle.colorFONT) + // means system foreground: Default foreground color. This is the window text color in the sheet display. + this.colorAsOOXMLRBG = FormatHandle.colorToHexString(FormatHandle.getColor(0)) + else + this.colorAsOOXMLRBG = FormatHandle.colorToHexString(FormatHandle.getColor(this.colorint))// this.colorint= 0; // black + } else if (this.colorType == COLORTYPETHEME) { // theme + val o = Color.parseThemeColor(this.colorval, this.tint, type, this.theme) + this.colorint = o[0] as Int + this.colorAsOOXMLRBG = o[1] as String + } + } catch (e: Exception) { + Logger.logWarn("color.parseColor: $colorType:$colorval: $e") + } + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2546003092245407502L + var COLORTYPEINDEXED = 0 + var COLORTYPERGB = 1 + var COLORTYPETHEME = 2 + + /** + * parses a color element + * root= color, fgColor or bgColor + * + * @param xpp + * @return + */ + fun parseOOXML(xpp: XmlPullParser, type: Short, bk: WorkBookHandle): OOXMLElement { + var element: String? = null + var auto = false + var colortype = -1 + var colorval: String? = null + var tint = 0.0 + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "color" || + tnm == "fgColor" || + tnm == "bgColor") { // get attributes + element = tnm // save element name + for (i in 0 until xpp.attributeCount) { + val n = xpp.getAttributeName(i) + val `val` = xpp.getAttributeValue(i) + if (n == "auto") { + auto = true + } else if (n == "indexed") { + colortype = Color.COLORTYPEINDEXED + colorval = `val` + } else if (n == "rgb") { + colortype = COLORTYPERGB + colorval = `val` + } else if (n == "theme") { + colortype = COLORTYPETHEME + colorval = `val` + } else if (n == "tint") { + tint = Double(`val`) + } + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == element) { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("color.parseOOXML: $e") + } + + return Color(element, auto, colortype, colorval, tint, type, bk.workBook!!.theme) + } + + fun getOOXML(element: String, colortype: Int, colorval: Int, colorstr: String, tint: Double): String { + val ooxml = StringBuffer() + ooxml.append("<$element") + if (colortype == COLORTYPERGB) + // rgb + ooxml.append(" rgb=\"$colorstr\"") + else if (colortype == COLORTYPEINDEXED) + // indexed + ooxml.append(" indexed=\"$colorval\"") + else if (colortype == COLORTYPETHEME) + // theme + ooxml.append(" theme=\"$colorval\"") + if (tint != 0.0) + ooxml.append(" tint=\"$tint\"") + ooxml.append("/>") + return ooxml.toString() + } + + /** + * static version of parseColor --takes a color value of OOXML type type + *

                  COLORTYPERBG, COLORTYPEINDEXED or COLORTYPETHEME + *

                  and returns the + * + * @param val String OOXML color value, value depends upon colortype + * @param colortype int val's colortype, one of: Color.COLORTYPERGB, Color.COLORTYPEINDEXED, Color.COLORTYPETHEME + * @param type 0= font, 1= fill + * @return String HEX-style color string + */ + fun parseColor(`val`: String, colortype: Int, type: Short, t: Theme): String? { + val c = Color("", false, colortype, `val`, 0.0, type, t) + return c.colorAsOOXMLRBG + } + + /** + * static version of parseColor --takes a color value of OOXML type type + *

                  COLORTYPERBG, COLORTYPEINDEXED or COLORTYPETHEME + *

                  and returns the indexed color int it represents + * + * @param val String OOXML color value, value depends upon colortype + * @param colortype int val's colortype, one of: Color.COLORTYPERGB, Color.COLORTYPEINDEXED, Color.COLORTYPETHEME + * @param type 0= font, 1= fill + * @return int + */ + fun parseColorInt(`val`: String, colortype: Int, type: Short, t: Theme): Int { + val c = Color("", false, colortype, `val`, 0.0, type, t) + // c.parseColor(type); + return c.colorint + } + + /** + * interprets theme colorval and tint + * TODO: read in theme colors from theme1.xml + * + * @param colorval String theme colorval (see SchemeClr and Theme for details) + * EITHER an Index into the collection, referencing a particular or value expressed in the Theme part. + * OR the actual clrScheme string + * @param tint double tint to apply to the base color (0 for none) + * @param type short 0= font, 1= fill + * @return + */ + fun parseThemeColor(colorval: String?, tint: Double, type: Short, t: Theme?): Array { + /** + * TODO: read this in from THEME!!! + */ + val themeColors = arrayOf(arrayOf("dk1", "000000"), arrayOf("lt1", "FFFFFF"), arrayOf("dk2", "1F497D"), arrayOf("lt2", "EEECE1"), arrayOf("accent1", "4F81BD"), arrayOf("accent1", "C0504D"), arrayOf("accent3", "9BBB59"), arrayOf("accent4", "8064A2"), arrayOf("accent5", "4BACC6"), arrayOf("accent6", "F79646"), arrayOf("hlink", "0000FF"), arrayOf("folHlink", "800080")) + + // TODO: read theme colors from file + val o = arrayOfNulls(2) + var i = 0 + var clr = "" + try { + i = Integer.valueOf(colorval!!).toInt() + clr = t!!.genericThemeClrs[i] + } catch (e: Exception) { // OpenXLS-created xlsx files will not have theme entries + i = 0 + while (i < themeColors.size) { + if (themeColors[i][0] == colorval) { + clr = themeColors[i][1] + break + } + i++ + } + } + + if (Math.abs(tint) > .005) { + val c = applyTint(tint, clr) + o[0] = FormatHandle.getColorInt(c) + o[1] = FormatHandle.colorToHexString(c).substring(1) // avoid # + } else { + o[0] = FormatHandle.HexStringToColorInt("#$clr", type) + o[1] = "#$clr" + } + return o + } + + + /** + * If tint is supplied, then it is applied to the RGB value of the color to determine the final + * color applied. + * The tint value is stored as a double from -1.0 .. 1.0, where -1.0 means 100% darken and + * 1.0 means 100% lighten. Also, 0.0 means no change. + * In loading the RGB value, it is converted to HLS where HLS values are (0..HLSMAX), where + * HLSMAX is currently 255. + */ + private fun applyTint(tint: Double, clr: String): java.awt.Color { + val HLSMAX = 255 + val r = Integer.parseInt(clr.substring(0, 2), 16) + val g = Integer.parseInt(clr.substring(2, 4), 16) + val b = Integer.parseInt(clr.substring(4, 6), 16) + + val hsl = HSLColor() + hsl.initHSLbyRGB(r, g, b) + var l = hsl.luminence.toDouble() + if (tint < 0) + //darken + l = l * (1 + tint) + else + // lighten + l = l * (1 - tint) + (HLSMAX - HLSMAX * (1.0 - tint)) + l = Math.round(l).toDouble() + hsl.initRGBbyHSL(hsl.hue, hsl.saturation, l.toInt()) + return java.awt.Color(hsl.red, hsl.green, hsl.blue) + + } + } +} + + +/* HSL stands for hue, saturation, lightness + * Both HSL and HSV describe colors as points in a cylinder + * whose central axis ranges from black at the bottom to white at the top + * with neutral colors between them, + * where angle around the axis corresponds to hue, + * distance from the axis corresponds to saturation, + * and distance along the axis corresponds to lightness, value, or brightness.*/ + +/* + * Conversion from RGB to HSL + + Let r, g, b E [0,1] be the red, green, and blue coordinates, respectively, of a color in RGB space. + Let max be the greatest of r, g, and b, and min the least. + + hue angle h: + + h= 0 if max=min (for grays) + = 60 deg x (g-b)/(max-min) + 360 deg) mod 360 deg if max=r + = 60 deg x (b-r)/(max-min) + 120 deg) if max=g + = 60 deg x (r-g)/(max-min) + 240 deg) if max=b + + lightness l= (max-min)/2 + + Saturation s + + s= 0 if max=min + = (max-min)/2l if l <= 1/2 + = (max-min)/(2-2l) if l > 1/2 + + The value of h is generally normalized to lie between 0 and 360, and h = 0 is used when max = min (that is, for grays) + though the hue has no geometric meaning there, where the saturation s is zero. + Similarly, the choice of 0 as the value for s when l is equal to 0 or 1 is arbitrary. + + Conversion from HSL to RGB + + Given a color defined by (h, s, l) values in HSL space, with h in the range [0, 360), indicating the angle, + in degrees of the hue, and with s and l in the range [0, 1], representing the saturation and lightness, respectively, + a corresponding (r, g, b) triplet in RGB space, with r, g, and b also in range [0, 1], and corresponding to red, green, and blue, + respectively, can be computed as follows: + + First, if s = 0, then the resulting color is achromatic, or gray. In this special case, r, g, and b all equal l. + Note that the value of h is ignored, and may be undefined in this situation. + + The following procedure can be used, even when s is zero: + + q= l x (1+s) if l < 1/2 + = l + s - (l x s) if l >= 1/2 + + p= 2 x l - q + + hk= h / 360 (h normalized in the range 0-1) + + tr= hk + 1/3 + + tg= hk + + tb= hk - 1/3 + + if tc + + */ +internal class HSLColor { + + private var pHue: Int = 0 + private var pSat: Int = 0 + private var pLum: Int = 0 + private var pRed: Int = 0 + private var pGreen: Int = 0 + private var pBlue: Int = 0 + + + // -- + + var hue: Int + get() = pHue + set(iToValue) { + var iToValue = iToValue + while (iToValue < 0) { + iToValue = HSLMAX + iToValue + } + while (iToValue > HSLMAX) { + iToValue = iToValue - HSLMAX + } + + initRGBbyHSL(iToValue, pSat, pLum) + } + + // -- + + var saturation: Int + get() = pSat + set(iToValue) { + var iToValue = iToValue + if (iToValue < 0) { + iToValue = 0 + } else if (iToValue > HSLMAX) { + iToValue = HSLMAX + } + + initRGBbyHSL(pHue, iToValue, pLum) + } + + // -- + + var luminence: Int + get() = pLum + set(iToValue) { + var iToValue = iToValue + if (iToValue < 0) { + iToValue = 0 + } else if (iToValue > HSLMAX) { + iToValue = HSLMAX + } + + initRGBbyHSL(pHue, pSat, iToValue) + } + + // -- + + var red: Int + get() = pRed + set(iNewValue) = initHSLbyRGB(iNewValue, pGreen, pBlue) + + // -- + + var green: Int + get() = pGreen + set(iNewValue) = initHSLbyRGB(pRed, iNewValue, pBlue) + + // -- + + var blue: Int + get() = pBlue + set(iNewValue) = initHSLbyRGB(pRed, pGreen, iNewValue) + + fun initHSLbyRGB(R: Int, G: Int, B: Int) { + // sets Hue, Sat, Lum + val cMax: Int + val cMin: Int + val RDelta: Int + val GDelta: Int + val BDelta: Int + val cMinus: Int + val cPlus: Int + + pRed = R + pGreen = G + pBlue = B + + //Set Max & MinColor Values + cMax = iMax(iMax(R, G), B) + cMin = iMin(iMin(R, G), B) + + cMinus = cMax - cMin + cPlus = cMax + cMin + + // Calculate luminescence (lightness) + pLum = (cPlus * HSLMAX + RGBMAX) / (2 * RGBMAX) + + if (cMax == cMin) { + // greyscale + pSat = 0 + pHue = UNDEFINED + } else { + // Calculate color saturation + if (pLum <= HSLMAX / 2) { + pSat = ((cMinus * HSLMAX + 0.5) / cPlus).toInt() + } else { + pSat = ((cMinus * HSLMAX + 0.5) / (2 * RGBMAX - cPlus)).toInt() + } + + //Calculate hue + RDelta = (((cMax - R) * (HSLMAX / 6) + 0.5) / cMinus).toInt() + GDelta = (((cMax - G) * (HSLMAX / 6) + 0.5) / cMinus).toInt() + BDelta = (((cMax - B) * (HSLMAX / 6) + 0.5) / cMinus).toInt() + + if (cMax == R) { + pHue = BDelta - GDelta + } else if (cMax == G) { + pHue = HSLMAX / 3 + RDelta - BDelta + } else if (cMax == B) { + pHue = 2 * HSLMAX / 3 + GDelta - RDelta + } + + if (pHue < 0) { + pHue = pHue + HSLMAX + } + } + } + + fun initRGBbyHSL(H: Int, S: Int, L: Int) { + val Magic1: Int + val Magic2: Int + + pHue = H + pLum = L + pSat = S + + if (S == 0) { //Greyscale + pRed = L * RGBMAX / HSLMAX //luminescence: set to range + pGreen = pRed + pBlue = pRed + } else { + if (L <= HSLMAX / 2) { + Magic2 = (L * (HSLMAX + S) + HSLMAX / 2) / HSLMAX + } else { + Magic2 = L + S - (L * S + HSLMAX / 2) / HSLMAX + } + Magic1 = 2 * L - Magic2 + + //get R, G, B; change units from HSLMAX range to RGBMAX range + pRed = (hueToRGB(Magic1, Magic2, H + HSLMAX / 3) * RGBMAX + HSLMAX / 2) / HSLMAX + if (pRed > RGBMAX) { + pRed = RGBMAX + } + + pGreen = (hueToRGB(Magic1, Magic2, H) * RGBMAX + HSLMAX / 2) / HSLMAX + if (pGreen > RGBMAX) { + pGreen = RGBMAX + } + + pBlue = (hueToRGB(Magic1, Magic2, H - HSLMAX / 3) * RGBMAX + HSLMAX / 2) / HSLMAX + if (pBlue > RGBMAX) { + pBlue = RGBMAX + } + } + } + + private fun hueToRGB(mag1: Int, mag2: Int, Hue: Int): Int { + var Hue = Hue + // check the range + if (Hue < 0) { + Hue = Hue + HSLMAX + } else if (Hue > HSLMAX) { + Hue = Hue - HSLMAX + } + + if (Hue < HSLMAX / 6) + return mag1 + ((mag2 - mag1) * Hue + HSLMAX / 12) / (HSLMAX / 6) + + if (Hue < HSLMAX / 2) + return mag2 + + return if (Hue < HSLMAX * 2 / 3) mag1 + ((mag2 - mag1) * (HSLMAX * 2 / 3 - Hue) + HSLMAX / 12) / (HSLMAX / 6) else mag1 + + } + + private fun iMax(a: Int, b: Int): Int { + return if (a > b) + a + else + b + } + + private fun iMin(a: Int, b: Int): Int { + return if (a < b) + a + else + b + } + + + fun greyscale() { + initRGBbyHSL(UNDEFINED, 0, pLum) + } + + // -- + + fun reverseColor() { + hue = pHue + HSLMAX / 2 + } + + // -- + + fun reverseLight() { + luminence = HSLMAX - pLum + } + + // -- + + fun brighten(fPercent: Float) { + var L: Int + + if (fPercent == 0f) { + return + } + + L = (pLum * fPercent).toInt() + if (L < 0) L = 0 + if (L > HSLMAX) L = HSLMAX + + luminence = L + } + + // -- + // -- + + fun blend(R: Int, G: Int, B: Int, fPercent: Float) { + if (fPercent >= 1) { + initHSLbyRGB(R, G, B) + return + } + if (fPercent <= 0) + return + + val newR = (R * fPercent + pRed * (1.0 - fPercent)).toInt() + val newG = (G * fPercent + pGreen * (1.0 - fPercent)).toInt() + val newB = (B * fPercent + pBlue * (1.0 - fPercent)).toInt() + + initHSLbyRGB(newR, newG, newB) + } + + companion object { + private val HSLMAX = 255 + private val RGBMAX = 255 + private val UNDEFINED = 170 + } +} + +/* + Color.getHSBColor(hue, saturation, brightness + * The hue parameter is a decimal number between 0.0 and 1.0 which indicates the hue of the color. You'll have to experiment with the hue number to find out what color it represents. + * The saturation is a decimal number between 0.0 and 1.0 which indicates how deep the color should be. Supplying a "1" will make the color as deep as possible, and to the other extreme, + * supplying a "0," will take all the color out of the mixture and make it a shade of gray. + * The brightness is also a decimal number between 0.0 and 1.0 which obviously indicates how bright the color should be. + * A 1 will make the color as light as possible and a 0 will make it very dark. + */ + +/* + int maxC = Math.max(Math.max(r,g),b); + int minC = Math.min(Math.min(r,g),b); + int l = (((maxC + minC)*HLSMAX) + RGBMAX)/(2*RGBMAX); + int delta= maxC - minC; + int sum= maxC + minC; + if (delta != 0) { + if (l <= (HLSMAX/2)) + s = ( (delta*HLSMAX) + (sum/2) ) / sum; + else + s = ( (delta*HLSMAX) + ((2*RGBMAX - sum)/2) ) / (2*RGBMAX - sum); + + if (r == maxC) + h = ((g - b) * HLSMAX) / (6 * delta); + else if (g == maxC) + h = ( HLSMAX/3) + ((b - r) * HLSMAX) / (6 * delta); + else if (b == maxC) + h = (2*HLSMAX/3) + ((r - g) * HLSMAX) / (6 * delta); + + if (h < 0) + h += HLSMAX; + else if (h >= HLSMAX) + h -= HLSMAX; + } else { + h = 0; + s = 0; + } + + + */ + + \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/ColorChoice.java b/src/main/java/io/starter/formats/OOXML/ColorChoice.java deleted file mode 100644 index 6296c46..0000000 --- a/src/main/java/io/starter/formats/OOXML/ColorChoice.java +++ /dev/null @@ -1,821 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * ColorChoice: choice of: hslClr (Hue, Saturation, Luminance Color Model) - * prstClr (Preset Color) §5.1.2.2.22 schemeClr (Scheme Color) §5.1.2.2.29 - * scrgbClr (RGB Color Model - Percentage Variant) srgbClr (RGB Color Model - - * Hex Variant) sysClr (System Color) - */ -// TODO: FINISH: child elements governing color transformations -// finish hslClr -public class ColorChoice implements OOXMLElement { - - private static final long serialVersionUID = -4117811305941771643L; - private SchemeClr s; - private SrgbClr srgb; - private SysClr sys; - private ScrgbClr scrgb; - private PrstClr p; - public Theme theme; - - public ColorChoice(SchemeClr s, SrgbClr srgb, SysClr sys, ScrgbClr scrgb, - PrstClr p) { - this.s = s; - this.srgb = srgb; - this.sys = sys; - this.scrgb = scrgb; - this.p = p; - } - - private void setTheme(Theme t) { - this.theme = t; - } - - public ColorChoice(ColorChoice c) { - this.s = c.s; - this.srgb = c.srgb; - this.sys = c.sys; - this.scrgb = c.scrgb; - this.p = c.p; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - SchemeClr s = null; - SrgbClr srgb = null; - SysClr sys = null; - ScrgbClr scrgb = null; - PrstClr p = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("schemeClr")) { - lastTag.push(tnm); - s = SchemeClr.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("srgbClr")) { - lastTag.push(tnm); - srgb = SrgbClr.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("sysClr")) { - lastTag.push(tnm); - sys = SysClr.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("scrgbClr")) { - lastTag.push(tnm); - scrgb = ScrgbClr.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("prstClr")) { - lastTag.push(tnm); - p = PrstClr.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } - /* tnm.equals("hslClr") */// TODO: finish - - } else if (eventType == XmlPullParser.END_TAG) { // shouldn't - // get here - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ColorChoice.parseOOXML: " + e.toString()); - } - ColorChoice c = new ColorChoice(s, srgb, sys, scrgb, p); - c.setTheme(bk.getWorkBook().getTheme()); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (s != null) - ooxml.append(s.getOOXML()); - else if (sys != null) - ooxml.append(sys.getOOXML()); - else if (srgb != null) - ooxml.append(srgb.getOOXML()); - else if (scrgb != null) - ooxml.append(scrgb.getOOXML()); - else if (p != null) - ooxml.append(p.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ColorChoice(this); - } - - public int getColor() { - if (s != null) - return s.getColor(); - else if (sys != null) - sys.getColor(); - else if (srgb != null) - srgb.getColor(); - else if (scrgb != null) - scrgb.getColor(); - else if (p != null) - p.getColor(); - return -1; - - } - -} - -/** - * schemeClr (Scheme Color) This element specifies a color bound to a user's - * theme. As with all elements which define a color, it is possible to apply a - * list of color transforms to the base color defined. - *

                  - * accent1 (Accent Color 1) Extra scheme color 1 accent2 (Accent Color 2) Extra - * scheme color 2 accent3 (Accent Color 3) Extra scheme color 3 accent4 (Accent - * Color 4) Extra scheme color 4 accent5 (Accent Color 5) Extra scheme color 5 - * accent6 (Accent Color 6) Extra scheme color 6 bg1 (Background Color 1) - * Semantic background color bg2 (Background Color 2) Semantic additional - * background color dk1 (Dark Color 1) Main dark color 1 dk2 (Dark Color 2) Main - * dark color 2 folHlink (Followed Hyperlink Color) Followed Hyperlink Color - * hlink (Hyperlink Color) Regular Hyperlink Color lt1 (Light Color 1) Main - * Light Color 1 lt2 (Light Color 2) Main Light Color 2 phClr (Style Color) A - * color used in theme definitions which means to use the color of the style. - * tx1 (Text Color 1) Semantic text color tx2 (Text Color 2) Semantic additional - * text color - *

                  - * parent: many children: many - TODO: handle color transformation children - * (alpha ...) - */ -class SchemeClr implements OOXMLElement { - - private static final long serialVersionUID = 2127868578801669266L; - private String val; - private ColorTransform clrTransform; - private Theme theme; - - public SchemeClr(String val, ColorTransform clrTransform, Theme t) { - this.val = val; - this.clrTransform = clrTransform; - this.theme = t; - } - - public SchemeClr(SchemeClr sc) { - this.val = sc.val; - this.clrTransform = sc.clrTransform; - this.theme = sc.theme; - } - - public static SchemeClr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String val = null; - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("schemeClr")) { - val = xpp.getAttributeValue(0); - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("schemeClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("schemeClr.parseOOXML: " + e.toString()); - } - SchemeClr sc = new SchemeClr(val, clrTransform, bk.getWorkBook().getTheme()); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SchemeClr(this); - } - - public int getColor() { - double tint = (clrTransform == null ? 0 : clrTransform.getTint()); - Object[] o = Color.parseThemeColor(val, tint, (short) 0, this.theme); - return (Integer) o[0]; - } -} - -/** - * sysClr (System Color) This element specifies a color bound to predefined - * operating system elements. // TODO: appropriate to hard-code??? - *

                  - * parent: many children: COLORSTRANSFORM - */ -class SysClr implements OOXMLElement { - - private static final long serialVersionUID = 8307422721346337409L; - private String val; // This simple type specifies a system color - // value. This color is based upon the value - // that this color currently has within the - // system on which the document is being viewed. - private String lastClr; // Specifies the color value that was last - // computed by the generating application. - // Applications shall use the lastClr - // attribute to determine the absolute value - // of the last color used if system colors - // are not supported. - private ColorTransform clrTransform; - - public SysClr(String val, String lastClr, ColorTransform clrTransform) { - this.val = val; - this.lastClr = lastClr; - this.clrTransform = clrTransform; - } - - public SysClr(SysClr sc) { - this.val = sc.val; - this.lastClr = sc.lastClr; - this.clrTransform = sc.clrTransform; - } - - public static SysClr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String val = null; - String lastClr = null; - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sysClr")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("val")) { // - val = xpp.getAttributeValue(i); - } else if (nm.equals("lastClr")) { - lastClr = xpp.getAttributeValue(i); - } - } - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("sysClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("sysClr.parseOOXML: " + e.toString()); - } - SysClr sc = new SysClr(val, lastClr, clrTransform); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - // TODO: Handle child elements - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SysClr(this); - } - - /** - * return the color int that represents this system color - * - * @return - */ - public int getColor() { - for (int i = 0; i < OOXMLConstants.systemColors.length; i++) { - if (OOXMLConstants.systemColors[i][0].equals(val)) { - return FormatHandle.HexStringToColorInt( - OOXMLConstants.systemColors[i][1], (short) 0); - } - } - return -1; - } -} - -/** - * srgbClr (RGB Color Model - Hex Variant) - *

                  - * This element specifies a color using the red, green, blue RGB color model. - * Red, green, and blue is expressed as sequence of hex digits, RRGGBB. A - * perceptual gamma of 2.2 is used. Specifies the level of red as expressed by a - * percentage offset increase or decrease relative to the input color. - *

                  - * parent: many children: COLORSTRANSFORM - */ -class SrgbClr implements OOXMLElement { - - private static final long serialVersionUID = -999813417659560045L; - private String val; - private ColorTransform clrTransform; - - public SrgbClr(String val, ColorTransform clrTransform) { - this.val = val; - this.clrTransform = clrTransform; - } - - public SrgbClr(SrgbClr sc) { - this.val = sc.val; - this.clrTransform = sc.clrTransform; - } - - public static SrgbClr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String val = null; - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("srgbClr")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("val")) { - val = xpp.getAttributeValue(i); - } - } - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("srgbClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("srgbClr.parseOOXML: " + e.toString()); - } - SrgbClr sc = new SrgbClr(val, clrTransform); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SrgbClr(this); - } - - /** - * interpret val and return color int from color table - * - * @return - */ - public int getColor() { - return FormatHandle.HexStringToColorInt(val, (short) 0); - } -} - -/** - * scrgbClr (Scheme Color) - *

                  - * This element specifies a color using the red, green, blue RGB color model. - * Each component, red, green, and blue is expressed as a percentage from 0% to - * 100%. A linear gamma of 1.0 is assumed. - *

                  - * parent: many children: COLORSTRANSFORM - */ -class ScrgbClr implements OOXMLElement { - - private static final long serialVersionUID = -8782954669829478560L; - private HashMap attrs; - private ColorTransform clrTransform; - - public ScrgbClr(HashMap attrs, ColorTransform clrTransform) { - this.attrs = attrs; - this.clrTransform = clrTransform; - } - - public ScrgbClr(ScrgbClr sc) { - this.attrs = sc.attrs; - this.clrTransform = sc.clrTransform; - } - - public static ScrgbClr parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("scrgbClr")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), - xpp.getAttributeValue(i)); // r, g, b - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("scrgbClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("scrgbClr.parseOOXML: " + e.toString()); - } - ScrgbClr sc = new ScrgbClr(attrs, clrTransform); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ScrgbClr(this); - } - - /** - * interpret the rbg value into a color int/index into color table - * - * @return - */ - public int getColor() { - // r, g, b in percentages (in 1000th of a percentage) - double rval = Integer.valueOf(attrs.get("r")) / 100000; // perecentage - rval *= 255; - double gval = Integer.valueOf(attrs.get("g")) / 100000; - gval *= 255; - double bval = Integer.valueOf(attrs.get("b")) / 100000; - bval *= 255; - if (clrTransform != null) - Logger.logWarn("Scheme Color must process color transforms"); - java.awt.Color c = new java.awt.Color((int) rval, (int) gval, - (int) bval); - return FormatHandle.getColorInt(c); - } -} - -/** - * prstClr (Preset Color) This element specifies a color which is bound to one - * of a predefined collection of colors. - *

                  - * parent: many children: many - */ -class PrstClr implements OOXMLElement { - - private static final long serialVersionUID = -5773022185972396279L; - private String val; - private ColorTransform clrTransform; - - public PrstClr(String val, ColorTransform clrTransform) { - this.val = val; - this.clrTransform = clrTransform; - } - - public PrstClr(PrstClr sc) { - this.val = sc.val; - this.clrTransform = sc.clrTransform; - } - - public static PrstClr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String val = null; - ColorTransform clrTransform = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstClr")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("val")) { - val = xpp.getAttributeValue(i); - } - } - } else { - clrTransform = ColorTransform.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("prstClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("prstClr.parseOOXML: " + e.toString()); - } - PrstClr sc = new PrstClr(val, clrTransform); - return sc; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (clrTransform != null) - ooxml.append(clrTransform.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PrstClr(this); - } - - public int getColor() { - if (clrTransform != null) - Logger.logWarn("Preset Color must process color transforms"); - for (int i = 0; i < FormatHandle.COLORNAMES.length; i++) { - if (FormatHandle.COLORNAMES[i].equalsIgnoreCase(val)) - return i; - } - return -1; - } -} - -/** - * common Color Transformes used by parent elements: schemeColor, systemColor, - * hslColor, presetColor, sRgbColor, scRgbColor color adjustments are in - * percentage units
                  - * // TODO: Finish // comp // inv // gamma // invGamma // gray, red, green, blue - */ -class ColorTransform { - private int[] lum; - private int[] hue; - private int[] sat; - private int[] alpha; - private int tint; - private int shade; - - // TODO: Finish - // comp - // inv - // gamma - // gray, red, green, blue - - public int getTint() { - return tint; - } - - public ColorTransform(int[] lum, int[] hue, int[] sat, int[] alpha, - int tint, int shade) { - this.lum = lum; - this.hue = hue; - this.sat = sat; - this.alpha = alpha; - this.tint = tint; - this.shade = shade; - } - - /** - * parse color transform elements, common children of color-type elements:
                  - * schemeColor, systemColor, hslColor, presetColor, sRgbColor, scRgbColor - * - * @param xpp - * @param lastTag - * @return - */ - public static ColorTransform parseOOXML(XmlPullParser xpp, Stack lastTag) { - int[] lum = null; - int[] hue = null; - int[] sat = null; - int[] alpha = null; - int tint = 0; - int shade = 0; - try { - String parentEl = lastTag.peek(); - - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("lum")) { // This element specifies the input - // color with its luminance - // modulated by the given - // percentage. - if (lum == null) - lum = new int[3]; - lum[0] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("lumMod")) { // This element specifies - // the input color with - // its luminance - // modulated by the - // given percentage. - if (lum == null) - lum = new int[3]; - lum[1] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("lumOff")) { // This element specifies - // the input color with - // its luminance - // shifted, but with its - // hue and saturation - // unchanged. - if (lum == null) - lum = new int[3]; - lum[2] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("hue")) { // This element specifies - // the input color with the - // specified hue, but with - // its saturation and - // luminance unchanged. - if (hue == null) - hue = new int[3]; - hue[0] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("hueMod")) { - if (hue == null) - hue = new int[3]; - hue[1] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("hueOff")) { - if (hue == null) - hue = new int[3]; - hue[2] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("sat")) { // This element specifies - // the input color with the - // specified saturation, but - // with its hue and - // luminance unchanged. - if (sat == null) - sat = new int[3]; - sat[0] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("satMod")) { - if (sat == null) - sat = new int[3]; - sat[1] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("satOff")) { - if (sat == null) - sat = new int[3]; - sat[2] = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("shade")) { // This element specifies - // a darker version of - // its input color - shade = Integer.valueOf(xpp.getAttributeValue(0)); - } else if (tnm.equals("tint")) { - tint = Integer.valueOf(xpp.getAttributeValue(0)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals(parentEl)) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ColorTransform.parseOOXML: " + e.toString()); - } - return new ColorTransform(lum, hue, sat, alpha, tint, shade); - } - - /** - * returns the OOXML associated with color transforms of a parent color - * element
                  - * note that these color transforms must be part of either
                  - * schemeColor, systemColor, hslColor, presetColor, sRgbColor, scRgbColor - * - * @return - */ - public StringBuffer getOOXML() { - StringBuffer ooxml = new StringBuffer(); - - /** - * a:tint Tint a:shade Shade a:comp Complement a:inv Inverse a:gray Gray - * a:alpha Alpha a:alphaOff Alpha Offset a:alphaMod Alpha Modulation - * a:hue Hue a:hueOff Hue Offset a:hueMod Hue Modulate a:sat Saturation - * a:satOff Saturation Offset a:satMod Saturation Modulation a:lum - * Luminance a:lumOff Luminance Offset a:lumMod Luminance Modulation - * a:red Red a:redOff Red Offset a:redMod Red Modulation a:green Green - * a:greenOff Green Offset a:greenMod Green Modification a:blue Blue - * a:blueOff Blue Offset a:blueMod Blue Modification a:gamma Gamma - * a:invGamma Inverse Gamma - */ - if (tint != 0) - ooxml.append(""); - if (shade != 0) - ooxml.append(""); - // Complement - // Inverse - // Gray - if (alpha != null) { - if (alpha[0] != 0) - ooxml.append(""); - if (alpha[2] != 0) - ooxml.append(""); - if (alpha[1] != 0) - ooxml.append(""); - } - if (hue != null) { - if (hue[0] != 0) - ooxml.append(""); - if (hue[2] != 0) - ooxml.append(""); - if (hue[1] != 0) - ooxml.append(""); - } - if (sat != null) { - if (sat[0] != 0) - ooxml.append(""); - if (sat[2] != 0) - ooxml.append(""); - if (sat[1] != 0) - ooxml.append(""); - } - if (lum != null) { - if (lum[0] != 0) - ooxml.append(""); - if (lum[2] != 0) - ooxml.append(""); - if (lum[1] != 0) - ooxml.append(""); - } - // red, - // green - // blue - // gamma - // invGamma - return ooxml; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/ColorChoice.kt b/src/main/java/io/starter/formats/OOXML/ColorChoice.kt new file mode 100644 index 0000000..2c4d019 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/ColorChoice.kt @@ -0,0 +1,841 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * ColorChoice: choice of: hslClr (Hue, Saturation, Luminance Color Model) + * prstClr (Preset Color) §5.1.2.2.22 schemeClr (Scheme Color) §5.1.2.2.29 + * scrgbClr (RGB Color Model - Percentage Variant) srgbClr (RGB Color Model - + * Hex Variant) sysClr (System Color) + */ +// TODO: FINISH: child elements governing color transformations +// finish hslClr +class ColorChoice : OOXMLElement { + private var s: SchemeClr? = null + private var srgb: SrgbClr? = null + private var sys: SysClr? = null + private var scrgb: ScrgbClr? = null + private var p: PrstClr? = null + var theme: Theme? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + if (s != null) + ooxml.append(s!!.ooxml) + else if (sys != null) + ooxml.append(sys!!.ooxml) + else if (srgb != null) + ooxml.append(srgb!!.ooxml) + else if (scrgb != null) + ooxml.append(scrgb!!.ooxml) + else if (p != null) + ooxml.append(p!!.ooxml) + return ooxml.toString() + } + + val color: Int + get() { + if (s != null) + return s!!.color + else if (sys != null) + sys!!.color + else if (srgb != null) + srgb!!.color + else if (scrgb != null) + scrgb!!.color + else if (p != null) + p!!.color + return -1 + + } + + constructor(s: SchemeClr, srgb: SrgbClr, sys: SysClr, scrgb: ScrgbClr, + p: PrstClr) { + this.s = s + this.srgb = srgb + this.sys = sys + this.scrgb = scrgb + this.p = p + } + + private fun setTheme(t: Theme?) { + this.theme = t + } + + constructor(c: ColorChoice) { + this.s = c.s + this.srgb = c.srgb + this.sys = c.sys + this.scrgb = c.scrgb + this.p = c.p + } + + override fun cloneElement(): OOXMLElement { + return ColorChoice(this) + } + + companion object { + + private val serialVersionUID = -4117811305941771643L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var s: SchemeClr? = null + var srgb: SrgbClr? = null + var sys: SysClr? = null + var scrgb: ScrgbClr? = null + var p: PrstClr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "schemeClr") { + lastTag.push(tnm) + s = SchemeClr.parseOOXML(xpp, lastTag, bk) + lastTag.pop() + break + } else if (tnm == "srgbClr") { + lastTag.push(tnm) + srgb = SrgbClr.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } else if (tnm == "sysClr") { + lastTag.push(tnm) + sys = SysClr.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } else if (tnm == "scrgbClr") { + lastTag.push(tnm) + scrgb = ScrgbClr.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } else if (tnm == "prstClr") { + lastTag.push(tnm) + p = PrstClr.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } + /* tnm.equals("hslClr") */// TODO: finish + + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't + // get here + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("ColorChoice.parseOOXML: $e") + } + + val c = ColorChoice(s, srgb, sys, scrgb, p) + c.setTheme(bk.workBook!!.theme) + return c + } + } + +} + +/** + * schemeClr (Scheme Color) This element specifies a color bound to a user's + * theme. As with all elements which define a color, it is possible to apply a + * list of color transforms to the base color defined. + * + * + * accent1 (Accent Color 1) Extra scheme color 1 accent2 (Accent Color 2) Extra + * scheme color 2 accent3 (Accent Color 3) Extra scheme color 3 accent4 (Accent + * Color 4) Extra scheme color 4 accent5 (Accent Color 5) Extra scheme color 5 + * accent6 (Accent Color 6) Extra scheme color 6 bg1 (Background Color 1) + * Semantic background color bg2 (Background Color 2) Semantic additional + * background color dk1 (Dark Color 1) Main dark color 1 dk2 (Dark Color 2) Main + * dark color 2 folHlink (Followed Hyperlink Color) Followed Hyperlink Color + * hlink (Hyperlink Color) Regular Hyperlink Color lt1 (Light Color 1) Main + * Light Color 1 lt2 (Light Color 2) Main Light Color 2 phClr (Style Color) A + * color used in theme definitions which means to use the color of the style. + * tx1 (Text Color 1) Semantic text color tx2 (Text Color 2) Semantic additional + * text color + * + * + * parent: many children: many - TODO: handle color transformation children + * (alpha ...) + */ +internal class SchemeClr : OOXMLElement { + private var `val`: String? = null + private var clrTransform: ColorTransform? = null + private var theme: Theme? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (clrTransform != null) + ooxml.append(clrTransform!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + val color: Int + get() { + val tint = (if (clrTransform == null) 0 else clrTransform!!.tint).toDouble() + val o = Color.parseThemeColor(`val`, tint, 0.toShort(), this.theme) + return o[0] as Int + } + + constructor(`val`: String, clrTransform: ColorTransform, t: Theme) { + this.`val` = `val` + this.clrTransform = clrTransform + this.theme = t + } + + constructor(sc: SchemeClr) { + this.`val` = sc.`val` + this.clrTransform = sc.clrTransform + this.theme = sc.theme + } + + override fun cloneElement(): OOXMLElement { + return SchemeClr(this) + } + + companion object { + + private val serialVersionUID = 2127868578801669266L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): SchemeClr { + var `val`: String? = null + var clrTransform: ColorTransform? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "schemeClr") { + `val` = xpp.getAttributeValue(0) + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag) + break + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "schemeClr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("schemeClr.parseOOXML: $e") + } + + return SchemeClr(`val`, clrTransform, bk.workBook!!.theme) + } + } +} + +/** + * sysClr (System Color) This element specifies a color bound to predefined + * operating system elements. // TODO: appropriate to hard-code??? + * + * + * parent: many children: COLORSTRANSFORM + */ +internal class SysClr : OOXMLElement { + private var `val`: String? = null // This simple type specifies a system color + // value. This color is based upon the value + // that this color currently has within the + // system on which the document is being viewed. + private var lastClr: String? = null // Specifies the color value that was last + // computed by the generating application. + // Applications shall use the lastClr + // attribute to determine the absolute value + // of the last color used if system colors + // are not supported. + private var clrTransform: ColorTransform? = null + + override// TODO: Handle child elements + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (clrTransform != null) + ooxml.append(clrTransform!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * return the color int that represents this system color + * + * @return + */ + val color: Int + get() { + for (i in OOXMLConstants.systemColors.indices) { + if (OOXMLConstants.systemColors[i][0] == `val`) { + return FormatHandle.HexStringToColorInt( + OOXMLConstants.systemColors[i][1], 0.toShort()) + } + } + return -1 + } + + constructor(`val`: String, lastClr: String, clrTransform: ColorTransform) { + this.`val` = `val` + this.lastClr = lastClr + this.clrTransform = clrTransform + } + + constructor(sc: SysClr) { + this.`val` = sc.`val` + this.lastClr = sc.lastClr + this.clrTransform = sc.clrTransform + } + + override fun cloneElement(): OOXMLElement { + return SysClr(this) + } + + companion object { + + private val serialVersionUID = 8307422721346337409L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): SysClr { + var `val`: String? = null + var lastClr: String? = null + var clrTransform: ColorTransform? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "sysClr") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "val") { // + `val` = xpp.getAttributeValue(i) + } else if (nm == "lastClr") { + lastClr = xpp.getAttributeValue(i) + } + } + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag) + break + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "sysClr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("sysClr.parseOOXML: $e") + } + + return SysClr(`val`, lastClr, clrTransform) + } + } +} + +/** + * srgbClr (RGB Color Model - Hex Variant) + * + * + * This element specifies a color using the red, green, blue RGB color model. + * Red, green, and blue is expressed as sequence of hex digits, RRGGBB. A + * perceptual gamma of 2.2 is used. Specifies the level of red as expressed by a + * percentage offset increase or decrease relative to the input color. + * + * + * parent: many children: COLORSTRANSFORM + */ +internal class SrgbClr : OOXMLElement { + private var `val`: String? = null + private var clrTransform: ColorTransform? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (clrTransform != null) + ooxml.append(clrTransform!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * interpret val and return color int from color table + * + * @return + */ + val color: Int + get() = FormatHandle.HexStringToColorInt(`val`!!, 0.toShort()) + + constructor(`val`: String, clrTransform: ColorTransform) { + this.`val` = `val` + this.clrTransform = clrTransform + } + + constructor(sc: SrgbClr) { + this.`val` = sc.`val` + this.clrTransform = sc.clrTransform + } + + override fun cloneElement(): OOXMLElement { + return SrgbClr(this) + } + + companion object { + + private val serialVersionUID = -999813417659560045L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): SrgbClr { + var `val`: String? = null + var clrTransform: ColorTransform? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "srgbClr") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "val") { + `val` = xpp.getAttributeValue(i) + } + } + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag) + break + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "srgbClr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("srgbClr.parseOOXML: $e") + } + + return SrgbClr(`val`, clrTransform) + } + } +} + +/** + * scrgbClr (Scheme Color) + * + * + * This element specifies a color using the red, green, blue RGB color model. + * Each component, red, green, and blue is expressed as a percentage from 0% to + * 100%. A linear gamma of 1.0 is assumed. + * + * + * parent: many children: COLORSTRANSFORM + */ +internal class ScrgbClr : OOXMLElement { + private var attrs: HashMap? = null + private var clrTransform: ColorTransform? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (clrTransform != null) + ooxml.append(clrTransform!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * interpret the rbg value into a color int/index into color table + * + * @return + */ + // r, g, b in percentages (in 1000th of a percentage) + // perecentage + val color: Int + get() { + var rval = (Integer.valueOf(attrs!!["r"]) / 100000).toDouble() + rval *= 255.0 + var gval = (Integer.valueOf(attrs!!["g"]) / 100000).toDouble() + gval *= 255.0 + var bval = (Integer.valueOf(attrs!!["b"]) / 100000).toDouble() + bval *= 255.0 + if (clrTransform != null) + Logger.logWarn("Scheme Color must process color transforms") + val c = java.awt.Color(rval.toInt(), gval.toInt(), + bval.toInt()) + return FormatHandle.getColorInt(c) + } + + constructor(attrs: HashMap, clrTransform: ColorTransform) { + this.attrs = attrs + this.clrTransform = clrTransform + } + + constructor(sc: ScrgbClr) { + this.attrs = sc.attrs + this.clrTransform = sc.clrTransform + } + + override fun cloneElement(): OOXMLElement { + return ScrgbClr(this) + } + + companion object { + + private val serialVersionUID = -8782954669829478560L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): ScrgbClr { + val attrs = HashMap() + var clrTransform: ColorTransform? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "scrgbClr") { + for (i in 0 until xpp.attributeCount) + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) // r, g, b + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag) + break + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "scrgbClr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("scrgbClr.parseOOXML: $e") + } + + return ScrgbClr(attrs, clrTransform) + } + } +} + +/** + * prstClr (Preset Color) This element specifies a color which is bound to one + * of a predefined collection of colors. + * + * + * parent: many children: many + */ +internal class PrstClr : OOXMLElement { + private var `val`: String? = null + private var clrTransform: ColorTransform? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (clrTransform != null) + ooxml.append(clrTransform!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + val color: Int + get() { + if (clrTransform != null) + Logger.logWarn("Preset Color must process color transforms") + for (i in FormatHandle.COLORNAMES.indices) { + if (FormatHandle.COLORNAMES[i].equals(`val`!!, ignoreCase = true)) + return i + } + return -1 + } + + constructor(`val`: String, clrTransform: ColorTransform) { + this.`val` = `val` + this.clrTransform = clrTransform + } + + constructor(sc: PrstClr) { + this.`val` = sc.`val` + this.clrTransform = sc.clrTransform + } + + override fun cloneElement(): OOXMLElement { + return PrstClr(this) + } + + companion object { + + private val serialVersionUID = -5773022185972396279L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): PrstClr { + var `val`: String? = null + var clrTransform: ColorTransform? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "prstClr") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "val") { + `val` = xpp.getAttributeValue(i) + } + } + } else { + clrTransform = ColorTransform.parseOOXML(xpp, lastTag) + break + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "prstClr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("prstClr.parseOOXML: $e") + } + + return PrstClr(`val`, clrTransform) + } + } +} + +/** + * common Color Transformes used by parent elements: schemeColor, systemColor, + * hslColor, presetColor, sRgbColor, scRgbColor color adjustments are in + * percentage units

                  + * // TODO: Finish // comp // inv // gamma // invGamma // gray, red, green, blue + */ +internal class ColorTransform(private val lum: IntArray?, private val hue: IntArray?, private val sat: IntArray?, private val alpha: IntArray?, + // TODO: Finish + // comp + // inv + // gamma + // gray, red, green, blue + + val tint: Int, private val shade: Int) { + + /** + * returns the OOXML associated with color transforms of a parent color + * element

                  + * note that these color transforms must be part of either

                  + * schemeColor, systemColor, hslColor, presetColor, sRgbColor, scRgbColor + * + * @return + */ + /** + * a:tint Tint a:shade Shade a:comp Complement a:inv Inverse a:gray Gray + * a:alpha Alpha a:alphaOff Alpha Offset a:alphaMod Alpha Modulation + * a:hue Hue a:hueOff Hue Offset a:hueMod Hue Modulate a:sat Saturation + * a:satOff Saturation Offset a:satMod Saturation Modulation a:lum + * Luminance a:lumOff Luminance Offset a:lumMod Luminance Modulation + * a:red Red a:redOff Red Offset a:redMod Red Modulation a:green Green + * a:greenOff Green Offset a:greenMod Green Modification a:blue Blue + * a:blueOff Blue Offset a:blueMod Blue Modification a:gamma Gamma + * a:invGamma Inverse Gamma + */// Complement + // Inverse + // Gray + // red, + // green + // blue + // gamma + // invGamma + val ooxml: StringBuffer + get() { + val ooxml = StringBuffer() + if (tint != 0) + ooxml.append("") + if (shade != 0) + ooxml.append("") + if (alpha != null) { + if (alpha[0] != 0) + ooxml.append("") + if (alpha[2] != 0) + ooxml.append("") + if (alpha[1] != 0) + ooxml.append("") + } + if (hue != null) { + if (hue[0] != 0) + ooxml.append("") + if (hue[2] != 0) + ooxml.append("") + if (hue[1] != 0) + ooxml.append("") + } + if (sat != null) { + if (sat[0] != 0) + ooxml.append("") + if (sat[2] != 0) + ooxml.append("") + if (sat[1] != 0) + ooxml.append("") + } + if (lum != null) { + if (lum[0] != 0) + ooxml.append("") + if (lum[2] != 0) + ooxml.append("") + if (lum[1] != 0) + ooxml.append("") + } + return ooxml + } + + companion object { + + /** + * parse color transform elements, common children of color-type elements:

                  + * schemeColor, systemColor, hslColor, presetColor, sRgbColor, scRgbColor + * + * @param xpp + * @param lastTag + * @return + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): ColorTransform { + var lum: IntArray? = null + var hue: IntArray? = null + var sat: IntArray? = null + val alpha: IntArray? = null + var tint = 0 + var shade = 0 + try { + val parentEl = lastTag.peek() + + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "lum") { // This element specifies the input + // color with its luminance + // modulated by the given + // percentage. + if (lum == null) + lum = IntArray(3) + lum[0] = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "lumMod") { // This element specifies + // the input color with + // its luminance + // modulated by the + // given percentage. + if (lum == null) + lum = IntArray(3) + lum[1] = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "lumOff") { // This element specifies + // the input color with + // its luminance + // shifted, but with its + // hue and saturation + // unchanged. + if (lum == null) + lum = IntArray(3) + lum[2] = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "hue") { // This element specifies + // the input color with the + // specified hue, but with + // its saturation and + // luminance unchanged. + if (hue == null) + hue = IntArray(3) + hue[0] = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "hueMod") { + if (hue == null) + hue = IntArray(3) + hue[1] = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "hueOff") { + if (hue == null) + hue = IntArray(3) + hue[2] = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "sat") { // This element specifies + // the input color with the + // specified saturation, but + // with its hue and + // luminance unchanged. + if (sat == null) + sat = IntArray(3) + sat[0] = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "satMod") { + if (sat == null) + sat = IntArray(3) + sat[1] = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "satOff") { + if (sat == null) + sat = IntArray(3) + sat[2] = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "shade") { // This element specifies + // a darker version of + // its input color + shade = Integer.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "tint") { + tint = Integer.valueOf(xpp.getAttributeValue(0)) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == parentEl) { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("ColorTransform.parseOOXML: $e") + } + + return ColorTransform(lum, hue, sat, alpha, tint, shade) + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/CxnSp.java b/src/main/java/io/starter/formats/OOXML/CxnSp.java deleted file mode 100644 index f77c230..0000000 --- a/src/main/java/io/starter/formats/OOXML/CxnSp.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * parents: absoluteAnchor, oneCellAnchor, twoCellAnchor - * children: nvCxnSpPr, spPr, style - */ -//TODO: finish nvCxnSpPr.cNvCxnSpPr element -public class CxnSp implements OOXMLElement { - - private static final long serialVersionUID = -8492664135843926551L; - private HashMap attrs = new HashMap(); - private NvCxnSpPr nvc; - private SpPr spPr; - private Style style; - - public CxnSp(HashMap attrs, NvCxnSpPr nvc, SpPr sp, Style s) { - this.attrs = attrs; - this.nvc = nvc; - this.spPr = sp; - this.style = s; - } - - public CxnSp(CxnSp c) { - this.attrs = c.attrs; - this.nvc = c.nvc; - this.spPr = c.spPr; - this.style = c.style; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - NvCxnSpPr nvc = null; - SpPr sp = null; - Style s = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cxnSp")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("nvCxnSpPr")) { - lastTag.push(tnm); - nvc = NvCxnSpPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); -// sp.setNS("xdr"); - } else if (tnm.equals("style")) { - lastTag.push(tnm); - s = (Style) Style.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("cxnSp")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cxnSp.parseOOXML: " + e.toString()); - } - CxnSp c = new CxnSp(attrs, nvc, sp, s); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (nvc != null) ooxml.append(nvc.getOOXML()); - if (spPr != null) ooxml.append(spPr.getOOXML()); - if (style != null) ooxml.append(style.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CxnSp(this); - } - - /** - * get cNvPr name attribute - * - * @return - */ - public String getName() { - if (nvc != null) - return nvc.getName(); - return null; - } - - /** - * set cNvPr name attribute - * - * @param name - */ - public void setName(String name) { - if (nvc != null) - nvc.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (nvc != null) - return nvc.getDescr(); - return null; - } - - /** - * set cNvPr description attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setDescr(String descr) { - if (nvc != null) - nvc.setDescr(descr); - } - - /** - * get Macro attribute - */ - public String getMacro() { - if (attrs.get("macro") != null) - return attrs.get("macro"); - return null; - } - - /** - * set Macro attribute - * - * @param macro - */ - public void setMacro(String macro) { - attrs.put("macro", macro); - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (nvc != null) - nvc.setId(id); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (nvc != null) - return nvc.getId(); - return -1; - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (spPr != null) - return spPr.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (spPr != null) - return spPr.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - if (spPr != null) spPr.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (spPr != null) spPr.setLink(link); - } -} - -/** - * nvCxnSpPr (Non-Visual Properties for a Connection Shape) - * This element specifies all non-visual properties for a connection shape. This element is a container for the non15 - * visual identification properties, shape properties and application properties that are to be associated with a - * DrawingML Reference Material - DrawingML - SpreadsheetML Drawing connection shape. - * This allows for additional information that does not affect 1 the appearance of the connection - * shape to be stored. - *

                  - * parent: cxnSp - * children: cNvPr, cNvCxnSpPr - */ -// TODO: finish cNvCxnSpPr -class NvCxnSpPr implements OOXMLElement { - - private static final long serialVersionUID = -4808617992996239153L; - private CNvPr cpr; -// private cNvCxnSpPr sppr= null; - - public NvCxnSpPr(CNvPr cpr/*, cNvCxnSpPr sppr*/) { - this.cpr = cpr; - //this.sppr= sppr; - } - - public NvCxnSpPr(NvCxnSpPr n) { - this.cpr = n.cpr; - //this.sppr= n.sppr; - } - - - public static NvCxnSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cpr = null; - // cNvCxnSpPr sppr= null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cpr = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); -/* } else if (tnm.equals("cNvCxnSpPr")) { - lastTag.push(tnm); - sppr= (cNvCxnSpPr) cNvCxnSpPr.parseOOXML(xpp, lastTag).clone(); -*/ - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("nvCxnSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("nvCxnSpPr.parseOOXML: " + e.toString()); - } - NvCxnSpPr n = new NvCxnSpPr(cpr/*, sppr*/); - return n; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (cpr != null) ooxml.append(cpr.getOOXML()); - // TODO: finihs cNvCxnSpPr - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NvCxnSpPr(this); - } - - /** - * get cNvPr name attribute - * - * @return - */ - public String getName() { - if (cpr != null) - return cpr.getName(); - return null; - } - - /** - * set cNvPr name attribute - * - * @param name - */ - public void setName(String name) { - if (cpr != null) - cpr.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (cpr != null) - return cpr.getDescr(); - return null; - } - - /** - * set cNvPr description attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setDescr(String descr) { - if (cpr != null) - cpr.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (cpr != null) - cpr.setId(id); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (cpr != null) - return cpr.getId(); - return -1; - } -} - - diff --git a/src/main/java/io/starter/formats/OOXML/CxnSp.kt b/src/main/java/io/starter/formats/OOXML/CxnSp.kt new file mode 100644 index 0000000..5945137 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/CxnSp.kt @@ -0,0 +1,359 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * parents: absoluteAnchor, oneCellAnchor, twoCellAnchor + * children: nvCxnSpPr, spPr, style + */ +//TODO: finish nvCxnSpPr.cNvCxnSpPr element +class CxnSp : OOXMLElement { + private var attrs = HashMap() + private var nvc: NvCxnSpPr? = null + private var spPr: SpPr? = null + private var style: Style? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (nvc != null) ooxml.append(nvc!!.ooxml) + if (spPr != null) ooxml.append(spPr!!.ooxml) + if (style != null) ooxml.append(style!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * get cNvPr name attribute + * + * @return + */ + /** + * set cNvPr name attribute + * + * @param name + */ + var name: String? + get() = if (nvc != null) nvc!!.name else null + set(name) { + if (nvc != null) + nvc!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + var descr: String? + get() = if (nvc != null) nvc!!.descr else null + set(descr) { + if (nvc != null) + nvc!!.descr = descr + } + + /** + * get Macro attribute + */ + /** + * set Macro attribute + * + * @param macro + */ + var macro: String? + get() = if (attrs["macro"] != null) attrs["macro"] else null + set(macro) { + attrs["macro"] = macro + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + var id: Int + get() = if (nvc != null) nvc!!.id else -1 + set(id) { + if (nvc != null) + nvc!!.id = id + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + var embed: String? + get() = if (spPr != null) spPr!!.embed else null + set(embed) { + if (spPr != null) spPr!!.embed = embed + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + var link: String? + get() = if (spPr != null) spPr!!.link else null + set(link) { + if (spPr != null) spPr!!.link = link + } + + constructor(attrs: HashMap, nvc: NvCxnSpPr, sp: SpPr, s: Style) { + this.attrs = attrs + this.nvc = nvc + this.spPr = sp + this.style = s + } + + constructor(c: CxnSp) { + this.attrs = c.attrs + this.nvc = c.nvc + this.spPr = c.spPr + this.style = c.style + } + + override fun cloneElement(): OOXMLElement { + return CxnSp(this) + } + + companion object { + + private val serialVersionUID = -8492664135843926551L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + val attrs = HashMap() + var nvc: NvCxnSpPr? = null + var sp: SpPr? = null + var s: Style? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cxnSp") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "nvCxnSpPr") { + lastTag.push(tnm) + nvc = NvCxnSpPr.parseOOXML(xpp, lastTag) + } else if (tnm == "spPr") { + lastTag.push(tnm) + sp = SpPr.parseOOXML(xpp, lastTag, bk) as SpPr + // sp.setNS("xdr"); + } else if (tnm == "style") { + lastTag.push(tnm) + s = Style.parseOOXML(xpp, lastTag, bk) as Style + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "cxnSp") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("cxnSp.parseOOXML: $e") + } + + return CxnSp(attrs, nvc, sp, s) + } + } +} + +/** + * nvCxnSpPr (Non-Visual Properties for a Connection Shape) + * This element specifies all non-visual properties for a connection shape. This element is a container for the non15 + * visual identification properties, shape properties and application properties that are to be associated with a + * DrawingML Reference Material - DrawingML - SpreadsheetML Drawing connection shape. + * This allows for additional information that does not affect 1 the appearance of the connection + * shape to be stored. + * + * + * parent: cxnSp + * children: cNvPr, cNvCxnSpPr + */ +// TODO: finish cNvCxnSpPr +internal class NvCxnSpPr : OOXMLElement { + private var cpr: CNvPr? = null + + override// TODO: finihs cNvCxnSpPr + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (cpr != null) ooxml.append(cpr!!.ooxml) + ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + /** + * get cNvPr name attribute + * + * @return + */ + /** + * set cNvPr name attribute + * + * @param name + */ + var name: String? + get() = if (cpr != null) cpr!!.name else null + set(name) { + if (cpr != null) + cpr!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + var descr: String? + get() = if (cpr != null) cpr!!.descr else null + set(descr) { + if (cpr != null) + cpr!!.descr = descr + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + var id: Int + get() = if (cpr != null) cpr!!.id else -1 + set(id) { + if (cpr != null) + cpr!!.id = id + } + // private cNvCxnSpPr sppr= null; + + constructor(cpr: CNvPr/*, cNvCxnSpPr sppr*/) { + this.cpr = cpr + //this.sppr= sppr; + } + + constructor(n: NvCxnSpPr) { + this.cpr = n.cpr + //this.sppr= n.sppr; + } + + override fun cloneElement(): OOXMLElement { + return NvCxnSpPr(this) + } + + companion object { + + private val serialVersionUID = -4808617992996239153L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): NvCxnSpPr { + var cpr: CNvPr? = null + // cNvCxnSpPr sppr= null; + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cNvPr") { + lastTag.push(tnm) + cpr = CNvPr.parseOOXML(xpp, lastTag) as CNvPr + /* } else if (tnm.equals("cNvCxnSpPr")) { + lastTag.push(tnm); + sppr= (cNvCxnSpPr) cNvCxnSpPr.parseOOXML(xpp, lastTag).clone(); +*/ + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "nvCxnSpPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("nvCxnSpPr.parseOOXML: $e") + } + + return NvCxnSpPr(cpr/*, sppr*/) + } + } +} + + diff --git a/src/main/java/io/starter/formats/OOXML/DLbls.java b/src/main/java/io/starter/formats/OOXML/DLbls.java deleted file mode 100644 index 53f39e2..0000000 --- a/src/main/java/io/starter/formats/OOXML/DLbls.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * dLbls (Data Labels) - * This element serves as a root element that specifies the settings for the data labels for an entire series or the - * entire chart. It contains child elements that specify the specific formatting and positioning settings. - *

                  - * parent: chart types, ser - * children: dLbl (0 or more times), GROUPDLBLS (numFmt, spPr, txPr, dLblPos, showLegendKey, showVal, showCatName, showSerName,showPercent, showBubbleSize, separator, showLeaderLines - */ -// TODO: Finish All Children!!!! leaderLines, numFmt, separator -public class DLbls implements OOXMLElement { - - private static final long serialVersionUID = -3765320144606034211L; - private SpPr sp = null; - private TxPr tx = null; - // all of these are child elements with one attribute=val, all default to "1" (true) - private int showVal = -1; - private int showLeaderLines = -1; - private int showLegendKey = -1; - private int showCatName = -1; - private int showSerName = -1; - private int showPercent = -1; - private int showBubbleSize = -1; - - public DLbls(int showVal, int showLeaderLines, int showLegendKey, int showCatName, - int showSerName, int showPercent, int showBubbleSize, SpPr sp, TxPr tx) { - this.showVal = showVal; - this.showLeaderLines = showLeaderLines; - this.showLegendKey = showLegendKey; - this.showCatName = showCatName; - this.showSerName = showSerName; - this.showPercent = showPercent; - this.showBubbleSize = showBubbleSize; - this.sp = sp; - this.tx = tx; - } - - public DLbls(boolean showVal, boolean showLeaderLines, boolean showLegendKey, boolean showCatName, - boolean showSerName, boolean showPercent, boolean showBubbleSize, SpPr sp, TxPr tx) { - if (showVal) this.showVal = 1; - if (showLeaderLines) this.showLeaderLines = 1; - if (showLegendKey) this.showLegendKey = 1; - if (showCatName) this.showCatName = 1; - if (showSerName) this.showSerName = 1; - if (showPercent) this.showPercent = 1; - if (showBubbleSize) this.showBubbleSize = 1; - this.sp = sp; - this.tx = tx; - } - - public DLbls(DLbls d) { - this.showVal = d.showVal; - this.showLeaderLines = d.showLeaderLines; - this.showLegendKey = d.showLegendKey; - this.showCatName = d.showCatName; - this.showSerName = d.showSerName; - this.showPercent = d.showPercent; - this.showBubbleSize = d.showBubbleSize; - this.sp = d.sp; - this.tx = d.tx; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - SpPr sp = null; - TxPr tx = null; - int showVal = -1; - int showLeaderLines = -1; - int showLegendKey = -1; - int showCatName = -1; - int showSerName = -1; - int showPercent = -1; - int showBubbleSize = -1; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); -// sp.setNS("c"); - } else if (tnm.equals("txPr")) { - lastTag.push(tnm); - tx = (TxPr) TxPr.parseOOXML(xpp, lastTag, bk).cloneElement(); - } else if (tnm.equals("showVal")) { - if (xpp.getAttributeCount() > 0) - showVal = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showLeaderLines")) { - if (xpp.getAttributeCount() > 0) - showLeaderLines = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showLegendKey")) { - if (xpp.getAttributeCount() > 0) - showLegendKey = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showCatName")) { - if (xpp.getAttributeCount() > 0) - showCatName = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showSerName")) { - if (xpp.getAttributeCount() > 0) - showSerName = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showPercent")) { - if (xpp.getAttributeCount() > 0) - showPercent = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("showBubbleSize")) { - if (xpp.getAttributeCount() > 0) - showBubbleSize = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("dLbls")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("dLbls.parseOOXML: " + e.toString()); - } - DLbls d = new DLbls(showVal, showLeaderLines, showLegendKey, showCatName, showSerName, - showPercent, showBubbleSize, sp, tx); - return d; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - // TODO: numFmt - if (sp != null) ooxml.append(sp.getOOXML()); - if (tx != null) ooxml.append(tx.getOOXML()); - // TODO: dLblPos - if (showLegendKey != -1) ooxml.append(""); - if (showVal != -1) ooxml.append(""); - if (showCatName != -1) ooxml.append(""); - if (showSerName != -1) ooxml.append(""); - if (showPercent != -1) ooxml.append(""); - if (showBubbleSize != -1) ooxml.append(""); - // TODO: separator - if (showLeaderLines != -1) ooxml.append(""); - // TODO: leaderLines - ooxml.append(""); - return ooxml.toString(); - } - - /** - * generate the ooxml necessary to define the data labels for a chart - * Controls view of Series name, Category Name, Percents, Leader Lines, Bubble Sizes where applicable - * - * @return public String getOOXML(ChartFormat cf) { - * StringBuffer ooxml= new StringBuffer(); - * ooxml.append(""); ooxml.append("\r\n"); - * // TODO: c:numFmt, c:spPr, c:txPr - * if (cf.getChartOption("ShowBubbleSizes")=="1") - * ooxml.append(""); - * if (cf.getChartOption("ShowValueLabel")=="1") - * ooxml.append(""); - * if (cf.getChartOption("ShowLabel")=="1") - * ooxml.append(""); - * if (cf.getChartOption("ShowCatLabel")=="1") - * ooxml.append(""); - * // Pie specific - * if (cf.getChartOption("ShowLabelPct")=="1") - * ooxml.append(""); - * if (cf.getChartOption("ShowLdrLines")=="true") - * ooxml.append(""); - *

                  - *

                  - * ooxml.append(""); ooxml.append("\r\n"); - * return ooxml.toString(); - * } - */ - - public OOXMLElement cloneElement() { - return new DLbls(this); - } - - /** - * get methods - */ - public boolean showLegendKey() { - return showLegendKey == 1; - } - - public boolean showVal() { - return showVal == 1; - } - - public boolean showCatName() { - return showCatName == 1; - } - - public boolean showSerName() { - return showSerName == 1; - } - - public boolean showPercent() { - return showPercent == 1; - } - - public boolean showBubbleSize() { - return showBubbleSize == 1; - } - - public boolean showLeaderLines() { - return showLeaderLines == 1; - } -} - - - diff --git a/src/main/java/io/starter/formats/OOXML/DLbls.kt b/src/main/java/io/starter/formats/OOXML/DLbls.kt new file mode 100644 index 0000000..91be01d --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/DLbls.kt @@ -0,0 +1,246 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * dLbls (Data Labels) + * This element serves as a root element that specifies the settings for the data labels for an entire series or the + * entire chart. It contains child elements that specify the specific formatting and positioning settings. + * + * + * parent: chart types, ser + * children: dLbl (0 or more times), GROUPDLBLS (numFmt, spPr, txPr, dLblPos, showLegendKey, showVal, showCatName, showSerName,showPercent, showBubbleSize, separator, showLeaderLines + */ +// TODO: Finish All Children!!!! leaderLines, numFmt, separator +class DLbls : OOXMLElement { + private var sp: SpPr? = null + private var tx: TxPr? = null + // all of these are child elements with one attribute=val, all default to "1" (true) + private var showVal = -1 + private var showLeaderLines = -1 + private var showLegendKey = -1 + private var showCatName = -1 + private var showSerName = -1 + private var showPercent = -1 + private var showBubbleSize = -1 + + override// TODO: numFmt + // TODO: dLblPos + // TODO: separator + // TODO: leaderLines + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (sp != null) ooxml.append(sp!!.ooxml) + if (tx != null) ooxml.append(tx!!.ooxml) + if (showLegendKey != -1) ooxml.append("") + if (showVal != -1) ooxml.append("") + if (showCatName != -1) ooxml.append("") + if (showSerName != -1) ooxml.append("") + if (showPercent != -1) ooxml.append("") + if (showBubbleSize != -1) ooxml.append("") + if (showLeaderLines != -1) ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + constructor(showVal: Int, showLeaderLines: Int, showLegendKey: Int, showCatName: Int, + showSerName: Int, showPercent: Int, showBubbleSize: Int, sp: SpPr, tx: TxPr) { + this.showVal = showVal + this.showLeaderLines = showLeaderLines + this.showLegendKey = showLegendKey + this.showCatName = showCatName + this.showSerName = showSerName + this.showPercent = showPercent + this.showBubbleSize = showBubbleSize + this.sp = sp + this.tx = tx + } + + constructor(showVal: Boolean, showLeaderLines: Boolean, showLegendKey: Boolean, showCatName: Boolean, + showSerName: Boolean, showPercent: Boolean, showBubbleSize: Boolean, sp: SpPr, tx: TxPr) { + if (showVal) this.showVal = 1 + if (showLeaderLines) this.showLeaderLines = 1 + if (showLegendKey) this.showLegendKey = 1 + if (showCatName) this.showCatName = 1 + if (showSerName) this.showSerName = 1 + if (showPercent) this.showPercent = 1 + if (showBubbleSize) this.showBubbleSize = 1 + this.sp = sp + this.tx = tx + } + + constructor(d: DLbls) { + this.showVal = d.showVal + this.showLeaderLines = d.showLeaderLines + this.showLegendKey = d.showLegendKey + this.showCatName = d.showCatName + this.showSerName = d.showSerName + this.showPercent = d.showPercent + this.showBubbleSize = d.showBubbleSize + this.sp = d.sp + this.tx = d.tx + } + + /** + * generate the ooxml necessary to define the data labels for a chart + * Controls view of Series name, Category Name, Percents, Leader Lines, Bubble Sizes where applicable + * + * @return public String getOOXML(ChartFormat cf) { + * StringBuffer ooxml= new StringBuffer(); + * ooxml.append(""); ooxml.append("\r\n"); + * // TODO: c:numFmt, c:spPr, c:txPr + * if (cf.getChartOption("ShowBubbleSizes")=="1") + * ooxml.append(""); + * if (cf.getChartOption("ShowValueLabel")=="1") + * ooxml.append(""); + * if (cf.getChartOption("ShowLabel")=="1") + * ooxml.append(""); + * if (cf.getChartOption("ShowCatLabel")=="1") + * ooxml.append(""); + * // Pie specific + * if (cf.getChartOption("ShowLabelPct")=="1") + * ooxml.append(""); + * if (cf.getChartOption("ShowLdrLines")=="true") + * ooxml.append(""); + * + * + * + * + * ooxml.append(""); ooxml.append("\r\n"); + * return ooxml.toString(); + * } + */ + + override fun cloneElement(): OOXMLElement { + return DLbls(this) + } + + /** + * get methods + */ + fun showLegendKey(): Boolean { + return showLegendKey == 1 + } + + fun showVal(): Boolean { + return showVal == 1 + } + + fun showCatName(): Boolean { + return showCatName == 1 + } + + fun showSerName(): Boolean { + return showSerName == 1 + } + + fun showPercent(): Boolean { + return showPercent == 1 + } + + fun showBubbleSize(): Boolean { + return showBubbleSize == 1 + } + + fun showLeaderLines(): Boolean { + return showLeaderLines == 1 + } + + companion object { + + private val serialVersionUID = -3765320144606034211L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var sp: SpPr? = null + var tx: TxPr? = null + var showVal = -1 + var showLeaderLines = -1 + var showLegendKey = -1 + var showCatName = -1 + var showSerName = -1 + var showPercent = -1 + var showBubbleSize = -1 + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "spPr") { + lastTag.push(tnm) + sp = SpPr.parseOOXML(xpp, lastTag, bk) as SpPr + // sp.setNS("c"); + } else if (tnm == "txPr") { + lastTag.push(tnm) + tx = TxPr.parseOOXML(xpp, lastTag, bk).cloneElement() as TxPr + } else if (tnm == "showVal") { + if (xpp.attributeCount > 0) + showVal = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "showLeaderLines") { + if (xpp.attributeCount > 0) + showLeaderLines = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "showLegendKey") { + if (xpp.attributeCount > 0) + showLegendKey = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "showCatName") { + if (xpp.attributeCount > 0) + showCatName = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "showSerName") { + if (xpp.attributeCount > 0) + showSerName = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "showPercent") { + if (xpp.attributeCount > 0) + showPercent = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "showBubbleSize") { + if (xpp.attributeCount > 0) + showBubbleSize = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "dLbls") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("dLbls.parseOOXML: $e") + } + + return DLbls(showVal, showLeaderLines, showLegendKey, showCatName, showSerName, + showPercent, showBubbleSize, sp, tx) + } + } +} + + + diff --git a/src/main/java/io/starter/formats/OOXML/DPt.java b/src/main/java/io/starter/formats/OOXML/DPt.java deleted file mode 100644 index ab26bc5..0000000 --- a/src/main/java/io/starter/formats/OOXML/DPt.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * dPt (Data Point) - * This element specifies a single data point. - *

                  - * parent: series - * children: idx REQ, invertIfNegative, marker, bubble3D, explosion, spPr, pictureOptions - */ -// TODO: finish pictureOptions -public class DPt implements OOXMLElement { - - private static final long serialVersionUID = 8354707071603571747L; - private int idx; - private boolean invertIfNegative; - private boolean bubble3D; - private Marker marker; - private SpPr spPr; - private int explosion; - - public DPt(int idx, boolean invertIfNegative, boolean bubble3D, Marker m, SpPr sp, int explosion) { - this.idx = idx; - this.invertIfNegative = invertIfNegative; - this.bubble3D = bubble3D; - this.marker = m; - this.spPr = sp; - this.explosion = explosion; - } - - public DPt(DPt d) { - this.idx = d.idx; - this.invertIfNegative = d.invertIfNegative; - this.bubble3D = d.bubble3D; - this.marker = d.marker; - this.spPr = d.spPr; - this.explosion = d.explosion; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - int idx = -1; - boolean invertIfNegative = true; - boolean bubble3D = true; - Marker m = null; - SpPr sp = null; - int explosion = 0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("idx")) { // child element only contains 1 element - if (xpp.getAttributeCount() > 0) - idx = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("invertIfNegative")) { // child element only contains 1 element - if (xpp.getAttributeCount() > 0) - invertIfNegative = (xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("bubble3D")) { // child element only contains 1 element - if (xpp.getAttributeCount() > 0) - bubble3D = (xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("explosion")) { // child element only contains 1 element - if (xpp.getAttributeCount() > 0) - explosion = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); -// sp.setNS("c"); - } else if (tnm.equals("marker")) { - lastTag.push(tnm); - m = (Marker) Marker.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("dPt")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("dPt.parseOOXML: " + e.toString()); - } - DPt oe = new DPt(idx, invertIfNegative, bubble3D, m, sp, explosion); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append(""); - if (!invertIfNegative) ooxml.append(""); // default= true - if (marker != null) ooxml.append(marker.getOOXML()); - if (!bubble3D) ooxml.append(""); // default= true - if (explosion != 0) ooxml.append(""); - if (spPr != null) ooxml.append(spPr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new DPt(this); - } -} - - -/* - * generates the OOXML necessary to represent the data points of the chart - * includes fill and line color - * @return -private String getDataPointOOXML(ChartSeriesHandle[] sh) { - StringBuffer ooxml= new StringBuffer(); - for (int i= 0; i < sh.length; i++) { - ooxml.append(""); ooxml.append("\r\n"); - ooxml.append(""); - if (sh[i].getSpPr()!=null) ooxml.append(sh[i].getSpPr().getOOXML("c")); - ooxml.append(""); ooxml.append("\r\n"); - } - return ooxml.toString(); -} -*/ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/DPt.kt b/src/main/java/io/starter/formats/OOXML/DPt.kt new file mode 100644 index 0000000..0395797 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/DPt.kt @@ -0,0 +1,156 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * dPt (Data Point) + * This element specifies a single data point. + * + * + * parent: series + * children: idx REQ, invertIfNegative, marker, bubble3D, explosion, spPr, pictureOptions + */ +// TODO: finish pictureOptions +class DPt : OOXMLElement { + private var idx: Int = 0 + private var invertIfNegative: Boolean = false + private var bubble3D: Boolean = false + private var marker: Marker? = null + private var spPr: SpPr? = null + private var explosion: Int = 0 + + override// default= true + // default= true + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append("") + if (!invertIfNegative) ooxml.append("") + if (marker != null) ooxml.append(marker!!.ooxml) + if (!bubble3D) ooxml.append("") + if (explosion != 0) ooxml.append("") + if (spPr != null) ooxml.append(spPr!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(idx: Int, invertIfNegative: Boolean, bubble3D: Boolean, m: Marker, sp: SpPr, explosion: Int) { + this.idx = idx + this.invertIfNegative = invertIfNegative + this.bubble3D = bubble3D + this.marker = m + this.spPr = sp + this.explosion = explosion + } + + constructor(d: DPt) { + this.idx = d.idx + this.invertIfNegative = d.invertIfNegative + this.bubble3D = d.bubble3D + this.marker = d.marker + this.spPr = d.spPr + this.explosion = d.explosion + } + + override fun cloneElement(): OOXMLElement { + return DPt(this) + } + + companion object { + + private val serialVersionUID = 8354707071603571747L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var idx = -1 + var invertIfNegative = true + var bubble3D = true + var m: Marker? = null + var sp: SpPr? = null + var explosion = 0 + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "idx") { // child element only contains 1 element + if (xpp.attributeCount > 0) + idx = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "invertIfNegative") { // child element only contains 1 element + if (xpp.attributeCount > 0) + invertIfNegative = xpp.getAttributeValue(0) == "1" + } else if (tnm == "bubble3D") { // child element only contains 1 element + if (xpp.attributeCount > 0) + bubble3D = xpp.getAttributeValue(0) == "1" + } else if (tnm == "explosion") { // child element only contains 1 element + if (xpp.attributeCount > 0) + explosion = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "spPr") { + lastTag.push(tnm) + sp = SpPr.parseOOXML(xpp, lastTag, bk) as SpPr + // sp.setNS("c"); + } else if (tnm == "marker") { + lastTag.push(tnm) + m = Marker.parseOOXML(xpp, lastTag, bk) as Marker + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "dPt") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("dPt.parseOOXML: $e") + } + + return DPt(idx, invertIfNegative, bubble3D, m, sp, explosion) + } + } +} + + +/* + * generates the OOXML necessary to represent the data points of the chart + * includes fill and line color + * @return +private String getDataPointOOXML(ChartSeriesHandle[] sh) { + StringBuffer ooxml= new StringBuffer(); + for (int i= 0; i < sh.length; i++) { + ooxml.append(""); ooxml.append("\r\n"); + ooxml.append(""); + if (sh[i].getSpPr()!=null) ooxml.append(sh[i].getSpPr().getOOXML("c")); + ooxml.append(""); ooxml.append("\r\n"); + } + return ooxml.toString(); +} +*/ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/DefRPr.java b/src/main/java/io/starter/formats/OOXML/DefRPr.java deleted file mode 100644 index 6e7675b..0000000 --- a/src/main/java/io/starter/formats/OOXML/DefRPr.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * defRPr (Default Text Run Properties) - *

                  - * This element contains all default run level text properties for the text runs within a containing paragraph. These - * properties are to be used when overriding properties have not been defined within the rPr element - *

                  - * parent: many, including pPr - * children: ln, FILLS, EFFECTS, highlight, TEXTUNDERLINE, TEXTUNDERLINEFILL, latin, ea, cs, sym, hlinkClick, hlinkMouseOver - * many attributes - */ -// TODO: FINISH CHILD ELEMENTS highlight TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver -public class DefRPr implements OOXMLElement { - - private static final long serialVersionUID = 6764149567499222506L; - private FillGroup fillGroup = null; - private EffectPropsGroup effect = null; - private Ln line = null; - private HashMap attrs = null; - private String latin = null, ea = null, cs = null; // really children but only have 1 attribute and no children - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - FillGroup fill = null; - EffectPropsGroup effect = null; - Ln l = null; - String latin = null, ea = null, cs = null; - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("defRPr")) { // default text properties - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("ln")) { - lastTag.push(tnm); - l = (Ln) Ln.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - // TODO: Eventually these will be objects - } else if (tnm.equals("latin")) { - latin = xpp.getAttributeValue(0); - } else if (tnm.equals("ea")) { - ea = xpp.getAttributeValue(0); - } else if (tnm.equals("cs")) { - cs = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("defRPr")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("defPr.parseOOXML: " + e.toString()); - } - DefRPr dp = new DefRPr(fill, effect, l, attrs, latin, ea, cs); - return dp; - } - - /** - * return all the text properties in hashmap from - * - * @return - */ - public HashMap getTextProperties() { - HashMap textprops = new HashMap(); - textprops.putAll(attrs); - textprops.put("latin_typeface", latin); - textprops.put("ea_typeface", ea); - textprops.put("cs_typeface", cs); - // TODO: Fill, line ... - return textprops; - } - - /** - * create a default default Run Properties - * for BIFF8 compatibility - * qaa - */ - public DefRPr() { - this.attrs = new HashMap(); - this.attrs.put("sz", "900"); - this.attrs.put("b", "1"); - this.attrs.put("i", "0"); - this.attrs.put("u", "none"); - this.attrs.put("strike", "noStrike"); - this.attrs.put("baseline", "0"); - this.fillGroup = new FillGroup(null, null, null, null, new SolidFill()); - this.latin = "Arial"; - this.ea = "Arial"; - this.cs = "Arial"; - } - - /** - * create a default paragraph property from the specified information - * - * @param fontFace String font face e.g. "Arial" - * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) - * @param b boolean true if bold - * @param i boolean true if italic - * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted - * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) - * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none - * @param clr String fill color in hex form without the # - */ - public DefRPr(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { - this.attrs = new HashMap(); - this.attrs.put("sz", String.valueOf(sz)); - this.attrs.put("b", (b ? "1" : "0")); - this.attrs.put("i", (i ? "1" : "0")); - this.attrs.put("u", u); - this.attrs.put("strike", strike); - this.attrs.put("baseline", "0"); - this.fillGroup = new FillGroup(null, null, null, null, new SolidFill(clr)); - this.latin = fontFace; - this.ea = fontFace; - this.cs = fontFace; - } - - public DefRPr(FillGroup fill, EffectPropsGroup effect, Ln l, HashMap attrs, String latin, String ea, String cs) { - this.fillGroup = fill; - this.effect = effect; - this.line = l; - this.latin = latin; - this.ea = ea; - this.cs = cs; - this.attrs = attrs; - } - - public DefRPr(DefRPr dp) { - this.fillGroup = dp.fillGroup; - this.effect = dp.effect; - this.line = dp.line; - this.latin = dp.latin; - this.ea = dp.ea; - this.cs = dp.cs; - this.attrs = dp.attrs; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (line != null) ooxml.append(line.getOOXML()); - if (fillGroup != null) ooxml.append(fillGroup.getOOXML()); // group fill - if (effect != null) ooxml.append(effect.getOOXML()); // group effect - // highlight - // TEXTUNDERLINELINE - // TEXTUNDERLINEFILL - if (latin != null) ooxml.append(""); - if (ea != null) ooxml.append(""); - if (cs != null) ooxml.append(""); - // hLinkClick - // hLinkMouseOver - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new DefRPr(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/DefRPr.kt b/src/main/java/io/starter/formats/OOXML/DefRPr.kt new file mode 100644 index 0000000..d988551 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/DefRPr.kt @@ -0,0 +1,227 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * defRPr (Default Text Run Properties) + * + * + * This element contains all default run level text properties for the text runs within a containing paragraph. These + * properties are to be used when overriding properties have not been defined within the rPr element + * + * + * parent: many, including pPr + * children: ln, FILLS, EFFECTS, highlight, TEXTUNDERLINE, TEXTUNDERLINEFILL, latin, ea, cs, sym, hlinkClick, hlinkMouseOver + * many attributes + */ +// TODO: FINISH CHILD ELEMENTS highlight TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver +class DefRPr : OOXMLElement { + private var fillGroup: FillGroup? = null + private var effect: EffectPropsGroup? = null + private var line: Ln? = null + private var attrs: HashMap? = null + private var latin: String? = null + private var ea: String? = null + private var cs: String? = null // really children but only have 1 attribute and no children + + /** + * return all the text properties in hashmap from + * + * @return + */ + // TODO: Fill, line ... + val textProperties: HashMap + get() { + val textprops = HashMap() + textprops.putAll(attrs!!) + textprops["latin_typeface"] = latin + textprops["ea_typeface"] = ea + textprops["cs_typeface"] = cs + return textprops + } + + override// group fill + // group effect + // highlight + // TEXTUNDERLINELINE + // TEXTUNDERLINEFILL + // hLinkClick + // hLinkMouseOver + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (line != null) ooxml.append(line!!.ooxml) + if (fillGroup != null) ooxml.append(fillGroup!!.ooxml) + if (effect != null) ooxml.append(effect!!.ooxml) + if (latin != null) ooxml.append("") + if (ea != null) ooxml.append("") + if (cs != null) ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + /** + * create a default default Run Properties + * for BIFF8 compatibility + * qaa + */ + constructor() { + this.attrs = HashMap() + this.attrs!!["sz"] = "900" + this.attrs!!["b"] = "1" + this.attrs!!["i"] = "0" + this.attrs!!["u"] = "none" + this.attrs!!["strike"] = "noStrike" + this.attrs!!["baseline"] = "0" + this.fillGroup = FillGroup(null, null, null, null, SolidFill()) + this.latin = "Arial" + this.ea = "Arial" + this.cs = "Arial" + } + + /** + * create a default paragraph property from the specified information + * + * @param fontFace String font face e.g. "Arial" + * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) + * @param b boolean true if bold + * @param i boolean true if italic + * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted + * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) + * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none + * @param clr String fill color in hex form without the # + */ + constructor(fontFace: String, sz: Int, b: Boolean, i: Boolean, u: String, strike: String, clr: String) { + this.attrs = HashMap() + this.attrs!!["sz"] = sz.toString() + this.attrs!!["b"] = if (b) "1" else "0" + this.attrs!!["i"] = if (i) "1" else "0" + this.attrs!!["u"] = u + this.attrs!!["strike"] = strike + this.attrs!!["baseline"] = "0" + this.fillGroup = FillGroup(null, null, null, null, SolidFill(clr)) + this.latin = fontFace + this.ea = fontFace + this.cs = fontFace + } + + constructor(fill: FillGroup, effect: EffectPropsGroup, l: Ln, attrs: HashMap, latin: String, ea: String, cs: String) { + this.fillGroup = fill + this.effect = effect + this.line = l + this.latin = latin + this.ea = ea + this.cs = cs + this.attrs = attrs + } + + constructor(dp: DefRPr) { + this.fillGroup = dp.fillGroup + this.effect = dp.effect + this.line = dp.line + this.latin = dp.latin + this.ea = dp.ea + this.cs = dp.cs + this.attrs = dp.attrs + } + + override fun cloneElement(): OOXMLElement { + return DefRPr(this) + } + + companion object { + + private val serialVersionUID = 6764149567499222506L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var fill: FillGroup? = null + var effect: EffectPropsGroup? = null + var l: Ln? = null + var latin: String? = null + var ea: String? = null + var cs: String? = null + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "defRPr") { // default text properties + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "ln") { + lastTag.push(tnm) + l = Ln.parseOOXML(xpp, lastTag, bk) as Ln + } else if (tnm == "solidFill" || + tnm == "noFill" || + tnm == "gradFill" || + tnm == "grpFill" || + tnm == "pattFill" || + tnm == "blipFill") { + lastTag.push(tnm) + fill = FillGroup.parseOOXML(xpp, lastTag, bk) as FillGroup + } else if (tnm == "effectLst" || tnm == "effectDag") { + lastTag.push(tnm) + effect = EffectPropsGroup.parseOOXML(xpp, lastTag) as EffectPropsGroup + // TODO: Eventually these will be objects + } else if (tnm == "latin") { + latin = xpp.getAttributeValue(0) + } else if (tnm == "ea") { + ea = xpp.getAttributeValue(0) + } else if (tnm == "cs") { + cs = xpp.getAttributeValue(0) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "defRPr") { + lastTag.pop() // pop this tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("defPr.parseOOXML: $e") + } + + return DefRPr(fill, effect, l, attrs, latin, ea, cs) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Dxf.java b/src/main/java/io/starter/formats/OOXML/Dxf.java deleted file mode 100644 index b4c5ec1..0000000 --- a/src/main/java/io/starter/formats/OOXML/Dxf.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Font; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Arrays; - - -/** - * dxf (Formatting) OOXML Element - *

                  - * A single dxf record, expressing incremental formatting to be applied. - * Used for Conditional Formatting, Tables, Sort Conditions, Color filters ... - *

                  - * Differntial Formatting: - * define formatting for all non-cell formatting in the workbook. Whereas xf records fully specify a particular aspect of formatting (e.g., cell borders) - * by referencing those formatting definitions elsewhere in the Styles part, dxf records specify incremental (or - * differential) aspects of formatting directly inline within the dxf element. The dxf formatting is to be applied on - * top of or in addition to any formatting already present on the object using the dxf record. - *

                  - * parent: (StyleSheet styles.xml) dxfs - * chilren: SEQUENCE: font, numFmt, fill, alignment, border, protection - */ -// TODO: protection element -public class Dxf implements OOXMLElement { - - private static final long serialVersionUID = -5999328795988018131L; - private Font font = null; - private NumFmt numFmt = null; - private Fill fill = null; - private Alignment alignment = null; - private Border border = null; - private WorkBookHandle wbh = null; - - public Dxf(Font fnt, NumFmt nf, Fill f, Alignment a, Border b, WorkBookHandle wbh) { - this.font = fnt; - this.numFmt = nf; - this.fill = f; - this.alignment = a; - this.border = b; - this.wbh = wbh; - } - - public Dxf(Dxf d) { - this.font = d.font; - this.numFmt = d.numFmt; - this.fill = d.fill; - this.alignment = d.alignment; - this.border = d.border; - this.wbh = d.wbh; - } - - public Dxf() { - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - Font fnt = null; - NumFmt nf = null; - Fill f = null; - Alignment a = null; - Border b = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("font")) { - fnt = Font.parseOOXML(xpp, bk); - } else if (tnm.equals("numFmt")) { - nf = (NumFmt) NumFmt.parseOOXML(xpp); - } else if (tnm.equals("fill")) { - f = (Fill) Fill.parseOOXML(xpp, true, bk); - } else if (tnm.equals("alignment")) { - a = (Alignment) Alignment.parseOOXML(xpp); - } else if (tnm.equals("border")) { - b = (Border) Border.parseOOXML(xpp, bk); - } else if (tnm.equals("protection")) { - // TODO: finish - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("dxf")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("dxf.parseOOXML: " + e.toString()); - } - Dxf d = new Dxf(fnt, nf, f, a, b, bk); - return d; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (font != null) ooxml.append(font.getOOXML()); - if (numFmt != null) ooxml.append(numFmt.getOOXML()); - if (fill != null) ooxml.append(fill.getOOXML(true)); - if (alignment != null) ooxml.append(alignment.getOOXML()); - if (border != null) ooxml.append(border.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public int[] getBorderColors() { - if (border != null) - return border.getBorderColorInts(); - return null; - } - - public int[] getBorderStyles() { - if (border != null) { - int[] styles = border.getBorderStyles(); - if (Arrays.equals(new int[]{0, 0, 0, 0}, styles)) - return null; - return styles; - } - return null; - } - - public int[] getBorderSizes() { - if (border != null) - return border.getBorderSizes(); - return null; - } - - public boolean isStriken() { - if (font != null) - return font.getStricken(); - return false; - } - - public int getFg() { - if (fill != null) - return fill.getFgColorAsInt(wbh.getWorkBook().getTheme()); - return -1; - } - - public int getFillPatternInt() { - if (fill != null) - return fill.getFillPatternInt(); - return -1; - } - - /** - * returns the OOXML Fill element - * - * @return - */ - public Fill getFill() { - return fill; - } - - public int getBg() { - if (fill != null) - return fill.getBgColorAsInt(wbh.getWorkBook().getTheme()); - return -1; - } - - public String getBgColorAsString() { - if (fill != null) - return fill.getBgColorAsRGB(wbh.getWorkBook().getTheme()); - return null; - } - - - public String getHorizontalAlign() { - if (alignment != null) - return alignment.getAlignment("horizontal"); - return null; - } - - public String getVerticalAlign() { - if (alignment != null) - return alignment.getAlignment("vertical"); - return null; - } - - public String getNumberFormat() { - if (numFmt != null) - return numFmt.getFormatId(); - return null; - } - - /** - * returns the Font for ths dxf, if any - * - * @return - */ - public Font getFont() { - return font; - } - - public int getFontHeight() { - if (font != null) - return font.getFontHeight(); - return -1; - } - - public int getFontWeight() { - if (font != null) - return font.getFontWeight(); - return -1; - } - - public String getFontName() { - if (font != null) - return font.getFontName(); - return null; - } - - public int getFontColor() { - if (font != null) - return font.getColor(); - return -1; - } - - public boolean isItalic() { - if (font != null) - return font.getItalic(); - return false; - } - - public int getFontUnderline() { - if (font != null) - return font.getUnderlineStyle(); - return -1; - } - - /** - * return a String representation of this Dxf in "style properties" notation - * - * @return String representation of this Dxf - * @see Cf.setStylePropsFromString - */ - public String getStyleProps() { - StringBuffer props = new StringBuffer(); - - // fill - if (fill != null) { - props.append("pattern:" + fill.getFillPatternInt() + ";"); - String s = fill.getFgColorAsRGB(wbh.getWorkBook().getTheme()); - if (s != null) // fg is pattern color - props.append("patterncolor:#" + s + ";"); - s = fill.getBgColorAsRGB(wbh.getWorkBook().getTheme()); - if (s != null) - props.append("background:#" + s + ";"); - } - - // font - if (font != null) { // note: since this is differential, many of these may not be set - if (!font.getFontName().equals("")) - props.append("font-name" + font.getFontName() + ";"); - if (font.getFontWeight() > -1) - props.append("font-weight:" + font.getFontWeight() + ";"); - if (font.getFontHeight() > -1) - props.append("font-Height:" + font.getFontHeight() + ";"); - props.append("font-ColorIndex:" + font.getColor() + ";"); - if (font.getStricken()) props.append("font-Striken:" + font.getStricken() + ";"); - if (font.getItalic()) props.append("font-italic:" + font.getItalic() + ";"); - if (font.getUnderlineStyle() != 0) props.append("font-UnderlineStyle:" + font.getUnderlineStyle() + ";"); - // TODO: italic, bold - } - - // borders - if (border != null) { - int[] sizes = border.getBorderSizes(); - int[] styles = border.getBorderStyles(); - String[] colors = border.getBorderColors(); - props.append("border-top:" + sizes[0] + " " + FormatHandle.BORDER_NAMES[styles[0]] + " " + colors[0] + ";"); - props.append("border-left:" + sizes[1] + " " + FormatHandle.BORDER_NAMES[styles[1]] + " " + colors[1] + ";"); - props.append("border-bottom:" + sizes[2] + " " + FormatHandle.BORDER_NAMES[styles[2]] + " " + colors[2] + ";"); - props.append("border-right:" + sizes[3] + " " + FormatHandle.BORDER_NAMES[styles[3]] + " " + colors[3] + ";"); - } - - // alignment - if (alignment != null) { - String s = alignment.getAlignment("vertical"); - if (s != null) - props.append("alignment-vertical" + s + ";"); - s = alignment.getAlignment("horizontal"); - if (s != null) - props.append("alignment-horizontal" + s + ";"); - } - - // number format - if (numFmt != null) { - String s = numFmt.getFormatId(); - props.append("numberformat:" + s + ";"); - - } - - return props.toString(); - } - - - /** - * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info - */ - public void createFont(int w, boolean i, int ustyle, int cl, int h) { - font = new Font("", w, h); - if (w == 700) - font.setBold(true); // why doesn't constructor do this? - if (i) font.setItalic(i); - if (ustyle != 0) font.setUnderlineStyle((byte) ustyle); - font.setColor(cl); - } - - /** - * Sets the fill for this dxf from an existing Fill element - * - * @param f - */ - public void setFill(Fill f) { - this.fill = (Fill) f.cloneElement(); - } - - /** - * Sts the Font for this dxf from an existing Font - * - * @param f - */ - public void setFont(Font f) { - this.font = f; - } - - /** - * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info - */ - public void createFill(int fs, int fg, int bg, WorkBookHandle bk) { - if (fs < 0 || fs > OOXMLConstants.patternFill.length) - this.fill = new Fill(null, fg, bg, bk.getWorkBook().getTheme()); // meaning it's the default (solid bg) pattern - else - this.fill = new Fill(OOXMLConstants.patternFill[fs], fg, bg, bk.getWorkBook().getTheme()); - } - - /** - * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info - */ - public void createBorder(WorkBookHandle bk, int[] styles, int[] colors) { - border = new Border(bk, styles, colors); - } - - public OOXMLElement cloneElement() { - return new Dxf(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Dxf.kt b/src/main/java/io/starter/formats/OOXML/Dxf.kt new file mode 100644 index 0000000..959e93c --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Dxf.kt @@ -0,0 +1,322 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Font +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Arrays + + +/** + * dxf (Formatting) OOXML Element + * + * + * A single dxf record, expressing incremental formatting to be applied. + * Used for Conditional Formatting, Tables, Sort Conditions, Color filters ... + * + * + * Differntial Formatting: + * define formatting for all non-cell formatting in the workbook. Whereas xf records fully specify a particular aspect of formatting (e.g., cell borders) + * by referencing those formatting definitions elsewhere in the Styles part, dxf records specify incremental (or + * differential) aspects of formatting directly inline within the dxf element. The dxf formatting is to be applied on + * top of or in addition to any formatting already present on the object using the dxf record. + * + * + * parent: (StyleSheet styles.xml) dxfs + * chilren: SEQUENCE: font, numFmt, fill, alignment, border, protection + */ +// TODO: protection element +class Dxf : OOXMLElement { + /** + * returns the Font for ths dxf, if any + * + * @return + */ + /** + * Sts the Font for this dxf from an existing Font + * + * @param f + */ + var font: Font? = null + private var numFmt: NumFmt? = null + private var fill: Fill? = null + private var alignment: Alignment? = null + private var border: Border? = null + private var wbh: WorkBookHandle? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (font != null) ooxml.append(font!!.ooxml) + if (numFmt != null) ooxml.append(numFmt!!.ooxml) + if (fill != null) ooxml.append(fill!!.getOOXML(true)) + if (alignment != null) ooxml.append(alignment!!.ooxml) + if (border != null) ooxml.append(border!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + val borderColors: IntArray? + get() = if (border != null) border!!.borderColorInts else null + + val borderStyles: IntArray? + get() { + if (border != null) { + val styles = border!!.borderStyles + return if (Arrays.equals(intArrayOf(0, 0, 0, 0), styles)) null else styles + } + return null + } + + val borderSizes: IntArray? + get() = if (border != null) border!!.borderSizes else null + + val isStriken: Boolean + get() = if (font != null) font!!.stricken else false + + val fg: Int + get() = if (fill != null) fill!!.getFgColorAsInt(wbh!!.workBook!!.theme) else -1 + + val fillPatternInt: Int + get() = if (fill != null) fill!!.fillPatternInt else -1 + + val bg: Int + get() = if (fill != null) fill!!.getBgColorAsInt(wbh!!.workBook!!.theme) else -1 + + val bgColorAsString: String? + get() = if (fill != null) fill!!.getBgColorAsRGB(wbh!!.workBook!!.theme) else null + + + val horizontalAlign: String? + get() = if (alignment != null) alignment!!.getAlignment("horizontal") else null + + val verticalAlign: String? + get() = if (alignment != null) alignment!!.getAlignment("vertical") else null + + val numberFormat: String? + get() = if (numFmt != null) numFmt!!.formatId else null + + val fontHeight: Int + get() = if (font != null) font!!.fontHeight else -1 + + val fontWeight: Int + get() = if (font != null) font!!.fontWeight else -1 + + val fontName: String? + get() = if (font != null) font!!.fontName else null + + val fontColor: Int + get() = if (font != null) font!!.color else -1 + + val isItalic: Boolean + get() = if (font != null) font!!.italic else false + + val fontUnderline: Int + get() = if (font != null) font!!.underlineStyle else -1 + + /** + * return a String representation of this Dxf in "style properties" notation + * + * @return String representation of this Dxf + * @see Cf.setStylePropsFromString + */ + // fill + // fg is pattern color + // font + // note: since this is differential, many of these may not be set + // TODO: italic, bold + // borders + // alignment + // number format + val styleProps: String + get() { + val props = StringBuffer() + if (fill != null) { + props.append("pattern:" + fill!!.fillPatternInt + ";") + var s = fill!!.getFgColorAsRGB(wbh!!.workBook!!.theme) + if (s != null) + props.append("patterncolor:#$s;") + s = fill!!.getBgColorAsRGB(wbh!!.workBook!!.theme) + if (s != null) + props.append("background:#$s;") + } + if (font != null) { + if (font!!.fontName != "") + props.append("font-name" + font!!.fontName + ";") + if (font!!.fontWeight > -1) + props.append("font-weight:" + font!!.fontWeight + ";") + if (font!!.fontHeight > -1) + props.append("font-Height:" + font!!.fontHeight + ";") + props.append("font-ColorIndex:" + font!!.color + ";") + if (font!!.stricken) props.append("font-Striken:" + font!!.stricken + ";") + if (font!!.italic) props.append("font-italic:" + font!!.italic + ";") + if (font!!.underlineStyle != 0) props.append("font-UnderlineStyle:" + font!!.underlineStyle + ";") + } + if (border != null) { + val sizes = border!!.borderSizes + val styles = border!!.borderStyles + val colors = border!!.borderColors + props.append("border-top:" + sizes[0] + " " + FormatHandle.BORDER_NAMES[styles[0]] + " " + colors[0] + ";") + props.append("border-left:" + sizes[1] + " " + FormatHandle.BORDER_NAMES[styles[1]] + " " + colors[1] + ";") + props.append("border-bottom:" + sizes[2] + " " + FormatHandle.BORDER_NAMES[styles[2]] + " " + colors[2] + ";") + props.append("border-right:" + sizes[3] + " " + FormatHandle.BORDER_NAMES[styles[3]] + " " + colors[3] + ";") + } + if (alignment != null) { + var s = alignment!!.getAlignment("vertical") + if (s != null) + props.append("alignment-vertical$s;") + s = alignment!!.getAlignment("horizontal") + if (s != null) + props.append("alignment-horizontal$s;") + } + if (numFmt != null) { + val s = numFmt!!.formatId + props.append("numberformat:$s;") + + } + + return props.toString() + } + + constructor(fnt: Font, nf: NumFmt, f: Fill, a: Alignment, b: Border, wbh: WorkBookHandle) { + this.font = fnt + this.numFmt = nf + this.fill = f + this.alignment = a + this.border = b + this.wbh = wbh + } + + constructor(d: Dxf) { + this.font = d.font + this.numFmt = d.numFmt + this.fill = d.fill + this.alignment = d.alignment + this.border = d.border + this.wbh = d.wbh + } + + constructor() {} + + /** + * returns the OOXML Fill element + * + * @return + */ + fun getFill(): Fill? { + return fill + } + + + /** + * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info + */ + fun createFont(w: Int, i: Boolean, ustyle: Int, cl: Int, h: Int) { + font = Font("", w, h) + if (w == 700) + font!!.bold = true // why doesn't constructor do this? + if (i) font!!.italic = i + if (ustyle != 0) font!!.setUnderlineStyle(ustyle.toByte()) + font!!.color = cl + } + + /** + * Sets the fill for this dxf from an existing Fill element + * + * @param f + */ + fun setFill(f: Fill) { + this.fill = f.cloneElement() as Fill + } + + /** + * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info + */ + fun createFill(fs: Int, fg: Int, bg: Int, bk: WorkBookHandle) { + if (fs < 0 || fs > OOXMLConstants.patternFill.size) + this.fill = Fill(null, fg, bg, bk.workBook!!.theme) // meaning it's the default (solid bg) pattern + else + this.fill = Fill(OOXMLConstants.patternFill[fs], fg, bg, bk.workBook!!.theme) + } + + /** + * for BIFF8->OOXML Compatiblity, create a dxf from Cf style info + */ + fun createBorder(bk: WorkBookHandle, styles: IntArray, colors: IntArray) { + border = Border(bk, styles, colors) + } + + override fun cloneElement(): OOXMLElement { + return Dxf(this) + } + + companion object { + + private val serialVersionUID = -5999328795988018131L + + fun parseOOXML(xpp: XmlPullParser, bk: WorkBookHandle): OOXMLElement { + var fnt: Font? = null + var nf: NumFmt? = null + var f: Fill? = null + var a: Alignment? = null + var b: Border? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "font") { + fnt = Font.parseOOXML(xpp, bk) + } else if (tnm == "numFmt") { + nf = NumFmt.parseOOXML(xpp) as NumFmt + } else if (tnm == "fill") { + f = Fill.parseOOXML(xpp, true, bk) as Fill + } else if (tnm == "alignment") { + a = Alignment.parseOOXML(xpp) as Alignment + } else if (tnm == "border") { + b = Border.parseOOXML(xpp, bk) as Border + } else if (tnm == "protection") { + // TODO: finish + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "dxf") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("dxf.parseOOXML: $e") + } + + return Dxf(fnt, nf, f, a, b, bk) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.java b/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.java deleted file mode 100644 index 1813718..0000000 --- a/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * EffectPropsGroup Effect Properties either effectDag or effectLst - */ -//TODO: FINISH CHILD ELEMENTS for both effectDag and effectLst -public class EffectPropsGroup implements OOXMLElement { - - private static final long serialVersionUID = 8250236905326475833L; - - private EffectDag effectDag; - private EffectLst effectLst; - - public EffectPropsGroup(EffectDag ed, EffectLst el) { - this.effectDag = ed; - this.effectLst = el; - } - - public EffectPropsGroup(EffectPropsGroup e) { - this.effectDag = e.effectDag; - this.effectLst = e.effectLst; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - EffectDag ed = null; - EffectLst el = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("effectDag")) { - lastTag.push(tnm); - ed = EffectDag.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("effectLst")) { - lastTag.push(tnm); - el = EffectLst.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("EffectPropsGroup.parseOOXML: " + e.toString()); - } - EffectPropsGroup e = new EffectPropsGroup(ed, el); - return e; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (effectDag != null) ooxml.append(effectDag.getOOXML()); - if (effectLst != null) ooxml.append(effectLst.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new EffectPropsGroup(this); - } -} - -/** - * effectDag (Effect Container) - * This element specifies a list of effects. Effects are applied in the order specified by the container type (sibling or - * tree). - *

                  - * parent: many - * children: MANY (EFFECT) - */ // TODO: FINISH CHILD ELEMENTS -class EffectDag implements OOXMLElement { - - private static final long serialVersionUID = 4786440439664356745L; - private HashMap attrs = null; - - public EffectDag(HashMap attrs) { - this.attrs = attrs; - } - - public EffectDag(EffectDag e) { - this.attrs = e.attrs; - } - - - public static EffectDag parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("effectDag")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("CHILDELEMENT")) { - lastTag.push(tnm); - //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("effectDag")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("effectDag.parseOOXML: " + e.toString()); - } - EffectDag e = new EffectDag(attrs); - return e; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); -// if (CHILD!=null) { ooxml.append(CHILD.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new EffectDag(this); - } -} - -/** - * effectLst (Effect Container) - * This element specifies a list of effects. Effects in an effectLst are applied in the default order by the rendering - * engine. The following diagrams illustrate the order in which effects are to be applied, both for shapes and for - * group shapes. - *

                  - * parent: many - * children: MANY (EFFECT) - */ // TODO: FINISH CHILD ELEMENTS -class EffectLst implements OOXMLElement { - - private static final long serialVersionUID = -6164888373165090983L; - - // public effectLst() { } - public EffectLst() { - } - - public EffectLst(EffectLst e) { - } - - - public static EffectLst parseOOXML(XmlPullParser xpp, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("CHILDELEMENT")) { - //lastTag.push(tnm); - //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("effectLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("effectLst.parseOOXML: " + e.toString()); - } - EffectLst e = new EffectLst(); - return e; - } - - public String getOOXML() { - //StringBuffer ooxml= new StringBuffer(); -// ooxml.append(""); - return ""; // TODO: FINISH CHILD ELEMENTS -// return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new EffectLst(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.kt b/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.kt new file mode 100644 index 0000000..d7e67bd --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/EffectPropsGroup.kt @@ -0,0 +1,243 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * EffectPropsGroup Effect Properties either effectDag or effectLst + */ +//TODO: FINISH CHILD ELEMENTS for both effectDag and effectLst +class EffectPropsGroup : OOXMLElement { + + private var effectDag: EffectDag? = null + private var effectLst: EffectLst? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + if (effectDag != null) ooxml.append(effectDag!!.ooxml) + if (effectLst != null) ooxml.append(effectLst!!.ooxml) + return ooxml.toString() + } + + constructor(ed: EffectDag, el: EffectLst) { + this.effectDag = ed + this.effectLst = el + } + + constructor(e: EffectPropsGroup) { + this.effectDag = e.effectDag + this.effectLst = e.effectLst + } + + override fun cloneElement(): OOXMLElement { + return EffectPropsGroup(this) + } + + companion object { + + private val serialVersionUID = 8250236905326475833L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + var ed: EffectDag? = null + var el: EffectLst? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "effectDag") { + lastTag.push(tnm) + ed = EffectDag.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } else if (tnm == "effectLst") { + lastTag.push(tnm) + el = EffectLst.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here + lastTag.pop() + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("EffectPropsGroup.parseOOXML: $e") + } + + return EffectPropsGroup(ed, el) + } + } +} + +/** + * effectDag (Effect Container) + * This element specifies a list of effects. Effects are applied in the order specified by the container type (sibling or + * tree). + * + * + * parent: many + * children: MANY (EFFECT) + */ // TODO: FINISH CHILD ELEMENTS +internal class EffectDag : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + // if (CHILD!=null) { ooxml.append(CHILD.getOOXML()); + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(e: EffectDag) { + this.attrs = e.attrs + } + + override fun cloneElement(): OOXMLElement { + return EffectDag(this) + } + + companion object { + + private val serialVersionUID = 4786440439664356745L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): EffectDag { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "effectDag") { + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "CHILDELEMENT") { + lastTag.push(tnm) + //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "effectDag") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("effectDag.parseOOXML: $e") + } + + return EffectDag(attrs) + } + } +} + +/** + * effectLst (Effect Container) + * This element specifies a list of effects. Effects in an effectLst are applied in the default order by the rendering + * engine. The following diagrams illustrate the order in which effects are to be applied, both for shapes and for + * group shapes. + * + * + * parent: many + * children: MANY (EFFECT) + */ // TODO: FINISH CHILD ELEMENTS +internal class EffectLst : OOXMLElement { + + override//StringBuffer ooxml= new StringBuffer(); + // ooxml.append(""); + // TODO: FINISH CHILD ELEMENTS + // return ooxml.toString(); + val ooxml: String + get() = "" + + // public effectLst() { } + constructor() {} + + constructor(e: EffectLst) {} + + override fun cloneElement(): OOXMLElement { + return EffectLst(this) + } + + companion object { + + private val serialVersionUID = -6164888373165090983L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): EffectLst { + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "CHILDELEMENT") { + //lastTag.push(tnm); + //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "effectLst") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("effectLst.parseOOXML: $e") + } + + return EffectLst() + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Ext.java b/src/main/java/io/starter/formats/OOXML/Ext.java deleted file mode 100644 index bb86621..0000000 --- a/src/main/java/io/starter/formats/OOXML/Ext.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -public class Ext implements OOXMLElement { - - private static final long serialVersionUID = 2827740330905704185L; - private HashMap attrs; - private String nameSpace = null; - - public Ext() { - attrs = new HashMap(); - attrs.put("cx", "0"); - attrs.put("cy", "0"); - } - - public Ext(HashMap attrs, String ns) { - this.attrs = attrs; - this.nameSpace = ns; - } - - public Ext(Ext e) { - this.attrs = e.attrs; - this.nameSpace = e.nameSpace; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - String ns = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ext")) { // get attributes - ns = xpp.getPrefix(); - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("ext")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ext.parseOOXML: " + e.toString()); - } - Ext e = new Ext(attrs, ns); - return e; - } - - /** - * set the namespace for ext element - * - * @param ns - */ - public void setNamespace(String ns) { - this.nameSpace = ns; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append("<" + this.nameSpace + ":ext"); - // attributes - Iterator i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Ext(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Ext.kt b/src/main/java/io/starter/formats/OOXML/Ext.kt new file mode 100644 index 0000000..6e1c1de --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Ext.kt @@ -0,0 +1,115 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +class Ext : OOXMLElement { + private var attrs: HashMap? = null + private var nameSpace: String? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("<" + this.nameSpace + ":ext") + val i = attrs!!.keys.iterator() + while (i.hasNext()) { + val key = i.next() + val `val` = attrs!![key] + ooxml.append(" $key=\"$`val`\"") + } + ooxml.append("/>") + return ooxml.toString() + } + + constructor() { + attrs = HashMap() + attrs!!["cx"] = "0" + attrs!!["cy"] = "0" + } + + constructor(attrs: HashMap, ns: String) { + this.attrs = attrs + this.nameSpace = ns + } + + constructor(e: Ext) { + this.attrs = e.attrs + this.nameSpace = e.nameSpace + } + + /** + * set the namespace for ext element + * + * @param ns + */ + fun setNamespace(ns: String) { + this.nameSpace = ns + } + + override fun cloneElement(): OOXMLElement { + return Ext(this) + } + + companion object { + + private val serialVersionUID = 2827740330905704185L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + val attrs = HashMap() + var ns: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "ext") { // get attributes + ns = xpp.prefix + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "ext") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("ext.parseOOXML: $e") + } + + return Ext(attrs, ns) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/ExtLst.java b/src/main/java/io/starter/formats/OOXML/ExtLst.java deleted file mode 100644 index 376d04b..0000000 --- a/src/main/java/io/starter/formats/OOXML/ExtLst.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Stack; - -// TODO: FINISH -public class ExtLst implements OOXMLElement { - - private static final long serialVersionUID = -4122012942547055359L; - private HashMap attrs = null; - private String nameSpace = null; - - public ExtLst() { - attrs = new HashMap(); - attrs.put("cx", "0"); - attrs.put("cy", "0"); - } - - public ExtLst(HashMap attrs, String ns) { - this.attrs = attrs; - this.nameSpace = ns; - } - - public ExtLst(ExtLst e) { - this.attrs = e.attrs; - this.nameSpace = e.nameSpace; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - String ns = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("extLst")) { // get attributes - ns = xpp.getPrefix(); - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("extLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("extLst.parseOOXML: " + e.toString()); - } - Ext e = new Ext(attrs, ns); - return e; - } - - /** - * set the namespace for ext element - * - * @param ns - */ - public void setNamespace(String ns) { - this.nameSpace = ns; - } - - public String getOOXML() { - //TODO: FINISH - return ""; - /* - StringBuffer ooxml= new StringBuffer(); - ooxml.append("<" + this.ns + ":ext"); - // attributes - Iterator i= attrs.keySet().iterator(); - while (i.hasNext()) { - String key= (String) i.next(); - String val= (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - */ - } - - public OOXMLElement cloneElement() { - return new ExtLst(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/ExtLst.kt b/src/main/java/io/starter/formats/OOXML/ExtLst.kt new file mode 100644 index 0000000..b2836cb --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/ExtLst.kt @@ -0,0 +1,117 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +// TODO: FINISH +class ExtLst : OOXMLElement { + private var attrs: HashMap? = null + private var nameSpace: String? = null + + override//TODO: FINISH + /* + StringBuffer ooxml= new StringBuffer(); + ooxml.append("<" + this.ns + ":ext"); + // attributes + Iterator i= attrs.keySet().iterator(); + while (i.hasNext()) { + String key= (String) i.next(); + String val= (String) attrs.get(key); + ooxml.append(" " + key + "=\"" + val + "\""); + } + ooxml.append("/>"); + return ooxml.toString(); + */ val ooxml: String + get() = "" + + constructor() { + attrs = HashMap() + attrs!!["cx"] = "0" + attrs!!["cy"] = "0" + } + + constructor(attrs: HashMap, ns: String) { + this.attrs = attrs + this.nameSpace = ns + } + + constructor(e: ExtLst) { + this.attrs = e.attrs + this.nameSpace = e.nameSpace + } + + /** + * set the namespace for ext element + * + * @param ns + */ + fun setNamespace(ns: String) { + this.nameSpace = ns + } + + override fun cloneElement(): OOXMLElement { + return ExtLst(this) + } + + companion object { + + private val serialVersionUID = -4122012942547055359L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + val attrs = HashMap() + var ns: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "extLst") { // get attributes + ns = xpp.prefix + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "extLst") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("extLst.parseOOXML: $e") + } + + return Ext(attrs, ns) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Fill.java b/src/main/java/io/starter/formats/OOXML/Fill.java deleted file mode 100644 index f37b33e..0000000 --- a/src/main/java/io/starter/formats/OOXML/Fill.java +++ /dev/null @@ -1,1186 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Xf; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; - -/** - * fill OOXML element - *

                  - * fill (Fill) This element specifies fill formatting - *

                  - * parent: styleSheet/fills element in styles.xml, dxf->fills children: REQ - * CHOICE OF: patternFill, gradientFill - */ -public class Fill implements OOXMLElement { - - private static final long serialVersionUID = -4510508531435037641L; - private PatternFill patternFill = null; - private GradientFill gradientFill = null; - private Theme theme = null; - - public Fill(PatternFill p, GradientFill g, Theme t) { - this.patternFill = p; - this.gradientFill = g; - this.theme = t; - } - - public Fill(Fill f) { - if (f.patternFill != null) - this.patternFill = (PatternFill) f.patternFill.cloneElement(); - if (f.gradientFill != null) - this.gradientFill = (GradientFill) f.gradientFill.cloneElement(); - this.theme = f.theme; - } - - /** - * create a new Fill from external vals - * - * @param fs String pattern type - * @param fg int color index - * @param bg int color index - */ - public Fill(String fs, int fg, int bg, Theme t) { - this.patternFill = new PatternFill(fs, fg, bg); - this.theme = t; - } - - /** - * create a new Fill from external vals - * - * @param i XLS indexed pattern - * @param fg int color index - * @param fgColorCustom - * @param bg int color index - */ - public Fill(int pattern, int fg, String fgColorCustom, int bg, - String bgColorCustom, Theme t) { - - this.patternFill = new PatternFill(PatternFill.translateIndexedFillPattern(pattern), - fg, fgColorCustom, bg, bgColorCustom); - this.theme = t; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, boolean isDxf, WorkBookHandle bk) { - PatternFill p = null; - GradientFill g = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("patternFill")) { - p = PatternFill.parseOOXML(xpp, isDxf, bk); - - } else if (tnm.equals("gradientFill")) { - g = GradientFill.parseOOXML(xpp, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fill")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fill.parseOOXML: " + e.toString()); - } - Fill oe = new Fill(p, g, bk.getWorkBook().getTheme()); - return oe; - } - - /** - * OOXML values are stored with an intiall FF in their value, - *

                  - * this method assures that values returned are 6 digits + # for web usage - * - * @param rgbcolor - */ - protected static String transformToWebRGBColor(String rgbcolor) { - if (rgbcolor.indexOf("#") == 0) return rgbcolor; - if (rgbcolor.indexOf("FF") == 0 && rgbcolor.length() == 8) { - return "#" + rgbcolor.substring(2); - } - return "#" + rgbcolor; - } - - /** - * OOXML values are stored with an intiall FF in their value, - *

                  - * this method assures that values returned are 8 digits with - * ff appended to rgb value for ooxml usage - * - * @param rgbcolor - */ - protected static String transformToOOXMLRGBColor(String rgbcolor) { - if (rgbcolor.indexOf("#") == 0) { - return "FF" + rgbcolor.substring(1); - } - if (rgbcolor.indexOf("FF") == 0 && rgbcolor.length() == 8) { - return rgbcolor; - } - return "FF" + rgbcolor; - } - - public String getOOXML() { - return getOOXML(false); - } - - /** - * dxfs apparently have different pattern fill syntax -- UNDOCUMENTED **** - * - * @param isDxf if this is an Dxf-generated fill, solid fills are handled - * differently than regular fills - * @return - */ - public String getOOXML(boolean isDxf) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (patternFill != null) - ooxml.append(patternFill.getOOXML(isDxf)); - if (gradientFill != null) - ooxml.append(gradientFill.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Fill(this); - } - - /** - * returns the OOXML specifying the fill based on this FormatHandle object - */ - public static String getOOXML(Xf xf) { - if (xf.getFill() != null) - return xf.getFill().getOOXML(); - - // otherwise, create fill from 2003-style xf - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - try { - ooxml.append(""); - } catch (IndexOutOfBoundsException e) { - ooxml.append(""); // apparently there are less patterns - // in xlsx? some other way of - // storage - } - int fg = xf.getForegroundColor(); - if (fg > -1 && fg != 64) - ooxml.append(""); - int bg = xf.getBackgroundColor(); - if (bg > -1 && bg != 64) - ooxml.append(""); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - return ooxml.toString(); - } - - /** - * returns the OOXML specifying the fill based on fill pattern fs, - * foreground color fg, background color bg - */ - public static String getOOXML(int fs, int fg, int bg) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - try { - ooxml.append(""); - } catch (ArrayIndexOutOfBoundsException e) { - ooxml.append(""); - } - if (fg > -1 && fg != 64) { - ooxml.append(""); - ooxml.append("\r\n"); - } - if (bg > -1 && bg != 64) { - ooxml.append(""); - ooxml.append("\r\n"); - } - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - return ooxml.toString(); - } - - /** - * return the foreground color of this fill, if any - * - * @return - */ - public String getFgColorAsRGB(Theme t) { - if (patternFill != null) - return this.patternFill.getFgColorAsRGB(t); - return null; - } - - /** - * return the fg color in indexed (int) representation - * - * @return - */ - public int getFgColorAsInt(Theme t) { - if (patternFill != null) - return this.patternFill.getFgColorAsInt(t); - return 0; // default= black - } - - /** - * return the bg color of this fill, if any - * - * @return - */ - public String getBgColorAsRGB(Theme t) { - if (patternFill != null) - return this.patternFill.getBgColorAsRGB(t); - return null; - } - - - /** - * return the bg color in indexed (int) representation - * - * @return - */ - public int getBgColorAsInt(Theme t) { - if (patternFill != null) - return this.patternFill.getBgColorAsInt(t); - return -1; - } - - /** - * sets the foreground fill color via color int - * - * @param t - */ - public void setFgColor(int t) { - if (patternFill != null) { - patternFill.setFgColor(t); - return; - } - } - - /** - * sets the foreground fill color to a color string and Excel-2003-mapped - * color int - * - * @param t Excel-2003-mapped color int for the hex color string - * @param colorString hex color string - */ - public void setFgColor(int t, String colorString) { - if (patternFill != null) - patternFill.setFgColor(t, colorString); - else - this.patternFill = new PatternFill("none", t, colorString, -1, null); - } - - /** - * sets the bg fill color via color int - * - * @param t - */ - public void setBgColor(int t) { - if (patternFill != null) { - patternFill.setBgColor(t); - return; - } - } - - /** - * sets the foreground fill color to a color string and Excel-2003-mapped - * color int - * - * @param t Excel-2003-mapped color int for the hex color string - * @param colorString hex color string - */ - public void setBgColor(int t, String colorString) { - if (patternFill != null) - patternFill.setBgColor(t, colorString); - else - this.patternFill = new PatternFill("none", -1, null, t, colorString); - } - - public String getFillPattern() { - if (patternFill != null) - return this.patternFill.getFillPattern(); - return null; - } - - /** - * return the fill pattern in 2003 int representation - * - * @return - */ - public int getFillPatternInt() { - if (patternFill != null) { - return this.patternFill.getFillPatternInt(); - } - return -1; - } - - /** - * sets the fill pattern - */ - public void setFillPattern(int t) { - if (patternFill != null) - this.patternFill.setFillPattern(t); - } - - /** - * returns true if the background pattern is solid - * - * @return - */ - public boolean isBackgroundSolid() { - if (patternFill != null) - return (this.patternFill.getFillPattern().equalsIgnoreCase("solid")); - return false; - } -} - -/** - * patternFill (Pattern) This element is used to specify cell fill information - * for pattern and solid color cell fills. For solid cell fills (no pattern), - * fgColor is used. For cell fills with patterns specified, then the cell fill - * color is specified by the bgColor element. - *

                  - * parent: fill children: SEQ: fgColor, bgColor - */ -class PatternFill implements OOXMLElement { - - private static final long serialVersionUID = -4399355217499895956L; - private String patternType = null; - private FgColor fgColor = null; - private BgColor bgColor = null; - private Theme theme = null; - - public PatternFill(String patternType, FgColor fg, BgColor bg, Theme t) { - this.patternType = patternType; - this.fgColor = fg; - this.bgColor = bg; - this.theme = t; - } - - public PatternFill(PatternFill p) { - this.patternType = p.patternType; - if (p.fgColor != null) - this.fgColor = (FgColor) p.fgColor.cloneElement(); - if (p.bgColor != null) - this.bgColor = (BgColor) p.bgColor.cloneElement(); - this.theme = p.theme; - } - - /** - * create a new pattern fill from external vals - * - * @param patternType String OOXML pattern type - * @param fg int color index - * @param bg int color index - */ - public PatternFill(String patternType, int fg, int bg) { - this.patternType = patternType; - if (fg > -1 && fg != 64) { - HashMap attrs = new HashMap(); - attrs.put( - "rgb", - "FF" + FormatHandle.colorToHexString(FormatHandle.COLORTABLE[fg]).substring(1)); - this.fgColor = new FgColor(attrs); - } - if (bg > -1 && bg != 65) { - HashMap attrs = new HashMap(); - attrs.put( - "rgb", - "FF" - + FormatHandle.colorToHexString( - FormatHandle.COLORTABLE[bg]).substring(1)); - this.bgColor = new BgColor(attrs); - } - } - - public PatternFill(String patternType, int fg, String fgCustom, int bg, - String bgCustom) { - this.patternType = patternType; - if (fg > 0 || fgCustom != null) { // 64= default fg color - HashMap attrs = new HashMap(); - if (fgCustom == null) - attrs.put("indexed", String.valueOf(fg)); - else - attrs.put("rgb", Fill.transformToOOXMLRGBColor(fgCustom)); - this.fgColor = new FgColor(attrs); - } - if (bg > -1 || bgCustom != null) { // 65= default bg color - HashMap attrs = new HashMap(); - if (bgCustom == null) - attrs.put("indexed", String.valueOf(bg)); - else - attrs.put("rgb", Fill.transformToOOXMLRGBColor(bgCustom)); - this.bgColor = new BgColor(attrs); - } - } - - public static PatternFill parseOOXML(XmlPullParser xpp, boolean isDxf, WorkBookHandle bk) { - String patternType = null; // "none"; // default when missing -- so sez - // the doc but doesn't appear what Excel - // does - /** - * APPARENLTY patternFills in dxfs are DIFFERENT AND NOT FOLLOWING THE - * DOCUMENTATION on regular patternFills - * - * APPARENTLY patternType="none" and missing patternType ARE NOT THE - * SAME: if Missing, APPARENTLY means to fill with BG color ("solid" - * means to fill with FG color) - */ - FgColor fg = null; - BgColor bg = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("patternFill")) { // get attributes - if (xpp.getAttributeCount() > 0) - patternType = xpp.getAttributeValue(0); - } else if (tnm.equals("fgColor")) { - fg = FgColor.parseOOXML(xpp); - } else if (tnm.equals("bgColor")) { - bg = BgColor.parseOOXML(xpp); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("patternFill")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("patternFill.parseOOXML: " + e.toString()); - } - if (isDxf) { - if (patternType == null) // null apparently does NOT mean none for - // Dxf's - patternType = "solid"; // see Dxf and Cf handling: null means - // solid fill with bg color as cell - // background - if (patternType.equals("solid")) { - if (bg != null) {// shouldn't! - fg = new FgColor(bg.getAttrs()); // so 2003-v can properly - // set solid cell - // pattern color - } - bg = new BgColor(64); - } - } - PatternFill p = new PatternFill(patternType, fg, bg, bk.getWorkBook().getTheme()); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (fgColor != null) - ooxml.append(fgColor.getOOXML()); - if (bgColor != null) - ooxml.append(bgColor.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - /** - * apparently Fill OOXML from Dxf has differnt syntax - UNDOCUMENTED - * - * @param isDxf if this is an Dxf-generated fill, solid fills are handled - * differently than regular fills - * @return - */ - public String getOOXML(boolean isDxf) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (fgColor != null) - ooxml.append(fgColor.getOOXML()); - if (bgColor != null) - ooxml.append(bgColor.getOOXML()); - } else { - if (patternType.equals("solid")) { // dxf needs "none" or "soild" to - // have bg color set, not fg - // color as is normal - ooxml.append(">"); - if (fgColor != null) { // shoudln't! - BgColor tempbg = new BgColor(fgColor.getAttrs()); - ooxml.append(tempbg.getOOXML()); - } - } else { - ooxml.append(" patternType=\"" + patternType + "\">"); - if (fgColor != null) - ooxml.append(fgColor.getOOXML()); - if (bgColor != null) - ooxml.append(bgColor.getOOXML()); - } - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PatternFill(this); - } - - public String toString() { - return ((patternType != null) ? patternType : "") + " fg:" - + ((fgColor != null) ? fgColor.toString() : "") + " bg:" - + ((bgColor != null) ? bgColor.toString() : ""); - } - - /** - * return the pattern type of this Fill in String representation - * - * @return - */ - public String getFillPattern() { - return patternType; - } - - /** - * return the pattern type of this fill pattern in 2003 int representation
                  - * BIG NOTE: Apparently doc is wrong in that if patternType is missing it - * does NOT == none; instead, it is a SOLID FILL with BG==fill color
                  - * THIS METHOD returns -1 in those cases - * - * @return int pattern fill integer - * @see PatternFill.setFillPattern - */ - public int getFillPatternInt() { - if (patternType == null) // a missing entry *should*==none or 0, but - // apparently is a distinct value in and of - // itself - return OOXMLConstants.patternFill.length + 1; // none --SHOULD BE - // NONE BUT IS - // 1==SOLID???? - for (int i = 0; i < OOXMLConstants.patternFill.length; i++) { - if (OOXMLConstants.patternFill[i].equals(patternType)) - return i; - } - return -1; // none - } - - /** - * sets the pattern type of this Fill
                  - * One of:

                • "none",
                • "solid",
                • "mediumGray",
                • "darkGray",
                • - * "lightGray",
                • "darkHorizontal",
                • "darkVertical",
                • "darkDown", - *
                • "darkUp",
                • "darkGrid",
                • "darkTrellis",
                • "lightHorizontal", - *
                • "lightVertical",
                • "lightDown",
                • "lightUp",
                • "lightGrid",
                • - * "lightTrellis",
                • "gray125",
                • "gray0625", - * - * @param s - */ - public void setFillPattern(String s) { - patternType = s; - } - - /** - * sets the pattern type of this fill via a 2003-style pattern int
                  - * One of:
                • FLSNULL 0x00 No fill pattern
                • FLSSOLID 0x01 Solid
                • - * FLSMEDGRAY 0x02 50% gray
                • FLSDKGRAY 0x03 75% gray
                • FLSLTGRAY 0x04 - * 25% gray
                • FLSDKHOR 0x05 Horizontal stripe
                • FLSDKVER 0x06 Vertical - * stripe
                • FLSDKDOWN 0x07 Reverse diagonal stripe
                • FLSDKUP 0x08 - * Diagonal stripe
                • FLSDKGRID 0x09 Diagonal crosshatch
                • FLSDKTRELLIS - * 0x0A Thick Diagonal crosshatch
                • FLSLTHOR 0x0B Thin horizontal stripe - *
                • FLSLTVER 0x0C Thin vertical stripe
                • FLSLTDOWN 0x0D Thin reverse - * diagonal stripe
                • FLSLTUP 0x0E Thin diagonal stripe
                • FLSLTGRID 0x0F - * Thin horizontal crosshatch
                • FLSLTTRELLIS 0x10 Thin diagonal crosshatch - *
                • FLSGRAY125 0x11 12.5% gray
                • FLSGRAY0625 0x12 6.25% gray
                  - * NOTE: There is a "Special Code" that indicates a missing patternType, - * which has a significant meaning in OOXML - * - * @param t - */ - public void setFillPattern(int t) { - this.patternType = translateIndexedFillPattern(t); - } - - public static String translateIndexedFillPattern(int pattern) { - String newPattern = null; - if (pattern == OOXMLConstants.patternFill.length + 1)// special code - newPattern = null; - else if (pattern >= 0 && pattern < OOXMLConstants.patternFill.length) - newPattern = OOXMLConstants.patternFill[pattern]; - - return newPattern; - } - - /** - * return the foreground color of this fill as an RGB string - * - * @return - */ - public String getFgColorAsRGB(Theme t) { - if (fgColor != null) - return Fill.transformToWebRGBColor(fgColor.getColorAsRGB(t)); - return null; - } - - /** - * return the foreground color of this fill as indexed color int - * - * @return - */ - public int getFgColorAsInt(Theme t) { - if (fgColor != null) - return fgColor.getColorAsInt(t); - if ("solid".equals(patternType)) - return 0; - return -1; - } - - /** - * sets the foreground color of this pattern fill via color int - * - * @param t - */ - public void setFgColor(int t) { - if (fgColor != null) - fgColor.setColor(t); - } - - /** - * sets the foreground fill color to a color string and Excel-2003-mapped - * color int - * - * @param t Excel-2003-mapped color int for the hex color string - * @param colorString hex color string - */ - public void setFgColor(int t, String colorString) { - if (t > 0 || colorString != null) { // 64= default fg color - HashMap attrs = new HashMap(); - if (colorString == null) - attrs.put("indexed", String.valueOf(t)); - else - attrs.put("rgb", colorString); - this.fgColor = new FgColor(attrs); - } - } - - /** - * return the background color of this fill as an RGB string - * - * @return - */ - public String getBgColorAsRGB(Theme t) { - if (bgColor != null) - return Fill.transformToWebRGBColor(bgColor.getColorAsRGB(t)); - return null; - } - - - /** - * return the background color of this fill as an indexed color int - * - * @return - */ - public int getBgColorAsInt(Theme t) { - if (bgColor != null) - return bgColor.getColorAsInt(t); - return -1; // default=white - } - - /** - * sets the background color of this pattern fill via color int - * - * @param t - */ - public void setBgColor(int t) { - if (bgColor != null) - bgColor.setColor(t); - } - - /** - * sets the background fill color to a color string and Excel-2003-mapped - * color int - * - * @param t Excel-2003-mapped color int for the hex color string - * @param colorString hex color string - */ - public void setBgColor(int t, String colorString) { - if (t > 0 || colorString != null) { // 65= default bg color - HashMap attrs = new HashMap(); - if (colorString == null) - attrs.put("indexed", String.valueOf(t)); - else - attrs.put("rgb", Fill.transformToOOXMLRGBColor(colorString)); - this.bgColor = new BgColor(attrs); - } - } - -} - -/** - * gradientFill (Gradient) This element defines a gradient-style cell fill. - * Gradient cell fills can use one or two colors as the end points of color - * interpolation. - *

                  - * parent: fill children: stop (0 or more) attributes: many - */ -class GradientFill implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3633230059631047503L; - private HashMap attrs = null; - private ArrayList stops = null; - - public GradientFill(HashMap attrs, ArrayList stops) { - this.attrs = attrs; - this.stops = stops; - } - - public GradientFill(GradientFill g) { - this.attrs = g.attrs; - this.stops = g.stops; - } - - public static GradientFill parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - ArrayList stops = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gradientFill")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), - xpp.getAttributeValue(i)); - } - } else if (tnm.equals("stop")) { - if (stops == null) - stops = new ArrayList(); - stops.add(Stop.parseOOXML(xpp, bk)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gradientFill")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gradientFill.parseOOXML: " + e.toString()); - } - GradientFill g = new GradientFill(attrs, stops); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (stops != null) { - for (int j = 0; j < stops.size(); j++) - ooxml.append(stops.get(j).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GradientFill(this); - } -} - -/** - * fgColor (Foreground Color) Foreground color of the cell fill pattern. Cell - * fill patterns operate with two colors: a background color and a foreground - * color. These combine together to make a patterned cell fill - *

                  - * parent: patternFill chilren: none - */ -class FgColor implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1274598491373019241L; - private HashMap attrs = null; - - protected FgColor(HashMap attrs) { - this.attrs = attrs; - } - - protected FgColor(FgColor f) { - this.attrs = (HashMap) f.attrs.clone(); - } - - protected FgColor(int c) { - attrs = new HashMap(); - attrs.put("indexed", String.valueOf(c)); - } - - protected HashMap getAttrs() { - return attrs; - } - - protected static FgColor parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fgColor")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fgColor")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fgColor.parseOOXML: " + e.toString()); - } - FgColor f = new FgColor(attrs); - return f; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FgColor(this); - } - - /** - * return the Html string representation of this foreground color - *

                  - * Note that this will return an HTML correct value, such as #000000. - *

                  - * Values set in ooxml need to look like FF000000 - * - * @return - */ - protected String getColorAsRGB(Theme t) { - String val = attrs.get("rgb"); - if (val != null) - return val; - val = attrs.get("indexed"); - if (val != null) { - if (Integer.parseInt(val) == 64) // default fg color - return null; // return "#000000"; //null; - return Color.parseColor(val, Color.COLORTYPEINDEXED, FormatHandle.colorFOREGROUND, t); - } - val = attrs.get("theme"); - if (val != null) - return Color.parseColor(val, Color.COLORTYPETHEME, FormatHandle.colorFOREGROUND, t); - val = attrs.get("auto"); - if (val != null) - return "#000000"; - return null; - } - - /** - * sets the foreground color to the indexed color integer - * - * @param c - */ - protected void setColor(int c) { - attrs.clear(); - attrs.put("indexed", String.valueOf(c)); - } - - /** - * returns the fg color as an indexed color int - */ - protected int getColorAsInt(Theme t) { - String val = attrs.get("auto"); - if (val != null) - return 0; - val = attrs.get("rgb"); - if (val != null) - return Color.parseColorInt(val, Color.COLORTYPERGB, FormatHandle.colorFOREGROUND, t); - val = attrs.get("indexed"); - if (val != null) - return Integer.valueOf(val); - val = attrs.get("theme"); - if (val != null) - return Color.parseColorInt(val, Color.COLORTYPETHEME, FormatHandle.colorFOREGROUND, t); - return -1; - } - - public String toString() { - if (attrs != null) { - String s = attrs.toString(); - return s.substring(1, s.length() - 1); - } - return "none"; - } -} - -/** - * bgColor (Background Color) Background color of the cell fill pattern. Cell - * fill patterns operate with two colors: a background color and a foreground - * color. These combine together to make a patterned cell fill. - *

                  - * parent: patternFill children: none - */ -class BgColor implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 43028503491956217L; - private HashMap attrs = null; - - protected BgColor(HashMap attrs) { - this.attrs = attrs; - } - - protected BgColor(BgColor f) { - this.attrs = (HashMap) f.attrs.clone(); - } - - protected BgColor(int c) { - attrs = new HashMap(); - attrs.put("indexed", String.valueOf(c)); - } - - protected HashMap getAttrs() { - return attrs; - } - - public static BgColor parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bgColor")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), - xpp.getAttributeValue(i)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("bgColor")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("bgColor.parseOOXML: " + e.toString()); - } - BgColor f = new BgColor(attrs); - return f; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BgColor(this); - } - - /** - * return the Html string representation of this background color - * - * @return - */ - protected String getColorAsRGB(Theme t) { - String val = attrs.get("rgb"); - if (val != null) - return val; - val = attrs.get("indexed"); - if (val != null) { - if (Integer.parseInt(val) == 65) // default bg color - return null; // return "#FFFFFF"; //return null; - return Color.parseColor(val, Color.COLORTYPEINDEXED, FormatHandle.colorBACKGROUND, t); - } - val = attrs.get("theme"); - if (val != null) - return Color.parseColor(val, Color.COLORTYPETHEME, FormatHandle.colorBACKGROUND, t); - return null; - } - - - /** - * sets the background color to the indexed color integer - */ - protected void setColor(int c) { - attrs.clear(); - attrs.put("indexed", String.valueOf(c)); - } - - /** - * returns the bg color as an indexed color int - */ - protected int getColorAsInt(Theme t) { - String val = attrs.get("rgb"); - if (val != null) - return Color.parseColorInt(val, Color.COLORTYPERGB, FormatHandle.colorBACKGROUND, t); - val = attrs.get("indexed"); - if (val != null) - return Integer.valueOf(val); - val = attrs.get("theme"); - if (val != null) - return Color.parseColorInt(val, Color.COLORTYPETHEME, FormatHandle.colorBACKGROUND, t); - return 64; // default - } - - public String toString() { - if (attrs != null) { - String s = attrs.toString(); - return s.substring(1, s.length() - 1); - } - return "none"; - } -} - -/** - * stop (Gradient Stop) One of a sequence of two or more gradient stops, - * constituting this gradient fill. - *

                  - * parent: gradientFill children: color REQ - */ -class Stop implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -9215564484103992694L; - private String position = null; - private Color c = null; - - public Stop(String position, Color c) { - this.position = position; - this.c = c; - } - - public Stop(Stop s) { - this.position = s.position; - this.c = s.c; - } - - public static Stop parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - String position = null; - Color c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("stop")) { - position = xpp.getAttributeValue(0); // position= - // REQUIRED - } else if (tnm.equals("color")) { - c = (Color) Color.parseOOXML(xpp, (short) -1, bk) - .cloneElement(); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("stop")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("stop.parseOOXML: " + e.toString()); - } - Stop s = new Stop(position, c); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (c != null) - ooxml.append(c.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Stop(this); - } - - -} - diff --git a/src/main/java/io/starter/formats/OOXML/Fill.kt b/src/main/java/io/starter/formats/OOXML/Fill.kt new file mode 100644 index 0000000..147b269 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Fill.kt @@ -0,0 +1,1170 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Xf +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.HashMap + +/** + * fill OOXML element + * + * + * fill (Fill) This element specifies fill formatting + * + * + * parent: styleSheet/fills element in styles.xml, dxf->fills children: REQ + * CHOICE OF: patternFill, gradientFill + */ +class Fill : OOXMLElement { + private var patternFill: PatternFill? = null + private var gradientFill: GradientFill? = null + private var theme: Theme? = null + + override val ooxml: String + get() = getOOXML(false) + + val fillPattern: String? + get() = if (patternFill != null) this.patternFill!!.fillPattern else null + + /** + * return the fill pattern in 2003 int representation + * + * @return + */ + val fillPatternInt: Int + get() = if (patternFill != null) { + this.patternFill!!.fillPatternInt + } else -1 + + /** + * returns true if the background pattern is solid + * + * @return + */ + val isBackgroundSolid: Boolean + get() = if (patternFill != null) this.patternFill!!.fillPattern!!.equals("solid", ignoreCase = true) else false + + constructor(p: PatternFill, g: GradientFill, t: Theme) { + this.patternFill = p + this.gradientFill = g + this.theme = t + } + + constructor(f: Fill) { + if (f.patternFill != null) + this.patternFill = f.patternFill!!.cloneElement() as PatternFill + if (f.gradientFill != null) + this.gradientFill = f.gradientFill!!.cloneElement() as GradientFill + this.theme = f.theme + } + + /** + * create a new Fill from external vals + * + * @param fs String pattern type + * @param fg int color index + * @param bg int color index + */ + constructor(fs: String, fg: Int, bg: Int, t: Theme) { + this.patternFill = PatternFill(fs, fg, bg) + this.theme = t + } + + /** + * create a new Fill from external vals + * + * @param i XLS indexed pattern + * @param fg int color index + * @param fgColorCustom + * @param bg int color index + */ + constructor(pattern: Int, fg: Int, fgColorCustom: String, bg: Int, + bgColorCustom: String, t: Theme) { + + this.patternFill = PatternFill(PatternFill.translateIndexedFillPattern(pattern), + fg, fgColorCustom, bg, bgColorCustom) + this.theme = t + } + + /** + * dxfs apparently have different pattern fill syntax -- UNDOCUMENTED **** + * + * @param isDxf if this is an Dxf-generated fill, solid fills are handled + * differently than regular fills + * @return + */ + fun getOOXML(isDxf: Boolean): String { + val ooxml = StringBuffer() + ooxml.append("") + if (patternFill != null) + ooxml.append(patternFill!!.getOOXML(isDxf)) + if (gradientFill != null) + ooxml.append(gradientFill!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + override fun cloneElement(): OOXMLElement { + return Fill(this) + } + + /** + * return the foreground color of this fill, if any + * + * @return + */ + fun getFgColorAsRGB(t: Theme): String? { + return if (patternFill != null) this.patternFill!!.getFgColorAsRGB(t) else null + } + + /** + * return the fg color in indexed (int) representation + * + * @return + */ + fun getFgColorAsInt(t: Theme): Int { + return if (patternFill != null) this.patternFill!!.getFgColorAsInt(t) else 0 +// default= black + } + + /** + * return the bg color of this fill, if any + * + * @return + */ + fun getBgColorAsRGB(t: Theme): String? { + return if (patternFill != null) this.patternFill!!.getBgColorAsRGB(t) else null + } + + + /** + * return the bg color in indexed (int) representation + * + * @return + */ + fun getBgColorAsInt(t: Theme): Int { + return if (patternFill != null) this.patternFill!!.getBgColorAsInt(t) else -1 + } + + /** + * sets the foreground fill color via color int + * + * @param t + */ + fun setFgColor(t: Int) { + if (patternFill != null) { + patternFill!!.setFgColor(t) + return + } + } + + /** + * sets the foreground fill color to a color string and Excel-2003-mapped + * color int + * + * @param t Excel-2003-mapped color int for the hex color string + * @param colorString hex color string + */ + fun setFgColor(t: Int, colorString: String) { + if (patternFill != null) + patternFill!!.setFgColor(t, colorString) + else + this.patternFill = PatternFill("none", t, colorString, -1, null) + } + + /** + * sets the bg fill color via color int + * + * @param t + */ + fun setBgColor(t: Int) { + if (patternFill != null) { + patternFill!!.setBgColor(t) + return + } + } + + /** + * sets the foreground fill color to a color string and Excel-2003-mapped + * color int + * + * @param t Excel-2003-mapped color int for the hex color string + * @param colorString hex color string + */ + fun setBgColor(t: Int, colorString: String) { + if (patternFill != null) + patternFill!!.setBgColor(t, colorString) + else + this.patternFill = PatternFill("none", -1, null, t, colorString) + } + + /** + * sets the fill pattern + */ + fun setFillPattern(t: Int) { + if (patternFill != null) + this.patternFill!!.setFillPattern(t) + } + + companion object { + + private val serialVersionUID = -4510508531435037641L + + fun parseOOXML(xpp: XmlPullParser, isDxf: Boolean, bk: WorkBookHandle): OOXMLElement { + var p: PatternFill? = null + var g: GradientFill? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "patternFill") { + p = PatternFill.parseOOXML(xpp, isDxf, bk) + + } else if (tnm == "gradientFill") { + g = GradientFill.parseOOXML(xpp, bk) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "fill") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("fill.parseOOXML: $e") + } + + return Fill(p, g, bk.workBook!!.theme) + } + + /** + * OOXML values are stored with an intiall FF in their value, + * + * + * this method assures that values returned are 6 digits + # for web usage + * + * @param rgbcolor + */ + fun transformToWebRGBColor(rgbcolor: String): String { + if (rgbcolor.indexOf("#") == 0) return rgbcolor + return if (rgbcolor.indexOf("FF") == 0 && rgbcolor.length == 8) { + "#" + rgbcolor.substring(2) + } else "#$rgbcolor" + } + + /** + * OOXML values are stored with an intiall FF in their value, + * + * + * this method assures that values returned are 8 digits with + * ff appended to rgb value for ooxml usage + * + * @param rgbcolor + */ + fun transformToOOXMLRGBColor(rgbcolor: String): String { + if (rgbcolor.indexOf("#") == 0) { + return "FF" + rgbcolor.substring(1) + } + return if (rgbcolor.indexOf("FF") == 0 && rgbcolor.length == 8) { + rgbcolor + } else "FF$rgbcolor" + } + + /** + * returns the OOXML specifying the fill based on this FormatHandle object + */ + fun getOOXML(xf: Xf): String { + if (xf.getFill() != null) + return xf.getFill()!!.ooxml + + // otherwise, create fill from 2003-style xf + val ooxml = StringBuffer() + ooxml.append("") + try { + ooxml.append("") + } catch (e: IndexOutOfBoundsException) { + ooxml.append("") // apparently there are less patterns + // in xlsx? some other way of + // storage + } + + val fg = xf.foregroundColor.toInt() + if (fg > -1 && fg != 64) + ooxml.append("") + val bg = xf.backgroundColor.toInt() + if (bg > -1 && bg != 64) + ooxml.append("") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + return ooxml.toString() + } + + /** + * returns the OOXML specifying the fill based on fill pattern fs, + * foreground color fg, background color bg + */ + fun getOOXML(fs: Int, fg: Int, bg: Int): String { + val ooxml = StringBuffer() + ooxml.append("") + try { + ooxml.append("") + } catch (e: ArrayIndexOutOfBoundsException) { + ooxml.append("") + } + + if (fg > -1 && fg != 64) { + ooxml.append("") + ooxml.append("\r\n") + } + if (bg > -1 && bg != 64) { + ooxml.append("") + ooxml.append("\r\n") + } + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + return ooxml.toString() + } + } +} + +/** + * patternFill (Pattern) This element is used to specify cell fill information + * for pattern and solid color cell fills. For solid cell fills (no pattern), + * fgColor is used. For cell fills with patterns specified, then the cell fill + * color is specified by the bgColor element. + * + * + * parent: fill children: SEQ: fgColor, bgColor + */ +internal class PatternFill : OOXMLElement { + /** + * return the pattern type of this Fill in String representation + * + * @return + */ + /** + * sets the pattern type of this Fill

                  + * One of: * "none", * "solid", * "mediumGray", * "darkGray", * + * "lightGray", * "darkHorizontal", * "darkVertical", * "darkDown", + * * "darkUp", * "darkGrid", * "darkTrellis", * "lightHorizontal", + * * "lightVertical", * "lightDown", * "lightUp", * "lightGrid", * + * "lightTrellis", * "gray125", * "gray0625", + * + * @param s + */ + var fillPattern: String? = null + private var fgColor: FgColor? = null + private var bgColor: BgColor? = null + private var theme: Theme? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (fgColor != null) + ooxml.append(fgColor!!.ooxml) + if (bgColor != null) + ooxml.append(bgColor!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * return the pattern type of this fill pattern in 2003 int representation

                  + * BIG NOTE: Apparently doc is wrong in that if patternType is missing it + * does NOT == none; instead, it is a SOLID FILL with BG==fill color

                  + * THIS METHOD returns -1 in those cases + * + * @return int pattern fill integer + * @see PatternFill.setFillPattern + */ + // a missing entry *should*==none or 0, but + // apparently is a distinct value in and of + // itself + // none --SHOULD BE + // NONE BUT IS + // 1==SOLID???? + // none + val fillPatternInt: Int + get() { + if (fillPattern == null) + return OOXMLConstants.patternFill.size + 1 + for (i in OOXMLConstants.patternFill.indices) { + if (OOXMLConstants.patternFill[i] == fillPattern) + return i + } + return -1 + } + + constructor(patternType: String, fg: FgColor, bg: BgColor, t: Theme) { + this.fillPattern = patternType + this.fgColor = fg + this.bgColor = bg + this.theme = t + } + + constructor(p: PatternFill) { + this.fillPattern = p.fillPattern + if (p.fgColor != null) + this.fgColor = p.fgColor!!.cloneElement() as FgColor + if (p.bgColor != null) + this.bgColor = p.bgColor!!.cloneElement() as BgColor + this.theme = p.theme + } + + /** + * create a new pattern fill from external vals + * + * @param patternType String OOXML pattern type + * @param fg int color index + * @param bg int color index + */ + constructor(patternType: String, fg: Int, bg: Int) { + this.fillPattern = patternType + if (fg > -1 && fg != 64) { + val attrs = HashMap() + attrs["rgb"] = "FF" + FormatHandle.colorToHexString(FormatHandle.COLORTABLE[fg]).substring(1) + this.fgColor = FgColor(attrs) + } + if (bg > -1 && bg != 65) { + val attrs = HashMap() + attrs["rgb"] = "FF" + FormatHandle.colorToHexString( + FormatHandle.COLORTABLE[bg]).substring(1) + this.bgColor = BgColor(attrs) + } + } + + constructor(patternType: String, fg: Int, fgCustom: String?, bg: Int, + bgCustom: String?) { + this.fillPattern = patternType + if (fg > 0 || fgCustom != null) { // 64= default fg color + val attrs = HashMap() + if (fgCustom == null) + attrs["indexed"] = fg.toString() + else + attrs["rgb"] = Fill.transformToOOXMLRGBColor(fgCustom) + this.fgColor = FgColor(attrs) + } + if (bg > -1 || bgCustom != null) { // 65= default bg color + val attrs = HashMap() + if (bgCustom == null) + attrs["indexed"] = bg.toString() + else + attrs["rgb"] = Fill.transformToOOXMLRGBColor(bgCustom) + this.bgColor = BgColor(attrs) + } + } + + /** + * apparently Fill OOXML from Dxf has differnt syntax - UNDOCUMENTED + * + * @param isDxf if this is an Dxf-generated fill, solid fills are handled + * differently than regular fills + * @return + */ + fun getOOXML(isDxf: Boolean): String { + val ooxml = StringBuffer() + ooxml.append("") + if (fgColor != null) + ooxml.append(fgColor!!.ooxml) + if (bgColor != null) + ooxml.append(bgColor!!.ooxml) + } else { + if (fillPattern == "solid") { // dxf needs "none" or "soild" to + // have bg color set, not fg + // color as is normal + ooxml.append(">") + if (fgColor != null) { // shoudln't! + val tempbg = BgColor(fgColor!!.attrs) + ooxml.append(tempbg.ooxml) + } + } else { + ooxml.append(" patternType=\"$fillPattern\">") + if (fgColor != null) + ooxml.append(fgColor!!.ooxml) + if (bgColor != null) + ooxml.append(bgColor!!.ooxml) + } + } + ooxml.append("") + return ooxml.toString() + } + + override fun cloneElement(): OOXMLElement { + return PatternFill(this) + } + + override fun toString(): String { + return ((if (fillPattern != null) fillPattern else "") + " fg:" + + (if (fgColor != null) fgColor!!.toString() else "") + " bg:" + + if (bgColor != null) bgColor!!.toString() else "") + } + + /** + * sets the pattern type of this fill via a 2003-style pattern int

                  + * One of: * FLSNULL 0x00 No fill pattern * FLSSOLID 0x01 Solid * + * FLSMEDGRAY 0x02 50% gray * FLSDKGRAY 0x03 75% gray * FLSLTGRAY 0x04 + * 25% gray * FLSDKHOR 0x05 Horizontal stripe * FLSDKVER 0x06 Vertical + * stripe * FLSDKDOWN 0x07 Reverse diagonal stripe * FLSDKUP 0x08 + * Diagonal stripe * FLSDKGRID 0x09 Diagonal crosshatch * FLSDKTRELLIS + * 0x0A Thick Diagonal crosshatch * FLSLTHOR 0x0B Thin horizontal stripe + * * FLSLTVER 0x0C Thin vertical stripe * FLSLTDOWN 0x0D Thin reverse + * diagonal stripe * FLSLTUP 0x0E Thin diagonal stripe * FLSLTGRID 0x0F + * Thin horizontal crosshatch * FLSLTTRELLIS 0x10 Thin diagonal crosshatch + * * FLSGRAY125 0x11 12.5% gray * FLSGRAY0625 0x12 6.25% gray

                  + * NOTE: There is a "Special Code" that indicates a missing patternType, + * which has a significant meaning in OOXML + * + * @param t + */ + fun setFillPattern(t: Int) { + this.fillPattern = translateIndexedFillPattern(t) + } + + /** + * return the foreground color of this fill as an RGB string + * + * @return + */ + fun getFgColorAsRGB(t: Theme): String? { + return if (fgColor != null) Fill.transformToWebRGBColor(fgColor!!.getColorAsRGB(t)!!) else null + } + + /** + * return the foreground color of this fill as indexed color int + * + * @return + */ + fun getFgColorAsInt(t: Theme): Int { + if (fgColor != null) + return fgColor!!.getColorAsInt(t) + return if ("solid" == fillPattern) 0 else -1 + } + + /** + * sets the foreground color of this pattern fill via color int + * + * @param t + */ + fun setFgColor(t: Int) { + if (fgColor != null) + fgColor!!.setColor(t) + } + + /** + * sets the foreground fill color to a color string and Excel-2003-mapped + * color int + * + * @param t Excel-2003-mapped color int for the hex color string + * @param colorString hex color string + */ + fun setFgColor(t: Int, colorString: String?) { + if (t > 0 || colorString != null) { // 64= default fg color + val attrs = HashMap() + if (colorString == null) + attrs["indexed"] = t.toString() + else + attrs["rgb"] = colorString + this.fgColor = FgColor(attrs) + } + } + + /** + * return the background color of this fill as an RGB string + * + * @return + */ + fun getBgColorAsRGB(t: Theme): String? { + return if (bgColor != null) Fill.transformToWebRGBColor(bgColor!!.getColorAsRGB(t)!!) else null + } + + + /** + * return the background color of this fill as an indexed color int + * + * @return + */ + fun getBgColorAsInt(t: Theme): Int { + return if (bgColor != null) bgColor!!.getColorAsInt(t) else -1 +// default=white + } + + /** + * sets the background color of this pattern fill via color int + * + * @param t + */ + fun setBgColor(t: Int) { + if (bgColor != null) + bgColor!!.setColor(t) + } + + /** + * sets the background fill color to a color string and Excel-2003-mapped + * color int + * + * @param t Excel-2003-mapped color int for the hex color string + * @param colorString hex color string + */ + fun setBgColor(t: Int, colorString: String?) { + if (t > 0 || colorString != null) { // 65= default bg color + val attrs = HashMap() + if (colorString == null) + attrs["indexed"] = t.toString() + else + attrs["rgb"] = Fill.transformToOOXMLRGBColor(colorString) + this.bgColor = BgColor(attrs) + } + } + + companion object { + + private val serialVersionUID = -4399355217499895956L + + fun parseOOXML(xpp: XmlPullParser, isDxf: Boolean, bk: WorkBookHandle): PatternFill { + var patternType: String? = null // "none"; // default when missing -- so sez + // the doc but doesn't appear what Excel + // does + /** + * APPARENLTY patternFills in dxfs are DIFFERENT AND NOT FOLLOWING THE + * DOCUMENTATION on regular patternFills + * + * APPARENTLY patternType="none" and missing patternType ARE NOT THE + * SAME: if Missing, APPARENTLY means to fill with BG color ("solid" + * means to fill with FG color) + */ + var fg: FgColor? = null + var bg: BgColor? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "patternFill") { // get attributes + if (xpp.attributeCount > 0) + patternType = xpp.getAttributeValue(0) + } else if (tnm == "fgColor") { + fg = FgColor.parseOOXML(xpp) + } else if (tnm == "bgColor") { + bg = BgColor.parseOOXML(xpp) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "patternFill") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("patternFill.parseOOXML: $e") + } + + if (isDxf) { + if (patternType == null) + // null apparently does NOT mean none for + // Dxf's + patternType = "solid" // see Dxf and Cf handling: null means + // solid fill with bg color as cell + // background + if (patternType == "solid") { + if (bg != null) {// shouldn't! + fg = FgColor(bg.attrs) // so 2003-v can properly + // set solid cell + // pattern color + } + bg = BgColor(64) + } + } + return PatternFill(patternType, fg, bg, bk.workBook!!.theme) + } + + fun translateIndexedFillPattern(pattern: Int): String? { + var newPattern: String? = null + if (pattern == OOXMLConstants.patternFill.size + 1) + // special code + newPattern = null + else if (pattern >= 0 && pattern < OOXMLConstants.patternFill.size) + newPattern = OOXMLConstants.patternFill[pattern] + + return newPattern + } + } + +} + +/** + * gradientFill (Gradient) This element defines a gradient-style cell fill. + * Gradient cell fills can use one or two colors as the end points of color + * interpolation. + * + * + * parent: fill children: stop (0 or more) attributes: many + */ +internal class GradientFill : OOXMLElement { + private var attrs: HashMap? = null + private var stops: ArrayList? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (stops != null) { + for (j in stops!!.indices) + ooxml.append(stops!![j].ooxml) + } + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap, stops: ArrayList) { + this.attrs = attrs + this.stops = stops + } + + constructor(g: GradientFill) { + this.attrs = g.attrs + this.stops = g.stops + } + + override fun cloneElement(): OOXMLElement { + return GradientFill(this) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 3633230059631047503L + + fun parseOOXML(xpp: XmlPullParser, bk: WorkBookHandle): GradientFill { + val attrs = HashMap() + var stops: ArrayList? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "gradientFill") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "stop") { + if (stops == null) + stops = ArrayList() + stops.add(Stop.parseOOXML(xpp, bk)) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "gradientFill") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("gradientFill.parseOOXML: $e") + } + + return GradientFill(attrs, stops) + } + } +} + +/** + * fgColor (Foreground Color) Foreground color of the cell fill pattern. Cell + * fill patterns operate with two colors: a background color and a foreground + * color. These combine together to make a patterned cell fill + * + * + * parent: patternFill chilren: none + */ +internal class FgColor : OOXMLElement { + var attrs: HashMap? = null + private set + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + protected constructor(f: FgColor) { + this.attrs = f.attrs!!.clone() as HashMap + } + + protected constructor(c: Int) { + attrs = HashMap() + attrs!!["indexed"] = c.toString() + } + + override fun cloneElement(): OOXMLElement { + return FgColor(this) + } + + /** + * return the Html string representation of this foreground color + * + * + * Note that this will return an HTML correct value, such as #000000. + * + * + * Values set in ooxml need to look like FF000000 + * + * @return + */ + fun getColorAsRGB(t: Theme): String? { + var `val`: String? = attrs!!["rgb"] + if (`val` != null) + return `val` + `val` = attrs!!["indexed"] + if (`val` != null) { + return if (Integer.parseInt(`val`) == 64) null else Color.parseColor(`val`, Color.COLORTYPEINDEXED, FormatHandle.colorFOREGROUND, t) // return "#000000"; //null; + } + `val` = attrs!!["theme"] + if (`val` != null) + return Color.parseColor(`val`, Color.COLORTYPETHEME, FormatHandle.colorFOREGROUND, t) + `val` = attrs!!["auto"] + return if (`val` != null) "#000000" else null + } + + /** + * sets the foreground color to the indexed color integer + * + * @param c + */ + fun setColor(c: Int) { + attrs!!.clear() + attrs!!["indexed"] = c.toString() + } + + /** + * returns the fg color as an indexed color int + */ + fun getColorAsInt(t: Theme): Int { + var `val`: String? = attrs!!["auto"] + if (`val` != null) + return 0 + `val` = attrs!!["rgb"] + if (`val` != null) + return Color.parseColorInt(`val`, Color.COLORTYPERGB, FormatHandle.colorFOREGROUND, t) + `val` = attrs!!["indexed"] + if (`val` != null) + return Integer.valueOf(`val`) + `val` = attrs!!["theme"] + return if (`val` != null) Color.parseColorInt(`val`, Color.COLORTYPETHEME, FormatHandle.colorFOREGROUND, t) else -1 + } + + override fun toString(): String { + if (attrs != null) { + val s = attrs!!.toString() + return s.substring(1, s.length - 1) + } + return "none" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -1274598491373019241L + + fun parseOOXML(xpp: XmlPullParser): FgColor { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "fgColor") { // get attributes + for (i in 0 until xpp.attributeCount) + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "fgColor") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("fgColor.parseOOXML: $e") + } + + return FgColor(attrs) + } + } +} + +/** + * bgColor (Background Color) Background color of the cell fill pattern. Cell + * fill patterns operate with two colors: a background color and a foreground + * color. These combine together to make a patterned cell fill. + * + * + * parent: patternFill children: none + */ +internal class BgColor : OOXMLElement { + var attrs: HashMap? = null + private set + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + protected constructor(f: BgColor) { + this.attrs = f.attrs!!.clone() as HashMap + } + + constructor(c: Int) { + attrs = HashMap() + attrs!!["indexed"] = c.toString() + } + + override fun cloneElement(): OOXMLElement { + return BgColor(this) + } + + /** + * return the Html string representation of this background color + * + * @return + */ + fun getColorAsRGB(t: Theme): String? { + var `val`: String? = attrs!!["rgb"] + if (`val` != null) + return `val` + `val` = attrs!!["indexed"] + if (`val` != null) { + return if (Integer.parseInt(`val`) == 65) null else Color.parseColor(`val`, Color.COLORTYPEINDEXED, FormatHandle.colorBACKGROUND, t) // return "#FFFFFF"; //return null; + } + `val` = attrs!!["theme"] + return if (`val` != null) Color.parseColor(`val`, Color.COLORTYPETHEME, FormatHandle.colorBACKGROUND, t) else null + } + + + /** + * sets the background color to the indexed color integer + */ + fun setColor(c: Int) { + attrs!!.clear() + attrs!!["indexed"] = c.toString() + } + + /** + * returns the bg color as an indexed color int + */ + fun getColorAsInt(t: Theme): Int { + var `val`: String? = attrs!!["rgb"] + if (`val` != null) + return Color.parseColorInt(`val`, Color.COLORTYPERGB, FormatHandle.colorBACKGROUND, t) + `val` = attrs!!["indexed"] + if (`val` != null) + return Integer.valueOf(`val`) + `val` = attrs!!["theme"] + return if (`val` != null) Color.parseColorInt(`val`, Color.COLORTYPETHEME, FormatHandle.colorBACKGROUND, t) else 64 +// default + } + + override fun toString(): String { + if (attrs != null) { + val s = attrs!!.toString() + return s.substring(1, s.length - 1) + } + return "none" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 43028503491956217L + + fun parseOOXML(xpp: XmlPullParser): BgColor { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "bgColor") { // get attributes + for (i in 0 until xpp.attributeCount) + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "bgColor") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("bgColor.parseOOXML: $e") + } + + return BgColor(attrs) + } + } +} + +/** + * stop (Gradient Stop) One of a sequence of two or more gradient stops, + * constituting this gradient fill. + * + * + * parent: gradientFill children: color REQ + */ +internal class Stop : OOXMLElement { + private var position: String? = null + private var c: Color? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (c != null) + ooxml.append(c!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(position: String, c: Color) { + this.position = position + this.c = c + } + + constructor(s: Stop) { + this.position = s.position + this.c = s.c + } + + override fun cloneElement(): OOXMLElement { + return Stop(this) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -9215564484103992694L + + fun parseOOXML(xpp: XmlPullParser, bk: WorkBookHandle): Stop { + var position: String? = null + var c: Color? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "stop") { + position = xpp.getAttributeValue(0) // position= + // REQUIRED + } else if (tnm == "color") { + c = Color.parseOOXML(xpp, (-1).toShort(), bk) + .cloneElement() as Color + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "stop") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("stop.parseOOXML: $e") + } + + return Stop(position, c) + } + } + + +} + diff --git a/src/main/java/io/starter/formats/OOXML/FillGroup.java b/src/main/java/io/starter/formats/OOXML/FillGroup.java deleted file mode 100644 index 6c8a5e2..0000000 --- a/src/main/java/io/starter/formats/OOXML/FillGroup.java +++ /dev/null @@ -1,1363 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * FillGroup -- holds ONE OF the Fill-type OOXML/DrawingML Elements: blipFill - * gradFill grpFill pattFill solidFill noFill - *

                  - * parents: bgPr, spPr, fmtScheme, endParaRPr, rPr ... - */ -// TODO: Handle blip element children -// TODO: Handle gradFill element shade properties -public class FillGroup implements OOXMLElement { - private static final long serialVersionUID = 8320871291479597945L; - private BlipFill bf; - private GradFill gpf; - private GrpFill grpf; - private PattFill pf; - private SolidFill sf; - - public FillGroup(BlipFill bf, GradFill gpf, GrpFill grpf, PattFill pf, - SolidFill sf) { - this.bf = bf; - this.gpf = gpf; - this.grpf = grpf; - this.pf = pf; - this.sf = sf; - } - - public FillGroup(FillGroup f) { - this.bf = f.bf; - this.gpf = f.gpf; - this.grpf = f.grpf; - this.pf = f.pf; - this.sf = f.sf; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - BlipFill bf = null; - GradFill gpf = null; - GrpFill grpf = null; - PattFill pf = null; - SolidFill sf = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("solidFill")) { - lastTag.push(tnm); - sf = SolidFill.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("noFill")) { - // do nothing - } else if (tnm.equals("gradFill")) { - lastTag.push(tnm); - gpf = GradFill.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("grpFill")) { - lastTag.push(tnm); - grpf = GrpFill.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("pattFill")) { - lastTag.push(tnm); - pf = PattFill.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("blipFill")) { - lastTag.push(tnm); - bf = BlipFill.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("FillGroup.parseOOXML: " + e.toString()); - } - FillGroup f = new FillGroup(bf, gpf, grpf, pf, sf); - return f; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - // CHOICE OF fill - if (sf != null) - ooxml.append(sf.getOOXML()); - else if (gpf != null) - ooxml.append(gpf.getOOXML()); - else if (grpf != null) - ooxml.append(grpf.getOOXML()); - else if (bf != null) - ooxml.append(bf.getOOXML()); - else if (pf != null) - ooxml.append(pf.getOOXML()); - else - // no fill - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FillGroup(this); - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (bf != null) - return bf.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (bf != null) - return bf.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - if (bf == null) - bf = new BlipFill(); - bf.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (bf == null) - bf = new BlipFill(); - bf.setLink(link); - } - - /** - * set the color for this fill - * NOTE: at this time only solid fills may be specified - * - * @param clr - */ - public void setColor(String clr) { - if (bf != null) - return; - gpf = null; - grpf = null; - pf = null; - sf = new SolidFill(); - sf.setColor(clr); - } - - public int getColor() { - if (sf != null) - return sf.getColor(); - return -1; - } -} - -/** - * gradFill (Gradient Fill) - *

                  - * This element defines a gradient fill. - *

                  - * parents: many children: gsLst, SHADEPROPERTIES, tileRect - */ -// TODO: finish SHADEPROPERTIES -class GradFill implements OOXMLElement { - - private static final long serialVersionUID = 8965776942160065286L; - private GsLst g; - // private ShadeProps sp= null; - private TileRect tr; - private HashMap attrs; - - public GradFill(GsLst g, /* ShadeProps sp, */TileRect tr, HashMap attrs) { - this.g = g; - // this.sp= sp; - this.tr = tr; - this.attrs = attrs; - } - - public GradFill(GradFill gf) { - this.g = gf.g; - // this.sp= gf.sp; - this.tr = gf.tr; - this.attrs = gf.attrs; - } - - public static GradFill parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - GsLst g = null; - // ShadeProps sp= null; - TileRect tr = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gradFill")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } else if (tnm.equals("tileRect")) { - lastTag.push(tnm); - tr = TileRect.parseOOXML(xpp, lastTag); - } else if (tnm.equals("gsLst")) { - lastTag.push(tnm); - g = GsLst.parseOOXML(xpp, lastTag, bk); - /* - * } else if (tnm.equals("lin") || tnm.equals("path")) { - * lastTag.push(tnm); sp= (shadeProps) - * shadeProps.parseOOXML(xpp, lastTag).clone(); - */ - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gradFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gradFill.parseOOXML: " + e.toString()); - } - GradFill gf = new GradFill(g, /* sp, */tr, attrs); - return gf; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (g != null) - ooxml.append(g.getOOXML()); - // if (sp!=null) ooxml.append(sp.getOOXML()); - if (tr != null) - ooxml.append(tr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GradFill(this); - } - -} - -/** - * blipFill (Picture Fill) - *

                  - * This element specifies the type of picture fill that the picture object will - * have. Because a picture has a picture fill already by default, it is possible - * to have two fills specified for a picture object. An example of this is shown - * below. - *

                  - * parents: many children: blip, srcRect, FillModeProperties - */ -class BlipFill implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2030570462677450734L; - private HashMap attrs = null; - private Blip blip = null; - private SrcRect srcRect; - private FillMode fillMode; - private String ns = "xdr"; // default namespace - - public BlipFill() { // no-parameter constructor, set common defaults - srcRect = new SrcRect(); - fillMode = new FillMode(null, false); - } - - public BlipFill(String ns, HashMap attrs, Blip b, SrcRect s, FillMode f) { - this.ns = ns; - this.attrs = attrs; - this.blip = b; - this.srcRect = s; - this.fillMode = f; - } - - public BlipFill(BlipFill bf) { - this.ns = bf.ns; - this.attrs = bf.attrs; - this.blip = bf.blip; - this.srcRect = bf.srcRect; - this.fillMode = bf.fillMode; - } - - public static BlipFill parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - Blip b = null; - SrcRect s = null; - FillMode f = null; - String ns = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("blipFill")) { // get attributes - ns = xpp.getPrefix(); - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } else if (tnm.equals("blip")) { - lastTag.push(tnm); - b = Blip.parseOOXML(xpp, lastTag); - } else if (tnm.equals("srcRect")) { - lastTag.push(tnm); - s = SrcRect.parseOOXML(xpp, lastTag); - } else if (tnm.equals("stretch") || tnm.equals("tile")) { - lastTag.push(tnm); - f = FillMode.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("blipFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("blipFill.parseOOXML: " + e.toString()); - } - BlipFill bf = new BlipFill(ns, attrs, b, s, f); - return bf; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (ns == null) - Logger.logErr("Error: BlipFill Namespace is null"); - ooxml.append("<" + ns + ":blipFill"); - // attributes - if (attrs != null) { - Iterator i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (blip != null) - ooxml.append(blip.getOOXML()); - if (srcRect != null) - ooxml.append(srcRect.getOOXML()); - if (fillMode != null) - ooxml.append(fillMode.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BlipFill(this); - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (blip != null) - return blip.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (blip != null) - return blip.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - if (blip == null) - blip = new Blip(); - blip.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (blip == null) - blip = new Blip(); - blip.setLink(link); - } -} - -/** - * solidFill (Solid Fill) This element specifies a solid color fill. The shape - * is filled entirely with the specified color - *

                  - * parents: many children: COLORCHOICE - */ -class SolidFill implements OOXMLElement { - - private static final long serialVersionUID = 3341509200573989744L; - private ColorChoice color; - - public SolidFill() { // no-param constructor, set up common defaults - this.color = new ColorChoice(null, new SrgbClr("000000", null), null, null, null); - } - - public SolidFill(ColorChoice c) { - this.color = c; - } - - public SolidFill(SolidFill s) { - this.color = s.color; - } - - /** - * creates a solid fill of a specific color - * - * @param clr hex color string without # - */ - public SolidFill(String clr) { - if (clr == null) clr = "FFFFFF"; - this.color = new ColorChoice(null, new SrgbClr(clr, null), null, null, null); - } - - public static SolidFill parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("hslClr") || tnm.equals("prstClr") - || tnm.equals("schemeClr") - || tnm.equals("scrgbClr") || tnm.equals("srgbClr") - || tnm.equals("sysClr")) { // get attributes - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk) - .cloneElement(); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("solidFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("solidFill.parseOOXML: " + e.toString()); - } - SolidFill s = new SolidFill(c); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (this.color != null) - ooxml.append(this.color.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SolidFill(this); - } - - /** - * set the color for this solid fill in hex (html) string format - * - * @param clr - */ - public void setColor(String clr) { - this.color = new ColorChoice(null, new SrgbClr(clr, null), null, null, null); - } - - public int getColor() { - return color.getColor(); - } -} - -/** - * pattFill (Pattern Fill) This element specifies a pattern fill. A repeated - * pattern is used to fill the object. - *

                  - * parent: many children: bgClr, fgClr - */ -class PattFill implements OOXMLElement { - - private static final long serialVersionUID = -1052627959661249692L; - private String prst; - private BgClr bg; - private FgClr fg; - - public PattFill(String prst, BgClr bg, FgClr fg) { - this.prst = prst; - this.bg = bg; - this.fg = fg; - } - - public PattFill(PattFill p) { - this.prst = p.prst; - this.bg = p.bg; - this.fg = p.fg; - } - - public static PattFill parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String prst = null; - BgClr bg = null; - FgClr fg = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pattFill")) { // get attributes - prst = xpp.getAttributeValue(0); - } else if (tnm.equals("bgClr")) { - lastTag.push(tnm); - bg = BgClr.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("fgClr")) { - lastTag.push(tnm); - fg = FgClr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("pattFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pattFill.parseOOXML: " + e.toString()); - } - PattFill p = new PattFill(prst, bg, fg); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (fg != null) - ooxml.append(fg.getOOXML()); - if (bg != null) - ooxml.append(bg.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PattFill(this); - } -} - -/** - * grpFill (Group Fill) This element specifies a group fill. When specified, - * this setting indicates that the parent element is part of a group and should - * inherit the fill properties of the group. - *

                  - * parent: many children: CT_GROUPFILL: ??? contains nothing? - */ -class GrpFill implements OOXMLElement { - private static final long serialVersionUID = 2388879629485740996L; - - public static GrpFill parseOOXML(XmlPullParser xpp, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("grpFill")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("grpFill.parseOOXML: " + e.toString()); - } - GrpFill g = new GrpFill(); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - // TODO: no attributes or children? - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GrpFill(); - } -} - -/** - * bgClr background color - *

                  - * parent: pattFill - */ -class BgClr implements OOXMLElement { - - private static final long serialVersionUID = -879409152334931909L; - private ColorChoice colorChoice; - - - public BgClr(ColorChoice c) { - this.colorChoice = c; - } - - public BgClr(BgClr s) { - this.colorChoice = s.colorChoice; - } - - public static BgClr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("hslClr") || tnm.equals("prstClr") - || tnm.equals("schemeClr") - || tnm.equals("scrgbClr") || tnm.equals("srgbClr") - || tnm.equals("sysClr")) { // get attributes - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("bgClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("bgClr.parseOOXML: " + e.toString()); - } - BgClr s = new BgClr(c); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (this.colorChoice != null) - ooxml.append(this.colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new BgClr(this); - } -} - -/** - * fgClr Foreground Color - *

                  - * parent: pattFill - */ -class FgClr implements OOXMLElement { - private static final long serialVersionUID = 6836994790529289731L; - private ColorChoice colorChoice; - - public FgClr(ColorChoice c) { - this.colorChoice = c; - } - - public FgClr(FgClr s) { - this.colorChoice = s.colorChoice; - } - - public static FgClr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("hslClr") || tnm.equals("prstClr") - || tnm.equals("schemeClr") - || tnm.equals("scrgbClr") || tnm.equals("srgbClr") - || tnm.equals("sysClr")) { // get attributes - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fgClr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fgClr.parseOOXML: " + e.toString()); - } - FgClr s = new FgClr(c); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (this.colorChoice != null) - ooxml.append(this.colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FgClr(this); - } -} - -/** - * blip (Blip) This element specifies the existence of an image (binary large - * image or picture) and contains a reference to the image data. - *

                  - * parent: blipFill, buFill children: MANY - */ -// TODO: HANDLE THE MANY CHILDREN -class Blip implements OOXMLElement { - - private static final long serialVersionUID = 5188967633123620513L; - private HashMap attrs = new HashMap(); - - public Blip() { - } - - public Blip(HashMap attrs) { - this.attrs = attrs; - } - - public Blip(Blip b) { - this.attrs = b.attrs; - } - - public static Blip parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("blip")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - if (xpp.getAttributePrefix(i) != null) - attrs.put(xpp.getAttributePrefix(i) + ":" - + xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - else - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - // } else if (tnm.equals("CHILDELEMENT")) { - // lastTag.push(tnm); - // layout = (layout) layout.parseOOXML(xpp, - // lastTag).clone(); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("blip")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("blip.parseOOXML: " + e.toString()); - } - Blip b = new Blip(attrs); - return b; - } - - // TODO: cstate= "print" - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - // TODO: HANDLE CHILDREN - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Blip(this); - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (attrs != null && attrs.get("r:embed") != null) { - return attrs.get("r:embed"); - } - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (attrs != null && attrs.get("link") != null) { - return attrs.get("link"); - } - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - attrs.put("r:embed", embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - attrs.put("link", link); - } -} - -/** - * tileRect (Tile Rectangle) This element specifies a rectangular region of the - * shape to which the gradient is applied. This region is then tiled across the - * remaining area of the shape to complete the fill. The tile rectangle is - * defined by percentage offsets from the sides of the shape's bounding box. - * parent: gradFill children: none - */ -class TileRect implements OOXMLElement { - - private static final long serialVersionUID = 5380575948049571420L; - private HashMap attrs; - - public TileRect(HashMap attrs) { - this.attrs = attrs; - } - - public TileRect(TileRect t) { - this.attrs = t.attrs; - } - - public static TileRect parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tileRect")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("tileRect")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("tileRect.parseOOXML: " + e.toString()); - } - TileRect t = new TileRect(attrs); - return t; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TileRect(this); - } -} - -/** - * srcRect (Source Rectangle) This element specifies the portion of the blip - * used for the fill - *

                  - * parent: blipFill children: NONE - */ -class SrcRect implements OOXMLElement { - - private static final long serialVersionUID = -6407800173040857433L; - private HashMap attrs = null; - - public SrcRect() { - } - - public SrcRect(HashMap attrs) { - this.attrs = attrs; - } - - public SrcRect(SrcRect s) { - this.attrs = s.attrs; - } - - public static SrcRect parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("srcRect")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("srcRect")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("srcRect.parseOOXML: " + e.toString()); - } - SrcRect s = new SrcRect(attrs); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SrcRect(this); - } -} - -/** - * fillRect (Fill Rectangle) This element specifies a fill rectangle. When - * stretching of an image is specified, a source rectangle, srcRect, is scaled - * to fit the specified fill rectangle. parent: stretch children: NONE - */ -class FillRect implements OOXMLElement { - - private static final long serialVersionUID = 7200764163180402065L; - private HashMap attrs = null; - - public FillRect() { - } - - public FillRect(HashMap attrs) { - this.attrs = attrs; - } - - public FillRect(FillRect f) { - this.attrs = f.attrs; - } - - public static FillRect parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fillRect")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fillRect")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fillRect.parseOOXML: " + e.toString()); - } - FillRect oe = new FillRect(); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = (String) i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FillRect(this); - } -} - -/** - * FillMode Choice of either tile or stretch elements - *

                  - * tile (Tile) This element specifies that a BLIP should be tiled to fill the - * available space. This element defines a "tile" rectangle within the bounding - * box. The image is encompassed within the tile rectangle, and the tile - * rectangle is tiled across the bounding box to fill the entire area. stretch - * (Stretch) This element specifies that a BLIP should be stretched to fill the - * target rectangle. The other option is a tile where a BLIP is tiled to fill - * the available area. - *

                  - * parent: blipFill choice of : stretch or tile - */ -class FillMode implements OOXMLElement { - - private static final long serialVersionUID = 967269629502516244L; - // Since both "child" elements are so small, just output OOXML "by hand" - private HashMap attrs = null; - private boolean tile = false; - - public FillMode() { - } - - public FillMode(HashMap attrs, boolean tile) { - this.attrs = attrs; - this.tile = tile; - } - - public FillMode(FillMode f) { - this.attrs = f.attrs; - this.tile = f.tile; - } - - public static FillMode parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - boolean tile = false; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tile")) { - tile = true; - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } else if (tnm.equals("fillRect")) { // only child of - // stretch - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp - .getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("tile") || endTag.equals("stretch")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("FillMode.parseOOXML: " + e.toString()); - } - FillMode fm = new FillMode(attrs, tile); - return fm; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - // Since both "child" elements are so small, just output OOXML "by hand" - if (this.tile) - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - if (this.tile) - ooxml.append("/>"); - else - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FillMode(this); - } -} - -/** - * gsLst (Gradient Stop List) The list of gradient stops that specifies the - * gradient colors and their relative positions in the color band. - *

                  - * parent: gradFill children: gs - */ -class GsLst implements OOXMLElement { - - private static final long serialVersionUID = 6576320251327916221L; - private ArrayList gs; - - public GsLst(ArrayList g) { - this.gs = g; - } - - public GsLst(GsLst gl) { - this.gs = gl.gs; - } - - public static GsLst parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - ArrayList g = new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gs")) { - lastTag.push(tnm); - g.add(Gs.parseOOXML(xpp, lastTag, bk)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gsLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gsLst.parseOOXML: " + e.toString()); - } - GsLst gl = new GsLst(g); - return gl; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - for (int i = 0; i < gs.size(); i++) { - ooxml.append(gs.get(i).getOOXML()); - } - ooxml.append(""); - } else - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GsLst(this); - } -} - -/** - * gs (Gradient stops) This element defines a gradient stop. A gradient stop - * consists of a position where the stop appears in the color band. - *

                  - * parent: gsLst children: COLORCHOICE - */ -class Gs implements OOXMLElement { - - private static final long serialVersionUID = 7626866241477598159L; - private String pos = null; - private ColorChoice colorChoice = null; - - public Gs(String pos, ColorChoice c) { - this.pos = pos; - this.colorChoice = c; - } - - public Gs(Gs g) { - this.pos = g.pos; - this.colorChoice = g.colorChoice; - } - - public static Gs parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String pos = null; - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gs")) { // get attributes - pos = xpp.getAttributeValue(0); - } else if (tnm.equals("hslClr") || tnm.equals("prstClr") - || tnm.equals("schemeClr") - || tnm.equals("scrgbClr") || tnm.equals("srgbClr") - || tnm.equals("sysClr")) { // get attributes - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gs")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gs.parseOOXML: " + e.toString()); - } - Gs g = new Gs(pos, c); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Gs(this); - } - -} diff --git a/src/main/java/io/starter/formats/OOXML/FillGroup.kt b/src/main/java/io/starter/formats/OOXML/FillGroup.kt new file mode 100644 index 0000000..738f962 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/FillGroup.kt @@ -0,0 +1,1410 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.HashMap +import java.util.Stack + +/** + * FillGroup -- holds ONE OF the Fill-type OOXML/DrawingML Elements: blipFill + * gradFill grpFill pattFill solidFill noFill + * + * + * parents: bgPr, spPr, fmtScheme, endParaRPr, rPr ... + */ +// TODO: Handle blip element children +// TODO: Handle gradFill element shade properties +class FillGroup : OOXMLElement { + private var bf: BlipFill? = null + private var gpf: GradFill? = null + private var grpf: GrpFill? = null + private var pf: PattFill? = null + private var sf: SolidFill? = null + + override// CHOICE OF fill + // no fill + val ooxml: String + get() { + val ooxml = StringBuffer() + if (sf != null) + ooxml.append(sf!!.ooxml) + else if (gpf != null) + ooxml.append(gpf!!.ooxml) + else if (grpf != null) + ooxml.append(grpf!!.ooxml) + else if (bf != null) + ooxml.append(bf!!.ooxml) + else if (pf != null) + ooxml.append(pf!!.ooxml) + else + ooxml.append("") + return ooxml.toString() + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + var embed: String? + get() = if (bf != null) bf!!.embed else null + set(embed) { + if (bf == null) + bf = BlipFill() + bf!!.embed = embed + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + var link: String? + get() = if (bf != null) bf!!.link else null + set(link) { + if (bf == null) + bf = BlipFill() + bf!!.link = link + } + + val color: Int + get() = if (sf != null) sf!!.getColor() else -1 + + constructor(bf: BlipFill, gpf: GradFill, grpf: GrpFill, pf: PattFill, + sf: SolidFill) { + this.bf = bf + this.gpf = gpf + this.grpf = grpf + this.pf = pf + this.sf = sf + } + + constructor(f: FillGroup) { + this.bf = f.bf + this.gpf = f.gpf + this.grpf = f.grpf + this.pf = f.pf + this.sf = f.sf + } + + override fun cloneElement(): OOXMLElement { + return FillGroup(this) + } + + /** + * set the color for this fill + * NOTE: at this time only solid fills may be specified + * + * @param clr + */ + fun setColor(clr: String) { + if (bf != null) + return + gpf = null + grpf = null + pf = null + sf = SolidFill() + sf!!.setColor(clr) + } + + companion object { + private val serialVersionUID = 8320871291479597945L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var bf: BlipFill? = null + var gpf: GradFill? = null + var grpf: GrpFill? = null + var pf: PattFill? = null + var sf: SolidFill? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "solidFill") { + lastTag.push(tnm) + sf = SolidFill.parseOOXML(xpp, lastTag, bk) + lastTag.pop() + break + } else if (tnm == "noFill") { + // do nothing + } else if (tnm == "gradFill") { + lastTag.push(tnm) + gpf = GradFill.parseOOXML(xpp, lastTag, bk) + lastTag.pop() + break + } else if (tnm == "grpFill") { + lastTag.push(tnm) + grpf = GrpFill.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } else if (tnm == "pattFill") { + lastTag.push(tnm) + pf = PattFill.parseOOXML(xpp, lastTag, bk) + lastTag.pop() + break + } else if (tnm == "blipFill") { + lastTag.push(tnm) + bf = BlipFill.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here + lastTag.pop() + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("FillGroup.parseOOXML: $e") + } + + return FillGroup(bf, gpf, grpf, pf, sf) + } + } +} + +/** + * gradFill (Gradient Fill) + * + * + * This element defines a gradient fill. + * + * + * parents: many children: gsLst, SHADEPROPERTIES, tileRect + */ +// TODO: finish SHADEPROPERTIES +internal class GradFill : OOXMLElement { + private var g: GsLst? = null + // private ShadeProps sp= null; + private var tr: TileRect? = null + private var attrs: HashMap? = null + + override// attributes + // if (sp!=null) ooxml.append(sp.getOOXML()); + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (g != null) + ooxml.append(g!!.ooxml) + if (tr != null) + ooxml.append(tr!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(g: GsLst, /* ShadeProps sp, */tr: TileRect, attrs: HashMap) { + this.g = g + // this.sp= sp; + this.tr = tr + this.attrs = attrs + } + + constructor(gf: GradFill) { + this.g = gf.g + // this.sp= gf.sp; + this.tr = gf.tr + this.attrs = gf.attrs + } + + override fun cloneElement(): OOXMLElement { + return GradFill(this) + } + + companion object { + + private val serialVersionUID = 8965776942160065286L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): GradFill { + val attrs = HashMap() + var g: GsLst? = null + // ShadeProps sp= null; + var tr: TileRect? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "gradFill") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp + .getAttributeValue(i) + } + } else if (tnm == "tileRect") { + lastTag.push(tnm) + tr = TileRect.parseOOXML(xpp, lastTag) + } else if (tnm == "gsLst") { + lastTag.push(tnm) + g = GsLst.parseOOXML(xpp, lastTag, bk) + /* + * } else if (tnm.equals("lin") || tnm.equals("path")) { + * lastTag.push(tnm); sp= (shadeProps) + * shadeProps.parseOOXML(xpp, lastTag).clone(); + */ + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "gradFill") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("gradFill.parseOOXML: $e") + } + + return GradFill(g, /* sp, */tr, attrs) + } + } + +} + +/** + * blipFill (Picture Fill) + * + * + * This element specifies the type of picture fill that the picture object will + * have. Because a picture has a picture fill already by default, it is possible + * to have two fills specified for a picture object. An example of this is shown + * below. + * + * + * parents: many children: blip, srcRect, FillModeProperties + */ +internal class BlipFill : OOXMLElement { + private var attrs: HashMap? = null + private var blip: Blip? = null + private var srcRect: SrcRect? = null + private var fillMode: FillMode? = null + private var ns: String? = "xdr" // default namespace + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + if (ns == null) + Logger.logErr("Error: BlipFill Namespace is null") + ooxml.append("<$ns:blipFill") + if (attrs != null) { + val i = attrs!!.keys.iterator() + while (i.hasNext()) { + val key = i.next() + val `val` = attrs!![key] + ooxml.append(" $key=\"$`val`\"") + } + } + ooxml.append(">") + if (blip != null) + ooxml.append(blip!!.ooxml) + if (srcRect != null) + ooxml.append(srcRect!!.ooxml) + if (fillMode != null) + ooxml.append(fillMode!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + var embed: String? + get() = if (blip != null) blip!!.embed else null + set(embed) { + if (blip == null) + blip = Blip() + blip!!.embed = embed + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + var link: String? + get() = if (blip != null) blip!!.link else null + set(link) { + if (blip == null) + blip = Blip() + blip!!.link = link + } + + constructor() { // no-parameter constructor, set common defaults + srcRect = SrcRect() + fillMode = FillMode(null, false) + } + + constructor(ns: String, attrs: HashMap, b: Blip, s: SrcRect, f: FillMode) { + this.ns = ns + this.attrs = attrs + this.blip = b + this.srcRect = s + this.fillMode = f + } + + constructor(bf: BlipFill) { + this.ns = bf.ns + this.attrs = bf.attrs + this.blip = bf.blip + this.srcRect = bf.srcRect + this.fillMode = bf.fillMode + } + + override fun cloneElement(): OOXMLElement { + return BlipFill(this) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -2030570462677450734L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): BlipFill { + val attrs = HashMap() + var b: Blip? = null + var s: SrcRect? = null + var f: FillMode? = null + var ns: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "blipFill") { // get attributes + ns = xpp.prefix + for (i in 0 until xpp.attributeCount) + attrs[xpp.getAttributeName(i)] = xpp + .getAttributeValue(i) + } else if (tnm == "blip") { + lastTag.push(tnm) + b = Blip.parseOOXML(xpp, lastTag) + } else if (tnm == "srcRect") { + lastTag.push(tnm) + s = SrcRect.parseOOXML(xpp, lastTag) + } else if (tnm == "stretch" || tnm == "tile") { + lastTag.push(tnm) + f = FillMode.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "blipFill") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("blipFill.parseOOXML: $e") + } + + return BlipFill(ns, attrs, b, s, f) + } + } +} + +/** + * solidFill (Solid Fill) This element specifies a solid color fill. The shape + * is filled entirely with the specified color + * + * + * parents: many children: COLORCHOICE + */ +internal class SolidFill : OOXMLElement { + private var color: ColorChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (this.color != null) + ooxml.append(this.color!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor() { // no-param constructor, set up common defaults + this.color = ColorChoice(null, SrgbClr("000000", null), null, null, null) + } + + constructor(c: ColorChoice) { + this.color = c + } + + constructor(s: SolidFill) { + this.color = s.color + } + + /** + * creates a solid fill of a specific color + * + * @param clr hex color string without # + */ + constructor(clr: String?) { + var clr = clr + if (clr == null) clr = "FFFFFF" + this.color = ColorChoice(null, SrgbClr(clr, null), null, null, null) + } + + override fun cloneElement(): OOXMLElement { + return SolidFill(this) + } + + /** + * set the color for this solid fill in hex (html) string format + * + * @param clr + */ + fun setColor(clr: String) { + this.color = ColorChoice(null, SrgbClr(clr, null), null, null, null) + } + + fun getColor(): Int { + return color!!.color + } + + companion object { + + private val serialVersionUID = 3341509200573989744L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): SolidFill { + var c: ColorChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "hslClr" || tnm == "prstClr" + || tnm == "schemeClr" + || tnm == "scrgbClr" || tnm == "srgbClr" + || tnm == "sysClr") { // get attributes + lastTag.push(tnm) + c = ColorChoice.parseOOXML(xpp, lastTag, bk) + .cloneElement() as ColorChoice + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "solidFill") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("solidFill.parseOOXML: $e") + } + + return SolidFill(c) + } + } +} + +/** + * pattFill (Pattern Fill) This element specifies a pattern fill. A repeated + * pattern is used to fill the object. + * + * + * parent: many children: bgClr, fgClr + */ +internal class PattFill : OOXMLElement { + private var prst: String? = null + private var bg: BgClr? = null + private var fg: FgClr? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (fg != null) + ooxml.append(fg!!.ooxml) + if (bg != null) + ooxml.append(bg!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(prst: String, bg: BgClr, fg: FgClr) { + this.prst = prst + this.bg = bg + this.fg = fg + } + + constructor(p: PattFill) { + this.prst = p.prst + this.bg = p.bg + this.fg = p.fg + } + + override fun cloneElement(): OOXMLElement { + return PattFill(this) + } + + companion object { + + private val serialVersionUID = -1052627959661249692L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): PattFill { + var prst: String? = null + var bg: BgClr? = null + var fg: FgClr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pattFill") { // get attributes + prst = xpp.getAttributeValue(0) + } else if (tnm == "bgClr") { + lastTag.push(tnm) + bg = BgClr.parseOOXML(xpp, lastTag, bk) + } else if (tnm == "fgClr") { + lastTag.push(tnm) + fg = FgClr.parseOOXML(xpp, lastTag, bk) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "pattFill") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("pattFill.parseOOXML: $e") + } + + return PattFill(prst, bg, fg) + } + } +} + +/** + * grpFill (Group Fill) This element specifies a group fill. When specified, + * this setting indicates that the parent element is part of a group and should + * inherit the fill properties of the group. + * + * + * parent: many children: CT_GROUPFILL: ??? contains nothing? + */ +internal class GrpFill : OOXMLElement { + + override// TODO: no attributes or children? + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + override fun cloneElement(): OOXMLElement { + return GrpFill() + } + + companion object { + private val serialVersionUID = 2388879629485740996L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): GrpFill { + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "grpFill") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("grpFill.parseOOXML: $e") + } + + return GrpFill() + } + } +} + +/** + * bgClr background color + * + * + * parent: pattFill + */ +internal class BgClr : OOXMLElement { + private var colorChoice: ColorChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (this.colorChoice != null) + ooxml.append(this.colorChoice!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + + constructor(c: ColorChoice) { + this.colorChoice = c + } + + constructor(s: BgClr) { + this.colorChoice = s.colorChoice + } + + override fun cloneElement(): OOXMLElement { + return BgClr(this) + } + + companion object { + + private val serialVersionUID = -879409152334931909L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): BgClr { + var c: ColorChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "hslClr" || tnm == "prstClr" + || tnm == "schemeClr" + || tnm == "scrgbClr" || tnm == "srgbClr" + || tnm == "sysClr") { // get attributes + lastTag.push(tnm) + c = ColorChoice.parseOOXML(xpp, lastTag, bk) as ColorChoice + + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "bgClr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("bgClr.parseOOXML: $e") + } + + return BgClr(c) + } + } +} + +/** + * fgClr Foreground Color + * + * + * parent: pattFill + */ +internal class FgClr : OOXMLElement { + private var colorChoice: ColorChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (this.colorChoice != null) + ooxml.append(this.colorChoice!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(c: ColorChoice) { + this.colorChoice = c + } + + constructor(s: FgClr) { + this.colorChoice = s.colorChoice + } + + override fun cloneElement(): OOXMLElement { + return FgClr(this) + } + + companion object { + private val serialVersionUID = 6836994790529289731L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): FgClr { + var c: ColorChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "hslClr" || tnm == "prstClr" + || tnm == "schemeClr" + || tnm == "scrgbClr" || tnm == "srgbClr" + || tnm == "sysClr") { // get attributes + lastTag.push(tnm) + c = ColorChoice.parseOOXML(xpp, lastTag, bk) as ColorChoice + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "fgClr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("fgClr.parseOOXML: $e") + } + + return FgClr(c) + } + } +} + +/** + * blip (Blip) This element specifies the existence of an image (binary large + * image or picture) and contains a reference to the image data. + * + * + * parent: blipFill, buFill children: MANY + */ +// TODO: HANDLE THE MANY CHILDREN +internal class Blip : OOXMLElement { + private var attrs: HashMap? = HashMap() + + // TODO: cstate= "print" + override// attributes + // TODO: HANDLE CHILDREN + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + var embed: String? + get() = if (attrs != null && attrs!!["r:embed"] != null) { + attrs!!["r:embed"] + } else null + set(embed) { + attrs!!["r:embed"] = embed + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + var link: String? + get() = if (attrs != null && attrs!!["link"] != null) { + attrs!!["link"] + } else null + set(link) { + attrs!!["link"] = link + } + + constructor() {} + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(b: Blip) { + this.attrs = b.attrs + } + + override fun cloneElement(): OOXMLElement { + return Blip(this) + } + + companion object { + + private val serialVersionUID = 5188967633123620513L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): Blip { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "blip") { // get attributes + for (i in 0 until xpp.attributeCount) { + if (xpp.getAttributePrefix(i) != null) + attrs[xpp.getAttributePrefix(i) + ":" + + xpp.getAttributeName(i)] = xpp + .getAttributeValue(i) + else + attrs[xpp.getAttributeName(i)] = xpp + .getAttributeValue(i) + } + // } else if (tnm.equals("CHILDELEMENT")) { + // lastTag.push(tnm); + // layout = (layout) layout.parseOOXML(xpp, + // lastTag).clone(); + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "blip") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("blip.parseOOXML: $e") + } + + return Blip(attrs) + } + } +} + +/** + * tileRect (Tile Rectangle) This element specifies a rectangular region of the + * shape to which the gradient is applied. This region is then tiled across the + * remaining area of the shape to complete the fill. The tile rectangle is + * defined by percentage offsets from the sides of the shape's bounding box. + * parent: gradFill children: none + */ +internal class TileRect : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(t: TileRect) { + this.attrs = t.attrs + } + + override fun cloneElement(): OOXMLElement { + return TileRect(this) + } + + companion object { + + private val serialVersionUID = 5380575948049571420L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): TileRect { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "tileRect") { // get attributes + for (i in 0 until xpp.attributeCount) + attrs[xpp.getAttributeName(i)] = xpp + .getAttributeValue(i) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "tileRect") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("tileRect.parseOOXML: $e") + } + + return TileRect(attrs) + } + } +} + +/** + * srcRect (Source Rectangle) This element specifies the portion of the blip + * used for the fill + * + * + * parent: blipFill children: NONE + */ +internal class SrcRect : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor() {} + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(s: SrcRect) { + this.attrs = s.attrs + } + + override fun cloneElement(): OOXMLElement { + return SrcRect(this) + } + + companion object { + + private val serialVersionUID = -6407800173040857433L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): SrcRect { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "srcRect") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp + .getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "srcRect") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("srcRect.parseOOXML: $e") + } + + return SrcRect(attrs) + } + } +} + +/** + * fillRect (Fill Rectangle) This element specifies a fill rectangle. When + * stretching of an image is specified, a source rectangle, srcRect, is scaled + * to fit the specified fill rectangle. parent: stretch children: NONE + */ +internal class FillRect : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor() {} + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(f: FillRect) { + this.attrs = f.attrs + } + + override fun cloneElement(): OOXMLElement { + return FillRect(this) + } + + companion object { + + private val serialVersionUID = 7200764163180402065L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack<*>): FillRect { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "fillRect") { // get attributes + for (i in 0 until xpp.attributeCount) + attrs[xpp.getAttributeName(i)] = xpp + .getAttributeValue(i) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "fillRect") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("fillRect.parseOOXML: $e") + } + + return FillRect() + } + } +} + +/** + * FillMode Choice of either tile or stretch elements + * + * + * tile (Tile) This element specifies that a BLIP should be tiled to fill the + * available space. This element defines a "tile" rectangle within the bounding + * box. The image is encompassed within the tile rectangle, and the tile + * rectangle is tiled across the bounding box to fill the entire area. stretch + * (Stretch) This element specifies that a BLIP should be stretched to fill the + * target rectangle. The other option is a tile where a BLIP is tiled to fill + * the available area. + * + * + * parent: blipFill choice of : stretch or tile + */ +internal class FillMode : OOXMLElement { + // Since both "child" elements are so small, just output OOXML "by hand" + private var attrs: HashMap? = null + private var tile = false + + override// Since both "child" elements are so small, just output OOXML "by hand" + // attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + if (this.tile) + ooxml.append("") + else + ooxml.append("/>") + return ooxml.toString() + } + + constructor() {} + + constructor(attrs: HashMap, tile: Boolean) { + this.attrs = attrs + this.tile = tile + } + + constructor(f: FillMode) { + this.attrs = f.attrs + this.tile = f.tile + } + + override fun cloneElement(): OOXMLElement { + return FillMode(this) + } + + companion object { + + private val serialVersionUID = 967269629502516244L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): FillMode { + val attrs = HashMap() + var tile = false + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "tile") { + tile = true + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp + .getAttributeValue(i) + } + } else if (tnm == "fillRect") { // only child of + // stretch + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp + .getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "tile" || endTag == "stretch") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("FillMode.parseOOXML: $e") + } + + return FillMode(attrs, tile) + } + } +} + +/** + * gsLst (Gradient Stop List) The list of gradient stops that specifies the + * gradient colors and their relative positions in the color band. + * + * + * parent: gradFill children: gs + */ +internal class GsLst : OOXMLElement { + private var gs: ArrayList? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + for (i in gs!!.indices) { + ooxml.append(gs!![i].ooxml) + } + ooxml.append("") + } else + ooxml.append("/>") + return ooxml.toString() + } + + constructor(g: ArrayList) { + this.gs = g + } + + constructor(gl: GsLst) { + this.gs = gl.gs + } + + override fun cloneElement(): OOXMLElement { + return GsLst(this) + } + + companion object { + + private val serialVersionUID = 6576320251327916221L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): GsLst { + val g = ArrayList() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "gs") { + lastTag.push(tnm) + g.add(Gs.parseOOXML(xpp, lastTag, bk)) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "gsLst") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("gsLst.parseOOXML: $e") + } + + return GsLst(g) + } + } +} + +/** + * gs (Gradient stops) This element defines a gradient stop. A gradient stop + * consists of a position where the stop appears in the color band. + * + * + * parent: gsLst children: COLORCHOICE + */ +internal class Gs : OOXMLElement { + private var pos: String? = null + private var colorChoice: ColorChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append(colorChoice!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(pos: String, c: ColorChoice) { + this.pos = pos + this.colorChoice = c + } + + constructor(g: Gs) { + this.pos = g.pos + this.colorChoice = g.colorChoice + } + + override fun cloneElement(): OOXMLElement { + return Gs(this) + } + + companion object { + + private val serialVersionUID = 7626866241477598159L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): Gs { + var pos: String? = null + var c: ColorChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "gs") { // get attributes + pos = xpp.getAttributeValue(0) + } else if (tnm == "hslClr" || tnm == "prstClr" + || tnm == "schemeClr" + || tnm == "scrgbClr" || tnm == "srgbClr" + || tnm == "sysClr") { // get attributes + lastTag.push(tnm) + c = ColorChoice.parseOOXML(xpp, lastTag, bk) as ColorChoice + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "gs") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("gs.parseOOXML: $e") + } + + return Gs(pos, c) + } + } + +} diff --git a/src/main/java/io/starter/formats/OOXML/Gd.java b/src/main/java/io/starter/formats/OOXML/Gd.java deleted file mode 100644 index 4f99263..0000000 --- a/src/main/java/io/starter/formats/OOXML/Gd.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * gd (Shape Guide) - *

                  - * This element specifies the presence of a shape guide that will be used to govern the geometry of the specified - * shape. A shape guide consists of a formula and a name that the result of the formula is assigned to. Recognized - * formulas are listed with the fmla attribute documentation for this element. - *

                  - * parents: avLst, gdLst - * children: none - */ -public class Gd implements OOXMLElement { - - private static final long serialVersionUID = -633176234309521998L; - private HashMap attrs = null; - - public Gd(HashMap attrs) { - this.attrs = attrs; - } - - public Gd(Gd g) { - this.attrs = g.attrs; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gd")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gd")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gd.parseOOXML: " + e.toString()); - } - Gd g = new Gd(attrs); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Gd(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Gd.kt b/src/main/java/io/starter/formats/OOXML/Gd.kt new file mode 100644 index 0000000..93d808c --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Gd.kt @@ -0,0 +1,106 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * gd (Shape Guide) + * + * + * This element specifies the presence of a shape guide that will be used to govern the geometry of the specified + * shape. A shape guide consists of a formula and a name that the result of the formula is assigned to. Recognized + * formulas are listed with the fmla attribute documentation for this element. + * + * + * parents: avLst, gdLst + * children: none + */ +class Gd : OOXMLElement { + private var attrs: HashMap<*, *>? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap<*, *>) { + this.attrs = attrs + } + + constructor(g: Gd) { + this.attrs = g.attrs + } + + override fun cloneElement(): OOXMLElement { + return Gd(this) + } + + companion object { + + private val serialVersionUID = -633176234309521998L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "gd") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "gd") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("gd.parseOOXML: $e") + } + + return Gd(attrs) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/GeomGroup.java b/src/main/java/io/starter/formats/OOXML/GeomGroup.java deleted file mode 100644 index 4ea05be..0000000 --- a/src/main/java/io/starter/formats/OOXML/GeomGroup.java +++ /dev/null @@ -1,788 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * Geometry Group: one of prstGeom (Preset shapes) or custGeom (custom shapes) - */ -//TODO: Finish custGeom child elements ahLst, cxnLst, rect (+ finish path element) -public class GeomGroup implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7202561792070909825L; - private PrstGeom p = null; - private CustGeom c = null; - - public GeomGroup() { - } - - public GeomGroup(PrstGeom p, CustGeom c) { - this.p = p; - this.c = c; - } - - public GeomGroup(GeomGroup g) { - this.p = g.p; - this.c = g.c; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - PrstGeom p = null; - CustGeom c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstGeom")) { - lastTag.push(tnm); - p = PrstGeom.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } else if (tnm.equals("custGeom")) { - lastTag.push(tnm); - c = CustGeom.parseOOXML(xpp, lastTag); - lastTag.pop(); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("GeomGroup.parseOOXML: " + e.toString()); - } - GeomGroup g = new GeomGroup(p, c); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (this.p != null) ooxml.append(p.getOOXML()); - else if (this.c != null) ooxml.append(c.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GeomGroup(this); - } - -} - -class PrstGeom implements OOXMLElement { - - private static final long serialVersionUID = 8327708502983472577L; - private String prst = null; - private AvLst avLst = null; - - public PrstGeom() { // no-param constructor, set up common defaults - prst = "rect"; - avLst = new AvLst(); - } - - public PrstGeom(String prst, AvLst a) { - this.prst = prst; - this.avLst = a; - } - - public PrstGeom(PrstGeom p) { - this.prst = p.prst; - this.avLst = p.avLst; - } - - public static PrstGeom parseOOXML(XmlPullParser xpp, Stack lastTag) { - String prst = null; - AvLst a = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstGeom")) { // get attributes - prst = xpp.getAttributeValue(0); - } else if (tnm.equals("avLst")) { - lastTag.push(tnm); - a = (AvLst) AvLst.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("prstGeom")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("prstGeom.parseOOXML: " + e.toString()); - } - PrstGeom p = new PrstGeom(prst, a); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (avLst != null) ooxml.append(avLst.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PrstGeom(this); - } - -} - - -/** - * custGeom (Custom Geometry) - * This element specifies the existence of a custom geometric shape. This shape will consist of a series of lines and - * curves described within a creation path. In addition to this there may also be adjust values, guides, adjust - * handles, connection sites and an inscribed rectangle specified for this custom geometric shape. - *

                  - * parent: soPr - * children: avLst, gdLst, ahLst, cxnLst, rect, pathLst (REQ) - */ -// TODO: Finish child elements ahLst, cxnLst -class CustGeom implements OOXMLElement { - - private static final long serialVersionUID = 4036207867619551810L; - private PathLst pathLst; - private GdLst gdLst; - private AvLst avLst; - private CxnLst cxnLst; - private Rect rect; - - public CustGeom(PathLst p, GdLst g, AvLst a, CxnLst cx, Rect r) { - this.pathLst = p; - this.gdLst = g; - this.avLst = a; - this.cxnLst = cx; - this.rect = r; - } - - public CustGeom(CustGeom c) { - this.pathLst = c.pathLst; - this.gdLst = c.gdLst; - this.avLst = c.avLst; - this.cxnLst = c.cxnLst; - this.rect = c.rect; - } - - public static CustGeom parseOOXML(XmlPullParser xpp, Stack lastTag) { - PathLst p = null; - GdLst g = null; - AvLst a = null; - CxnLst cx = null; - Rect r = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pathLst")) { // REQ - lastTag.push(tnm); - p = PathLst.parseOOXML(xpp, lastTag); - } else if (tnm.equals("gdLst")) { - lastTag.push(tnm); - g = GdLst.parseOOXML(xpp, lastTag); - } else if (tnm.equals("avLst")) { - lastTag.push(tnm); - a = (AvLst) AvLst.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cxnLst")) { - lastTag.push(tnm); - cx = CxnLst.parseOOXML(xpp, lastTag); - } else if (tnm.equals("rect")) { - lastTag.push(tnm); - r = Rect.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("custGeom")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("custGeom.parseOOXML: " + e.toString()); - } - CustGeom c = new CustGeom(p, g, a, cx, r); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (avLst != null) ooxml.append(avLst.getOOXML()); // avLst - if (gdLst != null) ooxml.append(gdLst.getOOXML()); // gdLst - // TODO: ahLst - ooxml.append(""); - if (cxnLst != null) ooxml.append(cxnLst.getOOXML()); // cxnLst - if (rect != null) ooxml.append(rect.getOOXML()); // rect - if (pathLst != null) ooxml.append(pathLst.getOOXML()); // pathLst - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CustGeom(this); - } -} - -/** - * pathLst (List of Shape Paths) - * This element specifies the entire path that is to make up a single geometric shape. The pathLst can consist of - * many individual paths within it. - *

                  - * parent: custGeom - * children path (multiple) - */ -class PathLst implements OOXMLElement { - - private static final long serialVersionUID = -1996347204024728000L; - private ArrayList path; - private HashMap attrs = null; - - - public PathLst(HashMap attrs, ArrayList p) { - this.attrs = attrs; - this.path = p; - } - - public PathLst(PathLst pl) { - this.attrs = pl.attrs; - this.path = pl.path; - } - - - public static PathLst parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - ArrayList p = new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pathLst")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } else if (tnm.equals("path")) { // get one or more path children - lastTag.push(tnm); - p.add(Path.parseOOXML(xpp, lastTag)); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("pathLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pathLst.parseOOXML: " + e.toString()); - } - PathLst pl = new PathLst(attrs, p); - return pl; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" ii = attrs.keySet().iterator(); - while (ii.hasNext()) { - String key = ii.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (path != null) { - for (int i = 0; i < path.size(); i++) - ooxml.append(path.get(i).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PathLst(this); - } -} - -/** - * - */ -class Path implements OOXMLElement { - - private static final long serialVersionUID = 6906237439620322589L; - private HashMap attrs = null; - - public Path(HashMap attrs) { - this.attrs = attrs; - } - - public Path(Path p) { - this.attrs = p.attrs; - } - - - public static Path parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("path")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("moveTo")) { - } else if (tnm.equals("lnTo")) { - } else if (tnm.equals("close")) { - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("path")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("path.parseOOXML: " + e.toString()); - } - Path p = new Path(attrs); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Path(this); - } -} - -/** - * gdLst (List of Shape Guides) - * This element specifies all the guides that will be used for this shape. A guide is specified by the gd element and - * defines a calculated value that may be used for the construction of the corresponding shape. - */ -class GdLst implements OOXMLElement { - - private static final long serialVersionUID = -7852193131141462744L; - private ArrayList gds; - - public GdLst(ArrayList gds) { - this.gds = gds; - } - - public GdLst(GdLst g) { - this.gds = g.gds; - } - - - public static GdLst parseOOXML(XmlPullParser xpp, Stack lastTag) { - ArrayList gds = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("gd")) { - lastTag.push(tnm); - if (gds == null) gds = new ArrayList(); - gds.add((Gd) Gd.parseOOXML(xpp, lastTag)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("gdLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("gdLst.parseOOXML: " + e.toString()); - } - GdLst g = new GdLst(gds); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (gds != null) { - for (int i = 0; i < gds.size(); i++) - ooxml.append(gds.get(i).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GdLst(this); - } -} - -/** - * ahLst (List of Shape Adjust Handles) - * This element specifies the adjust handles that will be applied to a custom geometry. These adjust handles will - * specify points within the geometric shape that can be used to perform certain transform operations on the - * shape. - * [Example: Consider the scenario where a custom geometry, an arrow in this case, has been drawn and adjust - * handles have been placed at the top left corner of both the arrow head and arrow body. The user interface can - * then be made to transform only certain parts of the shape by using the corresponding adjust handle - *

                  - * parent: custGeom - * children: one or more of [ahXY, ahPolar (both REQ)] - */ - - -/** - * rect (Shape Text Rectangle) - * - * This element specifies the rectangular bounding box for text within a custGeom shape. The default for this - * rectangle is the bounding box for the shape. This can be modified using this elements four attributes to inset or - * extend the text bounding box. - * - * parent: custGeom - * children: none - */ -class Rect implements OOXMLElement { - - private static final long serialVersionUID = 2790708601254975676L; - private HashMap attrs; - - public Rect(HashMap attrs) { - this.attrs = attrs; - } - - public Rect(Rect r) { - this.attrs = r.attrs; - } - - - public static Rect parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("rect")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("CHILDELEMENT")) { - lastTag.push(tnm); - //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("rect")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("rect.parseOOXML: " + e.toString()); - } - Rect r = new Rect(attrs); - return r; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Rect(this); - } -} - -/** - * cxnLst (List of Shape Connection Sites) - * This element specifies all the connection sites that will be used for this shape. A connection site is specified by - * defining a point within the shape bounding box that can have a cxnSp element attached to it. These connection - * sites are specified using the shape coordinate system that is specified within the ext transform element. - * - * parents: custGeom - * children: cxn (0 or more) - */ -class CxnLst implements OOXMLElement { - - private static final long serialVersionUID = -562847539163221621L; - private ArrayList cxns; - - public CxnLst(ArrayList cxns) { - this.cxns = cxns; - } - - public CxnLst(CxnLst c) { - this.cxns = c.cxns; - } - - public static CxnLst parseOOXML(XmlPullParser xpp, Stack lastTag) { - ArrayList cxns = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cxn")) { - lastTag.push(tnm); - if (cxns == null) cxns = new ArrayList(); - cxns.add(Cxn.parseOOXML(xpp, lastTag)); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("cxnLst")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cxnLst.parseOOXML: " + e.toString()); - } - CxnLst c = new CxnLst(cxns); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (cxns != null) { - for (int i = 0; i < cxns.size(); i++) - ooxml.append(cxns.get(i).getOOXML()); - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CxnLst(this); - } -} - - -/** - * cxn (Shape Connection Site) - * This element specifies the existence of a connection site on a custom shape. A connection site allows a cxnSp to - * be attached to this shape. This connection will be maintined when the shape is repositioned within the - * document. It should be noted that this connection is placed within the shape bounding box using the transform - * coordinate system which is also called the shape coordinate system, as it encompasses theentire shape. The - * width and height for this coordinate system are specified within the ext transform element. - * - * parents: cxnLst - * children: pos REQ - */ -class Cxn implements OOXMLElement { - - private static final long serialVersionUID = -4193511102420582252L; - private HashMap attrs; - private Pos pos = null; - - public Cxn(HashMap attrs, Pos p) { - this.attrs = attrs; - this.pos = p; - } - - public Cxn(Cxn c) { - this.attrs = c.attrs; - this.pos = c.pos; - } - - - public static Cxn parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - Pos p = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cxn")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); // ang REQ - } - } else if (tnm.equals("pos")) { - lastTag.push(tnm); - p = Pos.parseOOXML(xpp, lastTag); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("cxn")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cxn.parseOOXML: " + e.toString()); - } - Cxn c = new Cxn(attrs, p); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (pos != null) ooxml.append(pos.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Cxn(this); - } -} - - -/** - * pos (Shape Position Coordinate) - * Specifies a position coordinate within the shape bounding box. It should be noted that this coordinate is placed - * within the shape bounding box using the transform coordinate system which is also called the shape coordinate - * system, as it encompasses the entire shape. The width and height for this coordinate system are specified within - * the ext transform element. - * - * parents: cxn, ahPolar, ahXY - * children: none - */ -class Pos implements OOXMLElement { - - private static final long serialVersionUID = 5500991309750603125L; - private HashMap attrs; - - public Pos(HashMap attrs) { - this.attrs = attrs; - } - - public Pos(Pos p) { - this.attrs = p.attrs; - } - - - public static Pos parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pos")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("pos")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pos.parseOOXML: " + e.toString()); - } - Pos p = new Pos(attrs); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Pos(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/GeomGroup.kt b/src/main/java/io/starter/formats/OOXML/GeomGroup.kt new file mode 100644 index 0000000..4f50662 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/GeomGroup.kt @@ -0,0 +1,835 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.HashMap +import java.util.Stack + +/** + * Geometry Group: one of prstGeom (Preset shapes) or custGeom (custom shapes) + */ +//TODO: Finish custGeom child elements ahLst, cxnLst, rect (+ finish path element) +class GeomGroup : OOXMLElement { + private var p: PrstGeom? = null + private var c: CustGeom? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + if (this.p != null) + ooxml.append(p!!.ooxml) + else if (this.c != null) ooxml.append(c!!.ooxml) + return ooxml.toString() + } + + constructor() {} + + constructor(p: PrstGeom, c: CustGeom) { + this.p = p + this.c = c + } + + constructor(g: GeomGroup) { + this.p = g.p + this.c = g.c + } + + override fun cloneElement(): OOXMLElement { + return GeomGroup(this) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -7202561792070909825L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + var p: PrstGeom? = null + var c: CustGeom? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "prstGeom") { + lastTag.push(tnm) + p = PrstGeom.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } else if (tnm == "custGeom") { + lastTag.push(tnm) + c = CustGeom.parseOOXML(xpp, lastTag) + lastTag.pop() + break + } + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here + lastTag.pop() + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("GeomGroup.parseOOXML: $e") + } + + return GeomGroup(p, c) + } + } + +} + +internal class PrstGeom : OOXMLElement { + private var prst: String? = null + private var avLst: AvLst? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (avLst != null) ooxml.append(avLst!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor() { // no-param constructor, set up common defaults + prst = "rect" + avLst = AvLst() + } + + constructor(prst: String, a: AvLst) { + this.prst = prst + this.avLst = a + } + + constructor(p: PrstGeom) { + this.prst = p.prst + this.avLst = p.avLst + } + + override fun cloneElement(): OOXMLElement { + return PrstGeom(this) + } + + companion object { + + private val serialVersionUID = 8327708502983472577L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): PrstGeom { + var prst: String? = null + var a: AvLst? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "prstGeom") { // get attributes + prst = xpp.getAttributeValue(0) + } else if (tnm == "avLst") { + lastTag.push(tnm) + a = AvLst.parseOOXML(xpp, lastTag) as AvLst + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "prstGeom") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("prstGeom.parseOOXML: $e") + } + + return PrstGeom(prst, a) + } + } + +} + + +/** + * custGeom (Custom Geometry) + * This element specifies the existence of a custom geometric shape. This shape will consist of a series of lines and + * curves described within a creation path. In addition to this there may also be adjust values, guides, adjust + * handles, connection sites and an inscribed rectangle specified for this custom geometric shape. + * + * + * parent: soPr + * children: avLst, gdLst, ahLst, cxnLst, rect, pathLst (REQ) + */ +// TODO: Finish child elements ahLst, cxnLst +internal class CustGeom : OOXMLElement { + private var pathLst: PathLst? = null + private var gdLst: GdLst? = null + private var avLst: AvLst? = null + private var cxnLst: CxnLst? = null + private var rect: Rect? = null + + override// avLst + // gdLst + // TODO: ahLst + // cxnLst + // rect + // pathLst + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (avLst != null) ooxml.append(avLst!!.ooxml) + if (gdLst != null) ooxml.append(gdLst!!.ooxml) + ooxml.append("") + if (cxnLst != null) ooxml.append(cxnLst!!.ooxml) + if (rect != null) ooxml.append(rect!!.ooxml) + if (pathLst != null) ooxml.append(pathLst!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(p: PathLst, g: GdLst, a: AvLst, cx: CxnLst, r: Rect) { + this.pathLst = p + this.gdLst = g + this.avLst = a + this.cxnLst = cx + this.rect = r + } + + constructor(c: CustGeom) { + this.pathLst = c.pathLst + this.gdLst = c.gdLst + this.avLst = c.avLst + this.cxnLst = c.cxnLst + this.rect = c.rect + } + + override fun cloneElement(): OOXMLElement { + return CustGeom(this) + } + + companion object { + + private val serialVersionUID = 4036207867619551810L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): CustGeom { + var p: PathLst? = null + var g: GdLst? = null + var a: AvLst? = null + var cx: CxnLst? = null + var r: Rect? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pathLst") { // REQ + lastTag.push(tnm) + p = PathLst.parseOOXML(xpp, lastTag) + } else if (tnm == "gdLst") { + lastTag.push(tnm) + g = GdLst.parseOOXML(xpp, lastTag) + } else if (tnm == "avLst") { + lastTag.push(tnm) + a = AvLst.parseOOXML(xpp, lastTag) as AvLst + } else if (tnm == "cxnLst") { + lastTag.push(tnm) + cx = CxnLst.parseOOXML(xpp, lastTag) + } else if (tnm == "rect") { + lastTag.push(tnm) + r = Rect.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "custGeom") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("custGeom.parseOOXML: $e") + } + + return CustGeom(p, g, a, cx, r) + } + } +} + +/** + * pathLst (List of Shape Paths) + * This element specifies the entire path that is to make up a single geometric shape. The pathLst can consist of + * many individual paths within it. + * + * + * parent: custGeom + * children path (multiple) + */ +internal class PathLst : OOXMLElement { + private var path: ArrayList? = null + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (path != null) { + for (i in path!!.indices) + ooxml.append(path!![i].ooxml) + } + ooxml.append("") + return ooxml.toString() + } + + + constructor(attrs: HashMap, p: ArrayList) { + this.attrs = attrs + this.path = p + } + + constructor(pl: PathLst) { + this.attrs = pl.attrs + this.path = pl.path + } + + override fun cloneElement(): OOXMLElement { + return PathLst(this) + } + + companion object { + + private val serialVersionUID = -1996347204024728000L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): PathLst { + val attrs = HashMap() + val p = ArrayList() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pathLst") { // get attributes + for (i in 0 until xpp.attributeCount) + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } else if (tnm == "path") { // get one or more path children + lastTag.push(tnm) + p.add(Path.parseOOXML(xpp, lastTag)) + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "pathLst") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("pathLst.parseOOXML: $e") + } + + return PathLst(attrs, p) + } + } +} + +/** + * + */ +internal class Path : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(p: Path) { + this.attrs = p.attrs + } + + override fun cloneElement(): OOXMLElement { + return Path(this) + } + + companion object { + + private val serialVersionUID = 6906237439620322589L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): Path { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "path") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "moveTo") { + } else if (tnm == "lnTo") { + } else if (tnm == "close") { + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "path") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("path.parseOOXML: $e") + } + + return Path(attrs) + } + } +} + +/** + * gdLst (List of Shape Guides) + * This element specifies all the guides that will be used for this shape. A guide is specified by the gd element and + * defines a calculated value that may be used for the construction of the corresponding shape. + */ +internal class GdLst : OOXMLElement { + private var gds: ArrayList? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (gds != null) { + for (i in gds!!.indices) + ooxml.append(gds!![i].ooxml) + } + ooxml.append("") + return ooxml.toString() + } + + constructor(gds: ArrayList) { + this.gds = gds + } + + constructor(g: GdLst) { + this.gds = g.gds + } + + override fun cloneElement(): OOXMLElement { + return GdLst(this) + } + + companion object { + + private val serialVersionUID = -7852193131141462744L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): GdLst { + var gds: ArrayList? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "gd") { + lastTag.push(tnm) + if (gds == null) gds = ArrayList() + gds.add(Gd.parseOOXML(xpp, lastTag) as Gd) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "gdLst") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("gdLst.parseOOXML: $e") + } + + return GdLst(gds) + } + } +} + +/** + * ahLst (List of Shape Adjust Handles) + * This element specifies the adjust handles that will be applied to a custom geometry. These adjust handles will + * specify points within the geometric shape that can be used to perform certain transform operations on the + * shape. + * [Example: Consider the scenario where a custom geometry, an arrow in this case, has been drawn and adjust + * handles have been placed at the top left corner of both the arrow head and arrow body. The user interface can + * then be made to transform only certain parts of the shape by using the corresponding adjust handle + * + * + * parent: custGeom + * children: one or more of [ahXY, ahPolar (both REQ)] + */ + + +/** + * rect (Shape Text Rectangle) + * + * This element specifies the rectangular bounding box for text within a custGeom shape. The default for this + * rectangle is the bounding box for the shape. This can be modified using this elements four attributes to inset or + * extend the text bounding box. + * + * parent: custGeom + * children: none + */ +internal class Rect : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(r: Rect) { + this.attrs = r.attrs + } + + override fun cloneElement(): OOXMLElement { + return Rect(this) + } + + companion object { + + private val serialVersionUID = 2790708601254975676L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): Rect { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "rect") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "CHILDELEMENT") { + lastTag.push(tnm) + //layout = (layout) layout.parseOOXML(xpp, lastTag).clone(); + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "rect") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("rect.parseOOXML: $e") + } + + return Rect(attrs) + } + } +} + +/** + * cxnLst (List of Shape Connection Sites) + * This element specifies all the connection sites that will be used for this shape. A connection site is specified by + * defining a point within the shape bounding box that can have a cxnSp element attached to it. These connection + * sites are specified using the shape coordinate system that is specified within the ext transform element. + * + * parents: custGeom + * children: cxn (0 or more) + */ +internal class CxnLst : OOXMLElement { + private var cxns: ArrayList? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (cxns != null) { + for (i in cxns!!.indices) + ooxml.append(cxns!![i].ooxml) + } + ooxml.append("") + return ooxml.toString() + } + + constructor(cxns: ArrayList) { + this.cxns = cxns + } + + constructor(c: CxnLst) { + this.cxns = c.cxns + } + + override fun cloneElement(): OOXMLElement { + return CxnLst(this) + } + + companion object { + + private val serialVersionUID = -562847539163221621L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): CxnLst { + var cxns: ArrayList? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cxn") { + lastTag.push(tnm) + if (cxns == null) cxns = ArrayList() + cxns.add(Cxn.parseOOXML(xpp, lastTag)) + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "cxnLst") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("cxnLst.parseOOXML: $e") + } + + return CxnLst(cxns) + } + } +} + + +/** + * cxn (Shape Connection Site) + * This element specifies the existence of a connection site on a custom shape. A connection site allows a cxnSp to + * be attached to this shape. This connection will be maintined when the shape is repositioned within the + * document. It should be noted that this connection is placed within the shape bounding box using the transform + * coordinate system which is also called the shape coordinate system, as it encompasses theentire shape. The + * width and height for this coordinate system are specified within the ext transform element. + * + * parents: cxnLst + * children: pos REQ + */ +internal class Cxn : OOXMLElement { + private var attrs: HashMap? = null + private var pos: Pos? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (pos != null) ooxml.append(pos!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap, p: Pos) { + this.attrs = attrs + this.pos = p + } + + constructor(c: Cxn) { + this.attrs = c.attrs + this.pos = c.pos + } + + override fun cloneElement(): OOXMLElement { + return Cxn(this) + } + + companion object { + + private val serialVersionUID = -4193511102420582252L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): Cxn { + val attrs = HashMap() + var p: Pos? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cxn") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) // ang REQ + } + } else if (tnm == "pos") { + lastTag.push(tnm) + p = Pos.parseOOXML(xpp, lastTag) + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "cxn") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("cxn.parseOOXML: $e") + } + + return Cxn(attrs, p) + } + } +} + + +/** + * pos (Shape Position Coordinate) + * Specifies a position coordinate within the shape bounding box. It should be noted that this coordinate is placed + * within the shape bounding box using the transform coordinate system which is also called the shape coordinate + * system, as it encompasses the entire shape. The width and height for this coordinate system are specified within + * the ext transform element. + * + * parents: cxn, ahPolar, ahXY + * children: none + */ +internal class Pos : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(p: Pos) { + this.attrs = p.attrs + } + + override fun cloneElement(): OOXMLElement { + return Pos(this) + } + + companion object { + + private val serialVersionUID = 5500991309750603125L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): Pos { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pos") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "pos") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("pos.parseOOXML: $e") + } + + return Pos(attrs) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Graphic.java b/src/main/java/io/starter/formats/OOXML/Graphic.java deleted file mode 100644 index 9af0c01..0000000 --- a/src/main/java/io/starter/formats/OOXML/Graphic.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * graphic (Graphic Object) - * This element specifies the existence of a single graphic object. Document authors should refer to this element - * when they wish to persist a graphical object of some kind. The specification for this graphical object will be - * provided entirely by the document author and referenced within the graphicData child element - *

                  - * parent: anchor, graphicFrame, inline - * children: graphicData - */ -public class Graphic implements OOXMLElement { - - private static final long serialVersionUID = -7027946026352255398L; - private GraphicData graphicData = new GraphicData(); - - public Graphic() { - } - - public Graphic(GraphicData g) { - this.graphicData = g; - } - - public Graphic(Graphic gr) { - this.graphicData = gr.graphicData; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - GraphicData g = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("graphicData")) { - lastTag.push(tnm); - g = GraphicData.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("graphic")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("graphic.parseOOXML: " + e.toString()); - } - Graphic gr = new Graphic(g); - return gr; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (graphicData != null) ooxml.append(graphicData.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Graphic(this); - } - - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (graphicData != null) - return graphicData.getURI(); - return null; - } - - public void setURI(String uri) { - if (graphicData != null) - graphicData.setURI(uri); - } - - /** - * return the rid of the chart element, if exists - * - * @return - */ - public String getChartRId() { - if (graphicData != null) - return graphicData.getChartRId(); - return null; - } - - /** - * set the rid of the chart element - */ - public void setChartRId(String rid) { - if (graphicData != null) graphicData.setChartRId(rid); - } -} - -/** - * graphicData (Graphic Object Data) - * This element specifies the reference to a graphic object within the document. This graphic object is provided - * entirely by the document authors who choose to persist this data within the document. - *

                  - * parent: graphic - * children: chart ... anything else? - */ -class GraphicData implements OOXMLElement { - - private static final long serialVersionUID = 7395991759307532325L; - private String uri = OOXMLConstants.chartns; //xmlns:r=\"" + OOXMLConstants.relns + "\""; // default - private String rid = null; - - public GraphicData() { - } - - public GraphicData(String uri, String rid) { - this.uri = uri; - this.rid = rid; - } - - public GraphicData(GraphicData gd) { - this.uri = gd.uri; - this.rid = gd.rid; - } - - public static GraphicData parseOOXML(XmlPullParser xpp, Stack lastTag) { - String uri = null; - String rid = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("graphicData")) { // get attributes - if (xpp.getAttributeCount() > 0) - uri = xpp.getAttributeValue(0); - } else if (tnm.equals("chart")) { // one of many possible children - if (xpp.getAttributeCount() > 0) - rid = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("graphicData")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("graphicData.parseOOXML: " + e.toString()); - } - GraphicData g = new GraphicData(uri, rid); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - } else - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GraphicData(this); - } - - /** - * return the URI attribute associated with this graphic Data - * - * @return - */ - public String getURI() { - return uri; - } - - /** - * set the URI attribute for this graphic data - * - * @param uri - */ - public void setURI(String uri) { - this.uri = uri; - } - - /** - * return the rid of the chart element, if exists - * - * @return - */ - public String getChartRId() { - return rid; - } - - /** - * set the rid of the chart element - */ - public void setChartRId(String rid) { - this.rid = rid; - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Graphic.kt b/src/main/java/io/starter/formats/OOXML/Graphic.kt new file mode 100644 index 0000000..f6eec5a --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Graphic.kt @@ -0,0 +1,221 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * graphic (Graphic Object) + * This element specifies the existence of a single graphic object. Document authors should refer to this element + * when they wish to persist a graphical object of some kind. The specification for this graphical object will be + * provided entirely by the document author and referenced within the graphicData child element + * + * + * parent: anchor, graphicFrame, inline + * children: graphicData + */ +class Graphic : OOXMLElement { + private var graphicData: GraphicData? = GraphicData() + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (graphicData != null) ooxml.append(graphicData!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * get the URI associated with this graphic Data + */ + var uri: String? + get() = if (graphicData != null) graphicData!!.uri else null + set(uri) { + if (graphicData != null) + graphicData!!.uri = uri + } + + /** + * return the rid of the chart element, if exists + * + * @return + */ + /** + * set the rid of the chart element + */ + var chartRId: String? + get() = if (graphicData != null) graphicData!!.chartRId else null + set(rid) { + if (graphicData != null) graphicData!!.chartRId = rid + } + + constructor() {} + + constructor(g: GraphicData) { + this.graphicData = g + } + + constructor(gr: Graphic) { + this.graphicData = gr.graphicData + } + + override fun cloneElement(): OOXMLElement { + return Graphic(this) + } + + companion object { + + private val serialVersionUID = -7027946026352255398L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + var g: GraphicData? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "graphicData") { + lastTag.push(tnm) + g = GraphicData.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "graphic") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("graphic.parseOOXML: $e") + } + + return Graphic(g) + } + } +} + +/** + * graphicData (Graphic Object Data) + * This element specifies the reference to a graphic object within the document. This graphic object is provided + * entirely by the document authors who choose to persist this data within the document. + * + * + * parent: graphic + * children: chart ... anything else? + */ +internal class GraphicData : OOXMLElement { + /** + * return the URI attribute associated with this graphic Data + * + * @return + */ + /** + * set the URI attribute for this graphic data + * + * @param uri + */ + var uri: String? = OOXMLConstants.chartns //xmlns:r=\"" + OOXMLConstants.relns + "\""; // default + /** + * return the rid of the chart element, if exists + * + * @return + */ + /** + * set the rid of the chart element + */ + var chartRId: String? = null + + override// we'll assume it's a chart for nwo + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + } else + ooxml.append("/>") + return ooxml.toString() + } + + constructor() {} + + constructor(uri: String, rid: String) { + this.uri = uri + this.chartRId = rid + } + + constructor(gd: GraphicData) { + this.uri = gd.uri + this.chartRId = gd.chartRId + } + + override fun cloneElement(): OOXMLElement { + return GraphicData(this) + } + + companion object { + + private val serialVersionUID = 7395991759307532325L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack<*>): GraphicData { + var uri: String? = null + var rid: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "graphicData") { // get attributes + if (xpp.attributeCount > 0) + uri = xpp.getAttributeValue(0) + } else if (tnm == "chart") { // one of many possible children + if (xpp.attributeCount > 0) + rid = xpp.getAttributeValue(0) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "graphicData") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("graphicData.parseOOXML: $e") + } + + return GraphicData(uri, rid) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/GraphicFrame.java b/src/main/java/io/starter/formats/OOXML/GraphicFrame.java deleted file mode 100644 index 3d38bcb..0000000 --- a/src/main/java/io/starter/formats/OOXML/GraphicFrame.java +++ /dev/null @@ -1,458 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * graphicFrame (Graphic Frame) - * This element describes a single graphical object frame for a spreadsheet which contains a graphical object - *

                  - * parent: oneCellAnchor, twoCellAnchor, absoluteAnchor, grpSp - * children: graphic, nvGraphicFramePr, xfrm (all required and in sequence) - */ -//TODO: finish cNvGraphicFramePr.graphicFrameLocks element -public class GraphicFrame implements OOXMLElement { - - private static final long serialVersionUID = 2494490998000511917L; - private HashMap attrs = new HashMap(); - private Graphic graphic = new Graphic(); - - private NvGraphicFramePr graphicFramePr = new NvGraphicFramePr(); - private Xfrm xfrm = new Xfrm(); - - public GraphicFrame() { - attrs.put("macro", ""); - } - - public GraphicFrame(HashMap attrs, Graphic g, NvGraphicFramePr gfp, Xfrm x) { - this.attrs = attrs; - this.graphic = g; - this.graphicFramePr = gfp; - this.xfrm = x; - } - - public GraphicFrame(GraphicFrame gf) { - this.attrs = gf.attrs; - this.graphic = gf.graphic; - this.graphicFramePr = gf.graphicFramePr; - this.xfrm = gf.xfrm; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - Graphic g = null; - NvGraphicFramePr gfp = null; - Xfrm x = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("graphicFrame")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("xfrm")) { - lastTag.push(tnm); - x = (Xfrm) Xfrm.parseOOXML(xpp, lastTag); - x.setNS("xdr"); - } else if (tnm.equals("graphic")) { - lastTag.push(tnm); - g = (Graphic) Graphic.parseOOXML(xpp, lastTag); - } else if (tnm.equals("nvGraphicFramePr")) { - lastTag.push(tnm); - gfp = NvGraphicFramePr.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("graphicFrame")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("graphicFrame.parseOOXML: " + e.toString()); - } - GraphicFrame gf = new GraphicFrame(attrs, g, gfp, x); - return gf; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - // all are required so no null checks - must ensure x.ns is set - ooxml.append(graphicFramePr.getOOXML()); - ooxml.append(xfrm.getOOXML()); - ooxml.append(graphic.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GraphicFrame(this); - } - - /** - * get graphicFrame Macro attribute - */ - public String getMacro() { - if (attrs.get("macro") != null) - return attrs.get("macro"); - return null; - } - - /** - * set graphicFrame Macro attribute - * - * @param macro - */ - public void setMacro(String macro) { - attrs.put("macro", macro); - } - - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (graphic != null) - return graphic.getURI(); - return null; - } - - /** - * set the URI associated with this graphic data - * - * @param uri - */ - public void setURI(String uri) { - if (graphic != null) - graphic.setURI(uri); - } - - /** - * return the rid of the chart element, if exists - * - * @return - */ - public String getChartRId() { - if (graphic != null) - return graphic.getChartRId(); - return null; - } - - /** - * set the rid of the chart element - */ - public void setChartRId(String rid) { - if (graphic != null) graphic.setChartRId(rid); - } - - /** - * get the cNvPr name attribute - * - * @return - */ - public String getName() { - if (graphicFramePr != null) - return graphicFramePr.getName(); - return null; - } - - /** - * set cNvPr name attribute - * - * @param name - */ - public void setName(String name) { - if (graphicFramePr != null) - graphicFramePr.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (graphicFramePr != null) - return graphicFramePr.getDescr(); - return null; - } - - /** - * set cNvPr description attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setDescr(String descr) { - if (graphicFramePr != null) - graphicFramePr.setDescr(descr); - } - - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (graphicFramePr != null) - graphicFramePr.setId(id); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (graphicFramePr != null) - return graphicFramePr.getId(); - return -1; - } -} - -/** - * nvGraphicFramePr (Non-Visual Properties for a Graphic Frame) - * This element specifies all non-visual properties for a graphic frame. This element is a container for the non-visual - * identification properties, shape properties and application properties that are to be associated with a graphic - * frame. This allows for additional information that does not affect the appearance of the graphic frame to be - * stored. - *

                  - * parent: graphicFrame - * children: cNvPr REQ cNvGraphicFramePr REQ - */ -class NvGraphicFramePr implements OOXMLElement { - private static final long serialVersionUID = -47476384268955296L; - private CNvPr cp = new CNvPr(); - private CNvGraphicFramePr nvpr = new CNvGraphicFramePr(); - - public NvGraphicFramePr() { - } - - public NvGraphicFramePr(CNvPr cp, CNvGraphicFramePr nvpr) { - this.cp = cp; - this.nvpr = nvpr; - } - - public NvGraphicFramePr(NvGraphicFramePr nvg) { - this.cp = nvg.cp; - this.nvpr = nvg.nvpr; - } - - - public static NvGraphicFramePr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cp = null; - CNvGraphicFramePr nvpr = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvGraphicFramePr")) { - lastTag.push(tnm); - nvpr = CNvGraphicFramePr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cp = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("nvGraphicFramePr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("nvGraphicFramePr.parseOOXML: " + e.toString()); - } - NvGraphicFramePr gfp = new NvGraphicFramePr(cp, nvpr); - return gfp; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (cp != null) ooxml.append(cp.getOOXML()); - if (nvpr != null) ooxml.append(nvpr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NvGraphicFramePr(this); - } - - /** - * get cNvPr name attribute - * - * @return - */ - public String getName() { - if (cp != null) - return cp.getName(); - return null; - } - - /** - * set cNvPr name attribute - * - * @param name - */ - public void setName(String name) { - if (cp != null) - cp.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (cp != null) - return cp.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * - * @param name - */ - public void setDescr(String descr) { - if (cp != null) - cp.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (cp != null) - cp.setId(id); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (cp != null) - return cp.getId(); - return -1; - } - - -} - -/** - * cNvGraphicFramePr (Non-Visual Graphic Frame Drawing Properties) - *

                  - * This element specifies the non-visual properties for a single graphical object frame within a spreadsheet. These - * are the set of properties of a frame which do not affect its display within a spreadsheet - *

                  - * parent: nvGraphicFramePr - * children: graphicFrameLocks - */ -// TODO: finish graphicFrameLocks -class CNvGraphicFramePr implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 769474804434194488L; - - // private graphicFrameLocks gf= null; - public CNvGraphicFramePr() { - } - - public CNvGraphicFramePr(CNvGraphicFramePr g) { - } - - - public static CNvGraphicFramePr parseOOXML(XmlPullParser xpp, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { -/* String tnm = xpp.getName(); - if (tnm.equals("graphicFrameLocks")) { - lastTag.push(tnm); - //gf= (graphicFrameLocks) graphicFrameLocks.parseOOXML(xpp, lastTag).clone(); - } -*/ - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("cNvGraphicFramePr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cNvGraphicFramePr.parseOOXML: " + e.toString()); - } - CNvGraphicFramePr cpr = new CNvGraphicFramePr(); - return cpr; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - // TODO: Finish child graphicFrameLocks - //if (gf!=null) ooxml.append(gf.getOOXML()); - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvGraphicFramePr(this); - } - -} - diff --git a/src/main/java/io/starter/formats/OOXML/GraphicFrame.kt b/src/main/java/io/starter/formats/OOXML/GraphicFrame.kt new file mode 100644 index 0000000..674b2a2 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/GraphicFrame.kt @@ -0,0 +1,433 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * graphicFrame (Graphic Frame) + * This element describes a single graphical object frame for a spreadsheet which contains a graphical object + * + * + * parent: oneCellAnchor, twoCellAnchor, absoluteAnchor, grpSp + * children: graphic, nvGraphicFramePr, xfrm (all required and in sequence) + */ +//TODO: finish cNvGraphicFramePr.graphicFrameLocks element +class GraphicFrame : OOXMLElement { + private var attrs = HashMap() + private var graphic: Graphic? = Graphic() + + private var graphicFramePr: NvGraphicFramePr? = NvGraphicFramePr() + private var xfrm = Xfrm() + + override// attributes + // all are required so no null checks - must ensure x.ns is set + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append(graphicFramePr!!.ooxml) + ooxml.append(xfrm.ooxml) + ooxml.append(graphic!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * get graphicFrame Macro attribute + */ + /** + * set graphicFrame Macro attribute + * + * @param macro + */ + var macro: String? + get() = if (attrs["macro"] != null) attrs["macro"] else null + set(macro) { + attrs["macro"] = macro + } + + /** + * get the URI associated with this graphic Data + */ + /** + * set the URI associated with this graphic data + * + * @param uri + */ + var uri: String? + get() = if (graphic != null) graphic!!.uri else null + set(uri) { + if (graphic != null) + graphic!!.uri = uri + } + + /** + * return the rid of the chart element, if exists + * + * @return + */ + /** + * set the rid of the chart element + */ + var chartRId: String? + get() = if (graphic != null) graphic!!.chartRId else null + set(rid) { + if (graphic != null) graphic!!.chartRId = rid + } + + /** + * get the cNvPr name attribute + * + * @return + */ + /** + * set cNvPr name attribute + * + * @param name + */ + var name: String? + get() = if (graphicFramePr != null) graphicFramePr!!.name else null + set(name) { + if (graphicFramePr != null) + graphicFramePr!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + var descr: String? + get() = if (graphicFramePr != null) graphicFramePr!!.descr else null + set(descr) { + if (graphicFramePr != null) + graphicFramePr!!.descr = descr + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + var id: Int + get() = if (graphicFramePr != null) graphicFramePr!!.id else -1 + set(id) { + if (graphicFramePr != null) + graphicFramePr!!.id = id + } + + constructor() { + attrs["macro"] = "" + } + + constructor(attrs: HashMap, g: Graphic, gfp: NvGraphicFramePr, x: Xfrm) { + this.attrs = attrs + this.graphic = g + this.graphicFramePr = gfp + this.xfrm = x + } + + constructor(gf: GraphicFrame) { + this.attrs = gf.attrs + this.graphic = gf.graphic + this.graphicFramePr = gf.graphicFramePr + this.xfrm = gf.xfrm + } + + override fun cloneElement(): OOXMLElement { + return GraphicFrame(this) + } + + companion object { + + private val serialVersionUID = 2494490998000511917L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + val attrs = HashMap() + var g: Graphic? = null + var gfp: NvGraphicFramePr? = null + var x: Xfrm? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "graphicFrame") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "xfrm") { + lastTag.push(tnm) + x = Xfrm.parseOOXML(xpp, lastTag) as Xfrm + x.setNS("xdr") + } else if (tnm == "graphic") { + lastTag.push(tnm) + g = Graphic.parseOOXML(xpp, lastTag) as Graphic + } else if (tnm == "nvGraphicFramePr") { + lastTag.push(tnm) + gfp = NvGraphicFramePr.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "graphicFrame") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("graphicFrame.parseOOXML: $e") + } + + return GraphicFrame(attrs, g, gfp, x) + } + } +} + +/** + * nvGraphicFramePr (Non-Visual Properties for a Graphic Frame) + * This element specifies all non-visual properties for a graphic frame. This element is a container for the non-visual + * identification properties, shape properties and application properties that are to be associated with a graphic + * frame. This allows for additional information that does not affect the appearance of the graphic frame to be + * stored. + * + * + * parent: graphicFrame + * children: cNvPr REQ cNvGraphicFramePr REQ + */ +internal class NvGraphicFramePr : OOXMLElement { + private var cp: CNvPr? = CNvPr() + private var nvpr: CNvGraphicFramePr? = CNvGraphicFramePr() + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (cp != null) ooxml.append(cp!!.ooxml) + if (nvpr != null) ooxml.append(nvpr!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * get cNvPr name attribute + * + * @return + */ + /** + * set cNvPr name attribute + * + * @param name + */ + var name: String? + get() = if (cp != null) cp!!.name else null + set(name) { + if (cp != null) + cp!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr desc attribute + * + * @param name + */ + var descr: String? + get() = if (cp != null) cp!!.descr else null + set(descr) { + if (cp != null) + cp!!.descr = descr + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + var id: Int + get() = if (cp != null) cp!!.id else -1 + set(id) { + if (cp != null) + cp!!.id = id + } + + constructor() {} + + constructor(cp: CNvPr, nvpr: CNvGraphicFramePr) { + this.cp = cp + this.nvpr = nvpr + } + + constructor(nvg: NvGraphicFramePr) { + this.cp = nvg.cp + this.nvpr = nvg.nvpr + } + + override fun cloneElement(): OOXMLElement { + return NvGraphicFramePr(this) + } + + companion object { + private val serialVersionUID = -47476384268955296L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): NvGraphicFramePr { + var cp: CNvPr? = null + var nvpr: CNvGraphicFramePr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cNvGraphicFramePr") { + lastTag.push(tnm) + nvpr = CNvGraphicFramePr.parseOOXML(xpp, lastTag) + } else if (tnm == "cNvPr") { + lastTag.push(tnm) + cp = CNvPr.parseOOXML(xpp, lastTag) as CNvPr + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "nvGraphicFramePr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("nvGraphicFramePr.parseOOXML: $e") + } + + return NvGraphicFramePr(cp, nvpr) + } + } + + +} + +/** + * cNvGraphicFramePr (Non-Visual Graphic Frame Drawing Properties) + * + * + * This element specifies the non-visual properties for a single graphical object frame within a spreadsheet. These + * are the set of properties of a frame which do not affect its display within a spreadsheet + * + * + * parent: nvGraphicFramePr + * children: graphicFrameLocks + */ +// TODO: finish graphicFrameLocks +internal class CNvGraphicFramePr : OOXMLElement { + + override// TODO: Finish child graphicFrameLocks + //if (gf!=null) ooxml.append(gf.getOOXML()); + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + // private graphicFrameLocks gf= null; + constructor() {} + + constructor(g: CNvGraphicFramePr) {} + + override fun cloneElement(): OOXMLElement { + return CNvGraphicFramePr(this) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 769474804434194488L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): CNvGraphicFramePr { + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + /* String tnm = xpp.getName(); + if (tnm.equals("graphicFrameLocks")) { + lastTag.push(tnm); + //gf= (graphicFrameLocks) graphicFrameLocks.parseOOXML(xpp, lastTag).clone(); + } +*/ + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "cNvGraphicFramePr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("cNvGraphicFramePr.parseOOXML: $e") + } + + return CNvGraphicFramePr() + } + } + +} + diff --git a/src/main/java/io/starter/formats/OOXML/GrpSp.java b/src/main/java/io/starter/formats/OOXML/GrpSp.java deleted file mode 100644 index f8a74ae..0000000 --- a/src/main/java/io/starter/formats/OOXML/GrpSp.java +++ /dev/null @@ -1,825 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * grpSp (Group Shape) - * This element specifies a group shape that represents many shapes grouped together. - * Within a group shape 1 each of the shapes that make up the group are - * specified just as they normally would. The idea behind grouping elements however is that a - * single transform can apply to many shapes at the same time. - *

                  - * parents: absoluteAnchor, grpSp, oneCellAnchor, twoCellAnchor - * children: nvGrpSpPr (req), grpSpPr (req), choice of: (sp, grpSp, graphicFrame, cxnSp, pic) 0 to unbounded - */ -public class GrpSp implements OOXMLElement { - - private static final long serialVersionUID = -3276180769601314853L; - private NvGrpSpPr nvpr = null; - private GrpSpPr sppr = null; - private ArrayList choice = null; - - public GrpSp(NvGrpSpPr nvpr, GrpSpPr sppr, ArrayList choice) { - this.nvpr = nvpr; - this.sppr = sppr; - this.choice = choice; - } - - public GrpSp(GrpSp g) { - this.nvpr = g.nvpr; - this.sppr = g.sppr; - this.choice = g.choice; - } - - /** - * parse grpSp element OOXML - * - * @param xpp - * @param lastTag - * @return - */ - public static GrpSp parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - NvGrpSpPr nvpr = null; - GrpSpPr sppr = null; - ArrayList choice = new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("nvGrpSpPr")) { - lastTag.push(tnm); - nvpr = NvGrpSpPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("grpSpPr")) { - lastTag.push(tnm); - sppr = GrpSpPr.parseOOXML(xpp, lastTag, bk); - // choice of: 0 or more below: - } else if (tnm.equals("sp")) { - lastTag.push(tnm); - choice.add(Sp.parseOOXML(xpp, lastTag, bk)); - } else if (tnm.equals("grpSp")) { - if (nvpr != null) { // if not the initial start attribute, this is a child - lastTag.push(tnm); - choice.add(GrpSp.parseOOXML(xpp, lastTag, bk)); - } - } else if (tnm.equals("graphicFrame")) { - lastTag.push(tnm); - choice.add(GraphicFrame.parseOOXML(xpp, lastTag)); - } else if (tnm.equals("cxnSp")) { - lastTag.push(tnm); - choice.add(CxnSp.parseOOXML(xpp, lastTag, bk)); - } else if (tnm.equals("pic")) { - lastTag.push(tnm); - choice.add(Pic.parseOOXML(xpp, lastTag, bk)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("grpSp")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("GrpSp.parseOOXML: " + e.toString()); - } - GrpSp gf = new GrpSp(nvpr, sppr, choice); - return gf; - } - - /** - * return grpSp element OOXML - */ - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append(nvpr.getOOXML()); - ooxml.append(sppr.getOOXML()); - if (choice != null) { - for (int i = 0; i < choice.size(); i++) { - ooxml.append(choice.get(i).getOOXML()); - } - } - ooxml.append(""); - return ooxml.toString(); - } - - static int SP = 0; - static int PIC = 1; - static int CXN = 2; - static int GRAPHICFRAME = 3; - - private OOXMLElement getObject(int type) { - if (choice != null) { - for (int i = 0; i < choice.size(); i++) { - OOXMLElement oe = choice.get(i); - if (oe instanceof Sp && type == SP) - return oe; - else if (oe instanceof Pic && type == PIC) - return oe; - else if (oe instanceof CxnSp && type == CXN) - return oe; - else if (oe instanceof GraphicFrame && type == GRAPHICFRAME) - return oe; - else if (oe instanceof GrpSp) - return ((GrpSp) oe).getObject(type); - - } - } - return null; - } - - /** - * get name attribute of the group shape - * - * @return - */ - public String getName() { - return nvpr.getName(); - } - - /** - * set name attribute of the group shape - * - * @param name - */ - public void setName(String name) { - nvpr.setName(name); // set the group name - } - - /** - * get macro attribute of the group shape - * - * @return - */ - public String getMacro() { - String macro = null; - // Have a shape with an embed? - OOXMLElement oe = getObject(SP); - if (oe != null) - macro = ((Sp) oe).getMacro(); - // how's about a picture? - if (macro == null) - oe = getObject(PIC); - if (oe != null && macro == null) - macro = ((Pic) oe).getMacro(); - // or a connection shape? - if (macro == null) - oe = getObject(CXN); - if (oe != null && macro == null) - macro = ((CxnSp) oe).getMacro(); - return macro; - } - - /** - * set macro attribute of the group shape - * - * @param name - */ - public void setMacro(String macro) { - // Have a shape with a macro? - String m = null; - OOXMLElement oe = getObject(SP); - if (oe != null) - m = ((Sp) oe).getMacro(); - if (m != null) { - ((Sp) oe).setMacro(macro); - return; - } - // how's about a picture? - oe = getObject(PIC); - if (oe != null) - m = ((Pic) oe).getMacro(); - if (m != null) { - ((Pic) oe).setMacro(macro); - return; - } - // or a connection shape? - oe = getObject(CXN); - if (oe != null) - m = ((CxnSp) oe).getMacro(); - if (m != null) - ((CxnSp) oe).setMacro(macro); - } - - - /** - * get cthe descr attribute of the group shape - * - * @return - */ - public String getDescr() { - return nvpr.getDescr(); - } - - /** - * set description attribute of the group shape - * sometimes associated with shape name - * - * @param descr - */ - public void setDescr(String descr) { - nvpr.setDescr(descr); - } - - /** - * return the rid for the embedded object (picture or chart or picture shape) (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - String embed = null; - // Have a shape with an embed? - OOXMLElement oe = getObject(SP); - if (oe != null) - embed = ((Sp) oe).getEmbed(); - // how's about a picture? - if (embed == null) - oe = getObject(PIC); - if (oe != null && embed == null) - embed = ((Pic) oe).getEmbed(); - // or a connection shape? - if (embed == null) - oe = getObject(CXN); - if (oe != null && embed == null) - embed = ((CxnSp) oe).getEmbed(); - return embed; - } - - /** - * set the rid for this object (picture or chart or picture shape) (resides within the file) - * - * @param embed - */ - public void setEmbed(String embed) { - // Have a shape with an embed? - String e = null; - OOXMLElement oe = getObject(SP); - if (oe != null) - e = ((Sp) oe).getEmbed(); - if (e != null) { - ((Sp) oe).setEmbed(embed); - return; - } - // how's about a picture? - oe = getObject(PIC); - if (oe != null) - e = ((Pic) oe).getEmbed(); - if (e != null) { - ((Pic) oe).setEmbed(embed); - return; - } - // or a connection shape? - oe = getObject(CXN); - if (oe != null) - e = ((CxnSp) oe).getEmbed(); - if (e != null) - ((CxnSp) oe).setEmbed(embed); - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - String link = null; - // Have a shape with an embed? - OOXMLElement oe = getObject(SP); - if (oe != null) - link = ((Sp) oe).getLink(); - // how's about a picture? - if (link == null) - oe = getObject(PIC); - if (oe != null && link == null) - link = ((Pic) oe).getLink(); - // or a connection shape? - if (link == null) - oe = getObject(CXN); - if (oe != null && link == null) - link = ((CxnSp) oe).getLink(); - return link; - } - - /** - * rset the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public void setLink(String link) { - String l = null; - // Have a shape with an embed? - OOXMLElement oe = getObject(SP); - if (oe != null) - l = ((Sp) oe).getLink(); - if (l != null) { - ((Sp) oe).setLink(link); - return; - } - // how's about a picture? - oe = getObject(PIC); - if (oe != null) - l = ((Pic) oe).getLink(); - if (l != null) { - ((Pic) oe).setLink(link); - return; - } - // or a connection shape? - oe = getObject(CXN); - if (oe != null) - l = ((CxnSp) oe).getLink(); - if (l != null) - ((CxnSp) oe).setLink(link); - } - - /** - * return the rid of the chart element, if exists - * - * @return - */ - public String getChartRId() { - OOXMLElement oe = getObject(GRAPHICFRAME); - if (oe != null) - return ((GraphicFrame) oe).getChartRId(); - return null; - } - - /** - * set the URI associated with this graphic data - * - * @param uri - */ - public void setURI(String uri) { - OOXMLElement oe = getObject(GRAPHICFRAME); - ((GraphicFrame) oe).setURI(uri); - } - - /** - * utility to return the shape properties element for this picture - * should be depreciated when OOXML is completely distinct from BIFF8 - * - * @return - */ - public SpPr getSppr() { - OOXMLElement oe = getObject(PIC); - if (oe != null) - return ((Pic) oe).getSppr(); - return null; - - } - - /** - * return if this Group refers to a shape, as opposed a chart or an image - * - * @return - */ - public boolean hasShape() { - return (getObject(SP) != null || getObject(CXN) != null); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - - if (choice != null) { - // get the first?? - OOXMLElement oe = choice.get(0); - if (oe instanceof Sp) - return ((Sp) oe).getId(); - else if (oe instanceof Pic) - return ((Pic) oe).getId(); - else if (oe instanceof GraphicFrame) - return ((GraphicFrame) oe).getId(); - else if (oe instanceof CxnSp) - return ((CxnSp) oe).getId(); - else if (oe instanceof GrpSp) - return ((GrpSp) oe).getId(); - } - return -1; - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (choice != null) { - // get the first?? - OOXMLElement oe = choice.get(0); - if (oe instanceof Sp) - ((Sp) oe).setId(id); - else if (oe instanceof Pic) - ((Pic) oe).setId(id); - else if (oe instanceof GraphicFrame) - ((GraphicFrame) oe).setId(id); - else if (oe instanceof CxnSp) - ((CxnSp) oe).setId(id); - else if (oe instanceof GrpSp) - ((GrpSp) oe).setId(id); - } - } - - public OOXMLElement cloneElement() { - return new GrpSp(this); - } -} - -/** - * nvGrpSpPr 1 (Non-Visual Properties for a Group Shape) - */ -class NvGrpSpPr implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4404072268706949318L; - CNvPr cp = null; - CNvGrpSpPr cgrpsppr = null; - - public NvGrpSpPr(CNvPr cp, CNvGrpSpPr cgrpsppr) { - this.cp = cp; - this.cgrpsppr = cgrpsppr; - } - - public NvGrpSpPr(NvGrpSpPr g) { - this.cp = g.cp; - this.cgrpsppr = g.cgrpsppr; - } - - public static NvGrpSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cp = null; - CNvGrpSpPr cgrpsppr = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cp = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cNvGrpSpPr")) { - lastTag.push(tnm); - cgrpsppr = CNvGrpSpPr.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("nvGrpSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("NvGrpSpPr.parseOOXML: " + e.toString()); - } - NvGrpSpPr grpsppr = new NvGrpSpPr(cp, cgrpsppr); - return grpsppr; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append(cp.getOOXML()); - ooxml.append(cgrpsppr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NvGrpSpPr(this); - } - - /** - * get cNvPr name attribute - * - * @return - */ - public String getName() { - if (cp != null) - return cp.getName(); - return null; - } - - /** - * set cNvPr name attribute - * - * @param name - */ - public void setName(String name) { - if (cp != null) - cp.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (cp != null) - return cp.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * - * @param name - */ - public void setDescr(String descr) { - if (cp != null) - cp.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (cp != null) - cp.setId(id); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (cp != null) - return cp.getId(); - return -1; - } - - -} - -/** - * grpSpPr (Group Shape Properties) - * This element specifies the properties that are to be common across all of the shapes - * within the corresponding group. If there are any conflicting properties within the - * group shape properties and the individual shape properties then the individual shape properties - * should take precedence. - *

                  - * children: xfrm, fillproperties (group), effectproperties (group), scene3d, extLst (all optional) - */ -// TODO: FINISH scene3d, extLst -class GrpSpPr implements OOXMLElement { - - private static final long serialVersionUID = 7464871024304781512L; - private Xfrm xf = null; - private String bwmode = null; - private FillGroup fill = null; - private EffectPropsGroup effect = null; - - public GrpSpPr(Xfrm xf, String bwmode, FillGroup fill, EffectPropsGroup effect) { - this.xf = xf; - this.bwmode = bwmode; - this.fill = fill; - this.effect = effect; - } - - public GrpSpPr(GrpSpPr g) { - this.xf = g.xf; - this.bwmode = g.bwmode; - this.fill = g.fill; - this.effect = g.effect; - } - - public static GrpSpPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - Xfrm xf = null; - String bwmode = null; - FillGroup fill = null; - EffectPropsGroup effect = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("grpSpPr")) { // get attributes - if (xpp.getAttributeCount() == 1) { - bwmode = xpp.getAttributeValue(0); - } - } else if (tnm.equals("xfrm")) { - lastTag.push(tnm); - xf = (Xfrm) Xfrm.parseOOXML(xpp, lastTag); - // scene3d, extLst- finish - } else if ( - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("grpSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("GrpSpPr.parseOOXML: " + e.toString()); - } - GrpSpPr g = new GrpSpPr(xf, bwmode, fill, effect); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (xf != null) ooxml.append(xf.getOOXML()); - if (fill != null) ooxml.append(fill.getOOXML()); - if (effect != null) ooxml.append(effect.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GrpSpPr(this); - } -} - -/** - * cNvGrpSpPr (Non-Visual Group Shape Drawing Properties) - *

                  - * parent: nvGrpSpPr - * children: grpSpLocks, extLst - */ -class CNvGrpSpPr implements OOXMLElement { - - private static final long serialVersionUID = -1106010927060582127L; - private GrpSpLocks gsl = null; - - public CNvGrpSpPr(GrpSpLocks gsl) { - this.gsl = gsl; - } - - public CNvGrpSpPr(CNvGrpSpPr g) { - this.gsl = g.gsl; - } - - - public static CNvGrpSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - GrpSpLocks gsl = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("grpSpLocks")) { - lastTag.push(tnm); - gsl = GrpSpLocks.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("cNvGrpSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("CNvGrpSpPr.parseOOXML: " + e.toString()); - } - CNvGrpSpPr c = new CNvGrpSpPr(gsl); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (gsl != null) - ooxml.append(gsl.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvGrpSpPr(this); - } -} - - -/** - * grpSpLocks (Group Shape Locks) - * This element specifies all locking properties for a connection shape. These properties inform the generating - * application about specific properties that have been previously locked and thus should not be changed. - *

                  - * parent: cNvGrpSpPr - * attributes: many, all optional - * children: extLst - */ -class GrpSpLocks implements OOXMLElement { - - private static final long serialVersionUID = -2592038952923879415L; - private HashMap attrs; - - public GrpSpLocks(HashMap attrs) { - this.attrs = attrs; - } - - public GrpSpLocks(GrpSpLocks g) { - this.attrs = g.attrs; - } - - - public static GrpSpLocks parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("grpSpLocks")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("grpSpLocks")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("CNvGrpSpPr.parseOOXML: " + e.toString()); - } - GrpSpLocks g = new GrpSpLocks(attrs); - return g; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - // attributes - Iterator i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new GrpSpLocks(this); - } -} - - diff --git a/src/main/java/io/starter/formats/OOXML/GrpSp.kt b/src/main/java/io/starter/formats/OOXML/GrpSp.kt new file mode 100644 index 0000000..59796d2 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/GrpSp.kt @@ -0,0 +1,827 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.HashMap +import java.util.Stack + +/** + * grpSp (Group Shape) + * This element specifies a group shape that represents many shapes grouped together. + * Within a group shape 1 each of the shapes that make up the group are + * specified just as they normally would. The idea behind grouping elements however is that a + * single transform can apply to many shapes at the same time. + * + * + * parents: absoluteAnchor, grpSp, oneCellAnchor, twoCellAnchor + * children: nvGrpSpPr (req), grpSpPr (req), choice of: (sp, grpSp, graphicFrame, cxnSp, pic) 0 to unbounded + */ +class GrpSp : OOXMLElement { + private var nvpr: NvGrpSpPr? = null + private var sppr: GrpSpPr? = null + private var choice: ArrayList? = null + + /** + * return grpSp element OOXML + */ + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append(nvpr!!.ooxml) + ooxml.append(sppr!!.ooxml) + if (choice != null) { + for (i in choice!!.indices) { + ooxml.append(choice!![i].ooxml) + } + } + ooxml.append("") + return ooxml.toString() + } + + /** + * get name attribute of the group shape + * + * @return + */ + /** + * set name attribute of the group shape + * + * @param name + */ + // set the group name + var name: String? + get() = nvpr!!.name + set(name) { + nvpr!!.name = name + } + + /** + * get macro attribute of the group shape + * + * @return + */ + /** + * set macro attribute of the group shape + * + * @param name + */ + // Have a shape with an embed? + // how's about a picture? + // or a connection shape? + // Have a shape with a macro? + // how's about a picture? + // or a connection shape? + var macro: String? + get() { + var macro: String? = null + var oe = getObject(SP) + if (oe != null) + macro = (oe as Sp).macro + if (macro == null) + oe = getObject(PIC) + if (oe != null && macro == null) + macro = (oe as Pic).macro + if (macro == null) + oe = getObject(CXN) + if (oe != null && macro == null) + macro = (oe as CxnSp).macro + return macro + } + set(macro) { + var m: String? = null + var oe = getObject(SP) + if (oe != null) + m = (oe as Sp).macro + if (m != null) { + (oe as Sp).macro = macro + return + } + oe = getObject(PIC) + if (oe != null) + m = (oe as Pic).macro + if (m != null) { + (oe as Pic).macro = macro + return + } + oe = getObject(CXN) + if (oe != null) + m = (oe as CxnSp).macro + if (m != null) + (oe as CxnSp).macro = macro + } + + + /** + * get cthe descr attribute of the group shape + * + * @return + */ + /** + * set description attribute of the group shape + * sometimes associated with shape name + * + * @param descr + */ + var descr: String? + get() = nvpr!!.descr + set(descr) { + nvpr!!.descr = descr + } + + /** + * return the rid for the embedded object (picture or chart or picture shape) (i.e. resides within the file) + * + * @return + */ + /** + * set the rid for this object (picture or chart or picture shape) (resides within the file) + * + * @param embed + */ + // Have a shape with an embed? + // how's about a picture? + // or a connection shape? + // Have a shape with an embed? + // how's about a picture? + // or a connection shape? + var embed: String? + get() { + var embed: String? = null + var oe = getObject(SP) + if (oe != null) + embed = (oe as Sp).embed + if (embed == null) + oe = getObject(PIC) + if (oe != null && embed == null) + embed = (oe as Pic).embed + if (embed == null) + oe = getObject(CXN) + if (oe != null && embed == null) + embed = (oe as CxnSp).embed + return embed + } + set(embed) { + var e: String? = null + var oe = getObject(SP) + if (oe != null) + e = (oe as Sp).embed + if (e != null) { + (oe as Sp).embed = embed + return + } + oe = getObject(PIC) + if (oe != null) + e = (oe as Pic).embed + if (e != null) { + (oe as Pic).embed = embed + return + } + oe = getObject(CXN) + if (oe != null) + e = (oe as CxnSp).embed + if (e != null) + (oe as CxnSp).embed = embed + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + /** + * rset the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + // Have a shape with an embed? + // how's about a picture? + // or a connection shape? + // Have a shape with an embed? + // how's about a picture? + // or a connection shape? + var link: String? + get() { + var link: String? = null + var oe = getObject(SP) + if (oe != null) + link = (oe as Sp).link + if (link == null) + oe = getObject(PIC) + if (oe != null && link == null) + link = (oe as Pic).link + if (link == null) + oe = getObject(CXN) + if (oe != null && link == null) + link = (oe as CxnSp).link + return link + } + set(link) { + var l: String? = null + var oe = getObject(SP) + if (oe != null) + l = (oe as Sp).link + if (l != null) { + (oe as Sp).link = link + return + } + oe = getObject(PIC) + if (oe != null) + l = (oe as Pic).link + if (l != null) { + (oe as Pic).link = link + return + } + oe = getObject(CXN) + if (oe != null) + l = (oe as CxnSp).link + if (l != null) + (oe as CxnSp).link = link + } + + /** + * return the rid of the chart element, if exists + * + * @return + */ + val chartRId: String? + get() { + val oe = getObject(GRAPHICFRAME) + return if (oe != null) (oe as GraphicFrame).chartRId else null + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + // get the first?? + // get the first?? + var id: Int + get() { + + if (choice != null) { + val oe = choice!![0] + if (oe is Sp) + return oe.id + else if (oe is Pic) + return oe.id + else if (oe is GraphicFrame) + return oe.id + else if (oe is CxnSp) + return oe.id + else if (oe is GrpSp) + return oe.id + } + return -1 + } + set(id) { + if (choice != null) { + val oe = choice!![0] + if (oe is Sp) + oe.id = id + else if (oe is Pic) + oe.id = id + else if (oe is GraphicFrame) + oe.id = id + else if (oe is CxnSp) + oe.id = id + else if (oe is GrpSp) + oe.id = id + } + } + + constructor(nvpr: NvGrpSpPr, sppr: GrpSpPr, choice: ArrayList) { + this.nvpr = nvpr + this.sppr = sppr + this.choice = choice + } + + constructor(g: GrpSp) { + this.nvpr = g.nvpr + this.sppr = g.sppr + this.choice = g.choice + } + + private fun getObject(type: Int): OOXMLElement? { + if (choice != null) { + for (i in choice!!.indices) { + val oe = choice!![i] + if (oe is Sp && type == SP) + return oe + else if (oe is Pic && type == PIC) + return oe + else if (oe is CxnSp && type == CXN) + return oe + else if (oe is GraphicFrame && type == GRAPHICFRAME) + return oe + else if (oe is GrpSp) + return oe.getObject(type) + + } + } + return null + } + + /** + * set the URI associated with this graphic data + * + * @param uri + */ + fun setURI(uri: String) { + val oe = getObject(GRAPHICFRAME) + (oe as GraphicFrame).uri = uri + } + + /** + * utility to return the shape properties element for this picture + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + fun getSppr(): SpPr? { + val oe = getObject(PIC) + return if (oe != null) (oe as Pic).sppr else null + + } + + /** + * return if this Group refers to a shape, as opposed a chart or an image + * + * @return + */ + fun hasShape(): Boolean { + return getObject(SP) != null || getObject(CXN) != null + } + + override fun cloneElement(): OOXMLElement { + return GrpSp(this) + } + + companion object { + + private val serialVersionUID = -3276180769601314853L + + /** + * parse grpSp element OOXML + * + * @param xpp + * @param lastTag + * @return + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): GrpSp { + var nvpr: NvGrpSpPr? = null + var sppr: GrpSpPr? = null + val choice = ArrayList() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "nvGrpSpPr") { + lastTag.push(tnm) + nvpr = NvGrpSpPr.parseOOXML(xpp, lastTag) + } else if (tnm == "grpSpPr") { + lastTag.push(tnm) + sppr = GrpSpPr.parseOOXML(xpp, lastTag, bk) + // choice of: 0 or more below: + } else if (tnm == "sp") { + lastTag.push(tnm) + choice.add(Sp.parseOOXML(xpp, lastTag, bk)) + } else if (tnm == "grpSp") { + if (nvpr != null) { // if not the initial start attribute, this is a child + lastTag.push(tnm) + choice.add(GrpSp.parseOOXML(xpp, lastTag, bk)) + } + } else if (tnm == "graphicFrame") { + lastTag.push(tnm) + choice.add(GraphicFrame.parseOOXML(xpp, lastTag)) + } else if (tnm == "cxnSp") { + lastTag.push(tnm) + choice.add(CxnSp.parseOOXML(xpp, lastTag, bk)) + } else if (tnm == "pic") { + lastTag.push(tnm) + choice.add(Pic.parseOOXML(xpp, lastTag, bk)) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "grpSp") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("GrpSp.parseOOXML: $e") + } + + return GrpSp(nvpr, sppr, choice) + } + + internal var SP = 0 + internal var PIC = 1 + internal var CXN = 2 + internal var GRAPHICFRAME = 3 + } +} + +/** + * nvGrpSpPr 1 (Non-Visual Properties for a Group Shape) + */ +internal class NvGrpSpPr : OOXMLElement { + var cp: CNvPr? = null + var cgrpsppr: CNvGrpSpPr? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append(cp!!.ooxml) + ooxml.append(cgrpsppr!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * get cNvPr name attribute + * + * @return + */ + /** + * set cNvPr name attribute + * + * @param name + */ + var name: String? + get() = if (cp != null) cp!!.name else null + set(name) { + if (cp != null) + cp!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr desc attribute + * + * @param name + */ + var descr: String? + get() = if (cp != null) cp!!.descr else null + set(descr) { + if (cp != null) + cp!!.descr = descr + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + var id: Int + get() = if (cp != null) cp!!.id else -1 + set(id) { + if (cp != null) + cp!!.id = id + } + + constructor(cp: CNvPr, cgrpsppr: CNvGrpSpPr) { + this.cp = cp + this.cgrpsppr = cgrpsppr + } + + constructor(g: NvGrpSpPr) { + this.cp = g.cp + this.cgrpsppr = g.cgrpsppr + } + + override fun cloneElement(): OOXMLElement { + return NvGrpSpPr(this) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -4404072268706949318L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): NvGrpSpPr { + var cp: CNvPr? = null + var cgrpsppr: CNvGrpSpPr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cNvPr") { + lastTag.push(tnm) + cp = CNvPr.parseOOXML(xpp, lastTag) as CNvPr + } else if (tnm == "cNvGrpSpPr") { + lastTag.push(tnm) + cgrpsppr = CNvGrpSpPr.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "nvGrpSpPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("NvGrpSpPr.parseOOXML: $e") + } + + return NvGrpSpPr(cp, cgrpsppr) + } + } + + +} + +/** + * grpSpPr (Group Shape Properties) + * This element specifies the properties that are to be common across all of the shapes + * within the corresponding group. If there are any conflicting properties within the + * group shape properties and the individual shape properties then the individual shape properties + * should take precedence. + * + * + * children: xfrm, fillproperties (group), effectproperties (group), scene3d, extLst (all optional) + */ +// TODO: FINISH scene3d, extLst +internal class GrpSpPr : OOXMLElement { + private var xf: Xfrm? = null + private var bwmode: String? = null + private var fill: FillGroup? = null + private var effect: EffectPropsGroup? = null + + override// TODO: Finish + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (xf != null) ooxml.append(xf!!.ooxml) + if (fill != null) ooxml.append(fill!!.ooxml) + if (effect != null) ooxml.append(effect!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(xf: Xfrm, bwmode: String, fill: FillGroup, effect: EffectPropsGroup) { + this.xf = xf + this.bwmode = bwmode + this.fill = fill + this.effect = effect + } + + constructor(g: GrpSpPr) { + this.xf = g.xf + this.bwmode = g.bwmode + this.fill = g.fill + this.effect = g.effect + } + + override fun cloneElement(): OOXMLElement { + return GrpSpPr(this) + } + + companion object { + + private val serialVersionUID = 7464871024304781512L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): GrpSpPr { + var xf: Xfrm? = null + var bwmode: String? = null + var fill: FillGroup? = null + var effect: EffectPropsGroup? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "grpSpPr") { // get attributes + if (xpp.attributeCount == 1) { + bwmode = xpp.getAttributeValue(0) + } + } else if (tnm == "xfrm") { + lastTag.push(tnm) + xf = Xfrm.parseOOXML(xpp, lastTag) as Xfrm + // scene3d, extLst- finish + } else if (tnm == "solidFill" || + tnm == "noFill" || + tnm == "gradFill" || + tnm == "grpFill" || + tnm == "pattFill" || + tnm == "blipFill") { + lastTag.push(tnm) + fill = FillGroup.parseOOXML(xpp, lastTag, bk) as FillGroup + } else if (tnm == "effectLst" || tnm == "effectDag") { + lastTag.push(tnm) + effect = EffectPropsGroup.parseOOXML(xpp, lastTag) as EffectPropsGroup + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "grpSpPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("GrpSpPr.parseOOXML: $e") + } + + return GrpSpPr(xf, bwmode, fill, effect) + } + } +} + +/** + * cNvGrpSpPr (Non-Visual Group Shape Drawing Properties) + * + * + * parent: nvGrpSpPr + * children: grpSpLocks, extLst + */ +internal class CNvGrpSpPr : OOXMLElement { + private var gsl: GrpSpLocks? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (gsl != null) + ooxml.append(gsl!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(gsl: GrpSpLocks) { + this.gsl = gsl + } + + constructor(g: CNvGrpSpPr) { + this.gsl = g.gsl + } + + override fun cloneElement(): OOXMLElement { + return CNvGrpSpPr(this) + } + + companion object { + + private val serialVersionUID = -1106010927060582127L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): CNvGrpSpPr { + var gsl: GrpSpLocks? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "grpSpLocks") { + lastTag.push(tnm) + gsl = GrpSpLocks.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "cNvGrpSpPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("CNvGrpSpPr.parseOOXML: $e") + } + + return CNvGrpSpPr(gsl) + } + } +} + + +/** + * grpSpLocks (Group Shape Locks) + * This element specifies all locking properties for a connection shape. These properties inform the generating + * application about specific properties that have been previously locked and thus should not be changed. + * + * + * parent: cNvGrpSpPr + * attributes: many, all optional + * children: extLst + */ +internal class GrpSpLocks : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + val i = attrs!!.keys.iterator() + while (i.hasNext()) { + val key = i.next() + val `val` = attrs!![key] + ooxml.append(" $key=\"$`val`\"") + } + ooxml.append("/>") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(g: GrpSpLocks) { + this.attrs = g.attrs + } + + override fun cloneElement(): OOXMLElement { + return GrpSpLocks(this) + } + + companion object { + + private val serialVersionUID = -2592038952923879415L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): GrpSpLocks { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "grpSpLocks") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "grpSpLocks") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("CNvGrpSpPr.parseOOXML: $e") + } + + return GrpSpLocks(attrs) + } + } +} + + diff --git a/src/main/java/io/starter/formats/OOXML/Layout.java b/src/main/java/io/starter/formats/OOXML/Layout.java deleted file mode 100644 index d2aa2ca..0000000 --- a/src/main/java/io/starter/formats/OOXML/Layout.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * chart layout element - *

                  - *

                  - * parent: plotarea, title ... - * children: manualLayout - */ -public final class Layout implements OOXMLElement { - - private static final long serialVersionUID = -6547994902298821138L; - private ManualLayout ml; - - - private Layout(ManualLayout ml) { - this.ml = ml; - } - - private Layout(Layout l) { - this.ml = l.ml; - } - - /** - * create a new plot area Layout/manual layout element - *
                  note that the layout is calculated via "edge" i.e the w and h are the bottom and right edges - * - * @param target "inner" or "outer"
                  inner specifies the plot area size does not include tick marks and axis labels
                  outer does - * @param offs x, y, w, h as a fraction of the width or height of the actual chart - */ - public Layout(String target, double[] offs) { - String[] modes = new String[]{"edge", "edge", null, null}; - String[] soffs = new String[4]; - for (int i = 0; i < 4; i++) { - if (offs[i] > 0) - soffs[i] = String.valueOf(offs[i]); - } - this.ml = new ManualLayout(target, modes, soffs); - } - - /** - * return manual layout coords - * TODO: interpret xMode, yMode, hMode, wMode - * - * @return - */ - public float[] getCoords() { - float[] coords = new float[4]; - for (int i = 0; i < 4; i++) - if (ml.offs[i] != null) - coords[i] = new Float(ml.offs[i]); - return coords; - } - - /** - * parse title OOXML element title - * - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spPr object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - ManualLayout ml = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("manualLayout")) { - lastTag.push(tnm); - ml = ManualLayout.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("layout")) { - lastTag.pop(); // pop layout tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("layout.parseOOXML: " + e.toString()); - } - Layout l = new Layout(ml); - return l; - } - - /** - * generate ooxml to define a layout - * - * @return - */ - public String getOOXML() { - StringBuffer looxml = new StringBuffer(); - if (ml != null) { - looxml.append(""); - looxml.append(ml.getOOXML()); - looxml.append(""); - } else - looxml.append(""); - return looxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Layout(this); - } - - /** - * static version for 2003-style charts, takes offsets and creates a layout element - * - * @param offs int offsets of chart - * @return - */ - public static String getOOXML(double[] offs) { - StringBuffer looxml = new StringBuffer(); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - looxml.append(""); - return looxml.toString(); - } -} - -/** - * manualLayout - * specifies exact position of chart - *

                  - * parent: layout - * children: h, hMode, layoutTarget, w, wMode, x, xMode, y, yMode - */ -class ManualLayout implements OOXMLElement { - - private static final long serialVersionUID = 6460833211809500902L; - String[] modes; // xMode, yMode, wMode, hMode - String[] offs; // x, y, w, h; - String target; - - public ManualLayout(String target, String[] modes, String[] offs) { - this.modes = modes; - this.target = target; - this.offs = offs; - } - - public ManualLayout(ManualLayout ml) { - this.modes = ml.modes; - this.target = ml.target; - this.offs = ml.offs; - } - - - public static ManualLayout parseOOXML(XmlPullParser xpp, Stack lastTag) { - String[] modes = new String[4]; // xMode, yMode, wMode, hMode - String[] offs = new String[4]; // x, y, w, h; - String target = null; - - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("manualLayout")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - if (n.equals("ATTR")) { - //sz= Integer.valueOf(xpp.getAttributeValue(i)).intValue(); - } - } - } else if (tnm.equals("layoutTarget")) { - target = xpp.getAttributeValue(0); - } else if (tnm.equals("xMode")) { - modes[0] = xpp.getAttributeValue(0); - } else if (tnm.equals("yMode")) { - modes[1] = xpp.getAttributeValue(0); - } else if (tnm.equals("wMode")) { - modes[2] = xpp.getAttributeValue(0); - } else if (tnm.equals("hMode")) { - modes[3] = xpp.getAttributeValue(0); - } else if (tnm.equals("x")) { - offs[0] = xpp.getAttributeValue(0); - } else if (tnm.equals("y")) { - offs[1] = xpp.getAttributeValue(0); - } else if (tnm.equals("w")) { - offs[2] = xpp.getAttributeValue(0); - } else if (tnm.equals("h")) { - offs[3] = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("manualLayout")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("manualLayout.parseOOXML: " + e.toString()); - } - ManualLayout l = new ManualLayout(target, modes, offs); - return l; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (target != null) ooxml.append(""); - if (modes[0] != null) ooxml.append(""); - if (this.modes[1] != null) ooxml.append(""); - if (this.modes[2] != null) ooxml.append(""); - if (this.modes[3] != null) ooxml.append(""); - if (this.offs[0] != null) ooxml.append(""); - if (this.offs[1] != null) ooxml.append(""); - if (this.offs[2] != null) ooxml.append(""); - if (this.offs[3] != null) ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ManualLayout(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Layout.kt b/src/main/java/io/starter/formats/OOXML/Layout.kt new file mode 100644 index 0000000..0b72349 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Layout.kt @@ -0,0 +1,270 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * chart layout element + * + * + * + * + * parent: plotarea, title ... + * children: manualLayout + */ +class Layout : OOXMLElement { + private var ml: ManualLayout? = null + + /** + * return manual layout coords + * TODO: interpret xMode, yMode, hMode, wMode + * + * @return + */ + val coords: FloatArray + get() { + val coords = FloatArray(4) + for (i in 0..3) + if (ml!!.offs[i] != null) + coords[i] = Float(ml!!.offs[i]) + return coords + } + + /** + * generate ooxml to define a layout + * + * @return + */ + override val ooxml: String + get() { + val looxml = StringBuffer() + if (ml != null) { + looxml.append("") + looxml.append(ml!!.ooxml) + looxml.append("") + } else + looxml.append("") + return looxml.toString() + } + + + private constructor(ml: ManualLayout) { + this.ml = ml + } + + private constructor(l: Layout) { + this.ml = l.ml + } + + /** + * create a new plot area Layout/manual layout element + *

                  note that the layout is calculated via "edge" i.e the w and h are the bottom and right edges + * + * @param target "inner" or "outer"

                  inner specifies the plot area size does not include tick marks and axis labels

                  outer does + * @param offs x, y, w, h as a fraction of the width or height of the actual chart + */ + constructor(target: String, offs: DoubleArray) { + val modes = arrayOf("edge", "edge", null, null) + val soffs = arrayOfNulls(4) + for (i in 0..3) { + if (offs[i] > 0) + soffs[i] = offs[i].toString() + } + this.ml = ManualLayout(target, modes, soffs) + } + + override fun cloneElement(): OOXMLElement { + return Layout(this) + } + + companion object { + + private val serialVersionUID = -6547994902298821138L + + /** + * parse title OOXML element title + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + var ml: ManualLayout? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "manualLayout") { + lastTag.push(tnm) + ml = ManualLayout.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "layout") { + lastTag.pop() // pop layout tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("layout.parseOOXML: $e") + } + + return Layout(ml) + } + + /** + * static version for 2003-style charts, takes offsets and creates a layout element + * + * @param offs int offsets of chart + * @return + */ + fun getOOXML(offs: DoubleArray): String { + val looxml = StringBuffer() + looxml.append("") + looxml.append("") + looxml.append("") + looxml.append("") + looxml.append("") + looxml.append("") + looxml.append("") + looxml.append("") + looxml.append("") + looxml.append("") + looxml.append("") + return looxml.toString() + } + } +} + +/** + * manualLayout + * specifies exact position of chart + * + * + * parent: layout + * children: h, hMode, layoutTarget, w, wMode, x, xMode, y, yMode + */ +internal class ManualLayout : OOXMLElement { + var modes: Array // xMode, yMode, wMode, hMode + var offs: Array // x, y, w, h; + var target: String? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (target != null) ooxml.append("") + if (modes[0] != null) ooxml.append("") + if (this.modes[1] != null) ooxml.append("") + if (this.modes[2] != null) ooxml.append("") + if (this.modes[3] != null) ooxml.append("") + if (this.offs[0] != null) ooxml.append("") + if (this.offs[1] != null) ooxml.append("") + if (this.offs[2] != null) ooxml.append("") + if (this.offs[3] != null) ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + constructor(target: String, modes: Array, offs: Array) { + this.modes = modes + this.target = target + this.offs = offs + } + + constructor(ml: ManualLayout) { + this.modes = ml.modes + this.target = ml.target + this.offs = ml.offs + } + + override fun cloneElement(): OOXMLElement { + return ManualLayout(this) + } + + companion object { + + private val serialVersionUID = 6460833211809500902L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack<*>): ManualLayout { + val modes = arrayOfNulls(4) // xMode, yMode, wMode, hMode + val offs = arrayOfNulls(4) // x, y, w, h; + var target: String? = null + + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "manualLayout") { // get attributes + for (i in 0 until xpp.attributeCount) { + val n = xpp.getAttributeName(i) + if (n == "ATTR") { + //sz= Integer.valueOf(xpp.getAttributeValue(i)).intValue(); + } + } + } else if (tnm == "layoutTarget") { + target = xpp.getAttributeValue(0) + } else if (tnm == "xMode") { + modes[0] = xpp.getAttributeValue(0) + } else if (tnm == "yMode") { + modes[1] = xpp.getAttributeValue(0) + } else if (tnm == "wMode") { + modes[2] = xpp.getAttributeValue(0) + } else if (tnm == "hMode") { + modes[3] = xpp.getAttributeValue(0) + } else if (tnm == "x") { + offs[0] = xpp.getAttributeValue(0) + } else if (tnm == "y") { + offs[1] = xpp.getAttributeValue(0) + } else if (tnm == "w") { + offs[2] = xpp.getAttributeValue(0) + } else if (tnm == "h") { + offs[3] = xpp.getAttributeValue(0) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "manualLayout") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("manualLayout.parseOOXML: $e") + } + + return ManualLayout(target, modes, offs) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Legend.java b/src/main/java/io/starter/formats/OOXML/Legend.java deleted file mode 100644 index 02ab887..0000000 --- a/src/main/java/io/starter/formats/OOXML/Legend.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * OOXML Legend element - *

                  - * parent: chart - * children: layout, legendEntry, legendPos, overlay, spPr, txPr - */ -public class Legend implements OOXMLElement { - - private static final long serialVersionUID = 419453456635220517L; - private String legendpos; // actually an element but only contains 1 attribute: val - private LegendEntry le; - private Layout layout; - private SpPr shapeProps; // defines the shape properties for the legend for this chart - private TxPr txpr; // defines text properties - private String overlay; - - - public Legend(String pos, String overlay, Layout l, LegendEntry le, SpPr sp, TxPr txpr) { - this.legendpos = pos; - this.le = le; - this.overlay = overlay; - this.layout = l; - this.shapeProps = sp; - this.txpr = txpr; - } - - public Legend(Legend l) { - this.legendpos = l.legendpos; - this.le = l.le; - this.overlay = l.overlay; - this.layout = l.layout; - this.shapeProps = l.shapeProps; - this.txpr = l.txpr; - } - - /** - * parse OOXML legend element - * - * @param xpp - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String legendpos = null; - LegendEntry le = null; - String overlay = null; - Layout layout = null; - SpPr shapeProps = null; // defines the shape properties for the legend for this chart - TxPr txpr = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("legendPos")) { - legendpos = xpp.getAttributeValue(0); - } else if (tnm.equals("layout")) { - lastTag.push(tnm); - layout = (Layout) Layout.parseOOXML(xpp, lastTag); - } else if (tnm.equals("legendEntry")) { - lastTag.push(tnm); - le = LegendEntry.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - shapeProps = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - //shapeProps.setNS("c"); - } else if (tnm.equals("txPr")) { - lastTag.push(tnm); - txpr = (TxPr) TxPr.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("overlay")) { - overlay = xpp.getAttributeValue(0); - } - - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("legend")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parseLegendElement: " + e.toString()); - } - Legend l = new Legend(legendpos, overlay, layout, le, shapeProps, txpr); - return l; - } - - - /** - * fill 2003-style legend from OOXML Legend - * - * @param l_2003 - */ - public void fill2003Legend(io.starter.formats.XLS.charts.Legend l_2003) { - // 0= bottom, 1= corner, 2= top, 3= right, 4= left, 7= not docked - String[] pos = {"b", "tr", "t", "r", "l"}; - for (int i = 0; i < pos.length; i++) { - if (pos[i].equals(legendpos)) { - l_2003.setLegendPosition((short) i); - break; - } - } - if (this.hasBox()) - l_2003.addBox(); - - } - - /** - * generate the ooxml necessary to display chart legend - * - * @return - */ - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - // sequence - if (legendpos != null) ooxml.append(""); - if (le != null) ooxml.append(le.getOOXML()); - if (layout != null) ooxml.append(layout.getOOXML()); - if (overlay != null) ooxml.append(""); - if (shapeProps != null) ooxml.append(shapeProps.getOOXML()); - if (txpr != null) ooxml.append(txpr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Legend(this); - } - - /** - * returns true if this legend should have a box around it - * - * @return - */ - public boolean hasBox() { - if (this.shapeProps != null) - return this.shapeProps.hasLine(); - return false; - } - - /** - * create an OOXML legend from a 2003-vers legend - * - * @param l - */ - public static Legend createLegend(io.starter.formats.XLS.charts.Legend l) { - - Legend ooxmllegend = null; - try { - SpPr sp = null; - sp = new SpPr("c"); - sp.setLine(3175, "000000"); - l.getFnt(); - ooxmllegend = new Legend(l.getLegendPositionString(), "1", null, null, sp, null); - } catch (Exception e) { - Logger.logWarn("Error creating 2007+ version Legend: " + e.toString()); - } - return ooxmllegend; - } -} - -/** - * legend Entry element - *

                  - * parent: legend - * attributes; val= position of legend - * children: idx, choice of: delete or txPr - */ -class LegendEntry implements OOXMLElement { - - private static final long serialVersionUID = 1859347855337611982L; - private TxPr tx; - private int idx = -1; - private boolean delete; - - public LegendEntry(int idx, boolean d, TxPr tx) { - this.idx = idx; - this.delete = d; - this.tx = tx; - } - - public LegendEntry(LegendEntry le) { - this.idx = le.idx; - this.delete = le.delete; - this.tx = le.tx; - } - - - public static LegendEntry parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - TxPr tx = null; - int idx = -1; - boolean delete = true; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("idx")) { - idx = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("delete")) { - delete = Boolean.valueOf(xpp.getAttributeValue(0)).booleanValue(); - } else if (tnm.equals("txPr")) { - lastTag.push(tnm); - tx = (TxPr) TxPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("legendEntry")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("legendEntry.parseOOXML: " + e.toString()); - } - LegendEntry le = new LegendEntry(idx, delete, tx); - return le; - } - - public String getOOXML() { - StringBuffer tooxml = new StringBuffer(); - tooxml.append(""); - tooxml.append(""); - if (!delete) tooxml.append(""); - if (tx != null) tooxml.append(tx.getOOXML()); - tooxml.append(""); - return tooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new LegendEntry(this); - } -} diff --git a/src/main/java/io/starter/formats/OOXML/Legend.kt b/src/main/java/io/starter/formats/OOXML/Legend.kt new file mode 100644 index 0000000..1be8469 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Legend.kt @@ -0,0 +1,275 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * OOXML Legend element + * + * + * parent: chart + * children: layout, legendEntry, legendPos, overlay, spPr, txPr + */ +class Legend : OOXMLElement { + private var legendpos: String? = null // actually an element but only contains 1 attribute: val + private var le: LegendEntry? = null + private var layout: Layout? = null + private var shapeProps: SpPr? = null // defines the shape properties for the legend for this chart + private var txpr: TxPr? = null // defines text properties + private var overlay: String? = null + + /** + * generate the ooxml necessary to display chart legend + * + * @return + */ + override// sequence + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (legendpos != null) ooxml.append("") + if (le != null) ooxml.append(le!!.ooxml) + if (layout != null) ooxml.append(layout!!.ooxml) + if (overlay != null) ooxml.append("") + if (shapeProps != null) ooxml.append(shapeProps!!.ooxml) + if (txpr != null) ooxml.append(txpr!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + + constructor(pos: String, overlay: String, l: Layout, le: LegendEntry, sp: SpPr, txpr: TxPr) { + this.legendpos = pos + this.le = le + this.overlay = overlay + this.layout = l + this.shapeProps = sp + this.txpr = txpr + } + + constructor(l: Legend) { + this.legendpos = l.legendpos + this.le = l.le + this.overlay = l.overlay + this.layout = l.layout + this.shapeProps = l.shapeProps + this.txpr = l.txpr + } + + + /** + * fill 2003-style legend from OOXML Legend + * + * @param l_2003 + */ + fun fill2003Legend(l_2003: io.starter.formats.XLS.charts.Legend) { + // 0= bottom, 1= corner, 2= top, 3= right, 4= left, 7= not docked + val pos = arrayOf("b", "tr", "t", "r", "l") + for (i in pos.indices) { + if (pos[i] == legendpos) { + l_2003.legendPosition = i.toShort() + break + } + } + if (this.hasBox()) + l_2003.addBox() + + } + + override fun cloneElement(): OOXMLElement { + return Legend(this) + } + + /** + * returns true if this legend should have a box around it + * + * @return + */ + fun hasBox(): Boolean { + return if (this.shapeProps != null) this.shapeProps!!.hasLine() else false + } + + companion object { + + private val serialVersionUID = 419453456635220517L + + /** + * parse OOXML legend element + * + * @param xpp + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var legendpos: String? = null + var le: LegendEntry? = null + var overlay: String? = null + var layout: Layout? = null + var shapeProps: SpPr? = null // defines the shape properties for the legend for this chart + var txpr: TxPr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "legendPos") { + legendpos = xpp.getAttributeValue(0) + } else if (tnm == "layout") { + lastTag.push(tnm) + layout = Layout.parseOOXML(xpp, lastTag) as Layout + } else if (tnm == "legendEntry") { + lastTag.push(tnm) + le = LegendEntry.parseOOXML(xpp, lastTag, bk) + } else if (tnm == "spPr") { + lastTag.push(tnm) + shapeProps = SpPr.parseOOXML(xpp, lastTag, bk) as SpPr + //shapeProps.setNS("c"); + } else if (tnm == "txPr") { + lastTag.push(tnm) + txpr = TxPr.parseOOXML(xpp, lastTag, bk) as TxPr + } else if (tnm == "overlay") { + overlay = xpp.getAttributeValue(0) + } + + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "legend") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("OOXMLAdapter.parseLegendElement: $e") + } + + return Legend(legendpos, overlay, layout, le, shapeProps, txpr) + } + + /** + * create an OOXML legend from a 2003-vers legend + * + * @param l + */ + fun createLegend(l: io.starter.formats.XLS.charts.Legend): Legend? { + + var ooxmllegend: Legend? = null + try { + var sp: SpPr? = null + sp = SpPr("c") + sp.setLine(3175, "000000") + l.fnt + ooxmllegend = Legend(l.legendPositionString, "1", null, null, sp, null) + } catch (e: Exception) { + Logger.logWarn("Error creating 2007+ version Legend: $e") + } + + return ooxmllegend + } + } +} + +/** + * legend Entry element + * + * + * parent: legend + * attributes; val= position of legend + * children: idx, choice of: delete or txPr + */ +internal class LegendEntry : OOXMLElement { + private var tx: TxPr? = null + private var idx = -1 + private var delete: Boolean = false + + override val ooxml: String + get() { + val tooxml = StringBuffer() + tooxml.append("") + tooxml.append("") + if (!delete) tooxml.append("") + if (tx != null) tooxml.append(tx!!.ooxml) + tooxml.append("") + return tooxml.toString() + } + + constructor(idx: Int, d: Boolean, tx: TxPr) { + this.idx = idx + this.delete = d + this.tx = tx + } + + constructor(le: LegendEntry) { + this.idx = le.idx + this.delete = le.delete + this.tx = le.tx + } + + override fun cloneElement(): OOXMLElement { + return LegendEntry(this) + } + + companion object { + + private val serialVersionUID = 1859347855337611982L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): LegendEntry { + var tx: TxPr? = null + var idx = -1 + var delete = true + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "idx") { + idx = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "delete") { + delete = java.lang.Boolean.valueOf(xpp.getAttributeValue(0)).booleanValue() + } else if (tnm == "txPr") { + lastTag.push(tnm) + tx = TxPr.parseOOXML(xpp, lastTag, bk) as TxPr + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "legendEntry") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("legendEntry.parseOOXML: $e") + } + + return LegendEntry(idx, delete, tx) + } + } +} diff --git a/src/main/java/io/starter/formats/OOXML/Ln.java b/src/main/java/io/starter/formats/OOXML/Ln.java deleted file mode 100644 index a05285b..0000000 --- a/src/main/java/io/starter/formats/OOXML/Ln.java +++ /dev/null @@ -1,615 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * ln (Outline) - *

                  - * This OOXML/DrawingML element specifies an outline style that can be applied to a number of different objects such as shapes and - * text. The line allows for the specifying of many different types of outlines including even line dashes and bevels - *

                  - * parent: many - * children: bevel, custDash, gradFill, headEnd, miter, noFill, pattFill, prstDash, round, solidFill, tailEnd - * attributes: w (line width) cap (line ending cap) cmpd (compound line type) algn (stroke alignment) - */ -// TODO: Finish custDash -public class Ln implements OOXMLElement { - - private static final long serialVersionUID = -161619607936083688L; - private HashMap attrs = null; - private FillGroup fill = null; - private JoinGroup join = null; - private DashGroup dash = null; - private HeadEnd h = null; - private TailEnd t = null; - - /** - * create a new solid line in width w with color clr - * - * @param w int specifies the width of a line in EMUs. 1 pt = 12700 EMUs - * @param clr String hex color without the # - */ - public Ln(int w, String clr) { - this.fill = new FillGroup(null, null, null, null, new SolidFill(clr)); - this.dash = new DashGroup(new PrstDash("solid")); - this.setWidth(w); - } - - public Ln() { // no-param constructor, set up common defaults - this.fill = new FillGroup(null, null, null, null, new SolidFill()); - this.join = new JoinGroup("800000", true, false, false); - this.h = new HeadEnd(); - this.t = new TailEnd(); - } - - public Ln(HashMap attrs, FillGroup fill, JoinGroup join, DashGroup dash, HeadEnd h, TailEnd t) { - this.attrs = attrs; - this.fill = fill; - this.join = join; - this.dash = dash; - this.t = t; - this.h = h; - } - - public Ln(Ln l) { - this.attrs = l.attrs; - this.fill = l.fill; - this.join = l.join; - this.dash = l.dash; - this.t = l.t; - this.h = l.h; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - FillGroup fill = null; - JoinGroup join = null; - DashGroup dash = null; - HeadEnd h = null; - TailEnd t = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ln")) { // get ln attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("noFill") || - tnm.equals("solidFill") || - tnm.equals("pattFill") || - tnm.equals("gradFill")) { - lastTag.push(tnm); - fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("bevel") || - tnm.equals("round") || - tnm.equals("miter")) { - lastTag.push(tnm); - join = JoinGroup.parseOOXML(xpp, lastTag); - } else if (tnm.equals("prstDash") || - tnm.equals("custDash")) { - lastTag.push(tnm); - dash = DashGroup.parseOOXML(xpp, lastTag); - } else if (tnm.equals("headEnd")) { - lastTag.push(tnm); - h = HeadEnd.parseOOXML(xpp, lastTag); - } else if (tnm.equals("tailEnd")) { - lastTag.push(tnm); - t = TailEnd.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("ln")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ln.parseOOXML: " + e.toString()); - } - Ln l = new Ln(attrs, fill, join, dash, h, t); - return l; - } - - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (fill != null) ooxml.append(fill.getOOXML()); - if (dash != null) ooxml.append(dash.getOOXML()); - if (join != null) ooxml.append(join.getOOXML()); - if (h != null) ooxml.append(h.getOOXML()); - if (t != null) ooxml.append(t.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Ln(this); - } - - /** - * set the line width - * - * @param w - */ - public void setWidth(int w) { - if (attrs == null) - attrs = new HashMap(); - attrs.put("w", String.valueOf(w)); - - } - - /** - * return line width in 2003-style units, 0 for none - * - * @return - */ - public int getWidth() { - if (attrs != null && attrs.get("w") != null) - // Specifies the width to be used for the underline stroke. - // If this attribute is omitted, then a value of 0 is assumed. - return Integer.valueOf(attrs.get("w")) / 12700; // 1 pt = 12700 EMUs. - return 0; // default - } - - public int getColor() { - if (fill == null) - return -1; - return fill.getColor(); - } - - public void setColor(String clr) { - if (fill == null) - this.fill = new FillGroup(null, null, null, null, new SolidFill()); - fill.setColor(clr); - } - - /** - * convert 2007 preset dasing scheme to 2003 line stype - *
                  0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, - * 6= dk gray pattern, 7= med. gray, 8= light gray - * - * @return - */ - public int getLineStyle() { - if (dash != null) { - String style = dash.getPresetDashingScheme(); - if (style.equals("solid")) - return 0; // solid - else if (style.equals("dash") || - style.equals("sysDash") || - style.equals("lgDash")) - return 1; - else if (style.equals("sysDot")) - return 2; - else if (style.equals("dashDot") || - style.equals("sysDashDot") || - style.equals("lgDashDot")) - return 3; - else if (style.equals("sysDashDashDot") || - style.equals("lgDashDotDot")) - return 4; - } - // ? none = 5 ??? - return 0; // solid - } -} - - -/** - * line dash group: prstDash, custDash - */ -// TODO: Finish custDash -class DashGroup implements OOXMLElement { - - private static final long serialVersionUID = -6892326040716070609L; - private PrstDash prstDash; - - public DashGroup(PrstDash p) { - this.prstDash = p; - } - - public DashGroup(DashGroup d) { - this.prstDash = d.prstDash; - } - - - public static DashGroup parseOOXML(XmlPullParser xpp, Stack lastTag) { - PrstDash p = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (/*tnm.equals("custDash") ||*/ - tnm.equals("prstDash")) { - p = PrstDash.parseOOXML(xpp, lastTag); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("custDash") || - endTag.equals("prstDash")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("custDash.parseOOXML: " + e.toString()); - } - DashGroup dg = new DashGroup(p); - return dg; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (prstDash != null) ooxml.append(prstDash.getOOXML()); - // TODO: finish custDash - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new DashGroup(this); - } - - /** - * returns the preset dashing scheme for the line, if any - *
                  One of: - *
                  dash, dashDot, lgDash, lgDashDot, lgDashDotDot, solid, sysDash, sysDashDot, - * sysDashDotDot, sysDot - * - * @return - */ - public String getPresetDashingScheme() { - if (prstDash != null) - return prstDash.getPresetDashingScheme(); - return null; - } -} - -/** - * choice of miter, bevel or round - *

                  - * parent: ln - */ -/* since each child element is so simple, we will just store which element it is rather than create separate object */ -class JoinGroup implements OOXMLElement { - - private static final long serialVersionUID = -6107424300366896696L; - private boolean miter; - private boolean round; - private boolean bevel; - private String miterVal; - - public JoinGroup(String a, boolean m, boolean r, boolean b) { - this.miterVal = a; - this.miter = m; - this.round = r; - this.bevel = b; - } - - public JoinGroup(JoinGroup j) { - this.miterVal = j.miterVal; - this.miter = j.miter; - this.round = j.round; - this.bevel = j.bevel; - } - - public static JoinGroup parseOOXML(XmlPullParser xpp, Stack lastTag) { - boolean miter = false; - boolean round = false; - boolean bevel = false; - String a = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bevel")) { - bevel = true; - } else if (tnm.equals("round")) { - round = true; - } else if (tnm.equals("miter")) { - miter = true; - a = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("bevel") || - endTag.equals("round") || - endTag.equals("miter")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("JoinGroup.parseOOXML: " + e.toString()); - } - JoinGroup jg = new JoinGroup(a, miter, round, bevel); - return jg; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (miter) { - ooxml.append(""); - } else if (round) { - ooxml.append(""); - } else if (bevel) { - ooxml.append(""); - } - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new JoinGroup(this); - } -} - -/** - * headEnd (Line Head/End Style) - *

                  - * This element specifies decorations which can be added to the head of a line. - */ -class HeadEnd implements OOXMLElement { - private static final long serialVersionUID = -6744308104003922477L; - private String len = null; - private String type = null; - private String w = null; - - public HeadEnd() { - - } - - public HeadEnd(String len, String type, String w) { - this.len = len; - this.type = type; - this.w = w; - } - - public HeadEnd(HeadEnd te) { - this.len = te.len; - this.type = te.type; - this.w = te.w; - } - - public static HeadEnd parseOOXML(XmlPullParser xpp, Stack lastTag) { - String len = null; - String type = null; - String w = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("headEnd")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - if (n.equals("len")) - len = xpp.getAttributeValue(i); - else if (n.equals("type")) - type = xpp.getAttributeValue(i); - else if (n.equals("w")) - w = xpp.getAttributeValue(i); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("headEnd")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("headEnd.parseOOXML: " + e.toString()); - } - HeadEnd te = new HeadEnd(len, type, w); - return te; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new HeadEnd(this); - } -} - -/** - * tailEnd (Tail line end style) - *

                  - * This element specifies decorations which can be added to the tail of a line. - */ -class TailEnd implements OOXMLElement { - private static final long serialVersionUID = -5587427916156543370L; - private String len = null; - private String type = null; - private String w = null; - - public TailEnd() { - } - - public TailEnd(String len, String type, String w) { - this.len = len; - this.type = type; - this.w = w; - } - - public TailEnd(TailEnd te) { - this.len = te.len; - this.type = te.type; - this.w = te.w; - } - - public static TailEnd parseOOXML(XmlPullParser xpp, Stack lastTag) { - String len = null; - String type = null; - String w = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tailEnd")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - if (n.equals("len")) - len = xpp.getAttributeValue(i); - else if (n.equals("type")) - type = xpp.getAttributeValue(i); - else if (n.equals("w")) - w = xpp.getAttributeValue(i); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("tailEnd")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("tailEnd.parseOOXML: " + e.toString()); - } - TailEnd te = new TailEnd(len, type, w); - return te; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TailEnd(this); - } -} - - -class PrstDash implements OOXMLElement { - - private static final long serialVersionUID = -4645986946936173151L; - private String val = null; - - public PrstDash() { - } - - public PrstDash(String val) { - this.val = val; - } - - public PrstDash(PrstDash p) { - this.val = p.val; - } - - - public static PrstDash parseOOXML(XmlPullParser xpp, Stack lastTag) { - String val = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("prstDash")) { // get val attribute - val = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("prstDash")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("prstDash.parseOOXML: " + e.toString()); - } - PrstDash p = new PrstDash(val); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PrstDash(this); - } - - /** - * returns the preset dashing scheme for the line, if any - *
                  One of: - *
                  dash, dashDot, lgDash, lgDashDot, lgDashDotDot, solid, sysDash, sysDashDot, - * sysDashDotDot, sysDot - * - * @return - */ - public String getPresetDashingScheme() { - return val; - } -} diff --git a/src/main/java/io/starter/formats/OOXML/Ln.kt b/src/main/java/io/starter/formats/OOXML/Ln.kt new file mode 100644 index 0000000..59503cf --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Ln.kt @@ -0,0 +1,630 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * ln (Outline) + * + * + * This OOXML/DrawingML element specifies an outline style that can be applied to a number of different objects such as shapes and + * text. The line allows for the specifying of many different types of outlines including even line dashes and bevels + * + * + * parent: many + * children: bevel, custDash, gradFill, headEnd, miter, noFill, pattFill, prstDash, round, solidFill, tailEnd + * attributes: w (line width) cap (line ending cap) cmpd (compound line type) algn (stroke alignment) + */ +// TODO: Finish custDash +class Ln : OOXMLElement { + private var attrs: HashMap? = null + private var fill: FillGroup? = null + private var join: JoinGroup? = null + private var dash: DashGroup? = null + private var h: HeadEnd? = null + private var t: TailEnd? = null + + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (fill != null) ooxml.append(fill!!.ooxml) + if (dash != null) ooxml.append(dash!!.ooxml) + if (join != null) ooxml.append(join!!.ooxml) + if (h != null) ooxml.append(h!!.ooxml) + if (t != null) ooxml.append(t!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * return line width in 2003-style units, 0 for none + * + * @return + */ + /** + * set the line width + * + * @param w + */ + // Specifies the width to be used for the underline stroke. + // If this attribute is omitted, then a value of 0 is assumed. + // 1 pt = 12700 EMUs. + // default + var width: Int + get() = if (attrs != null && attrs!!["w"] != null) Integer.valueOf(attrs!!["w"]) / 12700 else 0 + set(w) { + if (attrs == null) + attrs = HashMap() + attrs!!["w"] = w.toString() + + } + + val color: Int + get() = if (fill == null) -1 else fill!!.color + + /** + * convert 2007 preset dasing scheme to 2003 line stype + *

                  0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, + * 6= dk gray pattern, 7= med. gray, 8= light gray + * + * @return + */ + // solid + // ? none = 5 ??? + // solid + val lineStyle: Int + get() { + if (dash != null) { + val style = dash!!.presetDashingScheme + if (style == "solid") + return 0 + else if (style == "dash" || + style == "sysDash" || + style == "lgDash") + return 1 + else if (style == "sysDot") + return 2 + else if (style == "dashDot" || + style == "sysDashDot" || + style == "lgDashDot") + return 3 + else if (style == "sysDashDashDot" || style == "lgDashDotDot") + return 4 + } + return 0 + } + + /** + * create a new solid line in width w with color clr + * + * @param w int specifies the width of a line in EMUs. 1 pt = 12700 EMUs + * @param clr String hex color without the # + */ + constructor(w: Int, clr: String) { + this.fill = FillGroup(null, null, null, null, SolidFill(clr)) + this.dash = DashGroup(PrstDash("solid")) + this.width = w + } + + constructor() { // no-param constructor, set up common defaults + this.fill = FillGroup(null, null, null, null, SolidFill()) + this.join = JoinGroup("800000", true, false, false) + this.h = HeadEnd() + this.t = TailEnd() + } + + constructor(attrs: HashMap, fill: FillGroup, join: JoinGroup, dash: DashGroup, h: HeadEnd, t: TailEnd) { + this.attrs = attrs + this.fill = fill + this.join = join + this.dash = dash + this.t = t + this.h = h + } + + constructor(l: Ln) { + this.attrs = l.attrs + this.fill = l.fill + this.join = l.join + this.dash = l.dash + this.t = l.t + this.h = l.h + } + + override fun cloneElement(): OOXMLElement { + return Ln(this) + } + + fun setColor(clr: String) { + if (fill == null) + this.fill = FillGroup(null, null, null, null, SolidFill()) + fill!!.setColor(clr) + } + + companion object { + + private val serialVersionUID = -161619607936083688L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + val attrs = HashMap() + var fill: FillGroup? = null + var join: JoinGroup? = null + var dash: DashGroup? = null + var h: HeadEnd? = null + var t: TailEnd? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "ln") { // get ln attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "noFill" || + tnm == "solidFill" || + tnm == "pattFill" || + tnm == "gradFill") { + lastTag.push(tnm) + fill = FillGroup.parseOOXML(xpp, lastTag, bk) as FillGroup + } else if (tnm == "bevel" || + tnm == "round" || + tnm == "miter") { + lastTag.push(tnm) + join = JoinGroup.parseOOXML(xpp, lastTag) + } else if (tnm == "prstDash" || tnm == "custDash") { + lastTag.push(tnm) + dash = DashGroup.parseOOXML(xpp, lastTag) + } else if (tnm == "headEnd") { + lastTag.push(tnm) + h = HeadEnd.parseOOXML(xpp, lastTag) + } else if (tnm == "tailEnd") { + lastTag.push(tnm) + t = TailEnd.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "ln") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("ln.parseOOXML: $e") + } + + return Ln(attrs, fill, join, dash, h, t) + } + } +} + + +/** + * line dash group: prstDash, custDash + */ +// TODO: Finish custDash +internal class DashGroup : OOXMLElement { + private var prstDash: PrstDash? = null + + override// TODO: finish custDash + val ooxml: String + get() { + val ooxml = StringBuffer() + if (prstDash != null) ooxml.append(prstDash!!.ooxml) + return ooxml.toString() + } + + /** + * returns the preset dashing scheme for the line, if any + *

                  One of: + *

                  dash, dashDot, lgDash, lgDashDot, lgDashDotDot, solid, sysDash, sysDashDot, + * sysDashDotDot, sysDot + * + * @return + */ + val presetDashingScheme: String? + get() = if (prstDash != null) prstDash!!.presetDashingScheme else null + + constructor(p: PrstDash) { + this.prstDash = p + } + + constructor(d: DashGroup) { + this.prstDash = d.prstDash + } + + override fun cloneElement(): OOXMLElement { + return DashGroup(this) + } + + companion object { + + private val serialVersionUID = -6892326040716070609L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): DashGroup { + var p: PrstDash? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (/*tnm.equals("custDash") ||*/ + tnm == "prstDash") { + p = PrstDash.parseOOXML(xpp, lastTag) + break + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "custDash" || endTag == "prstDash") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("custDash.parseOOXML: $e") + } + + return DashGroup(p) + } + } +} + +/** + * choice of miter, bevel or round + * + * + * parent: ln + */ +/* since each child element is so simple, we will just store which element it is rather than create separate object */ +internal class JoinGroup : OOXMLElement { + private var miter: Boolean = false + private var round: Boolean = false + private var bevel: Boolean = false + private var miterVal: String? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + if (miter) { + ooxml.append("") + } else if (round) { + ooxml.append("") + } else if (bevel) { + ooxml.append("") + } + return ooxml.toString() + } + + constructor(a: String, m: Boolean, r: Boolean, b: Boolean) { + this.miterVal = a + this.miter = m + this.round = r + this.bevel = b + } + + constructor(j: JoinGroup) { + this.miterVal = j.miterVal + this.miter = j.miter + this.round = j.round + this.bevel = j.bevel + } + + override fun cloneElement(): OOXMLElement { + return JoinGroup(this) + } + + companion object { + + private val serialVersionUID = -6107424300366896696L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): JoinGroup { + var miter = false + var round = false + var bevel = false + var a: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "bevel") { + bevel = true + } else if (tnm == "round") { + round = true + } else if (tnm == "miter") { + miter = true + a = xpp.getAttributeValue(0) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "bevel" || + endTag == "round" || + endTag == "miter") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("JoinGroup.parseOOXML: $e") + } + + return JoinGroup(a, miter, round, bevel) + } + } +} + +/** + * headEnd (Line Head/End Style) + * + * + * This element specifies decorations which can be added to the head of a line. + */ +internal class HeadEnd : OOXMLElement { + private var len: String? = null + private var type: String? = null + private var w: String? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor() { + + } + + constructor(len: String, type: String, w: String) { + this.len = len + this.type = type + this.w = w + } + + constructor(te: HeadEnd) { + this.len = te.len + this.type = te.type + this.w = te.w + } + + override fun cloneElement(): OOXMLElement { + return HeadEnd(this) + } + + companion object { + private val serialVersionUID = -6744308104003922477L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): HeadEnd { + var len: String? = null + var type: String? = null + var w: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "headEnd") { // get attributes + for (i in 0 until xpp.attributeCount) { + val n = xpp.getAttributeName(i) + if (n == "len") + len = xpp.getAttributeValue(i) + else if (n == "type") + type = xpp.getAttributeValue(i) + else if (n == "w") + w = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "headEnd") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("headEnd.parseOOXML: $e") + } + + return HeadEnd(len, type, w) + } + } +} + +/** + * tailEnd (Tail line end style) + * + * + * This element specifies decorations which can be added to the tail of a line. + */ +internal class TailEnd : OOXMLElement { + private var len: String? = null + private var type: String? = null + private var w: String? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor() {} + + constructor(len: String, type: String, w: String) { + this.len = len + this.type = type + this.w = w + } + + constructor(te: TailEnd) { + this.len = te.len + this.type = te.type + this.w = te.w + } + + override fun cloneElement(): OOXMLElement { + return TailEnd(this) + } + + companion object { + private val serialVersionUID = -5587427916156543370L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): TailEnd { + var len: String? = null + var type: String? = null + var w: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "tailEnd") { // get attributes + for (i in 0 until xpp.attributeCount) { + val n = xpp.getAttributeName(i) + if (n == "len") + len = xpp.getAttributeValue(i) + else if (n == "type") + type = xpp.getAttributeValue(i) + else if (n == "w") + w = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "tailEnd") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("tailEnd.parseOOXML: $e") + } + + return TailEnd(len, type, w) + } + } +} + + +internal class PrstDash : OOXMLElement { + /** + * returns the preset dashing scheme for the line, if any + *

                  One of: + *

                  dash, dashDot, lgDash, lgDashDot, lgDashDotDot, solid, sysDash, sysDashDot, + * sysDashDotDot, sysDot + * + * @return + */ + var presetDashingScheme: String? = null + private set + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor() {} + + constructor(`val`: String) { + this.presetDashingScheme = `val` + } + + constructor(p: PrstDash) { + this.presetDashingScheme = p.presetDashingScheme + } + + override fun cloneElement(): OOXMLElement { + return PrstDash(this) + } + + companion object { + + private val serialVersionUID = -4645986946936173151L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): PrstDash { + var `val`: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "prstDash") { // get val attribute + `val` = xpp.getAttributeValue(0) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "prstDash") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("prstDash.parseOOXML: $e") + } + + return PrstDash(`val`) + } + } +} diff --git a/src/main/java/io/starter/formats/OOXML/Marker.java b/src/main/java/io/starter/formats/OOXML/Marker.java deleted file mode 100644 index 404d106..0000000 --- a/src/main/java/io/starter/formats/OOXML/Marker.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * class holds OOXML to define markers for a chart (radar, scatter or line) - *

                  - * parent: dPt, Ser ... - * children: symbol, size, spPr - * NOTE: child elements symbol and size have only 1 attribute and no children, and so are treated as strings - */ -public final class Marker implements OOXMLElement { - - private static final long serialVersionUID = -5070227633357072878L; - private SpPr sp; - private String size; - private String symbol; - - public Marker(String s, String sz, SpPr sp) { - this.symbol = s; - this.size = sz; - this.sp = sp; - } - - public Marker(Marker m) { - this.symbol = m.symbol; - this.size = m.size; - this.sp = m.sp; - } - - /** - * parse marker OOXML element - * - * @param xpp XmlPullParser - * @param lastTag element stack - * @return marker object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - SpPr sp = null; - String size = null; // size element: val is only attribute + no children - String symbol = null; // symbol element: val is only attribute + no children - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("symbol")) { - symbol = xpp.getAttributeValue(0); - } else if (tnm.equals("size")) { - size = xpp.getAttributeValue(0); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - //sp.setNS("c"); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("marker")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("marker.parseOOXML: " + e.toString()); - } - Marker m = new Marker(symbol, size, sp); - return m; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (this.symbol != null) - ooxml.append(""); - if (this.size != null) - ooxml.append(""); - if (this.sp != null) ooxml.append(sp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Marker(this); - } -} diff --git a/src/main/java/io/starter/formats/OOXML/Marker.kt b/src/main/java/io/starter/formats/OOXML/Marker.kt new file mode 100644 index 0000000..8baed24 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Marker.kt @@ -0,0 +1,118 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * class holds OOXML to define markers for a chart (radar, scatter or line) + * + * + * parent: dPt, Ser ... + * children: symbol, size, spPr + * NOTE: child elements symbol and size have only 1 attribute and no children, and so are treated as strings + */ +class Marker : OOXMLElement { + private var sp: SpPr? = null + private var size: String? = null + private var symbol: String? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (this.symbol != null) + ooxml.append("") + if (this.size != null) + ooxml.append("") + if (this.sp != null) ooxml.append(sp!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(s: String, sz: String, sp: SpPr) { + this.symbol = s + this.size = sz + this.sp = sp + } + + constructor(m: Marker) { + this.symbol = m.symbol + this.size = m.size + this.sp = m.sp + } + + override fun cloneElement(): OOXMLElement { + return Marker(this) + } + + companion object { + + private val serialVersionUID = -5070227633357072878L + + /** + * parse marker OOXML element + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return marker object + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var sp: SpPr? = null + var size: String? = null // size element: val is only attribute + no children + var symbol: String? = null // symbol element: val is only attribute + no children + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "symbol") { + symbol = xpp.getAttributeValue(0) + } else if (tnm == "size") { + size = xpp.getAttributeValue(0) + } else if (tnm == "spPr") { + lastTag.push(tnm) + sp = SpPr.parseOOXML(xpp, lastTag, bk) as SpPr + //sp.setNS("c"); + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "marker") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("marker.parseOOXML: $e") + } + + return Marker(symbol, size, sp) + } + } +} diff --git a/src/main/java/io/starter/formats/OOXML/NumFmt.java b/src/main/java/io/starter/formats/OOXML/NumFmt.java deleted file mode 100644 index a3fd2e0..0000000 --- a/src/main/java/io/starter/formats/OOXML/NumFmt.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.formats.XLS.Xf; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.xmlpull.v1.XmlPullParser; - -/** - * numFmt OOXML element - *

                  - * numFmt (Number Format) - * This element specifies number format properties which indicate how to format and render the numeric value of - * a cell. - * Following is a listing of number formats whose formatCode value is implied rather than explicitly saved in - * the file. In this case a numFmtId value is written on the xf record, but no corresponding numFmt element is - * written. Some of these Ids are interpreted differently, depending on the UI language of the implementing - * application. - *

                  - * parent: styleSheet/numFmts element in styles.xml - * NOTE: numFmt element also occurs in drawingML, numFmt is replaced with sourceLinked - * children: none - */ -public class NumFmt implements OOXMLElement { - - private static final long serialVersionUID = -206715418106414662L; - private String formatCode, numFmtId; - private boolean sourceLinked = false; - - public NumFmt(String formatCode, String numFmtId, boolean sourceLinked) { - this.formatCode = formatCode; - this.numFmtId = numFmtId; - this.sourceLinked = sourceLinked; - } - - public NumFmt(NumFmt n) { - this.formatCode = n.formatCode; - this.numFmtId = n.numFmtId; - this.sourceLinked = n.sourceLinked; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp) { - String formatCode = null, numFmtId = null; - boolean sourceLinked = false; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("numFmt")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (n.equals("formatCode")) { - formatCode = v; - } else if (n.equals("numFmtId")) - numFmtId = v; - else if (n.equals("sourceLinked")) - sourceLinked = (v.equals("1")); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("numFmt")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("numFmt.parseOOXML: " + e.toString()); - } - NumFmt oe = new NumFmt(formatCode, numFmtId, sourceLinked); - return oe; - } - - public String getOOXML() { - return getOOXML(""); - } - - public String getOOXML(String ns) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append("<" + ns + "numFmt"); - // attributes - ooxml.append(" formatCode=\"" + OOXMLAdapter.stripNonAscii(formatCode) + "\""); - if (numFmtId != null) ooxml.append(" numFmtId=\"" + numFmtId + "\""); - if (sourceLinked) ooxml.append(" sourceLinked=\"1\""); - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NumFmt(this); - } - - /** - * returns the format id assoc with this number format - * - * @return - */ - public String getFormatId() { - return numFmtId; - } - - - /** - * returns the OOXML specifying the fill based on this FormatHandle object - */ - public static String getOOXML(Xf xf) { - // Number Format - StringBuffer ooxml = new StringBuffer(); - if (xf.getIfmt() > FormatConstants.BUILTIN_FORMATS.length) { // only input user defined formats ... - String s = xf.getFormatPattern(); - if (s != null) - s = StringTool.replaceText(s, "\"", """); // replace internal quotes // 1.6 only s= s.replace('"', """); // replace internal quotes - ooxml.append(""); - ooxml.append("\r\n"); - } - return ooxml.toString(); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/NumFmt.kt b/src/main/java/io/starter/formats/OOXML/NumFmt.kt new file mode 100644 index 0000000..7ebddde --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/NumFmt.kt @@ -0,0 +1,148 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.formats.XLS.FormatConstants +import io.starter.formats.XLS.OOXMLAdapter +import io.starter.formats.XLS.Xf +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.xmlpull.v1.XmlPullParser + +/** + * numFmt OOXML element + * + * + * numFmt (Number Format) + * This element specifies number format properties which indicate how to format and render the numeric value of + * a cell. + * Following is a listing of number formats whose formatCode value is implied rather than explicitly saved in + * the file. In this case a numFmtId value is written on the xf record, but no corresponding numFmt element is + * written. Some of these Ids are interpreted differently, depending on the UI language of the implementing + * application. + * + * + * parent: styleSheet/numFmts element in styles.xml + * NOTE: numFmt element also occurs in drawingML, numFmt is replaced with sourceLinked + * children: none + */ +class NumFmt : OOXMLElement { + private var formatCode: String? = null + /** + * returns the format id assoc with this number format + * + * @return + */ + var formatId: String? = null + private set + private var sourceLinked = false + + override val ooxml: String + get() = getOOXML("") + + constructor(formatCode: String, numFmtId: String, sourceLinked: Boolean) { + this.formatCode = formatCode + this.formatId = numFmtId + this.sourceLinked = sourceLinked + } + + constructor(n: NumFmt) { + this.formatCode = n.formatCode + this.formatId = n.formatId + this.sourceLinked = n.sourceLinked + } + + fun getOOXML(ns: String): String { + val ooxml = StringBuffer() + ooxml.append("<" + ns + "numFmt") + // attributes + ooxml.append(" formatCode=\"" + OOXMLAdapter.stripNonAscii(formatCode) + "\"") + if (formatId != null) ooxml.append(" numFmtId=\"$formatId\"") + if (sourceLinked) ooxml.append(" sourceLinked=\"1\"") + ooxml.append("/>") + return ooxml.toString() + } + + override fun cloneElement(): OOXMLElement { + return NumFmt(this) + } + + companion object { + + private val serialVersionUID = -206715418106414662L + + fun parseOOXML(xpp: XmlPullParser): OOXMLElement { + var formatCode: String? = null + var numFmtId: String? = null + var sourceLinked = false + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "numFmt") { + for (i in 0 until xpp.attributeCount) { + val n = xpp.getAttributeName(i) + val v = xpp.getAttributeValue(i) + if (n == "formatCode") { + formatCode = v + } else if (n == "numFmtId") + numFmtId = v + else if (n == "sourceLinked") + sourceLinked = v == "1" + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "numFmt") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("numFmt.parseOOXML: $e") + } + + return NumFmt(formatCode, numFmtId, sourceLinked) + } + + + /** + * returns the OOXML specifying the fill based on this FormatHandle object + */ + fun getOOXML(xf: Xf): String { + // Number Format + val ooxml = StringBuffer() + if (xf.ifmt > FormatConstants.BUILTIN_FORMATS.size) { // only input user defined formats ... + var s = xf.formatPattern + if (s != null) + s = StringTool.replaceText(s, "\"", """) // replace internal quotes // 1.6 only s= s.replace('"', """); // replace internal quotes + ooxml.append("") + ooxml.append("\r\n") + } + return ooxml.toString() + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/OOXMLConstants.java b/src/main/java/io/starter/formats/OOXML/OOXMLConstants.java deleted file mode 100644 index 58d0e86..0000000 --- a/src/main/java/io/starter/formats/OOXML/OOXMLConstants.java +++ /dev/null @@ -1,372 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -/** - * - */ -package io.starter.formats.OOXML; - -/** - * - */ -public interface OOXMLConstants { - String xmlHeader = ""; - String xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; - String pkgrelns = "http://schemas.openxmlformats.org/package/2006/relationships"; // .rels ns - String relns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; - String typens = "http://schemas.openxmlformats.org/package/2006/content-types"; - String drawingns = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"; - String drawingmlns = "http://schemas.openxmlformats.org/drawingml/2006/main"; - String chartns = "http://schemas.openxmlformats.org/drawingml/2006/chart"; - String drawingDir = "xl/drawings"; - String mediaDir = "xl/media"; - String chartDir = "xl/charts"; - String themeDir = "xl/theme"; - - String[][] contentTypes = { - {"document", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"}, - {"documentMacroEnabled", "application/vnd.ms-excel.sheet.macroEnabled.main+xml"}, - {"documentTemplateMacroEnabled", "application/vnd.ms-excel.template.macroEnabled.main+xml"}, - {"documentTemplate", "application/vnd.openxmlformats-officedocument.spreadsheetml.template+xml"}, /* TODO: is this correct?? */ - {"sheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"}, - {"styles", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"}, - {"sst", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"}, - {"drawing", "application/vnd.openxmlformats-officedocument.drawing+xml"}, - {"chart", "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"}, - {"theme", "application/vnd.openxmlformats-officedocument.theme+xml"}, - {"themeOverride", "application/vnd.openxmlformats-officedocument.themeOverride+xml"}, - {"props", "application/vnd.openxmlformats-package.core-properties+xml"}, - {"exprops", "application/vnd.openxmlformats-officedocument.extended-properties+xml"}, - {"custprops", "application/vnd.openxmlformats-officedocument.custom-properties+xml"}, - {"connections", "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"}, - {"calc", "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"}, - {"vba", "application/vnd.ms-office.vbaProject"}, - {"vdependencies", "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"}, - {"table", "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"}, - {"vmldrawing", "application/vnd.openxmlformats-officedocument.vmlDrawing"}, - {"externalLink", "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"}, - {"oleObject", "application/vnd.openxmlformats-officedocument.oleObject"}, - {"activeX", "application/vnd.ms-office.activeX+xml"}, - {"comments", "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"}, - {"image", "image"}, // images don't go into Content_Types - {"userShape", "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"}, - {"activeXBinary", "application/vnd.ms-office.activeX"}, - {"pivotCacheDefinition", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"}, - {"pivotCacheRecords", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"}, - {"pivotTable", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"}, - }; - - String[][] relsContentTypes = { - {"document", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, - {"documentMacroEnabled", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, - {"documentTemplate", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, - {"documentTemplateMacroEnabled", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"}, - {"sheet", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"}, - {"styles", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"}, - {"sst", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"}, - {"drawing", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"}, - {"image", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"}, - {"chart", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"}, - {"theme", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"}, - {"themeOverride", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride"}, - {"props", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"}, - {"exprops", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"}, - {"custprops", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"}, - {"connections", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections"}, - {"calc", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain"}, - {"printerSettings", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings"}, - {"vba", "http://schemas.microsoft.com/office/2006/relationships/vbaProject"}, - {"vdependencies", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies"}, - {"table", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"}, - {"vmldrawing", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"}, - {"externalLink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink"}, - {"externalLinkPath", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLinkPath"}, - {"macro", "http://schemas.microsoft.com/office/2006/relationships/xlMacrosheet"}, - {"hyperlink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"}, - {"activeX", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/control"}, - {"oleObject", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"}, - {"comments", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"}, - {"userShape", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes"}, - {"activeXBinary", "http://schemas.microsoft.com/office/2006/relationships/activeXControlBinary"}, - {"pivotCacheDefinition", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"}, - {"pivotCacheRecords", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords"}, - {"pivotTable", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"}, - }; - - String[] builtInNames = { - "_xlnm.Consolidate_Area", - "", //protected static final byte AUTO_OPEN = 0x1; - "", //protected static final byte AUTO_CLOSE = 0x2; - "_xlnm.Extract", - "_xlnm.Database", - "_xlnm.Criteria", - "_xlnm.Print_Area", - "_xlnm.Print_Titles", - "", //protected static final byte RECORDER = 0x8; - "", //protected static final byte DATA_FORM = 0x9; - "", //protected static final byte AUTO_ACTIVATE = 0xA; - "", //protected static final byte AUTO_DEACTIVATE = 0xB; - "_xlnm.Sheet_Title", - "_xlnm._FilterDatabase", - }; - - String[] patternFill = { - "none", - "solid", - "mediumGray", - "darkGray", - "lightGray", - "darkHorizontal", - "darkVertical", - "darkDown", - "darkUp", - "darkGrid", - "darkTrellis", - "lightHorizontal", - "lightVertical", - "lightDown", - "lightUp", - "lightGrid", - "lightTrellis", - "gray125", - "gray0625", - }; - String[] horizontalAlignment = { - "general", - "left", - "center", - "right", - "fill", - "justify", - "centerContinuous", // 6 - "distributed", - }; - - String[] verticalAlignment = { - "top", - "center", - "bottom", - "justify", - "distributed", - }; - - String[] fontScheme = { - "none", - "major", - "minor", - }; - String[] cellType = { - "b", // boolean - "n", // number - "e", // error - "s", // shared (sst) string - "str", // formula string - "inlineStr", // - }; - String[] cellFormulaType = { - "normal", - "array", - "dataTable", - "shared", - }; - String[] borderStyle = { - "none", - "thin", - "medium", - "dashed", - "dotted", - "thick", - "double", - "hair", - "mediumDashed", - "dashDot", - "mediumDashDot", - "dashDotDot", - "mediumDashDotDot", - "slantDashDot", - }; - - String[] fontFamily = { - "Roman", //1 - "Swiss", - "Modern", - "Script", - "Decorative", - }; - /** - * indexed via ChartConstants Chart Types - * @see ChartHandle.getOOXML - */ - String[] twoDchartTypes = { - "barChart", // including col and all shaped bar types such as pyramid, cone ... - "barChart", - "lineChart", // 2 - "pieChart", // 3 - "areaChart", // 4 - "scatterChart", // 5 - "radarChart", // 6 - "surfaceChart", // 7 - "doughnutChart", // 8 - "bubbleChart", // 9 - "ofPieChart", // 10 - "", // radararea - not used in OOXML - "", // pyrmaid == bar in OOXML - "", /// cylinder "" - "", // cone "" - "", // pyramidbar "" 15 - "", // cylinderbar "" - "", // condebar "" - "stockChart" - }; - /** - * indexed via ChartConstants Chart Types - * @see ChartHandle.getOOXML - */ - String[] threeDchartTypes = { - "bar3DChart", // including col and all shaped bar types such as pyramid, cone ... - "bar3DChart", - "line3DChart", - "pie3DChart", - "area3DChart", - "", // scatter - 5 no 3d - "", // radar - 6 no 3d - "surface3DChart", // 7 - "", // doughnut - 8 - no 3d - "", // bubble - 9 - no 3d - "", // ofPie 10 - no 3d - }; - String[] legendPos = { - "b", - "l", - "r", // default - "t", - "tr", - }; -/* - public static String[] indexedColors= { - "00000000", - "00FF0000", - "0000FF00", - "000000FF", - "00FFFF00", - "00FF00FF", - "0000FFFF", - "00000000", - "00FF0000", - "0000FF00", - "000000FF", - "00FFFF00", - "00FF00FF", - "0000FFFF", - "00800000", - "00006600", - "00000080", - "00669900", - "00800080", - "00008080", - "00EAEAEA", - "00808080", - "009999FF", - "00993366", - "00FFFFCC", - "00CCFFFF", - "00660066", - "00FF8080", - "000066CC", - "00CCCCFF", - "00000080", - "00FF00FF", - "00FFFF00", - "0000FFFF", - "00800080", - "00800000", - "00008080", - "000000FF", - "0000CCFF", - "00CCFFFF", - "00CCFFCC", - "00FFFF99", - "0099CCFF", - "00FF99CC", - "00CC99FF", - "00FFCC99", - "003366FF", - "0033CCCC", - "0099CC00", - "00FFCC00", - "00FF9900", - "00FF6600", - "00666699", - "00969696", - "00003366", - "00339966", - "00003300", - "00333300", - "00993300", - "00993366", - "00333399", - "00333333", - }; -*/ - - /** - * System Colors - * note some of these are guesswork - * @see - * */ - String[][] systemColors = { - {"3dDkShadow", "#A0A0A0"},/*(3D Dark System Color) Specifies a Dark shadow color for three-dimensional display elements.*/ - {"3dLight", "#FFFFFF"}, /* (3D Light System Color) Specifies a Light color for three-dimensional display elements (for edges facing the light source).*/ - {"activeBorder", "#B4B4B4"}, /* (Active Border System Color) Specifies an Active Window Border Color.*/ - {"activeCaption", "#99B4D1"}, /* (Active Caption System Color) Specifies the active window title bar color. In particular the left side color in the color gradient of an active - window's title bar if the gradient effect is enabled.*/ - {"appWorkspace", "#ABABAB"}, /* (Application Workspace System Color) Specifies the Background color of multiple document interface (MDI) applications.*/ - {"background", "#FFFFFF"}, /* (Background System Color) Specifies the desktop background color.*/ - {"btnFace", "#F0F0F0"}, /* (Button Face System Color) Specifies the face color for three-dimensional display elements and for dialog box backgrounds.*/ - {"btnHighlight", "#FFFFFF"}, /* (Button Highlight System Color) Specifies the highlight color for three-dimensional display elements (for edges facing the light source).*/ - {"btnShadow", "#A0A0A0"}, /* (Button Shadow System Color) Specifies the shadow color for three-dimensional display elements (for edges facing away from the light source).*/ - {"btnText", "#000000"}, /* (Button Text System Color) Specifies the color of text on push buttons.*/ - {"captionText", "#000000"}, /* (Caption Text System Color) Specifies the color of text in the caption, size box, and scroll bar arrow box.*/ - {"gradientActiveCaption", "#B9D1EA"}, /* (Gradient Active Caption System Color) Specifies the right side color in the color gradient of an active window's title bar.*/ - {"gradientInactiveCaption", "#D7E4F2"}, /* (Gradient Inactive Caption System Color) Specifies the right side color in the color gradient of an inactive window's title bar.*/ - {"grayText", "#6D6D6D"}, /* (Gray Text System Color) Specifies a grayed (disabled) text. This color is set to 0 if the current display driver does not support a solid gray color.*/ - {"highlight", "#0000FF"}, /* (Highlight System Color) Specifies the color of Item(s) selected in a control.*/ - {"highlightText", "#FFFFFF"}, /* (Highlight Text System Color) Specifies the text color of item(s) selected in a control.*/ - {"hotLight", "#0066CC"}, /* (Hot Light System Color) Specifies the color for a hyperlink or hot-tracked item.*/ - {"inactiveBorder", "#F4F7FC"}, /* (Inactive Border System Color) Specifies the color of the Inactive window border.*/ - {"inactiveCaption", "#BFCDDB"}, /* (Inactive Caption System Color) Specifies the color of the Inactive window caption. Specifies the left side color in the color gradient of an - inactive window's title bar if the gradient effect is enabled.*/ - {"inactiveCaptionText", "#434E54"}, /* (Inactive Caption Text System Color) Specifies the color of text in an inactive caption.*/ - {"infoBk", "#FFFFE1"}, /* (Info Back System Color) Specifies the background color for tooltip controls.*/ - {"infoText", "#000000"}, /* (Info Text System Color) Specifies the text color for tooltip controls.*/ - {"menu", "#F0F0F0"}, /* (Menu System Color) Specifies the menu background color. */ - {"menuBar", "#F0F0F0"}, /* (Menu Bar System Color) Specifies the background color for the menu bar when menus appear as flat menus.*/ - {"menuHighlight", "#3399FF"}, /* (Menu Highlight System Color) Specifies the color used to highlight menu items when the menu appears as a flat menu.*/ - {"menuText", "#000000"}, /* (Menu Text System Color) Specifies the color of Text in menus.*/ - {"scrollBar", "#C8C8C8"}, /* (Scroll Bar System Color) Specifies the scroll bar gray area color.*/ - {"window", "#FFFFFF"}, /* (Window System Color) Specifies window background color.*/ - {"windowFrame", "#646464"}, /* (Window Frame System Color) Specifies the window frame color.*/ - {"windowText", "#000000"} /* (Window Text System Color) Specifies the color of text in windows.*/ - - }; - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/OOXMLConstants.kt b/src/main/java/io/starter/formats/OOXML/OOXMLConstants.kt new file mode 100644 index 0000000..26169f7 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/OOXMLConstants.kt @@ -0,0 +1,219 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +/** + * + */ +package io.starter.formats.OOXML + +/** + * + */ +interface OOXMLConstants { + companion object { + val xmlHeader = "" + val xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main" + val pkgrelns = "http://schemas.openxmlformats.org/package/2006/relationships" // .rels ns + val relns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" + val typens = "http://schemas.openxmlformats.org/package/2006/content-types" + val drawingns = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" + val drawingmlns = "http://schemas.openxmlformats.org/drawingml/2006/main" + val chartns = "http://schemas.openxmlformats.org/drawingml/2006/chart" + val drawingDir = "xl/drawings" + val mediaDir = "xl/media" + val chartDir = "xl/charts" + val themeDir = "xl/theme" + + val contentTypes = arrayOf(arrayOf("document", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"), arrayOf("documentMacroEnabled", "application/vnd.ms-excel.sheet.macroEnabled.main+xml"), arrayOf("documentTemplateMacroEnabled", "application/vnd.ms-excel.template.macroEnabled.main+xml"), arrayOf("documentTemplate", "application/vnd.openxmlformats-officedocument.spreadsheetml.template+xml"), /* TODO: is this correct?? */ + arrayOf("sheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"), arrayOf("styles", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"), arrayOf("sst", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"), arrayOf("drawing", "application/vnd.openxmlformats-officedocument.drawing+xml"), arrayOf("chart", "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"), arrayOf("theme", "application/vnd.openxmlformats-officedocument.theme+xml"), arrayOf("themeOverride", "application/vnd.openxmlformats-officedocument.themeOverride+xml"), arrayOf("props", "application/vnd.openxmlformats-package.core-properties+xml"), arrayOf("exprops", "application/vnd.openxmlformats-officedocument.extended-properties+xml"), arrayOf("custprops", "application/vnd.openxmlformats-officedocument.custom-properties+xml"), arrayOf("connections", "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"), arrayOf("calc", "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"), arrayOf("vba", "application/vnd.ms-office.vbaProject"), arrayOf("vdependencies", "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"), arrayOf("table", "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"), arrayOf("vmldrawing", "application/vnd.openxmlformats-officedocument.vmlDrawing"), arrayOf("externalLink", "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"), arrayOf("oleObject", "application/vnd.openxmlformats-officedocument.oleObject"), arrayOf("activeX", "application/vnd.ms-office.activeX+xml"), arrayOf("comments", "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"), arrayOf("image", "image"), // images don't go into Content_Types + arrayOf("userShape", "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"), arrayOf("activeXBinary", "application/vnd.ms-office.activeX"), arrayOf("pivotCacheDefinition", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"), arrayOf("pivotCacheRecords", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"), arrayOf("pivotTable", "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml")) + + val relsContentTypes = arrayOf(arrayOf("document", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"), arrayOf("documentMacroEnabled", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"), arrayOf("documentTemplate", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"), arrayOf("documentTemplateMacroEnabled", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"), arrayOf("sheet", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"), arrayOf("styles", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"), arrayOf("sst", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"), arrayOf("drawing", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"), arrayOf("image", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"), arrayOf("chart", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"), arrayOf("theme", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"), arrayOf("themeOverride", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride"), arrayOf("props", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"), arrayOf("exprops", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"), arrayOf("custprops", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"), arrayOf("connections", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections"), arrayOf("calc", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain"), arrayOf("printerSettings", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings"), arrayOf("vba", "http://schemas.microsoft.com/office/2006/relationships/vbaProject"), arrayOf("vdependencies", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies"), arrayOf("table", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"), arrayOf("vmldrawing", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"), arrayOf("externalLink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink"), arrayOf("externalLinkPath", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLinkPath"), arrayOf("macro", "http://schemas.microsoft.com/office/2006/relationships/xlMacrosheet"), arrayOf("hyperlink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"), arrayOf("activeX", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/control"), arrayOf("oleObject", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"), arrayOf("comments", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"), arrayOf("userShape", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes"), arrayOf("activeXBinary", "http://schemas.microsoft.com/office/2006/relationships/activeXControlBinary"), arrayOf("pivotCacheDefinition", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"), arrayOf("pivotCacheRecords", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords"), arrayOf("pivotTable", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable")) + + val builtInNames = arrayOf("_xlnm.Consolidate_Area", "", //protected static final byte AUTO_OPEN = 0x1; + "", //protected static final byte AUTO_CLOSE = 0x2; + "_xlnm.Extract", "_xlnm.Database", "_xlnm.Criteria", "_xlnm.Print_Area", "_xlnm.Print_Titles", "", //protected static final byte RECORDER = 0x8; + "", //protected static final byte DATA_FORM = 0x9; + "", //protected static final byte AUTO_ACTIVATE = 0xA; + "", //protected static final byte AUTO_DEACTIVATE = 0xB; + "_xlnm.Sheet_Title", "_xlnm._FilterDatabase") + + val patternFill = arrayOf("none", "solid", "mediumGray", "darkGray", "lightGray", "darkHorizontal", "darkVertical", "darkDown", "darkUp", "darkGrid", "darkTrellis", "lightHorizontal", "lightVertical", "lightDown", "lightUp", "lightGrid", "lightTrellis", "gray125", "gray0625") + val horizontalAlignment = arrayOf("general", "left", "center", "right", "fill", "justify", "centerContinuous", // 6 + "distributed") + + val verticalAlignment = arrayOf("top", "center", "bottom", "justify", "distributed") + + val fontScheme = arrayOf("none", "major", "minor") + val cellType = arrayOf("b", // boolean + "n", // number + "e", // error + "s", // shared (sst) string + "str", // formula string + "inlineStr")// + val cellFormulaType = arrayOf("normal", "array", "dataTable", "shared") + val borderStyle = arrayOf("none", "thin", "medium", "dashed", "dotted", "thick", "double", "hair", "mediumDashed", "dashDot", "mediumDashDot", "dashDotDot", "mediumDashDotDot", "slantDashDot") + + val fontFamily = arrayOf("Roman", //1 + "Swiss", "Modern", "Script", "Decorative") + /** + * indexed via ChartConstants Chart Types + * @see ChartHandle.getOOXML + */ + val twoDchartTypes = arrayOf("barChart", // including col and all shaped bar types such as pyramid, cone ... + "barChart", "lineChart", // 2 + "pieChart", // 3 + "areaChart", // 4 + "scatterChart", // 5 + "radarChart", // 6 + "surfaceChart", // 7 + "doughnutChart", // 8 + "bubbleChart", // 9 + "ofPieChart", // 10 + "", // radararea - not used in OOXML + "", // pyrmaid == bar in OOXML + "", /// cylinder "" + "", // cone "" + "", // pyramidbar "" 15 + "", // cylinderbar "" + "", // condebar "" + "stockChart") + /** + * indexed via ChartConstants Chart Types + * @see ChartHandle.getOOXML + */ + val threeDchartTypes = arrayOf("bar3DChart", // including col and all shaped bar types such as pyramid, cone ... + "bar3DChart", "line3DChart", "pie3DChart", "area3DChart", "", // scatter - 5 no 3d + "", // radar - 6 no 3d + "surface3DChart", // 7 + "", // doughnut - 8 - no 3d + "", // bubble - 9 - no 3d + "")// ofPie 10 - no 3d + val legendPos = arrayOf("b", "l", "r", // default + "t", "tr") + /* + public static String[] indexedColors= { + "00000000", + "00FF0000", + "0000FF00", + "000000FF", + "00FFFF00", + "00FF00FF", + "0000FFFF", + "00000000", + "00FF0000", + "0000FF00", + "000000FF", + "00FFFF00", + "00FF00FF", + "0000FFFF", + "00800000", + "00006600", + "00000080", + "00669900", + "00800080", + "00008080", + "00EAEAEA", + "00808080", + "009999FF", + "00993366", + "00FFFFCC", + "00CCFFFF", + "00660066", + "00FF8080", + "000066CC", + "00CCCCFF", + "00000080", + "00FF00FF", + "00FFFF00", + "0000FFFF", + "00800080", + "00800000", + "00008080", + "000000FF", + "0000CCFF", + "00CCFFFF", + "00CCFFCC", + "00FFFF99", + "0099CCFF", + "00FF99CC", + "00CC99FF", + "00FFCC99", + "003366FF", + "0033CCCC", + "0099CC00", + "00FFCC00", + "00FF9900", + "00FF6600", + "00666699", + "00969696", + "00003366", + "00339966", + "00003300", + "00333300", + "00993300", + "00993366", + "00333399", + "00333333", + }; +*/ + + /** + * System Colors + * note some of these are guesswork + * @see + */ + val systemColors = arrayOf(arrayOf("3dDkShadow", "#A0A0A0"), /*(3D Dark System Color) Specifies a Dark shadow color for three-dimensional display elements.*/ + arrayOf("3dLight", "#FFFFFF"), /* (3D Light System Color) Specifies a Light color for three-dimensional display elements (for edges facing the light source).*/ + arrayOf("activeBorder", "#B4B4B4"), /* (Active Border System Color) Specifies an Active Window Border Color.*/ + arrayOf("activeCaption", "#99B4D1"), /* (Active Caption System Color) Specifies the active window title bar color. In particular the left side color in the color gradient of an active + window's title bar if the gradient effect is enabled.*/ + arrayOf("appWorkspace", "#ABABAB"), /* (Application Workspace System Color) Specifies the Background color of multiple document interface (MDI) applications.*/ + arrayOf("background", "#FFFFFF"), /* (Background System Color) Specifies the desktop background color.*/ + arrayOf("btnFace", "#F0F0F0"), /* (Button Face System Color) Specifies the face color for three-dimensional display elements and for dialog box backgrounds.*/ + arrayOf("btnHighlight", "#FFFFFF"), /* (Button Highlight System Color) Specifies the highlight color for three-dimensional display elements (for edges facing the light source).*/ + arrayOf("btnShadow", "#A0A0A0"), /* (Button Shadow System Color) Specifies the shadow color for three-dimensional display elements (for edges facing away from the light source).*/ + arrayOf("btnText", "#000000"), /* (Button Text System Color) Specifies the color of text on push buttons.*/ + arrayOf("captionText", "#000000"), /* (Caption Text System Color) Specifies the color of text in the caption, size box, and scroll bar arrow box.*/ + arrayOf("gradientActiveCaption", "#B9D1EA"), /* (Gradient Active Caption System Color) Specifies the right side color in the color gradient of an active window's title bar.*/ + arrayOf("gradientInactiveCaption", "#D7E4F2"), /* (Gradient Inactive Caption System Color) Specifies the right side color in the color gradient of an inactive window's title bar.*/ + arrayOf("grayText", "#6D6D6D"), /* (Gray Text System Color) Specifies a grayed (disabled) text. This color is set to 0 if the current display driver does not support a solid gray color.*/ + arrayOf("highlight", "#0000FF"), /* (Highlight System Color) Specifies the color of Item(s) selected in a control.*/ + arrayOf("highlightText", "#FFFFFF"), /* (Highlight Text System Color) Specifies the text color of item(s) selected in a control.*/ + arrayOf("hotLight", "#0066CC"), /* (Hot Light System Color) Specifies the color for a hyperlink or hot-tracked item.*/ + arrayOf("inactiveBorder", "#F4F7FC"), /* (Inactive Border System Color) Specifies the color of the Inactive window border.*/ + arrayOf("inactiveCaption", "#BFCDDB"), /* (Inactive Caption System Color) Specifies the color of the Inactive window caption. Specifies the left side color in the color gradient of an + inactive window's title bar if the gradient effect is enabled.*/ + arrayOf("inactiveCaptionText", "#434E54"), /* (Inactive Caption Text System Color) Specifies the color of text in an inactive caption.*/ + arrayOf("infoBk", "#FFFFE1"), /* (Info Back System Color) Specifies the background color for tooltip controls.*/ + arrayOf("infoText", "#000000"), /* (Info Text System Color) Specifies the text color for tooltip controls.*/ + arrayOf("menu", "#F0F0F0"), /* (Menu System Color) Specifies the menu background color. */ + arrayOf("menuBar", "#F0F0F0"), /* (Menu Bar System Color) Specifies the background color for the menu bar when menus appear as flat menus.*/ + arrayOf("menuHighlight", "#3399FF"), /* (Menu Highlight System Color) Specifies the color used to highlight menu items when the menu appears as a flat menu.*/ + arrayOf("menuText", "#000000"), /* (Menu Text System Color) Specifies the color of Text in menus.*/ + arrayOf("scrollBar", "#C8C8C8"), /* (Scroll Bar System Color) Specifies the scroll bar gray area color.*/ + arrayOf("window", "#FFFFFF"), /* (Window System Color) Specifies window background color.*/ + arrayOf("windowFrame", "#646464"), /* (Window Frame System Color) Specifies the window frame color.*/ + arrayOf("windowText", "#000000") /* (Window Text System Color) Specifies the color of text in windows.*/) + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/OOXMLElement.java b/src/main/java/io/starter/formats/OOXML/OOXMLElement.kt similarity index 73% rename from src/main/java/io/starter/formats/OOXML/OOXMLElement.java rename to src/main/java/io/starter/formats/OOXML/OOXMLElement.kt index 52b4e08..0ff879b 100644 --- a/src/main/java/io/starter/formats/OOXML/OOXMLElement.java +++ b/src/main/java/io/starter/formats/OOXML/OOXMLElement.kt @@ -20,18 +20,18 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.OOXML; +package io.starter.formats.OOXML -import java.io.Serializable; +import java.io.Serializable -public interface OOXMLElement extends Serializable { +interface OOXMLElement : Serializable { /** - * @see io.starter.formats.OOXML.OOXMLElement#getOOXML() + * @see io.starter.formats.OOXML.OOXMLElement.getOOXML */ - String getOOXML(); + val ooxml: String /** - * @see io.starter.formats.OOXML.OOXMLElement#cloneElement() + * @see io.starter.formats.OOXML.OOXMLElement.cloneElement */ - OOXMLElement cloneElement(); + fun cloneElement(): OOXMLElement } diff --git a/src/main/java/io/starter/formats/OOXML/OOXMLHandle.java b/src/main/java/io/starter/formats/OOXML/OOXMLHandle.kt similarity index 60% rename from src/main/java/io/starter/formats/OOXML/OOXMLHandle.java rename to src/main/java/io/starter/formats/OOXML/OOXMLHandle.kt index 1c97079..8a5c19c 100644 --- a/src/main/java/io/starter/formats/OOXML/OOXMLHandle.java +++ b/src/main/java/io/starter/formats/OOXML/OOXMLHandle.kt @@ -20,23 +20,23 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.OOXML; +package io.starter.formats.OOXML -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.CellNotFoundException; -import io.starter.formats.XLS.OOXMLReader; -import io.starter.formats.XLS.OOXMLWriter; -import io.starter.toolkit.StringTool; -import org.xmlpull.v1.XmlPullParserException; +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.CellNotFoundException +import io.starter.formats.XLS.OOXMLReader +import io.starter.formats.XLS.OOXMLWriter +import io.starter.toolkit.StringTool +import org.xmlpull.v1.XmlPullParserException -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; +import java.io.File +import java.io.FileOutputStream +import java.io.IOException /** * */ -public class OOXMLHandle { +object OOXMLHandle { /** * generates OOXML for a workbook @@ -47,23 +47,26 @@ public class OOXMLHandle { * @param bk workbookhandle * @param path output filename and path */ - public static void getOOXML(WorkBookHandle bk, String path) - throws IOException { + @Throws(IOException::class) + fun getOOXML(bk: WorkBookHandle, path: String) { + var path = path try { if (!io.starter.formats.XLS.OOXMLAdapter.hasMacros(bk)) - path = StringTool.replaceExtension(path, ".xlsx"); - else // it's a macro-enabled workbook - path = StringTool.replaceExtension(path, ".xlsm"); + path = StringTool.replaceExtension(path, ".xlsx") + else + // it's a macro-enabled workbook + path = StringTool.replaceExtension(path, ".xlsm") - java.io.File fout = new java.io.File(path); - File dirs = fout.getParentFile(); + val fout = java.io.File(path) + val dirs = fout.parentFile if (dirs != null && !dirs.exists()) - dirs.mkdirs(); - OOXMLWriter oe = new OOXMLWriter(); - oe.getOOXML(bk, new FileOutputStream(path)); - } catch (Exception e) { - throw new IOException("Error parsing OOXML file: " + e.toString()); + dirs.mkdirs() + val oe = OOXMLWriter() + oe.getOOXML(bk, FileOutputStream(path)) + } catch (e: Exception) { + throw IOException("Error parsing OOXML file: $e") } + } /** @@ -75,8 +78,9 @@ public static void getOOXML(WorkBookHandle bk, String path) * @throws IOException * @throws CellNotFoundException */ - public static void parseNBind(WorkBookHandle bk, String fName) throws XmlPullParserException, IOException, CellNotFoundException { - OOXMLReader oe = new OOXMLReader(); - oe.parseNBind(bk, fName); + @Throws(XmlPullParserException::class, IOException::class, CellNotFoundException::class) + fun parseNBind(bk: WorkBookHandle, fName: String) { + val oe = OOXMLReader() + oe.parseNBind(bk, fName) } } diff --git a/src/main/java/io/starter/formats/OOXML/ObjectChoice.java b/src/main/java/io/starter/formats/OOXML/ObjectChoice.java deleted file mode 100644 index c28d4ac..0000000 --- a/src/main/java/io/starter/formats/OOXML/ObjectChoice.java +++ /dev/null @@ -1,437 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * One of: cxnSp, graphicFrame, grpSp, pic, sp - */ -public class ObjectChoice implements OOXMLElement { - - private static final long serialVersionUID = 3548474869557092714L; - private CxnSp cxnSp = null; - private GraphicFrame graphicFrame = null; - private GrpSp grpSp = null; - private Pic pic = null; - private Sp sp = null; - - public ObjectChoice() { - - } - - public ObjectChoice(CxnSp c, GraphicFrame g, GrpSp grp, Pic p, Sp s) { - this.cxnSp = c; - this.graphicFrame = g; - this.grpSp = grp; - this.pic = p; - this.sp = s; - } - - public ObjectChoice(ObjectChoice oc) { - this.cxnSp = oc.cxnSp; - this.graphicFrame = oc.graphicFrame; - this.grpSp = oc.grpSp; - this.pic = oc.pic; - this.sp = oc.sp; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - CxnSp c = null; - GraphicFrame g = null; - GrpSp grp = null; - Pic p = null; - Sp s = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cxnSp")) { // connection shape - lastTag.push(tnm); - c = (CxnSp) CxnSp.parseOOXML(xpp, lastTag, bk); - break; - } else if (tnm.equals("graphicFrame")) { // graphic data usually chart - lastTag.push(tnm); - g = (GraphicFrame) GraphicFrame.parseOOXML(xpp, lastTag); - break; - } else if (tnm.equals("grpSp")) { // group shape - combines one or more of sp/pic/graphicFrame/cxnSp - lastTag.push(tnm); - grp = GrpSp.parseOOXML(xpp, lastTag, bk); - break; - } else if (tnm.equals("sp")) { // shape - lastTag.push(tnm); - s = (Sp) Sp.parseOOXML(xpp, lastTag, bk); - break; - } else if (tnm.equals("pic")) { // picture/image - lastTag.push(tnm); - p = (Pic) Pic.parseOOXML(xpp, lastTag, bk); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ObjectChoice.parseOOXML: " + e.toString()); - } - ObjectChoice o = new ObjectChoice(c, g, grp, p, s); - return o; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (cxnSp != null) ooxml.append(cxnSp.getOOXML()); - if (graphicFrame != null) ooxml.append(graphicFrame.getOOXML()); - if (grpSp != null) ooxml.append(grpSp.getOOXML()); - if (pic != null) ooxml.append(pic.getOOXML()); - if (sp != null) ooxml.append(sp.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new ObjectChoice(this); - } - - /** - * return if this Object Choice refers to an image rather than a chart or shape - * - * @return - */ - public boolean hasImage() { - // o will be a pic element or a group shape containing a pic element, it's blipFill.blip child references the rId of the embedded file - return this.getEmbed() != null; - } - - /** - * return if this Object Choice refers to a shape, as opposed a chart or an image - * - * @return - */ - public boolean hasShape() { - return (cxnSp != null || sp != null || (grpSp != null && grpSp.hasShape())); - } - - /** - * return if this Object Choice element refers to a chart as opposed to a shape or image - * - * @return - */ - public boolean hasChart() { - return this.getChartRId() != null; - } - - /** - * get cNvPr name attribute - * - * @return - */ - public String getName() { - if (cxnSp != null) - return cxnSp.getName(); - else if (sp != null) - return sp.getName(); - else if (pic != null) - return pic.getName(); - else if (graphicFrame != null) - return graphicFrame.getName(); - else if (grpSp != null) - return grpSp.getName(); - return null; - } - - /** - * set cNvPr name attribute - * - * @param name - */ - public void setName(String name) { - if (cxnSp != null) - cxnSp.setName(name); - else if (sp != null) - sp.setName(name); - else if (pic != null) - pic.setName(name); - else if (graphicFrame != null) - graphicFrame.setName(name); - else if (grpSp != null) - grpSp.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (cxnSp != null) - return cxnSp.getDescr(); - else if (sp != null) - return sp.getDescr(); - else if (pic != null) - return pic.getDescr(); - else if (graphicFrame != null) - return graphicFrame.getDescr(); - else if (grpSp != null) - return grpSp.getDescr(); - return null; - } - - /** - * set cNvPr description attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setDescr(String descr) { - if (cxnSp != null) - cxnSp.setDescr(descr); - else if (sp != null) - sp.setDescr(descr); - else if (pic != null) - pic.setDescr(descr); - else if (graphicFrame != null) - graphicFrame.setDescr(descr); - else if (grpSp != null) - grpSp.setDescr(descr); - } - - /** - * get macro attribute (valid for cnxSp, sp and graphicFrame) - * - * @return - */ - public String getMacro() { - if (cxnSp != null) - return cxnSp.getMacro(); - else if (graphicFrame != null) - return graphicFrame.getMacro(); - else if (sp != null) - return sp.getMacro(); - return null; - } - - /** - * set Macro attribute (valid for cnxSp, sp and graphicFrame) - * sometimes associated with shape name - * - * @param descr - */ - public void setMacro(String macro) { - if (cxnSp != null) - cxnSp.setMacro(macro); - else if (graphicFrame != null) - graphicFrame.setMacro(macro); - else if (sp != null) - sp.setMacro(macro); - else if (grpSp != null) - grpSp.setMacro(macro); - } - - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (graphicFrame != null) - return graphicFrame.getURI(); - return null; - } - - /** - * set the URI associated with this graphic data - * - * @param uri - */ - public void setURI(String uri) { - if (graphicFrame != null) - graphicFrame.setURI(uri); - else if (grpSp != null) - grpSp.setURI(uri); - } - - /** - * return the rid for the embedded object (picture or picture shape) (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (sp != null) - return sp.getEmbed(); // embedded blip/pict - else if (pic != null) - return pic.getEmbed(); // embedded image - else if (grpSp != null) - return grpSp.getEmbed(); // group shape embedded image - return null; - } - - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (sp != null) - return sp.getLink(); - else if (pic != null) - return pic.getLink(); - else if (grpSp != null) - return grpSp.getLink(); - return null; - } - - /** - * set the rid for this object (picture or picture shape) (resides within the file) - * - * @param embed - */ - public void setEmbed(String embed) { - if (sp != null) sp.setEmbed(embed); - else if (pic != null) pic.setEmbed(embed); - else if (grpSp != null) grpSp.setEmbed(embed); - } - - /** - * set the rid for this chart (resides within the file) - * - * @param rId - */ - public void setChartRId(String rId) { - if (graphicFrame != null) graphicFrame.setChartRId(rId); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param link - */ - public void setLink(String link) { - if (sp != null) sp.setLink(link); - else if (pic != null) pic.setLink(link); - else if (grpSp != null) grpSp.setLink(link); - } - - /** - * return the rid of the chart element, if exists - * - * @return - */ - public String getChartRId() { - if (graphicFrame != null) - return graphicFrame.getChartRId(); - else if (grpSp != null) - return grpSp.getChartRId(); - return null; - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (sp != null) - sp.setId(id); // embedded blip/pict - else if (pic != null) - pic.setId(id); // embedded image - else if (graphicFrame != null) - graphicFrame.setId(id); // chart - else if (cxnSp != null) - cxnSp.setId(id); - else if (grpSp != null) - grpSp.setId(id); // embedded image - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (sp != null) return sp.getId(); // embedded blip/pict - else if (pic != null) - return pic.getId(); // embedded image - else if (graphicFrame != null) - return graphicFrame.getId(); // chart - else if (cxnSp != null) - return cxnSp.getId(); - else if (grpSp != null) - return grpSp.getId(); // embedded image - return -1; - } - - /** - * utility to return the shape properties element for this picture - * should be depreciated when OOXML is completely distinct from BIFF8 - * - * @return - */ - public SpPr getSppr() { - if (pic != null) - return pic.getSppr(); - else if (grpSp != null) - return grpSp.getSppr(); - return null; - } - - /** - * return the actual object associated with this ObjectChoice - * - * @return - */ - public Object getObject() { - if (cxnSp != null) - return cxnSp; - else if (graphicFrame != null) - return graphicFrame; - else if (pic != null) - return pic; - else if (sp != null) - return sp; - else if (grpSp != null) - return grpSp; - return null; - } - - /** - * set the object associated with this ObjectChoice - * - * @param o - */ - public void setObject(Object o) { - if (o instanceof GraphicFrame) { - graphicFrame = (GraphicFrame) o; - } else if (o instanceof Pic) { - pic = (Pic) o; - } else if (o instanceof Sp) { - sp = (Sp) o; - } else if (o instanceof CxnSp) { - cxnSp = (CxnSp) o; - } else if (o instanceof GrpSp) { - grpSp = (GrpSp) o; - } - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/ObjectChoice.kt b/src/main/java/io/starter/formats/OOXML/ObjectChoice.kt new file mode 100644 index 0000000..d85d69c --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/ObjectChoice.kt @@ -0,0 +1,454 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * One of: cxnSp, graphicFrame, grpSp, pic, sp + */ +class ObjectChoice : OOXMLElement { + private var cxnSp: CxnSp? = null + private var graphicFrame: GraphicFrame? = null + private var grpSp: GrpSp? = null + private var pic: Pic? = null + private var sp: Sp? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + if (cxnSp != null) ooxml.append(cxnSp!!.ooxml) + if (graphicFrame != null) ooxml.append(graphicFrame!!.ooxml) + if (grpSp != null) ooxml.append(grpSp!!.ooxml) + if (pic != null) ooxml.append(pic!!.ooxml) + if (sp != null) ooxml.append(sp!!.ooxml) + return ooxml.toString() + } + + /** + * get cNvPr name attribute + * + * @return + */ + /** + * set cNvPr name attribute + * + * @param name + */ + var name: String? + get() { + if (cxnSp != null) + return cxnSp!!.name + else if (sp != null) + return sp!!.name + else if (pic != null) + return pic!!.name + else if (graphicFrame != null) + return graphicFrame!!.name + else if (grpSp != null) + return grpSp!!.name + return null + } + set(name) { + if (cxnSp != null) + cxnSp!!.name = name + else if (sp != null) + sp!!.name = name + else if (pic != null) + pic!!.name = name + else if (graphicFrame != null) + graphicFrame!!.name = name + else if (grpSp != null) + grpSp!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + var descr: String? + get() { + if (cxnSp != null) + return cxnSp!!.descr + else if (sp != null) + return sp!!.descr + else if (pic != null) + return pic!!.descr + else if (graphicFrame != null) + return graphicFrame!!.descr + else if (grpSp != null) + return grpSp!!.descr + return null + } + set(descr) { + if (cxnSp != null) + cxnSp!!.descr = descr + else if (sp != null) + sp!!.descr = descr + else if (pic != null) + pic!!.descr = descr + else if (graphicFrame != null) + graphicFrame!!.descr = descr + else if (grpSp != null) + grpSp!!.descr = descr + } + + /** + * get macro attribute (valid for cnxSp, sp and graphicFrame) + * + * @return + */ + /** + * set Macro attribute (valid for cnxSp, sp and graphicFrame) + * sometimes associated with shape name + * + * @param descr + */ + var macro: String? + get() { + if (cxnSp != null) + return cxnSp!!.macro + else if (graphicFrame != null) + return graphicFrame!!.macro + else if (sp != null) + return sp!!.macro + return null + } + set(macro) { + if (cxnSp != null) + cxnSp!!.macro = macro + else if (graphicFrame != null) + graphicFrame!!.macro = macro + else if (sp != null) + sp!!.macro = macro + else if (grpSp != null) + grpSp!!.macro = macro + } + + /** + * get the URI associated with this graphic Data + */ + /** + * set the URI associated with this graphic data + * + * @param uri + */ + var uri: String? + get() = if (graphicFrame != null) graphicFrame!!.uri else null + set(uri) { + if (graphicFrame != null) + graphicFrame!!.uri = uri + else if (grpSp != null) + grpSp!!.setURI(uri) + } + + /** + * return the rid for the embedded object (picture or picture shape) (i.e. resides within the file) + * + * @return + */ + /** + * set the rid for this object (picture or picture shape) (resides within the file) + * + * @param embed + */ + // embedded blip/pict + // embedded image + // group shape embedded image + var embed: String? + get() { + if (sp != null) + return sp!!.embed + else if (pic != null) + return pic!!.embed + else if (grpSp != null) + return grpSp!!.embed + return null + } + set(embed) { + if (sp != null) + sp!!.embed = embed + else if (pic != null) + pic!!.embed = embed + else if (grpSp != null) grpSp!!.embed = embed + } + + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param link + */ + var link: String? + get() { + if (sp != null) + return sp!!.link + else if (pic != null) + return pic!!.link + else if (grpSp != null) + return grpSp!!.link + return null + } + set(link) { + if (sp != null) + sp!!.link = link + else if (pic != null) + pic!!.link = link + else if (grpSp != null) grpSp!!.link = link + } + + /** + * return the rid of the chart element, if exists + * + * @return + */ + /** + * set the rid for this chart (resides within the file) + * + * @param rId + */ + var chartRId: String? + get() { + if (graphicFrame != null) + return graphicFrame!!.chartRId + else if (grpSp != null) + return grpSp!!.chartRId + return null + } + set(rId) { + if (graphicFrame != null) graphicFrame!!.chartRId = rId + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + // embedded blip/pict + // embedded image + // chart + // embedded image + // embedded blip/pict + // embedded image + // chart + // embedded image + var id: Int + get() { + if (sp != null) + return sp!!.id + else if (pic != null) + return pic!!.id + else if (graphicFrame != null) + return graphicFrame!!.id + else if (cxnSp != null) + return cxnSp!!.id + else if (grpSp != null) + return grpSp!!.id + return -1 + } + set(id) { + if (sp != null) + sp!!.id = id + else if (pic != null) + pic!!.id = id + else if (graphicFrame != null) + graphicFrame!!.id = id + else if (cxnSp != null) + cxnSp!!.id = id + else if (grpSp != null) + grpSp!!.id = id + } + + /** + * utility to return the shape properties element for this picture + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + val sppr: SpPr? + get() { + if (pic != null) + return pic!!.sppr + else if (grpSp != null) + return grpSp!!.sppr + return null + } + + /** + * return the actual object associated with this ObjectChoice + * + * @return + */ + /** + * set the object associated with this ObjectChoice + * + * @param o + */ + var `object`: Any? + get() { + if (cxnSp != null) + return cxnSp + else if (graphicFrame != null) + return graphicFrame + else if (pic != null) + return pic + else if (sp != null) + return sp + else if (grpSp != null) + return grpSp + return null + } + set(o) { + if (o is GraphicFrame) { + graphicFrame = o + } else if (o is Pic) { + pic = o + } else if (o is Sp) { + sp = o + } else if (o is CxnSp) { + cxnSp = o + } else if (o is GrpSp) { + grpSp = o + } + } + + constructor() { + + } + + constructor(c: CxnSp, g: GraphicFrame, grp: GrpSp, p: Pic, s: Sp) { + this.cxnSp = c + this.graphicFrame = g + this.grpSp = grp + this.pic = p + this.sp = s + } + + constructor(oc: ObjectChoice) { + this.cxnSp = oc.cxnSp + this.graphicFrame = oc.graphicFrame + this.grpSp = oc.grpSp + this.pic = oc.pic + this.sp = oc.sp + } + + override fun cloneElement(): OOXMLElement { + return ObjectChoice(this) + } + + /** + * return if this Object Choice refers to an image rather than a chart or shape + * + * @return + */ + fun hasImage(): Boolean { + // o will be a pic element or a group shape containing a pic element, it's blipFill.blip child references the rId of the embedded file + return this.embed != null + } + + /** + * return if this Object Choice refers to a shape, as opposed a chart or an image + * + * @return + */ + fun hasShape(): Boolean { + return cxnSp != null || sp != null || grpSp != null && grpSp!!.hasShape() + } + + /** + * return if this Object Choice element refers to a chart as opposed to a shape or image + * + * @return + */ + fun hasChart(): Boolean { + return this.chartRId != null + } + + companion object { + + private val serialVersionUID = 3548474869557092714L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var c: CxnSp? = null + var g: GraphicFrame? = null + var grp: GrpSp? = null + var p: Pic? = null + var s: Sp? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cxnSp") { // connection shape + lastTag.push(tnm) + c = CxnSp.parseOOXML(xpp, lastTag, bk) as CxnSp + break + } else if (tnm == "graphicFrame") { // graphic data usually chart + lastTag.push(tnm) + g = GraphicFrame.parseOOXML(xpp, lastTag) as GraphicFrame + break + } else if (tnm == "grpSp") { // group shape - combines one or more of sp/pic/graphicFrame/cxnSp + lastTag.push(tnm) + grp = GrpSp.parseOOXML(xpp, lastTag, bk) + break + } else if (tnm == "sp") { // shape + lastTag.push(tnm) + s = Sp.parseOOXML(xpp, lastTag, bk) as Sp + break + } else if (tnm == "pic") { // picture/image + lastTag.push(tnm) + p = Pic.parseOOXML(xpp, lastTag, bk) as Pic + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("ObjectChoice.parseOOXML: $e") + } + + return ObjectChoice(c, g, grp, p, s) + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/OOXML/OneCellAnchor.java b/src/main/java/io/starter/formats/OOXML/OneCellAnchor.java deleted file mode 100644 index 2c92142..0000000 --- a/src/main/java/io/starter/formats/OOXML/OneCellAnchor.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * oneCellAnchor (One Cell Anchor Shape Size) - *

                  - * This element specifies a one cell anchor placeholder for a group, a shape, or a drawing element. It moves with - * the cell and its extents is in EMU units. - *

                  - * parent: wsDr - * children: from, ext, OBJECTCHOICES (sp, grpSp, graphicFrame, cxnSp, pic), clientData - */ -//TODO: finish grpSp Group Shape -// TODO: finish clientData element -public class OneCellAnchor implements OOXMLElement { - - private static final long serialVersionUID = -8498556079325357165L; - public static final short EMU = 1270; - private From from; - private Ext ext; - private ObjectChoice objectChoice; - - public OneCellAnchor(From f, Ext e, ObjectChoice o) { - this.from = f; - this.ext = e; - this.objectChoice = o; - } - - public OneCellAnchor(OneCellAnchor oca) { - this.from = oca.from; - this.ext = oca.ext; - this.objectChoice = oca.objectChoice; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - From f = null; - Ext e = null; - ObjectChoice o = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("from")) { - lastTag.push(tnm); - f = From.parseOOXML(xpp, lastTag); - } else if (tnm.equals("ext")) { - lastTag.push(tnm); - e = (Ext) Ext.parseOOXML(xpp, lastTag).cloneElement(); - //e.setNS("xdr"); - } else if (tnm.equals("cxnSp") || // connection shape - tnm.equals("graphicFrame") || - tnm.equals("grpSp") || // group shape - tnm.equals("pic") || // picture - tnm.equals("sp")) { // shape - lastTag.push(tnm); - o = (ObjectChoice) ObjectChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("oneCellAnchor")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception ex) { - Logger.logErr("oneCellAnchor.parseOOXML: " + ex.toString()); - } - OneCellAnchor oca = new OneCellAnchor(f, e, o); - return oca; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (from != null) ooxml.append(from.getOOXML()); - ooxml.append(ext.getOOXML()); - ooxml.append(objectChoice.getOOXML()); - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new OneCellAnchor(this); - } - - // access methods ****** - - /** - * return the bounds of this object - * bounds represent: COL, COLOFFSET, ROW, ROWOFFST - * - * @return bounds short[4] - */ - public short[] getBounds() { - short[] bounds = new short[8]; - System.arraycopy(from.getBounds(), 0, bounds, 0, 4); // from bounds - return bounds; - } - - /** - * set the bounds of this object - * bounds represent: COL, COLOFFSET, ROW, ROWOFFST - * - * @param bounds short[4] - */ - public void setBounds(int[] bounds) { - int[] b = new int[4]; - System.arraycopy(bounds, 0, b, 0, 4); - if (from == null) from = new From(b); - else from.setBounds(b); - } - - /** - * get cNvPr name attribute - * - * @return - */ - public String getName() { - if (objectChoice != null) - return objectChoice.getName(); - return null; - } - - /** - * set cNvPr name attribute - * - * @param name - */ - public void setName(String name) { - if (objectChoice != null) - objectChoice.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (objectChoice != null) - return objectChoice.getDescr(); - return null; - } - - /** - * set cNvPr descr attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setDescr(String descr) { - if (objectChoice != null) - objectChoice.setDescr(descr); - } - - /** - * get macro attribute - * - * @return - */ - public String getMacro() { - if (objectChoice != null) - return objectChoice.getMacro(); - return null; - } - - /** - * set Macro attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setMacro(String macro) { - if (objectChoice != null) - objectChoice.setMacro(macro); - } - - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (objectChoice != null) - return objectChoice.getURI(); - return null; - } - - /** - * set the URI associated with this graphic data - * - * @param uri - */ - public void setURI(String uri) { - if (objectChoice != null) - objectChoice.setURI(uri); - } - - /** - * return the id for the embedded picture, shape or chart (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (objectChoice != null) - return objectChoice.getEmbed(); - return null; - } - - /** - * return the id for the linked object (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (objectChoice != null) - return objectChoice.getLink(); - return null; - } - - /** - * set the embed or rId attribute for the embedded picture, shape or chart (i.e. resides within the file) - * - * @param embed - */ - public void setEmbed(String embed) { - if (objectChoice != null) objectChoice.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (objectChoice != null) objectChoice.setLink(link); - } - - /** - * return if this oneCellAnchor element refers to an image rather than a chart or shape - * - * @return - */ - public boolean hasImage() { - if (objectChoice != null) // o will be a pic element, it's blipFill.blip child references the rId of the embedded file - return ((objectChoice.getObject() instanceof Pic) && objectChoice.getEmbed() != null); - return false; - } - - /** - * utility to return the shape properties element (picture element only) - * should be depreciated when OOXML is moved into ImageHandle - * - * @return - */ - public SpPr getSppr() { - if (objectChoice != null) - return objectChoice.getSppr(); - return null; - } - - /** - * return if this oneCellAnchor element refers to a chart as opposed to a shape or image - * - * @return - */ - public boolean hasChart() { - if (objectChoice != null) - return (objectChoice.getObject() instanceof GraphicFrame && objectChoice.getChartRId() != null); - return false; - } - - /** - * return if this oneCellAnchor element refers to a shape, as opposed a chart or an image - * - * @return - */ - public boolean hasShape() { - if (objectChoice != null) - return (objectChoice.getObject() instanceof CxnSp || objectChoice.getObject() instanceof Sp); - return false; - } - - /** - * set this oneCellAnchor as a chart element - * - * @param rid - * @param name - * @param bounds - */ - public void setAsChart(int rid, String name, int[] bounds) { - objectChoice = new ObjectChoice(); - objectChoice.setObject(new GraphicFrame()); - objectChoice.setName(name); - objectChoice.setEmbed("rId" + Integer.valueOf(rid).toString()); - objectChoice.setId(rid); - this.setBounds(bounds); - // id??? - } - - /** - * set this oneCellAnchor as an image - * - * @param rid - * @param name - * @param id - */ - public void setAsImage(String rid, String name, String id) { - ObjectChoice o = new ObjectChoice(); - o.setObject(new Pic()); - o.setName(name); - o.setEmbed(rid); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/OneCellAnchor.kt b/src/main/java/io/starter/formats/OOXML/OneCellAnchor.kt new file mode 100644 index 0000000..1d541b8 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/OneCellAnchor.kt @@ -0,0 +1,324 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * oneCellAnchor (One Cell Anchor Shape Size) + * + * + * This element specifies a one cell anchor placeholder for a group, a shape, or a drawing element. It moves with + * the cell and its extents is in EMU units. + * + * + * parent: wsDr + * children: from, ext, OBJECTCHOICES (sp, grpSp, graphicFrame, cxnSp, pic), clientData + */ +//TODO: finish grpSp Group Shape +// TODO: finish clientData element +class OneCellAnchor : OOXMLElement { + private var from: From? = null + private var ext: Ext? = null + private var objectChoice: ObjectChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (from != null) ooxml.append(from!!.ooxml) + ooxml.append(ext!!.ooxml) + ooxml.append(objectChoice!!.ooxml) + ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + // access methods ****** + + /** + * return the bounds of this object + * bounds represent: COL, COLOFFSET, ROW, ROWOFFST + * + * @return bounds short[4] + */ + // from bounds + val bounds: ShortArray + get() { + val bounds = ShortArray(8) + System.arraycopy(from!!.bounds, 0, bounds, 0, 4) + return bounds + } + + /** + * get cNvPr name attribute + * + * @return + */ + /** + * set cNvPr name attribute + * + * @param name + */ + var name: String? + get() = if (objectChoice != null) objectChoice!!.name else null + set(name) { + if (objectChoice != null) + objectChoice!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr descr attribute + * sometimes associated with shape name + * + * @param descr + */ + var descr: String? + get() = if (objectChoice != null) objectChoice!!.descr else null + set(descr) { + if (objectChoice != null) + objectChoice!!.descr = descr + } + + /** + * get macro attribute + * + * @return + */ + /** + * set Macro attribute + * sometimes associated with shape name + * + * @param descr + */ + var macro: String? + get() = if (objectChoice != null) objectChoice!!.macro else null + set(macro) { + if (objectChoice != null) + objectChoice!!.macro = macro + } + + /** + * get the URI associated with this graphic Data + */ + /** + * set the URI associated with this graphic data + * + * @param uri + */ + var uri: String? + get() = if (objectChoice != null) objectChoice!!.uri else null + set(uri) { + if (objectChoice != null) + objectChoice!!.uri = uri + } + + /** + * return the id for the embedded picture, shape or chart (i.e. resides within the file) + * + * @return + */ + /** + * set the embed or rId attribute for the embedded picture, shape or chart (i.e. resides within the file) + * + * @param embed + */ + var embed: String? + get() = if (objectChoice != null) objectChoice!!.embed else null + set(embed) { + if (objectChoice != null) objectChoice!!.embed = embed + } + + /** + * return the id for the linked object (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + var link: String? + get() = if (objectChoice != null) objectChoice!!.link else null + set(link) { + if (objectChoice != null) objectChoice!!.link = link + } + + /** + * utility to return the shape properties element (picture element only) + * should be depreciated when OOXML is moved into ImageHandle + * + * @return + */ + val sppr: SpPr? + get() = if (objectChoice != null) objectChoice!!.sppr else null + + constructor(f: From, e: Ext, o: ObjectChoice) { + this.from = f + this.ext = e + this.objectChoice = o + } + + constructor(oca: OneCellAnchor) { + this.from = oca.from + this.ext = oca.ext + this.objectChoice = oca.objectChoice + } + + override fun cloneElement(): OOXMLElement { + return OneCellAnchor(this) + } + + /** + * set the bounds of this object + * bounds represent: COL, COLOFFSET, ROW, ROWOFFST + * + * @param bounds short[4] + */ + fun setBounds(bounds: IntArray) { + val b = IntArray(4) + System.arraycopy(bounds, 0, b, 0, 4) + if (from == null) + from = From(b) + else + from!!.bounds = b + } + + /** + * return if this oneCellAnchor element refers to an image rather than a chart or shape + * + * @return + */ + fun hasImage(): Boolean { + return if (objectChoice != null) objectChoice!!.`object` is Pic && objectChoice!!.embed != null else false + } + + /** + * return if this oneCellAnchor element refers to a chart as opposed to a shape or image + * + * @return + */ + fun hasChart(): Boolean { + return if (objectChoice != null) objectChoice!!.`object` is GraphicFrame && objectChoice!!.chartRId != null else false + } + + /** + * return if this oneCellAnchor element refers to a shape, as opposed a chart or an image + * + * @return + */ + fun hasShape(): Boolean { + return if (objectChoice != null) objectChoice!!.`object` is CxnSp || objectChoice!!.`object` is Sp else false + } + + /** + * set this oneCellAnchor as a chart element + * + * @param rid + * @param name + * @param bounds + */ + fun setAsChart(rid: Int, name: String, bounds: IntArray) { + objectChoice = ObjectChoice() + objectChoice!!.`object` = GraphicFrame() + objectChoice!!.name = name + objectChoice!!.embed = "rId" + Integer.valueOf(rid)!!.toString() + objectChoice!!.id = rid + this.setBounds(bounds) + // id??? + } + + /** + * set this oneCellAnchor as an image + * + * @param rid + * @param name + * @param id + */ + fun setAsImage(rid: String, name: String, id: String) { + val o = ObjectChoice() + o.`object` = Pic() + o.name = name + o.embed = rid + } + + companion object { + + private val serialVersionUID = -8498556079325357165L + val EMU: Short = 1270 + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var f: From? = null + var e: Ext? = null + var o: ObjectChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "from") { + lastTag.push(tnm) + f = From.parseOOXML(xpp, lastTag) + } else if (tnm == "ext") { + lastTag.push(tnm) + e = Ext.parseOOXML(xpp, lastTag).cloneElement() as Ext + //e.setNS("xdr"); + } else if (tnm == "cxnSp" || // connection shape + + tnm == "graphicFrame" || + tnm == "grpSp" || // group shape + + tnm == "pic" || // picture + + tnm == "sp") { // shape + lastTag.push(tnm) + o = ObjectChoice.parseOOXML(xpp, lastTag, bk) as ObjectChoice + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "oneCellAnchor") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (ex: Exception) { + Logger.logErr("oneCellAnchor.parseOOXML: $ex") + } + + return OneCellAnchor(f, e, o) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/P.java b/src/main/java/io/starter/formats/OOXML/P.java deleted file mode 100644 index e9a6519..0000000 --- a/src/main/java/io/starter/formats/OOXML/P.java +++ /dev/null @@ -1,475 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.FormatConstants; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * p (Text Paragraph) - *

                  - * This element specifies the presence of a paragraph of text within the containing text body. The paragraph is the - * highest level text separation mechanism within a text body. A paragraph may contain text paragraph properties - * associated with the paragraph. If no properties are listed then properties specified in the defPPr element are - * used. - *

                  - * parent: r, t, txBody, txpr - * children: pPr, (r, br or fld), endParaRPr - */ -// TODO: Finish pPr Text Paragraph Properties -- MANY child elements not handled -//TODO: Finish endParaRPr children TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver, -public class P implements OOXMLElement { - - private static final long serialVersionUID = 6302706683933521698L; - private TextRun run = null; - private PPr ppr = null; - private EndParaRPr ep = null; - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - PPr ppr = null; - TextRun run = null; - EndParaRPr ep = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pPr")) { // paragraph-level text props - lastTag.push(tnm); - ppr = PPr.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("r") || - tnm.equals("br") || - tnm.equals("fld")) { // text run - lastTag.push(tnm); - run = (TextRun) TextRun.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("endParaRPr")) { - lastTag.push(tnm); - ep = EndParaRPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("p")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("p.parseOOXML: " + e.toString()); - - } - P pr = new P(ppr, run, ep); - return pr; - } - - public P(String s) { - this.run = new TextRun(s); - this.ppr = new PPr(new DefRPr(), null); - } - - public P(PPr ppr, TextRun run, EndParaRPr ep) { - this.ppr = ppr; - this.ep = ep; - this.run = run; - } - - public P(P p) { - this.ppr = p.ppr; - this.ep = p.ep; - this.run = p.run; - } - - /** - * create a default paragraph property from the specified information - * - * @param fontFace String font face e.g. "Arial" - * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) - * @param b boolean true if bold - * @param i boolean true if italic - * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted - * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) - * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none - * @param clr String fill color in hex form without the # - */ - public P(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { - this.ppr = new PPr(fontFace, sz, b, i, u, strike, clr); - } - - - /** - * create a default paragraph property from the specified Font and text s - * - * @param f Font - */ - public P(Font f, String s) { - int u = f.getUnderlineStyle(); - String usty = "none"; - switch (u) { - case FormatConstants.STYLE_UNDERLINE_SINGLE: - usty = "sng"; - break; - case FormatConstants.STYLE_UNDERLINE_DOUBLE: - usty = "dbl"; - break; - case FormatConstants.STYLE_UNDERLINE_SINGLE_ACCTG: - usty = "sng"; - break; - case FormatConstants.STYLE_UNDERLINE_DOUBLE_ACCTG: - usty = "dbl"; - break; - } - String strike = (f.getStricken() ? "sngStrike" : "noStrike"); - String clr = FormatHandle.colorToHexString(FormatHandle.getColor(f.getColor())).substring(1); - this.ppr = new PPr(f.getFontName(), (int) f.getFontHeightInPoints() * 100, f.getBold(), f.getItalic(), usty, strike, clr); - this.run = new TextRun(s); - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (ppr != null) ooxml.append(ppr.getOOXML()); - if (run != null) - ooxml.append(run.getOOXML()); // 20090526 KSC: order of children was wrong (Kaylan/Rajesh chart error) - if (ep != null) ooxml.append(ep.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new P(this); - } - - public String getTitle() { - if (run != null) return run.getTitle(); - return ""; - } - - /** - * returns a map of all text properties defined for this paragraph - * - * @return - */ - public HashMap getTextProperties() { - HashMap textprops = new HashMap(); - if (ppr != null) { - // algn- left, right, centered, just, distributed - // defTabSz - // fontAlgn (Font Alignment) - // hangingPunct (Hanging Punctuation) bool - // indent (Indent) - // lvl (Level) - // marL (Left Margin) - // marR (Right Margin) - // rtl (Right To Left) bool - /* This element contains all default run level text properties for the text runs within a containing paragraph. These - 10 properties are to be used when overriding properties have not been defined within the rPr element.*/ - textprops.putAll(ppr.getTextProperties()); - textprops.putAll(ppr.getDefaultTextProperties()); - /* - * altLang (Alternative Language) - * b (Bold) bool - * baseline (Baseline) - * bmk (Bookmark Link Target) - * cap (Capitalization) - * i (Italics) bool - * kern (Kerning) - * kumimoji - * lang (Language ID) - * spc (Spacing) - * strike (Strikethrough) - * sz (Font Size) size - * u (Underline) underline style - * - * PLUS-- fill, line, blipFill, cs/ea/latin font (attribute: typeface) **** - */ - } - if (run != null) { - textprops.putAll(run.getTextProperties()); - } - return textprops; - } -} - -/** - * pPr (Text Paragraph Properties) - *

                  - * This element contains all paragraph level text properties for the containing paragraph. These paragraph - * properties should override any and all conflicting properties that are associated with the paragraph in question - *

                  - * parent: p, fld - * children: many - * attributes: many - */ -// TODO: Handle child elements: lnSpc, spcBef, spcAft, TEXTBULLETCOLOR, TEXTBULLETSIZE, TEXTBULLET, tabLst -class PPr implements OOXMLElement { - - private static final long serialVersionUID = -6909210948618654877L; - private DefRPr dp; - private HashMap attrs; - - public PPr(DefRPr dp, HashMap attrs) { - this.dp = dp; - this.attrs = attrs; - } - - public PPr(PPr p) { - this.dp = p.dp; - this.attrs = p.attrs; - } - - /** - * create a default paragraph property from the specified information - * - * @param fontFace String font face e.g. "Arial" - * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) - * @param b boolean true if bold - * @param i boolean true if italic - * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted - * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) - * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none - * @param clr String fill color in hex form without the # - */ - public PPr(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { - this.dp = new DefRPr(fontFace, sz, b, i, u, strike, clr); - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (dp != null) ooxml.append(dp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public static PPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - DefRPr dp = null; - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pPr")) { // t element of text run -- the title string we are interested in - for (int i = 0; i < xpp.getAttributeCount(); i++) { // align, defTabSz, fontAlgn, hangingPunct, indent, lvl, rtl, marL, marR ... - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("defRPr")) { // default text properties - lastTag.push(tnm); - dp = (DefRPr) DefRPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("pPr")) { - lastTag.pop(); // pop this element - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("paraText.parseOOXML: " + e.toString()); - } - PPr pt = new PPr(dp, attrs); - return pt; - } - - public OOXMLElement cloneElement() { - return new PPr(this); - } - - /** - * return the default run properties for this para - * - * @return - */ - public HashMap getDefaultTextProperties() { - return dp.getTextProperties(); - } - - /** - * return the text properties defined by PPr: - * algn- left, right, centered, just, distributed - * defTabSz - * fontAlgn (Font Alignment) - * hangingPunct (Hanging Punctuation) bool - * indent (Indent) - * lvl (Level) - * marL (Left Margin) - * marR (Right Margin) - * rtl (Right To Left) bool - * - * @return - */ - public HashMap getTextProperties() { - if (attrs != null) return attrs; - return new HashMap(); - } -} - - -/** - * endParaRPr (End Paragraph Run Properties) - *

                  - * This element specifies the text run properties that are to be used if another run is inserted after the last run - * specified. This effectively saves the run property state so that it may be applied when the user enters additional - * text. If this element is omitted, then the application may determine which default properties to apply. It is - * recommended that this element be specified at the end of the list of text runs within the paragraph so that an - * orderly list is maintained. - *

                  - * parent: p - * children: ln, FillGroup, EffectGroup, highlight, TEXTUNDERLINE, TEXTUNDERLINEFILL, latin, ea, cs, sym, hlinkClick, hlinkMouseOver, - */ -// TODO: Finish children TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver, -class EndParaRPr implements OOXMLElement { - - private static final long serialVersionUID = -7094231887468090281L; - private HashMap attrs = null; - private Ln l; - private FillGroup fill; - private EffectPropsGroup effect; - private String latin, ea, cs; // really children but only have 1 attribute and no children - - public EndParaRPr(HashMap attrs, Ln l, FillGroup fill, EffectPropsGroup effect, String latin, String ea, String cs) { - this.attrs = attrs; - this.l = l; - this.fill = fill; - this.effect = effect; - this.latin = latin; - this.ea = ea; - this.cs = cs; - } - - public EndParaRPr(EndParaRPr ep) { - this.attrs = ep.attrs; - this.l = ep.l; - this.fill = ep.fill; - this.effect = ep.effect; - this.latin = ep.latin; - this.ea = ep.ea; - this.cs = ep.cs; - } - - - public static EndParaRPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - FillGroup fill = null; - EffectPropsGroup effect = null; - Ln l = null; - String latin = null, ea = null, cs = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("endParaRPr")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("ln")) { - lastTag.push(tnm); - l = (Ln) Ln.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - // TODO: Eventually these will be objects - } else if (tnm.equals("latin")) { - latin = xpp.getAttributeValue(0); - } else if (tnm.equals("ea")) { - ea = xpp.getAttributeValue(0); - } else if (tnm.equals("cs")) { - cs = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("endParaRPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("endParaRPr.parseOOXML: " + e.toString()); - } - EndParaRPr oe = new EndParaRPr(attrs, l, fill, effect, latin, ea, cs); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (l != null) ooxml.append(l.getOOXML()); - if (fill != null) ooxml.append(fill.getOOXML()); // group fill - if (latin != null) ooxml.append(""); - if (ea != null) ooxml.append(""); - if (cs != null) ooxml.append(""); - // sym - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new EndParaRPr(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/P.kt b/src/main/java/io/starter/formats/OOXML/P.kt new file mode 100644 index 0000000..cbf3942 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/P.kt @@ -0,0 +1,481 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Font +import io.starter.formats.XLS.FormatConstants +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * p (Text Paragraph) + * + * + * This element specifies the presence of a paragraph of text within the containing text body. The paragraph is the + * highest level text separation mechanism within a text body. A paragraph may contain text paragraph properties + * associated with the paragraph. If no properties are listed then properties specified in the defPPr element are + * used. + * + * + * parent: r, t, txBody, txpr + * children: pPr, (r, br or fld), endParaRPr + */ +// TODO: Finish pPr Text Paragraph Properties -- MANY child elements not handled +//TODO: Finish endParaRPr children TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver, +class P : OOXMLElement { + private var run: TextRun? = null + private var ppr: PPr? = null + private var ep: EndParaRPr? = null + + override// 20090526 KSC: order of children was wrong (Kaylan/Rajesh chart error) + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (ppr != null) ooxml.append(ppr!!.ooxml) + if (run != null) + ooxml.append(run!!.ooxml) + if (ep != null) ooxml.append(ep!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + val title: String? + get() = if (run != null) run!!.title else "" + + /** + * returns a map of all text properties defined for this paragraph + * + * @return + */ + // algn- left, right, centered, just, distributed + // defTabSz + // fontAlgn (Font Alignment) + // hangingPunct (Hanging Punctuation) bool + // indent (Indent) + // lvl (Level) + // marL (Left Margin) + // marR (Right Margin) + // rtl (Right To Left) bool + /* This element contains all default run level text properties for the text runs within a containing paragraph. These + 10 properties are to be used when overriding properties have not been defined within the rPr element.*//* + * altLang (Alternative Language) + * b (Bold) bool + * baseline (Baseline) + * bmk (Bookmark Link Target) + * cap (Capitalization) + * i (Italics) bool + * kern (Kerning) + * kumimoji + * lang (Language ID) + * spc (Spacing) + * strike (Strikethrough) + * sz (Font Size) size + * u (Underline) underline style + * + * PLUS-- fill, line, blipFill, cs/ea/latin font (attribute: typeface) **** + */ val textProperties: HashMap + get() { + val textprops = HashMap() + if (ppr != null) { + textprops.putAll(ppr!!.textProperties) + textprops.putAll(ppr!!.defaultTextProperties) + } + if (run != null) { + textprops.putAll(run!!.textProperties) + } + return textprops + } + + constructor(s: String) { + this.run = TextRun(s) + this.ppr = PPr(DefRPr(), null) + } + + constructor(ppr: PPr, run: TextRun, ep: EndParaRPr) { + this.ppr = ppr + this.ep = ep + this.run = run + } + + constructor(p: P) { + this.ppr = p.ppr + this.ep = p.ep + this.run = p.run + } + + /** + * create a default paragraph property from the specified information + * + * @param fontFace String font face e.g. "Arial" + * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) + * @param b boolean true if bold + * @param i boolean true if italic + * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted + * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) + * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none + * @param clr String fill color in hex form without the # + */ + constructor(fontFace: String, sz: Int, b: Boolean, i: Boolean, u: String, strike: String, clr: String) { + this.ppr = PPr(fontFace, sz, b, i, u, strike, clr) + } + + + /** + * create a default paragraph property from the specified Font and text s + * + * @param f Font + */ + constructor(f: Font, s: String) { + val u = f.underlineStyle + var usty = "none" + when (u) { + FormatConstants.STYLE_UNDERLINE_SINGLE -> usty = "sng" + FormatConstants.STYLE_UNDERLINE_DOUBLE -> usty = "dbl" + FormatConstants.STYLE_UNDERLINE_SINGLE_ACCTG -> usty = "sng" + FormatConstants.STYLE_UNDERLINE_DOUBLE_ACCTG -> usty = "dbl" + } + val strike = if (f.stricken) "sngStrike" else "noStrike" + val clr = FormatHandle.colorToHexString(FormatHandle.getColor(f.color)).substring(1) + this.ppr = PPr(f.fontName, f.fontHeightInPoints.toInt() * 100, f.bold, f.italic, usty, strike, clr) + this.run = TextRun(s) + } + + override fun cloneElement(): OOXMLElement { + return P(this) + } + + companion object { + + private val serialVersionUID = 6302706683933521698L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var ppr: PPr? = null + var run: TextRun? = null + var ep: EndParaRPr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pPr") { // paragraph-level text props + lastTag.push(tnm) + ppr = PPr.parseOOXML(xpp, lastTag, bk) + } else if (tnm == "r" || + tnm == "br" || + tnm == "fld") { // text run + lastTag.push(tnm) + run = TextRun.parseOOXML(xpp, lastTag, bk) as TextRun + } else if (tnm == "endParaRPr") { + lastTag.push(tnm) + ep = EndParaRPr.parseOOXML(xpp, lastTag, bk) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "p") { + lastTag.pop() // pop this tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("p.parseOOXML: $e") + + } + + return P(ppr, run, ep) + } + } +} + +/** + * pPr (Text Paragraph Properties) + * + * + * This element contains all paragraph level text properties for the containing paragraph. These paragraph + * properties should override any and all conflicting properties that are associated with the paragraph in question + * + * + * parent: p, fld + * children: many + * attributes: many + */ +// TODO: Handle child elements: lnSpc, spcBef, spcAft, TEXTBULLETCOLOR, TEXTBULLETSIZE, TEXTBULLET, tabLst +internal class PPr : OOXMLElement { + private var dp: DefRPr? = null + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (dp != null) ooxml.append(dp!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * return the default run properties for this para + * + * @return + */ + val defaultTextProperties: HashMap + get() = dp!!.textProperties + + /** + * return the text properties defined by PPr: + * algn- left, right, centered, just, distributed + * defTabSz + * fontAlgn (Font Alignment) + * hangingPunct (Hanging Punctuation) bool + * indent (Indent) + * lvl (Level) + * marL (Left Margin) + * marR (Right Margin) + * rtl (Right To Left) bool + * + * @return + */ + val textProperties: HashMap + get() = if (attrs != null) attrs else HashMap() + + constructor(dp: DefRPr, attrs: HashMap) { + this.dp = dp + this.attrs = attrs + } + + constructor(p: PPr) { + this.dp = p.dp + this.attrs = p.attrs + } + + /** + * create a default paragraph property from the specified information + * + * @param fontFace String font face e.g. "Arial" + * @param sz int size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) + * @param b boolean true if bold + * @param i boolean true if italic + * @param u String underline. One of the following Strings: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted + * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) + * @param strike String strike setting. One of the following Strings: dblStrike, noStrike or sngStrike or null if none + * @param clr String fill color in hex form without the # + */ + constructor(fontFace: String, sz: Int, b: Boolean, i: Boolean, u: String, strike: String, clr: String) { + this.dp = DefRPr(fontFace, sz, b, i, u, strike, clr) + } + + override fun cloneElement(): OOXMLElement { + return PPr(this) + } + + companion object { + + private val serialVersionUID = -6909210948618654877L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): PPr { + var dp: DefRPr? = null + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pPr") { // t element of text run -- the title string we are interested in + for (i in 0 until xpp.attributeCount) { // align, defTabSz, fontAlgn, hangingPunct, indent, lvl, rtl, marL, marR ... + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "defRPr") { // default text properties + lastTag.push(tnm) + dp = DefRPr.parseOOXML(xpp, lastTag, bk) as DefRPr + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "pPr") { + lastTag.pop() // pop this element + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("paraText.parseOOXML: $e") + } + + return PPr(dp, attrs) + } + } +} + + +/** + * endParaRPr (End Paragraph Run Properties) + * + * + * This element specifies the text run properties that are to be used if another run is inserted after the last run + * specified. This effectively saves the run property state so that it may be applied when the user enters additional + * text. If this element is omitted, then the application may determine which default properties to apply. It is + * recommended that this element be specified at the end of the list of text runs within the paragraph so that an + * orderly list is maintained. + * + * + * parent: p + * children: ln, FillGroup, EffectGroup, highlight, TEXTUNDERLINE, TEXTUNDERLINEFILL, latin, ea, cs, sym, hlinkClick, hlinkMouseOver, + */ +// TODO: Finish children TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver, +internal class EndParaRPr : OOXMLElement { + private var attrs: HashMap? = null + private var l: Ln? = null + private var fill: FillGroup? = null + private var effect: EffectPropsGroup? = null + private var latin: String? = null + private var ea: String? = null + private var cs: String? = null // really children but only have 1 attribute and no children + + override// attributes + // group fill + // sym + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (l != null) ooxml.append(l!!.ooxml) + if (fill != null) ooxml.append(fill!!.ooxml) + if (latin != null) ooxml.append("") + if (ea != null) ooxml.append("") + if (cs != null) ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap, l: Ln, fill: FillGroup, effect: EffectPropsGroup, latin: String, ea: String, cs: String) { + this.attrs = attrs + this.l = l + this.fill = fill + this.effect = effect + this.latin = latin + this.ea = ea + this.cs = cs + } + + constructor(ep: EndParaRPr) { + this.attrs = ep.attrs + this.l = ep.l + this.fill = ep.fill + this.effect = ep.effect + this.latin = ep.latin + this.ea = ep.ea + this.cs = ep.cs + } + + override fun cloneElement(): OOXMLElement { + return EndParaRPr(this) + } + + companion object { + + private val serialVersionUID = -7094231887468090281L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): EndParaRPr { + val attrs = HashMap() + var fill: FillGroup? = null + var effect: EffectPropsGroup? = null + var l: Ln? = null + var latin: String? = null + var ea: String? = null + var cs: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "endParaRPr") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "ln") { + lastTag.push(tnm) + l = Ln.parseOOXML(xpp, lastTag, bk) as Ln + } else if (tnm == "solidFill" || + tnm == "noFill" || + tnm == "gradFill" || + tnm == "grpFill" || + tnm == "pattFill" || + tnm == "blipFill") { + lastTag.push(tnm) + fill = FillGroup.parseOOXML(xpp, lastTag, bk) as FillGroup + } else if (tnm == "effectLst" || tnm == "effectDag") { + lastTag.push(tnm) + effect = EffectPropsGroup.parseOOXML(xpp, lastTag) as EffectPropsGroup + // TODO: Eventually these will be objects + } else if (tnm == "latin") { + latin = xpp.getAttributeValue(0) + } else if (tnm == "ea") { + ea = xpp.getAttributeValue(0) + } else if (tnm == "cs") { + cs = xpp.getAttributeValue(0) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "endParaRPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("endParaRPr.parseOOXML: $e") + } + + return EndParaRPr(attrs, l, fill, effect, latin, ea, cs) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Pic.java b/src/main/java/io/starter/formats/OOXML/Pic.java deleted file mode 100644 index 7af0275..0000000 --- a/src/main/java/io/starter/formats/OOXML/Pic.java +++ /dev/null @@ -1,504 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * pic (Picture) - * This element specifies the existence of a picture object within the spreadsheet. - *

                  - * parent: absoluteAnchor, grpSp, oneCellAnchor, twoCellAnchor - * children: nvPicPr, blipFill, spPr, style - */ -//TODO: handle nvPicPr.cNvPicPr.picLocks element -public class Pic implements OOXMLElement { - - private static final long serialVersionUID = -4929177274389163606L; - private HashMap attrs; - private NvPicPr nvPicPr; - private BlipFill blipFill; - private SpPr spPr; - private Style style; - - public Pic() { // set common defaults - nvPicPr = new NvPicPr(); - blipFill = new BlipFill(); - spPr = new SpPr("xdr"); - spPr.setNS("xdr"); - attrs = null; - } - - public Pic(HashMap attrs, NvPicPr nv, BlipFill bf, SpPr sp, Style s) { - this.attrs = attrs; - this.nvPicPr = nv; - this.blipFill = bf; - this.spPr = sp; - this.style = s; - } - - public Pic(Pic p) { - this.attrs = p.attrs; - this.nvPicPr = p.nvPicPr; - this.blipFill = p.blipFill; - this.spPr = p.spPr; - this.style = p.style; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - NvPicPr nv = null; - BlipFill bf = null; - SpPr sp = null; - Style s = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pic")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } else if (tnm.equals("nvPicPr")) { - lastTag.push(tnm); - nv = NvPicPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("blipFill")) { - lastTag.push(tnm); - bf = BlipFill.parseOOXML(xpp, lastTag); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - //sp.setNS("xdr"); - } else if (tnm.equals("style")) { - lastTag.push(tnm); - s = (Style) Style.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("pic")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pic.parseOOXML: " + e.toString()); - } - Pic p = new Pic(attrs, nv, bf, sp, s); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (nvPicPr != null) ooxml.append(nvPicPr.getOOXML()); - if (blipFill != null) ooxml.append(blipFill.getOOXML()); - if (spPr != null) ooxml.append(spPr.getOOXML()); - if (style != null) ooxml.append(style.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Pic(this); - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (blipFill != null) - return blipFill.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (blipFill != null) - return blipFill.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - if (blipFill != null) - blipFill.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (blipFill != null) - blipFill.setLink(link); - } - - /** - * return the name of this shape, if any - * - * @return - */ - public String getName() { - if (nvPicPr != null) - return nvPicPr.getName(); - return null; - } - - /** - * set the name of this shape, if any - */ - public void setName(String name) { - if (nvPicPr != null) - nvPicPr.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (nvPicPr != null) - return nvPicPr.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * - * @param name - */ - public void setDescr(String descr) { - if (nvPicPr != null) - nvPicPr.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (nvPicPr != null) - nvPicPr.setId(id); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (nvPicPr == null) - nvPicPr = new NvPicPr(); - return nvPicPr.getId(); - } - - /** - * get Macro attribute - */ - public String getMacro() { - if (attrs.get("macro") != null) - return attrs.get("macro"); - return null; - } - - /** - * set Macro attribute - * - * @param macro - */ - public void setMacro(String macro) { - attrs.put("macro", macro); - } - - /** - * add a line to this image - * - * @param w int line width in - * @param clr String color html string - * @return - */ - public void setLine(int w, String clr) { - if (spPr != null) { - spPr.setLine(w, clr); - } - } - - /** - * utility to return the shape properties element for this picture - * should be depreciated when OOXML is completely distinct from BIFF8 - * - * @return - */ - public SpPr getSppr() { - return spPr; - } - - /** - * utility to return the shape properties element for this picture - * should be depreciated when OOXML is completely distinct from BIFF8 - * - * @return - */ - public void setSppr(SpPr sp) { - this.spPr = sp; - } -} - -/** - * nvPicPr (Non-Visual Properties for a Picture) - * This element specifies all non-visual properties for a picture. This element is a container for the non-visual - * identification properties, shape properties and application properties that are to be associated with a picture. - * This allows for additional information that does not affect the appearance of the picture to be stored. - *

                  - * parent: pic - * children: cNvPr, cNvPicPr - */ -class NvPicPr implements OOXMLElement { - - private static final long serialVersionUID = -3722424348721713313L; - private CNvPr cpr; - private CNvPicPr ppr; - - public NvPicPr() { // set common defaults - this.cpr = new CNvPr(); - this.ppr = new CNvPicPr(); - } - - public NvPicPr(CNvPr cpr, CNvPicPr ppr) { - this.cpr = cpr; - this.ppr = ppr; - } - - public NvPicPr(NvPicPr n) { - this.cpr = n.cpr; - this.ppr = n.ppr; - } - - - public static NvPicPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cpr = null; - CNvPicPr ppr = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cpr = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cNvPicPr")) { - lastTag.push(tnm); - ppr = CNvPicPr.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("nvPicPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("nvPicPr.parseOOXML: " + e.toString()); - } - NvPicPr n = new NvPicPr(cpr, ppr); - return n; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append(cpr.getOOXML()); - ooxml.append(ppr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NvPicPr(this); - } - - /** - * return the name of this shape, if any - * - * @return - */ - public String getName() { - if (cpr != null) - return cpr.getName(); - return null; - } - - /** - * set the name of this shape, if any - */ - public void setName(String name) { - if (cpr != null) - cpr.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (cpr != null) - return cpr.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * - * @param name - */ - public void setDescr(String descr) { - if (cpr != null) - cpr.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (cpr != null) - cpr.setId(id); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (cpr != null) - return cpr.getId(); - return -1; - } - -} - -/** - * cNvPicPr (Non-Visual Picture Drawing Properties) - * This element describes the non-visual properties of a picture within a spreadsheet. These are the set of - * properties of a picture which do not affect its display within a spreadsheet. - */ -// TODO: handle child picLocks -class CNvPicPr implements OOXMLElement { - - private static final long serialVersionUID = 3690228348761065940L; - private String preferRelativeResize = null; - - public CNvPicPr() { - - } - - public CNvPicPr(String preferRelativeResize) { - this.preferRelativeResize = preferRelativeResize; - } - - public CNvPicPr(CNvPicPr c) { - this.preferRelativeResize = c.preferRelativeResize; - } - - - public static CNvPicPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String preferRelativeResize = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPicPr")) { // get attributes - if (xpp.getAttributeCount() > 0) - preferRelativeResize = xpp.getAttributeValue(0); -/* TODO: Finish } else if (tnm.equals("picLocks")) { - lastTag.push(tnm); - //p = (picLocks) picLocks.parseOOXML(xpp, lastTag).clone(); -*/ - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("cNvPicPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cNvPicPr.parseOOXML: " + e.toString()); - } - CNvPicPr c = new CNvPicPr(preferRelativeResize); - return c; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - // TODO: finish picLocks - //if (p!=null) ooxml.append(p.getOOXML()); - //ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvPicPr(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Pic.kt b/src/main/java/io/starter/formats/OOXML/Pic.kt new file mode 100644 index 0000000..576018e --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Pic.kt @@ -0,0 +1,477 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * pic (Picture) + * This element specifies the existence of a picture object within the spreadsheet. + * + * + * parent: absoluteAnchor, grpSp, oneCellAnchor, twoCellAnchor + * children: nvPicPr, blipFill, spPr, style + */ +//TODO: handle nvPicPr.cNvPicPr.picLocks element +class Pic : OOXMLElement { + private var attrs: HashMap? = null + private var nvPicPr: NvPicPr? = null + private var blipFill: BlipFill? = null + /** + * utility to return the shape properties element for this picture + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + /** + * utility to return the shape properties element for this picture + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + var sppr: SpPr? = null + private var style: Style? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (nvPicPr != null) ooxml.append(nvPicPr!!.ooxml) + if (blipFill != null) ooxml.append(blipFill!!.ooxml) + if (sppr != null) ooxml.append(sppr!!.ooxml) + if (style != null) ooxml.append(style!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + var embed: String? + get() = if (blipFill != null) blipFill!!.embed else null + set(embed) { + if (blipFill != null) + blipFill!!.embed = embed + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + var link: String? + get() = if (blipFill != null) blipFill!!.link else null + set(link) { + if (blipFill != null) + blipFill!!.link = link + } + + /** + * return the name of this shape, if any + * + * @return + */ + /** + * set the name of this shape, if any + */ + var name: String? + get() = if (nvPicPr != null) nvPicPr!!.name else null + set(name) { + if (nvPicPr != null) + nvPicPr!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr desc attribute + * + * @param name + */ + var descr: String? + get() = if (nvPicPr != null) nvPicPr!!.descr else null + set(descr) { + if (nvPicPr != null) + nvPicPr!!.descr = descr + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + var id: Int + get() { + if (nvPicPr == null) + nvPicPr = NvPicPr() + return nvPicPr!!.id + } + set(id) { + if (nvPicPr != null) + nvPicPr!!.id = id + } + + /** + * get Macro attribute + */ + /** + * set Macro attribute + * + * @param macro + */ + var macro: String? + get() = if (attrs!!["macro"] != null) attrs!!["macro"] else null + set(macro) { + attrs!!["macro"] = macro + } + + constructor() { // set common defaults + nvPicPr = NvPicPr() + blipFill = BlipFill() + sppr = SpPr("xdr") + sppr!!.setNS("xdr") + attrs = null + } + + constructor(attrs: HashMap, nv: NvPicPr, bf: BlipFill, sp: SpPr, s: Style) { + this.attrs = attrs + this.nvPicPr = nv + this.blipFill = bf + this.sppr = sp + this.style = s + } + + constructor(p: Pic) { + this.attrs = p.attrs + this.nvPicPr = p.nvPicPr + this.blipFill = p.blipFill + this.sppr = p.sppr + this.style = p.style + } + + override fun cloneElement(): OOXMLElement { + return Pic(this) + } + + /** + * add a line to this image + * + * @param w int line width in + * @param clr String color html string + * @return + */ + fun setLine(w: Int, clr: String) { + if (sppr != null) { + sppr!!.setLine(w, clr) + } + } + + companion object { + + private val serialVersionUID = -4929177274389163606L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + val attrs = HashMap() + var nv: NvPicPr? = null + var bf: BlipFill? = null + var sp: SpPr? = null + var s: Style? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pic") { // get attributes + for (i in 0 until xpp.attributeCount) + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } else if (tnm == "nvPicPr") { + lastTag.push(tnm) + nv = NvPicPr.parseOOXML(xpp, lastTag) + } else if (tnm == "blipFill") { + lastTag.push(tnm) + bf = BlipFill.parseOOXML(xpp, lastTag) + } else if (tnm == "spPr") { + lastTag.push(tnm) + sp = SpPr.parseOOXML(xpp, lastTag, bk) as SpPr + //sp.setNS("xdr"); + } else if (tnm == "style") { + lastTag.push(tnm) + s = Style.parseOOXML(xpp, lastTag, bk) as Style + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "pic") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("pic.parseOOXML: $e") + } + + return Pic(attrs, nv, bf, sp, s) + } + } +} + +/** + * nvPicPr (Non-Visual Properties for a Picture) + * This element specifies all non-visual properties for a picture. This element is a container for the non-visual + * identification properties, shape properties and application properties that are to be associated with a picture. + * This allows for additional information that does not affect the appearance of the picture to be stored. + * + * + * parent: pic + * children: cNvPr, cNvPicPr + */ +internal class NvPicPr : OOXMLElement { + private var cpr: CNvPr? = null + private var ppr: CNvPicPr? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append(cpr!!.ooxml) + ooxml.append(ppr!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * return the name of this shape, if any + * + * @return + */ + /** + * set the name of this shape, if any + */ + var name: String? + get() = if (cpr != null) cpr!!.name else null + set(name) { + if (cpr != null) + cpr!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr desc attribute + * + * @param name + */ + var descr: String? + get() = if (cpr != null) cpr!!.descr else null + set(descr) { + if (cpr != null) + cpr!!.descr = descr + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + var id: Int + get() = if (cpr != null) cpr!!.id else -1 + set(id) { + if (cpr != null) + cpr!!.id = id + } + + constructor() { // set common defaults + this.cpr = CNvPr() + this.ppr = CNvPicPr() + } + + constructor(cpr: CNvPr, ppr: CNvPicPr) { + this.cpr = cpr + this.ppr = ppr + } + + constructor(n: NvPicPr) { + this.cpr = n.cpr + this.ppr = n.ppr + } + + override fun cloneElement(): OOXMLElement { + return NvPicPr(this) + } + + companion object { + + private val serialVersionUID = -3722424348721713313L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): NvPicPr { + var cpr: CNvPr? = null + var ppr: CNvPicPr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cNvPr") { + lastTag.push(tnm) + cpr = CNvPr.parseOOXML(xpp, lastTag) as CNvPr + } else if (tnm == "cNvPicPr") { + lastTag.push(tnm) + ppr = CNvPicPr.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "nvPicPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("nvPicPr.parseOOXML: $e") + } + + return NvPicPr(cpr, ppr) + } + } + +} + +/** + * cNvPicPr (Non-Visual Picture Drawing Properties) + * This element describes the non-visual properties of a picture within a spreadsheet. These are the set of + * properties of a picture which do not affect its display within a spreadsheet. + */ +// TODO: handle child picLocks +internal class CNvPicPr : OOXMLElement { + private var preferRelativeResize: String? = null + + override// TODO: finish picLocks + //if (p!=null) ooxml.append(p.getOOXML()); + //ooxml.append(""); + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor() { + + } + + constructor(preferRelativeResize: String) { + this.preferRelativeResize = preferRelativeResize + } + + constructor(c: CNvPicPr) { + this.preferRelativeResize = c.preferRelativeResize + } + + override fun cloneElement(): OOXMLElement { + return CNvPicPr(this) + } + + companion object { + + private val serialVersionUID = 3690228348761065940L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): CNvPicPr { + var preferRelativeResize: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cNvPicPr") { // get attributes + if (xpp.attributeCount > 0) + preferRelativeResize = xpp.getAttributeValue(0) + /* TODO: Finish } else if (tnm.equals("picLocks")) { + lastTag.push(tnm); + //p = (picLocks) picLocks.parseOOXML(xpp, lastTag).clone(); +*/ + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "cNvPicPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("cNvPicPr.parseOOXML: $e") + } + + return CNvPicPr(preferRelativeResize) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.java b/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.java deleted file mode 100644 index 1ed4b40..0000000 --- a/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserFactory; - -import java.io.InputStream; - -public class PivotCacheDefinition implements OOXMLElement { - - private static final long serialVersionUID = -5070227633357072878L; - private String ref = null; - private String sheet = null; - private int icache; - - public OOXMLElement cloneElement() { - return null; - } - - public PivotCacheDefinition(String ref, String sheet, int icache) { - this.ref = ref; - this.sheet = sheet; - this.icache = icache; - } - - public static PivotCacheDefinition parseOOXML(WorkBookHandle bk, String cacheid, InputStream ii) { - String ref = null; - String sheet = null; - int icache = 1; - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pivotCacheDefinition")) { // get attributes - // r:id="rId1" refreshedBy="Kaia" refreshedDate="41038.467970833335" createdVersion="1" refreshedVersion="3" recordCount="4" upgradeOnRefresh="1"> - } else if (tnm.equals("cacheSource")) { - for (int z = 0; z < xpp.getAttributeCount(); z++) { - String nm = xpp.getAttributeName(z); - String v = xpp.getAttributeValue(z); - if (nm.equals("type")) - if (!v.equals("worksheet")) { - // consolidation, external, scenario -- - Logger.logWarn("PivotCacheDefinition: Data Souce " + v + " Not Supported"); - return null; - } - } - } else if (tnm.equals("worksheetSource")) { - // ref, sheet, id (sheet rid), name (range) - for (int z = 0; z < xpp.getAttributeCount(); z++) { - String nm = xpp.getAttributeName(z); - String v = xpp.getAttributeValue(z); - if (nm.equals("ref")) { - ref = v; - } else if (nm.equals("sheet")) { - sheet = v; - } else if (nm.equals("name")) { - ref = v; - } else if (nm.equals("id")) { - - } - } - } else if (tnm.equals("cacheFields")) { - } - } else if (eventType == XmlPullParser.END_TAG) { // go to end of file - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("PivotCacheDefinition.parseOOXML: " + e.toString()); - } - - if (cacheid != null) { -// KSC: TESTING!!! icache= Integer.valueOf((String)cacheid)+1; - icache = Integer.valueOf(1); - } - icache = bk.getWorkBook().addPivotStream(ref, sheet, icache); - return new PivotCacheDefinition(ref, sheet, icache); - } - - /** - * return the pivot cache id - */ - public int getICache() { - return icache; - } - - /** - * returns the data source reference - * - * @return - */ - public String getRef() { - return ref; - } - - /** - * return the sheet the data reference is on - * - * @return - */ - public String getSheet() { - return sheet; - } - - public String getOOXML() { - // TODO: Finish - return null; - } - - -} diff --git a/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.kt b/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.kt new file mode 100644 index 0000000..25b179b --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/PivotCacheDefinition.kt @@ -0,0 +1,129 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserFactory + +import java.io.InputStream + +class PivotCacheDefinition(ref: String, sheet: String, + /** + * return the pivot cache id + */ + val iCache: Int) : OOXMLElement { + /** + * returns the data source reference + * + * @return + */ + val ref: String? = null + /** + * return the sheet the data reference is on + * + * @return + */ + val sheet: String? = null + + override// TODO: Finish + val ooxml: String? + get() = null + + override fun cloneElement(): OOXMLElement? { + return null + } + + init { + this.ref = ref + this.sheet = sheet + } + + companion object { + + private val serialVersionUID = -5070227633357072878L + + fun parseOOXML(bk: WorkBookHandle, cacheid: String?, ii: InputStream): PivotCacheDefinition? { + var ref: String? = null + var sheet: String? = null + var icache = 1 + try { + val factory = XmlPullParserFactory.newInstance() + factory.isNamespaceAware = true + val xpp = factory.newPullParser() + xpp.setInput(ii, null) // using XML 1.0 specification + var eventType = xpp.eventType + + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pivotCacheDefinition") { // get attributes + // r:id="rId1" refreshedBy="Kaia" refreshedDate="41038.467970833335" createdVersion="1" refreshedVersion="3" recordCount="4" upgradeOnRefresh="1"> + } else if (tnm == "cacheSource") { + for (z in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(z) + val v = xpp.getAttributeValue(z) + if (nm == "type") + if (v != "worksheet") { + // consolidation, external, scenario -- + Logger.logWarn("PivotCacheDefinition: Data Souce $v Not Supported") + return null + } + } + } else if (tnm == "worksheetSource") { + // ref, sheet, id (sheet rid), name (range) + for (z in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(z) + val v = xpp.getAttributeValue(z) + if (nm == "ref") { + ref = v + } else if (nm == "sheet") { + sheet = v + } else if (nm == "name") { + ref = v + } else if (nm == "id") { + + } + } + } else if (tnm == "cacheFields") { + } + } else if (eventType == XmlPullParser.END_TAG) { // go to end of file + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("PivotCacheDefinition.parseOOXML: $e") + } + + if (cacheid != null) { + // KSC: TESTING!!! icache= Integer.valueOf((String)cacheid)+1; + icache = Integer.valueOf(1)!! + } + icache = bk.workBook!!.addPivotStream(ref, sheet, icache) + return PivotCacheDefinition(ref, sheet, icache) + } + } + + +} diff --git a/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.java b/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.java deleted file mode 100644 index 0f6550b..0000000 --- a/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.java +++ /dev/null @@ -1,717 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.PivotTableHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.SxStreamID; -import io.starter.formats.XLS.Sxvd; -import io.starter.formats.XLS.Sxview; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import java.io.InputStream; - -public class PivotTableDefinition implements OOXMLElement { - - private static final long serialVersionUID = -5070227633357072878L; - Sxview ptview = null; - - - /** - * NOT COMPLETED DO NOT USE - * parse a pivotTable OOXML element
                  - * Top-level attributes

                • Location information
                • Collection of fields - *
                • Fields on the row axis
                • Items on the row axis (specific values) - *
                • Fields on the column axis
                • Items on the column axis (specific - * values)
                • Fields on the report filter region
                • Fields in the values - * region
                • Style information
                  - * Outline of the XML for a pivotTableDefinition (sequence)
                • - * pivotTableDefinition
                • location
                • pivotFields
                • rowFields
                • - * rowItems
                • colFields
                • colItems
                • pageFields
                • dataFields
                • - * conditionalFormats
                • pivotTableStyleInfo - * - *
                  - * - *
                  -     * A PivotTable report that has more than one row field has one inner row field,
                  -     * 	the one closest to the data area.
                  -     *
                  -     * 	Any other row fields are outer row fields
                  -     *
                  -     * 	Items in the outermost row field are displayed only once,
                  -     * 	but items in the rest of the row fields are repeated as needed
                  -     *
                  -     * 	Page fields allow you to filter the entire PivotTable report to
                  -     * 	display data for a single item or all the items.
                  -     *
                  -     * 	Data fields provide the data values to be summarized. Usually data fields contain numbers,
                  -     * 	which are combined with the Sum summary function, but data fields can also contain text,
                  -     * 	in which case the PivotTable report uses the Count summary function.
                  -     *
                  -     * 	If a report has more than one data field, a single field button named Data
                  -     * 	appears in the report for access to all of the data fields.
                  -     * 
                  - * - * @param bk - * @param sheet - * @param ii - */ - public static PivotTableHandle parseOOXML(WorkBookHandle bk, /*Object cacheid, */Boundsheet sheet, InputStream ii) { - Sxview ptview = null; - - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pivotTableDefinition")) { // get attributes - String cacheId = "", tablename = ""; - for (int z = 0; z < xpp.getAttributeCount(); z++) { - String nm = xpp.getAttributeName(z); - String v = xpp.getAttributeValue(z); - if (nm.equals("name")) - tablename = v; - else if (nm.equals("cacheId")) { - cacheId = v; - } - //dataOnRows - //applyNumberFormats - //applyBorderFormats - //applyFontFormats - //applyPatternFormats - //applyAlignmentFormats - //applyWidthHeightFormats - //dataCaption - //updatedVersion - //showMemberPropertyTips - //useAutoFormatting - //itemPrintTitles - //createdVersion - //indent - //compact - //compactData - //gridDropZones - } -// KSC: TESTING!!! - cacheId = "0"; - short cid = Integer.valueOf(cacheId).shortValue(); - SxStreamID ptstream = bk.getWorkBook().getPivotStream(cid + 1); - ptview = sheet.addPivotTable(ptstream.getCellRange().toString(), bk, cid + 1, tablename); - ptview.setDataName("Values"); // default - ptview.setICache(cid); - } else if (tnm.equals("location")) { - parseLocationOOXML(xpp, ptview, bk); - } else if (tnm.equals("pivotFields")) { // Represents the collection of fields that appear on the PivotTable. - parsePivotFields(xpp, ptview); - } else if (tnm.equals("pageFields")) { // Represents the collection of items in the page or report filter region of the PivotTable. -// short count = Integer.valueOf(xpp.getAttributeValue(0)).shortValue(); -// ptview.setCDimPg(count); - } else if (tnm.equals("pageField")) { // count: # of pageField elements - parsePageFieldOOXML(xpp, ptview); - } else if (tnm.equals("dataFields")) { // Represents the collection of items in the data region of the PivotTable. -// short count = Integer.valueOf(xpp.getAttributeValue(0)).shortValue(); -// ptview.setCDimData(count); - } else if (tnm.equals("dataField")) { // count: # of dataField elements - parseDataFieldOOXML(xpp, ptview); - } else if (tnm.equals("colFields") || tnm.equals("rowFields")) { // the collection of fields on the column or row axis - parseFieldOOXML(xpp, ptview); - } else if (tnm.equals("rowItems") || tnm.equals("colItems")) { // the collection of column items or row items - parseLineItemOOXML(xpp, ptview); - } else if (tnm.equals("formats")) { - parseFormatsOOXML(xpp, ptview); - } else if (tnm.equals("chartFormats")) { - parseFormatsOOXML(xpp, ptview); - } else if (tnm.equals("pivotTableStyleInfo")) { - } - } else if (eventType == XmlPullParser.END_TAG) { // go to end of file - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("PivotTableDefinition.parseOOXML: " + e.toString()); - } - PivotTableHandle pth = new PivotTableHandle(ptview, bk); - return pth; - } - - /** - * parses a pivotTableDefinition location firstDataCol, firstDataRow, - * firstHeaderRow, ref [all req], colPageCount, rowPageCount - * - * @param xpp - * @param pth - * @return - * @throws XmlPullParserException - */ - private static void parseLocationOOXML(XmlPullParser xpp, Sxview ptview, WorkBookHandle bk) throws XmlPullParserException { - try { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equalsIgnoreCase("ref")) // req; Specifies the first row of the actual PivotTable (NOT the data) - ptview.setLocation(v); - else if (nm.equalsIgnoreCase("firstDataCol")) // req - // Specifies the first column of the PivotTable data, relative to the top left cell in the ref value. - ptview.setColFirstData(Integer.valueOf(v).shortValue()); - else if (nm.equalsIgnoreCase("firstDataRow")) // req - // Specifies the first row of the PivotTable data, relative to the top left cell in the ref value. - ptview.setRwFirstData(Integer.valueOf(v).shortValue()); - else if (nm.equalsIgnoreCase("firstHeaderRow")) // req - // Specifies the first row of the PivotTable header relative to the top left cell in the ref value. - ptview.setRwFirstHead(Integer.valueOf(v).shortValue()); - else if (nm.equalsIgnoreCase("rowPageCount")) // def= 0 - // Specifies the number of rows per page for this PivotTable that the filter area will occupy. By default there is a - // single column of filter fields per page and the fields occupy as many rows as there are fields. - ; - else if (nm.equalsIgnoreCase("colPageCount")) // def= 0 - // Specifies the number of columns per page for this PivotTable that the filter area will occupy. By default - // there is a single column of filter fields per page and the fields occupy as many rows as there are fields. - ; - } - } catch (Exception e) { - throw new XmlPullParserException("PivotTableHandle.parseLocation:"); - } - } - - /** - * parses the pivotFields element of the pivotTableDefinition parent - *
                  Represents the collection of fields that appear on the PivotTable. - * - * @param xpp - * @throws XmlPullParserException - */ - private static void parsePivotFields(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { - try { - int eventType = xpp.getEventType(); - String elname = xpp.getName(); - int fcount = 0; - Sxvd curAxis = null; // up to 4 axes: ROW, COL, PAGE or DATA - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pivotField")) { // Represents a single field in the PivotTable. This complex type contains information about the field, including the collection of items in the field. - curAxis = null; - for (int i = 0; i < xpp.getAttributeCount(); i++) { // TODO: HANDLE ALL ATTRIBUTES ***** - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equalsIgnoreCase("axis")) // Specifies the region of the PivotTable that this field is displayed - curAxis = ptview.addPivotFieldToAxis(axisLookup(v), fcount++); // axisPage, axisRow, axisCol - else if (nm.equalsIgnoreCase("showAll")) // Specifies a boolean value that indicates whether to show all items for this field. - ; // A value of off, 0, or false indicates items be shown according to user specified criteria - else if (nm.equalsIgnoreCase("defaultSubtotal")) // Specifies a boolean value that indicates whether the default subtotal aggregation // function is displayed for this field. - ; - else if (nm.equalsIgnoreCase("numFmtId")) // Specifies the identifier of the number format to apply to this field. - ; - else if (nm.equalsIgnoreCase("dataField")) { // Specifies a boolean value that indicates whether this field appears in the data region of the PivotTable. - if (v.equals("1") && curAxis == null) - curAxis = ptview.addPivotFieldToAxis(axisLookup("axisValues"), fcount++); // DATA axis - } else if (nm.equalsIgnoreCase("multipleItemSelectionAllowed")) // Specifies a boolean value that indicates whether the field can have multiple items selected in the page field. - ; - else if (nm.equalsIgnoreCase("sortType")) // ascending, descending or manual - ; - } - - } else if (tnm.equals("items")) { // Represents the collection of items in a PivotTable field. The items in the collection are ordered by index. Items - // represent the unique entries from the field in the source data. - parsePivotItemOOXML(xpp, ptview, curAxis); - } else if (tnm.equals("pivotArea")) { // parent= autoSortScope, which has no attributes or other children - parsePivotAreaOOXML(xpp); - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - throw new XmlPullParserException("parsePivotFields:" + e.toString()); - } - } - - /** - * parses pivot items -- Represents the collection of items in a PivotTable - * field. The items in the collection are ordered by index. Items represent - * the unique entries from the field in the source data The order in which - * the items are listed is the order they would appear on a particular axis - * (row or column, for example). parent= pivotField - * - * @param xpp - * @throws XmlPullParserException - */ - private static void parsePivotItemOOXML(XmlPullParser xpp, Sxview ptview, Sxvd axis) throws XmlPullParserException { - try { - int eventType = xpp.getEventType(); - String elname = xpp.getName(); - //int count = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // count # of item elements - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("item")) { // Represents a single item in PivotTable field. - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equals("c")) // Specifies a boolean value that indicates whether the approximate number of child items for this item is greater than zero. - ; - else if (nm.equals("d")) // Specifies a boolean value that indicates whether this item has been expanded in the PivotTable view. - ; - else if (nm.equals("e")) // Specifies a boolean value that indicates whether attribute hierarchies nested next to each other on a PivotTable row or column will offer drilling "across" each other or not. - ; - else if (nm.equals("f")) // Specifies a boolean value that indicates whether this item is a calculated member. - ; - else if (nm.equals("h")) // Specifies a boolean value that indicates whether the item is hidden. - ; - else if (nm.equals("m")) // Specifies a boolean value that indicate whether the item has a missing value. - ; - else if (nm.equals("n")) // Specifies the user caption of the item. - ; - else if (nm.equals("s")) // Specifies a boolean value that indicates whether the item has a character value. - ; - else if (nm.equals("sd")) // Specifies a boolean value that indicates whether the details are hidden for this item. - ; - else if (nm.equals("t")) // Specifies the type of this item. A value of 'default' indicates the subtotal or total item. - if (v.equals("default")) - ptview.addPivotItem(axis, 1, -1); - else - Logger.logWarn("PivitItem: Unknown type"); // REMOVE WHEN TESTED - else if (nm.equals("x")) // Specifies the item index in pivotFields collection in the PivotCache. Applies only non- OLAP PivotTables. - ptview.addPivotItem(axis, 0, Integer.valueOf(v).intValue()); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - throw new XmlPullParserException("parsePivotItemOOXML:" + e.toString()); - } - } - - /** - * parses the format element of the pivotTableDefinition. Represents the collection of formats applied to PivotTable. - * - * @param xpp - * @throws XmlPullParserException - */ - private static void parseFormatsOOXML(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { - try { - int eventType = xpp.getEventType(); - String elname = xpp.getName(); - //int count = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // count # of item elements - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("format")) { // parent= formats - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - //String v = xpp.getAttributeValue(i); - if (nm.equals("dxfId")) { // Specifies the identifier of the format the application is currently using for the PivotTable. Formatting information is written to the styles part. See the Styles section (§3.8) for more information on formats. - - } else if (nm.equals("action")) { - /* Specifies the formatting behavior for the area indicated in the pivotArea element. The - default value for this attribute is "formatting," which indicates that the specified cells - have some formatting applied. The format is specified in the dxfId attribute. If the - formatting is cleared from the cells, then the value of this attribute becomes "blank." - */ - } - } - } else if (tnm.equals("chartFormat")) { // parent= chartFormats - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - //String v = xpp.getAttributeValue(i); - if (nm.equals("chart")) { // Specifies the index of the chart part to which the formatting applies. - } else if (nm.equals("format")) { // Specifies the index of the pivot format that is currently in use. This index corresponds to a dxf element in the Styles part. - } else if (nm.equals("series")) { // Specifies a boolean value that indicates whether format applies to a series. (default=false) - } - } - } else if (tnm.equals("pivotArea")) - parsePivotAreaOOXML(xpp); - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - throw new XmlPullParserException("parsePivotAreaOOXML:" + e.toString()); - } - } - - /** - * parse pivotArea element: Rule describing a PivotTable selection (format, pivotField ...) - * - * @param xpp - * @throws XmlPullParserException - */ - private static void parsePivotAreaOOXML(XmlPullParser xpp) throws XmlPullParserException { - try { - int eventType = xpp.getEventType(); - String elname = xpp.getName(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pivotArea")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - //String v = xpp.getAttributeValue(i); - if (nm.equals("field")) // Index of the field that this selection rule refers to. - ; - else if (nm.equals("type")) // all, button, data, none, normal, origin, topRight - ; - else if (nm.equals("dataOnly")) // Flag indicating whether only the data values (in the data area of the view) for an item selection are selected and does not include the item labels. - ; - else if (nm.equals("labelOnly")) // Flag indicating whether only the item labels for an item selection are selected and does not include the data values (in the data area of the view). - ; - else if (nm.equals("outline")) // Flag indicating whether the rule refers to an area that is in outline mode. - ; - else if (nm.equals("axis")) //The region of the PivotTable to which this rule applies. - ; - else if (nm.equals("fieldPosition")) // Position of the field within the axis to which this rule applies. - ; - // grandRow, grandCol, cacheIndex, offset, collapsedLevelsAreSubtotals - } - } else if (tnm.equals("references")) { // Represents the set of selected fields and the selected items within those fields - // count - } else if (tnm.equals("reference")) { - /* - * -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 - */ - } else if (tnm.equals("x")) { - //int index= parseItemIndexOOXML(xpp); - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - throw new XmlPullParserException("parsePivotAreaOOXML:"); - } - - } - - /** - * element i parent= rowItem or colItem - *
                  the collection of items in the row axis -- index corresponds to that in the location range - *
                  OR - *
                  the collection of column items-- index corresponds to that in the location range - *
                  The first collection represents all item values for the first column in the column axis area - *
                  The first in the first corresponds to the first field in the columns area - * or - *
                  Represents the collection of items in the row region of the PivotTable. - * - * @param xpp - * @throws XmlPullParserException - */ - private static void parseLineItemOOXML(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { - try { - // number of row or col lines == cRw or cCol of SxVIEW - //int linecount= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // parent element= rowItems or colItems - int eventType = xpp.getEventType(); - String elname = xpp.getName(); - boolean isRowItems = (elname.equals("rowItems")); - - final Class enumType = ITEMTYPES.class; - int type = 0, repeat = 0; - short[] indexes = null; - int nIndexes = (isRowItems) ? ptview.getCDimRw() : ptview.getCDimCol(); - int index = 0, nLines = 0; - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("i")) { // a colItem or rowItem - indexes = new short[nIndexes]; - nLines = nIndexes; - index = 0; - type = repeat = 0; - for (int i = 0; i < xpp.getAttributeCount(); i++) { // i, r, t - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equals("i")) { // Specifies a zero-based index indicating the referenced data item it in a data field with multiple data items. - } else if (nm.equals("r")) {// Specifies the number of items to repeat from the previous row item. Note: The first item has no @r explicitly written. Since a default of "0" is specified in the schema, for any item - // whose @r is missing, a default value of "0" is implied. - repeat = Integer.valueOf(v).intValue(); - index += repeat; - nLines -= repeat; - } else if (nm.equals("t")) {// Specifies the type of the item. Value of 'default' indicates a grand total as the last row item value - // default= data, avg, blank, count, countA, data, grand, max, min, product, stdDev, stdDevP, sum, var, varP - type = Enum.valueOf(enumType, "_" + v).ordinal(); - if (type != 0 || type != 0xE) { -// index++; stil confused on this ... - nLines--; - } - } - - } - } else if (tnm.equals("x")) { - indexes[index++] = (short) parseItemIndexOOXML(xpp); // v - } - } else if (eventType == XmlPullParser.END_TAG) { - String endname = xpp.getName(); - if (endname.equals(elname)) - break; - else if (endname.equals("i")) { - if (isRowItems) - ptview.addPivotLineToROWAxis(repeat, nLines, type, indexes); - else - ptview.addPivotLineToCOLAxis(repeat, nLines, type, indexes); - } - - } - eventType = xpp.next(); - } - } catch (Exception e) { - throw new XmlPullParserException("parseItemOOXML:" + e.toString()); - } - } - - /* - * item types enum -- add an underscore because "default" is not a valid entry - */ - enum ITEMTYPES { - _data, - _default, - _sum, - _countA, - _count, - _avg, - _max, - _min, - _product, - _stdDev, - _stdDevP, - _var, - _varP, - _grand, - _blank, - } - - /** - * Represents a generic field that can appear either on the column or the - * row region of the PivotTable. There will be as many elements as there - * are item values in any particular column or row. attribute: x: Specifies - * the index to a pivotField item value - * - * @param xpp - * @throws XmlPullParserException - */ - private static void parseFieldOOXML(XmlPullParser xpp, Sxview ptview) throws XmlPullParserException { - try { - //int fieldcount = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - int eventType = xpp.getEventType(); - String elname = xpp.getName(); -/* if (elname.equals("rowFields")) - ptview.setCDimRw((short)fieldcount); - else - ptview.setCDimCol((short)fieldcount);*/ - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("field")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("x")) { - String v = xpp.getAttributeValue(i); - if (elname.equals("rowFields")) - ptview.addRowField(Integer.valueOf(v)); - else - ptview.addColField(Integer.valueOf(v)); - } - } - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(elname)) - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - throw new XmlPullParserException("parseFieldOOXML:" + e.toString()); - } - } - - /** - * This element represents an array of indexes to cached shared item values - *
                  element x - *
                  child of reference (...pivotField), i (rowField, colField) - * - * @param xpp - * @throws XmlPullParserException - * @return int index - */ - private static int parseItemIndexOOXML(XmlPullParser xpp) { - int v = 0; - try { - if (xpp.getAttributeCount() > 0) - v = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // index - } catch (Exception e) { - } - return v; - } - - private static void parseDataFieldOOXML(XmlPullParser xpp, Sxview ptview) { - /* - 9 - * 10 11 12 - */ - int fieldIndex = 0; - String aggregateFunction = null; - String name = null; - for (int z = 0; z < xpp.getAttributeCount(); z++) { - String nm = xpp.getAttributeName(z); - String v = xpp.getAttributeValue(z); - if (nm.equals("name")) - name = v; - else if (nm.equals("fld")) - fieldIndex = Integer.valueOf(xpp.getAttributeValue(z)); - else if (nm.equals("subtotal")) // default= "sum" - aggregateFunction = v; - } -// TODO: -// showDataAs, baseItem, baseField --> display format -// numFmtId - ptview.addDataField(fieldIndex, aggregateFunction, name); - } - - /** - * parse the pageField element, which defines a pivot field on the PAGE axis - * - * @param xpp - * @param ptview - */ - private static void parsePageFieldOOXML(XmlPullParser xpp, Sxview ptview) { - int fieldIndex = 0, itemIndex = 0x7FFD; - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("fld")) { - fieldIndex = Integer.valueOf(xpp.getAttributeValue(i)); - } else if (nm.equals("item")) { - itemIndex = Integer.valueOf(xpp.getAttributeValue(i)); - } - } - ptview.addPageField(fieldIndex, itemIndex); - } - /** - * Changes a range in a PivotTable to expand until it includes the cell - * address from CellHandle. - * - * Example: - * - * CellHandle cell = new Cellhandle("D4") PivotTable = SUM(A1:B2) - * addCellToRange("A1:B2",cell); would change the PivotTable to look like - * "SUM(A1:D4)" - * - * Returns false if PivotTable does not contain the PivotTableLoc range. - * - * @param String - * - the Cell Range as a String to add the Cell to - * @param CellHandle - * - the CellHandle to add to the range - * - * public boolean addCellToRange(String PivotTableLoc, CellHandle - * handle) throws PivotTableNotFoundException{ int[] - * PivotTableaddr = ExcelTools.getRangeRowCol(PivotTableLoc); - * String handleaddr = handle.getCellAddress(); int[] celladdr = - * ExcelTools.getRowColFromString(handleaddr); - * - * // check existing range and set new range vals if the new Cell - * is outside if(celladdr[0] > - * PivotTableaddr[2])PivotTableaddr[2] = celladdr[0]; - * if(celladdr[0] < PivotTableaddr[0])PivotTableaddr[0] = - * celladdr[0]; if(celladdr[1] > - * PivotTableaddr[3])PivotTableaddr[3] = celladdr[1]; - * if(celladdr[1] < PivotTableaddr[1])PivotTableaddr[1] = - * celladdr[1]; String newaddr = - * ExcelTools.formatRange(PivotTableaddr); boolean b = - * this.changePivotTableLocation(PivotTableLoc, newaddr); return - * b; } - */ - - /** - * Returns the "SXVIEW" record for this PivotTable. - * - * @return - * - * public Sxview getPt() { return pt; } - */ - - /** - * @param sxview public void setPt(Sxview sxview) { pt= sxview; } - */ - - public OOXMLElement cloneElement() { - return null; - } - - public String getOOXML() { - // TODO: Finish - return null; - } - - private static int axisLookup(String axis) { - if (axis.equals("axisRow")) - return Sxvd.AXIS_ROW; - else if (axis.equals("axisCol")) - return Sxvd.AXIS_COL; - else if (axis.equals("axisPage")) - return Sxvd.AXIS_PAGE; - else if (axis.equals("axisValues")) - return Sxvd.AXIS_DATA; - return Sxvd.AXIS_NONE; - - } - -} diff --git a/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.kt b/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.kt new file mode 100644 index 0000000..4a3c475 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/PivotTableDefinition.kt @@ -0,0 +1,746 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.PivotTableHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Boundsheet +import io.starter.formats.XLS.SxStreamID +import io.starter.formats.XLS.Sxvd +import io.starter.formats.XLS.Sxview +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import org.xmlpull.v1.XmlPullParserFactory + +import java.io.InputStream + +class PivotTableDefinition : OOXMLElement { + internal var ptview: Sxview? = null + + override// TODO: Finish + val ooxml: String? + get() = null + + /* + * item types enum -- add an underscore because "default" is not a valid entry + */ + internal enum class ITEMTYPES { + _data, + _default, + _sum, + _countA, + _count, + _avg, + _max, + _min, + _product, + _stdDev, + _stdDevP, + _var, + _varP, + _grand, + _blank + } + /** + * Changes a range in a PivotTable to expand until it includes the cell + * address from CellHandle. + * + * Example: + * + * CellHandle cell = new Cellhandle("D4") PivotTable = SUM(A1:B2) + * addCellToRange("A1:B2",cell); would change the PivotTable to look like + * "SUM(A1:D4)" + * + * Returns false if PivotTable does not contain the PivotTableLoc range. + * + * @param String + * - the Cell Range as a String to add the Cell to + * @param CellHandle + * - the CellHandle to add to the range + * + * public boolean addCellToRange(String PivotTableLoc, CellHandle + * handle) throws PivotTableNotFoundException{ int[] + * PivotTableaddr = ExcelTools.getRangeRowCol(PivotTableLoc); + * String handleaddr = handle.getCellAddress(); int[] celladdr = + * ExcelTools.getRowColFromString(handleaddr); + * + * // check existing range and set new range vals if the new Cell + * is outside if(celladdr[0] > + * PivotTableaddr[2])PivotTableaddr[2] = celladdr[0]; + * if(celladdr[0] < PivotTableaddr[0])PivotTableaddr[0] = + * celladdr[0]; if(celladdr[1] > + * PivotTableaddr[3])PivotTableaddr[3] = celladdr[1]; + * if(celladdr[1] < PivotTableaddr[1])PivotTableaddr[1] = + * celladdr[1]; String newaddr = + * ExcelTools.formatRange(PivotTableaddr); boolean b = + * this.changePivotTableLocation(PivotTableLoc, newaddr); return + * b; } + */ + + /** + * Returns the "SXVIEW" record for this PivotTable. + * + * @return + * + * public Sxview getPt() { return pt; } + */ + + /** + * @param sxview public void setPt(Sxview sxview) { pt= sxview; } + */ + + override fun cloneElement(): OOXMLElement? { + return null + } + + companion object { + + private val serialVersionUID = -5070227633357072878L + + + /** + * NOT COMPLETED DO NOT USE + * parse a pivotTable OOXML element

                  + * Top-level attributes * Location information * Collection of fields + * * Fields on the row axis * Items on the row axis (specific values) + * * Fields on the column axis * Items on the column axis (specific + * values) * Fields on the report filter region * Fields in the values + * region * Style information

                  + * Outline of the XML for a pivotTableDefinition (sequence) * + * pivotTableDefinition * location * pivotFields * rowFields * + * rowItems * colFields * colItems * pageFields * dataFields * + * conditionalFormats * pivotTableStyleInfo + * + *

                  + * + *
                  +         * A PivotTable report that has more than one row field has one inner row field,
                  +         * the one closest to the data area.
                  +         *
                  +         * Any other row fields are outer row fields
                  +         *
                  +         * Items in the outermost row field are displayed only once,
                  +         * but items in the rest of the row fields are repeated as needed
                  +         *
                  +         * Page fields allow you to filter the entire PivotTable report to
                  +         * display data for a single item or all the items.
                  +         *
                  +         * Data fields provide the data values to be summarized. Usually data fields contain numbers,
                  +         * which are combined with the Sum summary function, but data fields can also contain text,
                  +         * in which case the PivotTable report uses the Count summary function.
                  +         *
                  +         * If a report has more than one data field, a single field button named Data
                  +         * appears in the report for access to all of the data fields.
                  +        
                  * + * + * @param bk + * @param sheet + * @param ii + */ + fun parseOOXML(bk: WorkBookHandle, /*Object cacheid, */sheet: Boundsheet, ii: InputStream): PivotTableHandle { + var ptview: Sxview? = null + + try { + val factory = XmlPullParserFactory.newInstance() + factory.isNamespaceAware = true + val xpp = factory.newPullParser() + xpp.setInput(ii, null) // using XML 1.0 specification + var eventType = xpp.eventType + + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pivotTableDefinition") { // get attributes + var cacheId = "" + var tablename = "" + for (z in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(z) + val v = xpp.getAttributeValue(z) + if (nm == "name") + tablename = v + else if (nm == "cacheId") { + cacheId = v + } + //dataOnRows + //applyNumberFormats + //applyBorderFormats + //applyFontFormats + //applyPatternFormats + //applyAlignmentFormats + //applyWidthHeightFormats + //dataCaption + //updatedVersion + //showMemberPropertyTips + //useAutoFormatting + //itemPrintTitles + //createdVersion + //indent + //compact + //compactData + //gridDropZones + } + // KSC: TESTING!!! + cacheId = "0" + val cid = Integer.valueOf(cacheId).toShort() + val ptstream = bk.workBook!!.getPivotStream(cid + 1) + ptview = sheet.addPivotTable(ptstream!!.cellRange!!.toString(), bk, cid + 1, tablename) + ptview!!.dataName = "Values" // default + ptview.iCache = cid + } else if (tnm == "location") { + parseLocationOOXML(xpp, ptview, bk) + } else if (tnm == "pivotFields") { // Represents the collection of fields that appear on the PivotTable. + parsePivotFields(xpp, ptview) + } else if (tnm == "pageFields") { // Represents the collection of items in the page or report filter region of the PivotTable. + // short count = Integer.valueOf(xpp.getAttributeValue(0)).shortValue(); + // ptview.setCDimPg(count); + } else if (tnm == "pageField") { // count: # of pageField elements + parsePageFieldOOXML(xpp, ptview) + } else if (tnm == "dataFields") { // Represents the collection of items in the data region of the PivotTable. + // short count = Integer.valueOf(xpp.getAttributeValue(0)).shortValue(); + // ptview.setCDimData(count); + } else if (tnm == "dataField") { // count: # of dataField elements + parseDataFieldOOXML(xpp, ptview) + } else if (tnm == "colFields" || tnm == "rowFields") { // the collection of fields on the column or row axis + parseFieldOOXML(xpp, ptview) + } else if (tnm == "rowItems" || tnm == "colItems") { // the collection of column items or row items + parseLineItemOOXML(xpp, ptview) + } else if (tnm == "formats") { + parseFormatsOOXML(xpp, ptview) + } else if (tnm == "chartFormats") { + parseFormatsOOXML(xpp, ptview) + } else if (tnm == "pivotTableStyleInfo") { + } + } else if (eventType == XmlPullParser.END_TAG) { // go to end of file + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("PivotTableDefinition.parseOOXML: $e") + } + + return PivotTableHandle(ptview, bk) + } + + /** + * parses a pivotTableDefinition location firstDataCol, firstDataRow, + * firstHeaderRow, ref [all req], colPageCount, rowPageCount + * + * @param xpp + * @param pth + * @return + * @throws XmlPullParserException + */ + @Throws(XmlPullParserException::class) + private fun parseLocationOOXML(xpp: XmlPullParser, ptview: Sxview?, bk: WorkBookHandle) { + try { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + val v = xpp.getAttributeValue(i) + if (nm.equals("ref", ignoreCase = true)) + // req; Specifies the first row of the actual PivotTable (NOT the data) + ptview!!.setLocation(v) + else if (nm.equals("firstDataCol", ignoreCase = true)) + // req + // Specifies the first column of the PivotTable data, relative to the top left cell in the ref value. + ptview!!.colFirstData = Integer.valueOf(v).toShort() + else if (nm.equals("firstDataRow", ignoreCase = true)) + // req + // Specifies the first row of the PivotTable data, relative to the top left cell in the ref value. + ptview!!.rwFirstData = Integer.valueOf(v).toShort() + else if (nm.equals("firstHeaderRow", ignoreCase = true)) + // req + // Specifies the first row of the PivotTable header relative to the top left cell in the ref value. + ptview!!.rwFirstHead = Integer.valueOf(v).toShort() + else if (nm.equals("rowPageCount", ignoreCase = true)) + else if (nm.equals("colPageCount", ignoreCase = true)) + ;// def= 0 + // Specifies the number of columns per page for this PivotTable that the filter area will occupy. By default + // there is a single column of filter fields per page and the fields occupy as many rows as there are fields. + // def= 0 + // Specifies the number of rows per page for this PivotTable that the filter area will occupy. By default there is a + // single column of filter fields per page and the fields occupy as many rows as there are fields. + } + } catch (e: Exception) { + throw XmlPullParserException("PivotTableHandle.parseLocation:") + } + + } + + /** + * parses the pivotFields element of the pivotTableDefinition parent + *

                  Represents the collection of fields that appear on the PivotTable. + * + * @param xpp + * @throws XmlPullParserException + */ + @Throws(XmlPullParserException::class) + private fun parsePivotFields(xpp: XmlPullParser, ptview: Sxview?) { + try { + var eventType = xpp.eventType + val elname = xpp.name + var fcount = 0 + var curAxis: Sxvd? = null // up to 4 axes: ROW, COL, PAGE or DATA + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pivotField") { // Represents a single field in the PivotTable. This complex type contains information about the field, including the collection of items in the field. + curAxis = null + for (i in 0 until xpp.attributeCount) { // TODO: HANDLE ALL ATTRIBUTES ***** + val nm = xpp.getAttributeName(i) + val v = xpp.getAttributeValue(i) + if (nm.equals("axis", ignoreCase = true)) + // Specifies the region of the PivotTable that this field is displayed + curAxis = ptview!!.addPivotFieldToAxis(axisLookup(v), fcount++) // axisPage, axisRow, axisCol + else if (nm.equals("showAll", ignoreCase = true)) + else if (nm.equals("defaultSubtotal", ignoreCase = true)) + else if (nm.equals("numFmtId", ignoreCase = true)) + else if (nm.equals("dataField", ignoreCase = true)) { // Specifies a boolean value that indicates whether this field appears in the data region of the PivotTable. + if (v == "1" && curAxis == null) + curAxis = ptview!!.addPivotFieldToAxis(axisLookup("axisValues"), fcount++) // DATA axis + } else if (nm.equals("multipleItemSelectionAllowed", ignoreCase = true)) + else if (nm.equals("sortType", ignoreCase = true)) + ;// ascending, descending or manual + // Specifies a boolean value that indicates whether the field can have multiple items selected in the page field. + // Specifies the identifier of the number format to apply to this field. + // Specifies a boolean value that indicates whether the default subtotal aggregation // function is displayed for this field. + // Specifies a boolean value that indicates whether to show all items for this field. + // A value of off, 0, or false indicates items be shown according to user specified criteria + } + + } else if (tnm == "items") { // Represents the collection of items in a PivotTable field. The items in the collection are ordered by index. Items + // represent the unique entries from the field in the source data. + parsePivotItemOOXML(xpp, ptview, curAxis) + } else if (tnm == "pivotArea") { // parent= autoSortScope, which has no attributes or other children + parsePivotAreaOOXML(xpp) + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == elname) + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + throw XmlPullParserException("parsePivotFields:$e") + } + + } + + /** + * parses pivot items -- Represents the collection of items in a PivotTable + * field. The items in the collection are ordered by index. Items represent + * the unique entries from the field in the source data The order in which + * the items are listed is the order they would appear on a particular axis + * (row or column, for example). parent= pivotField + * + * @param xpp + * @throws XmlPullParserException + */ + @Throws(XmlPullParserException::class) + private fun parsePivotItemOOXML(xpp: XmlPullParser, ptview: Sxview?, axis: Sxvd?) { + try { + var eventType = xpp.eventType + val elname = xpp.name + //int count = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // count # of item elements + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "item") { // Represents a single item in PivotTable field. + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + val v = xpp.getAttributeValue(i) + if (nm == "c") + else if (nm == "d") + else if (nm == "e") + else if (nm == "f") + else if (nm == "h") + else if (nm == "m") + else if (nm == "n") + else if (nm == "s") + else if (nm == "sd") + else if (nm == "t") + // Specifies the type of this item. A value of 'default' indicates the subtotal or total item. + if (v == "default") + ptview!!.addPivotItem(axis!!, 1, -1) + else + Logger.logWarn("PivitItem: Unknown type") // REMOVE WHEN TESTED + else if (nm == "x") + // Specifies the item index in pivotFields collection in the PivotCache. Applies only non- OLAP PivotTables. + ptview!!.addPivotItem(axis!!, 0, Integer.valueOf(v).toInt())// Specifies a boolean value that indicates whether the details are hidden for this item. + // Specifies a boolean value that indicates whether the item has a character value. + // Specifies the user caption of the item. + // Specifies a boolean value that indicate whether the item has a missing value. + // Specifies a boolean value that indicates whether the item is hidden. + // Specifies a boolean value that indicates whether this item is a calculated member. + // Specifies a boolean value that indicates whether attribute hierarchies nested next to each other on a PivotTable row or column will offer drilling "across" each other or not. + // Specifies a boolean value that indicates whether this item has been expanded in the PivotTable view. + // Specifies a boolean value that indicates whether the approximate number of child items for this item is greater than zero. + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == elname) + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + throw XmlPullParserException("parsePivotItemOOXML:$e") + } + + } + + /** + * parses the format element of the pivotTableDefinition. Represents the collection of formats applied to PivotTable. + * + * @param xpp + * @throws XmlPullParserException + */ + @Throws(XmlPullParserException::class) + private fun parseFormatsOOXML(xpp: XmlPullParser, ptview: Sxview?) { + try { + var eventType = xpp.eventType + val elname = xpp.name + //int count = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // count # of item elements + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "format") { // parent= formats + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + //String v = xpp.getAttributeValue(i); + if (nm == "dxfId") { // Specifies the identifier of the format the application is currently using for the PivotTable. Formatting information is written to the styles part. See the Styles section (§3.8) for more information on formats. + + } else if (nm == "action") { + /* Specifies the formatting behavior for the area indicated in the pivotArea element. The + default value for this attribute is "formatting," which indicates that the specified cells + have some formatting applied. The format is specified in the dxfId attribute. If the + formatting is cleared from the cells, then the value of this attribute becomes "blank." + */ + } + } + } else if (tnm == "chartFormat") { // parent= chartFormats + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + //String v = xpp.getAttributeValue(i); + if (nm == "chart") { // Specifies the index of the chart part to which the formatting applies. + } else if (nm == "format") { // Specifies the index of the pivot format that is currently in use. This index corresponds to a dxf element in the Styles part. + } else if (nm == "series") { // Specifies a boolean value that indicates whether format applies to a series. (default=false) + } + } + } else if (tnm == "pivotArea") + parsePivotAreaOOXML(xpp) + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == elname) + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + throw XmlPullParserException("parsePivotAreaOOXML:$e") + } + + } + + /** + * parse pivotArea element: Rule describing a PivotTable selection (format, pivotField ...) + * + * @param xpp + * @throws XmlPullParserException + */ + @Throws(XmlPullParserException::class) + private fun parsePivotAreaOOXML(xpp: XmlPullParser) { + try { + var eventType = xpp.eventType + val elname = xpp.name + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pivotArea") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + //String v = xpp.getAttributeValue(i); + if (nm == "field") + else if (nm == "type") + else if (nm == "dataOnly") + else if (nm == "labelOnly") + else if (nm == "outline") + else if (nm == "axis") + else if (nm == "fieldPosition") + ;// Position of the field within the axis to which this rule applies. + //The region of the PivotTable to which this rule applies. + // Flag indicating whether the rule refers to an area that is in outline mode. + // Flag indicating whether only the item labels for an item selection are selected and does not include the data values (in the data area of the view). + // Flag indicating whether only the data values (in the data area of the view) for an item selection are selected and does not include the item labels. + // all, button, data, none, normal, origin, topRight + // Index of the field that this selection rule refers to. + // grandRow, grandCol, cacheIndex, offset, collapsedLevelsAreSubtotals + } + } else if (tnm == "references") { // Represents the set of selected fields and the selected items within those fields + // count + } else if (tnm == "reference") { + /* + * +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 + */ + } else if (tnm == "x") { + //int index= parseItemIndexOOXML(xpp); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == elname) + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + throw XmlPullParserException("parsePivotAreaOOXML:") + } + + } + + /** + * element i parent= rowItem or colItem + *

                  the collection of items in the row axis -- index corresponds to that in the location range + *

                  OR + *

                  the collection of column items-- index corresponds to that in the location range + *

                  The first * collection represents all item values for the first column in the column axis area + *

                  The first in the first * corresponds to the first field in the columns area + * or + *

                  Represents the collection of items in the row region of the PivotTable. + * + * @param xpp + * @throws XmlPullParserException + *
                  * */ + @Throws(XmlPullParserException::class) + private fun parseLineItemOOXML(xpp: XmlPullParser, ptview: Sxview?) { + try { + // number of row or col lines == cRw or cCol of SxVIEW + //int linecount= Integer.valueOf(xpp.getAttributeValue(0)).intValue(); // parent element= rowItems or colItems + var eventType = xpp.eventType + val elname = xpp.name + val isRowItems = elname == "rowItems" + + val enumType = ITEMTYPES::class.java + var type = 0 + var repeat = 0 + var indexes: ShortArray? = null + val nIndexes = (if (isRowItems) ptview!!.cDimRw else ptview!!.cDimCol).toInt() + var index = 0 + var nLines = 0 + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "i") { // a colItem or rowItem + indexes = ShortArray(nIndexes) + nLines = nIndexes + index = 0 + repeat = 0 + type = repeat + for (i in 0 until xpp.attributeCount) { // i, r, t + val nm = xpp.getAttributeName(i) + val v = xpp.getAttributeValue(i) + if (nm == "i") { // Specifies a zero-based index indicating the referenced data item it in a data field with multiple data items. + } else if (nm == "r") {// Specifies the number of items to repeat from the previous row item. Note: The first item has no @r explicitly written. Since a default of "0" is specified in the schema, for any item + // whose @r is missing, a default value of "0" is implied. + repeat = Integer.valueOf(v).toInt() + index += repeat + nLines -= repeat + } else if (nm == "t") {// Specifies the type of the item. Value of 'default' indicates a grand total as the last row item value + // default= data, avg, blank, count, countA, data, grand, max, min, product, stdDev, stdDevP, sum, var, varP + type = Enum.valueOf(enumType!!, "_$v").ordinal + if (type != 0 || type != 0xE) { + // index++; stil confused on this ... + nLines-- + } + } + + } + } else if (tnm == "x") { + indexes[index++] = parseItemIndexOOXML(xpp).toShort() // v + } + } else if (eventType == XmlPullParser.END_TAG) { + val endname = xpp.name + if (endname == elname) + break + else if (endname == "i") { + if (isRowItems) + ptview.addPivotLineToROWAxis(repeat, nLines, type, indexes) + else + ptview.addPivotLineToCOLAxis(repeat, nLines, type, indexes) + } + + } + eventType = xpp.next() + } + } catch (e: Exception) { + throw XmlPullParserException("parseItemOOXML:$e") + } + + } + + /** + * Represents a generic field that can appear either on the column or the + * row region of the PivotTable. There will be as many elements as there + * are item values in any particular column or row. attribute: x: Specifies + * the index to a pivotField item value + * + * @param xpp + * @throws XmlPullParserException + */ + @Throws(XmlPullParserException::class) + private fun parseFieldOOXML(xpp: XmlPullParser, ptview: Sxview?) { + try { + //int fieldcount = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); + var eventType = xpp.eventType + val elname = xpp.name + /* if (elname.equals("rowFields")) + ptview.setCDimRw((short)fieldcount); + else + ptview.setCDimCol((short)fieldcount);*/ + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "field") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "x") { + val v = xpp.getAttributeValue(i) + if (elname == "rowFields") + ptview!!.addRowField(Integer.valueOf(v)) + else + ptview!!.addColField(Integer.valueOf(v)) + } + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == elname) + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + throw XmlPullParserException("parseFieldOOXML:$e") + } + + } + + /** + * This element represents an array of indexes to cached shared item values + *

                  element x + *

                  child of reference (...pivotField), i (rowField, colField) + * + * @param xpp + * @throws XmlPullParserException + * @return int index + */ + private fun parseItemIndexOOXML(xpp: XmlPullParser): Int { + var v = 0 + try { + if (xpp.attributeCount > 0) + v = Integer.valueOf(xpp.getAttributeValue(0)).toInt() // index + } catch (e: Exception) { + } + + return v + } + + private fun parseDataFieldOOXML(xpp: XmlPullParser, ptview: Sxview?) { + /* + 9 + * 10 11 12 + */ + var fieldIndex = 0 + var aggregateFunction: String? = null + var name: String? = null + for (z in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(z) + val v = xpp.getAttributeValue(z) + if (nm == "name") + name = v + else if (nm == "fld") + fieldIndex = Integer.valueOf(xpp.getAttributeValue(z)) + else if (nm == "subtotal") + // default= "sum" + aggregateFunction = v + } + // TODO: + // showDataAs, baseItem, baseField --> display format + // numFmtId + ptview!!.addDataField(fieldIndex, aggregateFunction, name) + } + + /** + * parse the pageField element, which defines a pivot field on the PAGE axis + * + * @param xpp + * @param ptview + */ + private fun parsePageFieldOOXML(xpp: XmlPullParser, ptview: Sxview?) { + var fieldIndex = 0 + var itemIndex = 0x7FFD + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "fld") { + fieldIndex = Integer.valueOf(xpp.getAttributeValue(i)) + } else if (nm == "item") { + itemIndex = Integer.valueOf(xpp.getAttributeValue(i)) + } + } + ptview!!.addPageField(fieldIndex, itemIndex) + } + + private fun axisLookup(axis: String): Int { + if (axis == "axisRow") + return Sxvd.AXIS_ROW.toInt() + else if (axis == "axisCol") + return Sxvd.AXIS_COL.toInt() + else if (axis == "axisPage") + return Sxvd.AXIS_PAGE.toInt() + else if (axis == "axisValues") + return Sxvd.AXIS_DATA.toInt() + return Sxvd.AXIS_NONE.toInt() + + } + } + +} diff --git a/src/main/java/io/starter/formats/OOXML/SheetPr.java b/src/main/java/io/starter/formats/OOXML/SheetPr.java deleted file mode 100644 index 559d1fd..0000000 --- a/src/main/java/io/starter/formats/OOXML/SheetPr.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; - -/** - * sheetPr (Sheet Properties) - *

                  - * Sheet-level properties. - *

                  - * parent: worksheet, dialogsheet - * children: tabColor, outlinePr, pageSetUpPr - */ -// TODO: Finish pageSetUpPr + input into 2003 sheet settings ** -public class SheetPr implements OOXMLElement { - - private static final long serialVersionUID = 1781567781060400234L; - private HashMap attrs; - private TabColor tab; - private OutlinePr outlinePr; - private PageSetupPr pageSetupPr; - - - public SheetPr(HashMap attrs, TabColor tab, OutlinePr op, PageSetupPr pr) { - this.attrs = attrs; - this.tab = tab; - this.outlinePr = op; - this.pageSetupPr = pr; - } - - public SheetPr(SheetPr sp) { - this.attrs = sp.attrs; - this.tab = sp.tab; - this.outlinePr = sp.outlinePr; - this.pageSetupPr = sp.pageSetupPr; - } - - /** - * outlinePr - * pageSetUpPr - * tabColor - * codeName - * enableFormatConditionsCalculation - * filterMode - * published - * syncHorizontal - * syncRef - * syncVertical - * transitionEntry - * transitionEvaluation - * - * @param xpp - * @return - */ - public static SheetPr parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - TabColor tab = null; - OutlinePr op = null; - PageSetupPr pr = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sheetPr")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("pageSetUpPr")) { // Page setup properties of the worksheet - pr = PageSetupPr.parseOOXML(xpp); - } else if (tnm.equals("outlinePr")) { - op = OutlinePr.parseOOXML(xpp); - } else if (tnm.equals("tabColor")) { - tab = TabColor.parseOOXML(xpp); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("sheetPr")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("sheetPr.parseOOXML: " + e.toString()); - } - SheetPr sp = new SheetPr(attrs, tab, op, pr); - return sp; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - - ooxml.append(">"); - // ordered sequence: - if (tab != null) ooxml.append(tab.getOOXML()); - if (outlinePr != null) ooxml.append(outlinePr.getOOXML()); - if (pageSetupPr != null) ooxml.append(pageSetupPr.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SheetPr(this); - } - - /** - * return the codename used to link this sheet to vba code - * - * @return - */ - public String getCodename() { - if (attrs != null) - return attrs.get("codeName"); - return null; - } - - /** - * set the codename used to link this sheet to vba code - * - * @param codename - */ - public void setCodename(String codename) { - if (attrs == null) - attrs = new HashMap(); - attrs.put("codeName", codename); - } -} - -/** - * (Sheet Tab Color) - * Background color of the sheet tab. - *

                  - * parent: sheetPr - * children: none - */ -class TabColor implements OOXMLElement { - - private static final long serialVersionUID = -2862996863147633555L; - private HashMap attrs; - - public TabColor(HashMap attrs) { - this.attrs = attrs; - } - - public TabColor(TabColor t) { - this.attrs = t.attrs; - } - - - public static TabColor parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tabColor")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("tabColor")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("tabColor.parseOOXML: " + e.toString()); - } - TabColor t = new TabColor(attrs); - return t; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TabColor(this); - } -} - -/** - * outlinePr - * Sheet Group Outline Settings - * all attributes are optional, default values: - * applyStyles="0" showOutlineSymbols="1" summaryBelow="1" summaryRight="1" - */ -class OutlinePr implements OOXMLElement { - - private static final long serialVersionUID = 3030511803286369045L; - private HashMap attrs; - - public OutlinePr(HashMap attrs) { - this.attrs = attrs; - } - - public OutlinePr(OutlinePr op) { - this.attrs = op.attrs; - } - - - public static OutlinePr parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("outlinePr")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("outlinePr")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OutlinePr.parseOOXML: " + e.toString()); - } - OutlinePr op = new OutlinePr(attrs); - return op; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new OutlinePr(this); - } -} - -/** - * pageSetUpPr (Page Setup Properties) - * Page setup properties of the worksheet - * attributes: autoPageBreaks (default=true), fitToPage (default=false) - */ -class PageSetupPr implements OOXMLElement { - - private static final long serialVersionUID = 3030511803286369045L; - private boolean autoPageBreaks = true, fitToPage = false; - - public PageSetupPr(boolean autoPageBreaks, boolean fitToPage) { - this.autoPageBreaks = autoPageBreaks; - this.fitToPage = fitToPage; - } - - public PageSetupPr(PageSetupPr pr) { - this.autoPageBreaks = pr.autoPageBreaks; - this.fitToPage = pr.fitToPage; - } - - public static PageSetupPr parseOOXML(XmlPullParser xpp) { - boolean autoPageBreaks = true, fitToPage = false; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pageSetUpPr")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("fitToPage")) - fitToPage = (xpp.getAttributeValue(i).equals("1")); - else if (nm.equals("autoPageBreaks")) - autoPageBreaks = (xpp.getAttributeValue(i).equals("1")); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("pageSetUpPr")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("PageSetupPr: " + e.toString()); - } - PageSetupPr pr = new PageSetupPr(autoPageBreaks, fitToPage); - return pr; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new PageSetupPr(this); - } -} - - diff --git a/src/main/java/io/starter/formats/OOXML/SheetPr.kt b/src/main/java/io/starter/formats/OOXML/SheetPr.kt new file mode 100644 index 0000000..48c98ed --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/SheetPr.kt @@ -0,0 +1,383 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap + +/** + * sheetPr (Sheet Properties) + * + * + * Sheet-level properties. + * + * + * parent: worksheet, dialogsheet + * children: tabColor, outlinePr, pageSetUpPr + */ +// TODO: Finish pageSetUpPr + input into 2003 sheet settings ** +class SheetPr : OOXMLElement { + private var attrs: HashMap? = null + private var tab: TabColor? = null + private var outlinePr: OutlinePr? = null + private var pageSetupPr: PageSetupPr? = null + + override// attributes + // ordered sequence: + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (tab != null) ooxml.append(tab!!.ooxml) + if (outlinePr != null) ooxml.append(outlinePr!!.ooxml) + if (pageSetupPr != null) ooxml.append(pageSetupPr!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * return the codename used to link this sheet to vba code + * + * @return + */ + /** + * set the codename used to link this sheet to vba code + * + * @param codename + */ + var codename: String? + get() = if (attrs != null) attrs!!["codeName"] else null + set(codename) { + if (attrs == null) + attrs = HashMap() + attrs!!["codeName"] = codename + } + + + constructor(attrs: HashMap, tab: TabColor, op: OutlinePr, pr: PageSetupPr) { + this.attrs = attrs + this.tab = tab + this.outlinePr = op + this.pageSetupPr = pr + } + + constructor(sp: SheetPr) { + this.attrs = sp.attrs + this.tab = sp.tab + this.outlinePr = sp.outlinePr + this.pageSetupPr = sp.pageSetupPr + } + + override fun cloneElement(): OOXMLElement { + return SheetPr(this) + } + + companion object { + + private val serialVersionUID = 1781567781060400234L + + /** + * outlinePr + * pageSetUpPr + * tabColor + * codeName + * enableFormatConditionsCalculation + * filterMode + * published + * syncHorizontal + * syncRef + * syncVertical + * transitionEntry + * transitionEvaluation + * + * @param xpp + * @return + */ + fun parseOOXML(xpp: XmlPullParser): SheetPr { + val attrs = HashMap() + var tab: TabColor? = null + var op: OutlinePr? = null + var pr: PageSetupPr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "sheetPr") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "pageSetUpPr") { // Page setup properties of the worksheet + pr = PageSetupPr.parseOOXML(xpp) + } else if (tnm == "outlinePr") { + op = OutlinePr.parseOOXML(xpp) + } else if (tnm == "tabColor") { + tab = TabColor.parseOOXML(xpp) + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "sheetPr") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("sheetPr.parseOOXML: $e") + } + + return SheetPr(attrs, tab, op, pr) + } + } +} + +/** + * (Sheet Tab Color) + * Background color of the sheet tab. + * + * + * parent: sheetPr + * children: none + */ +internal class TabColor : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(t: TabColor) { + this.attrs = t.attrs + } + + override fun cloneElement(): OOXMLElement { + return TabColor(this) + } + + companion object { + + private val serialVersionUID = -2862996863147633555L + + + fun parseOOXML(xpp: XmlPullParser): TabColor { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "tabColor") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "tabColor") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("tabColor.parseOOXML: $e") + } + + return TabColor(attrs) + } + } +} + +/** + * outlinePr + * Sheet Group Outline Settings + * all attributes are optional, default values: + * applyStyles="0" showOutlineSymbols="1" summaryBelow="1" summaryRight="1" + */ +internal class OutlinePr : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(op: OutlinePr) { + this.attrs = op.attrs + } + + override fun cloneElement(): OOXMLElement { + return OutlinePr(this) + } + + companion object { + + private val serialVersionUID = 3030511803286369045L + + + fun parseOOXML(xpp: XmlPullParser): OutlinePr { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "outlinePr") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "outlinePr") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("OutlinePr.parseOOXML: $e") + } + + return OutlinePr(attrs) + } + } +} + +/** + * pageSetUpPr (Page Setup Properties) + * Page setup properties of the worksheet + * attributes: autoPageBreaks (default=true), fitToPage (default=false) + */ +internal class PageSetupPr : OOXMLElement { + private var autoPageBreaks = true + private var fitToPage = false + + override// attributes + // if not default + // if not default + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(autoPageBreaks: Boolean, fitToPage: Boolean) { + this.autoPageBreaks = autoPageBreaks + this.fitToPage = fitToPage + } + + constructor(pr: PageSetupPr) { + this.autoPageBreaks = pr.autoPageBreaks + this.fitToPage = pr.fitToPage + } + + override fun cloneElement(): OOXMLElement { + return PageSetupPr(this) + } + + companion object { + + private val serialVersionUID = 3030511803286369045L + + fun parseOOXML(xpp: XmlPullParser): PageSetupPr { + var autoPageBreaks = true + var fitToPage = false + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pageSetUpPr") { // get attributes + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "fitToPage") + fitToPage = xpp.getAttributeValue(i) == "1" + else if (nm == "autoPageBreaks") + autoPageBreaks = xpp.getAttributeValue(i) == "1" + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "pageSetUpPr") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("PageSetupPr: $e") + } + + return PageSetupPr(autoPageBreaks, fitToPage) + } + } +} + + diff --git a/src/main/java/io/starter/formats/OOXML/SheetView.java b/src/main/java/io/starter/formats/OOXML/SheetView.java deleted file mode 100644 index a72d559..0000000 --- a/src/main/java/io/starter/formats/OOXML/SheetView.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; - -/** - * sheetView (Worksheet View) - *

                  - * A single sheet view definition. When more than 1 sheet view is defined in the file, it means that when opening - * the workbook, each sheet view corresponds to a separate window within the spreadsheet application, where - * each window is showing the particular sheet. containing the same workbookViewId value, the last sheetView - * definition is loaded, and the others are discarded. When multiple windows are viewing the same sheet, multiple - * sheetView elements (with corresponding workbookView entries) are saved. - *

                  - * parent: sheetViews - * children: pane, selection, pivotSelection - */ -// TODO: finish pivotSelection -public class SheetView implements OOXMLElement { - - private static final long serialVersionUID = 8750051341951797617L; - private HashMap attrs = new HashMap(); - private Pane pane = null; - private ArrayList selections = new ArrayList(); - - public SheetView() { - - } - - public SheetView(HashMap attrs, Pane p, ArrayList selections) { - this.attrs = attrs; - this.pane = p; - this.selections = selections; - } - - public SheetView(SheetView s) { - this.attrs = s.attrs; - this.pane = s.pane; - this.selections = s.selections; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - Pane p = null; - ArrayList selections = new ArrayList(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sheetView")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("pane")) { - p = Pane.parseOOXML(xpp); - } else if (tnm.equals("selection")) { - selections.add(Selection.parseOOXML(xpp)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("sheetView")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("sheetView.parseOOXML: " + e.toString()); - } - SheetView s = new SheetView(attrs, p, selections); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = (String) attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - if (pane != null) ooxml.append(pane.getOOXML()); - if (selections.size() > 0) { - for (int j = 0; j < selections.size(); j++) { - ooxml.append(selections.get(j).getOOXML()); - } - } - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SheetView(this); - } - - - /** - * return the attribute value for key null if not found - * - * @param key - * @return - */ - public Object getAttr(String key) { - return this.attrs.get(key); - } - - /** - * set the atttribute value for key to val - * - * @param key - * @param val - */ - public void setAttr(String key, Object val) { - this.attrs.put(key, val); - } - - /** - * remove a previously set attribute, if found - * - * @param key - */ - public void removeAttr(String key) { - this.attrs.remove(key); - } - - public void removeSelection() { - this.removeAttr("tabSelected"); - selections = new ArrayList(); - } - - /** - * return the attribute value for key - * in String form "" if not found - * - * @param key - * @return - */ - public String getAttrS(String key) { - Object o = this.attrs.get(key); - if (o == null) - return ""; - return o.toString(); - } - -} - -/** - * pane (View Pane) - * Worksheet view pane - *

                  - * parent: sheetView, customSheetView - * children: none - */ -class Pane implements OOXMLElement { - private static final long serialVersionUID = 5570779997661362205L; - private HashMap attrs = null; - - public Pane(HashMap attrs) { - this.attrs = attrs; - } - - public Pane(Pane p) { - this.attrs = p.attrs; - } - - - public static Pane parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("pane")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("pane")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("pane.parseOOXML: " + e.toString()); - } - Pane p = new Pane(attrs); - return p; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Pane(this); - } -} - -/** - * selection (Selection) - * Worksheet view selection. - *

                  - * parent: sheetView, customSheetView - * children: none - */ -class Selection implements OOXMLElement { - - private static final long serialVersionUID = -5411798327743116154L; - private HashMap attrs = null; - - public Selection(HashMap attrs) { - this.attrs = attrs; - } - - public Selection(Selection s) { - this.attrs = s.attrs; - } - - public static Selection parseOOXML(XmlPullParser xpp) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("selection")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("selection")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("selection.parseOOXML: " + e.toString()); - } - Selection s = new Selection(attrs); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Selection(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/SheetView.kt b/src/main/java/io/starter/formats/OOXML/SheetView.kt new file mode 100644 index 0000000..6a0c93c --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/SheetView.kt @@ -0,0 +1,321 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.HashMap + +/** + * sheetView (Worksheet View) + * + * + * A single sheet view definition. When more than 1 sheet view is defined in the file, it means that when opening + * the workbook, each sheet view corresponds to a separate window within the spreadsheet application, where + * each window is showing the particular sheet. containing the same workbookViewId value, the last sheetView + * definition is loaded, and the others are discarded. When multiple windows are viewing the same sheet, multiple + * sheetView elements (with corresponding workbookView entries) are saved. + * + * + * parent: sheetViews + * children: pane, selection, pivotSelection + */ +// TODO: finish pivotSelection +class SheetView : OOXMLElement { + private var attrs = HashMap() + private var pane: Pane? = null + private var selections = ArrayList() + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (pane != null) ooxml.append(pane!!.ooxml) + if (selections.size > 0) { + for (j in selections.indices) { + ooxml.append(selections[j].ooxml) + } + } + ooxml.append("") + return ooxml.toString() + } + + constructor() { + + } + + constructor(attrs: HashMap, p: Pane, selections: ArrayList) { + this.attrs = attrs + this.pane = p + this.selections = selections + } + + constructor(s: SheetView) { + this.attrs = s.attrs + this.pane = s.pane + this.selections = s.selections + } + + override fun cloneElement(): OOXMLElement { + return SheetView(this) + } + + + /** + * return the attribute value for key null if not found + * + * @param key + * @return + */ + fun getAttr(key: String): Any { + return this.attrs[key] + } + + /** + * set the atttribute value for key to val + * + * @param key + * @param val + */ + fun setAttr(key: String, `val`: Any) { + this.attrs[key] = `val` + } + + /** + * remove a previously set attribute, if found + * + * @param key + */ + fun removeAttr(key: String) { + this.attrs.remove(key) + } + + fun removeSelection() { + this.removeAttr("tabSelected") + selections = ArrayList() + } + + /** + * return the attribute value for key + * in String form "" if not found + * + * @param key + * @return + */ + fun getAttrS(key: String): String { + val o = this.attrs[key] ?: return "" + return o.toString() + } + + companion object { + + private val serialVersionUID = 8750051341951797617L + + fun parseOOXML(xpp: XmlPullParser): OOXMLElement { + val attrs = HashMap() + var p: Pane? = null + val selections = ArrayList() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "sheetView") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "pane") { + p = Pane.parseOOXML(xpp) + } else if (tnm == "selection") { + selections.add(Selection.parseOOXML(xpp)) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "sheetView") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("sheetView.parseOOXML: $e") + } + + return SheetView(attrs, p, selections) + } + } + +} + +/** + * pane (View Pane) + * Worksheet view pane + * + * + * parent: sheetView, customSheetView + * children: none + */ +internal class Pane : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(p: Pane) { + this.attrs = p.attrs + } + + override fun cloneElement(): OOXMLElement { + return Pane(this) + } + + companion object { + private val serialVersionUID = 5570779997661362205L + + + fun parseOOXML(xpp: XmlPullParser): Pane { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "pane") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "pane") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("pane.parseOOXML: $e") + } + + return Pane(attrs) + } + } +} + +/** + * selection (Selection) + * Worksheet view selection. + * + * + * parent: sheetView, customSheetView + * children: none + */ +internal class Selection : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(s: Selection) { + this.attrs = s.attrs + } + + override fun cloneElement(): OOXMLElement { + return Selection(this) + } + + companion object { + + private val serialVersionUID = -5411798327743116154L + + fun parseOOXML(xpp: XmlPullParser): Selection { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "selection") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "selection") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("selection.parseOOXML: $e") + } + + return Selection(attrs) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Sp.java b/src/main/java/io/starter/formats/OOXML/Sp.java deleted file mode 100644 index 8d6043c..0000000 --- a/src/main/java/io/starter/formats/OOXML/Sp.java +++ /dev/null @@ -1,545 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * sp (Shape) - *

                  - * OOXML/DrawingML element representing a single shape - * A shape can either be a preset or a custom geometry, - * defined using the DrawingML framework. In addition to a geometry each shape can have both visual and non - * visual properties attached. Text and corresponding styling information can also be attached to a shape. - *

                  - * parent = twoCellAnchor, oneCellAnchor, absoluteAnchor, grpSp (group shape) - * children: nvSpPr, spPr, style, txBody - */ -public class Sp implements OOXMLElement { - - private static final long serialVersionUID = 7454285931503575078L; - private NvSpPr nvsp; - private SpPr sppr; - private Style sty; - private TxBody txb; - private HashMap attrs = null; - - public Sp(NvSpPr nvsp, SpPr sppr, Style sty, TxBody txb, HashMap attrs) { - this.nvsp = nvsp; - this.sppr = sppr; - this.sty = sty; - this.txb = txb; - this.attrs = attrs; - } - - public Sp(Sp shp) { - this.nvsp = shp.nvsp; - this.sppr = shp.sppr; - this.sty = shp.sty; - this.txb = shp.txb; - this.attrs = shp.attrs; - } - - - /** - * return the OOXML specific for this object - * - * @param xpp - * @param lastTag - * @return - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - NvSpPr nvsp = null; - SpPr sppr = null; - Style sty = null; - TxBody txb = null; - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sp")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("nvSpPr")) { // non-visual shape props - lastTag.push(tnm); - nvsp = NvSpPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("spPr")) { // shape properties - lastTag.push(tnm); - sppr = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - //sppr.setNS("xdr"); - } else if (tnm.equals("style")) { // shape style - lastTag.push(tnm); - sty = (Style) Style.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("txBody")) { // shape text body - lastTag.push(tnm); - txb = (TxBody) TxBody.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("sp")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("sp.parseOOXML: " + e.toString()); - } - Sp shp = new Sp(nvsp, sppr, sty, txb, attrs); - return shp; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append(">"); - ooxml.append(nvsp.getOOXML()); - ooxml.append(sppr.getOOXML()); - if (sty != null) ooxml.append(sty.getOOXML()); - if (txb != null) ooxml.append(txb.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Sp(this); - } - - /** - * get Macro attribute - */ - public String getMacro() { - if (attrs.get("macro") != null) - return attrs.get("macro"); - return null; - } - - /** - * set Macro attribute - * - * @param macro - */ - public void setMacro(String macro) { - attrs.put("macro", macro); - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (sppr != null) - return sppr.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (sppr != null) - return sppr.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - if (sppr != null) sppr.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (sppr != null) sppr.setLink(link); - } - - /** - * return the name of this shape, if any - * - * @return - */ - public String getName() { - if (nvsp != null) - return nvsp.getName(); - return null; - } - - /** - * set the name of this shape, if any - */ - public void setName(String name) { - if (nvsp != null) - nvsp.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (nvsp != null) - return nvsp.getDescr(); - return null; - } - - /** - * set cNvPr description attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setDescr(String descr) { - if (nvsp != null) - nvsp.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (nvsp != null) - nvsp.setId(id); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (nvsp != null) - return nvsp.getId(); - return -1; - } - -} - -/** - * This element specifies all non-visual properties for a shape. This element is a container for the non-visual - * identification properties, shape properties and application properties that are to be associated with a shape. - * This allows for additional information that does not affect the appearance of the shape to be stored - *

                  - * parent: sp - * children: cNvPr REQ cNvSpPr REQ - */ -class NvSpPr implements OOXMLElement { - - private static final long serialVersionUID = 9121235009516398367L; - private CNvPr cnv = null; - private CNvSpPr cnvsp = null; - - public NvSpPr(CNvPr cnv, CNvSpPr cnvsp) { - this.cnv = cnv; - this.cnvsp = cnvsp; - } - - public NvSpPr(NvSpPr nvsp) { - this.cnv = nvsp.cnv; - this.cnvsp = nvsp.cnvsp; - } - - public static NvSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - CNvPr cnv = null; - CNvSpPr cnvsp = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvPr")) { - lastTag.push(tnm); - cnv = (CNvPr) CNvPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cNvSpPr")) { - lastTag.push(tnm); - cnvsp = CNvSpPr.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("nvSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("txBody.parseOOXML: " + e.toString()); - } - NvSpPr nvp = new NvSpPr(cnv, cnvsp); - return nvp; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append(cnv.getOOXML()); - ooxml.append(cnvsp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new NvSpPr(this); - } - - /** - * get name attribute - * - * @return - */ - public String getName() { - if (cnv != null) - return cnv.getName(); - return null; - } - - /** - * set name attribute - * - * @param name - */ - public void setName(String name) { - if (cnv != null) - cnv.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (cnv != null) - return cnv.getDescr(); - return null; - } - - /** - * set cNvPr desc attribute - * - * @param name - */ - public void setDescr(String descr) { - if (cnv != null) - cnv.setDescr(descr); - } - - /** - * set the cNvPr id for this element - * - * @param id - */ - public void setId(int id) { - if (cnv != null) - cnv.setId(id); - } - - /** - * return the cNvPr id for this element - * - * @return - */ - public int getId() { - if (cnv != null) - return cnv.getId(); - return -1; - } -} - -/** - * cNvSpPr (Non-visual Drawing Properties for a shape - *

                  - * This OOXML/DrawingML element specifies the non-visual drawing properties for a shape. - * These properties are to be used by the generating application to determine how the shape should be dealt with - *

                  - * attributes: txBox (optional boolean) - * parent: nvSpPr - * children: spLocks (shapeLocks) - */ -class CNvSpPr implements OOXMLElement { - - private static final long serialVersionUID = 7895953516797436713L; - private String txBox = null; - private SpLocks sp = null; - - public CNvSpPr(String t, SpLocks sp) { - this.txBox = t; - this.sp = sp; - } - - public CNvSpPr(CNvSpPr c) { - this.txBox = c.txBox; - this.sp = c.sp; - } - - - public static CNvSpPr parseOOXML(XmlPullParser xpp, Stack lastTag) { - String txBox = null; - SpLocks sp = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("cNvSpPr")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - if (xpp.getAttributeName(i).equals("txBox")) - txBox = xpp.getAttributeValue(i); - } - } else if (tnm.equals("spLocks")) { - lastTag.push(tnm); - sp = SpLocks.parseOOXML(xpp, lastTag); - - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("cNvSpPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("cNvSpPr.parseOOXML: " + e.toString()); - } - CNvSpPr cnv = new CNvSpPr(txBox, sp); - return cnv; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (sp != null) ooxml.append(sp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new CNvSpPr(this); - } -} - -/** - * spLocks (Shape Locks) - *

                  - * OOXML/DrawingML element specifies all locking properties for a shape. - * These properties inform the generating application - * about specific properties that have been previously locked and thus should not be changed. - *

                  - * parents: cNvSpPr - */ -class SpLocks implements OOXMLElement { - - private static final long serialVersionUID = -3805557220039550941L; - private HashMap attrs = null; - - public SpLocks(HashMap attrs) { - this.attrs = attrs; - } - - public SpLocks(SpLocks sp) { - this.attrs = sp.attrs; - } - - - public static SpLocks parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spLocks")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("spLocks")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("spLocks.parseOOXML: " + e.toString()); - } - SpLocks sp = new SpLocks(attrs); - return sp; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SpLocks(this); - } -} - - diff --git a/src/main/java/io/starter/formats/OOXML/Sp.kt b/src/main/java/io/starter/formats/OOXML/Sp.kt new file mode 100644 index 0000000..11dea94 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Sp.kt @@ -0,0 +1,531 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * sp (Shape) + * + * + * OOXML/DrawingML element representing a single shape + * A shape can either be a preset or a custom geometry, + * defined using the DrawingML framework. In addition to a geometry each shape can have both visual and non + * visual properties attached. Text and corresponding styling information can also be attached to a shape. + * + * + * parent = twoCellAnchor, oneCellAnchor, absoluteAnchor, grpSp (group shape) + * children: nvSpPr, spPr, style, txBody + */ +class Sp : OOXMLElement { + private var nvsp: NvSpPr? = null + private var sppr: SpPr? = null + private var sty: Style? = null + private var txb: TxBody? = null + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append(nvsp!!.ooxml) + ooxml.append(sppr!!.ooxml) + if (sty != null) ooxml.append(sty!!.ooxml) + if (txb != null) ooxml.append(txb!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * get Macro attribute + */ + /** + * set Macro attribute + * + * @param macro + */ + var macro: String? + get() = if (attrs!!["macro"] != null) attrs!!["macro"] else null + set(macro) { + attrs!!["macro"] = macro + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + var embed: String? + get() = if (sppr != null) sppr!!.embed else null + set(embed) { + if (sppr != null) sppr!!.embed = embed + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + var link: String? + get() = if (sppr != null) sppr!!.link else null + set(link) { + if (sppr != null) sppr!!.link = link + } + + /** + * return the name of this shape, if any + * + * @return + */ + /** + * set the name of this shape, if any + */ + var name: String? + get() = if (nvsp != null) nvsp!!.name else null + set(name) { + if (nvsp != null) + nvsp!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr description attribute + * sometimes associated with shape name + * + * @param descr + */ + var descr: String? + get() = if (nvsp != null) nvsp!!.descr else null + set(descr) { + if (nvsp != null) + nvsp!!.descr = descr + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + var id: Int + get() = if (nvsp != null) nvsp!!.id else -1 + set(id) { + if (nvsp != null) + nvsp!!.id = id + } + + constructor(nvsp: NvSpPr, sppr: SpPr, sty: Style, txb: TxBody, attrs: HashMap) { + this.nvsp = nvsp + this.sppr = sppr + this.sty = sty + this.txb = txb + this.attrs = attrs + } + + constructor(shp: Sp) { + this.nvsp = shp.nvsp + this.sppr = shp.sppr + this.sty = shp.sty + this.txb = shp.txb + this.attrs = shp.attrs + } + + override fun cloneElement(): OOXMLElement { + return Sp(this) + } + + companion object { + + private val serialVersionUID = 7454285931503575078L + + + /** + * return the OOXML specific for this object + * + * @param xpp + * @param lastTag + * @return + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var nvsp: NvSpPr? = null + var sppr: SpPr? = null + var sty: Style? = null + var txb: TxBody? = null + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "sp") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "nvSpPr") { // non-visual shape props + lastTag.push(tnm) + nvsp = NvSpPr.parseOOXML(xpp, lastTag) + } else if (tnm == "spPr") { // shape properties + lastTag.push(tnm) + sppr = SpPr.parseOOXML(xpp, lastTag, bk) as SpPr + //sppr.setNS("xdr"); + } else if (tnm == "style") { // shape style + lastTag.push(tnm) + sty = Style.parseOOXML(xpp, lastTag, bk) as Style + } else if (tnm == "txBody") { // shape text body + lastTag.push(tnm) + txb = TxBody.parseOOXML(xpp, lastTag, bk) as TxBody + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "sp") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("sp.parseOOXML: $e") + } + + return Sp(nvsp, sppr, sty, txb, attrs) + } + } + +} + +/** + * This element specifies all non-visual properties for a shape. This element is a container for the non-visual + * identification properties, shape properties and application properties that are to be associated with a shape. + * This allows for additional information that does not affect the appearance of the shape to be stored + * + * + * parent: sp + * children: cNvPr REQ cNvSpPr REQ + */ +internal class NvSpPr : OOXMLElement { + private var cnv: CNvPr? = null + private var cnvsp: CNvSpPr? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append(cnv!!.ooxml) + ooxml.append(cnvsp!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * get name attribute + * + * @return + */ + /** + * set name attribute + * + * @param name + */ + var name: String? + get() = if (cnv != null) cnv!!.name else null + set(name) { + if (cnv != null) + cnv!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr desc attribute + * + * @param name + */ + var descr: String? + get() = if (cnv != null) cnv!!.descr else null + set(descr) { + if (cnv != null) + cnv!!.descr = descr + } + + /** + * return the cNvPr id for this element + * + * @return + */ + /** + * set the cNvPr id for this element + * + * @param id + */ + var id: Int + get() = if (cnv != null) cnv!!.id else -1 + set(id) { + if (cnv != null) + cnv!!.id = id + } + + constructor(cnv: CNvPr, cnvsp: CNvSpPr) { + this.cnv = cnv + this.cnvsp = cnvsp + } + + constructor(nvsp: NvSpPr) { + this.cnv = nvsp.cnv + this.cnvsp = nvsp.cnvsp + } + + override fun cloneElement(): OOXMLElement { + return NvSpPr(this) + } + + companion object { + + private val serialVersionUID = 9121235009516398367L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): NvSpPr { + var cnv: CNvPr? = null + var cnvsp: CNvSpPr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cNvPr") { + lastTag.push(tnm) + cnv = CNvPr.parseOOXML(xpp, lastTag) as CNvPr + } else if (tnm == "cNvSpPr") { + lastTag.push(tnm) + cnvsp = CNvSpPr.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "nvSpPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("txBody.parseOOXML: $e") + } + + return NvSpPr(cnv, cnvsp) + } + } +} + +/** + * cNvSpPr (Non-visual Drawing Properties for a shape + * + * + * This OOXML/DrawingML element specifies the non-visual drawing properties for a shape. + * These properties are to be used by the generating application to determine how the shape should be dealt with + * + * + * attributes: txBox (optional boolean) + * parent: nvSpPr + * children: spLocks (shapeLocks) + */ +internal class CNvSpPr : OOXMLElement { + private var txBox: String? = null + private var sp: SpLocks? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (sp != null) ooxml.append(sp!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(t: String, sp: SpLocks) { + this.txBox = t + this.sp = sp + } + + constructor(c: CNvSpPr) { + this.txBox = c.txBox + this.sp = c.sp + } + + override fun cloneElement(): OOXMLElement { + return CNvSpPr(this) + } + + companion object { + + private val serialVersionUID = 7895953516797436713L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): CNvSpPr { + var txBox: String? = null + var sp: SpLocks? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "cNvSpPr") { // get attributes + for (i in 0 until xpp.attributeCount) { + if (xpp.getAttributeName(i) == "txBox") + txBox = xpp.getAttributeValue(i) + } + } else if (tnm == "spLocks") { + lastTag.push(tnm) + sp = SpLocks.parseOOXML(xpp, lastTag) + + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "cNvSpPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("cNvSpPr.parseOOXML: $e") + } + + return CNvSpPr(txBox, sp) + } + } +} + +/** + * spLocks (Shape Locks) + * + * + * OOXML/DrawingML element specifies all locking properties for a shape. + * These properties inform the generating application + * about specific properties that have been previously locked and thus should not be changed. + * + * + * parents: cNvSpPr + */ +internal class SpLocks : OOXMLElement { + private var attrs: HashMap? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + return ooxml.toString() + } + + constructor(attrs: HashMap) { + this.attrs = attrs + } + + constructor(sp: SpLocks) { + this.attrs = sp.attrs + } + + override fun cloneElement(): OOXMLElement { + return SpLocks(this) + } + + companion object { + + private val serialVersionUID = -3805557220039550941L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): SpLocks { + val attrs = HashMap() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "spLocks") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "spLocks") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("spLocks.parseOOXML: $e") + } + + return SpLocks(attrs) + } + } +} + + diff --git a/src/main/java/io/starter/formats/OOXML/SpPr.java b/src/main/java/io/starter/formats/OOXML/SpPr.java deleted file mode 100644 index a04a3e2..0000000 --- a/src/main/java/io/starter/formats/OOXML/SpPr.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * spPr - * This OOXML/DrawingML element specifies the visual shape properties that can be applied to a shape. These properties include the - * shape fill, outline, geometry, effects, and 3D orientation. - *

                  - * parents: cxnSp, pic, sp - * children: - * blipFill (Picture Fill) §5.1.10.14 - * custGeom (Custom Geometry) §5.1.11.8 - * effectDag (Effect Container) §5.1.10.25 - * effectLst (Effect Container) §5.1.10.26 - * extLst (Extension List) §5.1.2.1.15 - * gradFill (Gradient Fill) §5.1.10.33 - * grpFill (Group Fill) §5.1.10.35 - * ln (Outline) §5.1.2.1.24 - * noFill (No Fill) §5.1.10.44 - * pattFill (Pattern Fill) §5.1.10.47 - * prstGeom (Preset geometry) §5.1.11.18 - * scene3d (3D Scene Properties) §5.1.4.1.26 - * solidFill (Solid Fill) §5.1.10.54 - * sp3d (Apply 3D shape properties) §5.1.7.12 - * xfrm (2D Transform) - */ -public class SpPr implements OOXMLElement { - - private static final long serialVersionUID = 4542844402486023785L; - private Xfrm x = null; - private GeomGroup geom = null; - private FillGroup fill = null; - private Ln l = null; - private EffectPropsGroup effect = null; - // scene3d, sp3d - String bwMode = null; - // namespace - private String ns = null; - - public SpPr(String ns) { // no-param constructor, set up common defaults - x = new Xfrm(); - x.setNS("a"); - this.ns = ns; - geom = new GeomGroup(new PrstGeom(), null); - bwMode = "auto"; - } - - public SpPr(Xfrm x, GeomGroup geom, FillGroup fill, Ln l, EffectPropsGroup effect, String bwMode, String ns) { - this.x = x; - this.geom = geom; - this.fill = fill; - this.l = l; - this.effect = effect; - this.bwMode = bwMode; - this.ns = ns; - } - - public SpPr(SpPr clone) { - this.x = clone.x; - this.geom = clone.geom; - this.fill = clone.fill; - this.l = clone.l; - this.effect = clone.effect; - this.bwMode = clone.bwMode; - this.ns = clone.ns; - } - - /** - * create a default shape property with a solid fill and a line - * - * @param solidfill - * @param w - * @param lnClr - */ - public SpPr(String ns, String solidfill, int w, String lnClr) { - this.ns = ns; - if (solidfill != null) - this.fill = new FillGroup(null, null, null, null, new SolidFill(solidfill)); - this.l = new Ln(w, lnClr); - } - - /** - * set the namespace for spPr element - * - * @param ns - */ - public void setNS(String ns) { - this.ns = ns; - } - - /** - * parse shape OOXML element spPr - * - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spPr object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - Xfrm x = null; - FillGroup fill = null; - Ln l = null; - EffectPropsGroup effect = null; - GeomGroup geom = null; - // scene3d, sp3d - String bwMode = null; - String ns = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spPr")) { - ns = xpp.getPrefix(); - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("bwMode")) - bwMode = xpp.getAttributeValue(i); - } - } else if (tnm.equals("xfrm")) { - lastTag.push(tnm); - x = (Xfrm) Xfrm.parseOOXML(xpp, lastTag); - //x.setNS("a"); - } else if (tnm.equals("ln")) { - lastTag.push(tnm); - l = (Ln) Ln.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("prstGeom") || - tnm.equals("custGeom")) { // GEOMETRY GROUP - lastTag.push(tnm); - geom = (GeomGroup) GeomGroup.parseOOXML(xpp, lastTag); - } else if ( // FILL GROUP - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("extLst")) { - lastTag.push(tnm); - ExtLst.parseOOXML(xpp, lastTag); // ignore for now TODO: FINISH - } else if ( // EFFECT GROUP - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("spPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("spPr.parseOOXML: " + e.toString()); - } - SpPr sp = new SpPr(x, geom, fill, l, effect, bwMode, ns); - return sp; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append("<" + this.ns + ":spPr"); - if (bwMode != null) ooxml.append(" bwMode=\"" + bwMode + "\">"); - else ooxml.append(">"); - if (x != null) ooxml.append(x.getOOXML()); // must pass namespace to xfrm - if (geom != null) ooxml.append(geom.getOOXML()); // geometry choice - if (fill != null) - ooxml.append(fill.getOOXML()); // fill choice - if (l != null) ooxml.append(l.getOOXML()); // ln element - if (effect != null) ooxml.append(effect.getOOXML()); // effect properties choice - // scene3d, sp3d - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new SpPr(this); - } - - /** - * return the id for the embedded picture (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (fill != null) - return fill.getEmbed(); - return null; - } - - /** - * return the id for the linked picture (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (fill != null) - return fill.getLink(); - return null; - } - - /** - * set the embed attribute for this blip (the id for the embedded picture) - * - * @param embed - */ - public void setEmbed(String embed) { - if (fill != null) fill.setEmbed(embed); - } - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (fill != null) fill.setLink(link); - } - - /** - * returns the ln element of this shape property set, if any - * - * @return - */ - public Ln getLn() { - return l; - } - - /** - * add a line for this shape property - * - * @param w line width - * @param clr html color string - */ - public void setLine(int w, String clr) { - l = new Ln(); - l.setWidth(w); - l.setColor(clr); - } - - /** - * returns the width of the line of this shape proeprty, - * or -1 if no line is present - * - * @return - */ - public int getLineWidth() { - if (l != null) return l.getWidth(); - return -1; - } - - /** - * returns the color of the line of this shape property, or -1 if no line is present - * - * @return - */ - public int getLineColor() { - if (l != null) return l.getColor(); - return -1; - } - - /** - * returns the line style of the line of this shape property, or -1 if no line is present - * - * @return - */ - public int getLineStyle() { - if (l != null) - return l.getLineStyle(); - return -1; - } - - /** - * return the fill color - * - * @return - */ - public int getColor() { - if (fill != null) - return fill.getColor(); - return 1; - } - - /** - * returns the fill of this shape property set, if any - * - * @return - */ - public FillGroup getFill() { - return fill; - } - - /** - * remove the line, if any, for this shape property - */ - public void removeLine() { - l = null; - } - - /** - * return true if this shape properties contains a line - */ - public boolean hasLine() { - return (l != null); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/SpPr.kt b/src/main/java/io/starter/formats/OOXML/SpPr.kt new file mode 100644 index 0000000..a3be7f2 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/SpPr.kt @@ -0,0 +1,325 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * spPr + * This OOXML/DrawingML element specifies the visual shape properties that can be applied to a shape. These properties include the + * shape fill, outline, geometry, effects, and 3D orientation. + * + * + * parents: cxnSp, pic, sp + * children: + * blipFill (Picture Fill) §5.1.10.14 + * custGeom (Custom Geometry) §5.1.11.8 + * effectDag (Effect Container) §5.1.10.25 + * effectLst (Effect Container) §5.1.10.26 + * extLst (Extension List) §5.1.2.1.15 + * gradFill (Gradient Fill) §5.1.10.33 + * grpFill (Group Fill) §5.1.10.35 + * ln (Outline) §5.1.2.1.24 + * noFill (No Fill) §5.1.10.44 + * pattFill (Pattern Fill) §5.1.10.47 + * prstGeom (Preset geometry) §5.1.11.18 + * scene3d (3D Scene Properties) §5.1.4.1.26 + * solidFill (Solid Fill) §5.1.10.54 + * sp3d (Apply 3D shape properties) §5.1.7.12 + * xfrm (2D Transform) + */ +class SpPr : OOXMLElement { + private var x: Xfrm? = null + private var geom: GeomGroup? = null + /** + * returns the fill of this shape property set, if any + * + * @return + */ + var fill: FillGroup? = null + private set + /** + * returns the ln element of this shape property set, if any + * + * @return + */ + var ln: Ln? = null + private set + private var effect: EffectPropsGroup? = null + // scene3d, sp3d + internal var bwMode: String? = null + // namespace + private var ns: String? = null + + override// must pass namespace to xfrm + // geometry choice + // fill choice + // ln element + // effect properties choice + // scene3d, sp3d + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("<" + this.ns + ":spPr") + if (bwMode != null) + ooxml.append(" bwMode=\"$bwMode\">") + else + ooxml.append(">") + if (x != null) ooxml.append(x!!.ooxml) + if (geom != null) ooxml.append(geom!!.ooxml) + if (fill != null) + ooxml.append(fill!!.ooxml) + if (ln != null) ooxml.append(ln!!.ooxml) + if (effect != null) ooxml.append(effect!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + /** + * return the id for the embedded picture (i.e. resides within the file) + * + * @return + */ + /** + * set the embed attribute for this blip (the id for the embedded picture) + * + * @param embed + */ + var embed: String? + get() = if (fill != null) fill!!.embed else null + set(embed) { + if (fill != null) fill!!.embed = embed + } + + /** + * return the id for the linked picture (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + var link: String? + get() = if (fill != null) fill!!.link else null + set(link) { + if (fill != null) fill!!.link = link + } + + /** + * returns the width of the line of this shape proeprty, + * or -1 if no line is present + * + * @return + */ + val lineWidth: Int + get() = if (ln != null) ln!!.width else -1 + + /** + * returns the color of the line of this shape property, or -1 if no line is present + * + * @return + */ + val lineColor: Int + get() = if (ln != null) ln!!.color else -1 + + /** + * returns the line style of the line of this shape property, or -1 if no line is present + * + * @return + */ + val lineStyle: Int + get() = if (ln != null) ln!!.lineStyle else -1 + + /** + * return the fill color + * + * @return + */ + val color: Int + get() = if (fill != null) fill!!.color else 1 + + constructor(ns: String) { // no-param constructor, set up common defaults + x = Xfrm() + x!!.setNS("a") + this.ns = ns + geom = GeomGroup(PrstGeom(), null) + bwMode = "auto" + } + + constructor(x: Xfrm, geom: GeomGroup, fill: FillGroup, l: Ln, effect: EffectPropsGroup, bwMode: String, ns: String) { + this.x = x + this.geom = geom + this.fill = fill + this.ln = l + this.effect = effect + this.bwMode = bwMode + this.ns = ns + } + + constructor(clone: SpPr) { + this.x = clone.x + this.geom = clone.geom + this.fill = clone.fill + this.ln = clone.ln + this.effect = clone.effect + this.bwMode = clone.bwMode + this.ns = clone.ns + } + + /** + * create a default shape property with a solid fill and a line + * + * @param solidfill + * @param w + * @param lnClr + */ + constructor(ns: String, solidfill: String?, w: Int, lnClr: String) { + this.ns = ns + if (solidfill != null) + this.fill = FillGroup(null, null, null, null, SolidFill(solidfill)) + this.ln = Ln(w, lnClr) + } + + /** + * set the namespace for spPr element + * + * @param ns + */ + fun setNS(ns: String) { + this.ns = ns + } + + override fun cloneElement(): OOXMLElement { + return SpPr(this) + } + + /** + * add a line for this shape property + * + * @param w line width + * @param clr html color string + */ + fun setLine(w: Int, clr: String) { + ln = Ln() + ln!!.width = w + ln!!.setColor(clr) + } + + /** + * remove the line, if any, for this shape property + */ + fun removeLine() { + ln = null + } + + /** + * return true if this shape properties contains a line + */ + fun hasLine(): Boolean { + return ln != null + } + + companion object { + + private val serialVersionUID = 4542844402486023785L + + /** + * parse shape OOXML element spPr + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var x: Xfrm? = null + var fill: FillGroup? = null + var l: Ln? = null + var effect: EffectPropsGroup? = null + var geom: GeomGroup? = null + // scene3d, sp3d + var bwMode: String? = null + var ns: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "spPr") { + ns = xpp.prefix + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "bwMode") + bwMode = xpp.getAttributeValue(i) + } + } else if (tnm == "xfrm") { + lastTag.push(tnm) + x = Xfrm.parseOOXML(xpp, lastTag) as Xfrm + //x.setNS("a"); + } else if (tnm == "ln") { + lastTag.push(tnm) + l = Ln.parseOOXML(xpp, lastTag, bk) as Ln + } else if (tnm == "prstGeom" || tnm == "custGeom") { // GEOMETRY GROUP + lastTag.push(tnm) + geom = GeomGroup.parseOOXML(xpp, lastTag) as GeomGroup + } else if (// FILL GROUP + tnm == "solidFill" || + tnm == "noFill" || + tnm == "gradFill" || + tnm == "grpFill" || + tnm == "pattFill" || + tnm == "blipFill") { + lastTag.push(tnm) + fill = FillGroup.parseOOXML(xpp, lastTag, bk) as FillGroup + } else if (tnm == "extLst") { + lastTag.push(tnm) + ExtLst.parseOOXML(xpp, lastTag) // ignore for now TODO: FINISH + } else if (// EFFECT GROUP + tnm == "effectLst" || tnm == "effectDag") { + lastTag.push(tnm) + effect = EffectPropsGroup.parseOOXML(xpp, lastTag) as EffectPropsGroup + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "spPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("spPr.parseOOXML: $e") + } + + return SpPr(x, geom, fill, l, effect, bwMode, ns) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Ss_rPr.java b/src/main/java/io/starter/formats/OOXML/Ss_rPr.java deleted file mode 100644 index 1639f6f..0000000 --- a/src/main/java/io/starter/formats/OOXML/Ss_rPr.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.DocumentHandle; -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Font; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; - -/** - * rPr (Run Properties for Shared Strings (see SharedStrings.xml)) - *

                  - * This element represents a set of properties to apply to the contents of this rich text run. - * This element corresponds to Unicode String formatting runs where a specific font is applied to a sub-section of a string - *

                  - * parent: r (rich text run) - * children: many - */ -public class Ss_rPr implements OOXMLElement { - - private static final long serialVersionUID = 8940630588129002652L; - private HashMap attrs = new HashMap(); - private Color color = null; - - public Ss_rPr() { - - } - - public Ss_rPr(HashMap attrs, Color c) { - this.attrs = attrs; - this.color = c; - } - - public Ss_rPr(Ss_rPr r) { - this.attrs = r.attrs; - this.color = r.color; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - Color c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("rFont")) { - attrs.put("rFont", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("charset")) { - attrs.put("charset", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("family")) { - attrs.put("family", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("b")) { - attrs.put("b", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("i")) { - attrs.put("i", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("strike")) { - attrs.put("strike", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("outline")) { - attrs.put("outline", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("shadow")) { - attrs.put("shadow", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("condense")) { - attrs.put("condense", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("extend")) { - attrs.put("extend", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("sz")) { - attrs.put("sz", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("u")) { - attrs.put("u", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("vertAlign")) { - attrs.put("vertAlign", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("scheme")) { - attrs.put("scheme", ((xpp.getAttributeCount() > 0) ? xpp.getAttributeValue(0) : "")); // val - } else if (tnm.equals("color")) { - c = (Color) Color.parseOOXML(xpp, FormatHandle.colorFONT, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("rPr")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("rPr.parseOOXML: " + e.toString()); - } - Ss_rPr r = new Ss_rPr(attrs, c); - return r; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - // attributes - Iterator i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - if (val.equals("")) // same as true for ... - ooxml.append("<" + key + "/>"); - else - ooxml.append("<" + key + " val=\"" + val + "\"/>"); - } - if (color != null) ooxml.append(color.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Ss_rPr(this); - } - - /** - * retrieve one of the following key values, if already set - * b, charset, condense, extend, family, i, outline, rFont, scheme, shadow, strike, sz, u, vertAlign - * - * @param key - * @return - */ - public String getAttr(String key) { - return attrs.get(key); - } - - /** - * set the value for one of the rPr children: - * b, charset, condense, extend, family, i, outline, rFont, scheme, shadow, strike, sz, u, vertAlign - *

                  - * most are boolean string values "0" or "1" - * except for sz, rFont, family, scheme - * - * @param key - * @param val - */ - public void setAttr(String key, String val) { - attrs.put(key, val); - } - - /** family: - 0 Not applicable. - 1 Roman - 2 Swiss - SpreadsheetML Reference Material - Styles - 2113 - Value Font Family - 3 Modern - 4 Script - 5 Decorative - */ - /** - * charset (Character Set) - * This element defines the font character set of this font. - * This field is used in font creation and selection if a font of the given facename is not available on the system. - * Although it is not required to have around when resolving font facename, the information can be stored for - * when needed to help resolve which font face to use of all available fonts on a system. - * - * int 0-255. - * - * The following are some of the possible the character sets: - INT - Value - Character Set - 0 ANSI_CHARSET - 1 DEFAULT_CHARSET - 2 SYMBOL_CHARSET - 77 MAC_CHARSET - 128 SHIFTJIS_CHARSET - 129 HANGEUL_CHARSET - 129 HANGUL_CHARSET - 130 JOHAB_CHARSET - 134 GB2312_CHARSET - 136 CHINESEBIG5_CHARSET - 161 GREEK_CHARSET - 162 TURKISH_CHARSET - 163 VIETNAMESE_CHARSET - 177 HEBREW_CHARSET - 178 ARABIC_CHARSET - 186 BALTIC_CHARSET - 204 RUSSIAN_CHARSET - 222 THAI_CHARSET - 238 EASTEUROPE_CHARSET - 255 OEM_CHARSET - */ - /** - * condense (Condense) - Macintosh compatibility setting. Represents special word/character rendering on Macintosh, when this flag is - set. The effect is to condense the text (squeeze it together). SpreadsheetML applications are not required to - render according to this flag. - */ - /** - * extend (Extend) - * This element specifies a compatibility setting used for previous spreadsheet applications, resulting in special - * word/character rendering on those legacy applications, when this flag is set. The effect extends or stretches out - * the text. SpreadsheetML applications are not required to render according to this flag. - */ - /** - * shadow (Shadow) - * Macintosh compatibility setting. Represents special word/character rendering on Macintosh, when this flag is - * set. The effect is to render a shadow behind, beneath and to the right of the text. SpreadsheetML applications - * are not required to render according to this flag. - */ - /** - * outline (Outline) - * This element displays only the inner and outer borders of each character. This is very similar to Bold in behavior - */ - /** - * vertAlign (Vertical Alignment) - * This element adjusts the vertical position of the text relative to the text's default appearance for this run. It is - * used to get 'superscript' or 'subscript' texts, and shall reduce the font size (if a smaller size is available) - * accordingly. - * - * val= An enumeration representing the vertical-alignment setting. - * baseline, subscript, superscript - Setting this to either subscript or superscript shall make the font size smaller if a - smaller font size is available. - */ - - /** - * given an rPr OOXML text run properties, create an OpenXLS font - * - * @param bk - * @return - */ - public Font generateFont(DocumentHandle bk) { - // not using attributes: charset, family, condense, extend, shadow, scheme, outline==bold - Font f = new Font("Arial", 400, 200); - Object o; - - o = this.getAttr("rFont"); - f.setFontName((String) o); - o = this.getAttr("sz"); - if (o != null) f.setFontHeight(Font.PointsToFontHeight(Double.parseDouble((String) o))); - - // boolean attributes - o = this.getAttr("b"); - if (o != null) f.setBold(true); - o = this.getAttr("i"); - if (o != null) f.setItalic(true); - o = this.getAttr("u"); - if (o != null) f.setUnderlined(true); - o = this.getAttr("strike"); - if (o != null) f.setStricken(true); - o = this.getAttr("outline"); - if (o != null) f.setBold(true); - o = this.getAttr("vertAlign"); - if (o != null) { - String s = (String) o; - if (s.equals("baseline")) - f.setScript(0); - else if (s.equals("superscript")) - f.setScript(1); - else if (s.equals("subscript")) - f.setScript(2); - } - f.setOOXMLColor(color); - return f; - } - - /** - * create a new OOXML ss_rPr shared string table text run properties object using attributes from Font f - * - * @param f - * @return - */ - public static Ss_rPr createFromFont(Font f) { - // not using attributes: charset, family, condense, extend, shadow, scheme, outline==bold - Ss_rPr rp = new Ss_rPr(); - rp.setAttr("rFont", f.getFontName()); - rp.setAttr("sz", new Double(f.getFontHeightInPoints()).toString()); - - // boolean attributes - if (f.getBold()) - rp.setAttr("b", ""); - if (f.getItalic()) - rp.setAttr("i", ""); - if (f.getUnderlined()) - rp.setAttr("u", ""); - if (f.getStricken()) - rp.setAttr("strike", ""); - int s = f.getScript(); - if (s == 1) - rp.setAttr("vertAlign", "superscript"); - else if (s == 2) - rp.setAttr("vertAlign", "subscript"); - rp.color = f.getOOXMLColor(); - return rp; - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Ss_rPr.kt b/src/main/java/io/starter/formats/OOXML/Ss_rPr.kt new file mode 100644 index 0000000..e5354d6 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Ss_rPr.kt @@ -0,0 +1,322 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.DocumentHandle +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Font +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap + +/** + * rPr (Run Properties for Shared Strings (see SharedStrings.xml)) + * + * + * This element represents a set of properties to apply to the contents of this rich text run. + * This element corresponds to Unicode String formatting runs where a specific font is applied to a sub-section of a string + * + * + * parent: r (rich text run) + * children: many + */ +class Ss_rPr : OOXMLElement { + private var attrs = HashMap() + private var color: Color? = null + + override// attributes + // same as true for ... + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + val i = attrs.keys.iterator() + while (i.hasNext()) { + val key = i.next() + val `val` = attrs[key] + if (`val` == "") + ooxml.append("<$key/>") + else + ooxml.append("<$key val=\"$`val`\"/>") + } + if (color != null) ooxml.append(color!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor() { + + } + + constructor(attrs: HashMap, c: Color) { + this.attrs = attrs + this.color = c + } + + constructor(r: Ss_rPr) { + this.attrs = r.attrs + this.color = r.color + } + + override fun cloneElement(): OOXMLElement { + return Ss_rPr(this) + } + + /** + * retrieve one of the following key values, if already set + * b, charset, condense, extend, family, i, outline, rFont, scheme, shadow, strike, sz, u, vertAlign + * + * @param key + * @return + */ + fun getAttr(key: String): String { + return attrs[key] + } + + /** + * set the value for one of the rPr children: + * b, charset, condense, extend, family, i, outline, rFont, scheme, shadow, strike, sz, u, vertAlign + * + * + * most are boolean string values "0" or "1" + * except for sz, rFont, family, scheme + * + * @param key + * @param val + */ + fun setAttr(key: String, `val`: String?) { + attrs[key] = `val` + } + + /** family: + * 0 Not applicable. + * 1 Roman + * 2 Swiss + * SpreadsheetML Reference Material - Styles + * 2113 + * Value Font Family + * 3 Modern + * 4 Script + * 5 Decorative + */ + /** + * charset (Character Set) + * This element defines the font character set of this font. + * This field is used in font creation and selection if a font of the given facename is not available on the system. + * Although it is not required to have around when resolving font facename, the information can be stored for + * when needed to help resolve which font face to use of all available fonts on a system. + * + * int 0-255. + * + * The following are some of the possible the character sets: + * INT + * Value + * Character Set + * 0 ANSI_CHARSET + * 1 DEFAULT_CHARSET + * 2 SYMBOL_CHARSET + * 77 MAC_CHARSET + * 128 SHIFTJIS_CHARSET + * 129 HANGEUL_CHARSET + * 129 HANGUL_CHARSET + * 130 JOHAB_CHARSET + * 134 GB2312_CHARSET + * 136 CHINESEBIG5_CHARSET + * 161 GREEK_CHARSET + * 162 TURKISH_CHARSET + * 163 VIETNAMESE_CHARSET + * 177 HEBREW_CHARSET + * 178 ARABIC_CHARSET + * 186 BALTIC_CHARSET + * 204 RUSSIAN_CHARSET + * 222 THAI_CHARSET + * 238 EASTEUROPE_CHARSET + * 255 OEM_CHARSET + */ + /** + * condense (Condense) + * Macintosh compatibility setting. Represents special word/character rendering on Macintosh, when this flag is + * set. The effect is to condense the text (squeeze it together). SpreadsheetML applications are not required to + * render according to this flag. + */ + /** + * extend (Extend) + * This element specifies a compatibility setting used for previous spreadsheet applications, resulting in special + * word/character rendering on those legacy applications, when this flag is set. The effect extends or stretches out + * the text. SpreadsheetML applications are not required to render according to this flag. + */ + /** + * shadow (Shadow) + * Macintosh compatibility setting. Represents special word/character rendering on Macintosh, when this flag is + * set. The effect is to render a shadow behind, beneath and to the right of the text. SpreadsheetML applications + * are not required to render according to this flag. + */ + /** + * outline (Outline) + * This element displays only the inner and outer borders of each character. This is very similar to Bold in behavior + */ + /** + * vertAlign (Vertical Alignment) + * This element adjusts the vertical position of the text relative to the text's default appearance for this run. It is + * used to get 'superscript' or 'subscript' texts, and shall reduce the font size (if a smaller size is available) + * accordingly. + * + * val= An enumeration representing the vertical-alignment setting. + * baseline, subscript, superscript + * Setting this to either subscript or superscript shall make the font size smaller if a + * smaller font size is available. + */ + + /** + * given an rPr OOXML text run properties, create an OpenXLS font + * + * @param bk + * @return + */ + fun generateFont(bk: DocumentHandle): Font { + // not using attributes: charset, family, condense, extend, shadow, scheme, outline==bold + val f = Font("Arial", 400, 200) + var o: Any? + + o = this.getAttr("rFont") + f.fontName = (o as String?)!! + o = this.getAttr("sz") + if (o != null) f.fontHeight = Font.PointsToFontHeight(java.lang.Double.parseDouble((o as String?)!!)) + + // boolean attributes + o = this.getAttr("b") + if (o != null) f.bold = true + o = this.getAttr("i") + if (o != null) f.italic = true + o = this.getAttr("u") + if (o != null) f.underlined = true + o = this.getAttr("strike") + if (o != null) f.stricken = true + o = this.getAttr("outline") + if (o != null) f.bold = true + o = this.getAttr("vertAlign") + if (o != null) { + val s = o as String? + if (s == "baseline") + f.script = 0 + else if (s == "superscript") + f.script = 1 + else if (s == "subscript") + f.script = 2 + } + f.ooxmlColor = color + return f + } + + companion object { + + private val serialVersionUID = 8940630588129002652L + + fun parseOOXML(xpp: XmlPullParser, bk: WorkBookHandle): OOXMLElement { + val attrs = HashMap() + var c: Color? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "rFont") { + attrs["rFont"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "charset") { + attrs["charset"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "family") { + attrs["family"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "b") { + attrs["b"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "i") { + attrs["i"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "strike") { + attrs["strike"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "outline") { + attrs["outline"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "shadow") { + attrs["shadow"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "condense") { + attrs["condense"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "extend") { + attrs["extend"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "sz") { + attrs["sz"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "u") { + attrs["u"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "vertAlign") { + attrs["vertAlign"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "scheme") { + attrs["scheme"] = if (xpp.attributeCount > 0) xpp.getAttributeValue(0) else "" // val + } else if (tnm == "color") { + c = Color.parseOOXML(xpp, FormatHandle.colorFONT, bk) as Color + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "rPr") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("rPr.parseOOXML: $e") + } + + return Ss_rPr(attrs, c) + } + + /** + * create a new OOXML ss_rPr shared string table text run properties object using attributes from Font f + * + * @param f + * @return + */ + fun createFromFont(f: Font): Ss_rPr { + // not using attributes: charset, family, condense, extend, shadow, scheme, outline==bold + val rp = Ss_rPr() + rp.setAttr("rFont", f.fontName) + rp.setAttr("sz", f.fontHeightInPoints.toString()) + + // boolean attributes + if (f.bold) + rp.setAttr("b", "") + if (f.italic) + rp.setAttr("i", "") + if (f.underlined) + rp.setAttr("u", "") + if (f.stricken) + rp.setAttr("strike", "") + val s = f.script + if (s == 1) + rp.setAttr("vertAlign", "superscript") + else if (s == 2) + rp.setAttr("vertAlign", "subscript") + rp.color = f.ooxmlColor + return rp + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/StrRef.java b/src/main/java/io/starter/formats/OOXML/StrRef.java deleted file mode 100644 index c65a0ee..0000000 --- a/src/main/java/io/starter/formats/OOXML/StrRef.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * OOXML element strRef, string reference, child of tx (chart text) element or cat (category) element - */ -public class StrRef implements OOXMLElement { - - private static final long serialVersionUID = -5992001371281543027L; - private String stringRef = null; - private StrCache strCache = null; - - public StrRef(String f, StrCache s) { - this.stringRef = f; - this.strCache = s; - } - - /** - * parse strRef OOXML element - * - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spRef object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - String f = null; - StrCache s = null; - - /** - * contains (in Sequence) - * f - * strRef - */ - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("f")) { - f = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); - } else if (tnm.equals("strCache")) { - lastTag.push(tnm); - s = StrCache.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("strRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("title.parseOOXML: " + e.toString()); - } - StrRef sr = new StrRef(f, s); - return sr; - } - - /** - * generate ooxml to define a strRef, part of tx element or cat element - * - * @return - */ - /** - * strRef contains f + strRef elements - */ - public String getOOXML() { - StringBuffer tooxml = new StringBuffer(); - tooxml.append(""); - if (this.stringRef != null) tooxml.append("" + this.stringRef + ""); - if (this.strCache != null) tooxml.append(strCache.getOOXML()); - tooxml.append(""); - return tooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new StrRef(this.stringRef, this.strCache); - } - -} - - -/** - * define OOXML strCache element - */ -class StrCache implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4914374179641060956L; - private int ptCount = -1, idx = -1; - private String pt = null; - - public StrCache(int ptCount, int idx, String pt) { - this.ptCount = ptCount; - this.idx = idx; - this.pt = pt; - } - - /** - * parse title OOXML element title - * - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spCache object - */ - public static StrCache parseOOXML(XmlPullParser xpp, Stack lastTag) { - int ptCount = -1, idx = -1; - String pt = null; - - /** - * contains (in Sequence) - * ptCount - * pt - */ - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ptCount")) { - ptCount = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - } else if (tnm.equals("pt")) { - idx = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - pt = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("strCache")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("strCache.parseOOXML: " + e.toString()); - } - StrCache sc = new StrCache(ptCount, idx, pt); - return sc; - } - - /** - * generate ooxml to define a strCache element, part of strRef element - * - * @return - */ - public String getOOXML() { - StringBuffer tooxml = new StringBuffer(); - tooxml.append(""); - tooxml.append(""); - tooxml.append(""); - tooxml.append("" + this.pt + ""); - tooxml.append(""); - tooxml.append(""); - return tooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new StrCache(this.ptCount, this.idx, this.pt); - } - -} diff --git a/src/main/java/io/starter/formats/OOXML/StrRef.kt b/src/main/java/io/starter/formats/OOXML/StrRef.kt new file mode 100644 index 0000000..e00f838 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/StrRef.kt @@ -0,0 +1,201 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * OOXML element strRef, string reference, child of tx (chart text) element or cat (category) element + */ +class StrRef(f: String, s: StrCache) : OOXMLElement { + private val stringRef: String? = null + private val strCache: StrCache? = null + + /** + * generate ooxml to define a strRef, part of tx element or cat element + * + * @return + */ + /** + * strRef contains f + strRef elements + */ + override val ooxml: String + get() { + val tooxml = StringBuffer() + tooxml.append("") + if (this.stringRef != null) tooxml.append("" + this.stringRef + "") + if (this.strCache != null) tooxml.append(strCache.ooxml) + tooxml.append("") + return tooxml.toString() + } + + init { + this.stringRef = f + this.strCache = s + } + + override fun cloneElement(): OOXMLElement { + return StrRef(this.stringRef, this.strCache) + } + + companion object { + + private val serialVersionUID = -5992001371281543027L + + /** + * parse strRef OOXML element + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spRef object + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + var f: String? = null + var s: StrCache? = null + + /** + * contains (in Sequence) + * f + * strRef + */ + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "f") { + f = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp) + } else if (tnm == "strCache") { + lastTag.push(tnm) + s = StrCache.parseOOXML(xpp, lastTag) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "strRef") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("title.parseOOXML: $e") + } + + return StrRef(f, s) + } + } + +} + + +/** + * define OOXML strCache element + */ +internal class StrCache(ptCount: Int, idx: Int, pt: String) : OOXMLElement { + private val ptCount = -1 + private val idx = -1 + private val pt: String? = null + + /** + * generate ooxml to define a strCache element, part of strRef element + * + * @return + */ + override val ooxml: String + get() { + val tooxml = StringBuffer() + tooxml.append("") + tooxml.append("") + tooxml.append("") + tooxml.append("" + this.pt + "") + tooxml.append("") + tooxml.append("") + return tooxml.toString() + } + + init { + this.ptCount = ptCount + this.idx = idx + this.pt = pt + } + + override fun cloneElement(): OOXMLElement { + return StrCache(this.ptCount, this.idx, this.pt) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -4914374179641060956L + + /** + * parse title OOXML element title + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spCache object + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack<*>): StrCache { + var ptCount = -1 + var idx = -1 + var pt: String? = null + + /** + * contains (in Sequence) + * ptCount + * pt + */ + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "ptCount") { + ptCount = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + } else if (tnm == "pt") { + idx = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + pt = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "strCache") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("strCache.parseOOXML: $e") + } + + return StrCache(ptCount, idx, pt) + } + } + +} diff --git a/src/main/java/io/starter/formats/OOXML/Style.java b/src/main/java/io/starter/formats/OOXML/Style.java deleted file mode 100644 index 1333bba..0000000 --- a/src/main/java/io/starter/formats/OOXML/Style.java +++ /dev/null @@ -1,433 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * style - *

                  - * This element specifies the style information for a shape. This is used to define a shape's appearance in terms of - * the preset styles defined by the style matrix for the theme. - *

                  - * parent: sp, pic, cnxSp - * children (required and in sequence): lnRef, fillRef, effectRef, fontRef - */ -public class Style implements OOXMLElement { - - private static final long serialVersionUID = -583023685473342509L; - private EffectRef effectRef; - private FontRef fontRef; - private FillRef fillRef; - private lnRef lRef; - - public Style(lnRef lr, FillRef flr, EffectRef er, FontRef fr) { - this.lRef = lr; - this.fillRef = flr; - this.effectRef = er; - this.fontRef = fr; - } - - public Style(Style s) { - this.lRef = s.lRef; - this.fillRef = s.fillRef; - this.effectRef = s.effectRef; - this.fontRef = s.fontRef; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - EffectRef er = null; - FontRef fr = null; - FillRef flr = null; - lnRef lr = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("effectRef")) { - lastTag.push(tnm); - er = EffectRef.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("fontRef")) { - lastTag.push(tnm); - fr = FontRef.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("fillRef")) { - lastTag.push(tnm); - flr = FillRef.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("lnRef")) { - lastTag.push(tnm); - lr = lnRef.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("style")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("style.parseOOXML: " + e.toString()); - } - Style s = new Style(lr, flr, er, fr); - return s; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append(lRef.getOOXML()); - ooxml.append(fillRef.getOOXML()); - ooxml.append(effectRef.getOOXML()); - ooxml.append(fontRef.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public String toString() { - return getOOXML(); - } - - public OOXMLElement cloneElement() { - return new Style(this); - } - -} - -/** - * effectRef (Effect Reference) - * This element defines a reference to an effect style within the style matrix. The idx attribute refers the index of - * an effect style within the effectStyleLst element. - *

                  - * parent: many - * children: COLORCHOICE - */ -class EffectRef implements OOXMLElement { - private static final long serialVersionUID = -7572271663955122478L; - private int idx; - private ColorChoice colorChoice = null; - - protected EffectRef(int idx, ColorChoice c) { - this.idx = idx; - this.colorChoice = c; - } - - protected EffectRef(EffectRef er) { - this.colorChoice = er.colorChoice; - this.idx = er.idx; - } - - protected static EffectRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - int idx = -1; - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("effectRef")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("idx")) { - idx = Integer.valueOf(xpp.getAttributeValue(i)).intValue(); - } - } - } else if (tnm.equals("schemeClr") || - tnm.equals("hslClr") || - tnm.equals("prstClr") || - tnm.equals("scrgbClr") || - tnm.equals("srgbClr") || - tnm.equals("sysClr")) { - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("effectRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("effectRef.parseOOXML: " + e.toString()); - } - EffectRef er = new EffectRef(idx, c); - return er; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (colorChoice != null) ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new EffectRef(this); - } -} - -/** - * fillRef (Fill Reference) - * This element defines a reference to a fill style within the style matrix. The idx attribute refers to the index of a - * fill style or background fill style within the presentation's style matrix, defined by the fmtScheme element. A - * value of 0 or 1000 indicates no background, values 1-999 refer to the index of a fill style within the fillStyleLst - * element, and values 1001 and above refer to the index of a background fill style within the bgFillStyleLst - * element. The value 1001 corresponds to the first background fill style, 1002 to the second background fill style, - * and so on. - *

                  - * parent: many - * children: COLORCHOICE - */ -class FillRef implements OOXMLElement { - private static final long serialVersionUID = 7691131082710785068L; - private int idx; - private ColorChoice colorChoice = null; - - protected FillRef(int idx, ColorChoice c) { - this.idx = idx; - this.colorChoice = c; - } - - protected FillRef(FillRef fr) { - this.colorChoice = fr.colorChoice; - this.idx = fr.idx; - } - - protected static FillRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - int idx = -1; - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fillRef")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("idx")) { - idx = Integer.valueOf(xpp.getAttributeValue(i)).intValue(); - } - } - } else if (tnm.equals("schemeClr") || - tnm.equals("hslClr") || - tnm.equals("prstClr") || - tnm.equals("scrgbClr") || - tnm.equals("srgbClr") || - tnm.equals("sysClr")) { - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk).cloneElement(); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fillRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fillRef.parseOOXML: " + e.toString()); - } - FillRef fr = new FillRef(idx, c); - return fr; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (colorChoice != null) ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FillRef(this); - } -} - -/** - * fontRef (Font Reference) - * This element represents a reference to a themed font. When used it specifies which themed font to use along - * with a choice of color. - *

                  - * parent: many - * children: COLORCHOICE - */ -class FontRef implements OOXMLElement { - - private static final long serialVersionUID = 2907761758443581273L; - private String idx = null; - private ColorChoice colorChoice = null; - - protected FontRef(String idx, ColorChoice c) { - this.idx = idx; - this.colorChoice = c; - } - - protected FontRef(FontRef fr) { - this.colorChoice = fr.colorChoice; - this.idx = fr.idx; - } - - protected static FontRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String idx = null; - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fontRef")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("idx")) { - idx = xpp.getAttributeValue(i); - } - } - } else if (tnm.equals("schemeClr") || - tnm.equals("hslClr") || - tnm.equals("prstClr") || - tnm.equals("scrgbClr") || - tnm.equals("srgbClr") || - tnm.equals("sysClr")) { - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fontRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("fontRef.parseOOXML: " + e.toString()); - } - FontRef fr = new FontRef(idx, c); - return fr; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (colorChoice != null) ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new FontRef(this); - } -} - -/** - * lnRef (Line Reference) - * This element defines a reference to a line style within the style matrix. The idx attribute refers the index of a - * line style within the fillStyleLst element - *

                  - * parent: many - * children: COLORCHOICE - */ -class lnRef implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4349076266006929729L; - private int idx; - private ColorChoice colorChoice = null; - - protected lnRef(int idx, ColorChoice c) { - this.idx = idx; - this.colorChoice = c; - } - - protected lnRef(lnRef lr) { - this.colorChoice = lr.colorChoice; - this.idx = lr.idx; - } - - protected static lnRef parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - int idx = -1; - ColorChoice c = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("lnRef")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - if (nm.equals("idx")) { - idx = Integer.valueOf(xpp.getAttributeValue(i)).intValue(); - } - } - } else if (tnm.equals("schemeClr") || - tnm.equals("hslClr") || - tnm.equals("prstClr") || - tnm.equals("scrgbClr") || - tnm.equals("srgbClr") || - tnm.equals("sysClr")) { - lastTag.push(tnm); - c = (ColorChoice) ColorChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("lnRef")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("lnRef.parseOOXML: " + e.toString()); - } - lnRef lr = new lnRef(idx, c); - return lr; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (colorChoice != null) ooxml.append(colorChoice.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new lnRef(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Style.kt b/src/main/java/io/starter/formats/OOXML/Style.kt new file mode 100644 index 0000000..eb2a700 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Style.kt @@ -0,0 +1,459 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * style + * + * + * This element specifies the style information for a shape. This is used to define a shape's appearance in terms of + * the preset styles defined by the style matrix for the theme. + * + * + * parent: sp, pic, cnxSp + * children (required and in sequence): lnRef, fillRef, effectRef, fontRef + */ +class Style : OOXMLElement { + private var effectRef: EffectRef? = null + private var fontRef: FontRef? = null + private var fillRef: FillRef? = null + private var lRef: lnRef? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append(lRef!!.ooxml) + ooxml.append(fillRef!!.ooxml) + ooxml.append(effectRef!!.ooxml) + ooxml.append(fontRef!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(lr: lnRef, flr: FillRef, er: EffectRef, fr: FontRef) { + this.lRef = lr + this.fillRef = flr + this.effectRef = er + this.fontRef = fr + } + + constructor(s: Style) { + this.lRef = s.lRef + this.fillRef = s.fillRef + this.effectRef = s.effectRef + this.fontRef = s.fontRef + } + + override fun toString(): String { + return ooxml + } + + override fun cloneElement(): OOXMLElement { + return Style(this) + } + + companion object { + + private val serialVersionUID = -583023685473342509L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var er: EffectRef? = null + var fr: FontRef? = null + var flr: FillRef? = null + var lr: lnRef? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "effectRef") { + lastTag.push(tnm) + er = EffectRef.parseOOXML(xpp, lastTag, bk) + } else if (tnm == "fontRef") { + lastTag.push(tnm) + fr = FontRef.parseOOXML(xpp, lastTag, bk) + } else if (tnm == "fillRef") { + lastTag.push(tnm) + flr = FillRef.parseOOXML(xpp, lastTag, bk) + } else if (tnm == "lnRef") { + lastTag.push(tnm) + lr = lnRef.parseOOXML(xpp, lastTag, bk) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "style") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("style.parseOOXML: $e") + } + + return Style(lr, flr, er, fr) + } + } + +} + +/** + * effectRef (Effect Reference) + * This element defines a reference to an effect style within the style matrix. The idx attribute refers the index of + * an effect style within the effectStyleLst element. + * + * + * parent: many + * children: COLORCHOICE + */ +internal class EffectRef : OOXMLElement { + private var idx: Int = 0 + private var colorChoice: ColorChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (colorChoice != null) ooxml.append(colorChoice!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + protected constructor(idx: Int, c: ColorChoice) { + this.idx = idx + this.colorChoice = c + } + + protected constructor(er: EffectRef) { + this.colorChoice = er.colorChoice + this.idx = er.idx + } + + override fun cloneElement(): OOXMLElement { + return EffectRef(this) + } + + companion object { + private val serialVersionUID = -7572271663955122478L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): EffectRef { + var idx = -1 + var c: ColorChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "effectRef") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "idx") { + idx = Integer.valueOf(xpp.getAttributeValue(i)).toInt() + } + } + } else if (tnm == "schemeClr" || + tnm == "hslClr" || + tnm == "prstClr" || + tnm == "scrgbClr" || + tnm == "srgbClr" || + tnm == "sysClr") { + lastTag.push(tnm) + c = ColorChoice.parseOOXML(xpp, lastTag, bk) as ColorChoice + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "effectRef") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("effectRef.parseOOXML: $e") + } + + return EffectRef(idx, c) + } + } +} + +/** + * fillRef (Fill Reference) + * This element defines a reference to a fill style within the style matrix. The idx attribute refers to the index of a + * fill style or background fill style within the presentation's style matrix, defined by the fmtScheme element. A + * value of 0 or 1000 indicates no background, values 1-999 refer to the index of a fill style within the fillStyleLst + * element, and values 1001 and above refer to the index of a background fill style within the bgFillStyleLst + * element. The value 1001 corresponds to the first background fill style, 1002 to the second background fill style, + * and so on. + * + * + * parent: many + * children: COLORCHOICE + */ +internal class FillRef : OOXMLElement { + private var idx: Int = 0 + private var colorChoice: ColorChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (colorChoice != null) ooxml.append(colorChoice!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + protected constructor(idx: Int, c: ColorChoice) { + this.idx = idx + this.colorChoice = c + } + + protected constructor(fr: FillRef) { + this.colorChoice = fr.colorChoice + this.idx = fr.idx + } + + override fun cloneElement(): OOXMLElement { + return FillRef(this) + } + + companion object { + private val serialVersionUID = 7691131082710785068L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): FillRef { + var idx = -1 + var c: ColorChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "fillRef") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "idx") { + idx = Integer.valueOf(xpp.getAttributeValue(i)).toInt() + } + } + } else if (tnm == "schemeClr" || + tnm == "hslClr" || + tnm == "prstClr" || + tnm == "scrgbClr" || + tnm == "srgbClr" || + tnm == "sysClr") { + lastTag.push(tnm) + c = ColorChoice.parseOOXML(xpp, lastTag, bk).cloneElement() as ColorChoice + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "fillRef") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("fillRef.parseOOXML: $e") + } + + return FillRef(idx, c) + } + } +} + +/** + * fontRef (Font Reference) + * This element represents a reference to a themed font. When used it specifies which themed font to use along + * with a choice of color. + * + * + * parent: many + * children: COLORCHOICE + */ +internal class FontRef : OOXMLElement { + private var idx: String? = null + private var colorChoice: ColorChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (colorChoice != null) ooxml.append(colorChoice!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + protected constructor(idx: String, c: ColorChoice) { + this.idx = idx + this.colorChoice = c + } + + protected constructor(fr: FontRef) { + this.colorChoice = fr.colorChoice + this.idx = fr.idx + } + + override fun cloneElement(): OOXMLElement { + return FontRef(this) + } + + companion object { + + private val serialVersionUID = 2907761758443581273L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): FontRef { + var idx: String? = null + var c: ColorChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "fontRef") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "idx") { + idx = xpp.getAttributeValue(i) + } + } + } else if (tnm == "schemeClr" || + tnm == "hslClr" || + tnm == "prstClr" || + tnm == "scrgbClr" || + tnm == "srgbClr" || + tnm == "sysClr") { + lastTag.push(tnm) + c = ColorChoice.parseOOXML(xpp, lastTag, bk) as ColorChoice + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "fontRef") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("fontRef.parseOOXML: $e") + } + + return FontRef(idx, c) + } + } +} + +/** + * lnRef (Line Reference) + * This element defines a reference to a line style within the style matrix. The idx attribute refers the index of a + * line style within the fillStyleLst element + * + * + * parent: many + * children: COLORCHOICE + */ +internal class lnRef : OOXMLElement { + private var idx: Int = 0 + private var colorChoice: ColorChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (colorChoice != null) ooxml.append(colorChoice!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + protected constructor(idx: Int, c: ColorChoice) { + this.idx = idx + this.colorChoice = c + } + + protected constructor(lr: lnRef) { + this.colorChoice = lr.colorChoice + this.idx = lr.idx + } + + override fun cloneElement(): OOXMLElement { + return lnRef(this) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -4349076266006929729L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): lnRef { + var idx = -1 + var c: ColorChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "lnRef") { + for (i in 0 until xpp.attributeCount) { + val nm = xpp.getAttributeName(i) + if (nm == "idx") { + idx = Integer.valueOf(xpp.getAttributeValue(i)).toInt() + } + } + } else if (tnm == "schemeClr" || + tnm == "hslClr" || + tnm == "prstClr" || + tnm == "scrgbClr" || + tnm == "srgbClr" || + tnm == "sysClr") { + lastTag.push(tnm) + c = ColorChoice.parseOOXML(xpp, lastTag, bk) as ColorChoice + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "lnRef") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("lnRef.parseOOXML: $e") + } + + return lnRef(idx, c) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/Text.java b/src/main/java/io/starter/formats/OOXML/Text.java deleted file mode 100644 index b53191b..0000000 --- a/src/main/java/io/starter/formats/OOXML/Text.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.OOXMLAdapter; -import io.starter.formats.XLS.Sst; -import io.starter.formats.XLS.Unicodestring; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.Stack; - -/** - * text (Comment Text) - * This element contains rich text which represents the text of a comment. The maximum length for this text is a - * spreadsheet application implementation detail. A recommended guideline is 32767 chars - * parent: comment - * children: t (text), r (Rich Text Run), rPh (phonetic run), phoneticPr (phonetic properties) - */ -// TODO: finish elements rPh and phoneticPr -// TODO: preserve -public class Text implements OOXMLElement { - - private static final long serialVersionUID = 5886384020139606328L; - private Unicodestring str = null; - - /** - * create a new comment WITH formatting - * - * @param strref - */ - public Text(Unicodestring str) { - this.str = str; - } - - public Text(Text t) { - this.str = t.str; - } - - /** - * create a new comment with NO formatting - * - * @param s - */ - public Text(String s) { - this.str = Sst.createUnicodeString(s, null, Sst.STRING_ENCODING_AUTO); - } - - /** - * parse this Text element into a unicode string with formatting runs - */ - public static Text parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - Unicodestring str = null; - String s = ""; - ArrayList formattingRuns = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("rPr")) { // intra-string formatting properties - int idx = s.length(); // index into character string to apply formatting to - Ss_rPr rp = (Ss_rPr) Ss_rPr.parseOOXML(xpp, bk); //.cloneElement(); - Font f = rp.generateFont(bk); // NOW CONVERT ss_rPr to a font!! - int fIndex = bk.getWorkBook().getFontIdx(f); // index for specific font formatting - if (fIndex == -1) // must insert new font - fIndex = bk.getWorkBook().insertFont(f) + 1; - if (formattingRuns == null) formattingRuns = new ArrayList(); - formattingRuns.add(new short[]{Integer.valueOf(idx).shortValue(), Integer.valueOf(fIndex).shortValue()}); - } else if (tnm.equals("t")) { - /*boolean bPreserve= false; - if (xpp.getAttributeCount()>0) { - if (xpp.getAttributeName(0).equals("space") && xpp.getAttributeValue(0).equals("preserve")) - bPreserve= true; - } - */ - eventType = xpp.next(); - while (eventType != XmlPullParser.END_DOCUMENT && - eventType != XmlPullParser.END_TAG && - eventType != XmlPullParser.TEXT) { - eventType = xpp.next(); - } - if (eventType == XmlPullParser.TEXT) { - s = s + xpp.getText(); - } - } - } else if (eventType == XmlPullParser.END_TAG && xpp.getName().equals("text")) { - str = Sst.createUnicodeString(s, formattingRuns, Sst.STRING_ENCODING_UNICODE); // create a new unicode string with formatting runs - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("r.parseOOXML: " + e.toString()); - } - Text oe = new Text(str); - return oe; - } - - public String getOOXML() { - return null; - } - - /** - * return the OOXML representation of this Text (Comment) element - * - * @param bk - * @return - */ - public String getOOXML(io.starter.formats.XLS.WorkBook bk) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (str != null) { - String s = OOXMLAdapter.stripNonAsciiRetainQuote(str.getStringVal()).toString(); - ArrayList runs = str.getFormattingRuns(); - if (runs == null) { // no intra-string formatting - if (s.indexOf(" ") == 0 || s.lastIndexOf(" ") == s.length() - 1) { - ooxml.append("" + s + ""); - } else { - ooxml.append("" + s + ""); - } - ooxml.append("\r\n"); - } else { // have formatting runs which split up string into areas with separate formats applied - /* - * - - */ - int begIdx = 0; - ooxml.append(""); // new rich text run - for (int j = 0; j < runs.size(); j++) { - short[] idxs = (short[]) runs.get(j); - if (idxs[0] > begIdx) { - ooxml.append(("" + OOXMLAdapter.stripNonAscii(s.substring(begIdx, idxs[0])) + "")); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - begIdx = idxs[0]; - } - Ss_rPr rp = Ss_rPr.createFromFont(bk.getFont(idxs[1])); - ooxml.append(rp.getOOXML()); - } - if (begIdx < s.length()) // output remaining string - s = s.substring(begIdx); - else - s = ""; - ooxml.append("" + OOXMLAdapter.stripNonAscii(s) + ""); - ooxml.append("\r\n"); - ooxml.append(""); - } - } - ooxml.append(""); - ooxml.append("\r\n"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Text(this); - } - - /** - * return the String value of this Text (Comment) element - * i.e. without formatting - * - * @return - */ - public String getComment() { - if (str != null) - return str.getStringVal(); - return null; - } - - /** - * return the String value of this Text (Comment) element - * Including formatting runs - * - * @return - */ - public Unicodestring getCommentWithFormatting() { - return str; - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Text.kt b/src/main/java/io/starter/formats/OOXML/Text.kt new file mode 100644 index 0000000..c757ba4 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Text.kt @@ -0,0 +1,203 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Font +import io.starter.formats.XLS.OOXMLAdapter +import io.starter.formats.XLS.Sst +import io.starter.formats.XLS.Unicodestring +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.Stack + +/** + * text (Comment Text) + * This element contains rich text which represents the text of a comment. The maximum length for this text is a + * spreadsheet application implementation detail. A recommended guideline is 32767 chars + * parent: comment + * children: t (text), r (Rich Text Run), rPh (phonetic run), phoneticPr (phonetic properties) + */ +// TODO: finish elements rPh and phoneticPr +// TODO: preserve +class Text : OOXMLElement { + /** + * return the String value of this Text (Comment) element + * Including formatting runs + * + * @return + */ + var commentWithFormatting: Unicodestring? = null + private set + + override val ooxml: String? + get() = null + + /** + * return the String value of this Text (Comment) element + * i.e. without formatting + * + * @return + */ + val comment: String? + get() = if (commentWithFormatting != null) commentWithFormatting!!.stringVal else null + + /** + * create a new comment WITH formatting + * + * @param strref + */ + constructor(str: Unicodestring) { + this.commentWithFormatting = str + } + + constructor(t: Text) { + this.commentWithFormatting = t.commentWithFormatting + } + + /** + * create a new comment with NO formatting + * + * @param s + */ + constructor(s: String) { + this.commentWithFormatting = Sst.createUnicodeString(s, null, Sst.STRING_ENCODING_AUTO) + } + + /** + * return the OOXML representation of this Text (Comment) element + * + * @param bk + * @return + */ + fun getOOXML(bk: io.starter.formats.XLS.WorkBook): String { + val ooxml = StringBuffer() + ooxml.append("") + if (commentWithFormatting != null) { + var s = OOXMLAdapter.stripNonAsciiRetainQuote(commentWithFormatting!!.stringVal).toString() + val runs = commentWithFormatting!!.formattingRuns + if (runs == null) { // no intra-string formatting + if (s.indexOf(" ") == 0 || s.lastIndexOf(" ") == s.length - 1) { + ooxml.append("$s") + } else { + ooxml.append("$s") + } + ooxml.append("\r\n") + } else { // have formatting runs which split up string into areas with separate formats applied + /* + * + + */ + var begIdx = 0 + ooxml.append("") // new rich text run + for (j in runs.indices) { + val idxs = runs[j] as ShortArray + if (idxs[0] > begIdx) { + ooxml.append("" + OOXMLAdapter.stripNonAscii(s.substring(begIdx, idxs[0].toInt())) + "") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + begIdx = idxs[0].toInt() + } + val rp = Ss_rPr.createFromFont(bk.getFont(idxs[1].toInt())) + ooxml.append(rp.ooxml) + } + if (begIdx < s.length) + // output remaining string + s = s.substring(begIdx) + else + s = "" + ooxml.append("" + OOXMLAdapter.stripNonAscii(s) + "") + ooxml.append("\r\n") + ooxml.append("") + } + } + ooxml.append("") + ooxml.append("\r\n") + return ooxml.toString() + } + + override fun cloneElement(): OOXMLElement { + return Text(this) + } + + companion object { + + private val serialVersionUID = 5886384020139606328L + + /** + * parse this Text element into a unicode string with formatting runs + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): Text { + var str: Unicodestring? = null + var s = "" + var formattingRuns: ArrayList? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "rPr") { // intra-string formatting properties + val idx = s.length // index into character string to apply formatting to + val rp = Ss_rPr.parseOOXML(xpp, bk) as Ss_rPr //.cloneElement(); + val f = rp.generateFont(bk) // NOW CONVERT ss_rPr to a font!! + var fIndex = bk.workBook!!.getFontIdx(f) // index for specific font formatting + if (fIndex == -1) + // must insert new font + fIndex = bk.workBook!!.insertFont(f) + 1 + if (formattingRuns == null) formattingRuns = ArrayList() + formattingRuns.add(shortArrayOf(Integer.valueOf(idx)!!.toShort(), Integer.valueOf(fIndex)!!.toShort())) + } else if (tnm == "t") { + /*boolean bPreserve= false; + if (xpp.getAttributeCount()>0) { + if (xpp.getAttributeName(0).equals("space") && xpp.getAttributeValue(0).equals("preserve")) + bPreserve= true; + } + */ + eventType = xpp.next() + while (eventType != XmlPullParser.END_DOCUMENT && + eventType != XmlPullParser.END_TAG && + eventType != XmlPullParser.TEXT) { + eventType = xpp.next() + } + if (eventType == XmlPullParser.TEXT) { + s = s + xpp.text + } + } + } else if (eventType == XmlPullParser.END_TAG && xpp.name == "text") { + str = Sst.createUnicodeString(s, formattingRuns, Sst.STRING_ENCODING_UNICODE) // create a new unicode string with formatting runs + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("r.parseOOXML: $e") + } + + return Text(str) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/TextRun.java b/src/main/java/io/starter/formats/OOXML/TextRun.java deleted file mode 100644 index 2cc95d7..0000000 --- a/src/main/java/io/starter/formats/OOXML/TextRun.java +++ /dev/null @@ -1,505 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -/** - * textRun group, either r (regular text), br (line break) or fld (text Field) - * parent: p - * children: either r, br or fld - */ -//TODO: Finish rPr children highlight TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver, -public class TextRun implements OOXMLElement { - - private static final long serialVersionUID = -6224636879471246452L; - private r run = null; - private Br brk = null; - private Fld f = null; - - public TextRun(r run, Br brk, Fld f) { - this.run = run; - this.brk = brk; - this.f = f; - } - - public TextRun(TextRun r) { - this.run = r.run; - this.brk = r.brk; - this.f = r.f; - } - - /** - * create a new regular text text run (OOXML element r) - * - * @param s - */ - public TextRun(String s) { - this.run = new r(s, null); - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - r run = null; - Br brk = null; - Fld f = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("r")) { - lastTag.push(tnm); - run = r.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("br")) { - lastTag.push(tnm); - brk = Br.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } else if (tnm.equals("fld")) { - lastTag.push(tnm); - f = Fld.parseOOXML(xpp, lastTag, bk); - lastTag.pop(); - break; - } - } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("textRun.parseOOXML: " + e.toString()); - } - TextRun oe = new TextRun(run, brk, f); - return oe; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (run != null) ooxml.append(run.getOOXML()); - else if (brk != null) ooxml.append(brk.getOOXML()); - else if (f != null) ooxml.append(f.getOOXML()); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TextRun(this); - } - - public String getTitle() { - if (run != null) return run.getTitle(); - if (f != null) return f.getTitle(); - return null; - } - - /** - * return the text properties for this text run - * - * @return - */ - public HashMap getTextProperties() { - if (run != null) - return run.getTextProperties(); - return new HashMap(); - } -} - -/** - * OOXML element r, text run, sub-element of p (paragraph) - *

                  - * children: rPr, t (actual text string) - */ -class r implements OOXMLElement { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 863254651451294443L; - private String t = ""; // t element just contains string - private RPr rp = null; - - public r(String title, RPr rp) { - this.rp = rp; - this.t = title; - } - - public r(r run) { - this.rp = run.rp; - this.t = run.t; - } - - public String getOOXML() { - if (t == null || t.equals("")) return ""; - - t = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(t).toString(); - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); // text run - if (rp != null) ooxml.append(rp.getOOXML()); - ooxml.append("" + t + ""); - ooxml.append(""); - return ooxml.toString(); - } - - public static r parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String t = ""; - RPr rp = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("t")) { // t element of text run -- the title string we are interested in - t = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); - } else if (tnm.equals("rPr")) { // text run properties - lastTag.push(tnm); - rp = RPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("r")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("r.parseOOXML: " + e.toString()); - } - r run = new r(t, rp); - return run; - } - - public OOXMLElement cloneElement() { - return new r(this); - } - - public String getTitle() { - return t; - } - - public HashMap getTextProperties() { - if (rp != null) - return rp.getTextProperties(); - return new HashMap(); - } -} - -/** - * OOXML element br, vertical break, sub-element of p (paragraph) - *

                  - * children: rPr - */ -class Br implements OOXMLElement { - private static final long serialVersionUID = -1724086871866480013L; - private RPr rp; - - public Br(RPr rp) { - this.rp = rp; - } - - public Br(Br b) { - this.rp = b.rp; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (rp != null) ooxml.append(rp.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public static Br parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - RPr rp = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("rPr")) { // text run properties - lastTag.push(tnm); - rp = RPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("br")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("br.parseOOXML: " + e.toString()); - } - Br b = new Br(rp); - return b; - } - - public OOXMLElement cloneElement() { - return new Br(this); - } -} - -/** - * OOXML element fld, text field, sub-element of p (paragraph) - *

                  - * children: pPr, rPr, t (actual text string) - */ -class Fld implements OOXMLElement { - - private static final long serialVersionUID = -7060602732912595402L; - private String t; // t element just contains string - private RPr rp; - private PPr p; - String id; - String type; - - public Fld(String id, String type, String title, RPr rp, PPr p) { - this.id = id; - this.type = type; - this.rp = rp; - this.t = title; - this.p = p; - } - - public Fld(Fld f) { - this.id = f.id; - this.type = f.type; - this.rp = f.rp; - this.t = f.t; - this.p = f.p; - } - - public String getOOXML() { - if (t == null || t.equals("")) return ""; - - t = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(t).toString(); - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (rp != null) ooxml.append(rp.getOOXML()); - if (p != null) ooxml.append(p.getOOXML()); - ooxml.append("" + t + ""); - ooxml.append(""); - return ooxml.toString(); - } - - public static Fld parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String t = ""; - String id = ""; - String type = ""; - PPr p = null; - RPr rp = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("fld")) { - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - if (n.equals("id")) { - id = xpp.getAttributeValue(i); - } else if (n.equals("type")) { - type = xpp.getAttributeValue(i); - } - } - } else if (tnm.equals("t")) { // t element -- the title string we are interested in - t = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp); - } else if (tnm.equals("rPr")) { // text run properties - lastTag.push(tnm); - rp = RPr.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("pPr")) { // text field properties - lastTag.push(tnm); - p = PPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("fld")) { - lastTag.pop(); // pop this tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("textRun.parseOOXML: " + e.toString()); - } - Fld f = new Fld(id, type, t, rp, p); - return f; - } - - public OOXMLElement cloneElement() { - return new Fld(this); - } - - public String getTitle() { - return t; - } - -} - -class RPr implements OOXMLElement { - - private static final long serialVersionUID = 228716184734751439L; - private HashMap attrs; - private Ln l; - private FillGroup fill; - private EffectPropsGroup effect; - private String latin, ea, cs; - - public RPr(HashMap attrs, Ln l, FillGroup fill, EffectPropsGroup effect, String latin, String ea, String cs) { - this.attrs = attrs; - this.l = l; - this.fill = fill; - this.effect = effect; - this.latin = latin; - this.ea = ea; - this.cs = cs; - } - - public RPr(RPr rp) { - this.attrs = rp.attrs; - this.l = rp.l; - this.fill = rp.fill; - this.effect = rp.effect; - this.latin = rp.latin; - this.ea = rp.ea; - this.cs = rp.cs; - } - - public static RPr parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - HashMap attrs = new HashMap(); - Ln l = null; - FillGroup fill = null; - EffectPropsGroup effect = null; - String latin = null, ea = null, cs = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("rPr")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("ln")) { - lastTag.push(tnm); - l = (Ln) Ln.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("solidFill") || - tnm.equals("noFill") || - tnm.equals("gradFill") || - tnm.equals("grpFill") || - tnm.equals("pattFill") || - tnm.equals("blipFill")) { - lastTag.push(tnm); - fill = (FillGroup) FillGroup.parseOOXML(xpp, lastTag, bk); - } else if ( - tnm.equals("effectLst") || - tnm.equals("effectDag")) { - lastTag.push(tnm); - effect = (EffectPropsGroup) EffectPropsGroup.parseOOXML(xpp, lastTag); - // TODO: Eventually these will be objects - } else if (tnm.equals("latin")) { - latin = xpp.getAttributeValue(0); - } else if (tnm.equals("ea")) { - ea = xpp.getAttributeValue(0); - } else if (tnm.equals("cs")) { - cs = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("rPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("rPr.parseOOXML: " + e.toString()); - } - RPr rp = new RPr(attrs, l, fill, effect, latin, ea, cs); - return rp; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (l != null) ooxml.append(l.getOOXML()); - if (fill != null) ooxml.append(fill.getOOXML()); // group fill - if (effect != null) ooxml.append(effect.getOOXML()); // group effect - // highlight - // TEXTUNDERLINELINE - // TEXTUNDERLINEFILL - if (latin != null) ooxml.append(""); - if (ea != null) ooxml.append(""); - if (cs != null) ooxml.append(""); - // sym - // hlinkClick - // hlinkMouseOver - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new RPr(this); - } - - /** - * return the text properties for this text run - * - * @return - */ - public HashMap getTextProperties() { - HashMap textprops = new HashMap(); - textprops.putAll(attrs); - if (latin != null) textprops.put("latin_typeface", latin); - if (ea != null) textprops.put("ea_typeface", ea); - if (cs != null) textprops.put("cs_typeface", cs); - // TODO: Fill, line ... - return textprops; - } -} diff --git a/src/main/java/io/starter/formats/OOXML/TextRun.kt b/src/main/java/io/starter/formats/OOXML/TextRun.kt new file mode 100644 index 0000000..28cfa5d --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/TextRun.kt @@ -0,0 +1,523 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * textRun group, either r (regular text), br (line break) or fld (text Field) + * parent: p + * children: either r, br or fld + */ +//TODO: Finish rPr children highlight TEXTUNDERLINE, TEXTUNDERLINEFILL, sym, hlinkClick, hlinkMouseOver, +class TextRun : OOXMLElement { + private var run: r? = null + private var brk: Br? = null + private var f: Fld? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + if (run != null) + ooxml.append(run!!.ooxml) + else if (brk != null) + ooxml.append(brk!!.ooxml) + else if (f != null) ooxml.append(f!!.ooxml) + return ooxml.toString() + } + + val title: String? + get() { + if (run != null) return run!!.title + return if (f != null) f!!.title else null + } + + /** + * return the text properties for this text run + * + * @return + */ + val textProperties: HashMap + get() = if (run != null) run!!.textProperties else HashMap() + + constructor(run: r, brk: Br, f: Fld) { + this.run = run + this.brk = brk + this.f = f + } + + constructor(r: TextRun) { + this.run = r.run + this.brk = r.brk + this.f = r.f + } + + /** + * create a new regular text text run (OOXML element r) + * + * @param s + */ + constructor(s: String) { + this.run = r(s, null) + } + + override fun cloneElement(): OOXMLElement { + return TextRun(this) + } + + companion object { + + private val serialVersionUID = -6224636879471246452L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var run: r? = null + var brk: Br? = null + var f: Fld? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "r") { + lastTag.push(tnm) + run = r.parseOOXML(xpp, lastTag, bk) + lastTag.pop() + break + } else if (tnm == "br") { + lastTag.push(tnm) + brk = Br.parseOOXML(xpp, lastTag, bk) + lastTag.pop() + break + } else if (tnm == "fld") { + lastTag.push(tnm) + f = Fld.parseOOXML(xpp, lastTag, bk) + lastTag.pop() + break + } + } else if (eventType == XmlPullParser.END_TAG) { // shouldn't get here + lastTag.pop() + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("textRun.parseOOXML: $e") + } + + return TextRun(run, brk, f) + } + } +} + +/** + * OOXML element r, text run, sub-element of p (paragraph) + * + * + * children: rPr, t (actual text string) + */ +internal class r : OOXMLElement { + var title: String? = "" + private set // t element just contains string + private var rp: RPr? = null + + override// text run + val ooxml: String + get() { + if (title == null || title == "") return "" + + title = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(title).toString() + val ooxml = StringBuffer() + ooxml.append("") + if (rp != null) ooxml.append(rp!!.ooxml) + ooxml.append("$title") + ooxml.append("") + return ooxml.toString() + } + + val textProperties: HashMap + get() = if (rp != null) rp!!.textProperties else HashMap() + + constructor(title: String, rp: RPr) { + this.rp = rp + this.title = title + } + + constructor(run: r) { + this.rp = run.rp + this.title = run.title + } + + override fun cloneElement(): OOXMLElement { + return r(this) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 863254651451294443L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): r { + var t: String? = "" + var rp: RPr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "t") { // t element of text run -- the title string we are interested in + t = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp) + } else if (tnm == "rPr") { // text run properties + lastTag.push(tnm) + rp = RPr.parseOOXML(xpp, lastTag, bk) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "r") { + lastTag.pop() // pop this tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("r.parseOOXML: $e") + } + + return r(t, rp) + } + } +} + +/** + * OOXML element br, vertical break, sub-element of p (paragraph) + * + * + * children: rPr + */ +internal class Br : OOXMLElement { + private var rp: RPr? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (rp != null) ooxml.append(rp!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor(rp: RPr) { + this.rp = rp + } + + constructor(b: Br) { + this.rp = b.rp + } + + override fun cloneElement(): OOXMLElement { + return Br(this) + } + + companion object { + private val serialVersionUID = -1724086871866480013L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): Br { + var rp: RPr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "rPr") { // text run properties + lastTag.push(tnm) + rp = RPr.parseOOXML(xpp, lastTag, bk) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "br") { + lastTag.pop() // pop this tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("br.parseOOXML: $e") + } + + return Br(rp) + } + } +} + +/** + * OOXML element fld, text field, sub-element of p (paragraph) + * + * + * children: pPr, rPr, t (actual text string) + */ +internal class Fld : OOXMLElement { + var title: String? = null + private set // t element just contains string + private var rp: RPr? = null + private var p: PPr? = null + var id: String + var type: String? = null + + override// text field + val ooxml: String + get() { + if (title == null || title == "") return "" + + title = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(title).toString() + val ooxml = StringBuffer() + ooxml.append("") + if (rp != null) ooxml.append(rp!!.ooxml) + if (p != null) ooxml.append(p!!.ooxml) + ooxml.append("$title") + ooxml.append("") + return ooxml.toString() + } + + constructor(id: String, type: String, title: String, rp: RPr, p: PPr) { + this.id = id + this.type = type + this.rp = rp + this.title = title + this.p = p + } + + constructor(f: Fld) { + this.id = f.id + this.type = f.type + this.rp = f.rp + this.title = f.title + this.p = f.p + } + + override fun cloneElement(): OOXMLElement { + return Fld(this) + } + + companion object { + + private val serialVersionUID = -7060602732912595402L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): Fld { + var t: String? = "" + var id = "" + var type = "" + var p: PPr? = null + var rp: RPr? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "fld") { + for (i in 0 until xpp.attributeCount) { + val n = xpp.getAttributeName(i) + if (n == "id") { + id = xpp.getAttributeValue(i) + } else if (n == "type") { + type = xpp.getAttributeValue(i) + } + } + } else if (tnm == "t") { // t element -- the title string we are interested in + t = io.starter.formats.XLS.OOXMLAdapter.getNextText(xpp) + } else if (tnm == "rPr") { // text run properties + lastTag.push(tnm) + rp = RPr.parseOOXML(xpp, lastTag, bk) + } else if (tnm == "pPr") { // text field properties + lastTag.push(tnm) + p = PPr.parseOOXML(xpp, lastTag, bk) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "fld") { + lastTag.pop() // pop this tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("textRun.parseOOXML: $e") + } + + return Fld(id, type, t, rp, p) + } + } + +} + +internal class RPr : OOXMLElement { + private var attrs: HashMap? = null + private var l: Ln? = null + private var fill: FillGroup? = null + private var effect: EffectPropsGroup? = null + private var latin: String? = null + private var ea: String? = null + private var cs: String? = null + + override// attributes + // group fill + // group effect + // highlight + // TEXTUNDERLINELINE + // TEXTUNDERLINEFILL + // sym + // hlinkClick + // hlinkMouseOver + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (l != null) ooxml.append(l!!.ooxml) + if (fill != null) ooxml.append(fill!!.ooxml) + if (effect != null) ooxml.append(effect!!.ooxml) + if (latin != null) ooxml.append("") + if (ea != null) ooxml.append("") + if (cs != null) ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + /** + * return the text properties for this text run + * + * @return + */ + // TODO: Fill, line ... + val textProperties: HashMap + get() { + val textprops = HashMap() + textprops.putAll(attrs!!) + if (latin != null) textprops["latin_typeface"] = latin + if (ea != null) textprops["ea_typeface"] = ea + if (cs != null) textprops["cs_typeface"] = cs + return textprops + } + + constructor(attrs: HashMap, l: Ln, fill: FillGroup, effect: EffectPropsGroup, latin: String, ea: String, cs: String) { + this.attrs = attrs + this.l = l + this.fill = fill + this.effect = effect + this.latin = latin + this.ea = ea + this.cs = cs + } + + constructor(rp: RPr) { + this.attrs = rp.attrs + this.l = rp.l + this.fill = rp.fill + this.effect = rp.effect + this.latin = rp.latin + this.ea = rp.ea + this.cs = rp.cs + } + + override fun cloneElement(): OOXMLElement { + return RPr(this) + } + + companion object { + + private val serialVersionUID = 228716184734751439L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): RPr { + val attrs = HashMap() + var l: Ln? = null + var fill: FillGroup? = null + var effect: EffectPropsGroup? = null + var latin: String? = null + var ea: String? = null + var cs: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "rPr") { // get attributes + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "ln") { + lastTag.push(tnm) + l = Ln.parseOOXML(xpp, lastTag, bk) as Ln + } else if (tnm == "solidFill" || + tnm == "noFill" || + tnm == "gradFill" || + tnm == "grpFill" || + tnm == "pattFill" || + tnm == "blipFill") { + lastTag.push(tnm) + fill = FillGroup.parseOOXML(xpp, lastTag, bk) as FillGroup + } else if (tnm == "effectLst" || tnm == "effectDag") { + lastTag.push(tnm) + effect = EffectPropsGroup.parseOOXML(xpp, lastTag) as EffectPropsGroup + // TODO: Eventually these will be objects + } else if (tnm == "latin") { + latin = xpp.getAttributeValue(0) + } else if (tnm == "ea") { + ea = xpp.getAttributeValue(0) + } else if (tnm == "cs") { + cs = xpp.getAttributeValue(0) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "rPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("rPr.parseOOXML: $e") + } + + return RPr(attrs, l, fill, effect, latin, ea, cs) + } + } +} diff --git a/src/main/java/io/starter/formats/OOXML/Theme.java b/src/main/java/io/starter/formats/OOXML/Theme.java deleted file mode 100644 index 73ddfc0..0000000 --- a/src/main/java/io/starter/formats/OOXML/Theme.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserFactory; - -import java.io.InputStream; - -/** - * not fully implemented yet - * Need to handle fonts, fills ... - * Until then themes will not be re-created; instead, it will be treated as a pass-through - */ -public class Theme implements OOXMLElement { - - private static final long serialVersionUID = -9201334460078323287L; - /** - * Generic "Office" Color Scheme TODO: Read from theme1.xml - * 12 colors: 2 darks, 2 lights, 6 accents, 2 hyperlinks - *

                  - * NOTE: APPEARS that 1st 4 are "swapped" so in theme1.xml - * dk1 is index 0, then lt1, dk2, lt2 - * but appears to be a known bug (or a known mystery) that - * one must swap the 1st two pairs - */ - public String[] genericThemeClrs = { - "FFFFFF", // text/bg lt1 = window bg (white) - "000000", // text/bg dk1 = window text (black) - "EEECE1", // text/bg lt2 = secondary window bg color (grayish) - "1F497D", // text/bg dk2 = secondary window text color (deep blue) - "4F81BD", // accent1 -- med-deep blue - "C0504D", // accent2 -- maroon - "9BBB59", // accent3 -- lime greenish - "8064A2", // accent4 -- purplish blue - "4BACC6", // accent5 -- med blue - "F79646", // accent6 -- orange-coral - "0000FF", // hlink -- blue - "800080", // folhlink -- dk purple (followed hlink) - }; - - public static Theme parseThemeOOXML(WorkBookHandle bk, InputStream ii) { - Theme t = new Theme(); - t.parseOOXML(bk, ii); - return t; - } - - /** - * given Theme OOXML inputstream, retrieve theme colors for later use - * This element holds all the different formatting options available to a document through a theme and defines the overall look - * and feel of the document when themed objects are used within the document. - * - * @param bk WorkBookHandle - * @param ii InputStream - * @see parseSheetOOXML - */ - public void parseOOXML(WorkBookHandle bk, InputStream ii) { - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - int idx = 0; - String tnm = xpp.getName(); - if (tnm.equals("clrScheme")) { // in both themeX.xml, themeOverrideX.xml. 12 colors defined. - eventType = xpp.next(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - tnm = xpp.getName(); - if (tnm.equals("dk1")) { - idx = 1; - } else if (tnm.equals("lt1")) { - idx = 0; - } else if (tnm.equals("dk2")) { - idx = 3; - } else if (tnm.equals("lt2")) { - idx = 2; - } else if (tnm.equals("accent1")) { - idx = 4; - } else if (tnm.equals("accent2")) { - idx = 5; - } else if (tnm.equals("accent3")) { - idx = 6; - } else if (tnm.equals("accent4")) { - idx = 7; - } else if (tnm.equals("accent5")) { - idx = 8; - } else if (tnm.equals("accent6")) { - idx = 9; - } else if (tnm.equals("hlink")) { - idx = 10; - } else if (tnm.equals("folHlink")) { - idx = 11; - } else if (tnm.equals("sysClr")) { // system color attributes val, lastClr - this.genericThemeClrs[idx] = xpp.getAttributeValue("", "lastClr"); - } else if (tnm.equals("srgbClr")) { - this.genericThemeClrs[idx] = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG - && xpp.getName().equals("clrScheme")) { - break; - } - eventType = xpp.next(); - } - } else if (tnm.equals("fmtScheme")) { - // This element contains the background fill styles, effect styles, fill styles, and line styles which define the style matrix for a theme. - // The style matrix consists of subtle, moderate, and intense fills, lines, and effects. - } else if (tnm.equals("fontScheme")) { - // majorFont defines the set of major fonts which are to be used under different languages or locals. - // minorFont defines the set of minor fonts that are to be used under different languages or locals - } else if (tnm.equals("objectDefaults")) { // only in themeX.xml - - } else if (tnm.equals("extraClrSchemeList")) { // only in themeX.xml - - } - } else if (eventType == XmlPullParser.END_TAG) { - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("Theme.parseOOXML: " + e.toString()); - } - return; - } - - // TODO: implement - public OOXMLElement cloneElement() { - return null; - } - - public String getOOXML() { - return null; - } - -} diff --git a/src/main/java/io/starter/formats/OOXML/Theme.kt b/src/main/java/io/starter/formats/OOXML/Theme.kt new file mode 100644 index 0000000..a9da0a4 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Theme.kt @@ -0,0 +1,161 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserFactory + +import java.io.InputStream + +/** + * not fully implemented yet + * Need to handle fonts, fills ... + * Until then themes will not be re-created; instead, it will be treated as a pass-through + */ +class Theme : OOXMLElement { + /** + * Generic "Office" Color Scheme TODO: Read from theme1.xml + * 12 colors: 2 darks, 2 lights, 6 accents, 2 hyperlinks + * + * + * NOTE: APPEARS that 1st 4 are "swapped" so in theme1.xml + * dk1 is index 0, then lt1, dk2, lt2 + * but appears to be a known bug (or a known mystery) that + * one must swap the 1st two pairs + */ + var genericThemeClrs = arrayOf("FFFFFF", // text/bg lt1 = window bg (white) + "000000", // text/bg dk1 = window text (black) + "EEECE1", // text/bg lt2 = secondary window bg color (grayish) + "1F497D", // text/bg dk2 = secondary window text color (deep blue) + "4F81BD", // accent1 -- med-deep blue + "C0504D", // accent2 -- maroon + "9BBB59", // accent3 -- lime greenish + "8064A2", // accent4 -- purplish blue + "4BACC6", // accent5 -- med blue + "F79646", // accent6 -- orange-coral + "0000FF", // hlink -- blue + "800080")// folhlink -- dk purple (followed hlink) + + override val ooxml: String? + get() = null + + /** + * given Theme OOXML inputstream, retrieve theme colors for later use + * This element holds all the different formatting options available to a document through a theme and defines the overall look + * and feel of the document when themed objects are used within the document. + * + * @param bk WorkBookHandle + * @param ii InputStream + * @see parseSheetOOXML + */ + fun parseOOXML(bk: WorkBookHandle, ii: InputStream) { + try { + val factory = XmlPullParserFactory.newInstance() + factory.isNamespaceAware = true + val xpp = factory.newPullParser() + xpp.setInput(ii, null) // using XML 1.0 specification + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + var idx = 0 + var tnm = xpp.name + if (tnm == "clrScheme") { // in both themeX.xml, themeOverrideX.xml. 12 colors defined. + eventType = xpp.next() + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + tnm = xpp.name + if (tnm == "dk1") { + idx = 1 + } else if (tnm == "lt1") { + idx = 0 + } else if (tnm == "dk2") { + idx = 3 + } else if (tnm == "lt2") { + idx = 2 + } else if (tnm == "accent1") { + idx = 4 + } else if (tnm == "accent2") { + idx = 5 + } else if (tnm == "accent3") { + idx = 6 + } else if (tnm == "accent4") { + idx = 7 + } else if (tnm == "accent5") { + idx = 8 + } else if (tnm == "accent6") { + idx = 9 + } else if (tnm == "hlink") { + idx = 10 + } else if (tnm == "folHlink") { + idx = 11 + } else if (tnm == "sysClr") { // system color attributes val, lastClr + this.genericThemeClrs[idx] = xpp.getAttributeValue("", "lastClr") + } else if (tnm == "srgbClr") { + this.genericThemeClrs[idx] = xpp.getAttributeValue(0) + } + } else if (eventType == XmlPullParser.END_TAG && xpp.name == "clrScheme") { + break + } + eventType = xpp.next() + } + } else if (tnm == "fmtScheme") { + // This element contains the background fill styles, effect styles, fill styles, and line styles which define the style matrix for a theme. + // The style matrix consists of subtle, moderate, and intense fills, lines, and effects. + } else if (tnm == "fontScheme") { + // majorFont defines the set of major fonts which are to be used under different languages or locals. + // minorFont defines the set of minor fonts that are to be used under different languages or locals + } else if (tnm == "objectDefaults") { // only in themeX.xml + + } else if (tnm == "extraClrSchemeList") { // only in themeX.xml + + } + } else if (eventType == XmlPullParser.END_TAG) { + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("Theme.parseOOXML: $e") + } + + return + } + + // TODO: implement + override fun cloneElement(): OOXMLElement? { + return null + } + + companion object { + + private val serialVersionUID = -9201334460078323287L + + fun parseThemeOOXML(bk: WorkBookHandle, ii: InputStream): Theme { + val t = Theme() + t.parseOOXML(bk, ii) + return t + } + } + +} diff --git a/src/main/java/io/starter/formats/OOXML/Title.java b/src/main/java/io/starter/formats/OOXML/Title.java deleted file mode 100644 index 33916e7..0000000 --- a/src/main/java/io/starter/formats/OOXML/Title.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.WorkBook; -import io.starter.formats.XLS.charts.TextDisp; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Stack; - -/** - * class holds OOXML title Property used to define chart and axis titles - */ -public class Title implements OOXMLElement { - - private static final long serialVersionUID = -3889674575558708481L; - private Layout layout = null; - private SpPr sp = null; - private ChartText chartText = null; // tx - private TxPr txpr = null; // xPr - - public Title(ChartText ct, TxPr txpr, Layout l, SpPr sp) { - this.layout = l; - this.sp = sp; - this.chartText = ct; - this.txpr = txpr; - } - - /** - * for BIFF8 compatibility, create a Title element from the title string - * - * @param t - */ - public Title(String t) { - this.chartText = new ChartText(t); - this.sp = new SpPr("c"); - // no spPr - } - - /** - * create an OOXML title from a 2003-v TextDisp record - * - * @param td - */ - public Title(TextDisp td, WorkBook bk) { - P para = new P(td.getFont(bk), td.toString()); - this.chartText = new ChartText(null, para, null); - } - - public void setLayout(double x, double y) { - this.layout = new Layout(null, new double[]{x, y, -1, -1}); - } - - /** - * parse title OOXML element title - * - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spPr object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - TxPr txpr = null; - ChartText ct = null; - Layout l = null; - SpPr sp = null; - - /* - * TextDisp td= (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_TITLE, - * str, this.getWorkBook()); this.addChartRecord((BiffRec) td); // add - * TextDisp title to end of chart recs ... charttitle= td; - */ - /** - * contains (in Sequence) layout overlay -- not handled yet spPr tx txPr - */ - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("tx")) { // chart text - lastTag.push(tnm); - ct = ChartText.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("manualLayout")) { - lastTag.push(tnm); - l = (Layout) Layout.parseOOXML(xpp, lastTag); - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk); - // sp.setNS("c"); - } else if (tnm.equals("txPr")) { - lastTag.push(tnm); - txpr = (TxPr) TxPr.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("title")) { - lastTag.pop(); // pop title tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("title.parseOOXML: " + e.toString()); - } - Title tt = new Title(ct, txpr, l, sp); - return tt; - } - - public Layout getLayout() { - return layout; - } - - public SpPr getSpPr() { - return sp; - } - - /** - * generate ooxml to define a title - * - * @return - */ - /** - * tx chart text layout overlay spPr txPr - */ - public String getOOXML() { - StringBuffer tooxml = new StringBuffer(); - tooxml.append(""); - if (this.chartText != null) - tooxml.append(chartText.getOOXML()); - if (this.layout != null) - tooxml.append(layout.getOOXML()); - // TODO: overlay - if (this.sp != null) - tooxml.append(sp.getOOXML()); - if (this.txpr != null) - tooxml.append(this.txpr.getOOXML()); - tooxml.append(""); - return tooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Title(this.chartText, this.txpr, this.layout, this.sp); - } - - public String getTitle() { - if (chartText != null) - return chartText.getTitle(); - return ""; - } - - /** - * return the font index for this title - * - * @param wb - * @return - */ - public int getFontId(WorkBookHandle wb) { - if (chartText != null) - return chartText.getFontId(wb); - return -1; - } -} - -/** - * chart text element tx - */ - -/** - * contains either strRef -- contains f, strCache rich -- contains bodyPr, - * lstStyle, p - */ -class ChartText implements OOXMLElement { - - private static final long serialVersionUID = -1175394918747218776L; - StrRef strref = null; - P para = null; - BodyPr bpr = null; - - /** - * for BIFF8 compatibility, create an OOXML chartText element from title - * string - * - * @param s - */ - public ChartText(String s) { - this.para = new P(s); - } - - public ChartText(StrRef s, P para, BodyPr bpr) { - this.strref = s; - this.para = para; - this.bpr = bpr; - } - - public static ChartText parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - P para = null; - BodyPr bpr = null; - StrRef s = null; - - try { // title->tx->rich->bodyPr lstStyle, p->pPr, r - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("p")) { // text Paragraph props - part of rich - lastTag.push(tnm); - para = (P) P.parseOOXML(xpp, lastTag, bk); - } else if (tnm.equals("bodyPr")) { // body-level Paragraph props -- part of rich - lastTag.push(tnm); - bpr = (BodyPr) BodyPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("strRef")) { - lastTag.push(tnm); - s = (StrRef) StrRef.parseOOXML(xpp, lastTag); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("tx")) { - lastTag.pop(); // pop title tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("chartText.parseOOXML: " + e.toString()); - } - ChartText ct = new ChartText(s, para, bpr); - return ct; - } - - public String getOOXML() { - StringBuffer cooxml = new StringBuffer(); - cooxml.append(""); // chart text - if (strref == null) { // it has a rich element - cooxml.append(""); - if (bpr != null) - cooxml.append(bpr.getOOXML()); - else - cooxml.append(""); - cooxml.append(""); // TODO: Handle!!! - if (para != null) - cooxml.append(para.getOOXML()); // text paragraph - cooxml.append(""); - } else { // it has a strRef element - cooxml.append(strref.getOOXML()); - } - cooxml.append(""); - return cooxml.toString(); - } - - public String getTitle() { - if (para != null) - return para.getTitle(); - return ""; - } - - public OOXMLElement cloneElement() { - return new ChartText(this.strref, this.para, this.bpr); - } - - - /** - * concatenate the 3 levels of text properties and either find an existing - * font or add new - * - * @return - */ - public int getFontId(WorkBookHandle wb) { - HashMap textprops = new HashMap(); - if (bpr != null) { - /* - * noAutofit (No AutoFit) §5.1.5.1.2 normAutofit (Normal AutoFit) - * §5.1.5.1.3 prstTxWarp (Preset Text Warp) §5.1.11.19 scene3d (3D - * Scene Properties) §5.1.4.1.26 sp3d (Apply 3D shape properties) - * §5.1.7.12 spAutoFit (Shape AutoFit) - */ - // TODO: set any of the above?? - } - if (para != null) { - textprops = para.getTextProperties(); - } - /* - * altLang (Alternative Language) b (Bold) bool baseline (Baseline) bmk - * (Bookmark Link Target) cap (Capitalization) i (Italics) bool kern - * (Kerning) kumimoji lang (Language ID) spc (Spacing) strike - * (Strikethrough) sz (Font Size) size u (Underline) underline style - * - * PLUS-- fill, line, blipFill, cs/ea/latin font (attribute: typeface) - * **** - */ - int w = 400, u = 0; - double h = 200; // default - boolean b = false, i = false; - String face = "Arial"; - if (textprops.get("b") != null) - b = ("1".equals(textprops.get("b"))); - if (textprops.get("i") != null) - i = ("1".equals(textprops.get("i"))); - if (textprops.get("latin_typeface") != null) - face = (String) textprops.get("latin_typeface"); - // if (textprops.get("u")!=null) - // u= textprops.get("u").toString(); - Object o = textprops.get("sz"); // Whole points are specified in - // increments of 100 starting with 100 - // being a point size of 1 - if (o != null) - h = Font.PointsToFontHeight(Integer.parseInt((String) o) / 100); - Font f = new Font(face, w, new Float(h).intValue()); - if (b) - f.setBold(true); - if (i) - f.setItalic(i); - if (u != 0) - f.setUnderlineStyle((byte) u); - o = textprops.get("vertAlign"); - if (o != null) { - String s = (String) o; - if (s.equals("baseline")) - f.setScript(0); - else if (s.equals("superscript")) - f.setScript(1); - else if (s.equals("subscript")) - f.setScript(2); - } - o = textprops.get("strike"); - if (o != null) - f.setStricken(true); - // f.setFontColor(cl); - return io.starter.OpenXLS.FormatHandle.addFont(f, wb); - } - -} diff --git a/src/main/java/io/starter/formats/OOXML/Title.kt b/src/main/java/io/starter/formats/OOXML/Title.kt new file mode 100644 index 0000000..bca9148 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Title.kt @@ -0,0 +1,358 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Font +import io.starter.formats.XLS.WorkBook +import io.starter.formats.XLS.charts.TextDisp +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +/** + * class holds OOXML title Property used to define chart and axis titles + */ +class Title : OOXMLElement { + var layout: Layout? = null + private set + var spPr: SpPr? = null + private set + private var chartText: ChartText? = null // tx + private val txpr: TxPr? = null // xPr + + /** + * generate ooxml to define a title + * + * @return + */ + /** + * tx chart text layout overlay spPr txPr + */ + override// TODO: overlay + val ooxml: String + get() { + val tooxml = StringBuffer() + tooxml.append("") + if (this.chartText != null) + tooxml.append(chartText!!.ooxml) + if (this.layout != null) + tooxml.append(layout!!.ooxml) + if (this.spPr != null) + tooxml.append(spPr!!.ooxml) + if (this.txpr != null) + tooxml.append(this.txpr.ooxml) + tooxml.append("") + return tooxml.toString() + } + + val title: String? + get() = if (chartText != null) chartText!!.title else "" + + constructor(ct: ChartText, txpr: TxPr, l: Layout, sp: SpPr) { + this.layout = l + this.spPr = sp + this.chartText = ct + this.txpr = txpr + } + + /** + * for BIFF8 compatibility, create a Title element from the title string + * + * @param t + */ + constructor(t: String) { + this.chartText = ChartText(t) + this.spPr = SpPr("c") + // no spPr + } + + /** + * create an OOXML title from a 2003-v TextDisp record + * + * @param td + */ + constructor(td: TextDisp, bk: WorkBook) { + val para = P(td.getFont(bk)!!, td.toString()) + this.chartText = ChartText(null, para, null) + } + + fun setLayout(x: Double, y: Double) { + this.layout = Layout(null, doubleArrayOf(x, y, -1.0, -1.0)) + } + + override fun cloneElement(): OOXMLElement { + return Title(this.chartText, this.txpr, this.layout, this.spPr) + } + + /** + * return the font index for this title + * + * @param wb + * @return + */ + fun getFontId(wb: WorkBookHandle): Int { + return if (chartText != null) chartText!!.getFontId(wb) else -1 + } + + companion object { + + private val serialVersionUID = -3889674575558708481L + + /** + * parse title OOXML element title + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var txpr: TxPr? = null + var ct: ChartText? = null + var l: Layout? = null + var sp: SpPr? = null + + /* + * TextDisp td= (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_TITLE, + * str, this.getWorkBook()); this.addChartRecord((BiffRec) td); // add + * TextDisp title to end of chart recs ... charttitle= td; + */ + /** + * contains (in Sequence) layout overlay -- not handled yet spPr tx txPr + */ + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "tx") { // chart text + lastTag.push(tnm) + ct = ChartText.parseOOXML(xpp, lastTag, bk) + } else if (tnm == "manualLayout") { + lastTag.push(tnm) + l = Layout.parseOOXML(xpp, lastTag) as Layout + } else if (tnm == "spPr") { + lastTag.push(tnm) + sp = SpPr.parseOOXML(xpp, lastTag, bk) as SpPr + // sp.setNS("c"); + } else if (tnm == "txPr") { + lastTag.push(tnm) + txpr = TxPr.parseOOXML(xpp, lastTag, bk) as TxPr + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "title") { + lastTag.pop() // pop title tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("title.parseOOXML: $e") + } + + return Title(ct, txpr, l, sp) + } + } +} + +/** + * chart text element tx + */ + +/** + * contains either strRef -- contains f, strCache rich -- contains bodyPr, + * lstStyle, p + */ +internal class ChartText : OOXMLElement { + var strref: StrRef? = null + var para: P? = null + var bpr: BodyPr? = null + + override// chart text + // it has a rich element + // TODO: Handle!!! + // text paragraph + // it has a strRef element + val ooxml: String + get() { + val cooxml = StringBuffer() + cooxml.append("") + if (strref == null) { + cooxml.append("") + if (bpr != null) + cooxml.append(bpr!!.ooxml) + else + cooxml.append("") + cooxml.append("") + if (para != null) + cooxml.append(para!!.ooxml) + cooxml.append("") + } else { + cooxml.append(strref!!.ooxml) + } + cooxml.append("") + return cooxml.toString() + } + + val title: String? + get() = if (para != null) para!!.title else "" + + /** + * for BIFF8 compatibility, create an OOXML chartText element from title + * string + * + * @param s + */ + constructor(s: String) { + this.para = P(s) + } + + constructor(s: StrRef, para: P, bpr: BodyPr) { + this.strref = s + this.para = para + this.bpr = bpr + } + + override fun cloneElement(): OOXMLElement { + return ChartText(this.strref, this.para, this.bpr) + } + + + /** + * concatenate the 3 levels of text properties and either find an existing + * font or add new + * + * @return + */ + fun getFontId(wb: WorkBookHandle): Int { + var textprops: HashMap<*, *> = HashMap() + if (bpr != null) { + /* + * noAutofit (No AutoFit) §5.1.5.1.2 normAutofit (Normal AutoFit) + * §5.1.5.1.3 prstTxWarp (Preset Text Warp) §5.1.11.19 scene3d (3D + * Scene Properties) §5.1.4.1.26 sp3d (Apply 3D shape properties) + * §5.1.7.12 spAutoFit (Shape AutoFit) + */ + // TODO: set any of the above?? + } + if (para != null) { + textprops = para!!.textProperties + } + /* + * altLang (Alternative Language) b (Bold) bool baseline (Baseline) bmk + * (Bookmark Link Target) cap (Capitalization) i (Italics) bool kern + * (Kerning) kumimoji lang (Language ID) spc (Spacing) strike + * (Strikethrough) sz (Font Size) size u (Underline) underline style + * + * PLUS-- fill, line, blipFill, cs/ea/latin font (attribute: typeface) + * **** + */ + val w = 400 + val u = 0 + var h = 200.0 // default + var b = false + var i = false + var face = "Arial" + if (textprops.get("b") != null) + b = "1" == textprops.get("b") + if (textprops.get("i") != null) + i = "1" == textprops.get("i") + if (textprops.get("latin_typeface") != null) + face = textprops.get("latin_typeface") as String + // if (textprops.get("u")!=null) + // u= textprops.get("u").toString(); + var o: Any? = textprops.get("sz") // Whole points are specified in + // increments of 100 starting with 100 + // being a point size of 1 + if (o != null) + h = Font.PointsToFontHeight((Integer.parseInt((o as String?)!!) / 100).toDouble()).toDouble() + val f = Font(face, w, h.toInt()) + if (b) + f.bold = true + if (i) + f.italic = i + if (u != 0) + f.setUnderlineStyle(u.toByte()) + o = textprops.get("vertAlign") + if (o != null) { + val s = o as String? + if (s == "baseline") + f.script = 0 + else if (s == "superscript") + f.script = 1 + else if (s == "subscript") + f.script = 2 + } + o = textprops.get("strike") + if (o != null) + f.stricken = true + // f.setFontColor(cl); + return io.starter.OpenXLS.FormatHandle.addFont(f, wb) + } + + companion object { + + private val serialVersionUID = -1175394918747218776L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): ChartText { + var para: P? = null + var bpr: BodyPr? = null + var s: StrRef? = null + + try { // title->tx->rich->bodyPr lstStyle, p->pPr, r + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "p") { // text Paragraph props - part of rich + lastTag.push(tnm) + para = P.parseOOXML(xpp, lastTag, bk) as P + } else if (tnm == "bodyPr") { // body-level Paragraph props -- part of rich + lastTag.push(tnm) + bpr = BodyPr.parseOOXML(xpp, lastTag) as BodyPr + } else if (tnm == "strRef") { + lastTag.push(tnm) + s = StrRef.parseOOXML(xpp, lastTag) as StrRef + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "tx") { + lastTag.pop() // pop title tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("chartText.parseOOXML: $e") + } + + return ChartText(s, para, bpr) + } + } + +} diff --git a/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.java b/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.java deleted file mode 100644 index 52e2123..0000000 --- a/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.java +++ /dev/null @@ -1,660 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.ColHandle; -import io.starter.OpenXLS.RowHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * twoCellAnchor (Two Cell Anchor Shape Size) - *

                  - * This element specifies a two cell anchor placeholder for a group, a shape, or a drawing element. - * It moves with cells and its extents are in EMU units. - *

                  - * This is the root element for charts, images and shapes. - *

                  - * parent: wsDr - * children: from, to, OBJECTCHOICES (sp, grpSp, graphicFrame, cxnSp, pic), clientData - */ -//TODO: finish grpSp Group Shape -// TODO: finish clientData element -public class TwoCellAnchor implements OOXMLElement { - - private static final long serialVersionUID = 4180396678197959710L; - // EMU = pixel * 914400 / Resolution (96?) - public static final short EMU = 1270; - private String editAs = null; - private String embedName = null; - private From from = null; - private To to = null; - private ObjectChoice o = null; - - public TwoCellAnchor(String editAs) { - this.editAs = editAs; - } - - public TwoCellAnchor(String editAs, From f, To t, ObjectChoice o) { - this.editAs = editAs; - this.from = f; - this.to = t; - this.o = o; - } - - public TwoCellAnchor(TwoCellAnchor tce) { - this.editAs = tce.editAs; - this.from = tce.from; - this.to = tce.to; - this.o = tce.o; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String editAs = null; - From f = null; - To t = null; - ObjectChoice o = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("twoCellAnchor")) { // get attributes - if (xpp.getAttributeCount() > 0) - editAs = xpp.getAttributeValue(0); - } else if (tnm.equals("from")) { - lastTag.push(tnm); - f = From.parseOOXML(xpp, lastTag); - } else if (tnm.equals("to")) { - lastTag.push(tnm); - t = To.parseOOXML(xpp, lastTag); - } else if (tnm.equals("cxnSp") || - tnm.equals("graphicFrame") || - tnm.equals("grpSp") || - tnm.equals("pic") || - tnm.equals("sp")) { - o = (ObjectChoice) ObjectChoice.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("twoCellAnchor")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("twoCellAnchor.parseOOXML: " + e.toString()); - } - TwoCellAnchor tca = new TwoCellAnchor(editAs, f, t, o); - return tca; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (from != null) ooxml.append(from.getOOXML()); - if (to != null) ooxml.append(to.getOOXML()); - ooxml.append(o.getOOXML()); - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TwoCellAnchor(this); - } - - // access methods ****** - - /** - * return the (to, from) bounds of this object - * by concatenating the bounds for the to and the from - * bounds represent: COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, ROWOFFSET1 - * - * @return bounds short[8] from [4] and to [4] - */ - public int[] getBounds() { - int[] bounds = new int[8]; - System.arraycopy(from.getBounds(), 0, bounds, 0, 4); // from bounds - System.arraycopy(to.getBounds(), 0, bounds, 4, 4); // to bounds - return bounds; - } - - /** - * set the (to, from) bounds of this object - * bounds represent: COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, ROWOFFSET1 - * NOTE: COL - * - * @param bounds int[8] from [4] and to [4] - */ - public void setBounds(int[] bounds) { - int[] b = new int[4]; - System.arraycopy(bounds, 0, b, 0, 4); - if (from == null) from = new From(b); - else from.setBounds(b); - System.arraycopy(bounds, 4, b, 0, 4); - if (to == null) to = new To(b); - else to.setBounds(b); - } - - /** - * get cNvPr name attribute - * - * @return - */ - public String getName() { - if (o != null) - return o.getName(); - return null; - } - - public String toString() { - return this.getName(); - } - - /** - * return if this twoCellAnchor element refers to an image rather than a chart or shape - * - * @return - */ - public boolean hasImage() { - return o.hasImage(); - } - - /** - * return if this twoCellAnchor element refers to a chart as opposed to a shape or image - * - * @return - */ - public boolean hasChart() { - if (o != null) - return o.hasChart(); - return false; - } - - /** - * return if this twoCellAnchor element refers to a shape, as opposed a chart or an image - * - * @return - */ - public boolean hasShape() { - if (o != null) - return o.hasShape(); - return false; - } - - /** - * set cNvPr name attribute - * - * @param name - */ - public void setName(String name) { - if (o != null) - o.setName(name); - } - - /** - * get cNvPr descr attribute - * - * @return - */ - public String getDescr() { - if (o != null) - return o.getDescr(); - return null; - } - - /** - * set cNvPr descr attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setDescr(String descr) { - if (o != null) - o.setDescr(descr); - } - - /** - * get macro attribute - * - * @return - */ - public String getMacro() { - if (o != null) - return o.getMacro(); - return null; - } - - /** - * set Macro attribute - * sometimes associated with shape name - * - * @param descr - */ - public void setMacro(String macro) { - if (o != null) - o.setMacro(macro); - } - - /** - * get the URI associated with this graphic Data - */ - public String getURI() { - if (o != null) - return o.getURI(); - return null; - } - - /** - * set the URI associated with this graphic data - * - * @param uri - */ - public void setURI(String uri) { - if (o != null) - o.setURI(uri); - } - - /** - * return the rid for the chart defined by this twocellanchor - * - * @return - */ - public String getChartRId() { - if (o != null) - return o.getChartRId(); - return null; - } - - public void setChartRId(String rId) { - if (o != null) o.setChartRId(rId); - } - - /** - * return the id for the embedded picture or shape (i.e. resides within the file) - * - * @return - */ - public String getEmbed() { - if (o != null) - return o.getEmbed(); - return null; - } - - /** - * set the embed for the embedded picture or shape (i.e. resides within the file) - * - * @param embed - */ - public void setEmbed(String embed) { - if (o != null) o.setEmbed(embed); - } - - - /** - * return the Embedded Object's filename as saved on disk - * - * @return - */ - public String getEmbedFilename() { - return embedName; - } - - /** - * set the Embedded Object's filename as saved on disk - * - * @param embed - */ - public void setEmbedFilename(String embedFile) { - this.embedName = embedFile; - } - - - /** - * return the id for the linked object (i.e. doesn't reside in the file) - * - * @return - */ - public String getLink() { - if (o != null) - return o.getLink(); - return null; - } - - - /** - * set the link attribute for this blip (the id for the linked picture) - * - * @param embed - */ - public void setLink(String link) { - if (o != null) o.setLink(link); - } - - /** - * return the editAs editMovement attribute - * - * @return - */ - public String getEditAs() { - return editAs; - } - - /** - * set the editAs editMovement attribute - * - * @return - */ - public void setEditAs(String editAs) { - this.editAs = editAs; - } - - /** - * utility to return the shape properties element (picture element only) - * should be depreciated when OOXML is completely distinct from BIFF8 - * - * @return - */ - public SpPr getSppr() { - if (o != null) - return o.getSppr(); - return null; - } - - /** - * set this twoCellAnchor as a chart element - * used for - * - * @param rid - * @param name - * @param bounds - */ - public void setAsChart(int rid, String name, int[] bounds) { - o = new ObjectChoice(); - o.setObject(new GraphicFrame()); - o.setName(name); - o.setChartRId("rId" + Integer.valueOf(rid).toString()); - o.setId(rid); - this.setBounds(bounds); - // id??? - } - - /** - * set this twoCellAnchor as an image - * - * @param rid - * @param name - * @param id - */ - public void setAsImage(int rid, String name, String descr, int spid, SpPr sp) { - o = new ObjectChoice(); - o.setObject(new Pic()); - o.setName(name); - o.setDescr(descr); - o.setEmbed("rId" + Integer.valueOf(rid).toString()); - o.setId(spid); - if (sp != null) { - ((Pic) o.getObject()).setSppr(sp); - } - } - - - /** - * given bounds[8] in BIFF 8 coordinates and convert to OOXML units - * mostly this means adjusting ROWOFFSET 0 & 1 (bounds[5] & bounds[7] - * and COLOFFSET 0 & 1 (bounds[1] & bounds[5] to EMU units + - * adjust according to emperical calc garnered from observation - * - * @param sheet - * @param bbounds short[] bounds in BIFF8 units - * @return new bounds int[] bounds in OOXML units - */ - public static int[] convertBoundsFromBIFF8(io.starter.formats.XLS.Boundsheet sheet, short[] bbounds) { - // note on bounds: - // -- offsets (bounds 1,3,5 & 7) are %'s of column width or row height, respectively - // below calculations are garnered from OOXML info + by comparing to Excel's values ... may not be 100% but appears to work for both 2003 and 2007 versions - // NOTE: if change offset algorithm here must modify algorithm in twoCellAnchor from and to classes - int[] bounds = new int[8]; - bounds[0] = bbounds[0]; - double cw = ColHandle.getWidth(sheet, bbounds[0]); ///5.0; -// below is more correct (???) bounds[1]= (int)(EMU*cw*(bbounds[1]/1024.0)); - bounds[1] = (int) (cw * 256 * (bbounds[1] / 1024.0)); - double rh = RowHandle.getHeight(sheet, bbounds[2]); ///2.0; - bounds[2] = bbounds[2]; - bounds[3] = (int) (EMU * rh * (bbounds[3] / 256.0)); - cw = ColHandle.getWidth(sheet, bbounds[4]); ///5.0; - bounds[4] = bbounds[4]; -// bounds[5]= (int)(EMU*cw*(bbounds[5]/1024.0)); - bounds[5] = (int) (cw * 256 * (bbounds[5] / 1024.0)); - rh = RowHandle.getHeight(sheet, bbounds[6]); ///2.0; - bounds[6] = bbounds[6]; - bounds[7] = (int) (EMU * rh * (bbounds[7] / 256.0)); - return bounds; - } - - /** - * convert bounds[8] from OOXML to BIFF8 Units - * basically must adjust COLOFFSETs and ROWOFFSETs to BIFF8 units - * - * @param sheet - * @param bounds int[] - * @return bounds short[] (BIFF8 uses short[] + different units) - */ - public static short[] convertBoundsToBIFF8(io.starter.formats.XLS.Boundsheet sheet, int[] bounds) { - short[] bbounds = new short[8]; - bbounds[0] = (short) bounds[0]; - double cw = ColHandle.getWidth(sheet, bounds[0]); ///5.0; -// below is more correct (???) bbounds[1]= (short)((bounds[1]*1024)/(TwoCellAnchor.EMU*cw)); - bbounds[1] = (short) ((bounds[1] * 1024.0) / (cw * 256)); - bbounds[2] = (short) bounds[2]; - double rh = RowHandle.getHeight(sheet, bounds[2]); ///2.0; - bbounds[3] = (short) ((bounds[3] * 256.0) / (TwoCellAnchor.EMU * rh)); - bbounds[4] = (short) bounds[4]; - cw = ColHandle.getWidth(sheet, bounds[4]); ///5.0; -// bbounds[5]= (short)((bounds[5]*1024)/(TwoCellAnchor.EMU*cw)); - bbounds[5] = (short) ((bounds[5] * 1024.0) / (cw * 256)); - bbounds[6] = (short) bounds[6]; - rh = RowHandle.getHeight(sheet, bounds[6]); ///2.0; - bbounds[7] = (short) ((bounds[7] * 256.0) / (TwoCellAnchor.EMU * rh)); - return bbounds; - } -} - -/** - * from (Starting Anchor Point) - * This element specifies the first anchor point for the drawing element. This will be used to anchor the top and left - * sides of the shape within the spreadsheet. That is when the cell that is specified in the from element is adjusted, - * the shape will also be adjusted. - *

                  - * NOTE: Coordinates are in OOXML units; they are converted to BIFF8 units using twoCellAnchor.convertBoundsToBIFF8 - * parent: oneCellAnchor, twoCellAnchor - * children: col, colOff, row, rowOff - */ -class From implements OOXMLElement { - - private static final long serialVersionUID = -4776435343244555855L; - private int[] bounds; - - - public From(int[] bounds) { - this.bounds = new int[4]; - System.arraycopy(bounds, 0, this.bounds, 0, 4); - } - - public From(From f) { - this.bounds = f.bounds; - } - - public static From parseOOXML(XmlPullParser xpp, Stack lastTag) { - int[] bounds = new int[4]; - int boundsidx = 0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("col")) { - bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("colOff")) { - bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("row")) { - bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("rowOff")) { - bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("from")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("from.parseOOXML: " + e.toString()); - } - From f = new From(bounds); - return f; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append("" + bounds[0] + ""); - ooxml.append("" + bounds[1] + ""); - ooxml.append("" + bounds[2] + ""); - ooxml.append("" + bounds[3] + ""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new From(this); - } - - public int[] getBounds() { - if (bounds == null) bounds = new int[4]; - return bounds; - } - - public void setBounds(int[] bounds) { - System.arraycopy(bounds, 0, this.bounds, 0, 4); - } -} - -/** - * to (Ending Anchor Point) - * This element specifies the second anchor point for the drawing element. This will be used to anchor the bottom - * and right sides of the shape within the spreadsheet. That is when the cell that is specified in the to element is - * adjusted, the shape will also be adjusted.to (Ending Anchor Point) - * This element specifies the second anchor point for the drawing element. This will be used to anchor the bottom - * and right sides of the shape within the spreadsheet. That is when the cell that is specified in the to element is - * adjusted, the shape will also be adjusted. - *

                  - * NOTE: Coordinates are in OOXML units; they are converted to BIFF8 units using twoCellAnchor.convertBoundsToBIFF8 - * parent: twoCellAnchor - * children: col, colOff, row, rowOff - */ -class To implements OOXMLElement { - - private static final long serialVersionUID = 1500243445505400113L; - private int[] bounds; - - public To(int[] bounds) { - this.bounds = new int[4]; - System.arraycopy(bounds, 0, this.bounds, 0, 4); - } - - public To(To f) { - this.bounds = f.bounds; - } - - public static To parseOOXML(XmlPullParser xpp, Stack lastTag) { - int[] bounds = new int[4]; - int boundsidx = 0; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("col")) { - bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("colOff")) { - bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("row")) { - bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); - } else if (tnm.equals("rowOff")) { - bounds[boundsidx++] = Integer.parseInt(xpp.nextText()); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("to")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("to.parseOOXML: " + e.toString()); - } - To f = new To(bounds); - return f; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append("" + bounds[0] + ""); - ooxml.append("" + bounds[1] + ""); - ooxml.append("" + bounds[2] + ""); - ooxml.append("" + bounds[3] + ""); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new To(this); - } - - // Access Methods - public int[] getBounds() { - if (this.bounds == null) this.bounds = new int[4]; - return bounds; - } - - public void setBounds(int[] bounds) { - this.bounds = new int[4]; - System.arraycopy(bounds, 0, this.bounds, 0, 4); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.kt b/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.kt new file mode 100644 index 0000000..2277199 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/TwoCellAnchor.kt @@ -0,0 +1,630 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.ColHandle +import io.starter.OpenXLS.RowHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * twoCellAnchor (Two Cell Anchor Shape Size) + * + * + * This element specifies a two cell anchor placeholder for a group, a shape, or a drawing element. + * It moves with cells and its extents are in EMU units. + * + * + * This is the root element for charts, images and shapes. + * + * + * parent: wsDr + * children: from, to, OBJECTCHOICES (sp, grpSp, graphicFrame, cxnSp, pic), clientData + */ +//TODO: finish grpSp Group Shape +// TODO: finish clientData element +class TwoCellAnchor : OOXMLElement { + /** + * return the editAs editMovement attribute + * + * @return + */ + /** + * set the editAs editMovement attribute + * + * @return + */ + var editAs: String? = null + /** + * return the Embedded Object's filename as saved on disk + * + * @return + */ + /** + * set the Embedded Object's filename as saved on disk + * + * @param embed + */ + var embedFilename: String? = null + private var from: From? = null + private var to: To? = null + private var o: ObjectChoice? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (from != null) ooxml.append(from!!.ooxml) + if (to != null) ooxml.append(to!!.ooxml) + ooxml.append(o!!.ooxml) + ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + + // access methods ****** + + /** + * return the (to, from) bounds of this object + * by concatenating the bounds for the to and the from + * bounds represent: COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, ROWOFFSET1 + * + * @return bounds short[8] from [4] and to [4] + */ + /** + * set the (to, from) bounds of this object + * bounds represent: COL, COLOFFSET, ROW, ROWOFFST, COL1, COLOFFSET1, ROW1, ROWOFFSET1 + * NOTE: COL + * + * @param bounds int[8] from [4] and to [4] + */ + // from bounds + // to bounds + var bounds: IntArray + get() { + val bounds = IntArray(8) + System.arraycopy(from!!.getBounds(), 0, bounds, 0, 4) + System.arraycopy(to!!.getBounds(), 0, bounds, 4, 4) + return bounds + } + set(bounds) { + val b = IntArray(4) + System.arraycopy(bounds, 0, b, 0, 4) + if (from == null) + from = From(b) + else + from!!.setBounds(b) + System.arraycopy(bounds, 4, b, 0, 4) + if (to == null) + to = To(b) + else + to!!.setBounds(b) + } + + /** + * get cNvPr name attribute + * + * @return + */ + /** + * set cNvPr name attribute + * + * @param name + */ + var name: String? + get() = if (o != null) o!!.name else null + set(name) { + if (o != null) + o!!.name = name + } + + /** + * get cNvPr descr attribute + * + * @return + */ + /** + * set cNvPr descr attribute + * sometimes associated with shape name + * + * @param descr + */ + var descr: String? + get() = if (o != null) o!!.descr else null + set(descr) { + if (o != null) + o!!.descr = descr + } + + /** + * get macro attribute + * + * @return + */ + /** + * set Macro attribute + * sometimes associated with shape name + * + * @param descr + */ + var macro: String? + get() = if (o != null) o!!.macro else null + set(macro) { + if (o != null) + o!!.macro = macro + } + + /** + * get the URI associated with this graphic Data + */ + /** + * set the URI associated with this graphic data + * + * @param uri + */ + var uri: String? + get() = if (o != null) o!!.uri else null + set(uri) { + if (o != null) + o!!.uri = uri + } + + /** + * return the rid for the chart defined by this twocellanchor + * + * @return + */ + var chartRId: String? + get() = if (o != null) o!!.chartRId else null + set(rId) { + if (o != null) o!!.chartRId = rId + } + + /** + * return the id for the embedded picture or shape (i.e. resides within the file) + * + * @return + */ + /** + * set the embed for the embedded picture or shape (i.e. resides within the file) + * + * @param embed + */ + var embed: String? + get() = if (o != null) o!!.embed else null + set(embed) { + if (o != null) o!!.embed = embed + } + + + /** + * return the id for the linked object (i.e. doesn't reside in the file) + * + * @return + */ + /** + * set the link attribute for this blip (the id for the linked picture) + * + * @param embed + */ + var link: String? + get() = if (o != null) o!!.link else null + set(link) { + if (o != null) o!!.link = link + } + + /** + * utility to return the shape properties element (picture element only) + * should be depreciated when OOXML is completely distinct from BIFF8 + * + * @return + */ + val sppr: SpPr? + get() = if (o != null) o!!.sppr else null + + constructor(editAs: String) { + this.editAs = editAs + } + + constructor(editAs: String, f: From, t: To, o: ObjectChoice) { + this.editAs = editAs + this.from = f + this.to = t + this.o = o + } + + constructor(tce: TwoCellAnchor) { + this.editAs = tce.editAs + this.from = tce.from + this.to = tce.to + this.o = tce.o + } + + override fun cloneElement(): OOXMLElement { + return TwoCellAnchor(this) + } + + override fun toString(): String? { + return this.name + } + + /** + * return if this twoCellAnchor element refers to an image rather than a chart or shape + * + * @return + */ + fun hasImage(): Boolean { + return o!!.hasImage() + } + + /** + * return if this twoCellAnchor element refers to a chart as opposed to a shape or image + * + * @return + */ + fun hasChart(): Boolean { + return if (o != null) o!!.hasChart() else false + } + + /** + * return if this twoCellAnchor element refers to a shape, as opposed a chart or an image + * + * @return + */ + fun hasShape(): Boolean { + return if (o != null) o!!.hasShape() else false + } + + /** + * set this twoCellAnchor as a chart element + * used for + * + * @param rid + * @param name + * @param bounds + */ + fun setAsChart(rid: Int, name: String, bounds: IntArray) { + o = ObjectChoice() + o!!.`object` = GraphicFrame() + o!!.name = name + o!!.chartRId = "rId" + Integer.valueOf(rid)!!.toString() + o!!.id = rid + this.bounds = bounds + // id??? + } + + /** + * set this twoCellAnchor as an image + * + * @param rid + * @param name + * @param id + */ + fun setAsImage(rid: Int, name: String, descr: String, spid: Int, sp: SpPr?) { + o = ObjectChoice() + o!!.`object` = Pic() + o!!.name = name + o!!.descr = descr + o!!.embed = "rId" + Integer.valueOf(rid)!!.toString() + o!!.id = spid + if (sp != null) { + (o!!.`object` as Pic).sppr = sp + } + } + + companion object { + + private val serialVersionUID = 4180396678197959710L + // EMU = pixel * 914400 / Resolution (96?) + val EMU: Short = 1270 + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var editAs: String? = null + var f: From? = null + var t: To? = null + var o: ObjectChoice? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "twoCellAnchor") { // get attributes + if (xpp.attributeCount > 0) + editAs = xpp.getAttributeValue(0) + } else if (tnm == "from") { + lastTag.push(tnm) + f = From.parseOOXML(xpp, lastTag) + } else if (tnm == "to") { + lastTag.push(tnm) + t = To.parseOOXML(xpp, lastTag) + } else if (tnm == "cxnSp" || + tnm == "graphicFrame" || + tnm == "grpSp" || + tnm == "pic" || + tnm == "sp") { + o = ObjectChoice.parseOOXML(xpp, lastTag, bk) as ObjectChoice + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "twoCellAnchor") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("twoCellAnchor.parseOOXML: $e") + } + + return TwoCellAnchor(editAs, f, t, o) + } + + + /** + * given bounds[8] in BIFF 8 coordinates and convert to OOXML units + * mostly this means adjusting ROWOFFSET 0 & 1 (bounds[5] & bounds[7] + * and COLOFFSET 0 & 1 (bounds[1] & bounds[5] to EMU units + + * adjust according to emperical calc garnered from observation + * + * @param sheet + * @param bbounds short[] bounds in BIFF8 units + * @return new bounds int[] bounds in OOXML units + */ + fun convertBoundsFromBIFF8(sheet: io.starter.formats.XLS.Boundsheet, bbounds: ShortArray): IntArray { + // note on bounds: + // -- offsets (bounds 1,3,5 & 7) are %'s of column width or row height, respectively + // below calculations are garnered from OOXML info + by comparing to Excel's values ... may not be 100% but appears to work for both 2003 and 2007 versions + // NOTE: if change offset algorithm here must modify algorithm in twoCellAnchor from and to classes + val bounds = IntArray(8) + bounds[0] = bbounds[0].toInt() + var cw = ColHandle.getWidth(sheet, bbounds[0].toInt()).toDouble() ///5.0; + // below is more correct (???) bounds[1]= (int)(EMU*cw*(bbounds[1]/1024.0)); + bounds[1] = (cw * 256.0 * (bbounds[1] / 1024.0)).toInt() + var rh = RowHandle.getHeight(sheet, bbounds[2].toInt()).toDouble() ///2.0; + bounds[2] = bbounds[2].toInt() + bounds[3] = (EMU.toDouble() * rh * (bbounds[3] / 256.0)).toInt() + cw = ColHandle.getWidth(sheet, bbounds[4].toInt()).toDouble() ///5.0; + bounds[4] = bbounds[4].toInt() + // bounds[5]= (int)(EMU*cw*(bbounds[5]/1024.0)); + bounds[5] = (cw * 256.0 * (bbounds[5] / 1024.0)).toInt() + rh = RowHandle.getHeight(sheet, bbounds[6].toInt()).toDouble() ///2.0; + bounds[6] = bbounds[6].toInt() + bounds[7] = (EMU.toDouble() * rh * (bbounds[7] / 256.0)).toInt() + return bounds + } + + /** + * convert bounds[8] from OOXML to BIFF8 Units + * basically must adjust COLOFFSETs and ROWOFFSETs to BIFF8 units + * + * @param sheet + * @param bounds int[] + * @return bounds short[] (BIFF8 uses short[] + different units) + */ + fun convertBoundsToBIFF8(sheet: io.starter.formats.XLS.Boundsheet, bounds: IntArray): ShortArray { + val bbounds = ShortArray(8) + bbounds[0] = bounds[0].toShort() + var cw = ColHandle.getWidth(sheet, bounds[0]).toDouble() ///5.0; + // below is more correct (???) bbounds[1]= (short)((bounds[1]*1024)/(TwoCellAnchor.EMU*cw)); + bbounds[1] = (bounds[1] * 1024.0 / (cw * 256)).toShort() + bbounds[2] = bounds[2].toShort() + var rh = RowHandle.getHeight(sheet, bounds[2]).toDouble() ///2.0; + bbounds[3] = (bounds[3] * 256.0 / (TwoCellAnchor.EMU * rh)).toShort() + bbounds[4] = bounds[4].toShort() + cw = ColHandle.getWidth(sheet, bounds[4]).toDouble() ///5.0; + // bbounds[5]= (short)((bounds[5]*1024)/(TwoCellAnchor.EMU*cw)); + bbounds[5] = (bounds[5] * 1024.0 / (cw * 256)).toShort() + bbounds[6] = bounds[6].toShort() + rh = RowHandle.getHeight(sheet, bounds[6]).toDouble() ///2.0; + bbounds[7] = (bounds[7] * 256.0 / (TwoCellAnchor.EMU * rh)).toShort() + return bbounds + } + } +} + +/** + * from (Starting Anchor Point) + * This element specifies the first anchor point for the drawing element. This will be used to anchor the top and left + * sides of the shape within the spreadsheet. That is when the cell that is specified in the from element is adjusted, + * the shape will also be adjusted. + * + * + * NOTE: Coordinates are in OOXML units; they are converted to BIFF8 units using twoCellAnchor.convertBoundsToBIFF8 + * parent: oneCellAnchor, twoCellAnchor + * children: col, colOff, row, rowOff + */ +internal class From : OOXMLElement { + private var bounds: IntArray? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append("" + bounds!![0] + "") + ooxml.append("" + bounds!![1] + "") + ooxml.append("" + bounds!![2] + "") + ooxml.append("" + bounds!![3] + "") + ooxml.append("") + return ooxml.toString() + } + + + constructor(bounds: IntArray) { + this.bounds = IntArray(4) + System.arraycopy(bounds, 0, this.bounds!!, 0, 4) + } + + constructor(f: From) { + this.bounds = f.bounds + } + + override fun cloneElement(): OOXMLElement { + return From(this) + } + + fun getBounds(): IntArray { + if (bounds == null) bounds = IntArray(4) + return bounds + } + + fun setBounds(bounds: IntArray) { + System.arraycopy(bounds, 0, this.bounds!!, 0, 4) + } + + companion object { + + private val serialVersionUID = -4776435343244555855L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack<*>): From { + val bounds = IntArray(4) + var boundsidx = 0 + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "col") { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()) + } else if (tnm == "colOff") { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()) + } else if (tnm == "row") { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()) + } else if (tnm == "rowOff") { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "from") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("from.parseOOXML: $e") + } + + return From(bounds) + } + } +} + +/** + * to (Ending Anchor Point) + * This element specifies the second anchor point for the drawing element. This will be used to anchor the bottom + * and right sides of the shape within the spreadsheet. That is when the cell that is specified in the to element is + * adjusted, the shape will also be adjusted.to (Ending Anchor Point) + * This element specifies the second anchor point for the drawing element. This will be used to anchor the bottom + * and right sides of the shape within the spreadsheet. That is when the cell that is specified in the to element is + * adjusted, the shape will also be adjusted. + * + * + * NOTE: Coordinates are in OOXML units; they are converted to BIFF8 units using twoCellAnchor.convertBoundsToBIFF8 + * parent: twoCellAnchor + * children: col, colOff, row, rowOff + */ +internal class To : OOXMLElement { + private var bounds: IntArray? = null + + override val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append("" + bounds!![0] + "") + ooxml.append("" + bounds!![1] + "") + ooxml.append("" + bounds!![2] + "") + ooxml.append("" + bounds!![3] + "") + ooxml.append("") + return ooxml.toString() + } + + constructor(bounds: IntArray) { + this.bounds = IntArray(4) + System.arraycopy(bounds, 0, this.bounds!!, 0, 4) + } + + constructor(f: To) { + this.bounds = f.bounds + } + + override fun cloneElement(): OOXMLElement { + return To(this) + } + + // Access Methods + fun getBounds(): IntArray { + if (this.bounds == null) this.bounds = IntArray(4) + return bounds + } + + fun setBounds(bounds: IntArray) { + this.bounds = IntArray(4) + System.arraycopy(bounds, 0, this.bounds!!, 0, 4) + } + + companion object { + + private val serialVersionUID = 1500243445505400113L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack<*>): To { + val bounds = IntArray(4) + var boundsidx = 0 + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "col") { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()) + } else if (tnm == "colOff") { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()) + } else if (tnm == "row") { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()) + } else if (tnm == "rowOff") { + bounds[boundsidx++] = Integer.parseInt(xpp.nextText()) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "to") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("to.parseOOXML: $e") + } + + return To(bounds) + } + } +} + diff --git a/src/main/java/io/starter/formats/OOXML/TxBody.java b/src/main/java/io/starter/formats/OOXML/TxBody.java deleted file mode 100644 index e695bfc..0000000 --- a/src/main/java/io/starter/formats/OOXML/TxBody.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * txBody (Shape Text Body) - *

                  - * OOXML/DrawingML element specifies the existence of text to be contained within the corresponding shape. All visible text and - * visible text related properties are contained within this element. There can be multiple paragraphs and within - * paragraphs multiple runs of text - *

                  - * parent: sp (shape) - * children: bodyPr REQ, lstStyle, p REQ - */ -// TODO: handle lstStyle Text List Styles -public class TxBody implements OOXMLElement { - - private static final long serialVersionUID = 2407194628070113668L; - private BodyPr bPr; - private P para; - - public TxBody(BodyPr b, P para) { - this.bPr = b; - this.para = para; - } - - public TxBody(TxBody tbd) { - this.bPr = tbd.bPr; - this.para = tbd.para; - } - - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - BodyPr b = null; - P para = null; - try { // need: endParaRPr? - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bodyPr")) { // default text properties - lastTag.push(tnm); - b = (BodyPr) BodyPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("p")) { // part of p element - lastTag.push(tnm); - para = (P) P.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("txBody")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("txBody.parseOOXML: " + e.toString()); - } - TxBody tBd = new TxBody(b, para); - return tBd; - } - - public String getOOXML() { - StringBuffer tooxml = new StringBuffer(); - tooxml.append(""); - if (bPr != null) tooxml.append(bPr.getOOXML()); - tooxml.append(""); // TODO: HANDLE - if (para != null) tooxml.append(para.getOOXML()); - tooxml.append(""); - return tooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TxBody(this); - } -} diff --git a/src/main/java/io/starter/formats/OOXML/TxBody.kt b/src/main/java/io/starter/formats/OOXML/TxBody.kt new file mode 100644 index 0000000..70d297a --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/TxBody.kt @@ -0,0 +1,110 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * txBody (Shape Text Body) + * + * + * OOXML/DrawingML element specifies the existence of text to be contained within the corresponding shape. All visible text and + * visible text related properties are contained within this element. There can be multiple paragraphs and within + * paragraphs multiple runs of text + * + * + * parent: sp (shape) + * children: bodyPr REQ, lstStyle, p REQ + */ +// TODO: handle lstStyle Text List Styles +class TxBody : OOXMLElement { + private var bPr: BodyPr? = null + private var para: P? = null + + override// TODO: HANDLE + val ooxml: String + get() { + val tooxml = StringBuffer() + tooxml.append("") + if (bPr != null) tooxml.append(bPr!!.ooxml) + tooxml.append("") + if (para != null) tooxml.append(para!!.ooxml) + tooxml.append("") + return tooxml.toString() + } + + constructor(b: BodyPr, para: P) { + this.bPr = b + this.para = para + } + + constructor(tbd: TxBody) { + this.bPr = tbd.bPr + this.para = tbd.para + } + + override fun cloneElement(): OOXMLElement { + return TxBody(this) + } + + companion object { + + private val serialVersionUID = 2407194628070113668L + + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var b: BodyPr? = null + var para: P? = null + try { // need: endParaRPr? + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "bodyPr") { // default text properties + lastTag.push(tnm) + b = BodyPr.parseOOXML(xpp, lastTag) as BodyPr + } else if (tnm == "p") { // part of p element + lastTag.push(tnm) + para = P.parseOOXML(xpp, lastTag, bk) as P + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "txBody") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("txBody.parseOOXML: $e") + } + + return TxBody(b, para) + } + } +} diff --git a/src/main/java/io/starter/formats/OOXML/TxPr.java b/src/main/java/io/starter/formats/OOXML/TxPr.java deleted file mode 100644 index 327b859..0000000 --- a/src/main/java/io/starter/formats/OOXML/TxPr.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.FormatConstants; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - - -/** - * txpr (Text Properties) - *

                  - * OOXML/DrawingML element specifies text formatting. The lstStyle element is not supported - *

                  - * parent: axes, title, labels .. - * children: bodyPr, lstStyle, p - */ -// TODO: Handle lstStyle -public class TxPr implements OOXMLElement { - - private static final long serialVersionUID = -4293247897525807479L; - // TODO: handle lstStyle Text List Styles - private BodyPr bPr; - private P para; - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - BodyPr b = null; - P para = null; - try { // need: endParaRPr? - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("bodyPr")) { // default text properties - lastTag.push(tnm); - b = (BodyPr) BodyPr.parseOOXML(xpp, lastTag); - } else if (tnm.equals("p")) { // part of p element - lastTag.push(tnm); - para = (P) P.parseOOXML(xpp, lastTag, bk); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("txPr")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("txPr.parseOOXML: " + e.toString()); - } - TxPr tPr = new TxPr(b, para); - return tPr; - } - - public TxPr(BodyPr b, P para) { - this.bPr = b; - this.para = para; - } - - public TxPr(TxPr tpr) { - this.bPr = tpr.bPr; - this.para = tpr.para; - } - - public String getOOXML() { - StringBuffer tooxml = new StringBuffer(); - tooxml.append(""); - if (bPr != null) tooxml.append(bPr.getOOXML()); - tooxml.append(""); // TODO: HANDLE - if (para != null) tooxml.append(para.getOOXML()); - tooxml.append(""); - return tooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new TxPr(this); - } - - /** - * specify text formatting properties - * - * @param fontFace font face - * @param sz size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) - * @param b true if bold - * @param i true if italic - * @param u underline: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted - * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) - * @param strike one of: dblStrike, noStrike or sngStrike or null if none - * @param clr fill color in hex form without the # - */ - public TxPr(String fontFace, int sz, boolean b, boolean i, String u, String strike, String clr) { - this.para = new P(fontFace, sz, b, i, u, strike, clr); - } - - public TxPr(Font fx, int hrot, String vrot) { - /* - * Specifies the rotation that is being applied to the text within the bounding box. If it not -specified, the rotation of the accompanying shape is used. If it is specified, then this is -applied independently from the shape. That is the shape can have a rotation applied in -addition to the text itself having a rotation applied to it. If this attribute is omitted, then a -value of 0, is implied. - -represents an angle in 60,000ths of a degree -5400000=90 degrees --5400000=90 degrees counter-clockwise - */ - int u = fx.getUnderlineStyle(); - String usty = "none"; - switch (u) { - case FormatConstants.STYLE_UNDERLINE_SINGLE: - usty = "sng"; - break; - case FormatConstants.STYLE_UNDERLINE_DOUBLE: - usty = "dbl"; - break; - case FormatConstants.STYLE_UNDERLINE_SINGLE_ACCTG: - usty = "sng"; - break; - case FormatConstants.STYLE_UNDERLINE_DOUBLE_ACCTG: - usty = "dbl"; - break; - } - String strike = (fx.getStricken() ? "sngStrike" : "noStrike"); - bPr = new BodyPr(hrot, "horz"); - this.para = new P(fx.getFontName(), (int) fx.getFontHeightInPoints() * 100, fx.getBold(), fx.getItalic(), usty, strike, - fx.getColorAsOOXMLRBG()); - /* - * - * - * - * - */ - } -} diff --git a/src/main/java/io/starter/formats/OOXML/TxPr.kt b/src/main/java/io/starter/formats/OOXML/TxPr.kt new file mode 100644 index 0000000..dd02382 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/TxPr.kt @@ -0,0 +1,159 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Font +import io.starter.formats.XLS.FormatConstants +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + + +/** + * txpr (Text Properties) + * + * + * OOXML/DrawingML element specifies text formatting. The lstStyle element is not supported + * + * + * parent: axes, title, labels .. + * children: bodyPr, lstStyle, p + */ +// TODO: Handle lstStyle +class TxPr : OOXMLElement { + // TODO: handle lstStyle Text List Styles + private var bPr: BodyPr? = null + private var para: P? = null + + override// TODO: HANDLE + val ooxml: String + get() { + val tooxml = StringBuffer() + tooxml.append("") + if (bPr != null) tooxml.append(bPr!!.ooxml) + tooxml.append("") + if (para != null) tooxml.append(para!!.ooxml) + tooxml.append("") + return tooxml.toString() + } + + constructor(b: BodyPr, para: P) { + this.bPr = b + this.para = para + } + + constructor(tpr: TxPr) { + this.bPr = tpr.bPr + this.para = tpr.para + } + + override fun cloneElement(): OOXMLElement { + return TxPr(this) + } + + /** + * specify text formatting properties + * + * @param fontFace font face + * @param sz size in 100 pts (e.g. font size 12.5 pts,, sz= 1250) + * @param b true if bold + * @param i true if italic + * @param u underline: dash, dashHeavy, dashLong, dashLongHeavy, dbl, dotDash, dotDashHeavy, dotDotDash, dotDotDashHeavy, dotted + * dottedHeavy, heavy, none, sng, wavy, wavyDbl, wavyHeavy, words (underline only words not spaces) + * @param strike one of: dblStrike, noStrike or sngStrike or null if none + * @param clr fill color in hex form without the # + */ + constructor(fontFace: String, sz: Int, b: Boolean, i: Boolean, u: String, strike: String, clr: String) { + this.para = P(fontFace, sz, b, i, u, strike, clr) + } + + constructor(fx: Font, hrot: Int, vrot: String) { + /* + * Specifies the rotation that is being applied to the text within the bounding box. If it not +specified, the rotation of the accompanying shape is used. If it is specified, then this is +applied independently from the shape. That is the shape can have a rotation applied in +addition to the text itself having a rotation applied to it. If this attribute is omitted, then a +value of 0, is implied. + +represents an angle in 60,000ths of a degree +5400000=90 degrees +-5400000=90 degrees counter-clockwise + */ + val u = fx.underlineStyle + var usty = "none" + when (u) { + FormatConstants.STYLE_UNDERLINE_SINGLE -> usty = "sng" + FormatConstants.STYLE_UNDERLINE_DOUBLE -> usty = "dbl" + FormatConstants.STYLE_UNDERLINE_SINGLE_ACCTG -> usty = "sng" + FormatConstants.STYLE_UNDERLINE_DOUBLE_ACCTG -> usty = "dbl" + } + val strike = if (fx.stricken) "sngStrike" else "noStrike" + bPr = BodyPr(hrot, "horz") + this.para = P(fx.fontName, fx.fontHeightInPoints.toInt() * 100, fx.bold, fx.italic, usty, strike, + fx.colorAsOOXMLRBG) + /* + * + * + * + * + */ + } + + companion object { + + private val serialVersionUID = -4293247897525807479L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle): OOXMLElement { + var b: BodyPr? = null + var para: P? = null + try { // need: endParaRPr? + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "bodyPr") { // default text properties + lastTag.push(tnm) + b = BodyPr.parseOOXML(xpp, lastTag) as BodyPr + } else if (tnm == "p") { // part of p element + lastTag.push(tnm) + para = P.parseOOXML(xpp, lastTag, bk) as P + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "txPr") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("txPr.parseOOXML: $e") + } + + return TxPr(b, para) + } + } +} diff --git a/src/main/java/io/starter/formats/OOXML/Xfrm.java b/src/main/java/io/starter/formats/OOXML/Xfrm.java deleted file mode 100644 index 14e4239..0000000 --- a/src/main/java/io/starter/formats/OOXML/Xfrm.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.OOXML; - -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Stack; - -class Xfrm implements OOXMLElement { - - private static final long serialVersionUID = 5383438744617393878L; - private HashMap attrs = null; - private Off o = null; - private Ext e = null; - private String ns = null; - - public Xfrm() { - this.ns = "xdr"; // set default - } - - public Xfrm(HashMap attrs, Off o, Ext e, String ns) { - this.attrs = attrs; - this.o = o; - this.e = e; - this.ns = ns; - } - - public Xfrm(Xfrm x) { - this.attrs = x.attrs; - this.o = x.o; - this.e = x.e; - this.ns = x.ns; - } - - /** - * set the namespace for xfrm element - * xdr (graphicFrame) or a(spPr) - * - * @param ns - */ - public void setNS(String ns) { - this.ns = ns; - } - - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - Off o = null; - Ext e = null; - String ns = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("xfrm")) { // get attributes - ns = xpp.getPrefix(); - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } else if (tnm.equals("off")) { - lastTag.push(tnm); - o = Off.parseOOXML(xpp, lastTag); - //o.setNS("a"); - } else if (tnm.equals("ext")) { - lastTag.push(tnm); - e = (Ext) Ext.parseOOXML(xpp, lastTag); - //e.setNS("a"); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("xfrm")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception ex) { - Logger.logErr("xfrm.parseOOXML: " + ex.toString()); - } - Xfrm x = new Xfrm(attrs, o, e, ns); - return x; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append("<" + this.ns + ":xfrm"); - // attributes - if (attrs != null) { - Iterator i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - } - ooxml.append(">"); - if (o != null) ooxml.append(o.getOOXML()); - if (e != null) ooxml.append(e.getOOXML()); - ooxml.append(""); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Xfrm(this); - } - -} - -class Off implements OOXMLElement { - - private static final long serialVersionUID = -7624630398053353694L; - private HashMap attrs = null; - private String ns = ""; - - public Off() { - attrs = new HashMap(); - attrs.put("x", "0"); - attrs.put("y", "0"); - } - - public Off(HashMap attrs, String ns) { - this.attrs = attrs; - this.ns = ns; - } - - public Off(Off o) { - this.attrs = o.attrs; - this.ns = o.ns; - } - - public void setNS(String ns) { - this.ns = ns; - } - - public static Off parseOOXML(XmlPullParser xpp, Stack lastTag) { - HashMap attrs = new HashMap(); - String ns = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("off")) { // get attributes - ns = xpp.getPrefix(); - for (int i = 0; i < xpp.getAttributeCount(); i++) { - attrs.put(xpp.getAttributeName(i), xpp.getAttributeValue(i)); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("off")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("off.parseOOXML: " + e.toString()); - } - Off o = new Off(attrs, ns); - return o; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append("<" + this.ns + ":off"); - // attributes - Iterator i = attrs.keySet().iterator(); - while (i.hasNext()) { - String key = i.next(); - String val = attrs.get(key); - ooxml.append(" " + key + "=\"" + val + "\""); - } - ooxml.append("/>"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Off(this); - } -} - diff --git a/src/main/java/io/starter/formats/OOXML/Xfrm.kt b/src/main/java/io/starter/formats/OOXML/Xfrm.kt new file mode 100644 index 0000000..60bda20 --- /dev/null +++ b/src/main/java/io/starter/formats/OOXML/Xfrm.kt @@ -0,0 +1,214 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.OOXML + +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.HashMap +import java.util.Stack + +internal class Xfrm : OOXMLElement { + private var attrs: HashMap? = null + private var o: Off? = null + private var e: Ext? = null + private var ns: String? = null + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("<" + this.ns + ":xfrm") + if (attrs != null) { + val i = attrs!!.keys.iterator() + while (i.hasNext()) { + val key = i.next() + val `val` = attrs!![key] + ooxml.append(" $key=\"$`val`\"") + } + } + ooxml.append(">") + if (o != null) ooxml.append(o!!.ooxml) + if (e != null) ooxml.append(e!!.ooxml) + ooxml.append("") + return ooxml.toString() + } + + constructor() { + this.ns = "xdr" // set default + } + + constructor(attrs: HashMap, o: Off, e: Ext, ns: String) { + this.attrs = attrs + this.o = o + this.e = e + this.ns = ns + } + + constructor(x: Xfrm) { + this.attrs = x.attrs + this.o = x.o + this.e = x.e + this.ns = x.ns + } + + /** + * set the namespace for xfrm element + * xdr (graphicFrame) or a(spPr) + * + * @param ns + */ + fun setNS(ns: String) { + this.ns = ns + } + + override fun cloneElement(): OOXMLElement { + return Xfrm(this) + } + + companion object { + + private val serialVersionUID = 5383438744617393878L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): OOXMLElement { + val attrs = HashMap() + var o: Off? = null + var e: Ext? = null + var ns: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "xfrm") { // get attributes + ns = xpp.prefix + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } else if (tnm == "off") { + lastTag.push(tnm) + o = Off.parseOOXML(xpp, lastTag) + //o.setNS("a"); + } else if (tnm == "ext") { + lastTag.push(tnm) + e = Ext.parseOOXML(xpp, lastTag) as Ext + //e.setNS("a"); + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "xfrm") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (ex: Exception) { + Logger.logErr("xfrm.parseOOXML: $ex") + } + + return Xfrm(attrs, o, e, ns) + } + } + +} + +internal class Off : OOXMLElement { + private var attrs: HashMap? = null + private var ns = "" + + override// attributes + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("<" + this.ns + ":off") + val i = attrs!!.keys.iterator() + while (i.hasNext()) { + val key = i.next() + val `val` = attrs!![key] + ooxml.append(" $key=\"$`val`\"") + } + ooxml.append("/>") + return ooxml.toString() + } + + constructor() { + attrs = HashMap() + attrs!!["x"] = "0" + attrs!!["y"] = "0" + } + + constructor(attrs: HashMap, ns: String) { + this.attrs = attrs + this.ns = ns + } + + constructor(o: Off) { + this.attrs = o.attrs + this.ns = o.ns + } + + fun setNS(ns: String) { + this.ns = ns + } + + override fun cloneElement(): OOXMLElement { + return Off(this) + } + + companion object { + + private val serialVersionUID = -7624630398053353694L + + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack): Off { + val attrs = HashMap() + var ns: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "off") { // get attributes + ns = xpp.prefix + for (i in 0 until xpp.attributeCount) { + attrs[xpp.getAttributeName(i)] = xpp.getAttributeValue(i) + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "off") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("off.parseOOXML: $e") + } + + return Off(attrs, ns) + } + } +} + diff --git a/src/main/java/io/starter/formats/XLS/Array.java b/src/main/java/io/starter/formats/XLS/Array.java deleted file mode 100644 index 3284fa7..0000000 --- a/src/main/java/io/starter/formats/XLS/Array.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.Stack; - - -/** - * The Array class describes a formula that was Array-entered into a range of cells. - *

                  - * The range of Cells in which the array is entered is defined by the rwFirst, last, colFirst and last fields. - *

                  - * The Array record occurs directly after the Formula record for the cell in the upper-left corner of the array -- that is, the cell - * defined by the rwFirst and colFirst fields. - *

                  - * The parsed expression is the array formula -- consisting of Ptgs. - *

                  - * You should ignore the chn field when you read the file, it must be 0 if written. - *

                  - *

                  - * OFFSET NAME SIZE CONTENTS - * ----- - * 4 rwFirst 2 FirstRow of the array - * 6 fwLast 2 Last Row of the array - * 8 colFirst 1 First Column of the array - * 9 colLast 1 Last Column of the array - * 10 grbit 2 Option Flags - * 12 chn 4 set to 0, ignore - * 16 cce 2 Length of the parsed expression - * 18 rgce var Parsed Expression - *

                  - * grbit fields - * bit 1 = fAlwaysCalc - always calc the formula - * bit 2 = fCalcOnLoad - calc formula on load - */ -public final class Array extends io.starter.formats.XLS.XLSRecord { - - private static final long serialVersionUID = -7316545663448065447L; - private short rwFirst; - private short rwLast; - private short colFirst; - private short colLast; - private short cce; - private short grbit; - private byte[] rgce; - private Formula parentRec = null; - - public int getFirstRow() { - return (int) rwFirst; - } - - public int getLastRow() { - return (int) rwLast; - } - - public int getFirstCol() { - return (int) colFirst; - } - - public int getLastCol() { - return (int) colLast; - } - - /* - * For getRow() and getCol() we are going to return the upper-right hand - * location of the sharedformula. This should be the same location referenced - * in the PTGExp associated with these formulas as well - */ - public int getRowNumber() { - return getFirstRow(); - } - - public int getCol() { - return getFirstCol(); - } - - public void setFirstRow(int i) { - rwFirst = (short) i; - } - - public void setLastRow(int i) { - rwLast = (short) i; - } - - public void setFirstCol(int i) { - colFirst = (byte) i; - } - - public void setLastCol(int i) { - colLast = (byte) i; - } - - private Stack expression; - - public void init() { - super.init(); - this.setOpcode(ARRAY); -// NO NOT TRUE!!!!! super.setIsValueForCell(true); // this ensures that it gets picked up by the Formula... -// will get "picked up" by formula due to WorkBook.addRecord code -// isValueForCell will DELETE THE CELL if remove/change NOT WHAT WE WANT HERE - rwFirst = (short) io.starter.toolkit.ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), (byte) 0, (byte) 0); - rwLast = (short) io.starter.toolkit.ByteTools.readInt(this.getByteAt(2), this.getByteAt(3), (byte) 0, (byte) 0); - - colFirst = (short) ByteTools.readUnsignedShort(this.getByteAt(4), (byte) 0); - colLast = (short) ByteTools.readUnsignedShort(this.getByteAt(5), (byte) 0); - grbit = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - cce = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - rgce = this.getBytesAt(14, this.cce); - - expression = ExpressionParser.parseExpression(rgce, this); - - // for PtgArray and PtgMemAreas, have Constant Array Tokens following expression - // this length is NOT included in cce - int posExtraData = cce + 14; - int len = this.getData().length; - for (int i = 0; i < expression.size(); i++) { - if (expression.get(i) instanceof PtgArray) { - try { - byte[] b = new byte[(len - posExtraData)]; - System.arraycopy(this.getData(), posExtraData, b, 0, len - posExtraData); - PtgArray pa = (PtgArray) expression.get(i); - posExtraData += pa.setArrVals(b); - } catch (Exception e) { - Logger.logErr("Array: error getting Constants " + e.getLocalizedMessage()); - } - } else if (expression.get(i) instanceof PtgMemArea) { - try { - PtgMemArea pm = (PtgMemArea) expression.get(i); - byte[] b = new byte[pm.getnTokens() + 8]; - System.arraycopy(pm.getRecord(), 0, b, 0, 7); - System.arraycopy(this.getData(), posExtraData, b, 7, pm.getnTokens()); - pm.init(b); - posExtraData += pm.getnTokens(); - } catch (Exception e) { - Logger.logErr("Array: error getting memarea constants " + e.toString()); - } - } - } - if (DEBUGLEVEL > DEBUG_LOW) - Logger.logInfo("Array encountered at: " + this.wkbook.getLastbound().getSheetName() + "!" + ExcelTools.getAlphaVal(colFirst) + (rwFirst + 1) + ":" + ExcelTools.getAlphaVal(colLast) + (rwLast + 1)); - } - - /** - * Associate this record with a worksheet. - * init array refs as well - */ - public void setSheet(Sheet b) { - this.worksheet = b; - // add to array formula references since this is the parent - if (expression != null) { // it's been initted - String loc = ExcelTools.formatLocation(new int[]{rwFirst, colFirst}); // this formula address == array formula references for OOXML usage - String ref = ExcelTools.formatRangeRowCol(new int[]{rwFirst, colFirst, rwLast, colLast}); - ((Boundsheet) b).addParentArrayRef(loc, ref); // formula address, array formula references OOXML usage - } - - } - - /** - * init Array record from formula string - * - * @param fmla - */ - public void init(String fmla, int rw, int col) { -// NO NOT TRUE!!!!! super.setIsValueForCell(true); // this ensures that it gets picked up by the Formula... -// will get "picked up" by formula due to WorkBook.addRecord code -// isValueForCell will DELETE THE CELL if remove/change NOT WHAT WE WANT HERE - // TODO: ever a case of rwFirst!=rwLast, colFirst!=colLast ????? - this.setOpcode(ARRAY); - rwFirst = (short) rw; - rwLast = (short) rw; - colFirst = (byte) col; - colLast = (byte) col; - grbit = 0x2; // calc on load = default 20090824 KSC [BugTracker 2683] - fmla = fmla.substring(1, fmla.length() - 1); // parse formula string and add stack to Array record - Stack newptgs = FormulaParser.getPtgsFromFormulaString(this, fmla); - expression = newptgs; - this.updateRecord(); - - } - - /* Update the record byte array with the modified ptg records - */ - public void updateRecord() { - // first, get expression length - cce = 0; // sum up expression (Ptg) records - for (int i = 0; i < expression.size(); i++) { - Ptg ptg = (Ptg) expression.elementAt(i); - cce += ptg.getRecord().length; - if (ptg instanceof PtgRef) { - ((PtgRef) ptg).setArrayTypeRef(); - } - } - - byte[] newdata = new byte[14 + cce]; // total record data (not including extra data, if any) - - int pos = 0; - // 20090824 KSC: [BugTracker 2683] - // use setOpcode rather than setting 1st byte as it's a record not a ptg newdata[0]= (byte) XLSConstants.ARRAY; pos++; - this.setOpcode(ARRAY); - System.arraycopy(ByteTools.shortToLEBytes(rwFirst), 0, newdata, pos, 2); - pos += 2; - System.arraycopy(ByteTools.shortToLEBytes(rwLast), 0, newdata, pos, 2); - pos += 2; - newdata[pos++] = (byte) colFirst; - newdata[pos++] = (byte) colLast; - System.arraycopy(ByteTools.shortToLEBytes(grbit), 0, newdata, pos, 2); // 20090824 KSC: Added [BugTracker 2683] - - pos = 12; - System.arraycopy(ByteTools.shortToLEBytes(cce), 0, newdata, pos, 2); - pos += 2; - - // expression - rgce = new byte[cce]; // expression record data - pos = 0; - byte[] arraybytes = new byte[0]; - for (int i = 0; i < expression.size(); i++) { - Ptg ptg = (Ptg) expression.elementAt(i); - // trap extra data after expression (not included in cce count) - if (ptg instanceof PtgArray) { - PtgArray pa = (PtgArray) ptg; - arraybytes = ByteTools.append(pa.getPostRecord(), arraybytes); - } else if (ptg instanceof PtgMemArea) { - PtgMemArea pm = (PtgMemArea) ptg; - arraybytes = ByteTools.append(pm.getPostRecord(), arraybytes); - } - byte[] b = ptg.getRecord(); - System.arraycopy(b, 0, rgce, pos, ptg.getLength()); - pos = pos + ptg.getLength(); - } - System.arraycopy(rgce, 0, newdata, 14, cce); - newdata = ByteTools.append(arraybytes, newdata); - this.setData(newdata); - } - - /** - * Returns the top left location of the array, used for identifying which array goes with what formula. - */ - public String getParentLocation() { - int[] in = new int[2]; - in[0] = colFirst; - in[1] = rwFirst; - String s = ExcelTools.formatLocation(in); - return s; - } - - /** - * Determines whether the address is part of the array Formula range - */ - boolean isInRange(String addr) { - return io.starter.OpenXLS.ExcelTools.isInRange(addr, rwFirst, rwLast, colFirst, colLast); - } - - - public Object getValue(PtgExp pxp) { - //try{ - return FormulaCalculator.calculateFormula(expression); - //}catch(FunctionNotSupportedException e){ - //Logger.logWarn("Array.getValue() failed: " + e); - //return null; - // } - } - - /** - * link this shared formula to it's parent formula - */ - public void setParentRec(Formula f) { - this.parentRec = f; - } - - /** - * return parent formula - * - * @return - */ - public Formula getParentRec() { - return parentRec; - } - - /** - * return the string representation of the array formula - */ - public String getFormulaString() { - String expressString = FormulaParser.getExpressionString(expression); - if (!"".equals(expressString)) - return expressString.substring(1); - return ""; - } - - /** - * allow access to expression - * - * @return - */ - public Stack getExpression() { - return expression; - } - - /** - * return the cells referenced by this array in string range form - * - * @return - */ - public String getArrayRefs() { - int[] rc = new int[2]; - rc[0] = rwFirst; - rc[1] = colFirst; - String rowcol1 = ExcelTools.formatLocation(rc); - rc[0] = rwLast; - rc[1] = colLast; - String rowcol2 = ExcelTools.formatLocation(rc); - return rowcol1 + ":" + rowcol2; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Array.kt b/src/main/java/io/starter/formats/XLS/Array.kt new file mode 100644 index 0000000..380f4c1 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Array.kt @@ -0,0 +1,329 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.formulas.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.Stack + + +/** + * The Array class describes a formula that was Array-entered into a range of cells. + * + * + * The range of Cells in which the array is entered is defined by the rwFirst, last, colFirst and last fields. + * + * + * The Array record occurs directly after the Formula record for the cell in the upper-left corner of the array -- that is, the cell + * defined by the rwFirst and colFirst fields. + * + * + * The parsed expression is the array formula -- consisting of Ptgs. + * + * + * You should ignore the chn field when you read the file, it must be 0 if written. + * + * + * + * + * OFFSET NAME SIZE CONTENTS + * ----- + * 4 rwFirst 2 FirstRow of the array + * 6 fwLast 2 Last Row of the array + * 8 colFirst 1 First Column of the array + * 9 colLast 1 Last Column of the array + * 10 grbit 2 Option Flags + * 12 chn 4 set to 0, ignore + * 16 cce 2 Length of the parsed expression + * 18 rgce var Parsed Expression + * + * + * grbit fields + * bit 1 = fAlwaysCalc - always calc the formula + * bit 2 = fCalcOnLoad - calc formula on load + */ +class Array : io.starter.formats.XLS.XLSRecord() { + private var rwFirst: Short = 0 + private var rwLast: Short = 0 + private var colFirst: Short = 0 + private var colLast: Short = 0 + private var cce: Short = 0 + private var grbit: Short = 0 + private var rgce: ByteArray? = null + /** + * return parent formula + * + * @return + */ + /** + * link this shared formula to it's parent formula + */ + var parentRec: Formula? = null + + var firstRow: Int + get() = rwFirst.toInt() + set(i) { + rwFirst = i.toShort() + } + + var lastRow: Int + get() = rwLast.toInt() + set(i) { + rwLast = i.toShort() + } + + var firstCol: Int + get() = colFirst.toInt() + set(i) { + colFirst = i.toByte().toShort() + } + + var lastCol: Int + get() = colLast.toInt() + set(i) { + colLast = i.toByte().toShort() + } + + /* + * For getRow() and getCol() we are going to return the upper-right hand + * location of the sharedformula. This should be the same location referenced + * in the PTGExp associated with these formulas as well + */ + override var rowNumber: Int + get() = firstRow + set + + val col: Int + get() = firstCol + + /** + * allow access to expression + * + * @return + */ + var expression: Stack<*>? = null + private set + + /** + * Returns the top left location of the array, used for identifying which array goes with what formula. + */ + val parentLocation: String + get() { + val `in` = IntArray(2) + `in`[0] = colFirst.toInt() + `in`[1] = rwFirst.toInt() + return ExcelTools.formatLocation(`in`) + } + + /** + * return the string representation of the array formula + */ + val formulaString: String + get() { + val expressString = FormulaParser.getExpressionString(expression!!) + return if ("" != expressString) expressString.substring(1) else "" + } + + /** + * return the cells referenced by this array in string range form + * + * @return + */ + val arrayRefs: String + get() { + val rc = IntArray(2) + rc[0] = rwFirst.toInt() + rc[1] = colFirst.toInt() + val rowcol1 = ExcelTools.formatLocation(rc) + rc[0] = rwLast.toInt() + rc[1] = colLast.toInt() + val rowcol2 = ExcelTools.formatLocation(rc) + return "$rowcol1:$rowcol2" + } + + override fun init() { + super.init() + this.opcode = XLSConstants.ARRAY + // NO NOT TRUE!!!!! super.setIsValueForCell(true); // this ensures that it gets picked up by the Formula... + // will get "picked up" by formula due to WorkBook.addRecord code + // isValueForCell will DELETE THE CELL if remove/change NOT WHAT WE WANT HERE + rwFirst = io.starter.toolkit.ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), 0.toByte(), 0.toByte()).toShort() + rwLast = io.starter.toolkit.ByteTools.readInt(this.getByteAt(2), this.getByteAt(3), 0.toByte(), 0.toByte()).toShort() + + colFirst = ByteTools.readUnsignedShort(this.getByteAt(4), 0.toByte()).toShort() + colLast = ByteTools.readUnsignedShort(this.getByteAt(5), 0.toByte()).toShort() + grbit = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + cce = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + rgce = this.getBytesAt(14, this.cce.toInt()) + + expression = ExpressionParser.parseExpression(rgce, this) + + // for PtgArray and PtgMemAreas, have Constant Array Tokens following expression + // this length is NOT included in cce + var posExtraData = cce + 14 + val len = this.getData()!!.size + for (i in expression!!.indices) { + if (expression!![i] is PtgArray) { + try { + val b = ByteArray(len - posExtraData) + System.arraycopy(this.getData()!!, posExtraData, b, 0, len - posExtraData) + val pa = expression!![i] as PtgArray + posExtraData += pa.setArrVals(b) + } catch (e: Exception) { + Logger.logErr("Array: error getting Constants " + e.localizedMessage) + } + + } else if (expression!![i] is PtgMemArea) { + try { + val pm = expression!![i] as PtgMemArea + val b = ByteArray(pm.getnTokens() + 8) + System.arraycopy(pm.record, 0, b, 0, 7) + System.arraycopy(this.getData()!!, posExtraData, b, 7, pm.getnTokens()) + pm.init(b) + posExtraData += pm.getnTokens() + } catch (e: Exception) { + Logger.logErr("Array: error getting memarea constants $e") + } + + } + } + if (DEBUGLEVEL > XLSConstants.DEBUG_LOW) + Logger.logInfo("Array encountered at: " + this.wkbook!!.lastbound!!.sheetName + "!" + ExcelTools.getAlphaVal(colFirst.toInt()) + (rwFirst + 1) + ":" + ExcelTools.getAlphaVal(colLast.toInt()) + (rwLast + 1)) + } + + /** + * Associate this record with a worksheet. + * init array refs as well + */ + override fun setSheet(b: Sheet?) { + this.worksheet = b + // add to array formula references since this is the parent + if (expression != null) { // it's been initted + val loc = ExcelTools.formatLocation(intArrayOf(rwFirst.toInt(), colFirst.toInt())) // this formula address == array formula references for OOXML usage + val ref = ExcelTools.formatRangeRowCol(intArrayOf(rwFirst.toInt(), colFirst.toInt(), rwLast.toInt(), colLast.toInt())) + (b as Boundsheet).addParentArrayRef(loc, ref) // formula address, array formula references OOXML usage + } + + } + + /** + * init Array record from formula string + * + * @param fmla + */ + fun init(fmla: String, rw: Int, col: Int) { + var fmla = fmla + // NO NOT TRUE!!!!! super.setIsValueForCell(true); // this ensures that it gets picked up by the Formula... + // will get "picked up" by formula due to WorkBook.addRecord code + // isValueForCell will DELETE THE CELL if remove/change NOT WHAT WE WANT HERE + // TODO: ever a case of rwFirst!=rwLast, colFirst!=colLast ????? + this.opcode = XLSConstants.ARRAY + rwFirst = rw.toShort() + rwLast = rw.toShort() + colFirst = col.toByte().toShort() + colLast = col.toByte().toShort() + grbit = 0x2 // calc on load = default 20090824 KSC [BugTracker 2683] + fmla = fmla.substring(1, fmla.length - 1) // parse formula string and add stack to Array record + val newptgs = FormulaParser.getPtgsFromFormulaString(this, fmla) + expression = newptgs + this.updateRecord() + + } + + /* Update the record byte array with the modified ptg records + */ + fun updateRecord() { + // first, get expression length + cce = 0 // sum up expression (Ptg) records + for (i in expression!!.indices) { + val ptg = expression!!.elementAt(i) as Ptg + cce += ptg.record.size.toShort() + if (ptg is PtgRef) { + ptg.setArrayTypeRef() + } + } + + var newdata = ByteArray(14 + cce) // total record data (not including extra data, if any) + + var pos = 0 + // 20090824 KSC: [BugTracker 2683] + // use setOpcode rather than setting 1st byte as it's a record not a ptg newdata[0]= (byte) XLSConstants.ARRAY; pos++; + this.opcode = XLSConstants.ARRAY + System.arraycopy(ByteTools.shortToLEBytes(rwFirst), 0, newdata, pos, 2) + pos += 2 + System.arraycopy(ByteTools.shortToLEBytes(rwLast), 0, newdata, pos, 2) + pos += 2 + newdata[pos++] = colFirst.toByte() + newdata[pos++] = colLast.toByte() + System.arraycopy(ByteTools.shortToLEBytes(grbit), 0, newdata, pos, 2) // 20090824 KSC: Added [BugTracker 2683] + + pos = 12 + System.arraycopy(ByteTools.shortToLEBytes(cce), 0, newdata, pos, 2) + pos += 2 + + // expression + rgce = ByteArray(cce) // expression record data + pos = 0 + var arraybytes = ByteArray(0) + for (i in expression!!.indices) { + val ptg = expression!!.elementAt(i) as Ptg + // trap extra data after expression (not included in cce count) + if (ptg is PtgArray) { + arraybytes = ByteTools.append(ptg.postRecord, arraybytes) + } else if (ptg is PtgMemArea) { + arraybytes = ByteTools.append(ptg.postRecord, arraybytes) + } + val b = ptg.record + System.arraycopy(b, 0, rgce!!, pos, ptg.length) + pos = pos + ptg.length + } + System.arraycopy(rgce!!, 0, newdata, 14, cce.toInt()) + newdata = ByteTools.append(arraybytes, newdata) + this.setData(newdata) + } + + /** + * Determines whether the address is part of the array Formula range + */ + internal fun isInRange(addr: String): Boolean { + return io.starter.OpenXLS.ExcelTools.isInRange(addr, rwFirst.toInt(), rwLast.toInt(), colFirst.toInt(), colLast.toInt()) + } + + + fun getValue(pxp: PtgExp): Any { + //try{ + return FormulaCalculator.calculateFormula(expression!!) + //}catch(FunctionNotSupportedException e){ + //Logger.logWarn("Array.getValue() failed: " + e); + //return null; + // } + } + + companion object { + + private val serialVersionUID = -7316545663448065447L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/AutoFilter.java b/src/main/java/io/starter/formats/XLS/AutoFilter.java deleted file mode 100644 index bc8aa03..0000000 --- a/src/main/java/io/starter/formats/XLS/AutoFilter.java +++ /dev/null @@ -1,1159 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - - -import io.starter.OpenXLS.ExcelTools; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; - - -/** - * Auto filter controls the auto-filter capabilities of Excel. It has numerous sub-records and references - *

                  - * p>

                  - * offset  name        size    contents
                  - * ---
                  - * 4       iEntry          2       Index of the active autofilter
                  - * 6       grbit           2       Option flags
                  - * 8       doper1          10      DOPER struct for the first filter condition
                  - * 18      doper2          10      DOPER struct for the second filter condition
                  - * 28      rgch            var     String storage for vtString DOPER
                  - *
                  - * 

                  - */ -/* TODO: ************************************************************************************************************ - * Creation of new AutoFilters, removal of existing (see Boundsheet) - * - * fix: iEntry is not always the index of the column - * APPARENTLY if there are more than two autofilters, iEntry is the index of the column - * if there is only 1 autofilter, iEntry is 0 --- dependent upon Obj record???? - * - * Finish: setTop10, setVal, setVal2: verify all is correctly done .... - * ******************************************************************************************************************* - */ -public final class AutoFilter extends io.starter.formats.XLS.XLSRecord { - - private static final long serialVersionUID = -5228830347211523997L; - - short iEntry; // *** this is the column number *** oops! not always!!! - Doper doper1, doper2; - - // booleans used here for memory space/grbit fields, these are really 1/0 values. whas the diff? - boolean wJoin;// true if custom filter conditions are ORed - boolean fSimple1;// true if the first condition is a simple equality;, - boolean fSimple2; // trueif the second condition is a simple equality; - boolean fTop10; // true if the condition is a Top10 autofilter - boolean fTop; // true if the top 10 AutoFilter shows the top itemsl 0 if it shows the bottom items - boolean fPercent; // true if the Top 10 AutoFilter shows percentage, 0 if it shows items - short wTop10; //The number of items to show (from 1-500) - - private byte[] PROTOTYPE_BYTES = { - 00, 00, /* iEntry */ - 00, 00, /* grbit */ - 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, /* unused Doper1 */ - 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, /* unused Doper2 */ - }; - - /** - * creates a new AutoFilter record - */ - public static XLSRecord getPrototype() { - AutoFilter af = new AutoFilter(); - af.setOpcode(AUTOFILTER); - af.setData(af.PROTOTYPE_BYTES); // 20090630 KSC: don't use static PROTOTYPE_BYTES as changes are propogated - af.init(); - return af; - } - - /** - * initialize the AutoFilter record - */ - public void init() { - super.init(); - iEntry = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - //parse out grbit flags - short grbit = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - this.decodeGrbit(grbit); - //parse both DOPERs - doper1 = parseDoper(this.getBytesAt(4, 10)); - doper2 = parseDoper(this.getBytesAt(14, 10)); - // parse string data, if any, appended to end of data record - if (this.getData().length > 25) { - byte[] rgch = this.getBytesAt(25, this.getData().length - 25); - int pos = 0; - if (doper1 instanceof StringDoper) { - ((StringDoper) doper1).setString(rgch, pos); - pos += ((StringDoper) doper1).getCch(); // set position pointer to next string data, if any - pos++; - } - if (doper2 instanceof StringDoper) { - ((StringDoper) doper2).setString(rgch, pos); - } - } - } - - /** - * Parse out the grbit - * - * @param grbt - */ - private void decodeGrbit(short grbit) { // top 500: grbit= -1488 - wJoin = ((grbit & 0x3) == 0x3); // 0= AND, 3= OR - fSimple1 = ((grbit & 0x4) == 0x4); - fSimple2 = ((grbit & 0x8) == 0x8); - fTop10 = ((grbit & 0x10) == 0x10); - fTop = ((grbit & 0x20) == 0x20); - fPercent = ((grbit & 0x40) == 0x40); - wTop10 = (short) ((grbit & 0xFF80) >> 7); - } - - /** - * encode the grbit from the source flags - * - * @return short encoded grbit - */ - private short encodeGrbit() { - short grbit = 0; - if (wJoin) grbit = 3; // Or 0= AND - grbit = ByteTools.updateGrBit(grbit, fSimple1, 2); - grbit = ByteTools.updateGrBit(grbit, fSimple2, 3); - grbit = ByteTools.updateGrBit(grbit, fTop10, 4); - grbit = ByteTools.updateGrBit(grbit, fTop, 5); - grbit = ByteTools.updateGrBit(grbit, fPercent, 6); - grbit = (short) ((wTop10 << 7) | grbit); - return grbit; - } - - /** - * Take an array of 10 bytes and parse out the doper - * - * @param doperBytes - * @return - */ - private Doper parseDoper(byte[] doperBytes) { - Doper retDoper = null; - switch (doperBytes[0]) { - case 0x0: - retDoper = new UnusedDoper(doperBytes); - break; - case 0x2: - retDoper = new RKDoper(doperBytes); - break; - case 0x4: - retDoper = new IEEEDoper(doperBytes); - break; - case 0x6: - retDoper = new StringDoper(doperBytes); - break; - case 0x8: - retDoper = new ErrorDoper(doperBytes); - break; - case 0xC: - retDoper = new AllBlanksDoper(doperBytes); - break; - case 0xE: - retDoper = new NoBlanksDoper(doperBytes); - break; - } - return retDoper; - } - - /** - * commit all the flags, dopers, etc to the byte rec array - */ - public void update() { - byte[] data = new byte[25]; - byte[] b = ByteTools.shortToLEBytes(iEntry); - System.arraycopy(b, 0, data, 0, 2); - b = ByteTools.shortToLEBytes(encodeGrbit()); - System.arraycopy(b, 0, data, 2, 2); - System.arraycopy(doper1.getRecord(), 0, data, 4, 10); - System.arraycopy(doper2.getRecord(), 0, data, 14, 10); - if (doper1 instanceof StringDoper) // append rgch bytes - data = ByteTools.append(((StringDoper) doper1).getStrBytes(), data); - if (doper2 instanceof StringDoper) // append rgch bytes - data = ByteTools.append(((StringDoper) doper2).getStrBytes(), data); - setData(data); - } - - /** - * Evaluates this AutoFilter's condition for each cell in the indicated column - * over all rows in the sheet; if the condition is not met, the row is set to hidden - *
                  NOTE: since there may be other conditions (other AutoFilters, for instance) - * setting the row to it's hidden state, this method will not - * set the row to unhidden if the condition passes. - */ - public void evaluate() { - Object val1, val2 = null; - val1 = getVal(doper1); // get the doper value from the 1st doper/comparison, if any - boolean hasDoper2 = !(doper2 instanceof UnusedDoper); - if (hasDoper2) - val2 = getVal(doper2); - - String op1 = "=", op2 = ""; - boolean passes = true; - if (!fSimple1) { - op1 = doper1.getComparisonOperator(); - } - if (!fSimple2 && hasDoper2) { - op2 = doper2.getComparisonOperator(); - } - - // TODO: - // above/below average? ooxml only? - // date/time comparisons???? - // begins with, ends with ... - if (fTop10) { - if (fTop) { // ascending - evaluateTopN(); - } else // descending - evaluateBottomN(); - } else if ((doper1 instanceof AllBlanksDoper) || - (doper1 instanceof NoBlanksDoper)) { - boolean filterBlanks = (doper1 instanceof NoBlanksDoper); - int n = this.getSheet().getNumRows(); - for (int i = 0; i < n; i++) { - Row r = this.getSheet().getRowByNumber(i); - if (r == null) {// it's blank - // create a blank cell and then set to hidden? - if (filterBlanks) { - this.getSheet().addValue("", ExcelTools.formatLocation(new int[]{i, iEntry})); - r = this.getSheet().getRowByNumber(i); - r.setHidden(true); - } - } else { //row is not blank, check to see if cell is blank - try { - BiffRec c = r.getCell(iEntry); - if (c instanceof Blank && filterBlanks) - r.setHidden(true); - else if (!filterBlanks) - r.setHidden(true); - } catch (NullPointerException e) { - // NPE= blank - if (filterBlanks) - r.setHidden(true); - } catch (CellNotFoundException e) { - - } - } - } - Row[] rows = this.getSheet().getRows(); - if (!filterBlanks) { // easy; everything that is NOT BLANK is hidden - for (int i = 1; i < rows.length; i++) - rows[i].setHidden(true); - } else { // everything that is blank is hidden - - } - } else { // all other criteria are based upon operator comparisons ... - Row[] rows = this.getSheet().getRows(); - for (int i = 1; i < rows.length; i++) { - try { - BiffRec c = rows[i].getCell(iEntry); - passes = io.starter.formats.XLS.formulas.Calculator.compareCellValue(c, val1, op1); - if (hasDoper2 && (wJoin || (!wJoin && passes))) - passes = io.starter.formats.XLS.formulas.Calculator.compareCellValue(c, val2, op2); - if (!passes) - rows[i].setHidden(true); - } catch (Exception e) { - } // just keep evaluation - } - } - } - - - /** - * gets the Object Value of the Doper - * either a Double, String or Boolean type - * - * @param d - Doper - * @return Object value of the Doper - */ - private Object getVal(Doper d) { - Object val = null; - if (d instanceof ErrorDoper) { - if (((ErrorDoper) d).isBooleanVal()) - val = Boolean.valueOf(((ErrorDoper) d).getBooleanVal()); - else - val = ((ErrorDoper) d).getErrVal(); // error doper - } else if (d instanceof StringDoper) // string comparison - val = d.toString(); - else if ((d instanceof RKDoper)) - val = new Double(((RKDoper) d).getVal()); - else if ((d instanceof IEEEDoper)) - val = new Double(((IEEEDoper) d).getVal()); - return val; - } - - /** - * evaluates a Top-N condition, hiding rows that are not in the top N values of the column - * - * @see evaluateBottomN for descending or Bottom-N evaluation - */ - private void evaluateTopN() { - // must go thru 1+ times as must gather up values then go back and set hidden ... - // identifies top n values then displays ALL rows that contain those values - ArrayList top10 = new ArrayList(); - int n = ((!fPercent) ? wTop10 : this.getSheet().getNumRows() / wTop10); - double[] maxVals = new double[n]; - for (int i = 0; i < n; i++) maxVals[i] = Double.NEGATIVE_INFINITY; - double curmin = Double.NEGATIVE_INFINITY; - Row[] rows = this.getSheet().getRows(); - for (int i = 1; i < rows.length; i++) { - try { - BiffRec c = rows[i].getCell(iEntry); - double val = c.getDblVal(); - int insertionpoint = -1; - if (val >= curmin) { - for (int j = 0; j < n; j++) { // see where new value falls - if (val == maxVals[j]) { // then no need to move values around; just add index to set of indexes - String idxs = (String) top10.get(j); - if (idxs == null) - idxs = i + ", "; - else - idxs += i + ", "; - top10.set(j, idxs); - insertionpoint = -1; // so don't add below - break; - } else if (val > maxVals[j]) { - if (insertionpoint == -1 || maxVals[j] < maxVals[insertionpoint]) { - insertionpoint = j; // overwrite point - } - } - } - if (insertionpoint >= 0) { - if (top10.size() > insertionpoint) - top10.remove(insertionpoint); // replace below - String idxs = i + ", "; - top10.add(insertionpoint, idxs); - maxVals[insertionpoint] = val; - // reestablish curmin - curmin = Double.MAX_VALUE; - for (int j = 0; j < n; j++) - curmin = Math.min(maxVals[j], curmin); - } - } // othwerwise doesn't meet criteia - - } catch (Exception e) { - } // just keep evaluation - } - // get master list of non-hidden rows - String nonhiddenIdxs = ""; - for (int i = 0; i < top10.size(); i++) { - nonhiddenIdxs += (String) top10.get(i); - } - // now that have list of rows which SHOULDN'T be hidden, set all else to hidden - for (int j = 1; j < rows.length; j++) { - String idx = j + ", "; - if (nonhiddenIdxs.indexOf(idx) == -1) // not found! - rows[j].setHidden(true); - } - } - - /** - * evaluates a Top-N condition in descending order, in other words, a Bottom-N evaluation, - * hiding rows that are not in the bottom N values of the column - * - * @see evaluateTopN for ascending or Top-N evaluation - */ - private void evaluateBottomN() { - // must go thru 1+ times as must gather up values then go back and set hidden ... - // identifies bottom n values then displays ALL rows that contain those values - ArrayList bottomN = new ArrayList(); - int n = ((!fPercent) ? wTop10 : this.getSheet().getNumRows() / wTop10); - double[] minVals = new double[n]; - for (int i = 0; i < n; i++) minVals[i] = Double.POSITIVE_INFINITY; - double curmax = Double.POSITIVE_INFINITY; - Row[] rows = this.getSheet().getRows(); - for (int i = 1; i < rows.length; i++) { - try { - BiffRec c = rows[i].getCell(iEntry); - double val = c.getDblVal(); - int insertionpoint = -1; - if (val <= curmax) { - for (int j = 0; j < n; j++) { // see where new value falls - if (val == minVals[j]) { // then no need to move values around; just add index to set of indexes - String idxs = (String) bottomN.get(j); - if (idxs == null) - idxs = i + ", "; - else - idxs += i + ", "; - bottomN.set(j, idxs); - insertionpoint = -1; // so don't add below - break; - } else if (val < minVals[j]) { - if (insertionpoint == -1 || minVals[j] > minVals[insertionpoint]) { - insertionpoint = j; // overwrite point - } - } - } - if (insertionpoint >= 0) { - if (bottomN.size() > insertionpoint) - bottomN.remove(insertionpoint); // replace below - String idxs = i + ", "; - bottomN.add(insertionpoint, idxs); - minVals[insertionpoint] = val; - // reestablish curmax - curmax = Double.NEGATIVE_INFINITY; - for (int j = 0; j < n; j++) - curmax = Math.max(minVals[j], curmax); - } - } // othwerwise doesn't meet criteia - - } catch (Exception e) { - } // just keep evaluation - } - // get master list of non-hidden rows - String nonhiddenIdxs = ""; - for (int i = 0; i < bottomN.size(); i++) { - nonhiddenIdxs += (String) bottomN.get(i); - } - // now that have list of rows which SHOULDN'T be hidden, set all else to hidden - for (int j = 1; j < rows.length; j++) { - String idx = j + ", "; - if (nonhiddenIdxs.indexOf(idx) == -1) // not found! - rows[j].setHidden(true); - } - } - - /** - * return a string representation of this autofilter - */ - public String toString() { - String op1 = "=", op2 = ""; - boolean hasDoper2 = (doper2 != null && !(doper2 instanceof UnusedDoper)); - if (fTop10) { - if (fTop) { - if (fPercent) - return "Top " + wTop10 + "%"; - return "Top " + wTop10 + " Items"; - } else { - if (fPercent) - return "Bottom " + wTop10 + "%"; - return "Bottom " + wTop10 + " Items"; - } - } else if (!fSimple1) { - op1 = doper1.getComparisonOperator(); - } else { - if (doper1 instanceof AllBlanksDoper) - return "Non Blanks"; - else if (doper1 instanceof NoBlanksDoper) - return "Blanks"; - } - - if (!fSimple2 && hasDoper2) { - op2 = doper2.getComparisonOperator(); - } - if (!hasDoper2) // just == the doper - return op1 + doper1.toString(); - return op1 + doper1.toString() + ((wJoin) ? " OR " : " AND ") + op2 + doper2.toString(); - } - - /** - * Update the record before streaming - * - * @see io.starter.formats.XLS.XLSRecord#preStream() - */ - public void preStream() { - // no need to update unless things have changed ... this.update(); - } - - /** - * return the column number (0-based) that this AutoFilter references - * - * @return int column number - */ - public int getCol() { - return iEntry; - } - - /** - * set the column number (0-based) that this AutoFilter references - * - * @param int col - 0-based column number - */ - public void setCol(int col) { - iEntry = (short) col; - update(); - } - - /** - * Sets the custom comparison of this AutoFilter via a String operator and an Object value - *
                  Only those records that meet the equation (column value) value will be shown - *
                  e.g show all rows where column value >= 2.99 - *

                  Object value can be of type - *

                  String - *
                  Boolean - *
                  Error - *
                  a Number type object - *

                  String operator may be one of: "=", ">", ">=", "<>", "<", "<=" - * - * @param Object val - value to set - * @param String op - operator - * @see setVal2 - */ - public void setVal(Object val, String op) { - byte[] doperRec = new byte[10]; - if (val instanceof String) {// doper1 should be a string - String s = (String) val; - if (!s.startsWith("!")) { // it's not an error val - doperRec[0] = 0x6; - doper1 = new StringDoper(doperRec); - ((StringDoper) doper1).setString(s); - } else { // it's an error doper - doperRec[0] = 0x8; - doperRec[2] = 1; // fError - doper1 = new ErrorDoper(doperRec); - } - } else if (val instanceof Boolean) { - doperRec[0] = 0x8; - doperRec[2] = 0; // fError - doperRec[3] = (byte) (((Boolean) val).booleanValue() ? 1 : 0); - doper1 = new ErrorDoper(doperRec); - } else { // assume a Number object - doperRec[0] = 0x2; - try { - double d = 0; - if (val instanceof Double) - d = ((Double) val).doubleValue(); - else if (val instanceof Integer) - d = ((Integer) val).doubleValue(); - else - throw new NumberFormatException("Unable to convert to Numeric Object" + val.getClass()); - doper1 = new RKDoper(doperRec); - ((RKDoper) doper1).setVal(d); - //doper1= new IEEEDoper(doperRec); - } catch (Exception e) { - Logger.logErr("AutoFilter.setVal: error setting value to " + val + ":" + e.toString()); - } - } - doper1.setComparisonOperator(op); - fSimple1 = ("=".equals(op)); - update(); - } - - - /** - * Sets the custom comparison of the second condition of this AutoFilter via a String operator and an Object value - *
                  This method sets the second condition of a two-condition filter - *

                  Only those records that meet the equation: - *
                  first condiition AND/OR (column value) Value will be shown - *
                  e.g show all rows where (column value) <= 1.99 AND (column value) >= 2.99 - *

                  Object value can be of type - *

                  String - *
                  Boolean - *
                  Error - *
                  a Number type object - *

                  String operator may be one of: "=", ">", ">=", "<>", "<", "<=" - * - * @param Object val - value to set - * @param String op - operator - * @param boolean AND - true if two conditions should be AND'ed, false if OR'd - * @see setVal2 - */ - public void setVal2(Object val, String op, boolean AND) { - byte[] doperRec = new byte[10]; - if (val instanceof String) {// doper1 should be a string - String s = (String) val; - if (!s.startsWith("!")) { // it's not an error val - doperRec[0] = 0x6; - doper2 = new StringDoper(doperRec); - ((StringDoper) doper1).setString(s); - } else { // it's an error doper - doperRec[0] = 0x8; - doperRec[2] = 1; // fError - doper2 = new ErrorDoper(doperRec); - } - } else if (val instanceof Boolean) { - doperRec[0] = 0x8; - doperRec[2] = 0; // fError - doperRec[3] = (byte) (((Boolean) val).booleanValue() ? 1 : 0); - doper2 = new ErrorDoper(doperRec); - } else { // assume a Number object - doperRec[0] = 0x2; - try { - double d = 0; - if (val instanceof Double) - d = ((Double) val).doubleValue(); - else if (val instanceof Integer) - d = ((Integer) val).doubleValue(); - else - throw new NumberFormatException("Unable to convert to Numeric Object" + val.getClass()); - doper2 = new RKDoper(doperRec); - ((RKDoper) doper2).setVal(d); - //doper1= new IEEEDoper(doperRec); - } catch (Exception e) { - Logger.logErr("AutoFilter.setVal: error setting value to " + val + ":" + e.toString()); - } - } - doper2.setComparisonOperator(op); - fSimple2 = ("=".equals(op)); - wJoin = !AND; - update(); - } - - /** - * return the value of the first comparison of this AutoFilter, if any - * - * @return Object value - * @see getVal2 - */ - public Object getVal() { - return doper1.toString(); - } - - /** - * returns the value of the second comparison of this AutoFilter, if any - * - * @return Object value - * @see getVal - */ - public Object getVal2() { - return doper2.toString(); - } - - /** - * get the operator associated with this AutoFilter - *
                  NOTE: this will return the operator in the first condition if this AutoFilter contains two conditions - *
                  Use getOp2 to retrieve the second condition operator - * - * @return String operator: one of "=", ">", ">=", "<>", "<", "<=" - * @see getOp2 - */ - public String getOp() { - return doper1.getComparisonOperator(); - } - - /** - * get the operator associated with the second condtion of this AutoFilter, if any - *
                  NOTE: this will return the operator in the second condition if this AutoFilter contains two conditions - * - * @return String operator: one of "=", ">", ">=", "<>", "<", "<=", or null, if no second condition - * @see getOp - */ - public String getOp2() { - if (!(doper2 instanceof UnusedDoper)) - return doper2.getComparisonOperator(); - return null; - } - - /** - * sets this AutoFilter to be a Top-n type of filter - *
                  Top-n filters only show the Top n values or percent in the column - *
                  n can be from 1-500, or 0 to turn off Top 10 filtering - * - * @param int n - 0-500 - * @param boolean percent - true if show Top-n percent; false to show Top-n items - * @param boolean top10 - true if show Top 10 (items or percent), false to show Bottom N (items or percent) - */ - public void setTop10(int n, boolean percent, boolean top10) { - if (n == 0) { - fTop = false; - fTop10 = false; - fPercent = false; - wTop10 = 0; - // TODO: set fSimple1? remove dopers?? - } else if (n > 0 && n <= 500) { - fTop = top10; - fTop10 = true; - wTop10 = (short) n; - fPercent = percent; - fSimple1 = false; // true if 1st condition is simple equality - fSimple2 = false; // true if 2nd condition is simple equality - doper1 = new IEEEDoper(new byte[]{4, 6, 0, 0, 0, 0, 0, 0, 0, 0}); - ((IEEEDoper) doper1).setVal(n); - doper2 = new UnusedDoper(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); - } else - Logger.logErr("AutoFilter.setTop10: value " + n + " must be between 0 and 500"); - update(); - } - - /** - * sets this AutoFilter to filter all blank rows - */ - public void setFilterBlanks() { - fSimple1 = true; - fSimple2 = false; - fTop = false; - fTop10 = false; - fPercent = false; - wTop10 = 0; - doper1 = new NoBlanksDoper(new byte[]{14, 5, 0, 0, 0, 0, 0, 0, 0, 0}); - doper2 = new UnusedDoper(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); - update(); - } - - /** - * returns true if this AutoFitler is set to filter blank rows - * - * @return boolean - */ - public boolean isFilterBlanks() { - return (fSimple1 && !fSimple2 && !fTop && !fTop10 && doper1 instanceof NoBlanksDoper); - } - - /** - * sets this AutoFilter to filter all non-blank rows - */ - public void setFilterNonBlanks() { - fSimple1 = true; - fSimple2 = false; - fTop = false; - fTop10 = false; - fPercent = false; - wTop10 = 0; - doper1 = new AllBlanksDoper(new byte[]{12, 2, 0, 0, 0, 0, 0, 1, 0, 0}); - doper2 = new UnusedDoper(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); - update(); - } - - /** - * returns true if this AutoFitler is set to filter all non-blank rows - * - * @return boolean - */ - public boolean isFilterNonBlanks() { - return (fSimple1 && !fSimple2 && !fTop && !fTop10 && doper1 instanceof AllBlanksDoper); - } - - /** - * returns true if this is a top-10-type AutoFilter - * - * @return true if this is a top-10-type AutoFilter, false otherwise - */ - public boolean isTop10() { - return (fTop10 && wTop10 > 0); - } - - - /** - * Doper (Database OPER Structure) are parsed definitions of the AutoFilter - *

                  - * 10-byte parsed definitions that appear in the Custom AutoFilter dialog box - *

                  There are several sub-types of Dopers: - *
                  String - *
                  IEEE Floating Point - *
                  RK - *
                  Error or Boolean - *
                  All Blanks - *
                  All non-blanksfs - *
                  Unused -- placeholder doper - * - * @see Excel bible page 284 - */ - private class Doper { - byte vt, grbitSign; - byte[] doperRec; - - protected Doper(byte[] rec) { - vt = rec[0]; - grbitSign = rec[1]; // comparison code - doperRec = rec; - } - - public byte getGrbitSign() { - return grbitSign; - } - - public void setGrbitSign(byte grbitSign) { - this.grbitSign = grbitSign; - } - - public byte getVt() { - return vt; - } - - public void setVt(byte vt) { - this.vt = vt; - } - - public byte[] getRecord() { - return doperRec; - } - - public String toString() { - return null; - } - - /** - * Returns the comparison operator for this doper, ie '>' '=', etc - * - * @return - */ - public String getComparisonOperator() { - switch (grbitSign) { - case 1: - return "<"; - case 2: - return "="; - case 3: - return "<="; - case 4: - return ">"; - case 5: - return "<>"; - case 6: - return ">="; - } - return ""; - } - - /** - * sets the custom comparison operator to one of: - *
                  "<" - *
                  "=" - *
                  "<=" - *
                  ">" - *
                  "<>" - *
                  ">=" - * - * @param String op - custom operator - */ - public void setComparisonOperator(String op) { - if ("<".equals(op)) - grbitSign = 1; - if ("=".equals(op)) - grbitSign = 2; - if ("<=".equals(op)) - grbitSign = 3; - if (">".equals(op)) - grbitSign = 4; - if ("<>".equals(op)) - grbitSign = 5; - if (">=".equals(op)) - grbitSign = 6; - doperRec[1] = grbitSign; - } - } - - /** - * A doper representing an unused value/filter condition unused. - */ - private class UnusedDoper extends Doper { - - protected UnusedDoper(byte[] rec) { - super(rec); - } - - public String toString() { - return "Unused"; - } - - } - - /** - * A doper representing an RK number - *

                  Dopers define an AutoFilter value using a 10-byte doperRec - *
                  For all dopers, doperRec[0]=vt or code - *
                  doperRec[1]=comparison operator - *

                  For RK Dopers, - * doperRec[2]->[6] = rk number, 6-9= reserved - */ - private class RKDoper extends Doper { - double val; - - protected RKDoper(byte[] rec) { - super(rec); - byte[] b = new byte[4]; - System.arraycopy(doperRec, 2, b, 0, 4); - val = Rk.parseRkNumber(b); // parse bytes in Rk-number format into a double value - } - - public String toString() { - return new Double(val).toString(); - } - - /** - * set the double value for this Rk-type Doper record - * - * @param double d - double value to set - */ - public void setVal(double d) { - val = d; - byte[] b = Rk.getRkBytes(d); - System.arraycopy(b, 0, doperRec, 2, 4); - } - - /** - * return the double value associated with this Rk-type Doper record - * - * @return double value - */ - public double getVal() { - return val; - } - } - - /** - * A doper representing a IEEE number - *

                  Dopers define an AutoFilter value using a 10-byte doperRec - *
                  For all dopers, doperRec[0]=vt or code - *
                  doperRec[1]=comparison operator - *

                  For IEEE Dopers, - * doperRec[2->9] = IEEE floating point number - */ - private class IEEEDoper extends Doper { - double val; - - /** - * create an IEEEDoper Object from doper record bytes - * (10 bytes as part of the AutoFilter record) - * - * @param byte[] rec 10 byte doper record - */ - protected IEEEDoper(byte[] rec) { - super(rec); - byte[] b = new byte[8]; - System.arraycopy(doperRec, 2, b, 0, 8); - val = ByteTools.eightBytetoLEDouble(b); // TODO: is this correct?? - } - - /** - * return the double value associated with this IEEE-type Doper record - * - * @return double value - */ - public double getVal() { - return val; - } - - /** - * set the double value for this IEEE-type Doper record - * - * @param double d - double value to set - */ - public void setVal(double d) { - val = d; - byte[] b = ByteTools.doubleToLEByteArray(val); - System.arraycopy(b, 0, doperRec, 2, 8); - } - - public String toString() { - return new Double(val).toString(); - } - } - - /** - * A doper representing a String - *

                  Dopers define an AutoFilter value using a 10-byte doperRec - *
                  For all dopers, doperRec[0]=vt or code - *
                  doperRec[1]=comparison operator - *

                  For String Dopers, - * doperRec[6]= cch or String length - *
                  The actual string data is appended to the end of the entire AutoFilter data record - *
                  - * NOTE: strings must be in normal or default encoding - */ - private class StringDoper extends Doper { - String s; - - protected StringDoper(byte[] rec) { - super(rec); - } - - /** - * sets the string for this StringDoper from a byte array and an index into said array - *
                  amount to read from byteArray is stored in cch, bit 6 of the 10-byte doperRec - * - * @param byte[] rgch - source byte array for string - * @param int start - start index into the byte array - */ - public void setString(byte[] rgch, int start) { - int cch = doperRec[6]; - byte[] stringbytes = new byte[cch]; - System.arraycopy(rgch, start, stringbytes, 0, cch); - s = new String(stringbytes); - } - - /** - * returns cch, the length of this string data - * - * @return int - */ - public int getCch() { - return doperRec[6]; - } - - /** - * s the string for this StringDoper from a String Object - * - * @param String s - */ - public void setString(String s) { - this.s = s; - byte[] b = s.getBytes(); - doperRec[6] = (byte) b.length; - } - - /** - * returns the byte array representing the String referenced by this StringDoper - * - * @return byte[] - */ - public byte[] getStrBytes() { - return s.getBytes(); - } - - /** - * returns the String referenced by this StringDoper - */ - public String toString() { - return s; - } - } - - /** - * A doper representing an Error or Boolean - *

                  Dopers define an AutoFilter value using a 10-byte doperRec - *
                  For all dopers, doperRec[0]=vt or code - *
                  doperRec[1]=comparison operator - *

                  For Error or Boolean Dopers, - *

                    doperRec[2]= fError;
                  - *
                    if 0, doperRec[3]= boolean value
                  - *
                    if 1, doperRec[3]= Error value
                  - * Error Values: - *
                  0 = #NULL! - *
                  0x7 = #DIV/0! - *
                  0xF = #VALUE! - *
                  0x17= #REF! - *
                  0x1D= #NAME? - *
                  0x24= #NUM! - *
                  0x2A= #N/A - */ - private class ErrorDoper extends Doper { - boolean bVal; - int errVal; - - protected ErrorDoper(byte[] rec) { - super(rec); - if (doperRec[2] == 0) // boolean doper - bVal = (doperRec[3] != 0); - else - errVal = doperRec[3]; // see above vals - } - - /** - * returns true if this is a Error Doper - * - * @return boolean true if this is a Error Doper - */ - public boolean isErrVal() { - return (doperRec[2] == 1); - } - - /** - * returns true if this is a Boolean Doper - * - * @return boolean true if this is a boolean Doper - */ - public boolean isBooleanVal() { - return (doperRec[2] == 0); - } - - /** - * returns the boolean value if this is a type Boolean Doper - * - * @return boolean - */ - public boolean getBooleanVal() { - return bVal; - } - - /** - * interprets the error code located in doperRec[2] into a String Error Value - * e.g. "#NULL!" - * - * @return String error Value - */ - public String getErrVal() { - if (doperRec[2] == 1) { - switch (errVal) { - case 0: - return "#NULL!"; - case 0x7: - return "#DIV/0!"; - case 0xF: - return "#VALUE!"; - case 0x17: - return "#REF!"; - case 0x1D: - return "#NAME?"; - case 0x24: - return "#NUM!"; - case 0x2A: - return "#N/A"; - } - } - return ""; - } - - - /** - * returns a String representation of this Error or Boolean Doper - *
                  If this is an Error Doper, returns one of the Error Values - *
                  If this is a booelan Doper, returns "true" or "false" - * - * @return String representation - * @see AutoFilter.getErrVal() - */ - public String toString() { - if (isErrVal()) - return getErrVal(); - return bVal ? "true" : "false"; - } - } - - /** - * A doper representing an all blanks selection. - *
                  bytes are unused other than identifier - */ - private class AllBlanksDoper extends Doper { - - protected AllBlanksDoper(byte[] rec) { - super(rec); - } - - public String toString() { - return "All Blanks"; - } - } - - /** - * A doper representing an all blanks selection. - *
                  bytes are unused other than identifier - */ - private class NoBlanksDoper extends Doper { - - protected NoBlanksDoper(byte[] rec) { - super(rec); - } - - public String toString() { - return "No Blanks"; - } - } - - -} diff --git a/src/main/java/io/starter/formats/XLS/AutoFilter.kt b/src/main/java/io/starter/formats/XLS/AutoFilter.kt new file mode 100644 index 0000000..29ea2f0 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/AutoFilter.kt @@ -0,0 +1,1109 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + + +import io.starter.OpenXLS.ExcelTools +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.ArrayList + + +/** + * Auto filter controls the auto-filter capabilities of Excel. It has numerous sub-records and references + * + * + * p>
                  + * offset  name        size    contents
                  + * ---
                  + * 4       iEntry          2       Index of the active autofilter
                  + * 6       grbit           2       Option flags
                  + * 8       doper1          10      DOPER struct for the first filter condition
                  + * 18      doper2          10      DOPER struct for the second filter condition
                  + * 28      rgch            var     String storage for vtString DOPER
                  + *
                  +
                  * + */ +/* TODO: ************************************************************************************************************ + * Creation of new AutoFilters, removal of existing (see Boundsheet) + * + * fix: iEntry is not always the index of the column + * APPARENTLY if there are more than two autofilters, iEntry is the index of the column + * if there is only 1 autofilter, iEntry is 0 --- dependent upon Obj record???? + * + * Finish: setTop10, setVal, setVal2: verify all is correctly done .... + * ******************************************************************************************************************* + */ +class AutoFilter : io.starter.formats.XLS.XLSRecord() { + + internal var iEntry: Short = 0 // *** this is the column number *** oops! not always!!! + internal var doper1: Doper? = null + internal var doper2: Doper? = null + + // booleans used here for memory space/grbit fields, these are really 1/0 values. whas the diff? + internal var wJoin: Boolean = false// true if custom filter conditions are ORed + internal var fSimple1: Boolean = false// true if the first condition is a simple equality;, + internal var fSimple2: Boolean = false // trueif the second condition is a simple equality; + internal var fTop10: Boolean = false // true if the condition is a Top10 autofilter + internal var fTop: Boolean = false // true if the top 10 AutoFilter shows the top itemsl 0 if it shows the bottom items + internal var fPercent: Boolean = false // true if the Top 10 AutoFilter shows percentage, 0 if it shows items + internal var wTop10: Short = 0 //The number of items to show (from 1-500) + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, /* iEntry */ + 0, 0, /* grbit */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* unused Doper1 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)/* unused Doper2 */ + + /** + * return the column number (0-based) that this AutoFilter references + * + * @return int column number + */ + /** + * set the column number (0-based) that this AutoFilter references + * + * @param int col - 0-based column number + */ + var col: Int + get() = iEntry.toInt() + set(col) { + iEntry = col.toShort() + update() + } + + /** + * return the value of the first comparison of this AutoFilter, if any + * + * @return Object value + * @see getVal2 + */ + override val `val`: Any? + get() = doper1!!.toString() + + /** + * returns the value of the second comparison of this AutoFilter, if any + * + * @return Object value + * @see getVal + */ + val val2: Any? + get() = doper2!!.toString() + + /** + * get the operator associated with this AutoFilter + *

                  NOTE: this will return the operator in the first condition if this AutoFilter contains two conditions + *

                  Use getOp2 to retrieve the second condition operator + * + * @return String operator: one of "=", ">", ">=", "<>", "<", "<=" + * @see getOp2 + */ + val op: String + get() = doper1!!.comparisonOperator + + /** + * get the operator associated with the second condtion of this AutoFilter, if any + *

                  NOTE: this will return the operator in the second condition if this AutoFilter contains two conditions + * + * @return String operator: one of "=", ">", ">=", "<>", "<", "<=", or null, if no second condition + * @see getOp + */ + val op2: String? + get() = if (doper2 !is UnusedDoper) doper2!!.comparisonOperator else null + + /** + * returns true if this AutoFitler is set to filter blank rows + * + * @return boolean + */ + val isFilterBlanks: Boolean + get() = fSimple1 && !fSimple2 && !fTop && !fTop10 && doper1 is NoBlanksDoper + + /** + * returns true if this AutoFitler is set to filter all non-blank rows + * + * @return boolean + */ + val isFilterNonBlanks: Boolean + get() = fSimple1 && !fSimple2 && !fTop && !fTop10 && doper1 is AllBlanksDoper + + /** + * returns true if this is a top-10-type AutoFilter + * + * @return true if this is a top-10-type AutoFilter, false otherwise + */ + val isTop10: Boolean + get() = fTop10 && wTop10 > 0 + + /** + * initialize the AutoFilter record + */ + override fun init() { + super.init() + iEntry = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + //parse out grbit flags + val grbit = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + this.decodeGrbit(grbit) + //parse both DOPERs + doper1 = parseDoper(this.getBytesAt(4, 10)!!) + doper2 = parseDoper(this.getBytesAt(14, 10)!!) + // parse string data, if any, appended to end of data record + if (this.getData()!!.size > 25) { + val rgch = this.getBytesAt(25, this.getData()!!.size - 25) + var pos = 0 + if (doper1 is StringDoper) { + (doper1 as StringDoper).setString(rgch, pos) + pos += (doper1 as StringDoper).cch // set position pointer to next string data, if any + pos++ + } + if (doper2 is StringDoper) { + (doper2 as StringDoper).setString(rgch, pos) + } + } + } + + /** + * Parse out the grbit + * + * @param grbt + */ + private fun decodeGrbit(grbit: Short) { // top 500: grbit= -1488 + wJoin = grbit and 0x3 == 0x3 // 0= AND, 3= OR + fSimple1 = grbit and 0x4 == 0x4 + fSimple2 = grbit and 0x8 == 0x8 + fTop10 = grbit and 0x10 == 0x10 + fTop = grbit and 0x20 == 0x20 + fPercent = grbit and 0x40 == 0x40 + wTop10 = (grbit and 0xFF80 shr 7).toShort() + } + + /** + * encode the grbit from the source flags + * + * @return short encoded grbit + */ + private fun encodeGrbit(): Short { + var grbit: Short = 0 + if (wJoin) grbit = 3 // Or 0= AND + grbit = ByteTools.updateGrBit(grbit, fSimple1, 2) + grbit = ByteTools.updateGrBit(grbit, fSimple2, 3) + grbit = ByteTools.updateGrBit(grbit, fTop10, 4) + grbit = ByteTools.updateGrBit(grbit, fTop, 5) + grbit = ByteTools.updateGrBit(grbit, fPercent, 6) + grbit = (wTop10 shl 7 or grbit).toShort() + return grbit + } + + /** + * Take an array of 10 bytes and parse out the doper + * + * @param doperBytes + * @return + */ + private fun parseDoper(doperBytes: ByteArray): Doper? { + var retDoper: Doper? = null + when (doperBytes[0]) { + 0x0 -> retDoper = UnusedDoper(doperBytes) + 0x2 -> retDoper = RKDoper(doperBytes) + 0x4 -> retDoper = IEEEDoper(doperBytes) + 0x6 -> retDoper = StringDoper(doperBytes) + 0x8 -> retDoper = ErrorDoper(doperBytes) + 0xC -> retDoper = AllBlanksDoper(doperBytes) + 0xE -> retDoper = NoBlanksDoper(doperBytes) + } + return retDoper + } + + /** + * commit all the flags, dopers, etc to the byte rec array + */ + fun update() { + var data = ByteArray(25) + var b = ByteTools.shortToLEBytes(iEntry) + System.arraycopy(b, 0, data, 0, 2) + b = ByteTools.shortToLEBytes(encodeGrbit()) + System.arraycopy(b, 0, data, 2, 2) + System.arraycopy(doper1!!.record, 0, data, 4, 10) + System.arraycopy(doper2!!.record, 0, data, 14, 10) + if (doper1 is StringDoper) + // append rgch bytes + data = ByteTools.append((doper1 as StringDoper).strBytes, data) + if (doper2 is StringDoper) + // append rgch bytes + data = ByteTools.append((doper2 as StringDoper).strBytes, data) + setData(data) + } + + /** + * Evaluates this AutoFilter's condition for each cell in the indicated column + * over all rows in the sheet; if the condition is not met, the row is set to hidden + *

                  NOTE: since there may be other conditions (other AutoFilters, for instance) + * setting the row to it's hidden state, this method **will not** + * set the row to unhidden if the condition passes. + */ + fun evaluate() { + val val1: Any? + var val2: Any? = null + val1 = getVal(doper1) // get the doper value from the 1st doper/comparison, if any + val hasDoper2 = doper2 !is UnusedDoper + if (hasDoper2) + val2 = getVal(doper2) + + var op1 = "=" + var op2 = "" + var passes = true + if (!fSimple1) { + op1 = doper1!!.comparisonOperator + } + if (!fSimple2 && hasDoper2) { + op2 = doper2!!.comparisonOperator + } + + // TODO: + // above/below average? ooxml only? + // date/time comparisons???? + // begins with, ends with ... + if (fTop10) { + if (fTop) { // ascending + evaluateTopN() + } else + // descending + evaluateBottomN() + } else if (doper1 is AllBlanksDoper || doper1 is NoBlanksDoper) { + val filterBlanks = doper1 is NoBlanksDoper + val n = this.sheet!!.numRows + for (i in 0 until n) { + var r = this.sheet!!.getRowByNumber(i) + if (r == null) {// it's blank + // create a blank cell and then set to hidden? + if (filterBlanks) { + this.sheet!!.addValue("", ExcelTools.formatLocation(intArrayOf(i, iEntry.toInt()))) + r = this.sheet!!.getRowByNumber(i) + r!!.isHidden = true + } + } else { //row is not blank, check to see if cell is blank + try { + val c = r.getCell(iEntry) + if (c is Blank && filterBlanks) + r.isHidden = true + else if (!filterBlanks) + r.isHidden = true + } catch (e: NullPointerException) { + // NPE= blank + if (filterBlanks) + r.isHidden = true + } catch (e: CellNotFoundException) { + + } + + } + } + val rows = this.sheet!!.rows + if (!filterBlanks) { // easy; everything that is NOT BLANK is hidden + for (i in 1 until rows.size) + rows[i].isHidden = true + } else { // everything that is blank is hidden + + } + } else { // all other criteria are based upon operator comparisons ... + val rows = this.sheet!!.rows + for (i in 1 until rows.size) { + try { + val c = rows[i].getCell(iEntry) + passes = io.starter.formats.XLS.formulas.Calculator.compareCellValue(c, val1, op1) + if (hasDoper2 && (wJoin || !wJoin && passes)) + passes = io.starter.formats.XLS.formulas.Calculator.compareCellValue(c, val2, op2) + if (!passes) + rows[i].isHidden = true + } catch (e: Exception) { + } + // just keep evaluation + } + } + } + + + /** + * gets the Object Value of the Doper + * either a Double, String or Boolean type + * + * @param d - Doper + * @return Object value of the Doper + */ + private fun getVal(d: Doper?): Any? { + var `val`: Any? = null + if (d is ErrorDoper) { + if (d.isBooleanVal) + `val` = java.lang.Boolean.valueOf(d.booleanVal) + else + `val` = d.getErrVal() // error doper + } else if (d is StringDoper) + // string comparison + `val` = d.toString() + else if (d is RKDoper) + `val` = d.getVal() + else if (d is IEEEDoper) + `val` = d.getVal() + return `val` + } + + /** + * evaluates a Top-N condition, hiding rows that are not in the top N values of the column + * + * @see evaluateBottomN for descending or Bottom-N evaluation + */ + private fun evaluateTopN() { + // must go thru 1+ times as must gather up values then go back and set hidden ... + // identifies top n values then displays ALL rows that contain those values + val top10 = ArrayList() + val n = if (!fPercent) wTop10 else this.sheet!!.numRows / wTop10 + val maxVals = DoubleArray(n) + for (i in 0 until n) maxVals[i] = java.lang.Double.NEGATIVE_INFINITY + var curmin = java.lang.Double.NEGATIVE_INFINITY + val rows = this.sheet!!.rows + for (i in 1 until rows.size) { + try { + val c = rows[i].getCell(iEntry) + val `val` = c.dblVal + var insertionpoint = -1 + if (`val` >= curmin) { + for (j in 0 until n) { // see where new value falls + if (`val` == maxVals[j]) { // then no need to move values around; just add index to set of indexes + var idxs: String? = top10.get(j) + if (idxs == null) + idxs = i + ", " + else + idxs += i + ", " + top10.set(j, idxs) + insertionpoint = -1 // so don't add below + break + } else if (`val` > maxVals[j]) { + if (insertionpoint == -1 || maxVals[j] < maxVals[insertionpoint]) { + insertionpoint = j // overwrite point + } + } + } + if (insertionpoint >= 0) { + if (top10.size > insertionpoint) + top10.removeAt(insertionpoint) // replace below + val idxs = i + ", " + top10.add(insertionpoint, idxs) + maxVals[insertionpoint] = `val` + // reestablish curmin + curmin = java.lang.Double.MAX_VALUE + for (j in 0 until n) + curmin = Math.min(maxVals[j], curmin) + } + } // othwerwise doesn't meet criteia + + } catch (e: Exception) { + } + // just keep evaluation + } + // get master list of non-hidden rows + var nonhiddenIdxs = "" + for (i in top10.indices) { + nonhiddenIdxs += top10.get(i) as String + } + // now that have list of rows which SHOULDN'T be hidden, set all else to hidden + for (j in 1 until rows.size) { + val idx = j + ", " + if (nonhiddenIdxs.indexOf(idx) == -1) + // not found! + rows[j].isHidden = true + } + } + + /** + * evaluates a Top-N condition in descending order, in other words, a Bottom-N evaluation, + * hiding rows that are not in the bottom N values of the column + * + * @see evaluateTopN for ascending or Top-N evaluation + */ + private fun evaluateBottomN() { + // must go thru 1+ times as must gather up values then go back and set hidden ... + // identifies bottom n values then displays ALL rows that contain those values + val bottomN = ArrayList() + val n = if (!fPercent) wTop10 else this.sheet!!.numRows / wTop10 + val minVals = DoubleArray(n) + for (i in 0 until n) minVals[i] = java.lang.Double.POSITIVE_INFINITY + var curmax = java.lang.Double.POSITIVE_INFINITY + val rows = this.sheet!!.rows + for (i in 1 until rows.size) { + try { + val c = rows[i].getCell(iEntry) + val `val` = c.dblVal + var insertionpoint = -1 + if (`val` <= curmax) { + for (j in 0 until n) { // see where new value falls + if (`val` == minVals[j]) { // then no need to move values around; just add index to set of indexes + var idxs: String? = bottomN.get(j) + if (idxs == null) + idxs = i + ", " + else + idxs += i + ", " + bottomN.set(j, idxs) + insertionpoint = -1 // so don't add below + break + } else if (`val` < minVals[j]) { + if (insertionpoint == -1 || minVals[j] > minVals[insertionpoint]) { + insertionpoint = j // overwrite point + } + } + } + if (insertionpoint >= 0) { + if (bottomN.size > insertionpoint) + bottomN.removeAt(insertionpoint) // replace below + val idxs = i + ", " + bottomN.add(insertionpoint, idxs) + minVals[insertionpoint] = `val` + // reestablish curmax + curmax = java.lang.Double.NEGATIVE_INFINITY + for (j in 0 until n) + curmax = Math.max(minVals[j], curmax) + } + } // othwerwise doesn't meet criteia + + } catch (e: Exception) { + } + // just keep evaluation + } + // get master list of non-hidden rows + var nonhiddenIdxs = "" + for (i in bottomN.indices) { + nonhiddenIdxs += bottomN.get(i) as String + } + // now that have list of rows which SHOULDN'T be hidden, set all else to hidden + for (j in 1 until rows.size) { + val idx = j + ", " + if (nonhiddenIdxs.indexOf(idx) == -1) + // not found! + rows[j].isHidden = true + } + } + + /** + * return a string representation of this autofilter + */ + override fun toString(): String { + var op1 = "=" + var op2 = "" + val hasDoper2 = doper2 != null && doper2 !is UnusedDoper + if (fTop10) { + return if (fTop) { + if (fPercent) "Top $wTop10%" else "Top $wTop10 Items" + } else { + if (fPercent) "Bottom $wTop10%" else "Bottom $wTop10 Items" + } + } else if (!fSimple1) { + op1 = doper1!!.comparisonOperator + } else { + if (doper1 is AllBlanksDoper) + return "Non Blanks" + else if (doper1 is NoBlanksDoper) + return "Blanks" + } + + if (!fSimple2 && hasDoper2) { + op2 = doper2!!.comparisonOperator + } + return if (!hasDoper2) op1 + doper1!!.toString()!! else op1 + doper1!!.toString() + (if (wJoin) " OR " else " AND ") + op2 + doper2!!.toString() + } + + /** + * Update the record before streaming + * + * @see io.starter.formats.XLS.XLSRecord.preStream + */ + override fun preStream() { + // no need to update unless things have changed ... this.update(); + } + + /** + * Sets the custom comparison of this AutoFilter via a String operator and an Object value + *

                  Only those records that meet the equation (column value) value will be shown + *

                  e.g show all rows where column value >= 2.99 + * + * Object value can be of type + * + * String + *

                  Boolean + *

                  Error + *

                  a Number type object + * + * String operator may be one of: "=", ">", ">=", "<>", "<", "<=" + * + * @param Object val - value to set + * @param String op - operator + * @see setVal2 +
                  */ + fun setVal(`val`: Any, op: String) { + val doperRec = ByteArray(10) + if (`val` is String) {// doper1 should be a string + val s = `val` + if (!s.startsWith("!")) { // it's not an error val + doperRec[0] = 0x6 + doper1 = StringDoper(doperRec) + (doper1 as StringDoper).setString(s) + } else { // it's an error doper + doperRec[0] = 0x8 + doperRec[2] = 1 // fError + doper1 = ErrorDoper(doperRec) + } + } else if (`val` is Boolean) { + doperRec[0] = 0x8 + doperRec[2] = 0 // fError + doperRec[3] = (if (`val`.booleanValue()) 1 else 0).toByte() + doper1 = ErrorDoper(doperRec) + } else { // assume a Number object + doperRec[0] = 0x2 + try { + var d = 0.0 + if (`val` is Double) + d = `val`.toDouble() + else if (`val` is Int) + d = `val`.toDouble() + else + throw NumberFormatException("Unable to convert to Numeric Object" + `val`.javaClass) + doper1 = RKDoper(doperRec) + (doper1 as RKDoper).setVal(d) + //doper1= new IEEEDoper(doperRec); + } catch (e: Exception) { + Logger.logErr("AutoFilter.setVal: error setting value to $`val`:$e") + } + + } + doper1!!.comparisonOperator = op + fSimple1 = "=" == op + update() + } + + + /** + * Sets the custom comparison of the second condition of this AutoFilter via a String operator and an Object value + *

                  This method sets the second condition of a two-condition filter + * + * Only those records that meet the equation: + *

                  first condiition AND/OR (column value) Value will be shown + *

                  e.g show all rows where (column value) <= 1.99 AND (column value) >= 2.99 +
                  * + * Object value can be of type + * + * String + *

                  Boolean + *

                  Error + *

                  a Number type object + * + * String operator may be one of: "=", ">", ">=", "<>", "<", "<=" + * + * @param Object val - value to set + * @param String op - operator + * @param boolean AND - true if two conditions should be AND'ed, false if OR'd + * @see setVal2 + */ + fun setVal2(`val`: Any, op: String, AND: Boolean) { + val doperRec = ByteArray(10) + if (`val` is String) {// doper1 should be a string + val s = `val` + if (!s.startsWith("!")) { // it's not an error val + doperRec[0] = 0x6 + doper2 = StringDoper(doperRec) + (doper1 as StringDoper).setString(s) + } else { // it's an error doper + doperRec[0] = 0x8 + doperRec[2] = 1 // fError + doper2 = ErrorDoper(doperRec) + } + } else if (`val` is Boolean) { + doperRec[0] = 0x8 + doperRec[2] = 0 // fError + doperRec[3] = (if (`val`.booleanValue()) 1 else 0).toByte() + doper2 = ErrorDoper(doperRec) + } else { // assume a Number object + doperRec[0] = 0x2 + try { + var d = 0.0 + if (`val` is Double) + d = `val`.toDouble() + else if (`val` is Int) + d = `val`.toDouble() + else + throw NumberFormatException("Unable to convert to Numeric Object" + `val`.javaClass) + doper2 = RKDoper(doperRec) + (doper2 as RKDoper).setVal(d) + //doper1= new IEEEDoper(doperRec); + } catch (e: Exception) { + Logger.logErr("AutoFilter.setVal: error setting value to $`val`:$e") + } + + } + doper2!!.comparisonOperator = op + fSimple2 = "=" == op + wJoin = !AND + update() + } + + /** + * sets this AutoFilter to be a Top-n type of filter + *

                  Top-n filters only show the Top n values or percent in the column + *

                  n can be from 1-500, or 0 to turn off Top 10 filtering + * + * @param int n - 0-500 + * @param boolean percent - true if show Top-n percent; false to show Top-n items + * @param boolean top10 - true if show Top 10 (items or percent), false to show Bottom N (items or percent) + */ + fun setTop10(n: Int, percent: Boolean, top10: Boolean) { + if (n == 0) { + fTop = false + fTop10 = false + fPercent = false + wTop10 = 0 + // TODO: set fSimple1? remove dopers?? + } else if (n > 0 && n <= 500) { + fTop = top10 + fTop10 = true + wTop10 = n.toShort() + fPercent = percent + fSimple1 = false // true if 1st condition is simple equality + fSimple2 = false // true if 2nd condition is simple equality + doper1 = IEEEDoper(byteArrayOf(4, 6, 0, 0, 0, 0, 0, 0, 0, 0)) + (doper1 as IEEEDoper).setVal(n.toDouble()) + doper2 = UnusedDoper(byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + } else + Logger.logErr("AutoFilter.setTop10: value $n must be between 0 and 500") + update() + } + + /** + * sets this AutoFilter to filter all blank rows + */ + fun setFilterBlanks() { + fSimple1 = true + fSimple2 = false + fTop = false + fTop10 = false + fPercent = false + wTop10 = 0 + doper1 = NoBlanksDoper(byteArrayOf(14, 5, 0, 0, 0, 0, 0, 0, 0, 0)) + doper2 = UnusedDoper(byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + update() + } + + /** + * sets this AutoFilter to filter all non-blank rows + */ + fun setFilterNonBlanks() { + fSimple1 = true + fSimple2 = false + fTop = false + fTop10 = false + fPercent = false + wTop10 = 0 + doper1 = AllBlanksDoper(byteArrayOf(12, 2, 0, 0, 0, 0, 0, 1, 0, 0)) + doper2 = UnusedDoper(byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + update() + } + + + /** + * Doper (Database OPER Structure) are parsed definitions of the AutoFilter + * + * + * 10-byte parsed definitions that appear in the Custom AutoFilter dialog box + * + * There are several sub-types of Dopers: + *

                  String + *

                  IEEE Floating Point + *

                  RK + *

                  Error or Boolean + *

                  All Blanks + *

                  All non-blanksfs + *

                  Unused -- placeholder doper + * + * @see Excel bible page 284 + */ + private open inner class Doper protected constructor(rec: ByteArray) { + var vt: Byte = 0 + var grbitSign: Byte = 0 + var record: ByteArray + internal set + + /** + * Returns the comparison operator for this doper, ie '>' '=', etc + * + * @return + */ + /** + * sets the custom comparison operator to one of: + *

                  "<" + *

                  "=" + *

                  "<=" + *

                  ">" + *

                  "<>" + *

                  ">=" + * + * @param String op - custom operator + */ + var comparisonOperator: String + get() { + when (grbitSign) { + 1 -> return "<" + 2 -> return "=" + 3 -> return "<=" + 4 -> return ">" + 5 -> return "<>" + 6 -> return ">=" + } + return "" + } + set(op) { + if ("<" == op) + grbitSign = 1 + if ("=" == op) + grbitSign = 2 + if ("<=" == op) + grbitSign = 3 + if (">" == op) + grbitSign = 4 + if ("<>" == op) + grbitSign = 5 + if (">=" == op) + grbitSign = 6 + record[1] = grbitSign + } + + init { + vt = rec[0] + grbitSign = rec[1] // comparison code + record = rec + } + + override fun toString(): String? { + return null + } + } + + /** + * A doper representing an unused value/filter condition unused. + */ + private inner class UnusedDoper(rec: ByteArray) : Doper(rec) { + + override fun toString(): String? { + return "Unused" + } + + } + + /** + * A doper representing an RK number + * + * Dopers define an AutoFilter value using a 10-byte doperRec + *

                  For all dopers, doperRec[0]=vt or code + *

                  doperRec[1]=comparison operator + * + * For RK Dopers, + * doperRec[2]->[6] = rk number, 6-9= reserved + */ + private inner class RKDoper(rec: ByteArray) : Doper(rec) { + internal var `val`: Double = 0.toDouble() + + init { + val b = ByteArray(4) + System.arraycopy(record, 2, b, 0, 4) + `val` = Rk.parseRkNumber(b) // parse bytes in Rk-number format into a double value + } + + override fun toString(): String? { + return `val`.toString() + } + + /** + * set the double value for this Rk-type Doper record + * + * @param double d - double value to set + */ + fun setVal(d: Double) { + `val` = d + val b = Rk.getRkBytes(d) + System.arraycopy(b, 0, record, 2, 4) + } + + /** + * return the double value associated with this Rk-type Doper record + * + * @return double value + */ + fun getVal(): Double { + return `val` + } + } + + /** + * A doper representing a IEEE number + * + * Dopers define an AutoFilter value using a 10-byte doperRec + *

                  For all dopers, doperRec[0]=vt or code + *

                  doperRec[1]=comparison operator + * + * For IEEE Dopers, + * doperRec[2->9] = IEEE floating point number + */ + private inner class IEEEDoper + /** + * create an IEEEDoper Object from doper record bytes + * (10 bytes as part of the AutoFilter record) + * + * @param byte[] rec 10 byte doper record + */ + (rec: ByteArray) : Doper(rec) { + internal var `val`: Double = 0.toDouble() + + init { + val b = ByteArray(8) + System.arraycopy(record, 2, b, 0, 8) + `val` = ByteTools.eightBytetoLEDouble(b) // TODO: is this correct?? + } + + /** + * return the double value associated with this IEEE-type Doper record + * + * @return double value + */ + fun getVal(): Double { + return `val` + } + + /** + * set the double value for this IEEE-type Doper record + * + * @param double d - double value to set + */ + fun setVal(d: Double) { + `val` = d + val b = ByteTools.doubleToLEByteArray(`val`) + System.arraycopy(b, 0, record, 2, 8) + } + + override fun toString(): String? { + return `val`.toString() + } + } + + /** + * A doper representing a String + * + * Dopers define an AutoFilter value using a 10-byte doperRec + *

                  For all dopers, doperRec[0]=vt or code + *

                  doperRec[1]=comparison operator + * + * For String Dopers, + * doperRec[6]= cch or String length + *

                  The actual string data is appended to the end of the entire AutoFilter data record + *

                  + * NOTE: strings must be in normal or default encoding + */ + private inner class StringDoper(rec: ByteArray) : Doper(rec) { + internal var s: String + + /** + * returns cch, the length of this string data + * + * @return int + */ + val cch: Int + get() = record[6].toInt() + + /** + * returns the byte array representing the String referenced by this StringDoper + * + * @return byte[] + */ + val strBytes: ByteArray + get() = s.toByteArray() + + /** + * sets the string for this StringDoper from a byte array and an index into said array + *

                  amount to read from byteArray is stored in cch, bit 6 of the 10-byte doperRec + * + * @param byte[] rgch - source byte array for string + * @param int start - start index into the byte array + */ + fun setString(rgch: ByteArray?, start: Int) { + val cch = record[6].toInt() + val stringbytes = ByteArray(cch) + System.arraycopy(rgch!!, start, stringbytes, 0, cch) + s = String(stringbytes) + } + + /** + * s the string for this StringDoper from a String Object + * + * @param String s + */ + fun setString(s: String) { + this.s = s + val b = s.toByteArray() + record[6] = b.size.toByte() + } + + /** + * returns the String referenced by this StringDoper + */ + override fun toString(): String? { + return s + } + } + + /** + * A doper representing an Error or Boolean + * + * Dopers define an AutoFilter value using a 10-byte doperRec + *

                  For all dopers, doperRec[0]=vt or code + *

                  doperRec[1]=comparison operator + * + * For Error or Boolean Dopers, + * doperRec[2]= fError; + * if 0, doperRec[3]= boolean value + * if 1, doperRec[3]= Error value + * Error Values: + *

                  0 = #NULL! + *

                  0x7 = #DIV/0! + *

                  0xF = #VALUE! + *

                  0x17= #REF! + *

                  0x1D= #NAME? + *

                  0x24= #NUM! + *

                  0x2A= #N/A + */ + private inner class ErrorDoper(rec: ByteArray) : Doper(rec) { + /** + * returns the boolean value if this is a type Boolean Doper + * + * @return boolean + */ + var booleanVal: Boolean = false + internal set + internal var errVal: Int = 0 + + /** + * returns true if this is a Error Doper + * + * @return boolean true if this is a Error Doper + */ + val isErrVal: Boolean + get() = record[2].toInt() == 1 + + /** + * returns true if this is a Boolean Doper + * + * @return boolean true if this is a boolean Doper + */ + val isBooleanVal: Boolean + get() = record[2].toInt() == 0 + + init { + if (record[2].toInt() == 0) + // boolean doper + booleanVal = record[3].toInt() != 0 + else + errVal = record[3].toInt() // see above vals + } + + /** + * interprets the error code located in doperRec[2] into a String Error Value + * e.g. "#NULL!" + * + * @return String error Value + */ + fun getErrVal(): String { + if (record[2].toInt() == 1) { + when (errVal) { + 0 -> return "#NULL!" + 0x7 -> return "#DIV/0!" + 0xF -> return "#VALUE!" + 0x17 -> return "#REF!" + 0x1D -> return "#NAME?" + 0x24 -> return "#NUM!" + 0x2A -> return "#N/A" + } + } + return "" + } + + + /** + * returns a String representation of this Error or Boolean Doper + *

                  If this is an Error Doper, returns one of the Error Values + *

                  If this is a booelan Doper, returns "true" or "false" + * + * @return String representation + * @see AutoFilter.getErrVal + */ + override fun toString(): String? { + if (isErrVal) + return getErrVal() + return if (booleanVal) "true" else "false" + } + } + + /** + * A doper representing an all blanks selection. + *

                  bytes are unused other than identifier + */ + private inner class AllBlanksDoper(rec: ByteArray) : Doper(rec) { + + override fun toString(): String? { + return "All Blanks" + } + } + + /** + * A doper representing an all blanks selection. + *

                  bytes are unused other than identifier + */ + private inner class NoBlanksDoper(rec: ByteArray) : Doper(rec) { + + override fun toString(): String? { + return "No Blanks" + } + } + + companion object { + + private val serialVersionUID = -5228830347211523997L + + /** + * creates a new AutoFilter record + */ + // 20090630 KSC: don't use static PROTOTYPE_BYTES as changes are propogated + val prototype: XLSRecord? + get() { + val af = AutoFilter() + af.opcode = XLSConstants.AUTOFILTER + af.setData(af.PROTOTYPE_BYTES) + af.init() + return af + } + } + + +} diff --git a/src/main/java/io/starter/formats/XLS/BiffRec.java b/src/main/java/io/starter/formats/XLS/BiffRec.kt similarity index 64% rename from src/main/java/io/starter/formats/XLS/BiffRec.java rename to src/main/java/io/starter/formats/XLS/BiffRec.kt index 6c2eba5..f176e38 100644 --- a/src/main/java/io/starter/formats/XLS/BiffRec.java +++ b/src/main/java/io/starter/formats/XLS/BiffRec.kt @@ -20,320 +20,288 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.OpenXLS.CellRange; -import io.starter.formats.LEO.BlockByteReader; - -import java.util.List; +import io.starter.OpenXLS.CellRange +import io.starter.formats.LEO.BlockByteReader /** * To change the template for this generated type comment go to * Window>Preferences>Java>Code Generation>Code and Comments */ -public interface BiffRec { +interface BiffRec { // integrating Cell Methods - Object getInternalVal(); - - void setHyperlink(Hlink h); - - Hlink getHyperlink(); - - CellRange getMergeRange(); - - void setMergeRange(CellRange r); - - Row getRow(); + val internalVal: Any - String getFormatPattern(); + var hyperlink: Hlink - void copyFormat(BiffRec source); + var mergeRange: CellRange - Font getFont(); + var row: Row - Xf getXfRec(); + val formatPattern: String - boolean remove(boolean t); + val font: Font - Formula getFormulaRec(); + val xfRec: Xf - void setRow(Row r); + val formulaRec: Formula - void setRowNumber(int i); + var intVal: Int + val dblVal: Double - void setIxfe(int xf); + var booleanVal: Boolean - int getIntVal(); - - double getDblVal(); - - boolean getBooleanVal(); - - boolean isReadOnly(); - - /** - * set the DEBUG level - */ - void setDebugLevel(int b); + val isReadOnly: Boolean /** * return the record index of this object */ - int getRecordIndex(); + val recordIndex: Int /** - * adds a CONTINUE record to the array of CONTINUE records for this record, - * containing all data beyond the 8224 byte record size limit. + * returns the array of CONTINUE records for this record, containing all + * data beyond the 8224 byte record size limit. */ - void addContinue(Continue c); + + val continueVect: List<*> /** - * remove all Continue records + * get the WorkSheet for this record. */ - void removeContinues(); + val sheet: Boundsheet + + var workBook: WorkBook + + + val colNumber: Short + + var rowNumber: Int /** - * returns the array of CONTINUE records for this record, containing all - * data beyond the 8224 byte record size limit. + * get a string address for the cell based on row and col ie: "H22" */ - - List getContinueVect(); + val cellAddress: String /** - * returns whether this record has a CONTINUE record containing data beyond - * the 8228 byte record size limit. - *

                  - * XLSRecords can have 0 or more CONTINUE records. + * get a default "empty" data value for this record */ - boolean hasContinues(); + val defaultVal: Any /** - * set whether this record contains the value of the Cell. + * get the data type name for this record */ - void setIsValueForCell(boolean b); + val dataType: String /** - * associate this record with its Index record + * Get the value of the record as a Float. Value must be parseable as an + * Float or it will throw a NumberFormatException. */ - void setIndex(Index id); + val floatVal: Float /** - * Associate this record with a worksheet. First checks to see if there is - * already a cell with this address. + * Get the value of the record as a String. */ - void setSheet(Sheet b); + var stringVal: String /** - * get the WorkSheet for this record. + * get the ixfe */ - Boundsheet getSheet(); + var ixfe: Int - void setWorkBook(WorkBook wk); - WorkBook getWorkBook(); + var opcode: Short /** - * set the column + * Returns the length of this record, including the 4 header bytes */ - void setCol(short i); - - void setRowCol(int[] x); + var length: Int + var byteReader: BlockByteReader - short getColNumber(); - - int getRowNumber(); + var encryptedByteReader: BlockByteReader /** - * get a string address for the cell based on row and col ie: "H22" + * gets the full record bytes for this record including header bytes + * + * @return byte[] of all rec bytes */ - String getCellAddress(); + val bytes: ByteArray /** - * perform record initialization + * gets the record data merging any Continue record data. */ - void init(); + //** Thread Safing OpenXLS **// + var data: ByteArray /** - * get a default "empty" data value for this record + * Get the relative position within the data underlying the block vector + * represented by the BlockByteReader. + * + * @return relative position */ - Object getDefaultVal(); - /** - * get the data type name for this record + * Set the relative position within the data underlying the block vector + * represented by the BlockByteReader. + * + * + * In other words, this is the relative position used by the BlockByteReader + * to offset the Consumer's read position within the collection of Data + * Blocks. + * + * + * This may be an offset relative to the data in a file, or within a Storage + * contained in a file. + * + * + * The Workbook Storage for example will contain a non-contiguous collection + * of Blocks containing data from any number of positions in a file. + * + * + * This collection forms a contiguous span of bytes comprising an XLS + * Workbook. The XLSRecords within this span of bytes will set their + * relative position within this 'virtual' array. Thus the XLSRecord + * positions are relative to the order of bytes contained in the Block + * collection. The BOF record then is at offset 0 within the data of the + * first Block, even though the underlying data of this first Block may be + * anywhere on disk. + * + * @param pos */ - String getDataType(); + var offset: Int /** - * Get the value of the record as a Float. Value must be parseable as an - * Float or it will throw a NumberFormatException. + * @return */ - float getFloatVal(); - /** - * Get the value of the record as a String. + * @param streamer */ - String getStringVal(); + var streamer: ByteStreamer /** - * Get the value of the record as a String. + * @return */ - String getStringVal(String encoding); - - void setStringVal(String v); - - void setBooleanVal(boolean b); + /** + * set whether this record contains the value of the Cell. + */ + var isValueForCell: Boolean - void setIntVal(int v); + fun copyFormat(source: BiffRec) - void setDoubleVal(double v); + fun remove(t: Boolean): Boolean /** - * do any pre-streaming processing such as expensive index updates or other - * deferrable processing. + * set the DEBUG level */ - void preStream(); + fun setDebugLevel(b: Int) /** - * do any post-streaming cleanup such as expensive index updates or other - * deferrable processing. + * adds a CONTINUE record to the array of CONTINUE records for this record, + * containing all data beyond the 8224 byte record size limit. */ - void postStream(); + fun addContinue(c: Continue) /** - * set the XF (format) record for this rec + * remove all Continue records */ - void setXFRecord(); + fun removeContinues() /** - * set the XF (format) record for this rec + * returns whether this record has a CONTINUE record containing data beyond + * the 8228 byte record size limit. + * + * + * XLSRecords can have 0 or more CONTINUE records. */ - void setXFRecord(int i); + fun hasContinues(): Boolean /** - * get the ixfe + * associate this record with its Index record */ - int getIxfe(); - - - short getOpcode(); - - void setOpcode(short opcode); + fun setIndex(id: Index) /** - * Returns the length of this record, including the 4 header bytes + * Associate this record with a worksheet. First checks to see if there is + * already a cell with this address. */ - int getLength(); - - void setLength(int len); - - void setByteReader(BlockByteReader db); - - BlockByteReader getByteReader(); - - void setEncryptedByteReader(BlockByteReader db); - - BlockByteReader getEncryptedByteReader(); - - //** Thread Safing OpenXLS **// - void setData(byte[] b); + fun setSheet(b: Sheet) /** - * gets the full record bytes for this record including header bytes - * - * @return byte[] of all rec bytes + * set the column */ - byte[] getBytes(); + fun setCol(i: Short) + + fun setRowCol(x: IntArray) /** - * gets the record data merging any Continue record data. + * perform record initialization */ - byte[] getData(); + fun init() /** - * Gets the byte from the specified position in the record byte array. - * - * @param off - * @return + * Get the value of the record as a String. */ - byte getByteAt(int off); + fun getStringVal(encoding: String): String + + fun setDoubleVal(v: Double) /** - * Gets the byte from the specified position in the record byte array. - * - * @param off - * @return + * do any pre-streaming processing such as expensive index updates or other + * deferrable processing. */ - byte[] getBytesAt(int off, int len); + fun preStream() /** - * Set the relative position within the data underlying the block vector - * represented by the BlockByteReader. - *

                  - * In other words, this is the relative position used by the BlockByteReader - * to offset the Consumer's read position within the collection of Data - * Blocks. - *

                  - * This may be an offset relative to the data in a file, or within a Storage - * contained in a file. - *

                  - * The Workbook Storage for example will contain a non-contiguous collection - * of Blocks containing data from any number of positions in a file. - *

                  - * This collection forms a contiguous span of bytes comprising an XLS - * Workbook. The XLSRecords within this span of bytes will set their - * relative position within this 'virtual' array. Thus the XLSRecord - * positions are relative to the order of bytes contained in the Block - * collection. The BOF record then is at offset 0 within the data of the - * first Block, even though the underlying data of this first Block may be - * anywhere on disk. - * - * @param pos + * do any post-streaming cleanup such as expensive index updates or other + * deferrable processing. */ - void setOffset(int pos); + fun postStream() /** - * Get the relative position within the data underlying the block vector - * represented by the BlockByteReader. - * - * @return relative position + * set the XF (format) record for this rec */ - int getOffset(); + fun setXFRecord() /** - * @return + * set the XF (format) record for this rec */ - ByteStreamer getStreamer(); + fun setXFRecord(i: Int) /** - * @param streamer + * Gets the byte from the specified position in the record byte array. + * + * @param off + * @return */ - void setStreamer(ByteStreamer streamer); + fun getByteAt(off: Int): Byte /** + * Gets the byte from the specified position in the record byte array. + * + * @param off * @return */ - boolean isValueForCell(); + fun getBytesAt(off: Int, len: Int): ByteArray /** * Dumps this record as a human-readable string. - * This method's output is more verbose than that of {@link #toString()}. + * This method's output is more verbose than that of [.toString]. * It generally includes a hex dump of the record's contents. */ - String toHexDump(); + fun toHexDump(): String /** - * collect cell change listeners and fire cell change event upon, well ... + * collect cell change listeners and fire cell change event upon, well ... * @param t * 20080204 KSC - - public void addCellChangeListener(CellChangeListener t); - public void removeCellChangeListener(CellChangeListener t); - // public void fireCellChangeEvent(); - public void setCachedValue(Object newValue); - public Object getCachedValue(); + * + * public void addCellChangeListener(CellChangeListener t); + * public void removeCellChangeListener(CellChangeListener t); + * // public void fireCellChangeEvent(); + * public void setCachedValue(Object newValue); + * public Object getCachedValue(); */ } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Blank.java b/src/main/java/io/starter/formats/XLS/Blank.java deleted file mode 100644 index a3f972e..0000000 --- a/src/main/java/io/starter/formats/XLS/Blank.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; - - -/** - * Blank: a blank cell value 0x201
                  - *

                  - * Blank records define a blank cell. The rw field defines row number (0 based) - * and the col field defines column number. - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       rw          2           Row
                  - * 6       col         2           Column
                  - * 8       ixfe        2           Index to the XF record
                  - * 

                  - **/ -public final class Blank - extends XLSCellRecord { - - - private static final long serialVersionUID = -3847009755105117050L; - - // return a blank string val - public String getStringVal() { - return ""; - } - - //protected byte[] BLANK_CELL_BYTES = { 0, 0, 0, 0, 0, 0}; - - public static XLSRecord getPrototype() { - Blank bl = new Blank(); - bl.setData(new byte[]{0, 0, 0, 0, 0, 0}); - return bl; - } - - Blank() { - this(new byte[]{0, 0, 0, 0, 0, 0}); - /* setData(BLANK_CELL_BYTES); - setOpcode(BLANK); - setLength((short)6); - this.init(); -*/ - } - - - /** - * Provide constructor which automatically - * sets the body data and header info. This - * is needed by Mulblank which creates the Blanks without - * the benefit of WorkBookFactory.parseRecord(). - */ - Blank(byte[] b) { - setData(b); - setOpcode(BLANK); - setLength((short) 6); - this.init(); - } - - - public void init() { - super.init(); - int pos = 4; - super.initRowCol(); - ixfe = ByteTools.readShort(this.getByteAt(pos++), this.getByteAt(pos++)); - this.setIsValueForCell(true); - this.isBlank = true; - } - - public void setCol(int i) { - if (this.isValueForCell) { - this.getData(); - if (data == null) setData(new byte[]{0, 0, 0, 0, 0, 0}); - byte[] c = ByteTools.shortToLEBytes((short) i); - System.arraycopy(c, 0, this.getData(), 2, 2); - } - col = (short) i; - } - - /** - * set the row - */ - public void setRow(int i) { - if (this.isValueForCell) { - this.getData(); - if (data == null) setData(new byte[]{0, 0, 0, 0, 0, 0}); - byte[] r = ByteTools.shortToLEBytes((short) i); - System.arraycopy(r, 0, this.getData(), 0, 2); - } - rw = i; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Blank.kt b/src/main/java/io/starter/formats/XLS/Blank.kt new file mode 100644 index 0000000..7d32c5d --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Blank.kt @@ -0,0 +1,117 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools + + +/** + * **Blank: a blank cell value 0x201**

                  + * + * + * Blank records define a blank cell. The rw field defines row number (0 based) + * and the col field defines column number. + * + *
                  + * offset  name        size    contents
                  + * ---
                  + * 4       rw          2           Row
                  + * 6       col         2           Column
                  + * 8       ixfe        2           Index to the XF record
                  +
                  * + */ +class Blank +/** + * Provide constructor which automatically + * sets the body data and header info. This + * is needed by Mulblank which creates the Blanks without + * the benefit of WorkBookFactory.parseRecord(). + */ +@JvmOverloads internal constructor(b: ByteArray = byteArrayOf(0, 0, 0, 0, 0, 0)) : XLSCellRecord() { + + // return a blank string val + override var stringVal: String + get() = "" + set + + + init { + setData(b) + opcode = XLSConstants.BLANK + length = 6.toShort() + this.init() + } + + + override fun init() { + super.init() + var pos = 4 + super.initRowCol() + ixfe = ByteTools.readShort(this.getByteAt(pos++).toInt(), this.getByteAt(pos++).toInt()).toInt() + this.isValueForCell = true + this.isBlank = true + } + + fun setCol(i: Int) { + if (this.isValueForCell) { + this.getData() + if (data == null) setData(byteArrayOf(0, 0, 0, 0, 0, 0)) + val c = ByteTools.shortToLEBytes(i.toShort()) + System.arraycopy(c, 0, this.getData()!!, 2, 2) + } + colNumber = i.toShort() + } + + /** + * set the row + */ + fun setRow(i: Int) { + if (this.isValueForCell) { + this.getData() + if (data == null) setData(byteArrayOf(0, 0, 0, 0, 0, 0)) + val r = ByteTools.shortToLEBytes(i.toShort()) + System.arraycopy(r, 0, this.getData()!!, 0, 2) + } + rw = i + } + + companion object { + + + private val serialVersionUID = -3847009755105117050L + + //protected byte[] BLANK_CELL_BYTES = { 0, 0, 0, 0, 0, 0}; + + val prototype: XLSRecord? + get() { + val bl = Blank() + bl.setData(byteArrayOf(0, 0, 0, 0, 0, 0)) + return bl + } + } + +}/* setData(BLANK_CELL_BYTES); + setOpcode(BLANK); + setLength((short)6); + this.init(); +*/ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Bof.java b/src/main/java/io/starter/formats/XLS/Bof.java deleted file mode 100644 index 77ca333..0000000 --- a/src/main/java/io/starter/formats/XLS/Bof.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.formats.LEO.InvalidFileException; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - - -/** - *
                  Bof: Beginning of File Stream 0x809
                  - * - * Marks the beginning of an XLS file Stream including Boundsheets - * - *

                  - * offset  name        size    contents
                  - * ---
                  - * 0       vers        1       version:
                  - * 1       bof         1       0x09
                  - * ...
                  - *
                  - * 

                  - */ -public final class Bof extends UnencryptedXLSRecord { - - - private static final long serialVersionUID = 3005631881544437570L; - short grbit; - String xlsver = ""; - int oldlen = -1; - - public String toString() { - return super.toString() + " lbplypos: " + this.getLbPlyPos(); - } - - /** - * Set the offset for this BOF - */ - public void setOffset(int s) { - super.setOffset(s); - if (worksheet != null) - if (isSheetBof() || isVBModuleBof() || (worksheet.getMyBof() == this)) - worksheet.setLbPlyPos(this.getLbPlyPos()); - } - - boolean isValidBIFF8() { - return oldlen == 20; - } - - String getXLSVersionString() { - return xlsver; - } - - /** - * Initialize the BOF record - */ - public void init() { - super.init(); - this.getData(); - grbit = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - int compat = ByteTools.readInt(this.getByteAt(12), this.getByteAt(13), this.getByteAt(14), this.getByteAt(15)); // 1996 - xlsver = compat + ""; - oldlen = this.getLength(); - if (oldlen < 16) { - Logger.logErr("Not Excel '97 (BIFF8) or later version. Unsupported file format."); - throw new InvalidFileException("InvalidFileException: Not Excel '97 (BIFF8) or later version. Unsupported file format."); - } - } - - /** - * this is equal to the lbPlyPos stored in - * the Boundsheet associated with this Bof - */ - long getLbPlyPos() { - if (!isValidBIFF8()) return offset + 8; - return offset; - } - - /** - * @return Returns the sheetbof. - */ - public boolean isSheetBof() { - return (grbit & 0x10) == 0x10; - } - - public boolean isVBModuleBof() { - return (grbit & 0x06) == 0x06; - - } - - /** - * @return Returns the sheetbof. - */ - public boolean isChartBof() { - return (grbit & 0x20) == 0x20; - } - - /** - * @param sheetbof The sheetbof to set. - */ - public void setSheetBof() { - grbit = 0x10; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Bof.kt b/src/main/java/io/starter/formats/XLS/Bof.kt new file mode 100644 index 0000000..0715555 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Bof.kt @@ -0,0 +1,119 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.formats.LEO.InvalidFileException +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + *
                  **Bof: Beginning of File Stream 0x809**

                  + * + * Marks the beginning of an XLS file Stream including Boundsheets + * + * + *
                  + * offset  name        size    contents
                  + * ---
                  + * 0       vers        1       version:
                  + * 1       bof         1       0x09
                  + * ...
                  + *
                  +
                  * + */ +class Bof : UnencryptedXLSRecord() { + internal var grbit: Short = 0 + internal var xlsVersionString = "" + internal var oldlen = -1 + + /** + * Set the offset for this BOF + */ + override var offset: Int + get + set(s) { + super.setOffset(s) + if (worksheet != null) + if (isSheetBof || isVBModuleBof || worksheet!!.myBof == this) + worksheet!!.lbPlyPos = this.lbPlyPos + } + + internal val isValidBIFF8: Boolean + get() = oldlen == 20 + + /** + * this is equal to the lbPlyPos stored in + * the Boundsheet associated with this Bof + */ + internal val lbPlyPos: Long + get() = if (!isValidBIFF8) (offset + 8).toLong() else offset.toLong() + + /** + * @return Returns the sheetbof. + */ + val isSheetBof: Boolean + get() = grbit and 0x10 == 0x10 + + val isVBModuleBof: Boolean + get() = grbit and 0x06 == 0x06 + + /** + * @return Returns the sheetbof. + */ + val isChartBof: Boolean + get() = grbit and 0x20 == 0x20 + + override fun toString(): String { + return super.toString() + " lbplypos: " + this.lbPlyPos + } + + /** + * Initialize the BOF record + */ + override fun init() { + super.init() + this.getData() + grbit = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + val compat = ByteTools.readInt(this.getByteAt(12), this.getByteAt(13), this.getByteAt(14), this.getByteAt(15)) // 1996 + xlsVersionString = compat.toString() + "" + oldlen = this.length + if (oldlen < 16) { + Logger.logErr("Not Excel '97 (BIFF8) or later version. Unsupported file format.") + throw InvalidFileException("InvalidFileException: Not Excel '97 (BIFF8) or later version. Unsupported file format.") + } + } + + /** + * @param sheetbof The sheetbof to set. + */ + fun setSheetBof() { + grbit = 0x10 + } + + companion object { + + + private val serialVersionUID = 3005631881544437570L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Book.java b/src/main/java/io/starter/formats/XLS/Book.kt similarity index 73% rename from src/main/java/io/starter/formats/XLS/Book.java rename to src/main/java/io/starter/formats/XLS/Book.kt index 6c684cc..cd297e7 100644 --- a/src/main/java/io/starter/formats/XLS/Book.java +++ b/src/main/java/io/starter/formats/XLS/Book.kt @@ -26,24 +26,31 @@ * To change the template for this generated file go to * Window>Preferences>Java>Code Generation>Code and Comments */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import java.io.OutputStream; +import java.io.OutputStream /** * To change the template for this generated type comment go to * Window>Preferences>Java>Code Generation>Code and Comments */ -public interface Book { +interface Book { + val continueHandler: ContinueHandler - BiffRec addRecord(BiffRec b, boolean y); + val streamer: ByteStreamer - ContinueHandler getContinueHandler(); + val fileName: String - ByteStreamer getStreamer(); + /** + * get a handle to the factory + */ + var factory: WorkBookFactory - void setFirstBof(Bof b); + + fun addRecord(b: BiffRec, y: Boolean): BiffRec + + fun setFirstBof(b: Bof) /** * Stream the Book bytes to out @@ -51,32 +58,23 @@ public interface Book { * @param out * @return */ - int stream(OutputStream out); + fun stream(out: OutputStream): Int /** * set the Debug level */ - void setDebugLevel(int i); - - String toString(); - - String getFileName(); - - /** - * get a handle to the factory - */ - WorkBookFactory getFactory(); + fun setDebugLevel(i: Int) - void setFactory(WorkBookFactory fact); + override fun toString(): String /** * Dec 15, 2010 * * @param rec */ - Boundsheet getSheetFromRec(BiffRec rec, Long l); + fun getSheetFromRec(rec: BiffRec, l: Long?): Boundsheet - /** set readiness -- is it done parsing? */ + /** set readiness -- is it done parsing? */ // public boolean isReady(); // public void setReady(boolean t); diff --git a/src/main/java/io/starter/formats/XLS/BookBool.java b/src/main/java/io/starter/formats/XLS/BookBool.kt similarity index 62% rename from src/main/java/io/starter/formats/XLS/BookBool.java rename to src/main/java/io/starter/formats/XLS/BookBool.kt index 493a0c6..ff46a3e 100644 --- a/src/main/java/io/starter/formats/XLS/BookBool.java +++ b/src/main/java/io/starter/formats/XLS/BookBool.kt @@ -20,36 +20,41 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger /** - * BOOKBOOL: Additional Workspace Information (DAh)
                  - *

                  + * **BOOKBOOL: Additional Workspace Information (DAh)**

                  + * + * * This record stores information about workspace settings - *

                  + *
                  + * 
                    * offset  name            size    contents
                    * ---
                    * 4       grbit          2      Option flags
                    *
                    * See book for details of grbit flags, page 425
                    *
                  - * 

                  +
                  * */ -public final class BookBool extends io.starter.formats.XLS.XLSRecord { - - private static final long serialVersionUID = -4544323710670598072L; - short grbit; +class BookBool : io.starter.formats.XLS.XLSRecord() { + internal var grbit: Short = 0 - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + override fun init() { + super.init() + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) if (DEBUGLEVEL > 5) - Logger.logInfo("BOOKBOOL: " + ((grbit == 0) ? "Save External Links" : "Don't Save External Links")); + Logger.logInfo("BOOKBOOL: " + if (grbit.toInt() == 0) "Save External Links" else "Don't Save External Links") + } + + companion object { + + private val serialVersionUID = -4544323710670598072L } diff --git a/src/main/java/io/starter/formats/XLS/BookProtectionManager.java b/src/main/java/io/starter/formats/XLS/BookProtectionManager.kt similarity index 70% rename from src/main/java/io/starter/formats/XLS/BookProtectionManager.java rename to src/main/java/io/starter/formats/XLS/BookProtectionManager.kt index 9b7c634..89ce2d9 100644 --- a/src/main/java/io/starter/formats/XLS/BookProtectionManager.java +++ b/src/main/java/io/starter/formats/XLS/BookProtectionManager.kt @@ -20,28 +20,23 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import java.io.Serializable; +import java.io.Serializable /** * Coordinates the various records involved in book-level protection. */ -public class BookProtectionManager - extends ProtectionManager implements Serializable { - /** - * The workbook whose protection state this instance manages. - */ - private WorkBook book; - - public BookProtectionManager(WorkBook target) { - book = target; - } +class BookProtectionManager( + /** + * The workbook whose protection state this instance manages. + */ + private var book: WorkBook?) : ProtectionManager(), Serializable { /** * clear out object references in prep for closing workbook */ - public void close() { - book = null; + override fun close() { + book = null } } diff --git a/src/main/java/io/starter/formats/XLS/Boolerr.java b/src/main/java/io/starter/formats/XLS/Boolerr.java deleted file mode 100644 index 456048e..0000000 --- a/src/main/java/io/starter/formats/XLS/Boolerr.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; - - -/** - * Boolerr: BiffRec Value, Boolean or Error (0x205)
                  - * Describes a cell that contains a constant Boolean or error - * value. - * - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       rw          2       Row
                  - * 6       col         2       Column
                  - * 8       ixfe        2       Index to XF record
                  - * 10      bBoolErr    1       Boolean value or error value
                  - * 11      fError      1       Boolean/error flag (0 = Boolean, 1 = Error)
                  - *
                  - * Boolean vals are 1 = true, 0 = false.
                  - *
                  - * 

                  - */ - -public final class Boolerr - extends XLSCellRecord { - - - private static final long serialVersionUID = 39663492256953223L; - private boolean val, iserr = false; - private byte errorval; - - /** - * Returns whether the value is a Boolean or - * an error. - */ - boolean getIsErr() { - return iserr; - } - - /** - * get the int val - */ - public int getIntVal() { - if (this.getBooleanVal()) return 1; - return 0; - } - - /** - * return boolean value in float version 0 or 1 - */ - public float getFloatVal() { - if (this.getBooleanVal()) return 1; - return 0; - } - - /** - * return the boolean value in double version 0 or 1 - */ - public double getDblVal() { - if (this.getBooleanVal()) return 1; - return 0; - } - - /** - * get the String val - */ - public String getStringVal(String encoding) { - if (this.getIsErr()) { - return this.getErrorCode(); - } - return String.valueOf(val); - } - - /** - * Returns the valid error code for this valrec - */ - public String getErrorCode() { - if (!this.getIsErr()) { - return String.valueOf(iserr); - } - String retval = ""; - if ((errorval & 0x0) == 0x0) retval = "#NULL!"; - if ((errorval & 0x7) == 0x7) retval = "#DIV/0!"; - if ((errorval & 0xF) == 0xF) retval = "#VALUE!"; - if ((errorval & 0x17) == 0x17) retval = "#REF!"; - if ((errorval & 0x1D) == 0x1D) retval = "#NAME?"; - if ((errorval & 0x24) == 0x24) retval = "#NUM!"; - if ((errorval & 0x2A) == 0x2A) retval = "#N/A"; - return retval; - } - - /** - * get the String val - */ - public String getStringVal() { - return this.getStringVal(null); // char encoding of true/false should be irrelevant - - //! NOPE, 'cause this also has error codes in it -NR 10/03 - } - - /** - * Get the value of the record as a Boolean. - * Value must be parseable as a Boolean. - */ - public boolean getBooleanVal() { - return val; - } - - public void setBooleanVal(boolean newv) { - if (newv) this.getData()[6] = 1; - else this.getData()[6] = 0; - this.val = newv; - } - - public void init() { - super.init(); - // get the row information - rw = (int) ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - col = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - ixfe = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - // get the value - int num = (int) this.getByteAt(6); - if (num == 0) val = false; - if (num == 1) val = true; - num = (int) this.getByteAt(7); - if (num == 0) iserr = false; - if (num == 1) iserr = true; - errorval = this.getByteAt(6); - this.setIsValueForCell(true); - if (!iserr) { - isBoolean = true; - } else { - isString = true; - } - } - - // these bytes are from a simple chart, 2 ranges a1:a2, b1:b2 - all default. Likely will need to be modified - // when we figure out wtf. - private byte[] PROTOTYPE_BYTES = {0, 0, 0, 0, 21, 0, 0, 0}; - - protected static XLSRecord getPrototype() { - Boolerr be = new Boolerr(); - be.setOpcode(BOOLERR); - be.setData(be.PROTOTYPE_BYTES); - be.init(); - return be; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Boolerr.kt b/src/main/java/io/starter/formats/XLS/Boolerr.kt new file mode 100644 index 0000000..acde690 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Boolerr.kt @@ -0,0 +1,168 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools + + +/** + * **Boolerr: BiffRec Value, Boolean or Error (0x205)**

                  + * Describes a cell that contains a constant Boolean or error + * value. + * + * + *
                  + * offset  name        size    contents
                  + * ---
                  + * 4       rw          2       Row
                  + * 6       col         2       Column
                  + * 8       ixfe        2       Index to XF record
                  + * 10      bBoolErr    1       Boolean value or error value
                  + * 11      fError      1       Boolean/error flag (0 = Boolean, 1 = Error)
                  + *
                  + * Boolean vals are 1 = true, 0 = false.
                  + *
                  +
                  * + */ + +class Boolerr : XLSCellRecord() { + private var `val`: Boolean = false + /** + * Returns whether the value is a Boolean or + * an error. + */ + internal var isErr = false + private set + private var errorval: Byte = 0 + + /** + * get the int val + */ + override var intVal: Int + get() = if (this.booleanVal) 1 else 0 + set + + /** + * return boolean value in float version 0 or 1 + */ + override val floatVal: Float + get() = if (this.booleanVal) 1f else 0f + + /** + * return the boolean value in double version 0 or 1 + */ + override val dblVal: Double + get() = if (this.booleanVal) 1.0 else 0.0 + + /** + * Returns the valid error code for this valrec + */ + val errorCode: String + get() { + if (!this.isErr) { + return isErr.toString() + } + var retval = "" + if (errorval and 0x0 == 0x0) retval = "#NULL!" + if (errorval and 0x7 == 0x7) retval = "#DIV/0!" + if (errorval and 0xF == 0xF) retval = "#VALUE!" + if (errorval and 0x17 == 0x17) retval = "#REF!" + if (errorval and 0x1D == 0x1D) retval = "#NAME?" + if (errorval and 0x24 == 0x24) retval = "#NUM!" + if (errorval and 0x2A == 0x2A) retval = "#N/A" + return retval + } + + /** + * get the String val + */ + override// char encoding of true/false should be irrelevant - + //! NOPE, 'cause this also has error codes in it -NR 10/03 + var stringVal: String? + get() = this.getStringVal(null) + set + + /** + * Get the value of the record as a Boolean. + * Value must be parseable as a Boolean. + */ + override var booleanVal: Boolean + get() = `val` + set(newv) { + if (newv) + this.getData()[6] = 1 + else + this.getData()[6] = 0 + this.`val` = newv + } + + // these bytes are from a simple chart, 2 ranges a1:a2, b1:b2 - all default. Likely will need to be modified + // when we figure out wtf. + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 21, 0, 0, 0) + + /** + * get the String val + */ + override fun getStringVal(encoding: String?): String? { + return if (this.isErr) { + this.errorCode + } else `val`.toString() + } + + override fun init() { + super.init() + // get the row information + rw = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() + colNumber = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + ixfe = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()).toInt() + // get the value + var num = this.getByteAt(6).toInt() + if (num == 0) `val` = false + if (num == 1) `val` = true + num = this.getByteAt(7).toInt() + if (num == 0) isErr = false + if (num == 1) isErr = true + errorval = this.getByteAt(6) + this.isValueForCell = true + if (!isErr) { + isBoolean = true + } else { + isString = true + } + } + + companion object { + + + private val serialVersionUID = 39663492256953223L + + val prototype: XLSRecord? + get() { + val be = Boolerr() + be.opcode = XLSConstants.BOOLERR + be.setData(be.PROTOTYPE_BYTES) + be.init() + return be + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/BottomMargin.java b/src/main/java/io/starter/formats/XLS/BottomMargin.kt similarity index 53% rename from src/main/java/io/starter/formats/XLS/BottomMargin.java rename to src/main/java/io/starter/formats/XLS/BottomMargin.kt index 570b29f..8db97b1 100644 --- a/src/main/java/io/starter/formats/XLS/BottomMargin.java +++ b/src/main/java/io/starter/formats/XLS/BottomMargin.kt @@ -20,42 +20,44 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** * Record specifying the bottom margin of the sheet for printing. */ -public class BottomMargin - extends XLSRecord { - private static final long serialVersionUID = -3649192673573344145L; +class BottomMargin : XLSRecord() { - double margin; + internal var margin: Double = 0.toDouble() - public BottomMargin() { - this.setOpcode(BOTTOMMARGIN); - margin = 1.0; // default - setData(ByteTools.doubleToLEByteArray(margin)); + init { + this.opcode = XLSConstants.BOTTOMMARGIN + margin = 1.0 // default + setData(ByteTools.doubleToLEByteArray(margin)) } - public void init() { - super.init(); + override fun init() { + super.init() - margin = ByteTools.eightBytetoLEDouble(getBytesAt(0, 8)); + margin = ByteTools.eightBytetoLEDouble(getBytesAt(0, 8)!!) } - public void setSheet(Sheet sheet) { - super.setSheet(sheet); - ((Boundsheet) sheet).addPrintRec(this); + override fun setSheet(sheet: Sheet?) { + super.setSheet(sheet) + (sheet as Boundsheet).addPrintRec(this) } - public double getMargin() { - return margin; + fun getMargin(): Double { + return margin } - public void setMargin(double value) { - margin = value; - setData(ByteTools.doubleToLEByteArray(value)); + fun setMargin(value: Double) { + margin = value + setData(ByteTools.doubleToLEByteArray(value)) + } + + companion object { + private val serialVersionUID = -3649192673573344145L } } diff --git a/src/main/java/io/starter/formats/XLS/Boundsheet.java b/src/main/java/io/starter/formats/XLS/Boundsheet.java deleted file mode 100644 index 6e31656..0000000 --- a/src/main/java/io/starter/formats/XLS/Boundsheet.java +++ /dev/null @@ -1,5582 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.*; -import io.starter.formats.OOXML.*; -import io.starter.formats.XLS.charts.Chart; -import io.starter.formats.XLS.charts.Fontx; -import io.starter.formats.XLS.charts.GenericChartObject; -import io.starter.formats.XLS.formulas.FormulaParser; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.formats.XLS.formulas.PtgRef; -import io.starter.formats.cellformat.CellFormatFactory; -import io.starter.toolkit.*; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -import java.io.*; -import java.util.*; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -/** - * Boundsheet: WorkSheet Information 0x85
                  - *

                  - * This record stores the sheet name, type and stream position. - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       lbPlyPos    4       Stream position of the BOF for the sheet
                  - * 8       grbit       2       Option flags
                  - * 10      cch         1       Length of sheet name
                  - * 11      grbitChr    1       Compressed/Uncompressed Unicode
                  - * 12      rgch        var     Sheet name
                  - * 

                  - *

                  - * ---- File Layout ---- - *

                  - * BOUNDSHEET - * Bof - * Index - * Row1 is first index in DBCELL - * Row2 is the offset of any DBCELLS - * Row... - * CELLREC - * CELLREC - * CELLREC - *

                  - * DBCELL rgdb rg rg rg rg - * ROWS - * CELLS - * DBCELL rg rg rg - * ... - * EOF - *

                  - * ---------------------- - *

                  - * lbplypos used to be the most important thing on earth. now it is not an issue. - * - * @see WorkBook - * @see Index - * @see Dbcell - * @see Row - * @see Cell - * @see XLSRecord - */ -public final class Boundsheet extends XLSRecord implements Sheet { - - /** - * - */ - private static final long serialVersionUID = 8977216410574107840L; - private Bof mybof = null; - private Eof myeof = null; - private String sheetname = ""; - private String sheetHash = ""; - private Map rows = new LinkedHashMap(); - - private SortedMap cellsByRow = new TreeMap( - new CellAddressible.RowMajorComparator()); - - private SortedMap cellsByCol = new TreeMap( - new CellAddressible.ColumnMajorComparator()); - - private Map arrFormulaLocs = new HashMap(); // use - // for - // trapping - // array - // formula - // refs - // to - // original - // cell - // reference - // [OOXML - // Array - // Formulas] - protected AbstractList arrayformulas = new ArrayList(); // trap - // array - // formulas - // that - // span - // one - // or - // more - // cells - private SortedMap colinfos = new TreeMap( - new ColumnRange.Comparator()); - private AbstractList SheetRecs = new ArrayList(); - private AbstractList localrecs; - - /** - * Records containting various bits of print setup. - */ - private List printRecs; - - // These records are for boundsheet transferral to a new - // book. - private List transferXfs = new ArrayList(); - private List transferFonts = new ArrayList(); - public HashMap imageMap = new HashMap(); - private List charts = new ArrayList(); // chart - // specific - // for - // this - // sheet - private long lbPlyPos; - private short grbit; - private byte cch; - private byte grbitChr; - // private int sheetnum; - private Index myidx; - private BiffRec lastCell; - private Row lastRow = null; - private Window2 win2; - private Scl scl; - private Pane pane; - private Dval dval; - - protected Headerrec hdr; - protected Footerrec ftr; - private WsBool wsbool; - private Guts guts; - - public int lastObjId = 0; // 20100210 - // KSC: - // track - // last-used - // Object - // id - // for - // this - // sheet - - @Override - public Headerrec getHeader() { - return this.hdr; - } - - @Override - public void setHeader(BiffRec h) { - this.hdr = (Headerrec) h; - } - - @Override - public Footerrec getFooter() { - return this.ftr; - } - - @Override - public void setFooter(BiffRec ftr) { - this.ftr = (Footerrec) ftr; - } - - public void setDvalRec(Dval d) { - dval = d; - } - - public Dval getDvalRec() { - return dval; - } - - // sheet types from grbit field offset 0 - static final byte SHEET_DIALOG = 0x00; - static final byte XL4_MACRO = 0x01; - static final byte CHART = 0x02; - static final byte VBMODULE = 0x06; - - // hidden states from grbit field offset 1 - public static final byte VISIBLE = 0x00; - public static final byte HIDDEN = 0x01; - public static final byte VERY_HIDDEN = 0x02; - - private boolean formulaShiftInclusive = false; - private AbstractList cond_formats = new Vector(); - private AbstractList autoFilters = new Vector(); // 20100111 - // KSC - - // OOXML use: stores external sheet-level OOXML objects - private AbstractList ooxmlObjects = new ArrayList(); - - // OOXML-specific sheet attributes TODO: translate to Excel - // 2003 version IF POSSIBLE - private boolean thickBottom = false; - private boolean thickTop = false; - private boolean zeroHeight = false; - private boolean customHeight = false; - private double defaultRowHeight = 12.75; // measured - // in - // point - // size - private float defaultColWidth = (float) -1.0; - private DefColWidth defColWidth = null; - private HashMap ooxmlShapes = null; // stores - // OOXML - // shapes - private SheetView sheetview = null; - private SheetPr sheetPr = null; - private io.starter.formats.OOXML.AutoFilter ooautofilter = null; - private SheetProtectionManager protector; - - private transient HashMap sheetNameRecs = new HashMap(); // sheet - // scoped - // names - List mc = new CompatibleVector(); - private Selection lastselection = null; - - /** - * Gets this sheet's SheetProtectionManager. - */ - public SheetProtectionManager getProtectionManager() { - if (protector == null) - protector = new SheetProtectionManager(this); - return protector; - } - - /** - * get the last BiffRec added to this sheet - */ - @Override - public BiffRec getLastCell() { - return lastCell; - } - - /* - * TODO: find calls to this method which really need to be - * calling 'assembleSheetRecs() -jm 8/05 - */ - @Override - public List getSheetRecs() { - return SheetRecs; - } - - /** - * Insert an image into the WorkBook - * - * @param im - */ - public void insertImage(ImageHandle im) { - insertImage(im, false); - } - - /** - * Please add comments for this method - * - * @param bAddUnconditionally - */ - public void insertImage(ImageHandle im, boolean bAddUnconditionally) { - MSODrawingGroup msodg = this.wkbook.getMSODrawingGroup(); - MSODrawing msoDrawing = (MSODrawing) MSODrawing.getPrototype(); - msoDrawing.setSheet(this); - msoDrawing.setCoords(im.getCoords()); - - im.setMsgdrawing(msoDrawing); // 20070924 KSC: link 2 actual msodrawing - // that describes this image for setting - // bounds, etc. - int insertIndex = -1; - Obj obj = (Obj) Obj.getPrototype(); - // now add to proper place in stream - if (msodg != null) { // already have drawing records; just add to - // records + update msodg - insertIndex = this.getIndexOf(MSODRAWINGSELECTION); - if (insertIndex < 0) - insertIndex = this.getIndexOf(WINDOW2); - if (msodg.getMsoHeaderRec(this) == null) // handle case of multiple - // sheets- each needs - // it's own mso header - // ... - msoDrawing.setIsHeader(); - } else { // No images present in workbook, must add appropriate - // records - // Create new msodg rec - this.wkbook.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup - .getPrototype()); - msodg = this.wkbook.getMSODrawingGroup(); - msodg.initNewMSODrawingGroup(); // generate and add required records - // for drawing records - // also add 1st portion for drawing rec - msoDrawing.setIsHeader(); - // insertion point for new msodrawing rec - insertIndex = getIndexOf(DIMENSIONS) + 1; - } - if (insertIndex > 0) { // should! then have a drawing record to insert - // 20071120 KSC: retrieve idx in order to reuse/link to - // existing image bytes if duplicating images - int idx = msodg.addImage(im.getImageBytes(), im - .getImageType(), bAddUnconditionally); - imageMap.put(im, Integer.valueOf(im.getImageIndex() - 1)); // add - // new - // image - // to - // map - // and - // link - // to - // actual - // imageIndex - // - - // moved - // from - // above - msoDrawing.createRecord(++this.wkbook.lastSPID, im - .getImageName(), im.getShapeName(), idx); // generate - // msoDrawing - // using correct - // values moved - // from above - this.SheetRecs.add(insertIndex++, msoDrawing); - this.SheetRecs.add(insertIndex++, obj); - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to - // the msodrawinggroup set of - // recs - wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing - // header record and update - // it (using info from other - // msodrawing recs) - // 20080908 KSC: moved from above - msodg.setSpidMax(this.wkbook.lastSPID + 1); // was ++lastSPID - msodg.updateRecord(); // given all information, generate appropriate - // bytes - msodg.dirtyflag = true; - } else { - Logger.logErr("Boundsheet.insertImage: Drawing Group not created."); - } - } - - /** - * returns the images list - */ - public List getImageVect() { - ArrayList im = new ArrayList(); - Iterator ir = imageMap.keySet().iterator(); - while (ir.hasNext()) { - im.add(ir.next()); - } - return im; - - } - - /** - * Get a collection of all names in the worksheet - */ - public Name[] getSheetScopedNames() { - if (this.sheetNameRecs == null) - this.sheetNameRecs = new HashMap(); - ArrayList a = new ArrayList(this.sheetNameRecs.values()); - Name[] n = new Name[a.size()]; - a.toArray(n); - return n; - } - - /** - * Rationalizes the itab (sheet reference) for name records, - * this has to occur after sheet insert/delete operations to keep the - * references intact. Unfortunately these references do not use the Externsheet, - * so are not ilbl listeners. - */ - void updateLocalNameReferences() { - if (sheetNameRecs == null) - return; - Iterator i = this.sheetNameRecs.values().iterator(); - while (i.hasNext()) { - Name n = (Name) i.next(); - n.setItab((short) (this.getSheetNum() + 1)); - } - } - - /** - * for whatever reason, we return a Handle from an internal class - * - * @return - */ - public ImageHandle[] getImages() { - /* - * 20071026 KSC: since there may be multiple copies of the - * same - * image in the sheet, must build imageHandle array by hand - */ - if (imageMap == null) - return null; - ImageHandle[] im = new ImageHandle[imageMap.size()]; - Iterator ir = imageMap.keySet().iterator(); - int i = 0; - while (ir.hasNext()) { - im[i++] = (ImageHandle) ir.next(); - } - return im; - } - - /** - * column formatting records - *

                  - * Note that it checks if exists. This is due to externally copied boundsheets already having - * the record in the array when addrecord occurs. - */ - @Override - public void addColinfo(Colinfo c) { - if (!this.colinfos.containsValue(c)) { - this.colinfos.put(c, c); - } - } - - /** - * For workbooks that do not contain a dval record, - * insert a default dval rec - * - * @return - */ - public Dval insertDvalRec() { - if (this.getDvalRec() != null) - return this.getDvalRec(); - Dval d = (Dval) Dval.getPrototype(); - d.setSheet(this); - int insertIdx = win2.getRecordIndex() + 1; - // correct position for DV block is before sheet protection - // records (if any) - // or before EOF - int opc = ((BiffRec) SheetRecs.get(insertIdx)).getOpcode(); - while (opc != EOF) { - if (opc == SHEETPROTECTION || opc == RANGEPROTECTION - || opc == SHEETLAYOUT) - break; - insertIdx++; - opc = ((BiffRec) SheetRecs.get(insertIdx)).getOpcode(); - } - this.SheetRecs.add(insertIdx, d); - this.setDvalRec(d); - return d; - } - - /** - * Create a dv (validation record) - * record gets inserted into the byte stream from - * within Dval - * - * @param location - * @return - */ - public Dv createDv(String location) { - if (this.getDvalRec() == null) - this.insertDvalRec(); - Dv dv = this.getDvalRec().createDvRec(location); - int insertIdx = this.SheetRecs.size() - 2; // start at 1 before EOF - int opc = ((BiffRec) SheetRecs.get(insertIdx)).getOpcode(); - while (opc != DV && opc != DVAL) { - insertIdx--; // insert after last DV - opc = ((BiffRec) SheetRecs.get(insertIdx)).getOpcode(); - } - this.SheetRecs.add(insertIdx + 1, dv); // insert after DVAL or last DV - return dv; - } - - /** - * Create a Condfmt (Conditional format) record and - * add it to sheet recs - * - * @param location - * @return - */ - public Condfmt createCondfmt(String location, WorkBookHandle wbh) { - Condfmt cfx = (Condfmt) Condfmt.getPrototype(); - int insertIdx = win2.getRecordIndex() + 1; - BiffRec rec = (BiffRec) this.SheetRecs.get(insertIdx); - while (rec.getOpcode() != HLINK && rec.getOffset() != DVAL - && rec.getOpcode() != 0x0862 /* SHEETLAYOUT */ - && rec.getOpcode() != 0x0867 /* SHEETPROTECTION */ - && rec.getOpcode() != 0x0868 /* RANGEPROTECTION */ - && rec.getOpcode() != EOF) - rec = (BiffRec) this.SheetRecs.get(++insertIdx); - - this.SheetRecs.add(insertIdx, cfx); - cfx.setStreamer(streamer); - cfx.setWorkBook(this.getWorkBook()); - cfx.resetRange(location); - this.addConditionalFormat(cfx); - cfx.setSheet(this); - return cfx; - } - - /** - * Create a Cf (Conditional format rule) record and - * add it to sheet recs - * - * @param Conditional format - * @param range - * @return - */ - public Cf createCf(Condfmt cfx) { - Cf cf = (Cf) Cf.getPrototype(); - // we add this rec to vec right after its Condfmt - int insertIdx = cfx.getRecordIndex() + 1; - this.SheetRecs.add(insertIdx, cf); - cf.setStreamer(streamer); - cf.setWorkBook(this.getWorkBook()); - cf.setSheet(this); - cf.setCondfmt(cfx); - cfx.addRule(cf); - return cf; - } - - /** - * obtain the desired image handle via the MsoDrawing Image Index - * used for mapping images from copied worksheets - * - * @param index - * @return - */ - public ImageHandle getImageByMsoIndex(int index) { - if (imageMap == null) - return null; - Iterator ir = imageMap.keySet().iterator(); - ImageHandle ret = null; - while (ir.hasNext() && ret == null) { - ImageHandle im = (ImageHandle) ir.next(); - if (im.getMsodrawing().getImageIndex() == index) - ret = im; - } - return ret; - } - - public int getIndexOfMsodrawingselection() { - BiffRec rec = null; - - int size = SheetRecs.size(); - int foundIndex = -1; - for (int i = 0; i < size; i++) { - rec = (BiffRec) SheetRecs.get(i); - if (rec instanceof MSODrawingSelection) { - foundIndex = i; - break; - } - } - return foundIndex; - } - - public int getIndexOfWindow2() { - BiffRec rec = null; - int size = SheetRecs.size(); - int foundIndex = -1; - for (int i = 0; i < size; i++) { - rec = (BiffRec) SheetRecs.get(i); - if (rec instanceof Window2) { - foundIndex = i; - break; - } - } - return foundIndex; - } - - public int getIndexOfDimensions() { - BiffRec rec = null; - int size = SheetRecs.size(); - int foundIndex = -1; - for (int i = 0; i < size; i++) { - rec = (BiffRec) SheetRecs.get(i); - if (rec instanceof Dimensions) { - foundIndex = i + 1; - break; - } - } - return foundIndex; - } - - // Generic getIndexOf - replace specific hardocoded cases - // ... - public int getIndexOf(short opc) { - BiffRec rec = null; - - int size = SheetRecs.size(); - int foundIndex = -1; - for (int i = 0; i < size && foundIndex == -1; i++) { - rec = (BiffRec) SheetRecs.get(i); - if (rec.getOpcode() == opc) - foundIndex = i; - } - return foundIndex; - } - - /** - * return the desired record from the sheetrecs, or null if doesn't exist - * - * @param opc - * @return - */ - public BiffRec getSheetRec(short opc) { - BiffRec rec = null; - - int size = SheetRecs.size(); - int foundIndex = -1; - for (int i = 0; i < size && foundIndex == -1; i++) { - rec = (BiffRec) SheetRecs.get(i); - if (rec.getOpcode() == opc) - return rec; - } - return null; - } - - // 20070916 KSC: access for inserting records into sheetrecs - // collection - public void insertSheetRecordAt(BiffRec r, int index) { - r.setSheet(this); - if (index > -1 && index < SheetRecs.size()) - SheetRecs.add(index, r); - else - SheetRecs.add(r); - } - - /** - * Sheet hash is a cross-workbook identifier for OpenXLS. The first time it is called it creates the sheet hash. - * - * @return - */ - public String getSheetHash() { - if (sheetHash.equals("")) { - sheetHash = this.getSheetName() + this.getSheetNum() - + this.getRealRecordIndex(); - } - return sheetHash; - } - - /** - * assembleSheetRecs assembles the array of records, then ouputs - * the ordered list to the bytestreamer, which should be the only - * thing calling this. - */ - public List assembleSheetRecs() { - return WorkBookAssembler.assembleSheetRecs(this); - } - - /** - * write this sheet as tabbed text output:
                  - * All rows and all characters in each cell are saved. Columns of data are - * separated by tab characters, and each row of data ends in a carriage - * return. If a cell contains a comma, the cell contents are enclosed in - * double quotation marks. All formatting, graphics, objects, and other - * worksheet contents are lost. The euro symbol will be converted to a - * question mark. If cells display formulas instead of formula values, the - * formulas are saved as text. - */ - /* - * From Excel: To preserve the formulas if you reopen the - * file in Microsoft - * Excel, select the Delimited option in the Text Import - * Wizard, and select - * tab characters as the delimiters. Note If your workbook - * contains special - * font characters, such as a copyright symbol (©), and you - * will be using - * the converted text file on a computer with a different - * operating system, - * save the workbook in the text file format appropriate for - * that system. - * For example, if you are using Windows and want to use the - * text file on a - * Macintosh computer, save the file in the Text (Macintosh) - * format. If you - * are using a Macintosh computer and want to use the text - * file on a system - * running Windows or Windows NT, save the file in the Text - * (Windows) - * format. - */ - public void writeAsTabbedText(OutputStream dest) throws IOException { - int lastrow = this.getMaxRow(); - int lastcol = this.getMaxCol(); - boolean isInteger = false; - byte[] tab = {9}; - byte[] crlf = {13, 10}; - for (int i = 0; i < lastrow; i++) { - Row r = this.getRowByNumber(i); - if (r != null) { - for (int j = 0; j < lastcol; j++) { - BiffRec c = null; - try { - // Look for the cell and output - c = r.getCell((short) j); - int type = ((XLSRecord) c).getCellType(); - Object o; - if (type != Cell.TYPE_FORMULA) { - isInteger = (type == Cell.TYPE_INT); - o = c.getStringVal(); - } else { - o = ((Formula) c).calculateFormula(); - isInteger = o instanceof Integer || (o instanceof Double - && ((Double) o).intValue() == ((Double) o) - .doubleValue()); - } - try { - String output = CellFormatFactory - .fromPatternString(c.getFormatPattern()) - .format(o.toString()); - if (output.indexOf(",") != -1) - output = "\"" + output + "\""; - dest.write(output.getBytes()); - } catch (Exception e) { - Logger.logWarn("Boundsheet.writeAsTabbedText: error writing " - + c.getCellAddress() + ":" + e.toString()); - } - } catch (CellNotFoundException e1) { - // No cell exists at this location, continue - } - dest.write(tab); - } - // } catch (RowNotFoundException e) { } - } - dest.write(crlf); - } - dest.flush(); - dest.close(); - } - - /** - * do all of the expensive updating here - * only right before streaming record. - */ - @Override - public void preStream() { - } - - @Override - public Bof getMyBof() { - return mybof; - } - - @Override - public Eof getMyEof() { - return myeof; - } - - /** - * Return an array of all the dvRecs within - * this boundsheet (Dval parent rec) - * - * @return - */ - public List getDvRecs() { - if (this.getDvalRec() != null) - return this.getDvalRec().getDvs(); - return null; - } - - /** - * @return conditional formats for this sheet - */ - public List getConditionalFormats() { - return cond_formats; - } - - /** - * add a new Condtional Format rec for this sheet - * - * @param cf - */ - public void addConditionalFormat(Condfmt cf) { - if (cond_formats == null) - cond_formats = new ArrayList(); - if (cond_formats.indexOf(cf) == -1) - cond_formats.add(cf); - } - - @Override - public void setWindow2(Window2 w) { - win2 = w; - } - - @Override - public Window2 getWindow2() { - return win2; - } - - /** - * set/save the Pane rec for this sheet - * also links the Window2 rec to the pane rec - * - * @param p - */ - public void setPane(Pane p) { - if (p == null) { // adds new - p = (Pane) Pane.getPrototype(); - int insertIdx = win2.getRecordIndex() + 1; - this.SheetRecs.add(insertIdx, p); - } - pane = p; - pane.setWindow2(win2); - } - - /** - * retrieve the Pane rec for this sheet - * - * @return - */ - public Pane getPane() { - return pane; - } - - /** - * remove pane rec, effectively unfreezing - */ - public void removePane() { - SheetRecs.remove(pane); - pane = null; - } - - @Override - public WorkBook getWorkBook() { - return wkbook; - } - - /** - * Remove a BiffRec from this WorkSheet. - * - * @deprecated Use {@link #removeCell(int, int)} instead. - */ - @Deprecated - @Override - public void removeCell(String celladdr) { - BiffRec c = this.getCell(celladdr); - if (c != null) { - this.removeCell(c); - } - } - - /** - * Remove a BiffRec from this WorkSheet. - */ - public void removeCell(int row, int col) { - BiffRec c; - try { - c = this.getCell(row, col); - this.removeCell(c); - } catch (CellNotFoundException e) { - // cell does not exist, this is fine - } - } - - /** - * remove a BiffRec from the worksheet. - *

                  - * Unfortunately this also has to manage mulrecs - */ - @Override - public void removeCell(BiffRec cell) { - if (cell.getOpcode() == MULBLANK) { - ((Mulblank) cell).removeCell(cell.getColNumber()); - } - if (cell.getOpcode() == XLSConstants.FORMULA) { - Formula f = (Formula) cell; - this.wkbook.removeFormula(f); - } - cellsByRow.remove(cell); - cellsByCol.remove(cell); - } - - /** - * removes an image from the imagehandle cache (should be in WSH) - *

                  - * Jan 22, 2010 - * - * @param img - * @return - */ - public boolean removeImage(ImageHandle img) { - return imageMap.remove(img) != null; - } - - /** - * remove a record from the vector via it's index - * into the SheetRecs aray, includes firing a change event - * - * @param idx - */ - public void removeRecFromVec(int idx) { - try { - BiffRec rec = (BiffRec) this.SheetRecs.get(idx); - removeRecFromVec(rec); - } catch (Exception e) { - Logger.logErr("Boundsheet.removeRecFromVec: " + e.toString()); - } - } - - /** - * Removes some rows and all associated cells from this sheet. - * References are not handled; for those see {@link ReferenceTracker}. - * - * @param first the zero-based index of the first row to be removed - * @param count the number of rows to be removed - * @param shift whether to shift subsequent rows up to fill the empty space - */ - public void removeRows(int first, int count, boolean shift) { - - for (int rowIdx = first; rowIdx < first + count; rowIdx++) { - // this.removeRowContents(rowIdx); - - Row row = rows.get(rowIdx); - if (null == row) - continue; - - Iterator iter = row.getCells().iterator(); - while (iter.hasNext()) { - BiffRec cell = iter.next(); - - // This removes the cell from the Row's map without - // perturbing - // the iterator. When removeCell tries to remove it later - // the - // map will silently do nothing instead of throwing a CME. - iter.remove(); - - this.removeCell(cell); - } - - rows.remove(rowIdx); - this.removeRecFromVec(row); - } - - // shift all following rows up to fill the gap left by the - // removed rows - if (shift && !rows.isEmpty()) { - int shiftBy = -1 * count; - int lastrow = lastRow.getRowNumber(); - for (int rowIdx = first + 1; rowIdx <= lastrow; rowIdx++) { - Row row = rows.get(rowIdx); - if (null == row) - continue; - - this.shiftRow(row, shiftBy); - } - } - - // update sheet dimensions - this.dimensions - .setRowLast(null != lastRow ? lastRow.getRowNumber() : 0); - } - - /** - * remove rec from the vector, includes firing - * a changeevent. - */ - @Override - public void removeRecFromVec(BiffRec rec) { - boolean removerec = true; - // is it an RK, maybe part of a Mulrk?? - if (rec.getOpcode() == RK) { - Rk thisrk = (Rk) rec; - this.removeMulrk(thisrk); - } else if (rec.getOpcode() == FORMULA) { - Formula f = (Formula) rec; - this.wkbook.removeFormula(f); - } else if (rec.getOpcode() == LABELSST) { - Labelsst lst = (Labelsst) rec; - Sst strtable = wkbook.getSharedStringTable(); - lst.initUnsharedString(); - strtable.removeUnicodestring(lst.getUnsharedString()); - } else if (rec instanceof Mulblank) { // KSC: Added - removerec = ((Mulblank) rec).removeCell(rec.getColNumber()); - } - if (removerec) { - if (streamer.removeRecord(rec)) { - if (DEBUGLEVEL > 5) - Logger.logInfo("Boundsheet RemoveRec Removed: " - + rec.toString()); - } else { - if (rec instanceof Mul) { - if (!((Mul) rec).removed()) - if (DEBUGLEVEL > 1) - Logger.logWarn("RemoveRec failed: " - + rec.getClass().getName() - + " not found in Streamer Vec"); - } else { - if (DEBUGLEVEL > 1) - Logger.logWarn("RemoveRec failed: " - + rec.getClass().getName() - + " not found in Streamer Vec"); - } - } - } - } - - /** - * Called from removeCell(), removeMulrk() handles the fact that you - * are trying to delete a rk that is really just a part of a Mulrk. This - * is handled by truncating the mulrk at the cell, then creating individual numbers - * after the deleted cell. - **/ - @Override - public void removeMulrk(Rk thisrk) { - Mulrk mymul = (Mulrk) thisrk.getMyMul(); - if (mymul != null) { // Part of a mulrk. JOY! - AbstractList vect = mymul.removeRk(thisrk); - boolean deletemulrk = false; - if (mymul.getColFirst() == thisrk.getColNumber()) { - deletemulrk = true; - } - if (vect != null) { // the mulrk contiued past the cell deleted - // Create new records for each of the Rks, - Iterator itv = vect.iterator(); - while (itv.hasNext()) { - Rk temprk = (Rk) itv.next(); - temprk.setNoMul(); - String loc = temprk.getCellAddress(); - - Double d = new Double(temprk.getDblVal()); - BiffRec g = this.getCell(loc); - int fmt = g.getIxfe(); - g.getRow().removeCell(g); - this.removeCell(loc); - - this.addValue(d, loc); - this.getCell(loc).setIxfe(fmt); - streamer.removeRecord(temprk); - } - } - if (deletemulrk) { - mymul.removed = true; - this.removeRecFromVec(mymul); - } - } - - } - - /** - * Remove a row, do not shift any other rows - * - * @throws RowNotFoundException - */ - public void removeRowContents(int rownum) throws RowNotFoundException { - Row r = this.getRowByNumber(rownum); - // First delete the desired row - if (r != null) { - Object[] cells = r.getCellArray(); - for (int x = 0; x < cells.length; x++) { // adjust cell's in row - this.removeCell((BiffRec) cells[x]); - } - rows.remove(Integer.valueOf(rownum)); - this.removeRecFromVec(r); - r = null; - } else { - throw new RowNotFoundException(this.getSheetName() + ":" + rownum); - } - - } - - /** - * Removes a set of columns and their associated cells from this sheet. - * Optionally shifts the subsequent columns left to fill the empty space. - * This method only updates the sheet and cell records. It doesn't adjust - * references; that's handled by {@link WorkSheetHandle#removeCols}. - * - * @param first this zero-based index of the first column to be removed - * @param count the number of columns to remove - * @param shift whether to shift subsequent columns left - */ - public void removeCols(int first, int count, boolean shift) { - - if (shift) { - ReferenceTracker.updateReferences(first, count * -1, this, false); // shift - // or - // expand/contract - // ALL - // affected - // references - // including - // named - // ranges - } - - for (int colIdx = first; colIdx < first + count; colIdx++) { - - // update or remove the ColInfo record as appropriate - Colinfo info = this.getColInfo(colIdx); - if (null != info) { - if (info.getColLast() < first + count) { - if (info.getColFirst() >= first) { - this.removeColInfo(info); - } else { - info.setColLast(first - 1); - } - } else if (info.getColFirst() >= first) { - info.setColFirst(first + count); - } - } - - // remove the cells in the column - try { - List cells = this.getCellsByCol(colIdx); - int cellCount = cells.size(); - for (int idx = cellCount - 1; idx >= 0; idx--) { - BiffRec cell = cells.get(idx); - if (null == cell) - continue; - - this.removeCell(cell); - } - } catch (CellNotFoundException e) { - // This is fine, no cells in this column - } - - } - - if (shift) { - int shiftBy = -1 * count; - int maxcol = this.getRealMaxCol(); - for (int colIdx = first + 1; colIdx <= maxcol; colIdx++) { - this.shiftCol(colIdx, shiftBy); - } - } - - // make sure dimensions record is correctly updated upon - // output - this.dimensions.setColLast(this.getRealMaxCol()); - } - - /** - * Access an arrayList of cells by column - * - * @param colNum - * @return - */ - public ArrayList getCellsByCol(int colNum) throws CellNotFoundException { - SortedMap theCells = cellsByCol - .subMap(new CellAddressible.RangeBoundary(0, colNum, - true), new CellAddressible.RangeBoundary(0, colNum + 1, - false)); - if (theCells.size() == 0) - throw new CellNotFoundException(this.sheetname, 0, col); - - Collection cells = theCells.values(); - Iterator i = cells.iterator(); - while (i.hasNext()) { - BiffRec biffrec = i.next(); - if (biffrec.getOpcode() == MULBLANK) { - ((Mulblank) biffrec).setCurrentCell((short) colNum); - } - } - return new ArrayList(cells); - } - - /** - * Access an arrayList of cells by column - * - * @param colNum - * @return - */ - public ArrayList getCellsByRow(int rowNum) throws CellNotFoundException { - SortedMap theCells = cellsByRow - .subMap(new CellAddressible.Reference(rowNum, - 0), new CellAddressible.Reference(rowNum + 1, 0)); - if (theCells.size() == 0) - throw new CellNotFoundException(this.sheetname, 0, col); - - Collection cells = theCells.values(); - return new ArrayList(cells); - } - - /** - * get a handle to a specific column of cells in this sheet - */ - @Override - public Colinfo getColInfo(int col) { - Colinfo info = this.colinfos.get(new ColumnRange.Reference(col, col)); - if (null == info) - return null; - - if (info.inrange(col)) - return info; - return null; - } - - /** - * remove all Sheet records from Sheet. - */ - @Override - public void removeAllRecords() { - // this.setSheetRecs(); - XLSRecord[] rx = new XLSRecord[SheetRecs.size()]; - SheetRecs.toArray(rx); - - for (int t = 0; t < rx.length; t++) { - int opcode = ((BiffRec) rx[t]).getOpcode(); // Handle continues - // masking mso's - if (opcode != MSODRAWING && !(opcode == CONTINUE - && ((Continue) rx[t]).maskedMso != null)) - this.removeRecFromVec(rx[t]); - else // must update MSODrawingGroup record as well ... - if (opcode == MSODRAWING) - this.wkbook.msodg - .removeMsodrawingrec((MSODrawing) rx[t], this, false); // don't - // remove - // assoc - // object - // record - else // a Continue record masking an MSoo - this.wkbook.msodg - .removeMsodrawingrec(((Continue) rx[t]).maskedMso, this, false); // don't - // remove - // assoc - // object - // record - rx[t] = null; - } - SheetRecs.clear(); - // System.gc(); - - } - - /** - * Shifts a single column. - * This adjusts any mention of the column number in the associated records. - * References are not handled; for those see {@link ReferenceTracker}. - * - * @param col the column to be shifted - * @param shift the number of columns by which to shift - */ - private void shiftCol(int colNum, int shift) { - Colinfo info = this.getColInfo(colNum); - int oldCol = colNum; - int newCol = oldCol + shift; - - List cells; - try { - cells = this.getCellsByCol(colNum); - for (BiffRec cell : cells) { - cell.setCol((short) newCol); - this.updateDimensions(cell.getRowNumber(), cell.getColNumber()); - } - } catch (CellNotFoundException e) { - // No cells exist in this column - } - - if (null != info) { - int first = info.getColFirst(); - if (first == oldCol || first > newCol) { - info.setColFirst(newCol); - } - - int last = info.getColLast(); - if (last == oldCol || last < newCol) - info.setColLast(newCol); - } - } - - private void removeColInfo(Colinfo ci) { - this.removeRecFromVec(ci); - this.colinfos.remove(ci); - } - - /* - * - */ - - /** - * set the Bof record for this Boundsheet - */ - @Override - public void setBOF(Bof b) { - mybof = b; - b.setSheet(this); - } - - /** - * Determine if the boundsheet is a chart only boundsheet - * - * @return - */ - @Override - public boolean isChartOnlySheet() { - if (mybof != null) - return mybof.isChartBof(); - return false; - } - - @Override - public void setEOF(Eof f) { - myeof = f; - } - - /** - * return the pos of the Bof for this Sheet - */ - @Override - public long getLbPlyPos() {// - if (mybof != null) - return mybof.getLbPlyPos(); - return this.lbPlyPos; - - } - - /** - * set the pos of the Bof for this Sheet - */ - @Override - public void setLbPlyPos(long newpos) { - byte[] newposbytes = ByteTools.cLongToLEBytes((int) newpos); - System.arraycopy(newposbytes, 0, this.getData(), 0, 4); - this.lbPlyPos = newpos; - } - - /** - * get the min/max dimensions - * for this sheet. - */ - private Dimensions dimensions; - - /** - * the beginning of the Dimensions record - * is the index of the RowBlocks - */ - @Override - public Dimensions getDimensions() { - return dimensions; - } - - @Override - public void setDimensions(Dimensions d) { - // only set the first dimensions. Other dimensions records - // may exist within - // the boundsheet stream from charts & msodrawing objects, - // but going to run with the - // assumption that the first one is the identifier for - // valrec start - if (dimensions == null) { - dimensions = d; - if (myidx != null) - this.myidx.setDimensions(d); - } - - } - - /** - * Shifts a single row. - * This adjusts any mention of the row number in the row records. Formula - * references are not handled; for those see {@link ReferenceTracker}. - * - * @param row the row to be shifted - * @param shift the number of rows by which to shift - */ - private void shiftRow(Row row, int shift) { - Iterator cells = row.getCells().iterator(); - Mulblank skipMulBlank = null; - while (cells.hasNext()) { - BiffRec cell = (BiffRec) cells.next(); - - if (cell == skipMulBlank) - continue; - else if (cell.getOpcode() == MULBLANK) - skipMulBlank = (Mulblank) cell; - - this.shiftCellRow(cell, shift); - } - - int oldRow = row.getRowNumber(); - int newRow = oldRow + shift; - row.setRowNumber(newRow); - - rows.remove(oldRow); - rows.put(newRow, row); - - if (this.dimensions.getRowLast() < newRow) { - this.dimensions.setRowLast(newRow); - this.lastRow = row; - } - } - - @Override - public int getMinRow() { - return dimensions.getRowFirst(); - } - - /** - * return true maximum/last row on the sheet - */ - @Override - public int getMaxRow() { - return dimensions.getRowLast(); - } - - @Override - public int getMinCol() { - return dimensions.getColFirst(); - } - - @Override - public int getMaxCol() { - return dimensions.getColLast(); - } - - /** - * update the INDEX record with the new max Row # - * why we need so many redundant references to the Min/Max Row/Cols - * is a question for the Redmond sages. - */ - @Override - public void updateDimensions(int row, int c) { - if (DEBUGLEVEL > 10) - Logger.logInfo("Boundsheet Updating Dimensions: " + row + ":" - + col); - short col = (short) c; - maximumCellCol = Math.max(maximumCellCol, col); - maximumCellRow = Math.max(maximumCellRow, row); - if (dimensions != null) - dimensions.updateDimensions(row - 1, col); - if (this.myidx != null) - this.myidx.updateRowDimensions(this.getMinRow(), this.getMaxRow()); // TODO: - // investigate - // why - // no - // Index - // is - // possible - } - - /** - * set the associated sheet index - */ - @Override - public void setSheetIDX(Index idx) { - idx.setSheet(this); - myidx = idx; - } - - /** - * set the associated sheet index - */ - public Index getSheetIDX() { - return myidx; - } - - /** - * Adjusts a cell to reflect its parent row being shifted. - * This adjusts any mention of the row number in the cell record. Formula - * references are not handled; for those see {@link ReferenceTracker}. - * - * @param cell the cell record to be shifted - * @param shift the number of rows by which to shift the cell - */ - private void shiftCellRow(BiffRec cell, int shift) { - int newrow = cell.getRowNumber() + shift; - cell.setRowNumber(newrow); - - // handle per-record special cases - switch (cell.getOpcode()) { - case XLSConstants.RK: - ((Rk) cell).setMulrkRow(newrow); - break; - - case XLSConstants.FORMULA: - Formula formula = (Formula) cell; - - // must also shift shared formulas if necessary - if (formula.isSharedFormula()) { - if (formula.getInternalRecords().size() > 0) {// is it the - // parent? - Object o = formula.getInternalRecords().get(0); - if (o instanceof Shrfmla) { // should! - Shrfmla s = (Shrfmla) o; - s.setFirstRow(s.getFirstRow() + shift); - s.setLastRow(s.getLastRow() + shift); - } - } - } - break; - } - } - - /** - * set the numeric sheet number - */ - @Override - public int getSheetNum() { - return this.wkbook.getSheetVect().indexOf(this); - } - - /** - * add a row to the worksheet as well - * as to the RowBlock which will handle - * the updating of Dbcell index behavior - * - * @param BiffRec the cell being added (can't add a row without one...) - */ - private Row addNewRow(BiffRec cell) { - int rn = cell.getRowNumber(); - if (this.getRowByNumber(rn) != null) - return this.getRowByNumber(rn); // already exists! - Row r = new Row(rn, wkbook); - try { // Out-of-spec wb's may not have dimensions record -- will - // be handled upon validation - if (rn >= this.getMaxRow()) - dimensions.setRowLast(rn); - } catch (NullPointerException e) { - } - r.setSheet(this); - this.addRowRec(r); - return r; - - } - - /** - * Inserts a row and shifts subsequent rows down by one. - * - * @param rownum the zero-based index of the row to be created - * @return the row that was just inserted - */ - // TODO: reduce this functionality to simply inserting a row - // and shifting the row number of subsequent rows and cells - public Row insertRow(int rownum, int firstcol, int flag, boolean shiftrows) { - Row roe = null; - if (shiftrows && !this.fastCellAdds) { - try { - // shift all rows after this one down... - // moves refs, formats, merges, etc. - if (lastRow != null) { - int startrow = lastRow.getRowNumber(); - if (startrow == MAXROWS) - startrow--; // 20080925 KSC: can't add more than maxrows - - for (int t = startrow; t >= rownum; t--) { // traverse from - // last row to - // current - Row rowtoshift = rows.get(Integer.valueOf(t)); - if (rowtoshift != null) { - try { - this.shiftRow(rowtoshift, 1);// pass original - // row # for - // formula - // shifting + - // flag - } catch (Exception e) { - Logger.logWarn("Boundsheet.insertRow() failed shifting row: " - + t + " - " + e.toString()); - } - } - } - } - - // we add a blank because a row cannot be empty - roe = this.getRowByNumber(rownum); - if (roe == null) { - int[] rc = {rownum, firstcol}; // added firstcol to not - // add bad cells at a1 - this.addRecord(Blank.getPrototype(), rc); - roe = this.getRowByNumber(rownum); - } - - } catch (Exception a) { - Logger.logInfo("Boundsheet.insertRow: Shifting row during Insert failed: " - + a); - } - } else { - roe = this.getRowByNumber(rownum); - if (roe == null) { - Row r = new Row(rownum, wkbook); - // must also update maxrow on sheet - if (rownum >= this.getMaxRow()) - dimensions.setRowLast(rownum); - r.setSheet(this); - this.addRowRec(r); - roe = this.getRowByNumber(rownum); - roe.resetCacheBytes(); - } - } - return roe; - } - - /** - * shifts Merged cells. 10-15-04 -jm - */ // used??? - @Override - public void updateMergedCells() { - if (this.mc.size() < 1) - return; - Iterator mcs = this.mc.iterator(); - while (mcs.hasNext()) - ((Mergedcells) mcs.next()).update(); - } - - /** - * associate an existing Row with this Boundsheet - * if the row already exists... ignore? - */ - public void addRowRec(Row r) { - int rwn = r.getRowNumber(); - if (rows.containsKey(Integer.valueOf(rwn))) { - if (DEBUGLEVEL > 2) - Logger.logWarn("Sheet.addRow() attempting to add existing row"); - } else { - rows.put(Integer.valueOf(rwn), r); - if (lastRow == null) { - this.lastRow = r; - } else if (rwn > lastRow.getRowNumber()) { - lastRow = r; - } - } - } - - /** - * get whether this sheet is hidden upon opening (either regular or "very hidden" - */ - @Override - public boolean getHidden() { - return grbit != VISIBLE; - } - - public boolean getVeryHidden() { - return (grbit == VERY_HIDDEN); - } - - /** - * set whether this sheet is hidden upon opening - */ - @Override - public void setHidden(int gr) { - grbit = (short) gr; - byte[] bt = ByteTools.shortToLEBytes(grbit); - System.arraycopy(bt, 0, getData(), 4, 2); - } - - boolean selected = false; - - /** - * returns the selected sheet status - */ - @Override - public boolean selected() { - return selected; - } - - /** - * set whether this sheet is selected upon opening - */ - @Override - public void setSelected(boolean b) { - if (this.win2 != null) - this.win2.setSelected(b); - if (b) { - this.getWorkBook().setSelectedSheet(this); - } - selected = b; - } - - /** - * associate an Array formula with this Boundsheet - */ - public void addArrayFormula(Array a) { - arrayformulas.add(a); - } - - /** - * Returns an array formula for the set address - */ - public Array getArrayFormula(String addr) { - Array form = null; - for (int i = 0; i < arrayformulas.size(); i++) { - form = (Array) arrayformulas.get(i); - if (form.isInRange(addr)) - return form; - } - return null; - } - - /** - * map array formula range reference to the parent array formula address - *
                  for Array Formula Parent Records only - * boundsheet - */ - public void addParentArrayRef(String addr, String ref) { - if (arrFormulaLocs.containsKey(addr)) - Logger.logWarn("PARENT ARRAY ALREADY FOUND"); - arrFormulaLocs.put(addr, ref); - } - - /** - * see if an array formula is part of an existing array formula - *
                  by checking to see if the address in quesion is - * referenced by any array formula references on this sheet - * - * @param rc row col of cell in question - * @return - * @see addArrayFormula - */ - public Object getArrayFormulaParent(int[] rc) { - Iterator i = arrFormulaLocs.keySet().iterator(); - while (i.hasNext()) { - String addr = (String) i.next(); - int[] arrayRC = ExcelTools - .getRangeRowCol((String) arrFormulaLocs.get(addr)); - if ((rc[1] >= arrayRC[1]) && (rc[1] <= arrayRC[3]) - && (rc[0] >= arrayRC[0]) && (rc[0] <= arrayRC[2])) { - return arrayRC; - } - } - return null; // no parent? - } - - /** - * return true if address refers to an Array Formula Parent - *
                  i.e. the parent array formula refers to one or multiple cell addreses - * - * @param addr - * @return - */ - public boolean isArrayFormulaParent(String addr) { - return (arrFormulaLocs.get(addr) != null); - } - - /** - * given an parent array formula at address formAddress, - * look up in saved arrFormulaLocs for the cell range it references - * - * @param formAddress - * @return - */ - public String getArrayRef(String formAddress) { - return (String) arrFormulaLocs.get(formAddress); - } - - /** - * inserts a col and shifts all of the other rows over one - * - * @param first zero-based int for the column (0='A') - */ - public void insertCols(int first, int count) { - - ReferenceTracker.updateReferences(first + 1, count, this, false); // shift - // or - // expand/contract - // ALL - // affected - // references - // including - // named - // ranges - - // shift the existing columns to the right to make room - for (int colIdx = this.getRealMaxCol(); colIdx >= first; colIdx--) { - this.shiftCol(colIdx, count); - } - - // update the new colinfos to include the formatting and the - // width of the inserted col - Colinfo movedCol = this.getColInfo(first + count); - if (movedCol != null) { - for (int i = 0; i < count; i++) { - Colinfo newcol = this.getColInfo(first + i); - if (newcol == null) { - this.addColinfo(first + i, first + i, movedCol - .getColWidth(), movedCol - .getIxfe(), movedCol.getGrbit()); - } else { - newcol.setGrbit(movedCol.getGrbit()); - newcol.setColWidth(movedCol.getColWidth()); - newcol.setIxfe(movedCol.getIxfe()); - } - } - } - - // ensure the sheet bounds are accurate - this.dimensions.setColLast(this.getRealMaxCol()); - } - - /** - * get the number of defined rows on this sheet - */ - @Override - public int getNumRows() { - return rows.size(); - } - - /** - * get the number of defined cells on this sheet - */ - @Override - public int getNumCells() { - int counter = 0; - Set cellset = rows.keySet(); - Object[] rws = cellset.toArray(); - if (rws.length == 0) - return 0; - for (int i = 0; i < rws.length; i++) { - Row r = rows.get(rws[i]); - counter += r.getNumberOfCells(); - } - return counter; - - } - - /** - * get the FastAddVector of columns defined on this sheet - */ - @Override - public List getColNames() { - FastAddVector retvec = new FastAddVector(); - for (int x = 0; x < this.getRealMaxCol(); x++) { - String c = ExcelTools.getAlphaVal(x); - retvec.add(c); - } - return retvec; - } - - /** - * get the Number of columns defined on this sheet - */ - @Override - public int getNumCols() { - return getRealMaxCol(); - } - - /** - * Add a new colinfo - * - * @param first The beginning column number (0 based) - * @param last The end column number - * @param width Initial width of the column - * @param ixfe formatting - * @param grbit ?? - * @return Colinfo - */ - public Colinfo createColinfo(int first, int last, int width, int ixfe, int grbit) { - Colinfo ci = Colinfo.getPrototype(first, last, width, ixfe); - ci.setGrbit(grbit); - ci.setWorkBook(getWorkBook()); - ci.setSheet(this); - this.addColinfo(ci); - int recpos = this.getDimensions().getRecordIndex(); - recpos--; - List sr = this.getSheetRecs(); - // get to last Colinfo record - BiffRec rec = (BiffRec) sr.get(recpos); - // TODO: is it ABSOLUTELY true that if no Colinfos there - // must be a DefColWidth record???? - while (!(rec instanceof Colinfo) && !(rec instanceof DefColWidth) - && recpos > 0) { // loop until we find either a colinfo or - // DEFCOLWIDTH - rec = (BiffRec) sr.get(--recpos); - } - // now position this Colinfo in the proper position within - // the Colinfo set - int cf = ci.getColFirst(); - while (rec instanceof Colinfo && ((Colinfo) rec).getColFirst() > cf) { - rec = (BiffRec) sr.get(--recpos); - } - recpos++; - this.getStreamer().addRecordAt(ci, recpos); - return ci; - } - - /** - * Create a colinfo using the values from an existing colinfo - * - * @param first first col in the colinfo - * @param last last col in the colinfo - * @param template template column - * @return - */ - public Colinfo createColinfo(int first, int last, Colinfo template) { - return this.createColinfo(first, last, template.getColWidth(), template - .getIxfe(), template.getGrbit()); - } - - public Colinfo createColinfo(int first, int last) { - return this.createColinfo(first, last, Colinfo.DEFAULT_COLWIDTH, 0, 0); - } - - /** - * get a handle to the Row at the specified - * row index - *

                  - * Zero-based Index. - *

                  - * ie: row 0 contains cell A1 - */ - @Override - public Row getRowByNumber(int r) { - return rows.get(Integer.valueOf(r)); - } - - /** - * get the FastAddVector of rows defined on this sheet - */ - @Override - public List getRowNums() { - Set e = rows.keySet(); - Iterator iter = e.iterator(); - FastAddVector rownames = new FastAddVector(); - while (iter.hasNext()) { - rownames.add(rownames.size(), iter.next()); - } - return rownames; - } - - /** - * return the map of row in this sheet sorted by row # - * (will be unsorted if insertions and deletions) - * - * @return - */ - public SortedMap getSortedRows() { - SortedMap sm = new TreeMap(rows); - return sm; - } - - /** - * return an Array of the Rows - */ - @Override - public Row[] getRows() { - Map rxs = new TreeMap(rows); // treemap does ordering... LHM does not - Row[] rarr = new Row[rxs.size()]; - return (Row[]) rxs.values().toArray(rarr); - } - - /** - * return a Map of the Rows - */ - public Map getRowMap() { - return rows; - } - - public boolean fastCellAdds = false; // performance setting which skips - // safety checks - - @Override - public BiffRec addValue(Object obj, String address) { - return addValue(obj, address, false); - } - - /** - * Add a Value record to a WorkSheet. - * This method's purpose is to handle default formatting - * of the cell that is being added, and to do any manipulations - * neccessary to handle mulrks, mulblanks, etc. It is also the - * main entry point of adding values to the boundsheet. These values - * are then passed into createValrec() which - * - * @param obj the value of the new Cell - * @param address the address of the new Cell - */ - public BiffRec addValue(Object obj, String address, boolean fixNumberAsString) { - - // first see if there's an existing item - int[] rc = ExcelTools.getRowColFromString(address); - - return addValue(obj, rc, fixNumberAsString); - - } - - /** - * adds a value to the sheet - * - * @param obj - * @param rc - * @return - */ - public BiffRec addValue(Object obj, int[] rc, boolean fixNumberAsString) { - return addValue(obj, rc, this.getWorkBook() - .getDefaultIxfe(), fixNumberAsString); - } - - /** - * adds a cell to the Sheet - * - * @param obj - * @param rc - * @param FORMAT_ID - * @return - */ - public BiffRec addValue(Object obj, int[] rc, int FORMAT_ID) { - return addValue(obj, rc, FORMAT_ID, false); - } - - /** - * adds a cell to the Sheet - * - * @param obj - * @param rc - * @param FORMAT_ID - * @param fixNumberAsString - whether to attempt to convert to a number if it is a NSaS situation - * @return - */ - public BiffRec addValue(Object obj, int[] rc, int FORMAT_ID, boolean fixNumberAsString) { - - if (rc[1] > WorkBook.MAXCOLS) - throw new InvalidRecordException("Cell Column number: " + rc[1] - + " is greater than maximum allowable Columns: " - + WorkBook.MAXCOLS); - - // sanity checks - if (rc[0] > WorkBook.MAXROWS) - throw new InvalidRecordException("Cell Row number: " + rc[0] - + " is greater than maximum allowable row: " - + WorkBook.MAXROWS); - - Row r = this.getRowByNumber(rc[0]); - - /* - * from Doc: The default cell format is always present in an - * Excel file, - * described by the XF record with the fixed index 15 - * (0-based). - * - * By default, it uses the worksheet/workbook default cell - * style, - * described by the very first XF record (index 0). - */ - if (FORMAT_ID <= 0) - FORMAT_ID = this.getWorkBook().getDefaultIxfe(); - if (FORMAT_ID == this.getWorkBook().getDefaultIxfe()) { - if (this.getColInfo(rc[1]) != null) { /* - * get default colinfo if - * possible - */ - Colinfo co = this.getColInfo(rc[1]); - if (co != null && co.getIxfe() != 0) - FORMAT_ID = co.getIxfe(); - } - if (r != null && r.getExplicitFormatSet()) { - FORMAT_ID = r.getIxfe(); - } - } - - CellRange merge_range = null; - - if (!fastCellAdds) { - try { - BiffRec mycell = this.getCell(rc[0], rc[1]); - BiffRec rec = mycell; - merge_range = rec.getMergeRange(); - // specific cell format overrides any other formats: if - // (FORMAT_ID == defaultFormatId) - if (rec.getIxfe() != this.getWorkBook().getDefaultIxfe() - && rec.getIxfe() != 0) - FORMAT_ID = rec.getIxfe(); - this.removeCell(mycell); - } catch (CellNotFoundException cnfe) { - // good! - } - } - - // Handle detection of Number stored as Strings - Object[] fixed = null; - if (!fastCellAdds && fixNumberAsString && obj != null) { - try { - fixed = this.fixNumberStoredAsString(obj); - obj = fixed[0]; - } catch (Exception e) { - // not a number! - } - } - - XLSRecord rec = this.createValrec(obj, rc, FORMAT_ID); - - if (!fastCellAdds) { // reapply conditional format and merges - if (merge_range != null) - rec.setMergeRange(merge_range); - } - - // check this does not touch affectedcells - this.addRecord(rec, rc); - - if (fixed != null) { - FormatHandle f = new FormatHandle(this.wkbook, - this.getWorkBook().getDefaultIxfe()); - f.setFormatPattern(fixed[1].toString()); - rec.setXFRecord(f.getFormatId()); - } - - rec.resetCacheBytes(); - - if (r == null) { // if no row initially, check default row height; if - // not Excel's default, set row height - double rh = this.getDefaultRowHeight(); - if (rh != 12.75) {// the default - r = this.getRowByNumber(rc[0]); - r.setRowHeight((int) (rh * 20)); - } - } - return rec; - } - - /** - * for numbers stored as strings, try to guess the - * format pattern used, and strip the value to a number - *

                  - * TODO: increase sophistication of pattern matching to better guess pattern used - * - * @param s - * @return Object[Double value, String formatPattern] - */ - Object[] fixNumberStoredAsString(Object s) throws NumberFormatException { - String input = s.toString(); - if (input.indexOf(" ") > -1) { - input = StringTool.allTrim(input); - } - String p = ""; // the format pattern - boolean matched = false; - - for (NumberAsStringFormat fmts : NumberAsStringFormat.values()) { - if (input.indexOf(fmts.identifier) > -1) { - input = StringTool.strip(input, fmts.identifier); - p = fmts.pattern; - matched = true; - Double d = new Double(input); - d = fmts.adjustValue(d); - Object[] ret = new Object[2]; - ret[0] = d; // value - ret[1] = p; // format pattern - return ret; - } - } - - throw new NumberFormatException(); - } - - /** - * A simple enum to store matching strings, format patterns, and value - * switches where necessary - */ - private enum NumberAsStringFormat { - PERCENT("%", "0%"), - EURO("€", "€#,##0;(€#,##0)"), - YEN("¥", "¥#,##0;(¥#,##0)"), - POUND("£", "£#,##0;(£#,##0)"), - DOLLAR("$", "$#,##0;(€#,##0)"), - ALT_POUND("₤", "₤#,##0;(₤#,##0)"); - - private final String identifier; - private final String pattern; - - NumberAsStringFormat(String id, String format) { - this.identifier = id; - this.pattern = format; - } - - public String identifier() { - return this.identifier; - } - - public String pattern() { - return this.pattern; - } - - /** - * adjust the value where necessary - **/ - public double adjustValue(double inputVal) { - if (this.identifier == "%") - return inputVal * .01; - return inputVal; - } - } - - /** - * Creates a valrec (Value containing XLSRecord). This method observes - * the object passed in, then creates a XLS record of the correct type depending - * on the object type. A default FormatID is handled as well. - *

                  - * The valrec at this point is not fully formed, it needs the row/col set - * along with some other default actions that occur in addRecord(). This is - * due to addRecord being the merge point for adding cells to a boundsheet between - * the parse-level additions and the user-level additions! - * - * @param obj the value of the new Cell - * @param row & col address of the new Cell - * @param FORMAT_ID, index to the XF record for this valrec - * @return partially formed XLS Record. - */ - private XLSRecord createValrec(Object obj, int[] rc, int FORMAT_ID) { - /* - * try{ - * BiffRec cx = this.getCell(rc[0],rc[1]); - * this.removeCell(cx); - * }catch(CellNotFoundException e){} - */ - XLSRecord rec = null; - if (obj == null) { - rec = new Blank(); - } else if (obj instanceof Formula) { - rec = (Formula) obj; - } else if (obj instanceof Double) { - rec = new NumberRec(((Double) obj).doubleValue()); - } else if (obj instanceof String) { - if (((String) obj).startsWith("=")) { - try { - // Logger.logInfo("adding formula"); - rec = FormulaParser - .getFormulaFromString((String) obj, this, rc); - - // this is a problem because workbook adds the rec to - // lastbounds - // in other words it will show up on the last sheet. Needed? - getWorkBook().addRecord(rec, false); - - // getWorkBook().addFormula((Formula)rec); // next best - // thing methinks... - } catch (Exception e) { - // 20070212 KSC: add sheet name + address - // Logger.logWarn("adding new Formula at row:" + rc[0] +" - // col: " + rc[1] + ":"+obj.toString()+" failed, adding - // value to worksheet as String."); - throw new FunctionNotSupportedException( - "Adding new Formula at " + this.getSheetName() + "!" - + ExcelTools.formatLocation(rc) - + " failed: " + e.toString() + "."); - // rec = Labelsst.getPrototype((String) obj, - // this.getWorkBook().getSharedStringTable()); - } - } else if (((String) obj).startsWith("{=")) { // interpret array - // formulas as well - // 20090526 KSC: - // changed from "{" - // to "{=" tracy vo - // complex string - // addition - try { - rec = FormulaParser - .getFormulaFromString((String) obj, this, rc); - rec.isFormula = true; - } catch (Exception e) { - throw new FunctionNotSupportedException( - "Adding new Formula at " + this.getSheetName() + "!" - + ExcelTools.formatLocation(rc) - + " failed: " + e.toString() + "."); - } - } else if (obj.toString().equalsIgnoreCase("")) { - rec = new Blank(); - } else { - rec = Labelsst.getPrototype((String) obj, this.getWorkBook()); - } - } else if (obj instanceof Integer) { - int l = ((Integer) obj).intValue(); - rec = new NumberRec(l); - - } else if (obj instanceof Long) { - long l = ((Long) obj).longValue(); - rec = new NumberRec(l); - } else if (obj instanceof Boolean) { - // Logger.logErr("Adding Boolean Not Implemented"); - rec = Boolerr.getPrototype(); - rec.setBooleanVal(((Boolean) obj).booleanValue()); - } else { - double d = new Double(String.valueOf(obj)).doubleValue(); // 20080211 - // KSC: - // Double.valueOf(String.valueOf(obj)).doubleValue(); - rec = new NumberRec(d); - } - rec.setWorkBook(getWorkBook()); - rec.setXFRecord(FORMAT_ID); - // 20100607 KSC: update maxrow/maxcol if necessary - if (rc[0] > getMaxRow() || rc[1] > getMaxCol()) - this.updateDimensions(rc[0], rc[1]); - return rec; - } - - /** - * Add an XLSRecord to a WorkSheet. - *

                  - * Creates the container cell for a record, sets the default - * information on the valrec (ie row/col/bs), checks to see if - * there is a container row for the cell, if not, then it creates the - * row. Finally, the cell is passed on to addCellToRowCol where it performs - * final initialization and is added to it's row - */ - @Override - public void addRecord(BiffRec rec, int[] rc) { - // check to see if there is a BiffRec already at the address - // add the rec to the Cell, - // set as value if it's a val type rec - - rec.setSheet(this);// create a new BiffRec if none exists - rec.setRowCol(rc); - rec.setIsValueForCell(true); - rec.setStreamer(streamer); - rec.setWorkBook(this.getWorkBook()); - - if (!this.fastCellAdds) { - - Row ro = null; - ro = rows.get(Integer.valueOf(rc[0])); - if (ro == null) - ro = this.addNewRow(rec); - - } - if (copypriorformats && !this.fastCellAdds) - this.copyPriorCellFormatForNewCells(rec); - - try { - this.addCell((CellRec) rec); - } catch (ArrayIndexOutOfBoundsException ax) { - Logger.logErr("Boundsheet.addRecord() failed. Column " + rc[1] - + " is greater than Maximum column count"); - throw new InvalidRecordException("Adding cell failed. Column " - + rc[1] + " is greater than the maximum column limit."); - } - } - - /** - * Add a cell to this boundsheet record and populate the cells array - * - * @param cell - */ - @Override - public void addCell(CellRec cell) { - cellsByRow.put(cell, cell); - cellsByCol.put(cell, cell); - Row row = rows.get(Integer.valueOf(cell.getRowNumber())); - if (null == row) - row = this.addNewRow(cell); - row.addCell(cell); - if (cell != null) - cell.setSheet(this); - this.updateDimensions(cell.getRowNumber(), cell.getColNumber()); - } - - private boolean copypriorformats = true; - - @Override - public void setCopyPriorCellFormats(boolean f) { - this.copypriorformats = f; - } - - private boolean copyPriorCellFormatForNewCells(BiffRec c) { - int row = c.getRowNumber() + 1; // get the prior cell addy - String cnm = ExcelTools.getAlphaVal(c.getColNumber()); - BiffRec ch = this.getCell(cnm + row); // try it... - if (ch == null) - return false; - c.setIxfe(ch.getIxfe()); - return true; - } - - /** - * Returns the *real* last col num. Unfortunately the dimensions record - * cannot be counted on to give a correct value. - **/ - public int getRealMaxCol() { - return this.maximumCellCol; - } - - /** - * Add a new colinfo - * - * @param begCol The beginning column number (0 based) - * @param endCol The end column number - * @param width Initial width of the column - * @param ixfe formatting - * @param grbit ?? - * @return Colinfo - */ - public Colinfo addColinfo(int begCol, int endCol, int width, int ixfe, int grbit) { - Colinfo ci = Colinfo.getPrototype(begCol, endCol, width, ixfe); - ci.setGrbit(grbit); - ci.setWorkBook(getWorkBook()); - ci.setSheet(this); - this.addColinfo(ci); - int recpos = this.getDimensions().getRecordIndex(); - recpos--; - List sr = this.getSheetRecs(); - // get to last Colinfo record - BiffRec rec = sr.get(recpos); - // TODO: is it ABSOLUTELY true that if no Colinfos there - // must be a DefColWidth record???? - while (!(rec instanceof Colinfo) && !(rec instanceof DefColWidth) - && recpos > 0) { // loop until we find either a colinfo or - // DEFCOLWIDTH - rec = sr.get(--recpos); - } - // now position this Colinfo in the proper position within - // the Colinfo set - int cf = ci.getColFirst(); - while (rec instanceof Colinfo && ((Colinfo) rec).getColFirst() > cf) { - rec = sr.get(--recpos); - } - recpos++; - this.getStreamer().addRecordAt(ci, recpos); - return ci; - } - - /** - * get a colinfo by name - */ - @Override - public Colinfo getColinfo(String c) { - return this.getColInfo(ExcelTools.getIntVal(c)); - } - - /** - * get the Collection of Colinfos - */ - @Override - public Collection getColinfos() { - return Collections.unmodifiableCollection(colinfos.values()); - } - - private int maximumCellCol = -1; - private int maximumCellRow = -1; - - /** - * Moves a cell location from one address to another - */ - public void moveCell(String startaddr, String endaddr) { - BiffRec c = getCell(startaddr); - if (c.getOpcode() == WorkBookFactory.RK) { - try { - Double d = new Double(c.getDblVal()); - this.removeCell(c); - this.addValue(d, endaddr); - } catch (Exception e) { - Logger.logInfo("Boundsheet.moveCell() error :" + e); - } - } else { - int[] s = ExcelTools.getRowColFromString(endaddr); - c.setCol((short) s[1]); - c.setRowNumber(s[0]); - removeCell(startaddr); - this.addCell((CellRec) c); - } - } - - /** - * Moves a cell location from one address to another, - * without any clearing of previous locations. This is used in sorting - * and other cell movements where we do not want to delete from starting address - */ - public void updateCellReferences(BiffRec c, String endaddr) { - if (c.getOpcode() == WorkBookFactory.RK) { - try { - Double d = new Double(c.getDblVal()); - this.removeCell(c); - this.addValue(d, endaddr); - } catch (Exception e) { - Logger.logInfo("Boundsheet.moveCell() error :" + e); - } - } else { - int[] s = ExcelTools.getRowColFromString(endaddr); - c.setCol((short) s[1]); - c.setRowNumber(s[0]); - this.addCell((CellRec) c); - } - } - - /** - * Gets a cell on this sheet by its Excel A1-style address. - * - * @param address the A1-style address of the cell to retrieve - * @return the cell record - * or null if no cell exists at the given address - * @deprecated Use {@link #getCell(int, int)} instead. - */ - @Deprecated - @Override - public BiffRec getCell(String address) { - int[] rc = ExcelTools.getRowColFromString(address); - try { - return this.getCell(rc[0], rc[1]); - } catch (CellNotFoundException ex) { - return null; - } - } - - /** - * Gets a cell on this sheet by its row and column indexes. - * - * @param row the zero-based index of the cell's parent row - * @param col the zero-based index of the cell's parent column - * @return the cell record at the given address - * @throws CellNotFoundException if no cell exists at the given address - */ - @Override - public BiffRec getCell(int row, int col) throws CellNotFoundException { - // get the nearest entry from the cell map - BiffRec theCell = cellsByRow - .get(new CellAddressible.Reference(row, col)); - if (null == theCell) - throw new CellNotFoundException(this.sheetname, row, col); - - if (theCell != null && theCell.getOpcode() == MULBLANK) { - ((Mulblank) theCell).setCurrentCell((short) col); - } - return theCell; - } - - /** - * get an array of all cells for this worksheet - */ - @Override - public BiffRec[] getCells() { - Collection cells = cellsByRow.values(); - return cells.toArray(new BiffRec[cells.size()]); - } - - @Override - public void addMergedCellsRec(Mergedcells r) { - mc.add(r); - } - - /** - * Get the built in names referring to this boundsheet - * - * @return - */ - private ArrayList getBuiltInNames() { - ArrayList retlist = new ArrayList(); - Name[] ns = this.getWorkBook().getNames(); - for (int i = 0; i < ns.length; i++) { - if (ns[i].isBuiltIn() - && ((ns[i].getIxals() == this.getSheetNum() + 1) - || (ns[i].getItab() == this.getSheetNum() + 1))) { - retlist.add(ns[i]); - } - } - return retlist; - } - - /** - * Get the print area or titles name rec for this - * boundsheet, return null if not exists - * - * @return - */ - protected Name getPrintAreaNameRec(byte type) { - ArrayList names = this.getBuiltInNames(); - for (int i = 0; i < names.size(); i++) { - Name n = (Name) names.get(i); - if (n.getBuiltInType() == type) { - return n; - } - } - return null; - } - - /** - * Get the print area name rec for this - * boundsheet, return null if not exists - * - * @return - */ - protected Name getPrintAreaNameRec() { - return getPrintAreaNameRec(Name.PRINT_AREA); - } - - /** - * Get the print area set for this WorkSheetHandle. - *

                  - * If no print area is set return null; - */ - public String getPrintArea() { - Name n = this.getPrintAreaNameRec(); - if (n != null) { - String ret = ""; - Stack s = n.getExpression(); - for (int x = 0; x < s.size(); x++) { - Ptg p = (Ptg) s.get(x); - /* - * if (p instanceof PtgArea3d) {// can be other than ptgarea - * ... - * ((PtgRef)p).clearLocationCache();// why?? - * return p.toString(); - * } - */ - ret += p.toString(); - } - return ret; - } - return null; - } - - /** - * Get the Print Titles range set for this WorkSheetHandle. - *

                  - * If no Print Titles are set, this returns null; - */ - public String getPrintTitles() { - Name n = getPrintAreaNameRec(Name.PRINT_TITLES); - if (n != null) { - Stack s = n.getExpression(); - for (int x = 0; x < s.size(); x++) { - Ptg p = (Ptg) s.get(x); - return p.toString(); - } - } - - return null; - } - - /** - * Set the print area for this worksheet. - */ - public void setPrintArea(String range) { - setPrintArea(range, Name.PRINT_AREA); - } - - /** - * Set the print titles for this worksheet= row(s) or col(s) to repeat at the top of each page - */ - public void setPrintTitles(String range) { - setPrintArea(range, Name.PRINT_TITLES); - } - - /** - * adds the _FILTERDATABASE name necessary for AutoFilter - * if not already presetn - */ - private void addFilterDatabase() { - List names = this.getBuiltInNames(); - Name n = null; - for (int i = 0; i < names.size() && n == null; i++) { - if (((Name) names.get(i)) - .getBuiltInType() == Name._FILTER_DATABASE) { - n = (Name) names.get(i); - } - } - if (n == null) { // not present - try { - n = new Name(this.getWorkBook(), "Built-in: _FILTER_DATABASE"); - n.setBuiltIn(Name._FILTER_DATABASE); - int xref = this.getWorkBook().getExternSheet(true) - .insertLocation(this.getSheetNum(), this.getSheetNum()); - n.setExternsheetRef(xref); - n.updateSheetReferences(this); - n.setSheet(this); - n.setIxals((short) (this.getSheetNum()/* +1 */)); - n.setItab((short) (this.getSheetNum() + 1)); - String loc = ExcelTools - .formatLocation(new int[]{this.getMinRow(), - this.getMinCol(), this.getMaxRow() - 1, - this.getMaxCol() - 1}, false, false); - Stack s = new Stack(); - s.push(PtgRef.createPtgRefFromString(this.getSheetName() + "!" - + loc, n)); - n.setExpression(s); - } catch (Exception e) { - - } - } - } - - /** - * remove the _FILTER_DATABASE name (necessary for AutoFilters) for this sheet - */ - private void removeFilterDatabase() { - List names = this.getBuiltInNames(); - Name n = null; - try { - for (int i = 0; i < names.size() && n == null; i++) { - if (((Name) names.get(i)) - .getBuiltInType() == Name._FILTER_DATABASE) { - n = (Name) names.get(i); - this.getWorkBook().removeName(n); - break; - } - } - } catch (Exception e) { - } - } - - /** - * Set the print area or titles for this worksheet. - */ - public void setPrintArea(String printarea, byte type) { - if (type != Name.PRINT_TITLES) {// can have multiple print title refs - Name n = this.getPrintAreaNameRec(type); - if (n != null) { // TODO: should check if same SHEET -- look at! - Stack s = n.getExpression(); - for (int x = 0; x < s.size(); x++) { - Ptg p = (Ptg) s.get(x); - if (p instanceof PtgRef) { - Ptg ptg = PtgRef.createPtgRefFromString(printarea, n); - s.remove(x); - s.add(x, ptg); - } - } - return; - } - } - // create the name - try { - String t; - if (type == Name.PRINT_AREA) { - t = "PRINT_AREA"; - } else { - t = "PRINT_TITLES"; - } - Name n = new Name(this.getWorkBook(), "Built-in: " + t); - n.setBuiltIn(type); - int xref = this.getWorkBook().getExternSheet(true) - .insertLocation(this.getSheetNum(), this.getSheetNum()); - n.setExternsheetRef(xref); - n.updateSheetReferences(this); - n.setSheet(this); - n.setIxals((short) (this.getSheetNum())); - n.setItab((short) (this.getSheetNum() + 1)); - Stack s = new Stack(); - Ptg p = PtgRef.createPtgRefFromString(printarea, n); - s.push(p); - n.setExpression(s); - } catch (Exception e) { - Logger.logErr("Error setting print area in boundsheet: " + e); - } - } - - /** - * returns an arrayList of notes in the worksheet - * - * @return - */ - public ArrayList getNotes() { - ArrayList notes = new ArrayList(); - int idx = this.getIndexOf(NOTE); - while (idx > -1) { - notes.add(SheetRecs.get(idx++)); - if (((BiffRec) SheetRecs.get(idx)).getOpcode() != NOTE) - break; - } - return notes; - } - - /*** - */ - @Override - public Mergedcells getMergedCellsRec() { - if (mc.size() == 0) - return null; // 20081031 KSC- don't automatically add new! - return (Mergedcells) this.getMergedCellsRecs() - .get(this.getMergedCellsRecs().size() - 1); - } - - @Override - public List getMergedCellsRecs() { - return mc; - /* - * 20081031 don't add a merged cell rec automatically - * if (mc.size()>0) { - * return mc; - * } - * Mergedcells mec = - * (Mergedcells)Mergedcells.getPrototype(); - * mec.setSheet(this); - * this.getStreamer().addRecordAt(mec, - * this.getSheetRecs().size()-1); - * this.addMergedCellsRec(mec); - */ - } - - /** - * adds a merged cell to this sheet - * - * @return - */ - public Mergedcells addMergedCellRec() { - Mergedcells mec = (Mergedcells) Mergedcells.getPrototype(); - mec.setSheet(this); - this.getStreamer().addRecordAt(mec, this.getSheetRecs().size() - 1); - this.addMergedCellsRec(mec); - return mec; - } - - /** - * return existing merged cell records without adding new blank - * - * @return - */ - public List getMergedCells() { - return mc; - } - - /** - * return truth of "has merged cells" - * - * @return - */ - public boolean hasMergedCells() { - return mc.size() > 0; - } - - /** - * get the name of the sheet - */ - @Override - public String getSheetName() { - return sheetname; - } - - /** - * get the name of the sheet - */ - @Override - public String toString() { - return getSheetName(); - } - - /** - * initialize the SheetImpl with data from - * the byte array. - */ - @Override - public void init() { - super.init(); - int lt = ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), this - .getByteAt(2), this.getByteAt(3)); - - // this is the index used by the BOF's Sheet to associate - // the record - lbPlyPos = lt; - grbit = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - if (DEBUGLEVEL > 9) { - Logger.logInfo("Sheet grbit: " + grbit); - Logger.logInfo(" lbplypos: " + lbPlyPos); - } - cch = this.getByteAt(6); - grbitChr = this.getByteAt(7); - byte[] namebytes = this.getBytesAt(8, this.getLength() - 12); - try { - if (grbitChr == 0x1) { - sheetname = new String(namebytes, - WorkBookFactory.UNICODEENCODING); - } else { - sheetname = new String(namebytes, - WorkBookFactory.DEFAULTENCODING); - } - } catch (UnsupportedEncodingException e) { - Logger.logInfo("Boundsheet.init() Unsupported Encoding error: " - + e); - } - if (DEBUGLEVEL > 9) - Logger.logInfo("Sheet name: " + sheetname); - ooxmlObjects = new ArrayList(); // possible that boundsheet is created - // by readObject and therefore - // ooxmlObjects will not be set - } - - /** - * change the displayed name of the sheet - *

                  - * Affects the following byte values: - * 10 cch 1 Length of sheet name - * 11 grbitChr 1 Compressed/Uncompressed Unicode - * 12 rgch var Sheet name - */ - @Override - public void setSheetName(String newname) { - - cch = (byte) newname.length(); - byte[] namebytes = newname.getBytes(); - // if (!ByteTools.isUnicode(namebytes)){ - if (!ByteTools.isUnicode(newname)) { - grbitChr = 0x0; - } else { - grbitChr = 0x1; - } - try { - if (grbitChr == 0x1) { - namebytes = newname.getBytes(WorkBookFactory.UNICODEENCODING); - } else { - namebytes = newname.getBytes(WorkBookFactory.DEFAULTENCODING); - } - } catch (UnsupportedEncodingException e) { - namebytes = newname.getBytes(); - Logger.logWarn("UnsupportedEncodingException in setting sheet name: " - + e + " falling back to system default."); - } - byte[] newdata = new byte[namebytes.length + 8]; - if (data == null) - this.data = newdata; - else - System.arraycopy(this.getData(), 0, newdata, 0, 8); - - System.arraycopy(namebytes, 0, newdata, 8, namebytes.length); - newdata[6] = cch; - newdata[7] = grbitChr; - this.setData(newdata); - this.init(); - } - - /** - * Returns a serialized copy of this Boundsheet - * - * @throws IOException - */ - @Override - public byte[] getSheetBytes() throws IOException { - this.setLocalRecs(); - ObjectOutputStream obs = null; - byte[] b = null; - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - obs = new ObjectOutputStream(baos); - obs.writeObject(this); - b = baos.toByteArray(); - - return b; - } - - /** - * prior to serializing the worksheet, - * we need to initialize the records which belong to this sheet - * instance. - */ - @Override - public void setLocalRecs() { - localrecs = new CompatibleVector(); - - List newSheetRecs = this.assembleSheetRecs(); - - Iterator shtr = newSheetRecs.iterator(); - while (shtr.hasNext()) { - try { - XLSRecord x = (XLSRecord) shtr.next(); - x.getData(); - if (x instanceof Labelsst) { // put the String in the label - ((Labelsst) x).initUnsharedString(); - } - localrecs.add(x); - } catch (Exception e) { - Logger.logWarn("Setting Boundsheet records problem: " + e); - } - } - // add the charts to the boundsheet, as they are stored in - // the workbook normally. (why?) - charts.clear(); - Chart[] chts = this.getWorkBook().getCharts(); - for (int i = 0; i < chts.length; i++) { - if (chts[i].getSheet().equals(this)) { - charts.add(chts[i]); - } - } - } - - /** - * This seems incorrect, should we not be just returning - * charts for the boundsheet in question? - * - * @return - */ - public List getCharts() { - return charts; - } - - /** - * get the type of sheet as a short - */ - @Override - public short getSheetType() { - - return grbit; - } - - /** - * get the type of sheet as a string - */ - @Override - public String getSheetTypeString() { - switch (grbit) { - case SHEET_DIALOG: - return "Sheet or Dialog"; - case XL4_MACRO: - return "XL4 Macro"; - case CHART: - return "Chart"; - case VBMODULE: - return "VB Module"; - default: - return null; - } - } - - /** - * add chart to sheet-specific list of charts - * - * @param c - * @see WorkBook.addRecord - */ - public void addChart(Chart c) { - charts.add(c); - } - - /** - * Adds a chart from a bytestream, keeps the title intact. - * - * @param inbytes - * @return - */ - public Chart addChart(byte[] inbytes, short[] coords) { - return this.addChart(inbytes, "useDefault", coords); - } - - /* - * Inserts a serialized boundsheet into the workbook, and - * changes the name. - */ - @Override - public Chart addChart(byte[] inbytes, String NewChartName, short[] coords) { - Chart destChart = null; - // Deserialize bytes - try { - ByteArrayInputStream bais = new ByteArrayInputStream(inbytes); - BufferedInputStream bufstr = new BufferedInputStream(bais); - ObjectInputStream o = new ObjectInputStream(bufstr); - destChart = (Chart) o.readObject(); - } catch (Exception e) { - Logger.logInfo("Boundsheet.addChart() failed:" + e); - } - if (destChart != null) { // got chart - if (!NewChartName.equals("useDefault")) - destChart.setTitle(NewChartName); // set new name - // why do we need this??? shouldn't it be already set?? - // destChart.getChartFormat().setParentChart(destChart); // - // make same as WorkBook.addChart - destChart.setSheet(this); - // BUGTRACKER 2372: chart bounds are dependent upon row+col - // sizes so use coordinates (which are row/col independent) - // does it makes sense to only set h + w and NOT x and y - short[] origCoords = destChart.getCoords(); - coords[0] = origCoords[0]; // don't set X and Y (keep to original - // row and column - coords[1] = origCoords[1]; - destChart.setCoords(coords); // but set w + h - destChart.setId(this.lastObjId + 1); // 20100210 KSC: track last obj - // id per sheet ... - HashMap localFonts = null; // fonts currently in workbook - if (this.getTransferFonts() != null - && this.getTransferFonts().size() > 0) { // then must - // translate old - // font indexes - // to new font - // indexes - localFonts = (HashMap) this.getWorkBook().getFontRecsAsXML(); // fonts - // in - // this - // workbook - } - List recs = destChart.getXLSrecs(); - for (int i = 0; i < recs.size(); i++) { - XLSRecord rec = (XLSRecord) recs.get(i); - rec.setWorkBook(wkbook); - rec.setSheet(this); - if (rec.getOpcode() == MSODRAWING) { - wkbook.addChartUpdateMsodg((MSODrawing) rec, this); - continue; - } - if (!(rec instanceof Bof)) // TODO: error/problem with the BOF - // record!!! - rec.init(); - if (rec instanceof Dimensions) - destChart.setDimensions((Dimensions) rec); - if (rec instanceof FontBasis) { // 20090506 KSC: fontbasis font - // indexes link to subsequent - // text displays [added for - // BUGTRACKER 2372] - int fid = ((FontBasis) rec).getFontIndex(); - // see if must translate old font indexes to new font - // indexes - fid = translateFontIndex(fid, localFonts); - ((FontBasis) rec).setFontIndex(fid); - } - if (rec instanceof Fontx) { // 20080911 KSC: must handle out of - // bounds font references upon chart - // copies [JPM BugTracker 1434] - int fid = ((Fontx) rec).getIfnt(); - if (fid > 0) - fid = translateFontIndex(fid, localFonts); - ((Fontx) rec).setIfnt(fid); - } - try { - ((GenericChartObject) rec).setParentChart(destChart); - } catch (ClassCastException e) { // Scl, Obj and others are not - // chart objects - } - // try{Logger.logInfo("Boundsheet Added new Chart rec:" + - // rec);}catch(Exception - // e){Logger.logWarn("Boundsheet.addChart() could not get - // String for rec: "+ rec.getCellAddress());} - } - this.wkbook.getChartVect().add(destChart); - } - charts.add(destChart); - return destChart; - } - - /** - * @param fid - * @return - */ - int translateFontIndex(int fid, HashMap localFonts) { - if (transferFonts != null && fid - 1 < transferFonts.size()) { - // must translate fid to corrent font index for current - // fonts - // translate font style and see if already present - Font thisFont = (Font) transferFonts.get(fid - 1); - String xmlFont = "<" + thisFont.getXML() + "/>"; - Object fontNum = localFonts.get(xmlFont); - if (fontNum != null) { // then get the fontnum in this book - fid = ((Integer) fontNum).intValue(); - } else { // it's a new font for this workbook, add it in - fid = this.getWorkBook().insertFont(thisFont) + 1; - localFonts.put(xmlFont, Integer.valueOf(fid)); - } - } - if (fid > this.getWorkBook().getNumFonts()) { // if fid is still - // incorrect, set to 0 - fid = 0; - } - return fid; - } - - /** - * populateForTransfer is a method that takes all of the shared resources (SST, XF, Font, etc) records and - * verifies that they are populated for use in a destination workbook - */ - public void populateForTransfer() { - this.getSheetHash(); - BiffRec[] recs = this.getCells(); - for (int i = 0; i < recs.length; i++) { - if (recs[i].getOpcode() == XLSConstants.LABELSST) { - Labelsst mylabel = (Labelsst) recs[i]; - mylabel.initUnsharedString(); - } - } - transferXfs = this.getWorkBook().getXfrecs(); - for (int i = 0; i < transferXfs.size(); i++) { - Xf x = (Xf) transferXfs.get(i); - x.populateForTransfer(); - } - - transferFonts = this.getWorkBook().getFontRecs(); - for (int i = 0; i < transferFonts.size(); i++) { - Font x = (Font) transferFonts.get(i); - x.getData(); - } - } - - /** - * return local XF records, used for boundsheet transferral. - */ - protected List getTransferXfs() { - return transferXfs; - } - - /** - * return local Font records, used for boundsheet transferral. - */ - protected List getTransferFonts() { - return transferFonts; - } - - @Override - public Guts getGuts() { - return guts; - } - - @Override - public void setGuts(Guts g) { - guts = g; - } - - @Override - public void setWsBool(WsBool ws) { - wsbool = ws; - } - - @Override - public WsBool getWsBool() { - return wsbool; - } - - /** - * @return Returns the localrecs. - */ - @Override - public List getLocalRecs() { - return localrecs; - } - - /** - * @param localrecs The localrecs to set. - */ - public void setLocalRecs(FastAddVector l) { - this.localrecs = l; - } - - public void setSheetRecs(AbstractList shtRecs) { - this.SheetRecs = shtRecs; - } - - /** - * @return Returns the grbitChr. - */ - @Override - public byte getGrbitChr() { - return grbitChr; - } - - /** - * @param grbitChr The grbitChr to set. - */ - @Override - public void setGrbitChr(byte gb) { - this.grbitChr = gb; - } - - /** - * @return Returns the lastselection. - */ - public Selection getLastselection() { - return lastselection; - } - - /** - * @param lastselection The lastselection to set. - */ - public void setLastselection(Selection lastselection) { - this.lastselection = lastselection; - } - - /** - * Set to true to turn off checking for existing cells, conditional formats and merged ranges in order to - * accelerate adding new cells - * - * @param fastCellAdds The fastCellAdds to set. - */ - public void setFastCellAdds(boolean fastCellAdds) { - this.fastCellAdds = fastCellAdds; - } - - /** - * scl is for zoom - * - * @return - */ - public Scl getScl() { - if (scl == null) { // we needs one! - scl = new Scl(); - this.SheetRecs.add(this.getIndexOfWindow2(), scl); - scl.setSheet(this); - } - return scl; - } - - /** - * scl is for zoom - * - * @param scl - */ - public void setScl(Scl s) { - this.scl = s; - } - - /** - * Set whether to shift formula cells inclusively - * i.e. if inserting row 5, shift formula D5:D6 down or D6:D7 OR shift inclusive by D5:D7 - */ - public void setShiftRule(boolean bShiftInclusive) { - formulaShiftInclusive = bShiftInclusive; - } - - /** - * return whether to shift (formula cells, named ranges) "inclusive" or not - * - * @return - */ - public boolean isShiftInclusive() { - return formulaShiftInclusive; - } - - /** - * Gets the printer setup handle for this sheet. - */ - public PrinterSettingsHandle getPrinterSetupHandle() { - return new PrinterSettingsHandle(this); - } - - /** - * Gets a list of the print related records for this sheet. - * - * @return an unmodifiable list of all printing-related records - */ - public List getPrintRecs() { - return Collections.unmodifiableList(printRecs); - } - - /** - * Adds a print-related record to the list of said. - */ - public void addPrintRec(BiffRec record) { - if (printRecs == null) - printRecs = new ArrayList(); - printRecs.add(record); - } - - /** - * return the AutoFilter record for this Boundsheet, if any - * TODO: Merge with OOXML Autofilter - * - * @return - */ - public List getAutoFilters() { - return autoFilters; - } - - /** - * Adds a new Note or Comment to the sheet at the desired address - * - * @param address - String cell address - * @param txt - Text of Note - * @param author - String Author of Note - * @return NoteHandle - a handle to the Note object which allows manipulation - */ - public Note createNote(String address, String txt, String author) { - // first check if a note is already attached to this addrss - ArrayList notes = this.getNotes(); - if (address.indexOf('!') == -1) - address = this.getSheetName() + "!" + address; - for (int i = 0; i < notes.size(); i++) { - Note n = (Note) notes.get(i); - if (n.getCellAddressWithSheet().equals(address)) { - n.setText(txt); - n.setAuthor(author); - return n; - } - } - - // add required Mso/object records - int[] coords = ExcelTools.getRowColFromString(address); - int insertIndex = this.insertMSOObjectsForNote(coords); - - // after mso/obj/mso, now add txo/continue/continue and note - // record - Txo t = (Txo) Txo.getPrototype(); - t.setSheet(this); - this.SheetRecs.add(insertIndex++, t); - this.SheetRecs.add(insertIndex++, t.text); // add the associated - // Continues that defines - // the text - t.text.setPredecessor(t); // link this continues to it's predecessor - Continue c = Continue.getBasicFormattingRunContinues(); - c.setPredecessor(t); // TODO: is this correct???? - c.setSheet(this); - this.SheetRecs.add(insertIndex++, c); // and add associated formatting - // runs - try { - t.setStringVal(txt); // must do *after* adding continues - } catch (IllegalArgumentException e) { - Logger.logErr(e.toString()); - } - // after (mso/obj/mso/txo/continue/continue) * n, note - // records are listed in order - insertIndex = this.getIndexOf(WINDOW2); - Note n = (Note) Note.getPrototype(author); - n.setId(this.lastObjId); // same as Obj record above - n.setSheet(this); - n.setRowCol(coords[0], coords[1]); - this.SheetRecs.add(insertIndex, n); - return n; - } - - /** - * Handles the MSO manipulations necessary for creating a note record - *

                  - * // For each note: - * // [msodrawing - * // obj - ftNts note - * // msodrawing - attached shape - * // Txo (text object), continue, continue] x n - * // [note 1] x n - * // window 2 - * // ************************************************************************************ - * // NOTE: - * // SOME TEMPLATES HAVE [obj= ftNts, Continue, Txo, continue, continue, continue] - * // MORE INFO (get this): - * // Obj, Continue= 2nd MSO!!!, Txo, Continue, Continue, Continue= 1st MSO!!! - * // ************************************************************************************ - * - * @param coords rowcol of the note record - * @return insertion index for note - */ - private int insertMSOObjectsForNote(int[] coords) { - int insertIndex; - MSODrawingGroup msodg = this.wkbook.getMSODrawingGroup(); - if (msodg == null) { - msodg = this.wkbook.createMSODrawingGroup(); - msodg.initNewMSODrawingGroup(); - } - - // insert either above first NOTE record or before WINDOW2 - // and certain other XLSRECORDS - insertIndex = this.getIndexOf(NOTE); - if (insertIndex == -1) // no existing notes - find proper insert index - insertIndex = this.getIndexOf(WINDOW2); - while ((insertIndex - 1) > 0) { - short opc = ((BiffRec) SheetRecs.get(insertIndex - 1)).getOpcode(); - if (opc == MSODRAWING || opc == CONTINUE) { - MSODrawing rec; - if (opc == MSODRAWING) - rec = ((MSODrawing) SheetRecs.get(insertIndex - 1)); - else { - rec = ((Continue) SheetRecs.get(insertIndex - 1)).maskedMso; - if (rec == null) - break; - } - if (rec.getSOLVERContainerLength() == 0) - break; // solver containers must be last, apparently ... - // sigh ... - // else - // Logger.logInfo("Boundsheet.InsertMSOObjectsForNote. - // SOLVER CONTAINER ENCOUNTED"); - } else if (opc == OBJ || opc == CONTINUE || opc == DIMENSIONS - || opc == 0x866 || opc == 0x1C2) - break; - insertIndex--; - } - - MSODrawing msoheader = msodg.getMsoHeaderRec(this); - MSODrawing msoDrawing = (MSODrawing) MSODrawing.getPrototype(); - msoDrawing.setSheet(this); - msoDrawing.setWorkBook(this.getWorkBook()); - if (msoheader == null) { - msoDrawing.setIsHeader(); - msoheader = msoDrawing; - } - - // mso record which creates a text box - msoDrawing.createCommentBox(coords[0], coords[1]); - this.SheetRecs.add(insertIndex++, msoDrawing); - msoheader.numShapes++; - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the - // msodrawinggroup set of recs - - // object record which defines a basic note - Obj obj = Obj.getBasicObjRecord(Obj.otNote, ++this.lastObjId); // create - // a - // note - // object - this.SheetRecs.add(insertIndex++, obj); - - // now add attached text-type mso, specifying the shape has - // attached text - msoDrawing = (MSODrawing) MSODrawing.getTextBoxPrototype(); - msoDrawing.setSheet(this); - this.SheetRecs.add(insertIndex++, msoDrawing); - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the - // msodrawinggroup set of recs - - // now update msodg + msoheader rec - wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing header - // record and update it (using - // info from other msodrawing - // recs) - msodg.setSpidMax(this.wkbook.lastSPID + 1); - msodg.updateRecord(); // given all information, generate appropriate - // bytes for the Mso rec - msodg.dirtyflag = true; - return insertIndex; - } - - /** - * Handles the MSO records necessary for defining a DropDown list object - * - * @return - */ - public int insertDropDownBox(int colNum) { - int insertIndex; - MSODrawingGroup msodg = this.wkbook.getMSODrawingGroup(); - if (msodg == null) { - msodg = this.wkbook.createMSODrawingGroup(); - msodg.initNewMSODrawingGroup(); - } - - // insert either above first NOTE record or before WINDOW2 - // and certain other XLSRECORDS - insertIndex = this.getIndexOf(NOTE); - if (insertIndex == -1) // no existing notes - find proper insert index - insertIndex = this.getIndexOf(WINDOW2); - while ((insertIndex - 1) > 0) { - short opc = ((BiffRec) SheetRecs.get(insertIndex - 1)).getOpcode(); - if (opc == MSODRAWING || opc == CONTINUE) { - MSODrawing rec; - if (opc == MSODRAWING) { - rec = ((MSODrawing) SheetRecs.get(insertIndex - 1)); - } else { - rec = ((Continue) SheetRecs.get(insertIndex - 1)).maskedMso; - if (rec == null) - break; - } - if (rec.getSOLVERContainerLength() == 0) - break; // solver containers must be last, apparently ... - // sigh ... - // else - // Logger.logInfo("Boundsheet.InsertMSOObjectsForNote. - // SOLVER CONTAINER ENCOUNTED"); - } else if (opc == OBJ) { - Obj rec = (Obj) SheetRecs.get(insertIndex - 1); - if (rec.getObjType() == Obj.otDropdownlist) // TODO: verify that - // drop downs are - // reused/shared in - // all cases!!!! - return rec.getObjId(); // already have one return object id - break; - } else if (opc == OBJ || opc == CONTINUE || opc == DIMENSIONS - || opc == 0x866 || opc == 0x1C2) { - break; - } - insertIndex--; - } - - MSODrawing msoheader = msodg.getMsoHeaderRec(this); - MSODrawing msoDrawing = (MSODrawing) MSODrawing.getPrototype(); - msoDrawing.setSheet(this); - msoDrawing.setWorkBook(this.getWorkBook()); - if (msoheader == null) { - msoDrawing.setIsHeader(); - msoheader = msoDrawing; - } - - msoDrawing.createDropDownListStyle(colNum); // create the records - // necessary to define the - // dropdown box symbol at - // the desired column - - // object record which defines a basic dropdown list - Obj obj = Obj.getBasicObjRecord(Obj.otDropdownlist, ++this.lastObjId); // create - // a - // drop-down - // object - // record - // for - // each - int objID = obj.getObjId(); - - // insert new mso + obj records into sheet - this.SheetRecs.add(insertIndex++, msoDrawing); - this.SheetRecs.add(insertIndex++, obj); - - // now update msodg + msoheader rec - msoheader.numShapes++; - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec to the - // msodrawinggroup set of recs - wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing header - // record and update it (using - // info from other msodrawing - // recs) - msodg.setSpidMax(this.wkbook.lastSPID + 1); - msodg.updateRecord(); // given all information, generate appropriate - // bytes - msodg.dirtyflag = true; - - return objID; - } - - /** - * Adds a new Note or Comment to the sheet at the desired address - * with Formatting (Font) information - * - * @param address - String cell address - * @param txt - Unicode string reprentation of the note, including formatting - * @param author - String Author of Note - * @return NoteHandle - a handle to the Note object which allows manipulation - */ - public Note createNote(String address, Unicodestring txt, String author) { - Note nh = this.createNote(address, txt.getStringVal(), author); - // TODO: deal with formats - incorporate into Txo/Continues - // -- for now they are just stored as-is, no modification - // allowed - nh.setFormattingRuns(txt.getFormattingRuns()); - return nh; - } - - /** - * removes the desired note from the sheet - * - * @param n - */ - public void removeNote(Note n) { - int id = n.getId(); - int idx = this.getIndexOf(OBJ); - if (idx == -1) - return; // should't! - while (idx < this.SheetRecs.size()) { - if (((BiffRec) this.SheetRecs.get(idx)).getOpcode() == OBJ) { - Obj o = ((Obj) this.SheetRecs.get(idx)); - // if it's of type Note + has the same id, this is it - if (o.getObjType() == 0x19 && o.getObjId() == id) { // got it! - // apparently sometimes you don't find the mso/obj/mso - // combo, so check - if (((BiffRec) this.SheetRecs.get(idx - 1)) - .getOpcode() == MSODRAWING) { - idx--; - break; - } else if ((((BiffRec) this.SheetRecs.get(idx + 1)) - .getOpcode() == CONTINUE) - && (((Continue) this.SheetRecs - .get(idx + 1))).maskedMso != null) { - // idx++; - break; - } - } - } - idx++; - } - // usual format= mso/obj/mso/txo/continue/continue but can - // also be: - // obj/continue (mso)/txo/continue/continue/continue (mso) - int objidx = 0; - int msoidx = 0; - boolean maskedMso = true; // handle continues masking mso's - while (idx < this.SheetRecs.size()) { - MSODrawing mso = null; - BiffRec rec = (BiffRec) this.SheetRecs.get(idx); - if (rec.getOpcode() == OBJ) - objidx++; - else if (rec.getOpcode() == MSODRAWING) { - mso = (MSODrawing) rec; - maskedMso = false; - if (mso.getShapeType() == MSODrawingConstants.msosptTextBox) - msoidx++; - else if ((((MSODrawing) rec).isShape)) // it's not a text box or - // the associated text - // "oddball" mso, so - // break (Another test: - // SPID==0??) - break; - } else if (rec.getOpcode() == CONTINUE && maskedMso) { - mso = ((Continue) rec).maskedMso; - if (mso.getShapeType() == MSODrawingConstants.msosptTextBox) - msoidx++; - else if ((((MSODrawing) rec).isShape)) // it's not a text box or - // the associated text - // "oddball" mso, so - // break (Another test: - // SPID==0??) - break; - } else if (rec.getOpcode() == NOTE) - break; - if (objidx > 1 || msoidx > 1) // reached the next set of - // note-associated recs, so get out - break; - this.SheetRecs.remove(idx); // otherwise, ok to delete - if (mso != null && mso.isShape) {// if removed an mso, must update - // msodg - MSODrawingGroup msodg = this.wkbook.getMSODrawingGroup(); - msodg.removeMsodrawingrec(mso, this, true); - } - } - // now remove the actual note record - idx = this.getIndexOf(NOTE); - while (idx < this.SheetRecs.size() - && ((BiffRec) this.SheetRecs.get(idx)).getOpcode() == NOTE) { - if (this.SheetRecs.get(idx).equals(n)) { - this.SheetRecs.remove(idx); - break; // we're done - } - idx++; - } - } - - /** - * Adds a new AutoFilter to the specified column - * - * @param int column - 0-based column number - * @return AutoFilterHandle Handle to the new AutoFilter - */ - public AutoFilter addAutoFilter(int column) { - // if there are no existing AutoFilters on the sheet, - // then must add a mso/obj pair for each column on the sheet - // to define dropdown box next to each column - // also must add built-in name _FILTERDATABASE + - // must add a mystery XlSRecord with opcode== 0x9D -- cannot - // find any information about this opcode - if (this.autoFilters == null || this.autoFilters.size() == 0) { - // add _FILTERDATABASE Name - this.addFilterDatabase(); - - /* - * 20100216 KSC: WHAT ARE THESE RECORD??? They are necessary - * for new AutoFilter's - */ - int zz = getIndexOf(COLINFO); - if (zz == -1) - zz = getIndexOf(DEFCOLWIDTH) + 1; - else { - while (((BiffRec) this.SheetRecs.get(zz)) - .getOpcode() == COLINFO) - zz++; - } - // insert after COLINFOs or DefColWidth - XLSRecord rec = new XLSRecord(); - rec.setOpcode((short) 155); // no data for this record - rec.setData(new byte[]{}); - this.SheetRecs.add(zz++, rec); - rec = new XLSRecord(); - rec.setOpcode((short) 157); // this has SOMETHING to do with # - // columns ... - rec.setData(new byte[]{(byte) this.getMaxCol(), 0}); - this.SheetRecs.add(zz, rec); - - // add required Mso/object records - int insertIndex; - MSODrawingGroup msodg = this.wkbook.getMSODrawingGroup(); - if (msodg != null) { // already have drawing records; just add to - // records + update msodg - insertIndex = this.getIndexOf(MSODRAWINGSELECTION); - if (insertIndex < 0) - insertIndex = this.getIndexOf(WINDOW2); - } else { // No images present in workbook, must add appropriate - // records - msodg = this.wkbook.createMSODrawingGroup(); - msodg.initNewMSODrawingGroup(); // generate and add required - // records for drawing records - // insertion point for new msodrawing rec - insertIndex = getIndexOf(DIMENSIONS) + 1; - } - - MSODrawing msoheader = msodg.getMsoHeaderRec(this); - - // Must add for each column - for (int i = 0; i < this.getRealMaxCol(); i++) { - try { - if (this.getCellsByCol(i).size() == 0) - break; - } catch (CellNotFoundException e) { - break; - } - // Colinfo ci= (Colinfo) this.colinfos.get(i); - // short j= (short) ci.getColFirst(); // column number' - short j = (short) i; - MSODrawing msoDrawing = (MSODrawing) MSODrawing.getPrototype(); - msoDrawing.setWorkBook(this.wkbook); - msoDrawing.setSheet(this); - if (msoheader == null) { - msoDrawing.setIsHeader(); - msoheader = msoDrawing; - } - - msoDrawing.createDropDownListStyle(j); // create the records - // necessary to define - // the dropdown box - // symbol - - // object record which defines a basic dropdown list - Obj obj = Obj - .getBasicObjRecord(Obj.otDropdownlist, ++this.lastObjId); // create - // a - // drop-down - // object - // record - // for - // each - - // insert new mso + obj records into sheet - this.SheetRecs.add(insertIndex++, msoDrawing); - this.SheetRecs.add(insertIndex++, obj); - - // now update msodg + msoheader rec - msoheader.numShapes++; - msodg.addMsodrawingrec(msoDrawing); // add the new drawing rec - // to the msodrawinggroup - // set of recs - wkbook.updateMsodrawingHeaderRec(this); // find the msodrawing - // header record and - // update it (using info - // from other msodrawing - // recs) - msodg.setSpidMax(this.wkbook.lastSPID + 1); - msodg.updateRecord(); // given all information, generate - // appropriate bytes - msodg.dirtyflag = true; - } - } - - AutoFilter af = (AutoFilter) AutoFilter.getPrototype(); - af.setSheet(this); - af.setCol(column); - int i = getIndexOf(DIMENSIONS); // insert just before DIMENSIONS record - this.SheetRecs.add(i, af); - - this.autoFilters.add(af); - return af; - } - - /** - * removes all autofilters from this sheet - */ - public void removeAutoFilter() { - this.removeFilterDatabase(); // remove the _FILTER_DATABASE name - // necessary for AutoFilters - int zz = getIndexOf(AUTOFILTER); // remove all AutoFitler records - while (zz != -1) { - this.SheetRecs.remove(zz); - zz = getIndexOf(AUTOFILTER); - } - // remove the two unknown records - zz = getIndexOf((short) 155); - if (zz > -1) - this.SheetRecs.remove(zz); - zz = getIndexOf((short) 157); - if (zz > -1) - this.SheetRecs.remove(zz); - // and hows about the Mso/Obj records, huh? huh? - this.autoFilters.clear(); - // finally, must set all rows to NOT hidden - I believe - // Excel does this when AutoFilters are turned off - for (int i = 0; i < rows.size(); i++) - rows.get(Integer.valueOf(i)).setHidden(false); - } - - /** - * adds a sxview - pivot table lead record - and required associated records to the worksheet - *
                  other methods that add data, row, col and page fields will fill in the pivot table fields and formatting info - * - * @param ref Cell Range which identifies pivot table data range - * @param wbh WorkBookHandle - * @param sId Stream or cachid Id -- links back to SxStream set of records - * @return - */ - public Sxview addPivotTable(String ref, WorkBookHandle wbh, int sId, String tablename) { - this.wkbook.addPivotCache(ref, wbh, sId); // create the - // directory/storage for a - // pivot cache, if not - // already created - // ensure the proper directory/storage and pivot cache - // record is created - int zz = win2.getRecordIndex() - 1; - while (zz > 0) { - if (((BiffRec) this.SheetRecs.get(zz)).getOpcode() == NOTE) - break; - if (((BiffRec) this.SheetRecs.get(zz)).getOpcode() == OBJ) - break; - if (((BiffRec) this.SheetRecs.get(zz)).getOpcode() == DIMENSIONS) - break; - zz--; - } - zz++; - // minimal configuration - Sxview sx = (Sxview) Sxview.getPrototype(); - this.SheetRecs.add(zz++, sx); - this.SheetRecs.addAll(zz, sx.addInitialRecords(this)); - sx.setTableName(tablename); - this.wkbook.addPivotTable(sx); // add to lookup - return sx; - } - - /** - * update row filter (hidden status) by evaluating AutoFilter conditions on the sheet - *
                  Must do after autofilter updates or additions - */ - public void evaluateAutoFilters() { - // first must set all rows to NOT hidden - for (int i = 0; i < rows.size(); i++) - try { - rows.get(Integer.valueOf(i)).setHidden(false); - } catch (NullPointerException e) { - // blank rows ... - } - - // now evaluate all autofilters - for (int i = 0; i < autoFilters.size(); i++) { - ((AutoFilter) this.autoFilters.get(i)).evaluate(); - } - } - - /** - * returns the list of Excel 2007 objects which are external or auxillary to this sheet - * e.g printerSeettings, vmlDrawings - * - * @return - */ - public List getOOXMLObjects() { - return ooxmlObjects; - } - - /** - * adds the object-specific signature of the external or auxillary Excel 2007 object - * e.g. oleObjects, vmlDrawings - * - * @param o - */ - public void addOOXMLObject(Object o) { - ooxmlObjects.add(o); - } - - // TODO: Handle below options in Excel 2003 i.e. create - // appropriate records - // ************************************************************* - - /** - * set if row has thick bottom by default (Excel 2007-Specific) - */ - public boolean hasThickBottom() { - return thickBottom; - } - - /** - * return true if row has thick top by default (Excel 2007-Specific) - */ - public boolean hasThickTop() { - return thickTop; - } - - /** - * return true if rows are hidden by default (Excel 2007-Specific) - */ - public boolean hasZeroHeight() { - return zeroHeight; - } - - /** - * return true if defaultrowheight is manually set (Excel 2007-Specific) - */ - public boolean hasCustomHeight() { - return customHeight; - } - - /** - * return the default row height in points (Excel 2007-Specific) - */ - public double getDefaultRowHeight() { - return defaultRowHeight; - } - - /** - * return the default column width in # characters of the maximum digit width of the normal style's font - *

                  - * This is currently a floating point value, something I question. I don't understand the need for this, - * and possibly it should be an int? - */ - public float getDefaultColumnWidth() { - // biff8 setting - if (defColWidth != null) { - return defColWidth.getDefaultWidth(); - } - return defaultColWidth; - } - - /** - * set if row has thick bottom by default (Excel 2007-Specific) - */ - public void setThickBottom(boolean b) { - thickBottom = b; - } - - /** - * set if row has thick top by default (Excel 2007-Specific) - */ - public void setThickTop(boolean b) { - thickTop = b; - } - - /** - * set if rows are hidden by default (Excel 2007-Specific) - */ - public void setZeroHeight(boolean b) { - zeroHeight = b; - } - - /** - * set if defaultrowheight is manually set (Excel 2007-Specific) - */ - public void setHasCustomHeight(boolean b) { - customHeight = b; - } - - /** - * set the default row height in points (Excel 2007-Specific) - */ - public void setDefaultRowHeight(double h) { - defaultRowHeight = h; - } - - /** - * set the default column width in # characters of the maximum digit width of the normal style's font - */ - public void setDefaultColumnWidth(float w) { - // ooxml setting - defaultColWidth = w; - // biff8 setting - if (defColWidth != null) { - defColWidth.setDefaultColWidth((int) w); - } - } - - /** - * store Excel 2007 shape via Shape Name - * - * @param tca - */ - public void addOOXMLShape(io.starter.formats.OOXML.TwoCellAnchor tca) { - if (ooxmlShapes == null) - ooxmlShapes = new HashMap(); - ooxmlShapes.put(tca.getName(), tca); - } - - /** - * store Excel 2007 shape via Shape Name - * - * @param tca - */ - public void addOOXMLShape(io.starter.formats.OOXML.OneCellAnchor oca) { - if (ooxmlShapes == null) - ooxmlShapes = new HashMap(); - ooxmlShapes.put(oca.getName(), oca); - } - - /** - * Store Excel 2007 legacy drawing shapes - * - * @param vml - */ - public void addOOXMLShape(Object vml) { - if (ooxmlShapes == null) - ooxmlShapes = new HashMap(); - ooxmlShapes.put("vml", vml); // only 1 vml (=legacy drawing info) per - // sheet so just refer to it as "vml" - } - - /** - * return map of Excel 2007 shapes in this workbook - * - * @return - */ - public HashMap getOOXMLShapes() { - return ooxmlShapes; - } - - /** - * returns the Excel 2007 sheetView element for this sheet (controls topLeftCell, pane attributes ... - * - * @return - */ - public SheetView getSheetView() { - return sheetview; - } - - /** - * set the Excel 2007 sheetView element for this sheet (controls topLeftCell, pane attributes ... - * - * @param s - */ - public void setSheetView(SheetView s) { - sheetview = s; - } - - /** - * returns the Excel 2007 sheetPr sheet Properties element for this sheet (controls codename, tabColor ...) - * - * @return - */ - public SheetPr getSheetPr() { - return sheetPr; - } - - /** - * set the Excel 2007 sheetView element for this sheet (controls topLeftCell, pane attributes ... - * - * @param s - */ - public void setSheetPr(SheetPr s) { - sheetPr = s; - } - - /** - * returns the Excel 2007 autoFilter element for this sheet (temporarily hides rows based upon filter criteria) - * TODO: Merge with 2003 AutoFilter - * - * @return - */ - public io.starter.formats.OOXML.AutoFilter getOOAutoFilter() { - return ooautofilter; - } - - /** - * set the Excel 2007 autoFilter element for this sheet (temporarily hides rows based upon filter criteria) - * TODO: Merge with 2003 AutoFilter - * - * @param strref - */ - public void setOOAutoFilter(io.starter.formats.OOXML.AutoFilter a) { - ooautofilter = a; - } - - /** - * returns a scoped named range by name string - * - * @param t - * @return - */ - public Name getScopedName(String nameRef) { - Object o = this.sheetNameRecs.get(nameRef.toUpperCase()); // case - // insensitive - if (o == null) { - return null; - } - return (Name) o; - } - - /** - * Add a sheet-scoped name record to the boundsheet - *

                  - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this sheet, adding them here will not add them to the workbook; - * - * @param sheetNameRecs - */ - public void addLocalName(Name name) { - if (sheetNameRecs == null) - sheetNameRecs = new HashMap(); - sheetNameRecs.put(name.getNameA(), name); - } - - /** - * Remove a sheet-scoped name record from the boundsheet. - *

                  - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this sheet, removing them here will not remove them completely from the workbook. - *

                  - * In order to do that you will need to call book.removeName - * - * @param sheetNameRecs - */ - public void removeLocalName(Name name) { - sheetNameRecs.remove(name.getNameA()); - } - - /** - * Get a sheet scoped name record from the boundsheet - * - * @return - */ - public Name getName(String name) { - if (sheetNameRecs == null) - return null; - Object o = sheetNameRecs.get(name.toUpperCase()); // case insensitive - if (o != null) - return (Name) o; - return null; - } - - /** - * Get all the names for this boundsheet - * - * @return - */ - public Name[] getAllNames() { - if (this.sheetNameRecs == null) - this.sheetNameRecs = new HashMap(); - ArrayList retnames = new ArrayList(sheetNameRecs.values()); - Name[] names = new Name[retnames.size()]; - return (Name[]) retnames.toArray(names); - } - - /** - * add pritner setting record to worksheet recs - * - * @param r printer setting record (Margins, PLS) - */ - public void addMarginRecord(BiffRec r) { - r.setSheet(this); - int i = this.getIndexOf(SETUP); - int thisOpCode = r.getOpcode(); - // iterate up from SETUP record - // desired order: - // WsBool, HeaderRec, FooterRec, HCenter, VCenter, - // LeftMargin, RightMargin, TopMargin, BottomMargin, Pls - while (i > 0) { - int prevOpCode = ((BiffRec) this.getSheetRecs().get(--i)) - .getOpcode(); - if (prevOpCode == VCENTER - || /* assume AT LEAST a VCENTER or FOOTERREC */ - prevOpCode == FOOTERREC || prevOpCode == LEFT_MARGIN) { - break; - } - if ((prevOpCode == BOTTOM_MARGIN || prevOpCode == TOP_MARGIN - || prevOpCode == RIGHT_MARGIN) && thisOpCode == PLS) - break; - if ((prevOpCode == TOP_MARGIN || prevOpCode == RIGHT_MARGIN) - && thisOpCode == BOTTOM_MARGIN) - break; - if (prevOpCode == RIGHT_MARGIN && thisOpCode == TOP_MARGIN) - break; - } - this.SheetRecs.add(++i, r); - } - - /** - * inserts a row and shifts all of the other rows down one - *

                  - * the rownum is zero based. calling insertrow(9,true) will - * create a row containing A10, and subsequently shift rows > 9 by 1. - * - * @return the row that was just inserted - */ - private Row insertRow(int rownum, boolean shiftrows) { - return insertRow(rownum, 0, WorkSheetHandle.ROW_INSERT_MULTI, shiftrows); - } - - /** - * given sheet.xml input stream, parse OOXML into the current sheet - * - * @param bk - * @param sheet - * @param ii - * @param sst The sst. - * @param formulas Arraylist stores all formulas/info - must be added after all sheets and cells - * @param hyperlinks - * @param inlineStrs Hashmap stores inline strings and cell addresses; must be added after all sheets and cells - * @throws IOException - * @throws XmlPullParserException - * @throws CellNotFoundException - */ - HashMap shExternalLinkInfo = null; - - void parseOOXML(WorkBookHandle bk, WorkSheetHandle sheet, InputStream ii, ArrayList sst, ArrayList formulas, ArrayList hyperlinks, HashMap inlineStrs) throws XmlPullParserException, IOException { - int sfindex = formulas.size(); - - // try { - Row r = null; - String cellAddr = null; - int formatId = 0; - String type = ""; - shExternalLinkInfo = new HashMap(); - - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sheetFormatPr")) { // baseColWidth, customHeight - // (true if defaultRowHeight - // has been manually set), - // defaultColWidth, - // defaultRowHeight - - // optimiztion so that we - // don't have to write out - // values on each - // thickBottom - true if - // rows have a thick bottom - // by default - // thickTop - true if rows - // have a thick top by - // default - // zeroHeight - true if rows - // are hidden by default (an - // optimization) - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (n.equals("thickBottom")) - this.setThickBottom(true); - else if (n.equals("thickTop")) - this.setThickTop(true); - else if (n.equals("zeroHeight")) - this.setZeroHeight(v.equals("1")); - else if (n.equals("customHeight")) - this.setHasCustomHeight(v.equals("1")); - else if (n.equals("defaultColWidth")) - this.setDefaultColumnWidth(new Float(v) - .floatValue()); - else if (n.equals("defaultRowHeight")) - this.setDefaultRowHeight(new Double(v) - .doubleValue()); - } - } else if (tnm.equals("sheetView")) { // TODO: finish handling - // options - SheetView s = (SheetView) SheetView.parseOOXML(xpp) - .cloneElement(); - this.setSheetView(s); - this.getWindow2() - .setShowGridlines(!(s.getAttrS("showGridlines")) - .equals("0")); - if (s.getAttr("showRowColHeaders") != null) - this.getWindow2() - .setShowSheetHeaders((s - .getAttrS("showRowColHeaders")) - .equals("1")); - if (s.getAttr("showZeros") != null) - this.getWindow2() - .setShowZeroValues((s.getAttrS("showZeros")) - .equals("1")); - if (s.getAttr("showOutlineSymbols") != null) - this.getWindow2() - .setShowOutlineSymbols((s - .getAttrS("showOutlineSymbols")) - .equals("1")); - if (s.getAttr("tabSelected") != null) - this.setSelected((s.getAttrS("tabSelected")) - .equals("1")); - if (s.getAttr("zoomScale") != null) - this.getScl() - .setZoom(new Double(s.getAttrS("zoomScale")) - .floatValue() / 100); - } else if (tnm.equals("sheetPr")) { // sheet properties element - SheetPr sp = (SheetPr) SheetPr.parseOOXML(xpp) - .cloneElement(); - this.setSheetPr(sp); - } else if (tnm.equals("dimension")) { // ref attribute - /* - * this may not reflect actual rows/cols in sheet - * just let our normal machinery set the sheet dimensions - * String ref= xpp.getAttributeValue(0); - * int[] rc= ExcelTools.getRangeCoords(ref); - * this.updateDimensions(rc[2]-1, rc[3]); - */ - } else if (tnm.equals("sheetProtection")) { // ref - // attribute - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equals("password")) - this.getProtectionManager().setPasswordHashed(v); - else if (nm.equals("sheet")) - this.getProtectionManager() - .setProtected(OOXMLReader.parseBoolean(v)); - } - } else if (tnm.equals("col")) { // min, max, width - int min = 0, max = 0, style = 0; - double width = 0; - boolean hidden = false; - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equals("min")) - min = Integer.valueOf(v).intValue(); - else if (nm.equals("max")) - max = Integer.valueOf(v).intValue(); - else if (nm.equals("width")) - width = new Double(v).doubleValue(); - else if (nm.equals("hidden")) - hidden = true; - else if (nm.equals("style")) // customFormat? - style = Integer.valueOf(v).intValue(); - } - if (max > WorkBook.MAXCOLS) - max = WorkBook.MAXCOLS - 1; - ColHandle col = sheet.addCol(min - 1, max - 1); - col.setWidth((int) (width * OOXMLReader.colWFactor)); - if (style > 0) - col.setFormatId(style); - // col.setColLast(max-1); - if (hidden) - col.setHidden(true); - } else if (tnm.equals("row")) { - int ht = -1, ixfe = 0; - boolean customHeight = false; - for (int i = 0; i < xpp.getAttributeCount(); i++) { // r, v= - // row - // #+1, - // ht, - // ... - String nm = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (nm.equals("r")) { - int rownum = Integer.valueOf(v).intValue() - 1; - r = this.insertRow(rownum, false); // now insertRow - // with no shift - // rows does NOT - // add a blank - // cell so no - // need to - // delete extra - // cell anymore - r.setIxfe(this.getWorkBook().getDefaultIxfe()); - } else if (nm.equals("ht")) { - ht = (int) (new Double(v).doubleValue() - * OOXMLReader.rowHtFactor); - } else if (nm.equals("s")) { // customFormat? - ixfe = Integer.valueOf(v).intValue(); - } else if (nm.equals("customFormat")) { - r.setIxfe(ixfe); - } else if (nm.equals("hidden")) { - r.setHidden(true); - } else if (nm.equals("collapsed")) { // 20090513 KSC: - // Added - // collapsed, - // outlineLevel - // [BUGTRACKER - // 2371] - boolean h = r.isHidden(); // setCollapsed - // unconditionally sets - // hidden - r.setCollapsed(true); - if (!h) - r.setHidden(false); - } else if (nm.equals("outlineLevel")) { - r.setOutlineLevel(Integer.valueOf(v).intValue()); - } else if (nm.equals("customHeight")) { - customHeight = true; - } else if (nm.equals("thickBot")) { - r.setHasAnyThickBottomBorder(true); - } else if (nm.equals("thickTop")) { - r.setHasAnyThickTopBorder(true); - } - if (ht != -1 && customHeight) // if customHeight is NOT - // set do not set row - // height (encountered - // in Baxter XLSM - // templates) - r.setRowHeight(ht); - } - // if customheight is NOT specified do not set row height - } else if (tnm.equals("c")) {// element c child v= value - if (cellAddr != null) { - if (r.getExplicitFormatSet() - || ((formatId != this.getWorkBook() - .getDefaultIxfe() && formatId != 0))) { // default - // or - // not - // specified - // NOTE: - // default - // for - // OOXML - // is - // 0 - // not - // 15 - int[] rc = ExcelTools.getRowColFromString(cellAddr); - OOXMLReader - .sheetAdd(sheet, null, rc[0], rc[1], formatId); - } - cellAddr = null; - } - formatId = 0; - type = "n"; // reset for those cells that don't specify a - // type, default = number - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String nm = xpp.getAttributeName(i); // r, s=style, t= - // type - String v = xpp.getAttributeValue(i); - if (nm.equals("r")) { // cell address - cellAddr = v; // save for setting later - } else if (nm.equals("s")) { - formatId = Integer.valueOf(v).intValue(); // save - // for - // setting - // later - } else if (nm.equals("t")) { - type = v; - } - } - // would be great if could peek at next tag to determine - // whether to add a blank cell here rather than catch it at - // end tag below - } else if (tnm.equals("is")) { // inline string child of - // control->controlPr - // Fallback - // control - // i.e. 1st choice is a control with control settings - // if not possible, fallback is - } else if (tnm.equals("Fallback")) { - OOXMLReader.getCurrentElement(xpp); // skip as can replicate - // Choice - } else if (tnm.equals("controlPr")) { - OOXMLReader.getCurrentElement(xpp); // skip for now!! - } else if (tnm.equals("extLst")) { // skip for now!! - OOXMLReader.getCurrentElement(xpp); // skip for now!! - } /* - * else { - * if (true) - * Logger.logWarn("unprocessed XLSX sheet element: " + tnm); - * } - */ - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("row") && cellAddr != null) { - - int[] rc = ExcelTools.getRowColFromString(cellAddr); - // if masking an explicit row format or if it's a unique - // format, set to new blank cell - if (r.getExplicitFormatSet() - || ((formatId != this.getWorkBook().getDefaultIxfe() - && formatId != 0))) { // default or not - // specified NOTE: - // default for OOXML - // is 0 not 15 - // (unless converted - // from XLS ((: - // if (r.myRow.getExplicitFormatSet() || (/*formatId!=15 - // && */formatId!=0 && uniqueFormat)) { //default or not - // specified NOTE: default for OOXML==0 NOT 15 - OOXMLReader - .sheetAdd(sheet, null, rc[0], rc[1], formatId); - // } else{ - // sheetAdd(sheet,null,rc[0],rc[1],formatId); - } - cellAddr = null; - } else /**/if (endTag.equals("worksheet")) // we're done! - break; - } - eventType = xpp.next(); - } - } - - /** - * associates external reference info with the r:id of the external reference - * for instance, oleObject elements are associated with a shape Id that links back to a .vml file entry - * - * @param externalobjs - * @param xpp - */ - protected static void addExternalInfo(Map externalobjs, XmlPullParser xpp) { - // String[] attrs= new String[xpp.getAttributeCount()-1]; - ArrayList attrs = new ArrayList(); - String rId = ""; - // int j= 0; - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - if (n.equals("id")) - rId = xpp.getAttributeValue(i); - else - // attrs[j++]= n+ "=\"" + xpp.getAttributeValue(i) +"\""; - attrs.add(n + "=\"" + xpp.getAttributeValue(i) + "\""); - } - String s = Arrays.asList(attrs.toArray()).toString(); // 1.6 only - // Arrays.toString(attrs.toArray()); - if (s.length() > 2) { - s = s.substring(1, s.length() - 1); - // 1.6 only s= s.replace(",", ""); // only issue is embedded - // ,'s in quoted strings, lets assume not! - s = StringTool.replaceText(s, ",", ""); // only issue is embedded - // ,'s in quoted strings, - // lets assume not! - } - externalobjs.put(rId, s); - } - - /** - * parses OOXML content files given a content list cl from zip file zip - * recurses if content file has it's own content - * ************************************* - * NOTE: certain elements we do not as of yet process; we "pass-through" or store such elements along with any embedded objects associated with them - * for example, activeX objects, vbaProject.bin, etc. - * ************************************* - * - * @param bk WorkBookHandle - * @param sheet WorkSheetHandle (set if recursing) - * @param zip currently open ZipOutputStream - * @param cl ArrayList of Contents (type, filename, rId) to parse - * @param parentDir Parent Directory for relative paths in content lists - * @param formulas, hyperlinks, inlineStrs -- ArrayLists/Hashmaps stores sheet-specific info for later entry - * @throws CellNotFoundException - * @throws XmlPullParserException - */ - protected void parseSheetElements(WorkBookHandle bk, ZipFile zip, ArrayList cl, String parentDir, String externalDir, ArrayList formulas, ArrayList hyperlinks, HashMap inlineStrs, HashMap pivotTables) throws XmlPullParserException, CellNotFoundException { - String p; - ZipEntry target; - - try { - for (int i = 0; i < cl.size(); i++) { - String[] c = (String[]) cl.get(i); - String ooxmlElement = c[0]; - - // if(DEBUG) - // Logger.logInfo("OOXMLReader.parse: " + ooxmlElement + ":" - // + c[1] + ":" + c[2]); - - p = StringTool.getPath(c[1]); - p = OOXMLReader.parsePathForZip(p, parentDir); - if (!ooxmlElement.equals("hyperlink")) // if it's a hyperlink - // reference, don't - // strip path info :) - c[1] = StringTool.stripPath(c[1]); - String f = c[1]; - String rId = c[2]; - - if (ooxmlElement.equals("drawing")) { // images, charts - // parse drawing rels to obtain image file names and chart - // xml files - target = OOXMLReader.getEntry(zip, p + "_rels/" - + f.substring(f.lastIndexOf("/") + 1) + ".rels"); - ArrayList drawingFiles = null; - if (target != null) // first retrieve enbedded content in - // .rels (images, charts ...) - drawingFiles = OOXMLReader.parseRels(OOXMLReader - .wrapInputStream(OOXMLReader.wrapInputStream(zip - .getInputStream(target)))); // obtain a - // list of - // image - // file - // references - // for use - // in later - // parsing - target = OOXMLReader.getEntry(zip, p + f); // now get - // drawingml - // file and - // process it - parseDrawingXML(bk, drawingFiles, OOXMLReader - .wrapInputStream(zip - .getInputStream(target)), zip, p, externalDir); - } else if (ooxmlElement.equals("vmldrawing")) { // legacy - // drawing - // elements - target = OOXMLReader.getEntry(zip, p + f); - StringBuffer vml = parseLegacyDrawingXML(bk, OOXMLReader - .wrapInputStream(zip.getInputStream(target))); - target = OOXMLReader.getEntry(zip, p + "_rels/" // get - // external - // objects - // linked to - // the vml - // by - // parsing - // it's rels - + f.substring(f.lastIndexOf("/") + 1) + ".rels"); - if (target != null) { - String[] embeds = OOXMLReader - .storeEmbeds(zip, target, p, externalDir); // passes - // thru - // embedded - // objects - this.addOOXMLShape(new Object[]{vml, embeds}); - } else - this.addOOXMLShape(vml); - /**/ - } else if (ooxmlElement.equals("hyperlink")) { // hyperlinks - c = (String[]) cl.get(i); // don't strip path - for (int j = 0; j < hyperlinks.size(); j++) { - if (rId.equals(((String[]) hyperlinks.get(j))[0])) { - String[] h = (String[]) hyperlinks.get(j); - try { // target= cl[2], ref= h[1], desc= h[2] - bk.getWorkSheet(this.getSheetName()) - .getCell(h[1]).setURL(rId, h[2], ""); // TODO: - // hyperlink - // text - // mark - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parse: failed setting hyperlink to cell " - + h[1] + ":" + e.toString()); - } - break; - } - } - } else if (OOXMLReader.parsePivotTables - && ooxmlElement.equals("pivotTable")) { // sheet-parent - /* - * TODO: Do we really need to get rels ???? - * // must lookup cacheid from rid of - * pivotCacheDefinitionX.xml in - * pivotTableDefinitionX.xml.rels - * target= OOXMLReader.getEntry(zip,p + "_rels/" + - * f.substring(f.lastIndexOf("/")+1)+".rels"); - * ArrayList ptrels= - * parseRels(wrapInputStream(wrapInputStream(zip. - * getInputStream(target)))); - * if (ptrels.size() > 1) { // what could this be? - * Logger. - * logWarn("OOXMLReader.parse: Unknown Pivot Table Association: " - * + ptrels.get(1)); - * } - * String pcd= ((String[])ptrels.get(0))[1]; - * pcd= pcd.substring(pcd.lastIndexOf("/")+1); - * Object cacheid= null; - * for (int z= 0; z < pivotCaches.size(); z++) { - * Object[] o= (Object[]) pivotCaches.get(z); - * if (pcd.equals(o[0])) { - * cacheid= o[1]; - * break; - * } - * } - * - * target = getEntry(zip,p + f); - * PivotTableDefinition.parseOOXML(bk, /*cacheid, * /this, - * wrapInputStream(zip.getInputStream(target))); - */ - try { // SAVE FOR LATER INPUT -- must do after all sheets - // are input ... - pivotTables.put(p + f, bk - .getWorkSheet(this.getSheetName())); - } catch (WorkSheetNotFoundException we) { - } - - } else if (ooxmlElement.equals("comments")) { // parse comments - // or notes - target = OOXMLReader.getEntry(zip, p + f); - parseCommentsXML(bk, OOXMLReader - .wrapInputStream(zip.getInputStream(target))); - - // Below are elements we do not as of yet handle - } else if (ooxmlElement.equals("macro") - || ooxmlElement.equals("activeX") - || ooxmlElement.equals("table") - || ooxmlElement.equals("vdependencies") - || ooxmlElement.equals("oleObject") - || ooxmlElement.equals("image") - || ooxmlElement.equals("printerSettings")) { - - String attrs = ""; - if (shExternalLinkInfo != null - && shExternalLinkInfo.get(rId) != null) - attrs = shExternalLinkInfo.get(rId); - OOXMLReader - .handleSheetPassThroughs(zip, bk, this, p, externalDir, c, attrs); - // OOXMLReader.handlePassThroughs(zip, bk, this, p, c); // - // pass-through this file and any embedded objects as well - } else { // unknown type - Logger.logWarn("OOXMLAdapter.parse: XLSX Option Not yet Implemented " - + ooxmlElement); - } - } - } catch (IOException e) { - Logger.logErr("OOXMLAdapter.parse failed: " + e.toString()); - } - shExternalLinkInfo = null; - } - - /** - * NOTE: commentsX.xml also needs legacy drawing info (vmlDrawingX.vml) - * to define the text box itself including position and size, plus the vml elements - * also define whether the note is hidden - */ - void parseCommentsXML(WorkBookHandle bk, InputStream ii) { - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - - java.util.Stack lastTag = new java.util.Stack(); // keep track of - // element - // hierarchy - - ArrayList authors = new ArrayList(); - String addr = ""; - int authId = -1; - Unicodestring comment = null; - // ignore for now: phonetic properties (phoneticPr), - // phonetic run (rPh) - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("author")) { - authors.add(OOXMLReader.getNextText(xpp)); - } else if (tnm.equals("comment")) { - if (comment != null && !"".equals(addr)) { - this.createNote(addr, comment, (String) authors - .get(authId)); - } - addr = xpp.getAttributeValue("", "ref"); - authId = Integer - .valueOf(xpp.getAttributeValue("", "authorId")) - .intValue(); - comment = null; - } else if (tnm.equals("text")) { - // read in text element - lastTag.push(tnm); - Text t = Text.parseOOXML(xpp, lastTag, bk); - // don't reset state vars as can there can be more - comment = t.getCommentWithFormatting(); - } - } else if (eventType == XmlPullParser.END_TAG) { - } - eventType = xpp.next(); - } - if (!"".equals(comment.toString()) && !"".equals(addr)) { - this.createNote(addr, comment, (String) authors.get(authId)); - } - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parseCommentsXML: " + e.toString()); - } - return; - } - - /** - * parse vml - legacy drawing info e.g. mso shapes and lines + note textboxes - *
                  for now, legacy drawing info is just stored and not parsed into BIFF8 structures - *
                  i.e. store everything but note textboxes at this time; intention is later on - * to store all mso shapes and objects in BIFF8 records - *
                  this vml is stored at the sheet level in the boundsheet's OOXMLShapes storage - *
                  Notes textboxes are being created upon writeLegacyDrawingXML - * - * @param bk - * @param sheet - * @param ii - * @return StringBuffer rep of saved vml - */ - StringBuffer parseLegacyDrawingXML(WorkBookHandle bk, InputStream ii) { - /** - * more info: - * The Shape element is the basic building block of VML. A shape may exist on its own or within a Group - element. Shape defines many attributes and sub-elements that control the look and behavior of the shape. A - shape must define at least a Path and size (Width, Height). VML 1 also uses properties of the CSS2 style - attribute to specify positioning and sizing - - The ShapeType element defines a definition, or template, for a shape. Such a template is “instantiated” by - creating a Shape element that references the ShapeType. The shape can override any value specified by its - ShapeType, or define attributes and elements the ShapeType does not provide. A ShapeType may not - reference another ShapeType. - The attributes and elements a ShapeType uses are identical to those of the Shape element, with these - exceptions: ShapeType may not use the Type element, Visibility is always hidden. - - Regarding Notes: - The visible box shown for comments attached to cells is persisted using VML. The comment contents are - stored separately as part of SpreadsheetML. - */ - StringBuffer savedVml = new StringBuffer(); - try { - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - // NOTE: since vml controls visibility (hidden or shown), - // text box size, etc., notes are created upon VML parsing - // and edited here for the actual text and formats ... ms's - // legacy drawing stuff makes for alot of convoluted - // processing ((; - FastAddVector nhs = new FastAddVector(); - { - CommentHandle[] anhs = bk.getWorkSheet(this.getSheetName()) - .getCommentHandles(); - for (int i = 0; i < anhs.length; i++) { - nhs.add(anhs[i]); - } - } - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("shapelayout")) { - // just store - savedVml.append(OOXMLReader.getCurrentElement(xpp)); - } else if (tnm.equals("shapetype")) { - // if spt==202 (shape-type=text box) id="_x0000_t202" - // then it's note textbox - if (!xpp.getAttributeValue("urn:schemas-microsoft-com:office:office", "spt") - .equals("202")) {// if it's not a note textbox - // shapetype, store it - savedVml.append(OOXMLReader.getCurrentElement(xpp)); - } else // ignore element - will be rebuilt upon write - OOXMLReader.getCurrentElement(xpp); - } else if (tnm.equals("shape")) { // this is basic - // several types: can contain images, shapes and notes - if (!xpp.getAttributeValue("", "type") - .endsWith("_x0000_t202")) {// if it's not a note - // textbox, save it - // if type="#_x0000_t202" it's a note textbox - savedVml.append(OOXMLReader.getCurrentElement(xpp)); - } else {// add note here, text and formatting will be - // input upon Comments parse; - int r = -1, c = -1; - boolean visible = false; - short[] bounds = new short[8]; - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - tnm = xpp.getName(); // Anchor - if (tnm.equals("Row")) - r = Integer - .valueOf(OOXMLReader - .getNextText(xpp)) - .intValue(); - else if (tnm.equals("Column")) - c = Integer - .valueOf(OOXMLReader - .getNextText(xpp)) - .intValue(); - else if (tnm.equals("Visible")) - visible = true; - else if (tnm.equals("Anchor")) { - // get a string rep of the bounds - String sbounds = OOXMLReader - .getNextText(xpp); - // prepare for parsing - sbounds = sbounds - .replaceAll("[^0-9,]+", ""); - String[] s = sbounds.split(","); - for (int i = 0; i < 8; i++) { - bounds[i] = Short.valueOf(s[i]) - .shortValue(); - } - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals("shape")) - break; - } - eventType = xpp.next(); - } - String addr = ExcelTools - .formatLocation(new int[]{r, c}); - for (int i = 0; i < nhs.size(); i++) { - CommentHandle nh = (CommentHandle) nhs.get(i); - if (nh.getAddress().endsWith(addr)) { - if (visible) - nh.show(); - nh.setTextBoxBounds(bounds); - nhs.remove(i); - break; - } - } - } - } else if (tnm.equals("xml")) { // ignore :) - } else if (tnm.equals("imagedata")) { - } else { // just store - // -- - savedVml.append(OOXMLReader.getCurrentElement(xpp)); - } - } else if (eventType == XmlPullParser.END_TAG) { - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parseLegacyDrawingXML: " - + e.toString()); - } - return savedVml; - } - - /** - * given drawingML drawing.xml inputstream, parse each twoCellAnchor tag into appropriate image or chart and insert into sheet - * - * @param bk - * @param sheet - * @param imgFiles list of image or chart files (referenced in drawing.xml via rId) - * @param ii InputStream - * @param zip Current Open ZipOutputStream - */ - void parseDrawingXML(WorkBookHandle bk, ArrayList drawingFiles, InputStream ii, ZipFile zip, String parentDir, String externalDir) { - try { - java.util.Stack lastTag = new java.util.Stack(); // keep track of - // element - // hierarchy - - XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); - factory.setNamespaceAware(true); - XmlPullParser xpp = factory.newPullParser(); - - xpp.setInput(ii, null); // using XML 1.0 specification - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("twoCellAnchor")) { // beginning of DrawingML - // for a single image or - // chart - lastTag.push(tnm); - // TODO: handle group shapes which combine images, - // shapes and/or charts - // ******************************************************** - TwoCellAnchor t = (TwoCellAnchor) TwoCellAnchor - .parseOOXML(xpp, lastTag, bk).cloneElement(); - if (t.hasImage()) { - String s = t.getEmbed(); // rid of embedded object - if (s.indexOf("rId") == 0) { // should! - String imgFile = OOXMLReader - .parsePathForZip(OOXMLReader - .getFilename(drawingFiles, s), parentDir); - ZipEntry img = new ZipEntry(imgFile); - BufferedInputStream is = new BufferedInputStream( - zip.getInputStream(img)); - ImageHandle im = new ImageHandle(is, this); - this.insertImage(im); - im.setName(t.getName()); - im.setShapeName(t.getDescr()); - im.setBounds(TwoCellAnchor - .convertBoundsToBIFF8(this, t - .getBounds())); // must do after - // insert - im.setSpPr(t.getSppr()); // set image shape - // properties - im.setEditMovement(t.getEditAs()); // specify - // how to - // resize or - // move - im.update(); // update underlying image record - // with set data - } - } else if (t.hasChart()) { - String s = t.getChartRId(); - if (s.indexOf("rId") == 0) { // should! - String chartfilename = OOXMLReader - .getFilename(drawingFiles, s); - String name = t.getName(); - if (name == null || name.equals("null")) { - name = "Untitled Chart"; - } - ChartHandle ch = bk.createChart(name, bk - .getWorkSheet(this.getSheetName())); - ch.setRelativeBounds(TwoCellAnchor - .convertBoundsToBIFF8(this, t - .getBounds())); // must do after - // insert - ch.setEditMovement(t.getEditAs()); // specify - // how to - // resize or - // move - ch.setOOXMLName(name); - chartfilename = OOXMLReader - .parsePathForZip(chartfilename, parentDir); - ZipEntry chFile = new ZipEntry(chartfilename); - // must account for default chart settings: set - // fontx recs to default font for this workbook - // ... - ch.resetFonts(); // reset all fonts for the - // chart - ch.removeLegend(); // not all charts have - // legends! - int ps = chartfilename.lastIndexOf("/") + 1; - ZipEntry rels = OOXMLReader - .getEntry(zip, chartfilename - .substring(0, ps) + "_rels/" - + chartfilename.substring(ps) - + ".rels"); - if (rels != null) { // chart file has embeds - - // usually drawing ml which - // defines userShapes - // xxx TODO: REFACTOR to get these specifics - // out - ArrayList chartEmbeds = OOXMLReader - .parseRels(OOXMLReader - .wrapInputStream(zip - .getInputStream(rels))); - for (int i = 0; i < chartEmbeds - .size(); i++) { - String[] dr = (String[]) chartEmbeds - .get(i); - if (dr[0].equals("userShape")) { // should! - dr[1] = dr[1].substring(dr[1] - .lastIndexOf("/") + 1); - ch.addChartEmbed(new String[]{ - dr[0], - externalDir + dr[1]}); - OOXMLReader - .passThrough(zip, parentDir - + dr[1], externalDir - + dr[1]); // Store - // Embedded - // Object - // on - // disk - // for - // later - // retrieval - } else if (dr[0].equals("image")) { - String parentp = OOXMLReader - .parsePathForZip(dr[1], parentDir); - parentp = parentp - .substring(0, parentp - .lastIndexOf("/") - + 1); - dr[1] = dr[1].substring(dr[1] - .lastIndexOf("/") + 1); - ch.addChartEmbed(new String[]{ - dr[0], - externalDir + dr[1]}); - OOXMLReader.passThrough(zip, parentp - + dr[1], externalDir - + dr[1]); // save - // the - // original - // target - // file - // for - // later - // re-packaging - } else if (dr[0] - .equals("themeOverride")) { - String parentp = OOXMLReader - .parsePathForZip(dr[1], parentDir); - parentp = parentp - .substring(0, parentp - .lastIndexOf("/") - + 1); - dr[1] = dr[1].substring(dr[1] - .lastIndexOf("/") + 1); - ch.addChartEmbed(new String[]{ - dr[0], - externalDir + dr[1]}); - ZipEntry target = OOXMLAdapter - .getEntry(zip, parentp - + dr[1]); - bk.getWorkBook().getTheme() - .parseOOXML(bk, OOXMLAdapter - .wrapInputStream(zip - .getInputStream(target))); - } else { - Logger.logWarn("OOXMLAdapter.parseDrawingML: unknown chart embed " - + dr[0]); - } - } - } - // do after parsing rels in case there is - // override theme colors ... - ch.parseOOXML(OOXMLReader.wrapInputStream(zip - .getInputStream(chFile))); - } - } else if (t.hasShape()) { - this.addOOXMLShape(t); // just store shape for later - // output since prev. - // versions do not handle - // shapes - if (t.getEmbed() != null) { // if this shape has - // embedded objects such - // as images - String imgFile = OOXMLReader - .parsePathForZip(OOXMLReader - .getFilename(drawingFiles, t - .getEmbed()), parentDir); // look - // up - // embedded - // rid - // in - // content - // list - // to - // get - // filename - t.setEmbedFilename(imgFile); // save embedded - // filename for - // later - // retrieval - OOXMLReader - .passThrough(zip, imgFile, externalDir - + imgFile); // Store Embedded - // Object on disk - // for later - // retrieval - } - } else { // TESTING! - Logger.logErr("OOXMLAdapter.parseDrawingXML: Unknown twoCellAnchor type"); - } - } else if (tnm.equals("oneCellAnchor")) { // unclear if this - // can be root - // of charts and - // images as - // well as - // shapes - lastTag.push(tnm); - OneCellAnchor oca = (OneCellAnchor) OneCellAnchor - .parseOOXML(xpp, lastTag, bk).cloneElement(); - if (oca.hasImage()) { - String s = oca.getEmbed(); // rid of embedded object - if (s.indexOf("rId") == 0) { // should! - String imgFile = OOXMLReader - .parsePathForZip(OOXMLReader - .getFilename(drawingFiles, s), parentDir); - ZipEntry img = new ZipEntry(imgFile); - BufferedInputStream is = new BufferedInputStream( - OOXMLReader.wrapInputStream(zip - .getInputStream(img))); - ImageHandle im = new ImageHandle(is, this); - this.insertImage(im); - im.setName(oca.getName()); - im.setShapeName(oca.getDescr()); - im.setBounds(oca.getBounds()); // must do after - // insert - im.setSpPr(oca.getSppr()); // set image shape - // properties - im.update(); // update underlying image record - // with set data - } - } else if (oca.hasChart()) { - String s = oca.getEmbed(); - if (s.indexOf("rId") == 0) { // should! - String chart = OOXMLReader - .getFilename(drawingFiles, s); - String name = oca.getName(); - if (name == null || name.equals("null")) { - name = "Untitled Chart"; - } - ChartHandle ch = bk.createChart(name, bk - .getWorkSheet(this.getSheetName())); - ch.setRelativeBounds(oca.getBounds()); - // ch.setChartTitle(name); - chart = OOXMLReader - .parsePathForZip(chart, parentDir); - ZipEntry chFile = new ZipEntry(chart); - ch.parseOOXML(OOXMLReader.wrapInputStream(zip - .getInputStream(chFile))); - } - } else if (oca.hasShape()) { - this.addOOXMLShape(oca); // just store shape for - // later output since - // prev. versions do not - // handle shapes - } else { // TESTING! - Logger.logErr("OOXMLAdapter.parseDrawingXML: Unknown oneCellAnchor type"); - } - } else if (tnm.equals("userShapes")) { // drawings ONTOP of - // charts = - // Reference to - // Chart Drawing - // Part - Logger.logErr("OOXMLAdapter.parseDrawingXML: USER SHAPE ENCOUNTERED"); - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OOXMLAdapter.parseDrawingXML: failed " - + e.toString()); - } - } - - /** - * clear out object references in prep for closing workbook - */ - @Override - public void close() { - wkbook = null; - for (Colinfo info : colinfos.values()) { - if (null != info) - info.close(); - } - colinfos.clear(); - Iterator ii = rows.keySet().iterator(); - while (ii.hasNext()) { - Row r = rows.get(ii.next()); - r.close(); - } - rows.clear(); - - cellsByRow = new TreeMap( - new CellAddressible.RowMajorComparator()); - - cellsByCol = new TreeMap( - new CellAddressible.ColumnMajorComparator()); - // TODO: clear recs - arrayformulas.clear(); - // TODO: clear recs - transferXfs.clear(); - // TODO: clear recs - transferFonts.clear(); - imageMap.clear(); - charts.clear(); - ooxmlObjects.clear(); - if (ooxmlShapes != null) - ooxmlShapes.clear(); - - ooautofilter = null; - mc.clear(); - sheetview = null; // OOXML sheet view object - sheetPr = null; // OOXML sheetPr object - if (lastselection != null) { - lastselection.close(); - lastselection = null; - } - if (protector != null) { - protector.close(); - protector = null; - } - - if (sheetNameRecs != null) { - ii = sheetNameRecs.keySet().iterator(); - while (ii.hasNext()) { - Name n = (Name) sheetNameRecs.get(ii.next()); - n.close(); - } - sheetNameRecs.clear(); - } - - for (int i = 0; i < cond_formats.size(); i++) { - Condfmt c = (Condfmt) cond_formats.get(i); - c.close(); - } - cond_formats.clear(); - - for (int i = 0; i < autoFilters.size(); i++) { - AutoFilter a = (AutoFilter) autoFilters.get(i); - a.close(); - } - autoFilters.clear(); - - if (lastCell != null) { - ((XLSRecord) lastCell).close(); - lastCell = null; - } - if (lastRow != null) { - lastRow.close(); - lastRow = null; - } - - if (win2 != null) { - win2.close(); - win2 = null; - } - if (scl != null) { - scl.close(); - scl = null; - } - if (pane != null) { - pane.close(); - pane = null; - } - if (dval != null) { - dval.close(); - dval = null; - } - if (hdr != null) { - hdr.close(); - hdr = null; - } - if (ftr != null) { - ftr.close(); - ftr = null; - } - if (wsbool != null) { - wsbool.close(); - wsbool = null; - } - if (guts != null) { - guts.close(); - guts = null; - } - if (dimensions != null) { - dimensions.close(); - dimensions = null; - } - if (mybof != null) { - mybof.close(); - mybof = null; - } - if (myeof != null) { - myeof.close(); - myeof = null; - } - if (myidx != null) { - myidx.close(); - myidx = null; - } - for (int i = 0; i < printRecs.size(); i++) { - XLSRecord r = (XLSRecord) printRecs.get(i); - r.close(); - } - printRecs.clear(); - - // clear out refs by sheet recs - for (int j = 0; j < SheetRecs.size(); j++) { - XLSRecord r = (XLSRecord) SheetRecs.get(j); - r.close(); - } - SheetRecs.clear(); - if (localrecs != null) - localrecs.clear(); - // col records - - } - - public DefColWidth getDefColWidth() { - return defColWidth; - } - - public void setDefColWidth(DefColWidth defColWidth) { - this.defColWidth = defColWidth; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Boundsheet.kt b/src/main/java/io/starter/formats/XLS/Boundsheet.kt new file mode 100644 index 0000000..4d702a3 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Boundsheet.kt @@ -0,0 +1,5911 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.* +import io.starter.formats.OOXML.* +import io.starter.formats.XLS.charts.Chart +import io.starter.formats.XLS.charts.Fontx +import io.starter.formats.XLS.charts.GenericChartObject +import io.starter.formats.XLS.formulas.FormulaParser +import io.starter.formats.XLS.formulas.Ptg +import io.starter.formats.XLS.formulas.PtgRef +import io.starter.formats.cellformat.CellFormatFactory +import io.starter.toolkit.* +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import org.xmlpull.v1.XmlPullParserFactory + +import java.io.* +import java.util.* +import java.util.zip.ZipEntry +import java.util.zip.ZipFile + +/** + * **Boundsheet: WorkSheet Information 0x85**

                  + * + * + * This record stores the sheet name, type and stream position. + * + *

                  + * offset  name        size    contents
                  + * ---
                  + * 4       lbPlyPos    4       Stream position of the BOF for the sheet
                  + * 8       grbit       2       Option flags
                  + * 10      cch         1       Length of sheet name
                  + * 11      grbitChr    1       Compressed/Uncompressed Unicode
                  + * 12      rgch        var     Sheet name
                  +
                  * + * + * + * ---- File Layout ---- + * + * + * BOUNDSHEET + * Bof + * Index + * Row1 is first index in DBCELL + * Row2 is the offset of any DBCELLS + * Row... + * CELLREC + * CELLREC + * CELLREC + * + * + * DBCELL rgdb rg rg rg rg + * ROWS + * CELLS + * DBCELL rg rg rg + * ... + * EOF + * + * + * ---------------------- + * + * + * lbplypos used to be the most important thing on earth. now it is not an issue. + * + * @see WorkBook + * + * @see Index + * + * @see Dbcell + * + * @see Row + * + * @see Cell + * + * @see XLSRecord + */ + class Boundsheet:XLSRecord(), Sheet { +override var myBof:Bof? = null +private set +override var myEof:Eof? = null +private set +private var sheetname = "" +private var sheetHash = "" +private val rows = LinkedHashMap() + +private var cellsByRow:SortedMap = TreeMap( +CellAddressible.RowMajorComparator()) + +private var cellsByCol:SortedMap = TreeMap( +CellAddressible.ColumnMajorComparator()) + +private val arrFormulaLocs = HashMap() // use + // for + // trapping + // array + // formula + // refs + // to + // original + // cell + // reference + // [OOXML + // Array + // Formulas] + protected var arrayformulas:AbstractList<*> = ArrayList() // trap + // array + // formulas + // that + // span + // one + // or + // more + // cells + private val colinfos = TreeMap( +ColumnRange.Comparator()) +private var SheetRecs:AbstractList<*> = ArrayList() +private var localrecs:AbstractList<*>? = null + +/** + * Records containting various bits of print setup. + */ + private var printRecs:MutableList<*>? = null + + // These records are for boundsheet transferral to a new + // book. + private var transferXfs:MutableList<*> = ArrayList() +private var transferFonts:MutableList<*>? = ArrayList() + var imageMap:HashMap<*, *>? = HashMap() +private val charts = ArrayList() // chart + // specific + // for + // this + // sheet + private var lbPlyPos:Long = 0 +/** + * get the type of sheet as a short + */ + override var sheetType:Short = 0 +private set +private var cch:Byte = 0 +/** + * @return Returns the grbitChr. + */ + /** + * @param grbitChr The grbitChr to set. + */ + override var grbitChr:Byte = 0 + // private int sheetnum; + private var myidx:Index? = null +/** + * get the last BiffRec added to this sheet + */ + override var lastCell:BiffRec? = null +private set +private var lastRow:Row? = null +override var window2:Window2? = null +private var scl:Scl? = null +private var pane:Pane? = null + var dvalRec:Dval? = null + + // KSC: + // track + // last-used + // Object + // id + // for + // this + // sheet + + override var header:Headerrec? = null +protected set +override var footer:Footerrec? = null +protected set +override var wsBool:WsBool? = null +override var guts:Guts? = null + + var lastObjId = 0 // 20100210 + +/** + * return whether to shift (formula cells, named ranges) "inclusive" or not + * + * @return + */ + var isShiftInclusive = false +private set +private var cond_formats:AbstractList<*>? = Vector() +private val autoFilters = Vector() // 20100111 + // KSC + + // OOXML use: stores external sheet-level OOXML objects + private var ooxmlObjects:AbstractList<*> = ArrayList() + + // OOXML-specific sheet attributes TODO: translate to Excel + // 2003 version IF POSSIBLE + private var thickBottom = false +private var thickTop = false +private var zeroHeight = false +private var customHeight = false +/** + * return the default row height in points (Excel 2007-Specific) + */ + /** + * set the default row height in points (Excel 2007-Specific) + */ + var defaultRowHeight = 12.75 // measured + // in + // point + // size + private var defaultColWidth = (-1.0).toFloat() + var defColWidth:DefColWidth? = null +/** + * return map of Excel 2007 shapes in this workbook + * + * @return + */ + var ooxmlShapes:HashMap<*, *>? = null +private set // stores + // OOXML + // shapes + /** + * returns the Excel 2007 sheetView element for this sheet (controls topLeftCell, pane attributes ... + * + * @return + */ + /** + * set the Excel 2007 sheetView element for this sheet (controls topLeftCell, pane attributes ... + * + * @param s + */ + var sheetView:SheetView? = null +/** + * returns the Excel 2007 sheetPr sheet Properties element for this sheet (controls codename, tabColor ...) + * + * @return + */ + /** + * set the Excel 2007 sheetView element for this sheet (controls topLeftCell, pane attributes ... + * + * @param s + */ + var sheetPr:SheetPr? = null +/** + * returns the Excel 2007 autoFilter element for this sheet (temporarily hides rows based upon filter criteria) + * TODO: Merge with 2003 AutoFilter + * + * @return + */ + /** + * set the Excel 2007 autoFilter element for this sheet (temporarily hides rows based upon filter criteria) + * TODO: Merge with 2003 AutoFilter + * + * @param strref + */ + var ooAutoFilter:io.starter.formats.OOXML.AutoFilter? = null +private var protector:SheetProtectionManager? = null + +@Transient private var sheetNameRecs:HashMap<*, *>? = HashMap() // sheet + // scoped + // names + internal var mc:MutableList<*> = CompatibleVector() +/** + * @return Returns the lastselection. + */ + /** + * @param lastselection The lastselection to set. + */ + var lastselection:Selection? = null + +/** + * Gets this sheet's SheetProtectionManager. + */ + val protectionManager:SheetProtectionManager +get() { +if (protector == null) +protector = SheetProtectionManager(this) +return protector +} + + /* + * TODO: find calls to this method which really need to be + * calling 'assembleSheetRecs() -jm 8/05 + */ + override val sheetRecs:List<*> +get() =SheetRecs + +/** + * returns the images list + */ + val imageVect:List<*> +get() { +val im = ArrayList() +val ir = imageMap!!.keys.iterator() +while (ir.hasNext()) +{ +im.add(ir.next()) +} +return im + +} + +/** + * Get a collection of all names in the worksheet + */ + val sheetScopedNames:Array +get() { +if (this.sheetNameRecs == null) +this.sheetNameRecs = HashMap() +val a = ArrayList(this.sheetNameRecs!!.values) +val n = arrayOfNulls(a.size) +a.toTypedArray() +return n +} + +/** + * for whatever reason, we return a Handle from an internal class + * + * @return + */ + /* + * 20071026 KSC: since there may be multiple copies of the + * same + * image in the sheet, must build imageHandle array by hand + */ val images:Array? +get() { +if (imageMap == null) +return null +val im = arrayOfNulls(imageMap!!.size) +val ir = imageMap!!.keys.iterator() +var i = 0 +while (ir.hasNext()) +{ +im[i++] = ir.next() as ImageHandle +} +return im +} + + val indexOfMsodrawingselection:Int +get() { +var rec:BiffRec? = null + +val size = SheetRecs.size +var foundIndex = -1 +for (i in 0 until size) +{ +rec = SheetRecs[i] as BiffRec +if (rec is MSODrawingSelection) +{ +foundIndex = i +break +} +} +return foundIndex +} + + val indexOfWindow2:Int +get() { +var rec:BiffRec? = null +val size = SheetRecs.size +var foundIndex = -1 +for (i in 0 until size) +{ +rec = SheetRecs[i] as BiffRec +if (rec is Window2) +{ +foundIndex = i +break +} +} +return foundIndex +} + + val indexOfDimensions:Int +get() { +var rec:BiffRec? = null +val size = SheetRecs.size +var foundIndex = -1 +for (i in 0 until size) +{ +rec = SheetRecs[i] as BiffRec +if (rec is Dimensions) +{ +foundIndex = i + 1 +break +} +} +return foundIndex +} + +/** + * Return an array of all the dvRecs within + * this boundsheet (Dval parent rec) + * + * @return + */ + val dvRecs:List<*>? +get() =if (this.dvalRec != null) this.dvalRec!!.dvs else null + +/** + * @return conditional formats for this sheet + */ + val conditionalFormats:List<*>? +get() =cond_formats + +override var workBook:WorkBook? +get() =wkbook +set + +/** + * Determine if the boundsheet is a chart only boundsheet + * + * @return + */ + override val isChartOnlySheet:Boolean +get() =if (myBof != null) myBof!!.isChartBof else false + +/** + * get the min/max dimensions + * for this sheet. + */ + private var dimensions:Dimensions? = null + +override val minRow:Int +get() =dimensions!!.getRowFirst() + +/** + * return true maximum/last row on the sheet + */ + override val maxRow:Int +get() =dimensions!!.getRowLast() + +override val minCol:Int +get() =dimensions!!.getColFirst() + +override val maxCol:Int +get() =dimensions!!.getColLast() + +/** + * set the numeric sheet number + */ + override val sheetNum:Int +get() =this.wkbook!!.sheetVect.indexOf(this) + +/** + * get whether this sheet is hidden upon opening (either regular or "very hidden" + */ + override val hidden:Boolean +get() =sheetType != VISIBLE.toShort() + + val veryHidden:Boolean +get() =sheetType == VERY_HIDDEN.toShort() + +internal var selected = false + +/** + * get the number of defined rows on this sheet + */ + override val numRows:Int +get() =rows.size + +/** + * get the number of defined cells on this sheet + */ + override val numCells:Int +get() { +var counter = 0 +val cellset = rows.keys +val rws = cellset.toTypedArray() +if (rws.size == 0) +return 0 +for (i in rws.indices) +{ +val r = rows[rws[i]] +counter += r.numberOfCells +} +return counter + +} + +/** + * get the FastAddVector of columns defined on this sheet + */ + override val colNames:List<*> +get() { +val retvec = FastAddVector() +for (x in 0 until this.realMaxCol) +{ +val c = ExcelTools.getAlphaVal(x) +retvec.add(c) +} +return retvec +} + +/** + * get the Number of columns defined on this sheet + */ + override val numCols:Int +get() =realMaxCol + +/** + * get the FastAddVector of rows defined on this sheet + */ + override val rowNums:List<*> +get() { +val e = rows.keys +val iter = e.iterator() +val rownames = FastAddVector() +while (iter.hasNext()) +{ +rownames.add(rownames.size, iter.next()) +} +return rownames +} + +/** + * return the map of row in this sheet sorted by row # + * (will be unsorted if insertions and deletions) + * + * @return + */ + val sortedRows:SortedMap<*, *> +get() =TreeMap(rows) + +/** + * return a Map of the Rows + */ + val rowMap:Map<*, *> +get() =rows + + var fastCellAdds = false // performance setting which skips + +private var copypriorformats = true + +/** + * Returns the *real* last col num. Unfortunately the dimensions record + * cannot be counted on to give a correct value. + */ + var realMaxCol = -1 +private set +private var maximumCellRow = -1 + +/** + * get an array of all cells for this worksheet + */ + override val cells:Array +get() { +val cells = cellsByRow.values +return cells.toTypedArray() +} + +/** + * Get the built in names referring to this boundsheet + * + * @return + */ + private val builtInNames:ArrayList<*> +get() { +val retlist = ArrayList() +val ns = this.workBook!!.names +for (i in ns.indices) +{ +if (ns[i].isBuiltIn && (ns[i].getIxals().toInt() == this.sheetNum + 1 || ns[i].getItab().toInt() == this.sheetNum + 1)) +{ +retlist.add(ns[i]) +} +} +return retlist +} + +/** + * Get the print area name rec for this + * boundsheet, return null if not exists + * + * @return + */ + protected val printAreaNameRec:Name? +get() =getPrintAreaNameRec(Name.PRINT_AREA) + +/** + * Get the print area set for this WorkSheetHandle. + * + * + * If no print area is set return null; + */ + /** + * Set the print area for this worksheet. + */ + /* + * if (p instanceof PtgArea3d) {// can be other than ptgarea + * ... + * ((PtgRef)p).clearLocationCache();// why?? + * return p.toString(); + * } + */ var printArea:String? +get() { +val n = this.printAreaNameRec +if (n != null) +{ +var ret = "" +val s = n.expression +for (x in s!!.indices) +{ +val p = s[x] as Ptg +ret += p.toString() +} +return ret +} +return null +} +set(range) =setPrintArea(range, Name.PRINT_AREA) + +/** + * Get the Print Titles range set for this WorkSheetHandle. + * + * + * If no Print Titles are set, this returns null; + */ + /** + * Set the print titles for this worksheet= row(s) or col(s) to repeat at the top of each page + */ + var printTitles:String? +get() { +val n = getPrintAreaNameRec(Name.PRINT_TITLES) +if (n != null) +{ +val s = n.expression +for (x in s!!.indices) +{ +val p = s[x] as Ptg +return p.toString() +} +} + +return null +} +set(range) =setPrintArea(range, Name.PRINT_TITLES) + +/** + * returns an arrayList of notes in the worksheet + * + * @return + */ + val notes:ArrayList<*> +get() { +val notes = ArrayList() +var idx = this.getIndexOf(XLSConstants.NOTE) +while (idx > -1) +{ +notes.add(SheetRecs[idx++]) +if ((SheetRecs[idx] as BiffRec).opcode != XLSConstants.NOTE) +break +} +return notes +} + +/*** + */ + override// 20081031 KSC- don't automatically add new! + val mergedCellsRec:Mergedcells? +get() =if (mc.size == 0) null else this.mergedCellsRecs[this.mergedCellsRecs.size - 1] as Mergedcells + +override/* + * 20081031 don't add a merged cell rec automatically + * if (mc.size()>0) { + * return mc; + * } + * Mergedcells mec = + * (Mergedcells)Mergedcells.getPrototype(); + * mec.setSheet(this); + * this.getStreamer().addRecordAt(mec, + * this.getSheetRecs().size()-1); + * this.addMergedCellsRec(mec); + */ val mergedCellsRecs:List<*> +get() =mc + +/** + * return existing merged cell records without adding new blank + * + * @return + */ + val mergedCells:List<*> +get() =mc + +/** + * get the name of the sheet + */ + /** + * change the displayed name of the sheet + * + * + * Affects the following byte values: + * 10 cch 1 Length of sheet name + * 11 grbitChr 1 Compressed/Uncompressed Unicode + * 12 rgch var Sheet name + */ + override// if (!ByteTools.isUnicode(namebytes)){ + var sheetName:String +get() =sheetname +set(newname) { + +cch = newname.length.toByte() +var namebytes = newname.toByteArray() +if (!ByteTools.isUnicode(newname)) +{ +grbitChr = 0x0 +} +else +{ +grbitChr = 0x1 +} +try +{ +if (grbitChr.toInt() == 0x1) +{ +namebytes = newname.toByteArray(charset(WorkBookFactory.UNICODEENCODING)) +} +else +{ +namebytes = newname.toByteArray(charset(WorkBookFactory.DEFAULTENCODING)) +} +} +catch (e:UnsupportedEncodingException) { +namebytes = newname.toByteArray() +Logger.logWarn("UnsupportedEncodingException in setting sheet name: " ++ e + " falling back to system default.") +} + +val newdata = ByteArray(namebytes.size + 8) +if (data == null) +this.data = newdata +else +System.arraycopy(this.getData()!!, 0, newdata, 0, 8) + +System.arraycopy(namebytes, 0, newdata, 8, namebytes.size) +newdata[6] = cch +newdata[7] = grbitChr +this.setData(newdata) +this.init() +} + +/** + * Returns a serialized copy of this Boundsheet + * + * @throws IOException + */ + override val sheetBytes:ByteArray? +@Throws(IOException::class) +get() { +this.setLocalRecs() +var obs:ObjectOutputStream? = null +var b:ByteArray? = null + +val baos = ByteArrayOutputStream() +obs = ObjectOutputStream(baos) +obs.writeObject(this) +b = baos.toByteArray() + +return b +} + +/** + * get the type of sheet as a string + */ + override val sheetTypeString:String? +get() { +when (sheetType) { +SHEET_DIALOG -> return "Sheet or Dialog" +XL4_MACRO -> return "XL4 Macro" +CHART -> return "Chart" +VBMODULE -> return "VB Module" +else -> return null +} +} + +/** + * @return Returns the localrecs. + */ + override val localRecs:List<*>? +get() =localrecs + +/** + * Gets the printer setup handle for this sheet. + */ + val printerSetupHandle:PrinterSettingsHandle +get() =PrinterSettingsHandle(this) + +/** + * return the default column width in # characters of the maximum digit width of the normal style's font + * + * + * This is currently a floating point value, something I question. I don't understand the need for this, + * and possibly it should be an int? + */ + /** + * set the default column width in # characters of the maximum digit width of the normal style's font + */ + // biff8 setting + // ooxml setting + // biff8 setting + var defaultColumnWidth:Float +get() =if (defColWidth != null) { + defColWidth!!.defaultWidth.toFloat() + } else defaultColWidth +set(w) { +defaultColWidth = w +if (defColWidth != null) +{ +defColWidth!!.setDefaultColWidth(w.toInt()) +} +} + +/** + * Get all the names for this boundsheet + * + * @return + */ + val allNames:Array +get() { +if (this.sheetNameRecs == null) +this.sheetNameRecs = HashMap() +val retnames = ArrayList(sheetNameRecs!!.values) +val names = arrayOfNulls(retnames.size) +return retnames.toTypedArray() as Array +} + +/** + * given sheet.xml input stream, parse OOXML into the current sheet + * + * @param bk + * @param sheet + * @param ii + * @param sst The sst. + * @param formulas Arraylist stores all formulas/info - must be added after all sheets and cells + * @param hyperlinks + * @param inlineStrs Hashmap stores inline strings and cell addresses; must be added after all sheets and cells + * @throws IOException + * @throws XmlPullParserException + * @throws CellNotFoundException + */ + internal var shExternalLinkInfo:HashMap? = null + +override fun setHeader(h:BiffRec) { +this.header = h as Headerrec +} + +override fun setFooter(ftr:BiffRec) { +this.footer = ftr as Footerrec +} + +/** + * Please add comments for this method + * + * @param bAddUnconditionally + */ + @JvmOverloads fun insertImage(im:ImageHandle, bAddUnconditionally:Boolean = false) { +var msodg = this.wkbook!!.msoDrawingGroup +val msoDrawing = MSODrawing.prototype as MSODrawing? +msoDrawing!!.setSheet(this) +msoDrawing.coords = im.coords + +im.setMsgdrawing(msoDrawing) // 20070924 KSC: link 2 actual msodrawing + // that describes this image for setting + // bounds, etc. + var insertIndex = -1 +val obj = Obj.prototype as Obj? + // now add to proper place in stream + if (msodg != null) +{ // already have drawing records; just add to + // records + update msodg + insertIndex = this.getIndexOf(XLSConstants.MSODRAWINGSELECTION) +if (insertIndex < 0) +insertIndex = this.getIndexOf(XLSConstants.WINDOW2) +if (msodg.getMsoHeaderRec(this) == null) + // handle case of multiple + // sheets- each needs + // it's own mso header + // ... + msoDrawing.setIsHeader() +} +else +{ // No images present in workbook, must add appropriate + // records + // Create new msodg rec + this.wkbook!!.msoDrawingGroup = MSODrawingGroup +.prototype as MSODrawingGroup? +msodg = this.wkbook!!.msoDrawingGroup +msodg!!.initNewMSODrawingGroup() // generate and add required records + // for drawing records + // also add 1st portion for drawing rec + msoDrawing.setIsHeader() + // insertion point for new msodrawing rec + insertIndex = getIndexOf(XLSConstants.DIMENSIONS) + 1 +} +if (insertIndex > 0) +{ // should! then have a drawing record to insert + // 20071120 KSC: retrieve idx in order to reuse/link to + // existing image bytes if duplicating images + val idx = msodg.addImage(im.imageBytes, im +.imageType, bAddUnconditionally) +imageMap!![im] = Integer.valueOf(im.imageIndex - 1) // add + // new + // image + // to + // map + // and + // link + // to + // actual + // imageIndex + // - + // moved + // from + // above + msoDrawing.createRecord(++this.wkbook!!.lastSPID, im +.imageName, im.shapeName, idx) // generate + // msoDrawing + // using correct + // values moved + // from above + this.SheetRecs.add(insertIndex++, msoDrawing) +this.SheetRecs.add(insertIndex++, obj) +msodg.addMsodrawingrec(msoDrawing) // add the new drawing rec to + // the msodrawinggroup set of + // recs + wkbook!!.updateMsodrawingHeaderRec(this) // find the msodrawing + // header record and update + // it (using info from other + // msodrawing recs) + // 20080908 KSC: moved from above + msodg.spidMax = this.wkbook!!.lastSPID + 1 // was ++lastSPID +msodg.updateRecord() // given all information, generate appropriate + // bytes + msodg.dirtyflag = true +} +else +{ +Logger.logErr("Boundsheet.insertImage: Drawing Group not created.") +} +} + +/** + * Rationalizes the itab (sheet reference) for name records, + * this has to occur after sheet insert/delete operations to keep the + * references intact. Unfortunately these references do not use the Externsheet, + * so are not ilbl listeners. + */ + internal fun updateLocalNameReferences() { +if (sheetNameRecs == null) +return +val i = this.sheetNameRecs!!.values.iterator() +while (i.hasNext()) +{ +val n = i.next() as Name +n.setItab((this.sheetNum + 1).toShort()) +} +} + +/** + * column formatting records + * + * + * Note that it checks if exists. This is due to externally copied boundsheets already having + * the record in the array when addrecord occurs. + */ + override fun addColinfo(c:Colinfo) { +if (!this.colinfos.containsValue(c)) +{ +this.colinfos[c] = c +} +} + +/** + * For workbooks that do not contain a dval record, + * insert a default dval rec + * + * @return + */ + fun insertDvalRec():Dval { +if (this.dvalRec != null) +return this.dvalRec +val d = Dval.prototype as Dval? +d!!.setSheet(this) +var insertIdx = window2!!.recordIndex + 1 + // correct position for DV block is before sheet protection + // records (if any) + // or before EOF + var opc = (SheetRecs[insertIdx] as BiffRec).opcode.toInt() +while (opc != XLSConstants.EOF.toInt()) +{ +if (opc == XLSConstants.SHEETPROTECTION.toInt() || opc == XLSConstants.RANGEPROTECTION.toInt() +|| opc == XLSConstants.SHEETLAYOUT.toInt()) +break +insertIdx++ +opc = (SheetRecs[insertIdx] as BiffRec).opcode.toInt() +} +this.SheetRecs.add(insertIdx, d) +this.dvalRec = d +return d +} + +/** + * Create a dv (validation record) + * record gets inserted into the byte stream from + * within Dval + * + * @param location + * @return + */ + fun createDv(location:String):Dv { +if (this.dvalRec == null) +this.insertDvalRec() +val dv = this.dvalRec!!.createDvRec(location) +var insertIdx = this.SheetRecs.size - 2 // start at 1 before EOF +var opc = (SheetRecs[insertIdx] as BiffRec).opcode.toInt() +while (opc != XLSConstants.DV.toInt() && opc != XLSConstants.DVAL.toInt()) +{ +insertIdx-- // insert after last DV +opc = (SheetRecs[insertIdx] as BiffRec).opcode.toInt() +} +this.SheetRecs.add(insertIdx + 1, dv) // insert after DVAL or last DV +return dv +} + +/** + * Create a Condfmt (Conditional format) record and + * add it to sheet recs + * + * @param location + * @return + */ + fun createCondfmt(location:String, wbh:WorkBookHandle):Condfmt { +val cfx = Condfmt.prototype as Condfmt? +var insertIdx = window2!!.recordIndex + 1 +var rec = this.SheetRecs[insertIdx] as BiffRec +while (rec.opcode != XLSConstants.HLINK && rec.offset != XLSConstants.DVAL.toInt() +&& rec.opcode.toInt() != 0x0862 /* SHEETLAYOUT */ +&& rec.opcode.toInt() != 0x0867 /* SHEETPROTECTION */ +&& rec.opcode.toInt() != 0x0868 /* RANGEPROTECTION */ +&& rec.opcode != XLSConstants.EOF) +rec = this.SheetRecs[++insertIdx] as BiffRec + +this.SheetRecs.add(insertIdx, cfx) +cfx!!.streamer = streamer +cfx.workBook = this.workBook +cfx.resetRange(location) +this.addConditionalFormat(cfx) +cfx.setSheet(this) +return cfx +} + +/** + * Create a Cf (Conditional format rule) record and + * add it to sheet recs + * + * @param Conditional format + * @param range + * @return + */ + fun createCf(cfx:Condfmt):Cf { +val cf = Cf.prototype as Cf? + // we add this rec to vec right after its Condfmt + val insertIdx = cfx.recordIndex + 1 +this.SheetRecs.add(insertIdx, cf) +cf!!.streamer = streamer +cf.workBook = this.workBook +cf.setSheet(this) +cf.condfmt = cfx +cfx.addRule(cf) +return cf +} + +/** + * obtain the desired image handle via the MsoDrawing Image Index + * used for mapping images from copied worksheets + * + * @param index + * @return + */ + fun getImageByMsoIndex(index:Int):ImageHandle? { +if (imageMap == null) +return null +val ir = imageMap!!.keys.iterator() +var ret:ImageHandle? = null +while (ir.hasNext() && ret == null) +{ +val im = ir.next() as ImageHandle +if (im.msodrawing!!.getImageIndex() == index) +ret = im +} +return ret +} + + // Generic getIndexOf - replace specific hardocoded cases + // ... + fun getIndexOf(opc:Short):Int { +var rec:BiffRec? = null + +val size = SheetRecs.size +var foundIndex = -1 +var i = 0 +while (i < size && foundIndex == -1) +{ +rec = SheetRecs[i] as BiffRec +if (rec.opcode == opc) +foundIndex = i +i++ +} +return foundIndex +} + +/** + * return the desired record from the sheetrecs, or null if doesn't exist + * + * @param opc + * @return + */ + fun getSheetRec(opc:Short):BiffRec? { +var rec:BiffRec? = null + +val size = SheetRecs.size +val foundIndex = -1 +var i = 0 +while (i < size && foundIndex == -1) +{ +rec = SheetRecs[i] as BiffRec +if (rec.opcode == opc) +return rec +i++ +} +return null +} + + // 20070916 KSC: access for inserting records into sheetrecs + // collection + fun insertSheetRecordAt(r:BiffRec, index:Int) { +r.setSheet(this) +if (index > -1 && index < SheetRecs.size) +SheetRecs.add(index, r) +else +SheetRecs.add(r) +} + +/** + * Sheet hash is a cross-workbook identifier for OpenXLS. The first time it is called it creates the sheet hash. + * + * @return + */ + fun getSheetHash():String { +if (sheetHash == "") +{ +sheetHash = (this.sheetName + this.sheetNum ++ this.realRecordIndex) +} +return sheetHash +} + +/** + * assembleSheetRecs assembles the array of records, then ouputs + * the ordered list to the bytestreamer, which should be the only + * thing calling this. + */ + fun assembleSheetRecs():List<*> { +return WorkBookAssembler.assembleSheetRecs(this) +} + +/** + * write this sheet as tabbed text output:

                  + * All rows and all characters in each cell are saved. Columns of data are + * separated by tab characters, and each row of data ends in a carriage + * return. If a cell contains a comma, the cell contents are enclosed in + * double quotation marks. All formatting, graphics, objects, and other + * worksheet contents are lost. The euro symbol will be converted to a + * question mark. If cells display formulas instead of formula values, the + * formulas are saved as text. + */ + /* + * From Excel: To preserve the formulas if you reopen the + * file in Microsoft + * Excel, select the Delimited option in the Text Import + * Wizard, and select + * tab characters as the delimiters. Note If your workbook + * contains special + * font characters, such as a copyright symbol (©), and you + * will be using + * the converted text file on a computer with a different + * operating system, + * save the workbook in the text file format appropriate for + * that system. + * For example, if you are using Windows and want to use the + * text file on a + * Macintosh computer, save the file in the Text (Macintosh) + * format. If you + * are using a Macintosh computer and want to use the text + * file on a system + * running Windows or Windows NT, save the file in the Text + * (Windows) + * format. + */ + @Throws(IOException::class) + fun writeAsTabbedText(dest:OutputStream) { +val lastrow = this.maxRow +val lastcol = this.maxCol +var isInteger = false +val tab = byteArrayOf(9) +val crlf = byteArrayOf(13, 10) +for (i in 0 until lastrow) +{ +val r = this.getRowByNumber(i) +if (r != null) +{ +for (j in 0 until lastcol) +{ +var c:BiffRec? = null +try +{ + // Look for the cell and output + c = r.getCell(j.toShort()) +val type = (c as XLSRecord).cellType +val o:Any? +if (type != Cell.TYPE_FORMULA) +{ +isInteger = type == Cell.TYPE_INT +o = c.stringVal +} +else +{ +o = (c as Formula).calculateFormula() +isInteger = o is Int || o is Double && o.toInt().toDouble() == o +.toDouble() +} +try +{ +var output = CellFormatFactory +.fromPatternString(c.formatPattern) +.format(o!!.toString()) +if (output.indexOf(",") != -1) +output = "\"" + output + "\"" +dest.write(output.toByteArray()) +} +catch (e:Exception) { +Logger.logWarn("Boundsheet.writeAsTabbedText: error writing " ++ c.cellAddress + ":" + e.toString()) +} + +} +catch (e1:CellNotFoundException) { + // No cell exists at this location, continue + } + +dest.write(tab) +} + // } catch (RowNotFoundException e) { } + } +dest.write(crlf) +} +dest.flush() +dest.close() +} + +/** + * do all of the expensive updating here + * only right before streaming record. + */ + override fun preStream() {} + +/** + * add a new Condtional Format rec for this sheet + * + * @param cf + */ + fun addConditionalFormat(cf:Condfmt) { +if (cond_formats == null) +cond_formats = ArrayList() +if (cond_formats!!.indexOf(cf) == -1) +cond_formats!!.add(cf) +} + +/** + * set/save the Pane rec for this sheet + * also links the Window2 rec to the pane rec + * + * @param p + */ + fun setPane(p:Pane?) { +var p = p +if (p == null) +{ // adds new +p = Pane.prototype as Pane? +val insertIdx = window2!!.recordIndex + 1 +this.SheetRecs.add(insertIdx, p) +} +pane = p +pane!!.setWindow2(window2) +} + +/** + * retrieve the Pane rec for this sheet + * + * @return + */ + fun getPane():Pane? { +return pane +} + +/** + * remove pane rec, effectively unfreezing + */ + fun removePane() { +SheetRecs.remove(pane) +pane = null +} + +/** + * Remove a BiffRec from this WorkSheet. + * + */ + @Deprecated("Use {@link #removeCell(int, int)} instead.") +override fun removeCell(celladdr:String) { +val c = this.getCell(celladdr) +if (c != null) +{ +this.removeCell(c) +} +} + +/** + * Remove a BiffRec from this WorkSheet. + */ + fun removeCell(row:Int, col:Int) { +val c:BiffRec +try +{ +c = this.getCell(row, col) +this.removeCell(c) +} +catch (e:CellNotFoundException) { + // cell does not exist, this is fine + } + +} + +/** + * remove a BiffRec from the worksheet. + * + * + * Unfortunately this also has to manage mulrecs + */ + override fun removeCell(cell:BiffRec) { +if (cell.opcode == XLSConstants.MULBLANK) +{ +(cell as Mulblank).removeCell(cell.colNumber) +} +if (cell.opcode == XLSConstants.FORMULA) +{ +val f = cell as Formula +this.wkbook!!.removeFormula(f) +} +cellsByRow.remove(cell) +cellsByCol.remove(cell) +} + +/** + * removes an image from the imagehandle cache (should be in WSH) + * + * + * Jan 22, 2010 + * + * @param img + * @return + */ + fun removeImage(img:ImageHandle):Boolean { +return imageMap!!.remove(img) != null +} + +/** + * remove a record from the vector via it's index + * into the SheetRecs aray, includes firing a change event + * + * @param idx + */ + fun removeRecFromVec(idx:Int) { +try +{ +val rec = this.SheetRecs[idx] as BiffRec +removeRecFromVec(rec) +} +catch (e:Exception) { +Logger.logErr("Boundsheet.removeRecFromVec: $e") +} + +} + +/** + * Removes some rows and all associated cells from this sheet. + * References are not handled; for those see [ReferenceTracker]. + * + * @param first the zero-based index of the first row to be removed + * @param count the number of rows to be removed + * @param shift whether to shift subsequent rows up to fill the empty space + */ + fun removeRows(first:Int, count:Int, shift:Boolean) { + +for (rowIdx in first until first + count) +{ + // this.removeRowContents(rowIdx); + + val row = rows[rowIdx] ?: continue + +val iter = row.cells.iterator() +while (iter.hasNext()) +{ +val cell = iter.next() + + // This removes the cell from the Row's map without + // perturbing + // the iterator. When removeCell tries to remove it later + // the + // map will silently do nothing instead of throwing a CME. + iter.remove() + +this.removeCell(cell) +} + +rows.remove(rowIdx) +this.removeRecFromVec(row) +} + + // shift all following rows up to fill the gap left by the + // removed rows + if (shift && !rows.isEmpty()) +{ +val shiftBy = -1 * count +val lastrow = lastRow!!.rowNumber +for (rowIdx in first + 1..lastrow) +{ +val row = rows[rowIdx] ?: continue + +this.shiftRow(row, shiftBy) +} +} + + // update sheet dimensions + this.dimensions!! +.setRowLast(if (null != lastRow) lastRow!!.rowNumber else 0) +} + +/** + * remove rec from the vector, includes firing + * a changeevent. + */ + override fun removeRecFromVec(rec:BiffRec) { +var removerec = true + // is it an RK, maybe part of a Mulrk?? + if (rec.opcode == XLSConstants.RK) +{ +val thisrk = rec as Rk +this.removeMulrk(thisrk) +} +else if (rec.opcode == XLSConstants.FORMULA) +{ +val f = rec as Formula +this.wkbook!!.removeFormula(f) +} +else if (rec.opcode == XLSConstants.LABELSST) +{ +val lst = rec as Labelsst +val strtable = wkbook!!.sharedStringTable +lst.initUnsharedString() +strtable!!.removeUnicodestring(lst.unsharedString!!) +} +else if (rec is Mulblank) +{ // KSC: Added +removerec = rec.removeCell(rec.colNumber) +} +if (removerec) +{ +if (streamer!!.removeRecord(rec)) +{ +if (DEBUGLEVEL > 5) +Logger.logInfo("Boundsheet RemoveRec Removed: $rec") +} +else +{ +if (rec is Mul) +{ +if (!(rec as Mul).removed()) +if (DEBUGLEVEL > 1) +Logger.logWarn("RemoveRec failed: " ++ rec.javaClass.getName() ++ " not found in Streamer Vec") +} +else +{ +if (DEBUGLEVEL > 1) +Logger.logWarn("RemoveRec failed: " ++ rec.javaClass.getName() ++ " not found in Streamer Vec") +} +} +} +} + +/** + * Called from removeCell(), removeMulrk() handles the fact that you + * are trying to delete a rk that is really just a part of a Mulrk. This + * is handled by truncating the mulrk at the cell, then creating individual numbers + * after the deleted cell. + */ + override fun removeMulrk(thisrk:Rk) { +val mymul = thisrk.myMul as Mulrk +if (mymul != null) +{ // Part of a mulrk. JOY! +val vect = mymul.removeRk(thisrk) +var deletemulrk = false +if (mymul.getColFirst() == thisrk.colNumber.toInt()) +{ +deletemulrk = true +} +if (vect != null) +{ // the mulrk contiued past the cell deleted + // Create new records for each of the Rks, + val itv = vect.iterator() +while (itv.hasNext()) +{ +val temprk = itv.next() as Rk +temprk.setNoMul() +val loc = temprk.cellAddress + +val d = temprk.dblVal +val g = this.getCell(loc) +val fmt = g!!.ixfe +g.row.removeCell(g) +this.removeCell(loc) + +this.addValue(d, loc) +this.getCell(loc)!!.ixfe = fmt +streamer!!.removeRecord(temprk) +} +} +if (deletemulrk) +{ +mymul.removed = true +this.removeRecFromVec(mymul) +} +} + +} + +/** + * Remove a row, do not shift any other rows + * + * @throws RowNotFoundException + */ + @Throws(RowNotFoundException::class) + fun removeRowContents(rownum:Int) { +var r = this.getRowByNumber(rownum) + // First delete the desired row + if (r != null) +{ +val cells = r.cellArray +for (x in cells.indices) +{ // adjust cell's in row +this.removeCell(cells[x] as BiffRec) +} +rows.remove(Integer.valueOf(rownum)) +this.removeRecFromVec(r) +r = null +} +else +{ +throw RowNotFoundException(this.sheetName + ":" + rownum) +} + +} + +/** + * Removes a set of columns and their associated cells from this sheet. + * Optionally shifts the subsequent columns left to fill the empty space. + * This method only updates the sheet and cell records. It doesn't adjust + * references; that's handled by [WorkSheetHandle.removeCols]. + * + * @param first this zero-based index of the first column to be removed + * @param count the number of columns to remove + * @param shift whether to shift subsequent columns left + */ + fun removeCols(first:Int, count:Int, shift:Boolean) { + +if (shift) +{ +ReferenceTracker.updateReferences(first, count * -1, this, false) // shift + // or + // expand/contract + // ALL + // affected + // references + // including + // named + // ranges + } + +for (colIdx in first until first + count) +{ + + // update or remove the ColInfo record as appropriate + val info = this.getColInfo(colIdx) +if (null != info) +{ +if (info.colLast < first + count) +{ +if (info.colFirst >= first) +{ +this.removeColInfo(info) +} +else +{ +info.colLast = first - 1 +} +} +else if (info.colFirst >= first) +{ +info.colFirst = first + count +} +} + + // remove the cells in the column + try +{ +val cells = this.getCellsByCol(colIdx) +val cellCount = cells.size +for (idx in cellCount - 1 downTo 0) +{ +val cell = cells[idx] ?: continue + +this.removeCell(cell) +} +} +catch (e:CellNotFoundException) { + // This is fine, no cells in this column + } + +} + +if (shift) +{ +val shiftBy = -1 * count +val maxcol = this.realMaxCol +for (colIdx in first + 1..maxcol) +{ +this.shiftCol(colIdx, shiftBy) +} +} + + // make sure dimensions record is correctly updated upon + // output + this.dimensions!!.setColLast(this.realMaxCol) +} + +/** + * Access an arrayList of cells by column + * + * @param colNum + * @return + */ + @Throws(CellNotFoundException::class) + fun getCellsByCol(colNum:Int):ArrayList { +val theCells = cellsByCol +.subMap(CellAddressible.RangeBoundary(0, colNum, +true), CellAddressible.RangeBoundary(0, colNum + 1, +false)) +if (theCells.size == 0) +throw CellNotFoundException(this.sheetname, 0, colNumber.toInt()) + +val cells = theCells.values +val i = cells.iterator() +while (i.hasNext()) +{ +val biffrec = i.next() +if (biffrec.opcode == XLSConstants.MULBLANK) +{ +(biffrec as Mulblank).setCurrentCell(colNum.toShort()) +} +} +return ArrayList(cells) +} + +/** + * Access an arrayList of cells by column + * + * @param colNum + * @return + */ + @Throws(CellNotFoundException::class) + fun getCellsByRow(rowNum:Int):ArrayList { +val theCells = cellsByRow +.subMap(CellAddressible.Reference(rowNum, +0), CellAddressible.Reference(rowNum + 1, 0)) +if (theCells.size == 0) +throw CellNotFoundException(this.sheetname, 0, colNumber.toInt()) + +val cells = theCells.values +return ArrayList(cells) +} + +/** + * get a handle to a specific column of cells in this sheet + */ + override fun getColInfo(col:Int):Colinfo? { +val info = this.colinfos[ColumnRange.Reference(col, col)] ?: return null + +return if (info.inrange(col)) info else null +} + +/** + * remove all Sheet records from Sheet. + */ + override fun removeAllRecords() { + // this.setSheetRecs(); + val rx = arrayOfNulls(SheetRecs.size) +SheetRecs.toTypedArray() + +for (t in rx.indices) +{ +val opcode = (rx[t] as BiffRec).opcode.toInt() // Handle continues + // masking mso's + if (opcode != XLSConstants.MSODRAWING.toInt() && !(opcode == XLSConstants.CONTINUE.toInt() && (rx[t] as Continue).maskedMso != null)) +this.removeRecFromVec(rx[t]) +else // must update MSODrawingGroup record as well ... + if (opcode == XLSConstants.MSODRAWING.toInt()) +this.wkbook!!.msodg!! +.removeMsodrawingrec(rx[t] as MSODrawing, this, false) // don't +else + // a Continue record masking an MSoo + this.wkbook!!.msodg!! +.removeMsodrawingrec((rx[t] as Continue).maskedMso!!, this, false)// remove + // assoc + // object + // record + // don't + // remove + // assoc + // object + // record + rx[t] = null +} +SheetRecs.clear() + // System.gc(); + + } + +/** + * Shifts a single column. + * This adjusts any mention of the column number in the associated records. + * References are not handled; for those see [ReferenceTracker]. + * + * @param col the column to be shifted + * @param shift the number of columns by which to shift + */ + private fun shiftCol(colNum:Int, shift:Int) { +val info = this.getColInfo(colNum) +val newCol = colNum + shift + +val cells:List +try +{ +cells = this.getCellsByCol(colNum) +for (cell in cells) +{ +cell.setCol(newCol.toShort()) +this.updateDimensions(cell.rowNumber, cell.colNumber.toInt()) +} +} +catch (e:CellNotFoundException) { + // No cells exist in this column + } + +if (null != info) +{ +val first = info.colFirst +if (first == colNum || first > newCol) +{ +info.colFirst = newCol +} + +val last = info.colLast +if (last == colNum || last < newCol) +info.colLast = newCol +} +} + +private fun removeColInfo(ci:Colinfo) { +this.removeRecFromVec(ci) +this.colinfos.remove(ci) +} + + /* + * + */ + + /** + * set the Bof record for this Boundsheet + */ + override fun setBOF(b:Bof) { +myBof = b +b.setSheet(this) +} + +override fun setEOF(f:Eof) { +myEof = f +} + +/** + * return the pos of the Bof for this Sheet + */ + override fun getLbPlyPos():Long {// +return if (myBof != null) myBof!!.lbPlyPos else this.lbPlyPos + +} + +/** + * set the pos of the Bof for this Sheet + */ + override fun setLbPlyPos(newpos:Long) { +val newposbytes = ByteTools.cLongToLEBytes(newpos.toInt()) +System.arraycopy(newposbytes, 0, this.getData()!!, 0, 4) +this.lbPlyPos = newpos +} + +/** + * the beginning of the Dimensions record + * is the index of the RowBlocks + */ + override fun getDimensions():Dimensions? { +return dimensions +} + +override fun setDimensions(d:Dimensions) { + // only set the first dimensions. Other dimensions records + // may exist within + // the boundsheet stream from charts & msodrawing objects, + // but going to run with the + // assumption that the first one is the identifier for + // valrec start + if (dimensions == null) +{ +dimensions = d +if (myidx != null) +this.myidx!!.setDimensions(d) +} + +} + +/** + * Shifts a single row. + * This adjusts any mention of the row number in the row records. Formula + * references are not handled; for those see [ReferenceTracker]. + * + * @param row the row to be shifted + * @param shift the number of rows by which to shift + */ + private fun shiftRow(row:Row, shift:Int) { +val cells = row.cells.iterator() +var skipMulBlank:Mulblank? = null +while (cells.hasNext()) +{ +val cell = cells.next() as BiffRec + +if (cell === skipMulBlank) +continue +else if (cell.opcode == XLSConstants.MULBLANK) +skipMulBlank = cell as Mulblank + +this.shiftCellRow(cell, shift) +} + +val oldRow = row.rowNumber +val newRow = oldRow + shift +row.rowNumber = newRow + +rows.remove(oldRow) +rows[newRow] = row + +if (this.dimensions!!.getRowLast() < newRow) +{ +this.dimensions!!.setRowLast(newRow) +this.lastRow = row +} +} + +/** + * update the INDEX record with the new max Row # + * why we need so many redundant references to the Min/Max Row/Cols + * is a question for the Redmond sages. + */ + override fun updateDimensions(row:Int, c:Int) { +if (DEBUGLEVEL > 10) +Logger.logInfo("Boundsheet Updating Dimensions: " + row + ":" ++ colNumber) +val col = c.toShort() +realMaxCol = Math.max(realMaxCol, col.toInt()) +maximumCellRow = Math.max(maximumCellRow, row) +if (dimensions != null) +dimensions!!.updateDimensions(row - 1, col) +if (this.myidx != null) +this.myidx!!.updateRowDimensions(this.minRow, this.maxRow) // TODO: + // investigate + // why + // no + // Index + // is + // possible + } + +/** + * set the associated sheet index + */ + override fun setSheetIDX(idx:Index) { +idx.setSheet(this) +myidx = idx +} + +/** + * set the associated sheet index + */ + fun getSheetIDX():Index? { +return myidx +} + +/** + * Adjusts a cell to reflect its parent row being shifted. + * This adjusts any mention of the row number in the cell record. Formula + * references are not handled; for those see [ReferenceTracker]. + * + * @param cell the cell record to be shifted + * @param shift the number of rows by which to shift the cell + */ + private fun shiftCellRow(cell:BiffRec, shift:Int) { +val newrow = cell.rowNumber + shift +cell.rowNumber = newrow + + // handle per-record special cases + when (cell.opcode) { +XLSConstants.RK -> (cell as Rk).setMulrkRow(newrow) + +XLSConstants.FORMULA -> { +val formula = cell as Formula + + // must also shift shared formulas if necessary + if (formula.isSharedFormula) +{ +if (formula.internalRecords.size > 0) +{// is it the + // parent? + val o = formula.internalRecords[0] +if (o is Shrfmla) +{ // should! +o.firstRow = o.firstRow + shift +o.lastRow = o.lastRow + shift +} +} +} +} +} +} + +/** + * add a row to the worksheet as well + * as to the RowBlock which will handle + * the updating of Dbcell index behavior + * + * @param BiffRec the cell being added (can't add a row without one...) + */ + private fun addNewRow(cell:BiffRec):Row? { +val rn = cell.rowNumber +if (this.getRowByNumber(rn) != null) +return this.getRowByNumber(rn) // already exists! +val r = Row(rn, wkbook) +try +{ // Out-of-spec wb's may not have dimensions record -- will + // be handled upon validation + if (rn >= this.maxRow) +dimensions!!.setRowLast(rn) +} +catch (e:NullPointerException) {} + +r.setSheet(this) +this.addRowRec(r) +return r + +} + +/** + * Inserts a row and shifts subsequent rows down by one. + * + * @param rownum the zero-based index of the row to be created + * @return the row that was just inserted + */ + // TODO: reduce this functionality to simply inserting a row + // and shifting the row number of subsequent rows and cells + fun insertRow(rownum:Int, firstcol:Int, flag:Int, shiftrows:Boolean):Row? { +var roe:Row? = null +if (shiftrows && !this.fastCellAdds) +{ +try +{ + // shift all rows after this one down... + // moves refs, formats, merges, etc. + if (lastRow != null) +{ +var startrow = lastRow!!.rowNumber +if (startrow == XLSConstants.MAXROWS) +startrow-- // 20080925 KSC: can't add more than maxrows + +for (t in startrow downTo rownum) +{ // traverse from + // last row to + // current + val rowtoshift = rows[Integer.valueOf(t)] +if (rowtoshift != null) +{ +try +{ +this.shiftRow(rowtoshift, 1)// pass original + // row # for + // formula + // shifting + + // flag + } +catch (e:Exception) { +Logger.logWarn("Boundsheet.insertRow() failed shifting row: " ++ t + " - " + e.toString()) +} + +} +} +} + + // we add a blank because a row cannot be empty + roe = this.getRowByNumber(rownum) +if (roe == null) +{ +val rc = intArrayOf(rownum, firstcol) // added firstcol to not + // add bad cells at a1 + this.addRecord(Blank.prototype!!, rc) +roe = this.getRowByNumber(rownum) +} + +} +catch (a:Exception) { +Logger.logInfo("Boundsheet.insertRow: Shifting row during Insert failed: $a") +} + +} +else +{ +roe = this.getRowByNumber(rownum) +if (roe == null) +{ +val r = Row(rownum, wkbook) + // must also update maxrow on sheet + if (rownum >= this.maxRow) +dimensions!!.setRowLast(rownum) +r.setSheet(this) +this.addRowRec(r) +roe = this.getRowByNumber(rownum) +roe!!.resetCacheBytes() +} +} +return roe +} + +/** + * shifts Merged cells. 10-15-04 -jm + */ // used??? + override fun updateMergedCells() { +if (this.mc.size < 1) +return +val mcs = this.mc.iterator() +while (mcs.hasNext()) +(mcs.next() as Mergedcells).update() +} + +/** + * associate an existing Row with this Boundsheet + * if the row already exists... ignore? + */ + fun addRowRec(r:Row) { +val rwn = r.rowNumber +if (rows.containsKey(Integer.valueOf(rwn))) +{ +if (DEBUGLEVEL > 2) +Logger.logWarn("Sheet.addRow() attempting to add existing row") +} +else +{ +rows[Integer.valueOf(rwn)] = r +if (lastRow == null) +{ +this.lastRow = r +} +else if (rwn > lastRow!!.rowNumber) +{ +lastRow = r +} +} +} + +/** + * set whether this sheet is hidden upon opening + */ + override fun setHidden(gr:Int) { +sheetType = gr.toShort() +val bt = ByteTools.shortToLEBytes(sheetType) +System.arraycopy(bt, 0, getData()!!, 4, 2) +} + +/** + * returns the selected sheet status + */ + override fun selected():Boolean { +return selected +} + +/** + * set whether this sheet is selected upon opening + */ + override fun setSelected(b:Boolean) { +if (this.window2 != null) +this.window2!!.setSelected(b) +if (b) +{ +this.workBook!!.setSelectedSheet(this) +} +selected = b +} + +/** + * associate an Array formula with this Boundsheet + */ + fun addArrayFormula(a:Array) { +arrayformulas.add(a) +} + +/** + * Returns an array formula for the set address + */ + fun getArrayFormula(addr:String):Array? { +var form:Array? = null +for (i in arrayformulas.indices) +{ +form = arrayformulas[i] as Array +if (form.isInRange(addr)) +return form +} +return null +} + +/** + * map array formula range reference to the parent array formula address + *

                  for Array Formula Parent Records only + * boundsheet + */ + fun addParentArrayRef(addr:String, ref:String) { +if (arrFormulaLocs.containsKey(addr)) +Logger.logWarn("PARENT ARRAY ALREADY FOUND") +arrFormulaLocs.put(addr, ref) +} + +/** + * see if an array formula is part of an existing array formula + *

                  by checking to see if the address in quesion is + * referenced by any array formula references on this sheet + * + * @param rc row col of cell in question + * @return + * @see addArrayFormula + */ + fun getArrayFormulaParent(rc:IntArray):Any? { +val i = arrFormulaLocs.keys.iterator() +while (i.hasNext()) +{ +val addr = i.next() as String +val arrayRC = ExcelTools +.getRangeRowCol(arrFormulaLocs.get(addr) as String) +if (rc[1] >= arrayRC[1] && rc[1] <= arrayRC[3] +&& rc[0] >= arrayRC[0] && rc[0] <= arrayRC[2]) +{ +return arrayRC +} +} +return null // no parent? +} + +/** + * return true if address refers to an Array Formula Parent + *

                  i.e. the parent array formula refers to one or multiple cell addreses + * + * @param addr + * @return + */ + fun isArrayFormulaParent(addr:String):Boolean { +return arrFormulaLocs.get(addr) != null +} + +/** + * given an parent array formula at address formAddress, + * look up in saved arrFormulaLocs for the cell range it references + * + * @param formAddress + * @return + */ + fun getArrayRef(formAddress:String):String { +return arrFormulaLocs.get(formAddress) +} + +/** + * inserts a col and shifts all of the other rows over one + * + * @param first zero-based int for the column (0='A') + */ + fun insertCols(first:Int, count:Int) { + +ReferenceTracker.updateReferences(first + 1, count, this, false) // shift + // or + // expand/contract + // ALL + // affected + // references + // including + // named + // ranges + + // shift the existing columns to the right to make room + for (colIdx in this.realMaxCol downTo first) +{ +this.shiftCol(colIdx, count) +} + + // update the new colinfos to include the formatting and the + // width of the inserted col + val movedCol = this.getColInfo(first + count) +if (movedCol != null) +{ +for (i in 0 until count) +{ +val newcol = this.getColInfo(first + i) +if (newcol == null) +{ +this.addColinfo(first + i, first + i, movedCol +.colWidth, movedCol +.ixfe, movedCol.grbit) +} +else +{ +newcol.grbit = movedCol.grbit +newcol.colWidth = movedCol.colWidth +newcol.ixfe = movedCol.ixfe +} +} +} + + // ensure the sheet bounds are accurate + this.dimensions!!.setColLast(this.realMaxCol) +} + +/** + * Add a new colinfo + * + * @param first The beginning column number (0 based) + * @param last The end column number + * @param width Initial width of the column + * @param ixfe formatting + * @param grbit ?? + * @return Colinfo + */ + fun createColinfo(first:Int, last:Int, width:Int, ixfe:Int, grbit:Int):Colinfo { +val ci = Colinfo.getPrototype(first, last, width, ixfe) +ci.grbit = grbit +ci.workBook = workBook +ci.setSheet(this) +this.addColinfo(ci) +var recpos = this.getDimensions()!!.recordIndex +recpos-- +val sr = this.sheetRecs + // get to last Colinfo record + var rec = sr[recpos] as BiffRec + // TODO: is it ABSOLUTELY true that if no Colinfos there + // must be a DefColWidth record???? + while (rec !is Colinfo && rec !is DefColWidth +&& recpos > 0) +{ // loop until we find either a colinfo or + // DEFCOLWIDTH + rec = sr[--recpos] as BiffRec +} + // now position this Colinfo in the proper position within + // the Colinfo set + val cf = ci.colFirst +while (rec is Colinfo && rec.colFirst > cf) +{ +rec = sr[--recpos] as BiffRec +} +recpos++ +this.streamer!!.addRecordAt(ci, recpos) +return ci +} + +/** + * Create a colinfo using the values from an existing colinfo + * + * @param first first col in the colinfo + * @param last last col in the colinfo + * @param template template column + * @return + */ + fun createColinfo(first:Int, last:Int, template:Colinfo):Colinfo { +return this.createColinfo(first, last, template.colWidth, template +.ixfe, template.grbit) +} + + fun createColinfo(first:Int, last:Int):Colinfo { +return this.createColinfo(first, last, Colinfo.DEFAULT_COLWIDTH, 0, 0) +} + +/** + * get a handle to the Row at the specified + * row index + * + * + * Zero-based Index. + * + * + * ie: row 0 contains cell A1 + */ + override fun getRowByNumber(r:Int):Row? { +return rows[Integer.valueOf(r)] +} + +/** + * return an Array of the Rows + */ + override fun getRows():Array { +val rxs = TreeMap(rows) // treemap does ordering... LHM does not +val rarr = arrayOfNulls(rxs.size) +return rxs.values.toTypedArray() as Array +} + // safety checks + + override fun addValue(obj:Any, address:String):BiffRec { +return addValue(obj, address, false) +} + +/** + * Add a Value record to a WorkSheet. + * This method's purpose is to handle default formatting + * of the cell that is being added, and to do any manipulations + * neccessary to handle mulrks, mulblanks, etc. It is also the + * main entry point of adding values to the boundsheet. These values + * are then passed into createValrec() which + * + * @param obj the value of the new Cell + * @param address the address of the new Cell + */ + fun addValue(obj:Any, address:String, fixNumberAsString:Boolean):BiffRec { + + // first see if there's an existing item + val rc = ExcelTools.getRowColFromString(address) + +return addValue(obj, rc, fixNumberAsString) + +} + +/** + * adds a value to the sheet + * + * @param obj + * @param rc + * @return + */ + fun addValue(obj:Any, rc:IntArray, fixNumberAsString:Boolean):BiffRec { +return addValue(obj, rc, this.workBook!! +.defaultIxfe, fixNumberAsString) +} + +/** + * adds a cell to the Sheet + * + * @param obj + * @param rc + * @param FORMAT_ID + * @param fixNumberAsString - whether to attempt to convert to a number if it is a NSaS situation + * @return + */ + @JvmOverloads fun addValue(obj:Any?, rc:IntArray, FORMAT_ID:Int, fixNumberAsString:Boolean = false):BiffRec { +var obj = obj +var FORMAT_ID = FORMAT_ID + +if (rc[1] > WorkBook.MAXCOLS) +throw InvalidRecordException("Cell Column number: " + rc[1] ++ " is greater than maximum allowable Columns: " ++ WorkBook.MAXCOLS) + + // sanity checks + if (rc[0] > WorkBook.MAXROWS) +throw InvalidRecordException("Cell Row number: " + rc[0] ++ " is greater than maximum allowable row: " ++ WorkBook.MAXROWS) + +var r = this.getRowByNumber(rc[0]) + + /* + * from Doc: The default cell format is always present in an + * Excel file, + * described by the XF record with the fixed index 15 + * (0-based). + * + * By default, it uses the worksheet/workbook default cell + * style, + * described by the very first XF record (index 0). + */ + if (FORMAT_ID <= 0) +FORMAT_ID = this.workBook!!.defaultIxfe +if (FORMAT_ID == this.workBook!!.defaultIxfe) +{ +if (this.getColInfo(rc[1]) != null) +{ /* + * get default colinfo if + * possible + */ +val co = this.getColInfo(rc[1]) +if (co != null && co.ixfe != 0) +FORMAT_ID = co.ixfe +} +if (r != null && r.explicitFormatSet) +{ +FORMAT_ID = r.ixfe +} +} + +var merge_range:CellRange? = null + +if (!fastCellAdds) +{ +try +{ +val mycell = this.getCell(rc[0], rc[1]) +merge_range = mycell.mergeRange + // specific cell format overrides any other formats: if + // (FORMAT_ID == defaultFormatId) + if (mycell.ixfe != this.workBook!!.defaultIxfe && mycell.ixfe != 0) +FORMAT_ID = mycell.ixfe +this.removeCell(mycell) +} +catch (cnfe:CellNotFoundException) { + // good! + } + +} + + // Handle detection of Number stored as Strings + var fixed:Array? = null +if (!fastCellAdds && fixNumberAsString && obj != null) +{ +try +{ +fixed = this.fixNumberStoredAsString(obj) +obj = fixed!![0] +} +catch (e:Exception) { + // not a number! + } + +} + +val rec = this.createValrec(obj, rc, FORMAT_ID) + +if (!fastCellAdds) +{ // reapply conditional format and merges +if (merge_range != null) +rec.mergeRange = merge_range +} + + // check this does not touch affectedcells + this.addRecord(rec, rc) + +if (fixed != null) +{ +val f = FormatHandle(this.wkbook, +this.workBook!!.defaultIxfe) +f.formatPattern = fixed!![1].toString() +rec.setXFRecord(f.formatId) +} + +rec.resetCacheBytes() + +if (r == null) +{ // if no row initially, check default row height; if + // not Excel's default, set row height + val rh = this.defaultRowHeight +if (rh != 12.75) +{// the default +r = this.getRowByNumber(rc[0]) +r!!.rowHeight = (rh * 20).toInt() +} +} +return rec +} + +/** + * for numbers stored as strings, try to guess the + * format pattern used, and strip the value to a number + * + * + * TODO: increase sophistication of pattern matching to better guess pattern used + * + * @param s + * @return Object[Double value, String formatPattern] + */ + @Throws(NumberFormatException::class) +internal fun fixNumberStoredAsString(s:Any):Array { +var input = s.toString() +if (input.indexOf(" ") > -1) +{ +input = StringTool.allTrim(input) +} +var p = "" // the format pattern +var matched = false + +for (fmts in NumberAsStringFormat.values()) +{ +if (input.indexOf(fmts.identifier) > -1) +{ +input = StringTool.strip(input, fmts.identifier) +p = fmts.pattern +matched = true +var d:Double? = Double(input) +d = fmts.adjustValue(d!!) +val ret = arrayOfNulls(2) +ret[0] = d // value +ret[1] = p // format pattern +return ret +} +} + +throw NumberFormatException() +} + +/** + * A simple enum to store matching strings, format patterns, and value + * switches where necessary + */ + private enum class NumberAsStringFormat private constructor(private val identifier:String, private val pattern:String) { +PERCENT("%", "0%"), +EURO("€", "€#,##0;(€#,##0)"), +YEN("¥", "¥#,##0;(¥#,##0)"), +POUND("£", "£#,##0;(£#,##0)"), +DOLLAR("$", "$#,##0;(€#,##0)"), +ALT_POUND("₤", "₤#,##0;(₤#,##0)"); + + fun identifier():String { +return this.identifier +} + + fun pattern():String { +return this.pattern +} + +/** + * adjust the value where necessary + */ + fun adjustValue(inputVal:Double):Double { +return if (this.identifier === "%") inputVal * .01 else inputVal +} +} + +/** + * Creates a valrec (Value containing XLSRecord). This method observes + * the object passed in, then creates a XLS record of the correct type depending + * on the object type. A default FormatID is handled as well. + * + * + * The valrec at this point is not fully formed, it needs the row/col set + * along with some other default actions that occur in addRecord(). This is + * due to addRecord being the merge point for adding cells to a boundsheet between + * the parse-level additions and the user-level additions! + * + * @param obj the value of the new Cell + * @param row & col address of the new Cell + * @param FORMAT_ID, index to the XF record for this valrec + * @return partially formed XLS Record. + */ + private fun createValrec(obj:Any?, rc:IntArray, FORMAT_ID:Int):XLSRecord { + /* + * try{ + * BiffRec cx = this.getCell(rc[0],rc[1]); + * this.removeCell(cx); + * }catch(CellNotFoundException e){} + */ + var rec:XLSRecord? = null +if (obj == null) +{ +rec = Blank() +} +else if (obj is Formula) +{ +rec = obj +} +else if (obj is Double) +{ +rec = NumberRec(obj.toDouble()) +} +else if (obj is String) +{ +if (obj.startsWith("=")) +{ +try +{ + // Logger.logInfo("adding formula"); + rec = FormulaParser +.getFormulaFromString(obj as String?, this, rc) + + // this is a problem because workbook adds the rec to + // lastbounds + // in other words it will show up on the last sheet. Needed? + workBook!!.addRecord(rec!!, false) + + // getWorkBook().addFormula((Formula)rec); // next best + // thing methinks... + } +catch (e:Exception) { + // 20070212 KSC: add sheet name + address + // Logger.logWarn("adding new Formula at row:" + rc[0] +" + // col: " + rc[1] + ":"+obj.toString()+" failed, adding + // value to worksheet as String."); + throw FunctionNotSupportedException( +"Adding new Formula at " + this.sheetName + "!" ++ ExcelTools.formatLocation(rc) ++ " failed: " + e.toString() + ".") + // rec = Labelsst.getPrototype((String) obj, + // this.getWorkBook().getSharedStringTable()); + } + +} +else if (obj.startsWith("{=")) +{ // interpret array + // formulas as well + // 20090526 KSC: + // changed from "{" + // to "{=" tracy vo + // complex string + // addition + try +{ +rec = FormulaParser +.getFormulaFromString(obj as String?, this, rc) +rec!!.isFormula = true +} +catch (e:Exception) { +throw FunctionNotSupportedException( +"Adding new Formula at " + this.sheetName + "!" ++ ExcelTools.formatLocation(rc) ++ " failed: " + e.toString() + ".") +} + +} +else if (obj.toString().equals("", ignoreCase = true)) +{ +rec = Blank() +} +else +{ +rec = Labelsst.getPrototype(obj as String?, this.workBook) +} +} +else if (obj is Int) +{ +val l = obj.toInt() +rec = NumberRec(l) + +} +else if (obj is Long) +{ +val l = obj.toLong() +rec = NumberRec(l) +} +else if (obj is Boolean) +{ + // Logger.logErr("Adding Boolean Not Implemented"); + rec = Boolerr.prototype +rec!!.booleanVal = obj.booleanValue() +} +else +{ +val d = Double(obj.toString()) // 20080211 + // KSC: + // Double.valueOf(String.valueOf(obj)).doubleValue(); + rec = NumberRec(d) +} +rec!!.workBook = workBook +rec.setXFRecord(FORMAT_ID) + // 20100607 KSC: update maxrow/maxcol if necessary + if (rc[0] > maxRow || rc[1] > maxCol) +this.updateDimensions(rc[0], rc[1]) +return rec +} + +/** + * Add an XLSRecord to a WorkSheet. + * + * + * Creates the container cell for a record, sets the default + * information on the valrec (ie row/col/bs), checks to see if + * there is a container row for the cell, if not, then it creates the + * row. Finally, the cell is passed on to addCellToRowCol where it performs + * final initialization and is added to it's row + */ + override fun addRecord(rec:BiffRec, rc:IntArray) { + // check to see if there is a BiffRec already at the address + // add the rec to the Cell, + // set as value if it's a val type rec + + rec.setSheet(this)// create a new BiffRec if none exists +rec.setRowCol(rc) +rec.isValueForCell = true +rec.streamer = streamer +rec.workBook = this.workBook + +if (!this.fastCellAdds) +{ + +var ro:Row? = null +ro = rows[Integer.valueOf(rc[0])] +if (ro == null) +ro = this.addNewRow(rec) + +} +if (copypriorformats && !this.fastCellAdds) +this.copyPriorCellFormatForNewCells(rec) + +try +{ +this.addCell(rec as CellRec) +} +catch (ax:ArrayIndexOutOfBoundsException) { +Logger.logErr("Boundsheet.addRecord() failed. Column " + rc[1] ++ " is greater than Maximum column count") +throw InvalidRecordException("Adding cell failed. Column " ++ rc[1] + " is greater than the maximum column limit.") +} + +} + +/** + * Add a cell to this boundsheet record and populate the cells array + * + * @param cell + */ + override fun addCell(cell:CellRec?) { +cellsByRow[cell] = cell +cellsByCol[cell] = cell +var row:Row? = rows[Integer.valueOf(cell!!.rowNumber)] +if (null == row) +row = this.addNewRow(cell) +row!!.addCell(cell) +cell?.setSheet(this) +this.updateDimensions(cell.rowNumber, cell.colNumber.toInt()) +} + +override fun setCopyPriorCellFormats(f:Boolean) { +this.copypriorformats = f +} + +private fun copyPriorCellFormatForNewCells(c:BiffRec):Boolean { +val row = c.rowNumber + 1 // get the prior cell addy +val cnm = ExcelTools.getAlphaVal(c.colNumber.toInt()) +val ch = this.getCell(cnm + row) ?: return false // try it... +c.ixfe = ch.ixfe +return true +} + +/** + * Add a new colinfo + * + * @param begCol The beginning column number (0 based) + * @param endCol The end column number + * @param width Initial width of the column + * @param ixfe formatting + * @param grbit ?? + * @return Colinfo + */ + fun addColinfo(begCol:Int, endCol:Int, width:Int, ixfe:Int, grbit:Int):Colinfo { +val ci = Colinfo.getPrototype(begCol, endCol, width, ixfe) +ci.grbit = grbit +ci.workBook = workBook +ci.setSheet(this) +this.addColinfo(ci) +var recpos = this.getDimensions()!!.recordIndex +recpos-- +val sr = this.sheetRecs + // get to last Colinfo record + var rec = sr[recpos] + // TODO: is it ABSOLUTELY true that if no Colinfos there + // must be a DefColWidth record???? + while (rec !is Colinfo && rec !is DefColWidth +&& recpos > 0) +{ // loop until we find either a colinfo or + // DEFCOLWIDTH + rec = sr[--recpos] +} + // now position this Colinfo in the proper position within + // the Colinfo set + val cf = ci.colFirst +while (rec is Colinfo && rec.colFirst > cf) +{ +rec = sr[--recpos] +} +recpos++ +this.streamer!!.addRecordAt(ci, recpos) +return ci +} + +/** + * get a colinfo by name + */ + override fun getColinfo(c:String):Colinfo? { +return this.getColInfo(ExcelTools.getIntVal(c)) +} + +/** + * get the Collection of Colinfos + */ + override fun getColinfos():Collection { +return Collections.unmodifiableCollection(colinfos.values) +} + +/** + * Moves a cell location from one address to another + */ + fun moveCell(startaddr:String, endaddr:String) { +val c = getCell(startaddr) +if (c!!.opcode == WorkBookFactory.RK) +{ +try +{ +val d = c.dblVal +this.removeCell(c) +this.addValue(d, endaddr) +} +catch (e:Exception) { +Logger.logInfo("Boundsheet.moveCell() error :$e") +} + +} +else +{ +val s = ExcelTools.getRowColFromString(endaddr) +c.setCol(s[1].toShort()) +c.rowNumber = s[0] +removeCell(startaddr) +this.addCell(c as CellRec?) +} +} + +/** + * Moves a cell location from one address to another, + * without any clearing of previous locations. This is used in sorting + * and other cell movements where we do not want to delete from starting address + */ + fun updateCellReferences(c:BiffRec, endaddr:String) { +if (c.opcode == WorkBookFactory.RK) +{ +try +{ +val d = c.dblVal +this.removeCell(c) +this.addValue(d, endaddr) +} +catch (e:Exception) { +Logger.logInfo("Boundsheet.moveCell() error :$e") +} + +} +else +{ +val s = ExcelTools.getRowColFromString(endaddr) +c.setCol(s[1].toShort()) +c.rowNumber = s[0] +this.addCell(c as CellRec) +} +} + +/** + * Gets a cell on this sheet by its Excel A1-style address. + * + * @param address the A1-style address of the cell to retrieve + * @return the cell record + * or `null` if no cell exists at the given address + */ + @Deprecated("Use {@link #getCell(int, int)} instead.") +override fun getCell(address:String):BiffRec? { +val rc = ExcelTools.getRowColFromString(address) +try +{ +return this.getCell(rc[0], rc[1]) +} +catch (ex:CellNotFoundException) { +return null +} + +} + +/** + * Gets a cell on this sheet by its row and column indexes. + * + * @param row the zero-based index of the cell's parent row + * @param col the zero-based index of the cell's parent column + * @return the cell record at the given address + * @throws CellNotFoundException if no cell exists at the given address + */ + @Throws(CellNotFoundException::class) +override fun getCell(row:Int, col:Int):BiffRec { + // get the nearest entry from the cell map + val theCell = cellsByRow[CellAddressible.Reference(row, col)] ?: throw CellNotFoundException(this.sheetname, row, col) + +if (theCell != null && theCell.opcode == XLSConstants.MULBLANK) +{ +(theCell as Mulblank).setCurrentCell(col.toShort()) +} +return theCell +} + +override fun addMergedCellsRec(r:Mergedcells) { +mc.add(r) +} + +/** + * Get the print area or titles name rec for this + * boundsheet, return null if not exists + * + * @return + */ + protected fun getPrintAreaNameRec(type:Byte):Name? { +val names = this.builtInNames +for (i in names.indices) +{ +val n = names[i] as Name +if (n.builtInType == type) +{ +return n +} +} +return null +} + +/** + * adds the _FILTERDATABASE name necessary for AutoFilter + * if not already presetn + */ + private fun addFilterDatabase() { +val names = this.builtInNames +var n:Name? = null +var i = 0 +while (i < names.size && n == null) +{ +if ((names[i] as Name) +.builtInType == Name._FILTER_DATABASE) +{ +n = names[i] as Name +} +i++ +} +if (n == null) +{ // not present +try +{ +n = Name(this.workBook, "Built-in: _FILTER_DATABASE") +n.setBuiltIn(Name._FILTER_DATABASE) +val xref = this.workBook!!.getExternSheet(true)!! +.insertLocation(this.sheetNum, this.sheetNum) +n.setExternsheetRef(xref) +n.updateSheetReferences(this) +n.setSheet(this) +n.setIxals(this.sheetNum/* +1 */.toShort()) +n.setItab((this.sheetNum + 1).toShort()) +val loc = ExcelTools +.formatLocation(intArrayOf(this.minRow, this.minCol, this.maxRow - 1, this.maxCol - 1), false, false) +val s = Stack() +s.push(PtgRef.createPtgRefFromString(this.sheetName + "!" ++ loc, n)) +n.expression = s +} +catch (e:Exception) { + +} + +} +} + +/** + * remove the _FILTER_DATABASE name (necessary for AutoFilters) for this sheet + */ + private fun removeFilterDatabase() { +val names = this.builtInNames +var n:Name? = null +try +{ +var i = 0 +while (i < names.size && n == null) +{ +if ((names[i] as Name) +.builtInType == Name._FILTER_DATABASE) +{ +n = names[i] as Name +this.workBook!!.removeName(n) +break +} +i++ +} +} +catch (e:Exception) {} + +} + +/** + * Set the print area or titles for this worksheet. + */ + fun setPrintArea(printarea:String, type:Byte) { +if (type != Name.PRINT_TITLES) +{// can have multiple print title refs +val n = this.getPrintAreaNameRec(type) +if (n != null) +{ // TODO: should check if same SHEET -- look at! +val s = n.expression +for (x in s!!.indices) +{ +val p = s[x] as Ptg +if (p is PtgRef) +{ +val ptg = PtgRef.createPtgRefFromString(printarea, n) +s.removeAt(x) +s.add(x, ptg) +} +} +return +} +} + // create the name + try +{ +val t:String +if (type == Name.PRINT_AREA) +{ +t = "PRINT_AREA" +} +else +{ +t = "PRINT_TITLES" +} +val n = Name(this.workBook, "Built-in: $t") +n.setBuiltIn(type) +val xref = this.workBook!!.getExternSheet(true)!! +.insertLocation(this.sheetNum, this.sheetNum) +n.setExternsheetRef(xref) +n.updateSheetReferences(this) +n.setSheet(this) +n.setIxals(this.sheetNum.toShort()) +n.setItab((this.sheetNum + 1).toShort()) +val s = Stack() +val p = PtgRef.createPtgRefFromString(printarea, n) +s.push(p) +n.expression = s +} +catch (e:Exception) { +Logger.logErr("Error setting print area in boundsheet: $e") +} + +} + +/** + * adds a merged cell to this sheet + * + * @return + */ + fun addMergedCellRec():Mergedcells { +val mec = Mergedcells.prototype as Mergedcells? +mec!!.setSheet(this) +this.streamer!!.addRecordAt(mec, this.sheetRecs.size - 1) +this.addMergedCellsRec(mec) +return mec +} + +/** + * return truth of "has merged cells" + * + * @return + */ + fun hasMergedCells():Boolean { +return mc.size > 0 +} + +/** + * get the name of the sheet + */ + override fun toString():String { +return sheetName +} + +/** + * initialize the SheetImpl with data from + * the byte array. + */ + override fun init() { +super.init() +val lt = ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), this +.getByteAt(2), this.getByteAt(3)) + + // this is the index used by the BOF's Sheet to associate + // the record + lbPlyPos = lt.toLong() +sheetType = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) +if (DEBUGLEVEL > 9) +{ +Logger.logInfo("Sheet grbit: $sheetType") +Logger.logInfo(" lbplypos: $lbPlyPos") +} +cch = this.getByteAt(6) +grbitChr = this.getByteAt(7) +val namebytes = this.getBytesAt(8, this.length - 12) +try +{ +if (grbitChr.toInt() == 0x1) +{ +sheetname = String(namebytes!!, +WorkBookFactory.UNICODEENCODING) +} +else +{ +sheetname = String(namebytes!!, +WorkBookFactory.DEFAULTENCODING) +} +} +catch (e:UnsupportedEncodingException) { +Logger.logInfo("Boundsheet.init() Unsupported Encoding error: $e") +} + +if (DEBUGLEVEL > 9) +Logger.logInfo("Sheet name: $sheetname") +ooxmlObjects = ArrayList() // possible that boundsheet is created + // by readObject and therefore + // ooxmlObjects will not be set + } + +/** + * prior to serializing the worksheet, + * we need to initialize the records which belong to this sheet + * instance. + */ + override fun setLocalRecs() { +localrecs = CompatibleVector() + +val newSheetRecs = this.assembleSheetRecs() + +val shtr = newSheetRecs.iterator() +while (shtr.hasNext()) +{ +try +{ +val x = shtr.next() as XLSRecord +x.getData() +if (x is Labelsst) +{ // put the String in the label +x.initUnsharedString() +} +localrecs!!.add(x) +} +catch (e:Exception) { +Logger.logWarn("Setting Boundsheet records problem: $e") +} + +} + // add the charts to the boundsheet, as they are stored in + // the workbook normally. (why?) + charts.clear() +val chts = this.workBook!!.charts +for (i in chts.indices) +{ +if (chts[i].sheet == this) +{ +charts.add(chts[i]) +} +} +} + +/** + * This seems incorrect, should we not be just returning + * charts for the boundsheet in question? + * + * @return + */ + fun getCharts():List<*> { +return charts +} + +/** + * add chart to sheet-specific list of charts + * + * @param c + * @see WorkBook.addRecord + */ + fun addChart(c:Chart) { +charts.add(c) +} + +/** + * Adds a chart from a bytestream, keeps the title intact. + * + * @param inbytes + * @return + */ + fun addChart(inbytes:ByteArray, coords:ShortArray):Chart? { +return this.addChart(inbytes, "useDefault", coords) +} + + /* + * Inserts a serialized boundsheet into the workbook, and + * changes the name. + */ + override fun addChart(inbytes:ByteArray, NewChartName:String, coords:ShortArray):Chart? { +var destChart:Chart? = null + // Deserialize bytes + try +{ +val bais = ByteArrayInputStream(inbytes) +val bufstr = BufferedInputStream(bais) +val o = ObjectInputStream(bufstr) +destChart = o.readObject() as Chart +} +catch (e:Exception) { +Logger.logInfo("Boundsheet.addChart() failed:$e") +} + +if (destChart != null) +{ // got chart +if (NewChartName != "useDefault") +destChart.title = NewChartName // set new name + // why do we need this??? shouldn't it be already set?? + // destChart.getChartFormat().setParentChart(destChart); // + // make same as WorkBook.addChart + destChart.setSheet(this) + // BUGTRACKER 2372: chart bounds are dependent upon row+col + // sizes so use coordinates (which are row/col independent) + // does it makes sense to only set h + w and NOT x and y + val origCoords = destChart.coords +coords[0] = origCoords[0] // don't set X and Y (keep to original + // row and column + coords[1] = origCoords[1] +destChart.coords = coords // but set w + h +destChart.id = this.lastObjId + 1 // 20100210 KSC: track last obj + // id per sheet ... + var localFonts:HashMap<*, *>? = null // fonts currently in workbook +if (this.getTransferFonts() != null && this.getTransferFonts()!!.size > 0) +{ // then must + // translate old + // font indexes + // to new font + // indexes + localFonts = this.workBook!!.fontRecsAsXML as HashMap<*, *> // fonts + // in + // this + // workbook + } +val recs = destChart.xlSrecs +for (i in recs.indices) +{ +val rec = recs[i] as XLSRecord +rec.workBook = wkbook +rec.setSheet(this) +if (rec.opcode == XLSConstants.MSODRAWING) +{ +wkbook!!.addChartUpdateMsodg(rec as MSODrawing, this) +continue +} +if (rec !is Bof) + // TODO: error/problem with the BOF + // record!!! + rec.init() +if (rec is Dimensions) +destChart.setDimensions(rec) +if (rec is FontBasis) +{ // 20090506 KSC: fontbasis font + // indexes link to subsequent + // text displays [added for + // BUGTRACKER 2372] + var fid = rec.fontIndex + // see if must translate old font indexes to new font + // indexes + fid = translateFontIndex(fid, localFonts) +rec.fontIndex = fid +} +if (rec is Fontx) +{ // 20080911 KSC: must handle out of + // bounds font references upon chart + // copies [JPM BugTracker 1434] + var fid = rec.ifnt +if (fid > 0) +fid = translateFontIndex(fid, localFonts) +rec.ifnt = fid +} +try +{ +(rec as GenericChartObject).parentChart = destChart +} +catch (e:ClassCastException) { // Scl, Obj and others are not + // chart objects + } + + // try{Logger.logInfo("Boundsheet Added new Chart rec:" + + // rec);}catch(Exception + // e){Logger.logWarn("Boundsheet.addChart() could not get + // String for rec: "+ rec.getCellAddress());} + } +this.wkbook!!.chartVect.add(destChart) +} +charts.add(destChart) +return destChart +} + +/** + * @param fid + * @return + */ + internal fun translateFontIndex(fid:Int, localFonts:HashMap<*, *>?):Int { +var fid = fid +if (transferFonts != null && fid - 1 < transferFonts!!.size) +{ + // must translate fid to corrent font index for current + // fonts + // translate font style and see if already present + val thisFont = transferFonts!![fid - 1] as Font +val xmlFont = "<" + thisFont.xml + "/>" +val fontNum = localFonts!!.get(xmlFont) +if (fontNum != null) +{ // then get the fontnum in this book +fid = (fontNum as Int).toInt() +} +else +{ // it's a new font for this workbook, add it in +fid = this.workBook!!.insertFont(thisFont) + 1 +localFonts[xmlFont] = Integer.valueOf(fid) +} +} +if (fid > this.workBook!!.numFonts) +{ // if fid is still + // incorrect, set to 0 + fid = 0 +} +return fid +} + +/** + * populateForTransfer is a method that takes all of the shared resources (SST, XF, Font, etc) records and + * verifies that they are populated for use in a destination workbook + */ + fun populateForTransfer() { +this.getSheetHash() +val recs = this.cells +for (i in recs.indices) +{ +if (recs[i].opcode == XLSConstants.LABELSST) +{ +val mylabel = recs[i] as Labelsst +mylabel.initUnsharedString() +} +} +transferXfs = this.workBook!!.xfrecs +for (i in transferXfs.indices) +{ +val x = transferXfs[i] as Xf +x.populateForTransfer() +} + +transferFonts = this.workBook!!.fontRecs +for (i in transferFonts!!.indices) +{ +val x = transferFonts!![i] as Font +x.getData() +} +} + +/** + * return local XF records, used for boundsheet transferral. + */ + fun getTransferXfs():List<*> { +return transferXfs +} + +/** + * return local Font records, used for boundsheet transferral. + */ + fun getTransferFonts():List<*>? { +return transferFonts +} + +/** + * @param localrecs The localrecs to set. + */ + fun setLocalRecs(l:FastAddVector) { +this.localrecs = l +} + + fun setSheetRecs(shtRecs:AbstractList<*>) { +this.SheetRecs = shtRecs +} + +/** + * Set to true to turn off checking for existing cells, conditional formats and merged ranges in order to + * accelerate adding new cells + * + * @param fastCellAdds The fastCellAdds to set. + */ + fun setFastCellAdds(fastCellAdds:Boolean) { +this.fastCellAdds = fastCellAdds +} + +/** + * scl is for zoom + * + * @return + */ + fun getScl():Scl { +if (scl == null) +{ // we needs one! +scl = Scl() +this.SheetRecs.add(this.indexOfWindow2, scl) +scl!!.setSheet(this) +} +return scl +} + +/** + * scl is for zoom + * + * @param scl + */ + fun setScl(s:Scl) { +this.scl = s +} + +/** + * Set whether to shift formula cells inclusively + * i.e. if inserting row 5, shift formula D5:D6 down or D6:D7 OR shift inclusive by D5:D7 + */ + fun setShiftRule(bShiftInclusive:Boolean) { +isShiftInclusive = bShiftInclusive +} + +/** + * Gets a list of the print related records for this sheet. + * + * @return an unmodifiable list of all printing-related records + */ + fun getPrintRecs():List<*> { +return Collections.unmodifiableList(printRecs!!) +} + +/** + * Adds a print-related record to the list of said. + */ + fun addPrintRec(record:BiffRec) { +if (printRecs == null) +printRecs = ArrayList() +printRecs!!.add(record) +} + +/** + * return the AutoFilter record for this Boundsheet, if any + * TODO: Merge with OOXML Autofilter + * + * @return + */ + fun getAutoFilters():List<*> { +return autoFilters +} + +/** + * Adds a new Note or Comment to the sheet at the desired address + * + * @param address - String cell address + * @param txt - Text of Note + * @param author - String Author of Note + * @return NoteHandle - a handle to the Note object which allows manipulation + */ + fun createNote(address:String, txt:String?, author:String):Note { +var address = address + // first check if a note is already attached to this addrss + val notes = this.notes +if (address.indexOf('!') == -1) +address = this.sheetName + "!" + address +for (i in notes.indices) +{ +val n = notes[i] as Note +if (n.cellAddressWithSheet == address) +{ +n.text = txt +n.author = author +return n +} +} + + // add required Mso/object records + val coords = ExcelTools.getRowColFromString(address) +var insertIndex = this.insertMSOObjectsForNote(coords) + + // after mso/obj/mso, now add txo/continue/continue and note + // record + val t = Txo.prototype as Txo? +t!!.setSheet(this) +this.SheetRecs.add(insertIndex++, t) +this.SheetRecs.add(insertIndex++, t.text) // add the associated + // Continues that defines + // the text + t.text!!.predecessor = t // link this continues to it's predecessor +val c = Continue.basicFormattingRunContinues +c.predecessor = t // TODO: is this correct???? +c.setSheet(this) +this.SheetRecs.add(insertIndex++, c) // and add associated formatting + // runs + try +{ +t.stringVal = txt // must do *after* adding continues +} +catch (e:IllegalArgumentException) { +Logger.logErr(e.toString()) +} + + // after (mso/obj/mso/txo/continue/continue) * n, note + // records are listed in order + insertIndex = this.getIndexOf(XLSConstants.WINDOW2) +val n = Note.getPrototype(author) as Note +n.id = this.lastObjId // same as Obj record above +n.setSheet(this) +n.setRowCol(coords[0], coords[1]) +this.SheetRecs.add(insertIndex, n) +return n +} + +/** + * Handles the MSO manipulations necessary for creating a note record + * + * + * // For each note: + * // [msodrawing + * // obj - ftNts note + * // msodrawing - attached shape + * // Txo (text object), continue, continue] x n + * // [note 1] x n + * // window 2 + * // ************************************************************************************ + * // NOTE: + * // SOME TEMPLATES HAVE [obj= ftNts, Continue, Txo, continue, continue, continue] + * // MORE INFO (get this): + * // Obj, Continue= 2nd MSO!!!, Txo, Continue, Continue, Continue= 1st MSO!!! + * // ************************************************************************************ + * + * @param coords rowcol of the note record + * @return insertion index for note + */ + private fun insertMSOObjectsForNote(coords:IntArray):Int { +var insertIndex:Int +var msodg = this.wkbook!!.msoDrawingGroup +if (msodg == null) +{ +msodg = this.wkbook!!.createMSODrawingGroup() +msodg!!.initNewMSODrawingGroup() +} + + // insert either above first NOTE record or before WINDOW2 + // and certain other XLSRECORDS + insertIndex = this.getIndexOf(XLSConstants.NOTE) +if (insertIndex == -1) + // no existing notes - find proper insert index + insertIndex = this.getIndexOf(XLSConstants.WINDOW2) +while (insertIndex - 1 > 0) +{ +val opc = (SheetRecs[insertIndex - 1] as BiffRec).opcode +if (opc == XLSConstants.MSODRAWING || opc == XLSConstants.CONTINUE) +{ +val rec:MSODrawing? +if (opc == XLSConstants.MSODRAWING) +rec = SheetRecs[insertIndex - 1] as MSODrawing +else +{ +rec = (SheetRecs[insertIndex - 1] as Continue).maskedMso +if (rec == null) +break +} +if (rec.solverContainerLength == 0) +break // solver containers must be last, apparently ... + // sigh ... + // else + // Logger.logInfo("Boundsheet.InsertMSOObjectsForNote. + // SOLVER CONTAINER ENCOUNTED"); + } +else if (opc == XLSConstants.OBJ || opc == XLSConstants.CONTINUE || opc == XLSConstants.DIMENSIONS +|| opc.toInt() == 0x866 || opc.toInt() == 0x1C2) +break +insertIndex-- +} + +var msoheader = msodg.getMsoHeaderRec(this) +var msoDrawing:MSODrawing = MSODrawing.prototype as MSODrawing? +msoDrawing.setSheet(this) +msoDrawing.workBook = this.workBook +if (msoheader == null) +{ +msoDrawing.setIsHeader() +msoheader = msoDrawing +} + + // mso record which creates a text box + msoDrawing.createCommentBox(coords[0], coords[1]) +this.SheetRecs.add(insertIndex++, msoDrawing) +msoheader.numShapes++ +msodg.addMsodrawingrec(msoDrawing) // add the new drawing rec to the + // msodrawinggroup set of recs + + // object record which defines a basic note + val obj = Obj.getBasicObjRecord(Obj.otNote.toInt(), ++this.lastObjId) // create + // a + // note + // object + this.SheetRecs.add(insertIndex++, obj) + + // now add attached text-type mso, specifying the shape has + // attached text + msoDrawing = MSODrawing.textBoxPrototype as MSODrawing +msoDrawing.setSheet(this) +this.SheetRecs.add(insertIndex++, msoDrawing) +msodg.addMsodrawingrec(msoDrawing) // add the new drawing rec to the + // msodrawinggroup set of recs + + // now update msodg + msoheader rec + wkbook!!.updateMsodrawingHeaderRec(this) // find the msodrawing header + // record and update it (using + // info from other msodrawing + // recs) + msodg.spidMax = this.wkbook!!.lastSPID + 1 +msodg.updateRecord() // given all information, generate appropriate + // bytes for the Mso rec + msodg.dirtyflag = true +return insertIndex +} + +/** + * Handles the MSO records necessary for defining a DropDown list object + * + * @return + */ + fun insertDropDownBox(colNum:Int):Int { +var insertIndex:Int +var msodg = this.wkbook!!.msoDrawingGroup +if (msodg == null) +{ +msodg = this.wkbook!!.createMSODrawingGroup() +msodg!!.initNewMSODrawingGroup() +} + + // insert either above first NOTE record or before WINDOW2 + // and certain other XLSRECORDS + insertIndex = this.getIndexOf(XLSConstants.NOTE) +if (insertIndex == -1) + // no existing notes - find proper insert index + insertIndex = this.getIndexOf(XLSConstants.WINDOW2) +while (insertIndex - 1 > 0) +{ +val opc = (SheetRecs[insertIndex - 1] as BiffRec).opcode +if (opc == XLSConstants.MSODRAWING || opc == XLSConstants.CONTINUE) +{ +val rec:MSODrawing? +if (opc == XLSConstants.MSODRAWING) +{ +rec = SheetRecs[insertIndex - 1] as MSODrawing +} +else +{ +rec = (SheetRecs[insertIndex - 1] as Continue).maskedMso +if (rec == null) +break +} +if (rec.solverContainerLength == 0) +break // solver containers must be last, apparently ... + // sigh ... + // else + // Logger.logInfo("Boundsheet.InsertMSOObjectsForNote. + // SOLVER CONTAINER ENCOUNTED"); + } +else if (opc == XLSConstants.OBJ) +{ +val rec = SheetRecs[insertIndex - 1] as Obj +if (rec.objType == Obj.otDropdownlist.toInt()) + // TODO: verify that + // drop downs are + // reused/shared in + // all cases!!!! + return rec.objId // already have one return object id +break +} +else if (opc == XLSConstants.OBJ || opc == XLSConstants.CONTINUE || opc == XLSConstants.DIMENSIONS +|| opc.toInt() == 0x866 || opc.toInt() == 0x1C2) +{ +break +} +insertIndex-- +} + +var msoheader = msodg.getMsoHeaderRec(this) +val msoDrawing = MSODrawing.prototype as MSODrawing? +msoDrawing!!.setSheet(this) +msoDrawing.workBook = this.workBook +if (msoheader == null) +{ +msoDrawing.setIsHeader() +msoheader = msoDrawing +} + +msoDrawing.createDropDownListStyle(colNum) // create the records + // necessary to define the + // dropdown box symbol at + // the desired column + + // object record which defines a basic dropdown list + val obj = Obj.getBasicObjRecord(Obj.otDropdownlist.toInt(), ++this.lastObjId) // create + // a + // drop-down + // object + // record + // for + // each + val objID = obj.objId + + // insert new mso + obj records into sheet + this.SheetRecs.add(insertIndex++, msoDrawing) +this.SheetRecs.add(insertIndex++, obj) + + // now update msodg + msoheader rec + msoheader.numShapes++ +msodg.addMsodrawingrec(msoDrawing) // add the new drawing rec to the + // msodrawinggroup set of recs + wkbook!!.updateMsodrawingHeaderRec(this) // find the msodrawing header + // record and update it (using + // info from other msodrawing + // recs) + msodg.spidMax = this.wkbook!!.lastSPID + 1 +msodg.updateRecord() // given all information, generate appropriate + // bytes + msodg.dirtyflag = true + +return objID +} + +/** + * Adds a new Note or Comment to the sheet at the desired address + * with Formatting (Font) information + * + * @param address - String cell address + * @param txt - Unicode string reprentation of the note, including formatting + * @param author - String Author of Note + * @return NoteHandle - a handle to the Note object which allows manipulation + */ + fun createNote(address:String, txt:Unicodestring, author:String):Note { +val nh = this.createNote(address, txt.stringVal, author) + // TODO: deal with formats - incorporate into Txo/Continues + // -- for now they are just stored as-is, no modification + // allowed + nh.formattingRuns = txt.formattingRuns +return nh +} + +/** + * removes the desired note from the sheet + * + * @param n + */ + fun removeNote(n:Note) { +val id = n.id +var idx = this.getIndexOf(XLSConstants.OBJ) +if (idx == -1) +return // should't! +while (idx < this.SheetRecs.size) +{ +if ((this.SheetRecs[idx] as BiffRec).opcode == XLSConstants.OBJ) +{ +val o = this.SheetRecs[idx] as Obj + // if it's of type Note + has the same id, this is it + if (o.objType == 0x19 && o.objId == id) +{ // got it! + // apparently sometimes you don't find the mso/obj/mso + // combo, so check + if ((this.SheetRecs[idx - 1] as BiffRec) +.opcode == XLSConstants.MSODRAWING) +{ +idx-- +break +} +else if ((this.SheetRecs[idx + 1] as BiffRec) +.opcode == XLSConstants.CONTINUE && (this.SheetRecs[idx + 1] as Continue).maskedMso != null) +{ + // idx++; + break +} +} +} +idx++ +} + // usual format= mso/obj/mso/txo/continue/continue but can + // also be: + // obj/continue (mso)/txo/continue/continue/continue (mso) + var objidx = 0 +var msoidx = 0 +var maskedMso = true // handle continues masking mso's +while (idx < this.SheetRecs.size) +{ +var mso:MSODrawing? = null +val rec = this.SheetRecs[idx] as BiffRec +if (rec.opcode == XLSConstants.OBJ) +objidx++ +else if (rec.opcode == XLSConstants.MSODRAWING) +{ +mso = rec as MSODrawing +maskedMso = false +if (mso.getShapeType() == MSODrawingConstants.msosptTextBox) +msoidx++ +else if (rec.isShape) + // it's not a text box or + // the associated text + // "oddball" mso, so + // break (Another test: + // SPID==0??) + break +} +else if (rec.opcode == XLSConstants.CONTINUE && maskedMso) +{ +mso = (rec as Continue).maskedMso +if (mso!!.getShapeType() == MSODrawingConstants.msosptTextBox) +msoidx++ +else if ((rec as MSODrawing).isShape) + // it's not a text box or + // the associated text + // "oddball" mso, so + // break (Another test: + // SPID==0??) + break +} +else if (rec.opcode == XLSConstants.NOTE) +break +if (objidx > 1 || msoidx > 1) + // reached the next set of + // note-associated recs, so get out + break +this.SheetRecs.removeAt(idx) // otherwise, ok to delete +if (mso != null && mso.isShape) +{// if removed an mso, must update + // msodg + val msodg = this.wkbook!!.msoDrawingGroup +msodg!!.removeMsodrawingrec(mso, this, true) +} +} + // now remove the actual note record + idx = this.getIndexOf(XLSConstants.NOTE) +while (idx < this.SheetRecs.size && (this.SheetRecs[idx] as BiffRec).opcode == XLSConstants.NOTE) +{ +if (this.SheetRecs[idx] == n) +{ +this.SheetRecs.removeAt(idx) +break // we're done +} +idx++ +} +} + +/** + * Adds a new AutoFilter to the specified column + * + * @param int column - 0-based column number + * @return AutoFilterHandle Handle to the new AutoFilter + */ + fun addAutoFilter(column:Int):AutoFilter { + // if there are no existing AutoFilters on the sheet, + // then must add a mso/obj pair for each column on the sheet + // to define dropdown box next to each column + // also must add built-in name _FILTERDATABASE + + // must add a mystery XlSRecord with opcode== 0x9D -- cannot + // find any information about this opcode + if (this.autoFilters == null || this.autoFilters.size == 0) +{ + // add _FILTERDATABASE Name + this.addFilterDatabase() + + /* + * 20100216 KSC: WHAT ARE THESE RECORD??? They are necessary + * for new AutoFilter's + */ + var zz = getIndexOf(XLSConstants.COLINFO) +if (zz == -1) +zz = getIndexOf(XLSConstants.DEFCOLWIDTH) + 1 +else +{ +while ((this.SheetRecs[zz] as BiffRec) +.opcode == XLSConstants.COLINFO) +zz++ +} + // insert after COLINFOs or DefColWidth + var rec = XLSRecord() +rec.opcode = 155.toShort() // no data for this record +rec.setData(byteArrayOf()) +this.SheetRecs.add(zz++, rec) +rec = XLSRecord() +rec.opcode = 157.toShort() // this has SOMETHING to do with # + // columns ... + rec.setData(byteArrayOf(this.maxCol.toByte(), 0)) +this.SheetRecs.add(zz, rec) + + // add required Mso/object records + var insertIndex:Int +var msodg = this.wkbook!!.msoDrawingGroup +if (msodg != null) +{ // already have drawing records; just add to + // records + update msodg + insertIndex = this.getIndexOf(XLSConstants.MSODRAWINGSELECTION) +if (insertIndex < 0) +insertIndex = this.getIndexOf(XLSConstants.WINDOW2) +} +else +{ // No images present in workbook, must add appropriate + // records + msodg = this.wkbook!!.createMSODrawingGroup() +msodg!!.initNewMSODrawingGroup() // generate and add required + // records for drawing records + // insertion point for new msodrawing rec + insertIndex = getIndexOf(XLSConstants.DIMENSIONS) + 1 +} + +var msoheader = msodg.getMsoHeaderRec(this) + + // Must add for each column + for (i in 0 until this.realMaxCol) +{ +try +{ +if (this.getCellsByCol(i).size == 0) +break +} +catch (e:CellNotFoundException) { +break +} + + // Colinfo ci= (Colinfo) this.colinfos.get(i); + // short j= (short) ci.getColFirst(); // column number' + val j = i.toShort() +val msoDrawing = MSODrawing.prototype as MSODrawing? +msoDrawing!!.workBook = this.wkbook +msoDrawing.setSheet(this) +if (msoheader == null) +{ +msoDrawing.setIsHeader() +msoheader = msoDrawing +} + +msoDrawing.createDropDownListStyle(j.toInt()) // create the records + // necessary to define + // the dropdown box + // symbol + + // object record which defines a basic dropdown list + val obj = Obj +.getBasicObjRecord(Obj.otDropdownlist.toInt(), ++this.lastObjId) // create + // a + // drop-down + // object + // record + // for + // each + + // insert new mso + obj records into sheet + this.SheetRecs.add(insertIndex++, msoDrawing) +this.SheetRecs.add(insertIndex++, obj) + + // now update msodg + msoheader rec + msoheader.numShapes++ +msodg.addMsodrawingrec(msoDrawing) // add the new drawing rec + // to the msodrawinggroup + // set of recs + wkbook!!.updateMsodrawingHeaderRec(this) // find the msodrawing + // header record and + // update it (using info + // from other msodrawing + // recs) + msodg.spidMax = this.wkbook!!.lastSPID + 1 +msodg.updateRecord() // given all information, generate + // appropriate bytes + msodg.dirtyflag = true +} +} + +val af = AutoFilter.prototype as AutoFilter? +af!!.setSheet(this) +af.col = column +val i = getIndexOf(XLSConstants.DIMENSIONS) // insert just before DIMENSIONS record +this.SheetRecs.add(i, af) + +this.autoFilters.add(af) +return af +} + +/** + * removes all autofilters from this sheet + */ + fun removeAutoFilter() { +this.removeFilterDatabase() // remove the _FILTER_DATABASE name + // necessary for AutoFilters + var zz = getIndexOf(XLSConstants.AUTOFILTER) // remove all AutoFitler records +while (zz != -1) +{ +this.SheetRecs.removeAt(zz) +zz = getIndexOf(XLSConstants.AUTOFILTER) +} + // remove the two unknown records + zz = getIndexOf(155.toShort()) +if (zz > -1) +this.SheetRecs.removeAt(zz) +zz = getIndexOf(157.toShort()) +if (zz > -1) +this.SheetRecs.removeAt(zz) + // and hows about the Mso/Obj records, huh? huh? + this.autoFilters.clear() + // finally, must set all rows to NOT hidden - I believe + // Excel does this when AutoFilters are turned off + for (i in 0 until rows.size) +rows[Integer.valueOf(i)].isHidden = false +} + +/** + * adds a sxview - pivot table lead record - and required associated records to the worksheet + *

                  other methods that add data, row, col and page fields will fill in the pivot table fields and formatting info + * + * @param ref Cell Range which identifies pivot table data range + * @param wbh WorkBookHandle + * @param sId Stream or cachid Id -- links back to SxStream set of records + * @return + */ + fun addPivotTable(ref:String, wbh:WorkBookHandle, sId:Int, tablename:String):Sxview { +this.wkbook!!.addPivotCache(ref, wbh, sId) // create the + // directory/storage for a + // pivot cache, if not + // already created + // ensure the proper directory/storage and pivot cache + // record is created + var zz = window2!!.recordIndex - 1 +while (zz > 0) +{ +if ((this.SheetRecs[zz] as BiffRec).opcode == XLSConstants.NOTE) +break +if ((this.SheetRecs[zz] as BiffRec).opcode == XLSConstants.OBJ) +break +if ((this.SheetRecs[zz] as BiffRec).opcode == XLSConstants.DIMENSIONS) +break +zz-- +} +zz++ + // minimal configuration + val sx = Sxview.prototype as Sxview? +this.SheetRecs.add(zz++, sx) +this.SheetRecs.addAll(zz, sx!!.addInitialRecords(this)) +sx.tableName = tablename +this.wkbook!!.addPivotTable(sx) // add to lookup +return sx +} + +/** + * update row filter (hidden status) by evaluating AutoFilter conditions on the sheet + *

                  Must do after autofilter updates or additions + */ + fun evaluateAutoFilters() { + // first must set all rows to NOT hidden + for (i in 0 until rows.size) +try +{ +rows[Integer.valueOf(i)].isHidden = false +} +catch (e:NullPointerException) { + // blank rows ... + } + + // now evaluate all autofilters + for (i in autoFilters.indices) +{ +(this.autoFilters.get(i) as AutoFilter).evaluate() +} +} + +/** + * returns the list of Excel 2007 objects which are external or auxillary to this sheet + * e.g printerSeettings, vmlDrawings + * + * @return + */ + fun getOOXMLObjects():List<*> { +return ooxmlObjects +} + +/** + * adds the object-specific signature of the external or auxillary Excel 2007 object + * e.g. oleObjects, vmlDrawings + * + * @param o + */ + fun addOOXMLObject(o:Any) { +ooxmlObjects.add(o) +} + + // TODO: Handle below options in Excel 2003 i.e. create + // appropriate records + // ************************************************************* + + /** + * set if row has thick bottom by default (Excel 2007-Specific) + */ + fun hasThickBottom():Boolean { +return thickBottom +} + +/** + * return true if row has thick top by default (Excel 2007-Specific) + */ + fun hasThickTop():Boolean { +return thickTop +} + +/** + * return true if rows are hidden by default (Excel 2007-Specific) + */ + fun hasZeroHeight():Boolean { +return zeroHeight +} + +/** + * return true if defaultrowheight is manually set (Excel 2007-Specific) + */ + fun hasCustomHeight():Boolean { +return customHeight +} + +/** + * set if row has thick bottom by default (Excel 2007-Specific) + */ + fun setThickBottom(b:Boolean) { +thickBottom = b +} + +/** + * set if row has thick top by default (Excel 2007-Specific) + */ + fun setThickTop(b:Boolean) { +thickTop = b +} + +/** + * set if rows are hidden by default (Excel 2007-Specific) + */ + fun setZeroHeight(b:Boolean) { +zeroHeight = b +} + +/** + * set if defaultrowheight is manually set (Excel 2007-Specific) + */ + fun setHasCustomHeight(b:Boolean) { +customHeight = b +} + +/** + * store Excel 2007 shape via Shape Name + * + * @param tca + */ + fun addOOXMLShape(tca:io.starter.formats.OOXML.TwoCellAnchor) { +if (ooxmlShapes == null) +ooxmlShapes = HashMap() +ooxmlShapes!![tca.name] = tca +} + +/** + * store Excel 2007 shape via Shape Name + * + * @param tca + */ + fun addOOXMLShape(oca:io.starter.formats.OOXML.OneCellAnchor) { +if (ooxmlShapes == null) +ooxmlShapes = HashMap() +ooxmlShapes!![oca.name] = oca +} + +/** + * Store Excel 2007 legacy drawing shapes + * + * @param vml + */ + fun addOOXMLShape(vml:Any) { +if (ooxmlShapes == null) +ooxmlShapes = HashMap() +ooxmlShapes!!["vml"] = vml // only 1 vml (=legacy drawing info) per + // sheet so just refer to it as "vml" + } + +/** + * returns a scoped named range by name string + * + * @param t + * @return + */ + fun getScopedName(nameRef:String):Name? { +val o = this.sheetNameRecs!![nameRef.toUpperCase()] ?: return null // case + // insensitive + return o as Name +} + +/** + * Add a sheet-scoped name record to the boundsheet + * + * + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this sheet, adding them here will not add them to the workbook; + * + * @param sheetNameRecs + */ + fun addLocalName(name:Name) { +if (sheetNameRecs == null) +sheetNameRecs = HashMap() +sheetNameRecs!![name.nameA] = name +} + +/** + * Remove a sheet-scoped name record from the boundsheet. + * + * + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this sheet, removing them here will not remove them completely from the workbook. + * + * + * In order to do that you will need to call book.removeName + * + * @param sheetNameRecs + */ + fun removeLocalName(name:Name) { +sheetNameRecs!!.remove(name.nameA) +} + +/** + * Get a sheet scoped name record from the boundsheet + * + * @return + */ + fun getName(name:String):Name? { +if (sheetNameRecs == null) +return null +val o = sheetNameRecs!![name.toUpperCase()] // case insensitive +return if (o != null) o as Name? else null +} + +/** + * add pritner setting record to worksheet recs + * + * @param r printer setting record (Margins, PLS) + */ + fun addMarginRecord(r:BiffRec) { +r.setSheet(this) +var i = this.getIndexOf(XLSConstants.SETUP) +val thisOpCode = r.opcode.toInt() + // iterate up from SETUP record + // desired order: + // WsBool, HeaderRec, FooterRec, HCenter, VCenter, + // LeftMargin, RightMargin, TopMargin, BottomMargin, Pls + while (i > 0) +{ +val prevOpCode = (this.sheetRecs[--i] as BiffRec) +.opcode.toInt() +if (prevOpCode == XLSConstants.VCENTER.toInt() +|| /* assume AT LEAST a VCENTER or FOOTERREC */ +prevOpCode == XLSConstants.FOOTERREC.toInt() || prevOpCode == XLSConstants.LEFT_MARGIN.toInt()) +{ +break +} +if ((prevOpCode == XLSConstants.BOTTOM_MARGIN.toInt() || prevOpCode == XLSConstants.TOP_MARGIN.toInt() +|| prevOpCode == XLSConstants.RIGHT_MARGIN.toInt()) && thisOpCode == XLSConstants.PLS.toInt()) +break +if ((prevOpCode == XLSConstants.TOP_MARGIN.toInt() || prevOpCode == XLSConstants.RIGHT_MARGIN.toInt()) && thisOpCode == XLSConstants.BOTTOM_MARGIN.toInt()) +break +if (prevOpCode == XLSConstants.RIGHT_MARGIN.toInt() && thisOpCode == XLSConstants.TOP_MARGIN.toInt()) +break +} +this.SheetRecs.add(++i, r) +} + +/** + * inserts a row and shifts all of the other rows down one + * + * + * the rownum is zero based. calling insertrow(9,true) will + * create a row containing A10, and subsequently shift rows > 9 by 1. + * + * @return the row that was just inserted + */ + private fun insertRow(rownum:Int, shiftrows:Boolean):Row? { +return insertRow(rownum, 0, WorkSheetHandle.ROW_INSERT_MULTI, shiftrows) +} + +@Throws(XmlPullParserException::class, IOException::class) +internal fun parseOOXML(bk:WorkBookHandle, sheet:WorkSheetHandle, ii:InputStream, sst:ArrayList<*>, formulas:ArrayList<*>, hyperlinks:ArrayList<*>, inlineStrs:HashMap<*, *>?) { +var inlineStrs = inlineStrs +val sfindex = formulas.size + + // try { + var r:Row? = null +var cellAddr:String? = null +var formatId = 0 +var type = "" +shExternalLinkInfo = HashMap() + +val factory = XmlPullParserFactory.newInstance() +factory.isNamespaceAware = true +val xpp = factory.newPullParser() + +xpp.setInput(ii, null) // using XML 1.0 specification +var eventType = xpp.eventType +while (eventType != XmlPullParser.END_DOCUMENT) +{ +if (eventType == XmlPullParser.START_TAG) +{ +val tnm = xpp.name +if (tnm == "sheetFormatPr") +{ // baseColWidth, customHeight + // (true if defaultRowHeight + // has been manually set), + // defaultColWidth, + // defaultRowHeight - + // optimiztion so that we + // don't have to write out + // values on each + // thickBottom - true if + // rows have a thick bottom + // by default + // thickTop - true if rows + // have a thick top by + // default + // zeroHeight - true if rows + // are hidden by default (an + // optimization) + for (i in 0 until xpp.attributeCount) +{ +val n = xpp.getAttributeName(i) +val v = xpp.getAttributeValue(i) +if (n == "thickBottom") +this.setThickBottom(true) +else if (n == "thickTop") +this.setThickTop(true) +else if (n == "zeroHeight") +this.setZeroHeight(v == "1") +else if (n == "customHeight") +this.setHasCustomHeight(v == "1") +else if (n == "defaultColWidth") +this.defaultColumnWidth = Float(v) +else if (n == "defaultRowHeight") +this.defaultRowHeight = Double(v) +} +} +else if (tnm == "sheetView") +{ // TODO: finish handling + // options + val s = SheetView.parseOOXML(xpp) +.cloneElement() as SheetView +this.sheetView = s +this.window2!! +.showGridlines = s.getAttrS("showGridlines") != "0" +if (s.getAttr("showRowColHeaders") != null) +this.window2!! +.showSheetHeaders = s +.getAttrS("showRowColHeaders") == "1" +if (s.getAttr("showZeros") != null) +this.window2!! +.showZeroValues = s.getAttrS("showZeros") == "1" +if (s.getAttr("showOutlineSymbols") != null) +this.window2!! +.showOutlineSymbols = s +.getAttrS("showOutlineSymbols") == "1" +if (s.getAttr("tabSelected") != null) +this.setSelected(s.getAttrS("tabSelected") == "1") +if (s.getAttr("zoomScale") != null) +this.getScl() +.zoom = Double(s.getAttrS("zoomScale")) +.toFloat() / 100 +} +else if (tnm == "sheetPr") +{ // sheet properties element +val sp = SheetPr.parseOOXML(xpp) +.cloneElement() as SheetPr +this.sheetPr = sp +} +else if (tnm == "dimension") +{ // ref attribute + /* + * this may not reflect actual rows/cols in sheet + * just let our normal machinery set the sheet dimensions + * String ref= xpp.getAttributeValue(0); + * int[] rc= ExcelTools.getRangeCoords(ref); + * this.updateDimensions(rc[2]-1, rc[3]); + */ + } +else if (tnm == "sheetProtection") +{ // ref + // attribute + for (i in 0 until xpp.attributeCount) +{ +val nm = xpp.getAttributeName(i) +val v = xpp.getAttributeValue(i) +if (nm == "password") +this.protectionManager.setPasswordHashed(v) +else if (nm == "sheet") +this.protectionManager +.protected = OOXMLReader.parseBoolean(v) +} +} +else if (tnm == "col") +{ // min, max, width +var min = 0 +var max = 0 +var style = 0 +var width = 0.0 +var hidden = false +for (i in 0 until xpp.attributeCount) +{ +val nm = xpp.getAttributeName(i) +val v = xpp.getAttributeValue(i) +if (nm == "min") +min = Integer.valueOf(v).toInt() +else if (nm == "max") +max = Integer.valueOf(v).toInt() +else if (nm == "width") +width = Double(v) +else if (nm == "hidden") +hidden = true +else if (nm == "style") + // customFormat? + style = Integer.valueOf(v).toInt() +} +if (max > WorkBook.MAXCOLS) +max = WorkBook.MAXCOLS - 1 +val col = sheet.addCol(min - 1, max - 1) +col.width = (width * OOXMLReader.colWFactor).toInt() +if (style > 0) +col.formatId = style + // col.setColLast(max-1); + if (hidden) +col.isHidden = true +} +else if (tnm == "row") +{ +var ht = -1 +var ixfe = 0 +var customHeight = false +for (i in 0 until xpp.attributeCount) +{ // r, v= + // row + // #+1, + // ht, + // ... + val nm = xpp.getAttributeName(i) +val v = xpp.getAttributeValue(i) +if (nm == "r") +{ +val rownum = Integer.valueOf(v).toInt() - 1 +r = this.insertRow(rownum, false) // now insertRow + // with no shift + // rows does NOT + // add a blank + // cell so no + // need to + // delete extra + // cell anymore + r!!.ixfe = this.workBook!!.defaultIxfe +} +else if (nm == "ht") +{ +ht = (Double(v) * OOXMLReader.rowHtFactor).toInt() +} +else if (nm == "s") +{ // customFormat? +ixfe = Integer.valueOf(v).toInt() +} +else if (nm == "customFormat") +{ +r!!.ixfe = ixfe +} +else if (nm == "hidden") +{ +r!!.isHidden = true +} +else if (nm == "collapsed") +{ // 20090513 KSC: + // Added + // collapsed, + // outlineLevel + // [BUGTRACKER + // 2371] + val h = r!!.isHidden // setCollapsed + // unconditionally sets + // hidden + r.isCollapsed = true +if (!h) +r.isHidden = false +} +else if (nm == "outlineLevel") +{ +r!!.outlineLevel = Integer.valueOf(v).toInt() +} +else if (nm == "customHeight") +{ +customHeight = true +} +else if (nm == "thickBot") +{ +r!!.setHasAnyThickBottomBorder(true) +} +else if (nm == "thickTop") +{ +r!!.hasAnyThickTopBorder = true +} +if (ht != -1 && customHeight) + // if customHeight is NOT + // set do not set row + // height (encountered + // in Baxter XLSM + // templates) + r!!.rowHeight = ht +} + // if customheight is NOT specified do not set row height + } +else if (tnm == "c") +{// element c child v= value +if (cellAddr != null) +{ +if (r!!.explicitFormatSet || formatId != this.workBook!! +.defaultIxfe && formatId != 0) +{ // default + // or + // not + // specified + // NOTE: + // default + // for + // OOXML + // is + // 0 + // not + // 15 + val rc = ExcelTools.getRowColFromString(cellAddr) +OOXMLReader +.sheetAdd(sheet, null, rc[0], rc[1], formatId) +} +cellAddr = null +} +formatId = 0 +type = "n" // reset for those cells that don't specify a + // type, default = number + for (i in 0 until xpp.attributeCount) +{ +val nm = xpp.getAttributeName(i) // r, s=style, t= + // type + val v = xpp.getAttributeValue(i) +if (nm == "r") +{ // cell address +cellAddr = v // save for setting later +} +else if (nm == "s") +{ +formatId = Integer.valueOf(v).toInt() // save + // for + // setting + // later + } +else if (nm == "t") +{ +type = v +} +} + // would be great if could peek at next tag to determine + // whether to add a blank cell here rather than catch it at + // end tag below + } +else if (tnm == "is") +{ // inline string child of (this.sheetName, cellAddr, "=" + v!!, si, ref, ftype, ca, Integer.valueOf(formatId)!!.toString(), "")) +type = "f" // cell will not be added below; rather, + // formula cells are processed en mass in + // parse + } +} +else if (tnm == "v") +{ +/** + * Cell Value + * handle based upon cell data type + */ + if (cellAddr != null) +{ // shouldn't be +val v = OOXMLAdapter.getNextText(xpp) + // use fast add method - uses int[] location + val rc = ExcelTools.getRowColFromString(cellAddr) +if (type == "s") +{ // shared string + // the SST has already been populated, now we just + // need to add + // Labelsst recs and hook up with the isst. + val labl = Labelsst +.getPrototype(null, bk.workBook) +labl.setIsst(Integer.valueOf(v!!).toInt()) +labl.setIxfe(formatId) +this.addRecord(labl, rc) +} +else if (type == "n") +{ +try +{ +if (v != "null") +OOXMLReader.sheetAdd(sheet, Integer +.valueOf(v!!), rc[0], rc[1], formatId) +else + // Should nepver get here + Logger.logWarn("OOXMLAdapter.parse: Unexpected null encountered at: $cellAddr") +} +catch (n:NumberFormatException) { // could be a + // double or + // float instead + // of an int + try +{ +OOXMLReader.sheetAdd(sheet, Double( +v!!), rc[0], rc[1], formatId) +} +catch (nn:NumberFormatException) { +OOXMLReader.sheetAdd(sheet, Float( +v!!), rc[0], rc[1], formatId) +} + +} + +} +else if (type == "b") +{ +val trx = v == "1" || v!!.equals("true", ignoreCase = true) +OOXMLReader.sheetAdd(sheet, java.lang.Boolean +.valueOf(trx), rc[0], rc[1], formatId) +} +else if (type == "f") +{ // grab cached value +val s = formulas[formulas.size - 1] as Array +s[8] = v +formulas[formulas.size - 1] = s +} +else if (type != "e") +{ // added handling for + // 'e' type which is a + // formula as well + // (containing an ERR + // cachedval) + OOXMLReader +.sheetAdd(sheet, v, rc[0], rc[1], formatId) +} +cellAddr = null // denote we processed this cell +} +} +else if (tnm == "mergeCell") +{ +val ref = xpp.getAttributeValue(0) +try +{ +val cr = CellRange( +this.sheetName + "!" + ref, bk) +cr.mergeCells(false) +} +catch (e:CellNotFoundException) { /* + * necessary to report + * error?? + */} + +} +else if (tnm == "conditionalFormatting") +{ +Condfmt.parseOOXML(xpp, bk, this) +} +else if (tnm == "dataValidations") +{ +Dval.parseOOXML(xpp, this) +} +else if (tnm == "autoFilter") +{ // Appears to sometimes + // work in tandem with + // dataValidtions (see + // Modeling Workbook - + // WKSHT.xlsm) + this.ooAutoFilter = io.starter.formats.OOXML.AutoFilter +.parseOOXML(xpp) as io.starter.formats.OOXML.AutoFilter +} +else if (tnm == "hyperlink") +{ +var ref = "" +var rid = "" +var desc = "" +for (i in 0 until xpp.attributeCount) +{ +if (xpp.getAttributeName(i) == "ref") +ref = xpp.getAttributeValue(i) +else if (xpp.getAttributeName(i) == "id") + // external + // ref + rid = xpp.getAttributeValue(i) +else if (xpp.getAttributeName(i) == "display") + // display + // or + // description + // text + desc = xpp.getAttributeValue(i) + // TODO: Also handle location, tooltip ... + } +hyperlinks.add(arrayOf(rid, ref, desc)) // must + // save + // hyperlink + // refernce + // cell + // and + // id + // and + // link + // to + // target + // info + // in + // .rels + // file + // External OOXML Objects controls=embedded controls, + // oleObject= embedded objects + // These external objects contain link information which + // links to id's in vmlDrawingX.vml, activeX.xml ... must + // save and reset for later use + } +else if (tnm == "pageSetup") +{ // scale orientation r:id + // ... + addExternalInfo(shExternalLinkInfo, xpp) +} +else if (tnm == "oleObject") +{ // progId shapeId r:id ... +addExternalInfo(shExternalLinkInfo, xpp) +} +else if (tnm == "control") +{ // progId shapeId r:id ... +addExternalInfo(shExternalLinkInfo, xpp) + // TODO: handle AlternateContent Machinery! + // for now, we are ignoring choice and fallback and ONLY + // extracting control element + } +else if (tnm == "AlternateContent") +{ // defines a + // mechanism for + // the storage + // of content + // which is not + // defined by + // this Office + // Open XML + // Standard, for + // example + // extensions + // developed by + // future + // software + // applications + // which + // leverage the + // Open XML + // formats + // skip, for now - may have elements + // Choice-> + // control->controlPr + // Fallback + // control + // i.e. 1st choice is a control with control settings + // if not possible, fallback is + } +else if (tnm == "Fallback") +{ +OOXMLReader.getCurrentElement(xpp) // skip as can replicate + // Choice + } +else if (tnm == "controlPr") +{ +OOXMLReader.getCurrentElement(xpp) // skip for now!! +} +else if (tnm == "extLst") +{ // skip for now!! +OOXMLReader.getCurrentElement(xpp) // skip for now!! +} /* + * else { + * if (true) + * Logger.logWarn("unprocessed XLSX sheet element: " + tnm); + * } + */ +} +else if (eventType == XmlPullParser.END_TAG) +{ +val endTag = xpp.name +if (endTag == "row" && cellAddr != null) +{ + +val rc = ExcelTools.getRowColFromString(cellAddr) + // if masking an explicit row format or if it's a unique + // format, set to new blank cell + if (r!!.explicitFormatSet || formatId != this.workBook!!.defaultIxfe && formatId != 0) +{ // default or not + // specified NOTE: + // default for OOXML + // is 0 not 15 + // (unless converted + // from XLS ((: + // if (r.myRow.getExplicitFormatSet() || (/*formatId!=15 + // && */formatId!=0 && uniqueFormat)) { //default or not + // specified NOTE: default for OOXML==0 NOT 15 + OOXMLReader +.sheetAdd(sheet, null, rc[0], rc[1], formatId) + // } else{ + // sheetAdd(sheet,null,rc[0],rc[1],formatId); + } +cellAddr = null +} +else /**/if (endTag == "worksheet") + // we're done! + break +} +eventType = xpp.next() +} +} + +/** + * parses OOXML content files given a content list cl from zip file zip + * recurses if content file has it's own content + * ************************************* + * NOTE: certain elements we do not as of yet process; we "pass-through" or store such elements along with any embedded objects associated with them + * for example, activeX objects, vbaProject.bin, etc. + * ************************************* + * + * @param bk WorkBookHandle + * @param sheet WorkSheetHandle (set if recursing) + * @param zip currently open ZipOutputStream + * @param cl ArrayList of Contents (type, filename, rId) to parse + * @param parentDir Parent Directory for relative paths in content lists + * @param formulas, hyperlinks, inlineStrs -- ArrayLists/Hashmaps stores sheet-specific info for later entry + * @throws CellNotFoundException + * @throws XmlPullParserException + */ + @Throws(XmlPullParserException::class, CellNotFoundException::class) + fun parseSheetElements(bk:WorkBookHandle, zip:ZipFile, cl:ArrayList<*>, parentDir:String, externalDir:String, formulas:ArrayList<*>, hyperlinks:ArrayList<*>, inlineStrs:HashMap<*, *>, pivotTables:HashMap) { +var p:String +var target:ZipEntry? + +try +{ +for (i in cl.indices) +{ +var c = cl[i] as Array +val ooxmlElement = c[0] + + // if(DEBUG) + // Logger.logInfo("OOXMLReader.parse: " + ooxmlElement + ":" + // + c[1] + ":" + c[2]); + + p = StringTool.getPath(c[1]) +p = OOXMLReader.parsePathForZip(p, parentDir) +if (ooxmlElement != "hyperlink") + // if it's a hyperlink + // reference, don't + // strip path info :) + c[1] = StringTool.stripPath(c[1]) +val f = c[1] +val rId = c[2] + +if (ooxmlElement == "drawing") +{ // images, charts + // parse drawing rels to obtain image file names and chart + // xml files + target = OOXMLReader.getEntry(zip, p + "_rels/" ++ f.substring(f.lastIndexOf("/") + 1) + ".rels") +var drawingFiles:ArrayList<*>? = null +if (target != null) + // first retrieve enbedded content in + // .rels (images, charts ...) + drawingFiles = OOXMLReader.parseRels(OOXMLReader +.wrapInputStream(OOXMLReader.wrapInputStream(zip +.getInputStream(target)))) // obtain a + // list of + // image + // file + // references + // for use + // in later + // parsing + target = OOXMLReader.getEntry(zip, p + f) // now get + // drawingml + // file and + // process it + parseDrawingXML(bk, drawingFiles, OOXMLReader +.wrapInputStream(zip +.getInputStream(target!!)), zip, p, externalDir) +} +else if (ooxmlElement == "vmldrawing") +{ // legacy + // drawing + // elements + target = OOXMLReader.getEntry(zip, p + f) +val vml = parseLegacyDrawingXML(bk, OOXMLReader +.wrapInputStream(zip.getInputStream(target!!))) +target = OOXMLReader.getEntry(zip, p + "_rels/" // get + + // external + // objects + // linked to + // the vml + // by + // parsing + // it's rels + + f.substring(f.lastIndexOf("/") + 1) + ".rels") +if (target != null) +{ +val embeds = OOXMLReader +.storeEmbeds(zip, target, p, externalDir) // passes + // thru + // embedded + // objects + this.addOOXMLShape(arrayOf(vml, embeds)) +} +else +this.addOOXMLShape(vml) + /**/ + } +else if (ooxmlElement == "hyperlink") +{ // hyperlinks +c = cl[i] as Array // don't strip path +for (j in hyperlinks.indices) +{ +if (rId == (hyperlinks[j] as Array)[0]) +{ +val h = hyperlinks[j] as Array +try +{ // target= cl[2], ref= h[1], desc= h[2] +bk.getWorkSheet(this.sheetName)!! +.getCell(h[1]).setURL(rId, h[2], "") // TODO: + // hyperlink + // text + // mark + } +catch (e:Exception) { +Logger.logErr("OOXMLAdapter.parse: failed setting hyperlink to cell " ++ h[1] + ":" + e.toString()) +} + +break +} +} +} +else if (OOXMLReader.parsePivotTables && ooxmlElement == "pivotTable") +{ // sheet-parent + /* + * TODO: Do we really need to get rels ???? + * // must lookup cacheid from rid of + * pivotCacheDefinitionX.xml in + * pivotTableDefinitionX.xml.rels + * target= OOXMLReader.getEntry(zip,p + "_rels/" + + * f.substring(f.lastIndexOf("/")+1)+".rels"); + * ArrayList ptrels= + * parseRels(wrapInputStream(wrapInputStream(zip. + * getInputStream(target)))); + * if (ptrels.size() > 1) { // what could this be? + * Logger. + * logWarn("OOXMLReader.parse: Unknown Pivot Table Association: " + * + ptrels.get(1)); + * } + * String pcd= ((String[])ptrels.get(0))[1]; + * pcd= pcd.substring(pcd.lastIndexOf("/")+1); + * Object cacheid= null; + * for (int z= 0; z < pivotCaches.size(); z++) { + * Object[] o= (Object[]) pivotCaches.get(z); + * if (pcd.equals(o[0])) { + * cacheid= o[1]; + * break; + * } + * } + * + * target = getEntry(zip,p + f); + * PivotTableDefinition.parseOOXML(bk, /*cacheid, * /this, + * wrapInputStream(zip.getInputStream(target))); + */ + try +{ // SAVE FOR LATER INPUT -- must do after all sheets + // are input ... + pivotTables.put(p + f, bk +.getWorkSheet(this.sheetName)) +} +catch (we:WorkSheetNotFoundException) {} + +} +else if (ooxmlElement == "comments") +{ // parse comments + // or notes + target = OOXMLReader.getEntry(zip, p + f) +parseCommentsXML(bk, OOXMLReader +.wrapInputStream(zip.getInputStream(target!!))) + + // Below are elements we do not as of yet handle + } +else if ((ooxmlElement == "macro" +|| ooxmlElement == "activeX" +|| ooxmlElement == "table" +|| ooxmlElement == "vdependencies" +|| ooxmlElement == "oleObject" +|| ooxmlElement == "image" +|| ooxmlElement == "printerSettings")) +{ + +var attrs = "" +if ((shExternalLinkInfo != null && shExternalLinkInfo!!.get(rId) != null)) +attrs = shExternalLinkInfo!!.get(rId) +OOXMLReader +.handleSheetPassThroughs(zip, bk, this, p, externalDir, c, attrs) + // OOXMLReader.handlePassThroughs(zip, bk, this, p, c); // + // pass-through this file and any embedded objects as well + } +else +{ // unknown type +Logger.logWarn(("OOXMLAdapter.parse: XLSX Option Not yet Implemented " + ooxmlElement)) +} +} +} +catch (e:IOException) { +Logger.logErr("OOXMLAdapter.parse failed: " + e.toString()) +} + +shExternalLinkInfo = null +} + +/** + * NOTE: commentsX.xml also needs legacy drawing info (vmlDrawingX.vml) + * to define the text box itself including position and size, plus the vml elements + * also define whether the note is hidden + */ + internal fun parseCommentsXML(bk:WorkBookHandle, ii:InputStream) { +try +{ +val factory = XmlPullParserFactory.newInstance() +factory.setNamespaceAware(true) +val xpp = factory.newPullParser() +xpp.setInput(ii, null) // using XML 1.0 specification +var eventType = xpp.getEventType() + +val lastTag = java.util.Stack() // keep track of + // element + // hierarchy + + val authors = ArrayList() +var addr = "" +var authId = -1 +var comment:Unicodestring? = null + // ignore for now: phonetic properties (phoneticPr), + // phonetic run (rPh) + while (eventType != XmlPullParser.END_DOCUMENT) +{ +if (eventType == XmlPullParser.START_TAG) +{ +val tnm = xpp.getName() +if (tnm == "author") +{ +authors.add(OOXMLReader.getNextText(xpp)) +} +else if (tnm == "comment") +{ +if (comment != null && "" != addr) +{ +this.createNote(addr, comment!!, authors +.get(authId) as String) +} +addr = xpp.getAttributeValue("", "ref") +authId = Integer +.valueOf(xpp.getAttributeValue("", "authorId")) +.toInt() +comment = null +} +else if (tnm == "text") +{ + // read in text element + lastTag.push(tnm) +val t = Text.parseOOXML(xpp, lastTag, bk) + // don't reset state vars as can there can be more + comment = t.commentWithFormatting +} +} +else if (eventType == XmlPullParser.END_TAG) +{} +eventType = xpp.next() +} +if ("" != comment!!.toString() && "" != addr) +{ +this.createNote(addr, comment!!, authors.get(authId) as String) +} +} +catch (e:Exception) { +Logger.logErr("OOXMLAdapter.parseCommentsXML: " + e.toString()) +} + +return +} + +/** + * parse vml - legacy drawing info e.g. mso shapes and lines + note textboxes + *

                  for now, legacy drawing info is just stored and not parsed into BIFF8 structures + *

                  i.e. store everything but note textboxes at this time; intention is later on + * to store all mso shapes and objects in BIFF8 records + *

                  this vml is stored at the sheet level in the boundsheet's OOXMLShapes storage + *

                  Notes textboxes are being created upon writeLegacyDrawingXML + * + * @param bk + * @param sheet + * @param ii + * @return StringBuffer rep of saved vml + */ + internal fun parseLegacyDrawingXML(bk:WorkBookHandle, ii:InputStream):StringBuffer { +/** + * more info: + * The Shape element is the basic building block of VML. A shape may exist on its own or within a Group + * element. Shape defines many attributes and sub-elements that control the look and behavior of the shape. A + * shape must define at least a Path and size (Width, Height). VML 1 also uses properties of the CSS2 style + * attribute to specify positioning and sizing + * + * The ShapeType element defines a definition, or template, for a shape. Such a template is “instantiated” by + * creating a Shape element that references the ShapeType. The shape can override any value specified by its + * ShapeType, or define attributes and elements the ShapeType does not provide. A ShapeType may not + * reference another ShapeType. + * The attributes and elements a ShapeType uses are identical to those of the Shape element, with these + * exceptions: ShapeType may not use the Type element, Visibility is always hidden. + * + * Regarding Notes: + * The visible box shown for comments attached to cells is persisted using VML. The comment contents are + * stored separately as part of SpreadsheetML. + */ + val savedVml = StringBuffer() +try +{ +val factory = XmlPullParserFactory.newInstance() +factory.setNamespaceAware(true) +val xpp = factory.newPullParser() +xpp.setInput(ii, null) // using XML 1.0 specification +var eventType = xpp.getEventType() + // NOTE: since vml controls visibility (hidden or shown), + // text box size, etc., notes are created upon VML parsing + // and edited here for the actual text and formats ... ms's + // legacy drawing stuff makes for alot of convoluted + // processing ((; + val nhs = FastAddVector() +run({ val anhs = bk.getWorkSheet(this.sheetName)!! +.commentHandles +for (i in anhs.indices) +{ +nhs.add(anhs[i]) +} }) +while (eventType != XmlPullParser.END_DOCUMENT) +{ +if (eventType == XmlPullParser.START_TAG) +{ +var tnm = xpp.getName() +if (tnm == "shapelayout") +{ + // just store + savedVml.append(OOXMLReader.getCurrentElement(xpp)) +} +else if (tnm == "shapetype") +{ + // if spt==202 (shape-type=text box) id="_x0000_t202" + // then it's note textbox + if (xpp.getAttributeValue("urn:schemas-microsoft-com:office:office", "spt") != "202") +{// if it's not a note textbox + // shapetype, store it + savedVml.append(OOXMLReader.getCurrentElement(xpp)) +} +else + // ignore element - will be rebuilt upon write + OOXMLReader.getCurrentElement(xpp) +} +else if (tnm == "shape") +{ // this is basic + // several types: can contain images, shapes and notes + if (!xpp.getAttributeValue("", "type") +.endsWith("_x0000_t202")) +{// if it's not a note + // textbox, save it + // if type="#_x0000_t202" it's a note textbox + savedVml.append(OOXMLReader.getCurrentElement(xpp)) +} +else +{// add note here, text and formatting will be + // input upon Comments parse; + var r = -1 +var c = -1 +var visible = false +val bounds = ShortArray(8) +while (eventType != XmlPullParser.END_DOCUMENT) +{ +if (eventType == XmlPullParser.START_TAG) +{ +tnm = xpp.getName() // Anchor +if (tnm == "Row") +r = Integer +.valueOf(OOXMLReader +.getNextText(xpp)!!) +.toInt() +else if (tnm == "Column") +c = Integer +.valueOf(OOXMLReader +.getNextText(xpp)!!) +.toInt() +else if (tnm == "Visible") +visible = true +else if (tnm == "Anchor") +{ + // get a string rep of the bounds + var sbounds = OOXMLReader +.getNextText(xpp) + // prepare for parsing + sbounds = sbounds!! +.replace(("[^0-9,]+").toRegex(), "") +val s = sbounds!!.split((",").toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() +for (i in 0..7) +{ +bounds[i] = java.lang.Short.valueOf(s[i]) +.toShort() +} +} +} +else if (eventType == XmlPullParser.END_TAG) +{ +if (xpp.getName() == "shape") +break +} +eventType = xpp.next() +} +val addr = ExcelTools +.formatLocation(intArrayOf(r, c)) +for (i in nhs.indices) +{ +val nh = nhs.get(i) as CommentHandle +if (nh.address!!.endsWith(addr)) +{ +if (visible) +nh.show() +nh.textBoxBounds = bounds +nhs.removeAt(i) +break +} +} +} +} +else if (tnm == "xml") +{ // ignore :) +} +else if (tnm == "imagedata") +{} +else +{ // just store + // -- + savedVml.append(OOXMLReader.getCurrentElement(xpp)) +} +} +else if (eventType == XmlPullParser.END_TAG) +{} +eventType = xpp.next() +} +} +catch (e:Exception) { +Logger.logErr(("OOXMLAdapter.parseLegacyDrawingXML: " + e.toString())) +} + +return savedVml +} + +/** + * given drawingML drawing.xml inputstream, parse each twoCellAnchor tag into appropriate image or chart and insert into sheet + * + * @param bk + * @param sheet + * @param imgFiles list of image or chart files (referenced in drawing.xml via rId) + * @param ii InputStream + * @param zip Current Open ZipOutputStream + */ + internal fun parseDrawingXML(bk:WorkBookHandle, drawingFiles:ArrayList<*>?, ii:InputStream, zip:ZipFile, parentDir:String, externalDir:String) { +try +{ +val lastTag = java.util.Stack() // keep track of + // element + // hierarchy + + val factory = XmlPullParserFactory.newInstance() +factory.setNamespaceAware(true) +val xpp = factory.newPullParser() + +xpp.setInput(ii, null) // using XML 1.0 specification +var eventType = xpp.getEventType() +while (eventType != XmlPullParser.END_DOCUMENT) +{ +if (eventType == XmlPullParser.START_TAG) +{ +val tnm = xpp.getName() +if (tnm == "twoCellAnchor") +{ // beginning of DrawingML + // for a single image or + // chart + lastTag.push(tnm) + // TODO: handle group shapes which combine images, + // shapes and/or charts + // ******************************************************** + val t = TwoCellAnchor +.parseOOXML(xpp, lastTag, bk).cloneElement() as TwoCellAnchor +if (t.hasImage()) +{ +val s = t.embed // rid of embedded object +if (s!!.indexOf("rId") == 0) +{ // should! +val imgFile = OOXMLReader +.parsePathForZip(OOXMLReader +.getFilename(drawingFiles!!, s)!!, parentDir) +val img = ZipEntry(imgFile) +val `is` = BufferedInputStream( +zip.getInputStream(img)) +val im = ImageHandle(`is`, this) +this.insertImage(im) +im.name = t.name +im.shapeName = t.descr +im.bounds = TwoCellAnchor +.convertBoundsToBIFF8(this, t +.bounds) // must do after + // insert + im.spPr = t.sppr // set image shape + // properties + im.setEditMovement(t.editAs) // specify + // how to + // resize or + // move + im.update() // update underlying image record + // with set data + } +} +else if (t.hasChart()) +{ +val s = t.chartRId +if (s!!.indexOf("rId") == 0) +{ // should! +var chartfilename = OOXMLReader +.getFilename(drawingFiles!!, s) +var name = t.name +if (name == null || name == "null") +{ +name = "Untitled Chart" +} +val ch = bk.createChart(name, bk +.getWorkSheet(this.sheetName)) +ch!!.relativeBounds = TwoCellAnchor +.convertBoundsToBIFF8(this, t +.bounds) // must do after + // insert + ch!!.setEditMovement(t.editAs) // specify + // how to + // resize or + // move + ch!!.ooxmlName = name +chartfilename = OOXMLReader +.parsePathForZip(chartfilename!!, parentDir) +val chFile = ZipEntry(chartfilename!!) + // must account for default chart settings: set + // fontx recs to default font for this workbook + // ... + ch!!.resetFonts() // reset all fonts for the + // chart + ch!!.removeLegend() // not all charts have + // legends! + val ps = chartfilename!!.lastIndexOf("/") + 1 +val rels = OOXMLReader +.getEntry(zip, (chartfilename!! +.substring(0, ps) + "_rels/" ++ chartfilename!!.substring(ps) ++ ".rels")) +if (rels != null) +{ // chart file has embeds - + // usually drawing ml which + // defines userShapes + // xxx TODO: REFACTOR to get these specifics + // out + val chartEmbeds = OOXMLReader +.parseRels(OOXMLReader +.wrapInputStream(zip +.getInputStream(rels!!))) +for (i in chartEmbeds.indices) +{ +val dr = chartEmbeds +.get(i) as Array +if (dr[0] == "userShape") +{ // should! +dr[1] = dr[1].substring((dr[1] +.lastIndexOf("/") + 1)) +ch!!.addChartEmbed(arrayOf(dr[0], externalDir + dr[1])) +OOXMLReader +.passThrough(zip, (parentDir + dr[1]), (externalDir + dr[1])) // Store + // Embedded + // Object + // on + // disk + // for + // later + // retrieval + } +else if (dr[0] == "image") +{ +var parentp = OOXMLReader +.parsePathForZip(dr[1], parentDir) +parentp = parentp +.substring(0, (parentp +.lastIndexOf("/") + 1)) +dr[1] = dr[1].substring((dr[1] +.lastIndexOf("/") + 1)) +ch!!.addChartEmbed(arrayOf(dr[0], externalDir + dr[1])) +OOXMLReader.passThrough(zip, (parentp + dr[1]), (externalDir + dr[1])) // save + // the + // original + // target + // file + // for + // later + // re-packaging + } +else if (dr[0] == "themeOverride") +{ +var parentp = OOXMLReader +.parsePathForZip(dr[1], parentDir) +parentp = parentp +.substring(0, (parentp +.lastIndexOf("/") + 1)) +dr[1] = dr[1].substring((dr[1] +.lastIndexOf("/") + 1)) +ch!!.addChartEmbed(arrayOf(dr[0], externalDir + dr[1])) +val target = OOXMLAdapter +.getEntry(zip, (parentp + dr[1])) +bk.workBook!!.theme!! +.parseOOXML(bk, OOXMLAdapter +.wrapInputStream(zip +.getInputStream(target))) +} +else +{ +Logger.logWarn(("OOXMLAdapter.parseDrawingML: unknown chart embed " + dr[0])) +} +} +} + // do after parsing rels in case there is + // override theme colors ... + ch!!.parseOOXML(OOXMLReader.wrapInputStream(zip +.getInputStream(chFile))) +} +} +else if (t.hasShape()) +{ +this.addOOXMLShape(t) // just store shape for later + // output since prev. + // versions do not handle + // shapes + if (t.embed != null) +{ // if this shape has + // embedded objects such + // as images + val imgFile = OOXMLReader +.parsePathForZip(OOXMLReader +.getFilename(drawingFiles!!, t +.embed)!!, parentDir) // look + // up + // embedded + // rid + // in + // content + // list + // to + // get + // filename + t.embedFilename = imgFile // save embedded + // filename for + // later + // retrieval + OOXMLReader +.passThrough(zip, imgFile, (externalDir + imgFile)) // Store Embedded + // Object on disk + // for later + // retrieval + } +} +else +{ // TESTING! +Logger.logErr("OOXMLAdapter.parseDrawingXML: Unknown twoCellAnchor type") +} +} +else if (tnm == "oneCellAnchor") +{ // unclear if this + // can be root + // of charts and + // images as + // well as + // shapes + lastTag.push(tnm) +val oca = OneCellAnchor +.parseOOXML(xpp, lastTag, bk).cloneElement() as OneCellAnchor +if (oca.hasImage()) +{ +val s = oca.embed // rid of embedded object +if (s!!.indexOf("rId") == 0) +{ // should! +val imgFile = OOXMLReader +.parsePathForZip(OOXMLReader +.getFilename(drawingFiles!!, s)!!, parentDir) +val img = ZipEntry(imgFile) +val `is` = BufferedInputStream( +OOXMLReader.wrapInputStream(zip +.getInputStream(img))) +val im = ImageHandle(`is`, this) +this.insertImage(im) +im.name = oca.name +im.shapeName = oca.descr +im.bounds = oca.bounds // must do after + // insert + im.spPr = oca.sppr // set image shape + // properties + im.update() // update underlying image record + // with set data + } +} +else if (oca.hasChart()) +{ +val s = oca.embed +if (s!!.indexOf("rId") == 0) +{ // should! +var chart = OOXMLReader +.getFilename(drawingFiles!!, s) +var name = oca.name +if (name == null || name == "null") +{ +name = "Untitled Chart" +} +val ch = bk.createChart(name, bk +.getWorkSheet(this.sheetName)) +ch!!.relativeBounds = oca.bounds + // ch.setChartTitle(name); + chart = OOXMLReader +.parsePathForZip(chart!!, parentDir) +val chFile = ZipEntry(chart!!) +ch!!.parseOOXML(OOXMLReader.wrapInputStream(zip +.getInputStream(chFile))) +} +} +else if (oca.hasShape()) +{ +this.addOOXMLShape(oca) // just store shape for + // later output since + // prev. versions do not + // handle shapes + } +else +{ // TESTING! +Logger.logErr("OOXMLAdapter.parseDrawingXML: Unknown oneCellAnchor type") +} +} +else if (tnm == "userShapes") +{ // drawings ONTOP of + // charts = + // Reference to + // Chart Drawing + // Part + Logger.logErr("OOXMLAdapter.parseDrawingXML: USER SHAPE ENCOUNTERED") +} +} +eventType = xpp.next() +} +} +catch (e:Exception) { +Logger.logErr(("OOXMLAdapter.parseDrawingXML: failed " + e.toString())) +} + +} + +/** + * clear out object references in prep for closing workbook + */ + public override fun close() { +wkbook = null +for (info in colinfos.values) +{ +if (null != info) +info!!.close() +} +colinfos.clear() +var ii:Iterator<*> = rows.keys.iterator() +while (ii.hasNext()) +{ +val r = rows.get(ii.next()) +r.close() +} +rows.clear() + +cellsByRow = TreeMap( +CellAddressible.RowMajorComparator()) + +cellsByCol = TreeMap( +CellAddressible.ColumnMajorComparator()) + // TODO: clear recs + arrayformulas.clear() + // TODO: clear recs + transferXfs.clear() + // TODO: clear recs + transferFonts!!.clear() +imageMap!!.clear() +charts.clear() +ooxmlObjects.clear() +if (ooxmlShapes != null) +ooxmlShapes!!.clear() + +ooAutoFilter = null +mc.clear() +sheetView = null // OOXML sheet view object +sheetPr = null // OOXML sheetPr object +if (lastselection != null) +{ +lastselection!!.close() +lastselection = null +} +if (protector != null) +{ +protector!!.close() +protector = null +} + +if (sheetNameRecs != null) +{ +ii = sheetNameRecs!!.keys.iterator() +while (ii.hasNext()) +{ +val n = sheetNameRecs!!.get(ii.next()) as Name +n.close() +} +sheetNameRecs!!.clear() +} + +for (i in cond_formats!!.indices) +{ +val c = cond_formats!!.get(i) as Condfmt +c.close() +} +cond_formats!!.clear() + +for (i in autoFilters.indices) +{ +val a = autoFilters.get(i) as AutoFilter +a.close() +} +autoFilters.clear() + +if (lastCell != null) +{ +(lastCell as XLSRecord).close() +lastCell = null +} +if (lastRow != null) +{ +lastRow!!.close() +lastRow = null +} + +if (window2 != null) +{ +window2!!.close() +window2 = null +} +if (scl != null) +{ +scl!!.close() +scl = null +} +if (pane != null) +{ +pane!!.close() +pane = null +} +if (dvalRec != null) +{ +dvalRec!!.close() +dvalRec = null +} +if (header != null) +{ +header!!.close() +header = null +} +if (footer != null) +{ +footer!!.close() +footer = null +} +if (wsBool != null) +{ +wsBool!!.close() +wsBool = null +} +if (guts != null) +{ +guts!!.close() +guts = null +} +if (dimensions != null) +{ +dimensions!!.close() +dimensions = null +} +if (myBof != null) +{ +myBof!!.close() +myBof = null +} +if (myEof != null) +{ +myEof!!.close() +myEof = null +} +if (myidx != null) +{ +myidx!!.close() +myidx = null +} +for (i in printRecs!!.indices) +{ +val r = printRecs!!.get(i) as XLSRecord +r.close() +} +printRecs!!.clear() + + // clear out refs by sheet recs + for (j in SheetRecs.indices) +{ +val r = SheetRecs.get(j) as XLSRecord +r.close() +} +SheetRecs.clear() +if (localrecs != null) +localrecs!!.clear() + // col records + + } + +companion object { + +/** + * + */ + private val serialVersionUID = 8977216410574107840L + + // sheet types from grbit field offset 0 + internal val SHEET_DIALOG:Byte = 0x00 +internal val XL4_MACRO:Byte = 0x01 +internal val CHART:Byte = 0x02 +internal val VBMODULE:Byte = 0x06 + + // hidden states from grbit field offset 1 + val VISIBLE:Byte = 0x00 + val HIDDEN:Byte = 0x01 + val VERY_HIDDEN:Byte = 0x02 + +/** + * associates external reference info with the r:id of the external reference + * for instance, oleObject elements are associated with a shape Id that links back to a .vml file entry + * + * @param externalobjs + * @param xpp + */ + protected fun addExternalInfo(externalobjs:MutableMap, xpp:XmlPullParser) { + // String[] attrs= new String[xpp.getAttributeCount()-1]; + val attrs = ArrayList() +var rId = "" + // int j= 0; + for (i in 0 until xpp.getAttributeCount()) +{ +val n = xpp.getAttributeName(i) +if (n == "id") +rId = xpp.getAttributeValue(i) +else + // attrs[j++]= n+ "=\"" + xpp.getAttributeValue(i) +"\""; + attrs.add(n + "=\"" + xpp.getAttributeValue(i) + "\"") +} +var s = Arrays.asList(*attrs.toTypedArray()).toString() // 1.6 only + // Arrays.toString(attrs.toArray()); + if (s.length > 2) +{ +s = s.substring(1, s.length - 1) + // 1.6 only s= s.replace(",", ""); // only issue is embedded + // ,'s in quoted strings, lets assume not! + s = StringTool.replaceText(s, ",", "") // only issue is embedded + // ,'s in quoted strings, + // lets assume not! + } +externalobjs.put(rId, s) +} +} +}/** + * Insert an image into the WorkBook + * + * @param im + *//** + * adds a cell to the Sheet + * + * @param obj + * @param rc + * @param FORMAT_ID + * @return + */ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/ByteStreamer.java b/src/main/java/io/starter/formats/XLS/ByteStreamer.java deleted file mode 100644 index 5864428..0000000 --- a/src/main/java/io/starter/formats/XLS/ByteStreamer.java +++ /dev/null @@ -1,576 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.formats.LEO.BIGBLOCK; -import io.starter.formats.LEO.Block; -import io.starter.formats.LEO.LEOFile; -import io.starter.formats.LEO.Storage; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.FastAddVector; -import io.starter.toolkit.Logger; - -import java.io.*; -import java.util.*; - - -/** - * ByteStreamer Handles the low-level byte array streaming - * of the WorkBook. Basically a collection of XLS and methods for getting - * at their data. - * - * @see WorkBook - * @see Boundsheet - */ -public class ByteStreamer - implements Serializable, XLSConstants { - private static final long serialVersionUID = -8188652784510579406L; - AbstractList records = new FastAddVector(); - private byte[] bytes; - private String myname = ""; - boolean DEBUG = !true; - protected WorkBook workbook = null; - private OutputStream out = null; - int dlen = -1; - - - public int getRecordIndex(int opcode) { - - for (int i = 0; i < records.size(); i++) { - XLSRecord rec = (XLSRecord) records.get(i); - if (rec.getOpcode() == opcode) return i; - } - return -1; - - } - - public void initTestRecVect() { - // Useful for looking at the output vector & debugging ... THANKS, NICK! -jm - Vector testVect; - if (true) { - Logger.logInfo("TestVector on in bytestreamer.stream"); - testVect = new Vector(); - testVect.addAll(records); - } - Logger.logInfo("TESTING Recvec done."); - } - - public ByteStreamer(WorkBook bk) { - this.workbook = bk; - } - - /** - * Returns an array of all the BiffRecs in the streamer. For debug purposes, - * as there is no way to directy view the FastAddVector - * - * @return - */ - public Object[] getBiffRecords() { - return records.toArray(); - } - - // 20060601 KSC: setBiffRecords - public void setBiffRecords(Collection recs) { - if (Arrays.equals(records.toArray(), recs.toArray())) { - // already set! - return; - } - records.clear(); - records.addAll(recs); - } - - // public ByteStreamer(WorkBook b){this.book = b;} - - public void setBytes(byte[] b) { - this.bytes = b; - } - - public byte[] getBytes() { - return this.bytes; - } - - public int getRecVecSize() { - return records.size(); - } - - public String getName() { - return myname; - } - - public String getSubstreamTypeName() { - return "ByteStreamer"; - } - - public short getSubstreamType() { - return WK_WORKSHEET; - } - - /** - * get a record from the underlying array - */ - public BiffRec getRecordAt(int t, BiffRec rec) { - if (rec instanceof Boundsheet || - rec instanceof Dbcell) - return this.getRecordAt(t); - if (rec.getSheet() != null) { - Sheet bs = rec.getSheet(); - return (BiffRec) bs.getSheetRecs().get(t); - } - throw new InvalidRecordException("ERROR: ByteStreamer.getRecord() could not retrieve record from"); - } - - /** - * get a record from the underlying array - */ - public BiffRec getRecordAt(int t) { - return (BiffRec) records.get(t); - } - - /** - * remove a record from the underlying array - */ - public boolean removeRecord(BiffRec rec) { -/* if(rec instanceof Mulled) { - if(((Mulled)rec).getMyMul()!=null) return true; - }*/ - if (rec.getSheet() != null) { - Sheet bs = rec.getSheet(); - if (bs.getSheetRecs().contains(rec)) return bs.getSheetRecs().remove(rec); - } - return records.remove(rec); - } - - /** get the recvec index of this record - - public List getRecordSubList(int start, int end){ - return records.subList(start,end); - }*/ - - /** - * get the recvec index of this record - */ - public int getRecordIndex(BiffRec rec) { - if (rec.getSheet() != null) { - Sheet bs = rec.getSheet(); - // Logger.logInfo("ByteStreamer getting index of sheetRec: " + bs.toString()); - int ret = bs.getSheetRecs().indexOf(rec); // bs.getRidx(); // - if (ret > -1) return ret; - return records.indexOf(rec); - } - return records.indexOf(rec); - } - - - /** - * get the real (non-boundsheet based) recvec index of this record - */ - public int getRealRecordIndex(BiffRec rec) { - return records.indexOf(rec); - } - - int ridx = 0; - - /** - * add an BiffRec to this streamer. - */ - public void addRecord(BiffRec rec) { - rec.setStreamer(this); - Sheet sht = rec.getSheet(); - if (sht != null) { - sht.getSheetRecs().add(rec); - } else { - records.add(rec); - } - } - - /** - * Bypass the sheet vecs... - * - * @param rec - * @param idx - */ - public void addRecordToBookStreamerAt(BiffRec rec, int idx) { - try { - records.add(idx, rec); - } catch (ArrayIndexOutOfBoundsException e) { - records.add(records.size(), rec); - } - } - - - /** - * add an BiffRec to this streamer at the specified index. - */ - public void addRecordToSheetStreamerAt(BiffRec rec, int idx, Boundsheet sht) { - rec.setStreamer(this); - List sr = sht.getSheetRecs(); - sr.add(idx, rec); - } - - /** - * add an BiffRec to this streamer at the specified index. - */ - public void addRecordAt(BiffRec rec, int idx) { - rec.setStreamer(this); - Sheet sht = rec.getSheet(); - if (sht != null) { - List sr = sht.getSheetRecs(); - // if(!sr.contains(rec)) - sr.add(idx, rec); - // Logger.logInfo("ByteStreamer adding recAT: " + rec.toString() + " to Sheet: " + sht.toString()); - } else { - // Logger.logInfo("ByteStreamer adding non-Sheet recAT: " + rec.toString()); - try { - records.add(idx, rec); - // rec.setRecordIndexHint(idx); - } catch (Exception e) { - records.add(records.size(), rec); - // rec.setRecordIndexHint(records.size()); - } - } - } - - /** - * stream the bytes to an outputstream - */ - public int streamOut(OutputStream _out) { - writeOut(_out); - return dlen - 4; - } - - public void writeRecord(OutputStream out, BiffRec rec) - throws IOException { - byte[] op = ByteTools.shortToLEBytes(rec.getOpcode()); - byte[] dt = rec.getData(); - byte[] ln = ByteTools.shortToLEBytes((short) dt.length); - out.write(op); - out.write(ln); - if (dt.length > 0) - out.write(dt); - if (LEOFile.DEBUG) LEOFile.actualOutput += (op.length + ln.length + dt.length); // debugging - rec.postStream(); - } - - /** - * Write all of the records to the output stream, including - * creating lbplypos records, assembling continues, etc. - */ - public StringBuffer writeOut(OutputStream out) { - // create a byte level lockdown file in same directory as output - StringBuffer lockdown = new StringBuffer(); - boolean lockit = false; - if (System.getProperties().get("io.starter.OpenXLS.autocreatelockdown") != null) - lockit = System.getProperties().get("io.starter.OpenXLS.autocreatelockdown").equals("true"); - - // update tracker cells, packs formats ... - this.workbook.prestream(); - byte[] dt; - int recpos = 0, recctr = 0, dlen = 0; - - - // get a private list of the records - AbstractList rex = new FastAddVector(records.size()); - rex.addAll(records); - - - // first pass -- prepare SST - BiffRec rec = null; - Iterator e = rex.iterator(); - while (e.hasNext()) { - rec = (BiffRec) e.next(); - ++recctr; - if (rec != null) { - if (rec.getByteReader() != null) - rec.getByteReader().setApplyRelativePosition(true); - // Logger.logInfo("ByteStreamer.stream() PREStreaming: "+ rec); - if (rec.getOpcode() == BOUNDSHEET) { -// add sheet recs to output vector - List lst = ((Boundsheet) rec).assembleSheetRecs(); - rex.addAll(rex.size(), lst); - } else if (rec.getOpcode() == SST) { - // add extra bytes necessary for adding continue recx - rec.preStream(); - recpos = (((Sst) rec).getNumContinues() * 4); - if (recpos < 0)// deal with empty SST - recpos = 0; - dlen += recpos; - } else - rec.preStream(); // perform expensive processes - } else { - Logger.logWarn("Body Rec missing while preStreaming(): " + rec.toString()); - } - } - e = rex.iterator(); - Index lastindex = null; - int ctr = 0; - while (e.hasNext()) { - rec = (BiffRec) e.next(); - if (ctr == 0) { // handle the first BOF offset - rec.setOffset(0); - ctr++; - } else { - rec.setOffset(recpos); - } - - if (rec.getOpcode() == INDEX) { // need to get all it's component dbcell offsets set before we can process! - if (lastindex != null) { - lastindex.updateDbcellPointers(); - } - lastindex = (Index) rec; - } -// offset dlen by number of new continue headers - if (rec.getOpcode() == CONTINUE && ((Continue) rec).maskedMso != null) { - rec.setData(((Continue) rec).maskedMso.getData()); // ensure any mso changes are propogated up - } - int rln = rec.getLength(); //length of total rec data including continue - int numcx = rln / MAXRECLEN; // num continues? - if ((rln % MAXRECLEN) <= 4 && numcx > 0) // hits boundary; since rlen==datalen+4, numcx is 1 more than actual continues - numcx--; - if (rec.getOpcode() == CONTINUE) { - Continue thiscont = (Continue) rec; - if (thiscont.isBigRecContinue()) {// could cause bugs... if related rec is trimmed - rln = 0; // do not count data byte size for Continues... - } - } - if (((rln > (MAXRECLEN + 4)) - && (numcx > 0) - && rec.getOpcode() != SST)) { - dlen += (numcx * 4); - recpos += (numcx * 4); - } - dlen += rln; - recpos += rln; - } - if (lastindex != null) lastindex.updateDbcellPointers(); - e = rex.iterator(); - - /** - * Get the updated Storages from LEO... output the RootStorage - * and return the other storages and block index for output after - * the workbook recs. - * - */ - LEOFile leo = this.workbook.factory.myLEO; - List storages = null; - storages = leo.writeBytes(out, (dlen)); - BIGBLOCK hdrBlock = new BIGBLOCK(); - hdrBlock.init(leo.getHeader().getBytes(), 0, 0); - - // ********************** WRITE OUT FILE IN CORRECT ORDER **************************************/ - /** Header = 1st sector/block */ - hdrBlock.writeBytes(out); - - // now output the workbook biff records - if (LEOFile.DEBUG) LEOFile.actualOutput = 0; // debugging - while (e.hasNext()) { - rec = (BiffRec) e.next(); - - try { // output the rec bytes - // deal with CONTINUE record changes before streaming - if (ContinueHandler.createContinues(rec, out, this)) { - // Logger.logInfo("Created continues for: " + rec.toString()); - } else {// Not a continued rec! - this.writeRecord(out, rec); - - if (lockit) { - byte[] op = ByteTools.shortToLEBytes(rec.getOpcode()); - dt = rec.getData(); - byte[] ln = ByteTools.shortToLEBytes((short) dt.length); - - // Logger.logInfo("=== WRITING RECORD DATA ==="); - //lockdown.append("rec:" + rec.toString()); - //lockdown.append("\r\n"); - lockdown.append("opc:0x" + Integer.toHexString(rec.getOpcode()) + " [" + ByteTools.getByteString(op, false) + "]"); - lockdown.append("\r\n"); - lockdown.append("len:0x" + Integer.toHexString(rec.getLength()) + " [" + ByteTools.getByteString(ln, false) + "]"); - lockdown.append("\r\n"); - //lockdown.append("off:0x" + Integer.toHexString(off+0x200)); - //lockdown.append("\r\n"); - lockdown.append(ByteTools.getByteDump(dt, 1)); - lockdown.append("\r\n"); - } - } - } catch (Exception a) { - throw new WorkBookException( - "Streaming WorkBook Bytes failed for record: " - + rec.toString() + ": " + a + " Output Corrupted.", - WorkBookException.WRITING_ERROR, a); - } - } - - // pad to fit FAT size - if (LEOFile.DEBUG) { - if (LEOFile.actualOutput != dlen) - Logger.logInfo("Expected:" + dlen + " Actual: " + LEOFile.actualOutput + " Diff: " + (LEOFile.actualOutput - dlen)); - } - int leftover; - int nBlocks = Math.max(leo.getMinBlocks(), (int) Math.ceil(dlen / (BIGBLOCK.SIZE * 1.0)) + 1); - leftover = (nBlocks * BIGBLOCK.SIZE) - dlen; // padding - - - byte[] filler = new byte[leftover]; - for (int t = 0; t < filler.length; t++) filler[t] = 0; - - // finally output the rest of the storages - try { - out.write(filler); // workbook data filler - Iterator its = storages.iterator(); - while (its.hasNext()) { - Storage ob = (Storage) its.next(); - if (ob != null) - if (!ob.getName().equals("Root Entry") || true) { // was newLeoProcessing, so should always run? - if (ob.getBlockType() != Block.SMALL) {// already written in miniFAT Container - see buildSAT - ob.writeBytes(out); - //Logger.logInfo("Streamed Storage:" + ob.getName() + ":"+ob.getActualFileSize()); - } - } - } - } catch (Exception a) { - Logger.logErr("Streaming WorkBook Storage Bytes failed.", a); - throw new WorkBookException( - "ByteStreamer.stream(): Body Rec missing while preStreaming(): " + rec.toString() - + " " + a.toString() + " Output Corrupted.", WorkBookException.WRITING_ERROR); - - } - if (lockit) { // write the lockdown file - return lockdown; - } - return null; - } - - - public void WriteAllRecs(String fName) { - WriteAllRecs(fName, false); - } - - /** - * Debug utility to write out ALL BIFF records - * - * @param fName output filename - * @param bWriteSheeRecs truth of "include sheet records in output" - */ - public void WriteAllRecs(String fName, boolean bWriteSheetRecs) { - try { - java.io.File f = new java.io.File(fName); - BufferedWriter writer = new BufferedWriter(new FileWriter(f)); - ArrayList recs = new ArrayList(java.util.Arrays.asList(this.getBiffRecords())); - int ctr = 0; - ctr = ByteStreamer.writeRecs(recs, writer, ctr, 0); - List sheets = this.workbook.getSheetVect(); - for (int i = 0; i < sheets.size(); i++) { - if (bWriteSheetRecs) { - ArrayList lst = (ArrayList) ((Boundsheet) sheets.get(i)).assembleSheetRecs(); - ctr = ByteStreamer.writeRecs(lst, writer, ctr, 0); - } else { - ctr = ByteStreamer.writeRecs((ArrayList) ((Boundsheet) sheets.get(i)).getSheetRecs(), writer, ctr, 0); - } - } - writer.flush(); - writer.close(); - writer = null; - } catch (Exception e) { - } - } - - /** - * Debug utility to write out BIFF records in friendly fashion - * - * @param recArr array of records - * @param writer BufferedWriter - * @param level For ChartRecs that have sub-records, recurse Level - */ - public static int writeRecs(ArrayList recArr, BufferedWriter writer, int ctr, int level) { - String tabs = "\t\t\t\t\t\t\t\t\t\t"; - for (int i = 0; i < recArr.size(); i++) { - try { - BiffRec b = (BiffRec) recArr.get(i); - if (b == null) - break; - writer.write(tabs.substring(0, level) + b.getClass().toString().substring(b.getClass().toString().lastIndexOf('.') + 1)); - if (b instanceof io.starter.formats.XLS.charts.SeriesText) - writer.write("\t[" + b.toString() + "]"); - else if (b instanceof Continue) { - if (((Continue) b).maskedMso != null) { - writer.write("\t[MASKED MSO ******************"); - writer.write("\t[" + ((Continue) b).maskedMso.toString() + "]"); - writer.write(((Continue) b).maskedMso.debugOutput()); - writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11) + "]"); - } else { - writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11) + "]"); - } - } else if (b instanceof MSODrawing) { - writer.write("\t[" + ((MSODrawing) b).toString() + "]"); -// writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); - writer.write(((MSODrawing) b).debugOutput()); - writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11) + "]"); - } else if (b instanceof Obj) { - writer.write(((Obj) b).debugOutput()); - } else if (b instanceof MSODrawingGroup) { - writer.write("\t[" + ((MSODrawingGroup) b).toString() + "]"); -// writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); - } else if (b instanceof Label) { - writer.write("\t[" + b.getStringVal() + "]"); - } else if (b instanceof Mulblank) { - writer.write("\t[" + b.getCellAddress() + "]"); - } else if (b instanceof Name) { - try { - writer.write("\t[" + ((Name) b).getName() + "-" + ((Name) b).getLocation() + "]"); - } catch (Exception ce) { - writer.write("\t[" + ((Name) b).getName() + "-ERROR IN LOCATION]"); - } - //writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); - } else if (b instanceof Sst) - writer.write("\t[" + ((Sst) b).toString() + "]"); - else if (b instanceof Pls) - writer.write("\t[" + b.toString() + "]"); - else if (b instanceof Supbook) { - writer.write("\t[" + b.toString() + "]"); -// writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); - } else if (b instanceof Crn) - writer.write("\t[" + b.toString() + "]"); - else if (b instanceof Formula || b instanceof Rk || b instanceof NumberRec || b instanceof Blank || b instanceof Labelsst) - writer.write(" " + ((XLSRecord) b).getCellAddressWithSheet() + "\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11) + "]"); - else // all else, write bytes - writer.write("\t[" + ByteTools.getByteDump(ByteTools.shortToLEBytes(b.getOpcode()), 0) + "][" + ByteTools.getByteDump(b.getData(), 0).substring(11) + "]"); - writer.newLine(); - if (b instanceof io.starter.formats.XLS.charts.ChartObject) { - writeRecs(((io.starter.formats.XLS.charts.ChartObject) b).getChartRecords(), writer, ctr, level + 1); - } - } catch (Exception e) { - } - } - return ctr; - } - - /** - * clear out object references - */ - public void close() { - for (int i = 0; i < records.size(); i++) { - XLSRecord r = (XLSRecord) records.get(i); - r.close(); - } - records.clear(); - workbook = null; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/ByteStreamer.kt b/src/main/java/io/starter/formats/XLS/ByteStreamer.kt new file mode 100644 index 0000000..44c8f12 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/ByteStreamer.kt @@ -0,0 +1,560 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.formats.LEO.BIGBLOCK +import io.starter.formats.LEO.Block +import io.starter.formats.LEO.LEOFile +import io.starter.formats.LEO.Storage +import io.starter.toolkit.ByteTools +import io.starter.toolkit.FastAddVector +import io.starter.toolkit.Logger + +import java.io.* +import java.util.* + + +/** + * ByteStreamer Handles the low-level byte array streaming + * of the WorkBook. Basically a collection of XLS and methods for getting + * at their data. + * + * @see WorkBook + * + * @see Boundsheet + */ +class ByteStreamer(bk: WorkBook) : Serializable, XLSConstants { + internal var records: AbstractList<*> = FastAddVector() + // public ByteStreamer(WorkBook b){this.book = b;} + + var bytes: ByteArray? = null + val name = "" + internal var DEBUG = !true + protected var workbook: WorkBook? = null + private val out: OutputStream? = null + internal var dlen = -1 + + /** + * Returns an array of all the BiffRecs in the streamer. For debug purposes, + * as there is no way to directy view the FastAddVector + * + * @return + */ + val biffRecords: Array + get() = records.toTypedArray() + + val recVecSize: Int + get() = records.size + + val substreamTypeName: String + get() = "ByteStreamer" + + val substreamType: Short + get() = XLSConstants.WK_WORKSHEET + + internal var ridx = 0 + + + fun getRecordIndex(opcode: Int): Int { + + for (i in records.indices) { + val rec = records[i] as XLSRecord + if (rec.opcode.toInt() == opcode) return i + } + return -1 + + } + + fun initTestRecVect() { + // Useful for looking at the output vector & debugging ... THANKS, NICK! -jm + val testVect: Vector<*> + if (true) { + Logger.logInfo("TestVector on in bytestreamer.stream") + testVect = Vector() + testVect.addAll(records) + } + Logger.logInfo("TESTING Recvec done.") + } + + init { + this.workbook = bk + } + + // 20060601 KSC: setBiffRecords + fun setBiffRecords(recs: Collection<*>) { + if (Arrays.equals(records.toTypedArray(), recs.toTypedArray())) { + // already set! + return + } + records.clear() + records.addAll(recs) + } + + /** + * get a record from the underlying array + */ + fun getRecordAt(t: Int, rec: BiffRec): BiffRec { + if (rec is Boundsheet || rec is Dbcell) + return this.getRecordAt(t) + if (rec.sheet != null) { + val bs = rec.sheet + return bs.sheetRecs[t] as BiffRec + } + throw InvalidRecordException("ERROR: ByteStreamer.getRecord() could not retrieve record from") + } + + /** + * get a record from the underlying array + */ + fun getRecordAt(t: Int): BiffRec { + return records[t] as BiffRec + } + + /** + * remove a record from the underlying array + */ + fun removeRecord(rec: BiffRec): Boolean { + /* if(rec instanceof Mulled) { + if(((Mulled)rec).getMyMul()!=null) return true; + }*/ + if (rec.sheet != null) { + val bs = rec.sheet + if (bs.sheetRecs.contains(rec)) return bs.sheetRecs.remove(rec) + } + return records.remove(rec) + } + + /** get the recvec index of this record + * + * public List getRecordSubList(int start, int end){ + * return records.subList(start,end); + * } */ + + /** + * get the recvec index of this record + */ + fun getRecordIndex(rec: BiffRec): Int { + if (rec.sheet != null) { + val bs = rec.sheet + // Logger.logInfo("ByteStreamer getting index of sheetRec: " + bs.toString()); + val ret = bs.sheetRecs.indexOf(rec) // bs.getRidx(); // + return if (ret > -1) ret else records.indexOf(rec) + } + return records.indexOf(rec) + } + + + /** + * get the real (non-boundsheet based) recvec index of this record + */ + fun getRealRecordIndex(rec: BiffRec): Int { + return records.indexOf(rec) + } + + /** + * add an BiffRec to this streamer. + */ + fun addRecord(rec: BiffRec) { + rec.streamer = this + val sht = rec.sheet + sht?.sheetRecs?.add(rec) ?: records.add(rec) + } + + /** + * Bypass the sheet vecs... + * + * @param rec + * @param idx + */ + fun addRecordToBookStreamerAt(rec: BiffRec, idx: Int) { + try { + records.add(idx, rec) + } catch (e: ArrayIndexOutOfBoundsException) { + records.add(records.size, rec) + } + + } + + + /** + * add an BiffRec to this streamer at the specified index. + */ + fun addRecordToSheetStreamerAt(rec: BiffRec, idx: Int, sht: Boundsheet) { + rec.streamer = this + val sr = sht.sheetRecs + sr.add(idx, rec) + } + + /** + * add an BiffRec to this streamer at the specified index. + */ + fun addRecordAt(rec: BiffRec, idx: Int) { + rec.streamer = this + val sht = rec.sheet + if (sht != null) { + val sr = sht.sheetRecs + // if(!sr.contains(rec)) + sr.add(idx, rec) + // Logger.logInfo("ByteStreamer adding recAT: " + rec.toString() + " to Sheet: " + sht.toString()); + } else { + // Logger.logInfo("ByteStreamer adding non-Sheet recAT: " + rec.toString()); + try { + records.add(idx, rec) + // rec.setRecordIndexHint(idx); + } catch (e: Exception) { + records.add(records.size, rec) + // rec.setRecordIndexHint(records.size()); + } + + } + } + + /** + * stream the bytes to an outputstream + */ + fun streamOut(_out: OutputStream): Int { + writeOut(_out) + return dlen - 4 + } + + @Throws(IOException::class) + fun writeRecord(out: OutputStream, rec: BiffRec) { + val op = ByteTools.shortToLEBytes(rec.opcode) + val dt = rec.data + val ln = ByteTools.shortToLEBytes(dt.size.toShort()) + out.write(op) + out.write(ln) + if (dt.size > 0) + out.write(dt) + if (LEOFile.DEBUG) LEOFile.actualOutput += op.size + ln.size + dt.size // debugging + rec.postStream() + } + + /** + * Write all of the records to the output stream, including + * creating lbplypos records, assembling continues, etc. + */ + fun writeOut(out: OutputStream): StringBuffer? { + // create a byte level lockdown file in same directory as output + val lockdown = StringBuffer() + var lockit = false + if (System.getProperties()["io.starter.OpenXLS.autocreatelockdown"] != null) + lockit = System.getProperties()["io.starter.OpenXLS.autocreatelockdown"] == "true" + + // update tracker cells, packs formats ... + this.workbook!!.prestream() + var dt: ByteArray + var recpos = 0 + var recctr = 0 + var dlen = 0 + + + // get a private list of the records + val rex = FastAddVector(records.size) + rex.addAll(records) + + + // first pass -- prepare SST + var rec: BiffRec? = null + var e: Iterator<*> = rex.iterator() + while (e.hasNext()) { + rec = e.next() as BiffRec + ++recctr + if (rec != null) { + if (rec.byteReader != null) + rec.byteReader.applyRelativePosition = true + // Logger.logInfo("ByteStreamer.stream() PREStreaming: "+ rec); + if (rec.opcode == XLSConstants.BOUNDSHEET) { + // add sheet recs to output vector + val lst = (rec as Boundsheet).assembleSheetRecs() + rex.addAll(rex.size, lst) + } else if (rec.opcode == XLSConstants.SST) { + // add extra bytes necessary for adding continue recx + rec.preStream() + recpos = (rec as Sst).numContinues * 4 + if (recpos < 0) + // deal with empty SST + recpos = 0 + dlen += recpos + } else + rec.preStream() // perform expensive processes + } else { + Logger.logWarn("Body Rec missing while preStreaming(): " + rec!!.toString()) + } + } + e = rex.iterator() + var lastindex: Index? = null + var ctr = 0 + while (e.hasNext()) { + rec = e.next() as BiffRec + if (ctr == 0) { // handle the first BOF offset + rec.offset = 0 + ctr++ + } else { + rec.offset = recpos + } + + if (rec.opcode == XLSConstants.INDEX) { // need to get all it's component dbcell offsets set before we can process! + lastindex?.updateDbcellPointers() + lastindex = rec as Index? + } + // offset dlen by number of new continue headers + if (rec.opcode == XLSConstants.CONTINUE && (rec as Continue).maskedMso != null) { + rec.data = rec.maskedMso!!.getData() // ensure any mso changes are propogated up + } + var rln = rec.length //length of total rec data including continue + var numcx = rln / XLSConstants.MAXRECLEN // num continues? + if (rln % XLSConstants.MAXRECLEN <= 4 && numcx > 0) + // hits boundary; since rlen==datalen+4, numcx is 1 more than actual continues + numcx-- + if (rec.opcode == XLSConstants.CONTINUE) { + val thiscont = rec as Continue? + if (thiscont!!.isBigRecContinue) {// could cause bugs... if related rec is trimmed + rln = 0 // do not count data byte size for Continues... + } + } + if (rln > XLSConstants.MAXRECLEN + 4 + && numcx > 0 + && rec.opcode != XLSConstants.SST) { + dlen += numcx * 4 + recpos += numcx * 4 + } + dlen += rln + recpos += rln + } + lastindex?.updateDbcellPointers() + e = rex.iterator() + + /** + * Get the updated Storages from LEO... output the RootStorage + * and return the other storages and block index for output after + * the workbook recs. + * + */ + val leo = this.workbook!!.factory!!.leoFile + var storages: List<*>? = null + storages = leo.writeBytes(out, dlen) + val hdrBlock = BIGBLOCK() + hdrBlock.init(leo.header!!.bytes, 0, 0) + + // ********************** WRITE OUT FILE IN CORRECT ORDER **************************************/ + /** Header = 1st sector/block */ + hdrBlock.writeBytes(out) + + // now output the workbook biff records + if (LEOFile.DEBUG) LEOFile.actualOutput = 0 // debugging + while (e.hasNext()) { + rec = e.next() as BiffRec + + try { // output the rec bytes + // deal with CONTINUE record changes before streaming + if (ContinueHandler.createContinues(rec, out, this)) { + // Logger.logInfo("Created continues for: " + rec.toString()); + } else {// Not a continued rec! + this.writeRecord(out, rec) + + if (lockit) { + val op = ByteTools.shortToLEBytes(rec.opcode) + dt = rec.data + val ln = ByteTools.shortToLEBytes(dt.size.toShort()) + + // Logger.logInfo("=== WRITING RECORD DATA ==="); + //lockdown.append("rec:" + rec.toString()); + //lockdown.append("\r\n"); + lockdown.append("opc:0x" + Integer.toHexString(rec.opcode.toInt()) + " [" + ByteTools.getByteString(op, false) + "]") + lockdown.append("\r\n") + lockdown.append("len:0x" + Integer.toHexString(rec.length) + " [" + ByteTools.getByteString(ln, false) + "]") + lockdown.append("\r\n") + //lockdown.append("off:0x" + Integer.toHexString(off+0x200)); + //lockdown.append("\r\n"); + lockdown.append(ByteTools.getByteDump(dt, 1)) + lockdown.append("\r\n") + } + } + } catch (a: Exception) { + throw WorkBookException( + "Streaming WorkBook Bytes failed for record: " + + rec.toString() + ": " + a + " Output Corrupted.", + WorkBookException.WRITING_ERROR, a) + } + + } + + // pad to fit FAT size + if (LEOFile.DEBUG) { + if (LEOFile.actualOutput != dlen) + Logger.logInfo("Expected:" + dlen + " Actual: " + LEOFile.actualOutput + " Diff: " + (LEOFile.actualOutput - dlen)) + } + val leftover: Int + val nBlocks = Math.max(leo.minBlocks, Math.ceil(dlen / (BIGBLOCK.SIZE * 1.0)).toInt() + 1) + leftover = nBlocks * BIGBLOCK.SIZE - dlen // padding + + + val filler = ByteArray(leftover) + for (t in filler.indices) filler[t] = 0 + + // finally output the rest of the storages + try { + out.write(filler) // workbook data filler + val its = storages!!.iterator() + while (its.hasNext()) { + val ob = its.next() as Storage + if (ob != null) + if (ob.name != "Root Entry" || true) { // was newLeoProcessing, so should always run? + if (ob.blockType != Block.SMALL) {// already written in miniFAT Container - see buildSAT + ob.writeBytes(out) + //Logger.logInfo("Streamed Storage:" + ob.getName() + ":"+ob.getActualFileSize()); + } + } + } + } catch (a: Exception) { + Logger.logErr("Streaming WorkBook Storage Bytes failed.", a) + throw WorkBookException( + "ByteStreamer.stream(): Body Rec missing while preStreaming(): " + rec!!.toString() + + " " + a.toString() + " Output Corrupted.", WorkBookException.WRITING_ERROR) + + } + + return if (lockit) { // write the lockdown file + lockdown + } else null + } + + /** + * Debug utility to write out ALL BIFF records + * + * @param fName output filename + * @param bWriteSheeRecs truth of "include sheet records in output" + */ + @JvmOverloads + fun WriteAllRecs(fName: String, bWriteSheetRecs: Boolean = false) { + try { + val f = java.io.File(fName) + var writer: BufferedWriter? = BufferedWriter(FileWriter(f)) + val recs = ArrayList(java.util.Arrays.asList(*this.biffRecords)) + var ctr = 0 + ctr = ByteStreamer.writeRecs(recs, writer, ctr, 0) + val sheets = this.workbook!!.sheetVect + for (i in sheets.indices) { + if (bWriteSheetRecs) { + val lst = (sheets[i] as Boundsheet).assembleSheetRecs() as ArrayList<*> + ctr = ByteStreamer.writeRecs(lst, writer, ctr, 0) + } else { + ctr = ByteStreamer.writeRecs((sheets[i] as Boundsheet).sheetRecs as ArrayList<*>, writer, ctr, 0) + } + } + writer!!.flush() + writer.close() + writer = null + } catch (e: Exception) { + } + + } + + /** + * clear out object references + */ + fun close() { + for (i in records.indices) { + val r = records[i] as XLSRecord + r.close() + } + records.clear() + workbook = null + } + + companion object { + private const val serialVersionUID = -8188652784510579406L + + /** + * Debug utility to write out BIFF records in friendly fashion + * + * @param recArr array of records + * @param writer BufferedWriter + * @param level For ChartRecs that have sub-records, recurse Level + */ + fun writeRecs(recArr: ArrayList<*>, writer: BufferedWriter, ctr: Int, level: Int): Int { + val tabs = "\t\t\t\t\t\t\t\t\t\t" + for (i in recArr.indices) { + try { + val b = recArr[i] as BiffRec ?: break + writer.write(tabs.substring(0, level) + b.javaClass.toString().substring(b.javaClass.toString().lastIndexOf('.') + 1)) + if (b is io.starter.formats.XLS.charts.SeriesText) + writer.write("\t[$b]") + else if (b is Continue) { + if (b.maskedMso != null) { + writer.write("\t[MASKED MSO ******************") + writer.write("\t[" + b.maskedMso!!.toString() + "]") + writer.write(b.maskedMso!!.debugOutput()) + writer.write("\t[" + ByteTools.getByteDump(b.data, 0).substring(11) + "]") + } else { + writer.write("\t[" + ByteTools.getByteDump(b.data, 0).substring(11) + "]") + } + } else if (b is MSODrawing) { + writer.write("\t[$b]") + // writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); + writer.write(b.debugOutput()) + writer.write("\t[" + ByteTools.getByteDump(b.data, 0).substring(11) + "]") + } else if (b is Obj) { + writer.write(b.debugOutput()) + } else if (b is MSODrawingGroup) { + writer.write("\t[$b]") + // writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); + } else if (b is Label) { + writer.write("\t[" + b.stringVal + "]") + } else if (b is Mulblank) { + writer.write("\t[" + b.cellAddress + "]") + } else if (b is Name) { + try { + writer.write("\t[" + b.name + "-" + b.location + "]") + } catch (ce: Exception) { + writer.write("\t[" + b.name + "-ERROR IN LOCATION]") + } + + //writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); + } else if (b is Sst) + writer.write("\t[$b]") + else if (b is Pls) + writer.write("\t[$b]") + else if (b is Supbook) { + writer.write("\t[$b]") + // writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); + } else if (b is Crn) + writer.write("\t[$b]") + else if (b is Formula || b is Rk || b is NumberRec || b is Blank || b is Labelsst) + writer.write(" " + (b as XLSRecord).cellAddressWithSheet + "\t[" + ByteTools.getByteDump(b.data, 0).substring(11) + "]") + else + // all else, write bytes + writer.write("\t[" + ByteTools.getByteDump(ByteTools.shortToLEBytes(b.opcode), 0) + "][" + ByteTools.getByteDump(b.data, 0).substring(11) + "]") + writer.newLine() + if (b is io.starter.formats.XLS.charts.ChartObject) { + writeRecs((b as io.starter.formats.XLS.charts.ChartObject).chartRecords, writer, ctr, level + 1) + } + } catch (e: Exception) { + } + + } + return ctr + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/CalcMode.java b/src/main/java/io/starter/formats/XLS/CalcMode.kt similarity index 61% rename from src/main/java/io/starter/formats/XLS/CalcMode.java rename to src/main/java/io/starter/formats/XLS/CalcMode.kt index 4a26f4e..d5579cb 100644 --- a/src/main/java/io/starter/formats/XLS/CalcMode.java +++ b/src/main/java/io/starter/formats/XLS/CalcMode.kt @@ -20,37 +20,28 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** - * CALCMODE: (OxD)
                  - *

                  + * **CALCMODE: (OxD)**

                  + * + * * It specifies whether to calculate formulas manually, * automatically or automatically except for multiple table operations. - *

                  + *
                  + * 
                    * Offset Size Contents
                    * 0 		2 	FFFFH = automatically except for multiple table operations
                    * 0000H = manually
                    * 0001H = automatically (default)
                  - * 

                  +
                  * */ -public final class CalcMode extends io.starter.formats.XLS.XLSRecord { - - private static final long serialVersionUID = -4544323710670598072L; - short calcmode; - - public void init() { - super.init(); - /* FFFFH = automatically except for multiple table operations - 0000H = manually - 0001H = automatically (default) - */ - calcmode = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } +class CalcMode : io.starter.formats.XLS.XLSRecord() { + internal var calcmode: Short = 0 /** * returns the recalculation mode: @@ -58,26 +49,38 @@ public void init() { * * @return int recalculation mode */ - public int getRecalcuationMode() { - if (calcmode < 0) - return 2; - return calcmode; + val recalcuationMode: Int + get() = if (calcmode < 0) 2 else calcmode.toInt() + + override fun init() { + super.init() + /* FFFFH = automatically except for multiple table operations + 0000H = manually + 0001H = automatically (default) + */ + calcmode = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) } /** * Sets the recalculation mode for the Workbook: - *
                  0= Manual - *
                  1= Automatic - *
                  2= Automatic except for multiple table operations + *

                  0= Manual + *

                  1= Automatic + *

                  2= Automatic except for multiple table operations */ - public void setRecalculationMode(int mode) { + fun setRecalculationMode(mode: Int) { + var mode = mode if (mode >= 0 && mode <= 2) { - if (mode == 2) mode = -1; - calcmode = (short) mode; - byte[] b = ByteTools.shortToLEBytes(calcmode); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; + if (mode == 2) mode = -1 + calcmode = mode.toShort() + val b = ByteTools.shortToLEBytes(calcmode) + this.getData()[0] = b[0] + this.getData()[1] = b[1] } } + companion object { + + private val serialVersionUID = -4544323710670598072L + } + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/CellAddressComparator.java b/src/main/java/io/starter/formats/XLS/CellAddressComparator.kt similarity index 54% rename from src/main/java/io/starter/formats/XLS/CellAddressComparator.java rename to src/main/java/io/starter/formats/XLS/CellAddressComparator.kt index a829994..681d5ae 100644 --- a/src/main/java/io/starter/formats/XLS/CellAddressComparator.java +++ b/src/main/java/io/starter/formats/XLS/CellAddressComparator.kt @@ -20,54 +20,51 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import java.io.Serializable; -import java.util.Comparator; +import java.io.Serializable +import java.util.Comparator /** - * A Comparator that sorts cell records by address. - * As XLSRecord currently uses default equality the + * A `Comparator` that sorts cell records by address. + * As `XLSRecord` currently uses default equality the * imposed ordering is inconsistent with equals. */ -public class CellAddressComparator - implements Comparator, Serializable { - private static final long serialVersionUID = 686639297047358268L; +class CellAddressComparator : Comparator<*>, Serializable { /** * Compares its two arguments for order. - * The arguments must be XLSRecords that represent cells - * (isValueForCell() must return true). + * The arguments must be `XLSRecord`s that represent cells + * (`isValueForCell()` must return true). * * @param o1 the first cell record to be compared * @param o2 the second cell record to be compared * @throws ClassCastException if either argument is not a cell record */ - public int compare(Object o1, Object o2) { - if (o1 == null || !(o1 instanceof XLSRecord) - || o2 == null || !(o2 instanceof XLSRecord)) - throw new ClassCastException(); + override fun compare(o1: Any?, o2: Any?): Int { + if (o1 == null || o1 !is XLSRecord + || o2 == null || o2 !is XLSRecord) + throw ClassCastException() - XLSRecord rec1 = (XLSRecord) o1; - XLSRecord rec2 = (XLSRecord) o2; + val rec1 = o1 as XLSRecord? + val rec2 = o2 as XLSRecord? - if (!rec1.isValueForCell() || !rec2.isValueForCell()) - throw new ClassCastException(); + if (!rec1!!.isValueForCell || !rec2!!.isValueForCell) + throw ClassCastException() - int diff = rec1.getRowNumber() - rec2.getRowNumber(); - if (diff != 0) return diff; + val diff = rec1.rowNumber - rec2.rowNumber + return if (diff != 0) diff else rec1.colNumber - rec2.colNumber - return rec1.getColNumber() - rec2.getColNumber(); } /** - * Indicates whether another Comparator imposes the same + * Indicates whether another `Comparator` imposes the same * ordering as this one. As all instances of this class impose the same * order, this method returns true if and only if the given object is * an instance of this class. */ - public boolean equals(Object obj) { - return obj != null && this.getClass().equals(obj.getClass()); + override fun equals(obj: Any?): Boolean { + return obj != null && this.javaClass == obj.javaClass } /** @@ -75,8 +72,12 @@ public boolean equals(Object obj) { * As all instances of this class are equivalent this returns a * constant value. */ - public int hashCode() { + override fun hashCode(): Int { // This is just a random number - return 973216835; + return 973216835 + } + + companion object { + private const val serialVersionUID = 686639297047358268L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/CellAddressible.java b/src/main/java/io/starter/formats/XLS/CellAddressible.java deleted file mode 100644 index 67587ad..0000000 --- a/src/main/java/io/starter/formats/XLS/CellAddressible.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import java.io.Serializable; - -/** - * Something with a cell address. - */ -public interface CellAddressible - extends ColumnRange { - - /** - * Returns the row the cell resides in. - * - * @return the zero-based index of the cell's parent row - */ - int getRowNumber(); - - /** - * A simple immutable cell reference useful as a map key. - */ - class Reference - implements CellAddressible, Serializable { - private static final long serialVersionUID = -9071483662123798966L; - private final int row, colFirst, colLast; - - public Reference(int row, int colFirst, int colLast) { - this.row = row; - this.colFirst = colFirst; - this.colLast = colLast; - } - - public Reference(int row, int col) { - this(row, col, col); - } - - public int getColFirst() { - return this.colFirst; - } - - public int getColLast() { - return this.colLast; - } - - public int getRowNumber() { - return this.row; - } - - public boolean isSingleCol() { - return (this.getColFirst() == this.getColLast()); - } - } - - /** - * Cell reference for use as the boundary of a column range. - */ - class RangeBoundary - extends Reference { - private static final long serialVersionUID = -1357617242449928095L; - private final boolean before; - - /** - * @param before whether the boundary should sort before (true) or - * after (false) a range which includes it - */ - public RangeBoundary(int row, int col, boolean before) { - super(row, col, col); - this.before = before; - } - - public int comareToRange() { - return before ? -1 : 1; - } - } - - /** - * {@link Comparator} that sorts cells in ascending row major order. - */ - final class RowMajorComparator - implements java.util.Comparator, Serializable { - private static final long serialVersionUID = 5477030152120715766L; - private static final ColumnRange.Comparator colComp = - new ColumnRange.Comparator(); - - public int compare(CellAddressible cell1, CellAddressible cell2) { - int rows = cell1.getRowNumber() - cell2.getRowNumber(); - if (0 != rows) return rows; - - return colComp.compare(cell1, cell2); - } - } - - /** - * {@link Comparator} that sorts cells in ascending column major order. - */ - final class ColumnMajorComparator - implements java.util.Comparator, Serializable { - private static final long serialVersionUID = -1193867650674693873L; - private static final ColumnRange.Comparator colComp = - new ColumnRange.Comparator(); - - public int compare(CellAddressible cell1, CellAddressible cell2) { - int cols = colComp.compare(cell1, cell2); - if (0 != cols) return cols; - - return cell1.getRowNumber() - cell2.getRowNumber(); - } - } -} diff --git a/src/main/java/io/starter/formats/XLS/CellAddressible.kt b/src/main/java/io/starter/formats/XLS/CellAddressible.kt new file mode 100644 index 0000000..d5bf6c8 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/CellAddressible.kt @@ -0,0 +1,106 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import java.io.Serializable + +/** + * Something with a cell address. + */ +interface CellAddressible : ColumnRange { + + /** + * Returns the row the cell resides in. + * + * @return the zero-based index of the cell's parent row + */ + val rowNumber: Int + + /** + * A simple immutable cell reference useful as a map key. + */ + open class Reference(override val rowNumber: Int, override val colFirst: Int, override val colLast: Int) : CellAddressible, Serializable { + + override val isSingleCol: Boolean + get() = this.colFirst == this.colLast + + constructor(row: Int, col: Int) : this(row, col, col) {} + + companion object { + private const val serialVersionUID = -9071483662123798966L + } + } + + /** + * Cell reference for use as the boundary of a column range. + */ + class RangeBoundary + /** + * @param before whether the boundary should sort before (true) or + * after (false) a range which includes it + */ + (row: Int, col: Int, private val before: Boolean) : Reference(row, col, col) { + + fun comareToRange(): Int { + return if (before) -1 else 1 + } + + companion object { + private val serialVersionUID = -1357617242449928095L + } + } + + /** + * [Comparator] that sorts cells in ascending row major order. + */ + class RowMajorComparator : java.util.Comparator, Serializable { + + override fun compare(cell1: CellAddressible, cell2: CellAddressible): Int { + val rows = cell1.rowNumber - cell2.rowNumber + return if (0 != rows) rows else colComp.compare(cell1, cell2) + + } + + companion object { + private const val serialVersionUID = 5477030152120715766L + private val colComp = ColumnRange.Comparator() + } + } + + /** + * [Comparator] that sorts cells in ascending column major order. + */ + class ColumnMajorComparator : java.util.Comparator, Serializable { + + override fun compare(cell1: CellAddressible, cell2: CellAddressible): Int { + val cols = colComp.compare(cell1, cell2) + return if (0 != cols) cols else cell1.rowNumber - cell2.rowNumber + + } + + companion object { + private const val serialVersionUID = -1193867650674693873L + private val colComp = ColumnRange.Comparator() + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/CellNotFoundException.java b/src/main/java/io/starter/formats/XLS/CellNotFoundException.java deleted file mode 100644 index ed0eeb7..0000000 --- a/src/main/java/io/starter/formats/XLS/CellNotFoundException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ExcelTools; - - -public final class CellNotFoundException - extends Exception { - private static final long serialVersionUID = 2805244698649022748L; - - public CellNotFoundException(String message) { - super(message); - } - - public CellNotFoundException(String sheet, int row, int col) { - super(sheet + "!" - + ExcelTools.formatLocation(new int[]{row, col}) - + " not found"); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.java b/src/main/java/io/starter/formats/XLS/CellNotFoundException.kt similarity index 66% rename from src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.java rename to src/main/java/io/starter/formats/XLS/CellNotFoundException.kt index fa8dd1e..beb104b 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.java +++ b/src/main/java/io/starter/formats/XLS/CellNotFoundException.kt @@ -20,23 +20,21 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.escher; +package io.starter.formats.XLS -//0xf001 -public class MsofbtBstoreContainer extends EscherRecord { +import io.starter.OpenXLS.ExcelTools - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8979132545323492655L; - public MsofbtBstoreContainer(int fbt, int inst, int version) { - super(fbt, inst, version); - } +class CellNotFoundException : Exception { - protected byte[] getData() { + constructor(message: String) : super(message) {} - return new byte[0]; + constructor(sheet: String, row: Int, col: Int) : super(sheet + "!" + + ExcelTools.formatLocation(intArrayOf(row, col)) + + " not found") { } -} + companion object { + private val serialVersionUID = 2805244698649022748L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/CellPositionConflictException.java b/src/main/java/io/starter/formats/XLS/CellPositionConflictException.kt similarity index 71% rename from src/main/java/io/starter/formats/XLS/CellPositionConflictException.java rename to src/main/java/io/starter/formats/XLS/CellPositionConflictException.kt index 602c8b4..ca45f4a 100644 --- a/src/main/java/io/starter/formats/XLS/CellPositionConflictException.java +++ b/src/main/java/io/starter/formats/XLS/CellPositionConflictException.kt @@ -20,36 +20,38 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * thrown when trying to add a BiffRec to the position of an * existing Cell. */ -public class CellPositionConflictException extends java.lang.Exception { +class CellPositionConflictException(n: String) : java.lang.Exception() { + internal var cellname = "" - /** - * serialVersionUID - */ - private static final long serialVersionUID = -923697641625221233L; - String cellname = ""; - - public CellPositionConflictException(String n) { - super(); - cellname = n; + init { + cellname = n } - public String getMessage() { + override fun getMessage(): String { // This method is derived from class java.lang.Throwable // to do: code goes here - return this.toString(); + return this.toString() } - public String toString() { + override fun toString(): String { // This method is derived from class java.lang.Throwable // to do: code goes here - return "BiffRec position conflicts with another Cell. : '" + cellname + "'"; + return "BiffRec position conflicts with another Cell. : '$cellname'" + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -923697641625221233L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/CellRec.java b/src/main/java/io/starter/formats/XLS/CellRec.kt similarity index 84% rename from src/main/java/io/starter/formats/XLS/CellRec.java rename to src/main/java/io/starter/formats/XLS/CellRec.kt index 7292189..e29bf44 100644 --- a/src/main/java/io/starter/formats/XLS/CellRec.java +++ b/src/main/java/io/starter/formats/XLS/CellRec.kt @@ -20,14 +20,11 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * A record that contains the value of a single cell. * For the moment this is just a tag interface. Eventually the cell-specific - * stuff from {@link BiffRec} should be moved here. + * stuff from [BiffRec] should be moved here. */ -public interface CellRec - extends BiffRec, CellAddressible { - -} +interface CellRec : BiffRec, CellAddressible diff --git a/src/main/java/io/starter/formats/XLS/CellTypeMismatchException.java b/src/main/java/io/starter/formats/XLS/CellTypeMismatchException.kt similarity index 69% rename from src/main/java/io/starter/formats/XLS/CellTypeMismatchException.java rename to src/main/java/io/starter/formats/XLS/CellTypeMismatchException.kt index 1dc787d..dfdb784 100644 --- a/src/main/java/io/starter/formats/XLS/CellTypeMismatchException.java +++ b/src/main/java/io/starter/formats/XLS/CellTypeMismatchException.kt @@ -20,34 +20,37 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * Thrown when trying to change the value of a BiffRec to an incompatible * data type. - *

                  + * + * * For example: changing a float BiffRec value to a String containing non-numeric * characters would throw one of these. */ -public class CellTypeMismatchException extends java.lang.NumberFormatException { +class CellTypeMismatchException(n: String) : java.lang.NumberFormatException() { + internal var cellname = "" - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8664358251873265167L; - String cellname = ""; + init { + this.cellname = n + } - public CellTypeMismatchException(String n) { - super(); - this.cellname = n; + override fun toString(): String { + return "Cell Type Mismatch Exception trying to set value of Cell: $cellname" } - public String toString() { - return "Cell Type Mismatch Exception trying to set value of Cell: " + cellname; + override fun getMessage(): String { + return toString() } - public String getMessage() { - return toString(); + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 8664358251873265167L } } diff --git a/src/main/java/io/starter/formats/XLS/Cf.java b/src/main/java/io/starter/formats/XLS/Cf.java deleted file mode 100644 index fc03c56..0000000 --- a/src/main/java/io/starter/formats/XLS/Cf.java +++ /dev/null @@ -1,2387 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.Dxf; -import io.starter.formats.OOXML.Fill; -import io.starter.formats.XLS.formulas.FormulaParser; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.formats.XLS.formulas.PtgArray; -import io.starter.formats.XLS.formulas.PtgRefN; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Stack; - - -/** - * Cf: Conditional Formatting Conditions 0x1B1
                  - *

                  - * This record stores a conditional formatting condition - *

                  - *

                  - * There are some restrictions in the usage of conditional formattings: - * In the user interface it is possible to modify the font style (boldness and posture), the text colour, the underline style - * and the strikeout style. It is not possible to change the used font, the font height, and the escapement style, though it is - * possible to specify a new font height and escapement style in this record which are correctly displayed - * It is not possible to change a border line style, but to preserve the line colour, and vice versa. Diagonal lines are not - * supported at all. The user interface only offers thin line styles, but files containing other line styles work correctly too. - * It is not possible to set the background pattern colour to No colour (using system window background - *

                  - *

                  - *

                  - * OFFSET NAME SIZE CONTENTS - * ----- - * 4 ct 1 Conditional formatting type - * 5 cp 1 Conditional formatting operator - * 6 cce1 2 Count of bytes in rgce1 -- size of formula data for first value or formula - * 8 cce2 2 Count of bytes in rgce2 -- size of formula data for first value or formula: used for second part of 'between' and 'not between' comparison, else 0 - * 10 4 Option flags (see below) - * 2 Not used - * 16 118 (optional, only if font = 1, see option flags) Font formatting block, see below - * var 8 (optional, only if bord = 1, see option flags) Border formatting block, see below - * var 4 (optional, only if patt = 1, see option flags) Pattern formatting block, see below - * var rgbdxf var Conditional format to apply - * var rgce1 var First formula for this condition (RPN token array without size field, ?4) - * var rgce2 var Second formula for this condition (RPN token array without size field, ?4) - *

                  - * Conditional formatting operator: - * 00H = No comparison (only valid for formula type, see above) - * 01H = Between 05H = Greater than - * 02H = Not between 06H = Less than - * 03H = Equal 07H = Greater or equal - * 04H = Not equal 08H = Less or equal - *

                  - * Option Flags - * If none of the formatting attributes is set, the option flags field contains 00000000H. - * The following table assumes that - * the conditional formatting contains at least one modified formatting attribute - * (it will occur at least one of the formatting - * information blocks in the record). In difference to the first case some of the - * bits are always set now. - * All flags specifying that an attribute is modified are 02, if the conditional formatting - * changes the respective attribute, - * and 12, if the original cell formatting is preserved. The flags for modified font - * attributes are not contained in this - * option flags field, but in the font formatting block itself. ! - *

                  - * Bit Mask Contents - * 9-0 000003FFH Always 11.1111.11112 (but not used) - * 10 00000400H 0 = Left border style and colour modified (bord - left ) - * 11 00000800H 0 = Right border style and colour modified (bord - right ) - * 12 00001000H 0 = Top border style and colour modified (bord - top ) - * 13 00002000H 0 = Bottom border style and colour modified (bord - bot ) - * 15-14 0000C000H Always 112 (but not used) - * 16 00010000H 0 = Pattern style modified (patt - style ) - * 17 00020000H 0 = Pattern colour modified (patt - col ) - * 18 00040000H 0 = Pattern background colour modified (patt - bgcol ) - * 21-19 00380000H Always 1112 (but not used) - * 26 04000000H 1 = Record contains font formatting block (font) - * 28 10000000H 1 = Record contains border formatting block (bord) - * 29 20000000H 1 = Record contains pattern formatting block (patt) - * - * @see Condfmt - */ -public final class Cf extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5624169378370505532L; - short ct = 0; // Conditional formatting type - short cp = 0; // Conditional formatting operator - int cce1 = 0; // Count of bytes in rgce1 - int cce2 = 0; // Count of bytes in rgce2 - String rgbdxf = ""; // Conditional format to apply - String rgce1 = ""; // First formula for this condition - String rgce2 = ""; // Second formula for this condition - int flags = 0; // option flags 20080303 KSC: - boolean bHasFontBlock = false; // "" - boolean bHasBorderBlock = false; // "" - boolean bHasPatternBlock = false; // "" - - private Ptg prefHolder = null; // this is a placeholder ptg for the conditional format expression - private Stack expression1 = new Stack(); // - private Stack expression2 = new Stack(); // - private Formula formula1 = null; // - private Formula formula2 = null; // - private String containsText = null; // OOXML-specific if type==containsText, this will hold the actual text to test - - private Condfmt condfmt = null; - // Offset Size Contents - // 0 2 Fill pattern style: - // Bit Mask Contents - // 15-10 FC00H Fill pattern style (only if patt - style = 0, ?3.11) - public static final int PATTERN_FILL_STYLE = 0xFC00; - private int patternFillStyle = -1; - - // 2 2 Fill pattern colour indexes: - // Bit Mask Contents - // 6-0 007FH Colour index (?6.70) for pattern (only if patt - col = 0) - // 13-7 3F80H Colour index (?6.70) for pattern background (only if patt - bgcol = 0) - public static final int PATTERN_FILL_COLOR = 0x007F; - public static final int PATTERN_FILL_BACK_COLOR = 0x3F80; - private int patternFillColorsFlag = 0; - private int patternFillColor = 0; - private int patternFillColorBack = -1; - private Fill fill = null; - private Font font = null; - - // Offset Size Contents - // 0 64 Not used - // 64 4 Font height (in twips = 1/20 of a point); or FFFFFFFFH to preserve the cell font height - private int fontHeight = -1; - - // 68 4 Font options: - // Bit Mask Contents - // 1 00000002H Posture: 0 = Normal; 1 = Italic (only if font - style = 0) - // 7 00000080H Cancellation: 0 = Off; 1 = On (only if font - canc = 0) - public static final int FONT_OPTIONS_POSTURE = 0x2; - public static final int FONT_OPTIONS_CANCELLATION = 0x80; - public static final int FONT_OPTIONS_POSTURE_NORMAL = 0; - public static final int FONT_OPTIONS_POSTURE_ITALIC = 1; - public static final int FONT_OPTIONS_CANCELLATION_OFF = 0; - public static final int FONT_OPTIONS_CANCELLATION_ON = 1; - private int fontOptsFlag = -1; - - // 72 2 Font weight (100-1000, only if font - style = 0). - private int fontWeight = -1; - - // Standard values are 0190H (400) for normal text and 02BCH (700) for bold text. - - // 74 2 Escapement type (only if font - esc = 0): - // 0000H = None; 0001H = Superscript; 0002H = Subscript - public static final int FONT_ESCAPEMENT_NONE = 0x0; - public static final int FONT_ESCAPEMENT_SUPER = 0x1; - public static final int FONT_ESCAPEMENT_SUB = 0x2; - private int fontEscapementFlag = -1; - - // 76 1 Underline type (only if font - underl = 0): - public static final int FONT_UNDERLINE_NONE = 0x0; - public static final int FONT_UNDERLINE_SINGLE = 0x1; - public static final int FONT_UNDERLINE_DOUBLE = 0x2; - public static final int FONT_UNDERLINE_SINGLEACCOUNTING = 0x21; - public static final int FONT_UNDERLINE_DOUBLEACCOUNTING = 0x22; - private int fontUnderlineStyle = -1; - - private int fontColorIndex = -1; - - public static final int FONT_MODIFIED_OPTIONS_STYLE = 0x00000002; - public static final int FONT_MODIFIED_OPTIONS_CANCELLATIONS = 0x00000080; - private int fontModifiedOptionsFlag = -1; - private int fontEscapementFlagModifiedFlag = -1; - private int fontUnderlineModifiedFlag = -1; - - /** - * Border Formatting Block - */ - // Offset Size Contents - // 0 2 Border line styles: - // Bit Mask Contents - // 3-0 000FH Left line style (only if bord - left = 0, ?3.10) - // 7-4 00F0H Right line style (only if bord - right = 0, ?3.10) - // 11-8 0F00H Top line style (only if bord - top = 0, ?3.10) - // 15-12 F000H Bottom line style (only if bord - bot = 0, ?3.10) - public static final int BORDER_LINESTYLE_LEFT = 0x000F; - public static final int BORDER_LINESTYLE_RIGHT = 0x00F0; - public static final int BORDER_LINESTYLE_TOP = 0x0F00; - public static final int BORDER_LINESTYLE_BOTTOM = 0xF000; - // if flags & BORDER_MODIFIED_XX == 0 means that this particular border has been modified - public static final int BORDER_MODIFIED_LEFT = 0x0400; - public static final int BORDER_MODIFIED_RIGHT = 0x0800; - public static final int BORDER_MODIFIED_TOP = 0x1000; - public static final int BORDER_MODIFIED_BOTTOM = 0x2000; - private short borderLineStylesFlag = 0; - private int borderLineStylesLeft = -1; - private int borderLineStylesRight = -1; - private int borderLineStylesTop = -1; - private int borderLineStylesBottom = -1; - - // 2 4 Border line colour indexes: - // Bit Mask Contents - // 6-0 0000007FH Colour index (?6.70) for left line (only if bord - left = 0) - // 13-7 00003F80H Colour index (?6.70) for right line (only if bord - right = 0) - // 22-16 007F0000H Colour index (?6.70) for top line (only if bord - top = 0) - // 29-23 3F800000H Colour index (?6.70) for bottom line (only if bord - bot = 0) - public static final int BORDER_LINECOLOR_LEFT = 0x0000007F; - public static final int BORDER_LINECOLOR_RIGHT = 0x00003F80; - public static final int BORDER_LINECOLOR_TOP = 0x007F0000; - public static final int BORDER_LINECOLOR_BOTTOM = 0x3F800000; - private int borderLineColorsFlag = 0; - private int borderLineColorLeft = 0; - private int borderLineColorRight = 0; - private int borderLineColorTop = 0; - private int borderLineColorBottom = 0; - - // TODO: finish Cf prototype bytes + formatting options such as font block - //private byte[] PROTOTYPE_BYTES = {1, 5, 3, 0, 0, 0, -1, -1, 59, -92, 2, -128, 0, 0, 2, 0, 2, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0, 6, -36, 0, -76, 5, 83, -17, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1, -1, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, -97, -1, -1, -1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, -64, 22, 30, 123, 0}; - // a very basic cf which only contains a pattern fill block and function compares <= 100 -// private byte[] PROTOTYPE_BYTES= {1, 8, 3, 0, 0, 0, -1, -1, 59, -96, 2, -128, 0, 0, -64, 26, 30, 100, 0}; - private byte[] PROTOTYPE_BYTES = {1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};//, -64, 26, 30, 100, 0}; - -// 0x1,0x1,0x3,0x0,0x3,0x0,0xf,0xc,0x3f,0x90,0x2,0x80,0x11,0x11,0x14,0xa,0x14,0xa,0x0,0x0,0x1e,0x7b,0x0,0x1e,0xea,0x0}; - - - /** - * default constructor - */ - public Cf() { - } - - /** - * constructor which takes the cellrange of cells - * and the Condfmt that reference this conditional format rule - * - * @param f the condfmt - * @param r the cellrange - */ - public Cf(Condfmt f) { - this(); - setData(this.PROTOTYPE_BYTES); - this.setCondfmt(f); - } - - - /** Pattern Formatting Block */ - /** - * initialize the Cf record - */ - public void init() { - super.init(); - data = this.getData(); - ct = this.getByteAt(0); - cp = this.getByteAt(1); - cce1 = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - cce2 = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - //parsing of formula refs - flags = ByteTools.readInt(this.getByteAt(6), this.getByteAt(7), this.getByteAt(8), this.getByteAt(9)); - // Font formatting Block - bHasFontBlock = ((flags & 0x04000000) == 0x04000000); - // Border Formatting Block - bHasBorderBlock = ((flags & 0x10000000) == 0x10000000); - // Pattern Formating Block - bHasPatternBlock = ((flags & 0x20000000) == 0x20000000); - int pos = 12; - - if (bHasFontBlock) { // handle Font formatting section - pos += 64; // 1st 64 bits of font block is unused - // 64 4 Font height (in twips = 1/20 of a point); or FFFFFFFFH to preserve the cell font height - fontHeight = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)); - - // 68 4 Font options: - // Bit Mask Contents - // 1 00000002H Posture: 0 = Normal; 1 = Italic (only if font - style = 0) - // 7 00000080H Cancellation: 0 = Off; 1 = On (only if font - canc = 0) - fontOptsFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)); - - // 72 2 Font weight (100-1000, only if font - style = 0). - // Standard values are 0190H (400) for normal text and 02BCH (700) for bold text. - fontWeight = ByteTools.readShort(getByteAt(pos++), getByteAt(pos++)); - - // 74 2 Escapement type (only if font - esc = 0): - // 0000H = None; 0001H = Superscript; 0002H = Subscript - // FONT_ESCAPEMENT_NONE = 0x0; - // FONT_ESCAPEMENT_SUPER = 0x1; - // FONT_ESCAPEMENT_SUB = 0x2; - fontEscapementFlag = ByteTools.readShort(getByteAt(pos++), getByteAt(pos++)); - - - // 76 1 Underline type (only if font - underl = 0): - // 00H = None - // 01H = Single 21H = Single accounting - // 02H = Double 22H = Double accounting - // FONT_UNDERLINE_NONE = 0x0; - // FONT_UNDERLINE_SINGLE = 0x1; - // FONT_UNDERLINE_DOUBLE = 0x2; - fontUnderlineStyle = getByteAt(pos++); - - // 77 3 Not used - pos += 3; - - // 80 4 Font colour index (?6.70); or FFFFFFFFH to preserve the cell font colour - fontColorIndex = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)); - - // 84 4 Not used - pos += 4; - - // 88 4 Option flags for modified font attributes: - // Bit Mask Contents - // 1 00000002H 0 = Font style (posture or boldness) modified (font - style ) - // 4-3 00000018H Always 112 (but not used) - // 7 00000080H 0 = Font cancellation modified (font - canc ) - fontModifiedOptionsFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)); - - // 92 4 0 = Escapement type modified (font - esc ) - fontEscapementFlagModifiedFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)); - - // 96 4 0 = Underline type modified (font - underl ) - fontUnderlineModifiedFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)); - // 100 16 Not used - pos += 16; - // 116 2 0001H - pos += 2; - if (pos != 130) { - Logger.logWarn("Cf font block parsing pos mismatch" + pos); - } - getFont(); - } - if (bHasBorderBlock) { - - // Offset Size Contents - // 0 2 Border line styles: - // Bit Mask Contents - // 3-0 000FH Left line style (only if bord - left = 0, ?3.10) - // 7-4 00F0H Right line style (only if bord - right = 0, ?3.10) - // 11-8 0F00H Top line style (only if bord - top = 0, ?3.10) - // 15-12 F000H Bottom line style (only if bord - bot = 0, ?3.10) - // BORDER_LINESTYLE_LEFT = 0x000F; - // BORDER_LINESTYLE_RIGHT = 0x00F0; - // BORDER_LINESTYLE_TOP = 0x0F00; - // BORDER_LINESTYLE_BOTTOM = 0xF000; - borderLineStylesFlag = ByteTools.readShort(getByteAt(pos++), getByteAt(pos++)); - updateBorderLineStyles(); - - // 2 4 Border line colour indexes: - // Bit Mask Contents - // 6-0 0000007FH Colour index (?6.70) for left line (only if bord - left = 0) - // 13-7 00003F80H Colour index (?6.70) for right line (only if bord - right = 0) - // 22-16 007F0000H Colour index (?6.70) for top line (only if bord - top = 0) - // 29-23 3F800000H Colour index (?6.70) for bottom line (only if bord - bot = 0) - // BORDER_LINECOLOR_LEFT = 0x0000007F; - // BORDER_LINECOLOR_RIGHT = 0x00003F80; - // BORDER_LINECOLOR_TOP = 0x007F0000; - // BORDER_LINECOLOR_BOTTOM = 0x3F800000; - borderLineColorsFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)); - updateBorderLineColors(); - - //6 2 Not used - pos += 2; - } - if (bHasPatternBlock) { - // Offset Size Contents - - // 0 2 Fill pattern style: - // Bit Mask Contents - // 15-10 FC00H Fill pattern style (only if patt - style = 0, ?3.11) - // PATTERN_FILL_STYLE = 0xFC00; - patternFillStyle = (ByteTools.readShort(getByteAt(pos++), getByteAt(pos++)) >> 9); - /* very strangely, patternFillStyle appears to be 0 when it should be 1 (solid) - * found in testing through our test suite ... - * according to documetation this algorithm *appears* ok but the doc is really confusing on cf's - */ - - // 2 2 Fill pattern colour indexes: - - // Bit Mask Contents - // 6-0 007FH Colour index (?6.70) for pattern (only if patt - col = 0) - // 13-7 3F80H Colour index (?6.70) for pattern background (only if patt - bgcol = 0) - // PATTERN_FILL_COLOR = 0x007F; - // PATTERN_FILL_BACK_COLOR = 0x3F80; - patternFillColorsFlag = ByteTools.readShort(getByteAt(pos++), getByteAt(pos++)); - - // update the current vals - updatePatternFillColors(); - - } - - int postest = 12; - if (bHasFontBlock) - postest += 118; - if (bHasBorderBlock) - postest += 8; - if (bHasPatternBlock) - postest += 4; - - if (postest != pos) { - Logger.logWarn("Cf bad pos offset during init()."); - pos = postest; - } - // 1st formula data= pos->cce1 - byte[] function = this.getBytesAt(pos, cce1); - try { - expression1 = ExpressionParser.parseExpression(function, this, cce1); - } catch (Exception e) { - Logger.logErr("Initializing expression1 for Cf failed: " + new String(function)); - } - pos += cce1; - // 2nd formula data= pos+cce1->cce2 - function = this.getBytesAt(pos, cce2); - if (cce2 > 0) { - try { - expression2 = ExpressionParser.parseExpression(function, this, cce2); - } catch (Exception e) { - Logger.logErr("Initializing expression2 for Cf failed: " + new String(function)); - } - } - if (DEBUGLEVEL > DEBUG_LOW) - Logger.logInfo("Cf record encountered."); - } - - /** - * take current state of Cf and update record - */ - private void updateRecord() { - byte[] newdata = new byte[12]; // enough room for basics; formatting blocks will be appended - newdata[0] = (byte) ct; - newdata[1] = (byte) cp; - flags = 0x3FF | 0xC000 | 0x380000; // set required and unused bits of flag - if (bHasFontBlock) - flags = (flags | 0x04000000); - if (bHasBorderBlock) { - flags = (flags | 0x10000000); - flags |= 0x400; // left border mod - flags |= 0x800; // right border mod - flags |= 0x1000; // top border mod - flags |= 0x2000; // bottom border mod - } - if (bHasPatternBlock) { - flags = (flags | 0x20000000); - flags |= 0x10000; // patt style mod - flags |= 0x20000; // patt fg color (pattern color) mod - flags |= 0x40000; // pat bg color mod - } - - byte[] b = ByteTools.cLongToLEBytes(flags); - System.arraycopy(b, 0, newdata, 6, 4); - - int pos = 12; - if (bHasFontBlock) { // update font section - newdata = ByteTools.append(new byte[118], newdata); - pos += 64; // 1st 64 bits of font block is unused - b = ByteTools.cLongToLEBytes(fontHeight); - System.arraycopy(b, 0, newdata, pos, 4); - pos += 4; - b = ByteTools.cLongToLEBytes(fontOptsFlag); - System.arraycopy(b, 0, newdata, pos, 4); - pos += 4; - b = ByteTools.shortToLEBytes((short) fontWeight); - System.arraycopy(b, 0, newdata, pos, 2); - pos += 2; - b = ByteTools.shortToLEBytes((short) fontEscapementFlag); - System.arraycopy(b, 0, newdata, pos, 2); - pos += 2; - b = ByteTools.shortToLEBytes((short) fontUnderlineStyle); - System.arraycopy(b, 0, newdata, pos, 1); - pos += 1; - // 77 3 Not used - pos += 3; - b = ByteTools.cLongToLEBytes(fontColorIndex); - System.arraycopy(b, 0, newdata, pos, 4); - pos += 4; - // 84 4 Not used - pos += 4; // 88: - b = ByteTools.cLongToLEBytes(fontModifiedOptionsFlag); - System.arraycopy(b, 0, newdata, pos, 4); - pos += 4; - b = ByteTools.cLongToLEBytes(fontEscapementFlagModifiedFlag); - System.arraycopy(b, 0, newdata, pos, 4); - pos += 4; - b = ByteTools.cLongToLEBytes(fontUnderlineModifiedFlag); - System.arraycopy(b, 0, newdata, pos, 4); - pos += 4; - // 100 16 Not used - pos += 16; - // 116 2 0001H - pos++; - newdata[pos++] = 1; - } - if (bHasBorderBlock) { - newdata = ByteTools.append(new byte[8], newdata); - b = ByteTools.shortToLEBytes(borderLineStylesFlag); - System.arraycopy(b, 0, newdata, pos, 2); - pos += 2; - b = ByteTools.cLongToLEBytes(borderLineColorsFlag); - System.arraycopy(b, 0, newdata, pos, 4); - pos += 4; - //6 2 Not used - pos += 2; - } - if (bHasPatternBlock) { - newdata = ByteTools.append(new byte[4], newdata); - b = ByteTools.shortToLEBytes((short) (patternFillStyle << 9)); - System.arraycopy(b, 0, newdata, pos, 2); - pos += 2; - b = ByteTools.shortToLEBytes((short) patternFillColorsFlag); - System.arraycopy(b, 0, newdata, pos, 2); - pos += 2; - } - - if (formula1 != null) { - byte[] function = getFormulaExpression(formula1); - newdata = ByteTools.append(function, newdata); - cce1 = function.length; - b = ByteTools.shortToLEBytes((short) cce1); - newdata[2] = b[0]; - newdata[3] = b[1]; - } - if (formula2 != null) { - byte[] function = getFormulaExpression(formula2); - newdata = ByteTools.append(function, newdata); - cce2 = function.length; - b = ByteTools.shortToLEBytes((short) cce2); - newdata[4] = b[0]; - newdata[5] = b[1]; - } - this.setData(newdata); -// this.init(); DO NOT DO AS can overwrite OOXML-specifics - } - - /** - * return the expression bytes of the specified formula - * - * @param f - * @return - */ - private byte[] getFormulaExpression(Formula f) { - boolean hasArray = false; - byte[] expbytes = new byte[0]; - byte[] arraybytes = null; - Stack expression = f.getExpression(); - for (int i = 0; i < expression.size(); i++) { - Object o = expression.elementAt(i); - Ptg ptg = (Ptg) o; - byte[] b; - if (o instanceof PtgArray) { - PtgArray pa = (PtgArray) o; - b = pa.getPreRecord(); - arraybytes = ByteTools.append(pa.getPostRecord(), arraybytes); - hasArray = true; - } else { - b = ptg.getRecord(); - } - expbytes = ByteTools.append(b, expbytes); - } - if (hasArray) { - expbytes = ByteTools.append(arraybytes, expbytes); - } - return expbytes; - } - - - /** - * helper method to set the style values on this object from a style string - *
                  each name/value pair is delimited by ; - *
                  possible tokens: - *
                  pattern pattern fill # - *
                  color pattern fg color - *
                  patterncolor pattern bg color - *
                  vertical vertical alignnment - *
                  horizontal horizontal alignment - *
                  border sub-tokens: border-top, border-left, border-bottom, border-top - * - * @param style - * @param cx - */ - public static void setStylePropsFromString(String style, Cf cx) { - String[] toks = StringTool.getTokensUsingDelim(style, ";"); - // iterate styles, set values - for (int t = 0; t < toks.length; t++) { - int pos = toks[t].indexOf(":"); - String n = ""; -// TODO: border line sizes, interpret border line styles -// TODO: handle vertical, horizontal alignment, number format ... - if (pos > 0) - n = toks[t].substring(0, pos); - - String v = toks[t].substring(toks[t].indexOf(":") + 1); - n = StringTool.strip(n, '"'); - v = StringTool.strip(v, '#'); - n = StringTool.allTrim(n); - v = StringTool.allTrim(v); - n = n.toLowerCase(); - if (n.indexOf("border") == 0) { // parse the border settings - String[] vs = StringTool.getTokensUsingDelim(v, " "); - int sz = -1, cz = -1; - int stl = -1; - String clr = null; - try { - sz = Integer.parseInt(vs[0]); // size - clr = vs[2]; // color String - // TODO: no way to set border size as of yet - - // interpret style string into #: - for (int i = 0; i < FormatConstants.BORDER_NAMES.length; i++) { - if (FormatConstants.BORDER_NAMES[i].equals(vs[1])) { - stl = i; - break; - } - } - - // HexStringToColorInt - // java.awt.Color c = FormatHandle.HexStringToColor(clr); - cz = FormatHandle.HexStringToColorInt(clr, FormatHandle.colorBACKGROUND); - // Logger.logInfo("ix " + sz + " " + clr + " " + stl ); - - } catch (Exception ex) { - } - if (n.indexOf("border-top") == 0) { - if (clr != null) { // set color - cx.setBorderLineColorTop(cz); - } - if (stl > -1) { - cx.setBorderLineStylesTop(stl); - } - } else if (n.indexOf("border-left") == 0) { - if (clr != null) { // set color - cx.setBorderLineColorLeft(cz); - } - if (stl > -1) { - cx.setBorderLineStylesLeft(stl); - } - } else if (n.indexOf("border-bottom") == 0) { - if (clr != null) { // set color - cx.setBorderLineColorBottom(cz); - } - if (stl > -1) { - cx.setBorderLineStylesBottom(stl); - } - } else if (n.indexOf("border-right") == 0) { - if (clr != null) { // set color - cx.setBorderLineColorRight(cz); - } - if (stl > -1) { - cx.setBorderLineStylesRight(stl); - } - } - - } else if (n.equalsIgnoreCase("text-line-through")) { - if (v.equalsIgnoreCase("none")) { - cx.setFontStriken(false); - } else { - cx.setFontStriken(true); - } - } else if (n.equalsIgnoreCase("fg")) { // font color - // set the color - int cl = FormatHandle.HexStringToColorInt(v, FormatHandle.colorFOREGROUND); - cx.setFontColorIndex(cl); - } else if (n.equalsIgnoreCase("pattern")) { //fill pattern - int vv = Integer.parseInt(v); - cx.setPatternFillStyle(vv); - } else if (n.equalsIgnoreCase("color") || n.equalsIgnoreCase("patterncolor")) { // fill (pattern or fg) color - int cl = FormatHandle.HexStringToColorInt(v, FormatHandle.colorFONT); // finds best match - cx.setPatternFillColor(cl, v); - } else if (n.equalsIgnoreCase("background")) { // fill bg color - int cl = FormatHandle.HexStringToColorInt(v, FormatHandle.colorBACKGROUND); // finds best match - cx.setPatternFillColorBack(cl); - // ALIGNMENT - } else if (n.equalsIgnoreCase("alignment-horizontal")) { - int s = Integer.parseInt(v); -// TODO: set alignment - } else if (n.equalsIgnoreCase("alignment-vertical")) { - int s = Integer.parseInt(v); -// TODO: set alignment - } else if (n.equalsIgnoreCase("numberformat")) { - int s = Integer.parseInt(v); -// TODO: set number format - // FONT options - } else if (n.equalsIgnoreCase("font-height")) { - int s = Integer.parseInt(v); - cx.setFontHeight(s); - } else if (n.equalsIgnoreCase("font-weight")) { - int s = Integer.parseInt(v); - cx.setFontWeight(s); - } else if (n.equalsIgnoreCase("font-EscapementFlag")) { - int s = Integer.parseInt(v); // 0= none, 1= superscript, 2= subscript - cx.setFontEscapement(s); - } else if (n.equalsIgnoreCase("font-striken")) { - boolean b = Boolean.valueOf(v).booleanValue(); - cx.setFontStriken(b); - } else if (n.equalsIgnoreCase("font-italic")) { - boolean b = Boolean.valueOf(v).booleanValue(); - cx.setFontItalic(b); - } else if (n.equalsIgnoreCase("font-ColorIndex")) { - int s = Integer.parseInt(v); - cx.setFontColorIndex(s); - } else if (n.equalsIgnoreCase("font-UnderlineStyle")) { - int s = Integer.parseInt(v); - cx.setFontUnderlineStyle(s); - } -/* TODO: handle - fontName? - * fontOptsFlag - fontModifiedOptionsFlag - fontUnderlineModified -*/ - } - cx.updateRecord(); - } - - - /** - * set the conditional format format settings from the OOXML dxf, or differential xf, format settings - * - * @param dxf - * @param cf - */ - public static void setStylePropsFromDxf(Dxf dxf, Cf cf) { - int[] borderStyles = dxf.getBorderStyles(); - if (borderStyles != null) { // then should have every other element - int[] borderColors = dxf.getBorderColors(); - int[] borderSizes = dxf.getBorderSizes(); - cf.setBorderLineColorTop(borderColors[0]); - cf.setBorderLineStylesTop(borderStyles[0]); - cf.setBorderLineColorLeft(borderColors[1]); - cf.setBorderLineStylesLeft(borderStyles[1]); - cf.setBorderLineColorBottom(borderColors[2]); - cf.setBorderLineStylesBottom(borderStyles[2]); - cf.setBorderLineColorRight(borderColors[3]); - cf.setBorderLineStylesRight(borderStyles[3]); -// TODO border size?? - } - - // FILL **************** - int fls = dxf.getFillPatternInt(); - if (fls >= 0) { - cf.setPatternFillStyle(fls); - int fg = dxf.getFg(); - int bg = dxf.getBg(); - if (fg != -1) - cf.setPatternFillColor(fg, null); - if (bg != -1) - cf.setPatternFillColorBack(bg); - } - cf.fill = dxf.getFill(); // save OOXML var as color settings sometimes cannot be interpreted in 2003 v. - - int z; - // ALIGNMENT ****************** - String s = dxf.getHorizontalAlign(); - if (s != null) { - z = Integer.parseInt(s); -//TODO: set alignment - } - s = dxf.getVerticalAlign(); - if (s != null) { - z = Integer.parseInt(s); -//TODO: set alignment - } - - // Number Format *************** - s = dxf.getNumberFormat(); - if (s != null) { - z = Integer.parseInt(s); -//TODO: set number format - } - - - // FONT ************************ - if (dxf.getFont() != null) { - cf.parseFont(dxf.getFont()); - cf.font = dxf.getFont(); - } - - -/* TODO: handle - fontName? -* fontOptsFlag - fontModifiedOptionsFlag - fontUnderlineModified -*/ - cf.updateRecord(); - } - - /** - * sets the operator for this Cf Rule from a String - * - * @param s - */ - public void setOperator(String s) { - this.cp = getConditionFromString(s); - if (cp == 0x0) { // no comparison - expression1 = ExpressionParser.parseExpression(s.getBytes(), this); - } else { - // val1 = cfx.getFormula1().calculateFormula(); - } - } - - /** - * sets the Operator of this Cf Rule - *
                  possible values: - *

                  0= no comparison - *

                  1= between - *

                  2= not between - *

                  3= equal - *

                  4= not equal - *

                  5= greater than - *

                  6= less than - *

                  7= greater than or equal - *

                  8= less than or equal - * ******************** - * 2007-specific operators - *

                  9= begins With - *

                  10= ends With - *

                  11= contains Text - *

                  12= not contains - * - * @param int qualifier - constant value as above - */ - public void setOperator(int op) { - this.cp = (short) op; - } - - /** - * returns the operator or qualifier of this Cf Rue as an int value - *
                  possible values: - *

                  0= no comparison - *

                  1= between - *

                  2= not between - *

                  3= equal - *

                  4= not equal - *

                  5= greater than - *

                  6= less than - *

                  7= greater than or equal - *

                  8= less than or equal - * ******************** - * 2007-specific operators - *

                  9= begins With - *

                  10= ends With - *

                  11= contains Text - *

                  12= not contains - * - * @return - */ - public short getOperator() { - return this.cp; - - } - - /** - * sets the type of this Cf rule from the int value - *

                  1= Cell is ("Cell value is") - *

                  2= expression ("formula value is") - * - * @param int type as above - */ - protected void setType(int type) { - this.ct = (short) type; - } - - /** - * return the type of this Cf Rule as an int - *

                  1= Cell is ("Cell value is") - *

                  2= expression ("formula value is") - * - * @return - */ - public short getType() { - return this.ct; - } - - /** - * reutrn the string representation of this Cf Rule type - * - * @return - */ - public String getTypeString() { - return Cf.translateType(this.ct); - - } - - /** - * sets the first condition from a String - * - * @param s - */ - public void setCondition1(String s) { - // takes "123" aka Value1 and converts to formula expression =123 - s = Cf.unescapeFormulaString(s); - if (s.indexOf("=") != 0) { - s = "=" + s; - try { - int[] r = {0, 0}; - formula1 = FormulaParser.getFormulaFromString(s, this.getSheet(), r); - formula1.setWorkBook(this.getWorkBook()); - expression1 = formula1.getExpression(); - } catch (Exception e) { - } - } - } - - /** - * sets the second condition from a String - * - * @param s - */ - public void setCondition2(String s) { - if (s.equals("")) return; // none - - s = Cf.unescapeFormulaString(s); - // takes "123" aka Value1 and converts to formula expression =123 - if (cp == 0x0) { // no comparison - expression2 = ExpressionParser.parseExpression(s.getBytes(), this); - } else { - if (s.indexOf("=") != 0) { - s = "=" + s; - try { - int[] r = {0, 0}; - formula2 = FormulaParser.getFormulaFromString(s, this.getSheet(), r); - expression2 = formula1.getExpression(); - } catch (Exception e) { - } - } - } - this.cce2 = s.length(); - } - - /** - * @return Returns the condfmt. - */ - public Condfmt getCondfmt() { - return condfmt; - } - - /** - * @param condfmt The condfmt to set. - */ - public void setCondfmt(Condfmt condfmt) { - this.condfmt = condfmt; - } - - /** - * @return Returns the fontEscapementFlag. - */ - public int getFontEscapement() { - return fontEscapementFlag; - } - - - /** - * returns the pattern color, if any, as an HTML color String. Includes custom OOXML colors. - * - * @return String HTML Color String - */ - public String getPatternFgColor() { - if (fill != null) - return fill.getFgColorAsRGB(getWorkBook().getTheme()); - if (patternFillColor != -1) - return FormatHandle.colorToHexString(this.getColorTable()[patternFillColor]); - return null; - } - - /** - * returns the pattern background color, if any, as an HTML color String. Includes custom OOXML colors. - * - * @return String HTML Color String - */ - public String getPatternBgColor() { - if (fill != null) - if (patternFillStyle == 1) - return fill.getFgColorAsRGB(getWorkBook().getTheme()); - else - return fill.getBgColorAsRGB(getWorkBook().getTheme()); - if (patternFillColorBack != -1) - return FormatHandle.colorToHexString(this.getColorTable()[patternFillColorBack]); - return null; - } - - /** - * parse cf font into it's member elements - */ - private void parseFont(Font font) { - int z = font.getFontHeight(); - if (z > 0) - setFontHeight(z); - z = font.getFontWeight(); - if (z > 0) - setFontWeight(z); -// } else if (n.equalsIgnoreCase("font-EscapementFlag")) { -// cx.setFontEscapement(s); - setFontStriken(font.getStricken()); - setFontItalic(font.getItalic()); - z = font.getColor(); - if (z > -1) - setFontColorIndex(z); - z = font.getUnderlineStyle(); - if (z > -1) - setFontUnderlineStyle(z); - } - - /** - * @param fontEscapementFlag The fontEscapementFlag to set. - */ - public void setFontEscapement(int fontEscapementFlag) { - this.fontEscapementFlag = fontEscapementFlag; - this.fontEscapementFlagModifiedFlag = 0; // set modified - bHasFontBlock = true; -// if (font!=null) -// font.setScript(ss); - } - - - // Start Font Formatting Block - - /** - * updates the values for the current border colors from the flag - * ----------------------------------------------------------- - */ - void updateBorderLineStyles() { - if ((flags & BORDER_MODIFIED_LEFT) == 0) - this.borderLineStylesLeft = (short) (this.borderLineStylesFlag & BORDER_LINESTYLE_LEFT); - if ((flags & BORDER_MODIFIED_RIGHT) == 0) - this.borderLineStylesRight = (short) ((this.borderLineStylesFlag & BORDER_LINESTYLE_RIGHT) >> 4); - if ((flags & BORDER_MODIFIED_TOP) == 0) - this.borderLineStylesTop = (short) ((this.borderLineStylesFlag & BORDER_LINESTYLE_TOP) >> 8); - if ((flags & BORDER_MODIFIED_BOTTOM) == 0) - this.borderLineStylesBottom = (short) ((this.borderLineStylesFlag & BORDER_LINESTYLE_BOTTOM) >> 12); - bHasBorderBlock = true; - } - - /** - * Apply all the borderLineStyle fields into the current border line styles flag - */ - public void updateBorderLineStylesFlag() { - flags = flags | 0x10000000; // set flags to denote has a border block - bHasBorderBlock = ((flags & 0x10000000) == 0x10000000); - - borderLineStylesFlag = 0; - if (borderLineStylesLeft >= 0) borderLineStylesFlag = (short) borderLineStylesLeft; - if (borderLineStylesRight >= 0) - borderLineStylesFlag = (short) ((borderLineStylesFlag | (borderLineStylesRight) << 4)); - if (borderLineStylesTop >= 0) - borderLineStylesFlag = (short) ((borderLineStylesFlag | (borderLineStylesTop) << 8)); - if (borderLineStylesBottom >= 0) - borderLineStylesFlag = (short) ((borderLineStylesFlag | (borderLineStylesBottom) << 12)); -/* - byte[] data = this.getData(); - byte[] updated = ByteTools.shortToLEBytes(borderLineStylesFlag); - int pos= 12; - if (bHasFontBlock) - pos+=118; - if (data.length< (pos+2)) { - byte[] tmp= new byte[pos+2]; - System.arraycopy(data, 0, tmp, 0, data.length); - this.setData(tmp); - } - data[pos++] = updated[0]; - data[pos++] = updated[1]; -*/ - } - - - //6 2 Not used - - /** - * updates the values for the current border colors from the flag - */ - void updateBorderLineColors() { - if ((flags & BORDER_MODIFIED_LEFT) == 0) - this.borderLineColorLeft = (short) (this.borderLineColorsFlag & BORDER_LINECOLOR_LEFT); - if ((flags & BORDER_MODIFIED_RIGHT) == 0) - this.borderLineColorRight = (short) ((this.borderLineColorsFlag & BORDER_LINECOLOR_RIGHT) >> 7); - if ((flags & BORDER_MODIFIED_TOP) == 0) - this.borderLineColorTop = (short) ((this.borderLineColorsFlag & BORDER_LINECOLOR_TOP) >> 16); - if ((flags & BORDER_MODIFIED_BOTTOM) == 0) - this.borderLineColorBottom = (short) ((this.borderLineColorsFlag & BORDER_LINECOLOR_BOTTOM) >> 23); - } - - /** - * Apply all the borderLineColor fields into the current border line colors flag - */ - public void updateBorderLineColorsFlag() { - flags = flags | 0x10000000; // set flags to denote has a border block - bHasBorderBlock = ((flags & 0x10000000) == 0x10000000); // = true - - borderLineColorsFlag = 0; - borderLineColorsFlag = (borderLineColorsFlag | borderLineColorLeft); - borderLineColorsFlag = ((borderLineColorsFlag | (borderLineColorRight) << 7)); - borderLineColorsFlag = ((borderLineColorsFlag | (borderLineColorTop) << 16)); - borderLineColorsFlag = ((borderLineColorsFlag | (borderLineColorBottom) << 23)); -/* - byte[] data = this.getData(); - byte[] updated = ByteTools.cLongToLEBytes( borderLineColorsFlag); - int pos= 12; - if (bHasFontBlock) - pos+=118; - pos+=2; // skip border line style - if (data.length<(pos+4)) { - byte[] tmp= new byte[pos+4]; - System.arraycopy(data, 0, tmp, 0, data.length); - this.setData(tmp); - } - data[pos++] = updated[0]; - data[pos++] = updated[1]; - data[pos++] = updated[2]; - data[pos++] = updated[3]; - // no need updateBorderLineColors(); -*/ - } - - /** - * updates the values for the current pattern fill colors from the flag - *

                  - * // Bit Mask Contents - * // 15-10 FC00H Fill pattern style (only if patt - style = 0, ?3.11) - * // PATTERN_FILL_STYLE = 0xFC00; - */ - void updatePatternFillColors() { -/* below appears correct in testing this.patternFillColor = (short)(this.patternFillColorsFlag & PATTERN_FILL_COLOR); - this.patternFillColorBack = (short)((this.patternFillColorsFlag & PATTERN_FILL_BACK_COLOR) >> 7); -*/ - this.patternFillColor = (short) ((this.patternFillColorsFlag & PATTERN_FILL_BACK_COLOR) >> 7); - this.patternFillColorBack = (short) (this.patternFillColorsFlag & PATTERN_FILL_COLOR); - bHasPatternBlock = true; - } - - /** - * return the 2007v Fill element, or null if not set - */ - public Fill getFill() { - return fill; - } - - /** - * Apply all the patternFillColor fields into the current pattern colors flag - */ - public void updatePatternFillColorsFlag() { - flags = flags | 0x20000000; // set flags to denote has a pattern block - bHasPatternBlock = true; - patternFillColorsFlag = (short) patternFillColorBack; - patternFillColorsFlag = (short) (patternFillColorsFlag | (patternFillColor << 7)); - } - - public Font getFont() { - if (!this.bHasFontBlock) - return null; - - if (font != null) - return font; - int t = this.fontHeight; - int x = this.fontWeight; - if (t == -1) - t = 180; - else - t *= 20; - if (x == -1) - x = Font.PLAIN; - font = new Font("Arial", x, t); - if (fontColorIndex > -1) - font.setColor(fontColorIndex); - return font; - } - - public java.awt.Color[] getBorderColors() { - if (!this.bHasBorderBlock) - return null; - java.awt.Color[] test = { - this.getColorTable()[this.getBorderLineColorTop()], - this.getColorTable()[this.getBorderLineColorLeft()], - this.getColorTable()[this.getBorderLineColorBottom()], - this.getColorTable()[this.getBorderLineColorRight()]}; - return test; - } - - - public int[] getAllBorderColors() { - if (!this.bHasBorderBlock) - return null; - int[] test = { - this.getBorderLineColorTop(), - this.getBorderLineColorLeft(), - this.getBorderLineColorBottom(), - this.getBorderLineColorRight()}; - return test; - } - - /** - * order= top, left, bottom, right - * - * @return - */ - public int[] getBorderStyles() { - if (!this.bHasBorderBlock) - return null; - int[] test = { - this.getBorderLineStylesTop(), - this.getBorderLineStylesLeft(), - this.getBorderLineStylesBottom(), - this.getBorderLineStylesRight(), - -1}; // diag - return test; - } - - public int[] getBorderSizes() { - if (!this.bHasBorderBlock) - return null; - boolean hasTop = this.getBorderLineStylesTop() > 0; - boolean hasLeft = this.getBorderLineStylesLeft() > 0; - boolean hasBottom = this.getBorderLineStylesBottom() > 0; - boolean hasRight = this.getBorderLineStylesRight() > 0; - int[] test = {(hasTop ? 1 : 0), (hasLeft ? 1 : 0), (hasBottom ? 1 : 0), (hasRight ? 1 : 0), 0}; - return test; - } - - public int getForegroundColor() { - if (!this.bHasPatternBlock) - return -1; - if (fill != null) - return fill.getFgColorAsInt(getWorkBook().getTheme()); - if (this.patternFillStyle == 1) - return this.patternFillColorBack; - else - return this.patternFillColor; - } - - - /** - * @return Returns the fontOptsPosture. - */ - public int getFontOptsPosture() { - return (short) (this.fontOptsFlag & FONT_OPTIONS_POSTURE); - } - - /** - * @param fontOptsPosture The fontOptsPosture to set. - */ - public void setFontOptsPosture(int fontOptsPosture) { - this.fontOptsFlag = (short) (this.fontOptsFlag & FONT_OPTIONS_POSTURE); - bHasFontBlock = true; - } - - /** - * @return Returns the fontOptsCancellation. - */ - public int getFontOptsCancellation() { - return (short) ((this.fontOptsFlag & FONT_OPTIONS_CANCELLATION) >> 7); - } - - /** - * @return Returns the fontOptsItalic. - */ - public boolean getFontItalic() { - return (fontOptsFlag & 0x2) == FONT_OPTIONS_POSTURE_ITALIC; // 1 if italic - } - - /** - * @param fontOptsItalic The fontOptsItalic to set. - */ - public void setFontItalic(boolean italic) { - if (italic) { - this.fontOptsFlag = this.fontOptsFlag | 0x2; // set italic - this.fontModifiedOptionsFlag = this.fontModifiedOptionsFlag | 0x2; - bHasFontBlock = true; - } else { // todo: is below correct? - this.fontOptsFlag = this.fontOptsFlag ^ 0x2; // clear italic bit - this.fontModifiedOptionsFlag = this.fontModifiedOptionsFlag ^ 0x2; - } - if (font != null) font.setItalic(italic); - } - - public boolean getFontStriken() { - return ((fontOptsFlag & 0x80) == FONT_OPTIONS_CANCELLATION_ON); - } - - public void setFontStriken(boolean bStriken) { - if (bStriken) { - fontOptsFlag = (fontOptsFlag | 0x80); - fontModifiedOptionsFlag = (fontModifiedOptionsFlag | 0x80); - bHasFontBlock = true; - } else { // todo: is below correct? - fontOptsFlag = (fontOptsFlag ^ 0x80); // turn off - fontModifiedOptionsFlag = (fontModifiedOptionsFlag ^ 0x80); - } - if (font != null) font.setStricken(bStriken); - } - - /** - * @return Returns true if font escapement is superscript - */ - public boolean getFontEscapementSuper() { - return (fontEscapementFlag == FONT_ESCAPEMENT_SUPER); - } - - /** - * sets the font escapement for this conditional format to superscript - */ - public void setFontEscapementSuper() { - this.fontEscapementFlag = FONT_ESCAPEMENT_SUPER; - this.fontEscapementFlagModifiedFlag = 0; // - this.bHasFontBlock = true; - } - - /** - * @return Returns true if font escapement is subscript - */ - public boolean getFontEscapementSub() { - return (fontEscapementFlag == FONT_ESCAPEMENT_SUB); - } - - /** - * sets the font escapement for this conditional format to subscript - */ - public void setFontEscapementSub() { - this.fontEscapementFlag = FONT_ESCAPEMENT_SUB; - this.fontEscapementFlagModifiedFlag = 0; // - this.bHasFontBlock = true; - } - - /** - * @return Returns the borderLineStylesLeft. - */ - public int getBorderLineStylesLeft() { - return borderLineStylesLeft; - } - - /** - * @param borderLineStylesLeft The borderLineStylesLeft to set. - */ - public void setBorderLineStylesLeft(int b) { - this.borderLineStylesLeft = b; - this.updateBorderLineStylesFlag(); - } - - /** - * @return Returns the borderLineStylesRight. - */ - public int getBorderLineStylesRight() { - return borderLineStylesRight; - } - - /** - * @param borderLineStylesRight The borderLineStylesRight to set. - */ - public void setBorderLineStylesRight(int b) { - this.borderLineStylesRight = b; - this.updateBorderLineStylesFlag(); - } - - /** - * @return Returns the borderLineStylesTop. - */ - public int getBorderLineStylesTop() { - return borderLineStylesTop; - } - - /** - * @param borderLineStylesTop The borderLineStylesTop to set. - */ - public void setBorderLineStylesTop(int b) { - this.borderLineStylesTop = b; - this.updateBorderLineStylesFlag(); - } - - /** - * @return Returns the borderLineStylesBottom. - */ - public int getBorderLineStylesBottom() { - return borderLineStylesBottom; - } - - /** - * @param borderLineStylesBottom The borderLineStylesBottom to set. - */ - public void setBorderLineStylesBottom(int b) { - this.borderLineStylesBottom = b; - this.updateBorderLineStylesFlag(); - } - - /** - * @return Returns the borderLineColorLeft. - */ - public int getBorderLineColorLeft() { - if (borderLineColorLeft > this.getColorTable().length) - return 0; - if (borderLineColorLeft < 0) - return 0; - return borderLineColorLeft; - } - - /** - * @param borderLineColorLeft The borderLineColorLeft to set. - */ - public void setBorderLineColorLeft(int borderLineColorLeft) { - this.borderLineColorLeft = borderLineColorLeft; - // 20091028 KSC: insure flag denotes borderlinecolor is modified - flags = flags & (BORDER_MODIFIED_LEFT - 1); // set flags to denote border top is modified (set bit=0) - this.updateBorderLineColorsFlag(); - } - - /** - * @return Returns the borderLineColorRight. - */ - public int getBorderLineColorRight() { - if (borderLineColorRight > this.getColorTable().length) - return 0; - if (borderLineColorRight < 0) - return 0; - return borderLineColorRight; - } - - /** - * @param borderLineColorRight The borderLineColorRight to set. - */ - public void setBorderLineColorRight(int borderLineColorRight) { - this.borderLineColorRight = borderLineColorRight; - // 20091028 KSC: insure flag denotes borderlinecolor is modified - flags = flags & (BORDER_MODIFIED_RIGHT - 1); // set flags to denote border top is modified (set bit=0) - this.updateBorderLineColorsFlag(); - } - - /** - * @return Returns the borderLineColorTop. - */ - public int getBorderLineColorTop() { - if (borderLineColorTop > this.getColorTable().length) - return 0; - if (borderLineColorTop < 0) - return 0; - return borderLineColorTop; - } - - /** - * @param borderLineColorTop The borderLineColorTop to set. - */ - public void setBorderLineColorTop(int b) { - this.borderLineColorTop = b; - // 20091028 KSC: insure flag denotes borderlinecolor is modified - flags = flags & (BORDER_MODIFIED_TOP - 1); // set flags to denote border top is modified (set bit=0) - this.updateBorderLineColorsFlag(); - if (this.borderLineColorTop != b) - Logger.logWarn("setBorderLineColorTop failed"); - } - - /** - * @return Returns the borderLineColorBottom. - */ - public int getBorderLineColorBottom() { - if (borderLineColorBottom > this.getColorTable().length) - return 0; - if (borderLineColorBottom < 0) - return 0; - return borderLineColorBottom; - } - - /** - * @param borderLineColorBottom The borderLineColorBottom to set. - */ - public void setBorderLineColorBottom(int b) { - this.borderLineColorBottom = b; - // 20091028 KSC: insure flag denotes borderlinecolor is modified - flags = flags & (BORDER_MODIFIED_BOTTOM - 1); // set flags to denote border top is modified (set bit=0) - this.updateBorderLineColorsFlag(); - if (this.borderLineColorBottom != b) - Logger.logWarn("borderLineColorBottom failed"); - } - - /** - * Returns the patternFillStyle. - *
                  NOTE in 2003-ver patternFillStyle is valid 1-> - * - * @return Returns the patternFillStyle. - */ - public int getPatternFillStyle() { - return patternFillStyle; - } - - /** - * @param patternFillStyle The patternFillStyle to set. - */ - public void setPatternFillStyle(int p) { - this.patternFillStyle = p; - bHasPatternBlock = true; - } - - /** - * @return Returns the patternFillColor. - */ - public int getPatternFillColor() { - if (fill != null) return fill.getFgColorAsInt(getWorkBook().getTheme()); - return patternFillColor; - } - - /** - * @param patternFillColor The patternFillColor to set. - */ - public void setPatternFillColor(int p, String custom) { - this.patternFillColor = p; - if (fill != null) fill.setFgColor(p); - this.updatePatternFillColorsFlag(); - } - - /** - * @return Returns the patternFillColorBack. - */ - public int getPatternFillColorBack() { - if (fill != null) return fill.getBgColorAsInt(getWorkBook().getTheme()); - return patternFillColorBack; - } - - /** - * @param patternFillColorBack The patternFillColorBack to set. - */ - public void setPatternFillColorBack(int p) { - this.patternFillColorBack = p; - if (fill != null) fill.setBgColor(p); - this.updatePatternFillColorsFlag(); - } - - /** - * @return Returns the fontHeight. - */ - public int getFontHeight() { - return fontHeight; - } - - /** - * @param fontHeight The fontHeight to set. - */ - public void setFontHeight(int fontHeight) { - this.fontHeight = fontHeight; - bHasFontBlock = true; - if (font != null) font.setFontHeight(fontHeight); - } - - /** - * @return Returns the fontWeight. - */ - public int getFontWeight() { - return fontWeight; - } - - /** - * @param fontWeight The fontWeight to set. - */ - public void setFontWeight(int f) { - this.fontWeight = f; - this.fontOptsFlag = this.fontOptsFlag & 0xFD; // turn off bit 1 = style bit - bHasFontBlock = true; - if (font != null) font.setFontWeight(f); - } - - /** - * @return Returns the fontUnderlineStyle. - */ - public int getFontUnderlineStyle() { - return fontUnderlineStyle; - } - - /** - * @param fontUnderlineStyle The fontUnderlineStyle to set. - */ - public void setFontUnderlineStyle(int fontUnderlineStyle) { - this.fontUnderlineStyle = fontUnderlineStyle; - this.fontUnderlineModifiedFlag = 0; // set modified flag - bHasFontBlock = true; - if (font != null) font.setUnderlineStyle((byte) fontUnderlineStyle); - } - - /** - * @return Returns the fontColorIndex. - */ - public int getFontColorIndex() { - return fontColorIndex; - } - - /** - * @param fontColorIndex The fontColorIndex to set. - */ - public void setFontColorIndex(int fontColorIndex) { - this.fontColorIndex = fontColorIndex; - bHasFontBlock = true; - if (font != null) font.setColor(fontColorIndex); - } - - - /** - * Create a Cf record & populate with prototype bytes - * - * @return TODO: NOT FINISHED - */ - protected static XLSRecord getPrototype() { - Cf cf = new Cf(); - cf.setOpcode(CF); - cf.setData(cf.PROTOTYPE_BYTES); - cf.init(); - return cf; - } - - private int refPos = -1; - - /** - * Reset the ptgRef to A1 in these that is replaced with - * current Ptg - */ - private void resetFormulaRef() { - // TODO: test what happens when A1 is a valid part of the expression - Stack expr = this.getFormula1().getExpression(); - Iterator itx = expr.iterator(); - if (refPos > -1) { - expr.insertElementAt(prefHolder, refPos); - } - } - - /** - * There is a ptgRef to A1 in these that is replaced with - * current Ptg - */ - private void setFormulaRef(Ptg refcell) - throws FormulaNotFoundException { - // TODO: test what happens when A1 is a valid part of the expression - Stack expr = this.getFormula1().getExpression(); - Iterator itx = expr.iterator(); - - int[] rc = refcell.getIntLocation(); - if (refPos == -1) { - while (itx.hasNext()) { - Ptg prex = (Ptg) itx.next(); - if (prex instanceof PtgRefN) { - ((PtgRefN) prex).setFormulaRow(rc[0]); - ((PtgRefN) prex).setFormulaCol(rc[1]); - } - } - } else { - expr.remove(refPos); - } - if (refPos > -1) { - expr.remove(prefHolder); - expr.insertElementAt(refcell, refPos); - } - - } - - /** - * pass in the referenced cell and attempt to - * create a valid formula for this thing and - * calculate whether the criteria passes - *

                  - * returns true or false - * - * @param the reference to evaluate - * @return boolean passes - */ - public boolean evaluate(Ptg refcell) { - try { - Object val2 = null; - Object val1 = null; - - if (this.cp != 0x0) // calcs later - val1 = this.getFormula1().calculateFormula(); - - if (this.cce2 > 0) - val2 = this.getFormula2().calculateFormula(); - - Object valX = refcell.getValue(); - - // cast to double then compare - double d1 = 0.0d; - double d2 = 0.0d; // second val from expression2 - - double dX = 0.0d; // the reference val - - try { - d1 = new Double(val1.toString()).doubleValue(); - dX = new Double(valX.toString()).doubleValue(); - if (this.cce2 > 0) { // we have a second value - d2 = new Double(val2.toString()).doubleValue(); - } - - } catch (Exception e) { - // not numeric - } - - // handle evaluated condition - switch (this.cp) { - case 0x0: // No comparison (only valid for formula type, see above) - setFormulaRef(refcell); - val1 = this.getFormula1().calculateFormula(); - return ((Boolean) val1).booleanValue(); - - case 01: // Between - // expression2 for the other bounds ... - return (dX >= d1) && (dX <= d2); - - case 0x5: // Greater than - return dX > d1; - - case 0x2: // Not between - if (dX < d1) - return false; - else if (dX > d1) // hmmm... d2 is where? an array? - return false; - - case 0x6: // Less than - return dX < d1; - - case 0x3: // Equal - return dX == d1; - - case 0x7: // Greater or equal - return dX >= d1; - - case 0x4: // Not equal - return dX != d1; - - case 0x8: // Less or equal - return dX <= d1; - - // 2007-specific operators - case 0x9: // begins With - case 0xA: // ends With - case 0xB: // contains text - case 0xC: // not contains - return false; - - default: - return false; - } - } catch (Exception ex) { - // Logger.logWarn("CF condition "+this.formula1.getFormulaString()+" evaluation failed for : " + refcell.toString()); - return false; - } - } - - - /** - * Returns the byte Condition type from a human-readable string - * - * @return the condition type for this rule - */ - public static final byte getConditionFromString(String cx) { - cx = StringTool.allTrim(cx); // trim - cx = cx.toUpperCase(); - // handle evaluated condition - if (cx.equals("BETWEEN")) - return 0x1; // Between - if (cx.equals("GREATER THAN")) - return 0x5; // Greater than - if (cx.equals("NOT BETWEEN")) - return 0x2; // Not between - if (cx.equals("LESS THAN")) - return 0x6; // Less than - if (cx.equals("EQUALS")) - return 0x3; // Equal - if (cx.equals("GREATER THAN OR EQUAL")) - return 0x7; // Greater or equal - if (cx.equals("NOT EQUAL")) - return 0x4; // Not equal - if (cx.equals("LESS THAN OR EQUAL")) - return 0x8; // Less or equal - else - return 0x0; // No comparison (only valid for formula type, see above) - } - - - /** - * Returns the human-readable Condition type - * - * @return the condition type for this rule - */ - public String getConditionString() { -// handle evaluated condition - switch (this.cp) { - case 0x0: // No comparison (only valid for formula type, see above) - // okay annoying, but apparenlty there is a ptgRef to A1 in these that should - // be replaced with our ptg... whatever!! - return this.expression1.toString() + this.expression2.toString(); - - case 01: // Between - // expression2 for the other bounds ... - return "Between"; - - case 0x5: // Greater than - return "Greater Than"; - - case 0x2: // Not between - return "Not Between"; - - case 0x6: // Less than - return "Less Than"; - - case 0x3: // Equal - return "Equals"; - - case 0x7: // Greater or equal - return "Greater Than or Equal"; - - case 0x4: // Not equal - return "Not Equal"; - - case 0x8: // Less or equal - return "Less Than or Equal"; - - // 2007-Specific - case 0x9: - return "Begins With"; - case 0xA: - return "Ends With"; - case 0xB: - return "Contains Text"; - case 0xC: - return "Not Contains"; - default: - return "Unknown"; - } - } - - - /** - * return the first formula referenced by the Conditional Format - * - * @return Formula - */ - public Formula getFormula1() { - if (formula1 == null) { // hasn't been set - formula1 = new Formula(); - formula1.setWorkBook(this.getWorkBook()); - if (this.getSheet() == null) - this.setSheet(this.condfmt.getSheet()); // help! - formula1.setSheet(this.getSheet()); - formula1.setExpression(expression1); - } -// 20101216 KSC: WHY???? formula1.setCachedValue(null); - return formula1; - } - - /** - * return the second formula referenced by the Conditional Format - * - * @return Formula - */ - public Formula getFormula2() { - if (formula2 == null && cce2 > 0) { // hasn't been set - formula2 = new Formula(); - formula2.setWorkBook(this.getWorkBook()); - if (this.getSheet() == null) - this.setSheet(this.condfmt.getSheet()); // help! - formula2.setSheet(this.getSheet()); - formula2.setExpression(expression2); - } - if (formula2 != null) { - formula2.setSheet(this.getSheet()); -// 20101216 KSC: WHY??? formula2.setCachedValue(null); - } - return formula2; - } - - /** - * restore Formula strings from XML serialization - * - * @param fmx - * @return - */ - private static String unescapeFormulaString(String fmx) { - fmx = StringTool.replaceText(fmx, """, "\""); - // fmx = StringTool.replaceText(fmx,"&","%"); - fmx = StringTool.replaceText(fmx, "<", "<"); - fmx = StringTool.replaceText(fmx, ">", ">"); - return fmx; - } - - /** - * taks a String representing the Operator for this Cf Rule - * and translates it to an int - *
                  NOTE: 2003 versions do not use types - * 0x9, 0xA, 0xB or 0xC - * - * @param String operator - String CfRule operator attribute - * @return int representing Cf operator value - */ - protected static int translateOperator(String operator) { - if (operator == null) // type is not cellIs - return 0; - if (operator.equals("between")) - return 0x1; - else if (operator.equals("greaterThan")) - return 0x5; - else if (operator.equals("notBetween")) - return 0x2; - else if (operator.equals("lessThan")) - return 0x6; - else if (operator.equals("equal")) - return 0x3; - else if (operator.equals("greaterThanOrEqual")) - return 0x7; - else if (operator.equals("notEqual")) - return 0x4; - else if (operator.equals("lessThanOrEqual")) - return 0x8; - // NO EQUIVALENT IN 2003: beginsWith, containsText, endsWith, notContains - else if (operator.equals("beginsWith")) - return 0x9; - else if (operator.equals("endsWith")) - return 0xA; - else if (operator.equals("containsText")) - return 0xB; - else if (operator.equals("notContains")) - return 0xC; - return 0; - } - - /** - * Given an int type, return it's string representation - *
                  NOTE: if type is between 3 and 18, - * it is an OOXML-specific type. - * - * @param type type integer - * @return String reprentation - * @see translateType(String) - */ - protected static String translateType(int type) { - switch (type) { - case 1: - return "Cell Is"; - case 2: - return "expression"; - case 3: - return "containsText"; - case 4: - return "aboveAverage"; - case 5: - return "beginsWith"; - case 6: - return "colorScale"; - case 7: - return "containsBlanks"; - case 8: - return "containsErrors"; - case 9: - return "dataBar"; - case 10: - return "duplicateValues"; - case 11: - return "endsWith"; - case 12: - return "iconSet"; - case 13: - return "notContainsBlanks"; - case 14: - return "notContainsErrors"; - case 15: - return "notContainsText"; - case 16: - return "timePeriod"; - case 17: - return "top10"; - case 18: - return "uniqueValues"; - default: - return "Unknnown"; - } - } - - /** - * takes a String representing the type attribute and translates it to - * the corresponding integer representation - *
                  IMPORTANT NOTE: OOXML-specific types are converted to an integer that is not valid in 2003 versions - * - * @param String type - OOXML CfRule type attribute - * @return int representing Cf type value - */ - protected static int translateOOXMLType(String type) { - if (type.equals("cellIs")) - return 1; - else if (type.equals("expression")) - return 2; - // no equivalent in 2003: but must track for 2007 uses - else if (type.equals("containsText")) - return 3; - else if (type.equals("aboveAverage")) - return 4; - else if (type.equals("beginsWith")) - return 5; - else if (type.equals("colorScale")) - return 6; - else if (type.equals("containsBlanks")) - return 7; - else if (type.equals("containsErrors")) - return 8; - else if (type.equals("dataBar")) - return 9; - else if (type.equals("duplicateValues")) - return 10; - else if (type.equals("endsWith")) - return 11; - else if (type.equals("iconSet")) - return 12; - else if (type.equals("notContainsBlanks")) - return 13; - else if (type.equals("notContainsErrors")) - return 14; - else if (type.equals("notContainsText")) - return 15; - else if (type.equals("timePeriod")) - return 16; - else if (type.equals("top10")) - return 17; - else if (type.equals("uniqueValues")) - return 18; - return 1; // default to cellIs ???? - } - - /** - * prepare Formula strings for XML serialization - * - * @param fmx - * @return - */ - private static String escapeFormulaString(String fmx) { - fmx = StringTool.replaceText(fmx, "\"", """); - if (fmx.indexOf("=") == 0) { - fmx = fmx.substring(1); - } - // fmx = StringTool.replaceText(fmx,"%","&"); - fmx = StringTool.replaceText(fmx, "<", "<"); - fmx = StringTool.replaceText(fmx, ">", ">"); - return fmx; - - } - - /** - * returns EXML (XMLSS) for the Conditional Format Rule - * - * - * - * - * R12C2:R16C2 - * - * Between - * 2 - * 4 - * - * - * - * - * - * R6C2 - * - * NOT(ISERROR(SEARCH("yes",RC))) - * - * - * - * - * @return - */ - public String getXML() { - return getXML(this); - } - - // helper inner method allows it to be static and reused... - private static final String getXML(Cf cfx) { - - // reset the placeholder formula reference - if (cfx.refPos > -1) - cfx.resetFormulaRef(); - - StringBuffer xml = new StringBuffer(); - - xml.append(""); - CellRange rn = new CellRange(cfx.getCondfmt().getBoundingRange());//getConditionalFormatRange(); - if (rn != null) - xml.append(rn.getR1C1Range()); - - xml.append(""); - xml.append(""); - if (cfx.cp != 0x0) { // calcer - xml.append(""); - xml.append(cfx.getConditionString()); - xml.append(""); - } - Object val1, val2; - if (cfx.cp != 0x0) { // calcer - xml.append(""); - val1 = cfx.getFormula1().calculateFormula(); - if (val1 == null) // a new formula + calc_explicit - { - } - else - xml.append(val1.toString()); - xml.append(""); - } else { - xml.append(""); - String fmx = cfx.getFormula1().getFormulaString(); - fmx = Cf.escapeFormulaString(fmx); - xml.append(fmx); - xml.append(""); - } - - if (cfx.cce2 > 0) { - val2 = cfx.getFormula2().calculateFormula(); - xml.append(""); - if (val2 == null) // a new formula + calc_explicit - { - } - else - xml.append(val2.toString()); - xml.append(""); - } - - xml.append(""); - xml.append(""); - - return xml.toString(); - } - - /** - * creates a Cf record from the EXML nodes - * - * - * - * Between - * 2 - * 4 - * - * - * - * @param xpp - * @return - */ - public static Cf parseXML(XmlPullParser xpp, Condfmt cfx, Boundsheet bs) { - Cf oe = bs.createCf(cfx); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("Condition")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (n.equals("Qualifier")) { - oe.setOperator(v); - } else if (n.equals("Value1")) { - oe.setCondition1(v); - } else if (n.equals("Value2")) { - oe.setCondition2(v); - } else if (n.equals("Format")) { - setStylePropsFromString(v, oe); - } - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("Condition")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("Cf.parseXML: " + e.toString()); - } - return oe; - } - - /** - * OOXML-specific, in a containsText-type condition, - * this field is the comparitor - * - * @param s - */ - public void setContainsText(String s) { - containsText = s; - } - - /** - * generate OOXML for this Cf (BIFF8->OOXML) - * attributes: type, dxfId, priority (REQ), stopIfTrue, aboveAverage, - * percent, bottom, operator, text, timePeriod, rank, stdDev, equalAverage - * children: SEQ: formula (0-3), colorScale, dataBar, iconSet - * - * @return - */ - public String getOOXML(WorkBookHandle bk, int priority, ArrayList dxfs) { - StringBuffer ooxml = new StringBuffer(); - - // first deal with dfx's (differential xf's) - part of styles.xml; here we need to add dxf element to dxf's plus trap dxfId - Dxf dxf = new Dxf(); - if (this.bHasFontBlock) { - if (font != null) - dxf.setFont(font); - else - dxf.createFont(this.fontWeight, this.getFontItalic(), this.fontUnderlineStyle, this.fontColorIndex, this.fontHeight); - } - if (this.bHasPatternBlock) { - if (fill != null) - dxf.setFill(fill); - else - dxf.createFill(this.patternFillStyle, this.patternFillColor, this.patternFillColorBack, bk); - } - if (this.bHasBorderBlock) { - dxf.createBorder(bk, this.getBorderStyles(), new int[]{this.getBorderLineColorTop(), this.getBorderLineColorLeft(), this.getBorderLineColorBottom(), this.getBorderLineColorRight()}); - } - // TODO: check if this dxf already exists **************************************************************************** - dxfs.add(dxf); // save newly created dxf (differential xf) to workbook store - int dxfId = dxfs.size() - 1; // link this cf to it's dxf NOTE: one of the ONLY OOXML id's that is 0-based ... - - ooxml.append(""); - if (this.getFormula1() != null) - ooxml.append("" + OOXMLAdapter.stripNonAsciiRetainQuote(this.getFormula1().getFormulaString()).substring(1) + ""); - if (this.getFormula2() != null) - ooxml.append("" + OOXMLAdapter.stripNonAsciiRetainQuote(this.getFormula2().getFormulaString()).substring(1) + ""); - - // TODO: finish children dataBar, colorScale, iconSet, aboveAverage, bottom, equalAverage, - ooxml.append(""); - return ooxml.toString(); - } - -} diff --git a/src/main/java/io/starter/formats/XLS/Cf.kt b/src/main/java/io/starter/formats/XLS/Cf.kt new file mode 100644 index 0000000..7943599 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Cf.kt @@ -0,0 +1,2317 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.Dxf +import io.starter.formats.OOXML.Fill +import io.starter.formats.XLS.formulas.FormulaParser +import io.starter.formats.XLS.formulas.Ptg +import io.starter.formats.XLS.formulas.PtgArray +import io.starter.formats.XLS.formulas.PtgRefN +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.Stack + + +/** + * **Cf: Conditional Formatting Conditions 0x1B1**

                  + * + * + * This record stores a conditional formatting condition + * + * + * + * + * There are some restrictions in the usage of conditional formattings: + * In the user interface it is possible to modify the font style (boldness and posture), the text colour, the underline style + * and the strikeout style. It is not possible to change the used font, the font height, and the escapement style, though it is + * possible to specify a new font height and escapement style in this record which are correctly displayed + * It is not possible to change a border line style, but to preserve the line colour, and vice versa. Diagonal lines are not + * supported at all. The user interface only offers thin line styles, but files containing other line styles work correctly too. + * It is not possible to set the background pattern colour to No colour (using system window background + * + * + * + * + * + * + * OFFSET NAME SIZE CONTENTS + * ----- + * 4 ct 1 Conditional formatting type + * 5 cp 1 Conditional formatting operator + * 6 cce1 2 Count of bytes in rgce1 -- size of formula data for first value or formula + * 8 cce2 2 Count of bytes in rgce2 -- size of formula data for first value or formula: used for second part of 'between' and 'not between' comparison, else 0 + * 10 4 Option flags (see below) + * 2 Not used + * 16 118 (optional, only if font = 1, see option flags) Font formatting block, see below + * var 8 (optional, only if bord = 1, see option flags) Border formatting block, see below + * var 4 (optional, only if patt = 1, see option flags) Pattern formatting block, see below + * var rgbdxf var Conditional format to apply + * var rgce1 var First formula for this condition (RPN token array without size field, ?4) + * var rgce2 var Second formula for this condition (RPN token array without size field, ?4) + * + * + * Conditional formatting operator: + * 00H = No comparison (only valid for formula type, see above) + * 01H = Between 05H = Greater than + * 02H = Not between 06H = Less than + * 03H = Equal 07H = Greater or equal + * 04H = Not equal 08H = Less or equal + * + * + * Option Flags + * If none of the formatting attributes is set, the option flags field contains 00000000H. + * The following table assumes that + * the conditional formatting contains at least one modified formatting attribute + * (it will occur at least one of the formatting + * information blocks in the record). In difference to the first case some of the + * bits are always set now. + * All flags specifying that an attribute is modified are 02, if the conditional formatting + * changes the respective attribute, + * and 12, if the original cell formatting is preserved. The flags for modified font + * attributes are not contained in this + * option flags field, but in the font formatting block itself. ! + * + * + * Bit Mask Contents + * 9-0 000003FFH Always 11.1111.11112 (but not used) + * 10 00000400H 0 = Left border style and colour modified (bord - left ) + * 11 00000800H 0 = Right border style and colour modified (bord - right ) + * 12 00001000H 0 = Top border style and colour modified (bord - top ) + * 13 00002000H 0 = Bottom border style and colour modified (bord - bot ) + * 15-14 0000C000H Always 112 (but not used) + * 16 00010000H 0 = Pattern style modified (patt - style ) + * 17 00020000H 0 = Pattern colour modified (patt - col ) + * 18 00040000H 0 = Pattern background colour modified (patt - bgcol ) + * 21-19 00380000H Always 1112 (but not used) + * 26 04000000H 1 = Record contains font formatting block (font) + * 28 10000000H 1 = Record contains border formatting block (bord) + * 29 20000000H 1 = Record contains pattern formatting block (patt) + * + * @see Condfmt + */ +class Cf +// 0x1,0x1,0x3,0x0,0x3,0x0,0xf,0xc,0x3f,0x90,0x2,0x80,0x11,0x11,0x14,0xa,0x14,0xa,0x0,0x0,0x1e,0x7b,0x0,0x1e,0xea,0x0}; + + +/** + * default constructor + */ +() : io.starter.formats.XLS.XLSRecord() { + /** + * return the type of this Cf Rule as an int + * + * 1= Cell is ("Cell value is") + * + * 2= expression ("formula value is") + * + * @return + */ + var type: Short = 0 + internal set // Conditional formatting type + /** + * returns the operator or qualifier of this Cf Rue as an int value + *

                  possible values: + * + * 0= no comparison + * + * 1= between + * + * 2= not between + * + * 3= equal + * + * 4= not equal + * + * 5= greater than + * + * 6= less than + * + * 7= greater than or equal + * + * 8= less than or equal + * ******************** + * 2007-specific operators + * + * 9= begins With + * + * 10= ends With + * + * 11= contains Text + * + * 12= not contains + * + * @return + */ + var operator: Short = 0 + internal set // Conditional formatting operator + internal var cce1 = 0 // Count of bytes in rgce1 + internal var cce2 = 0 // Count of bytes in rgce2 + internal var rgbdxf = "" // Conditional format to apply + internal var rgce1 = "" // First formula for this condition + internal var rgce2 = "" // Second formula for this condition + internal var flags = 0 // option flags 20080303 KSC: + internal var bHasFontBlock = false // "" + internal var bHasBorderBlock = false // "" + internal var bHasPatternBlock = false // "" + + private val prefHolder: Ptg? = null // this is a placeholder ptg for the conditional format expression + private var expression1: Stack<*>? = Stack() // + private var expression2: Stack<*>? = Stack() // + private var formula1: Formula? = null // + private var formula2: Formula? = null // + private var containsText: String? = null // OOXML-specific if type==containsText, this will hold the actual text to test + + /** + * @return Returns the condfmt. + */ + /** + * @param condfmt The condfmt to set. + */ + var condfmt: Condfmt? = null + private var patternFillStyle = -1 + private var patternFillColorsFlag = 0 + private var patternFillColor = 0 + private var patternFillColorBack = -1 + /** + * return the 2007v Fill element, or null if not set + */ + var fill: Fill? = null + private set + private var font: Font? = null + + // Offset Size Contents + // 0 64 Not used + // 64 4 Font height (in twips = 1/20 of a point); or FFFFFFFFH to preserve the cell font height + private var fontHeight = -1 + private var fontOptsFlag = -1 + + // 72 2 Font weight (100-1000, only if font - style = 0). + private var fontWeight = -1 + private var fontEscapementFlag = -1 + private var fontUnderlineStyle = -1 + + private var fontColorIndex = -1 + private var fontModifiedOptionsFlag = -1 + private var fontEscapementFlagModifiedFlag = -1 + private var fontUnderlineModifiedFlag = -1 + private var borderLineStylesFlag: Short = 0 + private var borderLineStylesLeft = -1 + private var borderLineStylesRight = -1 + private var borderLineStylesTop = -1 + private var borderLineStylesBottom = -1 + private var borderLineColorsFlag = 0 + private var borderLineColorLeft = 0 + private var borderLineColorRight = 0 + private var borderLineColorTop = 0 + private var borderLineColorBottom = 0 + + // TODO: finish Cf prototype bytes + formatting options such as font block + //private byte[] PROTOTYPE_BYTES = {1, 5, 3, 0, 0, 0, -1, -1, 59, -92, 2, -128, 0, 0, 2, 0, 2, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0, 6, -36, 0, -76, 5, 83, -17, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1, -1, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, -97, -1, -1, -1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, -64, 22, 30, 123, 0}; + // a very basic cf which only contains a pattern fill block and function compares <= 100 + // private byte[] PROTOTYPE_BYTES= {1, 8, 3, 0, 0, 0, -1, -1, 59, -96, 2, -128, 0, 0, -64, 26, 30, 100, 0}; + private val PROTOTYPE_BYTES = byteArrayOf(1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)//, -64, 26, 30, 100, 0}; + + /** + * reutrn the string representation of this Cf Rule type + * + * @return + */ + val typeString: String + get() = Cf.translateType(this.type.toInt()) + + /** + * @return Returns the fontEscapementFlag. + */ + /** + * @param fontEscapementFlag The fontEscapementFlag to set. + */ + // set modified + // if (font!=null) + // font.setScript(ss); + var fontEscapement: Int + get() = fontEscapementFlag + set(fontEscapementFlag) { + this.fontEscapementFlag = fontEscapementFlag + this.fontEscapementFlagModifiedFlag = 0 + bHasFontBlock = true + } + + + /** + * returns the pattern color, if any, as an HTML color String. Includes custom OOXML colors. + * + * @return String HTML Color String + */ + val patternFgColor: String? + get() { + if (fill != null) + return fill!!.getFgColorAsRGB(workBook!!.theme) + return if (patternFillColor != -1) FormatHandle.colorToHexString(this.colorTable[patternFillColor]) else null + } + + /** + * returns the pattern background color, if any, as an HTML color String. Includes custom OOXML colors. + * + * @return String HTML Color String + */ + val patternBgColor: String? + get() { + if (fill != null) + return if (patternFillStyle == 1) + fill!!.getFgColorAsRGB(workBook!!.theme) + else + fill!!.getBgColorAsRGB(workBook!!.theme) + return if (patternFillColorBack != -1) FormatHandle.colorToHexString(this.colorTable[patternFillColorBack]) else null + } + + val borderColors: Array? + get() = if (!this.bHasBorderBlock) null else arrayOf(colorTable[getBorderLineColorTop()], colorTable[getBorderLineColorLeft()], colorTable[getBorderLineColorBottom()], colorTable[getBorderLineColorRight()]) + + + val allBorderColors: IntArray? + get() = if (!this.bHasBorderBlock) null else intArrayOf(getBorderLineColorTop(), getBorderLineColorLeft(), getBorderLineColorBottom(), getBorderLineColorRight()) + + /** + * order= top, left, bottom, right + * + * @return + */ + // diag + val borderStyles: IntArray? + get() = if (!this.bHasBorderBlock) null else intArrayOf(getBorderLineStylesTop(), getBorderLineStylesLeft(), getBorderLineStylesBottom(), getBorderLineStylesRight(), -1) + + val borderSizes: IntArray? + get() { + if (!this.bHasBorderBlock) + return null + val hasTop = this.getBorderLineStylesTop() > 0 + val hasLeft = this.getBorderLineStylesLeft() > 0 + val hasBottom = this.getBorderLineStylesBottom() > 0 + val hasRight = this.getBorderLineStylesRight() > 0 + return intArrayOf(if (hasTop) 1 else 0, if (hasLeft) 1 else 0, if (hasBottom) 1 else 0, if (hasRight) 1 else 0, 0) + } + + val foregroundColor: Int + get() { + if (!this.bHasPatternBlock) + return -1 + if (fill != null) + return fill!!.getFgColorAsInt(workBook!!.theme) + return if (this.patternFillStyle == 1) + this.patternFillColorBack + else + this.patternFillColor + } + + + /** + * @return Returns the fontOptsPosture. + */ + /** + * @param fontOptsPosture The fontOptsPosture to set. + */ + var fontOptsPosture: Int + get() = (this.fontOptsFlag and FONT_OPTIONS_POSTURE).toShort().toInt() + set(fontOptsPosture) { + this.fontOptsFlag = (this.fontOptsFlag and FONT_OPTIONS_POSTURE).toShort().toInt() + bHasFontBlock = true + } + + /** + * @return Returns the fontOptsCancellation. + */ + val fontOptsCancellation: Int + get() = (this.fontOptsFlag and FONT_OPTIONS_CANCELLATION shr 7).toShort().toInt() + + /** + * @return Returns the fontOptsItalic. + */ + /** + * @param fontOptsItalic The fontOptsItalic to set. + */ + // 1 if italic + // set italic + // todo: is below correct? + // clear italic bit + var fontItalic: Boolean + get() = fontOptsFlag and 0x2 == FONT_OPTIONS_POSTURE_ITALIC + set(italic) { + if (italic) { + this.fontOptsFlag = this.fontOptsFlag or 0x2 + this.fontModifiedOptionsFlag = this.fontModifiedOptionsFlag or 0x2 + bHasFontBlock = true + } else { + this.fontOptsFlag = this.fontOptsFlag xor 0x2 + this.fontModifiedOptionsFlag = this.fontModifiedOptionsFlag xor 0x2 + } + if (font != null) font!!.italic = italic + } + + // todo: is below correct? + // turn off + var fontStriken: Boolean + get() = fontOptsFlag and 0x80 == FONT_OPTIONS_CANCELLATION_ON + set(bStriken) { + if (bStriken) { + fontOptsFlag = fontOptsFlag or 0x80 + fontModifiedOptionsFlag = fontModifiedOptionsFlag or 0x80 + bHasFontBlock = true + } else { + fontOptsFlag = fontOptsFlag xor 0x80 + fontModifiedOptionsFlag = fontModifiedOptionsFlag xor 0x80 + } + if (font != null) font!!.stricken = bStriken + } + + /** + * @return Returns true if font escapement is superscript + */ + val fontEscapementSuper: Boolean + get() = fontEscapementFlag == FONT_ESCAPEMENT_SUPER + + /** + * @return Returns true if font escapement is subscript + */ + val fontEscapementSub: Boolean + get() = fontEscapementFlag == FONT_ESCAPEMENT_SUB + + private val refPos = -1 + + + /** + * Returns the human-readable Condition type + * + * @return the condition type for this rule + */ + // handle evaluated condition + // No comparison (only valid for formula type, see above) + // okay annoying, but apparenlty there is a ptgRef to A1 in these that should + // be replaced with our ptg... whatever!! + // Between + // expression2 for the other bounds ... + // Greater than + // Not between + // Less than + // Equal + // Greater or equal + // Not equal + // Less or equal + // 2007-Specific + val conditionString: String + get() { + when (this.operator) { + 0x0 -> return this.expression1!!.toString() + this.expression2!!.toString() + + 1 -> return "Between" + + 0x5 -> return "Greater Than" + + 0x2 -> return "Not Between" + + 0x6 -> return "Less Than" + + 0x3 -> return "Equals" + + 0x7 -> return "Greater Than or Equal" + + 0x4 -> return "Not Equal" + + 0x8 -> return "Less Than or Equal" + 0x9 -> return "Begins With" + 0xA -> return "Ends With" + 0xB -> return "Contains Text" + 0xC -> return "Not Contains" + else -> return "Unknown" + } + } + + /** + * returns EXML (XMLSS) for the Conditional Format Rule + * + * + * + * + * R12C2:R16C2 + * + * Between + * 2 + * 4 + * + * + * + * + * + * R6C2 + * + * NOT(ISERROR(SEARCH("yes",RC))) + * + * + * + * + * @return + */ + val xml: String + get() = getXML(this) + + /** + * constructor which takes the cellrange of cells + * and the Condfmt that reference this conditional format rule + * + * @param f the condfmt + * @param r the cellrange + */ + constructor(f: Condfmt) : this() { + setData(this.PROTOTYPE_BYTES) + this.condfmt = f + } + + + /** Pattern Formatting Block */ + /** + * initialize the Cf record + */ + override fun init() { + super.init() + data = this.getData() + type = this.getByteAt(0).toShort() + operator = this.getByteAt(1).toShort() + cce1 = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()).toInt() + cce2 = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()).toInt() + //parsing of formula refs + flags = ByteTools.readInt(this.getByteAt(6), this.getByteAt(7), this.getByteAt(8), this.getByteAt(9)) + // Font formatting Block + bHasFontBlock = flags and 0x04000000 == 0x04000000 + // Border Formatting Block + bHasBorderBlock = flags and 0x10000000 == 0x10000000 + // Pattern Formating Block + bHasPatternBlock = flags and 0x20000000 == 0x20000000 + var pos = 12 + + if (bHasFontBlock) { // handle Font formatting section + pos += 64 // 1st 64 bits of font block is unused + // 64 4 Font height (in twips = 1/20 of a point); or FFFFFFFFH to preserve the cell font height + fontHeight = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)) + + // 68 4 Font options: + // Bit Mask Contents + // 1 00000002H Posture: 0 = Normal; 1 = Italic (only if font - style = 0) + // 7 00000080H Cancellation: 0 = Off; 1 = On (only if font - canc = 0) + fontOptsFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)) + + // 72 2 Font weight (100-1000, only if font - style = 0). + // Standard values are 0190H (400) for normal text and 02BCH (700) for bold text. + fontWeight = ByteTools.readShort(getByteAt(pos++).toInt(), getByteAt(pos++).toInt()).toInt() + + // 74 2 Escapement type (only if font - esc = 0): + // 0000H = None; 0001H = Superscript; 0002H = Subscript + // FONT_ESCAPEMENT_NONE = 0x0; + // FONT_ESCAPEMENT_SUPER = 0x1; + // FONT_ESCAPEMENT_SUB = 0x2; + fontEscapementFlag = ByteTools.readShort(getByteAt(pos++).toInt(), getByteAt(pos++).toInt()).toInt() + + + // 76 1 Underline type (only if font - underl = 0): + // 00H = None + // 01H = Single 21H = Single accounting + // 02H = Double 22H = Double accounting + // FONT_UNDERLINE_NONE = 0x0; + // FONT_UNDERLINE_SINGLE = 0x1; + // FONT_UNDERLINE_DOUBLE = 0x2; + fontUnderlineStyle = getByteAt(pos++).toInt() + + // 77 3 Not used + pos += 3 + + // 80 4 Font colour index (?6.70); or FFFFFFFFH to preserve the cell font colour + fontColorIndex = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)) + + // 84 4 Not used + pos += 4 + + // 88 4 Option flags for modified font attributes: + // Bit Mask Contents + // 1 00000002H 0 = Font style (posture or boldness) modified (font - style ) + // 4-3 00000018H Always 112 (but not used) + // 7 00000080H 0 = Font cancellation modified (font - canc ) + fontModifiedOptionsFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)) + + // 92 4 0 = Escapement type modified (font - esc ) + fontEscapementFlagModifiedFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)) + + // 96 4 0 = Underline type modified (font - underl ) + fontUnderlineModifiedFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)) + // 100 16 Not used + pos += 16 + // 116 2 0001H + pos += 2 + if (pos != 130) { + Logger.logWarn("Cf font block parsing pos mismatch$pos") + } + getFont() + } + if (bHasBorderBlock) { + + // Offset Size Contents + // 0 2 Border line styles: + // Bit Mask Contents + // 3-0 000FH Left line style (only if bord - left = 0, ?3.10) + // 7-4 00F0H Right line style (only if bord - right = 0, ?3.10) + // 11-8 0F00H Top line style (only if bord - top = 0, ?3.10) + // 15-12 F000H Bottom line style (only if bord - bot = 0, ?3.10) + // BORDER_LINESTYLE_LEFT = 0x000F; + // BORDER_LINESTYLE_RIGHT = 0x00F0; + // BORDER_LINESTYLE_TOP = 0x0F00; + // BORDER_LINESTYLE_BOTTOM = 0xF000; + borderLineStylesFlag = ByteTools.readShort(getByteAt(pos++).toInt(), getByteAt(pos++).toInt()) + updateBorderLineStyles() + + // 2 4 Border line colour indexes: + // Bit Mask Contents + // 6-0 0000007FH Colour index (?6.70) for left line (only if bord - left = 0) + // 13-7 00003F80H Colour index (?6.70) for right line (only if bord - right = 0) + // 22-16 007F0000H Colour index (?6.70) for top line (only if bord - top = 0) + // 29-23 3F800000H Colour index (?6.70) for bottom line (only if bord - bot = 0) + // BORDER_LINECOLOR_LEFT = 0x0000007F; + // BORDER_LINECOLOR_RIGHT = 0x00003F80; + // BORDER_LINECOLOR_TOP = 0x007F0000; + // BORDER_LINECOLOR_BOTTOM = 0x3F800000; + borderLineColorsFlag = ByteTools.readInt(getByteAt(pos++), getByteAt(pos++), getByteAt(pos++), getByteAt(pos++)) + updateBorderLineColors() + + //6 2 Not used + pos += 2 + } + if (bHasPatternBlock) { + // Offset Size Contents + + // 0 2 Fill pattern style: + // Bit Mask Contents + // 15-10 FC00H Fill pattern style (only if patt - style = 0, ?3.11) + // PATTERN_FILL_STYLE = 0xFC00; + patternFillStyle = ByteTools.readShort(getByteAt(pos++).toInt(), getByteAt(pos++).toInt()) shr 9 + /* very strangely, patternFillStyle appears to be 0 when it should be 1 (solid) + * found in testing through our test suite ... + * according to documetation this algorithm *appears* ok but the doc is really confusing on cf's + */ + + // 2 2 Fill pattern colour indexes: + + // Bit Mask Contents + // 6-0 007FH Colour index (?6.70) for pattern (only if patt - col = 0) + // 13-7 3F80H Colour index (?6.70) for pattern background (only if patt - bgcol = 0) + // PATTERN_FILL_COLOR = 0x007F; + // PATTERN_FILL_BACK_COLOR = 0x3F80; + patternFillColorsFlag = ByteTools.readShort(getByteAt(pos++).toInt(), getByteAt(pos++).toInt()).toInt() + + // update the current vals + updatePatternFillColors() + + } + + var postest = 12 + if (bHasFontBlock) + postest += 118 + if (bHasBorderBlock) + postest += 8 + if (bHasPatternBlock) + postest += 4 + + if (postest != pos) { + Logger.logWarn("Cf bad pos offset during init().") + pos = postest + } + // 1st formula data= pos->cce1 + var function = this.getBytesAt(pos, cce1) + try { + expression1 = ExpressionParser.parseExpression(function!!, this, cce1) + } catch (e: Exception) { + Logger.logErr("Initializing expression1 for Cf failed: " + String(function!!)) + } + + pos += cce1 + // 2nd formula data= pos+cce1->cce2 + function = this.getBytesAt(pos, cce2) + if (cce2 > 0) { + try { + expression2 = ExpressionParser.parseExpression(function!!, this, cce2) + } catch (e: Exception) { + Logger.logErr("Initializing expression2 for Cf failed: " + String(function!!)) + } + + } + if (DEBUGLEVEL > XLSConstants.DEBUG_LOW) + Logger.logInfo("Cf record encountered.") + } + + /** + * take current state of Cf and update record + */ + private fun updateRecord() { + var newdata = ByteArray(12) // enough room for basics; formatting blocks will be appended + newdata[0] = type.toByte() + newdata[1] = operator.toByte() + flags = 0x3FF or 0xC000 or 0x380000 // set required and unused bits of flag + if (bHasFontBlock) + flags = flags or 0x04000000 + if (bHasBorderBlock) { + flags = flags or 0x10000000 + flags = flags or 0x400 // left border mod + flags = flags or 0x800 // right border mod + flags = flags or 0x1000 // top border mod + flags = flags or 0x2000 // bottom border mod + } + if (bHasPatternBlock) { + flags = flags or 0x20000000 + flags = flags or 0x10000 // patt style mod + flags = flags or 0x20000 // patt fg color (pattern color) mod + flags = flags or 0x40000 // pat bg color mod + } + + var b = ByteTools.cLongToLEBytes(flags) + System.arraycopy(b, 0, newdata, 6, 4) + + var pos = 12 + if (bHasFontBlock) { // update font section + newdata = ByteTools.append(ByteArray(118), newdata) + pos += 64 // 1st 64 bits of font block is unused + b = ByteTools.cLongToLEBytes(fontHeight) + System.arraycopy(b, 0, newdata, pos, 4) + pos += 4 + b = ByteTools.cLongToLEBytes(fontOptsFlag) + System.arraycopy(b, 0, newdata, pos, 4) + pos += 4 + b = ByteTools.shortToLEBytes(fontWeight.toShort()) + System.arraycopy(b, 0, newdata, pos, 2) + pos += 2 + b = ByteTools.shortToLEBytes(fontEscapementFlag.toShort()) + System.arraycopy(b, 0, newdata, pos, 2) + pos += 2 + b = ByteTools.shortToLEBytes(fontUnderlineStyle.toShort()) + System.arraycopy(b, 0, newdata, pos, 1) + pos += 1 + // 77 3 Not used + pos += 3 + b = ByteTools.cLongToLEBytes(fontColorIndex) + System.arraycopy(b, 0, newdata, pos, 4) + pos += 4 + // 84 4 Not used + pos += 4 // 88: + b = ByteTools.cLongToLEBytes(fontModifiedOptionsFlag) + System.arraycopy(b, 0, newdata, pos, 4) + pos += 4 + b = ByteTools.cLongToLEBytes(fontEscapementFlagModifiedFlag) + System.arraycopy(b, 0, newdata, pos, 4) + pos += 4 + b = ByteTools.cLongToLEBytes(fontUnderlineModifiedFlag) + System.arraycopy(b, 0, newdata, pos, 4) + pos += 4 + // 100 16 Not used + pos += 16 + // 116 2 0001H + pos++ + newdata[pos++] = 1 + } + if (bHasBorderBlock) { + newdata = ByteTools.append(ByteArray(8), newdata) + b = ByteTools.shortToLEBytes(borderLineStylesFlag) + System.arraycopy(b, 0, newdata, pos, 2) + pos += 2 + b = ByteTools.cLongToLEBytes(borderLineColorsFlag) + System.arraycopy(b, 0, newdata, pos, 4) + pos += 4 + //6 2 Not used + pos += 2 + } + if (bHasPatternBlock) { + newdata = ByteTools.append(ByteArray(4), newdata) + b = ByteTools.shortToLEBytes((patternFillStyle shl 9).toShort()) + System.arraycopy(b, 0, newdata, pos, 2) + pos += 2 + b = ByteTools.shortToLEBytes(patternFillColorsFlag.toShort()) + System.arraycopy(b, 0, newdata, pos, 2) + pos += 2 + } + + if (formula1 != null) { + val function = getFormulaExpression(formula1!!) + newdata = ByteTools.append(function, newdata) + cce1 = function.size + b = ByteTools.shortToLEBytes(cce1.toShort()) + newdata[2] = b[0] + newdata[3] = b[1] + } + if (formula2 != null) { + val function = getFormulaExpression(formula2!!) + newdata = ByteTools.append(function, newdata) + cce2 = function.size + b = ByteTools.shortToLEBytes(cce2.toShort()) + newdata[4] = b[0] + newdata[5] = b[1] + } + this.setData(newdata) + // this.init(); DO NOT DO AS can overwrite OOXML-specifics + } + + /** + * return the expression bytes of the specified formula + * + * @param f + * @return + */ + private fun getFormulaExpression(f: Formula): ByteArray { + var hasArray = false + var expbytes = ByteArray(0) + var arraybytes: ByteArray? = null + val expression = f.expression + for (i in expression!!.indices) { + val o = expression.elementAt(i) + val ptg = o as Ptg + val b: ByteArray + if (o is PtgArray) { + b = o.preRecord + arraybytes = ByteTools.append(o.postRecord, arraybytes) + hasArray = true + } else { + b = ptg.record + } + expbytes = ByteTools.append(b, expbytes) + } + if (hasArray) { + expbytes = ByteTools.append(arraybytes, expbytes) + } + return expbytes + } + + /** + * sets the operator for this Cf Rule from a String + * + * @param s + */ + fun setOperator(s: String) { + this.operator = getConditionFromString(s).toShort() + if (operator.toInt() == 0x0) { // no comparison + expression1 = ExpressionParser.parseExpression(s.toByteArray(), this) + } else { + // val1 = cfx.getFormula1().calculateFormula(); + } + } + + /** + * sets the Operator of this Cf Rule + *

                  possible values: + * + * 0= no comparison + * + * 1= between + * + * 2= not between + * + * 3= equal + * + * 4= not equal + * + * 5= greater than + * + * 6= less than + * + * 7= greater than or equal + * + * 8= less than or equal + * ******************** + * 2007-specific operators + * + * 9= begins With + * + * 10= ends With + * + * 11= contains Text + * + * 12= not contains + * + * @param int qualifier - constant value as above + */ + fun setOperator(op: Int) { + this.operator = op.toShort() + } + + /** + * sets the type of this Cf rule from the int value + * + * 1= Cell is ("Cell value is") + * + * 2= expression ("formula value is") + * + * @param int type as above + */ + fun setType(type: Int) { + this.type = type.toShort() + } + + /** + * sets the first condition from a String + * + * @param s + */ + fun setCondition1(s: String) { + var s = s + // takes "123" aka Value1 and converts to formula expression =123 + s = Cf.unescapeFormulaString(s) + if (s.indexOf("=") != 0) { + s = "=$s" + try { + val r = intArrayOf(0, 0) + formula1 = FormulaParser.getFormulaFromString(s, this.sheet, r) + formula1!!.workBook = this.workBook + expression1 = formula1!!.expression + } catch (e: Exception) { + } + + } + } + + /** + * sets the second condition from a String + * + * @param s + */ + fun setCondition2(s: String) { + var s = s + if (s == "") return // none + + s = Cf.unescapeFormulaString(s) + // takes "123" aka Value1 and converts to formula expression =123 + if (operator.toInt() == 0x0) { // no comparison + expression2 = ExpressionParser.parseExpression(s.toByteArray(), this) + } else { + if (s.indexOf("=") != 0) { + s = "=$s" + try { + val r = intArrayOf(0, 0) + formula2 = FormulaParser.getFormulaFromString(s, this.sheet, r) + expression2 = formula1!!.expression + } catch (e: Exception) { + } + + } + } + this.cce2 = s.length + } + + /** + * parse cf font into it's member elements + */ + private fun parseFont(font: Font) { + var z = font.fontHeight + if (z > 0) + setFontHeight(z) + z = font.fontWeight + if (z > 0) + setFontWeight(z) + // } else if (n.equalsIgnoreCase("font-EscapementFlag")) { + // cx.setFontEscapement(s); + fontStriken = font.stricken + fontItalic = font.italic + z = font.color + if (z > -1) + setFontColorIndex(z) + z = font.underlineStyle + if (z > -1) + setFontUnderlineStyle(z) + } + + + // Start Font Formatting Block + + /** + * updates the values for the current border colors from the flag + * ----------------------------------------------------------- + */ + internal fun updateBorderLineStyles() { + if (flags and BORDER_MODIFIED_LEFT == 0) + this.borderLineStylesLeft = (this.borderLineStylesFlag and BORDER_LINESTYLE_LEFT).toShort().toInt() + if (flags and BORDER_MODIFIED_RIGHT == 0) + this.borderLineStylesRight = (this.borderLineStylesFlag and BORDER_LINESTYLE_RIGHT shr 4).toShort().toInt() + if (flags and BORDER_MODIFIED_TOP == 0) + this.borderLineStylesTop = (this.borderLineStylesFlag and BORDER_LINESTYLE_TOP shr 8).toShort().toInt() + if (flags and BORDER_MODIFIED_BOTTOM == 0) + this.borderLineStylesBottom = (this.borderLineStylesFlag and BORDER_LINESTYLE_BOTTOM shr 12).toShort().toInt() + bHasBorderBlock = true + } + + /** + * Apply all the borderLineStyle fields into the current border line styles flag + */ + fun updateBorderLineStylesFlag() { + flags = flags or 0x10000000 // set flags to denote has a border block + bHasBorderBlock = flags and 0x10000000 == 0x10000000 + + borderLineStylesFlag = 0 + if (borderLineStylesLeft >= 0) borderLineStylesFlag = borderLineStylesLeft.toShort() + if (borderLineStylesRight >= 0) + borderLineStylesFlag = (borderLineStylesFlag or (borderLineStylesRight shl 4)).toShort() + if (borderLineStylesTop >= 0) + borderLineStylesFlag = (borderLineStylesFlag or (borderLineStylesTop shl 8)).toShort() + if (borderLineStylesBottom >= 0) + borderLineStylesFlag = (borderLineStylesFlag or (borderLineStylesBottom shl 12)).toShort() + /* + byte[] data = this.getData(); + byte[] updated = ByteTools.shortToLEBytes(borderLineStylesFlag); + int pos= 12; + if (bHasFontBlock) + pos+=118; + if (data.length< (pos+2)) { + byte[] tmp= new byte[pos+2]; + System.arraycopy(data, 0, tmp, 0, data.length); + this.setData(tmp); + } + data[pos++] = updated[0]; + data[pos++] = updated[1]; +*/ + } + + + //6 2 Not used + + /** + * updates the values for the current border colors from the flag + */ + internal fun updateBorderLineColors() { + if (flags and BORDER_MODIFIED_LEFT == 0) + this.borderLineColorLeft = (this.borderLineColorsFlag and BORDER_LINECOLOR_LEFT).toShort().toInt() + if (flags and BORDER_MODIFIED_RIGHT == 0) + this.borderLineColorRight = (this.borderLineColorsFlag and BORDER_LINECOLOR_RIGHT shr 7).toShort().toInt() + if (flags and BORDER_MODIFIED_TOP == 0) + this.borderLineColorTop = (this.borderLineColorsFlag and BORDER_LINECOLOR_TOP shr 16).toShort().toInt() + if (flags and BORDER_MODIFIED_BOTTOM == 0) + this.borderLineColorBottom = (this.borderLineColorsFlag and BORDER_LINECOLOR_BOTTOM shr 23).toShort().toInt() + } + + /** + * Apply all the borderLineColor fields into the current border line colors flag + */ + fun updateBorderLineColorsFlag() { + flags = flags or 0x10000000 // set flags to denote has a border block + bHasBorderBlock = flags and 0x10000000 == 0x10000000 // = true + + borderLineColorsFlag = 0 + borderLineColorsFlag = borderLineColorsFlag or borderLineColorLeft + borderLineColorsFlag = borderLineColorsFlag or (borderLineColorRight shl 7) + borderLineColorsFlag = borderLineColorsFlag or (borderLineColorTop shl 16) + borderLineColorsFlag = borderLineColorsFlag or (borderLineColorBottom shl 23) + /* + byte[] data = this.getData(); + byte[] updated = ByteTools.cLongToLEBytes( borderLineColorsFlag); + int pos= 12; + if (bHasFontBlock) + pos+=118; + pos+=2; // skip border line style + if (data.length<(pos+4)) { + byte[] tmp= new byte[pos+4]; + System.arraycopy(data, 0, tmp, 0, data.length); + this.setData(tmp); + } + data[pos++] = updated[0]; + data[pos++] = updated[1]; + data[pos++] = updated[2]; + data[pos++] = updated[3]; + // no need updateBorderLineColors(); +*/ + } + + /** + * updates the values for the current pattern fill colors from the flag + * + * + * // Bit Mask Contents + * // 15-10 FC00H Fill pattern style (only if patt - style = 0, ?3.11) + * // PATTERN_FILL_STYLE = 0xFC00; + */ + internal fun updatePatternFillColors() { + /* below appears correct in testing this.patternFillColor = (short)(this.patternFillColorsFlag & PATTERN_FILL_COLOR); + this.patternFillColorBack = (short)((this.patternFillColorsFlag & PATTERN_FILL_BACK_COLOR) >> 7); +*/ + this.patternFillColor = (this.patternFillColorsFlag and PATTERN_FILL_BACK_COLOR shr 7).toShort().toInt() + this.patternFillColorBack = (this.patternFillColorsFlag and PATTERN_FILL_COLOR).toShort().toInt() + bHasPatternBlock = true + } + + /** + * Apply all the patternFillColor fields into the current pattern colors flag + */ + fun updatePatternFillColorsFlag() { + flags = flags or 0x20000000 // set flags to denote has a pattern block + bHasPatternBlock = true + patternFillColorsFlag = patternFillColorBack.toShort().toInt() + patternFillColorsFlag = (patternFillColorsFlag or (patternFillColor shl 7)).toShort().toInt() + } + + override fun getFont(): Font? { + if (!this.bHasFontBlock) + return null + + if (font != null) + return font + var t = this.fontHeight + var x = this.fontWeight + if (t == -1) + t = 180 + else + t *= 20 + if (x == -1) + x = Font.PLAIN + font = Font("Arial", x, t) + if (fontColorIndex > -1) + font!!.color = fontColorIndex + return font + } + + /** + * sets the font escapement for this conditional format to superscript + */ + fun setFontEscapementSuper() { + this.fontEscapementFlag = FONT_ESCAPEMENT_SUPER + this.fontEscapementFlagModifiedFlag = 0 // + this.bHasFontBlock = true + } + + /** + * sets the font escapement for this conditional format to subscript + */ + fun setFontEscapementSub() { + this.fontEscapementFlag = FONT_ESCAPEMENT_SUB + this.fontEscapementFlagModifiedFlag = 0 // + this.bHasFontBlock = true + } + + /** + * @return Returns the borderLineStylesLeft. + */ + fun getBorderLineStylesLeft(): Int { + return borderLineStylesLeft + } + + /** + * @param borderLineStylesLeft The borderLineStylesLeft to set. + */ + fun setBorderLineStylesLeft(b: Int) { + this.borderLineStylesLeft = b + this.updateBorderLineStylesFlag() + } + + /** + * @return Returns the borderLineStylesRight. + */ + fun getBorderLineStylesRight(): Int { + return borderLineStylesRight + } + + /** + * @param borderLineStylesRight The borderLineStylesRight to set. + */ + fun setBorderLineStylesRight(b: Int) { + this.borderLineStylesRight = b + this.updateBorderLineStylesFlag() + } + + /** + * @return Returns the borderLineStylesTop. + */ + fun getBorderLineStylesTop(): Int { + return borderLineStylesTop + } + + /** + * @param borderLineStylesTop The borderLineStylesTop to set. + */ + fun setBorderLineStylesTop(b: Int) { + this.borderLineStylesTop = b + this.updateBorderLineStylesFlag() + } + + /** + * @return Returns the borderLineStylesBottom. + */ + fun getBorderLineStylesBottom(): Int { + return borderLineStylesBottom + } + + /** + * @param borderLineStylesBottom The borderLineStylesBottom to set. + */ + fun setBorderLineStylesBottom(b: Int) { + this.borderLineStylesBottom = b + this.updateBorderLineStylesFlag() + } + + /** + * @return Returns the borderLineColorLeft. + */ + fun getBorderLineColorLeft(): Int { + if (borderLineColorLeft > this.colorTable.size) + return 0 + return if (borderLineColorLeft < 0) 0 else borderLineColorLeft + } + + /** + * @param borderLineColorLeft The borderLineColorLeft to set. + */ + fun setBorderLineColorLeft(borderLineColorLeft: Int) { + this.borderLineColorLeft = borderLineColorLeft + // 20091028 KSC: insure flag denotes borderlinecolor is modified + flags = flags and BORDER_MODIFIED_LEFT - 1 // set flags to denote border top is modified (set bit=0) + this.updateBorderLineColorsFlag() + } + + /** + * @return Returns the borderLineColorRight. + */ + fun getBorderLineColorRight(): Int { + if (borderLineColorRight > this.colorTable.size) + return 0 + return if (borderLineColorRight < 0) 0 else borderLineColorRight + } + + /** + * @param borderLineColorRight The borderLineColorRight to set. + */ + fun setBorderLineColorRight(borderLineColorRight: Int) { + this.borderLineColorRight = borderLineColorRight + // 20091028 KSC: insure flag denotes borderlinecolor is modified + flags = flags and BORDER_MODIFIED_RIGHT - 1 // set flags to denote border top is modified (set bit=0) + this.updateBorderLineColorsFlag() + } + + /** + * @return Returns the borderLineColorTop. + */ + fun getBorderLineColorTop(): Int { + if (borderLineColorTop > this.colorTable.size) + return 0 + return if (borderLineColorTop < 0) 0 else borderLineColorTop + } + + /** + * @param borderLineColorTop The borderLineColorTop to set. + */ + fun setBorderLineColorTop(b: Int) { + this.borderLineColorTop = b + // 20091028 KSC: insure flag denotes borderlinecolor is modified + flags = flags and BORDER_MODIFIED_TOP - 1 // set flags to denote border top is modified (set bit=0) + this.updateBorderLineColorsFlag() + if (this.borderLineColorTop != b) + Logger.logWarn("setBorderLineColorTop failed") + } + + /** + * @return Returns the borderLineColorBottom. + */ + fun getBorderLineColorBottom(): Int { + if (borderLineColorBottom > this.colorTable.size) + return 0 + return if (borderLineColorBottom < 0) 0 else borderLineColorBottom + } + + /** + * @param borderLineColorBottom The borderLineColorBottom to set. + */ + fun setBorderLineColorBottom(b: Int) { + this.borderLineColorBottom = b + // 20091028 KSC: insure flag denotes borderlinecolor is modified + flags = flags and BORDER_MODIFIED_BOTTOM - 1 // set flags to denote border top is modified (set bit=0) + this.updateBorderLineColorsFlag() + if (this.borderLineColorBottom != b) + Logger.logWarn("borderLineColorBottom failed") + } + + /** + * Returns the patternFillStyle. + *

                  NOTE in 2003-ver patternFillStyle is valid 1-> + * + * @return Returns the patternFillStyle. + */ + fun getPatternFillStyle(): Int { + return patternFillStyle + } + + /** + * @param patternFillStyle The patternFillStyle to set. + */ + fun setPatternFillStyle(p: Int) { + this.patternFillStyle = p + bHasPatternBlock = true + } + + /** + * @return Returns the patternFillColor. + */ + fun getPatternFillColor(): Int { + return if (fill != null) fill!!.getFgColorAsInt(workBook!!.theme) else patternFillColor + } + + /** + * @param patternFillColor The patternFillColor to set. + */ + fun setPatternFillColor(p: Int, custom: String?) { + this.patternFillColor = p + if (fill != null) fill!!.setFgColor(p) + this.updatePatternFillColorsFlag() + } + + /** + * @return Returns the patternFillColorBack. + */ + fun getPatternFillColorBack(): Int { + return if (fill != null) fill!!.getBgColorAsInt(workBook!!.theme) else patternFillColorBack + } + + /** + * @param patternFillColorBack The patternFillColorBack to set. + */ + fun setPatternFillColorBack(p: Int) { + this.patternFillColorBack = p + if (fill != null) fill!!.setBgColor(p) + this.updatePatternFillColorsFlag() + } + + /** + * @return Returns the fontHeight. + */ + fun getFontHeight(): Int { + return fontHeight + } + + /** + * @param fontHeight The fontHeight to set. + */ + fun setFontHeight(fontHeight: Int) { + this.fontHeight = fontHeight + bHasFontBlock = true + if (font != null) font!!.fontHeight = fontHeight + } + + /** + * @return Returns the fontWeight. + */ + fun getFontWeight(): Int { + return fontWeight + } + + /** + * @param fontWeight The fontWeight to set. + */ + fun setFontWeight(f: Int) { + this.fontWeight = f + this.fontOptsFlag = this.fontOptsFlag and 0xFD // turn off bit 1 = style bit + bHasFontBlock = true + if (font != null) font!!.fontWeight = f + } + + /** + * @return Returns the fontUnderlineStyle. + */ + fun getFontUnderlineStyle(): Int { + return fontUnderlineStyle + } + + /** + * @param fontUnderlineStyle The fontUnderlineStyle to set. + */ + fun setFontUnderlineStyle(fontUnderlineStyle: Int) { + this.fontUnderlineStyle = fontUnderlineStyle + this.fontUnderlineModifiedFlag = 0 // set modified flag + bHasFontBlock = true + if (font != null) font!!.setUnderlineStyle(fontUnderlineStyle.toByte()) + } + + /** + * @return Returns the fontColorIndex. + */ + fun getFontColorIndex(): Int { + return fontColorIndex + } + + /** + * @param fontColorIndex The fontColorIndex to set. + */ + fun setFontColorIndex(fontColorIndex: Int) { + this.fontColorIndex = fontColorIndex + bHasFontBlock = true + if (font != null) font!!.color = fontColorIndex + } + + /** + * Reset the ptgRef to A1 in these that is replaced with + * current Ptg + */ + private fun resetFormulaRef() { + // TODO: test what happens when A1 is a valid part of the expression + val expr = this.getFormula1()!!.expression + val itx = expr!!.iterator() + if (refPos > -1) { + expr.insertElementAt(prefHolder, refPos) + } + } + + /** + * There is a ptgRef to A1 in these that is replaced with + * current Ptg + */ + @Throws(FormulaNotFoundException::class) + private fun setFormulaRef(refcell: Ptg) { + // TODO: test what happens when A1 is a valid part of the expression + val expr = this.getFormula1()!!.expression + val itx = expr!!.iterator() + + val rc = refcell.intLocation + if (refPos == -1) { + while (itx.hasNext()) { + val prex = itx.next() as Ptg + if (prex is PtgRefN) { + prex.setFormulaRow(rc[0]) + prex.setFormulaCol(rc[1]) + } + } + } else { + expr.removeAt(refPos) + } + if (refPos > -1) { + expr.remove(prefHolder) + expr.insertElementAt(refcell, refPos) + } + + } + + /** + * pass in the referenced cell and attempt to + * create a valid formula for this thing and + * calculate whether the criteria passes + * + * + * returns true or false + * + * @param the reference to evaluate + * @return boolean passes + */ + fun evaluate(refcell: Ptg): Boolean { + try { + var val2: Any? = null + var val1: Any? = null + + if (this.operator.toInt() != 0x0) + // calcs later + val1 = this.getFormula1()!!.calculateFormula() + + if (this.cce2 > 0) + val2 = this.getFormula2()!!.calculateFormula() + + val valX = refcell.value + + // cast to double then compare + var d1 = 0.0 + var d2 = 0.0 // second val from expression2 + + var dX = 0.0 // the reference val + + try { + d1 = Double(val1!!.toString()) + dX = Double(valX.toString()) + if (this.cce2 > 0) { // we have a second value + d2 = Double(val2!!.toString()) + } + + } catch (e: Exception) { + // not numeric + } + + // handle evaluated condition + when (this.operator) { + 0x0 // No comparison (only valid for formula type, see above) + -> { + setFormulaRef(refcell) + val1 = this.getFormula1()!!.calculateFormula() + return (val1 as Boolean).booleanValue() + } + + 1 // Between + -> + // expression2 for the other bounds ... + return dX >= d1 && dX <= d2 + + 0x5 // Greater than + -> return dX > d1 + + 0x2 // Not between + -> { + if (dX < d1) + return false + else if (dX > d1) + // hmmm... d2 is where? an array? + return false + return dX < d1 + } + + 0x6 // Less than + -> return dX < d1 + + 0x3 // Equal + -> return dX == d1 + + 0x7 // Greater or equal + -> return dX >= d1 + + 0x4 // Not equal + -> return dX != d1 + + 0x8 // Less or equal + -> return dX <= d1 + + // 2007-specific operators + 0x9 // begins With + , 0xA // ends With + , 0xB // contains text + , 0xC // not contains + -> return false + + else -> return false + } + } catch (ex: Exception) { + // Logger.logWarn("CF condition "+this.formula1.getFormulaString()+" evaluation failed for : " + refcell.toString()); + return false + } + + } + + + /** + * return the first formula referenced by the Conditional Format + * + * @return Formula + */ + fun getFormula1(): Formula? { + if (formula1 == null) { // hasn't been set + formula1 = Formula() + formula1!!.workBook = this.workBook + if (this.sheet == null) + this.setSheet(this.condfmt!!.sheet) // help! + formula1!!.setSheet(this.sheet) + formula1!!.expression = expression1 + } + // 20101216 KSC: WHY???? formula1.setCachedValue(null); + return formula1 + } + + /** + * return the second formula referenced by the Conditional Format + * + * @return Formula + */ + fun getFormula2(): Formula? { + if (formula2 == null && cce2 > 0) { // hasn't been set + formula2 = Formula() + formula2!!.workBook = this.workBook + if (this.sheet == null) + this.setSheet(this.condfmt!!.sheet) // help! + formula2!!.setSheet(this.sheet) + formula2!!.expression = expression2 + } + if (formula2 != null) { + formula2!!.setSheet(this.sheet) + // 20101216 KSC: WHY??? formula2.setCachedValue(null); + } + return formula2 + } + + /** + * OOXML-specific, in a containsText-type condition, + * this field is the comparitor + * + * @param s + */ + fun setContainsText(s: String) { + containsText = s + } + + /** + * generate OOXML for this Cf (BIFF8->OOXML) + * attributes: type, dxfId, priority (REQ), stopIfTrue, aboveAverage, + * percent, bottom, operator, text, timePeriod, rank, stdDev, equalAverage + * children: SEQ: formula (0-3), colorScale, dataBar, iconSet + * + * @return + */ + fun getOOXML(bk: WorkBookHandle, priority: Int, dxfs: ArrayList<*>): String { + val ooxml = StringBuffer() + + // first deal with dfx's (differential xf's) - part of styles.xml; here we need to add dxf element to dxf's plus trap dxfId + val dxf = Dxf() + if (this.bHasFontBlock) { + if (font != null) + dxf.font = font + else + dxf.createFont(this.fontWeight, this.fontItalic, this.fontUnderlineStyle, this.fontColorIndex, this.fontHeight) + } + if (this.bHasPatternBlock) { + if (fill != null) + dxf.fill = fill!! + else + dxf.createFill(this.patternFillStyle, this.patternFillColor, this.patternFillColorBack, bk) + } + if (this.bHasBorderBlock) { + dxf.createBorder(bk, this.borderStyles, intArrayOf(this.getBorderLineColorTop(), this.getBorderLineColorLeft(), this.getBorderLineColorBottom(), this.getBorderLineColorRight())) + } + // TODO: check if this dxf already exists **************************************************************************** + dxfs.add(dxf) // save newly created dxf (differential xf) to workbook store + val dxfId = dxfs.size - 1 // link this cf to it's dxf NOTE: one of the ONLY OOXML id's that is 0-based ... + + ooxml.append(" ooxml.append(" type=\"cellIs\"") + 2 -> ooxml.append(" type=\"expression\"") + 3 -> ooxml.append(" type=\"containsText\"") + 4 -> ooxml.append(" type=\"aboveAverage\"") + 5 -> ooxml.append(" type=\"beginsWith\"") + 6 -> ooxml.append(" type=\"colorScale\"") + 7 -> ooxml.append(" type=\"containsBlanks\"") + 8 -> ooxml.append(" type=\"containsErrors\"") + 9 -> ooxml.append(" type=\"dataBar\"") + 10 -> ooxml.append(" type=\"duplicateValues\"") + 11 -> ooxml.append(" type=\"endsWith\"") + 12 -> ooxml.append(" type=\"iconSet\"") + 13 -> ooxml.append(" type=\"notContainsBlanks\"") + 14 -> ooxml.append(" type=\"notContainsErrors\"") + 15 -> ooxml.append(" type=\"notContainsText\"") + 16 -> ooxml.append(" type=\"timePeriod\"") + 17 -> ooxml.append(" type=\"top10\"") + 18 -> ooxml.append(" type=\"uniqueValues\"") + } + if (this.type.toInt() == 3 && containsText != null) + // containsText - shouldn't be null! + ooxml.append(" text=\"$containsText\"") + + // operator + when (this.operator) { + 1 // Between + -> ooxml.append(" operator=\"between\"") + 0x5 // Greater than + -> ooxml.append(" operator=\"greaterThan\"") + 0x2 // Not between + -> ooxml.append(" operator=\"notBetween\"") + 0x6 // Less than + -> ooxml.append(" operator=\"lessThan\"") + 0x3 // Equal + -> ooxml.append(" operator=\"equal\"") + 0x7 // Greater or equal + -> ooxml.append(" operator=\"greaterThanOrEqual\"") + 0x4 // Not equal + -> ooxml.append(" operator=\"notEqual\"") + 0x8 // Less or equal + -> ooxml.append(" operator=\"lessThanOrEqual\"") + // 2007-specific + 0x9 // begins With + -> ooxml.append(" operator=\"beginsWith\"") + 0xA // ends With + -> ooxml.append(" operator=\"endsWith\"") + 0xB // begins With + -> ooxml.append(" operator=\"containsText\"") + 0xC // begins With + -> ooxml.append(" operator=\"notContains\"") + } + // priority + ooxml.append(" priority=\"$priority\"") + // stopIfTrue == looks like this is set by default + ooxml.append(" stopIfTrue=\"1\"") + ooxml.append(">") + if (this.getFormula1() != null) + ooxml.append("" + OOXMLAdapter.stripNonAsciiRetainQuote(this.getFormula1()!!.formulaString).substring(1) + "") + if (this.getFormula2() != null) + ooxml.append("" + OOXMLAdapter.stripNonAsciiRetainQuote(this.getFormula2()!!.formulaString).substring(1) + "") + + // TODO: finish children dataBar, colorScale, iconSet, aboveAverage, bottom, equalAverage, + ooxml.append("") + return ooxml.toString() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5624169378370505532L + // Offset Size Contents + // 0 2 Fill pattern style: + // Bit Mask Contents + // 15-10 FC00H Fill pattern style (only if patt - style = 0, ?3.11) + val PATTERN_FILL_STYLE = 0xFC00 + + // 2 2 Fill pattern colour indexes: + // Bit Mask Contents + // 6-0 007FH Colour index (?6.70) for pattern (only if patt - col = 0) + // 13-7 3F80H Colour index (?6.70) for pattern background (only if patt - bgcol = 0) + val PATTERN_FILL_COLOR = 0x007F + val PATTERN_FILL_BACK_COLOR = 0x3F80 + + // 68 4 Font options: + // Bit Mask Contents + // 1 00000002H Posture: 0 = Normal; 1 = Italic (only if font - style = 0) + // 7 00000080H Cancellation: 0 = Off; 1 = On (only if font - canc = 0) + val FONT_OPTIONS_POSTURE = 0x2 + val FONT_OPTIONS_CANCELLATION = 0x80 + val FONT_OPTIONS_POSTURE_NORMAL = 0 + val FONT_OPTIONS_POSTURE_ITALIC = 1 + val FONT_OPTIONS_CANCELLATION_OFF = 0 + val FONT_OPTIONS_CANCELLATION_ON = 1 + + // Standard values are 0190H (400) for normal text and 02BCH (700) for bold text. + + // 74 2 Escapement type (only if font - esc = 0): + // 0000H = None; 0001H = Superscript; 0002H = Subscript + val FONT_ESCAPEMENT_NONE = 0x0 + val FONT_ESCAPEMENT_SUPER = 0x1 + val FONT_ESCAPEMENT_SUB = 0x2 + + // 76 1 Underline type (only if font - underl = 0): + val FONT_UNDERLINE_NONE = 0x0 + val FONT_UNDERLINE_SINGLE = 0x1 + val FONT_UNDERLINE_DOUBLE = 0x2 + val FONT_UNDERLINE_SINGLEACCOUNTING = 0x21 + val FONT_UNDERLINE_DOUBLEACCOUNTING = 0x22 + + val FONT_MODIFIED_OPTIONS_STYLE = 0x00000002 + val FONT_MODIFIED_OPTIONS_CANCELLATIONS = 0x00000080 + + /** + * Border Formatting Block + */ + // Offset Size Contents + // 0 2 Border line styles: + // Bit Mask Contents + // 3-0 000FH Left line style (only if bord - left = 0, ?3.10) + // 7-4 00F0H Right line style (only if bord - right = 0, ?3.10) + // 11-8 0F00H Top line style (only if bord - top = 0, ?3.10) + // 15-12 F000H Bottom line style (only if bord - bot = 0, ?3.10) + val BORDER_LINESTYLE_LEFT = 0x000F + val BORDER_LINESTYLE_RIGHT = 0x00F0 + val BORDER_LINESTYLE_TOP = 0x0F00 + val BORDER_LINESTYLE_BOTTOM = 0xF000 + // if flags & BORDER_MODIFIED_XX == 0 means that this particular border has been modified + val BORDER_MODIFIED_LEFT = 0x0400 + val BORDER_MODIFIED_RIGHT = 0x0800 + val BORDER_MODIFIED_TOP = 0x1000 + val BORDER_MODIFIED_BOTTOM = 0x2000 + + // 2 4 Border line colour indexes: + // Bit Mask Contents + // 6-0 0000007FH Colour index (?6.70) for left line (only if bord - left = 0) + // 13-7 00003F80H Colour index (?6.70) for right line (only if bord - right = 0) + // 22-16 007F0000H Colour index (?6.70) for top line (only if bord - top = 0) + // 29-23 3F800000H Colour index (?6.70) for bottom line (only if bord - bot = 0) + val BORDER_LINECOLOR_LEFT = 0x0000007F + val BORDER_LINECOLOR_RIGHT = 0x00003F80 + val BORDER_LINECOLOR_TOP = 0x007F0000 + val BORDER_LINECOLOR_BOTTOM = 0x3F800000 + + + /** + * helper method to set the style values on this object from a style string + *

                  each name/value pair is delimited by ; + *

                  possible tokens: + *

                  pattern pattern fill # + *

                  color pattern fg color + *

                  patterncolor pattern bg color + *

                  vertical vertical alignnment + *

                  horizontal horizontal alignment + *

                  border sub-tokens: border-top, border-left, border-bottom, border-top + * + * @param style + * @param cx + */ + fun setStylePropsFromString(style: String, cx: Cf) { + val toks = StringTool.getTokensUsingDelim(style, ";") + // iterate styles, set values + for (t in toks.indices) { + val pos = toks[t].indexOf(":") + var n = "" + // TODO: border line sizes, interpret border line styles + // TODO: handle vertical, horizontal alignment, number format ... + if (pos > 0) + n = toks[t].substring(0, pos) + + var v = toks[t].substring(toks[t].indexOf(":") + 1) + n = StringTool.strip(n, '"') + v = StringTool.strip(v, '#') + n = StringTool.allTrim(n) + v = StringTool.allTrim(v) + n = n.toLowerCase() + if (n.indexOf("border") == 0) { // parse the border settings + val vs = StringTool.getTokensUsingDelim(v, " ") + var sz = -1 + var cz = -1 + var stl = -1 + var clr: String? = null + try { + sz = Integer.parseInt(vs[0]) // size + clr = vs[2] // color String + // TODO: no way to set border size as of yet + + // interpret style string into #: + for (i in FormatConstants.BORDER_NAMES.indices) { + if (FormatConstants.BORDER_NAMES[i] == vs[1]) { + stl = i + break + } + } + + // HexStringToColorInt + // java.awt.Color c = FormatHandle.HexStringToColor(clr); + cz = FormatHandle.HexStringToColorInt(clr!!, FormatHandle.colorBACKGROUND) + // Logger.logInfo("ix " + sz + " " + clr + " " + stl ); + + } catch (ex: Exception) { + } + + if (n.indexOf("border-top") == 0) { + if (clr != null) { // set color + cx.setBorderLineColorTop(cz) + } + if (stl > -1) { + cx.setBorderLineStylesTop(stl) + } + } else if (n.indexOf("border-left") == 0) { + if (clr != null) { // set color + cx.setBorderLineColorLeft(cz) + } + if (stl > -1) { + cx.setBorderLineStylesLeft(stl) + } + } else if (n.indexOf("border-bottom") == 0) { + if (clr != null) { // set color + cx.setBorderLineColorBottom(cz) + } + if (stl > -1) { + cx.setBorderLineStylesBottom(stl) + } + } else if (n.indexOf("border-right") == 0) { + if (clr != null) { // set color + cx.setBorderLineColorRight(cz) + } + if (stl > -1) { + cx.setBorderLineStylesRight(stl) + } + } + + } else if (n.equals("text-line-through", ignoreCase = true)) { + if (v.equals("none", ignoreCase = true)) { + cx.fontStriken = false + } else { + cx.fontStriken = true + } + } else if (n.equals("fg", ignoreCase = true)) { // font color + // set the color + val cl = FormatHandle.HexStringToColorInt(v, FormatHandle.colorFOREGROUND) + cx.setFontColorIndex(cl) + } else if (n.equals("pattern", ignoreCase = true)) { //fill pattern + val vv = Integer.parseInt(v) + cx.setPatternFillStyle(vv) + } else if (n.equals("color", ignoreCase = true) || n.equals("patterncolor", ignoreCase = true)) { // fill (pattern or fg) color + val cl = FormatHandle.HexStringToColorInt(v, FormatHandle.colorFONT) // finds best match + cx.setPatternFillColor(cl, v) + } else if (n.equals("background", ignoreCase = true)) { // fill bg color + val cl = FormatHandle.HexStringToColorInt(v, FormatHandle.colorBACKGROUND) // finds best match + cx.setPatternFillColorBack(cl) + // ALIGNMENT + } else if (n.equals("alignment-horizontal", ignoreCase = true)) { + val s = Integer.parseInt(v) + // TODO: set alignment + } else if (n.equals("alignment-vertical", ignoreCase = true)) { + val s = Integer.parseInt(v) + // TODO: set alignment + } else if (n.equals("numberformat", ignoreCase = true)) { + val s = Integer.parseInt(v) + // TODO: set number format + // FONT options + } else if (n.equals("font-height", ignoreCase = true)) { + val s = Integer.parseInt(v) + cx.setFontHeight(s) + } else if (n.equals("font-weight", ignoreCase = true)) { + val s = Integer.parseInt(v) + cx.setFontWeight(s) + } else if (n.equals("font-EscapementFlag", ignoreCase = true)) { + val s = Integer.parseInt(v) // 0= none, 1= superscript, 2= subscript + cx.fontEscapement = s + } else if (n.equals("font-striken", ignoreCase = true)) { + val b = java.lang.Boolean.valueOf(v).booleanValue() + cx.fontStriken = b + } else if (n.equals("font-italic", ignoreCase = true)) { + val b = java.lang.Boolean.valueOf(v).booleanValue() + cx.fontItalic = b + } else if (n.equals("font-ColorIndex", ignoreCase = true)) { + val s = Integer.parseInt(v) + cx.setFontColorIndex(s) + } else if (n.equals("font-UnderlineStyle", ignoreCase = true)) { + val s = Integer.parseInt(v) + cx.setFontUnderlineStyle(s) + } + /* TODO: handle + fontName? + * fontOptsFlag + fontModifiedOptionsFlag + fontUnderlineModified +*/ + } + cx.updateRecord() + } + + + /** + * set the conditional format format settings from the OOXML dxf, or differential xf, format settings + * + * @param dxf + * @param cf + */ + fun setStylePropsFromDxf(dxf: Dxf, cf: Cf) { + val borderStyles = dxf.borderStyles + if (borderStyles != null) { // then should have every other element + val borderColors = dxf.borderColors + val borderSizes = dxf.borderSizes + cf.setBorderLineColorTop(borderColors!![0]) + cf.setBorderLineStylesTop(borderStyles[0]) + cf.setBorderLineColorLeft(borderColors[1]) + cf.setBorderLineStylesLeft(borderStyles[1]) + cf.setBorderLineColorBottom(borderColors[2]) + cf.setBorderLineStylesBottom(borderStyles[2]) + cf.setBorderLineColorRight(borderColors[3]) + cf.setBorderLineStylesRight(borderStyles[3]) + // TODO border size?? + } + + // FILL **************** + val fls = dxf.fillPatternInt + if (fls >= 0) { + cf.setPatternFillStyle(fls) + val fg = dxf.fg + val bg = dxf.bg + if (fg != -1) + cf.setPatternFillColor(fg, null) + if (bg != -1) + cf.setPatternFillColorBack(bg) + } + cf.fill = dxf.fill // save OOXML var as color settings sometimes cannot be interpreted in 2003 v. + + var z: Int + // ALIGNMENT ****************** + var s = dxf.horizontalAlign + if (s != null) { + z = Integer.parseInt(s) + //TODO: set alignment + } + s = dxf.verticalAlign + if (s != null) { + z = Integer.parseInt(s) + //TODO: set alignment + } + + // Number Format *************** + s = dxf.numberFormat + if (s != null) { + z = Integer.parseInt(s) + //TODO: set number format + } + + + // FONT ************************ + if (dxf.font != null) { + cf.parseFont(dxf.font!!) + cf.font = dxf.font + } + + + /* TODO: handle + fontName? +* fontOptsFlag + fontModifiedOptionsFlag + fontUnderlineModified +*/ + cf.updateRecord() + } + + + /** + * Create a Cf record & populate with prototype bytes + * + * @return TODO: NOT FINISHED + */ + val prototype: XLSRecord? + get() { + val cf = Cf() + cf.opcode = XLSConstants.CF + cf.setData(cf.PROTOTYPE_BYTES) + cf.init() + return cf + } + + + /** + * Returns the byte Condition type from a human-readable string + * + * @return the condition type for this rule + */ + fun getConditionFromString(cx: String): Byte { + var cx = cx + cx = StringTool.allTrim(cx) // trim + cx = cx.toUpperCase() + // handle evaluated condition + if (cx == "BETWEEN") + return 0x1 // Between + if (cx == "GREATER THAN") + return 0x5 // Greater than + if (cx == "NOT BETWEEN") + return 0x2 // Not between + if (cx == "LESS THAN") + return 0x6 // Less than + if (cx == "EQUALS") + return 0x3 // Equal + if (cx == "GREATER THAN OR EQUAL") + return 0x7 // Greater or equal + if (cx == "NOT EQUAL") + return 0x4 // Not equal + return if (cx == "LESS THAN OR EQUAL") + 0x8 // Less or equal + else + 0x0 // No comparison (only valid for formula type, see above) + } + + /** + * restore Formula strings from XML serialization + * + * @param fmx + * @return + */ + private fun unescapeFormulaString(fmx: String): String { + var fmx = fmx + fmx = StringTool.replaceText(fmx, """, "\"") + // fmx = StringTool.replaceText(fmx,"&","%"); + fmx = StringTool.replaceText(fmx, "<", "<") + fmx = StringTool.replaceText(fmx, ">", ">") + return fmx + } + + /** + * taks a String representing the Operator for this Cf Rule + * and translates it to an int + *

                  NOTE: 2003 versions do not use types + * 0x9, 0xA, 0xB or 0xC + * + * @param String operator - String CfRule operator attribute + * @return int representing Cf operator value + */ + fun translateOperator(operator: String?): Int { + if (operator == null) + // type is not cellIs + return 0 + if (operator == "between") + return 0x1 + else if (operator == "greaterThan") + return 0x5 + else if (operator == "notBetween") + return 0x2 + else if (operator == "lessThan") + return 0x6 + else if (operator == "equal") + return 0x3 + else if (operator == "greaterThanOrEqual") + return 0x7 + else if (operator == "notEqual") + return 0x4 + else if (operator == "lessThanOrEqual") + return 0x8 + else if (operator == "beginsWith") + return 0x9 + else if (operator == "endsWith") + return 0xA + else if (operator == "containsText") + return 0xB + else if (operator == "notContains") + return 0xC// NO EQUIVALENT IN 2003: beginsWith, containsText, endsWith, notContains + return 0 + } + + /** + * Given an int type, return it's string representation + *

                  NOTE: if type is between 3 and 18, + * it is an OOXML-specific type. + * + * @param type type integer + * @return String reprentation + * @see translateType + */ + protected fun translateType(type: Int): String { + when (type) { + 1 -> return "Cell Is" + 2 -> return "expression" + 3 -> return "containsText" + 4 -> return "aboveAverage" + 5 -> return "beginsWith" + 6 -> return "colorScale" + 7 -> return "containsBlanks" + 8 -> return "containsErrors" + 9 -> return "dataBar" + 10 -> return "duplicateValues" + 11 -> return "endsWith" + 12 -> return "iconSet" + 13 -> return "notContainsBlanks" + 14 -> return "notContainsErrors" + 15 -> return "notContainsText" + 16 -> return "timePeriod" + 17 -> return "top10" + 18 -> return "uniqueValues" + else -> return "Unknnown" + } + } + + /** + * takes a String representing the type attribute and translates it to + * the corresponding integer representation + *

                  IMPORTANT NOTE: OOXML-specific types are converted to an integer that is not valid in 2003 versions + * + * @param String type - OOXML CfRule type attribute + * @return int representing Cf type value + */ + fun translateOOXMLType(type: String): Int { + if (type == "cellIs") + return 1 + else if (type == "expression") + return 2 + else if (type == "containsText") + return 3 + else if (type == "aboveAverage") + return 4 + else if (type == "beginsWith") + return 5 + else if (type == "colorScale") + return 6 + else if (type == "containsBlanks") + return 7 + else if (type == "containsErrors") + return 8 + else if (type == "dataBar") + return 9 + else if (type == "duplicateValues") + return 10 + else if (type == "endsWith") + return 11 + else if (type == "iconSet") + return 12 + else if (type == "notContainsBlanks") + return 13 + else if (type == "notContainsErrors") + return 14 + else if (type == "notContainsText") + return 15 + else if (type == "timePeriod") + return 16 + else if (type == "top10") + return 17 + else if (type == "uniqueValues") + return 18// no equivalent in 2003: but must track for 2007 uses + return 1 // default to cellIs ???? + } + + /** + * prepare Formula strings for XML serialization + * + * @param fmx + * @return + */ + private fun escapeFormulaString(fmx: String): String { + var fmx = fmx + fmx = StringTool.replaceText(fmx, "\"", """) + if (fmx.indexOf("=") == 0) { + fmx = fmx.substring(1) + } + // fmx = StringTool.replaceText(fmx,"%","&"); + fmx = StringTool.replaceText(fmx, "<", "<") + fmx = StringTool.replaceText(fmx, ">", ">") + return fmx + + } + + // helper inner method allows it to be static and reused... + private fun getXML(cfx: Cf): String { + + // reset the placeholder formula reference + if (cfx.refPos > -1) + cfx.resetFormulaRef() + + val xml = StringBuffer() + + xml.append("") + val rn = CellRange(cfx.condfmt!!.boundingRange)//getConditionalFormatRange(); + if (rn != null) + xml.append(rn.r1C1Range) + + xml.append("") + xml.append("") + if (cfx.operator.toInt() != 0x0) { // calcer + xml.append("") + xml.append(cfx.conditionString) + xml.append("") + } + val val1: Any? + val val2: Any? + if (cfx.operator.toInt() != 0x0) { // calcer + xml.append("") + val1 = cfx.getFormula1()!!.calculateFormula() + if (val1 == null) + // a new formula + calc_explicit + { + } else + xml.append(val1.toString()) + xml.append("") + } else { + xml.append("") + var fmx = cfx.getFormula1()!!.formulaString + fmx = Cf.escapeFormulaString(fmx) + xml.append(fmx) + xml.append("") + } + + if (cfx.cce2 > 0) { + val2 = cfx.getFormula2()!!.calculateFormula() + xml.append("") + if (val2 == null) + // a new formula + calc_explicit + { + } else + xml.append(val2.toString()) + xml.append("") + } + + xml.append("") + xml.append("") + + return xml.toString() + } + + /** + * creates a Cf record from the EXML nodes + * + * + * + * Between + * 2 + * 4 + * + * + * + * @param xpp + * @return + */ + fun parseXML(xpp: XmlPullParser, cfx: Condfmt, bs: Boundsheet): Cf? { + val oe = bs.createCf(cfx) + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "Condition") { // get attributes + for (i in 0 until xpp.attributeCount) { + val n = xpp.getAttributeName(i) + val v = xpp.getAttributeValue(i) + if (n == "Qualifier") { + oe.setOperator(v) + } else if (n == "Value1") { + oe.setCondition1(v) + } else if (n == "Value2") { + oe.setCondition2(v) + } else if (n == "Format") { + setStylePropsFromString(v, oe) + } + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "Condition") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("Cf.parseXML: $e") + } + + return oe + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/Cf12.java b/src/main/java/io/starter/formats/XLS/Cf12.kt similarity index 83% rename from src/main/java/io/starter/formats/XLS/Cf12.java rename to src/main/java/io/starter/formats/XLS/Cf12.kt index f50e416..18df9ac 100644 --- a/src/main/java/io/starter/formats/XLS/Cf12.java +++ b/src/main/java/io/starter/formats/XLS/Cf12.kt @@ -20,21 +20,24 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * CF 12 is a FRT (Future record type) record that handles extended conditional formatting information that * was not available before excel 2007. It is currently unsupported in OpenXLS, but the record looks much the same * to a standard CF rule - *

                  + * + * * I'm guessing when implemented we will either want a member cf record or even just extend cf with this. */ -public class Cf12 extends XLSRecord { +class Cf12 : XLSRecord() { + companion object { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4974997074621442348L; + /** + * serialVersionUID + */ + private val serialVersionUID = 4974997074621442348L + } } diff --git a/src/main/java/io/starter/formats/XLS/ChartNotFoundException.java b/src/main/java/io/starter/formats/XLS/ChartNotFoundException.kt similarity index 66% rename from src/main/java/io/starter/formats/XLS/ChartNotFoundException.java rename to src/main/java/io/starter/formats/XLS/ChartNotFoundException.kt index 3957e60..81b5275 100644 --- a/src/main/java/io/starter/formats/XLS/ChartNotFoundException.java +++ b/src/main/java/io/starter/formats/XLS/ChartNotFoundException.kt @@ -20,38 +20,40 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** - * No Chart Found. + * **No Chart Found.** * * @see Chart */ -public final class ChartNotFoundException extends java.lang.Exception { +class ChartNotFoundException(n: String) : java.lang.Exception() { + internal var name = "" - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6189760703370814620L; - String name = ""; - - public ChartNotFoundException(String n) { - super(); - name = n; + init { + name = n } - public String getMessage() { + override fun getMessage(): String { // This method is derived from class java.lang.Throwable // to do: code goes here - return this.toString(); + return this.toString() } - public String toString() { + override fun toString(): String { // This method is derived from class java.lang.Throwable // to do: code goes here - return "Chart Not Found in File. : '" + name + "'"; + return "Chart Not Found in File. : '$name'" + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -6189760703370814620L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Codename.java b/src/main/java/io/starter/formats/XLS/Codename.java deleted file mode 100644 index 0063722..0000000 --- a/src/main/java/io/starter/formats/XLS/Codename.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; - -/** - * ' - * the CODENAME record stores tha name for a worksheet object. It is not necessarily the same name as you see - * in the worksheet tab, rather it is the VB identifier name! - */ -public class Codename extends io.starter.formats.XLS.XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8327865068784623792L; - - private String stCodeName; - - byte cch; - byte grbitChr; - - public void init() { - super.init(); - byte[] wtf = this.getBytesAt(0, this.getLength()); - cch = this.getByteAt(0); - grbitChr = this.getByteAt(2); - - byte[] namebytes = this.getBytesAt(3, cch); - - try { - if (grbitChr == 0x1) { - stCodeName = new String(namebytes, WorkBookFactory.UNICODEENCODING); - } else { - stCodeName = new String(namebytes, WorkBookFactory.DEFAULTENCODING); - } - } catch (UnsupportedEncodingException e) { - Logger.logWarn("UnsupportedEncodingException in setting codename: " + e); - } - - } - - public void setName(String newname) { - int modnamelen = 0; - int oldnamelen = 0; - if (grbitChr == 0x0) { - oldnamelen = stCodeName.length(); - } else { - oldnamelen = (stCodeName.length() * 2); - } - - cch = (byte) newname.length(); - byte[] namebytes = newname.getBytes(); - // if (!ByteTools.isUnicode(namebytes)){ - if (!ByteTools.isUnicode(newname)) { - grbitChr = 0x0; - modnamelen = newname.length(); - } else { - grbitChr = 0x1; - modnamelen = (newname.length() * 2); - } - byte[] newdata = new byte[(this.getData().length - oldnamelen) + modnamelen]; - try { - if (grbitChr == 0x1) { - namebytes = newname.getBytes(WorkBookFactory.UNICODEENCODING); - } else { - namebytes = newname.getBytes(WorkBookFactory.DEFAULTENCODING); - } - } catch (UnsupportedEncodingException e) { - Logger.logInfo("UnsupportedEncodingException in setting sheet name: " + e); - } - System.arraycopy(namebytes, 0, newdata, 3, namebytes.length); - newdata[0] = cch; - newdata[2] = grbitChr; - this.setData(newdata); - this.init(); - } - -} diff --git a/src/main/java/io/starter/formats/XLS/Codename.kt b/src/main/java/io/starter/formats/XLS/Codename.kt new file mode 100644 index 0000000..bb03592 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Codename.kt @@ -0,0 +1,107 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.UnsupportedEncodingException + +/** + * ' + * the CODENAME record stores tha name for a worksheet object. It is not necessarily the same name as you see + * in the worksheet tab, rather it is the VB identifier name! + */ +class Codename : io.starter.formats.XLS.XLSRecord() { + + private var stCodeName: String? = null + + internal var cch: Byte = 0 + internal var grbitChr: Byte = 0 + + override fun init() { + super.init() + val wtf = this.getBytesAt(0, this.length) + cch = this.getByteAt(0) + grbitChr = this.getByteAt(2) + + val namebytes = this.getBytesAt(3, cch.toInt()) + + try { + if (grbitChr.toInt() == 0x1) { + stCodeName = String(namebytes!!, WorkBookFactory.UNICODEENCODING) + } else { + stCodeName = String(namebytes!!, WorkBookFactory.DEFAULTENCODING) + } + } catch (e: UnsupportedEncodingException) { + Logger.logWarn("UnsupportedEncodingException in setting codename: $e") + } + + } + + fun setName(newname: String) { + var modnamelen = 0 + var oldnamelen = 0 + if (grbitChr.toInt() == 0x0) { + oldnamelen = stCodeName!!.length + } else { + oldnamelen = stCodeName!!.length * 2 + } + + cch = newname.length.toByte() + var namebytes = newname.toByteArray() + // if (!ByteTools.isUnicode(namebytes)){ + if (!ByteTools.isUnicode(newname)) { + grbitChr = 0x0 + modnamelen = newname.length + } else { + grbitChr = 0x1 + modnamelen = newname.length * 2 + } + val newdata = ByteArray(this.getData()!!.size - oldnamelen + modnamelen) + try { + if (grbitChr.toInt() == 0x1) { + namebytes = newname.toByteArray(charset(WorkBookFactory.UNICODEENCODING)) + } else { + namebytes = newname.toByteArray(charset(WorkBookFactory.DEFAULTENCODING)) + } + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("UnsupportedEncodingException in setting sheet name: $e") + } + + System.arraycopy(namebytes, 0, newdata, 3, namebytes.size) + newdata[0] = cch + newdata[2] = grbitChr + this.setData(newdata) + this.init() + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -8327865068784623792L + } + +} diff --git a/src/main/java/io/starter/formats/XLS/Colinfo.java b/src/main/java/io/starter/formats/XLS/Colinfo.java deleted file mode 100644 index 0225f4e..0000000 --- a/src/main/java/io/starter/formats/XLS/Colinfo.java +++ /dev/null @@ -1,386 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - - -/** - * Colinfo: Column Formatting Information (7Dh)
                  - *

                  - * Colinfo describes the formatting for a column range - * - *

                  - * offset  name            size    contents
                  - * ---------------------------------------------------------------
                  - * 4       colFirst        2       First formatted column (0)
                  - * 6       colLast         2       Last formatted column (0)
                  - * 8       colWidth        2       Column width in 1/256 character units
                  - * 10      ixfe            2       Index to XF for columns
                  - * 12      grbit           2       Options
                  - * 14      reserved        1       Must be zero
                  - *
                  - *
                  - * grib options
                  - * offset	Bits	mask	name		contents
                  - * ---------------------------------------------------------------
                  - * 0		0		01h		fHidden		=1 if the column range is hidden
                  - * 7-1		FEh		UNUSED
                  - * 1		2-0		07h		iOutLevel	Outline Level of the column
                  - * 3		08h		Reserved	must be zero
                  - * 4		10h		iCollapsed	=1 if the col is collapsed in outlining
                  - * 7-5		E0h		Reserved	must be zero
                  - *
                  - * etc.
                  - *
                  - * Note: for a discussion of Column widths see:
                  - * http://support.microsoft.com/?kbid=214123
                  - * 

                  - */ - -public final class Colinfo - extends XLSRecord implements ColumnRange { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3048724897018541459L; - public static final int DEFAULT_COLWIDTH = 2340; // why 2000???? excel reports 2340 ...? - private int colFirst, colLast, colWidth; - private short grbit; - private boolean collapsed, hidden; - private int outlineLevel = 0; - - /** - * set last/first cols/rows - */ - public void setColFirst(int c) { - byte[] b = ByteTools.shortToLEBytes((short) c); - byte[] dt = this.getData(); - System.arraycopy(b, 0, dt, 0, 2); - this.colFirst = c; - } - - public int getColFirst() { - return colFirst; - } - - /** - * set last/first cols/rows - */ - public void setColLast(int c) { - byte[] b = ByteTools.shortToLEBytes((short) c); - byte[] dt = this.getData(); - System.arraycopy(b, 0, dt, 2, 2); - this.colLast = c; - } - - public int getColLast() { - return colLast; - } - - /** - * Shifts the whole colinfo over the amount of the offset - */ - public void moveColInfo(int offset) { - this.setColFirst(this.getColFirst() + offset); - this.setColLast(this.getColLast() + offset); - } - - public void init() { - super.init(); - colFirst = (int) ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - colLast = (int) ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - colWidth = (int) ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - ixfe = (int) ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - grbit = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - decodeGrbit(); - if (DEBUGLEVEL > 5) - Logger.logInfo("Col: " + ExcelTools.getAlphaVal(colFirst) + "-" + ExcelTools.getAlphaVal(colLast) + " ixfe: " + ixfe + " width: " + colWidth); - } - - - public static Colinfo getPrototype(int colF, int colL, int wide, int formatIdx) { - Colinfo ret = new Colinfo(); - - ret.originalsize = 12; - // ret.setLabel("COLINFO"); - ret.setOpcode(COLINFO); - ret.setLength((short) ret.originalsize); - byte[] newbytes = new byte[ret.originalsize]; - // colF - byte[] b = ByteTools.shortToLEBytes((short) colF); - newbytes[0] = b[0]; - newbytes[1] = b[1]; - - // colL - b = ByteTools.shortToLEBytes((short) colL); - newbytes[2] = b[0]; - newbytes[3] = b[1]; - - // wide - b = ByteTools.shortToLEBytes((short) wide); - newbytes[4] = b[0]; - newbytes[5] = b[1]; - - // XF - b = ByteTools.shortToLEBytes((short) formatIdx); - newbytes[6] = b[0]; - newbytes[7] = b[1]; - - ret.setData(newbytes); - ret.init(); - return ret; - } - - /** - * Is this colinfo based on a single column format? - */ - public boolean isSingleColColinfo() { - return getColFirst() == getColLast(); - } - - /** - * returns whether a given col - * is referenced by this Colinfo - */ - public boolean inrange(int x) { - return (x <= colLast) && (x >= colFirst); - } - - /** - * Set the width of a column or columns in Excel units - * - * @param double x new column width in Excel Units - */ - private static double fudgefactor = 0.711; // calc needs fudge factor to equal excel values !!! - - public void setColWidthInChars(double x) { - // it's a value that needs to be converted to the appropriate units - x = (int) (x + fudgefactor) * 256.0; - byte[] cl = ByteTools.shortToLEBytes((short) x); - System.arraycopy(cl, 0, this.getData(), 4, 2); - colWidth = (int) ByteTools.readShort(this.getData()[4], this.getData()[5]); - // 20060609 KSC: APPEARS THAT grbit=0 means default column width so must set to either 2 or 6 --- - // there is NO documentation on this! - if (grbit == 0) - setGrbit(2); - } - - /** - * Set the width of a column or columns in internal units - *
                  Internal units are units of (defaultfontwidth/256) - *
                  Excel column width= default font width/256 * 8.43 - *
                  More specifically, column width is in 1/256 of the width of the zero character, - * using default font (first FONT record in the file) - * - * @param int x new column width - */ - public void setColWidth(int x) { - byte[] cl = ByteTools.shortToLEBytes((short) x); - System.arraycopy(cl, 0, this.getData(), 4, 2); - colWidth = ByteTools.readUnsignedShort(this.getData()[4], this.getData()[5]); - // 20060609 KSC: APPEARS THAT grbit=0 means default column width so must set to either 2 or 6 --- - // there is NO documentation on this! - if (grbit == 0) - setGrbit(2); - } - - /** - * Get the width of a column in internal units - *
                  Internal units are units of (defaultfontwidth/256) - *
                  Excel column width= default font width/256 * 8.43 - *
                  More specifically, column width is in 1/256 of the width of the zero character, - * using default font (first FONT record in the file) - */ - public int getColWidth() { - return colWidth; - - } - - /** - * returns Column Width in Chars or Excel-units - * - * @return - */ - public int getColWidthInChars() { - int colwidth = this.getColWidth(); - colwidth = (int) Math.round(((colwidth - fudgefactor) / 256)); - return colwidth; - } - - /** - * Flag indicating if the outlining of the affected column(s) is in the collapsed state. - * - * @param b boolean true if collapsed - */ - public void setCollapsed(boolean b) { - this.collapseIt(b); - // all previous columns are hidden - for (int i = 0; i < this.colFirst; i++) { - Colinfo r = this.getSheet().getColInfo(i); - if (r != null && r.getOutlineLevel() == this.getOutlineLevel()) { - r.setHidden(b); - } - } - } - - /** - * collapse it is called internally, as you need to call the next column from - * the colinfo. - * - * @param b - */ - private void collapseIt(boolean b) { - this.collapsed = b; - updateGrbit(); - } - - /** - * Set whether the column is hidden - * - * @param b - */ - public void setHidden(boolean b) { - this.hidden = b; - updateGrbit(); - this.getWorkBook().getUsersviewbegin().setDisplayOutlines(true); - } - - /** - * Set the Outline level (depth) of the column - * - * @param x - */ - public void setOutlineLevel(int x) { - this.outlineLevel = x; - updateGrbit(); - this.getSheet().getGuts().setColGutterSize(10 + (10 * x)); - this.getSheet().getGuts().setMaxColLevel(x + 1); - } - - /** - * This should be run at init() - * in order to populate grbit values - */ - private void decodeGrbit() { - byte[] grbytes = ByteTools.shortToLEBytes(grbit); - hidden = (grbytes[0] & 0x1) == 0x1; - collapsed = (grbytes[1] & 0x10) == 0x10; - outlineLevel = (grbytes[1] & 0x7); - } - - - public int getGrbit() { - return grbit; - } - - public void setGrbit(int grbit) { - this.grbit = (short) grbit; - updateGrbit(); - } - - /** - * set the grbit to match - * whatever values have been passed in to - * modify the grbit functions. It also updates - * underlying byte record of the XLSRecord. - */ - public void updateGrbit() { - byte[] grbytes = ByteTools.shortToLEBytes(grbit); - // set whether collapsed or not - if (collapsed) { - grbytes[1] = (byte) (0x10 | grbytes[1]); - } else { - grbytes[1] = (byte) (0xEF & grbytes[1]); - } - // set if hidden - if (hidden) { - grbytes[0] = (byte) (0x1 | grbytes[0]); - } else { - grbytes[0] = (byte) (0xFE & grbytes[0]); - } - // set the outline level - grbytes[1] = (byte) (outlineLevel | grbytes[1]); - // reset the grbit and the body rec - grbit = ByteTools.readShort(grbytes[0], grbytes[1]); - byte[] recdata = this.getData(); - recdata[8] = grbytes[0]; - recdata[9] = grbytes[1]; - this.setData(recdata); - } - - /** - * Returns the Outline level (depth) of the column - * - * @return - */ - public int getOutlineLevel() { - return outlineLevel; - } - - public int getIxfe() { - return ixfe; - } - - /** - * Returns whether the column is collapsed - * - * @return - */ - public boolean isCollapsed() { - return collapsed; - } - - /** - * Returns whether the column is hidden - * - * @return - */ - public boolean isHidden() { - return hidden; - } - - /** - * Sets the Ixfe for this record. For some stupid reason this is the *only* xls record - * that has it's ixfe in a different place. Intern time at microsoft I guess. - */ - public void setIxfe(int i) { - this.ixfe = i; - byte[] newxfe = ByteTools.cLongToLEBytes(i); - byte[] b = this.getData(); - - System.arraycopy(newxfe, 0, b, 6, 2); - this.setData(b); - } - - public String toString() { - return "ColInfo: " + ExcelTools.getAlphaVal(colFirst) + "-" + ExcelTools.getAlphaVal(colLast) + " ixfe: " + ixfe + " width: " + colWidth; - } - - public boolean isSingleCol() { - return (this.getColFirst() == this.getColLast()); - - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Colinfo.kt b/src/main/java/io/starter/formats/XLS/Colinfo.kt new file mode 100644 index 0000000..f6bf148 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Colinfo.kt @@ -0,0 +1,386 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.ExcelTools +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * **Colinfo: Column Formatting Information (7Dh)**

                  + * + * + * Colinfo describes the formatting for a column range + * + * + *
                  + * offset  name            size    contents
                  + * ---------------------------------------------------------------
                  + * 4       colFirst        2       First formatted column (0)
                  + * 6       colLast         2       Last formatted column (0)
                  + * 8       colWidth        2       Column width in 1/256 character units
                  + * 10      ixfe            2       Index to XF for columns
                  + * 12      grbit           2       Options
                  + * 14      reserved        1       Must be zero
                  + *
                  + *
                  + * grib options
                  + * offset	Bits	mask	name		contents
                  + * ---------------------------------------------------------------
                  + * 0		0		01h		fHidden		=1 if the column range is hidden
                  + * 7-1		FEh		UNUSED
                  + * 1		2-0		07h		iOutLevel	Outline Level of the column
                  + * 3		08h		Reserved	must be zero
                  + * 4		10h		iCollapsed	=1 if the col is collapsed in outlining
                  + * 7-5		E0h		Reserved	must be zero
                  + *
                  + * etc.
                  + *
                  + * Note: for a discussion of Column widths see:
                  + * http://support.microsoft.com/?kbid=214123
                  +
                  * + */ + +class Colinfo : XLSRecord(), ColumnRange { + private var colFirst: Int = 0 + private var colLast: Int = 0 + private var colWidth: Int = 0 + private var grbit: Short = 0 + private var collapsed: Boolean = false + private var hidden: Boolean = false + private var outlineLevel = 0 + + /** + * Is this colinfo based on a single column format? + */ + val isSingleColColinfo: Boolean + get() = getColFirst() == getColLast() + + /** + * returns Column Width in Chars or Excel-units + * + * @return + */ + val colWidthInChars: Int + get() { + var colwidth = this.getColWidth() + colwidth = Math.round((colwidth - fudgefactor) / 256).toInt() + return colwidth + } + + /** + * Sets the Ixfe for this record. For some stupid reason this is the *only* xls record + * that has it's ixfe in a different place. Intern time at microsoft I guess. + */ + override var ixfe: Int + get() = ixfe + set(i) { + this.ixfe = i + val newxfe = ByteTools.cLongToLEBytes(i) + val b = this.getData() + + System.arraycopy(newxfe, 0, b!!, 6, 2) + this.setData(b) + } + + /** + * Returns whether the column is collapsed + * + * @return + */ + /** + * Flag indicating if the outlining of the affected column(s) is in the collapsed state. + * + * @param b boolean true if collapsed + */ + // all previous columns are hidden + var isCollapsed: Boolean + get() = collapsed + set(b) { + this.collapseIt(b) + for (i in 0 until this.colFirst) { + val r = this.sheet!!.getColInfo(i) + if (r != null && r.outlineLevel == this.getOutlineLevel()) { + r.isHidden = b + } + } + } + + /** + * Returns whether the column is hidden + * + * @return + */ + /** + * Set whether the column is hidden + * + * @param b + */ + var isHidden: Boolean + get() = hidden + set(b) { + this.hidden = b + updateGrbit() + this.workBook!!.usersviewbegin.displayOutlines = true + } + + override val isSingleCol: Boolean + get() = this.getColFirst() == this.getColLast() + + /** + * set last/first cols/rows + */ + fun setColFirst(c: Int) { + val b = ByteTools.shortToLEBytes(c.toShort()) + val dt = this.getData() + System.arraycopy(b, 0, dt!!, 0, 2) + this.colFirst = c + } + + override fun getColFirst(): Int { + return colFirst + } + + /** + * set last/first cols/rows + */ + fun setColLast(c: Int) { + val b = ByteTools.shortToLEBytes(c.toShort()) + val dt = this.getData() + System.arraycopy(b, 0, dt!!, 2, 2) + this.colLast = c + } + + override fun getColLast(): Int { + return colLast + } + + /** + * Shifts the whole colinfo over the amount of the offset + */ + fun moveColInfo(offset: Int) { + this.setColFirst(this.getColFirst() + offset) + this.setColLast(this.getColLast() + offset) + } + + override fun init() { + super.init() + colFirst = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() + colLast = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()).toInt() + colWidth = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()).toInt() + ixfe = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()).toInt() + grbit = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + decodeGrbit() + if (DEBUGLEVEL > 5) + Logger.logInfo("Col: " + ExcelTools.getAlphaVal(colFirst) + "-" + ExcelTools.getAlphaVal(colLast) + " ixfe: " + ixfe + " width: " + colWidth) + } + + /** + * returns whether a given col + * is referenced by this Colinfo + */ + fun inrange(x: Int): Boolean { + return x <= colLast && x >= colFirst + } + + fun setColWidthInChars(x: Double) { + var x = x + // it's a value that needs to be converted to the appropriate units + x = (x + fudgefactor).toInt() * 256.0 + val cl = ByteTools.shortToLEBytes(x.toShort()) + System.arraycopy(cl, 0, this.getData()!!, 4, 2) + colWidth = ByteTools.readShort(this.getData()!![4].toInt(), this.getData()!![5].toInt()).toInt() + // 20060609 KSC: APPEARS THAT grbit=0 means default column width so must set to either 2 or 6 --- + // there is NO documentation on this! + if (grbit.toInt() == 0) + setGrbit(2) + } + + /** + * Set the width of a column or columns in internal units + *

                  Internal units are units of (defaultfontwidth/256) + *

                  Excel column width= default font width/256 * 8.43 + *

                  More specifically, column width is in 1/256 of the width of the zero character, + * using default font (first FONT record in the file) + * + * @param int x new column width + */ + fun setColWidth(x: Int) { + val cl = ByteTools.shortToLEBytes(x.toShort()) + System.arraycopy(cl, 0, this.getData()!!, 4, 2) + colWidth = ByteTools.readUnsignedShort(this.getData()!![4], this.getData()!![5]) + // 20060609 KSC: APPEARS THAT grbit=0 means default column width so must set to either 2 or 6 --- + // there is NO documentation on this! + if (grbit.toInt() == 0) + setGrbit(2) + } + + /** + * Get the width of a column in internal units + *

                  Internal units are units of (defaultfontwidth/256) + *

                  Excel column width= default font width/256 * 8.43 + *

                  More specifically, column width is in 1/256 of the width of the zero character, + * using default font (first FONT record in the file) + */ + fun getColWidth(): Int { + return colWidth + + } + + /** + * collapse it is called internally, as you need to call the next column from + * the colinfo. + * + * @param b + */ + private fun collapseIt(b: Boolean) { + this.collapsed = b + updateGrbit() + } + + /** + * Set the Outline level (depth) of the column + * + * @param x + */ + fun setOutlineLevel(x: Int) { + this.outlineLevel = x + updateGrbit() + this.sheet!!.guts!!.colGutterSize = 10 + 10 * x + this.sheet!!.guts!!.maxColLevel = x + 1 + } + + /** + * This should be run at init() + * in order to populate grbit values + */ + private fun decodeGrbit() { + val grbytes = ByteTools.shortToLEBytes(grbit) + hidden = grbytes[0] and 0x1 == 0x1 + collapsed = grbytes[1] and 0x10 == 0x10 + outlineLevel = grbytes[1] and 0x7 + } + + + fun getGrbit(): Int { + return grbit.toInt() + } + + fun setGrbit(grbit: Int) { + this.grbit = grbit.toShort() + updateGrbit() + } + + /** + * set the grbit to match + * whatever values have been passed in to + * modify the grbit functions. It also updates + * underlying byte record of the XLSRecord. + */ + fun updateGrbit() { + val grbytes = ByteTools.shortToLEBytes(grbit) + // set whether collapsed or not + if (collapsed) { + grbytes[1] = (0x10 or grbytes[1]).toByte() + } else { + grbytes[1] = (0xEF and grbytes[1]).toByte() + } + // set if hidden + if (hidden) { + grbytes[0] = (0x1 or grbytes[0]).toByte() + } else { + grbytes[0] = (0xFE and grbytes[0]).toByte() + } + // set the outline level + grbytes[1] = (outlineLevel or grbytes[1]).toByte() + // reset the grbit and the body rec + grbit = ByteTools.readShort(grbytes[0].toInt(), grbytes[1].toInt()) + val recdata = this.getData() + recdata[8] = grbytes[0] + recdata[9] = grbytes[1] + this.setData(recdata) + } + + /** + * Returns the Outline level (depth) of the column + * + * @return + */ + fun getOutlineLevel(): Int { + return outlineLevel + } + + override fun toString(): String { + return "ColInfo: " + ExcelTools.getAlphaVal(colFirst) + "-" + ExcelTools.getAlphaVal(colLast) + " ixfe: " + ixfe + " width: " + colWidth + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 3048724897018541459L + val DEFAULT_COLWIDTH = 2340 // why 2000???? excel reports 2340 ...? + + + fun getPrototype(colF: Int, colL: Int, wide: Int, formatIdx: Int): Colinfo { + val ret = Colinfo() + + ret.originalsize = 12 + // ret.setLabel("COLINFO"); + ret.opcode = XLSConstants.COLINFO + ret.length = ret.originalsize.toShort() + val newbytes = ByteArray(ret.originalsize) + // colF + var b = ByteTools.shortToLEBytes(colF.toShort()) + newbytes[0] = b[0] + newbytes[1] = b[1] + + // colL + b = ByteTools.shortToLEBytes(colL.toShort()) + newbytes[2] = b[0] + newbytes[3] = b[1] + + // wide + b = ByteTools.shortToLEBytes(wide.toShort()) + newbytes[4] = b[0] + newbytes[5] = b[1] + + // XF + b = ByteTools.shortToLEBytes(formatIdx.toShort()) + newbytes[6] = b[0] + newbytes[7] = b[1] + + ret.setData(newbytes) + ret.init() + return ret + } + + /** + * Set the width of a column or columns in Excel units + * + * @param double x new column width in Excel Units + */ + private val fudgefactor = 0.711 // calc needs fudge factor to equal excel values !!! + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Color.java b/src/main/java/io/starter/formats/XLS/Color.kt similarity index 59% rename from src/main/java/io/starter/formats/XLS/Color.java rename to src/main/java/io/starter/formats/XLS/Color.kt index befaf94..76309aa 100644 --- a/src/main/java/io/starter/formats/XLS/Color.java +++ b/src/main/java/io/starter/formats/XLS/Color.kt @@ -20,40 +20,43 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger -import java.io.Serializable; +import java.io.Serializable /** * An RGB Color Value */ -public final class Color implements Serializable { +class Color(d: ByteArray) : Serializable { + internal var data: ByteArray? = null + internal var myval = -1 - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5181253361814526629L; - byte[] data = null; - int myval = -1; - - public Color(byte[] d) { - this.data = d; - myval = ByteTools.readInt(d[0], d[1], d[2], d[3]); - if (false) Logger.logInfo("New Color: " + Integer.toHexString(myval)); + init { + this.data = d + myval = ByteTools.readInt(d[0], d[1], d[2], d[3]) + if (false) Logger.logInfo("New Color: " + Integer.toHexString(myval)) } // methods from BiffRec - public boolean isDirty(Object obj) { - return false; //immutable for now... + fun isDirty(obj: Any): Boolean { + return false //immutable for now... + } + + fun read(): ByteArray? { + return this.data } - public byte[] read() { - return this.data; + companion object { + + /** + * serialVersionUID + */ + private const val serialVersionUID = 5181253361814526629L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/ColumnNotFoundException.java b/src/main/java/io/starter/formats/XLS/ColumnNotFoundException.kt similarity index 64% rename from src/main/java/io/starter/formats/XLS/ColumnNotFoundException.java rename to src/main/java/io/starter/formats/XLS/ColumnNotFoundException.kt index 08781e2..89724c8 100644 --- a/src/main/java/io/starter/formats/XLS/ColumnNotFoundException.java +++ b/src/main/java/io/starter/formats/XLS/ColumnNotFoundException.kt @@ -20,39 +20,42 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** - * thrown when trying to access a col and the col is not Found. + * **thrown when trying to access a col and the col is not Found.** * * @see Col + * * @see WorkBook */ -public final class ColumnNotFoundException extends java.lang.Exception { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8070857151868706641L; - String colname = ""; +class ColumnNotFoundException(n: String) : java.lang.Exception() { + internal var colname = "" - public ColumnNotFoundException(String n) { - super(); - colname = n; + init { + colname = n } - public String getMessage() { + override fun getMessage(): String { // This method is derived from class java.lang.Throwable // to do: code goes here - return this.toString(); + return this.toString() } - public String toString() { + override fun toString(): String { // This method is derived from class java.lang.Throwable // to do: code goes here - return "Col Not Found in Worksheet. : '" + colname + "'"; + return "Col Not Found in Worksheet. : '$colname'" + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 8070857151868706641L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/ColumnRange.java b/src/main/java/io/starter/formats/XLS/ColumnRange.kt similarity index 50% rename from src/main/java/io/starter/formats/XLS/ColumnRange.java rename to src/main/java/io/starter/formats/XLS/ColumnRange.kt index 9136e74..548fe23 100644 --- a/src/main/java/io/starter/formats/XLS/ColumnRange.java +++ b/src/main/java/io/starter/formats/XLS/ColumnRange.kt @@ -20,56 +20,41 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import java.io.Serializable; +import java.io.Serializable -public interface ColumnRange { +interface ColumnRange { /** * Gets the first column in the range. */ - int getColFirst(); + val colFirst: Int /** * Gets the last column in the range. */ - int getColLast(); + val colLast: Int /** * determines if its a single column - **/ - boolean isSingleCol(); + */ + val isSingleCol: Boolean /** - * A lightweight immutable ColumnRange useful as a reference + * A lightweight immutable `ColumnRange` useful as a reference * element for searching collections. */ - final class Reference - implements ColumnRange, Serializable { - private static final long serialVersionUID = -2240322394559418980L; - private final int first, last; - - public Reference(int first, int last) { - this.first = first; - this.last = last; - } + class Reference(override val colFirst: Int, override val colLast: Int) : ColumnRange, Serializable { - public int getColFirst() { - return first; - } + override val isSingleCol: Boolean + get() = colFirst == colLast - public int getColLast() { - return last; - } - - public boolean isSingleCol() { - return (first == last); + companion object { + private const val serialVersionUID = -2240322394559418980L } } - final class Comparator - implements java.util.Comparator, Serializable { - private static final long serialVersionUID = -4506187924019516336L; + class Comparator : java.util.Comparator, Serializable { /** * This comparator will return equal if one of the column @@ -78,36 +63,37 @@ final class Comparator * within the cell collection. If it turns out to be an issue (colinfos?) we should * separate this out. */ - public int compare(ColumnRange r1, ColumnRange r2) { - boolean single1 = r1.isSingleCol(); - boolean single2 = r2.isSingleCol(); + override fun compare(r1: ColumnRange, r2: ColumnRange): Int { + val single1 = r1.isSingleCol + val single2 = r2.isSingleCol // if we're comparing a single column to a range if ((single1 || single2) && single1 != single2) { // XOR - ColumnRange range = (single1 ? r2 : r1); - ColumnRange single = (single1 ? r1 : r2); + val range = if (single1) r2 else r1 + val single = if (single1) r1 else r2 // and the single column falls within the range - if (range.getColFirst() <= single.getColFirst() - && range.getColLast() >= single.getColLast()) { + if (range.colFirst <= single.colFirst && range.colLast >= single.colLast) { // if it's a range boundary, it chooses what it is - if (single instanceof CellAddressible.RangeBoundary) { - int value = ((CellAddressible.RangeBoundary) - single).comareToRange(); + if (single is CellAddressible.RangeBoundary) { + val value = single.comareToRange() // it needs to be reversed if the range was first - return (single2 ? -value : value); + return if (single2) -value else value } // otherwise it's equal - return 0; + return 0 } } - int first = r1.getColFirst() - r2.getColFirst(); - if (0 != first) return first; - return r2.getColLast() - r1.getColLast(); + val first = r1.colFirst - r2.colFirst + return if (0 != first) first else r2.colLast - r1.colLast + } + + companion object { + private const val serialVersionUID = -4506187924019516336L } } } diff --git a/src/main/java/io/starter/formats/XLS/Condfmt.java b/src/main/java/io/starter/formats/XLS/Condfmt.java deleted file mode 100644 index ca284c6..0000000 --- a/src/main/java/io/starter/formats/XLS/Condfmt.java +++ /dev/null @@ -1,533 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.CfRule; -import io.starter.formats.OOXML.Dxf; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.Iterator; - - -/** - * Condfmt: Conditional Formatting Range Information 0x1B0
                  - *

                  - * This record stores a conditional format, including conditions and formatting info. - *

                  - * And, no it does not just point to an Xf record because that would be easy. - *

                  - *

                  - * OFFSET NAME SIZE CONTENTS - * ----- - * 4 ccf 2 Number of Conditional formats - * 6 grbit 2 Option flags (not a byte?) [1 = Conditionally formatted cells need recalculation or redraw] - * 8 rwFirst 2 First row to conditionally format (0 based) - * 10 rwLast 2 Last row to conditionally format (0 based) - * 12 colFirst 2 First column to conditionally format (0 based) - * 14 colLast 2 Last column to conditionally format (0 based) - * 16 sqrefCount 2 Count of sqrefs * - * 18 rgbSqref var Array of sqref structures - *

                  - *

                  - * Sqref Structures - *

                  - * OFFSET NAME SIZE CONTENTS - * ----- - * 0 rwFirst 2 First row in reference - * 2 rwLast 2 Last row in reference - * 4 colFirst 2 First column in reference - * 6 colLast 2 Last column in reference - * - * @see Cf - */ -public final class Condfmt extends io.starter.formats.XLS.XLSRecord { - - private FormatHandle formatHandle = null; - - private static final long serialVersionUID = -7923448634000437926L; - short grbit = 0; // Option flags (not a byte?) - - private int ccf; - DiscontiguousRefStruct refs = null; - private ArrayList cfRules = new ArrayList(); // 2003-version Cf recs OR OOXML cfRules TODO: eventually will generate Cf records instead - private int cfxe = -1; // a fake ixfe for use by OpenXLS to track formats - boolean isdirty = false; // if any changes to underlying record is made, set to true - - /** - * set dirty flag to rebuild condfmt record - * used when updated the underlying ranges wit - */ - public void setDirty() { - isdirty = true; - } - - /** - * initialize the condfmt record - *

                  - * Please note that the sqref structure is not initialized in this location, but is required for cfmt functionality. - *

                  - * It happens on parse after worksheet is set - */ - public void init() { - super.init(); - rw = 0; - ccf = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); // SHOULD BE # cf's but appears to be 1+ ?? - grbit = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); // SHOULD BE 1 to recalc but has been 3, 5, ...?? - } - - /** - * As the init() call occurs before worksheet is set upon this conditionalformat record, - * we have to initialze the references after init in order for referenceTracker to work correctly - */ - public void initializeReferences() { - data = this.getData(); - int sqrefCount = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - byte[] sqrefdata = new byte[sqrefCount * 8]; - System.arraycopy(data, 14, sqrefdata, 0, sqrefdata.length); - refs = new DiscontiguousRefStruct(sqrefdata, this); - } - - /** - * default constructor - */ - public Condfmt() { - } - - - /** - * @return Returns the formatHandle. - */ - public FormatHandle getFormatHandle() { - return formatHandle; - } - - - /** - * @param formatHandle The formatHandle to set. - */ - public void setFormatHandle(FormatHandle formatHandle) { - this.formatHandle = formatHandle; - } - - - /** - * This is an overall not ideal situation where we want a formatid that we can use in sheetster - * to identify this conditional format - *

                  - * // TODO: Perfect this algorithm!! :) cfxe should be constant for this Condfmt - * // ... if address changes? if sheet # changes - * - * @return Returns the cfxe. - */ - public int getCfxe() { - int[] rc = refs.getRowColBounds(); - cfxe = 50000 + (this.getSheet().getSheetNum() * 10000) + ByteTools.readShort(rc[0], rc[1]); // base cxfe on cell address - return cfxe; - } - - - /** - * @param cfxe The cfxe to set. - */ - public void setCfxe(int c) { - this.cfxe = c; - } - - /** - * returns the rules associated with this record - * - * @return - */ - public ArrayList getRules() { - return cfRules; - } - - /** - * add a new CF rule to this conditional format - */ - public void addRule(Cf c) { - if (cfRules.indexOf(c) == -1) { - cfRules.add(c); - } - c.setCondfmt(this); - } - - /** - * Return all ranges as strings - * - * @return - */ - public String[] getAllRanges() { - return refs.getRefs(); - } - - /** - * Returns the entire range this conditional format refers to in Row[0]Col[0]Row[n]Col[n] format. - * - * @return - */ - public int[] getEncompassingRange() { - return refs.getRowColBounds(); - } - - /** - * update data for streaming - * - * @param loc - */ - private void updateRecord() { - if (!isdirty) return; - // get the size of our output - byte[] outdata = new byte[(refs.getNumRefs() * 8) + 14]; - byte[] tmp = ByteTools.shortToLEBytes((short) (this.getRules().size())); - int offset = 0; - outdata[offset++] = tmp[0]; - outdata[offset++] = tmp[1]; - - tmp = ByteTools.shortToLEBytes(grbit); - outdata[offset++] = tmp[0]; - outdata[offset++] = tmp[1]; - - int[] rowcols = refs.getRowColBounds(); - tmp = ByteTools.shortToLEBytes((short) rowcols[0]); - outdata[offset++] = tmp[0]; - outdata[offset++] = tmp[1]; - tmp = ByteTools.shortToLEBytes((short) rowcols[2]); - outdata[offset++] = tmp[0]; - outdata[offset++] = tmp[1]; - tmp = ByteTools.shortToLEBytes((short) rowcols[1]); - outdata[offset++] = tmp[0]; - outdata[offset++] = tmp[1]; - tmp = ByteTools.shortToLEBytes((short) rowcols[3]); - outdata[offset++] = tmp[0]; - outdata[offset++] = tmp[1]; - - tmp = ByteTools.shortToLEBytes((short) refs.getNumRefs()); - outdata[offset++] = tmp[0]; - outdata[offset++] = tmp[1]; - - byte[] sqrefbytes = refs.getRecordData(); - System.arraycopy(sqrefbytes, 0, outdata, offset, sqrefbytes.length); - this.setData(outdata); - } - - /** - * Add a location to the conditional format record, this - * is a string representation that can be either a cell, ie "A1", or a range - * ie "A1:A12"; - * - * @param location string representing the added range - */ - public void addLocation(String location) { - refs.addRef(location); - isdirty = true; - } - - /** - * get the bounding range of this conditional format - * - * @return - */ - public String getBoundingRange() { - int[] rowcols = refs.getRowColBounds(); - return ExcelTools.formatRangeRowCol(rowcols); - } - - /** - * Set this cf to a new enclosing cell range - * This should only be used for inital creation of a conditional format - * record or when all other internal ranges should be cleared as it removes - * all others - * - * @param range - */ - public void resetRange(String range) { - refs = new DiscontiguousRefStruct(range, this); - isdirty = true; - } - - - private byte[] PROTOTYPE_BYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - - /** - * Create a Condfmt record & populate with prototype bytes - * - * @return - */ - protected static XLSRecord getPrototype() { - Condfmt cf = new Condfmt(); - cf.setOpcode(CONDFMT); - cf.setData(cf.PROTOTYPE_BYTES); - cf.init(); - return cf; - } - - /** - * update the bytes - */ - public void preStream() { - this.updateRecord(); - } - - - /** - * OOXML conditionalFormatting (Conditional Formatting) - * A Conditional Format is a format, such as cell shading or font color, - * that a spreadsheet application can - * automatically apply to cells if a specified condition is true. - * This collection expresses conditional formatting rules - * applied to a particular cell or range. - * - * parent: worksheet - * children: cfRule (1 or more) - * attributes: pivot (flag indicating this cf is assoc with a pivot table), sqref - */ - - - /** - * create one or more Data Validation records based on OOXML input - */ - // TODO: finish pivot option, create Cf recs on each cfRule - public static Condfmt parseOOXML(XmlPullParser xpp, WorkBookHandle wb, Boundsheet bs) { - Condfmt condfmt = null; - ArrayList dxfs = wb.getWorkBook().getDxfs(); - if (dxfs == null) dxfs = new ArrayList(); // shouldn't! - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("conditionalFormatting")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (n.equals("sqref")) { // series of references - condfmt = bs.createCondfmt("", wb); //(Condfmt) Condfmt.getPrototype(); - condfmt.initializeReferences(); - String[] ranges = StringTool.splitString(v, " "); - for (int z = 0; z < ranges.length; z++) { - condfmt.addCellRange(bs.getSheetName() + "!" + ranges[z]); - } - } else if (n.equals("pivot")) { - // ??? - } - } - // create a Cf record based upon cfRule info - } else if (tnm.equals("cfRule")) { // one or more - CfRule cfRule = (CfRule) CfRule.parseOOXML(xpp).cloneElement(); - Cf cf = bs.createCf(condfmt); // creates a new cf rule and links to the current condfmt - cf.setOperator(Cf.translateOperator(cfRule.getOperator())); // set the cf rule operator (greater than, equals ...) - cf.setType(Cf.translateOOXMLType(cfRule.getType())); // set the cf rule type (cell is, exrpression ...) - if (cf.getType() == 3)// containsText - cf.setContainsText(cfRule.getContainsText()); - if (cfRule.getFormula1() != null) - cf.setCondition1(cfRule.getFormula1()); - if (cfRule.getFormula2() != null) - cf.setCondition2(cfRule.getFormula2()); - int dxfId = cfRule.getDxfId(); - if (dxfId > -1) { // it's not required to have a dxf - Dxf dxf = (Dxf) dxfs.get(dxfId); // dxf= differential format, contains the specific styles to define this cf rule - Cf.setStylePropsFromDxf(dxf, cf); -// String dxfStyleString= dfx.getStyleProps(); // returns a string representation of the dxf or differential styles -// Cf.setStylePropsFromString(dxfStyleString,cf); // set the dxf styles to the cf rule - } - // original code that didn't input CfRules into Cf's just stored CfRule objects ... condfmt.cfRules.add((CfRule.parseOOXML(xpp).cloneElement())); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("conditionalFormatting")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("Condfmt.parseOOXML: " + e.toString()); - } - if (condfmt != null) - bs.addConditionalFormat(condfmt); // add this conditional format to the sheet - return condfmt; - } - - /** - * Add a cell range to this conditional format. - * - * @param string - */ - private void addCellRange(String range) { - refs.addRef(range); - isdirty = true; - updateRecord(); - } - - - /** - * returns EXML for the Conditional Format - * - * - * - * - * R12C2:R16C2 - * - * Between - * 2 - * 4 - * - * - * - * - * @return XML string for this record - */ - public String getXML() { - return getXML(false); - } - - /** - * returns XMLSS for the Conditional Format - * - * - * - * - * R12C2:R16C2 - * - * Between - * 2 - * 4 - * - * - * - * - * @return XML string for this record - */ - public String getXMLSS() { - return getXML(true); - } - - /** - * returns EXML (XMLSS) for the Conditional Format - * - * - * - * - * R12C2:R16C2 - * - * Between - * 2 - * 4 - * - * - * - * - * @return - */ - public String getXML(boolean useXMLSSNameSpace) { - - String ns = ""; - if (useXMLSSNameSpace) - ns = "xmlns=\"urn:schemas-microsoft-com:office:excel\""; - - StringBuffer xml = new StringBuffer(""); - // cf's - Iterator its = this.getRules().iterator(); - while (its.hasNext()) { - Cf c = (Cf) its.next(); - xml.append(c.getXML()); - } - xml.append(""); - return xml.toString(); - } - - /** - * generate the proper OOXML to define this set of Conditional Formatting - * - * @return - */ - public String getOOXML(WorkBookHandle bk, int[] priority) { - this.updateRecord(); - StringBuffer ooxml = new StringBuffer(); - ooxml.append(" 0) ooxml.append(" "); - ooxml.append(refStrs[i]); - } - ooxml.append("\""); - } - ooxml.append(">"); - // cf's - // NOTE: cf.getDxfId/setDxfId links this conditional formatting rule with the proper incremental style - // NOTE: cfRules must have a valid dxfId or the output file will open with errors - // NOTE: for now, dxfs can only be saved from the original styles.xml; - ArrayList dxfs = this.getWorkBook().getDxfs(); - if (dxfs == null) { - dxfs = new ArrayList(); - this.getWorkBook().setDxfs(dxfs); - } - if (cfRules != null) { - for (int i = 0; i < cfRules.size(); i++) { - ooxml.append(((Cf) cfRules.get(i)).getOOXML(bk, priority[0]++, dxfs)); - } - } - ooxml.append(""); - return ooxml.toString(); - } - - /** - * Checks if the conditional format contains the row/col passed in - * - * @param rowColFromString - * @return - */ - public boolean contains(int[] rowColFromString) { - return refs.containsReference(rowColFromString); - } - - /** - * clear out object referencse - */ - public void close() { - super.close(); - refs = null; - if (cfRules != null) { - for (int i = 0; i < cfRules.size(); i++) { - Cf cf = (Cf) cfRules.get(i); - cf.close(); - cf = null; - - } - } - } - -} diff --git a/src/main/java/io/starter/formats/XLS/Condfmt.kt b/src/main/java/io/starter/formats/XLS/Condfmt.kt new file mode 100644 index 0000000..7709192 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Condfmt.kt @@ -0,0 +1,522 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.ExcelTools +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.CfRule +import io.starter.formats.OOXML.Dxf +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList + + +/** + * **Condfmt: Conditional Formatting Range Information 0x1B0**

                  + * + * + * This record stores a conditional format, including conditions and formatting info. + * + * + * And, no it does not just point to an Xf record because that would be easy. + * + * + * + * + * OFFSET NAME SIZE CONTENTS + * ----- + * 4 ccf 2 Number of Conditional formats + * 6 grbit 2 Option flags (not a byte?) [1 = Conditionally formatted cells need recalculation or redraw] + * 8 rwFirst 2 First row to conditionally format (0 based) + * 10 rwLast 2 Last row to conditionally format (0 based) + * 12 colFirst 2 First column to conditionally format (0 based) + * 14 colLast 2 Last column to conditionally format (0 based) + * 16 sqrefCount 2 Count of sqrefs * + * 18 rgbSqref var Array of sqref structures + * + * + * + * + * Sqref Structures + * + * + * OFFSET NAME SIZE CONTENTS + * ----- + * 0 rwFirst 2 First row in reference + * 2 rwLast 2 Last row in reference + * 4 colFirst 2 First column in reference + * 6 colLast 2 Last column in reference + * + * @see Cf + */ +/** + * default constructor + */ +class Condfmt : io.starter.formats.XLS.XLSRecord() { + + /** + * @return Returns the formatHandle. + */ + /** + * @param formatHandle The formatHandle to set. + */ + var formatHandle: FormatHandle? = null + internal var grbit: Short = 0 // Option flags (not a byte?) + + private var ccf: Int = 0 + internal var refs: DiscontiguousRefStruct? = null + /** + * returns the rules associated with this record + * + * @return + */ + val rules = ArrayList() // 2003-version Cf recs OR OOXML cfRules TODO: eventually will generate Cf records instead + /** + * This is an overall not ideal situation where we want a formatid that we can use in sheetster + * to identify this conditional format + * + * + * // TODO: Perfect this algorithm!! :) cfxe should be constant for this Condfmt + * // ... if address changes? if sheet # changes + * + * @return Returns the cfxe. + */ + /** + * @param cfxe The cfxe to set. + */ + // base cxfe on cell address + var cfxe = -1 + get() { + val rc = refs!!.rowColBounds + this.cfxe = 50000 + this.sheet!!.sheetNum * 10000 + ByteTools.readShort(rc[0], rc[1]).toInt() + return field + } // a fake ixfe for use by OpenXLS to track formats + internal var isdirty = false // if any changes to underlying record is made, set to true + + /** + * Return all ranges as strings + * + * @return + */ + val allRanges: Array + get() = refs!!.refs + + /** + * Returns the entire range this conditional format refers to in Row[0]Col[0]Row[n]Col[n] format. + * + * @return + */ + val encompassingRange: IntArray + get() = refs!!.rowColBounds + + /** + * get the bounding range of this conditional format + * + * @return + */ + val boundingRange: String + get() { + val rowcols = refs!!.rowColBounds + return ExcelTools.formatRangeRowCol(rowcols) + } + + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + + + /** + * returns EXML for the Conditional Format + * + * + * + * + * R12C2:R16C2 + * + * Between + * 2 + * 4 + * + * + * + * + * @return XML string for this record + */ + val xml: String + get() = getXML(false) + + /** + * returns XMLSS for the Conditional Format + * + * + * + * + * R12C2:R16C2 + * + * Between + * 2 + * 4 + * + * + * + * + * @return XML string for this record + */ + val xmlss: String + get() = getXML(true) + + /** + * set dirty flag to rebuild condfmt record + * used when updated the underlying ranges wit + */ + fun setDirty() { + isdirty = true + } + + /** + * initialize the condfmt record + * + * + * Please note that the sqref structure is not initialized in this location, but is required for cfmt functionality. + * + * + * It happens on parse after worksheet is set + */ + override fun init() { + super.init() + rw = 0 + ccf = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() // SHOULD BE # cf's but appears to be 1+ ?? + grbit = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) // SHOULD BE 1 to recalc but has been 3, 5, ...?? + } + + /** + * As the init() call occurs before worksheet is set upon this conditionalformat record, + * we have to initialze the references after init in order for referenceTracker to work correctly + */ + fun initializeReferences() { + data = this.getData() + val sqrefCount = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()).toInt() + val sqrefdata = ByteArray(sqrefCount * 8) + System.arraycopy(data!!, 14, sqrefdata, 0, sqrefdata.size) + refs = DiscontiguousRefStruct(sqrefdata, this) + } + + /** + * add a new CF rule to this conditional format + */ + fun addRule(c: Cf) { + if (rules.indexOf(c) == -1) { + rules.add(c) + } + c.condfmt = this + } + + /** + * update data for streaming + * + * @param loc + */ + private fun updateRecord() { + if (!isdirty) return + // get the size of our output + val outdata = ByteArray(refs!!.numRefs * 8 + 14) + var tmp = ByteTools.shortToLEBytes(this.rules.size.toShort()) + var offset = 0 + outdata[offset++] = tmp[0] + outdata[offset++] = tmp[1] + + tmp = ByteTools.shortToLEBytes(grbit) + outdata[offset++] = tmp[0] + outdata[offset++] = tmp[1] + + val rowcols = refs!!.rowColBounds + tmp = ByteTools.shortToLEBytes(rowcols[0].toShort()) + outdata[offset++] = tmp[0] + outdata[offset++] = tmp[1] + tmp = ByteTools.shortToLEBytes(rowcols[2].toShort()) + outdata[offset++] = tmp[0] + outdata[offset++] = tmp[1] + tmp = ByteTools.shortToLEBytes(rowcols[1].toShort()) + outdata[offset++] = tmp[0] + outdata[offset++] = tmp[1] + tmp = ByteTools.shortToLEBytes(rowcols[3].toShort()) + outdata[offset++] = tmp[0] + outdata[offset++] = tmp[1] + + tmp = ByteTools.shortToLEBytes(refs!!.numRefs.toShort()) + outdata[offset++] = tmp[0] + outdata[offset++] = tmp[1] + + val sqrefbytes = refs!!.recordData + System.arraycopy(sqrefbytes, 0, outdata, offset, sqrefbytes.size) + this.setData(outdata) + } + + /** + * Add a location to the conditional format record, this + * is a string representation that can be either a cell, ie "A1", or a range + * ie "A1:A12"; + * + * @param location string representing the added range + */ + fun addLocation(location: String) { + refs!!.addRef(location) + isdirty = true + } + + /** + * Set this cf to a new enclosing cell range + * This should only be used for inital creation of a conditional format + * record or when all other internal ranges should be cleared as it removes + * all others + * + * @param range + */ + fun resetRange(range: String) { + refs = DiscontiguousRefStruct(range, this) + isdirty = true + } + + /** + * update the bytes + */ + override fun preStream() { + this.updateRecord() + } + + /** + * Add a cell range to this conditional format. + * + * @param string + */ + private fun addCellRange(range: String) { + refs!!.addRef(range) + isdirty = true + updateRecord() + } + + /** + * returns EXML (XMLSS) for the Conditional Format + * + * + * + * + * R12C2:R16C2 + * + * Between + * 2 + * 4 + * + * + * + * + * @return + */ + fun getXML(useXMLSSNameSpace: Boolean): String { + + var ns = "" + if (useXMLSSNameSpace) + ns = "xmlns=\"urn:schemas-microsoft-com:office:excel\"" + + val xml = StringBuffer("") + // cf's + val its = this.rules.iterator() + while (its.hasNext()) { + val c = its.next() as Cf + xml.append(c.xml) + } + xml.append("") + return xml.toString() + } + + /** + * generate the proper OOXML to define this set of Conditional Formatting + * + * @return + */ + fun getOOXML(bk: WorkBookHandle, priority: IntArray): String { + this.updateRecord() + val ooxml = StringBuffer() + ooxml.append(" 0) ooxml.append(" ") + ooxml.append(refStrs[i]) + } + ooxml.append("\"") + } + ooxml.append(">") + // cf's + // NOTE: cf.getDxfId/setDxfId links this conditional formatting rule with the proper incremental style + // NOTE: cfRules must have a valid dxfId or the output file will open with errors + // NOTE: for now, dxfs can only be saved from the original styles.xml; + var dxfs: ArrayList<*>? = this.workBook!!.dxfs + if (dxfs == null) { + dxfs = ArrayList() + this.workBook!!.dxfs = dxfs + } + if (rules != null) { + for (i in rules.indices) { + ooxml.append((rules.get(i) as Cf).getOOXML(bk, priority[0]++, dxfs)) + } + } + ooxml.append("") + return ooxml.toString() + } + + /** + * Checks if the conditional format contains the row/col passed in + * + * @param rowColFromString + * @return + */ + operator fun contains(rowColFromString: IntArray): Boolean { + return refs!!.containsReference(rowColFromString) + } + + /** + * clear out object referencse + */ + override fun close() { + super.close() + refs = null + if (rules != null) { + for (i in rules.indices) { + var cf: Cf? = rules.get(i) + cf!!.close() + cf = null + + } + } + } + + companion object { + + private val serialVersionUID = -7923448634000437926L + + /** + * Create a Condfmt record & populate with prototype bytes + * + * @return + */ + val prototype: XLSRecord? + get() { + val cf = Condfmt() + cf.opcode = XLSConstants.CONDFMT + cf.setData(cf.PROTOTYPE_BYTES) + cf.init() + return cf + } + + + /** + * OOXML conditionalFormatting (Conditional Formatting) + * A Conditional Format is a format, such as cell shading or font color, + * that a spreadsheet application can + * automatically apply to cells if a specified condition is true. + * This collection expresses conditional formatting rules + * applied to a particular cell or range. + * + * parent: worksheet + * children: cfRule (1 or more) + * attributes: pivot (flag indicating this cf is assoc with a pivot table), sqref + */ + + + /** + * create one or more Data Validation records based on OOXML input + */ + // TODO: finish pivot option, create Cf recs on each cfRule + fun parseOOXML(xpp: XmlPullParser, wb: WorkBookHandle, bs: Boundsheet): Condfmt? { + var condfmt: Condfmt? = null + var dxfs: ArrayList<*>? = wb.workBook!!.dxfs + if (dxfs == null) dxfs = ArrayList() // shouldn't! + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "conditionalFormatting") { // get attributes + for (i in 0 until xpp.attributeCount) { + val n = xpp.getAttributeName(i) + val v = xpp.getAttributeValue(i) + if (n == "sqref") { // series of references + condfmt = bs.createCondfmt("", wb) //(Condfmt) Condfmt.getPrototype(); + condfmt!!.initializeReferences() + val ranges = StringTool.splitString(v, " ") + for (z in ranges.indices) { + condfmt.addCellRange(bs.sheetName + "!" + ranges[z]) + } + } else if (n == "pivot") { + // ??? + } + } + // create a Cf record based upon cfRule info + } else if (tnm == "cfRule") { // one or more + val cfRule = CfRule.parseOOXML(xpp).cloneElement() as CfRule + val cf = bs.createCf(condfmt!!) // creates a new cf rule and links to the current condfmt + cf.setOperator(Cf.translateOperator(cfRule.operator)) // set the cf rule operator (greater than, equals ...) + cf.setType(Cf.translateOOXMLType(cfRule.type)) // set the cf rule type (cell is, exrpression ...) + if (cf.type.toInt() == 3) + // containsText + cf.setContainsText(cfRule.containsText) + if (cfRule.formula1 != null) + cf.setCondition1(cfRule.formula1) + if (cfRule.formula2 != null) + cf.setCondition2(cfRule.formula2!!) + val dxfId = cfRule.dxfId + if (dxfId > -1) { // it's not required to have a dxf + val dxf = dxfs!![dxfId] as Dxf // dxf= differential format, contains the specific styles to define this cf rule + Cf.setStylePropsFromDxf(dxf, cf) + // String dxfStyleString= dfx.getStyleProps(); // returns a string representation of the dxf or differential styles + // Cf.setStylePropsFromString(dxfStyleString,cf); // set the dxf styles to the cf rule + } + // original code that didn't input CfRules into Cf's just stored CfRule objects ... condfmt.cfRules.add((CfRule.parseOOXML(xpp).cloneElement())); + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "conditionalFormatting") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("Condfmt.parseOOXML: $e") + } + + if (condfmt != null) + bs.addConditionalFormat(condfmt) // add this conditional format to the sheet + return condfmt + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/Condfmt12.java b/src/main/java/io/starter/formats/XLS/Condfmt12.kt similarity index 81% rename from src/main/java/io/starter/formats/XLS/Condfmt12.java rename to src/main/java/io/starter/formats/XLS/Condfmt12.kt index abcd7d1..ffab89b 100644 --- a/src/main/java/io/starter/formats/XLS/Condfmt12.java +++ b/src/main/java/io/starter/formats/XLS/Condfmt12.kt @@ -20,25 +20,29 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.Logger; +import io.starter.toolkit.Logger /** * Condfmt 12 is a FRT (Future record type) record that handles extended conditional formatting information that * was not available before excel 2007. It is currently unsupported in OpenXLS, but the record looks much the same * to a standard conditional format record. - *

                  + * + * * I'm guessing when implemented we will either want a member Condfmt record or even just extend condfmt with this. */ -public class Condfmt12 extends XLSRecord { +class Condfmt12 : XLSRecord() { + + override fun init() { + Logger.logWarn("Future conditional record type (condfmt12) found, This conditional format cannot be modified by OpenXLS") + } - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3251845433389628844L; + companion object { - public void init() { - Logger.logWarn("Future conditional record type (condfmt12) found, This conditional format cannot be modified by OpenXLS"); + /** + * serialVersionUID + */ + private val serialVersionUID = 3251845433389628844L } } diff --git a/src/main/java/io/starter/formats/XLS/Continue.java b/src/main/java/io/starter/formats/XLS/Continue.kt similarity index 52% rename from src/main/java/io/starter/formats/XLS/Continue.java rename to src/main/java/io/starter/formats/XLS/Continue.kt index 61d8e59..47ce2e1 100644 --- a/src/main/java/io/starter/formats/XLS/Continue.java +++ b/src/main/java/io/starter/formats/XLS/Continue.kt @@ -20,16 +20,19 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.Logger; +import io.starter.toolkit.Logger /** - * Continue: Continues Long Records (3Ch)
                  + * **Continue: Continues Long Records (3Ch)**

                  * Records longer than 8228 must be split into several records. - *

                  - * These records contain only data.

                  + *
                  + *
                  + * These records contain only data.
                  + *
                  + *
                    *
                    * offset  name        size    contents
                    * ---
                  @@ -37,9 +40,10 @@
                    * segment is compressed or uncompressed data
                    * 5                   var     Continuation of record data.
                    *
                  - * 

                  +
                  * * * @see Sst + * * @see Txo */ /* more info: @@ -54,11 +58,7 @@ * the grbit flag will be 01h, and all characters in the string segment will be two-byte, * uncompressed Unicode. */ -public final class Continue extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6303887828816619839L; +class Continue : io.starter.formats.XLS.XLSRecord() { /** * The SST record is the Shared String Table record, * and will contain the strings of text from cells of the worksheet. @@ -71,21 +71,28 @@ public final class Continue extends io.starter.formats.XLS.XLSRecord { * that segment may be stored as either compressed or uncompressed * Unicode. Consequently, the first byte of the data will contain 00h or 01h. * This is a one-byte field called a grbit field. It is not part of the string segment. - *

                  + * + * * The grbit flag value 00h says no bytes of the data need * Unicode high-order byte data, so all are stored as compressed Unicode * (all the high-order bytes of the Unicode representation of the data * characters have been stripped. They all contained 00h, so Excel manages * the logic of restoring that high-order information when it loads the record.) - *

                  + * + * * Where any character in the data segment requires Unicode * high-order byte information, the grbit flag will be 01h, * and all characters in the string segment will be two-byte, * uncompressed Unicode. */ - Boolean hasgrbit = null; - MSODrawing maskedMso = null; // Continue's can mask Mso's-this links to Masked Mso rec created from this data; see ContinueHandler.addRec; + internal var hasgrbit: Boolean? = null + internal var maskedMso: MSODrawing? = null // Continue's can mask Mso's-this links to Masked Mso rec created from this data; see ContinueHandler.addRec; + + var grbit: Byte = 0x0 + + internal val isBigRecContinue: Boolean + get() = if (this.predecessor == null) true else this.predecessor!!.length >= XLSConstants.MAXRECLEN /** * Set that this continue has a grbit. I find this method very odd in that @@ -95,146 +102,131 @@ public final class Continue extends io.starter.formats.XLS.XLSRecord { * * @param b */ - public void setHasGrbit(boolean b) { - if (b && this.getEncryptedByteReader() == this.getByteReader()) - grbit = this.getByteAt(0); - hasgrbit = Boolean.valueOf(b); - } + internal// ie Bit is set + var hasGrbit: Boolean + get() { + if (this.hasgrbit != null) return hasgrbit!!.booleanValue() - public byte grbit = 0x0; + if (DEBUGLEVEL > 1) { + Logger.logInfo("Grbit pos0: " + (getGrbit() and 0)) + Logger.logInfo("Grbit pos2: " + (getGrbit() and 4)) + Logger.logInfo("Grbit pos3: " + (getGrbit() and 8)) + } - boolean isBigRecContinue() { - if (this.predecessor == null) return true; - return this.predecessor.getLength() >= MAXRECLEN; - } + return if (getGrbit() and 8 != 0) true else getGrbit() < 0x2 && getGrbit() >= 0x0 - boolean getHasGrbit() { - if (this.hasgrbit != null) return hasgrbit.booleanValue(); - - if (DEBUGLEVEL > 1) { - Logger.logInfo("Grbit pos0: " + (getGrbit() & 0)); - Logger.logInfo("Grbit pos2: " + (getGrbit() & 4)); - Logger.logInfo("Grbit pos3: " + (getGrbit() & 8)); + } + set(b) { + if (b && this.encryptedByteReader === this.byteReader) + grbit = this.getByteAt(0) + hasgrbit = java.lang.Boolean.valueOf(b) } - if ((getGrbit() & 8) != 0) - return true; // ie Bit is set + private var mygr: Byte = 0 - return ((getGrbit() < 0x2) && (getGrbit() >= 0x0)); - } + internal var deldata: ByteArray? = null - private byte mygr; + /** + * @return + */ + var predecessor: BiffRec? = null + internal set - byte getGrbit() { - if (this.data != null) return this.data[0]; - return super.getByteAt(0); - } + var grbitoff = 0 + /** + * @return + */ + /** + * @param i + */ + var continueOffset = -1 - byte[] deldata = null; - public void init() { - super.init(); - streaming = false; - mygr = super.getByteAt(0); - if (DEBUGLEVEL > 2) Logger.logInfo(" init() GRBIT: " + mygr); - } + /** + * Override this to not return the grbit as part of Continue data + */ + override var data: ByteArray? + get() { + if (hasGrbit && !streaming) { + super.getData() + return this.getBytesAt(0, this.length - 4) + } + streaming = false + return super.getData() + } + set - public BiffRec predecessor = null; + internal var streaming = false - void setPredecessor(BiffRec pr) { - this.predecessor = pr; + internal fun getGrbit(): Byte { + return if (this.data != null) this.data!![0] else super.getByteAt(0) } - public int grbitoff = 0; - private int continue_offset = -1; + override fun init() { + super.init() + streaming = false + mygr = super.getByteAt(0) + if (DEBUGLEVEL > 2) Logger.logInfo(" init() GRBIT: $mygr") + } - public byte getByteAt(int off) { - int s = off; - int rpos = s + this.grbitoff; + override fun getByteAt(off: Int): Byte { + var rpos = off + this.grbitoff if (rpos < 0) { - if (DEBUGLEVEL > 5) Logger.logWarn("Continue pointer is: " + rpos); - rpos = 0; + if (DEBUGLEVEL > 5) Logger.logWarn("Continue pointer is: $rpos") + rpos = 0 } - rpos -= continue_offset; - return super.getByteAt(rpos); - - } + rpos -= continueOffset + return super.getByteAt(rpos) - - /** - * Override this to not return the grbit as part of Continue data - */ - public byte[] getData() { - if (getHasGrbit() && !streaming) { - super.getData(); - return this.getBytesAt(0, this.getLength() - 4); - } - streaming = false; - return super.getData(); } - boolean streaming = false; - /** * set the streaming flag so we get the grbit in output */ - public void preStream() { - streaming = true; - } - - /** - * @return - */ - public int getContinueOffset() { - return continue_offset; + override fun preStream() { + streaming = true } - /** - * @param i - */ - public void setContinueOffset(int i) { - continue_offset = i; - } - - /** - * @return - */ - public BiffRec getPredecessor() { - return predecessor; - } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -6303887828816619839L - /** - * creates and returns a basic Continues record which defines a text string - * - * @param txt - String txt to represent - * @return new Continue record - */ - public static Continue getTextContinues(String txt) { - Continue c = new Continue(); - c.setOpcode(CONTINUE); - byte[] data = new byte[txt.getBytes().length + 1]; - System.arraycopy(txt.getBytes(), 0, data, 1, data.length - 1); - c.setData(data); - c.init(); - return c; - } + /** + * creates and returns a basic Continues record which defines a text string + * + * @param txt - String txt to represent + * @return new Continue record + */ + fun getTextContinues(txt: String): Continue { + val c = Continue() + c.opcode = XLSConstants.CONTINUE + val data = ByteArray(txt.toByteArray().size + 1) + System.arraycopy(txt.toByteArray(), 0, data, 1, data.size - 1) + c.setData(data) + c.init() + return c + } - /** - * create and return the absolute minimum Continue record defining a formatting run - * - * @return new Continue record - */ - public static Continue getBasicFormattingRunContinues() { /** - * 0 2 First formatted character (zero-based) - 2 2 Index to FONT record (➜5.45) + * create and return the absolute minimum Continue record defining a formatting run + * + * @return new Continue record */ - Continue c = new Continue(); - c.setOpcode(CONTINUE); - c.setData(new byte[4]); // meaning: from character 0 to the end, use default font 0 - c.init(); - return c; + /** + * 0 2 First formatted character (zero-based) + * 2 2 Index to FONT record (➜5.45) + */// meaning: from character 0 to the end, use default font 0 + val basicFormattingRunContinues: Continue + get() { + val c = Continue() + c.opcode = XLSConstants.CONTINUE + c.setData(ByteArray(4)) + c.init() + return c + } } } diff --git a/src/main/java/io/starter/formats/XLS/ContinueHandler.java b/src/main/java/io/starter/formats/XLS/ContinueHandler.java deleted file mode 100644 index ca12bd4..0000000 --- a/src/main/java/io/starter/formats/XLS/ContinueHandler.java +++ /dev/null @@ -1,757 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.Logger; - -import java.io.OutputStream; -import java.io.Serializable; -import java.util.Iterator; -import java.util.List; - - -/** - * this class takes care of the tasks related to - * working with Continue records. - *

                  - * when a Continue record is created, it needs to be - * associated with its related XLSRecord -- the record whose - * data it contains. - */ -public class ContinueHandler implements Serializable, XLSConstants { - /** - * - */ - private static final long serialVersionUID = 164009339243774537L; - private static int DEBUGLEVEL = 0; - private static boolean processContinues = true; // debug setting - private BiffRec continued; - private boolean handleTxo = false; - private boolean handleObj = false; - private Txo lastTxo; - private Obj lastObj; - private Continue lastCont; - private WorkBook book; - - private BiffRec splitPrevRec; - private BiffRec splitContRec; - - public ContinueHandler(WorkBook b) { - this.book = b; - } - - /** - * add an XLSRecord to this handler - * check if it needs a Continue, if - * so, put in our continued_recs. - */ - public void addRec(BiffRec rec, int datalen) { - // check if this record has Continue records - // if so, delay initialization until all Continues are read. - // In the body.getData() method we then need to check - // if the record has Continues and if so, then read/write init - // data from them. - - short opcode = rec.getOpcode(); - short nextOpcode = 0x0; - - if (opcode != EOF) { - nextOpcode = book.getFactory().lookAhead(rec); - } - - if (nextOpcode == CONTINUE) { - if (DEBUGLEVEL > 11) Logger.logInfo("Next OPCODE IS CONTINUE: " + Integer.toHexString(nextOpcode)); - } - if (nextOpcode == CONTINUE && opcode != CONTINUE) { // the continued rec - if (continued != null) { - continued.init(); - continued = null; - } - this.continued = rec; - this.splitPrevRec = this.continued; - // if the rec is a Txo, we need to process special - if (continued instanceof Txo) { - this.handleTxo = true; - lastTxo = (Txo) continued; - lastObj = null; - this.handleObj = false; // "" - } else if (continued instanceof Obj) { - this.handleObj = true; - lastObj = (Obj) continued; - // obj records need to be init'd before setsheet - lastObj.init(); - lastTxo = null; - this.handleTxo = false; // "" - } else { - this.handleTxo = false; - this.handleObj = false; - } - lastCont = null; - } else if (opcode == CONTINUE) { // add to the continued rec - splitContRec = rec; - rec.init(); - if (continued == null && lastCont == null) { - //This is a use case where a chart is in the middle of an Obj, PLS, or Txo record. A continue - // record appears at the end of the last chart EOF, and needs to remain inorder to not cause corruption - if (splitPrevRec != null) { - ((Continue) rec).setPredecessor(splitContRec); - } - if (DEBUGLEVEL > 0) - Logger.logWarn("Warning: Out of spec split txo continue record found, reconstructing." + splitPrevRec.toString()); - - } else { - if (lastCont != null) ((Continue) rec).setPredecessor(lastCont); - else ((Continue) rec).setPredecessor(continued); - if ((continued.getOpcode() == SST) || (continued.getOpcode() == STRINGREC)) { - if (DEBUGLEVEL > 2) Logger.logInfo("Sst Continue. grbit:" + ((Continue) rec).getGrbit()); - } else { - ((Continue) rec).setHasGrbit(false); // if it can't have one, don't - } - } - - // last data rec was a Txo -- add next 2 Continues - if (handleTxo) { - // txo's have either 2 continues following (text, formatting runs), no continues (an "empty" txo); may also contain continues masking mso's - if (lastTxo.text == null) { - if (!isMaskedMSODrawingRec(rec.getData())) // in almost all cases, the next continue is a Text continue - lastTxo.text = (Continue) rec; - else { // it is possible that an empty TXO (one that does NOT contain Text) is followed by a Continue rec which is masking an MSODrawing - continued = createMSODrawingFromContinue(rec); // create a new MSODrawing rec from the Continue rec's data - ((Continue) rec).maskedMso = (MSODrawing) continued; // set maskedMso in Continue to identify - } - } else if (lastTxo.formattingruns == null) - lastTxo.formattingruns = (Continue) rec; - else { // a third continues: will be a masked mso or possibly a "big rec" continues - try { - if (isMaskedMSODrawingRec(rec.getData())) { - continued = createMSODrawingFromContinue(rec); // create a new MSODrawing rec from the Continue rec's data - ((Continue) rec).maskedMso = (MSODrawing) continued; // set maskedMso in Continue to identify - } else if (continued != null) { // then it's a "big rec" continue - continued.addContinue((Continue) rec); - ((XLSRecord) continued).mergeContinues(); // must merge continues "by hand" because data member var is set already - continued.removeContinues(); - } - } catch (Exception e) { - if (DEBUGLEVEL > 0) - Logger.logErr("ContinueHandler.txo parsing- encountered unknown Continue record"); - } - lastCont = (Continue) rec; - } - } else if (handleObj) { - try { // When a Continue record follows an Obj record, it is either masking a Msodrawing record - or it's a "big rec" continues - if (isMaskedMSODrawingRec(rec.getData())) { - continued = createMSODrawingFromContinue(rec); // create a new MSODrawing rec from the Continue rec's data - ((Continue) rec).maskedMso = (MSODrawing) continued; // set maskedMso in Continue to identify - } else if (continued != null) { // then it's a "big rec" continue - continued.addContinue((Continue) rec); - ((XLSRecord) continued).mergeContinues(); // must merge continues "by hand" because data member var is set already - continued.removeContinues(); - } - lastCont = (Continue) rec; - } catch (Exception e) { - if (DEBUGLEVEL > 0) - Logger.logErr("ContinueHandler.Obj parsing- encountered unknown Continue record"); - } - } else { - // null continued? bizarre case testfiles/assess.xls -jm 10/01/2004 - if (continued != null) - continued.addContinue((Continue) rec); - - lastCont = (Continue) rec; - } - } else { - if (continued != null) { - continued.init(); - /* If Formula Attached String was a continue, Formula cachedValue WAS NOT init-ed. Do here.*/ - if (continued.getOpcode() == STRINGREC) - this.book.lastFormula.setCachedValue(((StringRec) continued).getStringVal()); - continued = null; - } - - // associate boundsheet for init - if (book.getLastbound() != null) { - if (/*opcode == NAME ||*/ - opcode == FORMULA) - rec.setSheet(book.getLastbound()); - } - // init names without init'ing expression -- must init expression after loading sheet recs ... - if (opcode != XLSConstants.NAME) - rec.init(); - else - ((Name) rec).init(false); // don't init expression here; do after loading sheet recs ... - lastCont = null; - } - } - - /** - * returns true if this id is one of an MSODrawing - *
                  occurs when a Continue record is masking an MSO - * (i.e. contains the record structure of an MSO in it's data, - * with an opcode of Continue) - * - * @param data - record data - * @return true if data is in form of an MSODrawing record - */ - private boolean isMaskedMSODrawingRec(byte[] data) { - if (data.length > 3) { - int id = (((0xFF & data[3]) << 8) | (0xFF & data[2])); - return ((id == MSODrawingConstants.MSOFBTSPCONTAINER || - id == MSODrawingConstants.MSOFBTSOLVERCONTAINER || - id == MSODrawingConstants.MSOFBTSPGRCONTAINER || - id == MSODrawingConstants.MSOFBTCLIENTTEXTBOX)); - } - return false; - } - - /** - * create an MSODrwawing Record from a Continue record - * which is masking an MSODrawing (i.e. contains the record - * structure of an MSO in it's data, with an opcode of Continue) - * - * @param rec - * @return - */ - private MSODrawing createMSODrawingFromContinue(BiffRec rec) { - // create an mso and add to drawing recs ... - MSODrawing mso = new MSODrawing(); - mso.setOpcode(MSODRAWING); - mso.setWorkBook(rec.getWorkBook()); - mso.setData(rec.getData()); - mso.setLength(rec.getData().length); - mso.setDebugLevel(DEBUGLEVEL); - mso.setStreamer(book.getStreamer()); - return mso; - } - - /** - * check if the record needs to have its data - * spanned across Continue records. - */ - public static boolean createContinues(BiffRec rec, OutputStream out, ByteStreamer streamer) { - int datalen = rec.getLength(); - int opc = rec.getOpcode(); - // Logger.logInfo("ContinueHandler creating output continues for: " + rec.toString() + " datalen: " + datalen); - // if greater than 8023, we need Continues - if (opc == CONTINUE) { - if (((Continue) rec).isBigRecContinue()) // skip ensuing Continues as should be written by main record - return true; - // handle masked mso's which have continues separately - else if (((Continue) rec).maskedMso != null && (((((Continue) rec).maskedMso.getLength() - 4) > MAXRECLEN))) { - ((Continue) rec).maskedMso.setOpcode(CONTINUE); // so can add the correct record to output - createBigRecContinues(((Continue) rec).maskedMso, out, streamer); - ((Continue) rec).maskedMso.setOpcode(MSODRAWING); // reset so can continue working with this record set - return true; // processed, return true - } - } else if ((opc == SST)) { - createSstContinues((Sst) rec, out, streamer); - return true; - } else if (opc == TXO) { - createTxoContinues((Txo) rec, out, streamer); - return true; - } else if (opc == MSODRAWINGGROUP) { - createMSODGContinues(rec, out, streamer); - return true; - } else if (((datalen - 4) > MAXRECLEN)) { - createBigRecContinues(rec, out, streamer); - return true; - } - return false; - } - - - /** - * check if the record needs to have its data - * spanned across Continue records. - */ - static int createContinues(BiffRec rec, int insertLoc) { - int datalen = rec.getLength(); - - //Logger.logInfo("ContinueHandler creating output continues for: " + rec.toString() + " datalen: " + datalen); - // if greater than 8023, we need Continues - if (rec instanceof Obj || rec instanceof MSODrawing || rec instanceof MSODrawingGroup) { - return createObjContinues(rec); - } - if ((rec instanceof Sst)) { - return createSstContinues((Sst) rec, insertLoc); - } else if ((datalen > MAXRECLEN) && (!(rec instanceof Continue))) { - return createBigContinues(rec, insertLoc); - } else if (rec instanceof Txo) { - return createTxoContinues((Txo) rec); - } - return 0; - } - - - /** - * generate Continue records for Sst records - */ - public static int createSstContinues(Sst rec, int insertLoc) { - byte[] dta = rec.getData(); - int datalen = dta.length; - if (datalen < MAXRECLEN) return 0; - // get the grbits and continue sizes - Object[] continuedef = Sst.getContinueDef(rec, false); - Integer[] continuesizes = (Integer[]) continuedef[0]; - Byte[] sstgrbits = (Byte[]) continuedef[1]; - // int sstoffset = continuesizes[0].intValue() - rec.getOrigSstLen(); - int numconts = continuesizes.length - 1; - - // blow out old record Continues - removeContinues(rec); - - // account for the offset caused by the Sstgrbits - int sizer = 0; - int dtapos = 0; - - // create Continues, skip the first which is Sst recordbody - for (int i = 1; i <= numconts; i++) { // start after the 1st continue length which is the Sst data body - if (continuesizes[i].intValue() == 0) break; - // numconts is one less than continuesizes, match continuesizes[1] with numconts[0]... - Byte thisgr = sstgrbits[i - 1]; - dtapos += continuesizes[i - 1].intValue(); - - // check for a grbit -- null grbit means Continue Breaks on a one-char UString - boolean hasGrbit = false; - if (thisgr != null) - hasGrbit = ((thisgr.byteValue() < 0x2) && (thisgr.byteValue() >= 0x0)); // Sst grbit is either 0h or 1h, otherwise it's String data - if (continuesizes[i].intValue() == MAXRECLEN) hasGrbit = false; // this is a non-standard Sst Continue - - sizer = continuesizes[i].intValue(); - if (i == numconts) - sizer = (datalen - dtapos); - if (hasGrbit) - sizer++; - byte[] continuedata = new byte[sizer]; - - if (hasGrbit) { - if (DEBUGLEVEL > 1) { - Logger.logInfo("New Continue. HAS grbit."); - Logger.logInfo("Continue GRBIT: " + thisgr); - } - continuedata[0] = thisgr.byteValue(); // set a grbit on the new Continue - System.arraycopy(dta, dtapos, continuedata, 1, continuedata.length - 1); - } else { - if (DEBUGLEVEL > 1) { - Logger.logInfo("New Continue. NO grbit."); - Logger.logInfo("Continue GRBIT: " + (dta[dtapos] & 0x1)); - } - System.arraycopy(dta, dtapos, continuedata, 0, continuedata.length); - } - - Continue thiscont = addContinue(rec, continuedata, insertLoc, rec.wkbook); - if (hasGrbit) thiscont.setHasGrbit(true); - insertLoc++; - } - int sstsize = continuesizes[0].intValue(); - trimRecSize(rec, sstsize); - return numconts; - } - - /** - * Write a record out to the output stream. I have zero idea why this is in here and - * not in bytestreamer or elsewhere. Anyway, we are passing around the streamer in here - * for when it's needed... - * - * @param rec - * @param out - */ - private static void writeRec(BiffRec rec, OutputStream out, ByteStreamer streamer) { - if (rec.getOpcode() == CONTINUE) - rec.preStream(); - - try { // output the rec bytes - streamer.writeRecord(out, rec); - } catch (Exception a) { - Logger.logErr("Streaming WorkBook Bytes for record:" + rec.toString() + " failed: " + a + " Output Corrupted."); - } - } - - /** - * generate Continue records for Sst records - */ - public static void createSstContinues(Sst rec, OutputStream out, ByteStreamer streamer) { - byte[] dta = rec.getData(); - int datalen = dta.length; - // get the grbits and continue sizes - Object[] continuedef = Sst.getContinueDef(rec, false); - Integer[] continuesizes = (Integer[]) continuedef[0]; - - int sstsize = 0; - if (continuesizes.length > 0) { - Integer sstz = continuesizes[0]; - if (sstz != null) - sstsize = sstz.intValue(); - trimRecSize(rec, sstsize); - } - // output the original rec - writeRec(rec, out, streamer); - - Byte[] sstgrbits = (Byte[]) continuedef[1]; - int numconts = continuesizes.length - 1; - - // blow out old record Continues - removeContinues(rec); // are they even there? Should not be! - - // account for the offset caused by the Sstgrbits - int sizer = 0; - int dtapos = 0; - - // create Continues, skip the first which is Sst recordbody - for (int i = 1; i <= numconts; i++) { // start after the 1st continue length which is the Sst data body - if (continuesizes[i].intValue() == 0) break; - // numconts is one less than continuesizes, match continuesizes[1] with numconts[0]... - Byte thisgr = sstgrbits[i - 1]; - dtapos += continuesizes[i - 1].intValue(); - - // check for a grbit -- null grbit means Continue Breaks on a one-char UString - boolean hasGrbit = false; - if (thisgr != null) - hasGrbit = ((thisgr.byteValue() < 0x2) && (thisgr.byteValue() >= 0x0)); // Sst grbit is either 0h or 1h, otherwise it's String data - if (continuesizes[i].intValue() == MAXRECLEN) hasGrbit = false; // this is a non-standard Sst Continue - - sizer = continuesizes[i].intValue(); - if (i == numconts) - sizer = (datalen - dtapos); - if (hasGrbit) { - sizer++; - } - byte[] continuedata = new byte[sizer]; - - if (hasGrbit) { - if (DEBUGLEVEL > 1) { - Logger.logInfo("New Continue. HAS grbit."); - Logger.logInfo("Continue GRBIT: " + thisgr); - } - continuedata[0] = thisgr.byteValue(); // set a grbit on the new Continue - System.arraycopy(dta, dtapos, continuedata, 1, continuedata.length - 1); - } else { - if (DEBUGLEVEL > 1) { - Logger.logInfo("New Continue. NO grbit."); - Logger.logInfo("Continue GRBIT: " + (dta[dtapos] & 0x1)); - } - System.arraycopy(dta, dtapos, continuedata, 0, continuedata.length); - } - - Continue thiscont = createContinue(continuedata, rec.wkbook); - if (hasGrbit) thiscont.setHasGrbit(true); - // output the original rec - writeRec(thiscont, out, streamer); - - } - } - - - /** - * remove Continues from a record - *

                  - * TODO: Can this be removed now? We shouldn't really have continues in memory, just on stream, NO? - */ - public static void removeContinues(BiffRec rec) { - // remove existing Continues (if any) - List oldconts = rec.getContinueVect(); - if (oldconts == null) return; - if (oldconts.size() > 0) { - Iterator it = oldconts.iterator(); - while (it.hasNext()) { - Object ob = it.next(); - rec.getStreamer().removeRecord((BiffRec) ob); - ob = null; // faster!! will it work? - } - rec.removeContinues(); - } - } - - /** - * generate Continue records for records with lots of data - */ - public static void createBigRecContinues(BiffRec rec, OutputStream out, ByteStreamer streamer) { - byte[] dta = rec.getData(); - int datalen = dta.length; - int numconts = datalen / MAXRECLEN; - - if (datalen > MAXRECLEN) { - trimRecSize(rec, MAXRECLEN); - writeRec(rec, out, streamer); - } else { - writeRec(rec, out, streamer); - return; - } - - // create Continues - int[] boundaries = ContinueHandler.getBoundaries(numconts); - int sizer = MAXRECLEN; - for (int i = 0; i < numconts; i++) { - // if this is the last Continue rec it is probably shorter than CONTINUESIZE - if (datalen - boundaries[i] < MAXRECLEN) - sizer = (datalen - boundaries[i]); - byte[] continuedata = new byte[sizer]; - System.arraycopy(dta, boundaries[i], continuedata, 0, continuedata.length); - Continue cr = createContinue(continuedata, rec.getWorkBook()); - writeRec(cr, out, streamer); - } - } - - /** - * generate Continue records for records with lots of data - */ - // 20070921 KSC: Is this used? - public static int createBigContinues(BiffRec rec, int insertLoc) { - byte[] dta = rec.getData(); - int datalen = dta.length; - int numconts = datalen / MAXRECLEN; - // create Continues - Continue[] conts = new Continue[numconts]; - int[] boundaries = ContinueHandler.getBoundaries(numconts); - // remove existing Continues (if any) - removeContinues(rec); - int sizer = MAXRECLEN; - for (int i = 0; i < numconts; i++) { - // if this is the last Continue rec it is probably shorter than CONTINUESIZE - if (datalen - boundaries[i] < MAXRECLEN) sizer = (datalen - boundaries[i]); - byte[] continuedata = new byte[sizer]; - System.arraycopy(dta, boundaries[i], continuedata, 0, continuedata.length); - conts[i] = addContinue(rec, continuedata, insertLoc, rec.getWorkBook()); - insertLoc++; - } - trimRecSize(rec, MAXRECLEN); - return numconts; - } - - /** - * Create and initialize a new Continue record - * - * @param rec the XLSRecord owner of the Continue - * @param data the pre-sized Continue body data - * @param streampos the position to insert the new Continue into the data stream - */ - public static Continue createContinue(byte[] data, Book book) { - Continue cont = new Continue(); - cont.setWorkBook((WorkBook) book); - cont.setData(data); - cont.setStreamer(book.getStreamer()); // 20070921 KSC: Addded - int len = data.length; - cont.setOpcode(CONTINUE); - cont.setLength((short) len); - return cont; - } - - - /** - * Create and initialize a new Continue record - * - * @param rec the XLSRecord owner of the Continue - * @param data the pre-sized Continue body data - * @param streampos the position to insert the new Continue into the data stream - */ - public static Continue addContinue(BiffRec rec, byte[] data, int streampos, Book book) { - Continue cont = new Continue(); - rec.addContinue(cont); - cont.setWorkBook((WorkBook) book); - cont.setData(data); - int len = data.length; - cont.setOpcode(CONTINUE); - cont.setLength((short) len); - return cont; - } - - /** - * generate the mandatory Continue records for the Txo rec type - *

                  - * Txo must have at least 2 Continue recs - * first one contains text data - * second (last) one contains formatting runs - */ - public static int createTxoContinues(Txo rec) { - List txoConts = rec.getContinueVect(); - if (txoConts == null) return 0; - // iterate through existing Continues and update their data. - // for(int i = 0;i< txoConts.length;i++){ - // step into first Continue and find its - // boundary -- everything after goes into - // the 'formatting' Continue rec. For now - // we assume that this can't change - - // now create data Continue(s) for text - - // we can ignore the formatting Continue - // but text may be formatted wierd -- SORRY! : ) - - // insert NEW Continues into byte stream - - // } - if (rec.getLength() > (MAXRECLEN + 4)) - ContinueHandler.trimRecSize(rec, MAXRECLEN); - return 0;//txoConts.length; - - } - - - /** - * generate the mandatory Continue records for the Txo rec type - *

                  - * Txo must have at least 2 Continue recs - * first one contains text data - * second (last) one contains formatting runs - */ - public static void createTxoContinues(Txo rec, OutputStream out, ByteStreamer streamer) { - byte[] dta = rec.getBytes(); - WorkBook book = rec.getWorkBook(); - if (dta.length > (MAXRECLEN + 4)) { - ContinueHandler.trimRecSize(rec, MAXRECLEN); - // stream the rec to out... - ContinueHandler.writeRec(rec, out, streamer); - // and now the restof the recs - Continue[] myconts = getContinues(dta, MAXRECLEN, book); - for (int x = 0; x < myconts.length; x++) { - ContinueHandler.writeRec(myconts[x], out, streamer); - } - } else { - // stream the rec to out... - ContinueHandler.writeRec(rec, out, streamer); - } - - } - - /** - * generate the mandatory Continue records for the Obj rec type - */ - public static void createMSODGContinues(BiffRec rec, OutputStream out, ByteStreamer streamer) { - byte[] dta = rec.getData(); - int datalen = dta.length; - int numconts = datalen / MAXRECLEN; - - if (datalen > MAXRECLEN) { - trimRecSize(rec, MAXRECLEN); - writeRec(rec, out, streamer); - } else { - writeRec(rec, out, streamer); - return; - } - // create Continues - int[] boundaries = ContinueHandler.getBoundaries(numconts); - int sizer = MAXRECLEN; - for (int i = 0; i < numconts; i++) { - // if this is the last Continue rec it is probably shorter than CONTINUESIZE - if (datalen - boundaries[i] < MAXRECLEN) - sizer = (datalen - boundaries[i]); - if (sizer == 0)// reclen hits boundary exactly; ignore last continue; see ByteStreamer.writeOut for boundary issues - break; - byte[] continuedata = new byte[sizer]; - System.arraycopy(dta, boundaries[i], continuedata, 0, continuedata.length); - - BiffRec cr = null; -// now add a second MSODG -- acts like a continue, exists to confuse and dismay - - if (i == 0) { - cr = MSODrawingGroup.getPrototype(); - cr.setData(continuedata); - } else { - cr = createContinue(continuedata, rec.getWorkBook()); - } - writeRec(cr, out, streamer); - } - // reset rec - rec.setData(dta); - } - - private static Continue[] getContinues(byte[] dta, int start, WorkBook book) { - - int clen = dta.length - start; - int len = 0, pos = 0; - int numconts = clen / MAXRECLEN; - numconts++; - Continue[] retconts = new Continue[numconts]; - - Logger.logInfo("Creating continues: " + numconts); - - byte[] dtx = null; - for (int x = 0; x < numconts; x++) { - if (clen > MAXRECLEN) - len = MAXRECLEN; - else - len = clen; - - if (len > 0) { - dtx = new byte[len]; - } else { - dtx = new byte[clen]; - len = clen; - } - - // populate the bytes - System.arraycopy(dta, start, dtx, 0, len); - retconts[x] = createContinue(dtx, book); - clen -= len; - } - return retconts; - } - - /** - * trims the current rec to MAXRECLEN size - */ - public static int createObjContinues(BiffRec rec) { - if (rec.getLength() > (MAXRECLEN + 4)) - ContinueHandler.trimRecSize(rec, MAXRECLEN); - return 0;//objConts.length; - - } - - /** - * get record size boundaries which determine the - * break-point of Continue record data - * - * @param x the number of boundaries needed - */ - static int[] getBoundaries(int x) { - int[] boundaries = new int[x]; - int thisbound = 0; - for (int i = 0; i < x; i++) { - thisbound += MAXRECLEN; - boundaries[i] = thisbound; - } - return boundaries; - } - - /** - * trim original rec to max rec size - * (for records with size-related Continues, not Txos) - */ - public static void trimRecSize(BiffRec rec, int CONTINUESIZE) { - byte[] dta = rec.getData(); - byte[] newdata = new byte[CONTINUESIZE]; - System.arraycopy(dta, 0, newdata, 0, CONTINUESIZE); - rec.setData(newdata); - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - continued = null; - lastTxo = null; - lastObj = null; - lastCont = null; - book = null; - - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/ContinueHandler.kt b/src/main/java/io/starter/formats/XLS/ContinueHandler.kt new file mode 100644 index 0000000..2c91417 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/ContinueHandler.kt @@ -0,0 +1,765 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.Logger + +import java.io.OutputStream +import java.io.Serializable + + +/** + * this class takes care of the tasks related to + * working with Continue records. + * + * + * when a Continue record is created, it needs to be + * associated with its related XLSRecord -- the record whose + * data it contains. + */ +class ContinueHandler(private var book: WorkBook?) : Serializable, XLSConstants { + private var continued: BiffRec? = null + private var handleTxo = false + private var handleObj = false + private var lastTxo: Txo? = null + private var lastObj: Obj? = null + private var lastCont: Continue? = null + + private var splitPrevRec: BiffRec? = null + private var splitContRec: BiffRec? = null + + /** + * add an XLSRecord to this handler + * check if it needs a Continue, if + * so, put in our continued_recs. + */ + fun addRec(rec: BiffRec, datalen: Int) { + // check if this record has Continue records + // if so, delay initialization until all Continues are read. + // In the body.getData() method we then need to check + // if the record has Continues and if so, then read/write init + // data from them. + + val opcode = rec.opcode + var nextOpcode: Short = 0x0 + + if (opcode != XLSConstants.EOF) { + nextOpcode = book!!.factory!!.lookAhead(rec) + } + + if (nextOpcode == XLSConstants.CONTINUE) { + if (DEBUGLEVEL > 11) Logger.logInfo("Next OPCODE IS CONTINUE: " + Integer.toHexString(nextOpcode.toInt())) + } + if (nextOpcode == XLSConstants.CONTINUE && opcode != XLSConstants.CONTINUE) { // the continued rec + if (continued != null) { + continued!!.init() + continued = null + } + this.continued = rec + this.splitPrevRec = this.continued + // if the rec is a Txo, we need to process special + if (continued is Txo) { + this.handleTxo = true + lastTxo = continued as Txo? + lastObj = null + this.handleObj = false // "" + } else if (continued is Obj) { + this.handleObj = true + lastObj = continued as Obj? + // obj records need to be init'd before setsheet + lastObj!!.init() + lastTxo = null + this.handleTxo = false // "" + } else { + this.handleTxo = false + this.handleObj = false + } + lastCont = null + } else if (opcode == XLSConstants.CONTINUE) { // add to the continued rec + splitContRec = rec + rec.init() + if (continued == null && lastCont == null) { + //This is a use case where a chart is in the middle of an Obj, PLS, or Txo record. A continue + // record appears at the end of the last chart EOF, and needs to remain inorder to not cause corruption + if (splitPrevRec != null) { + (rec as Continue).predecessor = splitContRec + } + if (DEBUGLEVEL > 0) + Logger.logWarn("Warning: Out of spec split txo continue record found, reconstructing." + splitPrevRec!!.toString()) + + } else { + if (lastCont != null) + (rec as Continue).predecessor = lastCont + else + (rec as Continue).predecessor = continued + if (continued!!.opcode == XLSConstants.SST || continued!!.opcode == XLSConstants.STRINGREC) { + if (DEBUGLEVEL > 2) Logger.logInfo("Sst Continue. grbit:" + rec.getGrbit()) + } else { + rec.hasGrbit = false // if it can't have one, don't + } + } + + // last data rec was a Txo -- add next 2 Continues + if (handleTxo) { + // txo's have either 2 continues following (text, formatting runs), no continues (an "empty" txo); may also contain continues masking mso's + if (lastTxo!!.text == null) { + if (!isMaskedMSODrawingRec(rec.data)) + // in almost all cases, the next continue is a Text continue + lastTxo!!.text = rec as Continue + else { // it is possible that an empty TXO (one that does NOT contain Text) is followed by a Continue rec which is masking an MSODrawing + continued = createMSODrawingFromContinue(rec) // create a new MSODrawing rec from the Continue rec's data + (rec as Continue).maskedMso = continued as MSODrawing? // set maskedMso in Continue to identify + } + } else if (lastTxo!!.formattingruns == null) + lastTxo!!.formattingruns = rec as Continue + else { // a third continues: will be a masked mso or possibly a "big rec" continues + try { + if (isMaskedMSODrawingRec(rec.data)) { + continued = createMSODrawingFromContinue(rec) // create a new MSODrawing rec from the Continue rec's data + (rec as Continue).maskedMso = continued as MSODrawing? // set maskedMso in Continue to identify + } else if (continued != null) { // then it's a "big rec" continue + continued!!.addContinue(rec as Continue) + (continued as XLSRecord).mergeContinues() // must merge continues "by hand" because data member var is set already + continued!!.removeContinues() + } + } catch (e: Exception) { + if (DEBUGLEVEL > 0) + Logger.logErr("ContinueHandler.txo parsing- encountered unknown Continue record") + } + + lastCont = rec as Continue + } + } else if (handleObj) { + try { // When a Continue record follows an Obj record, it is either masking a Msodrawing record - or it's a "big rec" continues + if (isMaskedMSODrawingRec(rec.data)) { + continued = createMSODrawingFromContinue(rec) // create a new MSODrawing rec from the Continue rec's data + (rec as Continue).maskedMso = continued as MSODrawing? // set maskedMso in Continue to identify + } else if (continued != null) { // then it's a "big rec" continue + continued!!.addContinue(rec as Continue) + (continued as XLSRecord).mergeContinues() // must merge continues "by hand" because data member var is set already + continued!!.removeContinues() + } + lastCont = rec as Continue + } catch (e: Exception) { + if (DEBUGLEVEL > 0) + Logger.logErr("ContinueHandler.Obj parsing- encountered unknown Continue record") + } + + } else { + // null continued? bizarre case testfiles/assess.xls -jm 10/01/2004 + if (continued != null) + continued!!.addContinue(rec as Continue) + + lastCont = rec as Continue + } + } else { + if (continued != null) { + continued!!.init() + /* If Formula Attached String was a continue, Formula cachedValue WAS NOT init-ed. Do here.*/ + if (continued!!.opcode == XLSConstants.STRINGREC) + this.book!!.lastFormula!!.setCachedValue((continued as StringRec).stringVal) + continued = null + } + + // associate boundsheet for init + if (book!!.lastbound != null) { + if (/*opcode == NAME ||*/ + opcode == XLSConstants.FORMULA) + rec.setSheet(book!!.lastbound) + } + // init names without init'ing expression -- must init expression after loading sheet recs ... + if (opcode != XLSConstants.NAME) + rec.init() + else + (rec as Name).init(false) // don't init expression here; do after loading sheet recs ... + lastCont = null + } + } + + /** + * returns true if this id is one of an MSODrawing + *

                  occurs when a Continue record is masking an MSO + * (i.e. contains the record structure of an MSO in it's data, + * with an opcode of Continue) + * + * @param data - record data + * @return true if data is in form of an MSODrawing record + */ + private fun isMaskedMSODrawingRec(data: ByteArray): Boolean { + if (data.size > 3) { + val id = 0xFF and data[3] shl 8 or (0xFF and data[2]) + return id == MSODrawingConstants.MSOFBTSPCONTAINER || + id == MSODrawingConstants.MSOFBTSOLVERCONTAINER || + id == MSODrawingConstants.MSOFBTSPGRCONTAINER || + id == MSODrawingConstants.MSOFBTCLIENTTEXTBOX + } + return false + } + + /** + * create an MSODrwawing Record from a Continue record + * which is masking an MSODrawing (i.e. contains the record + * structure of an MSO in it's data, with an opcode of Continue) + * + * @param rec + * @return + */ + private fun createMSODrawingFromContinue(rec: BiffRec): MSODrawing { + // create an mso and add to drawing recs ... + val mso = MSODrawing() + mso.opcode = XLSConstants.MSODRAWING + mso.workBook = rec.workBook + mso.setData(rec.data) + mso.length = rec.data.size + mso.setDebugLevel(DEBUGLEVEL) + mso.streamer = book!!.streamer + return mso + } + + /** + * clear out object references in prep for closing workbook + */ + fun close() { + continued = null + lastTxo = null + lastObj = null + lastCont = null + book = null + + } + + companion object { + /** + * + */ + private const val serialVersionUID = 164009339243774537L + private val DEBUGLEVEL = 0 + private val processContinues = true // debug setting + + /** + * check if the record needs to have its data + * spanned across Continue records. + */ + fun createContinues(rec: BiffRec, out: OutputStream, streamer: ByteStreamer): Boolean { + val datalen = rec.length + val opc = rec.opcode.toInt() + // Logger.logInfo("ContinueHandler creating output continues for: " + rec.toString() + " datalen: " + datalen); + // if greater than 8023, we need Continues + if (opc == XLSConstants.CONTINUE.toInt()) { + if ((rec as Continue).isBigRecContinue) + // skip ensuing Continues as should be written by main record + return true + else if (rec.maskedMso != null && rec.maskedMso!!.length - 4 > XLSConstants.MAXRECLEN) { + rec.maskedMso!!.opcode = XLSConstants.CONTINUE // so can add the correct record to output + createBigRecContinues(rec.maskedMso!!, out, streamer) + rec.maskedMso!!.opcode = XLSConstants.MSODRAWING // reset so can continue working with this record set + return true // processed, return true + }// handle masked mso's which have continues separately + } else if (opc == XLSConstants.SST.toInt()) { + createSstContinues(rec as Sst, out, streamer) + return true + } else if (opc == XLSConstants.TXO.toInt()) { + createTxoContinues(rec as Txo, out, streamer) + return true + } else if (opc == XLSConstants.MSODRAWINGGROUP.toInt()) { + createMSODGContinues(rec, out, streamer) + return true + } else if (datalen - 4 > XLSConstants.MAXRECLEN) { + createBigRecContinues(rec, out, streamer) + return true + } + return false + } + + + /** + * check if the record needs to have its data + * spanned across Continue records. + */ + internal fun createContinues(rec: BiffRec, insertLoc: Int): Int { + val datalen = rec.length + + //Logger.logInfo("ContinueHandler creating output continues for: " + rec.toString() + " datalen: " + datalen); + // if greater than 8023, we need Continues + if (rec is Obj || rec is MSODrawing || rec is MSODrawingGroup) { + return createObjContinues(rec) + } + if (rec is Sst) { + return createSstContinues(rec, insertLoc) + } else if (datalen > XLSConstants.MAXRECLEN && rec !is Continue) { + return createBigContinues(rec, insertLoc) + } else if (rec is Txo) { + return createTxoContinues(rec) + } + return 0 + } + + + /** + * generate Continue records for Sst records + */ + fun createSstContinues(rec: Sst, insertLoc: Int): Int { + var insertLoc = insertLoc + val dta = rec.getData() + val datalen = dta!!.size + if (datalen < XLSConstants.MAXRECLEN) return 0 + // get the grbits and continue sizes + val continuedef = Sst.getContinueDef(rec, false) + val continuesizes = continuedef[0] as Array + val sstgrbits = continuedef[1] as Array + // int sstoffset = continuesizes[0].intValue() - rec.getOrigSstLen(); + val numconts = continuesizes.size - 1 + + // blow out old record Continues + removeContinues(rec) + + // account for the offset caused by the Sstgrbits + var sizer = 0 + var dtapos = 0 + + // create Continues, skip the first which is Sst recordbody + for (i in 1..numconts) { // start after the 1st continue length which is the Sst data body + if (continuesizes[i].toInt() == 0) break + // numconts is one less than continuesizes, match continuesizes[1] with numconts[0]... + val thisgr = sstgrbits[i - 1] + dtapos += continuesizes[i - 1].toInt() + + // check for a grbit -- null grbit means Continue Breaks on a one-char UString + var hasGrbit = false + if (thisgr != null) + hasGrbit = thisgr!!.toByte() < 0x2 && thisgr!!.toByte() >= 0x0 // Sst grbit is either 0h or 1h, otherwise it's String data + if (continuesizes[i].toInt() == XLSConstants.MAXRECLEN) hasGrbit = false // this is a non-standard Sst Continue + + sizer = continuesizes[i].toInt() + if (i == numconts) + sizer = datalen - dtapos + if (hasGrbit) + sizer++ + val continuedata = ByteArray(sizer) + + if (hasGrbit) { + if (DEBUGLEVEL > 1) { + Logger.logInfo("New Continue. HAS grbit.") + Logger.logInfo("Continue GRBIT: " + thisgr!!) + } + continuedata[0] = thisgr!!.toByte() // set a grbit on the new Continue + System.arraycopy(dta, dtapos, continuedata, 1, continuedata.size - 1) + } else { + if (DEBUGLEVEL > 1) { + Logger.logInfo("New Continue. NO grbit.") + Logger.logInfo("Continue GRBIT: " + (dta[dtapos] and 0x1)) + } + System.arraycopy(dta, dtapos, continuedata, 0, continuedata.size) + } + + val thiscont = addContinue(rec, continuedata, insertLoc, rec.wkbook) + if (hasGrbit) thiscont.hasGrbit = true + insertLoc++ + } + val sstsize = continuesizes[0].toInt() + trimRecSize(rec, sstsize) + return numconts + } + + /** + * Write a record out to the output stream. I have zero idea why this is in here and + * not in bytestreamer or elsewhere. Anyway, we are passing around the streamer in here + * for when it's needed... + * + * @param rec + * @param out + */ + private fun writeRec(rec: BiffRec, out: OutputStream, streamer: ByteStreamer) { + if (rec.opcode == XLSConstants.CONTINUE) + rec.preStream() + + try { // output the rec bytes + streamer.writeRecord(out, rec) + } catch (a: Exception) { + Logger.logErr("Streaming WorkBook Bytes for record:$rec failed: $a Output Corrupted.") + } + + } + + /** + * generate Continue records for Sst records + */ + fun createSstContinues(rec: Sst, out: OutputStream, streamer: ByteStreamer) { + val dta = rec.getData() + val datalen = dta!!.size + // get the grbits and continue sizes + val continuedef = Sst.getContinueDef(rec, false) + val continuesizes = continuedef[0] as Array + + var sstsize = 0 + if (continuesizes.size > 0) { + val sstz = continuesizes[0] + if (sstz != null) + sstsize = sstz!!.toInt() + trimRecSize(rec, sstsize) + } + // output the original rec + writeRec(rec, out, streamer) + + val sstgrbits = continuedef[1] as Array + val numconts = continuesizes.size - 1 + + // blow out old record Continues + removeContinues(rec) // are they even there? Should not be! + + // account for the offset caused by the Sstgrbits + var sizer = 0 + var dtapos = 0 + + // create Continues, skip the first which is Sst recordbody + for (i in 1..numconts) { // start after the 1st continue length which is the Sst data body + if (continuesizes[i].toInt() == 0) break + // numconts is one less than continuesizes, match continuesizes[1] with numconts[0]... + val thisgr = sstgrbits[i - 1] + dtapos += continuesizes[i - 1].toInt() + + // check for a grbit -- null grbit means Continue Breaks on a one-char UString + var hasGrbit = false + if (thisgr != null) + hasGrbit = thisgr!!.toByte() < 0x2 && thisgr!!.toByte() >= 0x0 // Sst grbit is either 0h or 1h, otherwise it's String data + if (continuesizes[i].toInt() == XLSConstants.MAXRECLEN) hasGrbit = false // this is a non-standard Sst Continue + + sizer = continuesizes[i].toInt() + if (i == numconts) + sizer = datalen - dtapos + if (hasGrbit) { + sizer++ + } + val continuedata = ByteArray(sizer) + + if (hasGrbit) { + if (DEBUGLEVEL > 1) { + Logger.logInfo("New Continue. HAS grbit.") + Logger.logInfo("Continue GRBIT: " + thisgr!!) + } + continuedata[0] = thisgr!!.toByte() // set a grbit on the new Continue + System.arraycopy(dta, dtapos, continuedata, 1, continuedata.size - 1) + } else { + if (DEBUGLEVEL > 1) { + Logger.logInfo("New Continue. NO grbit.") + Logger.logInfo("Continue GRBIT: " + (dta[dtapos] and 0x1)) + } + System.arraycopy(dta, dtapos, continuedata, 0, continuedata.size) + } + + val thiscont = createContinue(continuedata, rec.wkbook!!) + if (hasGrbit) thiscont.hasGrbit = true + // output the original rec + writeRec(thiscont, out, streamer) + + } + } + + + /** + * remove Continues from a record + * + * + * TODO: Can this be removed now? We shouldn't really have continues in memory, just on stream, NO? + */ + fun removeContinues(rec: BiffRec) { + // remove existing Continues (if any) + val oldconts = rec.continueVect ?: return + if (oldconts.size > 0) { + val it = oldconts.iterator() + while (it.hasNext()) { + var ob: Any? = it.next() + rec.streamer.removeRecord((ob as BiffRec?)!!) + ob = null // faster!! will it work? + } + rec.removeContinues() + } + } + + /** + * generate Continue records for records with lots of data + */ + fun createBigRecContinues(rec: BiffRec, out: OutputStream, streamer: ByteStreamer) { + val dta = rec.data + val datalen = dta.size + val numconts = datalen / XLSConstants.MAXRECLEN + + if (datalen > XLSConstants.MAXRECLEN) { + trimRecSize(rec, XLSConstants.MAXRECLEN) + writeRec(rec, out, streamer) + } else { + writeRec(rec, out, streamer) + return + } + + // create Continues + val boundaries = ContinueHandler.getBoundaries(numconts) + var sizer = XLSConstants.MAXRECLEN + for (i in 0 until numconts) { + // if this is the last Continue rec it is probably shorter than CONTINUESIZE + if (datalen - boundaries[i] < XLSConstants.MAXRECLEN) + sizer = datalen - boundaries[i] + val continuedata = ByteArray(sizer) + System.arraycopy(dta, boundaries[i], continuedata, 0, continuedata.size) + val cr = createContinue(continuedata, rec.workBook) + writeRec(cr, out, streamer) + } + } + + /** + * generate Continue records for records with lots of data + */ + // 20070921 KSC: Is this used? + fun createBigContinues(rec: BiffRec, insertLoc: Int): Int { + var insertLoc = insertLoc + val dta = rec.data + val datalen = dta.size + val numconts = datalen / XLSConstants.MAXRECLEN + // create Continues + val conts = arrayOfNulls(numconts) + val boundaries = ContinueHandler.getBoundaries(numconts) + // remove existing Continues (if any) + removeContinues(rec) + var sizer = XLSConstants.MAXRECLEN + for (i in 0 until numconts) { + // if this is the last Continue rec it is probably shorter than CONTINUESIZE + if (datalen - boundaries[i] < XLSConstants.MAXRECLEN) sizer = datalen - boundaries[i] + val continuedata = ByteArray(sizer) + System.arraycopy(dta, boundaries[i], continuedata, 0, continuedata.size) + conts[i] = addContinue(rec, continuedata, insertLoc, rec.workBook) + insertLoc++ + } + trimRecSize(rec, XLSConstants.MAXRECLEN) + return numconts + } + + /** + * Create and initialize a new Continue record + * + * @param rec the XLSRecord owner of the Continue + * @param data the pre-sized Continue body data + * @param streampos the position to insert the new Continue into the data stream + */ + fun createContinue(data: ByteArray, book: Book): Continue { + val cont = Continue() + cont.workBook = book as WorkBook + cont.setData(data) + cont.streamer = book.streamer // 20070921 KSC: Addded + val len = data.size + cont.opcode = XLSConstants.CONTINUE + cont.length = len.toShort() + return cont + } + + + /** + * Create and initialize a new Continue record + * + * @param rec the XLSRecord owner of the Continue + * @param data the pre-sized Continue body data + * @param streampos the position to insert the new Continue into the data stream + */ + fun addContinue(rec: BiffRec, data: ByteArray, streampos: Int, book: Book?): Continue { + val cont = Continue() + rec.addContinue(cont) + cont.workBook = book as WorkBook? + cont.setData(data) + val len = data.size + cont.opcode = XLSConstants.CONTINUE + cont.length = len.toShort() + return cont + } + + /** + * generate the mandatory Continue records for the Txo rec type + * + * + * Txo must have at least 2 Continue recs + * first one contains text data + * second (last) one contains formatting runs + */ + fun createTxoContinues(rec: Txo): Int { + val txoConts = rec.continueVect ?: return 0 +// iterate through existing Continues and update their data. + // for(int i = 0;i< txoConts.length;i++){ + // step into first Continue and find its + // boundary -- everything after goes into + // the 'formatting' Continue rec. For now + // we assume that this can't change + + // now create data Continue(s) for text + + // we can ignore the formatting Continue + // but text may be formatted wierd -- SORRY! : ) + + // insert NEW Continues into byte stream + + // } + if (rec.length > XLSConstants.MAXRECLEN + 4) + ContinueHandler.trimRecSize(rec, XLSConstants.MAXRECLEN) + return 0//txoConts.length; + + } + + + /** + * generate the mandatory Continue records for the Txo rec type + * + * + * Txo must have at least 2 Continue recs + * first one contains text data + * second (last) one contains formatting runs + */ + fun createTxoContinues(rec: Txo, out: OutputStream, streamer: ByteStreamer) { + val dta = rec.bytes + val book = rec.workBook + if (dta!!.size > XLSConstants.MAXRECLEN + 4) { + ContinueHandler.trimRecSize(rec, XLSConstants.MAXRECLEN) + // stream the rec to out... + ContinueHandler.writeRec(rec, out, streamer) + // and now the restof the recs + val myconts = getContinues(dta, XLSConstants.MAXRECLEN, book) + for (x in myconts.indices) { + ContinueHandler.writeRec(myconts[x], out, streamer) + } + } else { + // stream the rec to out... + ContinueHandler.writeRec(rec, out, streamer) + } + + } + + /** + * generate the mandatory Continue records for the Obj rec type + */ + fun createMSODGContinues(rec: BiffRec, out: OutputStream, streamer: ByteStreamer) { + val dta = rec.data + val datalen = dta.size + val numconts = datalen / XLSConstants.MAXRECLEN + + if (datalen > XLSConstants.MAXRECLEN) { + trimRecSize(rec, XLSConstants.MAXRECLEN) + writeRec(rec, out, streamer) + } else { + writeRec(rec, out, streamer) + return + } + // create Continues + val boundaries = ContinueHandler.getBoundaries(numconts) + var sizer = XLSConstants.MAXRECLEN + for (i in 0 until numconts) { + // if this is the last Continue rec it is probably shorter than CONTINUESIZE + if (datalen - boundaries[i] < XLSConstants.MAXRECLEN) + sizer = datalen - boundaries[i] + if (sizer == 0) + // reclen hits boundary exactly; ignore last continue; see ByteStreamer.writeOut for boundary issues + break + val continuedata = ByteArray(sizer) + System.arraycopy(dta, boundaries[i], continuedata, 0, continuedata.size) + + var cr: BiffRec? = null + // now add a second MSODG -- acts like a continue, exists to confuse and dismay + + if (i == 0) { + cr = MSODrawingGroup.prototype + cr!!.data = continuedata + } else { + cr = createContinue(continuedata, rec.workBook) + } + writeRec(cr, out, streamer) + } + // reset rec + rec.data = dta + } + + private fun getContinues(dta: ByteArray, start: Int, book: WorkBook?): Array { + + var clen = dta.size - start + var len = 0 + val pos = 0 + var numconts = clen / XLSConstants.MAXRECLEN + numconts++ + val retconts = arrayOfNulls(numconts) + + Logger.logInfo("Creating continues: $numconts") + + var dtx: ByteArray? = null + for (x in 0 until numconts) { + if (clen > XLSConstants.MAXRECLEN) + len = XLSConstants.MAXRECLEN + else + len = clen + + if (len > 0) { + dtx = ByteArray(len) + } else { + dtx = ByteArray(clen) + len = clen + } + + // populate the bytes + System.arraycopy(dta, start, dtx, 0, len) + retconts[x] = createContinue(dtx, book!!) + clen -= len + } + return retconts + } + + /** + * trims the current rec to MAXRECLEN size + */ + fun createObjContinues(rec: BiffRec): Int { + if (rec.length > XLSConstants.MAXRECLEN + 4) + ContinueHandler.trimRecSize(rec, XLSConstants.MAXRECLEN) + return 0//objConts.length; + + } + + /** + * get record size boundaries which determine the + * break-point of Continue record data + * + * @param x the number of boundaries needed + */ + internal fun getBoundaries(x: Int): IntArray { + val boundaries = IntArray(x) + var thisbound = 0 + for (i in 0 until x) { + thisbound += XLSConstants.MAXRECLEN + boundaries[i] = thisbound + } + return boundaries + } + + /** + * trim original rec to max rec size + * (for records with size-related Continues, not Txos) + */ + fun trimRecSize(rec: BiffRec, CONTINUESIZE: Int) { + val dta = rec.data + val newdata = ByteArray(CONTINUESIZE) + System.arraycopy(dta, 0, newdata, 0, CONTINUESIZE) + rec.data = newdata + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Country.java b/src/main/java/io/starter/formats/XLS/Country.kt similarity index 72% rename from src/main/java/io/starter/formats/XLS/Country.java rename to src/main/java/io/starter/formats/XLS/Country.kt index 24d1620..8993205 100644 --- a/src/main/java/io/starter/formats/XLS/Country.java +++ b/src/main/java/io/starter/formats/XLS/Country.kt @@ -20,34 +20,38 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** - * COUNTRY: 8CH
                  - *

                  + * **COUNTRY: 8CH**

                  + * + * * This record stores two Windows country identifiers. The first represents the user interface language of the Excel version * that has saved the file, and the second represents the system regional settings at the time the file was saved. * Record COUNTRY, BIFF3-BIFF8: - *

                  + *
                  + * 
                    * Offset Size Contents
                    * 0 		2 	Windows country identifier of the user interface language of Excel
                    * 2 		2 	Windows country identifier of the system regional settings
                  - * 

                  +
                  * */ -public final class Country extends io.starter.formats.XLS.XLSRecord { +class Country : io.starter.formats.XLS.XLSRecord() { - private static final long serialVersionUID = -4544323710670598072L; + val defaultLanguage: Int + get() = ByteTools.readShort(this.getData()!![0].toInt(), this.getData()!![1].toInt()).toInt() - public void init() { - super.init(); - this.getData(); + override fun init() { + super.init() + this.getData() } - public int getDefaultLanguage() { - return ByteTools.readShort(this.getData()[0], this.getData()[1]); + companion object { + + private val serialVersionUID = -4544323710670598072L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Crn.java b/src/main/java/io/starter/formats/XLS/Crn.java deleted file mode 100644 index 2816d34..0000000 --- a/src/main/java/io/starter/formats/XLS/Crn.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; - -/** - * CRN (005Ah)
                  - *

                  - * This record stores the contents of an external cell or cell range. An external cell range has one row only. If a cell range - * spans over more than one row, several CRN records will be created. - * - *

                  - * offset  size    contents
                  - * ---
                  - * 0 		1 		Index to last column inside of the referenced sheet (lc)
                  - * 1 		1 		Index to first column inside of the referenced sheet (fc)
                  - * 2 		2 		Index to row inside of the referenced sheet
                  - * 4 		var.	List of lc-fc+1 cached values
                  - * 

                  - */ - -public class Crn extends XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3162130963170092322L; - private byte lc, fc; - private int rowIndex; - private ArrayList cachedValues = new ArrayList(); - - public void init() { - super.init(); - lc = this.getByteAt(0); - fc = this.getByteAt(1); - rowIndex = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - int pos = 4; - for (int i = 0; i < lc - fc + 1; i++) { - try { - int type = this.getByteAt(pos++); - switch (type) { - case 0: // empty - pos += 8; - break; - case 1: // numeric - cachedValues.add(new Float(ByteTools.eightBytetoLEDouble(this.getBytesAt(pos, 8)))); - pos += 8; - break; - case 2: // string - short ln = ByteTools.readShort(this.getByteAt(pos), this.getByteAt(pos + 1)); - byte encoding = this.getByteAt(pos + 2); - pos += 3; - if (encoding == 0) { - cachedValues.add(new String(this.getBytesAt(pos, ln))); - pos += ln; - } else {// unicode - cachedValues.add(new String(this.getBytesAt(pos, ln * 2), StandardCharsets.UTF_16LE)); - pos += ln * 2; - } - break; - case 4: // boolean - cachedValues.add(Boolean.valueOf(this.getByteAt(pos + 1) == 1)); - pos += 8; - break; - case 16: // error - cachedValues.add("Error Code: " + this.getByteAt(pos + 1)); - pos += 8; - break; - } - } catch (Exception e) { - - } - } - } - - public String toString() { - String ret = "CRN: lc=" + lc + " fc=" + fc + " rowIndex=" + rowIndex; - for (int i = 0; i < cachedValues.size(); i++) { - ret += " (" + i + ")=" + cachedValues.get(i); - } - return ret; - } -} diff --git a/src/main/java/io/starter/formats/XLS/Crn.kt b/src/main/java/io/starter/formats/XLS/Crn.kt new file mode 100644 index 0000000..4ad7fc6 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Crn.kt @@ -0,0 +1,117 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools + +import java.nio.charset.StandardCharsets +import java.util.ArrayList + +/** + * **CRN (005Ah)**

                  + * + * + * This record stores the contents of an external cell or cell range. An external cell range has one row only. If a cell range + * spans over more than one row, several CRN records will be created. + * + * + *
                  + * offset  size    contents
                  + * ---
                  + * 0 		1 		Index to last column inside of the referenced sheet (lc)
                  + * 1 		1 		Index to first column inside of the referenced sheet (fc)
                  + * 2 		2 		Index to row inside of the referenced sheet
                  + * 4 		var.	List of lc-fc+1 cached values
                  +
                  * + */ + +class Crn : XLSRecord() { + private var lc: Byte = 0 + private var fc: Byte = 0 + private var rowIndex: Int = 0 + private val cachedValues = ArrayList() + + override fun init() { + super.init() + lc = this.getByteAt(0) + fc = this.getByteAt(1) + rowIndex = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()).toInt() + var pos = 4 + for (i in 0 until lc - fc + 1) { + try { + val type = this.getByteAt(pos++).toInt() + when (type) { + 0 // empty + -> pos += 8 + 1 // numeric + -> { + cachedValues.add(ByteTools.eightBytetoLEDouble(this.getBytesAt(pos, 8)!!)) + pos += 8 + } + 2 // string + -> { + val ln = ByteTools.readShort(this.getByteAt(pos).toInt(), this.getByteAt(pos + 1).toInt()) + val encoding = this.getByteAt(pos + 2) + pos += 3 + if (encoding.toInt() == 0) { + cachedValues.add(String(this.getBytesAt(pos, ln.toInt())!!)) + pos += ln.toInt() + } else {// unicode + cachedValues.add(String(this.getBytesAt(pos, ln * 2)!!, StandardCharsets.UTF_16LE)) + pos += ln * 2 + } + } + 4 // boolean + -> { + cachedValues.add(java.lang.Boolean.valueOf(this.getByteAt(pos + 1).toInt() == 1)) + pos += 8 + } + 16 // error + -> { + cachedValues.add("Error Code: " + this.getByteAt(pos + 1)) + pos += 8 + } + } + } catch (e: Exception) { + + } + + } + } + + override fun toString(): String { + var ret = "CRN: lc=$lc fc=$fc rowIndex=$rowIndex" + for (i in cachedValues.indices) { + ret += " (" + i + ")=" + cachedValues.get(i) + } + return ret + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 3162130963170092322L + } +} diff --git a/src/main/java/io/starter/formats/XLS/CustomFormatHelper.java b/src/main/java/io/starter/formats/XLS/CustomFormatHelper.java deleted file mode 100644 index 121d957..0000000 --- a/src/main/java/io/starter/formats/XLS/CustomFormatHelper.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -/** - * CustomFormatHelper.java - *

                  - *

                  - * Feb 26, 2010 - */ -package io.starter.formats.XLS; - -/** - * This class is used by FormatConstants to provide for handling of custom formats - * - * - * From: http://support.microsoft.com/kb/264372 - * - * Custom Number Formats - If one of the built-in number formats does not display the data in the format that you require, you can create your own custom number format. You can create these custom number formats by modifying the built-in formats or by combining the formatting symbols into your own combination. - - Before you create your own custom number format, you need to be aware of a few simple rules governing the syntax for number formats: - Each format that you create can have up to three sections for numbers and a fourth section for text. - ;;; - - - The first section is the format for positive numbers, the second for negative numbers, and the third for zero values. - These sections are separated by semicolons. - If you have only one section, all numbers (positive, negative, and zero) are formatted with that format. - You can prevent any of the number types (positive, negative, zero) from being displayed by not typing symbols in the corresponding section. For example, the following number format prevents any negative or zero values from being displayed: - 0.00;; - To set the color for any section in the custom format, type the name of the color in brackets in the section. For example, the following number format formats positive numbers blue and negative numbers red: - [BLUE]#,##0;[RED]#,##0 - Instead of the default positive, negative and zero sections in the format, you can specify custom criteria that must be met for each section. The conditional statements that you specify must be contained within brackets. For example, the following number format formats all numbers greater than 100 as green, all numbers less than or equal to -100 as yellow, and all other numbers as cyan: - [>100][GREEN]#,##0;[<=-100][YELLOW]#,##0;[CYAN]#,##0 - For each part of the format, type symbols that represent how you want the number to look. See the table below for details on all the available symbols. - To create a custom number format, click Custom in the Category list on the Number tab in the Format Cells dialog box. Then, type your custom number format in the Type box. - - The following table outlines the different symbols available for use in custom number formats. - Format Symbol Description/result - ------------------------------------------------------------------------ - - 0 Digit placeholder. For example, if you type 8.9 and - you want it to display as 8.90, then use the - format #.00 - - # Digit placeholder. Follows the same rules as the 0 - symbol except Excel does not display extra zeros - when the number you type has fewer digits on either - side of the decimal than there are # symbols in the - format. For example, if the custom format is #.## and - you type 8.9 in the cell, the number 8.9 is - displayed. - - ? Digit placeholder. Follows the same rules as the 0 - symbol except Excel places a space for insignificant - zeros on either side of the decimal point so that - decimal points are aligned in the column. For - example, the custom format 0.0? aligns the decimal - points for the numbers 8.9 and 88.99 in a column. - - . (period) Decimal point. - - % Percentage. If you enter a number between 0 and 1, - and you use the custom format 0%, Excel multiplies - the number by 100 and adds the % symbol in the cell. - - , (comma) Thousands separator. Excel separates thousands by - commas if the format contains a comma surrounded by - '#'s or '0's. A comma following a placeholder - scales the number by a thousand. For example, if the - format is #.0,, and you type 12,200,000 in the cell, - the number 12.2 is displayed. - - E- E+ e- e+ Scientific format. Excel displays a number to the - right of the "E" symbol that corresponds to the - number of places the decimal point was moved. For - example, if the format is 0.00E+00 and you type - 12,200,000 in the cell, the number 1.22E+07 is - displayed. If you change the number format to #0.0E+0 - the number 12.2E+6 is displayed. - - $-+/():space Displays the symbol. If you want to display a - character that is different than one of these - symbols, precede the character with a backslash (\) - or enclose the character in quotation marks (" "). - For example, if the number format is (000) and you - type 12 in the cell, the number (012) is displayed. - - \ Display the next character in the format. Excel does - not display the backslash. For example, if the number - format is 0\! and you type 3 in the cell, the value - 3! is displayed. - * Repeat the next character in the format enough times - to fill the column to its current width. You cannot - have more than one asterisk in one section of the - format. For example, if the number format is 0*x and - you type 3 in the cell, the value 3xxxxxx is - displayed. Note, the number of "x" characters - displayed in the cell vary based on the width of the - column. - - _ (underline) Skip the width of the next character. This is useful - for lining up negative and positive values in - different cells of the same column. For example, the - number format _(0.0_);(0.0) align the numbers - 2.3 and -4.5 in the column even though the negative - number has parentheses around it. - - "text" Display whatever text is inside the quotation marks. - For example, the format 0.00 "dollars" displays - "1.23 dollars" (without quotation marks) when you - type 1.23 into the cell. - - @ Text placeholder. If there is text typed in the - cell, the text from the cell is placed in the format - where the @ symbol appears. For example, if the - number format is "Bob "@" Smith" (including - quotation marks) and you type "John" (without - quotation marks) in the cell, the value - "Bob John Smith" (without quotation marks) is - displayed. - - DATE FORMATS - - m Display the month as a number without a leading zero. - - mm Display the month as a number with a leading zero - when appropriate. - - mmm Display the month as an abbreviation (Jan-Dec). - - mmmm Display the month as a full name (January-December). - - d Display the day as a number without a leading zero. - - dd Display the day as a number with a leading zero - when appropriate. - - ddd Display the day as an abbreviation (Sun-Sat). - - dddd Display the day as a full name (Sunday-Saturday). - - yy Display the year as a two-digit number. - - yyyy Display the year as a four-digit number. - - TIME FORMATS - - h Display the hour as a number without a leading zero. - - [h] Elapsed time, in hours. If you are working with a - formula that returns a time where the number of hours - exceeds 24, use a number format similar to - [h]:mm:ss. - - hh Display the hour as a number with a leading zero when - appropriate. If the format contains AM or PM, then - the hour is based on the 12-hour clock. Otherwise, - the hour is based on the 24-hour clock. - - m Display the minute as a number without a leading - zero. - - [m] Elapsed time, in minutes. If you are working with a - formula that returns a time where the number of - minutes exceeds 60, use a number format similar to - [mm]:ss. - - mm Display the minute as a number with a leading zero - when appropriate. The m or mm must appear immediately - after the h or hh symbol, or Excel displays the - month rather than the minute. - - s Display the second as a number without a leading - zero. - - [s] Elapsed time, in seconds. If you are working with a - formula that returns a time where the number of - seconds exceeds 60, use a number format similar to - [ss]. - - ss Display the second as a number with a leading zero - when appropriate. - - NOTE: If you want to display fractions of a second, - use a number format similar to h:mm:ss.00. - - AM/PM Display the hour using a 12-hour clock. Excel - am/pm displays AM, am, A, or a for times from midnight - A/P until noon, and PM, pm, P, or p for times from noon - a/p until midnight. - * - * - * - */ -public class CustomFormatHelper { - -} diff --git a/src/main/java/io/starter/formats/XLS/CustomFormatHelper.kt b/src/main/java/io/starter/formats/XLS/CustomFormatHelper.kt new file mode 100644 index 0000000..8e0021a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/CustomFormatHelper.kt @@ -0,0 +1,217 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +/** + * CustomFormatHelper.java + * + * + * + * + * Feb 26, 2010 + */ +package io.starter.formats.XLS + +/** + * This class is used by FormatConstants to provide for handling of custom formats + * + * + * From: http://support.microsoft.com/kb/264372 + * + * Custom Number Formats + * If one of the built-in number formats does not display the data in the format that you require, you can create your own custom number format. You can create these custom number formats by modifying the built-in formats or by combining the formatting symbols into your own combination. + * + * Before you create your own custom number format, you need to be aware of a few simple rules governing the syntax for number formats: + * Each format that you create can have up to three sections for numbers and a fourth section for text. + * ;;; + * + * + * The first section is the format for positive numbers, the second for negative numbers, and the third for zero values. + * These sections are separated by semicolons. + * If you have only one section, all numbers (positive, negative, and zero) are formatted with that format. + * You can prevent any of the number types (positive, negative, zero) from being displayed by not typing symbols in the corresponding section. For example, the following number format prevents any negative or zero values from being displayed: + * 0.00;; + * To set the color for any section in the custom format, type the name of the color in brackets in the section. For example, the following number format formats positive numbers blue and negative numbers red: + * [BLUE]#,##0;[RED]#,##0 + * Instead of the default positive, negative and zero sections in the format, you can specify custom criteria that must be met for each section. The conditional statements that you specify must be contained within brackets. For example, the following number format formats all numbers greater than 100 as green, all numbers less than or equal to -100 as yellow, and all other numbers as cyan: + * [>100][GREEN]#,##0;[<=-100][YELLOW]#,##0;[CYAN]#,##0 + * For each part of the format, type symbols that represent how you want the number to look. See the table below for details on all the available symbols. + * To create a custom number format, click Custom in the Category list on the Number tab in the Format Cells dialog box. Then, type your custom number format in the Type box. + * + * The following table outlines the different symbols available for use in custom number formats. + * Format Symbol Description/result + * ------------------------------------------------------------------------ + * + * 0 Digit placeholder. For example, if you type 8.9 and + * you want it to display as 8.90, then use the + * format #.00 + * + * # Digit placeholder. Follows the same rules as the 0 + * symbol except Excel does not display extra zeros + * when the number you type has fewer digits on either + * side of the decimal than there are # symbols in the + * format. For example, if the custom format is #.## and + * you type 8.9 in the cell, the number 8.9 is + * displayed. + * + * ? Digit placeholder. Follows the same rules as the 0 + * symbol except Excel places a space for insignificant + * zeros on either side of the decimal point so that + * decimal points are aligned in the column. For + * example, the custom format 0.0? aligns the decimal + * points for the numbers 8.9 and 88.99 in a column. + * + * . (period) Decimal point. + * + * % Percentage. If you enter a number between 0 and 1, + * and you use the custom format 0%, Excel multiplies + * the number by 100 and adds the % symbol in the cell. + * + * , (comma) Thousands separator. Excel separates thousands by + * commas if the format contains a comma surrounded by + * '#'s or '0's. A comma following a placeholder + * scales the number by a thousand. For example, if the + * format is #.0,, and you type 12,200,000 in the cell, + * the number 12.2 is displayed. + * + * E- E+ e- e+ Scientific format. Excel displays a number to the + * right of the "E" symbol that corresponds to the + * number of places the decimal point was moved. For + * example, if the format is 0.00E+00 and you type + * 12,200,000 in the cell, the number 1.22E+07 is + * displayed. If you change the number format to #0.0E+0 + * the number 12.2E+6 is displayed. + * + * $-+/():space Displays the symbol. If you want to display a + * character that is different than one of these + * symbols, precede the character with a backslash (\) + * or enclose the character in quotation marks (" "). + * For example, if the number format is (000) and you + * type 12 in the cell, the number (012) is displayed. + * + * \ Display the next character in the format. Excel does + * not display the backslash. For example, if the number + * format is 0\! and you type 3 in the cell, the value + * 3! is displayed. + * Repeat the next character in the format enough times + * to fill the column to its current width. You cannot + * have more than one asterisk in one section of the + * format. For example, if the number format is 0*x and + * you type 3 in the cell, the value 3xxxxxx is + * displayed. Note, the number of "x" characters + * displayed in the cell vary based on the width of the + * column. + * + * _ (underline) Skip the width of the next character. This is useful + * for lining up negative and positive values in + * different cells of the same column. For example, the + * number format _(0.0_);(0.0) align the numbers + * 2.3 and -4.5 in the column even though the negative + * number has parentheses around it. + * + * "text" Display whatever text is inside the quotation marks. + * For example, the format 0.00 "dollars" displays + * "1.23 dollars" (without quotation marks) when you + * type 1.23 into the cell. + * + * @ Text placeholder. If there is text typed in the + * cell, the text from the cell is placed in the format + * where the @ symbol appears. For example, if the + * number format is "Bob "@" Smith" (including + * quotation marks) and you type "John" (without + * quotation marks) in the cell, the value + * "Bob John Smith" (without quotation marks) is + * displayed. + * + * DATE FORMATS + * + * m Display the month as a number without a leading zero. + * + * mm Display the month as a number with a leading zero + * when appropriate. + * + * mmm Display the month as an abbreviation (Jan-Dec). + * + * mmmm Display the month as a full name (January-December). + * + * d Display the day as a number without a leading zero. + * + * dd Display the day as a number with a leading zero + * when appropriate. + * + * ddd Display the day as an abbreviation (Sun-Sat). + * + * dddd Display the day as a full name (Sunday-Saturday). + * + * yy Display the year as a two-digit number. + * + * yyyy Display the year as a four-digit number. + * + * TIME FORMATS + * + * h Display the hour as a number without a leading zero. + * + * [h] Elapsed time, in hours. If you are working with a + * formula that returns a time where the number of hours + * exceeds 24, use a number format similar to + * [h]:mm:ss. + * + * hh Display the hour as a number with a leading zero when + * appropriate. If the format contains AM or PM, then + * the hour is based on the 12-hour clock. Otherwise, + * the hour is based on the 24-hour clock. + * + * m Display the minute as a number without a leading + * zero. + * + * [m] Elapsed time, in minutes. If you are working with a + * formula that returns a time where the number of + * minutes exceeds 60, use a number format similar to + * [mm]:ss. + * + * mm Display the minute as a number with a leading zero + * when appropriate. The m or mm must appear immediately + * after the h or hh symbol, or Excel displays the + * month rather than the minute. + * + * s Display the second as a number without a leading + * zero. + * + * [s] Elapsed time, in seconds. If you are working with a + * formula that returns a time where the number of + * seconds exceeds 60, use a number format similar to + * [ss]. + * + * ss Display the second as a number with a leading zero + * when appropriate. + * + * NOTE: If you want to display fractions of a second, + * use a number format similar to h:mm:ss.00. + * + * AM/PM Display the hour using a 12-hour clock. Excel + * am/pm displays AM, am, A, or a for times from midnight + * A/P until noon, and PM, pm, P, or p for times from noon + * a/p until midnight. + * + * + * + */ +class CustomFormatHelper diff --git a/src/main/java/io/starter/formats/XLS/DConBin.java b/src/main/java/io/starter/formats/XLS/DConBin.kt similarity index 57% rename from src/main/java/io/starter/formats/XLS/DConBin.java rename to src/main/java/io/starter/formats/XLS/DConBin.kt index d25e0e7..19f3736 100644 --- a/src/main/java/io/starter/formats/XLS/DConBin.java +++ b/src/main/java/io/starter/formats/XLS/DConBin.kt @@ -20,18 +20,20 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger /** * DConBin 0x1B5 - *

                  + * + * * The DConBin record specifies a built-in named range that is a data source (1) * for a PivotTable or a data source (1) for the data consolidation settings of * the associated sheet. - *

                  + * + * * nBuiltin (1 byte): An unsigned integer that specifies the built-in defined * name for the range. MUST be a value from the following table: * Value Meaning @@ -49,16 +51,20 @@ * 0x0B "Auto_Deactivate" * 0x0C "Sheet_Title" * 0x0D "_FilterDatabase" - *

                  + * + * * reserved1 (2 bytes): MUST be zero and MUST be ignored. - *

                  + * + * * reserved2 (1 byte): MUST be zero and MUST be ignored. - *

                  + * + * * cchFile (2 bytes): An unsigned integer that specifies the character count of * stFile. MUST be 0x0000, or greater than or equal to 0x0002. A value of 0x0000 * specifies that the built-in defined name specified in nBuiltin has a workbook * scope and is contained in this file. - *

                  + * + * * stFile (variable): An DConFile structure that specifies the workbook or * workbook and sheet that contains the range specified in nBuiltin. This field * MUST exist if and only if the value of cchFile is greater than zero. If the @@ -67,87 +73,90 @@ * built-in defined name has a sheet–level scope this field specifies both the * sheet name and the workbook file that contains the built-in defined name and * its associated range. - *

                  + * + * * unused (variable): An array of bytes that is unused and MUST be ignored. MUST * exist if and only if cchFile is greater than 0 and stFile specifies a * self-reference (the value of stFile.stFile.rgb[0] is 2). If the value * stFile.stFile.fHighByte is 0 the size of this array is 1. If the value of * stFile.stFile.fHighByte is 1 the size of this array is 2. */ -public class DConBin extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - private byte nBuiltin; - private short cchFile; - - public void init() { - super.init(); - nBuiltin = this.getByteAt(0); - cchFile = ByteTools.readShort(this.getByteAt(5), this.getByteAt(6)); - if (cchFile > 0) - Logger.logWarn("PivotTable: External Workbooks for Built-in Named Range Source are Unspported"); - if (DEBUGLEVEL > 3) Logger.logInfo("DCONBIN: nBuiltin:" + nBuiltin + " cchFile: " + cchFile); - } +class DConBin : XLSRecord(), XLSConstants { + private var nBuiltin: Byte = 0 + private var cchFile: Short = 0 /** * returns the built-in type for the built-in named range data source for a pivot table - *
                  Value Meaning - *

                • 0x00 "Consolidate_Area" - *
                • 0x01 "Auto_Open" - *
                • 0x02 "Auto_Close" - *
                • 0x03 "Extract" - *
                • 0x04 "Database" - *
                • 0x05 "Criteria" - *
                • 0x06 "Print_Area" - *
                • 0x07 "Print_Titles" - *
                • 0x08 "Recorder" - *
                • 0x09 "Data_Form" - *
                • 0x0A "Auto_Activate" - *
                • 0x0B "Auto_Deactivate" - *
                • 0x0C "Sheet_Title" - *
                • 0x0D "_FilterDatabase" + *

                  Value Meaning + * * 0x00 "Consolidate_Area" + * * 0x01 "Auto_Open" + * * 0x02 "Auto_Close" + * * 0x03 "Extract" + * * 0x04 "Database" + * * 0x05 "Criteria" + * * 0x06 "Print_Area" + * * 0x07 "Print_Titles" + * * 0x08 "Recorder" + * * 0x09 "Data_Form" + * * 0x0A "Auto_Activate" + * * 0x0B "Auto_Deactivate" + * * 0x0C "Sheet_Title" + * * 0x0D "_FilterDatabase" * * @return */ - public int getBuiltInType() { - return nBuiltin; - } - /** * sets the built-in type for the built-in named range data source for a pivot table - *
                  Value Meaning - *
                • 0x00 "Consolidate_Area" - *
                • 0x01 "Auto_Open" - *
                • 0x02 "Auto_Close" - *
                • 0x03 "Extract" - *
                • 0x04 "Database" - *
                • 0x05 "Criteria" - *
                • 0x06 "Print_Area" - *
                • 0x07 "Print_Titles" - *
                • 0x08 "Recorder" - *
                • 0x09 "Data_Form" - *
                • 0x0A "Auto_Activate" - *
                • 0x0B "Auto_Deactivate" - *
                • 0x0C "Sheet_Title" - *
                • 0x0D "_FilterDatabase" + *

                  Value Meaning + * * 0x00 "Consolidate_Area" + * * 0x01 "Auto_Open" + * * 0x02 "Auto_Close" + * * 0x03 "Extract" + * * 0x04 "Database" + * * 0x05 "Criteria" + * * 0x06 "Print_Area" + * * 0x07 "Print_Titles" + * * 0x08 "Recorder" + * * 0x09 "Data_Form" + * * 0x0A "Auto_Activate" + * * 0x0B "Auto_Deactivate" + * * 0x0C "Sheet_Title" + * * 0x0D "_FilterDatabase" */ - public void setBuiltInType(int builtinType) { - nBuiltin = (byte) builtinType; - this.getData()[0] = nBuiltin; + var builtInType: Int + get() = nBuiltin.toInt() + set(builtinType) { + nBuiltin = builtinType.toByte() + this.getData()[0] = nBuiltin + } + + override fun init() { + super.init() + nBuiltin = this.getByteAt(0) + cchFile = ByteTools.readShort(this.getByteAt(5).toInt(), this.getByteAt(6).toInt()) + if (cchFile > 0) + Logger.logWarn("PivotTable: External Workbooks for Built-in Named Range Source are Unspported") + if (DEBUGLEVEL > 3) Logger.logInfo("DCONBIN: nBuiltin:$nBuiltin cchFile: $cchFile") } - /** - * create a new DCONBIN - built-in named range data source for a pivot table - * - * @return - */ - public static XLSRecord getPrototype() { - DConBin db = new DConBin(); - db.setOpcode(DCONBIN); - db.setData(new byte[]{0, 0, 0, 0, 0, 0}); - db.init(); - return db; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2639291289806138985L + + /** + * create a new DCONBIN - built-in named range data source for a pivot table + * + * @return + */ + val prototype: XLSRecord? + get() { + val db = DConBin() + db.opcode = XLSConstants.DCONBIN + db.setData(byteArrayOf(0, 0, 0, 0, 0, 0)) + db.init() + return db + } } } diff --git a/src/main/java/io/starter/formats/XLS/DConName.java b/src/main/java/io/starter/formats/XLS/DConName.kt similarity index 61% rename from src/main/java/io/starter/formats/XLS/DConName.java rename to src/main/java/io/starter/formats/XLS/DConName.kt index 7a991e4..0b11573 100644 --- a/src/main/java/io/starter/formats/XLS/DConName.java +++ b/src/main/java/io/starter/formats/XLS/DConName.kt @@ -20,16 +20,17 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger -import java.io.UnsupportedEncodingException; +import java.io.UnsupportedEncodingException /** * DConName 0x52 - *

                  + * + * * The DConName record specifies a named range that is a data source (1) for a * PivotTable or a data source (1) for the data consolidation settings of the * associated sheet. The range is specified as a reference to an external @@ -37,57 +38,58 @@ * external workbook, this record specifies the path to the external workbook. * If the named range has a defined name that has a sheet-level scope, this * record also specifies the name of the sheet that contains the range. - *

                  + * + * * stName (variable): An XLNameUnicodeString structure that specifies a defined name for the source range. - *

                  + * + * * cchFile (2 bytes): An unsigned integer that specifies the character count of stFile. MUST be 0x0000, or greater than or equal to 0x0002. * A value of 0x0000 specifies that the defined name in stName has a workbook scope and is contained in this file. - *

                  + * + * * stFile (variable): A DConFile structure that specifies the workbook, or workbook and sheet, that contains the range specified in stName. * This field exists only if the value of cchFile is greater than zero. If the defined name in stName has workbook scope, * this field specifies the workbook file that contains the defined name and its associated range. * If the defined name in stName has a sheet-level scope, this field specifies both the sheet name and the workbook that * contains the defined name and its associated range. - *

                  + * + * * unused (variable): An array of bytes that is unused and MUST be ignored. MUST exist if and only if cchFile is greater than 0 and * stFile specifies a self-reference (the value of stFile.stFile.rgb[0] is 2). If the value stFile.stFile.fHighByte is 0, * the size of this array is 1. If the value of stFile.stFile.fHighByte is 1, the size of this array is 2. */ -public class DConName extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - private short cchFile; - private String namedRange = null; +class DConName : XLSRecord(), XLSConstants { + private var cchFile: Short = 0 + private var namedRange: String? = null - public void init() { - super.init(); - int cch = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - int pos = 1; + override fun init() { + super.init() + val cch = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() + var pos = 1 if (cch > 0) { //A - fHighByte (1 bit): A bit that specifies whether the characters in rgb are double-byte characters. // 0x0 All the characters in the string have a high byte of 0x00 and only the low bytes are in rgb. // 0x1 All the characters in the string are saved as double-byte characters in rgb. // reserved (7 bits): MUST be zero, and MUST be ignored. - byte encoding = this.getByteAt(++pos); - byte[] tmp = this.getBytesAt(++pos, (cch) * (encoding + 1)); + val encoding = this.getByteAt(++pos) + val tmp = this.getBytesAt(++pos, cch * (encoding + 1)) try { - if (encoding == 0) - namedRange = new String(tmp, DEFAULTENCODING); + if (encoding.toInt() == 0) + namedRange = String(tmp!!, XLSConstants.DEFAULTENCODING) else - namedRange = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding PivotTable name in DCONNAME: " + e); + namedRange = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding PivotTable name in DCONNAME: $e") } + } - cchFile = ByteTools.readShort(this.getByteAt(pos + cch), this.getByteAt(pos + cch + 1)); + cchFile = ByteTools.readShort(this.getByteAt(pos + cch).toInt(), this.getByteAt(pos + cch + 1).toInt()) // either 0 or >=2 if (cchFile > 0) - Logger.logWarn("PivotTable: External Workbooks for Named Range Source are Unspported"); - if (DEBUGLEVEL > 3) Logger.logInfo("DCONNAME: namedRange:" + namedRange + " cchFile: " + cchFile); + Logger.logWarn("PivotTable: External Workbooks for Named Range Source are Unspported") + if (DEBUGLEVEL > 3) Logger.logInfo("DCONNAME: namedRange:$namedRange cchFile: $cchFile") } @@ -96,8 +98,8 @@ public void init() { * * @return */ - public String getNamedRange() { - return this.namedRange; + fun getNamedRange(): String? { + return this.namedRange } /** @@ -105,31 +107,40 @@ public String getNamedRange() { * * @param namedRange */ - public void setNamedRange(String namedRange) { - int cch = (short) ((short) namedRange.length() + 1); - this.namedRange = namedRange; - byte[] data = new byte[3]; - byte[] b = ByteTools.shortToLEBytes(cchFile); - data[0] = b[0]; - data[1] = b[1]; + fun setNamedRange(namedRange: String) { + val cch = (namedRange.length.toShort() + 1).toShort().toInt() + this.namedRange = namedRange + var data = ByteArray(3) + val b = ByteTools.shortToLEBytes(cchFile) + data[0] = b[0] + data[1] = b[1] try { - data = ByteTools.append(namedRange.getBytes(DEFAULTENCODING), data); - } catch (UnsupportedEncodingException e) { + data = ByteTools.append(namedRange.toByteArray(charset(XLSConstants.DEFAULTENCODING)), data) + } catch (e: UnsupportedEncodingException) { } - data = ByteTools.append(new byte[]{0, 0}, data); // data in same workbook - this.setData(data); + + data = ByteTools.append(byteArrayOf(0, 0), data) // data in same workbook + this.setData(data) } - /** - * create a new DCONMANE - named range data source for a pivot table - * - * @return - */ - public static XLSRecord getPrototype() { - DConName dn = new DConName(); - dn.setOpcode(DCONNAME); - dn.setData(new byte[]{0, 0}); - dn.init(); - return dn; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2639291289806138985L + + /** + * create a new DCONMANE - named range data source for a pivot table + * + * @return + */ + val prototype: XLSRecord? + get() { + val dn = DConName() + dn.opcode = XLSConstants.DCONNAME + dn.setData(byteArrayOf(0, 0)) + dn.init() + return dn + } } } diff --git a/src/main/java/io/starter/formats/XLS/DConRef.java b/src/main/java/io/starter/formats/XLS/DConRef.kt similarity index 52% rename from src/main/java/io/starter/formats/XLS/DConRef.java rename to src/main/java/io/starter/formats/XLS/DConRef.kt index 4d9069d..e9d2dca 100644 --- a/src/main/java/io/starter/formats/XLS/DConRef.java +++ b/src/main/java/io/starter/formats/XLS/DConRef.kt @@ -20,43 +20,51 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ExcelTools; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ExcelTools +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger -import java.io.UnsupportedEncodingException; +import java.io.UnsupportedEncodingException /** * DConRef 0x51 - *

                  + * + * * The DConRef record specifies a range in this workbook or in an external * workbook that is a data source for a PivotTable or a data source for the data * consolidation settings of the associated sheet. If the range specified is in * an external workbook this record also specifies the path to the external * workbook. - *

                  + * + * * ref (6 bytes): A RefU structure that specifies the range. If this record is part of an SXTBL production as specified in the * Globals Substream ABNF and this field has a rwFirst equal to 0 and a rwLast equal to 16383, this reference specifies all rows * within the columns specified by colFirst and colLast. - *

                  + * + * * cchFile (2 bytes): An unsigned integer that specifies the count of characters in stFile. MUST be greater than or equal to 0x0002. - *

                  + * + * * stFile (variable): A DConFile structure that specifies the workbook and sheet that contains the range specified in the ref field. - *

                  + * + * * unused (variable): An array of bytes that is unused and MUST be ignored. MUST exist if and only if stFile specifies a self reference * (the value of stFile.stFile.rgb[0] is 2). If the value stFile.stFile.fHighByte is 0 the size of this array is 1. * If the value of stFile.stFile.fHighByte is 1 the size of this array is 2. - *

                  - *

                  + * + * + * + * * The RefU structure specifies a range of cells on the sheet. * rwFirst (2 bytes): A RwU structure that specifies the first row in the range. The value MUST be less than or equal to rwLast. * rwLast (2 bytes): A RwU structure that specifies the last row in the range. * colFirst (1 byte): A ColByteU structure that specifies the first column in the range. The value MUST be less than or equal to colLast. * colLast (1 byte): A ColByteU structure that specifies the last column in the range. - *

                  + * + * * the DConFile structure specifies the workbook file or workbook file and sheet that contain a data source range. * This structure is used by the DConBin, DConRef and DConName records. * stFile (variable): An XLUnicodeStringNoCch that specifies the workbook file or workbook file and sheet that contain the range specified in the DConBin, DConRef or DConName record. @@ -67,161 +75,154 @@ * self-reference = %x0002 sheet-name * See VirtualPath for the definition of the volume, unc-volume, rel-volume, transfer-protocol, startup, alt-startup, library, file-path and sheet-name rules used in the ABNF grammar. Note that the volume, unc-volume, rel-volume, transfer-protocol, startup, alt-startup, library, and file-path rules specify that an optional sheet name can be included. * If this structure is contained in a DConName or DConBin record and the defined name has a workbook scope, then this string MUST satisfy the external-virt-path rule and MUST NOT specify a sheet name. Otherwise a sheet name MUST be specified. - *

                  - *

                  - *

                  - * "http://www.extentech.com">Extentech Inc. + * + * + * + * + * + * + * "http://www.extentech.com">Extentech Inc. */ -public class DConRef extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - private short rwFirst, rwLast; - private short colFirst, colLast; - private short cchFile; - private String fileName = null; - private byte refType = 0; - - /** - * init method - */ - public void init() { - super.init(); - rwFirst = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - rwLast = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - colFirst = (short) this.getByteAt(4); - colLast = (short) this.getByteAt(5); - cchFile = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - if (cchFile > 0) { - //A - fHighByte (1 bit): A bit that specifies whether the characters in rgb are double-byte characters. - // 0x0 All the characters in the string have a high byte of 0x00 and only the low bytes are in rgb. - // 0x1 All the characters in the string are saved as double-byte characters in rgb. - // reserved (7 bits): MUST be zero, and MUST be ignored. - - byte encoding = this.getByteAt(8); - refType = this.getByteAt(9); // 1= simple-file-path-dcon 2= self-reference - - if (refType != 2) // TODO: handle external refs ... - Logger.logWarn("PivotTable: External Data Sources are not supported"); - byte[] tmp = this.getBytesAt(10, (cchFile - 1) * (encoding + 1)); - try { - if (encoding == 0) - fileName = new String(tmp, DEFAULTENCODING); - else - fileName = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding PivotTable name in DCONREF: " + e); - } - } - if (DEBUGLEVEL > 3) - Logger.logInfo("DCONREF: rwFirst:" + rwFirst + " rwLast:" + rwLast + " colFirst:" + colFirst + " colLast:" + colLast + " cchFile:" + cchFile + " fileName:" + fileName); - } +class DConRef : XLSRecord(), XLSConstants { + private var rwFirst: Short = 0 + private var rwLast: Short = 0 + private var colFirst: Short = 0 + private var colLast: Short = 0 + private var cchFile: Short = 0 + private var fileName: String? = null + private var refType: Byte = 0 /** * returns the source range for a pivot table in r0c0r1c form * * @return int[] */ - public int[] getRange() { - return new int[]{rwFirst, colFirst, rwLast, colLast}; - } + val range: IntArray + get() = intArrayOf(rwFirst.toInt(), colFirst.toInt(), rwLast.toInt(), colLast.toInt()) /** * if this is a self-referentail data source i.e. in same workbook, return souce sheet name * * @return */ - public String getSourceSheet() { - if (refType != 2) - Logger.logWarn("External Data Sources are not supported"); - return fileName; - } - - /** - * sets the source range and sheet for the pivot table - * - * @param rc - */ - public void setRange(int[] rc, String sheetName) { - rwFirst = (short) rc[0]; - colFirst = (short) rc[1]; - rwLast = (short) rc[2]; - colLast = (short) rc[3]; - // update record - byte[] data = this.getData(); - byte[] b = ByteTools.shortToLEBytes(rwFirst); - data[0] = b[0]; - data[1] = b[1]; - b = ByteTools.shortToLEBytes(rwLast); - data[2] = b[0]; - data[3] = b[1]; - data[4] = (byte) colFirst; - data[5] = (byte) colLast; - setSourceSheet(sheetName); - } - /** * sets the source sheet for the pivot table * * @param sheetName */ - public void setSourceSheet(String sheetName) { - cchFile = (short) ((short) sheetName.length() + 1); - fileName = sheetName; - byte[] data = new byte[10]; - System.arraycopy(this.getData(), 0, data, 0, 6); - byte[] b = ByteTools.shortToLEBytes(cchFile); - data[6] = b[0]; // cch - data[7] = b[1]; - data[8] = 0; // encoding - data[9] = 0x2; // self-reference flag - try { - data = ByteTools.append(sheetName.getBytes(DEFAULTENCODING), data); - data = ByteTools.append(new byte[]{0}, data); - } catch (UnsupportedEncodingException e) { + // cch + // encoding + // self-reference flag + var sourceSheet: String? + get() { + if (refType.toInt() != 2) + Logger.logWarn("External Data Sources are not supported") + return fileName + } + set(sheetName) { + cchFile = (sheetName.length.toShort() + 1).toShort() + fileName = sheetName + var data = ByteArray(10) + System.arraycopy(this.getData()!!, 0, data, 0, 6) + val b = ByteTools.shortToLEBytes(cchFile) + data[6] = b[0] + data[7] = b[1] + data[8] = 0 + data[9] = 0x2 + try { + data = ByteTools.append(sheetName.toByteArray(charset(XLSConstants.DEFAULTENCODING)), data) + data = ByteTools.append(byteArrayOf(0), data) + } catch (e: UnsupportedEncodingException) { + } + + this.setData(data) } - this.setData(data); - } /** - * create a new default DCONREF source data range record + * returns the cell range for this pivot cache * * @return */ - public static XLSRecord getPrototype() { - DConRef dr = new DConRef(); - dr.setOpcode(DCONREF); - dr.setData(new byte[]{0, 0, 0, 0, 0, 0, 0, 0}); - dr.init(); - return dr; - } - /** - * returns the cell range for this pivot cache + * sets the cell range for the pivot cache * - * @return + * @param cr */ - public CellRange getCellRange() { - String range = fileName + "!" + ExcelTools.formatLocation(new int[]{rwFirst, colFirst, rwLast, colLast}); - try { - return new CellRange(range, null); //this.getWorkBook()); - } catch (CellNotFoundException e) { + //this.getWorkBook()); + var cellRange: CellRange? + get() { + val range = fileName + "!" + ExcelTools.formatLocation(intArrayOf(rwFirst.toInt(), colFirst.toInt(), rwLast.toInt(), colLast.toInt())) + try { + return CellRange(range, null) + } catch (e: CellNotFoundException) { + } + + return null + } + set(cr) = try { + val rc = cr.rangeCoords + setRange(rc, cr.getSheet()!!.sheetName) + } catch (e: CellNotFoundException) { } - return null; + + /** + * init method + */ + override fun init() { + super.init() + rwFirst = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + rwLast = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + colFirst = this.getByteAt(4).toShort() + colLast = this.getByteAt(5).toShort() + cchFile = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + if (cchFile > 0) { + //A - fHighByte (1 bit): A bit that specifies whether the characters in rgb are double-byte characters. + // 0x0 All the characters in the string have a high byte of 0x00 and only the low bytes are in rgb. + // 0x1 All the characters in the string are saved as double-byte characters in rgb. + // reserved (7 bits): MUST be zero, and MUST be ignored. + + val encoding = this.getByteAt(8) + refType = this.getByteAt(9) // 1= simple-file-path-dcon 2= self-reference + + if (refType.toInt() != 2) + // TODO: handle external refs ... + Logger.logWarn("PivotTable: External Data Sources are not supported") + val tmp = this.getBytesAt(10, (cchFile - 1) * (encoding + 1)) + try { + if (encoding.toInt() == 0) + fileName = String(tmp!!, XLSConstants.DEFAULTENCODING) + else + fileName = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding PivotTable name in DCONREF: $e") + } + + } + if (DEBUGLEVEL > 3) + Logger.logInfo("DCONREF: rwFirst:$rwFirst rwLast:$rwLast colFirst:$colFirst colLast:$colLast cchFile:$cchFile fileName:$fileName") } /** - * sets the cell range for the pivot cache + * sets the source range and sheet for the pivot table * - * @param cr + * @param rc */ - public void setCellRange(CellRange cr) { - try { - int[] rc = cr.getRangeCoords(); - setRange(rc, cr.getSheet().getSheetName()); - } catch (CellNotFoundException e) { - } + fun setRange(rc: IntArray, sheetName: String?) { + rwFirst = rc[0].toShort() + colFirst = rc[1].toShort() + rwLast = rc[2].toShort() + colLast = rc[3].toShort() + // update record + val data = this.getData() + var b = ByteTools.shortToLEBytes(rwFirst) + data[0] = b[0] + data[1] = b[1] + b = ByteTools.shortToLEBytes(rwLast) + data[2] = b[0] + data[3] = b[1] + data[4] = colFirst.toByte() + data[5] = colLast.toByte() + sourceSheet = sheetName } /** @@ -229,13 +230,34 @@ public void setCellRange(CellRange cr) { * * @param cr */ - public void setCellRange(String cr) { - String sheetname; + fun setCellRange(cr: String) { + val sheetname: String? if (cr.indexOf("!") != -1) - sheetname = cr.split("!")[0]; + sheetname = cr.split("!".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()[0] else - sheetname = fileName; - int[] rc = ExcelTools.getRangeCoords(cr); - setRange(rc, sheetname); + sheetname = fileName + val rc = ExcelTools.getRangeCoords(cr) + setRange(rc, sheetname) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2639291289806138985L + + /** + * create a new default DCONREF source data range record + * + * @return + */ + val prototype: XLSRecord? + get() { + val dr = DConRef() + dr.opcode = XLSConstants.DCONREF + dr.setData(byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0)) + dr.init() + return dr + } } } diff --git a/src/main/java/io/starter/formats/XLS/Dbcell.java b/src/main/java/io/starter/formats/XLS/Dbcell.java deleted file mode 100644 index b9ff118..0000000 --- a/src/main/java/io/starter/formats/XLS/Dbcell.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.Iterator; -import java.util.List; - - -/** - * Dbcell: Stream Offsets 0xD7
                  - *

                  - * Offsets for value records. There is one DBCELL for - * each 32-row block of Row records and associated cell records. - * - *
                  - * - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       dbRtrw      4       Offset from the start of the DBCELL
                  - * to the start of the first Row in the block
                  - * 8       rgdb        var     Array of stream offsets, 2 bytes each.
                  - *
                  - * The internal format layout is basically:
                  - *
                  - *
                  - * When a record value changes in size, it fires a CellChangeEvent
                  - * which cascades through the other associated objects.
                  - *
                  - * The record size change has the following effects on DBCELL record fields:
                  - *
                  - * 1. Row records for the data block move relative to the
                  - * DBCELL for the block.  The dbRtrw field tracks the position
                  - * of the first Row record, so this needs to be updated in the
                  - * DBCELL only for the changed record block.
                  - *
                  - * 2. The *record*  offsets for the Row stored in the rgdb array
                  - * change starting with the changed value.  These only change
                  - * for the record block containing the changed value -- subsequent
                  - * blocks maintain their relative position to their row and row
                  - * records.
                  - * 

                  - * - * @see WorkBook - * @see Boundsheet - * @see Index - * @see Dbcell - * @see Row - * @see Cell - * @see XLSRecord - */ -public final class Dbcell extends io.starter.formats.XLS.XLSRecord implements XLSConstants { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3169134298616400374L; - private Bof mybof; - - void setBof(Bof b) { - mybof = b; - } - - private byte[] rkdata = new byte[0]; - private int numrecs = -1; - private int dbRtrw; - private int dbcnum; - private short[] rgdb; - private Index myidx; - - Row[] myrows = new Row[32]; - - - /** - * Init the dbcell with it's new values - *

                  - * TODO: review these calls to look for performance gain possibilities. This method can - * get called a lot on output. - * - * @param offsetToFirstRow - offset from the dbcell to the top row - * @param valrecOffsets - array of offsets from row to row in valrecs - */ - public void initDbCell(int offsetToFirstRow, List valrecOffsets) { - byte[] newData = new byte[4 + ((valrecOffsets.size() - 1) * 2)]; - byte[] firstOffset = ByteTools.cLongToLEBytes(offsetToFirstRow); - System.arraycopy(firstOffset, 0, newData, 0, 4); - Iterator it = valrecOffsets.iterator(); - int pointer = 4; - while (it.hasNext()) { - Short s = (Short) it.next(); - // we don't want the last length, not needed, end of chain! - if (it.hasNext()) { - short ns = s.shortValue(); - byte[] b = ByteTools.shortToLEBytes(ns); - newData[pointer++] = b[0]; - newData[pointer++] = b[1]; - } - } - this.setData(newData); - } - - /** - * set the Index record for this RowBlock - *

                  - * there are many RowBlocks per Index record - * Index records need to update their DBCELL - * pointers when we add or move a DBCELL. - */ - public void setIndex(Index idx) { - myidx = idx; - } - - int rwct = 0; - - /** - * this method adds a Row to the Dbcell, as well - * as updating the Dbcell position based on existing - * row data. For use in Index updateDbcellPOinters. - */ - boolean addRow(Row r) { - // if(!this.isFull()){ - myrows[rwct++] = r; - return true; - // }else return false; - } - - - /** - * gets the value and updates the pointer. this is the position - * of the first row in the row block. - */ - public int getdbRtrw() { - if (rwct > 0) { - Row rw1 = this.myrows[0]; - int i = rw1.getOffset(); - int y = this.getOffset(); - return y - i; - } - return -1; - } - - /** iterates through XLSRecords Contained in Rows - and gets offset pointers - - Deprecated? THinkso - - void updateIndexes(){ - Iterator e = myrows.iterator(); - int startingSize = this.getLength(); - int[] idxes = new int[myrows.size()]; - int i = 0, pos = 4; - byte[] newrgdb = new byte[(myrows.size()*2)+4]; - int offst = -1; - while(e.hasNext()){ - Row c = (Row) e.next(); - idxes[i]=c.getFirstCellOffset(); - if(i==0){ // read page 443 of Excel Format book for info on this... - offst = idxes[i]; - idxes[i] += 6; - Row r1 = null; - if(myrows.size() > 1) r1 = (Row) this.myrows.get(1); - else r1 = (Row) this.myrows.get(0); - int dd = r1.offset + r1.getLength(); - if(false)Logger.logInfo("Initializing of Dbcell: " + this.getdbRtrw() + " - " + dd); - idxes[i] = idxes[i] - dd; - if(myrows.size()>1)idxes[i]+=r1.getLength(); - }else{ - idxes[i] -= offst; - // offst+=idxes[i]; - } - if(false)Logger.logInfo(" dbcellpointer: " + String.valueOf( idxes[i] )); - byte[] barr = ByteTools.shortToLEBytes((short)idxes[i++]); - System.arraycopy(barr, 0, newrgdb, pos, 2); - pos+=2; - } - this.setData(newrgdb); - this.setDbRtrw(this.getdbRtrw()); - this.init(); - } - */ - - /** returns whether this RowBlock can - hold any more Row records - - boolean isFull(){ - if(myrows.size() >= 32)return true; - return false; - }*/ - - //Row[] getRows(){return myrows;} - - /** - * returns the byte array containing the DBCELL location - * as an offset from the Bof for the BOUNDSHEET. - *

                  - * this is used by the Index recordto locate RowBlocks. - */ - byte[] getDBCELLPointerPos() { - int bofpos = mybof.offset; - int thispos = this.offset; - int diff = thispos - bofpos; - return ByteTools.cLongToLEBytes(diff); - } - - - /** - * get a new, empty DBCELL - */ - public static XLSRecord getPrototype() { - Dbcell dbc = new Dbcell(); - byte[] dt = new byte[4]; // default val - dbc.originalsize = 4; - dbc.setData(dt); - dbc.setOpcode(DBCELL); - dbc.setLength((short) 4); - dbc.init(); - return dbc; - } - - /** handle a cell change event - - public void fireCellChangeEvent(CellChangeEvent c){ - // do its thing, - this.doCellSizeChangeAction(c); - // then pass it along... - this.getIDX().fireCellChangeEvent(c); - }*/ - - /** - * set the DBCELL number for use by the Index - */ - void setDBCNum(int l) { - dbcnum = l; - } - - /** - * get the DBCELL number for use by the Index - */ - int getDBCNum() { - return dbcnum; - } - - /** set the rows for this dbcell - - public void setRows(AbstractList r){ - this.myrows = r; - for(int i = 0;i 32)if(DEBUGLEVEL > -1)Logger.logInfo("DBCELL has too many rows: "+ String.valueOf(ret)); - //return ret; - } - - /** - * set the associated dbcell index - */ - void setIDX(Index indx) { - myidx = indx; - } - - /** - * get the associated dbcell index - */ - public Index getIDX() { - return myidx; - } - - /** - * Initialize the Dbcell - */ - public void init() { - super.init(); - dbRtrw = ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), this.getByteAt(2), this.getByteAt(3)); - numrecs = (this.getLength() - 8) / 2; - int pos = 4; - rgdb = new short[numrecs]; - for (int i = 0; i < numrecs; i++) { - rgdb[i] = ByteTools.readShort(this.getByteAt(pos++), this.getByteAt(pos++)); - } - if (DEBUGLEVEL > 10) { - // Logger.logInfo("DBCELL POINTER at: " + String.valueOf(dbRtrw)); - for (int t = 0; t < rgdb.length; t++) { - Logger.logInfo(" rgdb" + t + ":" + rgdb[t]); - } - Logger.logInfo(" num idxs: " + numrecs); - } - } - - /** - * set the dbRtrw pointer location. - */ - void setDbRtrw(int val) { - dbRtrw = val; - byte[] b = ByteTools.cLongToLEBytes(val); - System.arraycopy(b, 0, getData(), 0, 4); - } - - /** - * - */ - public void preStream() { - //this.updateIndexes(); - } - - public void close() { - super.close(); - rgdb = null; - myidx = null; - myrows = null; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Dbcell.kt b/src/main/java/io/starter/formats/XLS/Dbcell.kt new file mode 100644 index 0000000..661e747 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Dbcell.kt @@ -0,0 +1,339 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * **Dbcell: Stream Offsets 0xD7**

                  + * + * + * Offsets for value records. There is one DBCELL for + * each 32-row block of Row records and associated cell records. + * + *

                  + * + *

                  + * offset  name        size    contents
                  + * ---
                  + * 4       dbRtrw      4       Offset from the start of the DBCELL
                  + * to the start of the first Row in the block
                  + * 8       rgdb        var     Array of stream offsets, 2 bytes each.
                  + *
                  + * The internal format layout is basically:
                  + *
                  + *
                  + * When a record value changes in size, it fires a CellChangeEvent
                  + * which cascades through the other associated objects.
                  + *
                  + * The record size change has the following effects on DBCELL record fields:
                  + *
                  + * 1. Row records for the data block move relative to the
                  + * DBCELL for the block.  The dbRtrw field tracks the position
                  + * of the first Row record, so this needs to be updated in the
                  + * DBCELL only for the changed record block.
                  + *
                  + * 2. The *record*  offsets for the Row stored in the rgdb array
                  + * change starting with the changed value.  These only change
                  + * for the record block containing the changed value -- subsequent
                  + * blocks maintain their relative position to their row and row
                  + * records.
                  +
                  * + * + * @see WorkBook + * + * @see Boundsheet + * + * @see Index + * + * @see Dbcell + * + * @see Row + * + * @see Cell + * + * @see XLSRecord + */ +class Dbcell : io.starter.formats.XLS.XLSRecord(), XLSConstants { + private var mybof: Bof? = null + + private val rkdata = ByteArray(0) + private var numrecs = -1 + private var dbRtrw: Int = 0 + /** + * get the DBCELL number for use by the Index + */ + /** handle a cell change event + * + * public void fireCellChangeEvent(CellChangeEvent c){ + * // do its thing, + * this.doCellSizeChangeAction(c); + * // then pass it along... + * this.getIDX().fireCellChangeEvent(c); + * } */ + + /** + * set the DBCELL number for use by the Index + */ + internal var dbcNum: Int = 0 + private var rgdb: ShortArray? = null + /** + * get the associated dbcell index + */ + /** + * set the associated dbcell index + */ + var idx: Index? = null + internal set + + internal var myrows: Array? = arrayOfNulls(32) + + /** set the rows for this dbcell + * + * public void setRows(AbstractList r){ + * this.myrows = r; + * for(int i = 0;i();i++){ + * Row rt = (Row) myrows.get(i); + * rt.setDBCell(this); + * } + * } */ + + /** + * returns the number of rows + * contained in this DBCELL. There should + * never be more than 32. + */ + // if(ret > 32)if(DEBUGLEVEL > -1)Logger.logInfo("DBCELL has too many rows: "+ String.valueOf(ret)); + //return ret; + var numRows = 0 + internal set + + /** iterates through XLSRecords Contained in Rows + * and gets offset pointers + * + * Deprecated? THinkso + * + * void updateIndexes(){ + * Iterator e = myrows.iterator(); + * int startingSize = this.getLength(); + * int[] idxes = new int[myrows.size()]; + * int i = 0, pos = 4; + * byte[] newrgdb = new byte[(myrows.size()*2)+4]; + * int offst = -1; + * while(e.hasNext()){ + * Row c = (Row) e.next(); + * idxes[i]=c.getFirstCellOffset(); + * if(i==0){ // read page 443 of Excel Format book for info on this... + * offst = idxes[i]; + * idxes[i] += 6; + * Row r1 = null; + * if(myrows.size() > 1) r1 = (Row) this.myrows.get(1); + * else r1 = (Row) this.myrows.get(0); + * int dd = r1.offset + r1.getLength(); + * if(false)Logger.logInfo("Initializing of Dbcell: " + this.getdbRtrw() + " - " + dd); + * idxes[i] = idxes[i] - dd; + * if(myrows.size()>1)idxes[i]+=r1.getLength(); + * }else{ + * idxes[i] -= offst; + * // offst+=idxes[i]; + * } + * if(false)Logger.logInfo(" dbcellpointer: " + String.valueOf( idxes[i] )); + * byte[] barr = ByteTools.shortToLEBytes((short)idxes[i++]); + * System.arraycopy(barr, 0, newrgdb, pos, 2); + * pos+=2; + * } + * this.setData(newrgdb); + * this.setDbRtrw(this.getdbRtrw()); + * this.init(); + * } + */ + + /** returns whether this RowBlock can + * hold any more Row records + * + * boolean isFull(){ + * if(myrows.size() >= 32)return true; + * return false; + * } */ + + //Row[] getRows(){return myrows;} + + /** + * returns the byte array containing the DBCELL location + * as an offset from the Bof for the BOUNDSHEET. + * + * + * this is used by the Index recordto locate RowBlocks. + */ + internal val dbcellPointerPos: ByteArray + get() { + val bofpos = mybof!!.offset + val thispos = this.offset + val diff = thispos - bofpos + return ByteTools.cLongToLEBytes(diff) + } + + internal fun setBof(b: Bof) { + mybof = b + } + + + /** + * Init the dbcell with it's new values + * + * + * TODO: review these calls to look for performance gain possibilities. This method can + * get called a lot on output. + * + * @param offsetToFirstRow - offset from the dbcell to the top row + * @param valrecOffsets - array of offsets from row to row in valrecs + */ + fun initDbCell(offsetToFirstRow: Int, valrecOffsets: List<*>) { + val newData = ByteArray(4 + (valrecOffsets.size - 1) * 2) + val firstOffset = ByteTools.cLongToLEBytes(offsetToFirstRow) + System.arraycopy(firstOffset, 0, newData, 0, 4) + val it = valrecOffsets.iterator() + var pointer = 4 + while (it.hasNext()) { + val s = it.next() as Short + // we don't want the last length, not needed, end of chain! + if (it.hasNext()) { + val b = ByteTools.shortToLEBytes(s) + newData[pointer++] = b[0] + newData[pointer++] = b[1] + } + } + this.setData(newData) + } + + /** + * set the Index record for this RowBlock + * + * + * there are many RowBlocks per Index record + * Index records need to update their DBCELL + * pointers when we add or move a DBCELL. + */ + override fun setIndex(idx: Index) { + this.idx = idx + } + + /** + * this method adds a Row to the Dbcell, as well + * as updating the Dbcell position based on existing + * row data. For use in Index updateDbcellPOinters. + */ + internal fun addRow(r: Row): Boolean { + // if(!this.isFull()){ + myrows[numRows++] = r + return true + // }else return false; + } + + + /** + * gets the value and updates the pointer. this is the position + * of the first row in the row block. + */ + fun getdbRtrw(): Int { + if (numRows > 0) { + val rw1 = this.myrows!![0] + val i = rw1.getOffset() + val y = this.getOffset() + return y - i + } + return -1 + } + + /** + * Initialize the Dbcell + */ + override fun init() { + super.init() + dbRtrw = ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), this.getByteAt(2), this.getByteAt(3)) + numrecs = (this.length - 8) / 2 + var pos = 4 + rgdb = ShortArray(numrecs) + for (i in 0 until numrecs) { + rgdb[i] = ByteTools.readShort(this.getByteAt(pos++).toInt(), this.getByteAt(pos++).toInt()) + } + if (DEBUGLEVEL > 10) { + // Logger.logInfo("DBCELL POINTER at: " + String.valueOf(dbRtrw)); + for (t in rgdb!!.indices) { + Logger.logInfo(" rgdb" + t + ":" + rgdb!![t]) + } + Logger.logInfo(" num idxs: $numrecs") + } + } + + /** + * set the dbRtrw pointer location. + */ + internal fun setDbRtrw(`val`: Int) { + dbRtrw = `val` + val b = ByteTools.cLongToLEBytes(`val`) + System.arraycopy(b, 0, getData()!!, 0, 4) + } + + /** + * + */ + override fun preStream() { + //this.updateIndexes(); + } + + override fun close() { + super.close() + rgdb = null + this.idx = null + myrows = null + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -3169134298616400374L + + + /** + * get a new, empty DBCELL + */ + // default val + val prototype: XLSRecord? + get() { + val dbc = Dbcell() + val dt = ByteArray(4) + dbc.originalsize = 4 + dbc.setData(dt) + dbc.opcode = XLSConstants.DBCELL + dbc.length = 4.toShort() + dbc.init() + return dbc + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/DefColWidth.java b/src/main/java/io/starter/formats/XLS/DefColWidth.kt similarity index 54% rename from src/main/java/io/starter/formats/XLS/DefColWidth.java rename to src/main/java/io/starter/formats/XLS/DefColWidth.kt index 6aec539..7c422ae 100644 --- a/src/main/java/io/starter/formats/XLS/DefColWidth.java +++ b/src/main/java/io/starter/formats/XLS/DefColWidth.kt @@ -20,45 +20,46 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** - * DefColWidth: default column width for WorkBook (55h)
                  - *

                  + * **DefColWidth: default column width for WorkBook (55h)**

                  + * + * * offset name size contents * --- * 6 miyRw 2 Default Column Width * * - *

                • + * * * @see DefaultRowHeight */ -public final class DefColWidth extends io.starter.formats.XLS.XLSRecord { - short defWid; - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8726286841723548636L; +class DefColWidth : io.starter.formats.XLS.XLSRecord() { + var defaultWidth: Short = 0 + internal set - public void init() { - byte[] mydata = this.getData(); - defWid = ByteTools.readShort(mydata[0], mydata[1]); + override fun init() { + val mydata = this.getData() + defaultWidth = ByteTools.readShort(mydata!![0].toInt(), mydata[1].toInt()) } - public void setDefaultColWidth(int t) { - this.defWid = (short) t; - byte[] mydata = this.getData(); - byte[] heightbytes = ByteTools.shortToLEBytes((short) t); - mydata[0] = heightbytes[0]; - mydata[1] = heightbytes[1]; + fun setDefaultColWidth(t: Int) { + this.defaultWidth = t.toShort() + val mydata = this.getData() + val heightbytes = ByteTools.shortToLEBytes(t.toShort()) + mydata[0] = heightbytes[0] + mydata[1] = heightbytes[1] } - public short getDefaultWidth() { - return defWid; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8726286841723548636L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/DefaultRowHeight.java b/src/main/java/io/starter/formats/XLS/DefaultRowHeight.kt similarity index 56% rename from src/main/java/io/starter/formats/XLS/DefaultRowHeight.java rename to src/main/java/io/starter/formats/XLS/DefaultRowHeight.kt index e6c35eb..695508b 100644 --- a/src/main/java/io/starter/formats/XLS/DefaultRowHeight.java +++ b/src/main/java/io/starter/formats/XLS/DefaultRowHeight.kt @@ -20,19 +20,21 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** - * DefaultRowHeight: default row height for WorkBook (225h)
                  - *

                  + * **DefaultRowHeight: default row height for WorkBook (225h)**

                  + * + * * offset name size contents * --- * 4 options 2 comments * 6 miyRw 2 Default height for unused rows, in twips = 1/20 of a point - *

                  + * + * *  0 2 2 2 Option flags: * Bit Mask Contents * 0 0001H 1 = Row height and default font height do not match 1 @@ -42,52 +44,57 @@ * * * - *

                  + * * * @see DefColWidth */ -public final class DefaultRowHeight extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -930064032441287284L; - short rwh; +class DefaultRowHeight : io.starter.formats.XLS.XLSRecord() { + internal var rwh: Short = 0 + + override var workBook: WorkBook? + get + set(b) { + super.workBook = b + b!!.setDefaultRowHeightRec(this) + } /** * init: save the default row height */ - public void init() { - super.init(); - rwh = ByteTools.readShort(this.getData()[2], this.getData()[3]); - } - - public void setWorkBook(WorkBook b) { - super.setWorkBook(b); - b.setDefaultRowHeightRec(this); + override fun init() { + super.init() + rwh = ByteTools.readShort(this.getData()!![2].toInt(), this.getData()!![3].toInt()) } /** * set the default row height in twips (=1/20 of a point) - *
                  Twips are 20*Excel units - *
                  e.g. default row height in Excel units=12.75 - *
                  20*12.75= 256 (approx) twips + *

                  Twips are 20*Excel units + *

                  e.g. default row height in Excel units=12.75 + *

                  20*12.75= 256 (approx) twips * * @param t - desired default row height in twips */ - public void setDefaultRowHeight(int t) { - this.rwh = (short) t; - byte[] mydata = this.getData(); - byte[] heightbytes = ByteTools.shortToLEBytes(this.rwh); - mydata[2] = heightbytes[0]; - mydata[3] = heightbytes[1]; + fun setDefaultRowHeight(t: Int) { + this.rwh = t.toShort() + val mydata = this.getData() + val heightbytes = ByteTools.shortToLEBytes(this.rwh) + mydata[2] = heightbytes[0] + mydata[3] = heightbytes[1] } /** * set the sheet's default row height in Excel units or twips */ - public void setSheet(Sheet bs) { - this.worksheet = bs; - ((Boundsheet) bs).setDefaultRowHeight(this.rwh / 20.0); + override fun setSheet(bs: Sheet?) { + this.worksheet = bs + (bs as Boundsheet).defaultRowHeight = this.rwh / 20.0 + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -930064032441287284L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Dimensions.java b/src/main/java/io/starter/formats/XLS/Dimensions.java deleted file mode 100644 index db16100..0000000 --- a/src/main/java/io/starter/formats/XLS/Dimensions.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - - -/** - * Dimensions 0x200: Describes the max BiffRec dimensions of a Sheet.
                  - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       rowMic      4       First defined Row
                  - * 8       rowMac      4       Last defined Row plus 1
                  - * 12      colFirst    2       First defined column
                  - * 14      colLast     2       Last defined column plus 1
                  - * 16      reserved    2       must be 0
                  - *
                  - * When a record is added to a Boundsheet, we need to check if this
                  - * has changed the row/col dimensions of the sheet and update this
                  - * record accordingly.
                  - *
                  - * 

                  - * - * @see WorkBook - * @see Boundsheet - * @see Index - * @see Dbcell - * @see Row - * @see Cell - * @see XLSRecord - */ - - -public class Dimensions extends io.starter.formats.XLS.XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7156425132146869228L; - int rowFirst = 0; - int rowLast = 0; - short colFirst = 0; - short colLast = 0; - - - /** - * set last/first cols/rows - */ - public void setRowFirst(int c) { - c++; // inc here instead of updateRowDimensions - byte[] b = ByteTools.cLongToLEBytes(c); - byte[] dt = this.getData(); - if (dt.length > 4) System.arraycopy(b, 0, dt, 0, 4); - this.rowFirst = c; - } - - public int getRowFirst() { - return rowFirst; - } - - /** - * set last/first cols/rows - */ - public void setRowLast(int c) { - c++; // inc here instead of updateRowDimensions - byte[] b = ByteTools.cLongToLEBytes(c); - byte[] dt = this.getData(); - if (dt.length > 4) System.arraycopy(b, 0, dt, 4, 4); - this.rowLast = c; - } - - public int getRowLast() { - return rowLast; - } - - /** - * set last/first cols/rows - */ - public void setColFirst(int c) { - byte[] b = ByteTools.shortToLEBytes((short) c); - byte[] dt = this.getData(); - if (dt.length > 4) System.arraycopy(b, 0, dt, 8, 2); - this.colFirst = (short) c; - } - - public int getColFirst() { - return colFirst; - } - - /** - * set last/first cols/rows - */ - public void setColLast(int c) { - c++; - if (c >= MAXCOLS_BIFF8 && // warn about maxcols - !this.wkbook.getIsExcel2007()) - Logger.logWarn("Dimensions.setColLast column: " + c + " is incompatible with pre Excel2007 versions."); - - if (c >= MAXCOLS) - c = MAXCOLS;// odd case, its supposed to be last defined col +1, but this breaks last col - byte[] b = ByteTools.shortToLEBytes((short) c); - byte[] dt = this.getData(); - if (dt.length > 4) System.arraycopy(b, 0, dt, 10, 2); - this.colLast = (short) c; - } - - public int getColLast() { - return colLast; - } - - public void setSheet(Sheet bs) { - super.setSheet(bs); - bs.setDimensions(this); - } - - public void init() { - super.init(); - rowFirst = ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), this.getByteAt(2), this.getByteAt(3)); - rowLast = ByteTools.readInt(this.getByteAt(4), this.getByteAt(5), this.getByteAt(6), this.getByteAt(7)); - - colFirst = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - colLast = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - this.getData(); - } - - /** - * update the min/max cols and rows - */ - public void updateDimensions(int row, short col) { - this.updateRowDimensions(row); - this.updateColDimension(col); - } - - /** - * update the min/max cols and rows - */ - public void updateRowDimensions(int row) { - // check row dimension - //row++; // TODO: check why we are incrementing here... nowincremented in setRowLast - if (row >= rowLast) { - this.setRowLast(row); // now incremented only in setRowXX - } - if (row < rowFirst) { - this.setRowFirst(row); // now incremented only in setRowXX - } - - if (DEBUGLEVEL > 10) { - String shtnm = this.getSheet().getSheetName(); - Logger.logInfo(shtnm + " dimensions: " + rowFirst + ":" + colFirst + "-" + rowLast + ":" + colLast); - } - } - - /** - * update the min/max cols and rows - */ - public void updateColDimension(short col) { - // check cell dimension - if (col > colLast) { - this.setColLast(col); - } else if ((col - 1) < colFirst) { - this.setColFirst(col); - } - if (DEBUGLEVEL > 10) { - String shtnm = this.getSheet().getSheetName(); - Logger.logInfo(shtnm + " dimensions: " + rowFirst + ":" + colFirst + "-" + rowLast + ":" + colLast); - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Dimensions.kt b/src/main/java/io/starter/formats/XLS/Dimensions.kt new file mode 100644 index 0000000..ca0478e --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Dimensions.kt @@ -0,0 +1,204 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * **Dimensions 0x200: Describes the max BiffRec dimensions of a Sheet.**

                  + * + *
                  + * offset  name        size    contents
                  + * ---
                  + * 4       rowMic      4       First defined Row
                  + * 8       rowMac      4       Last defined Row plus 1
                  + * 12      colFirst    2       First defined column
                  + * 14      colLast     2       Last defined column plus 1
                  + * 16      reserved    2       must be 0
                  + *
                  + * When a record is added to a Boundsheet, we need to check if this
                  + * has changed the row/col dimensions of the sheet and update this
                  + * record accordingly.
                  + *
                  +
                  * + * + * @see WorkBook + * + * @see Boundsheet + * + * @see Index + * + * @see Dbcell + * + * @see Row + * + * @see Cell + * + * @see XLSRecord + */ + + +class Dimensions : io.starter.formats.XLS.XLSRecord() { + internal var rowFirst = 0 + internal var rowLast = 0 + internal var colFirst: Short = 0 + internal var colLast: Short = 0 + + + /** + * set last/first cols/rows + */ + fun setRowFirst(c: Int) { + var c = c + c++ // inc here instead of updateRowDimensions + val b = ByteTools.cLongToLEBytes(c) + val dt = this.getData() + if (dt!!.size > 4) System.arraycopy(b, 0, dt, 0, 4) + this.rowFirst = c + } + + fun getRowFirst(): Int { + return rowFirst + } + + /** + * set last/first cols/rows + */ + fun setRowLast(c: Int) { + var c = c + c++ // inc here instead of updateRowDimensions + val b = ByteTools.cLongToLEBytes(c) + val dt = this.getData() + if (dt!!.size > 4) System.arraycopy(b, 0, dt, 4, 4) + this.rowLast = c + } + + fun getRowLast(): Int { + return rowLast + } + + /** + * set last/first cols/rows + */ + fun setColFirst(c: Int) { + val b = ByteTools.shortToLEBytes(c.toShort()) + val dt = this.getData() + if (dt!!.size > 4) System.arraycopy(b, 0, dt, 8, 2) + this.colFirst = c.toShort() + } + + fun getColFirst(): Int { + return colFirst.toInt() + } + + /** + * set last/first cols/rows + */ + fun setColLast(c: Int) { + var c = c + c++ + if (c >= XLSConstants.MAXCOLS_BIFF8 && // warn about maxcols + !this.wkbook!!.isExcel2007) + Logger.logWarn("Dimensions.setColLast column: $c is incompatible with pre Excel2007 versions.") + + if (c >= XLSConstants.MAXCOLS) + c = XLSConstants.MAXCOLS// odd case, its supposed to be last defined col +1, but this breaks last col + val b = ByteTools.shortToLEBytes(c.toShort()) + val dt = this.getData() + if (dt!!.size > 4) System.arraycopy(b, 0, dt, 10, 2) + this.colLast = c.toShort() + } + + fun getColLast(): Int { + return colLast.toInt() + } + + override fun setSheet(bs: Sheet?) { + super.setSheet(bs) + bs!!.dimensions = this + } + + override fun init() { + super.init() + rowFirst = ByteTools.readInt(this.getByteAt(0), this.getByteAt(1), this.getByteAt(2), this.getByteAt(3)) + rowLast = ByteTools.readInt(this.getByteAt(4), this.getByteAt(5), this.getByteAt(6), this.getByteAt(7)) + + colFirst = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + colLast = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + this.getData() + } + + /** + * update the min/max cols and rows + */ + fun updateDimensions(row: Int, col: Short) { + this.updateRowDimensions(row) + this.updateColDimension(col) + } + + /** + * update the min/max cols and rows + */ + fun updateRowDimensions(row: Int) { + // check row dimension + //row++; // TODO: check why we are incrementing here... nowincremented in setRowLast + if (row >= rowLast) { + this.setRowLast(row) // now incremented only in setRowXX + } + if (row < rowFirst) { + this.setRowFirst(row) // now incremented only in setRowXX + } + + if (DEBUGLEVEL > 10) { + val shtnm = this.sheet!!.sheetName + Logger.logInfo("$shtnm dimensions: $rowFirst:$colFirst-$rowLast:$colLast") + } + } + + /** + * update the min/max cols and rows + */ + fun updateColDimension(col: Short) { + // check cell dimension + if (col > colLast) { + this.setColLast(col.toInt()) + } else if (col - 1 < colFirst) { + this.setColFirst(col.toInt()) + } + if (DEBUGLEVEL > 10) { + val shtnm = this.sheet!!.sheetName + Logger.logInfo("$shtnm dimensions: $rowFirst:$colFirst-$rowLast:$colLast") + } + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 7156425132146869228L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.java b/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.java deleted file mode 100644 index fddf048..0000000 --- a/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.java +++ /dev/null @@ -1,536 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.formulas.PtgArea; -import io.starter.formats.XLS.formulas.PtgRef; -import io.starter.toolkit.ByteTools; - -import java.io.Serializable; -import java.util.Comparator; -import java.util.Iterator; -import java.util.Map; -import java.util.TreeMap; - -/** - * DiscontiguousRefStruct manages discontiguous ranges within one sheet. Each of the ranges is managed via a Ref object, - * be it range or single cell references. - */ -public class DiscontiguousRefStruct implements Serializable { - - private static final long serialVersionUID = -7923448634000437926L; - // KSC: try to decrease processing time by using refPtgs treemap private ArrayList sqrefs = new ArrayList(); - private refPtgs allrefs = new refPtgs(new refPtgComparer()); - XLSRecord parentRec = null; - - /** - * Handles creating a SqRefStruct via a string passed in, this should be in the format - * reference1,reference2, - * or - * 'A1:A5,G21,H33' - */ - public DiscontiguousRefStruct(String ranges, XLSRecord parentRec) { - this.parentRec = parentRec; - String[] refs = ranges.split(","); - for (int i = 0; i < refs.length; i++) { - try { - if (!refs[i].equals("")) { -/* KSC: try to decrease processing time by using refPtgs treemap SqRef pref = new SqRef(refs[i],parentRec); - sqrefs.add(pref);*/ - allrefs.add(refs[i], parentRec); - } - } catch (NumberFormatException e) { - //keep going - } - } - } - - /** - * Returns the refs in R1C1 format - * - * @return - */ - public String[] getRefs() { -/* KSC: try to decrease processing time by using refPtgs treemap String[] s = new String[sqrefs.size()]; - for (int i=0;i - * 2 rgbSqref var Array of 8 byte sqref structures, format shown below - * in sqref class - * - * @param sqrefs - */ - public DiscontiguousRefStruct(byte[] sqrefrec, XLSRecord parentRec) { - this.parentRec = parentRec; - for (int i = 0; i < sqrefrec.length; ) { - byte[] sref = new byte[8]; - System.arraycopy(sqrefrec, i, sref, 0, 8); -/* KSC: try to decrease processing time by using refPtgs treemap SqRef s = new SqRef(sref, parentRec); - sqrefs.add(s);*/ - allrefs.add(sref, parentRec); - i += 8; - } - } - - /** - * Returns the binary record of this SQRef for output to Biff8 - */ - public byte[] getRecordData() { -/* KSC: try to decrease processing time by using refPtgs treemap - short s = (short) sqrefs.size(); - byte[] retData = ByteTools.shortToLEBytes(s); - for (int i=0;i= 0; i--) { - - byte[] retData = new byte[0]; - Iterator ptgs = allrefs.values().iterator(); - while (ptgs.hasNext()) { - try { - PtgRef pr = (PtgRef) ptgs.next(); -// retData = ByteTools.append(getRecordData((PtgRef)refs[i]), retData); - retData = ByteTools.append(retData, getRecordData(pr)); - } catch (Exception ex) { - } - } - return retData; - } - - /** - * Return the number of references this structure contains - * - * @return - */ - public int getNumRefs() { -// return sqrefs.size(); - return allrefs.size(); - } - - /** - * Return toString as an array of references - */ - public String toString() { - String result = "["; -/* KSC: TODO FINISH Iterator i = sqrefs.iterator(); - while(i.hasNext()) { - result += i.next().toString(); - if(i.hasNext())result += ", "; - }*/ - result += "]"; - return result; - } - - /** - * Adds a ref to the existing group of refs - * - * @param range - */ - public void addRef(String range) { -/* KSC: try to decrease processing time by using refPtgs treemap SqRef sr = new SqRef(range, this.parentRec); - sqrefs.add(sr);*/ - allrefs.add(range, this.parentRec); - } - - /** - * Determines if the reference structure encompasses the - * reference value passed in. - * - * @param rowcol - * @return - */ - public boolean containsReference(int[] rowcol) { -/* KSC: try to decrease processing time by using refPtgs treemap - for (int i=0;i - * Note that if you are including 3dReferences this could return inconsistent results - * - * @return - */ - public int[] getRowColBounds() { -/* KSC: try to decrease processing time by using refPtgs treemap - int[] retValues = {0,0,0,0}; - for (int i=0;iretValues[x])||i==0)retValues[x]=locs[x]; - } - }*/ -/* Object[] refs= allrefs.values().toArray(); - for (int i= refs.length-1; i >= 0; i--) {*/ - int[] retValues = {Integer.MAX_VALUE, Integer.MAX_VALUE, 0, 0}; - Iterator ptgs = allrefs.values().iterator(); - int i = 0; - while (ptgs.hasNext()) { - PtgRef pr = (PtgRef) ptgs.next(); -// PtgRef pr= (PtgRef)refs[i]; - int[] locs = pr.getIntLocation(); - for (int x = 0; x < 2; x++) { - if ((locs[x] < retValues[x])) retValues[x] = locs[x]; - } - for (int x = 2; x < 4; x++) { - if ((locs[x] > retValues[x])) retValues[x] = locs[x]; - } - i++; - } - return retValues; - } - - private byte[] getRecordData(PtgRef myPtg) { - byte[] retData = new byte[0]; - int[] rc = myPtg.getRowCol(); - if (rc[0] >= 65536)// TODO: if XLSX, would this get here???? - rc[0] = -1; - if (rc[2] >= 65536) - rc[2] = -1; - retData = ByteTools.append(ByteTools.shortToLEBytes((short) rc[0]), retData); - retData = ByteTools.append(ByteTools.shortToLEBytes((short) rc[2]), retData); - retData = ByteTools.append(ByteTools.shortToLEBytes((short) rc[1]), retData); - retData = ByteTools.append(ByteTools.shortToLEBytes((short) rc[3]), retData); - return retData; - } - - /** - * SQRef is a Ref (PtgArea) with specific methods to get and set via a different byte array than - * ptgArea normally uses. - *

                  - * Sqref Structures - *

                  - * OFFSET NAME SIZE CONTENTS - * ----- - * 2 rwFirst 2 First row in reference - * 4 rwLast 2 Last row in reference - * 6 colFirst 2 First column in reference - * 8 colLast 2 Last column in reference - */ - class SqRef implements Serializable { - private static final long serialVersionUID = -7923448634000437926L; - PtgRef myPtg; - - /** - * Construct an Sqref structure from a range string - * - * @param range - * @param parentRec - */ - public SqRef(String range, XLSRecord parentRec) { - // add handling for different ref types, for now utilize a PtgArea - if (range != null && !range.equals("")) { - myPtg = new PtgArea(range, parentRec); - myPtg.addToRefTracker(); - } - } - - - public String toString() { - return myPtg.toString(); - } - - /** - * Constructor for usage with any sort of ptgRef - * - * @param ptg - */ - public SqRef(PtgRef ptg) { - myPtg = ptg; - } - - - public String getLocation() { - return myPtg.getLocation(); - } - - - /** - * Determine if the ref contains the rowcol passed in - * - * @return - */ - public boolean contains(int[] rowcol) { - if (myPtg instanceof PtgArea) { - return ((PtgArea) myPtg).contains(rowcol); - } - return false; - } - - public int[] getIntLocation() { - return myPtg.getIntLocation(); - } - - /** - * Construct an Sqref structure from a byte array - * in the format specified above for an sqref - *

                  - * We do a string conversion for the absolute reference translation - * which all (?) sqrefs share. - * - * @param range - * @param parentRec - */ - public SqRef(byte[] b, XLSRecord parentRec) { - int row = ByteTools.readShort(b[0], b[1]); - int col = ByteTools.readShort(b[4], b[5]); - int row2 = ByteTools.readShort(b[2], b[3]); - if (row2 < 0) // if row truly references MAXROWS_BIFF8 comes out - - row2 += XLSConstants.MAXROWS_BIFF8; - - int col2 = ByteTools.readShort(b[6], b[7]); - int[] rc = {row, col, row2, col2}; - boolean[] bool = {true, true, true, true}; - String location = ExcelTools.formatRangeRowCol(rc, bool); - myPtg = new PtgArea(location, parentRec); - myPtg.addToRefTracker(); - } - - /** - * Get the sqref as a byte array in the standardized SqRef structure - * - * @return - */ - public byte[] getRecordData() { - byte[] retData = new byte[0]; - int[] rc = myPtg.getRowCol(); - if (rc[0] >= 65536)// TODO: if XLSX, would this get here???? - rc[0] = -1; - if (rc[2] >= 65536) - rc[2] = -1; - retData = ByteTools.append(ByteTools.shortToLEBytes((short) rc[0]), retData); - retData = ByteTools.append(ByteTools.shortToLEBytes((short) rc[2]), retData); - retData = ByteTools.append(ByteTools.shortToLEBytes((short) rc[1]), retData); - retData = ByteTools.append(ByteTools.shortToLEBytes((short) rc[3]), retData); - return retData; - } - - } - -} - -class refPtgs extends TreeMap implements Serializable { - private static final long serialVersionUID = -7923448634000437926L; - static final long SECONDPTGFACTOR = (((long) XLSRecord.MAXCOLS + (long) XLSRecord.MAXROWS * XLSRecord.MAXCOLS)); - - /** - * set the custom Comparitor for tracked Ptgs - * Tracked Ptgs are referened by a unique key that is based upon it's location and it's parent record - * - * @param c - */ - public refPtgs(Comparator c) { - super(c); - } - - /** - * single access point for unique key creation from a ptg location and the ptg's parent - * would be so much cleaner without the double precision issues ... sigh - * - * @param o - * @return - */ - private Object getKey(Object o) throws IllegalArgumentException { - long loc = ((PtgRef) o).hashcode; - if (loc == -1) // may happen on a referr (should have been caught earlier) or if not initialized yet - throw new IllegalArgumentException(); - - long ploc = ((PtgRef) o).getParentRec().hashCode(); - return new long[]{loc, ploc}; - } - - /** - * access point for unique key creation from separate identities - * - * @param loc -- location key for a ptgref - * @param ploc -- location key for a parent rec - * @return Object key - */ - private Object getKey(long loc, long ploc) { - return new long[]{loc, ploc}; - } - - /** - * override of add to record ptg location hash + parent has for later lookups - */ - public boolean add(Object o) { - try { - super.put(this.getKey(o), o); - } catch (IllegalArgumentException e) { // SHOULD NOT HAPPEN -- happens upon RefErrs but they shouldnt be added ... - } - return true; - } - - public boolean add(byte[] b, XLSRecord parentRec) { - int row = ByteTools.readShort(b[0], b[1]); - int col = ByteTools.readShort(b[4], b[5]); - int row2 = ByteTools.readShort(b[2], b[3]); - if (row2 < 0) // if row truly references MAXROWS_BIFF8 comes out - - row2 += XLSConstants.MAXROWS_BIFF8; - - int col2 = ByteTools.readShort(b[6], b[7]); - int[] rc = {row, col, row2, col2}; - boolean[] bool = {true, true, true, true}; - String location = ExcelTools.formatRangeRowCol(rc, bool); - PtgArea pa = new PtgArea(location, parentRec); - pa.addToRefTracker(); - return this.add(pa); - } - - public boolean add(String range, XLSRecord parentRec) { - // add handling for different ref types, for now utilize a PtgArea - if (range != null && !range.equals("")) { - PtgArea pa = new PtgArea(range, parentRec); - pa.addToRefTracker(); - return this.add(pa); - } - return false; - } - - /** - * override of the contains method to look up ptg location + parent record via hashcode - * to see if it is already contained within the store - */ - public boolean contains(Object o) { - return super.containsKey(getKey(o)); - } - - public boolean containsReference(int[] rc) { - long loc = PtgRef.getHashCode(rc[0], rc[1]); // get location in hashcode notation - Map m = this.subMap(getKey(loc, 0), getKey(loc + 1, 0)); // +1 for max parent - Object key; - if (m != null && m.size() > 0) { - Iterator ii = m.keySet().iterator(); - while (ii.hasNext()) { - key = ii.next(); - long testkey = ((long[]) key)[0]; - if (testkey == loc) { // longs to remove parent hashcode portion of double -//System.out.print(": Found ptg" + this.get((Integer)locs.get(key))); - return true; - } else - break; // shouldn't hit here - } - } - // now see if test cell falls into any areas - m = this.tailMap(getKey(SECONDPTGFACTOR, 0)); // ALL AREAS ... - if (m != null) { - Iterator ii = m.keySet().iterator(); - while (ii.hasNext()) { - key = ii.next(); - long testkey = ((long[]) key)[0]; - double firstkey = testkey / SECONDPTGFACTOR; - double secondkey = (testkey % SECONDPTGFACTOR); - if ((long) firstkey <= loc && (long) secondkey >= loc) { - int col0 = (int) firstkey % XLSRecord.MAXCOLS; - int col1 = (int) secondkey % XLSRecord.MAXCOLS; - int rw0 = ((int) (firstkey / XLSRecord.MAXCOLS)) - 1; - int rw1 = ((int) (secondkey / XLSRecord.MAXCOLS)) - 1; - if (this.isaffected(rc, new int[]{rw0, col0, rw1, col1})) { -//System.out.print(": Found area " + ((PtgRef)this.get(index))); - return true; - } - } else if (firstkey > loc) // we're done - break; - } - } -//io.starter.toolkit.Logger.log(""); - - return false; - } - - /** - * returns true if cell coordinates are contained within the area coordinates - * - * @param cellrc - * @param arearc - * @return - */ - private boolean isaffected(int[] cellrc, int[] arearc) { - if (cellrc[0] < arearc[0]) return false; // row above the first ref row? - if (cellrc[0] > arearc[2]) return false; // row after the last ref row? - - if (cellrc[1] < arearc[1]) return false; // col before the first ref col? - return cellrc[1] <= arearc[3]; // col after the last ref col? - } - - /** - * remove this PtgRef object via it's key - */ - @Override - public Object remove(Object o) { - return super.remove(getKey(o)); - } - - public Object[] toArray() { - return this.values().toArray(); - } -} - -/** - * custom comparitor which compares keys for TrackerPtgs - * consisting of a long ptg location hash, long parent record hashcode - */ -class refPtgComparer implements Comparator, Serializable { - public int compare(Object o1, Object o2) { - long[] key1 = (long[]) o1; - long[] key2 = (long[]) o2; - if (key1[0] < key2[0]) return -1; - if (key1[0] > key2[0]) return 1; - if (key1[0] == key2[0]) { - if (key1[1] == key2[1]) return 0; // equals - if (key1[1] < key2[1]) return -1; - if (key1[1] > key2[1]) return 1; - } - return -1; - } -} diff --git a/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.kt b/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.kt new file mode 100644 index 0000000..54d2ac1 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/DiscontiguousRefStruct.kt @@ -0,0 +1,552 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.formulas.PtgArea +import io.starter.formats.XLS.formulas.PtgRef +import io.starter.toolkit.ByteTools + +import java.io.Serializable +import java.util.Comparator +import java.util.TreeMap + +/** + * DiscontiguousRefStruct manages discontiguous ranges within one sheet. Each of the ranges is managed via a Ref object, + * be it range or single cell references. + */ +class DiscontiguousRefStruct : Serializable { + // KSC: try to decrease processing time by using refPtgs treemap private ArrayList sqrefs = new ArrayList(); + private val allrefs = refPtgs(refPtgComparer()) + internal var parentRec: XLSRecord? = null + + /** + * Returns the refs in R1C1 format + * + * @return + */ + /* KSC: try to decrease processing time by using refPtgs treemap String[] s = new String[sqrefs.size()]; + for (int i=0;i + get() { + + val s = arrayOfNulls(allrefs.size) + val ptgs = allrefs.values.iterator() + var i = 0 + while (ptgs.hasNext()) { + try { + val pr = ptgs.next() as PtgRef + s[i++] = pr.location + } catch (ex: Exception) { + } + + } + return s + } + + /** + * Returns the binary record of this SQRef for output to Biff8 + */ + /* KSC: try to decrease processing time by using refPtgs treemap + short s = (short) sqrefs.size(); + byte[] retData = ByteTools.shortToLEBytes(s); + for (int i=0;i= 0; i--) { + // retData = ByteTools.append(getRecordData((PtgRef)refs[i]), retData); + val recordData: ByteArray + get() { + + var retData = ByteArray(0) + val ptgs = allrefs.values.iterator() + while (ptgs.hasNext()) { + try { + val pr = ptgs.next() as PtgRef + retData = ByteTools.append(retData, getRecordData(pr)) + } catch (ex: Exception) { + } + + } + return retData + } + + /** + * Return the number of references this structure contains + * + * @return + */ + // return sqrefs.size(); + val numRefs: Int + get() = allrefs.size + + /** + * Get the bounding rectangle of all references in this structure in the format + * {topRow,leftCol,bottomRow,rightCol} + * + * + * Note that if you are including 3dReferences this could return inconsistent results + * + * @return + */ + /* KSC: try to decrease processing time by using refPtgs treemap + int[] retValues = {0,0,0,0}; + for (int i=0;iretValues[x])||i==0)retValues[x]=locs[x]; + } + }*//* Object[] refs= allrefs.values().toArray(); + for (int i= refs.length-1; i >= 0; i--) {*/// PtgRef pr= (PtgRef)refs[i]; + val rowColBounds: IntArray + get() { + val retValues = intArrayOf(Integer.MAX_VALUE, Integer.MAX_VALUE, 0, 0) + val ptgs = allrefs.values.iterator() + var i = 0 + while (ptgs.hasNext()) { + val pr = ptgs.next() as PtgRef + val locs = pr.intLocation + for (x in 0..1) { + if (locs!![x] < retValues[x]) retValues[x] = locs!![x] + } + for (x in 2..3) { + if (locs!![x] > retValues[x]) retValues[x] = locs!![x] + } + i++ + } + return retValues + } + + /** + * Handles creating a SqRefStruct via a string passed in, this should be in the format + * reference1,reference2, + * or + * 'A1:A5,G21,H33' + */ + constructor(ranges: String, parentRec: XLSRecord) { + this.parentRec = parentRec + val refs = ranges.split(",".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + for (i in refs.indices) { + try { + if (refs[i] != "") { + /* KSC: try to decrease processing time by using refPtgs treemap SqRef pref = new SqRef(refs[i],parentRec); + sqrefs.add(pref);*/ + allrefs.add(refs[i], parentRec) + } + } catch (e: NumberFormatException) { + //keep going + } + + } + } + + /** + * Takes a binary array representing a sqref struct. + * + * + * 2 rgbSqref var Array of 8 byte sqref structures, format shown below + * in sqref class + * + * @param sqrefs + */ + constructor(sqrefrec: ByteArray, parentRec: XLSRecord) { + this.parentRec = parentRec + var i = 0 + while (i < sqrefrec.size) { + val sref = ByteArray(8) + System.arraycopy(sqrefrec, i, sref, 0, 8) + /* KSC: try to decrease processing time by using refPtgs treemap SqRef s = new SqRef(sref, parentRec); + sqrefs.add(s);*/ + allrefs.add(sref, parentRec) + i += 8 + } + } + + /** + * Return toString as an array of references + */ + override fun toString(): String { + var result = "[" + /* KSC: TODO FINISH Iterator i = sqrefs.iterator(); + while(i.hasNext()) { + result += i.next().toString(); + if(i.hasNext())result += ", "; + }*/ + result += "]" + return result + } + + /** + * Adds a ref to the existing group of refs + * + * @param range + */ + fun addRef(range: String) { + /* KSC: try to decrease processing time by using refPtgs treemap SqRef sr = new SqRef(range, this.parentRec); + sqrefs.add(sr);*/ + allrefs.add(range, this.parentRec) + } + + /** + * Determines if the reference structure encompasses the + * reference value passed in. + * + * @param rowcol + * @return + */ + fun containsReference(rowcol: IntArray): Boolean { + /* KSC: try to decrease processing time by using refPtgs treemap + for (int i=0;i= 65536) + // TODO: if XLSX, would this get here???? + rc[0] = -1 + if (rc[2] >= 65536) + rc[2] = -1 + retData = ByteTools.append(ByteTools.shortToLEBytes(rc[0].toShort()), retData) + retData = ByteTools.append(ByteTools.shortToLEBytes(rc[2].toShort()), retData) + retData = ByteTools.append(ByteTools.shortToLEBytes(rc[1].toShort()), retData) + retData = ByteTools.append(ByteTools.shortToLEBytes(rc[3].toShort()), retData) + return retData + } + + /** + * SQRef is a Ref (PtgArea) with specific methods to get and set via a different byte array than + * ptgArea normally uses. + * + * + * Sqref Structures + * + * + * OFFSET NAME SIZE CONTENTS + * ----- + * 2 rwFirst 2 First row in reference + * 4 rwLast 2 Last row in reference + * 6 colFirst 2 First column in reference + * 8 colLast 2 Last column in reference + */ + internal inner class SqRef : Serializable { + var myPtg: PtgRef + + + val location: String? + get() = myPtg.location + + val intLocation: IntArray? + get() = myPtg.intLocation + + /** + * Get the sqref as a byte array in the standardized SqRef structure + * + * @return + */ + // TODO: if XLSX, would this get here???? + val recordData: ByteArray + get() { + var retData = ByteArray(0) + val rc = myPtg.rowCol + if (rc[0] >= 65536) + rc[0] = -1 + if (rc[2] >= 65536) + rc[2] = -1 + retData = ByteTools.append(ByteTools.shortToLEBytes(rc[0].toShort()), retData) + retData = ByteTools.append(ByteTools.shortToLEBytes(rc[2].toShort()), retData) + retData = ByteTools.append(ByteTools.shortToLEBytes(rc[1].toShort()), retData) + retData = ByteTools.append(ByteTools.shortToLEBytes(rc[3].toShort()), retData) + return retData + } + + /** + * Construct an Sqref structure from a range string + * + * @param range + * @param parentRec + */ + constructor(range: String?, parentRec: XLSRecord) { + // add handling for different ref types, for now utilize a PtgArea + if (range != null && range != "") { + myPtg = PtgArea(range, parentRec) + myPtg.addToRefTracker() + } + } + + + override fun toString(): String? { + return myPtg.toString() + } + + /** + * Constructor for usage with any sort of ptgRef + * + * @param ptg + */ + constructor(ptg: PtgRef) { + myPtg = ptg + } + + + /** + * Determine if the ref contains the rowcol passed in + * + * @return + */ + operator fun contains(rowcol: IntArray): Boolean { + return if (myPtg is PtgArea) { + (myPtg as PtgArea).contains(rowcol) + } else false + } + + /** + * Construct an Sqref structure from a byte array + * in the format specified above for an sqref + * + * + * We do a string conversion for the absolute reference translation + * which all (?) sqrefs share. + * + * @param range + * @param parentRec + */ + constructor(b: ByteArray, parentRec: XLSRecord) { + val row = ByteTools.readShort(b[0].toInt(), b[1].toInt()).toInt() + val col = ByteTools.readShort(b[4].toInt(), b[5].toInt()).toInt() + var row2 = ByteTools.readShort(b[2].toInt(), b[3].toInt()).toInt() + if (row2 < 0) + // if row truly references MAXROWS_BIFF8 comes out - + row2 += XLSConstants.MAXROWS_BIFF8 + + val col2 = ByteTools.readShort(b[6].toInt(), b[7].toInt()).toInt() + val rc = intArrayOf(row, col, row2, col2) + val bool = booleanArrayOf(true, true, true, true) + val location = ExcelTools.formatRangeRowCol(rc, bool) + myPtg = PtgArea(location, parentRec) + myPtg.addToRefTracker() + } + + companion object { + private const val serialVersionUID = -7923448634000437926L + } + + } + + companion object { + + private const val serialVersionUID = -7923448634000437926L + } + +} + +internal class refPtgs +/** + * set the custom Comparitor for tracked Ptgs + * Tracked Ptgs are referened by a unique key that is based upon it's location and it's parent record + * + * @param c + */ +(c: Comparator<*>) : TreeMap<*, *>(c), Serializable { + + /** + * single access point for unique key creation from a ptg location and the ptg's parent + * would be so much cleaner without the double precision issues ... sigh + * + * @param o + * @return + */ + @Throws(IllegalArgumentException::class) + private fun getKey(o: Any?): Any { + val loc = (o as PtgRef).hashcode + if (loc == -1) + // may happen on a referr (should have been caught earlier) or if not initialized yet + throw IllegalArgumentException() + + val ploc = (o as PtgRef).parentRec!!.hashCode().toLong() + return longArrayOf(loc, ploc) + } + + /** + * access point for unique key creation from separate identities + * + * @param loc -- location key for a ptgref + * @param ploc -- location key for a parent rec + * @return Object key + */ + private fun getKey(loc: Long, ploc: Long): Any { + return longArrayOf(loc, ploc) + } + + /** + * override of add to record ptg location hash + parent has for later lookups + */ + fun add(o: Any): Boolean { + try { + super.put(this.getKey(o), o) + } catch (e: IllegalArgumentException) { // SHOULD NOT HAPPEN -- happens upon RefErrs but they shouldnt be added ... + } + + return true + } + + fun add(b: ByteArray, parentRec: XLSRecord): Boolean { + val row = ByteTools.readShort(b[0].toInt(), b[1].toInt()).toInt() + val col = ByteTools.readShort(b[4].toInt(), b[5].toInt()).toInt() + var row2 = ByteTools.readShort(b[2].toInt(), b[3].toInt()).toInt() + if (row2 < 0) + // if row truly references MAXROWS_BIFF8 comes out - + row2 += XLSConstants.MAXROWS_BIFF8 + + val col2 = ByteTools.readShort(b[6].toInt(), b[7].toInt()).toInt() + val rc = intArrayOf(row, col, row2, col2) + val bool = booleanArrayOf(true, true, true, true) + val location = ExcelTools.formatRangeRowCol(rc, bool) + val pa = PtgArea(location, parentRec) + pa.addToRefTracker() + return this.add(pa) + } + + fun add(range: String?, parentRec: XLSRecord?): Boolean { + // add handling for different ref types, for now utilize a PtgArea + if (range != null && range != "") { + val pa = PtgArea(range, parentRec) + pa.addToRefTracker() + return this.add(pa) + } + return false + } + + /** + * override of the contains method to look up ptg location + parent record via hashcode + * to see if it is already contained within the store + */ + operator fun contains(o: Any): Boolean { + return super.containsKey(getKey(o)) + } + + fun containsReference(rc: IntArray): Boolean { + val loc = PtgRef.getHashCode(rc[0], rc[1]) // get location in hashcode notation + var m: Map<*, *>? = this.subMap(getKey(loc, 0), getKey(loc + 1, 0)) // +1 for max parent + val key: Any + if (m != null && m!!.size > 0) { + val ii = m!!.keys.iterator() + while (ii.hasNext()) { + key = ii.next() + val testkey = (key as LongArray)[0] + return if (testkey == loc) { // longs to remove parent hashcode portion of double + //System.out.print(": Found ptg" + this.get((Integer)locs.get(key))); + true + } else + break // shouldn't hit here + } + } + // now see if test cell falls into any areas + m = this.tailMap(getKey(SECONDPTGFACTOR, 0)) // ALL AREAS ... + if (m != null) { + val ii = m!!.keys.iterator() + while (ii.hasNext()) { + key = ii.next() + val testkey = (key as LongArray)[0] + val firstkey = (testkey / SECONDPTGFACTOR).toDouble() + val secondkey = (testkey % SECONDPTGFACTOR).toDouble() + if (firstkey.toLong() <= loc && secondkey.toLong() >= loc) { + val col0 = firstkey.toInt() % XLSRecord.MAXCOLS + val col1 = secondkey.toInt() % XLSRecord.MAXCOLS + val rw0 = (firstkey / XLSRecord.MAXCOLS).toInt() - 1 + val rw1 = (secondkey / XLSRecord.MAXCOLS).toInt() - 1 + if (this.isaffected(rc, intArrayOf(rw0, col0, rw1, col1))) { + //System.out.print(": Found area " + ((PtgRef)this.get(index))); + return true + } + } else if (firstkey > loc) + // we're done + break + } + } + //io.starter.toolkit.Logger.log(""); + + return false + } + + /** + * returns true if cell coordinates are contained within the area coordinates + * + * @param cellrc + * @param arearc + * @return + */ + private fun isaffected(cellrc: IntArray, arearc: IntArray): Boolean { + if (cellrc[0] < arearc[0]) return false // row above the first ref row? + if (cellrc[0] > arearc[2]) return false // row after the last ref row? + + return if (cellrc[1] < arearc[1]) false else cellrc[1] <= arearc[3] // col before the first ref col? +// col after the last ref col? + } + + /** + * remove this PtgRef object via it's key + */ + override fun remove(o: Any?): Any { + return super.remove(getKey(o)) + } + + fun toArray(): Array { + return this.values.toTypedArray() + } + + companion object { + private const val serialVersionUID = -7923448634000437926L + val SECONDPTGFACTOR = XLSRecord.MAXCOLS.toLong() + XLSRecord.MAXROWS.toLong() * XLSRecord.MAXCOLS + } +} + +/** + * custom comparitor which compares keys for TrackerPtgs + * consisting of a long ptg location hash, long parent record hashcode + */ +internal class refPtgComparer : Comparator<*>, Serializable { + override fun compare(o1: Any, o2: Any): Int { + val key1 = o1 as LongArray + val key2 = o2 as LongArray + if (key1[0] < key2[0]) return -1 + if (key1[0] > key2[0]) return 1 + if (key1[0] == key2[0]) { + if (key1[1] == key2[1]) return 0 // equals + if (key1[1] < key2[1]) return -1 + if (key1[1] > key2[1]) return 1 + } + return -1 + } +} diff --git a/src/main/java/io/starter/formats/XLS/Dsf.java b/src/main/java/io/starter/formats/XLS/Dsf.kt similarity index 68% rename from src/main/java/io/starter/formats/XLS/Dsf.java rename to src/main/java/io/starter/formats/XLS/Dsf.kt index 8e17c51..ac041a0 100644 --- a/src/main/java/io/starter/formats/XLS/Dsf.java +++ b/src/main/java/io/starter/formats/XLS/Dsf.kt @@ -20,28 +20,32 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** * Dsf Double Stream File (161h) - *

                  + * + * * simple flag indicating whether this is a double-stream file - **/ -public class Dsf extends io.starter.formats.XLS.XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4818410956902736572L; - int fDSF = -1; + */ +class Dsf : io.starter.formats.XLS.XLSRecord() { + internal var fDSF = -1 /** * all it does is indicate DSF-ness */ - public void init() { - super.init(); - fDSF = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + override fun init() { + super.init() + fDSF = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 4818410956902736572L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Dv.java b/src/main/java/io/starter/formats/XLS/Dv.java deleted file mode 100644 index c441f9f..0000000 --- a/src/main/java/io/starter/formats/XLS/Dv.java +++ /dev/null @@ -1,1369 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.DateConverter; -import io.starter.OpenXLS.ExcelTools; -import io.starter.OpenXLS.ValidationHandle; -import io.starter.formats.XLS.formulas.FormulaParser; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.formats.XLS.formulas.PtgArea; -import io.starter.formats.XLS.formulas.PtgRef; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.Stack; - - -/** - * Dv: Data Validity Settings (01BEh)
                  - *

                  - * This record is part of the Data Validity Table. It stores data validity settings and a list of cell ranges which - * contain these settings. The prompt box appears while editing such a cell. The error box appears, if the entered value - * does not fit the conditions. The data validity settings of a sheet are stored in a sequential list of DV records. This list is - * preluded by an DVAL record. If a string is empty and the default text should appear in the prompt box or error - * box, the string must contain a single zero character (string length will be 1). - *

                  - *
                  - *
                  - * Offset          Name              Size                Contents
                  - * --------------------------------------------------------
                  - * 0               dwDvFlags      4                   Option flags (see below)
                  - * 4               dTitlePrompt   var                 Title of the prompt box (Unicode string, 16-bit string length)
                  - * var.            dTitleError      var.                Title of the error box (Unicode string, 16-bit string length)
                  - * var.            dTextPrompt  var.                Text of the prompt box (Unicode string, 16-bit string length)
                  - * var.            dTextError      var.                Text of the error box (Unicode string, 16-bit string length)
                  - * var.            sz1                 2                    Size of the formula data for first condition (sz1)
                  - * var.            garbage          2                   Not used
                  - * var.            firstCond       sz1                Formula data for first condition (RPN token array without size field)
                  - * var.            sz2                 2                   Size of the formula data for second condition (sz2)
                  - * var.            garbage           2                   Not used
                  - * var.            secondCond sz2                  Formula data for second condition (RPN token array without size field)
                  - * var.            cRangeList    var.                 Cell range address list with all affected ranges
                  - *
                  - * Option flags field:
                  - * Bit             Mask                    Name                 Contents
                  - * --
                  - * 3-0         0000000FH           ValType             Data type: 00H = Any value
                  - * 01H = Integer values
                  - * 02H = Decimal values
                  - * 03H = User defined list
                  - * 04H = Date
                  - * 05H = Time
                  - * 06H = Text length
                  - * 07H = Formula
                  - * 6-4     00000070H               ErrStyle               Error style: 00H = Stop
                  - * 01H = Warning
                  - * 02H = Info
                  - * 7         00000080H              fStrLookup         1 = In list type validity the string list is explicitly given in the formula
                  - * 8         00000100H            fAllowBlank         1 = Empty cells allowed
                  - * 9         00000200H             fSuppressCombo 1 = Suppress the drop down arrow in list type validity
                  - * 18      00040000H             fShowInputMsg     1 = Show prompt box if cell selected
                  - * 19      00080000H             fShowErrorMsg      1 = Show error box if invalid values entered
                  - * 23-20 00F00000H             typOperator         Condition operator: 00H = Between
                  - * 01H = Not between
                  - * 02H = Equal
                  - * 03H = Not equal
                  - * 04H = Greater than
                  - * 05H = Less than
                  - * 06H = Greater or equal
                  - * 07H = Less or equal
                  - *
                  - * 

                  - * In list type validity it is possible to enter an explicit string list. This string list is stored as tStr token . The string - * items are separated by zero characters. There is no zero character at the end of the string list. - * Example for a string list with the 3 strings A, B, and C: A<00H>B<00H>C (contained in a tStr token, string - * length is 5). - */ -public class Dv extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7895028832113540094L; - private int grbit; - private Unicodestring dTitlePrompt; - private Unicodestring dTitleError; - private Unicodestring dTextPrompt; - private Unicodestring dTextError; - private Stack firstCond; - private Stack secondCond; - private ArrayList cRangeList; - private byte[] garbageByteOne = new byte[2]; - private byte[] garbageByteTwo = new byte[2]; - byte numLocs; - //private byte[] garbageByteThree = new byte[1]; - // grbit (dwDvFlags) fields - private byte valType; - private byte errStyle; - private boolean fStrLookup; - private boolean fAllowBlank; - private boolean fSuppressCombo; - private boolean fShowInputMsg; - private boolean fShowErrMsg; - private short IMEMode; - private byte typOperator; - private boolean dirtyflag = false; - - // 20090606: made prototype completely blank i.e. no prompt, error text or formulas ... -// private byte[] PROTOTYPE_BYTES = {0, 1, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -// 0, 0, 89, 84, 0 };//, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - private byte[] PROTOTYPE_BYTES = {3, 1, 12, 0, - 1, 0, 0, 0, - 1, 0, 0, 0, - 1, 0, 0, 0, - 1, 0, 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 1, - 0, 0, 0, 0, 0, 1, 0, 1, 0}; - - // BITMASK - private static final int BITMASK_VALTYPE = 0x0000000F; - private static final int BITMASK_ERRSTYLE = 0x00000070; - private static final int BITMASK_FSTRLOOKUP = 0x00000080; - private static final int BITMASK_FALLOWBLANK = 0x00000100; - private static final int BITMASK_FSUPRESSCOMBO = 0x00000200; - private static final int BITMASK_MDIMEMODE = 0x0003FC00; - private static final int BITMASK_FSHOWINPUTMSG = 0x00040000; - private static final int BITMASK_FSHOWERRORMSG = 0x00080000; - private static final int BITMASK_TYPOPERATOR = 0x00F00000; - - // 20090609 KSC: need to store ranges separately as OOXML ranges addresses may exceed 2003 maximum size - private String[] ooxmlranges = null; - - /** - * Determine if the value passed in is valid for - * this validation - * - * @param value - * @return - */ - public boolean isValid(Object value) throws ValidationException { - - // TODO: look into whether null is ever a valid value. for now we assume "no". - if (value == null) - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - - if (!this.isCorrectDataType(value)) - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - if (value instanceof Date) { - double d = DateConverter.getXLSDateVal((java.util.Date) value); - value = d + ""; - } - switch (typOperator) { - case ValidationHandle.CONDITION_BETWEEN: - if (isBetween(value)) return true; - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - case ValidationHandle.CONDITION_NOT_BETWEEN: - if (isNotBetween(value)) return true; - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - case ValidationHandle.CONDITION_EQUAL: - if (isEqual(value)) return true; - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - case ValidationHandle.CONDITION_GREATER_THAN: - if (isGreaterThan(value)) return true; - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - case ValidationHandle.CONDITION_GREATER_OR_EQUAL: - if (isGreaterOrEqual(value)) return true; - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - case ValidationHandle.CONDITION_LESS_OR_EQUAL: - if (isLessOrEqual(value)) return true; - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - - case ValidationHandle.CONDITION_LESS_THAN: - if (isGreaterOrEqual(value)) return true; - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - - case ValidationHandle.CONDITION_NOT_EQUAL: - if (isNotEqual(value)) return true; - throw new ValidationException(this.getErrorBoxTitle(), this.getErrorBoxText()); - - } - return true; - } - - /** - * Validate that the passed in value is between the two values specified in the parameters - * - * @param value - * @return - */ - private boolean isBetween(Object value) { - String s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond), "="); - String s2 = StringTool.strip(FormulaParser.getExpressionString(secondCond), "="); - String formulaStr = "=and(" + value.toString() + ">" + s1 + "," + s2 + ">" + value.toString() + ")"; - try { - Formula f = FormulaParser.getFormulaFromString(formulaStr, this.getWorkBook().getWorkSheetByNumber(0), new int[]{1, 1}); - f.setCachedValue(null); - Object o = f.calculateFormula(); - if (o instanceof Boolean) { - return ((Boolean) o).booleanValue(); - } - } catch (Exception e) { - Logger.logErr("Error calculating formula in validation " + e.toString()); - } - return false; - } - - /** - * Validate that the passed in value is NOT between the two passed in values - * - * @param value - * @return - */ - private boolean isNotBetween(Object value) { - return !isBetween(value); - } - - /** - * Validate that the passed in value is equivalant - * - * @param value - * @return - */ - private boolean isEqual(Object value) { - String s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond), "="); - String formulaStr = "=(" + value.toString() + "=" + s1 + ")"; - try { - Formula f = FormulaParser.getFormulaFromString(formulaStr, this.getWorkBook().getWorkSheetByNumber(0), new int[]{1, 1}); - Object o = f.calculateFormula(); - if (o instanceof Boolean) { - return ((Boolean) o).booleanValue(); - } - } catch (Exception e) { - Logger.logErr("Error calculating formula in validation " + e.toString()); - } - return false; - } - - /** - * Validate that the passed in value is NOT between the two passed in values - * - * @param value - * @return - */ - private boolean isNotEqual(Object value) { - return !isEqual(value); - } - - /** - * Validate that the passed in value is greater than - * - * @param value - * @return - */ - private boolean isGreaterThan(Object value) { - String s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond), "="); - String formulaStr = "=(" + value.toString() + ">" + s1 + ")"; - try { - Formula f = FormulaParser.getFormulaFromString(formulaStr, this.getWorkBook().getWorkSheetByNumber(0), new int[]{1, 1}); - Object o = f.calculateFormula(); - if (o instanceof Boolean) { - return ((Boolean) o).booleanValue(); - } - } catch (Exception e) { - Logger.logErr("Error calculating formula in validation " + e.toString()); - } - return false; - } - - /** - * Validate that the passed in value is greater than - * - * @param value - * @return - */ - private boolean isGreaterOrEqual(Object value) { - String s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond), "="); - String formulaStr = "=(" + value.toString() + ">=" + s1 + ")"; - try { - Formula f = FormulaParser.getFormulaFromString(formulaStr, this.getWorkBook().getWorkSheetByNumber(0), new int[]{1, 1}); - Object o = f.calculateFormula(); - if (o instanceof Boolean) { - return ((Boolean) o).booleanValue(); - } - } catch (Exception e) { - Logger.logErr("Error calculating formula in validation " + e.toString()); - } - return false; - } - - /** - * Validate that the passed in value is greater than - * - * @param value - * @return - */ - private boolean isLessThan(Object value) { - String s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond), "="); - String formulaStr = "=(" + value.toString() + "<" + s1 + ")"; - try { - Formula f = FormulaParser.getFormulaFromString(formulaStr, this.getWorkBook().getWorkSheetByNumber(0), new int[]{1, 1}); - Object o = f.calculateFormula(); - if (o instanceof Boolean) { - return ((Boolean) o).booleanValue(); - } - } catch (Exception e) { - Logger.logErr("Error calculating formula in validation " + e.toString()); - } - return false; - } - - /** - * Validate that the passed in value is greater than - * - * @param value - * @return - */ - private boolean isLessOrEqual(Object value) { - String s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond), "="); - String formulaStr = "=(" + value.toString() + "<=" + s1 + ")"; - try { - Formula f = FormulaParser.getFormulaFromString(formulaStr, this.getWorkBook().getWorkSheetByNumber(0), new int[]{1, 1}); - Object o = f.calculateFormula(); - if (o instanceof Boolean) { - return ((Boolean) o).booleanValue(); - } - } catch (Exception e) { - Logger.logErr("Error calculating formula in validation " + e.toString()); - } - return false; - } - - - /** - * Determines if the value passed in is of the - * correct data type - * - * @return - */ - public boolean isCorrectDataType(Object value) { - switch (valType) { - case ValidationHandle.VALUE_ANY: - return true; - case ValidationHandle.VALUE_DATE: - if (value instanceof Date) return true; - if (value instanceof Calendar) return true; - break; - case ValidationHandle.VALUE_DECIMAL: - String possibleDec = value.toString(); - try { - new Double(possibleDec); - return true; - } catch (NumberFormatException e) { - return false; - } - case ValidationHandle.VALUE_FORMULA: - String possibleFormula = value.toString(); - if (possibleFormula.indexOf("=") == 0) return true; - break; - case ValidationHandle.VALUE_INTEGER: - String possibleInt = value.toString(); - if (possibleInt.indexOf(".") > -1) return false; - try { - Long l = new Long(possibleInt); // excel ints go past boundary of java ints, so use long - return true; - } catch (NumberFormatException e) { - return false; - } - case ValidationHandle.VALUE_TEXT_LENGTH: - return true; //TODO - case ValidationHandle.VALUE_TIME: - return true; // TODO - case ValidationHandle.VALUE_USER_DEFINED_LIST: - return true; // TODO - } - return false; - } - - - /** - * Create a dv record & populate with prototype bytes - * - * @return - */ - protected static XLSRecord getPrototype(WorkBook bk) { - Dv dv = new Dv(); - dv.setOpcode(DV); - dv.setData(dv.PROTOTYPE_BYTES); - dv.setWorkBook(bk); - dv.init(); - return dv; - } - - /** - * Standard init method - * - * @see io.starter.formats.XLS.XLSRecord#init() - */ - public void init() { - super.init(); - - int offset = 0; - grbit = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)); - short strLen = ByteTools.readShort(this.getByteAt(offset), this.getByteAt(offset + 1)); - byte strGrbit = this.getByteAt(offset + 2); - if ((strGrbit & 0x1) == 0x1) { - strLen *= 2; - } - strLen += 3; - byte[] namebytes = this.getBytesAt(offset, strLen); - offset += strLen; - dTitlePrompt = new Unicodestring(); - dTitlePrompt.init(namebytes, false); - - strLen = ByteTools.readShort(this.getByteAt(offset), this.getByteAt(offset + 1)); - strGrbit = this.getByteAt(offset + 2); - if ((strGrbit & 0x1) == 0x1) { - strLen *= 2; - } - strLen += 3; - - namebytes = this.getBytesAt(offset, strLen); - offset += strLen; - dTitleError = new Unicodestring(); - dTitleError.init(namebytes, false); - - strLen = ByteTools.readShort(this.getByteAt(offset), this.getByteAt(offset + 1)); - strGrbit = this.getByteAt(offset + 2); - if ((strGrbit & 0x1) == 0x1) { - strLen *= 2; - } - strLen += 3; - namebytes = this.getBytesAt(offset, strLen); - offset += strLen; - dTextPrompt = new Unicodestring(); - dTextPrompt.init(namebytes, false); - - strLen = ByteTools.readShort(this.getByteAt(offset), this.getByteAt(offset + 1)); - strGrbit = this.getByteAt(offset + 2); - if ((strGrbit & 0x1) == 0x1) { - strLen *= 2; - } - strLen += 3; - namebytes = this.getBytesAt(offset, strLen); - offset += strLen; - dTextError = new Unicodestring(); - dTextError.init(namebytes, false); - - int sz1 = ByteTools.readShort(this.getByteAt(offset++), this.getByteAt(offset++)); - // unknown bytes - garbageByteOne[0] = this.getByteAt(offset++); - garbageByteOne[1] = this.getByteAt(offset++); - byte[] formulaBytes = this.getBytesAt(offset, sz1); - firstCond = ExpressionParser.parseExpression(formulaBytes, this); - offset += sz1; - - int sz2 = ByteTools.readShort(this.getByteAt(offset++), this.getByteAt(offset++)); - // unknown bytes - garbageByteTwo[0] = this.getByteAt(offset++); - garbageByteTwo[1] = this.getByteAt(offset++); - formulaBytes = this.getBytesAt(offset, sz2); - secondCond = ExpressionParser.parseExpression(formulaBytes, this); - offset += sz2; - - - numLocs = this.getByteAt(offset++); - cRangeList = new ArrayList(); - for (int i = 0; i < numLocs; i++) { - byte[] b = new byte[1]; - b[0] = 0x0; - b = ByteTools.append(b, this.getBytesAt(offset, 8)); - PtgArea p = new PtgArea(false); - p.setParentRec(this); - p.init(b); - cRangeList.add(p); - offset += 8; - } - - // set all the grbit fields - valType = (byte) ((grbit & BITMASK_VALTYPE)); - errStyle = (byte) ((grbit & BITMASK_ERRSTYLE) >> 4); - IMEMode = (short) ((grbit & BITMASK_MDIMEMODE) >> 10); - fStrLookup = ((grbit & BITMASK_FSTRLOOKUP) == BITMASK_FSTRLOOKUP); - fAllowBlank = ((grbit & BITMASK_FALLOWBLANK) == BITMASK_FALLOWBLANK); - fSuppressCombo = ((grbit & BITMASK_FSUPRESSCOMBO) == BITMASK_FSUPRESSCOMBO); - fShowInputMsg = ((BITMASK_FSHOWINPUTMSG) == BITMASK_FSHOWINPUTMSG); - fShowErrMsg = ((grbit & BITMASK_FSHOWERRORMSG) == BITMASK_FSHOWERRORMSG); - typOperator = (byte) ((grbit & BITMASK_TYPOPERATOR) >> 20); - } - - /** - * As most of these records have variable lengths we cannot just update part of - * the data for the record at a time, we just don't want to keep up updates. Rather than this, - * we'll update the entire record. To keep processing down, update the record before streaming - * rather than on each internal record change. - */ - private void updateRecord() { - this.updateGrbit(); - byte[] recbytes = new byte[0]; - - byte[] tmp = ByteTools.cLongToLEBytes(grbit); - recbytes = ByteTools.append(tmp, recbytes); - recbytes = ByteTools.append(dTitlePrompt.read(), recbytes); - recbytes = ByteTools.append(dTitleError.read(), recbytes); - recbytes = ByteTools.append(dTextPrompt.read(), recbytes); - recbytes = ByteTools.append(dTextError.read(), recbytes); - - // get the firstCond bytes - tmp = new byte[0]; - for (int i = 0; i < firstCond.size(); i++) { - Object o = firstCond.elementAt(i); - Ptg ptg = (Ptg) o; - tmp = ByteTools.append(ptg.getRecord(), tmp); - } - // get the length and add in. - short sz = (short) tmp.length; - recbytes = ByteTools.append(ByteTools.shortToLEBytes(sz), recbytes); - // add garbage - recbytes = ByteTools.append(garbageByteOne, recbytes); - recbytes = ByteTools.append(tmp, recbytes); - - // get the secondCond bytes - tmp = new byte[0]; - for (int i = 0; i < secondCond.size(); i++) { - Object o = secondCond.elementAt(i); - Ptg ptg = (Ptg) o; - tmp = ByteTools.append(ptg.getRecord(), tmp); - } - // get the length and add in. - sz = (short) tmp.length; - recbytes = ByteTools.append(ByteTools.shortToLEBytes(sz), recbytes); - // add garbage - recbytes = ByteTools.append(garbageByteTwo, recbytes); - recbytes = ByteTools.append(tmp, recbytes); - - tmp = new byte[1]; - if (cRangeList != null) { - tmp[0] = (byte) cRangeList.size(); - recbytes = ByteTools.append(tmp, recbytes); - for (int i = 0; i < cRangeList.size(); i++) { - tmp = ((PtgArea) cRangeList.get(i)).getRecord(); - byte[] tmp2 = new byte[8]; - tmp[0] = 0; - System.arraycopy(tmp, 0, tmp2, 0, tmp2.length); - recbytes = ByteTools.append(tmp2, recbytes); - } - // there is a trailing zero, not sure why... - if (cRangeList.size() > 0) { - tmp = new byte[1]; - tmp[0] = 0; - recbytes = ByteTools.append(tmp, recbytes); - } - } else if (ooxmlranges != null && ooxmlranges.length > 0) { - tmp[0] = (byte) ooxmlranges.length; - recbytes = ByteTools.append(tmp, recbytes); - for (int i = 0; i < ooxmlranges.length; i++) { - Ptg/*Ref*/ p = PtgRef.createPtgRefFromString(this.getSheet().getSheetName() + "!" + ooxmlranges[i], this); - tmp = p.getRecord(); - /* replace with above PtgArea pa= new PtgArea(); - try { - pa.setParentRec(this); - pa.setLocation(this.getSheet().getSheetName() + "!" + ooxmlranges[i]); - } catch (Exception e) { - // TODO: handle MAXROWS/MAXCOLS - } - tmp = pa.getRecord(); - /**/ - tmp[0] = 0; - byte[] tmp2 = new byte[8]; - System.arraycopy(tmp, 0, tmp2, 0, tmp2.length); - recbytes = ByteTools.append(tmp, recbytes); - } - // there is a trailing zero, not sure why... - if (ooxmlranges.length > 0) { - tmp = new byte[1]; - tmp[0] = 0; - recbytes = ByteTools.append(tmp, recbytes); - } - } - - this.setData(recbytes); - } - - - /** - * update record. - * - * @see io.starter.formats.XLS.XLSRecord#preStream() - */ - public void preStream() { - if (dirtyflag) this.updateRecord(); - } - - /** - * Apply all the grbit fields into the current grbit int - */ - public void updateGrbit() { - grbit = 0; - grbit |= valType; - grbit |= (errStyle << 4); - grbit |= (IMEMode << 10); - if (fStrLookup) - grbit = (grbit | BITMASK_FSTRLOOKUP); - - if (fAllowBlank) - grbit = (grbit | BITMASK_FALLOWBLANK); - - if (fSuppressCombo) - grbit = (grbit | BITMASK_FSUPRESSCOMBO); - - if (fShowInputMsg) - grbit = (grbit | BITMASK_FSHOWINPUTMSG); - - if (fShowErrMsg) - grbit = (grbit | BITMASK_FSHOWERRORMSG); - - grbit |= (typOperator << 20); - } - - /** - * Return the range of data this Dv refers to as a string array - *

                  - * Values are stored as absolute ($) references, but should be displayed - * as relative - * - * @return ptgRef.toString() - */ - public String[] getRanges() { - if (cRangeList == null && - ooxmlranges != null) { - if (ooxmlranges.length > 0) - return ooxmlranges; - } - String[] s = new String[cRangeList.size()]; - for (int i = 0; i < s.length; i++) { - s[i] = ((PtgArea) cRangeList.get(i)).getLocation(); - s[i] = StringTool.strip(s[i], "$"); - } - return s; - - } - - - /** - * Set the range this Dv refers to. Pass in a range string, sans worksheet - * Note that absolute ranges/ptrgrefs are always used, however returning - * values should not include the dollar sign - * - * @param range - */ - public void setRange(String range) { - if (range == null) { // for creating a dv and adding range info later - cRangeList = null; - return; - } - if (range.indexOf(":") == -1) range = range + ":" + range; - PtgArea p = new PtgArea(range, this, false); - cRangeList = new ArrayList(); - cRangeList.add(p); - dirtyflag = true; - } - - /** - * Add a range this Dv refers to. Pass in a range string, sans worksheet - * - * @param range - */ - public void addRange(String range) { - if (cRangeList == null) cRangeList = new ArrayList(); // 20090605 KSC: Added - /*int[] i = ExcelTools.getRowColFromString(range); - if(i.length==2) { - - }else {*/ - PtgArea p = new PtgArea(range, this, false); // 20090609 KSC: absolute refs if '$' -really should test if row or col - cRangeList.add(p); - dirtyflag = true; - } - - /** - * Add a range this Dv refers to. Pass in a range string, sans worksheet - * May need additional handling for records outside bounds? - * - * @param range - */ - public void addOoxmlRange(String range) { - if (cRangeList == null) cRangeList = new ArrayList(); // 20090605 KSC: Added - PtgArea p = new PtgArea(range, this, (range != null) && (range.indexOf('$') == -1)); // 20090609 KSC: absolute refs if '$' -really should test if row or col -// p.setUseReferenceTracker(false); - cRangeList.add(p); - dirtyflag = true; - } - - /** - * Return the text in the error box - * - * @return - */ - public String getErrorBoxText() { - return dTextError.toString().trim(); - } - - /** - * Set the text for the error box - * - * @param textError - */ - public void setErrorBoxText(String textError) { - dTextError.updateUnicodeString(textError); - dirtyflag = true; - } - - /** - * Return the text in the prompt box - * - * @return - */ - public String getPromptBoxText() { - return dTextPrompt.toString().trim(); - } - - /** - * Set the text for the prompt box - * - * @param text - */ - public void setPromptBoxText(String text) { - dTextPrompt.updateUnicodeString(text); - dirtyflag = true; - } - - /** - * Set the title for the error box - * - * @param textError - */ - public void setErrorBoxTitle(String textError) { - dTitleError.updateUnicodeString(textError); - dirtyflag = true; - } - - /** - * Return the title from the error box - * - * @return - */ - public String getErrorBoxTitle() { - return dTitleError.toString().trim(); - } - - /** - * Return the title in the prompt box - * - * @return - */ - public String getPromptBoxTitle() { - return dTitlePrompt.toString().trim(); - } - - /** - * Set the title for the prompt box - * - * @param text - */ - public void setPromptBoxTitle(String text) { - dTitlePrompt.updateUnicodeString(text); - dirtyflag = true; - } - - - /** - * Return a byte representing the error style for this DV - *

                  - * These map to the static final ints ERROR_* from ValidationHandle - * - * @return - */ - public byte getErrorStyle() { - return errStyle; - } - - /** - * Set the error style for this Dv record - *

                  - * These map to the static final ints ERROR_* from ValidationHandle - * - * @return - */ - public void setErrorStyle(byte errstyle) { - errStyle = errstyle; - dirtyflag = true; - - } - - /** - * Allow blank cells in the validation area? - * - * @return - */ - public boolean isAllowBlank() { - return fAllowBlank; - } - - /** - * Allow blank cells in the validation area? - * - * @return - */ - public void setAllowBlank(boolean allowBlank) { - fAllowBlank = allowBlank; - updateGrbit(); - dirtyflag = true; - } - - /** - * Get the first condition of the validation as - * a string representation - * - * @return - */ - public String getFirstCond() { - String s = FormulaParser.getExpressionString(firstCond); - if (s.substring(0, 1).equals("=")) return s.substring(1); - return s; - } - - /** - * Set the first condition of the validation utilizing - * a string. This value must conform to the Value Type of this - * validation or unexpected results may occur. For example, - * entering a string representation of a date here will not work - * if your validation is an integer... - *

                  - * String passed in should be a vaild XLS formula. Does not need to include the "=" - *

                  - * Types of conditions - * Integer values - * Decimal values - * User defined list - * Date - * Time - * Text length - * Formula - * - * @return - */ - public void setFirstCond(String firstCond) { - this.firstCond = FormulaParser.getPtgsFromFormulaString(this, firstCond); - dirtyflag = true; - } - - - /** - * Get the second condition of the validation as - * a string representation - * - * @return - */ - public String getSecondCond() { - String s = FormulaParser.getExpressionString(secondCond); - if (s.substring(0, 1).equals("=")) return s.substring(1); - return s; - } - - /** - * Set the first condition of the validation utilizing - * a string. This value must conform to the Value Type of this - * validation or unexpected results may occur. For example, - * entering a string representation of a date here will not work - * if your validation is an integer... - *

                  - * String passed in should be a vaild XLS formula. Does not need to include the "=" - *

                  - * Types of conditions - * Integer values - * Decimal values - * User defined list - * Date - * Time - * Text length - * Formula - * - * @return - */ - public void setSecondCond(String secondCond) { - this.secondCond = FormulaParser.getPtgsFromFormulaString(this, secondCond); - dirtyflag = true; - } - - /** - * Show error box if invalid values entered? - * - * @return - */ - public boolean isShowErrorMsg() { - return fShowErrMsg; - } - - /** - * set show error box if invalid values entered? - * - * @return - */ - public void setShowErrMsg(boolean showErrMsg) { - fShowErrMsg = showErrMsg; - dirtyflag = true; - } - - /** - * Show prompt box if cell selected? - * - * @return - */ - public boolean getShowInputMsg() { - return fShowInputMsg; - } - - /** - * Set show prompt box if cell selected? - * * - * - * @return - */ - public void setShowInputMsg(boolean showInputMsg) { - fShowInputMsg = showInputMsg; - dirtyflag = true; - } - - /** - * In list type validity the string list is explicitly given in the formula - * - * @return - */ - public boolean isStrLookup() { - return fStrLookup; - } - - /** - * In list type validity the string list is explicitly given in the formula - * - * @return - */ - public void setStrLookup(boolean strLookup) { - fStrLookup = strLookup; - dirtyflag = true; - } - - /** - * Suppress the drop down arrow in list type validity - * - * @return - */ - public boolean isSuppressCombo() { - return fSuppressCombo; - } - - /** - * Get the IME mode for this validation - * - * @return - */ - public short getIMEMode() { - return IMEMode; - } - - /** - * set the IME mode for this validation - * - * @return - */ - public void setIMEMode(short mode) { - IMEMode = mode; - dirtyflag = true; - } - - /** - * Suppress the drop down arrow in list type validity - * - * @return - */ - public void setSuppressCombo(boolean suppressCombo) { - fSuppressCombo = suppressCombo; - dirtyflag = true; - } - - /** - * Get the type operator of this validation as a byte. - *

                  - * These bytes map to the CONDITION_* static values in - * ValidationHandle - * - * @return - */ - public byte getTypeOperator() { - return typOperator; - } - - /** - * set the type operator of this validation as a byte. - *

                  - * These bytes map to the CONDITION_* static values in - * ValidationHandle - * - * @return - */ - public void setTypeOperator(byte typOperator) { - this.typOperator = typOperator; - dirtyflag = true; - } - - /** - * Get the validation type of this Dv as a byte - *

                  - * These bytes map to the VALUE_* static values in - * ValidationHandle - * - * @return - */ - public byte getValType() { - return valType; - } - - /** - * Set the validation type of this Dv as a byte - *

                  - * These bytes map to the VALUE_* static values in - * ValidationHandle - * - * @return - */ - public void setValType(byte valtype) { - valType = valtype; - dirtyflag = true; - } - - /** - * Determines if the Dv contains the cell address passed in - * - * @param range - * @return - */ - public boolean isInRange(String celladdy) { - // FIX broken COLROW - int[] rc = ExcelTools.getRowColFromString(celladdy); - for (int i = 0; i < cRangeList.size(); i++) { - if (((PtgArea) cRangeList.get(i)).contains(rc)) return true; - } - return false; - } - /** - * OOXML Element: - * dataValidation (Data Validation) - * A single item of data validation defined on a range of the worksheet - * - * parent: dataValidations (==Dval) - * children: formula1, formula2 - * attributes: many - */ - - /** - * create a new Dv record based on OOXML input - */ - public static Dv parseOOXML(XmlPullParser xpp, Boundsheet bs) { - Dv dv = bs.createDv(null); - dv.setSheet(bs); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("dataValidation")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (n.equals("allowBlank")) { - dv.setAllowBlank(true); - } else if (n.equals("error")) { - dv.setErrorBoxText(v); - } else if (n.equals("errorStyle")) { // default= stop - if (v.equals("information")) - dv.setErrorStyle((byte) 2); - else if (v.equals("stop")) - dv.setErrorStyle((byte) 0); - else if (v.equals("warning")) - dv.setErrorStyle((byte) 1); - } else if (n.equals("errorTitle")) { - dv.setErrorBoxTitle(v); - } else if (n.equals("imeMode")) { // TODO: what is the correct mapping?????????????????????? - if (v.equals("nocontrol")) { - dv.setIMEMode((short) 0); - } else if (v.equals("off")) { - dv.setIMEMode((short) 1); - } else if (v.equals("on")) { - dv.setIMEMode((short) 2); - } else if (v.equals("disabled")) { - dv.setIMEMode((short) 3); - } else if (v.equals("hiragana")) { - dv.setIMEMode((short) 4); - } else if (v.equals("fullKatakana")) { - dv.setIMEMode((short) 5); - } else if (v.equals("halfKatakana")) { - dv.setIMEMode((short) 6); - } else if (v.equals("fullAlpha")) { - dv.setIMEMode((short) 7); - } else if (v.equals("halfAlpha")) { - dv.setIMEMode((short) 8); - } else if (v.equals("fullHangul")) { - dv.setIMEMode((short) 9); - } else if (v.equals("halfHangul")) { - dv.setIMEMode((short) 10); - } - } else if (n.equals("operator")) { // default= "between" - if (v.equals("between")) - dv.setTypeOperator((byte) 0); - else if (v.equals("equal")) - dv.setTypeOperator((byte) 2); - else if (v.equals("greaterThan")) - dv.setTypeOperator((byte) 4); - else if (v.equals("greaterThanOrEqual")) - dv.setTypeOperator((byte) 6); - else if (v.equals("lessThan")) - dv.setTypeOperator((byte) 5); - else if (v.equals("lessThanOrEqual")) - dv.setTypeOperator((byte) 7); - else if (v.equals("notBetween")) - dv.setTypeOperator((byte) 1); - else if (v.equals("notEqual")) - dv.setTypeOperator((byte) 3); - } else if (n.equals("prompt")) { - dv.setPromptBoxText(v); - } else if (n.equals("promptTitle")) { - dv.setPromptBoxTitle(v); - } else if (n.equals("showDropDown")) { -// - } else if (n.equals("showErrorMessage")) { - dv.setShowErrMsg(true); - } else if (n.equals("showInputMessage")) { - dv.setShowInputMsg(true); - } else if (n.equals("sqref")) { - dv.ooxmlranges = StringTool.splitString(v, " "); - // 20090609 KSC: cannot add ranges in 2003 format as 2007 addresses can exceed 2003 limits - for (int z = 0; z < dv.ooxmlranges.length; z++) - dv.addOoxmlRange(dv.ooxmlranges[z]); - } else if (n.equals("type")) { // required - if (v.equals("custom")) // custom formula - dv.setValType((byte) 7); - else if (v.equals("date")) - dv.setValType((byte) 4); - else if (v.equals("decimal")) - dv.setValType((byte) 2); - else if (v.equals("list")) - dv.setValType((byte) 3); - else if (v.equals("none")) - dv.setValType((byte) 0); - else if (v.equals("textLength")) - dv.setValType((byte) 6); - else if (v.equals("time")) - dv.setValType((byte) 5); - else if (v.equals("whole")) - dv.setValType((byte) 1); - } - } - } else if (tnm.equals("formula1")) { - dv.setFirstCond(OOXMLAdapter.getNextText(xpp)); - } else if (tnm.equals("formula2")) { - dv.setSecondCond(OOXMLAdapter.getNextText(xpp)); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("dataValidation")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OOXMLELEMENT.parseOOXML: " + e.toString()); - } - return dv; - } - - /** - * generate the proper OOXML to define this Dv - * - * @return - */ - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append("0) ooxml.append(" "); - ooxml.append(ooxmlranges[i]); - } - ooxml.append("\""); - } else { // 2003-style ranges - **/ - String[] ranges = this.getRanges(); - if (ranges.length > 0) { - ooxml.append(" sqref=\""); - for (int i = 0; i < ranges.length; i++) { - if (i > 0) ooxml.append(" "); - ooxml.append(ranges[i]); - } - ooxml.append("\""); - } - //} - - if (this.isAllowBlank()) ooxml.append(" allowBlank=\"1\""); - if (this.isShowErrorMsg()) ooxml.append(" showErrorMessage=\"1\""); - if (this.getShowInputMsg()) ooxml.append(" showInputMessage=\"1\""); -/* - "showDropDown" -*/ - /** - * imwMode - * TODO: map options correctly!! where is the info?? - */ - switch (this.getIMEMode()) { - case 0: // nocontrol - break; - case 1: - ooxml.append(" imeMode=\"off\""); - break; - case 2: - ooxml.append(" imeMode=\"on\""); - break; - case 3: - ooxml.append(" imeMode=\"disabled\""); - break; - case 4: - ooxml.append(" imeMode=\"hiragana\""); - break; - case 5: - ooxml.append(" imeMode=\"fullKatakana\""); - break; - case 6: - ooxml.append(" imeMode=\"halfKatakana\""); - break; - case 7: - ooxml.append(" imeMode=\"fullAlpha\""); - break; - case 8: - ooxml.append(" imeMode=\"halfAlpha\""); - break; - case 9: - ooxml.append(" imeMode=\"fullHangul\""); - break; - case 10: - ooxml.append(" imeMode=\"halfHangul\""); - break; - } - ooxml.append(">"); - String formula1 = this.getFirstCond(); - if (formula1 != null && formula1.length() > 0) { - formula1 = formula1.replace((char) 0, ','); // DV Lists are delimited by 0 must replace with commas for OOXML use - ooxml.append("" + formula1 + ""); - } - String formula2 = this.getSecondCond(); - if (formula2 != null && formula2.length() > 0) { - formula2 = formula2.replace((char) 0, ','); // DV Lists are delimited by 0 must replace with commas for OOXML use - ooxml.append("" + formula2 + ""); - } - ooxml.append(""); - return ooxml.toString(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/Dv.kt b/src/main/java/io/starter/formats/XLS/Dv.kt new file mode 100644 index 0000000..ec69c64 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Dv.kt @@ -0,0 +1,1384 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.DateConverter +import io.starter.OpenXLS.ExcelTools +import io.starter.OpenXLS.ValidationHandle +import io.starter.formats.XLS.formulas.FormulaParser +import io.starter.formats.XLS.formulas.Ptg +import io.starter.formats.XLS.formulas.PtgArea +import io.starter.formats.XLS.formulas.PtgRef +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList +import java.util.Calendar +import java.util.Date +import java.util.Stack + + +/** + * **Dv: Data Validity Settings (01BEh)**

                  + * + * + * This record is part of the Data Validity Table. It stores data validity settings and a list of cell ranges which + * contain these settings. The prompt box appears while editing such a cell. The error box appears, if the entered value + * does not fit the conditions. The data validity settings of a sheet are stored in a sequential list of DV records. This list is + * preluded by an DVAL record. If a string is empty and the default text should appear in the prompt box or error + * box, the string must contain a single zero character (string length will be 1). + * + *

                  + *
                  + *
                  + * Offset          Name              Size                Contents
                  + * --------------------------------------------------------
                  + * 0               dwDvFlags      4                   Option flags (see below)
                  + * 4               dTitlePrompt   var                 Title of the prompt box (Unicode string, 16-bit string length)
                  + * var.            dTitleError      var.                Title of the error box (Unicode string, 16-bit string length)
                  + * var.            dTextPrompt  var.                Text of the prompt box (Unicode string, 16-bit string length)
                  + * var.            dTextError      var.                Text of the error box (Unicode string, 16-bit string length)
                  + * var.            sz1                 2                    Size of the formula data for first condition (sz1)
                  + * var.            garbage          2                   Not used
                  + * var.            firstCond       sz1                Formula data for first condition (RPN token array without size field)
                  + * var.            sz2                 2                   Size of the formula data for second condition (sz2)
                  + * var.            garbage           2                   Not used
                  + * var.            secondCond sz2                  Formula data for second condition (RPN token array without size field)
                  + * var.            cRangeList    var.                 Cell range address list with all affected ranges
                  + *
                  + * Option flags field:
                  + * Bit             Mask                    Name                 Contents
                  + * --
                  + * 3-0         0000000FH           ValType             Data type: 00H = Any value
                  + * 01H = Integer values
                  + * 02H = Decimal values
                  + * 03H = User defined list
                  + * 04H = Date
                  + * 05H = Time
                  + * 06H = Text length
                  + * 07H = Formula
                  + * 6-4     00000070H               ErrStyle               Error style: 00H = Stop
                  + * 01H = Warning
                  + * 02H = Info
                  + * 7         00000080H              fStrLookup         1 = In list type validity the string list is explicitly given in the formula
                  + * 8         00000100H            fAllowBlank         1 = Empty cells allowed
                  + * 9         00000200H             fSuppressCombo 1 = Suppress the drop down arrow in list type validity
                  + * 18      00040000H             fShowInputMsg     1 = Show prompt box if cell selected
                  + * 19      00080000H             fShowErrorMsg      1 = Show error box if invalid values entered
                  + * 23-20 00F00000H             typOperator         Condition operator: 00H = Between
                  + * 01H = Not between
                  + * 02H = Equal
                  + * 03H = Not equal
                  + * 04H = Greater than
                  + * 05H = Less than
                  + * 06H = Greater or equal
                  + * 07H = Less or equal
                  + *
                  +
                  * + * In list type validity it is possible to enter an explicit string list. This string list is stored as tStr token . The string + * items are separated by zero characters. There is no zero character at the end of the string list. + * Example for a string list with the 3 strings A, B, and C: A<00H>B<00H>C (contained in a tStr token, string + * length is 5). + */ +class Dv : io.starter.formats.XLS.XLSRecord() { + private var grbit: Int = 0 + private var dTitlePrompt: Unicodestring? = null + private var dTitleError: Unicodestring? = null + private var dTextPrompt: Unicodestring? = null + private var dTextError: Unicodestring? = null + private var firstCond: Stack<*>? = null + private var secondCond: Stack<*>? = null + private var cRangeList: ArrayList<*>? = null + private val garbageByteOne = ByteArray(2) + private val garbageByteTwo = ByteArray(2) + internal var numLocs: Byte = 0 + //private byte[] garbageByteThree = new byte[1]; + // grbit (dwDvFlags) fields + private var valType: Byte = 0 + private var errStyle: Byte = 0 + private var fStrLookup: Boolean = false + private var fAllowBlank: Boolean = false + private var fSuppressCombo: Boolean = false + private var fShowInputMsg: Boolean = false + /** + * Show error box if invalid values entered? + * + * @return + */ + var isShowErrorMsg: Boolean = false + private set + private var IMEMode: Short = 0 + private var typOperator: Byte = 0 + private var dirtyflag = false + + // 20090606: made prototype completely blank i.e. no prompt, error text or formulas ... + // private byte[] PROTOTYPE_BYTES = {0, 1, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // 0, 0, 89, 84, 0 };//, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + private val PROTOTYPE_BYTES = byteArrayOf(3, 1, 12, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0) + + // 20090609 KSC: need to store ranges separately as OOXML ranges addresses may exceed 2003 maximum size + private var ooxmlranges: Array? = null + + /** + * Return the range of data this Dv refers to as a string array + * + * + * Values are stored as absolute ($) references, but should be displayed + * as relative + * + * @return ptgRef.toString() + */ + val ranges: Array + get() { + if (cRangeList == null && ooxmlranges != null) { + if (ooxmlranges!!.size > 0) + return ooxmlranges + } + val s = arrayOfNulls(cRangeList!!.size) + for (i in s.indices) { + s[i] = (cRangeList!![i] as PtgArea).location + s[i] = StringTool.strip(s[i], "$") + } + return s + + } + + /** + * Return the text in the error box + * + * @return + */ + /** + * Set the text for the error box + * + * @param textError + */ + var errorBoxText: String + get() = dTextError!!.toString().trim { it <= ' ' } + set(textError) { + dTextError!!.updateUnicodeString(textError) + dirtyflag = true + } + + /** + * Return the text in the prompt box + * + * @return + */ + /** + * Set the text for the prompt box + * + * @param text + */ + var promptBoxText: String + get() = dTextPrompt!!.toString().trim { it <= ' ' } + set(text) { + dTextPrompt!!.updateUnicodeString(text) + dirtyflag = true + } + + /** + * Return the title from the error box + * + * @return + */ + /** + * Set the title for the error box + * + * @param textError + */ + var errorBoxTitle: String + get() = dTitleError!!.toString().trim { it <= ' ' } + set(textError) { + dTitleError!!.updateUnicodeString(textError) + dirtyflag = true + } + + /** + * Return the title in the prompt box + * + * @return + */ + /** + * Set the title for the prompt box + * + * @param text + */ + var promptBoxTitle: String + get() = dTitlePrompt!!.toString().trim { it <= ' ' } + set(text) { + dTitlePrompt!!.updateUnicodeString(text) + dirtyflag = true + } + + + /** + * Return a byte representing the error style for this DV + * + * + * These map to the static final ints ERROR_* from ValidationHandle + * + * @return + */ + /** + * Set the error style for this Dv record + * + * + * These map to the static final ints ERROR_* from ValidationHandle + * + * @return + */ + var errorStyle: Byte + get() = errStyle + set(errstyle) { + errStyle = errstyle + dirtyflag = true + + } + + /** + * Allow blank cells in the validation area? + * + * @return + */ + /** + * Allow blank cells in the validation area? + * + * @return + */ + var isAllowBlank: Boolean + get() = fAllowBlank + set(allowBlank) { + fAllowBlank = allowBlank + updateGrbit() + dirtyflag = true + } + + /** + * Show prompt box if cell selected? + * + * @return + */ + /** + * Set show prompt box if cell selected? + * * + * + * @return + */ + var showInputMsg: Boolean + get() = fShowInputMsg + set(showInputMsg) { + fShowInputMsg = showInputMsg + dirtyflag = true + } + + /** + * In list type validity the string list is explicitly given in the formula + * + * @return + */ + /** + * In list type validity the string list is explicitly given in the formula + * + * @return + */ + var isStrLookup: Boolean + get() = fStrLookup + set(strLookup) { + fStrLookup = strLookup + dirtyflag = true + } + + /** + * Suppress the drop down arrow in list type validity + * + * @return + */ + /** + * Suppress the drop down arrow in list type validity + * + * @return + */ + var isSuppressCombo: Boolean + get() = fSuppressCombo + set(suppressCombo) { + fSuppressCombo = suppressCombo + dirtyflag = true + } + + /** + * Get the IME mode for this validation + * + * @return + */ + /** + * set the IME mode for this validation + * + * @return + */ + var imeMode: Short + get() = IMEMode + set(mode) { + IMEMode = mode + dirtyflag = true + } + + /** + * Get the type operator of this validation as a byte. + * + * + * These bytes map to the CONDITION_* static values in + * ValidationHandle + * + * @return + */ + /** + * set the type operator of this validation as a byte. + * + * + * These bytes map to the CONDITION_* static values in + * ValidationHandle + * + * @return + */ + var typeOperator: Byte + get() = typOperator + set(typOperator) { + this.typOperator = typOperator + dirtyflag = true + } + + /** + * generate the proper OOXML to define this Dv + * + * @return + */ + // required + // any ???? + // TODO: this maps to ??? + //ooxml.append(" type="); + // default, leave out + // ooxml.append(" operator=\"between\""); + // default no need to outut ooxml.append(" errorStyle=\"stop\""); + //TODO "imeMode" + // This needs to be better thought out, currently it breaks/strips all changes made to the model, as ranges + // are not automatically added to ooxml ranges. + /**if (ooxmlranges!=null) {// have stored OOXML ranges + * ooxml.append(" sqref=\""); + * for (int i= 0; i < ooxmlranges.length; i++) { + * if (i>0) ooxml.append(" "); + * ooxml.append(ooxmlranges[i]); + * } + * ooxml.append("\""); + * } else { // 2003-style ranges + *///} + /* + "showDropDown" +*/ + /** + * imwMode + * TODO: map options correctly!! where is the info?? + */// nocontrol + // DV Lists are delimited by 0 must replace with commas for OOXML use + // DV Lists are delimited by 0 must replace with commas for OOXML use + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append(" { + } + 1 -> ooxml.append(" type=\"whole\"") + 2 -> ooxml.append(" type=\"decimal\"") + 3 -> ooxml.append(" type=\"list\"") + 4 -> ooxml.append(" type=\"date\"") + 5 -> ooxml.append(" type=\"time\"") + 6 -> ooxml.append(" type=\"textLength\"") + 7 -> ooxml.append(" type=\"custom\"") + } + when (typOperator) { + 0 -> { + } + 1 -> ooxml.append(" operator=\"notBetween\"") + 2 -> ooxml.append(" operator=\"equal\"") + 3 -> ooxml.append(" operator=\"notEqual\"") + 4 -> ooxml.append(" operator=\"greaterThan\"") + 5 -> ooxml.append(" operator=\"lessThan\"") + 6 -> ooxml.append(" operator=\"greaterThanOrEqual\"") + 7 -> ooxml.append(" operator=\"lessThanOrEqual\"") + } + when (errStyle) { + 0 -> { + } + 1 -> ooxml.append(" errorStyle=\"warning\"") + 2 -> ooxml.append(" errorStyle=\"information\"") + } + if (this.errorBoxText != "") + ooxml.append(" error=\"" + OOXMLAdapter.stripNonAscii(this.errorBoxText) + "\"") + if (this.errorBoxTitle != "") + ooxml.append(" errorTitle=\"" + OOXMLAdapter.stripNonAscii(this.errorBoxTitle) + "\"") + if (this.promptBoxText != "") + ooxml.append(" prompt=\"" + OOXMLAdapter.stripNonAscii(this.promptBoxText) + "\"") + if (this.promptBoxTitle != "") + ooxml.append(" promptTitle=\"" + OOXMLAdapter.stripNonAscii(this.promptBoxTitle) + "\"") + val ranges = this.ranges + if (ranges.size > 0) { + ooxml.append(" sqref=\"") + for (i in ranges.indices) { + if (i > 0) ooxml.append(" ") + ooxml.append(ranges[i]) + } + ooxml.append("\"") + } + + if (this.isAllowBlank) ooxml.append(" allowBlank=\"1\"") + if (this.isShowErrorMsg) ooxml.append(" showErrorMessage=\"1\"") + if (this.showInputMsg) ooxml.append(" showInputMessage=\"1\"") + when (this.imeMode) { + 0 -> { + } + 1 -> ooxml.append(" imeMode=\"off\"") + 2 -> ooxml.append(" imeMode=\"on\"") + 3 -> ooxml.append(" imeMode=\"disabled\"") + 4 -> ooxml.append(" imeMode=\"hiragana\"") + 5 -> ooxml.append(" imeMode=\"fullKatakana\"") + 6 -> ooxml.append(" imeMode=\"halfKatakana\"") + 7 -> ooxml.append(" imeMode=\"fullAlpha\"") + 8 -> ooxml.append(" imeMode=\"halfAlpha\"") + 9 -> ooxml.append(" imeMode=\"fullHangul\"") + 10 -> ooxml.append(" imeMode=\"halfHangul\"") + } + ooxml.append(">") + var formula1: String? = this.getFirstCond() + if (formula1 != null && formula1.length > 0) { + formula1 = formula1.replace(0.toChar(), ',') + ooxml.append("$formula1") + } + var formula2: String? = this.getSecondCond() + if (formula2 != null && formula2.length > 0) { + formula2 = formula2.replace(0.toChar(), ',') + ooxml.append("$formula2") + } + ooxml.append("") + return ooxml.toString() + } + + /** + * Determine if the value passed in is valid for + * this validation + * + * @param value + * @return + */ + @Throws(ValidationException::class) + fun isValid(value: Any?): Boolean { + var value: Any? = value ?: throw ValidationException(this.errorBoxTitle, this.errorBoxText) + + // TODO: look into whether null is ever a valid value. for now we assume "no". + + if (!this.isCorrectDataType(value)) + throw ValidationException(this.errorBoxTitle, this.errorBoxText) + if (value is Date) { + val d = DateConverter.getXLSDateVal(value as java.util.Date?) + value = d.toString() + "" + } + when (typOperator) { + ValidationHandle.CONDITION_BETWEEN -> { + if (isBetween(value!!)) return true + throw ValidationException(this.errorBoxTitle, this.errorBoxText) + } + ValidationHandle.CONDITION_NOT_BETWEEN -> { + if (isNotBetween(value)) return true + throw ValidationException(this.errorBoxTitle, this.errorBoxText) + } + ValidationHandle.CONDITION_EQUAL -> { + if (isEqual(value!!)) return true + throw ValidationException(this.errorBoxTitle, this.errorBoxText) + } + ValidationHandle.CONDITION_GREATER_THAN -> { + if (isGreaterThan(value!!)) return true + throw ValidationException(this.errorBoxTitle, this.errorBoxText) + } + ValidationHandle.CONDITION_GREATER_OR_EQUAL -> { + if (isGreaterOrEqual(value!!)) return true + throw ValidationException(this.errorBoxTitle, this.errorBoxText) + } + ValidationHandle.CONDITION_LESS_OR_EQUAL -> { + if (isLessOrEqual(value!!)) return true + throw ValidationException(this.errorBoxTitle, this.errorBoxText) + } + + ValidationHandle.CONDITION_LESS_THAN -> { + if (isGreaterOrEqual(value!!)) return true + throw ValidationException(this.errorBoxTitle, this.errorBoxText) + } + + ValidationHandle.CONDITION_NOT_EQUAL -> { + if (isNotEqual(value)) return true + throw ValidationException(this.errorBoxTitle, this.errorBoxText) + } + } + return true + } + + /** + * Validate that the passed in value is between the two values specified in the parameters + * + * @param value + * @return + */ + private fun isBetween(value: Any): Boolean { + val s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond!!), "=") + val s2 = StringTool.strip(FormulaParser.getExpressionString(secondCond!!), "=") + val formulaStr = "=and($value>$s1,$s2>$value)" + try { + val f = FormulaParser.getFormulaFromString(formulaStr, this.workBook!!.getWorkSheetByNumber(0), intArrayOf(1, 1)) + f.setCachedValue(null) + val o = f.calculateFormula() + if (o is Boolean) { + return o.booleanValue() + } + } catch (e: Exception) { + Logger.logErr("Error calculating formula in validation $e") + } + + return false + } + + /** + * Validate that the passed in value is NOT between the two passed in values + * + * @param value + * @return + */ + private fun isNotBetween(value: Any): Boolean { + return !isBetween(value) + } + + /** + * Validate that the passed in value is equivalant + * + * @param value + * @return + */ + private fun isEqual(value: Any): Boolean { + val s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond!!), "=") + val formulaStr = "=($value=$s1)" + try { + val f = FormulaParser.getFormulaFromString(formulaStr, this.workBook!!.getWorkSheetByNumber(0), intArrayOf(1, 1)) + val o = f.calculateFormula() + if (o is Boolean) { + return o.booleanValue() + } + } catch (e: Exception) { + Logger.logErr("Error calculating formula in validation $e") + } + + return false + } + + /** + * Validate that the passed in value is NOT between the two passed in values + * + * @param value + * @return + */ + private fun isNotEqual(value: Any): Boolean { + return !isEqual(value) + } + + /** + * Validate that the passed in value is greater than + * + * @param value + * @return + */ + private fun isGreaterThan(value: Any): Boolean { + val s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond!!), "=") + val formulaStr = "=($value>$s1)" + try { + val f = FormulaParser.getFormulaFromString(formulaStr, this.workBook!!.getWorkSheetByNumber(0), intArrayOf(1, 1)) + val o = f.calculateFormula() + if (o is Boolean) { + return o.booleanValue() + } + } catch (e: Exception) { + Logger.logErr("Error calculating formula in validation $e") + } + + return false + } + + /** + * Validate that the passed in value is greater than + * + * @param value + * @return + */ + private fun isGreaterOrEqual(value: Any): Boolean { + val s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond!!), "=") + val formulaStr = "=($value>=$s1)" + try { + val f = FormulaParser.getFormulaFromString(formulaStr, this.workBook!!.getWorkSheetByNumber(0), intArrayOf(1, 1)) + val o = f.calculateFormula() + if (o is Boolean) { + return o.booleanValue() + } + } catch (e: Exception) { + Logger.logErr("Error calculating formula in validation $e") + } + + return false + } + + /** + * Validate that the passed in value is greater than + * + * @param value + * @return + */ + private fun isLessThan(value: Any): Boolean { + val s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond!!), "=") + val formulaStr = "=($value<$s1)" + try { + val f = FormulaParser.getFormulaFromString(formulaStr, this.workBook!!.getWorkSheetByNumber(0), intArrayOf(1, 1)) + val o = f.calculateFormula() + if (o is Boolean) { + return o.booleanValue() + } + } catch (e: Exception) { + Logger.logErr("Error calculating formula in validation $e") + } + + return false + } + + /** + * Validate that the passed in value is greater than + * + * @param value + * @return + */ + private fun isLessOrEqual(value: Any): Boolean { + val s1 = StringTool.strip(FormulaParser.getExpressionString(firstCond!!), "=") + val formulaStr = "=($value<=$s1)" + try { + val f = FormulaParser.getFormulaFromString(formulaStr, this.workBook!!.getWorkSheetByNumber(0), intArrayOf(1, 1)) + val o = f.calculateFormula() + if (o is Boolean) { + return o.booleanValue() + } + } catch (e: Exception) { + Logger.logErr("Error calculating formula in validation $e") + } + + return false + } + + + /** + * Determines if the value passed in is of the + * correct data type + * + * @return + */ + fun isCorrectDataType(value: Any): Boolean { + when (valType) { + ValidationHandle.VALUE_ANY -> return true + ValidationHandle.VALUE_DATE -> { + if (value is Date) return true + if (value is Calendar) return true + } + ValidationHandle.VALUE_DECIMAL -> { + val possibleDec = value.toString() + try { + Double(possibleDec) + return true + } catch (e: NumberFormatException) { + return false + } + + val possibleFormula = value.toString() + if (possibleFormula.indexOf("=") == 0) return true + } + ValidationHandle.VALUE_FORMULA -> { + val possibleFormula = value.toString() + if (possibleFormula.indexOf("=") == 0) return true + } + ValidationHandle.VALUE_INTEGER -> { + val possibleInt = value.toString() + if (possibleInt.indexOf(".") > -1) return false + try { + val l = Long(possibleInt) // excel ints go past boundary of java ints, so use long + return true + } catch (e: NumberFormatException) { + return false + } + + return true //TODO + } + ValidationHandle.VALUE_TEXT_LENGTH -> return true + ValidationHandle.VALUE_TIME -> return true // TODO + ValidationHandle.VALUE_USER_DEFINED_LIST -> return true // TODO + } + return false + } + + /** + * Standard init method + * + * @see io.starter.formats.XLS.XLSRecord.init + */ + override fun init() { + super.init() + + var offset = 0 + grbit = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)) + var strLen = ByteTools.readShort(this.getByteAt(offset).toInt(), this.getByteAt(offset + 1).toInt()) + var strGrbit = this.getByteAt(offset + 2) + if (strGrbit and 0x1 == 0x1) { + strLen *= 2 + } + strLen += 3 + var namebytes = this.getBytesAt(offset, strLen.toInt()) + offset += strLen.toInt() + dTitlePrompt = Unicodestring() + dTitlePrompt!!.init(namebytes!!, false) + + strLen = ByteTools.readShort(this.getByteAt(offset).toInt(), this.getByteAt(offset + 1).toInt()) + strGrbit = this.getByteAt(offset + 2) + if (strGrbit and 0x1 == 0x1) { + strLen *= 2 + } + strLen += 3 + + namebytes = this.getBytesAt(offset, strLen.toInt()) + offset += strLen.toInt() + dTitleError = Unicodestring() + dTitleError!!.init(namebytes!!, false) + + strLen = ByteTools.readShort(this.getByteAt(offset).toInt(), this.getByteAt(offset + 1).toInt()) + strGrbit = this.getByteAt(offset + 2) + if (strGrbit and 0x1 == 0x1) { + strLen *= 2 + } + strLen += 3 + namebytes = this.getBytesAt(offset, strLen.toInt()) + offset += strLen.toInt() + dTextPrompt = Unicodestring() + dTextPrompt!!.init(namebytes!!, false) + + strLen = ByteTools.readShort(this.getByteAt(offset).toInt(), this.getByteAt(offset + 1).toInt()) + strGrbit = this.getByteAt(offset + 2) + if (strGrbit and 0x1 == 0x1) { + strLen *= 2 + } + strLen += 3 + namebytes = this.getBytesAt(offset, strLen.toInt()) + offset += strLen.toInt() + dTextError = Unicodestring() + dTextError!!.init(namebytes!!, false) + + val sz1 = ByteTools.readShort(this.getByteAt(offset++).toInt(), this.getByteAt(offset++).toInt()).toInt() + // unknown bytes + garbageByteOne[0] = this.getByteAt(offset++) + garbageByteOne[1] = this.getByteAt(offset++) + var formulaBytes = this.getBytesAt(offset, sz1) + firstCond = ExpressionParser.parseExpression(formulaBytes, this) + offset += sz1 + + val sz2 = ByteTools.readShort(this.getByteAt(offset++).toInt(), this.getByteAt(offset++).toInt()).toInt() + // unknown bytes + garbageByteTwo[0] = this.getByteAt(offset++) + garbageByteTwo[1] = this.getByteAt(offset++) + formulaBytes = this.getBytesAt(offset, sz2) + secondCond = ExpressionParser.parseExpression(formulaBytes, this) + offset += sz2 + + + numLocs = this.getByteAt(offset++) + cRangeList = ArrayList() + for (i in 0 until numLocs) { + var b = ByteArray(1) + b[0] = 0x0 + b = ByteTools.append(b, this.getBytesAt(offset, 8)) + val p = PtgArea(false) + p.parentRec = this + p.init(b) + cRangeList!!.add(p) + offset += 8 + } + + // set all the grbit fields + valType = (grbit and BITMASK_VALTYPE).toByte() + errStyle = (grbit and BITMASK_ERRSTYLE shr 4).toByte() + IMEMode = (grbit and BITMASK_MDIMEMODE shr 10).toShort() + fStrLookup = grbit and BITMASK_FSTRLOOKUP == BITMASK_FSTRLOOKUP + fAllowBlank = grbit and BITMASK_FALLOWBLANK == BITMASK_FALLOWBLANK + fSuppressCombo = grbit and BITMASK_FSUPRESSCOMBO == BITMASK_FSUPRESSCOMBO + fShowInputMsg = BITMASK_FSHOWINPUTMSG == BITMASK_FSHOWINPUTMSG + isShowErrorMsg = grbit and BITMASK_FSHOWERRORMSG == BITMASK_FSHOWERRORMSG + typOperator = (grbit and BITMASK_TYPOPERATOR shr 20).toByte() + } + + /** + * As most of these records have variable lengths we cannot just update part of + * the data for the record at a time, we just don't want to keep up updates. Rather than this, + * we'll update the entire record. To keep processing down, update the record before streaming + * rather than on each internal record change. + */ + private fun updateRecord() { + this.updateGrbit() + var recbytes = ByteArray(0) + + var tmp = ByteTools.cLongToLEBytes(grbit) + recbytes = ByteTools.append(tmp, recbytes) + recbytes = ByteTools.append(dTitlePrompt!!.read(), recbytes) + recbytes = ByteTools.append(dTitleError!!.read(), recbytes) + recbytes = ByteTools.append(dTextPrompt!!.read(), recbytes) + recbytes = ByteTools.append(dTextError!!.read(), recbytes) + + // get the firstCond bytes + tmp = ByteArray(0) + for (i in firstCond!!.indices) { + val o = firstCond!!.elementAt(i) + val ptg = o as Ptg + tmp = ByteTools.append(ptg.record, tmp) + } + // get the length and add in. + var sz = tmp.size.toShort() + recbytes = ByteTools.append(ByteTools.shortToLEBytes(sz), recbytes) + // add garbage + recbytes = ByteTools.append(garbageByteOne, recbytes) + recbytes = ByteTools.append(tmp, recbytes) + + // get the secondCond bytes + tmp = ByteArray(0) + for (i in secondCond!!.indices) { + val o = secondCond!!.elementAt(i) + val ptg = o as Ptg + tmp = ByteTools.append(ptg.record, tmp) + } + // get the length and add in. + sz = tmp.size.toShort() + recbytes = ByteTools.append(ByteTools.shortToLEBytes(sz), recbytes) + // add garbage + recbytes = ByteTools.append(garbageByteTwo, recbytes) + recbytes = ByteTools.append(tmp, recbytes) + + tmp = ByteArray(1) + if (cRangeList != null) { + tmp[0] = cRangeList!!.size.toByte() + recbytes = ByteTools.append(tmp, recbytes) + for (i in cRangeList!!.indices) { + tmp = (cRangeList!![i] as PtgArea).record + val tmp2 = ByteArray(8) + tmp[0] = 0 + System.arraycopy(tmp, 0, tmp2, 0, tmp2.size) + recbytes = ByteTools.append(tmp2, recbytes) + } + // there is a trailing zero, not sure why... + if (cRangeList!!.size > 0) { + tmp = ByteArray(1) + tmp[0] = 0 + recbytes = ByteTools.append(tmp, recbytes) + } + } else if (ooxmlranges != null && ooxmlranges!!.size > 0) { + tmp[0] = ooxmlranges!!.size.toByte() + recbytes = ByteTools.append(tmp, recbytes) + for (i in ooxmlranges!!.indices) { + val /*Ref*/ p = PtgRef.createPtgRefFromString(this.sheet!!.sheetName + "!" + ooxmlranges!![i], this) + tmp = p.record +/* replace with above PtgArea pa= new PtgArea(); + try { + pa.setParentRec(this); + pa.setLocation(this.getSheet().getSheetName() + "!" + ooxmlranges[i]); + } catch (Exception e) { + // TODO: handle MAXROWS/MAXCOLS + } + tmp = pa.getRecord(); + /**/ + tmp[0] = 0 +val tmp2 = ByteArray(8) +System.arraycopy(tmp, 0, tmp2, 0, tmp2.size) +recbytes = ByteTools.append(tmp, recbytes) +} + // there is a trailing zero, not sure why... + if (ooxmlranges!!.size > 0) +{ +tmp = ByteArray(1) +tmp[0] = 0 +recbytes = ByteTools.append(tmp, recbytes) +} +} + +this.setData(recbytes) +} + + +/** + * update record. + * + * @see io.starter.formats.XLS.XLSRecord.preStream + */ + public override fun preStream() { +if (dirtyflag) this.updateRecord() +} + +/** + * Apply all the grbit fields into the current grbit int + */ + fun updateGrbit() { +grbit = 0 +grbit = grbit or valType.toInt() +grbit = grbit or (errStyle shl 4) +grbit = grbit or (IMEMode shl 10) +if (fStrLookup) +grbit = (grbit or BITMASK_FSTRLOOKUP) + +if (fAllowBlank) +grbit = (grbit or BITMASK_FALLOWBLANK) + +if (fSuppressCombo) +grbit = (grbit or BITMASK_FSUPRESSCOMBO) + +if (fShowInputMsg) +grbit = (grbit or BITMASK_FSHOWINPUTMSG) + +if (isShowErrorMsg) +grbit = (grbit or BITMASK_FSHOWERRORMSG) + +grbit = grbit or (typOperator shl 20) +} + + +/** + * Set the range this Dv refers to. Pass in a range string, sans worksheet + * Note that absolute ranges/ptrgrefs are always used, however returning + * values should not include the dollar sign + * + * @param range + */ + fun setRange(range:String?) { +var range = range +if (range == null) +{ // for creating a dv and adding range info later +cRangeList = null +return +} +if (range!!.indexOf(":") == -1) range = range + ":" + range +val p = PtgArea(range, this, false) +cRangeList = ArrayList() +cRangeList!!.add(p) +dirtyflag = true +} + +/** + * Add a range this Dv refers to. Pass in a range string, sans worksheet + * + * @param range + */ + fun addRange(range:String) { +if (cRangeList == null) cRangeList = ArrayList() // 20090605 KSC: Added + /*int[] i = ExcelTools.getRowColFromString(range); + if(i.length==2) { + + }else {*/ + val p = PtgArea(range, this, false) // 20090609 KSC: absolute refs if '$' -really should test if row or col +cRangeList!!.add(p) +dirtyflag = true +} + +/** + * Add a range this Dv refers to. Pass in a range string, sans worksheet + * May need additional handling for records outside bounds? + * + * @param range + */ + fun addOoxmlRange(range:String?) { +if (cRangeList == null) cRangeList = ArrayList() // 20090605 KSC: Added +val p = PtgArea(range, this, (range != null) && (range!!.indexOf('$') == -1)) // 20090609 KSC: absolute refs if '$' -really should test if row or col + // p.setUseReferenceTracker(false); + cRangeList!!.add(p) +dirtyflag = true +} + +/** + * Get the first condition of the validation as + * a string representation + * + * @return + */ + fun getFirstCond():String { +val s = FormulaParser.getExpressionString(firstCond!!) +if (s.substring(0, 1) == "=") return s.substring(1) +return s +} + +/** + * Set the first condition of the validation utilizing + * a string. This value must conform to the Value Type of this + * validation or unexpected results may occur. For example, + * entering a string representation of a date here will not work + * if your validation is an integer... + * + * + * String passed in should be a vaild XLS formula. Does not need to include the "=" + * + * + * Types of conditions + * Integer values + * Decimal values + * User defined list + * Date + * Time + * Text length + * Formula + * + * @return + */ + fun setFirstCond(firstCond:String?) { +this.firstCond = FormulaParser.getPtgsFromFormulaString(this, firstCond) +dirtyflag = true +} + + +/** + * Get the second condition of the validation as + * a string representation + * + * @return + */ + fun getSecondCond():String { +val s = FormulaParser.getExpressionString(secondCond!!) +if (s.substring(0, 1) == "=") return s.substring(1) +return s +} + +/** + * Set the first condition of the validation utilizing + * a string. This value must conform to the Value Type of this + * validation or unexpected results may occur. For example, + * entering a string representation of a date here will not work + * if your validation is an integer... + * + * + * String passed in should be a vaild XLS formula. Does not need to include the "=" + * + * + * Types of conditions + * Integer values + * Decimal values + * User defined list + * Date + * Time + * Text length + * Formula + * + * @return + */ + fun setSecondCond(secondCond:String?) { +this.secondCond = FormulaParser.getPtgsFromFormulaString(this, secondCond) +dirtyflag = true +} + +/** + * set show error box if invalid values entered? + * + * @return + */ + fun setShowErrMsg(showErrMsg:Boolean) { +isShowErrorMsg = showErrMsg +dirtyflag = true +} + +/** + * Get the validation type of this Dv as a byte + * + * + * These bytes map to the VALUE_* static values in + * ValidationHandle + * + * @return + */ + fun getValType():Byte { +return valType +} + +/** + * Set the validation type of this Dv as a byte + * + * + * These bytes map to the VALUE_* static values in + * ValidationHandle + * + * @return + */ + fun setValType(valtype:Byte) { +valType = valtype +dirtyflag = true +} + +/** + * Determines if the Dv contains the cell address passed in + * + * @param range + * @return + */ + fun isInRange(celladdy:String):Boolean { + // FIX broken COLROW + val rc = ExcelTools.getRowColFromString(celladdy) +for (i in cRangeList!!.indices) +{ +if ((cRangeList!!.get(i) as PtgArea).contains(rc)) return true +} +return false +} + +companion object { +/** + * serialVersionUID + */ + private val serialVersionUID = -7895028832113540094L + + // BITMASK + private val BITMASK_VALTYPE = 0x0000000F +private val BITMASK_ERRSTYLE = 0x00000070 +private val BITMASK_FSTRLOOKUP = 0x00000080 +private val BITMASK_FALLOWBLANK = 0x00000100 +private val BITMASK_FSUPRESSCOMBO = 0x00000200 +private val BITMASK_MDIMEMODE = 0x0003FC00 +private val BITMASK_FSHOWINPUTMSG = 0x00040000 +private val BITMASK_FSHOWERRORMSG = 0x00080000 +private val BITMASK_TYPOPERATOR = 0x00F00000 + + +/** + * Create a dv record & populate with prototype bytes + * + * @return + */ + fun getPrototype(bk:WorkBook):XLSRecord { +val dv = Dv() +dv.opcode = XLSConstants.DV +dv.setData(dv.PROTOTYPE_BYTES) +dv.workBook = bk +dv.init() +return dv +} +/** + * OOXML Element: + * dataValidation (Data Validation) + * A single item of data validation defined on a range of the worksheet + * + * parent: dataValidations (==Dval) + * children: formula1, formula2 + * attributes: many + */ + + /** + * create a new Dv record based on OOXML input + */ + fun parseOOXML(xpp:XmlPullParser, bs:Boundsheet):Dv { +val dv = bs.createDv(null) +dv.setSheet(bs) +try +{ +var eventType = xpp.getEventType() +while (eventType != XmlPullParser.END_DOCUMENT) +{ +if (eventType == XmlPullParser.START_TAG) +{ +val tnm = xpp.getName() +if (tnm == "dataValidation") +{ // get attributes +for (i in 0 until xpp.getAttributeCount()) +{ +val n = xpp.getAttributeName(i) +val v = xpp.getAttributeValue(i) +if (n == "allowBlank") +{ +dv.isAllowBlank = true +} +else if (n == "error") +{ +dv.errorBoxText = v +} +else if (n == "errorStyle") +{ // default= stop +if (v == "information") +dv.errorStyle = 2.toByte() +else if (v == "stop") +dv.errorStyle = 0.toByte() +else if (v == "warning") +dv.errorStyle = 1.toByte() +} +else if (n == "errorTitle") +{ +dv.errorBoxTitle = v +} +else if (n == "imeMode") +{ // TODO: what is the correct mapping?????????????????????? +if (v == "nocontrol") +{ +dv.imeMode = 0.toShort() +} +else if (v == "off") +{ +dv.imeMode = 1.toShort() +} +else if (v == "on") +{ +dv.imeMode = 2.toShort() +} +else if (v == "disabled") +{ +dv.imeMode = 3.toShort() +} +else if (v == "hiragana") +{ +dv.imeMode = 4.toShort() +} +else if (v == "fullKatakana") +{ +dv.imeMode = 5.toShort() +} +else if (v == "halfKatakana") +{ +dv.imeMode = 6.toShort() +} +else if (v == "fullAlpha") +{ +dv.imeMode = 7.toShort() +} +else if (v == "halfAlpha") +{ +dv.imeMode = 8.toShort() +} +else if (v == "fullHangul") +{ +dv.imeMode = 9.toShort() +} +else if (v == "halfHangul") +{ +dv.imeMode = 10.toShort() +} +} +else if (n == "operator") +{ // default= "between" +if (v == "between") +dv.typeOperator = 0.toByte() +else if (v == "equal") +dv.typeOperator = 2.toByte() +else if (v == "greaterThan") +dv.typeOperator = 4.toByte() +else if (v == "greaterThanOrEqual") +dv.typeOperator = 6.toByte() +else if (v == "lessThan") +dv.typeOperator = 5.toByte() +else if (v == "lessThanOrEqual") +dv.typeOperator = 7.toByte() +else if (v == "notBetween") +dv.typeOperator = 1.toByte() +else if (v == "notEqual") +dv.typeOperator = 3.toByte() +} +else if (n == "prompt") +{ +dv.promptBoxText = v +} +else if (n == "promptTitle") +{ +dv.promptBoxTitle = v +} +else if (n == "showDropDown") +{ + // + } +else if (n == "showErrorMessage") +{ +dv.setShowErrMsg(true) +} +else if (n == "showInputMessage") +{ +dv.showInputMsg = true +} +else if (n == "sqref") +{ +dv.ooxmlranges = StringTool.splitString(v, " ") + // 20090609 KSC: cannot add ranges in 2003 format as 2007 addresses can exceed 2003 limits + for (z in dv.ooxmlranges!!.indices) +dv.addOoxmlRange(dv.ooxmlranges!![z]) +} +else if (n == "type") +{ // required +if (v == "custom") + // custom formula + dv.setValType(7.toByte()) +else if (v == "date") +dv.setValType(4.toByte()) +else if (v == "decimal") +dv.setValType(2.toByte()) +else if (v == "list") +dv.setValType(3.toByte()) +else if (v == "none") +dv.setValType(0.toByte()) +else if (v == "textLength") +dv.setValType(6.toByte()) +else if (v == "time") +dv.setValType(5.toByte()) +else if (v == "whole") +dv.setValType(1.toByte()) +} +} +} +else if (tnm == "formula1") +{ +dv.setFirstCond(OOXMLAdapter.getNextText(xpp)) +} +else if (tnm == "formula2") +{ +dv.setSecondCond(OOXMLAdapter.getNextText(xpp)) +} +} +else if (eventType == XmlPullParser.END_TAG) +{ +val endTag = xpp.getName() +if (endTag == "dataValidation") +{ +break +} +} +eventType = xpp.next() +} +} +catch (e:Exception) { +Logger.logErr("OOXMLELEMENT.parseOOXML: " + e.toString()) +} + +return dv +} +} +} diff --git a/src/main/java/io/starter/formats/XLS/Dval.java b/src/main/java/io/starter/formats/XLS/Dval.java deleted file mode 100644 index aa07cd2..0000000 --- a/src/main/java/io/starter/formats/XLS/Dval.java +++ /dev/null @@ -1,418 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.ArrayList; -import java.util.List; - -/** - * Dval: Data Validity Settings (01B2h)
                  - *

                  - * This record is the list header of the Data Validity Table in the current sheet. - *

                  - * Offset Name Size Contents - * ------------------------------------------------------- - * 0 wDviFlags 2 Option flags: - * 2 xLeft 4 Horizontal position of the prompt box, if it has fixed position, in pixel - * 6 yTop 4 Vertical position of the prompt box, if it has fixed position, in pixel - * 10 inObj 4 Object identifier of the drop down arrow object for a list box , if a list box is visible at - * the current cursor position, FFFFFFFFH otherwise - * 14 idvMac 4 Number of following DV records - *

                  - *

                  - * wDviFlags - * Bit Mask Name Contents - * ------------------------------------------------------ - * 0 0001H fWnClosed 0 = Prompt box not visible 1 = Prompt box currently visible - * 1 0002H fWnPinned 0 = Prompt box has fixed position 1 = Prompt box appears at cell - * 2 0004H fCached 1 = Cell validity data cached in following DV records - */ - -public class Dval extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3954586766300169606L; - // primary fields - private short grbit; - private int xLeft; - private int yTop; - private int inObj; - private int idvMac; - - // grbitlookups - private static final short BITMASK_F_WN_CLOSED = 0x0001; - private static final short BITMASK_F_WN_PINNED = 0x0002; - private static final short BITMASK_F_CACHED = 0x0004; - - // 200906060 KSC: Mod bytes to have no x, y or dv recs following: private static final byte[] PROTOTYPE_BYTES = {04, 00, 124, 00, 00, 00, 00, 00, 00, 00, -1, -1, -1, -1, 01, 00, 00, 00}; -// private static final byte[] PROTOTYPE_BYTES = {04, 00, 00, 00, 00, 00, 00, 00, 00, 00, -1, -1, -1, -1, 00, 00, 00, 00}; - private byte[] PROTOTYPE_BYTES = {04, 00, 00, 00, 00, 00, 00, 00, 00, 00, -1, -1, -1, -1, 00, 00, 00, 00}; - - private ArrayList dvRecs; - - - /** - * Standard init method, nothing new - * - * @see io.starter.formats.XLS.XLSRecord#init() - */ - public void init() { - super.init(); - int offset = 0; - dvRecs = new ArrayList(); - grbit = ByteTools.readShort(this.getByteAt(offset++), this.getByteAt(offset++)); - xLeft = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)); - yTop = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)); - inObj = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)); - idvMac = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)); - } - - /** - * Create a dval record & populate with prototype bytes - * - * @return - */ - protected static XLSRecord getPrototype() { - Dval dval = new Dval(); - dval.setOpcode(DVAL); - dval.setData(dval.PROTOTYPE_BYTES); - dval.init(); - return dval; - } - - /** - * Apply the grbit to the record in the streamer - */ - public void setGrbit() { - byte[] data = this.getData(); - byte[] b = ByteTools.shortToLEBytes(grbit); - System.arraycopy(b, 0, data, 0, 2); - this.setData(data); - } - - /** - * Is Cell validity data cached in following DV records? - * - * @return - */ - public boolean isValidityCached() { - return ((grbit & BITMASK_F_CACHED) == BITMASK_F_CACHED); - } - - /** - * Set cell validity data cached in following DV records - */ - public void setValidityCached(boolean cached) { - if (cached) - grbit = (short) (grbit | BITMASK_F_CACHED); - else - grbit = (short) (grbit ^ BITMASK_F_CACHED); - this.setGrbit(); - } - - /** - * Get where the prompt box is located. - * true = Prompt box is at cell; false= Prompt box in fixed position - * - * @return - */ - public boolean isPromptBoxAtCell() { - return ((grbit & BITMASK_F_WN_PINNED) == BITMASK_F_WN_PINNED); - } - - /** - * Set where the prompt box is located. - * true = Prompt box is at cell; false= Prompt box in fixed position - * - * @return - */ - public void setPromptBoxAtCell(boolean location) { - if (location) - grbit = (short) (grbit | BITMASK_F_WN_PINNED); - else - grbit = (short) (grbit ^ BITMASK_F_WN_PINNED); - this.setGrbit(); - } - - - /** - * Get visibility of prompt box - * true = Prompt box currently visible; false = Prompt box not visible - * - * @return - */ - public boolean isPromptBoxVisible() { - return ((grbit & BITMASK_F_WN_CLOSED) == BITMASK_F_WN_CLOSED); - } - - /** - * Set visibility of prompt box - * true = Prompt box currently visible; false = Prompt box not visible - * - * @return - */ - public void setPromptBoxVisible(boolean location) { - if (location) - grbit = (short) (grbit | BITMASK_F_WN_CLOSED); - else - grbit = (short) (grbit ^ BITMASK_F_WN_CLOSED); - this.setGrbit(); - } - - - /** - * Get the count of dv records following this Dval - * - * @return - */ - public int getFollowingDvCount() { - return idvMac; - } - - /** - * Set the count of following Dv records - * - * @param cnt = count - */ - public void setFollowingDvCount(int cnt) { - this.idvMac = cnt; - byte[] data = this.getData(); - byte[] b = ByteTools.cLongToLEBytes(idvMac); - System.arraycopy(b, 0, data, 14, 4); - this.setData(data); - } - - /** - * Object identifier of the drop down arrow object for a list box , - * if a list box is visible at the current cursor position, FFFFFFFFH otherwise - * - * @return - */ - public int getObjectIdentifier() { - return inObj; - } - - /** - * Object identifier of the drop down arrow object for a list box , - * if a list box is visible at the current cursor position, FFFFFFFFH otherwise - * - * @param cnt = identifier - */ - public void setObjectIdentifier(int cnt) { - this.inObj = cnt; - byte[] data = this.getData(); - byte[] b = ByteTools.cLongToLEBytes(inObj); - System.arraycopy(b, 0, data, 10, 4); - this.setData(data); - } - - /** - * Horizontal position of the prompt box, if it has fixed position, in pixel - * - * @return - */ - public int getHorizontalPosition() { - return xLeft; - } - - /** - * Horizontal position of the prompt box, if it has fixed position, in pixel - * - * @param cnt = position - */ - public void setHorizontalPosition(int cnt) { - this.xLeft = cnt; - byte[] data = this.getData(); - byte[] b = ByteTools.cLongToLEBytes(xLeft); - System.arraycopy(b, 0, data, 2, 4); - this.setData(data); - } - - /** - * Vertical position of the prompt box, if it has fixed position, in pixel - * - * @return - */ - public int getVerticalPosition() { - return yTop; - } - - /** - * Vertical position of the prompt box, if it has fixed position, in pixel - * - * @param cnt = position - */ - public void setVerticalPosition(int cnt) { - this.yTop = cnt; - byte[] data = this.getData(); - byte[] b = ByteTools.cLongToLEBytes(yTop); - System.arraycopy(b, 0, data, 2, 4); - this.setData(data); - } - - /** - * Add a new Dv record to this Dval; - * - * @param dv - */ - public void addDvRec(Dv dv) { - dvRecs.add(dv); - } - - /** - * Add a new (ie not on parse) dv record, - * updates the parent record with the count - * - * @param location = the cell/range that the dv attaches to. Sheet name not required - * as Dval is a sheet not book level record - */ - public Dv createDvRec(String location) { - Dv d = (Dv) Dv.getPrototype(this.getWorkBook()); - d.setSheet(this.getSheet()); - d.setRange(location); - this.addDvRec(d); - this.setFollowingDvCount(dvRecs.size()); - return d; - } - - /** - * Return all dvs for this Dval - * - * @return - */ - public List getDvs() { - return dvRecs; - } - - /** - * Remove a dv rec from this dval. - * - * @param dv - */ - public void removeDvRec(Dv dv) { - dvRecs.remove(dv); - } - - /** - * @param cellAddress - * @return - */ - public Dv getDv(String cellAddress) { - if (cellAddress.indexOf("!") != -1) { - cellAddress = cellAddress.substring(cellAddress.indexOf("!")); - } - for (int i = 0; i < dvRecs.size(); i++) { - Dv d = (Dv) dvRecs.get(i); - if (d.isInRange(cellAddress)) return d; - } - return null; - } - -/** - * OOXML Element: - * dataValidations (Data Validations) - * This collection expresses all data validation information for cells in a sheet which have data validation features - * applied. - * Data validation is used to specify constaints on the type of data that can be entered into a cell. Additional UI can - * be provided to help the user select valid values (e.g., a dropdown control on the cell or hover text when the cell - * is active), and to help the user understand why a particular entry was considered invalid (e.g., alerts and - * messages). - * Various data types can be selected, and logical operators (e.g., greater than, less than, equal to, etc) can be - * used. Additionally, instead of specifying an explicit set of values that are valid, a cell or range reference may be - * used. - * An input message can be specified to help the user know what kind of value is expected, and a warning message - * (and warning type) can be specified to alert the user when they've entered invalid data. - * - * parent: worksheet - * children: datraValidation - * attributes: count (uint), xWindow (uint)-per-sheet x, yWindow (uint)- per-sheet y, disablePrompts (bool) - * - * TODO: shouldnt this be in the OOXML package? why generate/treat this differently? - */ - - /** - * create one or more Data Validation records based on OOXML input - */ - public static Dval parseOOXML(XmlPullParser xpp, Boundsheet bs) { - Dval dval = bs.insertDvalRec(); // creates or retrieves Dval rec - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("dataValidations")) { // get attributes - for (int i = 0; i < xpp.getAttributeCount(); i++) { - String n = xpp.getAttributeName(i); - String v = xpp.getAttributeValue(i); - if (n.equals("count")) { - } else if (n.equals("disablePrompts")) { - dval.setPromptBoxVisible(false); - } else if (n.equals("xWindow")) { - dval.setHorizontalPosition(Integer.valueOf(v).intValue()); - } else if (n.equals("yWindow")) { - dval.setVerticalPosition(Integer.valueOf(v).intValue()); - } - } - } else if (tnm.equals("dataValidation")) { // one or more - Dv.parseOOXML(xpp, bs); // creates and adds a new Dv record to the Dval recs list - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("dataValidations")) { - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("OOXMLELEMENT.parseOOXML: " + e.toString()); - } - return dval; - } - - /** - * generate the proper OOXML to define this Dval - * - * @return - */ - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - if (this.dvRecs.size() > 0) { - ooxml.append(""); - for (int i = 0; i < this.dvRecs.size(); i++) { - ooxml.append(((Dv) dvRecs.get(i)).getOOXML()); - } - ooxml.append(""); - } - return ooxml.toString(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/Dval.kt b/src/main/java/io/starter/formats/XLS/Dval.kt new file mode 100644 index 0000000..070b7c2 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Dval.kt @@ -0,0 +1,413 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.ArrayList + +/** + * **Dval: Data Validity Settings (01B2h)**

                  + * + * + * This record is the list header of the Data Validity Table in the current sheet. + * + * + * Offset Name Size Contents + * ------------------------------------------------------- + * 0 wDviFlags 2 Option flags: + * 2 xLeft 4 Horizontal position of the prompt box, if it has fixed position, in pixel + * 6 yTop 4 Vertical position of the prompt box, if it has fixed position, in pixel + * 10 inObj 4 Object identifier of the drop down arrow object for a list box , if a list box is visible at + * the current cursor position, FFFFFFFFH otherwise + * 14 idvMac 4 Number of following DV records + * + * + * + * + * wDviFlags + * Bit Mask Name Contents + * ------------------------------------------------------ + * 0 0001H fWnClosed 0 = Prompt box not visible 1 = Prompt box currently visible + * 1 0002H fWnPinned 0 = Prompt box has fixed position 1 = Prompt box appears at cell + * 2 0004H fCached 1 = Cell validity data cached in following DV records + */ + +class Dval : io.starter.formats.XLS.XLSRecord() { + // primary fields + private var grbit: Short = 0 + private var xLeft: Int = 0 + private var yTop: Int = 0 + private var inObj: Int = 0 + private var idvMac: Int = 0 + + // 200906060 KSC: Mod bytes to have no x, y or dv recs following: private static final byte[] PROTOTYPE_BYTES = {04, 00, 124, 00, 00, 00, 00, 00, 00, 00, -1, -1, -1, -1, 01, 00, 00, 00}; + // private static final byte[] PROTOTYPE_BYTES = {04, 00, 00, 00, 00, 00, 00, 00, 00, 00, -1, -1, -1, -1, 00, 00, 00, 00}; + private val PROTOTYPE_BYTES = byteArrayOf(4, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0) + + private var dvRecs: ArrayList<*>? = null + + /** + * Is Cell validity data cached in following DV records? + * + * @return + */ + /** + * Set cell validity data cached in following DV records + */ + var isValidityCached: Boolean + get() = grbit and BITMASK_F_CACHED == BITMASK_F_CACHED.toInt() + set(cached) { + if (cached) + grbit = (grbit or BITMASK_F_CACHED).toShort() + else + grbit = (grbit xor BITMASK_F_CACHED).toShort() + this.setGrbit() + } + + /** + * Get where the prompt box is located. + * true = Prompt box is at cell; false= Prompt box in fixed position + * + * @return + */ + /** + * Set where the prompt box is located. + * true = Prompt box is at cell; false= Prompt box in fixed position + * + * @return + */ + var isPromptBoxAtCell: Boolean + get() = grbit and BITMASK_F_WN_PINNED == BITMASK_F_WN_PINNED.toInt() + set(location) { + if (location) + grbit = (grbit or BITMASK_F_WN_PINNED).toShort() + else + grbit = (grbit xor BITMASK_F_WN_PINNED).toShort() + this.setGrbit() + } + + + /** + * Get visibility of prompt box + * true = Prompt box currently visible; false = Prompt box not visible + * + * @return + */ + /** + * Set visibility of prompt box + * true = Prompt box currently visible; false = Prompt box not visible + * + * @return + */ + var isPromptBoxVisible: Boolean + get() = grbit and BITMASK_F_WN_CLOSED == BITMASK_F_WN_CLOSED.toInt() + set(location) { + if (location) + grbit = (grbit or BITMASK_F_WN_CLOSED).toShort() + else + grbit = (grbit xor BITMASK_F_WN_CLOSED).toShort() + this.setGrbit() + } + + + /** + * Get the count of dv records following this Dval + * + * @return + */ + /** + * Set the count of following Dv records + * + * @param cnt = count + */ + var followingDvCount: Int + get() = idvMac + set(cnt) { + this.idvMac = cnt + val data = this.getData() + val b = ByteTools.cLongToLEBytes(idvMac) + System.arraycopy(b, 0, data!!, 14, 4) + this.setData(data) + } + + /** + * Object identifier of the drop down arrow object for a list box , + * if a list box is visible at the current cursor position, FFFFFFFFH otherwise + * + * @return + */ + /** + * Object identifier of the drop down arrow object for a list box , + * if a list box is visible at the current cursor position, FFFFFFFFH otherwise + * + * @param cnt = identifier + */ + var objectIdentifier: Int + get() = inObj + set(cnt) { + this.inObj = cnt + val data = this.getData() + val b = ByteTools.cLongToLEBytes(inObj) + System.arraycopy(b, 0, data!!, 10, 4) + this.setData(data) + } + + /** + * Horizontal position of the prompt box, if it has fixed position, in pixel + * + * @return + */ + /** + * Horizontal position of the prompt box, if it has fixed position, in pixel + * + * @param cnt = position + */ + var horizontalPosition: Int + get() = xLeft + set(cnt) { + this.xLeft = cnt + val data = this.getData() + val b = ByteTools.cLongToLEBytes(xLeft) + System.arraycopy(b, 0, data!!, 2, 4) + this.setData(data) + } + + /** + * Vertical position of the prompt box, if it has fixed position, in pixel + * + * @return + */ + /** + * Vertical position of the prompt box, if it has fixed position, in pixel + * + * @param cnt = position + */ + var verticalPosition: Int + get() = yTop + set(cnt) { + this.yTop = cnt + val data = this.getData() + val b = ByteTools.cLongToLEBytes(yTop) + System.arraycopy(b, 0, data!!, 2, 4) + this.setData(data) + } + + /** + * Return all dvs for this Dval + * + * @return + */ + val dvs: List<*>? + get() = dvRecs + + /** + * generate the proper OOXML to define this Dval + * + * @return + */ + val ooxml: String + get() { + val ooxml = StringBuffer() + if (this.dvRecs!!.size > 0) { + ooxml.append("") + for (i in this.dvRecs!!.indices) { + ooxml.append((dvRecs!![i] as Dv).ooxml) + } + ooxml.append("") + } + return ooxml.toString() + } + + + /** + * Standard init method, nothing new + * + * @see io.starter.formats.XLS.XLSRecord.init + */ + override fun init() { + super.init() + var offset = 0 + dvRecs = ArrayList() + grbit = ByteTools.readShort(this.getByteAt(offset++).toInt(), this.getByteAt(offset++).toInt()) + xLeft = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)) + yTop = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)) + inObj = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)) + idvMac = ByteTools.readInt(this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++), this.getByteAt(offset++)) + } + + /** + * Apply the grbit to the record in the streamer + */ + fun setGrbit() { + val data = this.getData() + val b = ByteTools.shortToLEBytes(grbit) + System.arraycopy(b, 0, data!!, 0, 2) + this.setData(data) + } + + /** + * Add a new Dv record to this Dval; + * + * @param dv + */ + fun addDvRec(dv: Dv) { + dvRecs!!.add(dv) + } + + /** + * Add a new (ie not on parse) dv record, + * updates the parent record with the count + * + * @param location = the cell/range that the dv attaches to. Sheet name not required + * as Dval is a sheet not book level record + */ + fun createDvRec(location: String): Dv { + val d = Dv.getPrototype(this.workBook) as Dv + d.setSheet(this.sheet) + d.setRange(location) + this.addDvRec(d) + this.followingDvCount = dvRecs!!.size + return d + } + + /** + * Remove a dv rec from this dval. + * + * @param dv + */ + fun removeDvRec(dv: Dv) { + dvRecs!!.remove(dv) + } + + /** + * @param cellAddress + * @return + */ + fun getDv(cellAddress: String): Dv? { + var cellAddress = cellAddress + if (cellAddress.indexOf("!") != -1) { + cellAddress = cellAddress.substring(cellAddress.indexOf("!")) + } + for (i in dvRecs!!.indices) { + val d = dvRecs!![i] as Dv + if (d.isInRange(cellAddress)) return d + } + return null + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 3954586766300169606L + + // grbitlookups + private val BITMASK_F_WN_CLOSED: Short = 0x0001 + private val BITMASK_F_WN_PINNED: Short = 0x0002 + private val BITMASK_F_CACHED: Short = 0x0004 + + /** + * Create a dval record & populate with prototype bytes + * + * @return + */ + val prototype: XLSRecord? + get() { + val dval = Dval() + dval.opcode = XLSConstants.DVAL + dval.setData(dval.PROTOTYPE_BYTES) + dval.init() + return dval + } + + /** + * OOXML Element: + * dataValidations (Data Validations) + * This collection expresses all data validation information for cells in a sheet which have data validation features + * applied. + * Data validation is used to specify constaints on the type of data that can be entered into a cell. Additional UI can + * be provided to help the user select valid values (e.g., a dropdown control on the cell or hover text when the cell + * is active), and to help the user understand why a particular entry was considered invalid (e.g., alerts and + * messages). + * Various data types can be selected, and logical operators (e.g., greater than, less than, equal to, etc) can be + * used. Additionally, instead of specifying an explicit set of values that are valid, a cell or range reference may be + * used. + * An input message can be specified to help the user know what kind of value is expected, and a warning message + * (and warning type) can be specified to alert the user when they've entered invalid data. + * + * parent: worksheet + * children: datraValidation + * attributes: count (uint), xWindow (uint)-per-sheet x, yWindow (uint)- per-sheet y, disablePrompts (bool) + * + * TODO: shouldnt this be in the OOXML package? why generate/treat this differently? + */ + + /** + * create one or more Data Validation records based on OOXML input + */ + fun parseOOXML(xpp: XmlPullParser, bs: Boundsheet): Dval? { + val dval = bs.insertDvalRec() // creates or retrieves Dval rec + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "dataValidations") { // get attributes + for (i in 0 until xpp.attributeCount) { + val n = xpp.getAttributeName(i) + val v = xpp.getAttributeValue(i) + if (n == "count") { + } else if (n == "disablePrompts") { + dval.isPromptBoxVisible = false + } else if (n == "xWindow") { + dval.horizontalPosition = Integer.valueOf(v).toInt() + } else if (n == "yWindow") { + dval.verticalPosition = Integer.valueOf(v).toInt() + } + } + } else if (tnm == "dataValidation") { // one or more + Dv.parseOOXML(xpp, bs) // creates and adds a new Dv record to the Dval recs list + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "dataValidations") { + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("OOXMLELEMENT.parseOOXML: $e") + } + + return dval + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/Eof.java b/src/main/java/io/starter/formats/XLS/Eof.kt similarity index 74% rename from src/main/java/io/starter/formats/XLS/Eof.java rename to src/main/java/io/starter/formats/XLS/Eof.kt index a44d5fb..88e6696 100644 --- a/src/main/java/io/starter/formats/XLS/Eof.java +++ b/src/main/java/io/starter/formats/XLS/Eof.kt @@ -20,20 +20,22 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** - *

                  Eof: End of File Stream 0x0a
                  + *
                  **Eof: End of File Stream 0x0a**

                  * * Marks the end of an XLS file Stream including Boundsheets - */ +
                  */ -public final class Eof extends io.starter.formats.XLS.XLSRecord { +class Eof : io.starter.formats.XLS.XLSRecord() { + companion object { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1936402960576983597L; + /** + * serialVersionUID + */ + private val serialVersionUID = 1936402960576983597L + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/ExpressionParser.java b/src/main/java/io/starter/formats/XLS/ExpressionParser.java deleted file mode 100644 index 9d8e6b7..0000000 --- a/src/main/java/io/starter/formats/XLS/ExpressionParser.java +++ /dev/null @@ -1,1199 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.util.List; -import java.util.Stack; -import java.util.Vector; - -/** - * - */ -public final class ExpressionParser implements java.io.Serializable { - /** - * - */ - private static final long serialVersionUID = 4745215965823234010L; - private static int DEBUGLEVEL = 0; - /* - * All of the operand values - * - * Section of binary operator PTG's. These pop the two - * top values out of a stack and perform an operation on - * them before pushing back in - */ - // really "special" one, read all about it. - public static final short ptgExp = 0x1; - public static final short ptgAdd = 0x3; - public static final short ptgSub = 0x4; - public static final short ptgMlt = 0x5; - public static final short ptgDiv = 0x6; - public static final short ptgPower = 0x7; - public static final short ptgConcat = 0x8; - public static final short ptgLT = 0x09; - public static final short ptgLE = 0x0a; - public static final short ptgEQ = 0x0b; - public static final short ptgGE = 0x0c; - public static final short ptgGT = 0x0d; - public static final short ptgNE = 0x0e; - public static final short ptgIsect = 0x0f; - public static final short ptgUnion = 0x10; - public static final short ptgRange = 0x11; - // End of binary operator PTG's - - // Unary Operator tokens - public static final short ptgUPlus = 0x12; - public static final short ptgUMinus = 0x13; // todo - public static final short ptgPercent = 0x14; // todo - - // Controls - public static final short ptgParen = 0x15; - public static final short ptgAtr = 0x19; - // End of Controls - - // Constant operators - public static final short ptgMissArg = 0x16; - public static final short ptgStr = 0x17; - public static final short ptgEndSheet = 0x1b; - public static final short ptgErr = 0x1c; - public static final short ptgBool = 0x1d; - public static final short ptgInt = 0x1e; - public static final short ptgNum = 0x1f; - // End of Constant Operators - - public static final short ptgArray = 0x20; - public static final short ptgFunc = 0x21; - public static final short ptgFuncVar = 0x22; - public static final short ptgName = 0x23; - public static final short ptgRef = 0x24; - public static final short ptgArea = 0x25; - public static final short ptgMemArea = 0x26; - public static final short ptgMemErr = 0x27; - public static final short ptgMemFunc = 0x29; - public static final short ptgRefErr = 0x2a; - public static final short ptgAreaErr = 0x2b; - public static final short ptgRefN = 0x2c; - public static final short ptgAreaN = 0x2d; - public static final short ptgNameX = 0x39; - public static final short ptgRef3d = 0x3a; - public static final short ptgArea3d = 0x3b; - public static final short ptgRefErr3d = 0x3c; - - // who knows, added to fix broken Named ranges -jm 03/26/04 - public static final short ptgAreaErr3d = 0x3d; - public static final short ptgMemAreaA = 0x66; - public static final short ptgMemAreaNV = 0x4e; - public static final short ptgMemAreaN = 0x2e; - - /** - * Parse the byte array, create component Ptg's and insert - * them into a stack. - *

                  - *

                  - * Feb 8, 2010 - * - * @param function - * @param rec - * @return - */ - public static Stack parseExpression(byte[] function, XLSRecord rec) { - return ExpressionParser.parseExpression(function, rec, function.length); - } - - /** - * Parse the byte array, create component Ptg's and insert them into - * a stack. - *

                  - * Feb 8, 2010 - * - * @param function - * @param rec - * @param expressionLen - * @return - */ - public static Stack parseExpression(byte[] function, XLSRecord rec, int expressionLen) { - Stack stack = new Stack(); - short ptg = 0x0; - int ptgLen = 0; - boolean hasArrays = false; - /* - * Not really needed - * //boolean hasPtgExtraMem= false; - * //PtgMemArea pma= null; - */ - CompatibleVector arrayLocs = new CompatibleVector(); - if (expressionLen > function.length) - expressionLen = function.length; // deal with out of spec formulas - // (testJapanese:Open25.xls) -jm - // KSC: shared formula changes for peformance: now - // PtgRefN's/PtgAreaN's are instantiated and - // reference-tracked (of a sort) ... - XLSRecord p = rec; // parent - - // iterate the expression and create Ptgs. - for (int i = 0; i < expressionLen; ) { - // check if the 40 bit is set, is it a Array class? - if ((function[i] & 0x40) == 0x40) { - // rec is a value class - // we need to strip the high-order bits and set the 0x20 bit - ptg = (short) ((function[i] | 0x20) & 0x3f); - } else { - // the bit is already set, just strip the high order bits - // rec may be an array class. need to figure rec one out. - ptg = (short) (function[i] & 0x3f); - } - switch (ptg) { - - case ptgExp: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgExp Located"); - if (i == 0) {// MUST BE THE ONLY PTG in the formula expression - PtgExp px = new PtgExp(); - ptgLen = px.getLength(); - byte[] b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - px.setParentRec(p); - px.init(b); - stack.push(px); - break; - } - // ptgStr is one of the only ptg's that varies in length, so - // there is some special handling - // going on for it. - case ptgStr: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgStr Located"); - int x = i; - x += 1; // move past the opcode to the cch - ptgLen = function[x] & 0xff; // this is the cch - short theGrbit = function[x + 1];// this is the grbit; - if ((theGrbit & 0x1) == 0x1) { - // unicode string - ptgLen = ptgLen * 2; - } - ptgLen += 3; // include the PtgId, cch, & grbit; - PtgStr pst = new PtgStr(); - byte[] b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pst.init(b); - pst.setParentRec(p); - stack.push(pst); - break; - /* */ - - case ptgMemAreaA: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgMemAreaA Located" + function[i]); - x = i; - x += 5; // move past the opcode & reserved to the cce - ptgLen = ByteTools.readShort(function[x], function[x + 1]); // this - // is - // the - // cce - ptgLen += 7; // include the PtgId, cce, & reserv; - PtgMemAreaA pmema = new PtgMemAreaA(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pmema.init(b); - pmema.setParentRec(p); - stack.push(pmema); - break; - - case ptgMemAreaN: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgMemAreaN Located" + function[i]); - PtgMemAreaN pmemn = new PtgMemAreaN(); - ptgLen = pmemn.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pmemn.init(b); - pmemn.setParentRec(p); - stack.push(pmemn); - break; - - case ptgMemAreaNV: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgMemAreaNV Located" + function[i]); - x = i; - x += 5; // move past the opcode & reserved to the cce - ptgLen = ByteTools.readShort(function[x], function[x + 1]); // this - // is - // the - // cce - ptgLen += 7; // include the PtgId, cce, & reserv; - PtgMemAreaNV pmemv = new PtgMemAreaNV(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pmemv.init(b); - pmemv.setParentRec(p); - stack.push(pmemv); - break; - - // ptgMemArea also varies in length... - case ptgMemArea: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgMemArea Located" + function[i]); - ptgLen = 7; - PtgMemArea pmem = new PtgMemArea(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pmem.init(b); - // now grab the rest of the "extra data" that defines the - // ptgmemarea - // these are separate ptgs (PtgArea, PtgRef's ... plus - // PtgUnions) - // that comprise the PtgMemArea coordinates - pmem.setParentRec(p); - i += ptgLen; // after PtgMemArea record, get subexpression - ptgLen = pmem.getnTokens(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pmem.setSubExpression(b); - stack.push(pmem); - // hasPtgExtraMem= true; // has a PtgExtraMem structure - // after end of parsed expression: The PtgExtraMem structure - // specifies a range that corresponds to a PtgMemArea as - // specified in RgbExtra. - // pma= pmem; // save for later - break; - - case ptgMemFunc: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgMemFunc Located"); - PtgMemFunc pmemf = new PtgMemFunc(); - x = i; - x += 1; // move past the opcode to the cce - ptgLen = ByteTools.readShort(function[x], function[x + 1]); // this - // is - // the - // cce - ptgLen += 3; // include the PtgId, cce, & reserv; - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pmemf.setParentRec(p); - pmemf.init(b); - stack.push(pmemf); - break; - - case ptgInt: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgInt Located"); - PtgInt pi = new PtgInt(); - ptgLen = pi.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pi.init(b); - pi.setParentRec(p); - stack.push(pi); - break; - - case ptgErr: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgErr Located"); - PtgErr perr = new PtgErr(); - ptgLen = perr.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - perr.init(b); - perr.setParentRec(p); - stack.push(perr); - break; - - case ptgNum: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgNum Located"); - PtgNumber pnum = new PtgNumber(); - ptgLen = pnum.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pnum.init(b); - pnum.setParentRec(p); - stack.push(pnum); - break; - - case ptgBool: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgBool Located"); - PtgBool pboo = new PtgBool(); - ptgLen = pboo.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pboo.init(b); - pboo.setParentRec(p); - stack.push(pboo); - break; - - case ptgName: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgName Located"); - PtgName pn = new PtgName(); - ptgLen = pn.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pn.setParentRec(p); - pn.init(b); - pn.addListener(); - stack.push(pn); - int chk = (i + ptgLen); - if (chk < function.length) { - if (function[i + ptgLen] == 0x0) { - if (DEBUGLEVEL > 1) - Logger.logWarn("Undocumented Name Record mystery byte encountered in Formula: "); - i++; - } - } - break; - - case ptgNameX: - if (DEBUGLEVEL > 1) - Logger.logInfo("ptgNameX Located"); - if (DEBUGLEVEL > 0) - Logger.logWarn("referencing external spreadsheets unsupported."); - PtgNameX pnx = new PtgNameX(); - ptgLen = pnx.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pnx.init(b); - pnx.setParentRec(p); - pnx.addListener(); - stack.push(pnx); - break; - - case ptgRef: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgRef Located "); - PtgRef pt = new PtgRef(); - ptgLen = pt.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pt.setParentRec(p); // parent rec must be set before init - pt.init(b); - pt.addToRefTracker(); - stack.push(pt); - break; - - case ptgArray: - hasArrays = true; - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgArray Located "); - PtgArray pa = new PtgArray(); - ptgLen = 8; // 7 len + id - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pa.init(b); // setArrVals(b); // 20090820 KSC: b represents base - // record not array values - Integer ingr = Integer.valueOf(stack.size()); // constant value - // array for - // PtgArray - // appears at - // end of stack - // see hasArrays - // below - arrayLocs.add(ingr); - stack.push(pa); - break; - - case ptgRefN: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgRefN Located "); - PtgRefN ptn = new PtgRefN(false); - ptgLen = ptn.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - ptn.setParentRec(rec); // parent rec must be set before init - ptn.init(b); - if (rec.getOpcode() == XLSConstants.SHRFMLA) - ptn.addToRefTracker(); - stack.push(ptn); - break; - - case ptgArea: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgArea Located "); - PtgArea pg = new PtgArea(); - ptgLen = pg.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pg.setParentRec(p); // parent rec must be set before init - pg.init(b); - pg.addToRefTracker(); - stack.push(pg); - break; - - case ptgArea3d: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgArea3d Located "); - PtgArea3d pg3 = new PtgArea3d(); - ptgLen = pg3.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pg3.init(b, p); // we need this to init the sub-ptgs correctly - pg3.addListener(); - pg3.addToRefTracker(); - stack.push(pg3); - break; - - case ptgAreaN: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgAreaN Located "); - PtgAreaN pgn = new PtgAreaN(); - ptgLen = pgn.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pgn.setParentRec(rec); - pgn.init(b); - if (rec.getOpcode() == XLSConstants.SHRFMLA) { - pgn.addToRefTracker(); - } - stack.push(pgn); - break; - - case ptgAreaErr3d: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgAreaErr3d Located"); - PtgAreaErr3d ptfa = new PtgAreaErr3d(); - ptgLen = ptfa.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - ptfa.setParentRec(p); - ptfa.init(b); - // ptfa.addToRefTracker(); - stack.push(ptfa); - break; - - case ptgRefErr3d: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgRefErr3d Located"); - PtgRefErr3d ptfr = new PtgRefErr3d(); - ptgLen = ptfr.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - ptfr.setParentRec(p); - ptfr.init(b); - // ptfr.addToRefTracker(); - stack.push(ptfr); - break; - - case ptgMemErr: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgMemErr Located"); - PtgMemErr pm = new PtgMemErr(); - ptgLen = pm.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pm.setParentRec(p); - pm.init(b); - stack.push(pm); - break; - - case ptgRefErr: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgRefErr Located"); - PtgRefErr pr = new PtgRefErr(); - ptgLen = pr.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pr.setParentRec(p); // parent rec must be set before init - pr.init(b); - stack.push(pr); - break; - - case ptgEndSheet: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgEndSheet Located"); - PtgEndSheet prs = new PtgEndSheet(); - ptgLen = prs.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - prs.init(b); - prs.setParentRec(p); - stack.push(prs); - break; - - case ptgRef3d: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgRef3d Located"); - PtgRef3d pr3 = new PtgRef3d(); - ptgLen = pr3.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pr3.setParentRec(p); - pr3.init(b); - pr3.addListener(); - pr3.addToRefTracker(); - stack.push(pr3); - // if an External Link i.e. defined in another workbook, - // flag formula as such - if (pr3.isExternalLink() - && p.getOpcode() == XLSConstants.FORMULA) - ((Formula) p).setIsExternalRef(true); - break; - /* - * PtgAtr is another one of the ugly size-changing ptg's - */ - case ptgAtr: - PtgAtr pat = new PtgAtr((byte) 0); - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgAtr Located"); - ptgLen = pat.getLength(); - if ((function[i + 1] & 0x4) == 0x4) { - ptgLen = ByteTools - .readShort(function[i + 2], function[i + 3]); - ptgLen++; // one extra for some undocumented reason - ptgLen = ptgLen * 2; // seems to be two bytes per... - ptgLen += 4; // add the cch & grbit - } - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pat.init(b); - pat.init(); - pat.setParentRec(p); - stack.push(pat); - break; - - case ptgFunc: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgFunc Located"); - PtgFunc ptf = new PtgFunc(); - ptgLen = ptf.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - ptf.init(b); - ptf.setParentRec(p); - stack.push(ptf); - break; - - case ptgFuncVar: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgFuncVar Located"); - PtgFuncVar ptfv = new PtgFuncVar(); - ptgLen = ptfv.getLength(); - b = new byte[ptgLen]; - if ((ptgLen) + (i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - - ptfv.init(b); - ptfv.setParentRec(p); - if (ptfv.getFunctionId() == FunctionConstants.XLF_INDIRECT) { - /* - * TESTING NEW WAY: - * // New way does not account for expanded shared formula - * references, unfortunately so keep original for new - * - * Stack indirectStack= new Stack(); - * int z= stack.size()-1; - * int nparams= 1; - * for (; z > 0 && nparams > 0; z--) { - * Ptg p= (Ptg) stack.get(z); - * if (p instanceof PtgAtr) { - * continue; - * } - * if(p.getIsOperator()||p.getIsControl()||p.getIsFunction() - * ){ - * if(p.getIsControl() ){ - * if(p.getOpcode() == 0x15) { // its a parens! - * // the parens is already pop'd so just return and it is - * gone... - * continue; - * } - * } - * int t= 0; - * // make sure we have the correct amount popped back in.. - * if (p.getIsBinaryOperator()) t=2; - * if (p.getIsUnaryOperator()) t=1; - * if (p.getIsStandAloneOperator()) t=0; - * if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || - * p.getOpcode() == 0x62){t=p.getNumParams();}// it's a - * ptgfunkvar! - * if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || - * p.getOpcode() == 0x61){t=p.getNumParams();}// guess that - * ptgfunc is not only one.. - * nparams+=t-1; - * if (nparams==0) - * break; - * } else { - * nparams--; - * if (nparams==0) - * break; - * } - * } - * indirectStack.addAll(stack.subList(z, stack.size())); - * indirectStack.push(ptfv); - * rec.getWorkBook().addIndirectFormulaStack(indirectStack); - * // must save and calculate indirect reference AFTER all - * formulas/cells have been added ... - * // original is below - * / - **/ - /**/ - if (rec.getOpcode() == XLSConstants.FORMULA) { - ((Formula) rec).setContainsIndirectFunction(true); - } else if (rec.getOpcode() == XLSConstants.SHRFMLA) { - ((Shrfmla) rec).setContainsIndirectFunction(true); - } - /**/ - } - stack.push(ptfv); - break; - - case ptgAdd: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgAdd Located"); - PtgAdd pad = new PtgAdd(); - ptgLen = pad.getLength(); - b = new byte[ptgLen]; - // if((ptgLen+i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pad.init(b); - pad.setParentRec(p); - stack.push(pad); - break; - - case ptgMissArg: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgMissArg Located"); - PtgMissArg pmar = new PtgMissArg(); - ptgLen = pmar.getLength(); - b = new byte[ptgLen]; - // if((ptgLen+i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pmar.init(b); - pmar.setParentRec(p); - stack.push(pmar); - break; - - case ptgSub: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgSub Located"); - PtgSub psb = new PtgSub(); - ptgLen = psb.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - psb.init(b); - psb.setParentRec(p); - stack.push(psb); - break; - - case ptgMlt: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgMlt Located"); - PtgMlt pml = new PtgMlt(); - ptgLen = pml.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pml.init(b); - pml.setParentRec(p); - stack.push(pml); - break; - - case ptgDiv: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgDiv Located"); - PtgDiv pdiv = new PtgDiv(); - ptgLen = pdiv.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pdiv.init(b); - pdiv.setParentRec(p); - stack.push(pdiv); - break; - - case ptgUPlus: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgUPlus Located"); - PtgUPlus puplus = new PtgUPlus(); - ptgLen = puplus.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - puplus.init(b); - puplus.setParentRec(p); - stack.push(puplus); - break; - - case ptgUMinus: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgUminus Located"); - PtgUMinus puminus = new PtgUMinus(); - ptgLen = puminus.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - puminus.init(b); - puminus.setParentRec(p); - stack.push(puminus); - break; - - case ptgPercent: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgPercent Located"); - PtgPercent pperc = new PtgPercent(); - ptgLen = pperc.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pperc.init(b); - pperc.setParentRec(p); - stack.push(pperc); - break; - - case ptgPower: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgPower Located"); - PtgPower pow = new PtgPower(); - ptgLen = pow.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pow.init(b); - pow.setParentRec(p); - stack.push(pow); - break; - - case ptgConcat: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgConcat Located"); - PtgConcat pcon = new PtgConcat(); - ptgLen = pcon.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pcon.init(b); - pcon.setParentRec(p); - stack.push(pcon); - break; - - case ptgLT: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgLT Located"); - PtgLT plt = new PtgLT(); - ptgLen = plt.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - plt.init(b); - plt.setParentRec(p); - stack.push(plt); - break; - - case ptgLE: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgLE Located"); - PtgLE ple = new PtgLE(); - ptgLen = ple.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - ple.init(b); - ple.setParentRec(p); - stack.push(ple); - break; - - case ptgEQ: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgEQ Located"); - PtgEQ peq = new PtgEQ(); - ptgLen = peq.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - peq.init(b); - peq.setParentRec(p); - stack.push(peq); - break; - - case ptgGE: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgGE Located"); - PtgGE pge = new PtgGE(); - ptgLen = pge.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pge.init(b); - pge.setParentRec(p); - stack.push(pge); - break; - - case ptgGT: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgGT Located"); - PtgGT pgt = new PtgGT(); - ptgLen = pgt.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pgt.init(b); - pgt.setParentRec(p); - stack.push(pgt); - break; - - case ptgNE: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgNE Located"); - PtgNE pne = new PtgNE(); - ptgLen = pne.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - - pne.init(b); - pne.setParentRec(p); - stack.push(pne); - break; - - case ptgIsect: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgIsect Located"); - PtgIsect pist = new PtgIsect(); - ptgLen = pist.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - - pist.init(b); - pist.setParentRec(p); - stack.push(pist); - break; - - case ptgUnion: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgUnion Located"); - PtgUnion pun = new PtgUnion(); - ptgLen = pun.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pun.init(b); - pun.setParentRec(p); - stack.push(pun); - break; - - case ptgRange: - if (DEBUGLEVEL > 5) - Logger.logInfo("ptgRange Located"); - PtgRange pran = new PtgRange(); - ptgLen = pran.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - pran.init(b); - pran.setParentRec(p); - stack.push(pran); - break; - - case ptgParen: - if (DEBUGLEVEL > 5) - Logger.logInfo("PtgParens Located"); - PtgParen pp = new PtgParen(); - ptgLen = pp.getLength(); - b = new byte[ptgLen]; - if ((ptgLen + i) <= function.length) - System.arraycopy(function, (i), b, 0, ptgLen); - - pp.init(b); - pp.setParentRec(p); - stack.push(pp); - break; - - default: - PtgMystery pmy = new PtgMystery(); - ptgLen = function.length - i; - b = new byte[ptgLen]; - if (DEBUGLEVEL > 0) - Logger.logWarn("Unsupported Formula Function: 0x" - + Integer.toHexString(ptg) + " length: " + ptgLen); - System.arraycopy(function, i, b, 0, ptgLen); - pmy.init(b); - pmy.setParentRec(p); - stack.push(pmy); - break; - } - i += ptgLen; - } - if (hasArrays && rec instanceof Formula) { // Array Recs handle extra - // data differently - // array data is appended to end of expression - // for each array in the function list, - // get saved ptgArray var (stored in stack var), - // grab data and parse array components - int startPos = expressionLen; - for (int i = 0; i < arrayLocs.size(); i++) { - Integer ingr = (Integer) arrayLocs.elementAt(i); - PtgArray parr = (PtgArray) stack.elementAt(ingr.intValue()); - - // have to assume that remaining data all goes for this - // ptgarray - // since length is variable and can only be ascertained by - // parsing - // if multiple arrays are present, actual array length will - // be returned via setArrVals - byte[] b = new byte[function.length - startPos]; // get "extra" - // array - // data - System.arraycopy(function, startPos, b, 0, b.length); - try { - parr.setParentRec(rec); - startPos += parr.setArrVals(b); - } catch (Exception e) {// TODO: this needs to be caught due to - // "name" records being parsed - // incorrectly. The problem has to do - // with the lenght of the name record - // not including the extra 7 bytes of - // space. Temporary fix for infoteria - if (DEBUGLEVEL > 0) - Logger.logInfo("ExpressionParser.parseExpression: Array: " - + e); - } - } - } /* - * no need to keep PtgExtraMem as can regenerate easily else - * if (hasPtgExtraMem && rec instanceof Formula) { - * //The PtgExtraMem structure specifies a range that - * corresponds to a PtgMemArea as specified in RgbExtra.) - * // count (2 bytes): An unsigned integer that specifies - * the areas within the range. - * // array (variable): An array of Ref8U that specifies the - * range. The number of elements MUST be equal to count. - * pma.setPostExpression(function, expressionLen); - * } - */ - if (DEBUGLEVEL > 5) - Logger.logInfo("finished formula"); - return stack; - - } - - /* - * Returns the ptg that matches the string location sent to - * it. - * rec can either be in the format "C5" or a range, such as - * "C4:D9" - * - */ - public static List getPtgsByLocation(String loc, Stack expression) throws FormulaNotFoundException { - List lv = new Vector(); - for (int i = 0; i < expression.size(); i++) { - Object o = expression.elementAt(i); - if (o == null) - throw new FormulaNotFoundException( - "Couldn't get Ptg at: " + loc); - if (o instanceof Byte) { - // do nothing - } else if (o instanceof Ptg) { - Ptg part = (Ptg) o; - String lo = part.getLocation(); - if (lo == null) - lo = "none"; - String comp = loc; - if (loc.indexOf("!") > -1) { // the sheet is referenced - if (lo.indexOf("!") == -1) { // and the ptg does not have - // sheet referenced - comp = loc.substring(loc.indexOf("!") + 1); - } - } - - if (comp.equalsIgnoreCase(lo)) { - lv.add(part); - } else { - // try fq location - lo = part.toString(); - if (loc.equalsIgnoreCase(lo)) { - lv.add(part); - - } else if (o instanceof PtgRef3d) {// gotta look into the - // first & last - // already checked - } else if (o instanceof PtgArea) {// gotta look into the - // first & last - Ptg first = ((PtgArea) o).getFirstPtg(); - Ptg last = ((PtgArea) o).getLastPtg(); - if (first.getLocation().equalsIgnoreCase(loc)) - lv.add(first); - if (last.getLocation().equalsIgnoreCase(loc)) - lv.add(last); - } - } - } - } - return lv; - } - - /** - * returns the position in the expression stack for the ptg associated with this location - * - * @param loc String - * @param expression - * @return - * @throws FormulaNotFoundException - */ - public static int getExpressionLocByLocation(String loc, Stack expression) throws FormulaNotFoundException { - - for (int i = 0; i < expression.size(); i++) { - Object o = expression.elementAt(i); - if (o == null) - throw new FormulaNotFoundException( - "Couldn't get Ptg at: " + loc); - if (o instanceof Byte) { - // do nothing - } else if (o instanceof Ptg) { - Ptg part = (Ptg) o; - String lo = part.getLocation(); - if (loc.equalsIgnoreCase(lo)) { - return i; - } - // try full location - lo = part.toString(); - if (loc.equalsIgnoreCase(lo)) { - return i; - } - if (o instanceof PtgArea) {// gotta look into the first & last - Ptg first = ((PtgArea) o).getFirstPtg(); - Ptg last = ((PtgArea) o).getLastPtg(); - if (first.getLocation().equalsIgnoreCase(loc)) - return i; - if (last.getLocation().equalsIgnoreCase(loc)) - return i; - } - } - } - return -1; - } - - /** - * returns the position in the expression stack for the desired ptg - * - * @param ptg Ptg to lookk up - * @param expression - * @return - * @throws FormulaNotFoundException - */ - public static int getExpressionLocByPtg(Ptg ptg, Stack expression) throws FormulaNotFoundException { - - for (int i = 0; i < expression.size(); i++) { - Object o = expression.elementAt(i); - if (o == null) - throw new FormulaNotFoundException( - "Couldn't get Ptg at: " + ptg.toString()); - if (o instanceof Byte) { - // do nothing - } else if (o instanceof Ptg) { - if (o.equals(ptg)) - return i; - } - } - return -1; - } - - /** - * getCellRangePtgs handles locating which cells are refereced in an expression stack. - *

                  - * Essentially the use is we can check a formula if it refereces a cell that is moving, then we have - * the ability to manipulate these ranges in whatever way makes sense. - * - * @return an array of ptgs that are location based (ptgRef, PtgArea) - * @expression = a Stack of ptgs that represent an excel calculation. - */ - public static Ptg[] getCellRangePtgs(Stack expression) throws FormulaNotFoundException { - Vector ret = new Vector(); - for (int i = 0; i < expression.size(); i++) { - Object o = expression.elementAt(i); - if (o == null) - throw new FormulaNotFoundException("Couldn't get Ptg at: " + i); - if (o instanceof Byte) { - // do nothing - } else if (o instanceof Ptg) { - Ptg part = (Ptg) o; - // handle shared formula range - if (part instanceof PtgExp) { - String lox = part.getLocation(); - PtgRef ref = new PtgRef(); - ref.setParentRec(part.getParentRec()); // must be done - // before - // setLocation - ref.setLocation(lox); - ret.add(ref); - } else if (part instanceof PtgRefErr - || part instanceof PtgAreaErr3d) { - ret.add("#REF!"); - } else if (part instanceof PtgMemFunc) { - // Ptg[] p= - // getCellRangePtgs(((PtgMemFunc)part).getSubExpression()); - Ptg[] p = part.getComponents(); - for (int z = 0; z < p.length; z++) - ret.add(p[z]); - } else { - String lox = part.getLocation(); - if (lox != null) - ret.add(part); - } - } - } - Ptg[] retp = new Ptg[ret.size()]; - return (Ptg[]) ret.toArray(retp); - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/ExpressionParser.kt b/src/main/java/io/starter/formats/XLS/ExpressionParser.kt new file mode 100644 index 0000000..a284212 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/ExpressionParser.kt @@ -0,0 +1,1214 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.formats.XLS.formulas.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger +import java.util.Stack +import java.util.Vector + +/** + * + */ +class ExpressionParser : java.io.Serializable { + companion object { + /** + * + */ + private const val serialVersionUID = 4745215965823234010L + private val DEBUGLEVEL = 0 + /* + * All of the operand values + * + * Section of binary operator PTG's. These pop the two + * top values out of a stack and perform an operation on + * them before pushing back in + */ + // really "special" one, read all about it. + val ptgExp: Short = 0x1 + val ptgAdd: Short = 0x3 + val ptgSub: Short = 0x4 + val ptgMlt: Short = 0x5 + val ptgDiv: Short = 0x6 + val ptgPower: Short = 0x7 + val ptgConcat: Short = 0x8 + val ptgLT: Short = 0x09 + val ptgLE: Short = 0x0a + val ptgEQ: Short = 0x0b + val ptgGE: Short = 0x0c + val ptgGT: Short = 0x0d + val ptgNE: Short = 0x0e + val ptgIsect: Short = 0x0f + val ptgUnion: Short = 0x10 + val ptgRange: Short = 0x11 + // End of binary operator PTG's + + // Unary Operator tokens + val ptgUPlus: Short = 0x12 + val ptgUMinus: Short = 0x13 // todo + val ptgPercent: Short = 0x14 // todo + + // Controls + val ptgParen: Short = 0x15 + val ptgAtr: Short = 0x19 + // End of Controls + + // Constant operators + val ptgMissArg: Short = 0x16 + val ptgStr: Short = 0x17 + val ptgEndSheet: Short = 0x1b + val ptgErr: Short = 0x1c + val ptgBool: Short = 0x1d + val ptgInt: Short = 0x1e + val ptgNum: Short = 0x1f + // End of Constant Operators + + val ptgArray: Short = 0x20 + val ptgFunc: Short = 0x21 + val ptgFuncVar: Short = 0x22 + val ptgName: Short = 0x23 + val ptgRef: Short = 0x24 + val ptgArea: Short = 0x25 + val ptgMemArea: Short = 0x26 + val ptgMemErr: Short = 0x27 + val ptgMemFunc: Short = 0x29 + val ptgRefErr: Short = 0x2a + val ptgAreaErr: Short = 0x2b + val ptgRefN: Short = 0x2c + val ptgAreaN: Short = 0x2d + val ptgNameX: Short = 0x39 + val ptgRef3d: Short = 0x3a + val ptgArea3d: Short = 0x3b + val ptgRefErr3d: Short = 0x3c + + // who knows, added to fix broken Named ranges -jm 03/26/04 + val ptgAreaErr3d: Short = 0x3d + val ptgMemAreaA: Short = 0x66 + val ptgMemAreaNV: Short = 0x4e + val ptgMemAreaN: Short = 0x2e + + /** + * Parse the byte array, create component Ptg's and insert + * them into a stack. + * + * + * + * + * Feb 8, 2010 + * + * @param function + * @param rec + * @return + */ + fun parseExpression(function: ByteArray, rec: XLSRecord): Stack<*> { + return ExpressionParser.parseExpression(function, rec, function.size) + } + + /** + * Parse the byte array, create component Ptg's and insert them into + * a stack. + * + * + * Feb 8, 2010 + * + * @param function + * @param rec + * @param expressionLen + * @return + */ + fun parseExpression(function: ByteArray, rec: XLSRecord, expressionLen: Int): Stack<*> { + var expressionLen = expressionLen + val stack = Stack() + var ptg: Short = 0x0 + var ptgLen = 0 + var hasArrays = false + /* + * Not really needed + * //boolean hasPtgExtraMem= false; + * //PtgMemArea pma= null; + */ + val arrayLocs = CompatibleVector() + if (expressionLen > function.size) + expressionLen = function.size // deal with out of spec formulas + // (testJapanese:Open25.xls) -jm + // KSC: shared formula changes for peformance: now + // PtgRefN's/PtgAreaN's are instantiated and + // reference-tracked (of a sort) ... + + // iterate the expression and create Ptgs. + run { + var i = 0 + while (i < expressionLen) { + // check if the 40 bit is set, is it a Array class? + if (function[i] and 0x40 == 0x40) { + // rec is a value class + // we need to strip the high-order bits and set the 0x20 bit + ptg = (function[i] or 0x20 and 0x3f).toShort() + } else { + // the bit is already set, just strip the high order bits + // rec may be an array class. need to figure rec one out. + ptg = (function[i] and 0x3f).toShort() + } + when (ptg) { + + ptgExp -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgExp Located") + if (i == 0) {// MUST BE THE ONLY PTG in the formula expression + val px = PtgExp() + ptgLen = px.length + val b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + px.parentRec = rec + px.init(b) + stack.push(px) + break + } + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgStr Located") + var x = i + x += 1 // move past the opcode to the cch + ptgLen = function[x] and 0xff // this is the cch + val theGrbit = function[x + 1].toShort()// this is the grbit; + if (theGrbit and 0x1 == 0x1) { + // unicode string + ptgLen = ptgLen * 2 + } + ptgLen += 3 // include the PtgId, cch, & grbit; + val pst = PtgStr() + var b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pst.init(b) + pst.parentRec = rec + stack.push(pst) + } + // ptgStr is one of the only ptg's that varies in length, so + // there is some special handling + // going on for it. + ptgStr -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgStr Located") + var x = i + x += 1 + ptgLen = function[x] and 0xff + val theGrbit = function[x + 1].toShort() + if (theGrbit and 0x1 == 0x1) { + ptgLen = ptgLen * 2 + } + ptgLen += 3 + val pst = PtgStr() + var b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pst.init(b) + pst.parentRec = rec + stack.push(pst) + } + /* */ + + ptgMemAreaA -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemAreaA Located" + function[i]) + x = i + x += 5 // move past the opcode & reserved to the cce + ptgLen = ByteTools.readShort(function[x].toInt(), function[x + 1].toInt()).toInt() // this + // is + // the + // cce + ptgLen += 7 // include the PtgId, cce, & reserv; + val pmema = PtgMemAreaA() + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pmema.init(b) + pmema.parentRec = rec + stack.push(pmema) + } + + ptgMemAreaN -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemAreaN Located" + function[i]) + val pmemn = PtgMemAreaN() + ptgLen = pmemn.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pmemn.init(b) + pmemn.parentRec = rec + stack.push(pmemn) + } + + ptgMemAreaNV -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemAreaNV Located" + function[i]) + x = i + x += 5 // move past the opcode & reserved to the cce + ptgLen = ByteTools.readShort(function[x].toInt(), function[x + 1].toInt()).toInt() // this + // is + // the + // cce + ptgLen += 7 // include the PtgId, cce, & reserv; + val pmemv = PtgMemAreaNV() + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pmemv.init(b) + pmemv.parentRec = rec + stack.push(pmemv) + } + + // ptgMemArea also varies in length... + ptgMemArea -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemArea Located" + function[i]) + ptgLen = 7 + val pmem = PtgMemArea() + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pmem.init(b) + // now grab the rest of the "extra data" that defines the + // ptgmemarea + // these are separate ptgs (PtgArea, PtgRef's ... plus + // PtgUnions) + // that comprise the PtgMemArea coordinates + pmem.parentRec = rec + i += ptgLen // after PtgMemArea record, get subexpression + ptgLen = pmem.getnTokens() + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pmem.setSubExpression(b) + stack.push(pmem) + } + + ptgMemFunc -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemFunc Located") + val pmemf = PtgMemFunc() + x = i + x += 1 // move past the opcode to the cce + ptgLen = ByteTools.readShort(function[x].toInt(), function[x + 1].toInt()).toInt() // this + // is + // the + // cce + ptgLen += 3 // include the PtgId, cce, & reserv; + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pmemf.parentRec = rec + pmemf.init(b) + stack.push(pmemf) + } + + ptgInt -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgInt Located") + val pi = PtgInt() + ptgLen = pi.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pi.init(b) + pi.parentRec = rec + stack.push(pi) + } + + ptgErr -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgErr Located") + val perr = PtgErr() + ptgLen = perr.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + perr.init(b) + perr.parentRec = rec + stack.push(perr) + } + + ptgNum -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgNum Located") + val pnum = PtgNumber() + ptgLen = pnum.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pnum.init(b) + pnum.parentRec = rec + stack.push(pnum) + } + + ptgBool -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgBool Located") + val pboo = PtgBool() + ptgLen = pboo.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pboo.init(b) + pboo.parentRec = rec + stack.push(pboo) + } + + ptgName -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgName Located") + val pn = PtgName() + ptgLen = pn.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pn.parentRec = rec + pn.init(b) + pn.addListener() + stack.push(pn) + val chk = i + ptgLen + if (chk < function.size) { + if (function[i + ptgLen].toInt() == 0x0) { + if (DEBUGLEVEL > 1) + Logger.logWarn("Undocumented Name Record mystery byte encountered in Formula: ") + i++ + } + } + } + + ptgNameX -> { + if (DEBUGLEVEL > 1) + Logger.logInfo("ptgNameX Located") + if (DEBUGLEVEL > 0) + Logger.logWarn("referencing external spreadsheets unsupported.") + val pnx = PtgNameX() + ptgLen = pnx.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pnx.init(b) + pnx.parentRec = rec + pnx.addListener() + stack.push(pnx) + } + + ptgRef -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRef Located ") + val pt = PtgRef() + ptgLen = pt.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pt.parentRec = rec // parent rec must be set before init + pt.init(b) + pt.addToRefTracker() + stack.push(pt) + } + + ptgArray -> { + hasArrays = true + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgArray Located ") + val pa = PtgArray() + ptgLen = 8 // 7 len + id + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pa.init(b) // setArrVals(b); // 20090820 KSC: b represents base + // record not array values + val ingr = Integer.valueOf(stack.size) // constant value + // array for + // PtgArray + // appears at + // end of stack + // see hasArrays + // below + arrayLocs.add(ingr) + stack.push(pa) + } + + ptgRefN -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRefN Located ") + val ptn = PtgRefN(false) + ptgLen = ptn.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + ptn.parentRec = rec // parent rec must be set before init + ptn.init(b) + if (rec.opcode == XLSConstants.SHRFMLA) + ptn.addToRefTracker() + stack.push(ptn) + } + + ptgArea -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgArea Located ") + val pg = PtgArea() + ptgLen = pg.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pg.parentRec = rec // parent rec must be set before init + pg.init(b) + pg.addToRefTracker() + stack.push(pg) + } + + ptgArea3d -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgArea3d Located ") + val pg3 = PtgArea3d() + ptgLen = pg3.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pg3.init(b, rec) // we need this to init the sub-ptgs correctly + pg3.addListener() + pg3.addToRefTracker() + stack.push(pg3) + } + + ptgAreaN -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgAreaN Located ") + val pgn = PtgAreaN() + ptgLen = pgn.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pgn.parentRec = rec + pgn.init(b) + if (rec.opcode == XLSConstants.SHRFMLA) { + pgn.addToRefTracker() + } + stack.push(pgn) + } + + ptgAreaErr3d -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgAreaErr3d Located") + val ptfa = PtgAreaErr3d() + ptgLen = ptfa.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + ptfa.parentRec = rec + ptfa.init(b) + // ptfa.addToRefTracker(); + stack.push(ptfa) + } + + ptgRefErr3d -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRefErr3d Located") + val ptfr = PtgRefErr3d() + ptgLen = ptfr.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + ptfr.parentRec = rec + ptfr.init(b) + // ptfr.addToRefTracker(); + stack.push(ptfr) + } + + ptgMemErr -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMemErr Located") + val pm = PtgMemErr() + ptgLen = pm.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pm.parentRec = rec + pm.init(b) + stack.push(pm) + } + + ptgRefErr -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRefErr Located") + val pr = PtgRefErr() + ptgLen = pr.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pr.parentRec = rec // parent rec must be set before init + pr.init(b) + stack.push(pr) + } + + ptgEndSheet -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgEndSheet Located") + val prs = PtgEndSheet() + ptgLen = prs.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + prs.init(b) + prs.parentRec = rec + stack.push(prs) + } + + ptgRef3d -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRef3d Located") + val pr3 = PtgRef3d() + ptgLen = pr3.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pr3.parentRec = rec + pr3.init(b) + pr3.addListener() + pr3.addToRefTracker() + stack.push(pr3) + // if an External Link i.e. defined in another workbook, + // flag formula as such + if (pr3.isExternalLink && rec.opcode == XLSConstants.FORMULA) + (rec as Formula).setIsExternalRef(true) + } + /* + * PtgAtr is another one of the ugly size-changing ptg's + */ + ptgAtr -> { + val pat = PtgAtr(0.toByte()) + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgAtr Located") + ptgLen = pat.length + if (function[i + 1] and 0x4 == 0x4) { + ptgLen = ByteTools + .readShort(function[i + 2].toInt(), function[i + 3].toInt()).toInt() + ptgLen++ // one extra for some undocumented reason + ptgLen = ptgLen * 2 // seems to be two bytes per... + ptgLen += 4 // add the cch & grbit + } + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pat.init(b) + pat.init() + pat.parentRec = rec + stack.push(pat) + } + + ptgFunc -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgFunc Located") + val ptf = PtgFunc() + ptgLen = ptf.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + ptf.init(b) + ptf.parentRec = rec + stack.push(ptf) + } + + ptgFuncVar -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgFuncVar Located") + val ptfv = PtgFuncVar() + ptgLen = ptfv.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + + ptfv.init(b) + ptfv.parentRec = rec + if (ptfv.functionId.toInt() == FunctionConstants.XLF_INDIRECT) { + /* + * TESTING NEW WAY: + * // New way does not account for expanded shared formula + * references, unfortunately so keep original for new + * + * Stack indirectStack= new Stack(); + * int z= stack.size()-1; + * int nparams= 1; + * for (; z > 0 && nparams > 0; z--) { + * Ptg p= (Ptg) stack.get(z); + * if (p instanceof PtgAtr) { + * continue; + * } + * if(p.getIsOperator()||p.getIsControl()||p.getIsFunction() + * ){ + * if(p.getIsControl() ){ + * if(p.getOpcode() == 0x15) { // its a parens! + * // the parens is already pop'd so just return and it is + * gone... + * continue; + * } + * } + * int t= 0; + * // make sure we have the correct amount popped back in.. + * if (p.getIsBinaryOperator()) t=2; + * if (p.getIsUnaryOperator()) t=1; + * if (p.getIsStandAloneOperator()) t=0; + * if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || + * p.getOpcode() == 0x62){t=p.getNumParams();}// it's a + * ptgfunkvar! + * if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || + * p.getOpcode() == 0x61){t=p.getNumParams();}// guess that + * ptgfunc is not only one.. + * nparams+=t-1; + * if (nparams==0) + * break; + * } else { + * nparams--; + * if (nparams==0) + * break; + * } + * } + * indirectStack.addAll(stack.subList(z, stack.size())); + * indirectStack.push(ptfv); + * rec.getWorkBook().addIndirectFormulaStack(indirectStack); + * // must save and calculate indirect reference AFTER all + * formulas/cells have been added ... + * // original is below + * / + **/ + /**/ + if (rec.opcode == XLSConstants.FORMULA) { + (rec as Formula).setContainsIndirectFunction(true) + } else if (rec.opcode == XLSConstants.SHRFMLA) { + (rec as Shrfmla).setContainsIndirectFunction(true) + } + /**/ + } + stack.push(ptfv) + } + + ptgAdd -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgAdd Located") + val pad = PtgAdd() + ptgLen = pad.length + b = ByteArray(ptgLen) + // if((ptgLen+i) <= function.length) + System.arraycopy(function, i, b, 0, ptgLen) + pad.init(b) + pad.parentRec = rec + stack.push(pad) + } + + ptgMissArg -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgMissArg Located") + val pmar = PtgMissArg() + ptgLen = pmar.length + b = ByteArray(ptgLen) + // if((ptgLen+i) <= function.length) + System.arraycopy(function, i, b, 0, ptgLen) + pmar.init(b) + pmar.parentRec = rec + stack.push(pmar) + } + + ptgSub -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgSub Located") + val psb = PtgSub() + ptgLen = psb.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + psb.init(b) + psb.parentRec = rec + stack.push(psb) + } + + ptgMlt -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgMlt Located") + val pml = PtgMlt() + ptgLen = pml.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pml.init(b) + pml.parentRec = rec + stack.push(pml) + } + + ptgDiv -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgDiv Located") + val pdiv = PtgDiv() + ptgLen = pdiv.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pdiv.init(b) + pdiv.parentRec = rec + stack.push(pdiv) + } + + ptgUPlus -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgUPlus Located") + val puplus = PtgUPlus() + ptgLen = puplus.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + puplus.init(b) + puplus.parentRec = rec + stack.push(puplus) + } + + ptgUMinus -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgUminus Located") + val puminus = PtgUMinus() + ptgLen = puminus.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + puminus.init(b) + puminus.parentRec = rec + stack.push(puminus) + } + + ptgPercent -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgPercent Located") + val pperc = PtgPercent() + ptgLen = pperc.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pperc.init(b) + pperc.parentRec = rec + stack.push(pperc) + } + + ptgPower -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgPower Located") + val pow = PtgPower() + ptgLen = pow.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pow.init(b) + pow.parentRec = rec + stack.push(pow) + } + + ptgConcat -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgConcat Located") + val pcon = PtgConcat() + ptgLen = pcon.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pcon.init(b) + pcon.parentRec = rec + stack.push(pcon) + } + + ptgLT -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgLT Located") + val plt = PtgLT() + ptgLen = plt.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + plt.init(b) + plt.parentRec = rec + stack.push(plt) + } + + ptgLE -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgLE Located") + val ple = PtgLE() + ptgLen = ple.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + ple.init(b) + ple.parentRec = rec + stack.push(ple) + } + + ptgEQ -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgEQ Located") + val peq = PtgEQ() + ptgLen = peq.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + peq.init(b) + peq.parentRec = rec + stack.push(peq) + } + + ptgGE -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgGE Located") + val pge = PtgGE() + ptgLen = pge.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pge.init(b) + pge.parentRec = rec + stack.push(pge) + } + + ptgGT -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgGT Located") + val pgt = PtgGT() + ptgLen = pgt.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pgt.init(b) + pgt.parentRec = rec + stack.push(pgt) + } + + ptgNE -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgNE Located") + val pne = PtgNE() + ptgLen = pne.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + + pne.init(b) + pne.parentRec = rec + stack.push(pne) + } + + ptgIsect -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgIsect Located") + val pist = PtgIsect() + ptgLen = pist.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + + pist.init(b) + pist.parentRec = rec + stack.push(pist) + } + + ptgUnion -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgUnion Located") + val pun = PtgUnion() + ptgLen = pun.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pun.init(b) + pun.parentRec = rec + stack.push(pun) + } + + ptgRange -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("ptgRange Located") + val pran = PtgRange() + ptgLen = pran.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + pran.init(b) + pran.parentRec = rec + stack.push(pran) + } + + ptgParen -> { + if (DEBUGLEVEL > 5) + Logger.logInfo("PtgParens Located") + val pp = PtgParen() + ptgLen = pp.length + b = ByteArray(ptgLen) + if (ptgLen + i <= function.size) + System.arraycopy(function, i, b, 0, ptgLen) + + pp.init(b) + pp.parentRec = rec + stack.push(pp) + } + + else -> { + val pmy = PtgMystery() + ptgLen = function.size - i + b = ByteArray(ptgLen) + if (DEBUGLEVEL > 0) + Logger.logWarn("Unsupported Formula Function: 0x" + + Integer.toHexString(ptg.toInt()) + " length: " + ptgLen) + System.arraycopy(function, i, b, 0, ptgLen) + pmy.init(b) + pmy.parentRec = rec + stack.push(pmy) + } + }// hasPtgExtraMem= true; // has a PtgExtraMem structure + // after end of parsed expression: The PtgExtraMem structure + // specifies a range that corresponds to a PtgMemArea as + // specified in RgbExtra. + // pma= pmem; // save for later + i += ptgLen + } + } + if (hasArrays && rec is Formula) { // Array Recs handle extra + // data differently + // array data is appended to end of expression + // for each array in the function list, + // get saved ptgArray var (stored in stack var), + // grab data and parse array components + var startPos = expressionLen + for (i in arrayLocs.indices) { + val ingr = arrayLocs.elementAt(i) as Int + val parr = stack.elementAt(ingr) as PtgArray + + // have to assume that remaining data all goes for this + // ptgarray + // since length is variable and can only be ascertained by + // parsing + // if multiple arrays are present, actual array length will + // be returned via setArrVals + val b = ByteArray(function.size - startPos) // get "extra" + // array + // data + System.arraycopy(function, startPos, b, 0, b.size) + try { + parr.parentRec = rec + startPos += parr.setArrVals(b) + } catch (e: Exception) {// TODO: this needs to be caught due to + // "name" records being parsed + // incorrectly. The problem has to do + // with the lenght of the name record + // not including the extra 7 bytes of + // space. Temporary fix for infoteria + if (DEBUGLEVEL > 0) + Logger.logInfo("ExpressionParser.parseExpression: Array: $e") + } + + } + } /* + * no need to keep PtgExtraMem as can regenerate easily else + * if (hasPtgExtraMem && rec instanceof Formula) { + * //The PtgExtraMem structure specifies a range that + * corresponds to a PtgMemArea as specified in RgbExtra.) + * // count (2 bytes): An unsigned integer that specifies + * the areas within the range. + * // array (variable): An array of Ref8U that specifies the + * range. The number of elements MUST be equal to count. + * pma.setPostExpression(function, expressionLen); + * } + */ + if (DEBUGLEVEL > 5) + Logger.logInfo("finished formula") + return stack + + } + + /* + * Returns the ptg that matches the string location sent to + * it. + * rec can either be in the format "C5" or a range, such as + * "C4:D9" + * + */ + @Throws(FormulaNotFoundException::class) + fun getPtgsByLocation(loc: String, expression: Stack<*>): List { + val lv = Vector() + for (i in expression.indices) { + val o = expression.elementAt(i) ?: throw FormulaNotFoundException( + "Couldn't get Ptg at: $loc") + if (o is Byte) { + // do nothing + } else if (o is Ptg) { + var lo: String? = o.location + if (lo == null) + lo = "none" + var comp = loc + if (loc.indexOf("!") > -1) { // the sheet is referenced + if (lo.indexOf("!") == -1) { // and the ptg does not have + // sheet referenced + comp = loc.substring(loc.indexOf("!") + 1) + } + } + + if (comp.equals(lo, ignoreCase = true)) { + lv.add(o) + } else { + // try fq location + lo = o.toString() + if (loc.equals(lo, ignoreCase = true)) { + lv.add(o) + + } else if (o is PtgRef3d) {// gotta look into the + // first & last + // already checked + } else if (o is PtgArea) {// gotta look into the + // first & last + val first = o.firstPtg + val last = o.lastPtg + if (first!!.location.equals(loc, ignoreCase = true)) + lv.add(first) + if (last!!.location.equals(loc, ignoreCase = true)) + lv.add(last) + } + } + } + } + return lv + } + + /** + * returns the position in the expression stack for the ptg associated with this location + * + * @param loc String + * @param expression + * @return + * @throws FormulaNotFoundException + */ + @Throws(FormulaNotFoundException::class) + fun getExpressionLocByLocation(loc: String, expression: Stack<*>): Int { + + for (i in expression.indices) { + val o = expression.elementAt(i) ?: throw FormulaNotFoundException( + "Couldn't get Ptg at: $loc") + if (o is Byte) { + // do nothing + } else if (o is Ptg) { + var lo = o.location + if (loc.equals(lo, ignoreCase = true)) { + return i + } + // try full location + lo = o.toString() + if (loc.equals(lo, ignoreCase = true)) { + return i + } + if (o is PtgArea) {// gotta look into the first & last + val first = o.firstPtg + val last = o.lastPtg + if (first!!.location.equals(loc, ignoreCase = true)) + return i + if (last!!.location.equals(loc, ignoreCase = true)) + return i + } + } + } + return -1 + } + + /** + * returns the position in the expression stack for the desired ptg + * + * @param ptg Ptg to lookk up + * @param expression + * @return + * @throws FormulaNotFoundException + */ + @Throws(FormulaNotFoundException::class) + fun getExpressionLocByPtg(ptg: Ptg, expression: Stack<*>): Int { + + for (i in expression.indices) { + val o = expression.elementAt(i) ?: throw FormulaNotFoundException( + "Couldn't get Ptg at: $ptg") + if (o is Byte) { + // do nothing + } else if (o is Ptg) { + if (o == ptg) + return i + } + } + return -1 + } + + /** + * getCellRangePtgs handles locating which cells are refereced in an expression stack. + * + * + * Essentially the use is we can check a formula if it refereces a cell that is moving, then we have + * the ability to manipulate these ranges in whatever way makes sense. + * + * @return an array of ptgs that are location based (ptgRef, PtgArea) + * @expression = a Stack of ptgs that represent an excel calculation. + */ + @Throws(FormulaNotFoundException::class) + fun getCellRangePtgs(expression: Stack<*>): Array { + val ret = Vector() + for (i in expression.indices) { + val o = expression.elementAt(i) ?: throw FormulaNotFoundException("Couldn't get Ptg at: $i") + if (o is Byte) { + // do nothing + } else if (o is Ptg) { +// handle shared formula range + if (o is PtgExp) { + val lox = o.location + val ref = PtgRef() + ref.parentRec = o.parentRec // must be done + // before + // setLocation + ref.location = lox + ret.add(ref) + } else if (o is PtgRefErr || o is PtgAreaErr3d) { + ret.add("#REF!") + } else if (o is PtgMemFunc) { + // Ptg[] p= + // getCellRangePtgs(((PtgMemFunc)part).getSubExpression()); + val p = o.components + for (z in p.indices) + ret.add(p[z]) + } else { + val lox = o.location + if (lox != null) + ret.add(o) + } + } + } + val retp = arrayOfNulls(ret.size) + return ret.toTypedArray() as Array + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Externname.java b/src/main/java/io/starter/formats/XLS/Externname.java deleted file mode 100644 index 6bacee0..0000000 --- a/src/main/java/io/starter/formats/XLS/Externname.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -// TODO: Handle other types of External Names besides Add-Ins - -/** - * Externname: External Name Record (17h)
                  - * - *

                  - * for add-in formulas (1 for each add-in formula)
                  - *
                  - * offset  name        size    contents
                  - * ---
                  - * 0       Op Flags 		2       Always 00 for Add-ins
                  - * 2		Not used		4
                  - * 6		Name			var.	Unicode formula name (1st 2 bytes are length)
                  - * var.	#REF Err Code	4		always 02 00 1C 17  (2 0 28 23)
                  - *
                  - * for external names
                  - * offset  name        size    contents
                  - * ---
                  - * 0       Op Flags 		2       Always 00 for Add-ins
                  - * 2		INDEX			2		One-based index to EXTERNSHEET
                  - * 4		Not used		2
                  - * 6		Name			var.	External name
                  - * var.	Formula data	var.	RPN Token Array
                  - * 

                  - * - * @see WorkBook - * @see Boundsheet - * @see Externsheet - */ -public final class Externname extends XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7153354861666069899L; - - public void setWorkBook(WorkBook bk) { - super.setWorkBook(bk); - } - - public void init() { - super.init(); - short externnametype = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - if (externnametype == 0) {// add-in - // read in length of external name - short len = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - byte[] b = this.getBytesAt(8, len); - String s = new String(b); - getWorkBook().addExternalName(s); // store external names in workbook - } - } - - static final byte[] ENDOFRECORDBYTES = {0x2, 0x0, 0x1C, 0x17}; - static final int STRINGLENPOS = 6; - static final int STRINGPOS = 8; - static final int STATICPORTIONSIZE = 12; // header=6, endofrecord=4, strlength=2 - - // TODO: Finish for other types of external names (???) - protected static XLSRecord getPrototype(String s) { - Externname x = new Externname(); - int len = s.length(); - x.setLength((short) (STATICPORTIONSIZE + len)); - x.setOpcode(EXTERNNAME); - byte[] dta = new byte[STATICPORTIONSIZE + len]; - // write string - try { - // write length - byte[] slen = ByteTools.shortToLEBytes((short) len); - dta[STRINGLENPOS] = slen[0]; - dta[STRINGLENPOS + 1] = slen[1]; - // write string - byte[] bts = s.getBytes(); - System.arraycopy(bts, 0, dta, STRINGPOS, bts.length); - // write end of record - System.arraycopy(ENDOFRECORDBYTES, 0, dta, STRINGPOS + len, ENDOFRECORDBYTES.length); - - x.setData(dta); - x.originalsize = STATICPORTIONSIZE + len; - } catch (Exception e) { - Logger.logWarn("Exception excountered writing Externname: " + e.toString()); - } - return x; -// cch = bts.length/2; - // byte[] newbytes = new byte[cch+3]; -// byte[] cchx = io.starter.toolkit.ByteTools.shortToLEBytes((short)cch); - // newbytes[0] = cchx[0]; - // newbytes[1] = cchx[1]; - // newbytes[2] = 0x1; - // System.arraycopy(bts,0,newbytes,3, bts.length); - // this.setData(newbytes); - } - - public String[] getExternalNames() { - return getWorkBook().getExternalNames(); - } - - public String getExternalName(int t) { - return getWorkBook().getExternalName(t); - } -} diff --git a/src/main/java/io/starter/formats/XLS/Externname.kt b/src/main/java/io/starter/formats/XLS/Externname.kt new file mode 100644 index 0000000..ffe9d45 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Externname.kt @@ -0,0 +1,134 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +// TODO: Handle other types of External Names besides Add-Ins + +/** + * **Externname: External Name Record (17h)**

                  + * + * + *
                  + * for add-in formulas (1 for each add-in formula)
                  + *
                  + * offset  name        size    contents
                  + * ---
                  + * 0       Op Flags 		2       Always 00 for Add-ins
                  + * 2		Not used		4
                  + * 6		Name			var.	Unicode formula name (1st 2 bytes are length)
                  + * var.	#REF Err Code	4		always 02 00 1C 17  (2 0 28 23)
                  + *
                  + * for external names
                  + * offset  name        size    contents
                  + * ---
                  + * 0       Op Flags 		2       Always 00 for Add-ins
                  + * 2		INDEX			2		One-based index to EXTERNSHEET
                  + * 4		Not used		2
                  + * 6		Name			var.	External name
                  + * var.	Formula data	var.	RPN Token Array
                  +
                  * + * + * @see WorkBook + * + * @see Boundsheet + * + * @see Externsheet + */ +class Externname : XLSRecord() { + + override var workBook: WorkBook? + get + set(bk) { + super.workBook = bk + } + + val externalNames: Array + get() = workBook!!.externalNames + + override fun init() { + super.init() + val externnametype = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + if (externnametype.toInt() == 0) {// add-in + // read in length of external name + val len = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + val b = this.getBytesAt(8, len.toInt()) + val s = String(b!!) + workBook!!.addExternalName(s) // store external names in workbook + } + } + + fun getExternalName(t: Int): String { + return workBook!!.getExternalName(t) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -7153354861666069899L + + internal val ENDOFRECORDBYTES = byteArrayOf(0x2, 0x0, 0x1C, 0x17) + internal val STRINGLENPOS = 6 + internal val STRINGPOS = 8 + internal val STATICPORTIONSIZE = 12 // header=6, endofrecord=4, strlength=2 + + // TODO: Finish for other types of external names (???) + fun getPrototype(s: String): XLSRecord { + val x = Externname() + val len = s.length + x.length = (STATICPORTIONSIZE + len).toShort() + x.opcode = XLSConstants.EXTERNNAME + val dta = ByteArray(STATICPORTIONSIZE + len) + // write string + try { + // write length + val slen = ByteTools.shortToLEBytes(len.toShort()) + dta[STRINGLENPOS] = slen[0] + dta[STRINGLENPOS + 1] = slen[1] + // write string + val bts = s.toByteArray() + System.arraycopy(bts, 0, dta, STRINGPOS, bts.size) + // write end of record + System.arraycopy(ENDOFRECORDBYTES, 0, dta, STRINGPOS + len, ENDOFRECORDBYTES.size) + + x.setData(dta) + x.originalsize = STATICPORTIONSIZE + len + } catch (e: Exception) { + Logger.logWarn("Exception excountered writing Externname: $e") + } + + return x + // cch = bts.length/2; + // byte[] newbytes = new byte[cch+3]; + // byte[] cchx = io.starter.toolkit.ByteTools.shortToLEBytes((short)cch); + // newbytes[0] = cchx[0]; + // newbytes[1] = cchx[1]; + // newbytes[2] = 0x1; + // System.arraycopy(bts,0,newbytes,3, bts.length); + // this.setData(newbytes); + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/Externsheet.java b/src/main/java/io/starter/formats/XLS/Externsheet.java deleted file mode 100644 index 01636c6..0000000 --- a/src/main/java/io/starter/formats/XLS/Externsheet.java +++ /dev/null @@ -1,685 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.formats.XLS.formulas.IxtiListener; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.util.Iterator; - -/** - * Externsheet: External Sheet Record (17h)
                  - * - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       cXTI        2       Number of XTI Structures
                  - * 6       rgXTI       var     Array of XTI Structures
                  - *
                  - * XTI
                  - * offset  name        size    contents
                  - * ---
                  - * 0       iSUPBOOK    2       0-based index to table of SUPBOOK records
                  - * 2       itabFirst   2       0-based index to first sheet tab in reference
                  - * 4       itabLast    2       0-based index to last sheet tab in reference
                  - *
                  - * 

                  - * - * @see WorkBook - * @see Boundsheet - * @see Supbook - */ - -public final class Externsheet extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4460757130836967839L; - short cXTI = 0; - // int DEBUGLEVEL = 10; - CompatibleVector rgs = new CompatibleVector(); - - public void preStream() { - this.update(); - } - - public void setWorkBook(WorkBook bk) { - super.setWorkBook(bk); - Iterator it = rgs.iterator(); - while (it.hasNext()) - ((rgxti) it.next()).setWorkBook(bk); - } - - public void addPtgListener(IxtiListener p) - throws WorkSheetNotFoundException { - short ix = p.getIxti(); - // if(ix>0)ix--; - if (this.rgs.size() > ix) { - rgxti rg = (rgxti) this.rgs.get(ix); - rg.addListener(p); - } else { - rgxti rg = new rgxti(); - rg.setWorkBook(this.wkbook); // 20080306 KSC - rg.setSheet1(ix); // 20080306 KSC: use actual sheet# this.wkbook.getWorkSheetByNumber(ix)); - rg.setSheet2(ix); // "" this.wkbook.getWorkSheetByNumber(ix)); - this.rgs.add(rg); - rg.addListener(p); - this.update(); - } - } - - - public Boundsheet[] getBoundSheets(int cLoc) { - - if (rgs.size() == 0) - return null; - - if (cLoc > rgs.size() - 1) - cLoc = rgs.size() - 1; - - rgxti rg = (rgxti) rgs.get(cLoc); - - short first = rg.sheet1num; - short last = rg.sheet2num; - if (first == (short) 0xFFFE) // associated with a Name record - return null; - if (first == (short) 0xFFFF) // 20080212 KSC - should be a ref to a deleted or unfound sheet - return null; // error trap trying to get virtual sheet - - int numshts = (++last) - first; - if (numshts < 1) numshts = 1; - Boundsheet[] bs = new Boundsheet[numshts]; - int p = 0; - for (int t = first; t < last; t++) { - try { - bs[p++] = this.wkbook.getWorkSheetByNumber(t); - } catch (WorkSheetNotFoundException e) { - // don't error out on the external workbook sheet references - if (DEBUGLEVEL > 1 && t != 65535 && !rg.bIsExternal) // 20080306 KSC: add external check ... - Logger.logWarn("Attempt to access Externsheet reference for sheet failed: " + e); - } - } - return bs; - } - - /** - * returns array of referenced sheet names, including external references ... - * - * @param cLoc - * @return - */ - public String[] getBoundSheetNames(int cLoc) { - if (rgs.size() == 0) - return null; - if (cLoc > rgs.size() - 1) - cLoc = rgs.size() - 1; - - rgxti rg = (rgxti) rgs.get(cLoc); - - short first = rg.sheet1num; - short last = rg.sheet2num; - - if (first == (short) 0xFFFE) // associated with a Name record (=Add-in) - return new String[]{"AddIn"}; - - if (first == (short) 0xFFFF) // is a ref to a deleted or un-found sheet - return new String[]{"#REF!"}; - - int numshts = (++last) - first; - if (numshts < 1) numshts = 1; - if (first < 0) - first = 1; - - String[] sheets = new String[numshts]; - if (first == last) - return new String[]{"#REF!"}; - int p = 0; - for (int t = first; t < last; t++) { - try { - sheets[p++] = rg.getSheetName(t); // should successfully retrieve External Sheetnames - } catch (WorkSheetNotFoundException we) { - if (DEBUGLEVEL > 1) - Logger.logWarn("Attempt to access Externsheet reference for sheet failed: " + we); - } - } - return sheets; - } - - /** - * returns true if the passed in sheet number is an - * external link (i.e. an external sheet reference) - * - * @param loc external sheet number - * @return - */ - public boolean getIsExternalLink(int loc) { - if (rgs.size() == 0) - return false; - - rgxti rg = (rgxti) rgs.get(loc); - return rg.getIsExternal(); - } - - /** - * get the number of refs in this Externsheet rec - */ - public int getcXTI() { - return cXTI; - } - - public void init() { - super.init(); - cXTI = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - int pos = 2; - for (int t = 0; t < cXTI; t++) { - try { - byte[] bts = this.getBytesAt(pos, 6); // System.arraycopy(rkdata,pos,bts,0,6); - rgxti rg = new rgxti(bts); - rgs.add(rg); - if (wkbook != null) - rg.setWorkBook(wkbook); - } catch (Exception e) { - if (DEBUGLEVEL > 10) Logger.logWarn("init of Externsheet record failed: " + e); - } - pos += 6; - } - if (DEBUGLEVEL > 10) Logger.logInfo("Done Creating Externsheet"); - } - - /** - * update the underlying bytes by recreating from the properties - */ - void update() { - // init a new byte array - int blen = rgs.size() * 6; - blen += 2; - byte[] newbytes = new byte[blen]; - this.cXTI = (short) (rgs.size() - 1); - // get the number of structs - byte[] cx = ByteTools.shortToLEBytes((short) rgs.size()); - System.arraycopy(cx, 0, newbytes, 0, 2); - - // iterate the structs and get the bytes - int pos = 2; - Iterator it = rgs.iterator(); - while (it.hasNext()) { - byte[] btx = ((rgxti) it.next()).getBytes(); - System.arraycopy(btx, 0, newbytes, pos, 6); - pos += 6; - } - - // set the data - this.setData(newbytes); - } - - /** - * Remove a sheet from this reference table, - *

                  - * Also updates the ixti listeners, which appear to be records that contain an internal - * reference to a sheet number. - */ - void removeSheet(int sheetnum) - throws WorkSheetNotFoundException { - if (DEBUGLEVEL > 10) Logger.logInfo("Removing Sheet from Externsheet"); - // iterate the cXTI and check if this sheet is contained - Iterator it = rgs.iterator(); - while (it.hasNext()) { - rgxti rg = (rgxti) it.next(); - int first = rg.sheet1num; - int last = rg.sheet2num; - if ((sheetnum == first) && (sheetnum == last)) { - // reference should not be removed, rather set to -1; - rg.setSheet1(0xFFFF); - rg.setSheet2(0xFFFF); - this.cXTI--; - } else if ((sheetnum >= first) && (sheetnum <= last)) { // it's contained in the ref - rg.setSheet2(rg.getSheet2() - 1); - } else if (sheetnum <= first) { - rg.setSheet1(rg.getSheet1() - 1); - rg.setSheet2(rg.getSheet2() - 1); - } - } - this.update(); - } - - /* 20100506 KSC: this is not necessary any longer since - * rg entries are not removed upon deletes or moved in any operations - * therefore the ixti will stay constant - * -- for deleted sheets, the sheet reference will be set to the 0xFFFE deleted - * sheet reference - public void notifyIxtiListeners() { - Iterator it = rgs.iterator(); - while(it.hasNext()) { - ((rgxti)it.next()).notifyListeners(); - } - } - */ - - /** - * add a sheet to this reference table - *

                  - * why doesn't this return the new referenced int? NR - */ - void addSheet(int sheetnum) - throws WorkSheetNotFoundException { - this.addSheet(sheetnum, sheetnum); - } - - private byte getAddInIndex(Supbook[] sb) { - int i = 0; - while (i < sb.length) { - if (sb[i].isAddInRecord()) - return (byte) i; - i++; - } - return (byte) -1; - } - - private byte getGlobalSupBookIndex(Supbook[] sb) { - int i = 0; - while (i < sb.length) { - if (sb[i].isGlobalRecord()) - return (byte) i; - i++; - } - return (byte) -1; - } - - /** - * add a sheet range this reference table - */ - void addSheet(int firstSheet, int lastSheet) - throws WorkSheetNotFoundException { - if (DEBUGLEVEL > 10) Logger.logInfo("Adding new Sheet to Externsheet"); - - // KSC: Added logic to set correct supbook index for added XTI - byte[] bts = new byte[6]; - if (this.wkbook != null) { // should never happen! - Supbook[] sb = this.wkbook.getSupBooks(); // must have SUPBOOK records when have an EXTERNSHEET! - if (firstSheet == 0xFFFE) // then link to ADD-IN SUPBOOK - bts[0] = getAddInIndex(sb); - else {// link to global SUPBOOK record - bts[0] = getGlobalSupBookIndex(sb); - } - } - rgxti newcXTI = new rgxti(bts); - newcXTI.setWorkBook(this.wkbook); // 20080306 KSC - if (firstSheet == 0xFFFE) // it's a virtual sheet range for Add-ins - newcXTI.setIsAddIn(true); - - if (!newcXTI.getIsAddIn()) { - newcXTI.setSheet1(firstSheet); - newcXTI.setSheet2(lastSheet); - } - rgs.add(newcXTI); - - this.cXTI++; - this.update(); - } - - public short addExternalSheetRef(String externalWorkbook, String externalSheetName) { - // get the external supbook record for this external workbook, creates if not present - Supbook sb = this.wkbook.getExternalSupbook(externalWorkbook, true); - short sheetRef = sb.addExternalSheetReference(externalSheetName); - short sbRef = (short) this.wkbook.getSupbookIndex(sb); - - /* see if external ref exists already */ - Iterator it = rgs.iterator(); - int i = 0; - while (it.hasNext()) { - rgxti rg = (rgxti) it.next(); - if (rg.sheet1num == sheetRef && rg.sheet2num == sheetRef && rg.sbs == sbRef) - return (short) i; - i++; - } - - byte[] bts = new byte[6]; - System.arraycopy(ByteTools.shortToLEBytes(sbRef), 0, bts, 0, 2); // input SUPBOOK ref # - System.arraycopy(ByteTools.shortToLEBytes(sheetRef), 0, bts, 2, 2); // input Sheet ref # - System.arraycopy(ByteTools.shortToLEBytes(sheetRef), 0, bts, 4, 2); // input Sheet ref # - rgxti newcXTI = new rgxti(bts); - newcXTI.setIsExternalRef(true); // flag don't look up worksheets in this workbook - newcXTI.setWorkBook(this.wkbook); // 20080306 KSC - rgs.add(newcXTI); - - this.cXTI++; - this.update(); - return this.cXTI; - } - - /** - * Insert location checks if a specific boundsheet range already has a reference. - *

                  - * If the range already exists within the externsheet the index to the - * range is returned. Else, it adds the range to the externsheet and returns the index. - * - * @param firstBound - * @param lastBound - * @return - */ - public int insertLocation(int firstBound, int lastBound) - throws WorkSheetNotFoundException { - Iterator it = rgs.iterator(); - int i = 0; - while (it.hasNext()) { - rgxti rg = (rgxti) it.next(); - int first = rg.sheet1num; // 20080306 KSC: getSheet1Num(); - int last = rg.sheet2num; // getSheet2Num(); - - if (first == firstBound && last == lastBound && rg.sb.isGlobalRecord()) { - return i; - } - i++; - } - this.addSheet(firstBound, lastBound); - return rgs.size() - 1; - } - - /** - * Constructor - */ - protected static XLSRecord getPrototype() { - Externsheet x = new Externsheet(); - x.setLength((short) 8); - x.setOpcode(EXTERNSHEET); - byte[] dta = new byte[8]; - dta[0] = (byte) 0x1; // put a reference to sheet1 in as initial value - x.setData(dta); - x.originalsize = 8; - x.init(); - return x; - } - - /** - * Add new Externsheet record and set sheet - * - * @param sheetNum1 - * @param sheetNum2 - * @param bk - * @return - */ - protected static XLSRecord getPrototype(int sheetNum1, int sheetNum2, WorkBook bk) { - Externsheet x = (Externsheet) getPrototype(); - try { - x.cXTI--; - x.rgs.remove(0); - x.setWorkBook(bk); // must, for addSheet -// x.addSheet(sheetNum1, sheetNum2); - } catch (Exception e) { - Logger.logWarn("ExternSheet.getPrototype error:" + e.toString()); - } - return x; - } - - /** - * Gets the xti reference for a boundsheet name - * - * @return xti reference, -1 if not located. - */ - public int getXtiReference(String firstSheet, String secondSheet) { - for (int i = 0; i < rgs.size(); i++) { - rgxti thisXti = (rgxti) rgs.elementAt(i); - try { - if (thisXti.getSheetName(thisXti.sheet1num).equalsIgnoreCase(firstSheet) && thisXti.getSheetName(thisXti.sheet2num).equalsIgnoreCase(secondSheet)) { - return i; - } - } catch (WorkSheetNotFoundException we) { - if (DEBUGLEVEL > 10) - Logger.logWarn("Externsheet.getXtiReference: Attempt to find Externsheet reference for sheet failed: " + we); - } - } - return -1; - } - - /** - * Certain records require a virtual reference, this is not a real reference to a sheet, - * rather an entry that is used by add in formulas, values are FE FF FE FF - *

                  - * This method either finds the existing reference, or creates a new one and returns - * the pointer - * - * @return - */ - public int getVirtualReference() { - for (int i = 0; i < rgs.size(); i++) { - rgxti thisXti = (rgxti) rgs.elementAt(i); - if (thisXti.sheet1num == (short) 0xFFFE && - thisXti.sheet2num == (short) 0xFFFE) - return i; - } - byte[] bts = new byte[6]; - if (this.wkbook != null) { - Supbook[] sb = this.wkbook.getSupBooks(); - bts[0] = getAddInIndex(sb); - } - rgxti newcXTI = new rgxti(bts); - newcXTI.setWorkBook(this.wkbook); - newcXTI.setSheet1(0xFFFE/*null*/); - newcXTI.setSheet2(0xFFFE/*null*/); - rgs.add(newcXTI); - this.cXTI++; - this.update(); - return rgs.size() - 1; - } - - /** - * In some cases, we need to have an Xti reference to a non-existing sheet. For instance, a chart - * or formula with a ptgRef3d that referrs to a missing sheet. Externsheet handles this by having - * an internal record populated with -1's This method searches for a non-existing record, and if - * that doesn't exist it creates on and passes the reference back. - * - * @return pointer to broken (-1) reference - */ - public int getBrokenXtiReference() { - for (int i = 0; i < rgs.size(); i++) { - rgxti thisXti = (rgxti) rgs.elementAt(i); - if (thisXti.sheet1num/*getSheet1Num()*/ == 0xFFFF && - thisXti.sheet2num/*getSheet2Num()*/ == 0xFFFF) - return i; - } - byte[] bts = new byte[6]; - if (this.wkbook != null) { - Supbook[] sb = this.wkbook.getSupBooks(); - bts[0] = getGlobalSupBookIndex(sb); - } - rgxti newcXTI = new rgxti(bts); - newcXTI.setWorkBook(this.wkbook); - newcXTI.setSheet1(0xFFFF); - newcXTI.setSheet2(0xFFFF); - rgs.add(newcXTI); - this.cXTI++; - this.update(); - return rgs.size() - 1; - } - - public void close() { - while (rgs.size() > 0) - rgs.remove(0); - } - - /** - * Internal structure tracks Sheet references - */ - class rgxti implements java.io.Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1591367957030959727L; - short sbs = 0; // supbook # - short sheet1num, sheet2num; // store numbers as sometimes sheets are not in workbook ... - Supbook sb = null; - WorkBook wkbook = null; - byte[] bts = null; - private boolean bIsAddIn = false; // flag if this is "iSupbook" Externsheet - private boolean bIsExternal = false; // flag if this is an external ref. - // contains references to virtual 0xFFFE sheet # - - CompatibleVector listeners = new CompatibleVector(); - - public void addListener(IxtiListener p) { - listeners.add(p); - } - - public void notifyListeners() { - Iterator it = listeners.iterator(); - int locp = rgs.indexOf(this); - while (it.hasNext()) { - IxtiListener p = (IxtiListener) it.next(); - if (locp == -1) - ((Ptg) p).getParentRec().remove(true); // this reference has been deleted, rec not valid - else - p.setIxti((short) locp); - } - } - - /** - * default constructor - */ - rgxti() { - // do something? - } - - void setWorkBook(WorkBook bk) { - this.wkbook = bk; - sbs = ByteTools.readShort(bts[0], bts[1]); // supbook # - sb = wkbook.getSupBooks()[sbs]; // get supbook referenced by sbs - - sheet1num = ByteTools.readShort(bts[2], bts[3]); - sheet2num = ByteTools.readShort(bts[4], bts[5]); - - if (sheet1num == 0xFFFE) - bIsAddIn = true; - - if (sb.isExternalRecord()) - bIsExternal = true; - - } - - /** - * constructor used to init from bytes without a book - * - * @param initbytes - */ - rgxti(byte[] initbytes) { - this.bts = initbytes; - } - - /** - * @return Returns the bts. - */ - public byte[] getBytes() { - if (bts == null) bts = new byte[6]; - byte[] shtbt = ByteTools.shortToLEBytes(sheet1num); - System.arraycopy(shtbt, 0, bts, 2, 2); - shtbt = ByteTools.shortToLEBytes(sheet2num); - System.arraycopy(shtbt, 0, bts, 4, 2); - return bts; - } - - /** - * return the sheet name for the sheetNum in the associated supbook - */ - public String getSheetName(int sheetNum) throws WorkSheetNotFoundException { - if (bIsAddIn) // no sheets assoc; return virtual sheet # - return "Virtual Sheet Range 0xFFFE - 0xFFFE"; - if (bIsExternal) - return sb.getExternalSheetName(sheetNum); - if (sheetNum == 0xFFFF) - return "Deleted Sheet"; - //otherwise, try and get sheetname - return wkbook.getWorkSheetByNumber(sheetNum).getSheetName(); - - /* - if(this.sheet1==null) // if sheet ref is deleted, return 0xFFFF - return "null"; //-1; - return this.sheet1.getSheetName(); - */ - } - - public void setIsAddIn(boolean bIsAddin) { - this.bIsAddIn = bIsAddin; - } - - public boolean getIsAddIn() { - return bIsAddIn; - } - - /** - * set if this rgi references an external SUPBOOK record - * i.e. one that references an External Workbook - * (therefore sheet references are not found in the current workbook) - * - * @param bIsExternal - */ - public void setIsExternalRef(boolean bIsExternal) { - this.bIsExternal = bIsExternal; - } - - /** - * return if this rgi references an external SUPBOOK record - * i.e. one that references an External Workbook - * (therefore sheet references are not found in the current workbook) - */ - public boolean getIsExternal() { - return this.bIsExternal; - } - - /** - * set the sheet# referenced - * - * @param int sheet # - */ - public void setSheet1(int sh1) { - this.sheet1num = (short) sh1; - } - - public int getSheet1() { - return (int) this.sheet1num; - } - - /** - * set the sheet# referenced - * - * @param int sheet # - */ - public void setSheet2(int sh2) { - this.sheet2num = (short) sh2; - } - - public int getSheet2() { - return (int) this.sheet2num; - } - - public String toString() { - - try { - return "rgxti range: " + this.getSheetName(sheet1num) + "-" + this.getSheetName(sheet2num); - } catch (WorkSheetNotFoundException we) { - } - return "rgxti range: sheets not initialized"; - } - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Externsheet.kt b/src/main/java/io/starter/formats/XLS/Externsheet.kt new file mode 100644 index 0000000..23f2526 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Externsheet.kt @@ -0,0 +1,702 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.formats.XLS.formulas.IxtiListener +import io.starter.formats.XLS.formulas.Ptg +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + +/** + * **Externsheet: External Sheet Record (17h)**

                  + * + * + *

                  + * offset  name        size    contents
                  + * ---
                  + * 4       cXTI        2       Number of XTI Structures
                  + * 6       rgXTI       var     Array of XTI Structures
                  + *
                  + * XTI
                  + * offset  name        size    contents
                  + * ---
                  + * 0       iSUPBOOK    2       0-based index to table of SUPBOOK records
                  + * 2       itabFirst   2       0-based index to first sheet tab in reference
                  + * 4       itabLast    2       0-based index to last sheet tab in reference
                  + *
                  +
                  * + * + * @see WorkBook + * + * @see Boundsheet + * + * @see Supbook + */ + +class Externsheet : io.starter.formats.XLS.XLSRecord() { + internal var cXTI: Short = 0 + // int DEBUGLEVEL = 10; + internal var rgs = CompatibleVector() + + override var workBook: WorkBook? + get + set(bk) { + super.workBook = bk + val it = rgs.iterator() + while (it.hasNext()) + (it.next() as rgxti).setWorkBook(bk) + } + + /** + * Certain records require a virtual reference, this is not a real reference to a sheet, + * rather an entry that is used by add in formulas, values are FE FF FE FF + * + * + * This method either finds the existing reference, or creates a new one and returns + * the pointer + * + * @return + */ + /*null*//*null*/ val virtualReference: Int + get() { + for (i in rgs.indices) { + val thisXti = rgs.elementAt(i) as rgxti + if (thisXti.sheet1num == 0xFFFE.toShort() && thisXti.sheet2num == 0xFFFE.toShort()) + return i + } + val bts = ByteArray(6) + if (this.wkbook != null) { + val sb = this.wkbook!!.supBooks + bts[0] = getAddInIndex(sb) + } + val newcXTI = rgxti(bts) + newcXTI.setWorkBook(this.wkbook) + newcXTI.sheet1 = 0xFFFE + newcXTI.sheet2 = 0xFFFE + rgs.add(newcXTI) + this.cXTI++ + this.update() + return rgs.size - 1 + } + + /** + * In some cases, we need to have an Xti reference to a non-existing sheet. For instance, a chart + * or formula with a ptgRef3d that referrs to a missing sheet. Externsheet handles this by having + * an internal record populated with -1's This method searches for a non-existing record, and if + * that doesn't exist it creates on and passes the reference back. + * + * @return pointer to broken (-1) reference + */ + /*getSheet1Num()*//*getSheet2Num()*/ val brokenXtiReference: Int + get() { + for (i in rgs.indices) { + val thisXti = rgs.elementAt(i) as rgxti + if (thisXti.sheet1num.toInt() == 0xFFFF && thisXti.sheet2num.toInt() == 0xFFFF) + return i + } + val bts = ByteArray(6) + if (this.wkbook != null) { + val sb = this.wkbook!!.supBooks + bts[0] = getGlobalSupBookIndex(sb) + } + val newcXTI = rgxti(bts) + newcXTI.setWorkBook(this.wkbook) + newcXTI.sheet1 = 0xFFFF + newcXTI.sheet2 = 0xFFFF + rgs.add(newcXTI) + this.cXTI++ + this.update() + return rgs.size - 1 + } + + override fun preStream() { + this.update() + } + + @Throws(WorkSheetNotFoundException::class) + fun addPtgListener(p: IxtiListener) { + val ix = p.ixti + // if(ix>0)ix--; + if (this.rgs.size > ix) { + val rg = this.rgs[ix.toInt()] as rgxti + rg.addListener(p) + } else { + val rg = rgxti() + rg.setWorkBook(this.wkbook) // 20080306 KSC + rg.sheet1 = ix // 20080306 KSC: use actual sheet# this.wkbook.getWorkSheetByNumber(ix)); + rg.sheet2 = ix // "" this.wkbook.getWorkSheetByNumber(ix)); + this.rgs.add(rg) + rg.addListener(p) + this.update() + } + } + + + fun getBoundSheets(cLoc: Int): Array? { + var cLoc = cLoc + + if (rgs.size == 0) + return null + + if (cLoc > rgs.size - 1) + cLoc = rgs.size - 1 + + val rg = rgs[cLoc] as rgxti + + val first = rg.sheet1num + var last = rg.sheet2num + if (first == 0xFFFE.toShort()) + // associated with a Name record + return null + if (first == 0xFFFF.toShort()) + // 20080212 KSC - should be a ref to a deleted or unfound sheet + return null // error trap trying to get virtual sheet + + var numshts = ++last - first + if (numshts < 1) numshts = 1 + val bs = arrayOfNulls(numshts) + var p = 0 + for (t in first until last) { + try { + bs[p++] = this.wkbook!!.getWorkSheetByNumber(t) + } catch (e: WorkSheetNotFoundException) { + // don't error out on the external workbook sheet references + if (DEBUGLEVEL > 1 && t != 65535 && !rg.isExternal) + // 20080306 KSC: add external check ... + Logger.logWarn("Attempt to access Externsheet reference for sheet failed: $e") + } + + } + return bs + } + + /** + * returns array of referenced sheet names, including external references ... + * + * @param cLoc + * @return + */ + fun getBoundSheetNames(cLoc: Int): Array? { + var cLoc = cLoc + if (rgs.size == 0) + return null + if (cLoc > rgs.size - 1) + cLoc = rgs.size - 1 + + val rg = rgs[cLoc] as rgxti + + var first = rg.sheet1num + var last = rg.sheet2num + + if (first == 0xFFFE.toShort()) + // associated with a Name record (=Add-in) + return arrayOf("AddIn") + + if (first == 0xFFFF.toShort()) + // is a ref to a deleted or un-found sheet + return arrayOf("#REF!") + + var numshts = ++last - first + if (numshts < 1) numshts = 1 + if (first < 0) + first = 1 + + val sheets = arrayOfNulls(numshts) + if (first == last) + return arrayOf("#REF!") + var p = 0 + for (t in first until last) { + try { + sheets[p++] = rg.getSheetName(t) // should successfully retrieve External Sheetnames + } catch (we: WorkSheetNotFoundException) { + if (DEBUGLEVEL > 1) + Logger.logWarn("Attempt to access Externsheet reference for sheet failed: $we") + } + + } + return sheets + } + + /** + * returns true if the passed in sheet number is an + * external link (i.e. an external sheet reference) + * + * @param loc external sheet number + * @return + */ + fun getIsExternalLink(loc: Int): Boolean { + if (rgs.size == 0) + return false + + val rg = rgs[loc] as rgxti + return rg.isExternal + } + + /** + * get the number of refs in this Externsheet rec + */ + fun getcXTI(): Int { + return cXTI.toInt() + } + + override fun init() { + super.init() + cXTI = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + var pos = 2 + for (t in 0 until cXTI) { + try { + val bts = this.getBytesAt(pos, 6) // System.arraycopy(rkdata,pos,bts,0,6); + val rg = rgxti(bts) + rgs.add(rg) + if (wkbook != null) + rg.setWorkBook(wkbook) + } catch (e: Exception) { + if (DEBUGLEVEL > 10) Logger.logWarn("init of Externsheet record failed: $e") + } + + pos += 6 + } + if (DEBUGLEVEL > 10) Logger.logInfo("Done Creating Externsheet") + } + + /** + * update the underlying bytes by recreating from the properties + */ + internal fun update() { + // init a new byte array + var blen = rgs.size * 6 + blen += 2 + val newbytes = ByteArray(blen) + this.cXTI = (rgs.size - 1).toShort() + // get the number of structs + val cx = ByteTools.shortToLEBytes(rgs.size.toShort()) + System.arraycopy(cx, 0, newbytes, 0, 2) + + // iterate the structs and get the bytes + var pos = 2 + val it = rgs.iterator() + while (it.hasNext()) { + val btx = (it.next() as rgxti).bytes + System.arraycopy(btx, 0, newbytes, pos, 6) + pos += 6 + } + + // set the data + this.setData(newbytes) + } + + /** + * Remove a sheet from this reference table, + * + * + * Also updates the ixti listeners, which appear to be records that contain an internal + * reference to a sheet number. + */ + @Throws(WorkSheetNotFoundException::class) + internal fun removeSheet(sheetnum: Int) { + if (DEBUGLEVEL > 10) Logger.logInfo("Removing Sheet from Externsheet") + // iterate the cXTI and check if this sheet is contained + val it = rgs.iterator() + while (it.hasNext()) { + val rg = it.next() as rgxti + val first = rg.sheet1num.toInt() + val last = rg.sheet2num.toInt() + if (sheetnum == first && sheetnum == last) { + // reference should not be removed, rather set to -1; + rg.sheet1 = 0xFFFF + rg.sheet2 = 0xFFFF + this.cXTI-- + } else if (sheetnum >= first && sheetnum <= last) { // it's contained in the ref + rg.sheet2 = rg.sheet2 - 1 + } else if (sheetnum <= first) { + rg.sheet1 = rg.sheet1 - 1 + rg.sheet2 = rg.sheet2 - 1 + } + } + this.update() + } + + /* 20100506 KSC: this is not necessary any longer since + * rg entries are not removed upon deletes or moved in any operations + * therefore the ixti will stay constant + * -- for deleted sheets, the sheet reference will be set to the 0xFFFE deleted + * sheet reference + public void notifyIxtiListeners() { + Iterator it = rgs.iterator(); + while(it.hasNext()) { + ((rgxti)it.next()).notifyListeners(); + } + } + */ + + /** + * add a sheet to this reference table + * + * + * why doesn't this return the new referenced int? NR + */ + @Throws(WorkSheetNotFoundException::class) + internal fun addSheet(sheetnum: Int) { + this.addSheet(sheetnum, sheetnum) + } + + private fun getAddInIndex(sb: Array): Byte { + var i = 0 + while (i < sb.size) { + if (sb[i].isAddInRecord) + return i.toByte() + i++ + } + return (-1).toByte() + } + + private fun getGlobalSupBookIndex(sb: Array): Byte { + var i = 0 + while (i < sb.size) { + if (sb[i].isGlobalRecord) + return i.toByte() + i++ + } + return (-1).toByte() + } + + /** + * add a sheet range this reference table + */ + @Throws(WorkSheetNotFoundException::class) + internal fun addSheet(firstSheet: Int, lastSheet: Int) { + if (DEBUGLEVEL > 10) Logger.logInfo("Adding new Sheet to Externsheet") + + // KSC: Added logic to set correct supbook index for added XTI + val bts = ByteArray(6) + if (this.wkbook != null) { // should never happen! + val sb = this.wkbook!!.supBooks // must have SUPBOOK records when have an EXTERNSHEET! + if (firstSheet == 0xFFFE) + // then link to ADD-IN SUPBOOK + bts[0] = getAddInIndex(sb) + else {// link to global SUPBOOK record + bts[0] = getGlobalSupBookIndex(sb) + } + } + val newcXTI = rgxti(bts) + newcXTI.setWorkBook(this.wkbook) // 20080306 KSC + if (firstSheet == 0xFFFE) + // it's a virtual sheet range for Add-ins + newcXTI.isAddIn = true + + if (!newcXTI.isAddIn) { + newcXTI.sheet1 = firstSheet + newcXTI.sheet2 = lastSheet + } + rgs.add(newcXTI) + + this.cXTI++ + this.update() + } + + fun addExternalSheetRef(externalWorkbook: String, externalSheetName: String): Short { + // get the external supbook record for this external workbook, creates if not present + val sb = this.wkbook!!.getExternalSupbook(externalWorkbook, true) + val sheetRef = sb!!.addExternalSheetReference(externalSheetName) + val sbRef = this.wkbook!!.getSupbookIndex(sb).toShort() + + /* see if external ref exists already */ + val it = rgs.iterator() + var i = 0 + while (it.hasNext()) { + val rg = it.next() as rgxti + if (rg.sheet1num == sheetRef && rg.sheet2num == sheetRef && rg.sbs == sbRef) + return i.toShort() + i++ + } + + val bts = ByteArray(6) + System.arraycopy(ByteTools.shortToLEBytes(sbRef), 0, bts, 0, 2) // input SUPBOOK ref # + System.arraycopy(ByteTools.shortToLEBytes(sheetRef), 0, bts, 2, 2) // input Sheet ref # + System.arraycopy(ByteTools.shortToLEBytes(sheetRef), 0, bts, 4, 2) // input Sheet ref # + val newcXTI = rgxti(bts) + newcXTI.setIsExternalRef(true) // flag don't look up worksheets in this workbook + newcXTI.setWorkBook(this.wkbook) // 20080306 KSC + rgs.add(newcXTI) + + this.cXTI++ + this.update() + return this.cXTI + } + + /** + * Insert location checks if a specific boundsheet range already has a reference. + * + * + * If the range already exists within the externsheet the index to the + * range is returned. Else, it adds the range to the externsheet and returns the index. + * + * @param firstBound + * @param lastBound + * @return + */ + @Throws(WorkSheetNotFoundException::class) + fun insertLocation(firstBound: Int, lastBound: Int): Int { + val it = rgs.iterator() + var i = 0 + while (it.hasNext()) { + val rg = it.next() as rgxti + val first = rg.sheet1num.toInt() // 20080306 KSC: getSheet1Num(); + val last = rg.sheet2num.toInt() // getSheet2Num(); + + if (first == firstBound && last == lastBound && rg.sb!!.isGlobalRecord) { + return i + } + i++ + } + this.addSheet(firstBound, lastBound) + return rgs.size - 1 + } + + /** + * Gets the xti reference for a boundsheet name + * + * @return xti reference, -1 if not located. + */ + fun getXtiReference(firstSheet: String, secondSheet: String): Int { + for (i in rgs.indices) { + val thisXti = rgs.elementAt(i) as rgxti + try { + if (thisXti.getSheetName(thisXti.sheet1num.toInt()).equals(firstSheet, ignoreCase = true) && thisXti.getSheetName(thisXti.sheet2num.toInt()).equals(secondSheet, ignoreCase = true)) { + return i + } + } catch (we: WorkSheetNotFoundException) { + if (DEBUGLEVEL > 10) + Logger.logWarn("Externsheet.getXtiReference: Attempt to find Externsheet reference for sheet failed: $we") + } + + } + return -1 + } + + override fun close() { + while (rgs.size > 0) + rgs.removeAt(0) + } + + /** + * Internal structure tracks Sheet references + */ + internal inner class rgxti : java.io.Serializable { + var sbs: Short = 0 // supbook # + var sheet1num: Short = 0 + var sheet2num: Short = 0 // store numbers as sometimes sheets are not in workbook ... + var sb: Supbook? = null + var wkbook: WorkBook? = null + var bts: ByteArray? = null + var isAddIn = false // flag if this is "iSupbook" Externsheet + /** + * return if this rgi references an external SUPBOOK record + * i.e. one that references an External Workbook + * (therefore sheet references are not found in the current workbook) + */ + var isExternal = false + private set // flag if this is an external ref. + // contains references to virtual 0xFFFE sheet # + + var listeners = CompatibleVector() + + /** + * @return Returns the bts. + */ + val bytes: ByteArray + get() { + if (bts == null) bts = ByteArray(6) + var shtbt = ByteTools.shortToLEBytes(sheet1num) + System.arraycopy(shtbt, 0, bts!!, 2, 2) + shtbt = ByteTools.shortToLEBytes(sheet2num) + System.arraycopy(shtbt, 0, bts!!, 4, 2) + return bts + } + + /** + * set the sheet# referenced + * + * @param int sheet # + */ + var sheet1: Int + get() = this.sheet1num.toInt() + set(sh1) { + this.sheet1num = sh1.toShort() + } + + /** + * set the sheet# referenced + * + * @param int sheet # + */ + var sheet2: Int + get() = this.sheet2num.toInt() + set(sh2) { + this.sheet2num = sh2.toShort() + } + + fun addListener(p: IxtiListener) { + listeners.add(p) + } + + fun notifyListeners() { + val it = listeners.iterator() + val locp = rgs.indexOf(this) + while (it.hasNext()) { + val p = it.next() as IxtiListener + if (locp == -1) + (p as Ptg).parentRec.remove(true) // this reference has been deleted, rec not valid + else + p.ixti = locp.toShort() + } + } + + /** + * default constructor + */ + constructor() { + // do something? + } + + fun setWorkBook(bk: WorkBook?) { + this.wkbook = bk + sbs = ByteTools.readShort(bts!![0].toInt(), bts!![1].toInt()) // supbook # + sb = wkbook!!.supBooks[sbs] // get supbook referenced by sbs + + sheet1num = ByteTools.readShort(bts!![2].toInt(), bts!![3].toInt()) + sheet2num = ByteTools.readShort(bts!![4].toInt(), bts!![5].toInt()) + + if (sheet1num.toInt() == 0xFFFE) + isAddIn = true + + if (sb!!.isExternalRecord) + isExternal = true + + } + + /** + * constructor used to init from bytes without a book + * + * @param initbytes + */ + constructor(initbytes: ByteArray) { + this.bts = initbytes + } + + /** + * return the sheet name for the sheetNum in the associated supbook + */ + @Throws(WorkSheetNotFoundException::class) + fun getSheetName(sheetNum: Int): String { + if (isAddIn) + // no sheets assoc; return virtual sheet # + return "Virtual Sheet Range 0xFFFE - 0xFFFE" + if (isExternal) + return sb!!.getExternalSheetName(sheetNum) + return if (sheetNum == 0xFFFF) "Deleted Sheet" else wkbook!!.getWorkSheetByNumber(sheetNum).sheetName + //otherwise, try and get sheetname + + /* + if(this.sheet1==null) // if sheet ref is deleted, return 0xFFFF + return "null"; //-1; + return this.sheet1.getSheetName(); + */ + } + + /** + * set if this rgi references an external SUPBOOK record + * i.e. one that references an External Workbook + * (therefore sheet references are not found in the current workbook) + * + * @param bIsExternal + */ + fun setIsExternalRef(bIsExternal: Boolean) { + this.isExternal = bIsExternal + } + + override fun toString(): String { + + try { + return "rgxti range: " + this.getSheetName(sheet1num.toInt()) + "-" + this.getSheetName(sheet2num.toInt()) + } catch (we: WorkSheetNotFoundException) { + } + + return "rgxti range: sheets not initialized" + } + + companion object { + /** + * serialVersionUID + */ + private const val serialVersionUID = -1591367957030959727L + } + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -4460757130836967839L + + /** + * Constructor + */ + protected// put a reference to sheet1 in as initial value + val prototype: XLSRecord? + get() { + val x = Externsheet() + x.length = 8.toShort() + x.opcode = XLSConstants.EXTERNSHEET + val dta = ByteArray(8) + dta[0] = 0x1.toByte() + x.setData(dta) + x.originalsize = 8 + x.init() + return x + } + + /** + * Add new Externsheet record and set sheet + * + * @param sheetNum1 + * @param sheetNum2 + * @param bk + * @return + */ + fun getPrototype(sheetNum1: Int, sheetNum2: Int, bk: WorkBook): XLSRecord? { + val x = prototype as Externsheet? + try { + x!!.cXTI-- + x.rgs.removeAt(0) + x.workBook = bk // must, for addSheet + // x.addSheet(sheetNum1, sheetNum2); + } catch (e: Exception) { + Logger.logWarn("ExternSheet.getPrototype error:$e") + } + + return x + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Extsst.java b/src/main/java/io/starter/formats/XLS/Extsst.java deleted file mode 100644 index 775ec3a..0000000 --- a/src/main/java/io/starter/formats/XLS/Extsst.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.List; - - -/** - * Extsst: Extended Shared String Table (FFh)
                  - * hashtable that optimizes external copy operations. - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       Dsst        2       Number of strings in each bucket
                  - * 6       Rgisstinf   var     Array of ISSTINF Structures
                  - * 

                  - *

                  - *

                  - * The first WORD in each ISSTINF entry in the EXTSST record is an absolute - * offset into the Workbook stream. It's value isn't record dependent. The - * second WORD in each ISSTINF entry in the EXTSST record is the offset into - * the record where the string is found. In the case where the string is - * found in a CONTINUE record, the value should be the offset into the - * CONTINUE record. - *

                  - * For example: - * In an Excel workbook with a lot of strings, the SST record might start at - * offset 0x07F2 into the Workbook stream. The next record, a CONTINUE - * record, starts at offset 0x2810. The first 52 ISSTINF entries in the - * EXTSST record work just as we would expect, with offsets into the SST - * record. The 53 ISSTINF entry contains an offset into the CONTINUE record, - * and absolute addressing continues normally. - *

                  - * ISSTINF entries 52 and 53 look like this: - *

                  - * ISSTINF 52 absolute offset: 0x27E5 - * ISSTINF 52 relative offset: 0x1FF3 - *

                  - * ISSTINF 53 absolute offset: 0x28A4 - * ISSTINF53 relative offset: 0x0094 - *

                  - * As you can see, while the absolute offset continues to increment, the - * relative offset resets to begin addressing from the beginning of the - * CONTINUE record. - * - * @see SST - * @see LABELSST - * @see EXTSST - */ - -public final class Extsst extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2409458213287279987L; - Sst mysst = null; - short Dsst; - int numstructs; - Isstinf[] mystinfs = null; - boolean debug; - - void setSst(Sst s) { - this.mysst = s; - s.setExtsst(this); - } - - /** - * because the EXTSST comes between the BOUNDSHEET - * records and all BOUNDSHEET BOFs, the lbPlyPos needs - * to change for all Worksheets when this record's size changes. - */ - public boolean getUpdatesAllBOFPositions() { - return true; - } - - public void init() { - debug = true; - - super.init(); - Dsst = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - int bpos = 2; - if (false) { - byte[] b = this.getData(); - while (bpos < b.length) { - int sststartpos = ByteTools.readInt(b[bpos++], b[bpos++], b[bpos++], b[bpos++]); - int continueStrPos = ByteTools.readInt(b[bpos++], b[bpos++], b[bpos++], b[bpos++]); - Logger.logInfo("ExtSST IB = " + (sststartpos - 16)); - Logger.logInfo("ExtSST CB = " + continueStrPos); - Logger.logInfo(""); - } - } - - } - - /** - * update the Dsst number - */ - void setDsst(int newdst) { - byte[] newdt = ByteTools.shortToLEBytes((short) newdst); - this.getData()[0] = newdt[0]; - this.getData()[1] = newdt[1]; - this.Dsst = (short) newdst; - } - - /** - * create a new Isstinf array from Sst strings - *

                  - * If you don't update these you will have problems with particular files. Sorry!!! - */ - public void updateIsstinfs() { - List strs = mysst.getStringVector(); - int totstrs = strs.size(); - - // if the total of unique UStrings is < 1024, - // then Dsst = 8, total Isstinfs is cstun/8 - int newdsst = 8; - - // otherwise, divide cstun by 128 and round up - // to get Dsst. total Isstinfs is cstun/Dsst. - if (totstrs > 1024) { - newdsst = totstrs / 128; - if ((totstrs % newdsst) > 0) newdsst++; - } - int totissts = totstrs / newdsst; - if ((totstrs % newdsst) > 0) totissts++; - int ctr = 0; - // this is the location of the actual first Ustring data in the Workbook stream - // add the opcodeLen, length, and sst fields to this value to get the correct - // pointer directly into the sst - int sststartpos = mysst.getOffset() + 12; - - - Isstinf[] mynewstinfs = new Isstinf[totissts]; - int continueStrPos = 0, lastContinueStrPos = 0, strlen = 0, off3 = 0, bytepos = 2, sstpos = 0, contoff = 0; - byte[] bd = new byte[((totissts) * 8) + 2]; - byte[] numStrsPer = ByteTools.shortToLEBytes((short) newdsst); - bd[0] = numStrsPer[0]; - bd[1] = numStrsPer[1]; - int RecLen = WorkBookFactory.MAXRECLEN; - int whichContinue = -1; - boolean updateRecLen = false; - - int lastsstpos = 0; - - // iterate through the issts - int sstOffset = mysst.getOffset(); - for (int t = 0; t < totissts; t++) { - Unicodestring str; - if (t == totissts) { - str = mysst.getUStringAt(totstrs - 1); - } else { - // get the data - str = mysst.getUStringAt(ctr); - } - ctr += newdsst; - - lastsstpos = sstpos; - lastContinueStrPos = continueStrPos; - sstpos = str.getSSTPos() + 4; // always 4 off - - - boolean newbucket = false; - // set the offset - //int stringlength=0; - continueStrPos = (sstpos - contoff); - if (continueStrPos > RecLen && (whichContinue + 1) < mysst.continues.size()) { // new bucket - updateRecLen = true; - continueStrPos = (continueStrPos - RecLen + 1); - - if (updateRecLen) { - List v = mysst.continues; - BiffRec br = (BiffRec) v.get(++whichContinue); - RecLen = br.getLength(); - updateRecLen = false; - } - Object[] grbits = Sst.getContinueDef(mysst, false); - Byte[] grbytes = (Byte[]) grbits[1]; - if ((grbytes[whichContinue] != null) && (grbytes[whichContinue].byteValue() == 0x0 || grbytes[whichContinue].byteValue() == 0x1)) { - contoff += (RecLen); - sstOffset += 5; - } else { - contoff += (RecLen); - sstOffset += 4; - } - - } - - - sststartpos = sstpos + sstOffset; - // get the data into bytes - - if (!true) { - Logger.logInfo("ExtSST IB = " + sststartpos); - Logger.logInfo("ExtSST CB = " + (continueStrPos - 4)); - Logger.logInfo("String number " + t * newdsst); - Logger.logInfo("Continue number " + whichContinue); - Logger.logInfo(""); - } - - byte[] ib1 = ByteTools.cLongToLEBytes(sststartpos); - byte[] cb1 = ByteTools.shortToLEBytes((short) continueStrPos);//((short)0); - - bd[bytepos++] = ib1[0]; - bd[bytepos++] = ib1[1]; - bd[bytepos++] = ib1[2]; - bd[bytepos++] = ib1[3]; - bd[bytepos++] = cb1[0]; - bd[bytepos++] = cb1[1]; - bd[bytepos++] = 0x0; - bd[bytepos++] = 0x0; - - lastContinueStrPos = continueStrPos; - } - this.setData(bd); - this.setDsst(newdsst); - //int orsz = this.getOriginalDataSize(); - int newsz = this.getLength(); - if (DEBUGLEVEL > 3) Logger.logInfo("Done creating Isstinfs"); - } - - - public void preStream() { - try { - this.updateIsstinfs(); - } catch (Exception e) { - Logger.logErr("Extsst.preStream() failed updating Isstinfs.", e); - } - } - -} diff --git a/src/main/java/io/starter/formats/XLS/Extsst.kt b/src/main/java/io/starter/formats/XLS/Extsst.kt new file mode 100644 index 0000000..aeae76a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Extsst.kt @@ -0,0 +1,271 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * **Extsst: Extended Shared String Table (FFh)**

                  + * hashtable that optimizes external copy operations. + * + *

                  + * offset  name        size    contents
                  + * ---
                  + * 4       Dsst        2       Number of strings in each bucket
                  + * 6       Rgisstinf   var     Array of ISSTINF Structures
                  +
                  * + * + * + * + * + * The first WORD in each ISSTINF entry in the EXTSST record is an absolute + * offset into the Workbook stream. It's value isn't record dependent. The + * second WORD in each ISSTINF entry in the EXTSST record is the offset into + * the record where the string is found. In the case where the string is + * found in a CONTINUE record, the value should be the offset into the + * CONTINUE record. + * + * + * For example: + * In an Excel workbook with a lot of strings, the SST record might start at + * offset 0x07F2 into the Workbook stream. The next record, a CONTINUE + * record, starts at offset 0x2810. The first 52 ISSTINF entries in the + * EXTSST record work just as we would expect, with offsets into the SST + * record. The 53 ISSTINF entry contains an offset into the CONTINUE record, + * and absolute addressing continues normally. + * + * + * ISSTINF entries 52 and 53 look like this: + * + * + * ISSTINF 52 absolute offset: 0x27E5 + * ISSTINF 52 relative offset: 0x1FF3 + * + * + * ISSTINF 53 absolute offset: 0x28A4 + * ISSTINF53 relative offset: 0x0094 + * + * + * As you can see, while the absolute offset continues to increment, the + * relative offset resets to begin addressing from the beginning of the + * CONTINUE record. + * + * @see SST + * + * @see LABELSST + * + * @see EXTSST + */ + +class Extsst : io.starter.formats.XLS.XLSRecord() { + internal var mysst: Sst? = null + internal var Dsst: Short = 0 + internal var numstructs: Int = 0 + internal var mystinfs: Array? = null + internal var debug: Boolean = false + + /** + * because the EXTSST comes between the BOUNDSHEET + * records and all BOUNDSHEET BOFs, the lbPlyPos needs + * to change for all Worksheets when this record's size changes. + */ + val updatesAllBOFPositions: Boolean + get() = true + + internal fun setSst(s: Sst) { + this.mysst = s + s.extsst = this + } + + override fun init() { + debug = true + + super.init() + Dsst = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + var bpos = 2 + if (false) { + val b = this.getData() + while (bpos < b!!.size) { + val sststartpos = ByteTools.readInt(b[bpos++], b[bpos++], b[bpos++], b[bpos++]) + val continueStrPos = ByteTools.readInt(b[bpos++], b[bpos++], b[bpos++], b[bpos++]) + Logger.logInfo("ExtSST IB = " + (sststartpos - 16)) + Logger.logInfo("ExtSST CB = $continueStrPos") + Logger.logInfo("") + } + } + + } + + /** + * update the Dsst number + */ + internal fun setDsst(newdst: Int) { + val newdt = ByteTools.shortToLEBytes(newdst.toShort()) + this.getData()[0] = newdt[0] + this.getData()[1] = newdt[1] + this.Dsst = newdst.toShort() + } + + /** + * create a new Isstinf array from Sst strings + * + * + * If you don't update these you will have problems with particular files. Sorry!!! + */ + fun updateIsstinfs() { + val strs = mysst!!.stringVector + val totstrs = strs.size + + // if the total of unique UStrings is < 1024, + // then Dsst = 8, total Isstinfs is cstun/8 + var newdsst = 8 + + // otherwise, divide cstun by 128 and round up + // to get Dsst. total Isstinfs is cstun/Dsst. + if (totstrs > 1024) { + newdsst = totstrs / 128 + if (totstrs % newdsst > 0) newdsst++ + } + var totissts = totstrs / newdsst + if (totstrs % newdsst > 0) totissts++ + var ctr = 0 + // this is the location of the actual first Ustring data in the Workbook stream + // add the opcodeLen, length, and sst fields to this value to get the correct + // pointer directly into the sst + var sststartpos = mysst!!.getOffset() + 12 + + + val mynewstinfs = arrayOfNulls(totissts) + var continueStrPos = 0 + var lastContinueStrPos = 0 + val strlen = 0 + val off3 = 0 + var bytepos = 2 + var sstpos = 0 + var contoff = 0 + val bd = ByteArray(totissts * 8 + 2) + val numStrsPer = ByteTools.shortToLEBytes(newdsst.toShort()) + bd[0] = numStrsPer[0] + bd[1] = numStrsPer[1] + var RecLen = WorkBookFactory.MAXRECLEN + var whichContinue = -1 + var updateRecLen = false + + var lastsstpos = 0 + + // iterate through the issts + var sstOffset = mysst!!.getOffset() + for (t in 0 until totissts) { + val str: Unicodestring + if (t == totissts) { + str = mysst!!.getUStringAt(totstrs - 1) + } else { + // get the data + str = mysst!!.getUStringAt(ctr) + } + ctr += newdsst + + lastsstpos = sstpos + lastContinueStrPos = continueStrPos + sstpos = str.sstPos + 4 // always 4 off + + + val newbucket = false + // set the offset + //int stringlength=0; + continueStrPos = sstpos - contoff + if (continueStrPos > RecLen && whichContinue + 1 < mysst!!.continues!!.size) { // new bucket + updateRecLen = true + continueStrPos = continueStrPos - RecLen + 1 + + if (updateRecLen) { + val v = mysst!!.continues + val br = v!![++whichContinue] as BiffRec + RecLen = br.length + updateRecLen = false + } + val grbits = Sst.getContinueDef(mysst, false) + val grbytes = grbits[1] as Array + if (grbytes[whichContinue] != null && (grbytes[whichContinue].toByte().toInt() == 0x0 || grbytes[whichContinue].toByte().toInt() == 0x1)) { + contoff += RecLen + sstOffset += 5 + } else { + contoff += RecLen + sstOffset += 4 + } + + } + + + sststartpos = sstpos + sstOffset + // get the data into bytes + + if (!true) { + Logger.logInfo("ExtSST IB = $sststartpos") + Logger.logInfo("ExtSST CB = " + (continueStrPos - 4)) + Logger.logInfo("String number " + t * newdsst) + Logger.logInfo("Continue number $whichContinue") + Logger.logInfo("") + } + + val ib1 = ByteTools.cLongToLEBytes(sststartpos) + val cb1 = ByteTools.shortToLEBytes(continueStrPos.toShort())//((short)0); + + bd[bytepos++] = ib1[0] + bd[bytepos++] = ib1[1] + bd[bytepos++] = ib1[2] + bd[bytepos++] = ib1[3] + bd[bytepos++] = cb1[0] + bd[bytepos++] = cb1[1] + bd[bytepos++] = 0x0 + bd[bytepos++] = 0x0 + + lastContinueStrPos = continueStrPos + } + this.setData(bd) + this.setDsst(newdsst) + //int orsz = this.getOriginalDataSize(); + val newsz = this.length + if (DEBUGLEVEL > 3) Logger.logInfo("Done creating Isstinfs") + } + + + override fun preStream() { + try { + this.updateIsstinfs() + } catch (e: Exception) { + Logger.logErr("Extsst.preStream() failed updating Isstinfs.", e) + } + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -2409458213287279987L + } + +} diff --git a/src/main/java/io/starter/formats/XLS/FeatHeadr.java b/src/main/java/io/starter/formats/XLS/FeatHeadr.kt similarity index 56% rename from src/main/java/io/starter/formats/XLS/FeatHeadr.java rename to src/main/java/io/starter/formats/XLS/FeatHeadr.kt index 22f501d..40a5c72 100644 --- a/src/main/java/io/starter/formats/XLS/FeatHeadr.java +++ b/src/main/java/io/starter/formats/XLS/FeatHeadr.kt @@ -20,9 +20,9 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** * FEATHEADR @@ -37,14 +37,18 @@ * For example, if a Workbook has both Protection and SmartTag, there is one Shared Feature Header (FEATHEADER) created for Protection, and another Shared Feature Header * (FEATHEADER) created for SmartTag. Therefore, the data block of the Shared Feature Header (FEATHEADER) may have a different data structure depending on which * Shared Feature Type the record is for. - *

                  + * + * * Though Excel currently has many different Shared Features such as Formula Error Checking, Protection, SmartTag etc., * only 2 types of Shared Feature are persisted in Excel 2002: Protection and SmartTag. - *

                  + * + * * Special note: In Excel 2003, this FEATHEADER is not used for new shared features, such as Tables, since a new record of FEATFEADER11 was introduced for better feature * data round-tripping stories through earlier versions of Excel. - *

                  - *

                  + * + * + * + * * Contents size * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0867h * 6 grbitFrt 2 FRT cell reference flag =0 currently @@ -53,11 +57,13 @@ * 18 fHdr 1 =1 since this is a feat header * 19 cbHdrData 4 Size of rgbHdrSData =4 for simple feature headers =0 there is no rgbHdrData =-1 for complex feature headers, the size of rgbHdrData depends on the isf type. (prior to Excel 2003, all features saved using FEATHEAER use complex features.) * 23 rgbHdrData var Byte array of extra info, including from future versions of Excel - *

                  + * + * * The rgbHdrData block for Enhanced Protection * Contents size * 0 grbit 4 Bit flag for protection rules setting (see table below for detail bit settings) - *

                  + * + * * The bit settings for protection rules settings in the grbit: * Bits Mask Bit Name Description * 0 00000001h iprotObject Edit object @@ -75,7 +81,8 @@ * 12 00001000h iprotAutoFilter Use Autofilter * 13 00002000h iprotPivotTables Use PivotTable reports * 14 00004000h iprotSelUnlockedCells Select unlocked cells - *

                  + * + * * The rgbHdrData block for SmartTag * Offset Field Name Size Contents * 0 cSmartTag 4 Count of SmartTags @@ -86,7 +93,8 @@ * var Cste 4 String table entry * var cbUnknown 2 Count of bytes of unknown data * var pvUnknoan var The Unknown data - *

                  + * + * * Where the rgbSmartTag, the array of SmartTag header data block, has the fields: * Offset Field Name Size Contents * 0 cbSmartTag 4 Count of Byte of the SmartTag data @@ -100,103 +108,84 @@ * Var pvUnknown var Additional data in pvUnknown, of length cbSmartTag – cbUri – cbTag – cbDownLoadURL – 10 * In Excel 2003, only book level SmartTags‘ headers are saved. Sheet level SmatTag headers are not persisted. */ -public class FeatHeadr extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5775187385375827918L; - short isf = 0; - int grbit = 0; - boolean iprotObject, iprotScenario, iprotFormatCells, iprotFormatColumns, iprotFormatRows, iprotInsertColumns, iprotInsertRows, iprotInsertHyperlinks, - iprotDeleteColumns, iprotDeleteRows, iprotSelLockedCells, iprotSort, iprotAutoFilter, iprotPivotTable, iprotSelUnlockedCells; - public final static short ALLOWOBJECTS = 0x1; - public final static short ALLOWSCENARIOS = 0x2; - public final static short ALLOWFORMATCELLS = 0x4; - public final static short ALLOWFORMATCOLUMNS = 0x8; - public final static short ALLOWFORMATROWS = 0x10; - public final static short ALLOWINSERTCOLUMNS = 0x20; - public final static short ALLOWINSERTROWS = 0x40; - public final static short ALLOWINSERTHYPERLINKS = 0x80; - public final static short ALLOWDELETECOLUMNS = 0x100; - public final static short ALLOWDELETEROWS = 0x200; - public final static short ALLOWSELLOCKEDCELLS = 0x400; - public final static short ALLOWSORT = 0x800; - public final static short ALLOWAUTOFILTER = 0x1000; - public final static short ALLOWPIVOTTABLES = 0x2000; - public final static short ALLOWSELUNLOCKEDCELLS = 0x4000; +class FeatHeadr : io.starter.formats.XLS.XLSRecord() { + internal var isf: Short = 0 + internal var grbit = 0 + internal var iprotObject: Boolean = false + internal var iprotScenario: Boolean = false + internal var iprotFormatCells: Boolean = false + internal var iprotFormatColumns: Boolean = false + internal var iprotFormatRows: Boolean = false + internal var iprotInsertColumns: Boolean = false + internal var iprotInsertRows: Boolean = false + internal var iprotInsertHyperlinks: Boolean = false + internal var iprotDeleteColumns: Boolean = false + internal var iprotDeleteRows: Boolean = false + internal var iprotSelLockedCells: Boolean = false + internal var iprotSort: Boolean = false + internal var iprotAutoFilter: Boolean = false + internal var iprotPivotTable: Boolean = false + internal var iprotSelUnlockedCells: Boolean = false // TODO: handle SmartTag options /** * default constructor */ - private byte[] PROTOTYPE_BYTES = {0x67, 0x08, // rt or record type - 0, 0, // frt cell ref flag - 0, 0, 0, 0, 0, 0, 0, 0, // reserved 0 - 2, 0, // isf: shared feature flag 2=enhanced protection, 4=smart tag - 1, // always 1 - -1, -1, -1, -1, // size of rgbhdrSData, -1=complex size depends upon isf: - 0, 0, 0, 0}; // for enhanced protection, 4 bytes = protection rules - - /** - * Create a FeatHeadr record, default defines enhanced protection features - * - * @return - */ - protected static XLSRecord getPrototype() { - FeatHeadr f = new FeatHeadr(); - f.setOpcode(FEATHEADR); - f.setData(f.PROTOTYPE_BYTES); - f.init(); - return f; - } + private val PROTOTYPE_BYTES = byteArrayOf(0x67, 0x08, // rt or record type + 0, 0, // frt cell ref flag + 0, 0, 0, 0, 0, 0, 0, 0, // reserved 0 + 2, 0, // isf: shared feature flag 2=enhanced protection, 4=smart tag + 1, // always 1 + -1, -1, -1, -1, // size of rgbhdrSData, -1=complex size depends upon isf: + 0, 0, 0, 0) // for enhanced protection, 4 bytes = protection rules /** * init the record - as of now, only enhanced protection is supported */ - public void init() { - super.init(); - isf = ByteTools.readShort(this.getData()[12], this.getData()[13]); - if (isf == 2) {// Enhanced Protection - only option supported for now - read last 4 bytes for enhanced protection settings - grbit = ByteTools.readInt(this.getData()[19], this.getData()[20], this.getData()[21], this.getData()[22]); - parseProtectionGrbit(); + override fun init() { + super.init() + isf = ByteTools.readShort(this.getData()!![12].toInt(), this.getData()!![13].toInt()) + if (isf.toInt() == 2) {// Enhanced Protection - only option supported for now - read last 4 bytes for enhanced protection settings + grbit = ByteTools.readInt(this.getData()!![19], this.getData()!![20], this.getData()!![21], this.getData()!![22]) + parseProtectionGrbit() } } /** * parse grbit, get boolean protection values */ - private void parseProtectionGrbit() { + private fun parseProtectionGrbit() { // parse grbit - iprotObject = ((grbit & ALLOWOBJECTS) == ALLOWOBJECTS); - iprotScenario = ((grbit & ALLOWSCENARIOS) == ALLOWSCENARIOS); - iprotFormatCells = ((grbit & ALLOWFORMATCELLS) == ALLOWFORMATCELLS); - iprotFormatColumns = ((grbit & ALLOWFORMATCOLUMNS) == ALLOWFORMATCOLUMNS); - iprotFormatRows = ((grbit & ALLOWFORMATROWS) == ALLOWFORMATROWS); - iprotInsertColumns = ((grbit & ALLOWINSERTCOLUMNS) == ALLOWINSERTCOLUMNS); - iprotInsertRows = ((grbit & ALLOWINSERTROWS) == ALLOWINSERTROWS); - iprotInsertHyperlinks = ((grbit & ALLOWINSERTHYPERLINKS) == ALLOWINSERTHYPERLINKS); - iprotDeleteColumns = ((grbit & ALLOWDELETECOLUMNS) == ALLOWDELETECOLUMNS); - iprotDeleteRows = ((grbit & ALLOWDELETEROWS) == ALLOWDELETEROWS); - iprotSelLockedCells = ((grbit & ALLOWSELLOCKEDCELLS) == ALLOWSELLOCKEDCELLS); - iprotSort = ((grbit & ALLOWSORT) == ALLOWSORT); - iprotAutoFilter = ((grbit & ALLOWAUTOFILTER) == ALLOWAUTOFILTER); - iprotPivotTable = ((grbit & ALLOWPIVOTTABLES) == ALLOWPIVOTTABLES); - iprotSelUnlockedCells = ((grbit & ALLOWSELUNLOCKEDCELLS) == ALLOWSELUNLOCKEDCELLS); + iprotObject = grbit and ALLOWOBJECTS == ALLOWOBJECTS.toInt() + iprotScenario = grbit and ALLOWSCENARIOS == ALLOWSCENARIOS.toInt() + iprotFormatCells = grbit and ALLOWFORMATCELLS == ALLOWFORMATCELLS.toInt() + iprotFormatColumns = grbit and ALLOWFORMATCOLUMNS == ALLOWFORMATCOLUMNS.toInt() + iprotFormatRows = grbit and ALLOWFORMATROWS == ALLOWFORMATROWS.toInt() + iprotInsertColumns = grbit and ALLOWINSERTCOLUMNS == ALLOWINSERTCOLUMNS.toInt() + iprotInsertRows = grbit and ALLOWINSERTROWS == ALLOWINSERTROWS.toInt() + iprotInsertHyperlinks = grbit and ALLOWINSERTHYPERLINKS == ALLOWINSERTHYPERLINKS.toInt() + iprotDeleteColumns = grbit and ALLOWDELETECOLUMNS == ALLOWDELETECOLUMNS.toInt() + iprotDeleteRows = grbit and ALLOWDELETEROWS == ALLOWDELETEROWS.toInt() + iprotSelLockedCells = grbit and ALLOWSELLOCKEDCELLS == ALLOWSELLOCKEDCELLS.toInt() + iprotSort = grbit and ALLOWSORT == ALLOWSORT.toInt() + iprotAutoFilter = grbit and ALLOWAUTOFILTER == ALLOWAUTOFILTER.toInt() + iprotPivotTable = grbit and ALLOWPIVOTTABLES == ALLOWPIVOTTABLES.toInt() + iprotSelUnlockedCells = grbit and ALLOWSELUNLOCKEDCELLS == ALLOWSELUNLOCKEDCELLS.toInt() } /** * update protection-style grbit variables + update record */ - private void updateProtectionGrbit() { - byte[] b = ByteTools.cLongToLEBytes(grbit); - this.getData(); - data[19] = b[0]; - data[20] = b[1]; - data[21] = b[2]; - data[22] = b[3]; - parseProtectionGrbit(); + private fun updateProtectionGrbit() { + val b = ByteTools.cLongToLEBytes(grbit) + this.getData() + data[19] = b[0] + data[20] = b[1] + data[21] = b[2] + data[22] = b[3] + parseProtectionGrbit() } /** @@ -205,12 +194,12 @@ private void updateProtectionGrbit() { * @param protectionOption @see WorkSheetHandle.iprotXXX constants * @param set */ - public void setProtectionOption(int protectionOption, boolean set) { + fun setProtectionOption(protectionOption: Int, set: Boolean) { if (set) - grbit = (grbit | protectionOption); + grbit = grbit or protectionOption else - grbit = (grbit & ~protectionOption); - updateProtectionGrbit(); + grbit = grbit and protectionOption.inv() + updateProtectionGrbit() } @@ -220,7 +209,43 @@ public void setProtectionOption(int protectionOption, boolean set) { * @param protectionOption @see @WorkSheetHandle.iprotXXX constants * @return */ - public boolean getProtectionOption(int protectionOption) { - return ((grbit & protectionOption) == protectionOption); + fun getProtectionOption(protectionOption: Int): Boolean { + return grbit and protectionOption == protectionOption + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -5775187385375827918L + val ALLOWOBJECTS: Short = 0x1 + val ALLOWSCENARIOS: Short = 0x2 + val ALLOWFORMATCELLS: Short = 0x4 + val ALLOWFORMATCOLUMNS: Short = 0x8 + val ALLOWFORMATROWS: Short = 0x10 + val ALLOWINSERTCOLUMNS: Short = 0x20 + val ALLOWINSERTROWS: Short = 0x40 + val ALLOWINSERTHYPERLINKS: Short = 0x80 + val ALLOWDELETECOLUMNS: Short = 0x100 + val ALLOWDELETEROWS: Short = 0x200 + val ALLOWSELLOCKEDCELLS: Short = 0x400 + val ALLOWSORT: Short = 0x800 + val ALLOWAUTOFILTER: Short = 0x1000 + val ALLOWPIVOTTABLES: Short = 0x2000 + val ALLOWSELUNLOCKEDCELLS: Short = 0x4000 + + /** + * Create a FeatHeadr record, default defines enhanced protection features + * + * @return + */ + val prototype: XLSRecord? + get() { + val f = FeatHeadr() + f.opcode = XLSConstants.FEATHEADR + f.setData(f.PROTOTYPE_BYTES) + f.init() + return f + } } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/FileLock.kt b/src/main/java/io/starter/formats/XLS/FileLock.kt new file mode 100644 index 0000000..1e702fa --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/FileLock.kt @@ -0,0 +1,34 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +class FileLock : UnencryptedXLSRecord() { + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 2675990822253639187L + } + +} diff --git a/src/main/java/io/starter/formats/XLS/Font.java b/src/main/java/io/starter/formats/XLS/Font.java deleted file mode 100644 index c384b2b..0000000 --- a/src/main/java/io/starter/formats/XLS/Font.java +++ /dev/null @@ -1,908 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.Color; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.xmlpull.v1.XmlPullParser; - -import java.io.UnsupportedEncodingException; - - -/** - * Font: Font Description (231h)
                  - *

                  - * Font records describe a font in the workbook - * - *

                  - * - *

                  - *     offset  name            size    contents
                  - *     ---
                  - *     4       dyHeight        2       Height in 1/20 point (twips)
                  - *     6       grbit           2       attributes
                  - * 									grbit Mask Contents
                  - * 										0 0001H 1 = Characters are bold
                  - * 										1 0002H 1 = Characters are italic
                  - * 										2 0004H 1 = Characters are underlined
                  - * 										3 0008H 1 = Characters are struck out
                  - *     8       icv             2       index to color palette
                  - *     10      bls             2       bold style (weight)  100-1000 default is 190h norm 2bch bold
                  - *     12      sss             2       super/sub (0 = none, 1 = super, 2 = sub)
                  - *     14      uls             1       Underline Style (0 = none, 1 = single, 2 = double, 21h = single acctg, 22h = dble acctg)
                  - *     15      bFamily         1       Font Family (WinAPI LOGFONT struct)
                  - *     16      bCharSet        1       Characterset (WinAPI LOGFONT struct)
                  - *     17      reserved        0
                  - *     18      cch             1       Length of font name
                  - * 19      rgch            var     Font name
                  - *
                  - * 

                  - *
                  - *

                  - * "http://www.extentech.com">Extentech Inc. - * - * @see XF - * @see FORMAT - */ - -public final class Font extends io.starter.formats.XLS.XLSRecord implements - FormatConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -398444997553403671L; - private short grbit = -1, cch = -1, dyHeight = -1, icv = -1, bls = -1, - sss = -1, uls = -1, bFamily = -1; - private short bCharSet; - private String fontName = ""; - // OOXML specifics: - private Color customColor = null; // holds custom color (OOXML or other use) - private boolean condensed, extended; - - // grbit flags - static final int BITMASK_BOLD = 0x0001; - static final int BITMASK_ITALIC = 0x0002; - static final int BITMASK_UNDERLINED = 0x0004; - static final int BITMASK_STRIKEOUT = 0x0008; - - // charset values - static final int ANSI_CHARSET = 0; - static final int DEFAULT_CHARSET = 1; - static final int SYMBOL_CHARSET = 2; - static final int SHIFTJIS_CHARSET = 128; - static final int HANGEUL_CHARSET = 129; - static final int HANGUL_CHARSET = 129; - static final int GB2312_CHARSET = 134; - static final int CHINESEBIG5_CHARSET = 136; - static final int OEM_CHARSET = 255; - static final int JOHAB_CHARSET = 130; - static final int HEBREW_CHARSET = 177; - static final int ARABIC_CHARSET = 178; - static final int GREEK_CHARSET = 161; - static final int TURKISH_CHARSET = 162; - static final int VIETNAMESE_CHARSET = 163; - static final int THAI_CHARSET = 222; - static final int EASTEUROPE_CHARSET = 238; - static final int RUSSIAN_CHARSET = 204; - static final int MAC_CHARSET = 77; - static final int BALTIC_CHARSET = 186; - - /** - * Initialize the font record - */ - public void init() { - super.init(); - dyHeight = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1));// Height - // in - // 1/20 - // point - grbit = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3));// attributes - icv = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5));// index - // to - // color - // palette - bls = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7));// bold - // style - // (weight) - // 100-1000 - // default - // is - // 190h - // norm - // 2bch - // bold - sss = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9));// super/sub - // (0 = - // none, - // 1 = - // super, - // 2 = - // sub) - uls = this.getByteAt(10);// Underline Style (0 = none, 1 = single, 2 = - // double, 21h = single acctg, 22h = dble - // acctg) - bFamily = this.getByteAt(11);// Font Family (WinAPI LOGFONT struct) - /** - * lfCharSet - * - * The character set. The following values are predefined. - * - * ANSI_CHARSET BALTIC_CHARSET xx CHINESEBIG5_CHARSET xx DEFAULT_CHARSET - * xx EASTEUROPE_CHARSET GB2312_CHARSET xx GREEK_CHARSET xx - * HANGUL_CHARSET xx MAC_CHARSET xx OEM_CHARSET xx RUSSIAN_CHARSET xx - * SHIFTJIS_CHARSET xx SYMBOL_CHARSET xx TURKISH_CHARSET xx - * VIETNAMESE_CHARSET - * - * - * Korean language edition of Windows: JOHAB_CHARSET - * - * Middle East language edition of Windows: ARABIC_CHARSET - * HEBREW_CHARSET - * - * Thai language edition of Windows: THAI_CHARSET - * - * The OEM_CHARSET value specifies a character set that is - * operating-system dependent. - * - * DEFAULT_CHARSET is set to a value based on the current system locale. - * For example, when the system locale is English (United States), it is - * set as ANSI_CHARSET. - * - * Fonts with other character sets may exist in the operating system. If - * an application uses a font with an unknown character set, it should - * not attempt to translate or interpret strings that are rendered with - * that font. - * - * This parameter is important in the font mapping process. To ensure - * consistent results, specify a specific character set. If you specify - * a typeface name in the lfFaceName member, make sure that the - * lfCharSet value matches the character set of the typeface specified - * in lfFaceName. - */ - bCharSet = (short) ByteTools.readUnsignedShort(this.getByteAt(12), - (byte) 0);// Characterset (WinAPI LOGFONT struct) - - // this.getData()[13]= 0;// set byte to 0 for comparisons - // get the Name - int pos = 14; - cch = this.getByteAt(pos++); - int buflen = cch * 2; - pos++; - boolean compressed = false; - - if (buflen + pos >= this.getLength()) { - buflen = this.getLength() - pos; - compressed = true; - } - - if (buflen < 0) { - Logger.logWarn("could not parse font: length reported as " + buflen); - return; - } - - byte[] namebytes = this.getBytesAt(pos, buflen); - if (!compressed) { - pos = 0; - try { - fontName = new String(namebytes, XLSConstants.UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logErr("Font name decoding failed.", e); - } - } else { // compressed - fontName = new String(namebytes); - } - } - - /** - * Get if the font record is striken our or not - * - * @return - */ - public boolean getStricken() { - return ((grbit & BITMASK_STRIKEOUT) == BITMASK_STRIKEOUT); - } - - public void setStricken(boolean b) { - if (b) - grbit = (short) (grbit | BITMASK_STRIKEOUT); - else { - int grbittemp = grbit ^ BITMASK_STRIKEOUT; - grbit = (short) (grbittemp & grbit); - } - this.setGrbit(); - } - - /** - * Get if the font is italic or not - * - * @return - */ - public boolean getItalic() { - int isItalic = grbit & BITMASK_ITALIC; - return isItalic == BITMASK_ITALIC; - } - - public void setItalic(boolean b) { - if (b) - grbit = (short) (grbit | BITMASK_ITALIC); - // false: need to account for multiple font formats - add step 2: & - // original grbit - else { - int grbittemp = grbit ^ BITMASK_ITALIC; - grbit = (short) (grbittemp & grbit); - } - this.setGrbit(); - } - - /** - * Get if the font is underlined or not - * - * @return - */ - public boolean getUnderlined() { - return ((grbit & BITMASK_UNDERLINED) == BITMASK_UNDERLINED); - } - - public void setUnderlined(boolean b) { - if (b) - grbit = (short) (grbit | BITMASK_UNDERLINED); - else { - int grbittemp = grbit ^ BITMASK_UNDERLINED; - grbit = (short) (grbittemp & grbit); - } - this.setGrbit(); - setUnderlineStyle((byte) 1); // 20070821 KSC: should also set underline - // style ... - } - - /** - * get if the font is bold or not - * - * @return - */ - public boolean getBold() { - return ((grbit & BITMASK_BOLD) == BITMASK_BOLD); - } - - /** - * Set or unset bold attribute of the font record - * - * @param b - */ - public void setBold(boolean b) { - if (data == null) - this.setData(this.getData()); - if (b) { - byte[] boldbytes = ByteTools.shortToLEBytes((short) 0x2bc); - System.arraycopy(boldbytes, 0, data, 6, 2); - bls = 0x2bc; - grbit = (short) (grbit | BITMASK_BOLD); - } else { - byte[] boldbytes = ByteTools.shortToLEBytes((short) 0x190); - System.arraycopy(boldbytes, 0, data, 6, 2); - bls = 0x190; - int grbittemp = grbit ^ BITMASK_BOLD; - grbit = (short) (grbittemp & grbit); - } - this.setGrbit(); - } - - /** - * update the Grbit bytes in the underlying byte stream - */ - public void setGrbit() { - byte[] data = this.getData(); - byte[] b = ByteTools.shortToLEBytes(grbit); - System.arraycopy(b, 0, data, 2, 2); - this.setData(data); - - } - - private int tableidx = -1; - - public int getIdx() { - return tableidx; - } - - /** - * @param idx - */ - public void setIdx(int idx) { - tableidx = idx; - } - - /** - * add to Fonts table in Workbook - */ - public void setWorkBook(WorkBook b) { - super.setWorkBook(b); - if (tableidx == -1) { - tableidx = this.getWorkBook().addFont(this); - } - } - - public String toString() { - return this.fontName + "," + this.bls + "," + this.dyHeight + " " - + this.getColorAsColor() + " font style:[" + this.getBold() - + this.getItalic() + this.getStricken() + this.getUnderlined() - + this.getColor() + this.getUnderlineStyle() + "]"; - } - - public String getFontName() { - return fontName; - } - - /** - * Get an int representing the underline style of this record, matches int - * records in FormatConstants.STYLE_UNDERLINE***** - * - * @return - * @see - */ - public int getUnderlineStyle() { - return this.getData()[10]; - } - - /** - * Set the underline style of this font recotd - * - * @param styl - */ - public void setUnderlineStyle(byte styl) { - this.uls = styl; - this.getData()[10] = styl; - } - - public Font() { - } - - /** - * Create a New Font from the String definition. - *

                  - * Roughly matches the functionality of the java.awt.Font class. - * - * @param String font name - * @param int font style - * @param int font size in Points - */ - public Font(String nm, int stl, int sz) { - byte[] bl = new byte[]{-56, 0, 0, 0, -1, 127, -112, 1, 0, 0, 0, 0, 0, - 0, 5, 1, 65, 0, 114, 0, 105, 0, 97, 0, 108, 0,}; - setOpcode(FONT); - setLength((short) (bl.length)); - this.setData(bl); - this.init(); - this.setFontName(nm); - this.setFontWeight(stl); - this.setFontHeight(sz); - } - - /** - * Set the Font name. - *

                  - * To be valid, this font name must be available on the client system. - */ - public void setFontName(String fn) { - byte[] namebytes = null; - try { - namebytes = fn.getBytes(XLSConstants.UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logWarn("setting Font Name using Default Encoding failed: " - + e); - namebytes = fn.getBytes(); - } - cch = (short) (namebytes.length / 2); - fontName = fn; - byte[] newdata = new byte[namebytes.length + 16]; - System.arraycopy(this.getBytesAt(0, 13), 0, newdata, 0, 13);// 20061027 KSC: keep 13th byte for sake of comparisons - 20070816 - revert to original - System.arraycopy(this.getBytesAt(0, 14), 0, newdata, 0, 14); - newdata[14] = (byte) cch; - newdata[15] = (byte) 1; - System.arraycopy(namebytes, 0, newdata, 16, namebytes.length); - this.setData(newdata); - this.init(); - } - - /** - * - */ - /** - * Set the super/sub script for the Font - *

                  - * super/sub (0 = none, 1 = super, 2 = sub) - */ - public void setScript(int ss) { - if (data == null) - this.setData(this.getData()); - byte[] newss = ByteTools.shortToLEBytes((short) ss); - System.arraycopy(newss, 0, data, 8, 2); - sss = (short) ss; - } - - /** - * returns the super/sub script for the Font - * - * @return int (0 = none, 1 = super, 2 = sub) - */ - public int getScript() { - return sss; - } - - /** - * Set the font color via index into 2003 color table - */ - public void setColor(int cl) { - if (data == null) - setData(this.getData()); - if (cl != icv) { // don't do it if the font is already this color - byte[] newcl = ByteTools.shortToLEBytes((short) cl); - System.arraycopy(newcl, 0, data, 4, 2); - icv = (short) cl; - } - if (customColor != null) - customColor.setColorInt(cl); - } - - /** - * Sets the font color via java.awt.Color - */ - public void setColor(java.awt.Color color) { - if (customColor != null) - customColor.setColor(color); - else - customColor = new Color(color, "color", this.getWorkBook().getTheme()); - icv = (short) customColor.getColorInt(); - byte[] newcl = ByteTools.shortToLEBytes(icv); - System.arraycopy(newcl, 0, data, 4, 2); - } - - /** - * Sets the font color via a web-compliant Hex String - */ - public void setColor(String clr) { - if (customColor != null) - customColor.setColor(clr); - else - customColor = new Color(clr, "color", this.getWorkBook().getTheme()); - icv = (short) customColor.getColorInt(); - byte[] newcl = ByteTools.shortToLEBytes(icv); - System.arraycopy(newcl, 0, data, 4, 2); - } - - /** - * Set the size of the font in 1/20 point units - */ - public void setFontHeight(int ht) { - if (data == null) - this.setData(this.getData()); - byte[] newht = ByteTools.shortToLEBytes((short) ht); - System.arraycopy(newht, 0, data, 0, 2); - dyHeight = (short) ht; - } - - /** - * utility to convert points to correct font height - * - * @param h - * @return - */ - public static int PointsToFontHeight(double h) { - return (int) (h * 20); - } - - public static double FontHeightToPoints(int h) { - return h / 20.0; - } - - public int getFontWeight() { - return this.bls; - } - - public int getFontHeight() { - return this.dyHeight; - } - - public double getFontHeightInPoints() { - return this.dyHeight / 20.0; - } - - /** - * Get the color for this Font as a avt.Color - * - * @return - */ - public java.awt.Color getColorAsColor() { - if (customColor != null) - return customColor.getColorAsColor(); - // If icv is System window text color=7FFF, default fg color or default tooltip text color, return black - if (this.icv == 0x7FFF || this.icv == 0x40 || this.icv == 0x51) { - return java.awt.Color.BLACK; - } else if (this.icv > FormatHandle.COLORTABLE.length) { - return java.awt.Color.BLACK; - } - if (this.getWorkBook() == null) - return FormatHandle.COLORTABLE[this.icv]; - /* notes: special icv values: - 0x0040 Default foreground color. This is the window text color in the sheet display. - 0x0041 Default background color. This is the window background color in the sheet display and is the default background color for a cell. - 0x004D Default chart foreground color. This is the window text color in the chart display. - 0x004E Default chart background color. This is the window background color in the chart display. - 0x004F Chart neutral color which is black, an RGB value of (0,0,0). - 0x0051 ToolTip text color. This is the automatic font color for comments. - 0x7FFF Font automatic color. This is the window text color. - */ - return this.getColorTable()[this.icv]; - } - - /** - * Get the color of this Font as a web-compliant Hex String - */ - public String getColorAsHex() { - if (customColor != null && customColor.getColorAsOOXMLRBG() != null) - return "#" + customColor.getColorAsOOXMLRBG().substring(2); // remove "FF" from beginning - return FormatHandle.colorToHexString(getColorAsColor()); - } - - /** - * returns the font color as an OOXML-compliant Hex Stringf * - * - * @return - */ - public String getColorAsOOXMLRBG() { - String rgbcolor = getColorAsHex(); - return "FF" + rgbcolor.substring(1); - } - - /** - * gets the color of this font as an index into excel 2003 color table - * - * @return int - * @deprecated use getColor() - */ - public int getFontColor() { - return getColor(); - } - - /** - * gets the color of this font as an index into excel 2003 color table - * - * @return int - */ - public int getColor() { - if (customColor != null) - return customColor.getColorInt(); - if (this.icv == 32767) // this is a value for system font color, default - // to black - return 0; - return this.icv; - } - - /** - * Set the weight of the font in 1/20 point units 100-1000 range. - */ - public void setFontWeight(int wt) { - if (data == null) - this.setData(this.getData()); - byte[] newwt = ByteTools.shortToLEBytes((short) wt); - System.arraycopy(newwt, 0, data, 6, 2); - bls = (short) wt; - } - - /** - * Get if the font is bold or not - */ - public boolean getIsBold() { - return bls > 0x190; - } - - /** - * @return an XML descriptor for this Font - */ - // changed from 'getFontInfoXML' - public String getXML() { - return getXML(false); - } - - /** - * return an XML desciptor for this font - * - * @param convertToUnicodeFont if true, font family will be changed to ArialUnicodeMS - * (standard unicode) for non-ascii fonts - * @return - */ - public String getXML(boolean convertToUnicodeFont) { - StringBuffer sb = new StringBuffer(); - if (!convertToUnicodeFont || !isUnicodeCharSet()) - sb.append("name=\"" - + StringTool.convertXMLChars(this.getFontName()) + "\""); - else - sb.append("name=\"ArialUnicodeMS\""); - sb.append(" size=\"" + this.getFontHeightInPoints() + "\""); - sb.append(" color=\"" - + FormatHandle.colorToHexString(this.getColorAsColor()) + "\""); - sb.append(" weight=\"" + this.getFontWeight() + "\""); - if (this.getIsBold()) { - sb.append(" bold=\"1\""); - } - if (this.getUnderlineStyle() != Font.STYLE_UNDERLINE_NONE) - sb.append(" underline=\"" + getUnderlineStyle() + "\""); - return sb.toString(); - } - - /** - * return true if font f matches key attributes of this font - * - * @param f - * @return - */ - public boolean matches(Font f) { - return (this.fontName.equals(f.fontName) && this.dyHeight == f.dyHeight - && this.bls == f.bls && this.getColor() == f.getColor() - && this.sss == f.sss && this.uls == f.uls && this.grbit == f.grbit); - } - - /** - * store OOXML font color - * - * @param c - */ - public void setOOXMLColor(Color c) { - if (c != null) - this.setColor(c.getColorInt()); - customColor = c; - } - - /** - * return the OOXML font color element - * - * @return - */ - public Color getOOXMLColor() { - return customColor; - } - - /** - * set whether this font is Condensed (OOXML-specific) Macintosh - * compatibility setting. - * - * @param condensed - */ - public void setCondensed(boolean condensed) { - this.condensed = condensed; - } - - /** - * return whether this font is Condensed (OOXML-specific) Macintosh - * compatibility setting. - * - * @return - */ - public boolean isCondensed() { - return this.condensed; - } - - /** - * set whether this font is Extended (OOXML-specific) Macintosh - * compatibility setting. - * - * @param expanded - */ - public void setExtended(boolean extended) { - this.extended = extended; - } - - /** - * return whether this font is Expanded (OOXML-specific) Macintosh - * compatibility setting. - * - * @return expanded - */ - public boolean isExtended() { - return this.extended; - } - - /** - * generate the OOXML to define this Font - * - * @return - */ - // TODO: family, scheme - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - if (this.getIsBold()) - ooxml.append(""); - if (this.getItalic()) - ooxml.append(""); - if (this.getUnderlined()) { - int u = this.getUnderlineStyle(); - if (u == 1)// the default - ooxml.append(""); - else if (u == 2) - ooxml.append(""); - else if (u == 0x21) - ooxml.append(""); - else if (u == 0x22) - ooxml.append(""); - } - if (this.getStricken()) - ooxml.append(""); - if (!this.isCondensed()) - ooxml.append(""); - if (!this.isExtended()) - ooxml.append(""); - Color c = this.getOOXMLColor(); - if (c != null) { - ooxml.append(c.getOOXML()); - } else { - // KSC: modify due to certain XLS->XLSX issues with automatic color - if (this.icv != 9 && this.icv != 64) { // leave automatic "blank" - int cl = this.getColor(); - if (cl > 0) - ooxml.append(""); - } - } - double sz = this.getFontHeightInPoints(); - if (sz > 0) // for incremental styles, font size may not be set - ooxml.append(""); - String n = this.getFontName(); - if (n != null && !n.equals("")) // for incremental styles, font name may - // not be set - ooxml.append(""); - // TODO: family val= # (see OOXMLConstants) - - ooxml.append(""); - ooxml.append("\r\n"); - return ooxml.toString(); - } - - /** - * parse incoming OOXML into a Font object - * - * @param xpp - * @return - */ - // TODO: family, scheme - public static Font parseOOXML(XmlPullParser xpp, WorkBookHandle bk) { - Color c = null; - String sz = null, name = ""; - Object u = null; - boolean b = false, strike = false, ital = false; - boolean condense = false, expand = false; - try { - int eventType = xpp.next(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("sz")) { - sz = xpp.getAttributeValue(0); - } else if (tnm.equals("name")) { - name = xpp.getAttributeValue(0); - } else if (tnm.equals("b")) { - if (xpp.getAttributeCount() == 0) - b = true; - else - b = (xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("i")) { - if (xpp.getAttributeCount() == 0) - ital = true; - else - ital = (xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("u")) { - if (xpp.getAttributeCount() == 0) - u = Boolean.valueOf(true); - else - u = xpp.getAttributeValue(0); - } else if (tnm.equals("strike")) { - strike = true; - } else if (tnm.equals("condense")) { - condense = false; - } else if (tnm.equals("expand")) { - expand = false; - } else if (tnm.equals("color")) { - c = (Color) Color.parseOOXML(xpp, FormatHandle.colorFONT, bk); - } - } else if (eventType == XmlPullParser.END_TAG - && xpp.getName().equals("font")) - break; - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("Font.parseOOXML: " + e.toString()); - } - // for incremental styles, font size may not be set - int size = (sz == null) ? -1 : Font.PointsToFontHeight(new Double(sz) - .doubleValue()); - Font f = new Font(name, 400, size); - if (c != null) - f.setOOXMLColor(c); - if (u != null) { - f.setUnderlined(true); - if (u instanceof String) { - if (u.equals("double")) - f.setUnderlineStyle((byte) 2); - else if (u.equals("singleAccounting")) - f.setUnderlineStyle((byte) 0x21); - else if (u.equals("doubleAccounting")) - f.setUnderlineStyle((byte) 0x22); - } - } - if (b) - f.setBold(b); - if (ital) - f.setItalic(true); - if (strike) - f.setStricken(true); - if (!condense) - f.setCondensed(false); - if (!expand) - f.setExtended(false); - return f; - } - - /** - * return the appropriate SVG string to define this font - * - * @return - */ - public String getSVG() { - StringBuffer sbf = new StringBuffer("font-family='" - + this.getFontName() + "'"); - sbf.append(" font-size='" + this.getFontHeightInPoints() + "pt'"); - sbf.append(" font-weight='" + this.getFontWeight() + "'"); - // sbf.append(" fill='#222222'"); // TODO: get proper text color - if (this.icv != 9) - sbf.append(" fill='" - + FormatHandle.colorToHexString(FormatHandle.getColor(this - .getColor())) + "'"); - else - sbf.append(" fill='" - + FormatHandle.colorToHexString(FormatHandle.getColor(0)) - + "'"); - return sbf.toString(); - } - - /** - * EXPERIMENTAL AND MAY NOT BE COMPLETE
                  - * Returns true if this font is a Unicode (non-ascii) Charset - * - * @return - */ - private boolean isUnicodeCharSet() { - return (bCharSet == SHIFTJIS_CHARSET || bCharSet == HANGEUL_CHARSET - || bCharSet == HANGUL_CHARSET || bCharSet == GB2312_CHARSET - || bCharSet == CHINESEBIG5_CHARSET - || bCharSet == HEBREW_CHARSET || bCharSet == ARABIC_CHARSET - || bCharSet == GREEK_CHARSET || bCharSet == TURKISH_CHARSET - || bCharSet == VIETNAMESE_CHARSET || bCharSet == THAI_CHARSET - || bCharSet == EASTEUROPE_CHARSET - || bCharSet == RUSSIAN_CHARSET || bCharSet == BALTIC_CHARSET); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Font.kt b/src/main/java/io/starter/formats/XLS/Font.kt new file mode 100644 index 0000000..020ff23 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Font.kt @@ -0,0 +1,891 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.Color +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.xmlpull.v1.XmlPullParser + +import java.io.UnsupportedEncodingException + + +/** + * **Font: Font Description (231h)**

                  + * + * + * Font records describe a font in the workbook + * + * + * + * + *

                  + * offset  name            size    contents
                  + * ---
                  + * 4       dyHeight        2       Height in 1/20 point (twips)
                  + * 6       grbit           2       attributes
                  + * grbit Mask Contents
                  + * 0 0001H 1 = Characters are bold
                  + * 1 0002H 1 = Characters are italic
                  + * 2 0004H 1 = Characters are underlined
                  + * 3 0008H 1 = Characters are struck out
                  + * 8       icv             2       index to color palette
                  + * 10      bls             2       bold style (weight)  100-1000 default is 190h norm 2bch bold
                  + * 12      sss             2       super/sub (0 = none, 1 = super, 2 = sub)
                  + * 14      uls             1       Underline Style (0 = none, 1 = single, 2 = double, 21h = single acctg, 22h = dble acctg)
                  + * 15      bFamily         1       Font Family (WinAPI LOGFONT struct)
                  + * 16      bCharSet        1       Characterset (WinAPI LOGFONT struct)
                  + * 17      reserved        0
                  + * 18      cch             1       Length of font name
                  + * 19      rgch            var     Font name
                  + *
                  +
                  * + * + * + * + * "http://www.extentech.com">Extentech Inc. + * + * @see XF + * + * @see FORMAT + */ + +class Font : io.starter.formats.XLS.XLSRecord, FormatConstants { + private var grbit: Short = -1 + private var cch: Short = -1 + private var dyHeight: Short = -1 + private var icv: Short = -1 + private var bls: Short = -1 + private var sss: Short = -1 + private var uls: Short = -1 + private var bFamily: Short = -1 + private var bCharSet: Short = 0 + private var fontName = "" + // OOXML specifics: + private var customColor: Color? = null // holds custom color (OOXML or other use) + /** + * return whether this font is Condensed (OOXML-specific) Macintosh + * compatibility setting. + * + * @return + */ + /** + * set whether this font is Condensed (OOXML-specific) Macintosh + * compatibility setting. + * + * @param condensed + */ + var isCondensed: Boolean = false + /** + * return whether this font is Expanded (OOXML-specific) Macintosh + * compatibility setting. + * + * @return expanded + */ + /** + * set whether this font is Extended (OOXML-specific) Macintosh + * compatibility setting. + * + * @param expanded + */ + var isExtended: Boolean = false + + /** + * Get if the font record is striken our or not + * + * @return + */ + var stricken: Boolean + get() = grbit and BITMASK_STRIKEOUT == BITMASK_STRIKEOUT + set(b) { + if (b) + grbit = (grbit or BITMASK_STRIKEOUT).toShort() + else { + val grbittemp = grbit xor BITMASK_STRIKEOUT + grbit = (grbittemp and grbit).toShort() + } + this.setGrbit() + } + + /** + * Get if the font is italic or not + * + * @return + */ + // false: need to account for multiple font formats - add step 2: & + // original grbit + var italic: Boolean + get() { + val isItalic = grbit and BITMASK_ITALIC + return isItalic == BITMASK_ITALIC + } + set(b) { + if (b) + grbit = (grbit or BITMASK_ITALIC).toShort() + else { + val grbittemp = grbit xor BITMASK_ITALIC + grbit = (grbittemp and grbit).toShort() + } + this.setGrbit() + } + + /** + * Get if the font is underlined or not + * + * @return + */ + // 20070821 KSC: should also set underline + // style ... + var underlined: Boolean + get() = grbit and BITMASK_UNDERLINED == BITMASK_UNDERLINED + set(b) { + if (b) + grbit = (grbit or BITMASK_UNDERLINED).toShort() + else { + val grbittemp = grbit xor BITMASK_UNDERLINED + grbit = (grbittemp and grbit).toShort() + } + this.setGrbit() + setUnderlineStyle(1.toByte()) + } + + /** + * get if the font is bold or not + * + * @return + */ + /** + * Set or unset bold attribute of the font record + * + * @param b + */ + var bold: Boolean + get() = grbit and BITMASK_BOLD == BITMASK_BOLD + set(b) { + if (data == null) + this.setData(this.getData()) + if (b) { + val boldbytes = ByteTools.shortToLEBytes(0x2bc.toShort()) + System.arraycopy(boldbytes, 0, data!!, 6, 2) + bls = 0x2bc + grbit = (grbit or BITMASK_BOLD).toShort() + } else { + val boldbytes = ByteTools.shortToLEBytes(0x190.toShort()) + System.arraycopy(boldbytes, 0, data!!, 6, 2) + bls = 0x190 + val grbittemp = grbit xor BITMASK_BOLD + grbit = (grbittemp and grbit).toShort() + } + this.setGrbit() + } + + /** + * @param idx + */ + var idx = -1 + + /** + * add to Fonts table in Workbook + */ + override var workBook: WorkBook? + get + set(b) { + super.workBook = b + if (this.idx == -1) { + this.idx = this.workBook!!.addFont(this) + } + } + + /** + * Get an int representing the underline style of this record, matches int + * records in FormatConstants.STYLE_UNDERLINE***** + * + * @return + * @see + */ + val underlineStyle: Int + get() = this.getData()!![10].toInt() + + /** + * returns the super/sub script for the Font + * + * @return int (0 = none, 1 = super, 2 = sub) + */ + /** + * + */ + /** + * Set the super/sub script for the Font + * + * + * super/sub (0 = none, 1 = super, 2 = sub) + */ + var script: Int + get() = sss.toInt() + set(ss) { + if (data == null) + this.setData(this.getData()) + val newss = ByteTools.shortToLEBytes(ss.toShort()) + System.arraycopy(newss, 0, data!!, 8, 2) + sss = ss.toShort() + } + + /** + * Set the weight of the font in 1/20 point units 100-1000 range. + */ + var fontWeight: Int + get() = this.bls.toInt() + set(wt) { + if (data == null) + this.setData(this.getData()) + val newwt = ByteTools.shortToLEBytes(wt.toShort()) + System.arraycopy(newwt, 0, data!!, 6, 2) + bls = wt.toShort() + } + + /** + * Set the size of the font in 1/20 point units + */ + var fontHeight: Int + get() = this.dyHeight.toInt() + set(ht) { + if (data == null) + this.setData(this.getData()) + val newht = ByteTools.shortToLEBytes(ht.toShort()) + System.arraycopy(newht, 0, data!!, 0, 2) + dyHeight = ht.toShort() + } + + val fontHeightInPoints: Double + get() = this.dyHeight / 20.0 + + /** + * Get the color for this Font as a avt.Color + * + * @return + */ + // If icv is System window text color=7FFF, default fg color or default tooltip text color, return black + /* notes: special icv values: + 0x0040 Default foreground color. This is the window text color in the sheet display. + 0x0041 Default background color. This is the window background color in the sheet display and is the default background color for a cell. + 0x004D Default chart foreground color. This is the window text color in the chart display. + 0x004E Default chart background color. This is the window background color in the chart display. + 0x004F Chart neutral color which is black, an RGB value of (0,0,0). + 0x0051 ToolTip text color. This is the automatic font color for comments. + 0x7FFF Font automatic color. This is the window text color. + */ val colorAsColor: java.awt.Color + get() { + if (customColor != null) + return customColor!!.colorAsColor + if (this.icv.toInt() == 0x7FFF || this.icv.toInt() == 0x40 || this.icv.toInt() == 0x51) { + return java.awt.Color.BLACK + } else if (this.icv > FormatHandle.COLORTABLE.size) { + return java.awt.Color.BLACK + } + return if (this.workBook == null) FormatHandle.COLORTABLE[this.icv] else this.colorTable[this.icv] + } + + /** + * Get the color of this Font as a web-compliant Hex String + */ + // remove "FF" from beginning + val colorAsHex: String + get() = if (customColor != null && customColor!!.colorAsOOXMLRBG != null) "#" + customColor!!.colorAsOOXMLRBG!!.substring(2) else FormatHandle.colorToHexString(colorAsColor) + + /** + * returns the font color as an OOXML-compliant Hex Stringf * + * + * @return + */ + val colorAsOOXMLRBG: String + get() { + val rgbcolor = colorAsHex + return "FF" + rgbcolor.substring(1) + } + + /** + * gets the color of this font as an index into excel 2003 color table + * + * @return int + */ + val fontColor: Int + @Deprecated("use getColor()") + get() = color + + /** + * gets the color of this font as an index into excel 2003 color table + * + * @return int + */ + /** + * Set the font color via index into 2003 color table + */ + // this is a value for system font color, default + // to black + // don't do it if the font is already this color + var color: Int + get() { + if (customColor != null) + return customColor!!.colorInt + return if (this.icv.toInt() == 32767) 0 else this.icv.toInt() + } + set(cl) { + if (data == null) + setData(this.getData()) + if (cl != icv.toInt()) { + val newcl = ByteTools.shortToLEBytes(cl.toShort()) + System.arraycopy(newcl, 0, data!!, 4, 2) + icv = cl.toShort() + } + if (customColor != null) + customColor!!.colorInt = cl + } + + /** + * Get if the font is bold or not + */ + val isBold: Boolean + get() = bls > 0x190 + + /** + * @return an XML descriptor for this Font + */ + // changed from 'getFontInfoXML' + val xml: String + get() = getXML(false) + + /** + * return the OOXML font color element + * + * @return + */ + /** + * store OOXML font color + * + * @param c + */ + var ooxmlColor: Color? + get() = customColor + set(c) { + if (c != null) + this.color = c.colorInt + customColor = c + } + + /** + * generate the OOXML to define this Font + * + * @return + */ + // TODO: family, scheme + // the default + // KSC: modify due to certain XLS->XLSX issues with automatic color + // leave automatic "blank" + // for incremental styles, font size may not be set + // for incremental styles, font name may + // not be set + // TODO: family val= # (see OOXMLConstants) + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (this.isBold) + ooxml.append("") + if (this.italic) + ooxml.append("") + if (this.underlined) { + val u = this.underlineStyle + if (u == 1) + ooxml.append("") + else if (u == 2) + ooxml.append("") + else if (u == 0x21) + ooxml.append("") + else if (u == 0x22) + ooxml.append("") + } + if (this.stricken) + ooxml.append("") + if (!this.isCondensed) + ooxml.append("") + if (!this.isExtended) + ooxml.append("") + val c = this.ooxmlColor + if (c != null) { + ooxml.append(c.ooxml) + } else { + if (this.icv.toInt() != 9 && this.icv.toInt() != 64) { + val cl = this.color + if (cl > 0) + ooxml.append("") + } + } + val sz = this.fontHeightInPoints + if (sz > 0) + ooxml.append("") + val n = this.getFontName() + if (n != null && n != "") + ooxml.append("") + + ooxml.append("") + ooxml.append("\r\n") + return ooxml.toString() + } + + /** + * return the appropriate SVG string to define this font + * + * @return + */ + // sbf.append(" fill='#222222'"); // TODO: get proper text color + val svg: String + get() { + val sbf = StringBuffer("font-family='" + + this.getFontName() + "'") + sbf.append(" font-size='" + this.fontHeightInPoints + "pt'") + sbf.append(" font-weight='" + this.fontWeight + "'") + if (this.icv.toInt() != 9) + sbf.append(" fill='" + + FormatHandle.colorToHexString(FormatHandle.getColor(this + .color)) + "'") + else + sbf.append(" fill='" + + FormatHandle.colorToHexString(FormatHandle.getColor(0)) + + "'") + return sbf.toString() + } + + /** + * EXPERIMENTAL AND MAY NOT BE COMPLETE

                  + * Returns true if this font is a Unicode (non-ascii) Charset + * + * @return + */ + private val isUnicodeCharSet: Boolean + get() = (bCharSet.toInt() == SHIFTJIS_CHARSET || bCharSet.toInt() == HANGEUL_CHARSET + || bCharSet.toInt() == HANGUL_CHARSET || bCharSet.toInt() == GB2312_CHARSET + || bCharSet.toInt() == CHINESEBIG5_CHARSET + || bCharSet.toInt() == HEBREW_CHARSET || bCharSet.toInt() == ARABIC_CHARSET + || bCharSet.toInt() == GREEK_CHARSET || bCharSet.toInt() == TURKISH_CHARSET + || bCharSet.toInt() == VIETNAMESE_CHARSET || bCharSet.toInt() == THAI_CHARSET + || bCharSet.toInt() == EASTEUROPE_CHARSET + || bCharSet.toInt() == RUSSIAN_CHARSET || bCharSet.toInt() == BALTIC_CHARSET) + + /** + * Initialize the font record + */ + override fun init() { + super.init() + dyHeight = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt())// Height + // in + // 1/20 + // point + grbit = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt())// attributes + icv = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt())// index + // to + // color + // palette + bls = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt())// bold + // style + // (weight) + // 100-1000 + // default + // is + // 190h + // norm + // 2bch + // bold + sss = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt())// super/sub + // (0 = + // none, + // 1 = + // super, + // 2 = + // sub) + uls = this.getByteAt(10).toShort()// Underline Style (0 = none, 1 = single, 2 = + // double, 21h = single acctg, 22h = dble + // acctg) + bFamily = this.getByteAt(11).toShort()// Font Family (WinAPI LOGFONT struct) + /** + * lfCharSet + * + * The character set. The following values are predefined. + * + * ANSI_CHARSET BALTIC_CHARSET xx CHINESEBIG5_CHARSET xx DEFAULT_CHARSET + * xx EASTEUROPE_CHARSET GB2312_CHARSET xx GREEK_CHARSET xx + * HANGUL_CHARSET xx MAC_CHARSET xx OEM_CHARSET xx RUSSIAN_CHARSET xx + * SHIFTJIS_CHARSET xx SYMBOL_CHARSET xx TURKISH_CHARSET xx + * VIETNAMESE_CHARSET + * + * + * Korean language edition of Windows: JOHAB_CHARSET + * + * Middle East language edition of Windows: ARABIC_CHARSET + * HEBREW_CHARSET + * + * Thai language edition of Windows: THAI_CHARSET + * + * The OEM_CHARSET value specifies a character set that is + * operating-system dependent. + * + * DEFAULT_CHARSET is set to a value based on the current system locale. + * For example, when the system locale is English (United States), it is + * set as ANSI_CHARSET. + * + * Fonts with other character sets may exist in the operating system. If + * an application uses a font with an unknown character set, it should + * not attempt to translate or interpret strings that are rendered with + * that font. + * + * This parameter is important in the font mapping process. To ensure + * consistent results, specify a specific character set. If you specify + * a typeface name in the lfFaceName member, make sure that the + * lfCharSet value matches the character set of the typeface specified + * in lfFaceName. + */ + bCharSet = ByteTools.readUnsignedShort(this.getByteAt(12), + 0.toByte()).toShort()// Characterset (WinAPI LOGFONT struct) + + // this.getData()[13]= 0;// set byte to 0 for comparisons + // get the Name + var pos = 14 + cch = this.getByteAt(pos++).toShort() + var buflen = cch * 2 + pos++ + var compressed = false + + if (buflen + pos >= this.length) { + buflen = this.length - pos + compressed = true + } + + if (buflen < 0) { + Logger.logWarn("could not parse font: length reported as $buflen") + return + } + + val namebytes = this.getBytesAt(pos, buflen) + if (!compressed) { + pos = 0 + try { + fontName = String(namebytes!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logErr("Font name decoding failed.", e) + } + + } else { // compressed + fontName = String(namebytes!!) + } + } + + /** + * update the Grbit bytes in the underlying byte stream + */ + fun setGrbit() { + val data = this.getData() + val b = ByteTools.shortToLEBytes(grbit) + System.arraycopy(b, 0, data!!, 2, 2) + this.setData(data) + + } + + override fun toString(): String { + return (this.fontName + "," + this.bls + "," + this.dyHeight + " " + + this.colorAsColor + " font style:[" + this.bold + + this.italic + this.stricken + this.underlined + + this.color + this.underlineStyle + "]") + } + + fun getFontName(): String? { + return fontName + } + + /** + * Set the underline style of this font recotd + * + * @param styl + */ + fun setUnderlineStyle(styl: Byte) { + this.uls = styl.toShort() + this.getData()[10] = styl + } + + constructor() {} + + /** + * Create a New Font from the String definition. + * + * + * Roughly matches the functionality of the java.awt.Font class. + * + * @param String font name + * @param int font style + * @param int font size in Points + */ + constructor(nm: String, stl: Int, sz: Int) { + val bl = byteArrayOf(-56, 0, 0, 0, -1, 127, -112, 1, 0, 0, 0, 0, 0, 0, 5, 1, 65, 0, 114, 0, 105, 0, 97, 0, 108, 0) + opcode = XLSConstants.FONT + length = bl.size.toShort() + this.setData(bl) + this.init() + this.setFontName(nm) + this.fontWeight = stl + this.fontHeight = sz + } + + /** + * Set the Font name. + * + * + * To be valid, this font name must be available on the client system. + */ + fun setFontName(fn: String) { + var namebytes: ByteArray? = null + try { + namebytes = fn.toByteArray(charset(XLSConstants.UNICODEENCODING)) + } catch (e: UnsupportedEncodingException) { + Logger.logWarn("setting Font Name using Default Encoding failed: $e") + namebytes = fn.toByteArray() + } + + cch = (namebytes!!.size / 2).toShort() + fontName = fn + val newdata = ByteArray(namebytes.size + 16) + System.arraycopy(this.getBytesAt(0, 13)!!, 0, newdata, 0, 13)// 20061027 KSC: keep 13th byte for sake of comparisons - 20070816 - revert to original + System.arraycopy(this.getBytesAt(0, 14)!!, 0, newdata, 0, 14) + newdata[14] = cch.toByte() + newdata[15] = 1.toByte() + System.arraycopy(namebytes, 0, newdata, 16, namebytes.size) + this.setData(newdata) + this.init() + } + + /** + * Sets the font color via java.awt.Color + */ + fun setColor(color: java.awt.Color) { + if (customColor != null) + customColor!!.setColor(color) + else + customColor = Color(color, "color", this.workBook!!.theme) + icv = customColor!!.colorInt.toShort() + val newcl = ByteTools.shortToLEBytes(icv) + System.arraycopy(newcl, 0, data!!, 4, 2) + } + + /** + * Sets the font color via a web-compliant Hex String + */ + fun setColor(clr: String) { + if (customColor != null) + customColor!!.setColor(clr) + else + customColor = Color(clr, "color", this.workBook!!.theme) + icv = customColor!!.colorInt.toShort() + val newcl = ByteTools.shortToLEBytes(icv) + System.arraycopy(newcl, 0, data!!, 4, 2) + } + + /** + * return an XML desciptor for this font + * + * @param convertToUnicodeFont if true, font family will be changed to ArialUnicodeMS + * (standard unicode) for non-ascii fonts + * @return + */ + fun getXML(convertToUnicodeFont: Boolean): String { + val sb = StringBuffer() + if (!convertToUnicodeFont || !isUnicodeCharSet) + sb.append("name=\"" + + StringTool.convertXMLChars(this.getFontName()) + "\"") + else + sb.append("name=\"ArialUnicodeMS\"") + sb.append(" size=\"" + this.fontHeightInPoints + "\"") + sb.append(" color=\"" + + FormatHandle.colorToHexString(this.colorAsColor) + "\"") + sb.append(" weight=\"" + this.fontWeight + "\"") + if (this.isBold) { + sb.append(" bold=\"1\"") + } + if (this.underlineStyle != Font.STYLE_UNDERLINE_NONE.toInt()) + sb.append(" underline=\"$underlineStyle\"") + return sb.toString() + } + + /** + * return true if font f matches key attributes of this font + * + * @param f + * @return + */ + fun matches(f: Font): Boolean { + return (this.fontName == f.fontName && this.dyHeight == f.dyHeight + && this.bls == f.bls && this.color == f.color + && this.sss == f.sss && this.uls == f.uls && this.grbit == f.grbit) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -398444997553403671L + + // grbit flags + internal val BITMASK_BOLD = 0x0001 + internal val BITMASK_ITALIC = 0x0002 + internal val BITMASK_UNDERLINED = 0x0004 + internal val BITMASK_STRIKEOUT = 0x0008 + + // charset values + internal val ANSI_CHARSET = 0 + internal val DEFAULT_CHARSET = 1 + internal val SYMBOL_CHARSET = 2 + internal val SHIFTJIS_CHARSET = 128 + internal val HANGEUL_CHARSET = 129 + internal val HANGUL_CHARSET = 129 + internal val GB2312_CHARSET = 134 + internal val CHINESEBIG5_CHARSET = 136 + internal val OEM_CHARSET = 255 + internal val JOHAB_CHARSET = 130 + internal val HEBREW_CHARSET = 177 + internal val ARABIC_CHARSET = 178 + internal val GREEK_CHARSET = 161 + internal val TURKISH_CHARSET = 162 + internal val VIETNAMESE_CHARSET = 163 + internal val THAI_CHARSET = 222 + internal val EASTEUROPE_CHARSET = 238 + internal val RUSSIAN_CHARSET = 204 + internal val MAC_CHARSET = 77 + internal val BALTIC_CHARSET = 186 + + /** + * utility to convert points to correct font height + * + * @param h + * @return + */ + fun PointsToFontHeight(h: Double): Int { + return (h * 20).toInt() + } + + fun FontHeightToPoints(h: Int): Double { + return h / 20.0 + } + + /** + * parse incoming OOXML into a Font object + * + * @param xpp + * @return + */ + // TODO: family, scheme + fun parseOOXML(xpp: XmlPullParser, bk: WorkBookHandle): Font { + var c: Color? = null + var sz: String? = null + var name = "" + var u: Any? = null + var b = false + var strike = false + var ital = false + var condense = false + var expand = false + try { + var eventType = xpp.next() + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "sz") { + sz = xpp.getAttributeValue(0) + } else if (tnm == "name") { + name = xpp.getAttributeValue(0) + } else if (tnm == "b") { + if (xpp.attributeCount == 0) + b = true + else + b = xpp.getAttributeValue(0) == "1" + } else if (tnm == "i") { + if (xpp.attributeCount == 0) + ital = true + else + ital = xpp.getAttributeValue(0) == "1" + } else if (tnm == "u") { + if (xpp.attributeCount == 0) + u = java.lang.Boolean.valueOf(true) + else + u = xpp.getAttributeValue(0) + } else if (tnm == "strike") { + strike = true + } else if (tnm == "condense") { + condense = false + } else if (tnm == "expand") { + expand = false + } else if (tnm == "color") { + c = Color.parseOOXML(xpp, FormatHandle.colorFONT, bk) as Color + } + } else if (eventType == XmlPullParser.END_TAG && xpp.name == "font") + break + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("Font.parseOOXML: $e") + } + + // for incremental styles, font size may not be set + val size = if (sz == null) + -1 + else + Font.PointsToFontHeight(Double(sz)) + val f = Font(name, 400, size) + if (c != null) + f.ooxmlColor = c + if (u != null) { + f.underlined = true + if (u is String) { + if (u == "double") + f.setUnderlineStyle(2.toByte()) + else if (u == "singleAccounting") + f.setUnderlineStyle(0x21.toByte()) + else if (u == "doubleAccounting") + f.setUnderlineStyle(0x22.toByte()) + } + } + if (b) + f.bold = b + if (ital) + f.italic = true + if (strike) + f.stricken = true + if (!condense) + f.isCondensed = false + if (!expand) + f.isExtended = false + return f + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/FontBasis.java b/src/main/java/io/starter/formats/XLS/FontBasis.kt similarity index 65% rename from src/main/java/io/starter/formats/XLS/FontBasis.java rename to src/main/java/io/starter/formats/XLS/FontBasis.kt index 20ddcb9..aac37ef 100644 --- a/src/main/java/io/starter/formats/XLS/FontBasis.java +++ b/src/main/java/io/starter/formats/XLS/FontBasis.kt @@ -20,46 +20,54 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** * FBI: Font Basis (1060h) - *

                  + * + * * The FBI record stores font metrics. * Chart use only. - *

                  + * + * * Offset | Name | Size | Contents - *

                  + * + * * 4 | dmixBasis | 2 | Width of basis when font was applied - *

                  + * + * * 6 | dmiyBasis | 2 | Height of basis when font was applied - *

                  + * + * * 8 | twpHeightBasis | 2 | Font height applied - *

                  + * + * * 10 | scab | 2 | Scale basis - *

                  + * + * * 12 | ifnt | 2 | Index number into the font table */ -public class FontBasis extends XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6984935185426785077L; +class FontBasis : XLSRecord() { - public void init() { - super.init(); - } + var fontIndex: Int + get() = ByteTools.readShort(this.getData()!![8].toInt(), this.getData()!![9].toInt()).toInt() + set(id) { + val b = ByteTools.shortToLEBytes(id.toShort()) + this.getData()[8] = b[0] + this.getData()[9] = b[1] + } - public int getFontIndex() { - return ByteTools.readShort(this.getData()[8], this.getData()[9]); + override fun init() { + super.init() } - public void setFontIndex(int id) { - byte[] b = ByteTools.shortToLEBytes((short) id); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 6984935185426785077L } } diff --git a/src/main/java/io/starter/formats/XLS/Footerrec.java b/src/main/java/io/starter/formats/XLS/Footerrec.java deleted file mode 100644 index bf0265f..0000000 --- a/src/main/java/io/starter/formats/XLS/Footerrec.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - - -/** - * Footerrec: Print Footer on Each Page (15h)
                  - *

                  - * Footerrec describes the header printed on every page - * - *

                  - * offset  name            size    contents
                  - * ---
                  - * 4       cch             1       Length of the Footer String
                  - * 5       rgch            var     Footer String
                  - *
                  - * 

                  - **/ -public final class Footerrec extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 227652250172483965L; - int cch = -1; - String rgch = ""; - boolean DEBUG = false; - - - public void setSheet(Sheet bs) { - super.setSheet(bs); - bs.setFooter(this); - } - - /** - * set the footer text - * The key here is that we need to construct an excel formatted unicode string, so there will be 2 differing length fields. - *

                  - * Yes, this will probably be an issue in Japan some day.... - */ - public void setFooterText(String t) { - try { - if (ByteTools.isUnicode(t)) { - byte[] bts = t.getBytes(UNICODEENCODING); - cch = bts.length / 2; - byte[] newbytes = new byte[cch + 3]; - byte[] cchx = io.starter.toolkit.ByteTools.shortToLEBytes((short) cch); - newbytes[0] = cchx[0]; - newbytes[1] = cchx[1]; - newbytes[2] = 0x1; - System.arraycopy(bts, 0, newbytes, 3, bts.length); - this.setData(newbytes); - } else { - byte[] bts = t.getBytes(DEFAULTENCODING); - cch = bts.length; - byte[] newbytes = new byte[cch + 3]; - byte[] cchx = io.starter.toolkit.ByteTools.shortToLEBytes((short) cch); - newbytes[0] = cchx[0]; - newbytes[1] = cchx[1]; - newbytes[2] = 0x0; - System.arraycopy(bts, 0, newbytes, 3, bts.length); - this.setData(newbytes); - } - } catch (Exception e) { - Logger.logInfo("setting Footer text failed: " + e); - } - this.rgch = t; - } - - /** - * get the footer text - */ - public String getFooterText() { - return rgch; - } - - public void init() { - super.init(); - if (this.getLength() > 4) { - int cch = (int) this.getByteAt(0); - byte[] namebytes = this.getBytesAt(0, this.getLength() - 4); - Unicodestring fstr = new Unicodestring(); - fstr.init(namebytes, false); - rgch = fstr.toString(); - if (DEBUGLEVEL > DEBUG_LOW) - Logger.logInfo("Footer text: " + this.getFooterText()); - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Footerrec.kt b/src/main/java/io/starter/formats/XLS/Footerrec.kt new file mode 100644 index 0000000..38e215a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Footerrec.kt @@ -0,0 +1,117 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * **Footerrec: Print Footer on Each Page (15h)**

                  + * + * + * Footerrec describes the header printed on every page + * + * + *

                  + * offset  name            size    contents
                  + * ---
                  + * 4       cch             1       Length of the Footer String
                  + * 5       rgch            var     Footer String
                  + *
                  +
                  * + */ +class Footerrec : io.starter.formats.XLS.XLSRecord() { + internal var cch = -1 + internal var rgch: String? = "" + internal var DEBUG = false + + /** + * get the footer text + */ + /** + * set the footer text + * The key here is that we need to construct an excel formatted unicode string, so there will be 2 differing length fields. + * + * + * Yes, this will probably be an issue in Japan some day.... + */ + var footerText: String? + get() = rgch + set(t) { + try { + if (ByteTools.isUnicode(t)) { + val bts = t.toByteArray(charset(XLSConstants.UNICODEENCODING)) + cch = bts.size / 2 + val newbytes = ByteArray(cch + 3) + val cchx = io.starter.toolkit.ByteTools.shortToLEBytes(cch.toShort()) + newbytes[0] = cchx[0] + newbytes[1] = cchx[1] + newbytes[2] = 0x1 + System.arraycopy(bts, 0, newbytes, 3, bts.size) + this.setData(newbytes) + } else { + val bts = t.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + cch = bts.size + val newbytes = ByteArray(cch + 3) + val cchx = io.starter.toolkit.ByteTools.shortToLEBytes(cch.toShort()) + newbytes[0] = cchx[0] + newbytes[1] = cchx[1] + newbytes[2] = 0x0 + System.arraycopy(bts, 0, newbytes, 3, bts.size) + this.setData(newbytes) + } + } catch (e: Exception) { + Logger.logInfo("setting Footer text failed: $e") + } + + this.rgch = t + } + + + override fun setSheet(bs: Sheet?) { + super.setSheet(bs) + bs!!.setFooter(this) + } + + override fun init() { + super.init() + if (this.length > 4) { + val cch = this.getByteAt(0).toInt() + val namebytes = this.getBytesAt(0, this.length - 4) + val fstr = Unicodestring() + fstr.init(namebytes!!, false) + rgch = fstr.toString() + if (DEBUGLEVEL > XLSConstants.DEBUG_LOW) + Logger.logInfo("Footer text: " + this.footerText!!) + } + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 227652250172483965L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Format.java b/src/main/java/io/starter/formats/XLS/Format.kt similarity index 58% rename from src/main/java/io/starter/formats/XLS/Format.java rename to src/main/java/io/starter/formats/XLS/Format.kt index d3ef568..567811c 100644 --- a/src/main/java/io/starter/formats/XLS/Format.java +++ b/src/main/java/io/starter/formats/XLS/Format.kt @@ -27,23 +27,40 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.StringTool; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.StringTool /** * Stores a custom number format pattern. */ -public final class Format extends XLSRecord { - private static final long serialVersionUID = 1199947552103220748L; +class Format() : XLSRecord() { - private short ifmt = -1; - private String pattern; - private Unicodestring ustring; + private var ifmt: Short = -1 + /** + * Gets the format pattern string represented by this Format. + */ + var format: String? = null + private set + private var ustring: Unicodestring? = null + + override/* + * Not sure why this is here, but I think it has something + * to do with + * worksheet cloning. It's harmless, so might as well leave + * it alone. + * - Sam + */ var workBook: WorkBook? + get + set(book) { + super.workBook = book + if (ifmt.toInt() != -1) + book!!.addFormat(this) + } - public Format() { - setOpcode(XLSConstants.FORMAT); + init { + opcode = XLSConstants.FORMAT } /** @@ -52,40 +69,37 @@ public Format() { * @param book the workbook to which the pattern should belong * @param pattern the number format pattern to ensure exists */ - public Format(WorkBook book, String pattern) { - this(book, (short) -1, pattern); - } + constructor(book: WorkBook, pattern: String) : this(book, (-1).toShort(), pattern) {} /** * Makes a new Format record with the given ID and pattern. * This should only be used when parsing non-BIFF8 files. BIFF8 parsing * will use the normal XLSRecord init sequence. For programmatic creation - * of custom formats use {@link #Format(WorkBook, String)} instead. + * of custom formats use [.Format] instead. * * @param book the workbook to which the pattern should belong * @param id the format ID to use or -1 to generate one * @param pattern the number format pattern to ensure exists */ - public Format(WorkBook book, short id, String pattern) { - this(); - setWorkBook(book); + constructor(book: WorkBook, id: Short, pattern: String) : this() { + workBook = book - this.pattern = pattern; + this.format = pattern this.ustring = Sst - .createUnicodeString(pattern, null, WorkBook.STRING_ENCODING_AUTO); + .createUnicodeString(pattern, null, WorkBook.STRING_ENCODING_AUTO) - byte[] idbytes; + val idbytes: ByteArray if (id > 0) { - this.ifmt = id; - idbytes = ByteTools.shortToLEBytes(id); + this.ifmt = id + idbytes = ByteTools.shortToLEBytes(id) } else { // WorkBook.insertFormat will call setIfmt - idbytes = new byte[2]; + idbytes = ByteArray(2) } - setData(ByteTools.append(ustring.read(), idbytes)); + setData(ByteTools.append(ustring!!.read(), idbytes)) - book.insertFormat(this); + book.insertFormat(this) } /** @@ -95,20 +109,19 @@ public Format(WorkBook book, short id, String pattern) { * * @throws IllegalStateException if the record has already been parsed */ - @Override - public void init() { - if (pattern != null) - throw new IllegalStateException( - "the record has already been parsed"); + override fun init() { + if (format != null) + throw IllegalStateException( + "the record has already been parsed") - super.init(); + super.init() - ifmt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + ifmt = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) - ustring = new Unicodestring(); - ustring.init(this.getBytesAt(2, this.getLength() - 2), false); + ustring = Unicodestring() + ustring!!.init(this.getBytesAt(2, this.length - 2)!!, false) - pattern = ustring.toString(); + format = ustring!!.toString() /* * Strip double quotes and backslashes from the format @@ -122,59 +135,40 @@ public void init() { * format parser * - Sam */ - pattern = StringTool.replaceText(pattern, "\"", "", 0); - pattern = StringTool.replaceText(pattern, "\\", "", 0); - - this.getWorkBook().addFormat(this); - } - - @Override - public void setWorkBook(WorkBook book) { - super.setWorkBook(book); + format = StringTool.replaceText(format!!, "\"", "", 0) + format = StringTool.replaceText(format!!, "\\", "", 0) - /* - * Not sure why this is here, but I think it has something - * to do with - * worksheet cloning. It's harmless, so might as well leave - * it alone. - * - Sam - */ - if (ifmt != -1) - book.addFormat(this); + this.workBook!!.addFormat(this) } /** * Sets the format ID of this Format record. */ - public void setIfmt(short id) { - ifmt = id; + fun setIfmt(id: Short) { + ifmt = id // Update the record bytes System.arraycopy(ByteTools.shortToLEBytes(ifmt), 0, this - .getData(), 0, 2); - } - - /** - * Gets the format pattern string represented by this Format. - */ - public String getFormat() { - return pattern; + .getData()!!, 0, 2) } /** * Gets the format index of this Format in its workbook. */ - public short getIfmt() { - return ifmt; + fun getIfmt(): Short { + return ifmt } - @Override - public String toString() { - return getFormat(); + override fun toString(): String? { + return format } - public void init(WorkBook workBook, int fID, String format) { + fun init(workBook: WorkBook, fID: Int, format: String) { // TODO Auto-generated method stub } + + companion object { + private val serialVersionUID = 1199947552103220748L + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/FormatConstants.java b/src/main/java/io/starter/formats/XLS/FormatConstants.java deleted file mode 100644 index dcd669a..0000000 --- a/src/main/java/io/starter/formats/XLS/FormatConstants.java +++ /dev/null @@ -1,1020 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import java.awt.Color; - -/** - * /** Constants relevant to XLS Formatting - *

                  - * NOTE: - * Excel automatically assigns - * If you type this number format - * ------------------------------------------- - *

                  - * 1.0 General - * 1.123 General - * 1.1% 0.00% - * 1.1E+2 0.00E+00 - * 1 1/2 # ?/? - * $1.11 Currency, 2 decimal places - * 1/1/01 Date - * 1:10 Time - *

                  - * CUSTOM FORMATS: - *

                  - * Each format that you create can have up to three sections for numbers and a fourth section for text. - * ;;; - * - * @see CustomFormatHelper - */ -public interface FormatConstants { - - - int DEFAULT_FONT_WEIGHT = 200, DEFAULT_FONT_SIZE = 20; - String DEFAULT_FONT_FACE = "Arial"; - - - // Font Weights - int PLAIN = 400; - int BOLD = 700; - - // Font UnderLine Style constants - byte STYLE_UNDERLINE_NONE = 0x0; - byte STYLE_UNDERLINE_SINGLE = 0x1; - byte STYLE_UNDERLINE_DOUBLE = 0x2; - byte STYLE_UNDERLINE_SINGLE_ACCTG = 0x21; - byte STYLE_UNDERLINE_DOUBLE_ACCTG = 0x22; - - - // Color Constants - Color Black = new Color(0, 0, 0); - Color Brown = new Color(153, 51, 0); - Color OliveGreen = new Color(51, 51, 0); - Color Dark_Green = new Color(0, 51, 0); - Color Dark_Teal = new Color(0, 51, 102); - Color Dark_Blue = new Color(0, 0, 128); - Color Indigo = new Color(51, 51, 153); - Color Gray80 = new Color(51, 51, 51); - // could we be wrong?? test it per claritas -// public static Color Gray50 = new Color(110,110,110); - // Excel 07 likes real 50 percent = new Color(127,127,127); - Color Gray50 = new Color(136, 136, 136); - - Color Dark_Red = new Color(128, 0, 0); - Color Orange = new Color(255, 102, 0); - Color Dark_Yellow = new Color(128, 128, 0); - Color Green = new Color(0, 128, 0); - Color Teal = new Color(0, 128, 128); - Color Blue = new Color(0, 0, 255); - Color BlueGray = new Color(102, 102, 153); - - Color Red = new Color(255, 0, 0); - Color Light_Orange = new Color(255, 153, 0); - Color Lime = new Color(153, 204, 0); - Color SeaGreen = new Color(51, 153, 102); - Color Aqua = new Color(51, 204, 204); - Color Light_Blue = new Color(0, 128, 255); - Color Violet = new Color(128, 0, 128); - Color Gray40 = new Color(150, 150, 150); - - Color Pink = new Color(255, 0, 255); - Color Gold = new Color(255, 204, 0); - Color Yellow = new Color(255, 255, 0); - Color BrightGreen = new Color(0, 255, 0); - Color Turquoise = new Color(0, 255, 255); - Color SkyBlue = new Color(0, 204, 255); - Color Plum = new Color(153, 51, 102); - Color Gray25 = new Color(192, 192, 192); - - Color Rose = new Color(255, 153, 204); - Color Tan = new Color(255, 204, 153); - Color Light_Yellow = new Color(255, 255, 153); - Color Light_Green = new Color(204, 255, 204); - Color Light_Turquoise = new Color(204, 255, 255); - Color PaleBlue = new Color(153, 204, 255); - Color Lavender = new Color(204, 153, 255); - Color White = new Color(255, 255, 255); - Color Gray15 = new Color(216, 216, 216); - Color Dark_Purple = new Color(102, 0, 102); - Color Salmon = new Color(255, 128, 128); - Color Light_Purple = new Color(204, 204, 255); - Color Medium_Purple = new Color(153, 153, 255); - - /** - * Excel's basic icv color table. This may be altered by the Palette record. - * See WorkBook.CCLORTABLE - */ - Color[] COLORTABLE = { - FormatConstants.Black, - FormatConstants.White, - FormatConstants.Red, - FormatConstants.BrightGreen, /// WRONG, was duplicate, incorrect GREEN - FormatConstants.Blue, - FormatConstants.Yellow, - FormatConstants.Pink, - FormatConstants.Turquoise, - FormatConstants.Black, -/** - * NOTE: from documentation: - * if palette record exists 8-63 (0x3f) gets read from it, - * otherwise If a Palette record exists in this file, these icv values - * specify colors from the rgColor array in the Palette record. -*/ - /* - * Color 9 === AUTOMATIC color - * the opposite of what is normal; for instance, background color - * index 9= black, font color index 9= white. It is set here to - * black, and in Fonts.getColor, a special case is put for icv= 9 - */ - FormatConstants.White, // 9 - FormatConstants.Red, // 10 - FormatConstants.BrightGreen, - FormatConstants.Blue, - FormatConstants.Yellow, - FormatConstants.Pink, - FormatConstants.Turquoise, // 15 - FormatConstants.Dark_Red,// Burgundy //16 - FormatConstants.Green, - FormatConstants.Dark_Blue, // purple - FormatConstants.Dark_Yellow, - FormatConstants.Violet, // 20 dark pink - FormatConstants.Teal, - FormatConstants.Gray25, - FormatConstants.Gray50, - FormatConstants.Medium_Purple, - FormatConstants.Plum, // 25 - FormatConstants.Light_Yellow, - FormatConstants.Light_Turquoise, - FormatConstants.Dark_Purple, - FormatConstants.Salmon, - FormatConstants.BlueGray, // 30 - FormatConstants.Light_Purple, - FormatConstants.Dark_Blue, - FormatConstants.Pink, - FormatConstants.Yellow, - FormatConstants.Turquoise, // 35 - FormatConstants.Violet, - FormatConstants.Dark_Red, - FormatConstants.Teal, // ////////////WRONG - WAS DUPLICATE - FormatConstants.Blue, - FormatConstants.SkyBlue, // 40 - FormatConstants.Light_Turquoise, - FormatConstants.Light_Green, - FormatConstants.Light_Yellow, - FormatConstants.PaleBlue, - FormatConstants.Rose, // 45 - FormatConstants.Lavender, - FormatConstants.Tan, - FormatConstants.Dark_Yellow, - FormatConstants.Aqua, - FormatConstants.Lime, // 50 - FormatConstants.Gold, - FormatConstants.Light_Orange, - FormatConstants.Orange, - FormatConstants.BlueGray, - FormatConstants.Gray40, // 55 - FormatConstants.Dark_Teal, - FormatConstants.SeaGreen, - FormatConstants.Dark_Green, - FormatConstants.OliveGreen, - FormatConstants.Brown, // 60 - FormatConstants.Plum, - FormatConstants.Indigo, - FormatConstants.Gray80, - FormatConstants.White, // 64 - FormatConstants.Black, // 65 WHY DO THESE CHANGE??? - - }; - - // Formatting Color Constants - int COLOR_BLACK = 0; - int COLOR_WHITE = 1; - int COLOR_RED = 2; - int COLOR_BLUE = 4; - int COLOR_YELLOW = 5; - int COLOR_BLACK3 = 8; - int COLOR_WHITE3 = 9; - int COLOR_RED_CHART = 10; // chart fills use SPECIFIC icv numbers -- incorrect # results in black fill - int COLOR_BRIGHT_GREEN = 11; - int COLOR_YELLOW_CHART = 13; - int COLOR_DARK_RED = 16; - int COLOR_GREEN = 17; - int COLOR_DARK_BLUE = 18; - int COLOR_OLIVE_GREEN_CHART = 19; - int COLOR_DARK_YELLOW = 19; - int COLOR_VIOLET = 20; - int COLOR_TEAL = 21; - int COLOR_GRAY25 = 22; - int COLOR_GRAY50 = 23; - int COLOR_MEDIUM_PURPLE = 24; - int COLOR_PLUM = 25; - int COLOR_LIGHT_YELLOW = 26; - int COLOR_LIGHT_TURQUOISE = 27; - int COLOR_DARK_PURPLE = 28; - int COLOR_SALMON = 29; - int COLOR_LIGHT_BLUE = 30; - int COLOR_LIGHT_PURPLE = 31; - int COLOR_PINK = 33; - int COLOR_CYAN = 35; - int COLOR_BLUE_CHART = 39; - int COLOR_SKY_BLUE = 40; - - int COLOR_LIGHT_GREEN = 42; - int COLOR_PALE_BLUE = 44; - int COLOR_ROSE = 45; - int COLOR_LAVENDER = 46; - int COLOR_TAN = 47; - int COLOR_TURQUOISE = 48; - int COLOR_AQUA = 49; - int COLOR_LIME = 50; - int COLOR_GOLD = 51; - int COLOR_LIGHT_ORANGE = 52; - int COLOR_ORANGE = 53; - int COLOR_BLUE_GRAY = 54; - int COLOR_GRAY40 = 55; - int COLOR_DARK_TEAL = 56; - int COLOR_SEA_GREEN = 57; - int COLOR_DARK_GREEN = 58; - int COLOR_OLIVE_GREEN = 59; - int COLOR_BROWN = 60; - int COLOR_INDIGO = 62; - int COLOR_GRAY80 = 63; - int COLOR_WHITE2 = 64; - int COLOR_BLACK2 = 65; - - /** - * given color int ala COLORTABLE, interpret Excel Color name - */ - String[] COLORNAMES = { - "Black", - "White", - "Red", - "BrightGreen", - "Blue", - "Yellow", - "Pink", - "Turquoise", - "Black", - "White", - "Red", //10 - "BrightGreen", - "Blue", - "Yellow", - "Pink", - "Turquoise", // 15 - "Dark_Red",//Burgundy //16 - "Green", - "Dark_Blue", //purple - "Dark_Yellow", - "Violet", //20 dark pink - "Teal", - "Gray25", - "Gray50", - "MediumPurple", - "Plum", //25 - "Light_Yellow", - "Light_Turquoise", - "Dark_Purple", - "Salmon", - "BlueGray", //30 - "Light_Purple", - "Dark_Blue", - "Pink", - "Yellow", - "Turquoise", // 35 - "Violet", - "Dark_Red", - "Teal", - "Blue", - "SkyBlue", //40 - "Light_Turquoise", - "Light_Green", - "Light_Yellow", - "PaleBlue", - "Rose", //45 - "Lavender", - "Tan", - "Blue", - "Aqua", - "Lime", // 50 - "Gold", - "Light_Orange", - "Orange", - "BlueGray", - "Gray40", //55 - "Dark_Teal", - "SeaGreen", - "Dark_Green", - "OliveGreen", - "Brown", //60 - "Plum", - "Indigo", // WRONG - "Gray80", - "White", // 64 - "Black", // 65 - }; - - /** - * given color int ala COLORTABLE, interpret HTML Color name - */ - String[] HTMLCOLORNAMES = { - "Black", - "White", - "Red", - "Green", - "Blue", - "Yellow", - "Pink", - "Turquoise", - "Black", - "White", - "Red", //10 - "Lime", // BrightGreen", - "Blue", - "Yellow", - "Pink", - "Turquoise", // 15 - "Dark_Red", - "Green", - "DarkBlue", - "DarkKhaki", //Dark_Yellow", - "Violet", //20 - "Teal", - "LightGray", // Gray25 - "DarkGray", // Gray50" - "DodgerBlue", - "DarkRed", //25 - "LightYellow", - "PaleTurquoise", // Light_Turquoise", - "DarkMagenta", - "Red", - "CadetBlue", // BlueGray", //30 - "PowderBlue", // PaleBlue", - "DarkBlue", - "Pink", - "Yellow", - "Turquoise", // 35 - "Plum", - "Brown", - "SeaGreen", - "Blue", - "SkyBlue", //40 - "PaleTurquoise", //Light_Turquoise", - "LightGreen", - "LightYellow", - "PowderBlue", // PaleBlue", - "Crimson", // Rose", //45 - "Lavender", - "Tan", - "Blue", - "Aqua", - "Lime", // 50 - "Gold", - "Orange", //Light_Orange", - "Orange", - "CadetBlue", // BlueGray", - "YellowGreen", // Gray40", //55 - "DarkSlateBlue", //Dark_Teal", - "SeaGreen", - "DarkGreen", - "OliveDrab", //OliveGreen", - "Brown", //60 - "Plum", - "Indigo", // WRONG - "DimGray", // Gray80 - "White", // 64 - "Black", // 65 - }; - - String[] SVGCOLORSTRINGS = { - "black", - "white", - "red", - "lime", // = BrightGreen - "blue", - "yellow", // 5 - "fuchsia", // = Pink - "aqua", // = Turquoise - "black", - "white", - "red", // 10 - "lime", - "blue", - "yellow", - "fuchsia", - "aqua", // 15 - "maroon", // = Dark_Red - "green", - "navy", // = Dark_Blue - "olive", // = Dark_Yellow - "purple", // = Violet // 20 - "teal", - "silver", // = Gray25 - "gray", // = Gray50 - "lightblue", //lightsteelblue", - "palevioletred", //mediumorchid", // =Plum // 25 - "darkolivegreen", - "aqua", // = Light_Turqouise - "purple", // = Dark_Purple - "salmon", - "slateblue", // = BlueGray // 30 - "aliceblue", // = Light_Purple - "navy", // = Dark_Blue - "fuchsia", // = Pink - "yellow", - "aqua", // = Turquoise //35 - "purple", // = Violet - "maroon", // = Dark_Red - "teal", - "blue", - "deepskyblue", // = SkyBlue // 40 - "azure", // = Light_Turquoise - "honeydew", // = Light_Green - "lightyellow", - "powderblue", // = PaleBlue - "lightpink", // = Rose // 45 - "aliceblue", // = Lavendar - "navajowhite", // = Tan - "blue", - "mediumturquoise", // = Aqua - "lawngreen", // = Lime // 50 - "gold", - "orange", - "darkorange", - "slateblue", // = BlueGray - "darkgray", // = Gray40 //55 - "midnightblue", // = Dark_Teal - "seagreen", - "darkgreen", - "darkolivegreen", - "saddlebrown", // 60 - "mediumvioletred", - "darkslateblue", //= Indigo - "darkslategray", // = Gray80 - "white", // 64 - "black", // 65 - - }; - - // Formatting Pattern Constants - int PATTERN_NONE = 0; - int PATTERN_FILLED = 1; - int PATTERN_LIGHT_FILL = 2; - int PATTERN_MED_FILL = 3; - int PATTERN_HVY_FILL = 4; - int PATTERN_HOR_STRIPES1 = 5; - int PATTERN_VERT_STRIPES1 = 6; - int PATTERN_DIAG_STRIPES1 = 7; - int PATTERN_DIAG_STRIPES2 = 8; - int PATTERN_CHECKERBOARD1 = 9; - int PATTERN_CHECKERBOARD2 = 10; - int PATTERN_HOR_STRIPES2 = 11; - int PATTERN_VERT_STRIPES2 = 12; - int PATTERN_DIAG_STRIPES3 = 13; - int PATTERN_DIAG_STRIPES4 = 14; - int PATTERN_GRID1 = 15; - int PATTERN_CROSSPATCH1 = 16; - int PATTERN_MED_DOTS = 17; - int PATTERN_LIGHT_DOTS = 18; - int PATTERN_HVY_DOTS = 19; - int PATTERN_DIAG_STRIPES5 = 20; - int PATTERN_DIAG_STRIPES6 = 21; - int PATTERN_DIAG_STRIPES7 = 22; - int PATTERN_DIAG_STRIPES8 = 23; - int PATTERN_VERT_STRIPES3 = 24; - int PATTERN_HOR_STRIPES3 = 25; - int PATTERN_VERT_STRIPES4 = 26; - int PATTERN_HOR_STRIPES4 = 27; - int PATTERN_PATCHY1 = 28; - int PATTERN_PATCHY2 = 29; - int PATTERN_PATCHY3 = 30; - int PATTERN_PATCHY4 = 31; - - // Border line styles - - short BORDER_NONE = 0x0; - short BORDER_THIN = 0x1; - short BORDER_MEDIUM = 0x2; - short BORDER_DASHED = 0x3; - short BORDER_DOTTED = 0x4; - short BORDER_THICK = 0x5; - short BORDER_DOUBLE = 0x6; - short BORDER_HAIR = 0x7; - short BORDER_MEDIUM_DASHED = 0x8; - short BORDER_DASH_DOT = 0x9; - short BORDER_MEDIUM_DASH_DOT = 0xA; - short BORDER_DASH_DOT_DOT = 0xB; - short BORDER_MEDIUM_DASH_DOT_DOT = 0xC; - short BORDER_SLANTED_DASH_DOT = 0xD; - - String[] BORDER_NAMES = { - "None", - "Thin", - "Medium", - "Dashed", - "Dotted", - "Thick", - "Double", - "Hair", - "Medium dashed", - "Dash-dot", - "Medium dash-dot", - "Dash-dot-dot", - "Medium dash-dot-dot", - "Slanted dash-dot" - }; - - // interpret border sizes from specifications above - String[] BORDER_SIZES_HTML = { - "", - "2px", // thin - "3px", // medium - "2px", - "2px", - "4px", // thick - "2ox", - "1px", // hair - "3px", // medium dashed - "2px", - "2px", - "2px", - "2px", - "2px" - }; - - String[] BORDER_NAMES_HTML = { - "", - "solid", // thin - "solid", // medium - "dashed", - "dotted", - "solid", // thick - "double", - "solid", // hair - "dashed", - "dashed", - "dashed", - "dotted", - "dotted", - "dashed" - }; - - String[] BORDER_STYLES_JSON = { - "none", // 0x0 No border - "thin", // 0x1 Thin line - "medium", // 0x2 Medium line - "dashed", // 0x3 Dashed line - "dotted", // 0x4 Dotted line - "thick", // 0x5 Thick line - "double", // 0x6 Double line - "hairline", // 0x7 Hairline - "medium_dashed", // 0x8 Medium dashed line - "dash-dot", // 0x9 Dash-dot line - "medium_dash-dot", // 0xA Medium dash-dot line - "dash-dot-dot", // 0xB Dash-dot-dot line - "medium_dash-dot-dot", // 0xC Medium dash-dot-dot line - "slant_dash-dot-dot" // 0xD Slanted dash-dot-dot line - }; - - int FORMAT_SUBSCRIPT = 2, FORMAT_SUPERSCRIPT = 1, FORMAT_NOSCRIPT = 0; - - // Decoded Built-in Format Patterns + HEX Format IDs - // includes number, currency and date formats - /** - * Please use the getBuiltinFormats in FormatConstantsImpl for locale specific formatting! - */ - String[][] BUILTIN_FORMATS = { - {"General", "0"}, - {"0", "1"}, - {"0.00", "2"}, - {"#,##0", "3"}, - {"#,##0.00", "4"}, - {"$#,##0;($#,##0)", "5"}, - {"$#,##0;[Red]($#,##0)", "6"}, - {"$#,##0.00;($#,##0.00)", "7"}, - {"$#,##0.00;[Red]($#,##0.00)", "8"}, - {"0%", "9"}, - {"0.00%", "a"}, - {"0.00E+00", "b"}, - {"# ?/?", "c"}, - {"# ??/??", "d"}, - - // dates - {"mm-dd-yy", "E"}, // REGIONAL FORMAT! m/d/yyyy or m/d/yy - {"d-mmm-yy", "F"}, - {"d-mmm", "10"}, - {"mmm-yy", "11"}, - {"h:mm AM/PM", "12"}, - {"h:mm:ss AM/PM", "13"}, - {"h:mm", "14"}, - {"h:mm:ss", "15"}, - {"m/d/yy h:mm", "16"}, - - // 17h through 24h are undocumented international formats!!! - {"reserved", "17"}, - {"reserved", "18"}, - {"reserved", "19"}, - {"reserved", "1a"}, - {"reserved", "1b"}, - {"reserved", "1c"}, - {"reserved", "1d"}, - {"reserved", "1e"}, - {"reserved", "1f"}, - {"reserved", "20"}, - {"reserved", "21"}, - {"reserved", "22"}, - {"reserved", "23"}, - {"reserved", "24"}, - - // more currency - {"(#,##0_);(#,##0)", "25"}, - {"(#,##0_);[Red](#,##0)", "26"}, - {"(#,##0.00_);(#,##0.00)", "27"}, - {"(#,##0.00_);[Red](#,##0.00)", "28"}, - {"_(*#,##0_);_(*(#,##0);_(*\"-\"_);_(@_)", "29"}, - {"_($* #,##0_);_($* (#,##0);_($*\"-\"_);_(@_)", "2a"}, - {"_(* #,##0.00_);_(* (#,##0.00);_(*\"-\"??_);_(@_)", "2b"}, - {"_($* #,##0.00;_($* (#,##0.00);_($* \"-\"??;_(@_)", "2c"}, - - // more dates - {"mm:ss", "2D"}, - {"[h]:mm:ss", "2E"}, - {"mm:ss.0", "2F"}, - - // misc - {"##0.0E+0", "30"}, - {"@", "31"}, - }; - - - String[][] BUILTIN_FORMATS_JP = { - {"General", "0"}, - {"0", "1"}, - {"0.00", "2"}, - {"#,##0", "3"}, - {"#,##0.00", "4"}, - {"\u00a5#,##0;\u00a5-#,##0", "5"}, - {"\u00a5#,##0;[Red]\u00a5-#,##0", "6"}, - {"\u00a5#,##0.00;\u00a5-#,##0.00", "7"}, - {"\u00a5#,##0.00;[Red]\u00a5-#,##0.00", "8"}, - {"0%", "9"}, - {"0.00%", "a"}, - {"0.00E+00", "b"}, - {"# ?/?", "c"}, - {"# ??/??", "d"}, - - // dates - {"yyyy/m/d", "e"}, - {"d-mmm-yy", "f"}, - {"d-mmm", "10"}, - {"mmm-yy", "11"}, - {"h:mm AM/PM", "12"}, - {"h:mm:ss AM/PM", "13"}, - {"h:mm", "14"}, - {"h:mm:ss", "15"}, - {"yyyy/m/d h:mm", "16"}, - - // 17h through 24h are undocumented international formats!!! - {"($#,##0_);($#,##0)", "17"}, - {"($#,##0_);[Red]($#,##0)", "18"}, - {"($#,##0_);[Red]($#,##0)", "19"}, - {"($#,##0.00_);[Red]($#,##0.00)", "1a"}, - {"[$-411]ge.m.d", "1b"}, - {"[$-411]ggge\u5E74m\u6708d\u65E5", "1c"}, - {"reserved", "1d"}, - {"m/d/yy", "1e"}, - {"yyyy\u5E74m\u6708d\u65E5", "1f"}, - {"h\u6642mm\u5206", "20"}, - {"h\u6642mm\u5206ss\u79D2", "21"}, - {"yyyy\u5E74m\u6708", "22"}, - {"m\u6708d\u65E5", "23"}, - {"reserved", "24"}, - - // more currency - {"#,##0;-#,##0", "25"}, - {"#,##0;[Red]-#,##0", "26"}, - {"#,##0.00;-#,##0.00", "27"}, - {"#,##0.00;[Red]-#,##0.00", "28"}, - {"_ * #,##0_ ;_ * -#,##0_ ;_ * -_ ;_ @_ ", "29"}, - {"_ \u00a5* #,##0_ ;_ \u00a5* -#,##0_ ;_ \u00a5* -_ ;_ @_ ", "2a"}, - {"_ * #,##0.00_ ;_ * -#,##0.00_ ;_ * -_ ;_ @_ ", "2b"}, - {"_ \u00a5* #,##0.00_ ;_ \u00a5* -#,##0.00_ ;_ \u00a5* -_ ;_ @_ ", "2c"}, - - // misc - {"mm:ss", "2d"}, - {"[h]:mm:ss", "2e"}, - {"mm:ss.0", "2f"}, - {"##0.0E+0", "30"}, - {"@", "31"}, - {"yyyy\u5E74m\u6708", "37"}, - {"m\u6708d\u65E5", "38"}, - {"[$-411]ge.m.d", "39"}, - {"[$-411]ggge\u5E74m\u6708d\u65E5", "3a"}, - }; - - /* The NumberFormat element contains one of the following string constants - * specifying the date or time format: - * General Date, Short Date, Medium Date, Long Date, Short Time, Medium Time, or Long Time; - * one of the following string constants specifying the numeric format: - * Currency, Fixed, General, General Number, Percent, Scientific, or Standard (#,##0.00); - */ - // TODO: Add - // TODO: These may not be correct mappings - /* one of the following string constants specifying the Boolean values displayed: On/Off, True/False, or Yes/No;*/ - String[][] BUILTIN_FORMATS_MHTML = { - {"General Date", "M/D/YY"}, - {"Short Date", "D-MMM"}, - {"Medium Date", "D-MMM-YY"}, - {"Long Date", "MMM-YY"}, - {"Short Time", "mm:ss"}, - {"Medium Time", "h:mm AM/PM"}, - {"Long Time", "h:mm:ss AM/PM"}, - {"Currency", "\"$\"#,##0_);(\"$\"#,##0)"}, - {"Fixed", "0.00"}, - {"General Number", "0"}, - {"Percent", "0%"}, - {"Scientific", "0.00E+00"}, - {"Standard", "(#,##0.00);"}, - - }; - - /** - * - Now this array includes the java format pattern. If multiple patterns exist for negative - * numbers, that is after a semicolon. The [Red] needs to be stripped as well and applied outside - * the Java formatting if desired. - */ - String[][] NUMERIC_FORMATS = { - {"General", "0", ""}, - {"0", "1", "0"}, - {"0.00", "2", "0.00"}, - - // add below 2 to match excel - {"#,##0", "3", "#,##0"}, - {"#,##0.00", "4", "#,##0.00"}, - - - {"0%", "9", "0%"}, // percent - {"0.00%", "a", "#.00%"}, // percent - {"0.00E+00", "b", "0.00E00"}, // scientific - {"(#,##0.00_);(#,##0.00)", "27", "#,##0.00;(#,##0.00)"}, //? - {"_(*#,##0_);_(*(#,##0);_(*\"-\"_);_(@_)", "29", "#,##0;(#,##0)"}, //? - {"##00E+0", "30", "#.##E00"}, //TODO: unable to write a good generic conversion string here, returning generic exponent - {"@", "31", "@"}, //? - // User-defined (Format ID > 164, but variable. use Format ID= -1) - {"0.0", "FF", "0.0"}, - {"0.00;[Red]0.00", "FF", "#,##0.00;[Red]#,##0.00"}, - {"0.00_);(0.00)", "FF", "#,##0.00###;(#,##0.00)"}, - {"0.00_);[Red](0.00)", "FF", "#,##0.00###;[Red](#,##0.00)"}, - }; - -// Decoded format Patterns, HEX ids, and java format strings for all date formats - /** - * These formats are used in isDate handling and translating those into java format strings when possible - * for toFormattedString behavior - */ - String[][] DATE_FORMATS = { - {"mm-dd-yy", "E", "MM-dd-yy"}, - {"d-mmm-yy", "F", "d-MMM-yy"}, - {"d-mmm", "10", "d-MMM"}, - {"mmm-yy", "11", "MMM-yy"}, - {"h:mm AM/PM", "12", "h:mm aa"}, - {"h:mm:ss AM/PM", "13", "h:mm:ss aa"}, - {"h:mm", "14", "H:mm"}, - {"h:mm:ss", "15", "H:mm:ss"}, - {"m/d/yy h:mm", "16", "M/d/yy H:mm"}, - {"mm:ss", "2D", "mm:ss"}, - {"[h]:mm:ss", "2E"}, - {"mm:ss.0", "2F", "mm:ss.S"}, - // user-defined (Format ID > 164, but variable) - use Format ID of -1 - {"m/d/yyyy h:mm", "FF", "M/d/yyyy H:mm"}, - {"m/d/yyyy;@", "FF", "M/d/yyyy"}, - {"m/d/yy h:mm", "FF", "M/d/yy H:mm"}, - {"hh:mm AM/PM", "FF", "hh:mm aa"}, - {"m/d/yyyy", "FF", "M/d/yyyy"}, - {"yyyy-mm-dd", "FF", "yyyy-MM-dd"}, - // i know, weird formatting offset, but the xf says m/d/y and excel shows m/d/yyyy - {"m/d/y", "FF", "M/d/yyyy"}, - {"m/d/yy", "FF", "M/d/yy"}, - {"m/d;@", "FF", "M/d"}, - {"m/d/yy;@", "FF", "M/d/yy"}, - {"mm/dd/yy;@", "FF", "MM/dd/yy"}, - {"mm/dd/yy", "FF", "MM/dd/yy"}, - {"mm/dd/yyyy", "FF", "MM/dd/yyyy"}, - {"dd/mm/yy", "FF", "dd/MM/yy"}, - {"yy/mm/dd;@", "FF", "yy/MM/dd"}, - {"m/d/yy h:mm;@", "FF", "M/d/yy H:mm"}, - {"hh:mm:ss.000", "FF", "HH:mm:ss.SSS"}, - {"mmmm dd, yyyy", "FF", "MMMMM dd, yyyy"}, - {"mmm dd, yyyy", "FF", "MMM dd, yyyy"}, - {"m/d;@", "FF", "M/d"}, - {"[$-409]d-mmm;@", "FF", "d-MMM"}, - {"[$-409]d-mmm-yy;@", "FF", "d-MMM-yy"}, - {"[$-409]d-mmm-yyyy;@", "FF", "d-MMM-yyyy"}, - {"[$-409]dd-mmm-yy;@", "FF", "dd-Myyyy-mm-ddMM-yy"}, - {"[$-409]dddd, mmmm dd, yyyy", "FF", "EEEE, MMMM dd, yyyy"}, - {"[$-409]mmm-yy;@", "FF", "MMM-ss"}, - {"[$-409]mmmm-yy;@", "FF", "MMMM-yy"}, - {"[$-409]mmmm d, yyyy;@", "FF", "MMMM d, yyyy"}, - {"[$-409]mmmmm;@", "FF", "MMMMM"}, - {"[$-409]mmmmm-yy;@", "FF", "MMMMM-yy"}, - {"[$-409]m/d/yy h:mm AM/PM;@", "FF", "M/d/yy H:mm aa"}, - {"[$-409]mmmm d, yyyy;@", "FF", "MMMM d, yyyy"}, - {"[$-409]h:mm:ss AM/PM", "FF", "H:mm:ss aa"}, - {"[$-409]d-mmm;@", "FF", "d-MMM"}, - {"[$-F800]dddd,mmmm dd,yyyy", "FF", "EEEE,MMMM dd, yyyy"}, - {"[$-F800]dddd, mmmm dd, yyyy", "FF", "EEEE, MMMM dd, yyyy"}, - {"dd/mm/yy hh:mm", "FF", "dd/MM/yy HH:mm"}, - {"d-mmm-yyyy hh:mm", "FF", "d-MMM-yyyy HH:mm"}, - {"yyyy-mm-dd hh:mm", "FF", "yyyy-MM-dd HH:mm"}, - {"yyyy-mm-ss hh:mm:ss.000", "FF", "yyyy-MM-dd HH:mm:ss.SSS"}, - {"yyyy-mm-dd", "FF", "yyyy-MM-dd"}, - {"d/mm/yy", "FF", "d/MM/yy"}, - {"d/mm/yy;@", "FF", "d/MM/yy"}, - {"dddd, mmmm dd, yyyy", "FF", "EEEEE, MMMMM d, yyyy"}, - {"[hhh]:mm", "FF", "h:mm"}, /* ?? */ - - }; - // Decoded Format Patterns and hex Format Ids - String[][] CURRENCY_FORMATS = { -// below 4 do not match excel! -// {"$#,##0;($#,##0)", "1", "$#,##0;($#,##0)"}, -// {"$#,##0;[Red]($#,##0)", "2", "$#,##0;[Red]($#,##0)"}, -// {"$#,##0.00;($#,##0.00)", "3", "$#,##0.00;($#,##0.00)"}, -// {"$#,##0.00;[Red]($#,##0.00)", "4", "$#,##0.00;[Red]($#,##0.00)"}, - {"#,##0_;($#,##0)", "5", "#,##0;($#,##0)"}, - {"#,##0_;[Red]($#,##0)", "6", "#,##0;[Red]($#,##0)"}, - {"#,##0.00_;($#,##0.00)", "7", "#,##0.00###;($#,##0.00)"}, - {"#,##0.00_;[Red]($#,##0.00)", "8", "#,##0.00;[Red]($#,##0.00)"}, - {"_$*#,##0_;_($*($#,##0);_($*\"-\"_);_(@_)", "2a", "$#,##0;($#,##0)"}, - {"_*#,##0_;_(*($#,##0);_(*\"-\"??_);_(@_)", "2b", "#,##0;(#,##0)"}, - {"_$*#,##0_;_($*($#,##0);_($*\"-\"??_);_(@_)", "2c", "$#,##0;($#,##0)"}, - // User-defined (Format ID > 164, but variable. use Format ID= -1) - {"_$*#,##0_;_($*($#,##0);_($*\"-\"??_);_(@_)", "FF", "$#,##0;($#,##0)"}, - {"#,##0.00 [$-1]_);[Red](#,##0.00 [$-1])", "FF", "#,##0.00 ;[Red](#,##0.00 )"}, - {"_ * #,##0.00_) [$-1]_ ;_ * (#,##0.00) [$-1]_ ;_ * -??_) [$-1]_ ;_ @_ ", "FF", "#,##0.00 ;(#,##0.00) "}, - {"_ * #,##0.00_) [$€-1]_ ;_ * (#,##0.00) [$€-1]_ ;_ * -??_) [$€-1]_ ;_ @_ ", "FF", "#,##0.00 €;(#,##0.00) €"}, - {"#,##0.00 [$€-1]", "FF", "#,##0.00 €"}, - {"#,##0.00 [$-1]", "FF", "#,##0.00 "}, - {"$#,##0.00", "FF", "$#,##0.00"}, - {"_($* #,##0_);_($* (#,##0);_($* -??_);_(@_)", "FF", "$ #,##0;($* (#,##0)"}, - {"#,##0.00 [$-1];[Red]#,##0.00 [$-1]", "FF", "#,##0.00 ;[Red]#,##0.00 "}, - {"[$-411]#,##0.00", "FF", "#,##0.00"}, - {"[$-411]#,##0.00;[Red][$-411]#,##0.00", "FF", "#,##0.00;[Red]#,##0.00"}, - {"[$-411]#,##0.00;-[$-411]#,##0.00", "FF", "#,##0.00"}, - {"[$-411]#,##0.00;[Red]-[$-411]#,##0.00", "FF", "#,##0.00;[Red]-#,##0.00"}, - {"[$-809]#,##0.00", "FF", "#,##0.00"}, - {"[$-809]#,##0.00;[Red][$-809]#,##0.00", "FF", "#,##0.00;[Red]#,##0.00"}, - {"[$-809]#,##0.00;-[$-809]#,##0.00", "FF", "#,##0.00;-#,##0.00"}, - {"[$-809]#,##0.00;[Red]-[$-809]#,##0.00", "FF", "#,##0.00;[Red]-#,##0.00"}, - {"#,##0.0 [$$-C0C];[Red]#,##0.0 [$$-C0C]", "FF"}, - // user-defined - {"$#,##0;($#,##0)", "FF", "$#,##0;($#,##0)"}, - {"$#,##0.00;($#,##0.00)", "FF", "$#,##0.00;($#,##0.00)"}, - {"$#,##0;[Red]($#,##0)", "FF", "$#,##0;[Red]($#,##0)"}, - {"$#,##0.00;[Red]($#,##0.00)", "FF", "$#,##0.00;[Red]($#,##0.00)"}, - {"$#,##0.00;[Red]$#,##0.00", "FF", "$#,##0.00;[Red]$#,##0.00"}, - {"$#,##0", "FF", "$#,##0"} - }; - - // this maps Java formats to Excel formats - String[][] patternmap = { - // currency - {"#,##0.00", "#,##0.00"}, - {"$#,##0;($#,##0)", "(#,##0_);($#,##0)"}, - {"$#,##0;[Red]($#,##0)", "$###,###;($###,###)"}, - {"$#,##0.00;($#,##0.00)", "$###,###.##;($###,###.##)"}, - {"$#,##0.00;[Red]($#,##0.00)", "$###,###.##;($###,###.##)"}, - {"0%", "0%"}, - {"0.00%", "0.00%"}, - {"0.00E+00", "0.00E+00"}, - {"# ?/?", "# ?/?"}, - {"# ??/??", ""}, - {"#,##0_;($#,##0)", ""}, - {"#,##0_;[Red]($#,##0)", ""}, - {"#,##0.00_;($#,##0.00)", ""}, - {"#,##0.00_;[Red]($#,##0.00)", ""}, - {"_*#,##0_;_(*($#,##0);_(*-_);_(@_)", ""}, - {"_$*#,##0_;_($*($#,##0);_($*-_);_(@_)", ""}, - {"_*#,##0_;_(*($#,##0);_(*-??_);_(@_)", ""}, - {"_$*#,##0_;_($*($#,##0);_($*-??_);_(@_)", ""}, - - // date time - {"mm-mmm-yy", "MM-dd-YY"}, - {"m/d/y", "M/d/y"}, - {"d-mmm-yy", "d-MMM-yy"}, - {"d-mmm", "d-MMM"}, - {"mmm-yy", "MMM-yy"}, - {"h:mm AM/PM", "hh:mm a"}, - {"h:mm:ss AM/PM", "hh:mm:ss a"}, - {"h:mm", "hh:mm"}, - {"h:mm:ss", "hh:mm:ss"}, - {"m/d/yy h:mm", "M/d/yy hh:mm"}, - }; - - - // look up user-friendly format string and get Excel-qualified or encoded format string - // USED??? - String[][] EXCEL_FORMAT_LOOKUP = { - // currency - {"0", ""}, - {"0.00", ""}, - {"#,##0", ""}, - {"#,##0.00", ""}, - {"($#,##0);($#,##0)", "\"$\"#,##0);(\"$\"#,##0)"}, - {"($#,##0);[Red]($#,##0)", "\"$\"#,##0);[Red](\"$\"#,##0)"}, - {"($#,##0.00);($#,##0.00)", "\"$\"#,##0.00);(\"$\"#,##0.00)"}, - {"($#,##0.00);[Red]($#,##0.00)", "\"$\"#,##0.00);[Red](\"$\"#,##0.00)"}, - {"0%", ""}, - {"0.00%", ""}, - {"0.00E+00", ""}, - {"# ?/?", ""}, - {"# ??/??", ""}, - // Are these correct? Should they be qualified??? - {"(#,##0_);($#,##0)", ""}, - {"(#,##0_);[Red]($#,##0)", ""}, - {"(#,##0.00_);($#,##0.00)", ""}, - {"(#,##0.00_);[Red]($#,##0.00)", ""}, - - - {"(#,##0_);(#,##0)", ""}, - {"(#,##0_);[Red](#,##0)", ""}, - {"(#,##0.00_);(#,##0.00)", ""}, - {"(#,##0.00_);[Red](#,##0.00)", ""}, - {"_(*#,##0_);_(*($#,##0);_(*-_);_(@_)", "_(*#,##0_);_(*(\"$\"#,##0);_(*\"-\"_);_(@_)"}, - {"_($*#,##0_);_($*($#,##0);_($*-_);_(@_)", "_(\"$\"*#,##0_);_(\"$\"*($#,##0);_(\"$\"*\"-\"_);_(@_)"}, - {"_(*#,##0_);_(*($#,##0);_(*-??_);_(@_)", "_(*#,##0_);_(*(\"$\"#,##0);_(*\"-\"??_);_(@_)"}, - {"_($*#,##0_);_($*($#,##0);_($*-??_);_(@_)", "_(\"$\"*#,##0_);_(\"$\"*(\"$\"#,##0);_(\"$\"*\"-\"??_);_(@_)"}, - - // dates - {"mm-mmm-yy", ""}, - {"m/d/y", ""}, - {"d-mmm-yy", "d\\-mmm\\-yy"}, - {"[$-409]mmmm d, yyyy;@", "mmmm\\d\\,\\-yyyy"}, - {"d-mmm", "d\\-mmm"}, - {"mmm-yy", "mmm\\-yy"}, - {"h:mm AM/PM", "h:mm\\ AM/PM"}, - {"h:mm:ss AM/PM", ""}, - {"h:mm", ""}, - {"h:mm:ss", ""}, - {"m/d/yy h:mm", ""}, - }; - // Alignment's - int ALIGN_DEFAULT = 0; - int ALIGN_LEFT = 1; - int ALIGN_CENTER = 2; - int ALIGN_RIGHT = 3; - int ALIGN_FILL = 4; - int ALIGN_JUSTIFY = 5; - int ALIGN_CENTER_ACROSS_SELECTION = 6; - int ALIGN_VERTICAL_TOP = 0; - int ALIGN_VERTICAL_CENTER = 1; - int ALIGN_VERTICAL_BOTTOM = 2; - int ALIGN_VERTICAL_JUSTIFY = 3; - - String[] VERTICAL_ALIGNMENTS = { - "top", - "middle", - "bottom", - "text-bottom", // justified - "text-bottom", // distributed" - }; - - /** - * These Horizontal alignments are used to map to HTML output, so some - * values may not be as expected. For instance, "Center across selection" is - * not something you can do in HTML. We will use "Center" for this. - *

                  - * Not entirely True: HTML can center across merged table cells -- this is basically - * the purpse AFAIK. -jm - */ - String[] HORIZONTAL_ALIGNMENTS = { - "Default", - "Left", - "Center", - "Right", - "Center", - "Center", - "Center" - }; -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/FormatConstants.kt b/src/main/java/io/starter/formats/XLS/FormatConstants.kt new file mode 100644 index 0000000..41a7394 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/FormatConstants.kt @@ -0,0 +1,542 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import java.awt.Color + +/** + * /** Constants relevant to XLS Formatting + * + * + * NOTE: + * Excel automatically assigns + * If you type this number format + * ------------------------------------------- + * + * + * 1.0 General + * 1.123 General + * 1.1% 0.00% + * 1.1E+2 0.00E+00 + * 1 1/2 # ?/? + * $1.11 Currency, 2 decimal places + * 1/1/01 Date + * 1:10 Time + * + * + * CUSTOM FORMATS: + * + * + * Each format that you create can have up to three sections for numbers and a fourth section for text. + * ;;; + * + * @see CustomFormatHelper + */ +interface FormatConstants { +companion object { + + +val DEFAULT_FONT_WEIGHT = 200 +val DEFAULT_FONT_SIZE = 20 +val DEFAULT_FONT_FACE = "Arial" + + +// Font Weights +val PLAIN = 400 +val BOLD = 700 + +// Font UnderLine Style constants +val STYLE_UNDERLINE_NONE:Byte = 0x0 +val STYLE_UNDERLINE_SINGLE:Byte = 0x1 +val STYLE_UNDERLINE_DOUBLE:Byte = 0x2 +val STYLE_UNDERLINE_SINGLE_ACCTG:Byte = 0x21 +val STYLE_UNDERLINE_DOUBLE_ACCTG:Byte = 0x22 + + +// Color Constants +val Black = Color(0, 0, 0) +val Brown = Color(153, 51, 0) +val OliveGreen = Color(51, 51, 0) +val Dark_Green = Color(0, 51, 0) +val Dark_Teal = Color(0, 51, 102) +val Dark_Blue = Color(0, 0, 128) +val Indigo = Color(51, 51, 153) +val Gray80 = Color(51, 51, 51) +// could we be wrong?? test it per claritas +// public static Color Gray50 = new Color(110,110,110); +// Excel 07 likes real 50 percent = new Color(127,127,127); +val Gray50 = Color(136, 136, 136) + +val Dark_Red = Color(128, 0, 0) +val Orange = Color(255, 102, 0) +val Dark_Yellow = Color(128, 128, 0) +val Green = Color(0, 128, 0) +val Teal = Color(0, 128, 128) +val Blue = Color(0, 0, 255) +val BlueGray = Color(102, 102, 153) + +val Red = Color(255, 0, 0) +val Light_Orange = Color(255, 153, 0) +val Lime = Color(153, 204, 0) +val SeaGreen = Color(51, 153, 102) +val Aqua = Color(51, 204, 204) +val Light_Blue = Color(0, 128, 255) +val Violet = Color(128, 0, 128) +val Gray40 = Color(150, 150, 150) + +val Pink = Color(255, 0, 255) +val Gold = Color(255, 204, 0) +val Yellow = Color(255, 255, 0) +val BrightGreen = Color(0, 255, 0) +val Turquoise = Color(0, 255, 255) +val SkyBlue = Color(0, 204, 255) +val Plum = Color(153, 51, 102) +val Gray25 = Color(192, 192, 192) + +val Rose = Color(255, 153, 204) +val Tan = Color(255, 204, 153) +val Light_Yellow = Color(255, 255, 153) +val Light_Green = Color(204, 255, 204) +val Light_Turquoise = Color(204, 255, 255) +val PaleBlue = Color(153, 204, 255) +val Lavender = Color(204, 153, 255) +val White = Color(255, 255, 255) +val Gray15 = Color(216, 216, 216) +val Dark_Purple = Color(102, 0, 102) +val Salmon = Color(255, 128, 128) +val Light_Purple = Color(204, 204, 255) +val Medium_Purple = Color(153, 153, 255) + +/** + * Excel's basic icv color table. This may be altered by the Palette record. + * See WorkBook.CCLORTABLE +*/ +val COLORTABLE = arrayOf(FormatConstants.Black, FormatConstants.White, FormatConstants.Red, FormatConstants.BrightGreen, /// WRONG, was duplicate, incorrect GREEN +FormatConstants.Blue, FormatConstants.Yellow, FormatConstants.Pink, FormatConstants.Turquoise, FormatConstants.Black, /** + * NOTE: from documentation: + * if palette record exists 8-63 (0x3f) gets read from it, + * otherwise If a Palette record exists in this file, these icv values + * specify colors from the rgColor array in the Palette record. +*/ +/* + * Color 9 === AUTOMATIC color + * the opposite of what is normal; for instance, background color + * index 9= black, font color index 9= white. It is set here to + * black, and in Fonts.getColor, a special case is put for icv= 9 +*/ +FormatConstants.White, // 9 +FormatConstants.Red, // 10 +FormatConstants.BrightGreen, FormatConstants.Blue, FormatConstants.Yellow, FormatConstants.Pink, FormatConstants.Turquoise, // 15 +FormatConstants.Dark_Red, // Burgundy //16 +FormatConstants.Green, FormatConstants.Dark_Blue, // purple +FormatConstants.Dark_Yellow, FormatConstants.Violet, // 20 dark pink +FormatConstants.Teal, FormatConstants.Gray25, FormatConstants.Gray50, FormatConstants.Medium_Purple, FormatConstants.Plum, // 25 +FormatConstants.Light_Yellow, FormatConstants.Light_Turquoise, FormatConstants.Dark_Purple, FormatConstants.Salmon, FormatConstants.BlueGray, // 30 +FormatConstants.Light_Purple, FormatConstants.Dark_Blue, FormatConstants.Pink, FormatConstants.Yellow, FormatConstants.Turquoise, // 35 +FormatConstants.Violet, FormatConstants.Dark_Red, FormatConstants.Teal, // ////////////WRONG - WAS DUPLICATE +FormatConstants.Blue, FormatConstants.SkyBlue, // 40 +FormatConstants.Light_Turquoise, FormatConstants.Light_Green, FormatConstants.Light_Yellow, FormatConstants.PaleBlue, FormatConstants.Rose, // 45 +FormatConstants.Lavender, FormatConstants.Tan, FormatConstants.Dark_Yellow, FormatConstants.Aqua, FormatConstants.Lime, // 50 +FormatConstants.Gold, FormatConstants.Light_Orange, FormatConstants.Orange, FormatConstants.BlueGray, FormatConstants.Gray40, // 55 +FormatConstants.Dark_Teal, FormatConstants.SeaGreen, FormatConstants.Dark_Green, FormatConstants.OliveGreen, FormatConstants.Brown, // 60 +FormatConstants.Plum, FormatConstants.Indigo, FormatConstants.Gray80, FormatConstants.White, // 64 +FormatConstants.Black)// 65 WHY DO THESE CHANGE??? + +// Formatting Color Constants +val COLOR_BLACK = 0 +val COLOR_WHITE = 1 +val COLOR_RED = 2 +val COLOR_BLUE = 4 +val COLOR_YELLOW = 5 +val COLOR_BLACK3 = 8 +val COLOR_WHITE3 = 9 +val COLOR_RED_CHART = 10 // chart fills use SPECIFIC icv numbers -- incorrect # results in black fill +val COLOR_BRIGHT_GREEN = 11 +val COLOR_YELLOW_CHART = 13 +val COLOR_DARK_RED = 16 +val COLOR_GREEN = 17 +val COLOR_DARK_BLUE = 18 +val COLOR_OLIVE_GREEN_CHART = 19 +val COLOR_DARK_YELLOW = 19 +val COLOR_VIOLET = 20 +val COLOR_TEAL = 21 +val COLOR_GRAY25 = 22 +val COLOR_GRAY50 = 23 +val COLOR_MEDIUM_PURPLE = 24 +val COLOR_PLUM = 25 +val COLOR_LIGHT_YELLOW = 26 +val COLOR_LIGHT_TURQUOISE = 27 +val COLOR_DARK_PURPLE = 28 +val COLOR_SALMON = 29 +val COLOR_LIGHT_BLUE = 30 +val COLOR_LIGHT_PURPLE = 31 +val COLOR_PINK = 33 +val COLOR_CYAN = 35 +val COLOR_BLUE_CHART = 39 +val COLOR_SKY_BLUE = 40 + +val COLOR_LIGHT_GREEN = 42 +val COLOR_PALE_BLUE = 44 +val COLOR_ROSE = 45 +val COLOR_LAVENDER = 46 +val COLOR_TAN = 47 +val COLOR_TURQUOISE = 48 +val COLOR_AQUA = 49 +val COLOR_LIME = 50 +val COLOR_GOLD = 51 +val COLOR_LIGHT_ORANGE = 52 +val COLOR_ORANGE = 53 +val COLOR_BLUE_GRAY = 54 +val COLOR_GRAY40 = 55 +val COLOR_DARK_TEAL = 56 +val COLOR_SEA_GREEN = 57 +val COLOR_DARK_GREEN = 58 +val COLOR_OLIVE_GREEN = 59 +val COLOR_BROWN = 60 +val COLOR_INDIGO = 62 +val COLOR_GRAY80 = 63 +val COLOR_WHITE2 = 64 +val COLOR_BLACK2 = 65 + +/** + * given color int ala COLORTABLE, interpret Excel Color name +*/ +val COLORNAMES = arrayOf("Black", "White", "Red", "BrightGreen", "Blue", "Yellow", "Pink", "Turquoise", "Black", "White", "Red", //10 +"BrightGreen", "Blue", "Yellow", "Pink", "Turquoise", // 15 +"Dark_Red", //Burgundy //16 +"Green", "Dark_Blue", //purple +"Dark_Yellow", "Violet", //20 dark pink +"Teal", "Gray25", "Gray50", "MediumPurple", "Plum", //25 +"Light_Yellow", "Light_Turquoise", "Dark_Purple", "Salmon", "BlueGray", //30 +"Light_Purple", "Dark_Blue", "Pink", "Yellow", "Turquoise", // 35 +"Violet", "Dark_Red", "Teal", "Blue", "SkyBlue", //40 +"Light_Turquoise", "Light_Green", "Light_Yellow", "PaleBlue", "Rose", //45 +"Lavender", "Tan", "Blue", "Aqua", "Lime", // 50 +"Gold", "Light_Orange", "Orange", "BlueGray", "Gray40", //55 +"Dark_Teal", "SeaGreen", "Dark_Green", "OliveGreen", "Brown", //60 +"Plum", "Indigo", // WRONG +"Gray80", "White", // 64 +"Black")// 65 + +/** + * given color int ala COLORTABLE, interpret HTML Color name +*/ +val HTMLCOLORNAMES = arrayOf("Black", "White", "Red", "Green", "Blue", "Yellow", "Pink", "Turquoise", "Black", "White", "Red", //10 +"Lime", // BrightGreen", +"Blue", "Yellow", "Pink", "Turquoise", // 15 +"Dark_Red", "Green", "DarkBlue", "DarkKhaki", //Dark_Yellow", +"Violet", //20 +"Teal", "LightGray", // Gray25 +"DarkGray", // Gray50" +"DodgerBlue", "DarkRed", //25 +"LightYellow", "PaleTurquoise", // Light_Turquoise", +"DarkMagenta", "Red", "CadetBlue", // BlueGray", //30 +"PowderBlue", // PaleBlue", +"DarkBlue", "Pink", "Yellow", "Turquoise", // 35 +"Plum", "Brown", "SeaGreen", "Blue", "SkyBlue", //40 +"PaleTurquoise", //Light_Turquoise", +"LightGreen", "LightYellow", "PowderBlue", // PaleBlue", +"Crimson", // Rose", //45 +"Lavender", "Tan", "Blue", "Aqua", "Lime", // 50 +"Gold", "Orange", //Light_Orange", +"Orange", "CadetBlue", // BlueGray", +"YellowGreen", // Gray40", //55 +"DarkSlateBlue", //Dark_Teal", +"SeaGreen", "DarkGreen", "OliveDrab", //OliveGreen", +"Brown", //60 +"Plum", "Indigo", // WRONG +"DimGray", // Gray80 +"White", // 64 +"Black")// 65 + +val SVGCOLORSTRINGS = arrayOf("black", "white", "red", "lime", // = BrightGreen +"blue", "yellow", // 5 +"fuchsia", // = Pink +"aqua", // = Turquoise +"black", "white", "red", // 10 +"lime", "blue", "yellow", "fuchsia", "aqua", // 15 +"maroon", // = Dark_Red +"green", "navy", // = Dark_Blue +"olive", // = Dark_Yellow +"purple", // = Violet // 20 +"teal", "silver", // = Gray25 +"gray", // = Gray50 +"lightblue", //lightsteelblue", +"palevioletred", //mediumorchid", // =Plum // 25 +"darkolivegreen", "aqua", // = Light_Turqouise +"purple", // = Dark_Purple +"salmon", "slateblue", // = BlueGray // 30 +"aliceblue", // = Light_Purple +"navy", // = Dark_Blue +"fuchsia", // = Pink +"yellow", "aqua", // = Turquoise //35 +"purple", // = Violet +"maroon", // = Dark_Red +"teal", "blue", "deepskyblue", // = SkyBlue // 40 +"azure", // = Light_Turquoise +"honeydew", // = Light_Green +"lightyellow", "powderblue", // = PaleBlue +"lightpink", // = Rose // 45 +"aliceblue", // = Lavendar +"navajowhite", // = Tan +"blue", "mediumturquoise", // = Aqua +"lawngreen", // = Lime // 50 +"gold", "orange", "darkorange", "slateblue", // = BlueGray +"darkgray", // = Gray40 //55 +"midnightblue", // = Dark_Teal +"seagreen", "darkgreen", "darkolivegreen", "saddlebrown", // 60 +"mediumvioletred", "darkslateblue", //= Indigo +"darkslategray", // = Gray80 +"white", // 64 +"black")// 65 + +// Formatting Pattern Constants +val PATTERN_NONE = 0 +val PATTERN_FILLED = 1 +val PATTERN_LIGHT_FILL = 2 +val PATTERN_MED_FILL = 3 +val PATTERN_HVY_FILL = 4 +val PATTERN_HOR_STRIPES1 = 5 +val PATTERN_VERT_STRIPES1 = 6 +val PATTERN_DIAG_STRIPES1 = 7 +val PATTERN_DIAG_STRIPES2 = 8 +val PATTERN_CHECKERBOARD1 = 9 +val PATTERN_CHECKERBOARD2 = 10 +val PATTERN_HOR_STRIPES2 = 11 +val PATTERN_VERT_STRIPES2 = 12 +val PATTERN_DIAG_STRIPES3 = 13 +val PATTERN_DIAG_STRIPES4 = 14 +val PATTERN_GRID1 = 15 +val PATTERN_CROSSPATCH1 = 16 +val PATTERN_MED_DOTS = 17 +val PATTERN_LIGHT_DOTS = 18 +val PATTERN_HVY_DOTS = 19 +val PATTERN_DIAG_STRIPES5 = 20 +val PATTERN_DIAG_STRIPES6 = 21 +val PATTERN_DIAG_STRIPES7 = 22 +val PATTERN_DIAG_STRIPES8 = 23 +val PATTERN_VERT_STRIPES3 = 24 +val PATTERN_HOR_STRIPES3 = 25 +val PATTERN_VERT_STRIPES4 = 26 +val PATTERN_HOR_STRIPES4 = 27 +val PATTERN_PATCHY1 = 28 +val PATTERN_PATCHY2 = 29 +val PATTERN_PATCHY3 = 30 +val PATTERN_PATCHY4 = 31 + +// Border line styles + +val BORDER_NONE:Short = 0x0 +val BORDER_THIN:Short = 0x1 +val BORDER_MEDIUM:Short = 0x2 +val BORDER_DASHED:Short = 0x3 +val BORDER_DOTTED:Short = 0x4 +val BORDER_THICK:Short = 0x5 +val BORDER_DOUBLE:Short = 0x6 +val BORDER_HAIR:Short = 0x7 +val BORDER_MEDIUM_DASHED:Short = 0x8 +val BORDER_DASH_DOT:Short = 0x9 +val BORDER_MEDIUM_DASH_DOT:Short = 0xA +val BORDER_DASH_DOT_DOT:Short = 0xB +val BORDER_MEDIUM_DASH_DOT_DOT:Short = 0xC +val BORDER_SLANTED_DASH_DOT:Short = 0xD + +val BORDER_NAMES = arrayOf("None", "Thin", "Medium", "Dashed", "Dotted", "Thick", "Double", "Hair", "Medium dashed", "Dash-dot", "Medium dash-dot", "Dash-dot-dot", "Medium dash-dot-dot", "Slanted dash-dot") + +// interpret border sizes from specifications above +val BORDER_SIZES_HTML = arrayOf("", "2px", // thin +"3px", // medium +"2px", "2px", "4px", // thick +"2ox", "1px", // hair +"3px", // medium dashed +"2px", "2px", "2px", "2px", "2px") + +val BORDER_NAMES_HTML = arrayOf("", "solid", // thin +"solid", // medium +"dashed", "dotted", "solid", // thick +"double", "solid", // hair +"dashed", "dashed", "dashed", "dotted", "dotted", "dashed") + +val BORDER_STYLES_JSON = arrayOf("none", // 0x0 No border +"thin", // 0x1 Thin line +"medium", // 0x2 Medium line +"dashed", // 0x3 Dashed line +"dotted", // 0x4 Dotted line +"thick", // 0x5 Thick line +"double", // 0x6 Double line +"hairline", // 0x7 Hairline +"medium_dashed", // 0x8 Medium dashed line +"dash-dot", // 0x9 Dash-dot line +"medium_dash-dot", // 0xA Medium dash-dot line +"dash-dot-dot", // 0xB Dash-dot-dot line +"medium_dash-dot-dot", // 0xC Medium dash-dot-dot line +"slant_dash-dot-dot" // 0xD Slanted dash-dot-dot line +) + +val FORMAT_SUBSCRIPT = 2 +val FORMAT_SUPERSCRIPT = 1 +val FORMAT_NOSCRIPT = 0 + +// Decoded Built-in Format Patterns + HEX Format IDs +// includes number, currency and date formats +/** + * Please use the getBuiltinFormats in FormatConstantsImpl for locale specific formatting! +*/ +val BUILTIN_FORMATS = arrayOf>(arrayOf("General", "0"), arrayOf("0", "1"), arrayOf("0.00", "2"), arrayOf("#,##0", "3"), arrayOf("#,##0.00", "4"), arrayOf("$#,##0;($#,##0)", "5"), arrayOf("$#,##0;[Red]($#,##0)", "6"), arrayOf("$#,##0.00;($#,##0.00)", "7"), arrayOf("$#,##0.00;[Red]($#,##0.00)", "8"), arrayOf("0%", "9"), arrayOf("0.00%", "a"), arrayOf("0.00E+00", "b"), arrayOf("# ?/?", "c"), arrayOf("# ??/??", "d"), + +// dates +arrayOf("mm-dd-yy", "E"), // REGIONAL FORMAT! m/d/yyyy or m/d/yy +arrayOf("d-mmm-yy", "F"), arrayOf("d-mmm", "10"), arrayOf("mmm-yy", "11"), arrayOf("h:mm AM/PM", "12"), arrayOf("h:mm:ss AM/PM", "13"), arrayOf("h:mm", "14"), arrayOf("h:mm:ss", "15"), arrayOf("m/d/yy h:mm", "16"), + +// 17h through 24h are undocumented international formats!!! +arrayOf("reserved", "17"), arrayOf("reserved", "18"), arrayOf("reserved", "19"), arrayOf("reserved", "1a"), arrayOf("reserved", "1b"), arrayOf("reserved", "1c"), arrayOf("reserved", "1d"), arrayOf("reserved", "1e"), arrayOf("reserved", "1f"), arrayOf("reserved", "20"), arrayOf("reserved", "21"), arrayOf("reserved", "22"), arrayOf("reserved", "23"), arrayOf("reserved", "24"), + +// more currency +arrayOf("(#,##0_);(#,##0)", "25"), arrayOf("(#,##0_);[Red](#,##0)", "26"), arrayOf("(#,##0.00_);(#,##0.00)", "27"), arrayOf("(#,##0.00_);[Red](#,##0.00)", "28"), arrayOf("_(*#,##0_);_(*(#,##0);_(*\"-\"_);_(@_)", "29"), arrayOf("_($* #,##0_);_($* (#,##0);_($*\"-\"_);_(@_)", "2a"), arrayOf("_(* #,##0.00_);_(* (#,##0.00);_(*\"-\"??_);_(@_)", "2b"), arrayOf("_($* #,##0.00;_($* (#,##0.00);_($* \"-\"??;_(@_)", "2c"), + +// more dates +arrayOf("mm:ss", "2D"), arrayOf("[h]:mm:ss", "2E"), arrayOf("mm:ss.0", "2F"), + +// misc +arrayOf("##0.0E+0", "30"), arrayOf("@", "31")) + + +val BUILTIN_FORMATS_JP = arrayOf>(arrayOf("General", "0"), arrayOf("0", "1"), arrayOf("0.00", "2"), arrayOf("#,##0", "3"), arrayOf("#,##0.00", "4"), arrayOf("\u00a5#,##0;\u00a5-#,##0", "5"), arrayOf("\u00a5#,##0;[Red]\u00a5-#,##0", "6"), arrayOf("\u00a5#,##0.00;\u00a5-#,##0.00", "7"), arrayOf("\u00a5#,##0.00;[Red]\u00a5-#,##0.00", "8"), arrayOf("0%", "9"), arrayOf("0.00%", "a"), arrayOf("0.00E+00", "b"), arrayOf("# ?/?", "c"), arrayOf("# ??/??", "d"), + +// dates +arrayOf("yyyy/m/d", "e"), arrayOf("d-mmm-yy", "f"), arrayOf("d-mmm", "10"), arrayOf("mmm-yy", "11"), arrayOf("h:mm AM/PM", "12"), arrayOf("h:mm:ss AM/PM", "13"), arrayOf("h:mm", "14"), arrayOf("h:mm:ss", "15"), arrayOf("yyyy/m/d h:mm", "16"), + +// 17h through 24h are undocumented international formats!!! +arrayOf("($#,##0_);($#,##0)", "17"), arrayOf("($#,##0_);[Red]($#,##0)", "18"), arrayOf("($#,##0_);[Red]($#,##0)", "19"), arrayOf("($#,##0.00_);[Red]($#,##0.00)", "1a"), arrayOf("[$-411]ge.m.d", "1b"), arrayOf("[$-411]ggge\u5E74m\u6708d\u65E5", "1c"), arrayOf("reserved", "1d"), arrayOf("m/d/yy", "1e"), arrayOf("yyyy\u5E74m\u6708d\u65E5", "1f"), arrayOf("h\u6642mm\u5206", "20"), arrayOf("h\u6642mm\u5206ss\u79D2", "21"), arrayOf("yyyy\u5E74m\u6708", "22"), arrayOf("m\u6708d\u65E5", "23"), arrayOf("reserved", "24"), + +// more currency +arrayOf("#,##0;-#,##0", "25"), arrayOf("#,##0;[Red]-#,##0", "26"), arrayOf("#,##0.00;-#,##0.00", "27"), arrayOf("#,##0.00;[Red]-#,##0.00", "28"), arrayOf("_ * #,##0_ ;_ * -#,##0_ ;_ * -_ ;_ @_ ", "29"), arrayOf("_ \u00a5* #,##0_ ;_ \u00a5* -#,##0_ ;_ \u00a5* -_ ;_ @_ ", "2a"), arrayOf("_ * #,##0.00_ ;_ * -#,##0.00_ ;_ * -_ ;_ @_ ", "2b"), arrayOf("_ \u00a5* #,##0.00_ ;_ \u00a5* -#,##0.00_ ;_ \u00a5* -_ ;_ @_ ", "2c"), + +// misc +arrayOf("mm:ss", "2d"), arrayOf("[h]:mm:ss", "2e"), arrayOf("mm:ss.0", "2f"), arrayOf("##0.0E+0", "30"), arrayOf("@", "31"), arrayOf("yyyy\u5E74m\u6708", "37"), arrayOf("m\u6708d\u65E5", "38"), arrayOf("[$-411]ge.m.d", "39"), arrayOf("[$-411]ggge\u5E74m\u6708d\u65E5", "3a")) + +/* The NumberFormat element contains one of the following string constants + * specifying the date or time format: + * General Date, Short Date, Medium Date, Long Date, Short Time, Medium Time, or Long Time; + * one of the following string constants specifying the numeric format: + * Currency, Fixed, General, General Number, Percent, Scientific, or Standard (#,##0.00); +*/ +// TODO: Add +// TODO: These may not be correct mappings +/* one of the following string constants specifying the Boolean values displayed: On/Off, True/False, or Yes/No;*/ +val BUILTIN_FORMATS_MHTML = arrayOf>(arrayOf("General Date", "M/D/YY"), arrayOf("Short Date", "D-MMM"), arrayOf("Medium Date", "D-MMM-YY"), arrayOf("Long Date", "MMM-YY"), arrayOf("Short Time", "mm:ss"), arrayOf("Medium Time", "h:mm AM/PM"), arrayOf("Long Time", "h:mm:ss AM/PM"), arrayOf("Currency", "\"$\"#,##0_);(\"$\"#,##0)"), arrayOf("Fixed", "0.00"), arrayOf("General Number", "0"), arrayOf("Percent", "0%"), arrayOf("Scientific", "0.00E+00"), arrayOf("Standard", "(#,##0.00);")) + +/** + * - Now this array includes the java format pattern. If multiple patterns exist for negative + * numbers, that is after a semicolon. The [Red] needs to be stripped as well and applied outside + * the Java formatting if desired. +*/ +val NUMERIC_FORMATS = arrayOf>(arrayOf("General", "0", ""), arrayOf("0", "1", "0"), arrayOf("0.00", "2", "0.00"), + +// add below 2 to match excel +arrayOf("#,##0", "3", "#,##0"), arrayOf("#,##0.00", "4", "#,##0.00"), + + +arrayOf("0%", "9", "0%"), // percent +arrayOf("0.00%", "a", "#.00%"), // percent +arrayOf("0.00E+00", "b", "0.00E00"), // scientific +arrayOf("(#,##0.00_);(#,##0.00)", "27", "#,##0.00;(#,##0.00)"), //? +arrayOf("_(*#,##0_);_(*(#,##0);_(*\"-\"_);_(@_)", "29", "#,##0;(#,##0)"), //? +arrayOf("##00E+0", "30", "#.##E00"), //TODO: unable to write a good generic conversion string here, returning generic exponent +arrayOf("@", "31", "@"), //? +// User-defined (Format ID > 164, but variable. use Format ID= -1) +arrayOf("0.0", "FF", "0.0"), arrayOf("0.00;[Red]0.00", "FF", "#,##0.00;[Red]#,##0.00"), arrayOf("0.00_);(0.00)", "FF", "#,##0.00###;(#,##0.00)"), arrayOf("0.00_);[Red](0.00)", "FF", "#,##0.00###;[Red](#,##0.00)")) + +// Decoded format Patterns, HEX ids, and java format strings for all date formats +/** + * These formats are used in isDate handling and translating those into java format strings when possible + * for toFormattedString behavior +*/ +val DATE_FORMATS = arrayOf>(arrayOf("mm-dd-yy", "E", "MM-dd-yy"), arrayOf("d-mmm-yy", "F", "d-MMM-yy"), arrayOf("d-mmm", "10", "d-MMM"), arrayOf("mmm-yy", "11", "MMM-yy"), arrayOf("h:mm AM/PM", "12", "h:mm aa"), arrayOf("h:mm:ss AM/PM", "13", "h:mm:ss aa"), arrayOf("h:mm", "14", "H:mm"), arrayOf("h:mm:ss", "15", "H:mm:ss"), arrayOf("m/d/yy h:mm", "16", "M/d/yy H:mm"), arrayOf("mm:ss", "2D", "mm:ss"), arrayOf("[h]:mm:ss", "2E"), arrayOf("mm:ss.0", "2F", "mm:ss.S"), +// user-defined (Format ID > 164, but variable) - use Format ID of -1 +arrayOf("m/d/yyyy h:mm", "FF", "M/d/yyyy H:mm"), arrayOf("m/d/yyyy;@", "FF", "M/d/yyyy"), arrayOf("m/d/yy h:mm", "FF", "M/d/yy H:mm"), arrayOf("hh:mm AM/PM", "FF", "hh:mm aa"), arrayOf("m/d/yyyy", "FF", "M/d/yyyy"), arrayOf("yyyy-mm-dd", "FF", "yyyy-MM-dd"), +// i know, weird formatting offset, but the xf says m/d/y and excel shows m/d/yyyy +arrayOf("m/d/y", "FF", "M/d/yyyy"), arrayOf("m/d/yy", "FF", "M/d/yy"), arrayOf("m/d;@", "FF", "M/d"), arrayOf("m/d/yy;@", "FF", "M/d/yy"), arrayOf("mm/dd/yy;@", "FF", "MM/dd/yy"), arrayOf("mm/dd/yy", "FF", "MM/dd/yy"), arrayOf("mm/dd/yyyy", "FF", "MM/dd/yyyy"), arrayOf("dd/mm/yy", "FF", "dd/MM/yy"), arrayOf("yy/mm/dd;@", "FF", "yy/MM/dd"), arrayOf("m/d/yy h:mm;@", "FF", "M/d/yy H:mm"), arrayOf("hh:mm:ss.000", "FF", "HH:mm:ss.SSS"), arrayOf("mmmm dd, yyyy", "FF", "MMMMM dd, yyyy"), arrayOf("mmm dd, yyyy", "FF", "MMM dd, yyyy"), arrayOf("m/d;@", "FF", "M/d"), arrayOf("[$-409]d-mmm;@", "FF", "d-MMM"), arrayOf("[$-409]d-mmm-yy;@", "FF", "d-MMM-yy"), arrayOf("[$-409]d-mmm-yyyy;@", "FF", "d-MMM-yyyy"), arrayOf("[$-409]dd-mmm-yy;@", "FF", "dd-Myyyy-mm-ddMM-yy"), arrayOf("[$-409]dddd, mmmm dd, yyyy", "FF", "EEEE, MMMM dd, yyyy"), arrayOf("[$-409]mmm-yy;@", "FF", "MMM-ss"), arrayOf("[$-409]mmmm-yy;@", "FF", "MMMM-yy"), arrayOf("[$-409]mmmm d, yyyy;@", "FF", "MMMM d, yyyy"), arrayOf("[$-409]mmmmm;@", "FF", "MMMMM"), arrayOf("[$-409]mmmmm-yy;@", "FF", "MMMMM-yy"), arrayOf("[$-409]m/d/yy h:mm AM/PM;@", "FF", "M/d/yy H:mm aa"), arrayOf("[$-409]mmmm d, yyyy;@", "FF", "MMMM d, yyyy"), arrayOf("[$-409]h:mm:ss AM/PM", "FF", "H:mm:ss aa"), arrayOf("[$-409]d-mmm;@", "FF", "d-MMM"), arrayOf("[$-F800]dddd,mmmm dd,yyyy", "FF", "EEEE,MMMM dd, yyyy"), arrayOf("[$-F800]dddd, mmmm dd, yyyy", "FF", "EEEE, MMMM dd, yyyy"), arrayOf("dd/mm/yy hh:mm", "FF", "dd/MM/yy HH:mm"), arrayOf("d-mmm-yyyy hh:mm", "FF", "d-MMM-yyyy HH:mm"), arrayOf("yyyy-mm-dd hh:mm", "FF", "yyyy-MM-dd HH:mm"), arrayOf("yyyy-mm-ss hh:mm:ss.000", "FF", "yyyy-MM-dd HH:mm:ss.SSS"), arrayOf("yyyy-mm-dd", "FF", "yyyy-MM-dd"), arrayOf("d/mm/yy", "FF", "d/MM/yy"), arrayOf("d/mm/yy;@", "FF", "d/MM/yy"), arrayOf("dddd, mmmm dd, yyyy", "FF", "EEEEE, MMMMM d, yyyy"), arrayOf("[hhh]:mm", "FF", "h:mm"))/* ?? */ +// Decoded Format Patterns and hex Format Ids +val CURRENCY_FORMATS = arrayOf>( +// below 4 do not match excel! +// {"$#,##0;($#,##0)", "1", "$#,##0;($#,##0)"}, +// {"$#,##0;[Red]($#,##0)", "2", "$#,##0;[Red]($#,##0)"}, +// {"$#,##0.00;($#,##0.00)", "3", "$#,##0.00;($#,##0.00)"}, +// {"$#,##0.00;[Red]($#,##0.00)", "4", "$#,##0.00;[Red]($#,##0.00)"}, +arrayOf("#,##0_;($#,##0)", "5", "#,##0;($#,##0)"), arrayOf("#,##0_;[Red]($#,##0)", "6", "#,##0;[Red]($#,##0)"), arrayOf("#,##0.00_;($#,##0.00)", "7", "#,##0.00###;($#,##0.00)"), arrayOf("#,##0.00_;[Red]($#,##0.00)", "8", "#,##0.00;[Red]($#,##0.00)"), arrayOf("_$*#,##0_;_($*($#,##0);_($*\"-\"_);_(@_)", "2a", "$#,##0;($#,##0)"), arrayOf("_*#,##0_;_(*($#,##0);_(*\"-\"??_);_(@_)", "2b", "#,##0;(#,##0)"), arrayOf("_$*#,##0_;_($*($#,##0);_($*\"-\"??_);_(@_)", "2c", "$#,##0;($#,##0)"), +// User-defined (Format ID > 164, but variable. use Format ID= -1) +arrayOf("_$*#,##0_;_($*($#,##0);_($*\"-\"??_);_(@_)", "FF", "$#,##0;($#,##0)"), arrayOf("#,##0.00 [$-1]_);[Red](#,##0.00 [$-1])", "FF", "#,##0.00 ;[Red](#,##0.00 )"), arrayOf("_ * #,##0.00_) [$-1]_ ;_ * (#,##0.00) [$-1]_ ;_ * -??_) [$-1]_ ;_ @_ ", "FF", "#,##0.00 ;(#,##0.00) "), arrayOf("_ * #,##0.00_) [$€-1]_ ;_ * (#,##0.00) [$€-1]_ ;_ * -??_) [$€-1]_ ;_ @_ ", "FF", "#,##0.00 €;(#,##0.00) €"), arrayOf("#,##0.00 [$€-1]", "FF", "#,##0.00 €"), arrayOf("#,##0.00 [$-1]", "FF", "#,##0.00 "), arrayOf("$#,##0.00", "FF", "$#,##0.00"), arrayOf("_($* #,##0_);_($* (#,##0);_($* -??_);_(@_)", "FF", "$ #,##0;($* (#,##0)"), arrayOf("#,##0.00 [$-1];[Red]#,##0.00 [$-1]", "FF", "#,##0.00 ;[Red]#,##0.00 "), arrayOf("[$-411]#,##0.00", "FF", "#,##0.00"), arrayOf("[$-411]#,##0.00;[Red][$-411]#,##0.00", "FF", "#,##0.00;[Red]#,##0.00"), arrayOf("[$-411]#,##0.00;-[$-411]#,##0.00", "FF", "#,##0.00"), arrayOf("[$-411]#,##0.00;[Red]-[$-411]#,##0.00", "FF", "#,##0.00;[Red]-#,##0.00"), arrayOf("[$-809]#,##0.00", "FF", "#,##0.00"), arrayOf("[$-809]#,##0.00;[Red][$-809]#,##0.00", "FF", "#,##0.00;[Red]#,##0.00"), arrayOf("[$-809]#,##0.00;-[$-809]#,##0.00", "FF", "#,##0.00;-#,##0.00"), arrayOf("[$-809]#,##0.00;[Red]-[$-809]#,##0.00", "FF", "#,##0.00;[Red]-#,##0.00"), arrayOf("#,##0.0 [$$-C0C];[Red]#,##0.0 [$$-C0C]", "FF"), +// user-defined +arrayOf("$#,##0;($#,##0)", "FF", "$#,##0;($#,##0)"), arrayOf("$#,##0.00;($#,##0.00)", "FF", "$#,##0.00;($#,##0.00)"), arrayOf("$#,##0;[Red]($#,##0)", "FF", "$#,##0;[Red]($#,##0)"), arrayOf("$#,##0.00;[Red]($#,##0.00)", "FF", "$#,##0.00;[Red]($#,##0.00)"), arrayOf("$#,##0.00;[Red]$#,##0.00", "FF", "$#,##0.00;[Red]$#,##0.00"), arrayOf("$#,##0", "FF", "$#,##0")) + +// this maps Java formats to Excel formats +val patternmap = arrayOf>( +// currency +arrayOf("#,##0.00", "#,##0.00"), arrayOf("$#,##0;($#,##0)", "(#,##0_);($#,##0)"), arrayOf("$#,##0;[Red]($#,##0)", "$###,###;($###,###)"), arrayOf("$#,##0.00;($#,##0.00)", "$###,###.##;($###,###.##)"), arrayOf("$#,##0.00;[Red]($#,##0.00)", "$###,###.##;($###,###.##)"), arrayOf("0%", "0%"), arrayOf("0.00%", "0.00%"), arrayOf("0.00E+00", "0.00E+00"), arrayOf("# ?/?", "# ?/?"), arrayOf("# ??/??", ""), arrayOf("#,##0_;($#,##0)", ""), arrayOf("#,##0_;[Red]($#,##0)", ""), arrayOf("#,##0.00_;($#,##0.00)", ""), arrayOf("#,##0.00_;[Red]($#,##0.00)", ""), arrayOf("_*#,##0_;_(*($#,##0);_(*-_);_(@_)", ""), arrayOf("_$*#,##0_;_($*($#,##0);_($*-_);_(@_)", ""), arrayOf("_*#,##0_;_(*($#,##0);_(*-??_);_(@_)", ""), arrayOf("_$*#,##0_;_($*($#,##0);_($*-??_);_(@_)", ""), + +// date time +arrayOf("mm-mmm-yy", "MM-dd-YY"), arrayOf("m/d/y", "M/d/y"), arrayOf("d-mmm-yy", "d-MMM-yy"), arrayOf("d-mmm", "d-MMM"), arrayOf("mmm-yy", "MMM-yy"), arrayOf("h:mm AM/PM", "hh:mm a"), arrayOf("h:mm:ss AM/PM", "hh:mm:ss a"), arrayOf("h:mm", "hh:mm"), arrayOf("h:mm:ss", "hh:mm:ss"), arrayOf("m/d/yy h:mm", "M/d/yy hh:mm")) + + +// look up user-friendly format string and get Excel-qualified or encoded format string +// USED??? +val EXCEL_FORMAT_LOOKUP = arrayOf>( +// currency +arrayOf("0", ""), arrayOf("0.00", ""), arrayOf("#,##0", ""), arrayOf("#,##0.00", ""), arrayOf("($#,##0);($#,##0)", "\"$\"#,##0);(\"$\"#,##0)"), arrayOf("($#,##0);[Red]($#,##0)", "\"$\"#,##0);[Red](\"$\"#,##0)"), arrayOf("($#,##0.00);($#,##0.00)", "\"$\"#,##0.00);(\"$\"#,##0.00)"), arrayOf("($#,##0.00);[Red]($#,##0.00)", "\"$\"#,##0.00);[Red](\"$\"#,##0.00)"), arrayOf("0%", ""), arrayOf("0.00%", ""), arrayOf("0.00E+00", ""), arrayOf("# ?/?", ""), arrayOf("# ??/??", ""), +// Are these correct? Should they be qualified??? +arrayOf("(#,##0_);($#,##0)", ""), arrayOf("(#,##0_);[Red]($#,##0)", ""), arrayOf("(#,##0.00_);($#,##0.00)", ""), arrayOf("(#,##0.00_);[Red]($#,##0.00)", ""), + + +arrayOf("(#,##0_);(#,##0)", ""), arrayOf("(#,##0_);[Red](#,##0)", ""), arrayOf("(#,##0.00_);(#,##0.00)", ""), arrayOf("(#,##0.00_);[Red](#,##0.00)", ""), arrayOf("_(*#,##0_);_(*($#,##0);_(*-_);_(@_)", "_(*#,##0_);_(*(\"$\"#,##0);_(*\"-\"_);_(@_)"), arrayOf("_($*#,##0_);_($*($#,##0);_($*-_);_(@_)", "_(\"$\"*#,##0_);_(\"$\"*($#,##0);_(\"$\"*\"-\"_);_(@_)"), arrayOf("_(*#,##0_);_(*($#,##0);_(*-??_);_(@_)", "_(*#,##0_);_(*(\"$\"#,##0);_(*\"-\"??_);_(@_)"), arrayOf("_($*#,##0_);_($*($#,##0);_($*-??_);_(@_)", "_(\"$\"*#,##0_);_(\"$\"*(\"$\"#,##0);_(\"$\"*\"-\"??_);_(@_)"), + +// dates +arrayOf("mm-mmm-yy", ""), arrayOf("m/d/y", ""), arrayOf("d-mmm-yy", "d\\-mmm\\-yy"), arrayOf("[$-409]mmmm d, yyyy;@", "mmmm\\d\\,\\-yyyy"), arrayOf("d-mmm", "d\\-mmm"), arrayOf("mmm-yy", "mmm\\-yy"), arrayOf("h:mm AM/PM", "h:mm\\ AM/PM"), arrayOf("h:mm:ss AM/PM", ""), arrayOf("h:mm", ""), arrayOf("h:mm:ss", ""), arrayOf("m/d/yy h:mm", "")) +// Alignment's +val ALIGN_DEFAULT = 0 +val ALIGN_LEFT = 1 +val ALIGN_CENTER = 2 +val ALIGN_RIGHT = 3 +val ALIGN_FILL = 4 +val ALIGN_JUSTIFY = 5 +val ALIGN_CENTER_ACROSS_SELECTION = 6 +val ALIGN_VERTICAL_TOP = 0 +val ALIGN_VERTICAL_CENTER = 1 +val ALIGN_VERTICAL_BOTTOM = 2 +val ALIGN_VERTICAL_JUSTIFY = 3 + +val VERTICAL_ALIGNMENTS = arrayOf("top", "middle", "bottom", "text-bottom", // justified +"text-bottom")// distributed" + +/** + * These Horizontal alignments are used to map to HTML output, so some + * values may not be as expected. For instance, "Center across selection" is + * not something you can do in HTML. We will use "Center" for this. + * + * + * Not entirely True: HTML can center across merged table cells -- this is basically + * the purpse AFAIK. -jm +*/ +val HORIZONTAL_ALIGNMENTS = arrayOf("Default", "Left", "Center", "Right", "Center", "Center", "Center") +} +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/FormatConstantsImpl.java b/src/main/java/io/starter/formats/XLS/FormatConstantsImpl.kt similarity index 64% rename from src/main/java/io/starter/formats/XLS/FormatConstantsImpl.java rename to src/main/java/io/starter/formats/XLS/FormatConstantsImpl.kt index 67456b8..b30a814 100644 --- a/src/main/java/io/starter/formats/XLS/FormatConstantsImpl.java +++ b/src/main/java/io/starter/formats/XLS/FormatConstantsImpl.kt @@ -20,25 +20,26 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import java.util.Locale; +import java.util.Locale /** * A generic implementation of format constants */ -public class FormatConstantsImpl implements FormatConstants { +class FormatConstantsImpl : FormatConstants { + companion object { - /** - * Get the built in format for the correct locale - * - * @return - */ - public static String[][] getBuiltinFormats() { - if (Locale.JAPAN.equals(Locale.getDefault())) { - return FormatConstants.BUILTIN_FORMATS_JP; - } else { - return FormatConstants.BUILTIN_FORMATS; - } + /** + * Get the built in format for the correct locale + * + * @return + */ + val builtinFormats: Array> + get() = if (Locale.JAPAN == Locale.getDefault()) { + FormatConstants.BUILTIN_FORMATS_JP + } else { + FormatConstants.BUILTIN_FORMATS + } } } diff --git a/src/main/java/io/starter/formats/XLS/Formula.java b/src/main/java/io/starter/formats/XLS/Formula.java deleted file mode 100644 index d377ca3..0000000 --- a/src/main/java/io/starter/formats/XLS/Formula.java +++ /dev/null @@ -1,1259 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.*; - -/** - * FORMULA (0x406) describes a cell that contains a formula. - *

                  - * offset  name        size    contents
                  - * 0       rw          2       Row
                  - * 2       col         2       Column
                  - * 4       ixfe        2       Index to the XF record
                  - * 6       num         8       Current value of the formula
                  - * 14      grbit       2       Option Flags
                  - * 16      chn         4       (Reserved, must be zero) A field that specifies an application-specific cache of information. This cache exists for performance reasons only, and can be rebuilt based on information stored elsewhere in the file without affecting calculation results.
                  - * 20      cce         2       Parsed Expression length
                  - * 22      rgce        cce     Parsed Expression
                  - * 
                  - * The grbit field contains the following flags: - *
                  - * byte   bits   mask   name           asserted if
                  - * 0      0      0x01   fAlwaysCalc    the result must not be cached
                  - *        1      0x02   fCalcOnLoad    the cached value is incorrect
                  - *        2      0x04   (Reserved)
                  - *        3      0x08   fShrFmla       this is a reference to a shared formula
                  - *        7-4    0xF0   (Unused)
                  - * 1      7-0    0xFF   (Unused)
                  - * 
                  - * In most cases, formulas should have fAlwaysCalc asserted to ensure that - * correct values are displayed upon opening of the file in Excel. - */ -public final class Formula - extends XLSCellRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7563301825566021680L; - /** - * Mask for the fAlwaysCalc grbit flag. - */ - private static final short FALWAYSCALC = 0x01; - /** - * Mask for the fCalcOnLoad grbit flag. - */ - private static final short FCALCONLOAD = 0x02; - /** - * Mask for the fShrFmla grbit flag. - */ - private static final short FSHRFMLA = 0x08; - - private Object cachedValue; - private Stack expression; - - - /** - * Whether the record data needs to be updated. - */ - private boolean dirty = false; - - /** - * Whether this formula contains an indirect reference. - */ - private boolean containsIndirectFunction = false; - - /** - * Whether this FORMULA record has an attached STRING record. - */ - private boolean haveStringRec = false; - - /** - * Contains bitfield flags. - */ - private short grbit = FCALCONLOAD; - - /** - * The attached STRING record, if one exists. - */ - private StringRec string = null; - - /** - * The target ShrFmla record, if this is a shared formula reference. - */ - public Shrfmla shared = null; - - /** - * List of records attached to this one. - */ - private List internalRecords; - - /** - * true if it's sub-ptgs are defined in other workbooks and therefore unable to be resolved - */ - private boolean isExternalRef = false; - - /** - * Default constructor - */ - public Formula() { - setOpcode(XLSConstants.FORMULA); - setIsValueForCell(true); - isFormula = true; - } - - /** - * Parses the record bytes. - * This method only needs to be called when the record is being constructed - * from bytes. Calling it on a programmatically created formula is - * unnecessary and will probably throw an exception. - */ - public void init() { - // Prevent misuse of init - if (expression != null) throw new IllegalStateException( - "can't init a formula created from a string"); - - super.init(); - if ((data = getData()) == null) throw new IllegalStateException( - "can't init a formula without record bytes"); - super.initRowCol(); - ixfe = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - grbit = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); - - // get the cached value bytes from the record - byte[] currVal = this.getBytesAt(6, 8); - - // Is this a non-numeric value? - if (currVal[6] == (byte) 0xFF && currVal[7] == (byte) 0xFF) { - // String value - if (currVal[0] == (byte) 0x00) { - haveStringRec = true; // bytes 1-5 are not used - // Normally cachedValue will be set by StringRec's init. - // Setting cachedValue null forces calculation in the rare - // event that the STRING record is missing or fails to init. - cachedValue = null; - } - - // Empty string value - else if (currVal[0] == (byte) 0x03) { - // There is no attached STRING record. Set cachedValue directly. - cachedValue = ""; - } - - // Boolean value - else if (currVal[0] == (byte) 0x01) { - cachedValue = Boolean.valueOf(currVal[2] != (byte) 0x00); - } - - // Error value - else if (currVal[0] == (byte) 0x02) { - cachedValue = new CalculationException(currVal[2]); - } - - // Unknown value type - else { - cachedValue = null; - } - - } else { - // do not cache NaN stored bytes - double dbv = ByteTools.eightBytetoLEDouble(currVal); - if (!Double.isNaN(dbv)) { - cachedValue = new Double(dbv); - } - } - - if (this.getSheet() == null) - this.setSheet(this.wkbook.getLastbound()); - - if (DEBUGLEVEL > 10) { - try { - Logger.logInfo("INFO: Formula " + this.getCellAddress() + this.getFormulaString()); - } catch (Exception e) { - Logger.logInfo("Debug output of Formula failed: " + e); - } - } - - // The expression needs to be parsed on input in order to add it to - // the reference tracker - //TODO: Add a no calculation / read only mode without ref tracking - this.populateExpression(); - // Perform some special handling for formulas with indirect references - if (containsIndirectFunction) this.registerIndirectFunction(); - this.dirty = false; - } - - /** - * Performs cleanup required before changing or removing this Formula. - * This nulls out the expression, so save a copy if you need it. - * Possible sub-records associated with Formula: array, shared string and/or shared formula - */ - private void clearExpression() { - if (expression == null) - return; - - if (this.isArrayFormula()) { - Array a = this.getArray(); - if (a != null) - this.getSheet().removeRecFromVec(a); - } - if (this.hasAttachedString()) { // remove that too - if (string != null) - this.getSheet().removeRecFromVec(string); - string = null; - } - - if (isSharedFormula()) { - shared.removeMember(this); - shared = null; - setSharedFormula(false); - } - - Iterator iter = expression.iterator(); - while (iter.hasNext()) { - Ptg ptg = (Ptg) iter.next(); - if (ptg instanceof PtgRef) ((PtgRef) ptg).removeFromRefTracker(); - } - - expression = null; - } - - /** - * for creating a formula on the fly. This is used currently by FormulaParser - * to create a formula. The location and XF fields are filled in by boundsheet.add(); - */ - public void setExpression(Stack exp) { - if (expression != null) - clearExpression(); - expression = exp; - updateRecord(); - } - - /** - * get the expression stack - * - * @return - */ - public Stack getExpression() { - populateExpression(); - return expression; - } - - /** - * store formula-associated records: one or more of: - * ShrFmla - * StringRec - * Array - * - * @param b - */ - public void addInternalRecord(BiffRec b) { - if (internalRecords == null) internalRecords = new ArrayList(3); - - if (b instanceof Shrfmla) internalRecords.add(0, b); - else if (b instanceof StringRec) { - if (!haveStringRec || string == null) {// should ONLY have 1 StringRec - internalRecords.add(b); - haveStringRec = true; - string = (StringRec) b; - cachedValue = string.getStringVal(); - }/* TODO: this is such a rare occurrence - due possibly to OUR processing - keep if and wa - else - Logger.logErr("Formula.init: Out of Spec Formula Encountered (Multiple String Recs Encountered)- Ignoring"); - */ - } else // array formula - internalRecords.add(b); - - } - - public void removeInternalRecord(BiffRec b) { - if (internalRecords == null) return; - internalRecords.remove(b); - } - - public List getInternalRecords() { - if (internalRecords == null) return Collections.emptyList(); - return internalRecords; - } - - - public boolean hasAttachedString() { - return haveStringRec; - } - - /** - * Get the String that is attached to this formula (it has a result of a string..); - */ - public StringRec getAttatchedString() { - return string; - } - - /** - * return the "Calculate Always" setting for this formula - * used for formulas that always need calculating such as TODAY - * - * @return - */ - public boolean getCalcAlways() { - return (grbit & FALWAYSCALC) != 0; - } - - /** - * set the "Calculate Always setting for this formula - * used for formulas that always need calculating such as TODAY - * - * @param fAlwaysCalc - */ - public void setCalcAlways(boolean fAlwaysCalc) { - if (fAlwaysCalc) grbit |= FALWAYSCALC; - else grbit &= ~FALWAYSCALC; - } - - /** - * set if this formula refers to External References - * (references defined in other workbooks) - * - * @param isExternalRef - */ - public void setIsExternalRef(boolean isExternalRef) { - this.isExternalRef = isExternalRef; - } - - public String getTypeName() { - return "formula"; - } - - /** - * Adds an indirect function to the list of functions to be evaluated post load - */ - protected void registerIndirectFunction() { - this.getWorkBook().addIndirectFormula(this); - } - - /** - * If the method contains an indirect function then - * register those ptgs into the reference tracker. - *

                  - * In order to do this it is necessary to calculate the formula - * to retrieve the Ptg's - */ - protected void calculateIndirectFunction() { - this.clearCachedValue(); - try { - this.calculateFormula(); - } catch (FunctionNotSupportedException e) { - // If we do not support the function, calculation will throw a FNE anyway, so no need for logging - } catch (Exception e) { - // problematic here. As the calculation is happening on parse we dont really - // want to throw an exception and crap out on the book loading - // but the client should be informed in some way. Also a generic exception is caught - // because our code does not bubble a calc exception up. - Logger.logErr("Error registering lookup for INDIRECT() function at cell: " + this.getCellAddress() + " : " + e); - } - } - - /** - * Populates the expression in the formula. This has been moved out of init for performance reasons. - * The idea is that the processing is offloaded as a JIT for calculation/value retrieval. - */ - //TODO: refactor external references and make private - void populateExpression() { - if (expression != null || data == null) return; - - try { - short length = ByteTools.readShort( - this.getByteAt(20), this.getByteAt(21)); - - if (length + 22 > data.length) throw new Exception( - "cce longer than record"); - - expression = ExpressionParser.parseExpression( - this.getBytesAt(22, reclen - 22), this, length); - - // If this is a shared formula reference, do some special init - if (isSharedFormula()) - initSharedFormula(null); - } catch (Exception e) { - if (DEBUGLEVEL > -10) - Logger.logInfo("Formula.init: Parsing Formula failed: " + e); - } - } - - /** - * Performs special initialization for shared formula references. - * - * @param target the target SHRFMLA record. If this is - * null it will be retrieved from the cell pointed to - * by the PtgExp. - * @throws FormulaNotFoundException if the target shared formula is missing - * @throws IllegalArgumentException if this is not a shared formula member - */ - void initSharedFormula(Shrfmla target) throws FormulaNotFoundException { - if (!isSharedFormula()) { - setSharedFormula(true); - } - - // If we're already done, silently do nothing - if (shared != null) - return; - - // If this is an instantiation instead of a reference - if (expression.size() != 1 - || !(expression.get(0) instanceof PtgExp)) { - //TODO: find which ShrFmla this is and convert to reference - // For now, just clear fShrFmla - setSharedFormula(false); - return; - } - - PtgExp pointer = (PtgExp) expression.get(0); - - if (target != null) { - shared = target; - } else try { - shared = ((Formula) getSheet().getCell( - pointer.getRwFirst(), pointer.getColFirst())).shared; // find shared cell linked to host/first formula cell in shared formula series - if (shared == null) - throw new Exception(); - } catch (Exception e) { - // If this is the host cell, fail silently. This method will be - // re-called by the ShrFmla record's init method. - if (this.getCellAddress().equals( - pointer.getReferent())) - return; - - // Otherwise, complain and clear fShrFmla - throw new FormulaNotFoundException( - "FORMULA at " + this.getCellAddress() - + " refers to missing SHRFMLA at " - + pointer.getReferent()); - } - - //Shared Formula Init Performance Changes: do not instantiate until calculate -// expression = shared.instantiate( pointer ); - shared.addMember(this); - - if (shared.containsIndirectFunction) registerIndirectFunction(); - } - - /** - * Converts a shared formula reference into a normal formula. - * - * @throws IllegalStateException if this is not a shared formula member - */ - public void convertSharedFormula() { - if (!isSharedFormula()) - throw new IllegalStateException( - "not a shared formula reference"); - - shared = null; - setSharedFormula(false); - } - - - /** - * Returns the Human-Readable String Representation of - * this Formula. - */ - public String getFormulaString() { - populateExpression(); - if (!this.isArrayFormula()) { - return FormulaParser.getFormulaString(this); - } - return "{" + FormulaParser.getFormulaString(this) + "}"; - - } - - - /** - * Returns the ptg that matches the string location sent to it. - * this can either be in the format "C5" or a range, such as "C4:D9" - */ - public List getPtgsByLocation(String loc) - throws FormulaNotFoundException { - populateExpression(); - if (loc.indexOf("!") == -1) - loc = this.getSheet().getSheetName() + "!" + loc; - - return ExpressionParser.getPtgsByLocation(loc, expression); - } - - /** - * Returns an array of ptgs that represent any BiffRec ranges in the formula. - * Ranges can either be in the format "C5" or "Sheet1!C4:D9" - */ - public Ptg[] getCellRangePtgs() - throws FormulaNotFoundException { - return ExpressionParser.getCellRangePtgs(expression); - } - - /** - * locks the Ptg at the specified location - */ - - public boolean setLocationPolicy(String loc, int l) { - populateExpression(); - try { - List dx = this.getPtgsByLocation(loc); - Iterator lx = dx.iterator(); - while (lx.hasNext()) { - Ptg d = (Ptg) lx.next(); - d.setLocationPolicy(l); - if (l == Ptg.PTG_LOCATION_POLICY_TRACK) // init the tracker cell right away - d.initTrackerCell(); - } - return true; - } catch (FormulaNotFoundException e) { - Logger.logInfo("locking Formula Location failed:" + loc + ": " + e.toString()); - return false; - } - } - - /** - * Called to indicate that the parsed expression has changed. - * Does nothing if the expression doesn't exist yet. - */ - public void updateRecord() { - dirty = true; - if (this.data == null) - setData(new byte[6]); // happens when newly init'ing a formula - - if (cachedValue instanceof String && !"".equals(cachedValue) && !isErrorValue((String) cachedValue)) {// if it's a string and not an error string - if (!haveStringRec || string == null) { -// this.addInternalRecord( new StringRec( (String)cachedValue ) ); // will be added in workbook.addRecord - string = new StringRec((String) cachedValue); - string.setSheet(getSheet()); - string.setRowNumber(getRowNumber()); - string.setCol(getColNumber()); - getWorkBook().setLastFormula(this); // for addRecord, sets appropriate formula internal record - getWorkBook().addRecord(string, true); - haveStringRec = true; - } else { - string.setStringVal((String) cachedValue); - } - } else if (string != null) { - string.remove(false); - removeInternalRecord(string); - haveStringRec = false; - } - } - - /** - * Updates the record data if necessary to prepare for streaming. - */ - public void preStream() { - // If the record doesn't need to be updated, do nothing - if (!dirty && !isSharedFormula() && cachedValue != null && - (getWorkBook().getCalcMode() != WorkBook.CALCULATE_EXPLICIT)) - return; - - - // If the formula needs calculation, do so - try { - if (cachedValue == null) calculateFormula(); - } catch (FunctionNotSupportedException e) { - // Fall through to null cachedValue handling below - } - - // Sometimes we need to write a value other than the real cached value - Object writeValue = cachedValue; - - // Handle formulas that can't be calculated for whatever reason - if (cachedValue == null) { - grbit |= FCALCONLOAD; - if (writeValue == null) writeValue = new Double(Double.NaN); - } - - // Handle CALCULATE_EXPLICIT mode - if (getWorkBook().getCalcMode() == WorkBook.CALCULATE_EXPLICIT) { - grbit |= FCALCONLOAD; - } - - // If this is a shared formula, write a PtgExp - Stack expr = expression; - if (isSharedFormula()) { /* ONLY need to do this if shared formula member(s) have changed- - a better choice is to trap and change in respective method */ - expr = new Stack(); - expr.add(shared.getPointer()); - } - - // Fetch the Ptg data and calculate the expression size - byte[][] ptgdata = new byte[expr.size()][]; - byte[] rgb = null; - short cce = 0; - short rgblen = 0; - for (int idx = 0; idx < expr.size(); idx++) { - Ptg ptg = (Ptg) expr.get(idx); - ptgdata[idx] = ptg.getRecord(); - cce += ptgdata[idx].length; - - if (ptg instanceof PtgArray) { - byte[] extra = ((PtgArray) ptg).getPostRecord(); - rgb = ByteTools.append(extra, rgb); - rgblen += extra.length; - } else if (ptg instanceof PtgMemArea) { // has PtgExtraMem structure appended - byte[] extra = ((PtgMemArea) ptg).getPostRecord(); - rgb = ByteTools.append(extra, rgb); - rgblen += extra.length; - } - } - - byte[] newdata = new byte[22 + cce + rgblen]; - // Cell Header (row, col, ixfe) - System.arraycopy(data, 0, newdata, 0, 6); - - // Cached Value (num) - byte[] value; - if (writeValue instanceof Number) { - //TODO: Check infinity, NaN, etc. - value = ByteTools.toBEByteArray( - ((Number) writeValue).doubleValue()); - } else { - value = new byte[8]; - // byte 0 specifies the marker type - value[1] = (byte) 0x00; - // byte 2 is used by bool and boolerr - value[3] = (byte) 0x00; - value[4] = (byte) 0x00; - value[5] = (byte) 0x00; - value[6] = (byte) 0xFF; - value[7] = (byte) 0xFF; - - if (writeValue instanceof String) { - if (!isErrorValue((String) writeValue)) { - value[2] = (byte) 0x00; - String sval = (String) writeValue; - if (sval.equals("") || string == null) { // the latter can occur when input from XLSX; a cachedvalue is set without an associated StringRec - value[0] = (byte) 0x03; // means empty - } else { - value[0] = (byte) 0x00; - string.setStringVal(sval); - } - } else { - value[0] = (byte) 0x02; // error code - value[2] = CalculationException.getErrorCode((String) writeValue); - } - } else if (writeValue instanceof Boolean) { - value[0] = (byte) 0x01; - value[2] = (((Boolean) writeValue).booleanValue() - ? (byte) 0x01 : (byte) 0x00); - } else if (writeValue instanceof CalculationException) { - value[0] = (byte) 0x02; - value[2] = ((CalculationException) writeValue).getErrorCode(); - } else throw new Error("unknown value type " - + (writeValue == null ? "null" : - writeValue.getClass().getName())); - } - System.arraycopy(value, 0, newdata, 6, 8); - - // Bit Flags (grbit) - System.arraycopy(ByteTools.shortToLEBytes(grbit), 0, newdata, 14, 2); - - // chn - reserved zero - Arrays.fill(newdata, 16, 19, (byte) 0x00); - - // Expression Length (cce) - System.arraycopy(ByteTools.shortToLEBytes(cce), 0, newdata, 20, 2); - - // Expression Ptgs (rgce) - int offset = 22; - for (int idx = 0; idx < ptgdata.length; idx++) { - System.arraycopy( - ptgdata[idx], 0, newdata, offset, ptgdata[idx].length); - offset += ptgdata[idx].length; - } - - // Expression Extra Data (rgb) - if (rgblen > 0) System.arraycopy(rgb, 0, newdata, offset, rgblen); - setData(newdata); - dirty = false; - } - - public Object clone() { - // Make the record bytes available to XLSRecord.clone - preStream(); - return super.clone(); - } - - /** - * Get the value of the formula as an integer. - * If the formula exceeds integer boundaries, or is a float with - * a non-zero mantissa throw an exception - * - * @see io.starter.formats.XLS.XLSRecord#getIntVal() - */ - public int getIntVal() throws RuntimeException { - Object obx = calculateFormula(); - try { - double tl = ((Double) obx).doubleValue(); - if (tl > Integer.MAX_VALUE) { - throw new NumberFormatException("getIntVal: Formula value is larger than the maximum java signed int size"); - } - if (tl < Integer.MIN_VALUE) { - throw new NumberFormatException("getIntVal: Formula value is smaller than the minimum java signed int size"); - } - double db = ((Double) obx).doubleValue(); - int ret = ((Double) obx).intValue(); - if ((db - ret) > 0 && DEBUGLEVEL > this.DEBUG_LOW) - Logger.logWarn("Loss of precision converting " + tl + " to int."); - - return ret; - // not back-compat return Integer.valueOf(new Long((long) tl).intValue()).intValue(); - // throw new NumberFormatException("Loss of precision converting " + tl + " to int."); - } catch (ClassCastException e) { - } - - long l = 0; - String s = String.valueOf(obx); - - // return a zero for empties - if (s.equals("")) s = "0"; - try { - String t = ""; - java.math.BigDecimal bd = new java.math.BigDecimal(s); - l = bd.longValue(); // 20090514 KSC: bd.intValueExact(); is 1.6 compatible -- MAY CAUSE INFOTERIA REGRESSION ERROR - if (l > Integer.MAX_VALUE) { - throw new NumberFormatException("Formula value is larger than the maximum java signed int size"); - } - if (l < Integer.MIN_VALUE) { - throw new NumberFormatException("Formula value is smaller than the minimum java signed int size"); - } - return Integer.valueOf(new Long(l).toString()).intValue(); - } catch (NumberFormatException ne) { - throw new NumberFormatException("getIntVal: Formula is a non-numeric value"); - } catch (Exception e) { - throw new NumberFormatException("getIntVal: " + e); - } - } - - - public float getFloatVal() { - Object obx = calculateFormula(); - try { - if (obx instanceof Float) { - float d = ((Float) obx).floatValue(); - return d; - } - } catch (Exception e) { - Logger.logErr("Formula.getFloatVal failed for: " + this.toString(), e); - } - - try { - String s = String.valueOf(obx); - - // return a zero for empties - if (s.equals("")) s = "0"; - Float d = new Float(s); - return d.floatValue(); - } catch (NumberFormatException ex) { - return Float.NaN; - } catch (Exception e) { - Logger.logWarn("Formula.getFloatVal() failed: " + e); - } - return Float.NaN; - } - - public boolean getBooleanVal() { - Object obx = calculateFormula(); - try { - if (obx instanceof Boolean) { - return ((Boolean) obx).booleanValue(); - } - } catch (Exception e) { - Logger.logErr("getBooleanVal failed for: " + this.toString(), e); - } - - try { - String s = String.valueOf(obx); - if (s.equalsIgnoreCase("true") || s.equals("1")) return true; - - } catch (Exception e) { - Logger.logWarn("getBooleanVal() failed: " + e); - } - return false; - } - - public double getDblVal() { - Object obx = calculateFormula(); - try { - if (obx instanceof Double) { - double d = ((Double) obx).doubleValue(); - return d; - } - } catch (Exception e) { - Logger.logErr("Formula.getDblVal failed for: " + this.toString(), e); - } - - String s = String.valueOf(obx); - - // return a zero for empties - if (s.equals("")) s = "0"; - try { - Double d = new Double(s); - return d.doubleValue(); - } catch (NumberFormatException ex) { - return Double.NaN; - } catch (Exception e) { - Logger.logWarn("Formula.getDblVal() failed: " + e); - } - return Double.NaN; - } - - - /** - * Returns the correct string representation of a double for excel. - *

                  - * Note this is for the standards that were determined with excel and io.starter.OpenXLS - * - * @param num - * @return - */ - private static String getDoubleAsFormattedString(double theNum) { - return ExcelTools.getNumberAsString(theNum); - } - - /** - * return the String representation of the current Formula value - */ - public String getStringVal() { - Object obx = calculateFormula(); - try { - if (obx instanceof Double) { - double d = ((Double) obx).doubleValue(); - if (!Double.isNaN(d)) - return Formula.getDoubleAsFormattedString(d); - else - return "NaN"; - } - } catch (Exception e) { - Logger.logErr("Formula.getStringVal failed for: " + this.toString(), e); - } - // if null, return empty string - if (obx == null) - return ""; - return obx.toString(); - - } - - /** - * Calculates the formula honoring calculation mode. - * - * @throws CalculationException - */ - public Object calculateFormula() - throws FunctionNotSupportedException { - // if this is calc explicit, we ALWAYS use cache - if (getWorkBook().getCalcMode() == WorkBook.CALCULATE_EXPLICIT) - return cachedValue; - // TODO: IF ALREADY RECALCED DONT SET TO null -- need flag? - if (getWorkBook().getCalcMode() == WorkBook.CALCULATE_ALWAYS) { - if (!isExternalRef) // if it's an external reference DONT CLEAR CACHE - cachedValue = null; // force calc - else - return cachedValue; - } - return calculate(); - - } - - private static ThreadLocal recurseCount = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; - - /** - * Calculate the formula if necessary. This accessor resets the recurse count on the - * formula. Excel standard is to allow 100 recursions before throwing a circular reference. - * - * @throws CalculationException - */ - public Object calculate() { - Integer depth = recurseCount.get(); - - try { - recurseCount.set(depth + 1); - if (depth > WorkBookHandle.RECURSION_LEVELS_ALLOWED) { - Logger.logWarn("Recursion levels reached in calculating formula " - + this.getCellAddressWithSheet() - + ". Possible circular reference. Recursion levels can be set through WorkBookHandle.setFormulaRecursionLevels"); - cachedValue = new CalculationException( - CalculationException.CIR_ERR); - return cachedValue; - } - return this.calculateInternal(); - } finally { - recurseCount.set(depth); - } - } - - - /** - * Calculates the formula if necessary regardless of calculation mode. - * If there is a cached value it will be returned. Otherwise, the formula - * will be calculated and the result will be cached and returned. If you - * need to force calculation call {@link #clearCachedValue()} first. - */ - private Object calculateInternal() { - // If we have a cached value, return it instead of calculating - if (cachedValue != null) return cachedValue; - populateExpression(); - try { - cachedValue = FormulaCalculator.calculateFormula(this.expression); - } catch (StackOverflowError e) { - Logger.logWarn("Stack overflow while calculating " - + this.getCellAddressWithSheet() - + ". Possible circular reference."); - cachedValue = new CalculationException( - CalculationException.CIR_ERR); - return cachedValue; - } - - if (cachedValue == null) - throw new FunctionNotSupportedException("Unable to calculate Formula " + this.getFormulaString() + " at: " + this.getSheet().getSheetName() + "!" + this.getCellAddress()); - - if (cachedValue.toString().equals("#CIR_ERR!")) { - return new CircularReferenceException(CalculationException.CIR_ERR); - } - if (cachedValue.toString().length() < 1) { - // do something...? - } else if (cachedValue.toString().charAt(0) == '{') { - // it's an array, we need to find the particular value that we want. - // parse all array strings into rows, cols - String arrStr = (String) cachedValue; - arrStr = arrStr.substring(1, arrStr.length() - 1); - String[] rows = null; - String[][] cols = null; - // split rows - rows = arrStr.split(";"); - cols = new String[rows.length][]; - for (int i = 0; i < rows.length; i++) { - cols[i] = rows[i].split(",", -1); // include empty strings - } - PtgExp pxp; - int rowA; - int colA; - Ptg p; - try { - pxp = (PtgExp) expression.elementAt(0); - rowA = this.getRowNumber() - pxp.getRwFirst(); - colA = this.getColNumber() - pxp.getColFirst(); - // now, if it's a 1-dimensional array e.g {1,2,3,4,5}, nr=1, nc= 5 - // if formula address is traversing rows then switch - if (rows.length == 1 && rowA > 0 && colA == 0) { - colA = rowA; - rowA = 0; - } - } catch (ClassCastException e) { - // this is when we just calc'd a formula and have no exp reference. - // assume it is the location of the formula - // could be incorrect, may need to revisit - rowA = 0; - colA = 0; - } - cachedValue = cols[rowA][colA]; - // try to cast - try { - cachedValue = new Double((String) cachedValue); - } catch (Exception e) { - // let it go - } - if (DEBUGLEVEL > 10) Logger.log(cachedValue); - } - if (this.getAttatchedString() != null) { - this.getAttatchedString().setStringVal(String.valueOf(cachedValue)); - } - - updateRecord(); - return cachedValue; - } - - public String toString() { - populateExpression(); - return super.toString(); - //return this.worksheet.getSheetName() + "!" + this.getCellAddress() + ":" + this.getStringVal(); - } - - /** - * Returns whether this is a reference to a shared formula. - */ - public boolean isSharedFormula() { - return (grbit & FSHRFMLA) != 0; - } - - /** - * Sets whether this is a reference to a shared formula. - */ - private void setSharedFormula(boolean isSharedFormula) { - if (isSharedFormula) grbit |= FSHRFMLA; - else grbit &= ~FSHRFMLA; - } - - public void setStringVal(String v) { - throw new CellTypeMismatchException("Attempting to set a string value on a formula"); - // TODO: set the string value of the attached string? - } - - /** - * return truth of "this is an array formula" i.e. contains an Array sub-record - * - * @return - */ - public boolean isArrayFormula() { - if (internalRecords != null && internalRecords.size() > 0) - return (internalRecords.get(0) instanceof Array); - return false; - } - - /** - * fetches the internal Array record linked to this formula, if any, - * or null if not an array formula - * - * @return array record - * @see isArrayFormula - */ - public Array getArray() { - try { - return ((Array) internalRecords.get(0)); - } catch (Exception e) { - } -/* if (expression.get(0) instanceof PtgExp) { - // if it's the child of a parent array formula, obtain it's Array record - // TODO: verify this is correct + finish - }*/ - return null; - } - - /** - * OOXML-specific: set the range the Array references - * - * @param s - */ - public void setArrayRefs(String s) { - if (internalRecords != null && internalRecords.size() > 0) { - Object o = internalRecords.get(0); - if (o instanceof Array) { - Array a = (Array) o; - int[] rc = ExcelTools.getRangeRowCol(s); - a.setFirstRow(rc[0]); - a.setFirstCol(rc[1]); - a.setLastRow(rc[2]); - a.setLastCol(rc[3]); - } - } - } - - /** - * Set the cached value of this formula, - * in cases where the formula is null, set the cache to null, - * as well as updating the attached string to null in order to force - * recalc - * - * @see io.starter.formats.XLS.XLSRecord#setCachedValue(java.lang.Object) - */ - public void setCachedValue(Object newValue) { - if (newValue == null) { - this.clearCachedValue(); - } else { - cachedValue = newValue; // TODO: need to check/validate StringRec ???? - } - } - - - /** - * Set the cached value of this formula, - * in cases where the formula is null, set the cache to null, - * as welll as updating the attached string to null in order to force - * recalc - * - * @see io.starter.formats.XLS.XLSRecord#setCachedValue(java.lang.Object) - */ - public void clearCachedValue() { - cachedValue = null; - haveStringRec = false; -// this.updateRecord(); no need; will be updated after recalc, which will automatically happen on write - } - - public String getArrayRefs() { - if (internalRecords != null && internalRecords.size() > 0) { - Object o = internalRecords.get(0); - return ((Array) o).getArrayRefs(); - } - return ""; - } - - /** - * increment each PtgRef in expression stack via row or column based - * on rowInc or colInc values - * Used in OOXML parsing - */ - public static void incrementSharedFormula(java.util.Stack origStack, int rowInc, int colInc, int[] range) { - // traverse thru ptg's, incrementing row reference - //java.util.Stack origStack= form.getExpression(); Don't do "in place" as alters original expression - //Logger.logInfo("Before Inc: " + this.getFormulaString()); - for (int i = 0; i < origStack.size(); i++) { - Ptg p = (Ptg) origStack.elementAt(i); - try { - String s = p.getLocation(); - if (p.getIsReference()) { - if (!((((PtgRef) p).wholeRow && colInc != 0) || (((PtgRef) p).wholeCol && rowInc != 0))) { - if (!(p instanceof PtgArea)) { - boolean[] bRelRefs = {((PtgRef) p).isRowRel(), ((PtgRef) p).isColRel()}; - int[] rc = ExcelTools.getRowColFromString(s); - if (bRelRefs[0]) - rc[0] += rowInc; - if (bRelRefs[1]) - rc[1] += colInc; - PtgRef pr = new PtgRef(); - pr.setParentRec(p.getParentRec()); - pr.setUseReferenceTracker(false); // 20090827 KSC: don't petform expensive calcs on init + removereferenceTracker blows out cached value - pr.setLocation(ExcelTools.formatLocation(rc, bRelRefs[0], bRelRefs[1])); - pr.setUseReferenceTracker(true); - origStack.set(i, pr); - } else { - String sh = ExcelTools.stripSheetNameFromRange(s)[0]; - int[] rc = ExcelTools.getRangeRowCol(s); - boolean[] bRelRefs = {((PtgArea) p).getFirstPtg().isRowRel(), ((PtgArea) p).getLastPtg().isRowRel(), ((PtgArea) p).getFirstPtg().isColRel(), ((PtgArea) p).getLastPtg().isColRel()}; - if (bRelRefs[0]) - rc[0] += rowInc; - if (bRelRefs[1]) - rc[2] += rowInc; - if (bRelRefs[2]) - rc[1] += colInc; - if (bRelRefs[3]) - rc[3] += colInc; - PtgArea pa = new PtgArea(false); - pa.setParentRec(p.getParentRec()); - if (sh != null) - pa.setLocation(sh + "!" + ExcelTools.formatRangeRowCol(rc, bRelRefs)); - else - pa.setLocation(ExcelTools.formatRangeRowCol(rc, bRelRefs)); - pa.setUseReferenceTracker(true); - origStack.set(i, pa); - } - } - } - } catch (Exception ex) { - Logger.logErr("Formula.incrementSharedFormula: " + ex.toString()); - } - } - } - - /** - * Set if the formula contains Indirect() - * - * @param containsIndirectFunction The containsIndirectFunction to set. - */ - protected void setContainsIndirectFunction(boolean containsIndirectFunction) { - this.containsIndirectFunction = containsIndirectFunction; - } - - /** - * Performs cleanup needed before removing the formula cell from the - * work sheet. The formula will not behave correctly once this is called. - */ - public void destroy() { - clearExpression(); - } - - /** - * returns true if the value s is one of the Excel defined Error Strings - * - * @param s - * @return - */ - public static boolean isErrorValue(String s) { - if (s == null) return false; - return (Collections.binarySearch(Arrays.asList("#DIV/0!", "#N/A", "#NAME?", "#NULL!", "#NUM!", "#REF!", "#VALUE!"), s.trim()) > -1); - } - - /** - * clear out object references in prep for closing workbook - */ - private boolean closed = false; - - public void close() { - if (expression != null) { - while (!expression.isEmpty()) { - GenericPtg p = (GenericPtg) expression.pop(); - if (p instanceof PtgRef) - p.close(); -/* else if (p instanceof PtgExp ) { - Ptg[] ptgs= ((PtgExp)p).getConvertedExpression(); - for (int i= 0; i < ptgs.length; i++) { - if (ptgs[i] instanceof PtgRef) - ((PtgRef) ptgs[i]).close(); - else - ((GenericPtg)ptgs[i]).close(); - } - } */ - else - p.close(); - p = null; - } - } - if (string != null) { - string.close(); - string = null; - } - if (shared != null) { - if (shared.getMembers() == null || shared.getMembers().size() == 1) // last one - shared.close(); - else - shared.removeMember(this); - shared = null; - } - if (internalRecords != null) - internalRecords.clear(); - super.close(); - closed = true; - } - - protected void finalize() { - if (!closed) { - this.close(); - } - } - - /** - * Replaces a ptg in the active expression. Useful for replacing a ptgRef with a ptgError after a bad movement. - * - * @param thisptg - * @param ptgErr - */ - public void replacePtg(Ptg thisptg, Ptg ptgErr) { - ptgErr.setParentRec(this); - int idx = this.expression.indexOf(thisptg); - this.expression.remove(idx); - this.expression.insertElementAt(ptgErr, idx); - } - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Formula.kt b/src/main/java/io/starter/formats/XLS/Formula.kt new file mode 100644 index 0000000..aa50595 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Formula.kt @@ -0,0 +1,1257 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.ExcelTools +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.formulas.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.* + +/** + * FORMULA (0x406) describes a cell that contains a formula. + *

                  + * offset  name        size    contents
                  + * 0       rw          2       Row
                  + * 2       col         2       Column
                  + * 4       ixfe        2       Index to the XF record
                  + * 6       num         8       Current value of the formula
                  + * 14      grbit       2       Option Flags
                  + * 16      chn         4       (Reserved, must be zero) A field that specifies an application-specific cache of information. This cache exists for performance reasons only, and can be rebuilt based on information stored elsewhere in the file without affecting calculation results.
                  + * 20      cce         2       Parsed Expression length
                  + * 22      rgce        cce     Parsed Expression
                  +
                  * + * The grbit field contains the following flags: + *
                  + * byte   bits   mask   name           asserted if
                  + * 0      0      0x01   fAlwaysCalc    the result must not be cached
                  + * 1      0x02   fCalcOnLoad    the cached value is incorrect
                  + * 2      0x04   (Reserved)
                  + * 3      0x08   fShrFmla       this is a reference to a shared formula
                  + * 7-4    0xF0   (Unused)
                  + * 1      7-0    0xFF   (Unused)
                  +
                  * + * In most cases, formulas should have fAlwaysCalc asserted to ensure that + * correct values are displayed upon opening of the file in Excel. + */ +class Formula : XLSCellRecord() { + + private var cachedValue: Any? = null + private var expression: Stack<*>? = null + + + /** + * Whether the record data needs to be updated. + */ + private var dirty = false + + /** + * Whether this formula contains an indirect reference. + */ + private var containsIndirectFunction = false + + /** + * Whether this FORMULA record has an attached STRING record. + */ + private var haveStringRec = false + + /** + * Contains bitfield flags. + */ + private var grbit = FCALCONLOAD + + /** + * The attached STRING record, if one exists. + */ + /** + * Get the String that is attached to this formula (it has a result of a string..); + */ + var attatchedString: StringRec? = null + private set + + /** + * The target ShrFmla record, if this is a shared formula reference. + */ + var shared: Shrfmla? = null + + /** + * List of records attached to this one. + */ + private var internalRecords: MutableList<*>? = null + + /** + * true if it's sub-ptgs are defined in other workbooks and therefore unable to be resolved + */ + private var isExternalRef = false + + /** + * return the "Calculate Always" setting for this formula + * used for formulas that always need calculating such as TODAY + * + * @return + */ + /** + * set the "Calculate Always setting for this formula + * used for formulas that always need calculating such as TODAY + * + * @param fAlwaysCalc + */ + var calcAlways: Boolean + get() = grbit and FALWAYSCALC != 0 + set(fAlwaysCalc) = if (fAlwaysCalc) + grbit = grbit or FALWAYSCALC + else + grbit = grbit and FALWAYSCALC.inv().toShort() + + val typeName: String + get() = "formula" + + + /** + * Returns the Human-Readable String Representation of + * this Formula. + */ + val formulaString: String + get() { + populateExpression() + return if (!this.isArrayFormula) { + FormulaParser.getFormulaString(this) + } else "{" + FormulaParser.getFormulaString(this) + "}" + + } + + /** + * Returns an array of ptgs that represent any BiffRec ranges in the formula. + * Ranges can either be in the format "C5" or "Sheet1!C4:D9" + */ + val cellRangePtgs: Array + @Throws(FormulaNotFoundException::class) + get() = ExpressionParser.getCellRangePtgs(expression!!) + + /** + * Get the value of the formula as an integer. + * If the formula exceeds integer boundaries, or is a float with + * a non-zero mantissa throw an exception + * + * @see io.starter.formats.XLS.XLSRecord.getIntVal + */ + override// not back-compat return Integer.valueOf(new Long((long) tl).intValue()).intValue(); + // throw new NumberFormatException("Loss of precision converting " + tl + " to int."); + // return a zero for empties + // 20090514 KSC: bd.intValueExact(); is 1.6 compatible -- MAY CAUSE INFOTERIA REGRESSION ERROR + var intVal: Int + @Throws(RuntimeException::class) + get() { + val obx = calculateFormula() + try { + val tl = (obx as Double).toDouble() + if (tl > Integer.MAX_VALUE) { + throw NumberFormatException("getIntVal: Formula value is larger than the maximum java signed int size") + } + if (tl < Integer.MIN_VALUE) { + throw NumberFormatException("getIntVal: Formula value is smaller than the minimum java signed int size") + } + val db = obx.toDouble() + val ret = obx.toInt() + if (db - ret > 0 && DEBUGLEVEL > this.DEBUG_LOW) + Logger.logWarn("Loss of precision converting $tl to int.") + + return ret + } catch (e: ClassCastException) { + } + + var l: Long = 0 + var s = obx.toString() + if (s == "") s = "0" + try { + val t = "" + val bd = java.math.BigDecimal(s) + l = bd.toLong() + if (l > Integer.MAX_VALUE) { + throw NumberFormatException("Formula value is larger than the maximum java signed int size") + } + if (l < Integer.MIN_VALUE) { + throw NumberFormatException("Formula value is smaller than the minimum java signed int size") + } + return Integer.valueOf(l.toString()).toInt() + } catch (ne: NumberFormatException) { + throw NumberFormatException("getIntVal: Formula is a non-numeric value") + } catch (e: Exception) { + throw NumberFormatException("getIntVal: $e") + } + + } + set + + + override// return a zero for empties + val floatVal: Float + get() { + val obx = calculateFormula() + try { + if (obx is Float) { + return obx.toFloat() + } + } catch (e: Exception) { + Logger.logErr("Formula.getFloatVal failed for: $this", e) + } + + try { + var s = obx.toString() + if (s == "") s = "0" + return Float(s) + } catch (ex: NumberFormatException) { + return java.lang.Float.NaN + } catch (e: Exception) { + Logger.logWarn("Formula.getFloatVal() failed: $e") + } + + return java.lang.Float.NaN + } + + override var booleanVal: Boolean + get() { + val obx = calculateFormula() + try { + if (obx is Boolean) { + return obx.booleanValue() + } + } catch (e: Exception) { + Logger.logErr("getBooleanVal failed for: $this", e) + } + + try { + val s = obx.toString() + if (s.equals("true", ignoreCase = true) || s == "1") return true + + } catch (e: Exception) { + Logger.logWarn("getBooleanVal() failed: $e") + } + + return false + } + set + + override// return a zero for empties + val dblVal: Double + get() { + val obx = calculateFormula() + try { + if (obx is Double) { + return obx.toDouble() + } + } catch (e: Exception) { + Logger.logErr("Formula.getDblVal failed for: $this", e) + } + + var s = obx.toString() + if (s == "") s = "0" + try { + return Double(s) + } catch (ex: NumberFormatException) { + return java.lang.Double.NaN + } catch (e: Exception) { + Logger.logWarn("Formula.getDblVal() failed: $e") + } + + return java.lang.Double.NaN + } + + /** + * return the String representation of the current Formula value + */ + override// if null, return empty string + // TODO: set the string value of the attached string? + var stringVal: String? + get() { + val obx = calculateFormula() + try { + if (obx is Double) { + val d = obx.toDouble() + return if (!java.lang.Double.isNaN(d)) + Formula.getDoubleAsFormattedString(d) + else + "NaN" + } + } catch (e: Exception) { + Logger.logErr("Formula.getStringVal failed for: $this", e) + } + + return obx?.toString() ?: "" + + } + set(v) = throw CellTypeMismatchException("Attempting to set a string value on a formula") + + /** + * Returns whether this is a reference to a shared formula. + */ + /** + * Sets whether this is a reference to a shared formula. + */ + var isSharedFormula: Boolean + get() = grbit and FSHRFMLA != 0 + private set(isSharedFormula) = if (isSharedFormula) + grbit = grbit or FSHRFMLA + else + grbit = grbit and FSHRFMLA.inv().toShort() + + /** + * return truth of "this is an array formula" i.e. contains an Array sub-record + * + * @return + */ + val isArrayFormula: Boolean + get() = if (internalRecords != null && internalRecords!!.size > 0) internalRecords!![0] is Array else false + + /** + * fetches the internal Array record linked to this formula, if any, + * or null if not an array formula + * + * @return array record + * @see isArrayFormula + */ + /* if (expression.get(0) instanceof PtgExp) { + // if it's the child of a parent array formula, obtain it's Array record + // TODO: verify this is correct + finish + }*/ val array: Array? + get() { + try { + return internalRecords!![0] as Array + } catch (e: Exception) { + } + + return null + } + + /** + * OOXML-specific: set the range the Array references + * + * @param s + */ + var arrayRefs: String + get() { + if (internalRecords != null && internalRecords!!.size > 0) { + val o = internalRecords!![0] + return (o as Array).arrayRefs + } + return "" + } + set(s) { + if (internalRecords != null && internalRecords!!.size > 0) { + val o = internalRecords!![0] + if (o is Array) { + val rc = ExcelTools.getRangeRowCol(s) + o.firstRow = rc[0] + o.firstCol = rc[1] + o.lastRow = rc[2] + o.lastCol = rc[3] + } + } + } + + /** + * clear out object references in prep for closing workbook + */ + private var closed = false + + /** + * Default constructor + */ + init { + opcode = XLSConstants.FORMULA + isValueForCell = true + isFormula = true + } + + /** + * Parses the record bytes. + * This method only needs to be called when the record is being constructed + * from bytes. Calling it on a programmatically created formula is + * unnecessary and will probably throw an exception. + */ + override fun init() { + // Prevent misuse of init + if (expression != null) + throw IllegalStateException( + "can't init a formula created from a string") + + super.init() + if ((data = getData()) == null) + throw IllegalStateException( + "can't init a formula without record bytes") + super.initRowCol() + ixfe = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()).toInt() + grbit = ByteTools.readShort(this.getByteAt(14).toInt(), this.getByteAt(15).toInt()) + + // get the cached value bytes from the record + val currVal = this.getBytesAt(6, 8) + + // Is this a non-numeric value? + if (currVal!![6] == 0xFF.toByte() && currVal[7] == 0xFF.toByte()) { + // String value + if (currVal[0] == 0x00.toByte()) { + haveStringRec = true // bytes 1-5 are not used + // Normally cachedValue will be set by StringRec's init. + // Setting cachedValue null forces calculation in the rare + // event that the STRING record is missing or fails to init. + cachedValue = null + } else if (currVal[0] == 0x03.toByte()) { + // There is no attached STRING record. Set cachedValue directly. + cachedValue = "" + } else if (currVal[0] == 0x01.toByte()) { + cachedValue = java.lang.Boolean.valueOf(currVal[2] != 0x00.toByte()) + } else if (currVal[0] == 0x02.toByte()) { + cachedValue = CalculationException(currVal[2]) + } else { + cachedValue = null + }// Unknown value type + // Error value + // Boolean value + // Empty string value + + } else { + // do not cache NaN stored bytes + val dbv = ByteTools.eightBytetoLEDouble(currVal) + if (!java.lang.Double.isNaN(dbv)) { + cachedValue = dbv + } + } + + if (this.sheet == null) + this.setSheet(this.wkbook!!.lastbound) + + if (DEBUGLEVEL > 10) { + try { + Logger.logInfo("INFO: Formula " + this.cellAddress + this.formulaString) + } catch (e: Exception) { + Logger.logInfo("Debug output of Formula failed: $e") + } + + } + + // The expression needs to be parsed on input in order to add it to + // the reference tracker + //TODO: Add a no calculation / read only mode without ref tracking + this.populateExpression() + // Perform some special handling for formulas with indirect references + if (containsIndirectFunction) this.registerIndirectFunction() + this.dirty = false + } + + /** + * Performs cleanup required before changing or removing this Formula. + * This nulls out the expression, so save a copy if you need it. + * Possible sub-records associated with Formula: array, shared string and/or shared formula + */ + private fun clearExpression() { + if (expression == null) + return + + if (this.isArrayFormula) { + val a = this.array + if (a != null) + this.sheet!!.removeRecFromVec(a) + } + if (this.hasAttachedString()) { // remove that too + if (attatchedString != null) + this.sheet!!.removeRecFromVec(attatchedString!!) + attatchedString = null + } + + if (isSharedFormula) { + shared!!.removeMember(this) + shared = null + isSharedFormula = false + } + + val iter = expression!!.iterator() + while (iter.hasNext()) { + val ptg = iter.next() as Ptg + if (ptg is PtgRef) ptg.removeFromRefTracker() + } + + expression = null + } + + /** + * for creating a formula on the fly. This is used currently by FormulaParser + * to create a formula. The location and XF fields are filled in by boundsheet.add(); + */ + fun setExpression(exp: Stack<*>) { + if (expression != null) + clearExpression() + expression = exp + updateRecord() + } + + /** + * get the expression stack + * + * @return + */ + fun getExpression(): Stack<*>? { + populateExpression() + return expression + } + + /** + * store formula-associated records: one or more of: + * ShrFmla + * StringRec + * Array + * + * @param b + */ + fun addInternalRecord(b: BiffRec) { + if (internalRecords == null) internalRecords = ArrayList(3) + + if (b is Shrfmla) + internalRecords!!.add(0, b) + else if (b is StringRec) { + if (!haveStringRec || attatchedString == null) {// should ONLY have 1 StringRec + internalRecords!!.add(b) + haveStringRec = true + attatchedString = b + cachedValue = attatchedString!!.stringVal + }/* TODO: this is such a rare occurrence - due possibly to OUR processing - keep if and wa + else + Logger.logErr("Formula.init: Out of Spec Formula Encountered (Multiple String Recs Encountered)- Ignoring"); + */ + } else + // array formula + internalRecords!!.add(b) + + } + + fun removeInternalRecord(b: BiffRec) { + if (internalRecords == null) return + internalRecords!!.remove(b) + } + + fun getInternalRecords(): List<*> { + return if (internalRecords == null) emptyList() else internalRecords + } + + + fun hasAttachedString(): Boolean { + return haveStringRec + } + + /** + * set if this formula refers to External References + * (references defined in other workbooks) + * + * @param isExternalRef + */ + fun setIsExternalRef(isExternalRef: Boolean) { + this.isExternalRef = isExternalRef + } + + /** + * Adds an indirect function to the list of functions to be evaluated post load + */ + fun registerIndirectFunction() { + this.workBook!!.addIndirectFormula(this) + } + + /** + * If the method contains an indirect function then + * register those ptgs into the reference tracker. + * + * + * In order to do this it is necessary to calculate the formula + * to retrieve the Ptg's + */ + fun calculateIndirectFunction() { + this.clearCachedValue() + try { + this.calculateFormula() + } catch (e: FunctionNotSupportedException) { + // If we do not support the function, calculation will throw a FNE anyway, so no need for logging + } catch (e: Exception) { + // problematic here. As the calculation is happening on parse we dont really + // want to throw an exception and crap out on the book loading + // but the client should be informed in some way. Also a generic exception is caught + // because our code does not bubble a calc exception up. + Logger.logErr("Error registering lookup for INDIRECT() function at cell: " + this.cellAddress + " : " + e) + } + + } + + /** + * Populates the expression in the formula. This has been moved out of init for performance reasons. + * The idea is that the processing is offloaded as a JIT for calculation/value retrieval. + */ + //TODO: refactor external references and make private + internal fun populateExpression() { + if (expression != null || data == null) return + + try { + val length = ByteTools.readShort( + this.getByteAt(20).toInt(), this.getByteAt(21).toInt()) + + if (length + 22 > data!!.size) + throw Exception( + "cce longer than record") + + expression = ExpressionParser.parseExpression( + this.getBytesAt(22, reclen - 22)!!, this, length.toInt()) + + // If this is a shared formula reference, do some special init + if (isSharedFormula) + initSharedFormula(null) + } catch (e: Exception) { + if (DEBUGLEVEL > -10) + Logger.logInfo("Formula.init: Parsing Formula failed: $e") + } + + } + + /** + * Performs special initialization for shared formula references. + * + * @param target the target `SHRFMLA` record. If this is + * `null` it will be retrieved from the cell pointed to + * by the `PtgExp`. + * @throws FormulaNotFoundException if the target shared formula is missing + * @throws IllegalArgumentException if this is not a shared formula member + */ + @Throws(FormulaNotFoundException::class) + internal fun initSharedFormula(target: Shrfmla?) { + if (!isSharedFormula) { + isSharedFormula = true + } + + // If we're already done, silently do nothing + if (shared != null) + return + + // If this is an instantiation instead of a reference + if (expression!!.size != 1 || expression!![0] !is PtgExp) { + //TODO: find which ShrFmla this is and convert to reference + // For now, just clear fShrFmla + isSharedFormula = false + return + } + + val pointer = expression!![0] as PtgExp + + if (target != null) { + shared = target + } else + try { + shared = (sheet!!.getCell( + pointer.rwFirst, pointer.colFirst) as Formula).shared // find shared cell linked to host/first formula cell in shared formula series + if (shared == null) + throw Exception() + } catch (e: Exception) { + // If this is the host cell, fail silently. This method will be + // re-called by the ShrFmla record's init method. + if (this.cellAddress == pointer.referent) + return + + // Otherwise, complain and clear fShrFmla + throw FormulaNotFoundException( + "FORMULA at " + this.cellAddress + + " refers to missing SHRFMLA at " + + pointer.referent) + } + + //Shared Formula Init Performance Changes: do not instantiate until calculate + // expression = shared.instantiate( pointer ); + shared!!.addMember(this) + + if (shared!!.containsIndirectFunction) registerIndirectFunction() + } + + /** + * Converts a shared formula reference into a normal formula. + * + * @throws IllegalStateException if this is not a shared formula member + */ + fun convertSharedFormula() { + if (!isSharedFormula) + throw IllegalStateException( + "not a shared formula reference") + + shared = null + isSharedFormula = false + } + + + /** + * Returns the ptg that matches the string location sent to it. + * this can either be in the format "C5" or a range, such as "C4:D9" + */ + @Throws(FormulaNotFoundException::class) + fun getPtgsByLocation(loc: String): List<*> { + var loc = loc + populateExpression() + if (loc.indexOf("!") == -1) + loc = this.sheet!!.sheetName + "!" + loc + + return ExpressionParser.getPtgsByLocation(loc, expression!!) + } + + /** + * locks the Ptg at the specified location + */ + + fun setLocationPolicy(loc: String, l: Int): Boolean { + populateExpression() + try { + val dx = this.getPtgsByLocation(loc) + val lx = dx.iterator() + while (lx.hasNext()) { + val d = lx.next() as Ptg + d.locationPolicy = l + if (l == Ptg.PTG_LOCATION_POLICY_TRACK) + // init the tracker cell right away + d.initTrackerCell() + } + return true + } catch (e: FormulaNotFoundException) { + Logger.logInfo("locking Formula Location failed:$loc: $e") + return false + } + + } + + /** + * Called to indicate that the parsed expression has changed. + * Does nothing if the expression doesn't exist yet. + */ + fun updateRecord() { + dirty = true + if (this.data == null) + setData(ByteArray(6)) // happens when newly init'ing a formula + + if (cachedValue is String && "" != cachedValue && !isErrorValue(cachedValue as String?)) {// if it's a string and not an error string + if (!haveStringRec || attatchedString == null) { + // this.addInternalRecord( new StringRec( (String)cachedValue ) ); // will be added in workbook.addRecord + attatchedString = StringRec(cachedValue as String?) + attatchedString!!.setSheet(sheet) + attatchedString!!.rowNumber = rowNumber + attatchedString!!.setCol(colNumber) + workBook!!.setLastFormula(this) // for addRecord, sets appropriate formula internal record + workBook!!.addRecord(attatchedString!!, true) + haveStringRec = true + } else { + attatchedString!!.stringVal = cachedValue as String? + } + } else if (attatchedString != null) { + attatchedString!!.remove(false) + removeInternalRecord(attatchedString) + haveStringRec = false + } + } + + /** + * Updates the record data if necessary to prepare for streaming. + */ + override fun preStream() { + // If the record doesn't need to be updated, do nothing + if (!dirty && !isSharedFormula && cachedValue != null && + workBook!!.calcMode != WorkBook.CALCULATE_EXPLICIT) + return + + + // If the formula needs calculation, do so + try { + if (cachedValue == null) calculateFormula() + } catch (e: FunctionNotSupportedException) { + // Fall through to null cachedValue handling below + } + + // Sometimes we need to write a value other than the real cached value + var writeValue = cachedValue + + // Handle formulas that can't be calculated for whatever reason + if (cachedValue == null) { + grbit = grbit or FCALCONLOAD + if (writeValue == null) writeValue = java.lang.Double.NaN + } + + // Handle CALCULATE_EXPLICIT mode + if (workBook!!.calcMode == WorkBook.CALCULATE_EXPLICIT) { + grbit = grbit or FCALCONLOAD + } + + // If this is a shared formula, write a PtgExp + var expr: Stack<*>? = expression + if (isSharedFormula) { /* ONLY need to do this if shared formula member(s) have changed- + a better choice is to trap and change in respective method */ + expr = Stack() + expr!!.add(shared!!.pointer) + } + + // Fetch the Ptg data and calculate the expression size + val ptgdata = arrayOfNulls(expr!!.size) + var rgb: ByteArray? = null + var cce: Short = 0 + var rgblen: Short = 0 + for (idx in expr.indices) { + val ptg = expr[idx] as Ptg + ptgdata[idx] = ptg.record + cce += ptgdata[idx].size.toShort() + + if (ptg is PtgArray) { + val extra = ptg.postRecord + rgb = ByteTools.append(extra, rgb) + rgblen += extra!!.size.toShort() + } else if (ptg is PtgMemArea) { // has PtgExtraMem structure appended + val extra = ptg.postRecord + rgb = ByteTools.append(extra, rgb) + rgblen += extra.size.toShort() + } + } + + val newdata = ByteArray(22 + cce.toInt() + rgblen.toInt()) + // Cell Header (row, col, ixfe) + System.arraycopy(data!!, 0, newdata, 0, 6) + + // Cached Value (num) + val value: ByteArray + if (writeValue is Number) { + //TODO: Check infinity, NaN, etc. + value = ByteTools.toBEByteArray( + writeValue.toDouble()) + } else { + value = ByteArray(8) + // byte 0 specifies the marker type + value[1] = 0x00.toByte() + // byte 2 is used by bool and boolerr + value[3] = 0x00.toByte() + value[4] = 0x00.toByte() + value[5] = 0x00.toByte() + value[6] = 0xFF.toByte() + value[7] = 0xFF.toByte() + + if (writeValue is String) { + if (!isErrorValue(writeValue as String?)) { + value[2] = 0x00.toByte() + val sval = writeValue as String? + if (sval == "" || attatchedString == null) { // the latter can occur when input from XLSX; a cachedvalue is set without an associated StringRec + value[0] = 0x03.toByte() // means empty + } else { + value[0] = 0x00.toByte() + attatchedString!!.stringVal = sval + } + } else { + value[0] = 0x02.toByte() // error code + value[2] = CalculationException.getErrorCode(writeValue as String?) + } + } else if (writeValue is Boolean) { + value[0] = 0x01.toByte() + value[2] = if (writeValue.booleanValue()) + 0x01.toByte() + else + 0x00.toByte() + } else if (writeValue is CalculationException) { + value[0] = 0x02.toByte() + value[2] = writeValue.errorCode + } else + throw Error("unknown value type " + (writeValue?.javaClass?.getName() ?: "null")) + } + System.arraycopy(value, 0, newdata, 6, 8) + + // Bit Flags (grbit) + System.arraycopy(ByteTools.shortToLEBytes(grbit), 0, newdata, 14, 2) + + // chn - reserved zero + Arrays.fill(newdata, 16, 19, 0x00.toByte()) + + // Expression Length (cce) + System.arraycopy(ByteTools.shortToLEBytes(cce), 0, newdata, 20, 2) + + // Expression Ptgs (rgce) + var offset = 22 + for (idx in ptgdata.indices) { + System.arraycopy( + ptgdata[idx], 0, newdata, offset, ptgdata[idx].size) + offset += ptgdata[idx].size + } + + // Expression Extra Data (rgb) + if (rgblen > 0) System.arraycopy(rgb!!, 0, newdata, offset, rgblen.toInt()) + setData(newdata) + dirty = false + } + + override fun clone(): Any? { + // Make the record bytes available to XLSRecord.clone + preStream() + return super.clone() + } + + /** + * Calculates the formula honoring calculation mode. + * + * @throws CalculationException + */ + @Throws(FunctionNotSupportedException::class) + fun calculateFormula(): Any? { + // if this is calc explicit, we ALWAYS use cache + if (workBook!!.calcMode == WorkBook.CALCULATE_EXPLICIT) + return cachedValue + // TODO: IF ALREADY RECALCED DONT SET TO null -- need flag? + if (workBook!!.calcMode == WorkBook.CALCULATE_ALWAYS) { + if (!isExternalRef) + // if it's an external reference DONT CLEAR CACHE + cachedValue = null // force calc + else + return cachedValue + } + return calculate() + + } + + /** + * Calculate the formula if necessary. This accessor resets the recurse count on the + * formula. Excel standard is to allow 100 recursions before throwing a circular reference. + * + * @throws CalculationException + */ + fun calculate(): Any? { + val depth = recurseCount.get() + + try { + recurseCount.set(depth!! + 1) + if (depth > WorkBookHandle.RECURSION_LEVELS_ALLOWED) { + Logger.logWarn("Recursion levels reached in calculating formula " + + this.cellAddressWithSheet + + ". Possible circular reference. Recursion levels can be set through WorkBookHandle.setFormulaRecursionLevels") + cachedValue = CalculationException( + CalculationException.CIR_ERR) + return cachedValue + } + return this.calculateInternal() + } finally { + recurseCount.set(depth) + } + } + + + /** + * Calculates the formula if necessary regardless of calculation mode. + * If there is a cached value it will be returned. Otherwise, the formula + * will be calculated and the result will be cached and returned. If you + * need to force calculation call [.clearCachedValue] first. + */ + private fun calculateInternal(): Any? { + // If we have a cached value, return it instead of calculating + if (cachedValue != null) return cachedValue + populateExpression() + try { + cachedValue = FormulaCalculator.calculateFormula(this.expression!!) + } catch (e: StackOverflowError) { + Logger.logWarn("Stack overflow while calculating " + + this.cellAddressWithSheet + + ". Possible circular reference.") + cachedValue = CalculationException( + CalculationException.CIR_ERR) + return cachedValue + } + + if (cachedValue == null) + throw FunctionNotSupportedException("Unable to calculate Formula " + this.formulaString + " at: " + this.sheet!!.sheetName + "!" + this.cellAddress) + + if (cachedValue!!.toString() == "#CIR_ERR!") { + return CircularReferenceException(CalculationException.CIR_ERR) + } + if (cachedValue!!.toString().length < 1) { + // do something...? + } else if (cachedValue!!.toString()[0] == '{') { + // it's an array, we need to find the particular value that we want. + // parse all array strings into rows, cols + var arrStr: String = cachedValue as String? + arrStr = arrStr.substring(1, arrStr.length - 1) + var rows: Array? = null + var cols: Array>? = null + // split rows + rows = arrStr.split(";".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + cols = arrayOfNulls>(rows!!.size) + for (i in rows!!.indices) { + cols[i] = rows!![i].split(",".toRegex()).toTypedArray() // include empty strings + } + val pxp: PtgExp + var rowA: Int + var colA: Int + val p: Ptg + try { + pxp = expression!!.elementAt(0) as PtgExp + rowA = this.rowNumber - pxp.rwFirst + colA = this.colNumber - pxp.colFirst + // now, if it's a 1-dimensional array e.g {1,2,3,4,5}, nr=1, nc= 5 + // if formula address is traversing rows then switch + if (rows!!.size == 1 && rowA > 0 && colA == 0) { + colA = rowA + rowA = 0 + } + } catch (e: ClassCastException) { + // this is when we just calc'd a formula and have no exp reference. + // assume it is the location of the formula + // could be incorrect, may need to revisit + rowA = 0 + colA = 0 + } + + cachedValue = cols!![rowA][colA] + // try to cast + try { + cachedValue = Double((cachedValue as String?)!!) + } catch (e: Exception) { + // let it go + } + + if (DEBUGLEVEL > 10) Logger.log(cachedValue!!) + } + if (this.attatchedString != null) { + this.attatchedString!!.stringVal = cachedValue.toString() + } + + updateRecord() + return cachedValue + } + + override fun toString(): String { + populateExpression() + return super.toString() + //return this.worksheet.getSheetName() + "!" + this.getCellAddress() + ":" + this.getStringVal(); + } + + /** + * Set the cached value of this formula, + * in cases where the formula is null, set the cache to null, + * as well as updating the attached string to null in order to force + * recalc + * + * @see io.starter.formats.XLS.XLSRecord.setCachedValue + */ + fun setCachedValue(newValue: Any?) { + if (newValue == null) { + this.clearCachedValue() + } else { + cachedValue = newValue // TODO: need to check/validate StringRec ???? + } + } + + + /** + * Set the cached value of this formula, + * in cases where the formula is null, set the cache to null, + * as welll as updating the attached string to null in order to force + * recalc + * + * @see io.starter.formats.XLS.XLSRecord.setCachedValue + */ + fun clearCachedValue() { + cachedValue = null + haveStringRec = false + // this.updateRecord(); no need; will be updated after recalc, which will automatically happen on write + } + + /** + * Set if the formula contains Indirect() + * + * @param containsIndirectFunction The containsIndirectFunction to set. + */ + fun setContainsIndirectFunction(containsIndirectFunction: Boolean) { + this.containsIndirectFunction = containsIndirectFunction + } + + /** + * Performs cleanup needed before removing the formula cell from the + * work sheet. The formula will not behave correctly once this is called. + */ + fun destroy() { + clearExpression() + } + + override fun close() { + if (expression != null) { + while (!expression!!.isEmpty()) { + var p: GenericPtg? = expression!!.pop() as GenericPtg + if (p is PtgRef) + p.close() + else + p!!.close()/* else if (p instanceof PtgExp ) { + Ptg[] ptgs= ((PtgExp)p).getConvertedExpression(); + for (int i= 0; i < ptgs.length; i++) { + if (ptgs[i] instanceof PtgRef) + ((PtgRef) ptgs[i]).close(); + else + ((GenericPtg)ptgs[i]).close(); + } + } */ + p = null + } + } + if (attatchedString != null) { + attatchedString!!.close() + attatchedString = null + } + if (shared != null) { + if (shared!!.members == null || shared!!.members!!.size == 1) + // last one + shared!!.close() + else + shared!!.removeMember(this) + shared = null + } + if (internalRecords != null) + internalRecords!!.clear() + super.close() + closed = true + } + + protected fun finalize() { + if (!closed) { + this.close() + } + } + + /** + * Replaces a ptg in the active expression. Useful for replacing a ptgRef with a ptgError after a bad movement. + * + * @param thisptg + * @param ptgErr + */ + fun replacePtg(thisptg: Ptg, ptgErr: Ptg) { + ptgErr.parentRec = this + val idx = this.expression!!.indexOf(thisptg) + this.expression!!.removeAt(idx) + this.expression!!.insertElementAt(ptgErr, idx) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 7563301825566021680L + /** + * Mask for the fAlwaysCalc grbit flag. + */ + private val FALWAYSCALC: Short = 0x01 + /** + * Mask for the fCalcOnLoad grbit flag. + */ + private val FCALCONLOAD: Short = 0x02 + /** + * Mask for the fShrFmla grbit flag. + */ + private val FSHRFMLA: Short = 0x08 + + + /** + * Returns the correct string representation of a double for excel. + * + * + * Note this is for the standards that were determined with excel and io.starter.OpenXLS + * + * @param num + * @return + */ + private fun getDoubleAsFormattedString(theNum: Double): String { + return ExcelTools.getNumberAsString(theNum) + } + + private val recurseCount = object : ThreadLocal() { + override fun initialValue(): Int { + return 0 + } + } + + /** + * increment each PtgRef in expression stack via row or column based + * on rowInc or colInc values + * Used in OOXML parsing + */ + fun incrementSharedFormula(origStack: java.util.Stack<*>, rowInc: Int, colInc: Int, range: IntArray) { + // traverse thru ptg's, incrementing row reference + //java.util.Stack origStack= form.getExpression(); Don't do "in place" as alters original expression + //Logger.logInfo("Before Inc: " + this.getFormulaString()); + for (i in origStack.indices) { + val p = origStack.elementAt(i) as Ptg + try { + val s = p.location + if (p.isReference) { + if (!((p as PtgRef).isWholeRow && colInc != 0 || p.isWholeCol && rowInc != 0)) { + if (p !is PtgArea) { + val bRelRefs = booleanArrayOf(p.isRowRel, p.isColRel) + val rc = ExcelTools.getRowColFromString(s) + if (bRelRefs[0]) + rc[0] += rowInc + if (bRelRefs[1]) + rc[1] += colInc + val pr = PtgRef() + pr.parentRec = p.parentRec + pr.useReferenceTracker = false // 20090827 KSC: don't petform expensive calcs on init + removereferenceTracker blows out cached value + pr.location = ExcelTools.formatLocation(rc, bRelRefs[0], bRelRefs[1]) + pr.useReferenceTracker = true + origStack.set(i, pr) + } else { + val sh = ExcelTools.stripSheetNameFromRange(s)[0] + val rc = ExcelTools.getRangeRowCol(s) + val bRelRefs = booleanArrayOf(p.firstPtg!!.isRowRel, p.lastPtg!!.isRowRel, p.firstPtg!!.isColRel, p.lastPtg!!.isColRel) + if (bRelRefs[0]) + rc[0] += rowInc + if (bRelRefs[1]) + rc[2] += rowInc + if (bRelRefs[2]) + rc[1] += colInc + if (bRelRefs[3]) + rc[3] += colInc + val pa = PtgArea(false) + pa.parentRec = p.parentRec + if (sh != null) + pa.location = sh + "!" + ExcelTools.formatRangeRowCol(rc, bRelRefs) + else + pa.location = ExcelTools.formatRangeRowCol(rc, bRelRefs) + pa.useReferenceTracker = true + origStack.set(i, pa) + } + } + } + } catch (ex: Exception) { + Logger.logErr("Formula.incrementSharedFormula: $ex") + } + + } + } + + /** + * returns true if the value s is one of the Excel defined Error Strings + * + * @param s + * @return + */ + fun isErrorValue(s: String?): Boolean { + return if (s == null) false else Collections.binarySearch(Arrays.asList("#DIV/0!", "#N/A", "#NAME?", "#NULL!", "#NUM!", "#REF!", "#VALUE!"), s.trim { it <= ' ' }) > -1 + } + } + + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/Legendxn.java b/src/main/java/io/starter/formats/XLS/FormulaNotFoundException.kt similarity index 70% rename from src/main/java/io/starter/formats/XLS/charts/Legendxn.java rename to src/main/java/io/starter/formats/XLS/FormulaNotFoundException.kt index 1312c07..cb9709a 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Legendxn.java +++ b/src/main/java/io/starter/formats/XLS/FormulaNotFoundException.kt @@ -20,18 +20,22 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS /** - * Legendxn: Legend Exception (0x1043) + * No Formula Found. */ -public class Legendxn extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7960153045048996505L; +class FormulaNotFoundException : java.lang.Exception { - public void init() { - super.init(); + constructor(message: String) : super(message) {} + + constructor() : super() {} + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8810290427677467459L } -} + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/FunctionNotSupportedException.java b/src/main/java/io/starter/formats/XLS/FunctionNotSupportedException.java deleted file mode 100644 index 725985c..0000000 --- a/src/main/java/io/starter/formats/XLS/FunctionNotSupportedException.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.formats.XLS.formulas.FunctionConstants; - -import java.util.Locale; - -/** - * Formula function is not supported for calculation. - * - * @see Formula - */ - -public final class FunctionNotSupportedException extends java.lang.RuntimeException { - - - private static final long serialVersionUID = 3569219212252117988L; - String functionName = ""; - - public FunctionNotSupportedException(String n) { - super(); - functionName = n; - } - - public String getMessage() { - // This method is derived from class java.lang.Throwable - // to do: code goes here - return this.toString(); - } - - public String toString() { - // if it is a formula calc that is failing -// if (functionName.length() > 4){ - if (true) { // 20081203 KSC: functionName is offending function - return "Function Not Supported: " + functionName; - } - int fID = 0; - String f = "Unknown Formula"; - try { - fID = Integer.parseInt(functionName, 16); // hex - if (Locale.JAPAN.equals(Locale.getDefault())) { - f = FunctionConstants.getJFunctionString((short) fID); - } - if (f.equals("Unknown Formula")) { - f = FunctionConstants.getFunctionString((short) fID); - } - if (f.length() == 0) { - if (fID == FunctionConstants.xlfADDIN) - f = "AddIn Formula"; - else - f = "Unknown Formula"; - } else { - f += ")"; // add ending paren - } - } catch (Exception e) { - } - - return "Function: " + f + " " + functionName + " is not implemented."; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/FunctionNotSupportedException.kt b/src/main/java/io/starter/formats/XLS/FunctionNotSupportedException.kt new file mode 100644 index 0000000..f894fdb --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/FunctionNotSupportedException.kt @@ -0,0 +1,83 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.formats.XLS.formulas.FunctionConstants + +import java.util.Locale + +/** + * **Formula function is not supported for calculation.** + * + * @see Formula + */ + +class FunctionNotSupportedException(n: String) : java.lang.RuntimeException() { + internal var functionName = "" + + init { + functionName = n + } + + override fun getMessage(): String { + // This method is derived from class java.lang.Throwable + // to do: code goes here + return this.toString() + } + + override fun toString(): String { + // if it is a formula calc that is failing + // if (functionName.length() > 4){ + if (true) { // 20081203 KSC: functionName is offending function + return "Function Not Supported: $functionName" + } + var fID = 0 + var f: String? = "Unknown Formula" + try { + fID = Integer.parseInt(functionName, 16) // hex + if (Locale.JAPAN == Locale.getDefault()) { + f = FunctionConstants.getJFunctionString(fID.toShort()) + } + if (f == "Unknown Formula") { + f = FunctionConstants.getFunctionString(fID.toShort()) + } + if (f!!.length == 0) { + if (fID == FunctionConstants.xlfADDIN) + f = "AddIn Formula" + else + f = "Unknown Formula" + } else { + f += ")" // add ending paren + } + } catch (e: Exception) { + } + + return "Function: $f $functionName is not implemented." + } + + companion object { + + + private val serialVersionUID = 3569219212252117988L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Guts.java b/src/main/java/io/starter/formats/XLS/Guts.java deleted file mode 100644 index 63897b7..0000000 --- a/src/main/java/io/starter/formats/XLS/Guts.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - - -/** - * GUTS: Size of Row and Column Gutters
                  - *

                  - * This record stores information about gutter settings - *

                  - * offset  name            size    contents
                  - * ---
                  - * 4		dxRwGut			2		Size of the row gutter
                  - * 6		dyColGut		2		Size of the Col gutter
                  - * 8		iLevelRwMac		2		Maximum outline level for row
                  - * 10		iLevelColMac	2		Maximum outline level for col
                  - *
                  - * 

                  - **/ -public final class Guts extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2815489536116897500L; - private short dxRwGut; - private short dyColGut; - private short iLevelRwMac; - private short iLevelColMac; - - public void init() { - super.init(); - dxRwGut = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - dyColGut = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - iLevelRwMac = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - iLevelColMac = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - - if (DEBUGLEVEL > 5) - Logger.logInfo( - "INFO: Guts settings: dxRwGut:" - + dxRwGut - + " dyColGut:" - + dyColGut - + " iLevelRwMac:" - + iLevelRwMac - + " iLevelColMac:" - + iLevelColMac); - } - - public void setRowGutterSize(int i) { - dxRwGut = (short) i; - updateRecBody(); - } - - public int getRowGutterSize() { - return (int) dxRwGut; - } - - public void setColGutterSize(int i) { - dyColGut = (short) i; - updateRecBody(); - } - - public int getColGutterSize() { - return (int) dyColGut; - } - - public void setMaxRowLevel(int i) { - iLevelRwMac = (short) i; - updateRecBody(); - } - - public int getMaxRowLevel() { - return (int) iLevelRwMac; - } - - public void setMaxColLevel(int i) { - iLevelColMac = (short) i; - updateRecBody(); - } - - public int getMaxColLevel() { - return (int) iLevelColMac; - } - - private void updateRecBody() { - byte[] newbytes = ByteTools.shortToLEBytes(dxRwGut); - newbytes = ByteTools.append(ByteTools.shortToLEBytes(dyColGut), newbytes); - newbytes = ByteTools.append(ByteTools.shortToLEBytes(iLevelRwMac), newbytes); - newbytes = ByteTools.append(ByteTools.shortToLEBytes(iLevelColMac), newbytes); - this.setData(newbytes); - - } -} diff --git a/src/main/java/io/starter/formats/XLS/Guts.kt b/src/main/java/io/starter/formats/XLS/Guts.kt new file mode 100644 index 0000000..7f3bc25 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Guts.kt @@ -0,0 +1,113 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * **GUTS: Size of Row and Column Gutters**

                  + * + * + * This record stores information about gutter settings + * + *
                  + * offset  name            size    contents
                  + * ---
                  + * 4		dxRwGut			2		Size of the row gutter
                  + * 6		dyColGut		2		Size of the Col gutter
                  + * 8		iLevelRwMac		2		Maximum outline level for row
                  + * 10		iLevelColMac	2		Maximum outline level for col
                  + *
                  +
                  * + */ +class Guts : io.starter.formats.XLS.XLSRecord() { + private var dxRwGut: Short = 0 + private var dyColGut: Short = 0 + private var iLevelRwMac: Short = 0 + private var iLevelColMac: Short = 0 + + var rowGutterSize: Int + get() = dxRwGut.toInt() + set(i) { + dxRwGut = i.toShort() + updateRecBody() + } + + var colGutterSize: Int + get() = dyColGut.toInt() + set(i) { + dyColGut = i.toShort() + updateRecBody() + } + + var maxRowLevel: Int + get() = iLevelRwMac.toInt() + set(i) { + iLevelRwMac = i.toShort() + updateRecBody() + } + + var maxColLevel: Int + get() = iLevelColMac.toInt() + set(i) { + iLevelColMac = i.toShort() + updateRecBody() + } + + override fun init() { + super.init() + dxRwGut = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + dyColGut = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + iLevelRwMac = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + iLevelColMac = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + + if (DEBUGLEVEL > 5) + Logger.logInfo( + "INFO: Guts settings: dxRwGut:" + + dxRwGut + + " dyColGut:" + + dyColGut + + " iLevelRwMac:" + + iLevelRwMac + + " iLevelColMac:" + + iLevelColMac) + } + + private fun updateRecBody() { + var newbytes = ByteTools.shortToLEBytes(dxRwGut) + newbytes = ByteTools.append(ByteTools.shortToLEBytes(dyColGut), newbytes) + newbytes = ByteTools.append(ByteTools.shortToLEBytes(iLevelRwMac), newbytes) + newbytes = ByteTools.append(ByteTools.shortToLEBytes(iLevelColMac), newbytes) + this.setData(newbytes) + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2815489536116897500L + } +} diff --git a/src/main/java/io/starter/formats/XLS/HCenter.java b/src/main/java/io/starter/formats/XLS/HCenter.kt similarity index 62% rename from src/main/java/io/starter/formats/XLS/HCenter.java rename to src/main/java/io/starter/formats/XLS/HCenter.kt index 71decbf..c169f4f 100644 --- a/src/main/java/io/starter/formats/XLS/HCenter.java +++ b/src/main/java/io/starter/formats/XLS/HCenter.kt @@ -20,31 +20,31 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * Record specifying whether the sheet is to be centered horizontally * when printed. */ -public class HCenter - extends XLSRecord { - private static final long serialVersionUID = -3649192673573344145L; +class HCenter : XLSRecord() { - public void init() { - super.init(); - } + var isHCenter: Boolean + get() = getData()!![0] and 0x01 == 0x01 + set(center) = if (center) + getData()[0] = getData()[0] or 0x01 + else + getData()[0] = getData()[0] and 0x01.inv().toByte() - public void setSheet(Sheet sheet) { - super.setSheet(sheet); - ((Boundsheet) sheet).addPrintRec(this); + override fun init() { + super.init() } - public boolean isHCenter() { - return (getData()[0] & 0x01) == 0x01; + override fun setSheet(sheet: Sheet?) { + super.setSheet(sheet) + (sheet as Boundsheet).addPrintRec(this) } - public void setHCenter(boolean center) { - if (center) getData()[0] |= 0x01; - else getData()[0] &= ~0x01; + companion object { + private val serialVersionUID = -3649192673573344145L } } diff --git a/src/main/java/io/starter/formats/XLS/Headerrec.java b/src/main/java/io/starter/formats/XLS/Headerrec.java deleted file mode 100644 index 59700c9..0000000 --- a/src/main/java/io/starter/formats/XLS/Headerrec.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - - -/** - * Headerrec: Print Header on Each Page (14h)
                  - *

                  - * Headerrec describes the header printed on every page - * - *

                  - * offset  name            size    contents
                  - * ---
                  - * 4       cch             1       Length of the Header String
                  - * 5       rgch            var     Header String
                  - *
                  - * 

                  - **/ -public final class Headerrec extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8302043395108298631L; - int cch = -1; - String rgch = ""; - boolean DEBUG = false; - - public void setSheet(Sheet bs) { - super.setSheet(bs); - bs.setHeader(this); - } - - /** - * set the Header text - * The key here is that we need to construct an excel formatted unicode string, - *

                  - * Yes, this will probably be an issue in Japan some day.... - */ - public void setHeaderText(String t) { - try { - if (ByteTools.isUnicode(t)) { - byte[] bts = t.getBytes(UNICODEENCODING); - cch = bts.length / 2; - byte[] newbytes = new byte[cch + 3]; - byte[] cchx = io.starter.toolkit.ByteTools.shortToLEBytes((short) cch); - newbytes[0] = cchx[0]; - newbytes[1] = cchx[1]; - newbytes[2] = 0x1; - System.arraycopy(bts, 0, newbytes, 3, bts.length); - this.setData(newbytes); - } else { - byte[] bts = t.getBytes(DEFAULTENCODING); - cch = bts.length; - byte[] newbytes = new byte[cch + 3]; - byte[] cchx = io.starter.toolkit.ByteTools.shortToLEBytes((short) cch); - newbytes[0] = cchx[0]; - newbytes[1] = cchx[1]; - newbytes[2] = 0x0; - System.arraycopy(bts, 0, newbytes, 3, bts.length); - this.setData(newbytes); - } - } catch (Exception e) { - Logger.logInfo("setting Footer text failed: " + e); - } - this.rgch = t; - } - - /** - * get the Header text - */ - public String getHeaderText() { - return rgch; - } - - public void init() { - super.init(); - byte[] b = this.getData(); - if (this.getLength() > 4) { - int cch = (int) this.getByteAt(0); - byte[] namebytes = this.getBytesAt(0, this.getLength() - 4); - Unicodestring fstr = new Unicodestring(); - fstr.init(namebytes, false); - rgch = fstr.toString(); - if (DEBUGLEVEL > DEBUG_LOW) - Logger.logInfo("Header text: " + this.getHeaderText()); - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Headerrec.kt b/src/main/java/io/starter/formats/XLS/Headerrec.kt new file mode 100644 index 0000000..0b3e77e --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Headerrec.kt @@ -0,0 +1,117 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * **Headerrec: Print Header on Each Page (14h)**

                  + * + * + * Headerrec describes the header printed on every page + * + * + *

                  + * offset  name            size    contents
                  + * ---
                  + * 4       cch             1       Length of the Header String
                  + * 5       rgch            var     Header String
                  + *
                  +
                  * + */ +class Headerrec : io.starter.formats.XLS.XLSRecord() { + internal var cch = -1 + internal var rgch: String? = "" + internal var DEBUG = false + + /** + * get the Header text + */ + /** + * set the Header text + * The key here is that we need to construct an excel formatted unicode string, + * + * + * Yes, this will probably be an issue in Japan some day.... + */ + var headerText: String? + get() = rgch + set(t) { + try { + if (ByteTools.isUnicode(t)) { + val bts = t.toByteArray(charset(XLSConstants.UNICODEENCODING)) + cch = bts.size / 2 + val newbytes = ByteArray(cch + 3) + val cchx = io.starter.toolkit.ByteTools.shortToLEBytes(cch.toShort()) + newbytes[0] = cchx[0] + newbytes[1] = cchx[1] + newbytes[2] = 0x1 + System.arraycopy(bts, 0, newbytes, 3, bts.size) + this.setData(newbytes) + } else { + val bts = t.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + cch = bts.size + val newbytes = ByteArray(cch + 3) + val cchx = io.starter.toolkit.ByteTools.shortToLEBytes(cch.toShort()) + newbytes[0] = cchx[0] + newbytes[1] = cchx[1] + newbytes[2] = 0x0 + System.arraycopy(bts, 0, newbytes, 3, bts.size) + this.setData(newbytes) + } + } catch (e: Exception) { + Logger.logInfo("setting Footer text failed: $e") + } + + this.rgch = t + } + + override fun setSheet(bs: Sheet?) { + super.setSheet(bs) + bs!!.setHeader(this) + } + + override fun init() { + super.init() + val b = this.getData() + if (this.length > 4) { + val cch = this.getByteAt(0).toInt() + val namebytes = this.getBytesAt(0, this.length - 4) + val fstr = Unicodestring() + fstr.init(namebytes!!, false) + rgch = fstr.toString() + if (DEBUGLEVEL > XLSConstants.DEBUG_LOW) + Logger.logInfo("Header text: " + this.headerText!!) + } + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -8302043395108298631L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Hlink.java b/src/main/java/io/starter/formats/XLS/Hlink.java deleted file mode 100644 index b03dfa1..0000000 --- a/src/main/java/io/starter/formats/XLS/Hlink.java +++ /dev/null @@ -1,624 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.Serializable; -import java.io.UnsupportedEncodingException; - - -/** - * Hlink: Hyperlink (1b8h)
                  - *

                  - * hyperlink record - * - *

                  - * offset  name            size    contents
                  - * ---
                  - * 4       rwFirst         2       First row of link
                  - * 6       rwLast          2       Last row of link
                  - * 8       colFirst        2       First col of link
                  - * 10      colLast         2       Last col of link
                  - * 12      rgbHlink        var     HLINK data
                  - *
                  - *
                  - * 

                  - **/ -public final class Hlink extends XLSRecord { - - private static final long serialVersionUID = -4259979643231173799L; - int colFirst = -1, colLast = -1, rowFirst = -1, rowLast = -1; - private HLinkStruct linkStruct = null; - - /** - * set last/first cols/rows - */ - public void setRowFirst(int c) { - byte[] b = ByteTools.shortToLEBytes((short) c); - byte[] dt = this.getData(); - System.arraycopy(b, 0, dt, 0, 2); - this.rowFirst = c; - } - - public int getRowFirst() { - return rowFirst; - } - - /** - * set last/first cols/rows - */ - public void setRowLast(int c) { - byte[] b = ByteTools.shortToLEBytes((short) c); - byte[] dt = this.getData(); - System.arraycopy(b, 0, dt, 2, 2); - this.rowLast = c; - } - - public int getRowLast() { - return rowLast; - } - - /** - * set last/first cols/rows - */ - public void setColFirst(int c) { - byte[] b = ByteTools.shortToLEBytes((short) c); - byte[] dt = this.getData(); - System.arraycopy(b, 0, dt, 4, 2); - this.colFirst = c; - } - - public int getColFirst() { - return colFirst; - } - - /** - * set last/first cols/rows - */ - public void setColLast(int c) { - byte[] b = ByteTools.shortToLEBytes((short) c); - byte[] dt = this.getData(); - System.arraycopy(b, 0, dt, 6, 2); - this.colLast = c; - } - - public int getColLast() { - return colLast; - } - - /** - * get the URL for this Hlink - */ - public String getURL() { - if (linkStruct == null) return ""; - return linkStruct.getUrl(); - } - - /** - * return the description part of the hyperlink - * - * @return - */ - public String getDescription() { - if (linkStruct == null) return ""; - return linkStruct.getLinkText(); - } - - public static XLSRecord getPrototype() { - Hlink retlab = new Hlink(); - byte[] rbytes = {0, 0, 0, 0, 0, 0, - 0, 0, -48, -55, -22, 121, -7, -70, - -50, 17, -116, -126, 0, -86, 0, 75, - -87, 11, 2, 0, 0, 0, 2, 0, 0, 0, 0, - 0, 0, 0, -32, -55, -22, 121, -7, -70, - -50, 17, -116, -126, 0, -86, 0, 75, - -87, 11, -116, 0, 0, 0, 0, 0}; - - retlab.setOpcode(HLINK); - retlab.setLength((short) 94); - retlab.setData(rbytes); - retlab.init(); -// retlab.setURL(url, desc, textMark); 200606 KSC: do separately! - return retlab; - } - - /** - * set link URL with description and test mark - * note that either url or text mark must be present ... - * - * @param url - * @param textMark - * @param desc - */ - public void setURL(String url, String desc, String textMark) { - try { - if (url.equals("") && textMark.equals("")) { - Logger.logWarn("HLINK.setURL: no url or text mark specified"); - return; - } - linkStruct.setUrl(url, desc, textMark); - } catch (Exception e) { - Logger.logWarn("setting URL " + url + " failed: " + e); - } - byte[] bt = linkStruct.getBytes(); - this.setData(bt); - } - - public void setFileURL(String url, String desc, String textMark) { - try { - linkStruct.setFileURL(url, desc, textMark); - } catch (Exception e) { - Logger.logWarn("setting URL " + url + " failed: " + e); - } - byte[] bt = linkStruct.getBytes(); - this.setData(bt); - } - - /** - * returns whether a given col - * is referenced by this Hyperlink - */ - public boolean inrange(int x) { - return (x <= colLast) && (x >= colFirst); - } - - private CellRange range = null; - - public void init() { - super.init(); - int pos = 0; - rowFirst = (int) ByteTools.readShort(this.getByteAt(pos++), this.getByteAt(pos++)); - rowLast = (int) ByteTools.readShort(this.getByteAt(pos++), this.getByteAt(pos++)); - colFirst = (int) ByteTools.readShort(this.getByteAt(pos++), this.getByteAt(pos++)); - colLast = (int) ByteTools.readShort(this.getByteAt(pos++), this.getByteAt(pos++)); - Unicodestring ustr = new Unicodestring(); - - String nm = ""; - if (this.getSheet() != null) { - if (!this.getSheet().getSheetName().equals("")) nm = this.getSheet().getSheetName() + "!"; - } - nm += io.starter.OpenXLS.ExcelTools.getAlphaVal(colFirst); - nm += rowFirst + ":"; - nm += io.starter.OpenXLS.ExcelTools.getAlphaVal(colLast); - nm += rowLast; - try { - range = new CellRange(nm, null); - } catch (Exception e) { - Logger.logWarn("initializing Hlink record failed: " + e); - } - if (DEBUGLEVEL > 5) Logger.logInfo("Hlink Cells: " + range.toString()); - - try { - linkStruct = new HLinkStruct(this.getBytesAt(0, this.getLength())); - } catch (Exception e) { - Logger.logWarn("Hyperlink parse failed for Cells " + range.toString() + ": " + e); - } - if (DEBUGLEVEL > 5) Logger.logInfo("Hlink URL: " + linkStruct.getUrl()); - } - - /** - * @return - */ - public CellRange getRange() { - return range; - } - - /** - * @param range - */ - public void setRange(CellRange range) { - this.range = range; - } - - - public void initCells(WorkBookHandle wbook) { - int[] cellcoords = new int[4]; - cellcoords[0] = this.getRowFirst(); - cellcoords[2] = this.getRowLast(); - cellcoords[1] = this.getColFirst(); - cellcoords[3] = this.getColLast(); - - try { - CellRange cr = new CellRange(wbook.getWorkSheet(this.getSheet().getSheetName()), cellcoords); - cr.setWorkBook(wbook); - BiffRec[] ch = cr.getCellRecs(); - for (int t = 0; t < ch.length; t++) { - ch[t].setHyperlink(this); - } - } catch (Exception e) { - Logger.logWarn("initializing Hyperlink Cells failed: " + e); - } - } -} - -/* - HLINK Struct - ?? 16 - int? 4 - int? 4 - cch? 4 - var var - int? 4 - ? 16 - cch 4 - urlstr var -*/ -class HLinkStruct implements XLSConstants, Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1915454683496117350L; - boolean isLink = false; - boolean isAbsoluteLink = false; - boolean hasDescription = false; - boolean hasTextMark = false; - boolean hasTargetFrame = false; - boolean isUNCPath = false; - byte[] grbit = new byte[4]; - - /** - * decodes option flag into vars - * Here is the breakdown on what these mean: - *

                  - * standard (non-local) URL: isLink= true, isAbsoluteLink= true, isUNCPath= false - * local file: isLink= true, isUNCPath= false - * UNC path: isLink= true, isAbsoluteLink= true, isUNCPath= true - * link in current workbook: isLink= false, isAbsoluteLink= false, hasTextMark= true, isUNCPath= false - * - * @param grbytes - */ - void decodeGrbit(byte[] grbytes) { - if ((byte) (grbytes[0] & 0x1) > 0x0) isLink = true; - if ((byte) (grbytes[0] & 0x2) > 0x0) isAbsoluteLink = true; - // 20060406 KSC: need both bits 2 & 4 to be set for hasDescription -// if ((byte)(grbytes[0] & 0x14) > 0x0)hasDescription = true; - if ((byte) (grbytes[0] & 0x14) >= 0x14) hasDescription = true; - if ((byte) (grbytes[0] & 0x8) > 0x0) hasTextMark = true; - if ((byte) (grbytes[0] & 0x80) > 0x0) hasTargetFrame = true; - if ((byte) (grbytes[0] & 0x100) > 0x0) isUNCPath = true; - } - - void setGrbit() { - grbit[0] = 0x0; - if (isLink) grbit[0] = (byte) (0x1 | grbit[0]); - if (isAbsoluteLink) grbit[0] = (byte) (0x2 | grbit[0]); - if (hasDescription) grbit[0] = (byte) (0x14 | grbit[0]); - if (hasTextMark) grbit[0] = (byte) (0x8 | grbit[0]); - if (hasTargetFrame) grbit[0] = (byte) (0x80 | grbit[0]); - if (isUNCPath) grbit[0] = (byte) (0x100 | grbit[0]); - mybytes[28] = grbit[0]; - } - - boolean DEBUG = false; - - int getUrlPos() { - return urlcch; - } - - byte[] getBytes() { - return mybytes; - } - - String url = "", linktext = "", textMark = "", targetFrame = ""; - int int1 = -1, urlcch = -1, int4 = -1; - private byte[] mybytes = null; - - /** - * get the URL for this Hlink - */ - String getUrl() { - if (textMark.equals("")) - return url; - else - return url + "#" + textMark; - } - - /** - * get the URL link text for this Hlink - */ - public String getLinkText() { - return linktext; - } - - - // 20060406 KSC: mods to setUrl: - // Added ability to set description + modified byte input to work mo' betta ... - - /** - * set the URL for this Hlink, description= URL - *

                  - * Assume link URL i.e. no file URL, UNC path, etc. - */ - void setUrl(String url) { - setUrl(url, url, ""); - } - - /** - * set standard link URL for this Hlink and optional description - * i.e. no file URL, UNC path, text marks ... - */ - void setUrl(String ur, String desc) { - setUrl(url, desc, ""); - } - - /** - * set proper settings for URL and write bytes - * - * @param ur - * @param desc - * @param textMark - */ - void setUrl(String ur, String desc, String textMark) { - isLink = true; - isAbsoluteLink = true; - isUNCPath = false; - hasDescription = desc.length() > 0; - hasTextMark = textMark.length() > 0; - setBytes(ur, desc, textMark); - } - - /** - * Assume NOT TRUE FILE URL (avoids writing complex dir info + using FILE_GUID) - * so difference between link URL and file URL is the isAbsoluteLink var ... - * - * @param ur - * @param desc - * @param textMark - */ - void setFileURL(String ur, String desc, String textMark) { - isLink = true; - isAbsoluteLink = false; - isUNCPath = false; - hasDescription = desc.length() > 0; - hasTextMark = textMark.length() > 0; - setBytes(ur, desc, textMark); - } - - /** - * fills the HLINK bytes based on the settings of - * isLink - * isAbsoluteLink - * hasDescription - * hasTextMark - * hasTargetFrame - * isUNCPath - *

                  - * how these are set, before calling this method, determine - * how the HLINK record bytes are written - * - * @param ur URL string - * @param desc optional descrption - * @param tm optional text mark text as in: ...#textmarktext - */ - static final byte[] URL_GUID = {-32, -55, -22, 121, -7, -70, -50, 17, -116, -126, 0, -86, 0, 75, -87, 11}; - static final byte[] FILE_GUID = {3, 3, 0, 0, 0, 0, 0, 0, -64, 0, 0, 0, 0, 0, 0, 70}; - - void setBytes(String ur, String desc, String tm) { - try { - setGrbit(); - int pos = 32; // start of description/text input - - byte[] blankbytes = new byte[2]; // trailing zero word - byte[] newbytes = new byte[pos]; - System.arraycopy(mybytes, 0, newbytes, 0, pos); // copy old pre-string data into new array - - if (hasDescription) { - // copy char array length (cch) of description + description bytes - byte[] descbytes = desc.getBytes(UNICODEENCODING); - int newcch = descbytes.length; - byte[] newcchbytes = ByteTools.cLongToLEBytes(newcch / 2 + 1); - - // copy cch of desc in... - newbytes = ByteTools.append(newcchbytes, newbytes); - - //copy bytes of description in - newbytes = ByteTools.append(descbytes, newbytes); - // copy trailing dumb str bytes in - newbytes = ByteTools.append(blankbytes, newbytes); - } - - /* TODO: Implement target frame right here - if (hasTargetFrame) { - // copy targetFrame bytes + length - // get cch - byte[] tfbytes= tf.getBytes(UNICODEENCODING); - int newcch = tfbytes.length; - byte[] newcchbytes = ByteTools.cLongToLEBytes(newcch/2 +1); - // copy cch of tm in... - newbytes = ByteTools.append(newcchbytes, newbytes); - - //copy bytes of tf in - newbytes = ByteTools.append(tfbytes, newbytes); - - // copy trailing dumb str bytes in - newbytes = ByteTools.append(blankbytes, newbytes); - } - */ - - /* URL Handling */ - // copy GUID in - ASSUME URL_GUID since aren't supporting relative FILE_GUIDs! - newbytes = ByteTools.append(URL_GUID, newbytes); - - if (isLink) { - // copy url bytes + length - // get cch (which is different alg. from both description + textmark) - byte[] urlbytes = ur.getBytes(UNICODEENCODING); - int newcch = urlbytes.length; - - // copy cch of url in... - byte[] newcchbytes = ByteTools.cLongToLEBytes(newcch + 2); - newbytes = ByteTools.append(newcchbytes, newbytes); - - // copy url bytes in - newbytes = ByteTools.append(urlbytes, newbytes); - - // copy trailing dumb str bytes in - newbytes = ByteTools.append(blankbytes, newbytes); - } - - if (hasTextMark) { - // copy textmark bytes + length - // get cch - byte[] tmbytes = tm.getBytes(UNICODEENCODING); - int newcch = tmbytes.length; - byte[] newcchbytes = ByteTools.cLongToLEBytes(newcch / 2 + 1); - // copy cch of tm in... - newbytes = ByteTools.append(newcchbytes, newbytes); - - //copy bytes of tm in - newbytes = ByteTools.append(tmbytes, newbytes); - - // copy trailing dumb str bytes in - newbytes = ByteTools.append(blankbytes, newbytes); - } - this.mybytes = newbytes; - - this.linktext = desc; - this.textMark = tm; - this.url = ur; - } catch (UnsupportedEncodingException e) { - Logger.logWarn("Setting URL failed: " + ur + ": " + e); - } - - } - - /** - * Inner class with no documentation - */ - HLinkStruct(byte[] barr) { - mybytes = barr; - int pos = 28; - - System.arraycopy(barr, 28, grbit, 0, 4); - decodeGrbit(grbit); - pos += 4; - - /* - * This section gets the display string for the Hyperlink, if it exists. - */ - if (hasDescription) { - int cch = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]); - if (cch > 0) { // 20070814 KSC: shouldn't be 0 and also hasDescription ... - try { - byte[] descripbytes = new byte[(cch * 2) - 2]; - System.arraycopy(barr, pos, descripbytes, 0, (cch * 2) - 2); - linktext = new String(descripbytes, UNICODEENCODING); - pos += cch * 2; - if (DEBUG) Logger.logInfo("Hlink.hlstruct Display URL: " + linktext); - } catch (Exception e) { - if (DEBUG) Logger.logWarn("decoding Display URL in Hlink: " + e); - } - } - } - /* - * if it has a target frame, read in - */ - if (hasTargetFrame) { - int cch = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]); - if (cch > 0) { - try { - byte[] tfbytes = new byte[(cch * 2) - 2]; - System.arraycopy(barr, pos, tfbytes, 0, (cch * 2) - 2); - targetFrame = new String(tfbytes, UNICODEENCODING); - if (DEBUG) - Logger.logInfo("Hlink.hlstruct targetFrame: " + targetFrame); - pos += (cch * 2); - } catch (Exception e) { - if (DEBUG) - Logger.logWarn("Hlink Decode of targetFrame failed: " + e); - } - } - } - /* - * URL section: non-local URL or Link in current file - */ - if (isLink) { - byte[] GUID = new byte[16]; - System.arraycopy(barr, pos, GUID, 0, 16); - boolean bIsCurrentFileRef = java.util.Arrays.equals(GUID, FILE_GUID); - pos += 16; // skip GUID - if (!bIsCurrentFileRef) { // then it's a URL or non-relative file path - urlcch = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]); - if (urlcch > 0) { - try { - byte[] urlbytes = new byte[(urlcch) - 2]; - System.arraycopy(barr, pos, urlbytes, 0, (urlcch) - 2); - url = new String(urlbytes, UNICODEENCODING); - if (DEBUG) - Logger.logInfo("Hlink.hlstruct URL: " + url); - pos += urlcch; - } catch (Exception e) { - if (DEBUG) - Logger.logWarn("Hlink Decode of URL failed: " + e); - } - } - } else { // (appears to be a) current file link (Actuality is different than documentation!) - int dirUps = ByteTools.readShort(barr[pos++], barr[pos++]); - urlcch = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]); - if (urlcch > 0) { - try { - byte[] urlbytes = new byte[urlcch - 1]; - System.arraycopy(barr, pos, urlbytes, 0, urlcch - 1); - url = new String(urlbytes, DEFAULTENCODING); - if (DEBUG) - Logger.logInfo("Hlink.hlstruct File URL: " + url); - pos += urlcch + 24; // add char count + avoid the 24 "unknown" bytes - int extraInfo = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]); - if (extraInfo > 0) { - pos += extraInfo; - int sz = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]); - - - } - } catch (Exception e) { - if (DEBUG) - Logger.logWarn("Hlink Decode of File URL failed: " + e); - } - } - } - } - - if (hasTextMark) { - int cch = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]); - if (cch > 0) { - try { - byte[] tmbytes = new byte[(cch * 2) - 2]; - System.arraycopy(barr, pos, tmbytes, 0, (cch * 2) - 2); - textMark = new String(tmbytes, UNICODEENCODING); - if (DEBUG) - Logger.logInfo("Hlink.hlstruct textMark: " + textMark); - pos += (cch * 2); - } catch (Exception e) { - if (DEBUG) - Logger.logWarn("Hlink Decode of textmark failed: " + e); - } - } - } - } - -} - diff --git a/src/main/java/io/starter/formats/XLS/Hlink.kt b/src/main/java/io/starter/formats/XLS/Hlink.kt new file mode 100644 index 0000000..10b8279 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Hlink.kt @@ -0,0 +1,626 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.Serializable +import java.io.UnsupportedEncodingException + + +/** + * **Hlink: Hyperlink (1b8h)**

                  + * + * + * hyperlink record + * + * + *

                  + * offset  name            size    contents
                  + * ---
                  + * 4       rwFirst         2       First row of link
                  + * 6       rwLast          2       Last row of link
                  + * 8       colFirst        2       First col of link
                  + * 10      colLast         2       Last col of link
                  + * 12      rgbHlink        var     HLINK data
                  + *
                  + *
                  +
                  * + */ +class Hlink : XLSRecord() { + internal var colFirst = -1 + internal var colLast = -1 + internal var rowFirst = -1 + internal var rowLast = -1 + private var linkStruct: HLinkStruct? = null + + /** + * get the URL for this Hlink + */ + val url: String + get() = if (linkStruct == null) "" else linkStruct!!.getUrl() + + /** + * return the description part of the hyperlink + * + * @return + */ + val description: String + get() = if (linkStruct == null) "" else linkStruct!!.linkText + + /** + * @return + */ + /** + * @param range + */ + var range: CellRange? = null + + /** + * set last/first cols/rows + */ + fun setRowFirst(c: Int) { + val b = ByteTools.shortToLEBytes(c.toShort()) + val dt = this.getData() + System.arraycopy(b, 0, dt!!, 0, 2) + this.rowFirst = c + } + + fun getRowFirst(): Int { + return rowFirst + } + + /** + * set last/first cols/rows + */ + fun setRowLast(c: Int) { + val b = ByteTools.shortToLEBytes(c.toShort()) + val dt = this.getData() + System.arraycopy(b, 0, dt!!, 2, 2) + this.rowLast = c + } + + fun getRowLast(): Int { + return rowLast + } + + /** + * set last/first cols/rows + */ + fun setColFirst(c: Int) { + val b = ByteTools.shortToLEBytes(c.toShort()) + val dt = this.getData() + System.arraycopy(b, 0, dt!!, 4, 2) + this.colFirst = c + } + + fun getColFirst(): Int { + return colFirst + } + + /** + * set last/first cols/rows + */ + fun setColLast(c: Int) { + val b = ByteTools.shortToLEBytes(c.toShort()) + val dt = this.getData() + System.arraycopy(b, 0, dt!!, 6, 2) + this.colLast = c + } + + fun getColLast(): Int { + return colLast + } + + /** + * set link URL with description and test mark + * note that either url or text mark must be present ... + * + * @param url + * @param textMark + * @param desc + */ + fun setURL(url: String, desc: String, textMark: String) { + try { + if (url == "" && textMark == "") { + Logger.logWarn("HLINK.setURL: no url or text mark specified") + return + } + linkStruct!!.setUrl(url, desc, textMark) + } catch (e: Exception) { + Logger.logWarn("setting URL $url failed: $e") + } + + val bt = linkStruct!!.bytes + this.setData(bt) + } + + fun setFileURL(url: String, desc: String, textMark: String) { + try { + linkStruct!!.setFileURL(url, desc, textMark) + } catch (e: Exception) { + Logger.logWarn("setting URL $url failed: $e") + } + + val bt = linkStruct!!.bytes + this.setData(bt) + } + + /** + * returns whether a given col + * is referenced by this Hyperlink + */ + fun inrange(x: Int): Boolean { + return x <= colLast && x >= colFirst + } + + override fun init() { + super.init() + var pos = 0 + rowFirst = ByteTools.readShort(this.getByteAt(pos++).toInt(), this.getByteAt(pos++).toInt()).toInt() + rowLast = ByteTools.readShort(this.getByteAt(pos++).toInt(), this.getByteAt(pos++).toInt()).toInt() + colFirst = ByteTools.readShort(this.getByteAt(pos++).toInt(), this.getByteAt(pos++).toInt()).toInt() + colLast = ByteTools.readShort(this.getByteAt(pos++).toInt(), this.getByteAt(pos++).toInt()).toInt() + val ustr = Unicodestring() + + var nm = "" + if (this.sheet != null) { + if (this.sheet!!.sheetName != "") nm = this.sheet!!.sheetName + "!" + } + nm += io.starter.OpenXLS.ExcelTools.getAlphaVal(colFirst) + nm += "$rowFirst:" + nm += io.starter.OpenXLS.ExcelTools.getAlphaVal(colLast) + nm += rowLast + try { + range = CellRange(nm, null) + } catch (e: Exception) { + Logger.logWarn("initializing Hlink record failed: $e") + } + + if (DEBUGLEVEL > 5) Logger.logInfo("Hlink Cells: " + range!!.toString()) + + try { + linkStruct = HLinkStruct(this.getBytesAt(0, this.length)) + } catch (e: Exception) { + Logger.logWarn("Hyperlink parse failed for Cells " + range!!.toString() + ": " + e) + } + + if (DEBUGLEVEL > 5) Logger.logInfo("Hlink URL: " + linkStruct!!.getUrl()) + } + + + fun initCells(wbook: WorkBookHandle) { + val cellcoords = IntArray(4) + cellcoords[0] = this.getRowFirst() + cellcoords[2] = this.getRowLast() + cellcoords[1] = this.getColFirst() + cellcoords[3] = this.getColLast() + + try { + val cr = CellRange(wbook.getWorkSheet(this.sheet!!.sheetName), cellcoords) + cr.workBook = wbook + val ch = cr.cellRecs + for (t in ch.indices) { + ch[t].hyperlink = this + } + } catch (e: Exception) { + Logger.logWarn("initializing Hyperlink Cells failed: $e") + } + + } + + companion object { + + private val serialVersionUID = -4259979643231173799L + + // retlab.setURL(url, desc, textMark); 200606 KSC: do separately! + val prototype: XLSRecord? + get() { + val retlab = Hlink() + val rbytes = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, -48, -55, -22, 121, -7, -70, -50, 17, -116, -126, 0, -86, 0, 75, -87, 11, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, -32, -55, -22, 121, -7, -70, -50, 17, -116, -126, 0, -86, 0, 75, -87, 11, -116, 0, 0, 0, 0, 0) + + retlab.opcode = XLSConstants.HLINK + retlab.length = 94.toShort() + retlab.setData(rbytes) + retlab.init() + return retlab + } + } +} + +/* + HLINK Struct + ?? 16 + int? 4 + int? 4 + cch? 4 + var var + int? 4 + ? 16 + cch 4 + urlstr var +*/ +internal class HLinkStruct +/** + * Inner class with no documentation + */ +(barr: ByteArray) : XLSConstants, Serializable { + var isLink = false + var isAbsoluteLink = false + var hasDescription = false + var hasTextMark = false + var hasTargetFrame = false + var isUNCPath = false + var grbit = ByteArray(4) + + var DEBUG = false + + var url = "" + /** + * get the URL link text for this Hlink + */ + var linkText = "" + var textMark = "" + var targetFrame = "" + var int1 = -1 + var urlPos = -1 + var int4 = -1 + var bytes: ByteArray? = null + private set + + /** + * decodes option flag into vars + * Here is the breakdown on what these mean: + * + * + * standard (non-local) URL: isLink= true, isAbsoluteLink= true, isUNCPath= false + * local file: isLink= true, isUNCPath= false + * UNC path: isLink= true, isAbsoluteLink= true, isUNCPath= true + * link in current workbook: isLink= false, isAbsoluteLink= false, hasTextMark= true, isUNCPath= false + * + * @param grbytes + */ + fun decodeGrbit(grbytes: ByteArray) { + if ((grbytes[0] and 0x1).toByte() > 0x0) isLink = true + if ((grbytes[0] and 0x2).toByte() > 0x0) isAbsoluteLink = true + // 20060406 KSC: need both bits 2 & 4 to be set for hasDescription + // if ((byte)(grbytes[0] & 0x14) > 0x0)hasDescription = true; + if ((grbytes[0] and 0x14).toByte() >= 0x14) hasDescription = true + if ((grbytes[0] and 0x8).toByte() > 0x0) hasTextMark = true + if ((grbytes[0] and 0x80).toByte() > 0x0) hasTargetFrame = true + if ((grbytes[0] and 0x100).toByte() > 0x0) isUNCPath = true + } + + fun setGrbit() { + grbit[0] = 0x0 + if (isLink) grbit[0] = (0x1 or grbit[0]).toByte() + if (isAbsoluteLink) grbit[0] = (0x2 or grbit[0]).toByte() + if (hasDescription) grbit[0] = (0x14 or grbit[0]).toByte() + if (hasTextMark) grbit[0] = (0x8 or grbit[0]).toByte() + if (hasTargetFrame) grbit[0] = (0x80 or grbit[0]).toByte() + if (isUNCPath) grbit[0] = (0x100 or grbit[0]).toByte() + bytes[28] = grbit[0] + } + + /** + * get the URL for this Hlink + */ + fun getUrl(): String { + return if (textMark == "") + url + else + "$url#$textMark" + } + + + // 20060406 KSC: mods to setUrl: + // Added ability to set description + modified byte input to work mo' betta ... + + /** + * set the URL for this Hlink, description= URL + * + * + * Assume link URL i.e. no file URL, UNC path, etc. + */ + fun setUrl(url: String) { + setUrl(url, url, "") + } + + /** + * set standard link URL for this Hlink and optional description + * i.e. no file URL, UNC path, text marks ... + */ + fun setUrl(ur: String, desc: String) { + setUrl(url, desc, "") + } + + /** + * set proper settings for URL and write bytes + * + * @param ur + * @param desc + * @param textMark + */ + fun setUrl(ur: String, desc: String, textMark: String) { + isLink = true + isAbsoluteLink = true + isUNCPath = false + hasDescription = desc.length > 0 + hasTextMark = textMark.length > 0 + setBytes(ur, desc, textMark) + } + + /** + * Assume NOT TRUE FILE URL (avoids writing complex dir info + using FILE_GUID) + * so difference between link URL and file URL is the isAbsoluteLink var ... + * + * @param ur + * @param desc + * @param textMark + */ + fun setFileURL(ur: String, desc: String, textMark: String) { + isLink = true + isAbsoluteLink = false + isUNCPath = false + hasDescription = desc.length > 0 + hasTextMark = textMark.length > 0 + setBytes(ur, desc, textMark) + } + + fun setBytes(ur: String, desc: String, tm: String) { + try { + setGrbit() + val pos = 32 // start of description/text input + + val blankbytes = ByteArray(2) // trailing zero word + var newbytes = ByteArray(pos) + System.arraycopy(bytes!!, 0, newbytes, 0, pos) // copy old pre-string data into new array + + if (hasDescription) { + // copy char array length (cch) of description + description bytes + val descbytes = desc.toByteArray(charset(XLSConstants.UNICODEENCODING)) + val newcch = descbytes.size + val newcchbytes = ByteTools.cLongToLEBytes(newcch / 2 + 1) + + // copy cch of desc in... + newbytes = ByteTools.append(newcchbytes, newbytes) + + //copy bytes of description in + newbytes = ByteTools.append(descbytes, newbytes) + // copy trailing dumb str bytes in + newbytes = ByteTools.append(blankbytes, newbytes) + } + + /* TODO: Implement target frame right here + if (hasTargetFrame) { + // copy targetFrame bytes + length + // get cch + byte[] tfbytes= tf.getBytes(UNICODEENCODING); + int newcch = tfbytes.length; + byte[] newcchbytes = ByteTools.cLongToLEBytes(newcch/2 +1); + // copy cch of tm in... + newbytes = ByteTools.append(newcchbytes, newbytes); + + //copy bytes of tf in + newbytes = ByteTools.append(tfbytes, newbytes); + + // copy trailing dumb str bytes in + newbytes = ByteTools.append(blankbytes, newbytes); + } + */ + + /* URL Handling */ + // copy GUID in - ASSUME URL_GUID since aren't supporting relative FILE_GUIDs! + newbytes = ByteTools.append(URL_GUID, newbytes) + + if (isLink) { + // copy url bytes + length + // get cch (which is different alg. from both description + textmark) + val urlbytes = ur.toByteArray(charset(XLSConstants.UNICODEENCODING)) + val newcch = urlbytes.size + + // copy cch of url in... + val newcchbytes = ByteTools.cLongToLEBytes(newcch + 2) + newbytes = ByteTools.append(newcchbytes, newbytes) + + // copy url bytes in + newbytes = ByteTools.append(urlbytes, newbytes) + + // copy trailing dumb str bytes in + newbytes = ByteTools.append(blankbytes, newbytes) + } + + if (hasTextMark) { + // copy textmark bytes + length + // get cch + val tmbytes = tm.toByteArray(charset(XLSConstants.UNICODEENCODING)) + val newcch = tmbytes.size + val newcchbytes = ByteTools.cLongToLEBytes(newcch / 2 + 1) + // copy cch of tm in... + newbytes = ByteTools.append(newcchbytes, newbytes) + + //copy bytes of tm in + newbytes = ByteTools.append(tmbytes, newbytes) + + // copy trailing dumb str bytes in + newbytes = ByteTools.append(blankbytes, newbytes) + } + this.bytes = newbytes + + this.linkText = desc + this.textMark = tm + this.url = ur + } catch (e: UnsupportedEncodingException) { + Logger.logWarn("Setting URL failed: $ur: $e") + } + + } + + init { + bytes = barr + var pos = 28 + + System.arraycopy(barr, 28, grbit, 0, 4) + decodeGrbit(grbit) + pos += 4 + + /* + * This section gets the display string for the Hyperlink, if it exists. + */ + if (hasDescription) { + val cch = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]) + if (cch > 0) { // 20070814 KSC: shouldn't be 0 and also hasDescription ... + try { + val descripbytes = ByteArray(cch * 2 - 2) + System.arraycopy(barr, pos, descripbytes, 0, cch * 2 - 2) + linkText = String(descripbytes, XLSConstants.UNICODEENCODING) + pos += cch * 2 + if (DEBUG) Logger.logInfo("Hlink.hlstruct Display URL: $linkText") + } catch (e: Exception) { + if (DEBUG) Logger.logWarn("decoding Display URL in Hlink: $e") + } + + } + } + /* + * if it has a target frame, read in + */ + if (hasTargetFrame) { + val cch = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]) + if (cch > 0) { + try { + val tfbytes = ByteArray(cch * 2 - 2) + System.arraycopy(barr, pos, tfbytes, 0, cch * 2 - 2) + targetFrame = String(tfbytes, XLSConstants.UNICODEENCODING) + if (DEBUG) + Logger.logInfo("Hlink.hlstruct targetFrame: $targetFrame") + pos += cch * 2 + } catch (e: Exception) { + if (DEBUG) + Logger.logWarn("Hlink Decode of targetFrame failed: $e") + } + + } + } + /* + * URL section: non-local URL or Link in current file + */ + if (isLink) { + val GUID = ByteArray(16) + System.arraycopy(barr, pos, GUID, 0, 16) + val bIsCurrentFileRef = java.util.Arrays.equals(GUID, FILE_GUID) + pos += 16 // skip GUID + if (!bIsCurrentFileRef) { // then it's a URL or non-relative file path + urlPos = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]) + if (urlPos > 0) { + try { + val urlbytes = ByteArray(urlPos - 2) + System.arraycopy(barr, pos, urlbytes, 0, urlPos - 2) + url = String(urlbytes, XLSConstants.UNICODEENCODING) + if (DEBUG) + Logger.logInfo("Hlink.hlstruct URL: $url") + pos += urlPos + } catch (e: Exception) { + if (DEBUG) + Logger.logWarn("Hlink Decode of URL failed: $e") + } + + } + } else { // (appears to be a) current file link (Actuality is different than documentation!) + val dirUps = ByteTools.readShort(barr[pos++].toInt(), barr[pos++].toInt()).toInt() + urlPos = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]) + if (urlPos > 0) { + try { + val urlbytes = ByteArray(urlPos - 1) + System.arraycopy(barr, pos, urlbytes, 0, urlPos - 1) + url = String(urlbytes, XLSConstants.DEFAULTENCODING) + if (DEBUG) + Logger.logInfo("Hlink.hlstruct File URL: $url") + pos += urlPos + 24 // add char count + avoid the 24 "unknown" bytes + val extraInfo = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]) + if (extraInfo > 0) { + pos += extraInfo + val sz = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]) + + + } + } catch (e: Exception) { + if (DEBUG) + Logger.logWarn("Hlink Decode of File URL failed: $e") + } + + } + } + } + + if (hasTextMark) { + val cch = ByteTools.readInt(barr[pos++], barr[pos++], barr[pos++], barr[pos++]) + if (cch > 0) { + try { + val tmbytes = ByteArray(cch * 2 - 2) + System.arraycopy(barr, pos, tmbytes, 0, cch * 2 - 2) + textMark = String(tmbytes, XLSConstants.UNICODEENCODING) + if (DEBUG) + Logger.logInfo("Hlink.hlstruct textMark: $textMark") + pos += cch * 2 + } catch (e: Exception) { + if (DEBUG) + Logger.logWarn("Hlink Decode of textmark failed: $e") + } + + } + } + } + + companion object { + /** + * serialVersionUID + */ + private const val serialVersionUID = -1915454683496117350L + + /** + * fills the HLINK bytes based on the settings of + * isLink + * isAbsoluteLink + * hasDescription + * hasTextMark + * hasTargetFrame + * isUNCPath + * + * + * how these are set, before calling this method, determine + * how the HLINK record bytes are written + * + * @param ur URL string + * @param desc optional descrption + * @param tm optional text mark text as in: ...#textmarktext + */ + val URL_GUID = byteArrayOf(-32, -55, -22, 121, -7, -70, -50, 17, -116, -126, 0, -86, 0, 75, -87, 11) + val FILE_GUID = byteArrayOf(3, 3, 0, 0, 0, 0, 0, 0, -64, 0, 0, 0, 0, 0, 0, 70) + } + +} + diff --git a/src/main/java/io/starter/formats/XLS/ImageNotFoundException.java b/src/main/java/io/starter/formats/XLS/ImageNotFoundException.kt similarity index 62% rename from src/main/java/io/starter/formats/XLS/ImageNotFoundException.java rename to src/main/java/io/starter/formats/XLS/ImageNotFoundException.kt index 6aaf71c..1e8e716 100644 --- a/src/main/java/io/starter/formats/XLS/ImageNotFoundException.java +++ b/src/main/java/io/starter/formats/XLS/ImageNotFoundException.kt @@ -20,36 +20,39 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** - * thrown when trying to access an Image and it is not found. + * **thrown when trying to access an Image and it is not found.** * * @see ImageHandle + * * @see WorkBook */ -public final class ImageNotFoundException extends java.lang.Exception { - +class ImageNotFoundException(n: String) : java.lang.Exception() { + internal var description = "" - /** - * - */ - private static final long serialVersionUID = -5031711537364049574L; - String description = ""; + init { + description = n + } - public ImageNotFoundException(String n) { - super(); - description = n; + override fun getMessage(): String { + return this.toString() } - public String getMessage() { - return this.toString(); + override fun toString(): String { + return description } - public String toString() { - return description; + companion object { + + + /** + * + */ + private val serialVersionUID = -5031711537364049574L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Index.java b/src/main/java/io/starter/formats/XLS/Index.java deleted file mode 100644 index 2919751..0000000 --- a/src/main/java/io/starter/formats/XLS/Index.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.io.Serializable; - - -/** - * Index: Index Record 0x20B
                  - *

                  - * Index records are written after the Bof record for each Boundsheet. - *

                  - *
                  - * offset  name        size    contents
                  - * ---
                  - * 4       Reserved    4       Must be zero
                  - * 8       rwMic       4       First row that exists on the sheet
                  - * 12      rwMac       4       Last row that exists on the sheet, plus 1
                  - * 16      Reserved    4       Pointer to DIMENSIONS record for Boundsheet
                  - * 20      rgibRw      var     Array of file offsets to the Dbcell records
                  - * for each block of ROW records.  A block
                  - * contains up to 32 ROW records.
                  - *
                  - *
                  - * When a record value changes in size, it fires a CellChangeEvent
                  - * which cascades through the other associated objects.
                  - *
                  - * The record size change has the following effect on INDEX record fields:
                  - *
                  - * 1. ALL Dbcell records in the file starting with the one
                  - * containing the changed record move.  This requires updating
                  - * the dbcellpointers (rgibRw) in ALL INDEX records which
                  - * are located after the changed Dbcell within the file stream.
                  - *
                  - * 

                  - * - * @see WorkBook - * @see Boundsheet - * @see Dbcell - * @see Row - * @see Cell - * @see XLSRecord - */ - -public final class Index extends io.starter.formats.XLS.XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -753407655976707961L; - private int rwMic; - private int rwMac; - private int dbnum = 0; - // private dbCellPointer[] dbcellarray; not used - private CompatibleVector dbcells = new CompatibleVector(); - private int indexnum; - private static int defaultsize = 16; - private Dimensions dims; - - /** - * create a new INDEX rec - */ - public static XLSRecord getPrototype() { - Index idx = new Index(); - byte[] dt = new byte[defaultsize]; // default val - idx.originalsize = defaultsize; - idx.setData(dt); - idx.setOpcode(INDEX); - idx.setLength((short) defaultsize); - idx.init(); - return idx; - } - - /** - * fire the cell change event - *

                  - * public void fireCellChangeEvent(CellChangeEvent c){ - * // do its thing - * // this.doCellSizeChangeAction(c); - * // then pass it along... - * // this.getSheet().fireCellChangeEvent(c); - * } - */ - - void setDimensions(Dimensions d) { - this.dims = d; - } - - - // Not used?? - void setDimensionsOffset(int offset) { - byte[] recData = this.getData(); - byte[] newoff = ByteTools.cLongToLEBytes(offset); - System.arraycopy(newoff, 0, recData, 12, 4); - this.setData(recData); - } - - /** - * set the index number for - * addressing. - */ - public void setIndexNum(int n) { - indexnum = n; - } - - /** - * get the index number for - * addressing. - */ - public int getIndexNum() { - return indexnum; - } - - - /** - * add an associated Dbcell object - * to this INDEX. - */ - void addDBCell(Dbcell dbc) { - boolean bAdd = true; -/* KSC: TESTING testLostBorders bug for (int i= 0; i < dbcells.size() && bAdd; i++) { - if (Arrays.equals(((Dbcell) dbcells.get(i)).data, dbc.data)) - bAdd= false; - }*/ - if (bAdd) { -// if(!dbcells.contains(dbc)){ - dbc.setDBCNum(dbnum++); - dbcells.add(dbc); -// } - } - } - - void resetDBCells() { - dbnum = 0; - dbcells = new CompatibleVector(); - } - - /** - * Update the Dbcell Pointers - *

                  - * At this point the index should have all of it's dbcells in place, as well as - * thier offsets being populated, so all that should need to be done is to create - * the index correctly out of its values - */ - - void updateDbcellPointers() { - streamer = getSheet().streamer; - // first, get the collection of Rows from sheet - Boundsheet bs = this.getSheet(); - Row[] rowz = bs.getRows(); - if (rowz.length != 0) { - this.updateRowDimensions(rowz[0].getRowNumber(), rowz[(rowz.length) - 1].getRowNumber()); - } - // create the new Dbcells if any rows exist within the sheet - - // rebuild the record with the correct length body data to fit the new dbcells - int arrsize = 16 + (dbcells.size() * 4); - byte[] newBytes = new byte[arrsize]; - Dbcell dbc = null; - System.arraycopy(this.getData(), 0, newBytes, 0, 16); - int offset = 16; - for (int i = 0; i < dbcells.size(); i++) { - Dbcell db = (Dbcell) dbcells.elementAt(i); - int dbOff = db.getOffset(); - byte[] b = ByteTools.cLongToLEBytes(dbOff); - newBytes[offset++] = b[0]; - newBytes[offset++] = b[1]; - newBytes[offset++] = b[2]; - newBytes[offset++] = b[3]; - } - this.setData(newBytes); - } - - - /** - * update the dimensions info based on Dimensions rec - */ - void updateDimensions() { - byte[] rkdata = this.getData(); - byte[] newb = ByteTools.cLongToLEBytes(dims.offset); - rkdata[12] = newb[0]; - rkdata[13] = newb[1]; - rkdata[14] = newb[2]; - rkdata[15] = newb[3]; - - /* these should match rwMic/rwMac on dimensions rec - dims.rwMic; - dims.rwMac; - */ - } - - /** - * return the associated Dbcell objects - * for this INDEX. - */ - Dbcell[] getDBCells() { - Object[] obj = dbcells.toArray(); - Dbcell[] dbcs = new Dbcell[obj.length]; - System.arraycopy(obj, 0, dbcs, 0, obj.length); - return dbcs; - } - - public void init() { - super.init(); - // 1st 4 are reseverd-0 - rwMic = ByteTools.readInt(this.getBytesAt(4, 4)); - rwMac = ByteTools.readInt(this.getBytesAt(8, 4)); - // next 4 are position of defColWidth record - skip -/* no need to read in dbcell offsets as we don't do anything with it - * int pos= 16; - int recsize= 4; // KSC added - int numdbcells = (this.getLength()-pos)/recsize; -// dbcellarray = new dbCellPointer[numdbcells]; - // rest of data is position of dbCell records: rgibRw (variable): An array of FilePointer. Each FilePointer specifies the file position of each referenced DBCell record - for(int i = 0;i< numdbcells;i++){ - if(DEBUGLEVEL > 6)Logger.logInfo("Index -> initializing dbcell pointer: " + i); - byte[] bite = this.getBytesAt(pos,4); - dbCellPointer pointer = new dbCellPointer(bite); - pos += 4; -// dbcellarray[i] = pointer; - } -// */ - } - - /** compute the location of Dbcell records using - the INDEX dbcellpointers and the firstBof record position - in the workbook. - - NOT USED - * - int getDbcellPosition(int pointernum){ - int firstBofloc = wkbook.getFirstBof().offset; - byte[] b = dbcellarray[pointernum].cdb; - int pointerloc = ByteTools.readInt(b[0],b[1],b[2],b[3]); - return pointerloc + firstBofloc; - }*/ - - /** - * file offset to the Dbcell record - */ - class dbCellPointer implements Serializable { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5132922970171084839L; - int cellloc = 0; - int datasiz = 0; - short s2, s3; - byte[] cdb = new byte[4]; - - dbCellPointer(byte[] b) { - cdb = b; - cellloc = (int) ByteTools.readShort(b[0], b[1]); - datasiz = (int) ByteTools.readShort(b[2], b[3]); - } - - /*** Updates location of Dbcell pointer and data size(?) - */ - void adjustPosition(int i) { - cellloc += i; - datasiz += i; - } - - byte[] getBytes() { - byte[] bite = new byte[4]; - System.arraycopy(ByteTools.shortToLEBytes((short) cellloc), 0, bite, 0, 2); - System.arraycopy(ByteTools.shortToLEBytes((short) datasiz), 0, bite, 2, 2); - return bite; - } - } - - /** - * update the min/max cols and rows - * 8 rwMic 4 First row that exists on the sheet - * 12 rwMac 4 Last row that exists on the sheet, plus 1 - */ - public void updateRowDimensions(int lowRow, int hiRow) { - byte[] rw = ByteTools.cLongToLEBytes(lowRow); - System.arraycopy(rw, 0, this.getData(), 4, 4); - rw = ByteTools.cLongToLEBytes(hiRow + 1); - System.arraycopy(rw, 0, this.getData(), 8, 4); - } - - /** - * Called from streamer, this updates individual dbcell offset values. - *

                  - * Will only run correctly if called sequentially, ie dboffset [0], [1], [2] - * - * @param DbcellNumber - which dbcell to update - * @param DbOffset - the pure offset from beginning of file - */ - void setDbcellPosition(int DbcellNumber, int DbOffset) { - if (offsetStart == 0) { - offsetStart = this.getSheet().getMyBof().getOffset(); - } - int insertOffset = DbOffset - offsetStart; - if (DEBUGLEVEL > 10) { - Logger.logInfo("Setting DBBiffRec Position, offsetStart:" + offsetStart + " & InsertOffset = " + insertOffset); - } - offsetStart += insertOffset; - int insertloc = 16 + (DbcellNumber * 4); - byte[] off = ByteTools.cLongToLEBytes(insertOffset); - System.arraycopy(off, 0, data, insertloc, 4); - - } - - int offsetStart = 0; - - - /** - * Prestream for Index is going to create the correct size record, and populate the correct number of dbcells. - * The actual values will not yet be populated, but the record sizes will be correct in order to get offsets - * working correctly. - *

                  - * Once offsets are correctly calculated in bytestreamer.stream, we can come back and - * populate without the getIndex call overhead. - */ - public void preStream() { - // rebuild the record with the correct length body data to fit the new dbcells - this.getData(); - int arrsize = 16 + (dbcells.size() * 4); - byte[] newBytes = new byte[arrsize]; - Dbcell dbc = null; - // KSC: Changed from copying 12 bytes to copying 16 bytes to keep DIMENSIONS reference - System.arraycopy(this.getData(), 0, newBytes, 0, 16); - this.setData(newBytes); - } -} - diff --git a/src/main/java/io/starter/formats/XLS/Index.kt b/src/main/java/io/starter/formats/XLS/Index.kt new file mode 100644 index 0000000..0d455a2 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Index.kt @@ -0,0 +1,367 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + +import java.io.Serializable + + +/** + * **Index: Index Record 0x20B**

                  + * + * + * Index records are written after the Bof record for each Boundsheet. + * + *

                  + *
                  + * offset  name        size    contents
                  + * ---
                  + * 4       Reserved    4       Must be zero
                  + * 8       rwMic       4       First row that exists on the sheet
                  + * 12      rwMac       4       Last row that exists on the sheet, plus 1
                  + * 16      Reserved    4       Pointer to DIMENSIONS record for Boundsheet
                  + * 20      rgibRw      var     Array of file offsets to the Dbcell records
                  + * for each block of ROW records.  A block
                  + * contains up to 32 ROW records.
                  + *
                  + *
                  + * When a record value changes in size, it fires a CellChangeEvent
                  + * which cascades through the other associated objects.
                  + *
                  + * The record size change has the following effect on INDEX record fields:
                  + *
                  + * 1. ALL Dbcell records in the file starting with the one
                  + * containing the changed record move.  This requires updating
                  + * the dbcellpointers (rgibRw) in ALL INDEX records which
                  + * are located after the changed Dbcell within the file stream.
                  + *
                  +
                  * + * + * @see WorkBook + * + * @see Boundsheet + * + * @see Dbcell + * + * @see Row + * + * @see Cell + * + * @see XLSRecord + */ + +class Index : io.starter.formats.XLS.XLSRecord(), XLSConstants { + private var rwMic: Int = 0 + private var rwMac: Int = 0 + private var dbnum = 0 + // private dbCellPointer[] dbcellarray; not used + private var dbcells = CompatibleVector() + /** + * get the index number for + * addressing. + */ + /** + * set the index number for + * addressing. + */ + var indexNum: Int = 0 + private var dims: Dimensions? = null + + /** + * return the associated Dbcell objects + * for this INDEX. + */ + internal val dbCells: Array + get() { + val obj = dbcells.toTypedArray() + val dbcs = arrayOfNulls(obj.size) + System.arraycopy(obj, 0, dbcs, 0, obj.size) + return dbcs + } + + internal var offsetStart = 0 + + /** + * fire the cell change event + * + * + * public void fireCellChangeEvent(CellChangeEvent c){ + * // do its thing + * // this.doCellSizeChangeAction(c); + * // then pass it along... + * // this.getSheet().fireCellChangeEvent(c); + * } + */ + + internal fun setDimensions(d: Dimensions) { + this.dims = d + } + + + // Not used?? + internal fun setDimensionsOffset(offset: Int) { + val recData = this.getData() + val newoff = ByteTools.cLongToLEBytes(offset) + System.arraycopy(newoff, 0, recData!!, 12, 4) + this.setData(recData) + } + + + /** + * add an associated Dbcell object + * to this INDEX. + */ + internal fun addDBCell(dbc: Dbcell) { + val bAdd = true + /* KSC: TESTING testLostBorders bug for (int i= 0; i < dbcells.size() && bAdd; i++) { + if (Arrays.equals(((Dbcell) dbcells.get(i)).data, dbc.data)) + bAdd= false; + }*/ + if (bAdd) { + // if(!dbcells.contains(dbc)){ + dbc.dbcNum = dbnum++ + dbcells.add(dbc) + // } + } + } + + internal fun resetDBCells() { + dbnum = 0 + dbcells = CompatibleVector() + } + + /** + * Update the Dbcell Pointers + * + * + * At this point the index should have all of it's dbcells in place, as well as + * thier offsets being populated, so all that should need to be done is to create + * the index correctly out of its values + */ + + internal fun updateDbcellPointers() { + streamer = sheet!!.streamer + // first, get the collection of Rows from sheet + val bs = this.sheet + val rowz = bs!!.rows + if (rowz.size != 0) { + this.updateRowDimensions(rowz[0].rowNumber, rowz[rowz.size - 1].rowNumber) + } + // create the new Dbcells if any rows exist within the sheet + + // rebuild the record with the correct length body data to fit the new dbcells + val arrsize = 16 + dbcells.size * 4 + val newBytes = ByteArray(arrsize) + val dbc: Dbcell? = null + System.arraycopy(this.getData()!!, 0, newBytes, 0, 16) + var offset = 16 + for (i in dbcells.indices) { + val db = dbcells.elementAt(i) as Dbcell + val dbOff = db.getOffset() + val b = ByteTools.cLongToLEBytes(dbOff) + newBytes[offset++] = b[0] + newBytes[offset++] = b[1] + newBytes[offset++] = b[2] + newBytes[offset++] = b[3] + } + this.setData(newBytes) + } + + + /** + * update the dimensions info based on Dimensions rec + */ + internal fun updateDimensions() { + val rkdata = this.getData() + val newb = ByteTools.cLongToLEBytes(dims!!.offset) + rkdata[12] = newb[0] + rkdata[13] = newb[1] + rkdata[14] = newb[2] + rkdata[15] = newb[3] + + /* these should match rwMic/rwMac on dimensions rec + dims.rwMic; + dims.rwMac; + */ + } + + override fun init() { + super.init() + // 1st 4 are reseverd-0 + rwMic = ByteTools.readInt(this.getBytesAt(4, 4)!!) + rwMac = ByteTools.readInt(this.getBytesAt(8, 4)!!) + // next 4 are position of defColWidth record - skip + /* no need to read in dbcell offsets as we don't do anything with it + * int pos= 16; + int recsize= 4; // KSC added + int numdbcells = (this.getLength()-pos)/recsize; +// dbcellarray = new dbCellPointer[numdbcells]; + // rest of data is position of dbCell records: rgibRw (variable): An array of FilePointer. Each FilePointer specifies the file position of each referenced DBCell record + for(int i = 0;i< numdbcells;i++){ + if(DEBUGLEVEL > 6)Logger.logInfo("Index -> initializing dbcell pointer: " + i); + byte[] bite = this.getBytesAt(pos,4); + dbCellPointer pointer = new dbCellPointer(bite); + pos += 4; +// dbcellarray[i] = pointer; + } +// */ + } + + /** compute the location of Dbcell records using + * the INDEX dbcellpointers and the firstBof record position + * in the workbook. + * + * NOT USED + * + * int getDbcellPosition(int pointernum){ + * int firstBofloc = wkbook.getFirstBof().offset; + * byte[] b = dbcellarray[pointernum].cdb; + * int pointerloc = ByteTools.readInt(b[0],b[1],b[2],b[3]); + * return pointerloc + firstBofloc; + * } */ + + /** + * file offset to the Dbcell record + */ + internal inner class dbCellPointer(b: ByteArray) : Serializable { + var cellloc = 0 + var datasiz = 0 + var s2: Short = 0 + var s3: Short = 0 + var cdb = ByteArray(4) + + val bytes: ByteArray + get() { + val bite = ByteArray(4) + System.arraycopy(ByteTools.shortToLEBytes(cellloc.toShort()), 0, bite, 0, 2) + System.arraycopy(ByteTools.shortToLEBytes(datasiz.toShort()), 0, bite, 2, 2) + return bite + } + + init { + cdb = b + cellloc = ByteTools.readShort(b[0].toInt(), b[1].toInt()).toInt() + datasiz = ByteTools.readShort(b[2].toInt(), b[3].toInt()).toInt() + } + + /*** Updates location of Dbcell pointer and data size(?) + */ + fun adjustPosition(i: Int) { + cellloc += i + datasiz += i + } + + companion object { + + /** + * serialVersionUID + */ + private const val serialVersionUID = -5132922970171084839L + } + } + + /** + * update the min/max cols and rows + * 8 rwMic 4 First row that exists on the sheet + * 12 rwMac 4 Last row that exists on the sheet, plus 1 + */ + fun updateRowDimensions(lowRow: Int, hiRow: Int) { + var rw = ByteTools.cLongToLEBytes(lowRow) + System.arraycopy(rw, 0, this.getData()!!, 4, 4) + rw = ByteTools.cLongToLEBytes(hiRow + 1) + System.arraycopy(rw, 0, this.getData()!!, 8, 4) + } + + /** + * Called from streamer, this updates individual dbcell offset values. + * + * + * Will only run correctly if called sequentially, ie dboffset [0], [1], [2] + * + * @param DbcellNumber - which dbcell to update + * @param DbOffset - the pure offset from beginning of file + */ + internal fun setDbcellPosition(DbcellNumber: Int, DbOffset: Int) { + if (offsetStart == 0) { + offsetStart = this.sheet!!.myBof!!.getOffset() + } + val insertOffset = DbOffset - offsetStart + if (DEBUGLEVEL > 10) { + Logger.logInfo("Setting DBBiffRec Position, offsetStart:$offsetStart & InsertOffset = $insertOffset") + } + offsetStart += insertOffset + val insertloc = 16 + DbcellNumber * 4 + val off = ByteTools.cLongToLEBytes(insertOffset) + System.arraycopy(off, 0, data!!, insertloc, 4) + + } + + + /** + * Prestream for Index is going to create the correct size record, and populate the correct number of dbcells. + * The actual values will not yet be populated, but the record sizes will be correct in order to get offsets + * working correctly. + * + * + * Once offsets are correctly calculated in bytestreamer.stream, we can come back and + * populate without the getIndex call overhead. + */ + override fun preStream() { + // rebuild the record with the correct length body data to fit the new dbcells + this.getData() + val arrsize = 16 + dbcells.size * 4 + val newBytes = ByteArray(arrsize) + val dbc: Dbcell? = null + // KSC: Changed from copying 12 bytes to copying 16 bytes to keep DIMENSIONS reference + System.arraycopy(this.getData()!!, 0, newBytes, 0, 16) + this.setData(newBytes) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -753407655976707961L + private val defaultsize = 16 + + /** + * create a new INDEX rec + */ + // default val + val prototype: XLSRecord? + get() { + val idx = Index() + val dt = ByteArray(defaultsize) + idx.originalsize = defaultsize + idx.setData(dt) + idx.opcode = XLSConstants.INDEX + idx.length = defaultsize.toShort() + idx.init() + return idx + } + } +} + diff --git a/src/main/java/io/starter/formats/XLS/InterfaceHdr.kt b/src/main/java/io/starter/formats/XLS/InterfaceHdr.kt new file mode 100644 index 0000000..dcdc40a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/InterfaceHdr.kt @@ -0,0 +1,34 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +class InterfaceHdr : UnencryptedXLSRecord() { + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 1706123158030725587L + } + +} diff --git a/src/main/java/io/starter/formats/XLS/InvalidRecordException.java b/src/main/java/io/starter/formats/XLS/InvalidRecordException.java deleted file mode 100644 index af1403a..0000000 --- a/src/main/java/io/starter/formats/XLS/InvalidRecordException.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - - -public class InvalidRecordException extends java.lang.RuntimeException { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8754784258936538994L; - String errormsg = "Invalid Record Exception. "; - - public InvalidRecordException(String err) { - errormsg += err; - } - - public String toString() { - return errormsg; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/ConeChart.java b/src/main/java/io/starter/formats/XLS/InvalidRecordException.kt similarity index 66% rename from src/main/java/io/starter/formats/XLS/charts/ConeChart.java rename to src/main/java/io/starter/formats/XLS/InvalidRecordException.kt index 9b6aefc..b52a06b 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ConeChart.java +++ b/src/main/java/io/starter/formats/XLS/InvalidRecordException.kt @@ -20,20 +20,24 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS -import io.starter.formats.XLS.WorkBook; -public class ConeChart extends Col3DChart { +class InvalidRecordException(err: String) : java.lang.RuntimeException() { + internal var errormsg = "Invalid Record Exception. " - public ConeChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape = SHAPECONE; - chartobj.chartType = CYLINDERCHART; + init { + errormsg += err + } + override fun toString(): String { + return errormsg } - public int getBarShape() { - return defaultShape; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8754784258936538994L } -} +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Isstinf.java b/src/main/java/io/starter/formats/XLS/Isstinf.kt similarity index 66% rename from src/main/java/io/starter/formats/XLS/Isstinf.java rename to src/main/java/io/starter/formats/XLS/Isstinf.kt index d4f4509..b9793d8 100644 --- a/src/main/java/io/starter/formats/XLS/Isstinf.java +++ b/src/main/java/io/starter/formats/XLS/Isstinf.kt @@ -20,38 +20,43 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools -import java.io.Serializable; +import java.io.Serializable /** * ISSTINF Structure: - *

                  + * + * * offset name size contents * --- * 0 ib 4 Stream position into SST record * 4 cb 2 Offset into SST to where 'bucket' begins * 6 (Reserved) 2 Must be zero. - *

                  + * * * @see SST + * * @see EXTSST */ -public class Isstinf implements Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 989277769893893586L; - int ib; - short cb; - short myst; +class Isstinf(dta: ByteArray) : Serializable { + internal var ib: Int = 0 + internal var cb: Short = 0 + internal var myst: Short = 0 + + init { + this.ib = ByteTools.readInt(dta[0], dta[1], dta[2], dta[3]) + this.cb = ByteTools.readShort(dta[4].toInt(), dta[5].toInt()) + this.myst = ByteTools.readShort(dta[6].toInt(), dta[7].toInt()) + } - public Isstinf(byte[] dta) { - this.ib = ByteTools.readInt(dta[0], dta[1], dta[2], dta[3]); - this.cb = ByteTools.readShort(dta[4], dta[5]); - this.myst = ByteTools.readShort(dta[6], dta[7]); + companion object { + /** + * serialVersionUID + */ + private const val serialVersionUID = 989277769893893586L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Label.java b/src/main/java/io/starter/formats/XLS/Label.java deleted file mode 100644 index f214c27..0000000 --- a/src/main/java/io/starter/formats/XLS/Label.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - - -/** - * Label: BiffRec Value, String Constant (204h)
                  - * The Label record describes a cell that contains a string. - * The String length must be in the range 000h-00ffh (0-255). - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       rw          2       Row Number
                  - * 6       col         2       Column Number of the RK record
                  - * 8       ixfe        2       Index to XF cell format record
                  - * 10      cch         2       Length of the string
                  - * 12      rgch        var     The String
                  - * 

                  - * - * @see LABELSST - * @see STRING - * @see RSTRING - */ - -public final class Label - extends XLSCellRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2921430854162954640L; - int cch; - String val; - - public void init() { - super.init(); - short s, s1; - // get the row, col and ixfe information - s = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - rw = (int) s; - s = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - col = s; - s = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - ixfe = s; - // get the length of the string - s1 = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - cch = s1; - if (this.getByteAt(8) > 1) { // TODO KSC: Is this the correct indicator to read bytes as unicode?? - byte[] namebytes = this.getBytesAt(8, this.getLength() - 8); - val = new String(namebytes); - } else { - // 20060809 KSC: read correct bytes to interpret as unicode - try { - Unicodestring thistr = null; - byte[] tmpBytes = this.getBytesAt(6, cch * 2 + 4); // i.e. (cch * 2) - 2 - thistr = new Unicodestring(); - thistr.init(tmpBytes, false); - val = thistr.toString(); - } catch (Exception e) { - Logger.logWarn("ERROR Label.init: decoding string failed: " + e); - } - } - this.setIsValueForCell(true); - this.isString = true; - } - - public void setStringVal(String v) { - val = v; - int newstrlen = v.length(); - byte[] newbytes = new byte[newstrlen + 8]; - System.arraycopy(getData(), 0, newbytes, 0, 6); - // byte[] newlenbytes = bto - byte[] blen = ByteTools.cLongToLEBytes(newstrlen); - System.arraycopy(blen, 0, newbytes, 6, 2); - byte[] strbytes = v.getBytes(); - System.arraycopy(strbytes, 0, newbytes, 8, newstrlen); - this.setData(newbytes); - this.init(); - } - - void setStringVal(String v, boolean b) { - this.setStringVal(v); - } - - public String getStringVal() { - return val; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Label.kt b/src/main/java/io/starter/formats/XLS/Label.kt new file mode 100644 index 0000000..da59f0a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Label.kt @@ -0,0 +1,115 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * **Label: BiffRec Value, String Constant (204h)**

                  + * The Label record describes a cell that contains a string. + * The String length must be in the range 000h-00ffh (0-255). + * + *
                  + * offset  name        size    contents
                  + * ---
                  + * 4       rw          2       Row Number
                  + * 6       col         2       Column Number of the RK record
                  + * 8       ixfe        2       Index to XF cell format record
                  + * 10      cch         2       Length of the string
                  + * 12      rgch        var     The String
                  +
                  * + * + * @see LABELSST + * + * @see STRING + * + * @see RSTRING + */ + +class Label : XLSCellRecord() { + internal var cch: Int = 0 + internal var `val`: String? = null + + override// byte[] newlenbytes = bto + var stringVal: String? + get() = `val` + set(v) { + `val` = v + val newstrlen = v.length + val newbytes = ByteArray(newstrlen + 8) + System.arraycopy(getData()!!, 0, newbytes, 0, 6) + val blen = ByteTools.cLongToLEBytes(newstrlen) + System.arraycopy(blen, 0, newbytes, 6, 2) + val strbytes = v.toByteArray() + System.arraycopy(strbytes, 0, newbytes, 8, newstrlen) + this.setData(newbytes) + this.init() + } + + override fun init() { + super.init() + var s: Short + val s1: Short + // get the row, col and ixfe information + s = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + rw = s.toInt() + s = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + colNumber = s + s = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + ixfe = s.toInt() + // get the length of the string + s1 = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + cch = s1.toInt() + if (this.getByteAt(8) > 1) { // TODO KSC: Is this the correct indicator to read bytes as unicode?? + val namebytes = this.getBytesAt(8, this.length - 8) + `val` = String(namebytes!!) + } else { + // 20060809 KSC: read correct bytes to interpret as unicode + try { + var thistr: Unicodestring? = null + val tmpBytes = this.getBytesAt(6, cch * 2 + 4) // i.e. (cch * 2) - 2 + thistr = Unicodestring() + thistr.init(tmpBytes!!, false) + `val` = thistr.toString() + } catch (e: Exception) { + Logger.logWarn("ERROR Label.init: decoding string failed: $e") + } + + } + this.isValueForCell = true + this.isString = true + } + + internal fun setStringVal(v: String, b: Boolean) { + this.stringVal = v + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -2921430854162954640L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Labelsst.java b/src/main/java/io/starter/formats/XLS/Labelsst.java deleted file mode 100644 index 3c357e9..0000000 --- a/src/main/java/io/starter/formats/XLS/Labelsst.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.DateConverter; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.Calendar; -import java.util.GregorianCalendar; - - -/** - * Labelsst: BiffRec Value, String Constant/Sst 0xFD
                  - * The Labelsst record contains a string constant - * from the Shared String Table (Sst). - * The isst field contains a zero-based index into the shared string table - * - *
                  - * offset  name        size    contents
                  - * ---
                  - * 4       rw          2       Row Number
                  - * 6       col         2       Column Number
                  - * 8       ixfe        2       Index to XF format record
                  - * 10      isst        4       Index into the Sst record
                  - * 
                  - * - * @see Sst - * @see Labelsst - * @see Extsst - */ -public final class Labelsst - extends XLSCellRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 467127849827595055L; - int isst; - - void setIsst(int i) { - isst = i; - System.arraycopy(ByteTools.cLongToLEBytes(isst), 0, this.getData(), 6, 4); - try { - this.getWorkBook().getSharedStringTable().initSharingOnStrings(isst); - } catch (NullPointerException e) { - } - } - - /** - * Constructor which takes a number value - * an Sst to store its Unicodestring in, - * and returns an int offset to the string - * in the Sst. - */ - public static Labelsst getPrototype(String val, WorkBook bk) { - Labelsst retlab = new Labelsst(); - // associate with the Sst - retlab.originalsize = 10; - retlab.setOpcode(LABELSST); - retlab.setLength((short) 10); - retlab.setData(new byte[retlab.originalsize]); - //retlab.setDataContainsHeader(true); - if (val != null) { // for XLSX handling ... label is linked to sst later - // get the high Sst index, insert the new Unicodestring - Sst sst = bk.getSharedStringTable(); - retlab.isst = sst.insertUnicodestring(val); - System.arraycopy(ByteTools.cLongToLEBytes(retlab.isst), 0, retlab.getData(), 6, 4); - } else - retlab.isst = -1; // flag it's not set - MUST be set later - retlab.getData()[4] = 0x0f; - retlab.setWorkBook(bk); - retlab.init(); - return retlab; - } - - public void init() { - super.init(); - - // this.initCacheBytes(0,10); - // get the row, col and ixfe information - super.initRowCol(); - short s = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - ixfe = (int) s; - // get the length of the string - isst = - ByteTools.readInt( - this.getByteAt(6), - this.getByteAt(7), - this.getByteAt(8), - this.getByteAt(9)); - this.setIsValueForCell(true); - this.isString = true; - this.resetCacheBytes(); - // init shared string info. - if (isst != -1) {// not initialized - OOXML use - MUST be set later using setIsst - try { - this.getWorkBook().getSharedStringTable().initSharingOnStrings(isst); - } catch (NullPointerException e) { - // nothing. When adding new strings we have access issues, but it doesn't matter, we just care on book initialization for this.. - } - } - } - - - private Unicodestring unsharedstr; - - - void initUnsharedString() { - unsharedstr = this.getWorkBook().getSharedStringTable().getUStringAt(isst); - } - - public Unicodestring getUnsharedString() { - if (unsharedstr == null) - this.initUnsharedString(); - return unsharedstr; - } - - /** - * Adds the LabelSST's string to the sst. - *

                  - * This is used when a worksheet is transferred over to a book that - * does not contain it's entry in the sst. - */ - boolean insertUnsharedString(Sst sst) { - if (unsharedstr == null) { - return false; - } - this.isst = sst.insertUnicodestring(unsharedstr.toString()); - this.setIsst(isst); - return true; - } - - /** - * Returns the value of the Unicodestring - * int the Shared String Table pointed to by this - * LABELSst record. - */ - public String getStringVal() { - if (unsharedstr != null) - return unsharedstr.toString(); - else - return this.getWorkBook().getSharedStringTable().getUStringAt(isst).toCachingString(); - } - - /** - * try to convert the String Value of this Labelsst record to an int - * If it cannot be converted, returns NaN. - */ - public int getIntVal() { - String s = getStringVal(); - try { - Integer i = Integer.valueOf(s); - return i.intValue(); - } catch (NumberFormatException n) { - return (int) Float.NaN; - } - } - - /** - * try to convert the String Value of this Labelsst record to a double - * If it cannot be converted,return NaN. - */ - public double getDblVal() { - String s = getStringVal(); - try { - Double d = new Double(s); - return d.doubleValue(); - } catch (NumberFormatException n) { - this.getXfRec(); - if (myxf.isDatePattern()) { // use it - try { - String format = myxf.getFormatPattern(); - WorkBookHandle.simpledateformat.applyPattern(format); - java.util.Date d = WorkBookHandle.simpledateformat.parse(s); - Calendar c = new GregorianCalendar(); - c.setTime(d); - if (c == null) return Double.NaN; - return DateConverter.getXLSDateVal(c); - } catch (Exception e) { - // fall through - } - } - Calendar c = DateConverter.convertStringToCalendar(s); - if (c == null) return Double.NaN; - return DateConverter.getXLSDateVal(c); - } - } - - /** - * set a new value for the string - */ - public void setStringVal(String v) { - String ov = this.getStringVal(); - if (v.equals(ov)) return; - if (this.getSheet().getWorkBook().getSharedStringTable().isSharedString(isst)) { - isst = this.getSheet().getWorkBook().getSharedStringTable().insertUnicodestring(v); - System.arraycopy(ByteTools.cLongToLEBytes(isst), 0, getData(), 6, 4); - init(); - // reset unsharedstr (see getStringVal) specifically to fix OOXML t="s" setStringVal - Unicodestring str = this.getSheet().getWorkBook().getSharedStringTable().getUStringAt(isst); - this.unsharedstr = str; - } else { - Unicodestring str = this.getSheet().getWorkBook().getSharedStringTable().getUStringAt(isst); - //ensure reclen and datalen are maintained correctly: - int origLen = str.getLength(); - str.updateUnicodeString(v); - int delta = str.getLength() - origLen; - this.getSheet().getWorkBook().getSharedStringTable().adjustSstLength(delta); - this.unsharedstr = str; - } - } - - /** - * set this Label cell to a new Unicode string - * Rich Unicode strings include formatting information - */ - // 20090520 KSC: for OOXML, must use entire Unicode string so retain formatting info - public void setStringVal(Unicodestring v) { - if (v.equals(this.getUnsharedString())) - return; - isst = this.getSheet().getWorkBook().getSharedStringTable().find(v); // find this particular unicode string (including formatting) - if (isst == -1) - isst = this.getSheet().getWorkBook().getSharedStringTable().insertUnicodestring(v); - System.arraycopy(ByteTools.cLongToLEBytes(isst), 0, getData(), 6, 4); - init(); - this.unsharedstr = v; - } - - - /** - * return string representation - */ - public String toString() { - try { - return "LABELSST:" + this.getCellAddress() + ":" + getStringVal(); - } catch (Exception e) { - Logger.logErr("Labelsst toString failed.", e); - return "#ERR!"; - } - } -} diff --git a/src/main/java/io/starter/formats/XLS/Labelsst.kt b/src/main/java/io/starter/formats/XLS/Labelsst.kt new file mode 100644 index 0000000..970a4d7 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Labelsst.kt @@ -0,0 +1,272 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.DateConverter +import io.starter.OpenXLS.WorkBookHandle +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.Calendar +import java.util.GregorianCalendar + + +/** + * **Labelsst: BiffRec Value, String Constant/Sst 0xFD**

                  + * The Labelsst record contains a string constant + * from the Shared String Table (Sst). + * The isst field contains a zero-based index into the shared string table + * + *

                  + * offset  name        size    contents
                  + * ---
                  + * 4       rw          2       Row Number
                  + * 6       col         2       Column Number
                  + * 8       ixfe        2       Index to XF format record
                  + * 10      isst        4       Index into the Sst record
                  +
                  * + * + * @see Sst + * + * @see Labelsst + * + * @see Extsst + */ +class Labelsst : XLSCellRecord() { + internal var isst: Int = 0 + + + private var unsharedstr: Unicodestring? = null + + val unsharedString: Unicodestring? + get() { + if (unsharedstr == null) + this.initUnsharedString() + return unsharedstr + } + + /** + * Returns the value of the Unicodestring + * int the Shared String Table pointed to by this + * LABELSst record. + */ + /** + * set a new value for the string + */ + override// reset unsharedstr (see getStringVal) specifically to fix OOXML t="s" setStringVal + //ensure reclen and datalen are maintained correctly: + var stringVal: String? + get() = if (unsharedstr != null) + unsharedstr!!.toString() + else + this.workBook!!.sharedStringTable!!.getUStringAt(isst).toCachingString() + set(v) { + val ov = this.stringVal + if (v == ov) return + if (this.sheet!!.workBook!!.sharedStringTable!!.isSharedString(isst)) { + isst = this.sheet!!.workBook!!.sharedStringTable!!.insertUnicodestring(v) + System.arraycopy(ByteTools.cLongToLEBytes(isst), 0, getData()!!, 6, 4) + init() + val str = this.sheet!!.workBook!!.sharedStringTable!!.getUStringAt(isst) + this.unsharedstr = str + } else { + val str = this.sheet!!.workBook!!.sharedStringTable!!.getUStringAt(isst) + val origLen = str.length + str.updateUnicodeString(v) + val delta = str.length - origLen + this.sheet!!.workBook!!.sharedStringTable!!.adjustSstLength(delta) + this.unsharedstr = str + } + } + + /** + * try to convert the String Value of this Labelsst record to an int + * If it cannot be converted, returns NaN. + */ + override var intVal: Int + get() { + val s = stringVal + try { + val i = Integer.valueOf(s!!) + return i.toInt() + } catch (n: NumberFormatException) { + return java.lang.Float.NaN.toInt() + } + + } + set + + /** + * try to convert the String Value of this Labelsst record to a double + * If it cannot be converted,return NaN. + */ + override// use it + // fall through + val dblVal: Double + get() { + val s = stringVal + try { + return Double(s!!) + } catch (n: NumberFormatException) { + this.xfRec + if (myxf!!.isDatePattern) { + try { + val format = myxf!!.formatPattern + WorkBookHandle.simpledateformat.applyPattern(format!!) + val d = WorkBookHandle.simpledateformat.parse(s) + val c = GregorianCalendar() + c.time = d + return if (c == null) java.lang.Double.NaN else DateConverter.getXLSDateVal(c) + } catch (e: Exception) { + } + + } + val c = DateConverter.convertStringToCalendar(s) ?: return java.lang.Double.NaN + return DateConverter.getXLSDateVal(c) + } + + } + + internal fun setIsst(i: Int) { + isst = i + System.arraycopy(ByteTools.cLongToLEBytes(isst), 0, this.getData()!!, 6, 4) + try { + this.workBook!!.sharedStringTable!!.initSharingOnStrings(isst) + } catch (e: NullPointerException) { + } + + } + + override fun init() { + super.init() + + // this.initCacheBytes(0,10); + // get the row, col and ixfe information + super.initRowCol() + val s = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + ixfe = s.toInt() + // get the length of the string + isst = ByteTools.readInt( + this.getByteAt(6), + this.getByteAt(7), + this.getByteAt(8), + this.getByteAt(9)) + this.isValueForCell = true + this.isString = true + this.resetCacheBytes() + // init shared string info. + if (isst != -1) {// not initialized - OOXML use - MUST be set later using setIsst + try { + this.workBook!!.sharedStringTable!!.initSharingOnStrings(isst) + } catch (e: NullPointerException) { + // nothing. When adding new strings we have access issues, but it doesn't matter, we just care on book initialization for this.. + } + + } + } + + + internal fun initUnsharedString() { + unsharedstr = this.workBook!!.sharedStringTable!!.getUStringAt(isst) + } + + /** + * Adds the LabelSST's string to the sst. + * + * + * This is used when a worksheet is transferred over to a book that + * does not contain it's entry in the sst. + */ + internal fun insertUnsharedString(sst: Sst): Boolean { + if (unsharedstr == null) { + return false + } + this.isst = sst.insertUnicodestring(unsharedstr!!.toString()) + this.setIsst(isst) + return true + } + + /** + * set this Label cell to a new Unicode string + * Rich Unicode strings include formatting information + */ + // 20090520 KSC: for OOXML, must use entire Unicode string so retain formatting info + fun setStringVal(v: Unicodestring) { + if (v == this.unsharedString) + return + isst = this.sheet!!.workBook!!.sharedStringTable!!.find(v) // find this particular unicode string (including formatting) + if (isst == -1) + isst = this.sheet!!.workBook!!.sharedStringTable!!.insertUnicodestring(v) + System.arraycopy(ByteTools.cLongToLEBytes(isst), 0, getData()!!, 6, 4) + init() + this.unsharedstr = v + } + + + /** + * return string representation + */ + override fun toString(): String { + try { + return "LABELSST:" + this.cellAddress + ":" + stringVal + } catch (e: Exception) { + Logger.logErr("Labelsst toString failed.", e) + return "#ERR!" + } + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 467127849827595055L + + /** + * Constructor which takes a number value + * an Sst to store its Unicodestring in, + * and returns an int offset to the string + * in the Sst. + */ + fun getPrototype(`val`: String?, bk: WorkBook): Labelsst { + val retlab = Labelsst() + // associate with the Sst + retlab.originalsize = 10 + retlab.opcode = XLSConstants.LABELSST + retlab.length = 10.toShort() + retlab.setData(ByteArray(retlab.originalsize)) + //retlab.setDataContainsHeader(true); + if (`val` != null) { // for XLSX handling ... label is linked to sst later + // get the high Sst index, insert the new Unicodestring + val sst = bk.sharedStringTable + retlab.isst = sst!!.insertUnicodestring(`val`) + System.arraycopy(ByteTools.cLongToLEBytes(retlab.isst), 0, retlab.getData()!!, 6, 4) + } else + retlab.isst = -1 // flag it's not set - MUST be set later + retlab.getData()[4] = 0x0f + retlab.workBook = bk + retlab.init() + return retlab + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/LeftMargin.java b/src/main/java/io/starter/formats/XLS/LeftMargin.kt similarity index 53% rename from src/main/java/io/starter/formats/XLS/LeftMargin.java rename to src/main/java/io/starter/formats/XLS/LeftMargin.kt index dc97b04..86c9725 100644 --- a/src/main/java/io/starter/formats/XLS/LeftMargin.java +++ b/src/main/java/io/starter/formats/XLS/LeftMargin.kt @@ -20,42 +20,44 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** * Record specifying the left margin of the sheet for printing. */ -public class LeftMargin - extends XLSRecord { - private static final long serialVersionUID = -3649192673573344145L; +class LeftMargin : XLSRecord() { - double margin; + internal var margin: Double = 0.toDouble() - public void init() { - super.init(); + override fun init() { + super.init() - margin = ByteTools.eightBytetoLEDouble(getBytesAt(0, 8)); + margin = ByteTools.eightBytetoLEDouble(getBytesAt(0, 8)!!) } - public LeftMargin() { - this.setOpcode(LEFTMARGIN); - margin = 0.75; // default - setData(ByteTools.doubleToLEByteArray(margin)); + init { + this.opcode = XLSConstants.LEFTMARGIN + margin = 0.75 // default + setData(ByteTools.doubleToLEByteArray(margin)) } - public void setSheet(Sheet sheet) { - super.setSheet(sheet); - ((Boundsheet) sheet).addPrintRec(this); + override fun setSheet(sheet: Sheet?) { + super.setSheet(sheet) + (sheet as Boundsheet).addPrintRec(this) } - public double getMargin() { - return margin; + fun getMargin(): Double { + return margin } - public void setMargin(double value) { - margin = value; - setData(ByteTools.doubleToLEByteArray(value)); + fun setMargin(value: Double) { + margin = value + setData(ByteTools.doubleToLEByteArray(value)) + } + + companion object { + private val serialVersionUID = -3649192673573344145L } } diff --git a/src/main/java/io/starter/formats/XLS/MSODrawing.java b/src/main/java/io/starter/formats/XLS/MSODrawing.java deleted file mode 100644 index c787215..0000000 --- a/src/main/java/io/starter/formats/XLS/MSODrawing.java +++ /dev/null @@ -1,2220 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ColHandle; -import io.starter.OpenXLS.RowHandle; -import io.starter.formats.escher.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; - - -/** - * Msodrawing: MS Office Drawing (ECh)
                  - *

                  - * These records contain only data.

                  - *
                  - * offset  name        size    contents
                  - * ---
                  - * 4       rgMSODr     var    MSO Drawing Data
                  - *
                  - * 

                  - *

                  - * The Msodrawing record represents the MSOFBTDGCONTAINER (0xF002) in the Drawing Layer (Escher) format, and contains all per-sheet - * types of info, including the shapes themselves. (A shape=the elemental object that composes a drawing. All graphical figures on a - * drawing are shapes). With few exceptions, shapes are stored hierachically according to how they've been grouped thru the use of - * the Draw/Group command). - * Each Msodrawing record contains several sub-records or atoms (atoms are records that are kept inside container records; container - * records keep atoms and other container records organized). - * There are several such records that are important to us (these are always present - except for MSOFBTDG in subsequent recs): - * MSOFBTDG- Basic Drawing Info- #shapes in this drawing; last SPID given to an SP in this Drawing Group - * MSOFBTSPGRCONTAINER- Patriarch shape Container - always first MSOFBTSPGRCONTAINER in the drawing container - * MSOFBTSPCONTAINER- Shape Container - * MSOFBTSP- Shape Atom Record- SPID= shape id + a set of flags - * MSOFBTOPT- Property Table Record Associated with Shape Rec- holds image Name, index + many other properties - * MSOFBTCLIENTANCHOR- Client Anchor rec- holds size/bounds info - * MSOFBTCLIENTDATA- Host-specific client data record - *

                  - * There are many other records or atoms that are optional and that we will omit for now. - *

                  - * There appears to be 1 msodrawing record per image (there is also one OBJ record per Msodrawing record). - * The first or header msodrawing record contains MSODBTDG for # shapes - * There is one MsodrawingGroup record per file. This MsodrawingGroup record also contains sub-records that hold # shapes - *

                  - * Occasionally, when there is a lot of image data, there can be 2 MSODRAWINGGROUP objects -- continue recs will follow - * the second MSODG only. - *

                  - * SPIDs are unique per drawing group, and are parceled out by the drawing group to individual drawings in blocks of 1024 - * - * @see MSODrawingGroup - */ -// TODO: MSOFBTCLIENTANCHOR may be substituted for MSOFBTANCHOR (clipboard), MSOFBTCHILDANCHOR (if shape is a child of a group shape) -public final class MSODrawing extends io.starter.formats.XLS.XLSRecord { - private static final long serialVersionUID = 8275831369787287975L; - - public byte[] PROTOTYPE_BYTES = {15, 0, 4, -16, 92, 0, 0, 0, -78, 4, 10, -16, 8, 0, 0, 0, 2, 4, 0, 0, 0, 10, 0, 0, 35, 0, 11, -16, 34, 0, 0, 0, 4, 65, 2, 0, 0, 0, 5, -63, 22, 0, 0, 0, 66, 0, 108, 0, 117, 0, 101, 0, 32, 0, 104, 0, 105, 0, 108, 0, 108, 0, 115, 0, 0, 0, 0, 0, 16, -16, 18, 0, 0, 0, 2, 0, 2, 0, 0, 0, 11, 0, 0, 0, 6, 0, 0, 0, 22, 0, 75, 0, 0, 0, 17, -16, 0, 0, 0, 0}; - - int imageIndex = -1; - boolean bActive = false; // true if this image is Active (not deleted) - NOTE: setting Active algorithm is not definitively proven - String imageName = "", shapeName = ""; - short clientAnchorFlag; // MSOFBTCLIENTANCHOR 1st two bytes - * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells. */ - short[] bounds = new short[8]; // MSOFBTCLIENTANCHOR- - short origHeight, origWidth; // save original height and width so that if underlying row height(s) or column width(s) change, can still set dimensions correctly ... - private MsofbtOPT optrec = null; // 20091209 KSC: save MsofbtOPT records for later updating ... - private MsofbtOPT secondaryoptrec = null, tertiaryoptrec = null; // apparently can have secondary and tertiary obt recs depending upon version in which it was saved ... - short shapeType = 0; // shape type for this drawing record - - private int SPIDSEED = 1024; - boolean bIsHeader = false; // whether "this is the 1st Msodrawing rec in the sheet" - contains several header records - int SPID = 0; // Shape ID - int SPCONTAINERLENGTH = 0; // this shape's container length - boolean isShape = true; // false for Mso's which do not contain an SPCONTAINER sub-record (can be attached textbox or solver container); not included in number of shapes count - // only applicable to header records *** - int numShapes = 1; // TODO: how do we know the value for a new image???? - int lastSPID = SPIDSEED; // lastSPID is stored at book level so that we can track max SPIDs - useful when images have been deleted and SPIDs are not in order ... - static final int HEADERRECLENGTH = 24; // - int otherSPCONTAINERLENGTH = 0; // sum of other SPCONTAINERLENGTHs from other Msodrawing recs - int SOLVERCONTAINERLENGTH = 0; // Solver Container length - int drawingId = 0; // ordinal # of this drawing record in the workbook - - /** - * create a new msodrawing record with the desired SPID, imageName, shapeName and imageIndex - * bounds should also be set? - * create correct record bytes - * - * @param spid - * @param imageName - * @param shapeName - * @param imageIndex - * @return - */ - public byte[] createRecord(int spid, String imageName, String shapeName, int imageIndex) { - this.imageName = imageName; - this.shapeName = shapeName; - this.imageIndex = imageIndex; - - byte[] retData; - // Order of Msodrawing required records: - /* MSOFBTDG - * MSOFBTSPGRCONTAINER - * MSOFBTSPCONTAINER - * MSOFBTSP - * MSOFBTOPT - * MSOFBTCLIENTANCHOR (MSOFBTCHILDANCHOR, MSOFBTANCHOR) - * MSOFBTCLIENTDATA - * - * NOTE that every container has a length field that = the sum of the length of all the atoms (records) it contains as well as the length - * of its header - */ - // key sub-records to update: - // MSOFBTSP, MSOFBTOPT (image index, shape name, image name), CLIENTANCHOR if present - bounds - // plus container which must be calculated from it's sub-records or atoms - this.SPID = spid; - //this.SPID = SPIDSEED + imageIndex;// algorithm is incorrect for instances when images are deleted or out of order ... - // Following are present in all msodrawing: MSOFBTSPCONTAINER MSOFBTSP MSOFBTOPT MSOFBTCLIENTANCHOR MSOFBTCLIENTDATA -- not true! can have CHILDANCHOR, ANCHOR ... - //Shape Atom; shape type must be msosptPictureFrame = 75 - MsofbtSp msofbtSp1 = new MsofbtSp(MSODrawingConstants.MSOFBTSP, shapeType, 2); - msofbtSp1.setId(SPID); - msofbtSp1.setGrfPersistence(2560); //flag= hasSp type + has anchor -- usual for shape-type msoFbtSp's - byte[] msofbtSp1Bytes = msofbtSp1.toByteArray(); - - - // OPT= picture options - optrec = new MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3); //version is always 3, inst is current count of properties. - if (imageIndex != -1) - optrec.setImageIndex(imageIndex); - if (imageName != null && !imageName.equals("")) - optrec.setImageName(imageName); - if (shapeName != null && !shapeName.equals("")) - optrec.setShapeName(shapeName); - - byte[] msofbtOPT1Bytes = optrec.toByteArray(); - - // Client Anchor==Bounds - MsofbtClientAnchor msofbtClientAnchor1 = new MsofbtClientAnchor(MSODrawingConstants.MSOFBTCLIENTANCHOR, 0, 0); - msofbtClientAnchor1.setBounds(bounds); - byte[] msofbtClientAnchor1Bytes = msofbtClientAnchor1.toByteArray(); - - MsofbtClientData msofbtClientData1 = new MsofbtClientData(MSODrawingConstants.MSOFBTCLIENTDATA, 0, 0); //This is an empty record - byte[] msofbtClientData1Bytes = msofbtClientData1.toByteArray(); - - SPCONTAINERLENGTH = msofbtSp1Bytes.length + msofbtOPT1Bytes.length + msofbtClientAnchor1Bytes.length + msofbtClientData1Bytes.length; - - // 20100412 KSC: must count "oddball" msofbtClientTextBox record that follows this Mso's obj record ... - if (shapeType == MSODrawingConstants.msosptTextBox) { - SPCONTAINERLENGTH += 8; - } - MsofbtSpContainer msofbtSpContainer1 = new MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15); - msofbtSpContainer1.setLength(SPCONTAINERLENGTH); - byte[] msofbtSpContainer1Bytes = msofbtSpContainer1.toByteArray(); - SPCONTAINERLENGTH += +msofbtSpContainer1Bytes.length; // include this rec - retData = new byte[SPCONTAINERLENGTH]; - - int pos = 0; - System.arraycopy(msofbtSpContainer1Bytes, 0, retData, pos, msofbtSpContainer1Bytes.length); - pos += msofbtSpContainer1Bytes.length; - System.arraycopy(msofbtSp1Bytes, 0, retData, pos, msofbtSp1Bytes.length); - pos += msofbtSp1Bytes.length; - System.arraycopy(msofbtOPT1Bytes, 0, retData, pos, msofbtOPT1Bytes.length); - pos += msofbtOPT1Bytes.length; - System.arraycopy(msofbtClientAnchor1Bytes, 0, retData, pos, msofbtClientAnchor1Bytes.length); - pos += msofbtClientAnchor1Bytes.length; - System.arraycopy(msofbtClientData1Bytes, 0, retData, pos, msofbtClientData1Bytes.length); - pos += msofbtClientData1Bytes.length; // 20100420 KSC: empty client data record- necessary??? - - if (bIsHeader) { //This is only present in the first msodrawing per sheet - if (lastSPID < SPID) - lastSPID = SPID; // TODO: Shouldn't assume to be SPID - int totalSPRECORDS = 0; - - // Header also contains Shape Id Seed SP record - MsofbtSp msofbtSp = new MsofbtSp(MSODrawingConstants.MSOFBTSP, MSODrawingConstants.msosptMin, 2); //1st shape rec is of type MSOSPTMIN - msofbtSp.setId(SPIDSEED); // SPMIN==SPIDSEED - msofbtSp.setGrfPersistence(5); // flag= fPatriarch, - byte[] msofbtSpBytes = msofbtSp.toByteArray(); - - MsofbtSpgr msofbtSpgr = new MsofbtSpgr(MSODrawingConstants.MSOFBTSPGR, 0, 1); - msofbtSpgr.setRect(0, 0, 0, 0); - byte[] msofbtSpgrBytes = msofbtSpgr.toByteArray(); - totalSPRECORDS = msofbtSpgrBytes.length + msofbtSpBytes.length; - - MsofbtSpContainer msofbtSpContainer = new MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15); - msofbtSpContainer.setLength(totalSPRECORDS); - byte[] msofbtSpContainerBytes = msofbtSpContainer.toByteArray(); - totalSPRECORDS += msofbtSpContainerBytes.length; - - SPCONTAINERLENGTH += totalSPRECORDS; - MsofbtSpgrContainer msofbtSpgrContainer = new MsofbtSpgrContainer(MSODrawingConstants.MSOFBTSPGRCONTAINER, 0, 15); - msofbtSpgrContainer.setLength(SPCONTAINERLENGTH + otherSPCONTAINERLENGTH); - byte[] msofbtSpgrContainerBytes = msofbtSpgrContainer.toByteArray(); - - MsofbtDg msofbtDg = new MsofbtDg(MSODrawingConstants.MSOFBTDG, drawingId, 0); - msofbtDg.setNumShapes(numShapes); //Number of images and drawings. - msofbtDg.setLastSPID(lastSPID); //lastSPID - byte[] msofbtDgBytes = msofbtDg.toByteArray(); - - MsofbtDgContainer msofbtDgContainer = new MsofbtDgContainer(MSODrawingConstants.MSOFBTDGCONTAINER, 0, 15); - msofbtDgContainer.setLength(HEADERRECLENGTH + SPCONTAINERLENGTH + otherSPCONTAINERLENGTH); - byte[] msofbtDgContainerBytes = msofbtDgContainer.toByteArray(); - - byte[] headerRec = new byte[80 + retData.length]; //+retData.length]; - - pos = 0; - System.arraycopy(msofbtDgContainerBytes, 0, headerRec, pos, msofbtDgContainerBytes.length); - pos += msofbtDgContainerBytes.length; - System.arraycopy(msofbtDgBytes, 0, headerRec, pos, msofbtDgBytes.length); - pos += msofbtDgBytes.length; - System.arraycopy(msofbtSpgrContainerBytes, 0, headerRec, pos, msofbtSpgrContainerBytes.length); - pos += msofbtSpgrContainerBytes.length; - System.arraycopy(msofbtSpContainerBytes, 0, headerRec, pos, msofbtSpContainerBytes.length); - pos += msofbtSpContainerBytes.length; - System.arraycopy(msofbtSpgrBytes, 0, headerRec, pos, msofbtSpgrBytes.length); - pos += msofbtSpgrBytes.length; - System.arraycopy(msofbtSpBytes, 0, headerRec, pos, msofbtSpBytes.length); - pos += msofbtSpBytes.length; - - System.arraycopy(retData, 0, headerRec, pos, retData.length); - retData = headerRec; - - } - - this.setData(retData); - this.setLength(data.length); - return retData; - } - - - /** - * parse the data contained in this drawing record - */ - public void init() { - // ************************************************************************************************************************************* - // 20070910 KSC: parse MSO record + atom ids (records keep atoms and other containers; atoms contain info and are kept inside containers) - // common record header for both: ver, inst, fbt, len; fbt deterimes record type (0xF000 to 0xFFFF) - // for a specific record, inst differentiates atoms - // for atoms, ver= version; for records, ver= 0xFFFF - // for atoms, len= of atom excluding header; for records; sum of len of atoms contained within it - - /* an Msodrawing record may contain the following records and atoms: - * MSOFBTDG // drawing record: count + MSOSPID seed - MSOFBTREGROUPITEMS // Mappings to reconstitute groups (for regrouping) - MSOFBTCOLORSCHEME - MSOFBTSPGRCONTAINER // Group Shape Container - MSOFBTSPCONTAINER // Shape Container - MSOFBTSPGR // Group-shape-specific info (i.e. shapes that are groups) optional - ** MSOFBTSP // A Shape atom record ** - ** MSOFBTOPT // The Property Table for a shape ** - image index + name ... - MSOFBTTEXTBOX // if the shape has text - MSOFBTCLIENTTEXTBOX // for clipboard stream - MSOFBTANCHOR // Anchor or location fo a shape (if streamed to a clipboard) optional - MSOFBTCHILDANCHOR // " ", if shape is a child of a group shape optional - ** MSOFBTCLIENTANCHOR // Client Anchor/Bounds ** - MSOFBTCLIENTDATA // content is determined by host optional - MSOFBTOLEOBJECT // optional - MSOFBTDELETEDPSPL // optional - */ - // ************************************************************************************************************************************* - super.init(); - - ByteArrayInputStream bis = new ByteArrayInputStream(super.getData()); - int version, inst, fbt, len; - SPCONTAINERLENGTH = 0; // this shape container length - otherSPCONTAINERLENGTH = 0; // if header, all other SPCONTAINERLENGTHS -- calc from DGCONTAINERLENGTH + SPCONTAINERLENGTHS - - int SPGRCONTAINERLENGTH = 0; // group shape container length - int SPCONTAINERATOMS = 0; // atoms or sub-records which make up the shape container - int DGCONTAINERLENGTH = 0; // drawing container length - int DGCONTAINERATOMS = 0; // atoms or sub-records which, along with SPGRCONTAINER + SOLVERCONTAINER(s), make up the DGCONTAINERLENGHT - int SOLVERCONTAINERATOMS = 0; // atoms or sub-records which make up the SOLVERCONTAINERLENGTH - boolean hasUndoInfo = false; // true if a non-header Mso contains an SPGRCONTAINER - documentation states: Shapes that have been deleted but that could be brought back via Undo. - for (; bis.available() > 0; ) { - byte[] dat = new byte[8]; - bis.read(dat, 0, 8); - version = (0xF & dat[0]); // 15 for containers, version for atoms - inst = ((0xFF & dat[1]) << 4) | (0xF0 & dat[0]) >> 4; - fbt = ((0xFF & dat[3]) << 8) | (0xFF & dat[2]); // record type id - len = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]); // for atoms, record length - header length (=8), if container, refers to sum of lengths of atoms inside it, incl. record headers - if (version == 15) {// do not parse containers - // MSOFBTSPGRCONTAINER: // Shape Group Container, contains a variable number of shapes (=msofbtSpContainer) + other groups 0xF003 - // MSOFBTSPCONTAINER: // Shape Container 0xF004 - // may have several SPCONTAINERs, 1 for background shape, several for deleted shapes ... - // possible containers - // DGCONTAINER= DG, REGROUPITEMS, ColorSCHEME, SPGR, SPCONTAINER - // SPGRCONTAINER= SPCONTAINER(s) - // SPCONTAINER= SPGR, SP, OPT, TEXTBOX, ANCHOR, CHILDANCHOR, CLIENTANCHOR, CLIENTDATA, OLEOBJECT, DeletedPSPL - // SOLVERCONTAINER= ConnetorRule, AlignRule, ArcRule, ClientRule, CalloutRule, - if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) { - bIsHeader = true; - otherSPCONTAINERLENGTH = len; //-HEADERRECLENGTH; - DGCONTAINERLENGTH = len; - } else if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) {//patriarch shape, with all non-background non-deleted shapes in it - may have more than 1 subrecord - // A group is a collection of other shapes. The contained shapes are placed in the coordinate system of the group. - // The group container contains a variable number of shapes (msofbtSpContainer) and other groups (msofbtSpgrContainer, for nested groups). - // The group itself is a shape, and always appears as the first msofbtSpContainer in the group container. - if (SPGRCONTAINERLENGTH == 0) // only add 1st container length - others are deleted shapes ... - SPGRCONTAINERLENGTH = len; - if (!bIsHeader)// then this is a grouped shape, must add the header length as is apparent in existing container lengths (see below) - hasUndoInfo = true; - } else if (fbt == MSODrawingConstants.MSOFBTSPCONTAINER) { - SPCONTAINERLENGTH += (len + 8); // add 8 for record header - if (bIsHeader) // keep track of total "other sp container length" - necessary to calculate SPGRCONTAINERLENGTH + DGCONTAINERLENGTH - otherSPCONTAINERLENGTH -= (len + 8); - isShape = true; // any mso that contains a normal "spcontainer" is a shape - } else if (fbt == MSODrawingConstants.MSOFBTSOLVERCONTAINER) { // solver container: rules governing shapes - isShape = false; - SOLVERCONTAINERLENGTH = len + 8; // added to dgcontainerlength - } else { - Logger.logInfo("MSODrawing.init: unknown container encountered: " + fbt); - } - continue; - } - // parse atoms or sub-records (distinct from container records above) - dat = new byte[len]; - bis.read(dat, 0, len); - switch (fbt) { - case MSODrawingConstants.MSOFBTSP: // A shape atom rec (inst= shape type) rec= shape ID + group of flags - boolean fGroup, fChild, fPatriarch, fDeleted, fOleShape, fHaveMaster; - boolean fFlipH, fFlipV, fConnector, fHaveAnchor, fBackground, fHaveSpt; - int flag; - SPID = ByteTools.readInt(dat[0], dat[1], dat[2], dat[3]); - flag = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]); - // parse out flag: - fGroup = (flag & 0x1) == 0x1; // it's a group shape - fChild = (flag & 0x2) == 0x2; // it's not a top-level shape - fPatriarch = (flag & 0x4) == 0x4; // topmost group shape ** 1 per drawing *8 - fDeleted = (flag & 0x8) == 0x8; // had been deleted - // 4 - fOleShape = (flag & 0x10) == 0x10; // it's an OLE shape - fHaveMaster = (flag & 0x20) == 0x20; // it has a master prop - fFlipH = (flag & 0x40) == 0x40; // it's flipped horizontally - fFlipV = (flag & 0x80) == 0x80; // it's flipped vertically - // 8 - fConnector = (flag & 0x100) == 0x100; // it's a connector type - fHaveAnchor = (flag & 0x200) == 0x200; // it's an anchor type - fBackground = (flag & 0x400) == 0x400; // it's a background shape - fHaveSpt = (flag & 0x800) == 0x800; // it has a shape-type property - // FYI: there are normally two msofbtsp records for each drawing - // the first (flag==5) defines fGroup + fPatriarch, - // with inst==0, apparently shape type to define SPIDSEED - // the second msofbtsp record (flag=2560)defines the SPID and contains flags: - // fHaveAnchor, fHaveSpt and inst==shape type - //NOTE: setting Active algorithm is not definitively proven; - // if it ISN'T active, why isn't the fDeleted flag set??? - bActive = bActive || fPatriarch; // if we have fPatriarch, it's active ... - if (fHaveSpt) - shapeType = (short) inst; // save shape type - if (inst == 0) //== shape type - SPIDSEED = SPID; // seed+imageIndex= SPID - SPCONTAINERATOMS += len + 8; - break; - - case MSODrawingConstants.MSOFBTCLIENTANCHOR: // Anchor or location for a shape - // NOT SO! sheetIndex = ByteTools.readShort(buf[0],buf[1]); - /** - bounds[0]= column # of top left position (0-based) of the shape - bounds[1]= x offset within the top-left column - bounds[2]= row # for top left corner - bounds[3]= y offset within the top-left corner - bounds[4]= column # of the bottom right corner of the shape - bounds[5]= x offset within the cell for the bottom-right corner - bounds[6]= row # for bottom-right corner of the shape - bounds[7]= y offset within the cell for the bottom-right corner - */ - clientAnchorFlag = ByteTools.readShort(dat[0], dat[1]); - bounds[COL] = ByteTools.readShort(dat[2], dat[3]); - bounds[COLOFFSET] = ByteTools.readShort(dat[4], dat[5]); - bounds[ROW] = ByteTools.readShort(dat[6], dat[7]); - bounds[ROWOFFSET] = ByteTools.readShort(dat[8], dat[9]); - bounds[COL1] = ByteTools.readShort(dat[10], dat[11]); - bounds[COLOFFSET1] = ByteTools.readShort(dat[12], dat[13]); - bounds[ROW1] = ByteTools.readShort(dat[14], dat[15]); - bounds[ROWOFFSET1] = ByteTools.readShort(dat[16], dat[17]); - SPCONTAINERATOMS += len + 8; - break; - - case MSODrawingConstants.MSOFBTOPT: // property table atom - for 97 and earlier versions - // 20091209 KSC: save MsoFbtOpt record for later use in updating, if necessary - optrec = new MsofbtOPT(fbt, inst, version); - optrec.setData(dat); // sets and parses msoFbtOpt data, including imagename, shapename and imageindex - imageName = optrec.getImageName(); - shapeName = optrec.getShapeName(); - imageIndex = optrec.getImageIndex(); - SPCONTAINERATOMS += len + 8; - break; - - // 20100519 KSC: later versions can have secondary and tertiary opt blocks - case MSODrawingConstants.MSOFBTSECONDARYOPT: // movie (id= 274) - secondaryoptrec = new MsofbtOPT(fbt, inst, version); - secondaryoptrec.setData(dat); // sets and parses msoFbtOpt data - SPCONTAINERATOMS += len + 8; - break; - - case MSODrawingConstants.MSOFBTTERTIARYOPT: // for Office versions 2000, XP, 2003, and 2007 - tertiaryoptrec = new MsofbtOPT(fbt, inst, version); - tertiaryoptrec.setData(dat); // sets and parses msoFbtOpt data - SPCONTAINERATOMS += len + 8; - break; - - case MSODrawingConstants.MSOFBTDG: // Drawing Record: ID, num shapes + Last SPID for this DG - drawingId = inst; - numShapes = ByteTools.readInt(dat[0], dat[1], dat[2], dat[3]); // number of shapes in this drawing - lastSPID = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]); - case MSODrawingConstants.MSOFBTCOLORSCHEME: - case MSODrawingConstants.MSOFBTREGROUPITEMS: - DGCONTAINERATOMS += len + 8; - otherSPCONTAINERLENGTH -= (len + 8); // these atoms are part of DGCONTAINER, not SPCONTAINER - adjust accordingly - break; - - case MSODrawingConstants.MSOFBTCLIENTTEXTBOX: // msofbtClientTextbox sub-record, contains only this one atom no containers ... - case MSODrawingConstants.MSOFBTTEXTBOX: // msofbtClientTextbox sub-record, contains only this one atom no containers ... - isShape = false; // is treated differently, isn't counted in numShapes calcs - break; - - case MSODrawingConstants.MSOFBTCHILDANCHOR: // used for all shapes that belong to a group. The content of the record is simply a RECT in the coordinate system of the parent group shape - // If the shape is saved to a clipboard: - case MSODrawingConstants.MSOFBTSPGR: // shapes that ARE groups, not shapes that are IN groups; The group shape record defines the coordinate system of the shape - // If the shape is a child of a group shape: - case MSODrawingConstants.MSOFBTANCHOR: // used for top-level shapes when the shape streamed to the clipboard. The content of the record is simply a RECT with a coordinate system of 100,000 units per inch and origin in the top-left of the drawing - case MSODrawingConstants.MSOFBTCLIENTDATA: - case MSODrawingConstants.MSOFBTDELETEDPSPL: - SPCONTAINERATOMS += len + 8; - break; - - case MSODrawingConstants.MSOFBTCONNECTORRULE: - case MSODrawingConstants.MSOFBTALIGNRULE: - case MSODrawingConstants.MSOFBTARCRULE: - case MSODrawingConstants.MSOFBTCLIENTRULE: - case MSODrawingConstants.MSOFBTCALLOUTRULE: - SOLVERCONTAINERATOMS += len + 8; - break; - - default: - Logger.logInfo("MSODrawing.init: unknown subrecord encountered: " + fbt); - } - } - /* //DEBUGGING: THESE CONTAINER LENGTH CALCULATIONS PASS FOR ALL thus far MSO's ENCOUNTERED - boolean diff= false; - if (isHeader()) { - int d0= (DGCONTAINERLENGTH-(DGCONTAINERATOMS+SPGRCONTAINERLENGTH+SOLVERCONTAINERLENGTH+8)); - int d1= (SPGRCONTAINERLENGTH+8-(SPCONTAINERLENGTH+otherSPCONTAINERLENGTH)); - if (d0+d1!=0) { - if (DGCONTAINERLENGTH!=(DGCONTAINERATOMS+SPGRCONTAINERLENGTH+SOLVERCONTAINERLENGTH+8)) { // this may not be 100% since must account for OTHER record's SOLVERCONTAINER LENGTHS - io.starter.toolkit.Logger.log("DGCONTAINERLENGTH DIFF: " + (DGCONTAINERLENGTH-(DGCONTAINERATOMS+SPGRCONTAINERLENGTH+SOLVERCONTAINERLENGTH+8))); - diff= true; - } - // ******* sum of SPCONTAINERS *************** - if (SPGRCONTAINERLENGTH+8!=SPCONTAINERLENGTH+otherSPCONTAINERLENGTH) { - io.starter.toolkit.Logger.log("SPGRCONTAINERLENGTH DIFF: " + (SPGRCONTAINERLENGTH+8-(SPCONTAINERLENGTH+otherSPCONTAINERLENGTH))); - diff= true; - } - } - } - // one or two header lengths (8 bytes each) have been added to SPCONTAINERLENGTH - // adjust here: - int headerlens= 0; - if (isHeader()) - headerlens= 16; - else if (isShape) // non-shapes don't have SPGRCONTAINERS - headerlens= 8; - if (isHeader() && SPCONTAINERLENGTH==48) // only one SPCONTAINER, decrement by 1 header length - headerlens= 8; - if (optrec!=null && optrec.hasTextId()) // shape has an attached text box; must add 8 for following CLIENTTEXTBOX (the !isShape Mso which follows ...) - SPCONTAINERATOMS+=8; - if (SPCONTAINERLENGTH-headerlens!=SPCONTAINERATOMS) { - System.err.println("SPCONTAINERLEN IS OFF: " + (SPCONTAINERLENGTH-headerlens-SPCONTAINERATOMS)); - System.err.println(this.toString()); - System.err.println(this.debugOutput()); - diff= true; - } - /**/ - if (hasUndoInfo) - SPCONTAINERLENGTH += 8; // Shapes that have been deleted but that could be brought back via Undo. -- must add to sp container length for total container length calc (see UpdateHeader) - } - - /** - * update the existing mso with the appropriate basic mso data - *
                  NOTE: To set other mso data, see setShapeType, setIsHeader ... - * - * @param spid Unique Shape Id - * @param imageName String image name or null - * @param shapeName String shape name or null - * @param imageIndex int index into the image byte store (for a picture-type) or -1 for null - * @param bounds short[8] the position of this Mso given in rows, cols and offsets - */ - public void updateRecord(int spid, String imageName, String shapeName, int imageIndex, short[] bounds) { - this.SPID = spid; - this.imageName = imageName; - this.shapeName = shapeName; - this.imageIndex = imageIndex; - this.bounds = bounds; - updateRecord(); - } - - /** - * rebuild record bytes for this Msodrawing - * aside from updating significant atoms such as MSOFBTOPT, it also recalculates container lengths - * - * @param spid - * @return - */ - public void updateRecord() { -/*// debug: check algorithm: - io.starter.toolkit.Logger.log(this.toString()); - io.starter.toolkit.Logger.log(this.debugOutput()); - int origSP= SPCONTAINERLENGTH; - int origDG= 0; - int origSPGR= 0; -/**/ - byte[] spcontainer1atoms = new byte[0]; - byte[] spcontainer2atoms = new byte[0]; // header specific - byte[] dgcontaineratoms = new byte[0]; // header specific - - boolean hasUndoInfo = false; // true if a non-header Mso contains an SPGRCONTAINER - documentation states: Shapes that have been deleted but that could be brought back via Undo. - // reset state vars - SPCONTAINERLENGTH = 0; // this shape container length - - // first pass, update significant atoms and sum up container lengths - int fbt, len; - super.getData(); - ByteArrayInputStream bis = new ByteArrayInputStream(data); - for (; bis.available() > 0; ) { - byte[] header = new byte[8]; - bis.read(header, 0, 8); - fbt = ((0xFF & header[3]) << 8) | (0xFF & header[2]); - len = ByteTools.readInt(header[4], header[5], header[6], header[7]); - if ((0xF & header[0]) == 15) { // 15 for containers, version for atoms - // most containers, just skip; some, however, are necessaary for container length calcs (see below - if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) { - //if (origSPGR==0) origSPGR= len; - if (!bIsHeader)// then this is a grouped shape, must add the header length as is apparent in existing container lengths (see below) - hasUndoInfo = true; - } else if (fbt == MSODrawingConstants.MSOFBTSPCONTAINER) { - isShape = true; // any mso that contains a normal "spcontainer" is a shape - } else if (fbt == MSODrawingConstants.MSOFBTSOLVERCONTAINER) { // solver container: rules governing shapes - // TODO: is there EVER a reason to update a SOLVERCONTAINER RECORD??? - // STRUCTURE: - // MSOFBTSOLVERCONTAINER 61445 15/#/# (15/0/0 for an empty solvercontainer) - // then 0 or more rules: - // MSOFBTCONNECTORRULE 61458 1/0/24 ... etc - SOLVERCONTAINERLENGTH = len + 8; // added to dgcontainerlength - isShape = false; - // testing- remove when done - } else if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) - //origDG= len; - ; - } else { - // parse atoms or sub-records (distinct from container records above) - byte[] data = new byte[len]; - bis.read(data, 0, len); - switch (fbt) { - case MSODrawingConstants.MSOFBTDG: // update drawing record atom - System.arraycopy(ByteTools.cLongToLEBytes(this.numShapes), 0, data, 0, 4); - System.arraycopy(ByteTools.cLongToLEBytes(this.lastSPID), 0, data, 4, 4); - len = data.length; - data = ByteTools.append(data, header); - dgcontaineratoms = ByteTools.append(data, dgcontaineratoms); - break; - - case MSODrawingConstants.MSOFBTSP: // A shape atom rec (inst= shape type) rec= shape ID + group of flags - // TODO: necessary to ever update the SPIDSEED? - int flag = ByteTools.readInt(data[4], data[5], data[6], data[7]); - boolean fHaveSpt = (flag & 0x800) == 0x800; // it has a shape-type property - if (flag != 5) { // if it's not the SPIDseed, update SPID - System.arraycopy(ByteTools.cLongToLEBytes(SPID), 0, data, 0, 4); - } - if (fHaveSpt) { // shape type is contained within inst var - header[0] = (byte) ((0xF & 2) | (0xF0 & (shapeType << 4))); - header[1] = (byte) ((0x00000FF0 & shapeType) >> 4); - } - data = ByteTools.append(data, header); - if (flag != 5) // if it's not the header - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - else - spcontainer2atoms = ByteTools.append(data, spcontainer2atoms); - break; - - case MSODrawingConstants.MSOFBTCLIENTANCHOR: // Anchor or location for a shape - // udpate bounds - System.arraycopy(ByteTools.shortToLEBytes(clientAnchorFlag), 0, data, 0, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[0]), 0, data, 2, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[1]), 0, data, 4, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[2]), 0, data, 6, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[3]), 0, data, 8, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[4]), 0, data, 10, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[5]), 0, data, 12, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[6]), 0, data, 14, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[7]), 0, data, 16, 2); - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTOPT: // property table atom - for 97 and earlier versions - // OPT= picture options - if (optrec == null) // if do not have a MsoFbtOPT record, then create new -- shouldn't get here as should always have an existing record - optrec = new MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3); //version is always 3, inst is current count of properties. - if (imageIndex != optrec.getImageIndex()) - optrec.setImageIndex(imageIndex); - if (imageName == null || !imageName.equals(optrec.getImageName())) - optrec.setImageName(imageName); - if (shapeName == null || !shapeName.equals(optrec.getShapeName())) { - optrec.setShapeName(shapeName); - } - data = optrec.toByteArray(); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTTERTIARYOPT: // for Office versions 2000, XP, 2003, and 2007 - case MSODrawingConstants.MSOFBTSECONDARYOPT: // movie (id= 274) - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTCOLORSCHEME: - case MSODrawingConstants.MSOFBTREGROUPITEMS: - data = ByteTools.append(data, header); - dgcontaineratoms = ByteTools.append(data, dgcontaineratoms); - break; - - case MSODrawingConstants.MSOFBTCLIENTTEXTBOX: // msofbtClientTextbox sub-record, contains only this one atom no containers ... - case MSODrawingConstants.MSOFBTTEXTBOX: // msofbtClientTextbox sub-record, contains only this one atom no containers ... - // DON"T ADD LEN ************** - isShape = false; - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTSPGR: // shapes that ARE groups, not shapes that are IN groups; The group shape record defines the coordinate system of the shape - data = ByteTools.append(data, header); - spcontainer2atoms = ByteTools.append(data, spcontainer2atoms); - break; - - case MSODrawingConstants.MSOFBTCHILDANCHOR: // used for all shapes that belong to a group. The content of the record is simply a RECT in the coordinate system of the parent group shape - case MSODrawingConstants.MSOFBTANCHOR: // used for top-level shapes when the shape streamed to the clipboard. The content of the record is simply a RECT with a coordinate system of 100,000 units per inch and origin in the top-left of the drawing - case MSODrawingConstants.MSOFBTCLIENTDATA: - case MSODrawingConstants.MSOFBTDELETEDPSPL: - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTCONNECTORRULE: - case MSODrawingConstants.MSOFBTALIGNRULE: - case MSODrawingConstants.MSOFBTARCRULE: - case MSODrawingConstants.MSOFBTCLIENTRULE: - case MSODrawingConstants.MSOFBTCALLOUTRULE: - // SOLVERCONTAINERATOMS+=len+8; - // TODO: is there EVER a reason to update a SOLVERCONTAINER RECORD??? - //Logger.logInfo("MSODrawing.updateRecord: encountered solver container atom"); - break; - - default: - Logger.logInfo("MSODrawing.updateRecord: unknown subrecord encountered: " + fbt); - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - } - } - } - // container lengths: - // are these adjustments necessary?? -/* - if (optrec!=null && optrec.hasTextId()) // shape has an attached text box; must add 8 for following CLIENTTEXTBOX (the !isShape Mso which follows ...) - SPCONTAINERATOMS+=8; -*/ - SPCONTAINERLENGTH = spcontainer1atoms.length; - int additionalSP = 0; - if (hasUndoInfo) - additionalSP = 8; // Shapes that have been deleted but that could be brought back via Undo. -- must add to sp container length for total container length calc (see UpdateHeader) - if (shapeType == MSODrawingConstants.msosptTextBox) - additionalSP = 8; // account for attached text mso - which has no SPCONTAINER so must include in "controlling" rec - // 2nd pass: now have the important container lengths and their updated - // subrecords/atoms, create resulting byte array - - // build main spcontainer - valid for all records - MsofbtSpContainer spcontainer1 = new MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15); - spcontainer1.setLength(SPCONTAINERLENGTH + additionalSP); - byte[] container = spcontainer1.toByteArray(); - SPCONTAINERLENGTH += container.length; // include this header length - - /*// debugging - if (!bIsHeader && SPCONTAINERLENGTH!=origSP) - Logger.logErr("SPCONTAINERLENTH IS OFF: " + (SPCONTAINERLENGTH-origSP)); - */ - byte[] retData = new byte[SPCONTAINERLENGTH]; - System.arraycopy(container, 0, retData, 0, container.length); - System.arraycopy(spcontainer1atoms, 0, retData, container.length, spcontainer1atoms.length); - - SPCONTAINERLENGTH += additionalSP;// necessary when summing up container lengths -- see WorkBook.updateMsoHeaderRecord - if (bIsHeader) { - // SPCONTAINER - // sp2 -- SPIDSEED - // spgr if necessary - MsofbtSpContainer spcontainer2 = new MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15); - spcontainer2.setLength(spcontainer2atoms.length); - byte[] container2 = spcontainer2.toByteArray(); - SPCONTAINERLENGTH += spcontainer2atoms.length + container2.length; // include this header length - - /*// debugging - if (SPCONTAINERLENGTH!=origSP) - Logger.logErr("SPCONTAINERLENTH IS OFF: " + (SPCONTAINERLENGTH-origSP)); - /**/ - // SPGRCONTAINER - int spgrcontainerlen = SPCONTAINERLENGTH + otherSPCONTAINERLENGTH - 8; - /*// debugging - if (spgrcontainerlen!=origSPGR) - Logger.logErr("SPGRCONTAINERLENTH IS OFF: " + (spgrcontainerlen-origSPGR)); - /**/ - MsofbtSpgrContainer msofbtSpgrContainer = new MsofbtSpgrContainer(MSODrawingConstants.MSOFBTSPGRCONTAINER, 0, 15); - msofbtSpgrContainer.setLength(spgrcontainerlen); - byte[] spgrcontainer = msofbtSpgrContainer.toByteArray(); - - // DGCONTAINER - int dgcontainerlen = (dgcontaineratoms.length + spgrcontainerlen + SOLVERCONTAINERLENGTH + 8); // drawing container length - /*// debugging - if (dgcontainerlen!=origDG) - Logger.logErr("DGCONTAINERLENTH IS OFF: " + (dgcontainerlen-origDG)); - /**/ - MsofbtDgContainer msofbtDgContainer = new MsofbtDgContainer(MSODrawingConstants.MSOFBTDGCONTAINER, 0, 15); - msofbtDgContainer.setLength(dgcontainerlen); //HEADERRECLENGTH + SPCONTAINERLENGTH + otherSPCONTAINERLENGTH); - byte[] dgcontainer = msofbtDgContainer.toByteArray(); - - byte[] header = new byte[HEADERRECLENGTH + SPCONTAINERLENGTH - additionalSP + dgcontainer.length]; //+retData.length]; - - int pos = 0; - System.arraycopy(dgcontainer, 0, header, pos, dgcontainer.length); - pos += dgcontainer.length; - System.arraycopy(dgcontaineratoms, 0, header, pos, dgcontaineratoms.length); - pos += dgcontaineratoms.length; - System.arraycopy(spgrcontainer, 0, header, pos, spgrcontainer.length); - pos += spgrcontainer.length; - System.arraycopy(container2, 0, header, pos, container2.length); - pos += container2.length; - System.arraycopy(spcontainer2atoms, 0, header, pos, spcontainer2atoms.length); - pos += spcontainer2atoms.length; - - System.arraycopy(retData, 0, header, pos, retData.length); - retData = header; - } - this.setData(retData); - this.setLength(data.length); - - - // testing - /* - io.starter.toolkit.Logger.log(this.toString()); - io.starter.toolkit.Logger.log(this.debugOutput()); - /**/ - } - - /** - * add the set of subrecords necessary to define a Mso header record - *
                  used when removing images, charts, etc. and have removed a previous header record - */ - public void addHeader() { - /*// testing - System.err.println(this.toString()); - System.err.println(this.debugOutput()); - /**/ - bIsHeader = true; - if (lastSPID < SPID) - lastSPID = SPID; // TODO: Shouldn't assume to be SPID - - MsofbtSp msofbtSp = new MsofbtSp(MSODrawingConstants.MSOFBTSP, MSODrawingConstants.msosptMin, 2); //1st shape rec is of type MSOSPTMIN - msofbtSp.setId(SPIDSEED); // SPMIN==SPIDSEED - msofbtSp.setGrfPersistence(5); // flag= fPatriarch, - byte[] msofbtSpBytes = msofbtSp.toByteArray(); - SPCONTAINERLENGTH += msofbtSpBytes.length; - - MsofbtSpgr msofbtSpgr = new MsofbtSpgr(MSODrawingConstants.MSOFBTSPGR, 0, 1); - msofbtSpgr.setRect(0, 0, 0, 0); - byte[] msofbtSpgrBytes = msofbtSpgr.toByteArray(); - SPCONTAINERLENGTH += msofbtSpgrBytes.length; - - // SPCONTAINER - MsofbtSpContainer msofbtSpContainer = new MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15); - msofbtSpContainer.setLength(msofbtSpgrBytes.length + msofbtSpBytes.length); - byte[] msofbtSpContainerBytes = msofbtSpContainer.toByteArray(); - SPCONTAINERLENGTH += 8; // account for the SPCONTAINER header length; - - // SPGRCONTAINER - MsofbtSpgrContainer msofbtSpgrContainer = new MsofbtSpgrContainer(MSODrawingConstants.MSOFBTSPGRCONTAINER, 0, 15); - msofbtSpgrContainer.setLength(SPCONTAINERLENGTH + otherSPCONTAINERLENGTH); - byte[] msofbtSpgrContainerBytes = msofbtSpgrContainer.toByteArray(); - - MsofbtDg msofbtDg = new MsofbtDg(MSODrawingConstants.MSOFBTDG, drawingId, 0); - msofbtDg.setNumShapes(numShapes); //Number of images and drawings. - msofbtDg.setLastSPID(lastSPID); //lastSPID - byte[] msofbtDgBytes = msofbtDg.toByteArray(); - - // DGCONTAINER - MsofbtDgContainer msofbtDgContainer = new MsofbtDgContainer(MSODrawingConstants.MSOFBTDGCONTAINER, 0, 15); - msofbtDgContainer.setLength(HEADERRECLENGTH + SPCONTAINERLENGTH + otherSPCONTAINERLENGTH); - byte[] msofbtDgContainerBytes = msofbtDgContainer.toByteArray(); - - - byte[] headerRec = new byte[this.getData().length + 80]; // below records take 80 bytes - - int pos = 0; - System.arraycopy(msofbtDgContainerBytes, 0, headerRec, pos, msofbtDgContainerBytes.length); - pos += msofbtDgContainerBytes.length;// 8 - System.arraycopy(msofbtDgBytes, 0, headerRec, pos, msofbtDgBytes.length); - pos += msofbtDgBytes.length; - System.arraycopy(msofbtSpgrContainerBytes, 0, headerRec, pos, msofbtSpgrContainerBytes.length); - pos += msofbtSpgrContainerBytes.length;// 8 - System.arraycopy(msofbtSpContainerBytes, 0, headerRec, pos, msofbtSpContainerBytes.length); - pos += msofbtSpContainerBytes.length;// 8 - System.arraycopy(msofbtSpgrBytes, 0, headerRec, pos, msofbtSpgrBytes.length); - pos += msofbtSpgrBytes.length; - System.arraycopy(msofbtSpBytes, 0, headerRec, pos, msofbtSpBytes.length); - pos += msofbtSpBytes.length; - - System.arraycopy(this.getData(), 0, headerRec, pos, this.getData().length); - setData(headerRec); - - /*// testing - System.err.println(this.toString()); - System.err.println(this.debugOutput()); - /**/ - } - - /** - * remove the set of subrecords necessary to define a MSO header record - *
                  used when removing images, charts, etc. and have removed a previous header record - */ - public void removeHeader() { - /*// testing - System.err.println(this.toString()); - System.err.println(this.debugOutput()); - */ - byte[] spcontainer1atoms = new byte[0]; - // reset state vars - SPCONTAINERLENGTH = 0; // this shape container length - - // first pass, update significant atoms and sum up container lengths - int fbt, len; - super.getData(); - ByteArrayInputStream bis = new ByteArrayInputStream(data); - for (; bis.available() > 0; ) { - byte[] header = new byte[8]; - bis.read(header, 0, 8); - fbt = ((0xFF & header[3]) << 8) | (0xFF & header[2]); - len = ByteTools.readInt(header[4], header[5], header[6], header[7]); - if ((0xF & header[0]) != 15) { // skip containers - // parse atoms or sub-records (distinct from container records above) - byte[] data = new byte[len]; - bis.read(data, 0, len); - switch (fbt) { - case MSODrawingConstants.MSOFBTDG: // update drawing record atom - break; - - case MSODrawingConstants.MSOFBTSP: // A shape atom rec (inst= shape type) rec= shape ID + group of flags - // TODO: necessary to ever update the SPIDSEED? - int flag = ByteTools.readInt(data[4], data[5], data[6], data[7]); - if (flag != 5) { // if it's not the SPIDseed, update SPID - System.arraycopy(ByteTools.cLongToLEBytes(SPID), 0, data, 0, 4); - } - data = ByteTools.append(data, header); - if (flag != 5) // if it's not the header - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTCLIENTANCHOR: // Anchor or location for a shape - // udpate bounds - System.arraycopy(ByteTools.shortToLEBytes(clientAnchorFlag), 0, data, 0, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[0]), 0, data, 2, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[1]), 0, data, 4, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[2]), 0, data, 6, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[3]), 0, data, 8, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[4]), 0, data, 10, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[5]), 0, data, 12, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[6]), 0, data, 14, 2); - System.arraycopy(ByteTools.shortToLEBytes(bounds[7]), 0, data, 16, 2); - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTOPT: // property table atom - for 97 and earlier versions - // OPT= picture options - if (optrec == null) // if do not have a MsoFbtOPT record, then create new -- shouldn't get here as should always have an existing record - optrec = new MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3); //version is always 3, inst is current count of properties. - if (imageIndex != -1 && imageIndex != optrec.getImageIndex()) - optrec.setImageIndex(imageIndex); - if (imageName != null && !imageName.equals("") && !imageName.equals(optrec.getImageName())) - optrec.setImageName(imageName); - if (shapeName != null && !shapeName.equals("") && !shapeName.equals(optrec.getShapeName())) { - optrec.setShapeName(shapeName); - } - data = optrec.toByteArray(); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTTERTIARYOPT: // for Office versions 2000, XP, 2003, and 2007 - case MSODrawingConstants.MSOFBTSECONDARYOPT: // movie (id= 274) - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTCOLORSCHEME: - case MSODrawingConstants.MSOFBTREGROUPITEMS: - break; - - case MSODrawingConstants.MSOFBTCLIENTTEXTBOX: // msofbtClientTextbox sub-record, contains only this one atom no containers ... - case MSODrawingConstants.MSOFBTTEXTBOX: // msofbtClientTextbox sub-record, contains only this one atom no containers ... - // DON"T ADD LEN - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTSPGR: // shapes that ARE groups, not shapes that are IN groups; The group shape record defines the coordinate system of the shape - break; - - case MSODrawingConstants.MSOFBTCHILDANCHOR: // used for all shapes that belong to a group. The content of the record is simply a RECT in the coordinate system of the parent group shape - case MSODrawingConstants.MSOFBTANCHOR: // used for top-level shapes when the shape streamed to the clipboard. The content of the record is simply a RECT with a coordinate system of 100,000 units per inch and origin in the top-left of the drawing - case MSODrawingConstants.MSOFBTCLIENTDATA: - case MSODrawingConstants.MSOFBTDELETEDPSPL: - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - - case MSODrawingConstants.MSOFBTCONNECTORRULE: - case MSODrawingConstants.MSOFBTALIGNRULE: - case MSODrawingConstants.MSOFBTARCRULE: - case MSODrawingConstants.MSOFBTCLIENTRULE: - case MSODrawingConstants.MSOFBTCALLOUTRULE: - // SOLVERCONTAINERATOMS+=len+8; - //don't add to len - // TODO: HANDLE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - break; - - default: - Logger.logInfo("MSODrawing.removeHeader: unknown subrecord encountered: " + fbt); - data = ByteTools.append(data, header); - spcontainer1atoms = ByteTools.append(data, spcontainer1atoms); - break; - } - } - } - SPCONTAINERLENGTH = spcontainer1atoms.length; - // 2nd pass: now have the important container lengths and their updated - // subrecords/atoms, create resulting byte array - - // build main spcontainer - valid for all records - MsofbtSpContainer spcontainer1 = new MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15); - spcontainer1.setLength(SPCONTAINERLENGTH); - byte[] container = spcontainer1.toByteArray(); - SPCONTAINERLENGTH += container.length; // include this header length - - //if (!bIsHeader && SPCONTAINERLENGTH!=origSP) - //Logger.logErr("SPCONTAINERLENTH IS OFF: " + (SPCONTAINERLENGTH-origSP)); - - byte[] retData = new byte[SPCONTAINERLENGTH]; - System.arraycopy(container, 0, retData, 0, container.length); - System.arraycopy(spcontainer1atoms, 0, retData, container.length, spcontainer1atoms.length); - - setData(retData); - /*// testing - System.err.println(this.toString()); - System.err.println(this.debugOutput()); - /**/ - } - - /** - * update the header records with new container lengths - * - * @param otherlength - */ - public void updateHeader(int otherSPContainers, int otherContainers, int numShapes, int lastSPID) { - if (!isHeader()) { - Logger.logErr("Msodrawing.updateHeader is only applicable for the header drawing object"); - return; - } - /* - // dgcontainerlength= dg(+8) + regroupitems(+8) + spgrcontainer + solvercontainer(s) + colorscheme(+8) - // spgrcontainerlength= sum of spcontainers i.e this spcontainerlength + otherspcontainers - */ - this.numShapes = numShapes; - // this.lastSPID= SPIDSEED + nImages; algorithm is wrong when book contains deleted images, etc. - this.lastSPID = lastSPID; - int fbt, len; - // the two container lengths we are concerned about: - //SPGRCONTAINERLENGTH - otherSPCONTAINERLENGTH = otherSPContainers; - int spgrcontainerlength = SPCONTAINERLENGTH + otherSPCONTAINERLENGTH; - // DGCONTAINERLENGTH= spgrcontainerlength + all the atoms contained within the DG CONTAINER - int dgcontainerlength = otherContainers + spgrcontainerlength; - int DGATOMS = 0; - boolean hasSPGRCONTAINER = false; - /* // debugging container lengths on update - boolean diff= false; - int origSPGRL= 0; - int origDGL= 0; - /**/ - // count dgcontainer atorms=MSOFBTDG, MSOFBTREGROUPITEMS, MSOFBTCOLORSCHEME (MSOFBTSPGRCONTAINER is added separately) - super.getData(); - ByteArrayInputStream bis = new ByteArrayInputStream(data); - for (; bis.available() > 0; ) { - byte[] buf = new byte[8]; - bis.read(buf, 0, 8); - fbt = ((0xFF & buf[3]) << 8) | (0xFF & buf[2]); - len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); - // 1st pass count dgcontainer atoms - if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) { - // skip contianers - //origDGL= len; // debugging - } else if (fbt == MSODrawingConstants.MSOFBTSPCONTAINER) { - // skip containers - } else if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) { - if (hasSPGRCONTAINER) { // already has the 1 required SPGRCONTAINER, if more than 1, multiple groups - spgrcontainerlength += 8; - } - DGATOMS += 8; // just count header length here - //if (!hasSPGRCONTAINER) origSPGRL= len; // debugging - hasSPGRCONTAINER = true; - } else if (fbt == MSODrawingConstants.MSOFBTOPT) { - break; // nothing important after this - } else { - if (fbt == MSODrawingConstants.MSOFBTREGROUPITEMS || fbt == MSODrawingConstants.MSOFBTDG || fbt == MSODrawingConstants.MSOFBTCOLORSCHEME) - DGATOMS += (len + 8); - buf = new byte[len]; - bis.read(buf, 0, len); - } - } - dgcontainerlength += DGATOMS; - /** debugging */ - /*if (origSPGRL!=spgrcontainerlength || origDGL!=dgcontainerlength) { - io.starter.toolkit.Logger.log(this.toString()); - io.starter.toolkit.Logger.log(this.debugOutput()); - io.starter.toolkit.Logger.log("ORIGDG=" + origDGL + " ORIGSPL=" + origSPGRL + " DIFF: " + (origDGL-dgcontainerlength) + "-" + (origSPGRL-spgrcontainerlength)); - diff= true; - } - /**/ - - super.getData(); - bis = new ByteArrayInputStream(data); - for (; bis.available() > 0; ) { - byte[] buf = new byte[8]; - bis.read(buf, 0, 8); - fbt = ((0xFF & buf[3]) << 8) | (0xFF & buf[2]); - len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); - - if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) { - System.arraycopy(ByteTools.cLongToLEBytes(dgcontainerlength), 0, data, data.length - bis.available() - 4, 4); - } else if (fbt == MSODrawingConstants.MSOFBTDG) { // Drawing Record: count + MSOSPID seed - buf = new byte[len]; - bis.read(buf, 0, len); - byte[] newrec = new byte[8]; - System.arraycopy(ByteTools.cLongToLEBytes(this.numShapes), 0, newrec, 0, 4); - System.arraycopy(ByteTools.cLongToLEBytes(this.lastSPID), 0, newrec, 4, 4); - if (len == newrec.length) {// should!!! - // update Msodrawing data ... - System.arraycopy(newrec, 0, data, data.length - bis.available() - newrec.length, newrec.length); - } else - Logger.logErr("UpdateClientAnchorRecord: New Array Size=" + newrec.length); - } else if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) { // sum of all spcontainers on the sheet - System.arraycopy(ByteTools.cLongToLEBytes(spgrcontainerlength), 0, data, data.length - bis.available() - 4, 4); - /*if (diff) { // debugging container lengths - io.starter.toolkit.Logger.log(this.toString()); - io.starter.toolkit.Logger.log(this.debugOutput()); - }/**/ - return; - } else { // skip atoms - buf = new byte[len]; - bis.read(buf, 0, len); - } - } - } - - /** - * update just the image index portion of this record - * useful when you don't need to rebuild entire record (and thus possibly lose information) - * - * @param idx - */ - public void updateImageIndex(int idx) { - int version, inst, fbt, len; - super.getData(); - ByteArrayInputStream bis = new ByteArrayInputStream(data); - for (; bis.available() > 0; ) { - byte[] buf = new byte[8]; - bis.read(buf, 0, 8); - inst = ((0xFF & buf[1]) << 4) | (0xF0 & buf[0]) >> 4; - fbt = ((0xFF & buf[3]) << 8) | (0xFF & buf[2]); - len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); - if (fbt < 0xF005) { // ignore containers - continue; - } - buf = new byte[len]; - bis.read(buf, 0, len); // position at next sub-rec - if (fbt == MSODrawingConstants.MSOFBTOPT) { - // Find the property that governs image index and update the bytes - int propertyId; - int n = inst; // number of properties to parse - int pos = 0; // pointer to current property in data/property table - for (int i = 0; i < n; i++) { - propertyId = (0x3F & buf[pos + 1]) << 8 | (0xFF & buf[pos]); - if (propertyId == MSODrawingConstants.msooptpib) {// blip to display = image index - // testing int dtx = ByteTools.readInt(dat[pos+2],dat[pos+3],dat[pos+4],dat[pos+5]); - int insertPosition = data.length - bis.available() - len + pos + 2; - System.arraycopy(ByteTools.cLongToLEBytes(idx), 0, data, insertPosition, 4); - imageIndex = idx; - return; - } - pos += 6; - } - } - } - } - - - /** - * removes the header-specific portion of this Msodrawing record, if any - * useful when adding Msodrawng recs from other sources such as adding charts - * only one Msodrawing header record is allowed - */ - public void makeNonHeader() { - if (!bIsHeader) - return; // nothing to do! - bIsHeader = false; - this.removeHeader(); - } - - /** - * set whether "this is the header drawing object for the sheet" - * - * @param b - */ - public void setIsHeader() { - if (!bIsHeader) { - this.addHeader(); - } - } - - /** - * retrieve the OPT rec for specific option setting - * - * @return - */ - public MsofbtOPT getOPTRec() { - return optrec; - } - - /** - * @return whether "this is the header drawing object for the sheet" - */ - public boolean isHeader() { - return bIsHeader; - } - - public String getName() { - return imageName; - } - - /** - * @return explicitly-set shape name (= name in NamedRange box, I believe) - */ - public String getShapeName() { - return shapeName; - } - - - /** - * @return true if this image has a border - */ - // TODO: detecting a border may be more complicated than this - // TODO: ability to set a border - public boolean hasBorder() { - if (optrec != null) - return optrec.hasBorder(); - return false; - } - - /** - * if has a border, return the border line width - * - * @return - */ - public int borderLineWidth() { - if (optrec != null) - return optrec.getBorderLineWidth(); - return -1; - } - - public void setImageIndex(int value) { - imageIndex = value; - this.updateRecord(); - } - - public void setImageName(String name) { - if (!name.equals(imageName)) { - imageName = name; - updateRecord(); - } - } - - /** - * set the ordinal # for this drawing record - * - * @param id - */ - public void setDrawingId(int id) { - drawingId = id; - updateDGRecord(); - } - - /** - * return the ordinal # for this record - * - * @return - */ - public int getDrawingId() { - return drawingId; - } - - /** - * allow setting of "Named Range" name - * - * @param name - */ - public void setShapeName(String name) { - if (!name.equals(shapeName)) { - shapeName = name; - updateRecord(); - } - } - - public int getImageIndex() { - return imageIndex; - } - - // correct way of setting/getting shape (image and chart) bounds - /* position methods */ - public static final int COL = 0; - public static final int COLOFFSET = 1; - public static final int ROW = 2; - public static final int ROWOFFSET = 3; - public static final int COL1 = 4; - public static final int COLOFFSET1 = 5; - public static final int ROW1 = 6; - public static final int ROWOFFSET1 = 7; - public static final int OFFSETMAX = 1023; - - public void setBounds(short[] b) { - bounds = b.clone(); - origHeight = calcHeight(); // 20090831 KSC - updateClientAnchorRecord(bounds); - } - - - /** - * set coordinates as {X, Y, W, H} in 7 - * - * @param coords - */ - public void setBoundsInPixels(short[] coords) { - // 20090505 KSC: convert - short x = (short) Math.round(coords[0]); // convert pixels to excel units - short y = (short) Math.round(coords[1]); // convert pixels to points - short w = (short) Math.round(coords[2]); // convert pixels to excel units - short h = (short) Math.round(coords[3]); // convert pixels to points -/* setX(coords[0]); - setY(coords[1]); - setWidth(coords[2]); - setHeight(coords[3]); - */ - setX(x); - setY(y); - setWidth(w); - setHeight(h); - } - - // conversions: approx .136 excel units/pixel, 7.5 pixels/excel unit for X/W/Columns (really 6.4????) - // approx .75 pixels/points (Y,H/Rows) (really .60???) - /** - * Untested Info from Excel: - * width/height in pixels = (w/h field - 8) * DPI of the display device / 72 - * DPI for Windows is 96 (Mac= 72) - * thus conversion factor= 1.3333 for Windows devices - * NOTES: seems correct for y/h, not for x/w - * return coordinates as {X, Y, W, H} in pixels - * - * @return - */ - // these are approximate conversion factors to convert excel units to pixels ... - public static final double XCONVERSION = 10; // convert excel units to pixels, garnered from actual comparisions rather than any equation (since it's based upon the normal font, it cannot be 100% for all calculations ... - public static final double WCONVERSION = 8.8; // "" - public static final double PIXELCONVERSION = 1.333; // see above - - /** - * return coordinates as {X, Y, W, H} in pixels - * - * @param coords - */ - public short[] getCoords() { - short x = (short) Math.round(getX() * 256 / ColHandle.COL_UNITS_TO_PIXELS); - short y = (short) Math.round(getY() * 20 / (RowHandle.ROW_HEIGHT_DIVISOR - 4)); //*** WHY need -4 ????????? - short w = (short) Math.round(getWidth() * 256 / ColHandle.COL_UNITS_TO_PIXELS); - short h = (short) Math.round(calcHeight() * 20 / (RowHandle.ROW_HEIGHT_DIVISOR - 2)); //*** WHY need -2 ????????? - -/* testsing new way above short x= (short) Math.round(getX()*(XCONVERSION); // convert excel units to pixels - short y= (short) Math.round(getY()*PIXELCONVERSION); // convert points to pixels - short w= (short) Math.round(getWidth()*WCONVERSION); // convert excel units to pixels - short h= (short) Math.round((calcHeight()-8)*PIXELCONVERSION); // convert points to pixels -*/ - return new short[]{x, y, w, h}; - } - - /** - * set coordinates as {X, Y, W, H} in pixels - * - * @param coords - */ - public void setCoords(short[] coords) { - // TODO should use ABOVE CALC!!!! see getCoords ****************** - short x = (short) Math.round((coords[0] * ColHandle.COL_UNITS_TO_PIXELS) / 256); // convert pixels to excel units - short y = (short) Math.round((coords[1] * (RowHandle.ROW_HEIGHT_DIVISOR - 4)) / 20); // convert pixels to points - short w = (short) Math.round((coords[2] * ColHandle.COL_UNITS_TO_PIXELS) / 256); // convert pixels to excel units - short h = (short) Math.round((coords[3] * (RowHandle.ROW_HEIGHT_DIVISOR - 2)) / 20); // convert pixels to points - setX(x); - setY(y); - setWidth(w); - setHeight(h); - } - - /** - * returns the bounds of this object - * bounds are relative and based upon rows, columns and offsets within - */ - public short[] getBounds() { - return bounds; - } - - public int getCol() { - return bounds[MSODrawing.COL]; - } - - public int getCol1() { - return bounds[MSODrawing.COL1]; - } - - public int getRow0() { - return bounds[MSODrawing.ROW]; - } - - public int getRow1() { - return bounds[MSODrawing.ROW1]; - } - - public void setRow(int row) { - bounds[MSODrawing.ROW] = (short) row; - updateClientAnchorRecord(bounds); - } - - public void setRow1(int row) { - bounds[MSODrawing.ROW1] = (short) row; - updateClientAnchorRecord(bounds); - } - - /** - * get X value of upper left corner - * units are in excel units - * - * @return short x value - */ - public short getX() { - int col = bounds[MSODrawing.COL]; - double colOff = bounds[MSODrawing.COLOFFSET] / 1024.0; - double x = 0.0; - for (int i = 0; i < col; i++) { - x += getColWidth(i); - } - x += colOff * getColWidth(col); - return (short) Math.round(x); - } - - /** - * returns the offset within the column in pixels - * - * @return - */ - public short getColOffset() { - double colOff = bounds[MSODrawing.COLOFFSET] / 1024.0; - int col = bounds[MSODrawing.COL]; - double x = colOff * getColWidth(col); - return (short) (Math.round(x) * XCONVERSION); - } - - /** - * set the x position of this object - * units are in excel units - * - * @param x - */ - public void setX(int x) { - int z = 0; - short col = 0; - short colOffset = 0; - for (short i = 0; i < XLSConstants.MAXCOLS && z < x; i++) { - int w = getColWidth(i); - if (z + w < x) - z += w; - else { - col = i; - colOffset = (short) Math.round(1024 * (((double) (x - z)) / (double) w)); - z = x; - } - } - bounds[MSODrawing.COL] = col; - bounds[MSODrawing.COLOFFSET] = colOffset; - updateClientAnchorRecord(bounds); - } - - /** - * return the y position of this object in points - * - * @return - */ - public short getY() { - int row = bounds[MSODrawing.ROW]; - double y = 0.0; - for (int i = 0; i < row; i++) { - y += getRowHeight(i); - } - double rowOff = bounds[MSODrawing.ROWOFFSET] / 256.0; - y += getRowHeight(row) * rowOff; - return (short) Math.round(y); - } - - /** - * set the y position of this object - * units are in points - * - * @param y - */ - public void setY(int y) { - double z = 0; - short row = 0; - short rowOffset = 0; - for (short i = 0; z < y; i++) { - double h = getRowHeight(i); - if (z + h < y) - z += h; - else { - row = i; - rowOffset = (short) Math.round((256 * ((y - z) / getRowHeight(i)))); - z = y; - } - } - bounds[MSODrawing.ROW] = row; - bounds[MSODrawing.ROWOFFSET] = rowOffset; - updateClientAnchorRecord(bounds); - } - - /** - * public methd that returns saved width value; - * useful when calculated method is incorrect due to column width changes ... - * - * @return - */ - public short getOriginalWidth() { - return origWidth; - } - - /** - * calculate width based upon col#'s, coloffsets and col widths - * units are in excel column units - * - * @return short width - */ - public short getWidth() { - /* - bounds[0]= column # of top left position (0-based) of the shape - bounds[1]= x offset within the top-left column - bounds[2]= row # for top left corner - bounds[3]= y offset within the top-left corner - bounds[4]= column # of the bottom right corner of the shape - bounds[5]= x offset within the cell for the bottom-right corner - bounds[6]= row # for bottom-right corner of the shape - bounds[7]= y offset within the cell for the bottom-right corner - */ - - int col = bounds[MSODrawing.COL]; - double colOff = bounds[MSODrawing.COLOFFSET] / 1024.0; - int col1 = bounds[MSODrawing.COL1]; - double colOff1 = bounds[MSODrawing.COLOFFSET1] / 1024.0; - double w = getColWidth(col) - getColWidth(col) * colOff; - for (int i = col + 1; i < col1; i++) { - w += getColWidth(i); - } - if (col1 > col) - w += getColWidth(col1) * colOff1; - - else //correct???? - w = getColWidth(col1) * (colOff1 - colOff); - - return (short) Math.round(w); - } - - /** - * public method that returns saved height value; - * used when calculated method is incorrect due to row height changes ... - * - * @return - */ - public short getHeight() { - return origHeight; - } - - /** - * calculate height based upon row #s, row offsets and row heights - * units are in points - * - * @return short row height - */ - private short calcHeight() { - int row = bounds[MSODrawing.ROW]; - int row1 = bounds[MSODrawing.ROW1]; - double rowOff = bounds[MSODrawing.ROWOFFSET] / 256.0; - double rowOff1 = bounds[MSODrawing.ROWOFFSET1] / 256.0; - double y = getRowHeight(row) - getRowHeight(row) * rowOff; - for (int i = row + 1; i < row1; i++) { - y += getRowHeight(i); - } - if (row1 > row) - y += getRowHeight(row1) * rowOff1; - else - y = getRowHeight(row1) * (rowOff1 - rowOff); - return (short) Math.round(y); - } - - /** - * set the width of this object - * units are in excel units - * - * @param w - */ - public void setWidth(int w) { - int col = bounds[MSODrawing.COL]; - double colOff = bounds[MSODrawing.COLOFFSET] / 1024.0; - int col1 = col; - int colOff1 = 0; - int z = getColWidth(col) - (int) (getColWidth(col) * colOff); - if (z >= w) { // 20100322 KSC: was > w, so the == case never was handled, see TestImages.insertImageOffsetDisappearance bug - col1 = col; - colOff1 = (short) Math.round((1024 * (w / (double) getColWidth(col)))) + bounds[MSODrawing.COLOFFSET]; - } - for (int i = col + 1; i < XLSConstants.MAXCOLS && z < w; i++) { - int cw = getColWidth(i); - if (z + cw < w) - z += cw; - else { - col1 = i; - colOff1 = (short) (1024 * (((double) (w - z)) / (double) cw)); - z = w; - } - } - bounds[MSODrawing.COL1] = (short) col1; - bounds[MSODrawing.COLOFFSET1] = (short) colOff1; - updateClientAnchorRecord(bounds); - origWidth = getOriginalWidth(); // 20071024 KSC: if change width, record - } - - /** - * set the height of this object - * units are in points - * - * @param h - */ - public void setHeight(int h) { - int row = bounds[MSODrawing.ROW]; - double rowOff = bounds[MSODrawing.ROWOFFSET] / 256.0; - int row1 = row; - int rowOff1 = 0; - double rh = getRowHeight(row); - double y = rh - (rh * rowOff); // distance from start position to end of row - if (y > h) { - rowOff1 = (short) (256 * (h / rh)) + bounds[MSODrawing.ROWOFFSET]; - } - for (int i = row + 1; y < h; i++) { - rh = getRowHeight(i); - if (y + rh < h) - y += rh; - else { // height is met; see what offset into row i is necessary - row1 = i; - rowOff1 = (short) Math.round((256 * ((h - y) / rh))); - y = h; // exit loop - } - } - bounds[MSODrawing.ROW1] = (short) row1; - bounds[MSODrawing.ROWOFFSET1] = (short) rowOff1; - updateClientAnchorRecord(bounds); - origHeight = calcHeight(); // 20071024 KSC: if change height, record - } - - /* col/row and offset methods */ - public short[] getColAndOffset() { - return new short[]{bounds[COL], bounds[COLOFFSET]}; - } - - public void setColAndOffset(short[] b) { - bounds[COL] = b[0]; - bounds[COLOFFSET] = b[1]; - updateClientAnchorRecord(bounds); - } - - public short[] getRowAndOffset() { - return new short[]{bounds[ROW], bounds[ROWOFFSET]}; - } - - public void setRowAndOffset(short[] b) { - bounds[ROW] = b[0]; - bounds[ROWOFFSET] = b[1]; - updateClientAnchorRecord(bounds); - } - - /** - * return the column width in excel units - * - * @param col - * @return - */ - private int getColWidth(int col) { - // MSO column width is in Excel units= 0-255 characters - // Excel Units are 1/256 of default font width (10 pt Arial) - double w = Colinfo.DEFAULT_COLWIDTH; - try { - Colinfo co = this.getSheet().getColInfo(col); - if (co != null) - w = co.getColWidth(); - } catch (Exception e) { // exception if no col defined - } - - return (int) Math.round(w / 256); // 20090505 KSC: try this - } - - /** - * return the row height in points - * - * @param row - * @return - */ - private double getRowHeight(int row) { - // MSO row height is measured in points, 0-409 - // in Arial 10 pt, standard row height is 12.75 points - // .75 points/pixel - // row height is in twips= 1/20 of a point,1 pt= 1/72 inch - int h = 255; - try { - Row r = this.getSheet().getRowByNumber(row); - if (r != null) - h = r.getRowHeight(); - else // no row defined - use default row height 20100504 KSC - return this.getSheet().getDefaultRowHeight(); // default - } catch (Exception e) { // exception if no row defined // no row defined - use default row height 20100504 KSC - return this.getSheet().getDefaultRowHeight(); // default - } - return (h / 20.0); // 20090506 KSC: it's in twips 1/20 of a point - } - /* end position methods */ - - - private Phonetic phonetic = null; - - /** - * @return - */ - public Phonetic getMystery() { - return phonetic; - } - - /** - * @param phonetic - */ - public void setMystery(Phonetic p) { - phonetic = p; - } - - /** - * @return Returns the numShapes. - */ - public int getNumShapes() { - return numShapes; - } - - /** - * set the number of shapes for this drawing rec - * - * @param n - */ - public void setNumShapes(int n) { - numShapes = n; - updateDGRecord(); - } - - /** - * return the lastSPID (only valid for the header msodrawing record - * necessary to track so that newly added images have the appropriate SPID - * - * @return - */ - public int getlastSPID() { - return this.lastSPID; - } - - /** - * returns the Shape Container Length for this drawing record - * used when setting total size for the header drawing record - * - * @return - */ - public int getSPContainerLength() { - return SPCONTAINERLENGTH; - } - - /** - * returns the solver container length for this drawing record - * used when setting the total size for the header drawing record - * - * @return - */ - public int getSOLVERContainerLength() { - return SOLVERCONTAINERLENGTH; - } - - - protected static XLSRecord getPrototype() { - MSODrawing mso = new MSODrawing(); - mso.setOpcode(MSODRAWING); - mso.setData(mso.PROTOTYPE_BYTES); - mso.init(); - // prototype bytes contain an image index and an image name - remove here - // TODO: make prototype bytes correct -// mso.imageIndex= -1; -// mso.imageName= ""; -// mso.optrec.setImageName(""); -// mso.optrec.setImageIndex(-1); -// mso.updateRecord(); - return mso; - } - - /** - * creates a msofbtClientTextbox, a very strange mso record containing only one sub-record or atom; - * associated data=the text in the textbox, in a host-defined format i.e. text from a NOTE - *
                  - * NOTE: this must be flagged as incomplete so that it is not counted in number of shapes and other calcs ... - *
                  - * NOTE: knowledge of this record is very sketchy ... - * - * @return - */ - protected static XLSRecord getTextBoxPrototype() { - MSODrawing mso = new MSODrawing(); - mso.setOpcode(MSODRAWING); - mso.setData(new byte[]{0, 0, 13, -16, 0, 0, 0, 0}); // only contains 1 sub-record: 0xF00D or 61453= msofbtTextBox==shape has attached text - mso.init(); - return mso; - } - - /** - * update the bytes of the DG record - */ - private void updateDGRecord() { - int fbt, len; - - super.getData(); - ByteArrayInputStream bis = new ByteArrayInputStream(data); - for (; bis.available() > 0; ) { - byte[] buf = new byte[8]; - bis.read(buf, 0, 8); - fbt = ((0xFF & buf[3]) << 8) | (0xFF & buf[2]); - len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); - if (fbt < 0xF005) { // ignore containers - continue; - } - buf = new byte[len]; - bis.read(buf, 0, len); // position at next sub-rec - if (fbt == MSODrawingConstants.MSOFBTDG) { // Drawing Record: count + MSOSPID seed - data[8] = (byte) (drawingId * 16); //= the 1st byte of the header portion of the DG record 20080902 KSC - byte[] newrec = new byte[8]; - System.arraycopy(ByteTools.cLongToLEBytes(numShapes), 0, newrec, 0, 4); - System.arraycopy(ByteTools.cLongToLEBytes(this.lastSPID), 0, newrec, 4, 4); - if (len == newrec.length) {// should!!! - // update Msodrawing data ... - System.arraycopy(newrec, 0, data, data.length - bis.available() - newrec.length, newrec.length); - } else - Logger.logErr("UpdateClientAnchorRecord: New Array Size=" + newrec.length); - return; - } - } - } - - /** - * update the bytes of the CLIENTANCHOR record - * - * @param bounds bounds[0]= column # of top left position (0-based) of the shape - * bounds[1]= x offset within the top-left column - * bounds[2]= row # for top left corner - * bounds[3]= y offset within the top-left corner - * bounds[4]= column # of the bottom right corner of the shape - * bounds[5]= x offset within the cell for the bottom-right corner - * bounds[6]= row # for bottom-right corner of the shape - * bounds[7]= y offset within the cell for the bottom-right corner - */ - private void updateClientAnchorRecord(short[] bounds) { - int fbt, len; - - super.getData(); - ByteArrayInputStream bis = new ByteArrayInputStream(data); - for (; bis.available() > 0; ) { - byte[] buf = new byte[8]; - bis.read(buf, 0, 8); - fbt = ((0xFF & buf[3]) << 8) | (0xFF & buf[2]); - len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); - if (fbt < 0xF005) { // ignore containers - continue; - } - buf = new byte[len]; - bis.read(buf, 0, len); // position at next sub-rec - if (fbt == MSODrawingConstants.MSOFBTCLIENTANCHOR) { // Anchor or location fo a shape - // udpate bounds - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try { - bos.write(ByteTools.shortToLEBytes(clientAnchorFlag)); - bos.write(ByteTools.shortToLEBytes(bounds[0])); - bos.write(ByteTools.shortToLEBytes(bounds[1])); - bos.write(ByteTools.shortToLEBytes(bounds[2])); - bos.write(ByteTools.shortToLEBytes(bounds[3])); - bos.write(ByteTools.shortToLEBytes(bounds[4])); - bos.write(ByteTools.shortToLEBytes(bounds[5])); - bos.write(ByteTools.shortToLEBytes(bounds[6])); - bos.write(ByteTools.shortToLEBytes(bounds[7])); - } catch (Exception e) { - } - byte[] newrec = bos.toByteArray(); - if (buf.length == newrec.length) {// should!!! - // update Msodrawing data ... - System.arraycopy(newrec, 0, data, data.length - bis.available() - newrec.length, newrec.length); - } else - Logger.logErr("UpdateClientAnchorRecord: New Array Size=" + newrec.length); - return; - } - } - } - - /** - * sets a specific OPT subrecord - * - * @param propertyId int property id see Msoconstants - * @param isBid true if this is a BLIP id - * @param isComplex true if has complexBytes - * @param dtx if not isComplex, the value; if isComplex, length - * @param complexBytes complex bytes if isComplex - */ - public void setOPTSubRecord(int propertyId, boolean isBid, boolean isComplex, int dtx, byte[] complexBytes) { - MsofbtOPT optrec = this.getOPTRec(); - // TODO: store optrec length instead of calculating each time - int origlen = optrec.toByteArray().length; - optrec.setProperty(MSODrawingConstants.msooptGroupShapeProperties, isBid, isComplex, dtx, complexBytes); - updateRecord(); - if (origlen != optrec.toByteArray().length) { // must update header - this.getWorkBook().updateMsodrawingHeaderRec(this.getSheet()); - } - } - - /** - * set the LastSPID for this drawing record, if it's a header-type record - * - * @param spid - */ - public void setLastSPID(int spid) { - lastSPID = spid; - updateDGRecord(); - } - - /** - * set the SPID for this drawing record - * used upon copyworksheet ... - * - * @param spid - */ - public void setSPID(int spid) { - SPID = spid; - updateSPID(); - } - - /** - * return the SPID for this drawing record - */ - public int getSPID() { - return SPID; - } - - - public void setShapeType(int shapeType) { - this.shapeType = (short) shapeType; - } - - public int getShapeType() { - return shapeType; - } - - /** - * change the SPID for this record - * - * @param spid - */ - private void updateSPID() { - int fbt, len, inst; - super.getData(); - ByteArrayInputStream bis = new ByteArrayInputStream(data); - for (; bis.available() > 0; ) { - byte[] buf = new byte[8]; - bis.read(buf, 0, 8); - inst = ((0xFF & buf[1]) << 4) | (0xF0 & buf[0]) >> 4; - fbt = ((0xFF & buf[3]) << 8) | (0xFF & buf[2]); - len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); - if (fbt >= 0xF005) { // ignore containers - buf = new byte[len]; - bis.read(buf, 0, len); - - if (fbt == MSODrawingConstants.MSOFBTSP) { - //byte[] dat = new byte[len]; - //bis.read(dat,0,len); - int flag = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); -// if (flag!=5) { // if it's not the SPIDseed - System.arraycopy(ByteTools.cLongToLEBytes(SPID), 0, data, data.length - bis.available() - len, 4); - //SPID = ByteTools.readInt(dat[0],dat[1],dat[2],dat[3]); -// return; -// } - } - } - } - } - - - /** - * update the header records with new container lengths - * - * @param otherlength - */ - public String toString() { - StringBuffer sb = new StringBuffer(); - sb.append("Msodrawing: image=" + this.imageName + ".\t" + this.shapeName + "\timageIndex=" + imageIndex + " sheet=" + ((this.getSheet() != null) ? this.getSheet().getSheetName() : "none")); - sb.append(" ID= " + drawingId + " SPID=" + SPID); - sb.append("\tShapeType= " + shapeType); - if (isHeader()) - sb.append("\tNumber of Shapes=" + numShapes + " Last SPID=" + lastSPID + " SPCL=" + SPCONTAINERLENGTH + " otherLen=" + otherSPCONTAINERLENGTH); - else - sb.append(" SPCL=" + SPCONTAINERLENGTH); - if (!isShape) - sb.append(" NOT A SHAPE"); - return sb.toString(); - } - - /** - * 20081106 KSC: when set sheet, record original height and width - * as dependent upon row heights ... - */ - public void setSheet(Sheet bs) { - super.setSheet(bs); - // 20081106 Moved from parse as sheet must be set before calcHeight call - // Record original height and width in case of row height/col width changes - origHeight = calcHeight(); - origWidth = getOriginalWidth(); - } - - /** - * returns true if this drawing is active i.e. not deleted - *
                  Note this is experimental - * - * @return - */ - public boolean isActive() { - // TODO: also report falses if height or width is 0?? - return bActive; - } - - /** - * create records sub-records necessary to define an AutoFilter drop-down symbol - */ - public void createDropDownListStyle(int col) { - // mso record which - we hope - has the specific options necessary to define the dropdown box - MsofbtOPT optrec = this.getOPTRec(); - optrec.setInst(0); // clear out - optrec.setData(new byte[]{}); - optrec.setProperty(MSODrawingConstants.msooptfLockAgainstGrouping, false, false, 17039620, null); - optrec.setProperty(MSODrawingConstants.msooptfFitTextToShape, false, false, 524296, null); - optrec.setProperty(MSODrawingConstants.msooptfNoLineDrawDash, false, false, 524288, null); - optrec.setProperty(MSODrawingConstants.msooptGroupShapeProperties, false, false, 131072, null); - this.setShapeType(MSODrawingConstants.msosptHostControl); // shape type for these drop-downs - this.updateRecord(++this.wkbook.lastSPID, null, null, -1, new short[]{(short) col, 0, 0, 0, (short) (col + 1), 0, 1, 0}); // generate msoDrawing using correct values moved from above - // KSC: keep for testing - //col++; - //this.updateRecord(++this.wkbook.lastSPID, null, null, -1, new short[] {(short)col, 0, 0, 0, (short)(col+1), 272, 1, 0}); // generate msoDrawing using correct values moved from above - } - - /** - * create the sub-records necessary to define a Comment (Note) Box - */ - public void createCommentBox(int row, int col) { - MsofbtOPT optrec = this.getOPTRec(); - optrec.setInst(0); // clear out - optrec.setData(new byte[]{}); - //47752196 - // try a random text id ... instead of 48678864 - int id = new java.util.Random().nextInt(); - optrec.setProperty(MSODrawingConstants.msofbtlTxid, false, false, id, null); - optrec.setProperty(MSODrawingConstants.msofbttxdir, false, false, 2, null); - optrec.setProperty(MSODrawingConstants.msooptfFitTextToShape, false, false, 524296, null); - optrec.setProperty(344, false, false, 0, null); // no info on this subrecord - optrec.setProperty(MSODrawingConstants.msooptfillColor, false, false, 134217808, null); - optrec.setProperty(MSODrawingConstants.msooptfillBackColor, false, false, 134217808, null); - optrec.setProperty(MSODrawingConstants.msooptfNoFillHitTest, false, false, 1048592, null); - optrec.setProperty(MSODrawingConstants.msooptfshadowColor, false, false, 0, null); - optrec.setProperty(MSODrawingConstants.msooptfShadowObscured, false, false, 196611, null); - // this, strangely, controls note hidden or show- when it's 131074, it's hidden, when it's 131072, it's shown - optrec.setProperty(MSODrawingConstants.msooptGroupShapeProperties, false, false, 131074, null); - this.setShapeType(MSODrawingConstants.msosptTextBox); // shape type for text boxes - // position of text box - garnered from Excel examples - // [1, 240, 0, 30, 3, 496, 4, 196] A1 - // [4, 240, 2, 105, 6, 496, 7, 15] D4 - this.updateRecord(++this.wkbook.lastSPID, null, null, -1, new short[]{(short) (col + 1), 240, (short) row, 30, (short) (col + 3), 496, (short) (row + 4), 196}); // generate msoDrawing using correct values moved from above - } - /* notes from another attempt: does this match ours? -_store_mso_opt_comment { - my $self = shift; - my $type = 0xF00B; - my $version = 3; - my $instance = 9; - my $data = ''; - my $length = 54; - my $spid = $_[0]; - my $visible = $_[1]; - my $colour = $_[2] || 0x50; - $data = pack "V", $spid; - $data .= pack "H*", '0000BF00080008005801000000008101' ; - $data .= pack "C", $colour; - $data .= pack "H*", '000008830150000008BF011000110001' . - '02000000003F0203000300BF03'; - $data .= pack "v", $visible; - $data .= pack "H*", '0A00'; - * - */ - - - /** - * test debug output - FOR INTERNAL USE ONLY - * - * @return - */ - public String debugOutput() { - ByteArrayInputStream bis = new ByteArrayInputStream(super.getData()); - int version, inst, fbt, len; - StringBuffer log = new StringBuffer(); - try { - for (; bis.available() > 0; ) { - byte[] dat = new byte[8]; - bis.read(dat, 0, 8); - version = (0xF & dat[0]); - inst = ((0xFF & dat[1]) << 4) | (0xF0 & dat[0]) >> 4; - fbt = ((0xFF & dat[3]) << 8) | (0xFF & dat[2]); - len = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]); - - log.append(fbt + " " + version + "/" + inst + "/" + len); - -// if (fbt <= 0xF005) { // ignore containers - if (version == 15) { // it's a container - no parsing - // MSOFBTSPGRCONTAINER: // Shape Group Container, contains a variable number of shapes (=msofbtSpContainer) + other groups 0xF003 - // MSOFBTSPCONTAINER: // Shape Container 0xF004 - if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) { - log.append("\tMSOFBTDGCONTAINER"); - } else if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) { - log.append("\tMSOFBTSPGRCONTAINER"); - } else if (fbt == MSODrawingConstants.MSOFBTSPCONTAINER) { - log.append("\tMSOFBTSPCONTAINER"); - } else if (fbt == MSODrawingConstants.MSOFBTSOLVERCONTAINER) { - log.append("\tMSOFBTSOLVERCONTAINER"); - } else { - log.append("\tUNKNOWN CONTAINER"); - } - log.append("\r\n"); - continue; - } - - dat = new byte[len]; - bis.read(dat, 0, len); - switch (fbt) { - case MSODrawingConstants.MSOFBTCALLOUTRULE: - log.append("\tMSOFBTCALLOUTRULE"); - break; - - case MSODrawingConstants.MSOFBTDELETEDPSPL: - log.append("\tMSOFBTDELETEDPSPL"); - break; - - case MSODrawingConstants.MSOFBTREGROUPITEMS: - log.append("\tMSOFBTREGROUPITEMS"); - break; - - case MSODrawingConstants.MSOFBTSP: // A shape atom rec (inst= shape type) rec= shape ID + group of flags - log.append("\tMSOFBTSP"); - int flag; - flag = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]); - if ((flag & 0x800) == 0x800)// it has a shape-type property - log.append("\tshapeType=" + inst); - if (inst == 0) //== shape type - log.append("\tSPIDSEED=" + ByteTools.readInt(dat[0], dat[1], dat[2], dat[3])); - else - log.append("\tSPID=" + +ByteTools.readInt(dat[0], dat[1], dat[2], dat[3])); - log.append("\tflag=" + flag); - break; - - case MSODrawingConstants.MSOFBTCLIENTANCHOR: // Anchor or location fo a shape - log.append("\tMSOFBTCLIENTANCHOR"); - log.append("\t["); - log.append(ByteTools.readShort(dat[2], dat[3]) + ","); - log.append(ByteTools.readShort(dat[4], dat[5]) + ","); - log.append(ByteTools.readShort(dat[6], dat[7]) + ","); - log.append(ByteTools.readShort(dat[8], dat[9]) + ","); - log.append(ByteTools.readShort(dat[10], dat[11]) + ","); - log.append(ByteTools.readShort(dat[12], dat[13]) + ","); - log.append(ByteTools.readShort(dat[14], dat[15]) + ","); - log.append(ByteTools.readShort(dat[16], dat[17])); - log.append("]"); - break; - - case MSODrawingConstants.MSOFBTOPT: // property table atom - log.append("\tMSOFBTOPT"); - //MsofbtOPT optrec= new MsofbtOPT(fbt, inst, version); - //optrec.setData(dat); // sets and parses msoFbtOpt data, including imagename, shapename and imageindex - log.append(optrec.debugOutput()); - break; - - case MSODrawingConstants.MSOFBTSECONDARYOPT: // property table atom block 2 - log.append("\tMSOFBTSECONDARYOPT"); - MsofbtOPT secondaryoptrec = new MsofbtOPT(fbt, inst, version); - secondaryoptrec.setData(dat); // sets and parses msoFbtOpt data, including imagename, shapename and imageindex - log.append(secondaryoptrec.debugOutput()); - break; - - case MSODrawingConstants.MSOFBTTERTIARYOPT: // property table atom block 3 - log.append("\tMSOFBTTERTIARYOPT"); - MsofbtOPT tertiaryoptrec = new MsofbtOPT(fbt, inst, version); - tertiaryoptrec.setData(dat); // sets and parses msoFbtOpt data, including imagename, shapename and imageindex - log.append(tertiaryoptrec.debugOutput()); - break; - - case MSODrawingConstants.MSOFBTDG: // Drawing Record: ID, num shapes + Last SPID for this DG - log.append("\tMSOFBTDG"); - log.append("\tID=" + inst); - log.append("\tns=" + ByteTools.readInt(dat[0], dat[1], dat[2], dat[3])); // number of shapes in this drawing - log.append("\tllastSPID=" + ByteTools.readInt(dat[4], dat[5], dat[6], dat[7])); - break; - - case MSODrawingConstants.MSOFBTCLIENTTEXTBOX: - log.append("\tMSOFBTCLIENTTEXTBOX"); - break; - - case MSODrawingConstants.MSOFBTSPGR: - log.append("\tMSOFBTSPGR"); - break; - - case MSODrawingConstants.MSOFBTCLIENTDATA: - log.append("\tMSOFBTCLIENTDATA"); - break; - - case MSODrawingConstants.MSOFBTSOLVERCONTAINER: - log.append("\tMSOFBTSOLVERCONTAINER"); - break; - - case MSODrawingConstants.MSOFBTCHILDANCHOR: - log.append("\tMSOFBTCHILDANCHOR"); - break; - - case MSODrawingConstants.MSOFBTCONNECTORRULE: - log.append("\tMSOFBTCONNECTORRULE"); - break; - - default: //MSOFBTCONNECTORRULE - log.append("\tUNKNOWN ATOM"); - break; - } - log.append("\r\n"); - } - } catch (Exception e) { - log.append("\r\nEXCEPTION: " + e.toString()); - } - log.append("**"); - return log.toString(); - } - - public void close() { - super.close(); - this.bounds = null; - optrec = null; // 20091209 KSC: save MsofbtOPT records for later updating ... - secondaryoptrec = null; - tertiaryoptrec = null; // apparently can have secondary and tertiary obt recs depending upon version in which it was saved ... - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/MSODrawing.kt b/src/main/java/io/starter/formats/XLS/MSODrawing.kt new file mode 100644 index 0000000..e845fa2 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/MSODrawing.kt @@ -0,0 +1,2321 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.ColHandle +import io.starter.OpenXLS.RowHandle +import io.starter.formats.escher.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + + +/** + * **Msodrawing: MS Office Drawing (ECh)**

                  + * + * + * These records contain only data. + * + *

                  + *
                  + * offset  name        size    contents
                  + * ---
                  + * 4       rgMSODr     var    MSO Drawing Data
                  + *
                  +
                  * + * + * + * The Msodrawing record represents the MSOFBTDGCONTAINER (0xF002) in the Drawing Layer (Escher) format, and contains all per-sheet + * types of info, including the shapes themselves. (A shape=the elemental object that composes a drawing. All graphical figures on a + * drawing are shapes). With few exceptions, shapes are stored hierachically according to how they've been grouped thru the use of + * the Draw/Group command). + * Each Msodrawing record contains several sub-records or atoms (atoms are records that are kept inside container records; container + * records keep atoms and other container records organized). + * There are several such records that are important to us (these are always present - except for MSOFBTDG in subsequent recs): + * MSOFBTDG- Basic Drawing Info- #shapes in this drawing; last SPID given to an SP in this Drawing Group + * MSOFBTSPGRCONTAINER- Patriarch shape Container - always first MSOFBTSPGRCONTAINER in the drawing container + * MSOFBTSPCONTAINER- Shape Container + * MSOFBTSP- Shape Atom Record- SPID= shape id + a set of flags + * MSOFBTOPT- Property Table Record Associated with Shape Rec- holds image Name, index + many other properties + * MSOFBTCLIENTANCHOR- Client Anchor rec- holds size/bounds info + * MSOFBTCLIENTDATA- Host-specific client data record + * + * + * There are many other records or atoms that are optional and that we will omit for now. + * + * + * There appears to be 1 msodrawing record per image (there is also one OBJ record per Msodrawing record). + * The first or header msodrawing record contains MSODBTDG for # shapes + * There is one MsodrawingGroup record per file. This MsodrawingGroup record also contains sub-records that hold # shapes + * + * + * Occasionally, when there is a lot of image data, there can be 2 MSODRAWINGGROUP objects -- continue recs will follow + * the second MSODG only. + * + * + * SPIDs are unique per drawing group, and are parceled out by the drawing group to individual drawings in blocks of 1024 + * + * @see MSODrawingGroup + */ +// TODO: MSOFBTCLIENTANCHOR may be substituted for MSOFBTANCHOR (clipboard), MSOFBTCHILDANCHOR (if shape is a child of a group shape) +class MSODrawing : io.starter.formats.XLS.XLSRecord() { + + var PROTOTYPE_BYTES = byteArrayOf(15, 0, 4, -16, 92, 0, 0, 0, -78, 4, 10, -16, 8, 0, 0, 0, 2, 4, 0, 0, 0, 10, 0, 0, 35, 0, 11, -16, 34, 0, 0, 0, 4, 65, 2, 0, 0, 0, 5, -63, 22, 0, 0, 0, 66, 0, 108, 0, 117, 0, 101, 0, 32, 0, 104, 0, 105, 0, 108, 0, 108, 0, 115, 0, 0, 0, 0, 0, 16, -16, 18, 0, 0, 0, 2, 0, 2, 0, 0, 0, 11, 0, 0, 0, 6, 0, 0, 0, 22, 0, 75, 0, 0, 0, 17, -16, 0, 0, 0, 0) + + internal var imageIndex = -1 + /** + * returns true if this drawing is active i.e. not deleted + *

                  Note this is experimental + * + * @return + */ + // TODO: also report falses if height or width is 0?? + var isActive = false + internal set // true if this image is Active (not deleted) - NOTE: setting Active algorithm is not definitively proven + var name: String? = "" + internal set + internal var shapeName: String? = "" + internal var clientAnchorFlag: Short = 0 // MSOFBTCLIENTANCHOR 1st two bytes - * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells. */ + internal var bounds: ShortArray? = ShortArray(8) // MSOFBTCLIENTANCHOR- + /** + * public method that returns saved height value; + * used when calculated method is incorrect due to row height changes ... + * + * @return + */ + var height: Short = 0 + internal set + /** + * public methd that returns saved width value; + * useful when calculated method is incorrect due to column width changes ... + * + * @return + */ + var originalWidth: Short = 0 + internal set // save original height and width so that if underlying row height(s) or column width(s) change, can still set dimensions correctly ... + /** + * retrieve the OPT rec for specific option setting + * + * @return + */ + var optRec: MsofbtOPT? = null + private set // 20091209 KSC: save MsofbtOPT records for later updating ... + private var secondaryoptrec: MsofbtOPT? = null + private var tertiaryoptrec: MsofbtOPT? = null // apparently can have secondary and tertiary obt recs depending upon version in which it was saved ... + internal var shapeType: Short = 0 // shape type for this drawing record + + private var SPIDSEED = 1024 + /** + * @return whether "this is the header drawing object for the sheet" + */ + var isHeader = false + internal set // whether "this is the 1st Msodrawing rec in the sheet" - contains several header records + internal var SPID = 0 // Shape ID + /** + * returns the Shape Container Length for this drawing record + * used when setting total size for the header drawing record + * + * @return + */ + var spContainerLength = 0 + internal set // this shape's container length + internal var isShape = true // false for Mso's which do not contain an SPCONTAINER sub-record (can be attached textbox or solver container); not included in number of shapes count + // only applicable to header records *** + internal var numShapes = 1 // TODO: how do we know the value for a new image???? + internal var lastSPID = SPIDSEED // lastSPID is stored at book level so that we can track max SPIDs - useful when images have been deleted and SPIDs are not in order ... + internal var otherSPCONTAINERLENGTH = 0 // sum of other SPCONTAINERLENGTHs from other Msodrawing recs + /** + * returns the solver container length for this drawing record + * used when setting the total size for the header drawing record + * + * @return + */ + var solverContainerLength = 0 + internal set // Solver Container length + internal var drawingId = 0 // ordinal # of this drawing record in the workbook + + /** + * return coordinates as {X, Y, W, H} in pixels + * + * @param coords + */ + /** + * set coordinates as {X, Y, W, H} in pixels + * + * @param coords + */ + //*** WHY need -4 ????????? + //*** WHY need -2 ????????? + /* testsing new way above short x= (short) Math.round(getX()*(XCONVERSION); // convert excel units to pixels + short y= (short) Math.round(getY()*PIXELCONVERSION); // convert points to pixels + short w= (short) Math.round(getWidth()*WCONVERSION); // convert excel units to pixels + short h= (short) Math.round((calcHeight()-8)*PIXELCONVERSION); // convert points to pixels +*/// TODO should use ABOVE CALC!!!! see getCoords ****************** + // convert pixels to excel units + // convert pixels to points + // convert pixels to excel units + // convert pixels to points + var coords: ShortArray + get() { + val x = Math.round((x * 256 / ColHandle.COL_UNITS_TO_PIXELS).toFloat()).toShort() + val y = Math.round((y * 20 / (RowHandle.ROW_HEIGHT_DIVISOR - 4)).toFloat()).toShort() + val w = Math.round((width * 256 / ColHandle.COL_UNITS_TO_PIXELS).toFloat()).toShort() + val h = Math.round((calcHeight() * 20 / (RowHandle.ROW_HEIGHT_DIVISOR - 2)).toFloat()).toShort() + return shortArrayOf(x, y, w, h) + } + set(coords) { + val x = Math.round((coords[0] * ColHandle.COL_UNITS_TO_PIXELS / 256).toFloat()).toShort() + val y = Math.round((coords[1] * (RowHandle.ROW_HEIGHT_DIVISOR - 4) / 20).toFloat()).toShort() + val w = Math.round((coords[2] * ColHandle.COL_UNITS_TO_PIXELS / 256).toFloat()).toShort() + val h = Math.round((coords[3] * (RowHandle.ROW_HEIGHT_DIVISOR - 2) / 20).toFloat()).toShort() + setX(x.toInt()) + setY(y.toInt()) + setWidth(w.toInt()) + setHeight(h.toInt()) + } + + val col: Int + get() = bounds!![MSODrawing.COL].toInt() + + val col1: Int + get() = bounds!![MSODrawing.COL1].toInt() + + val row0: Int + get() = bounds!![MSODrawing.ROW].toInt() + + var row1: Int + get() = bounds!![MSODrawing.ROW1].toInt() + set(row) { + bounds[MSODrawing.ROW1] = row.toShort() + updateClientAnchorRecord(bounds) + } + + /** + * get X value of upper left corner + * units are in excel units + * + * @return short x value + */ + val x: Short + get() { + val col = bounds!![MSODrawing.COL].toInt() + val colOff = bounds!![MSODrawing.COLOFFSET] / 1024.0 + var x = 0.0 + for (i in 0 until col) { + x += getColWidth(i).toDouble() + } + x += colOff * getColWidth(col) + return Math.round(x).toShort() + } + + /** + * returns the offset within the column in pixels + * + * @return + */ + val colOffset: Short + get() { + val colOff = bounds!![MSODrawing.COLOFFSET] / 1024.0 + val col = bounds!![MSODrawing.COL].toInt() + val x = colOff * getColWidth(col) + return (Math.round(x) * XCONVERSION).toShort() + } + + /** + * return the y position of this object in points + * + * @return + */ + val y: Short + get() { + val row = bounds!![MSODrawing.ROW].toInt() + var y = 0.0 + for (i in 0 until row) { + y += getRowHeight(i) + } + val rowOff = bounds!![MSODrawing.ROWOFFSET] / 256.0 + y += getRowHeight(row) * rowOff + return Math.round(y).toShort() + } + + /** + * calculate width based upon col#'s, coloffsets and col widths + * units are in excel column units + * + * @return short width + */ + /* + bounds[0]= column # of top left position (0-based) of the shape + bounds[1]= x offset within the top-left column + bounds[2]= row # for top left corner + bounds[3]= y offset within the top-left corner + bounds[4]= column # of the bottom right corner of the shape + bounds[5]= x offset within the cell for the bottom-right corner + bounds[6]= row # for bottom-right corner of the shape + bounds[7]= y offset within the cell for the bottom-right corner + *///correct???? + val width: Short + get() { + + val col = bounds!![MSODrawing.COL].toInt() + val colOff = bounds!![MSODrawing.COLOFFSET] / 1024.0 + val col1 = bounds!![MSODrawing.COL1].toInt() + val colOff1 = bounds!![MSODrawing.COLOFFSET1] / 1024.0 + var w = getColWidth(col) - getColWidth(col) * colOff + for (i in col + 1 until col1) { + w += getColWidth(i).toDouble() + } + if (col1 > col) + w += getColWidth(col1) * colOff1 + else + w = getColWidth(col1) * (colOff1 - colOff) + + return Math.round(w).toShort() + } + + /* col/row and offset methods */ + var colAndOffset: ShortArray + get() = shortArrayOf(bounds!![COL], bounds!![COLOFFSET]) + set(b) { + bounds[COL] = b[0] + bounds[COLOFFSET] = b[1] + updateClientAnchorRecord(bounds) + } + + var rowAndOffset: ShortArray + get() = shortArrayOf(bounds!![ROW], bounds!![ROWOFFSET]) + set(b) { + bounds[ROW] = b[0] + bounds[ROWOFFSET] = b[1] + updateClientAnchorRecord(bounds) + } + /* end position methods */ + + + /** + * @return + */ + /** + * @param phonetic + */ + var mystery: Phonetic? = null + + /** + * return the SPID for this drawing record + */ + /** + * set the SPID for this drawing record + * used upon copyworksheet ... + * + * @param spid + */ + var spid: Int + get() = SPID + set(spid) { + SPID = spid + updateSPID() + } + + /** + * create a new msodrawing record with the desired SPID, imageName, shapeName and imageIndex + * bounds should also be set? + * create correct record bytes + * + * @param spid + * @param imageName + * @param shapeName + * @param imageIndex + * @return + */ + fun createRecord(spid: Int, imageName: String?, shapeName: String?, imageIndex: Int): ByteArray { + this.name = imageName + this.shapeName = shapeName + this.imageIndex = imageIndex + + var retData: ByteArray + // Order of Msodrawing required records: + /* MSOFBTDG + * MSOFBTSPGRCONTAINER + * MSOFBTSPCONTAINER + * MSOFBTSP + * MSOFBTOPT + * MSOFBTCLIENTANCHOR (MSOFBTCHILDANCHOR, MSOFBTANCHOR) + * MSOFBTCLIENTDATA + * + * NOTE that every container has a length field that = the sum of the length of all the atoms (records) it contains as well as the length + * of its header + */ + // key sub-records to update: + // MSOFBTSP, MSOFBTOPT (image index, shape name, image name), CLIENTANCHOR if present - bounds + // plus container which must be calculated from it's sub-records or atoms + this.SPID = spid + //this.SPID = SPIDSEED + imageIndex;// algorithm is incorrect for instances when images are deleted or out of order ... + // Following are present in all msodrawing: MSOFBTSPCONTAINER MSOFBTSP MSOFBTOPT MSOFBTCLIENTANCHOR MSOFBTCLIENTDATA -- not true! can have CHILDANCHOR, ANCHOR ... + //Shape Atom; shape type must be msosptPictureFrame = 75 + val msofbtSp1 = MsofbtSp(MSODrawingConstants.MSOFBTSP, shapeType.toInt(), 2) + msofbtSp1.setId(SPID) + msofbtSp1.setGrfPersistence(2560) //flag= hasSp type + has anchor -- usual for shape-type msoFbtSp's + val msofbtSp1Bytes = msofbtSp1.toByteArray() + + + // OPT= picture options + optRec = MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3) //version is always 3, inst is current count of properties. + if (imageIndex != -1) + optRec!!.imageIndex = imageIndex + if (imageName != null && imageName != "") + optRec!!.imageName = imageName + if (shapeName != null && shapeName != "") + optRec!!.shapeName = shapeName + + val msofbtOPT1Bytes = optRec!!.toByteArray() + + // Client Anchor==Bounds + val msofbtClientAnchor1 = MsofbtClientAnchor(MSODrawingConstants.MSOFBTCLIENTANCHOR, 0, 0) + msofbtClientAnchor1.setBounds(bounds) + val msofbtClientAnchor1Bytes = msofbtClientAnchor1.toByteArray() + + val msofbtClientData1 = MsofbtClientData(MSODrawingConstants.MSOFBTCLIENTDATA, 0, 0) //This is an empty record + val msofbtClientData1Bytes = msofbtClientData1.toByteArray() + + spContainerLength = msofbtSp1Bytes.size + msofbtOPT1Bytes.size + msofbtClientAnchor1Bytes.size + msofbtClientData1Bytes.size + + // 20100412 KSC: must count "oddball" msofbtClientTextBox record that follows this Mso's obj record ... + if (shapeType.toInt() == MSODrawingConstants.msosptTextBox) { + spContainerLength += 8 + } + val msofbtSpContainer1 = MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15) + msofbtSpContainer1.length = spContainerLength + val msofbtSpContainer1Bytes = msofbtSpContainer1.toByteArray() + spContainerLength += +msofbtSpContainer1Bytes.size // include this rec + retData = ByteArray(spContainerLength) + + var pos = 0 + System.arraycopy(msofbtSpContainer1Bytes, 0, retData, pos, msofbtSpContainer1Bytes.size) + pos += msofbtSpContainer1Bytes.size + System.arraycopy(msofbtSp1Bytes, 0, retData, pos, msofbtSp1Bytes.size) + pos += msofbtSp1Bytes.size + System.arraycopy(msofbtOPT1Bytes, 0, retData, pos, msofbtOPT1Bytes.size) + pos += msofbtOPT1Bytes.size + System.arraycopy(msofbtClientAnchor1Bytes, 0, retData, pos, msofbtClientAnchor1Bytes.size) + pos += msofbtClientAnchor1Bytes.size + System.arraycopy(msofbtClientData1Bytes, 0, retData, pos, msofbtClientData1Bytes.size) + pos += msofbtClientData1Bytes.size // 20100420 KSC: empty client data record- necessary??? + + if (isHeader) { //This is only present in the first msodrawing per sheet + if (lastSPID < SPID) + lastSPID = SPID // TODO: Shouldn't assume to be SPID + var totalSPRECORDS = 0 + + // Header also contains Shape Id Seed SP record + val msofbtSp = MsofbtSp(MSODrawingConstants.MSOFBTSP, MSODrawingConstants.msosptMin, 2) //1st shape rec is of type MSOSPTMIN + msofbtSp.setId(SPIDSEED) // SPMIN==SPIDSEED + msofbtSp.setGrfPersistence(5) // flag= fPatriarch, + val msofbtSpBytes = msofbtSp.toByteArray() + + val msofbtSpgr = MsofbtSpgr(MSODrawingConstants.MSOFBTSPGR, 0, 1) + msofbtSpgr.setRect(0, 0, 0, 0) + val msofbtSpgrBytes = msofbtSpgr.toByteArray() + totalSPRECORDS = msofbtSpgrBytes.size + msofbtSpBytes.size + + val msofbtSpContainer = MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15) + msofbtSpContainer.length = totalSPRECORDS + val msofbtSpContainerBytes = msofbtSpContainer.toByteArray() + totalSPRECORDS += msofbtSpContainerBytes.size + + spContainerLength += totalSPRECORDS + val msofbtSpgrContainer = MsofbtSpgrContainer(MSODrawingConstants.MSOFBTSPGRCONTAINER, 0, 15) + msofbtSpgrContainer.length = spContainerLength + otherSPCONTAINERLENGTH + val msofbtSpgrContainerBytes = msofbtSpgrContainer.toByteArray() + + val msofbtDg = MsofbtDg(MSODrawingConstants.MSOFBTDG, drawingId, 0) + msofbtDg.setNumShapes(numShapes) //Number of images and drawings. + msofbtDg.setLastSPID(lastSPID) //lastSPID + val msofbtDgBytes = msofbtDg.toByteArray() + + val msofbtDgContainer = MsofbtDgContainer(MSODrawingConstants.MSOFBTDGCONTAINER, 0, 15) + msofbtDgContainer.length = HEADERRECLENGTH + spContainerLength + otherSPCONTAINERLENGTH + val msofbtDgContainerBytes = msofbtDgContainer.toByteArray() + + val headerRec = ByteArray(80 + retData.size) //+retData.length]; + + pos = 0 + System.arraycopy(msofbtDgContainerBytes, 0, headerRec, pos, msofbtDgContainerBytes.size) + pos += msofbtDgContainerBytes.size + System.arraycopy(msofbtDgBytes, 0, headerRec, pos, msofbtDgBytes.size) + pos += msofbtDgBytes.size + System.arraycopy(msofbtSpgrContainerBytes, 0, headerRec, pos, msofbtSpgrContainerBytes.size) + pos += msofbtSpgrContainerBytes.size + System.arraycopy(msofbtSpContainerBytes, 0, headerRec, pos, msofbtSpContainerBytes.size) + pos += msofbtSpContainerBytes.size + System.arraycopy(msofbtSpgrBytes, 0, headerRec, pos, msofbtSpgrBytes.size) + pos += msofbtSpgrBytes.size + System.arraycopy(msofbtSpBytes, 0, headerRec, pos, msofbtSpBytes.size) + pos += msofbtSpBytes.size + + System.arraycopy(retData, 0, headerRec, pos, retData.size) + retData = headerRec + + } + + this.setData(retData) + this.length = data!!.size + return retData + } + + + /** + * parse the data contained in this drawing record + */ + override fun init() { + // ************************************************************************************************************************************* + // 20070910 KSC: parse MSO record + atom ids (records keep atoms and other containers; atoms contain info and are kept inside containers) + // common record header for both: ver, inst, fbt, len; fbt deterimes record type (0xF000 to 0xFFFF) + // for a specific record, inst differentiates atoms + // for atoms, ver= version; for records, ver= 0xFFFF + // for atoms, len= of atom excluding header; for records; sum of len of atoms contained within it + + /* an Msodrawing record may contain the following records and atoms: + * MSOFBTDG // drawing record: count + MSOSPID seed + MSOFBTREGROUPITEMS // Mappings to reconstitute groups (for regrouping) + MSOFBTCOLORSCHEME + MSOFBTSPGRCONTAINER // Group Shape Container + MSOFBTSPCONTAINER // Shape Container + MSOFBTSPGR // Group-shape-specific info (i.e. shapes that are groups) optional + ** MSOFBTSP // A Shape atom record ** + ** MSOFBTOPT // The Property Table for a shape ** - image index + name ... + MSOFBTTEXTBOX // if the shape has text + MSOFBTCLIENTTEXTBOX // for clipboard stream + MSOFBTANCHOR // Anchor or location fo a shape (if streamed to a clipboard) optional + MSOFBTCHILDANCHOR // " ", if shape is a child of a group shape optional + ** MSOFBTCLIENTANCHOR // Client Anchor/Bounds ** + MSOFBTCLIENTDATA // content is determined by host optional + MSOFBTOLEOBJECT // optional + MSOFBTDELETEDPSPL // optional + */ + // ************************************************************************************************************************************* + super.init() + + val bis = ByteArrayInputStream(super.getData()!!) + var version: Int + var inst: Int + var fbt: Int + var len: Int + spContainerLength = 0 // this shape container length + otherSPCONTAINERLENGTH = 0 // if header, all other SPCONTAINERLENGTHS -- calc from DGCONTAINERLENGTH + SPCONTAINERLENGTHS + + var SPGRCONTAINERLENGTH = 0 // group shape container length + var SPCONTAINERATOMS = 0 // atoms or sub-records which make up the shape container + var DGCONTAINERLENGTH = 0 // drawing container length + var DGCONTAINERATOMS = 0 // atoms or sub-records which, along with SPGRCONTAINER + SOLVERCONTAINER(s), make up the DGCONTAINERLENGHT + var SOLVERCONTAINERATOMS = 0 // atoms or sub-records which make up the SOLVERCONTAINERLENGTH + var hasUndoInfo = false // true if a non-header Mso contains an SPGRCONTAINER - documentation states: Shapes that have been deleted but that could be brought back via Undo. + while (bis.available() > 0) { + var dat = ByteArray(8) + bis.read(dat, 0, 8) + version = 0xF and dat[0] // 15 for containers, version for atoms + inst = 0xFF and dat[1] shl 4 or (0xF0 and dat[0] shr 4) + fbt = 0xFF and dat[3] shl 8 or (0xFF and dat[2]) // record type id + len = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]) // for atoms, record length - header length (=8), if container, refers to sum of lengths of atoms inside it, incl. record headers + if (version == 15) {// do not parse containers + // MSOFBTSPGRCONTAINER: // Shape Group Container, contains a variable number of shapes (=msofbtSpContainer) + other groups 0xF003 + // MSOFBTSPCONTAINER: // Shape Container 0xF004 + // may have several SPCONTAINERs, 1 for background shape, several for deleted shapes ... + // possible containers + // DGCONTAINER= DG, REGROUPITEMS, ColorSCHEME, SPGR, SPCONTAINER + // SPGRCONTAINER= SPCONTAINER(s) + // SPCONTAINER= SPGR, SP, OPT, TEXTBOX, ANCHOR, CHILDANCHOR, CLIENTANCHOR, CLIENTDATA, OLEOBJECT, DeletedPSPL + // SOLVERCONTAINER= ConnetorRule, AlignRule, ArcRule, ClientRule, CalloutRule, + if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) { + isHeader = true + otherSPCONTAINERLENGTH = len //-HEADERRECLENGTH; + DGCONTAINERLENGTH = len + } else if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) {//patriarch shape, with all non-background non-deleted shapes in it - may have more than 1 subrecord + // A group is a collection of other shapes. The contained shapes are placed in the coordinate system of the group. + // The group container contains a variable number of shapes (msofbtSpContainer) and other groups (msofbtSpgrContainer, for nested groups). + // The group itself is a shape, and always appears as the first msofbtSpContainer in the group container. + if (SPGRCONTAINERLENGTH == 0) + // only add 1st container length - others are deleted shapes ... + SPGRCONTAINERLENGTH = len + if (!isHeader) + // then this is a grouped shape, must add the header length as is apparent in existing container lengths (see below) + hasUndoInfo = true + } else if (fbt == MSODrawingConstants.MSOFBTSPCONTAINER) { + spContainerLength += len + 8 // add 8 for record header + if (isHeader) + // keep track of total "other sp container length" - necessary to calculate SPGRCONTAINERLENGTH + DGCONTAINERLENGTH + otherSPCONTAINERLENGTH -= len + 8 + isShape = true // any mso that contains a normal "spcontainer" is a shape + } else if (fbt == MSODrawingConstants.MSOFBTSOLVERCONTAINER) { // solver container: rules governing shapes + isShape = false + solverContainerLength = len + 8 // added to dgcontainerlength + } else { + Logger.logInfo("MSODrawing.init: unknown container encountered: $fbt") + } + continue + } + // parse atoms or sub-records (distinct from container records above) + dat = ByteArray(len) + bis.read(dat, 0, len) + when (fbt) { + MSODrawingConstants.MSOFBTSP // A shape atom rec (inst= shape type) rec= shape ID + group of flags + -> { + val fGroup: Boolean + val fChild: Boolean + val fPatriarch: Boolean + val fDeleted: Boolean + val fOleShape: Boolean + val fHaveMaster: Boolean + val fFlipH: Boolean + val fFlipV: Boolean + val fConnector: Boolean + val fHaveAnchor: Boolean + val fBackground: Boolean + val fHaveSpt: Boolean + val flag: Int + SPID = ByteTools.readInt(dat[0], dat[1], dat[2], dat[3]) + flag = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]) + // parse out flag: + fGroup = flag and 0x1 == 0x1 // it's a group shape + fChild = flag and 0x2 == 0x2 // it's not a top-level shape + fPatriarch = flag and 0x4 == 0x4 // topmost group shape ** 1 per drawing *8 + fDeleted = flag and 0x8 == 0x8 // had been deleted + // 4 + fOleShape = flag and 0x10 == 0x10 // it's an OLE shape + fHaveMaster = flag and 0x20 == 0x20 // it has a master prop + fFlipH = flag and 0x40 == 0x40 // it's flipped horizontally + fFlipV = flag and 0x80 == 0x80 // it's flipped vertically + // 8 + fConnector = flag and 0x100 == 0x100 // it's a connector type + fHaveAnchor = flag and 0x200 == 0x200 // it's an anchor type + fBackground = flag and 0x400 == 0x400 // it's a background shape + fHaveSpt = flag and 0x800 == 0x800 // it has a shape-type property + // FYI: there are normally two msofbtsp records for each drawing + // the first (flag==5) defines fGroup + fPatriarch, + // with inst==0, apparently shape type to define SPIDSEED + // the second msofbtsp record (flag=2560)defines the SPID and contains flags: + // fHaveAnchor, fHaveSpt and inst==shape type + //NOTE: setting Active algorithm is not definitively proven; + // if it ISN'T active, why isn't the fDeleted flag set??? + isActive = isActive || fPatriarch // if we have fPatriarch, it's active ... + if (fHaveSpt) + shapeType = inst.toShort() // save shape type + if (inst == 0) + //== shape type + SPIDSEED = SPID // seed+imageIndex= SPID + SPCONTAINERATOMS += len + 8 + } + + MSODrawingConstants.MSOFBTCLIENTANCHOR // Anchor or location for a shape + -> { + // NOT SO! sheetIndex = ByteTools.readShort(buf[0],buf[1]); + /** + * bounds[0]= column # of top left position (0-based) of the shape + * bounds[1]= x offset within the top-left column + * bounds[2]= row # for top left corner + * bounds[3]= y offset within the top-left corner + * bounds[4]= column # of the bottom right corner of the shape + * bounds[5]= x offset within the cell for the bottom-right corner + * bounds[6]= row # for bottom-right corner of the shape + * bounds[7]= y offset within the cell for the bottom-right corner + */ + clientAnchorFlag = ByteTools.readShort(dat[0].toInt(), dat[1].toInt()) + bounds[COL] = ByteTools.readShort(dat[2].toInt(), dat[3].toInt()) + bounds[COLOFFSET] = ByteTools.readShort(dat[4].toInt(), dat[5].toInt()) + bounds[ROW] = ByteTools.readShort(dat[6].toInt(), dat[7].toInt()) + bounds[ROWOFFSET] = ByteTools.readShort(dat[8].toInt(), dat[9].toInt()) + bounds[COL1] = ByteTools.readShort(dat[10].toInt(), dat[11].toInt()) + bounds[COLOFFSET1] = ByteTools.readShort(dat[12].toInt(), dat[13].toInt()) + bounds[ROW1] = ByteTools.readShort(dat[14].toInt(), dat[15].toInt()) + bounds[ROWOFFSET1] = ByteTools.readShort(dat[16].toInt(), dat[17].toInt()) + SPCONTAINERATOMS += len + 8 + } + + MSODrawingConstants.MSOFBTOPT // property table atom - for 97 and earlier versions + -> { + // 20091209 KSC: save MsoFbtOpt record for later use in updating, if necessary + optRec = MsofbtOPT(fbt, inst, version) + optRec!!.setData(dat) // sets and parses msoFbtOpt data, including imagename, shapename and imageindex + name = optRec!!.imageName + shapeName = optRec!!.shapeName + imageIndex = optRec!!.imageIndex + SPCONTAINERATOMS += len + 8 + } + + // 20100519 KSC: later versions can have secondary and tertiary opt blocks + MSODrawingConstants.MSOFBTSECONDARYOPT // movie (id= 274) + -> { + secondaryoptrec = MsofbtOPT(fbt, inst, version) + secondaryoptrec!!.setData(dat) // sets and parses msoFbtOpt data + SPCONTAINERATOMS += len + 8 + } + + MSODrawingConstants.MSOFBTTERTIARYOPT // for Office versions 2000, XP, 2003, and 2007 + -> { + tertiaryoptrec = MsofbtOPT(fbt, inst, version) + tertiaryoptrec!!.setData(dat) // sets and parses msoFbtOpt data + SPCONTAINERATOMS += len + 8 + } + + MSODrawingConstants.MSOFBTDG // Drawing Record: ID, num shapes + Last SPID for this DG + -> { + drawingId = inst + numShapes = ByteTools.readInt(dat[0], dat[1], dat[2], dat[3]) // number of shapes in this drawing + lastSPID = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]) + DGCONTAINERATOMS += len + 8 + otherSPCONTAINERLENGTH -= len + 8 // these atoms are part of DGCONTAINER, not SPCONTAINER - adjust accordingly + } + MSODrawingConstants.MSOFBTCOLORSCHEME, MSODrawingConstants.MSOFBTREGROUPITEMS -> { + DGCONTAINERATOMS += len + 8 + otherSPCONTAINERLENGTH -= len + 8 + } + + MSODrawingConstants.MSOFBTCLIENTTEXTBOX // msofbtClientTextbox sub-record, contains only this one atom no containers ... + , MSODrawingConstants.MSOFBTTEXTBOX // msofbtClientTextbox sub-record, contains only this one atom no containers ... + -> isShape = false // is treated differently, isn't counted in numShapes calcs + + MSODrawingConstants.MSOFBTCHILDANCHOR // used for all shapes that belong to a group. The content of the record is simply a RECT in the coordinate system of the parent group shape + , + // If the shape is saved to a clipboard: + MSODrawingConstants.MSOFBTSPGR // shapes that ARE groups, not shapes that are IN groups; The group shape record defines the coordinate system of the shape + , + // If the shape is a child of a group shape: + MSODrawingConstants.MSOFBTANCHOR // used for top-level shapes when the shape streamed to the clipboard. The content of the record is simply a RECT with a coordinate system of 100,000 units per inch and origin in the top-left of the drawing + , MSODrawingConstants.MSOFBTCLIENTDATA, MSODrawingConstants.MSOFBTDELETEDPSPL -> SPCONTAINERATOMS += len + 8 + + MSODrawingConstants.MSOFBTCONNECTORRULE, MSODrawingConstants.MSOFBTALIGNRULE, MSODrawingConstants.MSOFBTARCRULE, MSODrawingConstants.MSOFBTCLIENTRULE, MSODrawingConstants.MSOFBTCALLOUTRULE -> SOLVERCONTAINERATOMS += len + 8 + + else -> Logger.logInfo("MSODrawing.init: unknown subrecord encountered: $fbt") + } + } +/* //DEBUGGING: THESE CONTAINER LENGTH CALCULATIONS PASS FOR ALL thus far MSO's ENCOUNTERED + boolean diff= false; + if (isHeader()) { + int d0= (DGCONTAINERLENGTH-(DGCONTAINERATOMS+SPGRCONTAINERLENGTH+SOLVERCONTAINERLENGTH+8)); + int d1= (SPGRCONTAINERLENGTH+8-(SPCONTAINERLENGTH+otherSPCONTAINERLENGTH)); + if (d0+d1!=0) { + if (DGCONTAINERLENGTH!=(DGCONTAINERATOMS+SPGRCONTAINERLENGTH+SOLVERCONTAINERLENGTH+8)) { // this may not be 100% since must account for OTHER record's SOLVERCONTAINER LENGTHS + io.starter.toolkit.Logger.log("DGCONTAINERLENGTH DIFF: " + (DGCONTAINERLENGTH-(DGCONTAINERATOMS+SPGRCONTAINERLENGTH+SOLVERCONTAINERLENGTH+8))); + diff= true; + } + // ******* sum of SPCONTAINERS *************** + if (SPGRCONTAINERLENGTH+8!=SPCONTAINERLENGTH+otherSPCONTAINERLENGTH) { + io.starter.toolkit.Logger.log("SPGRCONTAINERLENGTH DIFF: " + (SPGRCONTAINERLENGTH+8-(SPCONTAINERLENGTH+otherSPCONTAINERLENGTH))); + diff= true; + } + } + } + // one or two header lengths (8 bytes each) have been added to SPCONTAINERLENGTH + // adjust here: + int headerlens= 0; + if (isHeader()) + headerlens= 16; + else if (isShape) // non-shapes don't have SPGRCONTAINERS + headerlens= 8; + if (isHeader() && SPCONTAINERLENGTH==48) // only one SPCONTAINER, decrement by 1 header length + headerlens= 8; + if (optrec!=null && optrec.hasTextId()) // shape has an attached text box; must add 8 for following CLIENTTEXTBOX (the !isShape Mso which follows ...) + SPCONTAINERATOMS+=8; + if (SPCONTAINERLENGTH-headerlens!=SPCONTAINERATOMS) { + System.err.println("SPCONTAINERLEN IS OFF: " + (SPCONTAINERLENGTH-headerlens-SPCONTAINERATOMS)); + System.err.println(this.toString()); + System.err.println(this.debugOutput()); + diff= true; + } + /**/ + if (hasUndoInfo) +spContainerLength += 8 // Shapes that have been deleted but that could be brought back via Undo. -- must add to sp container length for total container length calc (see UpdateHeader) +} + +/** + * update the existing mso with the appropriate basic mso data + *

                  NOTE: To set other mso data, see setShapeType, setIsHeader ... + * + * @param spid Unique Shape Id + * @param imageName String image name or null + * @param shapeName String shape name or null + * @param imageIndex int index into the image byte store (for a picture-type) or -1 for null + * @param bounds short[8] the position of this Mso given in rows, cols and offsets + */ + fun updateRecord(spid:Int, imageName:String?, shapeName:String?, imageIndex:Int, bounds:ShortArray) { +this.SPID = spid +this.name = imageName +this.shapeName = shapeName +this.imageIndex = imageIndex +this.bounds = bounds +updateRecord() +} + +/** + * rebuild record bytes for this Msodrawing + * aside from updating significant atoms such as MSOFBTOPT, it also recalculates container lengths + * + * @param spid + * @return + */ + fun updateRecord() { + /*// debug: check algorithm: + io.starter.toolkit.Logger.log(this.toString()); + io.starter.toolkit.Logger.log(this.debugOutput()); + int origSP= SPCONTAINERLENGTH; + int origDG= 0; + int origSPGR= 0; +/**/ + var spcontainer1atoms = ByteArray(0) +var spcontainer2atoms = ByteArray(0) // header specific +var dgcontaineratoms = ByteArray(0) // header specific + +var hasUndoInfo = false // true if a non-header Mso contains an SPGRCONTAINER - documentation states: Shapes that have been deleted but that could be brought back via Undo. + // reset state vars + spContainerLength = 0 // this shape container length + + // first pass, update significant atoms and sum up container lengths + val fbt:Int +val len:Int +super.getData() +val bis = ByteArrayInputStream(data!!) +while (bis.available() > 0) +{ +val header = ByteArray(8) +bis.read(header, 0, 8) +fbt = ((0xFF and header[3]) shl 8) or (0xFF and header[2]) +len = ByteTools.readInt(header[4], header[5], header[6], header[7]) +if ((0xF and header[0]) == 15) +{ // 15 for containers, version for atoms + // most containers, just skip; some, however, are necessaary for container length calcs (see below + if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) +{ + //if (origSPGR==0) origSPGR= len; + if (!isHeader) + // then this is a grouped shape, must add the header length as is apparent in existing container lengths (see below) + hasUndoInfo = true +} +else if (fbt == MSODrawingConstants.MSOFBTSPCONTAINER) +{ +isShape = true // any mso that contains a normal "spcontainer" is a shape +} +else if (fbt == MSODrawingConstants.MSOFBTSOLVERCONTAINER) +{ // solver container: rules governing shapes + // TODO: is there EVER a reason to update a SOLVERCONTAINER RECORD??? + // STRUCTURE: + // MSOFBTSOLVERCONTAINER 61445 15/#/# (15/0/0 for an empty solvercontainer) + // then 0 or more rules: + // MSOFBTCONNECTORRULE 61458 1/0/24 ... etc + solverContainerLength = len + 8 // added to dgcontainerlength +isShape = false + // testing- remove when done + } +else if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) +;//origDG= len; +} +else +{ + // parse atoms or sub-records (distinct from container records above) + var data = ByteArray(len) +bis.read(data, 0, len) +when (fbt) { +MSODrawingConstants.MSOFBTDG // update drawing record atom + -> { +System.arraycopy(ByteTools.cLongToLEBytes(this.numShapes), 0, data, 0, 4) +System.arraycopy(ByteTools.cLongToLEBytes(this.lastSPID), 0, data, 4, 4) +len = data.size +data = ByteTools.append(data, header) +dgcontaineratoms = ByteTools.append(data, dgcontaineratoms) +} + +MSODrawingConstants.MSOFBTSP // A shape atom rec (inst= shape type) rec= shape ID + group of flags + -> { + // TODO: necessary to ever update the SPIDSEED? + val flag = ByteTools.readInt(data[4], data[5], data[6], data[7]) +val fHaveSpt = (flag and 0x800) == 0x800 // it has a shape-type property +if (flag != 5) +{ // if it's not the SPIDseed, update SPID +System.arraycopy(ByteTools.cLongToLEBytes(SPID), 0, data, 0, 4) +} +if (fHaveSpt) +{ // shape type is contained within inst var +header[0] = ((0xF and 2) or (0xF0 and (shapeType shl 4))).toByte() +header[1] = ((0x00000FF0 and shapeType) shr 4).toByte() +} +data = ByteTools.append(data, header) +if (flag != 5) + // if it's not the header + spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +else +spcontainer2atoms = ByteTools.append(data, spcontainer2atoms) +} + +MSODrawingConstants.MSOFBTCLIENTANCHOR // Anchor or location for a shape + -> { + // udpate bounds + System.arraycopy(ByteTools.shortToLEBytes(clientAnchorFlag), 0, data, 0, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![0]), 0, data, 2, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![1]), 0, data, 4, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![2]), 0, data, 6, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![3]), 0, data, 8, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![4]), 0, data, 10, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![5]), 0, data, 12, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![6]), 0, data, 14, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![7]), 0, data, 16, 2) +data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTOPT // property table atom - for 97 and earlier versions + -> { + // OPT= picture options + if (optRec == null) + // if do not have a MsoFbtOPT record, then create new -- shouldn't get here as should always have an existing record + optRec = MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3) //version is always 3, inst is current count of properties. +if (imageIndex != optRec!!.getImageIndex()) +optRec!!.setImageIndex(imageIndex) +if (name == null || name != optRec!!.getImageName()) +optRec!!.setImageName(name) +if (shapeName == null || shapeName != optRec!!.getShapeName()) +{ +optRec!!.setShapeName(shapeName) +} +data = optRec!!.toByteArray() +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTTERTIARYOPT // for Office versions 2000, XP, 2003, and 2007 +, MSODrawingConstants.MSOFBTSECONDARYOPT // movie (id= 274) + -> { +data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTCOLORSCHEME, MSODrawingConstants.MSOFBTREGROUPITEMS -> { +data = ByteTools.append(data, header) +dgcontaineratoms = ByteTools.append(data, dgcontaineratoms) +} + +MSODrawingConstants.MSOFBTCLIENTTEXTBOX // msofbtClientTextbox sub-record, contains only this one atom no containers ... +, MSODrawingConstants.MSOFBTTEXTBOX // msofbtClientTextbox sub-record, contains only this one atom no containers ... + -> { + // DON"T ADD LEN ************** + isShape = false +data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTSPGR // shapes that ARE groups, not shapes that are IN groups; The group shape record defines the coordinate system of the shape + -> { +data = ByteTools.append(data, header) +spcontainer2atoms = ByteTools.append(data, spcontainer2atoms) +} + +MSODrawingConstants.MSOFBTCHILDANCHOR // used for all shapes that belong to a group. The content of the record is simply a RECT in the coordinate system of the parent group shape +, MSODrawingConstants.MSOFBTANCHOR // used for top-level shapes when the shape streamed to the clipboard. The content of the record is simply a RECT with a coordinate system of 100,000 units per inch and origin in the top-left of the drawing +, MSODrawingConstants.MSOFBTCLIENTDATA, MSODrawingConstants.MSOFBTDELETEDPSPL -> { +data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTCONNECTORRULE, MSODrawingConstants.MSOFBTALIGNRULE, MSODrawingConstants.MSOFBTARCRULE, MSODrawingConstants.MSOFBTCLIENTRULE, MSODrawingConstants.MSOFBTCALLOUTRULE -> {} + +else -> { +Logger.logInfo("MSODrawing.updateRecord: unknown subrecord encountered: " + fbt) +data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} +}// SOLVERCONTAINERATOMS+=len+8; + // TODO: is there EVER a reason to update a SOLVERCONTAINER RECORD??? + //Logger.logInfo("MSODrawing.updateRecord: encountered solver container atom"); +} +} + // container lengths: + // are these adjustments necessary?? + /* + if (optrec!=null && optrec.hasTextId()) // shape has an attached text box; must add 8 for following CLIENTTEXTBOX (the !isShape Mso which follows ...) + SPCONTAINERATOMS+=8; +*/ + spContainerLength = spcontainer1atoms.size +var additionalSP = 0 +if (hasUndoInfo) +additionalSP = 8 // Shapes that have been deleted but that could be brought back via Undo. -- must add to sp container length for total container length calc (see UpdateHeader) +if (shapeType.toInt() == MSODrawingConstants.msosptTextBox) +additionalSP = 8 // account for attached text mso - which has no SPCONTAINER so must include in "controlling" rec + // 2nd pass: now have the important container lengths and their updated + // subrecords/atoms, create resulting byte array + + // build main spcontainer - valid for all records + val spcontainer1 = MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15) +spcontainer1.length = spContainerLength + additionalSP +val container = spcontainer1.toByteArray() +spContainerLength += container.size // include this header length + + /*// debugging + if (!bIsHeader && SPCONTAINERLENGTH!=origSP) + Logger.logErr("SPCONTAINERLENTH IS OFF: " + (SPCONTAINERLENGTH-origSP)); + */ + var retData = ByteArray(spContainerLength) +System.arraycopy(container, 0, retData, 0, container.size) +System.arraycopy(spcontainer1atoms, 0, retData, container.size, spcontainer1atoms.size) + +spContainerLength += additionalSP// necessary when summing up container lengths -- see WorkBook.updateMsoHeaderRecord +if (isHeader) +{ + // SPCONTAINER + // sp2 -- SPIDSEED + // spgr if necessary + val spcontainer2 = MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15) +spcontainer2.length = spcontainer2atoms.size +val container2 = spcontainer2.toByteArray() +spContainerLength += spcontainer2atoms.size + container2.size // include this header length + + /*// debugging + if (SPCONTAINERLENGTH!=origSP) + Logger.logErr("SPCONTAINERLENTH IS OFF: " + (SPCONTAINERLENGTH-origSP)); + /**/ + // SPGRCONTAINER + val spgrcontainerlen = spContainerLength + otherSPCONTAINERLENGTH - 8 + /*// debugging + if (spgrcontainerlen!=origSPGR) + Logger.logErr("SPGRCONTAINERLENTH IS OFF: " + (spgrcontainerlen-origSPGR)); + /**/ + val msofbtSpgrContainer = MsofbtSpgrContainer(MSODrawingConstants.MSOFBTSPGRCONTAINER, 0, 15) +msofbtSpgrContainer.length = spgrcontainerlen +val spgrcontainer = msofbtSpgrContainer.toByteArray() + + // DGCONTAINER + val dgcontainerlen = (dgcontaineratoms.size + spgrcontainerlen + solverContainerLength + 8) // drawing container length + /*// debugging + if (dgcontainerlen!=origDG) + Logger.logErr("DGCONTAINERLENTH IS OFF: " + (dgcontainerlen-origDG)); + /**/ + val msofbtDgContainer = MsofbtDgContainer(MSODrawingConstants.MSOFBTDGCONTAINER, 0, 15) +msofbtDgContainer.length = dgcontainerlen //HEADERRECLENGTH + SPCONTAINERLENGTH + otherSPCONTAINERLENGTH); +val dgcontainer = msofbtDgContainer.toByteArray() + +val header = ByteArray(HEADERRECLENGTH + spContainerLength - additionalSP + dgcontainer.size) //+retData.length]; + +var pos = 0 +System.arraycopy(dgcontainer, 0, header, pos, dgcontainer.size) +pos += dgcontainer.size +System.arraycopy(dgcontaineratoms, 0, header, pos, dgcontaineratoms.size) +pos += dgcontaineratoms.size +System.arraycopy(spgrcontainer, 0, header, pos, spgrcontainer.size) +pos += spgrcontainer.size +System.arraycopy(container2, 0, header, pos, container2.size) +pos += container2.size +System.arraycopy(spcontainer2atoms, 0, header, pos, spcontainer2atoms.size) +pos += spcontainer2atoms.size + +System.arraycopy(retData, 0, header, pos, retData.size) +retData = header +} +this.setData(retData) +this.length = data!!.size + + + // testing + /* + io.starter.toolkit.Logger.log(this.toString()); + io.starter.toolkit.Logger.log(this.debugOutput()); + /**/ + } + +/** + * add the set of subrecords necessary to define a Mso header record + *

                  used when removing images, charts, etc. and have removed a previous header record + */ + fun addHeader() { + /*// testing + System.err.println(this.toString()); + System.err.println(this.debugOutput()); + /**/ + isHeader = true +if (lastSPID < SPID) +lastSPID = SPID // TODO: Shouldn't assume to be SPID + +val msofbtSp = MsofbtSp(MSODrawingConstants.MSOFBTSP, MSODrawingConstants.msosptMin, 2) //1st shape rec is of type MSOSPTMIN +msofbtSp.setId(SPIDSEED) // SPMIN==SPIDSEED +msofbtSp.setGrfPersistence(5) // flag= fPatriarch, +val msofbtSpBytes = msofbtSp.toByteArray() +spContainerLength += msofbtSpBytes.size + +val msofbtSpgr = MsofbtSpgr(MSODrawingConstants.MSOFBTSPGR, 0, 1) +msofbtSpgr.setRect(0, 0, 0, 0) +val msofbtSpgrBytes = msofbtSpgr.toByteArray() +spContainerLength += msofbtSpgrBytes.size + + // SPCONTAINER + val msofbtSpContainer = MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15) +msofbtSpContainer.length = msofbtSpgrBytes.size + msofbtSpBytes.size +val msofbtSpContainerBytes = msofbtSpContainer.toByteArray() +spContainerLength += 8 // account for the SPCONTAINER header length; + + // SPGRCONTAINER + val msofbtSpgrContainer = MsofbtSpgrContainer(MSODrawingConstants.MSOFBTSPGRCONTAINER, 0, 15) +msofbtSpgrContainer.length = spContainerLength + otherSPCONTAINERLENGTH +val msofbtSpgrContainerBytes = msofbtSpgrContainer.toByteArray() + +val msofbtDg = MsofbtDg(MSODrawingConstants.MSOFBTDG, drawingId, 0) +msofbtDg.setNumShapes(numShapes) //Number of images and drawings. +msofbtDg.setLastSPID(lastSPID) //lastSPID +val msofbtDgBytes = msofbtDg.toByteArray() + + // DGCONTAINER + val msofbtDgContainer = MsofbtDgContainer(MSODrawingConstants.MSOFBTDGCONTAINER, 0, 15) +msofbtDgContainer.length = HEADERRECLENGTH + spContainerLength + otherSPCONTAINERLENGTH +val msofbtDgContainerBytes = msofbtDgContainer.toByteArray() + + +val headerRec = ByteArray(this.getData()!!.size + 80) // below records take 80 bytes + +var pos = 0 +System.arraycopy(msofbtDgContainerBytes, 0, headerRec, pos, msofbtDgContainerBytes.size) +pos += msofbtDgContainerBytes.size// 8 +System.arraycopy(msofbtDgBytes, 0, headerRec, pos, msofbtDgBytes.size) +pos += msofbtDgBytes.size +System.arraycopy(msofbtSpgrContainerBytes, 0, headerRec, pos, msofbtSpgrContainerBytes.size) +pos += msofbtSpgrContainerBytes.size// 8 +System.arraycopy(msofbtSpContainerBytes, 0, headerRec, pos, msofbtSpContainerBytes.size) +pos += msofbtSpContainerBytes.size// 8 +System.arraycopy(msofbtSpgrBytes, 0, headerRec, pos, msofbtSpgrBytes.size) +pos += msofbtSpgrBytes.size +System.arraycopy(msofbtSpBytes, 0, headerRec, pos, msofbtSpBytes.size) +pos += msofbtSpBytes.size + +System.arraycopy(this.getData()!!, 0, headerRec, pos, this.getData()!!.size) +setData(headerRec) + + /*// testing + System.err.println(this.toString()); + System.err.println(this.debugOutput()); + /**/ + } + +/** + * remove the set of subrecords necessary to define a MSO header record + *

                  used when removing images, charts, etc. and have removed a previous header record + */ + fun removeHeader() { + /*// testing + System.err.println(this.toString()); + System.err.println(this.debugOutput()); + */ + var spcontainer1atoms = ByteArray(0) + // reset state vars + spContainerLength = 0 // this shape container length + + // first pass, update significant atoms and sum up container lengths + val fbt:Int +val len:Int +super.getData() +val bis = ByteArrayInputStream(data!!) +while (bis.available() > 0) +{ +val header = ByteArray(8) +bis.read(header, 0, 8) +fbt = ((0xFF and header[3]) shl 8) or (0xFF and header[2]) +len = ByteTools.readInt(header[4], header[5], header[6], header[7]) +if ((0xF and header[0]) != 15) +{ // skip containers + // parse atoms or sub-records (distinct from container records above) + var data = ByteArray(len) +bis.read(data, 0, len) +when (fbt) { +MSODrawingConstants.MSOFBTDG // update drawing record atom + -> {} + +MSODrawingConstants.MSOFBTSP // A shape atom rec (inst= shape type) rec= shape ID + group of flags + -> { + // TODO: necessary to ever update the SPIDSEED? + val flag = ByteTools.readInt(data[4], data[5], data[6], data[7]) +if (flag != 5) +{ // if it's not the SPIDseed, update SPID +System.arraycopy(ByteTools.cLongToLEBytes(SPID), 0, data, 0, 4) +} +data = ByteTools.append(data, header) +if (flag != 5) + // if it's not the header + spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTCLIENTANCHOR // Anchor or location for a shape + -> { + // udpate bounds + System.arraycopy(ByteTools.shortToLEBytes(clientAnchorFlag), 0, data, 0, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![0]), 0, data, 2, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![1]), 0, data, 4, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![2]), 0, data, 6, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![3]), 0, data, 8, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![4]), 0, data, 10, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![5]), 0, data, 12, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![6]), 0, data, 14, 2) +System.arraycopy(ByteTools.shortToLEBytes(bounds!![7]), 0, data, 16, 2) +data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTOPT // property table atom - for 97 and earlier versions + -> { + // OPT= picture options + if (optRec == null) + // if do not have a MsoFbtOPT record, then create new -- shouldn't get here as should always have an existing record + optRec = MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3) //version is always 3, inst is current count of properties. +if (imageIndex != -1 && imageIndex != optRec!!.getImageIndex()) +optRec!!.setImageIndex(imageIndex) +if (name != null && name != "" && name != optRec!!.getImageName()) +optRec!!.setImageName(name) +if (shapeName != null && shapeName != "" && shapeName != optRec!!.getShapeName()) +{ +optRec!!.setShapeName(shapeName) +} +data = optRec!!.toByteArray() +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTTERTIARYOPT // for Office versions 2000, XP, 2003, and 2007 +, MSODrawingConstants.MSOFBTSECONDARYOPT // movie (id= 274) + -> { +data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTCOLORSCHEME, MSODrawingConstants.MSOFBTREGROUPITEMS -> {} + +MSODrawingConstants.MSOFBTCLIENTTEXTBOX // msofbtClientTextbox sub-record, contains only this one atom no containers ... +, MSODrawingConstants.MSOFBTTEXTBOX // msofbtClientTextbox sub-record, contains only this one atom no containers ... + -> { + // DON"T ADD LEN + data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTSPGR // shapes that ARE groups, not shapes that are IN groups; The group shape record defines the coordinate system of the shape + -> {} + +MSODrawingConstants.MSOFBTCHILDANCHOR // used for all shapes that belong to a group. The content of the record is simply a RECT in the coordinate system of the parent group shape +, MSODrawingConstants.MSOFBTANCHOR // used for top-level shapes when the shape streamed to the clipboard. The content of the record is simply a RECT with a coordinate system of 100,000 units per inch and origin in the top-left of the drawing +, MSODrawingConstants.MSOFBTCLIENTDATA, MSODrawingConstants.MSOFBTDELETEDPSPL -> { +data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} + +MSODrawingConstants.MSOFBTCONNECTORRULE, MSODrawingConstants.MSOFBTALIGNRULE, MSODrawingConstants.MSOFBTARCRULE, MSODrawingConstants.MSOFBTCLIENTRULE, MSODrawingConstants.MSOFBTCALLOUTRULE -> {} + +else -> { +Logger.logInfo("MSODrawing.removeHeader: unknown subrecord encountered: " + fbt) +data = ByteTools.append(data, header) +spcontainer1atoms = ByteTools.append(data, spcontainer1atoms) +} +}// SOLVERCONTAINERATOMS+=len+8; + //don't add to len + // TODO: HANDLE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +} +} +spContainerLength = spcontainer1atoms.size + // 2nd pass: now have the important container lengths and their updated + // subrecords/atoms, create resulting byte array + + // build main spcontainer - valid for all records + val spcontainer1 = MsofbtSpContainer(MSODrawingConstants.MSOFBTSPCONTAINER, 0, 15) +spcontainer1.length = spContainerLength +val container = spcontainer1.toByteArray() +spContainerLength += container.size // include this header length + + //if (!bIsHeader && SPCONTAINERLENGTH!=origSP) + //Logger.logErr("SPCONTAINERLENTH IS OFF: " + (SPCONTAINERLENGTH-origSP)); + + val retData = ByteArray(spContainerLength) +System.arraycopy(container, 0, retData, 0, container.size) +System.arraycopy(spcontainer1atoms, 0, retData, container.size, spcontainer1atoms.size) + +setData(retData) + /*// testing + System.err.println(this.toString()); + System.err.println(this.debugOutput()); + /**/ + } + +/** + * update the header records with new container lengths + * + * @param otherlength + */ + fun updateHeader(otherSPContainers:Int, otherContainers:Int, numShapes:Int, lastSPID:Int) { +if (!isHeader) +{ +Logger.logErr("Msodrawing.updateHeader is only applicable for the header drawing object") +return +} + /* + // dgcontainerlength= dg(+8) + regroupitems(+8) + spgrcontainer + solvercontainer(s) + colorscheme(+8) + // spgrcontainerlength= sum of spcontainers i.e this spcontainerlength + otherspcontainers + */ + this.numShapes = numShapes + // this.lastSPID= SPIDSEED + nImages; algorithm is wrong when book contains deleted images, etc. + this.lastSPID = lastSPID +val fbt:Int +val len:Int + // the two container lengths we are concerned about: + //SPGRCONTAINERLENGTH + otherSPCONTAINERLENGTH = otherSPContainers +var spgrcontainerlength = spContainerLength + otherSPCONTAINERLENGTH + // DGCONTAINERLENGTH= spgrcontainerlength + all the atoms contained within the DG CONTAINER + var dgcontainerlength = otherContainers + spgrcontainerlength +var DGATOMS = 0 +var hasSPGRCONTAINER = false + /* // debugging container lengths on update + boolean diff= false; + int origSPGRL= 0; + int origDGL= 0; + /**/ + // count dgcontainer atorms=MSOFBTDG, MSOFBTREGROUPITEMS, MSOFBTCOLORSCHEME (MSOFBTSPGRCONTAINER is added separately) + super.getData() +var bis = ByteArrayInputStream(data!!) +while (bis.available() > 0) +{ +var buf = ByteArray(8) +bis.read(buf, 0, 8) +fbt = ((0xFF and buf[3]) shl 8) or (0xFF and buf[2]) +len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]) + // 1st pass count dgcontainer atoms + if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) +{ + // skip contianers + //origDGL= len; // debugging + } +else if (fbt == MSODrawingConstants.MSOFBTSPCONTAINER) +{ + // skip containers + } +else if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) +{ +if (hasSPGRCONTAINER) +{ // already has the 1 required SPGRCONTAINER, if more than 1, multiple groups +spgrcontainerlength += 8 +} +DGATOMS += 8 // just count header length here + //if (!hasSPGRCONTAINER) origSPGRL= len; // debugging + hasSPGRCONTAINER = true +} +else if (fbt == MSODrawingConstants.MSOFBTOPT) +{ +break // nothing important after this +} +else +{ +if (fbt == MSODrawingConstants.MSOFBTREGROUPITEMS || fbt == MSODrawingConstants.MSOFBTDG || fbt == MSODrawingConstants.MSOFBTCOLORSCHEME) +DGATOMS += (len + 8) +buf = ByteArray(len) +bis.read(buf, 0, len) +} +} +dgcontainerlength += DGATOMS +/** debugging */ + /*if (origSPGRL!=spgrcontainerlength || origDGL!=dgcontainerlength) { + io.starter.toolkit.Logger.log(this.toString()); + io.starter.toolkit.Logger.log(this.debugOutput()); + io.starter.toolkit.Logger.log("ORIGDG=" + origDGL + " ORIGSPL=" + origSPGRL + " DIFF: " + (origDGL-dgcontainerlength) + "-" + (origSPGRL-spgrcontainerlength)); + diff= true; + } + /**/ + + super.getData() +bis = ByteArrayInputStream(data!!) +while (bis.available() > 0) +{ +var buf = ByteArray(8) +bis.read(buf, 0, 8) +fbt = ((0xFF and buf[3]) shl 8) or (0xFF and buf[2]) +len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]) + +if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) +{ +System.arraycopy(ByteTools.cLongToLEBytes(dgcontainerlength), 0, data!!, data!!.size - bis.available() - 4, 4) +} +else if (fbt == MSODrawingConstants.MSOFBTDG) +{ // Drawing Record: count + MSOSPID seed +buf = ByteArray(len) +bis.read(buf, 0, len) +val newrec = ByteArray(8) +System.arraycopy(ByteTools.cLongToLEBytes(this.numShapes), 0, newrec, 0, 4) +System.arraycopy(ByteTools.cLongToLEBytes(this.lastSPID), 0, newrec, 4, 4) +if (len == newrec.size) +{// should!!! + // update Msodrawing data ... + System.arraycopy(newrec, 0, data!!, data!!.size - bis.available() - newrec.size, newrec.size) +} +else +Logger.logErr("UpdateClientAnchorRecord: New Array Size=" + newrec.size) +} +else if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) +{ // sum of all spcontainers on the sheet +System.arraycopy(ByteTools.cLongToLEBytes(spgrcontainerlength), 0, data!!, data!!.size - bis.available() - 4, 4) + /*if (diff) { // debugging container lengths + io.starter.toolkit.Logger.log(this.toString()); + io.starter.toolkit.Logger.log(this.debugOutput()); + }/**/ + return +} +else +{ // skip atoms +buf = ByteArray(len) +bis.read(buf, 0, len) +} +} +} + +/** + * update just the image index portion of this record + * useful when you don't need to rebuild entire record (and thus possibly lose information) + * + * @param idx + */ + fun updateImageIndex(idx:Int) { +val version:Int +val inst:Int +val fbt:Int +val len:Int +super.getData() +val bis = ByteArrayInputStream(data!!) +while (bis.available() > 0) +{ +var buf = ByteArray(8) +bis.read(buf, 0, 8) +inst = ((0xFF and buf[1]) shl 4) or ((0xF0 and buf[0]) shr 4) +fbt = ((0xFF and buf[3]) shl 8) or (0xFF and buf[2]) +len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]) +if (fbt < 0xF005) +{ // ignore containers +continue +} +buf = ByteArray(len) +bis.read(buf, 0, len) // position at next sub-rec +if (fbt == MSODrawingConstants.MSOFBTOPT) +{ + // Find the property that governs image index and update the bytes + val propertyId:Int +val n = inst // number of properties to parse +var pos = 0 // pointer to current property in data/property table +for (i in 0 until n) +{ +propertyId = ((0x3F and buf[pos + 1]) shl 8) or (0xFF and buf[pos]) +if (propertyId == MSODrawingConstants.msooptpib) +{// blip to display = image index + // testing int dtx = ByteTools.readInt(dat[pos+2],dat[pos+3],dat[pos+4],dat[pos+5]); + val insertPosition = data!!.size - bis.available() - len + pos + 2 +System.arraycopy(ByteTools.cLongToLEBytes(idx), 0, data!!, insertPosition, 4) +imageIndex = idx +return +} +pos += 6 +} +} +} +} + + +/** + * removes the header-specific portion of this Msodrawing record, if any + * useful when adding Msodrawng recs from other sources such as adding charts + * only one Msodrawing header record is allowed + */ + fun makeNonHeader() { +if (!isHeader) +return // nothing to do! +isHeader = false +this.removeHeader() +} + +/** + * set whether "this is the header drawing object for the sheet" + * + * @param b + */ + fun setIsHeader() { +if (!isHeader) +{ +this.addHeader() +} +} + +/** + * @return explicitly-set shape name (= name in NamedRange box, I believe) + */ + fun getShapeName():String? { +return shapeName +} + + +/** + * @return true if this image has a border + */ + // TODO: detecting a border may be more complicated than this + // TODO: ability to set a border + fun hasBorder():Boolean { +if (optRec != null) +return optRec!!.hasBorder() +return false +} + +/** + * if has a border, return the border line width + * + * @return + */ + fun borderLineWidth():Int { +if (optRec != null) +return optRec!!.borderLineWidth +return -1 +} + + fun setImageIndex(value:Int) { +imageIndex = value +this.updateRecord() +} + + fun setImageName(name:String) { +if (name != this.name) +{ +this.name = name +updateRecord() +} +} + +/** + * set the ordinal # for this drawing record + * + * @param id + */ + fun setDrawingId(id:Int) { +drawingId = id +updateDGRecord() +} + +/** + * return the ordinal # for this record + * + * @return + */ + fun getDrawingId():Int { +return drawingId +} + +/** + * allow setting of "Named Range" name + * + * @param name + */ + fun setShapeName(name:String) { +if (name != shapeName) +{ +shapeName = name +updateRecord() +} +} + + fun getImageIndex():Int { +return imageIndex +} + + fun setBounds(b:ShortArray) { +bounds = b.clone() +height = calcHeight() // 20090831 KSC +updateClientAnchorRecord(bounds) +} + + +/** + * set coordinates as {X, Y, W, H} in 7 + * + * @param coords + */ + fun setBoundsInPixels(coords:ShortArray) { + // 20090505 KSC: convert + val x = Math.round(coords[0].toFloat()).toShort() // convert pixels to excel units +val y = Math.round(coords[1].toFloat()).toShort() // convert pixels to points +val w = Math.round(coords[2].toFloat()).toShort() // convert pixels to excel units +val h = Math.round(coords[3].toFloat()).toShort() // convert pixels to points + /* setX(coords[0]); + setY(coords[1]); + setWidth(coords[2]); + setHeight(coords[3]); + */ + setX(x.toInt()) +setY(y.toInt()) +setWidth(w.toInt()) +setHeight(h.toInt()) +} + +/** + * returns the bounds of this object + * bounds are relative and based upon rows, columns and offsets within + */ + fun getBounds():ShortArray? { +return bounds +} + + fun setRow(row:Int) { +bounds[MSODrawing.ROW] = row.toShort() +updateClientAnchorRecord(bounds) +} + +/** + * set the x position of this object + * units are in excel units + * + * @param x + */ + fun setX(x:Int) { +var z = 0 +var col:Short = 0 +var colOffset:Short = 0 +var i:Short = 0 +while (i < XLSConstants.MAXCOLS && z < x) +{ +val w = getColWidth(i.toInt()) +if (z + w < x) +z += w +else +{ +col = i +colOffset = Math.round(1024 * (((x - z).toDouble()) / w.toDouble())).toShort() +z = x +} +i++ +} +bounds[MSODrawing.COL] = col +bounds[MSODrawing.COLOFFSET] = colOffset +updateClientAnchorRecord(bounds) +} + +/** + * set the y position of this object + * units are in points + * + * @param y + */ + fun setY(y:Int) { +var z = 0.0 +var row:Short = 0 +var rowOffset:Short = 0 +var i:Short = 0 +while (z < y) +{ +val h = getRowHeight(i.toInt()) +if (z + h < y) +z += h +else +{ +row = i +rowOffset = Math.round((256 * ((y - z) / getRowHeight(i.toInt())))).toShort() +z = y.toDouble() +} +i++ +} +bounds[MSODrawing.ROW] = row +bounds[MSODrawing.ROWOFFSET] = rowOffset +updateClientAnchorRecord(bounds) +} + +/** + * calculate height based upon row #s, row offsets and row heights + * units are in points + * + * @return short row height + */ + private fun calcHeight():Short { +val row = bounds!![MSODrawing.ROW].toInt() +val row1 = bounds!![MSODrawing.ROW1].toInt() +val rowOff = bounds!![MSODrawing.ROWOFFSET] / 256.0 +val rowOff1 = bounds!![MSODrawing.ROWOFFSET1] / 256.0 +var y = getRowHeight(row) - getRowHeight(row) * rowOff +for (i in row + 1 until row1) +{ +y += getRowHeight(i) +} +if (row1 > row) +y += getRowHeight(row1) * rowOff1 +else +y = getRowHeight(row1) * (rowOff1 - rowOff) +return Math.round(y).toShort() +} + +/** + * set the width of this object + * units are in excel units + * + * @param w + */ + fun setWidth(w:Int) { +val col = bounds!![MSODrawing.COL].toInt() +val colOff = bounds!![MSODrawing.COLOFFSET] / 1024.0 +var col1 = col +var colOff1 = 0 +var z = getColWidth(col) - (getColWidth(col) * colOff).toInt() +if (z >= w) +{ // 20100322 KSC: was > w, so the == case never was handled, see TestImages.insertImageOffsetDisappearance bug +col1 = col +colOff1 = Math.round((1024 * (w / getColWidth(col).toDouble()))).toShort() + bounds!![MSODrawing.COLOFFSET] +} +var i = col + 1 +while (i < XLSConstants.MAXCOLS && z < w) +{ +val cw = getColWidth(i) +if (z + cw < w) +z += cw +else +{ +col1 = i +colOff1 = (1024 * (((w - z).toDouble()) / cw.toDouble())).toShort().toInt() +z = w +} +i++ +} +bounds[MSODrawing.COL1] = col1.toShort() +bounds[MSODrawing.COLOFFSET1] = colOff1.toShort() +updateClientAnchorRecord(bounds) +originalWidth = originalWidth // 20071024 KSC: if change width, record +} + +/** + * set the height of this object + * units are in points + * + * @param h + */ + fun setHeight(h:Int) { +val row = bounds!![MSODrawing.ROW].toInt() +val rowOff = bounds!![MSODrawing.ROWOFFSET] / 256.0 +var row1 = row +var rowOff1 = 0 +var rh = getRowHeight(row) +var y = rh - (rh * rowOff) // distance from start position to end of row +if (y > h) +{ +rowOff1 = (256 * (h / rh)).toShort() + bounds!![MSODrawing.ROWOFFSET] +} +var i = row + 1 +while (y < h) +{ +rh = getRowHeight(i) +if (y + rh < h) +y += rh +else +{ // height is met; see what offset into row i is necessary +row1 = i +rowOff1 = Math.round((256 * ((h - y) / rh))).toShort().toInt() +y = h.toDouble() // exit loop +} +i++ +} +bounds[MSODrawing.ROW1] = row1.toShort() +bounds[MSODrawing.ROWOFFSET1] = rowOff1.toShort() +updateClientAnchorRecord(bounds) +height = calcHeight() // 20071024 KSC: if change height, record +} + +/** + * return the column width in excel units + * + * @param col + * @return + */ + private fun getColWidth(col:Int):Int { + // MSO column width is in Excel units= 0-255 characters + // Excel Units are 1/256 of default font width (10 pt Arial) + var w = Colinfo.DEFAULT_COLWIDTH.toDouble() +try +{ +val co = this.sheet!!.getColInfo(col) +if (co != null) +w = co!!.getColWidth().toDouble() +} +catch (e:Exception) { // exception if no col defined +} + +return Math.round(w / 256).toInt() // 20090505 KSC: try this +} + +/** + * return the row height in points + * + * @param row + * @return + */ + private fun getRowHeight(row:Int):Double { + // MSO row height is measured in points, 0-409 + // in Arial 10 pt, standard row height is 12.75 points + // .75 points/pixel + // row height is in twips= 1/20 of a point,1 pt= 1/72 inch + var h = 255 +try +{ +val r = this.sheet!!.getRowByNumber(row) +if (r != null) +h = r!!.rowHeight +else + // no row defined - use default row height 20100504 KSC + return this.sheet!!.defaultRowHeight // default +} +catch (e:Exception) { // exception if no row defined // no row defined - use default row height 20100504 KSC +return this.sheet!!.defaultRowHeight // default +} + +return (h / 20.0) // 20090506 KSC: it's in twips 1/20 of a point +} + +/** + * @return Returns the numShapes. + */ + fun getNumShapes():Int { +return numShapes +} + +/** + * set the number of shapes for this drawing rec + * + * @param n + */ + fun setNumShapes(n:Int) { +numShapes = n +updateDGRecord() +} + +/** + * return the lastSPID (only valid for the header msodrawing record + * necessary to track so that newly added images have the appropriate SPID + * + * @return + */ + fun getlastSPID():Int { +return this.lastSPID +} + +/** + * update the bytes of the DG record + */ + private fun updateDGRecord() { +val fbt:Int +val len:Int + +super.getData() +val bis = ByteArrayInputStream(data!!) +while (bis.available() > 0) +{ +var buf = ByteArray(8) +bis.read(buf, 0, 8) +fbt = ((0xFF and buf[3]) shl 8) or (0xFF and buf[2]) +len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]) +if (fbt < 0xF005) +{ // ignore containers +continue +} +buf = ByteArray(len) +bis.read(buf, 0, len) // position at next sub-rec +if (fbt == MSODrawingConstants.MSOFBTDG) +{ // Drawing Record: count + MSOSPID seed +data[8] = (drawingId * 16).toByte() //= the 1st byte of the header portion of the DG record 20080902 KSC +val newrec = ByteArray(8) +System.arraycopy(ByteTools.cLongToLEBytes(numShapes), 0, newrec, 0, 4) +System.arraycopy(ByteTools.cLongToLEBytes(this.lastSPID), 0, newrec, 4, 4) +if (len == newrec.size) +{// should!!! + // update Msodrawing data ... + System.arraycopy(newrec, 0, data!!, data!!.size - bis.available() - newrec.size, newrec.size) +} +else +Logger.logErr("UpdateClientAnchorRecord: New Array Size=" + newrec.size) +return +} +} +} + +/** + * update the bytes of the CLIENTANCHOR record + * + * @param bounds bounds[0]= column # of top left position (0-based) of the shape + * bounds[1]= x offset within the top-left column + * bounds[2]= row # for top left corner + * bounds[3]= y offset within the top-left corner + * bounds[4]= column # of the bottom right corner of the shape + * bounds[5]= x offset within the cell for the bottom-right corner + * bounds[6]= row # for bottom-right corner of the shape + * bounds[7]= y offset within the cell for the bottom-right corner + */ + private fun updateClientAnchorRecord(bounds:ShortArray?) { +val fbt:Int +val len:Int + +super.getData() +val bis = ByteArrayInputStream(data!!) +while (bis.available() > 0) +{ +var buf = ByteArray(8) +bis.read(buf, 0, 8) +fbt = ((0xFF and buf[3]) shl 8) or (0xFF and buf[2]) +len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]) +if (fbt < 0xF005) +{ // ignore containers +continue +} +buf = ByteArray(len) +bis.read(buf, 0, len) // position at next sub-rec +if (fbt == MSODrawingConstants.MSOFBTCLIENTANCHOR) +{ // Anchor or location fo a shape + // udpate bounds + val bos = ByteArrayOutputStream() +try +{ +bos.write(ByteTools.shortToLEBytes(clientAnchorFlag)) +bos.write(ByteTools.shortToLEBytes(bounds!![0])) +bos.write(ByteTools.shortToLEBytes(bounds!![1])) +bos.write(ByteTools.shortToLEBytes(bounds!![2])) +bos.write(ByteTools.shortToLEBytes(bounds!![3])) +bos.write(ByteTools.shortToLEBytes(bounds!![4])) +bos.write(ByteTools.shortToLEBytes(bounds!![5])) +bos.write(ByteTools.shortToLEBytes(bounds!![6])) +bos.write(ByteTools.shortToLEBytes(bounds!![7])) +} +catch (e:Exception) {} + +val newrec = bos.toByteArray() +if (buf.size == newrec.size) +{// should!!! + // update Msodrawing data ... + System.arraycopy(newrec, 0, data!!, data!!.size - bis.available() - newrec.size, newrec.size) +} +else +Logger.logErr("UpdateClientAnchorRecord: New Array Size=" + newrec.size) +return +} +} +} + +/** + * sets a specific OPT subrecord + * + * @param propertyId int property id see Msoconstants + * @param isBid true if this is a BLIP id + * @param isComplex true if has complexBytes + * @param dtx if not isComplex, the value; if isComplex, length + * @param complexBytes complex bytes if isComplex + */ + fun setOPTSubRecord(propertyId:Int, isBid:Boolean, isComplex:Boolean, dtx:Int, complexBytes:ByteArray) { +val optrec = this.optRec + // TODO: store optrec length instead of calculating each time + val origlen = optrec!!.toByteArray().size +optrec!!.setProperty(MSODrawingConstants.msooptGroupShapeProperties, isBid, isComplex, dtx, complexBytes) +updateRecord() +if (origlen != optrec!!.toByteArray().size) +{ // must update header +this.workBook!!.updateMsodrawingHeaderRec(this.sheet) +} +} + +/** + * set the LastSPID for this drawing record, if it's a header-type record + * + * @param spid + */ + fun setLastSPID(spid:Int) { +lastSPID = spid +updateDGRecord() +} + + + fun setShapeType(shapeType:Int) { +this.shapeType = shapeType.toShort() +} + + fun getShapeType():Int { +return shapeType.toInt() +} + +/** + * change the SPID for this record + * + * @param spid + */ + private fun updateSPID() { +val fbt:Int +val len:Int +val inst:Int +super.getData() +val bis = ByteArrayInputStream(data!!) +while (bis.available() > 0) +{ +var buf = ByteArray(8) +bis.read(buf, 0, 8) +inst = ((0xFF and buf[1]) shl 4) or ((0xF0 and buf[0]) shr 4) +fbt = ((0xFF and buf[3]) shl 8) or (0xFF and buf[2]) +len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]) +if (fbt >= 0xF005) +{ // ignore containers +buf = ByteArray(len) +bis.read(buf, 0, len) + +if (fbt == MSODrawingConstants.MSOFBTSP) +{ + //byte[] dat = new byte[len]; + //bis.read(dat,0,len); + val flag = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]) + // if (flag!=5) { // if it's not the SPIDseed + System.arraycopy(ByteTools.cLongToLEBytes(SPID), 0, data!!, data!!.size - bis.available() - len, 4) + //SPID = ByteTools.readInt(dat[0],dat[1],dat[2],dat[3]); + // return; + // } + } +} +} +} + + +/** + * update the header records with new container lengths + * + * @param otherlength + */ + public override fun toString():String { +val sb = StringBuffer() +sb.append("Msodrawing: image=" + this.name + ".\t" + this.shapeName + "\timageIndex=" + imageIndex + " sheet=" + (if ((this.sheet != null)) this.sheet!!.sheetName else "none")) +sb.append(" ID= " + drawingId + " SPID=" + SPID) +sb.append("\tShapeType= " + shapeType) +if (isHeader) +sb.append("\tNumber of Shapes=" + numShapes + " Last SPID=" + lastSPID + " SPCL=" + spContainerLength + " otherLen=" + otherSPCONTAINERLENGTH) +else +sb.append(" SPCL=" + spContainerLength) +if (!isShape) +sb.append(" NOT A SHAPE") +return sb.toString() +} + +/** + * 20081106 KSC: when set sheet, record original height and width + * as dependent upon row heights ... + */ + public override fun setSheet(bs:Sheet?) { +super.setSheet(bs) + // 20081106 Moved from parse as sheet must be set before calcHeight call + // Record original height and width in case of row height/col width changes + height = calcHeight() +originalWidth = originalWidth +} + +/** + * create records sub-records necessary to define an AutoFilter drop-down symbol + */ + fun createDropDownListStyle(col:Int) { + // mso record which - we hope - has the specific options necessary to define the dropdown box + val optrec = this.optRec +optrec!!.inst = 0 // clear out +optrec!!.setData(byteArrayOf()) +optrec!!.setProperty(MSODrawingConstants.msooptfLockAgainstGrouping, false, false, 17039620, null) +optrec!!.setProperty(MSODrawingConstants.msooptfFitTextToShape, false, false, 524296, null) +optrec!!.setProperty(MSODrawingConstants.msooptfNoLineDrawDash, false, false, 524288, null) +optrec!!.setProperty(MSODrawingConstants.msooptGroupShapeProperties, false, false, 131072, null) +this.setShapeType(MSODrawingConstants.msosptHostControl) // shape type for these drop-downs +this.updateRecord(++this.wkbook!!.lastSPID, null, null, -1, shortArrayOf(col.toShort(), 0, 0, 0, (col + 1).toShort(), 0, 1, 0)) // generate msoDrawing using correct values moved from above + // KSC: keep for testing + //col++; + //this.updateRecord(++this.wkbook.lastSPID, null, null, -1, new short[] {(short)col, 0, 0, 0, (short)(col+1), 272, 1, 0}); // generate msoDrawing using correct values moved from above + } + +/** + * create the sub-records necessary to define a Comment (Note) Box + */ + fun createCommentBox(row:Int, col:Int) { +val optrec = this.optRec +optrec!!.inst = 0 // clear out +optrec!!.setData(byteArrayOf()) + //47752196 + // try a random text id ... instead of 48678864 + val id = java.util.Random().nextInt() +optrec!!.setProperty(MSODrawingConstants.msofbtlTxid, false, false, id, null) +optrec!!.setProperty(MSODrawingConstants.msofbttxdir, false, false, 2, null) +optrec!!.setProperty(MSODrawingConstants.msooptfFitTextToShape, false, false, 524296, null) +optrec!!.setProperty(344, false, false, 0, null) // no info on this subrecord +optrec!!.setProperty(MSODrawingConstants.msooptfillColor, false, false, 134217808, null) +optrec!!.setProperty(MSODrawingConstants.msooptfillBackColor, false, false, 134217808, null) +optrec!!.setProperty(MSODrawingConstants.msooptfNoFillHitTest, false, false, 1048592, null) +optrec!!.setProperty(MSODrawingConstants.msooptfshadowColor, false, false, 0, null) +optrec!!.setProperty(MSODrawingConstants.msooptfShadowObscured, false, false, 196611, null) + // this, strangely, controls note hidden or show- when it's 131074, it's hidden, when it's 131072, it's shown + optrec!!.setProperty(MSODrawingConstants.msooptGroupShapeProperties, false, false, 131074, null) +this.setShapeType(MSODrawingConstants.msosptTextBox) // shape type for text boxes + // position of text box - garnered from Excel examples + // [1, 240, 0, 30, 3, 496, 4, 196] A1 + // [4, 240, 2, 105, 6, 496, 7, 15] D4 + this.updateRecord(++this.wkbook!!.lastSPID, null, null, -1, shortArrayOf((col + 1).toShort(), 240, row.toShort(), 30, (col + 3).toShort(), 496, (row + 4).toShort(), 196)) // generate msoDrawing using correct values moved from above +} + /* notes from another attempt: does this match ours? +_store_mso_opt_comment { + my $self = shift; + my $type = 0xF00B; + my $version = 3; + my $instance = 9; + my $data = ''; + my $length = 54; + my $spid = $_[0]; + my $visible = $_[1]; + my $colour = $_[2] || 0x50; + $data = pack "V", $spid; + $data .= pack "H*", '0000BF00080008005801000000008101' ; + $data .= pack "C", $colour; + $data .= pack "H*", '000008830150000008BF011000110001' . + '02000000003F0203000300BF03'; + $data .= pack "v", $visible; + $data .= pack "H*", '0A00'; + * + */ + + + /** + * test debug output - FOR INTERNAL USE ONLY + * + * @return + */ + fun debugOutput():String { +val bis = ByteArrayInputStream(super.getData()!!) +val version:Int +val inst:Int +val fbt:Int +val len:Int +val log = StringBuffer() +try +{ +while (bis.available() > 0) +{ +var dat = ByteArray(8) +bis.read(dat, 0, 8) +version = (0xF and dat[0]) +inst = ((0xFF and dat[1]) shl 4) or ((0xF0 and dat[0]) shr 4) +fbt = ((0xFF and dat[3]) shl 8) or (0xFF and dat[2]) +len = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]) + +log.append(fbt + " " + version + "/" + inst + "/" + len) + + // if (fbt <= 0xF005) { // ignore containers + if (version == 15) +{ // it's a container - no parsing + // MSOFBTSPGRCONTAINER: // Shape Group Container, contains a variable number of shapes (=msofbtSpContainer) + other groups 0xF003 + // MSOFBTSPCONTAINER: // Shape Container 0xF004 + if (fbt == MSODrawingConstants.MSOFBTDGCONTAINER) +{ +log.append("\tMSOFBTDGCONTAINER") +} +else if (fbt == MSODrawingConstants.MSOFBTSPGRCONTAINER) +{ +log.append("\tMSOFBTSPGRCONTAINER") +} +else if (fbt == MSODrawingConstants.MSOFBTSPCONTAINER) +{ +log.append("\tMSOFBTSPCONTAINER") +} +else if (fbt == MSODrawingConstants.MSOFBTSOLVERCONTAINER) +{ +log.append("\tMSOFBTSOLVERCONTAINER") +} +else +{ +log.append("\tUNKNOWN CONTAINER") +} +log.append("\r\n") +continue +} + +dat = ByteArray(len) +bis.read(dat, 0, len) +when (fbt) { +MSODrawingConstants.MSOFBTCALLOUTRULE -> log.append("\tMSOFBTCALLOUTRULE") + +MSODrawingConstants.MSOFBTDELETEDPSPL -> log.append("\tMSOFBTDELETEDPSPL") + +MSODrawingConstants.MSOFBTREGROUPITEMS -> log.append("\tMSOFBTREGROUPITEMS") + +MSODrawingConstants.MSOFBTSP // A shape atom rec (inst= shape type) rec= shape ID + group of flags + -> { +log.append("\tMSOFBTSP") +val flag:Int +flag = ByteTools.readInt(dat[4], dat[5], dat[6], dat[7]) +if ((flag and 0x800) == 0x800) + // it has a shape-type property + log.append("\tshapeType=" + inst) +if (inst == 0) + //== shape type + log.append("\tSPIDSEED=" + ByteTools.readInt(dat[0], dat[1], dat[2], dat[3])) +else +log.append("\tSPID=" + +ByteTools.readInt(dat[0], dat[1], dat[2], dat[3])) +log.append("\tflag=" + flag) +} + +MSODrawingConstants.MSOFBTCLIENTANCHOR // Anchor or location fo a shape + -> { +log.append("\tMSOFBTCLIENTANCHOR") +log.append("\t[") +log.append(ByteTools.readShort(dat[2].toInt(), dat[3].toInt()) + ",") +log.append(ByteTools.readShort(dat[4].toInt(), dat[5].toInt()) + ",") +log.append(ByteTools.readShort(dat[6].toInt(), dat[7].toInt()) + ",") +log.append(ByteTools.readShort(dat[8].toInt(), dat[9].toInt()) + ",") +log.append(ByteTools.readShort(dat[10].toInt(), dat[11].toInt()) + ",") +log.append(ByteTools.readShort(dat[12].toInt(), dat[13].toInt()) + ",") +log.append(ByteTools.readShort(dat[14].toInt(), dat[15].toInt()) + ",") +log.append(ByteTools.readShort(dat[16].toInt(), dat[17].toInt()).toInt()) +log.append("]") +} + +MSODrawingConstants.MSOFBTOPT // property table atom + -> { +log.append("\tMSOFBTOPT") + //MsofbtOPT optrec= new MsofbtOPT(fbt, inst, version); + //optrec.setData(dat); // sets and parses msoFbtOpt data, including imagename, shapename and imageindex + log.append(optRec!!.debugOutput()) +} + +MSODrawingConstants.MSOFBTSECONDARYOPT // property table atom block 2 + -> { +log.append("\tMSOFBTSECONDARYOPT") +val secondaryoptrec = MsofbtOPT(fbt, inst, version) +secondaryoptrec.setData(dat) // sets and parses msoFbtOpt data, including imagename, shapename and imageindex +log.append(secondaryoptrec.debugOutput()) +} + +MSODrawingConstants.MSOFBTTERTIARYOPT // property table atom block 3 + -> { +log.append("\tMSOFBTTERTIARYOPT") +val tertiaryoptrec = MsofbtOPT(fbt, inst, version) +tertiaryoptrec.setData(dat) // sets and parses msoFbtOpt data, including imagename, shapename and imageindex +log.append(tertiaryoptrec.debugOutput()) +} + +MSODrawingConstants.MSOFBTDG // Drawing Record: ID, num shapes + Last SPID for this DG + -> { +log.append("\tMSOFBTDG") +log.append("\tID=" + inst) +log.append("\tns=" + ByteTools.readInt(dat[0], dat[1], dat[2], dat[3])) // number of shapes in this drawing +log.append("\tllastSPID=" + ByteTools.readInt(dat[4], dat[5], dat[6], dat[7])) +} + +MSODrawingConstants.MSOFBTCLIENTTEXTBOX -> log.append("\tMSOFBTCLIENTTEXTBOX") + +MSODrawingConstants.MSOFBTSPGR -> log.append("\tMSOFBTSPGR") + +MSODrawingConstants.MSOFBTCLIENTDATA -> log.append("\tMSOFBTCLIENTDATA") + +MSODrawingConstants.MSOFBTSOLVERCONTAINER -> log.append("\tMSOFBTSOLVERCONTAINER") + +MSODrawingConstants.MSOFBTCHILDANCHOR -> log.append("\tMSOFBTCHILDANCHOR") + +MSODrawingConstants.MSOFBTCONNECTORRULE -> log.append("\tMSOFBTCONNECTORRULE") + +else //MSOFBTCONNECTORRULE + -> log.append("\tUNKNOWN ATOM") +} +log.append("\r\n") +} +} +catch (e:Exception) { +log.append("\r\nEXCEPTION: " + e.toString()) +} + +log.append("**") +return log.toString() +} + +public override fun close() { +super.close() +this.bounds = null +optRec = null // 20091209 KSC: save MsofbtOPT records for later updating ... +secondaryoptrec = null +tertiaryoptrec = null // apparently can have secondary and tertiary obt recs depending upon version in which it was saved ... +} + +companion object { +private val serialVersionUID = 8275831369787287975L +internal val HEADERRECLENGTH = 24 // + + // correct way of setting/getting shape (image and chart) bounds + /* position methods */ + val COL = 0 + val COLOFFSET = 1 + val ROW = 2 + val ROWOFFSET = 3 + val COL1 = 4 + val COLOFFSET1 = 5 + val ROW1 = 6 + val ROWOFFSET1 = 7 + val OFFSETMAX = 1023 + + // conversions: approx .136 excel units/pixel, 7.5 pixels/excel unit for X/W/Columns (really 6.4????) + // approx .75 pixels/points (Y,H/Rows) (really .60???) + /** + * Untested Info from Excel: + * width/height in pixels = (w/h field - 8) * DPI of the display device / 72 + * DPI for Windows is 96 (Mac= 72) + * thus conversion factor= 1.3333 for Windows devices + * NOTES: seems correct for y/h, not for x/w + * return coordinates as {X, Y, W, H} in pixels + * + * @return + */ + // these are approximate conversion factors to convert excel units to pixels ... + val XCONVERSION = 10.0 // convert excel units to pixels, garnered from actual comparisions rather than any equation (since it's based upon the normal font, it cannot be 100% for all calculations ... + val WCONVERSION = 8.8 // "" + val PIXELCONVERSION = 1.333 // see above + + + // prototype bytes contain an image index and an image name - remove here + // TODO: make prototype bytes correct + // mso.imageIndex= -1; + // mso.imageName= ""; + // mso.optrec.setImageName(""); + // mso.optrec.setImageIndex(-1); + // mso.updateRecord(); + val prototype:XLSRecord? +get() { +val mso = MSODrawing() +mso.opcode = XLSConstants.MSODRAWING +mso.setData(mso.PROTOTYPE_BYTES) +mso.init() +return mso +} + +/** + * creates a msofbtClientTextbox, a very strange mso record containing only one sub-record or atom; + * associated data=the text in the textbox, in a host-defined format i.e. text from a NOTE + *

                  + * NOTE: this must be flagged as incomplete so that it is not counted in number of shapes and other calcs ... + *

                  + * NOTE: knowledge of this record is very sketchy ... + * + * @return + */ + // only contains 1 sub-record: 0xF00D or 61453= msofbtTextBox==shape has attached text + val textBoxPrototype:XLSRecord +get() { +val mso = MSODrawing() +mso.opcode = XLSConstants.MSODRAWING +mso.setData(byteArrayOf(0, 0, 13, -16, 0, 0, 0, 0)) +mso.init() +return mso +} +} + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/MSODrawingConstants.java b/src/main/java/io/starter/formats/XLS/MSODrawingConstants.java deleted file mode 100644 index d803c80..0000000 --- a/src/main/java/io/starter/formats/XLS/MSODrawingConstants.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -/** - * Msodrawing record-related constants - * NOT A COMPLETE LIST! - */ -public class MSODrawingConstants { - // 20070910 KSC: constants for MSO record + atom ids (records keep atoms and other containers; atoms contain info and are kept inside containers) - // common record header for both: ver, inst, fbt, len; fbt deterimes record type (0xF000 to 0xFFFF) - // MsoDrawingGroup - public static final int MSOFBTDGGCONTAINER = 0xF000; // Drawing Group Container (Msodrawinggroup) - public static final int MSOFBTDGG = 0xF006; // Drawing Group Record (of Msodrawinggoup) - public static final int MSOFBTCLSID = 0xF016; // Clipboard format - public static final int MSOFBTOPT = 0xF00B; // Property Table Record for newly created shapes, array of FOPTEs - public static final int MSOFBTCOLORMRU = 0xF11A; - public static final int MSOFBTSPLITMENUCOLORS = 0xF11E; - public static final int MSOFBTBSTORECONTAINER = 0xF001; // Stores BLIPS (=pix) in a separate container - public static final int MSOFBTBSE = 0xF007; // BLIP Store Entry Record - public static final int MSOFBTCALLOUTRULE = 0xF017; // One callout rule per callout shape - public static final int MSOFBTBLIP = 0xF018; - // MsoDrawing - public static final int MSOFBTDGCONTAINER = 0xF002; // Drawing Container - (Msodrawing records contained in MsoDrawinggroup) - public static final int MSOFBTDG = 0xF008; // Basic drawing info - public static final int MSOFBTREGROUPITEMS = 0xF118; // Mappings to reconstitute groups - public static final int MSOFBTCOLORSCHEME = 0xF120; - public static final int MSOFBTSECONDARYOPT = 0xF121; // secondary opt block - "the property table msofbtOpt may be split into as many as 3 blocks" - public static final int MSOFBTTERTIARYOPT = 0xF122; // default properties of new shapes - only those props which differ from the per-property defaults are saved - public static final int MSOFBTSPGRCONTAINER = 0xF003; // Patriarch shape, with all non-bg non-deleted shapes inside it - public static final int MSOFBTSPCONTAINER = 0xF004; // Shape Container - public static final int MSOFBTSPGR = 0xF009; // Group-shape-specific info (i.e. shapes that are groups) - public static final int MSOFBTSP = 0xF00A; // A shape atom rec (inst= shape type) rec= shape ID + group of flags - public static final int MSOFBTTEXTBOX = 0xF00C; // if the shape has text - public static final int MSOFBTCLIENTTEXTBOX = 0xF00D; // for clipboard stream - public static final int MSOFBTANCHOR = 0xF00E; // Anchor or location fo a shape (if streamed to a clipboard) - public static final int MSOFBTCHILDANCHOR = 0xF00F; // " ", if shape is a child of a group shape - public static final int MSOFBTCLIENTANCHOR = 0xF010; // " ", for top-level shapes - public static final int MSOFBTCLIENTDATA = 0xF011; // content is determined by host - public static final int MSOFBTCONNECTORRULE = 0xF012; // connector rule - public static final int MSOFBTALIGNRULE = 0xF013; - public static final int MSOFBTARCRULE = 0xF014; - public static final int MSOFBTCLIENTRULE = 0xF015; - public static final int MSOFBTOLEOBJECT = 0xF11F; // - public static final int MSOFBTDELETEDPSPL = 0xF11D; - public static final int MSOFBTSOLVERCONTAINER = 0xF005; // the rules governing shapes; count of rules - public static final int MSOFBTSELECTION = 0xF119; - // MSOBI == encoded BLIP types - public static final int msobiUNKNOWN = 0; - public static final int msobiWMF = 0x216; - public static final int msobiEMF = 0x3D4; - public static final int msobiPICT = 0x542; - public static final int msobiPNG = 0x6E0; - public static final int msobiJFIF = 0x46A; - public static final int msobiJPEG = msobiJFIF; - public static final int msobiDIB = 0x7A8; - public static final int msobiCLIENT = 0x800; - // - public static final int msofbtBlipFirst = 0xF018; // used to calculate fbt of BLIP in MsofbtBSE - - // pertinent Property Table/MSOFBTOPT property id's - public static final int msooptfLockAgainstGrouping = 127; // BOOL - Do not group this shape - public static final int msofbtlTxid = 128; // LONG - id for the text, value determined by the host - //margins relative to shape's inscribed text rectangle (in EMUs) - public static final int dxTextLeft = 129; // LONG 1/10 inch - public static final int dyTextTop = 130; // LONG 1/20 inch - public static final int dxTextRight = 131; // LONG 1/10 inch - public static final int dyTextBottom = 132; // LONG 1/20 inch - // How to anchor the text - public static final int anchorText = 135; // MSOANCHOR def= Top - public static final int msofbttxdir = 139; // MSOTXDIR - Bi-Di Text direction - public static final int msooptfFitTextToShape = 191; // BOOL - Size text to fit shape size - public static final int msooptpib = 260; // IMsoBlip - id of Blip to display == imageIndex - public static final int msooptpibName = 261; // WCHAR - Blip File Name == imageName - public static final int msooptpibFlags = 262; // MSOBLIPFLAGS - Blip Flags - public static final int msooptpictureActive = 319; // Server is active (OLE objects only)default= false - // fill attrbutes - public static final int msooptFillType = 384; - public static final int msooptfillColor = 385; // MSOCLR - foreground color - public static final int msooptFillOpacity = 386; - public static final int msooptfillBackColor = 387; // MSOCLR - background color - public static final int msooptFillBackOpacity = 388; - public static final int msooptFillCrMod = 389; // foreground color of the fill for black-and-white display mode. - public static final int msooptFillBlip = 390; - public static final int msooptFillBlipName = 391; // fillBlipName_complex -- specifies additional data for the fillBlipName property - public static final int msooptFillBlipFlags = 392; // specifies how to interpret the fillBlipName_complex property - public static final int msooptFillWidth = 393; // the width of the fill. This property applies only to texture, picture, and pattern fills. - // A signed integer that specifies the width of the fill in units that are specified by the fillDztype property, as defined in section 2.3.7.24. If fillDztype equals msodztypeDefault, this value MUST be ignored. The default value for this property is 0x00000000. - public static final int msooptFillHeight = 394; // A signed integer that specifies the height of the fill in units that are specified by the fillDztype property, as defined in section 2.3.7.24. If fillDztype equals msodztypeDefault, this value MUST be ignored. The default value for this property is 0x00000000. - public static final int msooptFillAngle = 395; // A value of type FixedPoint, as specified in [MS-OSHARED] section 2.2.1.6, that specifies the angle of the gradient fill. Zero degrees represents a vertical vector from bottom to top. The default value for this property is 0x00000000. - public static final int msooptFillFocus = 396; // specifies the relative position of the last color in the shaded fill. - public static final int msooptFillToLeft = 397; - public static final int msooptFillToTop = 398; - public static final int msooptFillToRight = 399; - public static final int msooptFillToBottom = 400; // A signed integer that specifies the left boundary, in EMUs, of the bounding rectangle of the shaded fill. If the Fill Style BooleanfillUseRect property, as defined in section 2.3.7.43, equals 0x0, this value MUST be ignored. The default value for this property is 0x00000000. - public static final int msooptFillRectLeft = 401; // A signed integer that specifies the top boundary, in EMUs, of the bounding rectangle of the shaded fill. If the Fill Style BooleanfillUseRect property, as defined in section 2.3.7.43, equals 0x0, this value MUST be ignored. The default value for this property is 0x00000000. - public static final int msooptFillRectTop = 402; - public static final int msooptFillRectRight = 403; - public static final int msooptFillRectBottom = 404; - public static final int msooptFillDztype = 405; // An MSODZTYPE enumeration value, as defined in section 2.4.12, that specifies how the fillWidth, as defined in section 2.3.7.12, and fillHeight, as defined in section 2.3.7.13, properties are interpreted. The default value for this property is msodztypeDefault. - public static final int msooptFillShadePreset = 406; // A signed integer that specifies the preset colors of the gradient fill. This value MUST be from 0x00000088 through 0x0000009F, inclusive. if the fillShadeColors_complex property, as defined in section 2.3.7.27, exists, this value MUST be ignored. The default value for this property is 0x00000000. - public static final int msooptFillShadeColors = 407; // The number of bytes of data in the fillShadeColors_complex property. If opid.fComplex equals 0x0, this value MUST be 0x00000000. The default value for this property is 0x00000000. - public static final int msooptFillOriginX = 408; - public static final int msooptFillOriginY = 409; - public static final int msooptFillShapeOriginX = 410; - public static final int msooptFillShapeOriginY = 411; - public static final int msooptFillShadeType = 412; // : An MSOSHADETYPE record, as defined in section 2.2.50, that specifies how the shaded fill is computed. The default value for this property is msoshadeDefault. - public static final int msooptFFilled = 443; - // line attributes - public static final int msooptfNoFillHitTest = 447; // BOOL - Hit test a shape as though filled - public static final int msooptlineColor = 448; // MSOCLR - Color of line - public static final int msooptlineWidth = 459; // LONG - 1pt= 12700 EMUs - public static final int msooptLineMiterLimit = 460; - public static final int msooptLineStyle = 461; - public static final int msooptLineDashing = 462; - public static final int msooptLineDashStyle = 463; - public static final int msooptLineStartArrowhead = 464; - public static final int msooptLineEndArrowhead = 465; - public static final int msooptLineStartArrowWidth = 466; - public static final int msooptLineStartArrowLength = 467; - public static final int msooptLineEndArrowWidth = 468; - public static final int msooptLineEndArrowLength = 469; - public static final int msooptLineJoinStyle = 470; - public static final int msooptLineEndCapStyle = 471; - public static final int msooptFArrowheadsOK = 507; - public static final int msooptLine = 508; - public static final int msooptFHitTestLine = 509; - public static final int msooptLineFillShape = 510; - public static final int msooptfNoLineDrawDash = 511; // BOOL - Draw a dashed line if no line - // Shadow attributes - many to add - public static final int msooptfshadowColor = 513; // MSOCLR - foreground color - default = 0x808080 - public static final int msooptfShadowObscured = 575; // BOOL - Excel5-style Shadow - public static final int msooptfBackground = 831; // BOOL - If true, this is the background shape - public static final int msooptwzName = 896; // WCHAR - Shape Name (present only if explicitly set in Named Range box) - public static final int msooptwzDescription = 897; // WCHAR - Alternate text - // group shape attributes (selected) - public static final int msooptidRegroup = 904; // LONG - Regroup id - public static final int msooptMetroBlob = 937; /*The shape‘s 2007 representation in Office Open XML format. - The actual data is a package in Office XML format, which can simply be opened as a zip file. - This zip file contains an XML file with the root element ―sp‖. - Refer to the publically available Office Open XML documentation for more information about this data. - In case we lose any property when converting a 2007 Office Art shape to 2003 shape, - we use this blob to retrieve the original Office Art property data when opening the file in 2007. - See Appendix F for more information*/ - // misc - public static final int msooptGroupShapeProperties = 959; // The Group Shape Boolean Properties specify a 32-bit field of Boolean properties for either a shape or a group. - // ... - // right now, we support GIF, JPG and PNG - public static final int IMAGE_TYPE_GIF = 0; - public static final int IMAGE_TYPE_EMF = 2; - public static final int IMAGE_TYPE_WMF = 3; - public static final int IMAGE_TYPE_PICT = 4; - public static final int IMAGE_TYPE_JPG = 5; - public static final int IMAGE_TYPE_PNG = 6; - public static final int IMAGE_TYPE_DIB = 7; - - // Shape Types - /** - * Internally, a shape type is defined as a fixed set of property values, - * the most important being the geometry of the shape (the pVertices property, etc.). - * Each shape stores in itself only those properties that differ from its shape type. - * When a shape is asked for a property that isn't in its local table, - * it looks in the shape type's table. - * If the shape type doesn't define a value for the property, - * then the property's default value is used. - */ - public static final int msosptMin = 0; - public static final int msosptNotPrimitive = msosptMin, - msosptRectangle = 1, - msosptRoundRectangle = 2, - msosptEllipse = 3, - msosptDiamond = 4, - msosptIsocelesTriangle = 5, - msosptRightTriangle = 6, - msosptParallelogram = 7, - msosptTrapezoid = 8, - msosptHexagon = 9, - msosptOctagon = 10, - msosptPlus = 11, - msosptStar = 12, - msosptArrow = 13, - msosptThickArrow = 14, - msosptHomePlate = 15, - msosptCube = 16, - msosptBalloon = 17, - msosptSeal = 18, - msosptArc = 19, - msosptLine = 20, - msosptPlaque = 21, - msosptCan = 22, - msosptDonut = 23, - msosptTextSimple = 24, - msosptTextOctagon = 25, - msosptTextHexagon = 26, - msosptTextCurve = 27, - msosptTextWave = 28, - msosptTextRing = 29, - msosptTextOnCurve = 30, - msosptTextOnRing = 31, - msosptStraightConnector1 = 32, - msosptBentConnector2 = 33, - msosptBentConnector3 = 34, - msosptBentConnector4 = 35, - msosptBentConnector5 = 36, - msosptCurvedConnector2 = 37, - msosptCurvedConnector3 = 38, - msosptCurvedConnector4 = 39, - msosptCurvedConnector5 = 40, - msosptCallout1 = 41, - msosptCallout2 = 42, - msosptCallout3 = 43, - msosptAccentCallout1 = 44, - msosptAccentCallout2 = 45, - msosptAccentCallout3 = 46, - msosptBorderCallout1 = 47, - msosptBorderCallout2 = 48, - msosptBorderCallout3 = 49, - msosptAccentBorderCallout1 = 50, - msosptAccentBorderCallout2 = 51, - msosptAccentBorderCallout3 = 52, - msosptRibbon = 53, - msosptRibbon2 = 54, - msosptChevron = 55, - msosptPentagon = 56, - msosptNoSmoking = 57, - msosptSeal8 = 58, - msosptSeal16 = 59, - msosptSeal32 = 60, - msosptWedgeRectCallout = 61, - msosptWedgeRRectCallout = 62, - msosptWedgeEllipseCallout = 63, - msosptWave = 64, - msosptFoldedCorner = 65, - msosptLeftArrow = 66, - msosptDownArrow = 67, - msosptUpArrow = 68, - msosptLeftRightArrow = 69, - msosptUpDownArrow = 70, - msosptIrregularSeal1 = 71, - msosptIrregularSeal2 = 72, - msosptLightningBolt = 73, - msosptHeart = 74, - msosptPictureFrame = 75, - msosptQuadArrow = 76, - msosptLeftArrowCallout = 77, - msosptRightArrowCallout = 78, - msosptUpArrowCallout = 79, - msosptDownArrowCallout = 80, - msosptLeftRightArrowCallout = 81, - msosptUpDownArrowCallout = 82, - msosptQuadArrowCallout = 83, - msosptBevel = 84, - msosptLeftBracket = 85, - msosptRightBracket = 86, - msosptLeftBrace = 87, - msosptRightBrace = 88, - msosptLeftUpArrow = 89, - msosptBentUpArrow = 90, - msosptBentArrow = 91, - msosptSeal24 = 92, - msosptStripedRightArrow = 93, - msosptNotchedRightArrow = 94, - msosptBlockArc = 95, - msosptSmileyFace = 96, - msosptVerticalScroll = 97, - msosptHorizontalScroll = 98, - msosptCircularArrow = 99, - msosptNotchedCircularArrow = 100, - msosptUturnArrow = 101, - msosptCurvedRightArrow = 102, - msosptCurvedLeftArrow = 103, - msosptCurvedUpArrow = 104, - msosptCurvedDownArrow = 105, - msosptCloudCallout = 106, - msosptEllipseRibbon = 107, - msosptEllipseRibbon2 = 108, - msosptFlowChartProcess = 109, - msosptFlowChartDecision = 110, - msosptFlowChartInputOutput = 111, - msosptFlowChartPredefinedProcess = 112, - msosptFlowChartInternalStorage = 113, - msosptFlowChartDocument = 114, - msosptFlowChartMultidocument = 115, - msosptFlowChartTerminator = 116, - msosptFlowChartPreparation = 117, - msosptFlowChartManualInput = 118, - msosptFlowChartManualOperation = 119, - msosptFlowChartConnector = 120, - msosptFlowChartPunchedCard = 121, - msosptFlowChartPunchedTape = 122, - msosptFlowChartSummingJunction = 123, - msosptFlowChartOr = 124, - msosptFlowChartCollate = 125, - msosptFlowChartSort = 126, - msosptFlowChartExtract = 127, - msosptFlowChartMerge = 128, - msosptFlowChartOfflineStorage = 129, - msosptFlowChartOnlineStorage = 130, - msosptFlowChartMagneticTape = 131, - msosptFlowChartMagneticDisk = 132, - msosptFlowChartMagneticDrum = 133, - msosptFlowChartDisplay = 134, - msosptFlowChartDelay = 135, - msosptTextPlainText = 136, - msosptTextStop = 137, - msosptTextTriangle = 138, - msosptTextTriangleInverted = 139, - msosptTextChevron = 140, - msosptTextChevronInverted = 141, - msosptTextRingInside = 142, - msosptTextRingOutside = 143, - msosptTextArchUpCurve = 144, - msosptTextArchDownCurve = 145, - msosptTextCircleCurve = 146, - msosptTextButtonCurve = 147, - msosptTextArchUpPour = 148, - msosptTextArchDownPour = 149, - msosptTextCirclePour = 150, - msosptTextButtonPour = 151, - msosptTextCurveUp = 152, - msosptTextCurveDown = 153, - msosptTextCascadeUp = 154, - msosptTextCascadeDown = 155, - msosptTextWave1 = 156, - msosptTextWave2 = 157, - msosptTextWave3 = 158, - msosptTextWave4 = 159, - msosptTextInflate = 160, - msosptTextDeflate = 161, - msosptTextInflateBottom = 162, - msosptTextDeflateBottom = 163, - msosptTextInflateTop = 164, - msosptTextDeflateTop = 165, - msosptTextDeflateInflate = 166, - msosptTextDeflateInflateDeflate = 167, - msosptTextFadeRight = 168, - msosptTextFadeLeft = 169, - msosptTextFadeUp = 170, - msosptTextFadeDown = 171, - msosptTextSlantUp = 172, - msosptTextSlantDown = 173, - msosptTextCanUp = 174, - msosptTextCanDown = 175, - msosptFlowChartAlternateProcess = 176, - msosptFlowChartOffpageConnector = 177, - msosptCallout90 = 178, - msosptAccentCallout90 = 179, - msosptBorderCallout90 = 180, - msosptAccentBorderCallout90 = 181, - msosptLeftRightUpArrow = 182, - msosptSun = 183, - msosptMoon = 184, - msosptBracketPair = 185, - msosptBracePair = 186, - msosptSeal4 = 187, - msosptDoubleWave = 188, - msosptActionButtonBlank = 189, - msosptActionButtonHome = 190, - msosptActionButtonHelp = 191, - msosptActionButtonInformation = 192, - msosptActionButtonForwardNext = 193, - msosptActionButtonBackPrevious = 194, - msosptActionButtonEnd = 195, - msosptActionButtonBeginning = 196, - msosptActionButtonReturn = 197, - msosptActionButtonDocument = 198, - msosptActionButtonSound = 199, - msosptActionButtonMovie = 200, - msosptHostControl = 201, //Host controls extend various user interface (UI) objects in the Word and Excel object models - msosptTextBox = 202, - msosptMax = 0x0FFF, - msosptNil = 0x0FFF; -} diff --git a/src/main/java/io/starter/formats/XLS/MSODrawingConstants.kt b/src/main/java/io/starter/formats/XLS/MSODrawingConstants.kt new file mode 100644 index 0000000..95872e5 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/MSODrawingConstants.kt @@ -0,0 +1,394 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +/** + * Msodrawing record-related constants + * NOT A COMPLETE LIST! + */ +object MSODrawingConstants { + // 20070910 KSC: constants for MSO record + atom ids (records keep atoms and other containers; atoms contain info and are kept inside containers) + // common record header for both: ver, inst, fbt, len; fbt deterimes record type (0xF000 to 0xFFFF) + // MsoDrawingGroup + val MSOFBTDGGCONTAINER = 0xF000 // Drawing Group Container (Msodrawinggroup) + val MSOFBTDGG = 0xF006 // Drawing Group Record (of Msodrawinggoup) + val MSOFBTCLSID = 0xF016 // Clipboard format + val MSOFBTOPT = 0xF00B // Property Table Record for newly created shapes, array of FOPTEs + val MSOFBTCOLORMRU = 0xF11A + val MSOFBTSPLITMENUCOLORS = 0xF11E + val MSOFBTBSTORECONTAINER = 0xF001 // Stores BLIPS (=pix) in a separate container + val MSOFBTBSE = 0xF007 // BLIP Store Entry Record + val MSOFBTCALLOUTRULE = 0xF017 // One callout rule per callout shape + val MSOFBTBLIP = 0xF018 + // MsoDrawing + val MSOFBTDGCONTAINER = 0xF002 // Drawing Container - (Msodrawing records contained in MsoDrawinggroup) + val MSOFBTDG = 0xF008 // Basic drawing info + val MSOFBTREGROUPITEMS = 0xF118 // Mappings to reconstitute groups + val MSOFBTCOLORSCHEME = 0xF120 + val MSOFBTSECONDARYOPT = 0xF121 // secondary opt block - "the property table msofbtOpt may be split into as many as 3 blocks" + val MSOFBTTERTIARYOPT = 0xF122 // default properties of new shapes - only those props which differ from the per-property defaults are saved + val MSOFBTSPGRCONTAINER = 0xF003 // Patriarch shape, with all non-bg non-deleted shapes inside it + val MSOFBTSPCONTAINER = 0xF004 // Shape Container + val MSOFBTSPGR = 0xF009 // Group-shape-specific info (i.e. shapes that are groups) + val MSOFBTSP = 0xF00A // A shape atom rec (inst= shape type) rec= shape ID + group of flags + val MSOFBTTEXTBOX = 0xF00C // if the shape has text + val MSOFBTCLIENTTEXTBOX = 0xF00D // for clipboard stream + val MSOFBTANCHOR = 0xF00E // Anchor or location fo a shape (if streamed to a clipboard) + val MSOFBTCHILDANCHOR = 0xF00F // " ", if shape is a child of a group shape + val MSOFBTCLIENTANCHOR = 0xF010 // " ", for top-level shapes + val MSOFBTCLIENTDATA = 0xF011 // content is determined by host + val MSOFBTCONNECTORRULE = 0xF012 // connector rule + val MSOFBTALIGNRULE = 0xF013 + val MSOFBTARCRULE = 0xF014 + val MSOFBTCLIENTRULE = 0xF015 + val MSOFBTOLEOBJECT = 0xF11F // + val MSOFBTDELETEDPSPL = 0xF11D + val MSOFBTSOLVERCONTAINER = 0xF005 // the rules governing shapes; count of rules + val MSOFBTSELECTION = 0xF119 + // MSOBI == encoded BLIP types + val msobiUNKNOWN = 0 + val msobiWMF = 0x216 + val msobiEMF = 0x3D4 + val msobiPICT = 0x542 + val msobiPNG = 0x6E0 + val msobiJFIF = 0x46A + val msobiJPEG = msobiJFIF + val msobiDIB = 0x7A8 + val msobiCLIENT = 0x800 + // + val msofbtBlipFirst = 0xF018 // used to calculate fbt of BLIP in MsofbtBSE + + // pertinent Property Table/MSOFBTOPT property id's + val msooptfLockAgainstGrouping = 127 // BOOL - Do not group this shape + val msofbtlTxid = 128 // LONG - id for the text, value determined by the host + //margins relative to shape's inscribed text rectangle (in EMUs) + val dxTextLeft = 129 // LONG 1/10 inch + val dyTextTop = 130 // LONG 1/20 inch + val dxTextRight = 131 // LONG 1/10 inch + val dyTextBottom = 132 // LONG 1/20 inch + // How to anchor the text + val anchorText = 135 // MSOANCHOR def= Top + val msofbttxdir = 139 // MSOTXDIR - Bi-Di Text direction + val msooptfFitTextToShape = 191 // BOOL - Size text to fit shape size + val msooptpib = 260 // IMsoBlip - id of Blip to display == imageIndex + val msooptpibName = 261 // WCHAR - Blip File Name == imageName + val msooptpibFlags = 262 // MSOBLIPFLAGS - Blip Flags + val msooptpictureActive = 319 // Server is active (OLE objects only)default= false + // fill attrbutes + val msooptFillType = 384 + val msooptfillColor = 385 // MSOCLR - foreground color + val msooptFillOpacity = 386 + val msooptfillBackColor = 387 // MSOCLR - background color + val msooptFillBackOpacity = 388 + val msooptFillCrMod = 389 // foreground color of the fill for black-and-white display mode. + val msooptFillBlip = 390 + val msooptFillBlipName = 391 // fillBlipName_complex -- specifies additional data for the fillBlipName property + val msooptFillBlipFlags = 392 // specifies how to interpret the fillBlipName_complex property + val msooptFillWidth = 393 // the width of the fill. This property applies only to texture, picture, and pattern fills. + // A signed integer that specifies the width of the fill in units that are specified by the fillDztype property, as defined in section 2.3.7.24. If fillDztype equals msodztypeDefault, this value MUST be ignored. The default value for this property is 0x00000000. + val msooptFillHeight = 394 // A signed integer that specifies the height of the fill in units that are specified by the fillDztype property, as defined in section 2.3.7.24. If fillDztype equals msodztypeDefault, this value MUST be ignored. The default value for this property is 0x00000000. + val msooptFillAngle = 395 // A value of type FixedPoint, as specified in [MS-OSHARED] section 2.2.1.6, that specifies the angle of the gradient fill. Zero degrees represents a vertical vector from bottom to top. The default value for this property is 0x00000000. + val msooptFillFocus = 396 // specifies the relative position of the last color in the shaded fill. + val msooptFillToLeft = 397 + val msooptFillToTop = 398 + val msooptFillToRight = 399 + val msooptFillToBottom = 400 // A signed integer that specifies the left boundary, in EMUs, of the bounding rectangle of the shaded fill. If the Fill Style BooleanfillUseRect property, as defined in section 2.3.7.43, equals 0x0, this value MUST be ignored. The default value for this property is 0x00000000. + val msooptFillRectLeft = 401 // A signed integer that specifies the top boundary, in EMUs, of the bounding rectangle of the shaded fill. If the Fill Style BooleanfillUseRect property, as defined in section 2.3.7.43, equals 0x0, this value MUST be ignored. The default value for this property is 0x00000000. + val msooptFillRectTop = 402 + val msooptFillRectRight = 403 + val msooptFillRectBottom = 404 + val msooptFillDztype = 405 // An MSODZTYPE enumeration value, as defined in section 2.4.12, that specifies how the fillWidth, as defined in section 2.3.7.12, and fillHeight, as defined in section 2.3.7.13, properties are interpreted. The default value for this property is msodztypeDefault. + val msooptFillShadePreset = 406 // A signed integer that specifies the preset colors of the gradient fill. This value MUST be from 0x00000088 through 0x0000009F, inclusive. if the fillShadeColors_complex property, as defined in section 2.3.7.27, exists, this value MUST be ignored. The default value for this property is 0x00000000. + val msooptFillShadeColors = 407 // The number of bytes of data in the fillShadeColors_complex property. If opid.fComplex equals 0x0, this value MUST be 0x00000000. The default value for this property is 0x00000000. + val msooptFillOriginX = 408 + val msooptFillOriginY = 409 + val msooptFillShapeOriginX = 410 + val msooptFillShapeOriginY = 411 + val msooptFillShadeType = 412 // : An MSOSHADETYPE record, as defined in section 2.2.50, that specifies how the shaded fill is computed. The default value for this property is msoshadeDefault. + val msooptFFilled = 443 + // line attributes + val msooptfNoFillHitTest = 447 // BOOL - Hit test a shape as though filled + val msooptlineColor = 448 // MSOCLR - Color of line + val msooptlineWidth = 459 // LONG - 1pt= 12700 EMUs + val msooptLineMiterLimit = 460 + val msooptLineStyle = 461 + val msooptLineDashing = 462 + val msooptLineDashStyle = 463 + val msooptLineStartArrowhead = 464 + val msooptLineEndArrowhead = 465 + val msooptLineStartArrowWidth = 466 + val msooptLineStartArrowLength = 467 + val msooptLineEndArrowWidth = 468 + val msooptLineEndArrowLength = 469 + val msooptLineJoinStyle = 470 + val msooptLineEndCapStyle = 471 + val msooptFArrowheadsOK = 507 + val msooptLine = 508 + val msooptFHitTestLine = 509 + val msooptLineFillShape = 510 + val msooptfNoLineDrawDash = 511 // BOOL - Draw a dashed line if no line + // Shadow attributes - many to add + val msooptfshadowColor = 513 // MSOCLR - foreground color - default = 0x808080 + val msooptfShadowObscured = 575 // BOOL - Excel5-style Shadow + val msooptfBackground = 831 // BOOL - If true, this is the background shape + val msooptwzName = 896 // WCHAR - Shape Name (present only if explicitly set in Named Range box) + val msooptwzDescription = 897 // WCHAR - Alternate text + // group shape attributes (selected) + val msooptidRegroup = 904 // LONG - Regroup id + val msooptMetroBlob = 937 /*The shape‘s 2007 representation in Office Open XML format. + The actual data is a package in Office XML format, which can simply be opened as a zip file. + This zip file contains an XML file with the root element ―sp‖. + Refer to the publically available Office Open XML documentation for more information about this data. + In case we lose any property when converting a 2007 Office Art shape to 2003 shape, + we use this blob to retrieve the original Office Art property data when opening the file in 2007. + See Appendix F for more information*/ + // misc + val msooptGroupShapeProperties = 959 // The Group Shape Boolean Properties specify a 32-bit field of Boolean properties for either a shape or a group. + // ... + // right now, we support GIF, JPG and PNG + val IMAGE_TYPE_GIF = 0 + val IMAGE_TYPE_EMF = 2 + val IMAGE_TYPE_WMF = 3 + val IMAGE_TYPE_PICT = 4 + val IMAGE_TYPE_JPG = 5 + val IMAGE_TYPE_PNG = 6 + val IMAGE_TYPE_DIB = 7 + + // Shape Types + /** + * Internally, a shape type is defined as a fixed set of property values, + * the most important being the geometry of the shape (the pVertices property, etc.). + * Each shape stores in itself only those properties that differ from its shape type. + * When a shape is asked for a property that isn't in its local table, + * it looks in the shape type's table. + * If the shape type doesn't define a value for the property, + * then the property's default value is used. + */ + val msosptMin = 0 + val msosptNotPrimitive = msosptMin + val msosptRectangle = 1 + val msosptRoundRectangle = 2 + val msosptEllipse = 3 + val msosptDiamond = 4 + val msosptIsocelesTriangle = 5 + val msosptRightTriangle = 6 + val msosptParallelogram = 7 + val msosptTrapezoid = 8 + val msosptHexagon = 9 + val msosptOctagon = 10 + val msosptPlus = 11 + val msosptStar = 12 + val msosptArrow = 13 + val msosptThickArrow = 14 + val msosptHomePlate = 15 + val msosptCube = 16 + val msosptBalloon = 17 + val msosptSeal = 18 + val msosptArc = 19 + val msosptLine = 20 + val msosptPlaque = 21 + val msosptCan = 22 + val msosptDonut = 23 + val msosptTextSimple = 24 + val msosptTextOctagon = 25 + val msosptTextHexagon = 26 + val msosptTextCurve = 27 + val msosptTextWave = 28 + val msosptTextRing = 29 + val msosptTextOnCurve = 30 + val msosptTextOnRing = 31 + val msosptStraightConnector1 = 32 + val msosptBentConnector2 = 33 + val msosptBentConnector3 = 34 + val msosptBentConnector4 = 35 + val msosptBentConnector5 = 36 + val msosptCurvedConnector2 = 37 + val msosptCurvedConnector3 = 38 + val msosptCurvedConnector4 = 39 + val msosptCurvedConnector5 = 40 + val msosptCallout1 = 41 + val msosptCallout2 = 42 + val msosptCallout3 = 43 + val msosptAccentCallout1 = 44 + val msosptAccentCallout2 = 45 + val msosptAccentCallout3 = 46 + val msosptBorderCallout1 = 47 + val msosptBorderCallout2 = 48 + val msosptBorderCallout3 = 49 + val msosptAccentBorderCallout1 = 50 + val msosptAccentBorderCallout2 = 51 + val msosptAccentBorderCallout3 = 52 + val msosptRibbon = 53 + val msosptRibbon2 = 54 + val msosptChevron = 55 + val msosptPentagon = 56 + val msosptNoSmoking = 57 + val msosptSeal8 = 58 + val msosptSeal16 = 59 + val msosptSeal32 = 60 + val msosptWedgeRectCallout = 61 + val msosptWedgeRRectCallout = 62 + val msosptWedgeEllipseCallout = 63 + val msosptWave = 64 + val msosptFoldedCorner = 65 + val msosptLeftArrow = 66 + val msosptDownArrow = 67 + val msosptUpArrow = 68 + val msosptLeftRightArrow = 69 + val msosptUpDownArrow = 70 + val msosptIrregularSeal1 = 71 + val msosptIrregularSeal2 = 72 + val msosptLightningBolt = 73 + val msosptHeart = 74 + val msosptPictureFrame = 75 + val msosptQuadArrow = 76 + val msosptLeftArrowCallout = 77 + val msosptRightArrowCallout = 78 + val msosptUpArrowCallout = 79 + val msosptDownArrowCallout = 80 + val msosptLeftRightArrowCallout = 81 + val msosptUpDownArrowCallout = 82 + val msosptQuadArrowCallout = 83 + val msosptBevel = 84 + val msosptLeftBracket = 85 + val msosptRightBracket = 86 + val msosptLeftBrace = 87 + val msosptRightBrace = 88 + val msosptLeftUpArrow = 89 + val msosptBentUpArrow = 90 + val msosptBentArrow = 91 + val msosptSeal24 = 92 + val msosptStripedRightArrow = 93 + val msosptNotchedRightArrow = 94 + val msosptBlockArc = 95 + val msosptSmileyFace = 96 + val msosptVerticalScroll = 97 + val msosptHorizontalScroll = 98 + val msosptCircularArrow = 99 + val msosptNotchedCircularArrow = 100 + val msosptUturnArrow = 101 + val msosptCurvedRightArrow = 102 + val msosptCurvedLeftArrow = 103 + val msosptCurvedUpArrow = 104 + val msosptCurvedDownArrow = 105 + val msosptCloudCallout = 106 + val msosptEllipseRibbon = 107 + val msosptEllipseRibbon2 = 108 + val msosptFlowChartProcess = 109 + val msosptFlowChartDecision = 110 + val msosptFlowChartInputOutput = 111 + val msosptFlowChartPredefinedProcess = 112 + val msosptFlowChartInternalStorage = 113 + val msosptFlowChartDocument = 114 + val msosptFlowChartMultidocument = 115 + val msosptFlowChartTerminator = 116 + val msosptFlowChartPreparation = 117 + val msosptFlowChartManualInput = 118 + val msosptFlowChartManualOperation = 119 + val msosptFlowChartConnector = 120 + val msosptFlowChartPunchedCard = 121 + val msosptFlowChartPunchedTape = 122 + val msosptFlowChartSummingJunction = 123 + val msosptFlowChartOr = 124 + val msosptFlowChartCollate = 125 + val msosptFlowChartSort = 126 + val msosptFlowChartExtract = 127 + val msosptFlowChartMerge = 128 + val msosptFlowChartOfflineStorage = 129 + val msosptFlowChartOnlineStorage = 130 + val msosptFlowChartMagneticTape = 131 + val msosptFlowChartMagneticDisk = 132 + val msosptFlowChartMagneticDrum = 133 + val msosptFlowChartDisplay = 134 + val msosptFlowChartDelay = 135 + val msosptTextPlainText = 136 + val msosptTextStop = 137 + val msosptTextTriangle = 138 + val msosptTextTriangleInverted = 139 + val msosptTextChevron = 140 + val msosptTextChevronInverted = 141 + val msosptTextRingInside = 142 + val msosptTextRingOutside = 143 + val msosptTextArchUpCurve = 144 + val msosptTextArchDownCurve = 145 + val msosptTextCircleCurve = 146 + val msosptTextButtonCurve = 147 + val msosptTextArchUpPour = 148 + val msosptTextArchDownPour = 149 + val msosptTextCirclePour = 150 + val msosptTextButtonPour = 151 + val msosptTextCurveUp = 152 + val msosptTextCurveDown = 153 + val msosptTextCascadeUp = 154 + val msosptTextCascadeDown = 155 + val msosptTextWave1 = 156 + val msosptTextWave2 = 157 + val msosptTextWave3 = 158 + val msosptTextWave4 = 159 + val msosptTextInflate = 160 + val msosptTextDeflate = 161 + val msosptTextInflateBottom = 162 + val msosptTextDeflateBottom = 163 + val msosptTextInflateTop = 164 + val msosptTextDeflateTop = 165 + val msosptTextDeflateInflate = 166 + val msosptTextDeflateInflateDeflate = 167 + val msosptTextFadeRight = 168 + val msosptTextFadeLeft = 169 + val msosptTextFadeUp = 170 + val msosptTextFadeDown = 171 + val msosptTextSlantUp = 172 + val msosptTextSlantDown = 173 + val msosptTextCanUp = 174 + val msosptTextCanDown = 175 + val msosptFlowChartAlternateProcess = 176 + val msosptFlowChartOffpageConnector = 177 + val msosptCallout90 = 178 + val msosptAccentCallout90 = 179 + val msosptBorderCallout90 = 180 + val msosptAccentBorderCallout90 = 181 + val msosptLeftRightUpArrow = 182 + val msosptSun = 183 + val msosptMoon = 184 + val msosptBracketPair = 185 + val msosptBracePair = 186 + val msosptSeal4 = 187 + val msosptDoubleWave = 188 + val msosptActionButtonBlank = 189 + val msosptActionButtonHome = 190 + val msosptActionButtonHelp = 191 + val msosptActionButtonInformation = 192 + val msosptActionButtonForwardNext = 193 + val msosptActionButtonBackPrevious = 194 + val msosptActionButtonEnd = 195 + val msosptActionButtonBeginning = 196 + val msosptActionButtonReturn = 197 + val msosptActionButtonDocument = 198 + val msosptActionButtonSound = 199 + val msosptActionButtonMovie = 200 + val msosptHostControl = 201 + //Host controls extend various user interface (UI) objects in the Word and Excel object models + val msosptTextBox = 202 + val msosptMax = 0x0FFF + val msosptNil = 0x0FFF +} diff --git a/src/main/java/io/starter/formats/XLS/MSODrawingGroup.java b/src/main/java/io/starter/formats/XLS/MSODrawingGroup.java deleted file mode 100644 index 312b086..0000000 --- a/src/main/java/io/starter/formats/XLS/MSODrawingGroup.java +++ /dev/null @@ -1,842 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ImageHandle; -import io.starter.formats.escher.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Iterator; - - -/** - * MSODrawingGroup: MS Office Drawing Group (EBh)
                  - *

                  - * These records contain only data.

                  - *
                  - * offset  name        size    contents
                  - * ---
                  - * 4       rgMSODrGr    var    MSO Drawing Group Data
                  - *
                  - * 

                  - *

                  - *

                  - * There is only one drawing group per client document (=MSOFBTDGGCONTAINER, 0xF000 ?). - * OfficeArtDggContainer: - * rh (8 bytes): An OfficeArtRecordHeader structure, that specifies the header for this record. The following table specifies the subfields. - * rh.recVer A value that MUST be 0xF. - * rh.recInstance A value that MUST be 0x000. - * rh.recType A value that MUST be 0xF000. - * rh.recLen An unsigned integer specifying the number of bytes following the header that contain document-wide file records. - * drawingGroup (variable): An OfficeArtFDGGBlock record, that specifies document-wide information about all the drawings that are saved in the file. - * blipStore (variable): An OfficeArtBStoreContainer record, that specifies the container for all the BLIPs that are used in all the drawings in the parent document. - * drawingPrimaryOptions (variable): An OfficeArtFOPT record, that specifies the default properties for all drawing objects that are contained in all the drawings in the parent document. - * drawingTertiaryOptions (variable): An OfficeArtTertiaryFOPT record, that specifies the default properties for all the drawing objects that are contained in all the drawings in the parent document. - * colorMRU (variable): An OfficeArtColorMRUContainer record, that specifies the most recently used custom colors. - * splitColors (variable): An OfficeArtSplitMenuColorContainer record, that specifies a container for the colors that were most recently used to format shapes. - *

                  - *

                  - * Drawing groups contain drawings. (= numDrawings) - * Drawings in turn contain shapes that are the objects that actually mark a page. (= numShapes) - * --Each drawing has a collection of rules that govern the shapes in the drawing - * Shape store their properties in a property table (MSOFBTOPT record of Msodrawing) - * The actual pictures and images are kept in a separate collection so can load and save separately - *

                  - * Records that are required in the MSODrawingGroup: - * MSOFBTDGG- Drawing Group Record- holds total # shapes saved + last or max SPID (shapeID) + number of IDclusters(FIDCLs) + total # drawings saved - * MSOFBTOPT- Property Table Record- Default properties of newly created shapes (can be 0'd) - * MSOFBTBSTORECONTAINER- - * MSOFBTBSE- BLIP Store Entry- holds image type, id, size, index, len of blip name ... - * - * @see MSODrawing - */ -public final class MSODrawingGroup extends io.starter.formats.XLS.XLSRecord { - // 20070914 KSC: Save drawing recs here - private AbstractList msoRecs = new ArrayList(); - - // moved from Boundsheet + renamed - public void addMsodrawingrec(MSODrawing rec) { - msoRecs.add(rec); - } - - public boolean dirtyflag = false; - - /** - * loop through all the Msodrawing recs and return the next valid SPID - * - * @return - */ - public int getNextMsoSPID() { - int spid = 0; - for (int i = 0; i < msoRecs.size(); i++) - spid = Math.max(((MSODrawing) msoRecs.get(i)).getSPID(), spid); - return spid + 1; - } - - /** - * remove linked MsoDrawing rec from this drawing group + update image references if necessary - * NOTE THIS IS STILL EXPERIMENTAL; MUST BE TESTED WITH A VARIETY OF SCENARIOS - */ - public void removeMsodrawingrec(MSODrawing rec, Boundsheet sheet, boolean removeObjRec) { - int imgIdx = rec.getImageIndex() - 1; - int refCnt = this.getCRef(imgIdx); - boolean wasHeader = rec.bIsHeader; - if (refCnt > 0) - this.decCRef(imgIdx); - msoRecs.remove(rec); - this.updateRecord(); // update msodg rec - int idx = rec.getRecordIndex(); // chart mso's have been taken out of streamer so idx will be -1 - if (idx > -1) { - sheet.removeRecFromVec(rec); // remove Mso rec - if (removeObjRec) // also remove associated obj rec 20080804 KSC - sheet.removeRecFromVec(idx); // also remove linked Obj record - } - - if (this.getMsodrawingrecs().size() == 0) { // no more drawing recs, delete this msodg - sheet.removeRecFromVec(this); - this.getWorkBook().msodg = null; - // TODO: Unsure if there are other circumstances where MsodrawingSelection should be removed ... watch out for it - // KSC: TODO: Necessary???? Appears so for delete chart ...(WHY??????) - BiffRec b = sheet.getSheetRec(MSODRAWINGSELECTION); - if (b != null) { - sheet.removeRecFromVec(b); - } - } else { - if (wasHeader) { // we just removed the header; set 1st one to it - MSODrawing mso = null; - for (int z = 0; z < this.getMsodrawingrecs().size(); z++) { - mso = (MSODrawing) this.getMsodrawingrecs().get(z); - if (mso.getSheet().equals(sheet) && mso.isShape) { - mso.setIsHeader(); // make this one the header rec - break; - } - } - } - this.wkbook.updateMsodrawingHeaderRec(sheet); - } - } - - /** - * return the Msodrawing header record for the given sheet - * - * @param bs - * @return - */ - public MSODrawing getMsoHeaderRec(Boundsheet bs) { - for (int i = 0; i < msoRecs.size(); i++) { - MSODrawing msd = (MSODrawing) msoRecs.get(i); // get index of first Msodrawing rec for this sheet - // always: the 1st msodrawing rec for the sheet contains the # information ... - if (msd.getSheet().equals(bs)) { // got it! - if (msd.isHeader()) { - return msd; - } //else - //Logger.logErr("WorkBook.updateMsodrawingHeaderRec: Header Record should be first rec in group."); -// break; - } - } - return null; - } - - public AbstractList getMsodrawingrecs() { - return msoRecs; - } - - int spidMax = 1024, numIdClusters = 2, numShapes = 1, numDrawings = 1; - - private ArrayList imageData = new ArrayList(); - private ArrayList imageType = new ArrayList(); // parallel array with imageData - private ArrayList cRef = new ArrayList(); // 20071120 KSC: keep track of reference count for image data - - - public void init() { - super.init(); - data = super.getData(); - } - - /* 20070813 KSC: These prototype bytes works for both Images and Charts */ - public byte[] PROTOTYPE_BYTES = {15, 0, 0, -16, 82, 0, 0, 0, 0, 0, 6, -16, 24, 0, 0, 0, 2, 4, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 51, 0, 11, -16, 18, 0, 0, 0, -65, 0, 8, 0, 8, 0, -127, 1, 9, 0, 0, 8, -64, 1, 64, 0, 0, 8, 64, 0, 30, -15, 16, 0, 0, 0, 13, 0, 0, 8, 12, 0, 0, 8, 23, 0, 0, 8, -9, 0, 0, 16}; - - public static XLSRecord getPrototype() { - MSODrawingGroup grp = new MSODrawingGroup(); - grp.setOpcode(MSODRAWINGGROUP); - grp.setData(grp.PROTOTYPE_BYTES); - grp.init(); - return grp; - } - - // Add associated recs necessary for Msodrawing ... - public void initNewMSODrawingGroup() { - // add new msodg rec to stream (just before sst) - int index = streamer.getRecordIndex(XLSConstants.SST); - // add unknown record that appears just before MSODrawingGroup - XLSRecord rec = new XLSRecord(); - rec.setOpcode((short) 0x1C1); - rec.setData(this.PROTOTYPE_1C1); - streamer.addRecordAt(rec, index++); - // add MSODrawingGroup - streamer.addRecordAt(this, index); - // also need msymystery record + msoselection ... - Boundsheet[] b = this.getWorkBook().getWorkSheets(); - for (int i = 0; i < b.length; i++) { - int z = b[i].getIndexOf(PHONETIC); - if (z == -1) { - Phonetic p = new Phonetic(); - p.setData(p.PROTOTYPE_BYTES); - p.setOpcode(XLSRecord.PHONETIC); - p.setDebugLevel(this.DEBUGLEVEL); - p.setStreamer(this.getStreamer()); - b[i].insertSheetRecordAt(p, b[i].getIndexOf(SELECTION) + 1); - } -/* truly necessary??? if (i==0) { // msodrawingselection only for 1st sheet??????? - Msodrawingselection msoSelection = new Msodrawingselection(); - msoSelection.setData(msoSelection.PROTOTYPE_BYTES); - msoSelection.setOpcode(XLSRecord.MSODRAWINGSELECTION); - msoSelection.setDebugLevel(this.DEBUGLEVEL); - msoSelection.setStreamer(this.getStreamer()); - b[i].insertSheetRecordAt(msoSelection, b[i].getIndexOf(Window2.class)); - } -*/ - } - } - - /** - * - */ - private static final long serialVersionUID = 2378100973014157878L; - - /** - * The XF record can either be a style XF or a BiffRec XF. - */ - /*These are prototype bytes for record 0x1c1 and 0x863 that seem to accompany when there is MSODrawingGroup data*/ - public byte[] PROTOTYPE_1C1 = {-63, 1, 0, 0, -128, 56, 1, 0}; - public byte[] PROTOTYPE_863 = {99, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, -46}; - - - /** - * Parse the MSODrawingGroup bytes and generate state vars: - * imageData, imageType, cRef - * spidMax, numIdClusters, numDrawings, numShapes - */ - public void parse() { - imageData.clear(); - imageType.clear(); - cRef.clear(); - //data = getBytes(); - if (data == null) - return; // no data! - - /* - * This represents the MSOFBTDGGCONTAINER record (0xFOOO) which is the Drawing Group Container - * - * - * - * The MSOFBTDGGCONTAINER Contains the following records (some are optional): - * rh.recVer A value that MUST be 0xF. - rh.recInstance A value that MUST be 0x000. - rh.recType A value that MUST be 0xF000. - rh.recLen variable - * MSOFBTDGG (0xF006) Drawing Group Record, contains number of shapes, drawings and id clusters - * MSOFBTCLSID (0xF016) Clipboard format (optional) - * MSOFBTOPT (0xF00B) Property Table Record - default props of newly created shapes; only the properties that differ from - * the per-property defaults are saved. Format is same as Msodrawing.MSOFBTOPT format - * MSOFBTCOLORMRU (0xF11A) MRU Color swatch ... - * MSOFBTSPLITMENUCOLORS (0xF11E) MRU colors of the top-level ..split menus - * MSOFBTBSTORECONTAINER (0xF001) An array of BLIP Store Entry (BSE) Records; Each shape indexes into this array for the BLIP they use - * MSOFBTBSE (0xF007) File BLIP Store Entry Recod FBSE record; Encodes type of BLIP + size + ID + ref. count + file offset ... - * MSOFBTBLIP (0xF018) - */ - /* BLIP TYPE ENUM - * msoblipERROR= 0 - * msoblipUNKNOWN, - * msoblipEMF, // enhanced meta file - * msoblipWMF, // windows meta file - * msoblipPICT // MAC pic - * msoblipJPEG, - * msoblipPNG, - * msoblipDIB, - * msoblipFirstClient=32, // first client-defined BLIP type - * msoblipLastClient= 255 // last "" - */ - try { - byte[] buf; - ByteArrayInputStream bis = new ByteArrayInputStream(data); - while (bis.available() > 0) { - buf = new byte[8]; - int read = bis.read(buf, 0, 8); - int version = (0xF & buf[0]); - int inst = ((0xFF & buf[1]) << 4) | (0xF0 & buf[0]) >> 4; - int fbt = ((0xFF & buf[3]) << 8) | (0xFF & buf[2]); - int len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); - - //io.starter.toolkit.Logger.log("fbt:"+Integer.toHexString(fbt)+";len:"+len); - if (fbt < 0xF004) - continue; // under 0xF005 are container recs; we just parse the atoms for needed info ... - - // parse record denoted by fbt - buf = new byte[len]; - read = bis.read(buf, 0, len); - - switch (fbt) { - case MSODrawingConstants.MSOFBTDGG: //0xf006: // MSOFBTDGG - Drawing Group Record - // rh.recVer A value that MUST be 0x0. - // rh.recInstance A value that MUST be 0x000. - // rh.recType A value that MUST be 0xF006. - // rh.recLen A value that MUST be 0x00000010 + ((head.cidcl - 1) * 0x00000008) - // head (16 bytes): An OfficeArtFDGG record, that specifies document-wide information. - // - // Rgidcl (variable): An array of OfficeArtIDCL elements, specifying file identifier clusters that are used in the drawing. The number of elements in the array is specified by (head.cidcl – 1). - spidMax = ByteTools.readInt(buf[0], buf[1], buf[2], buf[3]); // maximum shape ID - numIdClusters = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]); // number of ID clusters - numShapes = ByteTools.readInt(buf[8], buf[9], buf[10], buf[11]); // total number of shapes saved - numDrawings = ByteTools.readInt(buf[12], buf[13], buf[14], buf[15]); // total number of drawings saved - // the fixed part is followed by and array of ID clusters used internally for the translation of SPIDs to MSHHSPs (Shape Handles) - break; - - case MSODrawingConstants.MSOFBTBSE: //0xf007: // File BLIP Store Entry Record (FBSE) - byte[] imgBuf = getImageBytesWithBuffer(buf); - // strip buffer from data image bytes -// numShapes--; // 20070914 KSC: WHY?? -- (not a real shape? -jm) - break; - } - } - } catch (Exception e) { - Logger.logErr("Msodrawingroup parse error.", e); - } - } - - private byte[] getImageBytesWithBuffer(byte[] buf) { -// Each BLIP in the BStore is serialized to a FBSE record; - // btWin32, btMacOS, rgbUid[16] = identifier of blip, tag, size=BLIP size in stream - int btWin32 = buf[0]; - imageType.add(Integer.valueOf(btWin32)); - /* parse header for testing purposes - // inst= encoded type - int pos= 1; - int btMac= buf[pos++]; - byte[] UID= new byte[16]; - byte[] UID2= new byte[16]; - System.arraycopy(buf, pos, UID, 0, 16); - pos+=16; - short tag= ByteTools.readShort(buf[pos],buf[pos+1]); pos+=2; - int size= ByteTools.readInt(buf[pos],buf[pos+1],buf[pos+2],buf[pos+3]); pos+=4; - int cref= ByteTools.readInt(buf[pos],buf[pos+1],buf[pos+2],buf[pos+3]); pos+=4; - int delayOffset= ByteTools.readInt(buf[pos],buf[pos+1],buf[pos+2],buf[pos+3]); pos+=4; - byte usage= buf[pos++]; - byte nameLen= buf[pos++]; - byte unused1= buf[pos++]; - byte unused2= buf[pos++]; - if (nameLen==0 && delayOffset==0 && buf.length > 36) { - // BLIP record follows then - byte[] BLIPbuf = new byte[24]; - System.arraycopy(buf, pos, BLIPbuf, 0, 24); - int version = (0xF&BLIPbuf[0]); - int inst = ((0xFF&BLIPbuf[1])<<4)|(0xF0&BLIPbuf[0])>>4; - int fbt = ((0xFF&BLIPbuf[3])<<8)|(0xFF&BLIPbuf[2]); - int len = ByteTools.readInt(BLIPbuf[4], BLIPbuf[5], BLIPbuf[6], BLIPbuf[7]); - UID2= new byte[16]; - System.arraycopy(buf, 8, UID2, 0, 16); - } else if (buf.length > 36){ - Logger.logWarn("Delay? " + ((delayOffset==0)?"No":"Yes") + " Namelen=" + nameLen); - } - */ - int ref = ByteTools.readInt(buf[24], buf[25], buf[26], buf[27]); - cRef.add(Integer.valueOf(ref)); - - int HEADERLEN = 61; - int STARTPOS = HEADERLEN; - int BYTELEN = buf.length; - if (HEADERLEN > BYTELEN) { - BYTELEN = 0; - STARTPOS = 0; - } - BYTELEN -= STARTPOS; - - byte[] imgBuf = new byte[BYTELEN]; - System.arraycopy(buf, STARTPOS, imgBuf, 0, BYTELEN); - - imageData.add(imgBuf); - return imgBuf; - } - - /** - * create a new MSODrawingGroup record based upon image datas defined in imageData/imageType/cRef arrays + - * spidMax, numDrawings, numShapes, numIdClusters - *

                  - * The squenence of records here are: - * F000, F006, F001, F007(xNumImages), F00B ,F11E - * MSOFBTDGG MSOFBTBSTORECONTAINER MSOFBTBSE (x numimages) - */ - public void updateRecord() { - MsofbtBSE[] BSE = new MsofbtBSE[imageData.size()]; //(0xF007,1,0); - byte[] imageBytes = null; - - ByteArrayOutputStream bos = null; - try { - bos = new ByteArrayOutputStream(); - for (int i = 0; i < imageData.size(); i++) { - BSE[i] = new MsofbtBSE(MSODrawingConstants.MSOFBTBSE, Integer.parseInt(imageType.get(i).toString()), 2); - BSE[i].setImageData((byte[]) imageData.get(i)); - BSE[i].setImageType(Integer.parseInt(imageType.get(i).toString())); - BSE[i].setRefCount(((Integer) cRef.get(i)).intValue()); // 20071120 KSC: set the reference count for this image data - bos.write(BSE[i].toByteArray()); - } - imageBytes = bos.toByteArray(); - } catch (Exception e) { - Logger.logErr("Msodrawingroup createData error.", e); - } - - MsofbtDgg dgg = new MsofbtDgg(MSODrawingConstants.MSOFBTDGG, 0, 0); - dgg.setSpidMax(spidMax); // 20071113 KSC - numDrawings = getNumDrawings(); // 20100324 KSC: changed from: msoRecs.size(); // 20080908 KSC - dgg.setNumDrawings(numDrawings); - numShapes = getNumShapes(); // 20080904 KSC: sum up dg's shapes - dgg.setNumShapes(numShapes); - // 2008003 KSC: numIdClusters is solely a function of spidMax dgg.setNumIdClusters(numIdClusters); - byte[] dggBytes = dgg.toByteArray(); - - MsofbtOPT OPT = new MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3); - // add the apparent basic shape options - OPT.setProperty(MSODrawingConstants.msooptfFitTextToShape, false, false, 0x80008, null); - OPT.setProperty(MSODrawingConstants.msooptfillColor, false, false, 0x8000041, null); - OPT.setProperty(MSODrawingConstants.msooptlineColor, false, false, 0x8000040, null); - byte[] OPTBytes = OPT.toByteArray(); - - /* 20070915 KSC not necessary for all msodgs*/ - MsofbtSplitMenuColors SplitMenuColors = new MsofbtSplitMenuColors(MSODrawingConstants.MSOFBTSPLITMENUCOLORS, 4, 0); - byte[] SplitMenuColorsBytes = SplitMenuColors.toByteArray(); - - int totalLength = imageBytes.length; - - // 20080910 KSC: if no images, don't input n MSOFBTBSTORE - byte[] BstoreContainerBytes = new byte[0]; - if (totalLength > 0) { - MsofbtBstoreContainer BstoreContainer = new MsofbtBstoreContainer(MSODrawingConstants.MSOFBTBSTORECONTAINER, imageData.size(), 15); - BstoreContainer.setLength(totalLength); - BstoreContainerBytes = BstoreContainer.toByteArray(); - - // add up the stuff - totalLength += - OPTBytes.length + - SplitMenuColorsBytes.length + - BstoreContainerBytes.length + - dggBytes.length; - } else { - // add up the stuff - totalLength += - OPTBytes.length + - SplitMenuColorsBytes.length + - dggBytes.length; - } - MsofbtDggContainer dggContainer = new MsofbtDggContainer(MSODrawingConstants.MSOFBTDGGCONTAINER, 0, 15); - dggContainer.setLength(totalLength); - - byte[] dggContainerBytes = dggContainer.toByteArray(); - - int pos = 0; - byte[] retData = new byte[totalLength + dggContainerBytes.length]; - - System.arraycopy(dggContainerBytes, 0, retData, pos, dggContainerBytes.length); - pos += dggContainerBytes.length; - System.arraycopy(dggBytes, 0, retData, pos, dggBytes.length); - pos += dggBytes.length; - System.arraycopy(BstoreContainerBytes, 0, retData, pos, BstoreContainerBytes.length); - pos += BstoreContainerBytes.length; - // this is the BSE array - System.arraycopy(imageBytes, 0, retData, pos, imageBytes.length); - pos += imageBytes.length; - // default OPT - System.arraycopy(OPTBytes, 0, retData, pos, OPTBytes.length); - pos += OPTBytes.length; - // 20070915 KSC not truly necessary - System.arraycopy(SplitMenuColorsBytes, 0, retData, pos, SplitMenuColorsBytes.length); - pos += SplitMenuColorsBytes.length; - - setData(retData); - } - - - /** - * sets the underlying image bytes - * - * @param bts new image bytes - * @param bs Boundsheet - * @param rec original Msodrawing rec linked to image - * @param name original image name (used for lookups) - * @return - */ - public boolean setImageBytes(byte[] bts, Boundsheet bs, MSODrawing rec, String name) { - // Find original image handle - often is different than getImageIndex due to reuse, etc. of image bytes - int trueIdx = rec.getImageIndex() - 1; // true index into imageData and cRef arrays - if (trueIdx < 0) - return false; - - if ((imageData.size()) <= trueIdx) - return false; - - try { - if (getCRef(trueIdx) > 1) { //20080802 KSC: if referenced more than 1x, add new so don't overwrite original - // create new image handle with new bytes - ImageHandle im = new ImageHandle(bts, bs); - // Find original image handle + fill new with original info - int index = -1; - ImageHandle[] imgz = bs.getImages(); - for (int i = 0; i < imgz.length; i++) { - if (imgz[i].getName().equals(name)) { - index = i; - break; - } - } - ImageHandle origIm = imgz[index]; // get original image handle - im.setName(origIm.getName()); // set new with original's data - im.setShapeName(origIm.getShapeName()); - im.setImageType(origIm.getImageType()); - // insert new image into sheet - bs.insertImage(im, true); - im.position(origIm); // position to original - // now remove original mso rec - removeMsodrawingrec(rec, bs, true); - index = imageData.size() - 1; // new index - } else // just set the image bytes - imageData.set(trueIdx, bts); - } catch (Exception ex) { - Logger.logErr("Msodrawingroup setImageBytes failed.", ex); - return false; - } - - updateRecord(); - parse(); - wkbook.initImages(); - return true; - } - - - /** - * returns the underlying image bytes - * - * @param index - * @return - */ - public byte[] getImageBytes(int index) { - if (index < 0) - return null; - - if (index >= imageData.size()) - return null; - - byte[] ret = null; - try { - ret = (byte[]) imageData.get(index); - } catch (Exception ex) { - Logger.logErr("Msodrawingroup getImageBytes error.", ex); - } - return ret; - - } - - public int getImageType(int index) { - - return Integer.parseInt(imageType.get(index).toString()); - } - - /** - * returns the number of *unique* images in this workbook - * - * @return - */ - public int getNumImages() { - return imageData.size(); - } - - /** - * related to number of drawing objects (= images + charts) but unclear how the count goes; may include deleted, etc . - * - * @return - */ - public int getNumDrawings() { - // 20100324 KSC: this is experimental as numDrawings do not follow any obvious logic ... - numDrawings = 0; - // 20100511 KSC: this is not correct ... - for (int i = 0; i < msoRecs.size(); i++) { - // 20100518 KSC: try this: - if (((MSODrawing) msoRecs.get(i)).isHeader()) - numDrawings++; - //numDrawings= Math.max(numDrawings, ((Msodrawing)msoRecs.get(i)).getDrawingId()); - } -// if (numDrawings==0 && msoRecs.size() > 0) numDrawings++; - return numDrawings; - } - - /** - * count the number of shapes in the document; shape mso's contain a msofbtSpContainer sub-record (TODO: is this true in every case?) - * - * @return - */ - public int getNumShapes() { - /** - * NOTE: I've never found a clear algorithm for numShapes which results in - * matching Excel results; however, - * it appears that numShapes can be >= to Excel's value and open correctly; - * problems occur when numShapes are less than what Excel expects. - * So the below is basically the maximum numShapes available and appears to - * result in templates - */ - /* - numShapes= 1; - for (int i= 0; i < msoRecs.size(); i++) { - MSODrawing mso= ((MSODrawing) msoRecs.get(i)); - if (mso.isShape) - numShapes++; - } - */ - numShapes = msoRecs.size(); - return numShapes; - } - - /** - * return the # of Id Clusters (charts related?) - * - * @return - */ - public int getNumIdClusters() { - return numIdClusters; - } - - /** - * return SpidMax - * - * @return - */ - public int getSpidMax() { - return spidMax; - } - - /** - * set SpidMax - * - * @param spid - */ - public void setSpidMax(int spid) { - spidMax = spid; - } - - /** - * test to see if imageData is in imageArray - * - * @param imgData byte[] defining image - * @return - */ - protected int containsImage(byte[] imgData) { - int z = -1; - for (int i = 0; i < imageData.size() && z < 0; i++) { - if (java.util.Arrays.equals(imgData, ((byte[]) imageData.get(0)))) - z = i; - } - return z; - } - - /** - * return the index into the imageData array for the specified image (via byte lookup) - * - * @param imgData image bytes - * @return index into imageData array - */ - public int findImage(byte[] imgData) { - return containsImage(imgData); - } - - /** - * if imageData doesn't exist, add to array - * otherwise just inc ref count - * - * @param imgData byte[] defining image - * @param imgType type of image - * @param bAddUnconditionally add new even if already referenced (used in setting image bytes) - * @return index to image - */ - public int addImage(byte[] imgData, int imgType, boolean bAddUnconditionally) { - int n = -1; - // 20080908 KSC: done automatically numShapes++; // 20080208 KSC: if add unconditionally, add even if imageData already exists - if (bAddUnconditionally || (n = containsImage(imgData)) == -1) { // 20071120 KSC: it's a unique image - imageData.add(imgData); - imageType.add(Integer.valueOf(imgType)); - cRef.add(Integer.valueOf(1)); - n = imageData.size(); - } else { // 20071120 KSC: If not a unqiue image, just update ref count - incCRef(n); - n++; - } - return n; - } - - public void clear() { - numShapes = 0; - imageData.clear(); - imageType.clear(); - } - - /** - * If large, MSODrawingGroup will span multiple records; merge data - * - * @param rec next MSODrawingGroup record in stream - */ - public void mergeRecords(MSODrawingGroup rec) { - // Merge and remove continues - if (rec.hasContinues()) { - rec.mergeAndRemoveContinues(); // now that data is merged, get rid of continues ... - } - byte[] prevData = this.getBytes(); - byte[] newData = rec.getBytes(); - byte[] totalData = new byte[newData.length]; - if (prevData != null) { // a simple append of the data together - totalData = new byte[prevData.length + newData.length]; - System.arraycopy(prevData, 0, totalData, 0, prevData.length); - System.arraycopy(newData, 0, totalData, prevData.length, newData.length); - } else - totalData = newData; - this.setData(totalData); - } - - /** - * show pertinent information for record - */ - public String toString() { - StringBuffer sb = new StringBuffer(); - sb.append("MSODrawingGroup: numShapes=" + numShapes + " numDrawings=" + numDrawings + " numIdCluster=" + numIdClusters + " spidMax=" + spidMax); - sb.append("\nNumber of drawing records=" + msoRecs.size()); - if (data != null) - sb.append(" Length of data=" + data.length); - return sb.toString(); - } - - // continue handling - public void mergeAndRemoveContinues() { - if (!this.isContinueMerged && this.hasContinues()) super.mergeContinues(); - if (this.hasContinues()) { - // now that data is merged, get rid of continues ... - Iterator it = this.continues.iterator(); - while (it.hasNext()) { - Continue ci = (Continue) it.next(); - this.getStreamer().removeRecord(ci); // remove existing continues from stream - } - super.removeContinues(); - this.continues = null; - } - } - - /** - * increment reference count for specific image data - * - * @param idx - */ - protected void incCRef(int idx) { - if (idx >= 0 && idx < cRef.size()) { - int cr = ((Integer) cRef.get(idx)).intValue() + 1; - cRef.remove(idx); - cRef.add(idx, Integer.valueOf(cr)); - } //else 20071126 KSC: it's OK, can have - indexes ... - //Logger.logErr("Index error encountered when updating Reference Count"); - } - - /** - * return the reference count for the specific image - * - * @param idx - */ - protected int getCRef(int idx) { - if (idx >= 0 && idx < cRef.size()) { - return ((Integer) cRef.get(idx)).intValue(); - } - //Logger.logErr("MSODrawingGroup: error encountered when returning Reference Count"); - return -1; - } - - /** - * decrement the reference count for the specific image - * - * @param idx - */ - protected void decCRef(int idx) { - if (idx >= 0 && idx < cRef.size()) { - int cr = ((Integer) cRef.get(idx)).intValue() - 1; - cRef.remove(idx); - cRef.add(idx, Integer.valueOf(cr)); - } else - Logger.logErr("MSODrawingGroup: error encountered when decrementing Reference Count"); - } - - /** - * add a new Drawing Record based on existing drawing record - * i.e. from CopyWorkSheet ... - * - * @param spidMax - * @param rec - */ - public void addDrawingRecord(int spidMax, MSODrawing rec) { - this.numDrawings++; - incCRef(rec.imageIndex - 1); // increment cRef - this.spidMax = spidMax; - this.updateRecord(); // given all information, generate appropriate bytes - } - - /** - * Must ensure that oridinal drawing Id for each drawing record is correct - * Plus ensure SPID's are correct - *

                  - * Not default prestreaming as we need these values when we assemble sheet recs - */ - public void prestream() { - int j = 0; - if (dirtyflag) { - for (int i = 0; i < msoRecs.size(); i++) { - MSODrawing mso = (MSODrawing) msoRecs.get(i); - if (mso.isHeader()) { - mso.setDrawingId(++j); - } - } - } - } - - /** - * return the number of drawing recs - */ - public int getNumDrawingRecs() { - return msoRecs.size(); - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - super.close(); - for (int i = 0; i < msoRecs.size(); i++) { - MSODrawing m = (MSODrawing) msoRecs.get(i); - m.close(); - } - msoRecs.clear(); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/MSODrawingGroup.kt b/src/main/java/io/starter/formats/XLS/MSODrawingGroup.kt new file mode 100644 index 0000000..f1984fd --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/MSODrawingGroup.kt @@ -0,0 +1,845 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.ImageHandle +import io.starter.formats.escher.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.util.AbstractList +import java.util.ArrayList + + +/** + * **MSODrawingGroup: MS Office Drawing Group (EBh)**

                  + * + * + * These records contain only data. + * + *

                  + *
                  + * offset  name        size    contents
                  + * ---
                  + * 4       rgMSODrGr    var    MSO Drawing Group Data
                  + *
                  +
                  * + * + * + * + * + * There is only one drawing group per client document (=MSOFBTDGGCONTAINER, 0xF000 ?). + * OfficeArtDggContainer: + * rh (8 bytes): An OfficeArtRecordHeader structure, that specifies the header for this record. The following table specifies the subfields. + * rh.recVer A value that MUST be 0xF. + * rh.recInstance A value that MUST be 0x000. + * rh.recType A value that MUST be 0xF000. + * rh.recLen An unsigned integer specifying the number of bytes following the header that contain document-wide file records. + * drawingGroup (variable): An OfficeArtFDGGBlock record, that specifies document-wide information about all the drawings that are saved in the file. + * blipStore (variable): An OfficeArtBStoreContainer record, that specifies the container for all the BLIPs that are used in all the drawings in the parent document. + * drawingPrimaryOptions (variable): An OfficeArtFOPT record, that specifies the default properties for all drawing objects that are contained in all the drawings in the parent document. + * drawingTertiaryOptions (variable): An OfficeArtTertiaryFOPT record, that specifies the default properties for all the drawing objects that are contained in all the drawings in the parent document. + * colorMRU (variable): An OfficeArtColorMRUContainer record, that specifies the most recently used custom colors. + * splitColors (variable): An OfficeArtSplitMenuColorContainer record, that specifies a container for the colors that were most recently used to format shapes. + * + * + * + * + * Drawing groups contain drawings. (= numDrawings) + * Drawings in turn contain shapes that are the objects that actually mark a page. (= numShapes) + * --Each drawing has a collection of rules that govern the shapes in the drawing + * Shape store their properties in a property table (MSOFBTOPT record of Msodrawing) + * The actual pictures and images are kept in a separate collection so can load and save separately + * + * + * Records that are required in the MSODrawingGroup: + * MSOFBTDGG- Drawing Group Record- holds total # shapes saved + last or max SPID (shapeID) + number of IDclusters(FIDCLs) + total # drawings saved + * MSOFBTOPT- Property Table Record- Default properties of newly created shapes (can be 0'd) + * MSOFBTBSTORECONTAINER- + * MSOFBTBSE- BLIP Store Entry- holds image type, id, size, index, len of blip name ... + * + * @see MSODrawing + */ +class MSODrawingGroup : io.starter.formats.XLS.XLSRecord() { + // 20070914 KSC: Save drawing recs here + val msodrawingrecs: AbstractList<*> = ArrayList() + + var dirtyflag = false + + /** + * loop through all the Msodrawing recs and return the next valid SPID + * + * @return + */ + val nextMsoSPID: Int + get() { + var spid = 0 + for (i in msodrawingrecs.indices) + spid = Math.max((msodrawingrecs[i] as MSODrawing).spid, spid) + return spid + 1 + } + + /** + * return SpidMax + * + * @return + */ + /** + * set SpidMax + * + * @param spid + */ + var spidMax = 1024 + /** + * return the # of Id Clusters (charts related?) + * + * @return + */ + var numIdClusters = 2 + internal set + internal var numShapes = 1 + internal var numDrawings = 1 + + private val imageData = ArrayList() + private val imageType = ArrayList() // parallel array with imageData + private val cRef = ArrayList() // 20071120 KSC: keep track of reference count for image data + + /* 20070813 KSC: These prototype bytes works for both Images and Charts */ + var PROTOTYPE_BYTES = byteArrayOf(15, 0, 0, -16, 82, 0, 0, 0, 0, 0, 6, -16, 24, 0, 0, 0, 2, 4, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 51, 0, 11, -16, 18, 0, 0, 0, -65, 0, 8, 0, 8, 0, -127, 1, 9, 0, 0, 8, -64, 1, 64, 0, 0, 8, 64, 0, 30, -15, 16, 0, 0, 0, 13, 0, 0, 8, 12, 0, 0, 8, 23, 0, 0, 8, -9, 0, 0, 16) + + /** + * The XF record can either be a style XF or a BiffRec XF. + */ + /*These are prototype bytes for record 0x1c1 and 0x863 that seem to accompany when there is MSODrawingGroup data*/ + var PROTOTYPE_1C1 = byteArrayOf(-63, 1, 0, 0, -128, 56, 1, 0) + var PROTOTYPE_863 = byteArrayOf(99, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, -46) + + /** + * returns the number of *unique* images in this workbook + * + * @return + */ + val numImages: Int + get() = imageData.size + + /** + * return the number of drawing recs + */ + val numDrawingRecs: Int + get() = msodrawingrecs.size + + // moved from Boundsheet + renamed + fun addMsodrawingrec(rec: MSODrawing) { + msodrawingrecs.add(rec) + } + + /** + * remove linked MsoDrawing rec from this drawing group + update image references if necessary + * NOTE THIS IS STILL EXPERIMENTAL; MUST BE TESTED WITH A VARIETY OF SCENARIOS + */ + fun removeMsodrawingrec(rec: MSODrawing, sheet: Boundsheet, removeObjRec: Boolean) { + val imgIdx = rec.getImageIndex() - 1 + val refCnt = this.getCRef(imgIdx) + val wasHeader = rec.isHeader + if (refCnt > 0) + this.decCRef(imgIdx) + msodrawingrecs.remove(rec) + this.updateRecord() // update msodg rec + val idx = rec.recordIndex // chart mso's have been taken out of streamer so idx will be -1 + if (idx > -1) { + sheet.removeRecFromVec(rec) // remove Mso rec + if (removeObjRec) + // also remove associated obj rec 20080804 KSC + sheet.removeRecFromVec(idx) // also remove linked Obj record + } + + if (this.msodrawingrecs.size == 0) { // no more drawing recs, delete this msodg + sheet.removeRecFromVec(this) + this.workBook!!.msodg = null + // TODO: Unsure if there are other circumstances where MsodrawingSelection should be removed ... watch out for it + // KSC: TODO: Necessary???? Appears so for delete chart ...(WHY??????) + val b = sheet.getSheetRec(XLSConstants.MSODRAWINGSELECTION) + if (b != null) { + sheet.removeRecFromVec(b) + } + } else { + if (wasHeader) { // we just removed the header; set 1st one to it + var mso: MSODrawing? = null + for (z in 0 until this.msodrawingrecs.size) { + mso = this.msodrawingrecs[z] as MSODrawing + if (mso.sheet == sheet && mso.isShape) { + mso.setIsHeader() // make this one the header rec + break + } + } + } + this.wkbook!!.updateMsodrawingHeaderRec(sheet) + } + } + + /** + * return the Msodrawing header record for the given sheet + * + * @param bs + * @return + */ + fun getMsoHeaderRec(bs: Boundsheet): MSODrawing? { + for (i in msodrawingrecs.indices) { + val msd = msodrawingrecs[i] as MSODrawing // get index of first Msodrawing rec for this sheet + // always: the 1st msodrawing rec for the sheet contains the # information ... + if (msd.sheet == bs) { // got it! + if (msd.isHeader) { + return msd + } //else + //Logger.logErr("WorkBook.updateMsodrawingHeaderRec: Header Record should be first rec in group."); + // break; + } + } + return null + } + + + override fun init() { + super.init() + data = super.getData() + } + + // Add associated recs necessary for Msodrawing ... + fun initNewMSODrawingGroup() { + // add new msodg rec to stream (just before sst) + var index = streamer!!.getRecordIndex(XLSConstants.SST.toInt()) + // add unknown record that appears just before MSODrawingGroup + val rec = XLSRecord() + rec.opcode = 0x1C1.toShort() + rec.setData(this.PROTOTYPE_1C1) + streamer!!.addRecordAt(rec, index++) + // add MSODrawingGroup + streamer!!.addRecordAt(this, index) + // also need msymystery record + msoselection ... + val b = this.workBook!!.workSheets + for (i in b.indices) { + val z = b[i].getIndexOf(XLSConstants.PHONETIC) + if (z == -1) { + val p = Phonetic() + p.setData(p.PROTOTYPE_BYTES) + p.opcode = XLSRecord.PHONETIC + p.setDebugLevel(this.DEBUGLEVEL) + p.streamer = this.streamer + b[i].insertSheetRecordAt(p, b[i].getIndexOf(XLSConstants.SELECTION) + 1) + } + /* truly necessary??? if (i==0) { // msodrawingselection only for 1st sheet??????? + Msodrawingselection msoSelection = new Msodrawingselection(); + msoSelection.setData(msoSelection.PROTOTYPE_BYTES); + msoSelection.setOpcode(XLSRecord.MSODRAWINGSELECTION); + msoSelection.setDebugLevel(this.DEBUGLEVEL); + msoSelection.setStreamer(this.getStreamer()); + b[i].insertSheetRecordAt(msoSelection, b[i].getIndexOf(Window2.class)); + } +*/ + } + } + + + /** + * Parse the MSODrawingGroup bytes and generate state vars: + * imageData, imageType, cRef + * spidMax, numIdClusters, numDrawings, numShapes + */ + fun parse() { + imageData.clear() + imageType.clear() + cRef.clear() + //data = getBytes(); + if (data == null) + return // no data! + + /* + * This represents the MSOFBTDGGCONTAINER record (0xFOOO) which is the Drawing Group Container + * + * + * + * The MSOFBTDGGCONTAINER Contains the following records (some are optional): + * rh.recVer A value that MUST be 0xF. + rh.recInstance A value that MUST be 0x000. + rh.recType A value that MUST be 0xF000. + rh.recLen variable + * MSOFBTDGG (0xF006) Drawing Group Record, contains number of shapes, drawings and id clusters + * MSOFBTCLSID (0xF016) Clipboard format (optional) + * MSOFBTOPT (0xF00B) Property Table Record - default props of newly created shapes; only the properties that differ from + * the per-property defaults are saved. Format is same as Msodrawing.MSOFBTOPT format + * MSOFBTCOLORMRU (0xF11A) MRU Color swatch ... + * MSOFBTSPLITMENUCOLORS (0xF11E) MRU colors of the top-level ..split menus + * MSOFBTBSTORECONTAINER (0xF001) An array of BLIP Store Entry (BSE) Records; Each shape indexes into this array for the BLIP they use + * MSOFBTBSE (0xF007) File BLIP Store Entry Recod FBSE record; Encodes type of BLIP + size + ID + ref. count + file offset ... + * MSOFBTBLIP (0xF018) + */ + /* BLIP TYPE ENUM + * msoblipERROR= 0 + * msoblipUNKNOWN, + * msoblipEMF, // enhanced meta file + * msoblipWMF, // windows meta file + * msoblipPICT // MAC pic + * msoblipJPEG, + * msoblipPNG, + * msoblipDIB, + * msoblipFirstClient=32, // first client-defined BLIP type + * msoblipLastClient= 255 // last "" + */ + try { + var buf: ByteArray + val bis = ByteArrayInputStream(data!!) + while (bis.available() > 0) { + buf = ByteArray(8) + var read = bis.read(buf, 0, 8) + val version = 0xF and buf[0] + val inst = 0xFF and buf[1] shl 4 or (0xF0 and buf[0] shr 4) + val fbt = 0xFF and buf[3] shl 8 or (0xFF and buf[2]) + val len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]) + + //io.starter.toolkit.Logger.log("fbt:"+Integer.toHexString(fbt)+";len:"+len); + if (fbt < 0xF004) + continue // under 0xF005 are container recs; we just parse the atoms for needed info ... + + // parse record denoted by fbt + buf = ByteArray(len) + read = bis.read(buf, 0, len) + + when (fbt) { + MSODrawingConstants.MSOFBTDGG //0xf006: // MSOFBTDGG - Drawing Group Record + -> { + // rh.recVer A value that MUST be 0x0. + // rh.recInstance A value that MUST be 0x000. + // rh.recType A value that MUST be 0xF006. + // rh.recLen A value that MUST be 0x00000010 + ((head.cidcl - 1) * 0x00000008) + // head (16 bytes): An OfficeArtFDGG record, that specifies document-wide information. + // + // Rgidcl (variable): An array of OfficeArtIDCL elements, specifying file identifier clusters that are used in the drawing. The number of elements in the array is specified by (head.cidcl – 1). + spidMax = ByteTools.readInt(buf[0], buf[1], buf[2], buf[3]) // maximum shape ID + numIdClusters = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]) // number of ID clusters + numShapes = ByteTools.readInt(buf[8], buf[9], buf[10], buf[11]) // total number of shapes saved + numDrawings = ByteTools.readInt(buf[12], buf[13], buf[14], buf[15]) // total number of drawings saved + } + + MSODrawingConstants.MSOFBTBSE //0xf007: // File BLIP Store Entry Record (FBSE) + -> + val imgBuf = getImageBytesWithBuffer(buf) + }// the fixed part is followed by and array of ID clusters used internally for the translation of SPIDs to MSHHSPs (Shape Handles) + // strip buffer from data image bytes + // numShapes--; // 20070914 KSC: WHY?? -- (not a real shape? -jm) + } + } catch (e: Exception) { + Logger.logErr("Msodrawingroup parse error.", e) + } + + } + + private fun getImageBytesWithBuffer(buf: ByteArray): ByteArray { + // Each BLIP in the BStore is serialized to a FBSE record; + // btWin32, btMacOS, rgbUid[16] = identifier of blip, tag, size=BLIP size in stream + val btWin32 = buf[0].toInt() + imageType.add(Integer.valueOf(btWin32)) + /* parse header for testing purposes + // inst= encoded type + int pos= 1; + int btMac= buf[pos++]; + byte[] UID= new byte[16]; + byte[] UID2= new byte[16]; + System.arraycopy(buf, pos, UID, 0, 16); + pos+=16; + short tag= ByteTools.readShort(buf[pos],buf[pos+1]); pos+=2; + int size= ByteTools.readInt(buf[pos],buf[pos+1],buf[pos+2],buf[pos+3]); pos+=4; + int cref= ByteTools.readInt(buf[pos],buf[pos+1],buf[pos+2],buf[pos+3]); pos+=4; + int delayOffset= ByteTools.readInt(buf[pos],buf[pos+1],buf[pos+2],buf[pos+3]); pos+=4; + byte usage= buf[pos++]; + byte nameLen= buf[pos++]; + byte unused1= buf[pos++]; + byte unused2= buf[pos++]; + if (nameLen==0 && delayOffset==0 && buf.length > 36) { + // BLIP record follows then + byte[] BLIPbuf = new byte[24]; + System.arraycopy(buf, pos, BLIPbuf, 0, 24); + int version = (0xF&BLIPbuf[0]); + int inst = ((0xFF&BLIPbuf[1])<<4)|(0xF0&BLIPbuf[0])>>4; + int fbt = ((0xFF&BLIPbuf[3])<<8)|(0xFF&BLIPbuf[2]); + int len = ByteTools.readInt(BLIPbuf[4], BLIPbuf[5], BLIPbuf[6], BLIPbuf[7]); + UID2= new byte[16]; + System.arraycopy(buf, 8, UID2, 0, 16); + } else if (buf.length > 36){ + Logger.logWarn("Delay? " + ((delayOffset==0)?"No":"Yes") + " Namelen=" + nameLen); + } + */ + val ref = ByteTools.readInt(buf[24], buf[25], buf[26], buf[27]) + cRef.add(Integer.valueOf(ref)) + + val HEADERLEN = 61 + var STARTPOS = HEADERLEN + var BYTELEN = buf.size + if (HEADERLEN > BYTELEN) { + BYTELEN = 0 + STARTPOS = 0 + } + BYTELEN -= STARTPOS + + val imgBuf = ByteArray(BYTELEN) + System.arraycopy(buf, STARTPOS, imgBuf, 0, BYTELEN) + + imageData.add(imgBuf) + return imgBuf + } + + /** + * create a new MSODrawingGroup record based upon image datas defined in imageData/imageType/cRef arrays + + * spidMax, numDrawings, numShapes, numIdClusters + * + * + * The squenence of records here are: + * F000, F006, F001, F007(xNumImages), F00B ,F11E + * MSOFBTDGG MSOFBTBSTORECONTAINER MSOFBTBSE (x numimages) + */ + fun updateRecord() { + val BSE = arrayOfNulls(imageData.size) //(0xF007,1,0); + var imageBytes: ByteArray? = null + + var bos: ByteArrayOutputStream? = null + try { + bos = ByteArrayOutputStream() + for (i in imageData.indices) { + BSE[i] = MsofbtBSE(MSODrawingConstants.MSOFBTBSE, Integer.parseInt(imageType.get(i).toString()), 2) + BSE[i].setImageData(imageData.get(i) as ByteArray) + BSE[i].imageType = Integer.parseInt(imageType.get(i).toString()) + BSE[i].refCount = (cRef.get(i) as Int).toInt() // 20071120 KSC: set the reference count for this image data + bos.write(BSE[i].toByteArray()) + } + imageBytes = bos.toByteArray() + } catch (e: Exception) { + Logger.logErr("Msodrawingroup createData error.", e) + } + + val dgg = MsofbtDgg(MSODrawingConstants.MSOFBTDGG, 0, 0) + dgg.setSpidMax(spidMax) // 20071113 KSC + numDrawings = getNumDrawings() // 20100324 KSC: changed from: msoRecs.size(); // 20080908 KSC + dgg.numDrawings = numDrawings + numShapes = getNumShapes() // 20080904 KSC: sum up dg's shapes + dgg.numShapes = numShapes + // 2008003 KSC: numIdClusters is solely a function of spidMax dgg.setNumIdClusters(numIdClusters); + val dggBytes = dgg.toByteArray() + + val OPT = MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3) + // add the apparent basic shape options + OPT.setProperty(MSODrawingConstants.msooptfFitTextToShape, false, false, 0x80008, null) + OPT.setProperty(MSODrawingConstants.msooptfillColor, false, false, 0x8000041, null) + OPT.setProperty(MSODrawingConstants.msooptlineColor, false, false, 0x8000040, null) + val OPTBytes = OPT.toByteArray() + + /* 20070915 KSC not necessary for all msodgs*/ + val SplitMenuColors = MsofbtSplitMenuColors(MSODrawingConstants.MSOFBTSPLITMENUCOLORS, 4, 0) + val SplitMenuColorsBytes = SplitMenuColors.toByteArray() + + var totalLength = imageBytes!!.size + + // 20080910 KSC: if no images, don't input n MSOFBTBSTORE + var BstoreContainerBytes = ByteArray(0) + if (totalLength > 0) { + val BstoreContainer = MsofbtBstoreContainer(MSODrawingConstants.MSOFBTBSTORECONTAINER, imageData.size, 15) + BstoreContainer.length = totalLength + BstoreContainerBytes = BstoreContainer.toByteArray() + + // add up the stuff + totalLength += OPTBytes.size + + SplitMenuColorsBytes.size + + BstoreContainerBytes.size + + dggBytes.size + } else { + // add up the stuff + totalLength += OPTBytes.size + + SplitMenuColorsBytes.size + + dggBytes.size + } + val dggContainer = MsofbtDggContainer(MSODrawingConstants.MSOFBTDGGCONTAINER, 0, 15) + dggContainer.length = totalLength + + val dggContainerBytes = dggContainer.toByteArray() + + var pos = 0 + val retData = ByteArray(totalLength + dggContainerBytes.size) + + System.arraycopy(dggContainerBytes, 0, retData, pos, dggContainerBytes.size) + pos += dggContainerBytes.size + System.arraycopy(dggBytes, 0, retData, pos, dggBytes.size) + pos += dggBytes.size + System.arraycopy(BstoreContainerBytes, 0, retData, pos, BstoreContainerBytes.size) + pos += BstoreContainerBytes.size + // this is the BSE array + System.arraycopy(imageBytes, 0, retData, pos, imageBytes.size) + pos += imageBytes.size + // default OPT + System.arraycopy(OPTBytes, 0, retData, pos, OPTBytes.size) + pos += OPTBytes.size + // 20070915 KSC not truly necessary + System.arraycopy(SplitMenuColorsBytes, 0, retData, pos, SplitMenuColorsBytes.size) + pos += SplitMenuColorsBytes.size + + setData(retData) + } + + + /** + * sets the underlying image bytes + * + * @param bts new image bytes + * @param bs Boundsheet + * @param rec original Msodrawing rec linked to image + * @param name original image name (used for lookups) + * @return + */ + fun setImageBytes(bts: ByteArray, bs: Boundsheet, rec: MSODrawing, name: String): Boolean { + // Find original image handle - often is different than getImageIndex due to reuse, etc. of image bytes + val trueIdx = rec.getImageIndex() - 1 // true index into imageData and cRef arrays + if (trueIdx < 0) + return false + + if (imageData.size <= trueIdx) + return false + + try { + if (getCRef(trueIdx) > 1) { //20080802 KSC: if referenced more than 1x, add new so don't overwrite original + // create new image handle with new bytes + val im = ImageHandle(bts, bs) + // Find original image handle + fill new with original info + var index = -1 + val imgz = bs.images + for (i in imgz!!.indices) { + if (imgz[i].name == name) { + index = i + break + } + } + val origIm = imgz[index] // get original image handle + im.name = origIm.name // set new with original's data + im.shapeName = origIm.shapeName + im.imageType = origIm.imageType + // insert new image into sheet + bs.insertImage(im, true) + im.position(origIm) // position to original + // now remove original mso rec + removeMsodrawingrec(rec, bs, true) + index = imageData.size - 1 // new index + } else + // just set the image bytes + imageData.set(trueIdx, bts) + } catch (ex: Exception) { + Logger.logErr("Msodrawingroup setImageBytes failed.", ex) + return false + } + + updateRecord() + parse() + wkbook!!.initImages() + return true + } + + + /** + * returns the underlying image bytes + * + * @param index + * @return + */ + fun getImageBytes(index: Int): ByteArray? { + if (index < 0) + return null + + if (index >= imageData.size) + return null + + var ret: ByteArray? = null + try { + ret = imageData.get(index) + } catch (ex: Exception) { + Logger.logErr("Msodrawingroup getImageBytes error.", ex) + } + + return ret + + } + + fun getImageType(index: Int): Int { + + return Integer.parseInt(imageType.get(index).toString()) + } + + /** + * related to number of drawing objects (= images + charts) but unclear how the count goes; may include deleted, etc . + * + * @return + */ + fun getNumDrawings(): Int { + // 20100324 KSC: this is experimental as numDrawings do not follow any obvious logic ... + numDrawings = 0 + // 20100511 KSC: this is not correct ... + for (i in msodrawingrecs.indices) { + // 20100518 KSC: try this: + if ((msodrawingrecs[i] as MSODrawing).isHeader) + numDrawings++ + //numDrawings= Math.max(numDrawings, ((Msodrawing)msoRecs.get(i)).getDrawingId()); + } + // if (numDrawings==0 && msoRecs.size() > 0) numDrawings++; + return numDrawings + } + + /** + * count the number of shapes in the document; shape mso's contain a msofbtSpContainer sub-record (TODO: is this true in every case?) + * + * @return + */ + fun getNumShapes(): Int { + /** + * NOTE: I've never found a clear algorithm for numShapes which results in + * matching Excel results; however, + * it appears that numShapes can be >= to Excel's value and open correctly; + * problems occur when numShapes are less than what Excel expects. + * So the below is basically the maximum numShapes available and appears to + * result in templates + */ + /* + numShapes= 1; + for (int i= 0; i < msoRecs.size(); i++) { + MSODrawing mso= ((MSODrawing) msoRecs.get(i)); + if (mso.isShape) + numShapes++; + } + */ + numShapes = msodrawingrecs.size + return numShapes + } + + /** + * test to see if imageData is in imageArray + * + * @param imgData byte[] defining image + * @return + */ + protected fun containsImage(imgData: ByteArray): Int { + var z = -1 + var i = 0 + while (i < imageData.size && z < 0) { + if (java.util.Arrays.equals(imgData, imageData.get(0) as ByteArray)) + z = i + i++ + } + return z + } + + /** + * return the index into the imageData array for the specified image (via byte lookup) + * + * @param imgData image bytes + * @return index into imageData array + */ + fun findImage(imgData: ByteArray): Int { + return containsImage(imgData) + } + + /** + * if imageData doesn't exist, add to array + * otherwise just inc ref count + * + * @param imgData byte[] defining image + * @param imgType type of image + * @param bAddUnconditionally add new even if already referenced (used in setting image bytes) + * @return index to image + */ + fun addImage(imgData: ByteArray, imgType: Int, bAddUnconditionally: Boolean): Int { + var n = -1 + // 20080908 KSC: done automatically numShapes++; // 20080208 KSC: if add unconditionally, add even if imageData already exists + if (bAddUnconditionally || (n = containsImage(imgData)) == -1) { // 20071120 KSC: it's a unique image + imageData.add(imgData) + imageType.add(Integer.valueOf(imgType)) + cRef.add(Integer.valueOf(1)) + n = imageData.size + } else { // 20071120 KSC: If not a unqiue image, just update ref count + incCRef(n) + n++ + } + return n + } + + fun clear() { + numShapes = 0 + imageData.clear() + imageType.clear() + } + + /** + * If large, MSODrawingGroup will span multiple records; merge data + * + * @param rec next MSODrawingGroup record in stream + */ + fun mergeRecords(rec: MSODrawingGroup) { + // Merge and remove continues + if (rec.hasContinues()) { + rec.mergeAndRemoveContinues() // now that data is merged, get rid of continues ... + } + val prevData = this.bytes + val newData = rec.bytes + var totalData = ByteArray(newData!!.size) + if (prevData != null) { // a simple append of the data together + totalData = ByteArray(prevData.size + newData.size) + System.arraycopy(prevData, 0, totalData, 0, prevData.size) + System.arraycopy(newData, 0, totalData, prevData.size, newData.size) + } else + totalData = newData + this.setData(totalData) + } + + /** + * show pertinent information for record + */ + override fun toString(): String { + val sb = StringBuffer() + sb.append("MSODrawingGroup: numShapes=$numShapes numDrawings=$numDrawings numIdCluster=$numIdClusters spidMax=$spidMax") + sb.append("\nNumber of drawing records=" + msodrawingrecs.size) + if (data != null) + sb.append(" Length of data=" + data!!.size) + return sb.toString() + } + + // continue handling + fun mergeAndRemoveContinues() { + if (!this.isContinueMerged && this.hasContinues()) super.mergeContinues() + if (this.hasContinues()) { + // now that data is merged, get rid of continues ... + val it = this.continues!!.iterator() + while (it.hasNext()) { + val ci = it.next() as Continue + this.streamer!!.removeRecord(ci) // remove existing continues from stream + } + super.removeContinues() + this.continues = null + } + } + + /** + * increment reference count for specific image data + * + * @param idx + */ + protected fun incCRef(idx: Int) { + if (idx >= 0 && idx < cRef.size) { + val cr = (cRef.get(idx) as Int).toInt() + 1 + cRef.removeAt(idx) + cRef.add(idx, Integer.valueOf(cr)) + } //else 20071126 KSC: it's OK, can have - indexes ... + //Logger.logErr("Index error encountered when updating Reference Count"); + } + + /** + * return the reference count for the specific image + * + * @param idx + */ + protected fun getCRef(idx: Int): Int { + return if (idx >= 0 && idx < cRef.size) { + (cRef.get(idx) as Int).toInt() + } else -1 + //Logger.logErr("MSODrawingGroup: error encountered when returning Reference Count"); + } + + /** + * decrement the reference count for the specific image + * + * @param idx + */ + protected fun decCRef(idx: Int) { + if (idx >= 0 && idx < cRef.size) { + val cr = (cRef.get(idx) as Int).toInt() - 1 + cRef.removeAt(idx) + cRef.add(idx, Integer.valueOf(cr)) + } else + Logger.logErr("MSODrawingGroup: error encountered when decrementing Reference Count") + } + + /** + * add a new Drawing Record based on existing drawing record + * i.e. from CopyWorkSheet ... + * + * @param spidMax + * @param rec + */ + fun addDrawingRecord(spidMax: Int, rec: MSODrawing) { + this.numDrawings++ + incCRef(rec.imageIndex - 1) // increment cRef + this.spidMax = spidMax + this.updateRecord() // given all information, generate appropriate bytes + } + + /** + * Must ensure that oridinal drawing Id for each drawing record is correct + * Plus ensure SPID's are correct + * + * + * Not default prestreaming as we need these values when we assemble sheet recs + */ + fun prestream() { + var j = 0 + if (dirtyflag) { + for (i in msodrawingrecs.indices) { + val mso = msodrawingrecs[i] as MSODrawing + if (mso.isHeader) { + mso.setDrawingId(++j) + } + } + } + } + + /** + * clear out object references in prep for closing workbook + */ + override fun close() { + super.close() + for (i in msodrawingrecs.indices) { + val m = msodrawingrecs[i] as MSODrawing + m.close() + } + msodrawingrecs.clear() + } + + companion object { + + val prototype: XLSRecord? + get() { + val grp = MSODrawingGroup() + grp.opcode = XLSConstants.MSODRAWINGGROUP + grp.setData(grp.PROTOTYPE_BYTES) + grp.init() + return grp + } + + /** + * + */ + private val serialVersionUID = 2378100973014157878L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/MSODrawingSelection.java b/src/main/java/io/starter/formats/XLS/MSODrawingSelection.kt similarity index 64% rename from src/main/java/io/starter/formats/XLS/MSODrawingSelection.java rename to src/main/java/io/starter/formats/XLS/MSODrawingSelection.kt index e948912..f54570d 100644 --- a/src/main/java/io/starter/formats/XLS/MSODrawingSelection.java +++ b/src/main/java/io/starter/formats/XLS/MSODrawingSelection.kt @@ -20,43 +20,49 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** - * Msodrawingselection: MS Office Drawing Selection (EDh)
                  - *

                  - * These records contain only data.

                  + * **Msodrawingselection: MS Office Drawing Selection (EDh)**

                  + * + * + * These records contain only data. + * + *
                    *
                    * offset  name        size    contents
                    * ---
                    * 4       rgMSODrSelr    var    MSO Drawing Group selectionData
                    *
                  - * 

                  +
                  * * * @see MSODrawing */ -public class MSODrawingSelection extends io.starter.formats.XLS.XLSRecord { +class MSODrawingSelection : io.starter.formats.XLS.XLSRecord() { + var PROTOTYPE_BYTES = byteArrayOf(0, 0, 25, -15, 16, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 1, 4, 0, 0) /** - * + * bypass continue handling for msodrawingselection, until we start + * modifing the record */ - private static final long serialVersionUID = 2799490308252319737L; - public byte[] PROTOTYPE_BYTES = {0, 0, 25, -15, 16, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 1, 4, 0, 0}; + override var data: ByteArray? + get() = super.getData() + set /** * not a lot going on here... */ - public void init() { - super.init(); + override fun init() { + super.init() } - /** - * bypass continue handling for msodrawingselection, until we start - * modifing the record - */ - public byte[] getData() { - return super.getData(); + companion object { + + /** + * + */ + private val serialVersionUID = 2799490308252319737L } } diff --git a/src/main/java/io/starter/formats/XLS/Mergedcells.java b/src/main/java/io/starter/formats/XLS/Mergedcells.java deleted file mode 100644 index e8703c4..0000000 --- a/src/main/java/io/starter/formats/XLS/Mergedcells.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.OpenXLS.WorkSheetHandle; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.util.Iterator; -import java.util.List; - - -/** - * Mergedcells: Merged Cells for Sheet (E5h)
                  - *

                  - * Merged Cells record - * - *

                  - * offset  name            size    contents
                  - * ---
                  - * 0	 					var		BiffRec range addresses
                  - *
                  - * 

                  - **/ -public final class Mergedcells extends io.starter.formats.XLS.XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6638569392267433468L; - public static int MAXRANGES = 1024; - private int nummerges = 0; - private CompatibleVector ranges = new CompatibleVector(); - - public void init() { - super.init(); - if (DEBUGLEVEL > 5) Logger.logInfo("Mergedcells record."); - } - - public static XLSRecord getPrototype() { - Mergedcells newrec = new Mergedcells(); - // for larger records, we need to read in from a file... - newrec.setOpcode(MERGEDCELLS); - newrec.setData(new byte[]{0x0, 0x0, 0x0, 0x0}); - return newrec; - } - - /** - * Un-merge a CellRange - * - * @param rng - */ - public void removeCellRange(CellRange rng) { - this.ranges.remove(rng); - this.update(); - } - - /** - * merge a CellRange - * - * @param rng - */ - public void addCellRange(CellRange rng) { - // rng.setIsmerge(true); - this.ranges.add(rng); - } - - /** - * returns the Merged BiffRec Ranges for this WorkSheet - * - * @return an array of CellRanges each containing Merged Cells. - */ - public CellRange[] getMergedRanges() { - if (ranges.size() < 1) return null; - CellRange[] ret = new CellRange[ranges.size()]; - ranges.toArray(ret); - return ret; - } - - /** - * update the underlying bytes with any new CellRange info - */ - public void update() { - if (ranges.size() > MAXRANGES) { - this.handleMultiRec(); - } - this.nummerges = ranges.size(); - int datasz = nummerges * 8; - datasz += 2; - data = new byte[datasz]; - // get the number of CellRanges - byte[] szbt = ByteTools.shortToLEBytes((short) ranges.size()); - data[0] = szbt[0]; - data[1] = szbt[1]; - int pos = 2; - if (DEBUGLEVEL > DEBUG_LOW) Logger.logInfo("updating Mergedcell with " + nummerges + " merges."); - for (int t = 0; t < ranges.size(); t++) { - CellRange thisrng = (CellRange) ranges.get(t); -// why call this now??? much overhead ... if(!thisrng.update()) -// note: range is updated upon setRange ... -// return; - int[] rints = thisrng.getRowInts(); - int[] cints = thisrng.getColInts(); - - //Logger.logInfo(rints[0]+","); - //for(int x=0;x. + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.WorkBookHandle +import io.starter.OpenXLS.WorkSheetHandle +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + + +/** + * **Mergedcells: Merged Cells for Sheet (E5h)**

                  + * + * + * Merged Cells record + * + * + *
                  + * offset  name            size    contents
                  + * ---
                  + * 0	 					var		BiffRec range addresses
                  + *
                  +
                  * + */ +class Mergedcells : io.starter.formats.XLS.XLSRecord() { + private var nummerges = 0 + private var ranges = CompatibleVector() + + /** + * returns the Merged BiffRec Ranges for this WorkSheet + * + * @return an array of CellRanges each containing Merged Cells. + */ + val mergedRanges: Array? + get() { + if (ranges.size < 1) return null + val ret = arrayOfNulls(ranges.size) + ranges.toTypedArray() + return ret + } + + override fun init() { + super.init() + if (DEBUGLEVEL > 5) Logger.logInfo("Mergedcells record.") + } + + /** + * Un-merge a CellRange + * + * @param rng + */ + fun removeCellRange(rng: CellRange) { + this.ranges.remove(rng) + this.update() + } + + /** + * merge a CellRange + * + * @param rng + */ + fun addCellRange(rng: CellRange) { + // rng.setIsmerge(true); + this.ranges.add(rng) + } + + /** + * update the underlying bytes with any new CellRange info + */ + fun update() { + if (ranges.size > MAXRANGES) { + this.handleMultiRec() + } + this.nummerges = ranges.size + var datasz = nummerges * 8 + datasz += 2 + data = ByteArray(datasz) + // get the number of CellRanges + val szbt = ByteTools.shortToLEBytes(ranges.size.toShort()) + data[0] = szbt[0] + data[1] = szbt[1] + var pos = 2 + if (DEBUGLEVEL > XLSConstants.DEBUG_LOW) Logger.logInfo("updating Mergedcell with $nummerges merges.") + for (t in ranges.indices) { + val thisrng = ranges[t] as CellRange + // why call this now??? much overhead ... if(!thisrng.update()) + // note: range is updated upon setRange ... + // return; + val rints = thisrng.rowInts + val cints = thisrng.colInts + + //Logger.logInfo(rints[0]+","); + //for(int x=0;x. * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; - -import java.util.List; +package io.starter.formats.XLS /** * Muls are used to represent multiple compressed values in a row. * * @see Mulrk + * * @see Mulblank + * * @see Rk + * * @see Blank */ -public interface Mul { +interface Mul { /** - * whether this mul was removed from the SheetRecs already + * return the list of active mulleds + * + * + * Feb 8, 2011 * * @return */ - boolean removed(); + val recs: List<*> /** - * return the list of active mulleds - *

                  - * Feb 8, 2011 + * whether this mul was removed from the SheetRecs already * * @return */ - List getRecs(); + fun removed(): Boolean } diff --git a/src/main/java/io/starter/formats/XLS/Mulblank.java b/src/main/java/io/starter/formats/XLS/Mulblank.java deleted file mode 100644 index 51e5c17..0000000 --- a/src/main/java/io/starter/formats/XLS/Mulblank.java +++ /dev/null @@ -1,422 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ExcelTools; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; - - -/** - * Mulblank: Multiple Blank Cells (BEh)
                  - * This record stores up to 256 BLANK equivalents in - * a space-saving format. - *

                  - * TODO: check compatibility with Excel2007 MAXCOLS - * - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       rw          2       Row Number
                  - * 6       colFirst    2       Column Number of the first col of multiple Blank record
                  - * 8       rgixfe      var     Array of indexes to XF records
                  - * 10      colLast     2       Last Column containing Blank objects
                  - * 

                  - * - * @see Blank - */ - -public final class Mulblank extends XLSCellRecord /*implements Mul*/ { - - private static final long serialVersionUID = 2707362447402042745L; - - short colFirst, colLast; // the colFirst/ColLast indexes determine - byte[] rgixfe; - - public String toString() { - return this.getCellAddress(); - } - - /** - * since this is a "MUL" we override this method to - * get a BiffRec Range, not a BiffRec Address. - */ - public String getCellAddress() { - String retval = "00"; - if (col == -1) { // KSC: if not referring to a single cell - int rownum = getRowNumber() + 1; - retval = ExcelTools.getAlphaVal(colFirst) + rownum; - retval += ":" + ExcelTools.getAlphaVal(colLast) + rownum; - } else { // referring to a single cell - int rownum = getRowNumber() + 1; - retval = ExcelTools.getAlphaVal(col) + rownum; - } - return retval; - } - - /** - * returns the cell address in int[] {row, col} format - */ - public int[] getIntLocation() { - if (col == -1) { - return new int[]{rw, colFirst, rw, colLast}; - } else { - return new int[]{rw, col}; - } - } - - - /** - * return entire range this Mulblank refers to - * - * @return - */ - public String getMulblankRange() { - String retval = "00"; - int rownum = getRowNumber() + 1; - retval = ExcelTools.getAlphaVal(colFirst) + rownum; - retval += ":" + ExcelTools.getAlphaVal(colLast) + rownum; - return retval; - } - - - /** - * set the Boundsheet for the Mulblank - * this is needed because Blanks are BiffRec valrecs and - * need to be assigned a BiffRec in the sheet... - *

                  - * the Mulblank itself does not get a cell. - */ - public void setSheet(Sheet bs) { - this.worksheet = bs; - } - - /** - * initialize this record - */ - public void init() { - data = getData(); - super.init(); - if (this.getLength() - 4 <= 0) { - if (DEBUGLEVEL > -1) - Logger.logInfo("no data in MULBLANK"); - } else { - rw = ByteTools.readUnsignedShort(this.getByteAt(0), this.getByteAt(1)); - colFirst = - ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - //col = colFirst; - col = -1; // flag that this rec hasn't been referred to one cell - colLast = - ByteTools.readShort( - this.getByteAt(this.reclen - 2), - this.getByteAt(this.reclen - 1)); - // Sometimes colFirst & colLast are reversed... WTFM$? -jm - if (colLast < colFirst) { - short csav = colLast; - colLast = colFirst; - colFirst = csav; - colLast--; - } - if (DEBUGLEVEL > 5) - Logger.logInfo( - "INFO: MULBLANK range: " + colFirst + ":" + colLast); - int numblanks = colLast - colFirst + 1; -// blanks = new ArrayList(); - if (numblanks < 1) { - Logger.logWarn( - "WARNING: could not parse Mulblank record: numblanks reported as:" - + numblanks); - //Logger.logInfo((numblanks >> 12)*-1); ha! - return; - } - rgixfe = this.getBytesAt(4, numblanks * 2); - } - // KSC: to use as a blank: - this.setIsValueForCell(true); - this.isBlank = true; - } - - /** - * returnt the "current" column indicator, if set - */ - public short getColNumber() { - if (col != -1) - return col; - return colFirst; - } - - /** - * reset the "current" column use to reference a single blank of this Mulblank range of blank cells - * - * @return - */ - public void resetCol() { - col = colFirst; - } - - /** - * sets the first column of the range of blank cells referenced by this Mulblank - * - * @param c - */ - public void setColFirst(int c) { - colFirst = (short) c; - } - - /** - * sets the last column of the range of blank cells referenced by this Mulblank - * - * @param c - */ - public void setColLast(int c) { - colLast = (short) c; - } - - /** - * return sthe first column of the range of blank cells referenced by this Mulblank - * - * @return - */ - public int getColFirst() { - return colFirst; - } - - /** - * return sthe last column of the range of blank cells referenced by this Mulblank - * - * @return - */ - public int getColLast() { - return colLast; - } - - /** - * revise range of cells this Mulblank refers to; return true if no more blanks in range - * - * @param c col number to remove, 0-based - */ - public boolean removeCell(short c) { - if (c == colFirst) { - colFirst++; - byte[] tmp = new byte[rgixfe.length - 2]; - System.arraycopy(rgixfe, 2, tmp, 0, tmp.length); // skip first - rgixfe = tmp; - } else if (c == colLast) { - colLast--; - byte[] tmp = new byte[rgixfe.length - 2]; - System.arraycopy(rgixfe, 0, tmp, 0, tmp.length); // skip last - rgixfe = tmp; - } - if (c > colFirst && c < colLast) { - // must break apart Mulblank as now is non-contiguous ... - // keep first colFirst->c as a MulBlank - try { - // create the blank records - for (int i = c + 1; i <= colLast; i++) { - byte[] newblank = {0, 0, 0, 0, 0, 0}; - // set the row... - System.arraycopy(this.getBytesAt(0, 2), 0, newblank, 0, 2); - // set the col... - System.arraycopy(ByteTools.shortToLEBytes((short) i), 0, newblank, 2, 2); - // set the ixfe - System.arraycopy(rgixfe, ((i - colFirst) * 2), newblank, 4, 2); - Blank b = new Blank(newblank); - b.streamer = this.streamer; - b.setWorkBook(this.getWorkBook()); - b.setSheet(this.getSheet()); - b.setMergeRange(this.getMergeRange(i - colFirst)); - this.getRow().removeCell((short) i);// remove this mulblank from the cells array - this.getWorkBook().addRecord(b, true); // and add a blank in it's place - } - // truncate the rgixfe: - byte[] tmp = new byte[(2 * (c - colFirst + 1))]; - System.arraycopy(rgixfe, 0, tmp, 0, tmp.length); // skip last - rgixfe = tmp; - // now truncate the Mulblank - colLast = (short) (c - 1); - } catch (Exception e) { - Logger.logInfo("initializing Mulblank failed: " + e); - } - col = c; // the blank to remove - } - if (colFirst < 0 || colLast < 0) { // can happen when removing multiple cells ..? - return true; - } - if (colFirst == colLast) {// covert to a single blank - byte[] newblank = {0, 0, 0, 0, 0, 0}; - // set the row... - System.arraycopy(this.getBytesAt(0, 2), 0, newblank, 0, 2); - // set the col... - System.arraycopy(ByteTools.shortToLEBytes(colFirst), 0, newblank, 2, 2); - // set the ixfe - System.arraycopy(rgixfe, 0, newblank, 4, 2); - Blank b = new Blank(newblank); - b.streamer = this.streamer; - b.setWorkBook(this.getWorkBook()); - b.setSheet(this.getSheet()); - b.setMergeRange(this.getMergeRange(colFirst)); - col = colFirst; - this.getRow().removeCell(this);// remove this mulblank from the cells array - this.getWorkBook().addRecord(b, true); - col = c; // still have to remove cell at col c - return false; // no more mulblanks - } - updateRecord(); - // no more blanks in range ... can happen?? - return colFirst > colLast; // can delete it -// don't delete this rec - } - - - /** - * return a blank string val - */ - public String getStringVal() { - return ""; - } - - /** - * used to set the cell which this will be referred to, used when trying to access - * ixfe - * - * @param c - */ - public void setCurrentCell(short c) { - col = c; - } - - /** - * NOTE: Mublanks can have a portion of it's blank range which is merged: must determine if - * the current cell is truly part of the merge range ... - * - * @return - */ - public CellRange getMergeRange() { - if (mergeRange == null) return null; - if (col == -1) - return mergeRange; // this shouldn't happen ... - if (mergeRange.contains(new int[]{getRowNumber(), col, getRowNumber(), col})) - return mergeRange; - return null; // desired cell is NOT contained within master merge range - } - - /** - * retrieves the merged range for the desired cell in this group of blanks - * - * @param col - * @return - */ - private CellRange getMergeRange(int col) { - if (mergeRange == null) return null; - if (col == -1) - return mergeRange; // this shouldn't happen ... - if (mergeRange.contains(new int[]{getRowNumber(), col, getRowNumber(), col})) - return mergeRange; - return null; // desired cell is NOT contained within master merge range - } - - /** - * get the ixfe for the desired referred-to blank - */ - public int getIxfe() { - int idx = 0; - if (col != -1 && col >= colFirst && col <= colLast) - idx = (col - colFirst) * 2; - ixfe = ByteTools.readShort(rgixfe[idx], rgixfe[idx + 1]); - myxf = this.getWorkBook().getXf(ixfe); // set myxf to correct xf for cell in group of mulblanks - return this.ixfe; - } - - /** - * Get the referenced columns this mulblank has, - */ - public ArrayList getColReferences() { - ArrayList colRefs = new ArrayList(); - for (int i = this.colFirst; i <= this.colLast; i++) { - colRefs.add(i); - } - return colRefs; - } - - /** - * sets the ixfe for the specific cell of the Mulblank - * (each cell in a series of multiple blanks has their own ixfe) - */ - public void setIxfe(int i) { - int idx = 0; - if (col != -1 && col >= colFirst && col <= colLast) - idx = (col - colFirst) * 2; - - byte[] b = ByteTools.shortToLEBytes((short) i); - rgixfe[idx] = b[0]; - rgixfe[idx + 1] = b[1]; - updateRecord(); - ixfe = i; - myxf = this.getWorkBook().getXf(ixfe); - } - - /** - * set the column - */ - public void setCol(short i) { - col = i; - } - - /** - * given new info (colFirst, colLast and rgixfe) update data record - */ - private void updateRecord() { - byte[] data = new byte[2 + 2 + 2 + rgixfe.length]; - data[0] = this.getData()[0]; // row shouldn't have changed - data[1] = this.getData()[1]; - byte[] b = ByteTools.shortToLEBytes(colFirst); - data[2] = b[0]; - data[3] = b[1]; - // after colfirst= rgixfe - System.arraycopy(rgixfe, 0, data, 4, rgixfe.length); - b = ByteTools.shortToLEBytes(colLast); - data[4 + rgixfe.length] = b[0]; - data[5 + rgixfe.length] = b[1]; - setData(data); - } - - public static XLSRecord getPrototype() { - Mulblank mb = new Mulblank(); - mb.setOpcode(MULBLANK); - mb.setData(new byte[]{0, 0, 0, 0, 0, 0}); - mb.col = -1; - return mb; - } - - /** - * returns the number of ixfe fields - */ - int getNumFields() { - return (colLast - colFirst) + 1; - } - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Mulblank.kt b/src/main/java/io/starter/formats/XLS/Mulblank.kt new file mode 100644 index 0000000..319540d --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Mulblank.kt @@ -0,0 +1,430 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ExcelTools +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.ArrayList + + +/** + * **Mulblank: Multiple Blank Cells (BEh)**

                  + * This record stores up to 256 BLANK equivalents in + * a space-saving format. + * + * + * TODO: check compatibility with Excel2007 MAXCOLS + * + * + *

                  + * offset  name        size    contents
                  + * ---
                  + * 4       rw          2       Row Number
                  + * 6       colFirst    2       Column Number of the first col of multiple Blank record
                  + * 8       rgixfe      var     Array of indexes to XF records
                  + * 10      colLast     2       Last Column containing Blank objects
                  +
                  * + * + * @see Blank + */ + +class Mulblank : XLSCellRecord /*implements Mul*/() { + + internal var colFirst: Short = 0 + internal var colLast: Short = 0 // the colFirst/ColLast indexes determine + internal var rgixfe: ByteArray? = null + + /** + * since this is a "MUL" we override this method to + * get a BiffRec Range, not a BiffRec Address. + */ + override// KSC: if not referring to a single cell + // referring to a single cell + val cellAddress: String + get() { + var retval = "00" + if (colNumber.toInt() == -1) { + val rownum = rowNumber + 1 + retval = ExcelTools.getAlphaVal(colFirst.toInt()) + rownum + retval += ":" + ExcelTools.getAlphaVal(colLast.toInt()) + rownum + } else { + val rownum = rowNumber + 1 + retval = ExcelTools.getAlphaVal(colNumber.toInt()) + rownum + } + return retval + } + + /** + * returns the cell address in int[] {row, col} format + */ + override val intLocation: IntArray + get() = if (colNumber.toInt() == -1) { + intArrayOf(rw, colFirst.toInt(), rw, colLast.toInt()) + } else { + intArrayOf(rw, colNumber.toInt()) + } + + + /** + * return entire range this Mulblank refers to + * + * @return + */ + val mulblankRange: String + get() { + var retval = "00" + val rownum = rowNumber + 1 + retval = ExcelTools.getAlphaVal(colFirst.toInt()) + rownum + retval += ":" + ExcelTools.getAlphaVal(colLast.toInt()) + rownum + return retval + } + + /** + * returnt the "current" column indicator, if set + */ + override var colNumber: Short + get() = if (colNumber.toInt() != -1) colNumber else colFirst + + + /** + * return a blank string val + */ + override var stringVal: String + get() = "" + set + + /** + * NOTE: Mublanks can have a portion of it's blank range which is merged: must determine if + * the current cell is truly part of the merge range ... + * + * @return + */ + override// this shouldn't happen ... + // desired cell is NOT contained within master merge range + var mergeRange: CellRange? + get() { + if (mergeRange == null) return null + if (colNumber.toInt() == -1) + return mergeRange + return if (mergeRange!!.contains(intArrayOf(rowNumber, colNumber.toInt(), rowNumber, colNumber.toInt()))) mergeRange else null + } + set + + /** + * get the ixfe for the desired referred-to blank + */ + /** + * sets the ixfe for the specific cell of the Mulblank + * (each cell in a series of multiple blanks has their own ixfe) + */ + override// set myxf to correct xf for cell in group of mulblanks + var ixfe: Int + get() { + var idx = 0 + if (colNumber.toInt() != -1 && colNumber >= colFirst && colNumber <= colLast) + idx = (colNumber - colFirst) * 2 + ixfe = ByteTools.readShort(rgixfe!![idx].toInt(), rgixfe!![idx + 1].toInt()).toInt() + myxf = this.workBook!!.getXf(ixfe) + return this.ixfe + } + set(i) { + var idx = 0 + if (colNumber.toInt() != -1 && colNumber >= colFirst && colNumber <= colLast) + idx = (colNumber - colFirst) * 2 + + val b = ByteTools.shortToLEBytes(i.toShort()) + rgixfe[idx] = b[0] + rgixfe[idx + 1] = b[1] + updateRecord() + ixfe = i + myxf = this.workBook!!.getXf(ixfe) + } + + /** + * Get the referenced columns this mulblank has, + */ + val colReferences: ArrayList + get() { + val colRefs = ArrayList() + for (i in this.colFirst..this.colLast) { + colRefs.add(i) + } + return colRefs + } + + /** + * returns the number of ixfe fields + */ + internal val numFields: Int + get() = colLast - colFirst + 1 + + override fun toString(): String { + return this.cellAddress + } + + + /** + * set the Boundsheet for the Mulblank + * this is needed because Blanks are BiffRec valrecs and + * need to be assigned a BiffRec in the sheet... + * + * + * the Mulblank itself does not get a cell. + */ + override fun setSheet(bs: Sheet?) { + this.worksheet = bs + } + + /** + * initialize this record + */ + override fun init() { + data = getData() + super.init() + if (this.length - 4 <= 0) { + if (DEBUGLEVEL > -1) + Logger.logInfo("no data in MULBLANK") + } else { + rw = ByteTools.readUnsignedShort(this.getByteAt(0), this.getByteAt(1)) + colFirst = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + //col = colFirst; + colNumber = -1 // flag that this rec hasn't been referred to one cell + colLast = ByteTools.readShort( + this.getByteAt(this.reclen - 2).toInt(), + this.getByteAt(this.reclen - 1).toInt()) + // Sometimes colFirst & colLast are reversed... WTFM$? -jm + if (colLast < colFirst) { + val csav = colLast + colLast = colFirst + colFirst = csav + colLast-- + } + if (DEBUGLEVEL > 5) + Logger.logInfo( + "INFO: MULBLANK range: $colFirst:$colLast") + val numblanks = colLast - colFirst + 1 + // blanks = new ArrayList(); + if (numblanks < 1) { + Logger.logWarn( + "WARNING: could not parse Mulblank record: numblanks reported as:$numblanks") + //Logger.logInfo((numblanks >> 12)*-1); ha! + return + } + rgixfe = this.getBytesAt(4, numblanks * 2) + } + // KSC: to use as a blank: + this.isValueForCell = true + this.isBlank = true + } + + /** + * reset the "current" column use to reference a single blank of this Mulblank range of blank cells + * + * @return + */ + fun resetCol() { + colNumber = colFirst + } + + /** + * sets the first column of the range of blank cells referenced by this Mulblank + * + * @param c + */ + fun setColFirst(c: Int) { + colFirst = c.toShort() + } + + /** + * sets the last column of the range of blank cells referenced by this Mulblank + * + * @param c + */ + fun setColLast(c: Int) { + colLast = c.toShort() + } + + /** + * return sthe first column of the range of blank cells referenced by this Mulblank + * + * @return + */ + override fun getColFirst(): Int { + return colFirst.toInt() + } + + /** + * return sthe last column of the range of blank cells referenced by this Mulblank + * + * @return + */ + override fun getColLast(): Int { + return colLast.toInt() + } + + /** + * revise range of cells this Mulblank refers to; return true if no more blanks in range + * + * @param c col number to remove, 0-based + */ + fun removeCell(c: Short): Boolean { + if (c == colFirst) { + colFirst++ + val tmp = ByteArray(rgixfe!!.size - 2) + System.arraycopy(rgixfe!!, 2, tmp, 0, tmp.size) // skip first + rgixfe = tmp + } else if (c == colLast) { + colLast-- + val tmp = ByteArray(rgixfe!!.size - 2) + System.arraycopy(rgixfe!!, 0, tmp, 0, tmp.size) // skip last + rgixfe = tmp + } + if (c > colFirst && c < colLast) { + // must break apart Mulblank as now is non-contiguous ... + // keep first colFirst->c as a MulBlank + try { + // create the blank records + for (i in c + 1..colLast) { + val newblank = byteArrayOf(0, 0, 0, 0, 0, 0) + // set the row... + System.arraycopy(this.getBytesAt(0, 2)!!, 0, newblank, 0, 2) + // set the col... + System.arraycopy(ByteTools.shortToLEBytes(i.toShort()), 0, newblank, 2, 2) + // set the ixfe + System.arraycopy(rgixfe!!, (i - colFirst) * 2, newblank, 4, 2) + val b = Blank(newblank) + b.streamer = this.streamer + b.workBook = this.workBook + b.setSheet(this.sheet) + b.mergeRange = this.getMergeRange(i - colFirst) + this.row.removeCell(i.toShort())// remove this mulblank from the cells array + this.workBook!!.addRecord(b, true) // and add a blank in it's place + } + // truncate the rgixfe: + val tmp = ByteArray(2 * (c - colFirst + 1)) + System.arraycopy(rgixfe!!, 0, tmp, 0, tmp.size) // skip last + rgixfe = tmp + // now truncate the Mulblank + colLast = (c - 1).toShort() + } catch (e: Exception) { + Logger.logInfo("initializing Mulblank failed: $e") + } + + colNumber = c // the blank to remove + } + if (colFirst < 0 || colLast < 0) { // can happen when removing multiple cells ..? + return true + } + if (colFirst == colLast) {// covert to a single blank + val newblank = byteArrayOf(0, 0, 0, 0, 0, 0) + // set the row... + System.arraycopy(this.getBytesAt(0, 2)!!, 0, newblank, 0, 2) + // set the col... + System.arraycopy(ByteTools.shortToLEBytes(colFirst), 0, newblank, 2, 2) + // set the ixfe + System.arraycopy(rgixfe!!, 0, newblank, 4, 2) + val b = Blank(newblank) + b.streamer = this.streamer + b.workBook = this.workBook + b.setSheet(this.sheet) + b.mergeRange = this.getMergeRange(colFirst.toInt()) + colNumber = colFirst + this.row.removeCell(this)// remove this mulblank from the cells array + this.workBook!!.addRecord(b, true) + colNumber = c // still have to remove cell at col c + return false // no more mulblanks + } + updateRecord() + // no more blanks in range ... can happen?? + return colFirst > colLast // can delete it + // don't delete this rec + } + + /** + * used to set the cell which this will be referred to, used when trying to access + * ixfe + * + * @param c + */ + fun setCurrentCell(c: Short) { + colNumber = c + } + + /** + * retrieves the merged range for the desired cell in this group of blanks + * + * @param col + * @return + */ + private fun getMergeRange(col: Int): CellRange? { + if (mergeRange == null) return null + if (col == -1) + return mergeRange // this shouldn't happen ... + return if (mergeRange!!.contains(intArrayOf(rowNumber, col, rowNumber, col))) mergeRange else null +// desired cell is NOT contained within master merge range + } + + /** + * set the column + */ + override fun setCol(i: Short) { + colNumber = i + } + + /** + * given new info (colFirst, colLast and rgixfe) update data record + */ + private fun updateRecord() { + val data = ByteArray(2 + 2 + 2 + rgixfe!!.size) + data[0] = this.getData()!![0] // row shouldn't have changed + data[1] = this.getData()!![1] + var b = ByteTools.shortToLEBytes(colFirst) + data[2] = b[0] + data[3] = b[1] + // after colfirst= rgixfe + System.arraycopy(rgixfe!!, 0, data, 4, rgixfe!!.size) + b = ByteTools.shortToLEBytes(colLast) + data[4 + rgixfe!!.size] = b[0] + data[5 + rgixfe!!.size] = b[1] + setData(data) + } + + companion object { + + private val serialVersionUID = 2707362447402042745L + + val prototype: XLSRecord? + get() { + val mb = Mulblank() + mb.opcode = XLSConstants.MULBLANK + mb.setData(byteArrayOf(0, 0, 0, 0, 0, 0)) + mb.colNumber = -1 + return mb + } + } + + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Mulled.java b/src/main/java/io/starter/formats/XLS/Mulled.kt similarity index 84% rename from src/main/java/io/starter/formats/XLS/Mulled.java rename to src/main/java/io/starter/formats/XLS/Mulled.kt index 6803e9c..d3dfc3e 100644 --- a/src/main/java/io/starter/formats/XLS/Mulled.java +++ b/src/main/java/io/starter/formats/XLS/Mulled.kt @@ -20,22 +20,23 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * Muls are used to represent multiple compressed values in a row. * * @see Mulrk + * * @see Mulblank + * * @see Rk + * * @see Blank */ -public interface Mulled extends BiffRec { - - void setMyMul(Mul m); +interface Mulled : BiffRec { - Mul getMyMul(); + var myMul: Mul - void setNoMul(); + fun setNoMul() } diff --git a/src/main/java/io/starter/formats/XLS/Mulrk.java b/src/main/java/io/starter/formats/XLS/Mulrk.java deleted file mode 100644 index d7254d0..0000000 --- a/src/main/java/io/starter/formats/XLS/Mulrk.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - - -/** - * Mulrk: Multiple Rk Cells (BDh)
                  - * This record stores up to 256 Rk equivalents in - *

                  - * TODO: check compatibility with Excel2007 MAXCOLS - *

                  - * a space-saving format. - * offset name size contents - * --- - * 4 rw 2 Row Number - * 6 colFirst 2 Column Number of the first col of multiple Rk record - * 8 rgrkrec var Array of 6-byte RkREC objects - * var colLast 2 Last Column containing the RkREC object - *

                  - * - * @see Rk - */ - -public final class Mulrk extends io.starter.formats.XLS.XLSRecord implements Mul { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1438740082267768419L; - boolean removed = false; - - /** - * whether this mul was removed from the SheetRecs already - * - * @return - */ - public boolean removed() { - return removed; - } - - short colFirst; - int colLast; - int datalen; - int numRkRecs = 0; - List rkrecs; - - Mulrk() { - super(); - } - - /** - * populate the MULRk with its data, as well as creating - * multiple Rk records per the Rk array. - **/ - public void init() { - super.init(); - int datalen = this.getData().length; //getLength(); - - if (datalen <= 0) { - if (DEBUGLEVEL > -1) Logger.logInfo("no data in MULRk"); - } else { - super.initRowCol(); - short s = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - colFirst = s; - col = colFirst; - s = ByteTools.readShort(this.getByteAt(datalen - 2), this.getByteAt(datalen - 1)); - colLast = (int) s; - // get the records data only - datalen = datalen - 6; - byte[] rkdatax = this.getBytesAt(4, datalen); - numRkRecs = datalen / 6; - //rkrecs = new Rk[numRkRecs]; Now its a vector - rkrecs = new ArrayList(numRkRecs); - int reccount = 0; - int rkcol = col; - // iterate through the rk data array and create - // a new 6-byte Rk for each. - for (int i = 4; i < rkdatax.length; ) { - byte[] rkd = this.getBytesAt(i, 6); - Rk r = new Rk(); - r.init(rkd, rw, rkcol++); - if (DEBUGLEVEL > 5) Logger.logInfo(" rk@" + (rkcol - 1) + ":" + r.getStringVal()); - i += 6; - if (reccount == numRkRecs) break; - r.setMyMul(this); - r.setSheet(getSheet()); - r.streamer = streamer; - rkrecs.add(r); - reccount++; - } - if (DEBUGLEVEL > 5) Logger.logInfo("Done adding Rk recs to: " + this.getCellAddress()); - } - } - - void deleteRk(Rk rik) { - rkrecs.remove(rik); - } - - void addRk(Rk rik) { - rkrecs.add(rik); - } - - int getColFirst() { - return colFirst; - } - - public List getRecs() { - return rkrecs; - } - - /** - * get a handle to a specific Rk for use in updating values - */ - Rk getRk(int rnum) { - return (Rk) rkrecs.get(rnum); - } - - /* - Changes the range of - **/ - public Mulrk splitMulrk(int splitcol) { - if (splitcol < colFirst || splitcol > colLast) return null; - Mulrk newmul = new Mulrk(); - newmul.colFirst = (short) splitcol; - newmul.colLast = this.colLast; - this.colLast = splitcol - 1; - Iterator rkr = this.getRecs().iterator(); - while (rkr.hasNext()) { - Rk r = (Rk) rkr.next(); - if (r.getRowNumber() >= splitcol) { - this.deleteRk(r); - newmul.addRk(r); - } - } - newmul.setOpcode(getOpcode()); - newmul.setLength(getLength()); - return newmul; - } - - - /** - * Remove an Rk from the record along with all of the - * folloing Rks. Returns a CompatableVector - * of RKs that have been cut off from the Mulrk. - * this is kinda deprecated because of the splitMulrk(), - * but could prove to be useful later... - */ - CompatibleVector removeRk(Rk rok) { - CompatibleVector rez = new CompatibleVector(); - // set the new last col of the Mulrk - colLast = (rok.getColNumber() - 1); - rkrecs.remove(rok); - int z = rkrecs.size() - 1; - for (int i = z; i >= 0; i--) { - Rk rec = (Rk) rkrecs.get(i); - if (rec.getColNumber() > colLast) { - rez.add(rec); - rkrecs.remove(i); - } - } - this.updateRks(); - return rez; - } - - /** - * set the row - */ - public void setRow(int i) { - byte[] r = ByteTools.shortToLEBytes((short) i); - System.arraycopy(r, 0, this.getData(), 0, 2); - rw = i; - } - - /** - * Update the underlying byte array for the MULRk record - * after changes have been made to individual Rk records. - */ - void updateRks() { - if (this.getRecs().size() < 1) { - this.getSheet().removeRecFromVec(this); - return; - } - byte[] tmp = new byte[4]; - System.arraycopy(getData(), 0, tmp, 0, 4); - Iterator it = this.getRecs().iterator(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try { - out.write(tmp); - // loop through the Rks and copy their bytes to the MULRk byte array. - while (it.hasNext()) { - out.write(((Rk) it.next()).getBytes()); - } - out.write(ByteTools.shortToLEBytes((short) colLast)); - } catch (IOException a) { - Logger.logInfo("parsing record continues failed: " + a); - } - this.setData(out.toByteArray()); - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Mulrk.kt b/src/main/java/io/starter/formats/XLS/Mulrk.kt new file mode 100644 index 0000000..8c30e62 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Mulrk.kt @@ -0,0 +1,229 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.util.ArrayList + + +/** + * **Mulrk: Multiple Rk Cells (BDh)**

                  + * This record stores up to 256 Rk equivalents in + * + * + * TODO: check compatibility with Excel2007 MAXCOLS + * + * + * a space-saving format. + * offset name size contents + * --- + * 4 rw 2 Row Number + * 6 colFirst 2 Column Number of the first col of multiple Rk record + * 8 rgrkrec var Array of 6-byte RkREC objects + * var colLast 2 Last Column containing the RkREC object + * + * + * @see Rk + */ + +class Mulrk internal constructor() : io.starter.formats.XLS.XLSRecord(), Mul { + internal var removed = false + + internal var colFirst: Short = 0 + internal var colLast: Int = 0 + internal var datalen: Int = 0 + internal var numRkRecs = 0 + internal var rkrecs: MutableList<*> + + override val recs: List<*> + get() = rkrecs + + /** + * whether this mul was removed from the SheetRecs already + * + * @return + */ + override fun removed(): Boolean { + return removed + } + + /** + * populate the MULRk with its data, as well as creating + * multiple Rk records per the Rk array. + */ + override fun init() { + super.init() + var datalen = this.getData()!!.size //getLength(); + + if (datalen <= 0) { + if (DEBUGLEVEL > -1) Logger.logInfo("no data in MULRk") + } else { + super.initRowCol() + var s = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + colFirst = s + colNumber = colFirst + s = ByteTools.readShort(this.getByteAt(datalen - 2).toInt(), this.getByteAt(datalen - 1).toInt()) + colLast = s.toInt() + // get the records data only + datalen = datalen - 6 + val rkdatax = this.getBytesAt(4, datalen) + numRkRecs = datalen / 6 + //rkrecs = new Rk[numRkRecs]; Now its a vector + rkrecs = ArrayList(numRkRecs) + var reccount = 0 + var rkcol = colNumber.toInt() + // iterate through the rk data array and create + // a new 6-byte Rk for each. + var i = 4 + while (i < rkdatax!!.size) { + val rkd = this.getBytesAt(i, 6) + val r = Rk() + r.init(rkd, rw, rkcol++) + if (DEBUGLEVEL > 5) Logger.logInfo(" rk@" + (rkcol - 1) + ":" + r.stringVal) + i += 6 + if (reccount == numRkRecs) break + r.myMul = this + r.setSheet(sheet) + r.streamer = streamer + rkrecs.add(r) + reccount++ + } + if (DEBUGLEVEL > 5) Logger.logInfo("Done adding Rk recs to: " + this.cellAddress) + } + } + + internal fun deleteRk(rik: Rk) { + rkrecs.remove(rik) + } + + internal fun addRk(rik: Rk) { + rkrecs.add(rik) + } + + internal fun getColFirst(): Int { + return colFirst.toInt() + } + + /** + * get a handle to a specific Rk for use in updating values + */ + internal fun getRk(rnum: Int): Rk { + return rkrecs[rnum] as Rk + } + + /* + Changes the range of + **/ + fun splitMulrk(splitcol: Int): Mulrk? { + if (splitcol < colFirst || splitcol > colLast) return null + val newmul = Mulrk() + newmul.colFirst = splitcol.toShort() + newmul.colLast = this.colLast + this.colLast = splitcol - 1 + val rkr = this.recs.iterator() + while (rkr.hasNext()) { + val r = rkr.next() as Rk + if (r.rowNumber >= splitcol) { + this.deleteRk(r) + newmul.addRk(r) + } + } + newmul.opcode = opcode + newmul.length = length + return newmul + } + + + /** + * Remove an Rk from the record along with all of the + * folloing Rks. Returns a CompatableVector + * of RKs that have been cut off from the Mulrk. + * this is kinda deprecated because of the splitMulrk(), + * but could prove to be useful later... + */ + internal fun removeRk(rok: Rk): CompatibleVector { + val rez = CompatibleVector() + // set the new last col of the Mulrk + colLast = rok.colNumber - 1 + rkrecs.remove(rok) + val z = rkrecs.size - 1 + for (i in z downTo 0) { + val rec = rkrecs[i] as Rk + if (rec.colNumber > colLast) { + rez.add(rec) + rkrecs.removeAt(i) + } + } + this.updateRks() + return rez + } + + /** + * set the row + */ + fun setRow(i: Int) { + val r = ByteTools.shortToLEBytes(i.toShort()) + System.arraycopy(r, 0, this.getData()!!, 0, 2) + rw = i + } + + /** + * Update the underlying byte array for the MULRk record + * after changes have been made to individual Rk records. + */ + internal fun updateRks() { + if (this.recs.size < 1) { + this.sheet!!.removeRecFromVec(this) + return + } + val tmp = ByteArray(4) + System.arraycopy(getData()!!, 0, tmp, 0, 4) + val it = this.recs.iterator() + val out = ByteArrayOutputStream() + try { + out.write(tmp) + // loop through the Rks and copy their bytes to the MULRk byte array. + while (it.hasNext()) { + out.write((it.next() as Rk).bytes!!) + } + out.write(ByteTools.shortToLEBytes(colLast.toShort())) + } catch (a: IOException) { + Logger.logInfo("parsing record continues failed: $a") + } + + this.setData(out.toByteArray()) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 1438740082267768419L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Name.java b/src/main/java/io/starter/formats/XLS/Name.java deleted file mode 100644 index a6825dd..0000000 --- a/src/main/java/io/starter/formats/XLS/Name.java +++ /dev/null @@ -1,1028 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Stack; - - -/** - * Name: Defined Name (218h)
                  - *

                  - * Name records describe a name in the workbook - * - *

                  - * offset  name            size    contents
                  - * ---
                  - * 4       grbit           2       option flags
                  - * 6       chKey           1       Keyboard Shortcut
                  - * 7       cch             1       length of name text
                  - * 8       cce             2       length of name definition *stored in Excel parsed format
                  - * 10      ixals           2       index to sheet containing name
                  - * 12      itab            2       NAME SCOPE -- 0= workbook, 1+= sheet
                  - * 14      cchCustMenu     1       length of custom menu text
                  - * 15      cchDescript     1       length of description text
                  - * 16      cchHelpTopic    1       length of help topic text
                  - * 17      cchStatusText   1       length of status bar text
                  - * 18      rgch            var     name text
                  - * var     rgce            var     name definition
                  - * var     rcchCustMenu    var     cust menu text
                  - * var     rgchDescr       var     description text
                  - * var     rgchHelpTopic   var     help text
                  - * var     rgchStatusText  var     status bar text
                  - *
                  - * 

                  - **/ -public final class Name extends XLSRecord { - - /** - * - */ - private static final long serialVersionUID = -7868028144327389601L; - short grbit = -1; - boolean DEBUG = false; - boolean builtIn = false; - String rgch = ""; // name text - String rgce = ""; // name definition - String rcchCustMenu = ""; // cust menu text - String rgchDescr = ""; // description text - String rgchHelpTopic = ""; // help text - String rgchStatusText = ""; // status bar text - byte chKey = -1; - byte cch = -1; - short cce = -1; // 2 - short ixals = -1; // 2 - short itab = -1; // 2 - byte cchCustMenu = -1; - byte cchDescript = -1; - byte cchHelpTopic = -1; - byte cchStatusText = -1; - public byte builtInType = -1; - private Stack expression = null; - Externsheet externsheet; - private Ptg ptga; - private ArrayList ilblListeners = new ArrayList(); - private String cachedOOXMLExpression = null; - private byte[] expressionbytes = null; // for deferred Name expression init - - protected static final byte CONSOLIDATE_AREA = 0x0; - protected static final byte AUTO_OPEN = 0x1; - protected static final byte AUTO_CLOSE = 0x2; - protected static final byte EXTRACT = 0x3; - protected static final byte DATABASE = 0x4; - protected static final byte CRITERIA = 0x5; - protected static final byte PRINT_AREA = 0x6; - protected static final byte PRINT_TITLES = 0x7; - protected static final byte RECORDER = 0x8; - protected static final byte DATA_FORM = 0x9; - protected static final byte AUTO_ACTIVATE = 0xA; - protected static final byte AUTO_DEACTIVATE = 0xB; - protected static final byte SHEET_TITLE = 0xC; - protected static final byte _FILTER_DATABASE = 0xD; - - /* this byte array differs from the prototype in that it has no description field. It is - used by formulas inserting names that don't exist within the workbook. The name will - show up in the formula, but nowhere else on the excel file - **/ - private byte[] FILLER_NAME_BYTES = {0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; - - // 20090811 KSC: this prototype contains a range; clear out private byte[] PROTOTYPE_NAME_BYTES = {0x0,0x0,0x0,0x6,0xb,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x61,0x64,0x66,0x61,0x64,0x66,0x3b,0x1,0x0,0x21,0x0,0x21,0x0,0x1,0x0,0x3,0x0}; - private byte[] PROTOTYPE_NAME_BYTES = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; - - public Name() { - // default constructor - } - - public Name(WorkBook bk, String namestr) { - byte[] bl = PROTOTYPE_NAME_BYTES; - this.setData(bl); - this.setOpcode(NAME); - this.setLength((short) (bl.length)); - this.setWorkBook(bk); - try { - this.setExternsheet(bk.getExternSheet()); - } catch (WorkSheetNotFoundException x) { - Logger.logWarn("Name could not reference WorkBook Externsheet." + x.toString()); - } - - this.init(false); - this.setName(namestr); - bk.insertName(this); - } - - /** - * Used for default name entry in formulas. The name will not exist in the workbook outside - * of the formula it is used in. It has no location. - * - * @param bk Workbook containing name - * @param b Use whatever, just a flag to use this different constructor - */ - public Name(WorkBook bk, boolean b) { - byte[] bl = FILLER_NAME_BYTES; - this.setData(bl); - this.setOpcode(NAME); - this.setLength((short) (bl.length)); - this.setWorkBook(bk); - try { - this.setExternsheet(bk.getExternSheet()); - } catch (WorkSheetNotFoundException x) { - Logger.logWarn("Name could not reference WorkBook Externsheet." + x.toString()); - } - this.init(); - bk.insertName(this); - } - - /** - * Store ptgName references to this Name record - * so they can be accessed - */ - public void addIlblListener(IlblListener ptgname) { - ilblListeners.add(ptgname); - } - - public void removeIlblListener(IlblListener ptgname) { - ilblListeners.remove(ptgname); - } - - public ArrayList getIlblListeners() { - return ilblListeners; - } - - public void updateIlblListeners() { - short ilbl = (short) this.getWorkBook().getNameNumber(this.getName()); - Iterator i = ilblListeners.iterator(); - while (i.hasNext()) { - ((IlblListener) i.next()).setIlbl(ilbl); - } - } - - /** - * Initialize the Name record - * - * @see io.starter.formats.XLS.XLSRecord#init() - */ - public void init() { - init(true); // default= init Expression - } - - /** - * init Name record - * - * @param initExpression true if should parse formula/ref expression (will be false on wb load) - */ - public void init(boolean initExpression) { - super.init(); - this.getData(); - // Logger.logInfo("[" + ByteTools.getByteString(data, false) + "]"); - - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - chKey = this.getByteAt(2); - cch = this.getByteAt(3); - cce = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - ixals = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - itab = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - - cchCustMenu = this.getByteAt(10); - cchDescript = this.getByteAt(11); - cchHelpTopic = this.getByteAt(12); - cchStatusText = this.getByteAt(13); - - if ((grbit & 0x20) == 0x20) builtIn = true; - - int pos = 15; - if (this.getByteAt(14) == 0x1) { - cch *= 2; - }// rich byte; - // get the Name - try { - byte[] namebytes = this.getBytesAt(pos, cch); - if (this.getByteAt(14) == 0x1) { - rgch = new String(namebytes, UNICODEENCODING); - } else if (builtIn) { - builtInType = namebytes[0]; - switch (builtInType) { - case CONSOLIDATE_AREA: - rgch = "Built-in: CONSOLIDATE_AREA"; - break; - - case AUTO_OPEN: - rgch = "Built-in: AUTO_OPEN"; - break; - - case AUTO_CLOSE: - rgch = "Built-in: AUTO_CLOSE"; - break; - - case EXTRACT: - rgch = "Built-in: EXTRACT"; - break; - - case DATABASE: - rgch = "Built-in: DATABASE"; - break; - - case CRITERIA: - rgch = "Built-in: CRITERIA"; - break; - - case PRINT_AREA: - rgch = "Built-in: PRINT_AREA"; - break; - - case PRINT_TITLES: - rgch = "Built-in: PRINT_TITLES"; - break; - - case RECORDER: - rgch = "Built-in: RECORDER"; - break; - - case DATA_FORM: - rgch = "Built-in: DATA_FORM"; - break; - - case AUTO_ACTIVATE: - rgch = "Built-in: AUTO_ACTIVATE"; - break; - - case AUTO_DEACTIVATE: - rgch = "Built-in: AUTO_DEACTIVATE"; - break; - - case SHEET_TITLE: - rgch = "Built-in: SHEET_TITLE"; - break; - - case _FILTER_DATABASE: - rgch = "Built-in: _FILTER_DATABASE"; - break; - } - } else { - rgch = new String(namebytes); - } - if (DEBUG) Logger.logInfo(this.getName()); - pos += cch; - - // get the parsed expression - /*byte[] */ - expressionbytes = this.getBytesAt(pos, cce); - if (initExpression) - parseExpression(); - - } catch (Exception e) { - if (DEBUGLEVEL > -1) - Logger.logWarn("problem reading Name record expression for Name:" + this.getName() + " " + e); - } - } - - /** - * parse Expression separately from init - */ - public void parseExpression() { - if (expressionbytes != null && expression == null) { - expression = ExpressionParser.parseExpression(expressionbytes, this); - if (DEBUGLEVEL == DEBUG_LOW) Logger.logInfo(this.getName() + ":" + this.getDefinition()); - if (expression == null) { - PtgMystery gpg = new PtgMystery(); - gpg.init(expressionbytes); - expression = new Stack(); - expression.push(gpg); - } - expressionbytes = null; - } - } - - public Boundsheet[] getBoundSheets() { - if (ptga == null) - try { - this.initPtga(); - } catch (Exception e) { - } - if (ptga instanceof PtgRef3d) { - PtgRef3d p3d = (PtgRef3d) ptga; - Boundsheet b = p3d.getSheet(this.getWorkBook()); - Boundsheet[] ret = new Boundsheet[1]; - ret[0] = b; - return ret; - } else if (ptga instanceof PtgArea3d) { - PtgArea3d p3d = (PtgArea3d) ptga; - return p3d.getSheets(this.getWorkBook()); - } else if (ptga instanceof PtgMemFunc) { - PtgMemFunc p = (PtgMemFunc) ptga; - return p.getSheets(this.getWorkBook()); - } - return null; - } - - /** - * Return the name which identifies this Name record. If this is a built in record - * it will return a generic version of what the built-in is doing. - * - * @see io.starter.formats.XLS.XLSRecord#toString() - */ - public String toString() { - return this.getName(); - } - - /** - * Return the expression string for this name record. - * - * @return - */ - public String getExpressionString() { - if ((this.expression == null || this.expression.size() == 0) && this.getCachedOOXMLExpression() != null) - return "=" + this.getCachedOOXMLExpression(); - else if (this.expression != null) - return FormulaParser.getExpressionString(this.expression); - return "="; - } - - /** - * Get the expression for this Name record - * - * @return - */ - public Stack getExpression() { - return this.expression; - } - - /** - * set the expression for this Name record - */ - public void setExpression(Stack x) { - expression = x; - this.updatePtgs(); - } - - /** - * Return the location of the Name record. This seems to be slightly wrong as it only - * returns the location of PTGA. It's possible to have more complex records than this. - * - * @return - * @throws Exception - */ - public String getLocation() - throws Exception { - if (ptga == null) { - try { - this.initPtga(); - } catch (Exception e) { - Logger.logWarn("Name.getLocation() failed: " + e.toString()); - } - } - if (ptga == null) - return null; // it's a NameX or some other non-Cell Name - if (ptga instanceof PtgRefErr3d) // 20080228 KSC: return Exception rather than null upon Deleted Named Range - throw new io.starter.formats.XLS.CellNotFoundException("Named Range " + this.getName() + " has been deleted or it's referenced cell is invalid"); // JM - why not return loc? 'Cause it's deleted!!! :) // 20071203 KSC - if (ptga instanceof PtgArea3d) - return ptga.getLocation(); //20080214 KSC: returns correct string - if (ptga instanceof PtgRef3d) - return ptga.getLocation();// +":"+ ptga.getLocation();; // BAD -- returns a 2d range for a 1d ref - return ptga.toString(); // PtgMemFunc ... - } - - /** - * Is this a string referencing Name? - * - * @return - */ - public boolean isStringReference() { - return this.expression.size() == 1 && this.expression.get(0) instanceof PtgStr; - } - - /** - * Essentially a wrapped setLocation call that is handled for initialization of names - * from OOXML files. Our parsing is not up to snuff for some more complex name records, - * and this is a workaround until we are able to handle those expressions - *

                  - * TODO: parse complex expressions better and get rid of this method - * - * @param xpression - */ - public void initializeExpression(String xpression) { - try { - this.setLocation(xpression, false); - } catch (FunctionNotSupportedException e) { - Logger.logWarn("Unable to parse Name record expression: " + xpression); - this.cachedOOXMLExpression = xpression; - } - } - - public void setLocation(String newloc) { - setLocation(newloc, true); - } - - /** - * Set the location for the first ptg in the expression - * - * @param newloc - * @param clearAffectedCells true if should clear ptga formula cached vals - * @throws FunctionNotSupportedException TODO - */ - public void setLocation(String newloc, boolean clearAffectedCells) - throws FunctionNotSupportedException { - if (newloc.indexOf("{") > -1) - throw new FunctionNotSupportedException("Unable to parse string expression for name record " + newloc); - if (ptga == null) - this.initPtga(); - if (ptga != null) - ((PtgRef) ptga).removeFromRefTracker(); -// ptga = new PtgArea3d(false); - try {// can be a named value constant - Formula f = new Formula(); - f.setWorkBook(this.wkbook); - Stack ptgs = FormulaParser.getPtgsFromFormulaString(f, newloc); - if (ptgs.size() == 1) { // usual case of 1 ref or 1 PtgMemFunc (complex expression) - ptga = (Ptg) ptgs.pop(); - // KSC: memory usage changes: now parent rec nec. for reference tracking; if change must update - if (ptga instanceof PtgRef && ((PtgRef) ptga).getUseReferenceTracker()) - ((PtgRef) ptga).updateInRefTracker(this); - ptga.setParentRec(this); - } else { // less common case of a formula expression - expression = ptgs; - ptga = null; // otherwise will overwrite expression - see below for handling - } - } catch (Exception e) { // usually some #REF! error - Logger.logErr("Name.setLocation: Error processing location " + e.toString()); - } - if (ptga instanceof PtgRef) { // ensure that references are absolute - ((PtgRef) ptga).setColRel(false); - ((PtgRef) ptga).setRowRel(false); - // TODO: get PtgMemFunc's components and ensure references are absolute - // clear affected cells so that formulas which reference the named range get recalced with the new value(s) - if (clearAffectedCells) { // will only be false when initializing an OOXML workbook, which does not need to clear affected cells since it's initializing - try { - BiffRec[] b = ((PtgRef) ptga).getRefCells(); - for (int i = 0; i < b.length; i++) - if (b[i] != null) - this.getWorkBook().getRefTracker().clearAffectedFormulaCells(b[i]); - } catch (NullPointerException e) { - } // if cells aren't present ... - } - } - if (ptga != null) { - expression = new Stack(); - expression.add(ptga); // update expression with new Ptg -- assume there's only 1 ptg!! - try { - this.externsheet.addPtgListener((IxtiListener) ptga); - } catch (Exception e) { - // ptg constants will fail here, ptgNames ... - } - } else { - initPtga(); // will calculate and set ptga to - } - this.updatePtgs(); - } - - // remove the name - // why the boolean - NR - public boolean remove(boolean b) { - boolean ret = super.remove(true); - this.wkbook.removeName(this); - return ret; - } - - public void setWorkBook(WorkBook b) { - super.setWorkBook(b); - } - - /** - * set the Externsheet rec - */ - void setExternsheet(Externsheet e) - throws WorkSheetNotFoundException { - this.externsheet = e; - if (e == null) this.externsheet = wkbook.getExternSheet(true); - } - - /** - * Initializes ptga. - *

                  - * Seems to be an init method to make this - */ - void initPtga() { - if (expression == null) - this.init(); - if (expression == null || expression.size() == 0) - return; - Ptg p; - //if the usual case of 1 reference-type (area, ref, memfunc...) ptg: - if (this.expression.size() == 1) { - p = (Ptg) expression.get(0); - if (p.getIsReference()) - ptga = p; - } else { // otherwise it's a formula expression - p = FormulaCalculator.calculateFormulaPtg(this.expression); - if (p.getIsReference()) - ptga = p; - } -/* - if (expression != null && expression.size() > 0){ - // this may be an invalid rec - for(int t=0;t 1) Logger.logInfo("PtgArea3d encountered in Ai record."); - PtgArea3d ptg3d = (PtgArea3d) p; - ptg3d.setIxti((short) x); - ptga = ptg3d; - } - if (p instanceof PtgRef3d) { - if (DEBUGLEVEL > 1) Logger.logInfo("PtgRef3d encountered in Ai record."); - PtgRef3d ptg3d = (PtgRef3d) p; - ptg3d.setIxti((short) x); - ptga = ptg3d; - } - } - } - - /** - * update Ptga ixti for moved/copied worksheets - */ - public void updateSheetRefs(String origWorkBookName) { - if (ptga == null) { - this.initPtga(); - } - if (ptga instanceof PtgArea3d) { // PtgRef3d,etc - PtgArea3d p = (PtgArea3d) ptga; - try { - this.getWorkBook().getWorkSheetByName(p.getSheetName()); - ptga.setLocation(ptga.toString()); - } catch (WorkSheetNotFoundException we) { - Logger.logWarn("External References Not Supported: UpdateSheetReferences: External Worksheet Reference Found: " + p.getSheetName()); - p.setExternalReference(origWorkBookName); - } - } - } - - /** - * set the display name - *

                  - * Affects the following byte values: - * 7 cch 1 length of name text - * 18 rgch var name text - */ - public void setName(String newname) { - try { - int modnamelen = 0; - byte[] dta = this.getData(); - byte[] namebytes; - boolean isuni = false; - if (this.getByteAt(14) == 0x1) { // 20100604 KSC: added handling of unicode - namebytes = newname.getBytes(UNICODEENCODING); - isuni = true; - } else { - namebytes = newname.getBytes(XLSConstants.DEFAULTENCODING); - } - modnamelen = namebytes.length; - int bodlen = dta.length; - bodlen -= cch; - bodlen += modnamelen; - byte[] newbytes = new byte[bodlen]; - System.arraycopy(dta, 0, newbytes, 0, 15); - System.arraycopy(namebytes, 0, newbytes, 15, modnamelen); - System.arraycopy(dta, cch + 15, newbytes, modnamelen + 15, (dta.length - (cch + 15))); - cch = (byte) modnamelen; - if (!isuni) - newbytes[3] = cch; - else - newbytes[3] = (byte) (cch / 2); - this.setData(newbytes); - rgch = newname; - // search for dreferenced names to rehook up - } catch (UnsupportedEncodingException e) { - Logger.logWarn("UnsupportedEncodingException in setting NamedRange name: " + e); - } - } - - /** - * get the display name - */ - public String getName() { - return rgch; - } - - /** - * return the case-insensitive version of the display name - * - * @return - */ - public String getNameA() { - return rgch.toUpperCase(); // Case-insensitive - } - - int calc_id = 1; - - /** - * return the calculated value of this Name - * if it contains a parsed Expression (Formula) - * - * @return - * @throws FunctionNotSupportedException - */ - public Object getCalculatedValue() - throws FunctionNotSupportedException { - return FormulaCalculator.calculateFormula(this.expression); - - } - - /** - * get the definition text - * the definition is stored - * in Excel parsed format - */ - String getDefinition() { - if (ptga == null) initPtga(); - return ptga.getString(); - /* - StringBuffer sb = new StringBuffer(); - Ptg[] ep = new Ptg[expression.size()]; - ep = (Ptg[]) expression.toArray(ep); - for(int t = 0;t 0) - System.arraycopy(rkdata, (rkdata.length - sz2), updated, offset, sz2); - this.setData(updated); - } - - public String getCellAddress() { - if (this.getSheet() != null) - return this.getSheet() + "!" + this.getName(); - //try{ - // return this.getLocation(); - //}catch(Exception e){ - return this.getName(); // ok - //} - } - - /** - * Returns an array of ptgs that represent any BiffRec ranges in the formula. - * Ranges can either be in the format "C5" or "Sheet1!C4:D9" - */ - public Ptg[] getCellRangePtgs() - throws FormulaNotFoundException { - if (ptga == null) - initPtga(); - Ptg[] p = ptga.getComponents(); - if (p == null) // a single ref - return new Ptg[]{ptga}; - return p; -// return ExpressionParser.getCellRangePtgs(expression); - } - - /** - * Returns the ptg that matches the string location sent to it. - * this can either be in the format "C5" or a range, such as "C4:D9" - */ - public List getPtgsByLocation(String loc) { - try { - return ExpressionParser.getPtgsByLocation(loc, expression); - } catch (FormulaNotFoundException e) { - Logger.logInfo("updating Chart Series Location failed: " + e); - } - return null; - } - - /** - * Set all ptg3ds to the new sheet - *
                  Used when copying worksheets .. - * - * @param newSheet - */ - public void updateSheetReferences(Boundsheet newSheet) { - for (int i = 0; i < expression.size(); i++) { - Object o = expression.elementAt(i); - if (o instanceof PtgMemFunc) { - PtgMemFunc pmf = (PtgMemFunc) o; - Stack s = pmf.getSubExpression(); - for (int x = 0; x < s.size(); x++) { - Object ox = s.elementAt(x); - if (ox instanceof PtgArea3d) { - PtgArea3d p3d = (PtgArea3d) ox; - p3d.setReferencedSheet(newSheet); - }// do we have other types we need to handle here? (within memfunc) - } - } else if (o instanceof PtgArea3d) {// do we have other types we need to handle here? (outside memfunc); - ((PtgArea3d) o).setReferencedSheet(newSheet); - } - // do nothing - } - this.updatePtgs(); - } - - - /** - * Return an array of ptgs that make up this Name record - * - * @return - */ -/* public Ptg[] getComponents(){ - if (ptga==null) - initPtga(); - return ptga.getComponents(); -/* -// if (ptga!=null) { // then this is a location-type ptg (ref or memfunc ...) - Ptg[] p = new Ptg[expression.size()]; - for (int i=0;i

                  + * offset  name            size    contents
                  + * ---
                  + * 4       grbit           2       option flags
                  + * 6       chKey           1       Keyboard Shortcut
                  + * 7       cch             1       length of name text
                  + * 8       cce             2       length of name definition *stored in Excel parsed format
                  + * 10      ixals           2       index to sheet containing name
                  + * 12      itab            2       NAME SCOPE -- 0= workbook, 1+= sheet
                  + * 14      cchCustMenu     1       length of custom menu text
                  + * 15      cchDescript     1       length of description text
                  + * 16      cchHelpTopic    1       length of help topic text
                  + * 17      cchStatusText   1       length of status bar text
                  + * 18      rgch            var     name text
                  + * var     rgce            var     name definition
                  + * var     rcchCustMenu    var     cust menu text
                  + * var     rgchDescr       var     description text
                  + * var     rgchHelpTopic   var     help text
                  + * var     rgchStatusText  var     status bar text
                  + *
                  +
                  * + */ +class Name : XLSRecord { + internal var grbit: Short = -1 + internal var DEBUG = false + var isBuiltIn = false + internal set + internal var rgch = "" // name text + internal var rgce = "" // name definition + internal var rcchCustMenu = "" // cust menu text + /** + * get the descriptive text + */ + internal var description = "" // description text + internal var rgchHelpTopic = "" // help text + internal var rgchStatusText = "" // status bar text + internal var chKey: Byte = -1 + internal var cch: Byte = -1 + internal var cce: Short = -1 // 2 + internal var ixals: Short = -1 // 2 + internal var itab: Short = -1 // 2 + internal var cchCustMenu: Byte = -1 + internal var cchDescript: Byte = -1 + internal var cchHelpTopic: Byte = -1 + internal var cchStatusText: Byte = -1 + var builtInType: Byte = -1 + private var expression: Stack<*>? = null + internal var externsheet: Externsheet? = null + private var ptga: Ptg? = null + val ilblListeners = ArrayList() + /** + * @return Returns the cachedOOXMLExpression. + */ + /** + * @param cachedOOXMLExpression The cachedOOXMLExpression to set. + */ + var cachedOOXMLExpression: String? = null + private var expressionbytes: ByteArray? = null // for deferred Name expression init + + /* this byte array differs from the prototype in that it has no description field. It is + used by formulas inserting names that don't exist within the workbook. The name will + show up in the formula, but nowhere else on the excel file + **/ + private val FILLER_NAME_BYTES = byteArrayOf(0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0) + + // 20090811 KSC: this prototype contains a range; clear out private byte[] PROTOTYPE_NAME_BYTES = {0x0,0x0,0x0,0x6,0xb,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x61,0x64,0x66,0x61,0x64,0x66,0x3b,0x1,0x0,0x21,0x0,0x21,0x0,0x1,0x0,0x3,0x0}; + private val PROTOTYPE_NAME_BYTES = byteArrayOf(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0) + + val boundSheets: Array? + get() { + if (ptga == null) + try { + this.initPtga() + } catch (e: Exception) { + } + + if (ptga is PtgRef3d) { + val p3d = ptga as PtgRef3d? + val b = p3d!!.getSheet(this.workBook!!) + val ret = arrayOfNulls(1) + ret[0] = b + return ret + } else if (ptga is PtgArea3d) { + val p3d = ptga as PtgArea3d? + return p3d!!.getSheets(this.workBook!!) + } else if (ptga is PtgMemFunc) { + val p = ptga as PtgMemFunc? + return p!!.getSheets(this.workBook) + } + return null + } + + /** + * Return the expression string for this name record. + * + * @return + */ + val expressionString: String + get() { + if ((this.expression == null || this.expression!!.size == 0) && this.cachedOOXMLExpression != null) + return "=" + this.cachedOOXMLExpression!! + else if (this.expression != null) + return FormulaParser.getExpressionString(this.expression!!) + return "=" + } + + /** + * Return the location of the Name record. This seems to be slightly wrong as it only + * returns the location of PTGA. It's possible to have more complex records than this. + * + * @return + * @throws Exception + */ + // it's a NameX or some other non-Cell Name + // 20080228 KSC: return Exception rather than null upon Deleted Named Range + // JM - why not return loc? 'Cause it's deleted!!! :) // 20071203 KSC + //20080214 KSC: returns correct string + // +":"+ ptga.getLocation();; // BAD -- returns a 2d range for a 1d ref + // PtgMemFunc ... + var location: String? + @Throws(Exception::class) + get() { + if (ptga == null) { + try { + this.initPtga() + } catch (e: Exception) { + Logger.logWarn("Name.getLocation() failed: $e") + } + + } + if (ptga == null) + return null + if (ptga is PtgRefErr3d) + throw io.starter.formats.XLS.CellNotFoundException("Named Range " + this.name + " has been deleted or it's referenced cell is invalid") + if (ptga is PtgArea3d) + return ptga!!.location + return if (ptga is PtgRef3d) ptga!!.location else ptga!!.toString() + } + set(newloc) = setLocation(newloc, true) + + /** + * Is this a string referencing Name? + * + * @return + */ + val isStringReference: Boolean + get() = this.expression!!.size == 1 && this.expression!![0] is PtgStr + + override var workBook: WorkBook? + get + set(b) { + super.workBook = b + } + + /** + * get the display name + */ + /** + * set the display name + * + * + * Affects the following byte values: + * 7 cch 1 length of name text + * 18 rgch var name text + */ + // 20100604 KSC: added handling of unicode + // search for dreferenced names to rehook up + var name: String + get() = rgch + set(newname) = try { + var modnamelen = 0 + val dta = this.getData() + val namebytes: ByteArray + var isuni = false + if (this.getByteAt(14).toInt() == 0x1) { + namebytes = newname.toByteArray(charset(XLSConstants.UNICODEENCODING)) + isuni = true + } else { + namebytes = newname.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + } + modnamelen = namebytes.size + var bodlen = dta!!.size + bodlen -= cch.toInt() + bodlen += modnamelen + val newbytes = ByteArray(bodlen) + System.arraycopy(dta, 0, newbytes, 0, 15) + System.arraycopy(namebytes, 0, newbytes, 15, modnamelen) + System.arraycopy(dta, cch + 15, newbytes, modnamelen + 15, dta.size - (cch + 15)) + cch = modnamelen.toByte() + if (!isuni) + newbytes[3] = cch + else + newbytes[3] = (cch / 2).toByte() + this.setData(newbytes) + rgch = newname + } catch (e: UnsupportedEncodingException) { + Logger.logWarn("UnsupportedEncodingException in setting NamedRange name: $e") + } + + /** + * return the case-insensitive version of the display name + * + * @return + */ + // Case-insensitive + val nameA: String + get() = rgch.toUpperCase() + + internal var calc_id = 1 + + /** + * return the calculated value of this Name + * if it contains a parsed Expression (Formula) + * + * @return + * @throws FunctionNotSupportedException + */ + val calculatedValue: Any + @Throws(FunctionNotSupportedException::class) + get() = FormulaCalculator.calculateFormula(this.expression!!) + + /** + * get the definition text + * the definition is stored + * in Excel parsed format + */ + internal/* + StringBuffer sb = new StringBuffer(); + Ptg[] ep = new Ptg[expression.size()]; + ep = (Ptg[]) expression.toArray(ep); + for(int t = 0;t + @Throws(FormulaNotFoundException::class) + get() { + if (ptga == null) + initPtga() + return ptga!!.components ?: return arrayOf(ptga) + } + + constructor() { + // default constructor + } + + constructor(bk: WorkBook, namestr: String) { + val bl = PROTOTYPE_NAME_BYTES + this.setData(bl) + this.opcode = XLSConstants.NAME + this.length = bl.size.toShort() + this.workBook = bk + try { + this.setExternsheet(bk.externSheet) + } catch (x: WorkSheetNotFoundException) { + Logger.logWarn("Name could not reference WorkBook Externsheet.$x") + } + + this.init(false) + this.name = namestr + bk.insertName(this) + } + + /** + * Used for default name entry in formulas. The name will not exist in the workbook outside + * of the formula it is used in. It has no location. + * + * @param bk Workbook containing name + * @param b Use whatever, just a flag to use this different constructor + */ + constructor(bk: WorkBook, b: Boolean) { + val bl = FILLER_NAME_BYTES + this.setData(bl) + this.opcode = XLSConstants.NAME + this.length = bl.size.toShort() + this.workBook = bk + try { + this.setExternsheet(bk.externSheet) + } catch (x: WorkSheetNotFoundException) { + Logger.logWarn("Name could not reference WorkBook Externsheet.$x") + } + + this.init() + bk.insertName(this) + } + + /** + * Store ptgName references to this Name record + * so they can be accessed + */ + fun addIlblListener(ptgname: IlblListener) { + ilblListeners.add(ptgname) + } + + fun removeIlblListener(ptgname: IlblListener) { + ilblListeners.remove(ptgname) + } + + fun updateIlblListeners() { + val ilbl = this.workBook!!.getNameNumber(this.name).toShort() + val i = ilblListeners.iterator() + while (i.hasNext()) { + (i.next() as IlblListener).ilbl = ilbl + } + } + + /** + * Initialize the Name record + * + * @see io.starter.formats.XLS.XLSRecord.init + */ + override fun init() { + init(true) // default= init Expression + } + + /** + * init Name record + * + * @param initExpression true if should parse formula/ref expression (will be false on wb load) + */ + fun init(initExpression: Boolean) { + super.init() + this.getData() + // Logger.logInfo("[" + ByteTools.getByteString(data, false) + "]"); + + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + chKey = this.getByteAt(2) + cch = this.getByteAt(3) + cce = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + ixals = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + itab = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + + cchCustMenu = this.getByteAt(10) + cchDescript = this.getByteAt(11) + cchHelpTopic = this.getByteAt(12) + cchStatusText = this.getByteAt(13) + + if (grbit and 0x20 == 0x20) isBuiltIn = true + + var pos = 15 + if (this.getByteAt(14).toInt() == 0x1) { + cch *= 2 + }// rich byte; + // get the Name + try { + val namebytes = this.getBytesAt(pos, cch.toInt()) + if (this.getByteAt(14).toInt() == 0x1) { + rgch = String(namebytes!!, XLSConstants.UNICODEENCODING) + } else if (isBuiltIn) { + builtInType = namebytes!![0] + when (builtInType) { + CONSOLIDATE_AREA -> rgch = "Built-in: CONSOLIDATE_AREA" + + AUTO_OPEN -> rgch = "Built-in: AUTO_OPEN" + + AUTO_CLOSE -> rgch = "Built-in: AUTO_CLOSE" + + EXTRACT -> rgch = "Built-in: EXTRACT" + + DATABASE -> rgch = "Built-in: DATABASE" + + CRITERIA -> rgch = "Built-in: CRITERIA" + + PRINT_AREA -> rgch = "Built-in: PRINT_AREA" + + PRINT_TITLES -> rgch = "Built-in: PRINT_TITLES" + + RECORDER -> rgch = "Built-in: RECORDER" + + DATA_FORM -> rgch = "Built-in: DATA_FORM" + + AUTO_ACTIVATE -> rgch = "Built-in: AUTO_ACTIVATE" + + AUTO_DEACTIVATE -> rgch = "Built-in: AUTO_DEACTIVATE" + + SHEET_TITLE -> rgch = "Built-in: SHEET_TITLE" + + _FILTER_DATABASE -> rgch = "Built-in: _FILTER_DATABASE" + } + } else { + rgch = String(namebytes!!) + } + if (DEBUG) Logger.logInfo(this.name) + pos += cch.toInt() + + // get the parsed expression + /*byte[] */ + expressionbytes = this.getBytesAt(pos, cce.toInt()) + if (initExpression) + parseExpression() + + } catch (e: Exception) { + if (DEBUGLEVEL > -1) + Logger.logWarn("problem reading Name record expression for Name:" + this.name + " " + e) + } + + } + + /** + * parse Expression separately from init + */ + fun parseExpression() { + if (expressionbytes != null && expression == null) { + expression = ExpressionParser.parseExpression(expressionbytes, this) + if (DEBUGLEVEL == XLSConstants.DEBUG_LOW) Logger.logInfo(this.name + ":" + this.definition) + if (expression == null) { + val gpg = PtgMystery() + gpg.init(expressionbytes!!) + expression = Stack() + expression!!.push(gpg) + } + expressionbytes = null + } + } + + /** + * Return the name which identifies this Name record. If this is a built in record + * it will return a generic version of what the built-in is doing. + * + * @see io.starter.formats.XLS.XLSRecord.toString + */ + override fun toString(): String { + return this.name + } + + /** + * Get the expression for this Name record + * + * @return + */ + fun getExpression(): Stack<*>? { + return this.expression + } + + /** + * set the expression for this Name record + */ + fun setExpression(x: Stack<*>) { + expression = x + this.updatePtgs() + } + + /** + * Essentially a wrapped setLocation call that is handled for initialization of names + * from OOXML files. Our parsing is not up to snuff for some more complex name records, + * and this is a workaround until we are able to handle those expressions + * + * + * TODO: parse complex expressions better and get rid of this method + * + * @param xpression + */ + fun initializeExpression(xpression: String) { + try { + this.setLocation(xpression, false) + } catch (e: FunctionNotSupportedException) { + Logger.logWarn("Unable to parse Name record expression: $xpression") + this.cachedOOXMLExpression = xpression + } + + } + + /** + * Set the location for the first ptg in the expression + * + * @param newloc + * @param clearAffectedCells true if should clear ptga formula cached vals + * @throws FunctionNotSupportedException TODO + */ + @Throws(FunctionNotSupportedException::class) + fun setLocation(newloc: String, clearAffectedCells: Boolean) { + if (newloc.indexOf("{") > -1) + throw FunctionNotSupportedException("Unable to parse string expression for name record $newloc") + if (ptga == null) + this.initPtga() + if (ptga != null) + (ptga as PtgRef).removeFromRefTracker() + // ptga = new PtgArea3d(false); + try {// can be a named value constant + val f = Formula() + f.workBook = this.wkbook + val ptgs = FormulaParser.getPtgsFromFormulaString(f, newloc) + if (ptgs.size == 1) { // usual case of 1 ref or 1 PtgMemFunc (complex expression) + ptga = ptgs.pop() as Ptg + // KSC: memory usage changes: now parent rec nec. for reference tracking; if change must update + if (ptga is PtgRef && (ptga as PtgRef).useReferenceTracker) + (ptga as PtgRef).updateInRefTracker(this) + ptga!!.parentRec = this + } else { // less common case of a formula expression + expression = ptgs + ptga = null // otherwise will overwrite expression - see below for handling + } + } catch (e: Exception) { // usually some #REF! error + Logger.logErr("Name.setLocation: Error processing location $e") + } + + if (ptga is PtgRef) { // ensure that references are absolute + (ptga as PtgRef).isColRel = false + (ptga as PtgRef).isRowRel = false + // TODO: get PtgMemFunc's components and ensure references are absolute + // clear affected cells so that formulas which reference the named range get recalced with the new value(s) + if (clearAffectedCells) { // will only be false when initializing an OOXML workbook, which does not need to clear affected cells since it's initializing + try { + val b = (ptga as PtgRef).refCells + for (i in b!!.indices) + if (b[i] != null) + this.workBook!!.refTracker!!.clearAffectedFormulaCells(b[i]) + } catch (e: NullPointerException) { + } + // if cells aren't present ... + } + } + if (ptga != null) { + expression = Stack() + expression!!.add(ptga) // update expression with new Ptg -- assume there's only 1 ptg!! + try { + this.externsheet!!.addPtgListener((ptga as IxtiListener?)!!) + } catch (e: Exception) { + // ptg constants will fail here, ptgNames ... + } + + } else { + initPtga() // will calculate and set ptga to + } + this.updatePtgs() + } + + // remove the name + // why the boolean - NR + override fun remove(b: Boolean): Boolean { + val ret = super.remove(true) + this.wkbook!!.removeName(this) + return ret + } + + /** + * set the Externsheet rec + */ + @Throws(WorkSheetNotFoundException::class) + internal fun setExternsheet(e: Externsheet?) { + this.externsheet = e + if (e == null) this.externsheet = wkbook!!.getExternSheet(true) + } + + /** + * Initializes ptga. + * + * + * Seems to be an init method to make this + */ + internal fun initPtga() { + if (expression == null) + this.init() + if (expression == null || expression!!.size == 0) + return + val p: Ptg + //if the usual case of 1 reference-type (area, ref, memfunc...) ptg: + if (this.expression!!.size == 1) { + p = expression!![0] as Ptg + if (p.isReference) + ptga = p + } else { // otherwise it's a formula expression + p = FormulaCalculator.calculateFormulaPtg(this.expression!!) + if (p.isReference) + ptga = p + } + /* + if (expression != null && expression.size() > 0){ + // this may be an invalid rec + for(int t=0;t 1) Logger.logInfo("PtgArea3d encountered in Ai record.") + p.setIxti(x.toShort()) + ptga = p + } + if (p is PtgRef3d) { + if (DEBUGLEVEL > 1) Logger.logInfo("PtgRef3d encountered in Ai record.") + p.setIxti(x.toShort()) + ptga = p + } + } + } + + /** + * update Ptga ixti for moved/copied worksheets + */ + fun updateSheetRefs(origWorkBookName: String) { + if (ptga == null) { + this.initPtga() + } + if (ptga is PtgArea3d) { // PtgRef3d,etc + val p = ptga as PtgArea3d? + try { + this.workBook!!.getWorkSheetByName(p!!.sheetName) + ptga!!.location = ptga!!.toString() + } catch (we: WorkSheetNotFoundException) { + Logger.logWarn("External References Not Supported: UpdateSheetReferences: External Worksheet Reference Found: " + p!!.sheetName!!) + p.setExternalReference(origWorkBookName) + } + + } + } + + + /** + * do any pre-streaming processing such as expensive + * index updates or other deferrable processing. + */ + override fun preStream() { + try { + updatePtgs() + } catch (e: Exception) { + Logger.logWarn("problem updating Name record expression for Name:" + this.name) + } + + } + + /*** Update the record byte array with the modified ptg records + */ + fun updatePtgs() { + if (expression == null) + // happens upon init + return + val rkdata = this.getData() + var offset = 15 + cch // the start of the parsed expression + var sz = offset + val sz2 = rkdata!!.size - (offset + cce) + cce = 0 + // add up the size of the expressions + for (i in expression!!.indices) { + val ptg = expression!!.elementAt(i) as Ptg + cce += ptg.length.toShort() + } + sz += cce.toInt() + sz += sz2 + var updated = ByteArray(sz) + System.arraycopy(rkdata, 0, updated, 0, offset) + val cbytes = ByteTools.shortToLEBytes(cce) + updated[4] = cbytes[0] + updated[5] = cbytes[1] + // 20090317 KSC: added handling for PtgArrays + var hasArray = false + var arraybytes = ByteArray(0) + for (i in expression!!.indices) { + val ptg = expression!!.elementAt(i) as Ptg + val b: ByteArray + // 20090317 KSC: added handling for PtgArrays + if (ptg is PtgArray) { + b = ptg.preRecord + arraybytes = ByteTools.append(ptg.postRecord, arraybytes) + hasArray = true + } else { + b = ptg.record + } + try { + System.arraycopy(b, 0, updated, offset, b.size/*20071206 KSC: Not necessarily the same ptg.getLength()*/) + } catch (e: Exception) { + Logger.logInfo("setting ExternalSheetValue in Name rec: value: " + ptg.opcode + ": " + e) + } + + offset = offset + ptg.length + } + // 20090317 KSC: added handling for PtgArrays + if (hasArray) { + updated = ByteTools.append(arraybytes, updated) + } + + // add the rest if any + if (sz2 > 0) + System.arraycopy(rkdata, rkdata.size - sz2, updated, offset, sz2) + this.setData(updated) + } + + /** + * Returns the ptg that matches the string location sent to it. + * this can either be in the format "C5" or a range, such as "C4:D9" + */ + fun getPtgsByLocation(loc: String): List<*>? { + try { + return ExpressionParser.getPtgsByLocation(loc, expression!!) + } catch (e: FormulaNotFoundException) { + Logger.logInfo("updating Chart Series Location failed: $e") + } + + return null + } + + /** + * Set all ptg3ds to the new sheet + *

                  Used when copying worksheets .. + * + * @param newSheet + */ + fun updateSheetReferences(newSheet: Boundsheet) { + for (i in expression!!.indices) { + val o = expression!!.elementAt(i) + if (o is PtgMemFunc) { + val s = o.subExpression + for (x in s!!.indices) { + val ox = s.elementAt(x) + if (ox is PtgArea3d) { + ox.setReferencedSheet(newSheet) + }// do we have other types we need to handle here? (within memfunc) + } + } else if (o is PtgArea3d) {// do we have other types we need to handle here? (outside memfunc); + o.setReferencedSheet(newSheet) + } + // do nothing + } + this.updatePtgs() + } + + +/** + * Return an array of ptgs that make up this Name record + * + * @return + */ +/* public Ptg[] getComponents(){ + if (ptga==null) + initPtga(); + return ptga.getComponents(); +/* +// if (ptga!=null) { // then this is a location-type ptg (ref or memfunc ...) + Ptg[] p = new Ptg[expression.size()]; + for (int i=0;i
                • value -- The data item value is displayed. - *
                • difference --Display as the difference between this data item value and the value of the pivot item - *
                • percentageValue -- Display as a percentage of the value of the pivot item - *
                • percentageDifference -- Display as a percentage difference from the value of the pivot item - *
                • runningTotal -- Display as the running total for successive pivot items in the pivot field - *
                • percentageTotalRow -- Display as a percentage of the total for the row containing this data item. - *
                • percentageTotalCol -- Display as a percentage of the total for the column containing this data item. - *
                • grandTotal -- Display as a percentage of the grand total of the data item. - *
                • calculated --Calculate the value to display using the following formula: - *
                  ((this data item value) * (grand total of grand totals)) / ((row grand total) * (column grand total))
                  + * specifies a pivot field index used in calculations as specified by the Display Calculation function + * + * @return */ - public enum DISPLAYTYPES {value, difference, percentageValue, percentageDifference, runningTotal, percentageTotalRow, percentageTotalCol, grandTotal, calculated} - - public void init() { - super.init(); - isxvdData = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); // pivot field index - iiftab = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); // aggregation function -- see aggregationfunctions - df = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); // display calculation - isxvd = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); // specifies a pivot field index used in calculations as specified by the df field. - isxvi = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); // A signed integer that specifies the pivot item used by df. - ifmt = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); // number format index - cchName = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - if (cchName != -1) { - byte encoding = this.getByteAt(14); - byte[] tmp = this.getBytesAt(15, (cchName) * (encoding + 1)); - try { - if (encoding == 0) - name = new String(tmp, DEFAULTENCODING); - else - name = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding PivotTable caption name in Sxvd: " + e); - } - } - if (DEBUGLEVEL > 3) - Logger.logInfo("SXDI - isxvdData:" + isxvdData + " iiftab:" + iiftab + " df:" + df + " isxvd:" + isxvd + " isxvi:" + isxvi + " ifmt:" + ifmt + " name:" + name); - } - + var calculationPivotFieldIndex: Short = 0 + internal set + internal var iiftab: Short = 0 + internal var df: Short = 0 /** - * returns the pivot field index for this data item; - *
                  the values in the source data associated with the associated cache field of the referenced pivot field are aggregated as specified in this record. + * specifies a pivot item index used in calculations as specified by the Display Calculation function * * @return */ - public short getPivotFieldIndex() { - return isxvdData; - } - + var calculationPivotItemIndex: Short = 0 + internal set + internal var cchName: Short = 0 /** - * sets the pivot field index for this data item; - *
                  the values in the source data associated with the associated cache field of the referenced pivot field are aggregated as specified in this record. + * returns the index to the number format pattern for this data field * * @return */ - public void setPivotFieldIndex(int fi) { - isxvdData = (short) fi; - byte[] b = ByteTools.shortToLEBytes(isxvdData); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } + var numberFormat: Short = 0 + internal set + internal var name: String? = null /** * returns the aggregation function for this data field @@ -209,34 +162,19 @@ public void setPivotFieldIndex(int fi) { * @return * @see AGGREGATIONFUNCTIONS */ - public int getAggregationFunction() { - return iiftab; - } - /** * sets the aggregation function for this data field * * @see AGGREGATIONFUNCTIONS */ - public void setAggregationFunction(int af) { - iiftab = (short) af; - byte[] b = ByteTools.shortToLEBytes(iiftab); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - } - - /** - * adds the data field to the DATA axis and it's aggregation function ("sum" is default) - * - * @param fieldIndex - * @param aggregationFunction - * @param name - */ - public void addDataField(int fieldIndex, String aggregationFunction, String name) { - setPivotFieldIndex(fieldIndex); - setAggregationFunction(AGGREGATIONFUNCTIONS.get(aggregationFunction)); - setName(name); - } + var aggregationFunction: Int + get() = iiftab.toInt() + set(af) { + iiftab = af.toShort() + val b = ByteTools.shortToLEBytes(iiftab) + this.getData()[2] = b[0] + this.getData()[3] = b[1] + } /** * A signed integer that specifies the calculation used to display the value of this data item. @@ -244,81 +182,160 @@ public void addDataField(int fieldIndex, String aggregationFunction, String name * @return * @see DISPLAYTYPES */ - public int getDisplayCalculation() { - return df; - } - /** * sets the Display Calculation for the Data item: A signed integer that specifies the calculation used to display the value of this data item. * * @see DISPLAYTYPES */ - public void setDisplayCalculation(int dc) { - df = (short) dc; - byte[] b = ByteTools.shortToLEBytes(df); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; + var displayCalculation: Int + get() = df.toInt() + set(dc) { + df = dc.toShort() + val b = ByteTools.shortToLEBytes(df) + this.getData()[4] = b[0] + this.getData()[5] = b[1] + } + + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, /* isxvdData */ + 0, 0, /* iiftab */ + 0, 0, /* df */ + 0, 0, /* isxvd */ + 0, 0, /* isxvi */ + 0, 0, /* ifmt */ + -1, -1)/* cchname */ + + /** + * enum possible aggregation functions for Data axis fields + *

                  Sum, Counta, Average, Max, Min, Product, Count, StdDev, StdDevP, Var, VarP + */ + enum class AGGREGATIONFUNCTIONS private constructor(private val agf: String) { + Sum("sum"), + Count("count"), + Average("average"), + Max("max"), + Min("min"), + Product("product"), + CountNums("countnums"), + StdDev("stdDev"), + StdDevP("stdDevp"), + Var("var"), + VarP("varp"); + + + companion object { + + operator fun get(s: String): Int { + for (c in values()) { + if (c.agf == s) + return c.ordinal + } + return 0 + } + } } /** - * specifies a pivot field index used in calculations as specified by the Display Calculation function - * - * @return + * enum possible display types for Data axis fields + * * value -- The data item value is displayed. + * * difference --Display as the difference between this data item value and the value of the pivot item + * * percentageValue -- Display as a percentage of the value of the pivot item + * * percentageDifference -- Display as a percentage difference from the value of the pivot item + * * runningTotal -- Display as the running total for successive pivot items in the pivot field + * * percentageTotalRow -- Display as a percentage of the total for the row containing this data item. + * * percentageTotalCol -- Display as a percentage of the total for the column containing this data item. + * * grandTotal -- Display as a percentage of the grand total of the data item. + * * calculated --Calculate the value to display using the following formula: + *
                  ((this data item value) * (grand total of grand totals)) / ((row grand total) * (column grand total))
                  */ - public short getCalculationPivotFieldIndex() { - return isxvd; + enum class DISPLAYTYPES { + value, difference, percentageValue, percentageDifference, runningTotal, percentageTotalRow, percentageTotalCol, grandTotal, calculated + } + + override fun init() { + super.init() + pivotFieldIndex = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) // pivot field index + iiftab = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) // aggregation function -- see aggregationfunctions + df = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) // display calculation + calculationPivotFieldIndex = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) // specifies a pivot field index used in calculations as specified by the df field. + calculationPivotItemIndex = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) // A signed integer that specifies the pivot item used by df. + numberFormat = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) // number format index + cchName = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + if (cchName.toInt() != -1) { + val encoding = this.getByteAt(14) + val tmp = this.getBytesAt(15, cchName * (encoding + 1)) + try { + if (encoding.toInt() == 0) + name = String(tmp!!, XLSConstants.DEFAULTENCODING) + else + name = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding PivotTable caption name in Sxvd: $e") + } + + } + if (DEBUGLEVEL > 3) + Logger.logInfo("SXDI - isxvdData:$pivotFieldIndex iiftab:$iiftab df:$df isxvd:$calculationPivotFieldIndex isxvi:$calculationPivotItemIndex ifmt:$numberFormat name:$name") } /** - * specifies a pivot field index used in calculations as specified by the Display Calculation function + * sets the pivot field index for this data item; + *

                  the values in the source data associated with the associated cache field of the referenced pivot field are aggregated as specified in this record. * * @return */ - public void setCalculationPivotFieldIndex(int ci) { - isxvd = (short) ci; - byte[] b = ByteTools.shortToLEBytes(isxvd); - this.getData()[6] = b[0]; - this.getData()[7] = b[1]; + fun setPivotFieldIndex(fi: Int) { + pivotFieldIndex = fi.toShort() + val b = ByteTools.shortToLEBytes(pivotFieldIndex) + this.getData()[0] = b[0] + this.getData()[1] = b[1] } /** - * specifies a pivot item index used in calculations as specified by the Display Calculation function + * adds the data field to the DATA axis and it's aggregation function ("sum" is default) * - * @return + * @param fieldIndex + * @param aggregationFunction + * @param name */ - public short getCalculationPivotItemIndex() { - return isxvi; + fun addDataField(fieldIndex: Int, aggregationFunction: String, name: String) { + setPivotFieldIndex(fieldIndex) + aggregationFunction = AGGREGATIONFUNCTIONS[aggregationFunction] + setName(name) } /** - * specifies a pivot item index used in calculations as specified by the Display Calculation function + * specifies a pivot field index used in calculations as specified by the Display Calculation function * * @return */ - public void setCalculationPivotItemIndex(int ci) { - isxvi = (short) ci; - byte[] b = ByteTools.shortToLEBytes(isxvi); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; + fun setCalculationPivotFieldIndex(ci: Int) { + calculationPivotFieldIndex = ci.toShort() + val b = ByteTools.shortToLEBytes(calculationPivotFieldIndex) + this.getData()[6] = b[0] + this.getData()[7] = b[1] } /** - * returns the index to the number format pattern for this data field + * specifies a pivot item index used in calculations as specified by the Display Calculation function * * @return */ - public short getNumberFormat() { - return ifmt; + fun setCalculationPivotItemIndex(ci: Int) { + calculationPivotItemIndex = ci.toShort() + val b = ByteTools.shortToLEBytes(calculationPivotItemIndex) + this.getData()[8] = b[0] + this.getData()[9] = b[1] } /** * sets the index to the number format pattern for this data field */ - public void setNumberFormat(int i) { - ifmt = (short) i; - byte[] b = ByteTools.shortToLEBytes(ifmt); - this.getData()[10] = b[0]; - this.getData()[11] = b[1]; + fun setNumberFormat(i: Int) { + numberFormat = i.toShort() + val b = ByteTools.shortToLEBytes(numberFormat) + this.getData()[10] = b[0] + this.getData()[11] = b[1] } /** @@ -326,60 +343,54 @@ public void setNumberFormat(int i) { * * @return */ - public String getName() { - return name; + fun getName(): String? { + return name } /** * sets the name of this data item; if not null, overrides the name in the corresponding cache field * for this pivot item */ - public void setName(String name) { - this.name = name; - byte[] data = new byte[14]; - System.arraycopy(this.getData(), 0, data, 0, 13); + fun setName(name: String?) { + this.name = name + var data = ByteArray(14) + System.arraycopy(this.getData()!!, 0, data, 0, 13) if (name != null) { - byte[] strbytes = null; + var strbytes: ByteArray? = null try { - strbytes = this.name.getBytes(DEFAULTENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding pivot table name in SXVI: " + e); + strbytes = this.name!!.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding pivot table name in SXVI: $e") } //update the lengths: - cchName = (short) strbytes.length; - byte[] nm = ByteTools.shortToLEBytes(cchName); - data[12] = nm[0]; - data[13] = nm[1]; + cchName = strbytes!!.size.toShort() + val nm = ByteTools.shortToLEBytes(cchName) + data[12] = nm[0] + data[13] = nm[1] // now append variable-length string data - byte[] newrgch = new byte[cchName + 1]; // account for encoding bytes - System.arraycopy(strbytes, 0, newrgch, 1, cchName); + val newrgch = ByteArray(cchName + 1) // account for encoding bytes + System.arraycopy(strbytes, 0, newrgch, 1, cchName.toInt()) - data = ByteTools.append(newrgch, data); + data = ByteTools.append(newrgch, data) } else { - data[12] = -1; - data[13] = -1; + data[12] = -1 + data[13] = -1 } - this.setData(data); + this.setData(data) } + companion object { + private val serialVersionUID = 2639291289806138985L - private byte[] PROTOTYPE_BYTES = new byte[]{ - 0, 0, /* isxvdData */ - 0, 0, /* iiftab */ - 0, 0, /* df */ - 0, 0, /* isxvd */ - 0, 0, /* isxvi */ - 0, 0, /* ifmt */ - -1, -1, /* cchname */ - }; - - public static XLSRecord getPrototype() { - SxDI di = new SxDI(); - di.setOpcode(SXDI); - di.setData(di.PROTOTYPE_BYTES); - di.init(); - return di; + val prototype: XLSRecord? + get() { + val di = SxDI() + di.opcode = XLSConstants.SXDI + di.setData(di.PROTOTYPE_BYTES) + di.init() + return di + } } } diff --git a/src/main/java/io/starter/formats/XLS/SxEX.java b/src/main/java/io/starter/formats/XLS/SxEX.kt similarity index 88% rename from src/main/java/io/starter/formats/XLS/SxEX.java rename to src/main/java/io/starter/formats/XLS/SxEX.kt index aa6b056..739141e 100644 --- a/src/main/java/io/starter/formats/XLS/SxEX.java +++ b/src/main/java/io/starter/formats/XLS/SxEX.kt @@ -20,129 +20,163 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.Logger; +import io.starter.toolkit.Logger /** * SxEX 0xF1 - *

                  + * + * * The SXEx record specifies additional properties of a PivotTable view and specifies the beginning of a collection of records as defined by the Worksheet substreamABNF. * The collection of records specifies selection and formatting properties for the PivotTable view. - *

                  + * + * * csxformat (2 bytes): An unsigned integer that specifies the number of SxFormat records that follow this record. MUST be less than or equal to 0xFFFF. - *

                  + * + * * cchErrorString (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stError field. * If the value is 0xFFFF, then stError does not exist. MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - *

                  + * + * * cchNullString (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stDisplayNull field. * If the value is 0xFFFF, then stDisplayNull does not exist. MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - *

                  + * + * * cchTag (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stTag field. * If the value is 0xFFFF, then stTag does not exist. MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - *

                  + * + * * csxselect (2 bytes): An unsigned integer that specifies the number of SxSelect records that follow this record. MUST be less than or equal to 0xFFFF. - *

                  + * + * * crwPage (2 bytes): A DRw structure that specifies the number of rows in the page area (see Location and Body) of the PivotTable view. - *

                  + * + * * ccolPage (2 bytes): A DCol structure that specifies the number of columns in the page area (see Location and Body) of the PivotTable view. - *

                  + * + * * A - fAcrossPageLay (1 bit): A bit that specifies how pivot fields are laid out in the page area (see Location and Body) when there are multiple pivot fields on the page axis. MUST be a value from the following table: * Value Meaning * 0x0 Pivot fields are displayed in the page area from the top to the bottom first, as fields are added, before moving to another column. * 0x1 Pivot fields are displayed in the page area from left to right first, as fields are added, before moving to another row. - *

                  + * + * * cWrapPage (8 bits): An unsigned integer that specifies the number of pivot fields in the page area (see Location and Body) to * display before moving to another row or column, as specified by fAcrossPageLay. * MUST be less than or equal to 0xFF. A value of 0 means that no wrap is allowed. - *

                  + * + * * B - unused (1 bit): Undefined and MUST be ignored. - *

                  + * + * * C - reserved1 (1 bit): MUST be zero and MUST be ignored. - *

                  + * + * * reserved2 (5 bits): MUST be zero and MUST be ignored. - *

                  + * + * * D - fEnableWizard (1 bit): A bit that specifies whether a wizard user interface is displayed to work with the PivotTable view. - *

                  + * + * * E - fEnableDrilldown (1 bit): A bit that specifies whether details can be shown for cells in the data area, as specified by PivotTable Layout. - *

                  + * + * * F - fEnableFieldDialog (1 bit): A bit that specifies whether a user interface for setting properties of a pivot field can be displayed. - *

                  + * + * * G - fPreserveFormatting (1 bit): A bit that specifies whether formatting is preserved when the PivotTable view is recalculated. * If the value is 1, csxformat MUST be 0 and there MUST be no SxFormat records following this record. - *

                  + * + * * H - fMergeLabels (1 bit): A bit that specifies whether empty cells adjacent to the cells displaying pivot item captions of * pivot fields on the row axis and column axis of the PivotTable view are merged into a single cell with center-aligned text. - *

                  + * + * * I - fDisplayErrorString (1 bit): A bit that specifies whether the PivotTable view displays the custom error string stError in cells that contain errors. - *

                  + * + * * J - fDisplayNullString (1 bit): A bit that specifies whether the PivotTable view displays the custom string stDisplayNull in cells that contain NULL values. - *

                  + * + * * K - fSubtotalHiddenPageItems (1 bit): A bit that specifies whether hidden pivot items, as specified by SXVI records with the fHidden field equal to 1, of a * pivot field on the page axis with the isxvi field of the corresponding SXPI_Item structure equal to 0x7FFD are filtered out when calculating the PivotTable view. * MUST be 0 for non-OLAPdata sources (1) if the PivotCache functionality level is 3. - *

                  + * + * * reserved3 (8 bits): MUST be zero and MUST be ignored. - *

                  + * + * * cchPageFieldStyle (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stPageFieldStyle field. * If the value is 0xFFFF, then stPageFieldStyle does not exist. * MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - *

                  + * + * * cchTableStyle (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stTableStyle field. * If the value is 0xFFFF, then stTableStyle does not exist. * MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - *

                  + * + * * cchVacateStyle (2 bytes): An unsigned integer that specifies the length, in characters, of the XLUnicodeStringNoCch structure in the stVacateStyle field. * If the value is 0xFFFF, then stVacateStyle does not exist. * MUST be 0xFFFF or MUST be greater than zero and less than or equal to 0x00FF. - *

                  + * + * * stError (variable): An XLUnicodeStringNoCch structure that specifies a custom string displayed in cells that contain * errors when the value of fDisplayErrorString is 1. The length is specified in cchErrorString. * This field is optional and MUST NOT exist if cchErrorString is 0xFFFF. - *

                  + * + * * stDisplayNull (variable): An XLUnicodeStringNoCch structure that specifies a custom string displayed in cells that contain * NULL values when fDisplayNullString is 1. The length is specified in cchNullString. * This field is optional and MUST NOT exist if cchNullString is 0xFFFF. - *

                  + * + * * stTag (variable): An XLUnicodeStringNoCch structure that specifies a custom string saved with the PivotTable view. The length is specified in cchTag. * This field is optional and MUST NOT exist if cchTag is 0xFFFF. - *

                  + * + * * stPageFieldStyle (variable): An XLUnicodeStringNoCch structure that specifies the style used in the page area (see Location and Body) of the PivotTable view. * The style is specified by the StyleExt record with its stName field equal to this field's value. * If cchPageFieldStyle is 0xFFFF or less than 1, no style is applied. The length is specified in cchPageFieldStyle. * This field is optional and MUST NOT exist if cchPageFieldStyle is 0xFFFF. - *

                  + * + * * stTableStyle (variable): An XLUnicodeStringNoCch structure that specifies the style used in the body of the PivotTable view. * The style is specified by the StyleExt record with its stName field equal to this field's value. * If cchTableStyle is 0xFFFF or less than 1, no style is applied. The length is specified in cchTableStyle. * This field is optional and MUST NOT exist if cchTableStyle is 0xFFFF. - *

                  + * + * * stVacateStyle (variable): An XLUnicodeStringNoCch structure that specifies the style applied to cells that * become empty when the PivotTable view is recalculated. The style is specified by the StyleExt record * with its stName field equal to this field's value. If cchVacateStyle is 0xFFFF or less than 1, no style is applied. * The length is specified in cchVacateStyle. * This field is optional and MUST NOT exist if cchVacateStyle is 0xFFFF. */ -public class SxEX extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; +class SxEX : XLSRecord(), XLSConstants { + + private val PROTOTYPE_BYTES = byteArrayOf(// default configuration + 0, 0, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 2, 79, 0, -1, -1, -1, -1, -1, -1) - public void init() { - super.init(); - if (DEBUGLEVEL > 3) Logger.logInfo("SXEX - "); + override fun init() { + super.init() + if (DEBUGLEVEL > 3) Logger.logInfo("SXEX - ") } - private byte[] PROTOTYPE_BYTES = new byte[]{ // default configuration - 0, 0, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 2, 79, 0, -1, -1, -1, -1, -1, -1 - }; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2639291289806138985L - public static XLSRecord getPrototype() { - SxEX sex = new SxEX(); - sex.setOpcode(SXEX); - sex.setData(sex.PROTOTYPE_BYTES); - sex.init(); - return sex; + val prototype: XLSRecord? + get() { + val sex = SxEX() + sex.opcode = XLSConstants.SXEX + sex.setData(sex.PROTOTYPE_BYTES) + sex.init() + return sex + } } } diff --git a/src/main/java/io/starter/formats/XLS/SxFDB.java b/src/main/java/io/starter/formats/XLS/SxFDB.kt similarity index 54% rename from src/main/java/io/starter/formats/XLS/SxFDB.java rename to src/main/java/io/starter/formats/XLS/SxFDB.kt index 15733f8..1a6f0af 100644 --- a/src/main/java/io/starter/formats/XLS/SxFDB.java +++ b/src/main/java/io/starter/formats/XLS/SxFDB.kt @@ -20,12 +20,14 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * The SXFDB record specifies properties for a cache field within a PivotCache. - *

                  - *

                  + * + * + * + * * grbit: * A - fAllAtoms (1 bit): A bit that specifies whether this cache field has a collection of cache items. If fSomeUnhashed is equal to 1, this value MUST be equal to 0. * B - fSomeUnhashed (1 bit): Undefined, and MUST be ignored. If the fAllAtoms field is equal to 1, MUST be equal to 0. @@ -46,35 +48,44 @@ * O - fCantGetUniqueItems (1 bit): A bit that specifies whether a list of unique values for the cache field was not available while refreshing the source data. This field applies only to a PivotCache that uses ODBC source data and is intended to be used in conjunction with optimization features. For example, the application can optimize memory usage when populating PivotCache records if it has a list of unique values for a cache field before all the records are retrieved from the ODBC connection. Or, the application can determine the appropriate setting of fServerBased based on this value. * MUST be 0 for fields in a non-ODBC PivotCache. * P - fCalculatedField (1 bit): A bit that specifies whether this field is a calculated field. The formula (section 2.2.2) of the calculated field is stored in a directly following SXFormula record. If fHasParent is equal to 1, this field MUST be equal to 0. - *

                  + * + * * ifdbParent (2 bytes): An unsigned integer that specifies the cache field index, * as specified by Cache Fields, of the grouping cache field for this cache field. MUST be greater than or equal to 0x0000 and less than or equal to the cfdbTot field of the SXDB record of this PivotCache. If fHasParent is equal to 0, then this field MUST be ignored. If fHasParent is equal to 1, and fRangeGroup is equal to 1, and the iByType field of the SXRng record of this cache field is greater than 0, then the fRangeGroup of the SXFDB record of the cache field specified by ifdbParent MUST be 1 and the iByType field of the SXRng record of the cache field * specified by ifdbParent MUST be greater than the iByType field of the SXRng record of this cache field. - *

                  + * + * * ifdbBase (2 bytes): An unsigned integer that specifies the cache field index, as specified by Cache Fields, of the base cache field, * as specified by Grouping, for the cache field specified by this record. * MUST be greater than or equal to 0x0000 and less than the value of the cfdbdb field of the SXDB record of this PivotCache. * If the cache field specified by this record is not a grouping cache field, then this field MUST be ignored. - *

                  + * + * * citmUnq (2 bytes): Undefined and MUST be ignored. - *

                  + * + * * csxoper (2 bytes): An unsigned integer that specifies the number of cache items in this cache field when this cache field is a grouping cache field, * as specified by Grouping. There MUST be an equivalent number of sequences of records that conform to the GRPSXOPER rule * following this record that specify the cache items. If the fRangeGroup field and the fCalculatedField field are equal to 0 * and this cache field corresponds to a source data entity, this field MUST be equal to 0. * If the fRangeGroup field is equal to 1, this value MUST be greater than or equal to 1. - *

                  + * + * * cisxoper (2 bytes): An unsigned integer that specifies the number of cache items in the base cache field that are grouped by this cache field. * There MUST be an equivalent number of SxIsxoper records following this record that specify which cache item in this cache * field groups each of the cache items in the base cache field. For more information, see Grouping. - *

                  + * + * * catm (2 bytes): An unsigned integer that specifies the number of cache items in the collection sequences of records that conform to the * SRCSXOPER rule in this cache field. If fAllAtoms is 0, then this field MUST be equal to 0x0000. * If this cache field corresponds to source data entities then there MUST be an equal number of SRCSXOPER rules in this cache field. - *

                  + * + * * stFieldName (variable): An XLUnicodeString structure that specifies the name of the cache field. MUST be less than or equal to 255 characters long. - *

                  - *

                  + * + * + * + * * GROUPING: There are three different types of grouping: numeric grouping, date grouping, and discrete grouping. * Numeric grouping combines numeric cache items into ranges of values. Date grouping combines date cache items into date ranges. * Discrete grouping combines specifically selected cache items into groups. @@ -85,228 +96,240 @@ * The base cache field is at the lowest level of the hierarchy. */ -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger -public class SxFDB extends XLSRecord implements XLSConstants, PivotCacheRecord { +import java.io.UnsupportedEncodingException +class SxFDB : XLSRecord(), XLSConstants, PivotCacheRecord { + private var ifdbParent: Short = 0 + private var ifdbBase: Short = 0 + private var csxoper: Short = 0 + private var cisxoper: Short = 0 + private var catm: Short = 0 + private var grbit: Short = 0 /** - * serialVersionUID + * returns the cache field name == cache row cell label */ - private static final long serialVersionUID = 9027599480633995587L; - private short ifdbParent, ifdbBase, csxoper, cisxoper, catm, grbit; - private String stFieldName; + var cachefield: String? = null + private set // significant bit fields - boolean fAllAtoms, fRangeGroup, fNumField, fTextEtcField, fnumMinMaxValid, fNonDates, fDateInField, fCalculatedField, fShortItms; - - // TODO: handle ranges/grouping and all the complications that it entails - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - // grouping-related - ifdbParent = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); // specifies the cache field index, as specified by Cache Fields, of the grouping cache field for this cache field - ifdbBase = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); // specifies the cache field index, as specified by Cache Fields, of the base cache field - //ignored: citmUnq= ByteTools.readShort(this.getByteAt(6),this.getByteAt(7)); // "Undefined and MUST be ignored." - csxoper = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); // specifies the number of cache items in this cache field when this cache field is a grouping cache field, - cisxoper = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); // specifies the number of cache items in the base cache field that are grouped by this cache field - // + fHasParent, fRangeGroup ... - catm = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); // number of cache items - int cch = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); - if (cch > 0) { // 0xFFFF if none - byte encoding = this.getByteAt(16); - byte[] tmp = this.getBytesAt(17, (cch) * (encoding + 1)); - try { - if (encoding == 0) - stFieldName = new String(tmp, DEFAULTENCODING); - else - stFieldName = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("SXString.init: " + e); - } - } - fAllAtoms = (grbit & 0x1) == 0x1; - // KSC: TESTING -//if (!fAllAtoms) -// Logger.logWarn("SXFDB: not all cache items are used"); - fRangeGroup = (grbit & 0x10) == 0x10; // A bit that specifies whether this cache field is grouped by using numeric grouping or date grouping, as specified by Grouping. - fNumField = (grbit & 0x20) == 0x20; // at least one numeric field (SxNum records follow) - fTextEtcField = (grbit & 0x80) == 0x80; // text field (SxString records follow)... if fNumField must be false - fnumMinMaxValid = (grbit & 0x100) == 0x100; // true date or num field - fShortItms = (grbit & 0x200) == 0x200; // true if > 255 cache items - fNonDates = (grbit & 0x400) == 0x400; - fDateInField = (grbit & 0x4000) == 0x4000; // at least one date field (SxDtr follows) - fCalculatedField = (grbit & 0x8000) == 0x8000; // Sxformulas follow "A calculated field is a cache field (section 2.2.5.3.5) and does not correspond to a column in the source data (section 2.2.5.3.2). The values for a calculated field are calculated based on the formula specified for the calculated field" - if (DEBUGLEVEL > 3) - Logger.logInfo(this.toString()); - } - - public String toString() { - return String.format("SXFDB -p: %d d: %d csxoper: %d cisxoper: %d catm: %d stFieldName: %s fAllAtoms? %b text? %b num? %b calculated? %b", ifdbParent, ifdbBase, csxoper, cisxoper, catm, stFieldName, fAllAtoms, fTextEtcField, fNumField, fCalculatedField); - } + internal var fAllAtoms: Boolean = false + internal var fRangeGroup: Boolean = false + internal var fNumField: Boolean = false + internal var fTextEtcField: Boolean = false + internal var fnumMinMaxValid: Boolean = false + internal var fNonDates: Boolean = false + internal var fDateInField: Boolean = false + internal var fCalculatedField: Boolean = false + internal var fShortItms: Boolean = false /** * return the bytes describing this record, including the header * @return */ - public byte[] getRecord() { - byte[] b = new byte[4]; - System.arraycopy(ByteTools.shortToLEBytes(this.getOpcode()), 0, b, 0, 2); - System.arraycopy(ByteTools.shortToLEBytes((short) this.getData().length), 0, b, 2, 2); - return ByteTools.append(this.getData(), b); + override val record: ByteArray + get() { + val b = ByteArray(4) + System.arraycopy(ByteTools.shortToLEBytes(this.opcode), 0, b, 0, 2) + System.arraycopy(ByteTools.shortToLEBytes(this.getData()!!.size.toShort()), 0, b, 2, 2) + return ByteTools.append(this.getData(), b) - } - - private byte[] PROTOTYPE_BYTES = new byte[]{ -// 1, 20, /* grbit 5121 -- fAllAtoms no dates or nums...*/ - -128, 4, /* grbit 1152 -- the most basic setting i.e. no fAllAtoms ...*/ - 0, 0, - 0, 0, - 1, 0, /* unique count -- ignored?? */ - 0, 0, - 0, 0, /* cisxoper */ - 0, 0, /* catm */ - -1, -1}; // cch + } - public static XLSRecord getPrototype() { - SxFDB sxfdb = new SxFDB(); - sxfdb.setOpcode(SXFDB); - sxfdb.setData(sxfdb.PROTOTYPE_BYTES); - sxfdb.init(); - return sxfdb; - } + private val PROTOTYPE_BYTES = byteArrayOf( + // 1, 20, /* grbit 5121 -- fAllAtoms no dates or nums...*/ + -128, 4, /* grbit 1152 -- the most basic setting i.e. no fAllAtoms ...*/ + 0, 0, 0, 0, 1, 0, /* unique count -- ignored?? */ + 0, 0, 0, 0, /* cisxoper */ + 0, 0, /* catm */ + -1, -1) // cch + /** + * returns the number of cache items (column cells in range) + * @return + */ /** * sets the number of cache items (column cells in range) * TODO: only set fAllAtoms if cache item is source data and not calculated ... * * @param n the number of cache items is the number of cache items actually on axes */ - public void setNCacheItems(int n) { - catm = (short) n; // If this cache field corresponds to source data entities then there MUST be an equal number of SRCSXOPER rules in this cache field. - byte[] b = ByteTools.shortToLEBytes(catm); - this.getData()[12] = b[0]; - this.getData()[13] = b[1]; + // If this cache field corresponds to source data entities then there MUST be an equal number of SRCSXOPER rules in this cache field. + // set that this has atoms (placed on pivot table axes) + // turn off fAllAtoms ... + var nCacheItems: Int + get() = catm.toInt() + set(n) { + catm = n.toShort() + var b = ByteTools.shortToLEBytes(catm) + this.getData()[12] = b[0] + this.getData()[13] = b[1] - if (n > 0) { - // set that this has atoms (placed on pivot table axes) - fAllAtoms = true; - grbit |= 0x1; - } else { - fAllAtoms = false; - grbit &= ~0x1; // turn off fAllAtoms ... + if (n > 0) { + fAllAtoms = true + grbit = grbit or 0x1 + } else { + fAllAtoms = false + grbit = grbit and 0x1.inv().toShort() + } + b = ByteTools.shortToLEBytes(grbit) + this.getData()[0] = b[0] + this.getData()[1] = b[1] } - b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; + + // TODO: handle ranges/grouping and all the complications that it entails + override fun init() { + super.init() + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + // grouping-related + ifdbParent = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) // specifies the cache field index, as specified by Cache Fields, of the grouping cache field for this cache field + ifdbBase = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) // specifies the cache field index, as specified by Cache Fields, of the base cache field + //ignored: citmUnq= ByteTools.readShort(this.getByteAt(6),this.getByteAt(7)); // "Undefined and MUST be ignored." + csxoper = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) // specifies the number of cache items in this cache field when this cache field is a grouping cache field, + cisxoper = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) // specifies the number of cache items in the base cache field that are grouped by this cache field + // + fHasParent, fRangeGroup ... + catm = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) // number of cache items + val cch = ByteTools.readShort(this.getByteAt(14).toInt(), this.getByteAt(15).toInt()).toInt() + if (cch > 0) { // 0xFFFF if none + val encoding = this.getByteAt(16) + val tmp = this.getBytesAt(17, cch * (encoding + 1)) + try { + if (encoding.toInt() == 0) + cachefield = String(tmp!!, XLSConstants.DEFAULTENCODING) + else + cachefield = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("SXString.init: $e") + } + + } + fAllAtoms = grbit and 0x1 == 0x1 + // KSC: TESTING + //if (!fAllAtoms) + // Logger.logWarn("SXFDB: not all cache items are used"); + fRangeGroup = grbit and 0x10 == 0x10 // A bit that specifies whether this cache field is grouped by using numeric grouping or date grouping, as specified by Grouping. + fNumField = grbit and 0x20 == 0x20 // at least one numeric field (SxNum records follow) + fTextEtcField = grbit and 0x80 == 0x80 // text field (SxString records follow)... if fNumField must be false + fnumMinMaxValid = grbit and 0x100 == 0x100 // true date or num field + fShortItms = grbit and 0x200 == 0x200 // true if > 255 cache items + fNonDates = grbit and 0x400 == 0x400 + fDateInField = grbit and 0x4000 == 0x4000 // at least one date field (SxDtr follows) + fCalculatedField = grbit and 0x8000 == 0x8000 // Sxformulas follow "A calculated field is a cache field (section 2.2.5.3.5) and does not correspond to a column in the source data (section 2.2.5.3.2). The values for a calculated field are calculated based on the formula specified for the calculated field" + if (DEBUGLEVEL > 3) + Logger.logInfo(this.toString()) } - /** - * returns the number of cache items (column cells in range) - * @return - */ - public int getNCacheItems() { - return catm; + override fun toString(): String { + return String.format("SXFDB -p: %d d: %d csxoper: %d cisxoper: %d catm: %d stFieldName: %s fAllAtoms? %b text? %b num? %b calculated? %b", ifdbParent, ifdbBase, csxoper, cisxoper, catm, cachefield, fAllAtoms, fTextEtcField, fNumField, fCalculatedField) } /** * sets the cache field name == cache row cell label * @param s */ - public void setCacheField(String s) { - stFieldName = s; - byte[] strbytes = new byte[0]; - if (stFieldName != null) { + fun setCacheField(s: String) { + cachefield = s + var strbytes = ByteArray(0) + if (cachefield != null) { try { - strbytes = stFieldName.getBytes(DEFAULTENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("SxFDB: " + e); + strbytes = cachefield!!.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("SxFDB: $e") } + } - short cch = (short) strbytes.length; - byte[] nm = ByteTools.shortToLEBytes(cch); - byte[] data = new byte[14]; - System.arraycopy(this.getData(), 0, data, 0, 14); - byte[] newdata = new byte[cch + 3]; // account for encoding bytes + cch - System.arraycopy(nm, 0, newdata, 0, 2); - System.arraycopy(strbytes, 0, newdata, 3, cch); + val cch = strbytes.size.toShort() + val nm = ByteTools.shortToLEBytes(cch) + var data = ByteArray(14) + System.arraycopy(this.getData()!!, 0, data, 0, 14) + val newdata = ByteArray(cch + 3) // account for encoding bytes + cch + System.arraycopy(nm, 0, newdata, 0, 2) + System.arraycopy(strbytes, 0, newdata, 3, cch.toInt()) - data = ByteTools.append(newdata, data); - this.setData(data); - } - - /** - * returns the cache field name == cache row cell label - */ - public String getCachefield() { - return stFieldName; + data = ByteTools.append(newdata, data) + this.setData(data) } /** * sets the type of the cache items within this cache field (== the column cells below the row header field in the pivot cache range) * @param type */ - public void setCacheItemsType(int type) { - switch (type) { - case XLSConstants.TYPE_STRING: - fTextEtcField = true; - fNumField = false; - fNonDates = true; - fDateInField = false; - fnumMinMaxValid = false; - fCalculatedField = false; - break; - case XLSConstants.TYPE_FP: - case XLSConstants.TYPE_INT: - case XLSConstants.TYPE_DOUBLE: - fTextEtcField = false; - fNumField = true; - fNonDates = true; - fDateInField = false; - fnumMinMaxValid = true; - fCalculatedField = false; - break; - case TYPE_FORMULA: - fCalculatedField = true; - break; + fun setCacheItemsType(type: Int) { + when (type) { + XLSConstants.TYPE_STRING -> { + fTextEtcField = true + fNumField = false + fNonDates = true + fDateInField = false + fnumMinMaxValid = false + fCalculatedField = false + } + XLSConstants.TYPE_FP, XLSConstants.TYPE_INT, XLSConstants.TYPE_DOUBLE -> { + fTextEtcField = false + fNumField = true + fNonDates = true + fDateInField = false + fnumMinMaxValid = true + fCalculatedField = false + } + XLSConstants.TYPE_FORMULA -> fCalculatedField = true //TYPE_BOOLEAN = 4, - case 6:// date - fTextEtcField = false; - fNumField = false; - fNonDates = false; - fDateInField = true; - fnumMinMaxValid = true; - fCalculatedField = false; + 6// date + -> { + fTextEtcField = false + fNumField = false + fNonDates = false + fDateInField = true + fnumMinMaxValid = true + fCalculatedField = false + } } if (fNumField) - grbit |= 0x20; + grbit = grbit or 0x20 else - grbit &= ~0x20; + grbit = grbit and 0x20.inv().toShort() if (fTextEtcField) - grbit |= 0x80; + grbit = grbit or 0x80 else - grbit &= ~0x80; + grbit = grbit and 0x80.inv().toShort() if (fnumMinMaxValid) - grbit |= 0x100; + grbit = grbit or 0x100 else - grbit &= ~0x100; + grbit = grbit and 0x100.inv().toShort() if (fNonDates) - grbit |= 0x400; + grbit = grbit or 0x400 else - grbit &= ~0x400; + grbit = grbit and 0x400.inv().toShort() if (fDateInField) - grbit |= 0x4000; + grbit = grbit or 0x4000 else - grbit &= ~0x4000; + grbit = grbit and 0x4000.inv().toShort() if (fCalculatedField) - grbit |= 0x8000; + grbit = grbit or 0x8000 else - grbit &= ~0x8000; + grbit = grbit and 0x8000.inv().toShort() - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; + val b = ByteTools.shortToLEBytes(grbit) + this.getData()[0] = b[0] + this.getData()[1] = b[1] + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 9027599480633995587L + + val prototype: XLSRecord? + get() { + val sxfdb = SxFDB() + sxfdb.opcode = XLSConstants.SXFDB + sxfdb.setData(sxfdb.PROTOTYPE_BYTES) + sxfdb.init() + return sxfdb + } } } diff --git a/src/main/java/io/starter/formats/XLS/SxPI.java b/src/main/java/io/starter/formats/XLS/SxPI.kt similarity index 53% rename from src/main/java/io/starter/formats/XLS/SxPI.java rename to src/main/java/io/starter/formats/XLS/SxPI.kt index 4e40759..f69429a 100644 --- a/src/main/java/io/starter/formats/XLS/SxPI.java +++ b/src/main/java/io/starter/formats/XLS/SxPI.kt @@ -20,34 +20,39 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger -import java.util.Arrays; +import java.util.Arrays /** * SXPI 0x86 - *

                  + * + * * The SXPI record specifies the pivot fields and information about filtering on * the page axis of a PivotTable view. MUST exist if and only if the value of * the cDimPg field of the SxView record of the PivotTable view is greater than * zero. - *

                  + * + * * rgsxpi (variable): An array of SXPI_Items that specifies the pivot fields and * information about filtering on the page axis of a PivotTable view. The number * of array elements MUST equal the value of the cDimPg field of the SxView * record of the PivotTable view. - *

                  + * + * * The SXPI_Item structure specifies information about a pivot field and its * filtering on the page axis of a PivotTable view. - *

                  + * + * * isxvd (2 bytes): A signed integer that specifies a pivot field index as * specified by Pivot Fields. The referenced pivot field is specified to be on * the page axis. MUST be greater than or equal to zero and less than the cDim * field of the SxView record of the PivotTable view. - *

                  + * + * * isxvi (2 bytes): A signed integer that specifies the pivot item used for the * page axis filtering. MUST be a value from the following table: Value Meaning * 0x0000 to 0x7FFC This value specifies a pivot item index that specifies a @@ -57,36 +62,33 @@ * non-OLAP PivotTable view the value MUST be 0x7FFD or greater than or equal to * zero and less than the cItm field of the Sxvd record of the pivot field. * Otherwise the value MUST be 0x7FFD. - *

                  + * + * * idObj (2 bytes): A signed integer that specifies the object identifier of the * Obj record with the page item drop-down arrow. */ -public class SxPI extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - private SXPI_Item[] sxpis; +class SxPI : XLSRecord(), XLSConstants { + private var sxpis: Array? = null - public void init() { - super.init(); - byte[] rgsxpi = this.getData(); // each item is 6 bytes + override fun init() { + super.init() + val rgsxpi = this.getData() // each item is 6 bytes if (rgsxpi != null) { - if ((rgsxpi.length % 6) != 0) - Logger.logWarn("PivotTable: Irregular SxPI structure"); - sxpis = new SXPI_Item[rgsxpi.length / 6]; - for (int j = 0; j < sxpis.length; j++) { - sxpis[j] = new SXPI_Item(rgsxpi, j * 6); + if (rgsxpi.size % 6 != 0) + Logger.logWarn("PivotTable: Irregular SxPI structure") + sxpis = arrayOfNulls(rgsxpi.size / 6) + for (j in sxpis!!.indices) { + sxpis[j] = SXPI_Item(rgsxpi, j * 6) } - if (DEBUGLEVEL > 3) Logger.logInfo("SXPI - n: " + sxpis.length + ": " + Arrays.toString(data)); - } else if (DEBUGLEVEL > 3) Logger.logInfo("SXPI - NULL"); + if (DEBUGLEVEL > 3) Logger.logInfo("SXPI - n: " + sxpis!!.size + ": " + Arrays.toString(data)) + } else if (DEBUGLEVEL > 3) Logger.logInfo("SXPI - NULL") } - public String toString() { - if (sxpis != null) - return "SXPI - n: " + sxpis.length + ": " + Arrays.toString(sxpis); + override fun toString(): String { + return if (sxpis != null) + "SXPI - n: " + sxpis!!.size + ": " + Arrays.toString(sxpis) else - return "SXPI - NULL"; + "SXPI - NULL" } /** @@ -95,11 +97,10 @@ public String toString() { * @param i page axis field * @return */ - public int[] getPivotFieldItem(int i) { - if (i >= 0 && i < sxpis.length) { - return new int[]{sxpis[i].isxvd, sxpis[i].idObj}; - } - return new int[]{-1, -1}; + fun getPivotFieldItem(i: Int): IntArray { + return if (i >= 0 && i < sxpis!!.size) { + intArrayOf(sxpis!![i].isxvd.toInt(), sxpis!![i].idObj.toInt()) + } else intArrayOf(-1, -1) } /** @@ -109,62 +110,70 @@ public int[] getPivotFieldItem(int i) { * @param fieldindex * @param itemindex */ - public void setPageFieldIndex(int i, int fieldindex, int itemindex) { - if (i >= 0 && i < sxpis.length) { - sxpis[i].isxvd = (short) fieldindex; - sxpis[i].isxvi = (short) itemindex; - byte[] b = ByteTools.shortToLEBytes((short) fieldindex); - this.getData()[(i * 6)] = b[0]; - this.getData()[(i * 6) + 1] = b[1]; - b = ByteTools.shortToLEBytes((short) itemindex); - this.getData()[(i * 6) + 2] = b[0]; - this.getData()[(i * 6) + 3] = b[1]; + fun setPageFieldIndex(i: Int, fieldindex: Int, itemindex: Int) { + if (i >= 0 && i < sxpis!!.size) { + sxpis!![i].isxvd = fieldindex.toShort() + sxpis!![i].isxvi = itemindex.toShort() + var b = ByteTools.shortToLEBytes(fieldindex.toShort()) + this.getData()[i * 6] = b[0] + this.getData()[i * 6 + 1] = b[1] + b = ByteTools.shortToLEBytes(itemindex.toShort()) + this.getData()[i * 6 + 2] = b[0] + this.getData()[i * 6 + 3] = b[1] } } - public static XLSRecord getPrototype() { - SxPI sp = new SxPI(); - sp.setOpcode(SXPI); - // no data, initially??? - sp.setData(new byte[]{}); - sp.init(); - return sp; - } - /** * add a pivot field to the page axis * * @param fieldIndex * @param itemIndex */ - public void addPageField(int fieldIndex, int itemIndex) { - getData(); - data = ByteTools.append(new byte[6], data); - SXPI_Item[] tmp = new SXPI_Item[sxpis.length + 1]; - System.arraycopy(sxpis, 0, tmp, 0, sxpis.length); - sxpis = tmp; - sxpis[sxpis.length - 1] = new SXPI_Item(); + fun addPageField(fieldIndex: Int, itemIndex: Int) { + getData() + data = ByteTools.append(ByteArray(6), data) + val tmp = arrayOfNulls(sxpis!!.size + 1) + System.arraycopy(sxpis!!, 0, tmp, 0, sxpis!!.size) + sxpis = tmp + sxpis[sxpis!!.size - 1] = SXPI_Item() - setPageFieldIndex(sxpis.length - 1, fieldIndex, itemIndex); + setPageFieldIndex(sxpis!!.size - 1, fieldIndex, itemIndex) } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2639291289806138985L + + // no data, initially??? + val prototype: XLSRecord? + get() { + val sp = SxPI() + sp.opcode = XLSConstants.SXPI + sp.setData(byteArrayOf()) + sp.init() + return sp + } + } + } /** * helper class defines SxPI structure */ -class SXPI_Item { - public short isxvd; // pivot field index - public short isxvi; // specifies the pivot item used for the page axis filtering; if 0x7FFD This valuespecifies all pivot items, otherwise it's item index of pivot field (isxvd) - public short idObj; +internal class SXPI_Item { + var isxvd: Short = 0 // pivot field index + var isxvi: Short = 0 // specifies the pivot item used for the page axis filtering; if 0x7FFD This valuespecifies all pivot items, otherwise it's item index of pivot field (isxvd) + var idObj: Short = 0 - SXPI_Item() { - } + constructor() {} - SXPI_Item(byte[] rgsxpi, int idx) { - isxvd = ByteTools.readShort(rgsxpi[idx++], rgsxpi[idx++]); - isxvi = ByteTools.readShort(rgsxpi[idx++], rgsxpi[idx++]); - idObj = ByteTools.readShort(rgsxpi[idx++], rgsxpi[idx++]); + constructor(rgsxpi: ByteArray, idx: Int) { + var idx = idx + isxvd = ByteTools.readShort(rgsxpi[idx++].toInt(), rgsxpi[idx++].toInt()) + isxvi = ByteTools.readShort(rgsxpi[idx++].toInt(), rgsxpi[idx++].toInt()) + idObj = ByteTools.readShort(rgsxpi[idx++].toInt(), rgsxpi[idx++].toInt()) } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/SxStreamID.java b/src/main/java/io/starter/formats/XLS/SxStreamID.java deleted file mode 100644 index 0686ff9..0000000 --- a/src/main/java/io/starter/formats/XLS/SxStreamID.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.SxAddl.SxcCache; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; - -/** - * SXStreamID 0xD5 - * The SXStreamID record specifies the start of the stream in the PivotCache storage. - *

                  - * idStm (2 bytes): An unsigned integer that specifies a stream in the PivotCache storage. The stream specified is the one that has its name equal to the hexadecimal representation of this field. The four-digit hexadecimal string representation of this field, where each hexadecimal letter digit is a capital letter, MUST be equal to the name of a stream (1) in the PivotCache storage. - */ -public class SxStreamID extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private short streamId = -1; - private static final long serialVersionUID = 2639291289806138985L; - private ArrayList subRecs = new ArrayList(); - - /** - * init method - */ - public void init() { - super.init(); - streamId = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - if (DEBUGLEVEL > 3) Logger.logInfo("SXSTREAMID: streamid:" + streamId); - } - - /** - * store like cache-related records under SxStreamID - * - * @param r - */ - public void addSubrecord(BiffRec r) { - subRecs.add(r); - } - - /** - * creates new, default SxStreamID - * - * @return - */ - public static XLSRecord getPrototype() { - SxStreamID ss = new SxStreamID(); - ss.setOpcode(SXSTREAMID); - ss.setData(new byte[]{0, 0}); - ss.init(); - return ss; - } - - /** - * returns the streamId -- index linked to appropriate SxView pivot table view - * - * @return - */ - public short getStreamID() { - return streamId; - } - - /** - * sets the streamId -- index linked to approriate SxView pivot table view - * - * @param sid - */ - public void setStreamID(int sid) { - streamId = (short) sid; - byte[] b = ByteTools.shortToLEBytes(streamId); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - - /** - * returns the cache data sources - *
                  NOT FULLY IMPLEMENTED - only valid for sheet data range data soures - * - * @return - */ - public CellRange getCellRange() { - for (int i = 0; i < subRecs.size(); i++) { - BiffRec br = (BiffRec) subRecs.get(i); - if (br.getOpcode() == SXVS) { - if (((SxVS) br).getSourceType() != SxVS.TYPE_SHEET) { - Logger.logErr("SXSTREAMID.getCellRange: Pivot Table Data Sources other than Sheet are not supported"); - return null; - } - } else if (br.getOpcode() == DCONREF) { - return ((DConRef) br).getCellRange(); - } else if (br.getOpcode() == DCONNAME) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return null; - } else if (br.getOpcode() == DCONBIN) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return null; - } - } - return null; - } - - /** - * sets the cell range for this pivot cache - * - * @param cr - */ - public void setCellRange(CellRange cr) { - for (int i = 0; i < subRecs.size(); i++) { - BiffRec br = (BiffRec) subRecs.get(i); - if (br.getOpcode() == SXVS) { - if (((SxVS) br).getSourceType() != SxVS.TYPE_SHEET) { - Logger.logErr("SXSTREAMID.setCellRange: Pivot Table Data Sources other than Sheet are not supported"); - return; - } - } else if (br.getOpcode() == DCONREF) { - ((DConRef) br).setCellRange(cr); - return; - } else if (br.getOpcode() == DCONNAME) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return; - } else if (br.getOpcode() == DCONBIN) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return; - } - } - } - - /** - * sets the cell range for this pivot cache - * - * @param cr - */ - public void setCellRange(String cr) { - for (int i = 0; i < subRecs.size(); i++) { - BiffRec br = (BiffRec) subRecs.get(i); - if (br.getOpcode() == SXVS) { - if (((SxVS) br).getSourceType() != SxVS.TYPE_SHEET) { - Logger.logErr("SXSTREAMID.setCellRange: Pivot Table Data Sources other than Sheet are not supported"); - return; - } - } else if (br.getOpcode() == DCONREF) { - ((DConRef) br).setCellRange(cr); - return; - } else if (br.getOpcode() == DCONNAME) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return; - } else if (br.getOpcode() == DCONBIN) { - Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported"); - return; - } - } - } - - /** - * creates the basic, default records necessary to define a pivot cache - * - * @param bk - * @param ref string datasource range or named range reference - * @param sheetName string datasource sheetname where ref is located - * @return arraylist of records - */ - public ArrayList addInitialRecords(WorkBook bk, String ref, String sheetName) { - ArrayList initialrecs = new ArrayList(); - int sid = this.getStreamID(); - SxVS sxvs = (SxVS) SxVS.getPrototype(); - addInit(initialrecs, sxvs, bk); - if (bk.getName(ref) != null) { - // DConName or DConBin - Logger.logErr("PivotCache: Name Data Sources are Not Supported"); - } else { // assume it's a regular reference - // DConRef - DConRef dc = (DConRef) DConRef.getPrototype(); - int[] rc = ExcelTools.getRangeRowCol(ref); - dc.setRange(rc, sheetName); - addInit(initialrecs, dc, bk); - } - // required SxAddl records: stores additional PivotTableView, PivotCache info of a variety of types - byte[] b = ByteTools.cLongToLEBytes(sid); - b = ByteTools.append(new byte[]{0, 0}, b); // add 2 reserved bytes - SxAddl sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdId.sxd(), b); //4 bytes sid, 2 bytes reserved - addInit(initialrecs, sa, bk); - sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdVer10Info.sxd(), null); - addInit(initialrecs, sa, bk); - sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdVerSxMacro.sxd(), null); - addInit(initialrecs, sa, bk); - sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdEnd.sxd(), null); - addInit(initialrecs, sa, bk); - return initialrecs; - } - - /** - * utility function to properly add a Pivot Table View subrec - * - * @param initialrecs - * @param rec - * @param addToInitRecords - * @param sheet - */ - private void addInit(ArrayList initialrecs, XLSRecord rec, WorkBook bk) { - rec.setWorkBook(bk); - initialrecs.add(rec); - this.addSubrecord(rec); - } -} diff --git a/src/main/java/io/starter/formats/XLS/SxStreamID.kt b/src/main/java/io/starter/formats/XLS/SxStreamID.kt new file mode 100644 index 0000000..1b61d85 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/SxStreamID.kt @@ -0,0 +1,231 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.SxAddl.SxcCache +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.ArrayList + +/** + * SXStreamID 0xD5 + * The SXStreamID record specifies the start of the stream in the PivotCache storage. + * + * + * idStm (2 bytes): An unsigned integer that specifies a stream in the PivotCache storage. The stream specified is the one that has its name equal to the hexadecimal representation of this field. The four-digit hexadecimal string representation of this field, where each hexadecimal letter digit is a capital letter, MUST be equal to the name of a stream (1) in the PivotCache storage. + */ +class SxStreamID : XLSRecord(), XLSConstants { + /** + * serialVersionUID + */ + /** + * returns the streamId -- index linked to appropriate SxView pivot table view + * + * @return + */ + var streamID: Short = -1 + private set + private val subRecs = ArrayList() + + + /** + * returns the cache data sources + *

                  NOT FULLY IMPLEMENTED - only valid for sheet data range data soures + * + * @return + */ + /** + * sets the cell range for this pivot cache + * + * @param cr + */ + var cellRange: CellRange? + get() { + for (i in subRecs.indices) { + val br = subRecs.get(i) as BiffRec + if (br.opcode == XLSConstants.SXVS) { + if ((br as SxVS).sourceType != SxVS.TYPE_SHEET) { + Logger.logErr("SXSTREAMID.getCellRange: Pivot Table Data Sources other than Sheet are not supported") + return null + } + } else if (br.opcode == XLSConstants.DCONREF) { + return (br as DConRef).cellRange + } else if (br.opcode == XLSConstants.DCONNAME) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported") + return null + } else if (br.opcode == XLSConstants.DCONBIN) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported") + return null + } + } + return null + } + set(cr) { + for (i in subRecs.indices) { + val br = subRecs.get(i) as BiffRec + if (br.opcode == XLSConstants.SXVS) { + if ((br as SxVS).sourceType != SxVS.TYPE_SHEET) { + Logger.logErr("SXSTREAMID.setCellRange: Pivot Table Data Sources other than Sheet are not supported") + return + } + } else if (br.opcode == XLSConstants.DCONREF) { + (br as DConRef).cellRange = cr + return + } else if (br.opcode == XLSConstants.DCONNAME) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported") + return + } else if (br.opcode == XLSConstants.DCONBIN) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported") + return + } + } + } + + /** + * init method + */ + override fun init() { + super.init() + streamID = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + if (DEBUGLEVEL > 3) Logger.logInfo("SXSTREAMID: streamid:$streamID") + } + + /** + * store like cache-related records under SxStreamID + * + * @param r + */ + fun addSubrecord(r: BiffRec) { + subRecs.add(r) + } + + /** + * sets the streamId -- index linked to approriate SxView pivot table view + * + * @param sid + */ + fun setStreamID(sid: Int) { + streamID = sid.toShort() + val b = ByteTools.shortToLEBytes(streamID) + this.getData()[0] = b[0] + this.getData()[1] = b[1] + } + + /** + * sets the cell range for this pivot cache + * + * @param cr + */ + fun setCellRange(cr: String) { + for (i in subRecs.indices) { + val br = subRecs.get(i) as BiffRec + if (br.opcode == XLSConstants.SXVS) { + if ((br as SxVS).sourceType != SxVS.TYPE_SHEET) { + Logger.logErr("SXSTREAMID.setCellRange: Pivot Table Data Sources other than Sheet are not supported") + return + } + } else if (br.opcode == XLSConstants.DCONREF) { + (br as DConRef).setCellRange(cr) + return + } else if (br.opcode == XLSConstants.DCONNAME) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported") + return + } else if (br.opcode == XLSConstants.DCONBIN) { + Logger.logErr("SXSTREAMID.getCellRange: Name sources are not yet supported") + return + } + } + } + + /** + * creates the basic, default records necessary to define a pivot cache + * + * @param bk + * @param ref string datasource range or named range reference + * @param sheetName string datasource sheetname where ref is located + * @return arraylist of records + */ + fun addInitialRecords(bk: WorkBook, ref: String, sheetName: String): ArrayList<*> { + val initialrecs = ArrayList() + val sid = this.streamID.toInt() + val sxvs = SxVS.prototype as SxVS? + addInit(initialrecs, sxvs!!, bk) + if (bk.getName(ref) != null) { + // DConName or DConBin + Logger.logErr("PivotCache: Name Data Sources are Not Supported") + } else { // assume it's a regular reference + // DConRef + val dc = DConRef.prototype as DConRef? + val rc = ExcelTools.getRangeRowCol(ref) + dc!!.setRange(rc, sheetName) + addInit(initialrecs, dc, bk) + } + // required SxAddl records: stores additional PivotTableView, PivotCache info of a variety of types + var b = ByteTools.cLongToLEBytes(sid) + b = ByteTools.append(byteArrayOf(0, 0), b) // add 2 reserved bytes + var sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdId.sxd().toInt(), b) //4 bytes sid, 2 bytes reserved + addInit(initialrecs, sa, bk) + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdVer10Info.sxd().toInt(), null) + addInit(initialrecs, sa, bk) + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdVerSxMacro.sxd().toInt(), null) + addInit(initialrecs, sa, bk) + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcCache, SxcCache.SxdEnd.sxd().toInt(), null) + addInit(initialrecs, sa, bk) + return initialrecs + } + + /** + * utility function to properly add a Pivot Table View subrec + * + * @param initialrecs + * @param rec + * @param addToInitRecords + * @param sheet + */ + private fun addInit(initialrecs: ArrayList<*>, rec: XLSRecord, bk: WorkBook) { + rec.workBook = bk + initialrecs.add(rec) + this.addSubrecord(rec) + } + + companion object { + private val serialVersionUID = 2639291289806138985L + + /** + * creates new, default SxStreamID + * + * @return + */ + val prototype: XLSRecord? + get() { + val ss = SxStreamID() + ss.opcode = XLSConstants.SXSTREAMID + ss.setData(byteArrayOf(0, 0)) + ss.init() + return ss + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/SxVIEWEX9.java b/src/main/java/io/starter/formats/XLS/SxVIEWEX9.kt similarity index 73% rename from src/main/java/io/starter/formats/XLS/SxVIEWEX9.java rename to src/main/java/io/starter/formats/XLS/SxVIEWEX9.kt index 881bd80..fa4d234 100644 --- a/src/main/java/io/starter/formats/XLS/SxVIEWEX9.java +++ b/src/main/java/io/starter/formats/XLS/SxVIEWEX9.kt @@ -20,69 +20,81 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * SXViewEx9 0x810 - *

                  + * + * * The SXViewEx9 record specifies extensions to the PivotTable view. - *

                  + * + * * rt (2 bytes): An unsigned integer that specifies the record type identifier. The value MUST be 0x0810. - *

                  + * + * * A - reserved1 (1 bit): MUST be zero, and MUST be ignored. - *

                  + * + * * B - fFrtAlert (1 bit): A bit that specifies whether features of this PivotTable are not supported in earlier versions of the BIFF. * An application can alert the user of possible problems when saving as an earlier version of the BIFF. - *

                  + * + * * reserved2 (14 bits): MUST be zero, and MUST be ignored. - *

                  + * + * * reserved3 (4 bytes): MUST be zero, and MUST be ignored. - *

                  + * + * * C - reserved4 (1 bit): MUST be zero, and MUST be ignored. - *

                  + * + * * D - fPrintTitles (1 bit): A bit that specifies whether the print titles for the worksheet are set based on the PivotTable report. * The row print titles are set to the pivot item captions on the column axis and the column print titles are set to the pivot item captions on the row axis. - *

                  + * + * * E - fLineMode (1 bit): A bit that specifies whether any pivot field is in outline mode. See Subtotalling for more information. - *

                  + * + * * F - reserved5 (2 bits): MUST be zero, and MUST be ignored. - *

                  + * + * * G - fRepeatItemsOnEachPrintedPage (1 bit): A bit that specifies whether pivot item captions on the row axis are repeated on each printed page for pivot fields in tabular form. - *

                  + * + * * reserved6 (26 bits): MUST be zero, and MUST be ignored. - *

                  + * + * * itblAutoFmt (2 bytes): An AutoFmt8 structure that specifies the PivotTable AutoFormat. If the value of this field is not 1, * this field overrides the itblAutoFmt field in the previous SxView record. - *

                  + * + * * chGrand (variable): An XLUnicodeString structure that specifies a user-entered caption to display for grand totals when the PivotTable is recalculated. * The length MUST be less than or equal to 255 characters. */ -public class SxVIEWEX9 extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; +class SxVIEWEX9 : XLSRecord(), XLSConstants { + + private val PROTOTYPE_BYTES = byteArrayOf(0x10, 0x8, /* rt= 0x810 */ + 0, 0, /* flags + reserved */ + 0, 0, 0, 0, 0x20, 0, 0, 0, 1, 0, /* itblAutoFmt*/ + 0, 0, 0 /* chGrand-- cch= 0, encoding= 0 */) - public void init() { - super.init(); + override fun init() { + super.init() } - private byte[] PROTOTYPE_BYTES = new byte[]{ - 0x10, 0x8, /* rt= 0x810 */ - 00, 00, /* flags + reserved */ - 00, 00, - 00, 00, - 0x20, 00, - 00, 00, - 01, 00, /* itblAutoFmt*/ - 00, 00, 00 /* chGrand-- cch= 0, encoding= 0 */ - }; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2639291289806138985L - public static XLSRecord getPrototype() { - SxVIEWEX9 sxv = new SxVIEWEX9(); - sxv.setOpcode(SXVIEWEX9); - sxv.setData(sxv.PROTOTYPE_BYTES); - sxv.init(); - return sxv; + val prototype: XLSRecord? + get() { + val sxv = SxVIEWEX9() + sxv.opcode = XLSConstants.SXVIEWEX9 + sxv.setData(sxv.PROTOTYPE_BYTES) + sxv.init() + return sxv + } } } diff --git a/src/main/java/io/starter/formats/XLS/SxVS.java b/src/main/java/io/starter/formats/XLS/SxVS.kt similarity index 66% rename from src/main/java/io/starter/formats/XLS/SxVS.java rename to src/main/java/io/starter/formats/XLS/SxVS.kt index a432bd8..e43eeb4 100644 --- a/src/main/java/io/starter/formats/XLS/SxVS.java +++ b/src/main/java/io/starter/formats/XLS/SxVS.kt @@ -20,59 +20,63 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger /** * SXVS 0xE3 * The SXVS record specifies the type of source data used for a PivotCache. * This record is followed by a sequence of records that specify additional information about the source data. - *

                  + * + * * sxvs (2 bytes): An unsigned integer that specifies the type of source data used for the PivotCache. The types of records that follow this record are dictated by the value of this field. MUST be a value from the following table: - *

                  + * + * * Name Value Meaning - *

                  + * + * * SHEET 0x0001 Specifies that the source data is a range. This record MUST be followed by a DConRef record that specifies a simple range, or a DConName record that specifies a named range, or a DConBin record that specifies a built-in named range. * EXTERNAL 0x0002 Specifies that external source data is used. This record MUST be followed by a sequence of records beginning with a DbQuery record that specifies connection and query information that is used to retrieve external data. * CONSOLIDATION 0x0004 Specifies that multiple consolidation ranges are used as the source data. This record MUST be followed by a sequence of records beginning with an SXTbl record that specifies information about the multiple consolidation ranges. * SCENARIO 0x0010 The source data is populated from a temporary internal structure. In this case there is no additional source data information because the raw data does not exist as a permanent structure and the logic to produce it is application-dependent. */ -public class SxVS extends XLSRecord implements XLSConstants { +class SxVS : XLSRecord(), XLSConstants { /** * serialVersionUID */ - private short sourceType = -1; - public static final short TYPE_SHEET = 0x1; - public static final short TYPE_EXTERNAL = 0x0002; - public static final short TYPE_CONSOLIDATION = 0x0004; - public static final short TYPE_SCENARIO = 0x0010; - - private static final long serialVersionUID = 2639291289806138985L; + var sourceType: Short = -1 + private set - public void init() { - super.init(); - sourceType = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - if (DEBUGLEVEL > 3) Logger.logInfo("SXVS - sourceType:" + sourceType); + override fun init() { + super.init() + sourceType = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + if (DEBUGLEVEL > 3) Logger.logInfo("SXVS - sourceType:$sourceType") } - public short getSourceType() { - return sourceType; + fun setSourceType(st: Int) { + sourceType = st.toShort() + val b = ByteTools.shortToLEBytes(sourceType) + this.getData()[0] = b[0] + this.getData()[1] = b[1] } - public void setSourceType(int st) { - sourceType = (short) st; - byte[] b = ByteTools.shortToLEBytes(sourceType); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } + companion object { + val TYPE_SHEET: Short = 0x1 + val TYPE_EXTERNAL: Short = 0x0002 + val TYPE_CONSOLIDATION: Short = 0x0004 + val TYPE_SCENARIO: Short = 0x0010 + + private val serialVersionUID = 2639291289806138985L - public static XLSRecord getPrototype() { - SxVS sv = new SxVS(); - sv.setOpcode(SXVS); - sv.setData(new byte[]{1, 0}); - sv.init(); - return sv; + val prototype: XLSRecord? + get() { + val sv = SxVS() + sv.opcode = XLSConstants.SXVS + sv.setData(byteArrayOf(1, 0)) + sv.init() + return sv + } } } diff --git a/src/main/java/io/starter/formats/XLS/SxVdEX.java b/src/main/java/io/starter/formats/XLS/SxVdEX.kt similarity index 83% rename from src/main/java/io/starter/formats/XLS/SxVdEX.java rename to src/main/java/io/starter/formats/XLS/SxVdEX.kt index cf8182c..994f311 100644 --- a/src/main/java/io/starter/formats/XLS/SxVdEX.java +++ b/src/main/java/io/starter/formats/XLS/SxVdEX.kt @@ -20,140 +20,174 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger /** * SxVDEx 0x100 - *

                  + * + * * The SXVDEx record specifies extended pivot field properties. - *

                  + * + * * A - fShowAllItems (1 bit): A bit that specifies whether to show all pivot items for this pivot field, * including pivot items that do not currently exist in the source data. The value MUST be 0 for an OLAP PivotTable view. * MUST be a value from the following table: * Value Meaning * 0x0 Specifies that all pivot items are not displayed. * 0x1 Specifies that all pivot items are displayed. - *

                  + * + * * B - fDragToRow (1 bit): A bit that specifies whether this pivot field can be placed on the row axis. This value MUST be ignored for an OLAP PivotTable view. * MUST be a value from the following table: * Value Meaning * 0x0 Specifies that the user is prevented from placing this pivot field on the row axis. * 0x1 Specifies that the user is not prevented from placing this pivot field on the row axis. - *

                  + * + * * C - fDragToColumn (1 bit): A bit that specifies whether this pivot field can be placed on the column axis. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: * Value Meaning * 0x0 Specifies that the user is prevented from placing this pivot field on the column axis. * 0x1 Specifies that the user is not prevented from placing this pivot field on the column axis. - *

                  + * + * * D - fDragToPage (1 bit): A bit that specifies whether this pivot field can be placed on the page axis. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: * Value Meaning * 0x0 Specifies that the user is prevented from placing this pivot field on the page axis. * 0x1 Specifies that the user is not prevented from placing this pivot field on the page axis. - *

                  + * + * * E - fDragToHide (1 bit): A bit that specifies whether this pivot field can be removed from the PivotTable view. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: * Value Meaning * 0x0 Specifies that the user is prevented from removing this pivot field from the PivotTable view. * 0x1 Specifies that the user is not prevented from removing this pivot field from the PivotTable view. - *

                  + * + * * F - fNotDragToData (1 bit): A bit that specifies whether this pivot field can be placed on the data axis. This value MUST be ignored for an OLAP PivotTable view. MUST be a value from the following table: * Value Meaning * 0x0 Specifies that the user is not prevented from placing this pivot field on the data axis. * 0x1 Specifies that the user is prevented from placing this pivot field on the data axis. - *

                  + * + * * G - reserved1 (1 bit): MUST be zero, and MUST be ignored. - *

                  + * + * * H - fServerBased (1 bit): A bit that specifies whether this pivot field is server-based when on the page axis. For more information, see Source Data. * A value of 1 specifies that this pivot field is a server-based pivot field. - *

                  + * + * * MUST be 1 if and only if the value of the fServerBased field of the SXFDB record of the associated cache field of this pivot field is 1. - *

                  + * + * * I - reserved2 (1 bit): MUST be zero, and MUST be ignored. - *

                  + * + * * J - fAutoSort (1 bit): A bit that specifies whether AutoSort will be applied to this pivot field. For more information, see Pivot Field Sorting. - *

                  + * + * * K - fAscendSort (1 bit): A bit that specifies whether any AutoSort applied to this pivot field will sort in ascending order. MUST be a value from the following table: * Value Meaning * 0x0 Sort in descending order. * 0x1 Sort in ascending order. - *

                  + * + * * L - fAutoShow (1 bit): A bit that specifies whether an AutoShowfilter is applied to this pivot field. For more information, see Simple Filters. - *

                  + * + * * M - fTopAutoShow (1 bit): A bit that specifies whether any AutoShow filter applied to this pivot field shows the top-ranked or bottom-ranked values. For more information, see Simple Filters. MUST be a value from the following table: * Value Meaning * 0x0 Any AutoShow filter applied to this pivot field shows the bottom-ranked values. * 0x1 Any AutoShow filter applied to this pivot field shows the top-ranked values. - *

                  + * + * * N - fCalculatedField (1 bit): A bit that specifies whether this pivot field is a calculated field. A value of 1 specifies that this pivot field is a calculated field. - *

                  + * + * * MUST be 1 if and only if the value of the fCalculatedField field of the SXFDB record of the cache field associated with this pivot field is 1. - *

                  + * + * * O - fPageBreaksBetweenItems (1 bit): A bit that specifies whether a page break (2) is inserted after each pivot item when the PivotTable is printed. - *

                  + * + * * P - fHideNewItems (1 bit): A bit that specifies whether new pivot items that appear after a refresh are hidden by default. This value MUST be equal to 0 for a non-OLAP PivotTable view. * Value Meaning * 0x0 New pivot items are shown by default. * 0x1 New pivot items are hidden by default. - *

                  + * + * * reserved3 (5 bits): MUST be zero, and MUST be ignored. - *

                  + * + * * Q - fOutline (1 bit): A bit that specifies whether this pivot field is in outline form. For more information, see PivotTable layout. - *

                  + * + * * R - fInsertBlankRow (1 bit): A bit that specifies whether to insert a blank row after each pivot item. - *

                  + * + * * S - fSubtotalAtTop (1 bit): A bit that specifies whether subtotals are displayed at the top of the group when the fOutline field is equal to 1. For more information, see PivotTable layout. - *

                  + * + * * citmAutoShow (8 bits): An unsigned integer that specifies the number of pivot items to show when the fAutoShow field is equal to 1. * The value MUST be greater than or equal to 1 and less than or equal to 255. - *

                  + * + * * isxdiAutoSort (2 bytes): A signed integer that specifies the data item that AutoSort uses when the fAutoSort field is equal to 1. If the value of the fAutoSort field is one, * the value MUST be greater than or equal to zero and less than the count of SXDI records. MUST be a value from the following table: * Value Meaning * -1 Specifies that the values of the pivot items themselves are used. * Greater than or equal to zero Specifies a data item index, as specified in Data Items, of the data item that is used. - *

                  + * + * * isxdiAutoShow (2 bytes): A signed integer that specifies the data item that AutoShow ranks by when the fAutoShow field is equal to 1. * For more information, see Simple Filters. If the value of the fAutoShow field is 1, this value MUST be greater than or equal to zero and less than the count of SXDI records. * MUST be a value from the following table: * Value Meaning * -1 AutoShow is not enabled for this pivot field. * Greater than or equal to zero Specifies a data item index, as specified in Data Items, of the data item that is used. - *

                  + * + * * ifmt (2 bytes): An IFmt structure that specifies the number format of this pivot field. - *

                  + * + * * subName (variable): An optional SXVDEx_Opt structure that specifies the name of the aggregate function used to calculate this pivot field's subtotals. SHOULD<124> be present. */ -public class SxVdEX extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - private short citmAutoShow, isxdiAutoSort, isxdiAutoShow, ifmt; +class SxVdEX : XLSRecord(), XLSConstants { + private var citmAutoShow: Short = 0 + private var isxdiAutoSort: Short = 0 + private var isxdiAutoShow: Short = 0 + private var ifmt: Short = 0 + + private val PROTOTYPE_BYTES = byteArrayOf(// default configuration + 30, 20, 0, 10, -1, -1, -1, -1, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0) - public void init() { - super.init(); + override fun init() { + super.init() // TODO: flags - citmAutoShow = this.getByteAt(4); - isxdiAutoSort = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - isxdiAutoShow = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - ifmt = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); + citmAutoShow = this.getByteAt(4).toShort() + isxdiAutoSort = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + isxdiAutoShow = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + ifmt = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) // TODO: subName (variable): An optional SXVDEx_Opt structure that specifies the name of the aggregate function used to calculate this pivot field's subtotals. SHOULD<124> be present. if (DEBUGLEVEL > 3) - Logger.logInfo("SXVDEX - citmAutoShow:" + citmAutoShow + " isxdiAutoSort:" + isxdiAutoSort + " isxdoAutoShow:" + isxdiAutoShow + " ifmt:" + ifmt); + Logger.logInfo("SXVDEX - citmAutoShow:$citmAutoShow isxdiAutoSort:$isxdiAutoSort isxdoAutoShow:$isxdiAutoShow ifmt:$ifmt") } - private byte[] PROTOTYPE_BYTES = new byte[]{ // default configuration - 30, 20, 0, 10, -1, -1, -1, -1, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0 - }; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2639291289806138985L - public static XLSRecord getPrototype() { - SxVdEX sv = new SxVdEX(); - sv.setOpcode(SXVDEX); - sv.setData(sv.PROTOTYPE_BYTES); - sv.init(); - return sv; + val prototype: XLSRecord? + get() { + val sv = SxVdEX() + sv.opcode = XLSConstants.SXVDEX + sv.setData(sv.PROTOTYPE_BYTES) + sv.init() + return sv + } } } diff --git a/src/main/java/io/starter/formats/XLS/Sxformat.java b/src/main/java/io/starter/formats/XLS/Sxformat.kt similarity index 72% rename from src/main/java/io/starter/formats/XLS/Sxformat.java rename to src/main/java/io/starter/formats/XLS/Sxformat.kt index b66e840..ae778bc 100644 --- a/src/main/java/io/starter/formats/XLS/Sxformat.java +++ b/src/main/java/io/starter/formats/XLS/Sxformat.kt @@ -20,39 +20,43 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.Logger; +import io.starter.toolkit.Logger /** * SXFORMAT FBh: This record contains formatting data * - *

                  + *
                  + * 
                    * offset  name        size    contents
                    * ---
                    * 4       rlType      2       0x0 = clear
                    * 0x1 = format applied
                    * 6       cbData      2       length of data
                  - * 

                  - **/ - +
                  * + */ -public class Sxformat extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8702313274711819140L; - byte[] data = null; +class Sxformat : XLSRecord(), XLSConstants { + internal var data: ByteArray? = null - public void init() { - super.init(); - if (this.getLength() <= 0) { // Is this record populated? - if (DEBUGLEVEL > -1) Logger.logInfo("no data in Sxformat"); + override fun init() { + super.init() + if (this.length <= 0) { // Is this record populated? + if (DEBUGLEVEL > -1) Logger.logInfo("no data in Sxformat") } else { // parse out all the fields // Logger.logInfo(ExcelTools.getRecordByteDef(this)); } } + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -8702313274711819140L + } + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxivd.java b/src/main/java/io/starter/formats/XLS/Sxivd.kt similarity index 66% rename from src/main/java/io/starter/formats/XLS/Sxivd.java rename to src/main/java/io/starter/formats/XLS/Sxivd.kt index f6bc484..8db7764 100644 --- a/src/main/java/io/starter/formats/XLS/Sxivd.java +++ b/src/main/java/io/starter/formats/XLS/Sxivd.kt @@ -20,20 +20,22 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger -import java.util.Arrays; +import java.util.Arrays /** * SXIVD B4h: Array of field ID numbers for the rows and columns in a PT. - *

                  + * + * * There are at most two of these recs per Table -- one for rows, one for cols. * - *

                  + *
                  + * 
                    * offset  name        size    contents
                    * ---
                    * 4       rgisxvd     var     Array of 2 byte field ids (row or col)
                  @@ -73,24 +75,20 @@
                    * is 1.
                    *
                    *
                  - * 

                  - **/ +
                  * + */ -public class Sxivd extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 9027599480633995587L; +class Sxivd : XLSRecord(), XLSConstants { - public void init() { - super.init(); + override fun init() { + super.init() if (DEBUGLEVEL > 3) - Logger.logInfo("SXIVD - n: " + this.getData().length + " array:" + Arrays.toString(this.getData())); + Logger.logInfo("SXIVD - n: " + this.getData()!!.size + " array:" + Arrays.toString(this.getData())) } - public String toString() { - return "SXIVD - n: " + this.getData().length + " array:" + Arrays.toString(this.getData()); + override fun toString(): String { + return "SXIVD - n: " + this.getData()!!.size + " array:" + Arrays.toString(this.getData()) } /** @@ -98,39 +96,47 @@ public String toString() { * * @param fieldNumber */ - public void addField(int fieldNumber) { - data = this.getData(); - byte[] b = ByteTools.shortToLEBytes((short) fieldNumber); - data = ByteTools.append(b, data); + fun addField(fieldNumber: Int) { + data = this.getData() + val b = ByteTools.shortToLEBytes(fieldNumber.toShort()) + data = ByteTools.append(b, data) } /** * for each two-byte pair in an array of [number of items]*2 bytes, - * // *
                  specifies either a pivot field index (must less than the total number of fields as specfied by Sxview) + * // *

                  specifies either a pivot field index (must less than the total number of fields as specfied by Sxview) * or -2 means that the data field is on the row or col axis * * @param items */ - public void setRowOrColItems(int[] items) { - byte[] data = intToByteArray(items); - this.setData(data); + fun setRowOrColItems(items: IntArray) { + val data = intToByteArray(items) + this.setData(data) } - public static XLSRecord getPrototype() { - Sxivd si = new Sxivd(); - si.setOpcode(SXIVD); - si.setData(new byte[]{}); - si.init(); - return si; - } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 9027599480633995587L + + val prototype: XLSRecord? + get() { + val si = Sxivd() + si.opcode = XLSConstants.SXIVD + si.setData(byteArrayOf()) + si.init() + return si + } - public static final byte[] intToByteArray(int[] value) { - if (value == null) return null; - byte[] b = new byte[value.length]; - int j = 0; - for (int i : value) { - b[j++] = (byte) i; + fun intToByteArray(value: IntArray?): ByteArray? { + if (value == null) return null + val b = ByteArray(value.size) + var j = 0 + for (i in value) { + b[j++] = i.toByte() + } + return b } - return b; } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxli.java b/src/main/java/io/starter/formats/XLS/Sxli.kt similarity index 53% rename from src/main/java/io/starter/formats/XLS/Sxli.java rename to src/main/java/io/starter/formats/XLS/Sxli.kt index d2b88d8..5f1df8d 100644 --- a/src/main/java/io/starter/formats/XLS/Sxli.java +++ b/src/main/java/io/starter/formats/XLS/Sxli.kt @@ -20,104 +20,117 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger -import java.util.ArrayList; -import java.util.Arrays; +import java.util.ArrayList +import java.util.Arrays /** * SxLI: The SXLI record specifies pivot lines for the row area or column area * of a PivotTable view. - *

                  + * + * * SXLI B5h: This record stores an array of variable-length SXLI structures * which describe row and col items. - *

                  + * + * * Thre are 2 of these recs per Table -- one for rows, one for cols * - *

                  + * + * * *

                  - *     offset  name        size    contents
                  - *     ---
                  - *     4       rgsxli      var     Array of SXLI structures
                  - *
                  - *
                  - *     SXLI Structures have variable length but will always be at least 10 bytes long.
                  - *
                  - *     offset  name        size    contents
                  - *     ---
                  - *     0       cSic        2       count of identical items to previous
                  - *     					A signed integer that specifies the count of pivot item indexes in the beginning of the rgisxvi array that are identical to the same number of pivot item indexes
                  - *     					in the beginning of the rgisxvi array of the previous SXLIItem structure in the rgsxli array of the preceding SXLI record.
                  - *     					The value MUST be greater than or equal to 0 and less than the isxviMac field. If the fGrand field equals 1, then this value MUST be 0.
                  - *     2       itmtype     2       type:
                  - *                                 0x0 = data
                  - *                                 0x1 = default
                  - *                                 0x2 = SUM
                  - *                                 0x3 = COUNTA
                  - *                                 0x4 = COUNT
                  - *                                 0x5 = AVERAGE
                  - *                                 0x6 = MAX
                  - *                                 0x7 = MIN
                  - *                                 0x8 = PRODUCT
                  - *                                 0x9 = STDEV
                  - *                                 0xA = STDEVP
                  - *                                 0xB = VAR
                  - *                                 0xC = VARP
                  - *                                 0xD = GRAND TOTAL
                  - *     4       isxviMac    2	   Number of elements in the rgisxvi array that are displayed in this pivot line. MUST be greater than or equal to 0.
                  - *     				   If the fGrand field equals 1, then the value of this field MUST be 1. If the fGrand field equals zero and the preceding SXLI record contains row area pivot items, then this value MUST be less than or equal to the cDimRw field of the preceding SxView. If the fGrand field equals zero and the preceding SXLI record contains column area pivot items, then this value MUST be less than or equal to the cDimCol field of the preceding SxView.
                  - *             fMultiDataName (1 bit):  A bit that specifies whether the data field name is used for the total or the subtotal.
                  - *             iData (8 bits): An unsigned integer that specifies a data item index as specified in Data Items, for an SXDI record specifying a data item used for a subtotal.
                  - *             fSbt (1 bit): A bit that specifies whether this pivot line is a subtotal.
                  - *             fBlock (1 bit): A bit that specifies whether this pivot line is a block total.
                  - *             fGrand (1 bit): A bit that specifies whether this pivot line is a grand total.
                  - *             fMultiDataOnAxis (1 bit): A bit that specifies whether a pivot line entry in this pivot line is a data item index.
                  - * 		3 bits- unused
                  - * 	rgisxvi (variable): An array of 2-byte signed integers that specifies a pivot line entry.  --> if 0x7FFF means no pivot line/blank
                  - *
                  - *     6       grbit       2       option flags
                  + * offset  name        size    contents
                  + * ---
                  + * 4       rgsxli      var     Array of SXLI structures
                  + *
                  + *
                  + * SXLI Structures have variable length but will always be at least 10 bytes long.
                  + *
                  + * offset  name        size    contents
                  + * ---
                  + * 0       cSic        2       count of identical items to previous
                  + * A signed integer that specifies the count of pivot item indexes in the beginning of the rgisxvi array that are identical to the same number of pivot item indexes
                  + * in the beginning of the rgisxvi array of the previous SXLIItem structure in the rgsxli array of the preceding SXLI record.
                  + * The value MUST be greater than or equal to 0 and less than the isxviMac field. If the fGrand field equals 1, then this value MUST be 0.
                  + * 2       itmtype     2       type:
                  + * 0x0 = data
                  + * 0x1 = default
                  + * 0x2 = SUM
                  + * 0x3 = COUNTA
                  + * 0x4 = COUNT
                  + * 0x5 = AVERAGE
                  + * 0x6 = MAX
                  + * 0x7 = MIN
                  + * 0x8 = PRODUCT
                  + * 0x9 = STDEV
                  + * 0xA = STDEVP
                  + * 0xB = VAR
                  + * 0xC = VARP
                  + * 0xD = GRAND TOTAL
                  + * 4       isxviMac    2	   Number of elements in the rgisxvi array that are displayed in this pivot line. MUST be greater than or equal to 0.
                  + * If the fGrand field equals 1, then the value of this field MUST be 1. If the fGrand field equals zero and the preceding SXLI record contains row area pivot items, then this value MUST be less than or equal to the cDimRw field of the preceding SxView. If the fGrand field equals zero and the preceding SXLI record contains column area pivot items, then this value MUST be less than or equal to the cDimCol field of the preceding SxView.
                  + * fMultiDataName (1 bit):  A bit that specifies whether the data field name is used for the total or the subtotal.
                  + * iData (8 bits): An unsigned integer that specifies a data item index as specified in Data Items, for an SXDI record specifying a data item used for a subtotal.
                  + * fSbt (1 bit): A bit that specifies whether this pivot line is a subtotal.
                  + * fBlock (1 bit): A bit that specifies whether this pivot line is a block total.
                  + * fGrand (1 bit): A bit that specifies whether this pivot line is a grand total.
                  + * fMultiDataOnAxis (1 bit): A bit that specifies whether a pivot line entry in this pivot line is a data item index.
                  + * 3 bits- unused
                  + * rgisxvi (variable): An array of 2-byte signed integers that specifies a pivot line entry.  --> if 0x7FFF means no pivot line/blank
                  + *
                  + * 6       grbit       2       option flags
                    * 8       rgisxvi     2       Array of indices to SXVI records -- number is isxviMac+1
                    *
                  - * 

                  - *
                  - *

                  + * + * + * + * * more info: rgsxli (variable): An array of SXLIItem. - *

                  + * + * * Zero or two records of this type appear in the file for each PivotTable view * depending on the values of the cRw and cCol fields of the associated SxView * record. - *

                  + * + * * If the value of either of the cRw or cCol fields of the associated SxView is * greater than zero, then two records of this type MUST exist in the file for * the associated SxView. The first record contains row area pivot lines and the * second record contains column area pivot lines. - *

                  + * + * * The count of SXLIItem structures in rgsxli, which are row area pivot lines, * MUST equal the cRw field of SxView. - *

                  + * + * * The count of SXLIItem structures in rgsxli, which are column area pivot * lines, MUST equal the cCol field of SxView. - *

                  + * + * * The associated SxView record is the SxView record of the PivotTable view. - *

                  + * + * * The SXLIItem structure specifies a pivot line in the row area or column area * of a PivotTable view. - *

                  + * + * * cSic (2 bytes): A signed integer that specifies the count of pivot item * indexes in the beginning of the rgisxvi array that are identical to the same * number of pivot item indexes in the beginning of the rgisxvi array of the * previous SXLIItem structure in the rgsxli array of the preceding SXLI record. * The value MUST be greater than or equal to 0 and less than the isxviMac * field. If the fGrand field equals 1, then this value MUST be 0. - *

                  + * + * * itmType (15 bits): An unsigned integer that specifies the type of this pivot * line. MUST be a value from the table: - *

                  + * + * * ITMTYPEDATA 0x0000 A value in the data ITMTYPEDEFAULT 0x0001 Automatic * subtotal selection ITMTYPESUM 0x0002 Sum of values in the data ITMTYPECOUNTA * 0x0003 Count of values in the data ITMTYPECOUNT 0x0004 Count of numbers in @@ -128,9 +141,11 @@ * standard deviation (entire population) ITMTYPEVAR 0x000B Statistical variance * (estimate) ITMTYPEVARP 0x000C Statistical variance (entire population) * ITMTYPEGRAND 0x000D Grand total ITMTYPEBLANK 0x000E Blank line - *

                  + * + * * A - reserved1 (1 bit): MUST be zero and MUST be ignored. - *

                  + * + * * isxviMac (2 bytes): A signed integer that specifies the number of elements in * the rgisxvi array that are displayed in this pivot line. MUST be greater than * or equal to 0. If the fGrand field equals 1, then the value of this field @@ -139,29 +154,34 @@ * the cDimRw field of the preceding SxView. If the fGrand field equals zero and * the preceding SXLI record contains column area pivot items, then this value * MUST be less than or equal to the cDimCol field of the preceding SxView. - *

                  + * + * * B - fMultiDataName (1 bit): A bit that specifies whether the data field name * is used for the total or the subtotal. MUST be a value from the following * table: Value Meaning 0 The data field name is used for the total. 1 The data * field name is used for the subtotal. - *

                  + * + * * If the fGrand field equals 1 or the fBlock field equals 1, then this value * MUST equal the value in the fMultiDataOnAxis field. If the fGrand and fBlock * fields equal zero, the fSbt and fMultiDataOnAxis fields equal 1, and the cSic * field is less than iposData, then this value MUST equal 1. Otherwise, this * value MUST be zero. - *

                  + * + * * iposData is specified as follows: If the preceding SXLI record contains row * area pivot items, iposData equals the index of the SxIvdRw record in the * rgSxivd array of the SxIvd containing SxIvdRw records where the rw field * equals -2. If there is not an SxIvdRw record with the rw field equal to -2, * iposData equals zero. - *

                  + * + * * If the preceding SXLI record contains column area pivot items, iposData * equals the index of the SxIvdCol record in the rgSxivd array of the SxIvd * containing SxIvdCol records where the col field equals -2. If there is not an * SxIvdCol record with the col field equal to -2, iposData equals zero. - *

                  + * + * * iData (8 bits): An unsigned integer that specifies a data item index as * specified in Data Items, for an SXDI record specifying a data item used for a * subtotal. This field MUST be 0 if the cDimData field of the preceding SxView @@ -173,44 +193,54 @@ * fMultiDataName, then the value of this field MUST equal the value of the * element of the rgisxvi array in the position equal to iposData as specified * in fMultiDataName. - *

                  + * + * * C - fSbt (1 bit): A bit that specifies whether this pivot line is a subtotal. * This value MUST equal 1 if the itmType field is greater than or equal to * ITMTYPEDEFAULT and the itmType field is less than or equal to ITMTYPEGRAND * and the fBlock field equals 0. Otherwise, this value MUST be 0. - *

                  + * + * * D - fBlock (1 bit): A bit that specifies whether this pivot line is a block * total. A block total is a total of a group of pivot items. For more details * see Grouping. If the fGrand field equals 0 and the fBlock field in the * previous SXLIItem record equals 1, this value MUST be 1. - *

                  + * + * * E - fGrand (1 bit): A bit that specifies whether this pivot line is a grand * total. If the fGrand field in the previous SXLIItem record is 1, then this * value MUST be 1. Otherwise, if the itmType field equals ITMTYPEGRAND this * field MUST equal 1 and if the itmType field does not equal ITMTYPEGRAND this * field MUST equal 0. - *

                  + * + * * F - fMultiDataOnAxis (1 bit): A bit that specifies whether a pivot line entry * in this pivot line is a data item index. - *

                  + * + * * If the preceding SXLI record contains row area pivot items, the cDimData * field of the preceding SxView record is greater than 1, the * sxaxis4Data.sxaxisRw field of the preceding SxView equals 1 and itmType is * not equal to ITMTYPEBLANK, then this value MUST be 1. Otherwise, this value * MUST be 0. - *

                  + * + * * If the preceding SXLI record contains column area pivot items, the cDimData * field of the preceding SxView record is greater than 1, the * sxaxis4Data.sxaxisCol field of the preceding SxView equals 1 and itmType is * not equal to ITMTYPEBLANK, then this value MUST be 1. Otherwise, this value * MUST be 0. - *

                  + * + * * G - unused1 (1 bit): Undefined, and MUST be ignored. - *

                  + * + * * H - unused2 (1 bit): Undefined, and MUST be ignored. - *

                  + * + * * I - reserved2 (1 bit): MUST be zero and MUST be ignored. - *

                  + * + * * rgisxvi (variable): An array of 2-byte signed integers that specifies a pivot * line entry. Each element of this array is either a pivot item index or a data * item index. If fGrand is 1 or itmType is ITMTYPEBLANK then all elements of @@ -219,76 +249,139 @@ * specifies a data item index or pivot item index in the associated pivot field * as specified in Pivot Items. 0x7FFF This value specifies that there is no * pivot item and that the cell in the pivot line is blank. - *

                  - *

                  - *

                  - * "http://www.extentech.com">Extentech Inc. - **/ -public class Sxli extends XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4157827774990504633L; - ArrayList items; - int nItemsPerLine; + * + * + * + * + * + * + * "http://www.extentech.com">Extentech Inc. + */ +class Sxli : XLSRecord() { + internal var items: ArrayList + internal var nItemsPerLine: Int = 0 - public void init() { - super.init(); - Sxview sx = wkbook.getAllPivotTableViews()[wkbook.getNPivotTableViews() - 1]; - nItemsPerLine = (sx.hasRowPivotItemsRecord() ? sx.cDimCol : sx.cDimRw); + override fun init() { + super.init() + val sx = wkbook!!.allPivotTableViews[wkbook!!.nPivotTableViews - 1] + nItemsPerLine = (if (sx.hasRowPivotItemsRecord()) sx.cDimCol else sx.cDimRw).toInt() // total # items will be sx.cRw or sx.cCol // per each pivot line, # items [see SXLI_Item.rgisxvi] will be sx.cDimRw or sx.cDimCol - items = SXLI_Item.parse(this.getData(), nItemsPerLine); + items = SXLI_Item.parse(this.getData()!!, nItemsPerLine) if (DEBUGLEVEL > 3) - Logger.logInfo("SXLI: rgsxli:" + items.toString()); + Logger.logInfo("SXLI: rgsxli:$items") } - public String toString() { - return "SXLI: rgsxli:" + items.toString(); + override fun toString(): String { + return "SXLI: rgsxli:$items" } /** * adds a field to the end of this field list */ - public void addField(int repeat, int nLines, int type, short[] indexes) { - SXLI_Item sxitem = new SXLI_Item(repeat, nLines, type, indexes, nItemsPerLine); - items.add(sxitem); - updateRecord(true); - } - - /** - * return a new, blank SxLi - * - * @return - */ - public static XLSRecord getPrototype(WorkBook wkbook) { - Sxli li = new Sxli(); - li.setWorkBook(wkbook); - li.setOpcode(SXLI); - li.setData(new byte[]{}); - li.init(); - return li; + fun addField(repeat: Int, nLines: Int, type: Int, indexes: ShortArray) { + val sxitem = SXLI_Item(repeat, nLines, type, indexes, nItemsPerLine) + items.add(sxitem) + updateRecord(true) } - void updateRecord(boolean appendLast) { + internal fun updateRecord(appendLast: Boolean) { if (!appendLast) { // add all - this.data = new byte[0]; - for (SXLI_Item sxitem : items) { - data = ByteTools.append(sxitem.getData(), data); + this.data = ByteArray(0) + for (sxitem in items) { + data = ByteTools.append(sxitem.data, data) } } else - data = ByteTools.append(items.get(items.size() - 1).getData(), data); + data = ByteTools.append(items[items.size - 1].data, data) } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 4157827774990504633L + + /** + * return a new, blank SxLi + * + * @return + */ + fun getPrototype(wkbook: WorkBook): XLSRecord { + val li = Sxli() + li.workBook = wkbook + li.opcode = XLSConstants.SXLI + li.setData(byteArrayOf()) + li.init() + return li + } + } } /** * The SXLIItem structure specifies a pivot line in the row area or column area * of a PivotTable view. */ -class SXLI_Item { - public enum ITEMTYPE { +internal class SXLI_Item { + + var cSic: Short = 0 + /* 0x0000 specifies that no pivot items in the rgisxvi array are identical to the first pivot items in the previous pivot line item in this record. + */ + var itmType: Short = 0 + /* see ITEMTYPE enum */ + var isxviMac: Short = 0 /* number of pivot items on the pivot line */ + var iData: Byte = 0 /* specifies a data item index as specified in Data Items, for an SXDI record specifying a data item used for a subtotal. This field MUST be 0 if the cDimData field of the preceding SxView record is 0 or if the fGrand field equals */ + var fMultiDataName: Boolean = false + /* specifies whether the data field name is used for the total or the subtotal */ + var fSbt: Boolean = false + /* Specifies whether this pivot line is a subtotal. */ + var fBlock: Boolean = false + /* Specifies whether this pivot line is a block total */ + var fGrand: Boolean = false + /* Specifies whether this pivot line is a grand total. */ + var fMultiDataOnAxis: Boolean = false /* Specifies whether a pivot line entry in this pivot line is a data item index. */ + var rgisxvi: ByteArray /* An array of 2-byte signed integers that specifies a pivot line entry. + Each element of this array is either a pivot item index or a data item index. + 0x7FFF means blank, no pivot item + */ + + /** + * package SXLI_ITEM into byte array + * + * @return byte[] + */ + val data: ByteArray + get() { + var data = ByteArray(8) + var b = ByteTools.shortToLEBytes(cSic) + data[0] = b[0] + data[1] = b[1] + b = ByteTools.shortToLEBytes(itmType) + data[2] = b[0] + data[3] = b[1] + b = ByteTools.shortToLEBytes(isxviMac) + data[4] = b[0] + data[5] = b[1] + var tmp = (if (fMultiDataName) 1 else 0).toShort() + tmp = (tmp or (iData shl 1)).toShort() + if (fSbt) + tmp = (tmp or 0x200).toShort() + if (fBlock) + tmp = (tmp or 0x400).toShort() + if (fGrand) + tmp = (tmp or 0x800).toShort() + if (fMultiDataOnAxis) + tmp = (tmp or 0x1000).toShort() + b = ByteTools.shortToLEBytes(tmp) + data[6] = b[0] + data[7] = b[1] + data = ByteTools.append(rgisxvi, data) + return data + } + + enum class ITEMTYPE private constructor(/* Blank line */ + private val itm: String) { ITMTYPEDATA("data"), /* A value in the data */ ITMTYPEDEFAULT("default"), /* Automatic subtotal selection */ ITMTYPESUM("sum"), /* Sum of values in the data */ @@ -306,78 +399,51 @@ public enum ITEMTYPE { ITMTYPEVAR("var"), /* Statistical variance (estimate) */ ITMTYPEVARP("varP"), /* Statistical variance (entire population) */ ITMTYPEGRAND("grand"), /* Grand total */ - ITMTYPEBLANK("blank"); /* Blank line */ - private final String itm; + ITMTYPEBLANK("blank"); - ITEMTYPE(String s) { - this.itm = s; - } - public static int get(String s) { - for (ITEMTYPE c : values()) { - if (c.itm.equals(s)) - return c.ordinal(); + companion object { + + operator fun get(s: String): Int { + for (c in values()) { + if (c.itm == s) + return c.ordinal + } + return 0 } - return 0; - } - public static ITEMTYPE get(int id) { - for (ITEMTYPE c : values()) { - if (c.ordinal() == id) - return c; + operator fun get(id: Int): ITEMTYPE? { + for (c in values()) { + if (c.ordinal == id) + return c + } + return null } - return null; } } - short cSic, /* 0x0000 specifies that no pivot items in the rgisxvi array are identical to the first pivot items in the previous pivot line item in this record. - */ - itmType, /* see ITEMTYPE enum */ - isxviMac; /* number of pivot items on the pivot line */ - byte iData; /* specifies a data item index as specified in Data Items, for an SXDI record specifying a data item used for a subtotal. This field MUST be 0 if the cDimData field of the preceding SxView record is 0 or if the fGrand field equals */ - boolean fMultiDataName, /* specifies whether the data field name is used for the total or the subtotal */ - fSbt, /* Specifies whether this pivot line is a subtotal. */ - fBlock, /* Specifies whether this pivot line is a block total */ - fGrand, /* Specifies whether this pivot line is a grand total. */ - fMultiDataOnAxis; /* Specifies whether a pivot line entry in this pivot line is a data item index. */ - byte[] rgisxvi; /* An array of 2-byte signed integers that specifies a pivot line entry. - Each element of this array is either a pivot item index or a data item index. - 0x7FFF means blank, no pivot item - */ - - static int pos = 0; - - public static ArrayList parse(byte[] data, int nItemsPerLine) { - pos = 0; - ArrayList items = new ArrayList(); - - while (pos < data.length - 7) { - items.add(new SXLI_Item(data, nItemsPerLine)); - } - return items; - } - - SXLI_Item(byte[] data, int nItemsPerLine) { + constructor(data: ByteArray, nItemsPerLine: Int) { try { - cSic = ByteTools.readShort(data[pos + 0], data[pos + 1]); - itmType = (short) (ByteTools.readShort(data[pos + 2], data[pos + 3]) & 0x7FFF); // 1st // 15 bits - isxviMac = ByteTools.readShort(data[pos + 4], data[pos + 5]); - short tmp = ByteTools.readShort(data[pos + 6], data[pos + 7]); - fMultiDataName = (tmp & 0x8000) == 0x8000; - iData = (byte) ((tmp >> 7) & 0x80); - tmp = (byte) (tmp >> 9); - fSbt = (tmp & 0x1) == 0x1; - fBlock = (tmp & 0x2) == 0x2; - fGrand = (tmp & 0x4) == 0x4; - fMultiDataOnAxis = (tmp & 0x8) == 0x8; - pos += 8; + cSic = ByteTools.readShort(data[pos + 0].toInt(), data[pos + 1].toInt()) + itmType = (ByteTools.readShort(data[pos + 2].toInt(), data[pos + 3].toInt()) and 0x7FFF).toShort() // 1st // 15 bits + isxviMac = ByteTools.readShort(data[pos + 4].toInt(), data[pos + 5].toInt()) + var tmp = ByteTools.readShort(data[pos + 6].toInt(), data[pos + 7].toInt()) + fMultiDataName = tmp and 0x8000 == 0x8000 + iData = (tmp shr 7 and 0x80).toByte() + tmp = (tmp shr 9).toByte().toShort() + fSbt = tmp and 0x1 == 0x1 + fBlock = tmp and 0x2 == 0x2 + fGrand = tmp and 0x4 == 0x4 + fMultiDataOnAxis = tmp and 0x8 == 0x8 + pos += 8 // not in 1.5 rgisxvi = Arrays.copyOfRange(data, pos, pos + (nItemsPerLine * 2)); - rgisxvi = new byte[nItemsPerLine * 2]; - System.arraycopy(data, pos, rgisxvi, 0, rgisxvi.length); - pos += rgisxvi.length; - } catch (Exception e) { + rgisxvi = ByteArray(nItemsPerLine * 2) + System.arraycopy(data, pos, rgisxvi, 0, rgisxvi.size) + pos += rgisxvi.size + } catch (e: Exception) { } + } /** @@ -389,24 +455,24 @@ public static ArrayList parse(byte[] data, int nItemsPerLine) { * @param indexes * @param nItemsPerLine */ - SXLI_Item(int repeat, int nLines, int type, short[] indexes, int nItemsPerLine) { - cSic = (short) repeat; - isxviMac = (short) nLines; - ITEMTYPE t = ITEMTYPE.get(type); - itmType = (short) t.ordinal(); + constructor(repeat: Int, nLines: Int, type: Int, indexes: ShortArray, nItemsPerLine: Int) { + cSic = repeat.toShort() + isxviMac = nLines.toShort() + val t = ITEMTYPE[type] + itmType = t!!.ordinal.toShort() // fMultiDataName ??? // iData ??? // fBlock ??? // fMultiDataOnAxis ??? - switch (t) { - case ITMTYPEDATA: - case ITMTYPEBLANK: - break; - case ITMTYPEGRAND: - fGrand = true; - default: - fSbt = true; -/* case ITMTYPEDEFAULT: + when (t) { + SXLI_Item.ITEMTYPE.ITMTYPEDATA, SXLI_Item.ITEMTYPE.ITMTYPEBLANK -> { + } + SXLI_Item.ITEMTYPE.ITMTYPEGRAND -> { + fGrand = true + fSbt = true + } + else -> fSbt = true + }/* case ITMTYPEDEFAULT: case ITMTYPEAVERAGE: case ITMTYPECOUNT: case ITMTYPECOUNTA: @@ -418,50 +484,33 @@ public static ArrayList parse(byte[] data, int nItemsPerLine) { case ITMTYPESUM: case ITMTYPEVAR: case ITMTYPEVARP:*/ - } - rgisxvi = new byte[indexes.length * 2]; - for (int i = 0; i < indexes.length * 2; i += 2) { - byte[] b = ByteTools.shortToLEBytes(indexes[i / 2]); - rgisxvi[i] = b[0]; - rgisxvi[i + 1] = b[1]; + rgisxvi = ByteArray(indexes.size * 2) + var i = 0 + while (i < indexes.size * 2) { + val b = ByteTools.shortToLEBytes(indexes[i / 2]) + rgisxvi[i] = b[0] + rgisxvi[i + 1] = b[1] + i += 2 } } - /** - * package SXLI_ITEM into byte array - * - * @return byte[] - */ - byte[] getData() { - byte[] data = new byte[8]; - byte[] b = ByteTools.shortToLEBytes(cSic); - data[0] = b[0]; - data[1] = b[1]; - b = ByteTools.shortToLEBytes(itmType); - data[2] = b[0]; - data[3] = b[1]; - b = ByteTools.shortToLEBytes(isxviMac); - data[4] = b[0]; - data[5] = b[1]; - short tmp = (short) ((fMultiDataName) ? 1 : 0); - tmp = (short) (tmp | (iData << 1)); - if (fSbt) - tmp = (short) (tmp | 0x200); - if (fBlock) - tmp = (short) (tmp | 0x400); - if (fGrand) - tmp = (short) (tmp | 0x800); - if (fMultiDataOnAxis) - tmp = (short) (tmp | 0x1000); - b = ByteTools.shortToLEBytes(tmp); - data[6] = b[0]; - data[7] = b[1]; - data = ByteTools.append(rgisxvi, data); - return data; + override fun toString(): String { + return String.format("[rep %d typ %d imax %d iData %d fSbt %b fBlock %b fGrand %b rgi: %s", + cSic, itmType, isxviMac, iData, fSbt, fBlock, fGrand, Arrays.toString(rgisxvi)) } - public String toString() { - return String.format("[rep %d typ %d imax %d iData %d fSbt %b fBlock %b fGrand %b rgi: %s", - cSic, itmType, isxviMac, iData, fSbt, fBlock, fGrand, Arrays.toString(rgisxvi)); + companion object { + + var pos = 0 + + fun parse(data: ByteArray, nItemsPerLine: Int): ArrayList { + pos = 0 + val items = ArrayList() + + while (pos < data.size - 7) { + items.add(SXLI_Item(data, nItemsPerLine)) + } + return items + } } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxvd.java b/src/main/java/io/starter/formats/XLS/Sxvd.kt similarity index 57% rename from src/main/java/io/starter/formats/XLS/Sxvd.java rename to src/main/java/io/starter/formats/XLS/Sxvd.kt index 91ff93e..3836db0 100644 --- a/src/main/java/io/starter/formats/XLS/Sxvd.java +++ b/src/main/java/io/starter/formats/XLS/Sxvd.kt @@ -20,12 +20,12 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger -import java.io.UnsupportedEncodingException; +import java.io.UnsupportedEncodingException /** @@ -34,7 +34,8 @@ * beginning of a collection of records. * This collection of records specifies details for a pivot field. * - *

                  + *
                  + * 
                    * offset  name        size    contents
                    * ---
                    * 4       sxaxis      2       0x0 = no axis
                  @@ -58,18 +59,22 @@
                    * bitFMax     0010
                    * bitFMin     0020
                    *
                  - * 

                  - *

                  +

                  * + * + * * MORE INFO: - *

                  + * + * * sxaxis (2 bytes): An SXAxis structure that specifies the PivotTable axis that this pivot field is on. * If the sxaxis.sxaxisData field equals 1, there MUST be a corresponding SXDI record with an isxvd field that specifies this Sxvd record. - *

                  + * + * * cSub (2 bytes): An unsigned integer that specifies the number of subtotal functions used for this pivot field. * MUST equal the count of subtotal fields of this record whose value is 1. * The subtotal fields of this record are fDefault, fSum, fCounta, fAverage, fMax, fMin, fProduct, fCount, fStdev, fStdevp, fVariance, and fVariancep. * For more information, see Subtotalling. - *

                  + * + * * A - fDefault (1 bit): A bit that specifies whether the default subtotal function is applied. * The default subtotal is separately determined for each data item. If the fDefault field equals 1 * and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage @@ -78,7 +83,8 @@ * Value Meaning * 0 The default subtotal function is not applied. * 1 The default subtotal function is applied. - *

                  + * + * * B - fSum (1 bit): A bit that specifies whether the sum subtotal function is displayed. If the fDefault field equals 1, * this value MUST be zero. If the fSum field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol * field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the @@ -86,7 +92,8 @@ * Value Meaning * 0 The sum subtotal function is not displayed. * 1 The sum subtotal function is displayed. - *

                  + * + * * C - fCounta (1 bit): A bit that specifies whether the count subtotal function is displayed. If the fDefault field equals 1, * this value MUST be zero. If the fCounta field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol * field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 3. @@ -94,7 +101,8 @@ * Value Meaning * 0 The count subtotal function is not displayed. * 1 The count subtotal function is displayed. - *

                  + * + * * D - fAverage (1 bit): A bit that specifies whether the average subtotal function is displayed. If the fDefault field equals 1, this value MUST be zero. * If the fAverage field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage * field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 4. @@ -102,7 +110,8 @@ * Value Meaning * 0 The average subtotal function is not displayed. * 1 The average subtotal function is displayed. - *

                  + * + * * E - fMax (1 bit): A bit that specifies whether the max subtotal function is displayed. If the fDefault field equals 1, this value MUST be zero. * If the fMax field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage * field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 5. @@ -110,7 +119,8 @@ * Value Meaning * 0 The max subtotal function is not displayed. * 1 The max subtotal function is displayed. - *

                  + * + * * F - fMin (1 bit): A bit that specifies whether the min subtotal function is displayed. If the fDefault field equals 1, this value MUST be zero. * If the fMin field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage * field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 6. @@ -118,7 +128,8 @@ * Value Meaning * 0 The min subtotal function is not displayed. * 1 The min subtotal function is displayed. - *

                  + * + * * G - fProduct (1 bit): A bit that specifies whether the product subtotal function is displayed. If the fDefault field is 1, this value MUST be zero. * If the fProduct field is 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage * field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 7. @@ -126,7 +137,8 @@ * Value Meaning * 0 The product subtotal function is not displayed. * 1 The product subtotal function is displayed. - *

                  + * + * * H - fCount (1 bit): A bit that specifies whether the count numbers subtotal function is displayed. If the fDefault field is 1, * this value MUST be zero. If the fCount field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol * field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 8. @@ -134,7 +146,8 @@ * Value Meaning * 0 The count numbers subtotal function is not displayed. * 1 The count numbers subtotal function is displayed. - *

                  + * + * * I - fStdev (1 bit): A bit that specifies whether the standard deviation subtotal function is displayed. If the fDefault field is 1, * this value MUST be zero. If the fStdev field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field * equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 9. @@ -142,7 +155,8 @@ * Value Meaning * 0 The standard deviation subtotal function is not displayed. * 1 The standard deviation subtotal function is displayed. - *

                  + * + * * J - fStdevp (1 bit): A bit that specifies whether the standard deviation population subtotal function is displayed. If the fDefault field equals 1, * this value MUST be zero. If the fStdevp field equals 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field * equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 10. @@ -150,7 +164,8 @@ * Value Meaning * 0 The standard deviation population subtotal function is not displayed. * 1 The standard deviation population subtotal function is displayed. - *

                  + * + * * K - fVariance (1 bit): A bit that specifies whether the variance subtotal function is displayed. If the fDefault field is 1, * this value MUST be zero. If the fVariance field is 1 and the sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol * field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 11. @@ -158,7 +173,8 @@ * Value Meaning * 0 The variance subtotal function is not displayed. * 1 The variance subtotal function is displayed. - *

                  + * + * * L - fVariancep (1 bit): A bit that specifies whether the variance population subtotal function is displayed. If the fDefault field is 1, * the value MUST be zero. If the fVariancep field equals 1 and sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field * equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 12. @@ -166,171 +182,76 @@ * Value Meaning * 0 The variance population subtotal function is not displayed. * 1 The variance population subtotal function is displayed. - *

                  + * + * * M - reserved (4 bits): MUST be zero, and MUST be ignored. - *

                  + * + * * cItm (2 bytes): A signed integer that specifies the number of pivot items for this pivot field. This value MUST match the number of * SXVI records following this record and MUST be less than or equal to the following formula: * 32500 + the cSub field - *

                  + * + * * cchName (2 bytes): An unsigned integer that specifies the length, in characters, of the stName field. * If the value is 0xFFFF then stName is NULL. The value MUST be 0xFFFF or greater than zero and less than or equal to 255. - *

                  + * + * * stName (variable): An XLUnicodeStringNoCch structure that specifies the caption of this pivot field. * A non-NULL value specifies that this string is used to override the stFieldName field in * SXFDB record from the associated cache field, as specified in pivot fields. The length is specified in cchName. * This field exists only if the value of cchName is not 0xFFFF. If this PivotTable view is not an OLAP PivotTable view * and this string is non-NULL, then stName MUST be unique within all Sxvd records in this PivotTable view. - **/ -public class Sxvd extends XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6537376162863865578L; - - public static final short AXIS_NONE = 0; - public static final short AXIS_ROW = 1; - public static final short AXIS_COL = 2; - public static final short AXIS_PAGE = 4; - public static final short AXIS_DATA = 8; + */ +class Sxvd : XLSRecord() { - private short cSub = -1; - private short cItm = -1; - private short axis = -1; - private short cchName = -1; - private String caption = null; + private var cSub: Short = -1 + private var cItm: Short = -1 + private var axis: Short = -1 + private var cchName: Short = -1 + private var caption: String? = null // flags: - boolean fDefault, fSum, fCounta, fAverage, fMax, fMin, fProduct, fCount, fStdev, fStdevp, fVariance, fVariancep; - - - public void init() { - super.init(); - axis = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - cSub = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - cItm = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - cchName = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - if (cchName != -1) { - byte encoding = this.getByteAt(10); - byte[] tmp = this.getBytesAt(11, (cchName) * (encoding + 1)); - try { - if (encoding == 0) - caption = new String(tmp, DEFAULTENCODING); - else - caption = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding PivotTable caption name in Sxvd: " + e); - } - } - // type of subtotal funtion - byte b0 = this.getByteAt(4); - byte b1 = this.getByteAt(5); - fDefault = ((b0 & 0x1) == 0x1); // default subtotal -- if 1, the rest of these flags are ignored - fSum = ((b0 & 0x2) == 0x2); // sum subtotal function -if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 2. - fCounta = ((b0 & 0x4) == 0x4); // count subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 3. - fAverage = ((b0 & 0x8) == 0x8); // average subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 4. - fMax = ((b0 & 0x10) == 0x10); // max subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 5. - fMin = ((b0 & 0x20) == 0x20); // min subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 6. - fProduct = ((b0 & 0x40) == 0x40); // product subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 7. - fCount = ((b0 & 0x80) == 0x80); // count numbers function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 8. - fStdev = ((b1 & 0x1) == 0x1); // standard deviation function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 9. - fStdevp = ((b1 & 0x2) == 0x2); // standard deviation population function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 10. - fVariance = ((b1 & 0x4) == 0x4);// variance subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 11. - fVariancep = ((b1 & 0x8) == 0x8);// variance population subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 12. - - if (DEBUGLEVEL > 3) - Logger.logInfo("SXVD - axis:" + getAxisTypeName() + " cSub:" + cSub + " cItm:" + cItm + " default?" + fDefault + " sum?" + fSum + " caption:" + caption); - } + internal var fDefault: Boolean = false + internal var fSum: Boolean = false + internal var fCounta: Boolean = false + internal var fAverage: Boolean = false + internal var fMax: Boolean = false + internal var fMin: Boolean = false + internal var fProduct: Boolean = false + internal var fCount: Boolean = false + internal var fStdev: Boolean = false + internal var fStdevp: Boolean = false + internal var fVariance: Boolean = false + internal var fVariancep: Boolean = false /** * returns the String representation of the subtotal function for the axis defined in this record * * @return */ - public String getSubTotalFunction() { - if (fDefault) return "Default"; - if (fSum) return "Sum"; - if (fCounta) return "Count"; - if (fAverage) return "Average"; - if (fMax) return "Max"; - if (fMin) return "Min"; - if (fProduct) return "Product"; - //if (fCount) - if (fStdev) return "Stddev"; - if (fStdevp) return "StddevP"; - if (fVariance) return "Variance"; - if (fVariancep) return "VarianceP"; - return "Default"; - } - - /** - * Sets the subtotal function(s) for this pivot field. - *

                • "Default"; - *
                • "Sum"; - *
                • "Count"; - *
                • "Average"; - *
                • "Max"; - *
                • "Min"; - *
                • "Product"; - *
                • "Stddev"; - *
                • "StddevP"; - *
                • "Variance"; - *
                • "VarianceP"; - * - * @param f - * @return - */ - public void setSubTotalFunction(String[] f) { - if (f == null) f = new String[]{"Default"}; - cSub = (short) f.length; - for (int i = 0; i < cSub; i++) { - fDefault = f[i].equalsIgnoreCase("Default"); - fSum = f[i].equalsIgnoreCase("Sum"); - fCounta = f[i].equalsIgnoreCase("Count"); - fAverage = f[i].equalsIgnoreCase("Average"); - fMax = f[i].equalsIgnoreCase("Max"); - fMin = f[i].equalsIgnoreCase("Min"); - fProduct = f[i].equalsIgnoreCase("Product"); - //if (fCount) - fStdev = f[i].equalsIgnoreCase("Stddev"); - fStdevp = f[i].equalsIgnoreCase("StddevP"); - fVariance = f[i].equalsIgnoreCase("Variance"); - fVariancep = f[i].equalsIgnoreCase("VarianceP"); + //if (fCount) + val subTotalFunction: String + get() { + if (fDefault) return "Default" + if (fSum) return "Sum" + if (fCounta) return "Count" + if (fAverage) return "Average" + if (fMax) return "Max" + if (fMin) return "Min" + if (fProduct) return "Product" + if (fStdev) return "Stddev" + if (fStdevp) return "StddevP" + if (fVariance) return "Variance" + return if (fVariancep) "VarianceP" else "Default" } - if (fDefault) cSub = 1; // is this correct? - // update record - byte[] b = ByteTools.shortToLEBytes(cSub); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - - b[0] = 0; - b[1] = 0; - if (fDefault) b[0] |= 0x1; - if (fSum) b[0] |= 0x2; - if (fCounta) b[0] |= 0x4; - if (fAverage) b[0] |= 0x8; - if (fMax) b[0] |= 0x10; - if (fMin) b[0] |= 0x20; - if (fProduct) b[0] |= 0x40; - if (fCount) b[0] |= (byte) 0x80; - if (fStdev) b[1] |= 0x1; - if (fStdevp) b[1] |= 0x2; - if (fVariance) b[1] |= 0x4; - if (fVariancep) b[1] |= 0x8; - - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - } /** * returns the axis type int * * @return */ - int getAxisType() { - return axis; - } + internal val axisType: Int + get() = axis.toInt() /** * returns the Pivot Table Axis this Sxvd record defines @@ -338,90 +259,191 @@ int getAxisType() { * @return * @see AXIS_ constants */ - String getAxisTypeName() { - String ret = ""; - switch (axis) { - case AXIS_NONE: - ret = "NONE"; - break; - case Sxvd.AXIS_ROW: - ret = "ROW"; - break; - case Sxvd.AXIS_COL: - ret = "COL"; - break; - case Sxvd.AXIS_PAGE: - ret = "PAGE"; - break; - case Sxvd.AXIS_DATA: - ret = "DATA"; - break; + internal val axisTypeName: String + get() { + var ret = "" + when (axis) { + AXIS_NONE -> ret = "NONE" + Sxvd.AXIS_ROW -> ret = "ROW" + Sxvd.AXIS_COL -> ret = "COL" + Sxvd.AXIS_PAGE -> ret = "PAGE" + Sxvd.AXIS_DATA -> ret = "DATA" + } + return ret } - return ret; - } /** - * sets the axis for this pivot field - *
                • 1= row - *
                • 2= col - *
                • 4= page - *
                • 8= data + * returns the number of pivot items for this pivot field * - * @param axis - * @see Sxvd.AXIS_ constants + * @return */ - public void setAxis(int axis) { - if (!(axis == 1 || axis == 2 || axis == 4 || axis == 8)) return; - this.axis = (short) axis; - byte[] b = ByteTools.shortToLEBytes(this.axis); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - // TODO if axis is data MUST have a SxDI otherwise if axis WAS a data axis must remove SxDI - } + internal var numItems: Int + get() = cItm.toInt() + set(n) { + cItm = n.toShort() + val b = ByteTools.shortToLEBytes(cItm) + this.getData()[6] = b[0] + this.getData()[7] = b[1] + } /** - * returns the number of pivot items for this pivot field + * returns the number of subtotal functions set for this pivot field * * @return */ - int getNumItems() { - return cItm; - } + internal val numSubtotals: Int + get() = cSub.toInt() + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, /* axis */ + 1, 0, /* cSub */ + 1, 0, /* flags */ + 0, 0, /* cItm */ + -1, -1)/* cchName */ + - public void setNumItems(int n) { - cItm = (short) n; - byte[] b = ByteTools.shortToLEBytes(cItm); - this.getData()[6] = b[0]; - this.getData()[7] = b[1]; + override fun init() { + super.init() + axis = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + cSub = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + cItm = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + cchName = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + if (cchName.toInt() != -1) { + val encoding = this.getByteAt(10) + val tmp = this.getBytesAt(11, cchName * (encoding + 1)) + try { + if (encoding.toInt() == 0) + caption = String(tmp!!, XLSConstants.DEFAULTENCODING) + else + caption = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding PivotTable caption name in Sxvd: $e") + } + + } + // type of subtotal funtion + val b0 = this.getByteAt(4) + val b1 = this.getByteAt(5) + fDefault = b0 and 0x1 == 0x1 // default subtotal -- if 1, the rest of these flags are ignored + fSum = b0 and 0x2 == 0x2 // sum subtotal function -if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 2. + fCounta = b0 and 0x4 == 0x4 // count subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 3. + fAverage = b0 and 0x8 == 0x8 // average subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 4. + fMax = b0 and 0x10 == 0x10 // max subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 5. + fMin = b0 and 0x20 == 0x20 // min subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 6. + fProduct = b0 and 0x40 == 0x40 // product subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 7. + fCount = b0 and 0x80 == 0x80 // count numbers function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 8. + fStdev = b1 and 0x1 == 0x1 // standard deviation function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 9. + fStdevp = b1 and 0x2 == 0x2 // standard deviation population function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 10. + fVariance = b1 and 0x4 == 0x4// variance subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 11. + fVariancep = b1 and 0x8 == 0x8// variance population subtotal function - if so: if sxaxis.sxaxisRw field equals 1 or if the sxaxis.sxaxisCol field equals 1 or if the sxaxis.sxaxisPage field equals 1, there MUST be one SXVI record with the itmType field of the SXVI record equal to 12. + + if (DEBUGLEVEL > 3) + Logger.logInfo("SXVD - axis:$axisTypeName cSub:$cSub cItm:$cItm default?$fDefault sum?$fSum caption:$caption") } /** - * returns the number of subtotal functions set for this pivot field + * Sets the subtotal function(s) for this pivot field. + * * "Default"; + * * "Sum"; + * * "Count"; + * * "Average"; + * * "Max"; + * * "Min"; + * * "Product"; + * * "Stddev"; + * * "StddevP"; + * * "Variance"; + * * "VarianceP"; * + * @param f * @return */ - int getNumSubtotals() { - return cSub; + fun setSubTotalFunction(f: Array?) { + var f = f + if (f == null) f = arrayOf("Default") + cSub = f!!.size.toShort() + for (i in 0 until cSub) { + fDefault = f!![i].equals("Default", ignoreCase = true) + fSum = f!![i].equals("Sum", ignoreCase = true) + fCounta = f!![i].equals("Count", ignoreCase = true) + fAverage = f!![i].equals("Average", ignoreCase = true) + fMax = f!![i].equals("Max", ignoreCase = true) + fMin = f!![i].equals("Min", ignoreCase = true) + fProduct = f!![i].equals("Product", ignoreCase = true) + //if (fCount) + fStdev = f!![i].equals("Stddev", ignoreCase = true) + fStdevp = f!![i].equals("StddevP", ignoreCase = true) + fVariance = f!![i].equals("Variance", ignoreCase = true) + fVariancep = f!![i].equals("VarianceP", ignoreCase = true) + } + if (fDefault) cSub = 1 // is this correct? + // update record + val b = ByteTools.shortToLEBytes(cSub) + this.getData()[2] = b[0] + this.getData()[3] = b[1] + + b[0] = 0 + b[1] = 0 + if (fDefault) b[0] = b[0] or 0x1 + if (fSum) b[0] = b[0] or 0x2 + if (fCounta) b[0] = b[0] or 0x4 + if (fAverage) b[0] = b[0] or 0x8 + if (fMax) b[0] = b[0] or 0x10 + if (fMin) b[0] = b[0] or 0x20 + if (fProduct) b[0] = b[0] or 0x40 + if (fCount) b[0] = b[0] or 0x80.toByte() + if (fStdev) b[1] = b[1] or 0x1 + if (fStdevp) b[1] = b[1] or 0x2 + if (fVariance) b[1] = b[1] or 0x4 + if (fVariancep) b[1] = b[1] or 0x8 + + this.getData()[4] = b[0] + this.getData()[5] = b[1] } - private byte[] PROTOTYPE_BYTES = new byte[]{ - 0, 0, /* axis */ - 1, 0, /* cSub */ - 1, 0, /* flags */ - 0, 0, /* cItm */ - -1, -1, /* cchName */ - }; + /** + * sets the axis for this pivot field + * * 1= row + * * 2= col + * * 4= page + * * 8= data + * + * @param axis + * @see Sxvd.AXIS_ constants + */ + fun setAxis(axis: Int) { + if (!(axis == 1 || axis == 2 || axis == 4 || axis == 8)) return + this.axis = axis.toShort() + val b = ByteTools.shortToLEBytes(this.axis) + this.getData()[0] = b[0] + this.getData()[1] = b[1] + // TODO if axis is data MUST have a SxDI otherwise if axis WAS a data axis must remove SxDI + } - public static XLSRecord getPrototype() { - Sxvd sv = new Sxvd(); - sv.setOpcode(SXVD); - sv.setData(sv.PROTOTYPE_BYTES); - sv.init(); - return sv; + override fun toString(): String { + return "SXVD - axis:$axisTypeName cSub:$cSub cItm:$cItm default?$fDefault sum?$fSum caption:$caption" } - public String toString() { - return "SXVD - axis:" + getAxisTypeName() + " cSub:" + cSub + " cItm:" + cItm + " default?" + fDefault + " sum?" + fSum + " caption:" + caption; + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -6537376162863865578L + + val AXIS_NONE: Short = 0 + val AXIS_ROW: Short = 1 + val AXIS_COL: Short = 2 + val AXIS_PAGE: Short = 4 + val AXIS_DATA: Short = 8 + + val prototype: XLSRecord? + get() { + val sv = Sxvd() + sv.opcode = XLSConstants.SXVD + sv.setData(sv.PROTOTYPE_BYTES) + sv.init() + return sv + } } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxvi.java b/src/main/java/io/starter/formats/XLS/Sxvi.java deleted file mode 100644 index bdd056c..0000000 --- a/src/main/java/io/starter/formats/XLS/Sxvi.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; - - -/** - * SXVI B2h: This record stores view information about an Item. - * itmType (2 bytes): A signed integer that specifies the pivot item type. - * The value MUST be one of the following values: - * Value Name Meaning - * 0x0000 itmtypeData A data value - * 0x0001 itmtypeDEFAULT Default subtotal for the pivot field - * 0x0002 itmtypeSUM Sum of values in the pivot field - * 0x0003 itmtypeCOUNTA Count of values in the pivot field - * 0x0004 itmtypeAVERAGE Average of values in the pivot field - * 0x0005 itmtypeMAX Max of values in the pivot field - * 0x0006 itmtypeMIN Min of values in the pivot field - * 0x0007 itmtypePRODUCT Product of values in the pivot field - * 0x0008 itmtypeCOUNT Count of numbers in the pivot field - * 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field - * 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field - * 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field - * 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field - *

                  - * A - fHidden (1 bit): A bit that specifies whether this pivot item is hidden. - * MUST be zero if itmType is not itmtypeData. MUST be zero for OLAP PivotTable view. - *

                  - * B - fHideDetail (1 bit): A bit that specifies whether the pivot item detail is collapsed. - * MUST be zero for OLAP PivotTable view. - *

                  - * C - reserved1 (1 bit): MUST be zero, and MUST be ignored. - *

                  - * D - fFormula (1 bit): A bit that specifies whether this pivot item is a calculated item. - * This field MUST be zero if any of the following apply: - * itmType is not zero. - * This item is in an OLAP PivotTable view. - * The sxaxisPage field of sxaxis in the Sxvd record of the pivot field equals 1 (the associated Sxvd is the last Sxvd record before this record in the stream (1)). - * The fCalculatedField field in the SXVDEx record of the pivot field equals 1. - * There is not an associated SXFDB record in the associated PivotCache. - * The fRangeGroup field of the SXFDB record, of the associated cache field of the pivot field, equals 1. - * The fCalculatedField field of the SXFDB record, of the associated cache field of the pivot field, equals 1. - *

                  - * E - fMissing (1 bit): A bit that specifies if this pivot item does not exist in the data source (1). - * MUST be zero if itmType is not zero. MUST be zero for OLAP PivotTable view. - *

                  - * reserved2 (11 bits): MUST be zero, and MUST be ignored. - *

                  - * iCache (2 bytes): A signed integer that specifies a reference to a cache item. - * MUST be a value from the following table: - * Value Meaning - * -1 No cache item is referenced. - * 0+ A cache item index in the cache field associated with the pivot field, as specified by Cache Items. - * If itmType is not zero, a reference to a cache item is not specified and this value MUST be -1. - * Otherwise, this value MUST be greater than or equal to 0. - *

                  - * cchName (2 bytes): An unsigned integer that specifies the length of the stName string. - * If the value is 0xFFFF then stName is NULL. - * Otherwise, the value MUST be less than or equal to 254. - *

                  - * stName (variable): An XLUnicodeStringNoCch structure that specifies the name of this pivot item. - * If not NULL, this is used as the caption of the pivot item instead of the value in the - * cache item specified by iCache. The length of this field is specified in cchName. - * This field exists only if cchName is not 0xFFFF. If this is in a non-OLAP PivotTable - * view and this string is not NULL, it MUST be unique within all SXVI records in associated - * with the pivot field. - **/ - -public class Sxvi extends XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6399665481118265257L; - byte[] data = null; - short itemtype = -1; - short cchName = -1; - String name = null; - short iCache = -1; - boolean fHidden, fHideDetail, fFormula, fMissing; - - public static final short itmtypeData = 0x0000; //A data value - public static final short itmtypeDEFAULT = 0x0001; //Default subtotal for the pivot field - public static final short itmtypeSUM = 0x0002; //Sum of values in the pivot field - public static final short itmtypeCOUNTA = 0x0003; //Count of values in the pivot field - public static final short itmtypeAVERAGE = 0x0004; // Average of values in the pivot field - public static final short itmtypeMAX = 0x0005; // Max of values in the pivot field - public static final short itmtypeMIN = 0x0006; //Min of values in the pivot field - public static final short itmtypePRODUCT = 0x0007; //Product of values in the pivot field - public static final short itmtypeCOUNT = 0x0008; //Count of numbers in the pivot field - public static final short itmtypeSTDEV = 0x0009; //Statistical standard deviation (estimate) of the pivot field - public static final short itmtypeSTDEVP = 0x000A; //Statistical standard deviation (entire population) of the pivot field - public static final short itmtypeVAR = 0x000B; //Statistical variance (estimate) of the pivot field - public static final short itmtypeVARP = 0x000C; //Statistical variance (entire population) of the pivot field - - - public void init() { - super.init(); - itemtype = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - byte b = this.getByteAt(2); - fHidden = (b & 0x1) == 0x1; - fHideDetail = (b & 0x2) == 0x2; - // bit 3- reserved - fFormula = (b & 0x8) == 0x8; - fMissing = (b & 0x10) == 0x10; - iCache = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - cchName = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - if (cchName != -1) { - byte encoding = this.getByteAt(10); - byte[] tmp = this.getBytesAt(11, (cchName) * (encoding + 1)); - try { - if (encoding == 0) - name = new String(tmp, DEFAULTENCODING); - else - name = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding PivotTable caption name in Sxvd: " + e); - } - } - if (DEBUGLEVEL > 3) Logger.logInfo("SXVI - itemtype:" + itemtype + " iCache: " + iCache + " name:" + name); - } - - public String toString() { - return "SXVI - itemtype:" + itemtype + " iCache: " + iCache + " name:" + name; - } - - /** - * returns the type of this pivot item - *
                  one of: - *

                • 0x0000 itmtypeData A data value - *
                • 0x0001 itmtypeDEFAULT Default subtotal for the pivot field - *
                • 0x0002 itmtypeSUM Sum of values in the pivot field - *
                • 0x0003 itmtypeCOUNTA Count of values in the pivot field - *
                • 0x0004 itmtypeAVERAGE Average of values in the pivot field - *
                • 0x0005 itmtypeMAX Max of values in the pivot field - *
                • 0x0006 itmtypeMIN Min of values in the pivot field - *
                • 0x0007 itmtypePRODUCT Product of values in the pivot field - *
                • 0x0008 itmtypeCOUNT Count of numbers in the pivot field - *
                • 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field - *
                • 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field - *
                • 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field - *
                • 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field - * - * @return - */ - public short getItemType() { - return itemtype; - } - - /** - * sets the pivot item type: - *
                  one of: - *
                • 0x0000 itmtypeData A data value - *
                • 0x0001 itmtypeDEFAULT Default subtotal for the pivot field - *
                • 0x0002 itmtypeSUM Sum of values in the pivot field - *
                • 0x0003 itmtypeCOUNTA Count of values in the pivot field - *
                • 0x0004 itmtypeAVERAGE Average of values in the pivot field - *
                • 0x0005 itmtypeMAX Max of values in the pivot field - *
                • 0x0006 itmtypeMIN Min of values in the pivot field - *
                • 0x0007 itmtypePRODUCT Product of values in the pivot field - *
                • 0x0008 itmtypeCOUNT Count of numbers in the pivot field - *
                • 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field - *
                • 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field - *
                • 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field - *
                • 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field - * - * @return - */ - public void setItemType(int type) { - itemtype = (short) type; - byte[] b = ByteTools.shortToLEBytes(itemtype); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - /** - * reference to a cache item : - *
                  -1 No cache item is referenced. - *
                  0+ A cache item index in the cache field associated with the pivot field, as specified by Cache Items. - * - * @param icache - */ - public void setCacheItem(int icache) { - this.iCache = (short) icache; - byte[] b = ByteTools.shortToLEBytes(this.iCache); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - } - - /** - * returns the name of this pivot item; if not null, is the caption - * for this pivot item - * - * @return - */ - public String getName() { - return name; - } - - /** - * returns the name of this pivot item; if not null, is the caption - * for this pivot item - */ - public void setName(String name) { - this.name = name; - byte[] data = new byte[8]; - System.arraycopy(this.getData(), 0, data, 0, 7); - if (name != null) { - byte[] strbytes = null; - try { - strbytes = this.name.getBytes(DEFAULTENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding pivot table name in SXVI: " + e); - } - - //update the lengths: - cchName = (short) strbytes.length; - byte[] nm = ByteTools.shortToLEBytes(cchName); - data[6] = nm[0]; - data[7] = nm[1]; - - // now append variable-length string data - byte[] newrgch = new byte[cchName + 1]; // account for encoding bytes - System.arraycopy(strbytes, 0, newrgch, 1, cchName); - - data = ByteTools.append(newrgch, data); - } else { - data[6] = -1; - data[7] = -1; - } - this.setData(data); - } - - /** - * returns true if this pivot item is hidden - * - * @return - */ - public boolean getIsHidden() { - return fHidden; - } - - /** - * sets the hidden state for this pivot item - * - * @param b - */ - public void setIsHidden(boolean b) { - fHidden = b; - byte by = this.getByteAt(2); - if (fHidden) - this.getData()[2] = (byte) (by & 0x1); - else - this.getData()[2] = (byte) (by ^ 0x1); - } - - /** - * specifies whether the pivot item detail is collapsed. - * - * @return - */ - public boolean getIsCollapsed() { - return fHideDetail; - } - - /** - * specifies whether the pivot item detail is collapsed. - * - * @return - */ - public void setIsCollapsed(boolean b) { - fHideDetail = b; - byte by = this.getByteAt(2); - if (fHideDetail) - this.getData()[2] = (byte) (by & 0x2); - else - this.getData()[2] = (byte) (by ^ 0x2); - } - // TODO: fFormula, fMissing - - - private byte[] PROTOTYPE_BYTES = new byte[]{ - 0, 0, /* itmtype */ - 0, 0, /* flags */ - 0, 0, /* icache */ - -1, -1, /* cchName */ - }; - - public static XLSRecord getPrototype() { - Sxvi si = new Sxvi(); - si.setOpcode(SXVI); - si.setData(si.PROTOTYPE_BYTES); - si.init(); - return si; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxvi.kt b/src/main/java/io/starter/formats/XLS/Sxvi.kt new file mode 100644 index 0000000..8d32aed --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Sxvi.kt @@ -0,0 +1,328 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.UnsupportedEncodingException + + +/** + * SXVI B2h: This record stores view information about an Item. + * itmType (2 bytes): A signed integer that specifies the pivot item type. + * The value MUST be one of the following values: + * Value Name Meaning + * 0x0000 itmtypeData A data value + * 0x0001 itmtypeDEFAULT Default subtotal for the pivot field + * 0x0002 itmtypeSUM Sum of values in the pivot field + * 0x0003 itmtypeCOUNTA Count of values in the pivot field + * 0x0004 itmtypeAVERAGE Average of values in the pivot field + * 0x0005 itmtypeMAX Max of values in the pivot field + * 0x0006 itmtypeMIN Min of values in the pivot field + * 0x0007 itmtypePRODUCT Product of values in the pivot field + * 0x0008 itmtypeCOUNT Count of numbers in the pivot field + * 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field + * 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field + * 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field + * 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field + * + * + * A - fHidden (1 bit): A bit that specifies whether this pivot item is hidden. + * MUST be zero if itmType is not itmtypeData. MUST be zero for OLAP PivotTable view. + * + * + * B - fHideDetail (1 bit): A bit that specifies whether the pivot item detail is collapsed. + * MUST be zero for OLAP PivotTable view. + * + * + * C - reserved1 (1 bit): MUST be zero, and MUST be ignored. + * + * + * D - fFormula (1 bit): A bit that specifies whether this pivot item is a calculated item. + * This field MUST be zero if any of the following apply: + * itmType is not zero. + * This item is in an OLAP PivotTable view. + * The sxaxisPage field of sxaxis in the Sxvd record of the pivot field equals 1 (the associated Sxvd is the last Sxvd record before this record in the stream (1)). + * The fCalculatedField field in the SXVDEx record of the pivot field equals 1. + * There is not an associated SXFDB record in the associated PivotCache. + * The fRangeGroup field of the SXFDB record, of the associated cache field of the pivot field, equals 1. + * The fCalculatedField field of the SXFDB record, of the associated cache field of the pivot field, equals 1. + * + * + * E - fMissing (1 bit): A bit that specifies if this pivot item does not exist in the data source (1). + * MUST be zero if itmType is not zero. MUST be zero for OLAP PivotTable view. + * + * + * reserved2 (11 bits): MUST be zero, and MUST be ignored. + * + * + * iCache (2 bytes): A signed integer that specifies a reference to a cache item. + * MUST be a value from the following table: + * Value Meaning + * -1 No cache item is referenced. + * 0+ A cache item index in the cache field associated with the pivot field, as specified by Cache Items. + * If itmType is not zero, a reference to a cache item is not specified and this value MUST be -1. + * Otherwise, this value MUST be greater than or equal to 0. + * + * + * cchName (2 bytes): An unsigned integer that specifies the length of the stName string. + * If the value is 0xFFFF then stName is NULL. + * Otherwise, the value MUST be less than or equal to 254. + * + * + * stName (variable): An XLUnicodeStringNoCch structure that specifies the name of this pivot item. + * If not NULL, this is used as the caption of the pivot item instead of the value in the + * cache item specified by iCache. The length of this field is specified in cchName. + * This field exists only if cchName is not 0xFFFF. If this is in a non-OLAP PivotTable + * view and this string is not NULL, it MUST be unique within all SXVI records in associated + * with the pivot field. + */ + +class Sxvi : XLSRecord() { + internal var data: ByteArray? = null + /** + * returns the type of this pivot item + *

                  one of: + * * 0x0000 itmtypeData A data value + * * 0x0001 itmtypeDEFAULT Default subtotal for the pivot field + * * 0x0002 itmtypeSUM Sum of values in the pivot field + * * 0x0003 itmtypeCOUNTA Count of values in the pivot field + * * 0x0004 itmtypeAVERAGE Average of values in the pivot field + * * 0x0005 itmtypeMAX Max of values in the pivot field + * * 0x0006 itmtypeMIN Min of values in the pivot field + * * 0x0007 itmtypePRODUCT Product of values in the pivot field + * * 0x0008 itmtypeCOUNT Count of numbers in the pivot field + * * 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field + * * 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field + * * 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field + * * 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field + * + * @return + */ + var itemType: Short = -1 + internal set + internal var cchName: Short = -1 + internal var name: String? = null + internal var iCache: Short = -1 + internal var fHidden: Boolean = false + internal var fHideDetail: Boolean = false + internal var fFormula: Boolean = false + internal var fMissing: Boolean = false + + /** + * returns true if this pivot item is hidden + * + * @return + */ + /** + * sets the hidden state for this pivot item + * + * @param b + */ + var isHidden: Boolean + get() = fHidden + set(b) { + fHidden = b + val by = this.getByteAt(2) + if (fHidden) + this.getData()[2] = (by and 0x1).toByte() + else + this.getData()[2] = (by xor 0x1).toByte() + } + + /** + * specifies whether the pivot item detail is collapsed. + * + * @return + */ + /** + * specifies whether the pivot item detail is collapsed. + * + * @return + */ + var isCollapsed: Boolean + get() = fHideDetail + set(b) { + fHideDetail = b + val by = this.getByteAt(2) + if (fHideDetail) + this.getData()[2] = (by and 0x2).toByte() + else + this.getData()[2] = (by xor 0x2).toByte() + } + // TODO: fFormula, fMissing + + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, /* itmtype */ + 0, 0, /* flags */ + 0, 0, /* icache */ + -1, -1)/* cchName */ + + + override fun init() { + super.init() + itemType = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + val b = this.getByteAt(2) + fHidden = b and 0x1 == 0x1 + fHideDetail = b and 0x2 == 0x2 + // bit 3- reserved + fFormula = b and 0x8 == 0x8 + fMissing = b and 0x10 == 0x10 + iCache = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + cchName = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + if (cchName.toInt() != -1) { + val encoding = this.getByteAt(10) + val tmp = this.getBytesAt(11, cchName * (encoding + 1)) + try { + if (encoding.toInt() == 0) + name = String(tmp!!, XLSConstants.DEFAULTENCODING) + else + name = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding PivotTable caption name in Sxvd: $e") + } + + } + if (DEBUGLEVEL > 3) Logger.logInfo("SXVI - itemtype:$itemType iCache: $iCache name:$name") + } + + override fun toString(): String { + return "SXVI - itemtype:$itemType iCache: $iCache name:$name" + } + + /** + * sets the pivot item type: + *

                  one of: + * * 0x0000 itmtypeData A data value + * * 0x0001 itmtypeDEFAULT Default subtotal for the pivot field + * * 0x0002 itmtypeSUM Sum of values in the pivot field + * * 0x0003 itmtypeCOUNTA Count of values in the pivot field + * * 0x0004 itmtypeAVERAGE Average of values in the pivot field + * * 0x0005 itmtypeMAX Max of values in the pivot field + * * 0x0006 itmtypeMIN Min of values in the pivot field + * * 0x0007 itmtypePRODUCT Product of values in the pivot field + * * 0x0008 itmtypeCOUNT Count of numbers in the pivot field + * * 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field + * * 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field + * * 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field + * * 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field + * + * @return + */ + fun setItemType(type: Int) { + itemType = type.toShort() + val b = ByteTools.shortToLEBytes(itemType) + this.getData()[0] = b[0] + this.getData()[1] = b[1] + } + + /** + * reference to a cache item : + *

                  -1 No cache item is referenced. + *

                  0+ A cache item index in the cache field associated with the pivot field, as specified by Cache Items. + * + * @param icache + */ + fun setCacheItem(icache: Int) { + this.iCache = icache.toShort() + val b = ByteTools.shortToLEBytes(this.iCache) + this.getData()[4] = b[0] + this.getData()[5] = b[1] + } + + /** + * returns the name of this pivot item; if not null, is the caption + * for this pivot item + * + * @return + */ + fun getName(): String? { + return name + } + + /** + * returns the name of this pivot item; if not null, is the caption + * for this pivot item + */ + fun setName(name: String?) { + this.name = name + var data = ByteArray(8) + System.arraycopy(this.getData()!!, 0, data, 0, 7) + if (name != null) { + var strbytes: ByteArray? = null + try { + strbytes = this.name!!.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding pivot table name in SXVI: $e") + } + + //update the lengths: + cchName = strbytes!!.size.toShort() + val nm = ByteTools.shortToLEBytes(cchName) + data[6] = nm[0] + data[7] = nm[1] + + // now append variable-length string data + val newrgch = ByteArray(cchName + 1) // account for encoding bytes + System.arraycopy(strbytes, 0, newrgch, 1, cchName.toInt()) + + data = ByteTools.append(newrgch, data) + } else { + data[6] = -1 + data[7] = -1 + } + this.setData(data) + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 6399665481118265257L + + val itmtypeData: Short = 0x0000 //A data value + val itmtypeDEFAULT: Short = 0x0001 //Default subtotal for the pivot field + val itmtypeSUM: Short = 0x0002 //Sum of values in the pivot field + val itmtypeCOUNTA: Short = 0x0003 //Count of values in the pivot field + val itmtypeAVERAGE: Short = 0x0004 // Average of values in the pivot field + val itmtypeMAX: Short = 0x0005 // Max of values in the pivot field + val itmtypeMIN: Short = 0x0006 //Min of values in the pivot field + val itmtypePRODUCT: Short = 0x0007 //Product of values in the pivot field + val itmtypeCOUNT: Short = 0x0008 //Count of numbers in the pivot field + val itmtypeSTDEV: Short = 0x0009 //Statistical standard deviation (estimate) of the pivot field + val itmtypeSTDEVP: Short = 0x000A //Statistical standard deviation (entire population) of the pivot field + val itmtypeVAR: Short = 0x000B //Statistical variance (estimate) of the pivot field + val itmtypeVARP: Short = 0x000C //Statistical variance (entire population) of the pivot field + + val prototype: XLSRecord? + get() { + val si = Sxvi() + si.opcode = XLSConstants.SXVI + si.setData(si.PROTOTYPE_BYTES) + si.init() + return si + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Sxview.java b/src/main/java/io/starter/formats/XLS/Sxview.java deleted file mode 100644 index 3be2e19..0000000 --- a/src/main/java/io/starter/formats/XLS/Sxview.java +++ /dev/null @@ -1,1330 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.SxAddl.SxcView; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; - - -/** - * SXVIEW B0h: This record contains top-level pivot table information.
                  - *

                  - * ref (8 bytes): A Ref8U structure that specifies the PivotTable report body. For more information, see Location and Body.
                  - * rwFirstHead (2 bytes): An RwU structure that specifies the first row of the row area.
                  - * MUST be 1 if none of the axes are assigned in this PivotTable view.
                  - * Otherwise, the value MUST be greater than or equal to ref.rwFirst.
                  - *
                  - * rwFirstData (2 bytes): An RwU structure that specifies the first row of the data area.
                  - * MUST be 1 if none of the axes are assigned in this PivotTable view.
                  - * Otherwise, it MUST be equal to the value as specified by the following formula:
                  - *
                  - * rwFirstData = rwFirstHead + cDimCol
                  - *
                  - * colFirstData (2 bytes): A ColU structure that specifies the first column of the data area.
                  - * It MUST be 1 if none of the axes are assigned in this PivotTable view.
                  - * Otherwise, the value MUST be greater than or equal to ref.colFirst, and if the value of cDimCol or cDimData is not zero,
                  - * it MUST be less than or equal to ref.colLast.
                  - *
                  - * iCache (2 bytes): A signed integer that specifies the zero-based index of an SXStreamID record in the Globals substream.
                  - * MUST be greater than or equal to zero and less than the number of SXStreamID records in the Globals substream.
                  - *
                  - * reserved (2 bytes): MUST be zero, and MUST be ignored.
                  - *
                  - * sxaxis4Data (2 bytes): An SXAxis structure that specifies the default axis for the data field.
                  - * Either the sxaxis4Data.sxaxisRw field MUST be 1 or the sxaxis4Data.sxaxisCol field MUST be 1.
                  - * The sxaxis4Data.sxaxisPage field MUST be 0 and the sxaxis4Data.sxaxisData field MUST be 0.
                  - *
                  - * ipos4Data (2 bytes): A signed integer that specifies the row or column position for the data field in the PivotTable view.
                  - * The sxaxis4Data field specifies whether this is a row or column position.
                  - * MUST be greater than or equal to -1 and less than or equal to 0x7FFF. A value of -1 specifies the default position.
                  - *
                  - * cDim (2 bytes): A signed integer that specifies the number of pivot fields in the PivotTable view.
                  - * MUST equal the number of Sxvd records following this record.
                  - * MUST equal the number of fields in the associated PivotCache specified by iCache.
                  - *
                  - * cDimRw (2 bytes): An unsigned integer that specifies the number of fields on the row axis of the PivotTable view.
                  - * MUST be less than or equal to 0x7FFF. MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain row items.
                  - *
                  - * cDimCol (2 bytes): An unsigned integer that specifies the number of fields on the column axis of the PivotTable view.
                  - * MUST be less than or equal to 0x7FFF.
                  - * MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain column items.
                  - *
                  - * cDimPg (2 bytes): An unsigned integer that specifies the number of page fields in the PivotTable view.
                  - * MUST be less than or equal to 0x7FFF.
                  - * MUST equal the number of array elements in the SXPI record in this PivotTable view.
                  - *
                  - * cDimData (2 bytes): A signed integer that specifies the number of data fields in the PivotTable view.
                  - * MUST be greater than or equal to zero and less than or equal to 0x7FFF.
                  - * MUST equal the number of SXDI records in this PivotTable view.
                  - *
                  - * cRw (2 bytes): An unsigned integer that specifies the number of pivot lines in the row area of the PivotTable view.
                  - * MUST be less than or equal to 0x7FFF.
                  - * MUST equal the number of array elements in the first SXLI record in this PivotTable view.
                  - *
                  - * cCol (2 bytes): An unsigned integer that specifies the number of pivot lines in the column area of the PivotTable view.
                  - * MUST equal the number of array elements in the second SXLI record in this PivotTable view.
                  - *
                  - * A - fRwGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for rows.
                  - * MUST be 0 if none of the axes have been assigned in this PivotTable view.
                  - *
                  - * B - fColGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for columns.
                  - * MUST be 1 if none of the axes are assigned in this PivotTable view.
                  - *
                  - * C - unused1 (1 bit): Undefined and MUST be ignored.
                  - *
                  - * D - fAutoFormat (1 bit): A bit that specifies whether the PivotTable has AutoFormat applied.
                  - *
                  - * E - fAtrNum (1 bit): A bit that specifies whether the PivotTable has number AutoFormat applied.
                  - *
                  - * F - fAtrFnt (1 bit): A bit that specifies whether the PivotTable has font AutoFormat applied.
                  - *
                  - * G - fAtrAlc (1 bit): A bit that specifies whether the PivotTable has alignment AutoFormat applied.
                  - *
                  - * H - fAtrBdr (1 bit): A bit that specifies whether the PivotTable has border AutoFormat applied.
                  - *
                  - * I - fAtrPat (1 bit): A bit that specifies whether the PivotTable has pattern AutoFormat applied.
                  - *
                  - * J - fAtrProc (1 bit): A bit that specifies whether the PivotTable has width/height AutoFormat applied.
                  - *
                  - * unused2 (6 bits): Undefined and MUST be ignored.
                  - *
                  - * itblAutoFmt (2 bytes): An AutoFmt8 structure that specifies the PivotTable AutoFormat.
                  - * If the value of itblAutoFmt in the associated SXViewEx9 record is not 1, this field is overridden by the value of itblAutoFmt in the associated SXViewEx9.
                  - *
                  - * cchTableName (2 bytes): An unsigned integer that specifies the length, in characters, of stTable.
                  - * MUST be greater than or equal to zero and less than or equal to 0x00FF.
                  - *
                  - * cchDataName (2 bytes): An unsigned integer that specifies the length, in characters of stData.
                  - * MUST be greater than zero and less than or equal to 0x00FE.
                  - *
                  - * stTable (variable): An XLUnicodeStringNoCch structure that specifies the name of the PivotTable.
                  - * The length of this field is specified by cchTableName.
                  - *
                  - * stData (variable): An XLUnicodeStringNoCch structure that specifies the name of the data field.
                  - * The length of this field is specified by cchDataName.
                  - *
                  - * 
                  - **/ -public class Sxview extends XLSRecord implements XLSConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2639291289806138985L; - short rwFirst = 0x0; // First Row of Pivot Table - short rwLast = 0x0; // Last Row of Pivot Table - short colFirst = 0x0; // First Column of Pivot Table - short colLast = 0x0; // Last Column of Pivot Table - short rwFirstHead = 0x0; // First Row containing Pivot Table headings - short rwFirstData = 0x0; // First Row containing Pivot Table data - short colFirstData = 0x0; // First Column containing Pivot Table data - short iCache = 0x0; // Index to the Cache - // offset 20, length 2 is reserved, must be 0 (zero) - short sxaxis4Data = 0x0; // Default axis for a data field - short ipos4Data = 0x0; // Default position for a data field - short cDim = 0x0; // Number of fields - short cDimRw = 0x0; // Number of row fields; - short cDimCol = 0x0; // Number of column fields - short cDimPg = 0x0; // Number of page fields; - short cDimData = 0x0; // Number of Data fields - short cRw = 0x0; // Number of data rows - short cCol = 0x0; // Number of data columns - byte grbit1 = 0x0; // if you dont know what this is.... - byte grbit2 = 0x0; // if you dont know what this is.... - short itblAutoFmt = 0x0; // Index to the Pivot Table autoformat - short cchName = 0x0; // Length the Pivot Table name - short cchData = 0x0; // Length of the data field name - byte[] rgch;// PivotTableName followed by the name of a data field - - /* The following member variables are populated from parsing the - grbit field - */ - boolean fRwGrand = false; // = 1 if the Pivot Table contains grand totals for rows - boolean fColGrand = false; // = 1 if the Pivot Table contains grand totals for Columns - // bit 2, mask 0004h is reserved, must be 0 (zero) - boolean fAutoFormat = false; // = 1 if the Pivot table has an autoformat applied - boolean fWH = false; // = 1 if the width/height autoformat is applied - boolean fFont = false; // = 1 if the font autoformat is applied - boolean fAlign = false; // = 1 if the alignment autoformat is applied - boolean fBorder = false; // = 1 if the border autoformat is applied - boolean fPattern = false; // = 1 if the pattern autoformat is applied - boolean fNumber = false; // = 1 if the number autoformat is applied - - /* - The following member variables are derived from the rgch field - */ - String PivotTableName = null; - String DataFieldName = null; - - private ArrayList subRecs = new ArrayList(); - - private byte[] PROTOTYPE_BYTES = new byte[]{ - 0, 0, 0, 0, 0, 0, 0, 0, /* ref */ - 0, 0, /* rwfirstHead */ - 0, 0, /* rwfirstData */ - 0, 0, /* colfirstHead */ - 0, 0, /* iCache */ - 0, 0, /* reserved */ - 1, 0, /* saxis4Data */ - -1, -1, /* ipos4Data */ - 0, 0, /* cDim */ - 0, 0, /* cDimRw */ - 0, 0, /* cDimCol */ - 0, 0, /* cDimPg */ - 0, 0, /* cDimData */ - 0, 0, /* cRw */ - 0, 0, /* cCol */ - 11, 2, /* grbit + reserved */ - 1, 0, /* itblAutoFmt */ - 0, 0, /* cchTableName */ - 0, 0, /* cchDataName */ - - }; - - public static XLSRecord getPrototype() { - Sxview sx = new Sxview(); - sx.setOpcode(SXVIEW); - sx.setData(sx.PROTOTYPE_BYTES); - sx.init(); - return sx; - } - - public void init() { - super.init(); - if (this.getLength() <= 0) { // Is this record populated? - if (DEBUGLEVEL > -1) Logger.logInfo("no data in SXVIEW"); - } else { // parse out all the fields - rwFirst = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - rwLast = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - colFirst = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - colLast = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - rwFirstHead = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - rwFirstData = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - colFirstData = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - iCache = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); - - // 16 & 17 - reserved must be zero - - sxaxis4Data = ByteTools.readShort(this.getByteAt(18), this.getByteAt(19)); - ipos4Data = ByteTools.readShort(this.getByteAt(20), this.getByteAt(21)); - cDim = ByteTools.readShort(this.getByteAt(22), this.getByteAt(23)); - cDimRw = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); - cDimCol = ByteTools.readShort(this.getByteAt(26), this.getByteAt(27)); - cDimPg = ByteTools.readShort(this.getByteAt(28), this.getByteAt(29)); - cDimData = ByteTools.readShort(this.getByteAt(30), this.getByteAt(31)); - cRw = ByteTools.readShort(this.getByteAt(32), this.getByteAt(33)); - cCol = ByteTools.readShort(this.getByteAt(34), this.getByteAt(35)); - grbit1 = this.getByteAt(37); - grbit2 = this.getByteAt(36); - - - this.initGrbit(); // note the manual hibyting - itblAutoFmt = ByteTools.readShort(this.getByteAt(38), this.getByteAt(39)); - cchName = ByteTools.readShort(this.getByteAt(40), this.getByteAt(41)); - cchData = ByteTools.readShort(this.getByteAt(42), this.getByteAt(43)); - int fullnamelen = (int) cchName + ((int) cchData); - rgch = new byte[fullnamelen]; - int pos = 44; - if (cchName > 0) { - //A - fHighByte (1 bit): A bit that specifies whether the characters in rgb are double-byte characters. - // 0x0 All the characters in the string have a high byte of 0x00 and only the low bytes are in rgb. - // 0x1 All the characters in the string are saved as double-byte characters in rgb. - // reserved (7 bits): MUST be zero, and MUST be ignored. - - byte encoding = this.getByteAt(pos++); - - byte[] tmp = this.getBytesAt(pos, (cchName) * (encoding + 1)); - try { - if (encoding == 0) - PivotTableName = new String(tmp, DEFAULTENCODING); - else - PivotTableName = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding PivotTable name in Sxview: " + e); - } - pos += cchName * (encoding + 1); - } - if (cchData > 0) { - byte encoding = this.getByteAt(pos++); - byte[] tmp = this.getBytesAt(pos, (cchData) * (encoding + 1)); - try { - if (encoding == 0) - DataFieldName = new String(tmp, DEFAULTENCODING); - else - DataFieldName = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding PivotTable name in Sxview: " + e); - } - } - } - if (DEBUGLEVEL > 3) - Logger.logInfo("SXVIEW: name:" + this.getTableName() + " iCache:" + iCache + " cDim:" + cDim + " cDimRw:" + cDimRw + " cDimCol:" + cDimCol + " cDimPg:" + cDimPg + " cDimData:" + cDimData + " cRw:" + cRw + " cCol:" + cCol + " datafieldname:" + DataFieldName); - } - - /** - * specifies the default axis for the data field: - *
                • 1- row - *
                • 2= col - *
                • 4= page - *
                • 8= data - * - * @param s - */ - protected void setAxis4Data(short s) { - sxaxis4Data = s; - byte[] b = ByteTools.shortToLEBytes(sxaxis4Data); - System.arraycopy(b, 0, data, 18, 2); - } - - /** - * retieves the default axis for the data field: - *
                • 1- row - *
                • 2= col - *
                • 4= page - *
                • 8= data - */ - public short getSxaxis4Data() { - return sxaxis4Data; - } - - /** - * A signed integer that specifies the row or column position for the data field in the PivotTable view. - *
                  see getSxaxis4Data to determine whether this is a row or column position - * - * @param s - */ - protected void setIpos4Data(short s) { - ipos4Data = s; - byte[] b = ByteTools.shortToLEBytes(ipos4Data); - System.arraycopy(b, 0, data, 20, 2); - } - - /** - * A signed integer that specifies the row or column position for the data field in the PivotTable view. - *
                  see getSxaxis4Data to determine whether this is a row or column position - */ - public short getIpos4Data() { - return ipos4Data; - } - - - /** - * sets the number of pivot fields (i.e. columns in the source data) for this pivot table - *
                  NOTE: this method wipes any existing data of the pivot table - * - * @param s - */ - public void setNPivotFields(short s) { - cDim = s; // number of pivot fields MUST==# of SxVd records - byte[] b = ByteTools.shortToLEBytes(cDim); - System.arraycopy(b, 0, data, 22, 2); - // remove ALL field-related records - for (int i = 0; i < subRecs.size(); i++) { - BiffRec br = subRecs.get(i); - if (br.getOpcode() != SXEX) { - this.getSheet().removeRecFromVec(br); - subRecs.remove(i); - i--; - } else - break; - } - // reset variables - cDimRw = 0; - cDimCol = 0; - cDimPg = 0; - cDimData = 0; - cRw = 0; - cCol = 0; - // now add the required records for each field - int zz = this.getRecordIndex() + 1; - for (int i = 0; i < cDim; i++) { - Sxvd svd = (Sxvd) Sxvd.getPrototype(); // for each pivot field (which goes on an axis) - svd.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(zz++, svd); - this.subRecs.add(i * 2, svd); - SxVdEX svdex = (SxVdEX) SxVdEX.getPrototype(); - svdex.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(zz++, svdex); - this.subRecs.add((i * 2) + 1, svdex); - } - } - - /** - * adds the pivot field corresponding to cache field at index fieldIndex to the desired axis - *
                  A pivot field is a cache field that has been added to the pivot table - *
                  Pivot fields are defined by SXVD and associated records - *
                  the SXVD record stores which axis the field is on - *
                  there are cDim pivot fields on the pivot table view - *
                • 1= row - *
                • 2= col - *
                • 4= page - *
                • 8= data - * - * @param axis - * @param fieldIndex 0-based pivot field index - * @see SxVd.AXIS_ constants - */ - public Sxvd addPivotFieldToAxis(int axis, int fieldIndex) { - int zz = this.getRecordIndex() + 1; - SxVdEX sxvdex = (SxVdEX) getSubRec(SXVDEX, fieldIndex); // end of last pivot field set (PIVOTVD rule) - if (sxvdex != null) - zz = sxvdex.getRecordIndex() + 1; - - Sxvd sxvd = (Sxvd) Sxvd.getPrototype(); // for each pivot field (which goes on an axis) - sxvd.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(zz++, sxvd); - this.subRecs.add(cDim * 2, sxvd); - SxVdEX svdex = (SxVdEX) SxVdEX.getPrototype(); - svdex.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(zz++, svdex); - this.subRecs.add((cDim * 2) + 1, svdex); - cDim++; - sxvd.setAxis(axis); - return sxvd; - -/* - Sxvd sxvd= (Sxvd) getSubRec(SXVD, fieldIndex); - if (sxvd!=null) { - // got it - sxvd.setAxis(axis); - return sxvd; - } - return null;*/ - } - - - /** - * returns the number of pivot fields (==columns in the pivot table data range) - * - * @return - */ - public short getNPivotFields() { - return cDim; - } - - /** - * adds a pivot field (0-based index) to the ROW axis - * - * @param fieldNumber - */ - public void addRowField(int fieldNumber) { - cDimRw++; // MUST==# row elements in SxIVD - byte[] b = ByteTools.shortToLEBytes(cDimRw); - System.arraycopy(b, 0, data, 24, 2); - Sxivd sxivd = (Sxivd) getSubRec(SXIVD, (cDimCol > 0) ? 1 : 0); - if (sxivd == null) { - int zz = getPivotRecordInsertionIndexes(SXIVD, 1, -1); - if (zz > 0) { // should!!! - sxivd = (Sxivd) Sxivd.getPrototype(); - sxivd.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxivd); - this.subRecs.add(zz, sxivd); - } - } - sxivd.addField(fieldNumber); - } - - /** - * adds a pivot field (0-based index) to the COL axis - */ - public void addColField(int fieldNumber) { - cDimCol++; - byte[] b = ByteTools.shortToLEBytes(cDimCol); - System.arraycopy(b, 0, data, 26, 2); - Sxivd sxivd = (Sxivd) getSubRec(SXIVD, 0); - if (sxivd == null) { - int zz = getPivotRecordInsertionIndexes(SXIVD, 0, -1); - if (zz > 0) { // should!! - sxivd = (Sxivd) Sxivd.getPrototype(); - sxivd.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxivd); - this.subRecs.add(zz, sxivd); - } - } - sxivd.addField(fieldNumber); - } - - /** - * returns the number of pivot fields on the ROW axis - * - * @return - */ - public short getCDimRw() { - return cDimRw; - } - - - /** - * returns the number of pivot fields on the COL axis - * - * @return - */ - public short getCDimCol() { - return cDimCol; - } - - /** - * adds a pivot field to the page axis - * - * @param strref - */ - public void addPageField(int fieldIndex, int itemIndex) { - cDimPg++; - byte[] b = ByteTools.shortToLEBytes(cDimPg); - System.arraycopy(b, 0, data, 28, 2); - SxPI sxpi = (SxPI) getSubRec(SXPI, -1); - if (sxpi == null) { - int zz = getPivotRecordInsertionIndexes(SXPI, -1, -1); - if (zz > 0) { // should!!! - sxpi = (SxPI) SxPI.getPrototype(); - sxpi.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxpi); - this.subRecs.add(zz, sxpi); - } - } - sxpi.addPageField(fieldIndex, itemIndex); - } - - /** - * returns the number of fields on the page axis - * - * @return - */ - public short getCDimPg() { - return cDimPg; - } - - /** - * adds a pivot field to the DATA axis - * - * @param fieldIndex - * @param aggregateFunction - */ - public void addDataField(int fieldIndex, String aggregateFunction, String name) { - cDimData++; - byte[] b = ByteTools.shortToLEBytes(cDimData); - System.arraycopy(b, 0, data, 30, 2); - SxDI sxdi = null; -/* SxDI sxdi= (SxDI) getSubRec(SXDI, -1); - if (sxdi==null) { */ - int zz = getPivotRecordInsertionIndexes(SXDI, -1, -1); - if (zz > 0) { // should!!! - sxdi = (SxDI) SxDI.getPrototype(); - sxdi.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxdi); - this.subRecs.add(zz, sxdi); - } - /* }*/ - sxdi.addDataField(fieldIndex, aggregateFunction, name); - } - - /** - * returns the number of pivot fields on the data axis - * - * @return - */ - public short getCDimData() { - return cDimData; - } - - /** - * adds a pivot item to the end of the list of items on this axis (ROW, COL, DATA or PAGE) - * - * @param axis Axis int: ROW, COL, DATA or PAGE - * @param itemType one of: - *
                • 0x0000 itmtypeData A data value - *
                • 0x0001 itmtypeDEFAULT Default subtotal for the pivot field - *
                • 0x0002 itmtypeSUM Sum of values in the pivot field - *
                • 0x0003 itmtypeCOUNTA Count of values in the pivot field - *
                • 0x0004 itmtypeAVERAGE Average of values in the pivot field - *
                • 0x0005 itmtypeMAX Max of values in the pivot field - *
                • 0x0006 itmtypeMIN Min of values in the pivot field - *
                • 0x0007 itmtypePRODUCT Product of values in the pivot field - *
                • 0x0008 itmtypeCOUNT Count of numbers in the pivot field - *
                • 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field - *
                • 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field - *
                • 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field - *
                • 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field - * @param cacheItem A cache item index in the cache field associated with the pivot field, as specified by Cache Items. - */ - public void addPivotItem(Sxvd axis, int itemType, int cacheItem) { - int n = axis.getNumItems(); // Axis record = Sxvd. Identifies pivot field on axis. Pivot items records follow until SXVDEX. - axis.setNumItems(n + 1); - int axisIndex = getSubRecIndex(axis); - int zz = getPivotRecordInsertionIndexes(SXVI, n, axisIndex); // get LAST pivot field on axis - Sxvi sxvi = (Sxvi) Sxvi.getPrototype(); // pivot item record - sxvi.setItemType(itemType); - sxvi.setCacheItem(cacheItem); - sxvi.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxvi); - subRecs.add(zz, sxvi); - /*if (axis.getAxisType()==Sxvd.AXIS_ROW) { - } else if (axis.getAxisType()==Sxvd.AXIS_COL) { - } else if (axis.getAxisType()==Sxvd.AXIS_PAGE) { - - } else if (axis.getAxisType()==Sxvd.AXIS_DATA) { - - }*/ - if (cacheItem != -1) { - PivotCache pc = this.getWorkBook().getPivotCache(); // TODO should this be here or in Sxstream? - pc.addCacheItem(iCache, cacheItem); // adds the cache item to the "used" list, as it were - } - } - - /** - * sets the number of pivot items or lines on the ROW axis - * - * @param strref - */ - public void addPivotLineToROWAxis(int repeat, int nLines, int type, short[] indexes) { - cRw++; - byte[] b = ByteTools.shortToLEBytes(cRw); - System.arraycopy(b, 0, data, 32, 2); - /** - If the value of either of the cRw or cCol fields of the associated SxView is greater than zero, - then two records of this type MUST exist in the file for the associated SxView. - The first record contains row area pivot lines and the second record contains column area pivot lines. */ - Sxli sxli = (Sxli) getSubRec(SXLI, 0); - if (sxli == null) { - int zz = getPivotRecordInsertionIndexes(SXLI, 0, -1); - if (zz > 0) { // should!!! - sxli = (Sxli) Sxli.getPrototype(this.getWorkBook()); - sxli.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxli); - this.subRecs.add(zz, sxli); - sxli.addField(repeat, nLines, type, indexes); - sxli = (Sxli) Sxli.getPrototype(this.getWorkBook()); - sxli.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz + 1).getRecordIndex(), sxli); - this.subRecs.add(zz + 1, sxli); - } - } else - sxli.addField(repeat, nLines, type, indexes); - } - - /** - * returns the number of pivot items or lines on the ROW axis - * - * @return - */ - public short getCRw() { - return cRw; - } - - protected boolean hasRowPivotItemsRecord() { - return (getSubRec(SXLI, 0) != null); - } - - - /** - * set the number of pivot items or lines on the COL axis - * - * @param strref - */ - public void addPivotLineToCOLAxis(int repeat, int nLines, int type, short[] indexes) { - cCol++; - byte[] b = ByteTools.shortToLEBytes(cCol); - System.arraycopy(b, 0, data, 34, 2); - Sxli sxli = (Sxli) getSubRec(SXLI, 1); - if (sxli == null) { - int zz = getPivotRecordInsertionIndexes(SXLI, 0, -1); - if (zz == -1) { // shouldn't! - sxli = (Sxli) Sxli.getPrototype(); - sxli.setSheet(this.getSheet()); - this.getSheet().getSheetRecs().add(subRecs.get(zz).getRecordIndex(), sxli); - this.subRecs.add(zz, sxli); - } - } - sxli.addField(repeat, nLines, type, indexes); - } - - /** - * returns the number of pivot items or lines on the COL axis - * - * @return - */ - public short getCCol() { - return cCol; - } - - /** - * iCache links this pivot table to a pivot data cache - * - * @param s - */ - public void setICache(short s) { - iCache = s; - byte[] b = ByteTools.shortToLEBytes(iCache); - System.arraycopy(b, 0, data, 14, 2); - } - - /** - * iCache links this pivot table to a pivot data cache - */ - public short getICache() { - return iCache; - } - - - /** - * Init the grbit, populate the member variables of the SXVIEW object. - * This can be called both at initial init, and later to reup the boolean fields - * after changes to the grbit. - */ - private void initGrbit() { - fRwGrand = (grbit1 & 0x1) == 0x1; - fColGrand = (grbit1 & 0x2) == 0x2; - fAutoFormat = (grbit1 & 0x8) == 0x8; - fWH = (grbit1 & 0x10) == 0x10; - fFont = (grbit1 & 0x20) == 0x20; - fAlign = (grbit1 & 0x40) == 0x40; - fBorder = (grbit1 & 0x80) == 0x80; - fPattern = (grbit2 & 0x1) == 0x1; - fNumber = (grbit2 & 0x2) == 0x2; - this.getData()[36] = grbit2; - this.getData()[37] = grbit1; - } - - /** - * store associated records for ease of lookup - * - * @param r - */ - public void addSubrecord(BiffRec r) { - subRecs.add(r); - } - /* - The following methods all change or get grbit fields - */ - - /** - * specifies whether the PivotTable contains grand totals for rows. - * MUST be 0 if none of the axes have been assigned in this PivotTable view. - */ - public void setFRwGrand(boolean b) { - if (b != fRwGrand) { - if (b == false) { - grbit1 = (byte) (grbit1 & 0xFE); - } else { - grbit1 = (byte) (grbit1 | 0x1); - } - this.initGrbit(); - } - } - - /** - * specifies whether the PivotTable contains grand totals for rows. - * MUST be 0 if none of the axes have been assigned in this PivotTable view. - */ - public boolean getFRwGrand() { - return fRwGrand; - } - - /** - * specifies whether the PivotTable contains grand totals for columns. - * MUST be 1 if none of the axes are assigned in this PivotTable view. - * - * @param b - */ - public void setColGrand(boolean b) { - if (b != fColGrand) { - if (b == false) { - grbit1 = (byte) (grbit1 & 0xFD); - } else { - grbit1 = (byte) (grbit1 | 0x2); - } - this.initGrbit(); - } - } - - /** - * specifies whether the PivotTable contains grand totals for columns. MUST be 1 if none of the axes are assigned in this PivotTable view. - */ - public boolean getFColGrand() { - return fColGrand; - } - - /** - * specifies whether the PivotTable has AutoFormat applied. - * - * @param b - */ - public void setFAutoFormat(boolean b) { - if (b != fAutoFormat) { - if (b == false) { - grbit1 = (byte) (grbit1 & 0xFB); - } else { - grbit1 = (byte) (grbit1 | 0x4); - } - this.initGrbit(); - } - } - - /** - * specifies whether the PivotTable has AutoFormat applied. - * - * @return - */ - public boolean getFAutoFormat() { - return fAutoFormat; - } - - - public void setFWH(boolean b) { - if (b != fWH) { - if (b == false) { - grbit1 = (byte) (grbit1 & 0xEF); - } else { - grbit1 = (byte) (grbit1 | 0x10); - } - this.initGrbit(); - } - } - - public boolean getFWH() { - return fWH; - } - - /** - * specifies whether the PivotTable has font AutoFormat applied. - * - * @param b - */ - public void setFFont(boolean b) { - if (b != fFont) { - if (b == false) { - grbit1 = (byte) (grbit1 & 0xDF); - } else { - grbit1 = (byte) (grbit1 | 0x20); - } - this.initGrbit(); - } - } - - /** - * specifies whether the PivotTable has font AutoFormat applied. - * - * @return - */ - public boolean getFFont() { - return fFont; - } - - /** - * specifies whether the PivotTable has alignment AutoFormat applied. - * - * @param b - */ - public void setFAlign(boolean b) { - if (b != fAlign) { - if (b == false) { - grbit1 = (byte) (grbit1 & 0xBF); - } else { - grbit1 = (byte) (grbit1 | 0x40); - } - this.initGrbit(); - } - } - - /** - * specifies whether the PivotTable has alignment AutoFormat applied. - * - * @return - */ - public boolean getFAlign() { - return fAlign; - } - - /** - * specifies whether the PivotTable has border AutoFormat applied. - * - * @param b - */ - public void setFBorder(boolean b) { - if (b != fBorder) { - if (b == false) { - grbit1 = (byte) (grbit1 & 0x7F); - } else { - grbit1 = (byte) (grbit1 | 0x80); - } - this.initGrbit(); - } - } - - /** - * specifies whether the PivotTable has border AutoFormat applied. - * - * @return - */ - public boolean getFBorder() { - return fBorder; - } - - /** - * specifies whether the PivotTable has pattern AutoFormat applied. - * - * @param b - */ - public void setFPattern(boolean b) { - if (b != fPattern) { - if (b == false) { - grbit2 = (byte) (grbit2 & 0xFE); - } else { - grbit2 = (byte) (grbit2 | 0x1); - } - this.initGrbit(); - } - } - - /** - * specifies whether the PivotTable has pattern AutoFormat applied. - * - * @return - */ - public boolean getFPattern() { - return fPattern; - } - - /** - * @param b - */ - public void setFNumber(boolean b) { - if (b != fNumber) { - if (b == false) { - grbit2 = (byte) (grbit2 & 0xFE); - } else { - grbit2 = (byte) (grbit2 | 0x1); - } - this.initGrbit(); - } - } - - public boolean getFNumber() { - return fNumber; - } - - - /** - * sets the location and size of this pivot table view - * - * @param range - */ - public void setLocation(String range) { - int[] rc = ExcelTools.getRangeRowCol(range); - setRwFirst((short) (rc[0])); - setColFirst((short) rc[1]); - setRwLast((short) (rc[2])); - setColLast((short) rc[3]); - } - /* - Set and get methods, these all basically do the same thing, update the - member variable, then update the body data with the new information. Simple, but large XLSRecord - */ - - /** - * sets the first row of the pivot table - */ - public void setRwFirst(short s) { - rwFirst = s; - byte[] b = ByteTools.shortToLEBytes(rwFirst); - System.arraycopy(b, 0, data, 0, 2); - } - - /** - * retrieves the first row of the pivot table - */ - public short getRwFirst() { - return rwFirst; - } - - /** - * sets the last row of the pivot table - */ - public void setRwLast(short s) { - rwLast = s; - byte[] b = ByteTools.shortToLEBytes(rwLast); - System.arraycopy(b, 0, data, 2, 2); - } - - /** - * retieves the first row of the pivot table - */ - public short getRwLast() { - return rwLast; - } - - /** - * sets the first column of the pivot table - */ - public void setColFirst(short s) { - colFirst = s; - byte[] b = ByteTools.shortToLEBytes(colFirst); - System.arraycopy(b, 0, data, 4, 2); - } - - /** - * retrieves the first column of the pivot table - */ - public short getColFirst() { - return colFirst; - } - - /** - * sets the last column of the pivot table - */ - public void setColLast(short s) { - colLast = s; - byte[] b = ByteTools.shortToLEBytes(colLast); - System.arraycopy(b, 0, data, 6, 2); - } - - /** - * sets the last column of the pivot table - */ - public short getColLast() { - return colLast; - } - - public void setRwFirstHead(short s) { - rwFirstHead = s; - byte[] b = ByteTools.shortToLEBytes(rwFirstHead); - System.arraycopy(b, 0, data, 8, 2); - } - - public short getRwFirstHead() { - return rwFirstHead; - } - - public void setRwFirstData(short s) { - rwFirstData = s; - byte[] b = ByteTools.shortToLEBytes(rwFirstData); - System.arraycopy(b, 0, data, 10, 2); - } - - public short getRwFirstData() { - return rwFirstData; - } - - public void setColFirstData(short s) { - colFirstData = s; - byte[] b = ByteTools.shortToLEBytes(colFirstData); - System.arraycopy(b, 0, data, 12, 2); - } - - public short getColFirstData() { - return colFirstData; - } - - /* - Probably want to use the individual grbit options instead of this, but - hey, I'm being thorough. - */ - public void setGrbit(short s) { - byte[] b = ByteTools.shortToLEBytes(s); - grbit2 = b[0]; - grbit1 = b[1]; - System.arraycopy(b, 0, data, 36, 2); - this.initGrbit(); - } - - - public void setItblAutoFmt(short s) { - itblAutoFmt = s; - byte[] b = ByteTools.shortToLEBytes(itblAutoFmt); - System.arraycopy(b, 0, data, 38, 2); - } - - public short getItblAutoFmt() { - return itblAutoFmt; - } - - /** - * Sets the name of the Pivot Table. - */ - public void setTableName(String s) { - PivotTableName = s; - this.buildRgch(); - // also set associated qsitag pivot view name - QsiSXTag qsi = (QsiSXTag) getSubRec(QSISXTAG, -1); - if (qsi != null) - qsi.setName(s); - // find SXADDL_SxView_SxDID record and set PivotTableView name - must match this view tablename - for (int i = subRecs.size() - 1; i > 0; i--) { - BiffRec br = subRecs.get(i); - if (br.getOpcode() == SXADDL) { - if (((SxAddl) br).getRecordId() == SxAddl.SxcView.sxdId) { - ((SxAddl) br).setViewName(s); - break; - } - } - } - - } - - /** - * return the name of the Pivot Table. - */ - public String getTableName() { - return PivotTableName; - } - - /** - * Sets the name of the Data field - */ - public void setDataName(String s) { - DataFieldName = s; - this.buildRgch(); - } - - public String getDataName() { - return DataFieldName; - } - - /* - Builds a new rgch field, changes the length of the entire record.... - */ - private void buildRgch() { - byte[] data = new byte[44]; - System.arraycopy(this.getData(), 0, data, 0, 44); - byte[] strbytes = new byte[0]; - byte[] databytes = new byte[0]; - try { - if (PivotTableName != null) - strbytes = PivotTableName.getBytes(DEFAULTENCODING); - if (DataFieldName != null) - databytes = DataFieldName.getBytes(DEFAULTENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding pivot table name in SXVIEW: " + e); - } - - //update the lengths: - cchName = (short) strbytes.length; - byte[] nm = ByteTools.shortToLEBytes(cchName); - data[40] = nm[0]; - data[41] = nm[1]; - - cchData = (short) databytes.length; - nm = ByteTools.shortToLEBytes(cchData); - data[42] = nm[0]; - data[43] = nm[1]; - - // now append variable-length string data - byte[] newrgch = new byte[cchName + cchData + 2]; // account for encoding bytes - System.arraycopy(strbytes, 0, newrgch, 1, cchName); - System.arraycopy(databytes, 0, newrgch, cchName + 2, cchData); - - data = ByteTools.append(newrgch, data); - this.setData(data); - } - - - /** - * utility method to retrieve the nth pivot table subrecord (SxVd, SxPI, SxDI ...) - * - * @param opcode opcode to look up - * @param index if > -1, the index or occurrence of the record to return - * @return - */ - private BiffRec getSubRec(int opcode, int index) { - int j = 0; - for (int i = 0; i < subRecs.size(); i++) { - BiffRec br = subRecs.get(i); - if (br.getOpcode() == opcode) { - if (index == -1 || (j++ == index)) - return br; - } - } - return null; - } - - /** - * utility method to retrieve the subrecord index of the desired pivot table record - * - * @param br pivot table record to look up - * @return - */ - private int getSubRecIndex(BiffRec br) { - int i = 0; - for (; i < subRecs.size(); i++) { - if (br == subRecs.get(i)) - break; - } - return i; - } - - /** - * utility method to retrieve a pivot table subrecord (SxVd, SxPI, SxDI ...) index - * - * @param opcode opcode to look up - * @param index if > -1, the index or occurrence of the record to return - * @return index in subrecords of desired record - */ - private int getSubRecIndex(int opcode, int index) { - int j = 0; - for (int i = 0; i < subRecs.size(); i++) { - BiffRec br = subRecs.get(i); - if (br.getOpcode() == opcode) { - if (index == -1 || (j++ == index)) - return i; - } - } - return -1; - } - - /** - * i - * finds the proper sheet rec insertion index for the desired opcode - * as well as the proper insertion index into the subrecord array - *
                  this method contains alot of intelligence regarding the order of pivot table records - * - * @param opcode one of SXVD, SXIVD, SXPI, SXDI, SXLI, or SXVDEX - * @param index either 0 (for ROW records) or 1 (for COL records) - * @return int[] {sheet record index, subrecord index} or -1's - */ - private int getPivotRecordInsertionIndexes(int opcode, int index, int pivotFieldIndex) { - int i, j = 0; - if (pivotFieldIndex < 0) { - for (i = subRecs.size() - 1; i >= 0; i--) { - BiffRec br = subRecs.get(i); - int bropcode = br.getOpcode(); - if (bropcode == opcode) { // - if (j < index) { - j++; - continue; // haven't found correct record jet - } - break; - } else if (opcode == SXLI && (bropcode == SXDI || bropcode == SXPI || bropcode == SXIVD || bropcode == SXVDEX)) { - break; - } else if (opcode == SXDI && (bropcode == SXPI || bropcode == SXIVD || bropcode == SXVDEX)) { - break; - } else if (opcode == SXPI && (bropcode == SXIVD || bropcode == SXVDEX)) { - break; - } else if (opcode == SXIVD && (bropcode == SXVDEX)) { - break; - } else if (opcode == SXVD && (bropcode == SXEX)) { - break; - } - } - i++; // counter reverse order - } else { // only lookup within the current pivot field - for (i = pivotFieldIndex + 1; i < subRecs.size(); i++) { - BiffRec br = subRecs.get(i); - int bropcode = br.getOpcode(); - if (bropcode == opcode) { // - if (j < index) { - j++; - continue; // haven't found correct record jet - } - break; - } else if (bropcode == SXVDEX) { - break; - } - } - } - return i; -/* if (i >= 0) { - return new int[] {subRecs.get(i+j).getRecordIndex(), i}; - } - return new int[] {-1, i};*/ - } - - - /** - * creates the basic, default records necessary to define a pivot table - * - * @param sheet string sheetname where pivot table is located - * @return arraylist of records - */ - public ArrayList addInitialRecords(Boundsheet sheet) { - /* - * basic blank pivot table: - * SXVIEW - * after SXVIEW pivot field and item records will go: - * for each pivot field: SxVd, SxVi* n items, SxVDEx - * SXIVD if cDimRw > 0 - * SXIVD if cDimCol > 0 - * SXPI if cDimPg > 0 - * SXDI if cDimData > 0 - * SXLI if cRw > 0 - * SXLI if cCol > 0 - * SXEX - * QXISTAG - * SXADDL records - * - */ - ArrayList initialrecs = new ArrayList(); - this.setSheet(sheet); - this.setWorkBook(sheet.getWorkBook()); - // SXEX - SxEX sxex = (SxEX) SxEX.getPrototype(); - addInit(initialrecs, sxex, sheet); - // QSISXTAG - QsiSXTag qsi = (QsiSXTag) QsiSXTag.getPrototype(); - addInit(initialrecs, qsi, sheet); - SxVIEWEX9 sxv = (SxVIEWEX9) SxVIEWEX9.getPrototype(); - addInit(initialrecs, sxv, sheet); - // SXADDLs -required SxAddl records: stores additional PivotTableView info of a variety of types -// byte[] b= ByteTools.cLongToLEBytes(sid); b= ByteTosols.append(new byte[] {0, 0}, b); // add 2 reserved bytes - SxAddl sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdId.sxd(), null); - addInit(initialrecs, sa, sheet); - //SXADDL_sxcView: record=sxdVer10Info data:[1, 65, 0, 0, 0, 0] - sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVer10Info.sxd(), null); - addInit(initialrecs, sa, sheet); - sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdTableStyleClient.sxd(), new byte[]{0, 0, 0, 0, 0, 0, 51, 0, 0, 0}); - addInit(initialrecs, sa, sheet); - sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVerUpdInv.sxd(), new byte[]{2, 0, 0, 0, 0, 0}); - addInit(initialrecs, sa, sheet); - sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVerUpdInv.sxd(), new byte[]{-1, 0, 0, 0, 0, 0}); - addInit(initialrecs, sa, sheet); - sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdEnd.sxd(), null); - addInit(initialrecs, sa, sheet); - return initialrecs; - } - - /** - * utility function to properly add a Pivot Table View subrec - * - * @param initialrecs - * @param rec - * @param addToInitRecords - * @param sheet - */ - private void addInit(ArrayList initialrecs, XLSRecord rec, Boundsheet sheet) { - initialrecs.add(rec); - rec.setSheet(sheet); - rec.setWorkBook(this.getWorkBook()); - this.addSubrecord(rec); - } - - public String toString() { - return "SXVIEW: name:" + this.getTableName() + " iCache:" + iCache + " cDim:" + cDim + " cDimRw:" + cDimRw + " cDimCol:" + cDimCol + - " cDimPg:" + cDimPg + " cDimData:" + cDimData + - " cRw:" + cRw + " cCol:" + cCol + - " datafieldname:" + DataFieldName; - } -} - diff --git a/src/main/java/io/starter/formats/XLS/Sxview.kt b/src/main/java/io/starter/formats/XLS/Sxview.kt new file mode 100644 index 0000000..fc28596 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Sxview.kt @@ -0,0 +1,1313 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.SxAddl.SxcView +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.UnsupportedEncodingException +import java.util.ArrayList + + +/** + * **SXVIEW B0h: This record contains top-level pivot table information.**

                  + * + *
                  + * ref (8 bytes): A Ref8U structure that specifies the PivotTable report body. For more information, see Location and Body.
                  + * rwFirstHead (2 bytes): An RwU structure that specifies the first row of the row area.
                  + * MUST be 1 if none of the axes are assigned in this PivotTable view.
                  + * Otherwise, the value MUST be greater than or equal to ref.rwFirst.
                  + *
                  + * rwFirstData (2 bytes): An RwU structure that specifies the first row of the data area.
                  + * MUST be 1 if none of the axes are assigned in this PivotTable view.
                  + * Otherwise, it MUST be equal to the value as specified by the following formula:
                  + *
                  + * rwFirstData = rwFirstHead + cDimCol
                  + *
                  + * colFirstData (2 bytes): A ColU structure that specifies the first column of the data area.
                  + * It MUST be 1 if none of the axes are assigned in this PivotTable view.
                  + * Otherwise, the value MUST be greater than or equal to ref.colFirst, and if the value of cDimCol or cDimData is not zero,
                  + * it MUST be less than or equal to ref.colLast.
                  + *
                  + * iCache (2 bytes): A signed integer that specifies the zero-based index of an SXStreamID record in the Globals substream.
                  + * MUST be greater than or equal to zero and less than the number of SXStreamID records in the Globals substream.
                  + *
                  + * reserved (2 bytes): MUST be zero, and MUST be ignored.
                  + *
                  + * sxaxis4Data (2 bytes): An SXAxis structure that specifies the default axis for the data field.
                  + * Either the sxaxis4Data.sxaxisRw field MUST be 1 or the sxaxis4Data.sxaxisCol field MUST be 1.
                  + * The sxaxis4Data.sxaxisPage field MUST be 0 and the sxaxis4Data.sxaxisData field MUST be 0.
                  + *
                  + * ipos4Data (2 bytes): A signed integer that specifies the row or column position for the data field in the PivotTable view.
                  + * The sxaxis4Data field specifies whether this is a row or column position.
                  + * MUST be greater than or equal to -1 and less than or equal to 0x7FFF. A value of -1 specifies the default position.
                  + *
                  + * cDim (2 bytes): A signed integer that specifies the number of pivot fields in the PivotTable view.
                  + * MUST equal the number of Sxvd records following this record.
                  + * MUST equal the number of fields in the associated PivotCache specified by iCache.
                  + *
                  + * cDimRw (2 bytes): An unsigned integer that specifies the number of fields on the row axis of the PivotTable view.
                  + * MUST be less than or equal to 0x7FFF. MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain row items.
                  + *
                  + * cDimCol (2 bytes): An unsigned integer that specifies the number of fields on the column axis of the PivotTable view.
                  + * MUST be less than or equal to 0x7FFF.
                  + * MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain column items.
                  + *
                  + * cDimPg (2 bytes): An unsigned integer that specifies the number of page fields in the PivotTable view.
                  + * MUST be less than or equal to 0x7FFF.
                  + * MUST equal the number of array elements in the SXPI record in this PivotTable view.
                  + *
                  + * cDimData (2 bytes): A signed integer that specifies the number of data fields in the PivotTable view.
                  + * MUST be greater than or equal to zero and less than or equal to 0x7FFF.
                  + * MUST equal the number of SXDI records in this PivotTable view.
                  + *
                  + * cRw (2 bytes): An unsigned integer that specifies the number of pivot lines in the row area of the PivotTable view.
                  + * MUST be less than or equal to 0x7FFF.
                  + * MUST equal the number of array elements in the first SXLI record in this PivotTable view.
                  + *
                  + * cCol (2 bytes): An unsigned integer that specifies the number of pivot lines in the column area of the PivotTable view.
                  + * MUST equal the number of array elements in the second SXLI record in this PivotTable view.
                  + *
                  + * A - fRwGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for rows.
                  + * MUST be 0 if none of the axes have been assigned in this PivotTable view.
                  + *
                  + * B - fColGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for columns.
                  + * MUST be 1 if none of the axes are assigned in this PivotTable view.
                  + *
                  + * C - unused1 (1 bit): Undefined and MUST be ignored.
                  + *
                  + * D - fAutoFormat (1 bit): A bit that specifies whether the PivotTable has AutoFormat applied.
                  + *
                  + * E - fAtrNum (1 bit): A bit that specifies whether the PivotTable has number AutoFormat applied.
                  + *
                  + * F - fAtrFnt (1 bit): A bit that specifies whether the PivotTable has font AutoFormat applied.
                  + *
                  + * G - fAtrAlc (1 bit): A bit that specifies whether the PivotTable has alignment AutoFormat applied.
                  + *
                  + * H - fAtrBdr (1 bit): A bit that specifies whether the PivotTable has border AutoFormat applied.
                  + *
                  + * I - fAtrPat (1 bit): A bit that specifies whether the PivotTable has pattern AutoFormat applied.
                  + *
                  + * J - fAtrProc (1 bit): A bit that specifies whether the PivotTable has width/height AutoFormat applied.
                  + *
                  + * unused2 (6 bits): Undefined and MUST be ignored.
                  + *
                  + * itblAutoFmt (2 bytes): An AutoFmt8 structure that specifies the PivotTable AutoFormat.
                  + * If the value of itblAutoFmt in the associated SXViewEx9 record is not 1, this field is overridden by the value of itblAutoFmt in the associated SXViewEx9.
                  + *
                  + * cchTableName (2 bytes): An unsigned integer that specifies the length, in characters, of stTable.
                  + * MUST be greater than or equal to zero and less than or equal to 0x00FF.
                  + *
                  + * cchDataName (2 bytes): An unsigned integer that specifies the length, in characters of stData.
                  + * MUST be greater than zero and less than or equal to 0x00FE.
                  + *
                  + * stTable (variable): An XLUnicodeStringNoCch structure that specifies the name of the PivotTable.
                  + * The length of this field is specified by cchTableName.
                  + *
                  + * stData (variable): An XLUnicodeStringNoCch structure that specifies the name of the data field.
                  + * The length of this field is specified by cchDataName.
                  + *
                  +
                  * + */ +class Sxview : XLSRecord(), XLSConstants { + internal var rwFirst: Short = 0x0 // First Row of Pivot Table + internal var rwLast: Short = 0x0 // Last Row of Pivot Table + internal var colFirst: Short = 0x0 // First Column of Pivot Table + internal var colLast: Short = 0x0 // Last Column of Pivot Table + internal var rwFirstHead: Short = 0x0 // First Row containing Pivot Table headings + internal var rwFirstData: Short = 0x0 // First Row containing Pivot Table data + internal var colFirstData: Short = 0x0 // First Column containing Pivot Table data + internal var iCache: Short = 0x0 // Index to the Cache + // offset 20, length 2 is reserved, must be 0 (zero) + /** + * retieves the default axis for the data field: + * * 1- row + * * 2= col + * * 4= page + * * 8= data + */ + var sxaxis4Data: Short = 0x0 + internal set // Default axis for a data field + internal var ipos4Data: Short = 0x0 // Default position for a data field + internal var cDim: Short = 0x0 // Number of fields + /** + * returns the number of pivot fields on the ROW axis + * + * @return + */ + var cDimRw: Short = 0x0 + internal set // Number of row fields; + /** + * returns the number of pivot fields on the COL axis + * + * @return + */ + var cDimCol: Short = 0x0 + internal set // Number of column fields + /** + * returns the number of fields on the page axis + * + * @return + */ + var cDimPg: Short = 0x0 + internal set // Number of page fields; + /** + * returns the number of pivot fields on the data axis + * + * @return + */ + var cDimData: Short = 0x0 + internal set // Number of Data fields + /** + * returns the number of pivot items or lines on the ROW axis + * + * @return + */ + var cRw: Short = 0x0 + internal set // Number of data rows + /** + * returns the number of pivot items or lines on the COL axis + * + * @return + */ + var cCol: Short = 0x0 + internal set // Number of data columns + internal var grbit1: Byte = 0x0 // if you dont know what this is.... + internal var grbit2: Byte = 0x0 // if you dont know what this is.... + internal var itblAutoFmt: Short = 0x0 // Index to the Pivot Table autoformat + internal var cchName: Short = 0x0 // Length the Pivot Table name + internal var cchData: Short = 0x0 // Length of the data field name + internal var rgch: ByteArray// PivotTableName followed by the name of a data field + + /* The following member variables are populated from parsing the + grbit field + */ + internal var fRwGrand = false // = 1 if the Pivot Table contains grand totals for rows + /** + * specifies whether the PivotTable contains grand totals for columns. MUST be 1 if none of the axes are assigned in this PivotTable view. + */ + var fColGrand = false + internal set // = 1 if the Pivot Table contains grand totals for Columns + // bit 2, mask 0004h is reserved, must be 0 (zero) + internal var fAutoFormat = false // = 1 if the Pivot table has an autoformat applied + internal var fWH = false // = 1 if the width/height autoformat is applied + internal var fFont = false // = 1 if the font autoformat is applied + internal var fAlign = false // = 1 if the alignment autoformat is applied + internal var fBorder = false // = 1 if the border autoformat is applied + internal var fPattern = false // = 1 if the pattern autoformat is applied + internal var fNumber = false // = 1 if the number autoformat is applied + + /* + The following member variables are derived from the rgch field + */ + internal var PivotTableName: String? = null + internal var DataFieldName: String? = null + + private val subRecs = ArrayList() + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, /* ref */ + 0, 0, /* rwfirstHead */ + 0, 0, /* rwfirstData */ + 0, 0, /* colfirstHead */ + 0, 0, /* iCache */ + 0, 0, /* reserved */ + 1, 0, /* saxis4Data */ + -1, -1, /* ipos4Data */ + 0, 0, /* cDim */ + 0, 0, /* cDimRw */ + 0, 0, /* cDimCol */ + 0, 0, /* cDimPg */ + 0, 0, /* cDimData */ + 0, 0, /* cRw */ + 0, 0, /* cCol */ + 11, 2, /* grbit + reserved */ + 1, 0, /* itblAutoFmt */ + 0, 0, /* cchTableName */ + 0, 0)/* cchDataName */ + + + /** + * returns the number of pivot fields (==columns in the pivot table data range) + * + * @return + */ + /** + * sets the number of pivot fields (i.e. columns in the source data) for this pivot table + *

                  NOTE: this method wipes any existing data of the pivot table + * + * @param s + */ + // number of pivot fields MUST==# of SxVd records + // remove ALL field-related records + // reset variables + // now add the required records for each field + // for each pivot field (which goes on an axis) + var nPivotFields: Short + get() = cDim + set(s) { + cDim = s + val b = ByteTools.shortToLEBytes(cDim) + System.arraycopy(b, 0, data!!, 22, 2) + run { + var i = 0 + while (i < subRecs.size) { + val br = subRecs.get(i) + if (br.opcode != XLSConstants.SXEX) { + this.sheet!!.removeRecFromVec(br) + subRecs.removeAt(i) + i-- + } else + break + i++ + } + } + cDimRw = 0 + cDimCol = 0 + cDimPg = 0 + cDimData = 0 + cRw = 0 + cCol = 0 + var zz = this.recordIndex + 1 + for (i in 0 until cDim) { + val svd = Sxvd.prototype as Sxvd? + svd!!.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(zz++, svd) + this.subRecs.add(i * 2, svd) + val svdex = SxVdEX.prototype as SxVdEX? + svdex!!.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(zz++, svdex) + this.subRecs.add(i * 2 + 1, svdex) + } + } + + var fwh: Boolean + get() = fWH + set(b) { + if (b != fWH) { + if (b == false) { + grbit1 = (grbit1 and 0xEF).toByte() + } else { + grbit1 = (grbit1 or 0x10).toByte() + } + this.initGrbit() + } + } + + /** + * return the name of the Pivot Table. + */ + /** + * Sets the name of the Pivot Table. + */ + // also set associated qsitag pivot view name + // find SXADDL_SxView_SxDID record and set PivotTableView name - must match this view tablename + var tableName: String? + get() = PivotTableName + set(s) { + PivotTableName = s + this.buildRgch() + val qsi = getSubRec(XLSConstants.QSISXTAG.toInt(), -1) as QsiSXTag? + if (qsi != null) + qsi.name = s + for (i in subRecs.size - 1 downTo 1) { + val br = subRecs.get(i) + if (br.opcode == XLSConstants.SXADDL) { + if ((br as SxAddl).recordId === SxAddl.SxcView.sxdId) { + (br as SxAddl).setViewName(s) + break + } + } + } + + } + + /** + * Sets the name of the Data field + */ + var dataName: String? + get() = DataFieldName + set(s) { + DataFieldName = s + this.buildRgch() + } + + override fun init() { + super.init() + if (this.length <= 0) { // Is this record populated? + if (DEBUGLEVEL > -1) Logger.logInfo("no data in SXVIEW") + } else { // parse out all the fields + rwFirst = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + rwLast = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + colFirst = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + colLast = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + rwFirstHead = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + rwFirstData = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + colFirstData = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + iCache = ByteTools.readShort(this.getByteAt(14).toInt(), this.getByteAt(15).toInt()) + + // 16 & 17 - reserved must be zero + + sxaxis4Data = ByteTools.readShort(this.getByteAt(18).toInt(), this.getByteAt(19).toInt()) + ipos4Data = ByteTools.readShort(this.getByteAt(20).toInt(), this.getByteAt(21).toInt()) + cDim = ByteTools.readShort(this.getByteAt(22).toInt(), this.getByteAt(23).toInt()) + cDimRw = ByteTools.readShort(this.getByteAt(24).toInt(), this.getByteAt(25).toInt()) + cDimCol = ByteTools.readShort(this.getByteAt(26).toInt(), this.getByteAt(27).toInt()) + cDimPg = ByteTools.readShort(this.getByteAt(28).toInt(), this.getByteAt(29).toInt()) + cDimData = ByteTools.readShort(this.getByteAt(30).toInt(), this.getByteAt(31).toInt()) + cRw = ByteTools.readShort(this.getByteAt(32).toInt(), this.getByteAt(33).toInt()) + cCol = ByteTools.readShort(this.getByteAt(34).toInt(), this.getByteAt(35).toInt()) + grbit1 = this.getByteAt(37) + grbit2 = this.getByteAt(36) + + + this.initGrbit() // note the manual hibyting + itblAutoFmt = ByteTools.readShort(this.getByteAt(38).toInt(), this.getByteAt(39).toInt()) + cchName = ByteTools.readShort(this.getByteAt(40).toInt(), this.getByteAt(41).toInt()) + cchData = ByteTools.readShort(this.getByteAt(42).toInt(), this.getByteAt(43).toInt()) + val fullnamelen = cchName.toInt() + cchData.toInt() + rgch = ByteArray(fullnamelen) + var pos = 44 + if (cchName > 0) { + //A - fHighByte (1 bit): A bit that specifies whether the characters in rgb are double-byte characters. + // 0x0 All the characters in the string have a high byte of 0x00 and only the low bytes are in rgb. + // 0x1 All the characters in the string are saved as double-byte characters in rgb. + // reserved (7 bits): MUST be zero, and MUST be ignored. + + val encoding = this.getByteAt(pos++) + + val tmp = this.getBytesAt(pos, cchName * (encoding + 1)) + try { + if (encoding.toInt() == 0) + PivotTableName = String(tmp!!, XLSConstants.DEFAULTENCODING) + else + PivotTableName = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding PivotTable name in Sxview: $e") + } + + pos += cchName * (encoding + 1) + } + if (cchData > 0) { + val encoding = this.getByteAt(pos++) + val tmp = this.getBytesAt(pos, cchData * (encoding + 1)) + try { + if (encoding.toInt() == 0) + DataFieldName = String(tmp!!, XLSConstants.DEFAULTENCODING) + else + DataFieldName = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding PivotTable name in Sxview: $e") + } + + } + } + if (DEBUGLEVEL > 3) + Logger.logInfo("SXVIEW: name:" + this.tableName + " iCache:" + iCache + " cDim:" + cDim + " cDimRw:" + cDimRw + " cDimCol:" + cDimCol + " cDimPg:" + cDimPg + " cDimData:" + cDimData + " cRw:" + cRw + " cCol:" + cCol + " datafieldname:" + DataFieldName) + } + + /** + * specifies the default axis for the data field: + * * 1- row + * * 2= col + * * 4= page + * * 8= data + * + * @param s + */ + protected fun setAxis4Data(s: Short) { + sxaxis4Data = s + val b = ByteTools.shortToLEBytes(sxaxis4Data) + System.arraycopy(b, 0, data!!, 18, 2) + } + + /** + * A signed integer that specifies the row or column position for the data field in the PivotTable view. + *

                  see getSxaxis4Data to determine whether this is a row or column position + * + * @param s + */ + protected fun setIpos4Data(s: Short) { + ipos4Data = s + val b = ByteTools.shortToLEBytes(ipos4Data) + System.arraycopy(b, 0, data!!, 20, 2) + } + + /** + * A signed integer that specifies the row or column position for the data field in the PivotTable view. + *

                  see getSxaxis4Data to determine whether this is a row or column position + */ + fun getIpos4Data(): Short { + return ipos4Data + } + + /** + * adds the pivot field corresponding to cache field at index fieldIndex to the desired axis + *

                  A pivot field is a cache field that has been added to the pivot table + *

                  Pivot fields are defined by SXVD and associated records + *

                  the SXVD record stores which axis the field is on + *

                  there are cDim pivot fields on the pivot table view + * * 1= row + * * 2= col + * * 4= page + * * 8= data + * + * @param axis + * @param fieldIndex 0-based pivot field index + * @see SxVd.AXIS_ constants + */ + fun addPivotFieldToAxis(axis: Int, fieldIndex: Int): Sxvd { + var zz = this.recordIndex + 1 + val sxvdex = getSubRec(XLSConstants.SXVDEX.toInt(), fieldIndex) as SxVdEX? // end of last pivot field set (PIVOTVD rule) + if (sxvdex != null) + zz = sxvdex.recordIndex + 1 + + val sxvd = Sxvd.prototype as Sxvd? // for each pivot field (which goes on an axis) + sxvd!!.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(zz++, sxvd) + this.subRecs.add(cDim * 2, sxvd) + val svdex = SxVdEX.prototype as SxVdEX? + svdex!!.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(zz++, svdex) + this.subRecs.add(cDim * 2 + 1, svdex) + cDim++ + sxvd.setAxis(axis) + return sxvd + + /* + Sxvd sxvd= (Sxvd) getSubRec(SXVD, fieldIndex); + if (sxvd!=null) { + // got it + sxvd.setAxis(axis); + return sxvd; + } + return null;*/ + } + + /** + * adds a pivot field (0-based index) to the ROW axis + * + * @param fieldNumber + */ + fun addRowField(fieldNumber: Int) { + cDimRw++ // MUST==# row elements in SxIVD + val b = ByteTools.shortToLEBytes(cDimRw) + System.arraycopy(b, 0, data!!, 24, 2) + var sxivd = getSubRec(XLSConstants.SXIVD.toInt(), if (cDimCol > 0) 1 else 0) as Sxivd? + if (sxivd == null) { + val zz = getPivotRecordInsertionIndexes(XLSConstants.SXIVD.toInt(), 1, -1) + if (zz > 0) { // should!!! + sxivd = Sxivd.prototype as Sxivd? + sxivd!!.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(subRecs.get(zz).recordIndex, sxivd) + this.subRecs.add(zz, sxivd) + } + } + sxivd!!.addField(fieldNumber) + } + + /** + * adds a pivot field (0-based index) to the COL axis + */ + fun addColField(fieldNumber: Int) { + cDimCol++ + val b = ByteTools.shortToLEBytes(cDimCol) + System.arraycopy(b, 0, data!!, 26, 2) + var sxivd = getSubRec(XLSConstants.SXIVD.toInt(), 0) as Sxivd? + if (sxivd == null) { + val zz = getPivotRecordInsertionIndexes(XLSConstants.SXIVD.toInt(), 0, -1) + if (zz > 0) { // should!! + sxivd = Sxivd.prototype as Sxivd? + sxivd!!.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(subRecs.get(zz).recordIndex, sxivd) + this.subRecs.add(zz, sxivd) + } + } + sxivd!!.addField(fieldNumber) + } + + /** + * adds a pivot field to the page axis + * + * @param strref + */ + fun addPageField(fieldIndex: Int, itemIndex: Int) { + cDimPg++ + val b = ByteTools.shortToLEBytes(cDimPg) + System.arraycopy(b, 0, data!!, 28, 2) + var sxpi = getSubRec(XLSConstants.SXPI.toInt(), -1) as SxPI? + if (sxpi == null) { + val zz = getPivotRecordInsertionIndexes(XLSConstants.SXPI.toInt(), -1, -1) + if (zz > 0) { // should!!! + sxpi = SxPI.prototype as SxPI? + sxpi!!.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(subRecs.get(zz).recordIndex, sxpi) + this.subRecs.add(zz, sxpi) + } + } + sxpi!!.addPageField(fieldIndex, itemIndex) + } + + /** + * adds a pivot field to the DATA axis + * + * @param fieldIndex + * @param aggregateFunction + */ + fun addDataField(fieldIndex: Int, aggregateFunction: String, name: String) { + cDimData++ + val b = ByteTools.shortToLEBytes(cDimData) + System.arraycopy(b, 0, data!!, 30, 2) + var sxdi: SxDI? = null + /* SxDI sxdi= (SxDI) getSubRec(SXDI, -1); + if (sxdi==null) { */ + val zz = getPivotRecordInsertionIndexes(XLSConstants.SXDI.toInt(), -1, -1) + if (zz > 0) { // should!!! + sxdi = SxDI.prototype as SxDI? + sxdi!!.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(subRecs.get(zz).recordIndex, sxdi) + this.subRecs.add(zz, sxdi) + } + /* }*/ + sxdi!!.addDataField(fieldIndex, aggregateFunction, name) + } + + /** + * adds a pivot item to the end of the list of items on this axis (ROW, COL, DATA or PAGE) + * + * @param axis Axis int: ROW, COL, DATA or PAGE + * @param itemType one of: + * * 0x0000 itmtypeData A data value + * * 0x0001 itmtypeDEFAULT Default subtotal for the pivot field + * * 0x0002 itmtypeSUM Sum of values in the pivot field + * * 0x0003 itmtypeCOUNTA Count of values in the pivot field + * * 0x0004 itmtypeAVERAGE Average of values in the pivot field + * * 0x0005 itmtypeMAX Max of values in the pivot field + * * 0x0006 itmtypeMIN Min of values in the pivot field + * * 0x0007 itmtypePRODUCT Product of values in the pivot field + * * 0x0008 itmtypeCOUNT Count of numbers in the pivot field + * * 0x0009 itmtypeSTDEV Statistical standard deviation (estimate) of the pivot field + * * 0x000A itmtypeSTDEVP Statistical standard deviation (entire population) of the pivot field + * * 0x000B itmtypeVAR Statistical variance (estimate) of the pivot field + * * 0x000C itmtypeVARP Statistical variance (entire population) of the pivot field + * @param cacheItem A cache item index in the cache field associated with the pivot field, as specified by Cache Items. + */ + fun addPivotItem(axis: Sxvd, itemType: Int, cacheItem: Int) { + val n = axis.numItems // Axis record = Sxvd. Identifies pivot field on axis. Pivot items records follow until SXVDEX. + axis.numItems = n + 1 + val axisIndex = getSubRecIndex(axis) + val zz = getPivotRecordInsertionIndexes(XLSConstants.SXVI.toInt(), n, axisIndex) // get LAST pivot field on axis + val sxvi = Sxvi.prototype as Sxvi? // pivot item record + sxvi!!.setItemType(itemType) + sxvi.setCacheItem(cacheItem) + sxvi.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(subRecs.get(zz).recordIndex, sxvi) + subRecs.add(zz, sxvi) + /*if (axis.getAxisType()==Sxvd.AXIS_ROW) { + } else if (axis.getAxisType()==Sxvd.AXIS_COL) { + } else if (axis.getAxisType()==Sxvd.AXIS_PAGE) { + + } else if (axis.getAxisType()==Sxvd.AXIS_DATA) { + + }*/ + if (cacheItem != -1) { + val pc = this.workBook!!.pivotCache // TODO should this be here or in Sxstream? + pc!!.addCacheItem(iCache.toInt(), cacheItem) // adds the cache item to the "used" list, as it were + } + } + + /** + * sets the number of pivot items or lines on the ROW axis + * + * @param strref + */ + fun addPivotLineToROWAxis(repeat: Int, nLines: Int, type: Int, indexes: ShortArray) { + cRw++ + val b = ByteTools.shortToLEBytes(cRw) + System.arraycopy(b, 0, data!!, 32, 2) + /** + * If the value of either of the cRw or cCol fields of the associated SxView is greater than zero, + * then two records of this type MUST exist in the file for the associated SxView. + * The first record contains row area pivot lines and the second record contains column area pivot lines. */ + var sxli = getSubRec(XLSConstants.SXLI.toInt(), 0) as Sxli? + if (sxli == null) { + val zz = getPivotRecordInsertionIndexes(XLSConstants.SXLI.toInt(), 0, -1) + if (zz > 0) { // should!!! + sxli = Sxli.getPrototype(this.workBook) as Sxli + sxli.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(subRecs.get(zz).recordIndex, sxli) + this.subRecs.add(zz, sxli) + sxli.addField(repeat, nLines, type, indexes) + sxli = Sxli.getPrototype(this.workBook) as Sxli + sxli.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(subRecs.get(zz + 1).recordIndex, sxli) + this.subRecs.add(zz + 1, sxli) + } + } else + sxli.addField(repeat, nLines, type, indexes) + } + + fun hasRowPivotItemsRecord(): Boolean { + return getSubRec(XLSConstants.SXLI.toInt(), 0) != null + } + + + /** + * set the number of pivot items or lines on the COL axis + * + * @param strref + */ + fun addPivotLineToCOLAxis(repeat: Int, nLines: Int, type: Int, indexes: ShortArray) { + cCol++ + val b = ByteTools.shortToLEBytes(cCol) + System.arraycopy(b, 0, data!!, 34, 2) + var sxli = getSubRec(XLSConstants.SXLI.toInt(), 1) as Sxli? + if (sxli == null) { + val zz = getPivotRecordInsertionIndexes(XLSConstants.SXLI.toInt(), 0, -1) + if (zz == -1) { // shouldn't! + sxli = Sxli.prototype as Sxli? + sxli!!.setSheet(this.sheet) + this.sheet!!.sheetRecs.add(subRecs.get(zz).recordIndex, sxli) + this.subRecs.add(zz, sxli) + } + } + sxli!!.addField(repeat, nLines, type, indexes) + } + + /** + * iCache links this pivot table to a pivot data cache + * + * @param s + */ + fun setICache(s: Short) { + iCache = s + val b = ByteTools.shortToLEBytes(iCache) + System.arraycopy(b, 0, data!!, 14, 2) + } + + /** + * iCache links this pivot table to a pivot data cache + */ + fun getICache(): Short { + return iCache + } + + + /** + * Init the grbit, populate the member variables of the SXVIEW object. + * This can be called both at initial init, and later to reup the boolean fields + * after changes to the grbit. + */ + private fun initGrbit() { + fRwGrand = grbit1 and 0x1 == 0x1 + fColGrand = grbit1 and 0x2 == 0x2 + fAutoFormat = grbit1 and 0x8 == 0x8 + fWH = grbit1 and 0x10 == 0x10 + fFont = grbit1 and 0x20 == 0x20 + fAlign = grbit1 and 0x40 == 0x40 + fBorder = grbit1 and 0x80 == 0x80 + fPattern = grbit2 and 0x1 == 0x1 + fNumber = grbit2 and 0x2 == 0x2 + this.getData()[36] = grbit2 + this.getData()[37] = grbit1 + } + + /** + * store associated records for ease of lookup + * + * @param r + */ + fun addSubrecord(r: BiffRec) { + subRecs.add(r) + } + /* + The following methods all change or get grbit fields + */ + + /** + * specifies whether the PivotTable contains grand totals for rows. + * MUST be 0 if none of the axes have been assigned in this PivotTable view. + */ + fun setFRwGrand(b: Boolean) { + if (b != fRwGrand) { + if (b == false) { + grbit1 = (grbit1 and 0xFE).toByte() + } else { + grbit1 = (grbit1 or 0x1).toByte() + } + this.initGrbit() + } + } + + /** + * specifies whether the PivotTable contains grand totals for rows. + * MUST be 0 if none of the axes have been assigned in this PivotTable view. + */ + fun getFRwGrand(): Boolean { + return fRwGrand + } + + /** + * specifies whether the PivotTable contains grand totals for columns. + * MUST be 1 if none of the axes are assigned in this PivotTable view. + * + * @param b + */ + fun setColGrand(b: Boolean) { + if (b != fColGrand) { + if (b == false) { + grbit1 = (grbit1 and 0xFD).toByte() + } else { + grbit1 = (grbit1 or 0x2).toByte() + } + this.initGrbit() + } + } + + /** + * specifies whether the PivotTable has AutoFormat applied. + * + * @param b + */ + fun setFAutoFormat(b: Boolean) { + if (b != fAutoFormat) { + if (b == false) { + grbit1 = (grbit1 and 0xFB).toByte() + } else { + grbit1 = (grbit1 or 0x4).toByte() + } + this.initGrbit() + } + } + + /** + * specifies whether the PivotTable has AutoFormat applied. + * + * @return + */ + fun getFAutoFormat(): Boolean { + return fAutoFormat + } + + /** + * specifies whether the PivotTable has font AutoFormat applied. + * + * @param b + */ + fun setFFont(b: Boolean) { + if (b != fFont) { + if (b == false) { + grbit1 = (grbit1 and 0xDF).toByte() + } else { + grbit1 = (grbit1 or 0x20).toByte() + } + this.initGrbit() + } + } + + /** + * specifies whether the PivotTable has font AutoFormat applied. + * + * @return + */ + fun getFFont(): Boolean { + return fFont + } + + /** + * specifies whether the PivotTable has alignment AutoFormat applied. + * + * @param b + */ + fun setFAlign(b: Boolean) { + if (b != fAlign) { + if (b == false) { + grbit1 = (grbit1 and 0xBF).toByte() + } else { + grbit1 = (grbit1 or 0x40).toByte() + } + this.initGrbit() + } + } + + /** + * specifies whether the PivotTable has alignment AutoFormat applied. + * + * @return + */ + fun getFAlign(): Boolean { + return fAlign + } + + /** + * specifies whether the PivotTable has border AutoFormat applied. + * + * @param b + */ + fun setFBorder(b: Boolean) { + if (b != fBorder) { + if (b == false) { + grbit1 = (grbit1 and 0x7F).toByte() + } else { + grbit1 = (grbit1 or 0x80).toByte() + } + this.initGrbit() + } + } + + /** + * specifies whether the PivotTable has border AutoFormat applied. + * + * @return + */ + fun getFBorder(): Boolean { + return fBorder + } + + /** + * specifies whether the PivotTable has pattern AutoFormat applied. + * + * @param b + */ + fun setFPattern(b: Boolean) { + if (b != fPattern) { + if (b == false) { + grbit2 = (grbit2 and 0xFE).toByte() + } else { + grbit2 = (grbit2 or 0x1).toByte() + } + this.initGrbit() + } + } + + /** + * specifies whether the PivotTable has pattern AutoFormat applied. + * + * @return + */ + fun getFPattern(): Boolean { + return fPattern + } + + /** + * @param b + */ + fun setFNumber(b: Boolean) { + if (b != fNumber) { + if (b == false) { + grbit2 = (grbit2 and 0xFE).toByte() + } else { + grbit2 = (grbit2 or 0x1).toByte() + } + this.initGrbit() + } + } + + fun getFNumber(): Boolean { + return fNumber + } + + + /** + * sets the location and size of this pivot table view + * + * @param range + */ + fun setLocation(range: String) { + val rc = ExcelTools.getRangeRowCol(range) + setRwFirst(rc[0].toShort()) + setColFirst(rc[1].toShort()) + setRwLast(rc[2].toShort()) + setColLast(rc[3].toShort()) + } + /* + Set and get methods, these all basically do the same thing, update the + member variable, then update the body data with the new information. Simple, but large XLSRecord + */ + + /** + * sets the first row of the pivot table + */ + fun setRwFirst(s: Short) { + rwFirst = s + val b = ByteTools.shortToLEBytes(rwFirst) + System.arraycopy(b, 0, data!!, 0, 2) + } + + /** + * retrieves the first row of the pivot table + */ + fun getRwFirst(): Short { + return rwFirst + } + + /** + * sets the last row of the pivot table + */ + fun setRwLast(s: Short) { + rwLast = s + val b = ByteTools.shortToLEBytes(rwLast) + System.arraycopy(b, 0, data!!, 2, 2) + } + + /** + * retieves the first row of the pivot table + */ + fun getRwLast(): Short { + return rwLast + } + + /** + * sets the first column of the pivot table + */ + fun setColFirst(s: Short) { + colFirst = s + val b = ByteTools.shortToLEBytes(colFirst) + System.arraycopy(b, 0, data!!, 4, 2) + } + + /** + * retrieves the first column of the pivot table + */ + fun getColFirst(): Short { + return colFirst + } + + /** + * sets the last column of the pivot table + */ + fun setColLast(s: Short) { + colLast = s + val b = ByteTools.shortToLEBytes(colLast) + System.arraycopy(b, 0, data!!, 6, 2) + } + + /** + * sets the last column of the pivot table + */ + fun getColLast(): Short { + return colLast + } + + fun setRwFirstHead(s: Short) { + rwFirstHead = s + val b = ByteTools.shortToLEBytes(rwFirstHead) + System.arraycopy(b, 0, data!!, 8, 2) + } + + fun getRwFirstHead(): Short { + return rwFirstHead + } + + fun setRwFirstData(s: Short) { + rwFirstData = s + val b = ByteTools.shortToLEBytes(rwFirstData) + System.arraycopy(b, 0, data!!, 10, 2) + } + + fun getRwFirstData(): Short { + return rwFirstData + } + + fun setColFirstData(s: Short) { + colFirstData = s + val b = ByteTools.shortToLEBytes(colFirstData) + System.arraycopy(b, 0, data!!, 12, 2) + } + + fun getColFirstData(): Short { + return colFirstData + } + + /* + Probably want to use the individual grbit options instead of this, but + hey, I'm being thorough. + */ + fun setGrbit(s: Short) { + val b = ByteTools.shortToLEBytes(s) + grbit2 = b[0] + grbit1 = b[1] + System.arraycopy(b, 0, data!!, 36, 2) + this.initGrbit() + } + + + fun setItblAutoFmt(s: Short) { + itblAutoFmt = s + val b = ByteTools.shortToLEBytes(itblAutoFmt) + System.arraycopy(b, 0, data!!, 38, 2) + } + + fun getItblAutoFmt(): Short { + return itblAutoFmt + } + + /* + Builds a new rgch field, changes the length of the entire record.... + */ + private fun buildRgch() { + var data = ByteArray(44) + System.arraycopy(this.getData()!!, 0, data, 0, 44) + var strbytes = ByteArray(0) + var databytes = ByteArray(0) + try { + if (PivotTableName != null) + strbytes = PivotTableName!!.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + if (DataFieldName != null) + databytes = DataFieldName!!.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding pivot table name in SXVIEW: $e") + } + + //update the lengths: + cchName = strbytes.size.toShort() + var nm = ByteTools.shortToLEBytes(cchName) + data[40] = nm[0] + data[41] = nm[1] + + cchData = databytes.size.toShort() + nm = ByteTools.shortToLEBytes(cchData) + data[42] = nm[0] + data[43] = nm[1] + + // now append variable-length string data + val newrgch = ByteArray(cchName.toInt() + cchData.toInt() + 2) // account for encoding bytes + System.arraycopy(strbytes, 0, newrgch, 1, cchName.toInt()) + System.arraycopy(databytes, 0, newrgch, cchName + 2, cchData.toInt()) + + data = ByteTools.append(newrgch, data) + this.setData(data) + } + + + /** + * utility method to retrieve the nth pivot table subrecord (SxVd, SxPI, SxDI ...) + * + * @param opcode opcode to look up + * @param index if > -1, the index or occurrence of the record to return + * @return + */ + private fun getSubRec(opcode: Int, index: Int): BiffRec? { + var j = 0 + for (i in subRecs.indices) { + val br = subRecs.get(i) + if (br.opcode.toInt() == opcode) { + if (index == -1 || j++ == index) + return br + } + } + return null + } + + /** + * utility method to retrieve the subrecord index of the desired pivot table record + * + * @param br pivot table record to look up + * @return + */ + private fun getSubRecIndex(br: BiffRec): Int { + var i = 0 + while (i < subRecs.size) { + if (br === subRecs.get(i)) + break + i++ + } + return i + } + + /** + * utility method to retrieve a pivot table subrecord (SxVd, SxPI, SxDI ...) index + * + * @param opcode opcode to look up + * @param index if > -1, the index or occurrence of the record to return + * @return index in subrecords of desired record + */ + private fun getSubRecIndex(opcode: Int, index: Int): Int { + var j = 0 + for (i in subRecs.indices) { + val br = subRecs.get(i) + if (br.opcode.toInt() == opcode) { + if (index == -1 || j++ == index) + return i + } + } + return -1 + } + + /** + * i + * finds the proper sheet rec insertion index for the desired opcode + * as well as the proper insertion index into the subrecord array + *

                  this method contains alot of intelligence regarding the order of pivot table records + * + * @param opcode one of SXVD, SXIVD, SXPI, SXDI, SXLI, or SXVDEX + * @param index either 0 (for ROW records) or 1 (for COL records) + * @return int[] {sheet record index, subrecord index} or -1's + */ + private fun getPivotRecordInsertionIndexes(opcode: Int, index: Int, pivotFieldIndex: Int): Int { + var i: Int + var j = 0 + if (pivotFieldIndex < 0) { + i = subRecs.size - 1 + while (i >= 0) { + val br = subRecs.get(i) + val bropcode = br.opcode.toInt() + if (bropcode == opcode) { // + if (j < index) { + j++ + i-- + continue // haven't found correct record jet + } + break + } else if (opcode == XLSConstants.SXLI.toInt() && (bropcode == XLSConstants.SXDI.toInt() || bropcode == XLSConstants.SXPI.toInt() || bropcode == XLSConstants.SXIVD.toInt() || bropcode == XLSConstants.SXVDEX.toInt())) { + break + } else if (opcode == XLSConstants.SXDI.toInt() && (bropcode == XLSConstants.SXPI.toInt() || bropcode == XLSConstants.SXIVD.toInt() || bropcode == XLSConstants.SXVDEX.toInt())) { + break + } else if (opcode == XLSConstants.SXPI.toInt() && (bropcode == XLSConstants.SXIVD.toInt() || bropcode == XLSConstants.SXVDEX.toInt())) { + break + } else if (opcode == XLSConstants.SXIVD.toInt() && bropcode == XLSConstants.SXVDEX.toInt()) { + break + } else if (opcode == XLSConstants.SXVD.toInt() && bropcode == XLSConstants.SXEX.toInt()) { + break + } + i-- + } + i++ // counter reverse order + } else { // only lookup within the current pivot field + i = pivotFieldIndex + 1 + while (i < subRecs.size) { + val br = subRecs.get(i) + val bropcode = br.opcode.toInt() + if (bropcode == opcode) { // + if (j < index) { + j++ + i++ + continue // haven't found correct record jet + } + break + } else if (bropcode == XLSConstants.SXVDEX.toInt()) { + break + } + i++ + } + } + return i + /* if (i >= 0) { + return new int[] {subRecs.get(i+j).getRecordIndex(), i}; + } + return new int[] {-1, i};*/ + } + + + /** + * creates the basic, default records necessary to define a pivot table + * + * @param sheet string sheetname where pivot table is located + * @return arraylist of records + */ + fun addInitialRecords(sheet: Boundsheet): ArrayList<*> { + /* + * basic blank pivot table: + * SXVIEW + * after SXVIEW pivot field and item records will go: + * for each pivot field: SxVd, SxVi* n items, SxVDEx + * SXIVD if cDimRw > 0 + * SXIVD if cDimCol > 0 + * SXPI if cDimPg > 0 + * SXDI if cDimData > 0 + * SXLI if cRw > 0 + * SXLI if cCol > 0 + * SXEX + * QXISTAG + * SXADDL records + * + */ + val initialrecs = ArrayList() + this.setSheet(sheet) + this.workBook = sheet.workBook + // SXEX + val sxex = SxEX.prototype as SxEX? + addInit(initialrecs, sxex, sheet) + // QSISXTAG + val qsi = QsiSXTag.prototype as QsiSXTag? + addInit(initialrecs, qsi, sheet) + val sxv = SxVIEWEX9.prototype as SxVIEWEX9? + addInit(initialrecs, sxv, sheet) + // SXADDLs -required SxAddl records: stores additional PivotTableView info of a variety of types + // byte[] b= ByteTools.cLongToLEBytes(sid); b= ByteTosols.append(new byte[] {0, 0}, b); // add 2 reserved bytes + var sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdId.sxd().toInt(), null) + addInit(initialrecs, sa, sheet) + //SXADDL_sxcView: record=sxdVer10Info data:[1, 65, 0, 0, 0, 0] + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVer10Info.sxd().toInt(), null) + addInit(initialrecs, sa, sheet) + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdTableStyleClient.sxd().toInt(), byteArrayOf(0, 0, 0, 0, 0, 0, 51, 0, 0, 0)) + addInit(initialrecs, sa, sheet) + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVerUpdInv.sxd().toInt(), byteArrayOf(2, 0, 0, 0, 0, 0)) + addInit(initialrecs, sa, sheet) + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdVerUpdInv.sxd().toInt(), byteArrayOf(-1, 0, 0, 0, 0, 0)) + addInit(initialrecs, sa, sheet) + sa = SxAddl.getDefaultAddlRecord(SxAddl.ADDL_CLASSES.sxcView, SxcView.sxdEnd.sxd().toInt(), null) + addInit(initialrecs, sa, sheet) + return initialrecs + } + + /** + * utility function to properly add a Pivot Table View subrec + * + * @param initialrecs + * @param rec + * @param addToInitRecords + * @param sheet + */ + private fun addInit(initialrecs: ArrayList<*>, rec: XLSRecord, sheet: Boundsheet) { + initialrecs.add(rec) + rec.setSheet(sheet) + rec.workBook = this.workBook + this.addSubrecord(rec) + } + + override fun toString(): String { + return "SXVIEW: name:" + this.tableName + " iCache:" + iCache + " cDim:" + cDim + " cDimRw:" + cDimRw + " cDimCol:" + cDimCol + + " cDimPg:" + cDimPg + " cDimData:" + cDimData + + " cRw:" + cRw + " cCol:" + cCol + + " datafieldname:" + DataFieldName + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2639291289806138985L + + val prototype: XLSRecord? + get() { + val sx = Sxview() + sx.opcode = XLSConstants.SXVIEW + sx.setData(sx.PROTOTYPE_BYTES) + sx.init() + return sx + } + } +} + diff --git a/src/main/java/io/starter/formats/XLS/TabID.java b/src/main/java/io/starter/formats/XLS/TabID.java deleted file mode 100644 index 8bbb89a..0000000 --- a/src/main/java/io/starter/formats/XLS/TabID.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - - -/** - * RRTabID: Revision Tab ID Record
                  - *

                  - * The RRTabId record specifies an array of unique sheet identifiers, - * each of which is associated with a sheet in the workbook. - * The order of the sheet identifiers in the array matches the order of - * the BoundSheet8 records as they appear in the Globals substream. - */ -public final class TabID extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 722748113519841817L; - CompatibleVector tabIDs = new CompatibleVector(); - - /** - * Default init - */ - public void init() { - super.init(); - for (int i = 0; i < this.getLength() - 4; ) { - short s = ByteTools.readShort(this.getByteAt(i), this.getByteAt(i + 1)); - Short sh = Short.valueOf(s); - tabIDs.add(sh); - i += 2; - } - } - - - /** - * Looks sequentally at the tabIDs and - * makes a new one larger than the previous largest... - */ - void removeRecord() { - short largest = 0; - for (int i = 0; i < tabIDs.size(); i++) { - Short sh = (Short) tabIDs.get(i); - short newshort = sh.shortValue(); - if (newshort > largest) largest = newshort; - } - tabIDs.remove(Short.valueOf(largest)); - this.updateRecord(); - } - - - /** - * Looks sequentally at the tabIDs and - * makes a new one larger than the previous largest... - */ - void addNewRecord() { - short largest = 0; - for (int i = 0; i < tabIDs.size(); i++) { - Short sh = (Short) tabIDs.get(i); - short newshort = sh.shortValue(); - if (newshort > largest) largest = newshort; - } - largest += 0x1; - Short sh = Short.valueOf(largest); - tabIDs.add(sh); - this.updateRecord(); - } - - - /** - * This DOES NOT do what was expected. Sheet order is soley based off of physical Boundsheet ordering - * in the output file. I'm keeping this code in here in case we start supporting revisions. - */ - private boolean changeOrder(int sheet, int newpos) { - int sz = tabIDs.size(); - if (((sheet < 0) || (newpos < 0)) || ((sheet >= sz) || (newpos >= sz))) { - Logger.logWarn("changing Sheet order failed: invalid Sheet Index: " + sheet + ":" + newpos); - return false; - } - Object b = tabIDs.get(sheet); - tabIDs.remove(b); - tabIDs.insertElementAt(b, newpos); - this.updateRecord(); - return true; - } - - /** - * Updates the underlying byte array with the ordered tabId's - * Call after any modification to this record - */ - public void updateRecord() { - short newlen = (short) (tabIDs.size() * 2); - byte[] newbody = new byte[newlen]; - int counter = 0; - for (int i = 0; i < tabIDs.size(); i++) { - Short sh = (Short) tabIDs.get(i); - byte[] b = ByteTools.shortToLEBytes(sh.shortValue()); - newbody[counter] = b[0]; - newbody[counter + 1] = b[1]; - counter += 2; - } - this.setData(newbody); - } - - /** - * @return Returns the tabIDs. - */ - public CompatibleVector getTabIDs() { - return tabIDs; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/TabID.kt b/src/main/java/io/starter/formats/XLS/TabID.kt new file mode 100644 index 0000000..de19f4f --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/TabID.kt @@ -0,0 +1,134 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + + +/** + * **RRTabID: Revision Tab ID Record**

                  + * + * + * The RRTabId record specifies an array of unique sheet identifiers, + * each of which is associated with a sheet in the workbook. + * The order of the sheet identifiers in the array matches the order of + * the BoundSheet8 records as they appear in the Globals substream. + */ +class TabID : io.starter.formats.XLS.XLSRecord() { + /** + * @return Returns the tabIDs. + */ + var tabIDs = CompatibleVector() + internal set + + /** + * Default init + */ + override fun init() { + super.init() + var i = 0 + while (i < this.length - 4) { + val s = ByteTools.readShort(this.getByteAt(i).toInt(), this.getByteAt(i + 1).toInt()) + val sh = java.lang.Short.valueOf(s) + tabIDs.add(sh) + i += 2 + } + } + + + /** + * Looks sequentally at the tabIDs and + * makes a new one larger than the previous largest... + */ + internal fun removeRecord() { + var largest: Short = 0 + for (i in tabIDs.indices) { + val sh = tabIDs[i] as Short + if (sh > largest) largest = sh + } + tabIDs.remove(java.lang.Short.valueOf(largest)) + this.updateRecord() + } + + + /** + * Looks sequentally at the tabIDs and + * makes a new one larger than the previous largest... + */ + internal fun addNewRecord() { + var largest: Short = 0 + for (i in tabIDs.indices) { + val sh = tabIDs[i] as Short + if (sh > largest) largest = sh + } + largest += 0x1 + val sh = java.lang.Short.valueOf(largest) + tabIDs.add(sh) + this.updateRecord() + } + + + /** + * This DOES NOT do what was expected. Sheet order is soley based off of physical Boundsheet ordering + * in the output file. I'm keeping this code in here in case we start supporting revisions. + */ + private fun changeOrder(sheet: Int, newpos: Int): Boolean { + val sz = tabIDs.size + if (sheet < 0 || newpos < 0 || sheet >= sz || newpos >= sz) { + Logger.logWarn("changing Sheet order failed: invalid Sheet Index: $sheet:$newpos") + return false + } + val b = tabIDs[sheet] + tabIDs.remove(b) + tabIDs.insertElementAt(b, newpos) + this.updateRecord() + return true + } + + /** + * Updates the underlying byte array with the ordered tabId's + * Call after any modification to this record + */ + fun updateRecord() { + val newlen = (tabIDs.size * 2).toShort() + val newbody = ByteArray(newlen) + var counter = 0 + for (i in tabIDs.indices) { + val sh = tabIDs[i] as Short + val b = ByteTools.shortToLEBytes(sh) + newbody[counter] = b[0] + newbody[counter + 1] = b[1] + counter += 2 + } + this.setData(newbody) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 722748113519841817L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/TableStyles.java b/src/main/java/io/starter/formats/XLS/TableStyles.kt similarity index 59% rename from src/main/java/io/starter/formats/XLS/TableStyles.java rename to src/main/java/io/starter/formats/XLS/TableStyles.kt index 2ebca25..c78dfb3 100644 --- a/src/main/java/io/starter/formats/XLS/TableStyles.java +++ b/src/main/java/io/starter/formats/XLS/TableStyles.kt @@ -20,78 +20,89 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger -import java.io.UnsupportedEncodingException; +import java.io.UnsupportedEncodingException /** * TableStyles 0x88E - *

                  + * + * * The TableStyles record specifies the default table and PivotTabletable styles and * specifies the beginning of a collection of TableStyle records as defined by the Globals substream. * The collection of TableStyle records specifies user-defined table styles. - *

                  + * + * * frtHeader (12 bytes): An FrtHeader structure. The frtHeader.rt field MUST be 0x088E. - *

                  + * + * * cts (4 bytes): An unsigned integer that specifies the total number of table styles in this document. This is the sum of the standard built-in table styles and all of the custom table styles. This value MUST be greater than or equal to 144 (the number of built-in table styles). - *

                  + * + * * cchDefTableStyle (2 bytes): An unsigned integer that specifies the count of characters in the rgchDefTableStyle field. This value MUST be less than or equal to 255. - *

                  + * + * * cchDefPivotStyle (2 bytes): An unsigned integer that specifies the count of characters in the rgchDefPivotStyle field. This value MUST be less than or equal to 255. - *

                  + * + * * rgchDefTableStyle (variable): An array of Unicode characters whose length is specified by cchDefTableStyle that specifies the name of the default table style. - *

                  + * + * * rgchDefPivotStyle (variable): An array of Unicode characters whose length is specified by cchDefPivotStyle that specifies the name of the default PivotTable style. */ -public class TableStyles extends XLSRecord implements XLSConstants { +class TableStyles : XLSRecord(), XLSConstants { /** * serialVersionUID */ - short cts, cchDefTableStyle, cchDefPivotStyle; - String rgchDefTableStyle = null, rgchDefPivotStyle = null; - private static final long serialVersionUID = 2639291289806138985L; + internal var cts: Short = 0 + internal var cchDefTableStyle: Short = 0 + internal var cchDefPivotStyle: Short = 0 + internal var rgchDefTableStyle: String? = null + internal var rgchDefPivotStyle: String? = null - public void init() { - super.init(); - // -114, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // old frtHeader - //-112, 0, 0, 0, == 144 + override fun init() { + super.init() + // -114, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // old frtHeader + //-112, 0, 0, 0, == 144 // An unsigned integer that specifies the total number of table styles in this document. This is the sum of the standard built-in table styles and all of the custom table styles. This value MUST be greater than or equal to 144 (the number of built-in table styles). - cts = (short) ByteTools.readInt(this.getByteAt(12), this.getByteAt(13), this.getByteAt(14), this.getByteAt(14)); - cchDefTableStyle = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); - cchDefPivotStyle = ByteTools.readShort(this.getByteAt(18), this.getByteAt(19)); - int pos = 20; + cts = ByteTools.readInt(this.getByteAt(12), this.getByteAt(13), this.getByteAt(14), this.getByteAt(14)).toShort() + cchDefTableStyle = ByteTools.readShort(this.getByteAt(16).toInt(), this.getByteAt(17).toInt()) + cchDefPivotStyle = ByteTools.readShort(this.getByteAt(18).toInt(), this.getByteAt(19).toInt()) + var pos = 20 if (cchDefTableStyle > 0) { - byte[] tmp = this.getBytesAt(pos, (cchDefTableStyle) * (2)); + val tmp = this.getBytesAt(pos, cchDefTableStyle * 2) try { - rgchDefTableStyle = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding Table Style name in TableStyles: " + e); + rgchDefTableStyle = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding Table Style name in TableStyles: $e") } - pos += cchDefTableStyle * (2); + + pos += cchDefTableStyle * 2 } if (cchDefPivotStyle > 0) { - byte[] tmp = this.getBytesAt(pos, (cchDefPivotStyle) * (2)); + val tmp = this.getBytesAt(pos, cchDefPivotStyle * 2) try { - rgchDefPivotStyle = new String(tmp, UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("encoding Pivot Style name in TableStyles: " + e); + rgchDefPivotStyle = String(tmp!!, XLSConstants.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("encoding Pivot Style name in TableStyles: $e") } + } } - public static XLSRecord getPrototype() { - TableStyles tx = new TableStyles(); - tx.setOpcode(TABLESTYLES); - tx.setData(new byte[]{-114, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* required id */ - -112, 0, 0, 0, /* cts */ - 17, 0, /* cch */ - 17, 0, /* cch */ - 84, 0, 97, 0, 98, 0, 108, 0, 101, 0, 83, 0, 116, 0, 121, 0, 108, 0, 101, 0, 77, 0, 101, 0, 100, 0, 105, 0, 117, 0, 109, 0, 57, 0, 80, 0, 105, 0, 118, 0, 111, 0, 116, 0, 83, 0, 116, 0, 121, 0, 108, 0, 101, 0, 76, 0, 105, 0, 103, 0, 104, 0, 116, 0, 49, 0, 54, 0 - }); - tx.init(); - return tx; + companion object { + private val serialVersionUID = 2639291289806138985L + + /* required id *//* cts *//* cch *//* cch */ val prototype: XLSRecord? + get() { + val tx = TableStyles() + tx.opcode = XLSConstants.TABLESTYLES + tx.setData(byteArrayOf(-114, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -112, 0, 0, 0, 17, 0, 17, 0, 84, 0, 97, 0, 98, 0, 108, 0, 101, 0, 83, 0, 116, 0, 121, 0, 108, 0, 101, 0, 77, 0, 101, 0, 100, 0, 105, 0, 117, 0, 109, 0, 57, 0, 80, 0, 105, 0, 118, 0, 111, 0, 116, 0, 83, 0, 116, 0, 121, 0, 108, 0, 101, 0, 76, 0, 105, 0, 103, 0, 104, 0, 116, 0, 49, 0, 54, 0)) + tx.init() + return tx + } } } diff --git a/src/main/java/io/starter/formats/XLS/TopMargin.java b/src/main/java/io/starter/formats/XLS/TopMargin.kt similarity index 53% rename from src/main/java/io/starter/formats/XLS/TopMargin.java rename to src/main/java/io/starter/formats/XLS/TopMargin.kt index 633bf4b..e782f27 100644 --- a/src/main/java/io/starter/formats/XLS/TopMargin.java +++ b/src/main/java/io/starter/formats/XLS/TopMargin.kt @@ -20,42 +20,44 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** * Record specifying the top margin of the sheet for printing. */ -public class TopMargin - extends XLSRecord { - private static final long serialVersionUID = -3649192673573344145L; +class TopMargin : XLSRecord() { - double margin; + internal var margin: Double = 0.toDouble() - public void init() { - super.init(); + override fun init() { + super.init() - margin = ByteTools.eightBytetoLEDouble(getBytesAt(0, 8)); + margin = ByteTools.eightBytetoLEDouble(getBytesAt(0, 8)!!) } - public TopMargin() { - this.setOpcode(TOPMARGIN); - margin = 1.0; // default - setData(ByteTools.doubleToLEByteArray(margin)); + init { + this.opcode = XLSConstants.TOPMARGIN + margin = 1.0 // default + setData(ByteTools.doubleToLEByteArray(margin)) } - public void setSheet(Sheet sheet) { - super.setSheet(sheet); - ((Boundsheet) sheet).addPrintRec(this); + override fun setSheet(sheet: Sheet?) { + super.setSheet(sheet) + (sheet as Boundsheet).addPrintRec(this) } - public double getMargin() { - return margin; + fun getMargin(): Double { + return margin } - public void setMargin(double value) { - margin = value; - setData(ByteTools.doubleToLEByteArray(value)); + fun setMargin(value: Double) { + margin = value + setData(ByteTools.doubleToLEByteArray(value)) + } + + companion object { + private val serialVersionUID = -3649192673573344145L } } diff --git a/src/main/java/io/starter/formats/XLS/Txo.java b/src/main/java/io/starter/formats/XLS/Txo.java deleted file mode 100644 index 21a0842..0000000 --- a/src/main/java/io/starter/formats/XLS/Txo.java +++ /dev/null @@ -1,372 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; - - -/** - * Txo: Text Object (1B6h)
                  - * This record stores a text object. This record is followed - * by two CONTINUE records which contain the text data and the - * formatting runs respectively. - *

                  - * If there is no text, the two CONTINUE records are absent. - * - *

                  - * offset  name        size    contents
                  - * ---
                  - * 4       grbit       2       Option flags.  See table.
                  - * 6       rot         2       Orientation of text within the object.
                  - * 8       Reserved    6       Must be zero.
                  - * 14      cchText     2       Length of text in first CONTINUE rec.
                  - * 16      cbRuns      2       Length of formatting runs in second CONTINUE rec.
                  - * 18      Reserved    4       Must be zero.
                  - *
                  - *
                  - * The grbit field contains the following option flags:
                  - *
                  - * bits    mask    name                contents
                  - * ----
                  - * 0       0x01    Reserved
                  - * 3-1     0x0e    alcH                Horizontal text alignment
                  - * 1 = left
                  - * 2 = centered
                  - * 3 = right
                  - * 4 = justified
                  - * 6-4     0x70    alcV                Vertical text alignment
                  - * 1 = top
                  - * 2 = center
                  - * 3 = bottom
                  - * 4 = justified
                  - * 8-7     0x180   Reserved
                  - * 9       0x200   fLockText           1 = lock text option is on
                  - * 15-10   0xfc00  Reserved
                  - *
                  - *
                  - * The first CONTINUE record contains text -- the length is cchText of this object.
                  - * The first byte of the CONTINUE record's body data is 0x0 = compressed unicode.  The rest is text.
                  - *
                  - * 

                  - * - * @see LABELSST - * @see STRING - * @see CONTINUE - */ - -public final class Txo extends io.starter.formats.XLS.XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7043468034346138525L; - Continue text, formattingruns; //20100430 KSC: garbagetxo is really a masked mso, garbagetxo; // garbagetxo is a third continue that appears to be cropping up in infoteria files. We are removing from the file stream currently, but may need to integrate - int state = 0; - short grbit = 0; - short cchText = 0; - short cbRuns = 0; - short rot = 0; - boolean compressedUnicode = false; - - public void init() { - super.init(); - int datalen = this.getLength(); // should be 18 - - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - rot = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - cchText = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - cbRuns = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - - this.setIsValueForCell(false); - // -- not always true... does it bother anybody though? - this.isString = true; - this.isContinueMerged = true; - } - - - /** - * returns the String value of this Text Object - */ - public String getStringVal() { - String s = ""; - if (text == null) return null; - byte[] barr; - int encoding = text.getData()[0]; - barr = new byte[text.getData().length - 1]; - System.arraycopy(text.getData(), 1, barr, 0, barr.length); - try { - if (encoding == 0) {// normal case (Default encoding) - s = new String(barr, WorkBookFactory.DEFAULTENCODING); - } else // encoding=1 (are there other encoding options?? - s = new String(barr, WorkBookFactory.UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("reading Text Object failed: " + e.toString()); - } - return s; - } - - /** - * sets the String value of this Text Object - *
                  if present, will parse embedded formats within the string as: - *
                  the format of the embedded information is as follows: - *
                  <font specifics>text segment<font specifics for next segment>text segment... - *
                  where font specifics can be one or more of: - *
                    b if present, bold - *
                    i if present, italic - *
                    s if present, strikethru - *
                    u if present, underlined - *
                    f="" font name e.g. "Arial" - *
                    sz="" font size in points e.g. "10" - *
                    delimited by ;'s - *
                    For Example: - *
                    <f="Tahoma";b;sz="16">Note: <f="Tahoma";sz="12">This is an important point - * - * @throws IllegalArgumentException if String is incorrect format - */ - // TODO: if length of string is > 8218, must have another continues ****************** - public void setStringVal(String v) throws IllegalArgumentException { - if (v != null && v.indexOf('<') >= 0) - v = parseFormatting(v); // extracts text string from formats and sets formatting runs - else // no formatting present: - this.setFormattingRuns(null); // reset formatting runs - // get the length of the first CONTINUE - byte[] a = v.getBytes(); - byte[] b = new byte[a.length + 1]; - System.arraycopy(a, 0, b, 1, a.length); - if (DEBUGLEVEL > 1) Logger.logInfo("Txo CHANGING: " + this.getStringVal()); - // TODO: checked for Compressed UNICODE in text CONTINUE - b[0] = 0x0; - if (text != null) - text.setData(b); - else // create new text-type continues - though should be already set (see getPrototype) - text = Continue.getTextContinues(v); - b = ByteTools.shortToLEBytes((short) a.length); - this.getData()[10] = b[0]; - this.getData()[11] = b[1]; - cchText = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); // reset text length - if (DEBUGLEVEL > 1) Logger.logInfo(" TO: " + this.getStringVal()); - } - - - /** - * String s contains formatting information; extracts text string from - * formats and sets formatting runs - *
                    the format of the embedded information is as follows: - *
                    < font specifics>text segment< font specifics for next segment>text segment... - *
                    where font specifics can be one or more of: - *
                      b if present, bold - *
                      i if present, italic - *
                      s if present, strikethru - *
                      u if present, underlined - *
                      f="" font name e.g. "Arial" - *
                      sz="" font size in points e.g. "10" - *
                      delimited by ;'s - *
                      For Example: - *
                      < f="Tahoma";b;sz="16">Note: < f="Tahoma";sz="12">This is an impotant point - * - * @param s String with formatting info - * @return txt- string with all formatting info removed - */ - private String parseFormatting(String s) throws IllegalArgumentException { - // parse string for formatting run info - try { - boolean informatting = false; - StringBuffer txt = new StringBuffer(); - short[] frs = new short[2]; - ArrayList formattingRuns = new ArrayList(); - boolean b, it, st, u; - b = it = st = u = false; - String font = "Arial"; // default - int sz = 10; // default - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (!informatting) { - if (c != '<') { - txt.append(c); - } else { - informatting = true; - // initialize formatting run - frs = new short[2]; - frs[0] = (short) txt.length(); - frs[1] = 0; - b = it = st = u = false; - sz = 10; // default - font = "Arial"; // default - } - } else { - String[] z = s.substring(i).split("[;>]"); - if (z == null || z.length == 0 /*would happen on invalid formats such as <;>*/ - || (z.length == 1 && - (!(z[0].endsWith(">") || z[0].endsWith(";"))))) { - txt.append('<'); // not a real embedded format - i--; - informatting = false; - continue; - } - String section = z[0]; - // gather up font info - if (section.equals("b")) { - b = true; - } else if (section.equals("i")) { - it = true; - } else if (section.equals("u")) { - u = true; - } else if (section.equals("s")) { - st = true; - } else if (section.startsWith("f=")) { - // font name - font = section.substring(3); - font = font.substring(0, font.indexOf('"')); - } else if (section.startsWith("sz=")) { - String ssz = section.substring(4); - ssz = ssz.substring(0, ssz.indexOf('"')); - sz = Integer.valueOf(ssz).intValue(); - } - i += section.length(); - if (i < s.length() && s.charAt(i) == '>') { // if got end of formatting section - // store formatting run - informatting = false; - Font f = new Font(font, 400, sz * 20);// sz must be in points - if (b) f.setBold(b); - if (it) f.setItalic(it); - if (u) f.setUnderlined(u); - if (st) f.setStricken(st); - int fIndex = this.getWorkBook().getFontIdx(f); // index for specific font formatting - if (fIndex == -1) // must insert new font - fIndex = this.getWorkBook().insertFont(f) + 1; - frs[1] = (short) fIndex; - formattingRuns.add(frs); - } - } - } - if (formattingRuns.size() > 0) { - formattingRuns.add(new short[]{(short) txt.toString().length(), 15}); // 20100430 KSC: add "extra" formatting run -- necessary for Excel 2003 - this.setFormattingRuns(formattingRuns); - } - return txt.toString(); - } catch (Exception e) { - throw new IllegalArgumentException("Unable to parse String Pattern: " + s); - } - } - - /** - * sets the text for this object, including formatting information - * - * @param txt - */ - public void setStringVal(Unicodestring txt) { - this.setStringVal(txt.getStringVal()); - this.setFormattingRuns(txt.getFormattingRuns()); - } - - /** - * get the formatting runs - fonts per character index, basically, for this text object, - * as an arraylist of short[] {char index, font index} - *

                      - * NOTE: formatting runs in actuality differ from doc: - * apparently each formatting run in the Continue record occupies 8 bytes (not 4) - * plus there is an additional entry appended to the end, that indicates the last char index of the - * string (this last entry is added by Excel 2003 in the Continues Record and is not present in Unicode Strings - */ - public java.util.ArrayList getFormattingRuns() { - java.util.ArrayList formattingRuns = new java.util.ArrayList(); - int frcontinues = this.getSheet().getSheetRecs().indexOf(this) + 2; - Continue fr = (Continue) this.getSheet().getSheetRecs().get(frcontinues); - byte[] frdata = fr.getData(); - int nFormattingRuns = (frdata.length / 8); - if (nFormattingRuns <= 1) return null; // only have the "NO FONT" entry - for (int i = 0; i < nFormattingRuns * 8; ) { - short idx, font; - idx = ByteTools.readShort(frdata[i++], frdata[i++]); - font = ByteTools.readShort(frdata[i++], frdata[i++]); - formattingRuns.add(new short[]{idx, font}); - i += 4; // skip the 4 "reserved" bytes - } - return formattingRuns; - } - - - /** - * set the formatting runs - fonts per character index, basically, for this text object - * as an arraylist of short[] {char index, font index} - * - * @param formattingRuns // NOTES: apparently must have a minimum of 24 bytes in the Continue formatting run to work - * // even though each formatting run=4 bytes, must have 4 bytes of padding between runs...? - *

                      - * // NOTES: apparently formatting runs are different than documentation indicates: - * // each formatting run is 8 bytes: charIndex (2), fontIndex (2), 4 bytes (ignored) - * // at end of formatting runs, must have an extra 8 bytes: charIndex==length/fontIndex, 4 bytes - * // for the purposes of this method, the extra entry must be already present - **/ - public void setFormattingRuns(ArrayList formattingRuns) { - byte[] frs = new byte[4]; // minimum "null formatting run" is 4? apparently must always have a "null" formatting run - if (formattingRuns != null) { - // formatting runs (charindex, fontindex)*n after string data - frs = new byte[formattingRuns.size() * 8]; - for (int i = 0; i < formattingRuns.size(); i++) { - short[] o = (short[]) formattingRuns.get(i); - byte[] charIndex = ByteTools.shortToLEBytes(o[0]); - byte[] fontIndex = ByteTools.shortToLEBytes(o[1]); - System.arraycopy(charIndex, 0, frs, (i * 8), 2); - System.arraycopy(fontIndex, 0, frs, (i * 8) + 2, 2); - } - } - int frcontinues = this.getSheet().getSheetRecs().indexOf(this) + 2; - Continue fr = (Continue) this.getSheet().getSheetRecs().get(frcontinues); - fr.setData(frs); - cbRuns = (short) frs.length; - byte[] b = ByteTools.shortToLEBytes(cbRuns); - this.getData()[12] = b[0]; - this.getData()[13] = b[1]; - - } - - /** - * generates a skeleton Txo with 0 for text length - * and the minimum length for formatting runs (=2) - */ - public static XLSRecord getPrototype() { - Txo t = new Txo(); - t.setOpcode(TXO); - t.setData(t.PROTOTYPE_BYTES); - t.init(); - t.text = Continue.getTextContinues(""); - return t; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{18, 2, /* grbit= 530 */ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, /* cch */ - 4, 0, /* formatting run length */ - 0, 0, 0, 0 /* reserved must be 0 */ - }; - - public String toString() { - return getStringVal(); - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Txo.kt b/src/main/java/io/starter/formats/XLS/Txo.kt new file mode 100644 index 0000000..2dfc41b --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Txo.kt @@ -0,0 +1,409 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.UnsupportedEncodingException +import java.util.ArrayList + + +/** + * **Txo: Text Object (1B6h)**

                      + * This record stores a text object. This record is followed + * by two CONTINUE records which contain the text data and the + * formatting runs respectively. + * + * + * If there is no text, the two CONTINUE records are absent. + * + * + *

                      + * offset  name        size    contents
                      + * ---
                      + * 4       grbit       2       Option flags.  See table.
                      + * 6       rot         2       Orientation of text within the object.
                      + * 8       Reserved    6       Must be zero.
                      + * 14      cchText     2       Length of text in first CONTINUE rec.
                      + * 16      cbRuns      2       Length of formatting runs in second CONTINUE rec.
                      + * 18      Reserved    4       Must be zero.
                      + *
                      + *
                      + * The grbit field contains the following option flags:
                      + *
                      + * bits    mask    name                contents
                      + * ----
                      + * 0       0x01    Reserved
                      + * 3-1     0x0e    alcH                Horizontal text alignment
                      + * 1 = left
                      + * 2 = centered
                      + * 3 = right
                      + * 4 = justified
                      + * 6-4     0x70    alcV                Vertical text alignment
                      + * 1 = top
                      + * 2 = center
                      + * 3 = bottom
                      + * 4 = justified
                      + * 8-7     0x180   Reserved
                      + * 9       0x200   fLockText           1 = lock text option is on
                      + * 15-10   0xfc00  Reserved
                      + *
                      + *
                      + * The first CONTINUE record contains text -- the length is cchText of this object.
                      + * The first byte of the CONTINUE record's body data is 0x0 = compressed unicode.  The rest is text.
                      + *
                      +
                      * + * + * @see LABELSST + * + * @see STRING + * + * @see CONTINUE + */ + +class Txo : io.starter.formats.XLS.XLSRecord() { + internal var text: Continue? = null + internal var formattingruns: Continue? = null //20100430 KSC: garbagetxo is really a masked mso, garbagetxo; // garbagetxo is a third continue that appears to be cropping up in infoteria files. We are removing from the file stream currently, but may need to integrate + internal var state = 0 + internal var grbit: Short = 0 + internal var cchText: Short = 0 + internal var cbRuns: Short = 0 + internal var rot: Short = 0 + internal var compressedUnicode = false + + + /** + * returns the String value of this Text Object + */ + /** + * sets the String value of this Text Object + *

                      if present, will parse embedded formats within the string as: + *

                      the format of the embedded information is as follows: + *

                      <font specifics>text segment<font specifics for next segment>text segment... + *

                      where font specifics can be one or more of: + * b if present, bold + *

                      i if present, italic + *

                      s if present, strikethru + *

                      u if present, underlined + *

                      f="" font name e.g. "Arial" + *

                      sz="" font size in points e.g. "10" + *

                      delimited by ;'s + *

                      For Example: + *

                      <f="Tahoma";b;sz="16">Note: <f="Tahoma";sz="12">This is an important point + * + * @throws IllegalArgumentException if String is incorrect format + */ + // TODO: if length of string is > 8218, must have another continues ****************** + override// normal case (Default encoding) + // encoding=1 (are there other encoding options?? + // extracts text string from formats and sets formatting runs + // no formatting present: + // reset formatting runs + // get the length of the first CONTINUE + // TODO: checked for Compressed UNICODE in text CONTINUE + // create new text-type continues - though should be already set (see getPrototype) + // reset text length + var stringVal: String? + get() { + var s = "" + if (text == null) return null + val barr: ByteArray + val encoding = text!!.data!![0].toInt() + barr = ByteArray(text!!.data!!.size - 1) + System.arraycopy(text!!.data!!, 1, barr, 0, barr.size) + try { + if (encoding == 0) { + s = String(barr, WorkBookFactory.DEFAULTENCODING) + } else + s = String(barr, WorkBookFactory.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("reading Text Object failed: $e") + } + + return s + } + @Throws(IllegalArgumentException::class) + set(v) { + var v = v + if (v != null && v.indexOf('<') >= 0) + v = parseFormatting(v) + else + this.formattingRuns = null + val a = v!!.toByteArray() + var b = ByteArray(a.size + 1) + System.arraycopy(a, 0, b, 1, a.size) + if (DEBUGLEVEL > 1) Logger.logInfo("Txo CHANGING: " + this.stringVal!!) + b[0] = 0x0 + if (text != null) + text!!.setData(b) + else + text = Continue.getTextContinues(v) + b = ByteTools.shortToLEBytes(a.size.toShort()) + this.getData()[10] = b[0] + this.getData()[11] = b[1] + cchText = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + if (DEBUGLEVEL > 1) Logger.logInfo(" TO: " + this.stringVal!!) + } + + /** + * get the formatting runs - fonts per character index, basically, for this text object, + * as an arraylist of short[] {char index, font index} + * + * + * NOTE: formatting runs in actuality differ from doc: + * apparently each formatting run in the Continue record occupies 8 bytes (not 4) + * plus there is an additional entry appended to the end, that indicates the last char index of the + * string (this last entry is added by Excel 2003 in the Continues Record and is not present in Unicode Strings + */ + /** + * set the formatting runs - fonts per character index, basically, for this text object + * as an arraylist of short[] {char index, font index} + * + * @param formattingRuns // NOTES: apparently must have a minimum of 24 bytes in the Continue formatting run to work + * // even though each formatting run=4 bytes, must have 4 bytes of padding between runs...? + * + * + * // NOTES: apparently formatting runs are different than documentation indicates: + * // each formatting run is 8 bytes: charIndex (2), fontIndex (2), 4 bytes (ignored) + * // at end of formatting runs, must have an extra 8 bytes: charIndex==length/fontIndex, 4 bytes + * // for the purposes of this method, the extra entry must be already present + */ + // only have the "NO FONT" entry + // skip the 4 "reserved" bytes + // minimum "null formatting run" is 4? apparently must always have a "null" formatting run + // formatting runs (charindex, fontindex)*n after string data + var formattingRuns: java.util.ArrayList<*>? + get() { + val formattingRuns = java.util.ArrayList() + val frcontinues = this.sheet!!.sheetRecs.indexOf(this) + 2 + val fr = this.sheet!!.sheetRecs[frcontinues] as Continue + val frdata = fr.data + val nFormattingRuns = frdata!!.size / 8 + if (nFormattingRuns <= 1) return null + var i = 0 + while (i < nFormattingRuns * 8) { + val idx: Short + val font: Short + idx = ByteTools.readShort(frdata[i++].toInt(), frdata[i++].toInt()) + font = ByteTools.readShort(frdata[i++].toInt(), frdata[i++].toInt()) + formattingRuns.add(shortArrayOf(idx, font)) + i += 4 + } + return formattingRuns + } + set(formattingRuns) { + var frs = ByteArray(4) + if (formattingRuns != null) { + frs = ByteArray(formattingRuns.size * 8) + for (i in formattingRuns.indices) { + val o = formattingRuns[i] as ShortArray + val charIndex = ByteTools.shortToLEBytes(o[0]) + val fontIndex = ByteTools.shortToLEBytes(o[1]) + System.arraycopy(charIndex, 0, frs, i * 8, 2) + System.arraycopy(fontIndex, 0, frs, i * 8 + 2, 2) + } + } + val frcontinues = this.sheet!!.sheetRecs.indexOf(this) + 2 + val fr = this.sheet!!.sheetRecs[frcontinues] as Continue + fr.setData(frs) + cbRuns = frs.size.toShort() + val b = ByteTools.shortToLEBytes(cbRuns) + this.getData()[12] = b[0] + this.getData()[13] = b[1] + + } + + private val PROTOTYPE_BYTES = byteArrayOf(18, 2, /* grbit= 530 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* cch */ + 4, 0, /* formatting run length */ + 0, 0, 0, 0 /* reserved must be 0 */) + + override fun init() { + super.init() + val datalen = this.length // should be 18 + + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + rot = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + cchText = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + cbRuns = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + + this.isValueForCell = false + // -- not always true... does it bother anybody though? + this.isString = true + this.isContinueMerged = true + } + + + /** + * String s contains formatting information; extracts text string from + * formats and sets formatting runs + *

                      the format of the embedded information is as follows: + *

                      < font specifics>text segment< font specifics for next segment>text segment... + *

                      where font specifics can be one or more of: + * b if present, bold + *

                      i if present, italic + *

                      s if present, strikethru + *

                      u if present, underlined + *

                      f="" font name e.g. "Arial" + *

                      sz="" font size in points e.g. "10" + *

                      delimited by ;'s + *

                      For Example: + *

                      < f="Tahoma";b;sz="16">Note: < f="Tahoma";sz="12">This is an impotant point + * + * @param s String with formatting info + * @return txt- string with all formatting info removed + */ + @Throws(IllegalArgumentException::class) + private fun parseFormatting(s: String): String { + // parse string for formatting run info + try { + var informatting = false + val txt = StringBuffer() + var frs = ShortArray(2) + val formattingRuns = ArrayList() + var b: Boolean + var it: Boolean + var st: Boolean + var u: Boolean + u = false + st = u + it = st + b = it + var font = "Arial" // default + var sz = 10 // default + var i = 0 + while (i < s.length) { + val c = s[i] + if (!informatting) { + if (c != '<') { + txt.append(c) + } else { + informatting = true + // initialize formatting run + frs = ShortArray(2) + frs[0] = txt.length.toShort() + frs[1] = 0 + u = false + st = u + it = st + b = it + sz = 10 // default + font = "Arial" // default + } + } else { + val z = s.substring(i).split("[;>]".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + if (z == null || z.size == 0 /*would happen on invalid formats such as <;>*/ + || z.size == 1 && !(z[0].endsWith(">") || z[0].endsWith(";"))) { + txt.append('<') // not a real embedded format + i-- + informatting = false + i++ + continue + } + val section = z[0] + // gather up font info + if (section == "b") { + b = true + } else if (section == "i") { + it = true + } else if (section == "u") { + u = true + } else if (section == "s") { + st = true + } else if (section.startsWith("f=")) { + // font name + font = section.substring(3) + font = font.substring(0, font.indexOf('"')) + } else if (section.startsWith("sz=")) { + var ssz = section.substring(4) + ssz = ssz.substring(0, ssz.indexOf('"')) + sz = Integer.valueOf(ssz).toInt() + } + i += section.length + if (i < s.length && s[i] == '>') { // if got end of formatting section + // store formatting run + informatting = false + val f = Font(font, 400, sz * 20)// sz must be in points + if (b) f.bold = b + if (it) f.italic = it + if (u) f.underlined = u + if (st) f.stricken = st + var fIndex = this.workBook!!.getFontIdx(f) // index for specific font formatting + if (fIndex == -1) + // must insert new font + fIndex = this.workBook!!.insertFont(f) + 1 + frs[1] = fIndex.toShort() + formattingRuns.add(frs) + } + } + i++ + } + if (formattingRuns.size > 0) { + formattingRuns.add(shortArrayOf(txt.toString().length.toShort(), 15)) // 20100430 KSC: add "extra" formatting run -- necessary for Excel 2003 + this.formattingRuns = formattingRuns + } + return txt.toString() + } catch (e: Exception) { + throw IllegalArgumentException("Unable to parse String Pattern: $s") + } + + } + + /** + * sets the text for this object, including formatting information + * + * @param txt + */ + fun setStringVal(txt: Unicodestring) { + this.stringVal = txt.stringVal + this.formattingRuns = txt.formattingRuns + } + + override fun toString(): String? { + return stringVal + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -7043468034346138525L + + /** + * generates a skeleton Txo with 0 for text length + * and the minimum length for formatting runs (=2) + */ + val prototype: XLSRecord? + get() { + val t = Txo() + t.opcode = XLSConstants.TXO + t.setData(t.PROTOTYPE_BYTES) + t.init() + t.text = Continue.getTextContinues("") + return t + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.java b/src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.kt similarity index 76% rename from src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.java rename to src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.kt index e0c2bae..b9ed84a 100644 --- a/src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.java +++ b/src/main/java/io/starter/formats/XLS/UnencryptedXLSRecord.kt @@ -20,7 +20,7 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * UnencryptedXLSRecord is an XLS record that upon output @@ -28,14 +28,17 @@ * on, and Biff8 is the output format. */ -public class UnencryptedXLSRecord extends XLSRecord { +open class UnencryptedXLSRecord : XLSRecord() { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 356283084373599557L; + override fun shouldEncrypt(): Boolean { + return false + } + + companion object { - public boolean shouldEncrypt() { - return false; + /** + * serialVersionUID + */ + private val serialVersionUID = 356283084373599557L } } diff --git a/src/main/java/io/starter/formats/XLS/Unicodestring.java b/src/main/java/io/starter/formats/XLS/Unicodestring.java deleted file mode 100644 index 8828e41..0000000 --- a/src/main/java/io/starter/formats/XLS/Unicodestring.java +++ /dev/null @@ -1,858 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; - - -/** - * To change the template for this generated type comment go to - * Window>Preferences>Java>Code Generation>Code and Comments - */ - -/** Unicode String: BIFF8 Compressed String format
                      -

                      - offer  name        size    contents
                      - ---
                      - 0       cch         2       Count of characters in string (NOT the number of bytes)
                      - 2       grbit       1       Option flags
                      - 3       rgb         var     Array of string characters and formatting runs.
                      -
                      - GRBIT Option codes:
                      - bits    mask        name        contents
                      - ---
                      - 0       0x1         fHighByte   =0 if all chars have high byte of 0x0
                      - (low bytes only are stored.)
                      - =1 if at least one char in string has nonzero
                      - highbyte.  All chars are double byte (uncompressed)
                      - 1       0x2         (Reserved)  Must be zero.
                      - 2       0x4         fExtSt      Far East Extended Strings.  Asian Phonetic Settings (phonetic)
                      - 3       0x8         fRichSt     Rich String
                      - 7-4     0xF0        (Reserved)  Must be zero.
                      -
                      - rgb:
                      - [2 or 3] 	2 		(optional, only if richtext=1) Number of Rich-Text formatting runs (rt)
                      - [var.] 		4 		(optional, only if phonetic=1) Size of Asian phonetic settings block (in bytes, sz)
                      - var. 	ln or 2∙ln 	Character array (8-bit characters or 16-bit characters, dependent on ccompr)
                      - [var.] 		4∙rt 	(optional, only if richtext=1) List of rt formatting runs
                      - [var.] 		sz 		(optional, only if phonetic=1) Asian Phonetic Settings Block
                      -
                      -
                      - Asian phonetic text can be used to provide extended phonetic information for specific characters or words. It
                      - appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or
                      - entire words.
                      - Offset Size Contents
                      - 0 		2 	identifier 0001H
                      - 2 		2 	Size of the following data (10 + 2∙ln + 6∙np)
                      - 4 		2 	Index to FONT record used for the Asian phonetic text
                      - 6 		2 	Additional settings for the Asian phonetic text:
                      - Bit Mask Contents
                      - 1-0 0003H Type of Japanese phonetic text (type):
                      - 00 = Katakana (narrow) 10 = Hiragana
                      - 01 = Katakana (wide)
                      - 3-2 000CH Alignment of all portions of the Asian phonetic text (align):
                      - 00 = Not specified (Japanese only) 10 = Centered
                      - 01 = Left (Top for vertical text)  11 = Distributed
                      - 5-4 0030H 11 (always set)
                      - 8 		2 	Number of portions the Asian phonetic text is broken into (np).
                      - If np = 0, the Asian phonetic text refers to the entire cell text.
                      - 10 		2 	Total length of the following Asian phonetic text (number of characters, ln)
                      - 12 		2 	Repeated total length of the text
                      - 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters.
                      - Note: If ln = 0, this field is not empty but contains 0000H.
                      - 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each
                      - structure contains the following fields:
                      - Offset Size Contents
                      - 0 2 First character in the Asian phonetic text of this portion (cpa)
                      - 2 2 First character of the main text belonging to this portion (cpm)
                      - 4 2 Number of characters in main text belonging to this portion (ccm)
                      -
                      -
                      - 

                      - * @see Sst - * @see Labelsst - */ -public final class Unicodestring implements XLSConstants, Serializable { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1800227752945355535L; - private int DEBUGLEVEL = -1; - int cch; - private int cchExtRst = 0; - private byte[] ExtRst; - // String stringVal; - private boolean fHighByte = false; - private int formatlen = 0; - private byte[] formatrunnum = new byte[2]; - private byte[] formattingarray; - private boolean fRichSt = false; - private byte grbit; - - // private Isstinf mybucket = null; - private int numformattingruns = 0, sstpos = -1; - private byte[] stringarray; - private int stringlen, offer; - private boolean westernencoding = true; - - /** override equals based on value - of String. - */ - public boolean equals(Object obj) { - if (obj instanceof Unicodestring) { - // compare Unicode strings at byte level rather than just string value - // if(val.equals(obj.toString()))return true; - if (this.westernencoding) { - return java.util.Arrays.equals(this.getWesternBytes(), ((Unicodestring) obj).getWesternBytes()); - } else { - return java.util.Arrays.equals(this.getEasternBytes(), ((Unicodestring) obj).getEasternBytes()); - } - } else if (obj instanceof String) { - String val = this.toString(); - return val.equals(obj); - } - return false; - } - - - /* return the eastern bytes for this Ustring - */ - byte[] getEasternBytes() { - byte[] data = new byte[stringarray.length + formatlen + offer + ExtRst.length]; - byte[] cchbyte = ByteTools.cLongToLEBytes(cch); - System.arraycopy(cchbyte, 0, data, 0, 2); - data[2] = grbit; - //cchExtRst = ExtRst.length; - byte[] cchExtByte = ByteTools.cLongToLEBytes(cchExtRst); - - int tempoffset = 3; - if (fRichSt) tempoffset = 5; - System.arraycopy(cchExtByte, 0, data, tempoffset, 4); - - stringlen = stringarray.length; - System.arraycopy(stringarray, 0, data, offer, stringlen); - if (isRichString()) { - formatrunnum = ByteTools.shortToLEBytes((short) numformattingruns); - // put the number of formatting runs back - data[3] = formatrunnum[0]; - data[4] = formatrunnum[1]; - System.arraycopy(formattingarray, 0, data, stringlen + offer, formatlen); - } - System.arraycopy(ExtRst, 0, data, (stringlen + offer + formatlen), ExtRst.length); - if (data[0] == 0x0) Logger.logInfo("Unicodestring has zero length."); - return data; - } - - /** return the grbit for this UString - */ - byte getGrbit() { - return this.grbit; - } - - /** returns the length of the UNICODESTRING's bytes - */ - int getLength() { - return stringarray.length + formatlen + offer; - } - - /** provide a hint to the CompatibleVector - about this objects likely position. - */ - // public int getRecordIndexHint(){return this.recordIdx;} - - /** set the position of this string in the SST - */ - int getSSTPos() { - return sstpos; - } - - // ride'em cowboy! - byte[] getWesternBytes() { - byte[] data = new byte[stringarray.length + formatlen + offer]; - byte[] cchbyte = ByteTools.cLongToLEBytes(cch); - System.arraycopy(cchbyte, 0, data, 0, 2); - data[2] = grbit; - stringlen = stringarray.length; - System.arraycopy(stringarray, 0, data, offer, stringlen); - if (isRichString()) { - formatrunnum = ByteTools.shortToLEBytes((short) numformattingruns); - // put the number of formatting runs back - data[3] = formatrunnum[0]; - data[4] = formatrunnum[1]; - System.arraycopy(formattingarray, 0, data, stringlen + offer, formatlen); - } - return data; - } - - int len = 0; - - - /**Your standard init method. Choose the format of the string and init it. - * - * @param ustrdata - the data to init the String from - * @param extrstbrk - whether the string ExtRst data spans a continue boundary - */ - void init(byte[] ustrdata, boolean extrstbrk) { - setLen(ustrdata.length); - grbit = ustrdata[2]; - try { - if ((grbit & 0x4) == 0x4) { - westernencoding = false; - this.initEasternEncoding(ustrdata, extrstbrk); - } else { - this.initWesternEncoding(ustrdata); - } - } catch (NegativeArraySizeException e) { - // error processing string but don't fail entirely - } - } - - /** - Init method for japanese/chinese/? style UnicodeStrings - */ - void initEasternEncoding(byte[] ustrdata, boolean extrstbrk) throws NegativeArraySizeException { - // get the row, col and ixfe information - cch = ByteTools.readShort(ustrdata[0], ustrdata[1]); - int dlen = cch; - grbit = ustrdata[2]; - offer = 7; - - // double byte? almost always here - if (((grbit & 0x1) == 0x1)) { - dlen *= 2; - fHighByte = true; - } - - stringarray = new byte[dlen]; - // is rich text? has formatting runs? - if ((grbit & 0x8) == 0x8) { - fRichSt = true; - } - - if (fRichSt) { - offer = 9; - try { - System.arraycopy(ustrdata, offer, stringarray, 0, dlen); // string data - formatrunnum[0] = ustrdata[3]; - formatrunnum[1] = ustrdata[4]; - numformattingruns = ByteTools.readShort(formatrunnum[0], formatrunnum[1]); - formatlen = numformattingruns * 4; - formattingarray = new byte[formatlen]; - System.arraycopy(ustrdata, (dlen + offer), formattingarray, 0, formatlen); // formatting info - cchExtRst = ByteTools.readInt(ustrdata[5], ustrdata[6], ustrdata[7], ustrdata[8]); -// report error??? - if ((ustrdata.length - dlen - offer - cchExtRst) != formatlen) - io.starter.toolkit.Logger.log("Unicodestring.initEasternEncoding: Format runs are not correct"); - ExtRst = new byte[cchExtRst]; - System.arraycopy(ustrdata, (dlen + offer + formatlen), ExtRst, 0, cchExtRst); // Extendadata -//report error??? - if (ExtRst[0] != 1 || ExtRst[1] != 0) - Logger.logWarn("Unicodestring.initEasternEncoding: Phonetic Data is not correct"); - } catch (Throwable e) { - Logger.logInfo("Problem parsing rich text Eastern Unicodestring. len:" + dlen + " rich:" + fRichSt + ". " + e); - } - } else { - try { - cchExtRst = ByteTools.readInt(ustrdata[3], ustrdata[4], ustrdata[5], ustrdata[6]); - int extrstLen = ustrdata.length - (formatlen + offer + dlen); - int off = 0; - - // the most important code in the world - if ((cchExtRst != extrstLen) || (extrstbrk)) { - if (DEBUGLEVEL > 0) Logger.logWarn("Unicodestring ExtRst Inconsistent."); - //off = 1; - cchExtRst = extrstLen; - } - ExtRst = new byte[cchExtRst]; - - System.arraycopy(ustrdata, 7, stringarray, 0, dlen); // string data - System.arraycopy(ustrdata, (ustrdata.length - ExtRst.length) - off, ExtRst, 0, ExtRst.length); // Extendadata -// report error??? - if (ExtRst[0] != 1 || ExtRst[1] != 0) - Logger.logWarn("Unicodestring.initEasternEncoding: Phonetic Data is not correct"); - } catch (Throwable t) { - Logger.logInfo("Problem Parsing non-rich Eastern Unicodestring. len:" + dlen + " rich:" + fRichSt + ". " + t.toString()); - } - } - } - - /** - Init method for default unicode string types. - */ - void initWesternEncoding(byte[] ustrdata) throws NegativeArraySizeException { - // get the row, col and ixfe information - cch = ByteTools.readShort(ustrdata[0], ustrdata[1]); - int dlen = cch + 0; - grbit = ustrdata[2]; - offer = 3; - - // is is double byte stream? - if ((grbit & 0x1) == 0x1) { - dlen *= 2; - fHighByte = true; - } else { - fHighByte = false; - } - - // handle data being greater than the length of the bytes - if (dlen + offer > ustrdata.length) { - dlen = ustrdata.length - offer; - } - - // is rich text? does it have formatting runs? - if ((grbit & 0x8) == 0x8) { - fRichSt = true; - } - - if (fRichSt) { - formatrunnum[0] = ustrdata[3]; - formatrunnum[1] = ustrdata[4]; - numformattingruns = ByteTools.readShort(formatrunnum[0], formatrunnum[1]); - offer = 5; - formatlen = (ustrdata.length - dlen) - offer; - formattingarray = new byte[formatlen]; - System.arraycopy(ustrdata, dlen + offer, formattingarray, 0, formatlen); - } - stringarray = new byte[dlen + 0]; - try { - System.arraycopy(ustrdata, offer, stringarray, 0, dlen); - } catch (Exception e) { - Logger.logInfo("Problem Parsing Western Unicodestring. len:" + dlen + " rich:" + fRichSt + ". " + e); - } - } - - - /** returns whether the Stream position passed in - falls within the UString's character data as opposed - to its length/formatting runs and other non character data - - Read as "typical plot by microsoft to make our lives miserable" - * - * @param pos - the position of the String in the Sst data - * @return whether the string can be broken into parts to span a continue - * - * FROM OPENOFFICE.ORG DOCS... - * Unicode strings are split in a special way. At the beginning of each CONTINUE record the option flags byte is repeated. - Only the character size flag will be set in this flags byte, the Rich-Text flag and the Far-East flag are set to zero. - - In each CONTINUE record it is possible that the character size changes from 8-bit characters to 16-bit characters - and vice versa. ! - Never a Unicode string is split until and including the first character. That means, all header fields (string length, - option flags, optional Rich-Text size, and optional Far-East data size) and the first character of the string have to - occur together in the leading record, or have to be moved completely into the CONTINUE record. ! - Formatting runs (?2.1) cannot be split between their components (character index and FONT record index). If a - string is split between two formatting runs, the option flags field will not be repeated in the CONTINUE record. - * - */ - boolean isBreakable(int pos) { - int mypos = this.getSSTPos(); - int strstart = mypos + offer; - int strend = strstart + stringarray.length; - byte[] bts = this.read(); - int brkpos = (strstart - pos); - if (brkpos < 0) brkpos *= -1; - if (this.cch < 2) return false; // don't break one-char strings -- the 0x1 is confused as a grbit - - if (cchExtRst > 0) return false; - if (pos > (mypos + cchExtRst + stringarray.length + offer)) return false; // in ExtRst data... - - if ((pos > strstart) && (pos < strend)) { - return this.getLength() > 8220; - } - return false; - } - - /** - * Returns wheter the specified index is breaking in the middle of a character. Only useful or needed - * for double byte strings, where the continue boundary cannot be in the middle of a char. - * @return true if illegal break - */ - public boolean charBreakOnBounds(int pos) { - int mypos = this.getSSTPos(); - int strstart = mypos + offer; - int strend = strstart + stringarray.length; - byte[] bts = this.read(); - int brkpos = (strstart - pos); - if (brkpos < 0) brkpos *= -1; - return (brkpos % 2) != 0; - - } - - - boolean isRichString() { - return fRichSt; - } - - /** - * returns true if this unicode string is of Eastern Character Set - * @return - */ - public boolean isEasternString() { - return !westernencoding; - } - - /** - * returns true if this unicode string is in Western Character Set - * @return - */ - public boolean isWesternString() { - return westernencoding; - } - - /** returns the UNICODESTRING's bytes - */ - - public byte[] read() { - if (westernencoding) return this.getWesternBytes(); - return this.getEasternBytes(); - } - - /** - * returns just the string array portion of this Unicode String - * minus header, formatting, etc. - * @return - */ - public byte[] readStr() { - return stringarray; - } - - /** set the Extsst Isstinf bucket for the USTRING see page 313 XL'97 book - */ - //void setBucket(Isstinf i){this.mybucket = i;} - - /** set the position of this string in the SST - */ - void setSSTPos(int p) { - sstpos = p; - } - - /** return the String representation of this Unicodestring - */ - public String toString() { - try { - if (fHighByte) { - return new String(stringarray, UNICODEENCODING); // defaultEncoding); - } - return new String(stringarray, DEFAULTENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("Problem decoding Unicodestring. " + e + " Resorting to default encoding: " + DEFAULTENCODING); - try { - return new String(stringarray, DEFAULTENCODING); // supported by JDK1.1 + - } catch (UnsupportedEncodingException t) { - if (DEBUGLEVEL > -1) Logger.logInfo("Problem decoding Unicodestring. " + t); - } - } - return null; - } - - /** return the String representation of this Unicodestring. Sets a caching string value in the Unicode String, - * primarily needed for lookups when shared strings = true - */ - public String toCachingString() { - try { - if (fHighByte) { -/* KSC: TESTING: for handling japanese fonts... boolean isjp; // KSC: testing - if (!westernencoding || Unicodestring.containsJapanese(this.getStringVal())) - isjp= true; -*/ - return new String(stringarray, UNICODEENCODING); // defaultEncoding); - } else { - return new String(stringarray, DEFAULTENCODING); - } - } catch (UnsupportedEncodingException e) { - Logger.logInfo("Problem decoding Unicodestring. " + e + " Resorting to default encoding: " + DEFAULTENCODING); - try { - return new String(stringarray, DEFAULTENCODING); // supported by JDK1.1 + - } catch (UnsupportedEncodingException t) { - if (DEBUGLEVEL > -1) Logger.logInfo("Problem decoding Unicodestring. " + t); - return null; - } - } - } - - /** updates the unicode string - */ - public void updateUnicodeString(String s) { - if (s.equals(this.toString())) return; - // 0 cch 2 Count of characters in string (NOT the number of bytes) - // 2 grbit 1 Option flags - // make sure to get formatting runs if present - byte[] strbytes = null; - try { - if (!ByteTools.isUnicode(s)) { - strbytes = s.getBytes(DEFAULTENCODING); - grbit = (byte) (grbit & 0xFE); - } else { - strbytes = s.getBytes(UNICODEENCODING); - if (!((grbit & 0x1) == 0x1)) { - grbit += 0x1; - } - } - } catch (UnsupportedEncodingException e) { - Logger.logInfo("Problem encoding string: " + e); - } - int strdatalen = strbytes.length; - int strlen = s.length(); - byte[] blen = ByteTools.cLongToLEBytes(strlen); - if ((grbit & 0x4) == 0x4) { // it was an eastern string. Blow that extrst out... - grbit = (byte) (grbit & 0xFB); - ExtRst = null; - offer -= 4; - westernencoding = true; - } - if ((grbit & 0x8) == 0x8) { // had formatting runs - remove (since cannot keep with new string) - formatlen = 0; - fRichSt = false; - grbit ^= 0x8; - offer -= 2; - } - byte[] newbytes = new byte[strdatalen + offer + formatlen]; - newbytes[2] = grbit; - System.arraycopy(blen, 0, newbytes, 0, 2); - - System.arraycopy(strbytes, 0, newbytes, offer, strdatalen); - if (this.isRichString()) { - System.arraycopy(this.formattingarray, 0, newbytes, strdatalen + this.offer, this.formatlen); - // put the number of formatting runs back - newbytes[3] = this.formatrunnum[0]; - newbytes[4] = this.formatrunnum[1]; - } - this.init(newbytes, false); - } - - public int getLen() { - return len; - } - - public void setLen(int leng) { - this.len = leng; - } - - public String getStringVal() { - return toString(); - } - - /** - * Return true if the string contains formatting runs embedded within it - * - * @return - */ - public boolean hasFormattingRuns() { - return numformattingruns > 0; - } - - /** - * return formatting runs, if any, for this unicode strings - * formatting runs are a list of one or more pairs of [char index, font index] - * where char index determines where to apply font specified by font index - * @return - */ - public ArrayList getFormattingRuns() { - if (numformattingruns == 0) return null; - ArrayList formattingruns = new ArrayList(); - for (int i = 0; i < numformattingruns; i++) { - short charIndex = ByteTools.readShort(formattingarray[(i * 4)], formattingarray[((i * 4) + 1)]); - short fontIndex = ByteTools.readShort(formattingarray[(i * 4) + 2], formattingarray[((i * 4) + 3)]); - formattingruns.add(new short[]{charIndex, fontIndex}); - } - return formattingruns; - } - - /** returns true if the char c is a double-byte character */ - private static boolean isJapanese(char c) { - return c >= '\u0100' && c <= '\uffff'; - // simpler: return c>'\u00ff'; - } - - /** returns true if the String s contains any Japanese characters */ - public static boolean containsJapanese(String s) { - for (int i = 0; i < s.length(); i++) { - if (isJapaneseII(s.charAt(i))) { - return true; - } - } - return false; - } - - /** returns true if the char c is a Japanese character. */ - private static boolean isJapaneseII(char c) { - // katakana: - if (c >= '\u30a0' && c <= '\u30ff') return true; - // hiragana - if (c >= '\u3040' && c <= '\u309f') return true; - // CJK Unified Ideographs - if (c >= '\u4e00' && c <= '\u9fff') return true; - // CJK symbols & punctuation - if (c >= '\u3000' && c <= '\u303f') return true; - // KangXi (kanji) - if (c >= '\u2f00' && c <= '\u2fdf') return true; - // KanBun - if (c >= '\u3190' && c <= '\u319f') return true; - // CJK Unified Ideographs Extension A - if (c >= '\u3400' && c <= '\u4db5') return true; - // CJK Compatibility Forms - if (c >= '\ufe30' && c <= '\ufe4f') return true; - // CJK Compatibility - if (c >= '\u3300' && c <= '\u33ff') return true; - // CJK Radicals Supplement - return c >= '\u2e80' && c <= '\u2eff'; - // other character.. - } - - /** info on encoding: - * - * - 16 code from 0 x0000 to 0 x007F) is the ASCII characters - the next 128 Unicode characters (x0080 code from 0 to 0 x00FF) is ISO? 8859-1 on the expansion of ASCII. - Unicode in different parts of the same characters are based on existing standards. - This is to facilitate the conversion. - Greek alphabet x0370 use from 0 to 0 x03FF code, - The use of Slavic language x0400 from 0 to 0x04FF code - United States use x0530 from 0 to 0x058F code - Hebrew x0590 from 0 to 0 x05FF code. - China, Japan and South Korea hieroglyphs (known as the CJK) occupiers from 0x3000 to 0x9FFF code. - * There are four types of Shift-JIS encoding available (although you may need to customize the encoding list to see them all). - Japanes(Shift JIS) contains the half-width alphanumeric characters, symbols, and katakana, along with JIS X0208 (JIS 1&2) characters. - Japanese (Mac OS) contains all of Japanese (Shift JIS) along with some Mac-specific symbols. - Japanese (Windows, DOS) contains all of Japanese (Shift JIS) along with some Windows-specific symbols. - Japanese (Shift JIS X0213) contains all of Japanese (Shift JIS) along with kanji specified in JIS 3&4 for a total of 13,000 characters total. - Japanese text created in Classic OS is encoded in Japanese (Mac OS), so this is the safest option among the various Shift JIS encodings. - - Asian characters are Kanji, Hiragana, Katakana - (full and half-width), full-width numbers and punctuation, - and Chinese and Korean characters. This is often abbreviated as “CJK.” - - - Japanese Language Encoding - Encoding Other Names Vendor/Standard Body Other Rosette Names - CCSID 1027 EBCDIK Microsoft & IBM CCSID-1027, CCSID1027 - CCSID 290 EBCDIK Microsoft & IBM CCSID-290, CCSID290 - CCSID 930 IBM CCSID-930, CCSID930 - CCSID 939 IBM CCSID-939, CCSID939 - CCSID 942 Microsoft & IBM CCSID-942, CCSID942 - CP10001 Macintosh Japanese Microsoft & IBM CP10001 - CP20290 (full/half width Latin & halfwidth katakana) Microsoft & IBM CP20290 - CP21027 (halfwidth Latin, halfwidth katakana&private use) Microsoft & IBM CP21027 - EUC-JP Unix EUC-JP, EUC-J - EUC-JP-JISROMAN Unix EUC-JP-JISROMAN - ISO 2022-JP International or National Standard ISO-2022-JP - JapaneseAutoDetect For encodings, see JapaneseAutodetect Rosette Autodetect JapaneseAutoDetect - - Encoding Other Names Vendor/Standard Body Other Rosette Names - JIS_X_0201 HalfWidthKatakana International or National Standard JIS_X_0201, IBM897 - JIS_X_0208 International or National Standard JIS_X_0208 - Shift-JISMS MS_Kanji, CP932 Microsoft & IBM Shift-JIS, SJIS - Shift_JIS-2004 ShiftJISX0213 Microsoft & IBM Shift-Jis2004, Shift_JISX0213,Shift-X - Shift-JIS78 Shift-JIS without MS/IBM extensions Unix/Macintosh Shift-JIS78, SJIS78 - - Chinese Language Encoding - Encoding Other Names Vendor/Standard Body Other Rosette Names - ChineseAutoDetect For encodings, see ChineseAutodetect Rosette Autodetect ChineseAutoDetect - HKSCS International or National Standard HKSCS - ISO 2022-CN International or National Standard ISO-2022-CN - GB 18030 International or National Standard GB18030 - Chinese, Simplified - CCSID 935 IBM CCSID-935, CCSID935 - EUC-CN GB2312, EUC-SC Unix GB2312 - GB2312 EUC-CN, EUC-SC International or National Standard GB2312 - HZ-GB-2312 HZ-GB-2312 International or National Standard HZ, HZ-GB-2312 - CP936 GBK Microsoft & IBM CP936, GBK - MacChineseSimplified Macintosh MacChineseSimplified - Chinese, Traditional - CCSID 937 IBM CCSID-937, CCSID937 - CNS-11643-1986 EUC-TW International or National Standard CNS-11643-1986 - CNS-11643-1992 EUC-TW International or National Standard CNS-11643, CNS-11643-1992 - EUC-TW CNS-11643-1986, CNS-11643-1992 Unix CNS-11643, CNS-11643-1992 - GB12345 International or National Standard GB12345 - Big5 International or National Standard Big5 - Big5+ International or National Standard Big5+, Big5Plus - CP10002 Macintosh Traditional Chinese Microsoft & IBM CP10002 - CP950 Microsoft & IBM CP950 - MacChineseTraditional Macintosh MacChineseTraditional - - The CCSID in the iSeries objects must be 935 (Simplified Chinese) - The code page in the PC for Simplified Chinese is 1388 - The Data Type in the DSPF/PRTF must be O (Other) - - more info: - Shift_JIS DBCS 16-bit Japanese encoding (Note that you must use an underscore character (_), not a hyphen (-) in the name in CFML attributes.) - (same as MS932) - EUC-KR DBCS 16-bit Korean encoding - UCS-2 DBCS Two-byte Unicode encoding - UTF-8 MBCS Multibyte Unicode encoding. ASCII is 7-bit; non-ASCII characters used in European and many Middle Eastern languages are two-byte; and most Asian characters are three-byte - - - - ExtRst data is defined as: - - ID (WORD): 0x0001 - Length (WORD) - unknown (WORD) - Flag (WORD) - Number of relation informations between Katakana and Kanji (WORD) - Number of characters #1 (WORD) - Number of characters #2 (WORD) - >>> always the same ? - Katakana characters - Relation informations between Katakana and Kanji (6 bytes each) - - - - mdImeMode (8 bits): An unsigned integer that specifies the Input Method Editor (IME) mode - enforced by this data validation. This value is only used when the input language is one of the - following languages: - - 1. Chinese Simplified (Locale ID = 2052) - 2. Chinese Traditional (Locale ID = 1028) - 3. Japanese (Locale ID = 1041) - 4. Korean (Locale ID = 1042) - The input for the cell can be restricted to specific sets of characters, as specified by the value of - mdImeMode. MUST be a value from the following table: - Value Meaning - 0x00 No Control - 0x01 On - 0x02 Off (English) - 0x04 Hiragana - 0x05 wide katakana - 0x06 narrow katakana - 0x07 Full-width alphanumeric - 0x08 Half-width alphanumeric - 0x09 Full-width hangul - 0x0A Half-width hangul - - - ExtRst: - Asian phonetic text5 (Ruby) can be used to provide extended phonetic information for specific characters or words. It - appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or - entire words. - Offset Size Contents - 0 2 Unknown identifier 0001H - 2 2 Size of the following data (10 + 2∙ln + 6∙np) - 4 2 Index to FONT record (➜5.45) used for the Asian phonetic text - 6 2 Additional settings for the Asian phonetic text: - Bit Mask Contents - 1-0 0003H Type of Japanese phonetic text (type): - 002 = Katakana (narrow) 102 = Hiragana - 012 = Katakana (wide) - 3-2 000CH Alignment of all portions of the Asian phonetic text (align): - 002 = Not specified (Japanese only) 102 = Centered - 012 = Left (Top for vertical text) 112 = Distributed - 5-4 0030H 112 (always set) - 8 2 Number of portions the Asian phonetic text is broken into (np). - If np = 0, the Asian phonetic text refers to the entire cell text. - 10 2 Total length of the following Asian phonetic text (number of characters, ln) - 12 2 Repeated total length of the text - 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters. - Note: If ln = 0, this field is not empty but contains 0000H. - 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each - structure contains the following fields: - Offset Size Contents - 0 2 First character in the Asian phonetic text of this portion (cpa) - 2 2 First character of the main text belonging to this portion (cpm) - 4 2 Number of characters in main text belonging to this portion (ccm) - - Example: Japanese word Tokyo (東京) with added hiragana (とうきょう)6. The following examples show the - contents of the important fields of the Asian Phonetic Settings Block. - Example 1: Hiragana centered over the entire word: - とうきょう - 東京 - type = 102 (hiragana) - align = 102 (centered) - np = 0 (no portions, hiragana refers to entire text) - ln = 5 (length of entire hiragana text) - No portion structures - - - Asian phonetic text5 (Ruby) can be used to provide extended phonetic information for specific characters or words. It - appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or - entire words. - Offset Size Contents - 0 2 Unknown identifier 0001H - 2 2 Size of the following data (10 + 2∙ln + 6∙np) - 4 2 Index to FONT record (➜5.45) used for the Asian phonetic text - 6 2 Additional settings for the Asian phonetic text: - Bit Mask Contents - 1-0 0003H Type of Japanese phonetic text (type): - 002 = Katakana (narrow) 102 = Hiragana - 012 = Katakana (wide) - 3-2 000CH Alignment of all portions of the Asian phonetic text (align): - 002 = Not specified (Japanese only) 102 = Centered - 012 = Left (Top for vertical text) 112 = Distributed - 5-4 0030H 112 (always set) - 8 2 Number of portions the Asian phonetic text is broken into (np). - If np = 0, the Asian phonetic text refers to the entire cell text. - 10 2 Total length of the following Asian phonetic text (number of characters, ln) - 12 2 Repeated total length of the text - 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters. - Note: If ln = 0, this field is not empty but contains 0000H. - 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each - structure contains the following fields: - Offset Size Contents - 0 2 First character in the Asian phonetic text of this portion (cpa) - 2 2 First character of the main text belonging to this portion (cpm) - 4 2 Number of characters in main text belonging to this portion (ccm) - 5 */ -} -/** - string 1= - [-115, -1, 111, -1, 107, -1, 102, -1, 114, 0, 108, 0, 100, 0, 33, 0] - phonetic - [1, 0, 12, 0, 1, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0] - new String(stringarray, "UTF-16LE") - (java.lang.String) ヘッォヲrld! - String 3= - [-40, 48, -61, 48, -87, 48, -14, 48, 82, -1, 76, -1, 68, -1, 1, -1] - phonetic - [1, 0, 12, 0, 1, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0] - new String(stringarray, "UTF-16LE") - (java.lang.String) ヘッォヲrld! - - - Excel 2007 Supported Character Sets: - 0x00 Specifies the ANSI character set. - 0x01 Specifies the default character set. - 0x02 Specifies the Symbol character set. - 0x4D Specifies a Macintosh (Standard Roman) character set. - 0x80 Specifies the JIS character set. - 0x81 Specifies the Hangul character set. - 0x82 Specifies a Johab character set. - 0x86 Specifies the GB-2312 character set. - 0x88 Specifies the Chinese Big Five character set. - 0xA1 Specifies a Greek character set. - 0xA2 Specifies a Turkish character set. - 0xA3 Specifies a Vietnamese character set. - 0xB1 Specifies a Hebrew character set. - 0xB2 Specifies an Arabic character set. - 0xBA Specifies a Baltic character set. - 0xCC Specifies a Russian character set. - 0xDE Specifies a Thai character set. - 0xEE Specifies an Eastern European character set. - 0xFF Specifies an OEM character set not defined by this Office Open XML Standard. - Any other value Application-defined, may be ignored. - */ diff --git a/src/main/java/io/starter/formats/XLS/Unicodestring.kt b/src/main/java/io/starter/formats/XLS/Unicodestring.kt new file mode 100644 index 0000000..6b8d0fb --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Unicodestring.kt @@ -0,0 +1,845 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.Serializable +import java.io.UnsupportedEncodingException +import java.util.ArrayList + + +/** + * To change the template for this generated type comment go to + * Window>Preferences>Java>Code Generation>Code and Comments + */ + +/** **Unicode String: BIFF8 Compressed String format**

                      + * + *
                      + * offer  name        size    contents
                      + * ---
                      + * 0       cch         2       Count of characters in string (NOT the number of bytes)
                      + * 2       grbit       1       Option flags
                      + * 3       rgb         var     Array of string characters and formatting runs.
                      + *
                      + * GRBIT Option codes:
                      + * bits    mask        name        contents
                      + * ---
                      + * 0       0x1         fHighByte   =0 if all chars have high byte of 0x0
                      + * (low bytes only are stored.)
                      + * =1 if at least one char in string has nonzero
                      + * highbyte.  All chars are double byte (uncompressed)
                      + * 1       0x2         (Reserved)  Must be zero.
                      + * 2       0x4         fExtSt      Far East Extended Strings.  Asian Phonetic Settings (phonetic)
                      + * 3       0x8         fRichSt     Rich String
                      + * 7-4     0xF0        (Reserved)  Must be zero.
                      + *
                      + * rgb:
                      + * [2 or 3] 	2 		(optional, only if richtext=1) Number of Rich-Text formatting runs (rt)
                      + * [var.] 		4 		(optional, only if phonetic=1) Size of Asian phonetic settings block (in bytes, sz)
                      + * var. 	ln or 2∙ln 	Character array (8-bit characters or 16-bit characters, dependent on ccompr)
                      + * [var.] 		4∙rt 	(optional, only if richtext=1) List of rt formatting runs
                      + * [var.] 		sz 		(optional, only if phonetic=1) Asian Phonetic Settings Block
                      + *
                      + *
                      + * Asian phonetic text can be used to provide extended phonetic information for specific characters or words. It
                      + * appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or
                      + * entire words.
                      + * Offset Size Contents
                      + * 0 		2 	identifier 0001H
                      + * 2 		2 	Size of the following data (10 + 2∙ln + 6∙np)
                      + * 4 		2 	Index to FONT record used for the Asian phonetic text
                      + * 6 		2 	Additional settings for the Asian phonetic text:
                      + * Bit Mask Contents
                      + * 1-0 0003H Type of Japanese phonetic text (type):
                      + * 00 = Katakana (narrow) 10 = Hiragana
                      + * 01 = Katakana (wide)
                      + * 3-2 000CH Alignment of all portions of the Asian phonetic text (align):
                      + * 00 = Not specified (Japanese only) 10 = Centered
                      + * 01 = Left (Top for vertical text)  11 = Distributed
                      + * 5-4 0030H 11 (always set)
                      + * 8 		2 	Number of portions the Asian phonetic text is broken into (np).
                      + * If np = 0, the Asian phonetic text refers to the entire cell text.
                      + * 10 		2 	Total length of the following Asian phonetic text (number of characters, ln)
                      + * 12 		2 	Repeated total length of the text
                      + * 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters.
                      + * Note: If ln = 0, this field is not empty but contains 0000H.
                      + * 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each
                      + * structure contains the following fields:
                      + * Offset Size Contents
                      + * 0 2 First character in the Asian phonetic text of this portion (cpa)
                      + * 2 2 First character of the main text belonging to this portion (cpm)
                      + * 4 2 Number of characters in main text belonging to this portion (ccm)
                      + *
                      + *
                      +
                      * + * @see Sst + * + * @see Labelsst + */ +class Unicodestring : XLSConstants, Serializable { + private val DEBUGLEVEL = -1 + internal var cch: Int = 0 + private var cchExtRst = 0 + private var ExtRst: ByteArray? = null + // String stringVal; + private var fHighByte = false + private var formatlen = 0 + private var formatrunnum = ByteArray(2) + private var formattingarray: ByteArray? = null + internal var isRichString = false + private set + /** return the grbit for this UString + */ + internal var grbit: Byte = 0 + private set + + // private Isstinf mybucket = null; + private var numformattingruns = 0 + /** provide a hint to the CompatibleVector + * about this objects likely position. + */ + // public int getRecordIndexHint(){return this.recordIdx;} + + /** set the position of this string in the SST + */ + /** set the Extsst Isstinf bucket for the USTRING see page 313 XL'97 book + */ + //void setBucket(Isstinf i){this.mybucket = i;} + + /** set the position of this string in the SST + */ + internal var sstPos = -1 + private var stringarray: ByteArray? = null + private var stringlen: Int = 0 + private var offer: Int = 0 + /** + * returns true if this unicode string is in Western Character Set + * @return + */ + var isWesternString = true + private set + + + /* return the eastern bytes for this Ustring + */ + internal//cchExtRst = ExtRst.length; + // put the number of formatting runs back + val easternBytes: ByteArray + get() { + val data = ByteArray(stringarray!!.size + formatlen + offer + ExtRst!!.size) + val cchbyte = ByteTools.cLongToLEBytes(cch) + System.arraycopy(cchbyte, 0, data, 0, 2) + data[2] = grbit + val cchExtByte = ByteTools.cLongToLEBytes(cchExtRst) + + var tempoffset = 3 + if (isRichString) tempoffset = 5 + System.arraycopy(cchExtByte, 0, data, tempoffset, 4) + + stringlen = stringarray!!.size + System.arraycopy(stringarray!!, 0, data, offer, stringlen) + if (isRichString) { + formatrunnum = ByteTools.shortToLEBytes(numformattingruns.toShort()) + data[3] = formatrunnum[0] + data[4] = formatrunnum[1] + System.arraycopy(formattingarray!!, 0, data, stringlen + offer, formatlen) + } + System.arraycopy(ExtRst!!, 0, data, stringlen + offer + formatlen, ExtRst!!.size) + if (data[0].toInt() == 0x0) Logger.logInfo("Unicodestring has zero length.") + return data + } + + /** returns the length of the UNICODESTRING's bytes + */ + internal val length: Int + get() = stringarray!!.size + formatlen + offer + + // ride'em cowboy! + internal// put the number of formatting runs back + val westernBytes: ByteArray + get() { + val data = ByteArray(stringarray!!.size + formatlen + offer) + val cchbyte = ByteTools.cLongToLEBytes(cch) + System.arraycopy(cchbyte, 0, data, 0, 2) + data[2] = grbit + stringlen = stringarray!!.size + System.arraycopy(stringarray!!, 0, data, offer, stringlen) + if (isRichString) { + formatrunnum = ByteTools.shortToLEBytes(numformattingruns.toShort()) + data[3] = formatrunnum[0] + data[4] = formatrunnum[1] + System.arraycopy(formattingarray!!, 0, data, stringlen + offer, formatlen) + } + return data + } + + var len = 0 + + /** + * returns true if this unicode string is of Eastern Character Set + * @return + */ + val isEasternString: Boolean + get() = !isWesternString + + val stringVal: String? + get() = toString() + + /** + * return formatting runs, if any, for this unicode strings + * formatting runs are a list of one or more pairs of [char index, font index] + * where char index determines where to apply font specified by font index + * @return + */ + val formattingRuns: ArrayList<*>? + get() { + if (numformattingruns == 0) return null + val formattingruns = ArrayList() + for (i in 0 until numformattingruns) { + val charIndex = ByteTools.readShort(formattingarray!![i * 4].toInt(), formattingarray!![i * 4 + 1].toInt()) + val fontIndex = ByteTools.readShort(formattingarray!![i * 4 + 2].toInt(), formattingarray!![i * 4 + 3].toInt()) + formattingruns.add(shortArrayOf(charIndex, fontIndex)) + } + return formattingruns + } + + /** override equals based on value + * of String. + */ + override fun equals(obj: Any?): Boolean { + if (obj is Unicodestring) { + // compare Unicode strings at byte level rather than just string value + // if(val.equals(obj.toString()))return true; + return if (this.isWesternString) { + java.util.Arrays.equals(this.westernBytes, obj.westernBytes) + } else { + java.util.Arrays.equals(this.easternBytes, obj.easternBytes) + } + } else if (obj is String) { + val `val` = this.toString() + return `val` == obj + } + return false + } + + + /**Your standard init method. Choose the format of the string and init it. + * + * @param ustrdata - the data to init the String from + * @param extrstbrk - whether the string ExtRst data spans a continue boundary + */ + internal fun init(ustrdata: ByteArray, extrstbrk: Boolean) { + len = ustrdata.size + grbit = ustrdata[2] + try { + if (grbit and 0x4 == 0x4) { + isWesternString = false + this.initEasternEncoding(ustrdata, extrstbrk) + } else { + this.initWesternEncoding(ustrdata) + } + } catch (e: NegativeArraySizeException) { + // error processing string but don't fail entirely + } + + } + + /** + * Init method for japanese/chinese/? style UnicodeStrings + */ + @Throws(NegativeArraySizeException::class) + internal fun initEasternEncoding(ustrdata: ByteArray, extrstbrk: Boolean) { + // get the row, col and ixfe information + cch = ByteTools.readShort(ustrdata[0].toInt(), ustrdata[1].toInt()).toInt() + var dlen = cch + grbit = ustrdata[2] + offer = 7 + + // double byte? almost always here + if (grbit and 0x1 == 0x1) { + dlen *= 2 + fHighByte = true + } + + stringarray = ByteArray(dlen) + // is rich text? has formatting runs? + if (grbit and 0x8 == 0x8) { + isRichString = true + } + + if (isRichString) { + offer = 9 + try { + System.arraycopy(ustrdata, offer, stringarray!!, 0, dlen) // string data + formatrunnum[0] = ustrdata[3] + formatrunnum[1] = ustrdata[4] + numformattingruns = ByteTools.readShort(formatrunnum[0].toInt(), formatrunnum[1].toInt()).toInt() + formatlen = numformattingruns * 4 + formattingarray = ByteArray(formatlen) + System.arraycopy(ustrdata, dlen + offer, formattingarray!!, 0, formatlen) // formatting info + cchExtRst = ByteTools.readInt(ustrdata[5], ustrdata[6], ustrdata[7], ustrdata[8]) + // report error??? + if (ustrdata.size - dlen - offer - cchExtRst != formatlen) + io.starter.toolkit.Logger.log("Unicodestring.initEasternEncoding: Format runs are not correct") + ExtRst = ByteArray(cchExtRst) + System.arraycopy(ustrdata, dlen + offer + formatlen, ExtRst!!, 0, cchExtRst) // Extendadata + //report error??? + if (ExtRst!![0].toInt() != 1 || ExtRst!![1].toInt() != 0) + Logger.logWarn("Unicodestring.initEasternEncoding: Phonetic Data is not correct") + } catch (e: Throwable) { + Logger.logInfo("Problem parsing rich text Eastern Unicodestring. len:$dlen rich:$isRichString. $e") + } + + } else { + try { + cchExtRst = ByteTools.readInt(ustrdata[3], ustrdata[4], ustrdata[5], ustrdata[6]) + val extrstLen = ustrdata.size - (formatlen + offer + dlen) + val off = 0 + + // the most important code in the world + if (cchExtRst != extrstLen || extrstbrk) { + if (DEBUGLEVEL > 0) Logger.logWarn("Unicodestring ExtRst Inconsistent.") + //off = 1; + cchExtRst = extrstLen + } + ExtRst = ByteArray(cchExtRst) + + System.arraycopy(ustrdata, 7, stringarray!!, 0, dlen) // string data + System.arraycopy(ustrdata, ustrdata.size - ExtRst!!.size - off, ExtRst!!, 0, ExtRst!!.size) // Extendadata + // report error??? + if (ExtRst!![0].toInt() != 1 || ExtRst!![1].toInt() != 0) + Logger.logWarn("Unicodestring.initEasternEncoding: Phonetic Data is not correct") + } catch (t: Throwable) { + Logger.logInfo("Problem Parsing non-rich Eastern Unicodestring. len:$dlen rich:$isRichString. $t") + } + + } + } + + /** + * Init method for default unicode string types. + */ + @Throws(NegativeArraySizeException::class) + internal fun initWesternEncoding(ustrdata: ByteArray) { + // get the row, col and ixfe information + cch = ByteTools.readShort(ustrdata[0].toInt(), ustrdata[1].toInt()).toInt() + var dlen = cch + 0 + grbit = ustrdata[2] + offer = 3 + + // is is double byte stream? + if (grbit and 0x1 == 0x1) { + dlen *= 2 + fHighByte = true + } else { + fHighByte = false + } + + // handle data being greater than the length of the bytes + if (dlen + offer > ustrdata.size) { + dlen = ustrdata.size - offer + } + + // is rich text? does it have formatting runs? + if (grbit and 0x8 == 0x8) { + isRichString = true + } + + if (isRichString) { + formatrunnum[0] = ustrdata[3] + formatrunnum[1] = ustrdata[4] + numformattingruns = ByteTools.readShort(formatrunnum[0].toInt(), formatrunnum[1].toInt()).toInt() + offer = 5 + formatlen = ustrdata.size - dlen - offer + formattingarray = ByteArray(formatlen) + System.arraycopy(ustrdata, dlen + offer, formattingarray!!, 0, formatlen) + } + stringarray = ByteArray(dlen + 0) + try { + System.arraycopy(ustrdata, offer, stringarray!!, 0, dlen) + } catch (e: Exception) { + Logger.logInfo("Problem Parsing Western Unicodestring. len:$dlen rich:$isRichString. $e") + } + + } + + + /** returns whether the Stream position passed in + * falls within the UString's character data as opposed + * to its length/formatting runs and other non character data + * + * Read as "typical plot by microsoft to make our lives miserable" + * + * @param pos - the position of the String in the Sst data + * @return whether the string can be broken into parts to span a continue + * + * FROM OPENOFFICE.ORG DOCS... + * Unicode strings are split in a special way. At the beginning of each CONTINUE record the option flags byte is repeated. + * Only the character size flag will be set in this flags byte, the Rich-Text flag and the Far-East flag are set to zero. + * + * In each CONTINUE record it is possible that the character size changes from 8-bit characters to 16-bit characters + * and vice versa. ! + * Never a Unicode string is split until and including the first character. That means, all header fields (string length, + * option flags, optional Rich-Text size, and optional Far-East data size) and the first character of the string have to + * occur together in the leading record, or have to be moved completely into the CONTINUE record. ! + * Formatting runs (?2.1) cannot be split between their components (character index and FONT record index). If a + * string is split between two formatting runs, the option flags field will not be repeated in the CONTINUE record. + */ + internal fun isBreakable(pos: Int): Boolean { + val mypos = this.sstPos + val strstart = mypos + offer + val strend = strstart + stringarray!!.size + val bts = this.read() + var brkpos = strstart - pos + if (brkpos < 0) brkpos *= -1 + if (this.cch < 2) return false // don't break one-char strings -- the 0x1 is confused as a grbit + + if (cchExtRst > 0) return false + if (pos > mypos + cchExtRst + stringarray!!.size + offer) return false // in ExtRst data... + + return if (pos > strstart && pos < strend) { + this.length > 8220 + } else false + } + + /** + * Returns wheter the specified index is breaking in the middle of a character. Only useful or needed + * for double byte strings, where the continue boundary cannot be in the middle of a char. + * @return true if illegal break + */ + fun charBreakOnBounds(pos: Int): Boolean { + val mypos = this.sstPos + val strstart = mypos + offer + val strend = strstart + stringarray!!.size + val bts = this.read() + var brkpos = strstart - pos + if (brkpos < 0) brkpos *= -1 + return brkpos % 2 != 0 + + } + + /** returns the UNICODESTRING's bytes + */ + + fun read(): ByteArray { + return if (isWesternString) this.westernBytes else this.easternBytes + } + + /** + * returns just the string array portion of this Unicode String + * minus header, formatting, etc. + * @return + */ + fun readStr(): ByteArray? { + return stringarray + } + + /** return the String representation of this Unicodestring + */ + override fun toString(): String? { + try { + return if (fHighByte) { + String(stringarray!!, XLSConstants.UNICODEENCODING) // defaultEncoding); + } else String(stringarray!!, XLSConstants.DEFAULTENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("Problem decoding Unicodestring. " + e + " Resorting to default encoding: " + XLSConstants.DEFAULTENCODING) + try { + return String(stringarray!!, XLSConstants.DEFAULTENCODING) // supported by JDK1.1 + + } catch (t: UnsupportedEncodingException) { + if (DEBUGLEVEL > -1) Logger.logInfo("Problem decoding Unicodestring. $t") + } + + } + + return null + } + + /** return the String representation of this Unicodestring. Sets a caching string value in the Unicode String, + * primarily needed for lookups when shared strings = true + */ + fun toCachingString(): String? { + try { + return if (fHighByte) { + /* KSC: TESTING: for handling japanese fonts... boolean isjp; // KSC: testing + if (!westernencoding || Unicodestring.containsJapanese(this.getStringVal())) + isjp= true; +*/ + String(stringarray!!, XLSConstants.UNICODEENCODING) // defaultEncoding); + } else { + String(stringarray!!, XLSConstants.DEFAULTENCODING) + } + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("Problem decoding Unicodestring. " + e + " Resorting to default encoding: " + XLSConstants.DEFAULTENCODING) + try { + return String(stringarray!!, XLSConstants.DEFAULTENCODING) // supported by JDK1.1 + + } catch (t: UnsupportedEncodingException) { + if (DEBUGLEVEL > -1) Logger.logInfo("Problem decoding Unicodestring. $t") + return null + } + + } + + } + + /** updates the unicode string + */ + fun updateUnicodeString(s: String) { + if (s == this.toString()) return + // 0 cch 2 Count of characters in string (NOT the number of bytes) + // 2 grbit 1 Option flags + // make sure to get formatting runs if present + var strbytes: ByteArray? = null + try { + if (!ByteTools.isUnicode(s)) { + strbytes = s.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + grbit = (grbit and 0xFE).toByte() + } else { + strbytes = s.toByteArray(charset(XLSConstants.UNICODEENCODING)) + if (grbit and 0x1 != 0x1) { + grbit += 0x1 + } + } + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("Problem encoding string: $e") + } + + val strdatalen = strbytes!!.size + val strlen = s.length + val blen = ByteTools.cLongToLEBytes(strlen) + if (grbit and 0x4 == 0x4) { // it was an eastern string. Blow that extrst out... + grbit = (grbit and 0xFB).toByte() + ExtRst = null + offer -= 4 + isWesternString = true + } + if (grbit and 0x8 == 0x8) { // had formatting runs - remove (since cannot keep with new string) + formatlen = 0 + isRichString = false + grbit = grbit xor 0x8 + offer -= 2 + } + val newbytes = ByteArray(strdatalen + offer + formatlen) + newbytes[2] = grbit + System.arraycopy(blen, 0, newbytes, 0, 2) + + System.arraycopy(strbytes, 0, newbytes, offer, strdatalen) + if (this.isRichString) { + System.arraycopy(this.formattingarray!!, 0, newbytes, strdatalen + this.offer, this.formatlen) + // put the number of formatting runs back + newbytes[3] = this.formatrunnum[0] + newbytes[4] = this.formatrunnum[1] + } + this.init(newbytes, false) + } + + /** + * Return true if the string contains formatting runs embedded within it + * + * @return + */ + fun hasFormattingRuns(): Boolean { + return numformattingruns > 0 + } + + companion object { + + /** + * serialVersionUID + */ + private const val serialVersionUID = -1800227752945355535L + + /** returns true if the char c is a double-byte character */ + private fun isJapanese(c: Char): Boolean { + return c >= '\u0100' && c <= '\uffff' + // simpler: return c>'\u00ff'; + } + + /** returns true if the String s contains any Japanese characters */ + fun containsJapanese(s: String): Boolean { + for (i in 0 until s.length) { + if (isJapaneseII(s[i])) { + return true + } + } + return false + } + + /** returns true if the char c is a Japanese character. */ + private fun isJapaneseII(c: Char): Boolean { + // katakana: + if (c >= '\u30a0' && c <= '\u30ff') return true + // hiragana + if (c >= '\u3040' && c <= '\u309f') return true + // CJK Unified Ideographs + if (c >= '\u4e00' && c <= '\u9fff') return true + // CJK symbols & punctuation + if (c >= '\u3000' && c <= '\u303f') return true + // KangXi (kanji) + if (c >= '\u2f00' && c <= '\u2fdf') return true + // KanBun + if (c >= '\u3190' && c <= '\u319f') return true + // CJK Unified Ideographs Extension A + if (c >= '\u3400' && c <= '\u4db5') return true + // CJK Compatibility Forms + if (c >= '\ufe30' && c <= '\ufe4f') return true + // CJK Compatibility + return if (c >= '\u3300' && c <= '\u33ff') true else c >= '\u2e80' && c <= '\u2eff' + // CJK Radicals Supplement + // other character.. + } + } + + /** info on encoding: + * + * + * 16 code from 0 x0000 to 0 x007F) is the ASCII characters + * the next 128 Unicode characters (x0080 code from 0 to 0 x00FF) is ISO? 8859-1 on the expansion of ASCII. + * Unicode in different parts of the same characters are based on existing standards. + * This is to facilitate the conversion. + * Greek alphabet x0370 use from 0 to 0 x03FF code, + * The use of Slavic language x0400 from 0 to 0x04FF code + * United States use x0530 from 0 to 0x058F code + * Hebrew x0590 from 0 to 0 x05FF code. + * China, Japan and South Korea hieroglyphs (known as the CJK) occupiers from 0x3000 to 0x9FFF code. + * There are four types of Shift-JIS encoding available (although you may need to customize the encoding list to see them all). + * Japanes(Shift JIS) contains the half-width alphanumeric characters, symbols, and katakana, along with JIS X0208 (JIS 1&2) characters. + * Japanese (Mac OS) contains all of Japanese (Shift JIS) along with some Mac-specific symbols. + * Japanese (Windows, DOS) contains all of Japanese (Shift JIS) along with some Windows-specific symbols. + * Japanese (Shift JIS X0213) contains all of Japanese (Shift JIS) along with kanji specified in JIS 3&4 for a total of 13,000 characters total. + * Japanese text created in Classic OS is encoded in Japanese (Mac OS), so this is the safest option among the various Shift JIS encodings. + * + * Asian characters are Kanji, Hiragana, Katakana + * (full and half-width), full-width numbers and punctuation, + * and Chinese and Korean characters. This is often abbreviated as “CJK.” + * + * + * Japanese Language Encoding + * Encoding Other Names Vendor/Standard Body Other Rosette Names + * CCSID 1027 EBCDIK Microsoft & IBM CCSID-1027, CCSID1027 + * CCSID 290 EBCDIK Microsoft & IBM CCSID-290, CCSID290 + * CCSID 930 IBM CCSID-930, CCSID930 + * CCSID 939 IBM CCSID-939, CCSID939 + * CCSID 942 Microsoft & IBM CCSID-942, CCSID942 + * CP10001 Macintosh Japanese Microsoft & IBM CP10001 + * CP20290 (full/half width Latin & halfwidth katakana) Microsoft & IBM CP20290 + * CP21027 (halfwidth Latin, halfwidth katakana&private use) Microsoft & IBM CP21027 + * EUC-JP Unix EUC-JP, EUC-J + * EUC-JP-JISROMAN Unix EUC-JP-JISROMAN + * ISO 2022-JP International or National Standard ISO-2022-JP + * JapaneseAutoDetect For encodings, see JapaneseAutodetect Rosette Autodetect JapaneseAutoDetect + * + * Encoding Other Names Vendor/Standard Body Other Rosette Names + * JIS_X_0201 HalfWidthKatakana International or National Standard JIS_X_0201, IBM897 + * JIS_X_0208 International or National Standard JIS_X_0208 + * Shift-JISMS MS_Kanji, CP932 Microsoft & IBM Shift-JIS, SJIS + * Shift_JIS-2004 ShiftJISX0213 Microsoft & IBM Shift-Jis2004, Shift_JISX0213,Shift-X + * Shift-JIS78 Shift-JIS without MS/IBM extensions Unix/Macintosh Shift-JIS78, SJIS78 + * + * Chinese Language Encoding + * Encoding Other Names Vendor/Standard Body Other Rosette Names + * ChineseAutoDetect For encodings, see ChineseAutodetect Rosette Autodetect ChineseAutoDetect + * HKSCS International or National Standard HKSCS + * ISO 2022-CN International or National Standard ISO-2022-CN + * GB 18030 International or National Standard GB18030 + * Chinese, Simplified + * CCSID 935 IBM CCSID-935, CCSID935 + * EUC-CN GB2312, EUC-SC Unix GB2312 + * GB2312 EUC-CN, EUC-SC International or National Standard GB2312 + * HZ-GB-2312 HZ-GB-2312 International or National Standard HZ, HZ-GB-2312 + * CP936 GBK Microsoft & IBM CP936, GBK + * MacChineseSimplified Macintosh MacChineseSimplified + * Chinese, Traditional + * CCSID 937 IBM CCSID-937, CCSID937 + * CNS-11643-1986 EUC-TW International or National Standard CNS-11643-1986 + * CNS-11643-1992 EUC-TW International or National Standard CNS-11643, CNS-11643-1992 + * EUC-TW CNS-11643-1986, CNS-11643-1992 Unix CNS-11643, CNS-11643-1992 + * GB12345 International or National Standard GB12345 + * Big5 International or National Standard Big5 + * Big5+ International or National Standard Big5+, Big5Plus + * CP10002 Macintosh Traditional Chinese Microsoft & IBM CP10002 + * CP950 Microsoft & IBM CP950 + * MacChineseTraditional Macintosh MacChineseTraditional + * + * The CCSID in the iSeries objects must be 935 (Simplified Chinese) + * The code page in the PC for Simplified Chinese is 1388 + * The Data Type in the DSPF/PRTF must be O (Other) + * + * more info: + * Shift_JIS DBCS 16-bit Japanese encoding (Note that you must use an underscore character (_), not a hyphen (-) in the name in CFML attributes.) + * (same as MS932) + * EUC-KR DBCS 16-bit Korean encoding + * UCS-2 DBCS Two-byte Unicode encoding + * UTF-8 MBCS Multibyte Unicode encoding. ASCII is 7-bit; non-ASCII characters used in European and many Middle Eastern languages are two-byte; and most Asian characters are three-byte + * + * + * + * ExtRst data is defined as: + * + * ID (WORD): 0x0001 + * Length (WORD) + * unknown (WORD) + * Flag (WORD) + * Number of relation informations between Katakana and Kanji (WORD) + * Number of characters #1 (WORD) + * Number of characters #2 (WORD) + * >>> always the same ? + * Katakana characters + * Relation informations between Katakana and Kanji (6 bytes each) + * + * + * + * mdImeMode (8 bits): An unsigned integer that specifies the Input Method Editor (IME) mode + * enforced by this data validation. This value is only used when the input language is one of the + * following languages: + * + * 1. Chinese Simplified (Locale ID = 2052) + * 2. Chinese Traditional (Locale ID = 1028) + * 3. Japanese (Locale ID = 1041) + * 4. Korean (Locale ID = 1042) + * The input for the cell can be restricted to specific sets of characters, as specified by the value of + * mdImeMode. MUST be a value from the following table: + * Value Meaning + * 0x00 No Control + * 0x01 On + * 0x02 Off (English) + * 0x04 Hiragana + * 0x05 wide katakana + * 0x06 narrow katakana + * 0x07 Full-width alphanumeric + * 0x08 Half-width alphanumeric + * 0x09 Full-width hangul + * 0x0A Half-width hangul + * + * + * ExtRst: + * Asian phonetic text5 (Ruby) can be used to provide extended phonetic information for specific characters or words. It + * appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or + * entire words. + * Offset Size Contents + * 0 2 Unknown identifier 0001H + * 2 2 Size of the following data (10 + 2∙ln + 6∙np) + * 4 2 Index to FONT record (➜5.45) used for the Asian phonetic text + * 6 2 Additional settings for the Asian phonetic text: + * Bit Mask Contents + * 1-0 0003H Type of Japanese phonetic text (type): + * 002 = Katakana (narrow) 102 = Hiragana + * 012 = Katakana (wide) + * 3-2 000CH Alignment of all portions of the Asian phonetic text (align): + * 002 = Not specified (Japanese only) 102 = Centered + * 012 = Left (Top for vertical text) 112 = Distributed + * 5-4 0030H 112 (always set) + * 8 2 Number of portions the Asian phonetic text is broken into (np). + * If np = 0, the Asian phonetic text refers to the entire cell text. + * 10 2 Total length of the following Asian phonetic text (number of characters, ln) + * 12 2 Repeated total length of the text + * 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters. + * Note: If ln = 0, this field is not empty but contains 0000H. + * 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each + * structure contains the following fields: + * Offset Size Contents + * 0 2 First character in the Asian phonetic text of this portion (cpa) + * 2 2 First character of the main text belonging to this portion (cpm) + * 4 2 Number of characters in main text belonging to this portion (ccm) + * + * Example: Japanese word Tokyo (東京) with added hiragana (とうきょう)6. The following examples show the + * contents of the important fields of the Asian Phonetic Settings Block. + * Example 1: Hiragana centered over the entire word: + * とうきょう + * 東京 + * type = 102 (hiragana) + * align = 102 (centered) + * np = 0 (no portions, hiragana refers to entire text) + * ln = 5 (length of entire hiragana text) + * No portion structures + * + * + * Asian phonetic text5 (Ruby) can be used to provide extended phonetic information for specific characters or words. It + * appears above the regular text (or to the right of vertical text), and can refer to single characters, groups of characters, or + * entire words. + * Offset Size Contents + * 0 2 Unknown identifier 0001H + * 2 2 Size of the following data (10 + 2∙ln + 6∙np) + * 4 2 Index to FONT record (➜5.45) used for the Asian phonetic text + * 6 2 Additional settings for the Asian phonetic text: + * Bit Mask Contents + * 1-0 0003H Type of Japanese phonetic text (type): + * 002 = Katakana (narrow) 102 = Hiragana + * 012 = Katakana (wide) + * 3-2 000CH Alignment of all portions of the Asian phonetic text (align): + * 002 = Not specified (Japanese only) 102 = Centered + * 012 = Left (Top for vertical text) 112 = Distributed + * 5-4 0030H 112 (always set) + * 8 2 Number of portions the Asian phonetic text is broken into (np). + * If np = 0, the Asian phonetic text refers to the entire cell text. + * 10 2 Total length of the following Asian phonetic text (number of characters, ln) + * 12 2 Repeated total length of the text + * 14 2∙ln or 2 Character array of Asian phonetic text, no Unicode string header, always 16-bit characters. + * Note: If ln = 0, this field is not empty but contains 0000H. + * 14+2∙ln 6∙np List of np structures that describe the position of each portion in the main text. Each + * structure contains the following fields: + * Offset Size Contents + * 0 2 First character in the Asian phonetic text of this portion (cpa) + * 2 2 First character of the main text belonging to this portion (cpm) + * 4 2 Number of characters in main text belonging to this portion (ccm) + * 5 */ +} +/** + * string 1= + * [-115, -1, 111, -1, 107, -1, 102, -1, 114, 0, 108, 0, 100, 0, 33, 0] + * phonetic + * [1, 0, 12, 0, 1, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0] + * new String(stringarray, "UTF-16LE") + * (java.lang.String) ヘッォヲrld! + * String 3= + * [-40, 48, -61, 48, -87, 48, -14, 48, 82, -1, 76, -1, 68, -1, 1, -1] + * phonetic + * [1, 0, 12, 0, 1, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0] + * new String(stringarray, "UTF-16LE") + * (java.lang.String) ヘッォヲrld! + * + * + * Excel 2007 Supported Character Sets: + * 0x00 Specifies the ANSI character set. + * 0x01 Specifies the default character set. + * 0x02 Specifies the Symbol character set. + * 0x4D Specifies a Macintosh (Standard Roman) character set. + * 0x80 Specifies the JIS character set. + * 0x81 Specifies the Hangul character set. + * 0x82 Specifies a Johab character set. + * 0x86 Specifies the GB-2312 character set. + * 0x88 Specifies the Chinese Big Five character set. + * 0xA1 Specifies a Greek character set. + * 0xA2 Specifies a Turkish character set. + * 0xA3 Specifies a Vietnamese character set. + * 0xB1 Specifies a Hebrew character set. + * 0xB2 Specifies an Arabic character set. + * 0xBA Specifies a Baltic character set. + * 0xCC Specifies a Russian character set. + * 0xDE Specifies a Thai character set. + * 0xEE Specifies an Eastern European character set. + * 0xFF Specifies an OEM character set not defined by this Office Open XML Standard. + * Any other value Application-defined, may be ignored. + */ diff --git a/src/main/java/io/starter/formats/XLS/Usersviewbegin.java b/src/main/java/io/starter/formats/XLS/Usersviewbegin.java deleted file mode 100644 index 1a1153b..0000000 --- a/src/main/java/io/starter/formats/XLS/Usersviewbegin.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - - -/** - * USERSVIEWBEGIN: Custom View Settings (1AAh)
                      - *

                      - * USERSVIEWBEGIN describes the settings for a custom view for the sheet - * - *

                      - * offset  name            size    contents
                      - * ---
                      - * 4       guid            16      GID for custom view
                      - * 20      iTabid          4       Tab index for the sheet (1-based)
                      - * 24      wScale          4       Window Zoom
                      - * 28      icv             4       Index to color val
                      - * 32      pnnSel          4       Pane number of active pane
                      - * 36      grbit           4       Option flags
                      - * 40      refTopLeft      8       Ref struct describing the visible area of top left pane
                      - * 48      operNum         16      array of 2 floats specifying vert/horiz pane split
                      - * 64      colRPane        2       first visible right pane col
                      - * 66      rwBPane         2       first visible bottom pane col
                      - *
                      - * 

                      - */ -public final class Usersviewbegin extends io.starter.formats.XLS.XLSRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1877650235927064991L; - // record fields - private int tabid = -1; - private int wScale = -1; - private int icv = -1; - private int pnnSel = -1; - private int grbit = -1; - //private float operNum1 = 0; - //private float operNum2 = 0; - //private short colRPane = 0; - //private short rwBPane = 0; - - //66 Long! - //byte[] RECBYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0}; - - /** - * Constructor for a Usersviewbegin to be made on the fly. - */ - Usersviewbegin() { - // TODO: init Usersviewbegin - this(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); - } - - Usersviewbegin(byte[] b) { - setData(b); - setOpcode(USERSVIEWBEGIN); - setLength((short) 6); - this.init(); - } - - // grbit fields - private boolean fDspGutsSv = false; // true if outline symbols are displayed - - // TODO: implement this class - public void init() { - super.init(); - short num1 = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); - short num2 = ByteTools.readShort(this.getByteAt(18), this.getByteAt(19)); - tabid = ByteTools.readInt(num2, num1); - num1 = ByteTools.readShort(this.getByteAt(20), this.getByteAt(21)); - num2 = ByteTools.readShort(this.getByteAt(22), this.getByteAt(23)); - wScale = ByteTools.readInt(num1, num2); - num1 = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); - num2 = ByteTools.readShort(this.getByteAt(26), this.getByteAt(27)); - icv = ByteTools.readInt(num1, num2); - num1 = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); - num2 = ByteTools.readShort(this.getByteAt(26), this.getByteAt(27)); - pnnSel = ByteTools.readInt(num1, num2); - num1 = ByteTools.readShort(this.getByteAt(28), this.getByteAt(29)); - num2 = ByteTools.readShort(this.getByteAt(30), this.getByteAt(31)); - grbit = ByteTools.readInt(num1, num2); - this.decodeGrbit(); - if (DEBUGLEVEL > 3) Logger.logInfo("Usersviewbegin Tab Index: " + tabid); - } - - /** - * decodeGrbit does masking to determine the grbit settings - */ - private void decodeGrbit() { - fDspGutsSv = (grbit & 0x00000010) == 0x00000010; - - - } - - /** - * updateGrbit looks at all the grbit variables and rebuilds the grbit - * field based off those. - */ - private void updateGrbit() { - if (fDspGutsSv) { - grbit = (0x00000010 | grbit); - } else { - grbit = (0xFFFFFFEF & grbit); - } - // update the record bytes - byte[] b = this.getData(); - byte[] grbytes = ByteTools.cLongToLEBytes(grbit); - System.arraycopy(grbytes, 0, b, 28, 4); - this.setData(b); - } - - /** - * Checks to see if outlines are being displayed on the worksheet - * - * @return fDspGutsSv - */ - public boolean getDisplayOutlines() { - return fDspGutsSv; - } - - /** - * Sets whether outlines are displayed on the worksheet. Should be set to true - * when using any of the 'grouping' methods. - * - * @param disp - */ - public void setDisplayOutlines(boolean disp) { - fDspGutsSv = disp; - updateGrbit(); - } - /* - 20 iTabid 4 Tab index for the sheet (1-based) - */ - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Usersviewbegin.kt b/src/main/java/io/starter/formats/XLS/Usersviewbegin.kt new file mode 100644 index 0000000..2c3ecb6 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Usersviewbegin.kt @@ -0,0 +1,156 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * **USERSVIEWBEGIN: Custom View Settings (1AAh)**

                      + * + * + * USERSVIEWBEGIN describes the settings for a custom view for the sheet + * + * + *
                      + * offset  name            size    contents
                      + * ---
                      + * 4       guid            16      GID for custom view
                      + * 20      iTabid          4       Tab index for the sheet (1-based)
                      + * 24      wScale          4       Window Zoom
                      + * 28      icv             4       Index to color val
                      + * 32      pnnSel          4       Pane number of active pane
                      + * 36      grbit           4       Option flags
                      + * 40      refTopLeft      8       Ref struct describing the visible area of top left pane
                      + * 48      operNum         16      array of 2 floats specifying vert/horiz pane split
                      + * 64      colRPane        2       first visible right pane col
                      + * 66      rwBPane         2       first visible bottom pane col
                      + *
                      +
                      * + */ +class Usersviewbegin @JvmOverloads internal constructor(b: ByteArray = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) : io.starter.formats.XLS.XLSRecord() { + // record fields + private var tabid = -1 + private var wScale = -1 + private var icv = -1 + private var pnnSel = -1 + private var grbit = -1 + + // grbit fields + private var fDspGutsSv = false // true if outline symbols are displayed + + /** + * Checks to see if outlines are being displayed on the worksheet + * + * @return fDspGutsSv + */ + /** + * Sets whether outlines are displayed on the worksheet. Should be set to true + * when using any of the 'grouping' methods. + * + * @param disp + */ + var displayOutlines: Boolean + get() = fDspGutsSv + set(disp) { + fDspGutsSv = disp + updateGrbit() + } + + init { + setData(b) + opcode = XLSConstants.USERSVIEWBEGIN + length = 6.toShort() + this.init() + } + + // TODO: implement this class + override fun init() { + super.init() + var num1 = ByteTools.readShort(this.getByteAt(16).toInt(), this.getByteAt(17).toInt()) + var num2 = ByteTools.readShort(this.getByteAt(18).toInt(), this.getByteAt(19).toInt()) + tabid = ByteTools.readInt(num2.toInt(), num1.toInt()) + num1 = ByteTools.readShort(this.getByteAt(20).toInt(), this.getByteAt(21).toInt()) + num2 = ByteTools.readShort(this.getByteAt(22).toInt(), this.getByteAt(23).toInt()) + wScale = ByteTools.readInt(num1.toInt(), num2.toInt()) + num1 = ByteTools.readShort(this.getByteAt(24).toInt(), this.getByteAt(25).toInt()) + num2 = ByteTools.readShort(this.getByteAt(26).toInt(), this.getByteAt(27).toInt()) + icv = ByteTools.readInt(num1.toInt(), num2.toInt()) + num1 = ByteTools.readShort(this.getByteAt(24).toInt(), this.getByteAt(25).toInt()) + num2 = ByteTools.readShort(this.getByteAt(26).toInt(), this.getByteAt(27).toInt()) + pnnSel = ByteTools.readInt(num1.toInt(), num2.toInt()) + num1 = ByteTools.readShort(this.getByteAt(28).toInt(), this.getByteAt(29).toInt()) + num2 = ByteTools.readShort(this.getByteAt(30).toInt(), this.getByteAt(31).toInt()) + grbit = ByteTools.readInt(num1.toInt(), num2.toInt()) + this.decodeGrbit() + if (DEBUGLEVEL > 3) Logger.logInfo("Usersviewbegin Tab Index: $tabid") + } + + /** + * decodeGrbit does masking to determine the grbit settings + */ + private fun decodeGrbit() { + fDspGutsSv = grbit and 0x00000010 == 0x00000010 + + + } + + /** + * updateGrbit looks at all the grbit variables and rebuilds the grbit + * field based off those. + */ + private fun updateGrbit() { + if (fDspGutsSv) { + grbit = 0x00000010 or grbit + } else { + grbit = -0x11 and grbit + } + // update the record bytes + val b = this.getData() + val grbytes = ByteTools.cLongToLEBytes(grbit) + System.arraycopy(grbytes, 0, b!!, 28, 4) + this.setData(b) + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -1877650235927064991L + } + /* + 20 iTabid 4 Tab index for the sheet (1-based) + */ + + +}//private float operNum1 = 0; +//private float operNum2 = 0; +//private short colRPane = 0; +//private short rwBPane = 0; +//66 Long! +//byte[] RECBYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0}; +/** + * Constructor for a Usersviewbegin to be made on the fly. + */// TODO: init Usersviewbegin \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Usersviewend.java b/src/main/java/io/starter/formats/XLS/Usersviewend.kt similarity index 64% rename from src/main/java/io/starter/formats/XLS/Usersviewend.java rename to src/main/java/io/starter/formats/XLS/Usersviewend.kt index 76b1486..fd55f9c 100644 --- a/src/main/java/io/starter/formats/XLS/Usersviewend.java +++ b/src/main/java/io/starter/formats/XLS/Usersviewend.kt @@ -20,41 +20,45 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** - * USERSVIEWEND: End Custom View Settings (1ABh)
                      - *

                      + * **USERSVIEWEND: End Custom View Settings (1ABh)**

                      + * + * * USERSVIEWEND marks the end of a custom view for the sheet * - *

                      + *
                      + * 
                        * offset  name            size    contents
                        * ---
                        * 4       fValid          2       = 1 if the settings saved are valid
                        *
                      - * 

                      +
                      * */ -public final class Usersviewend extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3120417369791123931L; - int fValid = -1; +class Usersviewend : io.starter.formats.XLS.XLSRecord() { + internal var fValid = -1 /** * return whether the settings * for this user view are valid */ - public boolean isValid() { - return fValid == 1; + val isValid: Boolean + get() = fValid == 1 + + override fun init() { + super.init() + fValid = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() } - public void init() { - super.init(); - fValid = (int) ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -3120417369791123931L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/RrdInfo.java b/src/main/java/io/starter/formats/XLS/UsrExcl.kt similarity index 81% rename from src/main/java/io/starter/formats/XLS/RrdInfo.java rename to src/main/java/io/starter/formats/XLS/UsrExcl.kt index 64d75f0..b21740c 100644 --- a/src/main/java/io/starter/formats/XLS/RrdInfo.java +++ b/src/main/java/io/starter/formats/XLS/UsrExcl.kt @@ -20,13 +20,19 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -public class RrdInfo extends UnencryptedXLSRecord { +/** + * + */ +class UsrExcl : UnencryptedXLSRecord() { + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -9123474758922477791L + } - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8771854310680798337L; } diff --git a/src/main/java/io/starter/formats/XLS/VCenter.java b/src/main/java/io/starter/formats/XLS/VCenter.kt similarity index 62% rename from src/main/java/io/starter/formats/XLS/VCenter.java rename to src/main/java/io/starter/formats/XLS/VCenter.kt index c638861..7a06438 100644 --- a/src/main/java/io/starter/formats/XLS/VCenter.java +++ b/src/main/java/io/starter/formats/XLS/VCenter.kt @@ -20,31 +20,31 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * Record specifying whether the sheet is to be centered vertically * when printed. */ -public class VCenter - extends XLSRecord { - private static final long serialVersionUID = -3649192673573344145L; +class VCenter : XLSRecord() { - public void init() { - super.init(); - } + var isVCenter: Boolean + get() = getData()!![0] and 0x01 == 0x01 + set(center) = if (center) + getData()[0] = getData()[0] or 0x01 + else + getData()[0] = getData()[0] and 0x01.inv().toByte() - public void setSheet(Sheet sheet) { - super.setSheet(sheet); - ((Boundsheet) sheet).addPrintRec(this); + override fun init() { + super.init() } - public boolean isVCenter() { - return (getData()[0] & 0x01) == 0x01; + override fun setSheet(sheet: Sheet?) { + super.setSheet(sheet) + (sheet as Boundsheet).addPrintRec(this) } - public void setVCenter(boolean center) { - if (center) getData()[0] |= 0x01; - else getData()[0] &= ~0x01; + companion object { + private val serialVersionUID = -3649192673573344145L } } diff --git a/src/main/java/io/starter/formats/XLS/ValidationException.java b/src/main/java/io/starter/formats/XLS/ValidationException.kt similarity index 63% rename from src/main/java/io/starter/formats/XLS/ValidationException.java rename to src/main/java/io/starter/formats/XLS/ValidationException.kt index cc07fbc..793b006 100644 --- a/src/main/java/io/starter/formats/XLS/ValidationException.java +++ b/src/main/java/io/starter/formats/XLS/ValidationException.kt @@ -20,46 +20,39 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * Validation Exceptions are thrown when a cell value is set to a value that does not pass * validity of an Excel validation record affecting said cell */ -public class ValidationException - extends Exception { - private static final long serialVersionUID = -6448974788123912538L; - - private String errorTitle = ""; - private String errorText = ""; - - - public ValidationException(String eTitle, String eText) { - super(); - errorTitle = eTitle; - errorText = eText; - } +class ValidationException(eTitle: String, eText: String) : Exception() { /** * Returns the title of the validation error dialog. */ - public String getTitle() { - return errorTitle; - } - + var title = "" /** * Returns the body of the validation error dialog. */ - public String getText() { - return errorText; + var text = "" + + + init { + title = eTitle + text = eText + } + + override fun getMessage(): String { + return this.toString() } - public String getMessage() { - return this.toString(); + override fun toString(): String { + return "$title: $text" } - public String toString() { - return errorTitle + ": " + errorText; + companion object { + private val serialVersionUID = -6448974788123912538L } } diff --git a/src/main/java/io/starter/formats/XLS/Window1.java b/src/main/java/io/starter/formats/XLS/Window1.java deleted file mode 100644 index 01b967f..0000000 --- a/src/main/java/io/starter/formats/XLS/Window1.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; - - -/** - * WINDOW1 0x3D: Contains window attributes for a Workbook.
                      - *

                      - * offset  name        size    contents
                      - * ---
                      - * 4       xWn         2       Horizontal Position of the window
                      - * 6       yWn         2       Vertical Position of the window
                      - * 8       dxWn        2       Width of the window
                      - * 10      dyWn        2       Height of the window
                      - * 12      grbit       2       Option Flags
                      - * 14      itabCur     2       Index of the selected workbook tab (0 based)
                      - * 16      itabFirst   2       Index of the first displayed workbook tab (0 based)
                      - * 18      ctabSel     2       Number of workbook tabs that are selected
                      - * 20      wTabRatio   2       Ratio of the width of the workbook tabs to the width of
                      - * the horizontal scroll bar; to obtain the ratio, convert to
                      - * decimal and then divide by 1000
                      - * 

                      - * - * @see WorkBook - * @see BOUNDSHEET - * @see INDEX - * @see DBCELL - * @see ROW - * @see Cell - * @see XLSRecord - */ - -public class Window1 extends io.starter.formats.XLS.XLSRecord { - - /** - * - */ - private static final long serialVersionUID = 2770922305028029883L; - short xWn = 0; - short yWn = 0; - short dxWn = 0; - short dyWn = 0; - short grbit = 0; - short itabCur = 0; - short itabFirst = 0; - short ctabSel = 0; - short wTabRatio = 0; - Boundsheet mybs = null; - - public int getCurrentTab() { - return itabCur; - } - - /** - * Sets the current tab that is displayed on opening. - * Note, this is not really the same thing as "selected". - * The selected parameter is from the Window2 record. As we - * don't really have much need to select more than one sheet - * on output, this method just delselects every other sheet than - * the one that is passed in, and selects that one in it's Window2 - * - * @param bs - */ - public void setCurrentTab(Boundsheet bs) { - mybs = bs; - int t = mybs.getSheetNum(); - Boundsheet[] bounds = this.getWorkBook().getWorkSheets(); - for (int i = 0; i < bounds.length; i++) { - bounds[i].getWindow2().setSelected(false); - } - mybs.getWindow2().setSelected(true); - byte[] mydata = this.getData(); - itabCur = (short) t; - byte[] tabbytes = ByteTools.shortToLEBytes((short) t); - mydata[10] = tabbytes[0]; - mydata[11] = tabbytes[1]; - } - - /** - * Sets which tab will display furthest to the left in the workbook. Sheets that have - * their tabid before this one will be 'pushed off' to the left. They can be retrieved in the - * GUI by clicking the left arrow next to the displayed worksheets. - * - * @param t - */ - public void setFirstTab(int t) { - byte[] mydata = this.getData(); - itabFirst = (short) t; - byte[] tabbytes = ByteTools.shortToLEBytes((short) t); - mydata[12] = tabbytes[0]; - mydata[13] = tabbytes[1]; - } - - /** - * Default init method - */ - public void init() { - super.init(); - xWn = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - yWn = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - dxWn = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - dyWn = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - grbit = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - itabCur = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - itabFirst = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - ctabSel = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); - wTabRatio = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); - - } - - /** - * Returns whether the sheet selection tabs should be shown. - */ - public boolean showSheetTabs() { - return (grbit & 0x20) == 0x20; - } - - /** - * Sets whether the sheet selection tabs should be shown. - */ - public void setShowSheetTabs(boolean show) { - if (show) grbit |= 0x20; - else grbit &= ~0x20; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Window1.kt b/src/main/java/io/starter/formats/XLS/Window1.kt new file mode 100644 index 0000000..d580075 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Window1.kt @@ -0,0 +1,161 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools + + +/** + * **WINDOW1 0x3D: Contains window attributes for a Workbook.**

                      + * + *
                      + * offset  name        size    contents
                      + * ---
                      + * 4       xWn         2       Horizontal Position of the window
                      + * 6       yWn         2       Vertical Position of the window
                      + * 8       dxWn        2       Width of the window
                      + * 10      dyWn        2       Height of the window
                      + * 12      grbit       2       Option Flags
                      + * 14      itabCur     2       Index of the selected workbook tab (0 based)
                      + * 16      itabFirst   2       Index of the first displayed workbook tab (0 based)
                      + * 18      ctabSel     2       Number of workbook tabs that are selected
                      + * 20      wTabRatio   2       Ratio of the width of the workbook tabs to the width of
                      + * the horizontal scroll bar; to obtain the ratio, convert to
                      + * decimal and then divide by 1000
                      +
                      * + * + * @see WorkBook + * + * @see BOUNDSHEET + * + * @see INDEX + * + * @see DBCELL + * + * @see ROW + * + * @see Cell + * + * @see XLSRecord + */ + +class Window1 : io.starter.formats.XLS.XLSRecord() { + internal var xWn: Short = 0 + internal var yWn: Short = 0 + internal var dxWn: Short = 0 + internal var dyWn: Short = 0 + internal var grbit: Short = 0 + internal var itabCur: Short = 0 + internal var itabFirst: Short = 0 + internal var ctabSel: Short = 0 + internal var wTabRatio: Short = 0 + internal var mybs: Boundsheet? = null + + val currentTab: Int + get() = itabCur.toInt() + + /** + * Sets the current tab that is displayed on opening. + * Note, this is not really the same thing as "selected". + * The selected parameter is from the Window2 record. As we + * don't really have much need to select more than one sheet + * on output, this method just delselects every other sheet than + * the one that is passed in, and selects that one in it's Window2 + * + * @param bs + */ + fun setCurrentTab(bs: Boundsheet) { + mybs = bs + val t = mybs!!.sheetNum + val bounds = this.workBook!!.workSheets + for (i in bounds.indices) { + bounds[i].window2!!.setSelected(false) + } + mybs!!.window2!!.setSelected(true) + val mydata = this.getData() + itabCur = t.toShort() + val tabbytes = ByteTools.shortToLEBytes(t.toShort()) + mydata[10] = tabbytes[0] + mydata[11] = tabbytes[1] + } + + /** + * Sets which tab will display furthest to the left in the workbook. Sheets that have + * their tabid before this one will be 'pushed off' to the left. They can be retrieved in the + * GUI by clicking the left arrow next to the displayed worksheets. + * + * @param t + */ + fun setFirstTab(t: Int) { + val mydata = this.getData() + itabFirst = t.toShort() + val tabbytes = ByteTools.shortToLEBytes(t.toShort()) + mydata[12] = tabbytes[0] + mydata[13] = tabbytes[1] + } + + /** + * Default init method + */ + override fun init() { + super.init() + xWn = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + yWn = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + dxWn = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + dyWn = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + grbit = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + itabCur = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + itabFirst = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + ctabSel = ByteTools.readShort(this.getByteAt(14).toInt(), this.getByteAt(15).toInt()) + wTabRatio = ByteTools.readShort(this.getByteAt(16).toInt(), this.getByteAt(17).toInt()) + + } + + /** + * Returns whether the sheet selection tabs should be shown. + */ + fun showSheetTabs(): Boolean { + return grbit and 0x20 == 0x20 + } + + /** + * Sets whether the sheet selection tabs should be shown. + */ + fun setShowSheetTabs(show: Boolean) { + if (show) + grbit = grbit or 0x20 + else + grbit = grbit and 0x20.inv().toShort() + val b = ByteTools.shortToLEBytes(grbit) + this.getData()[8] = b[0] + this.getData()[9] = b[1] + } + + companion object { + + /** + * + */ + private val serialVersionUID = 2770922305028029883L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Window2.java b/src/main/java/io/starter/formats/XLS/Window2.java deleted file mode 100644 index 5d2e2a2..0000000 --- a/src/main/java/io/starter/formats/XLS/Window2.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.toolkit.ByteTools; - - -/** - * WINDOW2 0x23E: Contains window attributes for a Sheet.
                      - *

                      - * offset  name        size    contents
                      - * ---
                      - * 4       grbit       2       Option Flags
                      - * 6       rwTop       2       Top row visible in the window
                      - * 8       colLeft     2       Leftmost visible col in window
                      - * 10      icvHdr      4       Index to color val for row/col headings & grids
                      - * 14      wScaleSLV   2       Zoom mag in page break preview
                      - * 16      wScaleNorm  2       Zoom mag in Normal preview
                      - * 18      reserved    4
                      - *
                      - * grbit Option flags
                      - * 0 0001H 0 = Show formula results 1 = Show formulas
                      - * 1 0002H 0 = Do not show grid lines 1 = Show grid lines
                      - * 2 0004H 0 = Do not show sheet headers 1 = Show sheet headers
                      - * 3 0008H 0 = Panes are not frozen 1 = Panes are frozen (freeze)
                      - * 4 0010H 0 = Show zero values as empty cells 1 = Show zero values
                      - * 5 0020H 0 = Manual grid line colour 1 = Automatic grid line colour
                      - * 6 0040H 0 = Columns from left to right 1 = Columns from right to left
                      - * 7 0080H 0 = Do not show outline symbols 1 = Show outline symbols
                      - * 8 0100H 0 = Keep splits if pane freeze is removed 1 = Remove splits if pane freeze is removed
                      - * 9 0200H 0 = Sheet not selected 1 = Sheet selected (BIFF5-BIFF8)
                      - * 10 0400H 0 = Sheet not visible 1 = Sheet visible (BIFF5-BIFF8)
                      - * 11 0800H 0 = Show in normal view 1 = Show in page break preview (BIFF8)    

                      - * - * @see WorkBook - * @see BOUNDSHEET - * @see INDEX - * @see DBCELL - * @see ROW - * @see Cell - * @see XLSRecord - */ - -public class Window2 extends io.starter.formats.XLS.XLSRecord { - /** - * - */ - private static final long serialVersionUID = -8316509425117672619L; - int grbit = -1; - int rwTop = -1; - int colLeft = -1; - int icvHdr = -1; - int wScaleSLV = -1; - int wScaleNorm = -1; - - //20060308 KSC: Added for get/set access to Window2 options - static final int BITMASK_SHOWFORMULARESULTS = 0x0001; - static final int BITMASK_SHOWGRIDLINES = 0x0002; - static final int BITMASK_SHOWSHEETHEADERS = 0x0004; - static final int BITMASK_FREEZEPANES = 0x0008; - static final int BITMASK_SHOWZEROVALUES = 0x0010; - static final int BITMASK_GRIDLINECOLOR = 0x0020; - static final int BITMASK_COLUMNDIRECTION = 0x0040; - static final int BITMASK_SHOWOUTLINESYMBOLS = 0x0080; - static final int BITMASK_KEEPSPLITS = 0x0100; - static final int BITMASK_SHEETSELECTED = 0x0200; - static final int BITMASK_SHEETVISIBLE = 0x0400; - static final int BITMASK_SHOWINPRINTPREVIEW = 0x0800; - - public void init() { - super.init(); - short s1, s2; - - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - rwTop = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - colLeft = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - - s1 = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - s2 = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - icvHdr = ByteTools.readInt(s1, s2); - - // the following do not necessarily exist in VB-manipulated windows - if (this.getLength() > 10) { - wScaleSLV = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - wScaleNorm = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - } - } - - void setSelected(boolean b) { - if (b) - this.getData()[1] |= 0x2; - else - this.getData()[1] &= 0xFD; - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - - /** - * returns the String Address of first visible cell on the sheet - * - * @return String - */ - public String getTopLeftCell() { - return ExcelTools.formatLocation(new int[]{rwTop, colLeft}); - } - - // add get/set for Window2 options - public void setGrbit() { - byte[] data = this.getData(); - byte[] b = ByteTools.shortToLEBytes((short) grbit); - System.arraycopy(b, 0, data, 0, 2); - this.setData(data); - - } - - public boolean getShowFormulaResults() { - return ((grbit & BITMASK_SHOWFORMULARESULTS) == BITMASK_SHOWFORMULARESULTS); - } - - public void setShowFormulaResults(boolean b) { - if (b) - grbit = grbit | BITMASK_SHOWFORMULARESULTS; - else - grbit = grbit & ~BITMASK_SHOWFORMULARESULTS; - this.setGrbit(); - } - - public boolean getShowGridlines() { - return ((grbit & BITMASK_SHOWGRIDLINES) == BITMASK_SHOWGRIDLINES); - } - - public void setShowGridlines(boolean b) { - if (b) - grbit = grbit | BITMASK_SHOWGRIDLINES; - else - grbit = grbit & ~BITMASK_SHOWGRIDLINES; - this.setGrbit(); - } - - public boolean getShowSheetHeaders() { - return ((grbit & BITMASK_SHOWSHEETHEADERS) == BITMASK_SHOWSHEETHEADERS); - } - - public void setShowSheetHeaders(boolean b) { - if (b) - grbit = grbit | BITMASK_SHOWSHEETHEADERS; - else - grbit = grbit & ~BITMASK_SHOWSHEETHEADERS; - this.setGrbit(); - } - - - public int getScaleNorm() { - //return wScaleSLV; - return this.wScaleNorm; - } - - public void setScaleNorm(int zm) { - wScaleNorm = zm; - byte[] data = this.getData(); - byte[] b = ByteTools.shortToLEBytes((short) zm); - // wScaleSLV 10,11 - // wScaleNorm 12,13; - System.arraycopy(b, 0, data, 12, 2); - this.setData(data); - } - - - public boolean getShowZeroValues() { - return ((grbit & BITMASK_SHOWZEROVALUES) == BITMASK_SHOWZEROVALUES); - } - - public void setShowZeroValues(boolean b) { - if (b) - grbit = grbit | BITMASK_SHOWZEROVALUES; - else - grbit = grbit & ~BITMASK_SHOWZEROVALUES; - this.setGrbit(); - } - - public boolean getShowOutlineSymbols() { - return ((grbit & BITMASK_SHOWOUTLINESYMBOLS) == BITMASK_SHOWOUTLINESYMBOLS); - } - - public void setShowOutlineSymbols(boolean b) { - if (b) - grbit = grbit | BITMASK_SHOWOUTLINESYMBOLS; - else - grbit = grbit & ~BITMASK_SHOWOUTLINESYMBOLS; - this.setGrbit(); - } - - /** - * true if sheet is in normal view mode (false if in page break preview mode) - * - * @return - */ - public boolean getShowInNormalView() { - return ((grbit & BITMASK_SHOWINPRINTPREVIEW) == 0); - } - - public void setShowInNormalView(boolean b) { - if (b) - grbit = grbit & ~BITMASK_SHOWINPRINTPREVIEW; - else - grbit = grbit | BITMASK_SHOWINPRINTPREVIEW; - this.setGrbit(); - } - - public boolean getFreezePanes() { - return ((grbit & BITMASK_FREEZEPANES) == BITMASK_FREEZEPANES); - } - - public void setFreezePanes(boolean b) { - if (b) - grbit = grbit | BITMASK_FREEZEPANES; - else - grbit = grbit & ~BITMASK_FREEZEPANES; - this.setGrbit(); - } - - public boolean getManualGridLineColor() { - return ((grbit & BITMASK_GRIDLINECOLOR) == BITMASK_GRIDLINECOLOR); - } - - public void setManualGridLineColor(boolean b) { - if (b) - grbit = grbit | BITMASK_GRIDLINECOLOR; - else - grbit = grbit & ~BITMASK_GRIDLINECOLOR; - this.setGrbit(); - } - /* // TODO: finish these - static final int BITMASK_COLUMNDIRECTION= 0x0040; - static final int BITMASK_KEEPSPLITS= 0x0100; - static final int BITMASK_SHEETSELECTED= 0x0200; - static final int BITMASK_SHEETVISIBLE= 0x0400; -*/ - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Window2.kt b/src/main/java/io/starter/formats/XLS/Window2.kt new file mode 100644 index 0000000..27631e8 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Window2.kt @@ -0,0 +1,252 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.ExcelTools +import io.starter.toolkit.ByteTools + + +/** + * **WINDOW2 0x23E: Contains window attributes for a Sheet.**

                      + * + *
                      + * offset  name        size    contents
                      + * ---
                      + * 4       grbit       2       Option Flags
                      + * 6       rwTop       2       Top row visible in the window
                      + * 8       colLeft     2       Leftmost visible col in window
                      + * 10      icvHdr      4       Index to color val for row/col headings & grids
                      + * 14      wScaleSLV   2       Zoom mag in page break preview
                      + * 16      wScaleNorm  2       Zoom mag in Normal preview
                      + * 18      reserved    4
                      + *
                      + * grbit Option flags
                      + * 0 0001H 0 = Show formula results 1 = Show formulas
                      + * 1 0002H 0 = Do not show grid lines 1 = Show grid lines
                      + * 2 0004H 0 = Do not show sheet headers 1 = Show sheet headers
                      + * 3 0008H 0 = Panes are not frozen 1 = Panes are frozen (freeze)
                      + * 4 0010H 0 = Show zero values as empty cells 1 = Show zero values
                      + * 5 0020H 0 = Manual grid line colour 1 = Automatic grid line colour
                      + * 6 0040H 0 = Columns from left to right 1 = Columns from right to left
                      + * 7 0080H 0 = Do not show outline symbols 1 = Show outline symbols
                      + * 8 0100H 0 = Keep splits if pane freeze is removed 1 = Remove splits if pane freeze is removed
                      + * 9 0200H 0 = Sheet not selected 1 = Sheet selected (BIFF5-BIFF8)
                      + * 10 0400H 0 = Sheet not visible 1 = Sheet visible (BIFF5-BIFF8)
                      + * 11 0800H 0 = Show in normal view 1 = Show in page break preview (BIFF8)    
                      + * + * @see WorkBook + * + * @see BOUNDSHEET + * + * @see INDEX + * + * @see DBCELL + * + * @see ROW + * + * @see Cell + * + * @see XLSRecord + */ + +class Window2 : io.starter.formats.XLS.XLSRecord() { + internal var grbit = -1 + internal var rwTop = -1 + internal var colLeft = -1 + internal var icvHdr = -1 + internal var wScaleSLV = -1 + internal var wScaleNorm = -1 + + /** + * returns the String Address of first visible cell on the sheet + * + * @return String + */ + val topLeftCell: String + get() = ExcelTools.formatLocation(intArrayOf(rwTop, colLeft)) + + var showFormulaResults: Boolean + get() = grbit and BITMASK_SHOWFORMULARESULTS == BITMASK_SHOWFORMULARESULTS + set(b) { + if (b) + grbit = grbit or BITMASK_SHOWFORMULARESULTS + else + grbit = grbit and BITMASK_SHOWFORMULARESULTS.inv() + this.setGrbit() + } + + var showGridlines: Boolean + get() = grbit and BITMASK_SHOWGRIDLINES == BITMASK_SHOWGRIDLINES + set(b) { + if (b) + grbit = grbit or BITMASK_SHOWGRIDLINES + else + grbit = grbit and BITMASK_SHOWGRIDLINES.inv() + this.setGrbit() + } + + var showSheetHeaders: Boolean + get() = grbit and BITMASK_SHOWSHEETHEADERS == BITMASK_SHOWSHEETHEADERS + set(b) { + if (b) + grbit = grbit or BITMASK_SHOWSHEETHEADERS + else + grbit = grbit and BITMASK_SHOWSHEETHEADERS.inv() + this.setGrbit() + } + + + //return wScaleSLV; + // wScaleSLV 10,11 + // wScaleNorm 12,13; + var scaleNorm: Int + get() = this.wScaleNorm + set(zm) { + wScaleNorm = zm + val data = this.getData() + val b = ByteTools.shortToLEBytes(zm.toShort()) + System.arraycopy(b, 0, data!!, 12, 2) + this.setData(data) + } + + + var showZeroValues: Boolean + get() = grbit and BITMASK_SHOWZEROVALUES == BITMASK_SHOWZEROVALUES + set(b) { + if (b) + grbit = grbit or BITMASK_SHOWZEROVALUES + else + grbit = grbit and BITMASK_SHOWZEROVALUES.inv() + this.setGrbit() + } + + var showOutlineSymbols: Boolean + get() = grbit and BITMASK_SHOWOUTLINESYMBOLS == BITMASK_SHOWOUTLINESYMBOLS + set(b) { + if (b) + grbit = grbit or BITMASK_SHOWOUTLINESYMBOLS + else + grbit = grbit and BITMASK_SHOWOUTLINESYMBOLS.inv() + this.setGrbit() + } + + /** + * true if sheet is in normal view mode (false if in page break preview mode) + * + * @return + */ + var showInNormalView: Boolean + get() = grbit and BITMASK_SHOWINPRINTPREVIEW == 0 + set(b) { + if (b) + grbit = grbit and BITMASK_SHOWINPRINTPREVIEW.inv() + else + grbit = grbit or BITMASK_SHOWINPRINTPREVIEW + this.setGrbit() + } + + var freezePanes: Boolean + get() = grbit and BITMASK_FREEZEPANES == BITMASK_FREEZEPANES + set(b) { + if (b) + grbit = grbit or BITMASK_FREEZEPANES + else + grbit = grbit and BITMASK_FREEZEPANES.inv() + this.setGrbit() + } + + var manualGridLineColor: Boolean + get() = grbit and BITMASK_GRIDLINECOLOR == BITMASK_GRIDLINECOLOR + set(b) { + if (b) + grbit = grbit or BITMASK_GRIDLINECOLOR + else + grbit = grbit and BITMASK_GRIDLINECOLOR.inv() + this.setGrbit() + } + + override fun init() { + super.init() + val s1: Short + val s2: Short + + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() + rwTop = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()).toInt() + colLeft = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()).toInt() + + s1 = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + s2 = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + icvHdr = ByteTools.readInt(s1.toInt(), s2.toInt()) + + // the following do not necessarily exist in VB-manipulated windows + if (this.length > 10) { + wScaleSLV = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()).toInt() + wScaleNorm = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()).toInt() + } + } + + internal fun setSelected(b: Boolean) { + if (b) + this.getData()[1] = this.getData()[1] or 0x2 + else + this.getData()[1] = this.getData()[1] and 0xFD + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() + } + + // add get/set for Window2 options + fun setGrbit() { + val data = this.getData() + val b = ByteTools.shortToLEBytes(grbit.toShort()) + System.arraycopy(b, 0, data!!, 0, 2) + this.setData(data) + + } + + companion object { + /** + * + */ + private val serialVersionUID = -8316509425117672619L + + //20060308 KSC: Added for get/set access to Window2 options + internal val BITMASK_SHOWFORMULARESULTS = 0x0001 + internal val BITMASK_SHOWGRIDLINES = 0x0002 + internal val BITMASK_SHOWSHEETHEADERS = 0x0004 + internal val BITMASK_FREEZEPANES = 0x0008 + internal val BITMASK_SHOWZEROVALUES = 0x0010 + internal val BITMASK_GRIDLINECOLOR = 0x0020 + internal val BITMASK_COLUMNDIRECTION = 0x0040 + internal val BITMASK_SHOWOUTLINESYMBOLS = 0x0080 + internal val BITMASK_KEEPSPLITS = 0x0100 + internal val BITMASK_SHEETSELECTED = 0x0200 + internal val BITMASK_SHEETVISIBLE = 0x0400 + internal val BITMASK_SHOWINPRINTPREVIEW = 0x0800 + } + /* // TODO: finish these + static final int BITMASK_COLUMNDIRECTION= 0x0040; + static final int BITMASK_KEEPSPLITS= 0x0100; + static final int BITMASK_SHEETSELECTED= 0x0200; + static final int BITMASK_SHEETVISIBLE= 0x0400; +*/ + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/WorkBook.java b/src/main/java/io/starter/formats/XLS/WorkBook.java deleted file mode 100644 index 2b39ed3..0000000 --- a/src/main/java/io/starter/formats/XLS/WorkBook.java +++ /dev/null @@ -1,3946 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.WorkBookException; -import io.starter.OpenXLS.*; -import io.starter.formats.OOXML.Theme; -import io.starter.formats.XLS.charts.Ai; -import io.starter.formats.XLS.charts.Chart; -import io.starter.formats.XLS.charts.Fontx; -import io.starter.formats.XLS.charts.GenericChartObject; -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.FastAddVector; -import io.starter.toolkit.Logger; - -import java.awt.Color; -import java.io.*; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.List; -import java.util.*; - -/** - *
                      - * The WorkBook record represents an XLS workbook substream containing worksheets and associated records.
                      - *
                      - * 
                      - * - * @see WorkBook - * @see Boundsheet - * @see Index - * @see Dbcell - * @see Row - * @see Cell - * @see XLSRecord - */ - -public class WorkBook implements Serializable, XLSConstants, Book { - - private static final long serialVersionUID = 2282017774412632087L; - - DateConverter.DateFormat dateFormat = DateConverter.DateFormat.LEGACY_1900; - - // public constants - public static int STRING_ENCODING_AUTO = 0; - public static int STRING_ENCODING_UNICODE = 1; - public static int STRING_ENCODING_COMPRESSED = 2; - public static int ALLOWDUPES = 0; - public static int SHAREDUPES = 1; - - private int DEBUGLEVEL = 0; - private int bofct = 0, eofct = 0, - indexnum = 0; - private int defaultIxfe = 15; - private int CalcMode = CALCULATE_AUTO; - private int defaultLanguage = 0; // default - // language - // code - // for - // the - // current - // workbook - private boolean copying = false; - private boolean sharedupes = false; - private AbstractList xfrecs = new Vector(); - private AbstractList indexes = new Vector(); - private AbstractList names = new Vector(); // ALL - // of - // the - // names - // (including - // worksheet - // scoped, - // etc) - private AbstractList orphanedPtgNames = new Vector(); - private AbstractList externalnames = new Vector(); - AbstractList formulas = new FastAddVector(); - private AbstractList fonts = new Vector(); - public Color[] colorTable; - - /** - * The list of Format records indexed by format ID. - */ - private TreeMap formats = new TreeMap(); - private AbstractList charts = new Vector(); - /** - * OOXML-specific - */ - private AbstractList ooxmlObjects = new Vector(); // stores - // OOXML - // objects - // external - // to - // workbook - // e.g. - // oleObjects, - private String ooxmlcodename = null; // stores - // OOXML - // codename - private ArrayList dxfs = null; // 20090622 - // KSC: - // stores - // dxf's - // (incremental - // style - // info) - // per - // workbook - private int firstSheet = 0; // specifies - // first - // sheet - // (ooxml) - private Theme theme = null; - - // Reference Tracking - private ReferenceTracker refTracker = new ReferenceTracker(); - - // various - private AbstractList boundsheets = new Vector(); // TODO: - // remove - // this - // variable? - // its - // duplicated - // in - // workSheets - private AbstractList hlinklookup = new Vector( - 20); - private AbstractList mergecelllookup = new Vector( - 20); - - /* - * Indirect formulas that need to be calculated after load - * for reftracker - */ - private AbstractList indirectFormulas = new ArrayList(); - private AbstractList supBooks = new Vector(); - - /* - * This is here only to allow client code to compile, should - * be removed - */ - public static String CONVERTMULBLANKS = "deprecated"; - - // We should consider using an ordered collections class for - // some of these? - // an enumeration of worksheets for instance will not always - // be in the same - // order, causing tests to fail... - private Hashtable workSheets = new Hashtable( - 100, .950f); - private HashMap bookNameRecs = new HashMap(); - private Hashtable formulashash = new Hashtable(); - - /** - * Maps number format patterns to their IDs. - */ - private Hashtable formatlookup = new Hashtable( - 30); - - private Hashtable ptViews = new Hashtable( - 20); // TODO: - // move - // to - // sheet - // - - // sheet-level - // pivot - // table - // view - // recordss - private ArrayList ptstream = new ArrayList(); // wb-level - // pivot - // cache - // definitions - // usually - // only - // 1 - private PivotCache ptcache = null; // if - // has - // pivot - // tables - // this - // is - // the - // one - // and - // only - // pivot - // cache - - public HashMap formatCache = new HashMap(); - - private Index lastidx; - private Sst stringTable; - private Bof lastBOF; - private Externsheet myexternsheet; - private Bof firstBOF; - private ByteStreamer streamer = createByteStreamer(); - private TabID tabs; - private Window1 win1; - private CalcMode calcmoderec; // determines - // recalculation - // mode - // for - // workbook - // - - // Manual, - // Auto - // ... - private DefaultRowHeight drh; - private Chart currchart; - private Ai currai; - private Supbook myADDINSUPBOOK = null; // for - // external - // names - // SUPBOOK - WorkBookFactory factory; - private ContinueHandler contHandler = new ContinueHandler( - this); - private Usersviewbegin usersview; - private Eof lasteof; - private BiffRec xl2k = null; - public AbstractList msodgMerge = new Vector(); - public MSODrawingGroup msodg = null; - public int lastSPID = 1024; // 20071030 - // last - // or - // next - // SPID - // (= - // shape - // ID - // or - // image - // ID); - // incremented - // upon - // new - // images - // ... - // appropriate - // to - // store - // at - // book - // level - // (?) - private MSODrawing currdrw = null; - - private BookProtectionManager protector; - - protected void reflectiveClone(WorkBook source) { - for (Field field : WorkBook.class.getDeclaredFields()) { - if (Modifier.isStatic(field.getModifiers())) - continue; - - try { - field.set(this, field.get(source)); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - } - - protected ByteStreamer createByteStreamer() { - return new ByteStreamer(this); - } - - /** - * Gets this sheet's SheetProtectionManager. - */ - public BookProtectionManager getProtectionManager() { - if (protector == null) - protector = new BookProtectionManager(this); - return protector; - } - - /** - * init the ImageHandles - */ - void initImages() { - lastSPID = Math.max(lastSPID, msodg.getSpidMax()); // 20090508 KSC: - // lastSPID should - // also account for - // charts - // [BUGTRACKER 2372 - // copyChartToSheet - // error] - // 20071217 KSC: clear out imageMap before inputting! - for (int x = 0; x < this.getWorkSheets().length; x++) { - (this.getWorkSheets()[x]).imageMap.clear(); - } - for (int i = 0; i < msodg.getMsodrawingrecs().size(); i++) { // 20070914 - // KSC: - // store - // msodrawingrecs - // with - // MSODrawingGroup - // instead - // of - // here - MSODrawing rec = (MSODrawing) msodg.getMsodrawingrecs().get(i); - lastSPID = Math.max(lastSPID, rec.getlastSPID()); // valid for - // header - // msodrawing - // record(s) - int imgdx = rec.getImageIndex() - 1; // it's 1-based - byte[] imageData = msodg.getImageBytes(imgdx); - if (imageData != null) { - ImageHandle im = new ImageHandle(imageData, rec.getSheet()); - im.setMsgdrawing(rec); // Link 2 actual Msodrawing rec - im.setName(rec.getName()); // set image name from rec ... - im.setShapeName(rec.getShapeName()); // set shape name as well - // ... - im.setImageType(msodg.getImageType(imgdx));// 20100519 KSC: - // added! - rec.getSheet().imageMap.put(im, Integer.valueOf(imgdx)); - } - } - } - - /* - * Once all the records are parsed, the msodrawinggroup - * needs to be parsed. - * We first merge all the msodrawinggroup records. From the - * till now experiment, - * there are maximum of two such records, adjacent to each - * other. If there are two, - * the size of the first is only 8228 and the remaining data - * is in second record. - * However, when writing, it doesn't seem important to break - * it into two and can be written - * as one. - * - * Once msodrawingrecords are parsed, it is not used. - * However, we have a msodrawingglobal class that - * records the current count of msodrawing related records, - * like shape count, max shape id till now etc. - * We need to maintain that for writing (creating the - * msodrawinggroup records) later on. - */ - public void mergeMSODrawingRecords() { - // 20070915 KSC: Now don't re-initialize Msodrawing recs; - // just merge and parse - if (msodg != null) { - msodg.mergeAndRemoveContinues(); - for (int i = 1; i < msodgMerge.size(); i++) { - msodg.mergeRecords((MSODrawingGroup) msodgMerge.get(i)); // merges - // and - // removes - // secondary - // msodrawinggroups - // 20071003 KSC: get rid of secondary msodg's, will be - // created upon createMSODGContinues - this.getStreamer() - .removeRecord((MSODrawingGroup) msodgMerge.get(i)); // remove - // existing - // continues - // from - // stream - } - while (msodgMerge.size() > 1) - msodgMerge.remove(msodgMerge.size() - 1); - msodg.parse(); - this.initImages(); - } - } - - /** - * after changing the MSODrawings on a sheet, this is called to - * update the header Msodrawing rec. - *
                      must sum up all other mso's on the particular sheet SPCONTAINERLEN and update the sheet mso header ... - *
                      this calculation is quite experimental, so far it's working in all known cases ... - * - * @param bs - */ - public void updateMsodrawingHeaderRec(Boundsheet bs) { - MSODrawing msdHeader = msodg.getMsoHeaderRec(bs); - if (msdHeader != null) { - int spContainerLength = 0; // count of all other spcontainer lengths - // (sum=header spgroupcontainer) - int otherContainerLength = 0; // count of other containers - // (solvercontainer,etc) added to - // dgcontainerlength - int numshapes = 2; // 20100324 KSC: total guess, really - int totalDrawingRecs = msodg.getMsodrawingrecs().size(); - for (int z = 0; z < totalDrawingRecs; z++) { - MSODrawing rec = (MSODrawing) msodg.getMsodrawingrecs().get(z); - if (rec.getSheet().equals(bs)) { - if (rec != msdHeader && !rec.isHeader()) { // added header - // check- seems - // like *can* - // have multiple - // header recs - // in charts! - spContainerLength += rec.getSPContainerLength(); - otherContainerLength += rec.getSOLVERContainerLength(); - if (rec.isShape) // if it's a shape-type mso, count; - // there are other mso-types that - // are not SPCONTAINERS; apparently - // don't count these ... - numshapes++; - } - } - } - msdHeader - .updateHeader(spContainerLength, otherContainerLength, numshapes, msdHeader - .getlastSPID()); - } - } - - /** - * Get the MsoDrawingGroup for this workbook - * - * @return msodrawinggroup - */ - public MSODrawingGroup getMSODrawingGroup() { - return msodg; - } - - /** - * Set the msodrawinggroup for this workbook - * - * @param msodg - */ - public void setMSODrawingGroup(MSODrawingGroup msodg) { - this.msodg = msodg; - this.msodg.setWorkBook(this); - this.msodg.setStreamer(this.getStreamer()); - } - - /** - * For workbooks that do not contain an MSODrawing group create a new one, - * if the drawing group already exists return the existing - * - * @return - */ - public MSODrawingGroup createMSODrawingGroup() { - if (msodg != null) - return msodg; - this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup - .getPrototype()); - return msodg; - } - - /** - * Return some useful statistics about the WorkBook - * - * @return - */ - public String getStats() { - return getStats(false); - } - - /** - * Return some useful statistics about the WorkBook - * - * @return - */ - public String getStats(boolean usehtml) { - String rex = "\r\n"; - if (usehtml) - rex = "
                      "; - - String ret = "-------------------------------------------" + rex; - ret += "OpenXLS Version: " + WorkBookHandle.getVersion() + rex; - ret += "Excel Version: " + getXLSVersionString() + rex; - ret += "-------------------------------------------------\r\n"; - ret += "Statistics for: " + this.toString() + rex; - ret += "Number of Worksheets: " + this.getNumWorkSheets() + rex; - ret += "Number of Cells: " + this.getNumCells() + rex; - ret += "Number of Formulas: " + this.getNumFormulas() + rex; - ret += "Number of Charts: " + this.getChartVect().size() + rex; - ret += "Number of Fonts: " + this.getNumFonts() + rex; - ret += "Number of Formats: " + this.getNumFormats() + rex; - ret += "Number of Xfs: " + this.getNumXfs() + rex; - // ret += "StringTable: " + this.stringTable.toString() + - // rex; - ret += "-------------------------------------------------\r\n"; - return ret; - } - - public String getXLSVersionString() { - return lastBOF.getXLSVersionString(); - } - - private void addMergedcells(Mergedcells c) { - this.mergecelllookup.add(c); - } - - private void addHlink(Hlink r) { - this.hlinklookup.add(r); - } - - /** - * take care of any lazy updating before output - */ - public void prestream() { - if (this.getMSODrawingGroup() != null) - this.getMSODrawingGroup().prestream(); - } - - public Chart[] getCharts() { - Chart[] chts = new Chart[this.charts.size()]; - return (Chart[]) charts.toArray(chts); - } - - public Supbook[] getSupBooks() { - Supbook[] sbs = new Supbook[this.supBooks.size()]; - return (Supbook[]) this.supBooks.toArray(sbs); - } - - /** - * returns the list of OOXML objects which are external or auxillary to the main workbook - * e.g. theme, doc properties - * - * @return - */ - public List getOOXMLObjects() { - return ooxmlObjects; - } - - /** - * adds the object-specific signature of the external or auxillary OOXML object - * Object should be of String[] form, - * key, path, local path + filename [, rid, [extra info], [embedded information]] - * e.g. theme, doc properties - * - * @param o - */ - public void addOOXMLObject(Object o) { - if (!((String[]) o)[0].equals("externalLink")) - ooxmlObjects.add(o); - else - ooxmlObjects.add(0, o); // ensure ExternalLinks are 1st because they - // are linked via rId in workbook.xml - } - - /** - * return the OOXML theme for this workbook, if any - * - * @return - */ - public Theme getTheme() { - return theme; - } - - /** - * sets the OOXML theme for this 2007 verison workbook - * - * @param t - */ - public void setTheme(Theme t) { - theme = t; - } - - /** - * return the External Supbook record associated with the desired externalWorkbook - * will create if bCreate - * - * @param externalWorkbook String URL (name) of External Workbook - * @param bCreate if true, will create an external SUPBOOK record for the externalWorkbook - * @return Supbook - */ - public Supbook getExternalSupbook(String externalWorkbook, boolean bCreate) { - Supbook sb = null; - if (externalWorkbook == null) - return null; - for (int i = 0; i < this.supBooks.size(); i++) { - if (((Supbook) this.supBooks.get(i)).isExternalRecord()) - if (externalWorkbook - .equalsIgnoreCase(((Supbook) this.supBooks.get(i)) - .getExternalWorkBook())) - sb = (Supbook) this.supBooks.get(i); - } - if (sb == null && bCreate) { // create - sb = (Supbook) Supbook.getExternalPrototype(externalWorkbook); - int loc = ((Supbook) supBooks.get(supBooks.size() - 1)) - .getRecordIndex(); // must have at least one global supbook - // present - streamer.addRecordAt(sb, loc + 1); // external supbooks appear to be - // before "normal" supbooks - // [BugTracker 1434] - this.supBooks.add(sb); // 20080714 KSC: add at beginning -- correct - // in all cases? - // this.addRecord(sb,false); // "" no need - } - return sb; - } - - /** - * return the index into the Supbook records for this supbook - * - * @param sb - * @return int - */ - public int getSupbookIndex(Supbook sb) { - for (int i = 0; i < this.supBooks.size(); i++) { - if (this.supBooks.get(i) == sb) - return i; - } - return -1; - } - - public AbstractList getChartVect() { - return charts; - } - - public Sxview getPivotTableView(String nm) { - return (Sxview) ptViews.get(nm); - } - - protected void addPivotTable(Sxview sx) { - this.ptViews.put(sx.getTableName(), sx); // Pivot Table View ==Top-level - // record for a Pivot Table - } - - /** - * return all pivot table views (==Sxview records) - *
                      SxView is the top-level record of a Pivot Table - * as distinct from the PivotCache (stored data source in a LEOFile Storage) - * and PivotTable Stream (SxStream top-level record) - * - * @return - */ - public Sxview[] getAllPivotTableViews() { - Sxview[] sv = new Sxview[ptViews.size()]; - Enumeration x = ptViews.elements(); - int t = 0; - while (x.hasMoreElements()) { - sv[t++] = (Sxview) x.nextElement(); - } - return sv; - } - - public int getNPivotTableViews() { - return ptViews.size(); - } - - /** - * return the Externsheet for this book - * - * @param create a new Externsheet if it does not exist - * @return the Externsheet - */ - public Externsheet getExternSheet(boolean create) { - if ((myexternsheet == null) && create) { - addExternsheet(); - } - return myexternsheet; - } - - /** - * get the Externsheet - */ - public Externsheet getExternSheet() { - if (myexternsheet == null) - addExternsheet(); - return myexternsheet; - } - - /** - * default constructor -- do init - */ - public WorkBook() { - - Object cm = System.getProperties().get(WorkBook.CALC_MODE_PROP); - if (cm != null) { - try { - this.CalcMode = Integer.parseInt(cm.toString()); - } catch (Exception e) { - Logger.logWarn("Invalid Calc Mode Setting in System properties:" - + cm); - } - } - if (System.getProperties() - .get("io.starter.OpenXLS.sharedupes") != null) { - this.sharedupes = System.getProperties() - .get("io.starter.OpenXLS.sharedupes").equals("true"); - if (this.sharedupes) { - this.setDupeStringMode(WorkBook.SHAREDUPES); - } - } - this.initBuiltinFormats(); - // re-init color table: initial state of color table if - // Pallete record exists, changes may occur - colorTable = new java.awt.Color[FormatHandle.COLORTABLE.length]; - for (int i = 0; i < FormatHandle.COLORTABLE.length; i++) - colorTable[i] = FormatHandle.COLORTABLE[i]; - } - - /** - * Gets the format ID for a given number format pattern. - * This lookup is completely case-insensitive. For most patterns this - * correctly reflects the case-insensitivity of the tokens. Custom patterns - * containing string literals could be matched incorrectly. - * - * @param pattern the number format pattern to look up - * @return the format ID of the given pattern or -1 if it's not recognized - */ - public short getFormatId(String pattern) { - Short res = (Short) formatlookup.get(pattern.toUpperCase()); - if (res != null) - return res.shortValue(); - return -1; - } - - /** - * Initializes the format lookup to contain the built-in formats. - */ - private void initBuiltinFormats() { - String[][] formats = FormatConstantsImpl.getBuiltinFormats(); - - for (int i = 0; i < formats.length; i++) - formatlookup.put(formats[i][0].toUpperCase(), Short - .valueOf(formats[i][1], 16)); - } - - /** - * Init names at Post-load - */ - public void initializeNames() { - for (int i = 0; i < names.size(); i++) { - Name n = (Name) names.get(i); - n.parseExpression(); // evaluate expression at postload, after sheet - // recs are loaded - } - } - - /** - * add a Name object to the collection of names - */ - public int addName(Name n) { - if (n.getItab() != 0) { - // its a sheet level name - try { - Boundsheet b = this.getWorkSheetByNumber(n.getItab() - 1);// one - // based - // pointer - b.addLocalName(n); - n.setSheet(b); - } catch (WorkSheetNotFoundException e) { - } - } else { - String sName = n.getNameA(); // returns upper case name - Object existo = bookNameRecs.get(sName); - if (existo != null) { // handle duplicate named ranges - String bnam = n.toString(); - if (bnam.indexOf("Built-in:") != 0) { - try { - if (((Name) existo).getLocation() != null) // use - // original - // - as good - // a guess - // as any - return -1; // an invalid sheet - } catch (Exception e) { - } - // if original does not have a location set, use this one - // instead - this.names.remove(existo); - this.bookNameRecs.remove(sName); - } - } - this.bookNameRecs.put(sName, n); - } - this.names.add(n); - if ((myexternsheet != null) && (n.getExternsheet() == null)) { - try { - n.setExternsheet(myexternsheet); // update sheet reference - } catch (WorkSheetNotFoundException e) { - Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " - + e.toString()); - } - } - return names.size() - 1; - } - - public void addNameUpdateSheetRefs(Name n, String origWorkBookName) { - if (bookNameRecs.get(n.getNameA()) == null) { - Name newName = new Name(this, n.getName()); - try { - newName.setLocation(n.getLocation()); - } catch (Exception e) { - } - if ((myexternsheet != null) && (newName.getExternsheet() == null)) { - try { - newName.setExternsheet(myexternsheet); // update sheet - // reference - } catch (WorkSheetNotFoundException e) { - Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: " - + e.toString()); - } - } - newName.updateSheetRefs(origWorkBookName); - } - } - - /** - * Store an external name - * - * @param n = String describing the name - * @return int location of the name - */ - public int addExternalName(String n) { - this.externalnames.add(n); - return externalnames.size(); // one-based index - } - - /** - * Get a string array of external names - * - * @return externalNames - */ - public String[] getExternalNames() { - String[] n = new String[externalnames.size()]; - externalnames.toArray(n); - return n; - } - - /** - * Get the external name at the specified index. - * - * @param t index of the name - * @return name at the index, empty string if it doesn't exist. - *

                      - * Why are we calling getExternalName one based, then removing for ordinal, internal processes should always - * be 0,1,2,3... -NR 1/06 - */ - public String getExternalName(int t) { - if (t > 0) - return (String) this.externalnames.get(t - 1); // one-based index - return ""; - } - - /** - * For workbooks that do not contain an externsheet - * this creates the externsheet record with one 0000 record - * and the related Supbook rec - */ - public void addDefaultExternsheet() { - Supbook sbb = (Supbook) Supbook.getPrototype(this.getNumWorkSheets()); - int l = this.stringTable.getRecordIndex(); - streamer.addRecordAt(sbb, l++); - supBooks.add(sbb); - Externsheet ex = (Externsheet) Externsheet - .getPrototype(0x0000, 0x0000, this); - streamer.addRecordAt(ex, l); - this.addRecord(ex, false); - } - - /** - * apparently this method adds an External name rec and returns the ilbl - *

                      - * Correct structure is - * Supbook - * Externname - * Supbook - * Externsheet - * - * @param s - * @return - * @see PtgNameX - */ - public int getExtenalNameNumber(String s) { - int i = externalnames.indexOf(s); - if (i > -1) // got it - return i + 1; - if (this.getExternSheet() == null) - this.addDefaultExternsheet(); - - // not found; add a new EXTERNNAME record to list of add-ins - int n = addExternalName(s); - try { - int loc; - if (myADDINSUPBOOK == null) { - Supbook sb = (Supbook) Supbook.getAddInPrototype(); - loc = this.getExternSheet().getRecordIndex(); - streamer.addRecordAt(sb, loc++); - this.addRecord(sb, false); - myADDINSUPBOOK = sb; - supBooks.add(sb); - int externref = this.getExternSheet().getVirtualReference(); - // Add EXTERNNAME record after ADD-IN SUPBOOK record and - // after existing EXTERNNAME records - Externname exn = (Externname) Externname.getPrototype(s); - streamer.addRecordAt(exn, loc++); - this.addRecord(exn, false); - } else { - loc = streamer.getRecordIndex(myADDINSUPBOOK); - // Add EXTERNNAME record after ADD-IN SUPBOOK record and - // after existing EXTERNNAME records - Externname exn = (Externname) Externname.getPrototype(s); - streamer.addRecordAt(exn, loc + externalnames.size()); - this.addRecord(exn, false); - } - - } catch (Exception e) { - Logger.logWarn("Error adding externname: " + e); - } - return n; - } - - /** - * Get a collection of all names in the workbook - */ - public Name[] getNames() { - Name[] n = new Name[names.size()]; - names.toArray(n); - return n; - } - - /** - * Get a collection of all names in the workbook - */ - public Name[] getWorkbookScopedNames() { - ArrayList a = new ArrayList(this.bookNameRecs.values()); - Name[] n = new Name[a.size()]; - a.toArray(n); - return n; - } - - /** - * returns the List of Formulas in the book - * - * @return - */ - public List getFormulaList() { - return formulas; - } - - /** - * returns the array of Formulas in the book - * - * @return - */ - public Formula[] getFormulas() { - Formula[] n = new Formula[formulas.size()]; - formulas.toArray(n); - return n; - } - - /** - * remove a formula from the book - * - * @param fmla - */ - public void removeFormula(Formula fmla) { - this.formulashash.remove(fmla.getCellAddressWithSheet()); - formulas.remove(fmla); - fmla.destroy(); - } - - /** - * Returns the recalculation mode for the Workbook: - *
                      0= Manual - *
                      1= Automatic - *
                      2= Automatic except for multiple table operations - * - * @return int - */ - public int getRecalculationMode() { - return this.calcmoderec.getRecalcuationMode(); - } - - /** - * Sets the recalculation mode for the Workbook: - *
                      0= Manual - *
                      1= Automatic - *
                      2= Automatic except for multiple table operations - */ - public void setRecalcuationMode(int mode) { - this.calcmoderec.setRecalculationMode(mode); - } - - /** - * returns a Named range by number - * - * @param t - * @return - */ - public Name getName(int t) { - return (Name) this.names.get(t - 1); - } - - /** - * rename the NamedRange in the lookup map - * - * @param t - * @return - */ - public void setNewName(String oldname, String newname) { - if (oldname.equals(newname)) - return; - oldname = oldname.toUpperCase(); // case-insensitive - newname = newname.toUpperCase(); // "" - Object old = bookNameRecs.get(oldname); - if (old == null) - return; // new name? - bookNameRecs.remove(oldname); - bookNameRecs.put(newname, old); - - } - - /** - * Re-assocates ptgrefs that are pointing to a name that has been deleted then - * is recreated - * - * @param name - */ - public void associateDereferencedNames(Name name) { - Iterator i = orphanedPtgNames.iterator(); - String theName = name.getName(); - while (i.hasNext()) { - IlblListener x = (IlblListener) i.next(); - if (x.getStoredName().equalsIgnoreCase(theName)) { - x.setIlbl((short) this.getNameNumber(theName)); - x.addListener(); - } - } - } - - /** - * returns a named range by name string - *

                      - * This method will first attempt to look in the book names, then the sheet names, - * obviously different scoped names can have the same identifying name, so this could return - * one of multiple names if this is the case - * - * @param t - * @return - */ - public Name getName(String nameRef) { - nameRef = nameRef.toUpperCase(); // case-insensitive - Object o = this.bookNameRecs.get(nameRef); - if (o == null) { - Boundsheet[] shts = this.getWorkSheets(); - for (int i = 0; i < shts.length; i++) { - o = shts[i].getName(nameRef); - if (o != null) - return (Name) o; - } - } - return (Name) o; - } - - /** - * returns a scoped named range by name string - * - * @param t - * @return - */ - public Name getScopedName(String nameRef) { - Object o = this.bookNameRecs.get(nameRef.toUpperCase()); // case-insensitive - if (o == null) { - return null; - } - return (Name) o; - } - - /** - * Returns the ilbl of the name record associated with the string passed in. - * If the name does not exist, it get's created without a location reference. - * This is needed to support formula creation with non-existent names referenced. - * - * @param t, the name record to search for - * @return the index of the name - */ - public int getNameNumber(String nameStr) { - for (int i = 0; i < names.size(); i++) { - Name n = (Name) this.names.get(i); - if (n.getName().equalsIgnoreCase(nameStr)) - return i + 1; - } - // no name exists, we need to create one. - Name myName; - myName = new Name(this, nameStr); - // myName = new Name(this, true); - // myName.setName(nameStr); - Name[] nmx = this.getNames(); - int namepos = -1; - if (nmx.length >= 1) { - namepos = nmx[nmx.length - 1].getRecordIndex(); - } else { - namepos = this.getExternSheet().getRecordIndex(); - } - namepos++; - this.getStreamer().addRecordAt(myName, namepos); - return this.getNameNumber(nameStr); - } - - /** - * Get's the index for this particular front. - *

                      - * NOTE: this doesn't actually get a "matching" font, it has to be the exact font. - * 20070826 KSC: changed to match font characterstics, not just return exact matching font - */ - public int getFontIdx(Font f) { - // 20070819 KSC: Try this to see if better! Matches 6 key - // attributes (size, name, color, etc.) - for (int i = fonts.size() - 1; i >= 0; i--) { // start from the back so - // don't initially match - // defaults... - if (f.matches((Font) fonts.get(i))) - return ((i > 3) ? i + 1 : i); - } - // return fonts.indexOf(f); - return -1; - } - - /** - * Get's the index for this font, based on matching through - * xml strings. If the font doesn't exist in the book it returns -1; - * - * @return KSC: is this method necessary now with above getFontIdx changes? - */ - public int getMatchingFontIndex(Font f) { - Map fontmap = this.getFontRecsAsXML(); - Object o = fontmap.get("<" + f.getXML() + "/>"); - if (o != null) { - Integer I = (Integer) o; - return I.intValue(); - } else { - return -1; - } - } - - /** - * InsertFont inserts a Font record into the workbook level stream, - * For some reason, the addFont only puts it into an array that is never accessed - * on output. This may have a reason, so I am not overwriting it currently, but - * let's check it out? - */ - public int insertFont(Font x) { - int insertIdx = this.getFont(this.getNumFonts()).getRecordIndex(); - // perform default add rec actions - this.getStreamer().addRecordAt(x, insertIdx + 1); - x.setIdx(-1); // flag to add into font array - this.addRecord(x, false); // also adds to font array so no need for - // additional addFont below - return fonts.indexOf(x); - } - - /** - * add a Font object to the collection of Fonts - */ - public int addFont(Font f) { - fonts.add(f); - if (fonts.size() > 4) // fake the evil 4! - return fonts.size(); - return fonts.size() - 1; - } - - public int getNumFonts() { - return fonts.size(); - } - - /** - * Get the font at the specified index. Note that the number 4 does not exist, so index correctly based of that. - *

                      - * So, if you call getFont(5), you are really doing getFont(4) from the internal array - * - * @param t - * @return - */ - public Font getFont(int t) { - if (t >= 4) { - t--; - } - if (this.fonts.size() >= t) { - if (t >= fonts.size()) { - Logger.logWarn("font " + t - + " not found. Workbook contains only: " + fonts.size() - + " defined fonts."); - return (Font) fonts.get(0); - } else { - return (Font) fonts.get(t); - } - } - return (Font) this.fonts.get(0); - } - - /** - * Inserts a newly created Format record into the workbook. - * This method handles assigning the format ID and adding the record to the - * workbook. If the record is already part of the workbook use - * {@link #addFormat} instead. - */ - public int insertFormat(Format format) { - Format last; - try { - last = (Format) formats.get(formats.lastKey()); - } catch (NoSuchElementException e) { - /* - * There are no other Format records in the workbook. - * This shouldn't happen because most (all?) Excel files - * contain - * Format records for the locale-specific (and thus not - * implied) - * built-in formats. If it does happen, either we need to - * re-assess - * the above assumption or this method was called before the - * Format - * records were parsed. Either way we need to know about it. - */ - throw new AssertionError("WorkBook.insertFormat called but no " - + "Format records exist. This should not happen. Please " - + "report this error to support@extentech.com."); - } - - // Add it to the streamer and workbook - getStreamer().addRecordAt(format, last.getRecordIndex() + 1); - addRecord(format, false); - - // Give it a format ID - if (format.getIfmt() == -1) - format.setIfmt((short) Math.max(last.getIfmt() + 1, 164)); - - // Add it to the format lookups - addFormat(format); - - return format.getIfmt(); - } - - public Formula getFormula(String cellAddress) throws FormulaNotFoundException { - Formula formula = (Formula) formulashash.get(cellAddress); - if (formula == null) - throw new FormulaNotFoundException( - "no formula found at " + cellAddress); - - return (formula); - } - - /** - * Adds an existing format record to the list of known formats. - * This method does not add the record to the workbook! If the format is - * not already in the workbook use {@link #insertFormat} instead. - * - * @param format the Format record to add - */ - public int addFormat(Format format) { - Short ifmt = Short.valueOf(format.getIfmt()); - - // Add it to the format record lookup - formats.put(ifmt, format); - - // Add it to the format string lookup - formatlookup.put(format.getFormat().toUpperCase(), ifmt); - - return format.getIfmt(); - } - - /** - * Gets the number of custom number formats registered on this book. - */ - public int getNumFormats() { - return formats.size(); - } - - /** - * Gets a custom number format by its format ID. - */ - public Format getFormat(int id) { - return (Format) formats.get(Short.valueOf((short) id)); - } - - public TabID getTabID() { - return this.tabs; - } - - /** - * associate default row/col size recs - */ - void setDefaultRowHeightRec(DefaultRowHeight dr) { - this.drh = dr; - } - - /** - * set Default row height in twips (1/20 of a point) - */ - // should be a double as Excel units are 1/20 of what is - // stored in defaultrowheight - // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 - // (approx) - // should expect users to use Excel units and target method - // do the 20* conversion - public void setDefaultRowHeight(int t) { - drh.setDefaultRowHeight(t); - } - - /** - * set Default col width for all worksheets in the workbook, - *

                      - * Default column width can also be set on individual worksheets - */ - public void setDefaultColWidth(int t) { - Boundsheet[] b = this.getWorkSheets(); - for (int i = 0; i < b.length; i++) { - b[i].setDefaultColumnWidth(t); - } - } - - /** - * sets the selected worksheet - */ - public int getSelectedSheetNum() { - return win1.getCurrentTab(); - } - - /** - * sets the selected worksheet - */ - public void setSelectedSheet(Boundsheet bs) { - Boundsheet[] bsx = this.getWorkSheets(); - for (int t = 0; t < bsx.length; t++) { - if (bsx[t] != bs) - bsx[t].setSelected(false); - } - this.win1.setCurrentTab(bs); - } - - Formula lastFormula = null; - XLSRecord countryRec = null; - boolean inChartSubstream = false; - ArrayList chartTemp = new ArrayList(); - - /** - * for those cases where a formula calculation adds a new string rec - * need to explicitly set lastFormula before calling addRecord - * - * @param f - */ - protected void setLastFormula(Formula f) { - lastFormula = f; - } - - /** - * Associate a record with its containers and related records. - */ - @Override - public BiffRec addRecord(BiffRec rec, boolean addtorec) { - short opc = rec.getOpcode(); - rec.setStreamer(streamer); - rec.setWorkBook(this); - - Boundsheet bs = null; - Long lbplypos = new Long(0l); - - // get the relevant Boundsheet for this rec - if (rec instanceof Bof) { - if (this.getFirstBof() == null) - this.setFirstBof((Bof) rec); - if (bofct == eofct) { // not a chart or other non Sheet Bof - this.setLastBOF((Bof) rec); - } - if (((Bof) rec).isChartBof()) { - inChartSubstream = true; - } - } - - if (this.lastBOF == null) - Logger.logWarn("WorkBook: NULL Last BOF"); - long lb = this.lastBOF.getLbPlyPos(); - if (!this.lastBOF.isValidBIFF8()) - lb += 8; - lbplypos = new Long(lb); // use last - - bs = getSheetFromRec(rec, lbplypos); - if (bs != null) - lbplypos = new Long(bs.getLbPlyPos()); - - if ((bs != null)) { // &&){ - lastbound = bs; - if (addtorec) - rec.setSheet(bs);// we don't include Bof or other Book-recs - // because it lives in the Streamer recvec - if /* - * ((rec.isValueForCell()) - * && - */ (!copying) { - if ((lastFormula != null) && (opc == STRINGREC)) { - lastFormula.addInternalRecord(rec); - } else if ((lastFormula != null) && (opc == ARRAY)) { - lastFormula.addInternalRecord(rec); - } else if (rec.isValueForCell()) { - if (currchart == null) - bs.addCell((CellRec) rec); - - } - } - } - - if (inChartSubstream) { - if (currchart == null) { - if (rec.getOpcode() == CHART) { - charts.add(rec); - if (bs != null) - bs.addChart((Chart) rec); - currchart = (Chart) rec; - currchart.setPreRecords(chartTemp); - chartTemp = new ArrayList(); // clear out - } else { - chartTemp.add(rec); - } - } else { - currchart.addInitialChartRecord(rec); - if (rec.getOpcode() == EOF) { - currchart.initChartRecords(); // finished - currchart = null; - inChartSubstream = false; - } - } - addtorec = false; - } - - // Rows, valrecs, dbcells, and muls are stored in the row, - // not the byte streamer - if (opc == XLSRecord.DBCELL || opc == XLSRecord.ROW - || rec.isValueForCell() || opc == XLSRecord.MULRK - /* || opc==MULBLANK */ - || opc == CHART || opc == XLSRecord.FILEPASS - || opc == XLSRecord.SHRFMLA || opc == XLSRecord.ARRAY - || opc == XLSRecord.STRINGREC) { - addtorec = false; - } - - // add it to the record stream - if (addtorec) { - - if (lbplypos.longValue() > 0) - streamer.addRecord(rec); - else - streamer.records.add(rec); - } - - switch (opc) { - case AUTOFILTER: - bs.getAutoFilters().add(rec); - break; - - case CONDFMT: - bs.getConditionalFormats().add(rec); - ((Condfmt) rec).initializeReferences(); - break; - - case CF: - Condfmt cfmt = (Condfmt) bs.getConditionalFormats() - .get(bs.getConditionalFormats().size() - 1); - cfmt.addRule((Cf) rec); - break; - - case MERGEDCELLS: - bs.addMergedCellsRec((Mergedcells) rec); - this.addMergedcells((Mergedcells) rec); - break; - - // give protection records to the relevant ProtectionManager - case PASSWORD: - case PROTECT: - case PROT4REV: - case OBJPROTECT: - case SCENPROTECT: - case FEATHEADR: - ProtectionManager manager; - if (bs != null) - manager = bs.getProtectionManager(); - else - manager = this.getProtectionManager(); - manager.addRecord(rec); - break; - - case DVAL: - if (bs != null) - bs.setDvalRec((Dval) rec); - break; - - case DV: - if (bs != null) { - if (bs.getDvalRec() != null) { - bs.getDvalRec().addDvRec((Dv) rec); - } - } - break; - - case INDEX: - Index id = (Index) rec; - id.setIndexNum(indexnum++); - indexes.add(indexes.size(), id); - this.setLastINDEX(id); - if (bs == null) { - Logger.logWarn("ERROR: WorkBook.addRecord( Index ) error: BAD LBPLYPOS. The wrong LB:" - + lbplypos); - try { - bs = this.getWorkSheetByNumber(indexnum - 1); - Logger.logInfo(" The RIGHT LB:" + bs.getLbPlyPos()); - } catch (WorkSheetNotFoundException e) { - Logger.logInfo("problem getting WorkSheetByNumber: " + e); - } - - } - bs.setSheetIDX(id); - break; - - case ROW: - Row rw = (Row) rec; - if (bs != null) - bs.addRowRec(rw); - - break; - - case FORMULA: - this.addFormula((Formula) rec); - lastFormula = (Formula) rec; - break; - - case ARRAY: - Array arr = (Array) rec; - if (bs != null) - bs.addArrayFormula(arr); - arr.setParentRec(lastFormula); // [BugTracker 1869] link array - // formula to it's parent formula - // rec - break; - - /* - * case SHRFMLA : done in shrfmla.init - * Shrfmla form = (Shrfmla) rec; - * try{ // throws exceptipon during pullparse - * form.setHostCell( lastFormula ); - * }catch(Exception e){;} - * break; - */ - - case DATE1904: - if (((NineteenOhFour) rec).is1904) { - this.dateFormat = DateConverter.DateFormat.LEGACY_1904; - } - break; - - /* - * case PALETTE : // palette now correctly read into - * COLORTABLE - * this.pal = (Palette) rec; - * break; - */ - - case HLINK: - Hlink hl = (Hlink) rec; - this.addHlink(hl); - break; - - case DSF: - Dsf dsf = (Dsf) rec; - if (dsf.fDSF == 1) { - Logger.logErr("DOUBLE STREAM FILE DETECTED!"); - Logger.logErr(" OpenXLS is compatible with Excel 97 and above only."); - throw new WorkBookException( - "ERROR: DOUBLE STREAM FILE DETECTED! OpenXLS is compatible with Excel 97 + only.", - WorkBookException.DOUBLE_STREAM_FILE); - } - break; - - case GUTS: - if (bs != null) { - bs.setGuts((Guts) rec); - } - break; - - case DBCELL: - break; - - case BOF: - if (DEBUGLEVEL > 5) - Logger.logInfo("BOF:" + bofct + " - " + rec); - if (eofct == bofct) { - if (bs != null) { - bs.setBOF((Bof) rec); - } - } - bofct++; - break; - - case EXTERNSHEET: - myexternsheet = (Externsheet) rec; - break; - - case DEFCOLWIDTH: - if (bs != null) { - bs.setDefColWidth((DefColWidth) rec); - } - break; - - case EOF: - this.lasteof = (Eof) rec; - eofct++; - if (eofct == bofct) { - if (bs != null) { - bs.setEOF((Eof) rec); - } - eofct--; - bofct--; - } - break; - - case SELECTION: // only used for Recvec index - bs.setLastselection((Selection) rec); - break; - - case COUNTRY: - // Added to save position of 1st bound sheet, which is 1 - // record - // before COUNTRY RECORD (= 2 before 1st SUPBOOK record - - // true in all cases?) - countryRec = (XLSRecord) rec; - // USA=1, Canada=1, Japan=81, China=86, Thailand= 66, Korea= - // 82, India=91 ... - this.defaultLanguage = ((Country) rec).getDefaultLanguage(); - break; - - case SUPBOOK: // KSC: must store ordinal positions of SupBooks, for - // adding Externsheets - supBooks.add(rec); - if (myADDINSUPBOOK == null) { // see if this is the ADD-IN SUPBOOK - // rec - Supbook sb = (Supbook) rec; - if (sb.isAddInRecord()) - myADDINSUPBOOK = sb; - } - break; - - case BOUNDSHEET: - Boundsheet sh = (Boundsheet) rec; - - /* - * Here we need to set the selected variable, - * but not mess with selected tabs - * when all of the sheets aren't in the book yet. - * -jm - */ - int ctab = 1; // default- select 1st sheet if no Windows1 record - if (win1 != null) {// Windows1 record is optional 20101004 - // TestCorruption.TestNPEOnOpen - ctab = win1.getCurrentTab(); - } - int shts = boundsheets.size(); - if (ctab == shts) - sh.selected = true; - - this.addWorkSheet(new Long(sh.getLbPlyPos()), sh); - break; - - case MULRK: - Mulrk mul = (Mulrk) rec; - Iterator xit = mul.getRecs().iterator(); - while (xit.hasNext()) { - this.addRecord(((Rk) xit.next()), false); - } - break; - - case SST: - this.setSharedStringTable((Sst) rec); - break; - - case EXTSST: - ((Extsst) rec).setSst(this.getSharedStringTable()); - break; - - case SXSTREAMID: - this.ptstream.add(rec); // Pivot Stream - break; - - case SXVS: - case DCONREF: - case DCONNAME: - case DCONBIN: - try { - SxStreamID sid = (SxStreamID) ptstream.get(ptstream.size() - 1); - sid.addSubrecord(rec); - } catch (Exception e) { - } - break; - - case SXVIEW: - addPivotTable(((Sxview) rec)); // Pivot Table View ==Top-level - // record for a Pivot Table - break; - - // all* possible records associated with SxView (=PivotTable - // View) (*hopefully) - case SXVD: - // case SXVI: // subrecords of SxVD - // case SXVDEX: - case SXIVD: - case SXPI: - case SXDI: - case SXLI: - case SXEX: - case SXVIEWEX9: - case QSISXTAG: - try { - Sxview sx = (Sxview) this.ptViews.values() - .toArray()[this.ptViews.size() - 1]; - sx.addSubrecord(rec); - } catch (Exception e) { - } - break; - - case TABID: - this.tabs = (TabID) rec; - break; - - case NAME: - addName((Name) rec); - break; - - case CALCMODE: - this.calcmoderec = (CalcMode) rec; - break; - - case WINDOW1: - this.win1 = (Window1) rec; - break; - - case WINDOW2: - if (bs != null) - bs.setWindow2((Window2) rec); - break; - - case SCL: // scl is for zoom - if (bs != null) - bs.setScl((Scl) rec); - break; - - case PANE: - if (bs != null) - bs.setPane((Pane) rec); - break; - case EXCEL2K: - xl2k = rec; - break; - - case PHONETIC: - // TODO: this isn't necessary anymore! look at and remove - if (this.currdrw != null) { - this.currdrw.setMystery((Phonetic) rec); - } - break; - - case MSODRAWINGGROUP: - if (msodg == null) - msodg = (MSODrawingGroup) rec; - msodgMerge.add(rec); - - break; - - case MSODRAWING: - rec.setSheet(bs); - if (msodg != null) - msodg.addMsodrawingrec((MSODrawing) rec); - else - ; - // do what???io.starter.toolkit.Logger.log("PROBLEM with - // MSODG!"); - break; - - case COLINFO: - bs.addColinfo((Colinfo) rec); - break; - - case USERSVIEWBEGIN: - this.usersview = (Usersviewbegin) rec; - break; - - case WSBOOL: - if (bs != null) { - bs.setWsBool((WsBool) rec); - } - break; - - // Handle continue records which are actually masked Mso's - case CONTINUE: - if (((Continue) rec).maskedMso != null) { - ((Continue) rec).maskedMso.setSheet(bs); - if (msodg != null) - msodg.addMsodrawingrec(((Continue) rec).maskedMso); - } - break; - - case XF: - try { - this.addXf((Xf) rec); - } catch (Exception e) { - // throws exceptions during PullParse - } - break; - - default: - // DO NOTHING - - } - - // finish up - rec.setIndex(getLastINDEX()); - rec.setXFRecord(); - return rec; - } - - /** - * Dec 15, 2010 - * - * @param rec - * @return - */ - @Override - public Boundsheet getSheetFromRec(BiffRec rec, Long lbplypos) { - Boundsheet bs = null; - - if (rec.getSheet() != null) { - bs = rec.getSheet(); - } else if (lbplypos != null) { - bs = this.getWorkSheet(lbplypos); - } else - bs = lastbound; - return bs; - } - - /** - * get a handle to the ContinueHandler - */ - @Override - public ContinueHandler getContinueHandler() { - return this.contHandler; - } - - /** - * get a handle to the Usersviewbegin for the workbook - */ - public Usersviewbegin getUsersviewbegin() { - if (usersview == null) { - usersview = new Usersviewbegin(); - streamer.addRecord(usersview); - this.addRecord(usersview, false); - } - return usersview; - } - - /** - * set the Debug level - */ - @Override - public void setDebugLevel(int i) { - this.DEBUGLEVEL = i; - } - - /** - * get a handle to the Reader for this - * WorkBook. - */ - @Override - public void setFactory(WorkBookFactory r) { - this.factory = r; - } - - /** - * Get the number of worksheets in this WorkBook - */ - public int getNumWorkSheets() { - return this.boundsheets.size(); - } - - /** - * get the number of formulas in this WorkBook - * - * @return - */ - public int getNumFormulas() { - return formulas.size(); - } - - /** - * get the number of Cells in this WorkBook - */ - public int getNumCells() { - int cellnum = 0; - Enumeration e = workSheets.elements(); - while (e.hasMoreElements()) { - Boundsheet b = (Boundsheet) e.nextElement(); - cellnum += b.getNumCells(); - } - return cellnum; - } - - /** - * get all of the Cells in this WorkBook - */ - public BiffRec[] getCells() { - List cellz = new FastAddVector(); - for (int i = 0; i < workSheets.size(); i++) { - try { - Boundsheet b = this.getWorkSheetByNumber(i); - BiffRec[] cz = b.getCells(); - for (int x = 0; x < cz.length; x++) { - cellz.add(cz[x]); - } - } catch (Exception e) { - Logger.logErr("Error retrieving worksheet for getCells: " + e); - } - } - BiffRec[] cellzr = new BiffRec[cellz.size()]; - cellz.toArray(cellzr); - return cellzr; - } - - /** - * get the cell by the following String Pattern - *

                      - * BiffRec c = getCell("SheetName!C17"); - */ - public BiffRec getCell(String cellname) throws CellNotFoundException, WorkSheetNotFoundException { - int semi = cellname.indexOf("!"); - String sname = cellname.substring(0, semi); - String cname = cellname.substring(semi + 1); - return getCell(sname, cname); - } - - /** - * get the cell by the following String Pattern - *

                      - * BiffRec c = getCell("Sheet1", "C17"); - */ - public BiffRec getCell(String sheetname, String cellname) throws CellNotFoundException, WorkSheetNotFoundException { - cellname = cellname.toUpperCase(); - try { - Boundsheet bs = this.getWorkSheetByName(sheetname); - BiffRec ret = bs.getCell(cellname); - if (ret == null) - throw new CellNotFoundException(sheetname + ":" + cellname); - return ret; - } catch (WorkSheetNotFoundException a) { - throw new WorkSheetNotFoundException(sheetname + " not found"); - } catch (NullPointerException e) { - throw new CellNotFoundException(sheetname + ":" + cellname); - } - } - - private Boundsheet lastbound = null; - - /** - * add a Boundsheet to the WorkBook - */ - private void addWorkSheet(Long lbplypos, Boundsheet sheet) { - if (sheet == null) { - Logger.logWarn("WorkBook.addWorkSheet() attempting to add null sheet."); - return; - } else { - this.lastbound = sheet; - if (DEBUGLEVEL > 10) - Logger.logInfo("Workbook Adding Sheet: " + sheet.getSheetName() - + ":" + lbplypos); - workSheets.put(lbplypos, sheet); - boundsheets.add(sheet); - } - } - - /** - * @param n - * @return - */ - public boolean removeName(Name n) { - if (this.names.contains(n)) { - this.names.remove(n); - if (n.getItab() != 0) { - try { - this.getWorkSheetByNumber(n.getItab() - 1) - .removeLocalName(n); - } catch (WorkSheetNotFoundException e) { - } - } else { - this.bookNameRecs.remove(n.toString().toUpperCase()); // case-insensitive - } - } - ArrayList al = n.getIlblListeners(); - this.orphanedPtgNames.addAll(al); - - this.updateNameIlbls(); - return this.getStreamer().removeRecord(n); - } - - /** - * Add a sheet-scoped name record to the boundsheet - *

                      - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this book, adding them here will not add them to the workbook; - * - * @param bookNameRecs - */ - public void addLocalName(Name name) { - bookNameRecs.put(name.getNameA(), name); - } - - /** - * Remove a sheet-scoped name record from the boundsheet. - *

                      - * Note this is not that primary repository for names, it just contains the name records - * that are bound to this book, removing them here will not remove them completely from the workbook. - *

                      - * In order to do that you will need to call book.removeName - * - * @param bookNameRecs - */ - public void removeLocalName(Name name) { - bookNameRecs.remove(name.getNameA()); - } - - /** - * After any changes in the name records - * this method needs to be called in order to - * update ilbl records - */ - public void updateNameIlbls() { - for (int i = 0; i < names.size(); i++) { - Name n = (Name) names.get(i); - n.updateIlblListeners(); - } - } - - /** - * remove a Boundsheet from the WorkBook - */ - public void removeWorkSheet(Boundsheet sheet) { - - int sheetNum = sheet.getSheetNum(); - // remove the sheet - // automatically deletes Named ranges scoped to the sheet - Name[] namesOnSheet = sheet.getAllNames(); - for (int i = 0; i < namesOnSheet.length; i++) - this.removeName(namesOnSheet[i]); - - // Remove Externsheet ref before removing sheet - // update any Externsheet references... - try { - Externsheet ext = this.getExternSheet(); - if (ext != null) - ext.removeSheet(sheet.getSheetNum()); - } catch (WorkSheetNotFoundException e) { - Logger.logInfo("could not update Externsheet reference from " - + sheet.toString() + " : " + e.toString()); - } - - sheet.removeAllRecords(); - streamer.removeRecord(sheet); - workSheets.remove(new Long(sheet.getLbPlyPos())); - boundsheets.remove(sheet); - // we need to reset the lastbound for adding new worksheets. - // Currently assume it is - // the last one in the vector. - if (boundsheets.size() > 0) { - lastbound = (Boundsheet) (boundsheets.get(boundsheets.size() - 1)); - lastBOF = lastbound.getMyBof(); - } - - // decrement the tab ids... - this.tabs.removeRecord(); - this.updateScopedNamedRanges(); - - // update wb chart cache - remove charts referenced by - // deleted sheet - for (int i = getChartVect().size() - 1; i >= 0; i--) { - if (((Chart) this.getChartVect().get(i)).getSheet() == sheet) - this.getChartVect().remove(i); - } - - if (this.getNumWorkSheets() == 0) - return; // empty book - try { // set the next sheet selected... - while (sheetNum <= this.getNumWorkSheets()) { - Boundsheet s2 = this.getWorkSheetByNumber(sheetNum++); - s2.setSelected(true); - if (!s2.getHidden()) - break; - - } - } catch (WorkSheetNotFoundException e) { - try { - Boundsheet s2 = this.getWorkSheetByNumber(0); - s2.setSelected(true); - } catch (Exception ee) { - throw new WorkBookException( - "Invalid WorkBook. WorkBook must contain at least one Sheet.", - WorkBookException.RUNTIME_ERROR); - } - } - } - - /** - * Updates all the name records in the workbook that are bound to a - * worksheet scope (as opposed to a workbook scope). Name records use - * their own non-externsheet based sheet references, so need to be modified - * whenever a sheet delete (or non-last sheet insert) operation occurs - */ - private void updateScopedNamedRanges() { - for (int i = 0; i < boundsheets.size(); i++) { - ((Boundsheet) boundsheets.get(i)).updateLocalNameReferences(); - } - } - - /** - * returns the Boundsheet identified by its - * offset to the BOF record indicating the - * start of the Boundsheet data stream. - *

                      - * used internally to access the Sheets to - * ensure that the lbplypos is correct -- essential - * to proper operation of XLS file. - * - * @param Long lbplypos of Boundsheet - */ - private Boundsheet getWorkSheet(Long lbplypos) { - return (Boundsheet) workSheets.get(lbplypos); - } - - /** - * returns the Boundsheet with the specific name - * - * @param String name of Boundsheet - */ - public Boundsheet getWorkSheetByName(String bstr) throws WorkSheetNotFoundException { - try { - if (bstr.startsWith("'") || bstr.startsWith("\"")) - bstr = bstr.substring(1, bstr.trim().length() - 1); - Iterator bs = boundsheets.iterator(); - while (bs.hasNext()) { - Boundsheet bsi = (Boundsheet) bs.next(); - String bsin = bsi.getSheetName(); - // TODO: check if we can have dupe names different case - if (bsin.equalsIgnoreCase(bstr)) - return bsi; - } - } catch (Exception ex) { - Logger.logWarn("WorkBook.getWorkSheetByName failed: " - + ex.toString()); - } - throw new WorkSheetNotFoundException( - "Worksheet " + bstr + " not found in " + this.toString()); - } - - /** - * returns the Boundsheet with the specific Hashname - * - * @param String hashname of Boundsheet - */ - public Boundsheet getWorkSheetByHash(String s) throws WorkSheetNotFoundException { - Boundsheet[] bs = this.getWorkSheets(); - for (int i = 0; i < bs.length; i++) { - if (bs[i].getSheetHash().equalsIgnoreCase(s)) - return bs[i]; - } - return null; - } - - /** - * returns the Boundsheet at the specific index - * - * @param int index of Boundsheet - */ - public Boundsheet getWorkSheetByNumber(int i) throws WorkSheetNotFoundException { - Boundsheet bs = null; - try { - bs = (Boundsheet) boundsheets.get(i); - } catch (ArrayIndexOutOfBoundsException e) { - } - if (bs == null) { // External Sheet Ref NOT FOUND - throw new WorkSheetNotFoundException( - i + " not found"); - } - return bs; - } - - /** - * set the last processed Index record - */ - private void setLastINDEX(Index id) { - lastidx = id; - } - - Index getLastINDEX() { - return lastidx; - } - - void setSharedStringTable(Sst s) { - stringTable = s; - } - - public Sst getSharedStringTable() { - return stringTable; - } - - /** - * returns the Vector of Boundsheets - */ - public AbstractList getSheetVect() { - return this.boundsheets; - } - - /** - * returns the boundsheets for this book as an array - */ - Boundsheet[] getWorkSheets() { - Boundsheet[] ret = new Boundsheet[boundsheets.size()]; - return (Boundsheet[]) boundsheets.toArray(ret); - } - - /** - * set the last BOF read in the stream - */ - void setLastBOF(Bof b) { - lastBOF = b; - } - /** return the last BOF read in the stream */ - // Bof lastBOF{return lastBOF;} - - /** - * get a handle to the first BOF to perform offset functions which don't know where the - * start of the file is due to the compound file format. - *

                      - * Referred to in Boundsheet as the 'lbPlyPos', this - * is the position of the BOF for the Boundsheet relative - * to the *first* BOF in the file (the firstBOF of the WorkBook) - * - * @see Boundsheet - */ - @Override - public void setFirstBof(Bof b) { - firstBOF = b; - } - - Bof getFirstBof() { - return firstBOF; - } - - @Override - public String toString() { - return this.getFileName(); - } - - @Override - public String getFileName() { - if (this.factory != null) // 2003-vers - return this.factory.getFileName(); - return "New Spreadsheet"; - } - - /** - * Returns whether the sheet selection tabs should be shown. - */ - public boolean showSheetTabs() { - return win1.showSheetTabs(); - } - - /** - * Sets whether the sheet selection tabs should be shown. - */ - public void setShowSheetTabs(boolean show) { - win1.setShowSheetTabs(show); - } - - /** - * set the first visible tab - */ - public void setFirstVisibleSheet(Boundsheet bs2) { - win1.setFirstTab(bs2.getSheetNum()); - } - - // Associate related records - - /** - * return the XF record at the specified index - */ - public Xf getXf(int i) { - if (xfrecs.size() < (i - 1)) - return null; - return (Xf) xfrecs.get(i); - } - - public int getNumXfs() { - return xfrecs.size(); - } - - /** - * InsertXF inserts an XF record into the workbook level stream, - * For some reason, the addXf only puts it into an array that is never accessed - * on output. This may have a reason, so I am not overwriting it currently, but - * let's check it out? - */ - int insertXf(Xf x) { - int insertIdx = this.getXf(this.getNumXfs() - 1).getRecordIndex(); - // perform default add rsec actions - this.getStreamer().addRecordAt(x, insertIdx + 1); - this.addRecord(x, false); // updates xfrecs + formatcache - x.ixfe = x.tableidx; - return x.tableidx; - } - - /** - * internally used in preparation for reading an 2007 and above workbook - */ - public void removeXfRecs() { - // must keep the 1st xf rec as default - for (int i = xfrecs.size() - 1; i > 0; i--) { - Xf xf = (Xf) xfrecs.get(i); - this.streamer.removeRecord(xf); - xfrecs.remove(i); - - } - } - - /** - * TODO: Does this function as desired? See comment for insertXf() above... - * tracks existing xf recs, used when testing whether xfrec exists or not ... - * -NR 1/06 - * ya should - called now from addRecord every time an xf record is added - * NOTE: this is the only place addXf is called - * - * @param xf - * @return - */ - int addXf(Xf xf) { - xfrecs.add(xf); - xf.tableidx = xfrecs.size() - 1; // flag that it's been added to records - this.updateFormatCache(xf); // links tostring of xf to xf rec for - // updating/reuse purposes - return xf.tableidx; - } - - /** - * formatCache: - * links tostring of xf to xf rec for updating/reuse purposes - * - * @param xf - * @see FormatHandle.updateXf - * @see WorkBook.addXf - */ - public void updateFormatCache(Xf xf) { - if (xf.tableidx != -1) { // if this xf has been already added to the - // workbook - if (formatCache.containsValue(xf)) { // xf signature has - // changed/it's been updated - Iterator ii = formatCache.keySet().iterator(); // remove and - // update below - while (ii.hasNext()) { - String key = (String) ii.next(); - Xf x = (Xf) formatCache.get(key); - if (x.equals(xf)) { - formatCache.remove(key); - break; - } - } - } - String formatStr = xf.toString(); - - if (!formatCache.containsKey(formatStr)) - formatCache.put(formatStr, xf); - } - } - - /** - * retrieve the format cache - links string vers. of xf to xf rec - * used for resusing xf's - * - * @return - * @see FormatHandle.updateXf - */ - public HashMap getFormatCache() { - return formatCache; - } - - /** - * Get a substream by name. - */ - @Override - public ByteStreamer getStreamer() { - return this.streamer; - } - - public void setSubStream(ByteStreamer s) { - this.streamer = s; - } - - /** - * Get the typename for this object. - */ - String getTypeName() { - return "WorkBook"; - } - - /** - * Write the contents of the WorkBook bytes to an OutputStream - * - * @param _out - */ - @Override - public int stream(OutputStream _out) { - return streamer.streamOut(_out); - } - - /** - * get a handle to the factory - */ - @Override - public WorkBookFactory getFactory() { - return this.factory; - } - - /** - * Copies a complete boundsheet within the workbook - *

                      - * If a name exists that refers directly to this sheet then duplicate it, otherwise workbook scoped names are - * not copied - */ - public void copyWorkSheet(String SourceSheetName, String NewSheetName) throws Exception { - Boundsheet origSheet = null; - origSheet = this.getWorkSheetByName(SourceSheetName); - List chts = origSheet.getCharts(); // 20080630 KSC: Added - for (int i = 0; i < chts.size(); i++) { - Chart cxi = (Chart) chts.get(i); - cxi.populateForTransfer(); - } - byte[] inbytes = origSheet.getSheetBytes(); - this.addBoundsheet(inbytes, SourceSheetName, NewSheetName, null, false); - Boundsheet bnd = this.getWorkSheetByName(NewSheetName); - // handle moving the built-in name records. These handle - // such items as print area, header/footer, etc - Name[] ns = this.getNames(); - for (int i = 0; i < ns.length; i++) { // 20100404 KSC: take out +1? - if (ns[i].getItab() == origSheet.getSheetNum() + 1) { - // it's a built in record, move it to the new sheet - int sheetnum = bnd.getSheetNum(); - int xref = this.getExternSheet(true) - .insertLocation(sheetnum, sheetnum); - Name n = (Name) ns[i].clone(); - n.setExternsheetRef(xref); - n.updateSheetReferences(bnd); - n.setSheet(bnd); - n.setItab((short) (bnd.getSheetNum() + 1)); - this.insertName(n); - } - } - } - - /** - * Inserts a newly created Name record into the correct location in the streamer. - * - * @param n - */ - public void insertName(Name n) { - int namepos = -1; - Name[] nmx = getNames(); - if (nmx.length > 0) { - if (nmx[nmx.length - 1].getSheet() != null) { - namepos = nmx[nmx.length - 1].getRecordIndex(); - } else { - namepos = getExternSheet(true).getRecordIndex() + nmx.length; - } - } else { - namepos = getExternSheet(true).getRecordIndex(); - } - namepos++; - getStreamer().addRecordToBookStreamerAt(n, namepos); - addRecord(n, false); - } - - /** - * Copies an existing Chart to another WorkSheet - * - * @param chartname - * @param sheetname - */ - public void copyChartToSheet(String chartname, String sheetname) throws ChartNotFoundException, WorkSheetNotFoundException { - Chart ct = this.getChart(chartname); - Boundsheet sht = this.getWorkSheetByName(sheetname); - byte[] bt = ct.getChartBytes(); - sht.addChart(bt, chartname, ct.getCoords()); - } - - /** - * Inserts a serialized boundsheet chart into the workboook - */ - public Chart addChart(Chart destChart, String NewChartName, Boundsheet boundsht) { - destChart.setWorkBook(this); - destChart.setSheet(boundsht); - List recs = destChart.getXLSrecs(); - for (int x = 0; x < recs.size(); x++) { - XLSRecord rec = (XLSRecord) recs.get(x); - rec.setWorkBook(this); - rec.setSheet(boundsht); - if (rec.getOpcode() == MSODRAWING) { - addChartUpdateMsodg((MSODrawing) rec, boundsht); - continue; - } - if (!(rec instanceof Bof)) // TODO: error/problem with the BOF - // record!!! - rec.init(); - if (rec instanceof Dimensions) - destChart.setDimensions((Dimensions) rec); - try { - ((GenericChartObject) rec).setParentChart(destChart); - } catch (ClassCastException e) { // Scl, Obj and others are not - // chart objects - } - - } - destChart.setTitle(NewChartName); - destChart.setId(boundsht.lastObjId + 1); // track last obj id per sheet - // ... - this.charts.add(destChart); - boundsht.getCharts().add(destChart); // should really have two lists??? - return destChart; - } - - /** - * updates Mso (MSODrawingGroup + Msodrawing) records upon add/copy worksheet and add/copy charts - * NOTE: this code is mainly garnered via trial and error, works - * - * @param mso Msodrawing record that is being added or copied - * @param sht Boundsheet - */ - public void addChartUpdateMsodg(MSODrawing mso, Boundsheet sht) { - if (msodg == null) { - this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup - .getPrototype()); - msodg.initNewMSODrawingGroup(); // generate and add required records - // for drawing records - } - msodg.addMsodrawingrec(mso); - MSODrawing hdr = msodg.getMsoHeaderRec(sht); - if (hdr != null && hdr != mso) { // already have a header rec - if (sht.getCharts().size() > 0) { - mso.makeNonHeader(); - hdr.setNumShapes(hdr.getNumShapes() + 1); - } - } else if (hdr == null) { - mso.setIsHeader(); - hdr = mso; - } - this.updateMsodrawingHeaderRec(sht); - msodg.dirtyflag = true; // flag to reset SPIDs on write - msodg.setSpidMax(++lastSPID); - msodg.updateRecord(); - } - - /** - * JM - - * Add the requisite records in the book streamer for the chart. \ - * Supbook, externsheet & msodrawinggroup - *

                      - * I think this is due to the fact that the referenced series are usually stored - * in the fashon 'Sheet1!A4:B6' The sheet1 reference requires a supbook, though the - * reference is internal. - */ - public void addPreChart() { - this.addExternsheet(); - if (msodg == null) { - this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup - .getPrototype()); - msodg.initNewMSODrawingGroup(); // generate and add required records - // for drawing records - } - - } - - /** - * remove an existing chart from the workbook - * NOTE: STILL EXPERIMENTAL TESTS OK IN BASIC CIRCUMSTANCES BUT MUST BE TESTED FURTHER - */ - public void deleteChart(String chartname, Boundsheet sheet) throws ChartNotFoundException { - Chart chart = this.getChart(chartname); - // TODO: Update Dimensions record?? - List recs = chart.getXLSrecs(); - // first rec SHOULD BE MsoDrawing!!! - try { - MSODrawing rec = (MSODrawing) recs.get(0); - msodg.removeMsodrawingrec(rec, sheet, true); // also remove - // associated Obj - // record - } catch (Exception e) { - Logger.logErr("deleteChart: expected Msodrawing record"); - } - /* - * shouldn't be necessary to remove chart recs as they are - * separated upon init of workbook and reassebmbled upon - * write - */ - this.removeChart(chartname); - } - - /** - * Inserts a serialized boundsheet into the workboook. - * - * @throws ClassNotFoundException - * @throws IOException - * @param inbytes original sheet bytes - * @param NewSheetName new Sheet Name - * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * - * @boolean SSTPopulatedBoundsheet - a boundsheet that has all of it's sst data saved off in LabelSST records. - * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. - * Do not use this if the data already exists in the SST, you are just causing bloat! - */ - public void addBoundsheet(byte[] inbytes, String origSheetName, String NewSheetName, String origWorkBookName, boolean SSTPopulatedBoundsheet) throws IOException, ClassNotFoundException { - Boundsheet destSheet = null; - ByteArrayInputStream bais = new ByteArrayInputStream(inbytes); - BufferedInputStream bufstr = new BufferedInputStream(bais); - ObjectInputStream o = new ObjectInputStream(bufstr); - destSheet = (Boundsheet) o.readObject(); - - if (destSheet != null) { - this.addBoundsheet(destSheet, origSheetName, NewSheetName, origWorkBookName, SSTPopulatedBoundsheet); - } - } - - /** - * change the tab order of a boundsheet - */ - public void changeWorkSheetOrder(Boundsheet bs, int idx) { - // reorder the sheet vector - if (idx >= 0 && idx < boundsheets.size()) { - boundsheets.remove(bs); - boundsheets.add(idx, bs); - for (int x = 0; x < boundsheets.size(); x++) { - Boundsheet bs1 = (Boundsheet) boundsheets.get(x); - boolean udpatewin1 = bs1.selected(); - if (udpatewin1) - bs1.setSelected(true); - } - } - - int insertLoc = Integer.MAX_VALUE; - // remove the existing boundsheet records in the streamer - for (int i = 0; i < boundsheets.size(); i++) { - Boundsheet bound = (Boundsheet) boundsheets.get(i); - int position = bound.getRecordIndex(); - insertLoc = Math.min(insertLoc, position); - streamer.removeRecord((XLSRecord) boundsheets.get(i)); - } - // enter the boundsheet records back in the streamer in - // correct order - for (int i = 0; i < boundsheets.size(); i++) { - Boundsheet bound = (Boundsheet) boundsheets.get(i); - streamer.addRecordAt(bound, insertLoc + i); - } - } - - /** - * add a deserialized boundsheet to this workbook. - * - * @param bound new (copied) sheet - * @param newSheetName new sheetname - * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * - * @boolean SSTPopulatedBoundsheet - the boundsheet has all of it's sst data saved off in LabelSST records. - * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. - * Do not use this if the data already exists in the SST, you are just causing bloat! - */ - public void addBoundsheet(Boundsheet bound, String origSheetName, String newSheetName, String origWorkBookName, boolean SSTPopulatedBoundsheet) { - bound.streamer = streamer; - boolean old_allowdupes = this.isSharedupes(); - this.setDupeStringMode(ALLOWDUPES); - - bound.mc.clear(); - bound.setWorkBook(this); - - // Check if sheetname already exists! - try { - while (this.getWorkSheetByName(newSheetName) != null) - newSheetName = newSheetName + "Copy"; - } catch (WorkSheetNotFoundException we) { - /* good !!! */ - } - bound.setSheetName(newSheetName); - - // get a hold of the lbplypos number that we will need for - // the new boundsheet - int recvecOffset = streamer.getRecVecSize() - 1; - XLSRecord x = null; - if (lastbound != null) { - try { // lastbound must be reset because other operations could - // alter - lastbound = this - .getWorkSheetByNumber(this.getNumWorkSheets() - 1); - x = (XLSRecord) lastbound.getSheetRecs() - .get(lastbound.getSheetRecs().size() - 1); - } catch (Exception e) { - } - } else if (countryRec != null) { - x = (XLSRecord) streamer.getRecordAt(countryRec.getRecordIndex()); - } else - x = this.lasteof; - // last record is a junkrec. We are going to move that down - // and put in the new BOF here - // TODO: recvecOffset position when no sheets?????? - if (x.getOpcode() != EOF) { - recvecOffset -= 1; - } - int newloc = x.offset; - // modify the boundsheet rec for its new location/info/name - int listenerpos = -1, newoffset = -1; - if (lastbound != null) { - listenerpos = lastbound.getRecordIndex(); - newoffset = lastbound.offset + lastbound.getLength() + 4; - // offset + reclen + headerlen - } else if (x != null) { - listenerpos = x.getRecordIndex() - 1; // account for +1 below - newoffset = x.offset + x.getLength() + 4; - } else { - listenerpos = recvecOffset - 1; - newoffset = streamer.getRecordAt(recvecOffset).getLength() + 4; - } - - // put the serialized recs from localrecs into the normal - // SheetRecs - bound.setLocalRecs(new FastAddVector()); // reset localrecs - List newrecs = bound.getSheetRecs(); - Bof newbof = (Bof) newrecs.get(0); - - newloc += newbof.getLength() + 4; - newbof.setOffset(newloc); - bound.setBOF(newbof); - this.addRecord(newbof, false); - - recvecOffset += 1; // move it past that last Eof - newoffset = newloc + newbof.getLength() + 4; - lastbound = bound; - // insert the actual boundsheet record into the recvec - streamer.addRecordAt(bound, listenerpos + 1); - - this.addRecord(bound, false); - - // modify the TabID record to reflect new sheet - tabs.addNewRecord(); - recvecOffset = newbof.getRecordIndex(); - - int tout = 0; - copying = true; - - // Add an externsheet ref for the new sheet - if (this.myexternsheet == null) - this.addExternsheet(); - try { - int sheetref = this.getNumWorkSheets() - 1; - myexternsheet.insertLocation(sheetref, sheetref); - } catch (Exception e) { - Logger.logWarn("Adding new sheetRef failed in addBoundsheet()" - + e.toString()); - } - - // update the chart references + add to wb - List chts = bound.getCharts(); - for (int i = 0; i < chts.size(); i++) { - Chart chart = (Chart) chts.get(i); // obviously algorithm has - // changed and chart is NOT - // removed :) [discovered by - // Shigeo/Infoteria/formatbroken273193.sce] - // // 20080702 KSC: since it's - // removed, don't inc index - chart.updateSheetRefs(bound.getSheetName(), origWorkBookName); - this.charts.add(chart); - } - - bound.lastObjId = 1; // see if resetting obj id helps in file open - // errors; if so, must reset Note obj id's as - // well ... - - /********** This loop handles Boundsheet records contained in the sheet level streamer, that is, not the valrecs *****/ - int numrecs = newrecs.size(); - for (int z = 1; z < numrecs; z++) { - XLSRecord xl = (XLSRecord) newrecs.get(z); - this.addRecord(xl, false); - if (DEBUGLEVEL > 5) - try { - Logger.logInfo("Copying: " + xl.toString() + ":" - + newoffset + ":" + xl.getLength()); - } catch (Exception e) { - } - if (xl instanceof Codename) { - Codename secretagent = (Codename) xl; // lol -nr - secretagent.setName(newSheetName); - } else if (xl instanceof Name) { - // Name records specify data ranges -- update to point to - // new sheet - Name n = (Name) xl; - int refnum = myexternsheet.getcXTI(); - n.setExternsheetRef(refnum); - } else if (xl instanceof Cf) { // must check Conditional Format - // formula refs and handle any - // external references - try { - updateFormulaPtgRefs(((Cf) xl) - .getFormula1(), origSheetName, newSheetName, origWorkBookName); - // NOTE: FORMULA2 can be null -- TODO: should check here - updateFormulaPtgRefs(((Cf) xl) - .getFormula2(), origSheetName, newSheetName, origWorkBookName); - } catch (Exception e) { - } - } else if (xl.getOpcode() == OBJ) { - ((Obj) xl).setObjId(bound.lastObjId++); - } else if (xl.getOpcode() == MSODRAWING - || (xl.getOpcode() == CONTINUE - && ((Continue) xl).maskedMso != null)) { // 20100510 - // KSC: - // handle - // masked - // mso's - MSODrawing mso; - if (xl.getOpcode() == MSODRAWING) - mso = (MSODrawing) xl; - else - mso = ((Continue) xl).maskedMso; - if (msodg == null) { - this.setMSODrawingGroup((MSODrawingGroup) MSODrawingGroup - .getPrototype()); - msodg.initNewMSODrawingGroup(); // generate and add required - // records for drawing - // records - msodg.addMsodrawingrec(mso); // only add when msodg is null - // b/c otherwise it's added - // via the addRecord - // statement above - } - if (mso.getImageIndex() > 0) { // add image bytes as well, if - // any - ImageHandle im = bound - .getImageByMsoIndex(mso.getImageIndex()); - int idx = msodg.addImage(im.getImageBytes(), im - .getImageType(), false); - bound.imageMap.put(im, Integer.valueOf(idx)); // 20100518 - // KSC: - // makes - // more - // sense? - // im.getImageIndex())); - // // add - // new image - // to map - // and link - // to actual - // imageIndex - // - moved - // from - // above - if (idx != mso.getImageIndex()) - mso.updateImageIndex(idx); - } - mso.setSPID(this.lastSPID); - msodg.setSpidMax(++this.lastSPID); - // resets drawing id's - necessarily correct? - // msodg.dirtyflag= true; // flag to reset SPIDs on write - } - xl.setOffset(newoffset); - tout += xl.getLength(); - newoffset += xl.getLength(); - } - if (msodg != null) {// Moved from above so don't udpate at every mso - // addition - // necessary? all mso sub-records on the sheet should have - // stayed the same ...this.updateMsodrawingHeaderRec(bound); - msodg.updateRecord(); - } - /*************** END handling of boundsheet streamer records *************************/ - - /*************** HANDLE Formats + PtgRefs in Cell Records ****************************/ - updateTransferedCellReferences(bound, origSheetName, origWorkBookName); - - // associate the records in the sheet - this.setSharedupes(old_allowdupes); - - if (SSTPopulatedBoundsheet) { - // bring over the sst - Sst sst = this.getSharedStringTable(); - BiffRec[] b = bound.getCells(); - for (int i = 0; i < b.length; i++) { - b[i].setWorkBook(this); - if (b[i].getOpcode() == XLSConstants.LABELSST) { - Labelsst s = (Labelsst) b[i]; - s.insertUnsharedString(sst); - } - } - } - - if (this.getNumWorkSheets() > 1) - bound.setSelected(false); - else - bound.setSelected(true); - - if (DEBUGLEVEL > 5) - Logger.logInfo("changesize for new boundsheet: " - + bound.getSheetName() + ": " + tout); - copying = false; - } - - /** - * traverses all rows and their associated cells in the newly transfered sheet, - * ensuring formula/cell references and format references are correctly transfered - * into the current workbook - * - * @param bound source sheet - */ - private void updateTransferedCellReferences(Boundsheet bound, String origSheetName, String origWorkBookName) { - HashMap localFonts = (HashMap) this.getFontRecsAsXML(); - List boundFonts = bound.getTransferFonts(); // ALL fonts in the source - // workbook - HashMap localXfs = (HashMap) this.getXfrecsAsString(); - List boundXfs = bound.getTransferXfs(); - // Set the workbook on all the cells - Row[] rows = bound.getRows(); - for (int i = 0; i < rows.length; i++) { - rows[i].setWorkBook(this); - if (rows[i].getIxfe() != this.getDefaultIxfe()) - transferFormatRecs(rows[i], localFonts, boundFonts, localXfs, boundXfs); // 20080709 - // KSC: - // handle - // default - // ixfe - // for - // row - Iterator rowcells = rows[i].getCells().iterator(); - Mulblank aMul = null; - short c = 0; - while (rowcells.hasNext()) { - BiffRec b = (BiffRec) rowcells.next(); - if (b.getOpcode() == MULBLANK) { - if (aMul == b) - c++; - else { - aMul = (Mulblank) b; - c = (short) aMul.getColFirst(); - } - aMul.setCurrentCell(c); - } - b.setWorkBook(this); // Moved to before updateFormulaPtgRefs - // [BugTracker 1434] - if (b instanceof Formula) { // Examine Ptg Refs to handle - // external sheet references not - // contained in this workbook - updateFormulaPtgRefs((Formula) b, origSheetName, bound - .getSheetName(), origWorkBookName); - if (((Formula) b).shared != null) - ((Formula) b).shared.setWorkBook(this); - - } - // 20080226 KSC: transfer format, fonts and xf here instead - // of populateWorkbookWithRemoteData() - transferFormatRecs(b, localFonts, boundFonts, localXfs, boundXfs); - } - } - // 20080226 KSC: handle xf's for columns - for (Colinfo co : bound.getColinfos()) { - transferFormatRecs(co, localFonts, boundFonts, localXfs, boundXfs); - } - List c = bound.getCharts(); - for (int i = 0; i < c.size(); i++) { - Chart cht = (Chart) c.get(i); - ArrayList fontrefs = cht.getFontxRecs(); - for (int x = 0; x < fontrefs.size(); x++) { - Fontx fontx = (Fontx) fontrefs.get(x); - int fid = fontx.getIfnt(); - if (fid > 3) { - fid = bound.translateFontIndex(fid, localFonts); - fontx.setIfnt(fid); - } - } - } - } - - /** - * examine all Ptg's referenced by this formula, looking for hanging or missing sheet references - * if found, sets sheet reference to the current sheet (TODO: a better way?) - * - * @param f Formula Rec - */ - private void updateFormulaPtgRefs(Formula f, String origSheetName, String newSheetName, String origWorkBookName) { - try { - if (f == null) - return; // 20100222 KSC - f.populateExpression(); - Ptg[] p = f.getCellRangePtgs(); - for (int k = 0; k < p.length; k++) { - if (p[k] instanceof PtgRef) { - PtgRef ptg = (PtgRef) p[k]; - try { - if (!(ptg instanceof PtgArea3d) || ((PtgArea3d) ptg) - .getFirstSheet() - .equals(((PtgArea3d) ptg).getLastSheet())) { - String sheetName = ptg.getSheetName(); - if (sheetName.equals(origSheetName)) - ptg.setSheetName(newSheetName); - ptg.addToRefTracker(); - /* - * changed to use above. don't understand this: - * if (!sheetName.equals(origSheetName)) { - * this.getWorkSheetByName(ptg.getSheetName()); - * ptg.setSheetName(newSheetName); - * } else - * ptg.setSheetName(newSheetName); - */ - } else { // uncommon case of two sheet range - PtgArea3d pref = (PtgArea3d) ptg; - // this.getWorkSheetByName(pref.getFirstPtg().getSheetName()); - // don't understand this - // this.getWorkSheetByName(pref.getLastPtg().getSheetName()); - ptg.setLocation(ptg.toString()); // reset ixti if - // nec. - } - } catch (WorkSheetNotFoundException we) { - Logger.logWarn("External Reference encountered upon updating formula references: Worksheet Reference Found: " - + ptg.getSheetName()); - ptg.setExternalReference(origWorkBookName); - } - } else if (p[k] instanceof PtgExp) { - PtgExp ptgexp = (PtgExp) p[k]; - try { - Ptg[] pe = ptgexp.getConvertedExpression(); // will fail - // if - // ShrFmla - // hasn't - // been - // input yet - for (int j = 0; j < pe.length; j++) { - if (pe[j] instanceof PtgRef) { - PtgRef ptg = (PtgRef) pe[j]; - try { - if (ptg instanceof PtgArea3d) { // PtgRef3d, - // etc. - this.getWorkSheetByName(ptg - .getSheetName()); - ptg.setLocation(ptg.toString()); // reset - // ixti - // if - // nec. - } - // otherwise, we're good - } catch (WorkSheetNotFoundException we) { - Logger.logWarn("External References Not Supported: UpdateFormulaReferences: External Worksheet Reference Found: " - + ptg.getSheetName()); - ptg.setExternalReference(origWorkBookName); - } - } - } - } catch (Exception e) { - // if links to "main" ShrFmla, won't be set yet and will - // give exception - see Shrfmla WorkBook.addRecord - } - } - } - } catch (Exception e) { - Logger.logErr("WorkBook.updateFormulaRefs: error parsing expression: " - + e); - } - } - - /** - * given a record in an previously external workbook, ensure that xf and font records - * are correctly input into the current workbook and that the pointers are correctly updated - * - * @param b BiffRec - * @param localFonts HashMap of string version of all fonts, font nums in current workbook - * @param boundFonts List of string version of all fonts, font nums in external workbook - * @param localXfs HashMap of string version of all xfs, xf nums in current workbook - * @param boundXfs List of string version of all xfs, xf nums in external workbook - */ - private void transferFormatRecs(BiffRec b, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { - int oldXfNum = b.getIxfe(); - int localNum = this - .transferFormatRecs(oldXfNum, localFonts, boundFonts, localXfs, boundXfs); - if (localNum != -1) - b.setIxfe(localNum); - } - - /** - * given a record in an previously external workbook, ensure that xf and font records - * are correctly input into the current workbook and that the pointers are correctly updated - * - * @param b BiffRec - * @param localFonts HashMap of string version of all fonts, font nums in current workbook - * @param boundFonts List of string version of all fonts, font nums in external workbook - * @param localXfs HashMap of string version of all xfs, xf nums in current workbook - * @param boundXfs List of string version of all xfs, xf nums in external workbook - */ - private int transferFormatRecs(int oldXfNum, HashMap localFonts, List boundFonts, HashMap localXfs, List boundXfs) { - int localNum = -1; - if (boundXfs.size() > oldXfNum) {// if haven't populatedForTransfer i.e. - // haven't opted to transfer formats - // ... - Xf origxf = (Xf) boundXfs.get(oldXfNum); // clone xf so modifcations - // don't affect original - if (origxf != null) { - /** FONT **/ - // must handle font first in order to create xf below - // see if referenced xf + fonts are already in workbook; if - // not, add - int localfNum; - // check to see if the font needs to be added - int fnum = origxf.getIfnt(); - if (fnum > 3) - fnum--; - Font thisFont = (Font) boundFonts.get(fnum); - String xmlFont = "<" + thisFont.getXML() + "/>"; - Object fontNum = localFonts.get(xmlFont); - if (fontNum != null) { // then get the fontnum in this book - localfNum = ((Integer) fontNum).intValue(); - } else { // it's a new font for this workbook, add it in - localfNum = this.insertFont(thisFont) + 1; - localFonts.put(xmlFont, Integer.valueOf(localfNum)); - } - - /** XF **/ - Xf localxf = FormatHandle - .cloneXf(origxf, origxf.getFont(), this); // clone xf so - // modifcations - // don't - // affect - // original - // input "local" versions of format and font - - /** FORMAT **/ - Format fmt = origxf.getFormat(); // number format - is null if - // format is general ... - if (fmt != null) // add if necessary - localxf.setFormatPattern(fmt.getFormat()); // adds new - // format - // pattern if - // not found - localxf.setFont(localfNum); - - // now check out to see if xf needs to be added - String xmlxf = localxf.toString(); - Object xfNum = localXfs.get(xmlxf); - if (xfNum == null) { // insert it into the book - localNum = this.insertXf(localxf); - localXfs.put(xmlxf, Integer.valueOf(localNum)); - } else // already exists in the destination - localNum = ((Integer) xfNum).intValue(); - - } - } - return localNum; - } - - public void setStringEncodingMode(int mode) { - this.getSharedStringTable().setStringEncodingMode(mode); - } - - public void setDupeStringMode(int mode) { - if (mode == ALLOWDUPES) - this.setSharedupes(false); - else if (mode == SHAREDUPES) - this.setSharedupes(true); - } - - /** - * Returns a Chart Handle - * - * @return ChartHandle a Chart in the WorkBook - */ - public Chart getChart(String chartname) throws ChartNotFoundException { - AbstractList cv = this.getChartVect(); - Chart cht = null; - // Get by MSODG Drawing Name - for (int x = 0; x < cv.size(); x++) { - cht = (Chart) cv.get(x); - MSODrawing titlemso = cht.getMsodrawobj(); - if (titlemso != null) { - String mson = titlemso.getName(); // shapeName; - if (mson.equalsIgnoreCase(chartname)) - return cht; - } - } - boolean untitled = chartname.equals("[Untitled]"); - // Try to get by title - for (int x = 0; x < cv.size(); x++) { - cht = (Chart) cv.get(x); - String cname = cht.getTitle(); - if (cname.equalsIgnoreCase(chartname)) - return cht; - else if (untitled && cname.equals("")) - return cht; - } - throw new ChartNotFoundException(chartname); - } - - /** - * removes the desired chart from the list of charts - * - * @param chartname - * @throws ChartNotFoundException - */ - public void removeChart(String chartname) throws ChartNotFoundException { - AbstractList cv = this.getChartVect(); - Chart cht = null; - for (int x = 0; x < cv.size(); x++) { - cht = (Chart) cv.get(x); - if (cht.getTitle().equalsIgnoreCase(chartname)) { - cv.remove(x); - return; - } - } - throw new ChartNotFoundException(chartname); - } - - /** - * NOT 100% IMPLEMENTE YET - * creates the initial records for a Pivot Cache - *
                      A Pivot Cache identifies the data used in a Pivot Table - *
                      NOTE: only SHEET cache sources are supported at this time - * - * @param ref String reference: either reference or named range - * @param sheetName String sheetname - * @param cacheid if > 0, the desired cacheid (useful only in OOXML parsing) - * @return int cacheid - */ - public int addPivotStream(String ref, String sheetName, int sid) { - // in wb substream, DIRECTLY AFTER STYLE records: - // STYLE/STYLEEX [TableStyle TableStyleElement] [Palette] - // [ClrtClient] - if (sid < 0) - sid = 0; // initial cache id if none already present - List records = this.getStreamer().records; - int z = -1; - for (int i = records.size() - 1; i > 0 && z == -1; i--) { - int opcode = ((BiffRec) records.get(i)).getOpcode(); - if (opcode == SXADDL) { // find last cache id and increment - /* - * while (i > 0 && opcode!=SXSTREAMID) - * opcode= ((BiffRec) records.get(i--)).getOpcode(); - * if (opcode==SXSTREAMID) { - * sid= ((SxStreamID) records.get(i+1)).getStreamID() + 1; - * } - */ - z = i + 1; - } else if (opcode == 4188) // ClrtClient - z = i + 1; - else if (opcode == PALETTE) - z = i + 1; - else if (opcode == 2192) // TableStyleElement - z = i + 1; - else if (opcode == 2194) // StyleEx - z = i + 1; - else if (opcode == STYLE) // Style - z = i + 1; - } - - TableStyles tx = (TableStyles) TableStyles.getPrototype(); // see if - // this is - // really - // necessary - // ... - this.getStreamer().addRecordAt(tx, z++); - SxStreamID sxid = (SxStreamID) SxStreamID.getPrototype(); - this.getStreamer().addRecordAt(sxid, z++); - this.ptstream.add(sxid); // Pivot Cache - - sxid.setStreamID(sid); // cache id - this.getStreamer().records - .addAll(z, sxid.addInitialRecords(this, ref, sheetName)); - - return sid; - } - - /** - * adds the Pivot Cache Directory Storage +Stream records necessary to - * define the pivot cache (==pivot table data) for pivot table(s) - *
                      NOTE: at this time only 1 pivot cache is supported - * - * @param ref Cell Range which identifies pivot table data range - * @param wbh - * @param sId Stream or cachid Id -- links back to SxStream set of records - */ - public void addPivotCache(String ref, WorkBookHandle wbh, int sId) { - if (ptcache == null) { - ptcache = new PivotCache(); - ptcache.createPivotCache(factory.myLEO - .getDirectoryArray(), wbh, ref, sId); - } - } - - /** - * returns the start of the stream defining the desired pivot cache - * - * @param cacheid - * @return - */ - public SxStreamID getPivotStream(int cacheid) { - // int z= 0; - for (int i = 0; i < ptstream.size(); i++) { - int sid = ((SxStreamID) ptstream.get(i)).getStreamID(); - if (sid == cacheid) - // if (z++==cacheid) - return (SxStreamID) ptstream.get(i); - } - /* - * List records= this.getStreamer().records; - * for (int i= 0; i < records.size(); i++) { - * int opcode= ((BiffRec) records.get(i)).getOpcode(); - * if (opcode==SXSTREAMID) { - * int sid= ((SxStreamID) records.get(i)).getStreamID() + 1; - * if (sid==cacheid) - * return (SxStreamID) records.get(i); - * } - * } - */ - return null; - } - - /** - * @return - */ - public AbstractList getHlinklookup() { - return hlinklookup; - } - - /** - * @return - */ - public AbstractList getMergecelllookup() { - return mergecelllookup; - } - - public void addIndirectFormula(Formula f) { - indirectFormulas.add(f); - } - - /** - * Initialize the indirect functions in this workbook by calculating the formulas - */ - public void initializeIndirectFormulas() { - Iterator i = indirectFormulas.iterator(); // contains all INDIRECT - // funcvars + params - while (i.hasNext()) { - Formula f = (Formula) i.next(); - f.calculateIndirectFunction(); - } - indirectFormulas = new ArrayList(); // clear out - } - - /** - * Inserts an externsheet into the recvec, provided one does not yet exist. - * also calls add supBook - */ - public void addExternsheet() { - if (myexternsheet == null) { - int numsheets = this.getNumWorkSheets(); - Supbook sb = (Supbook) Supbook.getPrototype(numsheets); - // put it in after the last boundsheet record - try { - Boundsheet b = this.getWorkSheetByNumber(numsheets - 1); - int loc = b.getRecordIndex() + 1; - if (streamer.getRecordAt(loc).getOpcode() == COUNTRY) - loc++; - streamer.addRecordAt(sb, loc); // 20080306 KSC: do first 'cause - // externsheet now references - // global sb store - this.addRecord(sb, false); - Externsheet ex = (Externsheet) Externsheet - .getPrototype(0, 0, this); - streamer.addRecordAt(ex, loc + 1);// 20080306 KSC: must inc loc - // since now inserting after - // sb - this.addRecord(ex, false); - myexternsheet = ex; - } catch (WorkSheetNotFoundException e) { - Logger.logWarn("WorkBook.addExternSheet() locating Sheet for adding Externsheet failed: " - + e); - } - - } - } - - /** - * Sets the OpenXLS calculation mode for the workbook. - * - * @param CalcMode - * @see WorkBookHandle.setFormulaCalculationMode() - */ - public void setCalcMode(int mode) { - CalcMode = mode; - } - - /** - * Sets the calculation mode for the workbook. - * - * @param CalcMode - * @see WorkBookHandle.setFormulaCalculationMode() - */ - public int getCalcMode() { - return CalcMode; - } - - /** - * @return Returns the xfrecs. - */ - public AbstractList getXfrecs() { - return xfrecs; - } - - /** - * Return the font records - */ - public AbstractList getFontRecs() { - return fonts; - } - - /** - * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. - * These are used as a comparitor to determine if additional xf's need to be brought in or - * not and to give the new XF number if the xf exists. - *

                      - * Changed 20080226 KSC: to use XF toString as XML is limited in format, toString is more complete - * - * @return map (String XfXml, Integer xfLookup) - */ - public Map getXfrecsAsString() { - Map retMap = new HashMap(); - for (int xfNum = 1; xfNum < this.getNumXfs(); xfNum++) { - Xf x = this.getXf(xfNum); - String xml = x.toString(); - retMap.put(xml, Integer.valueOf(xfNum)); - } - return retMap; - } - - /** - * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. - * These are used as a comparitor to determine if additional xf's need to be brought in or - * not and to give the new XF number if the xf exists. - * - * @return map (String XfXml, Integer xfLookup) - */ - public Map getFontRecsAsXML() { - Map retMap = new HashMap(); - for (int i = this.fonts.size() - 1; i >= 0; i--) { - Font fnt = (Font) this.fonts.get(i); - String xml = "<" + fnt.getXML() + "/>"; - retMap.put(xml, Integer.valueOf(fnt.getIdx())); - } - return retMap; - } - - /** - * @return Returns the lastbound. - */ - public Boundsheet getLastbound() { - return lastbound; - } - - /** - * @param lastbound The lastbound to set. - */ - public void setLastbound(Boundsheet lastbound) { - this.lastbound = lastbound; - } - - /** - * add formula to book and init the ptgs - * - * @param rec - */ - public void addFormula(Formula rec) { - this.formulas.add(rec); - String shn = rec.getSheet().getSheetName() + "!" + rec.getCellAddress(); - this.formulashash.put(shn, rec); - } - - public boolean isSharedupes() { - return sharedupes; - } - - public void setSharedupes(boolean sharedupes) { - this.sharedupes = sharedupes; - } - - /** - * Returns whether this book uses the 1904 date format. - * - * @deprecated Use {@link #getDateFormat()} instead. - */ - @Deprecated - public boolean is1904() { - return this.dateFormat == DateConverter.DateFormat.LEGACY_1904; - } - - /** - * Gets the date format used by this book. - */ - public DateConverter.DateFormat getDateFormat() { - return this.dateFormat; - } - - public ReferenceTracker getRefTracker() { - return refTracker; - } - - // OOXML Additions - private boolean isExcel2007 = false; - - /** - * set truth of "Is Excel 2007" - * true increases maximums of base storage, etc. - * - * @param b - */ - public void setIsExcel2007(boolean b) { - isExcel2007 = b; - } - - /** - * returns truth of "Excel 2007" format - */ - public boolean getIsExcel2007() { - return isExcel2007; - } - - /** - * returns the workbook codename used by vba macros OOXML-specific - */ - public String getCodename() { - return ooxmlcodename; - } - - /** - * returns true if the default language selected in Excel is one of - * the DBCS (Double-Byte Code Set) languages - *
                      - * The languages that support DBCS include - *
                      - * Japanese, Chinese (Simplified), Chinese (Traditional), and Korean - *

                      -     * Language        Country code    Countries/regions
                      -     * -------------------------------------------------------------
                      -     *
                      -     * Arabic                966       (Saudi Arabia)
                      -     * Czech                 42        (Czech Republic)
                      -     * Danish                45        (Denmark)
                      -     * Dutch                 31        (The Netherlands)
                      -     * English               1         (The United States of America)
                      -     * Farsi                 98        (Iran)
                      -     * Finnish               358       (Finland)
                      -     * French                33        (France)
                      -     * German                49        (Germany)
                      -     * Greek                 30        (Greece)
                      -     * Hebrew                972       (Israel)
                      -     * Hungarian             36        (Hungary)
                      -     * Indian                91        (India)
                      -     * Italian               39        (Italy)
                      -     * Japanese              81        (Japan)
                      -     * Korean                82        (Korea)
                      -     * Norwegian             47        (Norway)
                      -     * Polish                48        (Poland)
                      -     * Portuguese (Brazil)   55        (Brazil)
                      -     * Portuguese            351       (Portugal)
                      -     * Russian               7         (Russian Federation)
                      -     * Simplified Chinese    86        (People's Republic of China)
                      -     * Spanish               34        (Spain)
                      -     * Swedish               46        (Sweden)
                      -     * Thai                  66        (Thailand)
                      -     * Traditional Chinese   886       (Taiwan)
                      -     * Turkish               90        (Turkey)
                      -     * Urdu                  92        (Pakistan)
                      -     * Vietnamese            84        (Vietnam)
                      -     * 
                      - * - * @return boolean - */ - public boolean defaultLanguageIsDBCS() { - return (this.defaultLanguage == 81 || this.defaultLanguage == 886 - || this.defaultLanguage == 86 || this.defaultLanguage == 82); - // PROBLEM WITH THIS: POSSIBLE TO BE SET AS DBCS DEFAULT - // LANGUAGE - // BUT HAVE NON-DBCS TEXT or VISA VERSA - - /* - * In a double-byte character set, some characters require - * two bytes, - * while some require only one byte. - * The language driver can distinguish between these two - * types of characters by designating - * some characters as "lead bytes." - * A lead byte will be followed by another byte (a - * "tail byte") to create a - * Double-Byte Character (DBC). - * The set of lead bytes is different for each language. - * - * Lead bytes are always guaranteed to be extended - * characters; no 7-bit ASCII characters - * can be lead bytes. - * The tail byte may be any byte except a NULL byte. - * The end of a string is always defined as the first NULL - * byte in the string. - * Lead bytes are legal tail bytes; the only way to tell if - * a byte is acting as a - * lead byte is from the context. - */ - } - - /** - * sets the workbook codename used by vba macros OOXML-specific - * - * @param s - */ - public void setCodename(String s) { - ooxmlcodename = s; - } // TODO: input into Codename record - - /** - * sets the first sheet (int) in the workbook OOXML-specific - *

                      - * naive implementaiton does not account for hidden sheets - *

                      - * Mar 15, 2010 - * - * @param f - */ - public void setFirstSheet(int f) { - firstSheet = f; - } - - /** - * returns the first non-hidden - *

                      - * sheet (int) in the workbook OOXML-specific - *

                      - * Mar 15, 2010 - * - * @return - */ - public int getFirstSheet() { - try { - if (!getWorkSheetByNumber(firstSheet).getHidden()) - return this.firstSheet; - } catch (Exception x) { - } - - // first sheet is hidden -- fix - for (int t = 0; t < this.getNumWorkSheets(); t++) { - try { - if (!getWorkSheetByNumber(t).getHidden()) { - firstSheet = t; - return firstSheet; - } - } catch (Exception x) { - } - } - // all else failed - return firstSheet; - } - - /** - * sets the list of dxf's (incremental style info) (int) OOXML-specific - */ - public void setDxfs(ArrayList dxfs) { - this.dxfs = dxfs; - } - - /** - * returns the list of dxf's (incremental style info) (int) OOXML-specific - */ - public ArrayList getDxfs() { - return dxfs; - } - - /** - * Returns all strings that are in the SharedStringTable for this workbook. The SST contains - * all standard string records in cells, but may not include such things as strings that are contained - * within formulas. This is useful for such things as full text indexing of workbooks - * - * @return Strings in the workbook. - */ - public String[] getAllStrings() { - ArrayList al = this.getSharedStringTable().getAllStrings(); - String[] s = new String[al.size()]; - s = (String[]) al.toArray(s); - return s; - } - - /** - * set the pivot cache pointer - * - * @param pc initialized pivot cache - */ - public void setPivotCache(PivotCache pc) { - ptcache = pc; - } - - /** - * return the pivot cache, if any - * - * @return - */ - public PivotCache getPivotCache() { - return ptcache; - } - - /** - * clear out ALL sheet object refs - */ - public void closeSheets() { - for (int i = boundsheets.size() - 1; i > 0; i--) { - Boundsheet b = (Boundsheet) boundsheets.get(i); - if (b.streamer != null) { // do separately because may call - // boundsheet close - b.streamer.close(); - b.streamer = null; - } - b.close(); - if (tabs != null) - tabs.removeRecord(); - } - Object[] recs = this.getStreamer().getBiffRecords(); - boolean resetVars = (recs[0] != null); - boundsheets.clear(); - for (int i = 0; i < formulas.size(); i++) { - Formula f = (Formula) formulas.get(i); - f.close(); - } - this.formulas.clear(); - if (this.refTracker != null) - this.refTracker.clearCaches(); - this.formulashash.clear(); - // TODO: handle - this.indirectFormulas.clear(); - this.charts.clear(); - this.chartTemp.clear(); - if (firstBOF != null) { - firstBOF.close(); - this.firstBOF = null; - } - if (lasteof != null) { - lasteof.close(); - lasteof = null; - } - if (resetVars) { // just clearing out sheets instead of closing workbook - // reset lasteof for possible new insertion of sheets (if - // not removing workbook) ... - int i = recs.length - 1; - while (i > 0 && lasteof == null) { - if (recs[i] != null) { - if (((BiffRec) recs[i]).getOpcode() == EOF) - lasteof = (Eof) recs[i]; - } - i--; - } - } - if (lastBOF != null) { - lastBOF.close(); - lastBOF = null; - } - if (resetVars) { // just clearing out sheets instead of closing workbook - if (((BiffRec) recs[0]).getOpcode() == BOF) {// should!! - lastBOF = (Bof) recs[0]; - firstBOF = (Bof) recs[0]; - } - } - if (lastbound != null) { - lastbound.close(); - this.lastbound = null; - } - if (lastFormula != null) { - lastFormula.close(); - this.lastFormula = null; - } - this.workSheets.clear(); - } - - protected void closeRecords() { - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - closeSheets(); - - if (this.isExcel2007) { - try { - String externalDir = OOXMLAdapter - .getTempDir(this.getFactory().getFileName()); - OOXMLAdapter.deleteDir(new File(externalDir)); - } catch (Exception e) { - } - } - contHandler.close(); - contHandler = new ContinueHandler(this); - - // clear out array list references - Iterator ii = bookNameRecs.keySet().iterator(); - while (ii.hasNext()) { - Name n = (Name) bookNameRecs.get(ii.next()); - n.close(); - } - this.bookNameRecs.clear(); - for (int i = 0; i < xfrecs.size(); i++) { - Xf x = (Xf) xfrecs.get(i); - x.close(); - } - this.xfrecs.clear(); - this.formatCache.clear(); - this.formatlookup.clear(); - this.formats.clear(); - this.fonts.clear(); - if (this.dxfs != null) - this.dxfs.clear(); - for (int i = 0; i < msodgMerge.size(); i++) { - MSODrawingGroup m = (MSODrawingGroup) msodgMerge.get(i); - m.close(); - } - this.msodgMerge.clear(); - if (this.msodg != null) { - msodg.close(); - msodg = null; - } - // TODO: handle - this.mergecelllookup.clear(); - this.hlinklookup.clear(); - this.externalnames.clear(); - for (int i = 0; i < names.size(); i++) { - Name n = (Name) names.get(i); - n.close(); - } - this.names.clear(); - for (int i = 0; i < indexes.size(); i++) { - Index ind = (Index) indexes.get(i); - ind.close(); - } - this.indexes.clear(); - if (lastidx != null) { - lastidx.close(); - lastidx = null; - } - if (this.stringTable != null) { - this.stringTable.close(); - this.stringTable = null; - } - - if (countryRec != null) { - this.countryRec.close(); - this.countryRec = null; - } - if (win1 != null) { - win1.close(); - win1 = null; - } - - if (this.drh != null) { - this.drh.close(); - drh = null; - } - - // integration point for subclasses - this.closeRecords(); - - this.contHandler = new ContinueHandler(this); - if (this.currai != null) { - this.currai.close(); - this.currai = null; - } - if (calcmoderec != null) { - calcmoderec.close(); - calcmoderec = null; - } - this.currchart = null; - this.currdrw = null; - if (this.protector != null) { - this.protector.close(); - this.protector = null; - } - if (this.myexternsheet != null) { - this.myexternsheet.close(); - this.myexternsheet = null; - } - if (this.refTracker != null) { - this.refTracker.close(); - this.refTracker = null; - } - if (tabs != null) { - tabs.close(); - tabs = null; - } - // TODO: deal - this.factory = null; - streamer = createByteStreamer(); - if (xl2k != null) - ((XLSRecord) xl2k).close(); - xl2k = null; - - } - - public Color[] getColorTable() { - return colorTable; - } - - public void setColorTable(Color[] clrtable) { - colorTable = clrtable; - } - - public int getDefaultIxfe() { - return defaultIxfe; - } - - public void setDefaultIxfe(int defaultIxfe) { - this.defaultIxfe = defaultIxfe; - } - - public int getCalcId() { - // TODO Auto-generated method stub - return 0; - } - - public void setCalcId(int i) { - // TODO Auto-generated method stub - - } - -} diff --git a/src/main/java/io/starter/formats/XLS/WorkBook.kt b/src/main/java/io/starter/formats/XLS/WorkBook.kt new file mode 100644 index 0000000..f21640f --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/WorkBook.kt @@ -0,0 +1,4040 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.WorkBookException +import io.starter.OpenXLS.* +import io.starter.formats.OOXML.Theme +import io.starter.formats.XLS.charts.Ai +import io.starter.formats.XLS.charts.Chart +import io.starter.formats.XLS.charts.Fontx +import io.starter.formats.XLS.charts.GenericChartObject +import io.starter.formats.XLS.formulas.* +import io.starter.toolkit.FastAddVector +import io.starter.toolkit.Logger + +import java.awt.Color +import java.io.* +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import java.util.* + +/** + *

                      + * The WorkBook record represents an XLS workbook substream containing worksheets and associated records.
                      + *
                      +
                      * + * + * @see WorkBook + * + * @see Boundsheet + * + * @see Index + * + * @see Dbcell + * + * @see Row + * + * @see Cell + * + * @see XLSRecord + */ + + class WorkBook:Serializable, XLSConstants, Book { + +/** + * Gets the date format used by this book. + */ + var dateFormat:DateConverter.DateFormat = DateConverter.DateFormat.LEGACY_1900 +internal set + +private var DEBUGLEVEL = 0 +private var bofct = 0 +private var eofct = 0 +private var indexnum = 0 + var defaultIxfe = 15 +/** + * Sets the calculation mode for the workbook. + * + * @param CalcMode + * @see WorkBookHandle.setFormulaCalculationMode + */ + /** + * Sets the OpenXLS calculation mode for the workbook. + * + * @param CalcMode + * @see WorkBookHandle.setFormulaCalculationMode + */ + var calcMode = XLSConstants.CALCULATE_AUTO +private var defaultLanguage = 0 // default + // language + // code + // for + // the + // current + // workbook + private var copying = false + var isSharedupes = false +/** + * @return Returns the xfrecs. + */ + val xfrecs:AbstractList<*> = Vector() +private val indexes = Vector() +private val names = Vector() // ALL + // of + // the + // names + // (including + // worksheet + // scoped, + // etc) + private val orphanedPtgNames = Vector() +private val externalnames = Vector() +internal var formulas:AbstractList<*> = FastAddVector() +/** + * Return the font records + */ + val fontRecs:AbstractList<*> = Vector() + var colorTable:Array + +/** + * The list of Format records indexed by format ID. + */ + private val formats = TreeMap() + val chartVect:AbstractList<*> = Vector() +/** + * OOXML-specific + */ + private val ooxmlObjects = Vector() // stores + // OOXML + // objects + // external + // to + // workbook + // e.g. + // oleObjects, + /** + * returns the workbook codename used by vba macros OOXML-specific + */ + /** + * sets the workbook codename used by vba macros OOXML-specific + * + * @param s + */ + var codename:String? = null // stores + // TODO: input into Codename record + // OOXML + // codename + /** + * returns the list of dxf's (incremental style info) (int) OOXML-specific + */ + /** + * sets the list of dxf's (incremental style info) (int) OOXML-specific + */ + var dxfs:ArrayList<*>? = null // 20090622 + // KSC: + // stores + // dxf's + // (incremental + // style + // info) + // per + // workbook + /** + * returns the first non-hidden + * + * + * sheet (int) in the workbook OOXML-specific + * + * + * Mar 15, 2010 + * + * @return + */ + /** + * sets the first sheet (int) in the workbook OOXML-specific + * + * + * naive implementaiton does not account for hidden sheets + * + * + * Mar 15, 2010 + * + * @param f + */ + // first sheet is hidden -- fix + // all else failed + var firstSheet = 0 +get() { +try +{ +if (!getWorkSheetByNumber(field).hidden) +return field +} +catch (x:Exception) {} + +for (t in 0 until this.numWorkSheets) +{ +try +{ +if (!getWorkSheetByNumber(t).hidden) +{ +this.firstSheet = t +return field +} +} +catch (x:Exception) {} + +} +return field +} // specifies + // first + // sheet + // (ooxml) + /** + * return the OOXML theme for this workbook, if any + * + * @return + */ + /** + * sets the OOXML theme for this 2007 verison workbook + * + * @param t + */ + var theme:Theme? = null + + // Reference Tracking + var refTracker:ReferenceTracker? = ReferenceTracker() +private set + + // various + /** + * returns the Vector of Boundsheets + */ + val sheetVect:AbstractList<*> = Vector() // TODO: + // remove + // this + // variable? + // its + // duplicated + // in + // workSheets + /** + * @return + */ + val hlinklookup:AbstractList<*> = Vector( +20) +/** + * @return + */ + val mergecelllookup:AbstractList<*> = Vector( +20) + + /* + * Indirect formulas that need to be calculated after load + * for reftracker + */ + private var indirectFormulas:AbstractList<*> = ArrayList() +private val supBooks = Vector() + + // We should consider using an ordered collections class for + // some of these? + // an enumeration of worksheets for instance will not always + // be in the same + // order, causing tests to fail... + private val workSheets = Hashtable( +100, .950f) +private val bookNameRecs = HashMap() +private val formulashash = Hashtable() + +/** + * Maps number format patterns to their IDs. + */ + private val formatlookup = Hashtable( +30) + +private val ptViews = Hashtable( +20) // TODO: + // move + // to + // sheet + // - + // sheet-level + // pivot + // table + // view + // recordss + private val ptstream = ArrayList() // wb-level + // pivot + // cache + // definitions + // usually + // only + // 1 + /** + * return the pivot cache, if any + * + * @return + */ + /** + * set the pivot cache pointer + * + * @param pc initialized pivot cache + */ + var pivotCache:PivotCache? = null // if + // has + // pivot + // tables + // this + // is + // the + // one + // and + // only + // pivot + // cache + + /** + * retrieve the format cache - links string vers. of xf to xf rec + * used for resusing xf's + * + * @return + * @see FormatHandle.updateXf + */ + var formatCache = HashMap() + +/** + * set the last processed Index record + */ + internal var lastINDEX:Index? = null +private set + var sharedStringTable:Sst? = null +internal set +private var lastBOF:Bof? = null +private var myexternsheet:Externsheet? = null +private var firstBOF:Bof? = null +/** + * Get a substream by name. + */ + override var streamer = createByteStreamer() +private set + var tabID:TabID? = null +private set +private var win1:Window1? = null +private var calcmoderec:CalcMode? = null // determines + // recalculation + // mode + // for + // workbook + // - + // Manual, + // Auto + // ... + private var drh:DefaultRowHeight? = null +private var currchart:Chart? = null +private var currai:Ai? = null +private var myADDINSUPBOOK:Supbook? = null // for + // external + // names + // SUPBOOK + /** + * get a handle to the factory + */ + /** + * get a handle to the Reader for this + * WorkBook. + */ + override var factory:WorkBookFactory? = null +/** + * get a handle to the ContinueHandler + */ + override var continueHandler = ContinueHandler( +this) +private set +private var usersview:Usersviewbegin? = null +private var lasteof:Eof? = null +private var xl2k:BiffRec? = null + var msodgMerge:AbstractList<*> = Vector() + var msodg:MSODrawingGroup? = null + var lastSPID = 1024 // 20071030 + // last + // or + // next + // SPID + // (= + // shape + // ID + // or + // image + // ID); + // incremented + // upon + // new + // images + // ... + // appropriate + // to + // store + // at + // book + // level + // (?) + private var currdrw:MSODrawing? = null + +private var protector:BookProtectionManager? = null + +/** + * Gets this sheet's SheetProtectionManager. + */ + val protectionManager:BookProtectionManager +get() { +if (protector == null) +protector = BookProtectionManager(this) +return protector +} + +/** + * Get the MsoDrawingGroup for this workbook + * + * @return msodrawinggroup + */ + /** + * Set the msodrawinggroup for this workbook + * + * @param msodg + */ + var msoDrawingGroup:MSODrawingGroup? +get() =msodg +set(msodg) { +this.msodg = msodg +this.msodg!!.workBook = this +this.msodg!!.streamer = this.streamer +} + +/** + * Return some useful statistics about the WorkBook + * + * @return + */ + val stats:String +get() =getStats(false) + + val xlsVersionString:String +get() =lastBOF!!.xlsVersionString + + val charts:Array +get() { +val chts = arrayOfNulls(this.chartVect.size) +return chartVect.toTypedArray() as Array +} + +/** + * return all pivot table views (==Sxview records) + *

                      SxView is the top-level record of a Pivot Table + * as distinct from the PivotCache (stored data source in a LEOFile Storage) + * and PivotTable Stream (SxStream top-level record) + * + * @return + */ + val allPivotTableViews:Array +get() { +val sv = arrayOfNulls(ptViews.size) +val x = ptViews.elements() +var t = 0 +while (x.hasMoreElements()) +{ +sv[t++] = x.nextElement() as Sxview +} +return sv +} + + val nPivotTableViews:Int +get() =ptViews.size + +/** + * get the Externsheet + */ + val externSheet:Externsheet? +get() { +if (myexternsheet == null) +addExternsheet() +return myexternsheet +} + +/** + * Get a string array of external names + * + * @return externalNames + */ + val externalNames:Array +get() { +val n = arrayOfNulls(externalnames.size) +externalnames.toTypedArray() +return n +} + +/** + * Get a collection of all names in the workbook + */ + val workbookScopedNames:Array +get() { +val a = ArrayList(this.bookNameRecs.values) +val n = arrayOfNulls(a.size) +a.toTypedArray() +return n +} + +/** + * returns the List of Formulas in the book + * + * @return + */ + val formulaList:List<*> +get() =formulas + +/** + * Returns the recalculation mode for the Workbook: + *

                      0= Manual + *

                      1= Automatic + *

                      2= Automatic except for multiple table operations + * + * @return int + */ + val recalculationMode:Int +get() =this.calcmoderec!!.recalcuationMode + + val numFonts:Int +get() =fontRecs.size + +/** + * Gets the number of custom number formats registered on this book. + */ + val numFormats:Int +get() =formats.size + +/** + * sets the selected worksheet + */ + val selectedSheetNum:Int +get() =win1!!.currentTab + +internal var lastFormula:Formula? = null +internal var countryRec:XLSRecord? = null +internal var inChartSubstream = false +internal var chartTemp = ArrayList() + +/** + * get a handle to the Usersviewbegin for the workbook + */ + val usersviewbegin:Usersviewbegin +get() { +if (usersview == null) +{ +usersview = Usersviewbegin() +streamer.addRecord(usersview!!) +this.addRecord(usersview!!, false) +} +return usersview +} + +/** + * Get the number of worksheets in this WorkBook + */ + val numWorkSheets:Int +get() =this.sheetVect.size + +/** + * get the number of formulas in this WorkBook + * + * @return + */ + val numFormulas:Int +get() =formulas.size + +/** + * get the number of Cells in this WorkBook + */ + val numCells:Int +get() { +var cellnum = 0 +val e = workSheets.elements() +while (e.hasMoreElements()) +{ +val b = e.nextElement() as Boundsheet +cellnum += b.numCells +} +return cellnum +} + +/** + * get all of the Cells in this WorkBook + */ + val cells:Array +get() { +val cellz = FastAddVector() +for (i in 0 until workSheets.size) +{ +try +{ +val b = this.getWorkSheetByNumber(i) +val cz = b.cells +for (x in cz.indices) +{ +cellz.add(cz[x]) +} +} +catch (e:Exception) { +Logger.logErr("Error retrieving worksheet for getCells: $e") +} + +} +val cellzr = arrayOfNulls(cellz.size) +cellz.toTypedArray() +return cellzr +} + +/** + * @return Returns the lastbound. + */ + /** + * @param lastbound The lastbound to set. + */ + var lastbound:Boundsheet? = null + +override// 2003-vers + val fileName:String +get() =if (this.factory != null) this.factory!!.fileName else "New Spreadsheet" + + val numXfs:Int +get() =xfrecs.size + +/** + * Get the typename for this object. + */ + internal val typeName:String +get() ="WorkBook" + +/** + * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. + * These are used as a comparitor to determine if additional xf's need to be brought in or + * not and to give the new XF number if the xf exists. + * + * + * Changed 20080226 KSC: to use XF toString as XML is limited in format, toString is more complete + * + * @return map (String XfXml, Integer xfLookup) + */ + val xfrecsAsString:Map<*, *> +get() { +val retMap = HashMap() +for (xfNum in 1 until this.numXfs) +{ +val x = this.getXf(xfNum) +val xml = x!!.toString() +retMap.put(xml, Integer.valueOf(xfNum)) +} +return retMap +} + +/** + * Returns a map of xml strings representing the XF's in this workbook/Integer of lookup. + * These are used as a comparitor to determine if additional xf's need to be brought in or + * not and to give the new XF number if the xf exists. + * + * @return map (String XfXml, Integer xfLookup) + */ + val fontRecsAsXML:Map<*, *> +get() { +val retMap = HashMap() +for (i in this.fontRecs.indices.reversed()) +{ +val fnt = this.fontRecs[i] as Font +val xml = "<" + fnt.xml + "/>" +retMap.put(xml, Integer.valueOf(fnt.idx)) +} +return retMap +} + +/** + * Returns whether this book uses the 1904 date format. + * + */ + val is1904:Boolean +@Deprecated("Use {@link #getDateFormat()} instead.") +get() =this.dateFormat == DateConverter.DateFormat.LEGACY_1904 + + // OOXML Additions + /** + * returns truth of "Excel 2007" format + */ + /** + * set truth of "Is Excel 2007" + * true increases maximums of base storage, etc. + * + * @param b + */ + var isExcel2007 = false + +/** + * Returns all strings that are in the SharedStringTable for this workbook. The SST contains + * all standard string records in cells, but may not include such things as strings that are contained + * within formulas. This is useful for such things as full text indexing of workbooks + * + * @return Strings in the workbook. + */ + val allStrings:Array +get() { +val al = this.sharedStringTable!!.allStrings +var s = arrayOfNulls(al.size) +s = al.toTypedArray() as Array +return s +} + + // TODO Auto-generated method stub + // TODO Auto-generated method stub + var calcId:Int +get() =0 +set(i) { + +} + +protected fun reflectiveClone(source:WorkBook) { +for (field in WorkBook::class.java!!.getDeclaredFields()) +{ +if (Modifier.isStatic(field.getModifiers())) +continue + +try +{ +field.set(this, field.get(source)) +} +catch (e:IllegalAccessException) { +throw RuntimeException(e) +} + +} +} + +protected fun createByteStreamer():ByteStreamer { +return ByteStreamer(this) +} + +/** + * init the ImageHandles + */ + internal fun initImages() { +lastSPID = Math.max(lastSPID, msodg!!.spidMax) // 20090508 KSC: + // lastSPID should + // also account for + // charts + // [BUGTRACKER 2372 + // copyChartToSheet + // error] + // 20071217 KSC: clear out imageMap before inputting! + for (x in 0 until this.getWorkSheets().size) +{ +this.getWorkSheets()[x].imageMap!!.clear() +} +for (i in 0 until msodg!!.msodrawingrecs.size) +{ // 20070914 + // KSC: + // store + // msodrawingrecs + // with + // MSODrawingGroup + // instead + // of + // here + val rec = msodg!!.msodrawingrecs[i] as MSODrawing +lastSPID = Math.max(lastSPID, rec.getlastSPID()) // valid for + // header + // msodrawing + // record(s) + val imgdx = rec.getImageIndex() - 1 // it's 1-based +val imageData = msodg!!.getImageBytes(imgdx) +if (imageData != null) +{ +val im = ImageHandle(imageData, rec.sheet) +im.setMsgdrawing(rec) // Link 2 actual Msodrawing rec +im.name = rec.name // set image name from rec ... +im.shapeName = rec.getShapeName() // set shape name as well + // ... + im.imageType = msodg!!.getImageType(imgdx)// 20100519 KSC: + // added! + rec.sheet!!.imageMap!![im] = Integer.valueOf(imgdx) +} +} +} + + /* + * Once all the records are parsed, the msodrawinggroup + * needs to be parsed. + * We first merge all the msodrawinggroup records. From the + * till now experiment, + * there are maximum of two such records, adjacent to each + * other. If there are two, + * the size of the first is only 8228 and the remaining data + * is in second record. + * However, when writing, it doesn't seem important to break + * it into two and can be written + * as one. + * + * Once msodrawingrecords are parsed, it is not used. + * However, we have a msodrawingglobal class that + * records the current count of msodrawing related records, + * like shape count, max shape id till now etc. + * We need to maintain that for writing (creating the + * msodrawinggroup records) later on. + */ + fun mergeMSODrawingRecords() { + // 20070915 KSC: Now don't re-initialize Msodrawing recs; + // just merge and parse + if (msodg != null) +{ +msodg!!.mergeAndRemoveContinues() +for (i in 1 until msodgMerge.size) +{ +msodg!!.mergeRecords(msodgMerge[i] as MSODrawingGroup) // merges + // and + // removes + // secondary + // msodrawinggroups + // 20071003 KSC: get rid of secondary msodg's, will be + // created upon createMSODGContinues + this.streamer +.removeRecord(msodgMerge[i] as MSODrawingGroup) // remove + // existing + // continues + // from + // stream + } +while (msodgMerge.size > 1) +msodgMerge.removeAt(msodgMerge.size - 1) +msodg!!.parse() +this.initImages() +} +} + +/** + * after changing the MSODrawings on a sheet, this is called to + * update the header Msodrawing rec. + *

                      must sum up all other mso's on the particular sheet SPCONTAINERLEN and update the sheet mso header ... + *

                      this calculation is quite experimental, so far it's working in all known cases ... + * + * @param bs + */ + fun updateMsodrawingHeaderRec(bs:Boundsheet) { +val msdHeader = msodg!!.getMsoHeaderRec(bs) +if (msdHeader != null) +{ +var spContainerLength = 0 // count of all other spcontainer lengths + // (sum=header spgroupcontainer) + var otherContainerLength = 0 // count of other containers + // (solvercontainer,etc) added to + // dgcontainerlength + var numshapes = 2 // 20100324 KSC: total guess, really +val totalDrawingRecs = msodg!!.msodrawingrecs.size +for (z in 0 until totalDrawingRecs) +{ +val rec = msodg!!.msodrawingrecs[z] as MSODrawing +if (rec.sheet == bs) +{ +if (rec != msdHeader && !rec.isHeader) +{ // added header + // check- seems + // like *can* + // have multiple + // header recs + // in charts! + spContainerLength += rec.spContainerLength +otherContainerLength += rec.solverContainerLength +if (rec.isShape) + // if it's a shape-type mso, count; + // there are other mso-types that + // are not SPCONTAINERS; apparently + // don't count these ... + numshapes++ +} +} +} +msdHeader +.updateHeader(spContainerLength, otherContainerLength, numshapes, msdHeader +.getlastSPID()) +} +} + +/** + * For workbooks that do not contain an MSODrawing group create a new one, + * if the drawing group already exists return the existing + * + * @return + */ + fun createMSODrawingGroup():MSODrawingGroup? { +if (msodg != null) +return msodg +this.msoDrawingGroup = MSODrawingGroup +.prototype as MSODrawingGroup? +return msodg +} + +/** + * Return some useful statistics about the WorkBook + * + * @return + */ + fun getStats(usehtml:Boolean):String { +var rex = "\r\n" +if (usehtml) +rex = "
                      " + +var ret = "-------------------------------------------$rex" +ret += "OpenXLS Version: " + WorkBookHandle.version + rex +ret += "Excel Version: $xlsVersionString$rex" +ret += "-------------------------------------------------\r\n" +ret += "Statistics for: $this$rex" +ret += "Number of Worksheets: " + this.numWorkSheets + rex +ret += "Number of Cells: " + this.numCells + rex +ret += "Number of Formulas: " + this.numFormulas + rex +ret += "Number of Charts: " + this.chartVect.size + rex +ret += "Number of Fonts: " + this.numFonts + rex +ret += "Number of Formats: " + this.numFormats + rex +ret += "Number of Xfs: " + this.numXfs + rex + // ret += "StringTable: " + this.stringTable.toString() + + // rex; + ret += "-------------------------------------------------\r\n" +return ret +} + +private fun addMergedcells(c:Mergedcells) { +this.mergecelllookup.add(c) +} + +private fun addHlink(r:Hlink) { +this.hlinklookup.add(r) +} + +/** + * take care of any lazy updating before output + */ + fun prestream() { +if (this.msoDrawingGroup != null) +this.msoDrawingGroup!!.prestream() +} + + fun getSupBooks():Array { +val sbs = arrayOfNulls(this.supBooks.size) +return this.supBooks.toTypedArray() as Array +} + +/** + * returns the list of OOXML objects which are external or auxillary to the main workbook + * e.g. theme, doc properties + * + * @return + */ + fun getOOXMLObjects():List<*> { +return ooxmlObjects +} + +/** + * adds the object-specific signature of the external or auxillary OOXML object + * Object should be of String[] form, + * key, path, local path + filename [, rid, [extra info], [embedded information]] + * e.g. theme, doc properties + * + * @param o + */ + fun addOOXMLObject(o:Any) { +if ((o as Array)[0] != "externalLink") +ooxmlObjects.add(o) +else +ooxmlObjects.add(0, o) // ensure ExternalLinks are 1st because they + // are linked via rId in workbook.xml + } + +/** + * return the External Supbook record associated with the desired externalWorkbook + * will create if bCreate + * + * @param externalWorkbook String URL (name) of External Workbook + * @param bCreate if true, will create an external SUPBOOK record for the externalWorkbook + * @return Supbook + */ + fun getExternalSupbook(externalWorkbook:String?, bCreate:Boolean):Supbook? { +var sb:Supbook? = null +if (externalWorkbook == null) +return null +for (i in this.supBooks.indices) +{ +if ((this.supBooks.get(i) as Supbook).isExternalRecord) +if (externalWorkbook +.equals((this.supBooks.get(i) as Supbook) +.externalWorkBook!!, ignoreCase = true)) +sb = this.supBooks.get(i) +} +if (sb == null && bCreate) +{ // create +sb = Supbook.getExternalPrototype(externalWorkbook) as Supbook +val loc = (supBooks.get(supBooks.size - 1) as Supbook) +.recordIndex // must have at least one global supbook + // present + streamer.addRecordAt(sb, loc + 1) // external supbooks appear to be + // before "normal" supbooks + // [BugTracker 1434] + this.supBooks.add(sb) // 20080714 KSC: add at beginning -- correct + // in all cases? + // this.addRecord(sb,false); // "" no need + } +return sb +} + +/** + * return the index into the Supbook records for this supbook + * + * @param sb + * @return int + */ + fun getSupbookIndex(sb:Supbook):Int { +for (i in this.supBooks.indices) +{ +if (this.supBooks.get(i) === sb) +return i +} +return -1 +} + + fun getPivotTableView(nm:String):Sxview { +return ptViews.get(nm) +} + + fun addPivotTable(sx:Sxview) { +this.ptViews.put(sx.tableName!!, sx) // Pivot Table View ==Top-level + // record for a Pivot Table + } + +/** + * return the Externsheet for this book + * + * @param create a new Externsheet if it does not exist + * @return the Externsheet + */ + fun getExternSheet(create:Boolean):Externsheet? { +if (myexternsheet == null && create) +{ +addExternsheet() +} +return myexternsheet +} +/** + * default constructor -- do init + */ + init{ + +val cm = System.getProperties()[WorkBook.CALC_MODE_PROP] +if (cm != null) +{ +try +{ +this.calcMode = Integer.parseInt(cm.toString()) +} +catch (e:Exception) { +Logger.logWarn("Invalid Calc Mode Setting in System properties:$cm") +} + +} +if (System.getProperties()["io.starter.OpenXLS.sharedupes"] != null) +{ +this.isSharedupes = System.getProperties()["io.starter.OpenXLS.sharedupes"] == "true" +if (this.isSharedupes) +{ +this.setDupeStringMode(WorkBook.SHAREDUPES) +} +} +this.initBuiltinFormats() + // re-init color table: initial state of color table if + // Pallete record exists, changes may occur + colorTable = arrayOfNulls(FormatHandle.COLORTABLE.size) +for (i in FormatHandle.COLORTABLE.indices) +colorTable[i] = FormatHandle.COLORTABLE[i] +} + +/** + * Gets the format ID for a given number format pattern. + * This lookup is completely case-insensitive. For most patterns this + * correctly reflects the case-insensitivity of the tokens. Custom patterns + * containing string literals could be matched incorrectly. + * + * @param pattern the number format pattern to look up + * @return the format ID of the given pattern or -1 if it's not recognized + */ + fun getFormatId(pattern:String):Short { +val res = formatlookup.get(pattern.toUpperCase()) as Short +return res ?: -1 +} + +/** + * Initializes the format lookup to contain the built-in formats. + */ + private fun initBuiltinFormats() { +val formats = FormatConstantsImpl.builtinFormats + +for (i in formats.indices) +formatlookup.put(formats[i][0].toUpperCase(), java.lang.Short +.valueOf(formats[i][1], 16)) +} + +/** + * Init names at Post-load + */ + fun initializeNames() { +for (i in names.indices) +{ +val n = names.get(i) as Name +n.parseExpression() // evaluate expression at postload, after sheet + // recs are loaded + } +} + +/** + * add a Name object to the collection of names + */ + fun addName(n:Name):Int { +if (n.getItab().toInt() != 0) +{ + // its a sheet level name + try +{ +val b = this.getWorkSheetByNumber(n.getItab() - 1)// one + // based + // pointer + b.addLocalName(n) +n.setSheet(b) +} +catch (e:WorkSheetNotFoundException) {} + +} +else +{ +val sName = n.nameA // returns upper case name +val existo = bookNameRecs.get(sName) +if (existo != null) +{ // handle duplicate named ranges +val bnam = n.toString() +if (bnam.indexOf("Built-in:") != 0) +{ +try +{ +if ((existo as Name).location != null) + // use + // original + // - as good + // a guess + // as any + return -1 // an invalid sheet +} +catch (e:Exception) {} + + // if original does not have a location set, use this one + // instead + this.names.remove(existo) +this.bookNameRecs.remove(sName) +} +} +this.bookNameRecs.put(sName, n) +} +this.names.add(n) +if (myexternsheet != null && n.getExternsheet() == null) +{ +try +{ +n.setExternsheet(myexternsheet) // update sheet reference +} +catch (e:WorkSheetNotFoundException) { +Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: $e") +} + +} +return names.size - 1 +} + + fun addNameUpdateSheetRefs(n:Name, origWorkBookName:String) { +if (bookNameRecs.get(n.nameA) == null) +{ +val newName = Name(this, n.name) +try +{ +newName.location = n.location +} +catch (e:Exception) {} + +if (myexternsheet != null && newName.getExternsheet() == null) +{ +try +{ +newName.setExternsheet(myexternsheet) // update sheet + // reference + } +catch (e:WorkSheetNotFoundException) { +Logger.logWarn("WorkBookHandle.addName() setting Externsheet failed for new Name: $e") +} + +} +newName.updateSheetRefs(origWorkBookName) +} +} + +/** + * Store an external name + * + * @param n = String describing the name + * @return int location of the name + */ + fun addExternalName(n:String):Int { +this.externalnames.add(n) +return externalnames.size // one-based index +} + +/** + * Get the external name at the specified index. + * + * @param t index of the name + * @return name at the index, empty string if it doesn't exist. + * + * + * Why are we calling getExternalName one based, then removing for ordinal, internal processes should always + * be 0,1,2,3... -NR 1/06 + */ + fun getExternalName(t:Int):String { +return if (t > 0) this.externalnames.get(t - 1) else "" // one-based index +} + +/** + * For workbooks that do not contain an externsheet + * this creates the externsheet record with one 0000 record + * and the related Supbook rec + */ + fun addDefaultExternsheet() { +val sbb = Supbook.getPrototype(this.numWorkSheets) as Supbook +var l = this.sharedStringTable!!.recordIndex +streamer.addRecordAt(sbb, l++) +supBooks.add(sbb) +val ex = Externsheet +.getPrototype(0x0000, 0x0000, this) as Externsheet +streamer.addRecordAt(ex, l) +this.addRecord(ex, false) +} + +/** + * apparently this method adds an External name rec and returns the ilbl + * + * + * Correct structure is + * Supbook + * Externname + * Supbook + * Externsheet + * + * @param s + * @return + * @see PtgNameX + */ + fun getExtenalNameNumber(s:String):Int { +val i = externalnames.indexOf(s) +if (i > -1) + // got it + return i + 1 +if (this.externSheet == null) +this.addDefaultExternsheet() + + // not found; add a new EXTERNNAME record to list of add-ins + val n = addExternalName(s) +try +{ +var loc:Int +if (myADDINSUPBOOK == null) +{ +val sb = Supbook.addInPrototype as Supbook +loc = this.externSheet!!.recordIndex +streamer.addRecordAt(sb, loc++) +this.addRecord(sb, false) +myADDINSUPBOOK = sb +supBooks.add(sb) +val externref = this.externSheet!!.virtualReference + // Add EXTERNNAME record after ADD-IN SUPBOOK record and + // after existing EXTERNNAME records + val exn = Externname.getPrototype(s) as Externname +streamer.addRecordAt(exn, loc++) +this.addRecord(exn, false) +} +else +{ +loc = streamer.getRecordIndex(myADDINSUPBOOK!!) + // Add EXTERNNAME record after ADD-IN SUPBOOK record and + // after existing EXTERNNAME records + val exn = Externname.getPrototype(s) as Externname +streamer.addRecordAt(exn, loc + externalnames.size) +this.addRecord(exn, false) +} + +} +catch (e:Exception) { +Logger.logWarn("Error adding externname: $e") +} + +return n +} + +/** + * Get a collection of all names in the workbook + */ + fun getNames():Array { +val n = arrayOfNulls(names.size) +names.toTypedArray() +return n +} + +/** + * returns the array of Formulas in the book + * + * @return + */ + fun getFormulas():Array { +val n = arrayOfNulls(formulas.size) +formulas.toTypedArray() +return n +} + +/** + * remove a formula from the book + * + * @param fmla + */ + fun removeFormula(fmla:Formula) { +this.formulashash.remove(fmla.cellAddressWithSheet) +formulas.remove(fmla) +fmla.destroy() +} + +/** + * Sets the recalculation mode for the Workbook: + *

                      0= Manual + *

                      1= Automatic + *

                      2= Automatic except for multiple table operations + */ + fun setRecalcuationMode(mode:Int) { +this.calcmoderec!!.setRecalculationMode(mode) +} + +/** + * returns a Named range by number + * + * @param t + * @return + */ + fun getName(t:Int):Name { +return this.names.get(t - 1) +} + +/** + * rename the NamedRange in the lookup map + * + * @param t + * @return + */ + fun setNewName(oldname:String, newname:String) { +var oldname = oldname +var newname = newname +if (oldname == newname) +return +oldname = oldname.toUpperCase() // case-insensitive +newname = newname.toUpperCase() // "" +val old = bookNameRecs.get(oldname) ?: return +// new name? +bookNameRecs.remove(oldname) +bookNameRecs.put(newname, old) + +} + +/** + * Re-assocates ptgrefs that are pointing to a name that has been deleted then + * is recreated + * + * @param name + */ + fun associateDereferencedNames(name:Name) { +val i = orphanedPtgNames.iterator() +val theName = name.name +while (i.hasNext()) +{ +val x = i.next() as IlblListener +if (x.storedName.equals(theName, ignoreCase = true)) +{ +x.ilbl = this.getNameNumber(theName).toShort() +x.addListener() +} +} +} + +/** + * returns a named range by name string + * + * + * This method will first attempt to look in the book names, then the sheet names, + * obviously different scoped names can have the same identifying name, so this could return + * one of multiple names if this is the case + * + * @param t + * @return + */ + fun getName(nameRef:String):Name? { +var nameRef = nameRef +nameRef = nameRef.toUpperCase() // case-insensitive +var o:Any? = this.bookNameRecs.get(nameRef) +if (o == null) +{ +val shts = this.getWorkSheets() +for (i in shts.indices) +{ +o = shts[i].getName(nameRef) +if (o != null) +return o as Name? +} +} +return o as Name? +} + +/** + * returns a scoped named range by name string + * + * @param t + * @return + */ + fun getScopedName(nameRef:String):Name? { +return bookNameRecs.get(nameRef.toUpperCase()) ?: return null +} + +/** + * Returns the ilbl of the name record associated with the string passed in. + * If the name does not exist, it get's created without a location reference. + * This is needed to support formula creation with non-existent names referenced. + * + * @param t, the name record to search for + * @return the index of the name + */ + fun getNameNumber(nameStr:String?):Int { +for (i in names.indices) +{ +val n = this.names.get(i) as Name +if (n.name.equals(nameStr!!, ignoreCase = true)) +return i + 1 +} + // no name exists, we need to create one. + val myName:Name +myName = Name(this, nameStr) + // myName = new Name(this, true); + // myName.setName(nameStr); + val nmx = this.getNames() +var namepos = -1 +if (nmx.size >= 1) +{ +namepos = nmx[nmx.size - 1].recordIndex +} +else +{ +namepos = this.externSheet!!.recordIndex +} +namepos++ +this.streamer.addRecordAt(myName, namepos) +return this.getNameNumber(nameStr) +} + +/** + * Get's the index for this particular front. + * + * + * NOTE: this doesn't actually get a "matching" font, it has to be the exact font. + * 20070826 KSC: changed to match font characterstics, not just return exact matching font + */ + fun getFontIdx(f:Font):Int { + // 20070819 KSC: Try this to see if better! Matches 6 key + // attributes (size, name, color, etc.) + for (i in fontRecs.indices.reversed()) +{ // start from the back so + // don't initially match + // defaults... + if (f.matches(fontRecs[i] as Font)) +return if (i > 3) i + 1 else i +} + // return fonts.indexOf(f); + return -1 +} + +/** + * Get's the index for this font, based on matching through + * xml strings. If the font doesn't exist in the book it returns -1; + * + * @return KSC: is this method necessary now with above getFontIdx changes? + */ + fun getMatchingFontIndex(f:Font):Int { +val fontmap = this.fontRecsAsXML +val o = fontmap.get("<" + f.xml + "/>") +if (o != null) +{ +val I = o as Int? +return I!!.toInt() +} +else +{ +return -1 +} +} + +/** + * InsertFont inserts a Font record into the workbook level stream, + * For some reason, the addFont only puts it into an array that is never accessed + * on output. This may have a reason, so I am not overwriting it currently, but + * let's check it out? + */ + fun insertFont(x:Font):Int { +val insertIdx = this.getFont(this.numFonts).recordIndex + // perform default add rec actions + this.streamer.addRecordAt(x, insertIdx + 1) +x.idx = -1 // flag to add into font array +this.addRecord(x, false) // also adds to font array so no need for + // additional addFont below + return fontRecs.indexOf(x) +} + +/** + * add a Font object to the collection of Fonts + */ + fun addFont(f:Font):Int { +fontRecs.add(f) +return if (fontRecs.size > 4) fontRecs.size else fontRecs.size - 1 +} + +/** + * Get the font at the specified index. Note that the number 4 does not exist, so index correctly based of that. + * + * + * So, if you call getFont(5), you are really doing getFont(4) from the internal array + * + * @param t + * @return + */ + fun getFont(t:Int):Font { +var t = t +if (t >= 4) +{ +t-- +} +if (this.fontRecs.size >= t) +{ +if (t >= fontRecs.size) +{ +Logger.logWarn("font " + t ++ " not found. Workbook contains only: " + fontRecs.size ++ " defined fonts.") +return fontRecs[0] as Font +} +else +{ +return fontRecs[t] as Font +} +} +return this.fontRecs[0] as Font +} + +/** + * Inserts a newly created Format record into the workbook. + * This method handles assigning the format ID and adding the record to the + * workbook. If the record is already part of the workbook use + * [.addFormat] instead. + */ + fun insertFormat(format:Format):Int { +val last:Format +try +{ +last = formats.get(formats.lastKey()) +} +catch (e:NoSuchElementException) { + /* + * There are no other Format records in the workbook. + * This shouldn't happen because most (all?) Excel files + * contain + * Format records for the locale-specific (and thus not + * implied) + * built-in formats. If it does happen, either we need to + * re-assess + * the above assumption or this method was called before the + * Format + * records were parsed. Either way we need to know about it. + */ + throw AssertionError("WorkBook.insertFormat called but no " ++ "Format records exist. This should not happen. Please " ++ "report this error to support@extentech.com.") +} + + // Add it to the streamer and workbook + streamer.addRecordAt(format, last.recordIndex + 1) +addRecord(format, false) + + // Give it a format ID + if (format.ifmt.toInt() == -1) +format.ifmt = Math.max(last.ifmt + 1, 164).toShort() + + // Add it to the format lookups + addFormat(format) + +return format.ifmt.toInt() +} + +@Throws(FormulaNotFoundException::class) + fun getFormula(cellAddress:String):Formula { + +return formulashash.get(cellAddress) as Formula ?: throw FormulaNotFoundException( + "no formula found at $cellAddress") +} + +/** + * Adds an existing format record to the list of known formats. + * This method does not add the record to the workbook! If the format is + * not already in the workbook use [.insertFormat] instead. + * + * @param format the Format record to add + */ + fun addFormat(format:Format):Int { +val ifmt = java.lang.Short.valueOf(format.ifmt) + + // Add it to the format record lookup + formats.put(ifmt, format) + + // Add it to the format string lookup + formatlookup.put(format.format!!.toUpperCase(), ifmt) + +return format.ifmt.toInt() +} + +/** + * Gets a custom number format by its format ID. + */ + fun getFormat(id:Int):Format { +return formats.get(java.lang.Short.valueOf(id.toShort())) +} + +/** + * associate default row/col size recs + */ + internal fun setDefaultRowHeightRec(dr:DefaultRowHeight) { +this.drh = dr +} + +/** + * set Default row height in twips (1/20 of a point) + */ + // should be a double as Excel units are 1/20 of what is + // stored in defaultrowheight + // e.g. 12.75 is Excel Units, twips = 12.75*20 = 256 + // (approx) + // should expect users to use Excel units and target method + // do the 20* conversion + fun setDefaultRowHeight(t:Int) { +drh!!.setDefaultRowHeight(t) +} + +/** + * set Default col width for all worksheets in the workbook, + * + * + * Default column width can also be set on individual worksheets + */ + fun setDefaultColWidth(t:Int) { +val b = this.getWorkSheets() +for (i in b.indices) +{ +b[i].defaultColumnWidth = t +} +} + +/** + * sets the selected worksheet + */ + fun setSelectedSheet(bs:Boundsheet) { +val bsx = this.getWorkSheets() +for (t in bsx.indices) +{ +if (bsx[t] != bs) +bsx[t].setSelected(false) +} +this.win1!!.setCurrentTab(bs) +} + +/** + * for those cases where a formula calculation adds a new string rec + * need to explicitly set lastFormula before calling addRecord + * + * @param f + */ + fun setLastFormula(f:Formula) { +lastFormula = f +} + +/** + * Associate a record with its containers and related records. + */ + override fun addRecord(rec:BiffRec, addtorec:Boolean):BiffRec { +var addtorec = addtorec +val opc = rec.opcode +rec.streamer = streamer +rec.workBook = this + +var bs:Boundsheet? = null +var lbplypos:Long? = 0L + + // get the relevant Boundsheet for this rec + if (rec is Bof) +{ +if (this.getFirstBof() == null) +this.setFirstBof(rec) +if (bofct == eofct) +{ // not a chart or other non Sheet Bof +this.setLastBOF(rec) +} +if (rec.isChartBof) +{ +inChartSubstream = true +} +} + +if (this.lastBOF == null) +Logger.logWarn("WorkBook: NULL Last BOF") +var lb = this.lastBOF!!.lbPlyPos +if (!this.lastBOF!!.isValidBIFF8) +lb += 8 +lbplypos = lb // use last + +bs = getSheetFromRec(rec, lbplypos) +if (bs != null) +lbplypos = bs.lbPlyPos + +if (bs != null) +{ // &&){ +lastbound = bs +if (addtorec) +rec.setSheet(bs)// we don't include Bof or other Book-recs + // because it lives in the Streamer recvec + if (!copying) +{ +if (lastFormula != null && opc == XLSConstants.STRINGREC) +{ +lastFormula!!.addInternalRecord(rec) +} +else if (lastFormula != null && opc == XLSConstants.ARRAY) +{ +lastFormula!!.addInternalRecord(rec) +} +else if (rec.isValueForCell) +{ +if (currchart == null) +bs.addCell(rec as CellRec) + +} +}/* + * ((rec.isValueForCell()) + * && + */ +} + +if (inChartSubstream) +{ +if (currchart == null) +{ +if (rec.opcode == XLSConstants.CHART) +{ +chartVect.add(rec) +bs?.addChart(rec as Chart) +currchart = rec as Chart +currchart!!.setPreRecords(chartTemp) +chartTemp = ArrayList() // clear out +} +else +{ +chartTemp.add(rec) +} +} +else +{ +currchart!!.addInitialChartRecord(rec) +if (rec.opcode == XLSConstants.EOF) +{ +currchart!!.initChartRecords() // finished +currchart = null +inChartSubstream = false +} +} +addtorec = false +} + + // Rows, valrecs, dbcells, and muls are stored in the row, + // not the byte streamer + if (opc == XLSRecord.DBCELL || opc == XLSRecord.ROW +|| rec.isValueForCell || opc == XLSRecord.MULRK + /* || opc==MULBLANK */ + || opc == XLSConstants.CHART || opc == XLSRecord.FILEPASS +|| opc == XLSRecord.SHRFMLA || opc == XLSRecord.ARRAY +|| opc == XLSRecord.STRINGREC) +{ +addtorec = false +} + + // add it to the record stream + if (addtorec) +{ + +if (lbplypos.toLong() > 0) +streamer.addRecord(rec) +else +streamer.records.add(rec) +} + +when (opc) { +XLSConstants.AUTOFILTER -> bs!!.autoFilters.add(rec) + +XLSConstants.CONDFMT -> { +bs!!.conditionalFormats!!.add(rec) +(rec as Condfmt).initializeReferences() +} + +XLSConstants.CF -> { +val cfmt = bs!!.conditionalFormats!![bs.conditionalFormats!!.size - 1] as Condfmt +cfmt.addRule(rec as Cf) +} + +XLSConstants.MERGEDCELLS -> { +bs!!.addMergedCellsRec(rec as Mergedcells) +this.addMergedcells(rec) +} + + // give protection records to the relevant ProtectionManager + XLSConstants.PASSWORD, XLSConstants.PROTECT, XLSConstants.PROT4REV, XLSConstants.OBJPROTECT, XLSConstants.SCENPROTECT, XLSConstants.FEATHEADR -> { +val manager:ProtectionManager +if (bs != null) +manager = bs.protectionManager +else +manager = this.protectionManager +manager.addRecord(rec) +} + +XLSConstants.DVAL -> if (bs != null) +bs.dvalRec = rec as Dval + +XLSConstants.DV -> if (bs != null) +{ +if (bs.dvalRec != null) +{ +bs.dvalRec!!.addDvRec(rec as Dv) +} +} + +XLSConstants.INDEX -> { +val id = rec as Index +id.indexNum = indexnum++ +indexes.add(indexes.size, id) +this.lastINDEX = id +if (bs == null) +{ +Logger.logWarn("ERROR: WorkBook.addRecord( Index ) error: BAD LBPLYPOS. The wrong LB:$lbplypos") +try +{ +bs = this.getWorkSheetByNumber(indexnum - 1) +Logger.logInfo(" The RIGHT LB:" + bs.lbPlyPos) +} +catch (e:WorkSheetNotFoundException) { +Logger.logInfo("problem getting WorkSheetByNumber: $e") +} + +} +bs!!.sheetIDX = id +} + +XLSConstants.ROW -> { +val rw = rec as Row +bs?.addRowRec(rw) +} + +XLSConstants.FORMULA -> { +this.addFormula(rec as Formula) +lastFormula = rec +} + +XLSConstants.ARRAY -> { +val arr = rec as Array +bs?.addArrayFormula(arr) +arr.parentRec = lastFormula // [BugTracker 1869] link array +} + + /* + * case SHRFMLA : done in shrfmla.init + * Shrfmla form = (Shrfmla) rec; + * try{ // throws exceptipon during pullparse + * form.setHostCell( lastFormula ); + * }catch(Exception e){;} + * break; + */ + + XLSConstants.DATE1904 -> if ((rec as NineteenOhFour).is1904) +{ +this.dateFormat = DateConverter.DateFormat.LEGACY_1904 +} + + /* + * case PALETTE : // palette now correctly read into + * COLORTABLE + * this.pal = (Palette) rec; + * break; + */ + + XLSConstants.HLINK -> { +val hl = rec as Hlink +this.addHlink(hl) +} + +XLSConstants.DSF -> { +val dsf = rec as Dsf +if (dsf.fDSF == 1) +{ +Logger.logErr("DOUBLE STREAM FILE DETECTED!") +Logger.logErr(" OpenXLS is compatible with Excel 97 and above only.") +throw WorkBookException( +"ERROR: DOUBLE STREAM FILE DETECTED! OpenXLS is compatible with Excel 97 + only.", +WorkBookException.DOUBLE_STREAM_FILE) +} +} + +XLSConstants.GUTS -> if (bs != null) +{ +bs.guts = rec as Guts +} + +XLSConstants.DBCELL -> {} + +XLSConstants.BOF -> { +if (DEBUGLEVEL > 5) +Logger.logInfo("BOF:$bofct - $rec") +if (eofct == bofct) +{ +bs?.setBOF(rec as Bof) +} +bofct++ +} + +XLSConstants.EXTERNSHEET -> myexternsheet = rec as Externsheet + +XLSConstants.DEFCOLWIDTH -> if (bs != null) +{ +bs.defColWidth = rec as DefColWidth +} + +XLSConstants.EOF -> { +this.lasteof = rec as Eof +eofct++ +if (eofct == bofct) +{ +bs?.setEOF(rec) +eofct-- +bofct-- +} +} + +XLSConstants.SELECTION // only used for Recvec index + -> bs!!.lastselection = rec as Selection + +XLSConstants.COUNTRY -> { + // Added to save position of 1st bound sheet, which is 1 + // record + // before COUNTRY RECORD (= 2 before 1st SUPBOOK record - + // true in all cases?) + countryRec = rec as XLSRecord + // USA=1, Canada=1, Japan=81, China=86, Thailand= 66, Korea= + // 82, India=91 ... + this.defaultLanguage = (rec as Country).defaultLanguage +} + +XLSConstants.SUPBOOK // KSC: must store ordinal positions of SupBooks, for + -> { + // adding Externsheets + supBooks.add(rec) +if (myADDINSUPBOOK == null) +{ // see if this is the ADD-IN SUPBOOK + // rec + val sb = rec as Supbook +if (sb.isAddInRecord) +myADDINSUPBOOK = sb +} +} + +XLSConstants.BOUNDSHEET -> { +val sh = rec as Boundsheet + + /* + * Here we need to set the selected variable, + * but not mess with selected tabs + * when all of the sheets aren't in the book yet. + * -jm + */ + var ctab = 1 // default- select 1st sheet if no Windows1 record +if (win1 != null) +{// Windows1 record is optional 20101004 + // TestCorruption.TestNPEOnOpen + ctab = win1!!.currentTab +} +val shts = sheetVect.size +if (ctab == shts) +sh.selected = true + +this.addWorkSheet(sh.lbPlyPos, sh) +} + +XLSConstants.MULRK -> { +val mul = rec as Mulrk +val xit = mul.recs.iterator() +while (xit.hasNext()) +{ +this.addRecord(xit.next() as Rk, false) +} +} + +XLSConstants.SST -> this.sharedStringTable = rec as Sst + +XLSConstants.EXTSST -> (rec as Extsst).setSst(this.sharedStringTable!!) + +XLSConstants.SXSTREAMID -> this.ptstream.add(rec) // Pivot Stream + +XLSConstants.SXVS, XLSConstants.DCONREF, XLSConstants.DCONNAME, XLSConstants.DCONBIN -> try +{ +val sid = ptstream.get(ptstream.size - 1) as SxStreamID +sid.addSubrecord(rec) +} +catch (e:Exception) {} + +XLSConstants.SXVIEW -> addPivotTable(rec as Sxview) // Pivot Table View ==Top-level + + // all* possible records associated with SxView (=PivotTable + // View) (*hopefully) + XLSConstants.SXVD, + // case SXVI: // subrecords of SxVD + // case SXVDEX: + XLSConstants.SXIVD, XLSConstants.SXPI, XLSConstants.SXDI, XLSConstants.SXLI, XLSConstants.SXEX, XLSConstants.SXVIEWEX9, XLSConstants.QSISXTAG -> try +{ +val sx = this.ptViews.values +.toTypedArray()[this.ptViews.size - 1] as Sxview +sx.addSubrecord(rec) +} +catch (e:Exception) {} + +XLSConstants.TABID -> this.tabID = rec as TabID + +XLSConstants.NAME -> addName(rec as Name) + +XLSConstants.CALCMODE -> this.calcmoderec = rec as CalcMode + +XLSConstants.WINDOW1 -> this.win1 = rec as Window1 + +XLSConstants.WINDOW2 -> if (bs != null) +bs.window2 = rec as Window2 + +XLSConstants.SCL // scl is for zoom + -> if (bs != null) +bs.scl = rec as Scl + +XLSConstants.PANE -> if (bs != null) +bs.pane = rec as Pane +XLSConstants.EXCEL2K -> xl2k = rec + +XLSConstants.PHONETIC -> + // TODO: this isn't necessary anymore! look at and remove + if (this.currdrw != null) +{ +this.currdrw!!.mystery = rec as Phonetic +} + +XLSConstants.MSODRAWINGGROUP -> { +if (msodg == null) +msodg = rec as MSODrawingGroup +msodgMerge.add(rec) +} + +XLSConstants.MSODRAWING -> { +rec.setSheet(bs) +if (msodg != null) +msodg!!.addMsodrawingrec(rec as MSODrawing) +} + +XLSConstants.COLINFO -> bs!!.addColinfo(rec as Colinfo) + +XLSConstants.USERSVIEWBEGIN -> this.usersview = rec as Usersviewbegin + +XLSConstants.WSBOOL -> if (bs != null) +{ +bs.wsBool = rec as WsBool +} + + // Handle continue records which are actually masked Mso's + XLSConstants.CONTINUE -> if ((rec as Continue).maskedMso != null) +{ +rec.maskedMso!!.setSheet(bs) +if (msodg != null) +msodg!!.addMsodrawingrec(rec.maskedMso) +} + +XLSConstants.XF -> try +{ +this.addXf(rec as Xf) +} +catch (e:Exception) { + // throws exceptions during PullParse + } + +}// formula to it's parent formula + // rec + // record for a Pivot Table + // do what???io.starter.toolkit.Logger.log("PROBLEM with + // MSODG!"); + // DO NOTHING + + // finish up + rec.setIndex(lastINDEX) +rec.setXFRecord() +return rec +} + +/** + * Dec 15, 2010 + * + * @param rec + * @return + */ + override fun getSheetFromRec(rec:BiffRec, lbplypos:Long?):Boundsheet? { +var bs:Boundsheet? = null + +if (rec.sheet != null) +{ +bs = rec.sheet +} +else if (lbplypos != null) +{ +bs = this.getWorkSheet(lbplypos) +} +else +bs = lastbound +return bs +} + +/** + * set the Debug level + */ + override fun setDebugLevel(i:Int) { +this.DEBUGLEVEL = i +} + +/** + * get the cell by the following String Pattern + * + * + * BiffRec c = getCell("SheetName!C17"); + */ + @Throws(CellNotFoundException::class, WorkSheetNotFoundException::class) + fun getCell(cellname:String):BiffRec { +val semi = cellname.indexOf("!") +val sname = cellname.substring(0, semi) +val cname = cellname.substring(semi + 1) +return getCell(sname, cname) +} + +/** + * get the cell by the following String Pattern + * + * + * BiffRec c = getCell("Sheet1", "C17"); + */ + @Throws(CellNotFoundException::class, WorkSheetNotFoundException::class) + fun getCell(sheetname:String, cellname:String):BiffRec { +var cellname = cellname +cellname = cellname.toUpperCase() +try +{ +val bs = this.getWorkSheetByName(sheetname) +return bs.getCell(cellname) ?: throw CellNotFoundException("$sheetname:$cellname") +} +catch (a:WorkSheetNotFoundException) { +throw WorkSheetNotFoundException("$sheetname not found") +} +catch (e:NullPointerException) { +throw CellNotFoundException("$sheetname:$cellname") +} + +} + +/** + * add a Boundsheet to the WorkBook + */ + private fun addWorkSheet(lbplypos:Long?, sheet:Boundsheet?) { +if (sheet == null) +{ +Logger.logWarn("WorkBook.addWorkSheet() attempting to add null sheet.") +return +} +else +{ +this.lastbound = sheet +if (DEBUGLEVEL > 10) +Logger.logInfo("Workbook Adding Sheet: " + sheet.sheetName ++ ":" + lbplypos) +workSheets.put(lbplypos!!, sheet) +sheetVect.add(sheet) +} +} + +/** + * @param n + * @return + */ + fun removeName(n:Name):Boolean { +if (this.names.contains(n)) +{ +this.names.remove(n) +if (n.getItab().toInt() != 0) +{ +try +{ +this.getWorkSheetByNumber(n.getItab() - 1) +.removeLocalName(n) +} +catch (e:WorkSheetNotFoundException) {} + +} +else +{ +this.bookNameRecs.remove(n.toString().toUpperCase()) // case-insensitive +} +} +val al = n.ilblListeners +this.orphanedPtgNames.addAll(al) + +this.updateNameIlbls() +return this.streamer.removeRecord(n) +} + +/** + * Add a sheet-scoped name record to the boundsheet + * + * + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this book, adding them here will not add them to the workbook; + * + * @param bookNameRecs + */ + fun addLocalName(name:Name) { +bookNameRecs.put(name.nameA, name) +} + +/** + * Remove a sheet-scoped name record from the boundsheet. + * + * + * Note this is not that primary repository for names, it just contains the name records + * that are bound to this book, removing them here will not remove them completely from the workbook. + * + * + * In order to do that you will need to call book.removeName + * + * @param bookNameRecs + */ + fun removeLocalName(name:Name) { +bookNameRecs.remove(name.nameA) +} + +/** + * After any changes in the name records + * this method needs to be called in order to + * update ilbl records + */ + fun updateNameIlbls() { +for (i in names.indices) +{ +val n = names.get(i) as Name +n.updateIlblListeners() +} +} + +/** + * remove a Boundsheet from the WorkBook + */ + fun removeWorkSheet(sheet:Boundsheet) { + +var sheetNum = sheet.sheetNum + // remove the sheet + // automatically deletes Named ranges scoped to the sheet + val namesOnSheet = sheet.allNames +for (i in namesOnSheet.indices) +this.removeName(namesOnSheet[i]) + + // Remove Externsheet ref before removing sheet + // update any Externsheet references... + try +{ +val ext = this.externSheet +ext?.removeSheet(sheet.sheetNum) +} +catch (e:WorkSheetNotFoundException) { +Logger.logInfo("could not update Externsheet reference from " ++ sheet.toString() + " : " + e.toString()) +} + +sheet.removeAllRecords() +streamer.removeRecord(sheet) +workSheets.remove(sheet.lbPlyPos) +sheetVect.remove(sheet) + // we need to reset the lastbound for adding new worksheets. + // Currently assume it is + // the last one in the vector. + if (sheetVect.size > 0) +{ +lastbound = sheetVect[sheetVect.size - 1] as Boundsheet +lastBOF = lastbound!!.myBof +} + + // decrement the tab ids... + this.tabID!!.removeRecord() +this.updateScopedNamedRanges() + + // update wb chart cache - remove charts referenced by + // deleted sheet + for (i in chartVect.size - 1 downTo 0) +{ +if ((this.chartVect[i] as Chart).sheet == sheet) +this.chartVect.removeAt(i) +} + +if (this.numWorkSheets == 0) +return // empty book +try +{ // set the next sheet selected... +while (sheetNum <= this.numWorkSheets) +{ +val s2 = this.getWorkSheetByNumber(sheetNum++) +s2.setSelected(true) +if (!s2.hidden) +break + +} +} +catch (e:WorkSheetNotFoundException) { +try +{ +val s2 = this.getWorkSheetByNumber(0) +s2.setSelected(true) +} +catch (ee:Exception) { +throw WorkBookException( +"Invalid WorkBook. WorkBook must contain at least one Sheet.", +WorkBookException.RUNTIME_ERROR) +} + +} + +} + +/** + * Updates all the name records in the workbook that are bound to a + * worksheet scope (as opposed to a workbook scope). Name records use + * their own non-externsheet based sheet references, so need to be modified + * whenever a sheet delete (or non-last sheet insert) operation occurs + */ + private fun updateScopedNamedRanges() { +for (i in sheetVect.indices) +{ +(sheetVect[i] as Boundsheet).updateLocalNameReferences() +} +} + +/** + * returns the Boundsheet identified by its + * offset to the BOF record indicating the + * start of the Boundsheet data stream. + * + * + * used internally to access the Sheets to + * ensure that the lbplypos is correct -- essential + * to proper operation of XLS file. + * + * @param Long lbplypos of Boundsheet + */ + private fun getWorkSheet(lbplypos:Long?):Boundsheet { +return workSheets.get(lbplypos!!) +} + +/** + * returns the Boundsheet with the specific name + * + * @param String name of Boundsheet + */ + @Throws(WorkSheetNotFoundException::class) + fun getWorkSheetByName(bstr:String?):Boundsheet { +var bstr = bstr +try +{ +if (bstr!!.startsWith("'") || bstr.startsWith("\"")) +bstr = bstr.substring(1, bstr.trim{ it <= ' ' }.length - 1) +val bs = sheetVect.iterator() +while (bs.hasNext()) +{ +val bsi = bs.next() as Boundsheet +val bsin = bsi.sheetName + // TODO: check if we can have dupe names different case + if (bsin.equals(bstr, ignoreCase = true)) +return bsi +} +} +catch (ex:Exception) { +Logger.logWarn("WorkBook.getWorkSheetByName failed: $ex") +} + +throw WorkSheetNotFoundException( +"Worksheet $bstr not found in $this") +} + +/** + * returns the Boundsheet with the specific Hashname + * + * @param String hashname of Boundsheet + */ + @Throws(WorkSheetNotFoundException::class) + fun getWorkSheetByHash(s:String):Boundsheet? { +val bs = this.getWorkSheets() +for (i in bs.indices) +{ +if (bs[i].getSheetHash().equals(s, ignoreCase = true)) +return bs[i] +} +return null +} + +/** + * returns the Boundsheet at the specific index + * + * @param int index of Boundsheet + */ + @Throws(WorkSheetNotFoundException::class) + fun getWorkSheetByNumber(i:Int):Boundsheet { +var bs:Boundsheet? = null +try +{ +bs = sheetVect[i] as Boundsheet +} +catch (e:ArrayIndexOutOfBoundsException) {} + +if (bs == null) +{ // External Sheet Ref NOT FOUND +throw WorkSheetNotFoundException( +"$i not found") +} +return bs +} + +/** + * returns the boundsheets for this book as an array + */ + internal fun getWorkSheets():Array { +val ret = arrayOfNulls(sheetVect.size) +return sheetVect.toTypedArray() as Array +} + +/** + * set the last BOF read in the stream + */ + internal fun setLastBOF(b:Bof) { +lastBOF = b +} +/** return the last BOF read in the stream */ + // Bof lastBOF{return lastBOF;} + + /** + * get a handle to the first BOF to perform offset functions which don't know where the + * start of the file is due to the compound file format. + * + * + * Referred to in Boundsheet as the 'lbPlyPos', this + * is the position of the BOF for the Boundsheet relative + * to the *first* BOF in the file (the firstBOF of the WorkBook) + * + * @see Boundsheet + */ + override fun setFirstBof(b:Bof) { +firstBOF = b +} + +internal fun getFirstBof():Bof? { +return firstBOF +} + +override fun toString():String { +return this.fileName +} + +/** + * Returns whether the sheet selection tabs should be shown. + */ + fun showSheetTabs():Boolean { +return win1!!.showSheetTabs() +} + +/** + * Sets whether the sheet selection tabs should be shown. + */ + fun setShowSheetTabs(show:Boolean) { +win1!!.setShowSheetTabs(show) +} + +/** + * set the first visible tab + */ + fun setFirstVisibleSheet(bs2:Boundsheet) { +win1!!.setFirstTab(bs2.sheetNum) +} + + // Associate related records + + /** + * return the XF record at the specified index + */ + fun getXf(i:Int):Xf? { +return if (xfrecs.size < i - 1) null else xfrecs[i] as Xf +} + +/** + * InsertXF inserts an XF record into the workbook level stream, + * For some reason, the addXf only puts it into an array that is never accessed + * on output. This may have a reason, so I am not overwriting it currently, but + * let's check it out? + */ + internal fun insertXf(x:Xf):Int { +val insertIdx = this.getXf(this.numXfs - 1)!!.recordIndex + // perform default add rsec actions + this.streamer.addRecordAt(x, insertIdx + 1) +this.addRecord(x, false) // updates xfrecs + formatcache +x.ixfe = x.idx +return x.idx +} + +/** + * internally used in preparation for reading an 2007 and above workbook + */ + fun removeXfRecs() { + // must keep the 1st xf rec as default + for (i in xfrecs.size - 1 downTo 1) +{ +val xf = xfrecs[i] as Xf +this.streamer.removeRecord(xf) +xfrecs.removeAt(i) + +} +} + +/** + * TODO: Does this function as desired? See comment for insertXf() above... + * tracks existing xf recs, used when testing whether xfrec exists or not ... + * -NR 1/06 + * ya should - called now from addRecord every time an xf record is added + * NOTE: this is the only place addXf is called + * + * @param xf + * @return + */ + internal fun addXf(xf:Xf):Int { +xfrecs.add(xf) +xf.idx = xfrecs.size - 1 // flag that it's been added to records +this.updateFormatCache(xf) // links tostring of xf to xf rec for + // updating/reuse purposes + return xf.idx +} + +/** + * formatCache: + * links tostring of xf to xf rec for updating/reuse purposes + * + * @param xf + * @see FormatHandle.updateXf + * + * @see WorkBook.addXf + */ + fun updateFormatCache(xf:Xf) { +if (xf.idx != -1) +{ // if this xf has been already added to the + // workbook + if (formatCache.containsValue(xf)) +{ // xf signature has + // changed/it's been updated + val ii = formatCache.keys.iterator() // remove and + // update below + while (ii.hasNext()) +{ +val key = ii.next() as String +val x = formatCache.get(key) as Xf +if (x == xf) +{ +formatCache.remove(key) +break +} +} +} +val formatStr = xf.toString() + +if (!formatCache.containsKey(formatStr)) +formatCache.put(formatStr, xf) +} +} + + fun setSubStream(s:ByteStreamer) { +this.streamer = s +} + +/** + * Write the contents of the WorkBook bytes to an OutputStream + * + * @param _out + */ + override fun stream(_out:OutputStream):Int { +return streamer.streamOut(_out) +} + +/** + * Copies a complete boundsheet within the workbook + * + * + * If a name exists that refers directly to this sheet then duplicate it, otherwise workbook scoped names are + * not copied + */ + @Throws(Exception::class) + fun copyWorkSheet(SourceSheetName:String, NewSheetName:String) { +var origSheet:Boundsheet? = null +origSheet = this.getWorkSheetByName(SourceSheetName) +val chts = origSheet.charts // 20080630 KSC: Added +for (i in chts.indices) +{ +val cxi = chts[i] as Chart +cxi.populateForTransfer() +} +val inbytes = origSheet.sheetBytes +this.addBoundsheet(inbytes, SourceSheetName, NewSheetName, null, false) +val bnd = this.getWorkSheetByName(NewSheetName) + // handle moving the built-in name records. These handle + // such items as print area, header/footer, etc + val ns = this.getNames() +for (i in ns.indices) +{ // 20100404 KSC: take out +1? +if (ns[i].getItab().toInt() == origSheet.sheetNum + 1) +{ + // it's a built in record, move it to the new sheet + val sheetnum = bnd.sheetNum +val xref = this.getExternSheet(true)!! +.insertLocation(sheetnum, sheetnum) +val n = ns[i].clone() as Name +n.setExternsheetRef(xref) +n.updateSheetReferences(bnd) +n.setSheet(bnd) +n.setItab((bnd.sheetNum + 1).toShort()) +this.insertName(n) +} +} +} + +/** + * Inserts a newly created Name record into the correct location in the streamer. + * + * @param n + */ + fun insertName(n:Name) { +var namepos = -1 +val nmx = getNames() +if (nmx.size > 0) +{ +if (nmx[nmx.size - 1].sheet != null) +{ +namepos = nmx[nmx.size - 1].recordIndex +} +else +{ +namepos = getExternSheet(true)!!.recordIndex + nmx.size +} +} +else +{ +namepos = getExternSheet(true)!!.recordIndex +} +namepos++ +streamer.addRecordToBookStreamerAt(n, namepos) +addRecord(n, false) +} + +/** + * Copies an existing Chart to another WorkSheet + * + * @param chartname + * @param sheetname + */ + @Throws(ChartNotFoundException::class, WorkSheetNotFoundException::class) + fun copyChartToSheet(chartname:String, sheetname:String) { +val ct = this.getChart(chartname) +val sht = this.getWorkSheetByName(sheetname) +val bt = ct.chartBytes +sht.addChart(bt, chartname, ct.coords) +} + +/** + * Inserts a serialized boundsheet chart into the workboook + */ + fun addChart(destChart:Chart, NewChartName:String, boundsht:Boundsheet):Chart { +destChart.workBook = this +destChart.setSheet(boundsht) +val recs = destChart.xlSrecs +for (x in recs.indices) +{ +val rec = recs[x] as XLSRecord +rec.workBook = this +rec.setSheet(boundsht) +if (rec.opcode == XLSConstants.MSODRAWING) +{ +addChartUpdateMsodg(rec as MSODrawing, boundsht) +continue +} +if (rec !is Bof) + // TODO: error/problem with the BOF + // record!!! + rec.init() +if (rec is Dimensions) +destChart.setDimensions(rec) +try +{ +(rec as GenericChartObject).parentChart = destChart +} +catch (e:ClassCastException) { // Scl, Obj and others are not + // chart objects + } + +} +destChart.title = NewChartName +destChart.id = boundsht.lastObjId + 1 // track last obj id per sheet + // ... + this.chartVect.add(destChart) +boundsht.charts.add(destChart) // should really have two lists??? +return destChart +} + +/** + * updates Mso (MSODrawingGroup + Msodrawing) records upon add/copy worksheet and add/copy charts + * NOTE: this code is mainly garnered via trial and error, works + * + * @param mso Msodrawing record that is being added or copied + * @param sht Boundsheet + */ + fun addChartUpdateMsodg(mso:MSODrawing, sht:Boundsheet) { +if (msodg == null) +{ +this.msoDrawingGroup = MSODrawingGroup +.prototype as MSODrawingGroup? +msodg!!.initNewMSODrawingGroup() // generate and add required records + // for drawing records + } +msodg!!.addMsodrawingrec(mso) +var hdr = msodg!!.getMsoHeaderRec(sht) +if (hdr != null && hdr != mso) +{ // already have a header rec +if (sht.charts.size > 0) +{ +mso.makeNonHeader() +hdr.setNumShapes(hdr.getNumShapes() + 1) +} +} +else if (hdr == null) +{ +mso.setIsHeader() +hdr = mso +} +this.updateMsodrawingHeaderRec(sht) +msodg!!.dirtyflag = true // flag to reset SPIDs on write +msodg!!.spidMax = ++lastSPID +msodg!!.updateRecord() +} + +/** + * JM - + * Add the requisite records in the book streamer for the chart. \ + * Supbook, externsheet & msodrawinggroup + * + * + * I think this is due to the fact that the referenced series are usually stored + * in the fashon 'Sheet1!A4:B6' The sheet1 reference requires a supbook, though the + * reference is internal. + */ + fun addPreChart() { +this.addExternsheet() +if (msodg == null) +{ +this.msoDrawingGroup = MSODrawingGroup +.prototype as MSODrawingGroup? +msodg!!.initNewMSODrawingGroup() // generate and add required records + // for drawing records + } + +} + +/** + * remove an existing chart from the workbook + * NOTE: STILL EXPERIMENTAL TESTS OK IN BASIC CIRCUMSTANCES BUT MUST BE TESTED FURTHER + */ + @Throws(ChartNotFoundException::class) + fun deleteChart(chartname:String, sheet:Boundsheet) { +val chart = this.getChart(chartname) + // TODO: Update Dimensions record?? + val recs = chart.xlSrecs + // first rec SHOULD BE MsoDrawing!!! + try +{ +val rec = recs[0] as MSODrawing +msodg!!.removeMsodrawingrec(rec, sheet, true) // also remove + // associated Obj + // record + } +catch (e:Exception) { +Logger.logErr("deleteChart: expected Msodrawing record") +} + + /* + * shouldn't be necessary to remove chart recs as they are + * separated upon init of workbook and reassebmbled upon + * write + */ + this.removeChart(chartname) +} + +/** + * Inserts a serialized boundsheet into the workboook. + * + * @throws ClassNotFoundException + * @throws IOException + * @param inbytes original sheet bytes + * @param NewSheetName new Sheet Name + * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * + * @boolean SSTPopulatedBoundsheet - a boundsheet that has all of it's sst data saved off in LabelSST records. + * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. + * Do not use this if the data already exists in the SST, you are just causing bloat! + */ + @Throws(IOException::class, ClassNotFoundException::class) + fun addBoundsheet(inbytes:ByteArray?, origSheetName:String, NewSheetName:String, origWorkBookName:String?, SSTPopulatedBoundsheet:Boolean) { +var destSheet:Boundsheet? = null +val bais = ByteArrayInputStream(inbytes!!) +val bufstr = BufferedInputStream(bais) +val o = ObjectInputStream(bufstr) +destSheet = o.readObject() as Boundsheet + +if (destSheet != null) +{ +this.addBoundsheet(destSheet, origSheetName, NewSheetName, origWorkBookName, SSTPopulatedBoundsheet) +} +} + +/** + * change the tab order of a boundsheet + */ + fun changeWorkSheetOrder(bs:Boundsheet, idx:Int) { + // reorder the sheet vector + if (idx >= 0 && idx < sheetVect.size) +{ +sheetVect.remove(bs) +sheetVect.add(idx, bs) +for (x in sheetVect.indices) +{ +val bs1 = sheetVect[x] as Boundsheet +val udpatewin1 = bs1.selected() +if (udpatewin1) +bs1.setSelected(true) +} +} + +var insertLoc = Integer.MAX_VALUE + // remove the existing boundsheet records in the streamer + for (i in sheetVect.indices) +{ +val bound = sheetVect[i] as Boundsheet +val position = bound.recordIndex +insertLoc = Math.min(insertLoc, position) +streamer.removeRecord(sheetVect[i] as XLSRecord) +} + // enter the boundsheet records back in the streamer in + // correct order + for (i in sheetVect.indices) +{ +val bound = sheetVect[i] as Boundsheet +streamer.addRecordAt(bound, insertLoc + i) +} +} + +/** + * add a deserialized boundsheet to this workbook. + * + * @param bound new (copied) sheet + * @param newSheetName new sheetname + * @param origWorkBookName original WorkBook Name (nec. for resolving possible external references) * + * @boolean SSTPopulatedBoundsheet - the boundsheet has all of it's sst data saved off in LabelSST records. + * one would use this when moving a sheet from a workbook that was not an original or created from getNoSheetWorkBook. + * Do not use this if the data already exists in the SST, you are just causing bloat! + */ + fun addBoundsheet(bound:Boundsheet, origSheetName:String, newSheetName:String, origWorkBookName:String?, SSTPopulatedBoundsheet:Boolean) { +var newSheetName = newSheetName +bound.streamer = streamer +val old_allowdupes = this.isSharedupes +this.setDupeStringMode(ALLOWDUPES) + +bound.mc.clear() +bound.workBook = this + + // Check if sheetname already exists! + try +{ +while (this.getWorkSheetByName(newSheetName) != null) +newSheetName = newSheetName + "Copy" +} +catch (we:WorkSheetNotFoundException) { + /* good !!! */ + } + +bound.sheetName = newSheetName + + // get a hold of the lbplypos number that we will need for + // the new boundsheet + var recvecOffset = streamer.recVecSize - 1 +var x:XLSRecord? = null +if (lastbound != null) +{ +try +{ // lastbound must be reset because other operations could + // alter + lastbound = this +.getWorkSheetByNumber(this.numWorkSheets - 1) +x = lastbound!!.sheetRecs[lastbound!!.sheetRecs.size - 1] as XLSRecord +} +catch (e:Exception) {} + +} +else if (countryRec != null) +{ +x = streamer.getRecordAt(countryRec!!.recordIndex) as XLSRecord +} +else +x = this.lasteof + // last record is a junkrec. We are going to move that down + // and put in the new BOF here + // TODO: recvecOffset position when no sheets?????? + if (x!!.opcode != XLSConstants.EOF) +{ +recvecOffset -= 1 +} +var newloc = x.offset + // modify the boundsheet rec for its new location/info/name + var listenerpos = -1 +var newoffset = -1 +if (lastbound != null) +{ +listenerpos = lastbound!!.recordIndex +newoffset = lastbound!!.offset + lastbound!!.length + 4 + // offset + reclen + headerlen + } +else if (x != null) +{ +listenerpos = x.recordIndex - 1 // account for +1 below +newoffset = x.offset + x.length + 4 +} +else +{ +listenerpos = recvecOffset - 1 +newoffset = streamer.getRecordAt(recvecOffset).length + 4 +} + + // put the serialized recs from localrecs into the normal + // SheetRecs + bound.setLocalRecs(FastAddVector()) // reset localrecs +val newrecs = bound.sheetRecs +val newbof = newrecs[0] as Bof + +newloc += newbof.length + 4 +newbof.offset = newloc +bound.setBOF(newbof) +this.addRecord(newbof, false) + +recvecOffset += 1 // move it past that last Eof +newoffset = newloc + newbof.length + 4 +lastbound = bound + // insert the actual boundsheet record into the recvec + streamer.addRecordAt(bound, listenerpos + 1) + +this.addRecord(bound, false) + + // modify the TabID record to reflect new sheet + tabID!!.addNewRecord() +recvecOffset = newbof.recordIndex + +var tout = 0 +copying = true + + // Add an externsheet ref for the new sheet + if (this.myexternsheet == null) +this.addExternsheet() +try +{ +val sheetref = this.numWorkSheets - 1 +myexternsheet!!.insertLocation(sheetref, sheetref) +} +catch (e:Exception) { +Logger.logWarn("Adding new sheetRef failed in addBoundsheet()$e") +} + + // update the chart references + add to wb + val chts = bound.charts +for (i in chts.indices) +{ +val chart = chts[i] as Chart // obviously algorithm has + // changed and chart is NOT + // removed :) [discovered by + // Shigeo/Infoteria/formatbroken273193.sce] + // // 20080702 KSC: since it's + // removed, don't inc index + chart.updateSheetRefs(bound.sheetName, origWorkBookName) +this.chartVect.add(chart) +} + +bound.lastObjId = 1 // see if resetting obj id helps in file open + // errors; if so, must reset Note obj id's as + // well ... + + /********** This loop handles Boundsheet records contained in the sheet level streamer, that is, not the valrecs */ + val numrecs = newrecs.size +for (z in 1 until numrecs) +{ +val xl = newrecs[z] as XLSRecord +this.addRecord(xl, false) +if (DEBUGLEVEL > 5) +try +{ +Logger.logInfo("Copying: " + xl.toString() + ":" ++ newoffset + ":" + xl.length) +} +catch (e:Exception) {} + +if (xl is Codename) +{ +xl.setName(newSheetName) +} +else if (xl is Name) +{ + // Name records specify data ranges -- update to point to + // new sheet + val refnum = myexternsheet!!.getcXTI() +xl.setExternsheetRef(refnum) +} +else if (xl is Cf) +{ // must check Conditional Format + // formula refs and handle any + // external references + try +{ +updateFormulaPtgRefs(xl +.formula1, origSheetName, newSheetName, origWorkBookName) + // NOTE: FORMULA2 can be null -- TODO: should check here + updateFormulaPtgRefs(xl +.formula2, origSheetName, newSheetName, origWorkBookName) +} +catch (e:Exception) {} + +} +else if (xl.opcode == XLSConstants.OBJ) +{ +(xl as Obj).objId = bound.lastObjId++ +} +else if (xl.opcode == XLSConstants.MSODRAWING || xl.opcode == XLSConstants.CONTINUE && (xl as Continue).maskedMso != null) +{ // 20100510 + // KSC: + // handle + // masked + // mso's + val mso:MSODrawing? +if (xl.opcode == XLSConstants.MSODRAWING) +mso = xl as MSODrawing +else +mso = (xl as Continue).maskedMso +if (msodg == null) +{ +this.msoDrawingGroup = MSODrawingGroup +.prototype as MSODrawingGroup? +msodg!!.initNewMSODrawingGroup() // generate and add required + // records for drawing + // records + msodg!!.addMsodrawingrec(mso) // only add when msodg is null + // b/c otherwise it's added + // via the addRecord + // statement above + } +if (mso!!.getImageIndex() > 0) +{ // add image bytes as well, if + // any + val im = bound +.getImageByMsoIndex(mso.getImageIndex()) +val idx = msodg!!.addImage(im!!.imageBytes, im +.imageType, false) +bound.imageMap!![im] = Integer.valueOf(idx) // 20100518 + // KSC: + // makes + // more + // sense? + // im.getImageIndex())); + // // add + // new image + // to map + // and link + // to actual + // imageIndex + // - moved + // from + // above + if (idx != mso.getImageIndex()) +mso.updateImageIndex(idx) +} +mso.spid = this.lastSPID +msodg!!.spidMax = ++this.lastSPID + // resets drawing id's - necessarily correct? + // msodg.dirtyflag= true; // flag to reset SPIDs on write + } +xl.setOffset(newoffset) +tout += xl.length +newoffset += xl.length +} +if (msodg != null) +{// Moved from above so don't udpate at every mso + // addition + // necessary? all mso sub-records on the sheet should have + // stayed the same ...this.updateMsodrawingHeaderRec(bound); + msodg!!.updateRecord() +} +/*************** END handling of boundsheet streamer records */ + + /*************** HANDLE Formats + PtgRefs in Cell Records */ + updateTransferedCellReferences(bound, origSheetName, origWorkBookName) + + // associate the records in the sheet + this.isSharedupes = old_allowdupes + +if (SSTPopulatedBoundsheet) +{ + // bring over the sst + val sst = this.sharedStringTable +val b = bound.cells +for (i in b.indices) +{ +b[i].workBook = this +if (b[i].opcode == XLSConstants.LABELSST) +{ +val s = b[i] as Labelsst +s.insertUnsharedString(sst) +} +} +} + +if (this.numWorkSheets > 1) +bound.setSelected(false) +else +bound.setSelected(true) + +if (DEBUGLEVEL > 5) +Logger.logInfo("changesize for new boundsheet: " ++ bound.sheetName + ": " + tout) +copying = false +} + +/** + * traverses all rows and their associated cells in the newly transfered sheet, + * ensuring formula/cell references and format references are correctly transfered + * into the current workbook + * + * @param bound source sheet + */ + private fun updateTransferedCellReferences(bound:Boundsheet, origSheetName:String, origWorkBookName:String?) { +val localFonts = this.fontRecsAsXML as HashMap<*, *> +val boundFonts = bound.transferFonts // ALL fonts in the source + // workbook + val localXfs = this.xfrecsAsString as HashMap<*, *> +val boundXfs = bound.transferXfs + // Set the workbook on all the cells + val rows = bound.rows +for (i in rows.indices) +{ +rows[i].workBook = this +if (rows[i].ixfe != this.defaultIxfe) +transferFormatRecs(rows[i], localFonts, boundFonts, localXfs, boundXfs) // 20080709 + // KSC: + // handle + // default + // ixfe + // for + // row + val rowcells = rows[i].cells.iterator() +var aMul:Mulblank? = null +var c:Short = 0 +while (rowcells.hasNext()) +{ +val b = rowcells.next() as BiffRec +if (b.opcode == XLSConstants.MULBLANK) +{ +if (aMul == b) +c++ +else +{ +aMul = b as Mulblank +c = aMul.getColFirst().toShort() +} +aMul.setCurrentCell(c) +} +b.workBook = this // Moved to before updateFormulaPtgRefs + // [BugTracker 1434] + if (b is Formula) +{ // Examine Ptg Refs to handle + // external sheet references not + // contained in this workbook + updateFormulaPtgRefs(b, origSheetName, bound +.sheetName, origWorkBookName) +if (b.shared != null) +b.shared!!.workBook = this + +} + // 20080226 KSC: transfer format, fonts and xf here instead + // of populateWorkbookWithRemoteData() + transferFormatRecs(b, localFonts, boundFonts, localXfs, boundXfs) +} +} + // 20080226 KSC: handle xf's for columns + for (co in bound.colinfos) +{ +transferFormatRecs(co, localFonts, boundFonts, localXfs, boundXfs) +} +val c = bound.charts +for (i in c.indices) +{ +val cht = c[i] as Chart +val fontrefs = cht.fontxRecs +for (x in fontrefs.indices) +{ +val fontx = fontrefs[x] as Fontx +var fid = fontx.ifnt +if (fid > 3) +{ +fid = bound.translateFontIndex(fid, localFonts) +fontx.ifnt = fid +} +} +} +} + +/** + * examine all Ptg's referenced by this formula, looking for hanging or missing sheet references + * if found, sets sheet reference to the current sheet (TODO: a better way?) + * + * @param f Formula Rec + */ + private fun updateFormulaPtgRefs(f:Formula?, origSheetName:String, newSheetName:String, origWorkBookName:String?) { +try +{ +if (f == null) +return // 20100222 KSC +f.populateExpression() +val p = f.cellRangePtgs +for (k in p.indices) +{ +if (p[k] is PtgRef) +{ +val ptg = p[k] as PtgRef +try +{ +if (ptg !is PtgArea3d || ptg +.firstSheet == ptg.lastSheet) +{ +val sheetName = ptg.sheetName +if (sheetName == origSheetName) +ptg.sheetName = newSheetName +ptg.addToRefTracker() + /* + * changed to use above. don't understand this: + * if (!sheetName.equals(origSheetName)) { + * this.getWorkSheetByName(ptg.getSheetName()); + * ptg.setSheetName(newSheetName); + * } else + * ptg.setSheetName(newSheetName); + */ + } +else +{ // uncommon case of two sheet range +val pref = ptg + // this.getWorkSheetByName(pref.getFirstPtg().getSheetName()); + // don't understand this + // this.getWorkSheetByName(pref.getLastPtg().getSheetName()); + ptg.location = ptg.toString() // reset ixti if + // nec. + } +} +catch (we:WorkSheetNotFoundException) { +Logger.logWarn("External Reference encountered upon updating formula references: Worksheet Reference Found: " + ptg.sheetName!!) +ptg.setExternalReference(origWorkBookName) +} + +} +else if (p[k] is PtgExp) +{ +val ptgexp = p[k] as PtgExp +try +{ +val pe = ptgexp.convertedExpression // will fail + // if + // ShrFmla + // hasn't + // been + // input yet + for (j in pe.indices) +{ +if (pe[j] is PtgRef) +{ +val ptg = pe[j] as PtgRef +try +{ +if (ptg is PtgArea3d) +{ // PtgRef3d, + // etc. + this.getWorkSheetByName(ptg +.sheetName) +ptg.location = ptg.toString() // reset + // ixti + // if + // nec. + } + // otherwise, we're good + } +catch (we:WorkSheetNotFoundException) { +Logger.logWarn("External References Not Supported: UpdateFormulaReferences: External Worksheet Reference Found: " + ptg.sheetName!!) +ptg.setExternalReference(origWorkBookName) +} + +} +} +} +catch (e:Exception) { + // if links to "main" ShrFmla, won't be set yet and will + // give exception - see Shrfmla WorkBook.addRecord + } + +} +} +} +catch (e:Exception) { +Logger.logErr("WorkBook.updateFormulaRefs: error parsing expression: $e") +} + +} + +/** + * given a record in an previously external workbook, ensure that xf and font records + * are correctly input into the current workbook and that the pointers are correctly updated + * + * @param b BiffRec + * @param localFonts HashMap of string version of all fonts, font nums in current workbook + * @param boundFonts List of string version of all fonts, font nums in external workbook + * @param localXfs HashMap of string version of all xfs, xf nums in current workbook + * @param boundXfs List of string version of all xfs, xf nums in external workbook + */ + private fun transferFormatRecs(b:BiffRec, localFonts:HashMap, boundFonts:List<*>?, localXfs:HashMap, boundXfs:List<*>) { +val oldXfNum = b.ixfe +val localNum = this +.transferFormatRecs(oldXfNum, localFonts, boundFonts, localXfs, boundXfs) +if (localNum != -1) +b.ixfe = localNum +} + +/** + * given a record in an previously external workbook, ensure that xf and font records + * are correctly input into the current workbook and that the pointers are correctly updated + * + * @param b BiffRec + * @param localFonts HashMap of string version of all fonts, font nums in current workbook + * @param boundFonts List of string version of all fonts, font nums in external workbook + * @param localXfs HashMap of string version of all xfs, xf nums in current workbook + * @param boundXfs List of string version of all xfs, xf nums in external workbook + */ + private fun transferFormatRecs(oldXfNum:Int, localFonts:HashMap, boundFonts:List<*>?, localXfs:HashMap, boundXfs:List<*>):Int { +var localNum = -1 +if (boundXfs.size > oldXfNum) +{// if haven't populatedForTransfer i.e. + // haven't opted to transfer formats + // ... + val origxf = boundXfs[oldXfNum] as Xf // clone xf so modifcations + // don't affect original + if (origxf != null) +{ +/** FONT */ + // must handle font first in order to create xf below + // see if referenced xf + fonts are already in workbook; if + // not, add + val localfNum:Int + // check to see if the font needs to be added + var fnum = origxf.ifnt.toInt() +if (fnum > 3) +fnum-- +val thisFont = boundFonts!![fnum] as Font +val xmlFont = "<" + thisFont.xml + "/>" +val fontNum = localFonts[xmlFont] +if (fontNum != null) +{ // then get the fontnum in this book +localfNum = fontNum.toInt() +} +else +{ // it's a new font for this workbook, add it in +localfNum = this.insertFont(thisFont) + 1 +localFonts[xmlFont] = Integer.valueOf(localfNum) +} + +/** XF */ + val localxf = FormatHandle +.cloneXf(origxf, origxf.font, this) // clone xf so + // modifcations + // don't + // affect + // original + // input "local" versions of format and font + + /** FORMAT */ + val fmt = origxf.format // number format - is null if + // format is general ... + if (fmt != null) + // add if necessary + localxf.formatPattern = fmt.format // adds new + // format + // pattern if + // not found + localxf.setFont(localfNum) + + // now check out to see if xf needs to be added + val xmlxf = localxf.toString() +val xfNum = localXfs[xmlxf] +if (xfNum == null) +{ // insert it into the book +localNum = this.insertXf(localxf) +localXfs[xmlxf] = Integer.valueOf(localNum) +} +else + // already exists in the destination + localNum = xfNum.toInt() + +} +} +return localNum +} + + fun setStringEncodingMode(mode:Int) { +this.sharedStringTable!!.setStringEncodingMode(mode) +} + + fun setDupeStringMode(mode:Int) { +if (mode == ALLOWDUPES) +this.isSharedupes = false +else if (mode == SHAREDUPES) +this.isSharedupes = true +} + +/** + * Returns a Chart Handle + * + * @return ChartHandle a Chart in the WorkBook + */ + @Throws(ChartNotFoundException::class) + fun getChart(chartname:String):Chart { +val cv = this.chartVect +var cht:Chart? = null + // Get by MSODG Drawing Name + for (x in cv.indices) +{ +cht = cv[x] as Chart +val titlemso = cht.msodrawobj +if (titlemso != null) +{ +val mson = titlemso.name // shapeName; +if (mson!!.equals(chartname, ignoreCase = true)) +return cht +} +} +val untitled = chartname == "[Untitled]" + // Try to get by title + for (x in cv.indices) +{ +cht = cv[x] as Chart +val cname = cht.title +if (cname.equals(chartname, ignoreCase = true)) +return cht +else if (untitled && cname == "") +return cht +} +throw ChartNotFoundException(chartname) +} + +/** + * removes the desired chart from the list of charts + * + * @param chartname + * @throws ChartNotFoundException + */ + @Throws(ChartNotFoundException::class) + fun removeChart(chartname:String) { +val cv = this.chartVect +var cht:Chart? = null +for (x in cv.indices) +{ +cht = cv[x] as Chart +if (cht.title.equals(chartname, ignoreCase = true)) +{ +cv.removeAt(x) +return +} +} +throw ChartNotFoundException(chartname) +} + +/** + * NOT 100% IMPLEMENTE YET + * creates the initial records for a Pivot Cache + *

                      A Pivot Cache identifies the data used in a Pivot Table + *

                      NOTE: only SHEET cache sources are supported at this time + * + * @param ref String reference: either reference or named range + * @param sheetName String sheetname + * @param cacheid if > 0, the desired cacheid (useful only in OOXML parsing) + * @return int cacheid + */ + fun addPivotStream(ref:String, sheetName:String, sid:Int):Int { +var sid = sid + // in wb substream, DIRECTLY AFTER STYLE records: + // STYLE/STYLEEX [TableStyle TableStyleElement] [Palette] + // [ClrtClient] + if (sid < 0) +sid = 0 // initial cache id if none already present +val records = this.streamer.records +var z = -1 +var i = records.size - 1 +while (i > 0 && z == -1) +{ +val opcode = (records[i] as BiffRec).opcode.toInt() +if (opcode == XLSConstants.SXADDL.toInt()) +{ // find last cache id and increment + /* + * while (i > 0 && opcode!=SXSTREAMID) + * opcode= ((BiffRec) records.get(i--)).getOpcode(); + * if (opcode==SXSTREAMID) { + * sid= ((SxStreamID) records.get(i+1)).getStreamID() + 1; + * } + */ + z = i + 1 +} +else if (opcode == 4188) + // ClrtClient + z = i + 1 +else if (opcode == XLSConstants.PALETTE.toInt()) +z = i + 1 +else if (opcode == 2192) + // TableStyleElement + z = i + 1 +else if (opcode == 2194) + // StyleEx + z = i + 1 +else if (opcode == XLSConstants.STYLE.toInt()) + // Style + z = i + 1 +i-- +} + +val tx = TableStyles.prototype as TableStyles? // see if + // this is + // really + // necessary + // ... + this.streamer.addRecordAt(tx!!, z++) +val sxid = SxStreamID.prototype as SxStreamID? +this.streamer.addRecordAt(sxid!!, z++) +this.ptstream.add(sxid) // Pivot Cache - +sxid.setStreamID(sid) // cache id +this.streamer.records +.addAll(z, sxid.addInitialRecords(this, ref, sheetName)) + +return sid +} + +/** + * adds the Pivot Cache Directory Storage +Stream records necessary to + * define the pivot cache (==pivot table data) for pivot table(s) + *

                      NOTE: at this time only 1 pivot cache is supported + * + * @param ref Cell Range which identifies pivot table data range + * @param wbh + * @param sId Stream or cachid Id -- links back to SxStream set of records + */ + fun addPivotCache(ref:String, wbh:WorkBookHandle, sId:Int) { +if (pivotCache == null) +{ +pivotCache = PivotCache() +pivotCache!!.createPivotCache(factory!!.leoFile +.directoryArray, wbh, ref, sId) +} +} + +/** + * returns the start of the stream defining the desired pivot cache + * + * @param cacheid + * @return + */ + fun getPivotStream(cacheid:Int):SxStreamID? { + // int z= 0; + for (i in ptstream.indices) +{ +val sid = (ptstream.get(i) as SxStreamID).streamID.toInt() +if (sid == cacheid) + // if (z++==cacheid) + return ptstream.get(i) +} + /* + * List records= this.getStreamer().records; + * for (int i= 0; i < records.size(); i++) { + * int opcode= ((BiffRec) records.get(i)).getOpcode(); + * if (opcode==SXSTREAMID) { + * int sid= ((SxStreamID) records.get(i)).getStreamID() + 1; + * if (sid==cacheid) + * return (SxStreamID) records.get(i); + * } + * } + */ + return null +} + + fun addIndirectFormula(f:Formula) { +indirectFormulas.add(f) +} + +/** + * Initialize the indirect functions in this workbook by calculating the formulas + */ + fun initializeIndirectFormulas() { +val i = indirectFormulas.iterator() // contains all INDIRECT + // funcvars + params + while (i.hasNext()) +{ +val f = i.next() as Formula +f.calculateIndirectFunction() +} +indirectFormulas = ArrayList() // clear out +} + +/** + * Inserts an externsheet into the recvec, provided one does not yet exist. + * also calls add supBook + */ + fun addExternsheet() { +if (myexternsheet == null) +{ +val numsheets = this.numWorkSheets +val sb = Supbook.getPrototype(numsheets) as Supbook + // put it in after the last boundsheet record + try +{ +val b = this.getWorkSheetByNumber(numsheets - 1) +var loc = b.recordIndex + 1 +if (streamer.getRecordAt(loc).opcode == XLSConstants.COUNTRY) +loc++ +streamer.addRecordAt(sb, loc) // 20080306 KSC: do first 'cause + // externsheet now references + // global sb store + this.addRecord(sb, false) +val ex = Externsheet +.getPrototype(0, 0, this) as Externsheet +streamer.addRecordAt(ex, loc + 1)// 20080306 KSC: must inc loc + // since now inserting after + // sb + this.addRecord(ex, false) +myexternsheet = ex +} +catch (e:WorkSheetNotFoundException) { +Logger.logWarn("WorkBook.addExternSheet() locating Sheet for adding Externsheet failed: $e") +} + +} +} + +/** + * add formula to book and init the ptgs + * + * @param rec + */ + fun addFormula(rec:Formula) { +this.formulas.add(rec) +val shn = rec.sheet!!.sheetName + "!" + rec.cellAddress +this.formulashash.put(shn, rec) +} + +/** + * returns true if the default language selected in Excel is one of + * the DBCS (Double-Byte Code Set) languages + *

                      + * The languages that support DBCS include + *

                      + * Japanese, Chinese (Simplified), Chinese (Traditional), and Korean + *
                      + * Language        Country code    Countries/regions
                      + * -------------------------------------------------------------
                      + *
                      + * Arabic                966       (Saudi Arabia)
                      + * Czech                 42        (Czech Republic)
                      + * Danish                45        (Denmark)
                      + * Dutch                 31        (The Netherlands)
                      + * English               1         (The United States of America)
                      + * Farsi                 98        (Iran)
                      + * Finnish               358       (Finland)
                      + * French                33        (France)
                      + * German                49        (Germany)
                      + * Greek                 30        (Greece)
                      + * Hebrew                972       (Israel)
                      + * Hungarian             36        (Hungary)
                      + * Indian                91        (India)
                      + * Italian               39        (Italy)
                      + * Japanese              81        (Japan)
                      + * Korean                82        (Korea)
                      + * Norwegian             47        (Norway)
                      + * Polish                48        (Poland)
                      + * Portuguese (Brazil)   55        (Brazil)
                      + * Portuguese            351       (Portugal)
                      + * Russian               7         (Russian Federation)
                      + * Simplified Chinese    86        (People's Republic of China)
                      + * Spanish               34        (Spain)
                      + * Swedish               46        (Sweden)
                      + * Thai                  66        (Thailand)
                      + * Traditional Chinese   886       (Taiwan)
                      + * Turkish               90        (Turkey)
                      + * Urdu                  92        (Pakistan)
                      + * Vietnamese            84        (Vietnam)
                      +
                      * + * + * @return boolean + */ + fun defaultLanguageIsDBCS():Boolean { +return (this.defaultLanguage == 81 || this.defaultLanguage == 886 +|| this.defaultLanguage == 86 || this.defaultLanguage == 82) + // PROBLEM WITH THIS: POSSIBLE TO BE SET AS DBCS DEFAULT + // LANGUAGE + // BUT HAVE NON-DBCS TEXT or VISA VERSA + + /* + * In a double-byte character set, some characters require + * two bytes, + * while some require only one byte. + * The language driver can distinguish between these two + * types of characters by designating + * some characters as "lead bytes." + * A lead byte will be followed by another byte (a + * "tail byte") to create a + * Double-Byte Character (DBC). + * The set of lead bytes is different for each language. + * + * Lead bytes are always guaranteed to be extended + * characters; no 7-bit ASCII characters + * can be lead bytes. + * The tail byte may be any byte except a NULL byte. + * The end of a string is always defined as the first NULL + * byte in the string. + * Lead bytes are legal tail bytes; the only way to tell if + * a byte is acting as a + * lead byte is from the context. + */ + } + +/** + * clear out ALL sheet object refs + */ + fun closeSheets() { +for (i in sheetVect.size - 1 downTo 1) +{ +val b = sheetVect[i] as Boundsheet +if (b.streamer != null) +{ // do separately because may call + // boundsheet close + b.streamer!!.close() +b.streamer = null +} +b.close() +if (tabID != null) +tabID!!.removeRecord() +} +val recs = this.streamer.biffRecords +val resetVars = recs[0] != null +sheetVect.clear() +for (i in formulas.indices) +{ +val f = formulas[i] as Formula +f.close() +} +this.formulas.clear() +if (this.refTracker != null) +this.refTracker!!.clearCaches() +this.formulashash.clear() + // TODO: handle + this.indirectFormulas.clear() +this.chartVect.clear() +this.chartTemp.clear() +if (firstBOF != null) +{ +firstBOF!!.close() +this.firstBOF = null +} +if (lasteof != null) +{ +lasteof!!.close() +lasteof = null +} +if (resetVars) +{ // just clearing out sheets instead of closing workbook + // reset lasteof for possible new insertion of sheets (if + // not removing workbook) ... + var i = recs.size - 1 +while (i > 0 && lasteof == null) +{ +if (recs[i] != null) +{ +if ((recs[i] as BiffRec).opcode == XLSConstants.EOF) +lasteof = recs[i] as Eof +} +i-- +} +} +if (lastBOF != null) +{ +lastBOF!!.close() +lastBOF = null +} +if (resetVars) +{ // just clearing out sheets instead of closing workbook +if ((recs[0] as BiffRec).opcode == XLSConstants.BOF) +{// should!! +lastBOF = recs[0] as Bof +firstBOF = recs[0] as Bof +} +} +if (lastbound != null) +{ +lastbound!!.close() +this.lastbound = null +} +if (lastFormula != null) +{ +lastFormula!!.close() +this.lastFormula = null +} +this.workSheets.clear() +} + +protected fun closeRecords() {} + +/** + * clear out object references in prep for closing workbook + */ + fun close() { +closeSheets() + +if (this.isExcel2007) +{ +try +{ +val externalDir = OOXMLAdapter +.getTempDir(this.factory!!.fileName) +OOXMLAdapter.deleteDir(File(externalDir)) +} +catch (e:Exception) {} + +} +continueHandler.close() +continueHandler = ContinueHandler(this) + + // clear out array list references + val ii = bookNameRecs.keys.iterator() +while (ii.hasNext()) +{ +val n = bookNameRecs.get(ii.next()) as Name +n.close() +} +this.bookNameRecs.clear() +for (i in xfrecs.indices) +{ +val x = xfrecs[i] as Xf +x.close() +} +this.xfrecs.clear() +this.formatCache.clear() +this.formatlookup.clear() +this.formats.clear() +this.fontRecs.clear() +if (this.dxfs != null) +this.dxfs!!.clear() +for (i in msodgMerge.indices) +{ +val m = msodgMerge[i] as MSODrawingGroup +m.close() +} +this.msodgMerge.clear() +if (this.msodg != null) +{ +msodg!!.close() +msodg = null +} + // TODO: handle + this.mergecelllookup.clear() +this.hlinklookup.clear() +this.externalnames.clear() +for (i in names.indices) +{ +val n = names.get(i) as Name +n.close() +} +this.names.clear() +for (i in indexes.indices) +{ +val ind = indexes.get(i) as Index +ind.close() +} +this.indexes.clear() +if (lastINDEX != null) +{ +lastINDEX!!.close() +lastINDEX = null +} +if (this.sharedStringTable != null) +{ +this.sharedStringTable!!.close() +this.sharedStringTable = null +} + +if (countryRec != null) +{ +this.countryRec!!.close() +this.countryRec = null +} +if (win1 != null) +{ +win1!!.close() +win1 = null +} + +if (this.drh != null) +{ +this.drh!!.close() +drh = null +} + + // integration point for subclasses + this.closeRecords() + +this.continueHandler = ContinueHandler(this) +if (this.currai != null) +{ +this.currai!!.close() +this.currai = null +} +if (calcmoderec != null) +{ +calcmoderec!!.close() +calcmoderec = null +} +this.currchart = null +this.currdrw = null +if (this.protector != null) +{ +this.protector!!.close() +this.protector = null +} +if (this.myexternsheet != null) +{ +this.myexternsheet!!.close() +this.myexternsheet = null +} +if (this.refTracker != null) +{ +this.refTracker!!.close() +this.refTracker = null +} +if (tabID != null) +{ +tabID!!.close() +tabID = null +} + // TODO: deal + this.factory = null +streamer = createByteStreamer() +if (xl2k != null) +(xl2k as XLSRecord).close() +xl2k = null + +} + +companion object { + +private const val serialVersionUID = 2282017774412632087L + + // public constants + var STRING_ENCODING_AUTO = 0 + var STRING_ENCODING_UNICODE = 1 + var STRING_ENCODING_COMPRESSED = 2 + var ALLOWDUPES = 0 + var SHAREDUPES = 1 + + /* + * This is here only to allow client code to compile, should + * be removed + */ + var CONVERTMULBLANKS = "deprecated" +} + +} diff --git a/src/main/java/io/starter/formats/XLS/WorkBookAssembler.java b/src/main/java/io/starter/formats/XLS/WorkBookAssembler.java deleted file mode 100644 index 84d7d6d..0000000 --- a/src/main/java/io/starter/formats/XLS/WorkBookAssembler.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.formats.XLS.charts.Chart; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Random; - -/** - * WorkBookAssembler handles the details of how a biff8 file - * should be constructed, what records go in what order, etc - */ -public class WorkBookAssembler implements XLSConstants { - - - /** - * assembleSheetRecs assembles the array of records, then ouputs - * the ordered list to the bytestreamer, which should be the only - * thing calling this. - *

                      - * The SheetRecs should contain all records excepting valrecs, rows, index, and dbcells at this - * point, where they should be created. - *

                      - * The dbcell creation and population also occurs here. The first pointer is the offset between - * the beginning of the second row and the first valrec. After that it is just incrementing valrecs - *

                      - * TODO: John, please review use of collections & performance. Check the Dbcell.initDbCell method as well. - * TODO: Add handling for creation of mulblank and mulrk records on the fly. We no longer have them! - */ - public static List assembleSheetRecs(Boundsheet thissheet) { - List addVec = new ArrayList(); - WorkBookAssembler.preProcessSheet(thissheet); - addVec.addAll(thissheet.getSheetRecs()); - if (!thissheet.isChartOnlySheet()) { - addVec = WorkBookAssembler.assembleRows(thissheet, addVec); - } - if (thissheet.getWorkBook().getCharts().length > 0) { - addVec = WorkBookAssembler.assembleChartRecs(thissheet, addVec); - } - return addVec; - } - - /** - * Handles functionality that needs to occur before the boundsheet can be - * reliably created - */ - private static void preProcessSheet(Boundsheet thissheet) { - if (thissheet.hasMergedCells()) { - // update mergedcells first, as they may grow in record size and are not handled by continues. - Iterator itx = thissheet.getMergedCellsRecs().iterator(); - while (itx.hasNext()) { - Mergedcells mrg = (Mergedcells) itx.next(); - mrg.update(); - } - } - } - - /** - * Add the rows of data into the worksheet level stream - * - * @param thissheet - * @param addVec - * @return - */ - private static List assembleRows(Boundsheet thissheet, List addVec) { - Dimensions dim = thissheet.getDimensions(); - Random gen = new Random(); - int randomNumber = gen.nextInt(); - int insertRowidx = 0; - if (dim != null) insertRowidx = dim.getRecordIndex() + 1; - int insertValidx = insertRowidx; - int rowCount = 0; // use this to break every 32 rows for a new dbcell - int dbOffset = 0; // the offset between the first row and the dbcell - int maxRow = 0; - int maxCol = 0; - List dbOffsets = new ArrayList(); // dbcell offsets - int valrecOffset = 0; - // KSC: clear out index dbcells - if (thissheet.getSheetIDX() != null) - thissheet.getSheetIDX().resetDBCells(); - // NOTE: if below sorting is time-consuming, should input sort inserting and deleting rows and not here ... - Iterator outRows = thissheet.getSortedRows().keySet().iterator(); - while (outRows.hasNext()) { - if (rowCount == 32) { - // Add a new dbcell for every 32 rows - Dbcell d = (Dbcell) Dbcell.getPrototype(); - dbOffsets.add(0, Short.valueOf((short) ((rowCount - 1) * 20))); - d.initDbCell(dbOffset, dbOffsets); - addVec.add(insertValidx++, d); - if (thissheet.getSheetIDX() != null) - thissheet.getSheetIDX().addDBCell(d); - insertRowidx = insertValidx; - dbOffsets = new ArrayList(); - rowCount = 0; - dbOffset = 0; - } - Row r = (Row) thissheet.getRowMap().get(outRows.next()); - rowCount++; - maxRow = Math.max(r.getRowNumber(), maxRow); - dbOffset += r.getLength(); - addVec.add(insertRowidx++, r); - insertValidx++; - - // insert the valrec and child recs from the row - Mulblank skipMull = null; - List outRecs = r.getValRecs(randomNumber); - Iterator it = outRecs.iterator(); - while (it.hasNext()) { - BiffRec or = (BiffRec) it.next(); - if (skipMull != null) { - if (or == skipMull) - continue; - skipMull = null; - } - addVec.add(insertValidx++, or); - valrecOffset += or.getLength(); - dbOffset += or.getLength(); - short orc = or.getColNumber(); - if ((!it.hasNext()) && orc > maxCol) - maxCol = orc; - if (or.getOpcode() == MULBLANK) - skipMull = (Mulblank) or; - } - dbOffsets.add(Short.valueOf((short) valrecOffset)); - valrecOffset = 0; - } - // add the final dbcell. Chart only sheets will not have an index, so ignore if so. - if (dbOffsets.size() > 0) { - Dbcell d = (Dbcell) Dbcell.getPrototype(); - dbOffsets.add(0, Short.valueOf((short) ((rowCount - 1) * 20))); - d.initDbCell(dbOffset, dbOffsets); - d.setOffset(dbOffset); // KSC: Added to set dbCell offset - addVec.add(insertValidx++, d); - if (thissheet.getSheetIDX() != null) - thissheet.getSheetIDX().addDBCell(d); - } - thissheet.updateDimensions(maxRow, maxCol/* 20100225 KSC: incrementing does not match Excel results: take out +1*/); - return addVec; - } - - /** - * Get the index to the last db cell in the boundsheet - * this is where objects can start being inserted - * - * @return - */ - private static int getLastDBCellLocation(List addVec) { - for (int i = addVec.size() - 1; i >= 0; i--) { - BiffRec b = (BiffRec) addVec.get(i); - if (b.getOpcode() == DBCELL) return i; - } - return 0; - } - - /** - * Add the chart records to the array. There are two types of charts to be added, - * those that have a pointer into the worksheet level stream (ie they existed when the file was parsed) - * and those that have been added post parse and/or are converted from ooxml. - *

                      - * We hold the location of the parsed ones to deal with odd inconsistencies that exist - * with multiple drawing objects and txos - * - * @param thissheet - * @param addVec - * @return - */ - private static List assembleChartRecs(Boundsheet thissheet, List addVec) { - // insert charts that have bound obj records in the stream - int insertValidx = WorkBookAssembler.getLastDBCellLocation(addVec); - ArrayList insertedCharts = new ArrayList(); - int chartInsert = insertValidx; - while (chartInsert < addVec.size()) { - XLSRecord x = (XLSRecord) addVec.get(chartInsert); - if (x.getOpcode() == OBJ) { - Obj o = (Obj) x; - if (o.getChart() != null) { - insertedCharts.add(o.getChart().getTitle()); - List l = o.getChart().assembleChartRecords(); - addVec.addAll(chartInsert + 1, l); - chartInsert += l.size(); - insertValidx += l.size(); - } - } - chartInsert++; - } - - // insert charts that are new, either from transfers, insertions, etc - Chart[] chts = thissheet.getWorkBook().getCharts(); - chartInsert = insertValidx; - for (int i = 0; i < chts.length; i++) { - if (!insertedCharts.contains(chts[i].getTitle()) && chts[i].getSheet().equals(thissheet)) { - // if it's a chart only sheet, insertValidx will be '0' here, put it at the end of the current recordset - if (insertValidx == 0) insertValidx = addVec.size(); - List l = chts[i].assembleChartRecords(); - if (chts[i].getObj() != null) l.add(0, chts[i].getObj()); - if (chts[i].getMsodrawobj() != null) l.add(0, chts[i].getMsodrawobj()); - int spid = 0; - boolean isHeader = false; - if (l.get(0) instanceof MSODrawing) { - isHeader = ((MSODrawing) l.get(0)).isHeader(); - spid = ((MSODrawing) l.get(0)).getSPID(); - } - while (chartInsert < addVec.size()) { - XLSRecord x = (XLSRecord) addVec.get(chartInsert); - if (x.getOpcode() == MSODRAWING) { - MSODrawing mm = (MSODrawing) x; - if ((mm.getSPID() > spid && spid != 0 && !mm.isHeader()) || isHeader) { - insertValidx = chartInsert; - chartInsert = addVec.size(); - } - } - if (x.getOpcode() == WINDOW2 || x.getOpcode() == MSODRAWINGSELECTION - || x.getOpcode() == NOTE) { - insertValidx = chartInsert; - chartInsert = addVec.size(); - } - chartInsert++; - } - addVec.addAll(insertValidx, l); - insertValidx += l.size(); - } - } - return addVec; - } -} diff --git a/src/main/java/io/starter/formats/XLS/WorkBookAssembler.kt b/src/main/java/io/starter/formats/XLS/WorkBookAssembler.kt new file mode 100644 index 0000000..b9eb5ae --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/WorkBookAssembler.kt @@ -0,0 +1,252 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.formats.XLS.charts.Chart + +import java.util.ArrayList +import java.util.Random + +/** + * WorkBookAssembler handles the details of how a biff8 file + * should be constructed, what records go in what order, etc + */ +class WorkBookAssembler : XLSConstants { + companion object { + + + /** + * assembleSheetRecs assembles the array of records, then ouputs + * the ordered list to the bytestreamer, which should be the only + * thing calling this. + * + * + * The SheetRecs should contain all records excepting valrecs, rows, index, and dbcells at this + * point, where they should be created. + * + * + * The dbcell creation and population also occurs here. The first pointer is the offset between + * the beginning of the second row and the first valrec. After that it is just incrementing valrecs + * + * + * TODO: John, please review use of collections & performance. Check the Dbcell.initDbCell method as well. + * TODO: Add handling for creation of mulblank and mulrk records on the fly. We no longer have them! + */ + fun assembleSheetRecs(thissheet: Boundsheet): List<*> { + var addVec: MutableList<*> = ArrayList() + WorkBookAssembler.preProcessSheet(thissheet) + addVec.addAll(thissheet.sheetRecs) + if (!thissheet.isChartOnlySheet) { + addVec = WorkBookAssembler.assembleRows(thissheet, addVec) + } + if (thissheet.workBook!!.charts.size > 0) { + addVec = WorkBookAssembler.assembleChartRecs(thissheet, addVec) + } + return addVec + } + + /** + * Handles functionality that needs to occur before the boundsheet can be + * reliably created + */ + private fun preProcessSheet(thissheet: Boundsheet) { + if (thissheet.hasMergedCells()) { + // update mergedcells first, as they may grow in record size and are not handled by continues. + val itx = thissheet.mergedCellsRecs.iterator() + while (itx.hasNext()) { + val mrg = itx.next() as Mergedcells + mrg.update() + } + } + } + + /** + * Add the rows of data into the worksheet level stream + * + * @param thissheet + * @param addVec + * @return + */ + private fun assembleRows(thissheet: Boundsheet, addVec: MutableList<*>): MutableList<*> { + val dim = thissheet.dimensions + val gen = Random() + val randomNumber = gen.nextInt() + var insertRowidx = 0 + if (dim != null) insertRowidx = dim.recordIndex + 1 + var insertValidx = insertRowidx + var rowCount = 0 // use this to break every 32 rows for a new dbcell + var dbOffset = 0 // the offset between the first row and the dbcell + var maxRow = 0 + var maxCol = 0 + var dbOffsets: MutableList<*> = ArrayList() // dbcell offsets + var valrecOffset = 0 + // KSC: clear out index dbcells + if (thissheet.sheetIDX != null) + thissheet.sheetIDX!!.resetDBCells() + // NOTE: if below sorting is time-consuming, should input sort inserting and deleting rows and not here ... + val outRows = thissheet.sortedRows.keys.iterator() + while (outRows.hasNext()) { + if (rowCount == 32) { + // Add a new dbcell for every 32 rows + val d = Dbcell.prototype as Dbcell? + dbOffsets.add(0, java.lang.Short.valueOf(((rowCount - 1) * 20).toShort())) + d!!.initDbCell(dbOffset, dbOffsets) + addVec.add(insertValidx++, d) + if (thissheet.sheetIDX != null) + thissheet.sheetIDX!!.addDBCell(d) + insertRowidx = insertValidx + dbOffsets = ArrayList() + rowCount = 0 + dbOffset = 0 + } + val r = thissheet.rowMap[outRows.next()] as Row + rowCount++ + maxRow = Math.max(r.rowNumber, maxRow) + dbOffset += r.length + addVec.add(insertRowidx++, r) + insertValidx++ + + // insert the valrec and child recs from the row + var skipMull: Mulblank? = null + val outRecs = r.getValRecs(randomNumber) + val it = outRecs.iterator() + while (it.hasNext()) { + val or = it.next() as BiffRec + if (skipMull != null) { + if (or === skipMull) + continue + skipMull = null + } + addVec.add(insertValidx++, or) + valrecOffset += or.length + dbOffset += or.length + val orc = or.colNumber + if (!it.hasNext() && orc > maxCol) + maxCol = orc.toInt() + if (or.opcode == XLSConstants.MULBLANK) + skipMull = or as Mulblank + } + dbOffsets.add(java.lang.Short.valueOf(valrecOffset.toShort())) + valrecOffset = 0 + } + // add the final dbcell. Chart only sheets will not have an index, so ignore if so. + if (dbOffsets.size > 0) { + val d = Dbcell.prototype as Dbcell? + dbOffsets.add(0, java.lang.Short.valueOf(((rowCount - 1) * 20).toShort())) + d!!.initDbCell(dbOffset, dbOffsets) + d.setOffset(dbOffset) // KSC: Added to set dbCell offset + addVec.add(insertValidx++, d) + if (thissheet.sheetIDX != null) + thissheet.sheetIDX!!.addDBCell(d) + } + thissheet.updateDimensions(maxRow, maxCol/* 20100225 KSC: incrementing does not match Excel results: take out +1*/) + return addVec + } + + /** + * Get the index to the last db cell in the boundsheet + * this is where objects can start being inserted + * + * @return + */ + private fun getLastDBCellLocation(addVec: List<*>): Int { + for (i in addVec.indices.reversed()) { + val b = addVec[i] as BiffRec + if (b.opcode == XLSConstants.DBCELL) return i + } + return 0 + } + + /** + * Add the chart records to the array. There are two types of charts to be added, + * those that have a pointer into the worksheet level stream (ie they existed when the file was parsed) + * and those that have been added post parse and/or are converted from ooxml. + * + * + * We hold the location of the parsed ones to deal with odd inconsistencies that exist + * with multiple drawing objects and txos + * + * @param thissheet + * @param addVec + * @return + */ + private fun assembleChartRecs(thissheet: Boundsheet, addVec: MutableList<*>): MutableList<*> { + // insert charts that have bound obj records in the stream + var insertValidx = WorkBookAssembler.getLastDBCellLocation(addVec) + val insertedCharts = ArrayList() + var chartInsert = insertValidx + while (chartInsert < addVec.size) { + val x = addVec[chartInsert] as XLSRecord + if (x.opcode == XLSConstants.OBJ) { + val o = x as Obj + if (o.chart != null) { + insertedCharts.add(o.chart!!.title) + val l = o.chart!!.assembleChartRecords() + addVec.addAll(chartInsert + 1, l) + chartInsert += l.size + insertValidx += l.size + } + } + chartInsert++ + } + + // insert charts that are new, either from transfers, insertions, etc + val chts = thissheet.workBook!!.charts + chartInsert = insertValidx + for (i in chts.indices) { + if (!insertedCharts.contains(chts[i].title) && chts[i].sheet == thissheet) { + // if it's a chart only sheet, insertValidx will be '0' here, put it at the end of the current recordset + if (insertValidx == 0) insertValidx = addVec.size + val l = chts[i].assembleChartRecords() + if (chts[i].obj != null) l.add(0, chts[i].obj) + if (chts[i].msodrawobj != null) l.add(0, chts[i].msodrawobj) + var spid = 0 + var isHeader = false + if (l[0] is MSODrawing) { + isHeader = (l[0] as MSODrawing).isHeader + spid = (l[0] as MSODrawing).spid + } + while (chartInsert < addVec.size) { + val x = addVec[chartInsert] as XLSRecord + if (x.opcode == XLSConstants.MSODRAWING) { + val mm = x as MSODrawing + if (mm.spid > spid && spid != 0 && !mm.isHeader || isHeader) { + insertValidx = chartInsert + chartInsert = addVec.size + } + } + if (x.opcode == XLSConstants.WINDOW2 || x.opcode == XLSConstants.MSODRAWINGSELECTION + || x.opcode == XLSConstants.NOTE) { + insertValidx = chartInsert + chartInsert = addVec.size + } + chartInsert++ + } + addVec.addAll(insertValidx, l) + insertValidx += l.size + } + } + return addVec + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/WorkBookException.java b/src/main/java/io/starter/formats/XLS/WorkBookException.java deleted file mode 100644 index 68906f1..0000000 --- a/src/main/java/io/starter/formats/XLS/WorkBookException.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -/** - * - */ -public class WorkBookException - extends RuntimeException { - private static final long serialVersionUID = 6406202417397276014L; - - public final static int DOUBLE_STREAM_FILE = 0; - public final static int NOT_BIFF8_FILE = 1; - public final static int LICENSING_FAILED = 2; - public final static int UNSPECIFIED_INIT_ERROR = 3; - public final static int RUNTIME_ERROR = 4; - public final static int SMALLBLOCK_FILE = 5; - public final static int WRITING_ERROR = 6; - public final static int DECRYPTION_ERROR = 7; - public final static int DECRYPTION_INCORRECT_PASSWORD = 8; - public static final int ENCRYPTION_ERROR = 9; - public final static int DECRYPTION_INCORRECT_FORMAT = 10; - - private final int error_code; - - public WorkBookException(String message, int code) { - super(message); - error_code = code; - } - - public WorkBookException(String message, int code, Throwable cause) { - super(message, cause); - error_code = code; - } - - public int getErrorCode() { - return error_code; - } - - public String toString() { - return "WorkBook initialization failed: '" + getMessage() + "'"; - } - - /** - * Obsolete synonym for getCause(). - * - * @deprecated Use {@link Throwable#getCause()} instead. - */ - public Exception getWrappedException() { - Throwable cause = getCause(); - return cause instanceof Exception ? (Exception) cause : null; - } -} diff --git a/src/main/java/io/starter/formats/XLS/WorkBookException.kt b/src/main/java/io/starter/formats/XLS/WorkBookException.kt new file mode 100644 index 0000000..a75deec --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/WorkBookException.kt @@ -0,0 +1,70 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +/** + * + */ +open class WorkBookException : RuntimeException { + + val errorCode: Int + + /** + * Obsolete synonym for `getCause()`. + * + */ + val wrappedException: Exception? + @Deprecated("Use {@link Throwable#getCause()} instead.") + get() { + val cause = cause + return if (cause is Exception) cause else null + } + + constructor(message: String, code: Int) : super(message) { + errorCode = code + } + + constructor(message: String, code: Int, cause: Throwable) : super(message, cause) { + errorCode = code + } + + override fun toString(): String { + return "WorkBook initialization failed: '$message'" + } + + companion object { + private val serialVersionUID = 6406202417397276014L + + val DOUBLE_STREAM_FILE = 0 + val NOT_BIFF8_FILE = 1 + val LICENSING_FAILED = 2 + val UNSPECIFIED_INIT_ERROR = 3 + val RUNTIME_ERROR = 4 + val SMALLBLOCK_FILE = 5 + val WRITING_ERROR = 6 + val DECRYPTION_ERROR = 7 + val DECRYPTION_INCORRECT_PASSWORD = 8 + val ENCRYPTION_ERROR = 9 + val DECRYPTION_INCORRECT_FORMAT = 10 + } +} diff --git a/src/main/java/io/starter/formats/XLS/WorkBookFactory.java b/src/main/java/io/starter/formats/XLS/WorkBookFactory.java deleted file mode 100644 index 8dd2e2b..0000000 --- a/src/main/java/io/starter/formats/XLS/WorkBookFactory.java +++ /dev/null @@ -1,1170 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.LEO.BlockByteConsumer; -import io.starter.formats.LEO.BlockByteReader; -import io.starter.formats.LEO.LEOFile; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import io.starter.toolkit.ProgressListener; - -import java.io.Serializable; -import java.util.Iterator; -import java.util.LinkedHashMap; - - -/** - * Factory for creating WorkBook objects from Byte streams. - * - * @see WorkBook - * @see XLSRecord - */ -public class WorkBookFactory implements io.starter.toolkit.ProgressNotifier, XLSConstants, Serializable { - - public static final long serialVersionUID = 1233423412323l; - protected int DEBUGLEVEL = 0; - - protected LEOFile myLEO; - private String fname; - - public void setDebugLevel(int d) { - DEBUGLEVEL = d; - } - - public int getDebugLevel() { - return DEBUGLEVEL; - } - - // Methods from ProgressNotifier - private ProgressListener progresslistener; - private int progress = 0; - private boolean done = false; - private String progresstext = ""; - - public void register(ProgressListener j) { - progresslistener = j; - j.addTarget(this); - } - - public void fireProgressChanged() { - // if (progresslistener != null) { - // progresslistener.updateProgress(); - // } - } - - public int getProgress() { - return progress; - } - - public String getProgressText() { - return progresstext; - } - - public void setProgress(int progress) { - this.progress = progress; - } - - public void setProgressText(String s) { - progresstext = s; - } - - public boolean iscompleted() { - return done; - } - - // end ProgressNotifier methods - - /** - * get the file name for the WorkBook - */ - public String getFileName() { - if (fname == null) - fname = myLEO.getFileName(); - return fname; - } - - /** - * sets the workbook filename associated with this wbfactory - * - * @param f - */ - public void setFileName(String f) { - fname = f; - } - - /** - * return the next opcode/length in the Stream from the given record. - */ - public short lookAhead(BiffRec rec) { - int i = rec.getOffset() + rec.getLength(); - BlockByteReader parsedata = rec.getByteReader(); - if (parsedata == null) { - return rec.getOpcode(); - } - - byte[] b1 = parsedata.getHeaderBytes(i); - - short opcode = ByteTools.readShort(b1[0], b1[1]); - return opcode; - } - - LEOFile getLEOFile() { - return myLEO; - } - - /** - * read in a WorkBook from a byte array. - */ - public Book getWorkBook(BlockByteReader parsedata, LEOFile leo) throws InvalidRecordException { - Book book = new WorkBook(); - return this.initWorkBook(book, parsedata, leo); - } - - /** - * Initialize the workbook - * - * @param book - * @param parsedata - * @param leo - * @return - * @throws InvalidRecordException - */ - public Book initWorkBook(Book book, BlockByteReader parsedata, LEOFile leo) throws InvalidRecordException { - - BlockByteReader blockByteReader = parsedata; - blockByteReader.setApplyRelativePosition(true); - - /** KSC: record-level validation */ - boolean bPerformRecordLevelValidation = false; // perform record-level validation if set - if (System.getProperty(VALIDATEWORKBOOK) != null) { - if (System.getProperty(VALIDATEWORKBOOK).equals("true")) - bPerformRecordLevelValidation = true; - } - java.util.LinkedHashMap curSubstream = null; - java.util.LinkedHashMap sheetSubstream = null; - if (bPerformRecordLevelValidation) { - java.util.LinkedHashMap globalSubstream = new java.util.LinkedHashMap(); - fillGlobalSubstream(globalSubstream); - sheetSubstream = new java.util.LinkedHashMap(); - fillWorksSheetSubstream(sheetSubstream); - curSubstream = globalSubstream; - } - - myLEO = leo; - - book.setDebugLevel(this.DEBUGLEVEL); - book.setFactory(this); - boolean infile = false; - boolean isWBBOF = true; - short opcode = 0x00, reclen = 0x00, lastOpcode = 0x00; - int BofCount = 0; // track the number of 'Bof' records - - BiffRec rec = null; - int blen = parsedata.getLength(); - - // init the progress listener - this.progresstext = "Initializing Workbook..."; - progress = 0; - if (progresslistener != null) - progresslistener.setMaxProgress(blen); - this.fireProgressChanged(); - if (DEBUGLEVEL > 1) - Logger.logInfo("XLS File Size: " + blen); - - for (int i = 0; i <= blen - 4; ) { - - this.fireProgressChanged(); // "" - byte[] headerbytes = parsedata.getHeaderBytes(i); - opcode = ByteTools.readShort(headerbytes[0], headerbytes[1]); - reclen = ByteTools.readShort(headerbytes[2], headerbytes[3]); - - if (lastOpcode == EOF && (opcode == 0) || (opcode == 0xffffffff)) { - int startpos = i - 3, junkreclen = 0, offset = 0; - - if (offset != 0) - junkreclen = (offset - startpos); - else - junkreclen = blen - i; - i += junkreclen - 1; - i += junkreclen - 1; - } else { // REAL REC - // sanity checks - if (reclen < 0) - throw new InvalidRecordException("WorkBookFactory.getWorkBook() Negative Reclen encountered pos:" + i + " opcode:0x" + Integer.toHexString(opcode)); - else if ((reclen + 1) > blen) - throw new InvalidRecordException("WorkBookFactory.getWorkBook() Reclen longer than data pos:" + i + " opcode:0x" + Integer.toHexString(opcode)); - - if (opcode == BOF || infile) { // if the first Bof has been - // reached, start - infile = true; - - // Init Record' - rec = this.parse(book, opcode, i, reclen, blockByteReader); - - if (progresslistener != null) { - progresslistener.setValue(i); - } - - /**** KSC: record-level validation ****/ - if (bPerformRecordLevelValidation && curSubstream != null) { - markRecord(curSubstream, rec, opcode); - } - - // write to the dump file if necessary - if (WorkBookHandle.dump_input != null) - try { - WorkBookHandle.dump_input.write("-------------------------------------" + "-------------------------\n" + ((XLSRecord) rec).getRecDesc() + ByteTools.getByteDump(blockByteReader.get((XLSRecord) rec, 0, reclen), 0) + "\n"); - WorkBookHandle.dump_input.flush(); - } catch (Exception e) { - Logger.logErr("error writing to dump file, " + "ceasing dump output: " + e); - WorkBookHandle.dump_input = null; - } - - if (rec == null) { // Effectively an EOF - if (DEBUGLEVEL > 0) - Logger.logInfo("done parsing WorkBook storage."); - this.done = true; - this.progresstext = "Done Reading WorkBook."; - this.fireProgressChanged(); - return book; - } else { - // not used anymore ((XLSRecord)rec).resetCacheBytes(); - } - // int reco = rec.getOffset() ; - // int recl = rec.getLength(); - int thisrecpos = i + reclen + 4; - - if (opcode == BOF) { - if (isWBBOF) { // do first Bof initialization - book.setFirstBof((io.starter.formats.XLS.Bof) rec); - isWBBOF = false; - } else if (bPerformRecordLevelValidation && BofCount == 0 && lastOpcode != EOF && curSubstream != null) { - /***** KSC: record-level validation ****/ - // invalid record structure- no EOF before BOF - validateRecords(curSubstream, book, rec.getSheet()); - } - - BofCount++; - /***** KSC: record-level validation ****/ - if (bPerformRecordLevelValidation && curSubstream == null) { - // after global substream is processed, switch to sheet substream - reInitSubstream(sheetSubstream); - curSubstream = sheetSubstream; - curSubstream.get(BOF).isPresent = true; - curSubstream.get(BOF).recordPos = 0; - } - } else if (opcode == EOF) { - BofCount--; - - /***** KSC: record-level validation ****/ - if (bPerformRecordLevelValidation && BofCount == 0 && curSubstream != null) { - validateRecords(curSubstream, book, rec.getSheet()); - curSubstream = null; - } - } - // end of Workbook - if (BofCount == -1) { - if (DEBUGLEVEL > 2) - Logger.logInfo("Last Bof"); - i += reclen; - thisrecpos = blen; - } - if (thisrecpos > 0) - i = thisrecpos; - lastOpcode = opcode; - } else { - throw new InvalidRecordException("No valid record found."); - } - - } - } - if (DEBUGLEVEL > 0) - Logger.logInfo("done"); - progress = blen; - this.progresstext = "Done Reading WorkBook."; - this.fireProgressChanged(); - - this.done = true; - // flag the book so we know it's ready for shared access - // book.setReady(true); ENTERPRISE ONLY - // recordata.setApplyRelativePosition(false); - return book; - } - - /** - * create the individual records based on type - */ - protected synchronized BiffRec parse(Book book, short opcode, int offset, int datalen, BlockByteReader bytebuf) throws InvalidRecordException { - - // sanity checks - if ((datalen < 0) || (datalen > XLSConstants.MAXRECLEN)) - throw new InvalidRecordException("InvalidRecordException BAD RECORD LENGTH: " + " off: " + offset + " op: " + Integer.toHexString(opcode) + " len: " + datalen); - if (offset + datalen > bytebuf.getLength()) - throw new InvalidRecordException("InvalidRecordException RECORD LENGTH LONGER THAN FILE: " + " off: " + offset + " op: " + Integer.toHexString(opcode) + " len: " + datalen + " buflen:" + bytebuf.getLength()); - - // Create a new Record - BiffRec rec = XLSRecordFactory.getBiffRecord(opcode); - - // init the mighty rec - rec.setWorkBook((WorkBook) book); - rec.setByteReader(bytebuf); - rec.setLength((short) datalen); - rec.setOffset(offset); - rec.setDebugLevel(this.DEBUGLEVEL); - rec.setStreamer(book.getStreamer()); - - // send it to the CONTINUE handler - book.getContinueHandler().addRec(rec, (short) datalen); - // add it to the record stream - return book.addRecord(rec, true); - } - - /** - * Find the location of the next particular opcode - */ - protected static int getNextOpcodeOffset(short op, BlockByteConsumer rec, BlockByteReader parsedata) { - boolean found = false; - int x = rec.getOffset(); - short opcode = 0x0; - while (!found && x < parsedata.getLength() - 2) { - opcode = ByteTools.readShort(parsedata.get(rec, x), parsedata.get(rec, ++x)); - if (opcode == op) { - found = true; - break; - } - } - if (!found) - return 0; - return x - 3; - } - - /** - * record-level validation: - *

                    • have ordered list of records for each substream (wb/global, worksheet) - *
                    • upon each record that is processed, look up in list and mark present + record pos in correseponding list (streamer or sheet) - *
                    • upon EOF for stream, traverse thru list, if required and not present, add - *
                      Limitations: - * This methodology does NOT validate Chart records, Chart-only sheets, Macro Sheets, Dialog Sheets or Custom Views - */ - /** - * fill map with EVERY possible global (workbook)-level record, IN ORDER, plus flag if they are required or not - * Used in record-level validation - * - * @param map - */ - private void fillGlobalSubstream(LinkedHashMap map) { - // ordered list of all records in the global (workbook) substream, along - // with if they are required or not - map.put(BOF, new R(true)); - map.put((short) 134, new R(false)); // WriteProtect - map.put(FILEPASS, new R(false)); - map.put((short) 96, new R(false)); // Template - map.put(INTERFACE_HDR, new R(true)); - map.put((short) 193, new R(true)); // Mms - map.put((short) 226, new R(true)); // InterfaceEnd - map.put((short) 92, new R(true)); // WriteAccess - map.put((short) 91, new R(false)); // FileSharing - map.put((short) 66, new R(true)); // CodePage - map.put((short) 441, new R(false)); // LEL - SHOULD BE is required?? - map.put((short) 353, new R(true)); // DSF - is required?? - map.put((short) 448, new R(false)); // Excel9File - map.put(TABID, new R(true)); - map.put((short) 211, new R(false)); // ObjProj - map.put((short) 445, new R(false)); // *[ObNoMacros] - map.put(CODENAME, new R(false)); - map.put((short) 156, new R(false)); // BuiltInFnGroupCount - map.put((short) 154, new R(false)); // FnGroupName - map.put((short) 2200, new R(false)); // FnGrp12 - map.put((short) 222, new R(false)); // OleObjectSize - map.put(WINDOW_PROTECT, new R(true)); - map.put(PROTECT, new R(true)); - map.put(PASSWORD, new R(true)); - map.put(PROT4REV, new R(true)); - map.put((short) 444, new R(true)); // Prot4RevPass - map.put(WINDOW1, new R(true)); - map.put(BACKUP, new R(true)); - map.put((short) 141, new R(true)); // HideObj - map.put(DATE1904, new R(true)); - map.put((short) 14, new R(true)); // CalcPrecision - map.put((short) 439, new R(true)); // RefreshAll - map.put(BOOKBOOL, new R(true)); - map.put(FONT, new R(true)); // Is required??? - map.put(FORMAT, new R(true)); // Is required?? - map.put(XF, new R(true)); // Is required??? - map.put((short) 2172, new R(false)); // XfCRC - map.put((short) 2173, new R(false)); // XfExt - map.put((short) 2189, new R(false)); // DXF- Is required??? - map.put(STYLE, new R(true)); // Is required??? - map.put((short) 2194, new R(false)); // StyleExt - map.put(TABLESTYLES, new R(false)); - map.put((short) 2191, new R(false)); // TableStyle - map.put((short) 2192, new R(false)); // TABLESTYLEELEMENT - map.put(PALETTE, new R(false)); - map.put((short) 4188, new R(false)); // CLRTCLIENT - map.put(SXSTREAMID, new R(false)); - map.put(SXVS, new R(false)); - map.put(DCONNAME, new R(false)); - map.put(DCONBIN, new R(false)); - map.put(DCONREF, new R(false)); - map.put((short) 208, new R(false)); // SXTbl - map.put((short) 210, new R(false)); // SxTbpg - map.put((short) 209, new R(false)); // SXTBRGIITM - map.put(SXSTRING, new R(false)); - map.put((short) 220, new R(false)); // DbOrParamQry - map.put(SXADDL, new R(false)); - - map.put((short) 184, new R(false)); // DocRoute - map.put((short) 185, new R(false)); // RECIPNAME - map.put(USERBVIEW, new R(false)); // SHOULD BE Is but isn't present - // ************************* - map.put((short) 352, new R(true)); // UsesELFs - map.put(BOUNDSHEET, new R(true)); - map.put((short) 2180, new R(false)); // MDTInfo - // *ContinueFrt12 - map.put((short) 2181, new R(false)); // MDXStr - // *ContinueFrt12 - map.put((short) 2182, new R(false)); // MDXTuple - // *ContinueFrt12 - map.put((short) 2183, new R(false)); // MDXSet - map.put((short) 2184, new R(false)); // MDXProp - map.put((short) 2185, new R(false)); // MDXKPI - map.put((short) 2186, new R(false)); // MDB - map.put((short) 2202, new R(false)); // MTRSettings - map.put((short) 2211, new R(false)); // ForceFullCalculation - map.put(COUNTRY, new R(true)); - map.put(SUPBOOK, new R(false)); // SHOULD BE IsRequired but isn't present ****************** - map.put(EXTERNNAME, new R(false)); - map.put(XCT, new R(false)); - map.put(CRN, new R(false)); - map.put(EXTERNSHEET, new R(false)); - map.put(NAME, new R(false)); // Name is Required??? - map.put((short) 2196, new R(false)); // NameCmt - map.put((short) 2201, new R(false)); // NameFnGrp12 - map.put((short) 2195, new R(false)); // NamePublish - map.put((short) 2067, new R(false)); // RealTimeData A required record - // is not present: 430 - // *ContinueFrt - map.put((short) 449, new R(false)); // RecalcId - map.put((short) 2150, new R(false)); // HFPicture should be required???? - map.put(MSODRAWINGGROUP, new R(false)); // should be required?? - // Continue - map.put(SST, new R(true)); // should be required?? - // Continue - map.put(EXTSST, new R(true)); - map.put((short) 2049, new R(false)); // WebPub should be required??? - map.put((short) 2059, new R(false)); // WOpt - map.put((short) 2149, new R(false)); // CrErr - map.put((short) 2147, new R(false)); // BookExt - map.put((short) 2151, new R(false)); // FeatHdr should be required??? - map.put((short) 2166, new R(false)); // DConn should be required??? - map.put((short) 2198, new R(false)); // Theme - // *ContinueFrt12 - map.put((short) 2203, new R(false)); // CompressPictures - map.put((short) 2188, new R(false)); // Compat12 - map.put((short) 2199, new R(false)); // GUIDTypeLib - map.put(EOF, new R(true)); - } - - /** - * fill map with EVERY possible worksheet-level record, IN ORDER, plus flag if they are required or not. - * Used in record-level validation - * - * @param map - */ - private void fillWorksSheetSubstream(LinkedHashMap map) { - map.put(BOF, new R(true)); - map.put((short) 94, new R(false)); // Uncalced - map.put(INDEX, new R(true)); - map.put(CALCMODE, new R(true)); - map.put(CALCCOUNT, new R(true)); - map.put((short) 15, new R(true)); //CalcRefMode - map.put((short) 17, new R(true)); // CalcIter - map.put((short) 16, new R(true)); // CalcDelta - map.put((short) 95, new R(true)); // CalcSaveRecalc - map.put(PRINTROWCOL, new R(true)); - map.put(PRINTGRID, new R(true)); - map.put((short) 130, new R(true)); // GridSet - map.put(GUTS, new R(true)); - map.put(DEFAULTROWHEIGHT, new R(true)); - map.put(WSBOOL, new R(true)); - map.put((short) 151, new R(false)); // [Sync] - map.put((short) 152, new R(false)); // [LPr] - map.put(HORIZONTAL_PAGE_BREAKS, new R(false)); //[HorizontalPageBreaks] - map.put(VERTICAL_PAGE_BREAKS, new R(false)); //[VerticalPageBreaks] - map.put(HEADERREC, new R(true)); - map.put(FOOTERREC, new R(true)); - map.put(HCENTER, new R(true)); - map.put(VCENTER, new R(true)); - map.put(LEFT_MARGIN, new R(false)); - map.put(RIGHT_MARGIN, new R(false)); - map.put(TOP_MARGIN, new R(false)); - map.put(BOTTOM_MARGIN, new R(false)); - map.put(PLS, new R(false)); -// Continue - map.put(SETUP, new R(true)); - map.put((short) 0x89c, new R(false)); //[HeaderFooter] - map.put((short) 233, new R(false)); //[ BkHim ] - map.put((short) 1048, new R(false)); // BigName -// *ContinueBigName - map.put(PROTECT, new R(false)); - map.put(SCENPROTECT, new R(false)); - map.put(OBJPROTECT, new R(false)); - map.put(PASSWORD, new R(false)); - map.put(DEFCOLWIDTH, new R(true)); - map.put(COLINFO, new R(false)); - map.put((short) 174, new R(false)); // [ScenMan *( - map.put((short) 175, new R(false)); // SCENARIO - // Continue - map.put((short) 144, new R(false)); // Sort - map.put((short) 2197, new R(false)); // SortData - // ContinueFrt12 - map.put((short) 155, new R(false)); // Filtermode - map.put((short) 2164, new R(false)); // DropDownObjIds - map.put((short) 157, new R(false)); // AutoFilterInfo - map.put(AUTOFILTER, new R(false)); - map.put((short) 2174, new R(false)); // AutoFilter12 - map.put((short) 2197, new R(false)); // SortData -//*ContinueFrt12] - map.put(DIMENSIONS, new R(true)); -// [CELLTABLE] - map.put(ROW, new R(false)); - // celltable entries - map.put(BLANK, new R(false)); - map.put(MULBLANK, new R(false)); - map.put(RK, new R(false)); - map.put(BOOLERR, new R(false)); - map.put(NUMBER, new R(false)); - map.put(LABELSST, new R(false)); - map.put((short) 94, new R(false)); // uncalced - map.put(FORMULA, new R(false)); - map.put(MULRK, new R(false)); - map.put(STRINGREC, new R(false)); - map.put(SHRFMLA, new R(false)); - map.put(ARRAY, new R(false)); - map.put(TABLE, new R(false)); - map.put((short) 450, new R(false)); // EntExU2 -// [OBJECTS] - map.put(MSODRAWING, new R(false)); - map.put(TXO, new R(false)); - map.put(OBJ, new R(false)); - map.get(OBJ).altPrecedor = new short[]{MSODRAWING}; // Obj can follow MSODRAWING or TXO - //Charts -- most record opcodes are > 4000 - ignore !! - map.put(CHARTFRTINFO, new R(false)); - // do not include the majority of chart records but do include these: - map.put(STARTBLOCK, new R(false)); - map.put(CRTLAYOUT12, new R(false)); - map.put(CRTLAYOUT12A, new R(false)); - map.put(CATLAB, new R(false)); - map.put(DATALABEXT, new R(false)); - map.put(DATALABEXTCONTENTS, new R(false)); - map.put((short) 2138, new R(false)); // FrtFontList - map.put((short) 2213, new R(false)); // TextPropsStream - map.put((short) 2214, new R(false)); // RichTextStream - map.put((short) 2212, new R(false)); // ShapePropsStream - map.put((short) 2206, new R(false)); // CtrlMlFrt - map.put(ENDBLOCK, new R(false)); - map.put(STARTOBJECT, new R(false)); - map.put(FRTWRAPPER, new R(false)); - map.put(ENDOBJECT, new R(false)); -// CHARTSHEETCONTENT = BOF [WriteProtect] [SheetExt] [WebPub] *HFPicture PAGESETUP PrintSize [HeaderFooter] [BACKGROUND] *Fbi *Fbi2 [ClrtClient] [PROTECTION] [Palette] [SXViewLink] [PivotChartBits] [SBaseRef] [MsoDrawingGroup] OBJECTS Units CHARTFOMATS SERIESDATA *WINDOW *CUSTOMVIEW [CodeName] [CRTMLFRT] EOF -//Chart Begin *2FONTLIST SclPlotGrowth [FRAME] *SERIESFORMAT *SS ShtProps *2DFTTEXT AxesUsed 1*2AXISPARENT [CrtLayout12A] [DAT] *ATTACHEDLABEL [CRTMLFRT] *([DataLabExt StartObject] ATTACHEDLABEL [EndObject]) [TEXTPROPS] *2CRTMLFRT End - map.put(MSODRAWINGSELECTION, new R(false)); - map.put((short) 2150, new R(false)); //*HFPicture - map.put(NOTE, new R(false)); - //*PIVOTVIEW - map.put(SXVIEW, new R(false)); - map.put(SXVD, new R(false)); - map.put(SXVI, new R(false)); - map.put(SXVDEX, new R(false)); - map.put(SXIVD, new R(false)); - map.put(SXPI, new R(false)); - map.put(SXDI, new R(false)); - map.put(SXLI, new R(false)); - map.put(SXEX, new R(false)); - map.put((short) 247, new R(false)); //SXSelect - map.put((short) 240, new R(false)); // SXRULE - map.put(SXFORMAT, new R(false)); -// PIVOTRULE - map.put((short) 244, new R(false)); // SXDXF - map.put(QSISXTAG, new R(false)); - //DBQUERYEXT - map.put((short) 2051, new R(false)); // DBQUERYEX - map.put((short) 2052, new R(false)); // EXTSTRING - map.put(SXSTRING, new R(false)); - map.put((short) 2060, new R(false)); // SXVIEWEX - map.put((short) 2061, new R(false)); // SXTH - map.put((short) 2062, new R(false)); // SXPIEx - map.put((short) 256, new R(false)); // SXVDTEx - map.put(SXVIEWEX9, new R(false)); - map.put(SXADDL, new R(false)); - - map.put(DCON, new R(false)); - map.put(DCONNAME, new R(false)); - map.put(DCONBIN, new R(false)); - map.put(DCONREF, new R(false)); - map.put(WINDOW2, new R(true)); - map.put(PLV, new R(false)); - map.put(SCL, new R(false)); - map.put(PANE, new R(false)); - map.put(SELECTION, new R(false)); - map.put((short) 426, new R(false)); // UserSViewBegin -/* map.put(SELECTION, new R(false)); - map.put(HORIZONTAL_PAGE_BREAKS, new R(false)); - map.put(VERTICAL_PAGE_BREAKS, new R(false)); - map.put(HEADERREC, new R(false)); - map.put(FOOTERREC, new R(false)); - map.put(HCENTER, new R(false)); - map.put(VCENTER, new R(false)); - map.put(LEFT_MARGIN, new R(false)); - map.put(RIGHT_MARGIN, new R(false)); - map.put(TOP_MARGIN, new R(false)); - map.put(BOTTOM_MARGIN, new R(false)); - map.put(PLS, new R(false)); - map.put(SETUP, new R(false)); - map.put((short) 51, new R(false)); //([PrintSize] - map.put((short) 2204, new R(false)); //[HeaderFooter]*/ - // here ??? [AUTOFILTER] - map.put((short) 427, new R(false)); //UserSViewEnd - - map.put((short) 319, new R(false)); // RRSort - map.put((short) 153, new R(false)); //[DxGCol] - map.put(MERGEDCELLS, new R(false)); //*MergeCells - map.put((short) 351, new R(false)); // [LRng] -// *QUERYTABLE - map.put(PHONETIC, new R(false)); - map.put(CONDFMT, new R(false)); - map.put(CF, new R(false)); - map.put(CONDFMT12, new R(false)); - map.put(CF12, new R(false)); - map.put((short) 2171, new R(false)); // CFEx - map.put(HLINK, new R(false)); - map.put((short) 2048, new R(false)); //HLinkTooltip - map.put(DVAL, new R(false)); - map.put(DV, new R(false)); - map.put(CODENAME, new R(false)); // [CodeName] - map.put((short) 2049, new R(false)); // *WebPub - map.put((short) 2156, new R(false)); // *CellWatch - map.put((short) 2146, new R(false)); // SheetExt] - map.put(FEATHEADR, new R(false)); - map.put((short) 2152, new R(false)); // FEAT -// *FEAT11 -// *RECORD12 - map.put((short) 2248, new R(false)); // UNKNOWN RECORD!!! - map.put(EOF, new R(true)); - } - - /** - * add all missing REQUIRED records from the appropriate substream for record-level validation - * - * @param map substream list storing if present, required ... - * @param book - */ - private void validateRecords(LinkedHashMap map, Book book, Boundsheet bs) { - // use array instead of iterator as may have to traverse more than once - Short[] opcodes = new Short[0]; - java.util.Set ss = map.keySet(); - opcodes = ss.toArray(opcodes); - R lastR = new R(false); - lastR.recordPos = 0; - int lastOp = -1; - if (bs != null && bs.isChartOnlySheet()) - return; // don't validate chart-only sheets - - // traverse thru stream list, ensuring required records are present; create if not - for (int i = 0; i < opcodes.length; i++) { - short op = opcodes[i]; - R r = map.get(op); - if (!r.isPresent && r.isRequired) { - // io.starter.toolkit.Logger.log("A required record is not present: " + op); - // Create a new Record - BiffRec rec = createMissingRequiredRecord(op, book, bs); - rec.setDebugLevel(this.DEBUGLEVEL); - int recPos = lastR.recordPos + 1; - try { - while (true) { // now get to LAST record if there are multiple records - BiffRec lastRec = (bs == null) ? book.getStreamer().getRecordAt(recPos) : (BiffRec) bs.getSheetRecs().get(recPos); - if (lastRec.getOpcode() != lastOp) - break; - recPos++; - } - } catch (IndexOutOfBoundsException e) { - } - book.getStreamer().addRecordAt(rec, recPos); - book.addRecord(rec, false); // false= already added to streamer or sheet - if (bs != null) - rec.setSheet(bs); - - r.recordPos = rec.getRecordIndex(); - - // now must adjust ensuing record positions to account for inserted record - for (int zz = 0; zz < opcodes.length; zz++) { - R nextR = map.get(opcodes[zz]); - if (nextR.isPresent && !r.equals(nextR) && nextR.recordPos >= r.recordPos) - nextR.recordPos++; - } - r.isPresent = true; - } - if (r.isPresent) { - lastR = r; - lastOp = op; - } - } - // go thru 1 more time to ensure record order is correct - validateRecordOrder(map, ((bs == null) ? book.getStreamer().records : bs.getSheetRecs()), opcodes); - } - - /** - * given substream map, actual list of records and list of ordered opcodes, validate record order in substream - *
                      if necessary, reorgainze records to follow order in map - * - * @param map ordered map of substream records indexed by opcodes - * @param list list of actual records - * @param opcodes ordered list of opcodes - */ - private void validateRecordOrder(LinkedHashMap map, java.util.List list, Short[] opcodes) { - /* debugging: - io.starter.toolkit.Logger.log("BeFORE order:"); - for (int zz= 0; zz < list.size(); zz++) { - io.starter.toolkit.Logger.log(zz + "-" + list.get(zz)); - }*/ - R lastR = map.get(BOF); - short lastOp = BOF; - for (int i = 1; i < opcodes.length; i++) { - short op = opcodes[i]; - R r = map.get(op); - if (r.isPresent) { - // compare ordered list of records (R) to actual order (denoted via recordPos) - if (r.recordPos < lastR.recordPos && r.recordPos >= 0) { // Out Of Order (NOTE: CellTable entries will have a record pos = -1) - if (r.altPrecedor != null) { // record can have more than 1 valid predecessor - // TODO: this is ugly - do a different way ... - for (int zz = 0; zz < r.altPrecedor.length; zz++) { - R newR = map.get(r.altPrecedor[zz]); - if (r.recordPos > newR.recordPos) { - lastR = newR; - break; - } - } - if (r.recordPos > lastR.recordPos) - continue; - } - - int origRecPos = r.recordPos; -//io.starter.toolkit.Logger.log("Record out of order: r:" + op + "/" + r.recordPos + " lastR:" + lastOp + "/" + lastR.recordPos); - // find correct insertion point by looking at ordered map - for (int zz = i - 1; zz > 0; zz--) { - R prevr = map.get(opcodes[zz]); - if (prevr.isPresent && r.recordPos < prevr.recordPos) { -//io.starter.toolkit.Logger.log("\tInsert at " + prevr.recordPos + " before op= " + opcodes[zz]); - int recsMovedCount = 0; - BiffRec recToMove = (BiffRec) list.get(origRecPos); - do { - list.remove(origRecPos); - list.add(prevr.recordPos /*+ recsMovedCount*/, recToMove); - if (recsMovedCount == 0) - r.recordPos = recToMove.getRecordIndex(); -//io.starter.toolkit.Logger.log("\tMoved To " + recToMove.getRecordIndex()); - recsMovedCount++; - recToMove = (BiffRec) list.get(origRecPos); - } while (recToMove.getOpcode() == op); - - // after moved all the records necessary, adjust record positions - for (int jj = 0; jj < opcodes.length; jj++) { - R nextR = map.get(opcodes[jj]); - if (nextR.isPresent && nextR.recordPos >= origRecPos && nextR.recordPos <= r.recordPos && opcodes[jj] != op) - nextR.recordPos -= recsMovedCount; - } - break; - } - } - } - lastR = r; - lastOp = op; - } - } - /*io.starter.toolkit.Logger.log("AFTER order:"); - for (int zz= 0; zz < list.size(); zz++) { - io.starter.toolkit.Logger.log(zz + "-" + list.get(zz)); - }*/ - } - - // TODO: handle fonts,formats,xf, style -- what's minimum necessary?? - // TODO: handle TabId - // TODO: is Index OK? - - /** - * create missing required records for record-level validation - * - * @param opcode opcode of missing record - * @param book - * @param bs boundsheet- if null it's a wb level record - * @return new BiffRec - */ - private BiffRec createMissingRequiredRecord(short opcode, Book book, Boundsheet bs) { - BiffRec record = XLSRecordFactory.getBiffRecord(opcode); - if (bs != null) - record.setSheet(bs); - byte[] data = null; - try { - switch (opcode) { - case INTERFACE_HDR: - data = new byte[]{(byte) 0xB0, 0x04}; //codePage (2 bytes): An unsigned integer that specifies the code page. 1200==Unicode - break; - case (short) 193: // Mms - data = new byte[]{0, 0}; - break; - case (short) 226: // InterfaceEnd - data = new byte[]{}; - break; - case (short) 92: // WriteAccess -- user name - can be all blank - data = new byte[]{ - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20}; - break; - case (short) 66: // CodePage same as interfacehdr 1200==Unicode - data = new byte[]{(byte) 0xB0, 0x04}; - break; - case (short) 353: // DSF- truly is required? - data = new byte[]{0, 0}; - break; - case TABID: // - // count how many sheets in wb - int nSheets = 0; - for (int z = book.getStreamer().records.size() - 1; z > 0; z--) { - BiffRec b = (BiffRec) book.getStreamer().records.get(z); - if (b.getOpcode() == BOUNDSHEET) { - while (z > 0 && b.getOpcode() == BOUNDSHEET) { - nSheets++; - b = (BiffRec) book.getStreamer().records.get(--z); - } - break; - } - } - data = new byte[]{}; - for (int i = 0; i < nSheets; i++) { - data = ByteTools.append(ByteTools.shortToLEBytes((short) i), data); - } - break; - case WINDOW_PROTECT: - data = new byte[]{0, 0}; - break; - case PROTECT: - data = new byte[]{0, 0}; - break; - case PASSWORD: - data = new byte[]{0, 0}; - break; - case PROT4REV: - data = new byte[]{0, 0}; - break; - case (short) 444: // Prot4RevPass - data = new byte[]{0, 0}; - break; - case WINDOW1: // very general window settings - data = new byte[]{(byte) 0xE0, 1, 0x69, 0, 0x13, 0x38, 0x1F, 0x1D, 0x38, 0, 0, 0, 0, 0, 1, 0, 0x58, 0x02}; - break; - case BACKUP: - data = new byte[]{0, 0}; - break; - case (short) 141: // HideObj - data = new byte[]{0, 0}; - break; - case DATE1904: - data = new byte[]{0, 0}; - break; - case (short) 14: // CalcPrecision - data = new byte[]{1, 0}; - break; - case (short) 439: // RefreshAll - data = new byte[]{0, 0}; - break; - case BOOKBOOL: - data = new byte[]{0, 0}; - break; - case FONT: // truly required?? - data = new byte[]{(byte) 0xC8, 0, 0, 0, (byte) 0xFF, 0x7F, (byte) 0x90, 1, 0, 0, 0, 0, 0, 0, 5, 1, 0x41, 0, 0x72, 0, 0x69, 0, 0x61, 0, 0x6C, 0}; - break; -// case FORMAT: // truly required?? -// break; - case XF: // truly is required?? - data = new byte[]{0, 0, 0, 0, (byte) 0xF5, (byte) 0xFF, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xC0, 0x20}; - break; -// case STYLE: // truly is required? -// break; - case COUNTRY: - data = new byte[]{1, 0, 1, 0}; - break; - case SST: - data = new byte[]{0, 0, 0, 0, 0, 0, 0, 0}; - break; - case EXTSST: - data = new byte[]{0, 0}; // should be rebuilt upon output ... - break; - case (short) 352: // UsesELFs - data = new byte[]{0, 0}; - break; - case INDEX: - data = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0x4E, 6, 0, 0}; - break; - case CALCMODE: - data = new byte[]{1, 0}; - break; - case CALCCOUNT: - data = new byte[]{0x64, 0}; - break; - case 15: //CalcRefMode - data = new byte[]{1, 0}; - break; - case 17: // CalcIter - data = new byte[]{0, 0}; - break; - case 16: // CalcDelta An Xnum value that specifies the amount of change in value for a given cell from the previously calculated value for that cell that MUST exist for the iteration to continue. The value MUST be greater than or equal to 0. - data = new byte[]{(byte) 0xFC, (byte) 0xA9, (byte) 0xF1, (byte) 0xD2, 0x4D, 0x62, 0x50, 0x3F}; - case 95: // CalcSaveRecalc - data = new byte[]{1, 0}; - break; - case PRINTROWCOL: - data = new byte[]{0, 0}; - break; - case PRINTGRID: - data = new byte[]{0, 0}; - break; - case 130: // GridSet - data = new byte[]{0, 0}; - break; - case GUTS: - data = new byte[]{0, 0, 0, 0, 0, 0, 0, 0}; - break; - case DEFAULTROWHEIGHT: - data = new byte[]{0, 0, (byte) 0xFF, 0}; - break; - case WSBOOL: - data = new byte[]{(byte) 0xC1, 4}; - break; - case HEADERREC: - case FOOTERREC: - data = new byte[]{}; - break; - case HCENTER: - data = new byte[]{0, 0}; - break; - case VCENTER: - data = new byte[]{0, 0}; - break; - case SETUP: - data = new byte[]{ - 0, 0, (byte) 0xFF, 0, 1, 0, 1, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xE0, 0x3F, 0, 0, 0, 0, 0, 0, (byte) 0xE0, 0x3F, 0, 0}; - break; - case DEFCOLWIDTH: - data = new byte[]{8, 0}; - break; - case DIMENSIONS: - data = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - record.setData(data); - if (bs != null) { // see if rows have already been added - bs.setDimensions((Dimensions) record); - if (bs.getRowMap().size() > 0) { - int z = bs.getRows()[bs.getRowMap().size() - 1].getRowNumber(); - ((Dimensions) record).setRowLast(z); - } - -// z= ((Colinfo)bs.getColinfos().get(bs.getColinfos().size())).getColLast(); -// ((Dimensions) record).setColLast(z); - } - break; - case WINDOW2: - data = new byte[]{(byte) 0xB6, 6, 0, 0, 0, 0, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - break; - case EOF: - break; - default: - io.starter.toolkit.Logger.log("Must create required rec: " + opcode); - } - - - record.setData(data); - try { - record.init(); - } catch (Exception e) { - // TODO: ExtSst needs sst ... - } - - } catch (Exception e) { - Logger.logErr("Record Validation: Error creating missing record: " + opcode); - } - return record; - - } - - /** - * reset Record members in stream so can be used again for record-level validation - * - * @param map - */ - private void reInitSubstream(LinkedHashMap map) { - Iterator ii = map.keySet().iterator(); - while (ii.hasNext()) { - short op = (Short) ii.next(); - R r = map.get(op); - r.isPresent = false; - r.recordPos = -1; - } - } - - /** - * mark record present and record pertinent information for record-level validation - */ - private void markRecord(LinkedHashMap map, BiffRec rec, short opcode) { - try { - R r = map.get(opcode); - if (!r.isPresent) { // THIS STATEMENT FOR SOME REASON IS VERY VERY VERY TIME CONSUMING -- see testPerformance3 - r.isPresent = true; - r.recordPos = rec.getRecordIndex(); - } - } catch (NullPointerException ne) { -/* if (opcode != CONTINUE && opcode!=DBCELL - && opcode < 4000 /* chart records * / - && !rec.isValueForCell()) // ignore CELLTABLE records -// io.starter.toolkit.Logger.log("COULDN'T FIND Opcode: " + opcode);*/ - - } - } - - /** - * debug utility - */ - private void displayRecsInStream(LinkedHashMap map) { - Iterator ii = map.keySet().iterator(); - io.starter.toolkit.Logger.log("Present Records"); - while (ii.hasNext()) { - short op = ii.next(); - R r = map.get(op); - if (r.isPresent) { - io.starter.toolkit.Logger.log(op + " at " + r.recordPos); - } - } - } -} - -/** - * represents pertinent info for a BiffRec in an easy-to-access class - */ -class R { - public boolean isRequired, isPresent; - public int recordPos = -1; - public short[] altPrecedor = null; - - public R(boolean req) { - isRequired = req; - } -} - -/* - * missing records: - * notes: 2262 record is ????? see TestInsertRows.testInsertRow0FormulaMovement - * workingdir + "InsertRowBug1.xls"); TestColumns.testInsertColMoveReferences - * (this.workingdir + "InsertColumnBug1.xls"); - * - * TestReadWrite.testNPEOnOpen: workingdir + "equilar/proxycomp3_pwc.xls"); - * COULDN'T FIND Opcode: 194 - * - * Sheet-level recs: - * COULDN'T FIND Opcode: 171 ??? sheet substream just before 153 then EOF -- see testRecalc - COULDN'T FIND Opcode: 148 -->TestNPEOnOpen - * - * - * - * - * testCorruption.testOutOfSpec -- missing required records: workingdir + - * "Caribou_North_And_South.xls" A required record is not present: 225 A - * required record is not present: 193 A required record is not present: 226 A - * required record is not present: 92 A required record is not present: 25 A - * required record is not present: 19 A required record is not present: 431 A - * required record is not present: 444 - * - * TestFormulaCalculator.testRecalc: workingdir + "Sakonnet/smile.xls" A - * required record is not present: 225 A required record is not present: 193 A - * required record is not present: 226 A required record is not present: 353 A - * required record is not present: 317 A required record is not present: 18 A - * required record is not present: 431 A required record is not present: 444 A - * required record is not present: 64 A required record is not present: 141 A - * required record is not present: 439 A required record is not present: 218 A - * required record is not present: 352 A required record is not present: 255 - */ - -/* - * record order issues: 659= Style 352= UsesELFs 140= Country 146= Palette - * - * 2173= XfExt 2189= DXF 2190= TableStyles 2211= ForceFullCalculation - * - * - * testValidationHandle.testEquilarFile workingdir + - * "equilar/proxycomp3_formatted_new.xls" Record out of order: r:659/682 - * lastR:2189/903 Record out of order: r:352/732 lastR:2190/956 Record out of - * order: r:140/753 lastR:2211/958 - * - * TestScenarios.borderbrokenxx, formularesultxx, numericerrorxx - * TestFormulaParse.testRajeshOOM: workingdir + "OOXML/proxycomp3_cap_new.xls" - * Record out of order: r:659/1304 lastR:2189/1557 Record out of order: - * r:146/1375 lastR:2190/1610 Record out of order: r:140/1403 lastR:2211/1612 - * - * TestAutoFilter.XXX: workingdir + "testAutoFilter.xls" Record out of order: - * r:659/128 lastR:2173/204 Record out of order: r:352/175 lastR:2190/292 Record - * out of order: r:140/178 lastR:2211/294 - * - * also see TestAddAF w.r.t. insertion position -- supbook is wrong? ... - * - * TestMemoryUsage.testMemUsageWithManyReferences: workingdir + - * "equilar/proxycomp3_pmp_Model_T_new.xls" Record out of order: r:659/1202 - * lastR:2189/1448 Record out of order: r:146/1273 lastR:2190/1501 Record out of - * order: r:140/1297 lastR:2211/1503 Record out of order: r:89/1300 - * lastR:35/1308 - * - * TestInsertRows.testInsertRowsWithFormulas.this.workingsheetsterdir + - * "aviasphere/AirframeMaster_F20Import.xls" Record out of order: r:659/274 - * lastR:2173/352 Record out of order: r:352/323 lastR:2190/537 Record out of - * order: r:140/330 lastR:2211/539 - * - * TestXMLSS.testClasicXMLReadWriteError: workingdir + "xmlsavecorruption.xls" - * Record out of order: r:659/144 lastR:2173/208 Record out of order: r:352/194 - * lastR:2190/364 Record out of order: r:140/196 lastR:2211/366 - * - * TestRowCols.testRowInsertionFormatLoss.workingdir + "claritas/input/400.xls" - * Record out of order: r:659/230 lastR:2189/426 Record out of order: r:352/280 - * lastR:2190/481 Record out of order: r:140/288 lastR:2211/483 - * - * TEstRowCols.testRowFormats:testRowFormats.xls Record out of order: r:659/278 - * lastR:2173/356 Record out of order: r:352/327 lastR:2190/541 Record out of - * order: r:140/334 lastR:2211/543 - * - * TestRowCols.testAutoAjdustRowHeight.C:\eclipse\workspace\Testfiles\OpenXLS\input - * \equilar/tcr_formatted_2003.xls Record out of order: r:659/709 lastR:2189/973 - * Record out of order: r:352/774 lastR:2190/1038 Record out of order: r:140/800 - * lastR:2211/1040 - */ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/WorkBookFactory.kt b/src/main/java/io/starter/formats/XLS/WorkBookFactory.kt new file mode 100644 index 0000000..e551e02 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/WorkBookFactory.kt @@ -0,0 +1,1078 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.LEO.BlockByteConsumer +import io.starter.formats.LEO.BlockByteReader +import io.starter.formats.LEO.LEOFile +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import io.starter.toolkit.ProgressListener + +import java.io.Serializable +import java.util.LinkedHashMap + + +/** + * Factory for creating WorkBook objects from Byte streams. + * + * @see WorkBook + * + * @see XLSRecord + */ +class WorkBookFactory : io.starter.toolkit.ProgressNotifier, XLSConstants, Serializable { + var debugLevel = 0 + + internal var leoFile: LEOFile + set + // end ProgressNotifier methods + + /** + * get the file name for the WorkBook + */ + /** + * sets the workbook filename associated with this wbfactory + * + * @param f + */ + var fileName: String? = null + get() { + if (field == null) + fileName = leoFile.fileName + return field + } + + // Methods from ProgressNotifier + private var progresslistener: ProgressListener? = null + override var progress = 0 + private var done = false + override var progressText = "" + + override fun register(j: ProgressListener) { + progresslistener = j + j.addTarget(this) + } + + override fun fireProgressChanged() { + // if (progresslistener != null) { + // progresslistener.updateProgress(); + // } + } + + override fun iscompleted(): Boolean { + return done + } + + /** + * return the next opcode/length in the Stream from the given record. + */ + fun lookAhead(rec: BiffRec): Short { + val i = rec.offset + rec.length + val parsedata = rec.byteReader ?: return rec.opcode + + val b1 = parsedata.getHeaderBytes(i) + + return ByteTools.readShort(b1[0].toInt(), b1[1].toInt()) + } + + /** + * read in a WorkBook from a byte array. + */ + @Throws(InvalidRecordException::class) + fun getWorkBook(parsedata: BlockByteReader, leo: LEOFile): Book { + val book = WorkBook() + return this.initWorkBook(book, parsedata, leo) + } + + /** + * Initialize the workbook + * + * @param book + * @param parsedata + * @param leo + * @return + * @throws InvalidRecordException + */ + @Throws(InvalidRecordException::class) + fun initWorkBook(book: Book, parsedata: BlockByteReader, leo: LEOFile): Book { + + parsedata.applyRelativePosition = true + + /** KSC: record-level validation */ + var bPerformRecordLevelValidation = false // perform record-level validation if set + if (System.getProperty(XLSConstants.VALIDATEWORKBOOK) != null) { + if (System.getProperty(XLSConstants.VALIDATEWORKBOOK) == "true") + bPerformRecordLevelValidation = true + } + var curSubstream: java.util.LinkedHashMap? = null + var sheetSubstream: java.util.LinkedHashMap? = null + if (bPerformRecordLevelValidation) { + val globalSubstream = java.util.LinkedHashMap() + fillGlobalSubstream(globalSubstream) + sheetSubstream = java.util.LinkedHashMap() + fillWorksSheetSubstream(sheetSubstream) + curSubstream = globalSubstream + } + + leoFile = leo + + book.setDebugLevel(this.debugLevel) + book.factory = this + var infile = false + var isWBBOF = true + var opcode: Short = 0x00 + var reclen: Short = 0x00 + var lastOpcode: Short = 0x00 + var BofCount = 0 // track the number of 'Bof' records + + var rec: BiffRec? = null + val blen = parsedata.length + + // init the progress listener + this.progressText = "Initializing Workbook..." + progress = 0 + if (progresslistener != null) + progresslistener!!.setMaxProgress(blen) + this.fireProgressChanged() + if (debugLevel > 1) + Logger.logInfo("XLS File Size: $blen") + + var i = 0 + while (i <= blen - 4) { + + this.fireProgressChanged() // "" + val headerbytes = parsedata.getHeaderBytes(i) + opcode = ByteTools.readShort(headerbytes[0].toInt(), headerbytes[1].toInt()) + reclen = ByteTools.readShort(headerbytes[2].toInt(), headerbytes[3].toInt()) + + if (lastOpcode == XLSConstants.EOF && opcode.toInt() == 0 || opcode.toInt() == -0x1) { + val startpos = i - 3 + var junkreclen = 0 + val offset = 0 + + if (offset != 0) + junkreclen = offset - startpos + else + junkreclen = blen - i + i += junkreclen - 1 + i += junkreclen - 1 + } else { // REAL REC + // sanity checks + if (reclen < 0) + throw InvalidRecordException("WorkBookFactory.getWorkBook() Negative Reclen encountered pos:" + i + " opcode:0x" + Integer.toHexString(opcode.toInt())) + else if (reclen + 1 > blen) + throw InvalidRecordException("WorkBookFactory.getWorkBook() Reclen longer than data pos:" + i + " opcode:0x" + Integer.toHexString(opcode.toInt())) + + if (opcode == XLSConstants.BOF || infile) { // if the first Bof has been + // reached, start + infile = true + + // Init Record' + rec = this.parse(book, opcode, i, reclen.toInt(), parsedata) + + if (progresslistener != null) { + progresslistener!!.setValue(i) + } + + /**** KSC: record-level validation */ + if (bPerformRecordLevelValidation && curSubstream != null) { + markRecord(curSubstream, rec, opcode) + } + + // write to the dump file if necessary + if (WorkBookHandle.dump_input != null) + try { + WorkBookHandle.dump_input!!.write("-------------------------------------" + "-------------------------\n" + (rec as XLSRecord).recDesc + ByteTools.getByteDump(parsedata.get((rec as XLSRecord?)!!, 0, reclen.toInt()), 0) + "\n") + WorkBookHandle.dump_input!!.flush() + } catch (e: Exception) { + Logger.logErr("error writing to dump file, ceasing dump output: $e") + WorkBookHandle.dump_input = null + } + + if (rec == null) { // Effectively an EOF + if (debugLevel > 0) + Logger.logInfo("done parsing WorkBook storage.") + this.done = true + this.progressText = "Done Reading WorkBook." + this.fireProgressChanged() + return book + } else { + // not used anymore ((XLSRecord)rec).resetCacheBytes(); + } + // int reco = rec.getOffset() ; + // int recl = rec.getLength(); + var thisrecpos = i + reclen.toInt() + 4 + + if (opcode == XLSConstants.BOF) { + if (isWBBOF) { // do first Bof initialization + book.setFirstBof(rec as io.starter.formats.XLS.Bof?) + isWBBOF = false + } else if (bPerformRecordLevelValidation && BofCount == 0 && lastOpcode != XLSConstants.EOF && curSubstream != null) { + /***** KSC: record-level validation */ + // invalid record structure- no EOF before BOF + validateRecords(curSubstream, book, rec.sheet) + } + + BofCount++ + /***** KSC: record-level validation */ + if (bPerformRecordLevelValidation && curSubstream == null) { + // after global substream is processed, switch to sheet substream + reInitSubstream(sheetSubstream!!) + curSubstream = sheetSubstream + curSubstream[XLSConstants.BOF].isPresent = true + curSubstream[XLSConstants.BOF].recordPos = 0 + } + } else if (opcode == XLSConstants.EOF) { + BofCount-- + + /***** KSC: record-level validation */ + if (bPerformRecordLevelValidation && BofCount == 0 && curSubstream != null) { + validateRecords(curSubstream, book, rec.sheet) + curSubstream = null + } + } + // end of Workbook + if (BofCount == -1) { + if (debugLevel > 2) + Logger.logInfo("Last Bof") + i += reclen.toInt() + thisrecpos = blen + } + if (thisrecpos > 0) + i = thisrecpos + lastOpcode = opcode + } else { + throw InvalidRecordException("No valid record found.") + } + + } + } + if (debugLevel > 0) + Logger.logInfo("done") + progress = blen + this.progressText = "Done Reading WorkBook." + this.fireProgressChanged() + + this.done = true + // flag the book so we know it's ready for shared access + // book.setReady(true); ENTERPRISE ONLY + // recordata.setApplyRelativePosition(false); + return book + } + + /** + * create the individual records based on type + */ + @Synchronized + @Throws(InvalidRecordException::class) + protected fun parse(book: Book, opcode: Short, offset: Int, datalen: Int, bytebuf: BlockByteReader): BiffRec { + + // sanity checks + if (datalen < 0 || datalen > XLSConstants.MAXRECLEN) + throw InvalidRecordException("InvalidRecordException BAD RECORD LENGTH: " + " off: " + offset + " op: " + Integer.toHexString(opcode.toInt()) + " len: " + datalen) + if (offset + datalen > bytebuf.length) + throw InvalidRecordException("InvalidRecordException RECORD LENGTH LONGER THAN FILE: " + " off: " + offset + " op: " + Integer.toHexString(opcode.toInt()) + " len: " + datalen + " buflen:" + bytebuf.length) + + // Create a new Record + val rec = XLSRecordFactory.getBiffRecord(opcode) + + // init the mighty rec + rec.workBook = book as WorkBook + rec.byteReader = bytebuf + rec.length = datalen.toShort() + rec.offset = offset + rec.setDebugLevel(this.debugLevel) + rec.streamer = book.streamer + + // send it to the CONTINUE handler + book.continueHandler.addRec(rec, datalen.toShort().toInt()) + // add it to the record stream + return book.addRecord(rec, true) + } + + /** + * record-level validation: + * * have ordered list of records for each substream (wb/global, worksheet) + * * upon each record that is processed, look up in list and mark present + record pos in correseponding list (streamer or sheet) + * * upon EOF for stream, traverse thru list, if required and not present, add + *

                      Limitations: + * This methodology does NOT validate Chart records, Chart-only sheets, Macro Sheets, Dialog Sheets or Custom Views + */ + /** + * fill map with EVERY possible global (workbook)-level record, IN ORDER, plus flag if they are required or not + * Used in record-level validation + * + * @param map + */ + private fun fillGlobalSubstream(map: LinkedHashMap) { + // ordered list of all records in the global (workbook) substream, along + // with if they are required or not + map[XLSConstants.BOF] = R(true) + map[134.toShort()] = R(false) // WriteProtect + map[XLSConstants.FILEPASS] = R(false) + map[96.toShort()] = R(false) // Template + map[XLSConstants.INTERFACE_HDR] = R(true) + map[193.toShort()] = R(true) // Mms + map[226.toShort()] = R(true) // InterfaceEnd + map[92.toShort()] = R(true) // WriteAccess + map[91.toShort()] = R(false) // FileSharing + map[66.toShort()] = R(true) // CodePage + map[441.toShort()] = R(false) // LEL - SHOULD BE is required?? + map[353.toShort()] = R(true) // DSF - is required?? + map[448.toShort()] = R(false) // Excel9File + map[XLSConstants.TABID] = R(true) + map[211.toShort()] = R(false) // ObjProj + map[445.toShort()] = R(false) // *[ObNoMacros] + map[XLSConstants.CODENAME] = R(false) + map[156.toShort()] = R(false) // BuiltInFnGroupCount + map[154.toShort()] = R(false) // FnGroupName + map[2200.toShort()] = R(false) // FnGrp12 + map[222.toShort()] = R(false) // OleObjectSize + map[XLSConstants.WINDOW_PROTECT] = R(true) + map[XLSConstants.PROTECT] = R(true) + map[XLSConstants.PASSWORD] = R(true) + map[XLSConstants.PROT4REV] = R(true) + map[444.toShort()] = R(true) // Prot4RevPass + map[XLSConstants.WINDOW1] = R(true) + map[XLSConstants.BACKUP] = R(true) + map[141.toShort()] = R(true) // HideObj + map[XLSConstants.DATE1904] = R(true) + map[14.toShort()] = R(true) // CalcPrecision + map[439.toShort()] = R(true) // RefreshAll + map[XLSConstants.BOOKBOOL] = R(true) + map[XLSConstants.FONT] = R(true) // Is required??? + map[XLSConstants.FORMAT] = R(true) // Is required?? + map[XLSConstants.XF] = R(true) // Is required??? + map[2172.toShort()] = R(false) // XfCRC + map[2173.toShort()] = R(false) // XfExt + map[2189.toShort()] = R(false) // DXF- Is required??? + map[XLSConstants.STYLE] = R(true) // Is required??? + map[2194.toShort()] = R(false) // StyleExt + map[XLSConstants.TABLESTYLES] = R(false) + map[2191.toShort()] = R(false) // TableStyle + map[2192.toShort()] = R(false) // TABLESTYLEELEMENT + map[XLSConstants.PALETTE] = R(false) + map[4188.toShort()] = R(false) // CLRTCLIENT + map[XLSConstants.SXSTREAMID] = R(false) + map[XLSConstants.SXVS] = R(false) + map[XLSConstants.DCONNAME] = R(false) + map[XLSConstants.DCONBIN] = R(false) + map[XLSConstants.DCONREF] = R(false) + map[208.toShort()] = R(false) // SXTbl + map[210.toShort()] = R(false) // SxTbpg + map[209.toShort()] = R(false) // SXTBRGIITM + map[XLSConstants.SXSTRING] = R(false) + map[220.toShort()] = R(false) // DbOrParamQry + map[XLSConstants.SXADDL] = R(false) + + map[184.toShort()] = R(false) // DocRoute + map[185.toShort()] = R(false) // RECIPNAME + map[XLSConstants.USERBVIEW] = R(false) // SHOULD BE Is but isn't present + // ************************* + map[352.toShort()] = R(true) // UsesELFs + map[XLSConstants.BOUNDSHEET] = R(true) + map[2180.toShort()] = R(false) // MDTInfo + // *ContinueFrt12 + map[2181.toShort()] = R(false) // MDXStr + // *ContinueFrt12 + map[2182.toShort()] = R(false) // MDXTuple + // *ContinueFrt12 + map[2183.toShort()] = R(false) // MDXSet + map[2184.toShort()] = R(false) // MDXProp + map[2185.toShort()] = R(false) // MDXKPI + map[2186.toShort()] = R(false) // MDB + map[2202.toShort()] = R(false) // MTRSettings + map[2211.toShort()] = R(false) // ForceFullCalculation + map[XLSConstants.COUNTRY] = R(true) + map[XLSConstants.SUPBOOK] = R(false) // SHOULD BE IsRequired but isn't present ****************** + map[XLSConstants.EXTERNNAME] = R(false) + map[XLSConstants.XCT] = R(false) + map[XLSConstants.CRN] = R(false) + map[XLSConstants.EXTERNSHEET] = R(false) + map[XLSConstants.NAME] = R(false) // Name is Required??? + map[2196.toShort()] = R(false) // NameCmt + map[2201.toShort()] = R(false) // NameFnGrp12 + map[2195.toShort()] = R(false) // NamePublish + map[2067.toShort()] = R(false) // RealTimeData A required record + // is not present: 430 + // *ContinueFrt + map[449.toShort()] = R(false) // RecalcId + map[2150.toShort()] = R(false) // HFPicture should be required???? + map[XLSConstants.MSODRAWINGGROUP] = R(false) // should be required?? + // Continue + map[XLSConstants.SST] = R(true) // should be required?? + // Continue + map[XLSConstants.EXTSST] = R(true) + map[2049.toShort()] = R(false) // WebPub should be required??? + map[2059.toShort()] = R(false) // WOpt + map[2149.toShort()] = R(false) // CrErr + map[2147.toShort()] = R(false) // BookExt + map[2151.toShort()] = R(false) // FeatHdr should be required??? + map[2166.toShort()] = R(false) // DConn should be required??? + map[2198.toShort()] = R(false) // Theme + // *ContinueFrt12 + map[2203.toShort()] = R(false) // CompressPictures + map[2188.toShort()] = R(false) // Compat12 + map[2199.toShort()] = R(false) // GUIDTypeLib + map[XLSConstants.EOF] = R(true) + } + + /** + * fill map with EVERY possible worksheet-level record, IN ORDER, plus flag if they are required or not. + * Used in record-level validation + * + * @param map + */ + private fun fillWorksSheetSubstream(map: LinkedHashMap) { + map[XLSConstants.BOF] = R(true) + map[94.toShort()] = R(false) // Uncalced + map[XLSConstants.INDEX] = R(true) + map[XLSConstants.CALCMODE] = R(true) + map[XLSConstants.CALCCOUNT] = R(true) + map[15.toShort()] = R(true) //CalcRefMode + map[17.toShort()] = R(true) // CalcIter + map[16.toShort()] = R(true) // CalcDelta + map[95.toShort()] = R(true) // CalcSaveRecalc + map[XLSConstants.PRINTROWCOL] = R(true) + map[XLSConstants.PRINTGRID] = R(true) + map[130.toShort()] = R(true) // GridSet + map[XLSConstants.GUTS] = R(true) + map[XLSConstants.DEFAULTROWHEIGHT] = R(true) + map[XLSConstants.WSBOOL] = R(true) + map[151.toShort()] = R(false) // [Sync] + map[152.toShort()] = R(false) // [LPr] + map[XLSConstants.HORIZONTAL_PAGE_BREAKS] = R(false) //[HorizontalPageBreaks] + map[XLSConstants.VERTICAL_PAGE_BREAKS] = R(false) //[VerticalPageBreaks] + map[XLSConstants.HEADERREC] = R(true) + map[XLSConstants.FOOTERREC] = R(true) + map[XLSConstants.HCENTER] = R(true) + map[XLSConstants.VCENTER] = R(true) + map[XLSConstants.LEFT_MARGIN] = R(false) + map[XLSConstants.RIGHT_MARGIN] = R(false) + map[XLSConstants.TOP_MARGIN] = R(false) + map[XLSConstants.BOTTOM_MARGIN] = R(false) + map[XLSConstants.PLS] = R(false) + // Continue + map[XLSConstants.SETUP] = R(true) + map[0x89c.toShort()] = R(false) //[HeaderFooter] + map[233.toShort()] = R(false) //[ BkHim ] + map[1048.toShort()] = R(false) // BigName + // *ContinueBigName + map[XLSConstants.PROTECT] = R(false) + map[XLSConstants.SCENPROTECT] = R(false) + map[XLSConstants.OBJPROTECT] = R(false) + map[XLSConstants.PASSWORD] = R(false) + map[XLSConstants.DEFCOLWIDTH] = R(true) + map[XLSConstants.COLINFO] = R(false) + map[174.toShort()] = R(false) // [ScenMan *( + map[175.toShort()] = R(false) // SCENARIO + // Continue + map[144.toShort()] = R(false) // Sort + map[2197.toShort()] = R(false) // SortData + // ContinueFrt12 + map[155.toShort()] = R(false) // Filtermode + map[2164.toShort()] = R(false) // DropDownObjIds + map[157.toShort()] = R(false) // AutoFilterInfo + map[XLSConstants.AUTOFILTER] = R(false) + map[2174.toShort()] = R(false) // AutoFilter12 + map[2197.toShort()] = R(false) // SortData + //*ContinueFrt12] + map[XLSConstants.DIMENSIONS] = R(true) + // [CELLTABLE] + map[XLSConstants.ROW] = R(false) + // celltable entries + map[XLSConstants.BLANK] = R(false) + map[XLSConstants.MULBLANK] = R(false) + map[XLSConstants.RK] = R(false) + map[XLSConstants.BOOLERR] = R(false) + map[XLSConstants.NUMBER] = R(false) + map[XLSConstants.LABELSST] = R(false) + map[94.toShort()] = R(false) // uncalced + map[XLSConstants.FORMULA] = R(false) + map[XLSConstants.MULRK] = R(false) + map[XLSConstants.STRINGREC] = R(false) + map[XLSConstants.SHRFMLA] = R(false) + map[XLSConstants.ARRAY] = R(false) + map[XLSConstants.TABLE] = R(false) + map[450.toShort()] = R(false) // EntExU2 + // [OBJECTS] + map[XLSConstants.MSODRAWING] = R(false) + map[XLSConstants.TXO] = R(false) + map[XLSConstants.OBJ] = R(false) + map[XLSConstants.OBJ].altPrecedor = shortArrayOf(XLSConstants.MSODRAWING) // Obj can follow MSODRAWING or TXO + //Charts -- most record opcodes are > 4000 - ignore !! + map[XLSConstants.CHARTFRTINFO] = R(false) + // do not include the majority of chart records but do include these: + map[XLSConstants.STARTBLOCK] = R(false) + map[XLSConstants.CRTLAYOUT12] = R(false) + map[XLSConstants.CRTLAYOUT12A] = R(false) + map[XLSConstants.CATLAB] = R(false) + map[XLSConstants.DATALABEXT] = R(false) + map[XLSConstants.DATALABEXTCONTENTS] = R(false) + map[2138.toShort()] = R(false) // FrtFontList + map[2213.toShort()] = R(false) // TextPropsStream + map[2214.toShort()] = R(false) // RichTextStream + map[2212.toShort()] = R(false) // ShapePropsStream + map[2206.toShort()] = R(false) // CtrlMlFrt + map[XLSConstants.ENDBLOCK] = R(false) + map[XLSConstants.STARTOBJECT] = R(false) + map[XLSConstants.FRTWRAPPER] = R(false) + map[XLSConstants.ENDOBJECT] = R(false) + // CHARTSHEETCONTENT = BOF [WriteProtect] [SheetExt] [WebPub] *HFPicture PAGESETUP PrintSize [HeaderFooter] [BACKGROUND] *Fbi *Fbi2 [ClrtClient] [PROTECTION] [Palette] [SXViewLink] [PivotChartBits] [SBaseRef] [MsoDrawingGroup] OBJECTS Units CHARTFOMATS SERIESDATA *WINDOW *CUSTOMVIEW [CodeName] [CRTMLFRT] EOF + //Chart Begin *2FONTLIST SclPlotGrowth [FRAME] *SERIESFORMAT *SS ShtProps *2DFTTEXT AxesUsed 1*2AXISPARENT [CrtLayout12A] [DAT] *ATTACHEDLABEL [CRTMLFRT] *([DataLabExt StartObject] ATTACHEDLABEL [EndObject]) [TEXTPROPS] *2CRTMLFRT End + map[XLSConstants.MSODRAWINGSELECTION] = R(false) + map[2150.toShort()] = R(false) //*HFPicture + map[XLSConstants.NOTE] = R(false) + //*PIVOTVIEW + map[XLSConstants.SXVIEW] = R(false) + map[XLSConstants.SXVD] = R(false) + map[XLSConstants.SXVI] = R(false) + map[XLSConstants.SXVDEX] = R(false) + map[XLSConstants.SXIVD] = R(false) + map[XLSConstants.SXPI] = R(false) + map[XLSConstants.SXDI] = R(false) + map[XLSConstants.SXLI] = R(false) + map[XLSConstants.SXEX] = R(false) + map[247.toShort()] = R(false) //SXSelect + map[240.toShort()] = R(false) // SXRULE + map[XLSConstants.SXFORMAT] = R(false) + // PIVOTRULE + map[244.toShort()] = R(false) // SXDXF + map[XLSConstants.QSISXTAG] = R(false) + //DBQUERYEXT + map[2051.toShort()] = R(false) // DBQUERYEX + map[2052.toShort()] = R(false) // EXTSTRING + map[XLSConstants.SXSTRING] = R(false) + map[2060.toShort()] = R(false) // SXVIEWEX + map[2061.toShort()] = R(false) // SXTH + map[2062.toShort()] = R(false) // SXPIEx + map[256.toShort()] = R(false) // SXVDTEx + map[XLSConstants.SXVIEWEX9] = R(false) + map[XLSConstants.SXADDL] = R(false) + + map[XLSConstants.DCON] = R(false) + map[XLSConstants.DCONNAME] = R(false) + map[XLSConstants.DCONBIN] = R(false) + map[XLSConstants.DCONREF] = R(false) + map[XLSConstants.WINDOW2] = R(true) + map[XLSConstants.PLV] = R(false) + map[XLSConstants.SCL] = R(false) + map[XLSConstants.PANE] = R(false) + map[XLSConstants.SELECTION] = R(false) + map[426.toShort()] = R(false) // UserSViewBegin + /* map.put(SELECTION, new R(false)); + map.put(HORIZONTAL_PAGE_BREAKS, new R(false)); + map.put(VERTICAL_PAGE_BREAKS, new R(false)); + map.put(HEADERREC, new R(false)); + map.put(FOOTERREC, new R(false)); + map.put(HCENTER, new R(false)); + map.put(VCENTER, new R(false)); + map.put(LEFT_MARGIN, new R(false)); + map.put(RIGHT_MARGIN, new R(false)); + map.put(TOP_MARGIN, new R(false)); + map.put(BOTTOM_MARGIN, new R(false)); + map.put(PLS, new R(false)); + map.put(SETUP, new R(false)); + map.put((short) 51, new R(false)); //([PrintSize] + map.put((short) 2204, new R(false)); //[HeaderFooter]*/ + // here ??? [AUTOFILTER] + map[427.toShort()] = R(false) //UserSViewEnd + + map[319.toShort()] = R(false) // RRSort + map[153.toShort()] = R(false) //[DxGCol] + map[XLSConstants.MERGEDCELLS] = R(false) //*MergeCells + map[351.toShort()] = R(false) // [LRng] + // *QUERYTABLE + map[XLSConstants.PHONETIC] = R(false) + map[XLSConstants.CONDFMT] = R(false) + map[XLSConstants.CF] = R(false) + map[XLSConstants.CONDFMT12] = R(false) + map[XLSConstants.CF12] = R(false) + map[2171.toShort()] = R(false) // CFEx + map[XLSConstants.HLINK] = R(false) + map[2048.toShort()] = R(false) //HLinkTooltip + map[XLSConstants.DVAL] = R(false) + map[XLSConstants.DV] = R(false) + map[XLSConstants.CODENAME] = R(false) // [CodeName] + map[2049.toShort()] = R(false) // *WebPub + map[2156.toShort()] = R(false) // *CellWatch + map[2146.toShort()] = R(false) // SheetExt] + map[XLSConstants.FEATHEADR] = R(false) + map[2152.toShort()] = R(false) // FEAT + // *FEAT11 + // *RECORD12 + map[2248.toShort()] = R(false) // UNKNOWN RECORD!!! + map[XLSConstants.EOF] = R(true) + } + + /** + * add all missing REQUIRED records from the appropriate substream for record-level validation + * + * @param map substream list storing if present, required ... + * @param book + */ + private fun validateRecords(map: LinkedHashMap, book: Book, bs: Boundsheet?) { + // use array instead of iterator as may have to traverse more than once + var opcodes = arrayOfNulls(0) + val ss = map.keys + opcodes = ss.toTypedArray() + var lastR = R(false) + lastR.recordPos = 0 + var lastOp = -1 + if (bs != null && bs.isChartOnlySheet) + return // don't validate chart-only sheets + + // traverse thru stream list, ensuring required records are present; create if not + for (i in opcodes.indices) { + val op = opcodes[i] + val r = map.get(op) + if (!r.isPresent && r.isRequired) { + // io.starter.toolkit.Logger.log("A required record is not present: " + op); + // Create a new Record + val rec = createMissingRequiredRecord(op, book, bs) + rec!!.setDebugLevel(this.debugLevel) + var recPos = lastR.recordPos + 1 + try { + while (true) { // now get to LAST record if there are multiple records + val lastRec = if (bs == null) book.streamer.getRecordAt(recPos) else bs.sheetRecs[recPos] as BiffRec + if (lastRec.opcode.toInt() != lastOp) + break + recPos++ + } + } catch (e: IndexOutOfBoundsException) { + } + + book.streamer.addRecordAt(rec, recPos) + book.addRecord(rec, false) // false= already added to streamer or sheet + if (bs != null) + rec.setSheet(bs) + + r.recordPos = rec.recordIndex + + // now must adjust ensuing record positions to account for inserted record + for (zz in opcodes.indices) { + val nextR = map.get(opcodes[zz]) + if (nextR.isPresent && r != nextR && nextR.recordPos >= r.recordPos) + nextR.recordPos++ + } + r.isPresent = true + } + if (r.isPresent) { + lastR = r + lastOp = op.toInt() + } + } + // go thru 1 more time to ensure record order is correct + validateRecordOrder(map, if (bs == null) book.streamer.records else bs.sheetRecs, opcodes) + } + + /** + * given substream map, actual list of records and list of ordered opcodes, validate record order in substream + *

                      if necessary, reorgainze records to follow order in map + * + * @param map ordered map of substream records indexed by opcodes + * @param list list of actual records + * @param opcodes ordered list of opcodes + */ + private fun validateRecordOrder(map: LinkedHashMap, list: MutableList<*>, opcodes: Array) { + /* debugging: + io.starter.toolkit.Logger.log("BeFORE order:"); + for (int zz= 0; zz < list.size(); zz++) { + io.starter.toolkit.Logger.log(zz + "-" + list.get(zz)); + }*/ + var lastR = map[XLSConstants.BOF] + var lastOp = XLSConstants.BOF + for (i in 1 until opcodes.size) { + val op = opcodes[i] + val r = map[op] + if (r.isPresent) { + // compare ordered list of records (R) to actual order (denoted via recordPos) + if (r.recordPos < lastR.recordPos && r.recordPos >= 0) { // Out Of Order (NOTE: CellTable entries will have a record pos = -1) + if (r.altPrecedor != null) { // record can have more than 1 valid predecessor + // TODO: this is ugly - do a different way ... + for (zz in r.altPrecedor!!.indices) { + val newR = map[r.altPrecedor!![zz]] + if (r.recordPos > newR.recordPos) { + lastR = newR + break + } + } + if (r.recordPos > lastR.recordPos) + continue + } + + val origRecPos = r.recordPos + //io.starter.toolkit.Logger.log("Record out of order: r:" + op + "/" + r.recordPos + " lastR:" + lastOp + "/" + lastR.recordPos); + // find correct insertion point by looking at ordered map + for (zz in i - 1 downTo 1) { + val prevr = map[opcodes[zz]] + if (prevr.isPresent && r.recordPos < prevr.recordPos) { + //io.starter.toolkit.Logger.log("\tInsert at " + prevr.recordPos + " before op= " + opcodes[zz]); + var recsMovedCount = 0 + var recToMove = list[origRecPos] as BiffRec + do { + list.removeAt(origRecPos) + list.add(prevr.recordPos /*+ recsMovedCount*/, recToMove) + if (recsMovedCount == 0) + r.recordPos = recToMove.recordIndex + //io.starter.toolkit.Logger.log("\tMoved To " + recToMove.getRecordIndex()); + recsMovedCount++ + recToMove = list[origRecPos] as BiffRec + } while (recToMove.opcode == op) + + // after moved all the records necessary, adjust record positions + for (jj in opcodes.indices) { + val nextR = map[opcodes[jj]] + if (nextR.isPresent && nextR.recordPos >= origRecPos && nextR.recordPos <= r.recordPos && opcodes[jj] != op) + nextR.recordPos -= recsMovedCount + } + break + } + } + } + lastR = r + lastOp = op + } + } + /*io.starter.toolkit.Logger.log("AFTER order:"); + for (int zz= 0; zz < list.size(); zz++) { + io.starter.toolkit.Logger.log(zz + "-" + list.get(zz)); + }*/ + } + + // TODO: handle fonts,formats,xf, style -- what's minimum necessary?? + // TODO: handle TabId + // TODO: is Index OK? + + /** + * create missing required records for record-level validation + * + * @param opcode opcode of missing record + * @param book + * @param bs boundsheet- if null it's a wb level record + * @return new BiffRec + */ + private fun createMissingRequiredRecord(opcode: Short, book: Book, bs: Boundsheet?): BiffRec? { + val record = XLSRecordFactory.getBiffRecord(opcode) + if (bs != null) + record.setSheet(bs) + var data: ByteArray? = null + try { + when (opcode) { + XLSConstants.INTERFACE_HDR -> data = byteArrayOf(0xB0.toByte(), 0x04) //codePage (2 bytes): An unsigned integer that specifies the code page. 1200==Unicode + 193.toShort() // Mms + -> data = byteArrayOf(0, 0) + 226.toShort() // InterfaceEnd + -> data = byteArrayOf() + 92.toShort() // WriteAccess -- user name - can be all blank + -> data = byteArrayOf(0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20) + 66.toShort() // CodePage same as interfacehdr 1200==Unicode + -> data = byteArrayOf(0xB0.toByte(), 0x04) + 353.toShort() // DSF- truly is required? + -> data = byteArrayOf(0, 0) + XLSConstants.TABID // + -> { + // count how many sheets in wb + var nSheets = 0 + run { + var z = book.streamer.records.size - 1 + while (z > 0) { + var b = book.streamer.records[z] as BiffRec + if (b.opcode == XLSConstants.BOUNDSHEET) { + while (z > 0 && b.opcode == XLSConstants.BOUNDSHEET) { + nSheets++ + b = book.streamer.records[--z] as BiffRec + } + break + } + z-- + } + } + data = byteArrayOf() + for (i in 0 until nSheets) { + data = ByteTools.append(ByteTools.shortToLEBytes(i.toShort()), data) + } + } + XLSConstants.WINDOW_PROTECT -> data = byteArrayOf(0, 0) + XLSConstants.PROTECT -> data = byteArrayOf(0, 0) + XLSConstants.PASSWORD -> data = byteArrayOf(0, 0) + XLSConstants.PROT4REV -> data = byteArrayOf(0, 0) + 444.toShort() // Prot4RevPass + -> data = byteArrayOf(0, 0) + XLSConstants.WINDOW1 // very general window settings + -> data = byteArrayOf(0xE0.toByte(), 1, 0x69, 0, 0x13, 0x38, 0x1F, 0x1D, 0x38, 0, 0, 0, 0, 0, 1, 0, 0x58, 0x02) + XLSConstants.BACKUP -> data = byteArrayOf(0, 0) + 141.toShort() // HideObj + -> data = byteArrayOf(0, 0) + XLSConstants.DATE1904 -> data = byteArrayOf(0, 0) + 14.toShort() // CalcPrecision + -> data = byteArrayOf(1, 0) + 439.toShort() // RefreshAll + -> data = byteArrayOf(0, 0) + XLSConstants.BOOKBOOL -> data = byteArrayOf(0, 0) + XLSConstants.FONT // truly required?? + -> data = byteArrayOf(0xC8.toByte(), 0, 0, 0, 0xFF.toByte(), 0x7F, 0x90.toByte(), 1, 0, 0, 0, 0, 0, 0, 5, 1, 0x41, 0, 0x72, 0, 0x69, 0, 0x61, 0, 0x6C, 0) + // case FORMAT: // truly required?? + // break; + XLSConstants.XF // truly is required?? + -> data = byteArrayOf(0, 0, 0, 0, 0xF5.toByte(), 0xFF.toByte(), 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xC0.toByte(), 0x20) + // case STYLE: // truly is required? + // break; + XLSConstants.COUNTRY -> data = byteArrayOf(1, 0, 1, 0) + XLSConstants.SST -> data = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0) + XLSConstants.EXTSST -> data = byteArrayOf(0, 0) // should be rebuilt upon output ... + 352.toShort() // UsesELFs + -> data = byteArrayOf(0, 0) + XLSConstants.INDEX -> data = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0x4E, 6, 0, 0) + XLSConstants.CALCMODE -> data = byteArrayOf(1, 0) + XLSConstants.CALCCOUNT -> data = byteArrayOf(0x64, 0) + 15 //CalcRefMode + -> data = byteArrayOf(1, 0) + 17 // CalcIter + -> data = byteArrayOf(0, 0) + 16 // CalcDelta An Xnum value that specifies the amount of change in value for a given cell from the previously calculated value for that cell that MUST exist for the iteration to continue. The value MUST be greater than or equal to 0. + -> { + data = byteArrayOf(0xFC.toByte(), 0xA9.toByte(), 0xF1.toByte(), 0xD2.toByte(), 0x4D, 0x62, 0x50, 0x3F) + data = byteArrayOf(1, 0) + } + 95 // CalcSaveRecalc + -> data = byteArrayOf(1, 0) + XLSConstants.PRINTROWCOL -> data = byteArrayOf(0, 0) + XLSConstants.PRINTGRID -> data = byteArrayOf(0, 0) + 130 // GridSet + -> data = byteArrayOf(0, 0) + XLSConstants.GUTS -> data = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0) + XLSConstants.DEFAULTROWHEIGHT -> data = byteArrayOf(0, 0, 0xFF.toByte(), 0) + XLSConstants.WSBOOL -> data = byteArrayOf(0xC1.toByte(), 4) + XLSConstants.HEADERREC, XLSConstants.FOOTERREC -> data = byteArrayOf() + XLSConstants.HCENTER -> data = byteArrayOf(0, 0) + XLSConstants.VCENTER -> data = byteArrayOf(0, 0) + XLSConstants.SETUP -> data = byteArrayOf(0, 0, 0xFF.toByte(), 0, 1, 0, 1, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xE0.toByte(), 0x3F, 0, 0, 0, 0, 0, 0, 0xE0.toByte(), 0x3F, 0, 0) + XLSConstants.DEFCOLWIDTH -> data = byteArrayOf(8, 0) + XLSConstants.DIMENSIONS -> { + data = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + record.data = data + if (bs != null) { // see if rows have already been added + bs.dimensions = record as Dimensions + if (bs.rowMap.size > 0) { + val z = bs.rows[bs.rowMap.size - 1].rowNumber + record.setRowLast(z) + } + + // z= ((Colinfo)bs.getColinfos().get(bs.getColinfos().size())).getColLast(); + // ((Dimensions) record).setColLast(z); + } + } + XLSConstants.WINDOW2 -> data = byteArrayOf(0xB6.toByte(), 6, 0, 0, 0, 0, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + XLSConstants.EOF -> { + } + else -> io.starter.toolkit.Logger.log("Must create required rec: $opcode") + } + + + record.data = data + try { + record.init() + } catch (e: Exception) { + // TODO: ExtSst needs sst ... + } + + } catch (e: Exception) { + Logger.logErr("Record Validation: Error creating missing record: $opcode") + } + + return record + + } + + /** + * reset Record members in stream so can be used again for record-level validation + * + * @param map + */ + private fun reInitSubstream(map: LinkedHashMap) { + val ii = map.keys.iterator() + while (ii.hasNext()) { + val r = map[ii.next()] + r.isPresent = false + r.recordPos = -1 + } + } + + /** + * mark record present and record pertinent information for record-level validation + */ + private fun markRecord(map: LinkedHashMap, rec: BiffRec?, opcode: Short) { + try { + val r = map[opcode] + if (!r.isPresent) { // THIS STATEMENT FOR SOME REASON IS VERY VERY VERY TIME CONSUMING -- see testPerformance3 + r.isPresent = true + r.recordPos = rec!!.recordIndex + } + } catch (ne: NullPointerException) { +/* if (opcode != CONTINUE && opcode!=DBCELL + && opcode < 4000 /* chart records * / + && !rec.isValueForCell()) // ignore CELLTABLE records +// io.starter.toolkit.Logger.log("COULDN'T FIND Opcode: " + opcode);*/ + + } + +} + +/** + * debug utility + */ + private fun displayRecsInStream(map:LinkedHashMap) { +val ii = map.keys.iterator() +io.starter.toolkit.Logger.log("Present Records") +while (ii.hasNext()) +{ +val op = ii.next() +val r = map.get(op) +if (r.isPresent) +{ +io.starter.toolkit.Logger.log(op + " at " + r.recordPos) +} +} +} + +companion object { + +const val serialVersionUID = 1233423412323L + +/** + * Find the location of the next particular opcode + */ + protected fun getNextOpcodeOffset(op:Short, rec:BlockByteConsumer, parsedata:BlockByteReader):Int { +var found = false +var x = rec.offset +var opcode:Short = 0x0 +while (!found && x < parsedata.length - 2) +{ +opcode = ByteTools.readShort(parsedata.get(rec, x).toInt(), parsedata.get(rec, ++x).toInt()) +if (opcode == op) +{ +found = true +break +} +} +if (!found) +return 0 +return x - 3 +} +} +} + +/** + * represents pertinent info for a BiffRec in an easy-to-access class + */ +internal class R( var isRequired:Boolean) { + var isPresent:Boolean = false + var recordPos = -1 + var altPrecedor:ShortArray? = null +} + + /* + * missing records: + * notes: 2262 record is ????? see TestInsertRows.testInsertRow0FormulaMovement + * workingdir + "InsertRowBug1.xls"); TestColumns.testInsertColMoveReferences + * (this.workingdir + "InsertColumnBug1.xls"); + * + * TestReadWrite.testNPEOnOpen: workingdir + "equilar/proxycomp3_pwc.xls"); + * COULDN'T FIND Opcode: 194 + * + * Sheet-level recs: + * COULDN'T FIND Opcode: 171 ??? sheet substream just before 153 then EOF -- see testRecalc + COULDN'T FIND Opcode: 148 -->TestNPEOnOpen + * + * + * + * + * testCorruption.testOutOfSpec -- missing required records: workingdir + + * "Caribou_North_And_South.xls" A required record is not present: 225 A + * required record is not present: 193 A required record is not present: 226 A + * required record is not present: 92 A required record is not present: 25 A + * required record is not present: 19 A required record is not present: 431 A + * required record is not present: 444 + * + * TestFormulaCalculator.testRecalc: workingdir + "Sakonnet/smile.xls" A + * required record is not present: 225 A required record is not present: 193 A + * required record is not present: 226 A required record is not present: 353 A + * required record is not present: 317 A required record is not present: 18 A + * required record is not present: 431 A required record is not present: 444 A + * required record is not present: 64 A required record is not present: 141 A + * required record is not present: 439 A required record is not present: 218 A + * required record is not present: 352 A required record is not present: 255 + */ + + /* + * record order issues: 659= Style 352= UsesELFs 140= Country 146= Palette + * + * 2173= XfExt 2189= DXF 2190= TableStyles 2211= ForceFullCalculation + * + * + * testValidationHandle.testEquilarFile workingdir + + * "equilar/proxycomp3_formatted_new.xls" Record out of order: r:659/682 + * lastR:2189/903 Record out of order: r:352/732 lastR:2190/956 Record out of + * order: r:140/753 lastR:2211/958 + * + * TestScenarios.borderbrokenxx, formularesultxx, numericerrorxx + * TestFormulaParse.testRajeshOOM: workingdir + "OOXML/proxycomp3_cap_new.xls" + * Record out of order: r:659/1304 lastR:2189/1557 Record out of order: + * r:146/1375 lastR:2190/1610 Record out of order: r:140/1403 lastR:2211/1612 + * + * TestAutoFilter.XXX: workingdir + "testAutoFilter.xls" Record out of order: + * r:659/128 lastR:2173/204 Record out of order: r:352/175 lastR:2190/292 Record + * out of order: r:140/178 lastR:2211/294 + * + * also see TestAddAF w.r.t. insertion position -- supbook is wrong? ... + * + * TestMemoryUsage.testMemUsageWithManyReferences: workingdir + + * "equilar/proxycomp3_pmp_Model_T_new.xls" Record out of order: r:659/1202 + * lastR:2189/1448 Record out of order: r:146/1273 lastR:2190/1501 Record out of + * order: r:140/1297 lastR:2211/1503 Record out of order: r:89/1300 + * lastR:35/1308 + * + * TestInsertRows.testInsertRowsWithFormulas.this.workingsheetsterdir + + * "aviasphere/AirframeMaster_F20Import.xls" Record out of order: r:659/274 + * lastR:2173/352 Record out of order: r:352/323 lastR:2190/537 Record out of + * order: r:140/330 lastR:2211/539 + * + * TestXMLSS.testClasicXMLReadWriteError: workingdir + "xmlsavecorruption.xls" + * Record out of order: r:659/144 lastR:2173/208 Record out of order: r:352/194 + * lastR:2190/364 Record out of order: r:140/196 lastR:2211/366 + * + * TestRowCols.testRowInsertionFormatLoss.workingdir + "claritas/input/400.xls" + * Record out of order: r:659/230 lastR:2189/426 Record out of order: r:352/280 + * lastR:2190/481 Record out of order: r:140/288 lastR:2211/483 + * + * TEstRowCols.testRowFormats:testRowFormats.xls Record out of order: r:659/278 + * lastR:2173/356 Record out of order: r:352/327 lastR:2190/541 Record out of + * order: r:140/334 lastR:2211/543 + * + * TestRowCols.testAutoAjdustRowHeight.C:\eclipse\workspace\Testfiles\OpenXLS\input + * \equilar/tcr_formatted_2003.xls Record out of order: r:659/709 lastR:2189/973 + * Record out of order: r:352/774 lastR:2190/1038 Record out of order: r:140/800 + * lastR:2211/1040 + */ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java b/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.kt similarity index 65% rename from src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java rename to src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.kt index 5f93302..e238927 100644 --- a/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.java +++ b/src/main/java/io/starter/formats/XLS/WorkSheetNotFoundException.kt @@ -20,41 +20,42 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS -import io.starter.OpenXLS.Cell; +import io.starter.OpenXLS.Cell /** - * No WorkSheet Found. + * **No WorkSheet Found.** * * @see Cell + * * @see WorkBook */ -public final class WorkSheetNotFoundException extends java.lang.Exception { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1722195057857012811L; - String message = ""; +class WorkSheetNotFoundException(n: String) : java.lang.Exception() { + internal var message = "" - public WorkSheetNotFoundException(String n) { - super(); - message = n; + init { + message = n } - @Override - public String getMessage() { + override fun getMessage(): String { // This method is derived from class java.lang.Throwable // to do: code goes here - return this.toString(); + return this.toString() } - @Override - public String toString() { + override fun toString(): String { // This method is derived from class java.lang.Throwable // to do: code goes here - return message; + return message + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -1722195057857012811L } } diff --git a/src/main/java/io/starter/formats/XLS/Writeaccess.java b/src/main/java/io/starter/formats/XLS/Writeaccess.java deleted file mode 100644 index 26826df..0000000 --- a/src/main/java/io/starter/formats/XLS/Writeaccess.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; - - -/** - * WRITEACCESS 0x5C: Contains name of Excel installed user.
                      - *

                      - * offset  name        size    contents
                      - * ---
                      - * 4       stName      112     User Name as unformatted Unicodestring
                      - *
                      - * 

                      - * - * @see WorkBook - */ - -public class Writeaccess extends io.starter.formats.XLS.XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8868603864018600260L; - private Unicodestring strname; - - /** - * set the Writeaccess username - */ - public void setName(String str) { - try { - byte[] nameb = str.getBytes(DEFAULTENCODING); - byte[] newb = new byte[112]; - int diff = 112 - nameb.length; - if (diff < 0) System.arraycopy(nameb, 0, newb, 0, 112); - else System.arraycopy(nameb, 0, newb, 0, nameb.length); - strname.init(newb, false); - this.setData(newb); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("setting name in Writeaccess record failed: " + e); - } - } - - public String getName() { - return strname.toString(); - } - - public void init() { - super.init(); - strname = new Unicodestring(); - strname.init(getBytes(), false); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Writeaccess.kt b/src/main/java/io/starter/formats/XLS/Writeaccess.kt new file mode 100644 index 0000000..d72cd66 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Writeaccess.kt @@ -0,0 +1,77 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.toolkit.Logger + +import java.io.UnsupportedEncodingException + + +/** + * **WRITEACCESS 0x5C: Contains name of Excel installed user.**

                      + * + *
                      + * offset  name        size    contents
                      + * ---
                      + * 4       stName      112     User Name as unformatted Unicodestring
                      + *
                      +
                      * + * + * @see WorkBook + */ + +class Writeaccess : io.starter.formats.XLS.XLSRecord() { + private var strname: Unicodestring? = null + + /** + * set the Writeaccess username + */ + var name: String? + get() = strname!!.toString() + set(str) = try { + val nameb = str.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + val newb = ByteArray(112) + val diff = 112 - nameb.size + if (diff < 0) + System.arraycopy(nameb, 0, newb, 0, 112) + else + System.arraycopy(nameb, 0, newb, 0, nameb.size) + strname!!.init(newb, false) + this.setData(newb) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("setting name in Writeaccess record failed: $e") + } + + override fun init() { + super.init() + strname = Unicodestring() + strname!!.init(bytes!!, false) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8868603864018600260L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/WsBool.java b/src/main/java/io/starter/formats/XLS/WsBool.kt similarity index 82% rename from src/main/java/io/starter/formats/XLS/WsBool.java rename to src/main/java/io/starter/formats/XLS/WsBool.kt index cf6e98b..79b3e6f 100644 --- a/src/main/java/io/starter/formats/XLS/WsBool.java +++ b/src/main/java/io/starter/formats/XLS/WsBool.kt @@ -20,11 +20,12 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * Record containing miscellaneous sheet-level boolean values. - *

                      + * + * * A - fShowAutoBreaks (1 bit): A bit that specifies whether page breaks (2) inserted automatically are visible on the sheet. * B - reserved1 (3 bits): MUST be zero, and MUST be ignored. * C - fDialog (1 bit): A bit that specifies whether the sheet is a dialog sheet. @@ -42,34 +43,34 @@ * L - fAltExprEval (1 bit): A bit that specifies whether the sheet uses transition formula evaluation. * M - fAltFormulaEntry (1 bit): A bit that specifies whether the sheet uses transition formula entry. */ -public final class WsBool - extends XLSRecord { - private static final long serialVersionUID = 2794181135988750779L; - - public void init() { - super.init(); - // Make sure the data array is read in - getData(); - - } - - public void setSheet(Sheet sheet) { - super.setSheet(sheet); - ((Boundsheet) sheet).addPrintRec(this); - } +class WsBool : XLSRecord() { /** * Gets whether the sheet will be printed fit to some number of pages. */ - public boolean isFitToPage() { - return (data[1] & 0x01) == 0x01; - } - /** * Sets whether the sheet will be printed fit to some number of pages. */ - public void setFitToPage(boolean value) { - if (value) data[1] |= 0x01; - else data[1] &= ~0x01; + var isFitToPage: Boolean + get() = data!![1] and 0x01 == 0x01 + set(value) = if (value) + data[1] = data[1] or 0x01 + else + data[1] = data[1] and 0x01.inv().toByte() + + override fun init() { + super.init() + // Make sure the data array is read in + getData() + + } + + override fun setSheet(sheet: Sheet?) { + super.setSheet(sheet) + (sheet as Boundsheet).addPrintRec(this) + } + + companion object { + private val serialVersionUID = 2794181135988750779L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSCellRecord.java b/src/main/java/io/starter/formats/XLS/XLSCellRecord.kt similarity index 65% rename from src/main/java/io/starter/formats/XLS/XLSCellRecord.java rename to src/main/java/io/starter/formats/XLS/XLSCellRecord.kt index dec37a2..1c42846 100644 --- a/src/main/java/io/starter/formats/XLS/XLSCellRecord.java +++ b/src/main/java/io/starter/formats/XLS/XLSCellRecord.kt @@ -20,26 +20,25 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** * A BIFF8 record that contains the value of a single cell. * For the moment this contains very little. Eventually most of the - * cell-specific methods from {@link XLSRecord} should be moved here. + * cell-specific methods from [XLSRecord] should be moved here. */ -public abstract class XLSCellRecord - extends XLSRecord implements CellRec { - private static final long serialVersionUID = 7387720078386279196L; +abstract class XLSCellRecord : XLSRecord(), CellRec { - public int getColFirst() { - return this.getColNumber(); - } + override val colFirst: Int + get() = this.colNumber.toInt() - public int getColLast() { - return this.getColNumber(); - } + override val colLast: Int + get() = this.colNumber.toInt() + + override val isSingleCol: Boolean + get() = this.colFirst == this.colLast - public boolean isSingleCol() { - return (this.getColFirst() == this.getColLast()); + companion object { + private val serialVersionUID = 7387720078386279196L } } diff --git a/src/main/java/io/starter/formats/XLS/XLSConstants.java b/src/main/java/io/starter/formats/XLS/XLSConstants.java deleted file mode 100644 index eeb342e..0000000 --- a/src/main/java/io/starter/formats/XLS/XLSConstants.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -/** - * - */ -public interface XLSConstants { - // Stream types - short WK_GLOBALS = 0x5; - short VB_MODULE = 0x6; - short WK_WORKSHEET = 0x10; - short WK_CHART = 0x20; - short WK_MACROSHEET = 0x40; - short WK_FILE = 0x100; - - // Cell types - int - TYPE_BLANK = -1, - TYPE_STRING = 0, - TYPE_FP = 1, - TYPE_INT = 2, - TYPE_FORMULA = 3, - TYPE_BOOLEAN = 4, - TYPE_DOUBLE = 5; - - // Book Options and constants - - // CalculationOptions - int CALCULATE_ALWAYS = 0; - int CALCULATE_EXPLICIT = 1; - int CALCULATE_AUTO = 2; // replacement for calc always - String CALC_MODE_PROP = "io.starter.OpenXLS.calcmode"; - String REFTRACK_PROP = "io.starter.OpenXLS.trackreferences"; - String USETEMPFILE_PROP = "io.starter.formats.LEO.usetempfile"; - String VALIDATEWORKBOOK = "io.starter.formats.LEO.validateworkbook"; - // Debug options - int DEBUG_LOW = 50; - int DEBUG_MEDIUM = 60; - int DEBUG_HIGH = 100; - - // String table handling - int STRING_ENCODING_AUTO = 0; - int STRING_ENCODING_UNICODE = 1; - int STRING_ENCODING_COMPRESSED = 2; - int ALLOWDUPES = 0; - int SHAREDUPES = 1; - String DEFAULTENCODING = "ISO-8859-1"; // "UTF-8";"UTF-8"; - String UNICODEENCODING = "UTF-16LE"; // "UnicodeLittleUnmarked"; - - // XLSRecord Opcodes - short EXCEL2K = 0x1C0; - short GARBAGE = 0xFFFFFFFE; - short TXO = 0x1B6; - short MSODRAWINGGROUP = 0xEB; - short MSODRAWING = 0xEC; - short MSODRAWINGSELECTION = 0xED; - short PHONETIC = 0xEF; - short CONTINUE = 0x3C; - short COLINFO = 0x7D; - short SST = 0xFC; - short DSF = 0x161; - short EXTSST = 0xFF; - short ENDEXTSST = 0xFE; - short BOF = 0x809; - short FILEPASS = 0x2F; - short INDEX = 0x20B; - short DBCELL = 0xD7; - short BOUNDSHEET = 0x85; - short COUNTRY = 0x8C; // record just after bound sheet - short BOOKBOOL = 0xDA; - short CALCCOUNT = 0x0C; - short CALCMODE = 0x0D; - short PRECISION = 0x0E; - short REFMODE = 0x0F; - short DELTA = 0x10; - short ITERATION = 0x11; - short DATE1904 = 0x22; - short BACKUP = 0x40; - short PRINT_ROW_HEADERS = 0x2A; - short PRINT_GRIDLINES = 0x2B; - short HORIZONTAL_PAGE_BREAKS = 0x1B; - short HLINK = 0x1B8; - short VERTICAL_PAGE_BREAKS = 0x1A; - short DEFAULTROWHEIGHT = 0x225; - short FONT = 0x31; - short HEADERREC = 0x14; - short FOOTERREC = 0x15; - short LEFT_MARGIN = 0x26; - short RIGHT_MARGIN = 0x27; - short TOP_MARGIN = 0x28; - short BOTTOM_MARGIN = 0x29; - short DCON = 0x50; - short DEFCOLWIDTH = 0x55; - short EXTERNCOUNT = 0x16; - short EXTERNSHEET = 0x17; - short EXTERNNAME = 0x23; - short FORMAT = 0x41E; - short XF = 0xE0; - short NAME = 0x18; - short DIMENSIONS = 0x200; - short FILE_LOCK = 0x195; - short RRD_INFO = 0x196; - short RRD_HEAD = 0x138; - short EOF = 0x0A; - short BLANK = 0x201; - short MERGEDCELLS = 0xE5; - short MULBLANK = 0xBE; - short MULRK = 0xBD; - short NOTE = 0x1C; - short NUMBER = 0x203; - short LABEL = 0x204; - short LABELSST = 0xFD; - short BOOLERR = 0x205; - short FORMULA = 0x06; // 0x406; - short ARRAY = 0x221;//0x21; // - short SELECTION = 0x1D; - short STYLE = 0x293; - short ROW = 0x208; - short RK = 0x27E; // this is wrong according to the documentation (0x27) ... -jm - short RSTRING = 0xD6; - short SHRFMLA = 0x4BC; // according to docs this is 0xBC - short STRINGREC = 0x207; - short TABLE = 0x236; - short PANE = 0x41; - short PASSWORD = 0x13; - short INTERFACE_HDR = 0xE1; - short USR_EXCL = 0x194; - short PALETTE = 0x92; - short PROTECT = 0x12; - short OBJPROTECT = 0x63; - short SCENPROTECT = 0xDD; - short FEATHEADR = 0x867; // extra protection settings + smarttag settings - short SCL = 0xA0; // zoom - short SHEETPROTECTION = 0x867; // - short SHEETLAYOUT = 0x862; - short RANGEPROTECTION = 0x868; - short PROT4REV = 0x1AF; - short WINDOW_PROTECT = 0x19; - short WINDOW1 = 0x3D; - short WINDOW2 = 0x23E; - short PLV = 0x88B; - short RTENTEXU = 0x1B; - short DV = 0x1BE; - short DVAL = 0x1B2; - short RTMERGECELLS = 0xE5; - short SUPBOOK = 0x1AE; - short USERSVIEWBEGIN = 0x1AA; - short USERSVIEWEND = 0x1AB; - short USERBVIEW = 0x1A9; - short PLS = 0x4D; - short WSBOOL = 0x81; - short OBJ = 0x5D; - short OBPROJ = 0xD3; - short XLS_MAX_COLS = 0x100; - short TABID = 0x13d; - short GUTS = 0x80; - short CODENAME = 0x1BA; - short XCT = 0x59; // 20080122 KSC: - short CRN = 0x5A; // "" - - // Pivot Table records - short SXVIEW = 0xB0; - short TABLESTYLES = 0x88E; - short SXSTREAMID = 0xD5; - short SXVS = 0xE3; - short SXADDL = 0x864; - short SXVDEX = 0x100; - short SXPI = 0xB6; - short SXDI = 0xC5; - short SXDB = 0xC6; - short SXFDB = 0xC7; - short SXEX = 0xF1; - short QSISXTAG = 0x802; - short SXVIEWEX9 = 0x810; - short DCONREF = 0x51; - short DCONNAME = 0x52; - short DCONBIN = 0x1B5; - short SXFORMAT = 0xFB; - short SXLI = 0xB5; - short SXVI = 0xB2; - short SXVD = 0xB1; - short SXIVD = 0xB4; - short SXDBEX = 0x122; - short SXFDBTYPE = 0x1BB; - short SXDBB = 0xC8; - short SXNUM = 0xC9; - short SXBOOL = 0xCA; - short SXSTRING = 0xCD; - - // Printing records - short SETUP = 0xA1; - short HCENTER = 0x83; - short VCENTER = 0x84; - short LEFTMARGIN = 0x26; - short RIGHTMARGIN = 0x27; - short TOPMARGIN = 0x28; - short BOTTOMMARGIN = 0x29; - short PRINTGRID = 0x2B; - short PRINTROWCOL = 0x2A; - - // Conditional Formatting - short CF = 0x1B1; - short CONDFMT = 0x1B0; - // 2007 Conditional Formatting - short CF12 = 0x87A; - short CONDFMT12 = 0x879; - - // AutoFilter - short AUTOFILTER = 0x9E; - - // Chart items - short UNITS = 0x1001; - short CHART = 0x1002; - short SERIES = 0x1003; - short DATAFORMAT = 0x1006; - short LINEFORMAT = 0x1007; - short MARKERFORMAT = 0x1009; - short AREAFORMAT = 0x100A; - short PIEFORMAT = 0x100B; - short ATTACHEDLABEL = 0x100C; - short SERIESTEXT = 0x100D; - short CHARTFORMAT = 0x1014; - short LEGEND = 0x1015; - short SERIESLIST = 0x1016; - short BAR = 0x1017; - short LINE = 0x1018; - short PIE = 0x1019; - short AREA = 0x101A; - short SCATTER = 0x101B; - short CHARTLINE = 0x101C; - short AXIS = 0x101D; - short TICK = 0x101E; - short VALUERANGE = 0x101F; - short CATSERRANGE = 0x1020; - short AXISLINEFORMAT = 0x1021; - short CHARTFORMATLINK = 0x1022; - short DEFAULTTEXT = 0x1024; - short TEXTDISP = 0x1025; - short FONTX = 0x1026; - short OBJECTLINK = 0x1027; - short FRAME = 0x1032; - short BEGIN = 0x1033; - short END = 0x1034; - short PLOTAREA = 0x1035; - short THREED = 0x103A; - short PICF = 0x103C; - short DROPBAR = 0x103D; - short RADAR = 0x103E; - short SURFACE = 0x103F; - short RADARAREA = 0x1040; - short AXISPARENT = 0x1041; - short LEGENDXN = 0x1043; - short SHTPROPS = 0x1044; - short SERTOCRT = 0x1045; - short AXESUSED = 0x1046; - short SBASEREF = 0x1048; - short SERPARENT = 0x104A; - short SERAUXTREND = 0x104B; - short IFMT = 0x104E; - short POS = 0x104F; - short ALRUNS = 0x1450; - short AI = 0x1051; - short SERAUXERRBAR = 0x105B; - short SERFMT = 0x105D; - short CHART3DBARSHAPE = 0x105F; - short FBI = 0x1460; - short BOPPOP = 0x1061; - short AXCENT = 0x1062; - short DAT = 0x1063; - short PLOTGROWTH = 0x1064; - short SIIINDEX = 0x1065; - short GELFRAME = 0x1066; - short BOPPOPCUSTOM = 0x1067; - //20080703 KSC: Excel 9 Chart Records - short CRTLAYOUT12 = 0x89D; - short CRTLAYOUT12A = 0x08A7; - short CHARTFRTINFO = 0x0850; - short FRTWRAPPER = 0x0851; - short STARTBLOCK = 0x0852; - short ENDBLOCK = 0x0853; - short STARTOBJECT = 0x0854; - short ENDOBJECT = 0x0855; - short CATLAB = 0x0856; - short YMULT = 0x0857; - short SXVIEWLINK = 0x0858; - short PIVOTCHARTBITS = 0x0859; - short FRTFONTLIST = 0x085A; - short PIVOTCHARTLINK = 0x0861; - short DATALABEXT = 0x086A; - short DATALABEXTCONTENTS = 0x086B; - short FONTBASIS = 0x1060; - - // max size of records -- depends on XLS version - int MAXRECLEN = 8224; - int MAXROWS_BIFF8 = 65536; - int MAXCOLS_BIFF8 = 256; - int MAXROWS = 1048576; // new Excel 2007 limits - int MAXCOLS = 16384; // new Excel 2007 limits - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSConstants.kt b/src/main/java/io/starter/formats/XLS/XLSConstants.kt new file mode 100644 index 0000000..50562eb --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/XLSConstants.kt @@ -0,0 +1,322 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +/** + * + */ +interface XLSConstants { + companion object { + // Stream types + val WK_GLOBALS: Short = 0x5 + val VB_MODULE: Short = 0x6 + val WK_WORKSHEET: Short = 0x10 + val WK_CHART: Short = 0x20 + val WK_MACROSHEET: Short = 0x40 + val WK_FILE: Short = 0x100 + + // Cell types + val TYPE_BLANK = -1 + val TYPE_STRING = 0 + val TYPE_FP = 1 + val TYPE_INT = 2 + val TYPE_FORMULA = 3 + val TYPE_BOOLEAN = 4 + val TYPE_DOUBLE = 5 + + // Book Options and constants + + // CalculationOptions + val CALCULATE_ALWAYS = 0 + val CALCULATE_EXPLICIT = 1 + val CALCULATE_AUTO = 2 // replacement for calc always + val CALC_MODE_PROP = "io.starter.OpenXLS.calcmode" + val REFTRACK_PROP = "io.starter.OpenXLS.trackreferences" + val USETEMPFILE_PROP = "io.starter.formats.LEO.usetempfile" + val VALIDATEWORKBOOK = "io.starter.formats.LEO.validateworkbook" + // Debug options + val DEBUG_LOW = 50 + val DEBUG_MEDIUM = 60 + val DEBUG_HIGH = 100 + + // String table handling + val STRING_ENCODING_AUTO = 0 + val STRING_ENCODING_UNICODE = 1 + val STRING_ENCODING_COMPRESSED = 2 + val ALLOWDUPES = 0 + val SHAREDUPES = 1 + val DEFAULTENCODING = "ISO-8859-1" // "UTF-8";"UTF-8"; + val UNICODEENCODING = "UTF-16LE" // "UnicodeLittleUnmarked"; + + // XLSRecord Opcodes + val EXCEL2K: Short = 0x1C0 + val GARBAGE: Short = -0x2 + val TXO: Short = 0x1B6 + val MSODRAWINGGROUP: Short = 0xEB + val MSODRAWING: Short = 0xEC + val MSODRAWINGSELECTION: Short = 0xED + val PHONETIC: Short = 0xEF + val CONTINUE: Short = 0x3C + val COLINFO: Short = 0x7D + val SST: Short = 0xFC + val DSF: Short = 0x161 + val EXTSST: Short = 0xFF + val ENDEXTSST: Short = 0xFE + val BOF: Short = 0x809 + val FILEPASS: Short = 0x2F + val INDEX: Short = 0x20B + val DBCELL: Short = 0xD7 + val BOUNDSHEET: Short = 0x85 + val COUNTRY: Short = 0x8C // record just after bound sheet + val BOOKBOOL: Short = 0xDA + val CALCCOUNT: Short = 0x0C + val CALCMODE: Short = 0x0D + val PRECISION: Short = 0x0E + val REFMODE: Short = 0x0F + val DELTA: Short = 0x10 + val ITERATION: Short = 0x11 + val DATE1904: Short = 0x22 + val BACKUP: Short = 0x40 + val PRINT_ROW_HEADERS: Short = 0x2A + val PRINT_GRIDLINES: Short = 0x2B + val HORIZONTAL_PAGE_BREAKS: Short = 0x1B + val HLINK: Short = 0x1B8 + val VERTICAL_PAGE_BREAKS: Short = 0x1A + val DEFAULTROWHEIGHT: Short = 0x225 + val FONT: Short = 0x31 + val HEADERREC: Short = 0x14 + val FOOTERREC: Short = 0x15 + val LEFT_MARGIN: Short = 0x26 + val RIGHT_MARGIN: Short = 0x27 + val TOP_MARGIN: Short = 0x28 + val BOTTOM_MARGIN: Short = 0x29 + val DCON: Short = 0x50 + val DEFCOLWIDTH: Short = 0x55 + val EXTERNCOUNT: Short = 0x16 + val EXTERNSHEET: Short = 0x17 + val EXTERNNAME: Short = 0x23 + val FORMAT: Short = 0x41E + val XF: Short = 0xE0 + val NAME: Short = 0x18 + val DIMENSIONS: Short = 0x200 + val FILE_LOCK: Short = 0x195 + val RRD_INFO: Short = 0x196 + val RRD_HEAD: Short = 0x138 + val EOF: Short = 0x0A + val BLANK: Short = 0x201 + val MERGEDCELLS: Short = 0xE5 + val MULBLANK: Short = 0xBE + val MULRK: Short = 0xBD + val NOTE: Short = 0x1C + val NUMBER: Short = 0x203 + val LABEL: Short = 0x204 + val LABELSST: Short = 0xFD + val BOOLERR: Short = 0x205 + val FORMULA: Short = 0x06 // 0x406; + val ARRAY: Short = 0x221//0x21; // + val SELECTION: Short = 0x1D + val STYLE: Short = 0x293 + val ROW: Short = 0x208 + val RK: Short = 0x27E // this is wrong according to the documentation (0x27) ... -jm + val RSTRING: Short = 0xD6 + val SHRFMLA: Short = 0x4BC // according to docs this is 0xBC + val STRINGREC: Short = 0x207 + val TABLE: Short = 0x236 + val PANE: Short = 0x41 + val PASSWORD: Short = 0x13 + val INTERFACE_HDR: Short = 0xE1 + val USR_EXCL: Short = 0x194 + val PALETTE: Short = 0x92 + val PROTECT: Short = 0x12 + val OBJPROTECT: Short = 0x63 + val SCENPROTECT: Short = 0xDD + val FEATHEADR: Short = 0x867 // extra protection settings + smarttag settings + val SCL: Short = 0xA0 // zoom + val SHEETPROTECTION: Short = 0x867 // + val SHEETLAYOUT: Short = 0x862 + val RANGEPROTECTION: Short = 0x868 + val PROT4REV: Short = 0x1AF + val WINDOW_PROTECT: Short = 0x19 + val WINDOW1: Short = 0x3D + val WINDOW2: Short = 0x23E + val PLV: Short = 0x88B + val RTENTEXU: Short = 0x1B + val DV: Short = 0x1BE + val DVAL: Short = 0x1B2 + val RTMERGECELLS: Short = 0xE5 + val SUPBOOK: Short = 0x1AE + val USERSVIEWBEGIN: Short = 0x1AA + val USERSVIEWEND: Short = 0x1AB + val USERBVIEW: Short = 0x1A9 + val PLS: Short = 0x4D + val WSBOOL: Short = 0x81 + val OBJ: Short = 0x5D + val OBPROJ: Short = 0xD3 + val XLS_MAX_COLS: Short = 0x100 + val TABID: Short = 0x13d + val GUTS: Short = 0x80 + val CODENAME: Short = 0x1BA + val XCT: Short = 0x59 // 20080122 KSC: + val CRN: Short = 0x5A // "" + + // Pivot Table records + val SXVIEW: Short = 0xB0 + val TABLESTYLES: Short = 0x88E + val SXSTREAMID: Short = 0xD5 + val SXVS: Short = 0xE3 + val SXADDL: Short = 0x864 + val SXVDEX: Short = 0x100 + val SXPI: Short = 0xB6 + val SXDI: Short = 0xC5 + val SXDB: Short = 0xC6 + val SXFDB: Short = 0xC7 + val SXEX: Short = 0xF1 + val QSISXTAG: Short = 0x802 + val SXVIEWEX9: Short = 0x810 + val DCONREF: Short = 0x51 + val DCONNAME: Short = 0x52 + val DCONBIN: Short = 0x1B5 + val SXFORMAT: Short = 0xFB + val SXLI: Short = 0xB5 + val SXVI: Short = 0xB2 + val SXVD: Short = 0xB1 + val SXIVD: Short = 0xB4 + val SXDBEX: Short = 0x122 + val SXFDBTYPE: Short = 0x1BB + val SXDBB: Short = 0xC8 + val SXNUM: Short = 0xC9 + val SXBOOL: Short = 0xCA + val SXSTRING: Short = 0xCD + + // Printing records + val SETUP: Short = 0xA1 + val HCENTER: Short = 0x83 + val VCENTER: Short = 0x84 + val LEFTMARGIN: Short = 0x26 + val RIGHTMARGIN: Short = 0x27 + val TOPMARGIN: Short = 0x28 + val BOTTOMMARGIN: Short = 0x29 + val PRINTGRID: Short = 0x2B + val PRINTROWCOL: Short = 0x2A + + // Conditional Formatting + val CF: Short = 0x1B1 + val CONDFMT: Short = 0x1B0 + // 2007 Conditional Formatting + val CF12: Short = 0x87A + val CONDFMT12: Short = 0x879 + + // AutoFilter + val AUTOFILTER: Short = 0x9E + + // Chart items + val UNITS: Short = 0x1001 + val CHART: Short = 0x1002 + val SERIES: Short = 0x1003 + val DATAFORMAT: Short = 0x1006 + val LINEFORMAT: Short = 0x1007 + val MARKERFORMAT: Short = 0x1009 + val AREAFORMAT: Short = 0x100A + val PIEFORMAT: Short = 0x100B + val ATTACHEDLABEL: Short = 0x100C + val SERIESTEXT: Short = 0x100D + val CHARTFORMAT: Short = 0x1014 + val LEGEND: Short = 0x1015 + val SERIESLIST: Short = 0x1016 + val BAR: Short = 0x1017 + val LINE: Short = 0x1018 + val PIE: Short = 0x1019 + val AREA: Short = 0x101A + val SCATTER: Short = 0x101B + val CHARTLINE: Short = 0x101C + val AXIS: Short = 0x101D + val TICK: Short = 0x101E + val VALUERANGE: Short = 0x101F + val CATSERRANGE: Short = 0x1020 + val AXISLINEFORMAT: Short = 0x1021 + val CHARTFORMATLINK: Short = 0x1022 + val DEFAULTTEXT: Short = 0x1024 + val TEXTDISP: Short = 0x1025 + val FONTX: Short = 0x1026 + val OBJECTLINK: Short = 0x1027 + val FRAME: Short = 0x1032 + val BEGIN: Short = 0x1033 + val END: Short = 0x1034 + val PLOTAREA: Short = 0x1035 + val THREED: Short = 0x103A + val PICF: Short = 0x103C + val DROPBAR: Short = 0x103D + val RADAR: Short = 0x103E + val SURFACE: Short = 0x103F + val RADARAREA: Short = 0x1040 + val AXISPARENT: Short = 0x1041 + val LEGENDXN: Short = 0x1043 + val SHTPROPS: Short = 0x1044 + val SERTOCRT: Short = 0x1045 + val AXESUSED: Short = 0x1046 + val SBASEREF: Short = 0x1048 + val SERPARENT: Short = 0x104A + val SERAUXTREND: Short = 0x104B + val IFMT: Short = 0x104E + val POS: Short = 0x104F + val ALRUNS: Short = 0x1450 + val AI: Short = 0x1051 + val SERAUXERRBAR: Short = 0x105B + val SERFMT: Short = 0x105D + val CHART3DBARSHAPE: Short = 0x105F + val FBI: Short = 0x1460 + val BOPPOP: Short = 0x1061 + val AXCENT: Short = 0x1062 + val DAT: Short = 0x1063 + val PLOTGROWTH: Short = 0x1064 + val SIIINDEX: Short = 0x1065 + val GELFRAME: Short = 0x1066 + val BOPPOPCUSTOM: Short = 0x1067 + //20080703 KSC: Excel 9 Chart Records + val CRTLAYOUT12: Short = 0x89D + val CRTLAYOUT12A: Short = 0x08A7 + val CHARTFRTINFO: Short = 0x0850 + val FRTWRAPPER: Short = 0x0851 + val STARTBLOCK: Short = 0x0852 + val ENDBLOCK: Short = 0x0853 + val STARTOBJECT: Short = 0x0854 + val ENDOBJECT: Short = 0x0855 + val CATLAB: Short = 0x0856 + val YMULT: Short = 0x0857 + val SXVIEWLINK: Short = 0x0858 + val PIVOTCHARTBITS: Short = 0x0859 + val FRTFONTLIST: Short = 0x085A + val PIVOTCHARTLINK: Short = 0x0861 + val DATALABEXT: Short = 0x086A + val DATALABEXTCONTENTS: Short = 0x086B + val FONTBASIS: Short = 0x1060 + + // max size of records -- depends on XLS version + val MAXRECLEN = 8224 + val MAXROWS_BIFF8 = 65536 + val MAXCOLS_BIFF8 = 256 + val MAXROWS = 1048576 // new Excel 2007 limits + val MAXCOLS = 16384 // new Excel 2007 limits + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSRecord.java b/src/main/java/io/starter/formats/XLS/XLSRecord.java deleted file mode 100644 index bdc5456..0000000 --- a/src/main/java/io/starter/formats/XLS/XLSRecord.java +++ /dev/null @@ -1,1121 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ExcelTools; -import io.starter.OpenXLS.FormatHandle; -import io.starter.formats.LEO.BlockByteConsumer; -import io.starter.formats.LEO.BlockByteReader; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.Serializable; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - - -/** - *

                      - * The XLS byte stream is composed of records delimited by a header with type
                      - * and data length information, followed by a record Body which contains
                      - *
                      - * the byte[] data for the record.
                      - *
                      - * XLSRecord subclasses provide specific functionality.
                      - *
                      - * 
                      - * - * @see WorkBook - * @see Boundsheet - * @see Index - * @see Dbcell - * @see Row - * @see Cell - */ - -public class XLSRecord implements BiffRec, BlockByteConsumer, Serializable, XLSConstants { - - private static final long serialVersionUID = -106915096753184441L; - - private short opcode; - int reclen; - byte[] data; - private transient BlockByteReader databuf; - private transient BlockByteReader encryptedDatabuf; - protected boolean isContinueMerged = false; - protected transient int DEBUGLEVEL = 0; - boolean isValueForCell; - boolean isFPNumber; - boolean isDoubleNumber = false; - boolean isIntNumber; - public boolean isString; - public boolean isBoolean; - boolean isFormula; - public boolean isBlank; - boolean isReadOnly = false; - protected int rw = -1; - protected short col; - public int offset = 0; // byte stream offset of rec - protected transient Index idx; - protected Sheet worksheet; - public transient Xf myxf; - protected transient WorkBook wkbook; - protected transient ByteStreamer streamer; - protected AbstractList continues; - int originalsize = -1, - originalIndex = 0, - originalOffset = 0, - ixfe = -1; - public Hlink hyperlink = null; - Row myrow = null; - CellRange mergeRange; - private int firstblock, lastblock; - - - public short getOpcode() { - return opcode; - } - - public void setOpcode(short op) { - this.opcode = op; - } - - public void setHyperlink(Hlink hl) { - this.hyperlink = hl; - } - - public Formula getFormulaRec() { - if (this instanceof Formula) { - ((Formula) this).populateExpression(); - return (Formula) this; - } - return null; - } - - public boolean shouldEncrypt() { - return true; - } - - public void setRow(Row r) { - myrow = r; - } - - /** - * get the row of this cell - */ - public Row getRow() { - if (myrow == null) { - myrow = this.worksheet.getRowByNumber(rw); - } - return myrow; - } - - public Xf getXfRec() { - if (myxf == null) { - if ((ixfe > -1) && (ixfe < wkbook.getNumXfs())) { - this.myxf = wkbook.getXf(this.ixfe); - } - } - return myxf; - } - - - /** - * returns the existing font record - * for this Cell - */ - public Font getFont() { - WorkBook b = this.getWorkBook(); - if (b == null) return null; - this.getXfRec(); - if (myxf == null) return null; - return myxf.getFont(); - } - - /** - * @return - */ - public CellRange getMergeRange() { - return mergeRange; - } - - /** - * @param range - */ - public void setMergeRange(CellRange range) { - mergeRange = range; - } - - - /** - * Removes this BiffRec from the WorkSheet - * - * @param whether to nullify this Cell - */ - public boolean remove(boolean nullme) { - boolean success = false; - if (worksheet != null && isValueForCell) - getSheet().removeCell(this); - - if (streamer != null) - streamer.removeRecord(this); - - if (nullme) { - try { - this.finalize(); - } catch (Throwable t) { - } - } - this.worksheet = null; - return true; - } - - /** - * set the Formatting for this BiffRec from the pattern - * match. - *

                      - * case insensitive pattern match is performed... - */ - public String getFormatPattern() { - if (myxf == null) return ""; - return myxf.getFormatPattern(); - } - - /** - * get the int val of the type for the valrec - */ - public int getCellType() { - if (this.isBlank) return TYPE_BLANK; - if (this.isString) return TYPE_STRING; - if (this.isDoubleNumber) return TYPE_DOUBLE; - if (this.isFPNumber) return TYPE_FP; - if (this.isIntNumber) return TYPE_INT; - if (this.isFormula) return TYPE_FORMULA; - if (this.isBoolean) return TYPE_BOOLEAN; - return -1; - } - - // Methods from BlockByteConsumer // - - /** - * Set the relative position within the data - * underlying the block vector represented by - * the BlockByteReader. - *

                      - * In other words, this is the relative position - * used by the BlockByteReader to offset the Consumer's - * read position within the collection of Data Blocks. - *

                      - * This may be an offset relative to the data in a file, - * or within a Storage contained in a file. - *

                      - * The Workbook Storage for example will contain a non-contiguous - * collection of Blocks containing data from any number of - * positions in a file. - *

                      - * This collection forms a contiguous span of bytes comprising - * an XLS Workbook. The XLSRecords within this span of bytes will - * set their relative position within this 'virtual' array. Thus - * the XLSRecord positions are relative to the order of bytes contained - * in the Block collection. The BOF record then is at offset 0 within the - * data of the first Block, even though the underlying data of this - * first Block may be anywhere on disk. - * - * @param pos - */ - public void setOffset(int pos) { - if (originalOffset < 1) originalOffset = pos; - offset = pos; - } - - /** - * Get the relative position within the data - * underlying the block vector represented by - * the BlockByteReader. - * - * @return relative position - */ - public int getOffset() { - if (this.data == null) return this.originalOffset; - return offset; - } - - /** Get the blocks containing this Consumer's data - * - * @return - */ -/*unused: public Block[] getBlocks() { - return myblocks; - }*/ - - /** Set the blocks containing this Consumer's data - * - * @param myblocks - */ -/* unused: public void setBlocks(Block[] myb) { - myblocks = myb; - }*/ - - /** - * Sets the index of the first block - * - * @return - */ - public void setFirstBlock(int i) { - firstblock = i; - } - - /** - * Sets the index of the last block - * - * @return - */ - public void setLastBlock(int i) { - lastblock = i; - } - - /** - * Returns the index of the first block - * - * @return - */ - public int getFirstBlock() { - return firstblock; - } - - /** - * Returns the index of the last block - * - * @return - */ - public int getLastBlock() { - return lastblock; - } - - - protected void initRowCol() { - int pos = 0; - - byte[] bt = this.getBytesAt(pos, 2); - rw = ByteTools.readUnsignedShort(bt[0], bt[1]); - pos += 2; - col = ByteTools.readShort(this.getByteAt(pos++), this.getByteAt(pos++)); - } - - - public String toString() { - return getRecDesc(); - } - - public String getRecDesc() { - String ret = ""; - String name = this.getClass().getSimpleName(); - - // record name - ret += (name.equals("XLSRecord") ? "unknown" : name.toUpperCase()); - // hex record number - ret += " (" + Integer.toHexString(opcode).toUpperCase() + "h)"; - // stream offset - ret += " at " + Integer.toHexString(offset).toUpperCase() + "h"; - // size - ret += " length " + Integer.toHexString(reclen).toUpperCase() + "h"; - // file offset - ret += " file " + (databuf == null ? "no file"/*originalFileOffset*/ : - databuf.getFileOffsetString(offset, reclen)); - // cell address, if applicable - if (this.isValueForCell()) ret += " cell " + this.getCellAddress(); - - return ret; - } - - /** - * Dumps this record as a human-readable string. - */ - public String toHexDump() { - return getRecDesc() + "\n" + ByteTools.getByteDump(this.getData(), 0); - } - - /** - * Copy all formatting info from source biffrec - * - * @see io.starter.formats.XLS.BiffRec#copyFormat(io.starter.formats.XLS.BiffRec) - */ - public void copyFormat(BiffRec source) { - Xf clone = (Xf) source.getXfRec().clone(); - Font fontClone = (Font) source.getXfRec().getFont().clone(); - - Logger.logInfo(source + ":" + source.getXfRec() + ":" + clone); - int fid = -1; - int xid = -1; - // see if we have an equivalent Xf/Font combo - if (this.getWorkBook().getFontRecs().contains(fontClone)) { - fid = this.getWorkBook().getFontRecs().indexOf(fontClone); - } - if (this.getWorkBook().getXfrecs().contains(clone)) { - xid = this.getWorkBook().getXfrecs().indexOf(clone); - } - - // add the xf/font and set the ixfe - this.getWorkBook().addRecord(clone, false); - this.getWorkBook().addRecord(fontClone, false); - clone.setFont(fontClone.getIdx()); - this.setXFRecord(clone.getIdx()); - - } - - /** - * clone a record - */ - public Object clone() { - try { - String cn = getClass().getName(); - XLSRecord rec = (XLSRecord) Class.forName(cn).newInstance(); - byte[] inb = getBytes(); - rec.setData(inb); - rec.streamer = this.streamer; - rec.setWorkBook(getWorkBook()); - rec.setOpcode(getOpcode()); - rec.setLength(getLength()); - rec.setSheet(getSheet()); //20081120 KSC: otherwise may set sheet incorrectly in init - rec.init(); - return rec; - } catch (Exception e) { - Logger.logInfo("cloning XLSRecord " + this.getCellAddress() + " failed: " + e); - } - return null; - } - - /** - * return whether this is a numeric type - */ - public boolean isNumber() { - if (this.opcode == RK) return true; - return this.opcode == NUMBER; - } - - /** - * return whether this is a formula record - */ - public boolean isFormula() { - return this.isFormula; - } - - /** methods from CompatibleVectorHints - - protected transient int recordIdx = -1; - */ - /** provide a hint to the CompatibleVector - about this objects likely position. - - public int getRecordIndexHint(){return recordIdx;} - */ - - /** set index information about this - objects likely position. - - public void setRecordIndexHint(int i){ - lastidx = i; - recordIdx = i; - } - */ - /** - * set the DEBUG level - */ - public void setDebugLevel(int b) { - DEBUGLEVEL = b; - } - - public int lastidx = -1; - - /** - * return the real (not just boundsheet) record index of this object - */ - public int getRealRecordIndex() { - return streamer.getRealRecordIndex(this); - } - - /** - * return the record index of this object - */ - public int getRecordIndex() { - if (streamer == null) { - if (this.getSheet() != null) // KSC: Added - return this.getSheet().getSheetRecs().indexOf(this); - return -1; - } - return streamer.getRecordIndex(this); - } - - /** - * adds a CONTINUE record to the array of CONTINUE records - * for this record, containing all data - * beyond the 8224 byte record size limit. - */ - public void addContinue(Continue c) { - if (continues == null) continues = new ArrayList(); - continues.add(c); - } - - /** - * remove all Continue records - */ - public void removeContinues() { - if (continues != null) continues.clear(); - } - - public List getContinueVect() { - if (continues != null) return this.continues; - continues = new CompatibleVector(); - return continues; - } - - /** - * returns whether this record has a CONTINUE - * record containing data beyond the 8228 byte - * record size limit. - *

                      - * XLSRecords can have 0 or more CONTINUE records. - */ - public boolean hasContinues() { - if (continues == null) return false; - return this.continues.size() > 0; - } - - - /** - * set whether this record contains the value - * of the Cell. - */ - public void setIsValueForCell(boolean b) { - isValueForCell = b; - } - - /** - * associate this record with its Index record - */ - public void setIndex(Index id) { - idx = id; - } - - /** - * Associate this record with a worksheet. - * First checks to see if there is already - * a cell with this address. - */ - public void setSheet(Sheet b) { - this.worksheet = b; - } - - /** - * get the WorkSheet for this record. - */ - public Boundsheet getSheet() { - return (Boundsheet) worksheet; - } - - public void setWorkBook(WorkBook wk) { - wkbook = wk; - } - - public WorkBook getWorkBook() { - if ((wkbook == null) && (worksheet != null)) - wkbook = worksheet.getWorkBook(); - - return wkbook; - } - - /** - * set the column - */ - public void setCol(short i) { - byte[] c = ByteTools.shortToLEBytes(i); - System.arraycopy(c, 0, getData(), 2, 2); - col = i; - } - - public void setRowCol(int[] x) { - this.setRowNumber(x[0]); - this.setCol((short) x[1]); - } - - /** - * set the row - */ - public void setRowNumber(int i) { - byte[] r = ByteTools.cLongToLEBytes(i); - System.arraycopy(r, 0, getData(), 0, 2); - rw = i; - } - - - public short getColNumber() { - return col; - } - - public int getRowNumber() { - if (rw < 0) { - int rowi = rw * -1; - if (wkbook.getIsExcel2007()) - rw = MAXROWS - rowi; - else - rw = MAXCOLS_BIFF8 - rowi; - } - return rw; - } - - /** - * get a string address for the - * cell based on row and col ie: "H22" - */ - public String getCellAddress() { - int rownum = rw + 1; - if ((rownum < 0) && (col >= 0)) { // > 32k and the rows go negative... ! - rownum = MAXROWS + rownum; - } else if ((rownum == 0) && (col >= 0)) { // the very last row...MAXROWS_BIFF8 - rownum = MAXROWS; - } - if ((rownum > MAXROWS) || (col < 0)) { - if (DEBUGLEVEL > -1) - Logger.logWarn("XLSRecord.getCellAddress() Row/Col info incorrect for Cell:" + ExcelTools.getAlphaVal(col) + rownum); - return ""; - } - return ExcelTools.getAlphaVal(col) + rownum; - } - - /** - * returns the cell address in int[] {row, col} format - */ - public int[] getIntLocation() { - return new int[]{rw, col}; - } - - - /** - * return the cell address with sheet reference - * eg Sheet!A12 - * - * @return String - */ - public String getCellAddressWithSheet() { - if (this.getSheet() != null) - return this.getSheet().getSheetName() + "!" + this.getCellAddress(); - return this.getCellAddress(); - } - - /** - * perform record initialization - */ - public void init() { - if (originalsize == 0) originalsize = reclen; - } - - /** - * get a default "empty" data value for this record - */ - public Object getDefaultVal() { - if (this.isDoubleNumber) return new Double(0.0); - if (this.isFPNumber) return new Float(0.0f); - if (this.isBoolean) return Boolean.valueOf(false); - if (this.isIntNumber) return Integer.valueOf(0); - if (this.isString) return ""; - if (this.isFormula) return this.getFormulaRec().getFormulaString(); - if (this.isBlank) return ""; - return null; - } - - /** - * get the data type name for this record - */ - public String getDataType() { - if (this.isValueForCell) { - if (this.isBlank) return "Blank"; - if (this.isDoubleNumber) return "Double"; - if (this.isFPNumber) return "Float"; - if (this.isBoolean) return "Boolean"; - if (this.isIntNumber) return "Integer"; - if (this.isString) return "String"; - if (this.isFormula) return "Formula"; - } - return null; - } - - /** - * get the int val of the type for the valrec - */ - public Object getInternalVal() { - try { - switch (this.getCellType()) { - case TYPE_BLANK: - return getStringVal(); //essentially return ""; - - case TYPE_STRING: - return getStringVal(); - - case TYPE_FP: // always use Doubles to avoid loss of precision... see: - // details http://stackoverflow.com/questions/916081/convert-float-to-double-without-losing-precision - return new Double(getDblVal()); - - case TYPE_DOUBLE: - return new Double(getDblVal()); - - case TYPE_INT: - return Integer.valueOf(getIntVal()); - - case TYPE_FORMULA: - // OK this is broken, obviously we need to return a calced Object - Object obx = ((Formula) this).calculateFormula(); - return obx; -// return getStringVal(); - - case TYPE_BOOLEAN: - return Boolean.valueOf(getBooleanVal()); - - default: - return null; - } - } catch (Exception e) { - return null; - } // should never happen here... - } - - - /** - * Get the value of the record as an Object. - * To use the Object, cast it to the native - * type for the record. Ie: a String value - * would need to be cast to a String, an Integer - * to an Integer, etc. - */ - Object getVal() { - return null; - } - - /** - * Get the value of the record as a Boolean. - * Value must be parseable as a Boolean. - */ - public boolean getBooleanVal() { - return false; - } - - /** - * Get the value of the record as an Integer. - * Value must be parseable as an Integer or it - * will throw a NumberFormatException. - */ - public int getIntVal() { - return (int) Float.NaN; - } - - /** - * Get the value of the record as a Double. - * Value must be parseable as an Double or it - * will throw a NumberFormatException. - */ - public double getDblVal() { - return (double) Float.NaN; - } - - /** - * Get the value of the record as a Float. - * Value must be parseable as an Float or it - * will throw a NumberFormatException. - */ - public float getFloatVal() { - return Float.NaN; - } - - /** - * Get the value of the record as a String. - */ - public String getStringVal() { - return null; - } - - - /** - * Get the value of the record as a String. - */ - public String getStringVal(String encoding) { - return null; - } - - public void setStringVal(String v) { - Logger.logErr("Setting String Val on generic XLSRecord, value not held"); - } - - public void setBooleanVal(boolean b) { - Logger.logErr("Setting Boolean Val on generic XLSRecord, value not held"); - } - - public void setIntVal(int v) { - Logger.logErr("Setting int Val on generic XLSRecord, value not held"); - } - - public void setFloatVal(float v) { - Logger.logErr("Setting float Val on generic XLSRecord, value not held"); - } - - public void setDoubleVal(double v) { - Logger.logErr("Setting Double Val on generic XLSRecord, value not held"); - } - - /** - * do any pre-streaming processing such as expensive - * index updates or other deferrable processing. - */ - public void preStream() { - // override in sub-classes - } - - /** - * set the XF (format) record for this rec - */ - public void setXFRecord() { - if (wkbook == null) - return; - if ((ixfe > -1) && (ixfe < wkbook.getNumXfs())) { - if (myxf == null || myxf.tableidx != ixfe) { - this.myxf = wkbook.getXf(this.ixfe); - this.myxf.incUseCount(); - } - } - } - - /** - * set the XF (format) record for this rec - */ - public void setXFRecord(int i) { - if (i != ixfe || myxf == null) { - this.setIxfe(i); - this.setXFRecord(); - } - } - - /** - * set the XF (format) record for this rec - */ - public void setIxfe(int i) { - this.ixfe = i; - byte[] newxfe = ByteTools.cLongToLEBytes(i); - byte[] b = this.getData(); - if (b != null) - System.arraycopy(newxfe, 0, b, 4, 2); - this.setData(b); - } - - /** - * get the ixfe - */ - public int getIxfe() { - return this.ixfe; - } - - - /** - * get a new, generic instance of a Record. - */ - protected static XLSRecord getPrototype() { - Logger.logWarn("Attempt to get prototype XLSRecord failed. There is no prototype record defined for this record type."); - return null; - } - - - public void setByteReader(BlockByteReader db) { - databuf = db; - data = null; - } - - public BlockByteReader getByteReader() { - return databuf; - } - - /** - * Hold onto the original encrypted bytes so we can do a look ahead on records - */ - public void setEncryptedByteReader(BlockByteReader db) { - encryptedDatabuf = db; - - } - - public BlockByteReader getEncryptedByteReader() { - return encryptedDatabuf; - } - - public void setData(byte[] b) { - data = b; - this.databuf = null; - } - - /** - * gets the record data merging any Continue record - * data. - */ - public byte[] getData() { - int len = 0; - if ((len = this.getLength()) == 0) - return new byte[]{ - }; - if (data != null) - return data; - if (len > MAXRECLEN) { - setData(this.getBytesAt(0, MAXRECLEN)); - } else { - setData(this.getBytesAt(0, len - 4)); - } - - if (this.opcode == SST || this.opcode == SXLI) - return data; - - if (!isContinueMerged && hasContinues()) { - mergeContinues(); - } - return data; - } - - /** - * Merge continue data in to the record data array - */ - protected void mergeContinues() { - List cx = this.getContinueVect(); - if (cx != null) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - // get the main bytes first!!! - try { - out.write(data); - } catch (IOException e) { - Logger.logWarn( - "ERROR: parsing record continues failed: " - + this.toString() - + ": " - + e); - } - Iterator it = cx.iterator(); - while (it.hasNext()) { - Continue c = (Continue) it.next(); - byte[] nb = c.getData(); - if (c.getHasGrbit()) { // remove it - happens in continued StringRec ... a rare case - byte[] newnb = new byte[nb.length - 1]; - System.arraycopy(nb, 1, newnb, 0, newnb.length); - nb = newnb; - } - try { - out.write(nb); - } catch (IOException a) { - Logger.logWarn( - "ERROR: parsing record continues failed: " - + this.toString() - + ": " - + a); - } - } - this.data = out.toByteArray(); - } - isContinueMerged = true; - } - - - /** - * Gets the byte from the specified position in the - * record byte array. - * - * @param off - * @return - */ - public byte[] getBytes() { - return this.getBytesAt(0, this.getLength()); - } - - /** - * Gets the byte from the specified position in the - * record byte array. - * - * @param off - * @return - */ - public byte[] getBytesAt(int off, int len) { - if (this.data != null) { - if (len + off > data.length) len = data.length - off; // deal with bad requests - byte[] ret = new byte[len]; - System.arraycopy(data, off, ret, 0, len); - return ret; - } - if (databuf == null) return null; - return this.databuf.get(this, off, len); - } - - /** Sets a subset of temporary bytes for fast access during init methods... - * - - public void initCacheBytes(int start, int len) { - data = this.getBytesAt(start, len); - } */ - - /** - * resets the cache bytes so they do not take up space - */ - public void resetCacheBytes() { - // data = null; - } - - /** - * Gets the byte from the specified position in the - * record byte array. - * - * @param off - * @return - */ - public byte getByteAt(int off) { - if (this.data != null) { - return data[off]; - } - if (this.databuf == null) - throw new InvalidRecordException("XLSRecord has no data buffer." + this.getCellAddress()); - return this.databuf.get(this, off); - } - - public void setLength(int len) { - if (this.originalsize <= 0) this.originalsize = len; // returns the original len always - this.reclen = len; // returns updated lengths - } - - /** - * Returns the length of this - * record, including the 4 header bytes - */ - public int getLength() { - if (data != null) - return data.length + 4; - else if (this.databuf == null) // a new rec - return -1; - if ((hasContinues()) && - (!isContinueMerged) && - !(this.opcode == SST) && - !(this.opcode == SXLI)) { - if (this.reclen > MAXRECLEN) { - setData(this.getBytesAt(0, MAXRECLEN)); - } else { - setData(this.getBytesAt(0, this.reclen)); - } - mergeContinues(); - return data.length + 4; - } - return this.reclen + 4; - } - - /** - * @return Returns the isValueForCell. - */ - public boolean isValueForCell() { - return isValueForCell; - } - - /** - * @param isVal true if this is a cell-type record - */ - public void setValueForCell(boolean isVal) { - this.isValueForCell = isVal; - } - - /** - * @return Returns the isReadOnly. - */ - public boolean isReadOnly() { - return isReadOnly; - } - - /** - * @return Returns the streamer. - */ - public ByteStreamer getStreamer() { - return streamer; - } - - /** - * @param streamer The streamer to set. - */ - public void setStreamer(ByteStreamer str) { - streamer = str; - } - - /** - * @return Returns the hyperlink. - */ - public Hlink getHyperlink() { - return hyperlink; - } - - public void postStream() { - // nothing here -- use to blow out data - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - if (databuf != null) { - databuf.clear(); - databuf = null; - } - idx = null; - worksheet = null; - myxf = null; - wkbook = null; - streamer = null; - if (continues != null) { - for (int i = 0; i < continues.size(); i++) - ((XLSRecord) continues.get(i)).close(); - continues.clear(); - } - if (hyperlink != null) { - hyperlink.close(); - hyperlink = null; - } - mergeRange = null; - if (myrow != null) { - myrow = null; - } - mergeRange = null; - - } - - /** - * Get the color table for the associated workbook. If the workbook is null - * then the default colortable will be returned. - */ - public java.awt.Color[] getColorTable() { - try { - return this.getWorkBook().getColorTable(); - } catch (Exception e) { - return FormatHandle.COLORTABLE; - } - } - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSRecord.kt b/src/main/java/io/starter/formats/XLS/XLSRecord.kt new file mode 100644 index 0000000..1b74f9d --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/XLSRecord.kt @@ -0,0 +1,1042 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ExcelTools +import io.starter.OpenXLS.FormatHandle +import io.starter.formats.LEO.BlockByteConsumer +import io.starter.formats.LEO.BlockByteReader +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.Serializable +import java.util.AbstractList +import java.util.ArrayList + + +/** + *

                      + * The XLS byte stream is composed of records delimited by a header with type
                      + * and data length information, followed by a record Body which contains
                      + *
                      + * the byte[] data for the record.
                      + *
                      + * XLSRecord subclasses provide specific functionality.
                      + *
                      +
                      * + * + * @see WorkBook + * + * @see Boundsheet + * + * @see Index + * + * @see Dbcell + * + * @see Row + * + * @see Cell + */ + +open class XLSRecord : BiffRec, BlockByteConsumer, Serializable, XLSConstants { + + override var opcode: Short = 0 + internal var reclen: Int = 0 + internal var data: ByteArray? = null + @Transient + private var databuf: BlockByteReader? = null + /** + * Hold onto the original encrypted bytes so we can do a look ahead on records + */ + @Transient + override var encryptedByteReader: BlockByteReader? = null + protected var isContinueMerged = false + @Transient + protected var DEBUGLEVEL = 0 + /** + * @return Returns the isValueForCell. + */ + /** + * set whether this record contains the value + * of the Cell. + */ + override var isValueForCell: Boolean = false + internal var isFPNumber: Boolean = false + internal var isDoubleNumber = false + internal var isIntNumber: Boolean = false + var isString: Boolean = false + var isBoolean: Boolean = false + /** + * return whether this is a formula record + */ + var isFormula: Boolean = false + internal set + var isBlank: Boolean = false + /** + * @return Returns the isReadOnly. + */ + override var isReadOnly = false + internal set + protected var rw = -1 + override var colNumber: Short = 0 + protected set + var offset = 0 // byte stream offset of rec + @Transient + protected var idx: Index? = null + protected var worksheet: Sheet? = null + @Transient + var myxf: Xf? = null + @Transient + var wkbook: WorkBook? = null + /** + * @return Returns the streamer. + */ + /** + * @param streamer The streamer to set. + */ + @Transient + override var streamer: ByteStreamer? = null + var continues: AbstractList<*>? = null + internal var originalsize = -1 + internal var originalIndex = 0 + internal var originalOffset = 0 + internal var ixfe = -1 + /** + * @return Returns the hyperlink. + */ + override var hyperlink: Hlink? = null + internal var myrow: Row? = null + /** + * @return + */ + /** + * @param range + */ + override var mergeRange: CellRange? = null + /** + * Returns the index of the first block + * + * @return + */ + /** Get the blocks containing this Consumer's data + * + * @return + */ + /*unused: public Block[] getBlocks() { + return myblocks; + }*/ + + /** Set the blocks containing this Consumer's data + * + * @param myblocks + */ + /* unused: public void setBlocks(Block[] myb) { + myblocks = myb; + }*/ + + /** + * Sets the index of the first block + * + * @return + */ + override var firstBlock: Int = 0 + /** + * Returns the index of the last block + * + * @return + */ + /** + * Sets the index of the last block + * + * @return + */ + override var lastBlock: Int = 0 + + override val formulaRec: Formula? + get() { + if (this is Formula) { + this.populateExpression() + return this + } + return null + } + + /** + * get the row of this cell + */ + override var row: Row + get() { + if (myrow == null) { + myrow = this.worksheet!!.getRowByNumber(rw) + } + return myrow + } + set(r) { + myrow = r + } + + override val xfRec: Xf? + get() { + if (myxf == null) { + if (ixfe > -1 && ixfe < wkbook!!.numXfs) { + this.myxf = wkbook!!.getXf(this.ixfe) + } + } + return myxf + } + + + /** + * returns the existing font record + * for this Cell + */ + override val font: Font? + get() { + val b = this.workBook ?: return null + this.xfRec + return if (myxf == null) null else myxf!!.font + } + + /** + * set the Formatting for this BiffRec from the pattern + * match. + * + * + * case insensitive pattern match is performed... + */ + override val formatPattern: String? + get() = if (myxf == null) "" else myxf!!.formatPattern + + /** + * get the int val of the type for the valrec + */ + val cellType: Int + get() { + if (this.isBlank) return XLSConstants.TYPE_BLANK + if (this.isString) return XLSConstants.TYPE_STRING + if (this.isDoubleNumber) return XLSConstants.TYPE_DOUBLE + if (this.isFPNumber) return XLSConstants.TYPE_FP + if (this.isIntNumber) return XLSConstants.TYPE_INT + if (this.isFormula) return XLSConstants.TYPE_FORMULA + return if (this.isBoolean) XLSConstants.TYPE_BOOLEAN else -1 + } + + // record name + // hex record number + // stream offset + // size + // file offset + /*originalFileOffset*/// cell address, if applicable + val recDesc: String + get() { + var ret = "" + val name = this.javaClass.getSimpleName() + ret += if (name == "XLSRecord") "unknown" else name.toUpperCase() + ret += " (" + Integer.toHexString(opcode.toInt()).toUpperCase() + "h)" + ret += " at " + Integer.toHexString(offset).toUpperCase() + "h" + ret += " length " + Integer.toHexString(reclen).toUpperCase() + "h" + ret += " file " + if (databuf == null) + "no file" + else + databuf!!.getFileOffsetString(offset, reclen) + if (this.isValueForCell) ret += " cell " + this.cellAddress + + return ret + } + + /** + * return whether this is a numeric type + */ + val isNumber: Boolean + get() = if (this.opcode == XLSConstants.RK) true else this.opcode == XLSConstants.NUMBER + + var lastidx = -1 + + /** + * return the real (not just boundsheet) record index of this object + */ + val realRecordIndex: Int + get() = streamer!!.getRealRecordIndex(this) + + /** + * return the record index of this object + */ + override// KSC: Added + val recordIndex: Int + get() = if (streamer == null) { + if (this.sheet != null) this.sheet!!.sheetRecs.indexOf(this) else -1 + } else streamer!!.getRecordIndex(this) + + override val continueVect: List<*> + get() { + if (continues != null) return this.continues + continues = CompatibleVector() + return continues + } + + /** + * get the WorkSheet for this record. + */ + override val sheet: Boundsheet? + get() = worksheet as Boundsheet? + + override var workBook: WorkBook? + get() { + if (wkbook == null && worksheet != null) + wkbook = worksheet!!.workBook + + return wkbook + } + set(wk) { + wkbook = wk + } + + /** + * set the row + */ + override var rowNumber: Int + get() { + if (rw < 0) { + val rowi = rw * -1 + if (wkbook!!.isExcel2007) + rw = XLSConstants.MAXROWS - rowi + else + rw = XLSConstants.MAXCOLS_BIFF8 - rowi + } + return rw + } + set(i) { + val r = ByteTools.cLongToLEBytes(i) + System.arraycopy(r, 0, getData()!!, 0, 2) + rw = i + } + + /** + * get a string address for the + * cell based on row and col ie: "H22" + */ + override// > 32k and the rows go negative... ! + // the very last row...MAXROWS_BIFF8 + val cellAddress: String + get() { + var rownum = rw + 1 + if (rownum < 0 && colNumber >= 0) { + rownum = XLSConstants.MAXROWS + rownum + } else if (rownum == 0 && colNumber >= 0) { + rownum = XLSConstants.MAXROWS + } + if (rownum > XLSConstants.MAXROWS || colNumber < 0) { + if (DEBUGLEVEL > -1) + Logger.logWarn("XLSRecord.getCellAddress() Row/Col info incorrect for Cell:" + ExcelTools.getAlphaVal(colNumber.toInt()) + rownum) + return "" + } + return ExcelTools.getAlphaVal(colNumber.toInt()) + rownum + } + + /** + * returns the cell address in int[] {row, col} format + */ + open val intLocation: IntArray + get() = intArrayOf(rw, colNumber.toInt()) + + + /** + * return the cell address with sheet reference + * eg Sheet!A12 + * + * @return String + */ + val cellAddressWithSheet: String + get() = if (this.sheet != null) this.sheet!!.sheetName + "!" + this.cellAddress else this.cellAddress + + /** + * get a default "empty" data value for this record + */ + override val defaultVal: Any? + get() { + if (this.isDoubleNumber) return 0.0 + if (this.isFPNumber) return 0.0f + if (this.isBoolean) return java.lang.Boolean.valueOf(false) + if (this.isIntNumber) return Integer.valueOf(0) + if (this.isString) return "" + if (this.isFormula) return this.formulaRec!!.formulaString + return if (this.isBlank) "" else null + } + + /** + * get the data type name for this record + */ + override val dataType: String? + get() { + if (this.isValueForCell) { + if (this.isBlank) return "Blank" + if (this.isDoubleNumber) return "Double" + if (this.isFPNumber) return "Float" + if (this.isBoolean) return "Boolean" + if (this.isIntNumber) return "Integer" + if (this.isString) return "String" + if (this.isFormula) return "Formula" + } + return null + } + + /** + * get the int val of the type for the valrec + */ + override//essentially return ""; + // always use Doubles to avoid loss of precision... see: + // details http://stackoverflow.com/questions/916081/convert-float-to-double-without-losing-precision + // OK this is broken, obviously we need to return a calced Object + // return getStringVal(); + // should never happen here... + val internalVal: Any? + get() { + try { + when (this.cellType) { + XLSConstants.TYPE_BLANK -> return stringVal + + XLSConstants.TYPE_STRING -> return stringVal + + XLSConstants.TYPE_FP -> return dblVal + + XLSConstants.TYPE_DOUBLE -> return dblVal + + XLSConstants.TYPE_INT -> return Integer.valueOf(intVal) + + XLSConstants.TYPE_FORMULA -> { + return (this as Formula).calculateFormula() + } + + XLSConstants.TYPE_BOOLEAN -> return java.lang.Boolean.valueOf(booleanVal) + + else -> return null + } + } catch (e: Exception) { + return null + } + + } + + + /** + * Get the value of the record as an Object. + * To use the Object, cast it to the native + * type for the record. Ie: a String value + * would need to be cast to a String, an Integer + * to an Integer, etc. + */ + internal open val `val`: Any? + get() = null + + /** + * Get the value of the record as a Boolean. + * Value must be parseable as a Boolean. + */ + override var booleanVal: Boolean + get() = false + set(b) = Logger.logErr("Setting Boolean Val on generic XLSRecord, value not held") + + /** + * Get the value of the record as an Integer. + * Value must be parseable as an Integer or it + * will throw a NumberFormatException. + */ + override var intVal: Int + get() = java.lang.Float.NaN.toInt() + set(v) = Logger.logErr("Setting int Val on generic XLSRecord, value not held") + + /** + * Get the value of the record as a Double. + * Value must be parseable as an Double or it + * will throw a NumberFormatException. + */ + override val dblVal: Double + get() = java.lang.Float.NaN.toDouble() + + /** + * Get the value of the record as a Float. + * Value must be parseable as an Float or it + * will throw a NumberFormatException. + */ + override var floatVal: Float + get() = java.lang.Float.NaN + set(v) = Logger.logErr("Setting float Val on generic XLSRecord, value not held") + + /** + * Get the value of the record as a String. + */ + override var stringVal: String? + get() = null + set(v) = Logger.logErr("Setting String Val on generic XLSRecord, value not held") + + override var byteReader: BlockByteReader? + get() = databuf + set(db) { + databuf = db + data = null + } + + + /** + * Gets the byte from the specified position in the + * record byte array. + * + * @param off + * @return + */ + override val bytes: ByteArray? + get() = this.getBytesAt(0, this.length) + + /** + * Returns the length of this + * record, including the 4 header bytes + */ + override// a new rec + // returns the original len always + // returns updated lengths + var length: Int + get() { + if (data != null) + return data!!.size + 4 + else if (this.databuf == null) + return -1 + if (hasContinues() && + !isContinueMerged && + this.opcode != XLSConstants.SST && + this.opcode != XLSConstants.SXLI) { + if (this.reclen > XLSConstants.MAXRECLEN) { + setData(this.getBytesAt(0, XLSConstants.MAXRECLEN)) + } else { + setData(this.getBytesAt(0, this.reclen)) + } + mergeContinues() + return data!!.size + 4 + } + return this.reclen + 4 + } + set(len) { + if (this.originalsize <= 0) this.originalsize = len + this.reclen = len + } + + /** + * Get the color table for the associated workbook. If the workbook is null + * then the default colortable will be returned. + */ + val colorTable: Array + get() { + try { + return this.workBook!!.colorTable + } catch (e: Exception) { + return FormatHandle.COLORTABLE + } + + } + + open fun shouldEncrypt(): Boolean { + return true + } + + + /** + * Removes this BiffRec from the WorkSheet + * + * @param whether to nullify this Cell + */ + override fun remove(nullme: Boolean): Boolean { + val success = false + if (worksheet != null && isValueForCell) + sheet!!.removeCell(this) + + if (streamer != null) + streamer!!.removeRecord(this) + + if (nullme) { + try { + this.finalize() + } catch (t: Throwable) { + } + + } + this.worksheet = null + return true + } + + // Methods from BlockByteConsumer // + + /** + * Set the relative position within the data + * underlying the block vector represented by + * the BlockByteReader. + * + * + * In other words, this is the relative position + * used by the BlockByteReader to offset the Consumer's + * read position within the collection of Data Blocks. + * + * + * This may be an offset relative to the data in a file, + * or within a Storage contained in a file. + * + * + * The Workbook Storage for example will contain a non-contiguous + * collection of Blocks containing data from any number of + * positions in a file. + * + * + * This collection forms a contiguous span of bytes comprising + * an XLS Workbook. The XLSRecords within this span of bytes will + * set their relative position within this 'virtual' array. Thus + * the XLSRecord positions are relative to the order of bytes contained + * in the Block collection. The BOF record then is at offset 0 within the + * data of the first Block, even though the underlying data of this + * first Block may be anywhere on disk. + * + * @param pos + */ + override fun setOffset(pos: Int) { + if (originalOffset < 1) originalOffset = pos + offset = pos + } + + /** + * Get the relative position within the data + * underlying the block vector represented by + * the BlockByteReader. + * + * @return relative position + */ + override fun getOffset(): Int { + return if (this.data == null) this.originalOffset else offset + } + + + protected fun initRowCol() { + var pos = 0 + + val bt = this.getBytesAt(pos, 2) + rw = ByteTools.readUnsignedShort(bt!![0], bt[1]) + pos += 2 + colNumber = ByteTools.readShort(this.getByteAt(pos++).toInt(), this.getByteAt(pos++).toInt()) + } + + + override fun toString(): String { + return recDesc + } + + /** + * Dumps this record as a human-readable string. + */ + override fun toHexDump(): String { + return recDesc + "\n" + ByteTools.getByteDump(this.getData(), 0) + } + + /** + * Copy all formatting info from source biffrec + * + * @see io.starter.formats.XLS.BiffRec.copyFormat + */ + override fun copyFormat(source: BiffRec) { + val clone = source.xfRec.clone() as Xf + val fontClone = source.xfRec.font!!.clone() as Font + + Logger.logInfo(source.toString() + ":" + source.xfRec + ":" + clone) + var fid = -1 + var xid = -1 + // see if we have an equivalent Xf/Font combo + if (this.workBook!!.fontRecs.contains(fontClone)) { + fid = this.workBook!!.fontRecs.indexOf(fontClone) + } + if (this.workBook!!.xfrecs.contains(clone)) { + xid = this.workBook!!.xfrecs.indexOf(clone) + } + + // add the xf/font and set the ixfe + this.workBook!!.addRecord(clone, false) + this.workBook!!.addRecord(fontClone, false) + clone.setFont(fontClone.idx) + this.setXFRecord(clone.idx) + + } + + /** + * clone a record + */ + override fun clone(): Any? { + try { + val cn = javaClass.getName() + val rec = Class.forName(cn).newInstance() as XLSRecord + val inb = bytes + rec.setData(inb) + rec.streamer = this.streamer + rec.workBook = workBook + rec.opcode = opcode + rec.length = length + rec.setSheet(sheet) //20081120 KSC: otherwise may set sheet incorrectly in init + rec.init() + return rec + } catch (e: Exception) { + Logger.logInfo("cloning XLSRecord " + this.cellAddress + " failed: " + e) + } + + return null + } + + /** methods from CompatibleVectorHints + * + * protected transient int recordIdx = -1; + */ + /** provide a hint to the CompatibleVector + * about this objects likely position. + * + * public int getRecordIndexHint(){return recordIdx;} + */ + + /** set index information about this + * objects likely position. + * + * public void setRecordIndexHint(int i){ + * lastidx = i; + * recordIdx = i; + * } + */ + /** + * set the DEBUG level + */ + override fun setDebugLevel(b: Int) { + DEBUGLEVEL = b + } + + /** + * adds a CONTINUE record to the array of CONTINUE records + * for this record, containing all data + * beyond the 8224 byte record size limit. + */ + override fun addContinue(c: Continue) { + if (continues == null) continues = ArrayList() + continues!!.add(c) + } + + /** + * remove all Continue records + */ + override fun removeContinues() { + if (continues != null) continues!!.clear() + } + + /** + * returns whether this record has a CONTINUE + * record containing data beyond the 8228 byte + * record size limit. + * + * + * XLSRecords can have 0 or more CONTINUE records. + */ + override fun hasContinues(): Boolean { + return if (continues == null) false else this.continues!!.size > 0 + } + + /** + * associate this record with its Index record + */ + override fun setIndex(id: Index) { + idx = id + } + + /** + * Associate this record with a worksheet. + * First checks to see if there is already + * a cell with this address. + */ + override fun setSheet(b: Sheet?) { + this.worksheet = b + } + + /** + * set the column + */ + override fun setCol(i: Short) { + val c = ByteTools.shortToLEBytes(i) + System.arraycopy(c, 0, getData()!!, 2, 2) + colNumber = i + } + + override fun setRowCol(x: IntArray) { + this.rowNumber = x[0] + this.setCol(x[1].toShort()) + } + + /** + * perform record initialization + */ + override fun init() { + if (originalsize == 0) originalsize = reclen + } + + + /** + * Get the value of the record as a String. + */ + override fun getStringVal(encoding: String): String? { + return null + } + + override fun setDoubleVal(v: Double) { + Logger.logErr("Setting Double Val on generic XLSRecord, value not held") + } + + /** + * do any pre-streaming processing such as expensive + * index updates or other deferrable processing. + */ + override fun preStream() { + // override in sub-classes + } + + /** + * set the XF (format) record for this rec + */ + override fun setXFRecord() { + if (wkbook == null) + return + if (ixfe > -1 && ixfe < wkbook!!.numXfs) { + if (myxf == null || myxf!!.idx != ixfe) { + this.myxf = wkbook!!.getXf(this.ixfe) + this.myxf!!.incUseCount() + } + } + } + + /** + * set the XF (format) record for this rec + */ + override fun setXFRecord(i: Int) { + if (i != ixfe || myxf == null) { + this.setIxfe(i) + this.setXFRecord() + } + } + + /** + * set the XF (format) record for this rec + */ + override fun setIxfe(i: Int) { + this.ixfe = i + val newxfe = ByteTools.cLongToLEBytes(i) + val b = this.getData() + if (b != null) + System.arraycopy(newxfe, 0, b, 4, 2) + this.setData(b) + } + + /** + * get the ixfe + */ + override fun getIxfe(): Int { + return this.ixfe + } + + override fun setData(b: ByteArray?) { + data = b + this.databuf = null + } + + /** + * gets the record data merging any Continue record + * data. + */ + override fun getData(): ByteArray? { + var len = 0 + if ((len = this.length) == 0) + return byteArrayOf() + if (data != null) + return data + if (len > XLSConstants.MAXRECLEN) { + setData(this.getBytesAt(0, XLSConstants.MAXRECLEN)) + } else { + setData(this.getBytesAt(0, len - 4)) + } + + if (this.opcode == XLSConstants.SST || this.opcode == XLSConstants.SXLI) + return data + + if (!isContinueMerged && hasContinues()) { + mergeContinues() + } + return data + } + + /** + * Merge continue data in to the record data array + */ + fun mergeContinues() { + val cx = this.continueVect + if (cx != null) { + val out = ByteArrayOutputStream() + // get the main bytes first!!! + try { + out.write(data!!) + } catch (e: IOException) { + Logger.logWarn( + "ERROR: parsing record continues failed: " + + this.toString() + + ": " + + e) + } + + val it = cx.iterator() + while (it.hasNext()) { + val c = it.next() as Continue + var nb = c.data + if (c.hasGrbit) { // remove it - happens in continued StringRec ... a rare case + val newnb = ByteArray(nb!!.size - 1) + System.arraycopy(nb, 1, newnb, 0, newnb.size) + nb = newnb + } + try { + out.write(nb!!) + } catch (a: IOException) { + Logger.logWarn( + "ERROR: parsing record continues failed: " + + this.toString() + + ": " + + a) + } + + } + this.data = out.toByteArray() + } + isContinueMerged = true + } + + /** + * Gets the byte from the specified position in the + * record byte array. + * + * @param off + * @return + */ + override fun getBytesAt(off: Int, len: Int): ByteArray? { + var len = len + if (this.data != null) { + if (len + off > data!!.size) len = data!!.size - off // deal with bad requests + val ret = ByteArray(len) + System.arraycopy(data!!, off, ret, 0, len) + return ret + } + return if (databuf == null) null else this.databuf!!.get(this, off, len) + } + + /** Sets a subset of temporary bytes for fast access during init methods... + * + * + * public void initCacheBytes(int start, int len) { + * data = this.getBytesAt(start, len); + * } */ + + /** + * resets the cache bytes so they do not take up space + */ + fun resetCacheBytes() { + // data = null; + } + + /** + * Gets the byte from the specified position in the + * record byte array. + * + * @param off + * @return + */ + override fun getByteAt(off: Int): Byte { + if (this.data != null) { + return data!![off] + } + if (this.databuf == null) + throw InvalidRecordException("XLSRecord has no data buffer." + this.cellAddress) + return this.databuf!!.get(this, off) + } + + /** + * @param isVal true if this is a cell-type record + */ + fun setValueForCell(isVal: Boolean) { + this.isValueForCell = isVal + } + + override fun postStream() { + // nothing here -- use to blow out data + } + + /** + * clear out object references in prep for closing workbook + */ + open fun close() { + if (databuf != null) { + databuf!!.clear() + databuf = null + } + idx = null + worksheet = null + myxf = null + wkbook = null + streamer = null + if (continues != null) { + for (i in continues!!.indices) + (continues!![i] as XLSRecord).close() + continues!!.clear() + } + if (hyperlink != null) { + hyperlink!!.close() + hyperlink = null + } + mergeRange = null + if (myrow != null) { + myrow = null + } + mergeRange = null + + } + + companion object { + + private const val serialVersionUID = -106915096753184441L + + + /** + * get a new, generic instance of a Record. + */ + protected val prototype: XLSRecord? + get() { + Logger.logWarn("Attempt to get prototype XLSRecord failed. There is no prototype record defined for this record type.") + return null + } + } + + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSRecordFactory.java b/src/main/java/io/starter/formats/XLS/XLSRecordFactory.java deleted file mode 100644 index 928e321..0000000 --- a/src/main/java/io/starter/formats/XLS/XLSRecordFactory.java +++ /dev/null @@ -1,996 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.formats.XLS.charts.*; -import io.starter.formats.XLS.formulas.Ptg; - -/** - * Factory to create XLSRecords and Ptgs. - */ -public class XLSRecordFactory - implements XLSConstants { - - /** - * This class is static only, prohibit construction. - */ - private XLSRecordFactory() { - throw new UnsupportedOperationException( - "XLSRecordFactory is purely static"); - } - - // 20060504 KSC: add separate array for unary prefix operators: - // maps regular operator to unary pseudo version located in ptgLookup below - public static Object[][] ptgPrefixOperators = { - {"+", "u+"}, - {"-", "u-"}, - // should $ be in here??? - }; - - /** - * Maps BIFF8 record opcodes to classes. - */ - // Removed static object instance creations as very difficult (impossible) to dereference so as can release memeory ... -// TRY : private static final Map records; - - // subset of ptgLookup below used for pattern matching in formula strings - public static String[][] ptgOps = { - {"#VALUE!", "PtgErr"}, - {"#NULL!", "PtgErr"}, - {"#DIV/0!", "PtgErr"}, - {"#VALUE!", "PtgErr"}, - {"#REF!", "PtgErr"}, - {"#NUM!", "PtgErr"}, - {"#N/A", "PtgErr"}, - // operators - {"(", "PtgParen"}, - {"*", "PtgMlt"}, - {"/", "PtgDiv"}, - {"^", "PtgPower"}, - {"&", "PtgConcat"}, - {"<>", "PtgNE"}, - {"<=", "PtgLE"}, - {"<", "PtgLT"}, - {">=", "PtgGE"}, - {">", "PtgGT"}, - {"=", "PtgEQ"}, - {"!=", "PtgNE"}, - {"+", "PtgAdd"}, // moved to AFTER other operators - {"-", "PtgSub"}, - }; - // this is how you init a 2D array of Objects - public static String[][] ptgLookup = { - // 20070215 KSC: added constant strings to avoid PtgRef3d matches - {"#VALUE!", "PtgErr"}, - {"#NULL!", "PtgErr"}, - {"#DIV/0!", "PtgErr"}, - {"#VALUE!", "PtgErr"}, - {"#REF!", "PtgErr"}, - {"#NUM!", "PtgErr"}, - {"#N/A", "PtgErr"}, - // operators - {"(", "PtgParen"}, - {"*", "PtgMlt"}, - {"/", "PtgDiv"}, - {"^", "PtgPower"}, - {"&", "PtgConcat"}, - {"<>", "PtgNE"}, - {"<=", "PtgLE"}, - {"<", "PtgLT"}, - {">=", "PtgGE"}, - {">", "PtgGT"}, - {"=", "PtgEQ"}, - {"!=", "PtgNE"}, - //{" ","PtgIsect"}, intersection operator, need to work out how to use a space as operator-- SEE PTGMEMFUNC/MEMAREA -- only valid when parsing complex ranges - //{",","PtgUnion"}, problems matching as a separator in string parsing - //{":","PtgRange"}, // may have issues with ptgArea? - {"+", "PtgAdd"}, // moved to AFTER other operators - {"-", "PtgSub"}, - //operands - {"PtgStr", "PtgStr"}, - {"PtgNumber", "PtgNumber"}, - {"PtgInt", "PtgInt"}, - {"PtgRef", "PtgRef"}, - {"PtgArea", "PtgArea"}, - {"false", "PtgBool"}, - {"true", "PtgBool"}, - {"PtgArea3d", "PtgArea3d"}, - {"PtgRef3d", "PtgRef3d"}, - {"PtgArray", "PtgArray"}, - {"PtgMissArg", "PtgMissArg"}, - {"PtgMemFunc", "PtgMemFunc"}, - {"PtgAtr", "PtgAtr"}, - //functions - {"PtgFunc", "PtgFunc"}, - {"PtgFuncVar", "PtgFuncVar"}, - // unary prefix operators: see FormulaParser.splitString - {"u+", "PtgUPlus"}, - {"u-", "PtgUMinus"}, - {")", "PtgParen"}, - - - }; - - /* DO DIFFERENTLY SO TO AVOID HANGING OBJECT REFERENCES - static { - HashMap recmap = new HashMap(); - - // Most Frequent - case BLANK), new Blank() ); - case ROW), new Row() ); - case XF), new Xf() ); - case INDEX), new Index() ); - case COUNTRY), new Country() ); - case CALCMODE), new CalcMode() ); - case DIMENSIONS), new Dimensions() ); - case SELECTION), new Selection() ); - case DEFAULTROWHEIGHT), new DefaultRowHeight() ); - case DEFCOLWIDTH), new DefColWidth() ); - case DBCELL), new Dbcell() ); - case BOF), new Bof() ); - case BOUNDSHEET), new Boundsheet() ); - case EOF), new Eof() ); - case FORMAT), new Format() ); - case STYLE), new Style() ); - case PASSWORD), new Password() ); - case PALETTE), new Palette() ); - case ARRAY), new Array() ); - case BOOLERR), new Boolerr() ); - case EXTERNSHEET), new Externsheet() ); - case EXTERNNAME), new Externname() ); - case FORMULA), new Formula() ); - case LABEL), new Label() ); - case TXO), new Txo() ); - case CONTINUE), new Continue() ); - case SST), new Sst() ); - case GUTS), new Guts() ); - case EXTSST), new Extsst() ); - case HLINK), new Hlink() ); - case LABELSST), new Labelsst() ); - case NUMBER), new NumberRec() ); - case MERGEDCELLS), new Mergedcells() ); - case MULBLANK), new Mulblank() ); - case MULRK), new Mulrk() ); - case RK), new Rk() ); - case RSTRING), new Rstring() ); - case SHRFMLA), new Shrfmla() ); - case STRINGREC), new StringRec() ); - case SUPBOOK), new Supbook() ); - case DV), new Dv() ); - case DVAL), new Dval() ); - case SETUP), new Setup() ); - case HCENTER), new HCenter() ); - case VCENTER), new VCenter() ); - case LEFTMARGIN), new LeftMargin() ); - case RIGHTMARGIN), new RightMargin() ); - case TOPMARGIN), new TopMargin() ); - case BOTTOMMARGIN), new BottomMargin() ); - case PRINTGRID), new PrintGrid() ); - case PRINTROWCOL), new PrintRowCol() ); - case XCT), new Xct() ); - case CRN), new Crn() ); - case NOTE), new Note() ); - - // Named Ranges and References - case NAME), new Name() ); - - // Workbook Settings - case FONT), new Font() ); - case DSF), new Dsf() ); - case WINDOW1), new Window1() ); - case WINDOW2), new Window2() ); - case CODENAME), new Codename() ); - case PROTECT), new Protect() ); - case OBJPROTECT), new ObjProtect() ); - case SCENPROTECT), new ScenProtect() ); - case FEATHEADR), new FeatHeadr() ); - case PROT4REV), new Prot4rev() ); - case COLINFO), new Colinfo() ); - case USERSVIEWBEGIN), new Usersviewbegin() ); - case USERSVIEWEND), new Usersviewend() ); - case WSBOOL), new WsBool() ); - case BOOKBOOL), new BookBool() ); - case USR_EXCL), new UsrExcl() ); - case INTERFACE_HDR), new InterfaceHdr() ); - case RRD_INFO), new RrdInfo() ); - case RRD_HEAD), new RrdHead() ); - case FILE_LOCK), new FileLock() ); - case PLS), new Pls() ); - case HEADERREC), new Headerrec() ); - case DATE1904), new NineteenOhFour() ); - - // Sheet Settings - case OBJ), new Obj() ); - case OBPROJ), new Obproj() ); - case FOOTERREC), new Footerrec() ); - case TABID), new TabID() ); - case PANE), new Pane() ); - case SCL), new Scl() ); - - // Protection settings - case FILEPASS), new Filepass() ); - - // Conditional Formatting - case CF), new Cf() ); - case CONDFMT), new Condfmt() ); - - // Auto filter - case AUTOFILTER), new AutoFilter() ); - - // Chart Records - case CHART), new Chart() ); - case SERIES), new Series() ); - case SERIESTEXT), new SeriesText() ); - case SERIESLIST), new SeriesList() ); - case AI), new Ai() ); - case BEGIN), new Begin() ); - case END), new End() ); - case UNITS), new Units() ); - case CHART), new Chart() ); - case DATAFORMAT), new DataFormat() ); - case LINEFORMAT), new LineFormat() ); - case MARKERFORMAT), new MarkerFormat() ); - case AREAFORMAT), new AreaFormat() ); - case PIEFORMAT), new PieFormat() ); - case ATTACHEDLABEL), new AttachedLabel() ); - case CHARTFORMAT), new ChartFormat() ); - case LEGEND), new Legend() ); - case BAR), new Bar() ); - case LINE), new Line() ); - case PIE), new Pie() ); - case AREA), new Area() ); - case SCATTER), new Scatter() ); - case CHARTLINE), new ChartLine() ); - case AXIS), new Axis() ); - case TICK), new Tick() ); - case VALUERANGE), new ValueRange() ); - case CATSERRANGE), new CatserRange() ); - case AXISLINEFORMAT), new AxisLineFormat() ); - case CHARTFORMATLINK), new ChartFormatLink() ); - case DEFAULTTEXT), new DefaultText() ); - case TEXTDISP), new TextDisp() ); - case FONTX), new Fontx() ); - case OBJECTLINK), new ObjectLink() ); - case FRAME), new Frame() ); - case BEGIN), new Begin() ); - case END), new End() ); - case PLOTAREA), new PlotArea() ); - case THREED), new ThreeD() ); - case PICF), new Picf() ); - case DROPBAR), new Dropbar() ); - case RADAR), new Radar() ); - case SURFACE), new Surface() ); - case RADARAREA), new RadarArea() ); - case AXISPARENT), new AxisParent() ); - case LEGENDXN), new Legendxn() ); - case SHTPROPS), new ShtProps() ); - case SERTOCRT), new SerToCrt() ); - case AXESUSED), new Axesused() ); - case SBASEREF), new SbaseRef() ); - case SERPARENT), new SerParent() ); - case SERAUXTREND), new SerauxTrend() ); - case IFMT), new Ifmt() ); - case POS), new Pos() ); - case ALRUNS), new AlRuns() ); - case AI), new Ai() ); - case SERAUXERRBAR), new SerauxErrBar() ); - case SERFMT), new Serfmt() ); - case CHART3DBARSHAPE), new Chart3DBarShape() ); - case FBI), new Fbi() ); - case BOPPOP), new Boppop() ); - case AXCENT), new Axcent() ); - case DAT), new Dat() ); - case PLOTGROWTH), new PlotGrowth() ); - case SIIINDEX), new SiIndex() ); - case GELFRAME), new GelFrame() ); - case BOPPOPCUSTOM), new BoppopCustom() ); - case FONTBASIS), new FontBasis() ); - - // PivotTable Records - case SXVIEW), new Sxview() ); - case SXFORMAT), new Sxformat() ); - case SXLI), new Sxli() ); - case SXVI), new Sxvi() ); - case SXVD), new Sxvd() ); - case SXIVD), new Sxivd() ); - - // Object and Picture Records - case PHONETIC), new Phonetic() ); - case MSODRAWING), new MSODrawing() ); - case MSODRAWINGGROUP), new MSODrawingGroup() ); - case MSODRAWINGSELECTION), new MSODrawingSelection() ); - - // Excel 9 Chart Records - case CHARTFRTINFO), new ChartFrtInfo() ); - case FRTWRAPPER), new FrtWrapper() ); - case STARTBLOCK), new StartBlock() ); - case ENDBLOCK), new EndBlock() ); - case STARTOBJECT), new StartObject() ); - case ENDOBJECT), new EndObject() ); - case CATLAB), new CatLab() ); - case YMULT), new YMult() ); - case SXVIEWLINK), new SxViewLink() ); - case PIVOTCHARTBITS), new PivotChartBits() ); - case FRTFONTLIST), new FrtFontList() ); - case PIVOTCHARTLINK), new PivotChartLink() ); - case DATALABEXTCONTENTS), new DataLabExtContents() ); - case DATALABEXT), new DataLabExt() ); - - records = Collections.unmodifiableMap( recmap ); - } - */ - /* - * Create a ptg record from a name, will be init'ed elsewhere if needed. - * I am keeping this seperate from getBiffRecord for performance reasons. Why search - * through all the ptg/formula stuff every time you deal with a XLS record an vice-versa. Also, - * init'ing may be different. Small duplication of code, but I think it is worth it. - */ - public static Ptg getPtgRecord(String name) throws InvalidRecordException { - for (int t = 0; t < ptgLookup.length; t++) { - if (ptgLookup[t][0].equalsIgnoreCase(name)) { - try { - String classname = ptgLookup[t][1]; - return (Ptg) Class.forName("io.starter.formats.XLS.formulas." + classname).newInstance(); - } catch (Exception e) { - throw new InvalidRecordException("ERROR: Creating Record: " + name + "failed: " + e.toString()); - } - } - } - return null; - } - - - /** - * Get an instance of the record type corresponding to the given opcode. - * - * @param opcode the BIFF8 record opcode to be resolved - * @return an instance of the class corresponding to the given opcode - * or an XLSRecord if the opcode is unknown - * @throws RuntimeException if instantiation of the record fails - */ - public static BiffRec getBiffRecord(short opcode) { - // TRY THIS: - BiffRec record = null; - try { - switch (opcode) { - case BLANK: - record = new Blank(); - break; - case ROW: - record = new Row(); - break; - case XF: - record = new Xf(); - break; - case INDEX: - record = new Index(); - break; - case COUNTRY: - record = new Country(); - break; - case CALCMODE: - record = new CalcMode(); - break; - case DIMENSIONS: - record = new Dimensions(); - break; - case SELECTION: - record = new Selection(); - break; - case DEFAULTROWHEIGHT: - record = new DefaultRowHeight(); - break; - case DEFCOLWIDTH: - record = new DefColWidth(); - break; - case DBCELL: - record = new Dbcell(); - break; - case BOF: - record = new Bof(); - break; - case BOUNDSHEET: - record = new Boundsheet(); - break; - case EOF: - record = new Eof(); - break; - case FORMAT: - record = new Format(); - break; - case STYLE: - record = new Style(); - break; - case PASSWORD: - record = new Password(); - break; - case PALETTE: - record = new Palette(); - break; - case ARRAY: - record = new Array(); - break; - case BOOLERR: - record = new Boolerr(); - break; - case EXTERNSHEET: - record = new Externsheet(); - break; - case EXTERNNAME: - record = new Externname(); - break; - case FORMULA: - record = new Formula(); - break; - case LABEL: - record = new Label(); - break; - case TXO: - record = new Txo(); - break; - case CONTINUE: - record = new Continue(); - break; - case SST: - record = new Sst(); - break; - case GUTS: - record = new Guts(); - break; - case EXTSST: - record = new Extsst(); - break; - case HLINK: - record = new Hlink(); - break; - case LABELSST: - record = new Labelsst(); - break; - case NUMBER: - record = new NumberRec(); - break; - case MERGEDCELLS: - record = new Mergedcells(); - break; - case MULBLANK: - record = new Mulblank(); - break; - case MULRK: - record = new Mulrk(); - break; - case RK: - record = new Rk(); - break; - case RSTRING: - record = new Rstring(); - break; - case SHRFMLA: - record = new Shrfmla(); - break; - case STRINGREC: - record = new StringRec(); - break; - case SUPBOOK: - record = new Supbook(); - break; - case DV: - record = new Dv(); - break; - case DVAL: - record = new Dval(); - break; - case SETUP: - record = new Setup(); - break; - case HCENTER: - record = new HCenter(); - break; - case VCENTER: - record = new VCenter(); - break; - case LEFTMARGIN: - record = new LeftMargin(); - break; - case RIGHTMARGIN: - record = new RightMargin(); - break; - case TOPMARGIN: - record = new TopMargin(); - break; - case BOTTOMMARGIN: - record = new BottomMargin(); - break; - case PRINTGRID: - record = new PrintGrid(); - break; - case PRINTROWCOL: - record = new PrintRowCol(); - break; - case XCT: - record = new Xct(); - break; - case CRN: - record = new Crn(); - break; - case NOTE: - record = new Note(); - break; - - // Named Ranges and References - case NAME: - record = new Name(); - break; - - // Workbook Settings - case FONT: - record = new Font(); - break; - case DSF: - record = new Dsf(); - break; - case WINDOW1: - record = new Window1(); - break; - case WINDOW2: - record = new Window2(); - break; - case PLV: - record = new PLV(); - break; - case CODENAME: - record = new Codename(); - break; - case PROTECT: - record = new Protect(); - break; - case OBJPROTECT: - record = new ObjProtect(); - break; - case SCENPROTECT: - record = new ScenProtect(); - break; - case FEATHEADR: - record = new FeatHeadr(); - break; - case PROT4REV: - record = new Prot4rev(); - break; - case COLINFO: - record = new Colinfo(); - break; - case USERSVIEWBEGIN: - record = new Usersviewbegin(); - break; - case USERSVIEWEND: - record = new Usersviewend(); - break; - case WSBOOL: - record = new WsBool(); - break; - case BOOKBOOL: - record = new BookBool(); - break; - case USR_EXCL: - record = new UsrExcl(); - break; - case INTERFACE_HDR: - record = new InterfaceHdr(); - break; - case RRD_INFO: - record = new RrdInfo(); - break; - case RRD_HEAD: - record = new RrdHead(); - break; - case FILE_LOCK: - record = new FileLock(); - break; - case PLS: - record = new Pls(); - break; - case HEADERREC: - record = new Headerrec(); - break; - case DATE1904: - record = new NineteenOhFour(); - break; - - // Sheet Settings - case OBJ: - record = new Obj(); - break; - case OBPROJ: - record = new Obproj(); - break; - case FOOTERREC: - record = new Footerrec(); - break; - case TABID: - record = new TabID(); - break; - case PANE: - record = new Pane(); - break; - case SCL: - record = new Scl(); - break; - - // Conditional Formatting - case CF: - record = new Cf(); - break; - case CONDFMT: - record = new Condfmt(); - break; - - // Auto filter - case AUTOFILTER: - record = new AutoFilter(); - break; - - // Chart Records - case CHART: - record = new Chart(); - break; - case SERIES: - record = new Series(); - break; - case SERIESTEXT: - record = new SeriesText(); - break; - case SERIESLIST: - record = new SeriesList(); - break; - case AI: - record = new Ai(); - break; - case UNITS: - record = new Units(); - break; - case DATAFORMAT: - record = new DataFormat(); - break; - case LINEFORMAT: - record = new LineFormat(); - break; - case MARKERFORMAT: - record = new MarkerFormat(); - break; - case AREAFORMAT: - record = new AreaFormat(); - break; - case PIEFORMAT: - record = new PieFormat(); - break; - case ATTACHEDLABEL: - record = new AttachedLabel(); - break; - case CHARTFORMAT: - record = new ChartFormat(); - break; - case LEGEND: - record = new Legend(); - break; - case BAR: - record = new Bar(); - break; - case LINE: - record = new Line(); - break; - case PIE: - record = new Pie(); - break; - case AREA: - record = new Area(); - break; - case SCATTER: - record = new Scatter(); - break; - case CHARTLINE: - record = new ChartLine(); - break; - case AXIS: - record = new Axis(); - break; - case TICK: - record = new Tick(); - break; - case VALUERANGE: - record = new ValueRange(); - break; - case CATSERRANGE: - record = new CatserRange(); - break; - case AXISLINEFORMAT: - record = new AxisLineFormat(); - break; - case CHARTFORMATLINK: - record = new ChartFormatLink(); - break; - case DEFAULTTEXT: - record = new DefaultText(); - break; - case TEXTDISP: - record = new TextDisp(); - break; - case FONTX: - record = new Fontx(); - break; - case OBJECTLINK: - record = new ObjectLink(); - break; - case FRAME: - record = new Frame(); - break; - case BEGIN: - record = new Begin(); - break; - case END: - record = new End(); - break; - case PLOTAREA: - record = new PlotArea(); - break; - case THREED: - record = new ThreeD(); - break; - case PICF: - record = new Picf(); - break; - case DROPBAR: - record = new Dropbar(); - break; - case RADAR: - record = new Radar(); - break; - case SURFACE: - record = new Surface(); - break; - case RADARAREA: - record = new RadarArea(); - break; - case AXISPARENT: - record = new AxisParent(); - break; - case LEGENDXN: - record = new Legendxn(); - break; - case SHTPROPS: - record = new ShtProps(); - break; - case SERTOCRT: - record = new SerToCrt(); - break; - case AXESUSED: - record = new Axesused(); - break; - case SBASEREF: - record = new SbaseRef(); - break; - case SERPARENT: - record = new SerParent(); - break; - case SERAUXTREND: - record = new SerauxTrend(); - break; - case IFMT: - record = new Ifmt(); - break; - case POS: - record = new Pos(); - break; - case ALRUNS: - record = new AlRuns(); - break; - case SERAUXERRBAR: - record = new SerauxErrBar(); - break; - case SERFMT: - record = new Serfmt(); - break; - case CHART3DBARSHAPE: - record = new Chart3DBarShape(); - break; - case FBI: - record = new Fbi(); - break; - case BOPPOP: - record = new Boppop(); - break; - case AXCENT: - record = new Axcent(); - break; - case DAT: - record = new Dat(); - break; - case PLOTGROWTH: - record = new PlotGrowth(); - break; - case SIIINDEX: - record = new SiIndex(); - break; - case GELFRAME: - record = new GelFrame(); - break; - case BOPPOPCUSTOM: - record = new BoppopCustom(); - break; - case FONTBASIS: - record = new FontBasis(); - break; - - // PivotTable Records - case SXVIEW: - record = new Sxview(); - break; - case TABLESTYLES: - record = new TableStyles(); - break; - case SXFORMAT: - record = new Sxformat(); - break; - case SXLI: - record = new Sxli(); - break; - case SXVI: - record = new Sxvi(); - break; - case SXVD: - record = new Sxvd(); - break; - case SXIVD: - record = new Sxivd(); - break; - case SXSTREAMID: - record = new SxStreamID(); - break; - case SXVS: - record = new SxVS(); - break; - case SXADDL: - record = new SxAddl(); - break; - case SXVDEX: - record = new SxVdEX(); - break; - case SXPI: - record = new SxPI(); - break; - case SXDI: - record = new SxDI(); - break; - case SXDB: - record = new SxDB(); - break; - case SXFDB: - record = new SxFDB(); - break; - case SXDBEX: - record = new SXDBEx(); - break; - case SXFDBTYPE: - record = new SXFDBType(); - break; - case SXSTRING: - record = new SXString(); - break; - case SXNUM: - record = new SXNum(); - break; - case SXDBB: - record = new SxDBB(); - break; - case SXEX: - record = new SxEX(); - break; - case QSISXTAG: - record = new QsiSXTag(); - break; - case SXVIEWEX9: - record = new SxVIEWEX9(); - break; - case DCONREF: - record = new DConRef(); - break; - case DCONNAME: - record = new DConName(); - break; - case DCONBIN: - record = new DConBin(); - break; - - - // Object and Picture Records - case PHONETIC: - record = new Phonetic(); - break; - case MSODRAWING: - record = new MSODrawing(); - break; - case MSODRAWINGGROUP: - record = new MSODrawingGroup(); - break; - case MSODRAWINGSELECTION: - record = new MSODrawingSelection(); - break; - - // Excel 9 Chart Records - case CHARTFRTINFO: - record = new ChartFrtInfo(); - break; - case FRTWRAPPER: - record = new FrtWrapper(); - break; - case STARTBLOCK: - record = new StartBlock(); - break; - case ENDBLOCK: - record = new EndBlock(); - break; - case STARTOBJECT: - record = new StartObject(); - break; - case ENDOBJECT: - record = new EndObject(); - break; - case CATLAB: - record = new CatLab(); - break; - case YMULT: - record = new YMult(); - break; - case SXVIEWLINK: - record = new SxViewLink(); - break; - case PIVOTCHARTBITS: - record = new PivotChartBits(); - break; - case FRTFONTLIST: - record = new FrtFontList(); - break; - case PIVOTCHARTLINK: - record = new PivotChartLink(); - break; - case DATALABEXTCONTENTS: - record = new DataLabExtContents(); - break; - case DATALABEXT: - record = new DataLabExt(); - break; - case CRTLAYOUT12A: - record = new CrtLayout12A(); - break; - case CRTLAYOUT12: - record = new CrtLayout12(); - break; - default: - record = new XLSRecord(); - } - record.setOpcode(opcode); - } catch (Exception e) { - throw new RuntimeException("failed to instantiate record", e); - } - return record; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSRecordFactory.kt b/src/main/java/io/starter/formats/XLS/XLSRecordFactory.kt new file mode 100644 index 0000000..6d1e7e6 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/XLSRecordFactory.kt @@ -0,0 +1,548 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.formats.XLS.charts.* +import io.starter.formats.XLS.formulas.Ptg + +/** + * Factory to create XLSRecords and Ptgs. + */ +class XLSRecordFactory +/** + * This class is static only, prohibit construction. + */ +private constructor() : XLSConstants { + + init { + throw UnsupportedOperationException( + "XLSRecordFactory is purely static") + } + + companion object { + + // 20060504 KSC: add separate array for unary prefix operators: + // maps regular operator to unary pseudo version located in ptgLookup below + var ptgPrefixOperators = arrayOf>(arrayOf("+", "u+"), arrayOf("-", "u-"))// should $ be in here??? + + /** + * Maps BIFF8 record opcodes to classes. + */ + // Removed static object instance creations as very difficult (impossible) to dereference so as can release memeory ... + // TRY : private static final Map records; + + // subset of ptgLookup below used for pattern matching in formula strings + var ptgOps = arrayOf>(arrayOf("#VALUE!", "PtgErr"), arrayOf("#NULL!", "PtgErr"), arrayOf("#DIV/0!", "PtgErr"), arrayOf("#VALUE!", "PtgErr"), arrayOf("#REF!", "PtgErr"), arrayOf("#NUM!", "PtgErr"), arrayOf("#N/A", "PtgErr"), + // operators + arrayOf("(", "PtgParen"), arrayOf("*", "PtgMlt"), arrayOf("/", "PtgDiv"), arrayOf("^", "PtgPower"), arrayOf("&", "PtgConcat"), arrayOf("<>", "PtgNE"), arrayOf("<=", "PtgLE"), arrayOf("<", "PtgLT"), arrayOf(">=", "PtgGE"), arrayOf(">", "PtgGT"), arrayOf("=", "PtgEQ"), arrayOf("!=", "PtgNE"), arrayOf("+", "PtgAdd"), // moved to AFTER other operators + arrayOf("-", "PtgSub")) + // this is how you init a 2D array of Objects + var ptgLookup = arrayOf>( + // 20070215 KSC: added constant strings to avoid PtgRef3d matches + arrayOf("#VALUE!", "PtgErr"), arrayOf("#NULL!", "PtgErr"), arrayOf("#DIV/0!", "PtgErr"), arrayOf("#VALUE!", "PtgErr"), arrayOf("#REF!", "PtgErr"), arrayOf("#NUM!", "PtgErr"), arrayOf("#N/A", "PtgErr"), + // operators + arrayOf("(", "PtgParen"), arrayOf("*", "PtgMlt"), arrayOf("/", "PtgDiv"), arrayOf("^", "PtgPower"), arrayOf("&", "PtgConcat"), arrayOf("<>", "PtgNE"), arrayOf("<=", "PtgLE"), arrayOf("<", "PtgLT"), arrayOf(">=", "PtgGE"), arrayOf(">", "PtgGT"), arrayOf("=", "PtgEQ"), arrayOf("!=", "PtgNE"), + //{" ","PtgIsect"}, intersection operator, need to work out how to use a space as operator-- SEE PTGMEMFUNC/MEMAREA -- only valid when parsing complex ranges + //{",","PtgUnion"}, problems matching as a separator in string parsing + //{":","PtgRange"}, // may have issues with ptgArea? + arrayOf("+", "PtgAdd"), // moved to AFTER other operators + arrayOf("-", "PtgSub"), + //operands + arrayOf("PtgStr", "PtgStr"), arrayOf("PtgNumber", "PtgNumber"), arrayOf("PtgInt", "PtgInt"), arrayOf("PtgRef", "PtgRef"), arrayOf("PtgArea", "PtgArea"), arrayOf("false", "PtgBool"), arrayOf("true", "PtgBool"), arrayOf("PtgArea3d", "PtgArea3d"), arrayOf("PtgRef3d", "PtgRef3d"), arrayOf("PtgArray", "PtgArray"), arrayOf("PtgMissArg", "PtgMissArg"), arrayOf("PtgMemFunc", "PtgMemFunc"), arrayOf("PtgAtr", "PtgAtr"), + //functions + arrayOf("PtgFunc", "PtgFunc"), arrayOf("PtgFuncVar", "PtgFuncVar"), + // unary prefix operators: see FormulaParser.splitString + arrayOf("u+", "PtgUPlus"), arrayOf("u-", "PtgUMinus"), arrayOf(")", "PtgParen")) + + /* DO DIFFERENTLY SO TO AVOID HANGING OBJECT REFERENCES + static { + HashMap recmap = new HashMap(); + + // Most Frequent + case BLANK), new Blank() ); + case ROW), new Row() ); + case XF), new Xf() ); + case INDEX), new Index() ); + case COUNTRY), new Country() ); + case CALCMODE), new CalcMode() ); + case DIMENSIONS), new Dimensions() ); + case SELECTION), new Selection() ); + case DEFAULTROWHEIGHT), new DefaultRowHeight() ); + case DEFCOLWIDTH), new DefColWidth() ); + case DBCELL), new Dbcell() ); + case BOF), new Bof() ); + case BOUNDSHEET), new Boundsheet() ); + case EOF), new Eof() ); + case FORMAT), new Format() ); + case STYLE), new Style() ); + case PASSWORD), new Password() ); + case PALETTE), new Palette() ); + case ARRAY), new Array() ); + case BOOLERR), new Boolerr() ); + case EXTERNSHEET), new Externsheet() ); + case EXTERNNAME), new Externname() ); + case FORMULA), new Formula() ); + case LABEL), new Label() ); + case TXO), new Txo() ); + case CONTINUE), new Continue() ); + case SST), new Sst() ); + case GUTS), new Guts() ); + case EXTSST), new Extsst() ); + case HLINK), new Hlink() ); + case LABELSST), new Labelsst() ); + case NUMBER), new NumberRec() ); + case MERGEDCELLS), new Mergedcells() ); + case MULBLANK), new Mulblank() ); + case MULRK), new Mulrk() ); + case RK), new Rk() ); + case RSTRING), new Rstring() ); + case SHRFMLA), new Shrfmla() ); + case STRINGREC), new StringRec() ); + case SUPBOOK), new Supbook() ); + case DV), new Dv() ); + case DVAL), new Dval() ); + case SETUP), new Setup() ); + case HCENTER), new HCenter() ); + case VCENTER), new VCenter() ); + case LEFTMARGIN), new LeftMargin() ); + case RIGHTMARGIN), new RightMargin() ); + case TOPMARGIN), new TopMargin() ); + case BOTTOMMARGIN), new BottomMargin() ); + case PRINTGRID), new PrintGrid() ); + case PRINTROWCOL), new PrintRowCol() ); + case XCT), new Xct() ); + case CRN), new Crn() ); + case NOTE), new Note() ); + + // Named Ranges and References + case NAME), new Name() ); + + // Workbook Settings + case FONT), new Font() ); + case DSF), new Dsf() ); + case WINDOW1), new Window1() ); + case WINDOW2), new Window2() ); + case CODENAME), new Codename() ); + case PROTECT), new Protect() ); + case OBJPROTECT), new ObjProtect() ); + case SCENPROTECT), new ScenProtect() ); + case FEATHEADR), new FeatHeadr() ); + case PROT4REV), new Prot4rev() ); + case COLINFO), new Colinfo() ); + case USERSVIEWBEGIN), new Usersviewbegin() ); + case USERSVIEWEND), new Usersviewend() ); + case WSBOOL), new WsBool() ); + case BOOKBOOL), new BookBool() ); + case USR_EXCL), new UsrExcl() ); + case INTERFACE_HDR), new InterfaceHdr() ); + case RRD_INFO), new RrdInfo() ); + case RRD_HEAD), new RrdHead() ); + case FILE_LOCK), new FileLock() ); + case PLS), new Pls() ); + case HEADERREC), new Headerrec() ); + case DATE1904), new NineteenOhFour() ); + + // Sheet Settings + case OBJ), new Obj() ); + case OBPROJ), new Obproj() ); + case FOOTERREC), new Footerrec() ); + case TABID), new TabID() ); + case PANE), new Pane() ); + case SCL), new Scl() ); + + // Protection settings + case FILEPASS), new Filepass() ); + + // Conditional Formatting + case CF), new Cf() ); + case CONDFMT), new Condfmt() ); + + // Auto filter + case AUTOFILTER), new AutoFilter() ); + + // Chart Records + case CHART), new Chart() ); + case SERIES), new Series() ); + case SERIESTEXT), new SeriesText() ); + case SERIESLIST), new SeriesList() ); + case AI), new Ai() ); + case BEGIN), new Begin() ); + case END), new End() ); + case UNITS), new Units() ); + case CHART), new Chart() ); + case DATAFORMAT), new DataFormat() ); + case LINEFORMAT), new LineFormat() ); + case MARKERFORMAT), new MarkerFormat() ); + case AREAFORMAT), new AreaFormat() ); + case PIEFORMAT), new PieFormat() ); + case ATTACHEDLABEL), new AttachedLabel() ); + case CHARTFORMAT), new ChartFormat() ); + case LEGEND), new Legend() ); + case BAR), new Bar() ); + case LINE), new Line() ); + case PIE), new Pie() ); + case AREA), new Area() ); + case SCATTER), new Scatter() ); + case CHARTLINE), new ChartLine() ); + case AXIS), new Axis() ); + case TICK), new Tick() ); + case VALUERANGE), new ValueRange() ); + case CATSERRANGE), new CatserRange() ); + case AXISLINEFORMAT), new AxisLineFormat() ); + case CHARTFORMATLINK), new ChartFormatLink() ); + case DEFAULTTEXT), new DefaultText() ); + case TEXTDISP), new TextDisp() ); + case FONTX), new Fontx() ); + case OBJECTLINK), new ObjectLink() ); + case FRAME), new Frame() ); + case BEGIN), new Begin() ); + case END), new End() ); + case PLOTAREA), new PlotArea() ); + case THREED), new ThreeD() ); + case PICF), new Picf() ); + case DROPBAR), new Dropbar() ); + case RADAR), new Radar() ); + case SURFACE), new Surface() ); + case RADARAREA), new RadarArea() ); + case AXISPARENT), new AxisParent() ); + case LEGENDXN), new Legendxn() ); + case SHTPROPS), new ShtProps() ); + case SERTOCRT), new SerToCrt() ); + case AXESUSED), new Axesused() ); + case SBASEREF), new SbaseRef() ); + case SERPARENT), new SerParent() ); + case SERAUXTREND), new SerauxTrend() ); + case IFMT), new Ifmt() ); + case POS), new Pos() ); + case ALRUNS), new AlRuns() ); + case AI), new Ai() ); + case SERAUXERRBAR), new SerauxErrBar() ); + case SERFMT), new Serfmt() ); + case CHART3DBARSHAPE), new Chart3DBarShape() ); + case FBI), new Fbi() ); + case BOPPOP), new Boppop() ); + case AXCENT), new Axcent() ); + case DAT), new Dat() ); + case PLOTGROWTH), new PlotGrowth() ); + case SIIINDEX), new SiIndex() ); + case GELFRAME), new GelFrame() ); + case BOPPOPCUSTOM), new BoppopCustom() ); + case FONTBASIS), new FontBasis() ); + + // PivotTable Records + case SXVIEW), new Sxview() ); + case SXFORMAT), new Sxformat() ); + case SXLI), new Sxli() ); + case SXVI), new Sxvi() ); + case SXVD), new Sxvd() ); + case SXIVD), new Sxivd() ); + + // Object and Picture Records + case PHONETIC), new Phonetic() ); + case MSODRAWING), new MSODrawing() ); + case MSODRAWINGGROUP), new MSODrawingGroup() ); + case MSODRAWINGSELECTION), new MSODrawingSelection() ); + + // Excel 9 Chart Records + case CHARTFRTINFO), new ChartFrtInfo() ); + case FRTWRAPPER), new FrtWrapper() ); + case STARTBLOCK), new StartBlock() ); + case ENDBLOCK), new EndBlock() ); + case STARTOBJECT), new StartObject() ); + case ENDOBJECT), new EndObject() ); + case CATLAB), new CatLab() ); + case YMULT), new YMult() ); + case SXVIEWLINK), new SxViewLink() ); + case PIVOTCHARTBITS), new PivotChartBits() ); + case FRTFONTLIST), new FrtFontList() ); + case PIVOTCHARTLINK), new PivotChartLink() ); + case DATALABEXTCONTENTS), new DataLabExtContents() ); + case DATALABEXT), new DataLabExt() ); + + records = Collections.unmodifiableMap( recmap ); + } + */ + /* + * Create a ptg record from a name, will be init'ed elsewhere if needed. + * I am keeping this seperate from getBiffRecord for performance reasons. Why search + * through all the ptg/formula stuff every time you deal with a XLS record an vice-versa. Also, + * init'ing may be different. Small duplication of code, but I think it is worth it. + */ + @Throws(InvalidRecordException::class) + fun getPtgRecord(name: String): Ptg? { + for (t in ptgLookup.indices) { + if (ptgLookup[t][0].equals(name, ignoreCase = true)) { + try { + val classname = ptgLookup[t][1] + return Class.forName("io.starter.formats.XLS.formulas.$classname").newInstance() as Ptg + } catch (e: Exception) { + throw InvalidRecordException("ERROR: Creating Record: " + name + "failed: " + e.toString()) + } + + } + } + return null + } + + + /** + * Get an instance of the record type corresponding to the given opcode. + * + * @param opcode the BIFF8 record opcode to be resolved + * @return an instance of the class corresponding to the given opcode + * or an XLSRecord if the opcode is unknown + * @throws RuntimeException if instantiation of the record fails + */ + fun getBiffRecord(opcode: Short): BiffRec { + // TRY THIS: + var record: BiffRec? = null + try { + when (opcode) { + XLSConstants.BLANK -> record = Blank() + XLSConstants.ROW -> record = Row() + XLSConstants.XF -> record = Xf() + XLSConstants.INDEX -> record = Index() + XLSConstants.COUNTRY -> record = Country() + XLSConstants.CALCMODE -> record = CalcMode() + XLSConstants.DIMENSIONS -> record = Dimensions() + XLSConstants.SELECTION -> record = Selection() + XLSConstants.DEFAULTROWHEIGHT -> record = DefaultRowHeight() + XLSConstants.DEFCOLWIDTH -> record = DefColWidth() + XLSConstants.DBCELL -> record = Dbcell() + XLSConstants.BOF -> record = Bof() + XLSConstants.BOUNDSHEET -> record = Boundsheet() + XLSConstants.EOF -> record = Eof() + XLSConstants.FORMAT -> record = Format() + XLSConstants.STYLE -> record = Style() + XLSConstants.PASSWORD -> record = Password() + XLSConstants.PALETTE -> record = Palette() + XLSConstants.ARRAY -> record = Array() + XLSConstants.BOOLERR -> record = Boolerr() + XLSConstants.EXTERNSHEET -> record = Externsheet() + XLSConstants.EXTERNNAME -> record = Externname() + XLSConstants.FORMULA -> record = Formula() + XLSConstants.LABEL -> record = Label() + XLSConstants.TXO -> record = Txo() + XLSConstants.CONTINUE -> record = Continue() + XLSConstants.SST -> record = Sst() + XLSConstants.GUTS -> record = Guts() + XLSConstants.EXTSST -> record = Extsst() + XLSConstants.HLINK -> record = Hlink() + XLSConstants.LABELSST -> record = Labelsst() + XLSConstants.NUMBER -> record = NumberRec() + XLSConstants.MERGEDCELLS -> record = Mergedcells() + XLSConstants.MULBLANK -> record = Mulblank() + XLSConstants.MULRK -> record = Mulrk() + XLSConstants.RK -> record = Rk() + XLSConstants.RSTRING -> record = Rstring() + XLSConstants.SHRFMLA -> record = Shrfmla() + XLSConstants.STRINGREC -> record = StringRec() + XLSConstants.SUPBOOK -> record = Supbook() + XLSConstants.DV -> record = Dv() + XLSConstants.DVAL -> record = Dval() + XLSConstants.SETUP -> record = Setup() + XLSConstants.HCENTER -> record = HCenter() + XLSConstants.VCENTER -> record = VCenter() + XLSConstants.LEFTMARGIN -> record = LeftMargin() + XLSConstants.RIGHTMARGIN -> record = RightMargin() + XLSConstants.TOPMARGIN -> record = TopMargin() + XLSConstants.BOTTOMMARGIN -> record = BottomMargin() + XLSConstants.PRINTGRID -> record = PrintGrid() + XLSConstants.PRINTROWCOL -> record = PrintRowCol() + XLSConstants.XCT -> record = Xct() + XLSConstants.CRN -> record = Crn() + XLSConstants.NOTE -> record = Note() + + // Named Ranges and References + XLSConstants.NAME -> record = Name() + + // Workbook Settings + XLSConstants.FONT -> record = Font() + XLSConstants.DSF -> record = Dsf() + XLSConstants.WINDOW1 -> record = Window1() + XLSConstants.WINDOW2 -> record = Window2() + XLSConstants.PLV -> record = PLV() + XLSConstants.CODENAME -> record = Codename() + XLSConstants.PROTECT -> record = Protect() + XLSConstants.OBJPROTECT -> record = ObjProtect() + XLSConstants.SCENPROTECT -> record = ScenProtect() + XLSConstants.FEATHEADR -> record = FeatHeadr() + XLSConstants.PROT4REV -> record = Prot4rev() + XLSConstants.COLINFO -> record = Colinfo() + XLSConstants.USERSVIEWBEGIN -> record = Usersviewbegin() + XLSConstants.USERSVIEWEND -> record = Usersviewend() + XLSConstants.WSBOOL -> record = WsBool() + XLSConstants.BOOKBOOL -> record = BookBool() + XLSConstants.USR_EXCL -> record = UsrExcl() + XLSConstants.INTERFACE_HDR -> record = InterfaceHdr() + XLSConstants.RRD_INFO -> record = RrdInfo() + XLSConstants.RRD_HEAD -> record = RrdHead() + XLSConstants.FILE_LOCK -> record = FileLock() + XLSConstants.PLS -> record = Pls() + XLSConstants.HEADERREC -> record = Headerrec() + XLSConstants.DATE1904 -> record = NineteenOhFour() + + // Sheet Settings + XLSConstants.OBJ -> record = Obj() + XLSConstants.OBPROJ -> record = Obproj() + XLSConstants.FOOTERREC -> record = Footerrec() + XLSConstants.TABID -> record = TabID() + XLSConstants.PANE -> record = Pane() + XLSConstants.SCL -> record = Scl() + + // Conditional Formatting + XLSConstants.CF -> record = Cf() + XLSConstants.CONDFMT -> record = Condfmt() + + // Auto filter + XLSConstants.AUTOFILTER -> record = AutoFilter() + + // Chart Records + XLSConstants.CHART -> record = Chart() + XLSConstants.SERIES -> record = Series() + XLSConstants.SERIESTEXT -> record = SeriesText() + XLSConstants.SERIESLIST -> record = SeriesList() + XLSConstants.AI -> record = Ai() + XLSConstants.UNITS -> record = Units() + XLSConstants.DATAFORMAT -> record = DataFormat() + XLSConstants.LINEFORMAT -> record = LineFormat() + XLSConstants.MARKERFORMAT -> record = MarkerFormat() + XLSConstants.AREAFORMAT -> record = AreaFormat() + XLSConstants.PIEFORMAT -> record = PieFormat() + XLSConstants.ATTACHEDLABEL -> record = AttachedLabel() + XLSConstants.CHARTFORMAT -> record = ChartFormat() + XLSConstants.LEGEND -> record = Legend() + XLSConstants.BAR -> record = Bar() + XLSConstants.LINE -> record = Line() + XLSConstants.PIE -> record = Pie() + XLSConstants.AREA -> record = Area() + XLSConstants.SCATTER -> record = Scatter() + XLSConstants.CHARTLINE -> record = ChartLine() + XLSConstants.AXIS -> record = Axis() + XLSConstants.TICK -> record = Tick() + XLSConstants.VALUERANGE -> record = ValueRange() + XLSConstants.CATSERRANGE -> record = CatserRange() + XLSConstants.AXISLINEFORMAT -> record = AxisLineFormat() + XLSConstants.CHARTFORMATLINK -> record = ChartFormatLink() + XLSConstants.DEFAULTTEXT -> record = DefaultText() + XLSConstants.TEXTDISP -> record = TextDisp() + XLSConstants.FONTX -> record = Fontx() + XLSConstants.OBJECTLINK -> record = ObjectLink() + XLSConstants.FRAME -> record = Frame() + XLSConstants.BEGIN -> record = Begin() + XLSConstants.END -> record = End() + XLSConstants.PLOTAREA -> record = PlotArea() + XLSConstants.THREED -> record = ThreeD() + XLSConstants.PICF -> record = Picf() + XLSConstants.DROPBAR -> record = Dropbar() + XLSConstants.RADAR -> record = Radar() + XLSConstants.SURFACE -> record = Surface() + XLSConstants.RADARAREA -> record = RadarArea() + XLSConstants.AXISPARENT -> record = AxisParent() + XLSConstants.LEGENDXN -> record = Legendxn() + XLSConstants.SHTPROPS -> record = ShtProps() + XLSConstants.SERTOCRT -> record = SerToCrt() + XLSConstants.AXESUSED -> record = Axesused() + XLSConstants.SBASEREF -> record = SbaseRef() + XLSConstants.SERPARENT -> record = SerParent() + XLSConstants.SERAUXTREND -> record = SerauxTrend() + XLSConstants.IFMT -> record = Ifmt() + XLSConstants.POS -> record = Pos() + XLSConstants.ALRUNS -> record = AlRuns() + XLSConstants.SERAUXERRBAR -> record = SerauxErrBar() + XLSConstants.SERFMT -> record = Serfmt() + XLSConstants.CHART3DBARSHAPE -> record = Chart3DBarShape() + XLSConstants.FBI -> record = Fbi() + XLSConstants.BOPPOP -> record = Boppop() + XLSConstants.AXCENT -> record = Axcent() + XLSConstants.DAT -> record = Dat() + XLSConstants.PLOTGROWTH -> record = PlotGrowth() + XLSConstants.SIIINDEX -> record = SiIndex() + XLSConstants.GELFRAME -> record = GelFrame() + XLSConstants.BOPPOPCUSTOM -> record = BoppopCustom() + XLSConstants.FONTBASIS -> record = FontBasis() + + // PivotTable Records + XLSConstants.SXVIEW -> record = Sxview() + XLSConstants.TABLESTYLES -> record = TableStyles() + XLSConstants.SXFORMAT -> record = Sxformat() + XLSConstants.SXLI -> record = Sxli() + XLSConstants.SXVI -> record = Sxvi() + XLSConstants.SXVD -> record = Sxvd() + XLSConstants.SXIVD -> record = Sxivd() + XLSConstants.SXSTREAMID -> record = SxStreamID() + XLSConstants.SXVS -> record = SxVS() + XLSConstants.SXADDL -> record = SxAddl() + XLSConstants.SXVDEX -> record = SxVdEX() + XLSConstants.SXPI -> record = SxPI() + XLSConstants.SXDI -> record = SxDI() + XLSConstants.SXDB -> record = SxDB() + XLSConstants.SXFDB -> record = SxFDB() + XLSConstants.SXDBEX -> record = SXDBEx() + XLSConstants.SXFDBTYPE -> record = SXFDBType() + XLSConstants.SXSTRING -> record = SXString() + XLSConstants.SXNUM -> record = SXNum() + XLSConstants.SXDBB -> record = SxDBB() + XLSConstants.SXEX -> record = SxEX() + XLSConstants.QSISXTAG -> record = QsiSXTag() + XLSConstants.SXVIEWEX9 -> record = SxVIEWEX9() + XLSConstants.DCONREF -> record = DConRef() + XLSConstants.DCONNAME -> record = DConName() + XLSConstants.DCONBIN -> record = DConBin() + + + // Object and Picture Records + XLSConstants.PHONETIC -> record = Phonetic() + XLSConstants.MSODRAWING -> record = MSODrawing() + XLSConstants.MSODRAWINGGROUP -> record = MSODrawingGroup() + XLSConstants.MSODRAWINGSELECTION -> record = MSODrawingSelection() + + // Excel 9 Chart Records + XLSConstants.CHARTFRTINFO -> record = ChartFrtInfo() + XLSConstants.FRTWRAPPER -> record = FrtWrapper() + XLSConstants.STARTBLOCK -> record = StartBlock() + XLSConstants.ENDBLOCK -> record = EndBlock() + XLSConstants.STARTOBJECT -> record = StartObject() + XLSConstants.ENDOBJECT -> record = EndObject() + XLSConstants.CATLAB -> record = CatLab() + XLSConstants.YMULT -> record = YMult() + XLSConstants.SXVIEWLINK -> record = SxViewLink() + XLSConstants.PIVOTCHARTBITS -> record = PivotChartBits() + XLSConstants.FRTFONTLIST -> record = FrtFontList() + XLSConstants.PIVOTCHARTLINK -> record = PivotChartLink() + XLSConstants.DATALABEXTCONTENTS -> record = DataLabExtContents() + XLSConstants.DATALABEXT -> record = DataLabExt() + XLSConstants.CRTLAYOUT12A -> record = CrtLayout12A() + XLSConstants.CRTLAYOUT12 -> record = CrtLayout12() + else -> record = XLSRecord() + } + record.opcode = opcode + } catch (e: Exception) { + throw RuntimeException("failed to instantiate record", e) + } + + return record + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/XLSSubstream.java b/src/main/java/io/starter/formats/XLS/XLSSubstream.kt similarity index 69% rename from src/main/java/io/starter/formats/XLS/XLSSubstream.java rename to src/main/java/io/starter/formats/XLS/XLSSubstream.kt index 2e0e803..0074267 100644 --- a/src/main/java/io/starter/formats/XLS/XLSSubstream.java +++ b/src/main/java/io/starter/formats/XLS/XLSSubstream.kt @@ -20,7 +20,7 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.formats.XLS /** @@ -29,23 +29,27 @@ * class. * * @see WorkBook + * * @see WorkSheet */ -public interface XLSSubstream { - String getName(); +interface XLSSubstream { + val name: String + + val substreamTypeName: String - String getSubstreamTypeName(); + val substreamType: Short - short getSubstreamType(); + fun stream(): ByteArray - byte[] stream(); + companion object { - short WK_GLOBALS = 0x5; - short VB_MODULE = 0x6; - short WK_WORKSHEET = 0x10; - short WK_CHART = 0x20; - short WK_MACROSHEET = 0x40; - short WK_FILE = 0x100; + val WK_GLOBALS: Short = 0x5 + val VB_MODULE: Short = 0x6 + val WK_WORKSHEET: Short = 0x10 + val WK_CHART: Short = 0x20 + val WK_MACROSHEET: Short = 0x40 + val WK_FILE: Short = 0x100 + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/Xct.java b/src/main/java/io/starter/formats/XLS/Xct.java deleted file mode 100644 index 16ceb9f..0000000 --- a/src/main/java/io/starter/formats/XLS/Xct.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -/** - * - */ -package io.starter.formats.XLS; - -import io.starter.toolkit.ByteTools; - -/** - * XCT CRN Count (0059h)
                      - * -

                      - * 	This record stores the number of immediately following Crn records. 
                      - * 	These records are used to store the cell contents of external references.
                      - *
                      - offset  size 	contents
                      - ---
                      - 0 		2 		Number of following CRN records
                      - 2 		2 		Index into sheet table of the involved SUPBOOK record
                      - 

                      - * - */ - -public class Xct extends XLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5112701341711255711L; - private int nCRNs; // number of External Cell References CRN record, similar to EXTERNNAME - private int supBookIndex; - - public void init() { - super.init(); - nCRNs = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - supBookIndex = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - } - - public String toString() { - return "XTC: nCRNS=" + nCRNs + " SupBook Index: " + supBookIndex; - } -} diff --git a/src/main/java/io/starter/formats/XLS/Xct.kt b/src/main/java/io/starter/formats/XLS/Xct.kt new file mode 100644 index 0000000..2c91638 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Xct.kt @@ -0,0 +1,66 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +/** + * + */ +package io.starter.formats.XLS + +import io.starter.toolkit.ByteTools + +/** + * **XCT CRN Count (0059h)**

                      + * + * + *
                      + * This record stores the number of immediately following Crn records.
                      + * These records are used to store the cell contents of external references.
                      + *
                      + * offset  size 	contents
                      + * ---
                      + * 0 		2 		Number of following CRN records
                      + * 2 		2 		Index into sheet table of the involved SUPBOOK record
                      +
                      * + * + */ + +class Xct : XLSRecord() { + private var nCRNs: Int = 0 // number of External Cell References CRN record, similar to EXTERNNAME + private var supBookIndex: Int = 0 + + override fun init() { + super.init() + nCRNs = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() + supBookIndex = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()).toInt() + } + + override fun toString(): String { + return "XTC: nCRNS=$nCRNs SupBook Index: $supBookIndex" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -5112701341711255711L + } +} diff --git a/src/main/java/io/starter/formats/XLS/Xf.java b/src/main/java/io/starter/formats/XLS/Xf.java deleted file mode 100644 index d57ffb2..0000000 --- a/src/main/java/io/starter/formats/XLS/Xf.java +++ /dev/null @@ -1,1494 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.formats.OOXML.Fill; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.awt.Color; - - -/** - * XF: Extended Format (E0h)
                      - * The XF record stores formatting properties. - *

                      - * If fStyle bit is true, then the XF is a style XF, otherwise - * it is a BiffRec XF. Cells and Styles both contain ixfe pointers - * which correspond to their associated XF record. - * - *

                      - * BiffRec XF Record
                      - *
                      - * offset  Bits   MASK     name        contents
                      - * ---
                      - * 0       15-0   0xFFFF   ifnt        Index to the FONT record.
                      - * 2       15-0   0xFFFF   ifmt        Index to the FORMAT record.
                      - * 4       0      0x0001   fLocked     =1 if the cell is locked.
                      - * 1      0x0002   fHidden     =1 if the cell formula is hidden (value still shown)
                      - * 2      0x0004   fStyle      =0 for cell XF.
                      - * =1 for style XF.
                      - *
                      - * ~~~ additional option flags omitted ~~~
                      - *
                      - * 
                      - * - * @see SST - * @see LABELSST - * @see EXTSST - */ - - -public class Xf extends io.starter.formats.XLS.XLSRecord { - - private static final long serialVersionUID = -419388613530529316L; - - int tableidx = -1; - // Shared variables. - private short ifnt; - private short ifmt; - private short fLocked; - private short fHidden; - private short fStyle; - // the records that are initialized to "0" are a 1/0 flag. Just makin my life easier... - private short f123Prefix = 0; - private short ixfParent; - private short alc; - private short fWrap = 0; - private short alcV; - // private short fJustLast; Used in Far East version of Excel only!! - private short cIndent; - private short trot; - // private short cIntednt; - private short fShrinkToFit = 0; - private short fMergeCell = 0; - private short iReadingOrder; - private short fAtrNum = 0; - private short fAtrFnt = 0; - private short fAtrAlc = 0; - private short fAtrBdr = 0; - private short fAtrPat = 0; - private short fAtrProt = 0; - private short dgLeft; - private short dgRight; - private short dgTop; - private short dgBottom; - private short icvLeft; - private short icvRight; - private short grbitDiag; - private short icvTop; - private short icvBottom; - private short icvDiag; - private short dgDiag; - private short fls; - private short icvFore; - private short icvBack; - private short fSxButton = 0; - private short icvColorFlag = 0; - int Iflag = 0; - byte mystery; - public final static int NDEFAULTXFS = 20; - private String pat = null; - /** - * OOXML fill, if any - */ - public Fill fill = null; // ugly that it's public ... - // These should only be populated for boundsheet transferral issues. - private Font myFont; - private Format myFormat; - private short useCount = 0; // KSC: added 20121003 to keep track of xf usage by biffrecs - - public Xf() { - //empty constructor - } - - /** - * create a new Xf with pointer to its font - */ - public Xf(int f) { - byte[] bl = {0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32}; - this.setOpcode(XF); - this.setLength((short) (bl.length)); - this.setData(bl); - this.setFont(f); - this.init(); - } - - /** - * create a new Xf with pointer to its font and workbook set - */ - public Xf(int f, WorkBook wkbook) { - byte[] bl = {0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32}; - this.setOpcode(XF); - this.setLength((short) (bl.length)); - this.setData(bl); - super.setWorkBook(wkbook); // set workbook but don't insert rec or add to xfrecs - this.setFont(f); - this.init(); - } - - /** - * constructor which takes a Font object + a workbook - * useful for cloning xf's from other workbooks - * - * @param f font - * @param wkbook - */ - public Xf(Font f, WorkBook wkbook) { - byte[] bl = {0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32}; - this.setOpcode(XF); - this.setLength((short) (bl.length)); - myFont = f; - System.arraycopy(ByteTools.shortToLEBytes((short) f.getIdx()), 0, bl, 0, 2); - this.setData(bl); - super.setWorkBook(wkbook); // set workbook but don't insert rec or add to xfrecs - this.init(); - } - - /** - * Set the workbook for this XF - *

                      - * This can get called multiple times. This results in a disparity within - * xf counting in workbook. - */ - public void setWorkBook(WorkBook b) { - super.setWorkBook(b); - } - - /** - * Create a string representation of the Xf - */ - public String toString() { - String f = "unknown"; //Handle missing formats - try { - f = this.getFormatPattern(); - } catch (Exception e) { - } - String thisToString = - " format:" + f + " fill:" + this.getFillPattern() + - " fg:" + this.getForegroundColor() + - " bg:" + this.getBackgroundColor() + - " border:[" + - this.getTopBorderLineStyle() + "-" + this.getTopBorderColor() + ":" + - this.getLeftBorderLineStyle() + "-" + this.getLeftBorderColor() + ":" + - this.getBottomBorderLineStyle() + "-" + this.getBottomBorderColor() + ":" + - this.getRightBorderLineStyle() + "-" + this.getRightBorderColor() + "]" + - "W:" + this.getWrapText() + - "R:" + this.getRotation() + - "H:" + this.getHorizontalAlignment() + "V:" + this.getVerticalAlignment() + - "I:" + this.getIndent() + - "L:" + this.isLocked() + - "F:" + this.isFormulaHidden() + - "D:" + this.getRightToLeftReadingOrder(); - return this.getFont().toString() + thisToString; - } - - /** - * inc # records using this xf - */ - public void incUseCount() { - useCount++; - } - - /** - * dec # records using this xf - */ - public void decUseCoount() { - useCount--; - } - - /** - * return # records using this xf - * - * @return - */ - public short getUseCount() { - return useCount; - } - - /** - * Populates the myFont and myFormat variables to be held onto - * when the xf record is serialized for boundsheet transfer - */ - protected void populateForTransfer() { - myFont = this.getFont(); - myFormat = this.getWorkBook().getFormat(ifmt); - this.getData(); - } - - - public boolean getMerged() { - return fMergeCell == 1; - } - - - // marginal! - public void setMerged(boolean mgd) { - byte[] rkdata = this.getData(); - rkdata[9] = (byte) 0x78; // 0xf4 ? - if (DEBUGLEVEL > 1) Logger.logInfo("Xf The merge style bit is: " + fMergeCell); - - } - - /** - * The XF record can either be a style XF or a Cell XF. - */ - public void init() { - super.init(); - ifnt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - ifnt = (short) (ifnt & 0xffff); - ifmt = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - ifmt = (short) (ifmt & 0xffff); - - - short flag = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - // is the cell locked? - if ((flag & 0x1) == 0x1) { - fLocked = 0x1; - } else { - fLocked = 0; - } - // is the cell hidden? - if ((flag & 0x2) == 0x2) { - fHidden = 0x1; - } else { - fHidden = 0; - } - - // is it a cell rec or a style rec? - if ((flag & 0x4) == 0x4) { - fStyle = 1; - } else { - fStyle = 0; - } - if ((flag & 0x8) == 0x0008) - f123Prefix = 0x1; - ixfParent = (short) ((flag & 0xFFF0) >> 4); - - - initXF(); - - pat = null; // ensure reset if xf has changed - if (DEBUGLEVEL > DEBUG_LOW) Logger.logInfo("Xf.init() ifnt: " + ifnt - + " ifmt: " + ifmt + ":" + - this.toString() - + " border: " - + "l:" + this.getLeftBorderColor() + ":" - + "b:" + this.getBottomBorderColor() + ":" - + "r:" + this.getRightBorderColor() + ":" - + "t:" + this.getTopBorderColor() + ":"); - } - - /** - * read and interpret bytes 6-18) - */ - void initXF() { - short flag; - - // bytes 6, 7: alignment, rotation, text break - flag = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - alc = (short) (flag & 0x7); - if ((flag & 0x8) == 0x8) fWrap = 1; - alcV = (short) ((flag & 0x70) >> 4); - trot = (short) ((flag & 0xFF00) >> 8); - - // byte 8: indent, reading order, shrink - flag = this.getByteAt(8); - cIndent = (short) (flag & 0xF); - if ((flag & 0x10) == 0x10) fShrinkToFit = 1; - if ((flag & 0x20) == 0x20) fMergeCell = 1; - if (DEBUGLEVEL > 5) Logger.logInfo("Xf The merge cell bit is: " + fMergeCell + " and the int is " + flag); - - iReadingOrder = (short) ((flag & 0xC0));// >> 6); // reading order is byte 7-6 mask 0xCO - // USED_ATTRIB: bits 7-2 of byte 9 - flag = this.getByteAt(9); - /* for all these flags, a cleared bit means use Parent Style XF attribute - if set, means the attributes of THIS xf is used - bit mask meaning - 0 01H Flag for number format - 1 02H Flag for font - 2 04H Flag for horizontal and vertical alignment, text wrap, indentation, orientation, rotation, and - text direction - 3 08H Flag for border lines - 4 10H Flag for background area style - 5 20H Flag for cell protection (cell locked and formula hidden) - */ - if ((flag & 0x4) == 0x4) fAtrNum = 1; // number format - if ((flag & 0x8) == 0x8) fAtrFnt = 1; // font - if ((flag & 0x10) == 0x10) fAtrAlc = 1; // alignment (h + v) text wrap rotation direction indent - if ((flag & 0x20) == 0x20) fAtrBdr = 1; // border lines - if ((flag & 0x40) == 0x40) fAtrPat = 1; // background format pattern - if ((flag & 0x80) == 0x80) fAtrProt = 1; // cell protection - - // BORDER Section - flag = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - dgLeft = (short) (flag & 0xF); - dgRight = (short) ((flag & 0xF0) >> 4); - dgTop = (short) ((flag & 0xF00) >> 8); - dgBottom = (short) ((flag & 0xF000) >> 12); - - flag = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - icvLeft = (short) (flag & 0x7f); - icvRight = (short) ((flag & 0x3F80) >> 7); - grbitDiag = (short) ((flag & 0xC000) >> 15); - - // bytes 14-17 color and fill - Iflag = ByteTools.readInt(this.getByteAt(14), this.getByteAt(15), this.getByteAt(16), this.getByteAt(17)); - icvTop = (short) (Iflag & 0x7F); - icvBottom = (short) ((Iflag & 0x3F80) >> 7); - icvDiag = (short) ((Iflag & 0x1FC000) >> 14); - dgDiag = (short) ((Iflag & 0x1E00000) >> 21); - mystery = (byte) ((Iflag & 0x3800000) >> 25); - fls = (short) ((Iflag & 0xFC000000) >> 26); // fill pattern - - if (DEBUGLEVEL > 5 && icvTop > 0) Logger.logInfo("Xf The cell outline is true"); - // bytes 18, 19: fill pattern colors - icvColorFlag = ByteTools.readShort(this.getByteAt(18), this.getByteAt(19)); - icvFore = (short) (icvColorFlag & 0x7F); // = Pattern Color - icvBack = (short) ((icvColorFlag & 0x3F80) >> 7); // = Pattern Background Color - if ((icvColorFlag & 0x4000) == 0x4000) fSxButton = 1; - - // Logger.logInfo(io.starter.OpenXLS.ExcelTools.getRecordByteDef(this)); - } - - /** - * returns the associated Font record for this XF - */ - public Font getFont() { - if (myFont != null) return myFont; - myFont = this.getWorkBook().getFont(ifnt); - return myFont; - } - - /** - * returns whether this Format is a Date - *

                      - * Needs to be revisited. Currently I am only returning true for the standard "built in" dates - */ - public boolean isDatePattern() { - - // Check the format ID against all known date formats. Why do we do - // this instead of letting it be caught by the string matching below? - for (int x = 0; x < FormatConstants.DATE_FORMATS.length; x++) { - short sxt = (short) Integer.parseInt(FormatConstants.DATE_FORMATS[x][1], 16); - if (ifmt == sxt) - return true; - } - - Format fmt = this.getWorkBook().getFormat(ifmt); - if (fmt == null) return false; - - // toLowerCase is a simplistic way to implement the case insensitivity - // of the pattern tokens. It could cause issues with string literals. - String myfmt = fmt.getFormat().toLowerCase(); - return isDatePattern(myfmt); - } - - public static boolean isDatePattern(String myfmt) { - // Search for the format string in the list of known date formats - for (int x = 0; x < FormatConstants.DATE_FORMATS.length; x++) { - if (FormatConstants.DATE_FORMATS[x][0].equals(myfmt)) { - return true; - } - } - - // check for string patterns that only exist within date records (as far as we know, may need refining) - return myfmt.indexOf("mm") > -1 || myfmt.indexOf("yy") > -1 || myfmt.indexOf("dd") > -1; - } - - /** - * Parses an escaped xml format pattern (from ooxml) and returns an io.starter.OpenXLS compatible - * pattern. - *

                      - * This method certainly has weaknesses, but my intention is that if it is not a fairly standard format and/or - * we are not sure how to parse it we should leave the existent format intact soas to not break read/write operations - *

                      - * Oddly enough, excel seems to be able to handle biff8 patterns, in my testing so far there has been no need - * to reencode, that could obviously change... - * - * @param xmlFormatPattern - * @return compatible biff8 formatPattern - */ - public static String unescapeFormatPattern(String xmlFormatPattern) { - // strip escaping for currency pattern. Probably should explore all currency types and do an iteration - xmlFormatPattern = xmlFormatPattern.replace("\"$\"", "$"); - - // separator between positive/negative - xmlFormatPattern = xmlFormatPattern.replace("_);", ";"); - - // unescape parens - xmlFormatPattern = xmlFormatPattern.replace("\\(", "("); - xmlFormatPattern = xmlFormatPattern.replace("\\)", ")"); - return xmlFormatPattern; - } - - - /** - * returns whether this Format is a Currency - */ - public boolean isCurrencyPattern() { - if (pat == null) { - setFormatPattern(getFormatPattern()); - } - for (int x = 0; x < FormatConstants.CURRENCY_FORMATS.length; x++) { - short cpt = (short) Integer.parseInt(FormatConstants.CURRENCY_FORMATS[x][1], 16); - if (ifmt == cpt) { - String ptx = FormatConstants.CURRENCY_FORMATS[x][0]; - // what up with this? General? - if (cpt == 1) { - return pat.equals(ptx); - - } else { - return true; - } - } - } - // probably a built-in format that is not a currency format - Format fmt = this.getWorkBook().getFormat(ifmt); - if (fmt == null) return false; - String myfmt = fmt.getFormat(); - for (int x = 0; x < FormatConstants.CURRENCY_FORMATS.length; x++) { - if (FormatConstants.CURRENCY_FORMATS[x][0].equals(myfmt)) { - return true; - } - } - return false; - } - - /** - * get the Pattern Color index for this Cell if Solid Fill, or the Foreground color if no Solid Pattern - */ - public short getForegroundColor() { - if (fill != null) return (short) fill.getFgColorAsInt(getWorkBook().getTheme()); - return this.icvFore; - } - - /** - * get the Pattern Color for this Cell if Solid Fill, or the Foreground color if no Solid Pattern, as a Hex Color String - * - * @return Hex Color String - */ - public String getForegroundColorHEX() { - if (fill != null) - return fill.getFgColorAsRGB(getWorkBook().getTheme()); - return FormatHandle.colorToHexString(FormatHandle.getColor(this.icvFore)); - - } - - /** - * get the background Color for this Cell as a Hex Color String - * - * @return Hex Color String - */ - public String getBackgroundColorHEX() { - if (fill != null) - return fill.getBgColorAsRGB(getWorkBook().getTheme()); - if (this.icvBack == 65) // default background color - return "#FFFFFF"; // return white - return FormatHandle.colorToHexString(FormatHandle.getColor(this.icvBack)); - } - - /** - * get the background Color index for this Cell - */ - public short getBackgroundColor() { - if (fill != null) return (short) fill.getBgColorAsInt(getWorkBook().getTheme()); - if (this.icvBack == 65) // default background color - return 64; // return white - return this.icvBack; - } - - /** - * get the Formatting for this BiffRec from the pattern - * match. - *

                      - * case insensitive pattern match is performed... - */ - public String getFormatPattern() { - if (pat != null) return pat; - String[][] fmts = FormatConstantsImpl.getBuiltinFormats(); - for (int x = 0; x < fmts.length; x++) { - if (this.ifmt == Integer.parseInt(fmts[x][1], 16)) { - pat = fmts[x][0]; - return pat; - } - } - - Format fmt = this.getWorkBook().getFormat(ifmt); - if (fmt != null) { - pat = fmt.toString(); - return fmt.getFormat(); - } - return null; - } - - /** - * Sets the number format pattern for this format. - */ - public void setFormatPattern(String pattern) { - this.pat = pattern; - - if (this.getWorkBook() == null) throw new IllegalStateException( - "attempting to set format pattern but workbook is null"); - - this.setFormat(addFormatPattern(getWorkBook(), pattern)); - } - - /** - * Ensures that the given format pattern exists on the given workbook. - * - * @param book the workbook to which the pattern should belong - * @param pattern the number format pattern to ensure exists - * @return the format ID of the given format pattern - */ - public static short addFormatPattern(WorkBook book, String pattern) { - short ifmt = -1; - - // Look up the pattern on the workbook - ifmt = book.getFormatId(pattern); - - // If the pattern is unknown, create and add a Format record - if (ifmt == -1) { - Format format = new Format(book, pattern); - ifmt = format.getIfmt(); - } - - return ifmt; - } - - /** - * set the pointer to the XF's Format in the WorkBook - */ - public void setFormat(short ifm) { - ifmt = ifm; - byte[] nef = ByteTools.shortToLEBytes(ifmt); - this.getData()[2] = nef[0]; - this.getData()[3] = nef[1]; - this.pat = null; // 20080228 KSC: flag to re-input - } - - - /** - * set the pointer to the XF's Font in the WorkBook - */ - public void setFont(int ifn) { - ifnt = (short) ifn; - byte[] nef = ByteTools.shortToLEBytes(ifnt); - this.getData()[0] = nef[0]; - this.getData()[1] = nef[1]; - // reset the pointer for xf's and font's brought from other workbooks. - if (this.getWorkBook() != null) { - myFont = this.getWorkBook().getFont(ifn); - } - } - - /** - * Set myFont in XF to the same as Workbook's - */ - public void setMyFont(Font f) { - myFont = f; - } - - /** - * set the Fill Pattern for this Format - */ - public void setPattern(int t) { - fls = (short) t; - this.updatePattern(); - if (fill != null) - fill.setFillPattern(t); - } - - /** - * get the format pattern for this particular XF - * - * @return - */ - public int getFillPattern() { - if (fill != null) return fill.getFillPatternInt(); - return fls; - } - - - // BORDER SECTION - - public short getBottomBorderLineStyle() { - return this.dgBottom; - } - - public short getTopBorderLineStyle() { - return this.dgTop; - } - - public short getLeftBorderLineStyle() { - return this.dgLeft; - } - - public short getRightBorderLineStyle() { - return this.dgRight; - } - - - public short getDiagBorderLineStyle() { - return this.dgDiag; - } - - /** - * set the Top Border Color for this Format - */ - public void setTopBorderColor(int t) { - if (t == 0) t = 64; // 20080118 KSC - icvTop = (short) t; - updateBorderColors(); - } - - public int getTopBorderColor() { - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvTop == 64) return 65; - return (int) icvTop; - } - - /** - * set the Bottom Border Color for this Format - */ - public void setBottomBorderColor(int t) { - if (t == 0) t = 64; // 20080118 KSC - icvBottom = (short) t; - updateBorderColors(); - } - - public int getBottomBorderColor() { - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvBottom == 64) return 65; - return (int) icvBottom; - } - - /** - * set the Left Border Color for this Format - */ - public void setLeftBorderColor(int t) { - if (t == 0) t = 64; // 20080118 KSC - icvLeft = (short) t; - updateBorderColors(); - } - - public int getLeftBorderColor() { - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvLeft == 64) return 65; - return (int) icvLeft; - } - - /** - * set the Right Border Color for this Format - */ - public void setRightBorderColor(int t) { - if (t == 0) t = 64; // 20080118 KSC - icvRight = (short) t; - updateBorderColors(); - - } - - public short getRightBorderColor() { - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvRight == 64) return 65; - return icvRight; - } - - /** - * set the diagonal Border Color for this Format - */ - public void setDiagBorderColor(int t) { - if (t == 0) t = 64; // 20080118 KSC - icvDiag = (short) t; - updateBorderColors(); - - } - - /** - * get the diagonal border color - * - * @return - */ - public short getDiagBorderColor() { - // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 - if (icvDiag == 64) return 65; - return icvDiag; - } - - /** - * set the Left Border Color for this Format - */ - public void setLeftBorderColor(short t) { - if (t == 0) t = 64; // 20080118 KSC - icvLeft = t; - updateBorderColors(); - } - - /** - * set the diagonal border for this Format - */ - public void setBorderDiag(int t) { - Iflag = 0; - Iflag |= icvTop; - Iflag |= (icvBottom << 7); - Iflag |= ((short) t << 14); - Iflag |= (dgDiag << 21); - Iflag |= ((short) mystery << 25); - Iflag |= (fls << 26); - this.updatePattern(); - } - - /** - * set the border line style for this Format - */ - public void setBorderLineStyle(short t) { - dgLeft = t; - dgRight = t; - dgTop = t; - dgBottom = t; - this.updateBorders(); - } - - /** - * set border line styles via array of ints representing border styles - * order= top, left, bottom, right [diagonal] - * - * @param b int[] - */ - public void setAllBorderLineStyles(int[] b) { - try { - if (b[0] > -1) dgTop = (short) b[0]; - if (b[1] > -1) dgLeft = (short) b[1]; - if (b[2] > -1) dgBottom = (short) b[2]; - if (b[3] > -1) dgRight = (short) b[3]; - if (b[4] > -1) dgDiag = (short) b[4]; - } catch (ArrayIndexOutOfBoundsException e) { - } - this.updateBorders(); - } - - /** - * set all border colors via an array of ints representing border color ints - * order= top, left, bottom, right, [diagonal] - * - * @param b int[] - */ - public void setAllBorderColors(int[] b) { - try { - if (b[0] > -1) icvTop = (short) b[0]; - if (b[1] > -1) icvLeft = (short) b[1]; - if (b[2] > -1) icvBottom = (short) b[2]; - if (b[3] > -1) icvRight = (short) b[3]; - if (b[4] > -1) icvDiag = (short) b[4]; - } catch (ArrayIndexOutOfBoundsException e) { - } - this.updateBorderColors(); - } - - public void setTopBorderLineStyle(short t) { - dgTop = t; - this.updateBorders(); - } - - public void setBottomBorderLineStyle(short t) { - dgBottom = t; - this.updateBorders(); - } - - public void setLeftBorderLineStyle(short t) { - dgLeft = t; - this.updateBorders(); - } - - public void setRightBorderLineStyle(short t) { - dgRight = t; - this.updateBorders(); - } - - public void updateBorders() { - short borderflag = 0; - borderflag = dgLeft; - borderflag = (short) ((borderflag | (dgRight) << 4)); - borderflag = (short) ((borderflag | (dgTop) << 8)); - borderflag = (short) ((borderflag | (dgBottom) << 12)); - //byte[] rkdata = this.getData(); - byte[] bords = ByteTools.shortToLEBytes(borderflag); - this.getData()[10] = bords[0]; - this.getData()[11] = bords[1]; - setAttributeFlag(); - } - - /** - * removes all borders for the style - */ - public void removeBorders() { - this.dgBottom = 0; - this.dgTop = 0; - this.dgDiag = 0; - this.dgLeft = 0; - this.dgRight = 0; - this.dgBottom = 0; - this.updateBorders(); - } - - public void updateBorderColors() { - setAttributeFlag(); - } - - public void updatePattern() { - byte[] rkdata = this.getData(); - short thisFlag = 0; - thisFlag |= icvLeft; - thisFlag |= (icvRight << 7); - thisFlag |= (grbitDiag << 14); - byte[] bytes = ByteTools.shortToLEBytes(thisFlag); - rkdata[12] = bytes[0]; - rkdata[13] = bytes[1]; - - Iflag = 0; - Iflag |= icvTop; - Iflag |= (icvBottom << 7); - Iflag |= (icvDiag << 14); - Iflag |= (dgDiag << 21); - Iflag |= ((short) mystery << 25); - Iflag |= (fls << 26); - byte[] nef = ByteTools.cLongToLEBytes(Iflag); - rkdata[14] = nef[0]; - rkdata[15] = nef[1]; - rkdata[16] = nef[2]; - rkdata[17] = nef[3]; - // update format cache upon change - pat = null; - this.wkbook.updateFormatCache(this); - } - - /** - * set the Foreground Color for this Format - * THIS SETS THE BACKGROUND COLOR when PATTERN (fls) = PATTERN_SOLID - * THIS SETS THE PATTERN COLOR when PATTERN (fls) > PATTERN_SOLID - *
                      "If the fill style is solid: When solid is specified, the - * foreground color (fgColor) is the only color rendered, - * even when a background color (bgColor) is also specified" - * icvFore==Pattern Background Color - * - * @param clr java.awt.Color or null if use standard Excel 2003 Color Table - * @param t best match index into 2003-style Color tabe - */ - public void setForeColor(int t, Color clr) { - icvColorFlag = 0; - icvColorFlag |= ((short) t); - icvColorFlag |= (icvBack << 7); - if (clr != null) { - if (!clr.equals(FormatHandle.COLORTABLE[t])) { // no exact match for color - if (fill == null) - fill = new Fill(getFillPattern(), t, FormatHandle.colorToHexString(clr), icvBack, null, this.getWorkBook().getTheme()); - else - fill.setFgColor(t, FormatHandle.colorToHexString(clr)); - } - } else if (fill != null) - fill.setFgColor(t); - this.updateColors(); - } - - /** - * set the Background Color for this Format (when PATTERN - fls != PATTERN_SOLID) - * When PATTERN is PATTERN_SOLID, == 64 - * - * @param clr java.awt.Color or null if use standard Excel 2003 Color Table - * @param t best-match index into 2003-style Color table - */ - public void setBackColor(int t, Color clr) { - icvColorFlag = 0; - icvColorFlag |= icvFore; - icvColorFlag |= ((short) t << 7); - if (clr != null) { - if (!clr.equals(FormatHandle.COLORTABLE[t])) { // no exact match for color - store custom color - if (fill == null) - fill = new Fill(getFillPattern(), icvFore, null, t, FormatHandle.colorToHexString(clr), this.getWorkBook().getTheme()); - else - fill.setBgColor(t, FormatHandle.colorToHexString(clr)); - } - } else if (fill != null) - fill.setBgColor(t); - - this.updateColors(); - } - - void updateColors() { - byte[] rkdata = this.getData(); - byte[] nef = ByteTools.shortToLEBytes(icvColorFlag); - rkdata[18] = nef[0]; - rkdata[19] = nef[1]; - icvFore = (short) (icvColorFlag & 0x7F); - icvBack = (short) ((icvColorFlag & 0x3F80) >> 7); - // update format cache upon change - pat = null; - this.wkbook.updateFormatCache(this); - } - - /** - * PATTERN_SOLID is a special case where icvFore= the background color and icvBack=64. - * "If the fill style is solid: When solid is specified, the - * foreground color (fgColor) is the only color rendered, - * even when a background color (bgColor) is also - * specified" - */ - public static final int PATTERN_SOLID = 1; // was set to 4 but tht's wrong!! - - /** - * Sets the fill pattern to solid, which renders the background to 64=="the default fg color" - * "If the fill style is solid: When solid is specified, the - * foreground color (fgColor) is the only color rendered, - * even when a background color (bgColor) is also - * specified" - */ - public void setBackgroundSolid() { - setPattern(PATTERN_SOLID); - setBackColor(64, null); - if (fill != null) fill.setFillPattern(PATTERN_SOLID); - } - - public boolean isBackgroundSolid() { - if (fill != null) return fill.isBackgroundSolid(); - byte[] rkdata = this.getData(); - return (rkdata[17] == (byte) PATTERN_SOLID); - } - - - /** - * Sets the attribute flags for this xf record. These flags consist of - * // bit 8= fAtrProt - * // 7= fAtrPat - * // 6= fAtrBdr - * // 5= fAtrAlc (Alignment) - * // 4= fAtrFnt - * // 3= fAtrNum - */ - private void setAttributeFlag() { - setToCellXF(); - byte[] rkdata = this.getData(); - byte used_attrib = rkdata[9]; - byte borderFlag = (byte) ((dgBottom > 0 || dgTop > 0 || dgLeft > 0 || dgRight > 0 || dgDiag > 0) ? 1 : 0); // if border is set - if (borderFlag == 1) - used_attrib = (byte) (used_attrib | 0x20); // set bit # 6 - else - used_attrib = (byte) (used_attrib & 0xDF); // clear it - if (cIndent != 0 || iReadingOrder != 0 || alc != 0 || alcV != 0 || fWrap != 0 || trot != 0) // set bit # 5 - used_attrib = (byte) (used_attrib | 0x10); - else - used_attrib = (byte) (used_attrib & 0xEF); // clear it - - rkdata[9] = used_attrib; - fAtrNum = (short) ((used_attrib & 0x04) == 0x04 ? 1 : 0); - fAtrFnt = (short) ((used_attrib & 0x08) == 0x08 ? 1 : 0); - fAtrAlc = (short) ((used_attrib & 0x10) == 0x10 ? 1 : 0); - fAtrBdr = (short) ((used_attrib & 0x20) == 0x20 ? 1 : 0); - fAtrPat = (short) ((used_attrib & 0x40) == 0x40 ? 1 : 0); - fAtrProt = (short) ((used_attrib & 0x80) == 0x80 ? 1 : 0); - - - // must set color flag for borders or Excel will not like [BugTracker 2861] - if (dgTop > 0 && icvTop == 0) - icvTop = 64; - if (dgBottom > 0 && icvBottom == 0) - icvBottom = 64; - if (dgRight > 0 && icvRight == 0) - icvRight = 64; - if (dgLeft > 0 && icvLeft == 0) - icvLeft = 64; - if (dgDiag > 0 && icvDiag == 0) - icvDiag = 64; - this.updatePattern(); - } - - /** - * Switch the record to a cell XF record - */ - public void setToCellXF() { - if (fStyle != 0) {// must set to cell xf (fStyle==0) as changes will not show [BugTracker 2861] - fStyle = 0; - byte flag = (byte) fLocked; - flag = (byte) ((flag | (fHidden) << 1)); - this.getData()[4] = flag; - this.getData()[5] = 0; // upper bits are style parent rec index - } - } - - - /** - * @return Returns the ifnt. - */ - public short getIfnt() { - return ifnt; - } - - /** - * @param ifnt The ifnt to set. - */ - public void setIfnt(short ifnt) { - this.ifnt = ifnt; - } - - public short getIfmt() { - return ifmt; - } - - public void setHorizontalAlignment(int hAlign) { - alc = (short) hAlign; - updateAlignment(); - setAttributeFlag(); - } - - /** - * set the indent (1=3 spaces) - * - * @param indent - */ - public void setIndent(int indent) { // indent # = 3 spaces - cIndent = (short) indent; // mask = 0xF, 4 bits, - byte b = (byte) (this.getData()[8] & 0xF0); - b |= (cIndent); // 1st 4 bits - this.getData()[8] = b; - if (alc != FormatConstants.ALIGN_LEFT || alc != FormatConstants.ALIGN_RIGHT) // indent only valid for Left and Right (apparently - setHorizontalAlignment(FormatConstants.ALIGN_LEFT); - setAttributeFlag(); - } - - /** - * return the indent setting (1=3 spaces) - * - * @return - */ - public int getIndent() { - return cIndent; - } - - /** - * sets the Right to Left Text Direction or reading order of this style - * - * @param rtl possible values: - *
                      0=Context Dependent - *
                      1=Left-to-Right - *
                      2=Right-to-Let - */ - public void setRightToLeftReadingOrder(int rtl) { - // iReadingOrder= bits 7-6 - // 00= According to Context - // 01= Left to Right (0x40) - // 10= Right to Left (0x80) - if (rtl == 2) - iReadingOrder = 0x80; - else if (rtl == 1) - iReadingOrder = 0x40; - else - iReadingOrder = 0; - byte b = this.getData()[8]; - b |= (iReadingOrder); - this.getData()[8] = b; - this.wkbook.updateFormatCache(this); - setAttributeFlag(); - } - - /** - * returns true if this style is set to Right-to-Left text direction (reading order) - * - * @return - */ - public int getRightToLeftReadingOrder() { - return iReadingOrder >> 6; - } - - public int getHorizontalAlignment() { - return (int) alc; - } - - public void setWrapText(boolean wraptext) { - if (wraptext) { - fWrap = 1; - } else { - fWrap = 0; - } - updateAlignment(); - setAttributeFlag(); - } - - public boolean getWrapText() { - return fWrap == 1; - } - - public void setVerticalAlignment(int vAlign) { - alcV = (short) vAlign; - updateAlignment(); - setAttributeFlag(); - } - - public int getVerticalAlignment() { - return (int) alcV; - } - - public void setRotation(int rot) { - trot = (short) rot; - updateAlignment(); - setAttributeFlag(); - } - - public int getRotation() { - return trot; - } - - private void updateAlignment() { - //short tempAlc = (short)(alc << 3); - short tempfWrap = (short) (fWrap << 3); - short tempAlcV = (short) (alcV << 4); - short tempTrot = (short) (trot << 8); - short res = 0x0; - res = (short) (res | alc); - res = (short) (res | tempfWrap); - res = (short) (res | tempAlcV); - res = (short) (res | tempTrot); - byte[] rkdata = this.getData(); - byte[] bords = ByteTools.shortToLEBytes(res); - rkdata[6] = bords[0]; - rkdata[7] = bords[1]; - // update format cache upon change - this.wkbook.updateFormatCache(this); - } - - /** - * Returns an XML fragment representing the XF backing the format Handle. The XF record is style information - * associated with a cell. Font information/lookup is not included in this output so it can be used as a comparitor - * style - */ - public String getXML() { - StringBuffer sb = new StringBuffer(""); - Font myf = this.getFont(); - // font info... - sb.append(""); - // format info, should be expanded prolly - sb.append(""); - // 20071218 KSC: Add Fill - sb.append(""); - // get the border.. - sb.append(""); - if (getRightBorderLineStyle() != 0) { - sb.append(""); - } - if (getBottomBorderLineStyle() != 0) { - sb.append(""); - } - if (getLeftBorderLineStyle() != 0) { - sb.append(""); - } - if (getTopBorderLineStyle() != 0) { - sb.append(""); - } - sb.append(""); - - // get the alignment.. - sb.append(""); - - // get the background color - if (wkbook.colorTable[getForegroundColor()] != Color.WHITE) { - sb.append(""); - } - - sb.append(""); - return sb.toString(); - } - - /** - * @return Returns the myFormat. - */ - public Format getFormat() { - return myFormat; - } - - /** - * @param myFormat The myFormat to set. - */ - public void setFormat(Format myFormat) { - this.myFormat = myFormat; - } - - /** - * get whether this cell formula is hidden - * - * @return - */ - public boolean isFormulaHidden() { - return (this.fHidden == 0x1); - } - - /** - * sets the cell formula as hidden - * - * @param hd - */ - public void setFormulaHidden(boolean hd) { - if (hd) - this.fHidden = 0x1; - else - this.fHidden = 0x0; - updateLockedHidden(); - } - - /** - * get whether this is a locked Cell - * - * @return - */ - public boolean isLocked() { - return (this.fLocked == 0x1); - } - - /** - * return whether this cell is set to "shrink to fit" - * - * @return - */ - public boolean isShrinkToFit() { - return (this.fShrinkToFit == 0x1); - } - - public void setShrinkToFit(boolean b) { - if (b) { - this.fShrinkToFit = 0x1; - this.getData()[9] |= 0x10; - } else { - this.fShrinkToFit = 0x0; // turn off bit 4 - this.getData()[9] &= 0xF7; // set bit 4 - } - } - - /** - * sets the cell as locked - * - * @param lk - */ - public void setLocked(boolean lk) { - if (lk) - this.fLocked = 0x1; - else - this.fLocked = 0x0; - updateLockedHidden(); - } - - - /** - * 2 2 XF type, cell protection, and parent style XF: - * Bit Mask Contents - * 2-0 0007H XF_TYPE_PROT – XF type, cell protection (see above) - * 15-4 FFF0H Index to parent style XF (always FFFH in style XFs) - *

                      - * Bit Mask Contents - * 0 01H 1 = Cell is locked - * 1 02H 1 = Formula is hidden - * 2 04H 0 = Cell XF; 1 = Style XF - */ - private void updateLockedHidden() { - - short tempFL = (short) (fLocked << 0x0); - short tempFH = (short) (fHidden << 0x1); - short tempST = (short) (fStyle << 0x2); - - short flag = 0x0; - flag = (short) (flag | tempFL); - flag = (short) (flag | tempFH); - flag = (short) (flag | tempST); - - byte[] dx = this.getData(); - byte[] nef = ByteTools.shortToLEBytes(flag); - dx[4] = nef[0]; - dx[5] = nef[1]; - // update format cache upon change - pat = null; - this.wkbook.updateFormatCache(this); - } - - - /** - * @return - */ - public boolean getStricken() { - if (myFont != null) return myFont.getStricken(); - return false; - } - - public void setStricken(boolean b) { - if (myFont != null) myFont.setStricken(b); - } - - - /** - * @return - */ - public boolean getItalic() { - if (myFont != null) return myFont.getItalic(); - return false; - } - - public void setItalic(boolean b) { - if (myFont != null) myFont.setItalic(b); - } - - - /** - * @return - */ - public boolean getUnderlined() { - if (myFont != null) return myFont.getUnderlined(); - return false; - } - - public void setUnderlined(boolean b) { - if (myFont != null) myFont.setUnderlined(b); - } - - - /** - * @return - */ - public boolean getBold() { - if (myFont != null) return myFont.getBold(); - return false; - } - - public void setBold(boolean b) { - if (myFont != null) myFont.setBold(b); - } - - public int getIdx() { - return tableidx; - } - - /** - * return truth of "this Xf rec is a style xf" - * - * @return - */ - public boolean isStyleXf() { - return (fStyle == 1); - } - - /** - * clone the xf and add to streamer - * - * @param xf - * @return - */ - private static Xf cloneXf(Xf xf, WorkBook wkbook) { - Xf clone; - if (xf.getIdx() > -1) { // it's in the wb already - clone = new Xf(xf.ifnt, wkbook); - byte[] xfbytes = xf.getBytesAt(0, xf.getLength() - 4); - clone.setData(xfbytes); - clone.init(); - } else { // xf hasn't been added to wb yet, no need to clone - clone = xf; - } - clone.fill = xf.fill; - clone.setToCellXF(); // changes will not be seen if fstyle bit is set TODO: is this correct in all cases??? - clone.tableidx = wkbook.insertXf(clone); - return clone; - } - - /** - * if xf parameter doesn't exist, create; if it does, create a new xf based on it - * - * @param xf original xf - * @param fontIdx font to link xf to - * @param wkbook - * @return new xf - */ - public static Xf updateXf(Xf xf, int fontIdx, WorkBook wkbook) { - if (xf == null) { - xf = new Xf(fontIdx, wkbook); - xf.tableidx = wkbook.insertXf(xf); // insert new xf into stream ... - return xf; - } else { - xf = Xf.cloneXf(xf, wkbook); - } - return xf; - } - - /** - * set the OOXML fill for this xf - * - * @param f - */ - public void setFill(Fill f) { - this.fill = (Fill) f.cloneElement(); - fls = (short) this.fill.getFillPatternInt(); - icvFore = (short) this.fill.getFgColorAsInt(getWorkBook().getTheme()); - icvBack = (short) this.fill.getBgColorAsInt(getWorkBook().getTheme()); - } - - /** - * return the OOXML fill for this xf, if any - */ - public Fill getFill() { - return this.fill; - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - super.close(); - this.myFont.close(); - this.myFormat = null; - } - -} - diff --git a/src/main/java/io/starter/formats/XLS/Xf.kt b/src/main/java/io/starter/formats/XLS/Xf.kt new file mode 100644 index 0000000..459793c --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/Xf.kt @@ -0,0 +1,1445 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS + +import io.starter.OpenXLS.FormatHandle +import io.starter.formats.OOXML.Fill +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.awt.Color + + +/** + * **XF: Extended Format (E0h)**

                      + * The XF record stores formatting properties. + * + * + * If fStyle bit is true, then the XF is a style XF, otherwise + * it is a BiffRec XF. Cells and Styles both contain ixfe pointers + * which correspond to their associated XF record. + * + *

                      + * BiffRec XF Record
                      + *
                      + * offset  Bits   MASK     name        contents
                      + * ---
                      + * 0       15-0   0xFFFF   ifnt        Index to the FONT record.
                      + * 2       15-0   0xFFFF   ifmt        Index to the FORMAT record.
                      + * 4       0      0x0001   fLocked     =1 if the cell is locked.
                      + * 1      0x0002   fHidden     =1 if the cell formula is hidden (value still shown)
                      + * 2      0x0004   fStyle      =0 for cell XF.
                      + * =1 for style XF.
                      + *
                      + * ~~~ additional option flags omitted ~~~
                      + *
                      +
                      * + * + * @see SST + * + * @see LABELSST + * + * @see EXTSST + */ + + +class Xf : io.starter.formats.XLS.XLSRecord { + + var idx = -1 + internal set + // Shared variables. + /** + * @return Returns the ifnt. + */ + /** + * @param ifnt The ifnt to set. + */ + var ifnt: Short = 0 + var ifmt: Short = 0 + private set + private var fLocked: Short = 0 + private var fHidden: Short = 0 + private var fStyle: Short = 0 + // the records that are initialized to "0" are a 1/0 flag. Just makin my life easier... + private var f123Prefix: Short = 0 + private var ixfParent: Short = 0 + private var alc: Short = 0 + private var fWrap: Short = 0 + private var alcV: Short = 0 + // private short fJustLast; Used in Far East version of Excel only!! + private var cIndent: Short = 0 + private var trot: Short = 0 + // private short cIntednt; + private var fShrinkToFit: Short = 0 + private var fMergeCell: Short = 0 + private var iReadingOrder: Short = 0 + private var fAtrNum: Short = 0 + private var fAtrFnt: Short = 0 + private var fAtrAlc: Short = 0 + private var fAtrBdr: Short = 0 + private var fAtrPat: Short = 0 + private var fAtrProt: Short = 0 + private var dgLeft: Short = 0 + private var dgRight: Short = 0 + private var dgTop: Short = 0 + private var dgBottom: Short = 0 + private var icvLeft: Short = 0 + private var icvRight: Short = 0 + private var grbitDiag: Short = 0 + private var icvTop: Short = 0 + private var icvBottom: Short = 0 + private var icvDiag: Short = 0 + var diagBorderLineStyle: Short = 0 + private set + private var fls: Short = 0 + private var icvFore: Short = 0 + private var icvBack: Short = 0 + private var fSxButton: Short = 0 + private var icvColorFlag: Short = 0 + internal var Iflag = 0 + internal var mystery: Byte = 0 + private var pat: String? = null + /** + * OOXML fill, if any + */ + var fill: Fill? = null // ugly that it's public ... + // These should only be populated for boundsheet transferral issues. + private var myFont: Font? = null + private var myFormat: Format? = null + /** + * return # records using this xf + * + * @return + */ + var useCount: Short = 0 + private set // KSC: added 20121003 to keep track of xf usage by biffrecs + + /** + * Set the workbook for this XF + * + * + * This can get called multiple times. This results in a disparity within + * xf counting in workbook. + */ + override var workBook: WorkBook? + get + set(b) { + super.workBook = b + } + + + // marginal! + // 0xf4 ? + var merged: Boolean + get() = fMergeCell.toInt() == 1 + set(mgd) { + val rkdata = this.getData() + rkdata[9] = 0x78.toByte() + if (DEBUGLEVEL > 1) Logger.logInfo("Xf The merge style bit is: $fMergeCell") + + } + + /** + * returns the associated Font record for this XF + */ + override val font: Font? + get() { + if (myFont != null) return myFont + myFont = this.workBook!!.getFont(ifnt.toInt()) + return myFont + } + + /** + * returns whether this Format is a Date + * + * + * Needs to be revisited. Currently I am only returning true for the standard "built in" dates + */ + // Check the format ID against all known date formats. Why do we do + // this instead of letting it be caught by the string matching below? + // toLowerCase is a simplistic way to implement the case insensitivity + // of the pattern tokens. It could cause issues with string literals. + val isDatePattern: Boolean + get() { + for (x in FormatConstants.DATE_FORMATS.indices) { + val sxt = Integer.parseInt(FormatConstants.DATE_FORMATS[x][1], 16).toShort() + if (ifmt == sxt) + return true + } + + val fmt = this.workBook!!.getFormat(ifmt.toInt()) ?: return false + val myfmt = fmt.format!!.toLowerCase() + return isDatePattern(myfmt) + } + + + /** + * returns whether this Format is a Currency + */ + // what up with this? General? + // probably a built-in format that is not a currency format + val isCurrencyPattern: Boolean + get() { + if (pat == null) { + formatPattern = formatPattern + } + for (x in FormatConstants.CURRENCY_FORMATS.indices) { + val cpt = Integer.parseInt(FormatConstants.CURRENCY_FORMATS[x][1], 16).toShort() + if (ifmt == cpt) { + val ptx = FormatConstants.CURRENCY_FORMATS[x][0] + return if (cpt.toInt() == 1) { + pat == ptx + + } else { + true + } + } + } + val fmt = this.workBook!!.getFormat(ifmt.toInt()) ?: return false + val myfmt = fmt.format + for (x in FormatConstants.CURRENCY_FORMATS.indices) { + if (FormatConstants.CURRENCY_FORMATS[x][0] == myfmt) { + return true + } + } + return false + } + + /** + * get the Pattern Color index for this Cell if Solid Fill, or the Foreground color if no Solid Pattern + */ + val foregroundColor: Short + get() = if (fill != null) fill!!.getFgColorAsInt(workBook!!.theme).toShort() else this.icvFore + + /** + * get the Pattern Color for this Cell if Solid Fill, or the Foreground color if no Solid Pattern, as a Hex Color String + * + * @return Hex Color String + */ + val foregroundColorHEX: String? + get() = if (fill != null) fill!!.getFgColorAsRGB(workBook!!.theme) else FormatHandle.colorToHexString(FormatHandle.getColor(this.icvFore.toInt())) + + /** + * get the background Color for this Cell as a Hex Color String + * + * @return Hex Color String + */ + // default background color + // return white + val backgroundColorHEX: String? + get() { + if (fill != null) + return fill!!.getBgColorAsRGB(workBook!!.theme) + return if (this.icvBack.toInt() == 65) "#FFFFFF" else FormatHandle.colorToHexString(FormatHandle.getColor(this.icvBack.toInt())) + } + + /** + * get the background Color index for this Cell + */ + // default background color + // return white + val backgroundColor: Short + get() { + if (fill != null) return fill!!.getBgColorAsInt(workBook!!.theme).toShort() + return if (this.icvBack.toInt() == 65) 64 else this.icvBack + } + + /** + * get the Formatting for this BiffRec from the pattern + * match. + * + * + * case insensitive pattern match is performed... + */ + /** + * Sets the number format pattern for this format. + */ + override var formatPattern: String? + get() { + if (pat != null) return pat + val fmts = FormatConstantsImpl.builtinFormats + for (x in fmts.indices) { + if (this.ifmt.toInt() == Integer.parseInt(fmts[x][1], 16)) { + pat = fmts[x][0] + return pat + } + } + + val fmt = this.workBook!!.getFormat(ifmt.toInt()) + if (fmt != null) { + pat = fmt.toString() + return fmt.format + } + return null + } + set(pattern) { + this.pat = pattern + + if (this.workBook == null) + throw IllegalStateException( + "attempting to set format pattern but workbook is null") + + this.setFormat(addFormatPattern(workBook!!, pattern)) + } + + /** + * get the format pattern for this particular XF + * + * @return + */ + val fillPattern: Int + get() = if (fill != null) fill!!.fillPatternInt else fls.toInt() + + + // BORDER SECTION + + var bottomBorderLineStyle: Short + get() = this.dgBottom + set(t) { + dgBottom = t + this.updateBorders() + } + + var topBorderLineStyle: Short + get() = this.dgTop + set(t) { + dgTop = t + this.updateBorders() + } + + var leftBorderLineStyle: Short + get() = this.dgLeft + set(t) { + dgLeft = t + this.updateBorders() + } + + var rightBorderLineStyle: Short + get() = this.dgRight + set(t) { + dgRight = t + this.updateBorders() + } + + /** + * set the Top Border Color for this Format + */ + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + // 20080118 KSC + var topBorderColor: Int + get() = if (icvTop.toInt() == 64) 65 else icvTop.toInt() + set(t) { + var t = t + if (t == 0) t = 64 + icvTop = t.toShort() + updateBorderColors() + } + + /** + * set the Bottom Border Color for this Format + */ + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + // 20080118 KSC + var bottomBorderColor: Int + get() = if (icvBottom.toInt() == 64) 65 else icvBottom.toInt() + set(t) { + var t = t + if (t == 0) t = 64 + icvBottom = t.toShort() + updateBorderColors() + } + + /** + * set the Left Border Color for this Format + */ + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + // 20080118 KSC + var leftBorderColor: Int + get() = if (icvLeft.toInt() == 64) 65 else icvLeft.toInt() + set(t) { + var t = t + if (t == 0) t = 64 + icvLeft = t.toShort() + updateBorderColors() + } + + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + val rightBorderColor: Short + get() = if (icvRight.toInt() == 64) 65 else icvRight + + /** + * get the diagonal border color + * + * @return + */ + // 20070205 KSC: 64 is automatic border color but should be interpreted as 65 + val diagBorderColor: Short + get() = if (icvDiag.toInt() == 64) 65 else icvDiag + + val isBackgroundSolid: Boolean + get() { + if (fill != null) return fill!!.isBackgroundSolid + val rkdata = this.getData() + return rkdata!![17] == PATTERN_SOLID.toByte() + } + + /** + * return the indent setting (1=3 spaces) + * + * @return + */ + /** + * set the indent (1=3 spaces) + * + * @param indent + */ + // indent # = 3 spaces + // mask = 0xF, 4 bits, + // 1st 4 bits + // indent only valid for Left and Right (apparently + var indent: Int + get() = cIndent.toInt() + set(indent) { + cIndent = indent.toShort() + var b = (this.getData()!![8] and 0xF0).toByte() + b = b or cIndent.toByte() + this.getData()[8] = b + if (alc.toInt() != FormatConstants.ALIGN_LEFT || alc.toInt() != FormatConstants.ALIGN_RIGHT) + horizontalAlignment = FormatConstants.ALIGN_LEFT + setAttributeFlag() + } + + /** + * returns true if this style is set to Right-to-Left text direction (reading order) + * + * @return + */ + /** + * sets the Right to Left Text Direction or reading order of this style + * + * @param rtl possible values: + *

                      0=Context Dependent + *

                      1=Left-to-Right + *

                      2=Right-to-Let + */ + // iReadingOrder= bits 7-6 + // 00= According to Context + // 01= Left to Right (0x40) + // 10= Right to Left (0x80) + var rightToLeftReadingOrder: Int + get() = iReadingOrder shr 6 + set(rtl) { + if (rtl == 2) + iReadingOrder = 0x80 + else if (rtl == 1) + iReadingOrder = 0x40 + else + iReadingOrder = 0 + var b = this.getData()!![8] + b = b or iReadingOrder.toByte() + this.getData()[8] = b + this.wkbook!!.updateFormatCache(this) + setAttributeFlag() + } + + var horizontalAlignment: Int + get() = alc.toInt() + set(hAlign) { + alc = hAlign.toShort() + updateAlignment() + setAttributeFlag() + } + + var wrapText: Boolean + get() = fWrap.toInt() == 1 + set(wraptext) { + if (wraptext) { + fWrap = 1 + } else { + fWrap = 0 + } + updateAlignment() + setAttributeFlag() + } + + var verticalAlignment: Int + get() = alcV.toInt() + set(vAlign) { + alcV = vAlign.toShort() + updateAlignment() + setAttributeFlag() + } + + var rotation: Int + get() = trot.toInt() + set(rot) { + trot = rot.toShort() + updateAlignment() + setAttributeFlag() + } + + /** + * Returns an XML fragment representing the XF backing the format Handle. The XF record is style information + * associated with a cell. Font information/lookup is not included in this output so it can be used as a comparitor + * style + */ + // font info... + // format info, should be expanded prolly + // 20071218 KSC: Add Fill + // get the border.. + // get the alignment.. + // get the background color + val xml: String + get() { + val sb = StringBuffer("") + val myf = this.font + sb.append("") + sb.append("") + sb.append("") + sb.append("") + if (rightBorderLineStyle.toInt() != 0) { + sb.append("") + } + if (bottomBorderLineStyle.toInt() != 0) { + sb.append("") + } + if (leftBorderLineStyle.toInt() != 0) { + sb.append("") + } + if (topBorderLineStyle.toInt() != 0) { + sb.append("") + } + sb.append("") + sb.append("") + if (wkbook!!.colorTable[foregroundColor] !== Color.WHITE) { + sb.append("") + } + + sb.append("") + return sb.toString() + } + + /** + * get whether this cell formula is hidden + * + * @return + */ + /** + * sets the cell formula as hidden + * + * @param hd + */ + var isFormulaHidden: Boolean + get() = this.fHidden.toInt() == 0x1 + set(hd) { + if (hd) + this.fHidden = 0x1 + else + this.fHidden = 0x0 + updateLockedHidden() + } + + /** + * get whether this is a locked Cell + * + * @return + */ + /** + * sets the cell as locked + * + * @param lk + */ + var isLocked: Boolean + get() = this.fLocked.toInt() == 0x1 + set(lk) { + if (lk) + this.fLocked = 0x1 + else + this.fLocked = 0x0 + updateLockedHidden() + } + + /** + * return whether this cell is set to "shrink to fit" + * + * @return + */ + // turn off bit 4 + // set bit 4 + var isShrinkToFit: Boolean + get() = this.fShrinkToFit.toInt() == 0x1 + set(b) = if (b) { + this.fShrinkToFit = 0x1 + this.getData()[9] = this.getData()[9] or 0x10 + } else { + this.fShrinkToFit = 0x0 + this.getData()[9] = this.getData()[9] and 0xF7 + } + + + /** + * @return + */ + var stricken: Boolean + get() = if (myFont != null) myFont!!.stricken else false + set(b) { + if (myFont != null) myFont!!.stricken = b + } + + + /** + * @return + */ + var italic: Boolean + get() = if (myFont != null) myFont!!.italic else false + set(b) { + if (myFont != null) myFont!!.italic = b + } + + + /** + * @return + */ + var underlined: Boolean + get() = if (myFont != null) myFont!!.underlined else false + set(b) { + if (myFont != null) myFont!!.underlined = b + } + + + /** + * @return + */ + var bold: Boolean + get() = if (myFont != null) myFont!!.bold else false + set(b) { + if (myFont != null) myFont!!.bold = b + } + + /** + * return truth of "this Xf rec is a style xf" + * + * @return + */ + val isStyleXf: Boolean + get() = fStyle.toInt() == 1 + + constructor() { + //empty constructor + } + + /** + * create a new Xf with pointer to its font + */ + constructor(f: Int) { + val bl = byteArrayOf(0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32) + this.opcode = XLSConstants.XF + this.length = bl.size.toShort() + this.setData(bl) + this.setFont(f) + this.init() + } + + /** + * create a new Xf with pointer to its font and workbook set + */ + constructor(f: Int, wkbook: WorkBook) { + val bl = byteArrayOf(0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32) + this.opcode = XLSConstants.XF + this.length = bl.size.toShort() + this.setData(bl) + super.workBook = wkbook // set workbook but don't insert rec or add to xfrecs + this.setFont(f) + this.init() + } + + /** + * constructor which takes a Font object + a workbook + * useful for cloning xf's from other workbooks + * + * @param f font + * @param wkbook + */ + constructor(f: Font, wkbook: WorkBook) { + val bl = byteArrayOf(0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, -64, 32) + this.opcode = XLSConstants.XF + this.length = bl.size.toShort() + myFont = f + System.arraycopy(ByteTools.shortToLEBytes(f.idx.toShort()), 0, bl, 0, 2) + this.setData(bl) + super.workBook = wkbook // set workbook but don't insert rec or add to xfrecs + this.init() + } + + /** + * Create a string representation of the Xf + */ + override fun toString(): String { + var f: String? = "unknown" //Handle missing formats + try { + f = this.formatPattern + } catch (e: Exception) { + } + + val thisToString = " format:" + f + " fill:" + this.fillPattern + + " fg:" + this.foregroundColor + + " bg:" + this.backgroundColor + + " border:[" + + this.topBorderLineStyle + "-" + this.topBorderColor + ":" + + this.leftBorderLineStyle + "-" + this.leftBorderColor + ":" + + this.bottomBorderLineStyle + "-" + this.bottomBorderColor + ":" + + this.rightBorderLineStyle + "-" + this.rightBorderColor + "]" + + "W:" + this.wrapText + + "R:" + this.rotation + + "H:" + this.horizontalAlignment + "V:" + this.verticalAlignment + + "I:" + this.indent + + "L:" + this.isLocked + + "F:" + this.isFormulaHidden + + "D:" + this.rightToLeftReadingOrder + return this.font!!.toString() + thisToString + } + + /** + * inc # records using this xf + */ + fun incUseCount() { + useCount++ + } + + /** + * dec # records using this xf + */ + fun decUseCoount() { + useCount-- + } + + /** + * Populates the myFont and myFormat variables to be held onto + * when the xf record is serialized for boundsheet transfer + */ + fun populateForTransfer() { + myFont = this.font + myFormat = this.workBook!!.getFormat(ifmt.toInt()) + this.getData() + } + + /** + * The XF record can either be a style XF or a Cell XF. + */ + override fun init() { + super.init() + ifnt = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + ifnt = (ifnt and 0xffff).toShort() + ifmt = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + ifmt = (ifmt and 0xffff).toShort() + + + val flag = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + // is the cell locked? + if (flag and 0x1 == 0x1) { + fLocked = 0x1 + } else { + fLocked = 0 + } + // is the cell hidden? + if (flag and 0x2 == 0x2) { + fHidden = 0x1 + } else { + fHidden = 0 + } + + // is it a cell rec or a style rec? + if (flag and 0x4 == 0x4) { + fStyle = 1 + } else { + fStyle = 0 + } + if (flag and 0x8 == 0x0008) + f123Prefix = 0x1 + ixfParent = (flag and 0xFFF0 shr 4).toShort() + + + initXF() + + pat = null // ensure reset if xf has changed + if (DEBUGLEVEL > XLSConstants.DEBUG_LOW) + Logger.logInfo("Xf.init() ifnt: " + ifnt + + " ifmt: " + ifmt + ":" + + this.toString() + + " border: " + + "l:" + this.leftBorderColor + ":" + + "b:" + this.bottomBorderColor + ":" + + "r:" + this.rightBorderColor + ":" + + "t:" + this.topBorderColor + ":") + } + + /** + * read and interpret bytes 6-18) + */ + internal fun initXF() { + var flag: Short + + // bytes 6, 7: alignment, rotation, text break + flag = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + alc = (flag and 0x7).toShort() + if (flag and 0x8 == 0x8) fWrap = 1 + alcV = (flag and 0x70 shr 4).toShort() + trot = (flag and 0xFF00 shr 8).toShort() + + // byte 8: indent, reading order, shrink + flag = this.getByteAt(8).toShort() + cIndent = (flag and 0xF).toShort() + if (flag and 0x10 == 0x10) fShrinkToFit = 1 + if (flag and 0x20 == 0x20) fMergeCell = 1 + if (DEBUGLEVEL > 5) Logger.logInfo("Xf The merge cell bit is: $fMergeCell and the int is $flag") + + iReadingOrder = (flag and 0xC0).toShort()// >> 6); // reading order is byte 7-6 mask 0xCO + // USED_ATTRIB: bits 7-2 of byte 9 + flag = this.getByteAt(9).toShort() + /* for all these flags, a cleared bit means use Parent Style XF attribute + if set, means the attributes of THIS xf is used + bit mask meaning + 0 01H Flag for number format + 1 02H Flag for font + 2 04H Flag for horizontal and vertical alignment, text wrap, indentation, orientation, rotation, and + text direction + 3 08H Flag for border lines + 4 10H Flag for background area style + 5 20H Flag for cell protection (cell locked and formula hidden) + */ + if (flag and 0x4 == 0x4) fAtrNum = 1 // number format + if (flag and 0x8 == 0x8) fAtrFnt = 1 // font + if (flag and 0x10 == 0x10) fAtrAlc = 1 // alignment (h + v) text wrap rotation direction indent + if (flag and 0x20 == 0x20) fAtrBdr = 1 // border lines + if (flag and 0x40 == 0x40) fAtrPat = 1 // background format pattern + if (flag and 0x80 == 0x80) fAtrProt = 1 // cell protection + + // BORDER Section + flag = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + dgLeft = (flag and 0xF).toShort() + dgRight = (flag and 0xF0 shr 4).toShort() + dgTop = (flag and 0xF00 shr 8).toShort() + dgBottom = (flag and 0xF000 shr 12).toShort() + + flag = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + icvLeft = (flag and 0x7f).toShort() + icvRight = (flag and 0x3F80 shr 7).toShort() + grbitDiag = (flag and 0xC000 shr 15).toShort() + + // bytes 14-17 color and fill + Iflag = ByteTools.readInt(this.getByteAt(14), this.getByteAt(15), this.getByteAt(16), this.getByteAt(17)) + icvTop = (Iflag and 0x7F).toShort() + icvBottom = (Iflag and 0x3F80 shr 7).toShort() + icvDiag = (Iflag and 0x1FC000 shr 14).toShort() + diagBorderLineStyle = (Iflag and 0x1E00000 shr 21).toShort() + mystery = (Iflag and 0x3800000 shr 25).toByte() + fls = (Iflag and -0x4000000 shr 26).toShort() // fill pattern + + if (DEBUGLEVEL > 5 && icvTop > 0) Logger.logInfo("Xf The cell outline is true") + // bytes 18, 19: fill pattern colors + icvColorFlag = ByteTools.readShort(this.getByteAt(18).toInt(), this.getByteAt(19).toInt()) + icvFore = (icvColorFlag and 0x7F).toShort() // = Pattern Color + icvBack = (icvColorFlag and 0x3F80 shr 7).toShort() // = Pattern Background Color + if (icvColorFlag and 0x4000 == 0x4000) fSxButton = 1 + + // Logger.logInfo(io.starter.OpenXLS.ExcelTools.getRecordByteDef(this)); + } + + /** + * set the pointer to the XF's Format in the WorkBook + */ + fun setFormat(ifm: Short) { + ifmt = ifm + val nef = ByteTools.shortToLEBytes(ifmt) + this.getData()[2] = nef[0] + this.getData()[3] = nef[1] + this.pat = null // 20080228 KSC: flag to re-input + } + + + /** + * set the pointer to the XF's Font in the WorkBook + */ + fun setFont(ifn: Int) { + ifnt = ifn.toShort() + val nef = ByteTools.shortToLEBytes(ifnt) + this.getData()[0] = nef[0] + this.getData()[1] = nef[1] + // reset the pointer for xf's and font's brought from other workbooks. + if (this.workBook != null) { + myFont = this.workBook!!.getFont(ifn) + } + } + + /** + * Set myFont in XF to the same as Workbook's + */ + fun setMyFont(f: Font) { + myFont = f + } + + /** + * set the Fill Pattern for this Format + */ + fun setPattern(t: Int) { + fls = t.toShort() + this.updatePattern() + if (fill != null) + fill!!.setFillPattern(t) + } + + /** + * set the Right Border Color for this Format + */ + fun setRightBorderColor(t: Int) { + var t = t + if (t == 0) t = 64 // 20080118 KSC + icvRight = t.toShort() + updateBorderColors() + + } + + /** + * set the diagonal Border Color for this Format + */ + fun setDiagBorderColor(t: Int) { + var t = t + if (t == 0) t = 64 // 20080118 KSC + icvDiag = t.toShort() + updateBorderColors() + + } + + /** + * set the Left Border Color for this Format + */ + fun setLeftBorderColor(t: Short) { + var t = t + if (t.toInt() == 0) t = 64 // 20080118 KSC + icvLeft = t + updateBorderColors() + } + + /** + * set the diagonal border for this Format + */ + fun setBorderDiag(t: Int) { + Iflag = 0 + Iflag = Iflag or icvTop.toInt() + Iflag = Iflag or (icvBottom shl 7) + Iflag = Iflag or (t.toShort() shl 14) + Iflag = Iflag or (diagBorderLineStyle shl 21) + Iflag = Iflag or (mystery.toShort() shl 25) + Iflag = Iflag or (fls shl 26) + this.updatePattern() + } + + /** + * set the border line style for this Format + */ + fun setBorderLineStyle(t: Short) { + dgLeft = t + dgRight = t + dgTop = t + dgBottom = t + this.updateBorders() + } + + /** + * set border line styles via array of ints representing border styles + * order= top, left, bottom, right [diagonal] + * + * @param b int[] + */ + fun setAllBorderLineStyles(b: IntArray) { + try { + if (b[0] > -1) dgTop = b[0].toShort() + if (b[1] > -1) dgLeft = b[1].toShort() + if (b[2] > -1) dgBottom = b[2].toShort() + if (b[3] > -1) dgRight = b[3].toShort() + if (b[4] > -1) diagBorderLineStyle = b[4].toShort() + } catch (e: ArrayIndexOutOfBoundsException) { + } + + this.updateBorders() + } + + /** + * set all border colors via an array of ints representing border color ints + * order= top, left, bottom, right, [diagonal] + * + * @param b int[] + */ + fun setAllBorderColors(b: IntArray) { + try { + if (b[0] > -1) icvTop = b[0].toShort() + if (b[1] > -1) icvLeft = b[1].toShort() + if (b[2] > -1) icvBottom = b[2].toShort() + if (b[3] > -1) icvRight = b[3].toShort() + if (b[4] > -1) icvDiag = b[4].toShort() + } catch (e: ArrayIndexOutOfBoundsException) { + } + + this.updateBorderColors() + } + + fun updateBorders() { + var borderflag: Short = 0 + borderflag = dgLeft + borderflag = (borderflag or (dgRight shl 4)).toShort() + borderflag = (borderflag or (dgTop shl 8)).toShort() + borderflag = (borderflag or (dgBottom shl 12)).toShort() + //byte[] rkdata = this.getData(); + val bords = ByteTools.shortToLEBytes(borderflag) + this.getData()[10] = bords[0] + this.getData()[11] = bords[1] + setAttributeFlag() + } + + /** + * removes all borders for the style + */ + fun removeBorders() { + this.dgBottom = 0 + this.dgTop = 0 + this.diagBorderLineStyle = 0 + this.dgLeft = 0 + this.dgRight = 0 + this.dgBottom = 0 + this.updateBorders() + } + + fun updateBorderColors() { + setAttributeFlag() + } + + fun updatePattern() { + val rkdata = this.getData() + var thisFlag: Short = 0 + thisFlag = thisFlag or icvLeft + thisFlag = thisFlag or (icvRight shl 7).toShort() + thisFlag = thisFlag or (grbitDiag shl 14).toShort() + val bytes = ByteTools.shortToLEBytes(thisFlag) + rkdata[12] = bytes[0] + rkdata[13] = bytes[1] + + Iflag = 0 + Iflag = Iflag or icvTop.toInt() + Iflag = Iflag or (icvBottom shl 7) + Iflag = Iflag or (icvDiag shl 14) + Iflag = Iflag or (diagBorderLineStyle shl 21) + Iflag = Iflag or (mystery.toShort() shl 25) + Iflag = Iflag or (fls shl 26) + val nef = ByteTools.cLongToLEBytes(Iflag) + rkdata[14] = nef[0] + rkdata[15] = nef[1] + rkdata[16] = nef[2] + rkdata[17] = nef[3] + // update format cache upon change + pat = null + this.wkbook!!.updateFormatCache(this) + } + + /** + * set the Foreground Color for this Format + * THIS SETS THE BACKGROUND COLOR when PATTERN (fls) = PATTERN_SOLID + * THIS SETS THE PATTERN COLOR when PATTERN (fls) > PATTERN_SOLID + *

                      "If the fill style is solid: When solid is specified, the + * foreground color (fgColor) is the only color rendered, + * even when a background color (bgColor) is also specified" + * icvFore==Pattern Background Color + * + * @param clr java.awt.Color or null if use standard Excel 2003 Color Table + * @param t best match index into 2003-style Color tabe + */ + fun setForeColor(t: Int, clr: Color?) { + icvColorFlag = 0 + icvColorFlag = icvColorFlag or t.toShort() + icvColorFlag = icvColorFlag or (icvBack shl 7).toShort() + if (clr != null) { + if (clr != FormatHandle.COLORTABLE[t]) { // no exact match for color + if (fill == null) + fill = Fill(fillPattern, t, FormatHandle.colorToHexString(clr), icvBack.toInt(), null, this.workBook!!.theme) + else + fill!!.setFgColor(t, FormatHandle.colorToHexString(clr)) + } + } else if (fill != null) + fill!!.setFgColor(t) + this.updateColors() + } + + /** + * set the Background Color for this Format (when PATTERN - fls != PATTERN_SOLID) + * When PATTERN is PATTERN_SOLID, == 64 + * + * @param clr java.awt.Color or null if use standard Excel 2003 Color Table + * @param t best-match index into 2003-style Color table + */ + fun setBackColor(t: Int, clr: Color?) { + icvColorFlag = 0 + icvColorFlag = icvColorFlag or icvFore + icvColorFlag = icvColorFlag or (t.toShort() shl 7).toShort() + if (clr != null) { + if (clr != FormatHandle.COLORTABLE[t]) { // no exact match for color - store custom color + if (fill == null) + fill = Fill(fillPattern, icvFore.toInt(), null, t, FormatHandle.colorToHexString(clr), this.workBook!!.theme) + else + fill!!.setBgColor(t, FormatHandle.colorToHexString(clr)) + } + } else if (fill != null) + fill!!.setBgColor(t) + + this.updateColors() + } + + internal fun updateColors() { + val rkdata = this.getData() + val nef = ByteTools.shortToLEBytes(icvColorFlag) + rkdata[18] = nef[0] + rkdata[19] = nef[1] + icvFore = (icvColorFlag and 0x7F).toShort() + icvBack = (icvColorFlag and 0x3F80 shr 7).toShort() + // update format cache upon change + pat = null + this.wkbook!!.updateFormatCache(this) + } + + /** + * Sets the fill pattern to solid, which renders the background to 64=="the default fg color" + * "If the fill style is solid: When solid is specified, the + * foreground color (fgColor) is the only color rendered, + * even when a background color (bgColor) is also + * specified" + */ + fun setBackgroundSolid() { + setPattern(PATTERN_SOLID) + setBackColor(64, null) + if (fill != null) fill!!.setFillPattern(PATTERN_SOLID) + } + + + /** + * Sets the attribute flags for this xf record. These flags consist of + * // bit 8= fAtrProt + * // 7= fAtrPat + * // 6= fAtrBdr + * // 5= fAtrAlc (Alignment) + * // 4= fAtrFnt + * // 3= fAtrNum + */ + private fun setAttributeFlag() { + setToCellXF() + val rkdata = this.getData() + var used_attrib = rkdata!![9] + val borderFlag = (if (dgBottom > 0 || dgTop > 0 || dgLeft > 0 || dgRight > 0 || diagBorderLineStyle > 0) 1 else 0).toByte() // if border is set + if (borderFlag.toInt() == 1) + used_attrib = (used_attrib or 0x20).toByte() // set bit # 6 + else + used_attrib = (used_attrib and 0xDF).toByte() // clear it + if (cIndent.toInt() != 0 || iReadingOrder.toInt() != 0 || alc.toInt() != 0 || alcV.toInt() != 0 || fWrap.toInt() != 0 || trot.toInt() != 0) + // set bit # 5 + used_attrib = (used_attrib or 0x10).toByte() + else + used_attrib = (used_attrib and 0xEF).toByte() // clear it + + rkdata[9] = used_attrib + fAtrNum = (if (used_attrib and 0x04 == 0x04) 1 else 0).toShort() + fAtrFnt = (if (used_attrib and 0x08 == 0x08) 1 else 0).toShort() + fAtrAlc = (if (used_attrib and 0x10 == 0x10) 1 else 0).toShort() + fAtrBdr = (if (used_attrib and 0x20 == 0x20) 1 else 0).toShort() + fAtrPat = (if (used_attrib and 0x40 == 0x40) 1 else 0).toShort() + fAtrProt = (if (used_attrib and 0x80 == 0x80) 1 else 0).toShort() + + + // must set color flag for borders or Excel will not like [BugTracker 2861] + if (dgTop > 0 && icvTop.toInt() == 0) + icvTop = 64 + if (dgBottom > 0 && icvBottom.toInt() == 0) + icvBottom = 64 + if (dgRight > 0 && icvRight.toInt() == 0) + icvRight = 64 + if (dgLeft > 0 && icvLeft.toInt() == 0) + icvLeft = 64 + if (diagBorderLineStyle > 0 && icvDiag.toInt() == 0) + icvDiag = 64 + this.updatePattern() + } + + /** + * Switch the record to a cell XF record + */ + fun setToCellXF() { + if (fStyle.toInt() != 0) {// must set to cell xf (fStyle==0) as changes will not show [BugTracker 2861] + fStyle = 0 + var flag = fLocked.toByte() + flag = (flag or (fHidden shl 1)).toByte() + this.getData()[4] = flag + this.getData()[5] = 0 // upper bits are style parent rec index + } + } + + private fun updateAlignment() { + //short tempAlc = (short)(alc << 3); + val tempfWrap = (fWrap shl 3).toShort() + val tempAlcV = (alcV shl 4).toShort() + val tempTrot = (trot shl 8).toShort() + var res: Short = 0x0 + res = (res or alc).toShort() + res = (res or tempfWrap).toShort() + res = (res or tempAlcV).toShort() + res = (res or tempTrot).toShort() + val rkdata = this.getData() + val bords = ByteTools.shortToLEBytes(res) + rkdata[6] = bords[0] + rkdata[7] = bords[1] + // update format cache upon change + this.wkbook!!.updateFormatCache(this) + } + + /** + * @return Returns the myFormat. + */ + fun getFormat(): Format? { + return myFormat + } + + /** + * @param myFormat The myFormat to set. + */ + fun setFormat(myFormat: Format) { + this.myFormat = myFormat + } + + + /** + * 2 2 XF type, cell protection, and parent style XF: + * Bit Mask Contents + * 2-0 0007H XF_TYPE_PROT – XF type, cell protection (see above) + * 15-4 FFF0H Index to parent style XF (always FFFH in style XFs) + * + * + * Bit Mask Contents + * 0 01H 1 = Cell is locked + * 1 02H 1 = Formula is hidden + * 2 04H 0 = Cell XF; 1 = Style XF + */ + private fun updateLockedHidden() { + + val tempFL = (fLocked shl 0x0).toShort() + val tempFH = (fHidden shl 0x1).toShort() + val tempST = (fStyle shl 0x2).toShort() + + var flag: Short = 0x0 + flag = (flag or tempFL).toShort() + flag = (flag or tempFH).toShort() + flag = (flag or tempST).toShort() + + val dx = this.getData() + val nef = ByteTools.shortToLEBytes(flag) + dx[4] = nef[0] + dx[5] = nef[1] + // update format cache upon change + pat = null + this.wkbook!!.updateFormatCache(this) + } + + /** + * set the OOXML fill for this xf + * + * @param f + */ + fun setFill(f: Fill) { + this.fill = f.cloneElement() as Fill + fls = this.fill!!.fillPatternInt.toShort() + icvFore = this.fill!!.getFgColorAsInt(workBook!!.theme).toShort() + icvBack = this.fill!!.getBgColorAsInt(workBook!!.theme).toShort() + } + + /** + * return the OOXML fill for this xf, if any + */ + fun getFill(): Fill? { + return this.fill + } + + /** + * clear out object references in prep for closing workbook + */ + override fun close() { + super.close() + this.myFont!!.close() + this.myFormat = null + } + + companion object { + + private val serialVersionUID = -419388613530529316L + val NDEFAULTXFS = 20 + + fun isDatePattern(myfmt: String): Boolean { + // Search for the format string in the list of known date formats + for (x in FormatConstants.DATE_FORMATS.indices) { + if (FormatConstants.DATE_FORMATS[x][0] == myfmt) { + return true + } + } + + // check for string patterns that only exist within date records (as far as we know, may need refining) + return myfmt.indexOf("mm") > -1 || myfmt.indexOf("yy") > -1 || myfmt.indexOf("dd") > -1 + } + + /** + * Parses an escaped xml format pattern (from ooxml) and returns an io.starter.OpenXLS compatible + * pattern. + * + * + * This method certainly has weaknesses, but my intention is that if it is not a fairly standard format and/or + * we are not sure how to parse it we should leave the existent format intact soas to not break read/write operations + * + * + * Oddly enough, excel seems to be able to handle biff8 patterns, in my testing so far there has been no need + * to reencode, that could obviously change... + * + * @param xmlFormatPattern + * @return compatible biff8 formatPattern + */ + fun unescapeFormatPattern(xmlFormatPattern: String): String { + var xmlFormatPattern = xmlFormatPattern + // strip escaping for currency pattern. Probably should explore all currency types and do an iteration + xmlFormatPattern = xmlFormatPattern.replace("\"$\"", "$") + + // separator between positive/negative + xmlFormatPattern = xmlFormatPattern.replace("_);", ";") + + // unescape parens + xmlFormatPattern = xmlFormatPattern.replace("\\(", "(") + xmlFormatPattern = xmlFormatPattern.replace("\\)", ")") + return xmlFormatPattern + } + + /** + * Ensures that the given format pattern exists on the given workbook. + * + * @param book the workbook to which the pattern should belong + * @param pattern the number format pattern to ensure exists + * @return the format ID of the given format pattern + */ + fun addFormatPattern(book: WorkBook, pattern: String?): Short { + var ifmt: Short = -1 + + // Look up the pattern on the workbook + ifmt = book.getFormatId(pattern!!) + + // If the pattern is unknown, create and add a Format record + if (ifmt.toInt() == -1) { + val format = Format(book, pattern) + ifmt = format.ifmt + } + + return ifmt + } + + /** + * PATTERN_SOLID is a special case where icvFore= the background color and icvBack=64. + * "If the fill style is solid: When solid is specified, the + * foreground color (fgColor) is the only color rendered, + * even when a background color (bgColor) is also + * specified" + */ + val PATTERN_SOLID = 1 // was set to 4 but tht's wrong!! + + /** + * clone the xf and add to streamer + * + * @param xf + * @return + */ + private fun cloneXf(xf: Xf, wkbook: WorkBook): Xf { + val clone: Xf + if (xf.idx > -1) { // it's in the wb already + clone = Xf(xf.ifnt.toInt(), wkbook) + val xfbytes = xf.getBytesAt(0, xf.length - 4) + clone.setData(xfbytes) + clone.init() + } else { // xf hasn't been added to wb yet, no need to clone + clone = xf + } + clone.fill = xf.fill + clone.setToCellXF() // changes will not be seen if fstyle bit is set TODO: is this correct in all cases??? + clone.idx = wkbook.insertXf(clone) + return clone + } + + /** + * if xf parameter doesn't exist, create; if it does, create a new xf based on it + * + * @param xf original xf + * @param fontIdx font to link xf to + * @param wkbook + * @return new xf + */ + fun updateXf(xf: Xf?, fontIdx: Int, wkbook: WorkBook): Xf? { + var xf = xf + if (xf == null) { + xf = Xf(fontIdx, wkbook) + xf.idx = wkbook.insertXf(xf) // insert new xf into stream ... + return xf + } else { + xf = Xf.cloneXf(xf, wkbook) + } + return xf + } + } + +} + diff --git a/src/main/java/io/starter/formats/XLS/charts/Ai.java b/src/main/java/io/starter/formats/XLS/charts/Ai.java deleted file mode 100644 index 833bde7..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Ai.java +++ /dev/null @@ -1,817 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.*; -import io.starter.formats.XLS.formulas.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; - -import java.util.Iterator; -import java.util.List; -import java.util.Stack; - -/** - * Ai: Linked Chart Data (1051h)
                      - *

                      - * This record specifies linked series data or text. - *

                      - * Offset Name Size Contents - * -- - * 4 id 1 index id: (0=title or text, 1=series vals, 2=series cats + (3=bubbles) - * 5 rt 1 reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) - * 6 grbit 2 flags - * 8 ifmt 2 Index to number format (used if fCustomIfmt= true) - * 10 cce 2 size of rgce (in bytes) - * 12 rgce var Parsed formula of link - *

                      - * The grbit field contains the following option flags. - *

                      - * 0 0 0x1 fCustomIfmt TRUE if this object has a custom number format; FALSE if number format is linked to data source - * a 1 0x2 (reserved) Reserved; must be zero - * 0 5-2 0x3C st Source type (always zero) - * a 7-6 0xCO (reserved) Reserved; must be zero - * 1 7-0 0xFF (reserved) Reserved; must be zero - * - * - * - * @see Formula - * @see Chart - */ - -public final class Ai extends GenericChartObject implements ChartObject { - /** - * - */ - private static final long serialVersionUID = -6647823755603289012L; - private Stack expression; - protected int id = -1, ifmt = -1, cce = -1; - private short grbit = -1, rt = -1; - private boolean fCustomIfmt = false; - // private CompatibleVector xlsrecs = new - // CompatibleVector(); - private SeriesText st = null; - // define the type of Ai record - public static final int TYPE_TEXT = 0; - public static final int TYPE_VALS = 1; - public static final int TYPE_CATEGORIES = 2; - public static final int TYPE_BUBBLES = 3; - - /** - * This is for storage of the boundsheet name when the ai record is being moved from - * one workbook to another. In these cases, populate this value, and pull it back out - * to locate the cxti and reset the reerences - * - * @return the name of the original boundsheet this record is associated with. - */ - private String boundName = ""; - private String origSheetName = ""; - private int boundXti = -1; - - /** - * returns the bound sheet name - must be called after populateForTransfer - * - * @return String - */ // 20080116 KSC: Changed name for clarity - public String getBoundName() { - return boundName; - } - - /** - * returns the sheet reference index - must call after populateForTransfer - * - * @return - */ - public int getBoundXti() { - return boundXti; - } - - /** - * Sets the boundsheet name for the data referenced in this AI. - * + sets the xti of the boundsheet reference (necessary upon sheet copy/move) - * Does not currently support multi-boundsheet references. - * - * @see updateSheetRef - */ - public void populateForTransfer(String origSheetName) { - if ("".equals(boundName)) { - this.origSheetName = origSheetName; // 20080708 KSC: trap original - // sheet name for - // updateSheetRefs comparison - for (int t = 0; t < expression.size(); t++) { - Ptg p = expression.get(t); - if (p instanceof PtgArea3d) { - PtgArea3d pt = (PtgArea3d) p; - try { - boundName = pt.getSheetName(); // original boundname + - // xti sheet ref - boundXti = pt.getIxti(); - } catch (Exception e) { - Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); - } - } else if (p instanceof PtgRef3d) { - PtgRef3d pt = (PtgRef3d) p; - try { - boundName = pt.getSheetName(); // original boundname + - // xti sheet ref - boundXti = pt.getIxti(); - } catch (Exception e) { - Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); - } - } else if (p instanceof PtgMemFunc) { // 20091015 KSC: for - // non-contiguous ranges - Ptg ptg = ((PtgMemFunc) p).getFirstloc(); - if (ptg instanceof PtgRef3d) { - PtgRef3d pr = (PtgRef3d) ptg; - try { - boundName = pr.getSheetName(); // original boundname - // + xti sheet ref - boundXti = pr.getIxti(); - } catch (Exception e) { - Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); - } - } else { // should be a PtgArea3d - PtgArea3d pr = (PtgArea3d) ptg; - try { - boundName = pr.getSheetName(); // original boundname - // + xti sheet ref - boundXti = pr.getIxti(); - } catch (Exception e) { - Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources"); - } - } - } - } - } - } - - public void setLegend(String newLegend) { - this.setRt(1); - st.setText(newLegend); - expression = null; // if setting to a string, no expression! - } - - /** - * Refer to the associated Series Text. - * - * @param txt - * @return - */ - public boolean setText(String txt) { - try { - // XLSRecord x = getRecord(0); - // ((SeriesText)x ).setText(txt); - st.setText(txt); - return true; - } catch (ClassCastException e) { - // Logger.logInfo("Error getting Chart String value: " + e); - return false; - } - } - - public String getText() { - try { - // XLSRecord x = getRecord(0); - // if(x instanceof SeriesText)return ((SeriesText)x - // ).toString(); - if (st != null) - return st.toString(); - } catch (Exception e) { - if (DEBUGLEVEL > 0) - Logger.logWarn("Error getting Chart String value: " + e); - return getDefinition(); - } - // TODO: figure out why this doesn't find the title -- see - // "reportS01Template.xls" - return "undefined"; - } - - @Override - public String toString() { - switch (id) { - case Ai.TYPE_TEXT: - return getText(); - case Ai.TYPE_VALS: - return getDefinition(); - case Ai.TYPE_CATEGORIES: - return getDefinition(); - case Ai.TYPE_BUBBLES: - return getDefinition(); - } - return super.toString(); - } - - // public void addRecord(BiffRec rec){ - // xlsrecs.add(rec); - // } - public void setSeriesText(SeriesText s) { - st = s; - } - - // protected XLSRecord getRecord(int i){ - // return (XLSRecord) xlsrecs.get(i); - // } - - /** - * set the Externsheet reference - * for any associated PtgArea3d's - */ - public void setExternsheetRef(int x) throws WorkSheetNotFoundException { - byte[] dt = this.getData(); - int pos = 8; - - for (int t = 0; t < expression.size(); t++) { - Ptg p = expression.get(t); - if (p instanceof PtgArea3d) { - PtgArea3d pt = (PtgArea3d) p; - pt.setIxti((short) x); - pt.addToRefTracker(); - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pt.toString() + " in Ai record."); - // register with the Externsheet reference - this.getWorkBook().getExternSheet().addPtgListener(pt); - updateRecord(); - } else if (p instanceof PtgRef3d) { // 20091015 KSC: Added - PtgRef3d pr = (PtgRef3d) p; - pr.setIxti((short) x); - pr.addToRefTracker(); - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pr.toString() + " in Ai record."); - // register with the Externsheet reference - this.getWorkBook().getExternSheet().addPtgListener(pr); - updateRecord(); - } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added - Ptg pr = ((PtgMemFunc) p).getFirstloc(); - if (pr instanceof PtgRef3d) { - ((PtgRef3d) pr).setIxti((short) x); - ((PtgRef3d) pr).addToRefTracker(); - this.getWorkBook().getExternSheet() - .addPtgListener((PtgRef3d) pr); - } else { // should be a PtgArea3d - ((PtgArea3d) pr).setIxti((short) x); - ((PtgArea3d) pr).addToRefTracker(); - this.getWorkBook().getExternSheet() - .addPtgListener((PtgArea3d) pr); - } - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pr.toString() + " in Ai record."); - // register with the Externsheet reference - updateRecord(); - } else { - Logger.logInfo("Ai.setExternsheetRef: unknown Ptg"); - } - } - } - - /** - * set the Externsheet reference - * for any associated PtgArea3d's that match the old reference. - *

                      - * invaluble for modifying only one sheets worth of references (ie a move sheet situation) - */ - public void setExternsheetRef(int oldRef, int newRef) throws WorkSheetNotFoundException { - for (int t = 0; t < expression.size(); t++) { - Ptg p = expression.get(t); - if (p instanceof PtgArea3d) { - PtgArea3d pt = (PtgArea3d) p; - int oRef = pt.getIxti(); - if (oRef == oldRef) { // got the one to update - pt.removeFromRefTracker(); // 20100506 KSC: added - pt.setSheetName(this.getSheet().getSheetName()); // 20100415 - // KSC: - // added - pt.setIxti((short) newRef); - pt.addToRefTracker(); // 20080709 KSC - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pt.toString() + " in Ai record."); - // register with the Externsheet reference - this.getWorkBook().getExternSheet().addPtgListener(pt); - updateRecord(); - } - } else if (p instanceof PtgRef3d) { - PtgRef3d pr = (PtgRef3d) p; - int oRef = pr.getIxti(); - if (oRef == oldRef) { - pr.removeFromRefTracker(); - pr.setSheetName(this.getSheet().getSheetName()); // 20100415 - // KSC: - // added - pr.setIxti((short) newRef); - if (!pr.getIsRefErr()) - pr.addToRefTracker(); - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pr.toString() + " in Ai record."); - // register with the Externsheet reference - this.getWorkBook().getExternSheet().addPtgListener(pr); - updateRecord(); - } - } else if (p instanceof PtgMemFunc) { // 20091015 KSC: Added - Ptg pr = ((PtgMemFunc) p).getFirstloc(); - if (pr instanceof PtgRef3d) { - int oRef = ((PtgRef3d) pr).getIxti(); - if (oRef == oldRef) { - ((PtgRef3d) pr).removeFromRefTracker(); // 20100506 KSC: - // added - ((PtgArea3d) pr) - .setSheetName(this.getSheet().getSheetName()); // 20100415 - // KSC: - // added - ((PtgRef3d) pr).setIxti((short) newRef); - ((PtgRef3d) pr).addToRefTracker(); - this.getWorkBook().getExternSheet() - .addPtgListener((PtgRef3d) pr); - } - } else { // should be a PtgArea3d - int oRef = ((PtgRef3d) pr).getIxti(); - if (oRef == oldRef) { - ((PtgRef3d) pr).removeFromRefTracker(); // 20100506 KSC: - // added - ((PtgArea3d) pr) - .setSheetName(this.getSheet().getSheetName()); // 20100415 - // KSC: - // added - ((PtgArea3d) pr).setIxti((short) newRef); - ((PtgArea3d) pr).addToRefTracker(); - this.getWorkBook().getExternSheet() - .addPtgListener((PtgArea3d) pr); - } - } - if (DEBUGLEVEL > 3) - Logger.logInfo("Setting sheet reference for: " - + pr.toString() + " in Ai record."); - // register with the Externsheet reference - updateRecord(); - } else if (p instanceof PtgMystery) { - // TODO: do what??? - } - } - } - - /** - * take the original boundSheet + boundSheet xti reference and update it to - * the sheet reference in the new workbook (or same workbook but different ixti reference) - */ - public void updateSheetRef(String newSheetName, String origWorkBookName) { - try { - // 20080630/20080708 KSC: Fixes for [BugTracker 1799] + - // [BugTracker 1434] - if (boundXti > -1) { // has populate for transfer been called - - // should! - int newSheetNum = -1; - try { - if (!boundName.equalsIgnoreCase(origSheetName)) { // Ai - // reference - // is on - // a - // dfferent - // sheet, - // see - // if it - // exists - // in - // new - // workbook - newSheetNum = this.getWorkBook() - .getWorkSheetByName(boundName).getSheetNum(); - } else // Ai reference is on same sheet, point now to new - // sheet - newSheetNum = this.getWorkBook() - .getWorkSheetByName(newSheetName).getSheetNum(); - } catch (Exception e) { // 20080123 KSC: if links arent there, - // fix == try to make an External ref - for (int t = 0; t < expression.size(); t++) { - if (expression.get(t) instanceof PtgArea3d) { - PtgArea3d p = (PtgArea3d) expression.get(t); - Logger.logWarn("External References are unsupported: External reference found in Chart: " - + p.getSheetName()); - p.setSheetName(boundName); // set external reference - // to original - // boundsheet name - p.setExternalReference(origWorkBookName); - this.setExternsheetRef(p.getIxti()); - } else { - Logger.logInfo("Ai.updateSheetRef:"); - } - } - } - if (newSheetNum != -1) { - this.setSheet(this.getWorkBook() - .getWorkSheetByName(newSheetName)); // 20100415 KSC: - // set Ai sheet - // ref to new - // sheet - Externsheet xsht = this.getWorkBook().getExternSheet(true); // create - // if - // necessary - int newXRef = xsht.insertLocation(newSheetNum, newSheetNum); - this.setExternsheetRef(boundXti, newXRef); - boundXti = newXRef; // 20100506 KSC: reset - boundName = newSheetName; // "" - } - } else {// debugging 20100415 - // Logger.logErr("Ai.updateSheetRef: boundxti is -1 for AI " - // + this.toString()); - } - } catch (Exception e) { - Logger.logErr("Ai.updateSheetRef: " + e.toString()); - } - } - - /** - * get the display name - */ - String getName() { - return "Chart Ai"; - } - - /** - * get the definition text - * the definition is stored - * in Excel parsed format - */ - public String getDefinition() { - StringBuffer sb = new StringBuffer(); - Ptg[] ep = new Ptg[expression.size()]; - ep = expression.toArray(ep); - for (int t = 0; t < ep.length; t++) { - if (!(ep[t] instanceof PtgParen)) // 20091019 KSC: if complex - // series, will have a PtgParen - // after PtgMemFunc; - /* - * if (ep[t] instanceof PtgMemFunc) - * sb.append("(" + ep[t].getString() + ")"); - * else - */ - sb.append(ep[t].getString()); - } - return sb.toString(); - } - - /* - * Returns an array of ptgs that represent any BiffRec - * ranges in the formula. - * Ranges can either be in the format "C5" or "Sheet1!C4:D9" - */ - public Ptg[] getCellRangePtgs() throws FormulaNotFoundException { - return ExpressionParser.getCellRangePtgs(expression); - } - - /** - * Return the type (ID) of this Ai record - * - * @return int rt - */ - public int getType() { - return id; - } - - /** - * return the custom number format for this AI (category, series or bubble) - *
                      if 0, use default number format as specified by Axis or Chart - * - * @return - */ - public int getIfmt() { - return ifmt; - } - - /* - * Returns the ptg that matches the string location sent to - * it. - * this can either be in the format "C5" or a range, such as - * "C4:D9" - */ - public List getPtgsByLocation(String loc) { - try { - return ExpressionParser.getPtgsByLocation(loc, expression); - } catch (FormulaNotFoundException e) { - Logger.logWarn("failed to update Chart Series Location: " + e); - } - return null; - } - - /** - * locks the Ptg at the specified location - */ - - public boolean setLocationPolicy(String loc, int l) { - List dx = this.getPtgsByLocation(loc); - Iterator lx = dx.iterator(); - while (lx.hasNext()) { - Ptg d = lx.next(); - if (d != null) { - d.setLocationPolicy(l); - } - } - return true; - } - - /** - * simplified version of changeAiLocation which locates current Ptg and updates expression - * NOTE: newLoc is expected to be a valid reference - * - * @param p - * @param newLoc - * @return - */ - public boolean changeAiLocation(Ptg p, String newLoc) { - String[] aiLocs = StringTool.splitString(newLoc, ","); - for (int i = 0; i < aiLocs.length; i++) { - try { // NOTE: Ai has only 1 expression OR 2: 1= PtgMemFunc - // 2=PtgParen - // looking up via getExpressionLocByPtg errors if loc is the - // same but Ptg's are different objects eg. PtgRef3d vs - // PtgArea3d ... - if (expression.get(0) instanceof PtgMemFunc) { - try { // must find particular ptg in PtgMemFunc's - // subexression to reset - // APPARENTLY WE DO NOT NEED TO UDPATE PTGMEMFUNC - // SUBEXPRESSION EXPLICITLY - // int z= ExpressionParser.getExpressionLocByPtg(p, - // ((PtgMemFunc) - // expression.get(0)).getSubExpression()); - // Stack subexp= ((PtgMemFunc) - // expression.get(0)).getSubExpression(); - // for (int z= 0; z < subexp.size(); z++) { - // if (p.equals(subexp.get(z))) { - p.setLocation(aiLocs[i]); // updates ref. tracker - // ((PtgMemFunc) - // expression.get(0)).getSubExpression().set(z, p); // - // update expression with new Ptg - // } - // } - } catch (Exception ex) { - Logger.logErr("Ai.changeAiLocation: Error updating Location in non-contiguous range " - + ex.toString()); - } - } else { - p.setLocation(aiLocs[i]); // updates ref. tracker - expression.set(0, p); // update expression with new Ptg - if (this.getType() == Ai.TYPE_TEXT) { // must reset text for - // SeriesText as - // well - try { - Object o = p.getValue(); - this.setText(o.toString()); - } catch (Exception e) { - } - } - } - } catch (Exception e) { - Logger.logErr("Ai.changeAiLocation: Error updating Location to " - + newLoc + ":" + e.toString()); - return false; - } - } - updateRecord(); - return true; - } - - /** - * Takes a string as a current formula location, and changes - * that pointer in the formula to the new string that is sent. - * This can take single cells "A5" and cell ranges, "A3:d4" - * Returns true if the cell range specified in formulaLoc exists & can be changed - * else false. This also cannot change a cell pointer to a cell range or vice - * versa. - * - * @param String - range of Cells within Formula to modify - * @param String - new range of Cells within Formula - */ - public boolean changeAiLocation(String loc, String newLoc) { - // TODO: Implement formula policy!! -jm - Ptg ptg = null; - int z = -1; - try { - if (expression.size() > 0) { - z = ExpressionParser - .getExpressionLocByLocation(loc, expression); - ptg = expression.get(z); // 20090917 KSC: since creating - // new ptgs below, must remove - // original from reftracker "by - // hand" - } - if (ptg != null) - ((PtgRef) ptg).removeFromRefTracker(); - } catch (Exception e) { - } - if (z == -1 && newLoc.equals("")) {// no reference -- happens on - // legends, category ai's ... - this.getData()[1] = 1; // text reference rather than worksheet - // reference - return false; - } - ptg = PtgRef.createPtgRefFromString(newLoc, this); - if (z != -1) // then must change original - expression.set(z, ptg); // update expression with new Ptg - else - expression.add(ptg); - updateRecord(); - return true; - } - - /* - * Update the record byte array with the modified ptg - * records - */ - public void updateRecord() { - int offy = 8; // the start of the parsed expression - byte[] rkdata = this.getData(); - byte[] updated = new byte[rkdata.length]; - System.arraycopy(rkdata, 0, updated, 0, offy); - for (int i = 0; i < expression.size(); i++) { - Object o = expression.elementAt(i); - Ptg ptg = (Ptg) o; - byte[] b = ptg.getRecord(); - // must inc. size if Ptgs have inc.'d ... see - // changeAiLocation - int len = b.length; - if ((updated.length - offy) < len) { - byte[] newArr = new byte[offy + len]; - System.arraycopy(updated, 0, newArr, 0, updated.length); - // update cce in array as well ... - cce += (newArr.length - updated.length); - updated = newArr; - byte[] ix = ByteTools.shortToLEBytes((short) cce); - System.arraycopy(ix, 0, updated, 6, 2); - } - System.arraycopy(b, 0, updated, offy, len); - offy = offy + len; - } - this.setData(updated); - } - - @Override - public void init() { - super.init(); - id = this.getByteAt(0); - // index id: (0=title or text, 1=series vals, 2=series cats, - // 3= bubbles - rt = this.getByteAt(1); - // reference type(0=default,1=text in formula bar, - // 2=worksheet, 4=error) - grbit = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - setfCustomIfmt((grbit & 0x1) == 0x1); - // flags - ifmt = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - // Index to number format - cce = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - // size of rgce (in bytes) - int pos = 8; - // Parsed formula of link - - // get the parsed expression - byte[] expressionbytes = this.getBytesAt(pos, cce); - expression = ExpressionParser.parseExpression(expressionbytes, this); - pos += cce; - if (DEBUGLEVEL > 10) - Logger.logInfo(this.getName() + ":" + this.getDefinition()); - } - - /** - * set reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) - * - * @param i - */ - public void setRt(int i) { - rt = (short) i; - this.getData()[1] = (byte) rt; - } - - /** - * Get a prototype with the specified ai types. - *

                      - * 0 = Legend AI - * 1= Series Value Ai - * 2 = Category Ai - * 3 = Unknown, undocumented, but neccesarry AI - * 4 = Blank Legend AI with no reference. - */ - public static ChartObject getPrototype(byte[] aiType) { - Ai ai = new Ai(); - ai.setOpcode(AI); - ai.setData(aiType); - ai.init(); - return ai; - } - - // 20070801 KSC: since changeAiLocation now allows addition - // of new expression bytes, alter - // default prototype bytes here to not include any - // expression bytes .. - protected static byte[] AI_TYPE_LEGEND = new byte[]{0, 2, 0, 0, 0, 0, - 0, 0}; // , - // 7, - // 0, - // 58, - // 0, - // 0, - // 0, - // 0, - // 0, - // 0}; - protected static byte[] AI_TYPE_SERIES = new byte[]{1, 2, 0, 0, 0, 0, - 0, 0}; // , - // 11, - // 0, - // 59, - // 0, - // 0, - // 1, - // 0, - // 1, - // 0, - // 1, - // 0, - // 3, - // 0}; - protected static byte[] AI_TYPE_CATEGORY = new byte[]{2, 2, 0, 0, 0, 0, - 0, 0}; // 11, - // 0, - // 59, - // 0, - // 0, - // 0, - // 0, - // 0, - // 0, - // 1, - // 0, - // 3, - // 0}; - protected static byte[] AI_TYPE_BUBBLE = new byte[]{3, 1, 0, 0, 0, 0, - 0, 0}; - protected static byte[] AI_TYPE_NULL_LEGEND = new byte[]{0, 1, 0, 0, 0, 0, - 0, 0}; - - public Stack getExpression() { - return expression; - } - - @Override - public void close() { - if (expression != null) { - while (!expression.isEmpty()) { - GenericPtg p = (GenericPtg) expression.pop(); - if (p instanceof PtgRef) - p.close(); - else - p.close(); - p = null; - } - } - super.close(); - } - - @Override - protected void finalize() { - this.close(); - } - - /** - * @return the fCustomIfmt - */ - public boolean isfCustomIfmt() { - return fCustomIfmt; - } - - /** - * @param fCustomIfmt the fCustomIfmt to set - */ - public void setfCustomIfmt(boolean fCustomIfmt) { - this.fCustomIfmt = fCustomIfmt; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/Ai.kt b/src/main/java/io/starter/formats/XLS/charts/Ai.kt new file mode 100644 index 0000000..0e7f261 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Ai.kt @@ -0,0 +1,811 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.* +import io.starter.formats.XLS.formulas.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import java.util.Stack + +/** + * **Ai: Linked Chart Data (1051h)**

                      + * + * + * This record specifies linked series data or text. + * + * + * Offset Name Size Contents + * -- + * 4 id 1 index id: (0=title or text, 1=series vals, 2=series cats + (3=bubbles) + * 5 rt 1 reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) + * 6 grbit 2 flags + * 8 ifmt 2 Index to number format (used if fCustomIfmt= true) + * 10 cce 2 size of rgce (in bytes) + * 12 rgce var Parsed formula of link + * + * + * The grbit field contains the following option flags. + * + * + * 0 0 0x1 fCustomIfmt TRUE if this object has a custom number format; FALSE if number format is linked to data source + * a 1 0x2 (reserved) Reserved; must be zero + * 0 5-2 0x3C st Source type (always zero) + * a 7-6 0xCO (reserved) Reserved; must be zero + * 1 7-0 0xFF (reserved) Reserved; must be zero + * + * + * + * @see Formula + * + * @see Chart + */ + +class Ai : GenericChartObject(), ChartObject { + private var expression: Stack? = null + /** + * Return the type (ID) of this Ai record + * + * @return int rt + */ + var type = -1 + protected set + /** + * return the custom number format for this AI (category, series or bubble) + *

                      if 0, use default number format as specified by Axis or Chart + * + * @return + */ + var ifmt = -1 + protected set + protected var cce = -1 + private var grbit: Short = -1 + private var rt: Short = -1 + private var fCustomIfmt = false + // private CompatibleVector xlsrecs = new + // CompatibleVector(); + private var st: SeriesText? = null + + /** + * This is for storage of the boundsheet name when the ai record is being moved from + * one workbook to another. In these cases, populate this value, and pull it back out + * to locate the cxti and reset the reerences + * + * @return the name of the original boundsheet this record is associated with. + */ + /** + * returns the bound sheet name - must be called after populateForTransfer + * + * @return String + */ // 20080116 KSC: Changed name for clarity + var boundName: String? = "" + private set + private var origSheetName = "" + /** + * returns the sheet reference index - must call after populateForTransfer + * + * @return + */ + var boundXti = -1 + private set + + // XLSRecord x = getRecord(0); + // if(x instanceof SeriesText)return ((SeriesText)x + // ).toString(); + // TODO: figure out why this doesn't find the title -- see + // "reportS01Template.xls" + val text: String + get() { + try { + if (st != null) + return st!!.toString() + } catch (e: Exception) { + if (DEBUGLEVEL > 0) + Logger.logWarn("Error getting Chart String value: $e") + return definition + } + + return "undefined" + } + + /** + * get the display name + */ + internal val name: String + get() = "Chart Ai" + + /** + * get the definition text + * the definition is stored + * in Excel parsed format + */ + // 20091019 KSC: if complex + // series, will have a PtgParen + // after PtgMemFunc; + /* + * if (ep[t] instanceof PtgMemFunc) + * sb.append("(" + ep[t].getString() + ")"); + * else + */ val definition: String + get() { + val sb = StringBuffer() + var ep = arrayOfNulls(expression!!.size) + ep = expression!!.toTypedArray() + for (t in ep.indices) { + if (ep[t] !is PtgParen) + sb.append(ep[t].string) + } + return sb.toString() + } + + /* + * Returns an array of ptgs that represent any BiffRec + * ranges in the formula. + * Ranges can either be in the format "C5" or "Sheet1!C4:D9" + */ + val cellRangePtgs: Array + @Throws(FormulaNotFoundException::class) + get() = ExpressionParser.getCellRangePtgs(expression!!) + + /** + * Sets the boundsheet name for the data referenced in this AI. + * + sets the xti of the boundsheet reference (necessary upon sheet copy/move) + * Does not currently support multi-boundsheet references. + * + * @see updateSheetRef + */ + fun populateForTransfer(origSheetName: String) { + if ("" == boundName) { + this.origSheetName = origSheetName // 20080708 KSC: trap original + // sheet name for + // updateSheetRefs comparison + for (t in expression!!.indices) { + val p = expression!![t] + if (p is PtgArea3d) { + try { + boundName = p.sheetName // original boundname + + // xti sheet ref + boundXti = p.getIxti().toInt() + } catch (e: Exception) { + Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources") + } + + } else if (p is PtgRef3d) { + try { + boundName = p.sheetName // original boundname + + // xti sheet ref + boundXti = p.getIxti().toInt() + } catch (e: Exception) { + Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources") + } + + } else if (p is PtgMemFunc) { // 20091015 KSC: for + // non-contiguous ranges + val ptg = p.firstloc + if (ptg is PtgRef3d) { + try { + boundName = ptg.sheetName // original boundname + // + xti sheet ref + boundXti = ptg.getIxti().toInt() + } catch (e: Exception) { + Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources") + } + + } else { // should be a PtgArea3d + val pr = ptg as PtgArea3d + try { + boundName = pr.sheetName // original boundname + // + xti sheet ref + boundXti = pr.getIxti().toInt() + } catch (e: Exception) { + Logger.logErr("Ai.populateForTransfer: Chart contains links to other data sources") + } + + } + } + } + } + } + + fun setLegend(newLegend: String) { + this.setRt(1) + st!!.setText(newLegend) + expression = null // if setting to a string, no expression! + } + + /** + * Refer to the associated Series Text. + * + * @param txt + * @return + */ + fun setText(txt: String): Boolean { + try { + // XLSRecord x = getRecord(0); + // ((SeriesText)x ).setText(txt); + st!!.setText(txt) + return true + } catch (e: ClassCastException) { + // Logger.logInfo("Error getting Chart String value: " + e); + return false + } + + } + + override fun toString(): String { + when (type) { + Ai.TYPE_TEXT -> return text + Ai.TYPE_VALS -> return definition + Ai.TYPE_CATEGORIES -> return definition + Ai.TYPE_BUBBLES -> return definition + } + return super.toString() + } + + // public void addRecord(BiffRec rec){ + // xlsrecs.add(rec); + // } + fun setSeriesText(s: SeriesText) { + st = s + } + + // protected XLSRecord getRecord(int i){ + // return (XLSRecord) xlsrecs.get(i); + // } + + /** + * set the Externsheet reference + * for any associated PtgArea3d's + */ + @Throws(WorkSheetNotFoundException::class) + fun setExternsheetRef(x: Int) { + val dt = this.data + val pos = 8 + + for (t in expression!!.indices) { + val p = expression!![t] + if (p is PtgArea3d) { + p.setIxti(x.toShort()) + p.addToRefTracker() + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + p.toString() + " in Ai record.") + // register with the Externsheet reference + this.workBook!!.externSheet!!.addPtgListener(p) + updateRecord() + } else if (p is PtgRef3d) { // 20091015 KSC: Added + p.setIxti(x.toShort()) + p.addToRefTracker() + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + p.toString() + " in Ai record.") + // register with the Externsheet reference + this.workBook!!.externSheet!!.addPtgListener(p) + updateRecord() + } else if (p is PtgMemFunc) { // 20091015 KSC: Added + val pr = p.firstloc + if (pr is PtgRef3d) { + pr.setIxti(x.toShort()) + pr.addToRefTracker() + this.workBook!!.externSheet!! + .addPtgListener(pr) + } else { // should be a PtgArea3d + (pr as PtgArea3d).setIxti(x.toShort()) + pr.addToRefTracker() + this.workBook!!.externSheet!! + .addPtgListener(pr) + } + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record.") + // register with the Externsheet reference + updateRecord() + } else { + Logger.logInfo("Ai.setExternsheetRef: unknown Ptg") + } + } + } + + /** + * set the Externsheet reference + * for any associated PtgArea3d's that match the old reference. + * + * + * invaluble for modifying only one sheets worth of references (ie a move sheet situation) + */ + @Throws(WorkSheetNotFoundException::class) + fun setExternsheetRef(oldRef: Int, newRef: Int) { + for (t in expression!!.indices) { + val p = expression!![t] + if (p is PtgArea3d) { + val oRef = p.getIxti().toInt() + if (oRef == oldRef) { // got the one to update + p.removeFromRefTracker() // 20100506 KSC: added + p.sheetName = this.sheet!!.sheetName // 20100415 + // KSC: + // added + p.setIxti(newRef.toShort()) + p.addToRefTracker() // 20080709 KSC + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + p.toString() + " in Ai record.") + // register with the Externsheet reference + this.workBook!!.externSheet!!.addPtgListener(p) + updateRecord() + } + } else if (p is PtgRef3d) { + val oRef = p.getIxti().toInt() + if (oRef == oldRef) { + p.removeFromRefTracker() + p.sheetName = this.sheet!!.sheetName // 20100415 + // KSC: + // added + p.setIxti(newRef.toShort()) + if (!p.isRefErr) + p.addToRefTracker() + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + p.toString() + " in Ai record.") + // register with the Externsheet reference + this.workBook!!.externSheet!!.addPtgListener(p) + updateRecord() + } + } else if (p is PtgMemFunc) { // 20091015 KSC: Added + val pr = p.firstloc + if (pr is PtgRef3d) { + val oRef = pr.getIxti().toInt() + if (oRef == oldRef) { + pr.removeFromRefTracker() // 20100506 KSC: + // added + (pr as PtgArea3d) + .sheetName = this.sheet!!.sheetName // 20100415 + // KSC: + // added + (pr as PtgRef3d).setIxti(newRef.toShort()) + (pr as PtgRef3d).addToRefTracker() + this.workBook!!.externSheet!! + .addPtgListener(pr as PtgRef3d) + } + } else { // should be a PtgArea3d + val oRef = (pr as PtgRef3d).getIxti().toInt() + if (oRef == oldRef) { + pr.removeFromRefTracker() // 20100506 KSC: + // added + (pr as PtgArea3d) + .sheetName = this.sheet!!.sheetName // 20100415 + // KSC: + // added + (pr as PtgArea3d).setIxti(newRef.toShort()) + (pr as PtgArea3d).addToRefTracker() + this.workBook!!.externSheet!! + .addPtgListener(pr as PtgArea3d) + } + } + if (DEBUGLEVEL > 3) + Logger.logInfo("Setting sheet reference for: " + + pr.toString() + " in Ai record.") + // register with the Externsheet reference + updateRecord() + } else if (p is PtgMystery) { + // TODO: do what??? + } + } + } + + /** + * take the original boundSheet + boundSheet xti reference and update it to + * the sheet reference in the new workbook (or same workbook but different ixti reference) + */ + fun updateSheetRef(newSheetName: String, origWorkBookName: String) { + try { + // 20080630/20080708 KSC: Fixes for [BugTracker 1799] + + // [BugTracker 1434] + if (boundXti > -1) { // has populate for transfer been called - + // should! + var newSheetNum = -1 + try { + if (!boundName!!.equals(origSheetName, ignoreCase = true)) { // Ai + // reference + // is on + // a + // dfferent + // sheet, + // see + // if it + // exists + // in + // new + // workbook + newSheetNum = this.workBook!! + .getWorkSheetByName(boundName).sheetNum + } else + // Ai reference is on same sheet, point now to new + // sheet + newSheetNum = this.workBook!! + .getWorkSheetByName(newSheetName).sheetNum + } catch (e: Exception) { // 20080123 KSC: if links arent there, + // fix == try to make an External ref + for (t in expression!!.indices) { + if (expression!![t] is PtgArea3d) { + val p = expression!![t] as PtgArea3d + Logger.logWarn("External References are unsupported: External reference found in Chart: " + p.sheetName!!) + p.sheetName = boundName // set external reference + // to original + // boundsheet name + p.setExternalReference(origWorkBookName) + this.setExternsheetRef(p.getIxti().toInt()) + } else { + Logger.logInfo("Ai.updateSheetRef:") + } + } + } + + if (newSheetNum != -1) { + this.setSheet(this.workBook!! + .getWorkSheetByName(newSheetName)) // 20100415 KSC: + // set Ai sheet + // ref to new + // sheet + val xsht = this.workBook!!.getExternSheet(true) // create + // if + // necessary + val newXRef = xsht!!.insertLocation(newSheetNum, newSheetNum) + this.setExternsheetRef(boundXti, newXRef) + boundXti = newXRef // 20100506 KSC: reset + boundName = newSheetName // "" + } + } else {// debugging 20100415 + // Logger.logErr("Ai.updateSheetRef: boundxti is -1 for AI " + // + this.toString()); + } + } catch (e: Exception) { + Logger.logErr("Ai.updateSheetRef: $e") + } + + } + + /* + * Returns the ptg that matches the string location sent to + * it. + * this can either be in the format "C5" or a range, such as + * "C4:D9" + */ + fun getPtgsByLocation(loc: String): List? { + try { + return ExpressionParser.getPtgsByLocation(loc, expression!!) + } catch (e: FormulaNotFoundException) { + Logger.logWarn("failed to update Chart Series Location: $e") + } + + return null + } + + /** + * locks the Ptg at the specified location + */ + + fun setLocationPolicy(loc: String, l: Int): Boolean { + val dx = this.getPtgsByLocation(loc) + val lx = dx!!.iterator() + while (lx.hasNext()) { + val d = lx.next() + if (d != null) { + d.locationPolicy = l + } + } + return true + } + + /** + * simplified version of changeAiLocation which locates current Ptg and updates expression + * NOTE: newLoc is expected to be a valid reference + * + * @param p + * @param newLoc + * @return + */ + fun changeAiLocation(p: Ptg, newLoc: String): Boolean { + val aiLocs = StringTool.splitString(newLoc, ",") + for (i in aiLocs.indices) { + try { // NOTE: Ai has only 1 expression OR 2: 1= PtgMemFunc + // 2=PtgParen + // looking up via getExpressionLocByPtg errors if loc is the + // same but Ptg's are different objects eg. PtgRef3d vs + // PtgArea3d ... + if (expression!![0] is PtgMemFunc) { + try { // must find particular ptg in PtgMemFunc's + // subexression to reset + // APPARENTLY WE DO NOT NEED TO UDPATE PTGMEMFUNC + // SUBEXPRESSION EXPLICITLY + // int z= ExpressionParser.getExpressionLocByPtg(p, + // ((PtgMemFunc) + // expression.get(0)).getSubExpression()); + // Stack subexp= ((PtgMemFunc) + // expression.get(0)).getSubExpression(); + // for (int z= 0; z < subexp.size(); z++) { + // if (p.equals(subexp.get(z))) { + p.location = aiLocs[i] // updates ref. tracker + // ((PtgMemFunc) + // expression.get(0)).getSubExpression().set(z, p); // + // update expression with new Ptg + // } + // } + } catch (ex: Exception) { + Logger.logErr("Ai.changeAiLocation: Error updating Location in non-contiguous range $ex") + } + + } else { + p.location = aiLocs[i] // updates ref. tracker + expression!![0] = p // update expression with new Ptg + if (this.type == Ai.TYPE_TEXT) { // must reset text for + // SeriesText as + // well + try { + val o = p.value + this.setText(o.toString()) + } catch (e: Exception) { + } + + } + } + } catch (e: Exception) { + Logger.logErr("Ai.changeAiLocation: Error updating Location to " + + newLoc + ":" + e.toString()) + return false + } + + } + updateRecord() + return true + } + + /** + * Takes a string as a current formula location, and changes + * that pointer in the formula to the new string that is sent. + * This can take single cells "A5" and cell ranges, "A3:d4" + * Returns true if the cell range specified in formulaLoc exists & can be changed + * else false. This also cannot change a cell pointer to a cell range or vice + * versa. + * + * @param String - range of Cells within Formula to modify + * @param String - new range of Cells within Formula + */ + fun changeAiLocation(loc: String, newLoc: String): Boolean { + // TODO: Implement formula policy!! -jm + var ptg: Ptg? = null + var z = -1 + try { + if (expression!!.size > 0) { + z = ExpressionParser + .getExpressionLocByLocation(loc, expression!!) + ptg = expression!![z] // 20090917 KSC: since creating + // new ptgs below, must remove + // original from reftracker "by + // hand" + } + if (ptg != null) + (ptg as PtgRef).removeFromRefTracker() + } catch (e: Exception) { + } + + if (z == -1 && newLoc == "") {// no reference -- happens on + // legends, category ai's ... + this.data[1] = 1 // text reference rather than worksheet + // reference + return false + } + ptg = PtgRef.createPtgRefFromString(newLoc, this) + if (z != -1) + // then must change original + expression!![z] = ptg // update expression with new Ptg + else + expression!!.add(ptg) + updateRecord() + return true + } + + /* + * Update the record byte array with the modified ptg + * records + */ + fun updateRecord() { + var offy = 8 // the start of the parsed expression + val rkdata = this.data + var updated = ByteArray(rkdata!!.size) + System.arraycopy(rkdata, 0, updated, 0, offy) + for (i in expression!!.indices) { + val o = expression!!.elementAt(i) + val ptg = o as Ptg + val b = ptg.record + // must inc. size if Ptgs have inc.'d ... see + // changeAiLocation + val len = b.size + if (updated.size - offy < len) { + val newArr = ByteArray(offy + len) + System.arraycopy(updated, 0, newArr, 0, updated.size) + // update cce in array as well ... + cce += newArr.size - updated.size + updated = newArr + val ix = ByteTools.shortToLEBytes(cce.toShort()) + System.arraycopy(ix, 0, updated, 6, 2) + } + System.arraycopy(b, 0, updated, offy, len) + offy = offy + len + } + this.data = updated + } + + override fun init() { + super.init() + type = this.getByteAt(0).toInt() + // index id: (0=title or text, 1=series vals, 2=series cats, + // 3= bubbles + rt = this.getByteAt(1).toShort() + // reference type(0=default,1=text in formula bar, + // 2=worksheet, 4=error) + grbit = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + setfCustomIfmt(grbit and 0x1 == 0x1) + // flags + ifmt = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()).toInt() + // Index to number format + cce = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()).toInt() + // size of rgce (in bytes) + var pos = 8 + // Parsed formula of link + + // get the parsed expression + val expressionbytes = this.getBytesAt(pos, cce) + expression = ExpressionParser.parseExpression(expressionbytes, this) + pos += cce + if (DEBUGLEVEL > 10) + Logger.logInfo(this.name + ":" + this.definition) + } + + /** + * set reference type(0=default,1=text in formula bar, 2=worksheet, 4=error) + * + * @param i + */ + fun setRt(i: Int) { + rt = i.toShort() + this.data[1] = rt.toByte() + } + + fun getExpression(): Stack<*>? { + return expression + } + + override fun close() { + if (expression != null) { + while (!expression!!.isEmpty()) { + var p: GenericPtg? = expression!!.pop() as GenericPtg + if (p is PtgRef) + p.close() + else + p!!.close() + p = null + } + } + super.close() + } + + protected fun finalize() { + this.close() + } + + /** + * @return the fCustomIfmt + */ + fun isfCustomIfmt(): Boolean { + return fCustomIfmt + } + + /** + * @param fCustomIfmt the fCustomIfmt to set + */ + fun setfCustomIfmt(fCustomIfmt: Boolean) { + this.fCustomIfmt = fCustomIfmt + } + + companion object { + /** + * + */ + private val serialVersionUID = -6647823755603289012L + // define the type of Ai record + val TYPE_TEXT = 0 + val TYPE_VALS = 1 + val TYPE_CATEGORIES = 2 + val TYPE_BUBBLES = 3 + + /** + * Get a prototype with the specified ai types. + * + * + * 0 = Legend AI + * 1= Series Value Ai + * 2 = Category Ai + * 3 = Unknown, undocumented, but neccesarry AI + * 4 = Blank Legend AI with no reference. + */ + fun getPrototype(aiType: ByteArray): ChartObject { + val ai = Ai() + ai.opcode = XLSConstants.AI + ai.data = aiType + ai.init() + return ai + } + + // 20070801 KSC: since changeAiLocation now allows addition + // of new expression bytes, alter + // default prototype bytes here to not include any + // expression bytes .. + var AI_TYPE_LEGEND = byteArrayOf(0, 2, 0, 0, 0, 0, 0, 0) // , + // 7, + // 0, + // 58, + // 0, + // 0, + // 0, + // 0, + // 0, + // 0}; + var AI_TYPE_SERIES = byteArrayOf(1, 2, 0, 0, 0, 0, 0, 0) // , + // 11, + // 0, + // 59, + // 0, + // 0, + // 1, + // 0, + // 1, + // 0, + // 1, + // 0, + // 3, + // 0}; + var AI_TYPE_CATEGORY = byteArrayOf(2, 2, 0, 0, 0, 0, 0, 0) // 11, + // 0, + // 59, + // 0, + // 0, + // 0, + // 0, + // 0, + // 0, + // 1, + // 0, + // 3, + // 0}; + var AI_TYPE_BUBBLE = byteArrayOf(3, 1, 0, 0, 0, 0, 0, 0) + var AI_TYPE_NULL_LEGEND = byteArrayOf(0, 1, 0, 0, 0, 0, 0, 0) + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/Fbi.java b/src/main/java/io/starter/formats/XLS/charts/AlRuns.kt similarity index 71% rename from src/main/java/io/starter/formats/XLS/charts/Fbi.java rename to src/main/java/io/starter/formats/XLS/charts/AlRuns.kt index 4ffdf93..cb7b233 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Fbi.java +++ b/src/main/java/io/starter/formats/XLS/charts/AlRuns.kt @@ -20,18 +20,22 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; + +package io.starter.formats.XLS.charts /** - * Fbi: Font Basis(0x1060) + * ** AiRuns: Text Formatting (0x1050) ** */ -public class Fbi extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6933614402245236374L; +class AlRuns : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } - public void init() { - super.init(); + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -5255886234371123315L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Area.java b/src/main/java/io/starter/formats/XLS/charts/Area.java deleted file mode 100644 index a926c13..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Area.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS.charts; - - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * Area: Chart Group is an Area Chart Group (0x101a) - * 4 grbit 2 formatflags - *

                      - * grbit: - * 0 0 01h fStacked Series in this group are stacked - * 1 02h f100 Each cat is broken down as a percentge - * 2 04h fHasShadow 1= this are has a shadow - * 1 7-0 FFh reserved 0 - */ -public class Area extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4600344312324775780L; - private short grbit = 0; - protected boolean fStacked = false, f100 = false, fHasShadow = false; - - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fStacked = ((grbit & 0x1) == 0x1); - f100 = ((grbit & 0x2) == 0x2); - fHasShadow = ((grbit & 0x4) == 0x4); - chartType = ChartConstants.AREACHART; // 20070703 KSC - } - - protected void updateRecord() { - grbit = ByteTools.updateGrBit(grbit, fStacked, 0); - grbit = ByteTools.updateGrBit(grbit, f100, 1); - grbit = ByteTools.updateGrBit(grbit, fHasShadow, 2); - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - public static XLSRecord getPrototype() { - Area b = new Area(); - b.setOpcode(AREA); - b.setData(b.PROTOTYPE_BYTES); - b.init(); - return b; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; - - public void setIsStacked(boolean bIsStacked) { - fStacked = bIsStacked; - grbit = ByteTools.updateGrBit(grbit, fStacked, 0); - updateRecord(); - } - - public void setIs100Percent(boolean bOn) { - f100 = bOn; - grbit = ByteTools.updateGrBit(grbit, f100, 1); - updateRecord(); - } - - /** - * @return truth of "Chart is Stacked" - */ - public boolean isStacked() { - return fStacked; - } - - public boolean is100Percent() { - return f100; - } - - - /** - * Handle setting options - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("Stacked")) { - this.fStacked = val.equals("true"); - bHandled = true; - } else if (op.equalsIgnoreCase("PercentageDisplay")) { - this.f100 = val.equals("true"); - bHandled = true; - } else if (op.equalsIgnoreCase("Shadow")) { - this.fHasShadow = val.equals("true"); - bHandled = true; - } - if (bHandled) - this.updateRecord(); - return bHandled; - } - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (this.fStacked) - sb.append(" Stacked=\"true\""); - if (this.f100) - sb.append(" PercentageDisplay=\"true\""); - if (this.fHasShadow) - sb.append(" Shadow=\"true\""); - return sb.toString(); - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Area.kt b/src/main/java/io/starter/formats/XLS/charts/Area.kt new file mode 100644 index 0000000..44729a7 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Area.kt @@ -0,0 +1,138 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS.charts + + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **Area: Chart Group is an Area Chart Group (0x101a)** + * 4 grbit 2 formatflags + * + * + * grbit: + * 0 0 01h fStacked Series in this group are stacked + * 1 02h f100 Each cat is broken down as a percentge + * 2 04h fHasShadow 1= this are has a shadow + * 1 7-0 FFh reserved 0 + */ +class Area : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + protected var fStacked = false + protected var f100 = false + protected var fHasShadow = false + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0) + + /** + * @return truth of "Chart is Stacked" + */ + override var isStacked: Boolean + get() = fStacked + set(bIsStacked) { + fStacked = bIsStacked + grbit = ByteTools.updateGrBit(grbit, fStacked, 0) + updateRecord() + } + + override var is100Percent: Boolean + get() = f100 + set(bOn) { + f100 = bOn + grbit = ByteTools.updateGrBit(grbit, f100, 1) + updateRecord() + } + + /** + * @return String XML representation of this chart-type's options + */ + override val optionsXML: String + get() { + val sb = StringBuffer() + if (this.fStacked) + sb.append(" Stacked=\"true\"") + if (this.f100) + sb.append(" PercentageDisplay=\"true\"") + if (this.fHasShadow) + sb.append(" Shadow=\"true\"") + return sb.toString() + } + + override fun init() { + super.init() + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + fStacked = grbit and 0x1 == 0x1 + f100 = grbit and 0x2 == 0x2 + fHasShadow = grbit and 0x4 == 0x4 + chartType = ChartConstants.AREACHART // 20070703 KSC + } + + protected fun updateRecord() { + grbit = ByteTools.updateGrBit(grbit, fStacked, 0) + grbit = ByteTools.updateGrBit(grbit, f100, 1) + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 2) + val b = ByteTools.shortToLEBytes(grbit) + this.data[0] = b[0] + this.data[1] = b[1] + } + + + /** + * Handle setting options + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("Stacked", ignoreCase = true)) { + this.fStacked = `val` == "true" + bHandled = true + } else if (op.equals("PercentageDisplay", ignoreCase = true)) { + this.f100 = `val` == "true" + bHandled = true + } else if (op.equals("Shadow", ignoreCase = true)) { + this.fHasShadow = `val` == "true" + bHandled = true + } + if (bHandled) + this.updateRecord() + return bHandled + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -4600344312324775780L + + val prototype: XLSRecord? + get() { + val b = Area() + b.opcode = XLSConstants.AREA + b.data = b.PROTOTYPE_BYTES + b.init() + return b + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Area3DChart.java b/src/main/java/io/starter/formats/XLS/charts/Area3DChart.java deleted file mode 100644 index 4369960..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Area3DChart.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class Area3DChart extends AreaChart { - public Area3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - area = (Area) charttype; - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - // TODO: get real value - int gapdepth = this.getGapDepth(); // 150 is default - if (gapdepth != 0 && gapdepth != 150) - cooxml.append(""); - //DropLines - ChartLine cl = cf.getChartLinesRec(); - if (cl != null) - cooxml.append(cl.getOOXML()); - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/Area3DChart.kt b/src/main/java/io/starter/formats/XLS/charts/Area3DChart.kt new file mode 100644 index 0000000..0e01f74 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Area3DChart.kt @@ -0,0 +1,84 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +class Area3DChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : AreaChart(charttype, cf, wb) { + init { + area = charttype as Area + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + // TODO: get real value + val gapdepth = this.gapDepth // 150 is default + if (gapdepth != 0 && gapdepth != 150) + cooxml.append("") + //DropLines + val cl = cf!!.chartLinesRec + if (cl != null) + cooxml.append(cl.ooxml) + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/AreaChart.java b/src/main/java/io/starter/formats/XLS/charts/AreaChart.java deleted file mode 100644 index e39efac..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/AreaChart.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartSeriesHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * non-stacked area chart - */ -public class AreaChart extends ChartType { - Area area = null; - - public AreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - area = (Area) charttype; - } - - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) throws JSONException { - final JSONObject chartObjectJSON = new JSONObject(); - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // Series - double yMax = 0.0, yMin = 0.0; - int nSeries = 0; - final JSONArray seriesJSON = new JSONArray(); - final JSONArray seriesCOLORS = new JSONArray(); - try { - for (int i = 0; i < series.length; i++) { - final JSONArray seriesvals = CellRange - .getValuesAsJSON(series[i].getSeriesRange(), wbh); - // must trap min and max for axis tick and units - double sum = 0.0; // for area-type charts, ymax is the sum of - // all points in same series - nSeries = Math.max(nSeries, seriesvals.length()); - for (int j = 0; j < seriesvals.length(); j++) { - try { - sum += seriesvals.getDouble(j); - yMax = Math.max(yMax, sum); - yMin = Math.min(yMin, seriesvals.getDouble(j)); - } catch (final NumberFormatException n) { - } - } - seriesJSON.put(seriesvals); - seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i] - .getSeriesColor()]); - } - chartObjectJSON.put("Series", seriesJSON); - chartObjectJSON.put("SeriesFills", seriesCOLORS); - } catch (final JSONException je) { - // TODO: Log error - } - minMax[0] = new Double(yMin); - minMax[1] = new Double(yMax); - minMax[2] = new Double(nSeries); - return chartObjectJSON; - - } - - /** - * return the type JSON for this Chart Object - * - * @return - */ - @Override - public JSONObject getTypeJSON() throws JSONException { - final JSONObject typeJSON = new JSONObject(); - String dojoType; - if (!this.isStacked()) { - dojoType = "Areas"; - } else { - dojoType = "StackedAreas"; - } - typeJSON.put("type", dojoType); - return typeJSON; - } - - /** - * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - @Override - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - final double x = chartMetrics.get("x"); - final double y = chartMetrics.get("y"); - final double w = chartMetrics.get("w"); - final double h = chartMetrics.get("h"); - final double max = chartMetrics.get("max"); - chartMetrics.get("min"); - final Object[] categories = s.getCategories(); - final ArrayList series = s.getSeriesValues(); - final String[] seriescolors = s.getSeriesBarColors(); - final String[] legends = s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is - // xv*2 then increases from there - // y value for each point= h/YMAX - if (series.size() == 0) { - Logger.logErr("Area.getSVG: error in series"); - return ""; - } - final StringBuffer svg = new StringBuffer(); - final int[] dls = getDataLabelInts(); // get array of data labels (can - // be specific per series ...) - - double xfactor = 0, yfactor = 0; // - if (categories.length > 1) { - xfactor = w / (categories.length - 1); // w/#categories - } - if (max != 0) { - yfactor = h / max; // h/YMAXSCALE - } - - // for each series - final int n = series.size(); - for (int i = n - 1; i >= 0; i--) { // "paint" right to left - svg.append("\r\n"); - String points = ""; - double x1 = 0; - double y1 = 0; - String labels = null; - final double[] curseries = (double[]) series.get(i); - for (int j = 0; j < curseries.length; j++) { - x1 = (x) + j * xfactor; - final double yval = curseries[j]; // areapoints[j][i]; // - // current point - points += ((x) + (j) * xfactor) + "," - + ((y + h) - (yval * yfactor)); - - if (j == 0) { - y1 = ((y + h) - (yval * yfactor)); // end point (==start - // point) for path - // statement below - } - points += " "; - // DATA LABELS - final String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j] - .toString()); - if (l != null) { - // if only category label, center over all series; anything - // else, position at data point - final boolean showCategories = (dls[i] - & AttachedLabel.CATEGORYLABEL) == AttachedLabel.CATEGORYLABEL; - final boolean showValueLabel = (dls[i] - & AttachedLabel.VALUELABEL) == AttachedLabel.VALUELABEL; - final boolean showValue = (dls[i] - & AttachedLabel.VALUE) == AttachedLabel.VALUE; - if (showCategories && !(showValue || showValueLabel) - && j == 0) { // only 1 label, centered along - // category axis within area - final double hh = y1; // (areapoints[areapoints.length/2][i]*yfactor); - final double yy = (y + h) - hh + 10; - if (labels == null) { - labels = ""; - } - labels = "" + l - + "\r\n"; - } else if (showValue || showValueLabel) { // labels at each - // data point - if (labels == null) { - labels = ""; - } - final double yy = (((y + h) - - ((yval - (curseries[j] * .5)) * yfactor))); - labels += "" - + l + "\r\n"; - } - } - } - // pointsends connects up area to beginning - final double x0 = x; - final String pointsend = x1 + "," + (y + h) + " " + x0 + "," - + (y + h) + " " + x0 + "," + y1; - // String clr= getDarkColor(); - /* - * try { clr= - * FormatConstants.SVGCOLORSTRINGS[seriescolors[i]]; } - * catch(ArrayIndexOutOfBoundsException e) {; } - */ - svg.append("\r\n"); - - // Now print data labels, if any - if (labels != null) { - svg.append(labels); - } - svg.append("\r\n"); - } - return svg.toString(); - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - @Override - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - final StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries() - .getOOXML(this.getChartType(), false, 0)); - - // TODO: FINISH - // chart data labels, if any - // cooxml.append(getDataLabelsOOXML(cf)); - if (this.cf.getChartLinesRec() != null) { - cooxml.append(this.cf.getChartLinesRec().getOOXML()); - } - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/AreaChart.kt b/src/main/java/io/starter/formats/XLS/charts/AreaChart.kt new file mode 100644 index 0000000..1fcad54 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/AreaChart.kt @@ -0,0 +1,294 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ChartSeriesHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.FormatConstants +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +import java.util.ArrayList +import java.util.HashMap + +/** + * non-stacked area chart + */ +open class AreaChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + internal var area: Area? = null + + /** + * return the type JSON for this Chart Object + * + * @return + */ + override val typeJSON: JSONObject + @Throws(JSONException::class) + get() { + val typeJSON = JSONObject() + val dojoType: String + if (!this.isStacked) { + dojoType = "Areas" + } else { + dojoType = "StackedAreas" + } + typeJSON.put("type", dojoType) + return typeJSON + } + + init { + area = charttype as Area + } + + @Throws(JSONException::class) + fun getJSON(series: Array, wbh: WorkBookHandle, minMax: Array): JSONObject { + val chartObjectJSON = JSONObject() + // Type JSON + chartObjectJSON.put("type", this.typeJSON) + + // Series + var yMax = 0.0 + var yMin = 0.0 + var nSeries = 0 + val seriesJSON = JSONArray() + val seriesCOLORS = JSONArray() + try { + for (i in series.indices) { + val seriesvals = CellRange + .getValuesAsJSON(series[i].seriesRange, wbh) + // must trap min and max for axis tick and units + var sum = 0.0 // for area-type charts, ymax is the sum of + // all points in same series + nSeries = Math.max(nSeries, seriesvals.length()) + for (j in 0 until seriesvals.length()) { + try { + sum += seriesvals.getDouble(j) + yMax = Math.max(yMax, sum) + yMin = Math.min(yMin, seriesvals.getDouble(j)) + } catch (n: NumberFormatException) { + } + + } + seriesJSON.put(seriesvals) + seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i] + .seriesColor]) + } + chartObjectJSON.put("Series", seriesJSON) + chartObjectJSON.put("SeriesFills", seriesCOLORS) + } catch (je: JSONException) { + // TODO: Log error + } + + minMax[0] = yMin + minMax[1] = yMax + minMax[2] = nSeries + return chartObjectJSON + + } + + /** + * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val x = chartMetrics["x"] + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + chartMetrics["min"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + // x value for each point= w/(ncategories + 1) 1st one is + // xv*2 then increases from there + // y value for each point= h/YMAX + if (series!!.size == 0) { + Logger.logErr("Area.getSVG: error in series") + return "" + } + val svg = StringBuffer() + val dls = dataLabelInts // get array of data labels (can + // be specific per series ...) + + var xfactor = 0.0 + var yfactor = 0.0 // + if (categories!!.size > 1) { + xfactor = w / (categories.size - 1) // w/#categories + } + if (max != 0.0) { + yfactor = h / max // h/YMAXSCALE + } + + // for each series + val n = series.size + for (i in n - 1 downTo 0) { // "paint" right to left + svg.append("\r\n") + var points = "" + var x1 = 0.0 + var y1 = 0.0 + var labels: String? = null + val curseries = series[i] as DoubleArray + for (j in curseries.indices) { + x1 = x + j * xfactor + val yval = curseries[j] // areapoints[j][i]; // + // current point + points += (x + j * xfactor + "," + + (y + h - yval * yfactor)) + + if (j == 0) { + y1 = y + h - yval * yfactor // end point (==start + // point) for path + // statement below + } + points += " " + // DATA LABELS + val l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0.0, i, legends, categories[j] + .toString()) + if (l != null) { + // if only category label, center over all series; anything + // else, position at data point + val showCategories = dls[i] and AttachedLabel.CATEGORYLABEL == AttachedLabel.CATEGORYLABEL + val showValueLabel = dls[i] and AttachedLabel.VALUELABEL == AttachedLabel.VALUELABEL + val showValue = dls[i] and AttachedLabel.VALUE == AttachedLabel.VALUE + if (showCategories && !(showValue || showValueLabel) + && j == 0) { // only 1 label, centered along + // category axis within area + val hh = y1 // (areapoints[areapoints.length/2][i]*yfactor); + val yy = y + h - hh + 10 + if (labels == null) { + labels = "" + } + labels = ("" + l + + "\r\n") + } else if (showValue || showValueLabel) { // labels at each + // data point + if (labels == null) { + labels = "" + } + val yy = y + h - (yval - curseries[j] * .5) * yfactor + labels += ("" + + l + "\r\n") + } + } + } + // pointsends connects up area to beginning + val pointsend = (x1.toString() + "," + (y + h) + " " + x + "," + + (y + h) + " " + x + "," + y1) + // String clr= getDarkColor(); + /* + * try { clr= + * FormatConstants.SVGCOLORSTRINGS[seriescolors[i]]; } + * catch(ArrayIndexOutOfBoundsException e) {; } + */ + svg.append("\r\n") + + // Now print data labels, if any + if (labels != null) { + svg.append(labels) + } + svg.append("\r\n") + } + return svg.toString() + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries + .getOOXML(this.chartType, false, 0)) + + // TODO: FINISH + // chart data labels, if any + // cooxml.append(getDataLabelsOOXML(cf)); + if (this.cf!!.chartLinesRec != null) { + cooxml.append(this.cf!!.chartLinesRec.ooxml) + } + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/AreaFormat.java b/src/main/java/io/starter/formats/XLS/charts/AreaFormat.java deleted file mode 100644 index 227b416..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/AreaFormat.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.formats.OOXML.FillGroup; -import io.starter.formats.OOXML.SpPr; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; -/** - * AreaFormat: Colors and Patterns for an area(0x100a) - *

                      - * 4 rgbFore 4 FgColor RGB Value (High byte is 0) - * 8 rgbBack 4 Bg Color "" - * 12 fls 2 Pattern - * 14 grbit 2 flags - * 16 icvFore 2 index to fg color - * 18 icvBack 2 index to bg color - *

                      - * grbit: - * 0 0x1 fAuto Automatic Format - * 1 0x2 fInvertNeg Fg + Bg is swapped when data is neg. - */ - -/** - * more info on colors: - * - * The chart color table is a subset of the full color table. - * icv (2 bytes): An Icv that specifies a color from the chart color table. - * MUST be greater than or equal to 0x0008 and less than or equal to 0x003F, or greater than or equal to 0x004D and less than or equal to 0x004F. - * - * This info is not yet verified: - * - * For icvFore, icvBack, must be either 0-7 or 0x40 or 0x41 or icv???? - * "The default value of this field is selected automatically from the next available color in the Chart color table." - * - * For icvBack, must be either 0-7 or 0x40 or 0x41 - * The default value of this field is 0x0009. - * - * icv (chart color table index): > 0x0008 < 003F OR 0x4D, 0x4E, 0x4F OR 0x7FFF - * - * 0-7 are bascially the normal COLORTABLE entries - * The icv values greater than or equal to 0x0008 and less than or equal to 0x003F, specify the palette colors in the table. - * If a Palette record exists in this file, these icv values specify colors from the rgColor array in the Palette record. - * If no Palette record exists, these values specify colors in the default palette. - * - * The next 56 values in this part of the color table are specified as follows: - * 0x0040 - Default foreground color. This is the window text color in the data sheet display. - 0x0041 - Default background color. This is the window background color in the data sheet display and is the default background color for a cell. - 0x004D - Default chart foreground color. This is the window text color in the chart display. - 0x004E - Default chart background color. This is the window background color in the chart display. - 0x004F - Chart neutral color which is black, an RGB value of (0,0,0). - 0x7FFF - Font automatic color. This is the window text color. - */ -public class AreaFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -437132913972684937L; - private java.awt.Color rgbFore, rgbBack; - private short fls = 0; - private short grbit = 0; - private short icvFore = 0; - private short icvBack = 0; - boolean fAuto, fInvertNeg; - - public void init() { - super.init(); - byte[] data = this.getData(); - grbit = ByteTools.readShort(data[10], data[11]); - fAuto = (grbit & 0x1) == 0x1; - fInvertNeg = (grbit & 0x2) == 0x2; - rgbFore = new java.awt.Color((data[0] < 0 ? 255 + data[0] : data[0]), (data[1] < 0 ? 255 + data[1] : data[1]), (data[2] < 0 ? 255 + data[2] : data[2])); - rgbBack = new java.awt.Color((data[4] < 0 ? 255 + data[4] : data[4]), (data[5] < 0 ? 255 + data[5] : data[5]), (data[6] < 0 ? 255 + data[6] : data[6])); - fls = ByteTools.readShort(data[8], data[9]); - icvFore = ByteTools.readShort(data[12], data[13]); - icvBack = ByteTools.readShort(data[14], data[15]); - } - - // 20070716 KSC: Need to create new records - public static XLSRecord getPrototype() { - AreaFormat af = new AreaFormat(); - af.setOpcode(AREAFORMAT); - af.setData(af.PROTOTYPE_BYTES); - af.init(); - return af; - } // changed prototype bytes to ensure has default settings i.e. no pattern and automatic color - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 77, 0, 77, 0}; - private byte[] PROTOTYPE_BYTES_1 = new byte[]{-64, -64, -64, 0, 0, 0, 0, 0, 1, 0, 0, 0, 22, 0, 79, 0}; - private byte[] PROTOTYPE_BYTES_2 = new byte[]{0, 0, 0, 0, -1, -1, -1, 0, 1, 0, 1, 0, 77, 0, 78, 0}; - - public static XLSRecord getPrototype(int type) { - AreaFormat af = new AreaFormat(); - af.setOpcode(AREAFORMAT); - if (type == 0) - af.setData(af.PROTOTYPE_BYTES_1); // default is certain color combo - else if (type == 1) - af.setData(af.PROTOTYPE_BYTES_2); // "" - else - af.setData(af.PROTOTYPE_BYTES); - af.init(); - return af; - } - - - private void updateRecord() { - byte[] b = new byte[4]; - b[0] = (byte) rgbFore.getRed(); - b[1] = (byte) rgbFore.getGreen(); - b[2] = (byte) rgbFore.getBlue(); - b[3] = 0; // reserved/0 - System.arraycopy(b, 0, this.getData(), 0, 4); - b[0] = (byte) rgbBack.getRed(); - b[1] = (byte) rgbBack.getGreen(); - b[2] = (byte) rgbBack.getBlue(); - b[3] = 0; // reserved/0 - System.arraycopy(b, 0, this.getData(), 4, 4); - b = ByteTools.shortToLEBytes(fls); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; - grbit = 0; - if (fAuto) grbit = 0x1; - if (fInvertNeg) grbit |= 0x2; - b = ByteTools.shortToLEBytes(grbit); - this.getData()[10] = b[0]; - this.getData()[11] = b[1]; - b = ByteTools.shortToLEBytes(icvFore); - this.getData()[12] = b[0]; - this.getData()[13] = b[1]; - b = ByteTools.shortToLEBytes(icvBack); - this.getData()[14] = b[0]; - this.getData()[15] = b[1]; - } - - - public String toString() { - return "AreaFormat: Pattern=" + fls + " ForeColor=" + icvFore + " BackColor=" + icvBack + " Automatic Format=" + ((grbit & 0x1) == 0x1); - } - - /** - * return the bg color index for this Area Format - * @return - */ - public int geticvBack() { - if (icvBack > this.getColorTable().length) { // then it's one of the special codes: - return FormatHandle.interpretSpecialColorIndex(icvBack); - } - return icvBack; - } - - /** - * return the area fill color - * @return int index into color table - */ - public int getFillColor() { - if (fls == 0) // no fill - return FormatConstants.COLOR_WHITE; - if (fls == 1) // solid; forecolor is bg - return geticvFore(); - if (fls == 2) // medium grey - return FormatConstants.COLOR_GRAY50; - if (fls == 3) // dark grey (possible for more options not yet handled - return FormatConstants.COLOR_GRAY80; - if (fls == 4) // light grey - return FormatConstants.COLOR_GRAY25; - // rest are actual fill patterns TODO handle - return geticvFore(); - } - - /** - * return the area fill color - * @return String color hex string - */ - public String getFillColorStr() { - if (fAuto) - return null; - if (fls == 0) // no fill - return "#FFFFFF"; - if (fls == 1) // solid; forecolor is bg - return FormatHandle.colorToHexString(rgbFore); - if (fls == 2) // medium grey - return FormatHandle.colorToHexString(FormatConstants.Gray50); - if (fls == 3) // dark grey (possible for more options not yet handled - return FormatHandle.colorToHexString(FormatConstants.Gray80); - if (fls == 4) // light grey - return FormatHandle.colorToHexString(FormatConstants.Gray25); - // rest are actual fill patterns TODO handle - return FormatHandle.colorToHexString(rgbFore); - } - - /** - * return the fg color index for this Area Format - * @return - */ - public int geticvFore() { - if (icvFore > this.getColorTable().length) { // then it's one of the special codes: - return FormatHandle.interpretSpecialColorIndex(icvFore); - } - return icvFore; - } - - public void seticvBack(int clr) { - if (clr > -1 && clr < this.getColorTable().length) { - icvBack = (short) clr; - rgbBack = this.getColorTable()[clr]; - updateRecord(); - } else if (clr == 0x4D) { // special flag, default bg - icvBack = (short) clr; - rgbBack = this.getColorTable()[0]; - updateRecord(); - } - } - - /** - * sets the fill color for the area - * @param clr color index - */ - public void seticvFore(int clr) { - - // fls= 1 The fill pattern is solid. When solid is specified, - // rgbFore is the only color rendered, even if rgbBack is specified - if (clr > -1 && clr < this.getColorTable().length) { - fAuto = false; - fls = 1; - icvFore = (short) clr; - rgbFore = this.getColorTable()[clr]; - // must also set bg to 9 - seticvBack(9); - //updateRecord(); - } else if (clr == 0x4E) { // default fg - icvFore = (short) clr; - rgbFore = this.getColorTable()[1]; - updateRecord(); - } - } - - /** - * sets the fill color for the area - * @param clr Hex Color String - */ - public void seticvFore(String clr) { - fAuto = false; - fls = 1; - rgbFore = FormatHandle.HexStringToColor(clr); - icvFore = (short) FormatHandle.HexStringToColorInt(clr, FormatHandle.colorBACKGROUND); // finds best match - updateRecord(); - } - - - /** - * sets the OOXML settings for this Area Format - * @param sp - */ - public void setFromOOXML(SpPr sp) { - FillGroup f = sp.getFill(); - if (f != null) { - this.seticvFore(f.getColor()); - //this.seticvBack() - // fls= fill pattern - - } - } - - public StringBuffer getOOXML() { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); -// ooxml.append(""); - ooxml.append(""); - return ooxml; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/AreaFormat.kt b/src/main/java/io/starter/formats/XLS/charts/AreaFormat.kt new file mode 100644 index 0000000..cdfae65 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/AreaFormat.kt @@ -0,0 +1,317 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.FormatHandle +import io.starter.formats.OOXML.FillGroup +import io.starter.formats.OOXML.SpPr +import io.starter.formats.XLS.FormatConstants +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **AreaFormat: Colors and Patterns for an area(0x100a)** + * + * + * 4 rgbFore 4 FgColor RGB Value (High byte is 0) + * 8 rgbBack 4 Bg Color "" + * 12 fls 2 Pattern + * 14 grbit 2 flags + * 16 icvFore 2 index to fg color + * 18 icvBack 2 index to bg color + * + * + * grbit: + * 0 0x1 fAuto Automatic Format + * 1 0x2 fInvertNeg Fg + Bg is swapped when data is neg. + */ + +/** + * more info on colors: + * + * The chart color table is a subset of the full color table. + * icv (2 bytes): An Icv that specifies a color from the chart color table. + * MUST be greater than or equal to 0x0008 and less than or equal to 0x003F, or greater than or equal to 0x004D and less than or equal to 0x004F. + * + * This info is not yet verified: + * + * For icvFore, icvBack, must be either 0-7 or 0x40 or 0x41 or icv???? + * "The default value of this field is selected automatically from the next available color in the Chart color table." + * + * For icvBack, must be either 0-7 or 0x40 or 0x41 + * The default value of this field is 0x0009. + * + * icv (chart color table index): > 0x0008 < 003F OR 0x4D, 0x4E, 0x4F OR 0x7FFF + * + * 0-7 are bascially the normal COLORTABLE entries + * The icv values greater than or equal to 0x0008 and less than or equal to 0x003F, specify the palette colors in the table. + * If a Palette record exists in this file, these icv values specify colors from the rgColor array in the Palette record. + * If no Palette record exists, these values specify colors in the default palette. + * + * The next 56 values in this part of the color table are specified as follows: + * 0x0040 + * Default foreground color. This is the window text color in the data sheet display. + * 0x0041 + * Default background color. This is the window background color in the data sheet display and is the default background color for a cell. + * 0x004D + * Default chart foreground color. This is the window text color in the chart display. + * 0x004E + * Default chart background color. This is the window background color in the chart display. + * 0x004F + * Chart neutral color which is black, an RGB value of (0,0,0). + * 0x7FFF + * Font automatic color. This is the window text color. + */ +class AreaFormat : GenericChartObject(), ChartObject { + private var rgbFore: java.awt.Color? = null + private var rgbBack: java.awt.Color? = null + private var fls: Short = 0 + private var grbit: Short = 0 + private var icvFore: Short = 0 + private var icvBack: Short = 0 + internal var fAuto: Boolean = false + internal var fInvertNeg: Boolean = false + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 77, 0, 77, 0) + private val PROTOTYPE_BYTES_1 = byteArrayOf(-64, -64, -64, 0, 0, 0, 0, 0, 1, 0, 0, 0, 22, 0, 79, 0) + private val PROTOTYPE_BYTES_2 = byteArrayOf(0, 0, 0, 0, -1, -1, -1, 0, 1, 0, 1, 0, 77, 0, 78, 0) + + /** + * return the area fill color + * @return int index into color table + */ + // no fill + // solid; forecolor is bg + // medium grey + // dark grey (possible for more options not yet handled + // light grey + // rest are actual fill patterns TODO handle + val fillColor: Int + get() { + if (fls.toInt() == 0) + return FormatConstants.COLOR_WHITE + if (fls.toInt() == 1) + return geticvFore() + if (fls.toInt() == 2) + return FormatConstants.COLOR_GRAY50 + if (fls.toInt() == 3) + return FormatConstants.COLOR_GRAY80 + return if (fls.toInt() == 4) FormatConstants.COLOR_GRAY25 else geticvFore() + } + + /** + * return the area fill color + * @return String color hex string + */ + // no fill + // solid; forecolor is bg + // medium grey + // dark grey (possible for more options not yet handled + // light grey + // rest are actual fill patterns TODO handle + val fillColorStr: String? + get() { + if (fAuto) + return null + if (fls.toInt() == 0) + return "#FFFFFF" + if (fls.toInt() == 1) + return FormatHandle.colorToHexString(rgbFore!!) + if (fls.toInt() == 2) + return FormatHandle.colorToHexString(FormatConstants.Gray50) + if (fls.toInt() == 3) + return FormatHandle.colorToHexString(FormatConstants.Gray80) + return if (fls.toInt() == 4) FormatHandle.colorToHexString(FormatConstants.Gray25) else FormatHandle.colorToHexString(rgbFore!!) + } + + // ooxml.append(""); + val ooxml: StringBuffer + get() { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append("") + return ooxml + } + + override fun init() { + super.init() + val data = this.data + grbit = ByteTools.readShort(data!![10].toInt(), data[11].toInt()) + fAuto = grbit and 0x1 == 0x1 + fInvertNeg = grbit and 0x2 == 0x2 + rgbFore = java.awt.Color(if (data[0] < 0) 255 + data[0] else data[0], if (data[1] < 0) 255 + data[1] else data[1], if (data[2] < 0) 255 + data[2] else data[2]) + rgbBack = java.awt.Color(if (data[4] < 0) 255 + data[4] else data[4], if (data[5] < 0) 255 + data[5] else data[5], if (data[6] < 0) 255 + data[6] else data[6]) + fls = ByteTools.readShort(data[8].toInt(), data[9].toInt()) + icvFore = ByteTools.readShort(data[12].toInt(), data[13].toInt()) + icvBack = ByteTools.readShort(data[14].toInt(), data[15].toInt()) + } + + + private fun updateRecord() { + var b = ByteArray(4) + b[0] = rgbFore!!.red.toByte() + b[1] = rgbFore!!.green.toByte() + b[2] = rgbFore!!.blue.toByte() + b[3] = 0 // reserved/0 + System.arraycopy(b, 0, this.data!!, 0, 4) + b[0] = rgbBack!!.red.toByte() + b[1] = rgbBack!!.green.toByte() + b[2] = rgbBack!!.blue.toByte() + b[3] = 0 // reserved/0 + System.arraycopy(b, 0, this.data!!, 4, 4) + b = ByteTools.shortToLEBytes(fls) + this.data[8] = b[0] + this.data[9] = b[1] + grbit = 0 + if (fAuto) grbit = 0x1 + if (fInvertNeg) grbit = grbit or 0x2 + b = ByteTools.shortToLEBytes(grbit) + this.data[10] = b[0] + this.data[11] = b[1] + b = ByteTools.shortToLEBytes(icvFore) + this.data[12] = b[0] + this.data[13] = b[1] + b = ByteTools.shortToLEBytes(icvBack) + this.data[14] = b[0] + this.data[15] = b[1] + } + + + override fun toString(): String { + return "AreaFormat: Pattern=" + fls + " ForeColor=" + icvFore + " BackColor=" + icvBack + " Automatic Format=" + (grbit and 0x1 == 0x1) + } + + /** + * return the bg color index for this Area Format + * @return + */ + fun geticvBack(): Int { + return if (icvBack > this.colorTable.size) { // then it's one of the special codes: + FormatHandle.interpretSpecialColorIndex(icvBack.toInt()).toInt() + } else icvBack.toInt() + } + + /** + * return the fg color index for this Area Format + * @return + */ + fun geticvFore(): Int { + return if (icvFore > this.colorTable.size) { // then it's one of the special codes: + FormatHandle.interpretSpecialColorIndex(icvFore.toInt()).toInt() + } else icvFore.toInt() + } + + fun seticvBack(clr: Int) { + if (clr > -1 && clr < this.colorTable.size) { + icvBack = clr.toShort() + rgbBack = this.colorTable[clr] + updateRecord() + } else if (clr == 0x4D) { // special flag, default bg + icvBack = clr.toShort() + rgbBack = this.colorTable[0] + updateRecord() + } + } + + /** + * sets the fill color for the area + * @param clr color index + */ + fun seticvFore(clr: Int) { + + // fls= 1 The fill pattern is solid. When solid is specified, + // rgbFore is the only color rendered, even if rgbBack is specified + if (clr > -1 && clr < this.colorTable.size) { + fAuto = false + fls = 1 + icvFore = clr.toShort() + rgbFore = this.colorTable[clr] + // must also set bg to 9 + seticvBack(9) + //updateRecord(); + } else if (clr == 0x4E) { // default fg + icvFore = clr.toShort() + rgbFore = this.colorTable[1] + updateRecord() + } + } + + /** + * sets the fill color for the area + * @param clr Hex Color String + */ + fun seticvFore(clr: String) { + fAuto = false + fls = 1 + rgbFore = FormatHandle.HexStringToColor(clr) + icvFore = FormatHandle.HexStringToColorInt(clr, FormatHandle.colorBACKGROUND).toShort() // finds best match + updateRecord() + } + + + /** + * sets the OOXML settings for this Area Format + * @param sp + */ + fun setFromOOXML(sp: SpPr) { + val f = sp.fill + if (f != null) { + this.seticvFore(f.color) + //this.seticvBack() + // fls= fill pattern + + } + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -437132913972684937L + + // 20070716 KSC: Need to create new records + val prototype: XLSRecord? + get() { + val af = AreaFormat() + af.opcode = XLSConstants.AREAFORMAT + af.data = af.PROTOTYPE_BYTES + af.init() + return af + } // changed prototype bytes to ensure has default settings i.e. no pattern and automatic color + + fun getPrototype(type: Int): XLSRecord { + val af = AreaFormat() + af.opcode = XLSConstants.AREAFORMAT + if (type == 0) + af.data = af.PROTOTYPE_BYTES_1 // default is certain color combo + else if (type == 1) + af.data = af.PROTOTYPE_BYTES_2 // "" + else + af.data = af.PROTOTYPE_BYTES + af.init() + return af + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java b/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java deleted file mode 100644 index 245399f..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * AttachedLabel: Series Data/Value Labels (0x100c) - *

                      - *

                      - * bit - * 0 = show value label -- a bit that specifies whether the value, or the vertical value on bubble or scatter chart groups, is displayed in the data label. - * 1 = show value as percentage -- (must be 0 for non-pie) - * 2 = show cat/label as percentage -- A bit that specifies whether the category (3) name and value, - * represented as a percentage of the sum of the values of the series with - * which the data label is associated, are displayed in the data label. (pie charts only) - * 3 = unused - * 4 = show cat or series label -- A bit that specifies whether the category, or the horizontal value on bubble or scatter chart groups, - * is displayed in the data label on a non-area chart group, or the series name is displayed in the data label on an area chart group. - * 5 = show bubble label -- A bit that specifies whether the bubble size is displayed in the data label. - * 6 = show series name -- A bit that specifies whether the data label contains the name of the series. - *

                      - * ???? Series DataLabels do not appear to be done via AttachedLabel - */ -public class AttachedLabel extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2532517522176536995L; - public static final int VALUE = 0x1; - public static final int VALUEPERCENT = 0x2; - public static final int CATEGORYPERCENT = 0x4; - public static final int CATEGORYLABEL = 0x10; - public static final int BUBBLELABEL = 0x20; - public static final int VALUELABEL = 0x40; - - private short grbit = 0; - - @Override - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - - public static XLSRecord getPrototype() { - AttachedLabel al = new AttachedLabel(); - al.setOpcode(ATTACHEDLABEL); - al.setData(al.PROTOTYPE_BYTES); - al.init(); - return al; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; - - /** - * return a string of all the label options chosen - * - * @return - */ - public String getType() { - String ret = ""; - if ((grbit & VALUE) == VALUE) // bit 0 - ret = "Value "; - if ((grbit & VALUEPERCENT) == VALUEPERCENT) // bit 1 - ret = ret + "ValuePerecentage "; - if ((grbit & CATEGORYPERCENT) == CATEGORYPERCENT) // bit 2 - ret = ret + "CategoryPercentage "; // Pie only - if ((grbit & CATEGORYLABEL) == CATEGORYLABEL) // bit 4 - ret = ret + "CategoryLabel "; - if ((grbit & BUBBLELABEL) == BUBBLELABEL) // bit 5 - ret = ret + "BubbleLabel "; - if ((grbit & VALUELABEL) == VALUELABEL) // bit 6 - ret = ret + "SeriesLabel "; - return ret.trim(); - } - - /** - * return the data label options as an int - * - * @return a combination of data label options above or 0 if none - * @see AttachedLabel constants - *
                      SHOWVALUE= 0x1; - *
                      SHOWVALUEPERCENT= 0x2; - *
                      SHOWCATEGORYPERCENT= 0x4; - *
                      SHOWCATEGORYLABEL= 0x10; - *
                      SHOWBUBBLELABEL= 0x20; - *
                      SHOWSERIESLABEL= 0x40; - */ - public int getTypeInt() { - return grbit; - } - - public void setType(short type) { - grbit = type; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - /** - * Show or Hide AttachedLabel Option - *
                      ShowValueLabel, - *
                      ShowValueAsPercent, - *
                      ShowLabelAsPercent, - *
                      ShowLabel, - *
                      ShowBubbleLabel - *
                      ShowSeriesName - * - * @param val true or 1 to set - */ - public void setType(String type, String val) { - boolean bSet = val.equals("true") || val.equals("1"); - if (type.equals("ShowValueLabel")) - grbit = ByteTools.updateGrBit(grbit, bSet, 0); - if (type.equals("ShowValueAsPercent")) - grbit = ByteTools.updateGrBit(grbit, bSet, 1); - if (type.equals("ShowLabelAsPercent")) - grbit = ByteTools.updateGrBit(grbit, bSet, 2); - if (type.equals("ShowLabel")) - grbit = ByteTools.updateGrBit(grbit, bSet, 4); - if (type.equals("ShowBubbleLabel")) - grbit = ByteTools.updateGrBit(grbit, bSet, 5); - if (type.equals("ShowSeriesName")) - grbit = ByteTools.updateGrBit(grbit, bSet, 6); - byte[] bb = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = bb[0]; - this.getData()[1] = bb[1]; - } - - /** - * return the value of the specified option - * - * @param type Sting option - * @return string true or false - */ - public String getType(String type) { - boolean b = false; - - if (type.equals("ShowValueLabel")) - b = ((grbit & VALUE) == VALUE); // bit 0 - if (type.equals("ShowValueAsPercent")) - b = ((grbit & VALUEPERCENT) == VALUEPERCENT); - if (type.equals("ShowLabelAsPercent")) - b = (((grbit & CATEGORYPERCENT) == CATEGORYPERCENT)); - if (type.equals("ShowLabel")) - b = (((grbit & CATEGORYLABEL) == CATEGORYLABEL)); - if (type.equals("ShowBubbleLabel")) - b = (((grbit & BUBBLELABEL) == BUBBLELABEL)); - if (type.equals("ShowSeriesName")) - b = (((grbit & VALUELABEL) == VALUELABEL)); - return (b) ? ("1") : ("0"); - } - - /** - * @param type - * @deprecated - */ - @Deprecated - public void setType(String type) { - short t = 0; - if (type.equalsIgnoreCase("Value") || type.equalsIgnoreCase("Y Value")) - t = 1; - else if (type.equalsIgnoreCase("ValuePercentage")) - t = 2; - else if (type.equalsIgnoreCase("CategoryPercentage")) - t = 3; - else if (type.equalsIgnoreCase("Category") - || type.equalsIgnoreCase("X Value")) - t = 16; - else if (type.equalsIgnoreCase("CandP")) - t = 22; - else if (type.equalsIgnoreCase("Bubble")) - t = 32; - grbit = t; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - @Override - public String toString() { - return this.getType(); - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.kt b/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.kt new file mode 100644 index 0000000..86b347e --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/AttachedLabel.kt @@ -0,0 +1,224 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * ** AttachedLabel: Series Data/Value Labels (0x100c) ** + * + * + * + * + * bit + * 0 = show value label -- a bit that specifies whether the value, or the vertical value on bubble or scatter chart groups, is displayed in the data label. + * 1 = show value as percentage -- (must be 0 for non-pie) + * 2 = show cat/label as percentage -- A bit that specifies whether the category (3) name and value, + * represented as a percentage of the sum of the values of the series with + * which the data label is associated, are displayed in the data label. (pie charts only) + * 3 = unused + * 4 = show cat or series label -- A bit that specifies whether the category, or the horizontal value on bubble or scatter chart groups, + * is displayed in the data label on a non-area chart group, or the series name is displayed in the data label on an area chart group. + * 5 = show bubble label -- A bit that specifies whether the bubble size is displayed in the data label. + * 6 = show series name -- A bit that specifies whether the data label contains the name of the series. + * + * + * ???? Series DataLabels do not appear to be done via AttachedLabel + */ +class AttachedLabel : GenericChartObject(), ChartObject { + + private var grbit: Short = 0 + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0) + + /** + * return a string of all the label options chosen + * + * @return + */ + // bit 0 + // bit 1 + // bit 2 + // Pie only + // bit 4 + // bit 5 + // bit 6 + val type: String + get() { + var ret = "" + if (grbit and VALUE == VALUE) + ret = "Value " + if (grbit and VALUEPERCENT == VALUEPERCENT) + ret = ret + "ValuePerecentage " + if (grbit and CATEGORYPERCENT == CATEGORYPERCENT) + ret = ret + "CategoryPercentage " + if (grbit and CATEGORYLABEL == CATEGORYLABEL) + ret = ret + "CategoryLabel " + if (grbit and BUBBLELABEL == BUBBLELABEL) + ret = ret + "BubbleLabel " + if (grbit and VALUELABEL == VALUELABEL) + ret = ret + "SeriesLabel " + return ret.trim { it <= ' ' } + } + + /** + * return the data label options as an int + * + * @return a combination of data label options above or 0 if none + * @see AttachedLabel constants + *

                      SHOWVALUE= 0x1; + *

                      SHOWVALUEPERCENT= 0x2; + *

                      SHOWCATEGORYPERCENT= 0x4; + *

                      SHOWCATEGORYLABEL= 0x10; + *

                      SHOWBUBBLELABEL= 0x20; + *

                      SHOWSERIESLABEL= 0x40; + */ + val typeInt: Int + get() = grbit.toInt() + + override fun init() { + super.init() + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + } + + fun setType(type: Short) { + grbit = type + val b = ByteTools.shortToLEBytes(grbit) + this.data[0] = b[0] + this.data[1] = b[1] + } + + /** + * Show or Hide AttachedLabel Option + *

                      ShowValueLabel, + *

                      ShowValueAsPercent, + *

                      ShowLabelAsPercent, + *

                      ShowLabel, + *

                      ShowBubbleLabel + *

                      ShowSeriesName + * + * @param val true or 1 to set + */ + fun setType(type: String, `val`: String) { + val bSet = `val` == "true" || `val` == "1" + if (type == "ShowValueLabel") + grbit = ByteTools.updateGrBit(grbit, bSet, 0) + if (type == "ShowValueAsPercent") + grbit = ByteTools.updateGrBit(grbit, bSet, 1) + if (type == "ShowLabelAsPercent") + grbit = ByteTools.updateGrBit(grbit, bSet, 2) + if (type == "ShowLabel") + grbit = ByteTools.updateGrBit(grbit, bSet, 4) + if (type == "ShowBubbleLabel") + grbit = ByteTools.updateGrBit(grbit, bSet, 5) + if (type == "ShowSeriesName") + grbit = ByteTools.updateGrBit(grbit, bSet, 6) + val bb = ByteTools.shortToLEBytes(grbit) + this.data[0] = bb[0] + this.data[1] = bb[1] + } + + /** + * return the value of the specified option + * + * @param type Sting option + * @return string true or false + */ + fun getType(type: String): String { + var b = false + + if (type == "ShowValueLabel") + b = grbit and VALUE == VALUE // bit 0 + if (type == "ShowValueAsPercent") + b = grbit and VALUEPERCENT == VALUEPERCENT + if (type == "ShowLabelAsPercent") + b = grbit and CATEGORYPERCENT == CATEGORYPERCENT + if (type == "ShowLabel") + b = grbit and CATEGORYLABEL == CATEGORYLABEL + if (type == "ShowBubbleLabel") + b = grbit and BUBBLELABEL == BUBBLELABEL + if (type == "ShowSeriesName") + b = grbit and VALUELABEL == VALUELABEL + return if (b) "1" else "0" + } + + /** + * @param type + */ + @Deprecated("") + fun setType(type: String) { + var t: Short = 0 + if (type.equals("Value", ignoreCase = true) || type.equals("Y Value", ignoreCase = true)) + t = 1 + else if (type.equals("ValuePercentage", ignoreCase = true)) + t = 2 + else if (type.equals("CategoryPercentage", ignoreCase = true)) + t = 3 + else if (type.equals("Category", ignoreCase = true) || type.equals("X Value", ignoreCase = true)) + t = 16 + else if (type.equals("CandP", ignoreCase = true)) + t = 22 + else if (type.equals("Bubble", ignoreCase = true)) + t = 32 + grbit = t + val b = ByteTools.shortToLEBytes(grbit) + this.data[0] = b[0] + this.data[1] = b[1] + } + + override fun toString(): String { + return this.type + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2532517522176536995L + val VALUE = 0x1 + val VALUEPERCENT = 0x2 + val CATEGORYPERCENT = 0x4 + val CATEGORYLABEL = 0x10 + val BUBBLELABEL = 0x20 + val VALUELABEL = 0x40 + + val prototype: XLSRecord? + get() { + val al = AttachedLabel() + al.opcode = XLSConstants.ATTACHEDLABEL + al.data = al.PROTOTYPE_BYTES + al.init() + return al + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Axcent.java b/src/main/java/io/starter/formats/XLS/charts/Axcent.java deleted file mode 100644 index cbefddc..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Axcent.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * Axcent: Axis Options(0x1062) - *

                      - * 4 catMin 2 minimum date on axis. - * If fAutoMin is set to 1, MUST be ignored. - * If fDateAxis is set to 0, MUST be ignored. - * 6 catMax 2 maximum date on axis. - * fAutoMax is set to 1, MUST be ignored. - * If fDateAxis is set to 0, MUST be ignored. - * 8 catMajor 2 value of major unit - * MUST be greater than or equal to catMinor when duMajor is equal to duMinor. - * If fAutoMajor is set to 1, MUST be ignored. - * If fDateAxis is set to 0, MUST be ignored. - * 10 duMajor 2 Date Enumeration specifies unit of time for use of catMajor - * If fDateAxis is set to 0, MUST be ignored. - * 12 catMinor 2 value of minor unit - * 14 duMinor 2 time units of minor unit - * If fDateAxis is set to 0, MUST be ignored. - * 16 duBase 2 smallest unit of time used by the axis. - * If fAutoBase is set to 1, this field MUST be ignored. - * If fDateAxis is set to 0, MUST be ignored. - * 18 catCrossDate 2 crossing point of value axis (date) - * 20 grbit 2 - *

                      - * 0 0x1 fAutoMin 1= use default - * 1 0x2 fAutoMax "" - * 2 0x4 fAutoMajor "" - * 3 0x8 fAutoMinor "" - * 4 0x10 fdateAxis 1= this is a date axis - * 5 0x20 fAutoBase 1= use default base - * 6 0x40 fAutoCross "" - * 7 0x80 fAutoDate 1= use default date settings for axis - */ -public class Axcent extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -660100252646337769L; - short catMin, catMax, catMajor, duMajor, catMinor, duMinor, duBase, catCrossDate, grbit; - - public void init() { - super.init(); - // 20071223 KSC: Start parsing of options - catMin = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - catMax = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - catMajor = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - duMajor = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - catMinor = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - duMinor = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - duBase = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - catCrossDate = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); - grbit = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); - } - - // 20070723 KSC: Need to create new records - public static XLSRecord getPrototype() { - Axcent a = new Axcent(); - a.setOpcode(AXCENT); - a.setData(a.PROTOTYPE_BYTES); - a.init(); // important when we parse these options ... - return a; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -17, 0}; - - // 20071223 KSC: add access methods - public short getCatMin() { - return catMin; - } - - public short getCatMax() { - return catMax; - } - - public short getCatMajor() { - return catMajor; - } - - public short getDuMajor() { - return duMajor; - } - - public short getCatMinor() { - return catMinor; - } - - public short getDuMinor() { - return duMinor; - } - - public short getDuBase() { - return duBase; - } - - public boolean isDefaultMin() { - return ((grbit & 0x1) == 0x1); - } - - public boolean isDefaultMax() { - return ((grbit & 0x2) == 0x2); - } - - public boolean isDefaultMajorUnits() { - return ((grbit & 0x4) == 0x4); - } - - public boolean isDefaultMinorUnits() { - return ((grbit & 0x8) == 0x8); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Axcent.kt b/src/main/java/io/starter/formats/XLS/charts/Axcent.kt new file mode 100644 index 0000000..2785b14 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Axcent.kt @@ -0,0 +1,127 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **Axcent: Axis Options(0x1062)** + * + * + * 4 catMin 2 minimum date on axis. + * If fAutoMin is set to 1, MUST be ignored. + * If fDateAxis is set to 0, MUST be ignored. + * 6 catMax 2 maximum date on axis. + * fAutoMax is set to 1, MUST be ignored. + * If fDateAxis is set to 0, MUST be ignored. + * 8 catMajor 2 value of major unit + * MUST be greater than or equal to catMinor when duMajor is equal to duMinor. + * If fAutoMajor is set to 1, MUST be ignored. + * If fDateAxis is set to 0, MUST be ignored. + * 10 duMajor 2 Date Enumeration specifies unit of time for use of catMajor + * If fDateAxis is set to 0, MUST be ignored. + * 12 catMinor 2 value of minor unit + * 14 duMinor 2 time units of minor unit + * If fDateAxis is set to 0, MUST be ignored. + * 16 duBase 2 smallest unit of time used by the axis. + * If fAutoBase is set to 1, this field MUST be ignored. + * If fDateAxis is set to 0, MUST be ignored. + * 18 catCrossDate 2 crossing point of value axis (date) + * 20 grbit 2 + * + * + * 0 0x1 fAutoMin 1= use default + * 1 0x2 fAutoMax "" + * 2 0x4 fAutoMajor "" + * 3 0x8 fAutoMinor "" + * 4 0x10 fdateAxis 1= this is a date axis + * 5 0x20 fAutoBase 1= use default base + * 6 0x40 fAutoCross "" + * 7 0x80 fAutoDate 1= use default date settings for axis + */ +class Axcent : GenericChartObject(), ChartObject { + // 20071223 KSC: add access methods + var catMin: Short = 0 + internal set + var catMax: Short = 0 + internal set + var catMajor: Short = 0 + internal set + var duMajor: Short = 0 + internal set + var catMinor: Short = 0 + internal set + var duMinor: Short = 0 + internal set + var duBase: Short = 0 + internal set + internal var catCrossDate: Short = 0 + internal var grbit: Short = 0 + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -17, 0) + + val isDefaultMin: Boolean + get() = grbit and 0x1 == 0x1 + + val isDefaultMax: Boolean + get() = grbit and 0x2 == 0x2 + + val isDefaultMajorUnits: Boolean + get() = grbit and 0x4 == 0x4 + + val isDefaultMinorUnits: Boolean + get() = grbit and 0x8 == 0x8 + + override fun init() { + super.init() + // 20071223 KSC: Start parsing of options + catMin = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + catMax = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + catMajor = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + duMajor = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + catMinor = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + duMinor = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + duBase = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + catCrossDate = ByteTools.readShort(this.getByteAt(14).toInt(), this.getByteAt(15).toInt()) + grbit = ByteTools.readShort(this.getByteAt(16).toInt(), this.getByteAt(17).toInt()) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -660100252646337769L + + // 20070723 KSC: Need to create new records + // important when we parse these options ... + val prototype: XLSRecord? + get() { + val a = Axcent() + a.opcode = XLSConstants.AXCENT + a.data = a.PROTOTYPE_BYTES + a.init() + return a + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Axesused.java b/src/main/java/io/starter/formats/XLS/charts/Axesused.kt similarity index 70% rename from src/main/java/io/starter/formats/XLS/charts/Axesused.java rename to src/main/java/io/starter/formats/XLS/charts/Axesused.kt index b9d54fa..45b7eb2 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Axesused.java +++ b/src/main/java/io/starter/formats/XLS/charts/Axesused.kt @@ -27,22 +27,25 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts /** - * Axesused: Number of Axes Sets (0x1046) - *

                      + * ** Axesused: Number of Axes Sets (0x1046) ** + * + * * 4 cAxes 2 number of axes sets */ -public class Axesused extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4809979818183513617L; +class Axesused : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } - @Override - public void init() { - super.init(); + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -4809979818183513617L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Axis.java b/src/main/java/io/starter/formats/XLS/charts/Axis.java deleted file mode 100644 index e3d6127..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Axis.java +++ /dev/null @@ -1,2332 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.*; -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.XLSRecord; -import io.starter.formats.cellformat.CellFormatFactory; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -//OOXML-specific structures -//OOXML-specific structures - -/** - * Axis: Axis Type (0x101d) - *

                      - * 4 wType 2 axis type (0= category axis or x axis on a scatter chart, 1= value axis, 2= series axis - * 6 (reserved) 16 0 - *

                      - * Order of Axis Subrecords: - * X (Cat Axis) - *

                      - * CatSerRange - * AxcExt - * CatLab - * [IfmtRecord] - * [Tick] - * [FontX] - * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor - * [AreaFormat, GelFrame] - * [Shape or TextPropsStream] - * [CrtMltFrt] - *

                      - * Y (Value Axis) - * ValueRange - * [YMult -->Text-->Pos...] -- display units - * [IfmtRecord] - * [Tick] - * [FontX] - * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor - * [AreaFormat, GelFrame] - * [Shape or TextPropsStream] - * [CrtMltFrt] - *

                      - * XY (Series Axis) - * CatSerRange - * [IfmtRecord] - * [Tick] - * [FontX] - * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor - * [AreaFormat, GelFrame] - * [Shape or TextPropsStream] - * [CrtMltFrt] - */ -/* General info on Excel axis types: - * In Microsoft Excel charts, there are different types of X axes. - * While the Y axis is a Value type axis (i.e. containing a ValueRange record), - * the X axis can be a Category type axis or a Value type axis. - * Using a Value axis, the data is treated as continuously varying numerical data, - * and the marker is placed at a point along the axis which varies according to its - * numerical value. - * Using a Category axis, the data is treated as a sequence of non-numerical text labels, - * and the marker is placed at a point along the axis according to its position in - * the sequence. - * - * Note that Scatter (x/y) charts and Bubble charts have two Value axes, pie and donut charts have no axes - * - * How do you arrange your chart so the categories are displayed along the Y axis? - * The method involves adding a dummy series along the Y axis, - * applying data labels to its points for category labels, - * and making the original Y axis disappear. - */ -public class Axis extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8592219101790307789L; - short wType = 0; - private TextDisp linkedtd = null; // 20070730 KSC: hold axis legend TextDisp for this axis - private AxisParent ap = null; // 20090108 KSC: links to this axes parent -- - // Axis placement - public static final int INVISIBLE = 0; - public static final int LOW = 1; - public static final int HIGH = 2; - public static final int NEXTTO = 3; - // OOXML-specific - private SpPr shapeProps = null; // 20081224 KSC: OOXML-specific holds the shape properties (line and fill) for this axis - private Title ttl = null; // OOXML title element - private TxPr txpr = null; // text properties for axis - private NumFmt nf = null; // NumFmt prop for axis - String axPos = null; - - public void init() { - super.init(); - wType = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - - public short getAxis() { - return wType; - } - - public void setAxis(int wType) { - this.wType = (short) wType; - if (wType == XVALAXIS) wType = XAXIS; // 20090108 KSC: XVALAXIS is type of X axis with VAL records - byte[] b = ByteTools.shortToLEBytes((short) wType); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - public static XLSRecord getPrototype(int wType) { - Axis a = new Axis(); - a.setOpcode(AXIS); - a.setData(a.PROTOTYPE_BYTES); -// if (wType!=XVALAXIS) - a.setAxis(wType); -// else -// a.setAxis(XAXIS); - // also add the associated records - switch (wType) { - case XAXIS: - a.addChartRecord(CatserRange.getPrototype()); - a.addChartRecord(Axcent.getPrototype()); - a.addChartRecord(Tick.getPrototype()); - break; - case YAXIS: - case XVALAXIS: - a.addChartRecord(ValueRange.getPrototype()); - a.addChartRecord(Tick.getPrototype()); - AxisLineFormat alf = (AxisLineFormat) AxisLineFormat.getPrototype(); - alf.setId(AxisLineFormat.ID_MAJOR_GRID); // default has major gridlines - a.addChartRecord(alf); - a.addChartRecord(LineFormat.getPrototype()); - break; - case ZAXIS: - // KSC: TODO: Set CatserRange options correctly when get def!!! ********** - CatserRange c = (CatserRange) CatserRange.getPrototype(); - a.addChartRecord(c); - a.addChartRecord(Tick.getPrototype()); // TODO: Tick should have - break; - } - return a; - } - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - boolean hasMajorGridLines = false; - // Axis 0: CatserRange, Axcent, Tick [AxisLineFormat, LineFormat, AreaFormat] last 3 recs are for 3d formatting - // Axis 1: ValueRange, Tick, AxisLineFormat, LineFormat [AreaFormat, LineFormat, AreaFormat] " " - // Axis 2: [CatserRange, Tick] Z axis, for surface charts (only??) - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - // Handle subordinate record options here rather than in the specific rec - if (b instanceof CatserRange) { - CatserRange c = ((CatserRange) b); - // record deviations from defaults - if (c.getCatCross() != 1) - sb.append(" CatCross=\"" + c.getCatCross() + "\""); - if (c.getCatLabel() != 1) - sb.append(" LabelCross=\"" + c.getCatLabel() + "\""); - if (c.getCatMark() != 1) - sb.append(" Marks=\"" + c.getCatMark() + "\""); - if (!c.getCrossBetween()) - sb.append(" CrossBetween=\"false\""); - if (c.getCrossMax()) - sb.append(" CrossMax=\"true\""); - } else if (b instanceof AxisLineFormat) { // necessary for 3d charts and bg colors ... - int id = ((AxisLineFormat) b).getId(); - if (id == AxisLineFormat.ID_MAJOR_GRID) // Y Axis draw gridline usually true, trap if do not have (see below) - hasMajorGridLines = true; - else if (id == AxisLineFormat.ID_WALLORFLOOR) // indicates area - sb.append(" AddArea=\"true\""); - } else if (b instanceof AreaFormat) { // should ONLY be present if AxisLineFormat AddArea - // if not default background colors, record and reset - int icvFore = ((AreaFormat) b).geticvFore(); - int icvBack = ((AreaFormat) b).geticvBack(); - if (icvBack == -1) icvBack = 0x4D; - if (icvFore == 1) icvFore = 0x4E; - // if not defaults, set fore and back of walls, sides or floor - if ((wType == 0 && icvFore != 22) // xaxis - || (wType == 1 && icvFore != -1)) // yaxis - sb.append(" AreaFg=\"" + icvFore + "\""); - if ((wType == 0 && icvBack != 0) // xaxis - || (wType == 1 && icvBack != 1)) // yaxis - sb.append(" AreaBg=\"" + icvBack + "\""); - } - // TODO: parse LineFormat and, if not standard, add as option - // TODO: Parse Tick for options - } - if (wType == 1 && !hasMajorGridLines) // most Y Axes have major grid lines; flag if do not - sb.append(" MajorGridLines=\"false\""); - return sb.toString(); - } - - /** - * get/set linked TextDisp (legend for this axis) - * used for setting axis options - * - * @param td - */ - public void setTd(TextDisp td) { - linkedtd = td; - } - - public TextDisp getTd() { - if (linkedtd == null) - getTitleTD(false); - return linkedtd; - } - - /** - * remove the title for this axis - */ - public void removeTitle() { - if (linkedtd != null) { - int x = Chart.findRecPosition(ap.chartArr, TextDisp.class); - while (x > -1) { - if (((TextDisp) ap.chartArr.get(x)).getType() == TextDisp.convertType(wType)) { - ap.chartArr.remove(x); - break; - } - x++; - if (((BiffRec) ap.chartArr.get(x)).getOpcode() != TEXTDISP) - break; - } - } - linkedtd = null; - } - - /** - * returns true if plot area has a bonding box/border - * - * @return - */ - public boolean hasPlotAreaBorder() { - Frame f = (Frame) Chart.findRec(ap.chartArr, Frame.class); - if (f != null) - return f.hasBox(); - return false; - } - - /** - * link this axis with it's parent - * - * @param a - */ - public void setAP(AxisParent a) { - ap = a; - } - - /** - * return the Title associated with this Axis (through the linked TextDisp record) - * - * @return - * @deprecated use getTitle - */ - public String getLabel() { - return getTitle(); - - } - - public String getTitle() { - if (linkedtd == null) - getTitleTD(false); - if (linkedtd != null) - return linkedtd.toString(); - return ""; - } - - - /** - * return the Font object associated with the Axis Title, or null if none - */ - public Font getTitleFont() { - try { - if (linkedtd != null) { - Fontx fx = (Fontx) Chart.findRec(linkedtd.chartArr, Fontx.class); - return this.getParentChart().getWorkBook().getFont(fx.getIfnt()); - } - } catch (Exception e) { - } - return null; - } - - /** - * finds or adds TextDisp records for this axis (for labels and fonts) - */ - private void getTitleTD(boolean add) { - int tdtype = TextDisp.convertType(wType); - int pos = 0; - // Must add if can't find existing label - int x = Chart.findRecPosition(ap.chartArr, TextDisp.class); - pos = x; - while (x > 0 && linkedtd == null) { - TextDisp td = (TextDisp) ap.chartArr.get(x); - if (td.getType() == tdtype) // found it - linkedtd = td; - else { - if (!(ap.chartArr.get(++x) instanceof TextDisp)) - x = -1; - } - } - if (linkedtd == null && add) { - linkedtd = (TextDisp) TextDisp.getPrototype(tdtype, "", this.wkbook); - if (pos < 0) - pos = Chart.findRecPosition(ap.chartArr, PlotArea.class); - else { // otherwise, TextDisp(s) exist already; position correctly - // set font to other axis font as default - TextDisp td = (TextDisp) ap.chartArr.get(pos); - linkedtd.setFontId(td.getFontId()); - if (wType != XAXIS) - pos++; - } - if (pos < 0) - pos = Chart.findRecPosition(ap.chartArr, ChartFormat.class); - linkedtd.setParentChart(this.getParentChart()); - ap.chartArr.add(pos, linkedtd); - } - } - - /** - * set the Title associated with this Axis (through the linked TextDisp record) - * - * @param l - */ - public void setTitle(String l) { - if (l == null) { - this.removeTitle(); - return; - } - if (linkedtd == null) { - getTitleTD(true); // finds or adds TextDisp records for this axis (for labels and fonts) - } - linkedtd.setText(l); - } - - /** - * set the font index for this Axis (for title) - * - * @param fondId - */ - public void setFont(int fondId) { - if (linkedtd == null) { - getTitleTD(true); // finds or adds TextDisp records for this axis (for labels and fonts) - } - linkedtd.setFontId(fondId); - } - - /** - * returns the font of the Axis Title - */ - public io.starter.formats.XLS.Font getFont() { - if (linkedtd == null) { - getTitleTD(true); // finds or adds TextDisp records for this axis (for labels and fonts) - } - int idx = linkedtd.getFontId(); - return this.getWorkBook().getFont(idx); - } - - /** - * return the Font object used for Axis labels - * - * @return - */ - public io.starter.formats.XLS.Font getLabelFont() { - try { - Fontx fx = (Fontx) Chart.findRec(chartArr, Fontx.class); - return this.getParentChart().getWorkBook().getFont(fx.getIfnt()); - } catch (NullPointerException e) { - return this.getParentChart().getDefaultFont(); - } - - } - - /** - * utility method to find the CatserRange rec associated with this Axis - * - * @return - */ - protected CatserRange getCatserRange(boolean bCreate) { - CatserRange csr = (CatserRange) Chart.findRec(chartArr, CatserRange.class); - if (csr == null) { - csr = (CatserRange) CatserRange.getPrototype(); - csr.setParentChart(this.getParentChart()); - chartArr.add(0, csr); - } - return csr; - } - - /** - * returns true of this axis displays major gridlines - * - * @return - */ - protected boolean hasGridlines(int type) { - int j = Chart.findRecPosition(chartArr, AxisLineFormat.class); - if (j != -1) { - try { - while (j < chartArr.size()) { - AxisLineFormat al = (AxisLineFormat) chartArr.get(j); - int id = al.getId(); - if (id == type) // Y Axis draw gridline usually true, trap if do not have (see below) - return true; - j += 2; // Skip line format - } - } catch (ClassCastException e) { - } - } - return false; - } - - /** - * returns the SVG necessary to describe the desired line (referenced by id) - * - * @param id @see AxisLineFormat id types - * @return - */ - protected String getLineSVG(int id) { - LineFormat lf = getAxisLine(id); - if (lf != null) - return lf.getSVG(); - return ""; - } - - /** - * return the lineformat rec for the given axis line type - * - * @param type AxisLineFormat type - * @return Line Format - * @see AxisLineFormat.ID_MAJOR_GRID, et. atl. - */ - protected LineFormat getAxisLine(int type) { - int j = getAxisLineFormat(type); - if (j > -1) - return (LineFormat) chartArr.get(j + 1); - - return null; - } - - /** - * return the AxisLineFormat of the desired type, or create if none and bCreate - * - * @param type AxisLineFormat Type: AxisLineFormat.ID_AXIS_LINE, AxisLineFormat.ID_MAJOR_GRID, AxisLineFormat.ID_MINOR_GRID, AxisLineFormat.ID_WALLORFLOOR - * @param bCreate - * @return - */ - protected AxisLineFormat getAxisLineFormat(int type, boolean bCreate) { - AxisLineFormat alf = null; - int j = Chart.findRecPosition(chartArr, AxisLineFormat.class); - if (j == -1 && !bCreate) - return null; - if (j > -1) { - try { - while (j < chartArr.size()) { - alf = (AxisLineFormat) chartArr.get(j); - if (alf.getId() == type) - return alf; - else if (alf.getId() > type) - break; - j += 2; - } - } catch (ClassCastException e) { - } - } - j = 1; - for (; j < chartArr.size(); j++) { - BiffRec b = chartArr.get(j); - if (b.getOpcode() == AREAFORMAT || - b.getOpcode() == GELFRAME || - b.getOpcode() == 2213 || /* TextPropsStream */ - b.getOpcode() == 2212 || /* ShapePropsStream */ - b.getOpcode() == 2206) /* CtrlMlFrt */ - break; - } - alf = (AxisLineFormat) AxisLineFormat.getPrototype(); - alf.setId(type); // default has major gridlines - chartArr.add(j++, alf); - alf.setParentChart(this.getParentChart()); - LineFormat lf = (LineFormat) LineFormat.getPrototype(); - lf.setParentChart(this.getParentChart()); - chartArr.add(j, lf); - return alf; - - } - - /** - * return the index of the AxisLineFormat of the desired type, -1 if none - * - * @param type AxisLineFormat Type: AxisLineFormat.ID_AXIS_LINE, AxisLineFormat.ID_MAJOR_GRID, AxisLineFormat.ID_MINOR_GRID, AxisLineFormat.ID_WALLORFLOOR - * @return - */ - protected int getAxisLineFormat(int type) { - AxisLineFormat alf = null; - int j = Chart.findRecPosition(chartArr, AxisLineFormat.class); - if (j == -1) return j; - try { - while (j < chartArr.size()) { - alf = (AxisLineFormat) chartArr.get(j); - if (alf.getId() == type) - return j; - else if (alf.getId() > type) - break; - j += 2; - } - } catch (ClassCastException e) { - } - if (j > chartArr.size()) - return -1; // not found - return j; - - } - - /** - * gets or creates YMult (value multiplier) record - * - * @param bCreate - * @return - */ - protected YMult getYMultRec(boolean bCreate) { - YMult ym = (YMult) Chart.findRec(chartArr, YMult.class); - if (ym == null && bCreate) { - ym = (YMult) YMult.getPrototype(); - ym.setParentChart(this.getParentChart()); - chartArr.add(1, ym); // 2nd, after ValueRange - } - return ym; - } - - /** - * returns true if this axis is reversed - *
                      If horizontal axis, default= on bottom, reversed= on top - *
                      If vertical axis, default= LHS, reversed= RHS - * - * @return - */ - public boolean isReversed() { - if (wType == XAXIS) { - CatserRange c = getCatserRange(false); - if (c != null) - return c.isReversed(); - } - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { // shouldn't - return v.isReversed(); - } - return false; - } - - public String getNumberFormat() { - Ifmt f = (Ifmt) Chart.findRec(this.chartArr, Ifmt.class); - int i = 0; - if (f != null) { - i = f.getFmt(); - } else { - // see if have series-specific formats - java.util.Vector s = this.getParentChart().getAllSeries(-1); - if (s.size() > 0) { - if (wType == YAXIS) { - return ((Series) s.get(0)).getSeriesFormatPattern(); - } else // see if it's a value X axis with a custom - return ((Series) s.get(0)).getCategoryFormatPattern(); - } - } - return "General"; - } - - /** - * return the JSON/Dojo representation of this axis - * chartType int necessary for parsing AXIS options: horizontal charts "switch" axes ... - * All options have NOT been gathered at this point - * - * @return JSONObject - */ - public JSONObject getJSON(io.starter.OpenXLS.WorkBookHandle wbh, int chartType, double yMax, double yMin, int nSeries) { - JSONObject axisJSON = new JSONObject(); - JSONObject axisOptions = new JSONObject(); - try { - if (wType == YAXIS && chartType != ChartConstants.BARCHART) { - axisOptions.put("vertical", true); - } else if (wType == XAXIS && chartType == ChartConstants.BARCHART) - axisOptions.put("vertical", true); - - // 20090721 KSC: dojo 1.3.1 has label element - axisOptions.put("label", getTitle()); - - // TODO: Dojo Axis Options: - // fixLower, fixUpper ("minor"/"major") - // includeZero, natural (true) - // min, max - // minorTicks, majorTicks, microTicks (true/false) - // minorTickStep, majorTickStep, microTickStep (#) - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b instanceof CatserRange) { - CatserRange c = ((CatserRange) b); - // for x/Category axis: if has labels, gather and input into axis label JSON array - String[] categories = this.getParentChart().getCategories(-1); - if (categories != null) { - // Category Labels - JSONArray labels = new JSONArray(); - if (c.getCrossBetween()) { // categories appear mid-axis so put "spacers" at 0 and max - axisOptions.put("includeZero", true); // always true???? - JSONObject nullCat = new JSONObject(); - nullCat.put("value", 0); - nullCat.put("text", ""); - labels.put(nullCat); - } - JSONArray cats = CellRange.getValuesAsJSON(categories[0], wbh); // parse category range into JSON Array - for (int z = 0; z < cats.length(); z++) { - JSONObject aCat = new JSONObject(); - aCat.put("value", z + c.getCatLabel()); // should= +1 i.e. a category label appears with each category - aCat.put("text", cats.get(z)); - labels.put(aCat); - } - if (c.getCrossBetween()) { // categories appear mid-axis so put "spacers" at 0 and max - JSONObject nullCat = new JSONObject(); - nullCat.put("value", cats.length() + c.getCatLabel()); - nullCat.put("text", ""); - labels.put(nullCat); - axisOptions.put("max", cats.length() + c.getCatLabel()/*+1*/); - } - axisOptions.put("labels", labels); - // Defaults axis values ... - axisOptions.put("fixLower", "major"); // what do these do?? - axisOptions.put("fixUpper", "major"); - } - -// if (c.getCatCross()!=1) - if (c.getCatMark() != 1) { -/* The catMark field defines how often tick marks appear along the category or series axis. A value of 01 indicates - that a tick mark will appear between each category or series; a value of 02 means a label appears between every - other category or series, etc. -*/ - } - -// if (c.getCrossMax()) - } else if (b instanceof ValueRange) { - ValueRange v = (ValueRange) b; - if (wType == YAXIS) // normal - v.setMaxMin(yMax, yMin); // must do first - else - v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series - - // y major/minor scales - axisOptions.put("min", v.getMin()); - axisOptions.put("max", v.getMax()); - axisOptions.put("majorTickStep", v.getMajorTick()); - } else if (b instanceof AxisLineFormat) { - JSONObject gridJSON = new JSONObject(); - gridJSON.put("type", "Grid"); - int id = ((AxisLineFormat) b).getId(); - switch (id) { - case AxisLineFormat.ID_MAJOR_GRID: - if (wType == XAXIS || chartType == ChartConstants.BARCHART) - gridJSON.put("hMajorLines", false); - else - gridJSON.put("vMajorLines", false); - break; - case AxisLineFormat.ID_MINOR_GRID: - break; - } - axisJSON.put("back_grid", gridJSON); - } - } - if (wType == YAXIS) - axisJSON.put("y", axisOptions); - if (wType == XAXIS) - axisJSON.put("x", axisOptions); - } catch (JSONException e) { - Logger.logErr("Error getting Axis JSON: " + e); - } - return axisJSON; - } - - /** - * interface for setting Axis rec-specific XML options - * in a generic fashion - * - * @see OpenXLS.handleChartElement - * @see ChartHandle.getXML - */ - public boolean setChartOption(String op, String val) { - if (op.equalsIgnoreCase("Label")) { - this.setTitle(val); - return true; - } else if (op.equalsIgnoreCase("CatCross")) { - getCatserRange(true).setCatCross(Integer.parseInt(val)); - return true; - } else if (op.equalsIgnoreCase("LabelCross")) { - getCatserRange(true).setCatLabel(Integer.parseInt(val)); - return true; - } else if (op.equalsIgnoreCase("Marks")) { - getCatserRange(true).setCatMark(Integer.parseInt(val)); - return true; - } else if (op.equalsIgnoreCase("CrossBetween")) { - getCatserRange(true).setCrossBetween(val.equals("true")); - return true; - } else if (op.equalsIgnoreCase("CrossMax")) { - getCatserRange(true).setCrossMax(val.equals("true")); - return true; - } else if (op.equalsIgnoreCase("MajorGridLines")) { - if (val.equals("false")) { - int j = getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID); - if (j > -1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true); - } - } else if (op.equalsIgnoreCase("MinorGridLines")) { - if (val.equals("false")) { - int j = getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID); - if (j > -1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true); - } - } else if (op.equalsIgnoreCase("AddArea")) { - if (wType == XAXIS) { - AxisLineFormat alf0 = (AxisLineFormat) AxisLineFormat.getPrototype(); - alf0.setId(AxisLineFormat.ID_AXIS_LINE); - this.addChartRecord(alf0); - LineFormat lf0 = (LineFormat) LineFormat.getPrototype(0, 0); - this.addChartRecord(lf0); - } - AxisLineFormat alf = (AxisLineFormat) AxisLineFormat.getPrototype(); - alf.setId(AxisLineFormat.ID_WALLORFLOOR); - this.addChartRecord(alf); - LineFormat lf = (LineFormat) LineFormat.getPrototype(0, -1); - if (wType == 1) - lf.setLineStyle(5); //none - this.addChartRecord(lf); - AreaFormat af = (AreaFormat) AreaFormat.getPrototype(wType); - this.addChartRecord(af); - return true; - } else if (op.equals("AreaFg")) { // custom foreground on Wall, Side or Floor - AreaFormat af = (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); - af.seticvFore(Integer.valueOf(val).intValue()); - return true; - } else if (op.equals("AreaBg")) { // custom bg on Wall, SIde or Floor - AreaFormat af = (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); - af.seticvBack(Integer.valueOf(val).intValue()); - return true; - } else if (linkedtd != null) { // see if associated TextDisp can handle - return linkedtd.setChartOption(op, val); - } else if (op.equalsIgnoreCase("MajorGridLines")) { - if (val.equals("false")) { - int j = getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID); - if (j > -1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true); - } - } else if (op.equalsIgnoreCase("MinorGridLines")) { - if (val.equals("false")) { - int j = getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID); - if (j > -1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true); - } - } - return false; - } - - /** - * sets a specific OOXML axis option; most options apply to any type of axis (Cat, Value, Ser, Date) - *
                      can be one of: - *
                      axPos - position of the axis (b, t, r, l) - *
                      crosses possible crossing points (autoZero, max, min) - *
                      crossBeteween whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) - *
                      crossesAt where on axis the perpendicular axis crosses (double val) - *
                      lblAlign text alignment for tick labels (ctr, l, r) (cat only) - *
                      lblOffset distance of labels from the axis (0-1000) (cat only) - *
                      majorTickMark major tick mark position (cross, in, none, out) - *
                      minorTickMark minor tick mark position ("") - *
                      tickLblPos tick label position (high, low, nextTo, none) - *
                      tickLblSkip how many tick labels to skip between label (int >= 1) (cat only) - *
                      tickMarkSkip how many tick marks to skip betwen ticks (int >= 1) (cat only) - *
                      majorUnit distance between major tick marks (val, date ax only) (double >= 0) - *
                      minorUnit distance between minor tick marks (val, date ax only) (double >= 0) - *
                      MajorGridLines - *
                      MinorGridLines - * - * @param op - * @param val - */ - private void setOption(String op, String val) { - if (op.equals("axPos")) { // val= "b" (bottom) "l", "t", "r" -->????? - axPos = val; // for now - } else if (op.equals("lblOffset") || - op.equals("lblAlgn")) { - CatLab cl = (CatLab) Chart.findRec(chartArr, CatLab.class); - if (cl == null) { - cl = (CatLab) CatLab.getPrototype(); - cl.setParentChart(this.getParentChart()); - chartArr.add(1, cl); // second in chart array, after CatSerRange - } - cl.setOption(op, val); - } else if (op.equals("tickLblPos") || // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick - op.equals("majorTickMark") || // major tick marks (cross, in, none, out) - op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) - Tick t = (Tick) Chart.findRec(chartArr, Tick.class); - t.setOption(op, val); - } else if (op.equalsIgnoreCase("MajorGridLines")) { - if (val.equals("false")) { - int j = getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID); - if (j > -1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true); - } - } else if (op.equalsIgnoreCase("MinorGridLines")) { - if (val.equals("false")) { - int j = getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID); - if (j > -1) { - chartArr.remove(j); // remove AxisLineFormat - chartArr.remove(j); // remove corresponding Line Format - } - } else { // add major grid lines - getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true); - } - } else { // valuerange, caterrange options -- crosses, crossBetween, crossesAt, tickMarkSkip (cat only), tickLblSkip (cat only), majorUnit (val only), minorUnit (val only) -// KSC: TESTING -//Logger.logInfo("Setting option: " + op + "=" + val); - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b instanceof CatserRange) { - if (((CatserRange) b).setOption(op, val)) - break; - } else if (b instanceof ValueRange) { - if (((ValueRange) b).setOption(op, val)) - break; - } - } - } - } - - /** - * get the desired axis option for this axis - *
                      can be one of: - *
                      axPos - position of the axis (b, t, r, l) - *
                      crosses possible crossing points (autoZero, max, min) - *
                      crossBeteween whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) - *
                      crossesAt where on axis the perpendicular axis crosses (double val) - *
                      lblAlign text alignment for tick labels (ctr, l, r) (cat only) - *
                      lblOffset distance of labels from the axis (0-1000) (cat only) - *
                      majorTickMark major tick mark position (cross, in, none, out) - *
                      minorTickMark minor tick mark position ("") - *
                      tickLblPos tick label position (high, low, nextTo, none) - *
                      tickLblSkip how many tick labels to skip between label (int >= 1) (cat only) - *
                      tickMarkSkip how many tick marks to skip betwen ticks (int >= 1) (cat only) - *
                      majorUnit distance between major tick marks (val, date ax only) (double >= 0) - *
                      minorUnit distance between minor tick marks (val, date ax only) (double >= 0) - * - * @param op String option name - * @return String val of option or "" - */ - public String getOption(String op) { - if (op.equals("axPos")) { // val= "b" (bottom) "l", "t", "r" -->????? - return axPos; // for now -- can't find matching Axis attribute - } else if (op.equals("lblAlign") || - op.equals("lblOffset")) { - CatLab c = (CatLab) Chart.findRec(chartArr, CatLab.class); - if (c != null) - return c.getOption(op); - return null; // use defaults - } else if (op.equals("crossesAt") || // specifies where axis crosses -- numCross or catCross - op.equals("orientation") || // axis orientation minMax or maxMin -- fReverse - op.equals("crosses") || // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross - op.equals("max") || // axis max - valueRange only? - op.equals("max") || // axis min- valueRange only? - op.equals("tickLblSkip") || //val= how many tick labels to skip btwn label -- catLabel -- Catserrange only?? - op.equals("tickMarkSkip") || //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? - op.equals("crossBetween")) { // value axis only -- val= between, midCat, crossBetween - // logScale-- ValueRange - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b instanceof CatserRange) { - return ((CatserRange) b).getOption(op); - } else if (b instanceof ValueRange) { - return ((ValueRange) b).getOption(op); - } - } - //TICK Options - } else if (op.equals("tickLblPos") || // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick - op.equals("majorTickMark") || // major tick marks (cross, in, none, out) - op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) - Tick t = (Tick) Chart.findRec(chartArr, Tick.class); - return t.getOption(op); - } - return null; - } - - /** - * return the JSON/Dojo representation of this axis - * chartType int necessary for parsing AXIS options: horizontal charts "switch" axes ... - * - * @return JSONObject - */ - public JSONObject getMinMaxJSON(io.starter.OpenXLS.WorkBookHandle wbh, int chartType, double yMax, double yMin, int nSeries) { - JSONObject axisJSON = new JSONObject(); - JSONObject axisOptions = new JSONObject(); - try { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b instanceof CatserRange) { - CatserRange c = ((CatserRange) b); - // for x/Category axis: if has labels, gather and input into axis label JSON array - String[] categories = this.getParentChart().getCategories(-1); - if (categories != null) { - JSONArray cats = CellRange.getValuesAsJSON(categories[0], wbh); // parse category range into JSON Array - axisOptions.put("max", cats.length() + c.getCatLabel()/*+1*/); - } - break; // only need this per axis - } else if (b instanceof ValueRange) { - ValueRange v = (ValueRange) b; - if (wType == YAXIS) // normal - v.setMaxMin(yMax, yMin); // must do first - else - v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series - - // y major/minor scales - axisOptions.put("min", v.getMin()); - axisOptions.put("max", v.getMax()); - axisOptions.put("majorTickStep", v.getMajorTick()); - break; // only need this per axis - } - } - if (wType == YAXIS) - axisJSON.put("y", axisOptions); - if (wType == XAXIS) - axisJSON.put("x", axisOptions); - } catch (JSONException e) { - Logger.logErr("Error getting Axis JSON: " + e); - } - return axisJSON; - } - - /** - * return the OOXML shape property for this axis - * - * @return - */ - public SpPr getSpPr() { - return shapeProps; - } - - /** - * define the OOXML shape property for this axis from an existing spPr element - */ - public void setSpPr(SpPr sp) { - shapeProps = sp; - //shapeProps.setNS("c"); - } - - /** - * return the OOXML title element for this axis - * - * @return - */ - public io.starter.formats.OOXML.Title getOOXMLTitle() { - return ttl; - } - - /** - * set the OOXML title element for this axis - * - * @param t - */ - public void setOOXMLTitle(Title t) { - ttl = t; - } - - /** - * return the OOXML txPr element for this axis - * - * @return - */ - public TxPr gettxPr() { - return txpr; - } - - /** - * set the OOXML title element for this axis - * - * @param t - */ - public void settxPr(TxPr t) { - txpr = (TxPr) t.cloneElement(); - } - - public String toString() { - String s = ""; - switch (wType) { - case XAXIS: - s = "XAxis"; - break; - case YAXIS: - s = "YAxis"; - break; - case ZAXIS: - s = "ZAxis"; - break; - case XVALAXIS: - s = "XValAxis"; - break; - } - if (linkedtd != null) - s = s + " " + linkedtd.toString(); - return s; - } - - /** - * return the maximum value of this Value or Y axis scale - * - * @return - */ - public double getMaxScale(double[] minmax) { - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { // shouldn't - if (v.isAutomaticMax()) - v.setMaxMin(minmax[1], minmax[0]); - return v.getMax(); - } - return -1; - } - - protected double[] getMinMax() { - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { // shouldn't - return new double[]{v.getMin(), v.getMax()}; - } - return new double[]{0, 0}; - - } - - /** - * return the minimum value of this value or Y axis scale - * - * @return - */ - public double getMinScale(double[] minmax) { - /* - * Because a horizontal (category) axis (axis: A line bordering the chart plot area used as - * a frame of reference for measurement. * The y axis is usually the vertical axis and contains data. The x-axis is usually the horizontal axis and contains categories.) - * displays text labels instead of numeric intervals, - * there are fewer scaling options that you can change than there are for a vertical (value) axis. - * However, you can change the number of categories to display between tick marks, the order in which - * to display categories, and the point where the two axes cross. - */ - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { - /* - if (wType==YAXIS) // normal - v.setMaxMin(yMax, yMin); // must do first - else - v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series - */ -// why? should already v.setParentChart(this.getParentChart()); - if (v.isAutomaticMin()) - v.setMaxMin(minmax[1], minmax[0]); - return v.getMin(); - } - return -1; - } - - /** - * return the major tick unit of this Y or Value axis - * - * @return - */ - public int getMajorUnit() { - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { - return new Double(v.getMajorTick()).intValue(); - } - return 10; // try a default - } - - /** - * return the minor tick unit of this Y or Value axis - * - * @return - */ - public int getMinorUnit() { - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { - return new Double(v.getMinorTick()).intValue(); - } - return 0; // try a default - } - - /** - * returns true if either Automatic min or max scale is set for the Y or Value axis - * - * @return - */ - public boolean isAutomaticScale() { - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { - return (v.isAutomaticMin() || v.isAutomaticMax()); - } - return false; - } - - - /** - * sets the automatic scale option on or off for the Y or Value axis - *
                      Automatic Scaling will automatically set the scale maximum, minimum and tick units - * - * @param b - */ - public void setAutomaticScale(boolean b) { - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { - v.setAutomaticMin(b); - v.setAutomaticMax(b); - } - } - - /** - * set the minimum value of this axis scale - *
                      Note: this disables automatic scaling - * - * @param Min - */ - public void setMinScale(int min) { - // TODO: also update ticks? - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { // shouldn't - v.setMin(min); - } - } - - /** - * set the maximum value of this axis scale - *
                      Note: this disables automatic scaling - * - * @param Max - */ - public void setMaxScale(int max) { - // TODO: also update ticks? - ValueRange v = (ValueRange) Chart.findRec(this.chartArr, ValueRange.class); - if (v != null) { // shouldn't - v.setMax(max); - } - } - - /** - * sets the axis labels position or placement to the desired value (these match Excel placement options) - *

                      Possible options: - *
                      Axis.INVISIBLE - hides the axis - *
                      Axis.LOW - low end of plot area - *
                      Axis.HIGH - high end of plot area - *
                      Axis.NEXTTO- next to axis (default) - * - * @param Placement - int one of the Axis placement constants listed above - */ - public void setAxisPlacement(int Placement) { - Tick t = (Tick) Chart.findRec(this.chartArr, Tick.class); - if (t != null) { // shoudn't - switch (Placement) { - case Axis.INVISIBLE: - t.setOption("tickLblPos", "none"); - break; - case Axis.LOW: - t.setOption("tickLblPos", "low"); - break; - case Axis.HIGH: - t.setOption("tickLblPos", "high"); - break; - case Axis.NEXTTO: - t.setOption("tickLblPos", "nextTo"); - } - } - } - - /** - * returns the Axis Label Placement or position as an int - *

                      One of: - *
                      Axis.INVISIBLE - axis is hidden - *
                      Axis.LOW - low end of plot area - *
                      Axis.HIGH - high end of plot area - *
                      Axis.NEXTTO- next to axis (default) - * - * @return int - one of the Axis Label placement constants above - */ - public int getAxisPlacement() { - Tick t = (Tick) Chart.findRec(this.chartArr, Tick.class); - if (t != null) { // shoudn't - String p = t.getOption("tickLblPos"); - if (p == null || p.equals("none")) - return Axis.INVISIBLE; - else if (p.equals("low")) - return Axis.LOW; - else if (p.equals("high")) - return Axis.HIGH; - else if (p.equals("nextTo")) - return Axis.NEXTTO; - } - return Axis.INVISIBLE; - } - - /** - * parse OOXML axis element - * - * @param xpp XmlPullParser positioned at correct elemnt - * @param axisTag catAx, valAx, serAx, dateAx - * @param lastTag Stack of element names - */ - // noMultiLvlLbl -- val= 1 (true) means draw labels as flat text; not included or 0 (false)= draw labels as a heirarchy - public void parseOOXML(XmlPullParser xpp, String axisTag, Stack lastTag, WorkBookHandle bk) { -// crossAx -- need to parse? -// auto -- need to parse? - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("scaling")) { // additional axis settings - lastTag.push(tnm); - Scaling sc = (Scaling) Scaling.parseOOXML(xpp, lastTag); - String s = sc.getOption("orientation"); - if (s != null) this.setOption("orientation", s); - s = sc.getOption("min"); - if (s != null) this.setOption("min", s); - s = sc.getOption("max"); - if (s != null) this.setOption("max", s); - // the below children only have 1 attribute: val - } else if (tnm.equals("axPos")) { // // position of the axis (b, t, r, l) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if (tnm.equals("majorGridlines") || - tnm.equals("minorGridlines")) { - lastTag.push(tnm); - parseGridlinesOOXML(xpp, lastTag, bk); - } else if (tnm.equals("title")) { - lastTag.push(tnm); - this.setOOXMLTitle((Title) Title.parseOOXML(xpp, lastTag, bk).cloneElement()); - this.setTitle(this.getOOXMLTitle().getTitle()); - } else if (tnm.equals("numFmt")) { - this.nf = (NumFmt) NumFmt.parseOOXML(xpp).cloneElement(); - } else if ( - tnm.equals("majorTickMark") || // major tick mark position (cross, in, none, out) - tnm.equals("minorTickMark") || // minor tick mark position ("") - tnm.equals("tickLblPos")) { // tick label position (high, low, nextTo, none) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if (tnm.equals("spPr")) { // axis shape properties - for axis or gridlines - lastTag.push(tnm); - this.setSpPr((SpPr) SpPr.parseOOXML(xpp, lastTag, bk).cloneElement()); - } else if (tnm.equals("txPr")) { // text Properties for axis - lastTag.push(tnm); - this.settxPr((TxPr) TxPr.parseOOXML(xpp, lastTag, bk).cloneElement()); - // crossesAx = crossing axis id - need ? - } else if ( - tnm.equals("crosses") || // possible crossing points (autoZero, max, min) - tnm.equals("crossesAt")) { // where on axis the perpendicular axis crosses (double val) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if (tnm.equals("crossBetween")) { // whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if ( // cat, date, ser ax only - // auto -- Date only? - tnm.equals("lblAlign") || // text alignment for tick labels (ctr, l, r) only for cat - tnm.equals("lblOffset") || // distance of labels from the axis (0-1000) only for cat, date - tnm.equals("tickLblSkip") || // how many tick labels to skip between label (int >= 1) - tnm.equals("tickMarkSkip")) { // how many tick marks to skip betwen ticks (int >= 1) - this.setOption(tnm, xpp.getAttributeValue(0)); -// TODO: noMultiLvlLbl - } else if ( // val, ser ax + some date ax - tnm.equals("crossBeteween") || // whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) - tnm.equals("majorUnit") || // distance between major tick marks (val, date ax only) (double >= 0) - tnm.equals("minorUnit")) { // distance between minor tick marks (val, date ax only) (double >= 0) - this.setOption(tnm, xpp.getAttributeValue(0)); - } else if (tnm.equals("dispUnits")) { // valAx only - parseDispUnitsOOXML(xpp, lastTag); - } -// TODO: date ax specifics - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals(axisTag)) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("Axis: " + e.toString()); - } - } - - /** - * parse major or minor Gridlines element - * - * @param xpp - * @param lastTag - */ - private void parseGridlinesOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - String endTag = lastTag.peek(); - this.setOption(endTag, "true"); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spPr")) { - lastTag.push(tnm); - SpPr sppr = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk).cloneElement(); - LineFormat lf = getAxisLine((endTag.equals("majorGridlines") ? AxisLineFormat.ID_MAJOR_GRID : AxisLineFormat.ID_MINOR_GRID)); - lf.setFromOOXML(sppr); - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(endTag)) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("parseGridLinesOOXML: " + e.toString()); - } - } - - /** - * parse the dispUnits child element of valAx - *
                      TODO: do not know how to interpret most of these options - * - * @param xpp - * @param lastTag - */ - private void parseDispUnitsOOXML(XmlPullParser xpp, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - YMult ym = null; - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("custUnit")) { - ym = getYMultRec(true); - ym.setCustomMultiplier(Double.valueOf(xpp.getAttributeValue(0))); - } else if (tnm.equals("builtInUnit")) { - ym = getYMultRec(true); - ym.setAxMultiplierId(xpp.getAttributeValue(0)); - } - // TODO: dispUnitLbl - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals("dispUnits")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("parseDispUnitsOOXML: " + e.toString()); - } - } - - /** - * generate the appropriate ooxml for the given axis - * - * @param type 0= Category Axis, 1= Value Axis, 2= Ser Axis, 3= Date Axis - * @param id - * @param crossId crossing axis id - * @return ORDER: - * Axis Common: - * axisId REQ - * scaling -- orientation - * delete - * axPos REQ? - * majorGridlines - * minorGridlines - * title - * numFmt - * majorTickMark - * minorTickMark - * tickLblPos - * spPr - * txPr - * crossAx - * crosses OR - * crossesAt - * after: - * valAx: - * crossBetween - * majorUnit - * minorUnit - * dispUnits - * catAx: - * auto - * lblAlign - * lblOffet - * tickLblSkip - * tickMarkSkip - * noMultiLvlLbl - * serAx: - * tickLblSkip - * tickMarkSkip - */ - - public String getOOXML(int type, String id, String crossId) { - if (this.getParentChart() == null) // happens on ZAxis, XYValAxis ... - this.setParentChart(this.ap.getParentChart()); - boolean from2003 = (!parentChart.getWorkBook().getIsExcel2007()); - - StringBuffer axisooxml = new StringBuffer(); - String axis = ""; - switch (type) { - case 0: // cat axis "X" - axis = "catAx"; - break; - case 1: // val axis "Y" - axis = "valAx"; - break; - case 2: // xval axis "X" axis for multiple val axes: bubble & scatter charts - axis = "valAx"; - break; - case 3: // ser ("Z" axis) - axis = "serAx"; - break; - case 4: // date TODO: Not correct or tested! - axis = "dateAx"; - } - // axis main element - axisooxml.append(""); - axisooxml.append("\r\n"); - // axId - required - axisooxml.append(""); - axisooxml.append("\r\n"); - // scaling - required - String s = this.getOption("orientation"); - double[] d = this.getMinMax(); - if (s != null || d[0] != d[1]) { // if have orientation or min/max set .. - axisooxml.append("\r\n"); - if (s != null) axisooxml.append("\r\n"); - axisooxml.append("\r\n"); - } - // axPos - required - if (this.getOption("axPos") != null) { - axisooxml.append(""); - axisooxml.append("\r\n"); - } else {// it's required - if (this.getParentChart().getChartType() != BARCHART) { - if (axis.equals("catAx") || axis.equals("serAx")) - axisooxml.append(""); - else - axisooxml.append(""); - } else { - if (axis.equals("catAx") || axis.equals("serAx")) - axisooxml.append(""); - else - axisooxml.append(""); - } - axisooxml.append("\r\n"); - } - // major Gridlines - if (this.hasGridlines(AxisLineFormat.ID_MAJOR_GRID)) { - axisooxml.append(""); - axisooxml.append(getAxisLine(AxisLineFormat.ID_MAJOR_GRID).getOOXML()); - axisooxml.append("\r\n"); - } - // minor Gridlines - if (this.hasGridlines(AxisLineFormat.ID_MINOR_GRID)) { - axisooxml.append(""); - axisooxml.append(getAxisLine(AxisLineFormat.ID_MINOR_GRID).getOOXML()); - axisooxml.append("\r\n"); - } - // Title - if (this.getOOXMLTitle() != null) axisooxml.append(this.getOOXMLTitle().getOOXML()); - else if (from2003) { // create OOXML title - if (!this.getTitle().equals("")) { - io.starter.formats.OOXML.Title ttl = new io.starter.formats.OOXML.Title(this.getTitle()); - if (type == 0) - ttl.setLayout(.026, .378); - else if (type == 1) - ttl.setLayout(.468, .863); - axisooxml.append(ttl.getOOXML()); - } - } - // numFmt - if (this.nf != null) - axisooxml.append(nf.getOOXML("c:")); //need a default???: axisooxml.append(""); axisooxml.append("\r\n"); - // majorTickMark - s = this.getOption("majorTickMark"); // default= "cross" - if (s != null) axisooxml.append(""); - // minorTickMark - s = this.getOption("minorTickMark"); // default= "cross" - if (s != null) axisooxml.append(""); - // tickLblPos - s = this.getOption("tickLblPos"); // default= "nextTo" - if (s != null) axisooxml.append(""); - // shape properties - if (this.getSpPr() != null) axisooxml.append(this.getSpPr().getOOXML()); - // text props - if (this.gettxPr() != null) axisooxml.append(this.gettxPr().getOOXML()); - else if (from2003) { // XLS->XLSX -/* - * label font: - * Fontx fx= (Fontx) Chart.findRec(chartArr, Fontx.class); - return this.getParentChart().getWorkBook().getFont(fx.getIfnt()); -*/ - int rot = 0; - Tick t = (Tick) Chart.findRec(this.chartArr, Tick.class); - if (t != null) { // shoudn't - rot = t.getRotation(); - /** - 0= no rotation (text appears left-to-right), - 1= text appears top-~~ are upright, - 2= text is rotated 90 degrees counterclockwise, - 3= text is rotated - */ - // convert BIFF8 rotation to TxPr rotation: - switch (rot) { - case 1: - //???? - break; - case 2: - rot = -5400000; - break; - case 3: - rot = 5400000; - break; - } - // TODO: is vert rotation from td? - TxPr txpr = new TxPr(this.getLabelFont(), rot, null); - axisooxml.append(txpr.getOOXML()); - } - axisooxml.append("\r\n"); - } - // crossesAx - axisooxml.append(""); - axisooxml.append("\r\n"); // crosses axis ... - // crosses -- autoZero, max, min - if (this.getOption("crosses") != null) - axisooxml.append(""); - axisooxml.append("\r\n");// where axis crosses it's perpendicular axis - if (axis.equals("catAx") || axis.equals("serAx")) { - // auto - axisooxml.append("\r\n"); - s = this.getOption("lblAlgn"); - if (s != null) axisooxml.append("\r\n"); - s = this.getOption("lblOffset"); - if (s != null) axisooxml.append("\r\n"); - s = this.getOption("tickLblSkip"); - if (s != null) axisooxml.append("\r\n"); - s = this.getOption("tickMarkSkip"); - if (s != null) axisooxml.append("\r\n"); - // TODO: noMutliLvlLbl - } else { // val or date - s = this.getOption("crossBetween"); - if (s != null) axisooxml.append("\r\n"); - s = this.getOption("majorUnit"); - if (s != null) axisooxml.append("\r\n"); - s = this.getOption("minorUnit"); - if (s != null) axisooxml.append("\r\n"); -// TODO: dispUnit ************************************ - - } - axisooxml.append(""); - axisooxml.append("\r\n"); - return axisooxml.toString(); - } - - - /** - * returns the SVG representation of the desired axis - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @return String SVG - */ - public String getSVG(ChartAxes ca, java.util.Map chartMetrics, Object[] categories) { - StringBuffer svg = new StringBuffer(); - - // for all axies-- label and title fonts, rotation, tick info ... - // Title + Label SVG - String labelfontSVG = ""; - String titlefontSVG = ""; - - try { - labelfontSVG = this.getLabelFont().getSVG(); // uses specific or default for chart - } catch (Exception e) { // shouldn't - labelfontSVG = "font-family='Arial' font-size='9pt' fill='" + ChartType.getDarkColor() + "' "; - } - try { - titlefontSVG = linkedtd.getFont(this.getParentChart().getWorkBook()).getSVG(); - } catch (NullPointerException e) { - } - boolean showMinorTickMarks = false, showMajorTickMarks = true; - try { - Tick t = (Tick) Chart.findRec(chartArr, Tick.class); - showMinorTickMarks = t.showMinorTicks(); - showMajorTickMarks = t.showMajorTicks(); - } catch (Exception e) { - } - - // BAR CHART AXES ARE SWITCHED - handle seperately for clarity; radar axes are also handled separately as are significantly different than regualr charts - int charttype = this.getParentChart().getChartType(); - if (charttype == ChartConstants.BARCHART) - return getSVGBARCHART(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories); - if (charttype == ChartConstants.RADARCHART) - return getSVGRADARCHART(ca, titlefontSVG, labelfontSVG, chartMetrics, categories); - - int wtype = wType; - if (wtype == XAXIS && (charttype == ChartConstants.SCATTERCHART || charttype == ChartConstants.BUBBLECHART)) { // XY Charts - X Axis is a Value Axis - wtype = XVALAXIS; - } - - switch (wtype) { - case XAXIS: - svg.append(drawXAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories)); - - break; - case YAXIS: - svg.append(drawYAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics)); - - break; - case ZAXIS: // ?? - break; - - case XVALAXIS: // Scatter/Bubble Chart X Value Axis - svg.append(drawXYValAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories)); - break; - } - return svg.toString(); - } - - /** - * generate SVG for a basic (non-bar-chart, non-textual) X Axis - * - * @param ca - * @param titlefontSVG - * @param labelfontSVG - * @param rot - * @param showMinorTickMarks - * @param showMajorTickMarks - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @return - */ - private String drawXAxisSVG(ChartAxes ca, String titlefontSVG, String labelfontSVG, boolean showMinorTickMarks, boolean showMajorTickMarks, java.util.Map chartMetrics, Object[] categories) { - StringBuffer svg = new StringBuffer(); - // X Axis TICKS x= rectX, x2= x1, y1= recty+h, y2=y1+5 (major) x1 increments by (#Major Ticks-1)/Width (usually 4) - // x= rectX+17, x2= x1, y1= recty+h y2=y1+2 (minor) x1 increments by (approx 8) - // LABELS (CATEGORIES): - do before ticks NOTE: patterns and formatting is applied .adjustCoordinates in order to account for fitting in space - - // when x axis is reversed means that categories are right to left and the y axis is on the RHS - // when y axis is reversed means the categories are on TOP of the chart and y axis labels are reversed - double x0, x1, y0, y1; - double inc; - java.awt.Font f = null; - double x = chartMetrics.get("x"); - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double canvash = chartMetrics.get("canvash"); - boolean yAxisReversed = (Boolean) ca.getMetric("yAxisReversed"); - boolean xAxisReversed = (Boolean) ca.getMetric("xAxisReversed"); - int xAxisRotate = (Integer) ca.getMetric("xAxisRotate"); - double XAXISLABELOFFSET = (Double) ca.getMetric("XAXISLABELOFFSET"); - double XAXISTITLEOFFSET = (Double) ca.getMetric("XAXISTITLEOFFSET"); - - int labelRot = (wType == XAXIS ? xAxisRotate : 0); // TODO: handle Y axis rotation - if (labelRot != 0) { - // get font object so can calculate rotation point - io.starter.formats.XLS.Font lf = this.getLabelFont(); - try { - // get awt Font so can compute and fit category in width - f = new java.awt.Font(lf.getFontName(), lf.getFontWeight(), (int) lf.getFontHeightInPoints()); - } catch (Exception e) { - } - } - if (categories != null && categories.length > 0) { // shouldn't - // Category Labels - centered within area on X Axis - inc = w / categories.length; - svg.append(getCategoriesSVG(x, y, w, h, inc, labelRot, categories, f, labelfontSVG, yAxisReversed, xAxisReversed, XAXISLABELOFFSET)); - // TICK MARKS - y0 = y + (!yAxisReversed ? h : 0); // ticks at bottom edge of axis unless Y axiis is reversed - x0 = x; // start at chart x - int rfY = (!yAxisReversed ? 1 : -1); // reverse factor :) - int rfX = (!xAxisReversed ? 1 : -1); // reverse factor :) - svg.append("\r\n"); - inc = w / (categories.length); // w/scale factor - double minorinc = 0; - if (showMinorTickMarks) - minorinc = inc / 2; // half-marks for category axis - for (double i = 0; i <= categories.length; i++) { - y1 = y0 + 2 * rfY; // minor tick mark - if (showMinorTickMarks) { - for (int j = 0; j < 2; j++) { // for categories, only option is 1/2 major - svg.append("\r\n"); - x0 += minorinc; - } - } - y1 = y0 + 5 * rfY; // Major tick marks - if (showMajorTickMarks) - svg.append("\r\n"); - x0 += inc; - } - // bounding edge line - if (hasPlotAreaBorder()) { - x0 = x + (!xAxisReversed ? w : 0); - svg.append("\r\n"); - } - svg.append("\r\n"); - } - // X AXIS TITLE - int titleRot = (linkedtd != null) ? linkedtd.getRotation() : 0; - x0 = x + w / 2; - if (!yAxisReversed) // TODO: why doesn't "normal" calc work??????? - y0 = canvash - XAXISTITLEOFFSET; - else - y0 = y - XAXISTITLEOFFSET - XAXISLABELOFFSET; - - svg.append(getAxisTitleSVG(x0, y0, titlefontSVG, titleRot, "xaxistitle")); - return svg.toString(); - } - - static int YLABELSSPACER_X = 10; - static int YLABELSPACER_Y = 4; - - /** - * generate SVG for a basic (non-bar-chart, value or numeric) Y Axis - * - * @param ca - * @param titlefontSVG - * @param labelfontSVG - * @param rot - * @param showMinorTickMarks - * @param showMajorTickMarks - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @return - */ - // TODO: label rotation - private String drawYAxisSVG(ChartAxes ca, String titlefontSVG, String labelfontSVG, boolean showMinorTickMarks, boolean showMajorTickMarks, java.util.Map chartMetrics) { - StringBuffer svg = new StringBuffer(); - // Y or Value Axis- must be non-textual; values obtained in calling method - // When Y Axis is reversed, scale is reversed and x axis labels and title are on top - // When X Axis is reversed, Y scale/labels and title are on RHS - double x0, x1, y0, y1; - double inc; - // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 - double x = chartMetrics.get("x"); - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - double minor = (Double) ca.getMetric("minor"); - double major = (Double) ca.getMetric("major"); - boolean scaleIsInteger = (major == Math.floor(major)); // true if scale is in integer form rather than double (used for formatting label text) - int titleRot = (linkedtd != null) ? linkedtd.getRotation() : 0; - boolean xAxisReversed = (Boolean) ca.getMetric("xAxisReversed"); - boolean yAxisReversed = (Boolean) ca.getMetric("yAxisReversed"); - String xPattern = (String) ca.getMetric("xPattern"); - String yPattern = (String) ca.getMetric("yPattern"); - double YAXISLABELOFFSET = (Double) ca.getMetric("YAXISLABELOFFSET"); - double YAXISTITLEOFFSET = (Double) ca.getMetric("YAXISTITLEOFFSET"); - - // Y Axis GRIDLINES: -- x1=rectX x2=rectx+rect w y1=y2 y1 starts with recty+h, decrements by= approx 27 ==rectheight/# lines ***************************** - // Major/Minor tick marks - if (major > 0) { // if is displaying tick marks/grid lines - usual case - inc = h / ((max - min) / major); - double minorinc = 0; - if (minor > 0) - minorinc = inc / (major / minor); - x0 = x; - y0 = y + h; - x1 = x + w; // entire width - // GRIDLINES - String lineSVG = getLineSVG(AxisLineFormat.ID_MAJOR_GRID); - svg.append("\r\n"); - for (double i = min; i <= max; i += major) { - svg.append("\r\n"); - y0 -= inc; - } - svg.append("\r\n"); - // TICK MARKS - svg.append("\r\n"); - int rfY = (!yAxisReversed ? 1 : -1); // reverse factor :) - int rfX = (!xAxisReversed ? 1 : -1); // reverse factor :) - y0 = y + (!yAxisReversed ? h : 0); // starts at bottom, goes up to top except if reversed - lineSVG = ChartType.getStrokeSVG(); - int scale = 0; - { // figure out if scale has a fractional portion; if so, determine desired scale and keep to that - String s = String.valueOf(major); - int z = s.indexOf("."); - if (z != -1) - scale = s.length() - (z + 1); - } - int k = 0; // axis label index - for (double i = min; i <= max; i += major) { - x0 = x + (!xAxisReversed ? 0 : w); - x1 = x0 - 2 * rfX;// minor ticks - y1 = y0; - if (i < max && minor > 0) { - if (showMinorTickMarks) { - for (int j = 0; j < (major / minor); j++) { - svg.append("\r\n"); - y0 -= minorinc; - } - } - } - y0 = y1; - x1 = x0 - 5 * rfX;// Major tick Marks - if (showMajorTickMarks) - svg.append("\r\n"); - // Y Axis Labels - java.math.BigDecimal bd = new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); // if fractional, ensure java's floating point crap is handled corectly - if (!xAxisReversed) - svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); - else - svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); - y0 -= inc * rfY; - } - svg.append("\r\n"); - // AXIS bounding line - x0 = x + (!xAxisReversed ? 0 : w); - y0 = y; - svg.append("\r\n"); - // Y AXIS TITLE - if (!xAxisReversed) - x0 = /*YAXISTITLEOFFSET + */10; // TODO: 10 should be actual font height (when rotated 90 as is the norm) - else - x0 = x + w + YAXISTITLEOFFSET; - svg.append(getAxisTitleSVG(x0, (y + h / 2), titlefontSVG, titleRot, "yaxistitle")); - } - return svg.toString(); - } - - /** - * return the svg to display an XYValue axis (bubble, scatter) - * - * @param ca - * @param titlefontSVG - * @param labelfontSVG - * @param showMinorTickMarks - * @param showMajorTickMarks - * @param chartMetrics - * @return - */ - private String drawXYValAxisSVG(ChartAxes ca, String titlefontSVG, String labelfontSVG, boolean showMinorTickMarks, boolean showMajorTickMarks, java.util.Map chartMetrics, Object[] categories) { - StringBuffer svg = new StringBuffer(); - double x0, x1, y0, y1; - double minorinc = 0; - double inc; - // Y or Value Axis- must be non-textual; values obtained in calling method - // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 - double x = chartMetrics.get("x"); - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - double minor = (Double) ca.getMetric("minor"); - double major = (Double) ca.getMetric("major"); - boolean scaleIsInteger = (major == Math.floor(major)); // true if scale is in integer form rather than double (used for formatting label text) - String yPattern = (String) ca.getMetric("yPattern"); - double XAXISLABELOFFSET = (Double) ca.getMetric("XAXISLABELOFFSET"); - double XAXISTITLEOFFSET = (Double) ca.getMetric("XAXISTITLEOFFSET"); - - if (categories != null && categories.length > 0) { // shouldn't - double xmin = Double.MAX_VALUE, xmax = Double.MIN_VALUE; - boolean TEXTUALXAXIS = true; - for (int j = 0; j < categories.length; j++) { - try { - double d = new Double(categories[j].toString()); - xmax = Math.max(xmax, d); - xmin = Math.min(xmin, d); - TEXTUALXAXIS = false; // if ANY category val is a double, assume it's a normal xyvalue axis - } catch (Exception e) { - /* keep going */ - } - } - if (!TEXTUALXAXIS) { - double[] d = ValueRange.calcMaxMin(xmax, xmin, w); - minor = (int) d[0]; - major = (int) d[1]; - xmax = d[2]; - - } else { // NO category values are numeric -- x axis is Textual - just use count - major = 1; - minor = 0; - xmax = categories.length + 1; - XAXISLABELOFFSET = 30; // TODO: calculate - } - - // CATEGORY LABELS- centered within area on X Axis - y0 = y + h + XAXISLABELOFFSET; - inc = w / (xmax / major); - x0 = x; - int scale = 0; - { // figure out if scale has a fractional portion; if so, determine desired scale and keep to that - String s = String.valueOf(major); - int z = s.indexOf("."); - if (z != -1) - scale = s.length() - (z + 1); - } - int k = 0; // axis label index - for (double i = 0; i <= xmax; i += major) { - if (!TEXTUALXAXIS) { - java.math.BigDecimal bd = new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); - svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); - } else - svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(i) + "\r\n"); - x0 += inc; - } - - // TICK MARKS - y0 = h + y; // origin at y+h - x0 = x; // start at chart x - svg.append("\r\n"); - if (minor > 0) - minorinc = inc / minor; - for (int i = 0; i <= xmax; i += major) { - y1 = y0 + 2; // minor tick mark - for (int j = 0; j < minor; j++) { - svg.append("\r\n"); - x0 += minorinc; - } - y1 = y0 + 5; // Major tick marks - svg.append("\r\n"); - if (minorinc == 0) - x0 += inc; - } - svg.append("\r\n"); - } - // X AXIS TITLE - int titleRot = (linkedtd != null) ? linkedtd.getRotation() : 0; - svg.append(getAxisTitleSVG((x + w / 2), (y + h + XAXISTITLEOFFSET), titlefontSVG, titleRot, "zaxistitle")); - return svg.toString(); - } - - - /** - * Bar chart Axes are switched so handle seperately - * basically x axis holds Y labels and title, and visa versa + gridlines go up and down (traverse y) rather than across (traverse x) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @return String SVG - */ - private String getSVGBARCHART(ChartAxes ca, String titlefontSVG, String labelFontSVG, boolean showMinorTicks, boolean showMajorTicks, java.util.Map chartMetrics, Object[] categories) { - StringBuffer svg = new StringBuffer(); - - // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 - // X Axis/Cats in reversed order means X axis on Top, Y axis labels in reversed order (along with bars) - double x0, x1, y0, y1; - double inc; - int rfX = 1; // reverse factor used to reverse order + position of axes - int rfY = 1; // "" - boolean scaleIsInteger = true; // usual case - double x = chartMetrics.get("x"); - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double canvasw = chartMetrics.get("canvasw"); - double canvash = chartMetrics.get("canvash"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - boolean xAxisReversed = (Boolean) ca.getMetric("xAxisReversed"); - boolean yAxisReversed = (Boolean) ca.getMetric("yAxisReversed"); - double YAXISTITLEOFFSET = (Double) ca.getMetric("YAXISTITLEOFFSET"); - String yPattern = (String) ca.getMetric("yPattern"); - double XAXISLABELOFFSET = (Double) ca.getMetric("XAXISLABELOFFSET"); - double XAXISTITLEOFFSET = (Double) ca.getMetric("XAXISTITLEOFFSET"); - double major = (Double) ca.getMetric("major"); - - if (wType == XAXIS) { - scaleIsInteger = (major == Math.floor(major)); // true if scale is in integer form rather than double (used for formatting label text) - } - switch (wType) { - case XAXIS: - // X Axis TICKS x= rectX, x2= x1, y1= recty+h, y2=y1+5 (major) x1 increments by (#Major Ticks-1)/Width (usually 4) - // x= rectX+17, x2= x1, y1= recty+h y2=y1+2 (minor) x1 increments by (approx 8) - x0 = x; // start at chart x - y0 = y + (!xAxisReversed ? h : 0); // origin at y+h (unless reversed) - rfX = (!xAxisReversed ? 1 : -1); - rfY = (!yAxisReversed ? 1 : -1); - if (major > 0) { - svg.append("\r\n"); - inc = w / ((max - min) / major); // w/scale factor - int scale = 0; - { // figure out if scale has a fractional portion; if so, determine desired scale and keep to that - String s = String.valueOf(major); - int z = s.indexOf("."); - if (z != -1) - scale = s.length() - (z + 1); - } - int k = 0; // axis label index - for (double i = min; i <= max; i += major) {// traverse across bottom (or top, if reversed) of x axis) incrementing x value keeping y value constant - y1 = y0 + 5 * rfX; // Major tick marks - if (showMajorTicks) - svg.append("\r\n"); - // X axis labels= (Values) - java.math.BigDecimal bd = new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); - if (!xAxisReversed) - svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); - else - svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n"); - x0 += inc * rfY; - } - svg.append("\r\n"); - } - // AXIS bounding line - y0 = y + (!xAxisReversed ? h : 0); // origin at y+h (unless reversed) - x0 = x; // + (!yAxisReversed?0:ci.w); // start at chart x - svg.append("\r\n"); - // is the Y AXIS TITLE i.e. goes alongside Y AXIS - if (!yAxisReversed) - x0 = YAXISTITLEOFFSET; - else - x0 = x + w + YAXISTITLEOFFSET; - svg.append(getAxisTitleSVG(x0, (y + h / 2), titlefontSVG, 90, "xaxistitle")); - break; - case YAXIS: - rfX = (!xAxisReversed ? 1 : -1); // if reversed, y vals on top (x axis), cats are in reversed order (y axis) - rfY = (!yAxisReversed ? 1 : -1); // if reversed, y vals in reverse order (x axis), cats are on RHS (y axis) - if (categories != null && categories.length > 0) { // should! - inc = h / categories.length; - x0 = x; // + (!yAxisReversed?0:ci.w); // draw y axis tick marks + y axis labels (= categories) - y0 = y + (!xAxisReversed ? h : 0); // starts at bottom, goes up to top unless reversed - int k = 0; // axis label index - for (int i = 0; i < categories.length; i++) { // traverse Y axis, spacing category labels (x is constant, y is segmented) - x1 = x0 - 5 * rfY;// Major tick Marks - if (showMajorTicks) - svg.append("\r\n"); - // Y Axis Labels = categories - y1 = y0 - inc * rfX + inc * .5 * rfX; - if (!yAxisReversed) - svg.append("" + categories[i].toString() + "\r\n"); - else - svg.append("" + categories[i].toString() + "\r\n"); - y0 -= inc * rfX; - } - // show gridlines (top to bottom) - String lineSVG = getLineSVG(AxisLineFormat.ID_MAJOR_GRID); - if (!lineSVG.equals("")) { - y0 = y; - x0 = x; // start at chart x - inc = w / ((max - min) / major); // w/scale factor - // 1st line is axis line - svg.append("\r\n"); - // rest are grid lines - for (double i = min; i < max; i += major) { - x0 += inc; - svg.append("\r\n"); - } - } - // is the X AXIS TITLE i.e. GOES ON THE X AXIS - y0 = (!xAxisReversed ? canvash - XAXISTITLEOFFSET : y - XAXISTITLEOFFSET); - int titleRot = (linkedtd != null) ? linkedtd.getRotation() : 0; - svg.append(getAxisTitleSVG((x + w / 2), y0, titlefontSVG, titleRot, "yaxistitle")); - } - break; - } - return svg.toString(); - } - - /** - * returns the SVG representation of this Radar Chart Axes -- - *
                      Radar Chart Axis look like a spider web - *
                      Note that these coordinates, like all axis scales, match the coordinates and calculations in Rader.getSVG - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @return String SVG - */ - private String getSVGRADARCHART(ChartAxes ca, String titleFontSVG, String labelFontSVG, java.util.Map chartMetrics, Object[] categories) { - double x = chartMetrics.get("x"); - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - double major = (Double) ca.getMetric("major"); - StringBuffer svg = new StringBuffer(); - if (wType == YAXIS) { - svg.append("\r\n"); - if (categories != null && categories.length > 0) { // shouldn't - major = major * 2; // appears the scale calc doesn't follow other charts ... - double n = categories.length; - double centerx = w / 2 + x; - double centery = h / 2 + y; - double percentage = 1 / n; // divide into equal sections - double radius = Math.min(w, h) / 2.3; // should take up almost entire w/h of chart - double radiusinc = radius / (max / major); - double lastx = centerx, lasty = centery - radius; // again, start straight up - int k = 0; // axis label index - for (double j = min; j <= max; j += major) { // each major unit is a concentric line - double angle = 90; // starts straight up - for (int i = 0; i <= n; i++) { // each category is a radial line; <= n so can complete the spider web line path - // get next point on circumference - double x1 = centerx + radius * (Math.cos(Math.toRadians(angle))); - double y1 = centery - radius * (Math.sin(Math.toRadians(angle))); - svg.append("\r\n"); - if (j == 0 && i < n) { // print radial lines & labels at very end of chart/top of each line -- only do for 1st round - svg.append("\r\n"); - double labelx1 = centerx + (radius + 10) * (Math.cos(Math.toRadians(angle))); - double labely1 = centery - (radius + 10) * (Math.sin(Math.toRadians(angle))); - svg.append("" + categories[i].toString() + "\r\n"); - } - // next angle - angle -= (percentage * 360); - lastx = x1; - lasty = y1; - } - radius -= radiusinc; - } - } - svg.append("\r\n"); - } - return svg.toString(); - } - - - /** - * return the svg necessary to display categories along an x axis - * - * @param x - * @param y - * @param w - * @param h - * @param inc - * @param labelRot - * @param categories - * @param f - * @param labelfontSVG - * @param yAxisReversed - * @param xAxisReversed - * @param XAXISLABELOFFSET - * @return - */ - private String getCategoriesSVG(double x, double y, double w, double h, double inc, int labelRot, Object[] categories, java.awt.Font f, String labelfontSVG, boolean yAxisReversed, boolean xAxisReversed, double XAXISLABELOFFSET) { - // Category Labels - centered within area on X Axis - StringBuffer svg = new StringBuffer(); - double x0, x1, y0, y1; - int k = labelfontSVG.indexOf("font-size=") + 11; - double fh = Double.parseDouble(labelfontSVG.substring(k, labelfontSVG.indexOf("pt"))); // approximate height of a line of labels - y0 = y + (!yAxisReversed ? h + XAXISLABELOFFSET / 3 : -XAXISLABELOFFSET); // draw on bottom edge of axis unless Y axis is reversed - int m = 0; // axis label index - for (int i = 0; i < categories.length; i++) { - if (!xAxisReversed) - x0 = x + inc * i + (inc / 2); - else // reversed: category labels start from LHS and go to RHS - x0 = x + w - inc * i - (inc / 2); - if (labelRot != 0) { - double len = StringTool.getApproximateStringWidthLB(f, CellFormatFactory.fromPatternString(null).format(categories[i])); - if (labelRot == 45) - len = (int) Math.ceil(len * (Math.cos(Math.toRadians(labelRot)))); - int offset = (int) (len / 2) + 5; - y0 = y + (!yAxisReversed ? h + offset : -offset); - if (labelRot == 45) - x0 += inc / 2; - - } - // handle multiple lines in X axis labels - must do "by hand" - String[] s = categories[i].toString().split("\n"); - svg.append(""); - for (int z = 0; z < s.length; z++) - svg.append("" + s[z] + "\r\n"); - svg.append("\r\n"); - } - return svg.toString(); - } - - /** - * return the svg necessary to display an axis title - * - * @param x - * @param y - * @param titlefontSVG - * @param titleRot 0, 45 or -90 degrees - * @param scriptTitle either "xaxistitle", "yaxistitle" or "zaxistitle" - * @return - */ - private String getAxisTitleSVG(double x, double y, String titlefontSVG, int titleRot, String scriptTitle) { - StringBuffer svg = new StringBuffer(); - svg.append("\r\n"); - svg.append("" + this.getTitle() + "\r\n"); - svg.append("\r\n"); - return svg.toString(); - } - -} - -class Scaling implements OOXMLElement { - private String logBase, max, min, orientation; - - public Scaling() { // no-param constructor, set up common defaults - } - - public Scaling(String logBase, String max, String min, String orientation) { - this.logBase = logBase; - this.max = max; - this.min = min; - this.orientation = orientation; - } - - public Scaling(Scaling sc) { - this.logBase = sc.logBase; - this.max = sc.max; - this.min = sc.min; - this.orientation = sc.orientation; - } - - /** - * parse Axis OOXML element (catAx, valAx, serAx or dateAx) - * - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spPr object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag) { - String logBase = null, max = null, min = null, orientation = null; - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("scaling")) { - } else if (tnm.equals("logBase")) { - logBase = xpp.getAttributeValue(0); - } else if (tnm.equals("max")) { - max = xpp.getAttributeValue(0); - } else if (tnm.equals("min")) { - min = xpp.getAttributeValue(0); - } else if (tnm.equals("orientation")) { - orientation = xpp.getAttributeValue(0); - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("scaling")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("scaling.parseOOXML: " + e.toString()); - } - Scaling s = new Scaling(logBase, max, min, orientation); - return s; - } - - /** - * return the specific scaling option - * - * @param op - * @return - */ - public String getOption(String op) { - if (op.equals("logBase")) - return logBase; - else if (op.equals("max")) - return max; - else if (op.equals("min")) - return min; - else if (op.equals("orientation")) - return orientation; - return null; - } - - public String getOOXML() { - StringBuffer ooxml = new StringBuffer(); - // sequence: logBase, orientation, max, min - ooxml.append(""); - if (orientation != null) ooxml.append(""); - if (max != null) ooxml.append(""); - if (min != null) ooxml.append(""); - ooxml.append("\r\n"); - return ooxml.toString(); - } - - public OOXMLElement cloneElement() { - return new Scaling(this); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/Axis.kt b/src/main/java/io/starter/formats/XLS/charts/Axis.kt new file mode 100644 index 0000000..1be9632 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Axis.kt @@ -0,0 +1,2379 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ChartHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.* +import io.starter.formats.XLS.BiffRec +import io.starter.formats.XLS.Font +import io.starter.formats.XLS.XLSRecord +import io.starter.formats.cellformat.CellFormatFactory +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +//OOXML-specific structures +//OOXML-specific structures + +/** + * **Axis: Axis Type (0x101d)** + * + * + * 4 wType 2 axis type (0= category axis or x axis on a scatter chart, 1= value axis, 2= series axis + * 6 (reserved) 16 0 + * + * + * Order of Axis Subrecords: + * X (Cat Axis) + * + * + * CatSerRange + * AxcExt + * CatLab + * [IfmtRecord] + * [Tick] + * [FontX] + * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor + * [AreaFormat, GelFrame] + * [Shape or TextPropsStream] + * [CrtMltFrt] + * + * + * Y (Value Axis) + * ValueRange + * [YMult -->Text-->Pos...] -- display units + * [IfmtRecord] + * [Tick] + * [FontX] + * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor + * [AreaFormat, GelFrame] + * [Shape or TextPropsStream] + * [CrtMltFrt] + * + * + * XY (Series Axis) + * CatSerRange + * [IfmtRecord] + * [Tick] + * [FontX] + * [AxisLineFormat, LineFormat] - if gridlines (major or minor) or border around wall/floor + * [AreaFormat, GelFrame] + * [Shape or TextPropsStream] + * [CrtMltFrt] + */ +/* General info on Excel axis types: + * In Microsoft Excel charts, there are different types of X axes. + * While the Y axis is a Value type axis (i.e. containing a ValueRange record), + * the X axis can be a Category type axis or a Value type axis. + * Using a Value axis, the data is treated as continuously varying numerical data, + * and the marker is placed at a point along the axis which varies according to its + * numerical value. + * Using a Category axis, the data is treated as a sequence of non-numerical text labels, + * and the marker is placed at a point along the axis according to its position in + * the sequence. + * + * Note that Scatter (x/y) charts and Bubble charts have two Value axes, pie and donut charts have no axes + * + * How do you arrange your chart so the categories are displayed along the Y axis? + * The method involves adding a dummy series along the Y axis, + * applying data labels to its points for category labels, + * and making the original Y axis disappear. + */ +class Axis : GenericChartObject(), ChartObject { + var axis: Short = 0 + internal set + private var linkedtd: TextDisp? = null // 20070730 KSC: hold axis legend TextDisp for this axis + private var ap: AxisParent? = null // 20090108 KSC: links to this axes parent -- + // OOXML-specific + /** + * return the OOXML shape property for this axis + * + * @return + */ + /** + * define the OOXML shape property for this axis from an existing spPr element + */ + //shapeProps.setNS("c"); + var spPr: SpPr? = null // 20081224 KSC: OOXML-specific holds the shape properties (line and fill) for this axis + /** + * return the OOXML title element for this axis + * + * @return + */ + /** + * set the OOXML title element for this axis + * + * @param t + */ + var ooxmlTitle: io.starter.formats.OOXML.Title? = null // OOXML title element + private var txpr: TxPr? = null // text properties for axis + private var nf: NumFmt? = null // NumFmt prop for axis + internal var axPos: String? = null + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + + /** + * @return String XML representation of this chart-type's options + */ + override// Axis 0: CatserRange, Axcent, Tick [AxisLineFormat, LineFormat, AreaFormat] last 3 recs are for 3d formatting + // Axis 1: ValueRange, Tick, AxisLineFormat, LineFormat [AreaFormat, LineFormat, AreaFormat] " " + // Axis 2: [CatserRange, Tick] Z axis, for surface charts (only??) + // Handle subordinate record options here rather than in the specific rec + // record deviations from defaults + // necessary for 3d charts and bg colors ... + // Y Axis draw gridline usually true, trap if do not have (see below) + // indicates area + // should ONLY be present if AxisLineFormat AddArea + // if not default background colors, record and reset + // if not defaults, set fore and back of walls, sides or floor + // xaxis + // yaxis + // xaxis + // yaxis + // TODO: parse LineFormat and, if not standard, add as option + // TODO: Parse Tick for options + // most Y Axes have major grid lines; flag if do not + val optionsXML: String + get() { + val sb = StringBuffer() + var hasMajorGridLines = false + for (i in chartArr.indices) { + val b = chartArr[i] + if (b is CatserRange) { + if (b.catCross != 1) + sb.append(" CatCross=\"" + b.catCross + "\"") + if (b.catLabel != 1) + sb.append(" LabelCross=\"" + b.catLabel + "\"") + if (b.catMark != 1) + sb.append(" Marks=\"" + b.catMark + "\"") + if (!b.crossBetween) + sb.append(" CrossBetween=\"false\"") + if (b.crossMax) + sb.append(" CrossMax=\"true\"") + } else if (b is AxisLineFormat) { + val id = b.id.toInt() + if (id == AxisLineFormat.ID_MAJOR_GRID) + hasMajorGridLines = true + else if (id == AxisLineFormat.ID_WALLORFLOOR) + sb.append(" AddArea=\"true\"") + } else if (b is AreaFormat) { + var icvFore = b.geticvFore() + var icvBack = b.geticvBack() + if (icvBack == -1) icvBack = 0x4D + if (icvFore == 1) icvFore = 0x4E + if (axis.toInt() == 0 && icvFore != 22 || axis.toInt() == 1 && icvFore != -1) + sb.append(" AreaFg=\"$icvFore\"") + if (axis.toInt() == 0 && icvBack != 0 || axis.toInt() == 1 && icvBack != 1) + sb.append(" AreaBg=\"$icvBack\"") + } + } + if (axis.toInt() == 1 && !hasMajorGridLines) + sb.append(" MajorGridLines=\"false\"") + return sb.toString() + } + + /** + * get/set linked TextDisp (legend for this axis) + * used for setting axis options + * + * @param td + */ + var td: TextDisp? + get() { + if (linkedtd == null) + getTitleTD(false) + return linkedtd + } + set(td) { + linkedtd = td + } + + /** + * return the Title associated with this Axis (through the linked TextDisp record) + * + * @return + */ + val label: String + @Deprecated("use getTitle") + get() = title + + /** + * set the Title associated with this Axis (through the linked TextDisp record) + * + * @param l + */ + // finds or adds TextDisp records for this axis (for labels and fonts) + var title: String? + get() { + if (linkedtd == null) + getTitleTD(false) + return if (linkedtd != null) linkedtd!!.toString() else "" + } + set(l) { + if (l == null) { + this.removeTitle() + return + } + if (linkedtd == null) { + getTitleTD(true) + } + linkedtd!!.setText(l) + } + + + /** + * return the Font object associated with the Axis Title, or null if none + */ + val titleFont: Font? + get() { + try { + if (linkedtd != null) { + val fx = Chart.findRec(linkedtd!!.chartArr, Fontx::class.java) as Fontx + return this.parentChart!!.workBook!!.getFont(fx.ifnt) + } + } catch (e: Exception) { + } + + return null + } + + /** + * returns the font of the Axis Title + */ + override// finds or adds TextDisp records for this axis (for labels and fonts) + val font: io.starter.formats.XLS.Font? + get() { + if (linkedtd == null) { + getTitleTD(true) + } + val idx = linkedtd!!.fontId + return this.workBook!!.getFont(idx) + } + + /** + * return the Font object used for Axis labels + * + * @return + */ + val labelFont: io.starter.formats.XLS.Font? + get() { + try { + val fx = Chart.findRec(chartArr, Fontx::class.java) as Fontx + return this.parentChart!!.workBook!!.getFont(fx.ifnt) + } catch (e: NullPointerException) { + return this.parentChart!!.defaultFont + } + + } + + /** + * returns true if this axis is reversed + *

                      If horizontal axis, default= on bottom, reversed= on top + *

                      If vertical axis, default= LHS, reversed= RHS + * + * @return + */ + // shouldn't + val isReversed: Boolean + get() { + if (axis.toInt() == ChartConstants.XAXIS) { + val c = getCatserRange(false) + if (c != null) + return c.isReversed + } + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + return v?.isReversed ?: false + } + + // see if have series-specific formats + // see if it's a value X axis with a custom + val numberFormat: String? + get() { + val f = Chart.findRec(this.chartArr, Ifmt::class.java) as Ifmt + var i = 0 + if (f != null) { + i = f.fmt + } else { + val s = this.parentChart!!.getAllSeries(-1) + if (s.size > 0) { + return if (axis.toInt() == ChartConstants.YAXIS) { + (s[0] as Series).seriesFormatPattern + } else + (s[0] as Series).categoryFormatPattern + } + } + return "General" + } + + protected// shouldn't + val minMax: DoubleArray + get() { + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + return if (v != null) { + doubleArrayOf(v.min, v.max) + } else doubleArrayOf(0.0, 0.0) + + } + + /** + * return the major tick unit of this Y or Value axis + * + * @return + */ + // try a default + val majorUnit: Int + get() { + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + return v?.majorTick?.toInt() ?: 10 + } + + /** + * return the minor tick unit of this Y or Value axis + * + * @return + */ + // try a default + val minorUnit: Int + get() { + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + return v?.minorTick?.toInt() ?: 0 + } + + /** + * returns true if either Automatic min or max scale is set for the Y or Value axis + * + * @return + */ + /** + * sets the automatic scale option on or off for the Y or Value axis + *

                      Automatic Scaling will automatically set the scale maximum, minimum and tick units + * + * @param b + */ + var isAutomaticScale: Boolean + get() { + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + return if (v != null) { + v.isAutomaticMin || v.isAutomaticMax + } else false + } + set(b) { + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + if (v != null) { + v.isAutomaticMin = b + v.isAutomaticMax = b + } + } + + /** + * returns the Axis Label Placement or position as an int + * + * One of: + *

                      Axis.INVISIBLE - axis is hidden + *

                      Axis.LOW - low end of plot area + *

                      Axis.HIGH - high end of plot area + *

                      Axis.NEXTTO- next to axis (default) + * + * @return int - one of the Axis Label placement constants above + */ + /** + * sets the axis labels position or placement to the desired value (these match Excel placement options) + * + * Possible options: + *

                      Axis.INVISIBLE - hides the axis + *

                      Axis.LOW - low end of plot area + *

                      Axis.HIGH - high end of plot area + *

                      Axis.NEXTTO- next to axis (default) + * + * @param Placement - int one of the Axis placement constants listed above + */ + // shoudn't + // shoudn't + var axisPlacement: Int + get() { + val t = Chart.findRec(this.chartArr, Tick::class.java) as Tick + if (t != null) { + val p = t.getOption("tickLblPos") + if (p == null || p == "none") + return Axis.INVISIBLE + else if (p == "low") + return Axis.LOW + else if (p == "high") + return Axis.HIGH + else if (p == "nextTo") + return Axis.NEXTTO + } + return Axis.INVISIBLE + } + set(Placement) { + val t = Chart.findRec(this.chartArr, Tick::class.java) as Tick + if (t != null) { + when (Placement) { + Axis.INVISIBLE -> t.setOption("tickLblPos", "none") + Axis.LOW -> t.setOption("tickLblPos", "low") + Axis.HIGH -> t.setOption("tickLblPos", "high") + Axis.NEXTTO -> t.setOption("tickLblPos", "nextTo") + } + } + } + + override fun init() { + super.init() + axis = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + } + + fun setAxis(wType: Int) { + var wType = wType + this.axis = wType.toShort() + if (wType == ChartConstants.XVALAXIS) wType = ChartConstants.XAXIS // 20090108 KSC: XVALAXIS is type of X axis with VAL records + val b = ByteTools.shortToLEBytes(wType.toShort()) + this.data[0] = b[0] + this.data[1] = b[1] + } + + /** + * remove the title for this axis + */ + fun removeTitle() { + if (linkedtd != null) { + var x = Chart.findRecPosition(ap!!.chartArr, TextDisp::class.java) + while (x > -1) { + if ((ap!!.chartArr[x] as TextDisp).type == TextDisp.convertType(axis.toInt())) { + ap!!.chartArr.removeAt(x) + break + } + x++ + if ((ap!!.chartArr[x] as BiffRec).opcode != XLSConstants.TEXTDISP) + break + } + } + linkedtd = null + } + + /** + * returns true if plot area has a bonding box/border + * + * @return + */ + fun hasPlotAreaBorder(): Boolean { + val f = Chart.findRec(ap!!.chartArr, Frame::class.java) as Frame + return f?.hasBox() ?: false + } + + /** + * link this axis with it's parent + * + * @param a + */ + fun setAP(a: AxisParent) { + ap = a + } + + /** + * finds or adds TextDisp records for this axis (for labels and fonts) + */ + private fun getTitleTD(add: Boolean) { + val tdtype = TextDisp.convertType(axis.toInt()) + var pos = 0 + // Must add if can't find existing label + var x = Chart.findRecPosition(ap!!.chartArr, TextDisp::class.java) + pos = x + while (x > 0 && linkedtd == null) { + val td = ap!!.chartArr[x] as TextDisp + if (td.type == tdtype) + // found it + linkedtd = td + else { + if (ap!!.chartArr[++x] !is TextDisp) + x = -1 + } + } + if (linkedtd == null && add) { + linkedtd = TextDisp.getPrototype(tdtype, "", this.wkbook) as TextDisp + if (pos < 0) + pos = Chart.findRecPosition(ap!!.chartArr, PlotArea::class.java) + else { // otherwise, TextDisp(s) exist already; position correctly + // set font to other axis font as default + val td = ap!!.chartArr[pos] as TextDisp + linkedtd!!.fontId = td.fontId + if (axis.toInt() != ChartConstants.XAXIS) + pos++ + } + if (pos < 0) + pos = Chart.findRecPosition(ap!!.chartArr, ChartFormat::class.java) + linkedtd!!.parentChart = this.parentChart + ap!!.chartArr.add(pos, linkedtd) + } + } + + /** + * set the font index for this Axis (for title) + * + * @param fondId + */ + fun setFont(fondId: Int) { + if (linkedtd == null) { + getTitleTD(true) // finds or adds TextDisp records for this axis (for labels and fonts) + } + linkedtd!!.fontId = fondId + } + + /** + * utility method to find the CatserRange rec associated with this Axis + * + * @return + */ + protected fun getCatserRange(bCreate: Boolean): CatserRange { + var csr = Chart.findRec(chartArr, CatserRange::class.java) as CatserRange + if (csr == null) { + csr = CatserRange.prototype as CatserRange? + csr.parentChart = this.parentChart + chartArr.add(0, csr) + } + return csr + } + + /** + * returns true of this axis displays major gridlines + * + * @return + */ + protected fun hasGridlines(type: Int): Boolean { + var j = Chart.findRecPosition(chartArr, AxisLineFormat::class.java) + if (j != -1) { + try { + while (j < chartArr.size) { + val al = chartArr[j] as AxisLineFormat + val id = al.id.toInt() + if (id == type) + // Y Axis draw gridline usually true, trap if do not have (see below) + return true + j += 2 // Skip line format + } + } catch (e: ClassCastException) { + } + + } + return false + } + + /** + * returns the SVG necessary to describe the desired line (referenced by id) + * + * @param id @see AxisLineFormat id types + * @return + */ + protected fun getLineSVG(id: Int): String { + val lf = getAxisLine(id) + return if (lf != null) lf.svg else "" + } + + /** + * return the lineformat rec for the given axis line type + * + * @param type AxisLineFormat type + * @return Line Format + * @see AxisLineFormat.ID_MAJOR_GRID, et. atl. + */ + protected fun getAxisLine(type: Int): LineFormat? { + val j = getAxisLineFormat(type) + return if (j > -1) chartArr[j + 1] as LineFormat else null + + } + + /** + * return the AxisLineFormat of the desired type, or create if none and bCreate + * + * @param type AxisLineFormat Type: AxisLineFormat.ID_AXIS_LINE, AxisLineFormat.ID_MAJOR_GRID, AxisLineFormat.ID_MINOR_GRID, AxisLineFormat.ID_WALLORFLOOR + * @param bCreate + * @return + */ + protected fun getAxisLineFormat(type: Int, bCreate: Boolean): AxisLineFormat? { + var alf: AxisLineFormat? = null + var j = Chart.findRecPosition(chartArr, AxisLineFormat::class.java) + if (j == -1 && !bCreate) + return null + if (j > -1) { + try { + while (j < chartArr.size) { + alf = chartArr[j] as AxisLineFormat + if (alf.id.toInt() == type) + return alf + else if (alf.id > type) + break + j += 2 + } + } catch (e: ClassCastException) { + } + + } + j = 1 + while (j < chartArr.size) { + val b = chartArr[j] + if (b.opcode == XLSConstants.AREAFORMAT || + b.opcode == XLSConstants.GELFRAME || + b.opcode.toInt() == 2213 || /* TextPropsStream */ + b.opcode.toInt() == 2212 || /* ShapePropsStream */ + b.opcode.toInt() == 2206) + /* CtrlMlFrt */ + break + j++ + } + alf = AxisLineFormat.prototype as AxisLineFormat? + alf!!.setId(type) // default has major gridlines + chartArr.add(j++, alf) + alf.parentChart = this.parentChart + val lf = LineFormat.prototype as LineFormat? + lf!!.parentChart = this.parentChart + chartArr.add(j, lf) + return alf + + } + + /** + * return the index of the AxisLineFormat of the desired type, -1 if none + * + * @param type AxisLineFormat Type: AxisLineFormat.ID_AXIS_LINE, AxisLineFormat.ID_MAJOR_GRID, AxisLineFormat.ID_MINOR_GRID, AxisLineFormat.ID_WALLORFLOOR + * @return + */ + protected fun getAxisLineFormat(type: Int): Int { + var alf: AxisLineFormat? = null + var j = Chart.findRecPosition(chartArr, AxisLineFormat::class.java) + if (j == -1) return j + try { + while (j < chartArr.size) { + alf = chartArr[j] as AxisLineFormat + if (alf.id.toInt() == type) + return j + else if (alf.id > type) + break + j += 2 + } + } catch (e: ClassCastException) { + } + + return if (j > chartArr.size) -1 else j // not found + + } + + /** + * gets or creates YMult (value multiplier) record + * + * @param bCreate + * @return + */ + protected fun getYMultRec(bCreate: Boolean): YMult? { + var ym = Chart.findRec(chartArr, YMult::class.java) as YMult + if (ym == null && bCreate) { + ym = YMult.prototype as YMult? + ym.parentChart = this.parentChart + chartArr.add(1, ym) // 2nd, after ValueRange + } + return ym + } + + /** + * return the JSON/Dojo representation of this axis + * chartType int necessary for parsing AXIS options: horizontal charts "switch" axes ... + * All options have NOT been gathered at this point + * + * @return JSONObject + */ + fun getJSON(wbh: io.starter.OpenXLS.WorkBookHandle, chartType: Int, yMax: Double, yMin: Double, nSeries: Int): JSONObject { + val axisJSON = JSONObject() + val axisOptions = JSONObject() + try { + if (axis.toInt() == ChartConstants.YAXIS && chartType != ChartConstants.BARCHART) { + axisOptions.put("vertical", true) + } else if (axis.toInt() == ChartConstants.XAXIS && chartType == ChartConstants.BARCHART) + axisOptions.put("vertical", true) + + // 20090721 KSC: dojo 1.3.1 has label element + axisOptions.put("label", title) + + // TODO: Dojo Axis Options: + // fixLower, fixUpper ("minor"/"major") + // includeZero, natural (true) + // min, max + // minorTicks, majorTicks, microTicks (true/false) + // minorTickStep, majorTickStep, microTickStep (#) + for (i in chartArr.indices) { + val b = chartArr[i] + if (b is CatserRange) { +// for x/Category axis: if has labels, gather and input into axis label JSON array + val categories = this.parentChart!!.getCategories(-1) + if (categories != null) { + // Category Labels + val labels = JSONArray() + if (b.crossBetween) { // categories appear mid-axis so put "spacers" at 0 and max + axisOptions.put("includeZero", true) // always true???? + val nullCat = JSONObject() + nullCat.put("value", 0) + nullCat.put("text", "") + labels.put(nullCat) + } + val cats = CellRange.getValuesAsJSON(categories[0], wbh) // parse category range into JSON Array + for (z in 0 until cats.length()) { + val aCat = JSONObject() + aCat.put("value", z + b.catLabel) // should= +1 i.e. a category label appears with each category + aCat.put("text", cats.get(z)) + labels.put(aCat) + } + if (b.crossBetween) { // categories appear mid-axis so put "spacers" at 0 and max + val nullCat = JSONObject() + nullCat.put("value", cats.length() + b.catLabel) + nullCat.put("text", "") + labels.put(nullCat) + axisOptions.put("max", cats.length() + b.catLabel/*+1*/) + } + axisOptions.put("labels", labels) + // Defaults axis values ... + axisOptions.put("fixLower", "major") // what do these do?? + axisOptions.put("fixUpper", "major") + } + + // if (c.getCatCross()!=1) + if (b.catMark != 1) { + /* The catMark field defines how often tick marks appear along the category or series axis. A value of 01 indicates + that a tick mark will appear between each category or series; a value of 02 means a label appears between every + other category or series, etc. +*/ + } + + // if (c.getCrossMax()) + } else if (b is ValueRange) { + if (axis.toInt() == ChartConstants.YAXIS) + // normal + b.setMaxMin(yMax, yMin) // must do first + else + b.setMaxMin(nSeries.toDouble(), 0.0) // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series + + // y major/minor scales + axisOptions.put("min", b.min) + axisOptions.put("max", b.max) + axisOptions.put("majorTickStep", b.majorTick) + } else if (b is AxisLineFormat) { + val gridJSON = JSONObject() + gridJSON.put("type", "Grid") + val id = b.id.toInt() + when (id) { + AxisLineFormat.ID_MAJOR_GRID -> if (axis.toInt() == ChartConstants.XAXIS || chartType == ChartConstants.BARCHART) + gridJSON.put("hMajorLines", false) + else + gridJSON.put("vMajorLines", false) + AxisLineFormat.ID_MINOR_GRID -> { + } + } + axisJSON.put("back_grid", gridJSON) + } + } + if (axis.toInt() == ChartConstants.YAXIS) + axisJSON.put("y", axisOptions) + if (axis.toInt() == ChartConstants.XAXIS) + axisJSON.put("x", axisOptions) + } catch (e: JSONException) { + Logger.logErr("Error getting Axis JSON: $e") + } + + return axisJSON + } + + /** + * interface for setting Axis rec-specific XML options + * in a generic fashion + * + * @see OpenXLS.handleChartElement + * + * @see ChartHandle.getXML + */ + override fun setChartOption(op: String, `val`: String): Boolean { + if (op.equals("Label", ignoreCase = true)) { + this.title = `val` + return true + } else if (op.equals("CatCross", ignoreCase = true)) { + getCatserRange(true).catCross = Integer.parseInt(`val`) + return true + } else if (op.equals("LabelCross", ignoreCase = true)) { + getCatserRange(true).catLabel = Integer.parseInt(`val`) + return true + } else if (op.equals("Marks", ignoreCase = true)) { + getCatserRange(true).catMark = Integer.parseInt(`val`) + return true + } else if (op.equals("CrossBetween", ignoreCase = true)) { + getCatserRange(true).crossBetween = `val` == "true" + return true + } else if (op.equals("CrossMax", ignoreCase = true)) { + getCatserRange(true).crossMax = `val` == "true" + return true + } else if (op.equals("MajorGridLines", ignoreCase = true)) { + if (`val` == "false") { + val j = getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID) + if (j > -1) { + chartArr.removeAt(j) // remove AxisLineFormat + chartArr.removeAt(j) // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true) + } + } else if (op.equals("MinorGridLines", ignoreCase = true)) { + if (`val` == "false") { + val j = getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID) + if (j > -1) { + chartArr.removeAt(j) // remove AxisLineFormat + chartArr.removeAt(j) // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true) + } + } else if (op.equals("AddArea", ignoreCase = true)) { + if (axis.toInt() == ChartConstants.XAXIS) { + val alf0 = AxisLineFormat.prototype as AxisLineFormat? + alf0!!.setId(AxisLineFormat.ID_AXIS_LINE) + this.addChartRecord(alf0) + val lf0 = LineFormat.getPrototype(0, 0) as LineFormat + this.addChartRecord(lf0) + } + val alf = AxisLineFormat.prototype as AxisLineFormat? + alf!!.setId(AxisLineFormat.ID_WALLORFLOOR) + this.addChartRecord(alf) + val lf = LineFormat.getPrototype(0, -1) as LineFormat + if (axis.toInt() == 1) + lf.lineStyle = 5 //none + this.addChartRecord(lf) + val af = AreaFormat.getPrototype(axis.toInt()) as AreaFormat + this.addChartRecord(af) + return true + } else if (op == "AreaFg") { // custom foreground on Wall, Side or Floor + val af = Chart.findRec(chartArr, AreaFormat::class.java) as AreaFormat + af.seticvFore(Integer.valueOf(`val`).toInt()) + return true + } else if (op == "AreaBg") { // custom bg on Wall, SIde or Floor + val af = Chart.findRec(chartArr, AreaFormat::class.java) as AreaFormat + af.seticvBack(Integer.valueOf(`val`).toInt()) + return true + } else if (linkedtd != null) { // see if associated TextDisp can handle + return linkedtd!!.setChartOption(op, `val`) + } else if (op.equals("MajorGridLines", ignoreCase = true)) { + if (`val` == "false") { + val j = getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID) + if (j > -1) { + chartArr.removeAt(j) // remove AxisLineFormat + chartArr.removeAt(j) // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true) + } + } else if (op.equals("MinorGridLines", ignoreCase = true)) { + if (`val` == "false") { + val j = getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID) + if (j > -1) { + chartArr.removeAt(j) // remove AxisLineFormat + chartArr.removeAt(j) // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true) + } + } + return false + } + + /** + * sets a specific OOXML axis option; most options apply to any type of axis (Cat, Value, Ser, Date) + *

                      can be one of: + *

                      axPos - position of the axis (b, t, r, l) + *

                      crosses possible crossing points (autoZero, max, min) + *

                      crossBeteween whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) + *

                      crossesAt where on axis the perpendicular axis crosses (double val) + *

                      lblAlign text alignment for tick labels (ctr, l, r) (cat only) + *

                      lblOffset distance of labels from the axis (0-1000) (cat only) + *

                      majorTickMark major tick mark position (cross, in, none, out) + *

                      minorTickMark minor tick mark position ("") + *

                      tickLblPos tick label position (high, low, nextTo, none) + *

                      tickLblSkip how many tick labels to skip between label (int >= 1) (cat only) + *

                      tickMarkSkip how many tick marks to skip betwen ticks (int >= 1) (cat only) + *

                      majorUnit distance between major tick marks (val, date ax only) (double >= 0) + *

                      minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + *

                      MajorGridLines + *

                      MinorGridLines + * + * @param op + * @param val + */ + private fun setOption(op: String, `val`: String) { + if (op == "axPos") { // val= "b" (bottom) "l", "t", "r" -->????? + axPos = `val` // for now + } else if (op == "lblOffset" || op == "lblAlgn") { + var cl = Chart.findRec(chartArr, CatLab::class.java) as CatLab + if (cl == null) { + cl = CatLab.prototype as CatLab? + cl.parentChart = this.parentChart + chartArr.add(1, cl) // second in chart array, after CatSerRange + } + cl.setOption(op, `val`) + } else if (op == "tickLblPos" || // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick + + op == "majorTickMark" || // major tick marks (cross, in, none, out) + + op == "minorTickMark") { // minor tick marks (cross, in, none, out) + val t = Chart.findRec(chartArr, Tick::class.java) as Tick + t.setOption(op, `val`) + } else if (op.equals("MajorGridLines", ignoreCase = true)) { + if (`val` == "false") { + val j = getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID) + if (j > -1) { + chartArr.removeAt(j) // remove AxisLineFormat + chartArr.removeAt(j) // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MAJOR_GRID, true) + } + } else if (op.equals("MinorGridLines", ignoreCase = true)) { + if (`val` == "false") { + val j = getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID) + if (j > -1) { + chartArr.removeAt(j) // remove AxisLineFormat + chartArr.removeAt(j) // remove corresponding Line Format + } + } else { // add major grid lines + getAxisLineFormat(AxisLineFormat.ID_MINOR_GRID, true) + } + } else { // valuerange, caterrange options -- crosses, crossBetween, crossesAt, tickMarkSkip (cat only), tickLblSkip (cat only), majorUnit (val only), minorUnit (val only) + // KSC: TESTING + //Logger.logInfo("Setting option: " + op + "=" + val); + for (i in chartArr.indices) { + val b = chartArr[i] + if (b is CatserRange) { + if (b.setOption(op, `val`)) + break + } else if (b is ValueRange) { + if (b.setOption(op, `val`)) + break + } + } + } + } + + /** + * get the desired axis option for this axis + *

                      can be one of: + *

                      axPos - position of the axis (b, t, r, l) + *

                      crosses possible crossing points (autoZero, max, min) + *

                      crossBeteween whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) + *

                      crossesAt where on axis the perpendicular axis crosses (double val) + *

                      lblAlign text alignment for tick labels (ctr, l, r) (cat only) + *

                      lblOffset distance of labels from the axis (0-1000) (cat only) + *

                      majorTickMark major tick mark position (cross, in, none, out) + *

                      minorTickMark minor tick mark position ("") + *

                      tickLblPos tick label position (high, low, nextTo, none) + *

                      tickLblSkip how many tick labels to skip between label (int >= 1) (cat only) + *

                      tickMarkSkip how many tick marks to skip betwen ticks (int >= 1) (cat only) + *

                      majorUnit distance between major tick marks (val, date ax only) (double >= 0) + *

                      minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + * + * @param op String option name + * @return String val of option or "" + */ + fun getOption(op: String): String? { + if (op == "axPos") { // val= "b" (bottom) "l", "t", "r" -->????? + return axPos // for now -- can't find matching Axis attribute + } else if (op == "lblAlign" || op == "lblOffset") { + val c = Chart.findRec(chartArr, CatLab::class.java) as CatLab + return c?.getOption(op) +// use defaults + } else if (op == "crossesAt" || // specifies where axis crosses -- numCross or catCross + + op == "orientation" || // axis orientation minMax or maxMin -- fReverse + + op == "crosses" || // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross + + op == "max" || // axis max - valueRange only? + + op == "max" || // axis min- valueRange only? + + op == "tickLblSkip" || //val= how many tick labels to skip btwn label -- catLabel -- Catserrange only?? + + op == "tickMarkSkip" || //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? + + op == "crossBetween") { // value axis only -- val= between, midCat, crossBetween + // logScale-- ValueRange + for (i in chartArr.indices) { + val b = chartArr[i] + if (b is CatserRange) { + return b.getOption(op) + } else if (b is ValueRange) { + return b.getOption(op) + } + } + //TICK Options + } else if (op == "tickLblPos" || // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick + + op == "majorTickMark" || // major tick marks (cross, in, none, out) + + op == "minorTickMark") { // minor tick marks (cross, in, none, out) + val t = Chart.findRec(chartArr, Tick::class.java) as Tick + return t.getOption(op) + } + return null + } + + /** + * return the JSON/Dojo representation of this axis + * chartType int necessary for parsing AXIS options: horizontal charts "switch" axes ... + * + * @return JSONObject + */ + fun getMinMaxJSON(wbh: io.starter.OpenXLS.WorkBookHandle, chartType: Int, yMax: Double, yMin: Double, nSeries: Int): JSONObject { + val axisJSON = JSONObject() + val axisOptions = JSONObject() + try { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b is CatserRange) { +// for x/Category axis: if has labels, gather and input into axis label JSON array + val categories = this.parentChart!!.getCategories(-1) + if (categories != null) { + val cats = CellRange.getValuesAsJSON(categories[0], wbh) // parse category range into JSON Array + axisOptions.put("max", cats.length() + b.catLabel/*+1*/) + } + break // only need this per axis + } else if (b is ValueRange) { + if (axis.toInt() == ChartConstants.YAXIS) + // normal + b.setMaxMin(yMax, yMin) // must do first + else + b.setMaxMin(nSeries.toDouble(), 0.0) // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series + + // y major/minor scales + axisOptions.put("min", b.min) + axisOptions.put("max", b.max) + axisOptions.put("majorTickStep", b.majorTick) + break // only need this per axis + } + } + if (axis.toInt() == ChartConstants.YAXIS) + axisJSON.put("y", axisOptions) + if (axis.toInt() == ChartConstants.XAXIS) + axisJSON.put("x", axisOptions) + } catch (e: JSONException) { + Logger.logErr("Error getting Axis JSON: $e") + } + + return axisJSON + } + + /** + * return the OOXML txPr element for this axis + * + * @return + */ + fun gettxPr(): TxPr? { + return txpr + } + + /** + * set the OOXML title element for this axis + * + * @param t + */ + fun settxPr(t: TxPr) { + txpr = t.cloneElement() as TxPr + } + + override fun toString(): String { + var s = "" + when (axis) { + ChartConstants.XAXIS -> s = "XAxis" + ChartConstants.YAXIS -> s = "YAxis" + ChartConstants.ZAXIS -> s = "ZAxis" + ChartConstants.XVALAXIS -> s = "XValAxis" + } + if (linkedtd != null) + s = s + " " + linkedtd!!.toString() + return s + } + + /** + * return the maximum value of this Value or Y axis scale + * + * @return + */ + fun getMaxScale(minmax: DoubleArray): Double { + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + if (v != null) { // shouldn't + if (v.isAutomaticMax) + v.setMaxMin(minmax[1], minmax[0]) + return v.max + } + return -1.0 + } + + /** + * return the minimum value of this value or Y axis scale + * + * @return + */ + fun getMinScale(minmax: DoubleArray): Double { + /* + * Because a horizontal (category) axis (axis: A line bordering the chart plot area used as + * a frame of reference for measurement. * The y axis is usually the vertical axis and contains data. The x-axis is usually the horizontal axis and contains categories.) + * displays text labels instead of numeric intervals, + * there are fewer scaling options that you can change than there are for a vertical (value) axis. + * However, you can change the number of categories to display between tick marks, the order in which + * to display categories, and the point where the two axes cross. + */ + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + if (v != null) { + /* + if (wType==YAXIS) // normal + v.setMaxMin(yMax, yMin); // must do first + else + v.setMaxMin(nSeries, 0); // scatter and bubble charts have X axis with Value Range, scale is 0 to number of series + */ + // why? should already v.setParentChart(this.getParentChart()); + if (v.isAutomaticMin) + v.setMaxMin(minmax[1], minmax[0]) + return v.min + } + return -1.0 + } + + /** + * set the minimum value of this axis scale + *

                      Note: this disables automatic scaling + * + * @param Min + */ + fun setMinScale(min: Int) { + // TODO: also update ticks? + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + if (v != null) { // shouldn't + v.min = min + } + } + + /** + * set the maximum value of this axis scale + *

                      Note: this disables automatic scaling + * + * @param Max + */ + fun setMaxScale(max: Int) { + // TODO: also update ticks? + val v = Chart.findRec(this.chartArr, ValueRange::class.java) as ValueRange + if (v != null) { // shouldn't + v.max = max + } + } + + /** + * parse OOXML axis element + * + * @param xpp XmlPullParser positioned at correct elemnt + * @param axisTag catAx, valAx, serAx, dateAx + * @param lastTag Stack of element names + */ + // noMultiLvlLbl -- val= 1 (true) means draw labels as flat text; not included or 0 (false)= draw labels as a heirarchy + fun parseOOXML(xpp: XmlPullParser, axisTag: String, lastTag: Stack, bk: WorkBookHandle) { + // crossAx -- need to parse? + // auto -- need to parse? + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "scaling") { // additional axis settings + lastTag.push(tnm) + val sc = Scaling.parseOOXML(xpp, lastTag) as Scaling + var s = sc.getOption("orientation") + if (s != null) this.setOption("orientation", s) + s = sc.getOption("min") + if (s != null) this.setOption("min", s) + s = sc.getOption("max") + if (s != null) this.setOption("max", s) + // the below children only have 1 attribute: val + } else if (tnm == "axPos") { // // position of the axis (b, t, r, l) + this.setOption(tnm, xpp.getAttributeValue(0)) + } else if (tnm == "majorGridlines" || tnm == "minorGridlines") { + lastTag.push(tnm) + parseGridlinesOOXML(xpp, lastTag, bk) + } else if (tnm == "title") { + lastTag.push(tnm) + this.ooxmlTitle = Title.parseOOXML(xpp, lastTag, bk).cloneElement() as Title + this.title = this.ooxmlTitle!!.title + } else if (tnm == "numFmt") { + this.nf = NumFmt.parseOOXML(xpp).cloneElement() as NumFmt + } else if (tnm == "majorTickMark" || // major tick mark position (cross, in, none, out) + + tnm == "minorTickMark" || // minor tick mark position ("") + + tnm == "tickLblPos") { // tick label position (high, low, nextTo, none) + this.setOption(tnm, xpp.getAttributeValue(0)) + } else if (tnm == "spPr") { // axis shape properties - for axis or gridlines + lastTag.push(tnm) + this.spPr = SpPr.parseOOXML(xpp, lastTag, bk).cloneElement() as SpPr + } else if (tnm == "txPr") { // text Properties for axis + lastTag.push(tnm) + this.settxPr(TxPr.parseOOXML(xpp, lastTag, bk).cloneElement() as TxPr) + // crossesAx = crossing axis id - need ? + } else if (tnm == "crosses" || // possible crossing points (autoZero, max, min) + tnm == "crossesAt") { // where on axis the perpendicular axis crosses (double val) + this.setOption(tnm, xpp.getAttributeValue(0)) + } else if (tnm == "crossBetween") { // whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) + this.setOption(tnm, xpp.getAttributeValue(0)) + } else if (// cat, date, ser ax only + // auto -- Date only? + tnm == "lblAlign" || // text alignment for tick labels (ctr, l, r) only for cat + + tnm == "lblOffset" || // distance of labels from the axis (0-1000) only for cat, date + + tnm == "tickLblSkip" || // how many tick labels to skip between label (int >= 1) + + tnm == "tickMarkSkip") { // how many tick marks to skip betwen ticks (int >= 1) + this.setOption(tnm, xpp.getAttributeValue(0)) + // TODO: noMultiLvlLbl + } else if (// val, ser ax + some date ax + tnm == "crossBeteween" || // whether axis crosses the cat. axis between or on categories (value axis only) (between, midCat) + + tnm == "majorUnit" || // distance between major tick marks (val, date ax only) (double >= 0) + + tnm == "minorUnit") { // distance between minor tick marks (val, date ax only) (double >= 0) + this.setOption(tnm, xpp.getAttributeValue(0)) + } else if (tnm == "dispUnits") { // valAx only + parseDispUnitsOOXML(xpp, lastTag) + } + // TODO: date ax specifics + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == axisTag) { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("Axis: $e") + } + + } + + /** + * parse major or minor Gridlines element + * + * @param xpp + * @param lastTag + */ + private fun parseGridlinesOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle) { + val endTag = lastTag.peek() + this.setOption(endTag, "true") + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "spPr") { + lastTag.push(tnm) + val sppr = SpPr.parseOOXML(xpp, lastTag, bk).cloneElement() as SpPr + val lf = getAxisLine(if (endTag == "majorGridlines") AxisLineFormat.ID_MAJOR_GRID else AxisLineFormat.ID_MINOR_GRID) + lf!!.setFromOOXML(sppr) + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == endTag) { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("parseGridLinesOOXML: $e") + } + + } + + /** + * parse the dispUnits child element of valAx + *

                      TODO: do not know how to interpret most of these options + * + * @param xpp + * @param lastTag + */ + private fun parseDispUnitsOOXML(xpp: XmlPullParser, lastTag: Stack) { + try { + var eventType = xpp.eventType + var ym: YMult? = null + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "custUnit") { + ym = getYMultRec(true) + ym!!.customMultiplier = java.lang.Double.valueOf(xpp.getAttributeValue(0)) + } else if (tnm == "builtInUnit") { + ym = getYMultRec(true) + ym!!.setAxMultiplierId(xpp.getAttributeValue(0)) + } + // TODO: dispUnitLbl + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == "dispUnits") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("parseDispUnitsOOXML: $e") + } + + } + + /** + * generate the appropriate ooxml for the given axis + * + * @param type 0= Category Axis, 1= Value Axis, 2= Ser Axis, 3= Date Axis + * @param id + * @param crossId crossing axis id + * @return ORDER: + * Axis Common: + * axisId REQ + * scaling -- orientation + * delete + * axPos REQ? + * majorGridlines + * minorGridlines + * title + * numFmt + * majorTickMark + * minorTickMark + * tickLblPos + * spPr + * txPr + * crossAx + * crosses OR + * crossesAt + * after: + * valAx: + * crossBetween + * majorUnit + * minorUnit + * dispUnits + * catAx: + * auto + * lblAlign + * lblOffet + * tickLblSkip + * tickMarkSkip + * noMultiLvlLbl + * serAx: + * tickLblSkip + * tickMarkSkip + */ + + fun getOOXML(type: Int, id: String, crossId: String): String { + if (this.parentChart == null) + // happens on ZAxis, XYValAxis ... + this.parentChart = this.ap!!.parentChart + val from2003 = !parentChart!!.workBook!!.isExcel2007 + + val axisooxml = StringBuffer() + var axis = "" + when (type) { + 0 // cat axis "X" + -> axis = "catAx" + 1 // val axis "Y" + -> axis = "valAx" + 2 // xval axis "X" axis for multiple val axes: bubble & scatter charts + -> axis = "valAx" + 3 // ser ("Z" axis) + -> axis = "serAx" + 4 // date TODO: Not correct or tested! + -> axis = "dateAx" + } + // axis main element + axisooxml.append("") + axisooxml.append("\r\n") + // axId - required + axisooxml.append("") + axisooxml.append("\r\n") + // scaling - required + var s = this.getOption("orientation") + val d = this.minMax + if (s != null || d[0] != d[1]) { // if have orientation or min/max set .. + axisooxml.append("\r\n") + if (s != null) axisooxml.append("\r\n") + axisooxml.append("\r\n") + } + // axPos - required + if (this.getOption("axPos") != null) { + axisooxml.append("") + axisooxml.append("\r\n") + } else {// it's required + if (this.parentChart!!.chartType != ChartConstants.BARCHART) { + if (axis == "catAx" || axis == "serAx") + axisooxml.append("") + else + axisooxml.append("") + } else { + if (axis == "catAx" || axis == "serAx") + axisooxml.append("") + else + axisooxml.append("") + } + axisooxml.append("\r\n") + } + // major Gridlines + if (this.hasGridlines(AxisLineFormat.ID_MAJOR_GRID)) { + axisooxml.append("") + axisooxml.append(getAxisLine(AxisLineFormat.ID_MAJOR_GRID)!!.ooxml) + axisooxml.append("\r\n") + } + // minor Gridlines + if (this.hasGridlines(AxisLineFormat.ID_MINOR_GRID)) { + axisooxml.append("") + axisooxml.append(getAxisLine(AxisLineFormat.ID_MINOR_GRID)!!.ooxml) + axisooxml.append("\r\n") + } + // Title + if (this.ooxmlTitle != null) + axisooxml.append(this.ooxmlTitle!!.ooxml) + else if (from2003) { // create OOXML title + if (this.title != "") { + val ttl = io.starter.formats.OOXML.Title(this.title) + if (type == 0) + ttl.setLayout(.026, .378) + else if (type == 1) + ttl.setLayout(.468, .863) + axisooxml.append(ttl.ooxml) + } + } + // numFmt + if (this.nf != null) + axisooxml.append(nf!!.getOOXML("c:")) //need a default???: axisooxml.append(""); axisooxml.append("\r\n"); + // majorTickMark + s = this.getOption("majorTickMark") // default= "cross" + if (s != null) axisooxml.append("") + // minorTickMark + s = this.getOption("minorTickMark") // default= "cross" + if (s != null) axisooxml.append("") + // tickLblPos + s = this.getOption("tickLblPos") // default= "nextTo" + if (s != null) axisooxml.append("") + // shape properties + if (this.spPr != null) axisooxml.append(this.spPr!!.ooxml) + // text props + if (this.gettxPr() != null) + axisooxml.append(this.gettxPr()!!.ooxml) + else if (from2003) { // XLS->XLSX + /* + * label font: + * Fontx fx= (Fontx) Chart.findRec(chartArr, Fontx.class); + return this.getParentChart().getWorkBook().getFont(fx.getIfnt()); +*/ + var rot = 0 + val t = Chart.findRec(this.chartArr, Tick::class.java) as Tick + if (t != null) { // shoudn't + rot = t.rotation.toInt() + /** + * 0= no rotation (text appears left-to-right), + * 1= text appears top-~~ are upright, + * 2= text is rotated 90 degrees counterclockwise, + * 3= text is rotated + */ + // convert BIFF8 rotation to TxPr rotation: + when (rot) { + 1 -> { + } + 2 -> rot = -5400000 + 3 -> rot = 5400000 + }//???? + // TODO: is vert rotation from td? + val txpr = TxPr(this.labelFont!!, rot, null) + axisooxml.append(txpr.ooxml) + } + axisooxml.append("\r\n") + } + // crossesAx + axisooxml.append("") + axisooxml.append("\r\n") // crosses axis ... + // crosses -- autoZero, max, min + if (this.getOption("crosses") != null) + axisooxml.append("") + axisooxml.append("\r\n")// where axis crosses it's perpendicular axis + if (axis == "catAx" || axis == "serAx") { + // auto + axisooxml.append("\r\n") + s = this.getOption("lblAlgn") + if (s != null) axisooxml.append("\r\n") + s = this.getOption("lblOffset") + if (s != null) axisooxml.append("\r\n") + s = this.getOption("tickLblSkip") + if (s != null) axisooxml.append("\r\n") + s = this.getOption("tickMarkSkip") + if (s != null) axisooxml.append("\r\n") + // TODO: noMutliLvlLbl + } else { // val or date + s = this.getOption("crossBetween") + if (s != null) axisooxml.append("\r\n") + s = this.getOption("majorUnit") + if (s != null) axisooxml.append("\r\n") + s = this.getOption("minorUnit") + if (s != null) axisooxml.append("\r\n") + // TODO: dispUnit ************************************ + + } + axisooxml.append("") + axisooxml.append("\r\n") + return axisooxml.toString() + } + + + /** + * returns the SVG representation of the desired axis + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @return String SVG + */ + fun getSVG(ca: ChartAxes, chartMetrics: Map, categories: Array): String { + val svg = StringBuffer() + + // for all axies-- label and title fonts, rotation, tick info ... + // Title + Label SVG + var labelfontSVG = "" + var titlefontSVG = "" + + try { + labelfontSVG = this.labelFont!!.svg // uses specific or default for chart + } catch (e: Exception) { // shouldn't + labelfontSVG = "font-family='Arial' font-size='9pt' fill='" + ChartType.darkColor + "' " + } + + try { + titlefontSVG = linkedtd!!.getFont(this.parentChart!!.workBook!!)!!.svg + } catch (e: NullPointerException) { + } + + var showMinorTickMarks = false + var showMajorTickMarks = true + try { + val t = Chart.findRec(chartArr, Tick::class.java) as Tick + showMinorTickMarks = t.showMinorTicks() + showMajorTickMarks = t.showMajorTicks() + } catch (e: Exception) { + } + + // BAR CHART AXES ARE SWITCHED - handle seperately for clarity; radar axes are also handled separately as are significantly different than regualr charts + val charttype = this.parentChart!!.chartType + if (charttype == ChartConstants.BARCHART) + return getSVGBARCHART(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories) + if (charttype == ChartConstants.RADARCHART) + return getSVGRADARCHART(ca, titlefontSVG, labelfontSVG, chartMetrics, categories) + + var wtype = axis.toInt() + if (wtype == ChartConstants.XAXIS && (charttype == ChartConstants.SCATTERCHART || charttype == ChartConstants.BUBBLECHART)) { // XY Charts - X Axis is a Value Axis + wtype = ChartConstants.XVALAXIS + } + + when (wtype) { + ChartConstants.XAXIS -> svg.append(drawXAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories)) + ChartConstants.YAXIS -> svg.append(drawYAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics)) + ChartConstants.ZAXIS // ?? + -> { + } + + ChartConstants.XVALAXIS // Scatter/Bubble Chart X Value Axis + -> svg.append(drawXYValAxisSVG(ca, titlefontSVG, labelfontSVG, showMinorTickMarks, showMajorTickMarks, chartMetrics, categories)) + } + return svg.toString() + } + + /** + * generate SVG for a basic (non-bar-chart, non-textual) X Axis + * + * @param ca + * @param titlefontSVG + * @param labelfontSVG + * @param rot + * @param showMinorTickMarks + * @param showMajorTickMarks + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @return + */ + private fun drawXAxisSVG(ca: ChartAxes, titlefontSVG: String, labelfontSVG: String, showMinorTickMarks: Boolean, showMajorTickMarks: Boolean, chartMetrics: Map, categories: Array?): String { + val svg = StringBuffer() + // X Axis TICKS x= rectX, x2= x1, y1= recty+h, y2=y1+5 (major) x1 increments by (#Major Ticks-1)/Width (usually 4) + // x= rectX+17, x2= x1, y1= recty+h y2=y1+2 (minor) x1 increments by (approx 8) + // LABELS (CATEGORIES): - do before ticks NOTE: patterns and formatting is applied .adjustCoordinates in order to account for fitting in space + + // when x axis is reversed means that categories are right to left and the y axis is on the RHS + // when y axis is reversed means the categories are on TOP of the chart and y axis labels are reversed + var x0: Double + val x1: Double + var y0: Double + var y1: Double + var inc: Double + var f: java.awt.Font? = null + val x = chartMetrics["x"] + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val canvash = chartMetrics["canvash"] + val yAxisReversed = ca.getMetric("yAxisReversed") as Boolean + val xAxisReversed = ca.getMetric("xAxisReversed") as Boolean + val xAxisRotate = ca.getMetric("xAxisRotate") as Int + val XAXISLABELOFFSET = ca.getMetric("XAXISLABELOFFSET") as Double + val XAXISTITLEOFFSET = ca.getMetric("XAXISTITLEOFFSET") as Double + + val labelRot = if (axis.toInt() == ChartConstants.XAXIS) xAxisRotate else 0 // TODO: handle Y axis rotation + if (labelRot != 0) { + // get font object so can calculate rotation point + val lf = this.labelFont + try { + // get awt Font so can compute and fit category in width + f = java.awt.Font(lf!!.fontName, lf.fontWeight, lf.fontHeightInPoints.toInt()) + } catch (e: Exception) { + } + + } + if (categories != null && categories.size > 0) { // shouldn't + // Category Labels - centered within area on X Axis + inc = w / categories.size + svg.append(getCategoriesSVG(x, y, w, h, inc, labelRot, categories, f, labelfontSVG, yAxisReversed, xAxisReversed, XAXISLABELOFFSET)) + // TICK MARKS + y0 = y + if (!yAxisReversed) h else 0 // ticks at bottom edge of axis unless Y axiis is reversed + x0 = x // start at chart x + val rfY = if (!yAxisReversed) 1 else -1 // reverse factor :) + val rfX = if (!xAxisReversed) 1 else -1 // reverse factor :) + svg.append("\r\n") + inc = w / categories.size // w/scale factor + var minorinc = 0.0 + if (showMinorTickMarks) + minorinc = inc / 2 // half-marks for category axis + for (i in 0..categories.size) { + y1 = y0 + 2 * rfY // minor tick mark + if (showMinorTickMarks) { + for (j in 0..1) { // for categories, only option is 1/2 major + svg.append("\r\n") + x0 += minorinc + } + } + y1 = y0 + 5 * rfY // Major tick marks + if (showMajorTickMarks) + svg.append("\r\n") + x0 += inc + } + // bounding edge line + if (hasPlotAreaBorder()) { + x0 = x + if (!xAxisReversed) w else 0 + svg.append("\r\n") + } + svg.append("\r\n") + } + // X AXIS TITLE + val titleRot = if (linkedtd != null) linkedtd!!.rotation else 0 + x0 = x + w / 2 + if (!yAxisReversed) + // TODO: why doesn't "normal" calc work??????? + y0 = canvash - XAXISTITLEOFFSET + else + y0 = y - XAXISTITLEOFFSET - XAXISLABELOFFSET + + svg.append(getAxisTitleSVG(x0, y0, titlefontSVG, titleRot, "xaxistitle")) + return svg.toString() + } + + /** + * generate SVG for a basic (non-bar-chart, value or numeric) Y Axis + * + * @param ca + * @param titlefontSVG + * @param labelfontSVG + * @param rot + * @param showMinorTickMarks + * @param showMajorTickMarks + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @return + */ + // TODO: label rotation + private fun drawYAxisSVG(ca: ChartAxes, titlefontSVG: String, labelfontSVG: String, showMinorTickMarks: Boolean, showMajorTickMarks: Boolean, chartMetrics: Map): String { + val svg = StringBuffer() + // Y or Value Axis- must be non-textual; values obtained in calling method + // When Y Axis is reversed, scale is reversed and x axis labels and title are on top + // When X Axis is reversed, Y scale/labels and title are on RHS + var x0: Double + var x1: Double + var y0: Double + var y1: Double + val inc: Double + // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 + val x = chartMetrics["x"] + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val minor = ca.getMetric("minor") as Double + val major = ca.getMetric("major") as Double + val scaleIsInteger = major == Math.floor(major) // true if scale is in integer form rather than double (used for formatting label text) + val titleRot = if (linkedtd != null) linkedtd!!.rotation else 0 + val xAxisReversed = ca.getMetric("xAxisReversed") as Boolean + val yAxisReversed = ca.getMetric("yAxisReversed") as Boolean + val xPattern = ca.getMetric("xPattern") as String + val yPattern = ca.getMetric("yPattern") as String + val YAXISLABELOFFSET = ca.getMetric("YAXISLABELOFFSET") as Double + val YAXISTITLEOFFSET = ca.getMetric("YAXISTITLEOFFSET") as Double + + // Y Axis GRIDLINES: -- x1=rectX x2=rectx+rect w y1=y2 y1 starts with recty+h, decrements by= approx 27 ==rectheight/# lines ***************************** + // Major/Minor tick marks + if (major > 0) { // if is displaying tick marks/grid lines - usual case + inc = h / ((max - min) / major) + var minorinc = 0.0 + if (minor > 0) + minorinc = inc / (major / minor) + x0 = x + y0 = y + h + x1 = x + w // entire width + // GRIDLINES + var lineSVG = getLineSVG(AxisLineFormat.ID_MAJOR_GRID) + svg.append("\r\n") + run { + var i = min + while (i <= max) { + svg.append("\r\n") + y0 -= inc + i += major + } + } + svg.append("\r\n") + // TICK MARKS + svg.append("\r\n") + val rfY = if (!yAxisReversed) 1 else -1 // reverse factor :) + val rfX = if (!xAxisReversed) 1 else -1 // reverse factor :) + y0 = y + if (!yAxisReversed) h else 0 // starts at bottom, goes up to top except if reversed + lineSVG = ChartType.strokeSVG + var scale = 0 + run { + // figure out if scale has a fractional portion; if so, determine desired scale and keep to that + val s = major.toString() + val z = s.indexOf(".") + if (z != -1) + scale = s.length - (z + 1) + } + var k = 0 // axis label index + var i = min + while (i <= max) { + x0 = x + if (!xAxisReversed) 0 else w + x1 = x0 - 2 * rfX// minor ticks + y1 = y0 + if (i < max && minor > 0) { + if (showMinorTickMarks) { + var j = 0 + while (j < major / minor) { + svg.append("\r\n") + y0 -= minorinc + j++ + } + } + } + y0 = y1 + x1 = x0 - 5 * rfX// Major tick Marks + if (showMajorTickMarks) + svg.append("\r\n") + // Y Axis Labels + val bd = java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP) // if fractional, ensure java's floating point crap is handled corectly + if (!xAxisReversed) + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n") + else + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n") + y0 -= inc * rfY + i += major + } + svg.append("\r\n") + // AXIS bounding line + x0 = x + if (!xAxisReversed) 0 else w + y0 = y + svg.append("\r\n") + // Y AXIS TITLE + if (!xAxisReversed) + x0 = /*YAXISTITLEOFFSET + */ 10.0 // TODO: 10 should be actual font height (when rotated 90 as is the norm) + else + x0 = x + w + YAXISTITLEOFFSET + svg.append(getAxisTitleSVG(x0, y + h / 2, titlefontSVG, titleRot, "yaxistitle")) + } + return svg.toString() + } + + /** + * return the svg to display an XYValue axis (bubble, scatter) + * + * @param ca + * @param titlefontSVG + * @param labelfontSVG + * @param showMinorTickMarks + * @param showMajorTickMarks + * @param chartMetrics + * @return + */ + private fun drawXYValAxisSVG(ca: ChartAxes, titlefontSVG: String, labelfontSVG: String, showMinorTickMarks: Boolean, showMajorTickMarks: Boolean, chartMetrics: Map, categories: Array?): String { + val svg = StringBuffer() + var x0: Double + val x1: Double + var y0: Double + var y1: Double + var minorinc = 0.0 + val inc: Double + // Y or Value Axis- must be non-textual; values obtained in calling method + // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 + val x = chartMetrics["x"] + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + var minor = ca.getMetric("minor") as Double + var major = ca.getMetric("major") as Double + val scaleIsInteger = major == Math.floor(major) // true if scale is in integer form rather than double (used for formatting label text) + val yPattern = ca.getMetric("yPattern") as String + var XAXISLABELOFFSET = ca.getMetric("XAXISLABELOFFSET") as Double + val XAXISTITLEOFFSET = ca.getMetric("XAXISTITLEOFFSET") as Double + + if (categories != null && categories.size > 0) { // shouldn't + var xmin = java.lang.Double.MAX_VALUE + var xmax = java.lang.Double.MIN_VALUE + var TEXTUALXAXIS = true + for (j in categories.indices) { + try { + val d = Double(categories[j].toString()) + xmax = Math.max(xmax, d) + xmin = Math.min(xmin, d) + TEXTUALXAXIS = false // if ANY category val is a double, assume it's a normal xyvalue axis + } catch (e: Exception) { + /* keep going */ + } + + } + if (!TEXTUALXAXIS) { + val d = ValueRange.calcMaxMin(xmax, xmin, w) + minor = d[0].toInt().toDouble() + major = d[1].toInt().toDouble() + xmax = d[2] + + } else { // NO category values are numeric -- x axis is Textual - just use count + major = 1.0 + minor = 0.0 + xmax = (categories.size + 1).toDouble() + XAXISLABELOFFSET = 30.0 // TODO: calculate + } + + // CATEGORY LABELS- centered within area on X Axis + y0 = y + h + XAXISLABELOFFSET + inc = w / (xmax / major) + x0 = x + var scale = 0 + run { + // figure out if scale has a fractional portion; if so, determine desired scale and keep to that + val s = major.toString() + val z = s.indexOf(".") + if (z != -1) + scale = s.length - (z + 1) + } + var k = 0 // axis label index + run { + var i = 0.0 + while (i <= xmax) { + if (!TEXTUALXAXIS) { + val bd = java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP) + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n") + } else + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(i) + "\r\n") + x0 += inc + i += major + } + } + + // TICK MARKS + y0 = h + y // origin at y+h + x0 = x // start at chart x + svg.append("\r\n") + if (minor > 0) + minorinc = inc / minor + var i = 0 + while (i <= xmax) { + y1 = y0 + 2 // minor tick mark + var j = 0 + while (j < minor) { + svg.append("\r\n") + x0 += minorinc + j++ + } + y1 = y0 + 5 // Major tick marks + svg.append("\r\n") + if (minorinc == 0.0) + x0 += inc + i += major.toInt() + } + svg.append("\r\n") + } + // X AXIS TITLE + val titleRot = if (linkedtd != null) linkedtd!!.rotation else 0 + svg.append(getAxisTitleSVG(x + w / 2, y + h + XAXISTITLEOFFSET, titlefontSVG, titleRot, "zaxistitle")) + return svg.toString() + } + + + /** + * Bar chart Axes are switched so handle seperately + * basically x axis holds Y labels and title, and visa versa + gridlines go up and down (traverse y) rather than across (traverse x) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @return String SVG + */ + private fun getSVGBARCHART(ca: ChartAxes, titlefontSVG: String, labelFontSVG: String, showMinorTicks: Boolean, showMajorTicks: Boolean, chartMetrics: Map, categories: Array?): String { + val svg = StringBuffer() + + // major and minor tick marks, max and min axis scales NOTE: for MOST category (usually x) axes, they are textual; the max=# of categories; min=0 + // X Axis/Cats in reversed order means X axis on Top, Y axis labels in reversed order (along with bars) + var x0: Double + var x1: Double + var y0: Double + var y1: Double + var inc: Double + var rfX = 1 // reverse factor used to reverse order + position of axes + var rfY = 1 // "" + var scaleIsInteger = true // usual case + val x = chartMetrics["x"] + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val canvasw = chartMetrics["canvasw"] + val canvash = chartMetrics["canvash"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val xAxisReversed = ca.getMetric("xAxisReversed") as Boolean + val yAxisReversed = ca.getMetric("yAxisReversed") as Boolean + val YAXISTITLEOFFSET = ca.getMetric("YAXISTITLEOFFSET") as Double + val yPattern = ca.getMetric("yPattern") as String + val XAXISLABELOFFSET = ca.getMetric("XAXISLABELOFFSET") as Double + val XAXISTITLEOFFSET = ca.getMetric("XAXISTITLEOFFSET") as Double + val major = ca.getMetric("major") as Double + + if (axis.toInt() == ChartConstants.XAXIS) { + scaleIsInteger = major == Math.floor(major) // true if scale is in integer form rather than double (used for formatting label text) + } + when (axis) { + ChartConstants.XAXIS -> { + // X Axis TICKS x= rectX, x2= x1, y1= recty+h, y2=y1+5 (major) x1 increments by (#Major Ticks-1)/Width (usually 4) + // x= rectX+17, x2= x1, y1= recty+h y2=y1+2 (minor) x1 increments by (approx 8) + x0 = x // start at chart x + y0 = y + if (!xAxisReversed) h else 0 // origin at y+h (unless reversed) + rfX = if (!xAxisReversed) 1 else -1 + rfY = if (!yAxisReversed) 1 else -1 + if (major > 0) { + svg.append("\r\n") + inc = w / ((max - min) / major) // w/scale factor + var scale = 0 + run { + // figure out if scale has a fractional portion; if so, determine desired scale and keep to that + val s = major.toString() + val z = s.indexOf(".") + if (z != -1) + scale = s.length - (z + 1) + } + var k = 0 // axis label index + var i = min + while (i <= max) {// traverse across bottom (or top, if reversed) of x axis) incrementing x value keeping y value constant + y1 = y0 + 5 * rfX // Major tick marks + if (showMajorTicks) + svg.append("\r\n") + // X axis labels= (Values) + val bd = java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP) + if (!xAxisReversed) + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n") + else + svg.append("" + CellFormatFactory.fromPatternString(yPattern).format(bd) + "\r\n") + x0 += inc * rfY + i += major + } + svg.append("\r\n") + } + // AXIS bounding line + y0 = y + if (!xAxisReversed) h else 0 // origin at y+h (unless reversed) + x0 = x // + (!yAxisReversed?0:ci.w); // start at chart x + svg.append("\r\n") + // is the Y AXIS TITLE i.e. goes alongside Y AXIS + if (!yAxisReversed) + x0 = YAXISTITLEOFFSET + else + x0 = x + w + YAXISTITLEOFFSET + svg.append(getAxisTitleSVG(x0, y + h / 2, titlefontSVG, 90, "xaxistitle")) + } + ChartConstants.YAXIS -> { + rfX = if (!xAxisReversed) 1 else -1 // if reversed, y vals on top (x axis), cats are in reversed order (y axis) + rfY = if (!yAxisReversed) 1 else -1 // if reversed, y vals in reverse order (x axis), cats are on RHS (y axis) + if (categories != null && categories.size > 0) { // should! + inc = h / categories.size + x0 = x // + (!yAxisReversed?0:ci.w); // draw y axis tick marks + y axis labels (= categories) + y0 = y + if (!xAxisReversed) h else 0 // starts at bottom, goes up to top unless reversed + var k = 0 // axis label index + for (i in categories.indices) { // traverse Y axis, spacing category labels (x is constant, y is segmented) + x1 = x0 - 5 * rfY// Major tick Marks + if (showMajorTicks) + svg.append("\r\n") + // Y Axis Labels = categories + y1 = y0 - inc * rfX + inc * .5 * rfX.toDouble() + if (!yAxisReversed) + svg.append("" + categories[i].toString() + "\r\n") + else + svg.append("" + categories[i].toString() + "\r\n") + y0 -= inc * rfX + } + // show gridlines (top to bottom) + val lineSVG = getLineSVG(AxisLineFormat.ID_MAJOR_GRID) + if (lineSVG != "") { + y0 = y + x0 = x // start at chart x + inc = w / ((max - min) / major) // w/scale factor + // 1st line is axis line + svg.append("\r\n") + // rest are grid lines + var i = min + while (i < max) { + x0 += inc + svg.append("\r\n") + i += major + } + } + // is the X AXIS TITLE i.e. GOES ON THE X AXIS + y0 = if (!xAxisReversed) canvash - XAXISTITLEOFFSET else y - XAXISTITLEOFFSET + val titleRot = if (linkedtd != null) linkedtd!!.rotation else 0 + svg.append(getAxisTitleSVG(x + w / 2, y0, titlefontSVG, titleRot, "yaxistitle")) + } + } + } + return svg.toString() + } + + /** + * returns the SVG representation of this Radar Chart Axes -- + *

                      Radar Chart Axis look like a spider web + *

                      Note that these coordinates, like all axis scales, match the coordinates and calculations in Rader.getSVG + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @return String SVG + */ + private fun getSVGRADARCHART(ca: ChartAxes, titleFontSVG: String, labelFontSVG: String, chartMetrics: Map, categories: Array?): String { + val x = chartMetrics["x"] + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + var major = ca.getMetric("major") as Double + val svg = StringBuffer() + if (axis.toInt() == ChartConstants.YAXIS) { + svg.append("\r\n") + if (categories != null && categories.size > 0) { // shouldn't + major = major * 2 // appears the scale calc doesn't follow other charts ... + val n = categories.size.toDouble() + val centerx = w / 2 + x + val centery = h / 2 + y + val percentage = 1 / n // divide into equal sections + var radius = Math.min(w, h) / 2.3 // should take up almost entire w/h of chart + val radiusinc = radius / (max / major) + var lastx = centerx + var lasty = centery - radius // again, start straight up + var k = 0 // axis label index + var j = min + while (j <= max) { // each major unit is a concentric line + var angle = 90.0 // starts straight up + var i = 0 + while (i <= n) { // each category is a radial line; <= n so can complete the spider web line path + // get next point on circumference + val x1 = centerx + radius * Math.cos(Math.toRadians(angle)) + val y1 = centery - radius * Math.sin(Math.toRadians(angle)) + svg.append("\r\n") + if (j == 0.0 && i < n) { // print radial lines & labels at very end of chart/top of each line -- only do for 1st round + svg.append("\r\n") + val labelx1 = centerx + (radius + 10) * Math.cos(Math.toRadians(angle)) + val labely1 = centery - (radius + 10) * Math.sin(Math.toRadians(angle)) + svg.append("" + categories[i].toString() + "\r\n") + } + // next angle + angle -= percentage * 360 + lastx = x1 + lasty = y1 + i++ + } + radius -= radiusinc + j += major + } + } + svg.append("\r\n") + } + return svg.toString() + } + + + /** + * return the svg necessary to display categories along an x axis + * + * @param x + * @param y + * @param w + * @param h + * @param inc + * @param labelRot + * @param categories + * @param f + * @param labelfontSVG + * @param yAxisReversed + * @param xAxisReversed + * @param XAXISLABELOFFSET + * @return + */ + private fun getCategoriesSVG(x: Double, y: Double, w: Double, h: Double, inc: Double, labelRot: Int, categories: Array, f: java.awt.Font?, labelfontSVG: String, yAxisReversed: Boolean, xAxisReversed: Boolean, XAXISLABELOFFSET: Double): String { + // Category Labels - centered within area on X Axis + val svg = StringBuffer() + var x0: Double + val x1: Double + var y0: Double + val y1: Double + val k = labelfontSVG.indexOf("font-size=") + 11 + val fh = java.lang.Double.parseDouble(labelfontSVG.substring(k, labelfontSVG.indexOf("pt"))) // approximate height of a line of labels + y0 = y + if (!yAxisReversed) h + XAXISLABELOFFSET / 3 else -XAXISLABELOFFSET // draw on bottom edge of axis unless Y axis is reversed + var m = 0 // axis label index + for (i in categories.indices) { + if (!xAxisReversed) + x0 = x + inc * i + inc / 2 + else + // reversed: category labels start from LHS and go to RHS + x0 = x + w - inc * i - inc / 2 + if (labelRot != 0) { + var len = StringTool.getApproximateStringWidthLB(f, CellFormatFactory.fromPatternString(null).format(categories[i])) + if (labelRot == 45) + len = Math.ceil(len * Math.cos(Math.toRadians(labelRot.toDouble()))).toInt().toDouble() + val offset = (len / 2).toInt() + 5 + y0 = y + if (!yAxisReversed) h + offset else -offset + if (labelRot == 45) + x0 += inc / 2 + + } + // handle multiple lines in X axis labels - must do "by hand" + val s = categories[i].toString().split("\n".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + svg.append("") + for (z in s.indices) + svg.append("" + s[z] + "\r\n") + svg.append("\r\n") + } + return svg.toString() + } + + /** + * return the svg necessary to display an axis title + * + * @param x + * @param y + * @param titlefontSVG + * @param titleRot 0, 45 or -90 degrees + * @param scriptTitle either "xaxistitle", "yaxistitle" or "zaxistitle" + * @return + */ + private fun getAxisTitleSVG(x: Double, y: Double, titlefontSVG: String, titleRot: Int, scriptTitle: String): String { + val svg = StringBuffer() + svg.append("\r\n") + svg.append("" + this.title + "\r\n") + svg.append("\r\n") + return svg.toString() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -8592219101790307789L + // Axis placement + val INVISIBLE = 0 + val LOW = 1 + val HIGH = 2 + val NEXTTO = 3 + + fun getPrototype(wType: Int): XLSRecord { + val a = Axis() + a.opcode = XLSConstants.AXIS + a.data = a.PROTOTYPE_BYTES + // if (wType!=XVALAXIS) + a.setAxis(wType) + // else + // a.setAxis(XAXIS); + // also add the associated records + when (wType) { + ChartConstants.XAXIS -> { + a.addChartRecord(CatserRange.prototype) + a.addChartRecord(Axcent.prototype) + a.addChartRecord(Tick.prototype) + } + ChartConstants.YAXIS, ChartConstants.XVALAXIS -> { + a.addChartRecord(ValueRange.prototype) + a.addChartRecord(Tick.prototype) + val alf = AxisLineFormat.prototype as AxisLineFormat? + alf!!.setId(AxisLineFormat.ID_MAJOR_GRID) // default has major gridlines + a.addChartRecord(alf) + a.addChartRecord(LineFormat.prototype) + } + ChartConstants.ZAXIS -> { + // KSC: TODO: Set CatserRange options correctly when get def!!! ********** + val c = CatserRange.prototype as CatserRange? + a.addChartRecord(c) + a.addChartRecord(Tick.prototype) // TODO: Tick should have + } + } + return a + } + + internal var YLABELSSPACER_X = 10 + internal var YLABELSPACER_Y = 4 + } + +} + +internal class Scaling : OOXMLElement { + private var logBase: String? = null + private var max: String? = null + private var min: String? = null + private var orientation: String? = null + + override// sequence: logBase, orientation, max, min + val ooxml: String + get() { + val ooxml = StringBuffer() + ooxml.append("") + if (orientation != null) ooxml.append("") + if (max != null) ooxml.append("") + if (min != null) ooxml.append("") + ooxml.append("\r\n") + return ooxml.toString() + } + + constructor() { // no-param constructor, set up common defaults + } + + constructor(logBase: String, max: String, min: String, orientation: String) { + this.logBase = logBase + this.max = max + this.min = min + this.orientation = orientation + } + + constructor(sc: Scaling) { + this.logBase = sc.logBase + this.max = sc.max + this.min = sc.min + this.orientation = sc.orientation + } + + /** + * return the specific scaling option + * + * @param op + * @return + */ + fun getOption(op: String): String? { + if (op == "logBase") + return logBase + else if (op == "max") + return max + else if (op == "min") + return min + else if (op == "orientation") + return orientation + return null + } + + override fun cloneElement(): OOXMLElement { + return Scaling(this) + } + + companion object { + + /** + * parse Axis OOXML element (catAx, valAx, serAx or dateAx) + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack<*>): OOXMLElement { + var logBase: String? = null + var max: String? = null + var min: String? = null + var orientation: String? = null + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "scaling") { + } else if (tnm == "logBase") { + logBase = xpp.getAttributeValue(0) + } else if (tnm == "max") { + max = xpp.getAttributeValue(0) + } else if (tnm == "min") { + min = xpp.getAttributeValue(0) + } else if (tnm == "orientation") { + orientation = xpp.getAttributeValue(0) + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "scaling") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("scaling.parseOOXML: $e") + } + + return Scaling(logBase, max, min, orientation) + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.java b/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.java deleted file mode 100644 index ab47bfd..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * AxisLineFormat: Defines a Line that spans an Axis (0x1021) - * The AxisLineFormat record specifies which part of the axis is specified by the - * LineFormat record that follows. - *
                      - * 4 id 2 Axis Line identifier: - * 0= the axis line itself, - * 1= major grid line, - * 2= minor grid line, - * 3= walls or floor - * if 3, MUST be preceded by an Axis record with the wType set to: - * 0x0000 The walls of a 3-D chart - * 0x0001 The floor of a 3-D chart - */ -public class AxisLineFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5243346695500373630L; - private short id = 0; - - public void init() { - super.init(); - id = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - - // id MUST be greater than the id field values in preceding AxisLine records in the current axis - public static XLSRecord getPrototype() { - AxisLineFormat a = new AxisLineFormat(); - a.setOpcode(AXISLINEFORMAT); - a.setData(a.PROTOTYPE_BYTES); - a.init(); - return a; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; - public static final int ID_AXIS_LINE = 0; - public static final int ID_MAJOR_GRID = 1; - public static final int ID_MINOR_GRID = 2; - public static final int ID_WALLORFLOOR = 3; - - public short getId() { - return id; - } - - public void setId(int id) { - this.id = (short) id; - byte[] b = ByteTools.shortToLEBytes(this.id); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - public String toString() { - return "AxisLineFormat: " + ((id == 0) ? "Axis" : ((id == 1) ? "Major" : ((id == 2) ? "Minor" : ((id == 3) ? "Wall or Floor" : "Unknown")))); - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.kt b/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.kt new file mode 100644 index 0000000..071d4f5 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/AxisLineFormat.kt @@ -0,0 +1,86 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **AxisLineFormat: Defines a Line that spans an Axis (0x1021)** + * The AxisLineFormat record specifies which part of the axis is specified by the + * LineFormat record that follows. + *

                      + * 4 id 2 Axis Line identifier: + * 0= the axis line itself, + * 1= major grid line, + * 2= minor grid line, + * 3= walls or floor + * if 3, MUST be preceded by an Axis record with the wType set to: + * 0x0000 The walls of a 3-D chart + * 0x0001 The floor of a 3-D chart + */ +class AxisLineFormat : GenericChartObject(), ChartObject { + var id: Short = 0 + private set + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0) + + override fun init() { + super.init() + id = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + } + + fun setId(id: Int) { + this.id = id.toShort() + val b = ByteTools.shortToLEBytes(this.id) + this.data[0] = b[0] + this.data[1] = b[1] + } + + override fun toString(): String { + return "AxisLineFormat: " + if (id.toInt() == 0) "Axis" else if (id.toInt() == 1) "Major" else if (id.toInt() == 2) "Minor" else if (id.toInt() == 3) "Wall or Floor" else "Unknown" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5243346695500373630L + + // id MUST be greater than the id field values in preceding AxisLine records in the current axis + val prototype: XLSRecord? + get() { + val a = AxisLineFormat() + a.opcode = XLSConstants.AXISLINEFORMAT + a.data = a.PROTOTYPE_BYTES + a.init() + return a + } + val ID_AXIS_LINE = 0 + val ID_MAJOR_GRID = 1 + val ID_MINOR_GRID = 2 + val ID_WALLORFLOOR = 3 + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/AxisParent.java b/src/main/java/io/starter/formats/XLS/charts/AxisParent.java deleted file mode 100644 index 7162cf9..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/AxisParent.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.BiffRec; -import io.starter.toolkit.ByteTools; - -/** - * AxisParent: Axis Size and Location (0x1014) - *

                      - * This record specifies properties of an axis group and the beginning of a collection of records as defined by the chart sheet substream. - *

                      - *

                      - * The Axis parent record stores most of the actual chart information, what type of chart, x and y labels, etc. - *

                      - * 4 iax 2 axis index (0= main, 1= secondary) This field MUST be set to zero when it is in the first AxisParent record in the chart sheet substream, - * This field MUST be set to 1 when it is in the second AxisParent record in the chart sheet substream. - * 16 unused (16 bytes): Undefined and MUST be ignored. - *

                      - *

                      - * this doesn't appear correct given ms doc - * 6 x 4 - * 10 y 4 - * 14 dx 4 len of x axis - * 18 dy 4 len of y axis - */ -public class AxisParent extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2247258217367570732L; - private short iax = 0; - private int x = 0, y = 0, dx = 0, dy = 0; - - public void init() { - super.init(); - iax = ByteTools.readShort(this.getData()[0], this.getData()[1]); - x = ByteTools.readInt(this.getBytesAt(2, 4)); - y = ByteTools.readInt(this.getBytesAt(6, 4)); - dx = ByteTools.readInt(this.getBytesAt(10, 4)); - dy = ByteTools.readInt(this.getBytesAt(14, 4)); - - } - - /** - * default version, returns the 1sst chart format record - *
                      if there are more than 1 chart type in the chart - * there will be multiple chart format records - * - * @return - */ - protected ChartFormat getChartFormat() { - return getChartFormat(0, false); - } - - /** - * get the chart format collection - * - * @return - */ - protected ChartFormat getChartFormat(int nChart, boolean addNew) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == CHARTFORMAT) { - ChartFormat cf = (ChartFormat) b; - if (cf.getDrawingOrder() == nChart) - return cf; - } - } - if (addNew) - return createNewChart(nChart); - return null; - } - - /** - * create basic cf necessary to define multiple or overlay charts - * and add to this AxisParent - * - * @param nChart overlay chart number (>0 and <=9) - * @return new ChartFormat - */ - protected ChartFormat createNewChart(int nChart) { - // muliple charts, create one - if (nChart > 0 && nChart <= 9) { - ChartFormat cf = (ChartFormat) ChartFormat.getPrototype(); - cf.setParentChart(this.getParentChart()); - Bar b = (Bar) Bar.getPrototype(); - cf.chartArr.add(b); // add a dummy chart object - will be replaced later - ChartFormatLink cfl = (ChartFormatLink) ChartFormatLink.getPrototype(); - cf.chartArr.add(cfl); - SeriesList sl = new SeriesList(); - sl.setOpcode(SERIESLIST); - sl.setParentChart(this.getParentChart()); - cf.chartArr.add(sl); - cf.setDrawingOrder(nChart); - this.chartArr.add(cf); // add chartformat to chart array of axis parent - return cf; - } - return null; - } - - /** - * remove axis records for pie-type charts ... - */ - public void removeAxes() { - if (chartArr.get(1) instanceof Axis) - chartArr.remove(1); // remove Axis - if (chartArr.get(1) instanceof Axis) - chartArr.remove(1); // remove Axis - if (chartArr.get(1) instanceof TextDisp) - chartArr.remove(1); // remove Text for Axis - if (chartArr.get(1) instanceof TextDisp) - chartArr.remove(1); // remove Text for Axis - if (chartArr.get(1) instanceof PlotArea) - chartArr.remove(1); // remove - if (chartArr.get(1) instanceof Frame) - chartArr.remove(1); // remove Frame - // all should be left is pos + chartFormat - } - - /** - * remove the desired axis + associated records - * - * @param axisType - */ - public void removeAxis(int axisType) { - // Remove axis - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == AXIS) { - if (((Axis) b).getAxis() == axisType) { - chartArr.remove(i); - break; - } - } - } - int tdType = TextDisp.convertType(axisType); - // Remove TextDisp assoc with Axis - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == TEXTDISP) { - TextDisp td = (TextDisp) b; - if (tdType == td.getType()) { - chartArr.remove(i); - break; - } - } - } - } - - /** - * for those charts such as Radar, remove the - * axis frame and plot area records as are not necessary - */ - protected void removePlotArea() { - boolean remove = false; - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == AXIS) { - remove = true; - } else if (b.getOpcode() == CHARTFORMAT) { - break; - } else if (remove) { - chartArr.remove(i); - i--; - } - } - - } - - /** - * return XML of axis label and specific options for the desired axis - * (both the Axis record + it's "associated" TextDisp record are consulted) - * - * @param Axis int desired Axis - * @return String XML of axis label + specific options - * @see ObjectLink - */ // KSC: TODO: Get all axis ops ... - protected String getAxisOptionsXML(int axis) { - boolean bHasAxis = false; - boolean bHasLabel = false; - StringBuffer sb = new StringBuffer(); - int tdType = TextDisp.convertType(axis); - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == AXIS) { - if (((Axis) b).getAxis() == axis) { - sb.append(((Axis) b).getOptionsXML()); - bHasAxis = true; - } - } else if (b.getOpcode() == TEXTDISP) { - if (((TextDisp) b).getType() == tdType) { - sb.append(((TextDisp) b).getOptionsXML()); - bHasLabel = true; - break; - } - } - } - if (bHasAxis && !bHasLabel) // must include axis even if no textdisp/label - sb.append(" Label=\"\""); - return sb.toString(); - } - - - /** - * return the desired axis according to AxisType - * (will create the axis if it doesn't exist) - * Also finds and associates the axis rec with it's TextDisp - * so as to be able to set axis optins ... - * will create Axis + TextDisp if not present ... - * - * @param axisType - * @return Desired axis - */ - public Axis getAxis(int axisType) { - return getAxis(axisType, true); - } - - /** - * return the desired axis according to AxisType - * Also finds and associates the axis rec with it's TextDisp - * so as to be able to set axis optins ... - * will create Axis + TextDisp if not present ... - * - * @param axisType - * @param bCreateIfNecessary - if true, will create the axis if it doesn't exist - * @return Desired axis - */ - public Axis getAxis(int axisType, boolean bCreateIfNecessary) { - int lastTd = 0; - int lastAxis = 0; - Axis a = null; - TextDisp td = null; - int tdType = TextDisp.convertType(axisType); - for (int i = 0; i < chartArr.size() && td == null; i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == AXIS) { - lastAxis = i; - if (((Axis) b).getAxis() == axisType) { - a = ((Axis) b); - a.setAP(this); // ensure axis is linked to it's parent AxisParent 20090108 KSC: - //if (!bCreateIfNecessary) return a; - } - } else if (b.getOpcode() == TEXTDISP) { - lastTd = i; - if (tdType == ((TextDisp) b).getType()) { - td = (TextDisp) b; - if (bCreateIfNecessary) {// 20080723 KSC: added guard - but when is clearing td text necessary???? - td.setText(""); // clear out axis legend - } - } - } - } - if (a == null && bCreateIfNecessary) { - // if didn't find axis, then add axis + text disp ... - // first, add TD - td = (TextDisp) TextDisp.getPrototype(axisType, "", this.wkbook); - td.setParentChart(this.getParentChart()); - this.chartArr.add(lastTd + 1, td); - // next, add axis - a = (Axis) Axis.getPrototype(axisType); - a.setParentChart(this.getParentChart()); - this.chartArr.add(lastAxis + 1, a); - a.setAP(this); // ensure axis is linked to it's parent AxisParent 20090108 KSC: - } - if (a != null) { // associate this axis with the textdisp if any - a.setTd(td); - } - return a; - } - - /** - * returns the plot area background color - * - * @return plot area background color hex string - */ - public String getPlotAreaBgColor() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b instanceof Frame) { - return ((Frame) b).getBgColor(); - } - } - return null; //FormatHandle.COLOR_WHITE; - } - - /** - * sets the plot area background color - * - * @param bg color int - */ - public void setPlotAreaBgColor(int bg) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b instanceof Frame) { - ((Frame) b).setBgColor(bg); - break; - } - } - } - - /** - * adds a border around the plot area with the desired line width and line color - * - * @param lw - * @param lclr - */ - public void setPlotAreaBorder(int lw, int lclr) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b instanceof Frame) { - ((Frame) b).addBox(lw, lclr, -1); - break; - } - } - } - - - /** - * returns true if this is a secondary axis - * - * @return - */ - public boolean isSecondaryAxis() { - return (iax == 1); - } - - /** - * sets if this is a secondary axis - * - * @param b - */ - public void setIsSecondaryAxis(boolean b) { - if (b) - iax = 1; - else - iax = 0; - this.getData()[0] = (byte) iax; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/AxisParent.kt b/src/main/java/io/starter/formats/XLS/charts/AxisParent.kt new file mode 100644 index 0000000..ff9bb3a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/AxisParent.kt @@ -0,0 +1,370 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.BiffRec +import io.starter.toolkit.ByteTools + +/** + * **AxisParent: Axis Size and Location (0x1014)** + * + * + * This record specifies properties of an axis group and the beginning of a collection of records as defined by the chart sheet substream. + * + * + * + * + * The Axis parent record stores most of the actual chart information, what type of chart, x and y labels, etc. + * + * + * 4 iax 2 axis index (0= main, 1= secondary) This field MUST be set to zero when it is in the first AxisParent record in the chart sheet substream, + * This field MUST be set to 1 when it is in the second AxisParent record in the chart sheet substream. + * 16 unused (16 bytes): Undefined and MUST be ignored. + * + * + * + * + * this doesn't appear correct given ms doc + * 6 x 4 + * 10 y 4 + * 14 dx 4 len of x axis + * 18 dy 4 len of y axis + */ +class AxisParent : GenericChartObject(), ChartObject { + private var iax: Short = 0 + private var x = 0 + private var y = 0 + private var dx = 0 + private var dy = 0 + + /** + * default version, returns the 1sst chart format record + *

                      if there are more than 1 chart type in the chart + * there will be multiple chart format records + * + * @return + */ + protected val chartFormat: ChartFormat? + get() = getChartFormat(0, false) + + /** + * returns the plot area background color + * + * @return plot area background color hex string + */ + //FormatHandle.COLOR_WHITE; + val plotAreaBgColor: String? + get() { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b is Frame) { + return b.bgColor + } + } + return null + } + + + /** + * returns true if this is a secondary axis + * + * @return + */ + /** + * sets if this is a secondary axis + * + * @param b + */ + var isSecondaryAxis: Boolean + get() = iax.toInt() == 1 + set(b) { + if (b) + iax = 1 + else + iax = 0 + this.data[0] = iax.toByte() + } + + override fun init() { + super.init() + iax = ByteTools.readShort(this.data!![0].toInt(), this.data!![1].toInt()) + x = ByteTools.readInt(this.getBytesAt(2, 4)!!) + y = ByteTools.readInt(this.getBytesAt(6, 4)!!) + dx = ByteTools.readInt(this.getBytesAt(10, 4)!!) + dy = ByteTools.readInt(this.getBytesAt(14, 4)!!) + + } + + /** + * get the chart format collection + * + * @return + */ + protected fun getChartFormat(nChart: Int, addNew: Boolean): ChartFormat? { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.CHARTFORMAT) { + val cf = b as ChartFormat + if (cf.drawingOrder == nChart) + return cf + } + } + return if (addNew) createNewChart(nChart) else null + } + + /** + * create basic cf necessary to define multiple or overlay charts + * and add to this AxisParent + * + * @param nChart overlay chart number (>0 and <=9) + * @return new ChartFormat + */ + fun createNewChart(nChart: Int): ChartFormat? { + // muliple charts, create one + if (nChart > 0 && nChart <= 9) { + val cf = ChartFormat.prototype as ChartFormat? + cf!!.parentChart = this.parentChart + val b = Bar.prototype as Bar? + cf.chartArr.add(b) // add a dummy chart object - will be replaced later + val cfl = ChartFormatLink.prototype as ChartFormatLink? + cf.chartArr.add(cfl) + val sl = SeriesList() + sl.opcode = XLSConstants.SERIESLIST + sl.parentChart = this.parentChart + cf.chartArr.add(sl) + cf.drawingOrder = nChart + this.chartArr.add(cf) // add chartformat to chart array of axis parent + return cf + } + return null + } + + /** + * remove axis records for pie-type charts ... + */ + fun removeAxes() { + if (chartArr[1] is Axis) + chartArr.removeAt(1) // remove Axis + if (chartArr[1] is Axis) + chartArr.removeAt(1) // remove Axis + if (chartArr[1] is TextDisp) + chartArr.removeAt(1) // remove Text for Axis + if (chartArr[1] is TextDisp) + chartArr.removeAt(1) // remove Text for Axis + if (chartArr[1] is PlotArea) + chartArr.removeAt(1) // remove + if (chartArr[1] is Frame) + chartArr.removeAt(1) // remove Frame + // all should be left is pos + chartFormat + } + + /** + * remove the desired axis + associated records + * + * @param axisType + */ + fun removeAxis(axisType: Int) { + // Remove axis + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.AXIS) { + if ((b as Axis).axis.toInt() == axisType) { + chartArr.removeAt(i) + break + } + } + } + val tdType = TextDisp.convertType(axisType) + // Remove TextDisp assoc with Axis + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.TEXTDISP) { + val td = b as TextDisp + if (tdType == td.type) { + chartArr.removeAt(i) + break + } + } + } + } + + /** + * for those charts such as Radar, remove the + * axis frame and plot area records as are not necessary + */ + protected fun removePlotArea() { + var remove = false + var i = 0 + while (i < chartArr.size) { + val b = chartArr[i] + if (b.opcode == XLSConstants.AXIS) { + remove = true + } else if (b.opcode == XLSConstants.CHARTFORMAT) { + break + } else if (remove) { + chartArr.removeAt(i) + i-- + } + i++ + } + + } + + /** + * return XML of axis label and specific options for the desired axis + * (both the Axis record + it's "associated" TextDisp record are consulted) + * + * @param Axis int desired Axis + * @return String XML of axis label + specific options + * @see ObjectLink + */ // KSC: TODO: Get all axis ops ... + fun getAxisOptionsXML(axis: Int): String { + var bHasAxis = false + var bHasLabel = false + val sb = StringBuffer() + val tdType = TextDisp.convertType(axis) + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.AXIS) { + if ((b as Axis).axis.toInt() == axis) { + sb.append(b.optionsXML) + bHasAxis = true + } + } else if (b.opcode == XLSConstants.TEXTDISP) { + if ((b as TextDisp).type == tdType) { + sb.append(b.optionsXML) + bHasLabel = true + break + } + } + } + if (bHasAxis && !bHasLabel) + // must include axis even if no textdisp/label + sb.append(" Label=\"\"") + return sb.toString() + } + + /** + * return the desired axis according to AxisType + * Also finds and associates the axis rec with it's TextDisp + * so as to be able to set axis optins ... + * will create Axis + TextDisp if not present ... + * + * @param axisType + * @param bCreateIfNecessary - if true, will create the axis if it doesn't exist + * @return Desired axis + */ + @JvmOverloads + fun getAxis(axisType: Int, bCreateIfNecessary: Boolean = true): Axis? { + var lastTd = 0 + var lastAxis = 0 + var a: Axis? = null + var td: TextDisp? = null + val tdType = TextDisp.convertType(axisType) + var i = 0 + while (i < chartArr.size && td == null) { + val b = chartArr[i] + if (b.opcode == XLSConstants.AXIS) { + lastAxis = i + if ((b as Axis).axis.toInt() == axisType) { + a = b + a.setAP(this) // ensure axis is linked to it's parent AxisParent 20090108 KSC: + //if (!bCreateIfNecessary) return a; + } + } else if (b.opcode == XLSConstants.TEXTDISP) { + lastTd = i + if (tdType == (b as TextDisp).type) { + td = b + if (bCreateIfNecessary) {// 20080723 KSC: added guard - but when is clearing td text necessary???? + td.setText("") // clear out axis legend + } + } + } + i++ + } + if (a == null && bCreateIfNecessary) { + // if didn't find axis, then add axis + text disp ... + // first, add TD + td = TextDisp.getPrototype(axisType, "", this.wkbook) as TextDisp + td.parentChart = this.parentChart + this.chartArr.add(lastTd + 1, td) + // next, add axis + a = Axis.getPrototype(axisType) as Axis + a.parentChart = this.parentChart + this.chartArr.add(lastAxis + 1, a) + a.setAP(this) // ensure axis is linked to it's parent AxisParent 20090108 KSC: + } + if (a != null) { // associate this axis with the textdisp if any + a.td = td + } + return a + } + + /** + * sets the plot area background color + * + * @param bg color int + */ + fun setPlotAreaBgColor(bg: Int) { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b is Frame) { + b.setBgColor(bg) + break + } + } + } + + /** + * adds a border around the plot area with the desired line width and line color + * + * @param lw + * @param lclr + */ + fun setPlotAreaBorder(lw: Int, lclr: Int) { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b is Frame) { + b.addBox(lw, lclr, -1) + break + } + } + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -2247258217367570732L + } +} +/** + * return the desired axis according to AxisType + * (will create the axis if it doesn't exist) + * Also finds and associates the axis rec with it's TextDisp + * so as to be able to set axis optins ... + * will create Axis + TextDisp if not present ... + * + * @param axisType + * @return Desired axis + */ diff --git a/src/main/java/io/starter/formats/XLS/charts/Bar.java b/src/main/java/io/starter/formats/XLS/charts/Bar.java deleted file mode 100644 index 97c43a4..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Bar.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * Bar: Chart Group is a Bar or Column Chart Group (0x1017) - * NOTE: Bar is also base type for Pyramid, Cone and Cylinder charts; - * actual chart type is determined also by bar shape - * see ChartFormat.getChartType for more information - *

                      - *

                      - * 4 pcOverlap 2 Space between bars (default= 0) - * values: -100 to -1 Size of the separation between data points - * 0 No overlap. - * 1 to 100 Size of the overlap between data points - * 6 pcGap 2 Space between categories (%) (default=50%) - * An unsigned integer that specifies the width of the gap between the categories and the left and right edges of the plot area - * as a percentage of the data point width divided by 2. It also specifies the width of the gap between adjacent categories - * as a percentage of the data point width. MUST be less than or equal to 500. - * 8 grbit 2 - *

                      - * grbit: - *

                      - * 0 0 0x1 fTranspose 1= horizontal bars (=bar chart) 0= vertical bars (= column chart) - * 1 0x2 fStacked Stack the displayed values - * 2 0x4 f100 Each category is displayed as a percentage - * 3 0x8 fHasShadow 1= this bar has a shadow - */ -public class Bar extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8917510368688674273L; - private short grbit = 0; - protected boolean fStacked = false, f100 = false, fHasShadow = false; - protected short pcOverlap = 0; - protected short pcGap = 50; - - public void init() { - super.init(); - pcOverlap = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - // should be 50 default, but seems to be 150 ?????? - pcGap = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - // pcGap: 0x0096 specifies that the width of the gap between adjacent categories is 150% of the data point width. It also specifies that the width of the gap between the categories (3) and the left and right edges of the plot area is 75% of the data point width. - grbit = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - if ((grbit & 0x1) == 0x1) { - chartType = BARCHART; - } else { - chartType = COLCHART; - } - fStacked = ((grbit & 0x2) == 0x2); - f100 = ((grbit & 0x4) == 0x4); - fHasShadow = ((grbit & 0x8) == 0x8); - } - - // 20070716 KSC: get/set methods for format options - public boolean isStacked() { - return fStacked; - } - - public boolean is100Percent() { - return f100; - } - - public boolean hasShadow() { - return fHasShadow; - } - - public int getGap() { - return pcGap; - } - - public int getOverlap() { - return pcOverlap; - } - - /** - * sets this bar/col chart to have stacked series: series are drawn next to each other along the value axis - * - * @param bIsClustered - */ - public void setIsStacked(boolean bIsStacked) { - fStacked = bIsStacked; - grbit = ByteTools.updateGrBit(grbit, fStacked, 1); - if (bIsStacked) { - pcOverlap = -100; - pcGap = 150; - } - updateRecord(); - } - - public void setIs100Percent(boolean bOn) { - f100 = bOn; - grbit = ByteTools.updateGrBit(grbit, f100, 2); - if (bOn) { - pcOverlap = -100; - pcGap = 150; - } - updateRecord(); - } - - public void setHasShadow(boolean bHasShadow) { - fHasShadow = bHasShadow; - grbit = ByteTools.updateGrBit(grbit, fHasShadow, 3); - updateRecord(); - } - - /** - * sets this bar/col chart to have clustered series: series are drawn next to each other along the category axis - * - * @param bIsClustered - */ - public void setIsClustered() { - setIsStacked(false); - setIs100Percent(false); - } - - /** - * sets the Space between bars (default= 0) - * - * @param overlap - */ - public void setpcOverlap(int overlap) { - pcOverlap = (short) overlap; - updateRecord(); - } - - /** - * sets the Space between categories (%) (default=50%) - * - * @param gap - */ - public void setpcGap(int gap) { - pcGap = (short) gap; - updateRecord(); - } - - public void setAsBarChart() { - grbit = ByteTools.updateGrBit(grbit, true, 0); // set 0'th bit - chartType = ChartConstants.BARCHART; - this.updateRecord(); - } - - public void setAsColumnChart() { - grbit = ByteTools.updateGrBit(grbit, false, 0); // clear 0'th bit - chartType = ChartConstants.COLCHART; - this.updateRecord(); - } - - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(pcOverlap); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - b = ByteTools.shortToLEBytes(pcGap); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - b = ByteTools.shortToLEBytes(grbit); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - } - - public static XLSRecord getPrototype() { - Bar b = new Bar(); - b.setOpcode(BAR); - b.setData(b.PROTOTYPE_BYTES); - b.init(); - b.setAsColumnChart(); - return b; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, -106, 0, 0, 0}; - - /** - * Set specific options - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("Stacked")) { - setIsStacked(true); - bHandled = true; - } else if (op.equalsIgnoreCase("PercentageDisplay")) { - setIs100Percent(true); - bHandled = true; - } else if (op.equalsIgnoreCase("Shadow")) { - setHasShadow(true); - bHandled = true; - } else if (op.equalsIgnoreCase("Overlap")) { - setpcOverlap(Integer.parseInt(val)); - bHandled = true; - } else if (op.equalsIgnoreCase("Gap")) { - setpcGap(Integer.parseInt(val)); - bHandled = true; - } - return bHandled; - } - - /** - * look up bar-specific chart options such as "Gap" or "Overlap" setting - */ - public String getChartOption(String op) { - if (op.equals("Gap")) { // Bar - return String.valueOf(this.getGap()); - } else if (op.equals("Overlap")) { // Bar -// return String.valueOf(Math.abs(this.getOverlap())); // KSC: TESTING: OOXML apparently needs +100 pcOverlap NOT -100 ... WHY and TRUE FOR ALL CASES????? - return String.valueOf(this.getOverlap()); // KSC: TESTING: OOXML apparently needs +100 pcOverlap NOT -100 ... WHY and TRUE FOR ALL CASES????? - } else - return super.getChartOption(op); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Bar.kt b/src/main/java/io/starter/formats/XLS/charts/Bar.kt new file mode 100644 index 0000000..f78b6aa --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Bar.kt @@ -0,0 +1,236 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * Bar: Chart Group is a Bar or Column Chart Group (0x1017) + * NOTE: Bar is also base type for Pyramid, Cone and Cylinder charts; + * actual chart type is determined also by bar shape + * see ChartFormat.getChartType for more information + * + * + * + * + * 4 pcOverlap 2 Space between bars (default= 0) + * values: -100 to -1 Size of the separation between data points + * 0 No overlap. + * 1 to 100 Size of the overlap between data points + * 6 pcGap 2 Space between categories (%) (default=50%) + * An unsigned integer that specifies the width of the gap between the categories and the left and right edges of the plot area + * as a percentage of the data point width divided by 2. It also specifies the width of the gap between adjacent categories + * as a percentage of the data point width. MUST be less than or equal to 500. + * 8 grbit 2 + * + * + * grbit: + * + * + * 0 0 0x1 fTranspose 1= horizontal bars (=bar chart) 0= vertical bars (= column chart) + * 1 0x2 fStacked Stack the displayed values + * 2 0x4 f100 Each category is displayed as a percentage + * 3 0x8 fHasShadow 1= this bar has a shadow + */ +class Bar : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + var fStacked = false + var f100 = false + var fHasShadow = false + var pcOverlap: Short = 0 + var pcGap: Short = 50 + + // 20070716 KSC: get/set methods for format options + /** + * sets this bar/col chart to have stacked series: series are drawn next to each other along the value axis + * + * @param bIsClustered + */ + override var isStacked: Boolean + get() = fStacked + set(bIsStacked) { + fStacked = bIsStacked + grbit = ByteTools.updateGrBit(grbit, fStacked, 1) + if (bIsStacked) { + pcOverlap = -100 + pcGap = 150 + } + updateRecord() + } + + override var is100Percent: Boolean + get() = f100 + set(bOn) { + f100 = bOn + grbit = ByteTools.updateGrBit(grbit, f100, 2) + if (bOn) { + pcOverlap = -100 + pcGap = 150 + } + updateRecord() + } + + val gap: Int + get() = pcGap.toInt() + + val overlap: Int + get() = pcOverlap.toInt() + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, -106, 0, 0, 0) + + override fun init() { + super.init() + pcOverlap = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + // should be 50 default, but seems to be 150 ?????? + pcGap = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + // pcGap: 0x0096 specifies that the width of the gap between adjacent categories is 150% of the data point width. It also specifies that the width of the gap between the categories (3) and the left and right edges of the plot area is 75% of the data point width. + grbit = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + if (grbit and 0x1 == 0x1) { + chartType = ChartConstants.BARCHART + } else { + chartType = ChartConstants.COLCHART + } + fStacked = grbit and 0x2 == 0x2 + f100 = grbit and 0x4 == 0x4 + fHasShadow = grbit and 0x8 == 0x8 + } + + override fun hasShadow(): Boolean { + return fHasShadow + } + + fun setHasShadow(bHasShadow: Boolean) { + fHasShadow = bHasShadow + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 3) + updateRecord() + } + + /** + * sets this bar/col chart to have clustered series: series are drawn next to each other along the category axis + * + * @param bIsClustered + */ + fun setIsClustered() { + isStacked = false + is100Percent = false + } + + /** + * sets the Space between bars (default= 0) + * + * @param overlap + */ + fun setpcOverlap(overlap: Int) { + pcOverlap = overlap.toShort() + updateRecord() + } + + /** + * sets the Space between categories (%) (default=50%) + * + * @param gap + */ + fun setpcGap(gap: Int) { + pcGap = gap.toShort() + updateRecord() + } + + fun setAsBarChart() { + grbit = ByteTools.updateGrBit(grbit, true, 0) // set 0'th bit + chartType = ChartConstants.BARCHART + this.updateRecord() + } + + fun setAsColumnChart() { + grbit = ByteTools.updateGrBit(grbit, false, 0) // clear 0'th bit + chartType = ChartConstants.COLCHART + this.updateRecord() + } + + private fun updateRecord() { + var b = ByteTools.shortToLEBytes(pcOverlap) + this.data[0] = b[0] + this.data[1] = b[1] + b = ByteTools.shortToLEBytes(pcGap) + this.data[2] = b[0] + this.data[3] = b[1] + b = ByteTools.shortToLEBytes(grbit) + this.data[4] = b[0] + this.data[5] = b[1] + } + + /** + * Set specific options + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("Stacked", ignoreCase = true)) { + isStacked = true + bHandled = true + } else if (op.equals("PercentageDisplay", ignoreCase = true)) { + is100Percent = true + bHandled = true + } else if (op.equals("Shadow", ignoreCase = true)) { + setHasShadow(true) + bHandled = true + } else if (op.equals("Overlap", ignoreCase = true)) { + setpcOverlap(Integer.parseInt(`val`)) + bHandled = true + } else if (op.equals("Gap", ignoreCase = true)) { + setpcGap(Integer.parseInt(`val`)) + bHandled = true + } + return bHandled + } + + /** + * look up bar-specific chart options such as "Gap" or "Overlap" setting + */ + override fun getChartOption(op: String): String? { + return if (op == "Gap") { // Bar + this.gap.toString() + } else if (op == "Overlap") { // Bar + // return String.valueOf(Math.abs(this.getOverlap())); // KSC: TESTING: OOXML apparently needs +100 pcOverlap NOT -100 ... WHY and TRUE FOR ALL CASES????? + this.overlap.toString() // KSC: TESTING: OOXML apparently needs +100 pcOverlap NOT -100 ... WHY and TRUE FOR ALL CASES????? + } else + super.getChartOption(op) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8917510368688674273L + + val prototype: XLSRecord? + get() { + val b = Bar() + b.opcode = XLSConstants.BAR + b.data = b.PROTOTYPE_BYTES + b.init() + b.setAsColumnChart() + return b + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.java b/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.java deleted file mode 100644 index fd2bd7c..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class Bar3DChart extends BarChart { - - public Bar3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - bar = (Bar) charttype; - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - - if (!this.getChartOption("Gap").equals("150")) - cooxml.append(""); // default= 0 - int gapdepth = this.getGapDepth(); - if (gapdepth != 0) - cooxml.append(""); - cooxml.append(""); - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - if (this.getParentChart().getAxes().hasAxis(ZAXIS)) { - cooxml.append(""); - cooxml.append("\r\n"); - } else {// KSC: appears to be necessary but very unclear as to why - cooxml.append(""); - cooxml.append("\r\n"); - } - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.kt b/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.kt new file mode 100644 index 0000000..eea430a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Bar3DChart.kt @@ -0,0 +1,92 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +open class Bar3DChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : BarChart(charttype, cf, wb) { + + init { + bar = charttype as Bar + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + + if (this.getChartOption("Gap") != "150") + cooxml.append("") // default= 0 + val gapdepth = this.gapDepth + if (gapdepth != 0) + cooxml.append("") + cooxml.append("") + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + if (this.parentChart!!.axes!!.hasAxis(ChartConstants.ZAXIS)) { + cooxml.append("") + cooxml.append("\r\n") + } else {// KSC: appears to be necessary but very unclear as to why + cooxml.append("") + cooxml.append("\r\n") + } + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/BarChart.java b/src/main/java/io/starter/formats/XLS/charts/BarChart.java deleted file mode 100644 index e9825a6..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/BarChart.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.ChartSeriesHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; - -public class BarChart extends ChartType { - protected Bar bar = null; - - public BarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - bar = (Bar) charttype; - defaultShape = SHAPEDEFAULT; - } - - /** - * set the default shape for all bars in this chart - * - * @param shape - */ - public void setDefaultShape(int shape) { - defaultShape = shape; - } - - /** - * return the default shape for all bars in this chart - * - * @return - */ - public int getDefaultShape() { - return defaultShape; - } - - /** - * @return truth of "Chart is Clustered" (Bar/Col only) - */ - public boolean isClustered() { - return (/*cf.isClustered() || */(!isStacked() && !is100PercentStacked())); - } - - /** - * returns the bar shape for a column or bar type chart - * can be one of: - *
                      SHAPECOLUMN default - *
                      SHAPECONEd - *
                      SHAPECONETOMAX - *
                      SHAPECYLINDER - *
                      SHAPEPYRAMID - *
                      SHAPEPYRAMIDTOMAX - * - * @return int bar shape - */ - public int getBarShape() { - return cf.getBarShape(); - } - - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (bar.fStacked) - sb.append(" Stacked=\"true\""); - if (bar.f100) - sb.append(" PercentageDisplay=\"true\""); - if (bar.fHasShadow) - sb.append(" Shadow=\"true\""); - if (bar.pcOverlap != 0) - sb.append(" Overlap=\"" + bar.pcOverlap + "\""); - if (bar.pcGap != 50 && bar.pcGap != 150) - sb.append(" Gap=\"" + bar.pcGap + "\""); - return sb.toString(); - } - - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON = new JSONObject(); - // Dojo: Regular, Stacked, Clustered -/* String dojoType; - int shape=ChartConstants.SHAPECOLUMN; - //controls Data Legends, % Distance of sections, Line Format, Area Format, Bar Shapes ... - ChartFormat cf= this.getParentChart().getChartFormat(); - DataFormat df= cf.getDataFormatRec(false); - - if (df!=null) - shape= df.getShape(); - if (shape==ChartConstants.SHAPECONE || - shape==ChartConstants.SHAPECYLINDER || - shape==ChartConstants.SHAPEPYRAMID) - ; // TODO: SOMETHING!!! Interpret Shape for Pyramid, etc. charts - if (this.chartType==ChartConstants.BAR) { - if (!cf.isThreeD(ChartConstants.BAR)) { - dojoType="ClusteredBars"; // clustered dojo is actually Excel normal - typeJSON.put("gap", 5); //default val that looks pretty good ... - } - else if (cf.isClustered()) - dojoType="Bars"; // = - else - dojoType="StackedBars"; - } else { // Column - if (!cf.isThreeD(ChartConstants.COL)) { - dojoType="ClusteredColumns"; // clustered dojo is actually Excel normal col - typeJSON.put("gap", 5); //default val that looks pretty good ... - } - else if (cf.isClustered()) - dojoType="Columns"; - else - dojoType="StackedColumns"; - } - typeJSON.put("type", dojoType); -*/ - return typeJSON; - } - - /** - * replaces generic getJSON with Bar/Column specifics (necessary for stacked-type charts) - */ - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) - throws JSONException { - JSONObject chartObjectJSON = new JSONObject(); - -/* // 20080428 KSC: get stacked state - boolean bIsStacked= (cf.isThreeD() && !cf.isClustered()); - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // Deal with Series - double yMax= 0.0, yMin= 0.0; - int nSeries= 0; - // 20080428 KSC: stacked charts max is sum of all points - double[] ySum= null; - if (bIsStacked) { - // preprocess series to get # series values - for (int i= 0; i < series.length; i++) { - CellRange cr = new CellRange(series[i].getSeriesRange(), wbh, true); - nSeries= Math.max(nSeries, cr.getCells().length); - } - ySum= new double[nSeries]; - } - JSONArray seriesJSON= new JSONArray(); - JSONArray seriesCOLORS= new JSONArray(); - try { - for (int i= 0; i < series.length; i++) { - try{ - JSONArray seriesvals= CellRange.getJSON(series[i].getSeriesRange(), wbh); - // must trap min and max for axis tick and units - nSeries= Math.max(nSeries, seriesvals.length()); - for (int j= 0; j < seriesvals.length(); j++) { - try { - if (!bIsStacked) - yMax= Math.max(yMax, seriesvals.getDouble(j)); - else - ySum[j]+= seriesvals.getDouble(j); - yMin= Math.min(yMin, seriesvals.getDouble(j)); - } catch (NumberFormatException n) {;} - } - seriesJSON.put(seriesvals); - seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i].getSeriesColor()]); - }catch(Exception x){ - ; - } - } - if (bIsStacked) { - for (int i= 0; i < nSeries; i++) - yMax= Math.max(yMax, ySum[i]); - } - chartObjectJSON.put("Series", seriesJSON); - chartObjectJSON.put("SeriesFills", seriesCOLORS); - } catch (JSONException je) { - // TODO: Log error - } - minMax[0]= new Double(yMin); - minMax[1]= new Double(yMax); - minMax[2]= new Double(nSeries); -*/ - return chartObjectJSON; - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double x = chartMetrics.get("x"); - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - if (series.size() == 0) { - Logger.logErr("Bar.getSVG: error in series"); - return ""; - } - StringBuffer svg = new StringBuffer(); - int shape = cf.getBarShape(); - if (shape == ChartConstants.SHAPECONE || - shape == ChartConstants.SHAPECYLINDER || - shape == ChartConstants.SHAPEPYRAMID) - ; // TODO: SOMETHING!!! Interpret Shape for Pyramid, etc. charts - - - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - int n = series.size(); - svg.append("\r\n"); - - double barw = 0, yfactor = 0; // - barw = h / ((categories.length + 1.0) * (n + 1)); // bar width= height/total number of bars+separators - if (max != 0) - yfactor = w / max; // w/yMax= scale unit - - int rf = 1; //(!yAxisReversed?1:-1); - double y0 = h + y - barw * 0.5; //start at bottom and work up (unless reversed) - // for each series - for (int i = 0; i < n; i++) { // each series group i.e. each bar with a different color - svg.append("\r\n"); - double[] curseries = (double[]) series.get(i); - String[] curranges = (String[]) s.getSeriesRanges().get(i); - for (int j = 0; j < curseries.length; j++) { // each category (group of series) -// double y= y0 - barw*(i+1)*rf - (j*n*barw*1.5)*rf; // y goes from 1 series to next, corresponding to bar/column color - double yy = y0 - (barw * (n + 1) * 1.1 * (j)) /* each series (bar group) */ - - (barw * (i + 1)) /* each bar in the series group */ - - (barw * 1.5) * rf /* start */; - double ww = yfactor * curseries[j]; // width of bar -- measure of series - svg.append(""); - String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); - if (l != null) { - svg.append("" + l + "\r\n"); - } - } - svg.append("\r\n"); // each series group - } - svg.append("\r\n"); - return svg.toString(); - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - if (!this.getChartOption("Gap").equals("150")) - cooxml.append(""); // default= 0 - if (!this.getChartOption("Overlap").equals("0")) - cooxml.append(""); // default= 0 - // Series Lines - ChartLine cl = cf.getChartLinesRec(); - if (cl != null) - cooxml.append(cl.getOOXML()); - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/BarChart.kt b/src/main/java/io/starter/formats/XLS/charts/BarChart.kt new file mode 100644 index 0000000..4aa9ba0 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/BarChart.kt @@ -0,0 +1,323 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.ChartSeriesHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger +import org.json.JSONException +import org.json.JSONObject + +import java.util.ArrayList +import java.util.HashMap + +open class BarChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + protected var bar: Bar? = null + + /** + * return the default shape for all bars in this chart + * + * @return + */ + /** + * set the default shape for all bars in this chart + * + * @param shape + */ + var defaultShape: Int + get() = defaultShape + set(shape) { + defaultShape = shape + } + + /** + * @return truth of "Chart is Clustered" (Bar/Col only) + */ + override/*cf.isClustered() || */ val isClustered: Boolean + get() = !isStacked && !is100PercentStacked + + /** + * returns the bar shape for a column or bar type chart + * can be one of: + *

                      SHAPECOLUMN default + *

                      SHAPECONEd + *

                      SHAPECONETOMAX + *

                      SHAPECYLINDER + *

                      SHAPEPYRAMID + *

                      SHAPEPYRAMIDTOMAX + * + * @return int bar shape + */ + override val barShape: Int + get() = cf!!.barShape + + + /** + * @return String XML representation of this chart-type's options + */ + val optionsXML: String + get() { + val sb = StringBuffer() + if (bar!!.fStacked) + sb.append(" Stacked=\"true\"") + if (bar!!.f100) + sb.append(" PercentageDisplay=\"true\"") + if (bar!!.fHasShadow) + sb.append(" Shadow=\"true\"") + if (bar!!.pcOverlap.toInt() != 0) + sb.append(" Overlap=\"" + bar!!.pcOverlap + "\"") + if (bar!!.pcGap.toInt() != 50 && bar!!.pcGap.toInt() != 150) + sb.append(" Gap=\"" + bar!!.pcGap + "\"") + return sb.toString() + } + + override// Dojo: Regular, Stacked, Clustered + /* String dojoType; + int shape=ChartConstants.SHAPECOLUMN; + //controls Data Legends, % Distance of sections, Line Format, Area Format, Bar Shapes ... + ChartFormat cf= this.getParentChart().getChartFormat(); + DataFormat df= cf.getDataFormatRec(false); + + if (df!=null) + shape= df.getShape(); + if (shape==ChartConstants.SHAPECONE || + shape==ChartConstants.SHAPECYLINDER || + shape==ChartConstants.SHAPEPYRAMID) + ; // TODO: SOMETHING!!! Interpret Shape for Pyramid, etc. charts + if (this.chartType==ChartConstants.BAR) { + if (!cf.isThreeD(ChartConstants.BAR)) { + dojoType="ClusteredBars"; // clustered dojo is actually Excel normal + typeJSON.put("gap", 5); //default val that looks pretty good ... + } + else if (cf.isClustered()) + dojoType="Bars"; // = + else + dojoType="StackedBars"; + } else { // Column + if (!cf.isThreeD(ChartConstants.COL)) { + dojoType="ClusteredColumns"; // clustered dojo is actually Excel normal col + typeJSON.put("gap", 5); //default val that looks pretty good ... + } + else if (cf.isClustered()) + dojoType="Columns"; + else + dojoType="StackedColumns"; + } + typeJSON.put("type", dojoType); +*/ val typeJSON: JSONObject + @Throws(JSONException::class) + get() = JSONObject() + + init { + bar = charttype as Bar + defaultShape = ChartConstants.SHAPEDEFAULT + } + + /** + * replaces generic getJSON with Bar/Column specifics (necessary for stacked-type charts) + */ + @Throws(JSONException::class) + fun getJSON(series: Array, wbh: WorkBookHandle, minMax: Array): JSONObject { + + /* // 20080428 KSC: get stacked state + boolean bIsStacked= (cf.isThreeD() && !cf.isClustered()); + // Type JSON + chartObjectJSON.put("type", this.getTypeJSON()); + + // Deal with Series + double yMax= 0.0, yMin= 0.0; + int nSeries= 0; + // 20080428 KSC: stacked charts max is sum of all points + double[] ySum= null; + if (bIsStacked) { + // preprocess series to get # series values + for (int i= 0; i < series.length; i++) { + CellRange cr = new CellRange(series[i].getSeriesRange(), wbh, true); + nSeries= Math.max(nSeries, cr.getCells().length); + } + ySum= new double[nSeries]; + } + JSONArray seriesJSON= new JSONArray(); + JSONArray seriesCOLORS= new JSONArray(); + try { + for (int i= 0; i < series.length; i++) { + try{ + JSONArray seriesvals= CellRange.getJSON(series[i].getSeriesRange(), wbh); + // must trap min and max for axis tick and units + nSeries= Math.max(nSeries, seriesvals.length()); + for (int j= 0; j < seriesvals.length(); j++) { + try { + if (!bIsStacked) + yMax= Math.max(yMax, seriesvals.getDouble(j)); + else + ySum[j]+= seriesvals.getDouble(j); + yMin= Math.min(yMin, seriesvals.getDouble(j)); + } catch (NumberFormatException n) {;} + } + seriesJSON.put(seriesvals); + seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i].getSeriesColor()]); + }catch(Exception x){ + ; + } + } + if (bIsStacked) { + for (int i= 0; i < nSeries; i++) + yMax= Math.max(yMax, ySum[i]); + } + chartObjectJSON.put("Series", seriesJSON); + chartObjectJSON.put("SeriesFills", seriesCOLORS); + } catch (JSONException je) { + // TODO: Log error + } + minMax[0]= new Double(yMin); + minMax[1]= new Double(yMax); + minMax[2]= new Double(nSeries); +*/ + return JSONObject() + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val x = chartMetrics["x"] + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + if (series!!.size == 0) { + Logger.logErr("Bar.getSVG: error in series") + return "" + } + val svg = StringBuffer() + val shape = cf!!.barShape + if (shape == ChartConstants.SHAPECONE || + shape == ChartConstants.SHAPECYLINDER || + shape == ChartConstants.SHAPEPYRAMID) + ; // TODO: SOMETHING!!! Interpret Shape for Pyramid, etc. charts + + + val dls = dataLabelInts // get array of data labels (can be specific per series ...) + val n = series.size + svg.append("\r\n") + + var barw = 0.0 + var yfactor = 0.0 // + barw = h / ((categories!!.size + 1.0) * (n + 1)) // bar width= height/total number of bars+separators + if (max != 0.0) + yfactor = w / max // w/yMax= scale unit + + val rf = 1 //(!yAxisReversed?1:-1); + val y0 = h + y - barw * 0.5 //start at bottom and work up (unless reversed) + // for each series + for (i in 0 until n) { // each series group i.e. each bar with a different color + svg.append("\r\n") + val curseries = series[i] as DoubleArray + val curranges = s.seriesRanges!![i] as Array + for (j in curseries.indices) { // each category (group of series) + // double y= y0 - barw*(i+1)*rf - (j*n*barw*1.5)*rf; // y goes from 1 series to next, corresponding to bar/column color + val yy = (y0 - barw * (n + 1).toDouble() * 1.1 * j.toDouble() /* each series (bar group) */ + - barw * (i + 1) /* each bar in the series group */ + - barw * 1.5 * rf) /* start */ + val ww = yfactor * curseries[j] // width of bar -- measure of series + svg.append("") + val l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0.0, i, legends, categories[j].toString()) + if (l != null) { + svg.append("" + l + "\r\n") + } + } + svg.append("\r\n") // each series group + } + svg.append("\r\n") + return svg.toString() + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + if (this.getChartOption("Gap") != "150") + cooxml.append("") // default= 0 + if (this.getChartOption("Overlap") != "0") + cooxml.append("") // default= 0 + // Series Lines + val cl = cf!!.chartLinesRec + if (cl != null) + cooxml.append(cl.ooxml) + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Begin.java b/src/main/java/io/starter/formats/XLS/charts/Begin.kt similarity index 62% rename from src/main/java/io/starter/formats/XLS/charts/Begin.java rename to src/main/java/io/starter/formats/XLS/charts/Begin.kt index 1a55522..6f36444 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Begin.java +++ b/src/main/java/io/starter/formats/XLS/charts/Begin.kt @@ -21,33 +21,38 @@ * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * Begin: Begin identifier (0x1033) - *

                      + * **Begin: Begin identifier (0x1033)** + * + * * Begin is an identifier record for the chart record type. There is no data to a begin record, * and every begin record must have a corrosponding end record */ -public class Begin extends GenericChartObject implements ChartObject { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2732787714371128511L; +class Begin : GenericChartObject(), ChartObject { - public void init() { - super.init(); + override fun init() { + super.init() } - protected static XLSRecord getPrototype() { - Begin bl = new Begin(); - bl.setOpcode(BEGIN); - bl.setData(new byte[]{}); - return bl; + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -2732787714371128511L + + val prototype: XLSRecord? + get() { + val bl = Begin() + bl.opcode = XLSConstants.BEGIN + bl.data = byteArrayOf() + return bl + } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Boppop.java b/src/main/java/io/starter/formats/XLS/charts/Boppop.kt similarity index 65% rename from src/main/java/io/starter/formats/XLS/charts/Boppop.java rename to src/main/java/io/starter/formats/XLS/charts/Boppop.kt index cf251ef..f442332 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Boppop.java +++ b/src/main/java/io/starter/formats/XLS/charts/Boppop.kt @@ -21,18 +21,23 @@ * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools /** - * Boppop: Bar of Pie/Pie of Pie chart options(0x1061) - *

                      - *

                      - *

                      - *

                      - *

                      + * **Boppop: Bar of Pie/Pie of Pie chart options(0x1061)** + * + * + * + * + * + * + * + * + * + * * pst (1 byte): An unsigned integer that specifies whether this chart group is a bar of pie chart group or a pie of pie chart group. MUST be a value from the following table: * 1= pie of pie 2= bar of pie * fAutoSplit (1 byte): A Boolean that specifies whether the split point of the chart group is determined automatically. @@ -40,133 +45,103 @@ * and inserted into the secondary bar/pie automatically. * split (2 bytes): An unsigned integer that specifies what determines the split between the primary pie and the secondary bar/pie. * MUST be ignored if fAutoSplit is set to 1. MUST be a value from the following table: - *

                      + * + * * Value Type of split Meaning * 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. * 0x0001 Value The data is split based on a threshold value as specified by numSplitValue. * 0x0002 Percent The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. * 0x0003 Custom The data is split as arranged by the user. Custom split is specified in a following BopPopCustom record. - *

                      + * + * * iSplitPos (2 bytes): A signed integer that specifies how many data points are contained in the secondary bar/pie. * Data points are contained in the secondary bar/pie starting from the end of the series. * For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. * MUST be a value greater than or equal to 0 and less than or equal to 32000. * If the value is more than the number of data points in the series, the entire series will be in the secondary bar/pie, except for the first data point. * If split is not set to 0x0000 or fAutoSplit is set to 1, this value MUST be ignored. - *

                      + * + * * pcSplitPercent (2 bytes): A signed integer that specifies the percentage below which each data point is contained in the secondary bar/pie as opposed to the primary pie. * The percentage value of a data point is calculated using the following formula: * (value of the data point x 100) / sum of all data points in the series * If split is not set to 0x0002 or if fAutoSplit is set to 1, this value MUST be ignored - *

                      + * + * * pcPie2Size (2 bytes): A signed integer that specifies the size of the secondary bar/pie as a percentage of the size of the primary pie. * MUST be a value greater than or equal to 5 and less than or equal to 200. - *

                      + * + * * pcGap (2 bytes): A signed integer that specifies the distance between the primary pie and the secondary bar/pie. * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. * MUST be a value greater than or equal to 0 and less than or equal to 500, * where 0 is 0% of the average width of the primary pie and the secondary bar/pie, and 500 is 250% of the average width of the primary pie and the secondary bar/pie. - *

                      + * + * * numSplitValue (8 bytes): An Xnum value that specifies the split when the split field is set to 0x0001 * . The value of this field specifies the threshold that selects which data points of the primary pie move to the secondary bar/pie. * The secondary bar/pie contains any data points with a value less than the value of this field. If split is not set to 0x0001 or if fAutoSplit is set to 1, this value MUST be ignored. - *

                      + * + * * A - fHasShadow (1 bit): A bit that specifies whether one or more data points in the chart group have shadows. - *

                      + * + * * reserved (15 bits): MUST be zero, and MUST be ignored. */ -public class Boppop extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8071801452993935943L; - boolean fAutoSplit, fHasShadow; - short split, iSplitPos, pcSplitPercent, pcPieSize, pcGap; - float numSplitValue; - byte pst; - - public void init() { - super.init(); - byte[] data = this.getData(); - pst = data[0]; - fAutoSplit = (data[1] == 1); - split = ByteTools.readShort(data[2], data[3]); - iSplitPos = ByteTools.readShort(data[4], data[5]); - pcSplitPercent = ByteTools.readShort(data[6], data[7]); - pcPieSize = ByteTools.readShort(data[8], data[9]); - pcGap = ByteTools.readShort(data[10], data[11]); - numSplitValue = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(12, 8)); - fHasShadow = (data[20] & 1) == 1; - chartType = OFPIECHART; - } +class Boppop : GenericChartObject(), ChartObject { + internal var fAutoSplit: Boolean = false + internal var fHasShadow: Boolean = false + internal var split: Short = 0 + internal var iSplitPos: Short = 0 + internal var pcSplitPercent: Short = 0 + internal var pcPieSize: Short = 0 + internal var pcGap: Short = 0 + internal var numSplitValue: Float = 0.toFloat() + internal var pst: Byte = 0 - private byte[] PROTOTYPE_BYTES = new byte[]{1, 1, 0, 0, 0, 0, 0, 0, 75, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - - public static XLSRecord getPrototype() { - Boppop b = new Boppop(); - b.setOpcode(BOPPOP); - b.setData(b.PROTOTYPE_BYTES); - b.init(); - return b; - } + private val PROTOTYPE_BYTES = byteArrayOf(1, 1, 0, 0, 0, 0, 0, 0, 75, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) /** * returns true of pie of pie, false if bar of pie * * @return */ - public boolean isPieOfPie() { - return (pst == 1); - } - - public void setIsPieOfPie(boolean b) { - if (b) - pst = 1; - else - pst = 2; - this.getData()[0] = pst; - } - - /** - * specifies the distance between the primary pie and the secondary bar/pie. - * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. - * - * @param g - */ - public void setpcGap(int g) { - pcGap = (short) g; - byte[] b = ByteTools.shortToLEBytes(pcGap); - this.getData()[10] = b[0]; - this.getData()[11] = b[1]; - } + var isPieOfPie: Boolean + get() = pst.toInt() == 1 + set(b) { + if (b) + pst = 1 + else + pst = 2 + this.data[0] = pst + } /** - * returns the distance between the primary pie and the secondary bar/pie. - * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. + * returns the size of the secondary bar/pie as a percentage of the size of the primary pie. */ - public int getpcGap() { - return pcGap; - } - /** * specifies the size of the secondary bar/pie as a percentage of the size of the primary pie. * * @param s */ - public void setSecondPieSize(int s) { - pcPieSize = (short) s; - byte[] b = ByteTools.shortToLEBytes(pcPieSize); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; - } + var secondPieSize: Int + get() = pcPieSize.toInt() + set(s) { + pcPieSize = s.toShort() + val b = ByteTools.shortToLEBytes(pcPieSize) + this.data[8] = b[0] + this.data[9] = b[1] + } /** - * returns the size of the secondary bar/pie as a percentage of the size of the primary pie. + * returns split type, or -1 if autosplit + * 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. + * 0x0001 Value The data is split based on a threshold value as specified by numSplitValue. + * 0x0002 Percent The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. + * 0x0003 Custom The data is split as arranged by the user. Custom split is specified in a following BopPopCustom record. + * + * @return */ - public int getSecondPieSize() { - return pcPieSize; - } - /** * specifies what determines the split between the primary pie and the secondary bar/pie. * 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. @@ -176,106 +151,142 @@ public int getSecondPieSize() { * * @param t */ - public void setSplitType(int t) { - fAutoSplit = false; - this.getData()[1] = 0; - split = (short) t; - byte[] b = ByteTools.shortToLEBytes(split); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - } + var splitType: Int + get() = if (fAutoSplit) -1 else split.toInt() + set(t) { + fAutoSplit = false + this.data[1] = 0 + split = t.toShort() + val b = ByteTools.shortToLEBytes(split) + this.data[2] = b[0] + this.data[3] = b[1] + } + + // default + val splitTypeOOXML: String + get() { + if (fAutoSplit) + return "auto" + when (split) { + 0 -> return "pos" + 1 -> return "val" + 2 -> return "percent" + 3 -> return "cust" + } + return "auto" + } /** - * specifies what determines the split between the primary pie and the secondary bar/pie via OOXML string value: - * auto -- split point of the chart group is determined automatically - * pos -- The data is split based on the position of the data point in the series as specified by iSplitPos. - * val -- The data is split based on a threshold value as specified by numSplitValue. - * percent --The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. - * cust -- The data is split as arranged by the user. + * returns how many data points are contained in the secondary bar/pie. + * Data points are contained in the secondary bar/pie starting from the end of the series. + * For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. + */ + /** + * specifies how many data points are contained in the secondary bar/pie. + * Data points are contained in the secondary bar/pie starting from the end of the series. + * For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. * - * @param s + * @param sp */ - public void setSplitType(String s) { - if (s == null) { - fAutoSplit = true; - this.getData()[1] = 0; - return; + var splitPos: Int + get() = iSplitPos.toInt() + set(sp) { + iSplitPos = sp.toShort() + val b = ByteTools.shortToLEBytes(iSplitPos) + this.data[4] = b[0] + this.data[5] = b[1] } - fAutoSplit = false; - this.getData()[1] = 0; - if (s.equals("pos")) - split = 0; - else if (s.equals("val")) - split = 1; - else if (s.equals("percent")) - split = 2; - else if (s.equals("cust")) - split = 3; - byte[] b = ByteTools.shortToLEBytes(split); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; + + override fun init() { + super.init() + val data = this.data + pst = data!![0] + fAutoSplit = data[1].toInt() == 1 + split = ByteTools.readShort(data[2].toInt(), data[3].toInt()) + iSplitPos = ByteTools.readShort(data[4].toInt(), data[5].toInt()) + pcSplitPercent = ByteTools.readShort(data[6].toInt(), data[7].toInt()) + pcPieSize = ByteTools.readShort(data[8].toInt(), data[9].toInt()) + pcGap = ByteTools.readShort(data[10].toInt(), data[11].toInt()) + numSplitValue = ByteTools.eightBytetoLEDouble(this.getBytesAt(12, 8)!!).toFloat() + fHasShadow = data[20] and 1 == 1 + chartType = ChartConstants.OFPIECHART } /** - * returns split type, or -1 if autosplit - * 0x0000 Position The data is split based on the position of the data point in the series as specified by iSplitPos. - * 0x0001 Value The data is split based on a threshold value as specified by numSplitValue. - * 0x0002 Percent The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. - * 0x0003 Custom The data is split as arranged by the user. Custom split is specified in a following BopPopCustom record. + * specifies the distance between the primary pie and the secondary bar/pie. + * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. * - * @return + * @param g */ - public int getSplitType() { - if (fAutoSplit) return -1; - return split; - } - - public String getSplitTypeOOXML() { - if (fAutoSplit) - return "auto"; - switch (split) { - case 0: - return "pos"; - case 1: - return "val"; - case 2: - return "percent"; - case 3: - return "cust"; - } - return "auto"; // default + fun setpcGap(g: Int) { + pcGap = g.toShort() + val b = ByteTools.shortToLEBytes(pcGap) + this.data[10] = b[0] + this.data[11] = b[1] } /** - * specifies how many data points are contained in the secondary bar/pie. - * Data points are contained in the secondary bar/pie starting from the end of the series. - * For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. - * - * @param sp + * returns the distance between the primary pie and the secondary bar/pie. + * The distance is specified as a percentage of the average width of the primary pie and secondary bar/pie. */ - public void setSplitPos(int sp) { - iSplitPos = (short) sp; - byte[] b = ByteTools.shortToLEBytes(iSplitPos); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; + fun getpcGap(): Int { + return pcGap.toInt() } /** - * returns how many data points are contained in the secondary bar/pie. - * Data points are contained in the secondary bar/pie starting from the end of the series. - * For example, if the value is 2, the last 2 data points in the series are contained in the secondary bar/pie. + * specifies what determines the split between the primary pie and the secondary bar/pie via OOXML string value: + * auto -- split point of the chart group is determined automatically + * pos -- The data is split based on the position of the data point in the series as specified by iSplitPos. + * val -- The data is split based on a threshold value as specified by numSplitValue. + * percent --The data is split based on a percentage threshold and the data point values represented as a percentage as specified by pcSplitPercent. + * cust -- The data is split as arranged by the user. + * + * @param s */ - public int getSplitPos() { - return iSplitPos; + fun setSplitType(s: String?) { + if (s == null) { + fAutoSplit = true + this.data[1] = 0 + return + } + fAutoSplit = false + this.data[1] = 0 + if (s == "pos") + split = 0 + else if (s == "val") + split = 1 + else if (s == "percent") + split = 2 + else if (s == "cust") + split = 3 + val b = ByteTools.shortToLEBytes(split) + this.data[2] = b[0] + this.data[3] = b[1] } /** * Set specific options */ - public boolean setChartOption(String op, String val) { - if (op.equalsIgnoreCase("Gap")) { - setpcGap(Integer.parseInt(val)); + override fun setChartOption(op: String, `val`: String): Boolean { + if (op.equals("Gap", ignoreCase = true)) { + setpcGap(Integer.parseInt(`val`)) } - return true; + return true + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8071801452993935943L + + val prototype: XLSRecord? + get() { + val b = Boppop() + b.opcode = XLSConstants.BOPPOP + b.data = b.PROTOTYPE_BYTES + b.init() + return b + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/BoppopCustom.java b/src/main/java/io/starter/formats/XLS/charts/BoppopCustom.kt similarity index 69% rename from src/main/java/io/starter/formats/XLS/charts/BoppopCustom.java rename to src/main/java/io/starter/formats/XLS/charts/BoppopCustom.kt index 8ded118..e147a7c 100644 --- a/src/main/java/io/starter/formats/XLS/charts/BoppopCustom.java +++ b/src/main/java/io/starter/formats/XLS/charts/BoppopCustom.kt @@ -20,18 +20,21 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts /** - * BoppopCustom: Custom Bar of Pie/Pie of Pie Chart Options (0x1067) + * **BoppopCustom: Custom Bar of Pie/Pie of Pie Chart Options (0x1067)** */ -public class BoppopCustom extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 544717450572314091L; +class BoppopCustom : GenericChartObject(), ChartObject { - public void init() { - super.init(); + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 544717450572314091L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java b/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java deleted file mode 100644 index b603690..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/BubbleChart.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.HashMap; - -public class BubbleChart extends ChartType { - - private Scatter bubble = null; - private boolean is3d = false; - - public BubbleChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - bubble = (Scatter) charttype; - } - - /** - * Bubble charts handle 3d differently - * - * @param is3d - */ - public void setIs3d(boolean is3d) { - this.is3d = is3d; - } - - /** - * return true if this bubble chart series should be displayed as 3d - * - * @return - */ - public boolean is3d() { - return is3d; - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double x = chartMetrics.get("x"); - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there - // y value for each point= h/YMAX - StringBuffer svg = new StringBuffer(); - - if (series.size() == 0) { - Logger.logErr("Scatter.getSVG: error in series"); - return ""; - } - // gather data labels, markers, has lines, series colors for chart - boolean threeD = cf.isThreeD(BUBBLECHART); - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - boolean hasLines = cf.getHasLines(); // should be per-series? - int[] markers = getMarkerFormats(); // get an array of marker formats per series - int n = series.size(); - double[] seriesx = null; - double xfactor = 0, yfactor = 0, bfactor = 0; // - boolean TEXTUALXAXIS = true; - // get x axis max/min for an x axis which is a value axis - seriesx = new double[categories.length]; - double xmin = Double.MAX_VALUE, xmax = Double.MIN_VALUE; - for (int j = 0; j < categories.length; j++) { - try { - seriesx[j] = new Double(categories[j].toString()); - xmax = Math.max(xmax, seriesx[j]); - xmin = Math.min(xmin, seriesx[j]); - TEXTUALXAXIS = false; // if ANY category val is a double, assume it's a normal xyvalue axis - } catch (Exception e) { - /* keep going */ - } - } - if (!TEXTUALXAXIS) { - double[] d = ValueRange.calcMaxMin(xmax, xmin, w); - xfactor = w / (d[2]); // w/maximum scale - } else - xfactor = w / (categories.length + 1); // w/#categories - - if ((max - min) != 0) - yfactor = h / Math.abs(max - min); // h/YMAXSCALE - // get Bubble Size Factor - for (int i = 0; i < n; i++) { - double[] seriesy = (double[]) series.get(i); - String[] curranges = (String[]) s.getSeriesRanges().get(i); - int nseries = seriesy.length / 2; - double bmin = Double.MAX_VALUE, bmax = Double.MIN_VALUE; - for (int j = nseries; j < seriesy.length; j++) { - bmax = Math.max(bmax, seriesy[j]); - bmin = Math.min(bmin, seriesy[j]); - } - if ((bmax - bmin) != 0) - bfactor = h / Math.abs(bmax - bmin) / 5; - } - svg.append("\r\n"); - if (threeD) { - svg.append(get3DBubbleSVG(seriescolors)); - } - // for each series - for (int i = 0; i < n; i++) { - String labels = ""; - double[] seriesy = (double[]) series.get(i); - String[] curranges = (String[]) s.getSeriesRanges().get(i); - int nseries = seriesy.length / 2; - for (int j = 0; j < nseries; j++) { - double xval = 0; - if (TEXTUALXAXIS /*|| i > 0*/) - xval = j + 1; - else - xval = seriesx[j]; - double cx = x + xval * xfactor; - double cy = (y + h) - (seriesy[j] * yfactor); - double r = seriesy[j + nseries] * bfactor; -//io.starter.toolkit.Logger.log("x: " + xval + " val: " + seriesy[j] + " size: " + seriesy[j+nseries]); - if (!threeD) - svg.append("\r\n"); - else - svg.append("\r\n"); - String l = getSVGDataLabels(dls, axisMetrics, seriesy[j + nseries], 0, i, legends, categories[j].toString()); - if (l != null) - labels += "" + l + "\r\n"; - } - // labels after lines and markers - svg.append(labels); - } -//io.starter.toolkit.Logger.log("Bubble svg: " + svg.toString()); - svg.append("\r\n"); - return svg.toString(); - } - - /** - * returns the SVG necessary to define 3D bubbles (circles) for each series color - * - * @param seriescolors - * @return String SVG - */ - private String get3DBubbleSVG(String[] seriescolors) { - StringBuffer svg = new StringBuffer(); - svg.append("\r\n"); - for (int i = 0; i < seriescolors.length; i++) { - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - svg.append("\r\n"); - } - svg.append("\r\n"); - return svg.toString(); - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - if (this.is3d) - cooxml.append("bubble3d val=\"1\""); - // bubblescale - cooxml.append(""); // TODO: read correct value - // showNegBubbles - // sizeRepresents - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/BubbleChart.kt b/src/main/java/io/starter/formats/XLS/charts/BubbleChart.kt new file mode 100644 index 0000000..dd35a9a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/BubbleChart.kt @@ -0,0 +1,224 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.HashMap + +class BubbleChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + + private var bubble: Scatter? = null + /** + * return true if this bubble chart series should be displayed as 3d + * + * @return + */ + /** + * Bubble charts handle 3d differently + * + * @param is3d + */ + var is3d = false + + init { + bubble = charttype as Scatter + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val x = chartMetrics["x"] + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there + // y value for each point= h/YMAX + val svg = StringBuffer() + + if (series!!.size == 0) { + Logger.logErr("Scatter.getSVG: error in series") + return "" + } + // gather data labels, markers, has lines, series colors for chart + val threeD = cf!!.isThreeD(ChartConstants.BUBBLECHART) + val dls = dataLabelInts // get array of data labels (can be specific per series ...) + val hasLines = cf!!.hasLines // should be per-series? + val markers = markerFormats // get an array of marker formats per series + val n = series.size + var seriesx: DoubleArray? = null + var xfactor = 0.0 + var yfactor = 0.0 + var bfactor = 0.0 // + var TEXTUALXAXIS = true + // get x axis max/min for an x axis which is a value axis + seriesx = DoubleArray(categories!!.size) + var xmin = java.lang.Double.MAX_VALUE + var xmax = java.lang.Double.MIN_VALUE + for (j in categories.indices) { + try { + seriesx[j] = Double(categories[j].toString()) + xmax = Math.max(xmax, seriesx[j]) + xmin = Math.min(xmin, seriesx[j]) + TEXTUALXAXIS = false // if ANY category val is a double, assume it's a normal xyvalue axis + } catch (e: Exception) { + /* keep going */ + } + + } + if (!TEXTUALXAXIS) { + val d = ValueRange.calcMaxMin(xmax, xmin, w) + xfactor = w / d[2] // w/maximum scale + } else + xfactor = w / (categories.size + 1) // w/#categories + + if (max - min != 0.0) + yfactor = h / Math.abs(max - min) // h/YMAXSCALE + // get Bubble Size Factor + for (i in 0 until n) { + val seriesy = series[i] as DoubleArray + val curranges = s.seriesRanges!![i] as Array + val nseries = seriesy.size / 2 + var bmin = java.lang.Double.MAX_VALUE + var bmax = java.lang.Double.MIN_VALUE + for (j in nseries until seriesy.size) { + bmax = Math.max(bmax, seriesy[j]) + bmin = Math.min(bmin, seriesy[j]) + } + if (bmax - bmin != 0.0) + bfactor = h / Math.abs(bmax - bmin) / 5.0 + } + svg.append("\r\n") + if (threeD) { + svg.append(get3DBubbleSVG(seriescolors!!)) + } + // for each series + for (i in 0 until n) { + var labels = "" + val seriesy = series[i] as DoubleArray + val curranges = s.seriesRanges!![i] as Array + val nseries = seriesy.size / 2 + for (j in 0 until nseries) { + var xval = 0.0 + if (TEXTUALXAXIS /*|| i > 0*/) + xval = (j + 1).toDouble() + else + xval = seriesx[j] + val cx = x + xval * xfactor + val cy = y + h - seriesy[j] * yfactor + val r = seriesy[j + nseries] * bfactor + //io.starter.toolkit.Logger.log("x: " + xval + " val: " + seriesy[j] + " size: " + seriesy[j+nseries]); + if (!threeD) + svg.append("\r\n") + else + svg.append("\r\n") + val l = getSVGDataLabels(dls, axisMetrics, seriesy[j + nseries], 0.0, i, legends, categories[j].toString()) + if (l != null) + labels += "" + l + "\r\n" + } + // labels after lines and markers + svg.append(labels) + } + //io.starter.toolkit.Logger.log("Bubble svg: " + svg.toString()); + svg.append("\r\n") + return svg.toString() + } + + /** + * returns the SVG necessary to define 3D bubbles (circles) for each series color + * + * @param seriescolors + * @return String SVG + */ + private fun get3DBubbleSVG(seriescolors: Array): String { + val svg = StringBuffer() + svg.append("\r\n") + for (i in seriescolors.indices) { + svg.append("") + svg.append("") + svg.append("") + svg.append("") + svg.append("") + svg.append("") + svg.append("\r\n") + } + svg.append("\r\n") + return svg.toString() + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + if (this.is3d) + cooxml.append("bubble3d val=\"1\"") + // bubblescale + cooxml.append("") // TODO: read correct value + // showNegBubbles + // sizeRepresents + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/CatLab.java b/src/main/java/io/starter/formats/XLS/charts/CatLab.kt similarity index 61% rename from src/main/java/io/starter/formats/XLS/charts/CatLab.java rename to src/main/java/io/starter/formats/XLS/charts/CatLab.kt index a9fa478..401e126 100644 --- a/src/main/java/io/starter/formats/XLS/charts/CatLab.java +++ b/src/main/java/io/starter/formats/XLS/charts/CatLab.kt @@ -20,28 +20,32 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools /** - * CATLAB: Category Labels (856h) + * **CATLAB: Category Labels (856h)** * Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 10 wOffset 2 Distance between category label levels * 12 at 2 Category axis label alignment * 14 grbit 2 Option flags for category axis labels (see description below) * 16 (unused) 2 Reserved; must be zero - *

                      + * + * * The grbit field contains the following category axis label option flags: * Bits Mask Flag Name Contents * 0 0001h fAutoCatLabelReal =1 if the category label skip is automatic =0 otherwise * 15-1 FFFEh (unused) Reserved; must be zero - *

                      - *

                      + * + * + * + * * wOffset (2 bytes): An unsigned integer that specifies the distance between the axis and axis label. * It contains the offset as a percentage of the default distance. The default distance is equal to 1/3 the height of the font calculated in pixels. * MUST be a value greater than or equal to 0 (0%) and less than or equal to 1000 (1000%). @@ -50,7 +54,8 @@ * 0x0001 Top-aligned if the trot field of the Text record of the axis is not equal to 0. Left-aligned if the iReadingOrder field of the Text record of the axis specifies left-to-rightreading order; otherwise, right-aligned. * 0x0002 Center-alignment * 0x0003 Bottom-aligned if the trot field of the Text record of the axis is not equal to 0. Right-aligned if the iReadingOrder field of the Text record of the axis specifies left-to-right reading order; otherwise, left-aligned. - *

                      + * + * * A - cAutoCatLabelReal (1 bit): A bit that specifies whether the number of categories (3) between axis labels is set to the default value. MUST be a value from the following table: * Value Description * 0 The value is set to catLabel field as specified by CatSerRange record. @@ -58,66 +63,72 @@ * unused (15 bits): Undefined, and MUST be ignored. * reserved (2 bytes): MUST be zero, and MUST be ignored. */ -public class CatLab extends GenericChartObject implements ChartObject { +class CatLab : GenericChartObject(), ChartObject { /** * serialVersionUID */ - short wOffset, at; - private static final long serialVersionUID = 3042712098138741496L; - - public void init() { - super.init(); - wOffset = ByteTools.readShort(this.getData()[4], this.getData()[5]); - at = ByteTools.readShort(this.getData()[6], this.getData()[7]); - // cAutoCatLabelReal= (record[8] & 0x1==0x1 - } + internal var wOffset: Short = 0 + internal var at: Short = 0 // TODO: need prototype bytes - private byte[] PROTOTYPE_BYTES = new byte[]{(byte) 86, 8, 0, 0, (byte) 100, 0, 2, 0, 86, 66, 0, 0}; + private val PROTOTYPE_BYTES = byteArrayOf(86.toByte(), 8, 0, 0, 100.toByte(), 0, 2, 0, 86, 66, 0, 0) - public static XLSRecord getPrototype() { - CatLab cl = new CatLab(); - cl.setOpcode(CATLAB); - cl.setData(cl.PROTOTYPE_BYTES); - cl.init(); - return cl; + override fun init() { + super.init() + wOffset = ByteTools.readShort(this.data!![4].toInt(), this.data!![5].toInt()) + at = ByteTools.readShort(this.data!![6].toInt(), this.data!![7].toInt()) + // cAutoCatLabelReal= (record[8] & 0x1==0x1 } - public void setOption(String op, String val) { - if (op.equals("lblAlign")) { // ctr, l, r - if (val.equals("ctr")) - at = 2; - else if (val.equals("l")) - at = 1; + fun setOption(op: String, `val`: String) { + if (op == "lblAlign") { // ctr, l, r + if (`val` == "ctr") + at = 2 + else if (`val` == "l") + at = 1 else - at = 3; - } else if (op.equals("lblOffset")) { // 0-100 - wOffset = (short) Integer.parseInt(val); + at = 3 + } else if (op == "lblOffset") { // 0-100 + wOffset = Integer.parseInt(`val`).toShort() } - updateRecord(); + updateRecord() } - public String getOption(String op) { - if (op.equals("lblAlign")) { // ctr, l, r - if (at == 2) - return "ctr"; - else if (at == 1) - return "l"; + fun getOption(op: String): String? { + if (op == "lblAlign") { // ctr, l, r + return if (at.toInt() == 2) + "ctr" + else if (at.toInt() == 1) + "l" else - return "r"; - } else if (op.equals("lblOffset")) // 0-100 - return Integer.toString(wOffset); - return null; + "r" + } else if (op == "lblOffset") + // 0-100 + return Integer.toString(wOffset.toInt()) + return null + } + + private fun updateRecord() { + var b = ByteArray(2) + b = ByteTools.shortToLEBytes(wOffset) + this.data[4] = b[0] + this.data[5] = b[1] + b = ByteTools.shortToLEBytes(at) + this.data[6] = b[0] + this.data[7] = b[1] } - private void updateRecord() { - byte[] b = new byte[2]; - b = ByteTools.shortToLEBytes(wOffset); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - b = ByteTools.shortToLEBytes(at); - this.getData()[6] = b[0]; - this.getData()[7] = b[1]; + companion object { + private val serialVersionUID = 3042712098138741496L + + val prototype: XLSRecord? + get() { + val cl = CatLab() + cl.opcode = XLSConstants.CATLAB + cl.data = cl.PROTOTYPE_BYTES + cl.init() + return cl + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/CatserRange.java b/src/main/java/io/starter/formats/XLS/charts/CatserRange.java deleted file mode 100644 index e2473b7..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/CatserRange.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * CatserRange: Defines a Category or Series Axis (0x1020) - *

                      - * 4 catCross 2 Value axis/category crossing point (2-D charts only) - * If fMaxCross is set to 1, the value this field MUST be ignored. - * for series axes, must be 0 - * for cat: This field specifies the category at which the value axis crosses. - * For example, if this field is 2, the value axis crosses this axis at the second category on this axis. - * MUST be greater than or equal to 1 and less than or equal to 31999. - * 6 catLabel 2 Frequency of labels. A signed integer that specifies the interval between axis labels on this axis. - * MUST be greater than or equal to 1 and less than or equal to 31999. - * MUST be ignored for a date axis. - * 8 catMark 2 Frequency of tick marks. A signed integer that specifies the interval at which major tick marks - * and minor tick marks are displayed on the axis. Major tick marks and minor tick marks that would - * have been visible are hidden unless they are located at a multiple of this field. - * MUST be greater than or equal to 1, and less than or equal to 31999. - * MUST be ignored for a date axis. - * 10 grbit 2 Format flags - *

                      - * The catCross field defines the point on the category axis where the value axis crosses. - * A value of 01 indicates that the value axis crosses to the left, or in the center, of the first category - * (depending on the value of bit 0 of the grbit field); a value of 02 indicates that the value axis crosses - * to the left or center of the second category, and so on. Bit 2 of the grbit field overrides the value of catCross when set to 1. - *

                      - * The catLabel field defines how often labels appear along the category or series axis. A value of 01 indicates - * that a category label will appear with each category, a value of 02 means a label appears every other category, and so on. - *

                      - * The catMark field defines how often tick marks appear along the category or series axis. A value of 01 indicates - * that a tick mark will appear between each category or series; a value of 02 means a label appears between every - * other category or series, etc. - *

                      - * format flags: - * 0 0xOl fBetween Value axis crossing a = axis crosses midcategory I = axis crosses between categories - * 1 0x02 fMaxCross Value axis crosses at the far right category (in a line, bar, column, scatter, or area chart; 2-D charts only) - * 0 The value axis crosses this axis at the value specified by catCross. - * 1 The value axis crosses this axis at the last category, the last series, or the maximum date. - * 2 0x04 fReverse Display categories in reverse order - * 7-3 0xF8 (reserved) Reserved; must be zero - * 7-0 0xFF (reserved) Reserved; must be zero - */ -public class CatserRange extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 905038625844435651L; - private short grbit, catCross, catLabel, catMark; - private boolean fBetween, fMaxCross, fReverse; - - // 20070723 KSC: Need to create new records - public static XLSRecord getPrototype() { - CatserRange c = new CatserRange(); - c.setOpcode(CATSERRANGE); - c.setData(c.PROTOTYPE_BYTES); - c.init(); - return c; - } // 0, 0, 1, 0, 1, 0, 0, 0 - for axis 2 of surface chart - - private byte[] PROTOTYPE_BYTES = new byte[]{1, 0, 1, 0, 1, 0, 1, 0}; - - // 20070727 KSC: TODO: Get data def and parse correctly! - public void setOpt(int op) { - byte[] b = ByteTools.shortToLEBytes((short) op); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - // 20070802 KSC: don't know what this means - this.getData()[6] = 0; - } - - // 20070802 KSC: parse data - public void init() { - super.init(); - catCross = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - catLabel = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - catMark = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - grbit = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - fBetween = (grbit & 0x1) == 0x1; - fMaxCross = (grbit & 0x2) == 0x2; - fReverse = (grbit & 0x4) == 0x4; - } - - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(catCross); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - b = ByteTools.shortToLEBytes(catLabel); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - b = ByteTools.shortToLEBytes(catMark); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - b = ByteTools.shortToLEBytes(grbit); - this.getData()[6] = b[0]; - this.getData()[7] = b[1]; - } - - // get/set methods - public int getCatCross() { - return catCross; - } - - public int getCatLabel() { - return catLabel; - } - - public int getCatMark() { - return catMark; - } - - public boolean getCrossBetween() { - return fBetween; - } - - public boolean getCrossMax() { - return fMaxCross; - } - - public void setCatCross(int c) { - catCross = (short) c; - updateRecord(); - } - - public void setCatLabel(int c) { - catLabel = (short) c; - updateRecord(); - } - - public void setCatMark(int c) { - catMark = (short) c; - updateRecord(); - } - - public void setCrossBetween(boolean b) { - fBetween = b; - grbit = ByteTools.updateGrBit(grbit, fBetween, 0); - updateRecord(); - } - - public void setCrossMax(boolean b) { - fMaxCross = b; - grbit = ByteTools.updateGrBit(grbit, fMaxCross, 1); - updateRecord(); - } - - /** - * sets a specific OOXML axis option - *
                      can be one of: - *
                      auto - *
                      crosses possible crossing points (autoZero, max, min) - *
                      crossesAt where on axis the perpendicular axis crosses (double val) - *
                      lblAlign text alignment for tick labels (ctr, l, r) (cat only) - *
                      lblOffset distance of labels from the axis (0-1000) (cat only) - *
                      tickLblSkip how many tick labels to skip between label (int >= 1) - *
                      tickMarkSkip how many tick marks to skip betwen ticks (int >= 1) - * - * @param op - * @param val - */ - // TODO: auto - public boolean setOption(String op, String val) { - if (op.equals("crossesAt")) // specifies where axis crosses (double value - catCross = Short.valueOf(val).shortValue(); - else if (op.equals("orientation")) { // axis orientation minMax or maxMin -- fReverse - fReverse = (val.equals("maxMin")); // means in reverse order - ByteTools.updateGrBit(grbit, fReverse, 2); - } else if (op.equals("crosses")) { // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross - if (val.equals("max")) { // TODO: this is probly wrong - fMaxCross = true; - ByteTools.updateGrBit(grbit, fMaxCross, 7); - } else if (val.equals("mid")) { - fBetween = false; - ByteTools.updateGrBit(grbit, fBetween, 0); - } else if (val.equals("autoZero")) { - fBetween = true; // is this correct?? - ByteTools.updateGrBit(grbit, fBetween, 0); - } else if (val.equals("min")) ; - // TODO: ??? - } else if (op.equals("tickMarkSkip")) //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? - catMark = Integer.valueOf(val).shortValue(); - else if (op.equals("tickLblSkip")) - catLabel = Integer.valueOf(val).shortValue(); - else - return false; // not handled - this.updateRecord(); - return true; - } - - - /** - * retrieve generic Category axis option - * - * @param op - * @return - */ - public String getOption(String op) { - // TODO: auto, lblAlign, lblOffset - if (op.equals("crossesAt")) - return String.valueOf(catCross); - if (op.equals("orientation")) - return (fReverse) ? "maxMin" : "minMax"; - if (op.equals("crosses")) { - if (fMaxCross) return "max"; - if (fBetween) return "autoZero"; // correct?? - return "min"; // correct?? - } - if (op.equals("tickMarkSkip")) //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? - return String.valueOf(catMark); - if (op.equals("tickLblSkip")) - return String.valueOf(catLabel); - return null; - } - - /** - * returns true if the axis should be displayed at top of chart area - * false if axis is displayed in the default bottom location - * - * @return - */ - public boolean isReversed() { - return fReverse; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/CatserRange.kt b/src/main/java/io/starter/formats/XLS/charts/CatserRange.kt new file mode 100644 index 0000000..1cdcfa4 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/CatserRange.kt @@ -0,0 +1,256 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **CatserRange: Defines a Category or Series Axis (0x1020)** + * + * + * 4 catCross 2 Value axis/category crossing point (2-D charts only) + * If fMaxCross is set to 1, the value this field MUST be ignored. + * for series axes, must be 0 + * for cat: This field specifies the category at which the value axis crosses. + * For example, if this field is 2, the value axis crosses this axis at the second category on this axis. + * MUST be greater than or equal to 1 and less than or equal to 31999. + * 6 catLabel 2 Frequency of labels. A signed integer that specifies the interval between axis labels on this axis. + * MUST be greater than or equal to 1 and less than or equal to 31999. + * MUST be ignored for a date axis. + * 8 catMark 2 Frequency of tick marks. A signed integer that specifies the interval at which major tick marks + * and minor tick marks are displayed on the axis. Major tick marks and minor tick marks that would + * have been visible are hidden unless they are located at a multiple of this field. + * MUST be greater than or equal to 1, and less than or equal to 31999. + * MUST be ignored for a date axis. + * 10 grbit 2 Format flags + * + * + * The catCross field defines the point on the category axis where the value axis crosses. + * A value of 01 indicates that the value axis crosses to the left, or in the center, of the first category + * (depending on the value of bit 0 of the grbit field); a value of 02 indicates that the value axis crosses + * to the left or center of the second category, and so on. Bit 2 of the grbit field overrides the value of catCross when set to 1. + * + * + * The catLabel field defines how often labels appear along the category or series axis. A value of 01 indicates + * that a category label will appear with each category, a value of 02 means a label appears every other category, and so on. + * + * + * The catMark field defines how often tick marks appear along the category or series axis. A value of 01 indicates + * that a tick mark will appear between each category or series; a value of 02 means a label appears between every + * other category or series, etc. + * + * + * format flags: + * 0 0xOl fBetween Value axis crossing a = axis crosses midcategory I = axis crosses between categories + * 1 0x02 fMaxCross Value axis crosses at the far right category (in a line, bar, column, scatter, or area chart; 2-D charts only) + * 0 The value axis crosses this axis at the value specified by catCross. + * 1 The value axis crosses this axis at the last category, the last series, or the maximum date. + * 2 0x04 fReverse Display categories in reverse order + * 7-3 0xF8 (reserved) Reserved; must be zero + * 7-0 0xFF (reserved) Reserved; must be zero + */ +class CatserRange : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var catCross: Short = 0 + private var catLabel: Short = 0 + private var catMark: Short = 0 + private var fBetween: Boolean = false + private var fMaxCross: Boolean = false + /** + * returns true if the axis should be displayed at top of chart area + * false if axis is displayed in the default bottom location + * + * @return + */ + var isReversed: Boolean = false + private set + + private val PROTOTYPE_BYTES = byteArrayOf(1, 0, 1, 0, 1, 0, 1, 0) + + var crossBetween: Boolean + get() = fBetween + set(b) { + fBetween = b + grbit = ByteTools.updateGrBit(grbit, fBetween, 0) + updateRecord() + } + + var crossMax: Boolean + get() = fMaxCross + set(b) { + fMaxCross = b + grbit = ByteTools.updateGrBit(grbit, fMaxCross, 1) + updateRecord() + } + + // 20070727 KSC: TODO: Get data def and parse correctly! + fun setOpt(op: Int) { + val b = ByteTools.shortToLEBytes(op.toShort()) + this.data[0] = b[0] + this.data[1] = b[1] + // 20070802 KSC: don't know what this means + this.data[6] = 0 + } + + // 20070802 KSC: parse data + override fun init() { + super.init() + catCross = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + catLabel = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + catMark = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + grbit = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + fBetween = grbit and 0x1 == 0x1 + fMaxCross = grbit and 0x2 == 0x2 + isReversed = grbit and 0x4 == 0x4 + } + + private fun updateRecord() { + var b = ByteTools.shortToLEBytes(catCross) + this.data[0] = b[0] + this.data[1] = b[1] + b = ByteTools.shortToLEBytes(catLabel) + this.data[2] = b[0] + this.data[3] = b[1] + b = ByteTools.shortToLEBytes(catMark) + this.data[4] = b[0] + this.data[5] = b[1] + b = ByteTools.shortToLEBytes(grbit) + this.data[6] = b[0] + this.data[7] = b[1] + } + + // get/set methods + fun getCatCross(): Int { + return catCross.toInt() + } + + fun getCatLabel(): Int { + return catLabel.toInt() + } + + fun getCatMark(): Int { + return catMark.toInt() + } + + fun setCatCross(c: Int) { + catCross = c.toShort() + updateRecord() + } + + fun setCatLabel(c: Int) { + catLabel = c.toShort() + updateRecord() + } + + fun setCatMark(c: Int) { + catMark = c.toShort() + updateRecord() + } + + /** + * sets a specific OOXML axis option + *

                      can be one of: + *

                      auto + *

                      crosses possible crossing points (autoZero, max, min) + *

                      crossesAt where on axis the perpendicular axis crosses (double val) + *

                      lblAlign text alignment for tick labels (ctr, l, r) (cat only) + *

                      lblOffset distance of labels from the axis (0-1000) (cat only) + *

                      tickLblSkip how many tick labels to skip between label (int >= 1) + *

                      tickMarkSkip how many tick marks to skip betwen ticks (int >= 1) + * + * @param op + * @param val + */ + // TODO: auto + fun setOption(op: String, `val`: String): Boolean { + if (op == "crossesAt") + // specifies where axis crosses (double value + catCross = java.lang.Short.valueOf(`val`).toShort() + else if (op == "orientation") { // axis orientation minMax or maxMin -- fReverse + isReversed = `val` == "maxMin" // means in reverse order + ByteTools.updateGrBit(grbit, isReversed, 2) + } else if (op == "crosses") { // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross + if (`val` == "max") { // TODO: this is probly wrong + fMaxCross = true + ByteTools.updateGrBit(grbit, fMaxCross, 7) + } else if (`val` == "mid") { + fBetween = false + ByteTools.updateGrBit(grbit, fBetween, 0) + } else if (`val` == "autoZero") { + fBetween = true // is this correct?? + ByteTools.updateGrBit(grbit, fBetween, 0) + } else if (`val` == "min"); + // TODO: ??? + } else if (op == "tickMarkSkip") + //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? + catMark = Integer.valueOf(`val`).toShort() + else if (op == "tickLblSkip") + catLabel = Integer.valueOf(`val`).toShort() + else + return false // not handled + this.updateRecord() + return true + } + + + /** + * retrieve generic Category axis option + * + * @param op + * @return + */ + fun getOption(op: String): String? { + // TODO: auto, lblAlign, lblOffset + if (op == "crossesAt") + return catCross.toString() + if (op == "orientation") + return if (isReversed) "maxMin" else "minMax" + if (op == "crosses") { + if (fMaxCross) return "max" + return if (fBetween) "autoZero" else "min" // correct?? +// correct?? + } + if (op == "tickMarkSkip") + //val= how many tick marks to skip before next one is drawn -- catMark -- catsterrange only? + return catMark.toString() + return if (op == "tickLblSkip") catLabel.toString() else null + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 905038625844435651L + + // 20070723 KSC: Need to create new records + val prototype: XLSRecord? + get() { + val c = CatserRange() + c.opcode = XLSConstants.CATSERRANGE + c.data = c.PROTOTYPE_BYTES + c.init() + return c + } // 0, 0, 1, 0, 1, 0, 0, 0 - for axis 2 of surface chart + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Chart.java b/src/main/java/io/starter/formats/XLS/charts/Chart.java deleted file mode 100644 index 383f62b..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Chart.java +++ /dev/null @@ -1,1932 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.ChartHandle; -import io.starter.OpenXLS.ChartHandle.ChartOptions; -import io.starter.OpenXLS.ExcelTools; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import org.json.JSONArray; -import org.json.JSONException; - -import java.io.*; -import java.util.*; - - -/** - * Chart: Chart Location and Dimensions 0x1002h
                      - *

                      - * The Chart record determines the chart dimensions and - * marks the beginning of the Chart records. - * - *

                      - * * Note that all these values are split up 2 bytes integer and 2 bytes fractional
                      - *
                      - * offset  name        size    contents
                      - * ---
                      - * 4       x           4       x pos of upper left corner
                      - * 8       y           4       y pos of upper left corner
                      - * 12      dx          4       x-size
                      - * 16      dy          4       y-size
                      - *
                      - * 

                      - *

                      - *

                      - *

                      - * notes on implementation: - * - *
                      A chart may have up to 9 chart types (specific instances of a chart e.g. bar, line, etc.) known as chart groups. These are identified by the chartgroup array list. - *
                      The parent of these chart groups are the axis group. - *
                      A chart's series and trendlines contains indexes into the chart group. - * - * @see Ai - */ -public class Chart extends GenericChartObject implements ChartObject { - static final long serialVersionUID = 6702247464633674375l; - - // Objects which define a chart -- REMEMBER TO UPDATE OOXMLChart copy constructor if modify below member vars - protected ArrayList chartgroup = new ArrayList(); // can have up to 4 chart objects (Bar, Area, Line, etc.) per a given chart - protected int nCharts = 0; // total number of charts (0= the default chart + any overlay charts (up to 4 charts (9 in OOXML) see chartgroup list) - protected ChartAxes chartaxes = null; - protected ChartSeries chartseries = new ChartSeries(); - protected TextDisp charttitle = null; - protected Dimensions dimensions; // datarange dimensions ... - // The following two objects are external objects in the XLS stream that are associated with the chart. - protected Obj obj = null; - protected MSODrawing msodrawobj = null; // stores coordinates and shape info ... - HashMap chartMetrics = new HashMap(); // hold chartMetrics: x, y, w, h, canvasw, canvash - public transient WorkBookHandle wbh; - - // TODO: MANAGE FONTS ---? - - // internal chart records - protected ArrayList chartRecs = new ArrayList(); - protected AbstractList preRecs; - protected AbstractList postRecs = new ArrayList(); - protected boolean dirtyflag = false; // if anything has changed in the chart (except series, which is handled via another var) - protected boolean metricsDirty = true; // initially true so creates min, max and other metrics, true if should be recalculated - // below vars used to save state in addInitialChartRecord recursion - protected Ai currentAi; // used in init only - protected int hierarchyDepth = 0; // "" - protected ArrayList initobs = new ArrayList(); - - - /* - public Chart copy() throws InstantiationException, IllegalAccessException { - Chart copy = this.getClass().newInstance(); - return copy; - }*/ - - public void init() { - super.init(); - getData(); - chartseries.setParentChart(this); - /** - * NOTE: the x, y, dx (w) and dy (h) are in most cases - * not used; the MSODRAWING coords governs the - * chart canvas; see getMetrics for plot and other adjustments - * - * New Info: have to see if this works - * Get chart area width in pixels - chart area width in pixels = (dx field of Chart record - 8) * DPI of the display device / 72 - If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: - chart area width in pixels -= 2 * line width of the display device in pixels - - Get chart area height in pixels - chart area height in pixels = (dy field of Chart record - 8) * DPI of the display device / 72 - If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: - chart area height in pixels -= 2 * line height of the display device in pixels - - NOTE: - * Since the 1980s, the Microsoft Windows operating system has set the default display "DPI" to 96 PPI, - * while Apple/Macintosh computers have used a default of 72 PPI.[2] - * - byte[] rkdata = this.getData(); - try { - short s = ByteTools.readShort(rkdata[0],rkdata[1]); - short ss = ByteTools.readShort(rkdata[2],rkdata[3]); - - if(ss<0)ss=0; - String parser = s + "." + ss; - x = (new Float(parser)).floatValue(); - - s = ByteTools.readShort(rkdata[4],rkdata[5]); - ss = ByteTools.readShort(rkdata[6],rkdata[7]); - if(ss<0)ss=0; - - parser = s + "." + ss; - y = (new Float(parser)).floatValue(); - - - /*Value of the real number = Integral + (Fractional / 65536.0) - * Integral (2 bytes): A signed integer that specifies the integral part of the real number. - Fractional (2 bytes): An unsigned integer that specifies the fractional part of the real number. - * - int integral= 0; - integral |= rkdata[8] & 0xFF; - integral <<= 8; - integral |= rkdata[9] & 0xFF; - int fractional= ByteTools.readUnsignedShort(rkdata[10],rkdata[11]); - dx= (float)(integral + (fractional/65536.0)); - integral= ((rkdata[12] & 0xFF) << 8) | (rkdata[13] & 0xFF); - if (integral < 0) integral += 256; - fractional= ByteTools.readUnsignedShort(rkdata[14],rkdata[15]); - dy= (float) integral; - } catch (NumberFormatException e) { // 20080414 KSC: parsing appears to be off - TODO Check out Chart record specs - Logger.logErr("Chart.init: parsing dimensions failed: " + e); - } - - if ((DEBUGLEVEL > 3)) - Logger.logInfo("Chart Found @ x:" + x + " y:" + y + " dx:" + dx + " dy:" + dy);*/ - } - - - /* - * Note, other important chart records we are not as of yet trapping: - * CttMlFrt 0x89E -- additional properties for chart elements - * ShapePropsStream 0x8A4 - * TextPropsStream 0x8A5 - * CrtLayout12 0x89D -- layout info for attached label, legend - * 0x89E=The CrtMlFrt record specifies additional properties for chart elements, as specified by the Chart Sheet SubstreamABNF. These properties complement the record to which they correspond, and are stored as a structure chain defined in XmlTkChain. An application can ignore this record without loss of functionality, except for the additional properties. If this record is longer than 8224 bytes, it MUST be split into several records. The first section of the data appears in this record and subsequent sections appear in one or more CrtMlFrtContinue records that follow this record. - * - * Legend --> Pos, TextDisp, Frame, CrtLayout12, TextPropsStream, CrtLayout12 - */ - - /** - * Adds chart records to the chartRecs array. This array should be completed with all chart records - * before the initChartRecords method is called, creating the hierarchical chart structure. - * - * @param br - a chart record - * @return false if we reach the final "end" record, signifying the end of the chart object structure. - *

                      - * Be aware that there can be additional chart records after this structure, Chart only deals with it's own heirarchy. - */ - public boolean addInitialChartRecord(BiffRec br) { - if (br.getOpcode() == 0x1033) { - ++hierarchyDepth; - } else if (br.getOpcode() == 0x1034) { - --hierarchyDepth; - if (hierarchyDepth == 0) { - chartRecs.add(br); - return false; - } - } - if (hierarchyDepth != 0) { - if (br.getOpcode() == AI) { - currentAi = (Ai) br; - currentAi.setParentChart(this); // necessary for base ops - currentAi.setSheet(this.getSheet()); // needed for cell change updates - } else if (currentAi != null && br.getOpcode() == SERIESTEXT) { - currentAi.setSeriesText((SeriesText) br); - currentAi = null; // done - } else if (br.getOpcode() == SERIES) { - chartseries.add(new Object[]{br, Integer.valueOf(nCharts)}); // store series records - initially map all to default chart (normal case) - } else if (br.getOpcode() == AXISPARENT) { - chartaxes = new ChartAxes((AxisParent) br); - } else if (br.getOpcode() == AXIS) { - chartaxes.add((Axis) br); - } else if (br.getOpcode() == CHARTFORMAT) { // usually only 1 (default chart id= 0) but can have up to 9 overlay charts - nCharts++; - } else if (br.getOpcode() == SERIESLIST) { // maps chart # to series # - chartseries.addSeriesMapping(nCharts - 1, ((SeriesList) br).getSeriesMappings());// 1-based series #'s to "assign" to the overlay chart - } - try { - if (((GenericChartObject) br).chartType != -1) { // store the chart object which defines this chart (NOTE: may be up to 4 charts in one = overlay charts) - ChartFormat cf = null; - for (int i = chartRecs.size() - 1; i >= 0; i--) { - if (((BiffRec) chartRecs.get(i)).getOpcode() == XLSConstants.CHARTFORMAT) { - cf = (ChartFormat) chartRecs.get(i); - break; - } - } -// sadly, can't do it here - have to wait until process hierarchy chartobj.add(ChartType.getChartObject((GenericChartObject)br, cf, this.getWorkBook())); - initobs.add(new BiffRec[]{br, cf}); - } // Set Parent Chart as it's necessary for all basic chart ops - ((GenericChartObject) br).setParentChart(this); // Note: Scl is not a GenericChartObject + can have unknown (XLSRecords) - will cause ClassCastException - } catch (ClassCastException c) { - } - chartRecs.add(br); - } else { - if (br.getOpcode() == DIMENSIONS) dimensions = (Dimensions) br; - br.getData(); - postRecs.add(br); - } - - return true; - } - - - public void setDirtyFlag(boolean b) { - dirtyflag = b; - } - - /** - * Take the initial array of records for the chart, and create a - * hierarchial array of objects for the chart. Also, populate initial values for easy access. - */ - public void initChartRecords() { - try { // Added to find/set obj + msodrawing records - replaces setObj - // see TestReadWrite.TestIOOBError - // For Normal charts, Obj rec is the last record before the Chart record - if (!this.getSheet().isChartOnlySheet()) { - int pos = this.getSheet().getSheetRecs().size() - 1; - BiffRec rec = (BiffRec) this.getSheet().getSheetRecs().get(pos); - obj = (Obj) rec; - obj.setChart(this); - // Usually, MsoDrawing is just preceding Obj record, except in those rare carses where - // there are Continues and Txo's ... - while (--pos > 0) { - rec = (BiffRec) this.getSheet().getSheetRecs().get(pos); - if (rec.getOpcode() == MSODRAWING) { - this.msodrawobj = (MSODrawing) rec; - break; - } - } - } // chart-only worksheets have no obj/mso apparently - } catch (Exception e) { - Logger.logErr("initChartRecords: Error in Chart Records: " + e.toString()); - } - - // Turn it into a static array initially to speed up random access - BiffRec[] bArr = new BiffRec[chartRecs.size()]; - bArr = (BiffRec[]) chartRecs.toArray(bArr); - this.initChartObject(this, bArr); - for (int i = 0; i < initobs.size(); i++) { - BiffRec[] ios = (BiffRec[]) initobs.get(i); - chartgroup.add(ChartType.createChartTypeObject((GenericChartObject) ios[0], (ChartFormat) ios[1], this.getWorkBook())); - Legend l = (Legend) Chart.findRec(((ChartFormat) ios[1]).chartArr, Legend.class); - if (l != null) - chartgroup.get(chartgroup.size() - 1).addLegend(l); - } - initobs = new ArrayList(); // clear out - } - - /** - * Handle the iteration and creation of the chart objects - */ - private ChartObject initChartObject(ChartObject cobj, BiffRec[] cRecs) { - - for (int i = 0; i < cRecs.length; i++) { - BiffRec b = cRecs[i]; - b.getData(); - if (!(b.getOpcode() == BEGIN) && !(b.getOpcode() == END)) { - if ((cRecs.length > i + 1) && ((cRecs[i + 1]).getOpcode() == BEGIN)) { // this is an object with sub-data - try { - ChartObject co = (ChartObject) b; - int endloc = this.getMatchingEndRecordLocation(i, cRecs); - int arrlen = endloc - i; - BiffRec[] objArr = new BiffRec[arrlen]; - System.arraycopy(cRecs, i + 1, objArr, 0, arrlen); - cobj.addChartRecord((XLSRecord) this.initChartObject(co, objArr)); - // necessary initialization of key elements - if (co instanceof TextDisp) { - int type = ((TextDisp) co).getType(); - // -1 = default text ...? - if (type == ObjectLink.TYPE_TITLE) - charttitle = (TextDisp) co; - else if (type == ObjectLink.TYPE_XAXIS) - chartaxes.setTd(XAXIS, (TextDisp) co); - else if (type == ObjectLink.TYPE_YAXIS) - chartaxes.setTd(YAXIS, (TextDisp) co); - else if (type == ObjectLink.TYPE_ZAXIS) - chartaxes.setTd(ZAXIS, (TextDisp) co); - else if (type == ObjectLink.TYPE_DATAPOINTS) // series or data points - ; // do what?? - else if (type == ObjectLink.TYPE_DISPLAYUNITS) // - // KSC: TESTING!! Take out when done -// Logger.logInfo("Display Units"); - ; - } - try { - co.setParentChart(this); - } catch (Exception e) { - } - i += arrlen; - } catch (ClassCastException e) { - // it's not a defined chart object. Add it in!!! If we are missing a chart object containing other records we - // will not be able to write these out correctly. - Logger.logWarn("Error in parsing chart. Please add the correct object (opcode: " + b.getOpcode() + ") to be a Chart Object"); - } - } else { - cobj.addChartRecord((XLSRecord) b); - } - } - } - return cobj; - } - - /** - * Just a little helper method! Determines where the matching end record is for a begin record - * - * @param startLoc - * @param cRecs - * @return - */ - private int getMatchingEndRecordLocation(int startLoc, BiffRec[] cRecs) { - int offset = 0; - for (int i = startLoc + 2; i < cRecs.length; i++) { - BiffRec b = cRecs[i]; - if (b.getOpcode() == BEGIN) offset++; - if (b.getOpcode() == END) { - offset--; - if (offset < 0) return i; - } - } - return -1; - } - - /** - * put together the chart records for output to the streamer - * - * @return arranged List of Chart Records. - */ - public List assembleChartRecords() { - Vector outputVec = new Vector(); - if (preRecs != null) outputVec.addAll(preRecs); - if (dirtyflag) { - for (int i = 0; i < nCharts; i++) { - chartseries.updateSeriesMappings(chartgroup.get(i).getSeriesList(), i); // if has multiple or overlay charts, update series mappings - } - outputVec.addAll(this.getRecordArray()); - } else { - outputVec.add(this); - outputVec.addAll(chartRecs); - } - if (postRecs != null) outputVec.addAll(postRecs); - if (!true) { - for (int i = 0; i < outputVec.size(); i++) { - XLSRecord rec = (XLSRecord) outputVec.get(i); - Logger.logInfo("rec:" + rec.toString() + ""); - Logger.logInfo(ByteTools.getByteDump(rec.getData(), 0)); - Logger.logInfo("-------------------------------------------------"); - } - } - return outputVec; - } - - /** - * This handles setting the records external to, but supporting the chart object hierarchy. - *

                      - * Think of this kind of like the recvec & cell records in the XLS parsing we do. - *

                      - * We have a pre and post array of biffrecs that are (for now) unmodifieable. - * These get appended into the whole array on ouput in append chart records. - * - * @param recs - */ - public void setPreRecords(AbstractList recs) { - preRecs = recs; - } - - /** - * Returns a map of series ==> series ptgs - * Ranges can either be in the format "C5" or "Sheet1!C4:D9" - */ - public HashMap getSeriesPtgs() { - return this.chartseries.getSeriesPtgs(); - } - - /** - * Returns a serialized copy of this Chart - */ - public byte[] getChartBytes() { - for (int i = 0; i < preRecs.size(); i++) { - ((BiffRec) preRecs.get(i)).getData(); - } - - ObjectOutputStream obs = null; - byte[] b = null; - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - obs = new ObjectOutputStream(baos); - obs.writeObject(this); - b = baos.toByteArray(); - } catch (IOException e) { - Logger.logErr(e); - } - return b; - } - - public byte[] getSerialBytes() { - for (int i = 0; i < preRecs.size(); i++) { - ((BiffRec) preRecs.get(i)).getData(); - } - ObjectOutputStream obs = null; - byte[] b = null; - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - BufferedOutputStream bufo = new BufferedOutputStream(baos); - obs = new ObjectOutputStream(bufo); - obs.writeObject(this); - bufo.flush(); - b = baos.toByteArray(); - } catch (IOException e) { - Logger.logErr(e); - } - return b; - } - - /** - * Gets the records for the chart, including the global references, ie the MSODrawing and Obj records - * - * @return - */ - public List getXLSrecs() { - List l = this.assembleChartRecords(); - if (obj != null) l.add(0, obj); - if (msodrawobj != null) l.add(0, msodrawobj); - return l; - } - - - /** - * Dimensions rec holds the datarange for the chart; must update ... - * - * @return - */ - public int getMinRow() { - return dimensions.getRowFirst(); - } - - public int getMaxRow() { - return dimensions.getRowLast(); - } - - public int getMinCol() { - return dimensions.getColFirst(); - } - - public int getMaxCol() { - return dimensions.getColLast(); - } - - public void setDimensionsRecord(int r0, int r1, int c0, int c1) { - dimensions.setRowFirst(r0 - 1); - dimensions.setRowLast(r1 - 1); - dimensions.setColFirst(c0); - dimensions.setColLast(c1 - 1); - /* also must remove label and numberrec cached records if altered series - otherwise causes errors when removing or altering series - */ - for (int i = postRecs.size() - 1; i > 0; i--) { - BiffRec b = (BiffRec) postRecs.get(i); - int op = b.getOpcode(); - if (op == NUMBER || op == LABEL) { - postRecs.remove(i); - } - } - } - - - public void setDimensions(Dimensions d) { - dimensions = d; - } - - public void setDimensionsRecord() { - Vector serieslist = this.getAllSeries(-1); - int nSeries = serieslist.size(); - int nPoints = 0; - for (int i = 0; i < serieslist.size(); i++) { - try { - Series s = (Series) serieslist.get(i); - int[] coords = ExcelTools.getRangeCoords(s.getSeriesValueAi().getDefinition()); - if (coords[3] > coords[1]) - nPoints = Math.max(nPoints, coords[3] - coords[1] + 1); // c1-c0 - else - nPoints = Math.max(nPoints, coords[2] - coords[0] + 1); // r1-r0 - } catch (Exception e) { - } - } - this.setDimensionsRecord(0, nPoints, 0, nSeries); - } - - /** - * returns the unique Obj Id # of this chart as seen in Vb macros et. al - * - * @return in id # - */ - public int getId() { - if (this.obj != null) - return this.obj.getObjId(); - return -1; - } - - /** - * sets the unique id # for this chart - * used upon addChart - * - * @param id - */ - public void setId(int id) { - if (this.obj != null) - this.obj.setObjId(id); - } - - /** - * flags chart metrics should - * to be recalculated - */ - public void setMetricsDirty() { - dirtyflag = true; - metricsDirty = true; - } - - /** - * Change series ranges for ALL matching series - * - * @param originalrange - * @param newrange - * @return - */ - public boolean changeSeriesRange(String originalrange, String newrange) { - setMetricsDirty(); - return chartseries.changeSeriesRange(originalrange, newrange); - } - - /** - * Return a string representing all series in this chart - * - * @param nChart 0=default, 1-9= overlay charts -1 for ALL charts - * @return - */ - public String[] getSeries(int nChart) { - return chartseries.getSeries(nChart); - } - - /** - * Return an array of strings, one for each category - * - * @param nChart 0=default, 1-9= overlay charts -1 for All - * @return - */ - public String[] getCategories(int nChart) { - return chartseries.getCategories(nChart); - } - - /** - * get all the series objects for ALL charts - * (i.e. even in overlay charts) - * - * @return - */ - public Vector getAllSeries() { - return chartseries.getAllSeries(-1); - } - - /** - * get all the series objects in the specified chart (-1 for ALL) - * - * @param nChart 0=default, 1-9= overlay charts -1 for ALL series - * @return - */ - public Vector getAllSeries(int nChart) { - return chartseries.getAllSeries(nChart); - } - - /** - * Add a series object to the array. - * - * @param seriesRange = one row range, expressed as (Sheet1!A1:A12); - * @param categoryRange = category range - * @param bubbleRange= bubble range, if any 20070731 KSC - * @param seriesText = label for the series; - * @param nChart 0=default, 1-9= overlay charts - * s * @return - */ - public Series addSeries(String seriesRange, String categoryRange, String bubbleRange, String legendRange, String legendText, int nChart) { - Series s = chartseries.addSeries(seriesRange, categoryRange, bubbleRange, legendRange, legendText, chartgroup.get(nChart), nChart); - setMetricsDirty(); - return s; - } - - - /** - * return an array of legend text - * - * @param nChart 0=default, 1-9= overlay charts -1 for ALL - */ - public String[] getLegends(int nChart) { - return chartseries.getLegends(nChart); - } - - /** - * specialty method to take absoulte index of series and remove it - *
                      only used in WorkSheetHandle - * - * @param index - */ - public void removeSeries(int index) { - setMetricsDirty(); - chartseries.removeSeries(index); - } - - /** - * remove desired series from chart - * - * @param index - */ - public void removeSeries(Series seriestodelete) { - // remove from cache - setMetricsDirty(); - chartseries.removeSeries(seriestodelete); - // remove from chartArray - int nSeries = -1; - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == SERIES) { - nSeries++; - if (seriestodelete.equals(b)) { // this is the one to delete - chartArr.remove(i); - // now adjust series number for all subsequent Series - for (int j = i; j < chartArr.size(); j++) { - b = chartArr.get(j); - if (b.getOpcode() == SERIES) { - Series s = (Series) b; - int x = Chart.findRecPosition(s.chartArr, DataFormat.class); - if (x > 0) { - DataFormat df = (DataFormat) s.chartArr.get(x); - df.setSeriesIndex(nSeries++); - } - } else // we've got 'em all - break; - } - // now make sure referenced label and number recs are removed - /** how does this work, it does not reference a particular series label, just all of them? - for (int j= 0; j < postRecs.size(); j++) { - if ((postRecs.get(j) instanceof NumberRec) || - (postRecs.get(j) instanceof Label)) { - postRecs.remove(j); - j--; - } - - } - */ - break; - } - } - } - } - - /** - * get a chart series handle based off the series name - * - * @param seriesName - * @param nChart 0=default, 1-9= overlay charts - * @return - */ - public Series getSeries(String seriesName, int nChart) { - return chartseries.getSeries(seriesName, nChart); - } - - /** - * @param originalrange - * @param newrange - * @return - */ - public boolean changeCategoryRange(String originalrange, String newrange) { - setMetricsDirty(); - return chartseries.changeCategoryRange(originalrange, newrange); - } - - - public boolean changeTextValue(String originalval, String newval) { - setMetricsDirty(); - return chartseries.changeTextValue(originalval, newval); - } - - /** - * returns the plot area background color string (hex) - * - * @return color string - */ - public String getPlotAreaBgColor() { - String bg = null; - try { - Frame f = (Frame) Chart.findRec(this.chartArr, Frame.class); - bg = f.getBgColor(); - } catch (Exception e) { - } - int ct = this.getChartType(); - if (bg == null || (ct != PIECHART && ct != RADARCHART)) { - bg = chartaxes.getPlotAreaBgColor(); - } - if (bg == null) - bg = "#FFFFFF"; - return bg; - } - - /** - * sets the plot area background color - * - * @param bg color int - */ - public void setPlotAreaBgColor(int bg) { - this.chartaxes.setPlotAreaBgColor(bg); - setMetricsDirty(); - } - - - /** - * returns the plot area line color string (hex) - * - * @return color string - */ - public String getPlotAreaLnColor() { - String clr = "#000000"; - try { - Frame f = (Frame) Chart.findRec(this.chartArr, Frame.class); - clr = f.getLineColor(); - } catch (Exception e) { - } - return clr; - } - - /** - * Get the title of the Chart - * - * @return title of the Chart - */ - public String getTitle() { - if (charttitle != null) - return charttitle.toString(); - return ""; - // getTitleTD(); - } - - /** - * return the title TextDisp element - * - * @return - */ - public TextDisp getTitleTd() { - return charttitle; - } - - /** - * return the textdisp object that defines the chart title - * - * @return public TextDisp getTitleTD(){ - * if(title != null){ - * return title; - * } - * for (int i=0;i -1) - return wkbook.getFont(idx); - return null; - } - } - } - return null; - } - - /** - * Set the default font for the specific DefaultText rec - * - * @param type - * @param fontId - */ - public void setDefaultFont(int type, int fontId) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == TEXTDISP) { - //xxxxx - } - } - dirtyflag = true; - } - - /** - * Get all of the Fontx records in this chart. Utilized for - * updating font references when moving between workbooks - */ - public ArrayList getFontxRecs() { - ArrayList ret = new ArrayList(); - for (int i = 0; i < chartRecs.size(); i++) { - BiffRec b = (BiffRec) chartRecs.get(i); - if (b.getOpcode() == FONTX) { - ret.add(b); - } - } - return ret; - } - - /** - * resets all fonts in the chart to the default font of the workbook Useful - * for OOXML charts which are based upon default chart - */ - public void resetFonts() { - for (int i = 0; i < chartRecs.size(); i++) { - BiffRec b = (BiffRec) chartRecs.get(i); - if (b.getOpcode() == FONTX) { - ((Fontx) b).setIfnt(0); - } - } - } - - - /** - * return the font associated with the chart title - * - * @return Font - */ - public Font getTitleFont() { - if (charttitle == null) return null; - return charttitle.getFont(wkbook); - } - - /** - * Utilized by external chart copy, this - * sets references internally in this chart which allows cross-workbook - * identification of worksheets - * to occur. - */ - public void populateForTransfer() { - Iterator i = this.getAllSeries(-1).iterator(); - while (i.hasNext()) { - Series s = (Series) i.next(); - try { - for (int j = 0; j < s.chartArr.size(); j++) { - if (((BiffRec) s.chartArr.get(j)).getOpcode() == AI) { - ((Ai) s.chartArr.get(j)).populateForTransfer(this.getSheet().getSheetName()); - } - } - } catch (Exception e) { - Logger.logErr("Chart.populateForTransfer: " + e.toString()); - } - } - - } - - /** - * update Sheet References contained in associated Series/Category Ai's - * using saved Original Sheet References - */ - public void updateSheetRefs(String newSheetName, String origWorkBook) { - Iterator i = this.getAllSeries(-1).iterator(); - while (i.hasNext()) { - Series s = (Series) i.next(); - try { - for (int j = 0; j < s.chartArr.size(); j++) { - if (((BiffRec) s.chartArr.get(j)).getOpcode() == AI) { - ((Ai) s.chartArr.get(j)).updateSheetRef(newSheetName, origWorkBook); // 20080630 KSC: add sheet name for lookup - } - } - } catch (Exception e) { - Logger.logErr("Chart.updateSheetRefs: " + e.toString()); - } - } - } - - /** - * Set the title of the Chart - * - * @param str title of the Chart - */ - public void setTitle(String str) { - // No need to create a TD record if setting title to "" - if ((str == null || str.equals("")) && charttitle == null) // if no title and setting title to "", just leave it - return; - if (charttitle == null) { // 20070709 KSC: Adding a new chart, add Title recs ... - try { - TextDisp td = (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_TITLE, str, this.getWorkBook()); - this.addChartRecord(td); // add TextDisp title to end of chart recs ... - charttitle = td; - - } catch (Exception e) { - Logger.logWarn("Unable to set title of chart to: " + str + " This chart object does not contain a title record"); - } - } else { // just set title text - charttitle.setText(str); - } - setMetricsDirty(); - } - - /** - * returns the chart type for the default chart - */ - public int getChartType() { - return chartgroup.get(0).getChartType(); - } - - /** - * returns the chart type for the desired chart - * - * @param nChart 0=default, 1-9= overlay charts - * @return - */ - public int getChartType(int nChart) { - return chartgroup.get(nChart).getChartType(); - } - - /** - * returns an array of all chart types found in this chart - * 0= default, 1-9 (max) are overlay charts - * - * @return - */ - public int[] getAllChartTypes() { - int[] charttypes = new int[nCharts]; // max - for (int i = 0; i < nCharts; i++) { - charttypes[i] = chartgroup.get(i).getChartType(); - } - return charttypes; - } - - /** - * returns the number of charts in this chart - - * 1= default, can have up to 9 overlay charts as well - * - * @return - */ - public int getNumberOfCharts() { - return nCharts - 1; - } - - /** - * return the default Chart Object associated with this chart - * - * @return - */ - public ChartType getChartObject() { - return chartgroup.get(0); - } - - /** - * return the nth Chart Object associated with this chart (default=0, overlay=1 thru 9) - * - * @return - */ - public ChartType getChartObject(int nChart) { - return chartgroup.get(nChart); - } - - - /** - * gets the integral order of the specific chart obect in question - * - * @param ct Chart Type rwobject - * @return - */ - public int getChartOrder(ChartType ct) { - for (int i = 0; i < chartgroup.size(); i++) { - if (chartgroup.get(i).equals(ct)) - return i; - } - return 0; - } - - /** - * create internal records necessary for an overlay chart - */ - private ChartFormat createNewChart(int nChart) { - AxisParent ap = (AxisParent) findRec(chartArr, AxisParent.class); - return ap.createNewChart(nChart); - } - - - /** - * adds a new Chart Type to the group of Chart Type Objects, or replaces one at existing index - * - * @param ct Chart Type Object - * @param nChart index - */ - protected void addChartType(ChartType ct, int nChart) { - if (nChart < chartgroup.size()) - chartgroup.remove(nChart); - else - nCharts++; - chartgroup.add(nChart, ct); - dirtyflag = true; - } - - /** - * retrieves the parent of the chart type object (==ChartFormat) at index nChart, or creates a new Chart Type Parent - * - * @param nChart - * @return ChartFormat - */ - protected ChartFormat getChartOjectParent(int nChart) { - ChartFormat cf = null; - if (nChart >= chartgroup.size()) // create new - cf = this.createNewChart(nChart); - else - cf = chartgroup.get(nChart).cf; - return cf; - } - - /** - * changes this chart to be a specific chart type with specific display options - *
                      for multiple charts, specify nChart 1-9, for the default chart, - * nChart= 0 - * - * @param chartType chart type one of: BARCHART, LINECHART, AREACHART, COLCHART, PIECHART, - * DONUGHTCHART, RADARCHART, RADARAREACHART, PYRAMIDCHART, CONECHART, CYLINDERCHART, - * SURFACTCHART - * @param nChart chart # 0 is default - * @param EnumSet 0 or more chart options (Such as Stacked, Exploded ...) - * @see ChartOptions - */ - public void setChartType(int chartType, int nChart, EnumSet options) { - GenericChartObject c; - - ChartFormat cf = getChartOjectParent(nChart); - ChartType ct = ChartType.create(chartType, this, cf); - ct.setOptions(options); - - // save and reset legend: - Legend l = this.getLegend(); - ct.addLegend(l); - - if (ct instanceof BubbleChart && options.contains(ChartOptions.THREED)) - ((BubbleChart) ct).setIs3d(true); // when set, every series created will - - addChartType(ct, nChart); - - -/* TODO: axes, other chart options ... Legend??? - // The axis group MUST contain two value axes if and only if all chart groups are of type bubble or scatter. - } else if (chartType==BUBBLECHART || chartType==SCATTERCHART) { -// TODO: FINISH!!! IS NOT CORRECT UNLESS PROPER AXES ARE CREATED - //The axis group MUST contain a category or date axis if the axis group contains an area, bar, column, filled radar, line, radar, or surface chart group - } else { // ensure has proper axes -// TODO: FINISH!!! IS NOT CORRECT UNLESS PROPER AXES ARE CREATED - } -*/ - /** - The axis group MUST contain a series axis if and only if the chart group attached to the axis group is one of the following: - An area chart group with the fStacked field of the Area record equal to 0. - A column chart group with the fStacked field of the Bar record equal to 0 and the fClustered field of the Chart3d record equal to 0. - A line chart group with field fStacked of the Line record equal to 0. - A surface chart group - The chart group on the axis group MUST contain a Chart3d record if the axis group contains a series axis. - */ - /** - * for overlay charts i.e. multiple charts, restrictions: - * Because there are many different ways to represent data visually, each representation has specific requirements about the layout of the data and the way it is plotted. - * This results in restrictions on the combinations of chart group types that can be plotted on the same axis group, and the combinations of chart group types that can - be plotted in the same chart. - A chart MUST contain one of the following: - A single axis group that contains a single chart group that contains a Chart3d record. - One or two axis groups that each contain a single bubble chart group. - One or two axis groups that each conform to one of the following restrictions on chart group type combinations: - Zero or one of each of the following chart group types: area, column, line, and scatter. - Zero or one of each of the following chart group types: bar of pie, doughnut, pie, and pie of pie. - A single bar chart group. - A single filled radar chart group. - A single radar chart group. - - */ - } - - /** - * @return truth of "Chart is Three-D" - */ - public boolean isThreeD() { - return isThreeD(0); - } - - /** - * @param nChart 0=default, 1-9= overlay charts - * @return truth of "Chart is Three-D" - */ - public boolean isThreeD(int nChart) { - return chartgroup.get(nChart).isThreeD(); - } - - /** - * return the 3d rec for this chart or null if it doesn't exist - * - * @param nChart 0=default, 1-9= overlay charts - * @return - */ - public ThreeD getThreeDRec(int nChart) { - return chartgroup.get(nChart).getThreeDRec(false); - } - - /** - * @return truth of "Chart is Stacked" - */ - public boolean isStacked() { - return isStacked(0); - } - - /** - * @param nChart 0=default, 1-9= overlay charts - * @return truth of "Chart is Stacked" - */ - public boolean isStacked(int nChart) { - return chartgroup.get(nChart).isStacked(); - } - - /** - * return truth of "Chart is 100% Stacked" - * - * @return - */ - public boolean is100PercentStacked() { - return is100PercentStacked(0); - } - - /** - * return truth of "Chart is 100% Stacked" - * - * @param nChart 0=default, 1-9= overlay charts - * @return - */ - public boolean is100PercentStacked(int nChart) { - return chartgroup.get(nChart).is100PercentStacked(); - } - - /** - * @return truth of "Chart is Clustered" (Bar/Col only) - */ - public boolean isClustered() { - return isClustered(0); - } - - /** - * @param nChart 0=default, 1-9= overlay charts - * @return truth of "Chart is Clustered" (Bar/Col only) - */ - public boolean isClustered(int nChart) { - return chartgroup.get(nChart).isClustered(); - } - - /** - * return chart-type-specific options in XML form - * - * @param nChart 0=default, 1-9= overlay charts - * @return String XML - */ - public String getChartOptionsXML(int nChart) { - return chartgroup.get(nChart).getChartOptionsXML(); - } - - /** - * interface for setting chart-type-specific options - * in a generic fashion - * - * @param op option - * @param val value - * @see OpenXLS.handleChartElement - * @see ChartHandle.getXML - */ - public boolean setChartOption(String op, String val) { - dirtyflag = true; - return this.setChartOption(op, val, 0); - } - - /** - * interface for setting chart-type-specific options - * in a generic fashion - * - * @param op option - * @param val value - * @param nChart 0=default, 1-9= overlay charts - * @see OpenXLS.handleChartElement - * @see ChartHandle.getXML - */ - public boolean setChartOption(String op, String val, int nChart) { - dirtyflag = true; - return chartgroup.get(nChart).setChartOption(op, val); - } - - /** - * get the value of *almost* any chart option (axis options are in Axis) - * for the default chart - * - * @param op String option e.g. Shadow or Percentage - * @return String value of option - */ - public String getChartOption(String op) { - return chartgroup.get(0).getChartOption(op); - } - - /** - * return ThreeD settings for this chart in XML form - * - * @return String XML - */ - public String getThreeDXML() { - return getThreeDXML(0); - } - - /** - * return ThreeD settings for this chart in XML form - * - * @param nChart 0=default, 1-9= overlay charts - * @return String XML - */ - public String getThreeDXML(int nChart) { - return chartgroup.get(nChart).getThreeDXML(); - } - - /** - * returns the 3d record of the desired chart - * - * @param nChart 0=default, 1-9= overlay charts - * @return - */ - public ThreeD initThreeD() { - return initThreeD(0, this.getChartType(0)); - } - - /** - * returns the 3d record of the desired chart - * - * @param nChart 0=default, 1-9= overlay charts - * @param chartType one of the chart type constants - * @return - */ - public ThreeD initThreeD(int nChart, int chartType) { - return chartgroup.get(nChart).initThreeD(chartType); - } - - /** - * Return the Chart Axes object - * - * @return ChartAxis - */ - public ChartAxes getAxes() { - return this.chartaxes; - } - - /** - * return the Chart Series object - * - * @return - */ - public ChartSeries getChartSeries() { - return chartseries; - } - - /** - * Returns true if the Y Axis (Value axis) is set to automatic scale - *

                      The default setting for charts is known as Automatic Scaling - *
                      When data changes, the chart automatically adjusts the scale (minimum, maximum values - * plus major and minor tick units) as necessary - * - * @return boolean true if Automatic Scaling is turned on - * @see getAxisAutomaticScale(int axisType) - */ - public boolean getAxisAutomaticScale() { - return this.chartaxes.getAxisAutomaticScale(); - } - - /** - * returns the minimum and maximum values by examining all series on the chart - * - * @param bk - * @return double[] min, max - */ - public double[] getMinMax(WorkBookHandle wbh) { - if (metricsDirty) - getMetrics(wbh); - if (this.wbh == null) - this.wbh = wbh; - metricsDirty = false; - double[] minmaxcache = chartseries.getMetrics(metricsDirty); // Ignore Overlay charts for now! - return minmaxcache; - } - - /** - * Return all the Chart-specific font recs in XML form - * These include the default fonts + title font + axis fonts ... - * - * @return - */ - public String getChartFontRecsXML() { - HashMap fonts = new HashMap(); - int maxFont = 5; - for (int i = 0; i < chartRecs.size(); i++) { - BiffRec b = (BiffRec) chartRecs.get(i); - if (b.getOpcode() == FONTX) { - int fontId = ((Fontx) b).getIfnt(); //((TextDisp) b).getFontId(); - maxFont = Math.max(fontId, maxFont); - Font f = this.getWorkBook().getFont(fontId); - fonts.put(Integer.valueOf(fontId), f.getXML()); - } - } - StringBuffer sb = new StringBuffer(); - for (int i = 5; i <= maxFont; i++) { - if (fonts.get(Integer.valueOf(i)) != null) { - sb.append("\n\t\t"); - } - } - return sb.toString(); - } - - /** - * return the fontid for non-axis chart fonts (title, default ...) - * - * @return - */ - public String getChartFontsXML() { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == DEFAULTTEXT) { - sb.append(" Default" + ((DefaultText) b).getType() + "=\""); - i++; - b = chartArr.get(i); - if (b.getOpcode() == TEXTDISP) { // should!! - TextDisp td = (TextDisp) b; - sb.append(td.getFontId()); - } - sb.append("\""); - } else if (b.getOpcode() == TEXTDISP) { - TextDisp td = (TextDisp) b; - if (td.isChartTitle()) { // should!! - sb.append(" Title=\""); - sb.append(td.getFontId()); - sb.append("\""); - } - } - } - return sb.toString(); - } - - /** - * Set the specific chart font (title, default ...) - * NOTE: Axis fonts are handled separately - * - * @param type - * @param val - * @see Axis.setChartOption, TextDisp.setChartOption - */ - public void setChartFont(String type, String val) { - if (type.equalsIgnoreCase("Title")) - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == TEXTDISP) { // should be the title - TextDisp td = (TextDisp) b; - if (td.isChartTitle()) { // should!! - td.setFontId(Integer.parseInt(val)); - break; - } - } - } - else if (type.indexOf("Default") > -1) { - type = type.substring(7); - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == DEFAULTTEXT) { - if (((DefaultText) b).getType() == Integer.parseInt(type)) { - i++; - b = chartArr.get(i); - if (b.getOpcode() == TEXTDISP) { // should be!! - TextDisp td = (TextDisp) b; - td.setFontId(Integer.parseInt(val)); - break; - } - } - } - } - } - setMetricsDirty(); - } - - - /** - * set the fontId for the chart title rec - * - * @param fontId - */ - public void setTitleFont(int fontId) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == TEXTDISP) { - TextDisp td = (TextDisp) b; - if (td.isChartTitle()) { - td.setFontId(fontId); - } - } - } - setMetricsDirty(); - } - - - // 20070802 KSC: debugging utility to write out chart recs - public void writeChartRecs(String fName) { - try { - java.io.File f = new java.io.File(fName); - BufferedWriter writer = new BufferedWriter(new FileWriter(f, true)); - int ctr = 0; - if (preRecs != null) - ctr = ByteStreamer.writeRecs(new ArrayList(preRecs), writer, ctr, 0); - ctr = ByteStreamer.writeRecs(chartArr, writer, ctr, 0); // will recurse - writer.flush(); - writer.close(); - } catch (Exception e) { - } - } - - /** - * set the sheet for this chart plus its subrecords as well - */ - public void setSheet(Sheet b) { - super.setSheet(b); - if (this.msodrawobj != null) - this.msodrawobj.setSheet(b); - for (int i = 0; i < chartArr.size(); i++) { - chartArr.get(i).setSheet(b); - } - } - - - /** - * return int[4] representing the coordinates (L, T, W, H) of this chart in pixels - * - * @return int[4] bounds - */ - public short[] getCoords() { - short[] coords = {20, 20, 100, 100}; - if (this.msodrawobj != null) - coords = this.msodrawobj.getCoords(); - else - Logger.logWarn("Chart missing Msodrawing record. Chart has no coordinates."); - if (!metricsDirty) { // use adjusted values for w and h - coords[2] = chartMetrics.get("canvasw").shortValue(); - coords[3] = chartMetrics.get("canvash").shortValue(); - } - return coords; - } - - /** - * return the coordinates of the outer plot area (plot + axes + labels) in pixels - * - * @return - */ - public float[] getPlotAreaCoords(float w, float h) { - return this.chartaxes.getPlotAreaCoords(w, h); - } - - /** - * set the coordinates of ths chart in pixels - * - * @param coords int[4]: L, T, W, H - */ - public void setCoords(short[] coords) { - if (this.msodrawobj != null) { - msodrawobj.setCoords(coords); - setMetricsDirty(); // size is an element in minmax ... - } else // should NEVER happen - Logger.logWarn("Chart missing coordinates."); - - } - - /** - * return bounds relative to row/cols including their respective offsets - * - * @return - */ - public short[] getBounds() { - if (this.msodrawobj != null) - return msodrawobj.getBounds(); - return null; - } - - /** - * returns the offset within the column in pixels - * - * @return - */ - public short getColOffset() { - if (this.msodrawobj != null) - return msodrawobj.getColOffset(); - return 0; - } - - /** - * set the bounds relative to row/col and their offsets into them - * - * @param bounds short[] - */ - public void setBounds(short[] bounds) { - if (this.msodrawobj != null) - msodrawobj.setBounds(bounds); - setMetricsDirty(); - } - - /** - * return the top row of the chart - */ - public int getRow0() { - if (this.msodrawobj != null) - return msodrawobj.getRow0(); - return -1; - } - - /** - * set the top row for this chart - * - * @param r - */ - public void setRow(int r) { - if (this.msodrawobj != null) - msodrawobj.setRow(r); - setMetricsDirty(); - } - - /** - * return the left col of the chart - */ - public int getCol0() { - if (this.msodrawobj != null) - return msodrawobj.getCol(); - return -1; - } - - /** - * return the height of this chart - */ - public int getHeight() { - if (this.msodrawobj != null) - return msodrawobj.getHeight(); - return -1; - } - - /** - * set the height of this chart - * - * @param h - */ - public void setHeight(int h) { - if (this.msodrawobj != null) - msodrawobj.setHeight(h); - setMetricsDirty(); - } - - /** - * retrieve the current Series JSON for comparisons - * - * @return JSONArray - */ - public JSONArray getSeriesJSON() { - return chartseries.getSeriesJSON(); - } - - /** - * set the current Series JSON - * - * @param s JSONArray - */ - public void setSeriesJSON(JSONArray s) - throws JSONException { - chartseries.setSeriesJSON(s); - dirtyflag = true; - } - - /** - * Show or remove Data Table for Chart - * NOTE: METHOD IS STILL EXPERIMENTAL - * - * @param bShow - */ - public void showDataTable(boolean bShow) { -// TODO: FINISH chartobj[0].showDataTable(bShow); - } - - /** - * show or hide chart legend key - * - * @param bShow boolean show or hide - * @param vertical boolean show as vertical or horizontal - */ - public void showLegend(boolean bShow, boolean vertical) { - chartgroup.get(0).showLegend(bShow, vertical); - } - - /** - * remove the legend from the chart - */ - public void removeLegend() { - showLegend(false, false); - } - - /** - * return the Data Legend for this chart, if any - * - * @return - */ - public Legend getLegend() { - return chartgroup.get(0).getDataLegend(); - } - - /** - * return the default data label setting for the chart, if any - *
                      NOTE: each series can override the default data label fo the chart - *
                      can be one or more of: - *
                      VALUELABEL= 0x1; - *
                      VALUEPERCENT= 0x2; - *
                      CATEGORYPERCENT= 0x4; - *
                      CATEGORYLABEL= 0x10; - *
                      BUBBLELABEL= 0x20; - *
                      SERIESLABEL= 0x40; - * - * @return int default data label for chart - */ - protected int getDataLabel() { - return chartgroup.get(0).getDataLabel(); - - } - - /** - * return data label options for each series as an int array - *
                      each can be one or more of: - *
                      VALUELABEL= 0x1; - *
                      VALUEPERCENT= 0x2; - *
                      CATEGORYPERCENT= 0x4; - *
                      CATEGORYLABEL= 0x10; - *
                      BUBBLELABEL= 0x20; - *
                      SERIESLABEL= 0x40; - * - * @return int array - * @see AttachedLabel - */ - protected int[] getDataLabelsPerSeries(int defaultDL) { - /* NOTES: * - * A data label is a label on a chart that is associated with a data point, or associated with a series on an area or filled radar chart group. - * A data label contains information about the associated data point, such as the description of the data point, a legend key, or custom text. - * - * Inheritance - * For any given data point, there is an order of inheritance that determines the contents of a data label associated with the data point: - Data labels can be specified for a chart group, specifying the default setting for the data labels associated with the data points on the chart group . - Data labels can be specified for a series, specifying the default setting for the data labels associated with the data points of the series. - This type of data label overrides the data label properties specified on the chart group for the data labels associated with the data points in a given series. - Data labels can be specified for a data point, specifying the settings for a data label associated with a particular data point. - This type of data label overrides the data label properties specified on the chart group and series for the data labels associated with a given data point. - - * If formatting is not specified for an individual data point, the data point inherits the formatting of the series. - * If formatting is not specified for the series, the series inherits the formatting of the chart group that contains the series. - * The yi field of the DataFormat record MUST specify the zero-based index of the Series record associated with this series in the - * collection of all Series records in the current chart sheet substream that contains the series. - */ - return chartseries.getDataLabelsPerSeries(defaultDL, this.getChartType()); - } - - /** - * return an array of the type of markers for each series: - *
                      0 = no marker - *
                      1 = square - *
                      2 = diamond - *
                      3 = triangle - *
                      4 = X - *
                      5 = star - *
                      6 = Dow-Jones - *
                      7 = standard deviation - *
                      8 = circle - *
                      9 = plus sign - **/ - protected int[] getMarkerFormats(int nChart) { - int[] mf = chartseries.getMarkerFormats(); - for (int marker : mf) { - if (marker != 0) - return mf; - } - // see if chart format - return chartgroup.get(nChart).getMarkerFormats(); - } - - /** - * returns true if this chart has data markers (line, scatter and radar charts only) - * - * @return - */ - public boolean hasMarkers(int nChart) { - int[] markers = this.getMarkerFormats(nChart); - for (int marker : markers) { - if (marker != 0) - return true; - } - return false; - - } - - /** - * return truth if Chart has a data legend key showing - * - * @return - */ - public boolean hasDataLegend() { - return (chartgroup.get(0).getDataLegend() != null); - } - - public MSODrawing getMsodrawobj() { - return msodrawobj; - } - - public Obj getObj() { - return obj; - } - - /** - * sets bar colors to vary or not - * - * @param vary - * @param nChart - */ - public void setVaryColor(boolean vary, int nChart) { - this.getChartObject(nChart).cf.setVaryColor(vary); - } - - /** - * generic method to find a specific record in the list of recs in chartarr - * - * @param chartArr - * @param c class of record to find - * @return biffrec or null - */ - public static BiffRec findRec(ArrayList chartArr, Class c) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec) chartArr.get(i); - if (b.getClass() == c) - return b; - } - return null; - } - - /** - * generic method to find a specific record in the list of recs in chartArr - * - * @param c class of record to find - * @return position of record - */ - public static int findRecPosition(ArrayList chartArr, Class c) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = (BiffRec) chartArr.get(i); - if (b.getClass() == c) - return i; - } - return -1; - } - - // TODO: LATER - // **** INCLUDE cell selection in series bars ... - // chart.getSeries vs chart.getSeries(int) -- rename!!! - // chart axes need a dirty flag to rebuild metrics - // Axis.getSVG ==> passed categories -- should be passed chartseries instead?? - // merge minmaxcache into chartMetrics - // metricsDirty - ensure all ops are covered ... - // clean up Axes.getSVG w.r.t. label font, etc. - public HashMap getMetrics(WorkBookHandle wbh) { - if (metricsDirty) { - try { - this.wbh = wbh; - //chartseries.setWorkBook(wbh); - double[] minmax = chartseries.getMetrics(metricsDirty); // Ignore Overlay charts for now! - short[] coords = this.getCoords(); - chartMetrics.put("x", new Double(coords[0])); - chartMetrics.put("y", new Double(coords[1])); - chartMetrics.put("w", new Double(coords[2])); - chartMetrics.put("h", new Double(coords[3])); - chartMetrics.put("canvasw", new Double(coords[2])); - chartMetrics.put("canvash", new Double(coords[3])); - chartMetrics.put("min", minmax[0]); - chartMetrics.put("max", minmax[1]); - float[] plotcoords = null; - plotcoords = this.getPlotAreaCoords(chartMetrics.get("w").floatValue(), chartMetrics.get("h").floatValue()); - if (plotcoords == null) { - CrtLayout12A crt = (CrtLayout12A) Chart.findRec(this.chartArr, CrtLayout12A.class); - if (crt != null) - plotcoords = crt.getInnerPlotCoords(chartMetrics.get("w").floatValue(), chartMetrics.get("h").floatValue()); - } - - chartMetrics.put("x", new Double(plotcoords[0])); - chartMetrics.put("y", new Double(plotcoords[1])); - chartMetrics.put("w", new Double(plotcoords[2])); - chartMetrics.put("h", new Double(plotcoords[3])); - // Chart title offset - io.starter.formats.XLS.Font titlefont = this.getTitleFont(); - if (titlefont != null && !this.getTitle().equals("")) { // apparently can still have td even when no title is present ... - float[] tdcoords = this.charttitle.getCoords(); - double fh = titlefont.getFontHeightInPoints(); - if (tdcoords[1] == 0) { - chartMetrics.put("TITLEOFFSET", Math.ceil(fh * 1.5)); // with padding - } else { - chartMetrics.put("TITLEOFFSET", fh); // a little padding - } - } else if (chartMetrics.get("y") < 5.0) - chartMetrics.put("TITLEOFFSET", 10.0); // no title offset - a little padding - else - chartMetrics.put("TITLEOFFSET", 0.0); // no title offset and no need for padding - this.getAxes().getMetrics(this.getChartType(), chartMetrics, plotcoords, this.getChartSeries().getCategories()); - int[] lcoords = null; - double adjust = 10; - if (this.getLegend() != null) { - this.getLegend().getMetrics(chartMetrics, this.getChartType(), this.getChartSeries()); - lcoords = this.getLegend().getCoords(); - if (lcoords != null) - // TODO: legend adjustment may have to do with y title and label ofsets ...? - { - adjust = 2 * lcoords[4]; // spacing before and after legend box TODO this isn't correct !! -//KSC: TESTING! -//io.starter.toolkit.Logger.log("Original lcoords: " + Arrays.toString(lcoords)); - } else { - lcoords = new int[6]; - lcoords[0] = chartMetrics.get("canvasw").intValue(); - } - } else { - lcoords = new int[6]; - lcoords[0] = chartMetrics.get("canvasw").intValue(); - } - double ldist = lcoords[0] - chartMetrics.get("w"); // save distance between legend box and w (significant if legend is on rhs) -//io.starter.toolkit.Logger.log("Before Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); - // now adjust plot area coordinates based on canvas w, h, title and label offsets, and legend box, if any - chartMetrics.put("x", chartMetrics.get("x") + (Double) this.getAxes().axisMetrics.get("YAXISLABELOFFSET") + (Double) this.getAxes().axisMetrics.get("YAXISTITLEOFFSET")); - chartMetrics.put("y", chartMetrics.get("y") + chartMetrics.get("TITLEOFFSET")); - // TODO: seems that w is different doesn't need decrementing by x?? check out ... - chartMetrics.put("w", chartMetrics.get("w") - (Double) this.getAxes().axisMetrics.get("YAXISLABELOFFSET")); - chartMetrics.put("h", chartMetrics.get("canvash") - chartMetrics.get("y") - (Double) this.getAxes().axisMetrics.get("XAXISLABELOFFSET") - (Double) this.getAxes().axisMetrics.get("XAXISTITLEOFFSET") - 10); -//io.starter.toolkit.Logger.log("After Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); - - double cw = chartMetrics.get("canvasw"); - // rhs legend has to have some extra adjustments to w and/or canvasw ... - if (lcoords[5] == Legend.RIGHT) { - double legendBeg = lcoords[0] - (chartMetrics.get("w") + chartMetrics.get("x")); - double legendEnd = cw - (lcoords[0] + lcoords[2] + adjust); - - if (legendBeg < 0 || legendEnd < 0) { // try to adjust - if (legendEnd < 0) { - chartMetrics.put("canvasw", (lcoords[0] + lcoords[2] + adjust)); - cw = (lcoords[0] + lcoords[2] + adjust); - } -// if (legendBeg < 0) -// chartMetrics.put("w", lcoords[0]-10.0-chartMetrics.get("x")); - } - if (this.getAxes().hasAxis(XAXIS) && ldist > 0) // pie, donut, don't - // ensure distance between legend box and edge of plot area remains the same - chartMetrics.put("w", lcoords[0] - chartMetrics.get("x") - ldist); -//io.starter.toolkit.Logger.log("Adjusted LCoords: " + Arrays.toString(lcoords)); - - } else { - double w = chartMetrics.get("w") + chartMetrics.get("x"); - if (w > cw) - chartMetrics.put("w", chartMetrics.get("canvasw") - chartMetrics.get("x") - 10); - } - - metricsDirty = false; - } catch (Exception e) { - Logger.logErr("Chart.getMetrics: " + e.toString()); - } - } - return chartMetrics; - } -} - diff --git a/src/main/java/io/starter/formats/XLS/charts/Chart.kt b/src/main/java/io/starter/formats/XLS/charts/Chart.kt new file mode 100644 index 0000000..43cd14a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Chart.kt @@ -0,0 +1,2034 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.ChartHandle +import io.starter.OpenXLS.ChartHandle.ChartOptions +import io.starter.OpenXLS.ExcelTools +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import org.json.JSONArray +import org.json.JSONException + +import java.io.* +import java.util.* + + +/** + * **Chart: Chart Location and Dimensions 0x1002h**

                      + * + * + * The Chart record determines the chart dimensions and + * marks the beginning of the Chart records. + * + * + *

                      + * * Note that all these values are split up 2 bytes integer and 2 bytes fractional
                      + *
                      + * offset  name        size    contents
                      + * ---
                      + * 4       x           4       x pos of upper left corner
                      + * 8       y           4       y pos of upper left corner
                      + * 12      dx          4       x-size
                      + * 16      dy          4       y-size
                      + *
                      +
                      * + * + * + * + * + * + * + * notes on implementation: + * + *

                      A chart may have up to 9 chart types (specific instances of a chart e.g. bar, line, etc.) known as chart groups. These are identified by the chartgroup array list. + *

                      The parent of these chart groups are the axis group. + *

                      A chart's series and trendlines contains indexes into the chart group. + * + * @see Ai + */ +open class Chart : GenericChartObject(), ChartObject { + + // Objects which define a chart -- REMEMBER TO UPDATE OOXMLChart copy constructor if modify below member vars + protected var chartgroup: ArrayList = ArrayList() // can have up to 4 chart objects (Bar, Area, Line, etc.) per a given chart + protected var nCharts = 0 // total number of charts (0= the default chart + any overlay charts (up to 4 charts (9 in OOXML) see chartgroup list) + /** + * Return the Chart Axes object + * + * @return ChartAxis + */ + var axes: ChartAxes? = null + protected set + /** + * return the Chart Series object + * + * @return + */ + var chartSeries = ChartSeries() + protected set + /** + * return the title TextDisp element + * + * @return + */ + var titleTd: TextDisp? = null + protected set + protected var dimensions: Dimensions // datarange dimensions ... + // The following two objects are external objects in the XLS stream that are associated with the chart. + var obj: Obj? = null + protected set + var msodrawobj: MSODrawing? = null + protected set // stores coordinates and shape info ... + internal var chartMetrics: HashMap = HashMap() // hold chartMetrics: x, y, w, h, canvasw, canvash + @Transient + var wbh: WorkBookHandle? = null + + // TODO: MANAGE FONTS ---? + + // internal chart records + protected var chartRecs = ArrayList() + protected var preRecs: AbstractList<*>? = null + protected var postRecs: AbstractList<*>? = ArrayList() + protected var dirtyflag = false // if anything has changed in the chart (except series, which is handled via another var) + protected var metricsDirty = true // initially true so creates min, max and other metrics, true if should be recalculated + // below vars used to save state in addInitialChartRecord recursion + protected var currentAi: Ai? = null // used in init only + protected var hierarchyDepth = 0 // "" + protected var initobs = ArrayList() + + /** + * Returns a map of series ==> series ptgs + * Ranges can either be in the format "C5" or "Sheet1!C4:D9" + */ + val seriesPtgs: HashMap<*, *> + get() = this.chartSeries.seriesPtgs + + /** + * Returns a serialized copy of this Chart + */ + val chartBytes: ByteArray? + get() { + for (i in preRecs!!.indices) { + (preRecs!![i] as BiffRec).data + } + + var obs: ObjectOutputStream? = null + var b: ByteArray? = null + try { + val baos = ByteArrayOutputStream() + obs = ObjectOutputStream(baos) + obs.writeObject(this) + b = baos.toByteArray() + } catch (e: IOException) { + Logger.logErr(e) + } + + return b + } + + val serialBytes: ByteArray? + get() { + for (i in preRecs!!.indices) { + (preRecs!![i] as BiffRec).data + } + var obs: ObjectOutputStream? = null + var b: ByteArray? = null + try { + val baos = ByteArrayOutputStream() + val bufo = BufferedOutputStream(baos) + obs = ObjectOutputStream(bufo) + obs.writeObject(this) + bufo.flush() + b = baos.toByteArray() + } catch (e: IOException) { + Logger.logErr(e) + } + + return b + } + + /** + * Gets the records for the chart, including the global references, ie the MSODrawing and Obj records + * + * @return + */ + val xlSrecs: List<*> + get() { + val l = this.assembleChartRecords() + if (obj != null) l.add(0, obj) + if (msodrawobj != null) l.add(0, msodrawobj) + return l + } + + + /** + * Dimensions rec holds the datarange for the chart; must update ... + * + * @return + */ + val minRow: Int + get() = dimensions.rowFirst + + val maxRow: Int + get() = dimensions.rowLast + + val minCol: Int + get() = dimensions.colFirst + + val maxCol: Int + get() = dimensions.colLast + + /** + * returns the unique Obj Id # of this chart as seen in Vb macros et. al + * + * @return in id # + */ + /** + * sets the unique id # for this chart + * used upon addChart + * + * @param id + */ + var id: Int + get() = if (this.obj != null) this.obj!!.objId else -1 + set(id) { + if (this.obj != null) + this.obj!!.objId = id + } + + /** + * get all the series objects for ALL charts + * (i.e. even in overlay charts) + * + * @return + */ + val allSeries: Vector<*> + get() = chartSeries.getAllSeries(-1) + + /** + * returns the plot area background color string (hex) + * + * @return color string + */ + val plotAreaBgColor: String + get() { + var bg: String? = null + try { + val f = Chart.findRec(this.chartArr, Frame::class.java) as Frame? + bg = f!!.bgColor + } catch (e: Exception) { + } + + val ct = this.chartType + if (bg == null || ct != ChartConstants.PIECHART && ct != ChartConstants.RADARCHART) { + bg = axes!!.plotAreaBgColor + } + if (bg == null) + bg = "#FFFFFF" + return bg + } + + + /** + * returns the plot area line color string (hex) + * + * @return color string + */ + val plotAreaLnColor: String + get() { + var clr: String? = "#000000" + try { + val f = Chart.findRec(this.chartArr, Frame::class.java) as Frame? + clr = f!!.lineColor + } catch (e: Exception) { + } + + return clr + } + + /** + * Get the title of the Chart + * + * @return title of the Chart + */ + /** + * Set the title of the Chart + * + * @param str title of the Chart + */ + // getTitleTD(); + // No need to create a TD record if setting title to "" + // if no title and setting title to "", just leave it + // 20070709 KSC: Adding a new chart, add Title recs ... + // add TextDisp title to end of chart recs ... + // just set title text + var title: String? + get() = if (titleTd != null) titleTd!!.toString() else "" + set(str) { + if ((str == null || str == "") && titleTd == null) + return + if (titleTd == null) { + try { + val td = TextDisp.getPrototype(ObjectLink.TYPE_TITLE, str, this.workBook) as TextDisp + this.addChartRecord(td) + titleTd = td + + } catch (e: Exception) { + Logger.logWarn("Unable to set title of chart to: $str This chart object does not contain a title record") + } + + } else { + titleTd!!.setText(str) + } + setMetricsDirty() + } + + + /** + * return the default font for the chart + * + * @return + */ + // correct code???? 2 or 3 ?? + val defaultFont: Font? + get() { + for (i in chartArr.indices) { + if ((chartArr[i] as BiffRec).opcode == XLSConstants.DEFAULTTEXT) { + if ((chartArr[i] as DefaultText).type == 2) { + val td = chartArr[i + 1] as TextDisp + val idx = td.fontId + return if (idx > -1) wkbook!!.getFont(idx) else null + } + } + } + return null + } + + /** + * Get all of the Fontx records in this chart. Utilized for + * updating font references when moving between workbooks + */ + val fontxRecs: ArrayList<*> + get() { + val ret = ArrayList() + for (i in chartRecs.indices) { + val b = chartRecs.get(i) as BiffRec + if (b.opcode == XLSConstants.FONTX) { + ret.add(b) + } + } + return ret + } + + + /** + * return the font associated with the chart title + * + * @return Font + */ + val titleFont: Font? + get() = if (titleTd == null) null else titleTd!!.getFont(wkbook!!) + + /** + * returns the chart type for the default chart + */ + val chartType: Int + get() = chartgroup[0].chartType + + /** + * returns an array of all chart types found in this chart + * 0= default, 1-9 (max) are overlay charts + * + * @return + */ + // max + val allChartTypes: IntArray + get() { + val charttypes = IntArray(nCharts) + for (i in 0 until nCharts) { + charttypes[i] = chartgroup[i].chartType + } + return charttypes + } + + /** + * returns the number of charts in this chart - + * 1= default, can have up to 9 overlay charts as well + * + * @return + */ + val numberOfCharts: Int + get() = nCharts - 1 + + /** + * return the default Chart Object associated with this chart + * + * @return + */ + val chartObject: ChartType + get() = chartgroup[0] + + /** + * @return truth of "Chart is Three-D" + */ + val isThreeD: Boolean + get() = isThreeD(0) + + /** + * @return truth of "Chart is Stacked" + */ + override var isStacked: Boolean + get() = isStacked(0) + set(value: Boolean) { + super.isStacked = value + } + + /** + * return truth of "Chart is 100% Stacked" + * + * @return + */ + val is100PercentStacked: Boolean + get() = is100PercentStacked(0) + + /** + * @return truth of "Chart is Clustered" (Bar/Col only) + */ + val isClustered: Boolean + get() = isClustered(0) + + /** + * return ThreeD settings for this chart in XML form + * + * @return String XML + */ + val threeDXML: String + get() = getThreeDXML(0) + + /** + * Returns true if the Y Axis (Value axis) is set to automatic scale + * + * The default setting for charts is known as Automatic Scaling + *

                      When data changes, the chart automatically adjusts the scale (minimum, maximum values + * plus major and minor tick units) as necessary + * + * @return boolean true if Automatic Scaling is turned on + * @see getAxisAutomaticScale + */ + val axisAutomaticScale: Boolean + get() = this.axes!!.axisAutomaticScale + + /** + * Return all the Chart-specific font recs in XML form + * These include the default fonts + title font + axis fonts ... + * + * @return + */ + //((TextDisp) b).getFontId(); + val chartFontRecsXML: String + get() { + val fonts = HashMap() + var maxFont = 5 + for (i in chartRecs.indices) { + val b = chartRecs.get(i) as BiffRec + if (b.opcode == XLSConstants.FONTX) { + val fontId = (b as Fontx).ifnt + maxFont = Math.max(fontId, maxFont) + val f = this.workBook!!.getFont(fontId) + fonts.put(Integer.valueOf(fontId), f.xml) + } + } + val sb = StringBuffer() + for (i in 5..maxFont) { + if (fonts.get(Integer.valueOf(i)) != null) { + sb.append("\n\t\t") + } + } + return sb.toString() + } + + /** + * return the fontid for non-axis chart fonts (title, default ...) + * + * @return + */ + // should!! + // should!! + val chartFontsXML: String + get() { + val sb = StringBuffer() + var i = 0 + while (i < chartArr.size) { + var b: BiffRec = chartArr[i] + if (b.opcode == XLSConstants.DEFAULTTEXT) { + sb.append(" Default" + (b as DefaultText).type + "=\"") + i++ + b = chartArr[i] + if (b.opcode == XLSConstants.TEXTDISP) { + val td = b as TextDisp + sb.append(td.fontId) + } + sb.append("\"") + } else if (b.opcode == XLSConstants.TEXTDISP) { + val td = b as TextDisp + if (td.isChartTitle) { + sb.append(" Title=\"") + sb.append(td.fontId) + sb.append("\"") + } + } + i++ + } + return sb.toString() + } + + + /** + * return int[4] representing the coordinates (L, T, W, H) of this chart in pixels + * + * @return int[4] bounds + */ + /** + * set the coordinates of ths chart in pixels + * + * @param coords int[4]: L, T, W, H + */ + // use adjusted values for w and h + // size is an element in minmax ... + // should NEVER happen + var coords: ShortArray + get() { + var coords = shortArrayOf(20, 20, 100, 100) + if (this.msodrawobj != null) + coords = this.msodrawobj!!.coords + else + Logger.logWarn("Chart missing Msodrawing record. Chart has no coordinates.") + if (!metricsDirty) { + coords[2] = chartMetrics["canvasw"].toShort() + coords[3] = chartMetrics["canvash"].toShort() + } + return coords + } + set(coords) = if (this.msodrawobj != null) { + msodrawobj!!.coords = coords + setMetricsDirty() + } else + Logger.logWarn("Chart missing coordinates.") + + /** + * return bounds relative to row/cols including their respective offsets + * + * @return + */ + /** + * set the bounds relative to row/col and their offsets into them + * + * @param bounds short[] + */ + var bounds: ShortArray? + get() = if (this.msodrawobj != null) msodrawobj!!.bounds else null + set(bounds) { + if (this.msodrawobj != null) + msodrawobj!!.bounds = bounds + setMetricsDirty() + } + + /** + * returns the offset within the column in pixels + * + * @return + */ + val colOffset: Short + get() = if (this.msodrawobj != null) msodrawobj!!.colOffset else 0 + + /** + * return the top row of the chart + */ + val row0: Int + get() = if (this.msodrawobj != null) msodrawobj!!.row0 else -1 + + /** + * return the left col of the chart + */ + val col0: Int + get() = if (this.msodrawobj != null) msodrawobj!!.col else -1 + + /** + * return the height of this chart + */ + /** + * set the height of this chart + * + * @param h + */ + var height: Int + get() = if (this.msodrawobj != null) msodrawobj!!.height.toInt() else -1 + set(h) { + if (this.msodrawobj != null) + msodrawobj!!.setHeight(h) + setMetricsDirty() + } + + /** + * retrieve the current Series JSON for comparisons + * + * @return JSONArray + */ + /** + * set the current Series JSON + * + * @param s JSONArray + */ + var seriesJSON: JSONArray? + get() = chartSeries.seriesJSON + @Throws(JSONException::class) + set(s) { + chartSeries.seriesJSON = s + dirtyflag = true + } + + /** + * return the Data Legend for this chart, if any + * + * @return + */ + val legend: Legend? + get() = chartgroup[0].dataLegend + + /** + * return the default data label setting for the chart, if any + *

                      NOTE: each series can override the default data label fo the chart + *

                      can be one or more of: + *

                      VALUELABEL= 0x1; + *

                      VALUEPERCENT= 0x2; + *

                      CATEGORYPERCENT= 0x4; + *

                      CATEGORYLABEL= 0x10; + *

                      BUBBLELABEL= 0x20; + *

                      SERIESLABEL= 0x40; + * + * @return int default data label for chart + */ + val dataLabel: Int + get() = chartgroup[0].dataLabel + + + /* + public Chart copy() throws InstantiationException, IllegalAccessException { + Chart copy = this.getClass().newInstance(); + return copy; + }*/ + + override fun init() { + super.init() + data + chartSeries.setParentChart(this) +/** + * NOTE: the x, y, dx (w) and dy (h) are in most cases + * not used; the MSODRAWING coords governs the + * chart canvas; see getMetrics for plot and other adjustments + * + * New Info: have to see if this works + * Get chart area width in pixels + * chart area width in pixels = (dx field of Chart record - 8) * DPI of the display device / 72 + * If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: + * chart area width in pixels -= 2 * line width of the display device in pixels + * + * Get chart area height in pixels + * chart area height in pixels = (dy field of Chart record - 8) * DPI of the display device / 72 + * If the frt field of the Frame record following the Chart record is 0x0004 and the chart is not embedded, add the shadow size: + * chart area height in pixels -= 2 * line height of the display device in pixels + * + * NOTE: + * Since the 1980s, the Microsoft Windows operating system has set the default display "DPI" to 96 PPI, + * while Apple/Macintosh computers have used a default of 72 PPI.[2] + * + * byte[] rkdata = this.getData(); + * try { + * short s = ByteTools.readShort(rkdata[0],rkdata[1]); + * short ss = ByteTools.readShort(rkdata[2],rkdata[3]); + * + * if(ss<0)ss=0; + * String parser = s + "." + ss; + * x = (new Float(parser)).floatValue(); + * + * s = ByteTools.readShort(rkdata[4],rkdata[5]); + * ss = ByteTools.readShort(rkdata[6],rkdata[7]); + * if(ss<0)ss=0; + * + * parser = s + "." + ss; + * y = (new Float(parser)).floatValue(); + * + * + * /*Value of the real number = Integral + (Fractional / 65536.0) + * Integral (2 bytes): A signed integer that specifies the integral part of the real number. + * Fractional (2 bytes): An unsigned integer that specifies the fractional part of the real number. + * + * int integral= 0; + * integral |= rkdata[8] & 0xFF; + * integral <<= 8; + * integral |= rkdata[9] & 0xFF; + * int fractional= ByteTools.readUnsignedShort(rkdata[10],rkdata[11]); + * dx= (float)(integral + (fractional/65536.0)); + * integral= ((rkdata[12] & 0xFF) << 8) | (rkdata[13] & 0xFF); + * if (integral < 0) integral += 256; + * fractional= ByteTools.readUnsignedShort(rkdata[14],rkdata[15]); + * dy= (float) integral; + * } catch (NumberFormatException e) { // 20080414 KSC: parsing appears to be off - TODO Check out Chart record specs + * Logger.logErr("Chart.init: parsing dimensions failed: " + e); + * } + * + * if ((DEBUGLEVEL > 3)) + * Logger.logInfo("Chart Found @ x:" + x + " y:" + y + " dx:" + dx + " dy:" + dy); */ +} + + +/* + * Note, other important chart records we are not as of yet trapping: + * CttMlFrt 0x89E -- additional properties for chart elements + * ShapePropsStream 0x8A4 + * TextPropsStream 0x8A5 + * CrtLayout12 0x89D -- layout info for attached label, legend + * 0x89E=The CrtMlFrt record specifies additional properties for chart elements, as specified by the Chart Sheet SubstreamABNF. These properties complement the record to which they correspond, and are stored as a structure chain defined in XmlTkChain. An application can ignore this record without loss of functionality, except for the additional properties. If this record is longer than 8224 bytes, it MUST be split into several records. The first section of the data appears in this record and subsequent sections appear in one or more CrtMlFrtContinue records that follow this record. + * + * Legend --> Pos, TextDisp, Frame, CrtLayout12, TextPropsStream, CrtLayout12 +*/ + +/** + * Adds chart records to the chartRecs array. This array should be completed with all chart records + * before the initChartRecords method is called, creating the hierarchical chart structure. + * + * @param br - a chart record + * @return false if we reach the final "end" record, signifying the end of the chart object structure. + * + * + * Be aware that there can be additional chart records after this structure, Chart only deals with it's own heirarchy. +*/ +fun addInitialChartRecord(br:BiffRec):Boolean { +if (br.opcode.toInt() == 0x1033) +{ +++hierarchyDepth +} +else if (br.opcode.toInt() == 0x1034) +{ +--hierarchyDepth +if (hierarchyDepth == 0) +{ +chartRecs.add(br) +return false +} +} +if (hierarchyDepth != 0) +{ +if (br.opcode == XLSConstants.AI) +{ +currentAi = br as Ai +currentAi!!.parentChart = this // necessary for base ops +currentAi!!.setSheet(this.sheet) // needed for cell change updates +} +else if (currentAi != null && br.opcode == XLSConstants.SERIESTEXT) +{ +currentAi!!.setSeriesText(br as SeriesText) +currentAi = null // done +} +else if (br.opcode == XLSConstants.SERIES) +{ +chartSeries.add(arrayOf(br, Integer.valueOf(nCharts))) // store series records - initially map all to default chart (normal case) +} +else if (br.opcode == XLSConstants.AXISPARENT) +{ +axes = ChartAxes(br as AxisParent) +} +else if (br.opcode == XLSConstants.AXIS) +{ +axes!!.add(br as Axis) +} +else if (br.opcode == XLSConstants.CHARTFORMAT) +{ // usually only 1 (default chart id= 0) but can have up to 9 overlay charts +nCharts++ +} +else if (br.opcode == XLSConstants.SERIESLIST) +{ // maps chart # to series # +chartSeries.addSeriesMapping(nCharts - 1, (br as SeriesList).seriesMappings!!)// 1-based series #'s to "assign" to the overlay chart +} +try +{ +if ((br as GenericChartObject).chartType != -1) +{ // store the chart object which defines this chart (NOTE: may be up to 4 charts in one = overlay charts) +var cf:ChartFormat? = null +for (i in chartRecs.indices.reversed()) +{ +if ((chartRecs.get(i) as BiffRec).opcode == XLSConstants.CHARTFORMAT) +{ +cf = chartRecs.get(i) as ChartFormat +break +} +} +// sadly, can't do it here - have to wait until process hierarchy chartobj.add(ChartType.getChartObject((GenericChartObject)br, cf, this.getWorkBook())); +initobs.add(arrayOf(br, cf)) +} // Set Parent Chart as it's necessary for all basic chart ops +(br as GenericChartObject).parentChart = this // Note: Scl is not a GenericChartObject + can have unknown (XLSRecords) - will cause ClassCastException +} +catch (c:ClassCastException) {} + +chartRecs.add(br) +} +else +{ +if (br.opcode == XLSConstants.DIMENSIONS) dimensions = br as Dimensions +br.data +postRecs!!.add(br) +} + +return true +} + + +fun setDirtyFlag(b:Boolean) { +dirtyflag = b +} + +/** + * Take the initial array of records for the chart, and create a + * hierarchial array of objects for the chart. Also, populate initial values for easy access. +*/ +fun initChartRecords() { +try +{ // Added to find/set obj + msodrawing records - replaces setObj +// see TestReadWrite.TestIOOBError +// For Normal charts, Obj rec is the last record before the Chart record +if (!this.sheet!!.isChartOnlySheet) +{ +var pos = this.sheet!!.sheetRecs.size - 1 +var rec = this.sheet!!.sheetRecs.get(pos) as BiffRec +obj = rec as Obj +obj!!.chart = this +// Usually, MsoDrawing is just preceding Obj record, except in those rare carses where +// there are Continues and Txo's ... +while (--pos > 0) +{ +rec = this.sheet!!.sheetRecs.get(pos) as BiffRec +if (rec.opcode == XLSConstants.MSODRAWING) +{ +this.msodrawobj = rec as MSODrawing +break +} +} +} // chart-only worksheets have no obj/mso apparently +} +catch (e:Exception) { +Logger.logErr("initChartRecords: Error in Chart Records: " + e.toString()) +} + +// Turn it into a static array initially to speed up random access +var bArr = arrayOfNulls(chartRecs.size) +bArr = chartRecs.toTypedArray() as Array +this.initChartObject(this, bArr) +for (i in initobs.indices) +{ +val ios = initobs.get(i) as Array +chartgroup.add(ChartType.createChartTypeObject(ios[0] as GenericChartObject, ios[1] as ChartFormat, this.workBook)) +val l = Chart.findRec((ios[1] as ChartFormat).chartArr, Legend::class.java) as Legend? +if (l != null) +chartgroup.get(chartgroup.size - 1).addLegend(l) +} +initobs = ArrayList() // clear out +} + +/** + * Handle the iteration and creation of the chart objects +*/ +private fun initChartObject(cobj:ChartObject, cRecs:Array):ChartObject { + +var i = 0 +while (i < cRecs.size) +{ +val b = cRecs[i] +b.data +if (!(b.opcode == XLSConstants.BEGIN) && !(b.opcode == XLSConstants.END)) +{ +if ((cRecs.size > i + 1) && ((cRecs[i + 1]).opcode == XLSConstants.BEGIN)) +{ // this is an object with sub-data +try +{ +val co = b as ChartObject +val endloc = this.getMatchingEndRecordLocation(i, cRecs) +val arrlen = endloc - i +val objArr = arrayOfNulls(arrlen) +System.arraycopy(cRecs, i + 1, objArr, 0, arrlen) +cobj.addChartRecord(this.initChartObject(co, objArr) as XLSRecord) +// necessary initialization of key elements +if (co is TextDisp) +{ +val type = (co as TextDisp).type +// -1 = default text ...? +if (type == ObjectLink.TYPE_TITLE) +titleTd = co as TextDisp +else if (type == ObjectLink.TYPE_XAXIS) +axes!!.setTd(ChartConstants.XAXIS, co as TextDisp) +else if (type == ObjectLink.TYPE_YAXIS) +axes!!.setTd(ChartConstants.YAXIS, co as TextDisp) +else if (type == ObjectLink.TYPE_ZAXIS) +axes!!.setTd(ChartConstants.ZAXIS, co as TextDisp) +else if (type == ObjectLink.TYPE_DATAPOINTS) + +else if (type == ObjectLink.TYPE_DISPLAYUNITS) +;// +// KSC: TESTING!! Take out when done +// Logger.logInfo("Display Units"); +// series or data points +// do what?? +} +try +{ +co.parentChart = this +} +catch (e:Exception) {} + +i += arrlen +} +catch (e:ClassCastException) { +// it's not a defined chart object. Add it in!!! If we are missing a chart object containing other records we +// will not be able to write these out correctly. +Logger.logWarn("Error in parsing chart. Please add the correct object (opcode: " + b.opcode + ") to be a Chart Object") +} + +} +else +{ +cobj.addChartRecord(b as XLSRecord) +} +} +i++ +} +return cobj +} + +/** + * Just a little helper method! Determines where the matching end record is for a begin record + * + * @param startLoc + * @param cRecs + * @return +*/ +private fun getMatchingEndRecordLocation(startLoc:Int, cRecs:Array):Int { +var offset = 0 +for (i in startLoc + 2 until cRecs.size) +{ +val b = cRecs[i] +if (b.opcode == XLSConstants.BEGIN) offset++ +if (b.opcode == XLSConstants.END) +{ +offset-- +if (offset < 0) return i +} +} +return -1 +} + +/** + * put together the chart records for output to the streamer + * + * @return arranged List of Chart Records. +*/ +fun assembleChartRecords():MutableList<*> { +val outputVec = Vector() +if (preRecs != null) outputVec.addAll(preRecs!!) +if (dirtyflag) +{ +for (i in 0 until nCharts) +{ +chartSeries.updateSeriesMappings(chartgroup.get(i).seriesList, i) // if has multiple or overlay charts, update series mappings +} +outputVec.addAll(this.recordArray) +} +else +{ +outputVec.add(this) +outputVec.addAll(chartRecs) +} +if (postRecs != null) outputVec.addAll(postRecs!!) +if (!true) +{ +for (i in outputVec.indices) +{ +val rec = outputVec.get(i) as XLSRecord +Logger.logInfo("rec:" + rec.toString() + "") +Logger.logInfo(ByteTools.getByteDump(rec.getData(), 0)) +Logger.logInfo("-------------------------------------------------") +} +} +return outputVec +} + +/** + * This handles setting the records external to, but supporting the chart object hierarchy. + * + * + * Think of this kind of like the recvec & cell records in the XLS parsing we do. + * + * + * We have a pre and post array of biffrecs that are (for now) unmodifieable. + * These get appended into the whole array on ouput in append chart records. + * + * @param recs +*/ +fun setPreRecords(recs:AbstractList<*>) { +preRecs = recs +} + +fun setDimensionsRecord(r0:Int, r1:Int, c0:Int, c1:Int) { +dimensions.setRowFirst(r0 - 1) +dimensions.setRowLast(r1 - 1) +dimensions.setColFirst(c0) +dimensions.setColLast(c1 - 1) +/* also must remove label and numberrec cached records if altered series +otherwise causes errors when removing or altering series +*/ +for (i in postRecs!!.size - 1 downTo 1) +{ +val b = postRecs!!.get(i) as BiffRec +val op = b.opcode.toInt() +if (op == XLSConstants.NUMBER.toInt() || op == XLSConstants.LABEL.toInt()) +{ +postRecs!!.removeAt(i) +} +} +} + + +fun setDimensions(d:Dimensions) { +dimensions = d +} + +fun setDimensionsRecord() { +val serieslist = this.getAllSeries(-1) +val nSeries = serieslist.size +var nPoints = 0 +for (i in serieslist.indices) +{ +try +{ +val s = serieslist.get(i) as Series +val coords = ExcelTools.getRangeCoords(s.seriesValueAi!!.definition) +if (coords[3] > coords[1]) +nPoints = Math.max(nPoints, coords[3] - coords[1] + 1) // c1-c0 +else +nPoints = Math.max(nPoints, coords[2] - coords[0] + 1) // r1-r0 +} +catch (e:Exception) {} + +} +this.setDimensionsRecord(0, nPoints, 0, nSeries) +} + +/** + * flags chart metrics should + * to be recalculated +*/ +fun setMetricsDirty() { +dirtyflag = true +metricsDirty = true +} + +/** + * Change series ranges for ALL matching series + * + * @param originalrange + * @param newrange + * @return +*/ +fun changeSeriesRange(originalrange:String, newrange:String):Boolean { +setMetricsDirty() +return chartSeries.changeSeriesRange(originalrange, newrange) +} + +/** + * Return a string representing all series in this chart + * + * @param nChart 0=default, 1-9= overlay charts -1 for ALL charts + * @return +*/ +fun getSeries(nChart:Int):Array { +return chartSeries.getSeries(nChart) +} + +/** + * Return an array of strings, one for each category + * + * @param nChart 0=default, 1-9= overlay charts -1 for All + * @return +*/ +fun getCategories(nChart:Int):Array { +return chartSeries.getCategories(nChart) +} + +/** + * get all the series objects in the specified chart (-1 for ALL) + * + * @param nChart 0=default, 1-9= overlay charts -1 for ALL series + * @return +*/ +fun getAllSeries(nChart:Int):Vector<*> { +return chartSeries.getAllSeries(nChart) +} + +/** + * Add a series object to the array. + * + * @param seriesRange = one row range, expressed as (Sheet1!A1:A12); + * @param categoryRange = category range + * @param bubbleRange= bubble range, if any 20070731 KSC + * @param seriesText = label for the series; + * @param nChart 0=default, 1-9= overlay charts + * s * @return +*/ +fun addSeries(seriesRange:String, categoryRange:String, bubbleRange:String, legendRange:String, legendText:String, nChart:Int):Series { +val s = chartSeries.addSeries(seriesRange, categoryRange, bubbleRange, legendRange, legendText, chartgroup.get(nChart), nChart) +setMetricsDirty() +return s +} + + +/** + * return an array of legend text + * + * @param nChart 0=default, 1-9= overlay charts -1 for ALL +*/ +fun getLegends(nChart:Int):Array { +return chartSeries.getLegends(nChart) +} + +/** + * specialty method to take absoulte index of series and remove it + *

                      only used in WorkSheetHandle + * + * @param index +*/ +fun removeSeries(index:Int) { +setMetricsDirty() +chartSeries.removeSeries(index) +} + +/** + * remove desired series from chart + * + * @param index +*/ +fun removeSeries(seriestodelete:Series) { +// remove from cache +setMetricsDirty() +chartSeries.removeSeries(seriestodelete) +// remove from chartArray +var nSeries = -1 +for (i in chartArr.indices) +{ +var b:BiffRec = chartArr.get(i) +if (b.opcode == XLSConstants.SERIES) +{ +nSeries++ +if (seriestodelete == b) +{ // this is the one to delete +chartArr.removeAt(i) +// now adjust series number for all subsequent Series +for (j in i until chartArr.size) +{ +b = chartArr.get(j) +if (b.opcode == XLSConstants.SERIES) +{ +val s = b as Series +val x = Chart.findRecPosition(s.chartArr, DataFormat::class.java) +if (x > 0) +{ +val df = s.chartArr.get(x) as DataFormat +df.setSeriesIndex(nSeries++) +} +} +else +// we've got 'em all +break +} +// now make sure referenced label and number recs are removed +/** how does this work, it does not reference a particular series label, just all of them? + * for (int j= 0; j < postRecs.size(); j++) { + * if ((postRecs.get(j) instanceof NumberRec) || + * (postRecs.get(j) instanceof Label)) { + * postRecs.remove(j); + * j--; + * } + * + * } +*/ +break +} +} +} +} + +/** + * get a chart series handle based off the series name + * + * @param seriesName + * @param nChart 0=default, 1-9= overlay charts + * @return +*/ +fun getSeries(seriesName:String, nChart:Int):Series? { +return chartSeries.getSeries(seriesName, nChart) +} + +/** + * @param originalrange + * @param newrange + * @return +*/ +fun changeCategoryRange(originalrange:String, newrange:String):Boolean { +setMetricsDirty() +return chartSeries.changeCategoryRange(originalrange, newrange) +} + + +fun changeTextValue(originalval:String, newval:String):Boolean { +setMetricsDirty() +return chartSeries.changeTextValue(originalval, newval) +} + +/** + * sets the plot area background color + * + * @param bg color int +*/ +fun setPlotAreaBgColor(bg:Int) { +this.axes!!.setPlotAreaBgColor(bg) +setMetricsDirty() +} + +/** + * return the textdisp object that defines the chart title + * + * @return public TextDisp getTitleTD(){ + * if(title != null){ + * return title; + * } + * for (int i=0;i();i++) { + * BiffRec b = (BiffRec)chartArr.get(i); + * if (b.getOpcode()==TEXTDISP) { + * TextDisp td = (TextDisp)b; + * if (td.isChartTitle()){ + * title = td; + * } + * } + * } + * return title; + * } +*/ + +public override fun toString():String { +val t = title +if (t != "") +return t +return "Untitled Chart" +} + +/** + * Set the default font for the specific DefaultText rec + * + * @param type + * @param fontId +*/ +fun setDefaultFont(type:Int, fontId:Int) { +for (i in chartArr.indices) +{ +val b = chartArr.get(i) +if (b.opcode == XLSConstants.TEXTDISP) +{ +//xxxxx +} +} +dirtyflag = true +} + +/** + * resets all fonts in the chart to the default font of the workbook Useful + * for OOXML charts which are based upon default chart +*/ +fun resetFonts() { +for (i in chartRecs.indices) +{ +val b = chartRecs.get(i) as BiffRec +if (b.opcode == XLSConstants.FONTX) +{ +(b as Fontx).setIfnt(0) +} +} +} + +/** + * Utilized by external chart copy, this + * sets references internally in this chart which allows cross-workbook + * identification of worksheets + * to occur. +*/ +fun populateForTransfer() { +val i = this.getAllSeries(-1).iterator() +while (i.hasNext()) +{ +val s = i.next() as Series +try +{ +for (j in s.chartArr.indices) +{ +if ((s.chartArr.get(j) as BiffRec).opcode == XLSConstants.AI) +{ +(s.chartArr.get(j) as Ai).populateForTransfer(this.sheet!!.sheetName) +} +} +} +catch (e:Exception) { +Logger.logErr("Chart.populateForTransfer: " + e.toString()) +} + +} + +} + +/** + * update Sheet References contained in associated Series/Category Ai's + * using saved Original Sheet References +*/ +fun updateSheetRefs(newSheetName:String, origWorkBook:String) { +val i = this.getAllSeries(-1).iterator() +while (i.hasNext()) +{ +val s = i.next() as Series +try +{ +for (j in s.chartArr.indices) +{ +if ((s.chartArr.get(j) as BiffRec).opcode == XLSConstants.AI) +{ +(s.chartArr.get(j) as Ai).updateSheetRef(newSheetName, origWorkBook) // 20080630 KSC: add sheet name for lookup +} +} +} +catch (e:Exception) { +Logger.logErr("Chart.updateSheetRefs: " + e.toString()) +} + +} +} + +/** + * returns the chart type for the desired chart + * + * @param nChart 0=default, 1-9= overlay charts + * @return +*/ +fun getChartType(nChart:Int):Int { +return chartgroup.get(nChart).chartType +} + +/** + * return the nth Chart Object associated with this chart (default=0, overlay=1 thru 9) + * + * @return +*/ +fun getChartObject(nChart:Int):ChartType { +return chartgroup.get(nChart) +} + + +/** + * gets the integral order of the specific chart obect in question + * + * @param ct Chart Type rwobject + * @return +*/ +fun getChartOrder(ct:ChartType):Int { +for (i in chartgroup.indices) +{ +if (chartgroup.get(i) == ct) +return i +} +return 0 +} + +/** + * create internal records necessary for an overlay chart +*/ +private fun createNewChart(nChart:Int):ChartFormat? { +val ap = findRec(chartArr, AxisParent::class.java) as AxisParent? +return ap!!.createNewChart(nChart) +} + + +/** + * adds a new Chart Type to the group of Chart Type Objects, or replaces one at existing index + * + * @param ct Chart Type Object + * @param nChart index +*/ +fun addChartType(ct:ChartType, nChart:Int) { +if (nChart < chartgroup.size) +chartgroup.removeAt(nChart) +else +nCharts++ +chartgroup.add(nChart, ct) +dirtyflag = true +} + +/** + * retrieves the parent of the chart type object (==ChartFormat) at index nChart, or creates a new Chart Type Parent + * + * @param nChart + * @return ChartFormat +*/ +fun getChartOjectParent(nChart:Int):ChartFormat? { +var cf:ChartFormat? = null +if (nChart >= chartgroup.size) +// create new +cf = this.createNewChart(nChart) +else +cf = chartgroup.get(nChart).cf +return cf +} + +/** + * changes this chart to be a specific chart type with specific display options + *

                      for multiple charts, specify nChart 1-9, for the default chart, + * nChart= 0 + * + * @param chartType chart type one of: BARCHART, LINECHART, AREACHART, COLCHART, PIECHART, + * DONUGHTCHART, RADARCHART, RADARAREACHART, PYRAMIDCHART, CONECHART, CYLINDERCHART, + * SURFACTCHART + * @param nChart chart # 0 is default + * @param EnumSet 0 or more chart options (Such as Stacked, Exploded ...) + * @see ChartOptions + */ +fun setChartType(chartType:Int, nChart:Int, options:EnumSet) { +val c:GenericChartObject + +val cf = getChartOjectParent(nChart) +val ct = ChartType.create(chartType, this, cf) +ct!!.setOptions(options) + +// save and reset legend: +val l = this.legend +ct!!.addLegend(l) + +if (ct is BubbleChart && options.contains(ChartOptions.THREED)) +(ct as BubbleChart).is3d = true // when set, every series created will + +addChartType(ct, nChart) + + +/* TODO: axes, other chart options ... Legend??? +// The axis group MUST contain two value axes if and only if all chart groups are of type bubble or scatter. +} else if (chartType==BUBBLECHART || chartType==SCATTERCHART) { +// TODO: FINISH!!! IS NOT CORRECT UNLESS PROPER AXES ARE CREATED +//The axis group MUST contain a category or date axis if the axis group contains an area, bar, column, filled radar, line, radar, or surface chart group +} else { // ensure has proper axes +// TODO: FINISH!!! IS NOT CORRECT UNLESS PROPER AXES ARE CREATED +} +*/ +/** + * The axis group MUST contain a series axis if and only if the chart group attached to the axis group is one of the following: + * An area chart group with the fStacked field of the Area record equal to 0. + * A column chart group with the fStacked field of the Bar record equal to 0 and the fClustered field of the Chart3d record equal to 0. + * A line chart group with field fStacked of the Line record equal to 0. + * A surface chart group + * The chart group on the axis group MUST contain a Chart3d record if the axis group contains a series axis. +*/ +/** + * for overlay charts i.e. multiple charts, restrictions: + * Because there are many different ways to represent data visually, each representation has specific requirements about the layout of the data and the way it is plotted. + * This results in restrictions on the combinations of chart group types that can be plotted on the same axis group, and the combinations of chart group types that can + * be plotted in the same chart. + * A chart MUST contain one of the following: + * A single axis group that contains a single chart group that contains a Chart3d record. + * One or two axis groups that each contain a single bubble chart group. + * One or two axis groups that each conform to one of the following restrictions on chart group type combinations: + * Zero or one of each of the following chart group types: area, column, line, and scatter. + * Zero or one of each of the following chart group types: bar of pie, doughnut, pie, and pie of pie. + * A single bar chart group. + * A single filled radar chart group. + * A single radar chart group. + * +*/ +} + +/** + * @param nChart 0=default, 1-9= overlay charts + * @return truth of "Chart is Three-D" +*/ +fun isThreeD(nChart:Int):Boolean { +return chartgroup.get(nChart).isThreeD +} + +/** + * return the 3d rec for this chart or null if it doesn't exist + * + * @param nChart 0=default, 1-9= overlay charts + * @return +*/ +fun getThreeDRec(nChart:Int):ThreeD? { +return chartgroup.get(nChart).getThreeDRec(false) +} + +/** + * @param nChart 0=default, 1-9= overlay charts + * @return truth of "Chart is Stacked" +*/ +fun isStacked(nChart:Int):Boolean { +return chartgroup.get(nChart).isStacked +} + +/** + * return truth of "Chart is 100% Stacked" + * + * @param nChart 0=default, 1-9= overlay charts + * @return +*/ +fun is100PercentStacked(nChart:Int):Boolean { +return chartgroup.get(nChart).is100PercentStacked +} + +/** + * @param nChart 0=default, 1-9= overlay charts + * @return truth of "Chart is Clustered" (Bar/Col only) +*/ +fun isClustered(nChart:Int):Boolean { +return chartgroup.get(nChart).isClustered +} + +/** + * return chart-type-specific options in XML form + * + * @param nChart 0=default, 1-9= overlay charts + * @return String XML +*/ +fun getChartOptionsXML(nChart:Int):String { +return chartgroup.get(nChart).chartOptionsXML +} + +/** + * interface for setting chart-type-specific options + * in a generic fashion + * + * @param op option + * @param val value + * @see OpenXLS.handleChartElement + * + * @see ChartHandle.getXML +*/ +public override fun setChartOption(op:String, `val`:String):Boolean { +dirtyflag = true +return this.setChartOption(op, `val`, 0) +} + +/** + * interface for setting chart-type-specific options + * in a generic fashion + * + * @param op option + * @param val value + * @param nChart 0=default, 1-9= overlay charts + * @see OpenXLS.handleChartElement + * + * @see ChartHandle.getXML +*/ +fun setChartOption(op:String, `val`:String, nChart:Int):Boolean { +dirtyflag = true +return chartgroup.get(nChart).setChartOption(op, `val`) +} + +/** + * get the value of *almost* any chart option (axis options are in Axis) + * for the default chart + * + * @param op String option e.g. Shadow or Percentage + * @return String value of option +*/ +public override fun getChartOption(op:String):String? { +return chartgroup.get(0).getChartOption(op) +} + +/** + * return ThreeD settings for this chart in XML form + * + * @param nChart 0=default, 1-9= overlay charts + * @return String XML +*/ +fun getThreeDXML(nChart:Int):String { +return chartgroup.get(nChart).threeDXML +} + +/** + * returns the 3d record of the desired chart + * + * @param nChart 0=default, 1-9= overlay charts + * @param chartType one of the chart type constants + * @return +*/ +@JvmOverloads fun initThreeD(nChart:Int = 0, chartType:Int = this.getChartType(0)):ThreeD { +return chartgroup.get(nChart).initThreeD(chartType) +} + +/** + * returns the minimum and maximum values by examining all series on the chart + * + * @param bk + * @return double[] min, max +*/ +fun getMinMax(wbh:WorkBookHandle):DoubleArray { +if (metricsDirty) +getMetrics(wbh) +if (this.wbh == null) +this.wbh = wbh +metricsDirty = false +val minmaxcache = chartSeries.getMetrics(metricsDirty) // Ignore Overlay charts for now! +return minmaxcache +} + +/** + * Set the specific chart font (title, default ...) + * NOTE: Axis fonts are handled separately + * + * @param type + * @param val + * @see Axis.setChartOption, TextDisp.setChartOption +*/ +fun setChartFont(type:String, `val`:String) { +var type = type +if (type.equals("Title", ignoreCase = true)) +for (i in chartArr.indices) +{ +val b = chartArr.get(i) +if (b.opcode == XLSConstants.TEXTDISP) +{ // should be the title +val td = b as TextDisp +if (td.isChartTitle) +{ // should!! +td.fontId = Integer.parseInt(`val`) +break +} +} +} +else if (type.indexOf("Default") > -1) +{ +type = type.substring(7) +var i = 0 +while (i < chartArr.size) +{ +var b:BiffRec = chartArr.get(i) +if (b.opcode == XLSConstants.DEFAULTTEXT) +{ +if ((b as DefaultText).type == Integer.parseInt(type)) +{ +i++ +b = chartArr.get(i) +if (b.opcode == XLSConstants.TEXTDISP) +{ // should be!! +val td = b as TextDisp +td.fontId = Integer.parseInt(`val`) +break +} +} +} +i++ +} +} +setMetricsDirty() +} + + +/** + * set the fontId for the chart title rec + * + * @param fontId +*/ +fun setTitleFont(fontId:Int) { +for (i in chartArr.indices) +{ +val b = chartArr.get(i) +if (b.opcode == XLSConstants.TEXTDISP) +{ +val td = b as TextDisp +if (td.isChartTitle) +{ +td.fontId = fontId +} +} +} +setMetricsDirty() +} + + +// 20070802 KSC: debugging utility to write out chart recs +fun writeChartRecs(fName:String) { +try +{ +val f = java.io.File(fName) +val writer = BufferedWriter(FileWriter(f, true)) +var ctr = 0 +if (preRecs != null) +ctr = ByteStreamer.writeRecs(ArrayList(preRecs!!), writer, ctr, 0) +ctr = ByteStreamer.writeRecs(chartArr, writer, ctr, 0) // will recurse +writer.flush() +writer.close() +} +catch (e:Exception) {} + +} + +/** + * set the sheet for this chart plus its subrecords as well +*/ +public override fun setSheet(b:Sheet?) { +super.setSheet(b) +if (this.msodrawobj != null) +this.msodrawobj!!.setSheet(b) +for (i in chartArr.indices) +{ +chartArr.get(i).setSheet(b) +} +} + +/** + * return the coordinates of the outer plot area (plot + axes + labels) in pixels + * + * @return +*/ +fun getPlotAreaCoords(w:Float, h:Float):FloatArray? { +return this.axes!!.getPlotAreaCoords(w, h) +} + +/** + * set the top row for this chart + * + * @param r +*/ +fun setRow(r:Int) { +if (this.msodrawobj != null) +msodrawobj!!.setRow(r) +setMetricsDirty() +} + +/** + * Show or remove Data Table for Chart + * NOTE: METHOD IS STILL EXPERIMENTAL + * + * @param bShow +*/ +fun showDataTable(bShow:Boolean) { +// TODO: FINISH chartobj[0].showDataTable(bShow); +} + +/** + * show or hide chart legend key + * + * @param bShow boolean show or hide + * @param vertical boolean show as vertical or horizontal +*/ +fun showLegend(bShow:Boolean, vertical:Boolean) { +chartgroup.get(0).showLegend(bShow, vertical) +} + +/** + * remove the legend from the chart +*/ +open fun removeLegend() { +showLegend(false, false) +} + +/** + * return data label options for each series as an int array + *

                      each can be one or more of: + *

                      VALUELABEL= 0x1; + *

                      VALUEPERCENT= 0x2; + *

                      CATEGORYPERCENT= 0x4; + *

                      CATEGORYLABEL= 0x10; + *

                      BUBBLELABEL= 0x20; + *

                      SERIESLABEL= 0x40; + * + * @return int array + * @see AttachedLabel +*/ +fun getDataLabelsPerSeries(defaultDL:Int):IntArray { +/* NOTES: * + * A data label is a label on a chart that is associated with a data point, or associated with a series on an area or filled radar chart group. + * A data label contains information about the associated data point, such as the description of the data point, a legend key, or custom text. + * + * Inheritance + * For any given data point, there is an order of inheritance that determines the contents of a data label associated with the data point: +Data labels can be specified for a chart group, specifying the default setting for the data labels associated with the data points on the chart group . +Data labels can be specified for a series, specifying the default setting for the data labels associated with the data points of the series. +This type of data label overrides the data label properties specified on the chart group for the data labels associated with the data points in a given series. +Data labels can be specified for a data point, specifying the settings for a data label associated with a particular data point. +This type of data label overrides the data label properties specified on the chart group and series for the data labels associated with a given data point. + + * If formatting is not specified for an individual data point, the data point inherits the formatting of the series. + * If formatting is not specified for the series, the series inherits the formatting of the chart group that contains the series. + * The yi field of the DataFormat record MUST specify the zero-based index of the Series record associated with this series in the + * collection of all Series records in the current chart sheet substream that contains the series. +*/ +return chartSeries.getDataLabelsPerSeries(defaultDL, this.chartType) +} + +/** + * return an array of the type of markers for each series: + *

                      0 = no marker + *

                      1 = square + *

                      2 = diamond + *

                      3 = triangle + *

                      4 = X + *

                      5 = star + *

                      6 = Dow-Jones + *

                      7 = standard deviation + *

                      8 = circle + *

                      9 = plus sign +*/ +protected fun getMarkerFormats(nChart:Int):IntArray { +val mf = chartSeries.markerFormats +for (marker in mf) +{ +if (marker != 0) +return mf +} +// see if chart format +return chartgroup.get(nChart).markerFormats +} + +/** + * returns true if this chart has data markers (line, scatter and radar charts only) + * + * @return +*/ +fun hasMarkers(nChart:Int):Boolean { +val markers = this.getMarkerFormats(nChart) +for (marker in markers) +{ +if (marker != 0) +return true +} +return false + +} + +/** + * return truth if Chart has a data legend key showing + * + * @return +*/ +fun hasDataLegend():Boolean { +return (chartgroup.get(0).dataLegend != null) +} + +/** + * sets bar colors to vary or not + * + * @param vary + * @param nChart +*/ +fun setVaryColor(vary:Boolean, nChart:Int) { +this.getChartObject(nChart).cf!!.varyColor = vary +} + +// TODO: LATER +// **** INCLUDE cell selection in series bars ... +// chart.getSeries vs chart.getSeries(int) -- rename!!! +// chart axes need a dirty flag to rebuild metrics +// Axis.getSVG ==> passed categories -- should be passed chartseries instead?? +// merge minmaxcache into chartMetrics +// metricsDirty - ensure all ops are covered ... +// clean up Axes.getSVG w.r.t. label font, etc. +fun getMetrics(wbh:WorkBookHandle):HashMap<*, *> { +if (metricsDirty) +{ +try +{ +this.wbh = wbh +//chartseries.setWorkBook(wbh); +val minmax = chartSeries.getMetrics(metricsDirty) // Ignore Overlay charts for now! +val coords = this.coords +chartMetrics.put("x", coords[0]) +chartMetrics.put("y", coords[1]) +chartMetrics.put("w", coords[2]) +chartMetrics.put("h", coords[3]) +chartMetrics.put("canvasw", coords[2]) +chartMetrics.put("canvash", coords[3]) +chartMetrics.put("min", minmax[0]) +chartMetrics.put("max", minmax[1]) +var plotcoords:FloatArray? = null +plotcoords = this.getPlotAreaCoords(chartMetrics.get("w").toFloat(), chartMetrics.get("h").toFloat()) +if (plotcoords == null) +{ +val crt = Chart.findRec(this.chartArr, CrtLayout12A::class.java) as CrtLayout12A? +if (crt != null) +plotcoords = crt!!.getInnerPlotCoords(chartMetrics.get("w").toFloat(), chartMetrics.get("h").toFloat()) +} + +chartMetrics.put("x", plotcoords!![0]) +chartMetrics.put("y", plotcoords!![1]) +chartMetrics.put("w", plotcoords!![2]) +chartMetrics.put("h", plotcoords!![3]) +// Chart title offset +val titlefont = this.titleFont +if (titlefont != null && this.title != "") +{ // apparently can still have td even when no title is present ... +val tdcoords = this.titleTd!!.coords +val fh = titlefont!!.fontHeightInPoints +if (tdcoords!![1] == 0f) +{ +chartMetrics.put("TITLEOFFSET", Math.ceil(fh * 1.5)) // with padding +} +else +{ +chartMetrics.put("TITLEOFFSET", fh) // a little padding +} +} +else if (chartMetrics.get("y") < 5.0) +chartMetrics.put("TITLEOFFSET", 10.0) // no title offset - a little padding +else +chartMetrics.put("TITLEOFFSET", 0.0) // no title offset and no need for padding +this.axes!!.getMetrics(this.chartType, chartMetrics, plotcoords, this.chartSeries.getCategories()) +var lcoords:IntArray? = null +var adjust = 10.0 +if (this.legend != null) +{ +this.legend!!.getMetrics(chartMetrics, this.chartType, this.chartSeries) +lcoords = this.legend!!.coords +if (lcoords != null) +// TODO: legend adjustment may have to do with y title and label ofsets ...? +{ +adjust = (2 * lcoords!![4]).toDouble() // spacing before and after legend box TODO this isn't correct !! +//KSC: TESTING! +//io.starter.toolkit.Logger.log("Original lcoords: " + Arrays.toString(lcoords)); +} +else +{ +lcoords = IntArray(6) +lcoords[0] = chartMetrics.get("canvasw").toInt() +} +} +else +{ +lcoords = IntArray(6) +lcoords[0] = chartMetrics.get("canvasw").toInt() +} +val ldist = lcoords!![0] - chartMetrics.get("w") // save distance between legend box and w (significant if legend is on rhs) +//io.starter.toolkit.Logger.log("Before Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); +// now adjust plot area coordinates based on canvas w, h, title and label offsets, and legend box, if any +chartMetrics.put("x", chartMetrics.get("x") + this.axes!!.axisMetrics.get("YAXISLABELOFFSET") as Double + this.axes!!.axisMetrics.get("YAXISTITLEOFFSET") as Double) +chartMetrics.put("y", chartMetrics.get("y") + chartMetrics.get("TITLEOFFSET")) +// TODO: seems that w is different doesn't need decrementing by x?? check out ... +chartMetrics.put("w", chartMetrics.get("w") - this.axes!!.axisMetrics.get("YAXISLABELOFFSET") as Double) +chartMetrics.put("h", chartMetrics.get("canvash") - chartMetrics.get("y") - this.axes!!.axisMetrics.get("XAXISLABELOFFSET") as Double - this.axes!!.axisMetrics.get("XAXISTITLEOFFSET") as Double - 10.0) +//io.starter.toolkit.Logger.log("After Adjustments: x:" + chartMetrics.get("x") + " w:" + chartMetrics.get("w") + " cw:" + chartMetrics.get("canvasw") + " y:" + chartMetrics.get("y") + " h:" + chartMetrics.get("h") + " ch:" + chartMetrics.get("canvash")); + +var cw = chartMetrics.get("canvasw") +// rhs legend has to have some extra adjustments to w and/or canvasw ... +if (lcoords!![5] == Legend.RIGHT) +{ +val legendBeg = lcoords!![0] - (chartMetrics.get("w") + chartMetrics.get("x")) +val legendEnd = cw - (lcoords!![0].toDouble() + lcoords!![2].toDouble() + adjust) + +if (legendBeg < 0 || legendEnd < 0) +{ // try to adjust +if (legendEnd < 0) +{ +chartMetrics.put("canvasw", (lcoords!![0].toDouble() + lcoords!![2].toDouble() + adjust)) +cw = (lcoords!![0].toDouble() + lcoords!![2].toDouble() + adjust) +} +// if (legendBeg < 0) +// chartMetrics.put("w", lcoords[0]-10.0-chartMetrics.get("x")); +} +if (this.axes!!.hasAxis(ChartConstants.XAXIS) && ldist > 0) +// pie, donut, don't +// ensure distance between legend box and edge of plot area remains the same +chartMetrics.put("w", lcoords!![0].toDouble() - chartMetrics.get("x") - ldist) +//io.starter.toolkit.Logger.log("Adjusted LCoords: " + Arrays.toString(lcoords)); + +} +else +{ +val w = chartMetrics.get("w") + chartMetrics.get("x") +if (w > cw) +chartMetrics.put("w", chartMetrics.get("canvasw") - chartMetrics.get("x") - 10.0) +} + +metricsDirty = false +} +catch (e:Exception) { +Logger.logErr("Chart.getMetrics: " + e.toString()) +} + +} +return chartMetrics +} + +companion object { +internal val serialVersionUID = 6702247464633674375L + +/** + * generic method to find a specific record in the list of recs in chartarr + * + * @param chartArr + * @param c class of record to find + * @return biffrec or null +*/ +fun findRec(chartArr:ArrayList<*>, c:Class<*>):BiffRec? { +for (i in chartArr.indices) +{ +val b = chartArr.get(i) as BiffRec +if (b.javaClass == c) +return b +} +return null +} + +/** + * generic method to find a specific record in the list of recs in chartArr + * + * @param c class of record to find + * @return position of record +*/ +fun findRecPosition(chartArr:ArrayList<*>, c:Class<*>):Int { +for (i in chartArr.indices) +{ +val b = chartArr.get(i) as BiffRec +if (b.javaClass == c) +return i +} +return -1 +} +} +}/** + * returns the 3d record of the desired chart + * + * @param nChart 0=default, 1-9= overlay charts + * @return +*/ + diff --git a/src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.java b/src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.kt similarity index 72% rename from src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.java rename to src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.kt index 9ab5fe3..12c9edf 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.java +++ b/src/main/java/io/starter/formats/XLS/charts/Chart3DBarShape.kt @@ -20,15 +20,16 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** * Chart3DBarShape * This record specifies the shape of the data points in a bar or column chart group. * This record is used only for a bar or column chart group and MUST be ignored for all other chart group - *

                      + * + * * 1 1 riser specifies the shape of the base of the data points in a bar or column chart group * 0 =base is a rectangle. 1 =base is an ellipse * 2 1 taper specifies how the data points in a bar or column chart group taper from base to tip. @@ -36,22 +37,9 @@ * 1= The data points of the bar or column chart group taper to a point at the maximum value of each data point * 2= he data points of the bar or column chart group taper towards a projected point at the position of the maximum value of all data points in the chart group, but are clipped at the value of each data point. */ -public class Chart3DBarShape extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3029030180040933082L; - byte riser, taper; - - public void init() { - super.init(); - riser = this.getByteAt(0); - taper = this.getByteAt(1); - } - - public Chart3DBarShape() { - setData(new byte[2]); - } +class Chart3DBarShape : GenericChartObject(), ChartObject { + internal var riser: Byte = 0 + internal var taper: Byte = 0 /** * Bar Shapes @@ -62,13 +50,9 @@ public Chart3DBarShape() { * public static final int SHAPEPYRAMIDTOMAX= 516; * public static final int SHAPECONETOMAX= 517; */ - public short getShape() { - return ByteTools.readShort(riser, taper); - } - /** * set the shape of the bars - *
                      the shape is as follows: + *

                      the shape is as follows: * public static final int SHAPECOLUMN= 0; // default * public static final int SHAPECYLINDER= 1; * public static final int SHAPEPYRAMID= 256; @@ -76,12 +60,31 @@ public short getShape() { * public static final int SHAPEPYRAMIDTOMAX= 516; * public static final int SHAPECONETOMAX= 517; */ - public void setShape(short shape) { - byte[] b = ByteTools.shortToLEBytes(shape); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - riser = this.getByteAt(0); - taper = this.getByteAt(1); + var shape: Short + get() = ByteTools.readShort(riser.toInt(), taper.toInt()) + set(shape) { + val b = ByteTools.shortToLEBytes(shape) + this.data[0] = b[0] + this.data[1] = b[1] + riser = this.getByteAt(0) + taper = this.getByteAt(1) + } + + override fun init() { + super.init() + riser = this.getByteAt(0) + taper = this.getByteAt(1) + } + + init { + data = ByteArray(2) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 3029030180040933082L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartAxes.java b/src/main/java/io/starter/formats/XLS/charts/ChartAxes.java deleted file mode 100644 index 0e00896..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ChartAxes.java +++ /dev/null @@ -1,1043 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.Font; -import io.starter.formats.cellformat.CellFormatFactory; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Stack; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * An axis group is a set of axes that specify a coordinate system, a set of chart groups that are plotted using these and the plot area that defines - * where the axes are rendered on the chart. - *

                      - * In BIFF8, the AxisParent record governs the Axis Group. A typical arrangement of records is: - * AxisParent - * Pos - * Axis (X) - * Axis (Y) - * PlotArea - * Frame - * (Chart Group is one or more of:) - * ChartFormat (defines the chart type) + Legend - */ -public class ChartAxes implements ChartConstants, Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7862828186455339066L; - private AxisParent ap; - ArrayList axes = new ArrayList(); - HashMap axisMetrics = new HashMap(); // holds important axis metrics such as xAxisReversed, xPattern, for outside use -/** - * The AxisParent record specifies if the axis group is the primary axis group or the secondary axis group on a chart. - * Often the axes of the primary axis group are displayed to the left and bottom sides of the plot area, while axes of the secondary axis group are displayed on the right and top sides of the plot area. - The Pos record specifies the position and size of the outer plot area. The outer plot area is the bounding rectangle that includes the axis labels, the axis titles, - and data table of the chart. This record MUST be ignored on a secondary axis group. */ - /** The PlotArea record and the sequence of records that conforms to the FRAME rule in the sequence of records that conform to the AXES rule specify the properties of the inner plot area. The inner plot area is the rectangle bounded by the chart axes. - * The PlotArea record MUST not exist on a secondary axis group. - */ - /** - * @param ap - */ - public ChartAxes(AxisParent ap) { - this.ap = ap; - } - - /** - * store each axis - * - * @param a - */ - public void add(Axis a) { - axes.add(a); - a.setAP(this.ap); // ensure axis is linked to it's parent AxisParent - } - - public void setTd(int axisType, TextDisp td) { - Axis a = getAxis(axisType, false); - if (a != null) // shouldn't - a.setTd(td); - } - - /** - * returns true if axisType is found on chart - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return - */ - public boolean hasAxis(int axisType) { - for (int i = 0; i < axes.size(); i++) { - if (((Axis) axes.get(i)).getAxis() == axisType) - return true; - } - return false; - } - - public void createAxis(int axisType) { - Axis a = getAxis(axisType, true); - return; - } - - /** - * Return the desired axis - * @return Axis - * - public Axis getAxis(int axisType) { - return ap.getAxis(axisType); - }*/ - - /** - * returns the desired axis if it exists - * if bCreateIfNecessary, will create if it doesn't exist - * otherwise returns null - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param bCreateIfNecessary - * @return - */ - private Axis getAxis(int axisType, boolean bCreateIfNecessary) { - for (int i = 0; i < axes.size(); i++) { - if (((Axis) axes.get(i)).getAxis() == axisType) - return ((Axis) axes.get(i)); - } - if (bCreateIfNecessary) { - axes.add(ap.getAxis(axisType, bCreateIfNecessary)); - return (Axis) axes.get(axes.size() - 1); - } - return null; - } - - /** - * returns true if Axis is reversed - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return - */ - protected boolean isReversed(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.isReversed(); - return false; - } - - /** - * returns the number format for the desired axis - * - * @param axisType - * @return number format string - */ - protected String getNumberFormat(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.getNumberFormat(); - return null; - } - - /** - * remove ALL axes from this chart - */ - public void removeAxes() { - ap.removeAxes(); - while (axes.size() > 0) { - axes.remove(0); - } - } - - /** - * remove the desired axis + associated records - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - */ - public void removeAxis(int axisType) { - ap.removeAxis(axisType); - for (int i = 0; i < axes.size(); i++) { - if (((Axis) axes.get(i)).getAxis() == axisType) { - axes.remove(i); - break; - } - } - } - - /** - * return the coordinates of the outer plot area (plot + axes + labels) in pixels - * - * @return - */ - public float[] getPlotAreaCoords(float w, float h) { - Pos p = (Pos) Chart.findRec(ap.chartArr, Pos.class); - if (p != null) { - float[] plotcoords = p.getCoords(); - if (plotcoords != null) { - plotcoords[0] = Pos.convertFromSPRC(plotcoords[0], w, 0); - plotcoords[1] = Pos.convertFromSPRC(plotcoords[1], 0, h); - plotcoords[2] = Pos.convertFromSPRC(plotcoords[2], w, 0); // SPRC units see Pos - plotcoords[3] = Pos.convertFromSPRC(plotcoords[3], 0, h); // SPRC units see Pos - return plotcoords; - } - } - return null; - } - - /** - * returns the axis area background color hex string, or null if not set - * - * @return color hex string - */ - public String getPlotAreaBgColor() { - return ap.getPlotAreaBgColor(); - } - - - /** - * sets the plot area background color - * - * @param bg color int - */ - public void setPlotAreaBgColor(int bg) { - ap.setPlotAreaBgColor(bg); - } - - /** - * adds a border around the plot area with the desired line width and line color - * - * @param lw - * @param lclr - */ - public void setPlotAreaBorder(int lw, int lclr) { - ap.setPlotAreaBorder(lw, lclr); - } - - - /** - * obtain the desired axis' label and other options, if present, in XML form - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return String XML representation of desired axis' label and options - * @see ObjectLink - */ - public String getAxisOptionsXML(int axisType) { - return ap.getAxisOptionsXML(axisType); - } - - /** - * Return the Axis Title, or "" if none - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - */ - public String getTitle(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.getTitle(); - return ""; - } - - /** - * set the axis title string - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param Title - */ - public void setTitle(int axisType, String Title) { - Axis a = getAxis(axisType, false); - if (a != null) { - boolean defaultIsVert = (axisType == YAXIS && ("".equals(a.getTitle()))); - a.setTitle(Title); - if (defaultIsVert) a.getTd().setRotation(90); - } - } - - /** - * return the rotation of the axis labels, if any - * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise - * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) - * 255 Text top-to-bottom with letters upright - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - */ - public int getLabelRotation(int axisType) { - int rot = 0; - Axis a = getAxis(axisType, false); - if (a != null) { - Tick t = (Tick) Chart.findRec(a.chartArr, Tick.class); - if (t != null) { // shoudn't - rot = t.getRotation(); - switch (rot) { - case 0: - break; - case 1: // not correct ... - rot = 180; - break; - case 2: - rot = -90; - break; - case 3: - rot = 90; - break; - } - } - } - return rot; - } - - /** - * return the rotation of the axis Title, if any - * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise - * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) - * 255 Text top-to-bottom with letters upright - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - */ - public int getTitleRotation(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) { - TextDisp td = a.getTd(); - return td.getRotation(); - } - return 0; - } - - /** - * return the coordinates, in pixels, of the title text area, if possible - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return float[] - */ - public float[] getCoords(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) { - TextDisp td = a.getTd(); - return td.getCoords(); - } - return new float[]{0, 0, 0, 0}; - } - - /** - * Returns the YAXIS scale elements (min value, max value, minor (tick) scale, major (tick) scale) - * the scale elements are calculated from the minimum and maximum values on the chart - * - * @param ymin the minimum value of all the series values - * @param ymax the maximum value of all the series values - * @return double[] min, max, minor, major - */ - public double[] getMinMax(double ymin, double ymax) { - return getMinMax(ymin, ymax, YAXIS); - } - - /** - * Returns the scale values of the the desired Value axis - *
                      Scale elements: (min value, max value, minor (tick) scale, major (tick) scale) - *

                      The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well - * - * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, ChartHandle.ZAXIS - * @return int Miminum Scale value for the desired axis - * @see getAxisMinScale() - */ - public double[] getMinMax(double ymin, double ymax, int axisType) { - Axis a = getAxis(axisType, false); - double[] ret = new double[4]; - if (a != null) { - ValueRange v = (ValueRange) Chart.findRec(a.getChartRecords(), ValueRange.class); - if (v.isAutomaticScale()) // major crazy Excel "automatic max/min/tickmarks calc" ... a monster - v.setMaxMin(ymax, ymin); - ret[0] = v.getMin(); - ret[1] = v.getMax(); - ret[2] = v.getMinorTick(); - ret[3] = v.getMajorTick(); - } - return ret; - } - - /** - * Returns the major tick unit of the desired Value axis - *

                      The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return int major tick unit - * @see getAxisMajorUnit() - */ - public int getAxisMajorUnit(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.getMajorUnit(); - return 10; // TODO throw exception instead ??? - } - - /** - * Returns the minor tick unit of the desired Value axis - *

                      The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return int - minor tick unit of the desired axis - * @see getAxisMinorUnit() - */ - public int getAxisMinorUnit(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.getMinorUnit(); - return 1; // TODO throw exception instead - } - - /** - * Sets the maximum scale value of the desired Value axis - *

                      The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well - *

                      Note: The default scale setting for charts is known as Automatic Scaling - *
                      When data changes, the chart automatically adjusts the scale as necessary - *
                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param int MaxValue - desired maximum value of the desired axis - * @see setAxisMax(int MaxValue) - */ - public void setAxisMax(int axisType, int MaxValue) { - Axis a = getAxis(axisType, false); - if (a != null) - a.setMaxScale(MaxValue); - // TODO: throw exception if no axis? - } - - /** - * Sets the minimum scale value of the desired Value axis - *

                      The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well - *

                      Note: The default setting for charts is known as Automatic Scaling - *
                      When data values change, the chart automatically adjusts the scale as necessary - *
                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param int MinValue - the desired Minimum scale value - * @see setAxisMin(int MinValue) - */ - public void setAxisMin(int axisType, int MinValue) { - Axis a = getAxis(axisType, false); - if (a != null) - a.setMinScale(MinValue); - // TODO: throw exception if no axis? - } - - /** - * Sets the automatic scale option on or off for the desired Value axis - *

                      The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well - *

                      Automatic Scaling automatically sets the scale maximum, minimum and tick units - * upon data changes, and is the default setting for charts - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param boolean b - true if set Automatic scaling on, false otherwise - * @see setAxisAutomaticScale(boolean b) - */ - public void setAxisAutomaticScale(int axisType, boolean b) { - Axis a = getAxis(axisType, false); - if (a != null) - a.setAutomaticScale(b); - } - - /** - * Returns true if the desired Value axis is set to automatic scale - *

                      The Value axis contains numbers rather than labels, and is normally the Y axis, - * but Scatter and Bubble charts may have a value axis on the X Axis as well - *

                      Note: The default setting for charts is known as Automatic Scaling - *
                      When data changes, the chart automatically adjusts the scale as necessary - *
                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return boolean true if Automatic Scaling is turned on - * @see getAxisAutomaticScale() - */ - public boolean getAxisAutomaticScale(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.isAutomaticScale(); // group min and max together ... - return false; // TODO: throw exception? - } - - /** - * Returns true if the Y Axis (Value axis) is set to automatic scale - *

                      The default setting for charts is known as Automatic Scaling - *
                      When data changes, the chart automatically adjusts the scale (minimum, maximum values - * plus major and minor tick units) as necessary - * - * @return boolean true if Automatic Scaling is turned on - * @see getAxisAutomaticScale(int axisType) - */ - public boolean getAxisAutomaticScale() { - Axis a = getAxis(YAXIS, false); - if (a != null) - return a.isAutomaticScale(); // group min and max together ... - return false; // TODO: throw exception? - } - - /** - * Sets the automatic scale option on or off for the Y Axis (Value axis) - *

                      Automatic Scaling will automatically set the scale maximum, minimum and tick units - * upon data changes, and is the default chart setting - * - * @param b - * @see setAxisAutomaticScale(int axisType boolean b) - */ - public void setAxisAutomaticScale(boolean b) { - Axis a = getAxis(YAXIS, false); - if (a != null) - a.setAutomaticScale(b); - } - - /** - * Sets the maximum value of the Y Axis (Value Axis) Scale - *

                      Note: The default scale setting for charts is known as Automatic Scaling - *
                      When data changes, the chart automatically adjusts the scale as necessary - *
                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling - * - * @param int MaxValue - the desired maximum scale value - * @see ChartHandle.setAxisMax(int axisType, int MaxValue) - */ - public void setAxisMax(int MaxValue) { - Axis a = getAxis(YAXIS, false); - if (a != null) - a.setMaxScale(MaxValue); - // TODO: throw exception if no axis? - } - - /** - * Sets the minimum value of the Y Axis (Value Axis) Scale - *

                      Note: The default setting for charts is known as Automatic Scaling - *
                      When data changes, the chart automatically adjusts the scale as necessary - *
                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling - * - * @param int MinValue - the desired minimum scale value - * @see ChartHandle.setAxisMin(int axisType, int MinValue) - */ - public void setAxisMin(int MinValue) { - Axis a = getAxis(YAXIS, false); - if (a != null) - a.setMinScale(MinValue); - // TODO: throw exception if no axis? - } - - /** - * returns the SVG necesssary to define the desired axis - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @return - */ - public String getSVG(int axisType, java.util.Map chartMetrics, Object[] categories) { - Axis a = getAxis(axisType, false); - if (a != null) { - return a.getSVG(this, chartMetrics, categories); - } - return null; - } - - /** - * returns the OOXML necessary to define the desired axis - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param type - * @param id - * @param crossId - * @return - */ - public String getOOXML(int axisType, int type, String id, String crossId) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.getOOXML(type, id, crossId); - return ""; - } - - /** - * set the font index for this Axis (for title) - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param fondId - */ - public void setTitleFont(int axisType, int fondId) { - Axis a = getAxis(axisType, false); - if (a != null) - a.setFont(fondId); - } - - /** - * returns the font for the title for the desired axis - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return - */ - public Font getTitleFont(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.getFont(); - return null; - } - - /** - * returns the label font for the desired axis. - * If not explicitly set, returns the default font - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return - */ - public Font getLabelFont(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.getLabelFont(); - return null; - } - - - public JSONObject getJSON(int axisType, io.starter.OpenXLS.WorkBookHandle wbh, int chartType, double yMax, double yMin, int nSeries) { - return ap.getAxis(axisType, false).getJSON(wbh, chartType, yMax, yMin, nSeries); - } - - - /** - * sets an option for this axis - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param op option name; one of: CatCross, LabelCross, Marks, CrossBetween, CrossMax, - * MajorGridLines, AddArea, AreaFg, AreaBg - * or Linked Text Display options: - * Label, ShowKey, ShowValue, ShowLabelPct, ShowPct, - * ShowCatLabel, ShowBubbleSizes, TextRotation, Font - * @param val option value - */ - public void setChartOption(int axisType, String op, String val) { - Axis a = getAxis(axisType, false); - if (a != null) - a.setChartOption(op, val); - } - - - /** - * returns the Axis Label Placement or position as an int - *

                      One of: - *
                      Axis.INVISIBLE - axis is hidden - *
                      Axis.LOW - low end of plot area - *
                      Axis.HIGH - high end of plot area - *
                      Axis.NEXTTO- next to axis (default) - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @return int - one of the Axis Label placement constants above - */ - public int getAxisPlacement(int axisType) { - Axis a = getAxis(axisType, false); - if (a != null) - return a.getAxisPlacement(); - return -1; - } - - /** - * sets the axis labels position or placement to the desired value (these match Excel placement options) - *

                      Possible options: - *
                      Axis.INVISIBLE - hides the axis - *
                      Axis.LOW - low end of plot area - *
                      Axis.HIGH - high end of plot area - *
                      Axis.NEXTTO- next to axis (default) - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param Placement - int one of the Axis placement constants listed above - */ - public void setAxisPlacement(int axisType, int placement) { - Axis a = getAxis(axisType, false); - if (a != null) - a.setAxisPlacement(placement); - } - - - /** - * parse OOXML axis element - * - * @param axisType one of: XAXIS, YAXIS, ZAXIS - * @param xpp XmlPullParser positioned at correct elemnt - * @param axisTag catAx, valAx, serAx, dateAx - * @param lastTag Stack of element names - */ - public void parseOOXML(int axisType, XmlPullParser xpp, String tnm, Stack lastTag, WorkBookHandle bk) { - Axis a = getAxis(axisType, true); - if (a != null) { - a.removeTitle(); - a.setChartOption("MajorGridLines", "false"); // initally, remove any - will be set in parseOOXML if necessary - a.parseOOXML(xpp, tnm, lastTag, bk); - } - } - - public void close() { - axes.clear(); - axes = null; - ap = null; - axisMetrics.clear(); - } - - - /** - * returns already-set axisMetrics - *
                      Used only after calling chart.getMetrics - * - * @return axisMetrics -- map of useful chart display metrics - *
                      Contains: - *
                      - * XAXISLABELOFFSET double - * XAXISTITLEOFFSET double - * YAXISLABELOFFSET double - * YAXISTITLEOFFSET double - * xAxisRotate integer x axis lable rotation angle - * yPattern string numeric pattern for y axis - * xPattern string numeric pattern for x axis - * double major; // tick units - * double minor; - */ - public HashMap getMetrics() { - return axisMetrics; - } - - /** - * returns a specific axis Metric - *
                      Use only after calling chart.getMetrics - * - * @param metric String metric option one of - *
                      - * XAXISLABELOFFSET double - * XAXISTITLEOFFSET double - * YAXISLABELOFFSET double - * YAXISTITLEOFFSET double - * xAxisRotate integer x axis lable rotation angle - * yPattern string numeric pattern for y axis - * xPattern string numeric pattern for x axis - * double major; // tick units - * double minor; - * @return - */ - public Object getMetric(String metric) { - return axisMetrics.get(metric); - } - - /** - * generate Axis Metrics -- minor, major scale, title and label offsets, etc. - * for use in SVG generation and other operations - * - * @param charttype chart type constant - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param plotcoords == if set, plot-area coordinates (may not be set if automatic) - * @return axisMetrics Map: contains the important axis metrics for use in chart display - */ - public HashMap getMetrics(int charttype, HashMap chartMetrics, float[] plotcoords, Object[] categories) { - double[] minmax = this.getMinMax(chartMetrics.get("min"), chartMetrics.get("max")); // sets min/max on Value axis, based upon axis settings and actual minimun and maximum values - chartMetrics.put("min", minmax[0]); // set new values, if any - chartMetrics.put("max", minmax[1]); // "" - axisMetrics.put("minor", minmax[2]); - axisMetrics.put("major", minmax[3]); - axisMetrics.put("xAxisReversed", this.isReversed(XAXIS)); // default is Value axis crosses at bottom. reverse= crosses at top - axisMetrics.put("xPattern", this.getNumberFormat(XAXIS)); - axisMetrics.put("yAxisReversed", this.isReversed(YAXIS)); // if value (Y), default is on LHS; reverse= RHS - axisMetrics.put("yPattern", this.getNumberFormat(YAXIS)); - axisMetrics.put("XAXISLABELOFFSET", 0.0); - axisMetrics.put("XAXISTITLEOFFSET", 0.0); - axisMetrics.put("YAXISLABELOFFSET", 0.0); - axisMetrics.put("YAXISTITLEOFFSET", 0.0); - - // X axis title Offset - if (!this.getTitle(XAXIS).equals("")) { - io.starter.formats.XLS.Font ef = this.getTitleFont(XAXIS); - java.awt.Font f = new java.awt.Font(ef.getFontName(), ef.getFontWeight(), (int) ef.getFontHeightInPoints()); - AtomicInteger h = new AtomicInteger(0); - double w = getRotatedWidth(f, h, this.getTitle(XAXIS), this.getTitleRotation(XAXIS)); - axisMetrics.put("XAXISTITLEOFFSET", new Double(h.intValue() + 10)); /* add a little padding */ - } - - // Y Axis Title Offsets - if (this.hasAxis(YAXIS)) { - String title = this.getTitle(YAXIS); - if (!title.equals("")) { - io.starter.formats.XLS.Font ef = this.getTitleFont(YAXIS); - java.awt.Font f = new java.awt.Font(ef.getFontName(), ef.getFontWeight(), (int) ef.getFontHeightInPoints()); - AtomicInteger h = new AtomicInteger(0); - double w = getRotatedWidthVert(f, h, this.getTitle(YAXIS), this.getTitleRotation(YAXIS)); - axisMetrics.put("YAXISTITLEOFFSET", w); // add padding -/* - int rot= this.getTitleRotation(YAXIS); //0-180 or 255 (=vertical with letters upright) - if (rot==90 || rot==255 || charttype==BARCHART) {// Y axis title is almost always rotated, so use font height as offset - if (charttype!=BARCHART) - axisMetrics.put("YAXISTITLEOFFSET", Math.ceil(ef.getFontHeightInPoints()*xpaddingfactor)); - else - axisMetrics.put("XAXISTITLEOFFSET", Math.ceil(ef.getFontHeightInPoints()*ypaddingfactortitle)); - } else {// get width in desired rotation **** - if (rot > 180) rot-=180; // ensure angle is 0-90 - java.awt.Font f= new java.awt.Font(ef.getFontName(), ef.getFontWeight(), (int)ef.getFontHeightInPoints()); - double len= StringTool.getApproximateStringWidth(f, ExcelTools.getFormattedStringVal(title, null)); - axisMetrics.put("YAXISTITLEOFFSET", Math.ceil(len*(Math.cos(Math.toRadians(rot))))); - }*/ - } else - axisMetrics.put("YAXISTITLEOFFSET", 20.0); // no axis title- add a little padding from edge - } - // Label offsets - StringBuffer[] series = new StringBuffer[0]; - try { - double major = (Double) getMetric("major"); - double min = chartMetrics.get("min"); - double max = chartMetrics.get("max"); - // get java awt font so can compute approximate width of largest y axis label in order to compute YAXISLABELOFFSET - if (major > 0) { - int nSeries = 0; - if (min == 0) // usual case - nSeries = ((major != 0) ? (int) (max / major) + 1 : 0); - else - nSeries = ((major != 0) ? (int) ((max - min) / major) + 1 : 0); - nSeries = Math.abs(nSeries); - series = new StringBuffer[nSeries]; - if (Math.floor(major) != major) { // contains a fractional part ... avoid java floating point issues - // ensure y value matches scale/avoid double precision issues ... sigh ... - String s = String.valueOf(major); - int z = s.indexOf("."); - int scale = 0; - if (z != -1) - scale = s.length() - (z + 1); - z = 0; - for (double i = min; i <= max; i += major) { - java.math.BigDecimal bd = new java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP); - series[z++] = new StringBuffer( - CellFormatFactory.fromPatternString( - (String) getMetric("yPattern")).format(bd.toString())); - } - } else { // usual case of an int scale - int z = 0; - for (double i = min; i <= max; i += major) - series[z++] = new StringBuffer(CellFormatFactory.fromPatternString((String) getMetric("yPattern")).format(i)); - } - } - } catch (Exception e) { - Logger.logWarn("ChartAxes.getMetrics. Error obtaining Series: " + e.toString()); - } - - // Label Offsets ... - if (this.hasAxis(XAXIS) && charttype != RADARCHART && charttype != RADARAREACHART) { //(Pie, donut, etc. don't have axes labels so disregard - Object[] s; // Determine X Axis Label offsets - double width; - AtomicInteger rot; - io.starter.formats.XLS.Font lf; - String pattern; - if (charttype != BARCHART) { - s = categories; - pattern = (String) getMetric("xPattern"); - } else { // bar chart - series on x axis - s = series; - pattern = (String) getMetric("yPattern"); - } - width = (chartMetrics.get("w") / s.length) - 6; // ensure a bit of padding on either side - lf = this.getLabelFont(XAXIS); - rot = new AtomicInteger(this.getLabelRotation(XAXIS)); // if rot==0 and xaxis labels do not fit in width, a forced rotation will happen. ... - double off = getLabelOffsets(lf, width, s, rot, pattern, true); - axisMetrics.put("xAxisRotate", rot.intValue()); // possibly changed when calculating label offsets - axisMetrics.put("XAXISLABELOFFSET", off); - } - if (this.hasAxis(YAXIS) && charttype != RADARCHART && charttype != RADARAREACHART) { //(Pie, Donut, etc. don't have axes labels so disregard - // for Y axis, determine width of labels and use as offset (except for bar charts, use height as offset) - Object[] s; - double width; - AtomicInteger rot; - io.starter.formats.XLS.Font lf; - String pattern; - if (charttype != BARCHART) { - s = series; - pattern = (String) getMetric("yPattern"); - } else { // bar chart- categories on y axis - s = categories; - pattern = (String) getMetric("xPattern"); - } - rot = new AtomicInteger(this.getLabelRotation(YAXIS)); // if rot==0 and xaxis labels do not fit in width, a forced rotation will happen. ... - lf = this.getLabelFont(YAXIS); - width = (chartMetrics.get("w") / 2) - 10; // a good guess? - double off = getLabelOffsets(lf, width, s, rot, pattern, false); - axisMetrics.put("yAxisRotate", rot.intValue()); // possibly changed when calculating label offsets - axisMetrics.put("YAXISLABELOFFSET", off); // with padding - } - return axisMetrics; - } - - /** - * returns the offset of the labels for the axis given the length of the label strings, the width of the - *
                      this will attempt to break apart longer strings; if so will increment offset to accomodate multiple lines - * - * @param lf label font - * @param width max width of labels - * @param strings label strings - * @param rot desired rotation, if any (0 if none) - * @param horiz true if this is a horizontal axis - * @return - */ - private double getLabelOffsets(io.starter.formats.XLS.Font lf, double width, Object[] strings, AtomicInteger rot, String pattern, boolean horiz) { - double retwidth = 0; - java.awt.Font f = null; - double h = 0; - try { - // get awt Font so can compute and fit category in width - f = new java.awt.Font(lf.getFontName(), lf.getFontWeight(), (int) lf.getFontHeightInPoints()); - for (int i = 0; i < strings.length; i++) { - double w; - AtomicInteger height = new AtomicInteger(0); - StringBuffer s = null; - try { - s = new StringBuffer(CellFormatFactory.fromPatternString(pattern).format(strings[i].toString())); - } catch (IllegalArgumentException e) { // trap error formatting - s = new StringBuffer(strings[i].toString()); - } - if (horiz) // on horizontal axis - w = getRotatedWidth(f, height, s.toString(), rot.intValue()); - else // on vertical axis - w = getRotatedWidthVert(f, height, s.toString(), rot.intValue()); - h = Math.max(height.intValue(), h); - w = addLinesToFit(f, s, rot, w, width, height); - if (w > width) - width = w; - - strings[i] = s; - retwidth = Math.max(w, retwidth); - } - } catch (Exception e) { - } - if (horiz) - return h; - else - return retwidth; -// return offset + h; - } - - /** - * get the width of string s in font f rotated by rot (0, 90, -90, 180) - * - * @param f Font to display s in - * @param s string to display - * @param rot rotation (0= none) - */ - private double getRotatedWidth(java.awt.Font f, AtomicInteger height, String s, int rot) { - double retWidth = 0; - String[] slines = s.split("\n"); - for (int i = 0; i < slines.length; i++) { - double width; - if (rot == 0) { - width = StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])); - height.set((int) Math.ceil(f.getSize() * 3)); // width of the font + padding - } else if (Math.abs(rot) == 90) { - width = Math.ceil(f.getSize() * 2); // width of the font + padding - height.set((int) Math.max(height.intValue(), StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])))); - } else { // 45 - width = StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])); - width = Math.ceil(width * (Math.cos(Math.toRadians(rot)))); - height.set((int) Math.ceil(width * (Math.sin(Math.toRadians(rot))))); - } - retWidth = Math.max(width, retWidth); - } - return retWidth; - } - - /** - * get the width of string s in font f rotated by rot (0, 90, -90, 180) - * on a Vertical axis - * - * @param f Font to display s in - * @param s string to display - * @param rot rotation (0= none) - */ - private double getRotatedWidthVert(java.awt.Font f, AtomicInteger height, String s, int rot) { - double retWidth = 0; - String[] slines = s.split("\n"); - for (int i = 0; i < slines.length; i++) { - double width; - if (Math.abs(rot) == 90) { // means VERTICAL orientation - default - height.set((int) StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i]))); - width = Math.ceil(f.getSize() * 2); // width of the font + padding - } else if (rot == 0) { // means HORIZONTAL on vertical axis - height.set((int) Math.ceil(f.getSize() * 3)); // width of font - width = Math.max(height.intValue(), StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i]))); - } else { // 45 - width = StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])); - width = Math.ceil(width * (Math.cos(Math.toRadians(rot)))); - height.set((int) Math.ceil(width * (Math.sin(Math.toRadians(rot))))); - } - retWidth = Math.max(width, retWidth); - } - return retWidth; - } - - /** - * if formatted width of string doesn't fit into width, break apart at spaces with new lines - * return - * - * @param f font to display string in - * @param s target String - * @param rot rotation (0, 45, 90, -90 or 180) - * @param len formatted string length taking into account display font - * @param width maximum width to display string - * @return - */ - private double addLinesToFit(java.awt.Font f, StringBuffer s, AtomicInteger rot, double len, double width, AtomicInteger height) { - double retLen = Math.min(width, len); - String str = s.toString().trim(); - - while (len > width) { - int lastSpace = -1; - int j = s.lastIndexOf("\n") + 1; - len = -1; - while (len < width && j < str.length()) { - len += StringTool.getApproximateCharWidth(f, str.charAt(j)); - if (str.charAt(j) == ' ') - lastSpace = j; - j++; - } - if (len < width) - break; // got it - - if (lastSpace == -1) { // no spaces to break apart via \n's - rotate! - if (str.indexOf(' ') == -1) { - // see if string will fit in 45 degree rotation - if (rot.intValue() != -90) { - rot.set(45); - len = getRotatedWidth(f, height, str, rot.intValue()); - if (len > width) { // then all's fine - } else // doesn't fit in 45, must be 90 degrees - rot.set(-90); - } - if (rot.intValue() == -90) { - len = getRotatedWidth(f, height, str, rot.intValue()); - } - retLen = Math.max(len, retLen); - break; - } else lastSpace = s.toString().indexOf(' '); - } - s.replace(lastSpace, lastSpace + 1, "\n"); // + str.substring(lastSpace+1)); - str = s.toString(); - } - return retLen; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartAxes.kt b/src/main/java/io/starter/formats/XLS/charts/ChartAxes.kt new file mode 100644 index 0000000..bfaaa19 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ChartAxes.kt @@ -0,0 +1,1042 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.Font +import io.starter.formats.cellformat.CellFormatFactory +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool +import org.json.JSONObject +import org.xmlpull.v1.XmlPullParser + +import java.io.Serializable +import java.util.ArrayList +import java.util.HashMap +import java.util.Stack +import java.util.concurrent.atomic.AtomicInteger + +/** + * An axis group is a set of axes that specify a coordinate system, a set of chart groups that are plotted using these and the plot area that defines + * where the axes are rendered on the chart. + * + * + * In BIFF8, the AxisParent record governs the Axis Group. A typical arrangement of records is: + * AxisParent + * Pos + * Axis (X) + * Axis (Y) + * PlotArea + * Frame + * (Chart Group is one or more of:) + * ChartFormat (defines the chart type) + Legend + */ +class ChartAxes +/** + * The AxisParent record specifies if the axis group is the primary axis group or the secondary axis group on a chart. + * Often the axes of the primary axis group are displayed to the left and bottom sides of the plot area, while axes of the secondary axis group are displayed on the right and top sides of the plot area. + * The Pos record specifies the position and size of the outer plot area. The outer plot area is the bounding rectangle that includes the axis labels, the axis titles, + * and data table of the chart. This record MUST be ignored on a secondary axis group. */ +/** The PlotArea record and the sequence of records that conforms to the FRAME rule in the sequence of records that conform to the AXES rule specify the properties of the inner plot area. The inner plot area is the rectangle bounded by the chart axes. + * The PlotArea record MUST not exist on a secondary axis group. + */ +/** + * @param ap + */ +(private var ap: AxisParent?) : ChartConstants, Serializable { + internal var axes: ArrayList<*>? = ArrayList() + internal var axisMetrics: HashMap = HashMap() // holds important axis metrics such as xAxisReversed, xPattern, for outside use + + /** + * returns the axis area background color hex string, or null if not set + * + * @return color hex string + */ + val plotAreaBgColor: String? + get() = ap!!.plotAreaBgColor + + /** + * Returns true if the Y Axis (Value axis) is set to automatic scale + * + * The default setting for charts is known as Automatic Scaling + *

                      When data changes, the chart automatically adjusts the scale (minimum, maximum values + * plus major and minor tick units) as necessary + * + * @return boolean true if Automatic Scaling is turned on + * @see getAxisAutomaticScale + */ + /** + * Sets the automatic scale option on or off for the Y Axis (Value axis) + * + * Automatic Scaling will automatically set the scale maximum, minimum and tick units + * upon data changes, and is the default chart setting + * + * @param b + * @see setAxisAutomaticScale + */ + // group min and max together ... + // TODO: throw exception? + var axisAutomaticScale: Boolean + get() { + val a = getAxis(ChartConstants.YAXIS, false) + return a?.isAutomaticScale ?: false + } + set(b) { + val a = getAxis(ChartConstants.YAXIS, false) + if (a != null) + a.isAutomaticScale = b + } + + + /** + * returns already-set axisMetrics + *

                      Used only after calling chart.getMetrics + * + * @return axisMetrics -- map of useful chart display metrics + *

                      Contains: + *

                      + * XAXISLABELOFFSET double + * XAXISTITLEOFFSET double + * YAXISLABELOFFSET double + * YAXISTITLEOFFSET double + * xAxisRotate integer x axis lable rotation angle + * yPattern string numeric pattern for y axis + * xPattern string numeric pattern for x axis + * double major; // tick units + * double minor; + */ + val metrics: HashMap<*, *> + get() = axisMetrics + + /** + * store each axis + * + * @param a + */ + fun add(a: Axis) { + axes!!.add(a) + a.setAP(this.ap) // ensure axis is linked to it's parent AxisParent + } + + fun setTd(axisType: Int, td: TextDisp) { + val a = getAxis(axisType, false) + if (a != null) + // shouldn't + a.td = td + } + + /** + * returns true if axisType is found on chart + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return + */ + fun hasAxis(axisType: Int): Boolean { + for (i in axes!!.indices) { + if ((axes!![i] as Axis).axis.toInt() == axisType) + return true + } + return false + } + + fun createAxis(axisType: Int) { + val a = getAxis(axisType, true) + return + } + + /** + * Return the desired axis + * @return Axis + * + * public Axis getAxis(int axisType) { + * return ap.getAxis(axisType); + * } + */ + + /** + * returns the desired axis if it exists + * if bCreateIfNecessary, will create if it doesn't exist + * otherwise returns null + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param bCreateIfNecessary + * @return + */ + private fun getAxis(axisType: Int, bCreateIfNecessary: Boolean): Axis? { + for (i in axes!!.indices) { + if ((axes!![i] as Axis).axis.toInt() == axisType) + return axes!![i] as Axis + } + if (bCreateIfNecessary) { + axes!!.add(ap!!.getAxis(axisType, bCreateIfNecessary)) + return axes!![axes!!.size - 1] as Axis + } + return null + } + + /** + * returns true if Axis is reversed + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return + */ + protected fun isReversed(axisType: Int): Boolean { + val a = getAxis(axisType, false) + return a?.isReversed ?: false + } + + /** + * returns the number format for the desired axis + * + * @param axisType + * @return number format string + */ + protected fun getNumberFormat(axisType: Int): String? { + val a = getAxis(axisType, false) + return a?.numberFormat + } + + /** + * remove ALL axes from this chart + */ + fun removeAxes() { + ap!!.removeAxes() + while (axes!!.size > 0) { + axes!!.removeAt(0) + } + } + + /** + * remove the desired axis + associated records + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + */ + fun removeAxis(axisType: Int) { + ap!!.removeAxis(axisType) + for (i in axes!!.indices) { + if ((axes!![i] as Axis).axis.toInt() == axisType) { + axes!!.removeAt(i) + break + } + } + } + + /** + * return the coordinates of the outer plot area (plot + axes + labels) in pixels + * + * @return + */ + fun getPlotAreaCoords(w: Float, h: Float): FloatArray? { + val p = Chart.findRec(ap!!.chartArr, Pos::class.java) as Pos + if (p != null) { + val plotcoords = p.coords + if (plotcoords != null) { + plotcoords[0] = Pos.convertFromSPRC(plotcoords[0], w, 0f) + plotcoords[1] = Pos.convertFromSPRC(plotcoords[1], 0f, h) + plotcoords[2] = Pos.convertFromSPRC(plotcoords[2], w, 0f) // SPRC units see Pos + plotcoords[3] = Pos.convertFromSPRC(plotcoords[3], 0f, h) // SPRC units see Pos + return plotcoords + } + } + return null + } + + + /** + * sets the plot area background color + * + * @param bg color int + */ + fun setPlotAreaBgColor(bg: Int) { + ap!!.setPlotAreaBgColor(bg) + } + + /** + * adds a border around the plot area with the desired line width and line color + * + * @param lw + * @param lclr + */ + fun setPlotAreaBorder(lw: Int, lclr: Int) { + ap!!.setPlotAreaBorder(lw, lclr) + } + + + /** + * obtain the desired axis' label and other options, if present, in XML form + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return String XML representation of desired axis' label and options + * @see ObjectLink + */ + fun getAxisOptionsXML(axisType: Int): String { + return ap!!.getAxisOptionsXML(axisType) + } + + /** + * Return the Axis Title, or "" if none + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + */ + fun getTitle(axisType: Int): String { + val a = getAxis(axisType, false) + return if (a != null) a.title else "" + } + + /** + * set the axis title string + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param Title + */ + fun setTitle(axisType: Int, Title: String) { + val a = getAxis(axisType, false) + if (a != null) { + val defaultIsVert = axisType == ChartConstants.YAXIS && "" == a.title + a.title = Title + if (defaultIsVert) a.td!!.rotation = 90 + } + } + + /** + * return the rotation of the axis labels, if any + * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise + * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) + * 255 Text top-to-bottom with letters upright + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + */ + fun getLabelRotation(axisType: Int): Int { + var rot = 0 + val a = getAxis(axisType, false) + if (a != null) { + val t = Chart.findRec(a.chartArr, Tick::class.java) as Tick + if (t != null) { // shoudn't + rot = t.rotation.toInt() + when (rot) { + 0 -> { + } + 1 // not correct ... + -> rot = 180 + 2 -> rot = -90 + 3 -> rot = 90 + } + } + } + return rot + } + + /** + * return the rotation of the axis Title, if any + * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise + * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) + * 255 Text top-to-bottom with letters upright + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + */ + fun getTitleRotation(axisType: Int): Int { + val a = getAxis(axisType, false) + if (a != null) { + val td = a.td + return td!!.rotation + } + return 0 + } + + /** + * return the coordinates, in pixels, of the title text area, if possible + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return float[] + */ + fun getCoords(axisType: Int): FloatArray? { + val a = getAxis(axisType, false) + if (a != null) { + val td = a.td + return td!!.coords + } + return floatArrayOf(0f, 0f, 0f, 0f) + } + + /** + * Returns the scale values of the the desired Value axis + *

                      Scale elements: (min value, max value, minor (tick) scale, major (tick) scale) + * + * The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well + * + * @param int axisType - one of ChartHandle.YAXIS, ChartHandle.XAXIS, ChartHandle.ZAXIS + * @return int Miminum Scale value for the desired axis + * @see getAxisMinScale + */ + @JvmOverloads + fun getMinMax(ymin: Double, ymax: Double, axisType: Int = ChartConstants.YAXIS): DoubleArray { + val a = getAxis(axisType, false) + val ret = DoubleArray(4) + if (a != null) { + val v = Chart.findRec(a.chartRecords, ValueRange::class.java) as ValueRange + if (v.isAutomaticScale) + // major crazy Excel "automatic max/min/tickmarks calc" ... a monster + v.setMaxMin(ymax, ymin) + ret[0] = v.min + ret[1] = v.max + ret[2] = v.minorTick + ret[3] = v.majorTick + } + return ret + } + + /** + * Returns the major tick unit of the desired Value axis + * + * The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return int major tick unit + * @see getAxisMajorUnit + */ + fun getAxisMajorUnit(axisType: Int): Int { + val a = getAxis(axisType, false) + return a?.majorUnit ?: 10 +// TODO throw exception instead ??? + } + + /** + * Returns the minor tick unit of the desired Value axis + * + * The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return int - minor tick unit of the desired axis + * @see getAxisMinorUnit + */ + fun getAxisMinorUnit(axisType: Int): Int { + val a = getAxis(axisType, false) + return a?.minorUnit ?: 1 +// TODO throw exception instead + } + + /** + * Sets the maximum scale value of the desired Value axis + * + * The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well + * + * Note: The default scale setting for charts is known as Automatic Scaling + *

                      When data changes, the chart automatically adjusts the scale as necessary + *

                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param int MaxValue - desired maximum value of the desired axis + * @see setAxisMax + */ + fun setAxisMax(axisType: Int, MaxValue: Int) { + val a = getAxis(axisType, false) + a?.setMaxScale(MaxValue) + // TODO: throw exception if no axis? + } + + /** + * Sets the minimum scale value of the desired Value axis + * + * The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well + * + * Note: The default setting for charts is known as Automatic Scaling + *

                      When data values change, the chart automatically adjusts the scale as necessary + *

                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param int MinValue - the desired Minimum scale value + * @see setAxisMin + */ + fun setAxisMin(axisType: Int, MinValue: Int) { + val a = getAxis(axisType, false) + a?.setMinScale(MinValue) + // TODO: throw exception if no axis? + } + + /** + * Sets the automatic scale option on or off for the desired Value axis + * + * The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well + * + * Automatic Scaling automatically sets the scale maximum, minimum and tick units + * upon data changes, and is the default setting for charts + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param boolean b - true if set Automatic scaling on, false otherwise + * @see setAxisAutomaticScale + */ + fun setAxisAutomaticScale(axisType: Int, b: Boolean) { + val a = getAxis(axisType, false) + if (a != null) + a.isAutomaticScale = b + } + + /** + * Returns true if the desired Value axis is set to automatic scale + * + * The Value axis contains numbers rather than labels, and is normally the Y axis, + * but Scatter and Bubble charts may have a value axis on the X Axis as well + * + * Note: The default setting for charts is known as Automatic Scaling + *

                      When data changes, the chart automatically adjusts the scale as necessary + *

                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return boolean true if Automatic Scaling is turned on + * @see getAxisAutomaticScale + */ + fun getAxisAutomaticScale(axisType: Int): Boolean { + val a = getAxis(axisType, false) + return a?.isAutomaticScale ?: false // group min and max together ... +// TODO: throw exception? + } + + /** + * Sets the maximum value of the Y Axis (Value Axis) Scale + * + * Note: The default scale setting for charts is known as Automatic Scaling + *

                      When data changes, the chart automatically adjusts the scale as necessary + *

                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * + * @param int MaxValue - the desired maximum scale value + * @see ChartHandle.setAxisMax + */ + fun setAxisMax(MaxValue: Int) { + val a = getAxis(ChartConstants.YAXIS, false) + a?.setMaxScale(MaxValue) + // TODO: throw exception if no axis? + } + + /** + * Sets the minimum value of the Y Axis (Value Axis) Scale + * + * Note: The default setting for charts is known as Automatic Scaling + *

                      When data changes, the chart automatically adjusts the scale as necessary + *

                      Setting the scale manually (either Minimum or Maximum Value) removes Automatic Scaling + * + * @param int MinValue - the desired minimum scale value + * @see ChartHandle.setAxisMin + */ + fun setAxisMin(MinValue: Int) { + val a = getAxis(ChartConstants.YAXIS, false) + a?.setMinScale(MinValue) + // TODO: throw exception if no axis? + } + + /** + * returns the SVG necesssary to define the desired axis + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @return + */ + fun getSVG(axisType: Int, chartMetrics: Map, categories: Array): String? { + val a = getAxis(axisType, false) + return a?.getSVG(this, chartMetrics, categories) + } + + /** + * returns the OOXML necessary to define the desired axis + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param type + * @param id + * @param crossId + * @return + */ + fun getOOXML(axisType: Int, type: Int, id: String, crossId: String): String { + val a = getAxis(axisType, false) + return if (a != null) a.getOOXML(type, id, crossId) else "" + } + + /** + * set the font index for this Axis (for title) + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param fondId + */ + fun setTitleFont(axisType: Int, fondId: Int) { + val a = getAxis(axisType, false) + a?.setFont(fondId) + } + + /** + * returns the font for the title for the desired axis + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return + */ + fun getTitleFont(axisType: Int): Font? { + val a = getAxis(axisType, false) + return a?.font + } + + /** + * returns the label font for the desired axis. + * If not explicitly set, returns the default font + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return + */ + fun getLabelFont(axisType: Int): Font? { + val a = getAxis(axisType, false) + return a?.labelFont + } + + + fun getJSON(axisType: Int, wbh: io.starter.OpenXLS.WorkBookHandle, chartType: Int, yMax: Double, yMin: Double, nSeries: Int): JSONObject { + return ap!!.getAxis(axisType, false)!!.getJSON(wbh, chartType, yMax, yMin, nSeries) + } + + + /** + * sets an option for this axis + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param op option name; one of: CatCross, LabelCross, Marks, CrossBetween, CrossMax, + * MajorGridLines, AddArea, AreaFg, AreaBg + * or Linked Text Display options: + * Label, ShowKey, ShowValue, ShowLabelPct, ShowPct, + * ShowCatLabel, ShowBubbleSizes, TextRotation, Font + * @param val option value + */ + fun setChartOption(axisType: Int, op: String, `val`: String) { + val a = getAxis(axisType, false) + a?.setChartOption(op, `val`) + } + + + /** + * returns the Axis Label Placement or position as an int + * + * One of: + *

                      Axis.INVISIBLE - axis is hidden + *

                      Axis.LOW - low end of plot area + *

                      Axis.HIGH - high end of plot area + *

                      Axis.NEXTTO- next to axis (default) + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @return int - one of the Axis Label placement constants above + */ + fun getAxisPlacement(axisType: Int): Int { + val a = getAxis(axisType, false) + return a?.axisPlacement ?: -1 + } + + /** + * sets the axis labels position or placement to the desired value (these match Excel placement options) + * + * Possible options: + *

                      Axis.INVISIBLE - hides the axis + *

                      Axis.LOW - low end of plot area + *

                      Axis.HIGH - high end of plot area + *

                      Axis.NEXTTO- next to axis (default) + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param Placement - int one of the Axis placement constants listed above + */ + fun setAxisPlacement(axisType: Int, placement: Int) { + val a = getAxis(axisType, false) + if (a != null) + a.axisPlacement = placement + } + + + /** + * parse OOXML axis element + * + * @param axisType one of: XAXIS, YAXIS, ZAXIS + * @param xpp XmlPullParser positioned at correct elemnt + * @param axisTag catAx, valAx, serAx, dateAx + * @param lastTag Stack of element names + */ + fun parseOOXML(axisType: Int, xpp: XmlPullParser, tnm: String, lastTag: Stack, bk: WorkBookHandle) { + val a = getAxis(axisType, true) + if (a != null) { + a.removeTitle() + a.setChartOption("MajorGridLines", "false") // initally, remove any - will be set in parseOOXML if necessary + a.parseOOXML(xpp, tnm, lastTag, bk) + } + } + + fun close() { + axes!!.clear() + axes = null + ap = null + axisMetrics.clear() + } + + /** + * returns a specific axis Metric + *

                      Use only after calling chart.getMetrics + * + * @param metric String metric option one of + *

                      + * XAXISLABELOFFSET double + * XAXISTITLEOFFSET double + * YAXISLABELOFFSET double + * YAXISTITLEOFFSET double + * xAxisRotate integer x axis lable rotation angle + * yPattern string numeric pattern for y axis + * xPattern string numeric pattern for x axis + * double major; // tick units + * double minor; + * @return + */ + fun getMetric(metric: String): Any { + return axisMetrics[metric] + } + + /** + * generate Axis Metrics -- minor, major scale, title and label offsets, etc. + * for use in SVG generation and other operations + * + * @param charttype chart type constant + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param plotcoords == if set, plot-area coordinates (may not be set if automatic) + * @return axisMetrics Map: contains the important axis metrics for use in chart display + */ + fun getMetrics(charttype: Int, chartMetrics: HashMap, plotcoords: FloatArray, categories: Array): HashMap { + val minmax = this.getMinMax(chartMetrics["min"], chartMetrics["max"]) // sets min/max on Value axis, based upon axis settings and actual minimun and maximum values + chartMetrics["min"] = minmax[0] // set new values, if any + chartMetrics["max"] = minmax[1] // "" + axisMetrics["minor"] = minmax[2] + axisMetrics["major"] = minmax[3] + axisMetrics["xAxisReversed"] = this.isReversed(ChartConstants.XAXIS) // default is Value axis crosses at bottom. reverse= crosses at top + axisMetrics["xPattern"] = this.getNumberFormat(ChartConstants.XAXIS) + axisMetrics["yAxisReversed"] = this.isReversed(ChartConstants.YAXIS) // if value (Y), default is on LHS; reverse= RHS + axisMetrics["yPattern"] = this.getNumberFormat(ChartConstants.YAXIS) + axisMetrics["XAXISLABELOFFSET"] = 0.0 + axisMetrics["XAXISTITLEOFFSET"] = 0.0 + axisMetrics["YAXISLABELOFFSET"] = 0.0 + axisMetrics["YAXISTITLEOFFSET"] = 0.0 + + // X axis title Offset + if (this.getTitle(ChartConstants.XAXIS) != "") { + val ef = this.getTitleFont(ChartConstants.XAXIS) + val f = java.awt.Font(ef!!.fontName, ef.fontWeight, ef.fontHeightInPoints.toInt()) + val h = AtomicInteger(0) + val w = getRotatedWidth(f, h, this.getTitle(ChartConstants.XAXIS), this.getTitleRotation(ChartConstants.XAXIS)) + axisMetrics["XAXISTITLEOFFSET"] = h.toInt() + 10 /* add a little padding */ + } + + // Y Axis Title Offsets + if (this.hasAxis(ChartConstants.YAXIS)) { + val title = this.getTitle(ChartConstants.YAXIS) + if (title != "") { + val ef = this.getTitleFont(ChartConstants.YAXIS) + val f = java.awt.Font(ef!!.fontName, ef.fontWeight, ef.fontHeightInPoints.toInt()) + val h = AtomicInteger(0) + val w = getRotatedWidthVert(f, h, this.getTitle(ChartConstants.YAXIS), this.getTitleRotation(ChartConstants.YAXIS)) + axisMetrics["YAXISTITLEOFFSET"] = w // add padding + /* + int rot= this.getTitleRotation(YAXIS); //0-180 or 255 (=vertical with letters upright) + if (rot==90 || rot==255 || charttype==BARCHART) {// Y axis title is almost always rotated, so use font height as offset + if (charttype!=BARCHART) + axisMetrics.put("YAXISTITLEOFFSET", Math.ceil(ef.getFontHeightInPoints()*xpaddingfactor)); + else + axisMetrics.put("XAXISTITLEOFFSET", Math.ceil(ef.getFontHeightInPoints()*ypaddingfactortitle)); + } else {// get width in desired rotation **** + if (rot > 180) rot-=180; // ensure angle is 0-90 + java.awt.Font f= new java.awt.Font(ef.getFontName(), ef.getFontWeight(), (int)ef.getFontHeightInPoints()); + double len= StringTool.getApproximateStringWidth(f, ExcelTools.getFormattedStringVal(title, null)); + axisMetrics.put("YAXISTITLEOFFSET", Math.ceil(len*(Math.cos(Math.toRadians(rot))))); + }*/ + } else + axisMetrics["YAXISTITLEOFFSET"] = 20.0 // no axis title- add a little padding from edge + } + // Label offsets + var series = arrayOfNulls(0) + try { + val major = getMetric("major") as Double + val min = chartMetrics["min"] + val max = chartMetrics["max"] + // get java awt font so can compute approximate width of largest y axis label in order to compute YAXISLABELOFFSET + if (major > 0) { + var nSeries = 0 + if (min == 0.0) + // usual case + nSeries = (if (major != 0.0) (max / major).toInt() + 1 else 0) + else + nSeries = if (major != 0.0) ((max - min) / major).toInt() + 1 else 0 + nSeries = Math.abs(nSeries) + series = arrayOfNulls(nSeries) + if (Math.floor(major) != major) { // contains a fractional part ... avoid java floating point issues + // ensure y value matches scale/avoid double precision issues ... sigh ... + val s = major.toString() + var z = s.indexOf(".") + var scale = 0 + if (z != -1) + scale = s.length - (z + 1) + z = 0 + var i = min + while (i <= max) { + val bd = java.math.BigDecimal(i).setScale(scale, java.math.BigDecimal.ROUND_HALF_UP) + series[z++] = StringBuffer( + CellFormatFactory.fromPatternString( + getMetric("yPattern") as String).format(bd.toString())) + i += major + } + } else { // usual case of an int scale + var z = 0 + var i = min + while (i <= max) { + series[z++] = StringBuffer(CellFormatFactory.fromPatternString(getMetric("yPattern") as String).format(i)) + i += major + } + } + } + } catch (e: Exception) { + Logger.logWarn("ChartAxes.getMetrics. Error obtaining Series: $e") + } + + // Label Offsets ... + if (this.hasAxis(ChartConstants.XAXIS) && charttype != ChartConstants.RADARCHART && charttype != ChartConstants.RADARAREACHART) { //(Pie, donut, etc. don't have axes labels so disregard + val s: Array // Determine X Axis Label offsets + val width: Double + val rot: AtomicInteger + val lf: io.starter.formats.XLS.Font? + val pattern: String + if (charttype != ChartConstants.BARCHART) { + s = categories + pattern = getMetric("xPattern") as String + } else { // bar chart - series on x axis + s = series + pattern = getMetric("yPattern") as String + } + width = chartMetrics["w"] / s.size - 6 // ensure a bit of padding on either side + lf = this.getLabelFont(ChartConstants.XAXIS) + rot = AtomicInteger(this.getLabelRotation(ChartConstants.XAXIS)) // if rot==0 and xaxis labels do not fit in width, a forced rotation will happen. ... + val off = getLabelOffsets(lf, width, s, rot, pattern, true) + axisMetrics["xAxisRotate"] = rot.toInt() // possibly changed when calculating label offsets + axisMetrics["XAXISLABELOFFSET"] = off + } + if (this.hasAxis(ChartConstants.YAXIS) && charttype != ChartConstants.RADARCHART && charttype != ChartConstants.RADARAREACHART) { //(Pie, Donut, etc. don't have axes labels so disregard + // for Y axis, determine width of labels and use as offset (except for bar charts, use height as offset) + val s: Array + val width: Double + val rot: AtomicInteger + val lf: io.starter.formats.XLS.Font? + val pattern: String + if (charttype != ChartConstants.BARCHART) { + s = series + pattern = getMetric("yPattern") as String + } else { // bar chart- categories on y axis + s = categories + pattern = getMetric("xPattern") as String + } + rot = AtomicInteger(this.getLabelRotation(ChartConstants.YAXIS)) // if rot==0 and xaxis labels do not fit in width, a forced rotation will happen. ... + lf = this.getLabelFont(ChartConstants.YAXIS) + width = chartMetrics["w"] / 2 - 10 // a good guess? + val off = getLabelOffsets(lf, width, s, rot, pattern, false) + axisMetrics["yAxisRotate"] = rot.toInt() // possibly changed when calculating label offsets + axisMetrics["YAXISLABELOFFSET"] = off // with padding + } + return axisMetrics + } + + /** + * returns the offset of the labels for the axis given the length of the label strings, the width of the + *

                      this will attempt to break apart longer strings; if so will increment offset to accomodate multiple lines + * + * @param lf label font + * @param width max width of labels + * @param strings label strings + * @param rot desired rotation, if any (0 if none) + * @param horiz true if this is a horizontal axis + * @return + */ + private fun getLabelOffsets(lf: io.starter.formats.XLS.Font?, width: Double, strings: Array, rot: AtomicInteger, pattern: String, horiz: Boolean): Double { + var width = width + var retwidth = 0.0 + var f: java.awt.Font? = null + var h = 0.0 + try { + // get awt Font so can compute and fit category in width + f = java.awt.Font(lf!!.fontName, lf.fontWeight, lf.fontHeightInPoints.toInt()) + for (i in strings.indices) { + var w: Double + val height = AtomicInteger(0) + var s: StringBuffer? = null + try { + s = StringBuffer(CellFormatFactory.fromPatternString(pattern).format(strings[i].toString())) + } catch (e: IllegalArgumentException) { // trap error formatting + s = StringBuffer(strings[i].toString()) + } + + if (horiz) + // on horizontal axis + w = getRotatedWidth(f, height, s!!.toString(), rot.toInt()) + else + // on vertical axis + w = getRotatedWidthVert(f, height, s!!.toString(), rot.toInt()) + h = Math.max(height.toInt().toDouble(), h) + w = addLinesToFit(f, s, rot, w, width, height) + if (w > width) + width = w + + strings[i] = s + retwidth = Math.max(w, retwidth) + } + } catch (e: Exception) { + } + + return if (horiz) + h + else + retwidth + // return offset + h; + } + + /** + * get the width of string s in font f rotated by rot (0, 90, -90, 180) + * + * @param f Font to display s in + * @param s string to display + * @param rot rotation (0= none) + */ + private fun getRotatedWidth(f: java.awt.Font, height: AtomicInteger, s: String, rot: Int): Double { + var retWidth = 0.0 + val slines = s.split("\n".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + for (i in slines.indices) { + var width: Double + if (rot == 0) { + width = StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])) + height.set(Math.ceil((f.size * 3).toDouble()).toInt()) // width of the font + padding + } else if (Math.abs(rot) == 90) { + width = Math.ceil((f.size * 2).toDouble()) // width of the font + padding + height.set(Math.max(height.toInt().toDouble(), StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i]))).toInt()) + } else { // 45 + width = StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])) + width = Math.ceil(width * Math.cos(Math.toRadians(rot.toDouble()))) + height.set(Math.ceil(width * Math.sin(Math.toRadians(rot.toDouble()))).toInt()) + } + retWidth = Math.max(width, retWidth) + } + return retWidth + } + + /** + * get the width of string s in font f rotated by rot (0, 90, -90, 180) + * on a Vertical axis + * + * @param f Font to display s in + * @param s string to display + * @param rot rotation (0= none) + */ + private fun getRotatedWidthVert(f: java.awt.Font, height: AtomicInteger, s: String, rot: Int): Double { + var retWidth = 0.0 + val slines = s.split("\n".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + for (i in slines.indices) { + var width: Double + if (Math.abs(rot) == 90) { // means VERTICAL orientation - default + height.set(StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])).toInt()) + width = Math.ceil((f.size * 2).toDouble()) // width of the font + padding + } else if (rot == 0) { // means HORIZONTAL on vertical axis + height.set(Math.ceil((f.size * 3).toDouble()).toInt()) // width of font + width = Math.max(height.toInt().toDouble(), StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i]))) + } else { // 45 + width = StringTool.getApproximateStringWidth(f, CellFormatFactory.fromPatternString(null).format(slines[i])) + width = Math.ceil(width * Math.cos(Math.toRadians(rot.toDouble()))) + height.set(Math.ceil(width * Math.sin(Math.toRadians(rot.toDouble()))).toInt()) + } + retWidth = Math.max(width, retWidth) + } + return retWidth + } + + /** + * if formatted width of string doesn't fit into width, break apart at spaces with new lines + * return + * + * @param f font to display string in + * @param s target String + * @param rot rotation (0, 45, 90, -90 or 180) + * @param len formatted string length taking into account display font + * @param width maximum width to display string + * @return + */ + private fun addLinesToFit(f: java.awt.Font, s: StringBuffer, rot: AtomicInteger, len: Double, width: Double, height: AtomicInteger): Double { + var len = len + var retLen = Math.min(width, len) + var str = s.toString().trim { it <= ' ' } + + while (len > width) { + var lastSpace = -1 + var j = s.lastIndexOf("\n") + 1 + len = -1.0 + while (len < width && j < str.length) { + len += StringTool.getApproximateCharWidth(f, str.get(j)) + if (str.get(j) == ' ') + lastSpace = j + j++ + } + if (len < width) + break // got it + + if (lastSpace == -1) { // no spaces to break apart via \n's - rotate! + if (str.indexOf(' ') == -1) { + // see if string will fit in 45 degree rotation + if (rot.toInt() != -90) { + rot.set(45) + len = getRotatedWidth(f, height, str, rot.toInt()) + if (len > width) { // then all's fine + } else + // doesn't fit in 45, must be 90 degrees + rot.set(-90) + } + if (rot.toInt() == -90) { + len = getRotatedWidth(f, height, str, rot.toInt()) + } + retLen = Math.max(len, retLen) + break + } else + lastSpace = s.toString().indexOf(' ') + } + s.replace(lastSpace, lastSpace + 1, "\n") // + str.substring(lastSpace+1)); + str = s.toString() + } + return retLen + } + + companion object { + /** + * serialVersionUID + */ + private const val serialVersionUID = -7862828186455339066L + } +} +/** + * Returns the YAXIS scale elements (min value, max value, minor (tick) scale, major (tick) scale) + * the scale elements are calculated from the minimum and maximum values on the chart + * + * @param ymin the minimum value of all the series values + * @param ymax the maximum value of all the series values + * @return double[] min, max, minor, major + */ diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartConstants.java b/src/main/java/io/starter/formats/XLS/charts/ChartConstants.java deleted file mode 100644 index 14af0a7..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ChartConstants.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -/** - * Constants required for Chart functionality - */ - -public interface ChartConstants { - // Chart Types (used in Chart Creation) - int COLCHART = 0; - int BARCHART = 1; - int LINECHART = 2; - int PIECHART = 3; - int AREACHART = 4; // 20070703 KSC: - int SCATTERCHART = 5; // "" - int RADARCHART = 6; // "" - int SURFACECHART = 7; // "" - int DOUGHNUTCHART = 8; // "" - int BUBBLECHART = 9; // "" - int OFPIECHART = 10; - int PYRAMIDCHART = 11; // column-type pyramid - int CYLINDERCHART = 12; // column-type cylinder - int CONECHART = 13; // column-type cone - int PYRAMIDBARCHART = 14;// bar-type pyramid - int CYLINDERBARCHART = 15; // bar-type cylinder - int CONEBARCHART = 16; // bar-type cone - int RADARAREACHART = 17; // "" - int STOCKCHART = 18; - - // Bar Shapes - int SHAPEDEFAULT = 0; - int SHAPECOLUMN = SHAPEDEFAULT; // default - int SHAPECYLINDER = 1; - int SHAPEPYRAMID = 256; - int SHAPECONE = 257; - int SHAPEPYRAMIDTOMAX = 516; - int SHAPECONETOMAX = 517; - - // Axis Types - int XAXIS = 0; - int YAXIS = 1; - int ZAXIS = 2; - int XVALAXIS = 3; // an X axis type but VAL records -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartConstants.kt b/src/main/java/io/starter/formats/XLS/charts/ChartConstants.kt new file mode 100644 index 0000000..6b3b739 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ChartConstants.kt @@ -0,0 +1,67 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +/** + * Constants required for Chart functionality + */ + +interface ChartConstants { + companion object { + // Chart Types (used in Chart Creation) + val COLCHART = 0 + val BARCHART = 1 + val LINECHART = 2 + val PIECHART = 3 + val AREACHART = 4 // 20070703 KSC: + val SCATTERCHART = 5 // "" + val RADARCHART = 6 // "" + val SURFACECHART = 7 // "" + val DOUGHNUTCHART = 8 // "" + val BUBBLECHART = 9 // "" + val OFPIECHART = 10 + val PYRAMIDCHART = 11 // column-type pyramid + val CYLINDERCHART = 12 // column-type cylinder + val CONECHART = 13 // column-type cone + val PYRAMIDBARCHART = 14// bar-type pyramid + val CYLINDERBARCHART = 15 // bar-type cylinder + val CONEBARCHART = 16 // bar-type cone + val RADARAREACHART = 17 // "" + val STOCKCHART = 18 + + // Bar Shapes + val SHAPEDEFAULT = 0 + val SHAPECOLUMN = SHAPEDEFAULT // default + val SHAPECYLINDER = 1 + val SHAPEPYRAMID = 256 + val SHAPECONE = 257 + val SHAPEPYRAMIDTOMAX = 516 + val SHAPECONETOMAX = 517 + + // Axis Types + val XAXIS = 0 + val YAXIS = 1 + val ZAXIS = 2 + val XVALAXIS = 3 // an X axis type but VAL records + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartFormat.java b/src/main/java/io/starter/formats/XLS/charts/ChartFormat.java deleted file mode 100644 index 6d41011..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ChartFormat.java +++ /dev/null @@ -1,940 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.SpPr; -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - - -/** - * ChartFormat: Parent Record for Chart Group (0x1014) - *

                      - * 4 reserved 16 0 20 grbit 2 format flags 22 icrt 2 drawing order (0= bottom of - * z-order) - *

                      - *

                      - * 16 bytes- reserved must be 0 - * fVaried (1 bit): A bit that specifies whether the color for each data point and - * the color and type for each data marker vary. If the chart group has multiple series or the chart group has one - * series and the chart group type is a surface, stock, or area, this field MUST - * be ignored, and the data points do not vary. For all other chart group types, - * if the chart group has one series, a value of 0x1 specifies that the data - * points vary. - * 15 bits - reserved - 0 - * icrt (2 bytes): An unsigned integer that specifies the drawing order of the chart group relative to the other chart - * groups, where 0x0000 is the bottom of the z-order. - * This value MUST be unique for each instance of this record and MUST be less than or equal to 0x0009. - *

                      - *

                      - * ORDER OF SUBRECS: - * Bar/Pie/Scatter ... - * ChartFormatLink - * [SeriesList] - * [ThreeD] - * [Legend] - * [DropBar] - * [ChartLine, LineFormat] - * [DataLabExt] - * [DefaultText, Text] - * [DataLabExtContents] - * [DataFormat] - * [ShapePropsStream] - *

                      - * "http://www.extentech.com">Extentech Inc. - */ -public class ChartFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4000704166442059677L; - private short grbit = 0; - private boolean fVaried = false; - private short drawingOrder = 0; - - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(16), this.getByteAt(17)); - drawingOrder = ByteTools.readShort(this.getByteAt(18), - this.getByteAt(19)); - fVaried = ((grbit & 0x1) == 0x1); - } - - /** - * - */ - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[16] = b[0]; - this.getData()[17] = b[1]; - } - - /** - * specifies whether - * the color for each data point and the color and type for each data marker - * vary - * - * @param b - */ - public void setVaryColor(boolean vary) { - fVaried = vary; - grbit = ByteTools.updateGrBit(grbit, fVaried, 0); - updateRecord(); - } - - /** - * returns whether - * the color for each data point and the color and type for each data marker - * vary - * - * @param b - */ - public boolean getVaryColor() { - return fVaried; - } - - /** - * replace the existing chart object with the desired ChartObject, - * effectively changing the type of the chart - * - * @param co - */ - protected void setChartObject(ChartObject co) { - chartArr.remove(0); - chartArr.add(0, (XLSRecord) co); - - } - - /** - * @return truth of "Chart is Three D" - */ - public boolean isThreeD(int chartType) { - if (chartType != ChartConstants.BUBBLECHART) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == THREED) - return true; - } - } else { - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getHas3DBubbles(); - } - } - return false; - } - - /** - * return truth of "chart is 3d clustered" - * - * @return - */ - public boolean is3DClustered() { - ThreeD td = (ThreeD) Chart.findRec(this.chartArr, ThreeD.class); - if (td != null) { - return td.isClustered(); - } - return false; - } - - /** - * sets if this chart has clustered bar/columns - * - * @param bIsClustered - */ - public void setIsClustered(boolean bIsClustered) { - ThreeD td = getThreeDRec(false); - if (td != null) { - td.setIsClustered(bIsClustered); - } else { - if (chartArr.get(0).getOpcode() == BAR) { - ((Bar) chartArr.get(0)).setIsClustered(); - } - } - - } - - /** - * sets the Space between points (50 or 150 is default) - * - * @param gap - */ - public void setGapDepth(int gap) { - ThreeD td = getThreeDRec(true); - td.setPcGap(gap); - } - - /** - * returns an int representing the space between points in a 3d area, bar or line chart, or 0 if not 3d - * - * @return - */ - public int getGapDepth() { - ThreeD td = getThreeDRec(false); - if (td != null) - td.getPcGap(); - return 0; - } - - /** - * return ThreeD options in XML form - * - * @return String XML - */ - public String getThreeDXML() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == THREED) - return ((ThreeD) b).getOptionsXML(); - } - return ""; - } - - /** - * percentage=distance of pie slice from center of pie as % - * - * @param p - */ - public void setPercentage(int p) { - DataFormat df = this.getDataFormatRec(true); - df.setPercentage(p); - } - - /** - * bar shape: - *
                      the shape is as follows: - * public static final int SHAPECOLUMN= 0; // default - * public static final int SHAPECYLINDER= 1; - * public static final int SHAPEPYRAMID= 256; - * public static final int SHAPECONE= 257; - * public static final int SHAPEPYRAMIDTOMAX= 516; - * public static final int SHAPECONETOMAX= 517; - * - * @param shape - */ - public void setBarShape(int shape) { - DataFormat df = this.getDataFormatRec(true); - // THIS DOES NOT MAKE SENSE ACCORDING TO DOC BUT IS WHAT EXCEL DOES - df.setPointNumber(0); - df.setSeriesIndex(0); - df.setSeriesNumber(-3); - df.setShape(shape); - } - - /** - * sets chart options such as threed options, grid lines, etc ... these options - * are distinct from chart-type-specific options, which are handled by the - * appropriate chart record (pie, bar ...) - * - * @param op String option name - * @param val Object value - */ - public void setOption(String op, String val) { - if (op.equalsIgnoreCase("Percentage")) { - setPercentage(Short.valueOf(val).shortValue()); - } else if (op.equalsIgnoreCase("Shape")) { - setBarShape(Integer.parseInt(val)); - } else if (op.equals("ShowBubbleSizes") - || // TextDisp options - op.equals("ShowLabelPct") || op.equals("ShowCatLabel") - || op.equals("ShowPct") || op.equals("Rotation") || - // op.equals("ShowValue") || unknown - op.equals("Label") || op.equals("TextRotation")) { - TextDisp td = getDataLegendTextDisp(0); - td.setChartOption(op, val); - } else if (op.equals("Perspective") - || // ThreeD options - op.equals("Cluster") || op.equals("ThreeDScaling") - || op.equals("TwoDWalls") || op.equals("PcGap") - || op.equals("PcDepth") || op.equals("PcHeight") - || op.equals("PcDist") || op.equals("AnElev") - || op.equals("AnRot")) { - ThreeD td = this.getThreeDRec(true); - td.setChartOption(op, val); - } else if (op.equals("ShowValueLabel") - || // Attached Label Options - op.equals("ShowValueAsPercent") - || op.equals("ShowLabelAsPercent") - || op.equals("ShowLabel") - || op.equals("ShowSeriesName") || op.equals("ShowBubbleLabel")) { - DataFormat df = this.getDataFormatRec(true); - df.setDataLabels(op); - } else if (op.equalsIgnoreCase("SmoothedLine") - || op.equalsIgnoreCase("ThreeDBubbles") - || op.equalsIgnoreCase("ArShadow")) { - DataFormat df = this.getDataFormatRec(true); - if (op.equalsIgnoreCase("SmoothedLine")) df.setSmoothLines(true); - else if (op.equalsIgnoreCase("ThreeDBubbles")) df.setHas3DBubbles(true); - else df.setHasShadow(true); - } - } - - /** - * Return the ThreeD rec associated with this ChartFormat, create if not - * present - */ - // NOTES: - // The Chart3d record specifies that the plot area of the chart group is rendered in a 3-D scene - // and also specifies the attributes of the 3-D plot area. - // The preceding chart group type MUST be of type bar, pie, line, area, or surface. - public ThreeD getThreeDRec(boolean bCreate) { - ThreeD td = (ThreeD) Chart.findRec(this.chartArr, ThreeD.class); - if (td == null && bCreate) { // add ThreeD rec - for (int i = 0; i < this.chartArr.size(); i++) { - BiffRec b = this.chartArr.get(i); - if (b.getOpcode() == CHARTFORMATLINK) { - if ((i + 1) < chartArr.size() && ((BiffRec) this.chartArr.get(i + 1)).getOpcode() == SERIESLIST) - i++; // rare that SeriesList record appears - td = (ThreeD) ThreeD.getPrototype(); - td.setParentChart(this.getParentChart()); - this.chartArr.add(i + 1, td); - break; - } - } - } - return td; - } - - /** - * Add or Retrieve TextDisp and assoc records specific for Data Legends - * - * @return - */ - private TextDisp getDataLegendTextDisp(int type) { - int i = Chart.findRecPosition(this.chartArr, Legend.class); - TextDisp td = null; - if (this.chartArr.size() <= (i + 1) - || this.chartArr.get(i + 1).getClass() != DefaultText.class) { // then add one - DefaultText d = (DefaultText) DefaultText.getPrototype(); - d.setType((short) type); - d.setParentChart(this.getParentChart()); - this.chartArr.add(++i, d); - td = (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_DATAPOINTS, - "", this.getWorkBook()); - td.setParentChart(this.getParentChart()); - this.chartArr.add(++i, td); - } else { - DefaultText d = (DefaultText) this.chartArr.get(i + 1); - if (d.getType() != type) { // / add a new one - i += 2; // add after TextDisp - d = (DefaultText) DefaultText.getPrototype(); - d.setType((short) type); - d.setParentChart(this.getParentChart()); - this.chartArr.add(++i, d); - td = (TextDisp) TextDisp.getPrototype( - ObjectLink.TYPE_DATAPOINTS, "", this.getWorkBook()); - td.setParentChart(this.getParentChart()); - this.chartArr.add(++i, td); - } else { // it's the correct one - i += 2; - td = (TextDisp) this.chartArr.get(i); - } - } - return td; - } - - /** - * Gets the dataformat record associated with this ChartFormat If none - * present, creates a basic DataFormat set of records DataFormat controls - * Data Labels, % Distance from sections, line formats ... - * - * @return DataFormat Record - */ - private DataFormat getDataFormatRec(boolean bCreate) { - DataFormat df = (DataFormat) Chart.findRec(this.chartArr, DataFormat.class); - if (df == null && bCreate) { // create dataformat - df = (DataFormat) DataFormat.getPrototypeWithFormatRecs(this.getParentChart()); - this.addChartRecord(df); - } - return df; - } - - - /** - * return the Data Labels chosen for this chart, if any can be one or more - * of:
                      - * Value
                      - * ValuePerecentage
                      - * CategoryPercentage
                      - * CategoryLabel
                      - * BubbleLabel
                      - * SeriesLabel or an empty string if no data labels are chosen for the chart - * - * @return - */ - public String getDataLabels() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getDataLabelType(); - } - return ""; - } - - /** - * returns true if this chart displays lines (Line, Scatter, Radar) - * - * @return true if chart has lines (see Scatter, Line chart ...) - */ - public boolean getHasLines() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getHasLines(); - } - return false; - } - - - /** - * sets this chart to have default lines (Scatter, Line chart ...) - */ - public void setHasLines() { - DataFormat df = this.getDataFormatRec(true); - df.setHasLines(); - } - - - /** - * sets this chart to have lines (Scatter, Line chart ...) of style lineStyle - *
                      Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - */ - public void setHasLines(int lineStyle) { - DataFormat df = this.getDataFormatRec(true); - df.setHasLines(lineStyle); - } - - /** - * returns true if this chart has smoothed lines (Scatter, Line, Radar) - * - * @return - */ - public boolean getHasSmoothLines() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getSmoothedLines(); - } - return false; - } - - - /** - * sets this chart to have smoothed lines (Scatter, Line, Radar) - */ - public void setHasSmoothLines(boolean b) { - DataFormat df = this.getDataFormatRec(true); - df.setSmoothLines(b); - } - - /** - * returns true if chart has Drop Lines - * - * @return - */ - public boolean getHasDropLines() { - /* chartline: - line, chartformatlink, , <3d>, , chartline, lineformat, startblock, - shapepropsstream, [-92, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - -51, -110, - -66, 74, 1, -88, 0, 0, 0, 0] - endblock - - dropbar= chartline, lineformat - */ - return false; - } - - /** - * sets this chart (Area, Line or Stock) to have Drop Lines - */ - public void setHasDropLines() { - - } - - /** - * sets 3d bubble state - * - * @param has3dBubbles - */ - public void setHas3DBubbles(boolean has3dBubbles) { - DataFormat df = this.getDataFormatRec(true); - df.setHas3DBubbles(has3dBubbles); - - } - - /** - * return data label options as an int
                      - * can be one or more of:
                      - * SHOWVALUE= 0x1;
                      - * SHOWVALUEPERCENT= 0x2;
                      - * SHOWCATEGORYPERCENT= 0x4;
                      - * SHOWCATEGORYLABEL= 0x10;
                      - * SHOWBUBBLELABEL= 0x20;
                      - * SHOWSERIESLABEL= 0x40; - * - * @return a combination of data label options above or 0 if none - *

                      - * NOTE: this returns the Data Labels settings for the entire chart, - * not a particular series - * @see AttachedLabel - */ - public int getDataLabelsInt() { - int datalabels = 0; - int z = Chart.findRecPosition(this.chartArr, DataLabExtContents.class); - - // here we are assuming that the TextDisp is of the proper ObjectLink=4 type ... - if (z > 0 && this.chartArr.get(z - 1) instanceof TextDisp) { // Extended Label -- add to attachedlabel, if any - DataLabExtContents dl = (DataLabExtContents) chartArr.get(z); - datalabels = dl.getTypeInt(); // if so, no fontx record ... use default??? - } - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - datalabels |= df.getDataLabelTypeInt(); - } - return datalabels; - } - - /** - * sets the data labels for the entire chart (as opposed to a specific series/data point). - * A combination of: - *

                    • SHOWVALUE= 0x1; - *
                    • SHOWVALUEPERCENT= 0x2; - *
                    • SHOWCATEGORYPERCENT= 0x4; - *
                    • SHOWCATEGORYLABEL= 0x10; - *
                    • SHOWBUBBLELABEL= 0x20; - *
                    • SHOWSERIESLABEL= 0x40; - */ - public void setHasDataLabels(int dl) { - DataFormat df = this.getDataFormatRec(true); - df.setHasDataLabels(dl); - } - - /** - * returns the bar shape for a column or bar type chart can be one of:
                      - * ChartConstants.SHAPECOLUMN default
                      - * ChartConstants.SHAPECONE
                      - * ChartConstants.SHAPECONETOMAX
                      - * ChartConstants.SHAPECYLINDER
                      - * ChartConstants.SHAPEPYRAMID
                      - * ChartConstants.SHAPEPYRAMIDTOMAX - * - * @return - */ - public int getBarShape() { - int shape = SHAPEDEFAULT; - DataFormat df = this.getDataFormatRec(false); - if (df != null) - shape = df.getShape(); - return shape; - } - - /** - * returns type of marker, if any
                      - * 0 = no marker
                      - * 1 = square
                      - * 2 = diamond
                      - * 3 = triangle
                      - * 4 = X
                      - * 5 = star
                      - * 6 = Dow-Jones
                      - * 7 = standard deviation
                      - * 8 = circle
                      - * 9 = plus sign - * - * @return - */ - public int getMarkerFormat() { - int markertype = 8; // default= circles - // default actually looks like: 2, 1, 5, 4 ... - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getMarkerFormat(); - } - return 0; - } - - /** - * 0 = no marker
                      - * 1 = square
                      - * 2 = diamond
                      - * 3 = triangle
                      - * 4 = X
                      - * 5 = star
                      - * 6 = Dow-Jones
                      - * 7 = standard deviation
                      - * 8 = circle
                      - * 9 = plus sign - */ - public void setMarkers(int markerFormat) { - DataFormat df = this.getDataFormatRec(true); - df.setMarkerFormat(markerFormat); - } - - /** - * Chart Line options (not available on all charts): - *
                      Drop Lines available on Line, Area and Stock charts - *
                      HiLow Lines are available on Line and Stock charts - *
                      Series Lines are available on Bar and OfPie charts - *
                      -     * 0= drop lines below the data points of Line, Area and Stock charts
                      -     * 1= High-low lines around the data points of Line and Stock charts
                      -     * 2- Series Line connecting data points of stacked column and bar charts and OfPie Charts
                      -     * 
                      - *
                      - * - * @param lineType - */ - public ChartLine addChartLines(int lineType) { -// ChartLine cl= (ChartLine) Chart.findRec(chartArr, ChartLine.class); -// if (cl==null) { - ChartLine cl = null; - // goes After DropBar or legend or 3d (or CFL) - for (int i = chartArr.size() - 1; i >= 1; i--) { - BiffRec br = chartArr.get(i); - short op = br.getOpcode(); - if (op == DROPBAR || op == LEGEND || op == THREED || op == CHARTFORMATLINK) { - cl = (ChartLine) ChartLine.getPrototype(); - cl.setParentChart(this.getParentChart()); - chartArr.add(++i, cl); - cl.setParentChart(this.getParentChart()); // ensure can find - LineFormat lf = (LineFormat) LineFormat.getPrototype(0, 1); - lf.setParentChart(this.getParentChart()); - chartArr.add(++i, lf); - break; - } - } -// } - cl.setLineType(lineType); - return cl; - } - - /** - * return ChartLines option, if any - *
                      -     * 0= drop lines below the data points of Line, Area and Stock charts
                      -     * 1= High-low lines around the data points of Line and Stock charts
                      -     * 2- Series Line connecting data points of stacked column and bar charts and OfPie Charts
                      -     * 
                      - * - * @return 0-2 or -1 if no chart lines - */ - public int getChartLines() { - ChartLine cl = (ChartLine) Chart.findRec(chartArr, ChartLine.class); - if (cl != null) - return cl.getLineType(); - return -1; - } - - /** - * return the record governing chart lines: dropLines, Hi-low lines or Series Lines - * - * @return - */ - public ChartLine getChartLinesRec() { - return (ChartLine) Chart.findRec(chartArr, ChartLine.class); - } - - public ChartLine getChartLinesRec(int id) { - int i = Chart.findRecPosition(chartArr, ChartLine.class); - if (i > -1) { - while (i < chartArr.size()) { - if (((BiffRec) chartArr.get(i)).getOpcode() == CHARTLINE) { - ChartLine cl = (ChartLine) chartArr.get(i); - if (cl.getLineType() == id) - return cl; - } else - break; - i += 2; - } - } - return null; - } - - /** - * add up/down bars (line, area stock) - */ - public void addUpDownBars() { - if (Chart.findRec(chartArr, Dropbar.class) == null) { - // create necessary records to describe up/down bars - Dropbar upBar = (Dropbar) Dropbar.getPrototype(); - upBar.setParentChart(this.getParentChart()); - Dropbar downBar = (Dropbar) Dropbar.getPrototype(); - downBar.setParentChart(this.getParentChart()); - LineFormat lf = (LineFormat) LineFormat.getPrototype(); - lf.setParentChart(this.getParentChart()); - AreaFormat af = (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(this.getParentChart()); - upBar.chartArr.add(lf); - upBar.chartArr.add(af); - lf = (LineFormat) LineFormat.getPrototype(); - lf.setParentChart(this.getParentChart()); - af = (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(this.getParentChart()); - downBar.chartArr.add(lf); - downBar.chartArr.add(af); - - // add dropbar records to subarray - for (int i = chartArr.size() - 1; i >= 0; i--) { - BiffRec br = chartArr.get(i); - short op = br.getOpcode(); - if (op == SERIESLIST || op == LEGEND || op == THREED || op == CHARTFORMATLINK) { - chartArr.add(++i, upBar); - chartArr.add(++i, downBar); - break; - } - } - } - } - - - /** - * parse upDownBars OOXML element (controled by 2 DropBar records in this subArray) - *
                      Valid for Line and Stock charts only - */ - public void parseUpDownBarsOOXML(XmlPullParser xpp, Stack lastTag, WorkBookHandle bk) { - // assume dropbar records are not present ... - addUpDownBars(); - int z = Chart.findRecPosition(chartArr, Dropbar.class); - Dropbar downBar = (Dropbar) chartArr.get(z++); - Dropbar upBar = (Dropbar) chartArr.get(z); - - - try { - Dropbar curbar = null; - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("downBars")) { - curbar = downBar; - } else if (tnm.equals("upBars")) { - curbar = upBar; - } else if (tnm.equals("gapWidth")) { // default=150 - upBar.setGapWidth(Integer.valueOf(xpp.getAttributeValue(0))); // TODO: should this be in 1st dropbar or both?? - downBar.setGapWidth(Integer.valueOf(xpp.getAttributeValue(0))); // TODO: should this be in 1st dropbar or both?? - } else if (tnm.equals("spPr")) { - lastTag.push(tnm); - SpPr sppr = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk).cloneElement(); - LineFormat lf = (LineFormat) curbar.chartArr.get(0); - if (lf != null) - lf.setFromOOXML(sppr); - // TODO: fill AreaFormat with sppr - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals("upDownBars")) { - lastTag.pop(); - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("parseUpDownBarsOOXML: " + e.toString()); - } - } - - /** - * return the OOXML to define the upDownBars element - *
                      defined by 2 Dropbar records in this subarray - *
                      Only valid for Line and Stock charts - * - * @return - */ - public String getUpDownBarOOXML() { - int z = Chart.findRecPosition(chartArr, Dropbar.class); // - if (z == -1) return ""; - StringBuffer ooxml = new StringBuffer(); - try { - Dropbar upBars = (Dropbar) chartArr.get(z++); - Dropbar downBars = (Dropbar) chartArr.get(z); - z++; - ooxml.append(""); - // c:gapWidth - int gw = upBars.getGapWidth(); // TODO: only upBar? Should they match? - if (gw != 150)// default - ooxml.append(""); - // c:upBars - ooxml.append(upBars.getOOXML(true)); - // c:downBars - ooxml.append(downBars.getOOXML(false)); - ooxml.append(""); - } catch (Exception e) { - } - return ooxml.toString(); - } - - - /** - * return the drawing order of this ChartFormat
                      - * For multiple charts-in-one, drawing order determines the order of the - * charts - * - * @return - */ - public int getDrawingOrder() { - return drawingOrder; - } - - /** - * set the drawing order of this ChartFormat
                      - * For multiple charts-in-one, drawing order determines the order of the - * charts - * - * @param order - */ - public void setDrawingOrder(int order) { - drawingOrder = (short) order; - byte[] b = ByteTools.shortToLEBytes(drawingOrder); - this.getData()[18] = b[0]; - this.getData()[19] = b[1]; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - - public static XLSRecord getPrototype() { - ChartFormat cf = new ChartFormat(); - cf.setOpcode(CHARTFORMAT); - cf.setData(cf.PROTOTYPE_BYTES); - cf.init(); - return cf; - } - - /** - * access chart-type record and return any options specific for this chart - * in XML form Gathers chart options such as show legend, grid lines, etc - * ... these options are distinct from chart-type-specific options, which - * are handled by the appropriate chart record (pie, bar ...) Also, since - * both ThreeD options and Axis-specific options are quite extensive, these - * are handled separately - * - * @return String of options XML ("" if no options set) - * @see setChartOption - * @see setOption - * @see getThreeDXML() - * @see getAxesXML() - */ - public String getChartOptionsXML() { - boolean bFoundDefaultText0 = false; - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b instanceof ChartObject) { - ChartObject co = (ChartObject) b; - if (b instanceof DataFormat) { - // controls Data Legends, % Distance of sections, Line - // Format, Area Format, Bar Shapes ... - DataFormat df = (DataFormat) b; - int shape = df.getShape(); - if (shape != 0) - sb.append(" Shape=\"" + shape + "\""); - for (int z = 0; z < df.chartArr.size(); z++) { - b = df.chartArr.get(z); - if (b instanceof PieFormat) { - sb.append(((PieFormat) b).getOptionsXML()); - } else if (b instanceof AttachedLabel) { - String type = ((AttachedLabel) b).getType(); - sb.append(" DataLabel=\"" + type + "\""); - } else if (b instanceof Serfmt) { - sb.append(((Serfmt) b).getOptionsXML()); - } else if (b instanceof MarkerFormat) { - sb.append(((MarkerFormat) b).getOptionsXML()); - } - } - } else if (b instanceof DefaultText) { // controls Show Legend + // some Data Legends - if (!bFoundDefaultText0) - bFoundDefaultText0 = (((DefaultText) b).getType() == 0); - if (((DefaultText) b).getType() == 1 && bFoundDefaultText0) - sb.append(" ShowLegendKey=\"true\""); - } - } - } - return sb.toString(); - } - - /** - * get the value of *almost* any chart option (axis options are in Axis) - * - * @param op String option e.g. Shadow or Percentage - * @return String value of option - */ - public String getChartOption(String op) { - DataFormat df = this.getDataFormatRec(false); - try { - if (op.equals("Percentage")) { // pieformat - return String.valueOf(df.getPercentage()); - } else if (op.equals("ShowValueLabel") - || // Attached Label Options - op.equals("ShowValueAsPercent") - || op.equals("ShowLabelAsPercent") - || op.equals("ShowLabel") - || op.equals("ShowBubbleLabel")) { - return df.getDataLabelType(op); - } else if (op.equals("ShowBubbleSizes") - || // TextDisp options - op.equals("ShowLabelPct") || op.equals("ShowPct") - || op.equals("ShowCatLabel") - || - // op.equals("ShowValue") || unknown - op.equals("Rotation") || op.equals("Label") - || op.equals("TextRotation")) { - TextDisp td = getDataLegendTextDisp(0); - return td.getChartOption(op); - } else if (op.equals("Perspective") - || // ThreeD options - op.equals("Cluster") || op.equals("ThreeDScaling") - || op.equals("TwoDWalls") || op.equals("PcGap") - || op.equals("PcDepth") || op.equals("PcHeight") - || op.equals("PcDist") || op.equals("AnElev") - || op.equals("AnRot")) { - ThreeD td = this.getThreeDRec(false); - if (td != null) - return td.getChartOption(op); - return ""; - } else if (op.equals("ThreeDBubbles")) { - return String.valueOf(df.getHas3DBubbles()); - } else if (op.equals("ArShadow")) { - return String.valueOf(df.getHasShadow()); - } else if (op.equals("SmoothLines")) { - return String.valueOf(df.getSmoothedLines()); - // TODO: FINSIH REST! - } else if (op.equals("AxisLabels")) { // Radar, RadarArea - } else if (op.equals("BubbleSizeRatio")) { // Scatter - } else if (op.equals("BubbleSize")) { // Scatter - } else if (op.equals("ShowNeg")) { // Scatter - } else if (op.equals("ColorFill")) { // Surface - } else if (op.equals("Shading")) { // Surface - } else if (op.equals("MarkerFormat")) { // MarkerFormat - } - } catch (NullPointerException e) { - - } - return ""; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartFormat.kt b/src/main/java/io/starter/formats/XLS/charts/ChartFormat.kt new file mode 100644 index 0000000..c7d02a3 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ChartFormat.kt @@ -0,0 +1,959 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.SpPr +import io.starter.formats.XLS.BiffRec +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + + +/** + * **ChartFormat: Parent Record for Chart Group (0x1014)** + * + * + * 4 reserved 16 0 20 grbit 2 format flags 22 icrt 2 drawing order (0= bottom of + * z-order) + * + * + * + * + * 16 bytes- reserved must be 0 + * fVaried (1 bit): A bit that specifies whether the color for each data point and + * the color and type for each data marker vary. If the chart group has multiple series or the chart group has one + * series and the chart group type is a surface, stock, or area, this field MUST + * be ignored, and the data points do not vary. For all other chart group types, + * if the chart group has one series, a value of 0x1 specifies that the data + * points vary. + * 15 bits - reserved - 0 + * icrt (2 bytes): An unsigned integer that specifies the drawing order of the chart group relative to the other chart + * groups, where 0x0000 is the bottom of the z-order. + * This value MUST be unique for each instance of this record and MUST be less than or equal to 0x0009. + * + * + * + * + * ORDER OF SUBRECS: + * Bar/Pie/Scatter ... + * ChartFormatLink + * [SeriesList] + * [ThreeD] + * [Legend] + * [DropBar] + * [ChartLine, LineFormat] + * [DataLabExt] + * [DefaultText, Text] + * [DataLabExtContents] + * [DataFormat] + * [ShapePropsStream] + * + * + * "http://www.extentech.com">Extentech Inc. + */ +class ChartFormat : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var fVaried = false + private var drawingOrder: Short = 0 + + /** + * returns whether + * the color for each data point and the color and type for each data marker + * vary + * + * @param b + */ + /** + * specifies whether + * the color for each data point and the color and type for each data marker + * vary + * + * @param b + */ + var varyColor: Boolean + get() = fVaried + set(vary) { + fVaried = vary + grbit = ByteTools.updateGrBit(grbit, fVaried, 0) + updateRecord() + } + + /** + * return truth of "chart is 3d clustered" + * + * @return + */ + val is3DClustered: Boolean + get() { + val td = Chart.findRec(this.chartArr, ThreeD::class.java) as ThreeD + return td?.isClustered ?: false + } + + /** + * returns an int representing the space between points in a 3d area, bar or line chart, or 0 if not 3d + * + * @return + */ + /** + * sets the Space between points (50 or 150 is default) + * + * @param gap + */ + var gapDepth: Int + get() { + val td = getThreeDRec(false) + td?.pcGap + return 0 + } + set(gap) { + val td = getThreeDRec(true) + td!!.pcGap = gap + } + + /** + * return ThreeD options in XML form + * + * @return String XML + */ + val threeDXML: String + get() { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.THREED) + return (b as ThreeD).optionsXML + } + return "" + } + + + /** + * return the Data Labels chosen for this chart, if any can be one or more + * of:

                      + * Value

                      + * ValuePerecentage

                      + * CategoryPercentage

                      + * CategoryLabel

                      + * BubbleLabel

                      + * SeriesLabel or an empty string if no data labels are chosen for the chart + * + * @return + */ + val dataLabels: String? + get() { + val df = this.getDataFormatRec(false) + return if (df != null) { + df.dataLabelType + } else "" + } + + /** + * returns true if this chart displays lines (Line, Scatter, Radar) + * + * @return true if chart has lines (see Scatter, Line chart ...) + */ + val hasLines: Boolean + get() { + val df = this.getDataFormatRec(false) + return df?.hasLines ?: false + } + + /** + * returns true if this chart has smoothed lines (Scatter, Line, Radar) + * + * @return + */ + /** + * sets this chart to have smoothed lines (Scatter, Line, Radar) + */ + var hasSmoothLines: Boolean + get() { + val df = this.getDataFormatRec(false) + return df?.smoothedLines ?: false + } + set(b) { + val df = this.getDataFormatRec(true) + df!!.setSmoothLines(b) + } + + /** + * returns true if chart has Drop Lines + * + * @return + */ + /* chartline: + line, chartformatlink, , <3d>, , chartline, lineformat, startblock, + shapepropsstream, [-92, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + -51, -110, + -66, 74, 1, -88, 0, 0, 0, 0] + endblock + + dropbar= chartline, lineformat + */ val hasDropLines: Boolean + get() = false + + /** + * return data label options as an int

                      + * can be one or more of:

                      + * SHOWVALUE= 0x1;

                      + * SHOWVALUEPERCENT= 0x2;

                      + * SHOWCATEGORYPERCENT= 0x4;

                      + * SHOWCATEGORYLABEL= 0x10;

                      + * SHOWBUBBLELABEL= 0x20;

                      + * SHOWSERIESLABEL= 0x40; + * + * @return a combination of data label options above or 0 if none + * + * + * NOTE: this returns the Data Labels settings for the entire chart, + * not a particular series + * @see AttachedLabel + */ + // here we are assuming that the TextDisp is of the proper ObjectLink=4 type ... + // Extended Label -- add to attachedlabel, if any + // if so, no fontx record ... use default??? + val dataLabelsInt: Int + get() { + var datalabels = 0 + val z = Chart.findRecPosition(this.chartArr, DataLabExtContents::class.java) + if (z > 0 && this.chartArr[z - 1] is TextDisp) { + val dl = chartArr[z] as DataLabExtContents + datalabels = dl.typeInt + } + val df = this.getDataFormatRec(false) + if (df != null) { + datalabels = datalabels or df.dataLabelTypeInt + } + return datalabels + } + + /** + * returns the bar shape for a column or bar type chart can be one of:

                      + * ChartConstants.SHAPECOLUMN default

                      + * ChartConstants.SHAPECONE

                      + * ChartConstants.SHAPECONETOMAX

                      + * ChartConstants.SHAPECYLINDER

                      + * ChartConstants.SHAPEPYRAMID

                      + * ChartConstants.SHAPEPYRAMIDTOMAX + * + * @return + */ + /** + * bar shape: + *

                      the shape is as follows: + * public static final int SHAPECOLUMN= 0; // default + * public static final int SHAPECYLINDER= 1; + * public static final int SHAPEPYRAMID= 256; + * public static final int SHAPECONE= 257; + * public static final int SHAPEPYRAMIDTOMAX= 516; + * public static final int SHAPECONETOMAX= 517; + * + * @param shape + */ + // THIS DOES NOT MAKE SENSE ACCORDING TO DOC BUT IS WHAT EXCEL DOES + var barShape: Int + get() { + var shape = ChartConstants.SHAPEDEFAULT + val df = this.getDataFormatRec(false) + if (df != null) + shape = df.shape.toInt() + return shape + } + set(shape) { + val df = this.getDataFormatRec(true) + df!!.setPointNumber(0) + df.setSeriesIndex(0) + df.setSeriesNumber(-3) + df.setShape(shape) + } + + /** + * returns type of marker, if any

                      + * 0 = no marker

                      + * 1 = square

                      + * 2 = diamond

                      + * 3 = triangle

                      + * 4 = X

                      + * 5 = star

                      + * 6 = Dow-Jones

                      + * 7 = standard deviation

                      + * 8 = circle

                      + * 9 = plus sign + * + * @return + */ + // default= circles + // default actually looks like: 2, 1, 5, 4 ... + val markerFormat: Int + get() { + val markertype = 8 + val df = this.getDataFormatRec(false) + return df?.markerFormat ?: 0 + } + + /** + * return ChartLines option, if any + *
                      +     * 0= drop lines below the data points of Line, Area and Stock charts
                      +     * 1= High-low lines around the data points of Line and Stock charts
                      +     * 2- Series Line connecting data points of stacked column and bar charts and OfPie Charts
                      +    
                      * + * + * @return 0-2 or -1 if no chart lines + */ + val chartLines: Int + get() { + val cl = Chart.findRec(chartArr, ChartLine::class.java) as ChartLine + return cl?.lineType ?: -1 + } + + /** + * return the record governing chart lines: dropLines, Hi-low lines or Series Lines + * + * @return + */ + val chartLinesRec: ChartLine + get() = Chart.findRec(chartArr, ChartLine::class.java) as ChartLine + + /** + * return the OOXML to define the upDownBars element + *

                      defined by 2 Dropbar records in this subarray + *

                      Only valid for Line and Stock charts + * + * @return + */ + // + // c:gapWidth + // TODO: only upBar? Should they match? + // default + // c:upBars + // c:downBars + val upDownBarOOXML: String + get() { + var z = Chart.findRecPosition(chartArr, Dropbar::class.java) + if (z == -1) return "" + val ooxml = StringBuffer() + try { + val upBars = chartArr[z++] as Dropbar + val downBars = chartArr[z] as Dropbar + z++ + ooxml.append("") + val gw = upBars.gapWidth.toInt() + if (gw != 150) + ooxml.append("") + ooxml.append(upBars.getOOXML(true)) + ooxml.append(downBars.getOOXML(false)) + ooxml.append("") + } catch (e: Exception) { + } + + return ooxml.toString() + } + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + + /** + * access chart-type record and return any options specific for this chart + * in XML form Gathers chart options such as show legend, grid lines, etc + * ... these options are distinct from chart-type-specific options, which + * are handled by the appropriate chart record (pie, bar ...) Also, since + * both ThreeD options and Axis-specific options are quite extensive, these + * are handled separately + * + * @return String of options XML ("" if no options set) + * @see setChartOption + * + * @see setOption + * + * @see getThreeDXML + * @see getAxesXML + */ + // controls Data Legends, % Distance of sections, Line + // Format, Area Format, Bar Shapes ... + // controls Show Legend + // some Data Legends + val chartOptionsXML: String + get() { + var bFoundDefaultText0 = false + val sb = StringBuffer() + for (i in chartArr.indices) { + var b: BiffRec = chartArr[i] + if (b is ChartObject) { + val co = b as ChartObject + if (b is DataFormat) { + val df = b + val shape = df.shape.toInt() + if (shape != 0) + sb.append(" Shape=\"$shape\"") + for (z in df.chartArr.indices) { + b = df.chartArr[z] + if (b is PieFormat) { + sb.append(b.optionsXML) + } else if (b is AttachedLabel) { + val type = b.type + sb.append(" DataLabel=\"$type\"") + } else if (b is Serfmt) { + sb.append(b.optionsXML) + } else if (b is MarkerFormat) { + sb.append(b.optionsXML) + } + } + } else if (b is DefaultText) { + if (!bFoundDefaultText0) + bFoundDefaultText0 = b.type == 0 + if (b.type == 1 && bFoundDefaultText0) + sb.append(" ShowLegendKey=\"true\"") + } + } + } + return sb.toString() + } + + override fun init() { + super.init() + grbit = ByteTools.readShort(this.getByteAt(16).toInt(), this.getByteAt(17).toInt()) + drawingOrder = ByteTools.readShort(this.getByteAt(18).toInt(), + this.getByteAt(19).toInt()) + fVaried = grbit and 0x1 == 0x1 + } + + /** + * + */ + private fun updateRecord() { + val b = ByteTools.shortToLEBytes(grbit) + this.data[16] = b[0] + this.data[17] = b[1] + } + + /** + * replace the existing chart object with the desired ChartObject, + * effectively changing the type of the chart + * + * @param co + */ + fun setChartObject(co: ChartObject) { + chartArr.removeAt(0) + chartArr.add(0, co as XLSRecord) + + } + + /** + * @return truth of "Chart is Three D" + */ + fun isThreeD(chartType: Int): Boolean { + if (chartType != ChartConstants.BUBBLECHART) { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.THREED) + return true + } + } else { + val df = this.getDataFormatRec(false) + if (df != null) { + return df.has3DBubbles + } + } + return false + } + + /** + * sets if this chart has clustered bar/columns + * + * @param bIsClustered + */ + fun setIsClustered(bIsClustered: Boolean) { + val td = getThreeDRec(false) + if (td != null) { + td.isClustered = bIsClustered + } else { + if (chartArr[0].opcode == XLSConstants.BAR) { + (chartArr[0] as Bar).setIsClustered() + } + } + + } + + /** + * percentage=distance of pie slice from center of pie as % + * + * @param p + */ + fun setPercentage(p: Int) { + val df = this.getDataFormatRec(true) + df!!.percentage = p + } + + /** + * sets chart options such as threed options, grid lines, etc ... these options + * are distinct from chart-type-specific options, which are handled by the + * appropriate chart record (pie, bar ...) + * + * @param op String option name + * @param val Object value + */ + fun setOption(op: String, `val`: String) { + if (op.equals("Percentage", ignoreCase = true)) { + setPercentage(java.lang.Short.valueOf(`val`).toShort().toInt()) + } else if (op.equals("Shape", ignoreCase = true)) { + barShape = Integer.parseInt(`val`) + } else if (op == "ShowBubbleSizes" + || // TextDisp options + + op == "ShowLabelPct" || op == "ShowCatLabel" + || op == "ShowPct" || op == "Rotation" || + // op.equals("ShowValue") || unknown + op == "Label" || op == "TextRotation") { + val td = getDataLegendTextDisp(0) + td!!.setChartOption(op, `val`) + } else if (op == "Perspective" + || // ThreeD options + + op == "Cluster" || op == "ThreeDScaling" + || op == "TwoDWalls" || op == "PcGap" + || op == "PcDepth" || op == "PcHeight" + || op == "PcDist" || op == "AnElev" + || op == "AnRot") { + val td = this.getThreeDRec(true) + td!!.setChartOption(op, `val`) + } else if (op == "ShowValueLabel" + || // Attached Label Options + + op == "ShowValueAsPercent" + || op == "ShowLabelAsPercent" + || op == "ShowLabel" + || op == "ShowSeriesName" || op == "ShowBubbleLabel") { + val df = this.getDataFormatRec(true) + df!!.setDataLabels(op) + } else if (op.equals("SmoothedLine", ignoreCase = true) + || op.equals("ThreeDBubbles", ignoreCase = true) + || op.equals("ArShadow", ignoreCase = true)) { + val df = this.getDataFormatRec(true) + if (op.equals("SmoothedLine", ignoreCase = true)) + df!!.setSmoothLines(true) + else if (op.equals("ThreeDBubbles", ignoreCase = true)) + df!!.has3DBubbles = true + else + df!!.hasShadow = true + } + } + + /** + * Return the ThreeD rec associated with this ChartFormat, create if not + * present + */ + // NOTES: + // The Chart3d record specifies that the plot area of the chart group is rendered in a 3-D scene + // and also specifies the attributes of the 3-D plot area. + // The preceding chart group type MUST be of type bar, pie, line, area, or surface. + fun getThreeDRec(bCreate: Boolean): ThreeD? { + var td = Chart.findRec(this.chartArr, ThreeD::class.java) as ThreeD + if (td == null && bCreate) { // add ThreeD rec + var i = 0 + while (i < this.chartArr.size) { + val b = this.chartArr[i] + if (b.opcode == XLSConstants.CHARTFORMATLINK) { + if (i + 1 < chartArr.size && (this.chartArr[i + 1] as BiffRec).opcode == XLSConstants.SERIESLIST) + i++ // rare that SeriesList record appears + td = ThreeD.prototype as ThreeD? + td.parentChart = this.parentChart + this.chartArr.add(i + 1, td) + break + } + i++ + } + } + return td + } + + /** + * Add or Retrieve TextDisp and assoc records specific for Data Legends + * + * @return + */ + private fun getDataLegendTextDisp(type: Int): TextDisp? { + var i = Chart.findRecPosition(this.chartArr, Legend::class.java) + var td: TextDisp? = null + if (this.chartArr.size <= i + 1 || this.chartArr[i + 1].javaClass != DefaultText::class.java) { // then add one + val d = DefaultText.prototype as DefaultText? + d!!.setType(type.toShort()) + d.parentChart = this.parentChart + this.chartArr.add(++i, d) + td = TextDisp.getPrototype(ObjectLink.TYPE_DATAPOINTS, + "", this.workBook) as TextDisp + td.parentChart = this.parentChart + this.chartArr.add(++i, td) + } else { + var d = this.chartArr[i + 1] as DefaultText + if (d.type != type) { // / add a new one + i += 2 // add after TextDisp + d = DefaultText.prototype as DefaultText? + d.setType(type.toShort()) + d.parentChart = this.parentChart + this.chartArr.add(++i, d) + td = TextDisp.getPrototype( + ObjectLink.TYPE_DATAPOINTS, "", this.workBook) as TextDisp + td.parentChart = this.parentChart + this.chartArr.add(++i, td) + } else { // it's the correct one + i += 2 + td = this.chartArr[i] as TextDisp + } + } + return td + } + + /** + * Gets the dataformat record associated with this ChartFormat If none + * present, creates a basic DataFormat set of records DataFormat controls + * Data Labels, % Distance from sections, line formats ... + * + * @return DataFormat Record + */ + private fun getDataFormatRec(bCreate: Boolean): DataFormat? { + var df = Chart.findRec(this.chartArr, DataFormat::class.java) as DataFormat + if (df == null && bCreate) { // create dataformat + df = DataFormat.getPrototypeWithFormatRecs(this.parentChart) as DataFormat + this.addChartRecord(df) + } + return df + } + + + /** + * sets this chart to have default lines (Scatter, Line chart ...) + */ + fun setHasLines() { + val df = this.getDataFormatRec(true) + df!!.setHasLines() + } + + + /** + * sets this chart to have lines (Scatter, Line chart ...) of style lineStyle + *

                      Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + */ + fun setHasLines(lineStyle: Int) { + val df = this.getDataFormatRec(true) + df!!.setHasLines(lineStyle) + } + + /** + * sets this chart (Area, Line or Stock) to have Drop Lines + */ + fun setHasDropLines() { + + } + + /** + * sets 3d bubble state + * + * @param has3dBubbles + */ + fun setHas3DBubbles(has3dBubbles: Boolean) { + val df = this.getDataFormatRec(true) + df!!.has3DBubbles = has3dBubbles + + } + + /** + * sets the data labels for the entire chart (as opposed to a specific series/data point). + * A combination of: + * * SHOWVALUE= 0x1; + * * SHOWVALUEPERCENT= 0x2; + * * SHOWCATEGORYPERCENT= 0x4; + * * SHOWCATEGORYLABEL= 0x10; + * * SHOWBUBBLELABEL= 0x20; + * * SHOWSERIESLABEL= 0x40; + */ + fun setHasDataLabels(dl: Int) { + val df = this.getDataFormatRec(true) + df!!.setHasDataLabels(dl) + } + + /** + * 0 = no marker

                      + * 1 = square

                      + * 2 = diamond

                      + * 3 = triangle

                      + * 4 = X

                      + * 5 = star

                      + * 6 = Dow-Jones

                      + * 7 = standard deviation

                      + * 8 = circle

                      + * 9 = plus sign + */ + fun setMarkers(markerFormat: Int) { + val df = this.getDataFormatRec(true) + df!!.markerFormat = markerFormat + } + + /** + * Chart Line options (not available on all charts): + *

                      Drop Lines available on Line, Area and Stock charts + *

                      HiLow Lines are available on Line and Stock charts + *

                      Series Lines are available on Bar and OfPie charts + *
                      +     * 0= drop lines below the data points of Line, Area and Stock charts
                      +     * 1= High-low lines around the data points of Line and Stock charts
                      +     * 2- Series Line connecting data points of stacked column and bar charts and OfPie Charts
                      +    
                      * + *

                      + * + * @param lineType + */ + fun addChartLines(lineType: Int): ChartLine { + // ChartLine cl= (ChartLine) Chart.findRec(chartArr, ChartLine.class); + // if (cl==null) { + var cl: ChartLine? = null + // goes After DropBar or legend or 3d (or CFL) + var i = chartArr.size - 1 + while (i >= 1) { + val br = chartArr[i] + val op = br.opcode + if (op == XLSConstants.DROPBAR || op == XLSConstants.LEGEND || op == XLSConstants.THREED || op == XLSConstants.CHARTFORMATLINK) { + cl = ChartLine.prototype as ChartLine? + cl!!.parentChart = this.parentChart + chartArr.add(++i, cl) + cl.parentChart = this.parentChart // ensure can find + val lf = LineFormat.getPrototype(0, 1) as LineFormat + lf.parentChart = this.parentChart + chartArr.add(++i, lf) + break + } + i-- + } + // } + cl!!.lineType = lineType + return cl + } + + fun getChartLinesRec(id: Int): ChartLine? { + var i = Chart.findRecPosition(chartArr, ChartLine::class.java) + if (i > -1) { + while (i < chartArr.size) { + if ((chartArr[i] as BiffRec).opcode == XLSConstants.CHARTLINE) { + val cl = chartArr[i] as ChartLine + if (cl.lineType == id) + return cl + } else + break + i += 2 + } + } + return null + } + + /** + * add up/down bars (line, area stock) + */ + fun addUpDownBars() { + if (Chart.findRec(chartArr, Dropbar::class.java) == null) { + // create necessary records to describe up/down bars + val upBar = Dropbar.prototype as Dropbar? + upBar!!.parentChart = this.parentChart + val downBar = Dropbar.prototype as Dropbar? + downBar!!.parentChart = this.parentChart + var lf: LineFormat = LineFormat.prototype as LineFormat? + lf.parentChart = this.parentChart + var af: AreaFormat = AreaFormat.prototype as AreaFormat? + af.parentChart = this.parentChart + upBar.chartArr.add(lf) + upBar.chartArr.add(af) + lf = LineFormat.prototype as LineFormat? + lf.parentChart = this.parentChart + af = AreaFormat.prototype as AreaFormat? + af.parentChart = this.parentChart + downBar.chartArr.add(lf) + downBar.chartArr.add(af) + + // add dropbar records to subarray + var i = chartArr.size - 1 + while (i >= 0) { + val br = chartArr[i] + val op = br.opcode + if (op == XLSConstants.SERIESLIST || op == XLSConstants.LEGEND || op == XLSConstants.THREED || op == XLSConstants.CHARTFORMATLINK) { + chartArr.add(++i, upBar) + chartArr.add(++i, downBar) + break + } + i-- + } + } + } + + + /** + * parse upDownBars OOXML element (controled by 2 DropBar records in this subArray) + *

                      Valid for Line and Stock charts only + */ + fun parseUpDownBarsOOXML(xpp: XmlPullParser, lastTag: Stack, bk: WorkBookHandle) { + // assume dropbar records are not present ... + addUpDownBars() + var z = Chart.findRecPosition(chartArr, Dropbar::class.java) + val downBar = chartArr[z++] as Dropbar + val upBar = chartArr[z] as Dropbar + + + try { + var curbar: Dropbar? = null + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "downBars") { + curbar = downBar + } else if (tnm == "upBars") { + curbar = upBar + } else if (tnm == "gapWidth") { // default=150 + upBar.setGapWidth(Integer.valueOf(xpp.getAttributeValue(0))) // TODO: should this be in 1st dropbar or both?? + downBar.setGapWidth(Integer.valueOf(xpp.getAttributeValue(0))) // TODO: should this be in 1st dropbar or both?? + } else if (tnm == "spPr") { + lastTag.push(tnm) + val sppr = SpPr.parseOOXML(xpp, lastTag, bk).cloneElement() as SpPr + val lf = curbar!!.chartArr[0] as LineFormat + lf?.setFromOOXML(sppr) + // TODO: fill AreaFormat with sppr + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == "upDownBars") { + lastTag.pop() + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("parseUpDownBarsOOXML: $e") + } + + } + + + /** + * return the drawing order of this ChartFormat

                      + * For multiple charts-in-one, drawing order determines the order of the + * charts + * + * @return + */ + fun getDrawingOrder(): Int { + return drawingOrder.toInt() + } + + /** + * set the drawing order of this ChartFormat

                      + * For multiple charts-in-one, drawing order determines the order of the + * charts + * + * @param order + */ + fun setDrawingOrder(order: Int) { + drawingOrder = order.toShort() + val b = ByteTools.shortToLEBytes(drawingOrder) + this.data[18] = b[0] + this.data[19] = b[1] + } + + /** + * get the value of *almost* any chart option (axis options are in Axis) + * + * @param op String option e.g. Shadow or Percentage + * @return String value of option + */ + override fun getChartOption(op: String): String? { + val df = this.getDataFormatRec(false) + try { + if (op == "Percentage") { // pieformat + return df!!.percentage.toString() + } else if (op == "ShowValueLabel" + || // Attached Label Options + + op == "ShowValueAsPercent" + || op == "ShowLabelAsPercent" + || op == "ShowLabel" + || op == "ShowBubbleLabel") { + return df!!.getDataLabelType(op) + } else if (op == "ShowBubbleSizes" + || // TextDisp options + + op == "ShowLabelPct" || op == "ShowPct" + || op == "ShowCatLabel" + || + // op.equals("ShowValue") || unknown + op == "Rotation" || op == "Label" + || op == "TextRotation") { + val td = getDataLegendTextDisp(0) + return td!!.getChartOption(op) + } else if (op == "Perspective" + || // ThreeD options + + op == "Cluster" || op == "ThreeDScaling" + || op == "TwoDWalls" || op == "PcGap" + || op == "PcDepth" || op == "PcHeight" + || op == "PcDist" || op == "AnElev" + || op == "AnRot") { + val td = this.getThreeDRec(false) + return if (td != null) td.getChartOption(op) else "" + } else if (op == "ThreeDBubbles") { + return df!!.has3DBubbles.toString() + } else if (op == "ArShadow") { + return df!!.hasShadow.toString() + } else if (op == "SmoothLines") { + return df!!.smoothedLines.toString() + // TODO: FINSIH REST! + } else if (op == "AxisLabels") { // Radar, RadarArea + } else if (op == "BubbleSizeRatio") { // Scatter + } else if (op == "BubbleSize") { // Scatter + } else if (op == "ShowNeg") { // Scatter + } else if (op == "ColorFill") { // Surface + } else if (op == "Shading") { // Surface + } else if (op == "MarkerFormat") { // MarkerFormat + } + } catch (e: NullPointerException) { + + } + + return "" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 4000704166442059677L + + val prototype: XLSRecord? + get() { + val cf = ChartFormat() + cf.opcode = XLSConstants.CHARTFORMAT + cf.data = cf.PROTOTYPE_BYTES + cf.init() + return cf + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.java b/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.java deleted file mode 100644 index dbca0ce..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; - -/** - * ChartFormatLink: Not Used. Great. (0x1022) - */ -public class ChartFormatLink extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6928761103400718842L; - - public void init() { - super.init(); - } - - public static XLSRecord getPrototype() { - ChartFormatLink cfl = new ChartFormatLink(); - cfl.setOpcode(CHARTFORMATLINK); - cfl.setData(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); // unused - return cfl; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.kt b/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.kt new file mode 100644 index 0000000..0fe1e60 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ChartFormatLink.kt @@ -0,0 +1,51 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord + +/** + * **ChartFormatLink: Not Used. Great. (0x1022)** + */ +class ChartFormatLink : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 6928761103400718842L + + // unused + val prototype: XLSRecord? + get() { + val cfl = ChartFormatLink() + cfl.opcode = XLSConstants.CHARTFORMATLINK + cfl.data = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + return cfl + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.java b/src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.kt similarity index 64% rename from src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.java rename to src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.kt index ca4cfef..ef2daf2 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartFrtInfo.kt @@ -20,23 +20,25 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * CHARTFRTINFO: Chart Future Record Type Info (850h) - *

                      + * **CHARTFRTINFO: Chart Future Record Type Info (850h)** + * + * * Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. * This record contains information describing the versions of Excel that originally * created and last saved the file, and the FRT IDs that are used in the file. - *
                      In a file written by Excel 2000 or later, this record appears before the end of CHART + *

                      In a file written by Excel 2000 or later, this record appears before the end of CHART * record block and before any other FRT in the Chart record stream. - *
                      In a file written by Excel 97, this record may be missing or will appear after + *

                      In a file written by Excel 97, this record may be missing or will appear after * the CHART record block. If this record appears after the END record of CHART record block, * the verWriter field is assumed to be 8 for Excel 97 regardless of the actual value * in the record. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0850h @@ -45,33 +47,37 @@ * 9 verWriter 1 Excel version that last saved the file * 10 cCFRTID 2 Count of FRT ID value ranges in list * 12 rgCFRTID var List of FRT ID values used for charts - *

                      + * + * * CFRTID Structure * Offset Field Name Size Contents * 0 rtFirst 2 First FRT in range * 2 rtLast 2 Last FRT in range */ -public class ChartFrtInfo extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7722730813154117198L; +class ChartFrtInfo : GenericChartObject(), ChartObject { - public void init() { - super.init(); + private val PROTOTYPE_BYTES = byteArrayOf(80, 8, 0, 0, 10, 10, 3, 0, 80, 8, 90, 8, 97, 8, 97, 8, 106, 8, 107, 8) + + override fun init() { + super.init() } - private byte[] PROTOTYPE_BYTES = new byte[]{80, 8, 0, 0, 10, 10, 3, 0, 80, 8, 90, 8, 97, 8, 97, 8, 106, 8, 107, 8}; + private fun updateRecord() {} - public static XLSRecord getPrototype() { - ChartFrtInfo cri = new ChartFrtInfo(); - cri.setOpcode(CHARTFRTINFO); - cri.setData(cri.PROTOTYPE_BYTES); - cri.init(); - return cri; - } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 7722730813154117198L - private void updateRecord() { + val prototype: XLSRecord? + get() { + val cri = ChartFrtInfo() + cri.opcode = XLSConstants.CHARTFRTINFO + cri.data = cri.PROTOTYPE_BYTES + cri.init() + return cri + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartLine.java b/src/main/java/io/starter/formats/XLS/charts/ChartLine.java deleted file mode 100644 index 7511751..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ChartLine.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.SpPr; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * ChartLine: Drop/Hi-Lo/Series Lines on a Line Chart (0x101c) - *

                      - * The CrtLine record specifies the presence of drop lines, high-low lines, series lines or leader lines on the chart group. - * This record is followed by a LineFormat record which specifies the format of the lines. - * - *
                      id (2 bytes): An unsigned integer that specifies the type of line that is present on the chart group. - * This field value MUST be unique among the other id field values in CrtLine records in the current chart group. - * This field MUST be greater than the id field values in preceding CrtLine records in the current chart group. MUST be a value from the following table: - *

                      - * Value Type of Line - * 0x0000 Drop lines below the data points of line, area, and stock chart groups. - * 0x0001 High-low lines around the data points of line and stock chart groups. - * 0x0002 Series lines connecting data points of stacked column and bar chart groups, and the primary pie to the secondary bar/pie of bar of pie and pie of pie chart groups. - * 0x0003 Leader lines with non-default formatting connecting data labels to the data point of pie and pie of pie chart groups. - *

                      - * But also there is: - * DROPBAR = DropBar Begin LineFormat AreaFormat [GELFRAME] [SHAPEPROPS] End - */ -public class ChartLine extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8311605814020380069L; - private short id; - private ChartFormat cf; // necessary to link to corresponding ChartFormat - public static byte TYPE_DROPLINE = 0; - public static byte TYPE_HILOWLINE = 1; - public static byte TYPE_SERIESLINE = 2; - public static byte TYPE_LEADERLINE = 3; - - public void init() { - super.init(); - id = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } - - public static XLSRecord getPrototype() { - ChartLine cl = new ChartLine(); - cl.setOpcode(CHARTLINE); - cl.setData(new byte[]{0, 0}); - cl.init(); - return cl; - } - - /** - *
                      0= drop lines below the data points of line, area and stock charts - *
                      1= High-low lines around the data points of line and stock charts - *
                      2- Series Line connecting data points of stacked column and bar charts + some pie chart configurations - *
                      3= Leader lines with non-default formatting for pie and pie of pie - * - * @return - */ - public int getLineType() { - return id; - } - - /** - * sets the chart line type: - *
                      0= drop lines below the data points of line, area and stock charts - *
                      1= High-low lines around the data points of line and stock charts - *
                      2- Series Line connecting data points of stacked column and bar charts + some pie chart configurations - *
                      3= Leader lines with non-default formatting for pie and pie of pie - */ - public void setLineType(int id) { - this.id = (short) id; - byte[] b = ByteTools.shortToLEBytes(this.id); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - /** - * return the LineFormat rec associated with this ChartLine - * (== the next record in the cf chart array) - * - * @return - */ - private LineFormat findLineFormatRec() { - LineFormat lf = null; - for (int i = 0; i < cf.chartArr.size(); i++) { - if (cf.chartArr.get(i).equals(this)) { - lf = (LineFormat) cf.chartArr.get(i + 1); - break; - } - } - return lf; - } - - /** - * parse a Chart Line OOXML element: either - *

                    • dropLines - *
                    • hiLowLines - *
                    • serLines - * - * @param xpp - * @param lastTag - * @param cf - */ - public void parseOOXML(XmlPullParser xpp, Stack lastTag, ChartFormat cf, WorkBookHandle bk) { - this.cf = cf; - String endTag = lastTag.peek(); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("spPr")) { - lastTag.push(tnm); - SpPr sppr = (SpPr) SpPr.parseOOXML(xpp, lastTag, bk).cloneElement(); - LineFormat lf = findLineFormatRec(); - if (lf != null) - lf.setFromOOXML(sppr); - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(endTag)) - lastTag.pop(); - break; - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ChartLine.parseOOXML: " + e.toString()); - } - } - - /** - * return the OOXML to define this ChartLine - * - * @return - */ - public StringBuffer getOOXML() { - StringBuffer cooxml = new StringBuffer(); - String tag = null; - if (id == TYPE_DROPLINE) - tag = "c:dropLines>"; - else if (id == TYPE_HILOWLINE) - tag = "c:hiLowLines>"; - else if (id == TYPE_SERIESLINE) - tag = "c:serLines>"; - cooxml.append("<" + tag); - LineFormat lf = findLineFormatRec(); - if (lf != null) - cooxml.append(lf.getOOXML()); - cooxml.append(". + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.SpPr +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * **ChartLine: Drop/Hi-Lo/Series Lines on a Line Chart (0x101c)** + * + * + * The CrtLine record specifies the presence of drop lines, high-low lines, series lines or leader lines on the chart group. + * This record is followed by a LineFormat record which specifies the format of the lines. + * + *

                      id (2 bytes): An unsigned integer that specifies the type of line that is present on the chart group. + * This field value MUST be unique among the other id field values in CrtLine records in the current chart group. + * This field MUST be greater than the id field values in preceding CrtLine records in the current chart group. MUST be a value from the following table: + * + * + * Value Type of Line + * 0x0000 Drop lines below the data points of line, area, and stock chart groups. + * 0x0001 High-low lines around the data points of line and stock chart groups. + * 0x0002 Series lines connecting data points of stacked column and bar chart groups, and the primary pie to the secondary bar/pie of bar of pie and pie of pie chart groups. + * 0x0003 Leader lines with non-default formatting connecting data labels to the data point of pie and pie of pie chart groups. + * + * + * But also there is: + * DROPBAR = DropBar Begin LineFormat AreaFormat [GELFRAME] [SHAPEPROPS] End + */ +class ChartLine : GenericChartObject(), ChartObject { + private var id: Short = 0 + private var cf: ChartFormat? = null // necessary to link to corresponding ChartFormat + + /** + *

                      0= drop lines below the data points of line, area and stock charts + *

                      1= High-low lines around the data points of line and stock charts + *

                      2- Series Line connecting data points of stacked column and bar charts + some pie chart configurations + *

                      3= Leader lines with non-default formatting for pie and pie of pie + * + * @return + */ + /** + * sets the chart line type: + *

                      0= drop lines below the data points of line, area and stock charts + *

                      1= High-low lines around the data points of line and stock charts + *

                      2- Series Line connecting data points of stacked column and bar charts + some pie chart configurations + *

                      3= Leader lines with non-default formatting for pie and pie of pie + */ + var lineType: Int + get() = id.toInt() + set(id) { + this.id = id.toShort() + val b = ByteTools.shortToLEBytes(this.id) + this.data[0] = b[0] + this.data[1] = b[1] + } + + /** + * return the OOXML to define this ChartLine + * + * @return + */ + val ooxml: StringBuffer + get() { + val cooxml = StringBuffer() + var tag: String? = null + if (id == TYPE_DROPLINE.toShort()) + tag = "c:dropLines>" + else if (id == TYPE_HILOWLINE.toShort()) + tag = "c:hiLowLines>" + else if (id == TYPE_SERIESLINE.toShort()) + tag = "c:serLines>" + cooxml.append("<" + tag!!) + val lf = findLineFormatRec() + if (lf != null) + cooxml.append(lf.ooxml) + cooxml.append(", cf: ChartFormat, bk: WorkBookHandle) { + this.cf = cf + val endTag = lastTag.peek() + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + if (tnm == "spPr") { + lastTag.push(tnm) + val sppr = SpPr.parseOOXML(xpp, lastTag, bk).cloneElement() as SpPr + val lf = findLineFormatRec() + lf?.setFromOOXML(sppr) + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == endTag) + lastTag.pop() + break + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("ChartLine.parseOOXML: $e") + } + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -8311605814020380069L + var TYPE_DROPLINE: Byte = 0 + var TYPE_HILOWLINE: Byte = 1 + var TYPE_SERIESLINE: Byte = 2 + var TYPE_LEADERLINE: Byte = 3 + + val prototype: XLSRecord? + get() { + val cl = ChartLine() + cl.opcode = XLSConstants.CHARTLINE + cl.data = byteArrayOf(0, 0) + cl.init() + return cl + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartObject.java b/src/main/java/io/starter/formats/XLS/charts/ChartObject.kt similarity index 79% rename from src/main/java/io/starter/formats/XLS/charts/ChartObject.java rename to src/main/java/io/starter/formats/XLS/charts/ChartObject.kt index 06aabf2..493c26b 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ChartObject.java +++ b/src/main/java/io/starter/formats/XLS/charts/ChartObject.kt @@ -21,7 +21,7 @@ * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts /** @@ -30,40 +30,38 @@ * and/or other chartObjects containing their own arrays. */ -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord -import java.util.ArrayList; +import java.util.ArrayList -public interface ChartObject { +interface ChartObject { /** - * Add a chart record to this chart object - * - */ - void addChartRecord(XLSRecord b); - - /** - * Return a list of chart records from this chart object. This should include begin/end records, along with + * Return a list of chart records from this chart object. This should include begin/end records, along with * those of the objects sub-object, correctly ordered. * @return */ - ArrayList getChartRecords(); + val chartRecords: ArrayList<*> /** * Return the chart record that this object is associated with * @return */ - Chart getParentChart(); - /** * Set the parent chart for this record */ - void setParentChart(Chart c); + var parentChart: Chart /** * Get the output array of records, including begin/end records and those of it's children. */ - ArrayList getRecordArray(); + val recordArray: ArrayList<*> + + /** + * Add a chart record to this chart object + * + */ + fun addChartRecord(b: XLSRecord) } diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java b/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java deleted file mode 100644 index b375d0f..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ChartSeries.java +++ /dev/null @@ -1,991 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.CellHandle; -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ExcelTools; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.DLbls; -import io.starter.formats.OOXML.DPt; -import io.starter.formats.OOXML.Marker; -import io.starter.formats.OOXML.SpPr; -import io.starter.formats.XLS.*; -import io.starter.formats.XLS.formulas.Ptg; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Stack; -import java.util.Vector; - -public class ChartSeries implements ChartConstants, Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7862828186455339066L; - private ArrayList series = new ArrayList(); // ALL series in chart MAPPED to chart that "owns" it - private JSONArray seriesJSON = null; // save series JSON for update comparisons later - protected double[] minmaxcache = null; // stores minimum/maximum/minor/major for chart scale; cached; - protected String[] legends; - protected ArrayList seriesranges; - protected Object[] categories; - protected ArrayList seriesvalues; - protected String[] seriescolors; - protected Chart parentChart; -// protected transient WorkBookHandle wbh; - - /** - * series stores new Object[] {Series Record, Integer.valueOf(nCharts)} - * - * @param o - */ - public void add(Object[] o) { - series.add(o); - } - - public void setParentChart(Chart c) { - parentChart = c; - } -// public void setWorkBook(WorkBookHandle wbh) { this.wbh= wbh; } - - /** - * returns an array containing the and maximum values of Y (Value) axis, along with the - * maximum number of series values of each series (bar, line, etc.) ... - *
                      sets all series cache values: seriesvalues, seriesranges, seriescolors, legends and minmaxcache - * - *
                      Note: this will only reset cached values if isDirty - * - * @return double[] - */ - public double[] getMetrics(boolean isDirty) { - if (!isDirty && minmaxcache != null) return minmaxcache; - // trap minimum, maximum + number of series - ChartType co = parentChart.getChartObject(); // default chart object TODO: overlay charts - seriesvalues = new ArrayList(); - seriesranges = new ArrayList(); - io.starter.formats.XLS.Boundsheet sht = parentChart.getSheet(); - java.util.Vector s = this.getAllSeries(-1); - // Category values ******************************************************************************* - if (s.size() > 0) { - try { - CellRange cr = new CellRange(((Series) s.get(0)).getCategoryValueAi().toString(), parentChart.wbh, true); - CellHandle[] ch = cr.getCells(); - if (ch != null) { // found a template with a chart with series but no categories - categories = new Object[ch.length]; - for (int j = 0; j < ch.length; j++) { - try { - categories[j] = ch[j].getFormattedStringVal(true); - } catch (IllegalArgumentException e) { // catch format exceptions - categories[j] = ch[j].getStringVal(); - } - } - } else if (s.size() > 0) { - cr = new CellRange(((Series) s.get(0)).getSeriesValueAi().toString(), parentChart.wbh, true); - int sz = cr.getCells().length; - categories = new Object[sz]; - for (int j = 0; j < sz; j++) - categories[j] = Integer.valueOf(j + 1).toString(); - } - } catch (Exception e) { - Logger.logWarn("ChartSeries.getMinMax: " + e.toString()); - } - } - // Series colors, labels and values *************************************************************** - double yMax = 0.0, yMin = Double.MAX_VALUE; - int nseries = 0; - seriescolors = null; - legends = null; - int charttype = co.getChartType(); - // obtain/store series colors, store series values and trap maximum and - // minimun values so can be used below for axis scale - /* - * A Scatter chart has two value axes, showing one set of numerical data along the x-axis and another along the y-axis. - * It combines these values into single data points and displays them in uneven intervals, or clusters - */ - if (charttype != PIECHART && charttype != DOUGHNUTCHART) { - seriescolors = new String[s.size()]; - legends = new String[s.size()]; - for (int i = 0; i < s.size(); i++) { - Series myseries = ((Series) s.get(i)); - seriescolors[i] = myseries.getSeriesColor(); - legends[i] = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegendText()).toString(); - CellRange cr = new CellRange(myseries.getSeriesValueAi().toString(), parentChart.wbh, true); - CellHandle[] ch = cr.getCells(); - nseries = Math.max(nseries, ch.length); - double[] seriesvals; - String[] sranges; - // String[] series_strings; - if (!myseries.hasBubbleSizes()) { - seriesvals = new double[nseries]; - sranges = new String[nseries]; - } else { - seriesvals = new double[nseries * 2]; - sranges = new String[nseries * 2]; - } - // series_strings = new String[seriesvals.length]; - - for (int j = 0; j < ch.length; j++) { - try { - sranges[j] = ch[j].getCellAddressWithSheet(); - seriesvals[j] = ch[j].getDoubleVal(); - if (Double.isNaN(seriesvals[j])) - seriesvals[j] = 0.0; - yMax = Math.max(yMax, seriesvals[j]); - yMin = Math.min(yMin, seriesvals[j]); - } catch (NumberFormatException n) { - } - } - if (myseries.hasBubbleSizes()) { // append bubble sizes to series values ... see BubbleChart.getSVG for parsing - int z = ch.length; - CellRange crb = new CellRange(myseries.getBubbleValueAi().toString(), parentChart.wbh, true); - CellHandle[] chb = crb.getCells(); - for (int j = 0; j < ch.length; j++) { - seriesvals[j + z] = chb[j].getDoubleVal(); - sranges[j + z] = chb[j].getCellAddressWithSheet(); - } - } - seriesvalues.add(seriesvals); // trap and add series value points - seriesranges.add(sranges); // trap series range - } - } else if (charttype == DOUGHNUTCHART && s.size() > 1) { // like a PIE chart but can have multiple series - legends = new String[categories.length]; // for PIE/DONUT charts, legends are actually category labels, not series labels - for (int i = 0; i < categories.length; i++) - legends[i] = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(categories[i].toString()).toString(); - for (int i = 0; i < s.size(); i++) { - Series myseries = ((Series) s.get(i)); - // legends[i]= - // io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegend()); - CellRange cr = new CellRange(myseries.getSeriesValueAi().toString(), parentChart.wbh, true); - CellHandle[] ch = cr.getCells(); - double[] seriesvals = new double[ch.length]; - String[] sranges = new String[ch.length]; - if (seriescolors == null) - seriescolors = new String[ch.length]; - for (int j = 0; j < ch.length; j++) { - try { - seriesvals[j] = ch[j].getDoubleVal(); - if (ch[j].getWorkSheetHandle().getMysheet().equals(sht)) - sranges[j] = ch[j].getCellAddress(); - yMax = Math.max(yMax, seriesvals[j]); - yMin = Math.min(yMin, seriesvals[j]); - if (i == 0) { // only do for 1st series; will be the - // same for rest - seriescolors[j] = myseries.getPieSliceColor(j); - /*if (seriescolors[j] == 0x4D - || seriescolors[j] == 0x4E) - seriescolors[j] = io.starter.formats.XLS.FormatConstants.COLOR_WHITE;*/ - } - - } catch (NumberFormatException n) { - } - } - seriesvalues.add(seriesvals); // trap and add series value points - seriesranges.add(sranges); // trap series range - } - } else { // PIES - only 1 series - if (s.size() > 0) { - // PIE: 1 series data - CellHandle[] cats = new CellRange(((Series) s.get(0)).getCategoryValueAi().toString(), parentChart.wbh, true).getCells(); - if (cats != null) { - nseries = cats.length; - legends = new String[cats.length]; // for PIE charts, legends are actually category labels, not series labels - for (int i = 0; i < cats.length; i++) - legends[i] = cats[i].getFormattedStringVal(true); - } - seriescolors = new String[nseries]; - Series myseries = ((Series) s.get(0)); - try { - CellRange cr = new CellRange(myseries.getSeriesValueAi().toString(), parentChart.wbh, true); - CellHandle[] ch = cr.getCells(); - // error trap - shouldn't happen - if (ch.length != nseries) { - Logger.logWarn("ChartHandle.getSeriesInfo: unexpected Pie Chart structure"); - nseries = Math.min(nseries, ch.length); - } - double[] seriesvals = new double[nseries]; - String[] sranges = new String[nseries]; - for (int i = 0; i < nseries; i++) { - seriescolors[i] = myseries.getPieSliceColor(i); - /*if (seriescolors[i] == 0x4D || seriescolors[i] == 0x4E) - seriescolors[i] = io.starter.formats.XLS.FormatConstants.COLOR_WHITE;*/ - // legends[i]= - // io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegend()); - // // same for every series ... - seriesvals[i] = ch[i].getDoubleVal(); - if (ch[i].getWorkSheetHandle().getMysheet().equals(sht)) - sranges[i] = ch[i].getCellAddress(); - yMax = Math.max(yMax, seriesvals[i]); - yMin = Math.min(yMin, seriesvals[i]); - } - seriesvalues.add(seriesvals); // trap and add series value points - seriesranges.add(sranges); // trap series range - } catch (IllegalArgumentException e) { - // error in cell range sheet ... - } - } - } - // For stacked-type charts, must sum values for ymax - if (co.isStacked()) { - // scale is SUM of values, yMax is maximum total per series point - double[] sum = new double[nseries]; - for (int i = 0; i < seriesvalues.size(); i++) { - double[] seriesv = (double[]) seriesvalues.get(i); - for (int j = 0; j < seriesv.length; j++) { - sum[j] = sum[j] + seriesv[j]; - } - } - yMax = 0; - for (int i = 0; i < nseries; i++) { - yMax = Math.max(sum[i], yMax); - } - } - minmaxcache = new double[2]; - minmaxcache[0] = yMin; - minmaxcache[1] = yMax; - // minmaxcache[2]= nSeries; - // minmaxcache= new double[3][]; // 3 possible axes: x, y and z - // minmaxcache[axisType]= minMax; - return minmaxcache; - } - - /** - * Change series ranges for ALL matching series - * - * @param originalrange - * @param newrange - * @return - */ - public boolean changeSeriesRange(String originalrange, String newrange) { - boolean changed = false; - for (int i = 0; i < series.size(); i++) { - Series s = (Series) series.get(i)[0]; - Ai ai = s.getSeriesValueAi(); - if (ai != null) { - if (ai.toString().equalsIgnoreCase(originalrange)) { - changed = ai.changeAiLocation(originalrange, newrange); - } - } - } - - return changed; - } - - /** - * Return a string representing all series in this chart - * - * @param nChart 0=default, 1-9= overlay charts -1 for ALL charts - * @return - */ - public String[] getSeries(int nChart) { - Vector seriesperchart = this.getAllSeries(nChart); - String[] retStr = new String[seriesperchart.size()]; - for (int i = 0; i < seriesperchart.size(); i++) { - Series s = (Series) seriesperchart.get(i); - Ai a = s.getSeriesValueAi(); - retStr[i] = a.toString(); - } - return retStr; - } - - /** - * Return an array of strings, one for each category - * - * @param nChart 0=default, 1-9= overlay charts -1 for All - * @return - */ - public String[] getCategories(int nChart) { - Vector seriesperchart = this.getAllSeries(nChart); - String[] retStr = new String[seriesperchart.size()]; - for (int i = 0; i < seriesperchart.size(); i++) { - Series s = (Series) seriesperchart.get(i); - Ai a = s.getCategoryValueAi(); - retStr[i] = a.toString(); - } - return retStr; - } - - /** - * get all the series objects for ALL charts - * (i.e. even in overlay charts) - * - * @return - */ - public Vector getAllSeries() { - return getAllSeries(-1); - } - - /** - * get all the series objects in the specified chart (-1 for ALL) - * - * @param nChart 0=default, 1-9= overlay charts -1 for ALL series - * @return - */ - public Vector getAllSeries(int nChart) { -/* if (nChart==-1) { //return all series - return new Vector(series.keySet()); // unordered!!! - } -*/ - Vector retVec = new Vector(); - for (int i = 0; i < series.size(); i++) { - Integer chart = (Integer) series.get(i)[1]; - if (nChart == -1 || nChart == chart.intValue()) { - Series s = (Series) series.get(i)[0]; - retVec.add(s); - } - } - return retVec; - } - - /** - * Add a series object to the array. - * - * @param seriesRange = one row range, expressed as (Sheet1!A1:A12); - * @param categoryRange = category range - * @param bubbleRange= bubble range, if any 20070731 KSC - * @param seriesText = label for the series; - * @param nChart 0=default, 1-9= overlay charts - * s * @return - */ - public Series addSeries(String seriesRange, String categoryRange, String bubbleRange, String legendRange, String legendText, ChartType chartObject, int nChart) { - Series s = Series.getPrototype(seriesRange, categoryRange, bubbleRange, legendRange, legendText, chartObject); - s.setParentChart(chartObject.getParentChart()); - s.setShape(chartObject.getBarShape()); - series.add(new Object[]{s, Integer.valueOf(nChart)}); - // Update parent chartArr - ArrayList chartArr = s.getParentChart().chartArr; - for (int i = 0; i < chartArr.size(); i++) { - BiffRec br = chartArr.get(i); - BiffRec br2 = null; - if (i < chartArr.size() - 1) br2 = chartArr.get(i + 1); - if (br != null && ((br.getOpcode() == XLSConstants.SERIES && br2.getOpcode() != XLSConstants.SERIES) - || br.getOpcode() == XLSConstants.FRAME && br2.getOpcode() == XLSConstants.SHTPROPS)) { - chartArr.add(i + 1, s); - break; - } - } - if (chartObject.getChartType() == STOCKCHART) - s.setHasLines(5); - return s; - } - - /** - * specialty method to take absolute index of series and remove it - *
                      only used in WorkSheetHandle - * - * @param index - */ - public void removeSeries(int index) { - Vector v = this.getAllSeries(); - this.removeSeries((Series) v.get(index)); - } - - /** - * remove desired series from chart - * - * @param index - */ - public void removeSeries(Series seriestodelete) { - for (int z = 0; z < series.size(); z++) { - Series ss = (Series) series.get(z)[0]; - if (ss.equals(seriestodelete)) { - series.remove(z); - break; - } - } - } - - /** - * get a chart series handle based off the series name - * - * @param seriesName - * @param nChart 0=default, 1-9= overlay charts - * @return - */ - public Series getSeries(String seriesName, int nChart) { - Vector seriesperchart = this.getAllSeries(nChart); - for (int i = 0; i < seriesperchart.size(); i++) { - Series s = (Series) seriesperchart.get(i); - if (s.getLegendText().equalsIgnoreCase(seriesName)) return s; - } - return null; - } - - /** - * changes the category range which matches originalrange to new range - * - * @param originalrange - * @param newrange - * @return - */ - public boolean changeCategoryRange(String originalrange, String newrange) { - boolean changed = false; - for (int i = 0; i < series.size(); i++) { - Series s = (Series) series.get(i)[0]; - Ai ai = s.getCategoryValueAi(); - if (ai.toString().equalsIgnoreCase(originalrange)) { - changed = ai.changeAiLocation(originalrange, newrange); - } - } - return changed; - } - - /** - * attempts to replace all category elements containing originalval text to newval - * - * @param originalval - * @param newval - * @return - */ - public boolean changeTextValue(String originalval, String newval) { - boolean changed = false; - for (int i = 0; i < series.size(); i++) { - Series s = (Series) series.get(i)[0]; - Ai ai = s.getCategoryValueAi(); - if (ai.getText().equals(originalval)) { - ai.setText(newval); - } - - } - return changed; - } - - /** - * retrieve the current data range JSON for comparisons - * - * @return JSONObject in form of: c:catgeory range, {v:series range, l:legendrange, b: bubble sizes} for each series - */ - public org.json.JSONObject getDataRangeJSON() { - JSONObject seriesJSON = new JSONObject(); - java.util.Vector allseries = this.getAllSeries(-1); - try { - JSONArray series = new JSONArray(); - for (int i = 0; i < allseries.size(); i++) { - try { - //v=series val range, l=legend cell [,b= bubble sizes] - Series thisseries = (Series) allseries.get(i); - Ai serAi = thisseries.getSeriesValueAi(); - if (i == 0) { - Ai catAi = thisseries.getCategoryValueAi(); - seriesJSON.put("c", catAi.toString()); - } - JSONObject seriesvals = new JSONObject(); - seriesvals.put("v", serAi.toString()); - seriesvals.put("l", thisseries.getLegendRef()); - if (thisseries.hasBubbleSizes()) - seriesvals.put("b", thisseries.getBubbleValueAi().toString()); - series.put(seriesvals); - } catch (Exception e) { // keep going - } - } - seriesJSON.put("Series", series); - } catch (JSONException e) { - Logger.logErr("ChartSeries.getDataRangeJSON: Error retrieving Series Information: " + e.toString()); - } - // seriesJSON.getJSONArray("Series").length() - // seriesJSON.getJSONArray("Series").get(1) - // ((JSONObject)seriesJSON.getJSONArray("Series").get(1)).get("v") value range - // ((JSONObject)seriesJSON.getJSONArray("Series").get(1)).get("l") legend ref - // ((JSONObject)seriesJSON.getJSONArray("Series").get(1)).has("b") bubble sizes - // seriesJSON.get("c") category range - return seriesJSON; - } - - /** - * retrieve the current Series JSON for comparisons - * - * @return JSONArray - */ - public JSONArray getSeriesJSON() { - return seriesJSON; - } - - /** - * set the current Series JSON - * - * @param s JSONArray - */ - public void setSeriesJSON(JSONArray s) - throws JSONException { - seriesJSON = new JSONArray(s.toString()); - } - - /** - * return an array of ALL cell references of the chart - */ - public Ptg[] getCellRangePtgs() { - CompatibleVector locptgs = new CompatibleVector(); - for (int i = 0; i < series.size(); i++) { - Series s = (Series) series.get(i)[0]; - for (int j = 0; j < s.chartArr.size(); j++) { - BiffRec br = s.chartArr.get(j); - if (br.getOpcode() == XLSConstants.AI) { - try { - Ptg[] ps = ((Ai) br).getCellRangePtgs(); - for (int t = 0; t < ps.length; t++) locptgs.add(ps[t]); - } catch (Exception e) { - } - } - } - } - Ptg[] ret = new Ptg[locptgs.size()]; - locptgs.toArray(ret); - return ret; - } - - /** - * @return an HashMap of Series Range Ptgs mapped by Series representing all the series in the chart - */ - public HashMap getSeriesPtgs() { - HashMap seriesPtgs = new HashMap(); - for (int i = 0; i < series.size(); i++) { - Series s = (Series) series.get(i)[0]; - for (int j = 0; j < s.chartArr.size(); j++) { - BiffRec br = s.chartArr.get(j); - if (br.getOpcode() == XLSConstants.AI) { - if (((Ai) br).getType() == Ai.TYPE_VALS) { - try { - Ptg[] ps = ((Ai) br).getCellRangePtgs(); - seriesPtgs.put(s, ps); - break; // we're done, move onto next - } catch (Exception e) { - } - } - } - } - } - return seriesPtgs; - } - - - /** - * for overlay charts, store series list - * - * @param nCharts - * @param seriesList - */ - public void addSeriesMapping(int nCharts, int[] seriesList) { - for (int i = 0; i < seriesList.length; i++) { - try { - int idx = seriesList[i] - 1; - // ALL series in chart MAPPED to chart that "owns" it - Series s = (Series) series.get(idx)[0]; - series.add(idx, new Object[]{s, Integer.valueOf(nCharts)}); - } catch (ArrayIndexOutOfBoundsException ae) { - // happens -- are they deleted series????? - } - } - } - - /** - * return an array of legend text - * - * @param nChart 0=default, 1-9= overlay charts -1 for ALL - */ - public String[] getLegends(int nChart) { - Vector seriesperchart = this.getAllSeries(nChart); - String[] ret = new String[seriesperchart.size()]; - for (int i = 0; i < seriesperchart.size(); i++) { - ret[i] = ((Series) seriesperchart.get(i)).getLegendText(); - } - return ret; - } - - /** - * return the type of markers for each series: - *
                      0 = no marker - *
                      1 = square - *
                      2 = diamond - *
                      3 = triangle - *
                      4 = X - *
                      5 = star - *
                      6 = Dow-Jones - *
                      7 = standard deviation - *
                      8 = circle - *
                      9 = plus sign - **/ - public int[] getMarkerFormats() { - int[] markers = new int[series.size()]; - for (int i = 0; i < series.size(); i++) { - Series s = (Series) series.get(i)[0]; - markers[i] = s.getMarkerFormat(); - } - return markers; - } - - - /** - * parse a chartSpace->chartType->ser element into our Series record/structure - * - * @param xpp XML pullparser positioned at ser element - * @param wbh WorkBookHandle - * @param parentChart parent chart object - * @param lastTag - * @return - */ - public static Series parseOOXML(XmlPullParser xpp, WorkBookHandle wbh, ChartType parentChart, boolean hasPivotTableSource, Stack lastTag) { - try { - int eventType = xpp.getEventType(); - int idx = 0; - int seriesidx = parentChart.getParentChart().getAllSeries().size(); - String[] ranges = {"", "", "", ""}; //legend, cat, ser/value, bubble cell references - String legendText = ""; - SpPr sp = null; - DLbls d = null; - Marker m = null; - boolean smooth = false; - ArrayList dpts = null; - String cache = null; - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - if (tnm.equals("ser")) { // Represents a single field in the PivotTable. This complex type contains - idx = 0; - } else if (tnm.equals("order")) {// attr val= order - } else if (tnm.equals("cat") || tnm.equals("xVal")) { // children: CHOICE OF: multiLvlStrRef, numLiteral, numRef, strLit, strRef - idx = 1; - } else if (tnm.equals("val") || tnm.equals("yVal")) { // children: CHOICE OF: numLit, numRef - idx = 2; - } else if (tnm.equals("dLbls")) { // data labels - lastTag.push(tnm); // keep track of element hierarchy - d = (DLbls) DLbls.parseOOXML(xpp, lastTag, wbh).cloneElement(); - if (d.showBubbleSize()) parentChart.setChartOption("ShowBubbleSizes", "1"); - if (d.showCatName()) parentChart.setChartOption("ShowCatLabel", "1"); - if (d.showLeaderLines()) parentChart.setChartOption("ShowLdrLines", "1"); - if (d.showLegendKey()) - ; // TODO: handle show legend key - if (d.showPercent()) parentChart.setChartOption("ShowLabelPct", "1"); - if (d.showSerName()) parentChart.setChartOption("ShowLabel", "1"); - if (d.showVal()) parentChart.setChartOption("ShowValueLabel", "1"); - // data label options - } else if (tnm.equals("dPt")) { // data point(s) - if (dpts == null) dpts = new ArrayList(); - lastTag.push(tnm); // keep track of element hierarchy - dpts.add(DPt.parseOOXML(xpp, lastTag, wbh).cloneElement()); - } else if (tnm.equals("spPr")) { // series spPr - lastTag.push(tnm); // keep track of element hierarchy - sp = (SpPr) SpPr.parseOOXML(xpp, lastTag, wbh).cloneElement(); - } else if (tnm.equals("marker")) { - lastTag.push(tnm); // keep track of element hierarchy - m = (Marker) Marker.parseOOXML(xpp, lastTag, wbh).cloneElement(); - } else if (tnm.equals("bubbleSize")) { - idx = 3; - } else if (tnm.equals("shape")) { // bar only - parentChart.convertShape(xpp.getAttributeValue(0)); - } else if (tnm.equals("smooth")) { // line chart - smooth = (xpp.getAttributeCount() == 0 || !xpp.getAttributeValue(0).equals("0")); - } else if (tnm.equals("explosion")) { - String v = xpp.getAttributeValue(0); - parentChart.setChartOption("Percentage", v); - // NOTE: two types of values; 1- reference denoted by f element parents can be numRef, strRef or multiLvlStrRef - // 2- text value denoted by v element parent is strRef - } else if (tnm.equals("formatCode")) { // part of numCache element - Xf.addFormatPattern(wbh.getWorkBook(), OOXMLAdapter.getNextText(xpp)); -// ******************************************* -// TODO: add to y pattern *** -// ******************************************* - } else if (tnm.equals("f")) { // range element -- legend cell, Cat range, Value range, Bubble data reference - ranges[idx] = OOXMLAdapter.getNextText(xpp); - } else if (tnm.equals("v")) { // value or text of series or category (parent=tx) - if (idx == 0) // legend text - legendText = OOXMLAdapter.getNextText(xpp); // legend text; possible to have legend text without a legend cell range (ranges[1]) - else if (idx == -1 || ranges[idx].equals("")) { // shoudln't!! can't have a textual refernce in place of a series or cat value (can you?) - Logger.logWarn("ChartSeries.parseOOXML: unexpected text value"); - } - } else if (tnm.equals("numCache") || tnm.equals("strCache") || tnm.equals("multiLvlStrRef")) { // parent= cat or vals (series values) - cache = tnm; - } else if (tnm.equals("ptCount")) { // parent= numCache or strCache (governs either f element) - if (hasPivotTableSource) { // OK, if have a pivot table source then the range referenced in f is only a SUBSET - // unclear if at any other time the range referenced is a subset ... [NOTE: in testing, only pivot charts hit] - // another assumption: assume that range is only TRUNCATED -- in testing, true - int npoints = Integer.valueOf(xpp.getAttributeValue(0)).intValue(); - if (!ranges[idx].equals("") && ranges[idx].indexOf(",") == -1) { - try { - CellRange cells = new CellRange(ranges[idx], wbh, false, true); - if (cells.getCells().length != npoints) { //must adjust - int z = 0; - CellHandle[] clist = cells.getCells(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - tnm = xpp.getName(); - if (tnm.equals("pt")) { - // format code idx - } else if (tnm.equals("v")) { -/* this case should NOT happen - String s= OOXMLAdapter.getNextText(xpp); - if (z < clist.length) - if (!clist[z].getVal().toString().equals(s)) - Logger.logWarn("ChartSeries.parseOOXML: unexpected pivot value order- skipping"); - z++; -*/ - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(cache)) { - cache = null; - break; - } - } - eventType = xpp.next(); - } - // pivot charts: apparently always truncate/skip last cell in range (which represents the grand total) - if (npoints < clist.length) {// truncate! - int[] rc = cells.getRangeCoords(); - rc[0]--; - rc[2]--; // make 0-based - if (rc[0] == rc[2]) - rc[3] -= (clist.length - npoints); - else - rc[2] -= (clist.length - npoints); -// KSC: TESTING: REMOVE WHEN DONE -//io.starter.toolkit.Logger.log("Truncate list: old range: " + ranges[idx] + " new range: " + cells.getSheet().getQualifiedSheetName() + "!" + ExcelTools.formatLocation(rc)); - ranges[idx] = cells.getSheet().getQualifiedSheetName() + "!" + ExcelTools.formatLocation(rc); - } - - continue; // don't hit xpp.next() below - } - } catch (Exception e) { - Logger.logErr("ChartSeries.parseOOXML: Error adjusting pivot range for " + parentChart + ":" + e.toString()); - } // problems parsing range - skp - } - } - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals("ser")) { - lastTag.pop(); - // must only have 1 for pie-type charts (pie, of pie - bar of, pie of) - Series s = parentChart.getParentChart().getChartSeries().addSeries(ranges[2], ranges[1], ranges[3], ranges[0], legendText, parentChart, parentChart.getParentChart().getChartOrder(parentChart)); - if (sp != null) - s.setSpPr(sp); - // TODO: " When you create a chart, by default - the first six series are the six accent colors in order - but not the exact color or any variation that appears in the palette. They're typically (unless the primary accent color being modified is extremely dark) a bit darker than the primary accent color. Chart series 7 - 12 use the actual primary accent colors 1 through 6 ... and then chart series 13 starts a set of lighter variations of the six accent colors that are also slightly different from any position in the palette." - else if (seriesidx < 7) // TODO: figure out where to get colors past 6 - if (seriesidx > 0 && parentChart instanceof PieChart) - Logger.logWarn("ChartSeries.parseOOXML: more than 1 series encountered for a Pie-style chart"); - else - s.setColor(wbh.getWorkBook().getTheme().genericThemeClrs[seriesidx + 4]); // series colors start at 4 - if (d != null) - s.setDLbls(d); - if (dpts != null) { - for (int z = 0; z < dpts.size(); z++) - s.addDpt((DPt) dpts.get(z)); - } - if (m != null) - s.setMarker(m); - if (smooth) - s.setHasSmoothLines(smooth); - return s; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ChartSeries.parseOOXML: Error parsing series for " + parentChart + ":" + e.toString()); - } - return null; - } - - /** - * Generate the OOXML used to represent all series for this chart type - * @param ct chart type - * @return - */ - /** - * all contain: - * idx (index) - * order (order) - * tx (series text) - * spPr (shape properties) - * then after, may contain: - * bubble: invertIfNegative, dPt, dLbls, trendline, errBars, xVal, yVal, bubbleSize, bubble3D (bubbleChart) - * line marker, dPt, dLbls, trendline, errBars, cat, val, smooth (line3DChart, lineChart, stockChart) - * pie: explosion, dPt,dLbls, cat, val (doughnutChart, ofPieChart, pie3DChart, pieChart) - * surface: cat, val (surfaceChart, surface3DChart) - * scatter: marker, dPt, dLbls, trendline, errBars, xVal, yVal, smooth (scatterChart) - * radar: marker, dPt, dLbls, cat, val (radarChart) - * area: pictureOptions, dPt, dLbls, trendline, errBars, cat, val (area3DChart, areaChart) - * bar: invertIfNegative, pictureOptions, dPt, dLbls, trendline, errBars, cat, val, shape (bar3DChart, barChart) - */ - // TODO: finish options - // TODO: refactor !!! - public void resetSeriesNumber() { - seriesNumber = 0; - } - - int seriesNumber = 0; - - public String getOOXML(int ct, boolean isBubble3d, int nChart) { - String catstr = (ct == SCATTERCHART || ct == BUBBLECHART) ? ("xVal") : ("cat"); - String valstr = (ct == SCATTERCHART || ct == BUBBLECHART) ? ("yVal") : ("val"); - StringBuffer ooxml = new StringBuffer(); - - Vector v = parentChart.getAllSeries(nChart); - int defaultDL = parentChart.getDataLabel(); - boolean from2003 = (!parentChart.getWorkBook().getIsExcel2007()); - String[] cats = this.getCategories(nChart); // do 1x - - - for (int i = 0; i < v.size(); i++) { - Series s = (Series) v.get(i); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - // Series Legend - ooxml.append(s.getLegendOOXML(from2003)); - // Options for current series - if (ct == PIECHART /*&& i==0*/) - ooxml.append(""); - if (s.getMarker() != null) ooxml.append(s.getMarker().getOOXML()); // only for Radar, Line or Scatter - if (s.getDPt() != null) { - DPt[] datapoints = s.getDPt(); - for (int z = 0; z < datapoints.length; z++) { - ooxml.append(datapoints[z].getOOXML()); - } - } - if (s.getDLbls() != null) - ooxml.append(s.getDLbls().getOOXML()); - else if (from2003) { - int dl = s.getDataLabel() | defaultDL; - if (dl > 0) { // todo: showLegendKey catpercent ? sppr + txpr - // TODO: spPr, txPr - DLbls dlbl = new DLbls(((dl | 0x1) == 0x1), ((dl | 0x08) == 0x08), false, ((dl | 0x10) == 0x10), ((dl | 0x40) == 0x40), - ((dl | 0x2) == 0x2), ((dl | 0x20) == 0x20), null /*sppr*/, null /*txpr*/); - ooxml.append(dlbl.getOOXML()); - } - } - if (s.getHasSmoothedLines()) { - ooxml.append(""); - ooxml.append("\r\n"); - } - - // Categories NOTE: Categories==xVals for Scatter charts, cat for all others - ooxml.append(s.getCatOOXML(cats[i], catstr)); - // Series ("vals") NOTE: Series==yVals for Scatter charts, val for all others - ooxml.append(s.getValOOXML(valstr)); // gets the numeric data reference to define the series (values) - - if (ct == BUBBLECHART) { // also include bubble sizes - ooxml.append(s.getBubbleOOXML(isBubble3d)); - } - ooxml.append(""); - ooxml.append("\r\n"); - } - return ooxml.toString(); - } - - - /** - * if has multiple or overlay charts, update series mappings - * - * @param sl - * @param thischartnumber - */ - protected void updateSeriesMappings(SeriesList sl, int thischartnumber) { - if (sl == null) - return; // NO seriesList record means no mapping - ArrayList seriesmappings = new ArrayList(); - for (int z = 0; z < series.size(); z++) { - int chartnumber = ((Integer) series.get(z)[1]).intValue(); - if (chartnumber == thischartnumber) // mappped to this chart - seriesmappings.add(Integer.valueOf(z + 1)); - } - int[] mappings = new int[seriesmappings.size()]; - for (int z = 0; z < seriesmappings.size(); z++) { - mappings[z] = ((Integer) seriesmappings.get(z)).intValue(); - } - try { - sl.setSeriesMappings(mappings); - } catch (Exception e) { - throw new WorkBookException("ChartSeries.updateSeriesMappings failed:" + e.toString(), WorkBookException.RUNTIME_ERROR); - } - } - - /** - * return Data Labels Per Series or default Data Labels, if no overrides specified - * - * @param defaultDL Default Data labels - * @param charttype Chart Type Int - * @return - */ - public int[] getDataLabelsPerSeries(int defaultDL, int charttype) { - if (charttype == PIECHART || charttype == DOUGHNUTCHART) { // handled differently - if (series.size() > 0) { - Series s = (Series) series.get(0)[0]; - int[] dls = s.getDataLabelsPIE(defaultDL); - if (dls == null) { - dls = new int[]{defaultDL}; - } - return dls; - } - } - int[] datalabels = new int[series.size()]; - for (int i = 0; i < series.size(); i++) { - Series s = (Series) series.get(i)[0]; - datalabels[i] = s.getDataLabel(); - datalabels[i] |= defaultDL; // if no per-series setting use overall chart setting - } - return datalabels; - } - - - // TODO: FINISH -- include cell range ... + overlay charts ...? - public String[] getLegends() { - if (legends == null) { - this.getMetrics(true); - } - return legends; - } - - - public Object[] getCategories() { - if (categories == null) - this.getMetrics(true); - return categories; - } - - public ArrayList getSeriesRanges() { - if (seriesranges == null) - this.getMetrics(true); - return seriesranges; - } - - public ArrayList getSeriesValues() { - if (seriesvalues == null) - this.getMetrics(true); - return seriesvalues; - } - - public String[] getSeriesBarColors() { - if (seriescolors == null) - this.getMetrics(true); - return seriescolors; - } - - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartSeries.kt b/src/main/java/io/starter/formats/XLS/charts/ChartSeries.kt new file mode 100644 index 0000000..1895313 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ChartSeries.kt @@ -0,0 +1,1013 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.CellHandle +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ExcelTools +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.DLbls +import io.starter.formats.OOXML.DPt +import io.starter.formats.OOXML.Marker +import io.starter.formats.OOXML.SpPr +import io.starter.formats.XLS.* +import io.starter.formats.XLS.formulas.Ptg +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.xmlpull.v1.XmlPullParser + +import java.io.Serializable +import java.util.ArrayList +import java.util.HashMap +import java.util.Stack +import java.util.Vector + +class ChartSeries : ChartConstants, Serializable { + private val series = ArrayList() // ALL series in chart MAPPED to chart that "owns" it + /** + * retrieve the current Series JSON for comparisons + * + * @return JSONArray + */ + /** + * set the current Series JSON + * + * @param s JSONArray + */ + var seriesJSON: JSONArray? = null + @Throws(JSONException::class) + set(s) { + field = JSONArray(s.toString()) + } // save series JSON for update comparisons later + protected var minmaxcache: DoubleArray? = null // stores minimum/maximum/minor/major for chart scale; cached; + var legends: Array? = null + protected var seriesranges: ArrayList<*>? = null + protected var categories: Array? = null + protected var seriesvalues: ArrayList<*>? = null + protected var seriescolors: Array? = null + protected var parentChart: Chart + + /** + * get all the series objects for ALL charts + * (i.e. even in overlay charts) + * + * @return + */ + val allSeries: Vector<*> + get() = getAllSeries(-1) + + /** + * retrieve the current data range JSON for comparisons + * + * @return JSONObject in form of: c:catgeory range, {v:series range, l:legendrange, b: bubble sizes} for each series + */ + //v=series val range, l=legend cell [,b= bubble sizes] + // keep going + // seriesJSON.getJSONArray("Series").length() + // seriesJSON.getJSONArray("Series").get(1) + // ((JSONObject)seriesJSON.getJSONArray("Series").get(1)).get("v") value range + // ((JSONObject)seriesJSON.getJSONArray("Series").get(1)).get("l") legend ref + // ((JSONObject)seriesJSON.getJSONArray("Series").get(1)).has("b") bubble sizes + // seriesJSON.get("c") category range + val dataRangeJSON: org.json.JSONObject + get() { + val seriesJSON = JSONObject() + val allseries = this.getAllSeries(-1) + try { + val series = JSONArray() + for (i in allseries.indices) { + try { + val thisseries = allseries[i] as Series + val serAi = thisseries.seriesValueAi + if (i == 0) { + val catAi = thisseries.categoryValueAi + seriesJSON.put("c", catAi!!.toString()) + } + val seriesvals = JSONObject() + seriesvals.put("v", serAi!!.toString()) + seriesvals.put("l", thisseries.legendRef) + if (thisseries.hasBubbleSizes()) + seriesvals.put("b", thisseries.bubbleValueAi!!.toString()) + series.put(seriesvals) + } catch (e: Exception) { + } + + } + seriesJSON.put("Series", series) + } catch (e: JSONException) { + Logger.logErr("ChartSeries.getDataRangeJSON: Error retrieving Series Information: $e") + } + + return seriesJSON + } + + /** + * return an array of ALL cell references of the chart + */ + val cellRangePtgs: Array + get() { + val locptgs = CompatibleVector() + for (i in series.indices) { + val s = series.get(i)[0] as Series + for (j in s.chartArr.indices) { + val br = s.chartArr[j] + if (br.opcode == XLSConstants.AI) { + try { + val ps = (br as Ai).cellRangePtgs + for (t in ps.indices) locptgs.add(ps[t]) + } catch (e: Exception) { + } + + } + } + } + val ret = arrayOfNulls(locptgs.size) + locptgs.toTypedArray() + return ret + } + + /** + * @return an HashMap of Series Range Ptgs mapped by Series representing all the series in the chart + */ + // we're done, move onto next + val seriesPtgs: HashMap<*, *> + get() { + val seriesPtgs = HashMap() + for (i in series.indices) { + val s = series.get(i)[0] as Series + for (j in s.chartArr.indices) { + val br = s.chartArr[j] + if (br.opcode == XLSConstants.AI) { + if ((br as Ai).type == Ai.TYPE_VALS) { + try { + val ps = br.cellRangePtgs + seriesPtgs.put(s, ps) + break + } catch (e: Exception) { + } + + } + } + } + } + return seriesPtgs + } + + /** + * return the type of markers for each series: + *

                      0 = no marker + *

                      1 = square + *

                      2 = diamond + *

                      3 = triangle + *

                      4 = X + *

                      5 = star + *

                      6 = Dow-Jones + *

                      7 = standard deviation + *

                      8 = circle + *

                      9 = plus sign + */ + val markerFormats: IntArray + get() { + val markers = IntArray(series.size) + for (i in series.indices) { + val s = series.get(i)[0] as Series + markers[i] = s.markerFormat + } + return markers + } + + internal var seriesNumber = 0 + + val seriesRanges: ArrayList<*>? + get() { + if (seriesranges == null) + this.getMetrics(true) + return seriesranges + } + + val seriesValues: ArrayList<*>? + get() { + if (seriesvalues == null) + this.getMetrics(true) + return seriesvalues + } + + val seriesBarColors: Array? + get() { + if (seriescolors == null) + this.getMetrics(true) + return seriescolors + } + // protected transient WorkBookHandle wbh; + + /** + * series stores new Object[] {Series Record, Integer.valueOf(nCharts)} + * + * @param o + */ + fun add(o: Array) { + series.add(o) + } + + fun setParentChart(c: Chart) { + parentChart = c + } + // public void setWorkBook(WorkBookHandle wbh) { this.wbh= wbh; } + + /** + * returns an array containing the and maximum values of Y (Value) axis, along with the + * maximum number of series values of each series (bar, line, etc.) ... + *

                      sets all series cache values: seriesvalues, seriesranges, seriescolors, legends and minmaxcache + * + *

                      Note: this will only reset cached values if isDirty + * + * @return double[] + */ + fun getMetrics(isDirty: Boolean): DoubleArray { + if (!isDirty && minmaxcache != null) return minmaxcache + // trap minimum, maximum + number of series + val co = parentChart.chartObject // default chart object TODO: overlay charts + seriesvalues = ArrayList() + seriesranges = ArrayList() + val sht = parentChart.sheet + val s = this.getAllSeries(-1) + // Category values ******************************************************************************* + if (s.size > 0) { + try { + var cr = CellRange((s[0] as Series).categoryValueAi!!.toString(), parentChart.wbh, true) + val ch = cr.getCells() + if (ch != null) { // found a template with a chart with series but no categories + categories = arrayOfNulls(ch.size) + for (j in ch.indices) { + try { + categories[j] = ch[j].getFormattedStringVal(true) + } catch (e: IllegalArgumentException) { // catch format exceptions + categories[j] = ch[j].stringVal + } + + } + } else if (s.size > 0) { + cr = CellRange((s[0] as Series).seriesValueAi!!.toString(), parentChart.wbh, true) + val sz = cr.getCells()!!.size + categories = arrayOfNulls(sz) + for (j in 0 until sz) + categories[j] = Integer.valueOf(j + 1)!!.toString() + } + } catch (e: Exception) { + Logger.logWarn("ChartSeries.getMinMax: $e") + } + + } + // Series colors, labels and values *************************************************************** + var yMax = 0.0 + var yMin = java.lang.Double.MAX_VALUE + var nseries = 0 + seriescolors = null + legends = null + val charttype = co.chartType + // obtain/store series colors, store series values and trap maximum and + // minimun values so can be used below for axis scale + /* + * A Scatter chart has two value axes, showing one set of numerical data along the x-axis and another along the y-axis. + * It combines these values into single data points and displays them in uneven intervals, or clusters + */ + if (charttype != ChartConstants.PIECHART && charttype != ChartConstants.DOUGHNUTCHART) { + seriescolors = arrayOfNulls(s.size) + legends = arrayOfNulls(s.size) + for (i in s.indices) { + val myseries = s[i] as Series + seriescolors[i] = myseries.seriesColor + legends[i] = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.legendText).toString() + val cr = CellRange(myseries.seriesValueAi!!.toString(), parentChart.wbh, true) + val ch = cr.getCells() + nseries = Math.max(nseries, ch!!.size) + val seriesvals: DoubleArray + val sranges: Array + // String[] series_strings; + if (!myseries.hasBubbleSizes()) { + seriesvals = DoubleArray(nseries) + sranges = arrayOfNulls(nseries) + } else { + seriesvals = DoubleArray(nseries * 2) + sranges = arrayOfNulls(nseries * 2) + } + // series_strings = new String[seriesvals.length]; + + for (j in ch.indices) { + try { + sranges[j] = ch[j].cellAddressWithSheet + seriesvals[j] = ch[j].doubleVal + if (java.lang.Double.isNaN(seriesvals[j])) + seriesvals[j] = 0.0 + yMax = Math.max(yMax, seriesvals[j]) + yMin = Math.min(yMin, seriesvals[j]) + } catch (n: NumberFormatException) { + } + + } + if (myseries.hasBubbleSizes()) { // append bubble sizes to series values ... see BubbleChart.getSVG for parsing + val z = ch.size + val crb = CellRange(myseries.bubbleValueAi!!.toString(), parentChart.wbh, true) + val chb = crb.getCells() + for (j in ch.indices) { + seriesvals[j + z] = chb!![j].doubleVal + sranges[j + z] = chb[j].cellAddressWithSheet + } + } + seriesvalues!!.add(seriesvals) // trap and add series value points + seriesranges!!.add(sranges) // trap series range + } + } else if (charttype == ChartConstants.DOUGHNUTCHART && s.size > 1) { // like a PIE chart but can have multiple series + legends = arrayOfNulls(categories!!.size) // for PIE/DONUT charts, legends are actually category labels, not series labels + for (i in categories!!.indices) + legends[i] = io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(categories!![i].toString()).toString() + for (i in s.indices) { + val myseries = s[i] as Series + // legends[i]= + // io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegend()); + val cr = CellRange(myseries.seriesValueAi!!.toString(), parentChart.wbh, true) + val ch = cr.getCells() + val seriesvals = DoubleArray(ch!!.size) + val sranges = arrayOfNulls(ch.size) + if (seriescolors == null) + seriescolors = arrayOfNulls(ch.size) + for (j in ch.indices) { + try { + seriesvals[j] = ch[j].doubleVal + if (ch[j].workSheetHandle!!.mysheet == sht) + sranges[j] = ch[j].cellAddress + yMax = Math.max(yMax, seriesvals[j]) + yMin = Math.min(yMin, seriesvals[j]) + if (i == 0) { // only do for 1st series; will be the + // same for rest + seriescolors[j] = myseries.getPieSliceColor(j) + /*if (seriescolors[j] == 0x4D + || seriescolors[j] == 0x4E) + seriescolors[j] = io.starter.formats.XLS.FormatConstants.COLOR_WHITE;*/ + } + + } catch (n: NumberFormatException) { + } + + } + seriesvalues!!.add(seriesvals) // trap and add series value points + seriesranges!!.add(sranges) // trap series range + } + } else { // PIES - only 1 series + if (s.size > 0) { + // PIE: 1 series data + val cats = CellRange((s[0] as Series).categoryValueAi!!.toString(), parentChart.wbh, true).getCells() + if (cats != null) { + nseries = cats.size + legends = arrayOfNulls(cats.size) // for PIE charts, legends are actually category labels, not series labels + for (i in cats.indices) + legends[i] = cats[i].getFormattedStringVal(true) + } + seriescolors = arrayOfNulls(nseries) + val myseries = s[0] as Series + try { + val cr = CellRange(myseries.seriesValueAi!!.toString(), parentChart.wbh, true) + val ch = cr.getCells() + // error trap - shouldn't happen + if (ch!!.size != nseries) { + Logger.logWarn("ChartHandle.getSeriesInfo: unexpected Pie Chart structure") + nseries = Math.min(nseries, ch.size) + } + val seriesvals = DoubleArray(nseries) + val sranges = arrayOfNulls(nseries) + for (i in 0 until nseries) { + seriescolors[i] = myseries.getPieSliceColor(i) + /*if (seriescolors[i] == 0x4D || seriescolors[i] == 0x4E) + seriescolors[i] = io.starter.formats.XLS.FormatConstants.COLOR_WHITE;*/ + // legends[i]= + // io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(myseries.getLegend()); + // // same for every series ... + seriesvals[i] = ch[i].doubleVal + if (ch[i].workSheetHandle!!.mysheet == sht) + sranges[i] = ch[i].cellAddress + yMax = Math.max(yMax, seriesvals[i]) + yMin = Math.min(yMin, seriesvals[i]) + } + seriesvalues!!.add(seriesvals) // trap and add series value points + seriesranges!!.add(sranges) // trap series range + } catch (e: IllegalArgumentException) { + // error in cell range sheet ... + } + + } + } + // For stacked-type charts, must sum values for ymax + if (co.isStacked) { + // scale is SUM of values, yMax is maximum total per series point + val sum = DoubleArray(nseries) + for (i in seriesvalues!!.indices) { + val seriesv = seriesvalues!![i] as DoubleArray + for (j in seriesv.indices) { + sum[j] = sum[j] + seriesv[j] + } + } + yMax = 0.0 + for (i in 0 until nseries) { + yMax = Math.max(sum[i], yMax) + } + } + minmaxcache = DoubleArray(2) + minmaxcache[0] = yMin + minmaxcache[1] = yMax + // minmaxcache[2]= nSeries; + // minmaxcache= new double[3][]; // 3 possible axes: x, y and z + // minmaxcache[axisType]= minMax; + return minmaxcache + } + + /** + * Change series ranges for ALL matching series + * + * @param originalrange + * @param newrange + * @return + */ + fun changeSeriesRange(originalrange: String, newrange: String): Boolean { + var changed = false + for (i in series.indices) { + val s = series.get(i)[0] as Series + val ai = s.seriesValueAi + if (ai != null) { + if (ai.toString().equals(originalrange, ignoreCase = true)) { + changed = ai.changeAiLocation(originalrange, newrange) + } + } + } + + return changed + } + + /** + * Return a string representing all series in this chart + * + * @param nChart 0=default, 1-9= overlay charts -1 for ALL charts + * @return + */ + fun getSeries(nChart: Int): Array { + val seriesperchart = this.getAllSeries(nChart) + val retStr = arrayOfNulls(seriesperchart.size) + for (i in seriesperchart.indices) { + val s = seriesperchart[i] as Series + val a = s.seriesValueAi + retStr[i] = a!!.toString() + } + return retStr + } + + /** + * Return an array of strings, one for each category + * + * @param nChart 0=default, 1-9= overlay charts -1 for All + * @return + */ + fun getCategories(nChart: Int): Array { + val seriesperchart = this.getAllSeries(nChart) + val retStr = arrayOfNulls(seriesperchart.size) + for (i in seriesperchart.indices) { + val s = seriesperchart[i] as Series + val a = s.categoryValueAi + retStr[i] = a!!.toString() + } + return retStr + } + + /** + * get all the series objects in the specified chart (-1 for ALL) + * + * @param nChart 0=default, 1-9= overlay charts -1 for ALL series + * @return + */ + fun getAllSeries(nChart: Int): Vector<*> { + /* if (nChart==-1) { //return all series + return new Vector(series.keySet()); // unordered!!! + } +*/ + val retVec = Vector() + for (i in series.indices) { + val chart = series.get(i)[1] as Int + if (nChart == -1 || nChart == chart) { + val s = series.get(i)[0] as Series + retVec.add(s) + } + } + return retVec + } + + /** + * Add a series object to the array. + * + * @param seriesRange = one row range, expressed as (Sheet1!A1:A12); + * @param categoryRange = category range + * @param bubbleRange= bubble range, if any 20070731 KSC + * @param seriesText = label for the series; + * @param nChart 0=default, 1-9= overlay charts + * s * @return + */ + fun addSeries(seriesRange: String, categoryRange: String, bubbleRange: String, legendRange: String, legendText: String, chartObject: ChartType, nChart: Int): Series { + val s = Series.getPrototype(seriesRange, categoryRange, bubbleRange, legendRange, legendText, chartObject) + s.parentChart = chartObject.parentChart + s.shape = chartObject.barShape + series.add(arrayOf(s, Integer.valueOf(nChart))) + // Update parent chartArr + val chartArr = s.parentChart!!.chartArr + for (i in chartArr.indices) { + val br = chartArr[i] + var br2: BiffRec? = null + if (i < chartArr.size - 1) br2 = chartArr[i + 1] + if (br != null && (br.opcode == XLSConstants.SERIES && br2!!.opcode != XLSConstants.SERIES || br.opcode == XLSConstants.FRAME && br2!!.opcode == XLSConstants.SHTPROPS)) { + chartArr.add(i + 1, s) + break + } + } + if (chartObject.chartType == ChartConstants.STOCKCHART) + s.setHasLines(5) + return s + } + + /** + * specialty method to take absolute index of series and remove it + *

                      only used in WorkSheetHandle + * + * @param index + */ + fun removeSeries(index: Int) { + val v = this.allSeries + this.removeSeries(v[index] as Series) + } + + /** + * remove desired series from chart + * + * @param index + */ + fun removeSeries(seriestodelete: Series) { + for (z in series.indices) { + val ss = series.get(z)[0] as Series + if (ss == seriestodelete) { + series.removeAt(z) + break + } + } + } + + /** + * get a chart series handle based off the series name + * + * @param seriesName + * @param nChart 0=default, 1-9= overlay charts + * @return + */ + fun getSeries(seriesName: String, nChart: Int): Series? { + val seriesperchart = this.getAllSeries(nChart) + for (i in seriesperchart.indices) { + val s = seriesperchart[i] as Series + if (s.legendText.equals(seriesName, ignoreCase = true)) return s + } + return null + } + + /** + * changes the category range which matches originalrange to new range + * + * @param originalrange + * @param newrange + * @return + */ + fun changeCategoryRange(originalrange: String, newrange: String): Boolean { + var changed = false + for (i in series.indices) { + val s = series.get(i)[0] as Series + val ai = s.categoryValueAi + if (ai!!.toString().equals(originalrange, ignoreCase = true)) { + changed = ai.changeAiLocation(originalrange, newrange) + } + } + return changed + } + + /** + * attempts to replace all category elements containing originalval text to newval + * + * @param originalval + * @param newval + * @return + */ + fun changeTextValue(originalval: String, newval: String): Boolean { + val changed = false + for (i in series.indices) { + val s = series.get(i)[0] as Series + val ai = s.categoryValueAi + if (ai!!.text == originalval) { + ai.text = newval + } + + } + return changed + } + + + /** + * for overlay charts, store series list + * + * @param nCharts + * @param seriesList + */ + fun addSeriesMapping(nCharts: Int, seriesList: IntArray) { + for (i in seriesList.indices) { + try { + val idx = seriesList[i] - 1 + // ALL series in chart MAPPED to chart that "owns" it + val s = series.get(idx)[0] as Series + series.add(idx, arrayOf(s, Integer.valueOf(nCharts))) + } catch (ae: ArrayIndexOutOfBoundsException) { + // happens -- are they deleted series????? + } + + } + } + + /** + * return an array of legend text + * + * @param nChart 0=default, 1-9= overlay charts -1 for ALL + */ + fun getLegends(nChart: Int): Array { + val seriesperchart = this.getAllSeries(nChart) + val ret = arrayOfNulls(seriesperchart.size) + for (i in seriesperchart.indices) { + ret[i] = (seriesperchart[i] as Series).legendText + } + return ret + } + + /** + * Generate the OOXML used to represent all series for this chart type + * @param ct chart type + * @return + */ + /** + * all contain: + * idx (index) + * order (order) + * tx (series text) + * spPr (shape properties) + * then after, may contain: + * bubble: invertIfNegative, dPt, dLbls, trendline, errBars, xVal, yVal, bubbleSize, bubble3D (bubbleChart) + * line marker, dPt, dLbls, trendline, errBars, cat, val, smooth (line3DChart, lineChart, stockChart) + * pie: explosion, dPt,dLbls, cat, val (doughnutChart, ofPieChart, pie3DChart, pieChart) + * surface: cat, val (surfaceChart, surface3DChart) + * scatter: marker, dPt, dLbls, trendline, errBars, xVal, yVal, smooth (scatterChart) + * radar: marker, dPt, dLbls, cat, val (radarChart) + * area: pictureOptions, dPt, dLbls, trendline, errBars, cat, val (area3DChart, areaChart) + * bar: invertIfNegative, pictureOptions, dPt, dLbls, trendline, errBars, cat, val, shape (bar3DChart, barChart) + */ + // TODO: finish options + // TODO: refactor !!! + fun resetSeriesNumber() { + seriesNumber = 0 + } + + fun getOOXML(ct: Int, isBubble3d: Boolean, nChart: Int): String { + val catstr = if (ct == ChartConstants.SCATTERCHART || ct == ChartConstants.BUBBLECHART) "xVal" else "cat" + val valstr = if (ct == ChartConstants.SCATTERCHART || ct == ChartConstants.BUBBLECHART) "yVal" else "val" + val ooxml = StringBuffer() + + val v = parentChart.getAllSeries(nChart) + val defaultDL = parentChart.dataLabel + val from2003 = !parentChart.workBook!!.isExcel2007 + val cats = this.getCategories(nChart) // do 1x + + + for (i in v.indices) { + val s = v[i] as Series + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + // Series Legend + ooxml.append(s.getLegendOOXML(from2003)) + // Options for current series + if (ct == ChartConstants.PIECHART /*&& i==0*/) + ooxml.append("") + if (s.marker != null) ooxml.append(s.marker!!.ooxml) // only for Radar, Line or Scatter + if (s.dPt != null) { + val datapoints = s.dPt + for (z in datapoints!!.indices) { + ooxml.append(datapoints[z].ooxml) + } + } + if (s.dLbls != null) + ooxml.append(s.dLbls!!.ooxml) + else if (from2003) { + val dl = s.dataLabel or defaultDL + if (dl > 0) { // todo: showLegendKey catpercent ? sppr + txpr + // TODO: spPr, txPr + val dlbl = DLbls(dl or 0x1 == 0x1, dl or 0x08 == 0x08, false, dl or 0x10 == 0x10, dl or 0x40 == 0x40, + dl or 0x2 == 0x2, dl or 0x20 == 0x20, null, null/*txpr*/)/*sppr*/ + ooxml.append(dlbl.ooxml) + } + } + if (s.hasSmoothedLines) { + ooxml.append("") + ooxml.append("\r\n") + } + + // Categories NOTE: Categories==xVals for Scatter charts, cat for all others + ooxml.append(s.getCatOOXML(cats[i], catstr)) + // Series ("vals") NOTE: Series==yVals for Scatter charts, val for all others + ooxml.append(s.getValOOXML(valstr)) // gets the numeric data reference to define the series (values) + + if (ct == ChartConstants.BUBBLECHART) { // also include bubble sizes + ooxml.append(s.getBubbleOOXML(isBubble3d)) + } + ooxml.append("") + ooxml.append("\r\n") + } + return ooxml.toString() + } + + + /** + * if has multiple or overlay charts, update series mappings + * + * @param sl + * @param thischartnumber + */ + fun updateSeriesMappings(sl: SeriesList?, thischartnumber: Int) { + if (sl == null) + return // NO seriesList record means no mapping + val seriesmappings = ArrayList() + for (z in series.indices) { + val chartnumber = (series.get(z)[1] as Int).toInt() + if (chartnumber == thischartnumber) + // mappped to this chart + seriesmappings.add(Integer.valueOf(z + 1)) + } + val mappings = IntArray(seriesmappings.size) + for (z in seriesmappings.indices) { + mappings[z] = (seriesmappings.get(z) as Int).toInt() + } + try { + sl.seriesMappings = mappings + } catch (e: Exception) { + throw WorkBookException("ChartSeries.updateSeriesMappings failed:$e", WorkBookException.RUNTIME_ERROR) + } + + } + + /** + * return Data Labels Per Series or default Data Labels, if no overrides specified + * + * @param defaultDL Default Data labels + * @param charttype Chart Type Int + * @return + */ + fun getDataLabelsPerSeries(defaultDL: Int, charttype: Int): IntArray { + if (charttype == ChartConstants.PIECHART || charttype == ChartConstants.DOUGHNUTCHART) { // handled differently + if (series.size > 0) { + val s = series.get(0)[0] as Series + var dls = s.getDataLabelsPIE(defaultDL) + if (dls == null) { + dls = intArrayOf(defaultDL) + } + return dls + } + } + val datalabels = IntArray(series.size) + for (i in series.indices) { + val s = series.get(i)[0] as Series + datalabels[i] = s.dataLabel + datalabels[i] = datalabels[i] or defaultDL // if no per-series setting use overall chart setting + } + return datalabels + } + + + // TODO: FINISH -- include cell range ... + overlay charts ...? + fun getLegends(): Array? { + if (legends == null) { + this.getMetrics(true) + } + return legends + } + + + fun getCategories(): Array? { + if (categories == null) + this.getMetrics(true) + return categories + } + + companion object { + /** + * serialVersionUID + */ + private const val serialVersionUID = -7862828186455339066L + + + /** + * parse a chartSpace->chartType->ser element into our Series record/structure + * + * @param xpp XML pullparser positioned at ser element + * @param wbh WorkBookHandle + * @param parentChart parent chart object + * @param lastTag + * @return + */ + fun parseOOXML(xpp: XmlPullParser, wbh: WorkBookHandle, parentChart: ChartType, hasPivotTableSource: Boolean, lastTag: Stack): Series? { + try { + var eventType = xpp.eventType + var idx = 0 + val seriesidx = parentChart.parentChart!!.allSeries.size + val ranges = arrayOf("", "", "", "") //legend, cat, ser/value, bubble cell references + var legendText: String? = "" + var sp: SpPr? = null + var d: DLbls? = null + var m: Marker? = null + var smooth = false + var dpts: ArrayList<*>? = null + var cache: String? = null + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + var tnm = xpp.name + if (tnm == "ser") { // Represents a single field in the PivotTable. This complex type contains + idx = 0 + } else if (tnm == "order") {// attr val= order + } else if (tnm == "cat" || tnm == "xVal") { // children: CHOICE OF: multiLvlStrRef, numLiteral, numRef, strLit, strRef + idx = 1 + } else if (tnm == "val" || tnm == "yVal") { // children: CHOICE OF: numLit, numRef + idx = 2 + } else if (tnm == "dLbls") { // data labels + lastTag.push(tnm) // keep track of element hierarchy + d = DLbls.parseOOXML(xpp, lastTag, wbh).cloneElement() as DLbls + if (d.showBubbleSize()) parentChart.setChartOption("ShowBubbleSizes", "1") + if (d.showCatName()) parentChart.setChartOption("ShowCatLabel", "1") + if (d.showLeaderLines()) parentChart.setChartOption("ShowLdrLines", "1") + if (d.showLegendKey()) + ; // TODO: handle show legend key + if (d.showPercent()) parentChart.setChartOption("ShowLabelPct", "1") + if (d.showSerName()) parentChart.setChartOption("ShowLabel", "1") + if (d.showVal()) parentChart.setChartOption("ShowValueLabel", "1") + // data label options + } else if (tnm == "dPt") { // data point(s) + if (dpts == null) dpts = ArrayList() + lastTag.push(tnm) // keep track of element hierarchy + dpts!!.add(DPt.parseOOXML(xpp, lastTag, wbh).cloneElement()) + } else if (tnm == "spPr") { // series spPr + lastTag.push(tnm) // keep track of element hierarchy + sp = SpPr.parseOOXML(xpp, lastTag, wbh).cloneElement() as SpPr + } else if (tnm == "marker") { + lastTag.push(tnm) // keep track of element hierarchy + m = Marker.parseOOXML(xpp, lastTag, wbh).cloneElement() as Marker + } else if (tnm == "bubbleSize") { + idx = 3 + } else if (tnm == "shape") { // bar only + parentChart.convertShape(xpp.getAttributeValue(0)) + } else if (tnm == "smooth") { // line chart + smooth = xpp.attributeCount == 0 || xpp.getAttributeValue(0) != "0" + } else if (tnm == "explosion") { + val v = xpp.getAttributeValue(0) + parentChart.setChartOption("Percentage", v) + // NOTE: two types of values; 1- reference denoted by f element parents can be numRef, strRef or multiLvlStrRef + // 2- text value denoted by v element parent is strRef + } else if (tnm == "formatCode") { // part of numCache element + Xf.addFormatPattern(wbh.workBook!!, OOXMLAdapter.getNextText(xpp)) + // ******************************************* + // TODO: add to y pattern *** + // ******************************************* + } else if (tnm == "f") { // range element -- legend cell, Cat range, Value range, Bubble data reference + ranges[idx] = OOXMLAdapter.getNextText(xpp) + } else if (tnm == "v") { // value or text of series or category (parent=tx) + if (idx == 0) + // legend text + legendText = OOXMLAdapter.getNextText(xpp) // legend text; possible to have legend text without a legend cell range (ranges[1]) + else if (idx == -1 || ranges[idx] == "") { // shoudln't!! can't have a textual refernce in place of a series or cat value (can you?) + Logger.logWarn("ChartSeries.parseOOXML: unexpected text value") + } + } else if (tnm == "numCache" || tnm == "strCache" || tnm == "multiLvlStrRef") { // parent= cat or vals (series values) + cache = tnm + } else if (tnm == "ptCount") { // parent= numCache or strCache (governs either f element) + if (hasPivotTableSource) { // OK, if have a pivot table source then the range referenced in f is only a SUBSET + // unclear if at any other time the range referenced is a subset ... [NOTE: in testing, only pivot charts hit] + // another assumption: assume that range is only TRUNCATED -- in testing, true + val npoints = Integer.valueOf(xpp.getAttributeValue(0)).toInt() + if (ranges[idx] != "" && ranges[idx].indexOf(",") == -1) { + try { + val cells = CellRange(ranges[idx], wbh, false, true) + if (cells.getCells()!!.size != npoints) { //must adjust + val z = 0 + val clist = cells.getCells() + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + tnm = xpp.name + if (tnm == "pt") { + // format code idx + } else if (tnm == "v") { + /* this case should NOT happen + String s= OOXMLAdapter.getNextText(xpp); + if (z < clist.length) + if (!clist[z].getVal().toString().equals(s)) + Logger.logWarn("ChartSeries.parseOOXML: unexpected pivot value order- skipping"); + z++; +*/ + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == cache) { + cache = null + break + } + } + eventType = xpp.next() + } + // pivot charts: apparently always truncate/skip last cell in range (which represents the grand total) + if (npoints < clist!!.size) {// truncate! + val rc = cells.rangeCoords + rc[0]-- + rc[2]-- // make 0-based + if (rc[0] == rc[2]) + rc[3] -= clist.size - npoints + else + rc[2] -= clist.size - npoints + // KSC: TESTING: REMOVE WHEN DONE + //io.starter.toolkit.Logger.log("Truncate list: old range: " + ranges[idx] + " new range: " + cells.getSheet().getQualifiedSheetName() + "!" + ExcelTools.formatLocation(rc)); + ranges[idx] = cells.sheet!!.qualifiedSheetName + "!" + ExcelTools.formatLocation(rc) + } + + continue // don't hit xpp.next() below + } + } catch (e: Exception) { + Logger.logErr("ChartSeries.parseOOXML: Error adjusting pivot range for $parentChart:$e") + } + // problems parsing range - skp + } + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == "ser") { + lastTag.pop() + // must only have 1 for pie-type charts (pie, of pie - bar of, pie of) + val s = parentChart.parentChart!!.chartSeries.addSeries(ranges[2], ranges[1], ranges[3], ranges[0], legendText, parentChart, parentChart.parentChart!!.getChartOrder(parentChart)) + if (sp != null) + s.spPr = sp + else if (seriesidx < 7) + // TODO: figure out where to get colors past 6 + if (seriesidx > 0 && parentChart is PieChart) + Logger.logWarn("ChartSeries.parseOOXML: more than 1 series encountered for a Pie-style chart") + else + s.setColor(wbh.workBook!!.theme!!.genericThemeClrs[seriesidx + 4])// TODO: " When you create a chart, by default - the first six series are the six accent colors in order - but not the exact color or any variation that appears in the palette. They're typically (unless the primary accent color being modified is extremely dark) a bit darker than the primary accent color. Chart series 7 - 12 use the actual primary accent colors 1 through 6 ... and then chart series 13 starts a set of lighter variations of the six accent colors that are also slightly different from any position in the palette." + // series colors start at 4 + if (d != null) + s.dLbls = d + if (dpts != null) { + for (z in dpts.indices) + s.addDpt(dpts[z] as DPt) + } + if (m != null) + s.marker = m + if (smooth) + s.setHasSmoothLines(smooth) + return s + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("ChartSeries.parseOOXML: Error parsing series for $parentChart:$e") + } + + return null + } + } + + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartType.java b/src/main/java/io/starter/formats/XLS/charts/ChartType.java deleted file mode 100644 index 10f1609..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ChartType.java +++ /dev/null @@ -1,1485 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartHandle; -import io.starter.OpenXLS.ChartHandle.ChartOptions; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.OOXMLConstants; -import io.starter.formats.XLS.*; -import io.starter.formats.cellformat.CellFormatFactory; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; - -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; - -/** - * This abstract class defines a Chart Group for a BIFF Chart. An Excel Chart may have up to 4 (2003 and previous versions) or 9 (post-2003) charts within one chart. - * The 0th chart type in the Chart Group is the default chart. - *
                      - * NOTES: - *
                      - * CRT = ChartFormat Begin (Bar / Line / (BopPop [BopPopCustom]) / Pie / Area / Scatter / Radar / RadarArea / Surf) CrtLink [SeriesList] [Chart3d] [LD] [2DROPBAR] *4(CrtLine LineFormat) *2DFTTEXT [DataLabExtContents] [SS] *4SHAPEPROPS End - *

                      - * MUST CHANGE ChartObject if change chart type - * MUST ADD/REMOVE ChartObject if add/remove multiple charts - * axisparent --> when add a new chart a new crt is added axes + titles/labels, number format, etc - *

                      - * REFACTOR: TO FINISH - * showDataTable -- Finish, TODO - * NOTE: The SeriesList record specifies the series of the chart. This record MUST NOT exist in the first chart group in the chart sheet substream. - * This record MUST exist when not in the first chart group in the chart sheet substream - *

                      - * NOTE: - * The Chart3d record specifies that the plot area, axis group, and chart group are rendered in a 3-D scene, rather than a 2-D scene, and specifies properties of the 3-D scene. If this record exists in the chart sheet substream, the chart sheet substream MUST have exactly one chart group. This record MUST NOT exist in a bar of pie, bubble, doughnut, filled radar, pie of pie, radar, or scatter chart group. - *

                      - * NOTE: legends only in 1st chart group - */ -public abstract class ChartType implements ChartConstants, Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7862828186455339066L; - protected GenericChartObject chartobj; - protected Legend legend = null; - protected ChartFormat cf = null; - // protected ChartSeries chartseries= new ChartSeries(); - protected transient WorkBook wb = null; - protected int defaultShape = 0; // controls default bar shape for all bars in the chart; used when adding or setting series - - public ChartType() { - } - - public ChartType(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - chartobj = charttype; // record that defines the chart type (Bar, Area, Line ...) - this.wb = wb; - this.cf = cf; - } - - public Chart getParentChart() { - return chartobj.getParentChart(); - } - - /** - * creates a new chart type object of the desired chart type. Will set options if already set via ChartFormat - * - * @param chartType - * @param parentChart - * @param cf - * @return - */ - public static ChartType create(int chartType, Chart parentChart, ChartFormat cf) { - GenericChartObject co = ChartType.createUnderlyingChartObject(chartType, parentChart, cf); //, ((ChartType)chartobj.get(0))); - - ChartType ct = ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); - return ct; - } - - - /** - * create and return the appropriate ChartType for the given - * chart record (Bar, Radar, Line, etc) - * - * @param ch GenericChartObject representing the Chart Type - *
                      Must be one of: Area, Bar, Line, Surface, Pie, Radar, Scatter, BopPop, RadarArea - * @return ChartType chart object - */ - public static ChartType createChartTypeObject(GenericChartObject ch, ChartFormat cf, WorkBook wb) { - if (cf == null) // TODO: throw exception - return null; - - int barshape = cf.getBarShape(); - boolean threeD = cf.isThreeD(ch.chartType); - boolean isStacked = ch.isStacked(); - boolean is100Percent = ch.is100Percent(); - - switch (ch.chartType) { - case ChartConstants.COLCHART: - if (barshape == SHAPEDEFAULT) { // regular column - if (isStacked || is100Percent) - return new StackedColumn(ch, cf, wb); - if (threeD) - return new Col3DChart(ch, cf, wb); - return new ColChart(ch, cf, wb); - } else if (barshape == SHAPECONE) { // Cone chart always 3d - return new ConeChart(ch, cf, wb); - } else if (barshape == SHAPECYLINDER) { // Cylinder chart always 3d - return new CylinderChart(ch, cf, wb); - } else if (barshape == SHAPEPYRAMID) { // Pyramid chart alwasy 3d - return new PyramidChart(ch, cf, wb); - } - case ChartConstants.BARCHART: - if (barshape == SHAPEDEFAULT) { // regular Bar - if (threeD) - return new Bar3DChart(ch, cf, wb); - return new BarChart(ch, cf, wb); - } else if (barshape == SHAPECONE) { // ConeBarchart always 3d - return new ConeBarChart(ch, cf, wb); - } else if (barshape == SHAPECYLINDER) { // CylinderBar chart always 3d - return new CylinderBarChart(ch, cf, wb); - } else if (barshape == SHAPEPYRAMID) { // PyramidBar chart always 3d - return new PyramidBarChart(ch, cf, wb); - } - case ChartConstants.LINECHART: - if (threeD) - return new Line3DChart(ch, cf, wb); - return new LineChart(ch, cf, wb); - case ChartConstants.STOCKCHART: - return new StockChart(ch, cf, wb); - case ChartConstants.PIECHART: - cf.setPercentage(0); - if (threeD) - return new Pie3dChart(ch, cf, wb); - return new PieChart(ch, cf, wb); - case ChartConstants.AREACHART: - if (ch.isStacked()) - return new StackedAreaChart(ch, cf, wb); - if (threeD) - return new Area3DChart(ch, cf, wb); - return new AreaChart(ch, cf, wb); - case ChartConstants.SCATTERCHART: - return new ScatterChart(ch, cf, wb); - case ChartConstants.RADARCHART: - return new RadarChart(ch, cf, wb); - case ChartConstants.SURFACECHART: - if (((Surface) ch).getIs3d()) - return new Surface3DChart(ch, cf, wb); - return new SurfaceChart(ch, cf, wb); - case ChartConstants.DOUGHNUTCHART: - cf.setPercentage(0); - return new DoughnutChart(ch, cf, wb); - case ChartConstants.BUBBLECHART: - return new BubbleChart(ch, cf, wb); - case ChartConstants.RADARAREACHART: - return new RadarAreaChart(ch, cf, wb); - case ChartConstants.OFPIECHART: - cf.setPercentage(0); - return new OfPieChart(ch, cf, wb); - default: - return null; - } - } - - /** - * create a new low-level chart object which determines the ChartTypeObject, and update the existing chartobj - * - * @param chartType - * @param parentchart - * @param chartgroup - * @return - */ - public static GenericChartObject createUnderlyingChartObject(int chartType, Chart parentchart, ChartFormat cf) {//, ChartType chartobj) { - GenericChartObject c = null; - switch (chartType) { - case ChartConstants.COLCHART: // column-type - Bar col = (Bar) Bar.getPrototype(); - col.setAsColumnChart(); - c = col; - break; - - case ChartConstants.BARCHART: // bar-type - Bar bar = (Bar) Bar.getPrototype(); - bar.setAsBarChart(); - c = bar; - break; - - case ChartConstants.PIECHART: // Pie - Pie p = (Pie) Pie.getPrototype(); - p.setAsPieChart(); - c = p; - break; - - case ChartConstants.STOCKCHART: - Line st = (Line) Line.getPrototype(); - st.setAsStockChart(); - c = st; - break; - - case ChartConstants.LINECHART: - Line l = (Line) Line.getPrototype(); - c = l; - break; - - case ChartConstants.AREACHART: - Area a = (Area) Area.getPrototype(); - c = a; - break; - - case ChartConstants.SCATTERCHART: - Scatter s = (Scatter) Scatter.getPrototype(); - s.setAsScatterChart(); - c = s; - break; - - case ChartConstants.RADARCHART: - Radar r = (Radar) Radar.getPrototype(); - c = r; - break; - - case ChartConstants.SURFACECHART: - Surface su = (Surface) Surface.getPrototype(); - c = su; - break; - - case ChartConstants.DOUGHNUTCHART: - Pie d = (Pie) Pie.getPrototype(); - d.setAsDoughnutChart(); - c = d; - break; - - case ChartConstants.BUBBLECHART: - Scatter bu = (Scatter) Scatter.getPrototype(); - bu.setAsBubbleChart(); - c = bu; - break; - - case ChartConstants.RADARAREACHART: - RadarArea ra = (RadarArea) RadarArea.getPrototype(); - c = ra; - break; - - // note that, for the below chart types, the underlying type will be either COL or BAR - // which actual chart is determined also by the bar shape - case ChartConstants.PYRAMIDCHART: - Bar pyramid = (Bar) Bar.getPrototype(); - pyramid.setAsColumnChart(); - cf.setBarShape(SHAPEPYRAMID); - c = pyramid; - break; - - case ChartConstants.CONECHART: - Bar cone = (Bar) Bar.getPrototype(); - cone.setAsColumnChart(); - cf.setBarShape(SHAPECONE); - c = cone; - break; - - case ChartConstants.CYLINDERCHART: - Bar cy = (Bar) Bar.getPrototype(); - cy.setAsColumnChart(); - cf.setBarShape(SHAPECYLINDER); - c = cy; - break; - - case ChartConstants.PYRAMIDBARCHART: - Bar pb = (Bar) Bar.getPrototype(); - pb.setAsBarChart(); - cf.setBarShape(SHAPEPYRAMID); - c = pb; - break; - - case ChartConstants.CONEBARCHART: - Bar cb = (Bar) Bar.getPrototype(); - cb.setAsBarChart(); - cf.setBarShape(SHAPECONE); - c = cb; - break; - - case ChartConstants.CYLINDERBARCHART: - Bar cyb = (Bar) Bar.getPrototype(); - cyb.setAsBarChart(); - cf.setBarShape(SHAPECYLINDER); - c = cyb; - break; - - case ChartConstants.OFPIECHART: - Boppop ofpie = (Boppop) Boppop.getPrototype(); - c = ofpie; - break; - } - if (c != null) { - c.setParentChart(parentchart); - } - return c; - } - - public void setOptions(EnumSet options) { - // FYI: The CrtLine (section 2.4.68) LineFormat (section 2.4.156) record pairs and the sequences of records that conform to the SHAPEPROPS rule (section 2.1.7.20.1) - // specify the drop lines, high-low lines, series lines, and leader lines for the chart (section 2.2.3.3). - // NO 3d record for: bar of pie, bubble, doughnut, filled radar, pie of pie, radar, or scatter chart group. - // Has Ser (Z) axis: Surface, fStacked==0 & Line, Area, fStacked==0 && fClustered==0 && Bar (Col) (Must also have ThreeD record) - // 2 Value Axes: Scatter, Bubble - // NO Axes: Pie, Doughnut, PieOfPie, BarOfPie - - - ChartAxes ca = this.getParentChart().getAxes(); - int chartType = this.getChartType(); - if (options.contains(ChartOptions.STACKED)) // bar/col types, line, area - chartobj.setIsStacked(true); - - if (options.contains(ChartOptions.PERCENTSTACKED)) // bar/col types, line, area - chartobj.setIs100Percent(true); - - if (options.contains(ChartOptions.CLUSTERED)) // bar/col only - cf.setIsClustered(true); - - if (options.contains(ChartOptions.SERLINES)) // bar, line, stock - cf.addChartLines(ChartLine.TYPE_SERIESLINE); - - if (options.contains(ChartOptions.HILOWLINES)) // bar, OfPie - cf.addChartLines(ChartLine.TYPE_HILOWLINE); - - if (options.contains(ChartOptions.DROPLINES)) // Surface chart - cf.addChartLines(ChartLine.TYPE_DROPLINE); - - if (options.contains(ChartOptions.UPDOWNBARS)) // line, area,stock - cf.addUpDownBars(); - - if (options.contains(ChartOptions.HASLINES)) // line, scatter ... - cf.setHasLines(); - - if (options.contains(ChartOptions.SMOOTHLINES)) // line, scatter, radar - cf.setHasSmoothLines(true); -// HANDLE FILLED for radar -// HANDLE bubble 3d -// HANDLE bar shapes *** - - cf.setChartObject(chartobj); - boolean use3Ddefaults = true; // init 3D record with default values for specific chart type - ThreeD threeD = cf.getThreeDRec(false); - if (threeD == null) { - if (options.contains(ChartOptions.THREED) || chartType == SURFACECHART) { // surface charts ALWAYS have a 3 record as does pyramid, cone and cylinder charts - if (chartType != BUBBLECHART && chartType != SCATTERCHART) // supposed to be also donught, radar as well ... - threeD = this.initThreeD(chartType); - else if (chartType == BUBBLECHART) { // scatter charts have no 3d option - cf.setHas3DBubbles(true); - } - } - } else // 3D record already set (via OOXML) - do not use defaults - use3Ddefaults = false; - switch (chartType) { - case BARCHART: - case COLCHART: - if (use3Ddefaults && threeD != null) { - threeD.setChartOption("AnRot", "20"); - threeD.setChartOption("AnElev", "15"); - threeD.setChartOption("TwoDWalls", "true"); - threeD.setChartOption("ThreeDScaling", "false"); - threeD.setChartOption("Cluster", "false"); - threeD.setChartOption("PcDepth", "100"); - threeD.setChartOption("PcDist", "30"); - threeD.setChartOption("PcGap", "150"); - threeD.setChartOption("PcHeight", "72"); // ?????? - threeD.setChartOption("Perspective", "false"); - if (options.contains(ChartOptions.CLUSTERED)) { - threeD.setChartOption("Cluster", "true"); - threeD.setChartOption("PcHeight", "62"); // ?????? - if (chartType == COLCHART) - threeD.setChartOption("Perspective", "true"); - else { // bar chart - threeD.setChartOption("Perspective", "false"); - threeD.setChartOption("ThreeDScaling", "true"); - threeD.setChartOption("PcHeight", "150"); // ?????? - } - } - } - ca.setChartOption(XAXIS, "AddArea", "true"); - ca.setChartOption(YAXIS, "AddArea", "true"); - if (!isStacked() && !isClustered()) { - ca.createAxis(ZAXIS); - ca.setChartOption(ZAXIS, "AddArea", "true"); - } - break; - case LINECHART: - if (options.contains(ChartOptions.THREED)) { - ca.setChartOption(XAXIS, "AddArea", "true"); - ca.setChartOption(YAXIS, "AddArea", "true"); - if (!isStacked()) { - ca.createAxis(ZAXIS); - ca.setChartOption(ZAXIS, "AddArea", "true"); - } - } - break; - case STOCKCHART: - cf.addChartLines(ChartLine.TYPE_HILOWLINE); - cf.setMarkers(0); - break; - case SCATTERCHART: - if (!options.contains(ChartOptions.HASLINES)) { - cf.setHasLines(5); // no line style -- doesn't appear to work - } - break; - case AREACHART: - if (use3Ddefaults && threeD != null) { - threeD.setChartOption("AnRot", "20"); - threeD.setChartOption("TwoDWalls", "true"); - threeD.setChartOption("ThreeDScaling", "false"); - threeD.setChartOption("Perspective", "true"); - } - this.setChartOption("Percentage", "25"); - this.setChartOption("SmoothedLine", "true"); - ca.setChartOption(XAXIS, "CrossBetween", "false"); - if (options.contains(ChartOptions.THREED)) { - ca.setChartOption(XAXIS, "AddArea", "true"); - ca.setChartOption(YAXIS, "AddArea", "true"); - if (!isStacked()) { - ca.createAxis(ZAXIS); - ca.setChartOption(ZAXIS, "AddArea", "true"); - } - } - break; - case BUBBLECHART: - if (options.contains(ChartOptions.THREED)) { - this.setChartOption("Percentage", "25"); - this.setChartOption("SmoothedLine", "true"); - this.setChartOption("ThreeDBubbles", "true"); - } - break; - case PIECHART: // Pie - cf.setVaryColor(true); // Should be true for all pie charts ... - if (options.contains(ChartOptions.EXPLODED)) { - this.setChartOption("SmoothedLine", "true"); - this.setChartOption("Percentage", "25"); - } - if (use3Ddefaults && options.contains(ChartOptions.THREED)) - this.setChartOption("AnRot", "236"); - ca.removeAxes(); - break; - case DOUGHNUTCHART: - if (options.contains(ChartOptions.EXPLODED)) { - this.setChartOption("SmoothedLine", "true"); - this.setChartOption("Percentage", "25"); - } - cf.setVaryColor(true); // Should be true for all pie charts ... - ca.removeAxes(); - break; - case SURFACECHART: // NOTE: For Surface charts, non-threeD==Contour - if (use3Ddefaults && threeD != null) { // shouldn't - threeD.setChartOption("Cluster", "false"); - threeD.setChartOption("TwoDWalls", "true"); - threeD.setChartOption("ThreeDScaling", "true"); - threeD.setChartOption("Perspective", "true"); - } - ca.setChartOption(XAXIS, "AddArea", "true"); - ca.setChartOption(XAXIS, "AreaFg", "8"); - ca.setChartOption(XAXIS, "AreaBg", "78"); - ca.setChartOption(YAXIS, "AddArea", "true"); - ca.setChartOption(YAXIS, "AreaFg", "22"); - ca.setChartOption(YAXIS, "AreaBg", "78"); - ca.createAxis(ZAXIS); - if (use3Ddefaults && options.contains(ChartOptions.THREED)) { // "regular" 3d surface - threeD.setChartOption("AnElev", "15"); - threeD.setChartOption("AnRot", "20"); - threeD.setChartOption("PcDepth", "100"); - threeD.setChartOption("PcDist", "30"); - threeD.setChartOption("PcGap", "150"); - threeD.setChartOption("PcHeight", "50"); // ?????? - } else if (use3Ddefaults) { // contour (non-3d) - threeD.setChartOption("AnElev", "90"); - threeD.setChartOption("AnRot", "0"); - threeD.setChartOption("PcDepth", "100"); - threeD.setChartOption("PcDist", "0"); - threeD.setChartOption("PcGap", "150"); - threeD.setChartOption("PcHeight", "50"); // ?????? - } - if (options.contains(ChartOptions.WIREFRAME)) - ((Surface) chartobj).setIsWireframe(true); - if (!options.contains(ChartOptions.WIREFRAME) && options.contains(ChartOptions.THREED)) { - chartobj.setChartOption("ColorFill", "true"); - chartobj.setChartOption("Shading", "true"); - } else if (options.contains(ChartOptions.WIREFRAME)) { // conotur (flat, non-3d) wireframe - chartobj.setChartOption("Shading", "true"); - } else // contour filled (i.e. plain Surface) - chartobj.setChartOption("ColorFill", "true"); - break; - case RADARCHART: - if (options.contains(ChartOptions.FILLED)) - ((RadarChart) this).setFilled(true); - break; - case PYRAMIDCHART: - case CONECHART: - case CYLINDERCHART: - case PYRAMIDBARCHART: - case CONEBARCHART: - case CYLINDERBARCHART: - // Shaped Bar/Col charts are all 3d - if (threeD == null) threeD = this.initThreeD(chartType); - if (use3Ddefaults) { - threeD.setChartOption("AnElev", "15"); - threeD.setChartOption("AnRot", "20"); - threeD.setChartOption("Cluster", "true"); // only for regular pyramid charts; stacked, etc. will alter - threeD.setChartOption("TwoDWalls", "true"); - if (!options.contains(ChartOptions.THREED)) { - threeD.setChartOption("ThreeDScaling", "false"); - threeD.setChartOption("Cluster", "true"); - } - threeD.setChartOption("Perspective", "false"); - threeD.setChartOption("PcDepth", "100"); - threeD.setChartOption("PcDist", "30"); - threeD.setChartOption("PcGap", "150"); - threeD.setChartOption("PcHeight", "52"); // ?????? - } - if (chartType == PYRAMIDCHART || chartType == PYRAMIDBARCHART) - cf.setBarShape(ChartConstants.SHAPEPYRAMID); - else if (chartType == CONECHART || chartType == CONEBARCHART) - cf.setBarShape(ChartConstants.SHAPECONE); - else if (chartType == CYLINDERCHART || chartType == CYLINDERBARCHART) - cf.setBarShape(ChartConstants.SHAPECYLINDER); - break; - } - } - - - /** - * parse the chart object OOXML element (barchart, area3DChart, etc.) and create the corresponding chart type objects - * - * @param xpp XmlPullParser - * @param wbh workBookHandle - * @param parentChart parent Chart Object - * @param nChart chart grouping number, 0 for default, 1-9 for overlay - * @return - */ - public static ChartType parseOOXML(XmlPullParser xpp, WorkBookHandle wbh, Chart parentChart, int nChart) { - try { - String endTag = xpp.getName(); - String tnm = xpp.getName(); - int eventType = xpp.getEventType(); - int chartType = BARCHART; - ChartAxes ca = parentChart.getAxes(); - - java.util.Stack lastTag = new java.util.Stack(); // keep track of element hierarchy - ChartFormat cf = parentChart.getChartOjectParent(nChart); - - EnumSet options = EnumSet.noneOf(ChartOptions.class); // chart-specific options such as threed, stacked ... - if ((tnm.equals("bubble3D"))) { // bubble3D tag appears for each series in 3D bubble chart - options.add(ChartOptions.THREED); - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART])) { - chartType = BARCHART; - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.LINECHART])) { - chartType = LINECHART; - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.PIECHART])) { - chartType = PIECHART; - parentChart.getAxes().removeAxes(); - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.AREACHART])) { - chartType = AREACHART; - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SCATTERCHART])) { - chartType = SCATTERCHART; - ca.removeAxis(XAXIS); - ca.createAxis(XVALAXIS); - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.RADARCHART])) { - chartType = RADARCHART; - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.SURFACECHART])) { - chartType = SURFACECHART; - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.DOUGHNUTCHART])) { - chartType = DOUGHNUTCHART; - parentChart.getAxes().removeAxes(); - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.BUBBLECHART])) { - chartType = BUBBLECHART; - ca.removeAxis(XAXIS); - ca.createAxis(XVALAXIS); - } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.BARCHART])) { - chartType = BARCHART; - options.add(ChartOptions.THREED); - } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.LINECHART])) { - chartType = LINECHART; - options.add(ChartOptions.THREED); - } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.PIECHART])) { - chartType = PIECHART; - options.add(ChartOptions.THREED); - parentChart.getAxes().removeAxes(); - } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.AREACHART])) { - chartType = AREACHART; - options.add(ChartOptions.THREED); - } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SCATTERCHART])) { - chartType = SCATTERCHART; - ca.removeAxis(XAXIS); - ca.createAxis(XVALAXIS); - options.add(ChartOptions.THREED); - } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.RADARCHART])) { - chartType = RADARCHART; - options.add(ChartOptions.THREED); - } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART])) { - chartType = SURFACECHART; - options.add(ChartOptions.THREED); - } else if (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.DOUGHNUTCHART])) { - chartType = DOUGHNUTCHART; - options.add(ChartOptions.THREED); - parentChart.getAxes().removeAxes(); - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.OFPIECHART])) { - chartType = OFPIECHART; - ca.removeAxis(XAXIS); - ca.removeAxis(YAXIS); - } else if (tnm.equals(OOXMLConstants.twoDchartTypes[ChartHandle.STOCKCHART])) { - chartType = STOCKCHART; - } - - GenericChartObject co = ChartType.createUnderlyingChartObject(chartType, parentChart, cf); //, ((ChartType)chartobj.get(0))); - cf.setChartObject(co); // sets the chart format (parent of chart item) to the specific chart item - // exception for surface charts in 3d - if (chartType == SURFACECHART && (tnm.equals(OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART]))) - ((Surface) co).setIs3d(true); - ChartType ct = ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); - parentChart.addChartType(ct, nChart); - - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - tnm = xpp.getName(); - lastTag.push(tnm); - String v = null; - try { - v = xpp.getAttributeValue(0); - } catch (IndexOutOfBoundsException e) { - } -// TODO: -// hasMarkers **** -// dLbls -// smooth -- line -// marker -- line -// bandfmts -- surface, surface3d -// bubble3D, -// bubbleScale -// showNegBubbles -// sizeRepresents -// custSplit -- OfPie - if (tnm.equals("grouping")) { //This element specifies the type of grouping for a column, line, or area chart + 3d versions - if (v.equals("stacked")) - co.setIsStacked(true); - else if (v.equals("percentStacked")) - co.setIs100Percent(true); - else if (v.equals("clustered")) - cf.setIsClustered(true); // bar/col only - else if (v.equals("standard")) { // for Line, Line3d, Area, Area3d and Bar3d, Col3d - does not appear valid for Bar 2d and Col 2d - co.setIsStacked(false); - co.setIs100Percent(false); - } - } else if (tnm.equals("barDir")) { // - if (v.equals("col")) { - ((Bar) co).setAsColumnChart(); - ct = ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); - parentChart.addChartType(ct, nChart); - } - } else if (tnm.equals("shape")) { // bar3d - cf.setBarShape(ct.convertShape(v)); - if (ct.defaultShape != 0) { - ct = ChartType.createChartTypeObject(co, cf, parentChart.getWorkBook()); - parentChart.addChartType(ct, nChart); - } - } else if (tnm.equals("radarStyle")) { - if (v.equals("filled")) { - ((RadarChart) ct).setFilled(true); - } else if (v.equals("marker")) { - - } - } else if (tnm.equals("wireframe")) { // surface - ((Surface) co).setIsWireframe(v != null && v.equals("1")); - } else if (tnm.equals("scatterStyle")) { - if (v.equals("lineMarker")) { - cf.setHasLines(); - } else if (v.equals("smoothMarker")) { - cf.setHasSmoothLines(true); - } else if (v.equals("marker")) - ; //cf.seth; - else if (v.equals("line")) - cf.setHasLines(); - else if (v.equals("smooth")) - cf.setHasSmoothLines(true); - } else if (tnm.equals("varyColors")) { - cf.setVaryColor(xpp.getAttributeValue(0).equals("1")); - } else if (tnm.equals("dropLines")) { - ChartLine cl = cf.addChartLines(ChartLine.TYPE_DROPLINE); - cl.parseOOXML(xpp, lastTag, cf, wbh); - } else if (tnm.equals("hiLowLines")) { - ChartLine cl = cf.addChartLines(ChartLine.TYPE_HILOWLINE); - cl.parseOOXML(xpp, lastTag, cf, wbh); - } else if (tnm.equals("upDownBars")) { - cf.parseUpDownBarsOOXML(xpp, lastTag, wbh); - } else if (tnm.equals("serLines")) { - ChartLine cl = cf.addChartLines(ChartLine.TYPE_SERIESLINE); - cl.parseOOXML(xpp, lastTag, cf, wbh); - } else if (tnm.equals("overlap")) { // bar - co.setChartOption("Overlap", v); - } else if (tnm.equals("gapWidth")) { - co.setChartOption("Gap", v); // bar - } else if (tnm.equals("ofPieType")) { - ((Boppop) co).setIsPieOfPie("pie".equals(v)); - } else if (tnm.equals("gapDepth")) { - cf.setGapDepth(Integer.valueOf(v)); // bar3d, area3d, line3d - } else if (tnm.equals("firstSliceAn")) { - ((Pie) co).setAnStart(Integer.valueOf(v)); // pie or doughnut - } else if (tnm.equals("holeSize")) { - ((Pie) co).setDoughnutSize(Integer.valueOf(v)); // pie or doughnut - } else if (tnm.equals("secondPieSize")) { - ((Boppop) co).setSecondPieSize(Integer.valueOf(v)); // OfPie (Pie of Pie, Bar of Pie) - } else if (tnm.equals("splitType")) { - ((Boppop) co).setSplitType(v); // OfPie (Pie of Pie, Bar of Pie) - } else if (tnm.equals("splitPos")) { - ((Boppop) co).setSplitPos(Integer.valueOf(v)); // OfPie (Pie of Pie, Bar of Pie) - } else if (tnm.equals("ser")) { - Series s = ChartSeries.parseOOXML(xpp, wbh, ct, false, lastTag); - } else if (tnm.equals("dLbls")) { - } else if (tnm.equals("marker")) { // line only? -// m= (Marker) Marker.parseOOXML(xpp, lastTag).cloneElement(); - } - } else if (eventType == XmlPullParser.END_TAG) { - if (xpp.getName().equals(endTag)) - break; - - } - eventType = xpp.next(); - } - return ct; - -// lastTag.pop(); // chart type tag will be added in parseOOXML -//mychart.getChartObject(nChart).parseOOXML(xpp, this.wbh, lastTag); - } catch (Exception e) { - Logger.logErr("ChartType.parseChartType: " + e.toString()); - } - return null; - } - - /** - * return data label options for each series as an int array - *
                      each can be one or more of: - *
                      VALUELABEL= 0x1; - *
                      VALUEPERCENT= 0x2; - *
                      CATEGORYPERCENT= 0x4; - *
                      SMOOTHEDLINE= 0x8; - *
                      CATEGORYLABEL= 0x10; - *
                      BUBBLELABEL= 0x20; - *
                      SERIESLABEL= 0x40; - * - * @return int array - * @see AttachedLabel - */ - protected int[] getDataLabelInts() { - return chartobj.getParentChart().getDataLabelsPerSeries(cf.getDataLabelsInt()); // data label options, if any, per series - } - - /** - * return the default data label setting for the chart, if any - *
                      NOTE: each series can override the default data label for the chart - *
                      can be one or more of: - *
                      VALUELABEL= 0x1; - *
                      VALUEPERCENT= 0x2; - *
                      CATEGORYPERCENT= 0x4; - *
                      SMOOTHEDLINE= 0x8; - *
                      CATEGORYLABEL= 0x10; - *
                      BUBBLELABEL= 0x20; - *
                      SERIESLABEL= 0x40; - * - * @return int default data label for chart - */ - protected int getDataLabel() { - return cf.getDataLabelsInt(); - } - - /** - * return an array of the type of markers for each series: - *
                      0 = no marker - *
                      1 = square - *
                      2 = diamond - *
                      3 = triangle - *
                      4 = X - *
                      5 = star - *
                      6 = Dow-Jones - *
                      7 = standard deviation - *
                      8 = circle - *
                      9 = plus sign - **/ - protected int[] getMarkerFormats() { - int mf = cf.getMarkerFormat(); - int[] markers = new int[getParentChart().getAllSeries(getParentChart().getChartOrder(this)).size()]; - if (mf > 0) { - for (int i = 0; i < markers.length; i++) { - markers[i] = mf; - } - } - return markers; - } - - /** - * specifies whether - * the color for each data point and the color and type for each data marker - * vary - * - * @param b - */ - protected void setVaryColor(boolean b) { - cf.setVaryColor(b); - } - - /** - * returns true if this chart has smoothed lines - * - * @return - */ - public boolean getHasSmoothLines() { - return cf.getHasSmoothLines(); - } - - - public void setHasSmoothLines(boolean b) { - cf.setHasSmoothLines(b); - } - - - /** - * returns the chart type for the default chart - */ - public int getChartType() { - return chartobj.chartType; - } - - public void addLegend(Legend l) { - legend = l; - } - - public String getSVG() { - return null; - } - - public String getJSON() { - return null; - } - - public JSONObject getOptionsJSON() { - return null; - } - - /** - * gets the chart-type specific OOXML representation (representing child element of plotArea element) - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - return null; - } - - public JSONObject getJSON(ChartSeries s, WorkBookHandle wbh, Double[] minMax) throws JSONException { - JSONObject chartObjectJSON = new JSONObject(); - - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // Deal with Series - double yMax = 0.0, yMin = 0.0; - int nSeries = 0; - JSONArray seriesJSON = new JSONArray(); - JSONArray seriesCOLORS = new JSONArray(); - try { - ArrayList series = s.getSeriesRanges(); - String[] scolors = s.getSeriesBarColors(); - for (int i = 0; i < series.size(); i++) { - JSONArray seriesvals = CellRange.getValuesAsJSON(series.get(i).toString(), wbh); - // must trap min and max for axis tick and units - nSeries = Math.max(nSeries, seriesvals.length()); - for (int j = 0; j < seriesvals.length(); j++) { - try { - yMax = Math.max(yMax, seriesvals.getDouble(j)); - yMin = Math.min(yMin, seriesvals.getDouble(j)); - } catch (NumberFormatException n) { - } - } - seriesJSON.put(seriesvals); - seriesCOLORS.put(scolors[i]); - } - chartObjectJSON.put("Series", seriesJSON); - chartObjectJSON.put("SeriesFills", seriesCOLORS); - } catch (JSONException je) { - // TODO: Log error - } - minMax[0] = new Double(yMin); - minMax[1] = new Double(yMax); - minMax[2] = new Double(nSeries); - return chartObjectJSON; - } - - /** - * return Type JSON for generic chart types - * - * @return - * @throws JSONException - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON = new JSONObject(); - typeJSON.put("type", "Default"); - return typeJSON; - } - - /** - * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param s ChartSeries - holds legends, categories, seriesdata ... - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - return ""; - } - - /** - * returns the SVG-ready data label for the given set of data label options - * TODO: get separator character - * - * @param datalabel int[] Data label options (indexed by current series # s) - *
                      can be one or more of: - *
                      VALUELABEL= 0x1; - *
                      VALUEPERCENT= 0x2; - *
                      CATEGORYPERCENT= 0x4; - *
                      SMOOTHEDLINE= 0x8; - *
                      CATEGORYLABEL= 0x10; - *
                      BUBBLELABEL= 0x20; - *
                      SERIESLABEL= 0x40; - * @param series ArrayList of series and category data: structure: category data, series 0-n data, series colors, series Labels (Eventually will be an object) - * @param val double current series value - * @param percentage double percentage value (pie charts only) - * @param s int current series # - * @param cat string current cat - * @return - */ - public String getSVGDataLabels(int[] datalabels, HashMap axisMetrics, double val, double percentage, int s, String[] legends, String cat) { - - if (s >= datalabels.length) // can happen with Pie-style charts - return null; - boolean showValueLabel = (datalabels[s] & AttachedLabel.VALUELABEL) == AttachedLabel.VALUELABEL; - boolean showValuePercent = (datalabels[s] & AttachedLabel.VALUEPERCENT) == AttachedLabel.VALUEPERCENT; - boolean showCatPercent = (datalabels[s] & AttachedLabel.CATEGORYPERCENT) == AttachedLabel.CATEGORYPERCENT; - boolean showCategories = (datalabels[s] & AttachedLabel.CATEGORYLABEL) == AttachedLabel.CATEGORYLABEL; - boolean showBubbleLabel = (datalabels[s] & AttachedLabel.BUBBLELABEL) == AttachedLabel.BUBBLELABEL; - boolean showValue = (datalabels[s] & AttachedLabel.VALUE) == AttachedLabel.VALUE; - if (showValue || showCategories || showValueLabel || showValuePercent || showBubbleLabel) { - String l = ""; - if (showValueLabel) - l += legends[s] + " "; // series names - if (showCategories) - l += CellFormatFactory.fromPatternString( - (String) axisMetrics.get("xPattern")).format(cat) + " "; // categories - if (showValue || showBubbleLabel) { - l += CellFormatFactory.fromPatternString( - (String) axisMetrics.get("yPattern")).format(String.valueOf(val)); - /*try { - int v= new Double(val).intValue(); - if (v==val) - l+=v + " "; - else - l+=val + " "; - } catch (Exception e) { - l+= val + " "; - }*/ - } - if (showValuePercent) - l += (int) Math.round(percentage * 100) + "%"; - return l; - } - return null; - } - - /** Show or remove Data Table for Chart - * NOTE: METHOD IS STILL EXPERIMENTAL - * @param bShow - * - public void showDataTable(boolean bShow) { - int i= Chart.findRecPosition(chartArr, Dat.class); - if (bShow) { - if (i==-1) { // add Dat - Dat d= (Dat)Dat.getPrototype(true); // create data table - i= Chart.findRecPosition(chartArr, AxisParent.class); - this.chartArr.add(++i, d); - } - } else if (i > 0) { - chartArr.remove(i); // remove Dat - Data Table options + all associated recs - } - }*/ - - - /** - * return truth if Chart has a data legend key showing - * - * @return - */ - public boolean hasDataLegend() { - return (legend != null); - } - - /** - * return the data legend for this chart - * - * @return - */ - public Legend getDataLegend() { - return legend; - } - - /** - * show or hide chart legend key - * - * @param bShow boolean show or hide - * @param vertical boolean show as vertical or horizontal - */ - public void showLegend(boolean bShow, boolean vertical) { - if (bShow && legend == null) { - legend = Legend.createDefaultLegend(wb); - legend.setParentChart(chartobj.getParentChart()); - for (int j = 0; j < legend.chartArr.size(); j++) - ((GenericChartObject) legend.chartArr.get(j)).setParentChart(chartobj.getParentChart()); - legend.setVertical(vertical); - cf.chartArr.add(legend); - } else if (bShow) { - legend.setVertical(vertical); - } else if (legend != null) { - int i = Chart.findRecPosition(cf.chartArr, Legend.class); - cf.chartArr.remove(i); - legend = null; - } - } - - /** - * return the Data Labels chosen for this chart, if any - * can be one or more of: - *
                      Value - *
                      ValuePerecentage - *
                      CategoryPercentage - *
                      CategoryLabel - *
                      BubbleLabel - *
                      SeriesLabel - * or an empty string if no data labels are chosen for the chart - * - * @return - */ - public String getDataLabels() { - return cf.getDataLabels(); - } - - /** - * return data label options as an int - *
                      can be one or more of: - *
                      VALUELABEL= 0x1; - *
                      VALUEPERCENT= 0x2; - *
                      CATEGORYPERCENT= 0x4; - *
                      SMOOTHEDLINE= 0x8; - *
                      CATEGORYLABEL= 0x10; - *
                      BUBBLELABEL= 0x20; - *
                      SERIESLABEL= 0x40; - * - * @return a combination of data label options above or 0 if none - * @see AttachedLabel - */ - public int getDataLabelsInt() { - return cf.getDataLabelsInt(); - } - - /** - * returns the bar shape for a column or bar type chart - * can be one of: - *
                      ChartConstants.SHAPECOLUMN default - *
                      ChartConstants.SHAPECONEd - *
                      ChartConstants.SHAPECONETOMAX - *
                      ChartConstants.SHAPECYLINDER - *
                      ChartConstants.SHAPEPYRAMID - *
                      ChartConstants.SHAPEPYRAMIDTOMAX - * - * @return int bar shape - */ - public int getBarShape() { - return SHAPEDEFAULT; - } - - /** - * returns type of marker for this chart, if any - *
                      0 = no marker - *
                      1 = square - *
                      2 = diamond - *
                      3 = triangle - *
                      4 = X - *
                      5 = star - *
                      6 = Dow-Jones - *
                      7 = standard deviation - *
                      8 = circle - *
                      9 = plus sign - * - * @return int marker type - */ - public int getMarkerFormat() { - return cf.getMarkerFormat(); - } - - /** - * returns true if this chart has lines (see Scatter, Line Charts amongst others) - * - * @return - */ - public boolean getHasLines() { - return cf.getHasLines(); - } - - - /** - * returns true if this chart has drop lines - * - * @return - */ - public boolean getHasDropLines() { - return cf.getHasDropLines(); - } - - /** - * sets this chart (Area, Line, Scatter) to have drop lines - */ - public void setHasDropLines() { - cf.setHasDropLines(); - } - - /** - * look up a generic chart option - * - * @param op - * @return - */ - public String getChartOption(String op) { - String ret = chartobj.getChartOption(op); // if not a chart-specific option, see if it's more generic - if (ret == null) - return cf.getChartOption(op); - return ret; - } - - /** - * return chart-type-specific options in XML form - * - * @param nChart 0=default, 1-9= overlay charts - * @return String XML - */ - public String getChartOptionsXML() { - StringBuffer sb = new StringBuffer(); - sb.append(chartobj.getOptionsXML()); // chart-type-specific - sb.append(cf.getChartOptionsXML()); // governs threed settings and other misc. options - return sb.toString(); - } - - /** - * interface for setting chart-type-specific options - * in a generic fashion - * - * @param op option - * @param val value - * @see OpenXLS.handleChartElement - * @see ChartHandle.getXML - */ - public boolean setChartOption(String op, String val) { - if (!chartobj.setChartOption(op, val)) // if not handled, - cf.setOption(op, val); - return true; - } - - - /** - * @return truth of "Chart is Three-D" - */ - public boolean isThreeD() { - return cf.isThreeD(chartobj.chartType); - } - - /** - * return ThreeD settings for this chart in XML form - * - * @return String XML - */ - public String getThreeDXML() { - return cf.getThreeDXML(); - } - - /** - * returns the 3d record of the desired chart - * - * @param chartType one of the chart type constants - * @return - */ - public ThreeD initThreeD(int charttype) { -/* TODO: test if this is necessary AxisParent ap = this.getAxisParent(); - try { - // first thing, remove the PlotArea and Frame - don't know why but is necessary!! - int x= Chart.findRecPosition(ap.chartArr, PlotArea.class); - if (x!=-1) ap.chartArr.remove(x); - x= Chart.findRecPosition(ap.chartArr, Frame.class); - if (x!=-1) ap.chartArr.remove(x); - } catch (Exception e) {}*/ - ThreeD td = cf.getThreeDRec(true); - td.setIsPie(charttype == PIECHART || charttype == DOUGHNUTCHART); - return td; - } - - /** - * return the 3d record of the chart - *
                      Creates if not present, if bCreate is true - * - * @return - */ - public ThreeD getThreeDRec(boolean bCreate) { - return cf.getThreeDRec(bCreate); - } - - - /** - * @return truth of "Chart is Stacked" - */ - public boolean isStacked() { - return chartobj.isStacked(); - } - - /** - * return truth of "Chart is 100% Stacked" - * - * @return - */ - public boolean is100PercentStacked() { - return (chartobj.is100Percent()); - } - - public void setIsStacked(boolean isstacked) { - chartobj.setIsStacked(isstacked); - } - - public void setIs100Psercent(boolean ispercentage) { - chartobj.setIs100Percent(ispercentage); - } - - /** - * @return truth of "Chart is Clustered" (Bar/Col only) - */ - public boolean isClustered() { - return false; - } - - public void addLegend() { - if (legend == null) { - // TODO: ADD LEGEND to cf - //int i = Chart.findRecPosition(cf.chartArr, Legend.class); - } - } - - // SVG Convenience Methods - public static String getScript(String range) { - return "onmouseover='highLight(evt); showRange(\"" + range + "\");' onclick='handleClick(evt);' onmouseout='restore(evt); hideRange();'"; -// return "onmouseover='highLight(evt);' onclick='handleClick(evt);' onmouseout='restore(evt);'"; - } - - public static String getFillOpacity() { - return ".75"; - } - - - public static String getTextColor() { - return "#222222"; - } - - public static String getLightColor() { - return "#CCCCCC"; - } - - public static String getMediumColor() { - return "#555555"; - } - - - public static String getDarkColor() { - return "#333333"; - } - - /** - * returns the SVG for the font style of this object - */ - protected String getFontSVG() { - return getFontSVG(-1); - } - - /** - * Returns SVG used to define font for data labels - * TODO: read correct value from chart recs - * - * @return - */ - public static String getDataLabelFontSVG() { - int sz = 9; - return "font-family='Arial' font-size='" + sz + "' fill='" + ChartType.getDarkColor() + "' "; - } - - /** - * returns the SVG for the font style of this object - * - * @param stroke size in pt - */ - public static String getStrokeSVG() { - return getStrokeSVG(1f, getMediumColor()); - } - - /** - * returns the SVG for the font style of this object - * - * @param stroke size in pt - * @param String stroke color in HTML format - */ - public static String getStrokeSVG(float sz, String strokeclr) { - String stk = " stroke='" + strokeclr + "' stroke-opacity='1' stroke-width='" + sz + "' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='4'"; - return stk; - } - - - /** - * returns the SVG for the font style of this object - * - * @param font size in pt - */ - protected String getFontSVG(int sz) { -// io.starter.formats.XLS.Font f = this.getFont(); // this is not correct! - // if(f == null) // return a default - return "font-family='Arial' font-size='" + sz + "' fill='" + getDarkColor() + "' "; - - //return f.getSVG(); - } - - /** - * Convert the user-friendly OOXML shape string to 2003-v int shape flag - * - * @param shape - * @return - */ - public int convertShape(String shape) { - defaultShape = 0; - if (shape.equals("box")) - defaultShape = 0; - if (shape.equals("cone")) // 1 1 - defaultShape = 257; - if (shape.equals("coneToMax")) // 1 2 - defaultShape = 513; - if (shape.equals("cylinder")) // 1 0 - defaultShape = 1; - if (shape.equals("pyramid")) // 0 1 - defaultShape = 256; - if (shape.equals("pyramidToMax")) // 0 2 - defaultShape = 512; - return defaultShape; - } - - /** - * convert the default shape flag to a user-friendly (OOXML-compliant) String - * - * @param shape int - * @return - */ - public String getShape() { - switch (defaultShape) { - case 0: - return "box"; - case 1: - return "cylinder"; - case 256: - return "pyramid"; - case 257: - return "cone"; - case 512: - return "pyramidToMax"; - case 513: - return "coneToMax"; - } - return null; - } - - /** - * returns an int representing the space between points in a 3d area, bar or line chart, or 0 if not 3d - * - * @return - */ - public int getGapDepth() { - return cf.getGapDepth(); - } - - /** - * return the SeriesList record for this chart object - * The SeriesList record maps the series for the chart. - * - * @return - */ - protected SeriesList getSeriesList() { - return (SeriesList) Chart.findRec(cf.chartArr, SeriesList.class); - } - - /** - * @param fName - */ - public void WriteMainChartRecs(String fName) { - class util { - public void writeRecs(BiffRec b, BufferedWriter writer, int level) throws IOException { - String tabs = "\t\t\t\t\t\t\t\t\t\t"; - if (b == null) return; - writer.write(tabs.substring(0, level) + b.getClass().toString().substring(b.getClass().toString().lastIndexOf('.') + 1)); - if (b instanceof io.starter.formats.XLS.charts.SeriesText) - writer.write("\t[" + b.toString() + "]"); - else if (b instanceof MSODrawing) { - writer.write("\t[" + ((MSODrawing) b).toString() + "]"); - // writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); - writer.write(((MSODrawing) b).debugOutput()); - writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11) + "]"); - } else if (b instanceof Obj) { - writer.write(((Obj) b).debugOutput()); - } else if (b instanceof Label) { - writer.write("\t[" + b.getStringVal() + "]"); - } else // all else, write bytes - writer.write("\t[" + ByteTools.getByteDump(ByteTools.shortToLEBytes(b.getOpcode()), 0) + "][" + ByteTools.getByteDump(b.getData(), 0).substring(11) + "]"); - writer.newLine(); - try { - if (((GenericChartObject) b).chartArr.size() > 0) { - ArrayList chartArr = ((GenericChartObject) b).chartArr; - for (int i = 0; i < chartArr.size(); i++) { - writeRecs(chartArr.get(i), writer, level + 1); - - } - } - } catch (ClassCastException ce) { - } - } - } - - - try { - java.io.File f = new java.io.File(fName); - BufferedWriter writer = new BufferedWriter(new FileWriter(f)); - util u = new util(); - - java.util.Vector v = this.getParentChart().getAllSeries(); - for (int i = 0; i < v.size(); i++) { - u.writeRecs((BiffRec) v.get(i), writer, 0); - } - writer.newLine(); - ArrayList chartArr = this.cf.chartArr; - for (int i = 0; i < chartArr.size(); i++) { - u.writeRecs(chartArr.get(i), writer, 0); - } - - writer.flush(); - writer.close(); - writer = null; - } catch (Exception e) { - } - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ChartType.kt b/src/main/java/io/starter/formats/XLS/charts/ChartType.kt new file mode 100644 index 0000000..34d84b8 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ChartType.kt @@ -0,0 +1,1480 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ChartHandle +import io.starter.OpenXLS.ChartHandle.ChartOptions +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.OOXMLConstants +import io.starter.formats.XLS.* +import io.starter.formats.XLS.charts.ChartType.Companion +import io.starter.formats.cellformat.CellFormatFactory +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.xmlpull.v1.XmlPullParser + +import java.io.BufferedWriter +import java.io.FileWriter +import java.io.IOException +import java.io.Serializable +import java.util.ArrayList +import java.util.EnumSet +import java.util.HashMap + +/** + * This abstract class defines a Chart Group for a BIFF Chart. An Excel Chart may have up to 4 (2003 and previous versions) or 9 (post-2003) charts within one chart. + * The 0th chart type in the Chart Group is the default chart. + *

                      + * NOTES: + *

                      + * CRT = ChartFormat Begin (Bar / Line / (BopPop [BopPopCustom]) / Pie / Area / Scatter / Radar / RadarArea / Surf) CrtLink [SeriesList] [Chart3d] [LD] [2DROPBAR] *4(CrtLine LineFormat) *2DFTTEXT [DataLabExtContents] [SS] *4SHAPEPROPS End + * + * + * MUST CHANGE ChartObject if change chart type + * MUST ADD/REMOVE ChartObject if add/remove multiple charts + * axisparent --> when add a new chart a new crt is added axes + titles/labels, number format, etc + * + * + * REFACTOR: TO FINISH + * showDataTable -- Finish, TODO + * NOTE: The SeriesList record specifies the series of the chart. This record MUST NOT exist in the first chart group in the chart sheet substream. + * This record MUST exist when not in the first chart group in the chart sheet substream + * + * + * NOTE: + * The Chart3d record specifies that the plot area, axis group, and chart group are rendered in a 3-D scene, rather than a 2-D scene, and specifies properties of the 3-D scene. If this record exists in the chart sheet substream, the chart sheet substream MUST have exactly one chart group. This record MUST NOT exist in a bar of pie, bubble, doughnut, filled radar, pie of pie, radar, or scatter chart group. + * + * + * NOTE: legends only in 1st chart group + */ +abstract class ChartType : ChartConstants, Serializable { + protected var chartobj: GenericChartObject + /** + * return the data legend for this chart + * + * @return + */ + var dataLegend: Legend? = null + protected set + var cf: ChartFormat? = null + // protected ChartSeries chartseries= new ChartSeries(); + @Transient + protected var wb: WorkBook? = null + protected var defaultShape = 0 // controls default bar shape for all bars in the chart; used when adding or setting series + + val parentChart: Chart? + get() = chartobj.parentChart + + /** + * return data label options for each series as an int array + *

                      each can be one or more of: + *

                      VALUELABEL= 0x1; + *

                      VALUEPERCENT= 0x2; + *

                      CATEGORYPERCENT= 0x4; + *

                      SMOOTHEDLINE= 0x8; + *

                      CATEGORYLABEL= 0x10; + *

                      BUBBLELABEL= 0x20; + *

                      SERIESLABEL= 0x40; + * + * @return int array + * @see AttachedLabel + */ + protected// data label options, if any, per series + val dataLabelInts: IntArray + get() = chartobj.parentChart!!.getDataLabelsPerSeries(cf!!.dataLabelsInt) + + /** + * return the default data label setting for the chart, if any + *

                      NOTE: each series can override the default data label for the chart + *

                      can be one or more of: + *

                      VALUELABEL= 0x1; + *

                      VALUEPERCENT= 0x2; + *

                      CATEGORYPERCENT= 0x4; + *

                      SMOOTHEDLINE= 0x8; + *

                      CATEGORYLABEL= 0x10; + *

                      BUBBLELABEL= 0x20; + *

                      SERIESLABEL= 0x40; + * + * @return int default data label for chart + */ + val dataLabel: Int + get() = cf!!.dataLabelsInt + + /** + * return an array of the type of markers for each series: + *

                      0 = no marker + *

                      1 = square + *

                      2 = diamond + *

                      3 = triangle + *

                      4 = X + *

                      5 = star + *

                      6 = Dow-Jones + *

                      7 = standard deviation + *

                      8 = circle + *

                      9 = plus sign + */ + val markerFormats: IntArray + get() { + val mf = cf!!.markerFormat + val markers = IntArray(parentChart!!.getAllSeries(parentChart!!.getChartOrder(this)).size) + if (mf > 0) { + for (i in markers.indices) { + markers[i] = mf + } + } + return markers + } + + /** + * returns true if this chart has smoothed lines + * + * @return + */ + var hasSmoothLines: Boolean + get() = cf!!.hasSmoothLines + set(b) { + cf!!.hasSmoothLines = b + } + + + /** + * returns the chart type for the default chart + */ + val chartType: Int + get() = chartobj.chartType + + val svg: String? + get() = null + + val json: String? + get() = null + + val optionsJSON: JSONObject? + get() = null + + /** + * return Type JSON for generic chart types + * + * @return + * @throws JSONException + */ + val typeJSON: JSONObject + @Throws(JSONException::class) + get() { + val typeJSON = JSONObject() + typeJSON.put("type", "Default") + return typeJSON + } + + /** + * return the Data Labels chosen for this chart, if any + * can be one or more of: + *

                      Value + *

                      ValuePerecentage + *

                      CategoryPercentage + *

                      CategoryLabel + *

                      BubbleLabel + *

                      SeriesLabel + * or an empty string if no data labels are chosen for the chart + * + * @return + */ + val dataLabels: String? + get() = cf!!.dataLabels + + /** + * return data label options as an int + *

                      can be one or more of: + *

                      VALUELABEL= 0x1; + *

                      VALUEPERCENT= 0x2; + *

                      CATEGORYPERCENT= 0x4; + *

                      SMOOTHEDLINE= 0x8; + *

                      CATEGORYLABEL= 0x10; + *

                      BUBBLELABEL= 0x20; + *

                      SERIESLABEL= 0x40; + * + * @return a combination of data label options above or 0 if none + * @see AttachedLabel + */ + val dataLabelsInt: Int + get() = cf!!.dataLabelsInt + + /** + * returns the bar shape for a column or bar type chart + * can be one of: + *

                      ChartConstants.SHAPECOLUMN default + *

                      ChartConstants.SHAPECONEd + *

                      ChartConstants.SHAPECONETOMAX + *

                      ChartConstants.SHAPECYLINDER + *

                      ChartConstants.SHAPEPYRAMID + *

                      ChartConstants.SHAPEPYRAMIDTOMAX + * + * @return int bar shape + */ + val barShape: Int + get() = ChartConstants.SHAPEDEFAULT + + /** + * returns type of marker for this chart, if any + *

                      0 = no marker + *

                      1 = square + *

                      2 = diamond + *

                      3 = triangle + *

                      4 = X + *

                      5 = star + *

                      6 = Dow-Jones + *

                      7 = standard deviation + *

                      8 = circle + *

                      9 = plus sign + * + * @return int marker type + */ + val markerFormat: Int + get() = cf!!.markerFormat + + /** + * returns true if this chart has lines (see Scatter, Line Charts amongst others) + * + * @return + */ + val hasLines: Boolean + get() = cf!!.hasLines + + + /** + * returns true if this chart has drop lines + * + * @return + */ + val hasDropLines: Boolean + get() = cf!!.hasDropLines + + /** + * return chart-type-specific options in XML form + * + * @param nChart 0=default, 1-9= overlay charts + * @return String XML + */ + // chart-type-specific + // governs threed settings and other misc. options + val chartOptionsXML: String + get() { + val sb = StringBuffer() + sb.append(chartobj.optionsXML) + sb.append(cf!!.chartOptionsXML) + return sb.toString() + } + + + /** + * @return truth of "Chart is Three-D" + */ + val isThreeD: Boolean + get() = cf!!.isThreeD(chartobj.chartType) + + /** + * return ThreeD settings for this chart in XML form + * + * @return String XML + */ + val threeDXML: String + get() = cf!!.threeDXML + + + /** + * @return truth of "Chart is Stacked" + */ + var isStacked: Boolean + get() = chartobj.isStacked + set(isstacked) { + chartobj.isStacked = isstacked + } + + /** + * return truth of "Chart is 100% Stacked" + * + * @return + */ + val is100PercentStacked: Boolean + get() = chartobj.is100Percent + + /** + * @return truth of "Chart is Clustered" (Bar/Col only) + */ + val isClustered: Boolean + get() = false + + /** + * returns the SVG for the font style of this object + */ + protected val fontSVG: String + get() = getFontSVG(-1) + + /** + * convert the default shape flag to a user-friendly (OOXML-compliant) String + * + * @param shape int + * @return + */ + val shape: String? + get() { + when (defaultShape) { + 0 -> return "box" + 1 -> return "cylinder" + 256 -> return "pyramid" + 257 -> return "cone" + 512 -> return "pyramidToMax" + 513 -> return "coneToMax" + } + return null + } + + /** + * returns an int representing the space between points in a 3d area, bar or line chart, or 0 if not 3d + * + * @return + */ + val gapDepth: Int + get() = cf!!.gapDepth + + /** + * return the SeriesList record for this chart object + * The SeriesList record maps the series for the chart. + * + * @return + */ + val seriesList: SeriesList + get() = Chart.findRec(cf!!.chartArr, SeriesList::class.java) as SeriesList + + constructor() {} + + constructor(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) { + chartobj = charttype // record that defines the chart type (Bar, Area, Line ...) + this.wb = wb + this.cf = cf + } + + fun setOptions(options: EnumSet) { + // FYI: The CrtLine (section 2.4.68) LineFormat (section 2.4.156) record pairs and the sequences of records that conform to the SHAPEPROPS rule (section 2.1.7.20.1) + // specify the drop lines, high-low lines, series lines, and leader lines for the chart (section 2.2.3.3). + // NO 3d record for: bar of pie, bubble, doughnut, filled radar, pie of pie, radar, or scatter chart group. + // Has Ser (Z) axis: Surface, fStacked==0 & Line, Area, fStacked==0 && fClustered==0 && Bar (Col) (Must also have ThreeD record) + // 2 Value Axes: Scatter, Bubble + // NO Axes: Pie, Doughnut, PieOfPie, BarOfPie + + + val ca = this.parentChart!!.axes + val chartType = this.chartType + if (options.contains(ChartOptions.STACKED)) + // bar/col types, line, area + chartobj.isStacked = true + + if (options.contains(ChartOptions.PERCENTSTACKED)) + // bar/col types, line, area + chartobj.is100Percent = true + + if (options.contains(ChartOptions.CLUSTERED)) + // bar/col only + cf!!.setIsClustered(true) + + if (options.contains(ChartOptions.SERLINES)) + // bar, line, stock + cf!!.addChartLines(ChartLine.TYPE_SERIESLINE.toInt()) + + if (options.contains(ChartOptions.HILOWLINES)) + // bar, OfPie + cf!!.addChartLines(ChartLine.TYPE_HILOWLINE.toInt()) + + if (options.contains(ChartOptions.DROPLINES)) + // Surface chart + cf!!.addChartLines(ChartLine.TYPE_DROPLINE.toInt()) + + if (options.contains(ChartOptions.UPDOWNBARS)) + // line, area,stock + cf!!.addUpDownBars() + + if (options.contains(ChartOptions.HASLINES)) + // line, scatter ... + cf!!.setHasLines() + + if (options.contains(ChartOptions.SMOOTHLINES)) + // line, scatter, radar + cf!!.hasSmoothLines = true + // HANDLE FILLED for radar + // HANDLE bubble 3d + // HANDLE bar shapes *** + + cf!!.setChartObject(chartobj) + var use3Ddefaults = true // init 3D record with default values for specific chart type + var threeD = cf!!.getThreeDRec(false) + if (threeD == null) { + if (options.contains(ChartOptions.THREED) || chartType == ChartConstants.SURFACECHART) { // surface charts ALWAYS have a 3 record as does pyramid, cone and cylinder charts + if (chartType != ChartConstants.BUBBLECHART && chartType != ChartConstants.SCATTERCHART) + // supposed to be also donught, radar as well ... + threeD = this.initThreeD(chartType) + else if (chartType == ChartConstants.BUBBLECHART) { // scatter charts have no 3d option + cf!!.setHas3DBubbles(true) + } + } + } else + // 3D record already set (via OOXML) - do not use defaults + use3Ddefaults = false + when (chartType) { + ChartConstants.BARCHART, ChartConstants.COLCHART -> { + if (use3Ddefaults && threeD != null) { + threeD.setChartOption("AnRot", "20") + threeD.setChartOption("AnElev", "15") + threeD.setChartOption("TwoDWalls", "true") + threeD.setChartOption("ThreeDScaling", "false") + threeD.setChartOption("Cluster", "false") + threeD.setChartOption("PcDepth", "100") + threeD.setChartOption("PcDist", "30") + threeD.setChartOption("PcGap", "150") + threeD.setChartOption("PcHeight", "72") // ?????? + threeD.setChartOption("Perspective", "false") + if (options.contains(ChartOptions.CLUSTERED)) { + threeD.setChartOption("Cluster", "true") + threeD.setChartOption("PcHeight", "62") // ?????? + if (chartType == ChartConstants.COLCHART) + threeD.setChartOption("Perspective", "true") + else { // bar chart + threeD.setChartOption("Perspective", "false") + threeD.setChartOption("ThreeDScaling", "true") + threeD.setChartOption("PcHeight", "150") // ?????? + } + } + } + ca!!.setChartOption(ChartConstants.XAXIS, "AddArea", "true") + ca.setChartOption(ChartConstants.YAXIS, "AddArea", "true") + if (!isStacked && !isClustered) { + ca.createAxis(ChartConstants.ZAXIS) + ca.setChartOption(ChartConstants.ZAXIS, "AddArea", "true") + } + } + ChartConstants.LINECHART -> if (options.contains(ChartOptions.THREED)) { + ca!!.setChartOption(ChartConstants.XAXIS, "AddArea", "true") + ca.setChartOption(ChartConstants.YAXIS, "AddArea", "true") + if (!isStacked) { + ca.createAxis(ChartConstants.ZAXIS) + ca.setChartOption(ChartConstants.ZAXIS, "AddArea", "true") + } + } + ChartConstants.STOCKCHART -> { + cf!!.addChartLines(ChartLine.TYPE_HILOWLINE.toInt()) + cf!!.setMarkers(0) + } + ChartConstants.SCATTERCHART -> if (!options.contains(ChartOptions.HASLINES)) { + cf!!.setHasLines(5) // no line style -- doesn't appear to work + } + ChartConstants.AREACHART -> { + if (use3Ddefaults && threeD != null) { + threeD.setChartOption("AnRot", "20") + threeD.setChartOption("TwoDWalls", "true") + threeD.setChartOption("ThreeDScaling", "false") + threeD.setChartOption("Perspective", "true") + } + this.setChartOption("Percentage", "25") + this.setChartOption("SmoothedLine", "true") + ca!!.setChartOption(ChartConstants.XAXIS, "CrossBetween", "false") + if (options.contains(ChartOptions.THREED)) { + ca.setChartOption(ChartConstants.XAXIS, "AddArea", "true") + ca.setChartOption(ChartConstants.YAXIS, "AddArea", "true") + if (!isStacked) { + ca.createAxis(ChartConstants.ZAXIS) + ca.setChartOption(ChartConstants.ZAXIS, "AddArea", "true") + } + } + } + ChartConstants.BUBBLECHART -> if (options.contains(ChartOptions.THREED)) { + this.setChartOption("Percentage", "25") + this.setChartOption("SmoothedLine", "true") + this.setChartOption("ThreeDBubbles", "true") + } + ChartConstants.PIECHART // Pie + -> { + cf!!.varyColor = true // Should be true for all pie charts ... + if (options.contains(ChartOptions.EXPLODED)) { + this.setChartOption("SmoothedLine", "true") + this.setChartOption("Percentage", "25") + } + if (use3Ddefaults && options.contains(ChartOptions.THREED)) + this.setChartOption("AnRot", "236") + ca!!.removeAxes() + } + ChartConstants.DOUGHNUTCHART -> { + if (options.contains(ChartOptions.EXPLODED)) { + this.setChartOption("SmoothedLine", "true") + this.setChartOption("Percentage", "25") + } + cf!!.varyColor = true // Should be true for all pie charts ... + ca!!.removeAxes() + } + ChartConstants.SURFACECHART // NOTE: For Surface charts, non-threeD==Contour + -> { + if (use3Ddefaults && threeD != null) { // shouldn't + threeD.setChartOption("Cluster", "false") + threeD.setChartOption("TwoDWalls", "true") + threeD.setChartOption("ThreeDScaling", "true") + threeD.setChartOption("Perspective", "true") + } + ca!!.setChartOption(ChartConstants.XAXIS, "AddArea", "true") + ca.setChartOption(ChartConstants.XAXIS, "AreaFg", "8") + ca.setChartOption(ChartConstants.XAXIS, "AreaBg", "78") + ca.setChartOption(ChartConstants.YAXIS, "AddArea", "true") + ca.setChartOption(ChartConstants.YAXIS, "AreaFg", "22") + ca.setChartOption(ChartConstants.YAXIS, "AreaBg", "78") + ca.createAxis(ChartConstants.ZAXIS) + if (use3Ddefaults && options.contains(ChartOptions.THREED)) { // "regular" 3d surface + threeD!!.setChartOption("AnElev", "15") + threeD.setChartOption("AnRot", "20") + threeD.setChartOption("PcDepth", "100") + threeD.setChartOption("PcDist", "30") + threeD.setChartOption("PcGap", "150") + threeD.setChartOption("PcHeight", "50") // ?????? + } else if (use3Ddefaults) { // contour (non-3d) + threeD!!.setChartOption("AnElev", "90") + threeD.setChartOption("AnRot", "0") + threeD.setChartOption("PcDepth", "100") + threeD.setChartOption("PcDist", "0") + threeD.setChartOption("PcGap", "150") + threeD.setChartOption("PcHeight", "50") // ?????? + } + if (options.contains(ChartOptions.WIREFRAME)) + (chartobj as Surface).isWireframe = true + if (!options.contains(ChartOptions.WIREFRAME) && options.contains(ChartOptions.THREED)) { + chartobj.setChartOption("ColorFill", "true") + chartobj.setChartOption("Shading", "true") + } else if (options.contains(ChartOptions.WIREFRAME)) { // conotur (flat, non-3d) wireframe + chartobj.setChartOption("Shading", "true") + } else + // contour filled (i.e. plain Surface) + chartobj.setChartOption("ColorFill", "true") + } + ChartConstants.RADARCHART -> if (options.contains(ChartOptions.FILLED)) + (this as RadarChart).isFilled = true + ChartConstants.PYRAMIDCHART, ChartConstants.CONECHART, ChartConstants.CYLINDERCHART, ChartConstants.PYRAMIDBARCHART, ChartConstants.CONEBARCHART, ChartConstants.CYLINDERBARCHART -> { + // Shaped Bar/Col charts are all 3d + if (threeD == null) threeD = this.initThreeD(chartType) + if (use3Ddefaults) { + threeD.setChartOption("AnElev", "15") + threeD.setChartOption("AnRot", "20") + threeD.setChartOption("Cluster", "true") // only for regular pyramid charts; stacked, etc. will alter + threeD.setChartOption("TwoDWalls", "true") + if (!options.contains(ChartOptions.THREED)) { + threeD.setChartOption("ThreeDScaling", "false") + threeD.setChartOption("Cluster", "true") + } + threeD.setChartOption("Perspective", "false") + threeD.setChartOption("PcDepth", "100") + threeD.setChartOption("PcDist", "30") + threeD.setChartOption("PcGap", "150") + threeD.setChartOption("PcHeight", "52") // ?????? + } + if (chartType == ChartConstants.PYRAMIDCHART || chartType == ChartConstants.PYRAMIDBARCHART) + cf!!.barShape = ChartConstants.SHAPEPYRAMID + else if (chartType == ChartConstants.CONECHART || chartType == ChartConstants.CONEBARCHART) + cf!!.barShape = ChartConstants.SHAPECONE + else if (chartType == ChartConstants.CYLINDERCHART || chartType == ChartConstants.CYLINDERBARCHART) + cf!!.barShape = ChartConstants.SHAPECYLINDER + } + } + } + + /** + * specifies whether + * the color for each data point and the color and type for each data marker + * vary + * + * @param b + */ + protected fun setVaryColor(b: Boolean) { + cf!!.varyColor = b + } + + fun addLegend(l: Legend) { + dataLegend = l + } + + /** + * gets the chart-type specific OOXML representation (representing child element of plotArea element) + * + * @return + */ + open fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + return null + } + + @Throws(JSONException::class) + fun getJSON(s: ChartSeries, wbh: WorkBookHandle, minMax: Array): JSONObject { + val chartObjectJSON = JSONObject() + + // Type JSON + chartObjectJSON.put("type", this.typeJSON) + + // Deal with Series + var yMax = 0.0 + var yMin = 0.0 + var nSeries = 0 + val seriesJSON = JSONArray() + val seriesCOLORS = JSONArray() + try { + val series = s.seriesRanges + val scolors = s.seriesBarColors + for (i in series!!.indices) { + val seriesvals = CellRange.getValuesAsJSON(series[i].toString(), wbh) + // must trap min and max for axis tick and units + nSeries = Math.max(nSeries, seriesvals.length()) + for (j in 0 until seriesvals.length()) { + try { + yMax = Math.max(yMax, seriesvals.getDouble(j)) + yMin = Math.min(yMin, seriesvals.getDouble(j)) + } catch (n: NumberFormatException) { + } + + } + seriesJSON.put(seriesvals) + seriesCOLORS.put(scolors!![i]) + } + chartObjectJSON.put("Series", seriesJSON) + chartObjectJSON.put("SeriesFills", seriesCOLORS) + } catch (je: JSONException) { + // TODO: Log error + } + + minMax[0] = yMin + minMax[1] = yMax + minMax[2] = nSeries + return chartObjectJSON + } + + /** + * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param s ChartSeries - holds legends, categories, seriesdata ... + * @return String svg + */ + open fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + return "" + } + + /** + * returns the SVG-ready data label for the given set of data label options + * TODO: get separator character + * + * @param datalabel int[] Data label options (indexed by current series # s) + *

                      can be one or more of: + *

                      VALUELABEL= 0x1; + *

                      VALUEPERCENT= 0x2; + *

                      CATEGORYPERCENT= 0x4; + *

                      SMOOTHEDLINE= 0x8; + *

                      CATEGORYLABEL= 0x10; + *

                      BUBBLELABEL= 0x20; + *

                      SERIESLABEL= 0x40; + * @param series ArrayList of series and category data: structure: category data, series 0-n data, series colors, series Labels (Eventually will be an object) + * @param val double current series value + * @param percentage double percentage value (pie charts only) + * @param s int current series # + * @param cat string current cat + * @return + */ + fun getSVGDataLabels(datalabels: IntArray, axisMetrics: HashMap, `val`: Double, percentage: Double, s: Int, legends: Array, cat: String): String? { + + if (s >= datalabels.size) + // can happen with Pie-style charts + return null + val showValueLabel = datalabels[s] and AttachedLabel.VALUELABEL == AttachedLabel.VALUELABEL + val showValuePercent = datalabels[s] and AttachedLabel.VALUEPERCENT == AttachedLabel.VALUEPERCENT + val showCatPercent = datalabels[s] and AttachedLabel.CATEGORYPERCENT == AttachedLabel.CATEGORYPERCENT + val showCategories = datalabels[s] and AttachedLabel.CATEGORYLABEL == AttachedLabel.CATEGORYLABEL + val showBubbleLabel = datalabels[s] and AttachedLabel.BUBBLELABEL == AttachedLabel.BUBBLELABEL + val showValue = datalabels[s] and AttachedLabel.VALUE == AttachedLabel.VALUE + if (showValue || showCategories || showValueLabel || showValuePercent || showBubbleLabel) { + var l = "" + if (showValueLabel) + l += legends[s] + " " // series names + if (showCategories) + l += CellFormatFactory.fromPatternString( + axisMetrics["xPattern"] as String).format(cat) + " " // categories + if (showValue || showBubbleLabel) { + l += CellFormatFactory.fromPatternString( + axisMetrics["yPattern"] as String).format(`val`.toString()) + /*try { + int v= new Double(val).intValue(); + if (v==val) + l+=v + " "; + else + l+=val + " "; + } catch (Exception e) { + l+= val + " "; + }*/ + } + if (showValuePercent) + l += Math.round(percentage * 100).toInt().toString() + "%" + return l + } + return null + } + + /** Show or remove Data Table for Chart + * NOTE: METHOD IS STILL EXPERIMENTAL + * @param bShow + * + * public void showDataTable(boolean bShow) { + * int i= Chart.findRecPosition(chartArr, Dat.class); + * if (bShow) { + * if (i==-1) { // add Dat + * Dat d= (Dat)Dat.getPrototype(true); // create data table + * i= Chart.findRecPosition(chartArr, AxisParent.class); + * this.chartArr.add(++i, d); + * } + * } else if (i > 0) { + * chartArr.remove(i); // remove Dat - Data Table options + all associated recs + * } + * } + */ + + + /** + * return truth if Chart has a data legend key showing + * + * @return + */ + fun hasDataLegend(): Boolean { + return dataLegend != null + } + + /** + * show or hide chart legend key + * + * @param bShow boolean show or hide + * @param vertical boolean show as vertical or horizontal + */ + fun showLegend(bShow: Boolean, vertical: Boolean) { + if (bShow && dataLegend == null) { + dataLegend = Legend.createDefaultLegend(wb) + dataLegend!!.parentChart = chartobj.parentChart + for (j in dataLegend!!.chartArr.indices) + (dataLegend!!.chartArr[j] as GenericChartObject).parentChart = chartobj.parentChart + dataLegend!!.setVertical(vertical) + cf!!.chartArr.add(dataLegend) + } else if (bShow) { + dataLegend!!.setVertical(vertical) + } else if (dataLegend != null) { + val i = Chart.findRecPosition(cf!!.chartArr, Legend::class.java) + cf!!.chartArr.removeAt(i) + dataLegend = null + } + } + + /** + * sets this chart (Area, Line, Scatter) to have drop lines + */ + fun setHasDropLines() { + cf!!.setHasDropLines() + } + + /** + * look up a generic chart option + * + * @param op + * @return + */ + fun getChartOption(op: String): String? { + return chartobj.getChartOption(op) ?: return cf!!.getChartOption(op) + } + + /** + * interface for setting chart-type-specific options + * in a generic fashion + * + * @param op option + * @param val value + * @see OpenXLS.handleChartElement + * + * @see ChartHandle.getXML + */ + fun setChartOption(op: String, `val`: String): Boolean { + if (!chartobj.setChartOption(op, `val`)) + // if not handled, + cf!!.setOption(op, `val`) + return true + } + + /** + * returns the 3d record of the desired chart + * + * @param chartType one of the chart type constants + * @return + */ + fun initThreeD(charttype: Int): ThreeD { + /* TODO: test if this is necessary AxisParent ap = this.getAxisParent(); + try { + // first thing, remove the PlotArea and Frame - don't know why but is necessary!! + int x= Chart.findRecPosition(ap.chartArr, PlotArea.class); + if (x!=-1) ap.chartArr.remove(x); + x= Chart.findRecPosition(ap.chartArr, Frame.class); + if (x!=-1) ap.chartArr.remove(x); + } catch (Exception e) {}*/ + val td = cf!!.getThreeDRec(true) + td!!.setIsPie(charttype == ChartConstants.PIECHART || charttype == ChartConstants.DOUGHNUTCHART) + return td + } + + /** + * return the 3d record of the chart + *

                      Creates if not present, if bCreate is true + * + * @return + */ + fun getThreeDRec(bCreate: Boolean): ThreeD? { + return cf!!.getThreeDRec(bCreate) + } + + fun setIs100Psercent(ispercentage: Boolean) { + chartobj.is100Percent = ispercentage + } + + fun addLegend() { + if (dataLegend == null) { + // TODO: ADD LEGEND to cf + //int i = Chart.findRecPosition(cf.chartArr, Legend.class); + } + } + + + /** + * returns the SVG for the font style of this object + * + * @param font size in pt + */ + protected fun getFontSVG(sz: Int): String { + // io.starter.formats.XLS.Font f = this.getFont(); // this is not correct! + // if(f == null) // return a default + return "font-family='Arial' font-size='$sz' fill='$darkColor' " + + //return f.getSVG(); + } + + /** + * Convert the user-friendly OOXML shape string to 2003-v int shape flag + * + * @param shape + * @return + */ + fun convertShape(shape: String): Int { + defaultShape = 0 + if (shape == "box") + defaultShape = 0 + if (shape == "cone") + // 1 1 + defaultShape = 257 + if (shape == "coneToMax") + // 1 2 + defaultShape = 513 + if (shape == "cylinder") + // 1 0 + defaultShape = 1 + if (shape == "pyramid") + // 0 1 + defaultShape = 256 + if (shape == "pyramidToMax") + // 0 2 + defaultShape = 512 + return defaultShape + } + + /** + * @param fName + */ + fun WriteMainChartRecs(fName: String) { + class util { + @Throws(IOException::class) + fun writeRecs(b: BiffRec?, writer: BufferedWriter, level: Int) { + val tabs = "\t\t\t\t\t\t\t\t\t\t" + if (b == null) return + writer.write(tabs.substring(0, level) + b.javaClass.toString().substring(b.javaClass.toString().lastIndexOf('.') + 1)) + if (b is io.starter.formats.XLS.charts.SeriesText) + writer.write("\t[$b]") + else if (b is MSODrawing) { + writer.write("\t[$b]") + // writer.write("\t[" + ByteTools.getByteDump(b.getData(), 0).substring(11)+ "]"); + writer.write(b.debugOutput()) + writer.write("\t[" + ByteTools.getByteDump(b.data, 0).substring(11) + "]") + } else if (b is Obj) { + writer.write(b.debugOutput()) + } else if (b is Label) { + writer.write("\t[" + b.stringVal + "]") + } else + // all else, write bytes + writer.write("\t[" + ByteTools.getByteDump(ByteTools.shortToLEBytes(b.opcode), 0) + "][" + ByteTools.getByteDump(b.data, 0).substring(11) + "]") + writer.newLine() + try { + if ((b as GenericChartObject).chartArr.size > 0) { + val chartArr = b.chartArr + for (i in chartArr.indices) { + writeRecs(chartArr[i], writer, level + 1) + + } + } + } catch (ce: ClassCastException) { + } + + } + } + + + try { + val f = java.io.File(fName) + var writer: BufferedWriter? = BufferedWriter(FileWriter(f)) + val u = util() + + val v = this.parentChart!!.allSeries + for (i in v.indices) { + u.writeRecs(v[i] as BiffRec, writer, 0) + } + writer!!.newLine() + val chartArr = this.cf!!.chartArr + for (i in chartArr.indices) { + u.writeRecs(chartArr[i], writer, 0) + } + + writer.flush() + writer.close() + writer = null + } catch (e: Exception) { + } + + } + + companion object { + /** + * serialVersionUID + */ + private const val serialVersionUID = -7862828186455339066L + + /** + * creates a new chart type object of the desired chart type. Will set options if already set via ChartFormat + * + * @param chartType + * @param parentChart + * @param cf + * @return + */ + fun create(chartType: Int, parentChart: Chart, cf: ChartFormat): ChartType? { + val co = ChartType.createUnderlyingChartObject(chartType, parentChart, cf) //, ((ChartType)chartobj.get(0))); + + return createChartTypeObject(co, cf, parentChart.workBook) + } + + + /** + * create and return the appropriate ChartType for the given + * chart record (Bar, Radar, Line, etc) + * + * @param ch GenericChartObject representing the Chart Type + *

                      Must be one of: Area, Bar, Line, Surface, Pie, Radar, Scatter, BopPop, RadarArea + * @return ChartType chart object + */ + fun createChartTypeObject(ch: GenericChartObject?, cf: ChartFormat?, wb: WorkBook?): ChartType? { + if (cf == null) + // TODO: throw exception + return null + + val barshape = cf.barShape + val threeD = cf.isThreeD(ch!!.chartType) + val isStacked = ch.isStacked + val is100Percent = ch.is100Percent + + when (ch.chartType) { + ChartConstants.COLCHART -> { + if (barshape == ChartConstants.SHAPEDEFAULT) { // regular column + if (isStacked || is100Percent) + return StackedColumn(ch, cf, wb) + return if (threeD) Col3DChart(ch, cf, wb) else ColChart(ch, cf, wb) + } else if (barshape == ChartConstants.SHAPECONE) { // Cone chart always 3d + return ConeChart(ch, cf, wb) + } else if (barshape == ChartConstants.SHAPECYLINDER) { // Cylinder chart always 3d + return CylinderChart(ch, cf, wb) + } else if (barshape == ChartConstants.SHAPEPYRAMID) { // Pyramid chart alwasy 3d + return PyramidChart(ch, cf, wb) + } + if (barshape == ChartConstants.SHAPEDEFAULT) { // regular Bar + return if (threeD) Bar3DChart(ch, cf, wb) else BarChart(ch, cf, wb) + } else if (barshape == ChartConstants.SHAPECONE) { // ConeBarchart always 3d + return ConeBarChart(ch, cf, wb) + } else if (barshape == ChartConstants.SHAPECYLINDER) { // CylinderBar chart always 3d + return CylinderBarChart(ch, cf, wb) + } else if (barshape == ChartConstants.SHAPEPYRAMID) { // PyramidBar chart always 3d + return PyramidBarChart(ch, cf, wb) + } + return if (threeD) Line3DChart(ch, cf, wb) else LineChart(ch, cf, wb) + } + ChartConstants.BARCHART -> { + if (barshape == ChartConstants.SHAPEDEFAULT) { + return if (threeD) Bar3DChart(ch, cf, wb) else BarChart(ch, cf, wb) + } else if (barshape == ChartConstants.SHAPECONE) { + return ConeBarChart(ch, cf, wb) + } else if (barshape == ChartConstants.SHAPECYLINDER) { + return CylinderBarChart(ch, cf, wb) + } else if (barshape == ChartConstants.SHAPEPYRAMID) { + return PyramidBarChart(ch, cf, wb) + } + return if (threeD) Line3DChart(ch, cf, wb) else LineChart(ch, cf, wb) + } + ChartConstants.LINECHART -> { + return if (threeD) Line3DChart(ch, cf, wb) else LineChart(ch, cf, wb) + } + ChartConstants.STOCKCHART -> return StockChart(ch, cf, wb) + ChartConstants.PIECHART -> { + cf.setPercentage(0) + return if (threeD) Pie3dChart(ch, cf, wb) else PieChart(ch, cf, wb) + } + ChartConstants.AREACHART -> { + if (ch.isStacked) + return StackedAreaChart(ch, cf, wb) + return if (threeD) Area3DChart(ch, cf, wb) else AreaChart(ch, cf, wb) + } + ChartConstants.SCATTERCHART -> return ScatterChart(ch, cf, wb) + ChartConstants.RADARCHART -> return RadarChart(ch, cf, wb) + ChartConstants.SURFACECHART -> { + return if ((ch as Surface).is3d) Surface3DChart(ch, cf, wb) else SurfaceChart(ch, cf, wb) + } + ChartConstants.DOUGHNUTCHART -> { + cf.setPercentage(0) + return DoughnutChart(ch, cf, wb) + } + ChartConstants.BUBBLECHART -> return BubbleChart(ch, cf, wb) + ChartConstants.RADARAREACHART -> return RadarAreaChart(ch, cf, wb) + ChartConstants.OFPIECHART -> { + cf.setPercentage(0) + return OfPieChart(ch, cf, wb) + } + else -> return null + } + } + + /** + * create a new low-level chart object which determines the ChartTypeObject, and update the existing chartobj + * + * @param chartType + * @param parentchart + * @param chartgroup + * @return + */ + fun createUnderlyingChartObject(chartType: Int, parentchart: Chart, cf: ChartFormat?): GenericChartObject? {//, ChartType chartobj) { + var c: GenericChartObject? = null + when (chartType) { + ChartConstants.COLCHART // column-type + -> { + val col = Bar.prototype as Bar? + col!!.setAsColumnChart() + c = col + } + + ChartConstants.BARCHART // bar-type + -> { + val bar = Bar.prototype as Bar? + bar!!.setAsBarChart() + c = bar + } + + ChartConstants.PIECHART // Pie + -> { + val p = Pie.prototype as Pie? + p!!.setAsPieChart() + c = p + } + + ChartConstants.STOCKCHART -> { + val st = Line.prototype as Line? + st!!.setAsStockChart() + c = st + } + + ChartConstants.LINECHART -> { + val l = Line.prototype as Line? + c = l + } + + ChartConstants.AREACHART -> { + val a = Area.prototype as Area? + c = a + } + + ChartConstants.SCATTERCHART -> { + val s = Scatter.prototype as Scatter? + s!!.setAsScatterChart() + c = s + } + + ChartConstants.RADARCHART -> { + val r = Radar.prototype as Radar? + c = r + } + + ChartConstants.SURFACECHART -> { + val su = Surface.prototype as Surface? + c = su + } + + ChartConstants.DOUGHNUTCHART -> { + val d = Pie.prototype as Pie? + d!!.setAsDoughnutChart() + c = d + } + + ChartConstants.BUBBLECHART -> { + val bu = Scatter.prototype as Scatter? + bu!!.setAsBubbleChart() + c = bu + } + + ChartConstants.RADARAREACHART -> { + val ra = RadarArea.prototype as RadarArea? + c = ra + } + + // note that, for the below chart types, the underlying type will be either COL or BAR + // which actual chart is determined also by the bar shape + ChartConstants.PYRAMIDCHART -> { + val pyramid = Bar.prototype as Bar? + pyramid!!.setAsColumnChart() + cf!!.barShape = ChartConstants.SHAPEPYRAMID + c = pyramid + } + + ChartConstants.CONECHART -> { + val cone = Bar.prototype as Bar? + cone!!.setAsColumnChart() + cf!!.barShape = ChartConstants.SHAPECONE + c = cone + } + + ChartConstants.CYLINDERCHART -> { + val cy = Bar.prototype as Bar? + cy!!.setAsColumnChart() + cf!!.barShape = ChartConstants.SHAPECYLINDER + c = cy + } + + ChartConstants.PYRAMIDBARCHART -> { + val pb = Bar.prototype as Bar? + pb!!.setAsBarChart() + cf!!.barShape = ChartConstants.SHAPEPYRAMID + c = pb + } + + ChartConstants.CONEBARCHART -> { + val cb = Bar.prototype as Bar? + cb!!.setAsBarChart() + cf!!.barShape = ChartConstants.SHAPECONE + c = cb + } + + ChartConstants.CYLINDERBARCHART -> { + val cyb = Bar.prototype as Bar? + cyb!!.setAsBarChart() + cf!!.barShape = ChartConstants.SHAPECYLINDER + c = cyb + } + + ChartConstants.OFPIECHART -> { + val ofpie = Boppop.prototype as Boppop? + c = ofpie + } + } + if (c != null) { + c.parentChart = parentchart + } + return c + } + + + /** + * parse the chart object OOXML element (barchart, area3DChart, etc.) and create the corresponding chart type objects + * + * @param xpp XmlPullParser + * @param wbh workBookHandle + * @param parentChart parent Chart Object + * @param nChart chart grouping number, 0 for default, 1-9 for overlay + * @return + */ + fun parseOOXML(xpp: XmlPullParser, wbh: WorkBookHandle, parentChart: Chart, nChart: Int): ChartType? { + try { + val endTag = xpp.name + var tnm = xpp.name + var eventType = xpp.eventType + var chartType = ChartConstants.BARCHART + val ca = parentChart.axes + + val lastTag = java.util.Stack() // keep track of element hierarchy + val cf = parentChart.getChartOjectParent(nChart) + + val options = EnumSet.noneOf(ChartOptions::class.java) // chart-specific options such as threed, stacked ... + if (tnm == "bubble3D") { // bubble3D tag appears for each series in 3D bubble chart + options.add(ChartOptions.THREED) + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.BARCHART]) { + chartType = ChartConstants.BARCHART + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.LINECHART]) { + chartType = ChartConstants.LINECHART + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.PIECHART]) { + chartType = ChartConstants.PIECHART + parentChart.axes!!.removeAxes() + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.AREACHART]) { + chartType = ChartConstants.AREACHART + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.SCATTERCHART]) { + chartType = ChartConstants.SCATTERCHART + ca!!.removeAxis(ChartConstants.XAXIS) + ca.createAxis(ChartConstants.XVALAXIS) + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.RADARCHART]) { + chartType = ChartConstants.RADARCHART + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.SURFACECHART]) { + chartType = ChartConstants.SURFACECHART + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.DOUGHNUTCHART]) { + chartType = ChartConstants.DOUGHNUTCHART + parentChart.axes!!.removeAxes() + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.BUBBLECHART]) { + chartType = ChartConstants.BUBBLECHART + ca!!.removeAxis(ChartConstants.XAXIS) + ca.createAxis(ChartConstants.XVALAXIS) + } else if (tnm == OOXMLConstants.threeDchartTypes[ChartHandle.BARCHART]) { + chartType = ChartConstants.BARCHART + options.add(ChartOptions.THREED) + } else if (tnm == OOXMLConstants.threeDchartTypes[ChartHandle.LINECHART]) { + chartType = ChartConstants.LINECHART + options.add(ChartOptions.THREED) + } else if (tnm == OOXMLConstants.threeDchartTypes[ChartHandle.PIECHART]) { + chartType = ChartConstants.PIECHART + options.add(ChartOptions.THREED) + parentChart.axes!!.removeAxes() + } else if (tnm == OOXMLConstants.threeDchartTypes[ChartHandle.AREACHART]) { + chartType = ChartConstants.AREACHART + options.add(ChartOptions.THREED) + } else if (tnm == OOXMLConstants.threeDchartTypes[ChartHandle.SCATTERCHART]) { + chartType = ChartConstants.SCATTERCHART + ca!!.removeAxis(ChartConstants.XAXIS) + ca.createAxis(ChartConstants.XVALAXIS) + options.add(ChartOptions.THREED) + } else if (tnm == OOXMLConstants.threeDchartTypes[ChartHandle.RADARCHART]) { + chartType = ChartConstants.RADARCHART + options.add(ChartOptions.THREED) + } else if (tnm == OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART]) { + chartType = ChartConstants.SURFACECHART + options.add(ChartOptions.THREED) + } else if (tnm == OOXMLConstants.threeDchartTypes[ChartHandle.DOUGHNUTCHART]) { + chartType = ChartConstants.DOUGHNUTCHART + options.add(ChartOptions.THREED) + parentChart.axes!!.removeAxes() + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.OFPIECHART]) { + chartType = ChartConstants.OFPIECHART + ca!!.removeAxis(ChartConstants.XAXIS) + ca.removeAxis(ChartConstants.YAXIS) + } else if (tnm == OOXMLConstants.twoDchartTypes[ChartHandle.STOCKCHART]) { + chartType = ChartConstants.STOCKCHART + } + + val co = ChartType.createUnderlyingChartObject(chartType, parentChart, cf) //, ((ChartType)chartobj.get(0))); + cf!!.setChartObject(co) // sets the chart format (parent of chart item) to the specific chart item + // exception for surface charts in 3d + if (chartType == ChartConstants.SURFACECHART && tnm == OOXMLConstants.threeDchartTypes[ChartHandle.SURFACECHART]) + (co as Surface).is3d = true + var ct = ChartType.createChartTypeObject(co, cf, parentChart.workBook) + parentChart.addChartType(ct, nChart) + + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + tnm = xpp.name + lastTag.push(tnm) + var v: String? = null + try { + v = xpp.getAttributeValue(0) + } catch (e: IndexOutOfBoundsException) { + } + + // TODO: + // hasMarkers **** + // dLbls + // smooth -- line + // marker -- line + // bandfmts -- surface, surface3d + // bubble3D, + // bubbleScale + // showNegBubbles + // sizeRepresents + // custSplit -- OfPie + if (tnm == "grouping") { //This element specifies the type of grouping for a column, line, or area chart + 3d versions + if (v == "stacked") + co!!.isStacked = true + else if (v == "percentStacked") + co!!.is100Percent = true + else if (v == "clustered") + cf.setIsClustered(true) // bar/col only + else if (v == "standard") { // for Line, Line3d, Area, Area3d and Bar3d, Col3d - does not appear valid for Bar 2d and Col 2d + co!!.isStacked = false + co.is100Percent = false + } + } else if (tnm == "barDir") { // + if (v == "col") { + (co as Bar).setAsColumnChart() + ct = ChartType.createChartTypeObject(co, cf, parentChart.workBook) + parentChart.addChartType(ct, nChart) + } + } else if (tnm == "shape") { // bar3d + cf.barShape = ct!!.convertShape(v!!) + if (ct.defaultShape != 0) { + ct = ChartType.createChartTypeObject(co, cf, parentChart.workBook) + parentChart.addChartType(ct, nChart) + } + } else if (tnm == "radarStyle") { + if (v == "filled") { + (ct as RadarChart).isFilled = true + } else if (v == "marker") { + + } + } else if (tnm == "wireframe") { // surface + (co as Surface).isWireframe = v != null && v == "1" + } else if (tnm == "scatterStyle") { + if (v == "lineMarker") { + cf.setHasLines() + } else if (v == "smoothMarker") { + cf.hasSmoothLines = true + } else if (v == "marker") + else if (v == "line") + cf.setHasLines() + else if (v == "smooth") + cf.hasSmoothLines = true//cf.seth; + } else if (tnm == "varyColors") { + cf.varyColor = xpp.getAttributeValue(0) == "1" + } else if (tnm == "dropLines") { + val cl = cf.addChartLines(ChartLine.TYPE_DROPLINE.toInt()) + cl.parseOOXML(xpp, lastTag, cf, wbh) + } else if (tnm == "hiLowLines") { + val cl = cf.addChartLines(ChartLine.TYPE_HILOWLINE.toInt()) + cl.parseOOXML(xpp, lastTag, cf, wbh) + } else if (tnm == "upDownBars") { + cf.parseUpDownBarsOOXML(xpp, lastTag, wbh) + } else if (tnm == "serLines") { + val cl = cf.addChartLines(ChartLine.TYPE_SERIESLINE.toInt()) + cl.parseOOXML(xpp, lastTag, cf, wbh) + } else if (tnm == "overlap") { // bar + co!!.setChartOption("Overlap", v) + } else if (tnm == "gapWidth") { + co!!.setChartOption("Gap", v) // bar + } else if (tnm == "ofPieType") { + (co as Boppop).isPieOfPie = "pie" == v + } else if (tnm == "gapDepth") { + cf.gapDepth = Integer.valueOf(v!!) // bar3d, area3d, line3d + } else if (tnm == "firstSliceAn") { + (co as Pie).setAnStart(Integer.valueOf(v!!)) // pie or doughnut + } else if (tnm == "holeSize") { + (co as Pie).doughnutSize = Integer.valueOf(v!!) // pie or doughnut + } else if (tnm == "secondPieSize") { + (co as Boppop).secondPieSize = Integer.valueOf(v!!) // OfPie (Pie of Pie, Bar of Pie) + } else if (tnm == "splitType") { + (co as Boppop).setSplitType(v) // OfPie (Pie of Pie, Bar of Pie) + } else if (tnm == "splitPos") { + (co as Boppop).splitPos = Integer.valueOf(v!!) // OfPie (Pie of Pie, Bar of Pie) + } else if (tnm == "ser") { + val s = ChartSeries.parseOOXML(xpp, wbh, ct, false, lastTag) + } else if (tnm == "dLbls") { + } else if (tnm == "marker") { // line only? + // m= (Marker) Marker.parseOOXML(xpp, lastTag).cloneElement(); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (xpp.name == endTag) + break + + } + eventType = xpp.next() + } + return ct + + // lastTag.pop(); // chart type tag will be added in parseOOXML + //mychart.getChartObject(nChart).parseOOXML(xpp, this.wbh, lastTag); + } catch (e: Exception) { + Logger.logErr("ChartType.parseChartType: $e") + } + + return null + } + + // SVG Convenience Methods + fun getScript(range: String): String { + return "onmouseover='highLight(evt); showRange(\"$range\");' onclick='handleClick(evt);' onmouseout='restore(evt); hideRange();'" + // return "onmouseover='highLight(evt);' onclick='handleClick(evt);' onmouseout='restore(evt);'"; + } + + val fillOpacity: String + get() = ".75" + + + val textColor: String + get() = "#222222" + + val lightColor: String + get() = "#CCCCCC" + + val mediumColor: String + get() = "#555555" + + + val darkColor: String + get() = "#333333" + + /** + * Returns SVG used to define font for data labels + * TODO: read correct value from chart recs + * + * @return + */ + val dataLabelFontSVG: String + get() { + val sz = 9 + return "font-family='Arial' font-size='" + sz + "' fill='" + ChartType.darkColor + "' " + } + + /** + * returns the SVG for the font style of this object + * + * @param stroke size in pt + */ + val strokeSVG: String + get() = getStrokeSVG(1f, mediumColor) + + /** + * returns the SVG for the font style of this object + * + * @param stroke size in pt + * @param String stroke color in HTML format + */ + fun getStrokeSVG(sz: Float, strokeclr: String): String { + return " stroke='$strokeclr' stroke-opacity='1' stroke-width='$sz' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='4'" + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Col3DChart.java b/src/main/java/io/starter/formats/XLS/charts/Col3DChart.java deleted file mode 100644 index 6b3a387..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Col3DChart.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class Col3DChart extends ColChart { - public Col3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - col = (Bar) charttype; - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - if (!this.getChartOption("Gap").equals("150")) - cooxml.append(""); // default= 0 - int gapdepth = this.getGapDepth(); - if (gapdepth != 0) - cooxml.append(""); - cooxml.append(""); - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - if (this.getParentChart().getAxes().hasAxis(ZAXIS)) { - cooxml.append(""); - cooxml.append("\r\n"); - } else {// KSC: // KSC: appears to be necessary but very unclear as to why - cooxml.append(""); - cooxml.append("\r\n"); - } - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Col3DChart.kt b/src/main/java/io/starter/formats/XLS/charts/Col3DChart.kt new file mode 100644 index 0000000..f2ca3d6 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Col3DChart.kt @@ -0,0 +1,91 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +open class Col3DChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ColChart(charttype, cf, wb) { + init { + col = charttype as Bar + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + if (this.getChartOption("Gap") != "150") + cooxml.append("") // default= 0 + val gapdepth = this.gapDepth + if (gapdepth != 0) + cooxml.append("") + cooxml.append("") + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + if (this.parentChart!!.axes!!.hasAxis(ChartConstants.ZAXIS)) { + cooxml.append("") + cooxml.append("\r\n") + } else {// KSC: // KSC: appears to be necessary but very unclear as to why + cooxml.append("") + cooxml.append("\r\n") + } + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ColChart.java b/src/main/java/io/starter/formats/XLS/charts/ColChart.java deleted file mode 100644 index eb7e8da..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ColChart.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.HashMap; - -public class ColChart extends ChartType { - protected Bar col = null; - - public ColChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - col = (Bar) charttype; - defaultShape = SHAPEDEFAULT; - } - - /** - * @return truth of "Chart is Clustered" (Bar/Col only) - */ - public boolean isClustered() { - return (/*cf.isClustered()); */ (!isStacked() && !is100PercentStacked())); - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - if (series.size() == 0) { - Logger.logErr("Bar.getSVG: error in series"); - return ""; - } - int n = series.size(); - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - boolean isXReversed = (Boolean) axisMetrics.get("xAxisReversed"); - boolean isYReversed = (Boolean) axisMetrics.get("yAxisReversed"); - - StringBuffer svg = new StringBuffer(); // return svg - svg.append("\r\n"); - - double barw = 0, yfactor = 0; // - if (n > 0) { - barw = w / ((categories.length * (n + 1.0)) + 1); // w/(#cats * nseries+1) + 1 - if (max != 0) - yfactor = h / max; // h/YMAXSCALE - } - int rfX = (!isXReversed ? 1 : -1); // reverse factor - int rfY = (!isYReversed ? 1 : -1); - // for each series - for (int i = 0; i < n; i++) { // each series group - svg.append("\r\n"); - double y0 = y + (!isYReversed ? h : 0); // start from bottom and work up (unless reversed) - double[] curseries = (double[]) series.get(i); - String[] curranges = (String[]) s.getSeriesRanges().get(i); - - //x+=barw; // a barwidth separates each series group - for (int j = 0; j < curseries.length; j++) { // each series - double xx = x + barw * (i + 1) + (j * (n + 1) * barw); // x goes from 1 series to next, corresponding to bar/column color - double hh = yfactor * curseries[j]; // bar height = measure of series value - double yy = y0 - (!isYReversed ? hh : 0); // start drawing column - svg.append(""); - String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); - if (l != null) { - svg.append("" + l + "\r\n"); - } - } - svg.append("\r\n"); // each series group - } - svg.append("\r\n"); - return svg.toString(); - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - //TODO: FINISH DR0P LINES - /// if (this.hasDropLines() ) /* Area, Line, Stock*/ - // cooxml.append() - if (!this.getChartOption("Gap").equals("150")) - cooxml.append(""); // default= 0 - if (!this.getChartOption("Overlap").equals("0")) - cooxml.append(""); // default= 0 - // Series Lines - ChartLine cl = cf.getChartLinesRec(); - if (cl != null) - cooxml.append(cl.getOOXML()); - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ColChart.kt b/src/main/java/io/starter/formats/XLS/charts/ColChart.kt new file mode 100644 index 0000000..5984486 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ColChart.kt @@ -0,0 +1,169 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.HashMap + +open class ColChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + protected var col: Bar? = null + + /** + * @return truth of "Chart is Clustered" (Bar/Col only) + */ + override/*cf.isClustered()); */ val isClustered: Boolean + get() = !isStacked && !is100PercentStacked + + init { + col = charttype as Bar + defaultShape = ChartConstants.SHAPEDEFAULT + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val x = chartMetrics["x"] // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + if (series!!.size == 0) { + Logger.logErr("Bar.getSVG: error in series") + return "" + } + val n = series.size + val dls = dataLabelInts // get array of data labels (can be specific per series ...) + val isXReversed = axisMetrics["xAxisReversed"] as Boolean + val isYReversed = axisMetrics["yAxisReversed"] as Boolean + + val svg = StringBuffer() // return svg + svg.append("\r\n") + + var barw = 0.0 + var yfactor = 0.0 // + if (n > 0) { + barw = w / (categories!!.size * (n + 1.0) + 1) // w/(#cats * nseries+1) + 1 + if (max != 0.0) + yfactor = h / max // h/YMAXSCALE + } + val rfX = if (!isXReversed) 1 else -1 // reverse factor + val rfY = if (!isYReversed) 1 else -1 + // for each series + for (i in 0 until n) { // each series group + svg.append("\r\n") + val y0 = y + if (!isYReversed) h else 0 // start from bottom and work up (unless reversed) + val curseries = series[i] as DoubleArray + val curranges = s.seriesRanges!![i] as Array + + //x+=barw; // a barwidth separates each series group + for (j in curseries.indices) { // each series + val xx = x + barw * (i + 1) + j.toDouble() * (n + 1).toDouble() * barw // x goes from 1 series to next, corresponding to bar/column color + val hh = yfactor * curseries[j] // bar height = measure of series value + val yy = y0 - if (!isYReversed) hh else 0 // start drawing column + svg.append("") + val l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0.0, i, legends, categories!![j].toString()) + if (l != null) { + svg.append("" + l + "\r\n") + } + } + svg.append("\r\n") // each series group + } + svg.append("\r\n") + return svg.toString() + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + //TODO: FINISH DR0P LINES + /// if (this.hasDropLines() ) /* Area, Line, Stock*/ + // cooxml.append() + if (this.getChartOption("Gap") != "150") + cooxml.append("") // default= 0 + if (this.getChartOption("Overlap") != "0") + cooxml.append("") // default= 0 + // Series Lines + val cl = cf!!.chartLinesRec + if (cl != null) + cooxml.append(cl.ooxml) + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.java b/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.java deleted file mode 100644 index bc76bf7..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class ConeBarChart extends Bar3DChart { - - public ConeBarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape = SHAPECONE; - chartobj.chartType = CONEBARCHART; - } - - public int getBarShape() { - return defaultShape; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.kt b/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.kt new file mode 100644 index 0000000..c1fa32d --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ConeBarChart.kt @@ -0,0 +1,36 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +class ConeBarChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : Bar3DChart(charttype, cf, wb) { + + override val barShape: Int + get() = defaultShape + + init { + defaultShape = ChartConstants.SHAPECONE + chartobj.chartType = ChartConstants.CONEBARCHART + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ConeChart.kt b/src/main/java/io/starter/formats/XLS/charts/ConeChart.kt new file mode 100644 index 0000000..99fdffb --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ConeChart.kt @@ -0,0 +1,37 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +class ConeChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : Col3DChart(charttype, cf, wb) { + + override val barShape: Int + get() = defaultShape + + init { + defaultShape = ChartConstants.SHAPECONE + chartobj.chartType = ChartConstants.CYLINDERCHART + + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/CrtLayout12.java b/src/main/java/io/starter/formats/XLS/charts/CrtLayout12.kt similarity index 67% rename from src/main/java/io/starter/formats/XLS/charts/CrtLayout12.java rename to src/main/java/io/starter/formats/XLS/charts/CrtLayout12.kt index 9ed93c6..46a06f5 100644 --- a/src/main/java/io/starter/formats/XLS/charts/CrtLayout12.java +++ b/src/main/java/io/starter/formats/XLS/charts/CrtLayout12.kt @@ -20,9 +20,9 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** * The CrtLayout12 record specifies the layout information for attached label @@ -74,29 +74,20 @@ * 8 dy (8 bytes): An Xnum value that specifies a height or an vertical offset. The meaning is determined by wHeightMode. * 2 reserved2 (2 bytes): MUST be zero, and MUST be ignored. */ -public class CrtLayout12 extends GenericChartObject implements ChartObject { - byte autolayouttype; - short wXMode, wYMode; - short wWidthMode, wHeightMode; - float x, y, dx, dy; - - public void init() { - super.init(); - byte[] data = this.getData(); - autolayouttype = (byte) (data[16] >> 1); - wXMode = ByteTools.readShort(data[18], data[19]); - wYMode = ByteTools.readShort(data[20], data[21]); - wWidthMode = ByteTools.readShort(data[22], data[23]); - wHeightMode = ByteTools.readShort(data[24], data[25]); - x = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(26, 8)); - y = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(34, 8)); - dx = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(42, 8)); - dy = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(50, 8)); - } +class CrtLayout12 : GenericChartObject(), ChartObject { + internal var autolayouttype: Byte = 0 + internal var wXMode: Short = 0 + internal var wYMode: Short = 0 + internal var wWidthMode: Short = 0 + internal var wHeightMode: Short = 0 + internal var x: Float = 0.toFloat() + internal var y: Float = 0.toFloat() + internal var dx: Float = 0.toFloat() + internal var dy: Float = 0.toFloat() /** * returns the legend autolayout type: - *
                      + *

                      * 0x0 Align to the bottom * 0x1 Align to top right corner * 0x2 Align to the top @@ -105,13 +96,9 @@ public void init() { * * @return */ - public int getLayout() { - return autolayouttype; - } - /** * sets the layout position of the attached legend or attached label - * *
                      + * *

                      * 0x0 Align to the bottom * 0x1 Align to top right corner * 0x2 Align to the top @@ -120,28 +107,44 @@ public int getLayout() { * * @param pos */ - public void setLayout(int pos) { - autolayouttype = (byte) pos; - this.getData()[16] = (byte) (autolayouttype << 1); - } + var layout: Int + get() = autolayouttype.toInt() + set(pos) { + autolayouttype = pos.toByte() + this.data[16] = (autolayouttype shl 1).toByte() + } - public float[] getCoords() { -/* return new float[] { Pos.convertFromSPRC(xTL, w, 0), // x offset + /* return new float[] { Pos.convertFromSPRC(xTL, w, 0), // x offset Pos.convertFromSPRC(yTL, 0, h), // y offset - Pos.convertFromSPRC(xBR, w, 0), // w + Pos.convertFromSPRC(xBR, w, 0), // w Pos.convertFromSPRC(yBR, 0, h) }; // h - */ - if (wWidthMode == 0) { - return null; - } - if (wWidthMode == 1) { - //x and y specify the offset of the top left corner, relative to its default position, as a fraction of the chart area. MUST be greater than or equal to -1.0 and MUST be less than or equal to 1.0. dx and dy specify the width and height, as a fraction of the chart area, MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. + *///x and y specify the offset of the top left corner, relative to its default position, as a fraction of the chart area. MUST be greater than or equal to -1.0 and MUST be less than or equal to 1.0. dx and dy specify the width and height, as a fraction of the chart area, MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. + // x and y specify the offset of the upper-left corner; dx and dy specify the offset of the bottom-right corner. x, y, dx and dy are specified relative to the upper-left corner of the chart area as a fraction of the chart area. x, y, dx and dy MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. + val coords: FloatArray? + get() { + if (wWidthMode.toInt() == 0) { + return null + } + if (wWidthMode.toInt() == 1) { + } + if (wWidthMode.toInt() == 2) { + } + return null } - if (wWidthMode == 2) { - // x and y specify the offset of the upper-left corner; dx and dy specify the offset of the bottom-right corner. x, y, dx and dy are specified relative to the upper-left corner of the chart area as a fraction of the chart area. x, y, dx and dy MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. - } - return null; + + override fun init() { + super.init() + val data = this.data + autolayouttype = (data!![16] shr 1).toByte() + wXMode = ByteTools.readShort(data[18].toInt(), data[19].toInt()) + wYMode = ByteTools.readShort(data[20].toInt(), data[21].toInt()) + wWidthMode = ByteTools.readShort(data[22].toInt(), data[23].toInt()) + wHeightMode = ByteTools.readShort(data[24].toInt(), data[25].toInt()) + x = ByteTools.eightBytetoLEDouble(this.getBytesAt(26, 8)!!).toFloat() + y = ByteTools.eightBytetoLEDouble(this.getBytesAt(34, 8)!!).toFloat() + dx = ByteTools.eightBytetoLEDouble(this.getBytesAt(42, 8)!!).toFloat() + dy = ByteTools.eightBytetoLEDouble(this.getBytesAt(50, 8)!!).toFloat() } } diff --git a/src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.java b/src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.kt similarity index 68% rename from src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.java rename to src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.kt index 8c64e6d..2a3bd9f 100644 --- a/src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.java +++ b/src/main/java/io/starter/formats/XLS/charts/CrtLayout12A.kt @@ -20,28 +20,33 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts /** * CrtLayout12A 0x08A7 * The CrtLayout12A record specifies layout information for a plot area. - *

                      + * + * * frtHeader (12 bytes): An FrtHeader structure. The frtheader.rt field MUST be 0x08A7. - *

                      + * + * * dwCheckSum (4 bytes): An unsigned integer that specifies the checksum. MUST be a value from the following table: * fManPlotArea field of ShtProps fAlwaysAutoPlotArea field of ShtProps dwCheckSum * 0x0 0x0 0x00000001 * 0x0 0x1 0x00000000 * 0x1 0x0 0x00000000 * 0x1 0x1 0x00000001 - *

                      + * + * * A - fLayoutTargetInner (1 bit): A bit that specifies the type of plot area for the layout target. * Value Meaning * 0x0 Outer plot area - The bounding rectangle that includes the axis labels, axis titles, data table (2) and plot area of the chart. * 0x1 Inner plot area – The rectangle bounded by the chart axes. - *

                      + * + * * reserved1 (15 bits): MUST be zero, and MUST be ignored. - *

                      + * + * * xTL (2 bytes): A signed integer that specifies the horizontal offset of the plot area’s upper-left corner, relative to the upper-left corner of the chart area, in SPRC. * yTL (2 bytes): A signed integer that specifies the vertical offset of the plot area’s upper-left corner, relative to the upper-left corner of the chart area, in SPRC. * xBR (2 bytes): A signed integer that specifies the width of the plot area, in SPRC. @@ -55,71 +60,83 @@ * dx (8 bytes): An Xnum value that specifies a width or a horizontal offset. The meaning is determined by wWidthMode. * dy (8 bytes): An Xnum value that specifies a height or a vertical offset. The meaning is determined by wHeightMode. * reserved2 (2 bytes): MUST be zero, and MUST be ignored. - *

                      + * + * * The CrtLayout12Mode record specifies a layout mode. Each layout mode specifies a different meaning of the x, y, dx, and dy fields of CrtLayout12 and CrtLayout12A. * Name Value Meaning * L12MAUTO 0x0000 Position and dimension (2) are determined by the application. x, y, dx and dy MUST be ignored. * L12MFACTOR 0x0001 x and y specify the offset of the top left corner, relative to its default position, as a fraction of the chart area. MUST be greater than or equal to -1.0 and MUST be less than or equal to 1.0. dx and dy specify the width and height, as a fraction of the chart area, MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. * L12MEDGE 0x0002 x and y specify the offset of the upper-left corner; dx and dy specify the offset of the bottom-right corner. x, y, dx and dy are specified relative to the upper-left corner of the chart area as a fraction of the chart area. x, y, dx and dy MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0 - *

                      + * + * * Xnum is a 64-bit binary floating-point number as specified in [IEEE754]. This value MUST NOT be infinity, denormalized, not-a-number (NaN), nor negative zero. - *

                      + * + * * A SPRC is a unit of measurement that is 1/4000th of the height or width of the chart. If the field is being used to specify a width or horizontal distance, the SPRC is 1/4000th of the width of the chart. If the field is being used to specify a height or vertical distance, the SPRC is 1/4000th of the height of the chart. */ -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools -public class CrtLayout12A extends GenericChartObject implements ChartObject { - byte fLayoutTargetInner; - short xTL, yTL; - short xBR, yBR; - short wXMode, wYMode; - short wWidthMode, wHeightMode; - float x, y, dx, dy; - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1868700214505277636L; +class CrtLayout12A : GenericChartObject(), ChartObject { + internal var fLayoutTargetInner: Byte = 0 + internal var xTL: Short = 0 + internal var yTL: Short = 0 + internal var xBR: Short = 0 + internal var yBR: Short = 0 + internal var wXMode: Short = 0 + internal var wYMode: Short = 0 + internal var wWidthMode: Short = 0 + internal var wHeightMode: Short = 0 + internal var x: Float = 0.toFloat() + internal var y: Float = 0.toFloat() + internal var dx: Float = 0.toFloat() + internal var dy: Float = 0.toFloat() - public void init() { - super.init(); - byte[] data = this.getData(); - fLayoutTargetInner = data[16]; - xTL = ByteTools.readShort(data[18], data[19]); - yTL = ByteTools.readShort(data[20], data[21]); - xBR = ByteTools.readShort(data[22], data[23]); - yBR = ByteTools.readShort(data[24], data[25]); - wXMode = ByteTools.readShort(data[26], data[27]); - wYMode = ByteTools.readShort(data[28], data[29]); - wWidthMode = ByteTools.readShort(data[30], data[31]); - wHeightMode = ByteTools.readShort(data[32], data[33]); - x = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(34, 8)); - y = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(42, 8)); - dx = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(50, 8)); - dy = (float) ByteTools.eightBytetoLEDouble(this.getBytesAt(58, 8)); + override fun init() { + super.init() + val data = this.data + fLayoutTargetInner = data!![16] + xTL = ByteTools.readShort(data[18].toInt(), data[19].toInt()) + yTL = ByteTools.readShort(data[20].toInt(), data[21].toInt()) + xBR = ByteTools.readShort(data[22].toInt(), data[23].toInt()) + yBR = ByteTools.readShort(data[24].toInt(), data[25].toInt()) + wXMode = ByteTools.readShort(data[26].toInt(), data[27].toInt()) + wYMode = ByteTools.readShort(data[28].toInt(), data[29].toInt()) + wWidthMode = ByteTools.readShort(data[30].toInt(), data[31].toInt()) + wHeightMode = ByteTools.readShort(data[32].toInt(), data[33].toInt()) + x = ByteTools.eightBytetoLEDouble(this.getBytesAt(34, 8)!!).toFloat() + y = ByteTools.eightBytetoLEDouble(this.getBytesAt(42, 8)!!).toFloat() + dx = ByteTools.eightBytetoLEDouble(this.getBytesAt(50, 8)!!).toFloat() + dy = ByteTools.eightBytetoLEDouble(this.getBytesAt(58, 8)!!).toFloat() } /** * If this CrtLayout contains information for the inner plot area coords * and the coordinates are NOT determined by the application, * calculate the plot area coordinates. - *
                      If not possible, return null; - *
                      [Inner plot area – The rectangle bounded by the chart axes] + *

                      If not possible, return null; + *

                      [Inner plot area – The rectangle bounded by the chart axes] * @param w chart width for SPRC calc * @param h chart height for SPRC calc * @return */ - float[] getInnerPlotCoords(float w, float h) { - /*L12MAUTO 0x0000 Position and dimension (2) are determined by the application. x, y, dx and dy MUST be ignored. + internal fun getInnerPlotCoords(w: Float, h: Float): FloatArray? { + /*L12MAUTO 0x0000 Position and dimension (2) are determined by the application. x, y, dx and dy MUST be ignored. L12MFACTOR 0x0001 x and y specify the offset of the top left corner, relative to its default position, as a fraction of the chart area. MUST be greater than or equal to -1.0 and MUST be less than or equal to 1.0. dx and dy specify the width and height, as a fraction of the chart area, MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0. L12MEDGE 0x0002 x and y specify the offset of the upper-left corner; dx and dy specify the offset of the bottom-right corner. x, y, dx and dy are specified relative to the upper-left corner of the chart area as a fraction of the chart area. x, y, dx and dy MUST be greater than or equal to 0.0, and MUST be less than or equal to 1.0 */ - if (fLayoutTargetInner == 0) - return null; - return new float[]{Pos.convertFromSPRC(xTL, w, 0), // x offset - Pos.convertFromSPRC(yTL, 0, h), // y offset - Pos.convertFromSPRC(xBR, w, 0), // w - Pos.convertFromSPRC(yBR, 0, h)}; // h + return if (fLayoutTargetInner.toInt() == 0) null else floatArrayOf(Pos.convertFromSPRC(xTL.toFloat(), w, 0f), // x offset + Pos.convertFromSPRC(yTL.toFloat(), 0f, h), // y offset + Pos.convertFromSPRC(xBR.toFloat(), w, 0f), // w + Pos.convertFromSPRC(yBR.toFloat(), 0f, h)) +// h + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -1868700214505277636L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.java b/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.java deleted file mode 100644 index d0db69d..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class CylinderBarChart extends Bar3DChart { - - public CylinderBarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape = SHAPECYLINDER; - chartobj.chartType = CYLINDERBARCHART; - } - - public int getBarShape() { - return defaultShape; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.kt b/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.kt new file mode 100644 index 0000000..e1f6b45 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/CylinderBarChart.kt @@ -0,0 +1,36 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +class CylinderBarChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : Bar3DChart(charttype, cf, wb) { + + override val barShape: Int + get() = defaultShape + + init { + defaultShape = ChartConstants.SHAPECYLINDER + chartobj.chartType = ChartConstants.CYLINDERBARCHART + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/CylinderChart.java b/src/main/java/io/starter/formats/XLS/charts/CylinderChart.java deleted file mode 100644 index 0f9ed3b..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/CylinderChart.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class CylinderChart extends Col3DChart { - - public CylinderChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape = SHAPECYLINDER; - chartobj.chartType = CYLINDERCHART; - } - - public int getBarShape() { - return defaultShape; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/CylinderChart.kt b/src/main/java/io/starter/formats/XLS/charts/CylinderChart.kt new file mode 100644 index 0000000..860c448 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/CylinderChart.kt @@ -0,0 +1,36 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +class CylinderChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : Col3DChart(charttype, cf, wb) { + + override val barShape: Int + get() = defaultShape + + init { + defaultShape = ChartConstants.SHAPECYLINDER + chartobj.chartType = ChartConstants.CYLINDERCHART + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Dat.java b/src/main/java/io/starter/formats/XLS/charts/Dat.java deleted file mode 100644 index 687b10f..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Dat.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * Dat: Data Table Options (0x1063) - *

                      - * Offset Name Size Contents - * 4 grbit 2 Option flags (see following table) - *

                      - *

                      - * The grbit field contains the following flags. - *

                      - * Offset Bits Mask Name Contents - * 0 0 01h fHasBordHorz 1 = data table has horizontal borders - * 1 02h fHasBordVert 1 = data table has vertical borders - * 2 04h fhasBordOutline 1 = data table has a border - * 3 08h fShowSeriesKey 1 = data table shows series keys - * 7-4 F0h reserved Reserved; must be zero - * 1 7-0 FFh reserved Reserved; must be zero - */ -public class Dat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1138056714558134785L; - private short grbit; - boolean fHasBordHorz, fHasBordVert, fHasBordOutline, fShowSeriesKey; - - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fHasBordHorz = ((grbit & 0x1) == 0x1); - fHasBordVert = ((grbit & 0x2) == 0x2); - fHasBordOutline = ((grbit & 0x4) == 0x4); - fShowSeriesKey = ((grbit & 0x8) == 0x8); - - } - - private byte[] PROTOTYPE_BYTES = new byte[]{15, 0}; - - /** - * creates a new Dat record; if bCreateDataTable is true, - * will also add the associated records required to create - * a Data Table - * - * @param bCreateDataTable - * @return - */ - public static XLSRecord getPrototype(boolean bCreateDataTable) { - Dat d = new Dat(); - d.setOpcode(DAT); - d.setData(d.PROTOTYPE_BYTES); - d.init(); - if (bCreateDataTable) { - Legend l = (Legend) Legend.getPrototype(); - l.setIsDataTable(true); - //l.setwType(Legend.NOT_DOCKED); - d.chartArr.add(l); - // add pos record - Pos p = (Pos) Pos.getPrototype(Pos.TYPE_DATATABLE); -// p.setWorkBook(book); - l.addChartRecord(p); - // TextDisp - TextDisp td = (TextDisp) TextDisp.getPrototype(); - l.addChartRecord(td); - // TextDisp sub-recs - p = (Pos) Pos.getPrototype(Pos.TYPE_TEXTDISP); - td.addChartRecord(p); - Fontx f = (Fontx) Fontx.getPrototype(); -// f.setWorkBook(book); -// EVENTUALLY! - f.setIfnt(6); - td.addChartRecord(f); - Ai ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND); -// ai.setWorkBook(book); - td.addChartRecord(ai); - } - return d; - } - - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(grbit); - this.setData(b); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Dat.kt b/src/main/java/io/starter/formats/XLS/charts/Dat.kt new file mode 100644 index 0000000..667013c --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Dat.kt @@ -0,0 +1,119 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * ** Dat: Data Table Options (0x1063)** + * + * + * Offset Name Size Contents + * 4 grbit 2 Option flags (see following table) + * + * + * + * + * The grbit field contains the following flags. + * + * + * Offset Bits Mask Name Contents + * 0 0 01h fHasBordHorz 1 = data table has horizontal borders + * 1 02h fHasBordVert 1 = data table has vertical borders + * 2 04h fhasBordOutline 1 = data table has a border + * 3 08h fShowSeriesKey 1 = data table shows series keys + * 7-4 F0h reserved Reserved; must be zero + * 1 7-0 FFh reserved Reserved; must be zero + */ +class Dat : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + internal var fHasBordHorz: Boolean = false + internal var fHasBordVert: Boolean = false + internal var fHasBordOutline: Boolean = false + internal var fShowSeriesKey: Boolean = false + + private val PROTOTYPE_BYTES = byteArrayOf(15, 0) + + override fun init() { + super.init() + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + fHasBordHorz = grbit and 0x1 == 0x1 + fHasBordVert = grbit and 0x2 == 0x2 + fHasBordOutline = grbit and 0x4 == 0x4 + fShowSeriesKey = grbit and 0x8 == 0x8 + + } + + private fun updateRecord() { + val b = ByteTools.shortToLEBytes(grbit) + this.data = b + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 1138056714558134785L + + /** + * creates a new Dat record; if bCreateDataTable is true, + * will also add the associated records required to create + * a Data Table + * + * @param bCreateDataTable + * @return + */ + fun getPrototype(bCreateDataTable: Boolean): XLSRecord { + val d = Dat() + d.opcode = XLSConstants.DAT + d.data = d.PROTOTYPE_BYTES + d.init() + if (bCreateDataTable) { + val l = Legend.prototype as Legend? + l!!.setIsDataTable(true) + //l.setwType(Legend.NOT_DOCKED); + d.chartArr.add(l) + // add pos record + var p = Pos.getPrototype(Pos.TYPE_DATATABLE) as Pos + // p.setWorkBook(book); + l.addChartRecord(p) + // TextDisp + val td = TextDisp.prototype as TextDisp? + l.addChartRecord(td) + // TextDisp sub-recs + p = Pos.getPrototype(Pos.TYPE_TEXTDISP) as Pos + td!!.addChartRecord(p) + val f = Fontx.prototype as Fontx? + // f.setWorkBook(book); + // EVENTUALLY! + f!!.ifnt = 6 + td.addChartRecord(f) + val ai = Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND) as Ai + // ai.setWorkBook(book); + td.addChartRecord(ai) + } + return d + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/DataFormat.java b/src/main/java/io/starter/formats/XLS/charts/DataFormat.java deleted file mode 100644 index 3d0cd6b..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/DataFormat.java +++ /dev/null @@ -1,679 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * DataFormat: Series and Data Point Numbers(0x1006) - *

                      - * 4 xi 2 the zero-based index of the data point within the series specified by yi. (FFFFh means entire series) - * 6 yi 2 the zero-based index of a Series record - * 8 iss 2 An unsigned integer that specifies properties of the data series, trendline or error bar, depending on the type of records in sequence of records: - *

                      - * If does not contain a SerAuxTrend or SerAuxErrBar record, then this field specifies the plot order of the data series. - * If the series order was changed, this field can be different from yi. MUST be less than or equal to the number of series in the chart. - * MUST be unique among iss values for all instances of this record contained in the SERIESFORMAT rule that does not contain a SerAuxTrend or SerAuxErrBar record. - *

                      - * If the SERIESFORMAT rule contains a SerAuxTrend record on the chart group, then this field specifies the trendline number for the series. - *

                      - * If the SERIESFORMAT rule contains a SerAuxErrBar record on the chart group, then this field specifies a zero-based index into a Series record in the - * collection of Series records in the current chart sheet substream for which the error bar applies to. - *

                      - * 10 grbit 2 flags (0?) ignored - *

                      - *

                      - * ORDER OF SUB-RECS: - * [Chart3DBarShape] - * [LineFormat, AreaFormat, PieFormat] == lines, fill - * [SerFormat] == smoothed lines ... - * [GelFrame] - * [MarkerFormat] - * [AttachedLabel] == data labels - * [ShapeProps] - * [CtrlMltFrt] - */ -public class DataFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3526272512004348462L; - private short yi, xi; - private short iss; - - public void init() { - super.init(); - byte[] rkdata = this.getData(); - xi = ByteTools.readShort(rkdata[0], rkdata[1]); - yi = ByteTools.readShort(rkdata[2], rkdata[3]); - iss = (short) ByteTools.readUnsignedShort(rkdata[4], rkdata[5]); - } - - private byte[] PROTOTYPE_BYTES = new byte[]{-1, -1, 0, 0, 0, 0, 0, 0}; - - - public void initNew() { - this.setOpcode(DATAFORMAT); - this.setData(PROTOTYPE_BYTES); - this.init(); - Chart3DBarShape cs = new Chart3DBarShape(); - cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 - this.addChartRecord(cs); - } - - /** - * Create a new dataformat - * - * @param parentChart - * @return - */ - public static XLSRecord getPrototype() { - DataFormat df = new DataFormat(); - df.setOpcode(DATAFORMAT); - df.setData(df.PROTOTYPE_BYTES); - df.init(); - Chart3DBarShape cs = new Chart3DBarShape(); - cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 - df.addChartRecord(cs); - return df; - } - - public static XLSRecord getPrototypeWithFormatRecs(Chart parentChart) { - return getPrototypeWithFormatRecs(0, parentChart); - } - - - /** - * Create DataFormat Record that HOPEFULLY reflects the necessary associated recs - * - * @param type - * @return DataFormat Record - */ - public static XLSRecord getPrototypeWithFormatRecs(int seriesNumber, Chart parentChart) { - DataFormat df = new DataFormat(); - df.setOpcode(DATAFORMAT); - df.setData(df.PROTOTYPE_BYTES); - df.init(); - Chart3DBarShape cs = new Chart3DBarShape(); - cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 - df.addChartRecord(cs); - df.setSeriesNumber(seriesNumber); - LineFormat lf = (LineFormat) LineFormat.getPrototype(); - df.addChartRecord(lf); - AreaFormat af = (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(parentChart); - df.addChartRecord(af); - PieFormat pf = (PieFormat) PieFormat.getPrototype(); - pf.setParentChart(parentChart); - df.addChartRecord(pf); - MarkerFormat mf = (MarkerFormat) MarkerFormat.getPrototype(); - mf.setParentChart(parentChart); - df.addChartRecord(mf); - /* pieformat: - MUST not exist on chart group types other than ---> doesn't appear true (???) - pie, - doughnut, - bar of pie, or - pie of pie. - MUST not exist if the chart group type is doughnut and the series is not the outermost series. - MUST not exist on the data points on the secondary bar/pie of a bar of pie chart group. - */ - return df; - } - - public void setPointNumber(int idx) { - xi = (short) idx; - byte[] rkdata = this.getData(); - byte[] num = ByteTools.shortToLEBytes((short) idx); - rkdata[0] = num[0]; - rkdata[1] = num[1]; - this.setData(rkdata); - } - - /** - * Set the series index - */ - public void setSeriesIndex(int idx) { - yi = (short) idx; - byte[] rkdata = this.getData(); - byte[] num = ByteTools.shortToLEBytes((short) idx); - rkdata[2] = num[0]; - rkdata[3] = num[1]; - this.setData(rkdata); - } - - public void setSeriesNumber(int idx) { - iss = (short) idx; - byte[] rkdata = this.getData(); - byte[] num = ByteTools.shortToLEBytes((short) idx); - rkdata[4] = num[0]; - rkdata[5] = num[1]; - this.setData(rkdata); - } - - public short getSeriesIndex() { - return yi; - } - - public short getSeriesNumber() { - return iss; - } - - public short getPointNumber() { - return xi; - } - - private AttachedLabel getAttachedLabelRec(boolean bCreate) { - AttachedLabel al = null; - al = (AttachedLabel) Chart.findRec(this.chartArr, AttachedLabel.class); - if (al == null && bCreate) { // basic options are handled via AttachedLabel rec - al = (AttachedLabel) AttachedLabel.getPrototype(); - int z = Chart.findRecPosition(this.chartArr, MarkerFormat.class); - if (z > 0) - chartArr.add(z + 1, al); - else - this.addChartRecord(al); - } - return al; - } - - private AreaFormat getAreaFormatRec(boolean bCreate) { - AreaFormat af = (AreaFormat) Chart.findRec(this.chartArr, AreaFormat.class); - if (af == null) { - af = (AreaFormat) AreaFormat.getPrototype(0); - this.addChartRecord(LineFormat.getPrototype()); - this.addChartRecord(af); - this.addChartRecord(PieFormat.getPrototype()); - this.addChartRecord(MarkerFormat.getPrototype()); - } - return af; - } - - /** - * return XLSRecord data (2 bytes), which controls bar shape - */ - public short getShape() { - Chart3DBarShape cs = (Chart3DBarShape) Chart.findRec(this.chartArr, Chart3DBarShape.class); - return cs.getShape(); - } - - /** - * set the shape bit of the associated XLSRecord - * - * @param shape - */ - public void setShape(int shape) { - Chart3DBarShape cs = (Chart3DBarShape) Chart.findRec(this.chartArr, Chart3DBarShape.class); - cs.setShape((short) shape); - } - - /** - * set smooth lines setting (applicable for line, scatter charts) - * - * @param smooth - */ - public void setSmoothLines(boolean smooth) { - Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); - if (sf == null) { - if (smooth) { - setHasLines(); - sf = (Serfmt) Serfmt.getPrototype(); - int i = Chart.findRecPosition(this.chartArr, PieFormat.class); - this.chartArr.add(i + 1, sf); - sf.setSmoothedLine(true); - } - } else { - sf.setSmoothedLine(smooth); - } - } - - /** - * returns true if this parent chart has smoothed lines (Line, Scatter, Radar charts) - * - * @return - */ - public boolean getSmoothedLines() { - Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); - if (sf != null) - return sf.getSmoothLine(); - return false; - } - - /** - * returns true if this parent chart has lines (Line, Scatter, Radar charts) - * - * @return - */ - public boolean getHasLines() { - LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); - if (l != null) - return l.getLineStyle() != LineFormat.NONE; - return false; - } - - /** - * sets this chart to have lines (line chart, radar, scatter ...) - */ - public void setHasLines() { - setHasLines(0); - } - - /** - * sets this chart to have lines (line chart, radar, scatter ...) of the specific line style - *
                      Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - */ - public void setHasLines(int lineStyle) { - LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); - if (l == null) { // these come as a group - assume none or only has Chart3DBarShape ... - int z = Chart.findRecPosition(chartArr, Chart3DBarShape.class) + 1; - LineFormat lf = (LineFormat) LineFormat.getPrototype(lineStyle, -1); - chartArr.add(z++, lf); - AreaFormat af = (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(parentChart); - chartArr.add(z++, af); - PieFormat pf = (PieFormat) PieFormat.getPrototype(); - pf.setParentChart(parentChart); - chartArr.add(z++, pf); - MarkerFormat mf = (MarkerFormat) MarkerFormat.getPrototype(); - mf.setParentChart(parentChart); - chartArr.add(z++, mf); - - } else { - l.setLineStyle(lineStyle); - } - } - - /** - * sets 3D Bubbles (Bubble Chart only) - * - * @param b true if has 3d Bubbles - */ - public void setHas3DBubbles(boolean b) { - Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); - if (sf == null) { - if (b) { - sf = (Serfmt) Serfmt.getPrototype(); - int i = Chart.findRecPosition(this.chartArr, PieFormat.class); - sf.setParentChart(this.getParentChart()); - this.chartArr.add(i + 1, sf); - sf.setHas3dBubbles(true); - } - } else { - sf.setHas3dBubbles(b); - } - } - - /** - * returns true if this paernt chart has 3D bubbles (Bubble chart only) - * - * @return - */ - public boolean getHas3DBubbles() { - Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); - if (sf != null) - return sf.get3DBubbles(); - return false; - } - - - /** - * return if data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. - */ - public boolean getHasShadow() { - Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); - if (sf != null) - return sf.hasShadow(); - return false; - } - - /** - * data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. - * - * @param b - */ - public void setHasShadow(boolean b) { - Serfmt sf = (Serfmt) Chart.findRec(this.chartArr, Serfmt.class); - if (sf == null) { - if (b) { - sf = (Serfmt) Serfmt.getPrototype(); - int i = Chart.findRecPosition(this.chartArr, PieFormat.class); - sf.setParentChart(this.getParentChart()); - this.chartArr.add(i + 1, sf); - sf.setHasShadow(true); - } - } else { - sf.setHasShadow(b); - } - } - - /** - * percentage=distance of pie slice from center of pie as % - * - * @param p - */ - public void setPercentage(int p) { - PieFormat pf = (PieFormat) Chart.findRec(this.chartArr, PieFormat.class); - if (pf == null) { - setHasLines(LineFormat.NONE); - pf = (PieFormat) Chart.findRec(this.chartArr, PieFormat.class); - } - pf.setPercentage((short) p); - } - - /** - * return percentage=distance of pie slice from center of pie as % - */ - public int getPercentage() { - PieFormat pf = (PieFormat) Chart.findRec(this.chartArr, PieFormat.class); - if (pf != null) - return pf.getPercentage(); - return 0; - - } - - /** - * sets the data labels to the desired type: - *

                    • "ShowValueLabel" - *
                    • "ShowValueAsPercent" - *
                    • "ShowLabelAsPercent" - *
                    • "ShowLabel" - *
                    • "ShowSeriesName" - *
                    • "ShowBubbleLabel" - * - * @param type - */ - public void setDataLabels(String type) { - AttachedLabel al = this.getAttachedLabelRec(true); - al.setType(type, "1"); - } - - /** - * returns true if has data labels - * - * @return - */ - public boolean getHasDataLabels() { - return (this.getAttachedLabelRec(false) != null); - } - - /** - * return if has the specified data label - *
                    • "ShowValueLabel" - *
                    • "ShowValueAsPercent" - *
                    • "ShowLabelAsPercent" - *
                    • "ShowLabel" - *
                    • "ShowSeriesName" - *
                    • "ShowBubbleLabel" - * - * @param type String option - * @return string true or false - */ - public String getDataLabelType(String type) { - AttachedLabel al = this.getAttachedLabelRec(false); - if (al != null) - return al.getType(type); - return null; - } - - /** - * return a string of ALL the label options chosen. One or more of: - *
                    • Value - *
                    • ValuePerecentage - *
                    • CategoryPercentage // Pie only - *
                    • CategoryLabel - *
                    • BubbleLabel - *
                    • SeriesLabel - * - * @return string true or false - */ - public String getDataLabelType() { - AttachedLabel al = this.getAttachedLabelRec(false); - if (al != null) - return al.getType(); - return null; - } - - /** - * return the data label int or 0 if no data labels chosen - * - * @return - */ - public int getDataLabelTypeInt() { - AttachedLabel al = this.getAttachedLabelRec(false); - if (al != null) - return al.getTypeInt(); - return 0; - } - - /** - * sets the data labels for the entire chart (as opposed to a specific series/data point). - * A combination of: - *
                    • SHOWVALUE= 0x1; - *
                    • SHOWVALUEPERCENT= 0x2; - *
                    • SHOWCATEGORYPERCENT= 0x4; - *
                    • SHOWCATEGORYLABEL= 0x10; - *
                    • SHOWBUBBLELABEL= 0x20; - *
                    • SHOWSERIESLABEL= 0x40; - */ - public void setHasDataLabels(int dl) { - AttachedLabel al = this.getAttachedLabelRec(true); - al.setType((short) dl); - } - - /** - * returns type of marker, if any
                      - * 0 = no marker
                      - * 1 = square
                      - * 2 = diamond
                      - * 3 = triangle
                      - * 4 = X
                      - * 5 = star
                      - * 6 = Dow-Jones
                      - * 7 = standard deviation
                      - * 8 = circle
                      - * 9 = plus sign - * - * @return - */ - public int getMarkerFormat() { - // default actually looks like: 2, 1, 5, 4 ... - MarkerFormat mf = (MarkerFormat) Chart.findRec(this.chartArr, MarkerFormat.class); - if (mf != null) - return mf.getMarkerFormat(); - - return 0; - } - - /** - * 0 = no marker
                      - * 1 = square
                      - * 2 = diamond
                      - * 3 = triangle
                      - * 4 = X
                      - * 5 = star
                      - * 6 = Dow-Jones
                      - * 7 = standard deviation
                      - * 8 = circle
                      - * 9 = plus sign - */ - public void setMarkerFormat(int marker) { - MarkerFormat mf = (MarkerFormat) Chart.findRec(this.chartArr, MarkerFormat.class); - if (mf == null) { - if (chartArr.isEmpty()) { // these records come in a set - Chart3DBarShape cs = new Chart3DBarShape(); - cs.setOpcode(CHART3DBARSHAPE); // creates default bar shape==0, 0 - this.addChartRecord(cs); - LineFormat lf = (LineFormat) LineFormat.getPrototype(); - lf.setParentChart(this.parentChart); - lf.setLineStyle(5); - chartArr.add(lf); - AreaFormat af = (AreaFormat) AreaFormat.getPrototype(); - af.setParentChart(this.parentChart); - chartArr.add(af); - PieFormat pf = (PieFormat) PieFormat.getPrototype(); - pf.setParentChart(this.parentChart); - chartArr.add(pf); - mf = (MarkerFormat) MarkerFormat.getPrototype(); - mf.setParentChart(this.parentChart); - chartArr.add(mf); - } else { // shouldn't get here but it goes - mf = (MarkerFormat) MarkerFormat.getPrototype(); - mf.setParentChart(this.parentChart); - int z = Chart.findRecPosition(chartArr, PieFormat.class); - if (z > -1) - chartArr.add(z + 1, mf); - else // dunno, add to end - chartArr.add(mf); - } - } - mf.setMarkerFormat((short) marker); - } - -// 0x893 CtlMltFrt - // [-98, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 13, 19, 0, 6, -128, 34, 0, 35, 0, -67, 64, 0, 0] - - /** - * sets the color identified by this DataFormat in the group of records - * belonging to the parent series - * - * @param clr color int - */ - public void setSeriesColor(int clr) { - AreaFormat af = getAreaFormatRec(true); - // Finally got the AreaFormat record that governs the color for this series - af.seticvFore(clr); - } - - /** - * sets the color identified by this DataFormat in the group of records - * belonging to the parent series - * - * @param clr Hex color string - */ - public void setSeriesColor(String clr) { - AreaFormat af = getAreaFormatRec(true); - // Finally got the AreaFormat record that governs the color for this series - af.seticvFore(clr); - } - - -/* - public int[] getDataLabelsPIE(int defaultdl) { - i++; - //ArrayList dls= new ArrayList(); - int[] dls= new int[chartArr.size()-i-1]; - int j= 0; - for (; i < chartArr.size(); i++) { - if (chartArr.get(i) instanceof DataFormat) { - DataFormat df= ((DataFormat) chartArr.get(i)); - AttachedLabel al= (AttachedLabel) Chart.findRec(df.chartArr, AttachedLabel.class); - if (al!=null) - dls[j]= al.getTypeInt(); - dls[j++]|=defaultdl; - } - } - return dls; - } -*/ - - /** - * get the bg color identified by this DataFormat - *
                      Usually part of a series group of records - * - * @return - */ - public String getBgColor() { - String bg = Frame.getBgColor(this.chartArr); - return bg; - } - - /** - * sets the color of the desired pie slice - * - * @param clr color int - * @param slice 0-based pie slice number - */ - public void setPieSliceColor(String clr, int slice) { - AreaFormat af = getAreaFormatPie(slice); - af.seticvFore(clr); - } - - /** - * sets the color of the desired pie slice - * - * @param clr color int - * @param slice 0-based pie slice number - */ - public void setPieSliceColor(int clr, int slice) { - AreaFormat af = getAreaFormatPie(slice); - af.seticvFore(clr); - } - - /** - * returns (creates if necessary) the area format for the desired pie slice (pie charts only) - * - * @param slice int 0-based slice nmber - * @return AreaFormat record - */ - private AreaFormat getAreaFormatPie(int slice) { - // must add x number of dataformat recs -// FINISH- not 100% -/* if (i==s.chartArr.size()) { - df= (DataFormat) DataFormat.getPrototypeWithFormatRecs(0); - df.setPointNumber(slice); - PieFormat pf= (PieFormat) Chart.findRec(df.chartArr, PieFormat.class); - pf.setPercentage((short)25); // default percentage - AttachedLabel al= (AttachedLabel) AttachedLabel.getPrototype(); - al.setType("CandP"); // default - df.addChartRecord(al); - s.chartArr.add(s.chartArr.size()-1, df); // -1 to skip SERTOCRT rec - } -*/ - AreaFormat af = (AreaFormat) Chart.findRec(this.chartArr, AreaFormat.class); - return af; - } - - /** - * returns (creates if necessary) the area format for this series- controls bar/series colors - * - * @return AreaFormat record - */ - private AreaFormat getAreaFormat() { - AreaFormat af = (AreaFormat) Chart.findRec(this.chartArr, AreaFormat.class); - if (af == null) { - af = (AreaFormat) AreaFormat.getPrototype(0); - // NOTE: below list of records is what has been observed in Excel 2003 chart files - - // unsure if need marker format always ? - this.addChartRecord(LineFormat.getPrototype()); - this.addChartRecord(af); - this.addChartRecord(PieFormat.getPrototype()); - this.addChartRecord(MarkerFormat.getPrototype()); - } - return af; - } - - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/DataFormat.kt b/src/main/java/io/starter/formats/XLS/charts/DataFormat.kt new file mode 100644 index 0000000..a08cda8 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/DataFormat.kt @@ -0,0 +1,670 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **DataFormat: Series and Data Point Numbers(0x1006)** + * + * + * 4 xi 2 the zero-based index of the data point within the series specified by yi. (FFFFh means entire series) + * 6 yi 2 the zero-based index of a Series record + * 8 iss 2 An unsigned integer that specifies properties of the data series, trendline or error bar, depending on the type of records in sequence of records: + * + * + * If does not contain a SerAuxTrend or SerAuxErrBar record, then this field specifies the plot order of the data series. + * If the series order was changed, this field can be different from yi. MUST be less than or equal to the number of series in the chart. + * MUST be unique among iss values for all instances of this record contained in the SERIESFORMAT rule that does not contain a SerAuxTrend or SerAuxErrBar record. + * + * + * If the SERIESFORMAT rule contains a SerAuxTrend record on the chart group, then this field specifies the trendline number for the series. + * + * + * If the SERIESFORMAT rule contains a SerAuxErrBar record on the chart group, then this field specifies a zero-based index into a Series record in the + * collection of Series records in the current chart sheet substream for which the error bar applies to. + * + * + * 10 grbit 2 flags (0?) ignored + * + * + * + * + * ORDER OF SUB-RECS: + * [Chart3DBarShape] + * [LineFormat, AreaFormat, PieFormat] == lines, fill + * [SerFormat] == smoothed lines ... + * [GelFrame] + * [MarkerFormat] + * [AttachedLabel] == data labels + * [ShapeProps] + * [CtrlMltFrt] + */ +class DataFormat : GenericChartObject(), ChartObject { + var seriesIndex: Short = 0 + private set + var pointNumber: Short = 0 + private set + var seriesNumber: Short = 0 + private set + + private val PROTOTYPE_BYTES = byteArrayOf(-1, -1, 0, 0, 0, 0, 0, 0) + + /** + * return XLSRecord data (2 bytes), which controls bar shape + */ + val shape: Short + get() { + val cs = Chart.findRec(this.chartArr, Chart3DBarShape::class.java) as Chart3DBarShape + return cs.shape + } + + /** + * returns true if this parent chart has smoothed lines (Line, Scatter, Radar charts) + * + * @return + */ + val smoothedLines: Boolean + get() { + val sf = Chart.findRec(this.chartArr, Serfmt::class.java) as Serfmt + return sf?.smoothLine ?: false + } + + /** + * returns true if this parent chart has lines (Line, Scatter, Radar charts) + * + * @return + */ + val hasLines: Boolean + get() { + val l = Chart.findRec(this.chartArr, LineFormat::class.java) as LineFormat + return if (l != null) l.lineStyle != LineFormat.NONE else false + } + + /** + * returns true if this paernt chart has 3D bubbles (Bubble chart only) + * + * @return + */ + /** + * sets 3D Bubbles (Bubble Chart only) + * + * @param b true if has 3d Bubbles + */ + var has3DBubbles: Boolean + get() { + val sf = Chart.findRec(this.chartArr, Serfmt::class.java) as Serfmt + if (sf != null) + return sf.3DBubbles + return false + } + set(b) { + var sf = Chart.findRec(this.chartArr, Serfmt::class.java) as Serfmt + if (sf == null) { + if (b) { + sf = Serfmt.prototype as Serfmt? + val i = Chart.findRecPosition(this.chartArr, PieFormat::class.java) + sf.parentChart = this.parentChart + this.chartArr.add(i + 1, sf) + sf.setHas3dBubbles(true) + } + } else { + sf.setHas3dBubbles(b) + } + } + + + /** + * return if data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. + */ + /** + * data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. + * + * @param b + */ + var hasShadow: Boolean + get() { + val sf = Chart.findRec(this.chartArr, Serfmt::class.java) as Serfmt + return sf?.hasShadow() ?: false + } + set(b) { + var sf = Chart.findRec(this.chartArr, Serfmt::class.java) as Serfmt + if (sf == null) { + if (b) { + sf = Serfmt.prototype as Serfmt? + val i = Chart.findRecPosition(this.chartArr, PieFormat::class.java) + sf.parentChart = this.parentChart + this.chartArr.add(i + 1, sf) + sf.setHasShadow(true) + } + } else { + sf.setHasShadow(b) + } + } + + /** + * return percentage=distance of pie slice from center of pie as % + */ + /** + * percentage=distance of pie slice from center of pie as % + * + * @param p + */ + var percentage: Int + get() { + val pf = Chart.findRec(this.chartArr, PieFormat::class.java) as PieFormat + return pf?.getPercentage()?.toInt() ?: 0 + + } + set(p) { + var pf = Chart.findRec(this.chartArr, PieFormat::class.java) as PieFormat + if (pf == null) { + setHasLines(LineFormat.NONE) + pf = Chart.findRec(this.chartArr, PieFormat::class.java) as PieFormat + } + pf.setPercentage(p.toShort()) + } + + /** + * returns true if has data labels + * + * @return + */ + val hasDataLabels: Boolean + get() = this.getAttachedLabelRec(false) != null + + /** + * return a string of ALL the label options chosen. One or more of: + * * Value + * * ValuePerecentage + * * CategoryPercentage // Pie only + * * CategoryLabel + * * BubbleLabel + * * SeriesLabel + * + * @return string true or false + */ + val dataLabelType: String? + get() { + val al = this.getAttachedLabelRec(false) + return al?.type + } + + /** + * return the data label int or 0 if no data labels chosen + * + * @return + */ + val dataLabelTypeInt: Int + get() { + val al = this.getAttachedLabelRec(false) + return al?.typeInt ?: 0 + } + + /** + * returns type of marker, if any

                      + * 0 = no marker

                      + * 1 = square

                      + * 2 = diamond

                      + * 3 = triangle

                      + * 4 = X

                      + * 5 = star

                      + * 6 = Dow-Jones

                      + * 7 = standard deviation

                      + * 8 = circle

                      + * 9 = plus sign + * + * @return + */ + /** + * 0 = no marker

                      + * 1 = square

                      + * 2 = diamond

                      + * 3 = triangle

                      + * 4 = X

                      + * 5 = star

                      + * 6 = Dow-Jones

                      + * 7 = standard deviation

                      + * 8 = circle

                      + * 9 = plus sign + */ + // default actually looks like: 2, 1, 5, 4 ... + // these records come in a set + // creates default bar shape==0, 0 + // shouldn't get here but it goes + // dunno, add to end + var markerFormat: Int + get() { + val mf = Chart.findRec(this.chartArr, MarkerFormat::class.java) as MarkerFormat + return mf?.markerFormat ?: 0 + + } + set(marker) { + var mf = Chart.findRec(this.chartArr, MarkerFormat::class.java) as MarkerFormat + if (mf == null) { + if (chartArr.isEmpty()) { + val cs = Chart3DBarShape() + cs.opcode = XLSConstants.CHART3DBARSHAPE + this.addChartRecord(cs) + val lf = LineFormat.prototype as LineFormat? + lf!!.parentChart = this.parentChart + lf.lineStyle = 5 + chartArr.add(lf) + val af = AreaFormat.prototype as AreaFormat? + af!!.parentChart = this.parentChart + chartArr.add(af) + val pf = PieFormat.prototype as PieFormat? + pf!!.parentChart = this.parentChart + chartArr.add(pf) + mf = MarkerFormat.prototype as MarkerFormat? + mf.parentChart = this.parentChart + chartArr.add(mf) + } else { + mf = MarkerFormat.prototype as MarkerFormat? + mf.parentChart = this.parentChart + val z = Chart.findRecPosition(chartArr, PieFormat::class.java) + if (z > -1) + chartArr.add(z + 1, mf) + else + chartArr.add(mf) + } + } + mf.setMarkerFormat(marker.toShort()) + } + + + /* + public int[] getDataLabelsPIE(int defaultdl) { + i++; + //ArrayList dls= new ArrayList(); + int[] dls= new int[chartArr.size()-i-1]; + int j= 0; + for (; i < chartArr.size(); i++) { + if (chartArr.get(i) instanceof DataFormat) { + DataFormat df= ((DataFormat) chartArr.get(i)); + AttachedLabel al= (AttachedLabel) Chart.findRec(df.chartArr, AttachedLabel.class); + if (al!=null) + dls[j]= al.getTypeInt(); + dls[j++]|=defaultdl; + } + } + return dls; + } +*/ + + /** + * get the bg color identified by this DataFormat + *

                      Usually part of a series group of records + * + * @return + */ + val bgColor: String? + get() = Frame.getBgColor(chartArr) + + /** + * returns (creates if necessary) the area format for this series- controls bar/series colors + * + * @return AreaFormat record + */ + private// NOTE: below list of records is what has been observed in Excel 2003 chart files - + // unsure if need marker format always ? + val areaFormat: AreaFormat? + get() { + var af = Chart.findRec(this.chartArr, AreaFormat::class.java) as AreaFormat + if (af == null) { + af = AreaFormat.getPrototype(0) as AreaFormat + this.addChartRecord(LineFormat.prototype) + this.addChartRecord(af) + this.addChartRecord(PieFormat.prototype) + this.addChartRecord(MarkerFormat.prototype) + } + return af + } + + override fun init() { + super.init() + val rkdata = this.data + pointNumber = ByteTools.readShort(rkdata!![0].toInt(), rkdata[1].toInt()) + seriesIndex = ByteTools.readShort(rkdata[2].toInt(), rkdata[3].toInt()) + seriesNumber = ByteTools.readUnsignedShort(rkdata[4], rkdata[5]).toShort() + } + + + fun initNew() { + this.opcode = XLSConstants.DATAFORMAT + this.data = PROTOTYPE_BYTES + this.init() + val cs = Chart3DBarShape() + cs.opcode = XLSConstants.CHART3DBARSHAPE // creates default bar shape==0, 0 + this.addChartRecord(cs) + } + + fun setPointNumber(idx: Int) { + pointNumber = idx.toShort() + val rkdata = this.data + val num = ByteTools.shortToLEBytes(idx.toShort()) + rkdata[0] = num[0] + rkdata[1] = num[1] + this.data = rkdata + } + + /** + * Set the series index + */ + fun setSeriesIndex(idx: Int) { + seriesIndex = idx.toShort() + val rkdata = this.data + val num = ByteTools.shortToLEBytes(idx.toShort()) + rkdata[2] = num[0] + rkdata[3] = num[1] + this.data = rkdata + } + + fun setSeriesNumber(idx: Int) { + seriesNumber = idx.toShort() + val rkdata = this.data + val num = ByteTools.shortToLEBytes(idx.toShort()) + rkdata[4] = num[0] + rkdata[5] = num[1] + this.data = rkdata + } + + private fun getAttachedLabelRec(bCreate: Boolean): AttachedLabel? { + var al: AttachedLabel? = null + al = Chart.findRec(this.chartArr, AttachedLabel::class.java) as AttachedLabel + if (al == null && bCreate) { // basic options are handled via AttachedLabel rec + al = AttachedLabel.prototype as AttachedLabel? + val z = Chart.findRecPosition(this.chartArr, MarkerFormat::class.java) + if (z > 0) + chartArr.add(z + 1, al) + else + this.addChartRecord(al) + } + return al + } + + private fun getAreaFormatRec(bCreate: Boolean): AreaFormat? { + var af = Chart.findRec(this.chartArr, AreaFormat::class.java) as AreaFormat + if (af == null) { + af = AreaFormat.getPrototype(0) as AreaFormat + this.addChartRecord(LineFormat.prototype) + this.addChartRecord(af) + this.addChartRecord(PieFormat.prototype) + this.addChartRecord(MarkerFormat.prototype) + } + return af + } + + /** + * set the shape bit of the associated XLSRecord + * + * @param shape + */ + fun setShape(shape: Int) { + val cs = Chart.findRec(this.chartArr, Chart3DBarShape::class.java) as Chart3DBarShape + cs.shape = shape.toShort() + } + + /** + * set smooth lines setting (applicable for line, scatter charts) + * + * @param smooth + */ + fun setSmoothLines(smooth: Boolean) { + var sf = Chart.findRec(this.chartArr, Serfmt::class.java) as Serfmt + if (sf == null) { + if (smooth) { + setHasLines() + sf = Serfmt.prototype as Serfmt? + val i = Chart.findRecPosition(this.chartArr, PieFormat::class.java) + this.chartArr.add(i + 1, sf) + sf.setSmoothedLine(true) + } + } else { + sf.setSmoothedLine(smooth) + } + } + + /** + * sets this chart to have lines (line chart, radar, scatter ...) of the specific line style + *

                      Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + */ + @JvmOverloads + fun setHasLines(lineStyle: Int = 0) { + val l = Chart.findRec(this.chartArr, LineFormat::class.java) as LineFormat + if (l == null) { // these come as a group - assume none or only has Chart3DBarShape ... + var z = Chart.findRecPosition(chartArr, Chart3DBarShape::class.java) + 1 + val lf = LineFormat.getPrototype(lineStyle, -1) as LineFormat + chartArr.add(z++, lf) + val af = AreaFormat.prototype as AreaFormat? + af!!.parentChart = parentChart + chartArr.add(z++, af) + val pf = PieFormat.prototype as PieFormat? + pf!!.parentChart = parentChart + chartArr.add(z++, pf) + val mf = MarkerFormat.prototype as MarkerFormat? + mf!!.parentChart = parentChart + chartArr.add(z++, mf) + + } else { + l.lineStyle = lineStyle + } + } + + /** + * sets the data labels to the desired type: + * * "ShowValueLabel" + * * "ShowValueAsPercent" + * * "ShowLabelAsPercent" + * * "ShowLabel" + * * "ShowSeriesName" + * * "ShowBubbleLabel" + * + * @param type + */ + fun setDataLabels(type: String) { + val al = this.getAttachedLabelRec(true) + al!!.setType(type, "1") + } + + /** + * return if has the specified data label + * * "ShowValueLabel" + * * "ShowValueAsPercent" + * * "ShowLabelAsPercent" + * * "ShowLabel" + * * "ShowSeriesName" + * * "ShowBubbleLabel" + * + * @param type String option + * @return string true or false + */ + fun getDataLabelType(type: String): String? { + val al = this.getAttachedLabelRec(false) + return al?.getType(type) + } + + /** + * sets the data labels for the entire chart (as opposed to a specific series/data point). + * A combination of: + * * SHOWVALUE= 0x1; + * * SHOWVALUEPERCENT= 0x2; + * * SHOWCATEGORYPERCENT= 0x4; + * * SHOWCATEGORYLABEL= 0x10; + * * SHOWBUBBLELABEL= 0x20; + * * SHOWSERIESLABEL= 0x40; + */ + fun setHasDataLabels(dl: Int) { + val al = this.getAttachedLabelRec(true) + al!!.setType(dl.toShort()) + } + + // 0x893 CtlMltFrt + // [-98, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 13, 19, 0, 6, -128, 34, 0, 35, 0, -67, 64, 0, 0] + + /** + * sets the color identified by this DataFormat in the group of records + * belonging to the parent series + * + * @param clr color int + */ + fun setSeriesColor(clr: Int) { + val af = getAreaFormatRec(true) + // Finally got the AreaFormat record that governs the color for this series + af!!.seticvFore(clr) + } + + /** + * sets the color identified by this DataFormat in the group of records + * belonging to the parent series + * + * @param clr Hex color string + */ + fun setSeriesColor(clr: String) { + val af = getAreaFormatRec(true) + // Finally got the AreaFormat record that governs the color for this series + af!!.seticvFore(clr) + } + + /** + * sets the color of the desired pie slice + * + * @param clr color int + * @param slice 0-based pie slice number + */ + fun setPieSliceColor(clr: String, slice: Int) { + val af = getAreaFormatPie(slice) + af.seticvFore(clr) + } + + /** + * sets the color of the desired pie slice + * + * @param clr color int + * @param slice 0-based pie slice number + */ + fun setPieSliceColor(clr: Int, slice: Int) { + val af = getAreaFormatPie(slice) + af.seticvFore(clr) + } + + /** + * returns (creates if necessary) the area format for the desired pie slice (pie charts only) + * + * @param slice int 0-based slice nmber + * @return AreaFormat record + */ + private fun getAreaFormatPie(slice: Int): AreaFormat { + // must add x number of dataformat recs + // FINISH- not 100% + /* if (i==s.chartArr.size()) { + df= (DataFormat) DataFormat.getPrototypeWithFormatRecs(0); + df.setPointNumber(slice); + PieFormat pf= (PieFormat) Chart.findRec(df.chartArr, PieFormat.class); + pf.setPercentage((short)25); // default percentage + AttachedLabel al= (AttachedLabel) AttachedLabel.getPrototype(); + al.setType("CandP"); // default + df.addChartRecord(al); + s.chartArr.add(s.chartArr.size()-1, df); // -1 to skip SERTOCRT rec + } +*/ + return Chart.findRec(chartArr, AreaFormat::class.java) as AreaFormat + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -3526272512004348462L + + /** + * Create a new dataformat + * + * @param parentChart + * @return + */ + // creates default bar shape==0, 0 + val prototype: XLSRecord? + get() { + val df = DataFormat() + df.opcode = XLSConstants.DATAFORMAT + df.data = df.PROTOTYPE_BYTES + df.init() + val cs = Chart3DBarShape() + cs.opcode = XLSConstants.CHART3DBARSHAPE + df.addChartRecord(cs) + return df + } + + fun getPrototypeWithFormatRecs(parentChart: Chart): XLSRecord { + return getPrototypeWithFormatRecs(0, parentChart) + } + + + /** + * Create DataFormat Record that HOPEFULLY reflects the necessary associated recs + * + * @param type + * @return DataFormat Record + */ + fun getPrototypeWithFormatRecs(seriesNumber: Int, parentChart: Chart): XLSRecord { + val df = DataFormat() + df.opcode = XLSConstants.DATAFORMAT + df.data = df.PROTOTYPE_BYTES + df.init() + val cs = Chart3DBarShape() + cs.opcode = XLSConstants.CHART3DBARSHAPE // creates default bar shape==0, 0 + df.addChartRecord(cs) + df.setSeriesNumber(seriesNumber) + val lf = LineFormat.prototype as LineFormat? + df.addChartRecord(lf) + val af = AreaFormat.prototype as AreaFormat? + af!!.parentChart = parentChart + df.addChartRecord(af) + val pf = PieFormat.prototype as PieFormat? + pf!!.parentChart = parentChart + df.addChartRecord(pf) + val mf = MarkerFormat.prototype as MarkerFormat? + mf!!.parentChart = parentChart + df.addChartRecord(mf) + /* pieformat: + MUST not exist on chart group types other than ---> doesn't appear true (???) + pie, + doughnut, + bar of pie, or + pie of pie. + MUST not exist if the chart group type is doughnut and the series is not the outermost series. + MUST not exist on the data points on the secondary bar/pie of a bar of pie chart group. + */ + return df + } + } + + +} +/** + * sets this chart to have lines (line chart, radar, scatter ...) + */ diff --git a/src/main/java/io/starter/formats/XLS/charts/DataLabExt.java b/src/main/java/io/starter/formats/XLS/charts/DataLabExt.kt similarity index 61% rename from src/main/java/io/starter/formats/XLS/charts/DataLabExt.java rename to src/main/java/io/starter/formats/XLS/charts/DataLabExt.kt index 33c5472..6bd447a 100644 --- a/src/main/java/io/starter/formats/XLS/charts/DataLabExt.java +++ b/src/main/java/io/starter/formats/XLS/charts/DataLabExt.kt @@ -20,43 +20,47 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * DATALABEXT: Chart Data Label Extension (86Ah) + * **DATALABEXT: Chart Data Label Extension (86Ah)** * Introduced in Excel 10 (2002) this BIFF record is an FRT record * for Charts. This record is the parent of DATALABEXTCONTENTS, but * contains no other information. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =086Ah * 6 grbitFrt 2 FRT flags; must be zero * 8 (unused) 8 Reserved; must be zero */ -public class DataLabExt extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1868700214505277636L; - - public void init() { - super.init(); - } +class DataLabExt : GenericChartObject(), ChartObject { - private byte[] PROTOTYPE_BYTES = new byte[]{106, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + private val PROTOTYPE_BYTES = byteArrayOf(106, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - public static XLSRecord getPrototype() { - DataLabExt dl = new DataLabExt(); - dl.setOpcode(DATALABEXTCONTENTS); - dl.setData(dl.PROTOTYPE_BYTES); - dl.init(); - return dl; + override fun init() { + super.init() } - private void updateRecord() { + private fun updateRecord() {} + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -1868700214505277636L + + val prototype: XLSRecord? + get() { + val dl = DataLabExt() + dl.opcode = XLSConstants.DATALABEXTCONTENTS + dl.data = dl.PROTOTYPE_BYTES + dl.init() + return dl + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.java b/src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.kt similarity index 59% rename from src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.java rename to src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.kt index 98c37f8..b0505ee 100644 --- a/src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.java +++ b/src/main/java/io/starter/formats/XLS/charts/DataLabExtContents.kt @@ -20,15 +20,16 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools /** * The DataLabExtContents record specifies the contents of an extended data label. * 12 bytes- FRTHEADER - *

                      + * + * * A - fSerName (1 bit): A bit that specifies whether the name of the series is displayed in the extended data label. * B - fCatName (1 bit): A bit that specifies whether the category (3) name, or the horizontal value on bubble or scatter chart groups, is displayed in the extended data label. MUST be a value from the following table: * 0 Neither of the data values are displayed in the extended data label. @@ -44,32 +45,62 @@ * rgchSep (variable): A case-sensitive XLUnicodeStringMin2 structure that specifies the string that is inserted between every data value to form the extended data label. * For example, if fCatName and fValue are set to 1, the labels will look like "Category NameData Value". * The length of the string is contained in the cch field of the XLUnicodeStringMin2 structure. - */ -public class DataLabExtContents extends GenericChartObject implements ChartObject { + */ +class DataLabExtContents : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var fSerName: Boolean = false + private var fCatName: Boolean = false + private var fValue: Boolean = false + private var fPercent: Boolean = false + private var fBubSizes: Boolean = false + + private val PROTOTYPE_BYTES = byteArrayOf(107, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0) + /** - * serialVersionUID + * return extended data label options as an int + *

                      SHOWVALUE= 0x1; + *

                      SHOWVALUEPERCENT= 0x2; + *

                      SHOWCATEGORYPERCENT= 0x4; + *

                      SMOOTHEDLINE= 0x8; + *

                      SHOWCATEGORYLABEL= 0x10; + *

                      SHOWBUBBLELABEL= 0x20; + *

                      SHOWSERIESLABEL= 0x40; + * + * @return a combination of data label options above or 0 if none */ - private static final long serialVersionUID = -1228364285066204304L; - private short grbit; - private boolean fSerName, fCatName, fValue, fPercent, fBubSizes; + val typeInt: Int + get() { + var grbit: Short = 0 + if (fValue) + grbit = ByteTools.updateGrBit(grbit, true, 0) + if (fPercent) + grbit = ByteTools.updateGrBit(grbit, true, 1) + if (fCatName) + grbit = ByteTools.updateGrBit(grbit, true, 4) + if (fBubSizes) + grbit = ByteTools.updateGrBit(grbit, true, 5) + if (fSerName) + grbit = ByteTools.updateGrBit(grbit, true, 6) + return grbit.toInt() + } - public void init() { - super.init(); - byte[] data = this.getData(); - grbit = ByteTools.readShort(data[12], data[13]); - fSerName = (grbit & 0x1) == 0x1; - fCatName = (grbit & 0x2) == 0x2; - fValue = (grbit & 0x4) == 0x4; - fPercent = (grbit & 0x8) == 0x8; - fBubSizes = (grbit & 0x10) == 0x10; - if (data.length > 14) { // seperator value -/* not following documentation: TODO: figure out + override fun init() { + super.init() + val data = this.data + grbit = ByteTools.readShort(data!![12].toInt(), data[13].toInt()) + fSerName = grbit and 0x1 == 0x1 + fCatName = grbit and 0x2 == 0x2 + fValue = grbit and 0x4 == 0x4 + fPercent = grbit and 0x8 == 0x8 + fBubSizes = grbit and 0x10 == 0x10 + if (data.size > 14) { // seperator value + /* not following documentation: TODO: figure out int cch= data[14]; byte[] sepbytes= this.getBytesAt(16, data.length-16); try { String sep= new String(sepbytes, UNICODEENCODING); } catch(Exception e) { - + } */ } @@ -77,40 +108,19 @@ public void init() { } - private byte[] PROTOTYPE_BYTES = new byte[]{107, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}; - - public static XLSRecord getPrototype() { - DataLabExtContents dlc = new DataLabExtContents(); - dlc.setOpcode(DATALABEXTCONTENTS); - dlc.setData(dlc.PROTOTYPE_BYTES); - dlc.init(); - return dlc; - } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -1228364285066204304L - /** - * return extended data label options as an int - *
                      SHOWVALUE= 0x1; - *
                      SHOWVALUEPERCENT= 0x2; - *
                      SHOWCATEGORYPERCENT= 0x4; - *
                      SMOOTHEDLINE= 0x8; - *
                      SHOWCATEGORYLABEL= 0x10; - *
                      SHOWBUBBLELABEL= 0x20; - *
                      SHOWSERIESLABEL= 0x40; - * - * @return a combination of data label options above or 0 if none - */ - public int getTypeInt() { - short grbit = 0; - if (fValue) - grbit = ByteTools.updateGrBit(grbit, true, 0); - if (fPercent) - grbit = ByteTools.updateGrBit(grbit, true, 1); - if (fCatName) - grbit = ByteTools.updateGrBit(grbit, true, 4); - if (fBubSizes) - grbit = ByteTools.updateGrBit(grbit, true, 5); - if (fSerName) - grbit = ByteTools.updateGrBit(grbit, true, 6); - return grbit; + val prototype: XLSRecord? + get() { + val dlc = DataLabExtContents() + dlc.opcode = XLSConstants.DATALABEXTCONTENTS + dlc.data = dlc.PROTOTYPE_BYTES + dlc.init() + return dlc + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/DefaultText.java b/src/main/java/io/starter/formats/XLS/charts/DefaultText.kt similarity index 60% rename from src/main/java/io/starter/formats/XLS/charts/DefaultText.java rename to src/main/java/io/starter/formats/XLS/charts/DefaultText.kt index a716ae4..7cb9b3d 100644 --- a/src/main/java/io/starter/formats/XLS/charts/DefaultText.java +++ b/src/main/java/io/starter/formats/XLS/charts/DefaultText.kt @@ -21,14 +21,15 @@ * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools /** - * DefaultText: Default Data Label Text Properties(0x1024) - *

                      + * **DefaultText: Default Data Label Text Properties(0x1024)** + * + * * id (2 bytes): An unsigned integer that specifies the text elements that are formatted using the position and appearance information specified by the Text record immediately following this record. MUST be a value from the following table. * If this record is in a sequence of records that conforms to the CRT rule as specified by the Chart Sheet Substream ABNF, then this field MUST be 0x0000 or 0x0001. If this record is not in a sequence of records that conforms to the CRT rule as specified by the Chart Sheet Substream ABNF, then this field MUST be 0x0002 or 0x0003. * Value Meaning @@ -37,45 +38,48 @@ * 0x0002 Format all Text records in the chart where the value of fScaled of the associated FontInfo structure is equal to 0. * 0x0003 Format all Text records in the chart where the value of fScaled of the associated FontInfo structure is equal to 1. * */ -public class DefaultText extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6845131434077760152L; +class DefaultText : GenericChartObject(), ChartObject { - private short grbit = 0; + private var grbit: Short = 0 - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } + private val PROTOTYPE_BYTES = byteArrayOf(0, 0) + + // try to interpret!!! + val type: Int + get() = grbit.toInt() - // 20070716 KSC: Need to create new records - public static XLSRecord getPrototype() { - DefaultText t = new DefaultText(); - t.setOpcode(DEFAULTTEXT); - t.setData(t.PROTOTYPE_BYTES); - t.init(); - return t; + override fun init() { + super.init() + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) } - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; + fun setType(type: Short) { + grbit = type + val b = ByteTools.shortToLEBytes(grbit) + this.data[0] = b[0] + this.data[1] = b[1] + } - public static final int TYPE_SHOWLABELS = 0; - public static final int TYPE_VALUELABELS = 1; - public static final int TYPE_ALLTEXT = 2; - public static final int TYPE_UNKNOWN = 3; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 6845131434077760152L - // try to interpret!!! - public int getType() { - return grbit; - } + // 20070716 KSC: Need to create new records + val prototype: XLSRecord? + get() { + val t = DefaultText() + t.opcode = XLSConstants.DEFAULTTEXT + t.data = t.PROTOTYPE_BYTES + t.init() + return t + } - public void setType(short type) { - grbit = type; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; + val TYPE_SHOWLABELS = 0 + val TYPE_VALUELABELS = 1 + val TYPE_ALLTEXT = 2 + val TYPE_UNKNOWN = 3 } } diff --git a/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.java b/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.java deleted file mode 100644 index d804645..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartSeriesHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; - -public class DoughnutChart extends ChartType { - private Pie doughnut = null; - - public DoughnutChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - doughnut = (Pie) charttype; - } - - /** - * return the JSON that - * - * @param seriesvals - * @param range - * @return - */ - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) - throws JSONException { - JSONObject chartObjectJSON = new JSONObject(); - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // TODO: check out labels options chosen: default label is Category name + percentage ... - double yMax = 0.0, yMin = 0.0; - double len = 0.0; - JSONArray pieSeries = new JSONArray(); - try { - String range = series[0].getCategoryRange(); // 20080516 KSC: retrieve cat range instead of parameter - JSONArray cats = CellRange.getValuesAsJSON(range, wbh); // parse category range into JSON Array - JSONArray seriesvals = CellRange.getValuesAsJSON(series[0].getSeriesRange(), wbh); - double piesum = 0; - for (int k = 0; k < seriesvals.length(); k++) { - piesum += seriesvals.getDouble(k); - yMax = Math.max(yMax, seriesvals.getDouble(k)); - yMin = Math.min(yMin, seriesvals.getDouble(k)); - } - double percent = 100 / piesum; - for (int k = 0; k < seriesvals.length(); k++) { - JSONObject piepoint = new JSONObject(); - piepoint.put("y", seriesvals.getDouble(k)); - piepoint.put("text", cats.getString(k) + "\n" + Math.round(percent * seriesvals.getDouble(k)) + "%"); - piepoint.put("color", FormatConstants.SVGCOLORSTRINGS[series[k].getPieChartSliceColor(k)]); - piepoint.put("stroke", getDarkColor()); - pieSeries.put(piepoint); - } - len = seriesvals.length(); - } catch (Exception e) { - // TODO: warning ...? - } - // 20090717 KSC: input outside of try/catch to always set - minMax[0] = new Double(yMin); - minMax[1] = new Double(yMax); - minMax[2] = new Double(len); - chartObjectJSON.put("Series", pieSeries); - chartObjectJSON.put("SeriesFills", ""); // not applicable for pie charts; color is set above - return chartObjectJSON; - } - - /** - * return the type JSON for this Chart Object - * - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON = new JSONObject(); - typeJSON.put("labelOffset", -25); // default - typeJSON.put("precision", 0); // default - rounds percentages up - typeJSON.put("type", "Pie"); - // TODO: Interpret distance ... - return typeJSON; - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - StringBuffer svg = new StringBuffer(); - if (series.size() == 0) { - Logger.logErr("DoughnutChart.getSVG: error in series"); - return ""; - } - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - boolean threeD = this.isThreeD(); - final int LABELOFFSET = 15; - - int n = series.size(); - double centerx = 0.0, centery = 0.0, radius = 0.0; - double radiusy = 0.0; - centerx = w / 2 + chartMetrics.get("x"); - centery = h / 2 + chartMetrics.get("y"); - if (threeD) { - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - } - svg.append("\r\n"); - - //radius= Math.min(w, h)/2.3; // should take up almost entire w/h of chart - radius = Math.min(w, h) / 1.9; // should take up almost entire w/h of chart - double r0 = (radius / 2) / n; // radius/2 to account for hole - double r = radius; // start at outside and work inside - for (int i = n - 1; i >= 0; i--) { // for each series - double[] curseries = (double[]) series.get(i); // series data points - double total = 0.0; - for (int j = 0; j < curseries.length; j++) { // get total in order to calculate percentages - total += curseries[j]; - } - if (dls.length == 1) { // no seriess-specific data labels; expand to entire series for loop below - int dl = dls[0]; - dls = new int[curseries.length]; - for (int z = 0; z < dls.length; z++) - dls[z] = dl; - } - svg.append("\r\n"); - double x = centerx + r; - double y = centery; - String path = ""; - double percentage = 0; - double lasta = 0; - int largearcflag = 0; - int sweepflag = 0; - // Now create each pie wedge according to it's percentage value - for (int j = 0; j < curseries.length; j++) { - percentage = curseries[j] / total; - double angle = (percentage * 360) + lasta; - double x1 = centerx + r * (Math.cos(Math.toRadians(angle))); - double y1 = centery - r * (Math.sin(Math.toRadians(angle))); - if ((percentage * 360) > 180) { - sweepflag = 0; - largearcflag = 1; - } else - largearcflag = 0; - path = "M" + centerx + " " + centery + " L" + x + " " + y + " A" + r + " " + r + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z"; - // paint wedge of color to center of chart -- the inner will overwrite so don't have to worry about segments and arcs, etc - svg.append("\r\n"); - // data labels - String l = getSVGDataLabels(dls, axisMetrics, curseries[j], percentage, j, legends, categories[j].toString()); - if (l != null) { - double halfa = ((percentage / 2) * 360) + lasta; // center in area - double x2 = centerx + (r - r0 / 2) * (Math.cos(Math.toRadians(halfa))); - double y2 = centery - (r - r0 / 2) * (Math.sin(Math.toRadians(halfa))); - svg.append("" + l + "\r\n"); - } - lasta = angle; - x = x1; - y = y1; - } // each point in current series - r -= r0; - } // each series - // complete inner circle & create "hole" - svg.append("\r\n"); - svg.append("\r\n"); - - return svg.toString(); - } - /** - * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), - * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). - * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, - * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) - * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). - * A value of 0 causes the arc to be drawn in a "negative-angle" direction - * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). - */ - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - cooxml.append(""); - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - // TODO: firstSLiceAng - cooxml.append(""); - cooxml.append(""); - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.kt b/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.kt new file mode 100644 index 0000000..eca7056 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/DoughnutChart.kt @@ -0,0 +1,256 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ChartSeriesHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.FormatConstants +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +import java.util.ArrayList +import java.util.HashMap + +class DoughnutChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + private var doughnut: Pie? = null + + /** + * return the type JSON for this Chart Object + * + * @return + */ + override// default + // default - rounds percentages up + // TODO: Interpret distance ... + val typeJSON: JSONObject + @Throws(JSONException::class) + get() { + val typeJSON = JSONObject() + typeJSON.put("labelOffset", -25) + typeJSON.put("precision", 0) + typeJSON.put("type", "Pie") + return typeJSON + } + + init { + doughnut = charttype as Pie + } + + /** + * return the JSON that + * + * @param seriesvals + * @param range + * @return + */ + @Throws(JSONException::class) + fun getJSON(series: Array, wbh: WorkBookHandle, minMax: Array): JSONObject { + val chartObjectJSON = JSONObject() + // Type JSON + chartObjectJSON.put("type", this.typeJSON) + + // TODO: check out labels options chosen: default label is Category name + percentage ... + var yMax = 0.0 + var yMin = 0.0 + var len = 0.0 + val pieSeries = JSONArray() + try { + val range = series[0].categoryRange // 20080516 KSC: retrieve cat range instead of parameter + val cats = CellRange.getValuesAsJSON(range, wbh) // parse category range into JSON Array + val seriesvals = CellRange.getValuesAsJSON(series[0].seriesRange, wbh) + var piesum = 0.0 + for (k in 0 until seriesvals.length()) { + piesum += seriesvals.getDouble(k) + yMax = Math.max(yMax, seriesvals.getDouble(k)) + yMin = Math.min(yMin, seriesvals.getDouble(k)) + } + val percent = 100 / piesum + for (k in 0 until seriesvals.length()) { + val piepoint = JSONObject() + piepoint.put("y", seriesvals.getDouble(k)) + piepoint.put("text", cats.getString(k) + "\n" + Math.round(percent * seriesvals.getDouble(k)) + "%") + piepoint.put("color", FormatConstants.SVGCOLORSTRINGS[series[k].getPieChartSliceColor(k)]) + piepoint.put("stroke", darkColor) + pieSeries.put(piepoint) + } + len = seriesvals.length().toDouble() + } catch (e: Exception) { + // TODO: warning ...? + } + + // 20090717 KSC: input outside of try/catch to always set + minMax[0] = yMin + minMax[1] = yMax + minMax[2] = len + chartObjectJSON.put("Series", pieSeries) + chartObjectJSON.put("SeriesFills", "") // not applicable for pie charts; color is set above + return chartObjectJSON + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + val svg = StringBuffer() + if (series!!.size == 0) { + Logger.logErr("DoughnutChart.getSVG: error in series") + return "" + } + var dls = dataLabelInts // get array of data labels (can be specific per series ...) + val threeD = this.isThreeD + val LABELOFFSET = 15 + + val n = series.size + var centerx = 0.0 + var centery = 0.0 + var radius = 0.0 + val radiusy = 0.0 + centerx = w / 2 + chartMetrics["x"] + centery = h / 2 + chartMetrics["y"] + if (threeD) { + svg.append("\r\n") + svg.append("\r\n") + svg.append("\r\n") + svg.append("\r\n") + svg.append("\r\n") + } + svg.append("\r\n") + + //radius= Math.min(w, h)/2.3; // should take up almost entire w/h of chart + radius = Math.min(w, h) / 1.9 // should take up almost entire w/h of chart + val r0 = radius / 2 / n // radius/2 to account for hole + var r = radius // start at outside and work inside + for (i in n - 1 downTo 0) { // for each series + val curseries = series[i] as DoubleArray // series data points + var total = 0.0 + for (j in curseries.indices) { // get total in order to calculate percentages + total += curseries[j] + } + if (dls.size == 1) { // no seriess-specific data labels; expand to entire series for loop below + val dl = dls[0] + dls = IntArray(curseries.size) + for (z in dls.indices) + dls[z] = dl + } + svg.append("\r\n") + var x = centerx + r + var y = centery + var path = "" + var percentage = 0.0 + var lasta = 0.0 + var largearcflag = 0 + var sweepflag = 0 + // Now create each pie wedge according to it's percentage value + for (j in curseries.indices) { + percentage = curseries[j] / total + val angle = percentage * 360 + lasta + val x1 = centerx + r * Math.cos(Math.toRadians(angle)) + val y1 = centery - r * Math.sin(Math.toRadians(angle)) + if (percentage * 360 > 180) { + sweepflag = 0 + largearcflag = 1 + } else + largearcflag = 0 + path = "M" + centerx + " " + centery + " L" + x + " " + y + " A" + r + " " + r + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z" + // paint wedge of color to center of chart -- the inner will overwrite so don't have to worry about segments and arcs, etc + svg.append("\r\n") + // data labels + val l = getSVGDataLabels(dls, axisMetrics, curseries[j], percentage, j, legends, categories!![j].toString()) + if (l != null) { + val halfa = percentage / 2 * 360 + lasta // center in area + val x2 = centerx + (r - r0 / 2) * Math.cos(Math.toRadians(halfa)) + val y2 = centery - (r - r0 / 2) * Math.sin(Math.toRadians(halfa)) + svg.append("$l\r\n") + } + lasta = angle + x = x1 + y = y1 + } // each point in current series + r -= r0 + } // each series + // complete inner circle & create "hole" + svg.append("\r\n") + svg.append("\r\n") + + return svg.toString() + } + /** + * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), + * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). + * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, + * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) + * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). + * A value of 0 causes the arc to be drawn in a "negative-angle" direction + * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). + */ + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + cooxml.append("") + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + // TODO: firstSLiceAng + cooxml.append("") + cooxml.append("") + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Dropbar.java b/src/main/java/io/starter/formats/XLS/charts/Dropbar.kt similarity index 55% rename from src/main/java/io/starter/formats/XLS/charts/Dropbar.java rename to src/main/java/io/starter/formats/XLS/charts/Dropbar.kt index a1eee8d..b7f1d10 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Dropbar.java +++ b/src/main/java/io/starter/formats/XLS/charts/Dropbar.kt @@ -20,46 +20,46 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools /** - * Dropbar: Defines Drop Bars (0x103d) + * **Dropbar: Defines Drop Bars (0x103d)** * Controls up or down bars on a line (or stock, for 2007 v) chart with multiple series * the first dropBar record controls upBars * the second record controls downBars * Also, if these records exist, SeriesList cSer > 1 - *

                      - *

                      - *

                      + * + * + * + * + * + * * pcGap (2 bytes): A signed integer that specifies the width of the gap between the up bars or the down bars. MUST be a value between 0 and 500. * The width of the gap in SPRCs can be calculated by the following formula: * Width of the gap in SPRCs = 1 + pcGap * - *
                      + *

                      * The DropBar record occurs in the ChartFormat subrecord stream after the Legend record, * and contains subrecords LineFormat, AreaFormat, [GelFrame], [ShapeProps] */ -public class Dropbar extends GenericChartObject implements ChartObject { +class Dropbar : GenericChartObject(), ChartObject { /** - * serialVersionUID + * returns the width of the gap between the up bars or the down bars. + *

                      The gap width is a value between 0 and 500. + *

                      The width of the gap in SPRCs can be calculated by the following formula: + * Width of the gap in SPRCs = 1 + pcGap + * + * @return */ - private static final long serialVersionUID = 6826327230442065566L; - short pcGap; - - public void init() { - super.init(); - pcGap = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } + var gapWidth: Short = 0 + internal set - public static XLSRecord getPrototype() { - Dropbar db = new Dropbar(); - db.setOpcode(DROPBAR); - db.setData(new byte[]{-106, 0}); // 150 is default gap width - db.init(); - return db; + override fun init() { + super.init() + gapWidth = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) } /** @@ -69,23 +69,11 @@ public static XLSRecord getPrototype() { * * @param gap */ - public void setGapWidth(int gap) { - pcGap = (short) gap; - byte[] b = ByteTools.shortToLEBytes(pcGap); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - /** - * returns the width of the gap between the up bars or the down bars. - *
                      The gap width is a value between 0 and 500. - *
                      The width of the gap in SPRCs can be calculated by the following formula: - * Width of the gap in SPRCs = 1 + pcGap - * - * @return - */ - public short getGapWidth() { - return pcGap; + fun setGapWidth(gap: Int) { + gapWidth = gap.toShort() + val b = ByteTools.shortToLEBytes(gapWidth) + this.data[0] = b[0] + this.data[1] = b[1] } /** @@ -93,22 +81,39 @@ public short getGapWidth() { * * @return */ - public StringBuffer getOOXML(boolean upBars) { - StringBuffer cooxml = new StringBuffer(); - String tag; + fun getOOXML(upBars: Boolean): StringBuffer { + val cooxml = StringBuffer() + val tag: String if (upBars) - tag = "c:upBars>"; + tag = "c:upBars>" else - tag = "c:downBars>"; - cooxml.append("<" + tag); - LineFormat lf = (LineFormat) chartArr.get(0); - cooxml.append(lf.getOOXML()); + tag = "c:downBars>" + cooxml.append("<$tag") + val lf = chartArr[0] as LineFormat + cooxml.append(lf.ooxml) // TODO: finish this logic - if (!parentChart.getWorkBook().getIsExcel2007()) { - AreaFormat af = (AreaFormat) chartArr.get(1); - cooxml.append(af.getOOXML()); + if (!parentChart!!.workBook!!.isExcel2007) { + val af = chartArr[1] as AreaFormat + cooxml.append(af.ooxml) } - cooxml.append(" End: End of chart substream (0x1034)
                      - *

                      + * ** End: End of chart substream (0x1034)** + * + * * End is an identifier record for the chart record type. There is no data to a end record, * and every end record must have a corrosponding begin record */ -public class End extends GenericChartObject implements ChartObject { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 9022736093645720842L; +class End : GenericChartObject(), ChartObject { - public void init() { - super.init(); + override fun init() { + super.init() } - protected static XLSRecord getPrototype() { - End bl = new End(); - bl.setOpcode(END); - bl.setData(new byte[]{}); - return bl; + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 9022736093645720842L + + val prototype: XLSRecord? + get() { + val bl = End() + bl.opcode = XLSConstants.END + bl.data = byteArrayOf() + return bl + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/EndBlock.java b/src/main/java/io/starter/formats/XLS/charts/EndBlock.kt similarity index 54% rename from src/main/java/io/starter/formats/XLS/charts/EndBlock.java rename to src/main/java/io/starter/formats/XLS/charts/EndBlock.kt index dc7268a..c07ab25 100644 --- a/src/main/java/io/starter/formats/XLS/charts/EndBlock.java +++ b/src/main/java/io/starter/formats/XLS/charts/EndBlock.kt @@ -20,17 +20,18 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools /** - * ENDBLOCK: Chart Future Record Type End Block (853h) + * **ENDBLOCK: Chart Future Record Type End Block (853h)** * Introduced in Excel 9 (2000), this BIFF record is an FRT record for * Charts that indicates end of an object's scope for Pre-Excel 9 objects. * Paired with STARTBLOCK. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0853h @@ -38,37 +39,41 @@ * 8 iObjectKind 2 Sanity check for object scope being ended * 10 (unused) 6 Reserved; must be zero */ -public class EndBlock extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1132544432743236942L; - short iObjectKind = 0; +class EndBlock : GenericChartObject(), ChartObject { + internal var iObjectKind: Short = 0 - public void init() { - super.init(); - iObjectKind = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - } // iObjectKind= 5 or 0 or 13 + private val PROTOTYPE_BYTES = byteArrayOf(83, 8, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0) - private byte[] PROTOTYPE_BYTES = new byte[]{83, 8, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0}; + override fun init() { + super.init() + iObjectKind = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + } // iObjectKind= 5 or 0 or 13 - public static XLSRecord getPrototype() { - EndBlock eb = new EndBlock(); - eb.setOpcode(ENDBLOCK); - eb.setData(eb.PROTOTYPE_BYTES); - eb.init(); - return eb; + fun setObjectKind(i: Int) { + iObjectKind = i.toShort() + updateRecord() } - public void setObjectKind(int i) { - iObjectKind = (short) i; - updateRecord(); + private fun updateRecord() { + val b = ByteTools.shortToLEBytes(iObjectKind) + this.data[4] = b[0] + this.data[5] = b[1] } - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(iObjectKind); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 1132544432743236942L + + val prototype: XLSRecord? + get() { + val eb = EndBlock() + eb.opcode = XLSConstants.ENDBLOCK + eb.data = eb.PROTOTYPE_BYTES + eb.init() + return eb + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/EndObject.java b/src/main/java/io/starter/formats/XLS/charts/EndObject.kt similarity index 63% rename from src/main/java/io/starter/formats/XLS/charts/EndObject.java rename to src/main/java/io/starter/formats/XLS/charts/EndObject.kt index f5661a3..37d0e47 100644 --- a/src/main/java/io/starter/formats/XLS/charts/EndObject.java +++ b/src/main/java/io/starter/formats/XLS/charts/EndObject.kt @@ -20,15 +20,16 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * ENDOBJECT: Chart Future Record Type End Object (855h) + * **ENDOBJECT: Chart Future Record Type End Object (855h)** * Introduced in Excel 9 (2000), this BIFF record is an FRT record * for Charts that indicates the end of an object's scope for Excel 9 and later objects. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0855h @@ -36,27 +37,30 @@ * 8 iObjectKind 2 Sanity check for object scope being ended * 10 (unused) 6 Reserved; must be zero */ -public class EndObject extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8367476159843855287L; - - public void init() { - super.init(); +class EndObject : GenericChartObject(), ChartObject { + + private val PROTOTYPE_BYTES = byteArrayOf(85, 8, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0) + + override fun init() { + super.init() } // iObjectKind of 18 appears to be Chart object - private byte[] PROTOTYPE_BYTES = new byte[]{85, 8, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0}; + private fun updateRecord() {} - public static XLSRecord getPrototype() { - EndObject eo = new EndObject(); - eo.setOpcode(ENDOBJECT); - eo.setData(eo.PROTOTYPE_BYTES); - eo.init(); - return eo; - } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8367476159843855287L - private void updateRecord() { + val prototype: XLSRecord? + get() { + val eo = EndObject() + eo.opcode = XLSConstants.ENDOBJECT + eo.data = eo.PROTOTYPE_BYTES + eo.init() + return eo + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Picf.java b/src/main/java/io/starter/formats/XLS/charts/Fbi.kt similarity index 72% rename from src/main/java/io/starter/formats/XLS/charts/Picf.java rename to src/main/java/io/starter/formats/XLS/charts/Fbi.kt index 69895ab..257c391 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Picf.java +++ b/src/main/java/io/starter/formats/XLS/charts/Fbi.kt @@ -20,18 +20,21 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts /** - * Picf: Picture Format(0x103c) + * **Fbi: Font Basis(0x1060)** */ -public class Picf extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7241922273506449916L; +class Fbi : GenericChartObject(), ChartObject { - public void init() { - super.init(); + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 6933614402245236374L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Fontx.java b/src/main/java/io/starter/formats/XLS/charts/Fontx.kt similarity index 60% rename from src/main/java/io/starter/formats/XLS/charts/Fontx.java rename to src/main/java/io/starter/formats/XLS/charts/Fontx.kt index 21e2568..8069482 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Fontx.java +++ b/src/main/java/io/starter/formats/XLS/charts/Fontx.kt @@ -20,16 +20,17 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools /** - * Fontx: Font Index (0x1026) + * **Fontx: Font Index (0x1026)** * Child of a TEXT record and defines a text font by indexing the appropriate font in * the font table. - *

                      + * + * * 4 iFont 2 Index number into the font table * If this field is less than or equal to the number of Font records * in the workbook, this field is a one-based index to a Font record in @@ -37,55 +38,60 @@ * collection of Font records in the chart sheet substream, where the index is equal to * iFont minus n and n is the number of Font records in the workbook. */ -public class Fontx extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4255798925225768809L; +class Fontx : GenericChartObject(), ChartObject { // 20070806 KSC: Add init/update to control FontX opts - private short ifnt = 0; + private var ifnt: Short = 0 - public void init() { - super.init(); - ifnt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - } + private val PROTOTYPE_BYTES = byteArrayOf(5, 0) - public static XLSRecord getPrototype() { - Fontx f = new Fontx(); - f.setOpcode(FONTX); - f.setData(f.PROTOTYPE_BYTES); - f.init(); - return f; + override fun init() { + super.init() + ifnt = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) } - private byte[] PROTOTYPE_BYTES = new byte[]{5, 0}; - /** * returns the index into the wb font table referenced by this Fontx record for the chart */ - /* - If this field is less than or equal to the number of Font records - in the workbook, this field is a one-based index to a Font record in - the workbook. Otherwise, this field is a one-based index into the - collection of Font records in the chart sheet substream, where the index is equal to + /* + If this field is less than or equal to the number of Font records + in the workbook, this field is a one-based index to a Font record in + the workbook. Otherwise, this field is a one-based index into the + collection of Font records in the chart sheet substream, where the index is equal to iFont minus n and n is the number of Font records in the workbook. */ - public int getIfnt() { - int n = 0; + fun getIfnt(): Int { + var n = 0 try { - n = this.getWorkBook().getNumFonts(); - } catch (Exception e) { + n = this.workBook!!.numFonts + } catch (e: Exception) { } - if (ifnt <= n) - return ifnt; + + return if (ifnt <= n) + ifnt.toInt() else - return (ifnt - n) + 1; + ifnt - n + 1 } - public void setIfnt(int id) { - this.ifnt = (short) id; - byte[] b = ByteTools.shortToLEBytes(this.ifnt); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; + fun setIfnt(id: Int) { + this.ifnt = id.toShort() + val b = ByteTools.shortToLEBytes(this.ifnt) + this.data[0] = b[0] + this.data[1] = b[1] + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 4255798925225768809L + + val prototype: XLSRecord? + get() { + val f = Fontx() + f.opcode = XLSConstants.FONTX + f.data = f.PROTOTYPE_BYTES + f.init() + return f + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Frame.java b/src/main/java/io/starter/formats/XLS/charts/Frame.java deleted file mode 100644 index 261e34f..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Frame.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; -/** - * Frame: Defines Border Shape Around Displayed Text (0x1032) - */ - -/** - frt (2 bytes): An unsigned integer that specifies the type of frame to be drawn. MUST be a value from the following table: - Type of frame - 0x0000 A frame surrounding the chart element. - - 0x0004 A frame with shadow surrounding the chart element. - - A - fAutoSize (1 bit): A bit that specifies if the size of the frame is automatically calculated. If the value is 1, the size of the frame is automatically calculated. In this case, the width and height specified by the chart element are ignored and the size of the frame is calculated automatically. If the value is 0, the width and height specified by the chart element are used as the size of the frame. - - B - fAutoPosition (1 bit): A bit that specifies if the position of the frame is automatically calculated. If the value is 1, the position of the frame is automatically calculated. In this case, the (x, y) specified by the chart element are ignored, and the position of the frame is automatically calculated. If the value is 0, the (x, y) location specified by the chart element are used as the position of the frame. - */ -public class Frame extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6302152932127918650L; - boolean fAutoSize, fAutoPosition; - int frt; - - public void init() { - frt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - byte flag = this.getByteAt(2);// 3= autosize, autoposition - fAutoSize = (flag & 0x01) == 0x01; - fAutoPosition = (flag & 0x02) == 0x02; - super.init(); - } - - public static XLSRecord getPrototype() { - Frame f = new Frame(); - f.setOpcode(FRAME); - f.setData(new byte[]{0, 0, 2, 0}); - f.init(); - return f; - } - - /** - * return the bg color assoc with this frame rec - * in main chart record - * NOTE that bg color is defined by the frame rec's associated - * AreaFormat Record's FOREGROUND color (icvFore) - * @return bg color Hex String - */ - public String getBgColor() { - return getBgColor(chartArr); - } - - - /** - * static utility to return the bg color assoc with the desired object - *
                      first checks if a gelFrame record exists; if so, it uses that color. - *
                      if no gelFrame record exists, it looks for an AreaFormat record. - * @return bg color Hex String - */ - public static String getBgColor(java.util.ArrayList chartArr) { - GelFrame gf = (GelFrame) Chart.findRec(chartArr, GelFrame.class); - if (gf != null) - return gf.getFillColor(); - AreaFormat af = (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); - if (af != null) { - return af.getFillColorStr(); - } - return null; // use default - } - - /** - * sets the background color assoc with this frame rec - * NOTE that bg color is defined by the frame rec's associated - * AreaFormat Record's FOREGROUND color (icvFore) - * @param bg color int - */ - public void setBgColor(int bg) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b instanceof AreaFormat) - ((AreaFormat) b).seticvFore(bg); - } - } - - /** - * set the frame autosize/autoposition, necessary for legend expansion - * @see Legend.setAutoPosition, Pos.setAutosizeLegend - * [BugTracker 2844] - */ - public void setAutosize() { - this.getData()[2] = 3; // sets both fAutoSize and fAutoPostion - } - - /** - * adds a frame box with the desired lineweight and fill color - * @param lw if -1, none - * @param lclr - * @param bgcolor if -1, none - */ - public void addBox(int lw, int lclr, int bgcolor) { - LineFormat lf = (LineFormat) Chart.findRec(chartArr, LineFormat.class); - if (lf == null) { - lf = (LineFormat) LineFormat.getPrototype(0, 0); - this.addChartRecord(lf); - } - if (lw != -1) { - lf.setLineWeight(lw); - lf.setLineStyle(0); // solid - } else { - lf.setLineStyle(5); // none - } - if (lclr != -1) - lf.setLineColor(lclr); - AreaFormat af = (AreaFormat) Chart.findRec(chartArr, AreaFormat.class); - if (af == null) { - af = (AreaFormat) AreaFormat.getPrototype(1); - this.addChartRecord(af); - } - if (bgcolor != -1) - af.seticvBack(bgcolor); - } - - /** - * returns true if this Frame is surrounded by a box - * @return - */ - public boolean hasBox() { - LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); - return (l.getLineStyle() != LineFormat.NONE); - } - - /** - * return the line color as a hex String - * @return - */ - public String getLineColor() { - LineFormat l = (LineFormat) Chart.findRec(this.chartArr, LineFormat.class); - if (l != null) - return l.getLineColor(); - return null; - } - - /** - * return the svg representation of this Frame element - * @param coords - * @return - */ - public StringBuffer getSVG(float[] coords) { - StringBuffer svg = new StringBuffer(); - String lineSVG = ""; - String bgclr = getBgColor(); -// String bgclr= "white"; - if (bgclr == null) - bgclr = "white"; - - LineFormat lf = (LineFormat) Chart.findRec(chartArr, LineFormat.class); - if (lf != null) - lineSVG = lf.getSVG(); - - float x = coords[0] - coords[2] / 2; // apparently coords are center-point; adjust - float y = coords[1] - coords[3] / 2; - svg.append("\r\n"); - - return svg; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Frame.kt b/src/main/java/io/starter/formats/XLS/charts/Frame.kt new file mode 100644 index 0000000..ac0f87c --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Frame.kt @@ -0,0 +1,194 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.BiffRec +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **Frame: Defines Border Shape Around Displayed Text (0x1032)** + */ + +/** + * frt (2 bytes): An unsigned integer that specifies the type of frame to be drawn. MUST be a value from the following table: + * Type of frame + * 0x0000 A frame surrounding the chart element. + * + * 0x0004 A frame with shadow surrounding the chart element. + * + * A - fAutoSize (1 bit): A bit that specifies if the size of the frame is automatically calculated. If the value is 1, the size of the frame is automatically calculated. In this case, the width and height specified by the chart element are ignored and the size of the frame is calculated automatically. If the value is 0, the width and height specified by the chart element are used as the size of the frame. + * + * B - fAutoPosition (1 bit): A bit that specifies if the position of the frame is automatically calculated. If the value is 1, the position of the frame is automatically calculated. In this case, the (x, y) specified by the chart element are ignored, and the position of the frame is automatically calculated. If the value is 0, the (x, y) location specified by the chart element are used as the position of the frame. + */ +class Frame : GenericChartObject(), ChartObject { + internal var fAutoSize: Boolean = false + internal var fAutoPosition: Boolean = false + internal var frt: Int = 0 + + /** + * return the bg color assoc with this frame rec + * in main chart record + * NOTE that bg color is defined by the frame rec's associated + * AreaFormat Record's FOREGROUND color (icvFore) + * @return bg color Hex String + */ + val bgColor: String? + get() = getBgColor(chartArr) + + /** + * return the line color as a hex String + * @return + */ + val lineColor: String? + get() { + val l = Chart.findRec(this.chartArr, LineFormat::class.java) as LineFormat + return l?.lineColor + } + + override fun init() { + frt = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() + val flag = this.getByteAt(2)// 3= autosize, autoposition + fAutoSize = flag and 0x01 == 0x01 + fAutoPosition = flag and 0x02 == 0x02 + super.init() + } + + /** + * sets the background color assoc with this frame rec + * NOTE that bg color is defined by the frame rec's associated + * AreaFormat Record's FOREGROUND color (icvFore) + * @param bg color int + */ + fun setBgColor(bg: Int) { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b is AreaFormat) + b.seticvFore(bg) + } + } + + /** + * set the frame autosize/autoposition, necessary for legend expansion + * @see Legend.setAutoPosition, Pos.setAutosizeLegend + * [BugTracker 2844] + */ + fun setAutosize() { + this.data[2] = 3 // sets both fAutoSize and fAutoPostion + } + + /** + * adds a frame box with the desired lineweight and fill color + * @param lw if -1, none + * @param lclr + * @param bgcolor if -1, none + */ + fun addBox(lw: Int, lclr: Int, bgcolor: Int) { + var lf = Chart.findRec(chartArr, LineFormat::class.java) as LineFormat + if (lf == null) { + lf = LineFormat.getPrototype(0, 0) as LineFormat + this.addChartRecord(lf) + } + if (lw != -1) { + lf.setLineWeight(lw) + lf.lineStyle = 0 // solid + } else { + lf.lineStyle = 5 // none + } + if (lclr != -1) + lf.setLineColor(lclr) + var af = Chart.findRec(chartArr, AreaFormat::class.java) as AreaFormat + if (af == null) { + af = AreaFormat.getPrototype(1) as AreaFormat + this.addChartRecord(af) + } + if (bgcolor != -1) + af.seticvBack(bgcolor) + } + + /** + * returns true if this Frame is surrounded by a box + * @return + */ + fun hasBox(): Boolean { + val l = Chart.findRec(this.chartArr, LineFormat::class.java) as LineFormat + return l.lineStyle != LineFormat.NONE + } + + /** + * return the svg representation of this Frame element + * @param coords + * @return + */ + fun getSVG(coords: FloatArray): StringBuffer { + val svg = StringBuffer() + var lineSVG = "" + var bgclr = bgColor + // String bgclr= "white"; + if (bgclr == null) + bgclr = "white" + + val lf = Chart.findRec(chartArr, LineFormat::class.java) as LineFormat + if (lf != null) + lineSVG = lf.svg + + val x = coords[0] - coords[2] / 2 // apparently coords are center-point; adjust + val y = coords[1] - coords[3] / 2 + svg.append("\r\n") + + return svg + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 6302152932127918650L + + val prototype: XLSRecord? + get() { + val f = Frame() + f.opcode = XLSConstants.FRAME + f.data = byteArrayOf(0, 0, 2, 0) + f.init() + return f + } + + + /** + * static utility to return the bg color assoc with the desired object + *

                      first checks if a gelFrame record exists; if so, it uses that color. + *

                      if no gelFrame record exists, it looks for an AreaFormat record. + * @return bg color Hex String + */ + fun getBgColor(chartArr: java.util.ArrayList<*>): String? { + val gf = Chart.findRec(chartArr, GelFrame::class.java) as GelFrame + if (gf != null) + return gf.fillColor + val af = Chart.findRec(chartArr, AreaFormat::class.java) as AreaFormat + return af?.fillColorStr +// use default + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/FrtFontList.java b/src/main/java/io/starter/formats/XLS/charts/FrtFontList.kt similarity index 74% rename from src/main/java/io/starter/formats/XLS/charts/FrtFontList.java rename to src/main/java/io/starter/formats/XLS/charts/FrtFontList.kt index f5cf642..81f6d15 100644 --- a/src/main/java/io/starter/formats/XLS/charts/FrtFontList.java +++ b/src/main/java/io/starter/formats/XLS/charts/FrtFontList.kt @@ -20,12 +20,12 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * FRTFONTLIST: Chart Font List (85Ah) + * **FRTFONTLIST: Chart Font List (85Ah)** * Introduced in Excel 9 (2000), this BIFF record is an FRT record for * Charts. This record stores font information for Excel 9 or later chart * objects. On round-tripping through an earlier version of Excel, the fonts @@ -36,7 +36,8 @@ * information in a STARTOBJECT/ENDOBJECT block that immediately follows. The block * has objectKind = 17, objectContext = 0, objectInstance1 = 0, objectInstance2 = 0. * The block has cfont FONT records and FBI records (for those with fScaled =1 only). - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =085Ah @@ -44,39 +45,44 @@ * 8 verChart 1 Version of Charting this list applies to * 9 cfont 2 Number of fonts in list * 11 rgFontInfo var Array of font IDs - *

                      + * + * * FontInfo Structure * Offset Field Name Size Contents * 0 grbit 2 Option flags for chart fonts (see description below) * 2 ifnt 2 Font ID of this font entry - *

                      + * + * * The grbit field contains the following chart font flags: * Bits Mask Flag Name Contents * 0 0001h fScaled =1 if the font is scaled =0 otherwise * 15-1 FFFEh (unused) Reserved; must be zero */ -public class FrtFontList extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5374035624852924277L; - - public void init() { - super.init(); - } +class FrtFontList : GenericChartObject(), ChartObject { // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[]{}; + private val PROTOTYPE_BYTES = byteArrayOf() - public static XLSRecord getPrototype() { - FrtFontList ffl = new FrtFontList(); - ffl.setOpcode(FRTFONTLIST); - ffl.setData(ffl.PROTOTYPE_BYTES); - ffl.init(); - return ffl; + override fun init() { + super.init() } - private void updateRecord() { + private fun updateRecord() {} + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5374035624852924277L + + val prototype: XLSRecord? + get() { + val ffl = FrtFontList() + ffl.opcode = XLSConstants.FRTFONTLIST + ffl.data = ffl.PROTOTYPE_BYTES + ffl.init() + return ffl + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.java b/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.java deleted file mode 100644 index d206c71..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; - -/** - * FRTWRAPPER: Chart Future Record Type Wrapper (851h) - * Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. - * This record is used to disguise a normal, non-FRT record as a FRT record. - * This is necessary whenever a new Excel element must save a pre-Excel 9 record - * as a child record. As an FRT record, Excel 97 will keep the record together with - * its associated STARTOBJECT/ENDOBJECT when round-tripping FRTs. The size of this - * record varies depending on the record that was wrapped. - *

                      - *

                      - * Record Data - * Offset Field Name Size Contents - * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0851h - * 6 grbitFrt 2 FRT flags; must be zero - * 8 rt 2 RT of wrapped record - * 10 cb 2 Size of wrapped RT's data in bytes - * 12 rgb var RT's data - */ -public class FrtWrapper extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4527467252753642328L; - private int type; - public static final int DEFAULTTEXT = 0, TEXTDISPWITHDATALABELS = 1, BEGIN = 2, POS = 3, AI = 4, OBJECTLINK = 5, END = 6, TEXTDISP = 7; - - public void init() { - super.init(); - } - - // 20080703 KSC: these bytes sequences haven't been entirelyy figured out but are necessary for Series Data Labels .. - private byte[] P_0 = new byte[]{81, 8, 0, 0, 36, 16, 2, 0, 0, 0, 0, 0}; // rt= 1024, DEFTEXT - // rt= 1025, TEXTDISP - private byte[] P_1 = new byte[]{81, 8, 0, 0, 37, 16, 32, 0, 2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -77, 0, 77, 0, 16, 61, 0, 0}; - private byte[] P_2 = new byte[]{81, 8, 0, 0, 51, 16, 0, 0, 0, 0, 0, 0}; //rt= 1033, BEGIN - private byte[] P_3 = new byte[]{81, 8, 0, 0, 79, 16, 20, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // rt= 104F, POS - private byte[] P_4 = new byte[]{81, 8, 0, 0, 81, 16, 8, 0, 0, 1, 0, 0, 0, 0, 0, 0}; // rt= 1051, AI - private byte[] P_5 = new byte[]{81, 8, 0, 0, 39, 16, 6, 0, 4, 0, 0, 0, 0, 0}; // rt= 1027, OBJECTLINK - private byte[] P_6 = new byte[]{81, 8, 0, 0, 52, 16, 0, 0, 0, 0, 0, 0}; // rt= 1034, END - private byte[] P_7 = new byte[]{81, 8, 0, 0, 37, 16, 32, 0, 2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -79, 0, 77, 0, 16, 61, 0, 0}; - - /** - * create a new FrtWrapper record - * each record wraps around another record - * - * @param type type of other record - * @return - */ - public static XLSRecord getPrototype(int type) { - FrtWrapper frt = new FrtWrapper(); - frt.type = type; // save wrapped type - frt.setOpcode(FRTWRAPPER); - byte[] b = null; - switch (type) { - case DEFAULTTEXT: - b = frt.P_0; // generate a default text record with 0=show labels - break; - case TEXTDISPWITHDATALABELS: // generate a text display with showKey (I believe!) - b = frt.P_1; - break; - case TEXTDISP: // generate a text display with showKey (I believe!) and NO data labels - b = frt.P_7; - break; - case BEGIN: - b = frt.P_2; - break; - case POS: - b = frt.P_3; // generate a POS record with default values - break; - case AI: - b = frt.P_4; // generate an AI record for SERIES Values - break; - case OBJECTLINK: - b = frt.P_5; // generate record with TYPE_DATASERIES - break; - case END: - b = frt.P_6; - break; - } - frt.setData(b); - frt.init(); - return frt; - } - - private void updateRecord() { - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.kt b/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.kt new file mode 100644 index 0000000..3c25589 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/FrtWrapper.kt @@ -0,0 +1,111 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord + +/** + * **FRTWRAPPER: Chart Future Record Type Wrapper (851h)** + * Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. + * This record is used to disguise a normal, non-FRT record as a FRT record. + * This is necessary whenever a new Excel element must save a pre-Excel 9 record + * as a child record. As an FRT record, Excel 97 will keep the record together with + * its associated STARTOBJECT/ENDOBJECT when round-tripping FRTs. The size of this + * record varies depending on the record that was wrapped. + * + * + * + * + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0851h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 rt 2 RT of wrapped record + * 10 cb 2 Size of wrapped RT's data in bytes + * 12 rgb var RT's data + */ +class FrtWrapper : GenericChartObject(), ChartObject { + private var type: Int = 0 + + // 20080703 KSC: these bytes sequences haven't been entirelyy figured out but are necessary for Series Data Labels .. + private val P_0 = byteArrayOf(81, 8, 0, 0, 36, 16, 2, 0, 0, 0, 0, 0) // rt= 1024, DEFTEXT + // rt= 1025, TEXTDISP + private val P_1 = byteArrayOf(81, 8, 0, 0, 37, 16, 32, 0, 2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -77, 0, 77, 0, 16, 61, 0, 0) + private val P_2 = byteArrayOf(81, 8, 0, 0, 51, 16, 0, 0, 0, 0, 0, 0) //rt= 1033, BEGIN + private val P_3 = byteArrayOf(81, 8, 0, 0, 79, 16, 20, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) // rt= 104F, POS + private val P_4 = byteArrayOf(81, 8, 0, 0, 81, 16, 8, 0, 0, 1, 0, 0, 0, 0, 0, 0) // rt= 1051, AI + private val P_5 = byteArrayOf(81, 8, 0, 0, 39, 16, 6, 0, 4, 0, 0, 0, 0, 0) // rt= 1027, OBJECTLINK + private val P_6 = byteArrayOf(81, 8, 0, 0, 52, 16, 0, 0, 0, 0, 0, 0) // rt= 1034, END + private val P_7 = byteArrayOf(81, 8, 0, 0, 37, 16, 32, 0, 2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -79, 0, 77, 0, 16, 61, 0, 0) + + override fun init() { + super.init() + } + + private fun updateRecord() {} + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -4527467252753642328L + val DEFAULTTEXT = 0 + val TEXTDISPWITHDATALABELS = 1 + val BEGIN = 2 + val POS = 3 + val AI = 4 + val OBJECTLINK = 5 + val END = 6 + val TEXTDISP = 7 + + /** + * create a new FrtWrapper record + * each record wraps around another record + * + * @param type type of other record + * @return + */ + fun getPrototype(type: Int): XLSRecord { + val frt = FrtWrapper() + frt.type = type // save wrapped type + frt.opcode = XLSConstants.FRTWRAPPER + var b: ByteArray? = null + when (type) { + DEFAULTTEXT -> b = frt.P_0 // generate a default text record with 0=show labels + TEXTDISPWITHDATALABELS // generate a text display with showKey (I believe!) + -> b = frt.P_1 + TEXTDISP // generate a text display with showKey (I believe!) and NO data labels + -> b = frt.P_7 + BEGIN -> b = frt.P_2 + POS -> b = frt.P_3 // generate a POS record with default values + AI -> b = frt.P_4 // generate an AI record for SERIES Values + OBJECTLINK -> b = frt.P_5 // generate record with TYPE_DATASERIES + END -> b = frt.P_6 + } + frt.data = b + frt.init() + return frt + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/GelFrame.java b/src/main/java/io/starter/formats/XLS/charts/GelFrame.kt similarity index 51% rename from src/main/java/io/starter/formats/XLS/charts/GelFrame.java rename to src/main/java/io/starter/formats/XLS/charts/GelFrame.kt index 5f55e63..4333d3b 100644 --- a/src/main/java/io/starter/formats/XLS/charts/GelFrame.java +++ b/src/main/java/io/starter/formats/XLS/charts/GelFrame.kt @@ -20,34 +20,30 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.OpenXLS.FormatHandle; -import io.starter.formats.XLS.MSODrawingConstants; -import io.starter.formats.escher.MsofbtOPT; +import io.starter.OpenXLS.FormatHandle +import io.starter.formats.XLS.MSODrawingConstants +import io.starter.formats.escher.MsofbtOPT /** - * GelFrame: Fill Data(0x1066) + * **GelFrame: Fill Data(0x1066)** * The GelFrame record specifies the properties of a fill pattern for parts of a chart. */ -public class GelFrame extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 581278144607124129L; - private java.awt.Color fillColor = null; - private int fillType = 0; // default= solid +class GelFrame : GenericChartObject(), ChartObject { + private var fillColor: java.awt.Color? = null + private val fillType = 0 // default= solid - public void init() { - super.init(); + override fun init() { + super.init() // try to interpret - MsofbtOPT optrec = new MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3); //version is always 3, inst is current count of properties. - optrec.setData(this.getData()); // sets and parses msoFbtOpt data, including imagename, shapename and imageindex - fillColor = optrec.getFillColor(); + val optrec = MsofbtOPT(MSODrawingConstants.MSOFBTOPT, 0, 3) //version is always 3, inst is current count of properties. + optrec.setData(this.data) // sets and parses msoFbtOpt data, including imagename, shapename and imageindex + fillColor = optrec.fillColor } - public String toString() { - return "GelFrame: fillType=" + fillType + " fillColor:" + fillColor.toString(); + override fun toString(): String { + return "GelFrame: fillType=" + fillType + " fillColor:" + fillColor!!.toString() } /** @@ -55,8 +51,14 @@ public String toString() { * * @return Color Hex String */ - public String getFillColor() { - if (fillColor == null) return null; - return FormatHandle.colorToHexString(fillColor); + fun getFillColor(): String? { + return if (fillColor == null) null else FormatHandle.colorToHexString(fillColor!!) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 581278144607124129L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.java b/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.java deleted file mode 100644 index b074267..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.XLSRecord; - -import java.util.ArrayList; - -public class GenericChartObject extends XLSRecord implements ChartObject, ChartConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1919254120575019160L; - - protected int chartType = -1; // this will be >=0 when record defines the type of chart - protected ArrayList chartArr = new ArrayList(); - protected Chart parentChart; - - public boolean setChartOption(String op, String val) { - return false; - } - - public boolean isStacked() { - return false; - } - - public boolean is100Percent() { - return false; - } - - public boolean hasShadow() { - return false; - } - - public void setIsStacked(boolean bIsStacked) { - } - - public void setIs100Percent(boolean bOn) { - } - - /** - * get chart option common to almost all chart types - * - * @param op - * @return - */ - public String getChartOption(String op) { - if (op.equals("Stacked")) { // Area, Bar, Pie, Line - return String.valueOf(isStacked()); - } else if (op.equals("Shadow")) { // Pie, Area, Bar, Line, Radar, Scatter - return String.valueOf(hasShadow()); - } else if (op.equals("PercentageDisplay")) { // Area, Bar,Line - return String.valueOf(is100Percent()); - } - return null; - } - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - return ""; - } - - /** - * adds html id and handlers for generic chart svg element - * - * @param title id of element - * @return String html - */ - public static String getScript(String id) { - String ret = ""; - if (id != null) - ret = "id='" + id + "' "; - ret += "onmouseover='highLight(evt);' onclick='handleClick(evt);' onmouseout='restore(evt)'"; - return ret; - } - - public ArrayList getChartRecords() { - return chartArr; - } - - public void addChartRecord(XLSRecord b) { - chartArr.add(b); - } - - public Chart getParentChart() { - return parentChart; - } - - public void setParentChart(Chart c) { - parentChart = c; - } - - /** - * Get the output array of records, including begin/end records and those of it's children. - */ - public ArrayList getRecordArray() { - ArrayList outputArr = new ArrayList(); - outputArr.add(this); - int nChart = -1; - for (int i = 0; i < chartArr.size(); i++) { - if (i == 0) { - Begin b = (Begin) Begin.getPrototype(); - outputArr.add(b); - } - - Object o = chartArr.get(i); - if (o instanceof ChartObject) { - ChartObject co = (ChartObject) o; - outputArr.addAll(co.getRecordArray()); - } else { - BiffRec b = (BiffRec) o; - outputArr.add(b); // 20070712 KSC: missed some recs! - } - - if (i == chartArr.size() - 1) { - End e = (End) End.getPrototype(); - outputArr.add(e); - } - } - return outputArr; - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - for (int i = 0; i < chartArr.size(); i++) { - XLSRecord r = chartArr.get(i); - r.close(); - } - chartArr.clear(); - parentChart = null; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.kt b/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.kt new file mode 100644 index 0000000..ddf61e5 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/GenericChartObject.kt @@ -0,0 +1,147 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.BiffRec +import io.starter.formats.XLS.XLSRecord + +import java.util.ArrayList + +open class GenericChartObject : XLSRecord(), ChartObject, ChartConstants { + + var chartType = -1 // this will be >=0 when record defines the type of chart + var chartArr: ArrayList = ArrayList() + override var parentChart: Chart? = null + + open var isStacked: Boolean + get() = false + set(bIsStacked) {} + + open var is100Percent: Boolean + get() = false + set(bOn) {} + + /** + * @return String XML representation of this chart-type's options + */ + open val optionsXML: String + get() = "" + + override val chartRecords: ArrayList<*> + get() = chartArr + + /** + * Get the output array of records, including begin/end records and those of it's children. + */ + override// 20070712 KSC: missed some recs! + val recordArray: ArrayList<*> + get() { + val outputArr = ArrayList() + outputArr.add(this) + val nChart = -1 + for (i in chartArr.indices) { + if (i == 0) { + val b = Begin.prototype as Begin? + outputArr.add(b) + } + + val o = chartArr[i] + if (o is ChartObject) { + val co = o as ChartObject + outputArr.addAll(co.recordArray) + } else { + val b = o as BiffRec + outputArr.add(b) + } + + if (i == chartArr.size - 1) { + val e = End.prototype as End? + outputArr.add(e) + } + } + return outputArr + } + + open fun setChartOption(op: String, `val`: String): Boolean { + return false + } + + open fun hasShadow(): Boolean { + return false + } + + /** + * get chart option common to almost all chart types + * + * @param op + * @return + */ + open fun getChartOption(op: String): String? { + if (op == "Stacked") { // Area, Bar, Pie, Line + return isStacked.toString() + } else if (op == "Shadow") { // Pie, Area, Bar, Line, Radar, Scatter + return hasShadow().toString() + } else if (op == "PercentageDisplay") { // Area, Bar,Line + return is100Percent.toString() + } + return null + } + + override fun addChartRecord(b: XLSRecord) { + chartArr.add(b) + } + + /** + * clear out object references in prep for closing workbook + */ + override fun close() { + for (i in chartArr.indices) { + val r = chartArr[i] + r.close() + } + chartArr.clear() + parentChart = null + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -1919254120575019160L + + /** + * adds html id and handlers for generic chart svg element + * + * @param title id of element + * @return String html + */ + fun getScript(id: String?): String { + var ret = "" + if (id != null) + ret = "id='$id' " + ret += "onmouseover='highLight(evt);' onclick='handleClick(evt);' onmouseout='restore(evt)'" + return ret + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Ifmt.java b/src/main/java/io/starter/formats/XLS/charts/Ifmt.kt similarity index 64% rename from src/main/java/io/starter/formats/XLS/charts/Ifmt.java rename to src/main/java/io/starter/formats/XLS/charts/Ifmt.kt index 9f77064..445c5f0 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Ifmt.java +++ b/src/main/java/io/starter/formats/XLS/charts/Ifmt.kt @@ -20,24 +20,26 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** - * Ifmt: Number-Format Index(0x104e) + * **Ifmt: Number-Format Index(0x104e)** */ -public class Ifmt extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -509816915750594317L; +class Ifmt : GenericChartObject(), ChartObject { - public void init() { - super.init(); + val fmt: Int + get() = ByteTools.readShort(this.data!![0].toInt(), this.data!![1].toInt()).toInt() + + override fun init() { + super.init() } - public int getFmt() { - return ByteTools.readShort(this.getData()[0], this.getData()[1]); + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -509816915750594317L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Legend.java b/src/main/java/io/starter/formats/XLS/charts/Legend.java deleted file mode 100644 index 561a799..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Legend.java +++ /dev/null @@ -1,604 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.MSODrawing; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.StringTool; - -import java.util.HashMap; - -/** - * Legend: Legend Type and Position (0x1015) - *

                      - *

                      - * 4 x 4 x position of upper-left corner -- MUST be ignored and the x1 field from the following Pos record MUST be used instead. - * 8 y 4 y position of upper-left corner -- MUST be ignored and the y1 field from the following Pos record MUST be used instead. - * 12 dx 4 width in SPRC -- MUST be ignored and the x2 field from the following Pos record MUST be used instead. - * 16 dy 4 height in SPRC -- MUST be ignored and the y2 field from the following Pos record MUST be used instead. - * 20 1 Undefined and MUST be ignored. - * 21 wSpacing1 Spacing (0= close, 1= medium, 2= open) (0x1= 40 twips==4 pts) - * 22 grbit 2 Option Flags - *

                      - * grbit Option Flags - * bits Mask - * 0 01h fAutoPostion Automatic positioning (1= legend is docked) - * 1 02h fAutoSeries Automatic series distribution - * 2 04h fAutoPosX X positioning is automatic - * 3 08h fAutoPosY Y positioning is automatic - * 4 10h fVert 1= vertical legend, 0= horizontal - * 5 20h fWasDataTable 1= chart contains data table - *

                      - * NOTES: - * A SPRC is a unit of measurement that is 1/4000th of the height or width of the chart - * If the field is being used to specify a width or horizontal distance, the SPRC is 1/4000th - * of the width of the chart. If the field is being used to specify a height or vertical - * distance, the SPRC is 1/4000th of the height of the chart. - *

                      - * Sequence of records: - * ATTACHEDLABEL = TextDisp Begin Pos [FontX] [AlRuns] AI [FRAME] [ObjectLink] [DataLabExtContents] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] End - * LD = Legend Begin Pos ATTACHEDLABEL [FRAME] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] End - */ -public class Legend extends GenericChartObject implements ChartObject, ChartConstants { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4041111720696805018L; - protected int x_defunct = -1, y_defunct = -1, dx_defunct = -1, dy_defunct = -1; // these vars are now defunct; see doc above + Pos/getSVG for coordinate info - protected byte /*wType= -1, */wSpacing = -1; - protected short grbit = -1; - protected boolean fAutoPosition, fAutoSeries, fAutoPosX, fAutoPosY; - protected boolean fVert, fWasDataTable; - public static final int BOTTOM = 0, CORNER = 1, TOP = 2, RIGHT = 3, LEFT = 4, NOT_DOCKED = 7; - int[] legendCoords = null; - - public void init() { - super.init(); - byte[] rkdata = this.getData(); - x_defunct = ByteTools.readInt(this.getBytesAt(0, 4)); - y_defunct = ByteTools.readInt(this.getBytesAt(4, 4)); - dx_defunct = ByteTools.readInt(this.getBytesAt(8, 4)); - dy_defunct = ByteTools.readInt(this.getBytesAt(12, 4)); - // unused: wType= rkdata[16]; layout position is controled by CrtLayout12 record - wSpacing = rkdata[17]; - grbit = ByteTools.readShort(rkdata[18], rkdata[19]); - parseGrbit(); - } - - /** - * The following records and rules define the significant parts of a legend: - *

                      - * The Legend record specifies the layout of the legend and specifies if the legend is automatically positioned. - * The Pos record, CrtLayout12 record, specify the position of the legend. - * The sequences of records that conform to the ATTACHEDLABEL (TextDisp ->Pos [FontX] [AlRuns] AI [FRAME] [ObjectLink] [DataLabExtContents] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] ) - * and TEXTPROPS (RichTextStream|TextPropStream) rules specify the default text formatting for the legend entries. - * The Pos record of the attached label MUST be ignored. The ObjectLink record of the attached label MUST NOT exist. - * A series can specify formatting exceptions for individual legend entries. - * The sequence of records that conforms to the FRAME (Frame ->LineFormat AreaFormat [GELFRAME] [SHAPEPROPS]) rule specifies the fill and border formatting properties of the legend. - */ - - protected void parseGrbit() { - byte[] grbytes = ByteTools.shortToLEBytes(grbit); - fAutoPosition = ((grbytes[0] & 0x01) == 0x01); - fAutoSeries = ((grbytes[0] & 0x02) == 0x02); - fAutoPosX = ((grbytes[0] & 0x04) == 0x04); - fAutoPosY = ((grbytes[0] & 0x08) == 0x08); - fVert = ((grbytes[0] & 0x10) == 0x10); - fWasDataTable = ((grbytes[0] & 0x20) == 0x20); - } - - - public void setIsDataTable(boolean isDataTable) { - fWasDataTable = isDataTable; - grbit = ByteTools.updateGrBit(grbit, fWasDataTable, 5); - updateRecord(); - } - /* unused - public void setwType(int type) { - wType= (byte) type; - updateRecord(); - } - */ - - public void setVertical(boolean isVertical) { - fVert = isVertical; - grbit = ByteTools.updateGrBit(grbit, fVert, 4); - updateRecord(); - } - - public static Legend createDefaultLegend(io.starter.formats.XLS.WorkBook book) { - Legend l = (Legend) Legend.getPrototype(); - Pos p = (Pos) Pos.getPrototype(Pos.TYPE_LEGEND); - l.chartArr.add(p); - TextDisp td = (TextDisp) TextDisp.getPrototype(ObjectLink.TYPE_TITLE, "", book); - l.chartArr.add(td); - return l; - } - - private void updateRecord() { - System.arraycopy(ByteTools.cLongToLEBytes(x_defunct), 0, this.getData(), 0, 4); - System.arraycopy(ByteTools.cLongToLEBytes(y_defunct), 0, this.getData(), 4, 4); - System.arraycopy(ByteTools.cLongToLEBytes(dx_defunct), 0, this.getData(), 8, 4); - System.arraycopy(ByteTools.cLongToLEBytes(dy_defunct), 0, this.getData(), 12, 4); - // unused this.getData()[16]= wType; - this.getData()[17] = wSpacing; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[18] = b[0]; - this.getData()[19] = b[1]; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{-11, 13, 0, 0, -72, 3, 0, 0, -111, 1, 0, 0, -31, 4, 0, 0, 3, 1, 31, 0}; - - public static XLSRecord getPrototype() { - Legend l = new Legend(); - l.setOpcode(LEGEND); - l.setData(l.PROTOTYPE_BYTES); - l.init(); - return l; - } - - /** - * legend position: - * controlled by CrtLayout12 - * 0= bottom, 1= corner, 2= top, 3= right, 4= left - * - * @return - */ - public short getLegendPosition() { - CrtLayout12 crt = (CrtLayout12) Chart.findRec(this.chartArr, CrtLayout12.class); - if (crt != null) - return (short) crt.getLayout(); - if (fVert || !fAutoPosition) - return RIGHT; // default - return BOTTOM; // default if not vert - } - - /** - * return the legend position in string (OOXML) form - * - * @return b, l, r, t, tr - */ - public String getLegendPositionString() { - int lpos = getLegendPosition(); - String[] pos = {"b", "tr", "t", "r", "l"}; - if (lpos >= 0 && lpos < pos.length) - return pos[lpos]; - return "r"; - } - - /** - * set Legend Positon: one of: - * 0= bottom, 1= corner, 2= top, 3= right, 4= left, 7= not docked - * - * @param pos - */ - public void setLegendPosition(short pos) { - CrtLayout12 crt = (CrtLayout12) Chart.findRec(this.chartArr, CrtLayout12.class); - if (crt != null) - crt.setLayout(pos); - } - - /** - * retrieves the specific font for these legends, if set (null if not) - * - * @return - */ - private Fontx getLegendFont() { - TextDisp td = (TextDisp) Chart.findRec(chartArr, TextDisp.class); - if (td != null) - return (Fontx) Chart.findRec(td.chartArr, Fontx.class); - return null; - } - - /** - * returns true if this legend is surrounded by a box (the default) - * - * @return - */ - public boolean hasBox() { - Frame f = (Frame) Chart.findRec(chartArr, Frame.class); - if (f != null) { - return f.hasBox(); - } -// return true; // the default - return false; - } - - public void addBox() { - Frame f = (Frame) Chart.findRec(chartArr, Frame.class); - if (f == null) { - f = (Frame) Frame.getPrototype(); - f.addBox(0, -1, -1); - this.chartArr.add(f); - } - } - - /** - * sets or turns off auto positioning - * [BugTracker 2844] - * - * @param auto - */ - public void setAutoPosition(boolean auto) { - if (auto && !fAutoPosition) { - // if setting to autosize/position and it wasn't currently set as so, - // check Pos and Frame records (if present) as they also controls automatic positioning ((: - if (this.chartArr.size() > 0) { - try { - Pos p = (Pos) this.chartArr.get(0); - p.setAutosizeLegend(); - Frame f = (Frame) Chart.findRec(chartArr, Frame.class); // find the first one - if (f != null) f.setAutosize(); - } catch (Exception e) { - } - } - } - fAutoPosition = auto; - fAutoSeries = auto; - fAutoPosX = auto; - fAutoPosY = auto; - //if (wType==3 || wType==4 && auto) - //fVert= true; - grbit = ByteTools.updateGrBit(grbit, fAutoPosition, 0); - grbit = ByteTools.updateGrBit(grbit, fAutoSeries, 1); - grbit = ByteTools.updateGrBit(grbit, fAutoPosX, 2); - grbit = ByteTools.updateGrBit(grbit, fAutoPosY, 3); - grbit = ByteTools.updateGrBit(grbit, fVert, 4); - this.updateRecord(); - } - - /** - * a rough estimate of expanding legend dimensions of - * 1 normal entry - */ - public void incrementHeight(float h) { - Pos p = (Pos) Chart.findRec(this.chartArr, Pos.class); - int[] coords = p.getLegendCoords(); // x, y, w, h, fh, legendpos - Font f = this.getFnt(); - int fh = 10; // default - if (f != null) - fh = (int) (f.getFontHeightInPoints() * 1.2); // a little padding - if (coords != null) { - int z = coords[1] - (int) Math.ceil(Pos.convertToSPRC(fh / 2, 0, h)); - p.setY(z); - } - } - - /** - * called up change of legend text to adjust width of the legend bounding box - * - * @param chartMetrics - * @param chartType - * @param legends String[] text of legends (containing new legend text) - */ - public void adjustWidth(HashMap chartMetrics, int chartType, String[] legends) { - Pos p = (Pos) Chart.findRec(this.chartArr, Pos.class); - int[] coords = p.getLegendCoords(); // x, y, w, h, fh, legendpos - if (coords != null) { - Font f = this.getFnt(); - // legend position LEFT and RIGHT display each legend on a separate line (fVert==true) - // TOP and BOTTOM are displayed horizontally with symbols and spacing between entries (fVert==false) - int position = this.getLegendPosition(); - float cw = chartMetrics.get("canvasw").floatValue(); - float x = (int) Math.ceil(Pos.convertFromSPRC(coords[0], cw, 0)) - 3; - float w = chartMetrics.get("w").floatValue(); - - // calculate how much width the legends take up -- algorithm works well for about 80% of the cases ... - double legendsWidth = 0; - java.awt.Font jf = new java.awt.Font(f.getFontName(), f.getFontWeight(), (int) f.getFontHeightInPoints()); - int extras = (chartType == ChartConstants.LINECHART || chartType == ChartConstants.RADARCHART ? 15 : 5); // pad for legend symbols, etc - - for (int i = 0; i < legends.length; i++) { - if (fVert) - legendsWidth = Math.max(legendsWidth, StringTool.getApproximateStringWidth(jf, " " + legends[i] + " ")); - else - legendsWidth += StringTool.getApproximateStringWidth(jf, " " + legends[i] + " ") + extras; - } - if (!fVert) - legendsWidth -= StringTool.getApproximateStringWidth(jf, " "); // decrement one space - else - legendsWidth += StringTool.getApproximateStringWidth(jf, " "); - -// io.starter.toolkit.Logger.log(this.getParentChart().toString() + String.format(": legend box x: %.1f legend box w: %.0f chart x: %.1f w: %.1f cw: %.1f font size: %.0f L.W: %.1f Auto? %b Vertical? %b", -// x, (float)coords[2], chartMetrics.get("x"), w, cw, (float) jf.getSize(), legendsWidth, fAutoPosition, fVert)); - p.setLegendW((int) legendsWidth); - if (((x + legendsWidth) > cw) || (position == Legend.RIGHT || position == Legend.CORNER)) { - x = (float) (cw - (legendsWidth + 5)); - if (x < 0) x = 0; - int z = (int) Math.ceil(Pos.convertToSPRC(x, cw, 0)); - p.setX(z); - } - - - - /* - if (position==Legend.RIGHT || position==Legend.CORNER) { // usual case - totalWidth+= extras; - if ((x+totalWidth) > cw) { // if legends will extend over edge of chart, must adjust either x or chart canvas width to accommodate legends - x= cw-totalWidth; - } - } -// p.setLegendW((int)totalWidth); - - - - - else if (position==Legend.LEFT) { - totalWidth+= extras; - if (totalwidth > x) - double originalDist= x-w; // original distance between legend and edge of plot area - if ((w + totalWidth + originalDist) < cw) - ; - - if (totalWidth > (cw-w)) { // can fit in space between plot area and - //KSC: TESTINGs - //io.starter.toolkit.Logger.log("Original Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); - float newX= (int)Math.ceil(cw-w); - if (originalDist > 0 && (w+x) > newX) - chartMetrics.put("w", newX-originalDist); - //io.starter.toolkit.Logger.log("After Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); - } - } - } else if (totalWidth > cw) { // legends are displayed horizontally, can't fit in canvas width -// TODO: must extend cw ?? wrap ??? - /* float overage= ((x+w+10)-cw); - if (overage > 0) - chartMetrics.put("canvasw", chartMetrics.get("canvasw")+overage);* / - }*/ - } - } - - /** - * reset initial position of legend to accommodate nLines of legend text - */ - public void resetPos(double y, double h, double ch, int nLines) { - Pos p = (Pos) Chart.findRec(this.chartArr, Pos.class); -/* apparently just setting to 1/2 h works well!! - * Font f= this.getFnt(); - int fh= 10; // default - if (f!=null) - fh=(int)(f.getFontHeightInPoints()); - fh=(int)(f.getFontHeightInPoints()*1.2); // a little padding*/ - int z = (int) Math.ceil(Pos.convertToSPRC((float) (ch / 2/*+((fh*nLines)/2)*/), 0, (float) ch)); - p.setY(z); - - } - - /** - * return the coordinates of the legend box in pixels - *
                      An approximation at this point - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param fh -- font height in points - * @return int[4] x, y, w, h - */ - public int[] getCoords(int charttype, HashMap chartMetrics, String[] legends, java.awt.Font f) { - // calcs are not 100% **** - // space between legend entries = 40 twips = 1 twip equals one-twentieth of a printer's point - Pos p = (Pos) Chart.findRec(this.chartArr, Pos.class); - int[] coords = p.getLegendCoords(); - int[] retcoords = new int[6]; - int fh = f.getSize(); //*1.2); // a little padding - retcoords[4] = f.getSize(); // store font height - retcoords[5] = this.getLegendPosition(); // store legend position - - boolean canMoveCW = false; - if (coords != null) { - retcoords[0] = (int) Math.ceil(Pos.convertFromSPRC(coords[0], chartMetrics.get("canvasw").floatValue(), 0)) - 3; - retcoords[1] = (int) Math.ceil(Pos.convertFromSPRC(coords[1], 0, chartMetrics.get("canvash").floatValue())); - } else { // happens upon OOXML - retcoords[0] = (int) (chartMetrics.get("w") + chartMetrics.get("x") + 20); // start just after right side of plot - retcoords[1] = chartMetrics.get("y").intValue() + (int) (chartMetrics.get("h") / 4); - coords = new int[4]; - canMoveCW = true; - } - if (coords[2] != 0) { - retcoords[2] = (int) (coords[2] * MSODrawing.PIXELCONVERSION); - retcoords[2] += 3; // pad slightly - } else { - double len = 0; - for (int i = 0; i < legends.length; i++) { - len = Math.max(len, StringTool.getApproximateStringWidth(f, legends[i])); - } - retcoords[2] = (int) Math.ceil(len); - retcoords[2] += 15 + (charttype == ChartConstants.LINECHART || charttype == ChartConstants.RADARCHART ? 25 : 5); // pad for legend symbols, etc - - // if now legend box extends over edge reduce plot area width, not canvas width ... EXCEPT for OOXML; in those cases, extend CW - if (!canMoveCW && ((retcoords[0] + retcoords[2]) > chartMetrics.get("canvasw").floatValue())) { - double cw = chartMetrics.get("canvasw"); - double w = chartMetrics.get("w"); -// KSC: TESTING -//io.starter.toolkit.Logger.log("Original Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); - double ldist = retcoords[0] - w; // original distance between legend and edge of plot area - retcoords[0] = (int) Math.ceil(cw - retcoords[2]); - if (ldist > 0 && (chartMetrics.get("w") + chartMetrics.get("x")) > retcoords[0]) - chartMetrics.put("w", retcoords[0] - ldist); -//io.starter.toolkit.Logger.log("After Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); - } - - } - if (canMoveCW) { - float overage = ((retcoords[0] + retcoords[2] + 10) - (chartMetrics.get("canvasw").floatValue())); - if (overage > 0) - chartMetrics.put("canvasw", chartMetrics.get("canvasw") + overage); - } - if (coords[3] != 0) - retcoords[3] = (int) (coords[3] * MSODrawing.PIXELCONVERSION); - else - retcoords[3] = ((legends.length + 2) * (fh + 2)); - return retcoords; - } - - /** - * tries to get the best match - * - * @return - */ - public Font getFnt() { - try { - Fontx fx = getLegendFont(); - Font f = this.getParentChart().getWorkBook().getFont(fx.getIfnt()); - if (f != null) - return f; - else // shouldn't get here ... - return this.getParentChart().getDefaultFont(); - } catch (NullPointerException e) { - // this actually doesn't get the actual font for the legend but can't find correct Fontx record! - return this.getParentChart().getDefaultFont(); - } - } - - - public void getMetrics(HashMap chartMetrics, int chartType, ChartSeries s) { - String[] legends = s.getLegends(); - if (legends == null || legends.length == 0) return; - - Font f = getFnt(); - if (f != null) { - legendCoords = this.getCoords(chartType, chartMetrics, legends, new java.awt.Font(f.getFontName(), f.getFontWeight(), (int) f.getFontHeightInPoints())); - } else { // can't find any font ... shouldn't really happen ...? - legendCoords = this.getCoords(chartType, chartMetrics, legends, new java.awt.Font("Arial", 400, 10)); - } - } - - /** - * return the coordinates of the legend box, relative to the chart - * - * @return int[] coordinates x, y, w, h [fh, legendpos] - */ - public int[] getCoords() { - return legendCoords; - } - - - /** - * returns the Data Legend Box svg for this chart - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @return - */ - int XOFFSET = 12; - - public String getSVG(HashMap chartMetrics, ChartType chartobj, ChartSeries s) { - StringBuffer svg = new StringBuffer(); - // position information fro Pos record: - /** - * legend MDCHART MDABS The values x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, - relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 specify the - width and height of the legend, in points. - legend MDCHART MDPARENT The values of x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, - relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 MUST be ignored. - The size of the legend is determined by the application. - legend MDKTH MDPARENT The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table. - - */ - - String[] legends = s.getLegends(); - String[] seriescolors = s.getSeriesBarColors(); - if (legends == null || legends.length == 0) return ""; - - if (legendCoords == null) - this.getMetrics(chartMetrics, chartobj.getChartType(), s); - String font; // font svg - int fh; // font height - Font f = getFnt(); - if (f != null) { - font = f.getSVG(); - font = "' " + /*"' vertical-align='bottom' " +*/ font; - fh = (int) Math.ceil(f.getFontHeightInPoints()); - } else { // can't find any font ... shouldn't really happen ...? - font = "' " + "font-family='Arial' font-size='9pt'"; - fh = 10; - } - // get legend info in order to get dimensions - final int YOFFSET = legendCoords[3] / (legends.length); - - int x = legendCoords[0]; - int y = legendCoords[1]; - int boxw = legendCoords[2]; - int boxh = legendCoords[3]; - - svg.append("\r\n"); - if (this.hasBox()) { - svg.append(""); - x += 5; // start of labels (offset from box) - y += (YOFFSET / 3); - } - if (chartobj.getChartType() == ChartConstants.BARCHART) { // same as below except order is reversed - // draw a little box in appropriate color - int h = 8; // box size - for (int i = legends.length - 1; i >= 0; i--) { - svg.append(""); - svg.append(""); - svg.append(""); - svg.append(""); - if (markers[i] > 0) - svg.append(MarkerFormat.getMarkerSVG(x + w / 2 - 5, y + fh / 2, seriescolors[i], markers[i])); - - svg.append("Pos [FontX] [AlRuns] AI [FRAME] [ObjectLink] [DataLabExtContents] [CrtLayout12] [TEXTPROPS] [CRTMLFRT] ) + * and TEXTPROPS (RichTextStream|TextPropStream) rules specify the default text formatting for the legend entries. + * The Pos record of the attached label MUST be ignored. The ObjectLink record of the attached label MUST NOT exist. + * A series can specify formatting exceptions for individual legend entries. + * The sequence of records that conforms to the FRAME (Frame ->LineFormat AreaFormat [GELFRAME] [SHAPEPROPS]) rule specifies the fill and border formatting properties of the legend. + */ + + protected fun parseGrbit() { + val grbytes = ByteTools.shortToLEBytes(grbit) + fAutoPosition = grbytes[0] and 0x01 == 0x01 + fAutoSeries = grbytes[0] and 0x02 == 0x02 + fAutoPosX = grbytes[0] and 0x04 == 0x04 + fAutoPosY = grbytes[0] and 0x08 == 0x08 + fVert = grbytes[0] and 0x10 == 0x10 + fWasDataTable = grbytes[0] and 0x20 == 0x20 + } + + + fun setIsDataTable(isDataTable: Boolean) { + fWasDataTable = isDataTable + grbit = ByteTools.updateGrBit(grbit, fWasDataTable, 5) + updateRecord() + } + /* unused + public void setwType(int type) { + wType= (byte) type; + updateRecord(); + } + */ + + fun setVertical(isVertical: Boolean) { + fVert = isVertical + grbit = ByteTools.updateGrBit(grbit, fVert, 4) + updateRecord() + } + + private fun updateRecord() { + System.arraycopy(ByteTools.cLongToLEBytes(x_defunct), 0, this.data!!, 0, 4) + System.arraycopy(ByteTools.cLongToLEBytes(y_defunct), 0, this.data!!, 4, 4) + System.arraycopy(ByteTools.cLongToLEBytes(dx_defunct), 0, this.data!!, 8, 4) + System.arraycopy(ByteTools.cLongToLEBytes(dy_defunct), 0, this.data!!, 12, 4) + // unused this.getData()[16]= wType; + this.data[17] = wSpacing + val b = ByteTools.shortToLEBytes(grbit) + this.data[18] = b[0] + this.data[19] = b[1] + } + + /** + * returns true if this legend is surrounded by a box (the default) + * + * @return + */ + fun hasBox(): Boolean { + val f = Chart.findRec(chartArr, Frame::class.java) as Frame + return f?.hasBox() ?: false + // return true; // the default + } + + fun addBox() { + var f = Chart.findRec(chartArr, Frame::class.java) as Frame + if (f == null) { + f = Frame.prototype as Frame? + f.addBox(0, -1, -1) + this.chartArr.add(f) + } + } + + /** + * sets or turns off auto positioning + * [BugTracker 2844] + * + * @param auto + */ + fun setAutoPosition(auto: Boolean) { + if (auto && !fAutoPosition) { + // if setting to autosize/position and it wasn't currently set as so, + // check Pos and Frame records (if present) as they also controls automatic positioning ((: + if (this.chartArr.size > 0) { + try { + val p = this.chartArr[0] as Pos + p.setAutosizeLegend() + val f = Chart.findRec(chartArr, Frame::class.java) as Frame // find the first one + f?.setAutosize() + } catch (e: Exception) { + } + + } + } + fAutoPosition = auto + fAutoSeries = auto + fAutoPosX = auto + fAutoPosY = auto + //if (wType==3 || wType==4 && auto) + //fVert= true; + grbit = ByteTools.updateGrBit(grbit, fAutoPosition, 0) + grbit = ByteTools.updateGrBit(grbit, fAutoSeries, 1) + grbit = ByteTools.updateGrBit(grbit, fAutoPosX, 2) + grbit = ByteTools.updateGrBit(grbit, fAutoPosY, 3) + grbit = ByteTools.updateGrBit(grbit, fVert, 4) + this.updateRecord() + } + + /** + * a rough estimate of expanding legend dimensions of + * 1 normal entry + */ + fun incrementHeight(h: Float) { + val p = Chart.findRec(this.chartArr, Pos::class.java) as Pos + val coords = p.legendCoords // x, y, w, h, fh, legendpos + val f = this.fnt + var fh = 10 // default + if (f != null) + fh = (f.fontHeightInPoints * 1.2).toInt() // a little padding + if (coords != null) { + val z = coords[1] - Math.ceil(Pos.convertToSPRC((fh / 2).toFloat(), 0f, h).toDouble()).toInt() + p.setY(z) + } + } + + /** + * called up change of legend text to adjust width of the legend bounding box + * + * @param chartMetrics + * @param chartType + * @param legends String[] text of legends (containing new legend text) + */ + fun adjustWidth(chartMetrics: HashMap, chartType: Int, legends: Array) { + val p = Chart.findRec(this.chartArr, Pos::class.java) as Pos + val coords = p.legendCoords // x, y, w, h, fh, legendpos + if (coords != null) { + val f = this.fnt + // legend position LEFT and RIGHT display each legend on a separate line (fVert==true) + // TOP and BOTTOM are displayed horizontally with symbols and spacing between entries (fVert==false) + val position = this.legendPosition.toInt() + val cw = chartMetrics["canvasw"].toFloat() + var x = (Math.ceil(Pos.convertFromSPRC(coords[0].toFloat(), cw, 0f).toDouble()).toInt() - 3).toFloat() + val w = chartMetrics["w"].toFloat() + + // calculate how much width the legends take up -- algorithm works well for about 80% of the cases ... + var legendsWidth = 0.0 + val jf = java.awt.Font(f!!.fontName, f.fontWeight, f.fontHeightInPoints.toInt()) + val extras = if (chartType == ChartConstants.LINECHART || chartType == ChartConstants.RADARCHART) 15 else 5 // pad for legend symbols, etc - + for (i in legends.indices) { + if (fVert) + legendsWidth = Math.max(legendsWidth, StringTool.getApproximateStringWidth(jf, " " + legends[i] + " ")) + else + legendsWidth += StringTool.getApproximateStringWidth(jf, " " + legends[i] + " ") + extras + } + if (!fVert) + legendsWidth -= StringTool.getApproximateStringWidth(jf, " ") // decrement one space + else + legendsWidth += StringTool.getApproximateStringWidth(jf, " ") + + // io.starter.toolkit.Logger.log(this.getParentChart().toString() + String.format(": legend box x: %.1f legend box w: %.0f chart x: %.1f w: %.1f cw: %.1f font size: %.0f L.W: %.1f Auto? %b Vertical? %b", + // x, (float)coords[2], chartMetrics.get("x"), w, cw, (float) jf.getSize(), legendsWidth, fAutoPosition, fVert)); + p.setLegendW(legendsWidth.toInt()) + if (x + legendsWidth > cw || position == Legend.RIGHT || position == Legend.CORNER) { + x = (cw - (legendsWidth + 5)).toFloat() + if (x < 0) x = 0f + val z = Math.ceil(Pos.convertToSPRC(x, cw, 0f).toDouble()).toInt() + p.setX(z) + } + + +/* + if (position==Legend.RIGHT || position==Legend.CORNER) { // usual case + totalWidth+= extras; + if ((x+totalWidth) > cw) { // if legends will extend over edge of chart, must adjust either x or chart canvas width to accommodate legends + x= cw-totalWidth; + } + } +// p.setLegendW((int)totalWidth); + + + + + else if (position==Legend.LEFT) { + totalWidth+= extras; + if (totalwidth > x) + double originalDist= x-w; // original distance between legend and edge of plot area + if ((w + totalWidth + originalDist) < cw) + ; + + if (totalWidth > (cw-w)) { // can fit in space between plot area and + //KSC: TESTINGs + //io.starter.toolkit.Logger.log("Original Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); + float newX= (int)Math.ceil(cw-w); + if (originalDist > 0 && (w+x) > newX) + chartMetrics.put("w", newX-originalDist); + //io.starter.toolkit.Logger.log("After Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); + } + } + } else if (totalWidth > cw) { // legends are displayed horizontally, can't fit in canvas width +// TODO: must extend cw ?? wrap ??? + /* float overage= ((x+w+10)-cw); + if (overage > 0) + chartMetrics.put("canvasw", chartMetrics.get("canvasw")+overage);* / + }*/ + } +} + +/** + * reset initial position of legend to accommodate nLines of legend text + */ + fun resetPos(y:Double, h:Double, ch:Double, nLines:Int) { +val p = Chart.findRec(this.chartArr, Pos::class.java) as Pos? + /* apparently just setting to 1/2 h works well!! + * Font f= this.getFnt(); + int fh= 10; // default + if (f!=null) + fh=(int)(f.getFontHeightInPoints()); + fh=(int)(f.getFontHeightInPoints()*1.2); // a little padding*/ + val z = Math.ceil(Pos.convertToSPRC((ch / 2/*+((fh*nLines)/2)*/).toFloat(), 0f, ch.toFloat()).toDouble()).toInt() +p!!.setY(z) + +} + +/** + * return the coordinates of the legend box in pixels + *

                      An approximation at this point + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param fh -- font height in points + * @return int[4] x, y, w, h + */ + fun getCoords(charttype:Int, chartMetrics:HashMap, legends:Array, f:java.awt.Font):IntArray { + // calcs are not 100% **** + // space between legend entries = 40 twips = 1 twip equals one-twentieth of a printer's point + val p = Chart.findRec(this.chartArr, Pos::class.java) as Pos? +var coords = p!!.legendCoords +val retcoords = IntArray(6) +val fh = f.getSize() //*1.2); // a little padding +retcoords[4] = f.getSize() // store font height +retcoords[5] = this.legendPosition.toInt() // store legend position + +var canMoveCW = false +if (coords != null) +{ +retcoords[0] = Math.ceil(Pos.convertFromSPRC(coords!![0].toFloat(), chartMetrics.get("canvasw").toFloat(), 0f).toDouble()).toInt() - 3 +retcoords[1] = Math.ceil(Pos.convertFromSPRC(coords!![1].toFloat(), 0f, chartMetrics.get("canvash").toFloat()).toDouble()).toInt() +} +else +{ // happens upon OOXML +retcoords[0] = (chartMetrics.get("w") + chartMetrics.get("x") + 20.0).toInt() // start just after right side of plot +retcoords[1] = chartMetrics.get("y").toInt() + (chartMetrics.get("h") / 4).toInt() +coords = IntArray(4) +canMoveCW = true +} +if (coords!![2] != 0) +{ +retcoords[2] = (coords!![2] * MSODrawing.PIXELCONVERSION).toInt() +retcoords[2] += 3 // pad slightly +} +else +{ +var len = 0.0 +for (i in legends.indices) +{ +len = Math.max(len, StringTool.getApproximateStringWidth(f, legends[i])) +} +retcoords[2] = Math.ceil(len).toInt() +retcoords[2] += 15 + (if (charttype == ChartConstants.LINECHART || charttype == ChartConstants.RADARCHART) 25 else 5) // pad for legend symbols, etc - + // if now legend box extends over edge reduce plot area width, not canvas width ... EXCEPT for OOXML; in those cases, extend CW + if (!canMoveCW && ((retcoords[0] + retcoords[2]) > chartMetrics.get("canvasw").toFloat())) +{ +val cw = chartMetrics.get("canvasw") +val w = chartMetrics.get("w") + // KSC: TESTING + //io.starter.toolkit.Logger.log("Original Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); + val ldist = retcoords[0] - w // original distance between legend and edge of plot area +retcoords[0] = Math.ceil(cw - retcoords[2]).toInt() +if (ldist > 0 && (chartMetrics.get("w") + chartMetrics.get("x")) > retcoords[0]) +chartMetrics.put("w", retcoords[0] - ldist) + //io.starter.toolkit.Logger.log("After Lcoords: w" + w + " cw:" + cw + " coords:" + Arrays.toString(retcoords)); + } + +} +if (canMoveCW) +{ +val overage = ((retcoords[0] + retcoords[2] + 10) - (chartMetrics.get("canvasw").toFloat())) +if (overage > 0) +chartMetrics.put("canvasw", chartMetrics.get("canvasw") + overage) +} +if (coords!![3] != 0) +retcoords[3] = (coords!![3] * MSODrawing.PIXELCONVERSION).toInt() +else +retcoords[3] = ((legends.size + 2) * (fh + 2)) +return retcoords +} + + + fun getMetrics(chartMetrics:HashMap, chartType:Int, s:ChartSeries) { +val legends = s.getLegends() +if (legends == null || legends!!.size == 0) return + +val f = fnt +if (f != null) +{ +coords = this.getCoords(chartType, chartMetrics, legends, java.awt.Font(f!!.getFontName(), f!!.fontWeight, f!!.fontHeightInPoints.toInt())) +} +else +{ // can't find any font ... shouldn't really happen ...? +coords = this.getCoords(chartType, chartMetrics, legends, java.awt.Font("Arial", 400, 10)) +} +} + + fun getSVG(chartMetrics:HashMap, chartobj:ChartType, s:ChartSeries):String { +val svg = StringBuffer() + // position information fro Pos record: + /** + * legend MDCHART MDABS The values x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, + * relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 specify the + * width and height of the legend, in points. + * legend MDCHART MDPARENT The values of x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, + * relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 MUST be ignored. + * The size of the legend is determined by the application. + * legend MDKTH MDPARENT The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table. + * + */ + + val legends = s.getLegends() +val seriescolors = s.seriesBarColors +if (legends == null || legends!!.size == 0) return "" + +if (coords == null) +this.getMetrics(chartMetrics, chartobj.chartType, s) +val font:String // font svg +val fh:Int // font height +val f = fnt +if (f != null) +{ +font = f!!.svg +font = "' " + /*"' vertical-align='bottom' " +*/ font +fh = Math.ceil(f!!.fontHeightInPoints).toInt() +} +else +{ // can't find any font ... shouldn't really happen ...? +font = "' " + "font-family='Arial' font-size='9pt'" +fh = 10 +} + // get legend info in order to get dimensions + val YOFFSET = coords!![3] / (legends!!.size) + +var x = coords!![0] +var y = coords!![1] +val boxw = coords!![2] +val boxh = coords!![3] + +svg.append("\r\n") +if (this.hasBox()) +{ +svg.append(("")) +x += 5 // start of labels (offset from box) +y += (YOFFSET / 3) +} +if (chartobj.chartType == ChartConstants.BARCHART) +{ // same as below except order is reversed + // draw a little box in appropriate color + val h = 8 // box size +for (i in legends!!.indices.reversed()) +{ +svg.append(("")) +svg.append("")) +svg.append("") +svg.append("")) +if (markers[i] > 0) +svg.append(MarkerFormat.getMarkerSVG((x + w / 2 - 5).toDouble(), (y + fh / 2).toDouble(), seriescolors!![i], markers[i])) + +svg.append(". + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **Line: Chart Group Is a Line Chart Group (0x1018)** + * + * + * 4 grbit 2 flags + * + * + * 0 0x1 fStacked Stack the displayed values + * 1 0x2 f100 Each category is broken down as a percentage + * 2 0x4 fHasShadow 1= this line has a shadow + */ +class Line : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var fStacked = false + private var f100 = false + private var fHasShadow = false + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0) + + /** + * @return String XML representation of this chart-type's options + */ + override val optionsXML: String + get() { + val sb = StringBuffer() + if (fStacked) + sb.append(" Stacked=\"true\"") + if (f100) + sb.append(" PercentageDisplay=\"true\"") + if (fHasShadow) + sb.append(" Shadow=\"true\"") + return sb.toString() + } + + override var is100Percent: Boolean + get() = super.is100Percent + set(bOn) { + f100 = bOn + grbit = ByteTools.updateGrBit(grbit, f100, 1) + updateRecord() + } + + /** + * @return truth of "Chart is Stacked" + */ + override var isStacked: Boolean + get() = fStacked + set(bIsStacked) { + fStacked = bIsStacked + grbit = ByteTools.updateGrBit(grbit, fStacked, 0) + updateRecord() + } + + override fun init() { + super.init() + chartType = ChartConstants.LINECHART // 20070703 KSC + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + fStacked = grbit and 0x1 == 0x1 + f100 = grbit and 0x2 == 0x2 + fHasShadow = grbit and 0x4 == 0x4 + + } + + // 20070725 KSC: + private fun updateRecord() { + grbit = ByteTools.updateGrBit(grbit, fStacked, 0) + grbit = ByteTools.updateGrBit(grbit, f100, 1) + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 2) + val b = ByteTools.shortToLEBytes(grbit) + this.data[0] = b[0] + this.data[1] = b[1] + } + + fun setAsStockChart() { + chartType = ChartConstants.STOCKCHART + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("Stacked", ignoreCase = true)) { + fStacked = `val` == "true" + bHandled = true + } else if (op.equals("PercentageDisplay", ignoreCase = true)) { + f100 = `val` == "true" + bHandled = true + } else if (op.equals("Shadow", ignoreCase = true)) { + fHasShadow = `val` == "true" + bHandled = true + } else if (op.equals("Smooth", ignoreCase = true)) { // smooth lines + /* if (b instanceof Serfmt) { + Serfmt s= (Serfmt) b; + if (s.getSmoothLine()) {*/ + } + if (bHandled) + updateRecord() + return bHandled + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 6526476252082906554L + + val prototype: XLSRecord? + get() { + val b = Line() + b.opcode = XLSConstants.LINE + b.data = b.PROTOTYPE_BYTES + b.init() + return b + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Line3DChart.java b/src/main/java/io/starter/formats/XLS/charts/Line3DChart.java deleted file mode 100644 index bc98cd1..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Line3DChart.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class Line3DChart extends LineChart { - public Line3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - line = (Line) charttype; - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - //dropLines - ChartLine cl = this.cf.getChartLinesRec(ChartLine.TYPE_DROPLINE); - if (cl != null) - cooxml.append(cl.getOOXML()); - // gapDepth - int gapdepth = this.getGapDepth(); - if (gapdepth != 0) - cooxml.append(""); - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Line3DChart.kt b/src/main/java/io/starter/formats/XLS/charts/Line3DChart.kt new file mode 100644 index 0000000..9b3fc49 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Line3DChart.kt @@ -0,0 +1,84 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +class Line3DChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : LineChart(charttype, cf, wb) { + init { + line = charttype as Line + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + //dropLines + val cl = this.cf!!.getChartLinesRec(ChartLine.TYPE_DROPLINE.toInt()) + if (cl != null) + cooxml.append(cl.ooxml) + // gapDepth + val gapdepth = this.gapDepth + if (gapdepth != 0) + cooxml.append("") + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/LineChart.java b/src/main/java/io/starter/formats/XLS/charts/LineChart.java deleted file mode 100644 index e7287a3..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/LineChart.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; - -public class LineChart extends ChartType { - Line line = null; - - public LineChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - line = (Line) charttype; - } - - /** - * return the type JSON for this Chart Object - * - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON = new JSONObject(); -/* String dojoType; - if (!this.isStacked()) { - dojoType="Default"; - typeJSON.put("markers", true); // default, apparently- doesn't require a MarkerFormat record - } else - dojoType="StackedLines"; - //controls Data Legends, % Distance of sections, Line Format, Area Format, Bar Shapes ... - DataFormat df= this.getParentChart().getChartFormat().getDataFormatRec(false); - if (df!=null) { - for (int z= 0; z < df.chartArr.size(); z++) { - BiffRec b = (BiffRec)df.chartArr.get(z); - // LineFormat,AreaFormat,PieFormat,MarkerFormat - if (b instanceof Serfmt) { - Serfmt s= (Serfmt) b; - if (s.getSmoothLine()) { - // can be: Scatter with markers and smoothed lines - typeJSON.put("type", "Default"); // change to Line with Markers - typeJSON.put("markers", true); // default, apparently- doesn't require a MarkerFormat record - } - } else if (b instanceof MarkerFormat) { // markers for legend (attached label should follow) BUT also can mean NO markers - if (((MarkerFormat)b).getMarkerFormat()==0 && dojoType.equals("Default")) - typeJSON.remove("markers"); - } - } - } - typeJSON.put("type", dojoType);*/ - return typeJSON; - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - if (series.size() == 0) { - Logger.logErr("Line.getSVG: error in series"); - return ""; - } - - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there - // y value for each point= h/YMAX - StringBuffer svg = new StringBuffer(); - // get data labels, marker formats, series colors - int n = series.size(); - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - int[] markers = getMarkerFormats(); - double xfactor = 0, yfactor = 0; // - if (categories.length != 0) - xfactor = w / (categories.length); // w/#categories - else - xfactor = w; - if (max != 0) - yfactor = h / max; // h/YMAXSCALE - svg.append("\r\n"); - // define marker shapes for later use - svg.append(MarkerFormat.getMarkerSVGDefs()); - // for each series - for (int i = 0; i < n; i++) { - // each visible line on the chart consists of two lines- 1 black, 1 color - String points = ""; - String labels = ""; - double[] curseries = (double[]) series.get(i); - String[] curranges = (String[]) s.getSeriesRanges().get(i); - for (int j = 0; j < curseries.length; j++) { - points += ((x) + (j + .5) * xfactor) + "," + ((y + h) - (curseries[j] * yfactor)); - points += " "; - String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); - if (l != null) { - double xx = (2 + (x) + (j + .5) * xfactor); - if (markers[i] > 0) xx += 10; // scoot over for markers - labels += "" + l + "\r\n"; - } - } - // 1st line is black - svg.append("\r\n"); - // 2nd line is the series color - svg.append("\r\n"); - // Markers, if any, along data points in series - if (markers[i] > 0) { - String[] markerpoints = points.split(" "); - for (int j = 0; j < markerpoints.length; j++) { - String markerpoint = markerpoints[j]; - String[] xy = markerpoint.split(","); - double xx = Double.valueOf(xy[0]); - double yy = Double.valueOf(xy[1]); - svg.append(MarkerFormat.getMarkerSVG(xx, yy, seriescolors[i], markers[i]) + "\r\n"); - } - } - // data labels, if any, after lines and markers - svg.append(labels); - } - - svg.append("\r\n"); - return svg.toString(); - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH dlbls - //cooxml.append(getDataLabelsOOXML(cf)); - - //dropLines - ChartLine cl = this.cf.getChartLinesRec(ChartLine.TYPE_DROPLINE); - if (cl != null) - cooxml.append(cl.getOOXML()); - // hiLowLines - cl = this.cf.getChartLinesRec(ChartLine.TYPE_HILOWLINE); - if (cl != null) - cooxml.append(cl.getOOXML()); - // upDownBars - cooxml.append(cf.getUpDownBarOOXML()); - // marker - // smooth - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/LineChart.kt b/src/main/java/io/starter/formats/XLS/charts/LineChart.kt new file mode 100644 index 0000000..0ed2c85 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/LineChart.kt @@ -0,0 +1,217 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger +import org.json.JSONException +import org.json.JSONObject + +import java.util.ArrayList +import java.util.HashMap + +open class LineChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + internal var line: Line? = null + + /** + * return the type JSON for this Chart Object + * + * @return + */ + override/* String dojoType; + if (!this.isStacked()) { + dojoType="Default"; + typeJSON.put("markers", true); // default, apparently- doesn't require a MarkerFormat record + } else + dojoType="StackedLines"; + //controls Data Legends, % Distance of sections, Line Format, Area Format, Bar Shapes ... + DataFormat df= this.getParentChart().getChartFormat().getDataFormatRec(false); + if (df!=null) { + for (int z= 0; z < df.chartArr.size(); z++) { + BiffRec b = (BiffRec)df.chartArr.get(z); + // LineFormat,AreaFormat,PieFormat,MarkerFormat + if (b instanceof Serfmt) { + Serfmt s= (Serfmt) b; + if (s.getSmoothLine()) { + // can be: Scatter with markers and smoothed lines + typeJSON.put("type", "Default"); // change to Line with Markers + typeJSON.put("markers", true); // default, apparently- doesn't require a MarkerFormat record + } + } else if (b instanceof MarkerFormat) { // markers for legend (attached label should follow) BUT also can mean NO markers + if (((MarkerFormat)b).getMarkerFormat()==0 && dojoType.equals("Default")) + typeJSON.remove("markers"); + } + } + } + typeJSON.put("type", dojoType);*/ val typeJSON: JSONObject + @Throws(JSONException::class) + get() = JSONObject() + + init { + line = charttype as Line + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val x = chartMetrics["x"] // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + if (series!!.size == 0) { + Logger.logErr("Line.getSVG: error in series") + return "" + } + + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there + // y value for each point= h/YMAX + val svg = StringBuffer() + // get data labels, marker formats, series colors + val n = series.size + val dls = dataLabelInts // get array of data labels (can be specific per series ...) + val markers = markerFormats + var xfactor = 0.0 + var yfactor = 0.0 // + if (categories!!.size != 0) + xfactor = w / categories.size // w/#categories + else + xfactor = w + if (max != 0.0) + yfactor = h / max // h/YMAXSCALE + svg.append("\r\n") + // define marker shapes for later use + svg.append(MarkerFormat.markerSVGDefs) + // for each series + for (i in 0 until n) { + // each visible line on the chart consists of two lines- 1 black, 1 color + var points = "" + var labels = "" + val curseries = series[i] as DoubleArray + val curranges = s.seriesRanges!![i] as Array + for (j in curseries.indices) { + points += x + (j + .5) * xfactor + "," + (y + h - curseries[j] * yfactor) + points += " " + val l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0.0, i, legends, categories[j].toString()) + if (l != null) { + var xx = 2.0 + x + (j + .5) * xfactor + if (markers[i] > 0) xx += 10.0 // scoot over for markers + labels += "" + l + "\r\n" + } + } + // 1st line is black + svg.append("\r\n") + // 2nd line is the series color + svg.append("\r\n") + // Markers, if any, along data points in series + if (markers[i] > 0) { + val markerpoints = points.split(" ".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + for (j in markerpoints.indices) { + val markerpoint = markerpoints[j] + val xy = markerpoint.split(",".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + val xx = java.lang.Double.valueOf(xy[0]) + val yy = java.lang.Double.valueOf(xy[1]) + svg.append(MarkerFormat.getMarkerSVG(xx, yy, seriescolors[i], markers[i]) + "\r\n") + } + } + // data labels, if any, after lines and markers + svg.append(labels) + } + + svg.append("\r\n") + return svg.toString() + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH dlbls + //cooxml.append(getDataLabelsOOXML(cf)); + + //dropLines + var cl = this.cf!!.getChartLinesRec(ChartLine.TYPE_DROPLINE.toInt()) + if (cl != null) + cooxml.append(cl.ooxml) + // hiLowLines + cl = this.cf!!.getChartLinesRec(ChartLine.TYPE_HILOWLINE.toInt()) + if (cl != null) + cooxml.append(cl.ooxml) + // upDownBars + cooxml.append(cf!!.upDownBarOOXML) + // marker + // smooth + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/LineFormat.java b/src/main/java/io/starter/formats/XLS/charts/LineFormat.java deleted file mode 100644 index 9a9daf2..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/LineFormat.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.formats.OOXML.SpPr; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * LineFormat: Style of a Line or border(0x1007) - *

                      - * 4 rgb 4 Color of line: high byte must be 0 - * 8 lnx 2 Pattern of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - * When the value of this field is 0x0005 (None), the values of we and icv MUST be set to: Line thickness (we)= 0xFFFF (Hairline) Line color (icv) 0x004D - * 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) - * 12 grbit 2 flags - * 14 icv Index to color of line. - * An Icv that specifies a color from the chart color table. This value MUST be greater than or equal to 0x0000 and less than or equal to 0x0041, - * or greater than or equal to 0x004D and less than or equal to 0x00004F. This value SHOULD NOT be less than 0x0008. - * 0x0040 == Default foreground color. This is the window text color in the sheet display. - * 0x0041 == Default background color. This is the window background color in the sheet display and is the default background color for a cell. - * 0x004D == Default chart foreground color. This is the window text color in the chart display. - *

                      - * grbit: - * 0 0x1 fAuto Automatic format - * 1 reserved, 0 - * 2 0x4 fAxisOn specifies whether axis line is displayed - * If the previous record is AxisLine and the value of the id field of the AxisLine record is equal to 0x0000, this field MUST be a value from the following table: - * fAxisOn Lns Meaning - * 0 0x0005 The axis line is not displayed. - * 0 Any legal value except 0x0005 The axis line is displayed. - * 1 Any legal value The axis line is displayed. - * If the previous record is not AxisLine and the value of the id field of the AxisLine record is equal to 0x0000, this field MUST be zero, and MUST be ignored. - * 3 0x8 fAutoColor specifies whether icv= 0x4D. if 1, icv must= 0x4D. - */ -public class LineFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3051781109844837056L; - private java.awt.Color rgb; - private short lnx = 0; - private short we = 0; - private short grbit = 0; - private short icv = 0; - private SpPr sppr = null; - public static final int SOLID = 0; - public static final int DASH = 1; - public static final int DOT = 2; - public static final int DASHDOT = 3; - public static final int DASHDASHDOT = 4; - public static final int NONE = 5; - public static final int DKGRAY = 6; - public static final int MEDGRAY = 7; - public static final int LTGRAY = 8; - - public void init() { - super.init(); - byte[] data = this.getData(); - rgb = new java.awt.Color((data[0] < 0 ? 255 + data[0] : data[0]), (data[1] < 0 ? 255 + data[1] : data[1]), (data[2] < 0 ? 255 + data[2] : data[2])); - lnx = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - we = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - grbit = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - icv = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - } - - public static XLSRecord getPrototype() { - LineFormat lf = new LineFormat(); - lf.setOpcode(LINEFORMAT); - lf.setData(lf.PROTOTYPE_BYTES); - lf.init(); - return lf; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, -1, -1, 9, 0, 77, 0}; // no line default - // TODO: Figure this out!! - private byte[] PROTOTYPE_BYTES_1 = new byte[]{-128, -128, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - - /** - * get new Line Format in desired pattern and weight - *
                      pattern= Pattern of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - *
                      Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) - *
                      note color is set to default - * - * @return new LineFormat record - */ - public static XLSRecord getPrototype(int style, int weight) { - LineFormat lf = new LineFormat(); - lf.setOpcode(LINEFORMAT); - lf.setData(lf.PROTOTYPE_BYTES_1); - lf.init(); - lf.setLineStyle(style); - lf.setLineWeight(weight); - return lf; - } - - /** - * 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) - */ - public void setLineWeight(int weight) { - we = (short) weight; - updateRecord(); - } - - /** - * Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - * - * @param style - */ - public void setLineStyle(int style) { - lnx = (short) style; - // When the value of this field is 0x0005 (None), the values of we and icv MUST be set to: Line thickness (we)= 0xFFFF (Hairline) Line color (icv) 0x004D - //* 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) - if (lnx == 5) { - we = -1; - grbit = 0x8; // auto color - setLineColor(0x4D); - } - updateRecord(); - } - - /** - * return Pattern of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - */ - public int getLineStyle() { - return lnx; - } - - - /** - * Index to color of line - * - * @param clr - */ - public void setLineColor(int clr) { - if (clr > -1 && clr < this.getColorTable().length) { - icv = (short) clr; - rgb = this.getColorTable()[clr]; - updateRecord(); - } else if (clr == 0x4D) { // special flag, default fg - icv = (short) clr; - rgb = this.getColorTable()[0]; - updateRecord(); - } - // TOOD: finish - if (sppr != null) { -// sppr.setLine(we, clr); - } - } - - /** - * return the line color as a hex String - * - * @return - */ - public String getLineColor() { - return FormatHandle.colorToHexString(rgb); - } - - /** - * update the underlying data - */ - private void updateRecord() { - byte[] b = new byte[4]; - b[0] = (byte) rgb.getRed(); - b[1] = (byte) rgb.getGreen(); - b[2] = (byte) rgb.getBlue(); - b[3] = 0; // reserved/0 - System.arraycopy(b, 0, this.getData(), 0, 4); - b = ByteTools.shortToLEBytes(lnx); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - b = ByteTools.shortToLEBytes(we); - this.getData()[6] = b[0]; - this.getData()[7] = b[1]; - b = ByteTools.shortToLEBytes(grbit); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; - b = ByteTools.shortToLEBytes(icv); - this.getData()[10] = b[0]; - this.getData()[11] = b[1]; - } - - public String toString() { - return "LineFormat: LinePattern=" + lnx + " Weight=" + we + " Draw Ticks=" + ((grbit & 0x4) == 0x4); - } - - public String getSVG() { - if (lnx == 5) - return ""; // no line - float sz = 1f; - if (we == -1) // hairline - sz = 1f; - else if (we == 0) // narrow - rest are just guesses really - sz = 2f; - else if (we == 1) // medium - sz = 4f; - else if (we == 2) // wide - sz = 6f; - String clr = ChartType.getMediumColor(); - if (lnx == DKGRAY) // dark grey pattern - clr = ChartType.getDarkColor(); - else if (lnx == MEDGRAY) // medium grey pattern - clr = ChartType.getMediumColor(); - else if (lnx == LTGRAY) // light grey pattern - clr = ChartType.getLightColor(); - String style = ""; - if (lnx == DASH) { - style = " style='stroke-dasharray: 9, 5;' "; - } else if (lnx == DOT) { - style = " style='stroke-dasharray:2, 2;' "; - } else if (lnx == DASHDOT) { - style = " style='stroke-dasharray: 3, 2, 9, 2;' "; - } else if (lnx == DASHDASHDOT) { - style = " style='stroke-dasharray: 9, 5, 9, 5, 3, 2;' "; - } - return " stroke='" + clr + "' stroke-opacity='1' stroke-width='" + sz + "' " + style + "stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='4'"; - } - - /** - * if have 2007+v settings, use them. Otherwise, interpret line settings to OOXML - * - * @return - */ - public String getOOXML() { - // TODO: if changed lineformat info, change in sp - if (sppr != null) { - return sppr.getOOXML(); - } - - if (!parentChart.getWorkBook().getIsExcel2007()) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - // TODO: line styles + weight - ooxml.append(""); - ooxml.append(""); - ooxml.append(""); - ooxml.append(""); - ooxml.append(""); - ooxml.append(""); - return ooxml.toString(); - } - return ""; - } - - /** - * sets the OOXML settings for this line - * - * @param sp - */ - public void setFromOOXML(SpPr sp) { - this.sppr = sp; - int lw = sp.getLineWidth(); - this.setLineWeight(lw); // sp lw in emus. 1 pt= 12700 emus. - this.setLineColor(sp.getLineColor()); - this.setLineStyle(sp.getLineStyle()); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/LineFormat.kt b/src/main/java/io/starter/formats/XLS/charts/LineFormat.kt new file mode 100644 index 0000000..134557f --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/LineFormat.kt @@ -0,0 +1,294 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.FormatHandle +import io.starter.formats.OOXML.SpPr +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **LineFormat: Style of a Line or border(0x1007)** + * + * + * 4 rgb 4 Color of line: high byte must be 0 + * 8 lnx 2 Pattern of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + * When the value of this field is 0x0005 (None), the values of we and icv MUST be set to: Line thickness (we)= 0xFFFF (Hairline) Line color (icv) 0x004D + * 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) + * 12 grbit 2 flags + * 14 icv Index to color of line. + * An Icv that specifies a color from the chart color table. This value MUST be greater than or equal to 0x0000 and less than or equal to 0x0041, + * or greater than or equal to 0x004D and less than or equal to 0x00004F. This value SHOULD NOT be less than 0x0008. + * 0x0040 == Default foreground color. This is the window text color in the sheet display. + * 0x0041 == Default background color. This is the window background color in the sheet display and is the default background color for a cell. + * 0x004D == Default chart foreground color. This is the window text color in the chart display. + * + * + * grbit: + * 0 0x1 fAuto Automatic format + * 1 reserved, 0 + * 2 0x4 fAxisOn specifies whether axis line is displayed + * If the previous record is AxisLine and the value of the id field of the AxisLine record is equal to 0x0000, this field MUST be a value from the following table: + * fAxisOn Lns Meaning + * 0 0x0005 The axis line is not displayed. + * 0 Any legal value except 0x0005 The axis line is displayed. + * 1 Any legal value The axis line is displayed. + * If the previous record is not AxisLine and the value of the id field of the AxisLine record is equal to 0x0000, this field MUST be zero, and MUST be ignored. + * 3 0x8 fAutoColor specifies whether icv= 0x4D. if 1, icv must= 0x4D. + */ +class LineFormat : GenericChartObject(), ChartObject { + private var rgb: java.awt.Color? = null + private var lnx: Short = 0 + private var we: Short = 0 + private var grbit: Short = 0 + private var icv: Short = 0 + private var sppr: SpPr? = null + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, -1, -1, 9, 0, 77, 0) // no line default + // TODO: Figure this out!! + private val PROTOTYPE_BYTES_1 = byteArrayOf(-128, -128, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0) + + /** + * return Pattern of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + */ + /** + * Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + * + * @param style + */ + // When the value of this field is 0x0005 (None), the values of we and icv MUST be set to: Line thickness (we)= 0xFFFF (Hairline) Line color (icv) 0x004D + //* 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) + // auto color + var lineStyle: Int + get() = lnx.toInt() + set(style) { + lnx = style.toShort() + if (lnx.toInt() == 5) { + we = -1 + grbit = 0x8 + setLineColor(0x4D) + } + updateRecord() + } + + /** + * return the line color as a hex String + * + * @return + */ + val lineColor: String + get() = FormatHandle.colorToHexString(rgb!!) + + // no line + // hairline + // narrow - rest are just guesses really + // medium + // wide + // dark grey pattern + // medium grey pattern + // light grey pattern + val svg: String + get() { + if (lnx.toInt() == 5) + return "" + var sz = 1f + if (we.toInt() == -1) + sz = 1f + else if (we.toInt() == 0) + sz = 2f + else if (we.toInt() == 1) + sz = 4f + else if (we.toInt() == 2) + sz = 6f + var clr = ChartType.mediumColor + if (lnx.toInt() == DKGRAY) + clr = ChartType.darkColor + else if (lnx.toInt() == MEDGRAY) + clr = ChartType.mediumColor + else if (lnx.toInt() == LTGRAY) + clr = ChartType.lightColor + var style = "" + if (lnx.toInt() == DASH) { + style = " style='stroke-dasharray: 9, 5;' " + } else if (lnx.toInt() == DOT) { + style = " style='stroke-dasharray:2, 2;' " + } else if (lnx.toInt() == DASHDOT) { + style = " style='stroke-dasharray: 3, 2, 9, 2;' " + } else if (lnx.toInt() == DASHDASHDOT) { + style = " style='stroke-dasharray: 9, 5, 9, 5, 3, 2;' " + } + return " stroke='" + clr + "' stroke-opacity='1' stroke-width='" + sz + "' " + style + "stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='4'" + } + + /** + * if have 2007+v settings, use them. Otherwise, interpret line settings to OOXML + * + * @return + */ + // TODO: if changed lineformat info, change in sp + // TODO: line styles + weight + val ooxml: String + get() { + if (sppr != null) { + return sppr!!.ooxml + } + + if (!parentChart!!.workBook!!.isExcel2007) { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append("") + ooxml.append("") + ooxml.append("") + ooxml.append("") + ooxml.append("") + ooxml.append("") + return ooxml.toString() + } + return "" + } + + override fun init() { + super.init() + val data = this.data + rgb = java.awt.Color(if (data!![0] < 0) 255 + data[0] else data[0], if (data[1] < 0) 255 + data[1] else data[1], if (data[2] < 0) 255 + data[2] else data[2]) + lnx = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + we = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + grbit = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + icv = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + } + + /** + * 10 we 2 Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) + */ + fun setLineWeight(weight: Int) { + we = weight.toShort() + updateRecord() + } + + + /** + * Index to color of line + * + * @param clr + */ + fun setLineColor(clr: Int) { + if (clr > -1 && clr < this.colorTable.size) { + icv = clr.toShort() + rgb = this.colorTable[clr] + updateRecord() + } else if (clr == 0x4D) { // special flag, default fg + icv = clr.toShort() + rgb = this.colorTable[0] + updateRecord() + } + // TOOD: finish + if (sppr != null) { + // sppr.setLine(we, clr); + } + } + + /** + * update the underlying data + */ + private fun updateRecord() { + var b = ByteArray(4) + b[0] = rgb!!.red.toByte() + b[1] = rgb!!.green.toByte() + b[2] = rgb!!.blue.toByte() + b[3] = 0 // reserved/0 + System.arraycopy(b, 0, this.data!!, 0, 4) + b = ByteTools.shortToLEBytes(lnx) + this.data[4] = b[0] + this.data[5] = b[1] + b = ByteTools.shortToLEBytes(we) + this.data[6] = b[0] + this.data[7] = b[1] + b = ByteTools.shortToLEBytes(grbit) + this.data[8] = b[0] + this.data[9] = b[1] + b = ByteTools.shortToLEBytes(icv) + this.data[10] = b[0] + this.data[11] = b[1] + } + + override fun toString(): String { + return "LineFormat: LinePattern=" + lnx + " Weight=" + we + " Draw Ticks=" + (grbit and 0x4 == 0x4) + } + + /** + * sets the OOXML settings for this line + * + * @param sp + */ + fun setFromOOXML(sp: SpPr) { + this.sppr = sp + val lw = sp.lineWidth + this.setLineWeight(lw) // sp lw in emus. 1 pt= 12700 emus. + this.setLineColor(sp.lineColor) + this.lineStyle = sp.lineStyle + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 3051781109844837056L + val SOLID = 0 + val DASH = 1 + val DOT = 2 + val DASHDOT = 3 + val DASHDASHDOT = 4 + val NONE = 5 + val DKGRAY = 6 + val MEDGRAY = 7 + val LTGRAY = 8 + + val prototype: XLSRecord? + get() { + val lf = LineFormat() + lf.opcode = XLSConstants.LINEFORMAT + lf.data = lf.PROTOTYPE_BYTES + lf.init() + return lf + } + + /** + * get new Line Format in desired pattern and weight + *

                      pattern= Pattern of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + *

                      Weight of line (-1= hairline, 0= narrow, 1= med (double), 2= wide (triple) + *

                      note color is set to default + * + * @return new LineFormat record + */ + fun getPrototype(style: Int, weight: Int): XLSRecord { + val lf = LineFormat() + lf.opcode = XLSConstants.LINEFORMAT + lf.data = lf.PROTOTYPE_BYTES_1 + lf.init() + lf.lineStyle = style + lf.setLineWeight(weight) + return lf + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.java b/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.java deleted file mode 100644 index c01b553..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * MarkerFormat: Style of a Line Marker(0x1009) - *

                      - * The MarkerFormat record specifies the color, size, and shape of the associated data markers that appear on line, - * radar, and scatter chart groups. The associated data markers are specified by the preceding DataFormat record. - * If this record is not present then the color, size, and shape of the associated data markers are specified by the - * default values of the fields of this record. - *

                      - * Offset Name Size Contents - * 4 rgbFore 4 Foreground color: RGB value (high byte = 0) - * 8 rgbBack 4 Background color: RGB value (high byte = 0) - * 12 imk 2 Type of marker - * 0 = no marker - * 1 = square - * 2 = diamond - * 3 = triangle - * 4 = X - * 5 = star - * 6 = Dow-Jones - * 7 = standard deviation - * 8 = circle - * 9 = plus sign - * 14 grbit 2 Format flags - * 16 icvFore 2 Index to color of marker border - * 18 icvBack 2 Index to color of marker fill - * 20 miSize 4 Size of line markers. An unsigned integer that specifies the size in twips of the data marker. MUST be greater than or equal to 40 and less than or equal to 1440. The default value for this field is 100. - *

                      - * The icvBack field describes the color of the marker's background, such as the center of the square, - * while the icvFore field describes the color of the border or the marker itself. The imk field defines - * the type of marker. - *

                      - * The grbit field contains the following option flags. - *

                      - * Offset Bits Mask Name Contents - * 0 0 01h fAuto Automatic color - * 0 3-1 0Eh (reserved) Reserved; must be zero - * 0 4 10h fNotShowInt 1 = "background = none" - * 0 5 20h fNotShowBrd 1 = "foreground = none" - * 0 7-6 C0h (reserved) Reserved; must be zero - * 1 7-0 FFh (reserved) Reserved; must be zero - */ -public class MarkerFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7526015026522467305L; - private java.awt.Color rgbFore, rgbBack; - private int miSize = 0; - private short imk = 0; - private short icvFore = 0; - private short icvBack = 0; - private short grbit = 0; - - public void init() { - super.init(); - byte[] data = this.getData(); - rgbFore = new java.awt.Color((data[0] < 0 ? 255 + data[0] : data[0]), (data[1] < 0 ? 255 + data[1] : data[1]), (data[2] < 0 ? 255 + data[2] : data[2])); - rgbBack = new java.awt.Color((data[4] < 0 ? 255 + data[4] : data[4]), (data[5] < 0 ? 255 + data[5] : data[5]), (data[6] < 0 ? 255 + data[6] : data[6])); - imk = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - grbit = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - icvFore = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - icvBack = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - miSize = ByteTools.readInt(this.getBytesAt(14, 4)); - } - - // 20070716 KSC: Need to create new records - public static XLSRecord getPrototype() { - MarkerFormat mf = new MarkerFormat(); - mf.setOpcode(MARKERFORMAT); - mf.setData(mf.PROTOTYPE_BYTES); - return mf; - } // imk was 2 in default - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 77, 0, 60, 0, 0, 0}; - - /** - * @return String XML representation of this chart-type's options - */ - // TODO: Finish MarkerFormat Options - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - //if (imk!=0) - sb.append(" MarkerFormat=\"" + imk + "\""); - return sb.toString(); - } - - /** - * Handle setting options from XML in a generic manner - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("MarkerFormat")) { - imk = Short.parseShort(val); - bHandled = true; - } - if (bHandled) - updateRecord(); - return bHandled; - } - - private void updateRecord() { - imk = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - byte[] b = ByteTools.shortToLEBytes(imk); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; - } - - /** - * return the Marker Format field imk - * - * @return - */ - public int getMarkerFormat() { - return imk; - } - - /** - * set the Marker Format field imk + update the record - * - * @param imk - */ - public void setMarkerFormat(short imk) { - this.imk = imk; - updateRecord(); - } - - public String toString() { - return "MarkerFormat: imk= " + imk + " grbit=" + grbit; - } - - /** - * returns the SVG defs element used, in conjuction with getMarkerSVG, to define markers on charts in SVG - * - * @return String SVG marker defs - */ - public static String getMarkerSVGDefs() { - return "\r\n" + //-5 -5 - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - // "star - // "dow-jones - // "stddev - "\r\n"; - } - - /** - * returns the svg line to define the marker at the point x,y, in color clr, of marker style marker (1-9) - *
                      NOTE: you must use getMarkerSVGdefs before using this method - * - * @param x double x point - * @param y double y point - * @param clr String SVG color string - * @param marker int marker format (1-9) - * @return SVG - */ - public static String getMarkerSVG(double x, double y, String clr, int marker) { - String markersvg = ""; - else if (marker == 2) // diamond - markersvg += "diamond1'/>"; - else if (marker == 3) // triangle - markersvg += "triangle1'/>"; - else if (marker == 4) // X - markersvg += "cross1'/>"; - else if (marker == 5) // Star ????????????? - markersvg += "circle1'/>"; - else if (marker == 6) // Dow-Jones ?????????? - markersvg += "circle1'/>"; - else if (marker == 7) // Std Dev ????????????? - markersvg += "circle1'/>"; - else if (marker == 8) // circle - markersvg += "circle1'/>"; - else if (marker == 9) // + sign - markersvg += "plus1'/>"; - return markersvg; - } -} - - diff --git a/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.kt b/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.kt new file mode 100644 index 0000000..f7d9242 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/MarkerFormat.kt @@ -0,0 +1,230 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **MarkerFormat: Style of a Line Marker(0x1009)** + * + * + * The MarkerFormat record specifies the color, size, and shape of the associated data markers that appear on line, + * radar, and scatter chart groups. The associated data markers are specified by the preceding DataFormat record. + * If this record is not present then the color, size, and shape of the associated data markers are specified by the + * default values of the fields of this record. + * + * + * Offset Name Size Contents + * 4 rgbFore 4 Foreground color: RGB value (high byte = 0) + * 8 rgbBack 4 Background color: RGB value (high byte = 0) + * 12 imk 2 Type of marker + * 0 = no marker + * 1 = square + * 2 = diamond + * 3 = triangle + * 4 = X + * 5 = star + * 6 = Dow-Jones + * 7 = standard deviation + * 8 = circle + * 9 = plus sign + * 14 grbit 2 Format flags + * 16 icvFore 2 Index to color of marker border + * 18 icvBack 2 Index to color of marker fill + * 20 miSize 4 Size of line markers. An unsigned integer that specifies the size in twips of the data marker. MUST be greater than or equal to 40 and less than or equal to 1440. The default value for this field is 100. + * + * + * The icvBack field describes the color of the marker's background, such as the center of the square, + * while the icvFore field describes the color of the border or the marker itself. The imk field defines + * the type of marker. + * + * + * The grbit field contains the following option flags. + * + * + * Offset Bits Mask Name Contents + * 0 0 01h fAuto Automatic color + * 0 3-1 0Eh (reserved) Reserved; must be zero + * 0 4 10h fNotShowInt 1 = "background = none" + * 0 5 20h fNotShowBrd 1 = "foreground = none" + * 0 7-6 C0h (reserved) Reserved; must be zero + * 1 7-0 FFh (reserved) Reserved; must be zero + */ +class MarkerFormat : GenericChartObject(), ChartObject { + private var rgbFore: java.awt.Color? = null + private var rgbBack: java.awt.Color? = null + private var miSize = 0 + private var imk: Short = 0 + private var icvFore: Short = 0 + private var icvBack: Short = 0 + private var grbit: Short = 0 + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 77, 0, 60, 0, 0, 0) + + /** + * @return String XML representation of this chart-type's options + */ + // TODO: Finish MarkerFormat Options + override//if (imk!=0) + val optionsXML: String + get() { + val sb = StringBuffer() + sb.append(" MarkerFormat=\"$imk\"") + return sb.toString() + } + + /** + * return the Marker Format field imk + * + * @return + */ + val markerFormat: Int + get() = imk.toInt() + + override fun init() { + super.init() + val data = this.data + rgbFore = java.awt.Color(if (data!![0] < 0) 255 + data[0] else data[0], if (data[1] < 0) 255 + data[1] else data[1], if (data[2] < 0) 255 + data[2] else data[2]) + rgbBack = java.awt.Color(if (data!![4] < 0) 255 + data[4] else data[4], if (data[5] < 0) 255 + data[5] else data[5], if (data[6] < 0) 255 + data[6] else data[6]) + imk = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + grbit = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + icvFore = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + icvBack = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + miSize = ByteTools.readInt(this.getBytesAt(14, 4)!!) + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("MarkerFormat", ignoreCase = true)) { + imk = java.lang.Short.parseShort(`val`) + bHandled = true + } + if (bHandled) + updateRecord() + return bHandled + } + + private fun updateRecord() { + imk = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + val b = ByteTools.shortToLEBytes(imk) + this.data[8] = b[0] + this.data[9] = b[1] + } + + /** + * set the Marker Format field imk + update the record + * + * @param imk + */ + fun setMarkerFormat(imk: Short) { + this.imk = imk + updateRecord() + } + + override fun toString(): String { + return "MarkerFormat: imk= $imk grbit=$grbit" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 7526015026522467305L + + // 20070716 KSC: Need to create new records + val prototype: XLSRecord? + get() { + val mf = MarkerFormat() + mf.opcode = XLSConstants.MARKERFORMAT + mf.data = mf.PROTOTYPE_BYTES + return mf + } // imk was 2 in default + + /** + * returns the SVG defs element used, in conjuction with getMarkerSVG, to define markers on charts in SVG + * + * @return String SVG marker defs + */ + //-5 -5 + // "star + // "dow-jones + // "stddev + val markerSVGDefs: String + get() = "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + /** + * returns the svg line to define the marker at the point x,y, in color clr, of marker style marker (1-9) + *

                      NOTE: you must use getMarkerSVGdefs before using this method + * + * @param x double x point + * @param y double y point + * @param clr String SVG color string + * @param marker int marker format (1-9) + * @return SVG + */ + fun getMarkerSVG(x: Double, y: Double, clr: String, marker: Int): String { + var markersvg = "" + else if (marker == 2) + // diamond + markersvg += "diamond1'/>" + else if (marker == 3) + // triangle + markersvg += "triangle1'/>" + else if (marker == 4) + // X + markersvg += "cross1'/>" + else if (marker == 5) + // Star ????????????? + markersvg += "circle1'/>" + else if (marker == 6) + // Dow-Jones ?????????? + markersvg += "circle1'/>" + else if (marker == 7) + // Std Dev ????????????? + markersvg += "circle1'/>" + else if (marker == 8) + // circle + markersvg += "circle1'/>" + else if (marker == 9) + // + sign + markersvg += "plus1'/>" + return markersvg + } + } +} + + diff --git a/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java b/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java deleted file mode 100644 index 940c43c..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.java +++ /dev/null @@ -1,433 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -//OOXML-specific structures - -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.Layout; -import io.starter.formats.OOXML.SpPr; -import io.starter.formats.OOXML.Title; -import io.starter.formats.OOXML.TxPr; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.HashMap; - - -public class OOXMLChart extends Chart { - public String lang = "en-US"; // default - public boolean roundedCorners = false; - public Title ttl = null; // title element - public io.starter.formats.OOXML.Legend ooxmlLegend = null; - public Layout plotAreaLayout = null; - private SpPr plotareashapeProps = null; // defines the shape properties for this chart (line and fill) - private SpPr csshapeProps = null; // defines the shape properties for this chart space - private TxPr txpr = null; - private String editMovement = "twoCell"; // default - private String name = null; // name property of cNvPr - private ArrayList chartEmbeds = null; // if present, name(s) of chart embeds (images, userShape definitions (drawingml files that define shapes ontop of chart)) - - /** - * create a new OOXMLChart from a 2003-v chart object - * - * @param c - * @param wbh - */ - public OOXMLChart(Chart c, WorkBookHandle wbh) { - // Walk up the superclass hierarchy - //io.starter.toolkit.Logger.log("BEFORE: chartArr: " + Arrays.toString(chartArr.toArray())); - for (Class obj = c.getClass(); - !obj.equals(Object.class); - obj = obj.getSuperclass()) { - java.lang.reflect.Field[] fields = obj.getDeclaredFields(); - for (int i = 0; i < fields.length; i++) { - fields[i].setAccessible(true); - try { - // for each class/suerclass, copy all fields - // from this object to the clone - fields[i].set(this, fields[i].get(c)); - } catch (IllegalArgumentException e) { - } catch (IllegalAccessException e) { - } - } - } - - - // ttl? - // chartlaout? - // txpr? - // name? - this.name = c.getTitle(); - if (c.hasDataLegend()) { - ooxmlLegend = io.starter.formats.OOXML.Legend.createLegend(c.getLegend()); - } - this.wbh = wbh; - } - - public String toString() { - String t = getTitle(); - if (!t.equals("")) - return t; - return name; // if no title, return OOXMLName - } - - - /** - * return the OOXML shape property for this chart - * - * @return - * @param type 0= chart shape props, 1=plot area shape props 2= chartspace shape props, 3= legend shape props - */ - public SpPr getSpPr(int type) { - if (type == 0) - return plotareashapeProps; - if (type == 1) - return csshapeProps; - return null; - } - - /** - * store the OOXML text formatting element for this chart - */ - public void setTxPr(TxPr txPr) { - txpr = txPr; - } - - /** - * return the OOXML text formatting element for this chart, if present - * - * @return - */ - public TxPr getTxPr() { - return txpr; - } - - /** - * define the OOXML shape property for this chart from an existing spPr element - * - * @param type 0=plot area shape props 1= chartspace shape props, 2= legend shape props - */ - public void setSpPr(int type, SpPr spPr) { - if (type == 0) { - plotareashapeProps = spPr; // plot area - int lw = -1, lclr = 0, bgcolor = -1; - lw = spPr.getLineWidth(); // TO DO: Style - lclr = spPr.getLineColor(); - //bgcolor= spPr.getColor(); - this.getAxes().setPlotAreaBorder(lw, lclr); - } else if (type == 1) { - csshapeProps = spPr; - } - - } - - /** - * return the OOXML title element for this chart - * - * @return - */ - public Title getOOXMLTitle() { - return ttl; - } - - /** - * set the OOXML title element for this chart - * - * @param t - */ - public void setOOXMLTitle(Title t, WorkBookHandle wb) { - ttl = t; - int fid = ttl.getFontId(wb); - if (fid == -1) fid = 5; // default ...? - float[] coords = null; - int lw = -1, lclr = 0, bgcolor = 0; - if (ttl.getLayout() != null) { // pos - coords = ttl.getLayout().getCoords(); - } - if (ttl.getSpPr() != null) { // Area Fill, Line Format - SpPr sp = ttl.getSpPr(); - lw = sp.getLineWidth(); // TO DO: Style, fill/color ... - lclr = sp.getLineColor(); - bgcolor = sp.getColor(); - } - if (coords != null) - charttitle.setFrame(lw, lclr, bgcolor, coords); - - // must also set the fontx id for the title - if (charttitle != null) - charttitle.setFontId(fid); - - } - - /** - * specify how to resize or move upon edit OOXML specific - * - * @param editMovement - */ - public void setEditMovement(String editMovement) { - this.editMovement = editMovement; - dirtyflag = true; - } - - /** - * return state of how to resize or move upon edit OOXML specific - * - * @return editMovement string - */ - public String getEditMovement() { - return this.editMovement; - } - - /** - * remove the legend from the chart - */ - public void removeLegend() { - showLegend(false, false); - ooxmlLegend = null; - } - - /** - * - */ - public String getOOXMLName() { - return this.name; - } - - /** - * set the OOXML-specific name for this chart - * - * @param name - */ - public void setOOXMLName(String name) { - this.name = name; - dirtyflag = true; - } - - /** - * returns information regarding external files associated with this chart - *
                      e.g. a chart user shape, an image - * - * @return - */ - public ArrayList getChartEmbeds() { - return chartEmbeds; - } - - /** - * sets external information linked to or "embedded" in this OOXML chart; - * can be a chart user shape, an image ... - *
                      NOTE: a userShape is a drawingml file name which defines the userShape (if any) - *
                      a userShape is a drawing or shape ontop of a chart - * - * @param String[] embedType, filename e.g. {"userShape", "userShape file name"} - */ - public void addChartEmbed(String[] ce) { - if (chartEmbeds == null) - chartEmbeds = new ArrayList(); - chartEmbeds.add(ce); - } - - /** - * return the OOXML representation of this chart object "c:chart" representing OOXML element in chartX.xml - *
                      below is complete the ordered sequence of child elements of the chart element - *
                      c:chart - parent= chartSpace - *

                    • title - *
                    • autoTitleDeleted - *
                    • pivotFmts - *
                    • view3d - *
                    • floor - *
                    • sideWall - *
                    • backWall - *
                    • plotArea (see below) - *
                    • legend - *
                    • plotVisOnly - *
                    • dispBlankAs - *
                    • showDlblsOverMax - * - *
                      plotArea: - *
                    • layout - *
                    • chart type (see below) - *
                    • axes *** - *
                    • dTable - *
                    • spPr - * - *
                      chart type: - *
                    • barDir Bar, Bar3d only - *
                    • radarStyle || scatterStyle - *
                    • ofPieType - *
                    • wireFrame surface - *
                    • grouping Area, Area3d, Line, Line3d, Bar, Bar3D - *
                    • varyColors not for Stock - *
                    • ser *n series - *
                    • dLbls not for surface - * Area Chart, AreaChart3D, LineChart, Line3D, Stock - *
                    • dropLines - * Bar Chart, Bar3d, ofPieChart - *
                    • gapWidth - * AreaChart3D, Line3D, Bar3D - *
                    • gapDepth - * Line, Stock - *
                    • hiLowLines - *
                    • upDownBars - * Line - *
                    • marker - *
                    • smooth - * BarChart only - *
                    • overlap - *
                    • serLines - * Bar 3d only - *
                    • shape - * ofPieChart - *
                    • splitType - *
                    • splitPos - *
                    • custSplit - *
                    • secondPieSize - *
                    • serLines - * Pie, Doughnut - *
                    • firstSliceAng - * Doughnut - *
                    • holeSize - * Surface - *
                    • bandFmts - * Bubble - *
                    • bubble3D - *
                    • bubbleScale - *
                    • showNegBubbles - *
                    • sizeRepresents - *
                    • axId - * - * @return StringBuffer - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - try { - int[] allCharts = this.getAllChartTypes(); // usually only 1 chart but some have overlay charts in addition to the default chart (chart 0) - - // lang - cooxml.append(""); - cooxml.append("\r\n"); - // rounded corners - if (this.roundedCorners) - cooxml.append(""); - // chart - cooxml.append(""); - cooxml.append("\r\n"); - // title - if (this.getOOXMLTitle() == null) {// if no OOXML title, see if have a BIFF8 title - if (!this.getTitle().equals("")) { - this.setOOXMLTitle(new Title(this.getTitleTd(), this.wbh.getWorkBook()), this.wbh); - } - } - if (this.getOOXMLTitle() != null) // otherwise there's no title - cooxml.append(this.getOOXMLTitle().getOOXML()); - - if (allCharts[0] != BUBBLECHART) { // bubble threeD handled in series for some reason - // Q: what if overlay charts are not 3D? what if default isn't and overlay is? - // view 3D - ThreeD td = this.getThreeDRec(0); - if (td != null) - cooxml.append(td.getOOXML()); - } - // TODO: Handle: - // floor - // sideWall - // backWall - // plot area (contains all chart types such as barChart, lineChart ...) - cooxml.append(""); - cooxml.append("\r\n"); - // layout: size and position - if (this.plotAreaLayout == null) { // if converted from XLS will hit here - HashMap chartMetrics = this.getMetrics(wbh); - double x = chartMetrics.get("x") / chartMetrics.get("canvasw"); - double y = chartMetrics.get("y") / chartMetrics.get("canvash"); - double w = chartMetrics.get("w") / chartMetrics.get("canvasw"); - double h = chartMetrics.get("h") / chartMetrics.get("canvash"); - this.plotAreaLayout = new Layout("inner", new double[]{x, y, w, h}); - } - cooxml.append(this.plotAreaLayout.getOOXML()); - - for (ChartType ch : chartgroup) { - cooxml.append(ch.getOOXML(catAxisId, valAxisId, serAxisId)); - } - - /* TODO: - * 1- varyColors ??? - * 2- getChartSeries.getOOXML -> nchart? - * 3- data labels - * 4- drop lines - * - * area charts -- bar colors!!! - * bar - serLines - * pie, doughnut - firstSliceArg - * radar -- filled? - * bubble -- bubbleScale, showNegBubbles, sizeRepresents - * surface -- wireframe, bandfmts - * - * pie of pie, bar of pie - * surface3d - * stock - * - * - */ - - // ****************************************************************************** - // after chart type ooxml, axes (if present) - cooxml.append(this.getAxes().getOOXML(XAXIS, 0, catAxisId, valAxisId)); - cooxml.append(this.getAxes().getOOXML(XVALAXIS, 2, catAxisId, valAxisId)); // valAx - for bubble/scatter - // val axis - cooxml.append(this.getAxes().getOOXML(YAXIS, 1, valAxisId, catAxisId)); // val axis - // ser axis - cooxml.append(this.getAxes().getOOXML(ZAXIS, 3, serAxisId, valAxisId)); // ser axis (crosses val axis) - // TODO: dateAx - if (this.getSpPr(0) != null) { // plot area shape props - cooxml.append(this.getSpPr(0).getOOXML()); - } else if (!this.wbh.getIsExcel2007()) { - SpPr sp = new SpPr("c", this.getPlotAreaBgColor().substring(1), 12700, this.getPlotAreaLnColor().substring(1)); - cooxml.append(sp.getOOXML()); - - } - - cooxml.append(""); - cooxml.append("\r\n"); - // legend - if (this.ooxmlLegend != null) cooxml.append(this.ooxmlLegend.getOOXML()); - - cooxml.append(""); // specifies that only visible cells should be plotted on the chart -// "gap", "span", "zero" --> default - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - if (this.getSpPr(1) != null) { // chart space shape props - cooxml.append(this.getSpPr(1).getOOXML()); - } - if (this.getTxPr() != null) { // text formatting - cooxml.append(this.getTxPr().getOOXML()); - } - } catch (Exception e) { - Logger.logErr("OOXMLChart.getOOXML: error generating OOXML. Chart not created: " + e.toString()); - } - return cooxml; //.toString(); - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.kt b/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.kt new file mode 100644 index 0000000..91bdfe3 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/OOXMLChart.kt @@ -0,0 +1,418 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +//OOXML-specific structures + +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.Layout +import io.starter.formats.OOXML.SpPr +import io.starter.formats.OOXML.Title +import io.starter.formats.OOXML.TxPr +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.HashMap + + +class OOXMLChart +/** + * create a new OOXMLChart from a 2003-v chart object + * + * @param c + * @param wbh + */ +(c: Chart, wbh: WorkBookHandle) : Chart() { + var lang = "en-US" // default + var roundedCorners = false + /** + * return the OOXML title element for this chart + * + * @return + */ + var ooxmlTitle: Title? = null // title element + var ooxmlLegend: io.starter.formats.OOXML.Legend? = null + var plotAreaLayout: Layout? = null + private var plotareashapeProps: SpPr? = null // defines the shape properties for this chart (line and fill) + private var csshapeProps: SpPr? = null // defines the shape properties for this chart space + /** + * return the OOXML text formatting element for this chart, if present + * + * @return + */ + /** + * store the OOXML text formatting element for this chart + */ + var txPr: TxPr? = null + /** + * return state of how to resize or move upon edit OOXML specific + * + * @return editMovement string + */ + /** + * specify how to resize or move upon edit OOXML specific + * + * @param editMovement + */ + var editMovement = "twoCell" + set(editMovement) { + field = editMovement + dirtyflag = true + } // default + private var name: String? = null // name property of cNvPr + /** + * returns information regarding external files associated with this chart + *

                      e.g. a chart user shape, an image + * + * @return + */ + var chartEmbeds: ArrayList<*>? = null + private set // if present, name(s) of chart embeds (images, userShape definitions (drawingml files that define shapes ontop of chart)) + + /** + * + */ + /** + * set the OOXML-specific name for this chart + * + * @param name + */ + var ooxmlName: String? + get() = this.name + set(name) { + this.name = name + dirtyflag = true + } + + init { + // Walk up the superclass hierarchy + //io.starter.toolkit.Logger.log("BEFORE: chartArr: " + Arrays.toString(chartArr.toArray())); + run { + var obj: Class<*> = c.javaClass + while (obj != Any::class.java) { + val fields = obj.declaredFields + for (i in fields.indices) { + fields[i].isAccessible = true + try { + // for each class/suerclass, copy all fields + // from this object to the clone + fields[i].set(this, fields[i].get(c)) + } catch (e: IllegalArgumentException) { + } catch (e: IllegalAccessException) { + } + + } + obj = obj.superclass + } + } + + + // ttl? + // chartlaout? + // txpr? + // name? + this.name = c.title + if (c.hasDataLegend()) { + ooxmlLegend = io.starter.formats.OOXML.Legend.createLegend(c.legend) + } + this.wbh = wbh + } + + override fun toString(): String { + val t = title + return if (t != "") t else name +// if no title, return OOXMLName + } + + + /** + * return the OOXML shape property for this chart + * + * @return + * @param type 0= chart shape props, 1=plot area shape props 2= chartspace shape props, 3= legend shape props + */ + fun getSpPr(type: Int): SpPr? { + if (type == 0) + return plotareashapeProps + return if (type == 1) csshapeProps else null + } + + /** + * define the OOXML shape property for this chart from an existing spPr element + * + * @param type 0=plot area shape props 1= chartspace shape props, 2= legend shape props + */ + fun setSpPr(type: Int, spPr: SpPr) { + if (type == 0) { + plotareashapeProps = spPr // plot area + var lw = -1 + var lclr = 0 + val bgcolor = -1 + lw = spPr.lineWidth // TO DO: Style + lclr = spPr.lineColor + //bgcolor= spPr.getColor(); + this.axes!!.setPlotAreaBorder(lw, lclr) + } else if (type == 1) { + csshapeProps = spPr + } + + } + + /** + * set the OOXML title element for this chart + * + * @param t + */ + fun setOOXMLTitle(t: Title, wb: WorkBookHandle) { + ooxmlTitle = t + var fid = ooxmlTitle!!.getFontId(wb) + if (fid == -1) fid = 5 // default ...? + var coords: FloatArray? = null + var lw = -1 + var lclr = 0 + var bgcolor = 0 + if (ooxmlTitle!!.layout != null) { // pos + coords = ooxmlTitle!!.layout!!.coords + } + if (ooxmlTitle!!.spPr != null) { // Area Fill, Line Format + val sp = ooxmlTitle!!.spPr + lw = sp!!.lineWidth // TO DO: Style, fill/color ... + lclr = sp.lineColor + bgcolor = sp.color + } + if (coords != null) + titleTd!!.setFrame(lw, lclr, bgcolor, coords) + + // must also set the fontx id for the title + if (titleTd != null) + titleTd!!.fontId = fid + + } + + /** + * remove the legend from the chart + */ + override fun removeLegend() { + showLegend(false, false) + ooxmlLegend = null + } + + /** + * sets external information linked to or "embedded" in this OOXML chart; + * can be a chart user shape, an image ... + *

                      NOTE: a userShape is a drawingml file name which defines the userShape (if any) + *

                      a userShape is a drawing or shape ontop of a chart + * + * @param String[] embedType, filename e.g. {"userShape", "userShape file name"} + */ + fun addChartEmbed(ce: Array) { + if (chartEmbeds == null) + chartEmbeds = ArrayList() + chartEmbeds!!.add(ce) + } + + /** + * return the OOXML representation of this chart object "c:chart" representing OOXML element in chartX.xml + *

                      below is complete the ordered sequence of child elements of the chart element + *

                      c:chart - parent= chartSpace + * * title + * * autoTitleDeleted + * * pivotFmts + * * view3d + * * floor + * * sideWall + * * backWall + * * plotArea (see below) + * * legend + * * plotVisOnly + * * dispBlankAs + * * showDlblsOverMax + * + *

                      plotArea: + * * layout + * * chart type (see below) + * * axes *** + * * dTable + * * spPr + * + *

                      chart type: + * * barDir Bar, Bar3d only + * * radarStyle || scatterStyle + * * ofPieType + * * wireFrame surface + * * grouping Area, Area3d, Line, Line3d, Bar, Bar3D + * * varyColors not for Stock + * * ser *n series + * * dLbls not for surface + * Area Chart, AreaChart3D, LineChart, Line3D, Stock + * * dropLines + * Bar Chart, Bar3d, ofPieChart + * * gapWidth + * AreaChart3D, Line3D, Bar3D + * * gapDepth + * Line, Stock + * * hiLowLines + * * upDownBars + * Line + * * marker + * * smooth + * BarChart only + * * overlap + * * serLines + * Bar 3d only + * * shape + * ofPieChart + * * splitType + * * splitPos + * * custSplit + * * secondPieSize + * * serLines + * Pie, Doughnut + * * firstSliceAng + * Doughnut + * * holeSize + * Surface + * * bandFmts + * Bubble + * * bubble3D + * * bubbleScale + * * showNegBubbles + * * sizeRepresents + * * axId + * + * @return StringBuffer + */ + fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer { + val cooxml = StringBuffer() + try { + val allCharts = this.allChartTypes // usually only 1 chart but some have overlay charts in addition to the default chart (chart 0) + + // lang + cooxml.append("") + cooxml.append("\r\n") + // rounded corners + if (this.roundedCorners) + cooxml.append("") + // chart + cooxml.append("") + cooxml.append("\r\n") + // title + if (this.ooxmlTitle == null) {// if no OOXML title, see if have a BIFF8 title + if (this.title != "") { + this.setOOXMLTitle(Title(this.titleTd!!, this.wbh!!.workBook), this.wbh) + } + } + if (this.ooxmlTitle != null) + // otherwise there's no title + cooxml.append(this.ooxmlTitle!!.ooxml) + + if (allCharts[0] != ChartConstants.BUBBLECHART) { // bubble threeD handled in series for some reason + // Q: what if overlay charts are not 3D? what if default isn't and overlay is? + // view 3D + val td = this.getThreeDRec(0) + if (td != null) + cooxml.append(td.ooxml) + } + // TODO: Handle: + // floor + // sideWall + // backWall + // plot area (contains all chart types such as barChart, lineChart ...) + cooxml.append("") + cooxml.append("\r\n") + // layout: size and position + if (this.plotAreaLayout == null) { // if converted from XLS will hit here + val chartMetrics = this.getMetrics(wbh) + val x = chartMetrics["x"] / chartMetrics["canvasw"] + val y = chartMetrics["y"] / chartMetrics["canvash"] + val w = chartMetrics["w"] / chartMetrics["canvasw"] + val h = chartMetrics["h"] / chartMetrics["canvash"] + this.plotAreaLayout = Layout("inner", doubleArrayOf(x, y, w, h)) + } + cooxml.append(this.plotAreaLayout!!.ooxml) + + for (ch in chartgroup) { + cooxml.append(ch.getOOXML(catAxisId, valAxisId, serAxisId)) + } + + /* TODO: + * 1- varyColors ??? + * 2- getChartSeries.getOOXML -> nchart? + * 3- data labels + * 4- drop lines + * + * area charts -- bar colors!!! + * bar - serLines + * pie, doughnut - firstSliceArg + * radar -- filled? + * bubble -- bubbleScale, showNegBubbles, sizeRepresents + * surface -- wireframe, bandfmts + * + * pie of pie, bar of pie + * surface3d + * stock + * + * + */ + + // ****************************************************************************** + // after chart type ooxml, axes (if present) + cooxml.append(this.axes!!.getOOXML(ChartConstants.XAXIS, 0, catAxisId, valAxisId)) + cooxml.append(this.axes!!.getOOXML(ChartConstants.XVALAXIS, 2, catAxisId, valAxisId)) // valAx - for bubble/scatter + // val axis + cooxml.append(this.axes!!.getOOXML(ChartConstants.YAXIS, 1, valAxisId, catAxisId)) // val axis + // ser axis + cooxml.append(this.axes!!.getOOXML(ChartConstants.ZAXIS, 3, serAxisId, valAxisId)) // ser axis (crosses val axis) + // TODO: dateAx + if (this.getSpPr(0) != null) { // plot area shape props + cooxml.append(this.getSpPr(0)!!.ooxml) + } else if (!this.wbh!!.isExcel2007) { + val sp = SpPr("c", this.plotAreaBgColor.substring(1), 12700, this.plotAreaLnColor.substring(1)) + cooxml.append(sp.ooxml) + + } + + cooxml.append("") + cooxml.append("\r\n") + // legend + if (this.ooxmlLegend != null) cooxml.append(this.ooxmlLegend!!.ooxml) + + cooxml.append("") // specifies that only visible cells should be plotted on the chart + // "gap", "span", "zero" --> default + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + if (this.getSpPr(1) != null) { // chart space shape props + cooxml.append(this.getSpPr(1)!!.ooxml) + } + if (this.txPr != null) { // text formatting + cooxml.append(this.txPr!!.ooxml) + } + } catch (e: Exception) { + Logger.logErr("OOXMLChart.getOOXML: error generating OOXML. Chart not created: $e") + } + + return cooxml //.toString(); + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ObjectLink.java b/src/main/java/io/starter/formats/XLS/charts/ObjectLink.java deleted file mode 100644 index 20221bb..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ObjectLink.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * ObjectLink: Attaches Text to Chart or to Chart Item (0x1027) - *

                      - * 4 wLinkObj 2 Object text is linked to (1= chart title, 2= Veritcal (y) axis title, 3= Category (x) axis title, 4= data series points, 7=Series Axis 12= Display Units - * 6 wLinkVar1 2 0-based series number (only if wLinkObj=4, otherwise 0) - * 8 wLinkVar2 2 0-based category number within the series specified by wLinkVar1. (only if wLinkObj=4, otherwise 0). If attached to entire series rather - * than a single data point, = 0xFFFF. - */ -public class ObjectLink extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -301929936750246017L; - short wLinkObj; - public static final int TYPE_TITLE = 1; - public static final int TYPE_XAXIS = 3; - public static final int TYPE_YAXIS = 2; - public static final int TYPE_DATAPOINTS = 4; - public static final int TYPE_ZAXIS = 7; - /** - * An axis-formatting option that determines how numeric units are displayed on a value axis. - */ - public static final int TYPE_DISPLAYUNITS = 0xC; - - public void init() { - super.init(); - byte[] rkdata = this.getData(); - wLinkObj = ByteTools.readShort(rkdata[0], rkdata[1]); - } - - /** - * Does this object link refer to the chart title? - * - * @return - */ - boolean isChartTitle() { - return wLinkObj == TYPE_TITLE; - } - - /** - * Does this object link refer to the XAxis Label? - * - * @return - */ - boolean isXAxisLabel() { - return wLinkObj == TYPE_XAXIS; - } - - - /** - * Does this object link refer to the YAxis Label? - * - * @return - */ - boolean isYAxisLabel() { - return wLinkObj == TYPE_YAXIS; - } - - public int getType() { - return wLinkObj; - } - - public void setType(int type) { - wLinkObj = (short) type; - this.getData()[0] = (byte) wLinkObj; - } - - public static XLSRecord getPrototype(int type) { - ObjectLink o = new ObjectLink(); - o.setOpcode(OBJECTLINK); - o.setData(o.PROTOTYPE_BYTES); - o.setType(type); - return o; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{1, 0, 0, 0, 0, 0}; -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ObjectLink.kt b/src/main/java/io/starter/formats/XLS/charts/ObjectLink.kt new file mode 100644 index 0000000..b3f72a9 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ObjectLink.kt @@ -0,0 +1,103 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **ObjectLink: Attaches Text to Chart or to Chart Item (0x1027)** + * + * + * 4 wLinkObj 2 Object text is linked to (1= chart title, 2= Veritcal (y) axis title, 3= Category (x) axis title, 4= data series points, 7=Series Axis 12= Display Units + * 6 wLinkVar1 2 0-based series number (only if wLinkObj=4, otherwise 0) + * 8 wLinkVar2 2 0-based category number within the series specified by wLinkVar1. (only if wLinkObj=4, otherwise 0). If attached to entire series rather + * than a single data point, = 0xFFFF. + */ +class ObjectLink : GenericChartObject(), ChartObject { + internal var wLinkObj: Short = 0 + + /** + * Does this object link refer to the chart title? + * + * @return + */ + internal val isChartTitle: Boolean + get() = wLinkObj.toInt() == TYPE_TITLE + + /** + * Does this object link refer to the XAxis Label? + * + * @return + */ + internal val isXAxisLabel: Boolean + get() = wLinkObj.toInt() == TYPE_XAXIS + + + /** + * Does this object link refer to the YAxis Label? + * + * @return + */ + internal val isYAxisLabel: Boolean + get() = wLinkObj.toInt() == TYPE_YAXIS + + var type: Int + get() = wLinkObj.toInt() + set(type) { + wLinkObj = type.toShort() + this.data[0] = wLinkObj.toByte() + } + + private val PROTOTYPE_BYTES = byteArrayOf(1, 0, 0, 0, 0, 0) + + override fun init() { + super.init() + val rkdata = this.data + wLinkObj = ByteTools.readShort(rkdata!![0].toInt(), rkdata[1].toInt()) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -301929936750246017L + val TYPE_TITLE = 1 + val TYPE_XAXIS = 3 + val TYPE_YAXIS = 2 + val TYPE_DATAPOINTS = 4 + val TYPE_ZAXIS = 7 + /** + * An axis-formatting option that determines how numeric units are displayed on a value axis. + */ + val TYPE_DISPLAYUNITS = 0xC + + fun getPrototype(type: Int): XLSRecord { + val o = ObjectLink() + o.opcode = XLSConstants.OBJECTLINK + o.data = o.PROTOTYPE_BYTES + o.type = type + return o + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/OfPieChart.java b/src/main/java/io/starter/formats/XLS/charts/OfPieChart.java deleted file mode 100644 index 39d94e8..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/OfPieChart.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class OfPieChart extends ChartType { - Boppop ofPie = null; - - public OfPieChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - ofPie = (Boppop) charttype; - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append(""); - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - // gapWidth - if (ofPie.getpcGap() != 150) - cooxml.append(""); - // splitType - if (ofPie.getSplitType() != -1) - cooxml.append(""); - // splitPos - if (ofPie.getSplitPos() != 0) - cooxml.append(""); - // custSplit TODO: FINISH - // secondPieSize - if (ofPie.getSecondPieSize() != 75) - cooxml.append(""); - // serLines - ChartLine cl = cf.getChartLinesRec(); - if (cl != null) - cooxml.append(cl.getOOXML()); - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/OfPieChart.kt b/src/main/java/io/starter/formats/XLS/charts/OfPieChart.kt new file mode 100644 index 0000000..c0c4b04 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/OfPieChart.kt @@ -0,0 +1,76 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +class OfPieChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + internal var ofPie: Boppop? = null + + init { + ofPie = charttype as Boppop + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("") + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + // gapWidth + if (ofPie!!.getpcGap() != 150) + cooxml.append("") + // splitType + if (ofPie!!.splitType != -1) + cooxml.append("") + // splitPos + if (ofPie!!.splitPos != 0) + cooxml.append("") + // custSplit TODO: FINISH + // secondPieSize + if (ofPie!!.secondPieSize != 75) + cooxml.append("") + // serLines + val cl = cf!!.chartLinesRec + if (cl != null) + cooxml.append(cl.ooxml) + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Units.java b/src/main/java/io/starter/formats/XLS/charts/Picf.kt similarity index 71% rename from src/main/java/io/starter/formats/XLS/charts/Units.java rename to src/main/java/io/starter/formats/XLS/charts/Picf.kt index 6ccdd86..5c4a36c 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Units.java +++ b/src/main/java/io/starter/formats/XLS/charts/Picf.kt @@ -20,18 +20,21 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts /** - * Units: Chart Units (0x1001) + * **Picf: Picture Format(0x103c)** */ -public class Units extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8391773940364481970L; +class Picf : GenericChartObject(), ChartObject { - public void init() { - super.init(); + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 7241922273506449916L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Pie.java b/src/main/java/io/starter/formats/XLS/charts/Pie.java deleted file mode 100644 index 841a246..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Pie.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - - -/** - * Pie: Chart Group Is a pie Chart Group(0x1019) - *

                      - * 4 anStart 2 Angle of the first pie slice expressed in degrees. Must be <= 360 - * 6 pcDonut 2 0= true pie chart, non-zero= size of center hole in a donut chart, as a percentage - * 8 grbit 2 Option Flags - *

                      - *

                      - * grbit: - * 0 0x1 fHasShadow 1= has shadow - * 1 0x2 fShowLdrLines 1= show leader lines to data labels - */ -public class Pie extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7851320124576950635L; - private short grbit = 0; - private boolean fHasShadow = false, fShowLdrLines = false; - protected short pcDonut = 0, anStart = 0; - - public void init() { - super.init(); - byte[] data = getData(); - anStart = ByteTools.readShort(data[0], data[1]); - pcDonut = data[2]; - if (pcDonut == 0) - chartType = ChartConstants.PIECHART; // 20070703 KSC - else - chartType = ChartConstants.DOUGHNUTCHART; - grbit = ByteTools.readShort(data[4], data[5]); - fHasShadow = ((grbit & 0x1) == 0x1); - fShowLdrLines = ((grbit & 0x2) == 0x2); - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 2, 0}; - - public static XLSRecord getPrototype() { - Pie b = new Pie(); - b.setOpcode(PIE); - b.setData(b.PROTOTYPE_BYTES); - b.init(); - return b; - } - - // 20070703 KSC and below ... - private void updateRecord() { - this.getData()[2] = (byte) pcDonut; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - } - - public void setAsPieChart() { - pcDonut = 0; - chartType = ChartConstants.PIECHART; - this.updateRecord(); - } - - public void setAsDoughnutChart() { - pcDonut = 50; // default % - chartType = ChartConstants.DOUGHNUTCHART; - this.updateRecord(); - } - - /** - * return the Doughnut hole size (if > 0) - * - * @return - */ - public int getDoughnutSize() { - return pcDonut; - } - - - /** - * size of center hole in a donut chart, as a percentage. 0 for pie - * - * @return - */ - public void setDoughnutSize(int s) { - pcDonut = (short) s; - byte[] b = ByteTools.shortToLEBytes(pcDonut); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - } - - /** - * return the Angle of the first pie slice expressed in degrees. Must be <= 360 - * - * @return - */ - public int getAnStart() { - return anStart; - } - - - /** - * sets the Angle of the first pie slice expressed in degrees. Must be <= 360 - * - * @return - */ - public void setAnStart(int a) { - anStart = (short) a; - byte[] b = ByteTools.shortToLEBytes(anStart); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (fHasShadow) - sb.append(" Shadow=\"true\""); - if (fShowLdrLines) - sb.append(" ShowLdrLines=\"true\""); - if (pcDonut > 0) - sb.append(" Donut=\"" + pcDonut + "\""); - return sb.toString(); - } - - /** - * Handle setting options from XML in a generic manner - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("Shadow")) { - setHasShadow(true); - bHandled = true; - } else if (op.equalsIgnoreCase("ShowLdrLines")) { - setShowLdrLines(true); - bHandled = true; - } else if (op.equalsIgnoreCase("Donut")) { - setDonutPercentage(val); - bHandled = true; - } else if (op.equalsIgnoreCase("donutSize")) { - setDonutPercentage(val); - bHandled = true; - } - return bHandled; - } - - public String getChartOption(String op) { - if (op.equals("ShowLdrLines")) { // Pie - return String.valueOf(this.showLdrLines()); - } else if (op.equals("donutSize")) { // Pie - return String.valueOf(this.getDonutPercentage()); - } else - return super.getChartOption(op); - } - - public boolean hasShadow() { - return fHasShadow; - } - - public boolean showLdrLines() { - return fShowLdrLines; - } - - public void setHasShadow(boolean bHasShadow) { - fHasShadow = bHasShadow; - grbit = ByteTools.updateGrBit(grbit, fHasShadow, 0); - updateRecord(); - } - - public void setShowLdrLines(boolean bShowLdrLines) { - fShowLdrLines = bShowLdrLines; - grbit = ByteTools.updateGrBit(grbit, fShowLdrLines, 1); - updateRecord(); - } - - public void setDonutPercentage(String val) { - try { - pcDonut = Short.valueOf(val).shortValue(); - } catch (Exception e) { - } - updateRecord(); - } - - public short getDonutPercentage() { - return pcDonut; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Pie.kt b/src/main/java/io/starter/formats/XLS/charts/Pie.kt new file mode 100644 index 0000000..c26e4dc --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Pie.kt @@ -0,0 +1,219 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + + +/** + * **Pie: Chart Group Is a pie Chart Group(0x1019)** + * + * + * 4 anStart 2 Angle of the first pie slice expressed in degrees. Must be <= 360 + * 6 pcDonut 2 0= true pie chart, non-zero= size of center hole in a donut chart, as a percentage + * 8 grbit 2 Option Flags + * + * + * + * + * grbit: + * 0 0x1 fHasShadow 1= has shadow + * 1 0x2 fShowLdrLines 1= show leader lines to data labels + */ +class Pie : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var fHasShadow = false + private var fShowLdrLines = false + var donutPercentage: Short = 0 + protected set + protected var anStart: Short = 0 + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 2, 0) + + /** + * return the Doughnut hole size (if > 0) + * + * @return + */ + /** + * size of center hole in a donut chart, as a percentage. 0 for pie + * + * @return + */ + var doughnutSize: Int + get() = donutPercentage.toInt() + set(s) { + donutPercentage = s.toShort() + val b = ByteTools.shortToLEBytes(donutPercentage) + this.data[2] = b[0] + this.data[3] = b[1] + } + + /** + * @return String XML representation of this chart-type's options + */ + override val optionsXML: String + get() { + val sb = StringBuffer() + if (fHasShadow) + sb.append(" Shadow=\"true\"") + if (fShowLdrLines) + sb.append(" ShowLdrLines=\"true\"") + if (donutPercentage > 0) + sb.append(" Donut=\"$donutPercentage\"") + return sb.toString() + } + + override fun init() { + super.init() + val data = data + anStart = ByteTools.readShort(data!![0].toInt(), data[1].toInt()) + donutPercentage = data[2].toShort() + if (donutPercentage.toInt() == 0) + chartType = ChartConstants.PIECHART // 20070703 KSC + else + chartType = ChartConstants.DOUGHNUTCHART + grbit = ByteTools.readShort(data[4].toInt(), data[5].toInt()) + fHasShadow = grbit and 0x1 == 0x1 + fShowLdrLines = grbit and 0x2 == 0x2 + } + + // 20070703 KSC and below ... + private fun updateRecord() { + this.data[2] = donutPercentage.toByte() + val b = ByteTools.shortToLEBytes(grbit) + this.data[4] = b[0] + this.data[5] = b[1] + } + + fun setAsPieChart() { + donutPercentage = 0 + chartType = ChartConstants.PIECHART + this.updateRecord() + } + + fun setAsDoughnutChart() { + donutPercentage = 50 // default % + chartType = ChartConstants.DOUGHNUTCHART + this.updateRecord() + } + + /** + * return the Angle of the first pie slice expressed in degrees. Must be <= 360 + * + * @return + */ + fun getAnStart(): Int { + return anStart.toInt() + } + + + /** + * sets the Angle of the first pie slice expressed in degrees. Must be <= 360 + * + * @return + */ + fun setAnStart(a: Int) { + anStart = a.toShort() + val b = ByteTools.shortToLEBytes(anStart) + this.data[0] = b[0] + this.data[1] = b[1] + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("Shadow", ignoreCase = true)) { + setHasShadow(true) + bHandled = true + } else if (op.equals("ShowLdrLines", ignoreCase = true)) { + setShowLdrLines(true) + bHandled = true + } else if (op.equals("Donut", ignoreCase = true)) { + setDonutPercentage(`val`) + bHandled = true + } else if (op.equals("donutSize", ignoreCase = true)) { + setDonutPercentage(`val`) + bHandled = true + } + return bHandled + } + + override fun getChartOption(op: String): String? { + return if (op == "ShowLdrLines") { // Pie + this.showLdrLines().toString() + } else if (op == "donutSize") { // Pie + this.donutPercentage.toString() + } else + super.getChartOption(op) + } + + override fun hasShadow(): Boolean { + return fHasShadow + } + + fun showLdrLines(): Boolean { + return fShowLdrLines + } + + fun setHasShadow(bHasShadow: Boolean) { + fHasShadow = bHasShadow + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 0) + updateRecord() + } + + fun setShowLdrLines(bShowLdrLines: Boolean) { + fShowLdrLines = bShowLdrLines + grbit = ByteTools.updateGrBit(grbit, fShowLdrLines, 1) + updateRecord() + } + + fun setDonutPercentage(`val`: String) { + try { + donutPercentage = java.lang.Short.valueOf(`val`).toShort() + } catch (e: Exception) { + } + + updateRecord() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -7851320124576950635L + + val prototype: XLSRecord? + get() { + val b = Pie() + b.opcode = XLSConstants.PIE + b.data = b.PROTOTYPE_BYTES + b.init() + return b + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.java b/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.java deleted file mode 100644 index ef7a7c5..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.HashMap; - -public class Pie3dChart extends PieChart { - public Pie3dChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - StringBuffer svg = new StringBuffer(); - if (series.size() == 0) { - Logger.logErr("Pie.getSVG: error in series"); - return ""; - } - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - final int LABELOFFSET = 15; - - int n = series.size(); - double centerx = 0.0, centery = 0.0, radius = 0.0; - double radiusy = 0.0; - - centerx = w / 2 + chartMetrics.get("x"); - centery = h / 2 + chartMetrics.get("y"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - svg.append("\r\n"); - - // if have any series - should :) - radius = Math.min(w, h) / 2; - //radius= Math.min(w, h); // ????????????? just an estimate? - double depth3d = 0; // only used for 3d - radiusy = radius * 2 / 3; - depth3d = radius / 5.5; - if (n > 0) { - double[] oneseries = (double[]) series.get(0); //FOR PIE CHARTS ONLY 1 SERIES VALUE POINTS ARE USED ********************************** - String[] curranges = (String[]) s.getSeriesRanges().get(0); - double total = 0.0; - for (int j = 0; j < oneseries.length; j++) { // get total in order to calculate percentages - total += oneseries[j]; - } - if (dls.length == 1) { // no series-specific data labels; expand to entire series for loop below - int dl = dls[0]; - dls = new int[oneseries.length]; - for (int i = 0; i < dls.length; i++) - dls[i] = dl; - } - String path = ""; - double x = centerx + radius; - double y = centery; - double percentage = 0; - double lasta = 0; - int largearcflag = 0; - int sweepflag = 0; - - // Now create each pie wedge according to it's percentage value - for (int j = 0; j < oneseries.length; j++) { - if (total > 0) - percentage = oneseries[j] / total; - double angle = (percentage * 360) + lasta; - double x1 = centerx + radius * (Math.cos(Math.toRadians(angle))); - double y1 = centery - radiusy * (Math.sin(Math.toRadians(angle))); - if ((percentage * 360)/*angle*/ > 180) { - largearcflag = 1; - } else - largearcflag = 0; - - if (y1 >= centery) { // the lower portion of a 3d pie chart that makes it look 3d - double x0 = x; - double y0 = y; - if (centery > y) { // lower portion has to be below origin - y0 = centery; - x0 = centerx - radius; - } - path = "M" + x0 + "," + y0 + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + x1 + " " + (y1 + depth3d); - path += " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + "1" + " " + x0 + " " + (y0 + depth3d) + " Z"; - svg.append("\r\n"); - } - path = "M" + centerx + " " + centery + " L" + x + " " + y + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z"; - svg.append("\r\n"); - - String l = getSVGDataLabels(dls, axisMetrics, oneseries[j], percentage, j, legends, categories[j].toString()); - if (l != null) { - // apparently labels are outside of wedge unless angle is >= 30 ... - // category labels - double halfa = ((percentage / 2) * 360) + lasta; // center in area - double x2, y2; - if (percentage < .3) { // display label on outside with leader lines - x2 = centerx + (radius + LABELOFFSET) * (Math.cos(Math.toRadians(halfa))); - y2 = centery - (radiusy + LABELOFFSET) * (Math.sin(Math.toRadians(halfa))); - } else { // display label within wedge for > 30% - x2 = centerx + (radius / 2) * (Math.cos(Math.toRadians(halfa))); - y2 = centery - (radiusy / 2) * (Math.sin(Math.toRadians(halfa))); - } - String style = ""; - if (percentage >= .3) - style = " style='text-anchor: middle;'"; - else if (lasta > 90 && lasta < 270) { // right-align text for wedges on left side of pie - style = " style='text-anchor: end;'"; - // TODO: dec x2 - } - svg.append("" + l + "\r\n"); - // leaderline - not exactly like Excel's but ... :) do when NOT putting text within wedge - if (percentage < .3) { - double x0 = centerx + (radius) * (Math.cos(Math.toRadians(halfa))); - double y0 = centery - (radiusy) * (Math.sin(Math.toRadians(halfa))); - svg.append("\r\n"); - } - } - lasta = angle; - x = x1; - y = y1; - } - } - svg.append("\r\n"); - - return svg.toString(); - } - /** - * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), - * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). - * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, - * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) - * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). - * A value of 0 causes the arc to be drawn in a "negative-angle" direction - * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). - */ - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } -} - - diff --git a/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.kt b/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.kt new file mode 100644 index 0000000..d63ca13 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Pie3dChart.kt @@ -0,0 +1,202 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.HashMap + +class Pie3dChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : PieChart(charttype, cf, wb) { + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + val svg = StringBuffer() + if (series!!.size == 0) { + Logger.logErr("Pie.getSVG: error in series") + return "" + } + var dls = dataLabelInts // get array of data labels (can be specific per series ...) + val LABELOFFSET = 15 + + val n = series.size + var centerx = 0.0 + var centery = 0.0 + var radius = 0.0 + var radiusy = 0.0 + + centerx = w / 2 + chartMetrics["x"] + centery = h / 2 + chartMetrics["y"] + svg.append("\r\n") + svg.append("\r\n") + svg.append("\r\n") + svg.append("\r\n") + svg.append("\r\n") + svg.append("\r\n") + + // if have any series - should :) + radius = Math.min(w, h) / 2 + //radius= Math.min(w, h); // ????????????? just an estimate? + var depth3d = 0.0 // only used for 3d + radiusy = radius * 2 / 3 + depth3d = radius / 5.5 + if (n > 0) { + val oneseries = series[0] as DoubleArray //FOR PIE CHARTS ONLY 1 SERIES VALUE POINTS ARE USED ********************************** + val curranges = s.seriesRanges!![0] as Array + var total = 0.0 + for (j in oneseries.indices) { // get total in order to calculate percentages + total += oneseries[j] + } + if (dls.size == 1) { // no series-specific data labels; expand to entire series for loop below + val dl = dls[0] + dls = IntArray(oneseries.size) + for (i in dls.indices) + dls[i] = dl + } + var path = "" + var x = centerx + radius + var y = centery + var percentage = 0.0 + var lasta = 0.0 + var largearcflag = 0 + val sweepflag = 0 + + // Now create each pie wedge according to it's percentage value + for (j in oneseries.indices) { + if (total > 0) + percentage = oneseries[j] / total + val angle = percentage * 360 + lasta + val x1 = centerx + radius * Math.cos(Math.toRadians(angle)) + val y1 = centery - radiusy * Math.sin(Math.toRadians(angle)) + if (percentage * 360/*angle*/ > 180) { + largearcflag = 1 + } else + largearcflag = 0 + + if (y1 >= centery) { // the lower portion of a 3d pie chart that makes it look 3d + var x0 = x + var y0 = y + if (centery > y) { // lower portion has to be below origin + y0 = centery + x0 = centerx - radius + } + path = "M" + x0 + "," + y0 + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + x1 + " " + (y1 + depth3d) + path += " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + "1" + " " + x0 + " " + (y0 + depth3d) + " Z" + svg.append("\r\n") + } + path = "M" + centerx + " " + centery + " L" + x + " " + y + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z" + svg.append("\r\n") + + val l = getSVGDataLabels(dls, axisMetrics, oneseries[j], percentage, j, legends, categories!![j].toString()) + if (l != null) { + // apparently labels are outside of wedge unless angle is >= 30 ... + // category labels + val halfa = percentage / 2 * 360 + lasta // center in area + val x2: Double + val y2: Double + if (percentage < .3) { // display label on outside with leader lines + x2 = centerx + (radius + LABELOFFSET) * Math.cos(Math.toRadians(halfa)) + y2 = centery - (radiusy + LABELOFFSET) * Math.sin(Math.toRadians(halfa)) + } else { // display label within wedge for > 30% + x2 = centerx + radius / 2 * Math.cos(Math.toRadians(halfa)) + y2 = centery - radiusy / 2 * Math.sin(Math.toRadians(halfa)) + } + var style = "" + if (percentage >= .3) + style = " style='text-anchor: middle;'" + else if (lasta > 90 && lasta < 270) { // right-align text for wedges on left side of pie + style = " style='text-anchor: end;'" + // TODO: dec x2 + } + svg.append("$l\r\n") + // leaderline - not exactly like Excel's but ... :) do when NOT putting text within wedge + if (percentage < .3) { + val x0 = centerx + radius * Math.cos(Math.toRadians(halfa)) + val y0 = centery - radiusy * Math.sin(Math.toRadians(halfa)) + svg.append("\r\n") + } + } + lasta = angle + x = x1 + y = y1 + } + } + svg.append("\r\n") + + return svg.toString() + } + /** + * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), + * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). + * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, + * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) + * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). + * A value of 0 causes the arc to be drawn in a "negative-angle" direction + * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). + */ + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } +} + + diff --git a/src/main/java/io/starter/formats/XLS/charts/PieChart.java b/src/main/java/io/starter/formats/XLS/charts/PieChart.java deleted file mode 100644 index 0e82728..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/PieChart.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartSeriesHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; - - -public class PieChart extends ChartType { - Pie pie = null; - - public PieChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - pie = (Pie) charttype; - } - - /** - * return the JSON that - * - * @param seriesvals - * @param range - * @return - */ - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) - throws JSONException { - JSONObject chartObjectJSON = new JSONObject(); - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // TODO: check out labels options chosen: default label is Category name + percentage ... - double yMax = 0.0, yMin = 0.0; - double len = 0.0; - JSONArray pieSeries = new JSONArray(); - try { - String range = series[0].getCategoryRange(); // 20080516 KSC: retrieve cat range instead of parameter - JSONArray cats = CellRange.getValuesAsJSON(range, wbh); // parse category range into JSON Array - JSONArray seriesvals = CellRange.getValuesAsJSON(series[0].getSeriesRange(), wbh); - double piesum = 0; - for (int k = 0; k < seriesvals.length(); k++) { - piesum += seriesvals.getDouble(k); - yMax = Math.max(yMax, seriesvals.getDouble(k)); - yMin = Math.min(yMin, seriesvals.getDouble(k)); - } - double percent = 100 / piesum; - for (int k = 0; k < seriesvals.length(); k++) { - JSONObject piepoint = new JSONObject(); - piepoint.put("y", seriesvals.getDouble(k)); - piepoint.put("text", cats.getString(k) + "\n" + Math.round(percent * seriesvals.getDouble(k)) + "%"); - piepoint.put("color", FormatConstants.SVGCOLORSTRINGS[series[k].getPieChartSliceColor(k)]); - piepoint.put("stroke", getDarkColor()); - pieSeries.put(piepoint); - } - len = seriesvals.length(); - } catch (Exception e) { - // TODO: warning ...? - } - // 20090717 KSC: input outside of try/catch to always set - minMax[0] = new Double(yMin); - minMax[1] = new Double(yMax); - minMax[2] = new Double(len); - chartObjectJSON.put("Series", pieSeries); - chartObjectJSON.put("SeriesFills", ""); // not applicable for pie charts; color is set above - return chartObjectJSON; - } - - /** - * return the type JSON for this Chart Object - * - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON = new JSONObject(); - typeJSON.put("labelOffset", -25); // default - typeJSON.put("precision", 0); // default - rounds percentages up - typeJSON.put("type", "Pie"); - // TODO: Interpret distance ... - /* - DataFormat df= this.getParentChart().getChartFormat().getDataFormatRec(); - for (int z= 0; z < df.chartArr.size(); z++) { - // LineFormat = color, ticks.., AreaFormat= color/patterns ..., PieFormat= distance as percentage, MarkerFormat, AttachedLabel - BiffRec b = (BiffRec)df.chartArr.get(z); - if (b instanceof PieFormat) { - //if (((PieFormat) b).getPercentage()!=0) - // typeJSON.put("percentage", ((PieFormat) b).getPercentage()); - // TODO: Convert Excel percentage to Dojo labelOffset - */ - return typeJSON; - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - StringBuffer svg = new StringBuffer(); - // if have any series - should :) - if (series.size() == 0) { - Logger.logErr("Pie.getSVG: error in series"); - return ""; - } - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - final int LABELOFFSET = 15; - - int n = series.size(); - double centerx = 0.0, centery = 0.0, radius = 0.0; - double radiusy = 0.0; - centerx = w / 2 + chartMetrics.get("x"); - centery = h / 2 + chartMetrics.get("y"); - svg.append("\r\n"); - - radius = (Math.min(w, h)) / 2; - //radius= Math.min(w, h)/2; // too small - //radius= Math.min(w, h); // ????????????? just an estimate? - radiusy = radius; - if (n > 0) { - double[] oneseries = (double[]) series.get(0); //FOR PIE CHARTS ONLY 1 SERIES VALUE POINTS ARE USED ********************************** - String[] curranges = (String[]) s.getSeriesRanges().get(0); - double total = 0.0; - for (int j = 0; j < oneseries.length; j++) { // get total in order to calculate percentages - total += oneseries[j]; - } - if (dls.length == 1) { // no series-specific data labels; expand to entire series for loop below - int dl = dls[0]; - dls = new int[oneseries.length]; - for (int i = 0; i < dls.length; i++) - dls[i] = dl; - } - String path = ""; - double x = centerx + radius; - double y = centery; - double percentage = 0; - double lasta = 0; - int largearcflag = 0; - int sweepflag = 0; - - // Now create each pie wedge according to it's percentage value - for (int j = 0; j < oneseries.length; j++) { - if (total > 0) - percentage = oneseries[j] / total; - double angle = (percentage * 360) + lasta; - double x1 = centerx + radius * (Math.cos(Math.toRadians(angle))); - double y1 = centery - radiusy * (Math.sin(Math.toRadians(angle))); - if ((percentage * 360)/*angle*/ > 180) { - largearcflag = 1; - } else - largearcflag = 0; - path = "M" + centerx + " " + centery + " L" + x + " " + y + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z"; - svg.append("\r\n"); -// " path='' d='" + path + "' fill-rule='evenodd' transform='matrix(1, 0, 0, 1, 0, 0)'/>\r\n"); - - String l = getSVGDataLabels(dls, axisMetrics, oneseries[j], percentage, j, legends, categories[j].toString()); - if (l != null) { - // apparently labels are outside of wedge unless angle is >= 30 ... - // category labels - double halfa = ((percentage / 2) * 360) + lasta; // center in area - double x2, y2; - if (percentage < .3) { // display label on outside with leader lines - x2 = centerx + (radius + LABELOFFSET) * (Math.cos(Math.toRadians(halfa))); - y2 = centery - (radiusy + LABELOFFSET) * (Math.sin(Math.toRadians(halfa))); - } else { // display label within wedge for > 30% - x2 = centerx + (radius / 2) * (Math.cos(Math.toRadians(halfa))); - y2 = centery - (radiusy / 2) * (Math.sin(Math.toRadians(halfa))); - } - String style = ""; - if (percentage >= .3) - style = " style='text-anchor: middle;'"; - else if (lasta > 90 && lasta < 270) { // right-align text for wedges on left side of pie - style = " style='text-anchor: end;'"; - // TODO: dec x2 - } - svg.append("" + l + "\r\n"); - // leaderline - not exactly like Excel's but ... :) do when NOT putting text within wedge - if (percentage < .3) { - double x0 = centerx + (radius) * (Math.cos(Math.toRadians(halfa))); - double y0 = centery - (radiusy) * (Math.sin(Math.toRadians(halfa))); - svg.append("\r\n"); - } - } - lasta = angle; - x = x1; - y = y1; - } - } - svg.append("\r\n"); - - return svg.toString(); - } - /** - * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), - * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). - * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, - * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) - * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). - * A value of 0 causes the arc to be drawn in a "negative-angle" direction - * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). - */ - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - //TODO: FINISH - //cooxml.append(getDataLabelsOOXML(cf)); - - // TODO: firstSLiceAng -// if (pie.getAnStart()!=0) - cooxml.append(""); - cooxml.append(""); - cooxml.append("\r\n"); - - return cooxml; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/PieChart.kt b/src/main/java/io/starter/formats/XLS/charts/PieChart.kt new file mode 100644 index 0000000..3443548 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/PieChart.kt @@ -0,0 +1,276 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ChartSeriesHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.FormatConstants +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +import java.util.ArrayList +import java.util.HashMap + + +open class PieChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + internal var pie: Pie? = null + + /** + * return the type JSON for this Chart Object + * + * @return + */ + override// default + // default - rounds percentages up + // TODO: Interpret distance ... + /* + DataFormat df= this.getParentChart().getChartFormat().getDataFormatRec(); + for (int z= 0; z < df.chartArr.size(); z++) { + // LineFormat = color, ticks.., AreaFormat= color/patterns ..., PieFormat= distance as percentage, MarkerFormat, AttachedLabel + BiffRec b = (BiffRec)df.chartArr.get(z); + if (b instanceof PieFormat) { + //if (((PieFormat) b).getPercentage()!=0) + // typeJSON.put("percentage", ((PieFormat) b).getPercentage()); + // TODO: Convert Excel percentage to Dojo labelOffset + */ val typeJSON: JSONObject + @Throws(JSONException::class) + get() { + val typeJSON = JSONObject() + typeJSON.put("labelOffset", -25) + typeJSON.put("precision", 0) + typeJSON.put("type", "Pie") + return typeJSON + } + + init { + pie = charttype as Pie + } + + /** + * return the JSON that + * + * @param seriesvals + * @param range + * @return + */ + @Throws(JSONException::class) + fun getJSON(series: Array, wbh: WorkBookHandle, minMax: Array): JSONObject { + val chartObjectJSON = JSONObject() + // Type JSON + chartObjectJSON.put("type", this.typeJSON) + + // TODO: check out labels options chosen: default label is Category name + percentage ... + var yMax = 0.0 + var yMin = 0.0 + var len = 0.0 + val pieSeries = JSONArray() + try { + val range = series[0].categoryRange // 20080516 KSC: retrieve cat range instead of parameter + val cats = CellRange.getValuesAsJSON(range, wbh) // parse category range into JSON Array + val seriesvals = CellRange.getValuesAsJSON(series[0].seriesRange, wbh) + var piesum = 0.0 + for (k in 0 until seriesvals.length()) { + piesum += seriesvals.getDouble(k) + yMax = Math.max(yMax, seriesvals.getDouble(k)) + yMin = Math.min(yMin, seriesvals.getDouble(k)) + } + val percent = 100 / piesum + for (k in 0 until seriesvals.length()) { + val piepoint = JSONObject() + piepoint.put("y", seriesvals.getDouble(k)) + piepoint.put("text", cats.getString(k) + "\n" + Math.round(percent * seriesvals.getDouble(k)) + "%") + piepoint.put("color", FormatConstants.SVGCOLORSTRINGS[series[k].getPieChartSliceColor(k)]) + piepoint.put("stroke", darkColor) + pieSeries.put(piepoint) + } + len = seriesvals.length().toDouble() + } catch (e: Exception) { + // TODO: warning ...? + } + + // 20090717 KSC: input outside of try/catch to always set + minMax[0] = yMin + minMax[1] = yMax + minMax[2] = len + chartObjectJSON.put("Series", pieSeries) + chartObjectJSON.put("SeriesFills", "") // not applicable for pie charts; color is set above + return chartObjectJSON + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + val svg = StringBuffer() + // if have any series - should :) + if (series!!.size == 0) { + Logger.logErr("Pie.getSVG: error in series") + return "" + } + var dls = dataLabelInts // get array of data labels (can be specific per series ...) + val LABELOFFSET = 15 + + val n = series.size + var centerx = 0.0 + var centery = 0.0 + var radius = 0.0 + var radiusy = 0.0 + centerx = w / 2 + chartMetrics["x"] + centery = h / 2 + chartMetrics["y"] + svg.append("\r\n") + + radius = Math.min(w, h) / 2 + //radius= Math.min(w, h)/2; // too small + //radius= Math.min(w, h); // ????????????? just an estimate? + radiusy = radius + if (n > 0) { + val oneseries = series[0] as DoubleArray //FOR PIE CHARTS ONLY 1 SERIES VALUE POINTS ARE USED ********************************** + val curranges = s.seriesRanges!![0] as Array + var total = 0.0 + for (j in oneseries.indices) { // get total in order to calculate percentages + total += oneseries[j] + } + if (dls.size == 1) { // no series-specific data labels; expand to entire series for loop below + val dl = dls[0] + dls = IntArray(oneseries.size) + for (i in dls.indices) + dls[i] = dl + } + var path = "" + var x = centerx + radius + var y = centery + var percentage = 0.0 + var lasta = 0.0 + var largearcflag = 0 + val sweepflag = 0 + + // Now create each pie wedge according to it's percentage value + for (j in oneseries.indices) { + if (total > 0) + percentage = oneseries[j] / total + val angle = percentage * 360 + lasta + val x1 = centerx + radius * Math.cos(Math.toRadians(angle)) + val y1 = centery - radiusy * Math.sin(Math.toRadians(angle)) + if (percentage * 360/*angle*/ > 180) { + largearcflag = 1 + } else + largearcflag = 0 + path = "M" + centerx + " " + centery + " L" + x + " " + y + " A" + radius + " " + radiusy + " 0 " + largearcflag + " " + sweepflag + " " + x1 + " " + y1 + " L" + centerx + " " + centery + "Z" + svg.append("\r\n") + // " path='' d='" + path + "' fill-rule='evenodd' transform='matrix(1, 0, 0, 1, 0, 0)'/>\r\n"); + + val l = getSVGDataLabels(dls, axisMetrics, oneseries[j], percentage, j, legends, categories!![j].toString()) + if (l != null) { + // apparently labels are outside of wedge unless angle is >= 30 ... + // category labels + val halfa = percentage / 2 * 360 + lasta // center in area + val x2: Double + val y2: Double + if (percentage < .3) { // display label on outside with leader lines + x2 = centerx + (radius + LABELOFFSET) * Math.cos(Math.toRadians(halfa)) + y2 = centery - (radiusy + LABELOFFSET) * Math.sin(Math.toRadians(halfa)) + } else { // display label within wedge for > 30% + x2 = centerx + radius / 2 * Math.cos(Math.toRadians(halfa)) + y2 = centery - radiusy / 2 * Math.sin(Math.toRadians(halfa)) + } + var style = "" + if (percentage >= .3) + style = " style='text-anchor: middle;'" + else if (lasta > 90 && lasta < 270) { // right-align text for wedges on left side of pie + style = " style='text-anchor: end;'" + // TODO: dec x2 + } + svg.append("$l\r\n") + // leaderline - not exactly like Excel's but ... :) do when NOT putting text within wedge + if (percentage < .3) { + val x0 = centerx + radius * Math.cos(Math.toRadians(halfa)) + val y0 = centery - radiusy * Math.sin(Math.toRadians(halfa)) + svg.append("\r\n") + } + } + lasta = angle + x = x1 + y = y1 + } + } + svg.append("\r\n") + + return svg.toString() + } + /** + * Of the four candidate arc sweeps, two will represent an arc sweep of greater than or equal to 180 degrees (the "large-arc"), + * and two will represent an arc sweep of less than or equal to 180 degrees (the "small-arc"). + * If large-arc-flag is '1', then one of the two larger arc sweeps will be chosen; otherwise, if large-arc-flag is '0', one of the smaller arc sweeps will be chosen, + * If sweep-flag is '1', then the arc will be drawn in a "positive-angle" direction (i.e., the ellipse formula x=cx+rx*cos(theta) + * and y=cy+ry*sin(theta) is evaluated such that theta starts at an angle corresponding to the current point and increases positively until the arc reaches (x,y)). + * A value of 0 causes the arc to be drawn in a "negative-angle" direction + * (i.e., theta starts at an angle value corresponding to the current point and decreases until the arc reaches (x,y)). + */ + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + //TODO: FINISH + //cooxml.append(getDataLabelsOOXML(cf)); + + // TODO: firstSLiceAng + // if (pie.getAnStart()!=0) + cooxml.append("") + cooxml.append("") + cooxml.append("\r\n") + + return cooxml + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/PieFormat.java b/src/main/java/io/starter/formats/XLS/charts/PieFormat.java deleted file mode 100644 index fcd5690..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/PieFormat.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * PieFormat: Position of the Pie Slice(0x100b) - *

                      - * percentage 2 distance of pie slice from center of pie as % - */ -public class PieFormat extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 28305957039802849L; - short percentage = 0; - - public void init() { - super.init(); - this.getData(); - percentage = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - - } - - // 20070716 KSC: Need to create new records - public static XLSRecord getPrototype() { - PieFormat pf = new PieFormat(); - pf.setOpcode(PIEFORMAT); - pf.setData(pf.PROTOTYPE_BYTES); - pf.init(); - return pf; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; - - // 20070717 KSC: get/set methods for format options - public short getPercentage() { - return percentage; - } - - public void setPercentage(short p) { - percentage = p; - updateRecord(); - } - - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(percentage); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - public String getOptionsXML() { - return " Percentage=\"" + percentage + "\""; - } - - public String toString() { - return "PieFormat: Percentage=" + percentage; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/PieFormat.kt b/src/main/java/io/starter/formats/XLS/charts/PieFormat.kt new file mode 100644 index 0000000..fbbe16d --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/PieFormat.kt @@ -0,0 +1,85 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **PieFormat: Position of the Pie Slice(0x100b)** + * + * + * percentage 2 distance of pie slice from center of pie as % + */ +class PieFormat : GenericChartObject(), ChartObject { + internal var percentage: Short = 0 + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0) + + override val optionsXML: String + get() = " Percentage=\"$percentage\"" + + override fun init() { + super.init() + this.data + percentage = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + + } + + // 20070717 KSC: get/set methods for format options + fun getPercentage(): Short { + return percentage + } + + fun setPercentage(p: Short) { + percentage = p + updateRecord() + } + + private fun updateRecord() { + val b = ByteTools.shortToLEBytes(percentage) + this.data[0] = b[0] + this.data[1] = b[1] + } + + override fun toString(): String { + return "PieFormat: Percentage=$percentage" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 28305957039802849L + + // 20070716 KSC: Need to create new records + val prototype: XLSRecord? + get() { + val pf = PieFormat() + pf.opcode = XLSConstants.PIEFORMAT + pf.data = pf.PROTOTYPE_BYTES + pf.init() + return pf + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/PivotChartBits.java b/src/main/java/io/starter/formats/XLS/charts/PivotChartBits.kt similarity index 66% rename from src/main/java/io/starter/formats/XLS/charts/PivotChartBits.java rename to src/main/java/io/starter/formats/XLS/charts/PivotChartBits.kt index 2cdaef1..b51ef12 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PivotChartBits.java +++ b/src/main/java/io/starter/formats/XLS/charts/PivotChartBits.kt @@ -20,49 +20,54 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * PIVOTCHARTBITS: PivotChart Bits (859h) + * **PIVOTCHARTBITS: PivotChart Bits (859h)** * Introduced in Excel 9 (2000), this BIFF record is an FRT * record for Charts. This stores flags for a PivotChart. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0859h * 6 grbitFrt 2 FRT flags; must be zero * 8 grbit 2 Option flags for PivotCharts (see description below) * 10 (unused) 6 Reserved; must be zero - *

                      + * + * * The grbit field contains the following PivotChart option flags: * Bits Mask Flag Name Contents * 0 0001h fGXHide =1 if the field buttons are hidden =0 otherwise * 15-1 FFFEh (unused) Reserved; must be zero */ -public class PivotChartBits extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4587483948421928667L; - - public void init() { - super.init(); - } +class PivotChartBits : GenericChartObject(), ChartObject { // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[]{}; + private val PROTOTYPE_BYTES = byteArrayOf() - public static XLSRecord getPrototype() { - PivotChartBits pcb = new PivotChartBits(); - pcb.setOpcode(PIVOTCHARTBITS); - pcb.setData(pcb.PROTOTYPE_BYTES); - pcb.init(); - return pcb; + override fun init() { + super.init() } - private void updateRecord() { + private fun updateRecord() {} + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -4587483948421928667L + + val prototype: XLSRecord? + get() { + val pcb = PivotChartBits() + pcb.opcode = XLSConstants.PIVOTCHARTBITS + pcb.data = pcb.PROTOTYPE_BYTES + pcb.init() + return pcb + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/PivotChartLink.java b/src/main/java/io/starter/formats/XLS/charts/PivotChartLink.kt similarity index 63% rename from src/main/java/io/starter/formats/XLS/charts/PivotChartLink.java rename to src/main/java/io/starter/formats/XLS/charts/PivotChartLink.kt index 972312c..1cad8ad 100644 --- a/src/main/java/io/starter/formats/XLS/charts/PivotChartLink.java +++ b/src/main/java/io/starter/formats/XLS/charts/PivotChartLink.kt @@ -20,43 +20,47 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * PIVOTCHARTLINK: Pivot Chart Link (861h) + * **PIVOTCHARTLINK: Pivot Chart Link (861h)** * Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. * This record stores the link to a PivotTable for a PivotChart. Similar in function * to SXVIEWLINK but used only during copy & paste of a chart via BIFF. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0861h * 6 grbitFrt 2 FRT flags; must be zero * 8 ai var same as AI record */ -public class PivotChartLink extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6202325538826559210L; - - public void init() { - super.init(); +class PivotChartLink : GenericChartObject(), ChartObject { + + private val PROTOTYPE_BYTES = byteArrayOf() + + override fun init() { + super.init() } // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[]{}; + private fun updateRecord() {} - public static XLSRecord getPrototype() { - PivotChartLink pcl = new PivotChartLink(); - pcl.setOpcode(PIVOTCHARTLINK); - pcl.setData(pcl.PROTOTYPE_BYTES); - pcl.init(); - return pcl; - } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -6202325538826559210L - private void updateRecord() { + val prototype: XLSRecord? + get() { + val pcl = PivotChartLink() + pcl.opcode = XLSConstants.PIVOTCHARTLINK + pcl.data = pcl.PROTOTYPE_BYTES + pcl.init() + return pcl + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/PlotArea.java b/src/main/java/io/starter/formats/XLS/charts/PlotArea.java deleted file mode 100644 index b5cde5c..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/PlotArea.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -/** - * PlotArea: Frame Belongs to Plot Area(0x1035) - */ -public class PlotArea extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 834805225367462259L; - - public void init() { - super.init(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/PlotArea.kt b/src/main/java/io/starter/formats/XLS/charts/PlotArea.kt new file mode 100644 index 0000000..ca80a9b --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/PlotArea.kt @@ -0,0 +1,40 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +/** + * **PlotArea: Frame Belongs to Plot Area(0x1035)** + */ +class PlotArea : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 834805225367462259L + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.java b/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.java deleted file mode 100644 index 1f92b81..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -/** - * PlotGrowth: Font Scale Factors(0x1064) - */ -public class PlotGrowth extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 514340954251194805L; - - public void init() { - super.init(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.kt b/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.kt new file mode 100644 index 0000000..f7c052b --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/PlotGrowth.kt @@ -0,0 +1,40 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +/** + * **PlotGrowth: Font Scale Factors(0x1064)** + */ +class PlotGrowth : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 514340954251194805L + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Pos.java b/src/main/java/io/starter/formats/XLS/charts/Pos.kt similarity index 61% rename from src/main/java/io/starter/formats/XLS/charts/Pos.java rename to src/main/java/io/starter/formats/XLS/charts/Pos.kt index eb73b1d..3cb8824 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Pos.java +++ b/src/main/java/io/starter/formats/XLS/charts/Pos.kt @@ -20,26 +20,30 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools /** - * Pos: Position Information(0x104f) - *

                      - *

                      + * **Pos: Position Information(0x104f)** + * + * + * + * * for TextDisp, sets the label position as an offset from the default position * for PlotArea, used only for main axis + describes the plot-area bounding box; the tMainPlotArea in the SHTPROPS rec must be 1 or the POS rec is ignored * for Legend, describes legend pos + size - *

                      + * + * * 4 rndTopLt 2 for PlotArea, TextDisp= 2; legend= 5 (what=3??? Data Table!) * 6 rndTopRt 2 for PlotArea, TextDisp= 2; legend: 1= use x2 and y2 for legend size; 2= autosize legend (ignore x2 + y2; if so, the fAutoSize bit of FRAME rec should be 1) * 8 x1 4 for PlotArea, x coord of bounding box; for TextDisp, horiz. offset from default pos; for legend, x coord in 1/4000 * 12 y1 4 for PlotArea, y coord of bounding box; for TextDisp, vert. offset from default pos; for legend, y coord " " * 16 x2 4 for PlotArea, w of bounding box; for TextDisp, ignored; for legend= width * 20 y2 4 for PlotArea, h of bounding box; ""; for legend= height - *

                      + * + * * Above is not correct; * Correct Information: * mdTopLt (2 bytes): A PositionMode structure that specifies the positioning mode for the upper-left corner of a legend, @@ -48,7 +52,8 @@ * mdBotRt (2 bytes): A PositionMode structure that specifies the positioning mode for the lower-right corner of a legend, * an attached label, or the plot area. The valid combinations of mdTopLt and mdBotRt and the meaning of x1, y1, x2, y2 * are specified in the following table. - *

                      + * + * * x1 (2 bytes): A signed integer that specifies a position. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. * unused1 (2 bytes): Undefined and MUST be ignored. * y1 (2 bytes): A signed integer that specifies a position. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. @@ -57,7 +62,8 @@ * unused3 (2 bytes): Undefined and MUST be ignored. * y2 (2 bytes): A signed integer that specifies a height. The meaning is specified in the earlier table showing the valid combinations mdTopLt and mdBotRt by type. * unused4 (2 bytes): Undefined and MUST be ignored. - *

                      + * + * * Table: * Type mdTopLtPosition Mode mdBotRt Position Mode Meaning * plot area (axis group) MDPARENT MDPARENT The values of x1 and y1 specify the horizontal and vertical offsets of the primary axis group's @@ -70,11 +76,14 @@ * relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 MUST be ignored. * The size of the legend is determined by the application. * legend MDKTH MDPARENT The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table. - *

                      + * + * * attached label MDPARENT MDPARENT The meaning of x1 and y1 is specified in the Type of Attached Label table. x2 and y2 MUST be ignored. * The size of the attached label is determined by the application. - *

                      - *

                      + * + * + * + * * The PositionMode structure specifies positioning mode for position information saved in a Pos record. * Name Value Meaning * MDFX 0x0000 Relative position to the chart, in points. @@ -82,7 +91,8 @@ * MDPARENT 0x0002 Owner of Pos determines how to interpret the position data. * MDKTH 0x0003 Offset to default position, in 1/1000th of the plot area size. * MDCHART 0x0005 Relative position to the chart, in SPRC (A SPRC is a unit of measurement that is 1/4000th of the height or width of the chart). - *

                      + * + * * Type of Attached Label Meaning * Chart title The value of x1 and y1 specify the horizontal and vertical offset of the title, relative to its default position, in SPRC. * Axis title The value of x1 and y1 specify the offset of the title along the direction of a specific axis. The value of x1 specifies an offset along the category (3) axis, date axis, or horizontal value axis. The value of y1 specifies an offset along the value axis. Both offsets are relative to the title's default position, in 1/1000th of the axis length. @@ -93,41 +103,79 @@ * For a pie chart group, the value of x1 specifies the clockwise angle, in degrees, and the value of y1 specifies the radius offset of the label relative to its default position, in 1/1000th of the pie radius length. A label moved toward the pie center has a negative radius offset. * For a radar chart group, the values of x1 and y1 specify the horizontal and vertical offset of the label relative to its default position, in 1/1000th of the axis length. */ -public class Pos extends GenericChartObject implements ChartObject { +class Pos : GenericChartObject(), ChartObject { + internal var rndTopLt: Short = 0 + internal var rndTopRt: Short = 0 + internal var x1: Int = 0 + internal var y1: Int = 0 + internal var x2: Int = 0 + internal var y2: Int = 0 + + private val PROTOTYPE_BYTES = byteArrayOf(2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + /** - * serialVersionUID + * return the legend coordinates (x, y in 1/4000 of the chart height or width, w, h in points) or null, depending upon legend options + *

                      NOTE: if the w or h are 0, use default. + * + * @return int[] x, y, w, h + */ + /*The values x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, + relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 specify the + width and height of the legend, in points.*//*The values of x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, + relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 MUST be ignored. + The size of the legend is determined by the application.*//*The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table.*/ val legendCoords: IntArray? + get() { + if (rndTopLt.toInt() == 5 && rndTopRt.toInt() == 1) { + return intArrayOf(x1, y1, x2, y2) + } else if (rndTopLt.toInt() == 5 && rndTopRt.toInt() == 2) { + return intArrayOf(x1, y1, 0, 0) + } else if (rndTopLt.toInt() == 3 && rndTopRt.toInt() == 2) { + return null + } + return null + } + + /** + * if plot area, + * return the Plot Area (x, y, w, h in 1/4000 of the chart height or width) or null, depending upon options + *

                      NOTES: Pos record of the Axis Parent: + *

                      The Pos record specifies the position and size of the outer plot area. The outer plot area is the bounding rectangle that includes the axis labels, + *

                      the axis titles, and data table of the chart. + * if attached label, + * Chart title The value of x1 and y1 specify the horizontal and vertical offset of the title, relative to its default position, in SPRC. + * Axis title The value of x1 and y1 specify the offset of the title along the direction of a specific axis. + * The value of x1 specifies an offset along the category axis, date axis, or horizontal value axis. + * The value of y1 specifies an offset along the value axis. Both offsets are relative to the title's default position, in 1/1000th of the axis length. + * Data label If the chart is not a pie chart group or a radar chart group, x1 and y1 specify the offset of the label along the direction of the specific axis. + * The x1 value is an offset along the category axis, date axis, or horizontal value axis. The y1 value is an offset along the value axis, + * opposite to the direction of the value axis. Both offsets are relative to the label's default position, in 1/1000th of the axis length. + * For a pie chart group, the value of x1 specifies the clockwise angle, in degrees, and the value of y1 specifies the radius offset of the label + * relative to its default position, in 1/1000th of the pie radius length. A label moved toward the pie center has a negative radius offset. + * For a radar chart group, the values of x1 and y1 specify the horizontal and vertical offset of the label relative to its default position, + * in 1/1000th of the axis length. + * + * @return int[] x, y, w, h */ - private static final long serialVersionUID = 7920967716354683818L; - short rndTopLt, rndTopRt; - int x1, y1, x2, y2; - public static final int TYPE_TEXTDISP = 0; - public static final int TYPE_LEGEND = 1; - public static final int TYPE_PLOTAREA = 2; - public static final int TYPE_DATATABLE = 3; + /* The values of x1 and y1 specify the horizontal and vertical offsets of the primary axis group's + upper-left corner, relative to the upper-left corner of the chart area, in SPRC. The values of x2 + and y2 specify the width and height of the primary axis group, in SPRC.*/ val coords: FloatArray? + get() = if (rndTopLt.toInt() == 2 && rndTopRt.toInt() == 2) { + floatArrayOf(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat()) + } else null - public void init() { - super.init(); + override fun init() { + super.init() // 0= in points, relative to the position of the chart // 1= absolute, in points // 2= parent of this rec determines // 3= offset to default // 5= relative, in 1/4000 of the w or h of the chart - rndTopLt = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - rndTopRt = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - x1 = ByteTools.readInt(this.getBytesAt(4, 4)); - y1 = ByteTools.readInt(this.getBytesAt(8, 4)); - x2 = ByteTools.readInt(this.getBytesAt(12, 4)); - y2 = ByteTools.readInt(this.getBytesAt(16, 4)); - } - - // TODO: Get def and parse options accordingly! - public static XLSRecord getPrototype(int type) { - Pos p = new Pos(); - p.setOpcode(POS); - p.setData(p.PROTOTYPE_BYTES); - p.init(); - p.setType(type); - return p; + rndTopLt = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + rndTopRt = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + x1 = ByteTools.readInt(this.getBytesAt(4, 4)!!) + y1 = ByteTools.readInt(this.getBytesAt(8, 4)!!) + x2 = ByteTools.readInt(this.getBytesAt(12, 4)!!) + y2 = ByteTools.readInt(this.getBytesAt(16, 4)!!) } /** @@ -135,35 +183,35 @@ public static XLSRecord getPrototype(int type) { * * @param type */ - public void setType(int type) { - switch (type) { - case TYPE_PLOTAREA: - case TYPE_TEXTDISP: - this.getData()[0] = 2; - this.getData()[1] = 0; - break; - case TYPE_LEGEND: - this.getData()[0] = 5; - this.getData()[1] = 2; - break; - case TYPE_DATATABLE: - this.getData()[0] = 3; - this.getData()[1] = 0; + fun setType(type: Int) { + when (type) { + TYPE_PLOTAREA, TYPE_TEXTDISP -> { + this.data[0] = 2 + this.data[1] = 0 + } + TYPE_LEGEND -> { + this.data[0] = 5 + this.data[1] = 2 + } + TYPE_DATATABLE -> { + this.data[0] = 3 + this.data[1] = 0 + } } - this.getData()[2] = 2; - this.getData()[3] = 0; + this.data[2] = 2 + this.data[3] = 0 } - public void setX(int x) { - x1 = x; - byte[] b = ByteTools.cLongToLEBytes(x); - System.arraycopy(b, 0, this.getData(), 4, 4); + fun setX(x: Int) { + x1 = x + val b = ByteTools.cLongToLEBytes(x) + System.arraycopy(b, 0, this.data!!, 4, 4) } - public void setY(int y) { - y1 = y; - byte[] b = ByteTools.cLongToLEBytes(y); - System.arraycopy(b, 0, this.getData(), 8, 4); + fun setY(y: Int) { + y1 = y + val b = ByteTools.cLongToLEBytes(y) + System.arraycopy(b, 0, this.data!!, 8, 4) } /** @@ -171,133 +219,97 @@ public void setY(int y) { * * @param w */ - public void setLegendW(int w) { - if (rndTopLt == 5 && rndTopRt == 1) { - x2 = w; - byte[] b = ByteTools.cLongToLEBytes(x2); - System.arraycopy(b, 0, this.getData(), 12, 4); + fun setLegendW(w: Int) { + if (rndTopLt.toInt() == 5 && rndTopRt.toInt() == 1) { + x2 = w + val b = ByteTools.cLongToLEBytes(x2) + System.arraycopy(b, 0, this.data!!, 12, 4) } // else throw exception? } - private byte[] PROTOTYPE_BYTES = new byte[]{2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - /** * set autosize bit for legend Pos's * valid only for legend-type Pos's (rndTopLt==5) * also must set any associated Frame Autosize bits * [BugTracker 2844] */ - public void setAutosizeLegend() { - if (this.rndTopLt == 5) {// it's a legend Pos - this.rndTopRt = 2;// set to autoposition - this.getData()[2] = 2; + fun setAutosizeLegend() { + if (this.rndTopLt.toInt() == 5) {// it's a legend Pos + this.rndTopRt = 2// set to autoposition + this.data[2] = 2 } } - /** - * return the legend coordinates (x, y in 1/4000 of the chart height or width, w, h in points) or null, depending upon legend options - *
                      NOTE: if the w or h are 0, use default. - * - * @return int[] x, y, w, h - */ - public int[] getLegendCoords() { - if (rndTopLt == 5 && rndTopRt == 1) { - /*The values x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, - relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 specify the - width and height of the legend, in points.*/ - return new int[]{x1, y1, x2, y2}; - } else if (rndTopLt == 5 && rndTopRt == 2) { - /*The values of x1 and y1 specify the horizontal and vertical offsets of the legend's upper-left corner, - relative to the upper-left corner of the chart area, in SPRC. The values of x2 and y2 MUST be ignored. - The size of the legend is determined by the application.*/ - return new int[]{x1, y1, 0, 0}; - } else if (rndTopLt == 3 && rndTopRt == 2) { - /*The values of x1, y1, x2 and y2 MUST be ignored. The legend is located inside a data table.*/ - return null; - } - return null; - } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 7920967716354683818L + val TYPE_TEXTDISP = 0 + val TYPE_LEGEND = 1 + val TYPE_PLOTAREA = 2 + val TYPE_DATATABLE = 3 - /** - * if plot area, - * return the Plot Area (x, y, w, h in 1/4000 of the chart height or width) or null, depending upon options - *
                      NOTES: Pos record of the Axis Parent: - *
                      The Pos record specifies the position and size of the outer plot area. The outer plot area is the bounding rectangle that includes the axis labels, - *
                      the axis titles, and data table of the chart. - * if attached label, - * Chart title The value of x1 and y1 specify the horizontal and vertical offset of the title, relative to its default position, in SPRC. - * Axis title The value of x1 and y1 specify the offset of the title along the direction of a specific axis. - * The value of x1 specifies an offset along the category axis, date axis, or horizontal value axis. - * The value of y1 specifies an offset along the value axis. Both offsets are relative to the title's default position, in 1/1000th of the axis length. - * Data label If the chart is not a pie chart group or a radar chart group, x1 and y1 specify the offset of the label along the direction of the specific axis. - * The x1 value is an offset along the category axis, date axis, or horizontal value axis. The y1 value is an offset along the value axis, - * opposite to the direction of the value axis. Both offsets are relative to the label's default position, in 1/1000th of the axis length. - * For a pie chart group, the value of x1 specifies the clockwise angle, in degrees, and the value of y1 specifies the radius offset of the label - * relative to its default position, in 1/1000th of the pie radius length. A label moved toward the pie center has a negative radius offset. - * For a radar chart group, the values of x1 and y1 specify the horizontal and vertical offset of the label relative to its default position, - * in 1/1000th of the axis length. - * - * @return int[] x, y, w, h - */ - public float[] getCoords() { - if (rndTopLt == 2 && rndTopRt == 2) { - /* The values of x1 and y1 specify the horizontal and vertical offsets of the primary axis group's - upper-left corner, relative to the upper-left corner of the chart area, in SPRC. The values of x2 - and y2 specify the width and height of the primary axis group, in SPRC.*/ - return new float[]{x1, y1, x2, y2}; + // TODO: Get def and parse options accordingly! + fun getPrototype(type: Int): XLSRecord { + val p = Pos() + p.opcode = XLSConstants.POS + p.data = p.PROTOTYPE_BYTES + p.init() + p.setType(type) + return p } - return null; - } - /** - * convert a coordinate value in SPRC units to points - * - * @param val - * @param w - * @param h - * @return - */ - public static float convertFromSPRC(float val, float w, float h) { - // try this: - if (w != 0) { - return (float) (val / 4000.0) * w; - } else { - return (float) (val / 4000.0) * h; + /** + * convert a coordinate value in SPRC units to points + * + * @param val + * @param w + * @param h + * @return + */ + fun convertFromSPRC(`val`: Float, w: Float, h: Float): Float { + // try this: + return if (w != 0f) { + (`val` / 4000.0).toFloat() * w + } else { + (`val` / 4000.0).toFloat() * h + } } - } - /** - * convert a coordinate value in points to SPRC units - *
                      Experimental at this point - * - * @param val - * @param w - * @param h - * @return - */ - public static float convertToSPRC(float val, float w, float h) { - // try this: - if (w != 0) { - return (float) (val * 4000.0) / w; - } else { - return (float) (val * 4000.0) / h; + /** + * convert a coordinate value in points to SPRC units + *

                      Experimental at this point + * + * @param val + * @param w + * @param h + * @return + */ + fun convertToSPRC(`val`: Float, w: Float, h: Float): Float { + // try this: + return if (w != 0f) { + (`val` * 4000.0).toFloat() / w + } else { + (`val` * 4000.0).toFloat() / h + } } - } - /** - * convert a coordinate value in SPRC units to points - * - * @param val - * @param w - * @param h - * @return - */ - public static float convertFromLabelUnits(float val, float w, float h) { - // try this: - if (w != 0) { - return (float) (val / 1000.0) * w; - } else { - return (float) (val / 1000.0) * h; + /** + * convert a coordinate value in SPRC units to points + * + * @param val + * @param w + * @param h + * @return + */ + fun convertFromLabelUnits(`val`: Float, w: Float, h: Float): Float { + // try this: + return if (w != 0f) { + (`val` / 1000.0).toFloat() * w + } else { + (`val` / 1000.0).toFloat() * h + } } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.java b/src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.java deleted file mode 100644 index 6adbd2f..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class PyramidBarChart extends Bar3DChart { - - public PyramidBarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape = ChartConstants.SHAPEPYRAMID; - chartobj.chartType = PYRAMIDBARCHART; - } - - public int getBarShape() { - return defaultShape; - } -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSpContainer.java b/src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.kt similarity index 68% rename from src/main/java/io/starter/formats/escher/MsofbtSpContainer.java rename to src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.kt index 1bd5cfc..cb21634 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtSpContainer.java +++ b/src/main/java/io/starter/formats/XLS/charts/PyramidBarChart.kt @@ -20,21 +20,17 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.escher; +package io.starter.formats.XLS.charts -//0xf004 -public class MsofbtSpContainer extends EscherRecord { +import io.starter.formats.XLS.WorkBook - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3635215155760095395L; +class PyramidBarChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : Bar3DChart(charttype, cf, wb) { - public MsofbtSpContainer(int fbt, int inst, int version) { - super(fbt, inst, version); - } + override val barShape: Int + get() = defaultShape - protected byte[] getData() { - return new byte[0]; + init { + defaultShape = ChartConstants.SHAPEPYRAMID + chartobj.chartType = ChartConstants.PYRAMIDBARCHART } } diff --git a/src/main/java/io/starter/formats/XLS/charts/PyramidChart.java b/src/main/java/io/starter/formats/XLS/charts/PyramidChart.java deleted file mode 100644 index 8d12409..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/PyramidChart.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; - -public class PyramidChart extends Col3DChart { - - public PyramidChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape = ChartConstants.SHAPEPYRAMID; - chartobj.chartType = PYRAMIDCHART; - } - - public int getBarShape() { - return defaultShape; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/PyramidChart.kt b/src/main/java/io/starter/formats/XLS/charts/PyramidChart.kt new file mode 100644 index 0000000..c0719f1 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/PyramidChart.kt @@ -0,0 +1,36 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook + +class PyramidChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : Col3DChart(charttype, cf, wb) { + + override val barShape: Int + get() = defaultShape + + init { + defaultShape = ChartConstants.SHAPEPYRAMID + chartobj.chartType = ChartConstants.PYRAMIDCHART + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Radar.java b/src/main/java/io/starter/formats/XLS/charts/Radar.java deleted file mode 100644 index 8d692a6..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Radar.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * Radar: Chart Group Is a Radar Chart Group(0x103e) - *

                      - * 4 grbit 2 - *

                      - * 0 0x1 fRdrAxLab 1= chart contains radar axis labels - * 1 0x2 fHasShadow 1= this radar series has a shadow - */ -public class Radar extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3443368503725347910L; - private short grbit = 0; - private boolean fRdrAxLab = true; - private boolean fHasShadow = false; - - public void init() { - super.init(); - chartType = ChartConstants.RADARCHART; - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fRdrAxLab = (grbit & 0x1) == 0x1; - fHasShadow = (grbit & 0x2) == 0x2; - } - - // 20070703 KSC: taken from Bar.java - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - public static XLSRecord getPrototype() { - Radar b = new Radar(); - b.setOpcode(RADAR); - b.setData(b.PROTOTYPE_BYTES); - b.init(); - return b; - } - - - private byte[] PROTOTYPE_BYTES = new byte[]{1, 0, 18, 0}; - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (fRdrAxLab) - sb.append(" AxisLabels=\"true\""); - if (fHasShadow) - sb.append(" Shadow=\"true\""); - return sb.toString(); - } - - /** - * Handle setting options from XML in a generic manner - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("AxisLabels")) { - fRdrAxLab = val.equals("true"); - grbit = ByteTools.updateGrBit(grbit, fRdrAxLab, 0); - bHandled = true; - } else if (op.equalsIgnoreCase("Shadow")) { - fHasShadow = val.equals("true"); - grbit = ByteTools.updateGrBit(grbit, fHasShadow, 1); - bHandled = true; - } - if (bHandled) - updateRecord(); - return bHandled; - } - - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Radar.kt b/src/main/java/io/starter/formats/XLS/charts/Radar.kt new file mode 100644 index 0000000..bac61c8 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Radar.kt @@ -0,0 +1,110 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **Radar: Chart Group Is a Radar Chart Group(0x103e)** + * + * + * 4 grbit 2 + * + * + * 0 0x1 fRdrAxLab 1= chart contains radar axis labels + * 1 0x2 fHasShadow 1= this radar series has a shadow + */ +class Radar : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var fRdrAxLab = true + private var fHasShadow = false + + + private val PROTOTYPE_BYTES = byteArrayOf(1, 0, 18, 0) + + /** + * @return String XML representation of this chart-type's options + */ + override val optionsXML: String + get() { + val sb = StringBuffer() + if (fRdrAxLab) + sb.append(" AxisLabels=\"true\"") + if (fHasShadow) + sb.append(" Shadow=\"true\"") + return sb.toString() + } + + override fun init() { + super.init() + chartType = ChartConstants.RADARCHART + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + fRdrAxLab = grbit and 0x1 == 0x1 + fHasShadow = grbit and 0x2 == 0x2 + } + + // 20070703 KSC: taken from Bar.java + private fun updateRecord() { + val b = ByteTools.shortToLEBytes(grbit) + this.data[0] = b[0] + this.data[1] = b[1] + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("AxisLabels", ignoreCase = true)) { + fRdrAxLab = `val` == "true" + grbit = ByteTools.updateGrBit(grbit, fRdrAxLab, 0) + bHandled = true + } else if (op.equals("Shadow", ignoreCase = true)) { + fHasShadow = `val` == "true" + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 1) + bHandled = true + } + if (bHandled) + updateRecord() + return bHandled + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 3443368503725347910L + + val prototype: XLSRecord? + get() { + val b = Radar() + b.opcode = XLSConstants.RADAR + b.data = b.PROTOTYPE_BYTES + b.init() + return b + } + } + + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/RadarArea.java b/src/main/java/io/starter/formats/XLS/charts/RadarArea.java deleted file mode 100644 index e6d5250..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/RadarArea.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * RadarArea: Chart Group Is a Radar Area Chart Group(0x1040) - * (i.e. a filled radar chart) - *

                      - * A - fRdrAxLab (1 bit): A bit that specifies whether category (3) labels are displayed. - *

                      - * B - fHasShadow (1 bit): A bit that specifies whether the data points in the chart group have shadows. - *

                      - * reserved (14 bits): MUST be zero, and MUST be ignored. - *

                      - * unused (2 bytes): Undefined and MUST be ignored. - */ -public class RadarArea extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5731720802332312350L; - private short grbit = 0; - private boolean fRdrAxLab = false; - - public void init() { - super.init(); - chartType = ChartConstants.RADARAREACHART; - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fRdrAxLab = (grbit & 0x1) == 0x1; - } - - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{1, 0}; - - public static XLSRecord getPrototype() { - RadarArea r = new RadarArea(); - r.setOpcode(RADARAREA); - r.setData(r.PROTOTYPE_BYTES); - r.init(); - return r; - } - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (fRdrAxLab) - sb.append(" AxisLabels=\"true\""); - return sb.toString(); - } - - /** - * Handle setting options from XML in a generic manner - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("AxisLabels")) { - fRdrAxLab = val.equals("true"); - grbit = ByteTools.updateGrBit(grbit, fRdrAxLab, 0); - bHandled = true; - } - if (bHandled) - updateRecord(); - return bHandled; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/RadarArea.kt b/src/main/java/io/starter/formats/XLS/charts/RadarArea.kt new file mode 100644 index 0000000..3e09873 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/RadarArea.kt @@ -0,0 +1,105 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **RadarArea: Chart Group Is a Radar Area Chart Group(0x1040)** + * (i.e. a filled radar chart) + * + * + * A - fRdrAxLab (1 bit): A bit that specifies whether category (3) labels are displayed. + * + * + * B - fHasShadow (1 bit): A bit that specifies whether the data points in the chart group have shadows. + * + * + * reserved (14 bits): MUST be zero, and MUST be ignored. + * + * + * unused (2 bytes): Undefined and MUST be ignored. + */ +class RadarArea : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var fRdrAxLab = false + + private val PROTOTYPE_BYTES = byteArrayOf(1, 0) + + /** + * @return String XML representation of this chart-type's options + */ + override val optionsXML: String + get() { + val sb = StringBuffer() + if (fRdrAxLab) + sb.append(" AxisLabels=\"true\"") + return sb.toString() + } + + override fun init() { + super.init() + chartType = ChartConstants.RADARAREACHART + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + fRdrAxLab = grbit and 0x1 == 0x1 + } + + private fun updateRecord() { + val b = ByteTools.shortToLEBytes(grbit) + this.data[0] = b[0] + this.data[1] = b[1] + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("AxisLabels", ignoreCase = true)) { + fRdrAxLab = `val` == "true" + grbit = ByteTools.updateGrBit(grbit, fRdrAxLab, 0) + bHandled = true + } + if (bHandled) + updateRecord() + return bHandled + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5731720802332312350L + + val prototype: XLSRecord? + get() { + val r = RadarArea() + r.opcode = XLSConstants.RADARAREA + r.data = r.PROTOTYPE_BYTES + r.init() + return r + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.java b/src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.kt similarity index 54% rename from src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.java rename to src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.kt index 19f486f..8a25e2a 100644 --- a/src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/RadarAreaChart.kt @@ -20,50 +20,49 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.WorkBook; +import io.starter.formats.XLS.WorkBook -public class RadarAreaChart extends RadarChart { - protected RadarArea radararea = null; +class RadarAreaChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : RadarChart(charttype, cf, wb) { + protected var radararea: RadarArea? = null - public RadarAreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - radararea = (RadarArea) charttype; + init { + radararea = charttype as RadarArea } /** * gets the chart-type specific ooxml representation: * * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") - String style = "filled"; + val style = "filled" - cooxml.append(""); + cooxml.append("") // vary colors??? // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) // chart data labels, if any // TODO: FINISH // cooxml.append(getDataLabelsOOXML(cf)); // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") - cooxml.append(""); - cooxml.append("\r\n"); - return cooxml; + cooxml.append("") + cooxml.append("\r\n") + return cooxml } } diff --git a/src/main/java/io/starter/formats/XLS/charts/RadarChart.java b/src/main/java/io/starter/formats/XLS/charts/RadarChart.java deleted file mode 100644 index 465c9d3..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/RadarChart.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.HashMap; - -public class RadarChart extends ChartType { - //private Radar radar = null; can be Radar or RadarArea - - public RadarChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); -// radar = (Radar) charttype; - } - - public void setFilled(boolean isFilled) { - if (isFilled) - chartobj = (RadarArea) RadarArea.getPrototype(); - else - chartobj = (Radar) Radar.getPrototype(); - chartobj.setParentChart(cf.parentChart); - cf.chartArr.remove(0); - cf.chartArr.add(chartobj); - } - - public boolean getIsFilled() { - return (chartobj.chartType == ChartConstants.RADARAREACHART); - } - - - /** - * returns SVG to represent the actual chart object i.e. the representation - * of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, - * max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, - HashMap axisMetrics, ChartSeries s) { - double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is - // constant at x origin unless - // reversed - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then - // increases from there - // y value for each point= h/YMAX - if (series.size() == 0) { - Logger.logErr("Radar.getSVG: error in series"); - return ""; - } - StringBuffer svg = new StringBuffer(); - - // obtain data label, marker info for chart + series colors - int[] dls = getDataLabelInts(); // get array of data labels (can be - // specific per series ...) - int[] markers = getMarkerFormats(); // get an array of marker formats - // per series - int n = series.size(); - int nseries = categories.length; - - double centerx = w / 2 + x; - double centery = h / 2 + y; - double percentage = 1.0 / nseries; // divide into equal sections - double radius = Math.min(w, h) / 2.3; // should take up almost entire - // w/h of chart - - svg.append("\r\n"); - // define marker shapes for later use - svg.append(MarkerFormat.getMarkerSVGDefs()); - // for each series - for (int i = 0; i < n; i++) { - String points = ""; - String labels = ""; - double angle = 90; // starts straight up - double[] curseries = (double[]) series.get(i); - String[] curranges = (String[]) s.getSeriesRanges().get(i); - double x0 = 0, y0 = 0; - for (int j = 0; j < curseries.length; j++) { - // get next point as a percentage of the radius - double r = radius * (curseries[j] / (max - min)); - double x1 = centerx + r * (Math.cos(Math.toRadians(angle))); // - double y1 = centery - r * (Math.sin(Math.toRadians(angle))); - if (j == 0) { // save initial points so can close the loop at - // end - x0 = x1; - y0 = y1; - } - points += x1 + "," + y1 + " "; - String l = getSVGDataLabels(dls, axisMetrics, curseries[j], - percentage, j, legends, categories[j].toString()); - if (l != null) { - double labelx1 = centerx + (r + 5) - * (Math.cos(Math.toRadians(angle))); // - double labely1 = centery - (r + 5) - * (Math.sin(Math.toRadians(angle))); - labels += "" + l + "\r\n"; - } - angle -= (percentage * 360); // next point on next category - // radial line - } - // close loop - points += x0 + "," + y0; - // 1st line is black - svg.append("\r\n"); - // 2nd line is the series color - svg.append("\r\n"); - // Markers, if any, along data points in series - if (markers[i] > 0) { - String[] markerpoints = points.split(" "); - for (int j = 0; j < markerpoints.length; j++) { - String markerpoint = markerpoints[j]; - String[] xy = markerpoint.split(","); - double xx = Double.valueOf(xy[0]); - double yy = Double.valueOf(xy[1]); - svg.append(MarkerFormat.getMarkerSVG(xx, yy, - seriescolors[i], markers[i]) + "\r\n"); - } - } - // labels after lines and markers - svg.append(labels); - } - - svg.append("\r\n"); - return svg.toString(); - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - - String style = "standard"; - if (this.getIsFilled()) - style = "filled"; - else { - int[] markers = getMarkerFormats(); - for (int m : markers) { - if (m != 0) { - style = "marker"; - break; - } - } - } - cooxml.append(""); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - // TODO: FINISH - // cooxml.append(getDataLabelsOOXML(cf)); - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - - cooxml.append(""); - cooxml.append("\r\n"); - return cooxml; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/RadarChart.kt b/src/main/java/io/starter/formats/XLS/charts/RadarChart.kt new file mode 100644 index 0000000..96e86f9 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/RadarChart.kt @@ -0,0 +1,209 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.HashMap + +open class RadarChart +//private Radar radar = null; can be Radar or RadarArea + +(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook)// radar = (Radar) charttype; + : ChartType(charttype, cf, wb) { + + var isFilled: Boolean + get() = chartobj.chartType == ChartConstants.RADARAREACHART + set(isFilled) { + if (isFilled) + chartobj = RadarArea.prototype as RadarArea? + else + chartobj = Radar.prototype as Radar? + chartobj.parentChart = cf!!.parentChart + cf!!.chartArr.removeAt(0) + cf!!.chartArr.add(chartobj) + } + + + /** + * returns SVG to represent the actual chart object i.e. the representation + * of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, + * max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, + axisMetrics: HashMap, s: ChartSeries): String { + val x = chartMetrics["x"] // + (!yAxisReversed?0:w); // x is + // constant at x origin unless + // reversed + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then + // increases from there + // y value for each point= h/YMAX + if (series!!.size == 0) { + Logger.logErr("Radar.getSVG: error in series") + return "" + } + val svg = StringBuffer() + + // obtain data label, marker info for chart + series colors + val dls = dataLabelInts // get array of data labels (can be + // specific per series ...) + val markers = markerFormats // get an array of marker formats + // per series + val n = series.size + val nseries = categories!!.size + + val centerx = w / 2 + x + val centery = h / 2 + y + val percentage = 1.0 / nseries // divide into equal sections + val radius = Math.min(w, h) / 2.3 // should take up almost entire + // w/h of chart + + svg.append("\r\n") + // define marker shapes for later use + svg.append(MarkerFormat.markerSVGDefs) + // for each series + for (i in 0 until n) { + var points = "" + var labels = "" + var angle = 90.0 // starts straight up + val curseries = series[i] as DoubleArray + val curranges = s.seriesRanges!![i] as Array + var x0 = 0.0 + var y0 = 0.0 + for (j in curseries.indices) { + // get next point as a percentage of the radius + val r = radius * (curseries[j] / (max - min)) + val x1 = centerx + r * Math.cos(Math.toRadians(angle)) // + val y1 = centery - r * Math.sin(Math.toRadians(angle)) + if (j == 0) { // save initial points so can close the loop at + // end + x0 = x1 + y0 = y1 + } + points += "$x1,$y1 " + val l = getSVGDataLabels(dls, axisMetrics, curseries[j], + percentage, j, legends, categories[j].toString()) + if (l != null) { + val labelx1 = centerx + (r + 5) * Math.cos(Math.toRadians(angle)) // + val labely1 = centery - (r + 5) * Math.sin(Math.toRadians(angle)) + labels += ("" + l + "\r\n") + } + angle -= percentage * 360 // next point on next category + // radial line + } + // close loop + points += "$x0,$y0" + // 1st line is black + svg.append("\r\n") + // 2nd line is the series color + svg.append("\r\n") + // Markers, if any, along data points in series + if (markers[i] > 0) { + val markerpoints = points.split(" ".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + for (j in markerpoints.indices) { + val markerpoint = markerpoints[j] + val xy = markerpoint.split(",".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + val xx = java.lang.Double.valueOf(xy[0]) + val yy = java.lang.Double.valueOf(xy[1]) + svg.append(MarkerFormat.getMarkerSVG(xx, yy, + seriescolors[i], markers[i]) + "\r\n") + } + } + // labels after lines and markers + svg.append(labels) + } + + svg.append("\r\n") + return svg.toString() + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + + var style = "standard" + if (this.isFilled) + style = "filled" + else { + val markers = markerFormats + for (m in markers) { + if (m != 0) { + style = "marker" + break + } + } + } + cooxml.append("") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + // TODO: FINISH + // cooxml.append(getDataLabelsOOXML(cf)); + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + + cooxml.append("") + cooxml.append("\r\n") + return cooxml + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/SbaseRef.java b/src/main/java/io/starter/formats/XLS/charts/SbaseRef.java deleted file mode 100644 index b4d8c86..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/SbaseRef.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -/** - * SbaseRef: PivotTable Reference(0x1048) - */ -public class SbaseRef extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7832632121207578401L; - - public void init() { - super.init(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/SbaseRef.kt b/src/main/java/io/starter/formats/XLS/charts/SbaseRef.kt new file mode 100644 index 0000000..0cb30c9 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/SbaseRef.kt @@ -0,0 +1,40 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +/** + * **SbaseRef: PivotTable Reference(0x1048)** + */ +class SbaseRef : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 7832632121207578401L + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Scatter.java b/src/main/java/io/starter/formats/XLS/charts/Scatter.java deleted file mode 100644 index bf73610..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Scatter.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * Scatter: Chart Group is a Scatter Chart Group(0x101b) - *

                      - * 4 pcBubbleSizeRatio 2 Percent of largest bubble compared to chart in general default= 100 - * 6 wBubbleSize 2 Bubble size: 1= bubble size is area, 2= bubble size is width default= 1 - * 8 grbit 2 flags - *

                      - * grbit - * 0 0x1 fBubbles 1= this is a bubble series - * 1 0x2 fShowNegBubbles 1= show negative bubbles - * 2 0x4 fHasShadow 1= bubble series has a shadow - */ -public class Scatter extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -289334135036242100L; - private short grbit = 0; - private short pcBubbleSizeRatio = 100; - private short wBubbleSize = 1; - private boolean fBubbles = false; - private boolean fShowNegBubbles = false; - private boolean fHasShadow = false; - - public void init() { - super.init(); - // 20070703 KSC: - pcBubbleSizeRatio = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - wBubbleSize = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - grbit = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - fBubbles = (grbit & 0x1) == 0x1; - fShowNegBubbles = (grbit & 0x2) == 0x2; - fHasShadow = (grbit & 0x4) == 0x4; - if (fBubbles) - chartType = ChartConstants.BUBBLECHART; - else - chartType = ChartConstants.SCATTERCHART; - } - - // 20070703 KSC - private void updateRecord() { - grbit = ByteTools.updateGrBit(grbit, fBubbles, 0); - grbit = ByteTools.updateGrBit(grbit, fShowNegBubbles, 1); - grbit = ByteTools.updateGrBit(grbit, fHasShadow, 2); - byte[] b = ByteTools.shortToLEBytes(pcBubbleSizeRatio); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - b = ByteTools.shortToLEBytes(wBubbleSize); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - b = ByteTools.shortToLEBytes(grbit); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - } - - public void setAsScatterChart() { - fBubbles = false; - chartType = ChartConstants.SCATTERCHART; - this.updateRecord(); - } - - public void setAsBubbleChart() { - fBubbles = true; - chartType = ChartConstants.BUBBLECHART; - this.updateRecord(); - } - - public static XLSRecord getPrototype() { - Scatter b = new Scatter(); - b.setOpcode(SCATTER); - b.setData(b.PROTOTYPE_BYTES); - b.init(); - b.setAsScatterChart(); - return b; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{100, 0, 1, 0, 0, 0}; - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (pcBubbleSizeRatio != 100) - sb.append(" BubbleSizeRatio=\"" + pcBubbleSizeRatio + "\""); - if (wBubbleSize != 1) - sb.append(" BubbleSize=\"" + wBubbleSize + "\""); - if (fShowNegBubbles) - sb.append(" ShowNeg=\"true\""); - if (fHasShadow) - sb.append(" Shadow=\"true\""); - return sb.toString(); - } - - /** - * Handle setting options from XML in a generic manner - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("BubbleSizeRatio")) { - pcBubbleSizeRatio = Short.parseShort(val); - bHandled = true; - } else if (op.equalsIgnoreCase("BubbleSize")) { - wBubbleSize = Short.parseShort(val); - bHandled = true; - } else if (op.equalsIgnoreCase("ShowNeg")) { - fShowNegBubbles = val.equals("true"); - bHandled = true; - } else if (op.equalsIgnoreCase("Shadow")) { - fHasShadow = val.equals("true"); - bHandled = true; - } - if (bHandled) - updateRecord(); - return bHandled; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Scatter.kt b/src/main/java/io/starter/formats/XLS/charts/Scatter.kt new file mode 100644 index 0000000..19f59d1 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Scatter.kt @@ -0,0 +1,152 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **Scatter: Chart Group is a Scatter Chart Group(0x101b)** + * + * + * 4 pcBubbleSizeRatio 2 Percent of largest bubble compared to chart in general default= 100 + * 6 wBubbleSize 2 Bubble size: 1= bubble size is area, 2= bubble size is width default= 1 + * 8 grbit 2 flags + * + * + * grbit + * 0 0x1 fBubbles 1= this is a bubble series + * 1 0x2 fShowNegBubbles 1= show negative bubbles + * 2 0x4 fHasShadow 1= bubble series has a shadow + */ +class Scatter : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var pcBubbleSizeRatio: Short = 100 + private var wBubbleSize: Short = 1 + private var fBubbles = false + private var fShowNegBubbles = false + private var fHasShadow = false + + private val PROTOTYPE_BYTES = byteArrayOf(100, 0, 1, 0, 0, 0) + + /** + * @return String XML representation of this chart-type's options + */ + override val optionsXML: String + get() { + val sb = StringBuffer() + if (pcBubbleSizeRatio.toInt() != 100) + sb.append(" BubbleSizeRatio=\"$pcBubbleSizeRatio\"") + if (wBubbleSize.toInt() != 1) + sb.append(" BubbleSize=\"$wBubbleSize\"") + if (fShowNegBubbles) + sb.append(" ShowNeg=\"true\"") + if (fHasShadow) + sb.append(" Shadow=\"true\"") + return sb.toString() + } + + override fun init() { + super.init() + // 20070703 KSC: + pcBubbleSizeRatio = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + wBubbleSize = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + grbit = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + fBubbles = grbit and 0x1 == 0x1 + fShowNegBubbles = grbit and 0x2 == 0x2 + fHasShadow = grbit and 0x4 == 0x4 + if (fBubbles) + chartType = ChartConstants.BUBBLECHART + else + chartType = ChartConstants.SCATTERCHART + } + + // 20070703 KSC + private fun updateRecord() { + grbit = ByteTools.updateGrBit(grbit, fBubbles, 0) + grbit = ByteTools.updateGrBit(grbit, fShowNegBubbles, 1) + grbit = ByteTools.updateGrBit(grbit, fHasShadow, 2) + var b = ByteTools.shortToLEBytes(pcBubbleSizeRatio) + this.data[0] = b[0] + this.data[1] = b[1] + b = ByteTools.shortToLEBytes(wBubbleSize) + this.data[2] = b[0] + this.data[3] = b[1] + b = ByteTools.shortToLEBytes(grbit) + this.data[4] = b[0] + this.data[5] = b[1] + } + + fun setAsScatterChart() { + fBubbles = false + chartType = ChartConstants.SCATTERCHART + this.updateRecord() + } + + fun setAsBubbleChart() { + fBubbles = true + chartType = ChartConstants.BUBBLECHART + this.updateRecord() + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("BubbleSizeRatio", ignoreCase = true)) { + pcBubbleSizeRatio = java.lang.Short.parseShort(`val`) + bHandled = true + } else if (op.equals("BubbleSize", ignoreCase = true)) { + wBubbleSize = java.lang.Short.parseShort(`val`) + bHandled = true + } else if (op.equals("ShowNeg", ignoreCase = true)) { + fShowNegBubbles = `val` == "true" + bHandled = true + } else if (op.equals("Shadow", ignoreCase = true)) { + fHasShadow = `val` == "true" + bHandled = true + } + if (bHandled) + updateRecord() + return bHandled + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -289334135036242100L + + val prototype: XLSRecord? + get() { + val b = Scatter() + b.opcode = XLSConstants.SCATTER + b.data = b.PROTOTYPE_BYTES + b.init() + b.setAsScatterChart() + return b + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ScatterChart.java b/src/main/java/io/starter/formats/XLS/charts/ScatterChart.java deleted file mode 100644 index 8cb5ff5..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ScatterChart.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.ChartSeriesHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; - -public class ScatterChart extends ChartType { - private Scatter scatter = null; - - public ScatterChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - scatter = (Scatter) charttype; - } - - public JSONObject getJSON(ChartSeriesHandle[] series, WorkBookHandle wbh, Double[] minMax) - throws JSONException { - JSONObject chartObjectJSON = new JSONObject(); - - // Type JSON - chartObjectJSON.put("type", this.getTypeJSON()); - - // Deal with Series - double yMax = 0.0, yMin = 0.0; - int nSeries = 0; - JSONArray seriesJSON = new JSONArray(); - JSONArray seriesCOLORS = new JSONArray(); - boolean bHasBubbles = false; - try { - // must trap min and max for axis tick and units - for (int i = 0; i < series.length; i++) { - JSONArray seriesvals = CellRange.getValuesAsJSON(series[i].getSeriesRange(), wbh); - nSeries = Math.max(nSeries, seriesvals.length()); - for (int j = 0; j < seriesvals.length(); j++) { - try { - yMax = Math.max(yMax, seriesvals.getDouble(j)); - yMin = Math.min(yMin, seriesvals.getDouble(j)); - } catch (NumberFormatException n) { - } - } - if (!series[i].hasBubbleSizes()) - seriesJSON.put(seriesvals); - else - bHasBubbles = true; - seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i].getSeriesColor()]); - } - if (bHasBubbles) { - // 20080423 KSC: Go thru a second time, after obtaining yMax and yMin, for bubble sizes ... - for (int i = 0; i < series.length; i++) { - JSONArray bubbles = new JSONArray(); - JSONArray seriesvals = CellRange.getValuesAsJSON(series[i].getSeriesRange(), wbh); - JSONArray catvals = CellRange.getValuesAsJSON(series[i].getCategoryRange(), wbh); - JSONArray bubblesizes = CellRange.getValuesAsJSON(series[i].getBubbleSizes(), wbh); - for (int j = 0; j < catvals.length(); j++) { - JSONObject jo = new JSONObject(); - try { - jo.put("x", catvals.getDouble(j)); - } catch (Exception e) { - jo.put("x", j + 1); - } - jo.put("y", seriesvals.getDouble(j)); - jo.put("size", Math.round(bubblesizes.getDouble(j) / ((yMax - yMin) / nSeries))); // TODO: bubble sizes ration is a guess!! - bubbles.put(jo); - } - seriesJSON.put(bubbles); - } - } - chartObjectJSON.put("Series", seriesJSON); - chartObjectJSON.put("SeriesFills", seriesCOLORS); - } catch (JSONException je) { - // TODO: Log error - } - minMax[0] = new Double(yMin); - minMax[1] = new Double(yMax); - minMax[2] = new Double(nSeries); - return chartObjectJSON; - } - - /** - * return the type JSON for this Chart Object - * - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON = new JSONObject(); -/* String dojoType; - if (this.chartType==ChartConstants.SCATTER) { - dojoType="Default"; // = MarkersOnly - typeJSON.put("markers", true); - } else { // Bubble - dojoType="Bubble"; // "MarkersOnly"; - // shadows: {dx: 2, dy: 2, dw: 2} - } - //controls Data Legends, % Distance of sections, Line Format, Area Format, Bar Shapes ... - DataFormat df= this.getParentChart().getChartFormat().getDataFormatRec(false); - if (df!=null) { - for (int z= 0; z < df.chartArr.size(); z++) { - BiffRec b = (BiffRec)df.chartArr.get(z); - if (b instanceof LineFormat) { - if (chartType==ChartConstants.SCATTER) - typeJSON.put("type", "MarkersOnly"); // if Has LineFormat means NO lines! - } else if (b instanceof Serfmt) { - Serfmt s= (Serfmt) b; - if (s.getSmoothLine()) { - // can be: Scatter with markers and smoothed lines - typeJSON.put("type", "Default"); // change to Line with Markers - typeJSON.put("markers", true); // default, apparently- doesn't require a MarkerFormat record - } - } - } - } - typeJSON.put("type", dojoType); -*/ - return typeJSON; - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there - // y value for each point= h/YMAX - StringBuffer svg = new StringBuffer(); - - if (series.size() == 0) { - Logger.logErr("Scatter.getSVG: error in series"); - return ""; - } - // gather data labels, markers, has lines, series colors for chart - boolean threeD = cf.isThreeD(ChartConstants.SCATTERCHART); - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - int n = series.size(); - boolean hasLines = cf.getHasLines(); // should be per-series? - int[] markers = getMarkerFormats(); // get an array of marker formats per series - if (!hasLines && (markers[0] == 0)) { - // if no lines AND no markers set, MUST use default markers (this is what excel does ...) - int[] defaultmarkers = {2, 3, 1, 4, 8, 9, 5, 6, 7}; - for (int i = 0; i < n; i++) { - markers[i] = defaultmarkers[i]; - } - } - /** - * A Scatter chart has two value axes, showing one set of numerical data along the x-axis - * and another along the y-axis. - * It combines these values into single data points and displays them in uneven intervals, - * or clusters. - */ - double[] seriesx = null; - double xfactor = 0, yfactor = 0; // - boolean TEXTUALXAXIS = true; - // get x axis max/min for an x axis which is a value axis - double xmin = Double.MAX_VALUE, xmax = Double.MIN_VALUE; - seriesx = new double[categories.length]; - for (int j = 0; j < categories.length; j++) { - try { - seriesx[j] = new Double(categories[j].toString()); - xmax = Math.max(xmax, seriesx[j]); - xmin = Math.min(xmin, seriesx[j]); - TEXTUALXAXIS = false; // if ANY category val is a double, assume it's a normal xyvalue axis - } catch (Exception e) { - /* keep going */ - } - } - if (!TEXTUALXAXIS) { - double[] d = ValueRange.calcMaxMin(xmax, xmin, w); - xfactor = w / (d[2]); // w/maximum scale - } else - xfactor = w / (categories.length + 1); // w/#categories - - if (max != 0) - yfactor = h / max; // h/YMAXSCALE - svg.append("\r\n"); - // define marker shapes for later use - svg.append(MarkerFormat.getMarkerSVGDefs()); - // for each series - for (int i = 0; i < n; i++) { - // two lines- 1 black, 1 color - String points = ""; - String labels = ""; - double[] seriesy = (double[]) series.get(i); - for (int j = 0; j < seriesy.length; j++) { - double xval = 0; - if (TEXTUALXAXIS /*|| i > 0*/) - xval = j + 1; - else - xval = seriesx[j]; - points += ((x) + xval * xfactor) + "," + ((y + h) - (seriesy[j] * yfactor)); - points += " "; - String l = getSVGDataLabels(dls, axisMetrics, seriesy[j], 0, i, legends, categories[j].toString()); - if (l != null) - labels += "" + l + "\r\n"; - - } - if (hasLines) { - svg.append(getLineSVG(points, seriescolors[i])); - } - // Markers, if any, along data points in series - if (markers[i] > 0) { - String[] markerpoints = points.split(" "); - for (int j = 0; j < markerpoints.length; j++) { - String markerpoint = markerpoints[j]; - String[] xy = markerpoint.split(","); - double xx = Double.valueOf(xy[0]); - double yy = Double.valueOf(xy[1]); - svg.append(MarkerFormat.getMarkerSVG(xx, yy, seriescolors[i], markers[i]) + "\r\n"); - } - } - // labels after lines and markers - svg.append(labels); - } - svg.append("\r\n"); - return svg.toString(); - } - - /** - * returns the SVG necessary to define a line at points in color clr - * - * @param points String of SVG points - * @param clr SVG color String - * @return - */ - private String getLineSVG(String points, String clr) { - String s = ""; - // each line is comprised of 1 black line and 1 series color line: - // 1st line is black - s = "\r\n"; - // 2nd line is the series color - s += "\r\n"; - return s; - } - - /** - * gets the chart-type specific ooxml representation: - * - * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, - String serAxisId) { - StringBuffer cooxml = new StringBuffer(); - - // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); - int[] markers = getMarkerFormats(); - String style = null; - for (int m : markers) { - if (m != 0) { - if (this.getHasSmoothLines()) - style = "smoothMarker"; - else if (this.getHasLines()) - style = "lineMarker"; - else - style = "marker"; - break; - } - } - if (style == null && this.getHasLines()) style = "line"; - if (style == null && this.getHasSmoothLines()) style = "smooth"; - if (style == null) style = "none"; - - cooxml.append(""); - // vary colors??? - - // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); - - // chart data labels, if any - // TODO: FINISH - // cooxml.append(getDataLabelsOOXML(cf)); - - // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - - cooxml.append(""); - cooxml.append("\r\n"); - return cooxml; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ScatterChart.kt b/src/main/java/io/starter/formats/XLS/charts/ScatterChart.kt new file mode 100644 index 0000000..97eaf74 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ScatterChart.kt @@ -0,0 +1,336 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.ChartSeriesHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.FormatConstants +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +import java.util.ArrayList +import java.util.HashMap + +class ScatterChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + private var scatter: Scatter? = null + + /** + * return the type JSON for this Chart Object + * + * @return + */ + override/* String dojoType; + if (this.chartType==ChartConstants.SCATTER) { + dojoType="Default"; // = MarkersOnly + typeJSON.put("markers", true); + } else { // Bubble + dojoType="Bubble"; // "MarkersOnly"; + // shadows: {dx: 2, dy: 2, dw: 2} + } + //controls Data Legends, % Distance of sections, Line Format, Area Format, Bar Shapes ... + DataFormat df= this.getParentChart().getChartFormat().getDataFormatRec(false); + if (df!=null) { + for (int z= 0; z < df.chartArr.size(); z++) { + BiffRec b = (BiffRec)df.chartArr.get(z); + if (b instanceof LineFormat) { + if (chartType==ChartConstants.SCATTER) + typeJSON.put("type", "MarkersOnly"); // if Has LineFormat means NO lines! + } else if (b instanceof Serfmt) { + Serfmt s= (Serfmt) b; + if (s.getSmoothLine()) { + // can be: Scatter with markers and smoothed lines + typeJSON.put("type", "Default"); // change to Line with Markers + typeJSON.put("markers", true); // default, apparently- doesn't require a MarkerFormat record + } + } + } + } + typeJSON.put("type", dojoType); +*/ val typeJSON: JSONObject + @Throws(JSONException::class) + get() = JSONObject() + + init { + scatter = charttype as Scatter + } + + @Throws(JSONException::class) + fun getJSON(series: Array, wbh: WorkBookHandle, minMax: Array): JSONObject { + val chartObjectJSON = JSONObject() + + // Type JSON + chartObjectJSON.put("type", this.typeJSON) + + // Deal with Series + var yMax = 0.0 + var yMin = 0.0 + var nSeries = 0 + val seriesJSON = JSONArray() + val seriesCOLORS = JSONArray() + var bHasBubbles = false + try { + // must trap min and max for axis tick and units + for (i in series.indices) { + val seriesvals = CellRange.getValuesAsJSON(series[i].seriesRange, wbh) + nSeries = Math.max(nSeries, seriesvals.length()) + for (j in 0 until seriesvals.length()) { + try { + yMax = Math.max(yMax, seriesvals.getDouble(j)) + yMin = Math.min(yMin, seriesvals.getDouble(j)) + } catch (n: NumberFormatException) { + } + + } + if (!series[i].hasBubbleSizes()) + seriesJSON.put(seriesvals) + else + bHasBubbles = true + seriesCOLORS.put(FormatConstants.SVGCOLORSTRINGS[series[i].seriesColor]) + } + if (bHasBubbles) { + // 20080423 KSC: Go thru a second time, after obtaining yMax and yMin, for bubble sizes ... + for (i in series.indices) { + val bubbles = JSONArray() + val seriesvals = CellRange.getValuesAsJSON(series[i].seriesRange, wbh) + val catvals = CellRange.getValuesAsJSON(series[i].categoryRange, wbh) + val bubblesizes = CellRange.getValuesAsJSON(series[i].bubbleSizes, wbh) + for (j in 0 until catvals.length()) { + val jo = JSONObject() + try { + jo.put("x", catvals.getDouble(j)) + } catch (e: Exception) { + jo.put("x", j + 1) + } + + jo.put("y", seriesvals.getDouble(j)) + jo.put("size", Math.round(bubblesizes.getDouble(j) / ((yMax - yMin) / nSeries))) // TODO: bubble sizes ration is a guess!! + bubbles.put(jo) + } + seriesJSON.put(bubbles) + } + } + chartObjectJSON.put("Series", seriesJSON) + chartObjectJSON.put("SeriesFills", seriesCOLORS) + } catch (je: JSONException) { + // TODO: Log error + } + + minMax[0] = yMin + minMax[1] = yMax + minMax[2] = nSeries + return chartObjectJSON + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val x = chartMetrics["x"] // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there + // y value for each point= h/YMAX + val svg = StringBuffer() + + if (series!!.size == 0) { + Logger.logErr("Scatter.getSVG: error in series") + return "" + } + // gather data labels, markers, has lines, series colors for chart + val threeD = cf!!.isThreeD(ChartConstants.SCATTERCHART) + val dls = dataLabelInts // get array of data labels (can be specific per series ...) + val n = series.size + val hasLines = cf!!.hasLines // should be per-series? + val markers = markerFormats // get an array of marker formats per series + if (!hasLines && markers[0] == 0) { + // if no lines AND no markers set, MUST use default markers (this is what excel does ...) + val defaultmarkers = intArrayOf(2, 3, 1, 4, 8, 9, 5, 6, 7) + for (i in 0 until n) { + markers[i] = defaultmarkers[i] + } + } + /** + * A Scatter chart has two value axes, showing one set of numerical data along the x-axis + * and another along the y-axis. + * It combines these values into single data points and displays them in uneven intervals, + * or clusters. + */ + var seriesx: DoubleArray? = null + var xfactor = 0.0 + var yfactor = 0.0 // + var TEXTUALXAXIS = true + // get x axis max/min for an x axis which is a value axis + var xmin = java.lang.Double.MAX_VALUE + var xmax = java.lang.Double.MIN_VALUE + seriesx = DoubleArray(categories!!.size) + for (j in categories.indices) { + try { + seriesx[j] = Double(categories[j].toString()) + xmax = Math.max(xmax, seriesx[j]) + xmin = Math.min(xmin, seriesx[j]) + TEXTUALXAXIS = false // if ANY category val is a double, assume it's a normal xyvalue axis + } catch (e: Exception) { + /* keep going */ + } + + } + if (!TEXTUALXAXIS) { + val d = ValueRange.calcMaxMin(xmax, xmin, w) + xfactor = w / d[2] // w/maximum scale + } else + xfactor = w / (categories.size + 1) // w/#categories + + if (max != 0.0) + yfactor = h / max // h/YMAXSCALE + svg.append("\r\n") + // define marker shapes for later use + svg.append(MarkerFormat.markerSVGDefs) + // for each series + for (i in 0 until n) { + // two lines- 1 black, 1 color + var points = "" + var labels = "" + val seriesy = series[i] as DoubleArray + for (j in seriesy.indices) { + var xval = 0.0 + if (TEXTUALXAXIS /*|| i > 0*/) + xval = (j + 1).toDouble() + else + xval = seriesx[j] + points += (x + xval * xfactor).toString() + "," + (y + h - seriesy[j] * yfactor) + points += " " + val l = getSVGDataLabels(dls, axisMetrics, seriesy[j], 0.0, i, legends, categories[j].toString()) + if (l != null) + labels += "" + l + "\r\n" + + } + if (hasLines) { + svg.append(getLineSVG(points, seriescolors!![i])) + } + // Markers, if any, along data points in series + if (markers[i] > 0) { + val markerpoints = points.split(" ".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + for (j in markerpoints.indices) { + val markerpoint = markerpoints[j] + val xy = markerpoint.split(",".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + val xx = java.lang.Double.valueOf(xy[0]) + val yy = java.lang.Double.valueOf(xy[1]) + svg.append(MarkerFormat.getMarkerSVG(xx, yy, seriescolors!![i], markers[i]) + "\r\n") + } + } + // labels after lines and markers + svg.append(labels) + } + svg.append("\r\n") + return svg.toString() + } + + /** + * returns the SVG necessary to define a line at points in color clr + * + * @param points String of SVG points + * @param clr SVG color String + * @return + */ + private fun getLineSVG(points: String, clr: String): String { + var s = "" + // each line is comprised of 1 black line and 1 series color line: + // 1st line is black + s = "\r\n" + // 2nd line is the series color + s += "\r\n" + return s + } + + /** + * gets the chart-type specific ooxml representation: + * + * @return + */ + override fun getOOXML(catAxisId: String, valAxisId: String, + serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() + + // chart type: contains chart options and series data + cooxml.append("") + cooxml.append("\r\n") + val markers = markerFormats + var style: String? = null + for (m in markers) { + if (m != 0) { + if (this.hasSmoothLines) + style = "smoothMarker" + else if (this.hasLines) + style = "lineMarker" + else + style = "marker" + break + } + } + if (style == null && this.hasLines) style = "line" + if (style == null && this.hasSmoothLines) style = "smooth" + if (style == null) style = "none" + + cooxml.append("") + // vary colors??? + + // *** Series Data: ser, cat, val for most chart types + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) + + // chart data labels, if any + // TODO: FINISH + // cooxml.append(getDataLabelsOOXML(cf)); + + // axis ids - unsigned int strings + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + + cooxml.append("") + cooxml.append("\r\n") + return cooxml + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/SerParent.java b/src/main/java/io/starter/formats/XLS/charts/SerParent.kt similarity index 70% rename from src/main/java/io/starter/formats/XLS/charts/SerParent.java rename to src/main/java/io/starter/formats/XLS/charts/SerParent.kt index a665184..5bf6f42 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SerParent.java +++ b/src/main/java/io/starter/formats/XLS/charts/SerParent.kt @@ -20,18 +20,21 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts /** - * SerParent: Trendline or ErrorBar Series Index (0x104a) + * **SerParent: Trendline or ErrorBar Series Index (0x104a)** */ -public class SerParent extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4960939713989511226L; +class SerParent : GenericChartObject(), ChartObject { - public void init() { - super.init(); + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 4960939713989511226L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SerToCrt.java b/src/main/java/io/starter/formats/XLS/charts/SerToCrt.kt similarity index 61% rename from src/main/java/io/starter/formats/XLS/charts/SerToCrt.java rename to src/main/java/io/starter/formats/XLS/charts/SerToCrt.kt index d945c1c..977b8cd 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SerToCrt.java +++ b/src/main/java/io/starter/formats/XLS/charts/SerToCrt.kt @@ -21,28 +21,32 @@ * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * SerToCrt: Series Chart-Broup Index (0x1045) + * **SerToCrt: Series Chart-Broup Index (0x1045)** * 0 chartGroup 2 chart-group index: the number of the chart group (specified by a CHARTFORMAT record, starts at 0) */ -public class SerToCrt extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8217594656389677975L; +class SerToCrt : GenericChartObject(), ChartObject { - public void init() { - super.init(); + override fun init() { + super.init() } - protected static XLSRecord getPrototype() { - SerToCrt bl = new SerToCrt(); - bl.setOpcode(SERTOCRT); - bl.setData(new byte[]{0, 0}); - return bl; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8217594656389677975L + + val prototype: XLSRecord? + get() { + val bl = SerToCrt() + bl.opcode = XLSConstants.SERTOCRT + bl.data = byteArrayOf(0, 0) + return bl + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.java b/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.java deleted file mode 100644 index 29810d9..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -/** - * SerauxErrBar: Series ErrorBar(0x105b) - */ -public class SerauxErrBar extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7334046087318159714L; - - public void init() { - super.init(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.kt b/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.kt new file mode 100644 index 0000000..1d311f0 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/SerauxErrBar.kt @@ -0,0 +1,40 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +/** + * **SerauxErrBar: Series ErrorBar(0x105b)** + */ +class SerauxErrBar : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -7334046087318159714L + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.java b/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.java deleted file mode 100644 index 849c4e8..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -/** - * SerauxTrend: Series Trendline(0x104b) - */ -public class SerauxTrend extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2866392332146662156L; - - public void init() { - super.init(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.kt b/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.kt new file mode 100644 index 0000000..5e707ca --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/SerauxTrend.kt @@ -0,0 +1,40 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +/** + * **SerauxTrend: Series Trendline(0x104b)** + */ +class SerauxTrend : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2866392332146662156L + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Serfmt.java b/src/main/java/io/starter/formats/XLS/charts/Serfmt.java deleted file mode 100644 index 0c08bed..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Serfmt.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * Serfmt: Series Format(0x105d) - *

                      - * Specifies series formatting information - *

                      - * 0 grbit 2 - *

                      - * bits - * 0 0x1 fSmoothedLine 1= the line series has a smoothed line (Line, Scatter or Radar) - * 1 0x2 f3DBubbles 1= draw bubbles with 3-D effects - * 2 0x4 fArShadow 1= specifies whether the data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. - * rest are reserved - */ -public class Serfmt extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8307035373276421283L; - // 20070810 KSC: parse options ... - private short grbit = 0; - private boolean fSmoothedLine = false, f3dBubbles = false, fArShadow = false; - - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fSmoothedLine = (grbit & 0x1) == 0x1; - f3dBubbles = (grbit & 0x2) == 0x2; - fArShadow = (grbit & 0x4) == 0x4; - } - - private void updateRecord() { - grbit = ByteTools.updateGrBit(grbit, fSmoothedLine, 0); - grbit = ByteTools.updateGrBit(grbit, f3dBubbles, 1); - grbit = ByteTools.updateGrBit(grbit, fArShadow, 2); - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; - - /** - * Handle setting options from XML in a generic manner - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("SmoothedLine")) { - fSmoothedLine = val.equals("true"); - bHandled = true; - } else if (op.equalsIgnoreCase("ThreeDBubbles")) { - f3dBubbles = val.equals("true"); - bHandled = true; - } else if (op.equalsIgnoreCase("ArShadow")) { - fArShadow = val.equals("true"); - bHandled = true; - } - if (bHandled) - updateRecord(); - return bHandled; - } - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (fSmoothedLine) - sb.append(" SmoothedLine=\"true\""); - if (f3dBubbles) - sb.append(" ThreeDBubbles=\"true\""); - if (fArShadow) - sb.append(" ArShadow=\"true\""); - return sb.toString(); - } - - public static XLSRecord getPrototype() { - Serfmt s = new Serfmt(); - s.setOpcode(SERFMT); - s.setData(s.PROTOTYPE_BYTES); - s.init(); - return s; - } - - - public void setHas3dBubbles(boolean has3dBubbles) { - f3dBubbles = has3dBubbles; - updateRecord(); - } - - public boolean get3DBubbles() { - return f3dBubbles; - } - - /** - * sets whether the parent chart or series has smoothed lines - * - * @param b - */ - public void setSmoothedLine(boolean b) { - fSmoothedLine = b; - updateRecord(); - } - - public boolean getSmoothLine() { - return fSmoothedLine; - } - - /** - * data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. - */ - public boolean getShadow() { - return fArShadow; - } - - /** - * data markers are displayed with a shadow on bubble, - * scatter, radar, stock, and line chart groups. - * - * @param b - */ - public void setHasShadow(boolean b) { - fArShadow = b; - updateRecord(); - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Serfmt.kt b/src/main/java/io/starter/formats/XLS/charts/Serfmt.kt new file mode 100644 index 0000000..05426ea --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Serfmt.kt @@ -0,0 +1,156 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **Serfmt: Series Format(0x105d)** + * + * + * Specifies series formatting information + * + * + * 0 grbit 2 + * + * + * bits + * 0 0x1 fSmoothedLine 1= the line series has a smoothed line (Line, Scatter or Radar) + * 1 0x2 f3DBubbles 1= draw bubbles with 3-D effects + * 2 0x4 fArShadow 1= specifies whether the data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. + * rest are reserved + */ +class Serfmt : GenericChartObject(), ChartObject { + // 20070810 KSC: parse options ... + private var grbit: Short = 0 + var smoothLine = false + private set + var 3DBubbles = false + private set + /** + * data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. + */ + var shadow = false + private set + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0) + + /** + * @return String XML representation of this chart-type's options + */ + override val optionsXML: String + get() { + val sb = StringBuffer() + if (smoothLine) + sb.append(" SmoothedLine=\"true\"") + if (3 DBubbles) + sb.append(" ThreeDBubbles=\"true\"") + if (shadow) + sb.append(" ArShadow=\"true\"") + return sb.toString() + } + + override fun init() { + super.init() + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + smoothLine = grbit and 0x1 == 0x1 + 3 DBubbles =(grbit and 0x2) == 0x2 + shadow = grbit and 0x4 == 0x4 + } + + private fun updateRecord() { + grbit = ByteTools.updateGrBit(grbit, smoothLine, 0) + grbit = ByteTools.updateGrBit(grbit, 3 DBubbles, 1) + grbit = ByteTools.updateGrBit(grbit, shadow, 2) + val b = ByteTools.shortToLEBytes(grbit) + this.data[0] = b[0] + this.data[1] = b[1] + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("SmoothedLine", ignoreCase = true)) { + smoothLine = `val` == "true" + bHandled = true + } else if (op.equals("ThreeDBubbles", ignoreCase = true)) { + 3 DBubbles = `val` == "true" + bHandled = true + } else if (op.equals("ArShadow", ignoreCase = true)) { + shadow = `val` == "true" + bHandled = true + } + if (bHandled) + updateRecord() + return bHandled + } + + + fun setHas3dBubbles(has3dBubbles: Boolean) { + 3 DBubbles = has3dBubbles + updateRecord() + } + + /** + * sets whether the parent chart or series has smoothed lines + * + * @param b + */ + fun setSmoothedLine(b: Boolean) { + smoothLine = b + updateRecord() + } + + /** + * data markers are displayed with a shadow on bubble, + * scatter, radar, stock, and line chart groups. + * + * @param b + */ + fun setHasShadow(b: Boolean) { + shadow = b + updateRecord() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -8307035373276421283L + + val prototype: XLSRecord? + get() { + val s = Serfmt() + s.opcode = XLSConstants.SERFMT + s.data = s.PROTOTYPE_BYTES + s.init() + return s + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Series.java b/src/main/java/io/starter/formats/XLS/charts/Series.java deleted file mode 100644 index c208658..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Series.java +++ /dev/null @@ -1,1266 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - - -import io.starter.OpenXLS.CellHandle; -import io.starter.OpenXLS.CellRange; -import io.starter.OpenXLS.FormatHandle; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.OOXML.DLbls; -import io.starter.formats.OOXML.DPt; -import io.starter.formats.OOXML.Marker; -import io.starter.formats.OOXML.SpPr; -import io.starter.formats.XLS.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.Vector; - -/** - * Series: Series Definition (1003h)
                      - *

                      - * This record defines the Series data of a chart. - *

                      - * sdtX and sdtY fields determine data type (numeric and text) - * cValx and cValy fields determine number of cells in series - *

                      - * Offset Name Size Contents - * -- - * 4 sdtX 2 Type of data in cats (1=num, 3=str) - * 8 sdtY 2 Type of data in values (1=num, 3=str) - * 10 cValx 2 Count of categories - * 12 cValy 2 Count of Values - * 14 sdtBSz 2 Type of data in Bubble size series (0=dates, 1=num, 2=seq., 3=text) - * 16 sdtValBSz 2 Count of Bubble series vals - *

                      - * sdtX (2 bytes): An unsigned integer that specifies the type of data in categories (3), or horizontal values on bubble and scatter chart groups, in the series. - * value :0x0001 The series contains categories), or horizontal values on bubble and scatter chart groups, with numeric information. - * value: 0x0003 The series contains categories, or horizontal values on bubble and scatter chart groups, with text information. - * sdtY (2 bytes): An unsigned integer that specifies that the values, or vertical values on bubble and scatter chart groups, in the series contain numeric information. - * MUST be set to 0x0001, and MUST be ignored. - * cValx (2 bytes): An unsigned integer that specifies the count of categories (3), or horizontal values on bubble and scatter chart groups, in the series. - * This value MUST be less than or equal to 0x0F9F. - * cValy (2 bytes): An unsigned integer that specifies the count of values, or vertical values on bubble and scatter chart groups, in the series. - * This value MUST be less than or equal to 0x0F9F. - * sdtBSize (2 bytes): An unsigned integer that specifies that the bubble size values in the series contain numeric information. - * This value MUST be set to 0x0001, and MUST be ignored. - * cValBSize (2 bytes): An unsigned integer that specifies the count of bubble size values in the series. - * This value MUST be less than or equal to 0x0F9F. - *

                      - *

                      - *

                      - * The series object contains a collection of sub objects. Usually this will take the form of 4 ai records, - * type 0-3, and supporting records such as labels. - * - * - * - * @see Chart - */ - -/** - * sdtX (2 bytes): An unsigned integer that specifies the type of data in categories (3), or horizontal values on bubble and scatter chart groups, in the series. MUST be a value from the following table. - * Value Meaning - * 0x0001 The series contains categories, or horizontal values on bubble and scatter chart groups, with numeric information. - * 0x0003 The series contains categories, or horizontal values on bubble and scatter chart groups, with text information. - * sdtY (2 bytes): An unsigned integer that specifies that the values, or vertical values on bubble and scatter chart groups, in the series contain numeric information. MUST be set to 0x0001, and MUST be ignored. - * cValx (2 bytes): An unsigned integer that specifies the count of categories (3), or horizontal values on bubble and scatter chart groups, in the series. This value MUST be less than or equal to 0x0F9F. - * cValy (2 bytes): An unsigned integer that specifies the count of values, or vertical values on bubble and scatter chart groups, in the series. This value MUST be less than or equal to 0x0F9F. - * sdtBSize (2 bytes): An unsigned integer that specifies that the bubble size values in the series contain numeric information. This value MUST be set to 0x0001, and MUST be ignored. - * cValBSize (2 bytes): An unsigned integer that specifies the count of bubble size values in the series. This value MUST be less than or equal to 0x0F9F. - */ -public final class Series extends GenericChartObject implements ChartObject { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7290108485347063887L; - public static int SERIES_TYPE_NUMERIC = 1; - public static int SERIES_TYPE_STRING = 3; - - protected int sdtX = -1, sdtY = -1, cValx = -1, cValy = -1, sdtBSz = -1, sdtValBSz = -1; - - private SpPr shapeProps = null; // OOXML-specific holds the shape properties (line and fill) for this series (all charts) - private Marker m = null; // OOXML-specific object to hold marker properties for this series (radar, scatter and line charts only) - private DLbls d = null; // OOXML-specific object holds Data Labels properties for this series (all charts except surface) - private ArrayList dPts = null; // OOXML-specific object holds Data Labels properties for this series (all charts except surface) - - public void init() { - super.init(); - sdtX = (int) ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - sdtY = (int) ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - cValx = (int) ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - cValy = (int) ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - sdtBSz = (int) ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - sdtValBSz = (int) ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - if (DEBUGLEVEL > 10) Logger.logInfo(toString()); - } - - public Series() { - - } - - public void update() { - byte[] rkdata = this.getData(); - byte[] b = ByteTools.shortToLEBytes((short) sdtX); - rkdata[0] = b[0]; - rkdata[1] = b[1]; - b = ByteTools.shortToLEBytes((short) sdtY); - rkdata[2] = b[0]; - rkdata[3] = b[1]; - b = ByteTools.shortToLEBytes((short) cValx); - rkdata[4] = b[0]; - rkdata[5] = b[1]; - b = ByteTools.shortToLEBytes((short) cValy); - rkdata[6] = b[0]; - rkdata[7] = b[1]; - b = ByteTools.shortToLEBytes((short) sdtBSz); - rkdata[8] = b[0]; - rkdata[9] = b[1]; - b = ByteTools.shortToLEBytes((short) sdtValBSz); - rkdata[10] = b[0]; - rkdata[11] = b[1]; - this.setData(rkdata); - } - - /** - * Returns the series value AI associated with this series. - * - * @return - */ - public Ai getSeriesValueAi() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec br = chartArr.get(i); - if (br.getOpcode() == AI) { - Ai thisAi = (Ai) br; - if (thisAi.getType() == Ai.TYPE_VALS) - return thisAi; - } - } - return null; - } - - /** - * returns the custom number format for this series, 0 if none - * - * @return - */ - private int getSeriesNumberFormat() { - Ai ai = this.getSeriesValueAi(); - int i = 0; - if (ai != null) { - i = ai.getIfmt(); - if (i != 0) - return i; - // if 0, number format is determined by the application - // meaning it uses the number format of the source data - try { - io.starter.formats.XLS.formulas.PtgRef p = (io.starter.formats.XLS.formulas.PtgRef) ai.getCellRangePtgs()[0].getComponents()[0]; - i = ai.getWorkBook().getXf(p.getRefCells()[0].getIxfe()).getIfmt(); - } catch (Exception e) { - } - } - return i; - } - - /** - * return the String representation of the numeric format pattern for the series (values) axis - * - * @return - */ - public String getSeriesFormatPattern() { - int ifmt = getSeriesNumberFormat(); - String[][] fmts = FormatConstantsImpl.getBuiltinFormats(); - for (int x = 0; x < fmts.length; x++) { - if (ifmt == Integer.parseInt(fmts[x][1], 16)) - return fmts[x][0]; - } - // custom?? - try { - Format fmt = this.getWorkBook().getFormat(ifmt); - return fmt.getFormat(); - } catch (Exception e) { - } - return "General"; - } - - /** - * returns the custom number format for a value-type axis - * - * @return - */ - private int getCategoryNumberFormat() { - Ai ai = this.getCategoryValueAi(); - if (ai != null) - return ai.getIfmt(); - return 0; - } - - /** - * return the String representation of the numeric format pattern for the Catgeory axis - * - * @return - */ - public String getCategoryFormatPattern() { - int ifmt = getCategoryNumberFormat(); - String[][] fmts = FormatConstantsImpl.getBuiltinFormats(); - for (int x = 0; x < fmts.length; x++) { - if (ifmt == Integer.parseInt(fmts[x][1], 16)) - return fmts[x][0]; - } - // custom?? - try { - Format fmt = this.getWorkBook().getFormat(ifmt); - return fmt.getFormat(); - } catch (Exception e) { - } - return "General"; - } - - /** - * sets the legend for this series to a text value - * - * @param newLegend new text value for legend for the current series - * @param wbh workbookhandle - */ - public void setLegend(String newLegend, WorkBookHandle wbh) { - this.getLegendAi().setLegend(newLegend); - Chart parent = this.getParentChart(); - parent.getChartSeries().legends = null; // ensure cache is cleared - parent.getLegend().adjustWidth(parent.getMetrics(wbh), parent.getChartType(), parent.getChartSeries().getLegends()); - } - - /** - * set legend to a cell ref. - * - * @param newLegendCell - */ - public void setLegendRef(String newLegendCell) { - Ai ai = this.getLegendAi(); - ai.changeAiLocation(ai.toString(), newLegendCell); - SeriesText st = this.getLegendSeriesText(); - ai.setRt(2); - String legendText = ""; - try { - //CellHandle cell= this.getWorkBook().getCell(newLegendCell); -// newLegendCell= newLegendCell.replace('!', ':'); // for this method it's Sheet:cell (????) - BiffRec r = ai.getWorkBook().getCell(newLegendCell); - legendText = r.getStringVal(); - } catch (Exception e) { - Logger.logErr("Series.setLegendRef: Error setting Legend Reference to '" + newLegendCell + "': " + e.toString()); - } - st.setText(legendText); - } - - - /** - * get legend text - * - * @return - */ - public String getLegendText() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec br = chartArr.get(i); - if (br.getOpcode() == AI) { - Ai thisAi = (Ai) br; - if (thisAi.getType() == Ai.TYPE_TEXT) { - try { - io.starter.formats.XLS.formulas.Ptg[] p = thisAi.getCellRangePtgs(); - return ((io.starter.formats.XLS.formulas.PtgRef) p[0]).getFormattedValue(); - } catch (Exception e) { - } - try { - if (chartArr.size() > i + 1) { - SeriesText st = (SeriesText) chartArr.get(i + 1); - if (st != null) - return st.toString(); - } - } catch (ClassCastException e) { - // couldn't find it! - } - } - } - } - return ""; - } - - /** - * return the legend cell reference - * - * @return - */ - public String getLegendRef() { - Ai ai = this.getLegendAi(); - if (ai != null) return ai.getDefinition(); - return null; - } - - /** - * Return the SeriesText object related to the Legend - * - * @return - */ - protected SeriesText getLegendSeriesText() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec br = chartArr.get(i); - if (br.getOpcode() == AI) { - Ai thisAi = (Ai) br; - if (thisAi.getType() == Ai.TYPE_TEXT) { - if (chartArr.size() > i + 1) { - try { - SeriesText st = (SeriesText) chartArr.get(i + 1); - return st; - } catch (ClassCastException e) { - // couldn't find it! - return null; - } - } - } - } - } - return null; - } - - /** - * Returns the legend value Ai associated with this series - * - * @return - */ - public Ai getLegendAi() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec br = chartArr.get(i); - if (br.getOpcode() == AI) { - Ai thisAi = (Ai) br; - if (thisAi.getType() == Ai.TYPE_TEXT) - return thisAi; - } - } - return null; - } - - - public Ai getBubbleValueAi() { -// if (hasBubbleSizes()) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec br = chartArr.get(i); - if (br.getOpcode() == AI) { - Ai thisAi = (Ai) br; - if (thisAi.getType() == Ai.TYPE_BUBBLES) - return thisAi; - } - } -// } - return null; - } - - /** - * Create a series with all sub components. - *

                      - * REcords are... - * AI - * SeriesText (optional?) - * AI - * AI - * AI - * DataFormat - * SerToCrt - * - * @param seriesData - * @return - */ - protected static Series getPrototype(String seriesRange, String categoryRange, String bubbleRange, String legendRange, String legendText, ChartType chartobj) { - Series series = (Series) Series.getPrototype(); - Chart parentChart = chartobj.getParentChart(); - WorkBook book = parentChart.getWorkBook(); - Ai ai; - // create Series text with Legend - if (legendRange != null) { - ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_LEGEND); - ai.setWorkBook(book); - ai.setSheet(parentChart.getSheet()); // 20080124 KSC: changeAiLocation from "A1" to "" - try { - ai.changeAiLocation("", legendRange/*(seriesText.getWorkSheetName() + "!" + seriesText.getCellAddress())*/); - } catch (Exception e) { - } // it's OK to not have a valid range - series.addChartRecord(ai); - SeriesText st = SeriesText.getPrototype(legendText); - ai.setSeriesText(st); - series.addChartRecord(st); - // 20091102 KSC: when adding series legend will not expand correctly if autopositioning is turned off - // [BugTracker 2844] - Legend l = chartobj.getDataLegend(); - if (l != null) { - l.setAutoPosition(true); - l.incrementHeight(parentChart.getCoords()[3]); - } - - } else { - ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND); - ai.setWorkBook(book); - ai.setSheet(parentChart.getSheet()); - series.addChartRecord(ai); - } -// parentChart.addAi(ai); - // create Series Value Ai - ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_SERIES); - ai.setParentChart(parentChart); - ai.setWorkBook(book); - ai.setSheet(parentChart.getSheet()); - try { - ai.changeAiLocation(ai.toString(), seriesRange); - } catch (Exception e) { -// not necessary to report Logger.logErr("Error setting Series Range: '" + seriesRange + "'-" + e.toString()); - } // it's OK to not have a valid range - series.addChartRecord(ai); -// parentChart.addAi(ai); - // create Category Ai - ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_CATEGORY); - ai.setWorkBook(book); - ai.setSheet(parentChart.getSheet()); - try { - ai.changeAiLocation(ai.toString(), categoryRange); - } catch (Exception e) { - } // it's OK to not have a valid range - series.addChartRecord(ai); -// parentChart.addAi(ai); - // create Bubble (undocumented) Ai - ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_BUBBLE); - ai.setWorkBook(book); - ai.setSheet(parentChart.getSheet()); - if (!bubbleRange.equals("")) { - try { - ai.changeAiLocation(ai.toString(), bubbleRange); - } catch (Exception e) { - } // it's OK to not have a valid range - ai.setRt(2); -/* 20120123 KSC: confused vs. per-series format verses format in chartformat rec ... - * if (((BubbleChart) parentChart.getChartObject()).is3d()) { - - }*/ - } - series.addChartRecord(ai); - DataFormat df = null; - df = (DataFormat) DataFormat.getPrototype(); - // update the data format correctly - Vector ser = parentChart.getAllSeries(); // get ALL series - int yi = -1; // Changed from 0 - int iss = -1; // "" - for (int i = 0; i < ser.size(); i++) { - Series srs = (Series) ser.get(i); - int newYi = srs.getSeriesIndex(); - int newIss = srs.getSeriesNumber(); - if (newYi > yi) yi = newYi; - if (newIss > iss) iss = newIss; - } - yi++; - iss++; - df.setSeriesIndex(yi); - df.setSeriesNumber(iss); - if (chartobj.getBarShape() != 0) { // must ensure each series contains proper shape records - df.setShape(chartobj.getBarShape()); - } - series.addChartRecord(df); - SerToCrt stc = (SerToCrt) SerToCrt.getPrototype(); - // get the correct chart index for the sertocrt - int vCount = 0; - int cCount = 0; - int bCount = 0; - if (ser.size() > 0) { // 20070709 KSC: will be 0 if adding new blank chart - Series s = (Series) ser.get(0); - ArrayList cr = s.getChartRecords(); - for (int i = 0; i < cr.size(); i++) { - BiffRec b = (BiffRec) cr.get(i); - if (b.getOpcode() == SERTOCRT) { - SerToCrt stcc = (SerToCrt) b; - stc.setData(stcc.getData()); - } - } - // set the series level variables correctly - vCount = s.getValueCount(); - cCount = s.getCategoryCount(); - bCount = s.getBubbleCount(); - } - series.init(); - // 20070711 KSC: vCount and cCount are via current range, no?? - try { - if (seriesRange.indexOf(":") != -1) { - int[] coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(seriesRange); - vCount = coords[4]; - } else { - vCount = 1; - } - series.setValueCount(vCount); - } catch (Exception e) { - } - try { - cCount = io.starter.OpenXLS.ExcelTools.getRangeCoords(categoryRange)[4]; - series.setCategoryCount(cCount); - } catch (Exception e) { - } - if (!bubbleRange.equals("")) { - try { - bCount = io.starter.OpenXLS.ExcelTools.getRangeCoords(bubbleRange)[4]; - series.setBubbleCount(bCount); - } catch (Exception e) { - } - } - series.addChartRecord(stc); - return series; - } - - public static XLSRecord getPrototype() { - Series s = new Series(); - s.setOpcode(SERIES); - s.setData(s.PROTOTYPE_BYTES); - s.init(); - return s; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{3, 0, 1, 0, 3, 0, 3, 0, 1, 0, 0, 0}; - - /** - * Returns the category value AI associated with this series. - * - * @return - */ - public Ai getCategoryValueAi() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec br = chartArr.get(i); - if (br.getOpcode() == AI) { - Ai thisAi = (Ai) br; - if (thisAi.getType() == Ai.TYPE_CATEGORIES) - return thisAi; - } - } - return null; - } - - - /** - * Get the series index (file relative) - * - * @return - */ - protected int getSeriesIndex() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) - return (int) df.getSeriesIndex(); - return -1; - } - - /** - * Get the series Number (display) - * - * @return - */ - protected int getSeriesNumber() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) - return (int) df.getSeriesNumber(); - return -1; - } - - protected int getCategoryCount() { - return cValx; - } - - protected int getValueCount() { - return cValy; - } - - public void setCategoryCount(int i) { //20070711 KSC: changed from protected - cValx = i; - this.update(); - } - - protected int getBubbleCount() { - return sdtValBSz; - } - - public void setBubbleCount(int i) { //20070711 KSC: changed from protected - sdtValBSz = i; - this.update(); - } - - public void setValueCount(int i) { //20070711 KSC: changed from protected - cValy = i; - this.update(); - } - - // 20070712 KSC: get/set for data types - public int getCategoryDataType() { - return sdtX; - } - - public int getValueDataType() { - return sdtY; - } - - public void setCategoryDataType(int i) { - sdtX = i; - this.update(); - } - - public void setValueDataType(int i) { - sdtY = i; - this.update(); - } - - public boolean hasBubbleSizes() { - return sdtValBSz > 0; - } - - /** - * Gets the dataformat record associated with this Series, if any. - * If none present, option to create a basic DataFormat set of records DataFormat controls - * - * @return DataFormat Record - */ - private DataFormat getDataFormatRec(boolean bCreate) { - DataFormat df = (DataFormat) Chart.findRec(this.chartArr, DataFormat.class); - if (df == null && bCreate) { // create dataformat - df = (DataFormat) DataFormat.getPrototypeWithFormatRecs(this.getParentChart()); - this.addChartRecord(df); - } - return df; - } - - - /** - * retrieves the data format record which corresponds to the desired pie slice - * - * @param slice - * @return - */ - private DataFormat getDataFormatRecSlice(int slice, boolean bCreate) { - DataFormat df = this.getDataFormatRec(false); - if (df == null) { - Logger.logErr("Series.getDataFormatRecSlice: cannot find data format record"); - return null; - } - int seriesNumber = df.getSeriesNumber(); - // for PIE charts, DataFormats are stored in 1st series, - // just after the initial data format rec - // must check point number to see if it's the desired df - Series s; - if (seriesNumber == 0) // we're on the first one - s = this; - else { // should not have more than 1 series for a pie chart! - s = (Series) getParentChart().getAllSeries().get(seriesNumber); - } - int i = Chart.findRecPosition(s.chartArr, DataFormat.class); // get position of the first df - i++; // skip 1st - int lastSlice = 0; - while (i < s.chartArr.size()) { - if (s.chartArr.get(i) instanceof DataFormat) { - df = (DataFormat) s.chartArr.get(i); - lastSlice = df.getPointNumber(); - if (df.getPointNumber() == slice) - return df; - } - i++; - } - // create - if (bCreate) { - i--; - while (lastSlice <= slice) { - df = (DataFormat) DataFormat.getPrototypeWithFormatRecs(this.getParentChart()); - df.setPointNumber(lastSlice++); - df.setParentChart(this.getParentChart()); - s.chartArr.add(i++, df); - } - return df; - } - return null; - } - - /** - * returns the shape of the data point for this series - * - * @return - */ - public int getShape() { - int ret = 0; - DataFormat df = this.getDataFormatRec(false); - if (df != null) - ret = df.getShape(); - return ret; - } - - public void setShape(int shape) { - DataFormat df = this.getDataFormatRec(true); - df.setShape(shape); - } - - /** - * returns true if this series has smoothed lines - * - * @return - */ - public boolean getHasSmoothedLines() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) - return df.getSmoothedLines(); - return false; - } - - /** - * set smooth lines setting (applicable for line, scatter charts) - * - * @param smooth - */ - public void setHasSmoothLines(boolean smooth) { - DataFormat df = this.getDataFormatRec(true); - df.setSmoothLines(smooth); - } - - /** - * sets this series to have lines - *
                      Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray - */ - public void setHasLines(int lineStyle) { - DataFormat df = this.getDataFormatRec(true); - df.setHasLines(lineStyle); - } - - /** - * sets the color for this series - * NOTE: for PIE Charts, use setPieSliceColor - * - * @param clr String color hex String - * @see setPieSliceColor - */ - public void setColor(String clr) { - int type = this.getParentChart().getChartType(); - if (type == ChartConstants.PIECHART) { - setPieSliceColor(clr, this.getSeriesIndex()); - return; - } - DataFormat df = this.getDataFormatRec(true); - df.setSeriesColor(clr); - } - - /** - * if the exact/correct color index is not used, the fill color comes out black - * - * @param clr - * @return - */ - private int ensureCorrectColorInt(int clr) { - // "The Chart color table is a subset of the full color table" - if (clr == FormatConstants.COLOR_RED) - clr = FormatConstants.COLOR_RED_CHART; - if (clr == FormatConstants.COLOR_BLUE) - clr = FormatConstants.COLOR_BLUE_CHART; - if (clr == FormatConstants.COLOR_YELLOW) - clr = FormatConstants.COLOR_YELLOW_CHART; - if (clr == FormatConstants.COLOR_DARK_GREEN) // no standard chart dark green ... - clr = FormatConstants.COLOR_GREEN; - if (clr == FormatConstants.COLOR_DARK_YELLOW) // no standard chart dark yellow ... - clr = FormatConstants.COLOR_YELLOW_CHART; - if (clr == FormatConstants.COLOR_OLIVE_GREEN) - clr = FormatConstants.COLOR_OLIVE_GREEN_CHART; - if (clr == FormatConstants.COLOR_WHITE) - clr = FormatConstants.COLOR_WHITE3; - return clr; - - } - - /** - * sets the color for this series - * NOTE: for PIE Charts, use setPieSliceColor - * - * @param clr color int - * @see setPieSliceColor - */ - public void setColor(int clr) { - clr = ensureCorrectColorInt(clr); - int type = this.getParentChart().getChartType(); - if (type == ChartConstants.PIECHART) { - setPieSliceColor(clr, this.getSeriesIndex()); - return; - } - DataFormat df = this.getDataFormatRec(true); - df.setSeriesColor(clr); - - } - - - /** - * sets the color of the desired pie slice - * - * @param clr color int - * @param slice 0-based pie slice number - */ - public void setPieSliceColor(int clr, int slice) { - clr = ensureCorrectColorInt(clr); - int type = this.getParentChart().getChartType(); - if (type != ChartConstants.PIECHART) - return; - DataFormat df = this.getDataFormatRecSlice(slice, true); - if (df != null) - df.setPieSliceColor(clr, slice); - else - Logger.logErr("Series.setPieSliceColor: unable to fnd pie slice record"); - } - - - /** - * sets the color of the desired pie slice - * - * @param clr color int - * @param slice 0-based pie slice number - */ - public void setPieSliceColor(String clr, int slice) { - int type = this.getParentChart().getChartType(); - if (type != ChartConstants.PIECHART) - return; - DataFormat df = this.getDataFormatRecSlice(slice, true); - if (df != null) - df.setPieSliceColor(clr, slice); - else - Logger.logErr("Series.setPieSliceColor: unable to fnd pie slice record"); - } - - // Periwinkle Plum+ Ivory Light Turquoise Dark Purple Coral Ocean Blue Ice Blue {17, 25, 19, 27, 28, 22, 23, 24}; - // try these color int numbers instead: - // alternative explanation: chart fills 16-23, chart lines 24-31 - public static int[] automaticSeriesColors = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62}; // also used in mapping default colors in AreaFormat, MarkerFormat, Frame ... - - /** - * retrieve the series/bar color for this series - * NOTE: for Pie Charts, must use getPieSliceColor - * - * @return color int - * @see getPieSliceColor - */ - public String getSeriesColor() { - DataFormat df = this.getDataFormatRec(false); - int type = this.getParentChart().getChartType(); - int seriesNumber = df.getSeriesNumber(); - if (type == ChartConstants.PIECHART) - return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[seriesNumber]]); - String bg = df.getBgColor(); - if (bg != null) - return bg; - // otherwise, color is automatic or default chart series color - return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[seriesNumber]]); - } - - - /** - * get the pie slice color in this pie chart - * - * @param slice - * @return color int - */ - public String getPieSliceColor(int slice) { - int type = this.getParentChart().getChartType(); - if (type != ChartConstants.PIECHART) - return null; - DataFormat df = this.getDataFormatRecSlice(slice, false); - if (df != null) { - String bg = df.getBgColor(); - if (bg != null) - return bg; - } - // otherwise, color is automatic or default chart series color - return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[slice]]); - } - - /** - * return the type of markers for each series: - *
                      0 = no marker - *
                      1 = square - *
                      2 = diamond - *
                      3 = triangle - *
                      4 = X - *
                      5 = star - *
                      6 = Dow-Jones - *
                      7 = standard deviation - *
                      8 = circle - *
                      9 = plus sign - **/ - public int getMarkerFormat() { - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - return df.getMarkerFormat(); - } - return 0; - } - - - /** - * return data label options as an int - *
                      can be one or more of: - *
                      SHOWVALUE= 0x1; - *
                      SHOWVALUEPERCENT= 0x2; - *
                      SHOWCATEGORYPERCENT= 0x4; - *
                      SMOOTHEDLINE= 0x8; - *
                      SHOWCATEGORYLABEL= 0x10; - *
                      SHOWBUBBLELABEL= 0x20; - *
                      SHOWSERIESLABEL= 0x40; - * - * @return a combination of data label options above or 0 if none - * @see AttachedLabel - */ - public int getDataLabel() { - int datalabels = 0; - DataLabExtContents dl = (DataLabExtContents) Chart.findRec(this.chartArr, DataLabExtContents.class); - if (dl != null) { // Extended Label -- add to attachedlabel, if any - datalabels = dl.getTypeInt(); - } - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - datalabels |= df.getDataLabelTypeInt(); - } - return datalabels; - } - - /** - * PIE data label information is contained within the 1st series only - *
                      TODO: not implemented yet - * return data label options as an int - *
                      can be one or more of: - *
                      SHOWVALUE= 0x1; - *
                      SHOWVALUEPERCENT= 0x2; - *
                      SHOWCATEGORYPERCENT= 0x4; - *
                      SMOOTHEDLINE= 0x8; - *
                      SHOWCATEGORYLABEL= 0x10; - *
                      SHOWBUBBLELABEL= 0x20; - *
                      SHOWSERIESLABEL= 0x40; - * - * @param defaultdl int default data label setting for overall chart - * @return int array of data labels for each pie slice - * @see AttachedLabel - */ - public int[] getDataLabelsPIE(int defaultdl) { - int datalabels = 0; - DataFormat df = this.getDataFormatRec(false); - if (df != null) { - datalabels |= df.getDataLabelTypeInt(); - } - return null; - } - - /** - * return the OOXML shape property for this series - * - * @return - */ - public SpPr getSpPr() { - return shapeProps; - } - - /** - * set the OOXML shape properties for this series - * - * @param sp - */ - public void setSpPr(SpPr sp) { - shapeProps = sp; - } - - /** - * return the OOXML marker properties for this series - * - * @return - */ - public Marker getMarker() { - return m; - } - - /** - * set the OOXML marker properties for this series - * - * @param Sp - */ - public void setMarker(Marker m) { - this.m = m; - } - - /** - * return the OOXML dLbls (data labels) properties for this series - * - * @return - */ - public DLbls getDLbls() { - return d; - } - - /** - * set the OOXML dLbls (data labels) properties for this series - * - * @param Sp - */ - public void setDLbls(DLbls d) { - this.d = d; - } - - /** - * return OOXML dPt (data points) for this series - * - * @return - */ - public DPt[] getDPt() { - if (dPts != null) - return (DPt[]) dPts.toArray(new DPt[]{}); - return null; - } - - /** - * add a dPt element (data point) for this series - * - * @param Sp - */ - public void addDpt(DPt d) { - if (dPts == null) - dPts = new ArrayList(); - dPts.add(d); - } - - /** - * returns the val OOXML element that defines the values for the series values - * - * @param valstr either "val" or "yval" for scatter/bubble charts - * @return - */ - public StringBuffer getValOOXML(String valstr) { - StringBuffer ooxml = new StringBuffer(); - ooxml.append(""); - ooxml.append("\r\n"); - - Ai seriesAi = null; - for (int i = 0; i < chartArr.size(); i++) { - BiffRec br = chartArr.get(i); - if (br.getOpcode() == AI) { - Ai thisAi = (Ai) br; - if (thisAi.getType() == Ai.TYPE_VALS) { - seriesAi = thisAi; - break; - } - - } - } - - ooxml.append(""); - ooxml.append("\r\n"); // number reference - ooxml.append("" + OOXMLAdapter.stripNonAscii(seriesAi.toString()) + ""); - ooxml.append("\r\n"); // string range - // Need numCache for chart lines apparently - ooxml.append(""); - ooxml.append("\r\n"); // specifies the last data shown on the chart for a series - // formatCode == format pattern - ooxml.append("" + this.getSeriesFormatPattern() + ""); - CellRange cr = new CellRange(seriesAi.toString(), parentChart.wbh, false); - CellHandle[] ch = cr.getCells(); - // ptCount == point count - ooxml.append(getValueRangeOOXML(ch)); - // pt * n == a Numeric Point each has a child, an idx attribute and an optional formatcode attribute - ooxml.append(""); - ooxml.append("\r\n"); // - - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - return ooxml; - - } - - /** - * return the cat OOXML element used to define a series category - * - * @param cat string category cell range for the given series -- - * almost always the same for each series (except for scatter/bubble charts) - * @param catstr either "cat" or "xval" for scatter/bubble charts - * cat elements must contain string references - * xval contain numeric references - * @return - */ - public StringBuffer getCatOOXML(String cat, String catstr) { - StringBuffer ooxml = new StringBuffer(); - if (!"".equals(cat)) { // causes 1004 vb error upon Excel SAVE - BAXTER SAVE BUG - ooxml.append(""); - ooxml.append("\r\n"); // categories contain a string "formula" ref + string caches - if (catstr.equals("cat")) - ooxml.append(""); // string reference - else - ooxml.append(""); // number reference - ooxml.append("\r\n"); - ooxml.append("" + OOXMLAdapter.stripNonAscii(cat) + ""); - ooxml.append("\r\n"); - /* 20090211 KSC: if errors in referenced cells whole chart will error; best to avoid caching at all */ - if (catstr.equals("cat")) - ooxml.append(""); // string reference - else - ooxml.append(""); // number reference - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - } else { - // TESTING-- remove when done - //Logger.logWarn("ChartHandle.getOOXML: null category found- skipping"); - } - return ooxml; - } - - /** - * returns the bubbleSize OOXML element that defines the values for the series values - * - * @param isBubble3d true if it's a 3d bubble chart - * @return - */ - public StringBuffer getBubbleOOXML(boolean isBubble3d) { - StringBuffer ooxml = new StringBuffer(); - Ai bubbleAi = null; - for (int i = 0; i < chartArr.size(); i++) { - BiffRec br = chartArr.get(i); - if (br.getOpcode() == AI) { - Ai thisAi = (Ai) br; - if (thisAi.getType() == Ai.TYPE_BUBBLES) { - bubbleAi = thisAi; - break; - } - } - } - - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); // number reference - ooxml.append("" + bubbleAi.toString() + ""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - try { - CellHandle[] cells = CellRange.getCells(bubbleAi.toString(), parentChart.wbh); - ooxml.append(getValueRangeOOXML(cells)); - ooxml.append("\r\n"); - } catch (NumberFormatException e) { - Logger.logErr("geteriesOOXML: Number format exception for Bubble Range: " + bubbleAi.toString()); - } - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - if (isBubble3d) - ooxml.append(""); - ooxml.append("\r\n"); - return ooxml; - } - - /** - * returns the bubbleSize OOXML element that defines the values for the series values - * - * @param isBubble3d true if it's a 3d bubble chart - * @return - */ - public StringBuffer getLegendOOXML(boolean from2003) { - StringBuffer ooxml = new StringBuffer(); - String txt = this.getLegendText(); - Ai ai = this.getLegendAi(); -/* String txt= null; - try { - io.starter.formats.XLS.formulas.Ptg[] p= ai.getCellRangePtgs(); - txt= ((io.starter.formats.XLS.formulas.PtgRef)p[0]).getFormattedValue(); - } catch (Exception e) {} - try { - if (chartArr.size()>i+1) { - SeriesText st = (SeriesText)chartArr.get(i+1); - if (st!=null) - txt= st.toString(); - } - }catch(ClassCastException e) { - // couldn't find it! - } -*/ - - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); // string reference - if (ai != null) { - ooxml.append("" + OOXMLAdapter.stripNonAscii(ai.getDefinition()) + ""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append("" + OOXMLAdapter.stripNonAscii(txt) + ""); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - ooxml.append(""); - ooxml.append("\r\n"); - if (this.getSpPr() != null) - ooxml.append(this.getSpPr().getOOXML()); - else if (from2003) { - SpPr ss; - if (parentChart.getChartType() != RADARCHART) - ss = new SpPr("c", this.getSeriesColor().substring(1), 12700, "000000"); - else - ss = new SpPr("c", null, 25400, this.getSeriesColor().substring(1)); - ooxml.append(ss.getOOXML()); - } - } - return ooxml; - } - - /** - * generate the OOXML used to represent this set of value cells (element numRef) - * - * @param cells - * @return - */ - private static String getValueRangeOOXML(CellHandle[] cells) { - StringBuffer ooxml = new StringBuffer(); - if (cells == null) return ""; - ooxml.append(""); - ooxml.append("\r\n"); - for (int j = 0; j < cells.length; j++) { - ooxml.append(""); - ooxml.append("\r\n"); - if (!cells[j].getStringVal().equals("NaN")) { - ooxml.append("" + cells[j].getStringVal() + ""); - ooxml.append("\r\n"); - } else { // appears that NaN is an invalid entry - ooxml.append("0"); - ooxml.append("\r\n"); - } - ooxml.append(""); - ooxml.append("\r\n"); - } - return ooxml.toString(); - } - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/Series.kt b/src/main/java/io/starter/formats/XLS/charts/Series.kt new file mode 100644 index 0000000..156065c --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Series.kt @@ -0,0 +1,1268 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + + +import io.starter.OpenXLS.CellHandle +import io.starter.OpenXLS.CellRange +import io.starter.OpenXLS.FormatHandle +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.OOXML.DLbls +import io.starter.formats.OOXML.DPt +import io.starter.formats.OOXML.Marker +import io.starter.formats.OOXML.SpPr +import io.starter.formats.XLS.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.Vector + +/** + * **Series: Series Definition (1003h)**

                      + * + * + * This record defines the Series data of a chart. + * + * + * sdtX and sdtY fields determine data type (numeric and text) + * cValx and cValy fields determine number of cells in series + * + * + * Offset Name Size Contents + * -- + * 4 sdtX 2 Type of data in cats (1=num, 3=str) + * 8 sdtY 2 Type of data in values (1=num, 3=str) + * 10 cValx 2 Count of categories + * 12 cValy 2 Count of Values + * 14 sdtBSz 2 Type of data in Bubble size series (0=dates, 1=num, 2=seq., 3=text) + * 16 sdtValBSz 2 Count of Bubble series vals + * + * + * sdtX (2 bytes): An unsigned integer that specifies the type of data in categories (3), or horizontal values on bubble and scatter chart groups, in the series. + * value :0x0001 The series contains categories), or horizontal values on bubble and scatter chart groups, with numeric information. + * value: 0x0003 The series contains categories, or horizontal values on bubble and scatter chart groups, with text information. + * sdtY (2 bytes): An unsigned integer that specifies that the values, or vertical values on bubble and scatter chart groups, in the series contain numeric information. + * MUST be set to 0x0001, and MUST be ignored. + * cValx (2 bytes): An unsigned integer that specifies the count of categories (3), or horizontal values on bubble and scatter chart groups, in the series. + * This value MUST be less than or equal to 0x0F9F. + * cValy (2 bytes): An unsigned integer that specifies the count of values, or vertical values on bubble and scatter chart groups, in the series. + * This value MUST be less than or equal to 0x0F9F. + * sdtBSize (2 bytes): An unsigned integer that specifies that the bubble size values in the series contain numeric information. + * This value MUST be set to 0x0001, and MUST be ignored. + * cValBSize (2 bytes): An unsigned integer that specifies the count of bubble size values in the series. + * This value MUST be less than or equal to 0x0F9F. + * + * + * + * + * + * + * The series object contains a collection of sub objects. Usually this will take the form of 4 ai records, + * type 0-3, and supporting records such as labels. + * + * + * + * @see Chart + */ + +/** + * sdtX (2 bytes): An unsigned integer that specifies the type of data in categories (3), or horizontal values on bubble and scatter chart groups, in the series. MUST be a value from the following table. + * Value Meaning + * 0x0001 The series contains categories, or horizontal values on bubble and scatter chart groups, with numeric information. + * 0x0003 The series contains categories, or horizontal values on bubble and scatter chart groups, with text information. + * sdtY (2 bytes): An unsigned integer that specifies that the values, or vertical values on bubble and scatter chart groups, in the series contain numeric information. MUST be set to 0x0001, and MUST be ignored. + * cValx (2 bytes): An unsigned integer that specifies the count of categories (3), or horizontal values on bubble and scatter chart groups, in the series. This value MUST be less than or equal to 0x0F9F. + * cValy (2 bytes): An unsigned integer that specifies the count of values, or vertical values on bubble and scatter chart groups, in the series. This value MUST be less than or equal to 0x0F9F. + * sdtBSize (2 bytes): An unsigned integer that specifies that the bubble size values in the series contain numeric information. This value MUST be set to 0x0001, and MUST be ignored. + * cValBSize (2 bytes): An unsigned integer that specifies the count of bubble size values in the series. This value MUST be less than or equal to 0x0F9F. + */ +class Series : GenericChartObject(), ChartObject { + + protected var sdtX = -1 + protected var sdtY = -1 + protected var cValx = -1 + protected var cValy = -1 + protected var sdtBSz = -1 + protected var sdtValBSz = -1 + + /** + * return the OOXML shape property for this series + * + * @return + */ + /** + * set the OOXML shape properties for this series + * + * @param sp + */ + var spPr: SpPr? = null // OOXML-specific holds the shape properties (line and fill) for this series (all charts) + /** + * return the OOXML marker properties for this series + * + * @return + */ + /** + * set the OOXML marker properties for this series + * + * @param Sp + */ + var marker: Marker? = null // OOXML-specific object to hold marker properties for this series (radar, scatter and line charts only) + /** + * return the OOXML dLbls (data labels) properties for this series + * + * @return + */ + /** + * set the OOXML dLbls (data labels) properties for this series + * + * @param Sp + */ + var dLbls: DLbls? = null // OOXML-specific object holds Data Labels properties for this series (all charts except surface) + private var dPts: ArrayList<*>? = null // OOXML-specific object holds Data Labels properties for this series (all charts except surface) + + /** + * Returns the series value AI associated with this series. + * + * @return + */ + val seriesValueAi: Ai? + get() { + for (i in chartArr.indices) { + val br = chartArr[i] + if (br.opcode == XLSConstants.AI) { + val thisAi = br as Ai + if (thisAi.type == Ai.TYPE_VALS) + return thisAi + } + } + return null + } + + /** + * returns the custom number format for this series, 0 if none + * + * @return + */ + private// if 0, number format is determined by the application + // meaning it uses the number format of the source data + val seriesNumberFormat: Int + get() { + val ai = this.seriesValueAi + var i = 0 + if (ai != null) { + i = ai.ifmt + if (i != 0) + return i + try { + val p = ai.cellRangePtgs[0].components[0] as io.starter.formats.XLS.formulas.PtgRef + i = ai.workBook!!.getXf(p.refCells!![0].ixfe)!!.ifmt.toInt() + } catch (e: Exception) { + } + + } + return i + } + + /** + * return the String representation of the numeric format pattern for the series (values) axis + * + * @return + */ + // custom?? + val seriesFormatPattern: String? + get() { + val ifmt = seriesNumberFormat + val fmts = FormatConstantsImpl.builtinFormats + for (x in fmts.indices) { + if (ifmt == Integer.parseInt(fmts[x][1], 16)) + return fmts[x][0] + } + try { + val fmt = this.workBook!!.getFormat(ifmt) + return fmt.format + } catch (e: Exception) { + } + + return "General" + } + + /** + * returns the custom number format for a value-type axis + * + * @return + */ + private val categoryNumberFormat: Int + get() { + val ai = this.categoryValueAi + return ai?.ifmt ?: 0 + } + + /** + * return the String representation of the numeric format pattern for the Catgeory axis + * + * @return + */ + // custom?? + val categoryFormatPattern: String? + get() { + val ifmt = categoryNumberFormat + val fmts = FormatConstantsImpl.builtinFormats + for (x in fmts.indices) { + if (ifmt == Integer.parseInt(fmts[x][1], 16)) + return fmts[x][0] + } + try { + val fmt = this.workBook!!.getFormat(ifmt) + return fmt.format + } catch (e: Exception) { + } + + return "General" + } + + + /** + * get legend text + * + * @return + */ + // couldn't find it! + val legendText: String + get() { + for (i in chartArr.indices) { + val br = chartArr[i] + if (br.opcode == XLSConstants.AI) { + val thisAi = br as Ai + if (thisAi.type == Ai.TYPE_TEXT) { + try { + val p = thisAi.cellRangePtgs + return (p[0] as io.starter.formats.XLS.formulas.PtgRef).formattedValue + } catch (e: Exception) { + } + + try { + if (chartArr.size > i + 1) { + val st = chartArr[i + 1] as SeriesText + if (st != null) + return st.toString() + } + } catch (e: ClassCastException) { + } + + } + } + } + return "" + } + + /** + * return the legend cell reference + * + * @return + */ + /** + * set legend to a cell ref. + * + * @param newLegendCell + */ + //CellHandle cell= this.getWorkBook().getCell(newLegendCell); + // newLegendCell= newLegendCell.replace('!', ':'); // for this method it's Sheet:cell (????) + var legendRef: String? + get() { + val ai = this.legendAi + return ai?.definition + } + set(newLegendCell) { + val ai = this.legendAi + ai!!.changeAiLocation(ai.toString(), newLegendCell) + val st = this.legendSeriesText + ai.setRt(2) + var legendText = "" + try { + val r = ai.workBook!!.getCell(newLegendCell) + legendText = r.stringVal + } catch (e: Exception) { + Logger.logErr("Series.setLegendRef: Error setting Legend Reference to '$newLegendCell': $e") + } + + st!!.setText(legendText) + } + + /** + * Return the SeriesText object related to the Legend + * + * @return + */ + protected// couldn't find it! + val legendSeriesText: SeriesText? + get() { + for (i in chartArr.indices) { + val br = chartArr[i] + if (br.opcode == XLSConstants.AI) { + val thisAi = br as Ai + if (thisAi.type == Ai.TYPE_TEXT) { + if (chartArr.size > i + 1) { + try { + return chartArr[i + 1] as SeriesText + } catch (e: ClassCastException) { + return null + } + + } + } + } + } + return null + } + + /** + * Returns the legend value Ai associated with this series + * + * @return + */ + val legendAi: Ai? + get() { + for (i in chartArr.indices) { + val br = chartArr[i] + if (br.opcode == XLSConstants.AI) { + val thisAi = br as Ai + if (thisAi.type == Ai.TYPE_TEXT) + return thisAi + } + } + return null + } + + + // if (hasBubbleSizes()) { + // } + val bubbleValueAi: Ai? + get() { + for (i in chartArr.indices) { + val br = chartArr[i] + if (br.opcode == XLSConstants.AI) { + val thisAi = br as Ai + if (thisAi.type == Ai.TYPE_BUBBLES) + return thisAi + } + } + return null + } + + private val PROTOTYPE_BYTES = byteArrayOf(3, 0, 1, 0, 3, 0, 3, 0, 1, 0, 0, 0) + + /** + * Returns the category value AI associated with this series. + * + * @return + */ + val categoryValueAi: Ai? + get() { + for (i in chartArr.indices) { + val br = chartArr[i] + if (br.opcode == XLSConstants.AI) { + val thisAi = br as Ai + if (thisAi.type == Ai.TYPE_CATEGORIES) + return thisAi + } + } + return null + } + + + /** + * Get the series index (file relative) + * + * @return + */ + protected val seriesIndex: Int + get() { + val df = this.getDataFormatRec(false) + return df?.seriesIndex?.toInt() ?: -1 + } + + /** + * Get the series Number (display) + * + * @return + */ + protected val seriesNumber: Int + get() { + val df = this.getDataFormatRec(false) + return df?.seriesNumber?.toInt() ?: -1 + } + + protected//20070711 KSC: changed from protected + var categoryCount: Int + get() = cValx + set(i) { + cValx = i + this.update() + } + + protected//20070711 KSC: changed from protected + var valueCount: Int + get() = cValy + set(i) { + cValy = i + this.update() + } + + protected//20070711 KSC: changed from protected + var bubbleCount: Int + get() = sdtValBSz + set(i) { + sdtValBSz = i + this.update() + } + + // 20070712 KSC: get/set for data types + var categoryDataType: Int + get() = sdtX + set(i) { + sdtX = i + this.update() + } + + var valueDataType: Int + get() = sdtY + set(i) { + sdtY = i + this.update() + } + + /** + * returns the shape of the data point for this series + * + * @return + */ + var shape: Int + get() { + var ret = 0 + val df = this.getDataFormatRec(false) + if (df != null) + ret = df.shape.toInt() + return ret + } + set(shape) { + val df = this.getDataFormatRec(true) + df!!.setShape(shape) + } + + /** + * returns true if this series has smoothed lines + * + * @return + */ + val hasSmoothedLines: Boolean + get() { + val df = this.getDataFormatRec(false) + return df?.smoothedLines ?: false + } + + /** + * retrieve the series/bar color for this series + * NOTE: for Pie Charts, must use getPieSliceColor + * + * @return color int + * @see getPieSliceColor + */ + // otherwise, color is automatic or default chart series color + val seriesColor: String + get() { + val df = this.getDataFormatRec(false) + val type = this.parentChart!!.chartType + val seriesNumber = df!!.seriesNumber.toInt() + if (type == ChartConstants.PIECHART) + return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[seriesNumber]]) + val bg = df.bgColor + return bg ?: FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[seriesNumber]]) + } + + /** + * return the type of markers for each series: + *

                      0 = no marker + *

                      1 = square + *

                      2 = diamond + *

                      3 = triangle + *

                      4 = X + *

                      5 = star + *

                      6 = Dow-Jones + *

                      7 = standard deviation + *

                      8 = circle + *

                      9 = plus sign + */ + val markerFormat: Int + get() { + val df = this.getDataFormatRec(false) + return df?.markerFormat ?: 0 + } + + + /** + * return data label options as an int + *

                      can be one or more of: + *

                      SHOWVALUE= 0x1; + *

                      SHOWVALUEPERCENT= 0x2; + *

                      SHOWCATEGORYPERCENT= 0x4; + *

                      SMOOTHEDLINE= 0x8; + *

                      SHOWCATEGORYLABEL= 0x10; + *

                      SHOWBUBBLELABEL= 0x20; + *

                      SHOWSERIESLABEL= 0x40; + * + * @return a combination of data label options above or 0 if none + * @see AttachedLabel + */ + // Extended Label -- add to attachedlabel, if any + val dataLabel: Int + get() { + var datalabels = 0 + val dl = Chart.findRec(this.chartArr, DataLabExtContents::class.java) as DataLabExtContents + if (dl != null) { + datalabels = dl.typeInt + } + val df = this.getDataFormatRec(false) + if (df != null) { + datalabels = datalabels or df.dataLabelTypeInt + } + return datalabels + } + + /** + * return OOXML dPt (data points) for this series + * + * @return + */ + val dPt: Array? + get() = if (dPts != null) dPts!!.toTypedArray() as Array else null + + override fun init() { + super.init() + sdtX = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()).toInt() + sdtY = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()).toInt() + cValx = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()).toInt() + cValy = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()).toInt() + sdtBSz = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()).toInt() + sdtValBSz = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()).toInt() + if (DEBUGLEVEL > 10) Logger.logInfo(toString()) + } + + fun update() { + val rkdata = this.data + var b = ByteTools.shortToLEBytes(sdtX.toShort()) + rkdata[0] = b[0] + rkdata[1] = b[1] + b = ByteTools.shortToLEBytes(sdtY.toShort()) + rkdata[2] = b[0] + rkdata[3] = b[1] + b = ByteTools.shortToLEBytes(cValx.toShort()) + rkdata[4] = b[0] + rkdata[5] = b[1] + b = ByteTools.shortToLEBytes(cValy.toShort()) + rkdata[6] = b[0] + rkdata[7] = b[1] + b = ByteTools.shortToLEBytes(sdtBSz.toShort()) + rkdata[8] = b[0] + rkdata[9] = b[1] + b = ByteTools.shortToLEBytes(sdtValBSz.toShort()) + rkdata[10] = b[0] + rkdata[11] = b[1] + this.data = rkdata + } + + /** + * sets the legend for this series to a text value + * + * @param newLegend new text value for legend for the current series + * @param wbh workbookhandle + */ + fun setLegend(newLegend: String, wbh: WorkBookHandle) { + this.legendAi!!.setLegend(newLegend) + val parent = this.parentChart + parent!!.chartSeries.legends = null // ensure cache is cleared + parent.legend!!.adjustWidth(parent.getMetrics(wbh), parent.chartType, parent.chartSeries.getLegends()) + } + + fun hasBubbleSizes(): Boolean { + return sdtValBSz > 0 + } + + /** + * Gets the dataformat record associated with this Series, if any. + * If none present, option to create a basic DataFormat set of records DataFormat controls + * + * @return DataFormat Record + */ + private fun getDataFormatRec(bCreate: Boolean): DataFormat? { + var df = Chart.findRec(this.chartArr, DataFormat::class.java) as DataFormat + if (df == null && bCreate) { // create dataformat + df = DataFormat.getPrototypeWithFormatRecs(this.parentChart) as DataFormat + this.addChartRecord(df) + } + return df + } + + + /** + * retrieves the data format record which corresponds to the desired pie slice + * + * @param slice + * @return + */ + private fun getDataFormatRecSlice(slice: Int, bCreate: Boolean): DataFormat? { + var df = this.getDataFormatRec(false) + if (df == null) { + Logger.logErr("Series.getDataFormatRecSlice: cannot find data format record") + return null + } + val seriesNumber = df.seriesNumber.toInt() + // for PIE charts, DataFormats are stored in 1st series, + // just after the initial data format rec + // must check point number to see if it's the desired df + val s: Series + if (seriesNumber == 0) + // we're on the first one + s = this + else { // should not have more than 1 series for a pie chart! + s = parentChart!!.allSeries[seriesNumber] as Series + } + var i = Chart.findRecPosition(s.chartArr, DataFormat::class.java) // get position of the first df + i++ // skip 1st + var lastSlice = 0 + while (i < s.chartArr.size) { + if (s.chartArr[i] is DataFormat) { + df = s.chartArr[i] as DataFormat + lastSlice = df.pointNumber.toInt() + if (df.pointNumber.toInt() == slice) + return df + } + i++ + } + // create + if (bCreate) { + i-- + while (lastSlice <= slice) { + df = DataFormat.getPrototypeWithFormatRecs(this.parentChart) as DataFormat + df.setPointNumber(lastSlice++) + df.parentChart = this.parentChart + s.chartArr.add(i++, df) + } + return df + } + return null + } + + /** + * set smooth lines setting (applicable for line, scatter charts) + * + * @param smooth + */ + fun setHasSmoothLines(smooth: Boolean) { + val df = this.getDataFormatRec(true) + df!!.setSmoothLines(smooth) + } + + /** + * sets this series to have lines + *

                      Style of line (0= solid, 1= dash, 2= dot, 3= dash-dot,4= dash dash-dot, 5= none, 6= dk gray pattern, 7= med. gray, 8= light gray + */ + fun setHasLines(lineStyle: Int) { + val df = this.getDataFormatRec(true) + df!!.setHasLines(lineStyle) + } + + /** + * sets the color for this series + * NOTE: for PIE Charts, use setPieSliceColor + * + * @param clr String color hex String + * @see setPieSliceColor + */ + fun setColor(clr: String) { + val type = this.parentChart!!.chartType + if (type == ChartConstants.PIECHART) { + setPieSliceColor(clr, this.seriesIndex) + return + } + val df = this.getDataFormatRec(true) + df!!.setSeriesColor(clr) + } + + /** + * if the exact/correct color index is not used, the fill color comes out black + * + * @param clr + * @return + */ + private fun ensureCorrectColorInt(clr: Int): Int { + var clr = clr + // "The Chart color table is a subset of the full color table" + if (clr == FormatConstants.COLOR_RED) + clr = FormatConstants.COLOR_RED_CHART + if (clr == FormatConstants.COLOR_BLUE) + clr = FormatConstants.COLOR_BLUE_CHART + if (clr == FormatConstants.COLOR_YELLOW) + clr = FormatConstants.COLOR_YELLOW_CHART + if (clr == FormatConstants.COLOR_DARK_GREEN) + // no standard chart dark green ... + clr = FormatConstants.COLOR_GREEN + if (clr == FormatConstants.COLOR_DARK_YELLOW) + // no standard chart dark yellow ... + clr = FormatConstants.COLOR_YELLOW_CHART + if (clr == FormatConstants.COLOR_OLIVE_GREEN) + clr = FormatConstants.COLOR_OLIVE_GREEN_CHART + if (clr == FormatConstants.COLOR_WHITE) + clr = FormatConstants.COLOR_WHITE3 + return clr + + } + + /** + * sets the color for this series + * NOTE: for PIE Charts, use setPieSliceColor + * + * @param clr color int + * @see setPieSliceColor + */ + fun setColor(clr: Int) { + var clr = clr + clr = ensureCorrectColorInt(clr) + val type = this.parentChart!!.chartType + if (type == ChartConstants.PIECHART) { + setPieSliceColor(clr, this.seriesIndex) + return + } + val df = this.getDataFormatRec(true) + df!!.setSeriesColor(clr) + + } + + + /** + * sets the color of the desired pie slice + * + * @param clr color int + * @param slice 0-based pie slice number + */ + fun setPieSliceColor(clr: Int, slice: Int) { + var clr = clr + clr = ensureCorrectColorInt(clr) + val type = this.parentChart!!.chartType + if (type != ChartConstants.PIECHART) + return + val df = this.getDataFormatRecSlice(slice, true) + if (df != null) + df.setPieSliceColor(clr, slice) + else + Logger.logErr("Series.setPieSliceColor: unable to fnd pie slice record") + } + + + /** + * sets the color of the desired pie slice + * + * @param clr color int + * @param slice 0-based pie slice number + */ + fun setPieSliceColor(clr: String, slice: Int) { + val type = this.parentChart!!.chartType + if (type != ChartConstants.PIECHART) + return + val df = this.getDataFormatRecSlice(slice, true) + if (df != null) + df.setPieSliceColor(clr, slice) + else + Logger.logErr("Series.setPieSliceColor: unable to fnd pie slice record") + } + + + /** + * get the pie slice color in this pie chart + * + * @param slice + * @return color int + */ + fun getPieSliceColor(slice: Int): String? { + val type = this.parentChart!!.chartType + if (type != ChartConstants.PIECHART) + return null + val df = this.getDataFormatRecSlice(slice, false) + if (df != null) { + val bg = df.bgColor + if (bg != null) + return bg + } + // otherwise, color is automatic or default chart series color + return FormatHandle.colorToHexString(FormatHandle.COLORTABLE[automaticSeriesColors[slice]]) + } + + /** + * PIE data label information is contained within the 1st series only + *

                      TODO: not implemented yet + * return data label options as an int + *

                      can be one or more of: + *

                      SHOWVALUE= 0x1; + *

                      SHOWVALUEPERCENT= 0x2; + *

                      SHOWCATEGORYPERCENT= 0x4; + *

                      SMOOTHEDLINE= 0x8; + *

                      SHOWCATEGORYLABEL= 0x10; + *

                      SHOWBUBBLELABEL= 0x20; + *

                      SHOWSERIESLABEL= 0x40; + * + * @param defaultdl int default data label setting for overall chart + * @return int array of data labels for each pie slice + * @see AttachedLabel + */ + fun getDataLabelsPIE(defaultdl: Int): IntArray? { + var datalabels = 0 + val df = this.getDataFormatRec(false) + if (df != null) { + datalabels = datalabels or df.dataLabelTypeInt + } + return null + } + + /** + * add a dPt element (data point) for this series + * + * @param Sp + */ + fun addDpt(d: DPt) { + if (dPts == null) + dPts = ArrayList() + dPts!!.add(d) + } + + /** + * returns the val OOXML element that defines the values for the series values + * + * @param valstr either "val" or "yval" for scatter/bubble charts + * @return + */ + fun getValOOXML(valstr: String): StringBuffer { + val ooxml = StringBuffer() + ooxml.append("") + ooxml.append("\r\n") + + var seriesAi: Ai? = null + for (i in chartArr.indices) { + val br = chartArr[i] + if (br.opcode == XLSConstants.AI) { + val thisAi = br as Ai + if (thisAi.type == Ai.TYPE_VALS) { + seriesAi = thisAi + break + } + + } + } + + ooxml.append("") + ooxml.append("\r\n") // number reference + ooxml.append("" + OOXMLAdapter.stripNonAscii(seriesAi!!.toString()) + "") + ooxml.append("\r\n") // string range + // Need numCache for chart lines apparently + ooxml.append("") + ooxml.append("\r\n") // specifies the last data shown on the chart for a series + // formatCode == format pattern + ooxml.append("" + this.seriesFormatPattern + "") + val cr = CellRange(seriesAi.toString(), parentChart!!.wbh, false) + val ch = cr.getCells() + // ptCount == point count + ooxml.append(getValueRangeOOXML(ch)) + // pt * n == a Numeric Point each has a child, an idx attribute and an optional formatcode attribute + ooxml.append("") + ooxml.append("\r\n") // + + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + return ooxml + + } + + /** + * return the cat OOXML element used to define a series category + * + * @param cat string category cell range for the given series -- + * almost always the same for each series (except for scatter/bubble charts) + * @param catstr either "cat" or "xval" for scatter/bubble charts + * cat elements must contain string references + * xval contain numeric references + * @return + */ + fun getCatOOXML(cat: String, catstr: String): StringBuffer { + val ooxml = StringBuffer() + if ("" != cat) { // causes 1004 vb error upon Excel SAVE - BAXTER SAVE BUG + ooxml.append("") + ooxml.append("\r\n") // categories contain a string "formula" ref + string caches + if (catstr == "cat") + ooxml.append("") // string reference + else + ooxml.append("") // number reference + ooxml.append("\r\n") + ooxml.append("" + OOXMLAdapter.stripNonAscii(cat) + "") + ooxml.append("\r\n") + /* 20090211 KSC: if errors in referenced cells whole chart will error; best to avoid caching at all */ + if (catstr == "cat") + ooxml.append("") // string reference + else + ooxml.append("") // number reference + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + } else { + // TESTING-- remove when done + //Logger.logWarn("ChartHandle.getOOXML: null category found- skipping"); + } + return ooxml + } + + /** + * returns the bubbleSize OOXML element that defines the values for the series values + * + * @param isBubble3d true if it's a 3d bubble chart + * @return + */ + fun getBubbleOOXML(isBubble3d: Boolean): StringBuffer { + val ooxml = StringBuffer() + var bubbleAi: Ai? = null + for (i in chartArr.indices) { + val br = chartArr[i] + if (br.opcode == XLSConstants.AI) { + val thisAi = br as Ai + if (thisAi.type == Ai.TYPE_BUBBLES) { + bubbleAi = thisAi + break + } + } + } + + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") // number reference + ooxml.append("" + bubbleAi!!.toString() + "") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + try { + val cells = CellRange.getCells(bubbleAi.toString(), parentChart!!.wbh) + ooxml.append(getValueRangeOOXML(cells)) + ooxml.append("\r\n") + } catch (e: NumberFormatException) { + Logger.logErr("geteriesOOXML: Number format exception for Bubble Range: $bubbleAi") + } + + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + if (isBubble3d) + ooxml.append("") + ooxml.append("\r\n") + return ooxml + } + + /** + * returns the bubbleSize OOXML element that defines the values for the series values + * + * @param isBubble3d true if it's a 3d bubble chart + * @return + */ + fun getLegendOOXML(from2003: Boolean): StringBuffer { + val ooxml = StringBuffer() + val txt = this.legendText + val ai = this.legendAi + /* String txt= null; + try { + io.starter.formats.XLS.formulas.Ptg[] p= ai.getCellRangePtgs(); + txt= ((io.starter.formats.XLS.formulas.PtgRef)p[0]).getFormattedValue(); + } catch (Exception e) {} + try { + if (chartArr.size()>i+1) { + SeriesText st = (SeriesText)chartArr.get(i+1); + if (st!=null) + txt= st.toString(); + } + }catch(ClassCastException e) { + // couldn't find it! + } +*/ + + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") // string reference + if (ai != null) { + ooxml.append("" + OOXMLAdapter.stripNonAscii(ai.definition) + "") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("" + OOXMLAdapter.stripNonAscii(txt) + "") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + ooxml.append("") + ooxml.append("\r\n") + if (this.spPr != null) + ooxml.append(this.spPr!!.ooxml) + else if (from2003) { + val ss: SpPr + if (parentChart!!.chartType != ChartConstants.RADARCHART) + ss = SpPr("c", this.seriesColor.substring(1), 12700, "000000") + else + ss = SpPr("c", null, 25400, this.seriesColor.substring(1)) + ooxml.append(ss.ooxml) + } + } + return ooxml + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 7290108485347063887L + var SERIES_TYPE_NUMERIC = 1 + var SERIES_TYPE_STRING = 3 + + /** + * Create a series with all sub components. + * + * + * REcords are... + * AI + * SeriesText (optional?) + * AI + * AI + * AI + * DataFormat + * SerToCrt + * + * @param seriesData + * @return + */ + fun getPrototype(seriesRange: String, categoryRange: String, bubbleRange: String, legendRange: String?, legendText: String, chartobj: ChartType): Series { + val series = Series.prototype as Series? + val parentChart = chartobj.parentChart + val book = parentChart!!.workBook + var ai: Ai + // create Series text with Legend + if (legendRange != null) { + ai = Ai.getPrototype(Ai.AI_TYPE_LEGEND) as Ai + ai.workBook = book + ai.setSheet(parentChart.sheet) // 20080124 KSC: changeAiLocation from "A1" to "" + try { + ai.changeAiLocation("", legendRange/*(seriesText.getWorkSheetName() + "!" + seriesText.getCellAddress())*/) + } catch (e: Exception) { + } + // it's OK to not have a valid range + series!!.addChartRecord(ai) + val st = SeriesText.getPrototype(legendText) + ai.setSeriesText(st) + series.addChartRecord(st) + // 20091102 KSC: when adding series legend will not expand correctly if autopositioning is turned off + // [BugTracker 2844] + val l = chartobj.dataLegend + if (l != null) { + l.setAutoPosition(true) + l.incrementHeight(parentChart.coords[3].toFloat()) + } + + } else { + ai = Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND) as Ai + ai.workBook = book + ai.setSheet(parentChart.sheet) + series!!.addChartRecord(ai) + } + // parentChart.addAi(ai); + // create Series Value Ai + ai = Ai.getPrototype(Ai.AI_TYPE_SERIES) as Ai + ai.parentChart = parentChart + ai.workBook = book + ai.setSheet(parentChart.sheet) + try { + ai.changeAiLocation(ai.toString(), seriesRange) + } catch (e: Exception) { + // not necessary to report Logger.logErr("Error setting Series Range: '" + seriesRange + "'-" + e.toString()); + } + // it's OK to not have a valid range + series.addChartRecord(ai) + // parentChart.addAi(ai); + // create Category Ai + ai = Ai.getPrototype(Ai.AI_TYPE_CATEGORY) as Ai + ai.workBook = book + ai.setSheet(parentChart.sheet) + try { + ai.changeAiLocation(ai.toString(), categoryRange) + } catch (e: Exception) { + } + // it's OK to not have a valid range + series.addChartRecord(ai) + // parentChart.addAi(ai); + // create Bubble (undocumented) Ai + ai = Ai.getPrototype(Ai.AI_TYPE_BUBBLE) as Ai + ai.workBook = book + ai.setSheet(parentChart.sheet) + if (bubbleRange != "") { + try { + ai.changeAiLocation(ai.toString(), bubbleRange) + } catch (e: Exception) { + } + // it's OK to not have a valid range + ai.setRt(2) + /* 20120123 KSC: confused vs. per-series format verses format in chartformat rec ... + * if (((BubbleChart) parentChart.getChartObject()).is3d()) { + + }*/ + } + series.addChartRecord(ai) + var df: DataFormat? = null + df = DataFormat.prototype as DataFormat? + // update the data format correctly + val ser = parentChart.allSeries // get ALL series + var yi = -1 // Changed from 0 + var iss = -1 // "" + for (i in ser.indices) { + val srs = ser[i] as Series + val newYi = srs.seriesIndex + val newIss = srs.seriesNumber + if (newYi > yi) yi = newYi + if (newIss > iss) iss = newIss + } + yi++ + iss++ + df!!.setSeriesIndex(yi) + df.setSeriesNumber(iss) + if (chartobj.barShape != 0) { // must ensure each series contains proper shape records + df.setShape(chartobj.barShape) + } + series.addChartRecord(df) + val stc = SerToCrt.prototype as SerToCrt? + // get the correct chart index for the sertocrt + var vCount = 0 + var cCount = 0 + var bCount = 0 + if (ser.size > 0) { // 20070709 KSC: will be 0 if adding new blank chart + val s = ser[0] as Series + val cr = s.chartRecords + for (i in cr.indices) { + val b = cr[i] as BiffRec + if (b.opcode == XLSConstants.SERTOCRT) { + val stcc = b as SerToCrt + stc!!.data = stcc.data + } + } + // set the series level variables correctly + vCount = s.valueCount + cCount = s.categoryCount + bCount = s.bubbleCount + } + series.init() + // 20070711 KSC: vCount and cCount are via current range, no?? + try { + if (seriesRange.indexOf(":") != -1) { + val coords = io.starter.OpenXLS.ExcelTools.getRangeCoords(seriesRange) + vCount = coords[4] + } else { + vCount = 1 + } + series.valueCount = vCount + } catch (e: Exception) { + } + + try { + cCount = io.starter.OpenXLS.ExcelTools.getRangeCoords(categoryRange)[4] + series.categoryCount = cCount + } catch (e: Exception) { + } + + if (bubbleRange != "") { + try { + bCount = io.starter.OpenXLS.ExcelTools.getRangeCoords(bubbleRange)[4] + series.bubbleCount = bCount + } catch (e: Exception) { + } + + } + series.addChartRecord(stc) + return series + } + + val prototype: XLSRecord? + get() { + val s = Series() + s.opcode = XLSConstants.SERIES + s.data = s.PROTOTYPE_BYTES + s.init() + return s + } + + // Periwinkle Plum+ Ivory Light Turquoise Dark Purple Coral Ocean Blue Ice Blue {17, 25, 19, 27, 28, 22, 23, 24}; + // try these color int numbers instead: + // alternative explanation: chart fills 16-23, chart lines 24-31 + var automaticSeriesColors = intArrayOf(24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62) // also used in mapping default colors in AreaFormat, MarkerFormat, Frame ... + + /** + * generate the OOXML used to represent this set of value cells (element numRef) + * + * @param cells + * @return + */ + private fun getValueRangeOOXML(cells: Array?): String { + val ooxml = StringBuffer() + if (cells == null) return "" + ooxml.append("") + ooxml.append("\r\n") + for (j in cells.indices) { + ooxml.append("") + ooxml.append("\r\n") + if (cells[j].stringVal != "NaN") { + ooxml.append("" + cells[j].stringVal + "") + ooxml.append("\r\n") + } else { // appears that NaN is an invalid entry + ooxml.append("0") + ooxml.append("\r\n") + } + ooxml.append("") + ooxml.append("\r\n") + } + return ooxml.toString() + } + } + + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/charts/SeriesList.java b/src/main/java/io/starter/formats/XLS/charts/SeriesList.java deleted file mode 100644 index dc28ba9..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/SeriesList.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.toolkit.ByteTools; - -/** - * SeriesList: Specifies the Series in an Overlay Chart (0x1016) - *

                      - * bytes - 2 - nseries following - * 2 * nseries = An array of 2-byte unsigned integers, - * each of which specifies a one-based index of a Series record - * in the collection of Series records in the current chart sheet substream - */ -public class SeriesList extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7852050067799624402L; - int[] seriesmap = null; - - public void init() { - super.init(); - int nseries = ByteTools.readShort(this.getData()[0], this.getData()[1]); - seriesmap = new int[nseries]; - for (int i = 0; i < nseries; i++) { - int idx = ((i + 1) * 2); - seriesmap[i] = ByteTools.readShort(this.getData()[idx], this.getData()[idx + 1]); - } - } - - /** - * return the series mappings for the associated overlay chart - *
                      series mappings links the overlay chart to the absolute series number - * (determined by the actual order of the series in the chart array structure) - * - * @return - */ - public int[] getSeriesMappings() { - return seriesmap; - } - - /** - * set the series mappings for the associated overlay chart - *
                      series mappings links the overlay chart to the absolute series number - * (determined by the actual order of the series in the chart array structure) - * - * @param seriesmap - */ - public void setSeriesMappings(int[] smap) { - short nseries = (short) smap.length; - seriesmap = new int[nseries]; - byte[] data = new byte[(nseries + 1) * 2]; - byte[] b = ByteTools.shortToLEBytes(nseries); - data[0] = b[0]; - data[1] = b[1]; - for (int i = 0; i < nseries; i++) { - int idx = ((i + 1) * 2); - seriesmap[i] = smap[i]; - b = ByteTools.shortToLEBytes((short) smap[i]); - data[idx] = b[0]; - data[idx + 1] = b[1]; - } - setData(data); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/SeriesList.kt b/src/main/java/io/starter/formats/XLS/charts/SeriesList.kt new file mode 100644 index 0000000..3518d82 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/SeriesList.kt @@ -0,0 +1,88 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.toolkit.ByteTools + +/** + * **SeriesList: Specifies the Series in an Overlay Chart (0x1016)** + * + * + * bytes - 2 - nseries following + * 2 * nseries = An array of 2-byte unsigned integers, + * each of which specifies a one-based index of a Series record + * in the collection of Series records in the current chart sheet substream + */ +class SeriesList : GenericChartObject(), ChartObject { + internal var seriesmap: IntArray? = null + + /** + * return the series mappings for the associated overlay chart + *

                      series mappings links the overlay chart to the absolute series number + * (determined by the actual order of the series in the chart array structure) + * + * @return + */ + /** + * set the series mappings for the associated overlay chart + *

                      series mappings links the overlay chart to the absolute series number + * (determined by the actual order of the series in the chart array structure) + * + * @param seriesmap + */ + var seriesMappings: IntArray? + get() = seriesmap + set(smap) { + val nseries = smap.size.toShort() + seriesmap = IntArray(nseries) + val data = ByteArray((nseries + 1) * 2) + var b = ByteTools.shortToLEBytes(nseries) + data[0] = b[0] + data[1] = b[1] + for (i in 0 until nseries) { + val idx = (i + 1) * 2 + seriesmap[i] = smap[i] + b = ByteTools.shortToLEBytes(smap[i].toShort()) + data[idx] = b[0] + data[idx + 1] = b[1] + } + setData(data) + } + + override fun init() { + super.init() + val nseries = ByteTools.readShort(this.data!![0].toInt(), this.data!![1].toInt()).toInt() + seriesmap = IntArray(nseries) + for (i in 0 until nseries) { + val idx = (i + 1) * 2 + seriesmap[i] = ByteTools.readShort(this.data!![idx].toInt(), this.data!![idx + 1].toInt()).toInt() + } + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -7852050067799624402L + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/SeriesText.java b/src/main/java/io/starter/formats/XLS/charts/SeriesText.java deleted file mode 100644 index 7e76e82..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/SeriesText.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBookFactory; -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; - - -/** - * SeriesText: Chart Legend/Category/Value Text Definition (100Dh)
                      - *

                      - * This record defines the SeriesText data of a chart. - *

                      - * sdtX and sdtY fields determine data type (numeric and text) - *

                      - * cValx and cValy fields determine number of cells in series - *

                      - * Offset Name Size Contents - * -- - * 4 id 2 Text identifier (should be zero) - * 8 cch 2 length of String text - * 10 rgch 2 String text - * - * - * - * @see Chart - */ - -public final class SeriesText extends GenericChartObject implements ChartObject { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3794355940075116165L; - protected int id = -1, cch = -1; - private String text = ""; - - public void setText(String t) { - // create a new SeriesText value from the passed-in String - byte[] strbytes = null; - byte uni = 0x0; - int lent = 0; - try { - strbytes = t.getBytes(WorkBookFactory.UNICODEENCODING); - uni = 0x1; - lent = strbytes.length / 2; - } catch (Exception e) { - strbytes = t.getBytes(); - lent = strbytes.length; - } - byte[] newbytes = new byte[strbytes.length + 4]; -// byte[] lenbytes = ByteTools.shortToLEBytes((short)strbytes.length); - newbytes[0] = 0x0; - newbytes[1] = 0x0; - newbytes[2] = (byte) lent; - newbytes[3] = uni; - System.arraycopy(strbytes, 0, newbytes, 4, strbytes.length); - this.setData(newbytes); - this.text = t; - } - - public void init() { - super.init(); - //byte[] data = this.getData(); - int multi = 2; - if (this.getByteAt(3) == 0x0) multi = 1; - cch = (int) this.getByteAt(2) * multi; - if (cch < 0) cch *= -1; // strangely it can be negative... - try { - byte[] namebytes = this.getBytesAt(4, cch); - try { - text = new String(namebytes, WorkBookFactory.UNICODEENCODING); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("Unsupported Encoding error in SeriesText: " + e); - } - if ((DEBUGLEVEL > 10)) Logger.logInfo("Series Text Value: " + text); - - } catch (Exception ex) { - Logger.logWarn("SeriesText.init failed: " + ex); - } - //Logger.logInfo("Initialized SeriesText: "+ text); - } - - public String toString() { - return this.text; - } - - public static SeriesText getPrototype(String text) { - SeriesText st = new SeriesText(); - st.setOpcode(SERIESTEXT); - st.setData(st.PROTOTYPE_BYTES); - st.init(); - st.setText(text); - return st; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 7, 1, 74, 0, 97, 0, 110, 0, 117, 0, 97, 0, 114, 0, 121, 0}; -} diff --git a/src/main/java/io/starter/formats/XLS/charts/SeriesText.kt b/src/main/java/io/starter/formats/XLS/charts/SeriesText.kt new file mode 100644 index 0000000..e1730c5 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/SeriesText.kt @@ -0,0 +1,131 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBookFactory +import io.starter.toolkit.Logger + +import java.io.UnsupportedEncodingException + + +/** + * **SeriesText: Chart Legend/Category/Value Text Definition (100Dh)**

                      + * + * + * This record defines the SeriesText data of a chart. + * + * + * sdtX and sdtY fields determine data type (numeric and text) + * + * + * cValx and cValy fields determine number of cells in series + * + * + * Offset Name Size Contents + * -- + * 4 id 2 Text identifier (should be zero) + * 8 cch 2 length of String text + * 10 rgch 2 String text + * + * + * + * @see Chart + */ + +class SeriesText : GenericChartObject(), ChartObject { + protected var id = -1 + protected var cch = -1 + private var text = "" + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 7, 1, 74, 0, 97, 0, 110, 0, 117, 0, 97, 0, 114, 0, 121, 0) + + fun setText(t: String) { + // create a new SeriesText value from the passed-in String + var strbytes: ByteArray? = null + var uni: Byte = 0x0 + var lent = 0 + try { + strbytes = t.toByteArray(charset(WorkBookFactory.UNICODEENCODING)) + uni = 0x1 + lent = strbytes!!.size / 2 + } catch (e: Exception) { + strbytes = t.toByteArray() + lent = strbytes!!.size + } + + val newbytes = ByteArray(strbytes!!.size + 4) + // byte[] lenbytes = ByteTools.shortToLEBytes((short)strbytes.length); + newbytes[0] = 0x0 + newbytes[1] = 0x0 + newbytes[2] = lent.toByte() + newbytes[3] = uni + System.arraycopy(strbytes, 0, newbytes, 4, strbytes.size) + this.data = newbytes + this.text = t + } + + override fun init() { + super.init() + //byte[] data = this.getData(); + var multi = 2 + if (this.getByteAt(3).toInt() == 0x0) multi = 1 + cch = this.getByteAt(2).toInt() * multi + if (cch < 0) cch *= -1 // strangely it can be negative... + try { + val namebytes = this.getBytesAt(4, cch) + try { + text = String(namebytes!!, WorkBookFactory.UNICODEENCODING) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("Unsupported Encoding error in SeriesText: $e") + } + + if (DEBUGLEVEL > 10) Logger.logInfo("Series Text Value: $text") + + } catch (ex: Exception) { + Logger.logWarn("SeriesText.init failed: $ex") + } + + //Logger.logInfo("Initialized SeriesText: "+ text); + } + + override fun toString(): String { + return this.text + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -3794355940075116165L + + fun getPrototype(text: String): SeriesText { + val st = SeriesText() + st.opcode = XLSConstants.SERIESTEXT + st.data = st.PROTOTYPE_BYTES + st.init() + st.setText(text) + return st + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ShtProps.java b/src/main/java/io/starter/formats/XLS/charts/ShtProps.kt similarity index 77% rename from src/main/java/io/starter/formats/XLS/charts/ShtProps.java rename to src/main/java/io/starter/formats/XLS/charts/ShtProps.kt index 4f26591..7481893 100644 --- a/src/main/java/io/starter/formats/XLS/charts/ShtProps.java +++ b/src/main/java/io/starter/formats/XLS/charts/ShtProps.kt @@ -20,14 +20,16 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts /** - * ShtProps: Sheet Properties (0x1044) - *

                      + * **ShtProps: Sheet Properties (0x1044)** + * + * * 4 grbit 2 * 6 mdBlank 1 Empty cells plotted as: 0= not plotted, 1= 0, 2= interpolated - *

                      + * + * * grbit: * 0 0x1 fManSerAlloc 1= chart has been changed from default * 1 0x2 fPlotVisOnly 1= plot visible cells only @@ -35,13 +37,16 @@ * 3 0x8 fManPlotArea 0= default dimensions 1= use POS rec * 4 0x10 fAlwaysAutoPlotArea 1= user has modified chart enough that fManPlotArea should be set to 0 (!!!) */ -public class ShtProps extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5462571460161191942L; +class ShtProps : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } - public void init() { - super.init(); + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5462571460161191942L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SiIndex.java b/src/main/java/io/starter/formats/XLS/charts/SiIndex.kt similarity index 82% rename from src/main/java/io/starter/formats/XLS/charts/SiIndex.java rename to src/main/java/io/starter/formats/XLS/charts/SiIndex.kt index 46a2bff..f836028 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SiIndex.java +++ b/src/main/java/io/starter/formats/XLS/charts/SiIndex.kt @@ -20,15 +20,17 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /** - * SiIndex: Series Index (0x1065) - *

                      + * **SiIndex: Series Index (0x1065)** + * + * * Indicates the type of data contained in the Number records following it. - *

                      + * + * * 2 bytes * 0x0001 * Series values or vertical values (for scatter or bubble chart groups) @@ -36,7 +38,8 @@ * Category labels or horizontal values (for scatter or bubble chart groups) * 0x0003 * Bubble sizes - *

                      + * + * * specifies the beginning of a sequence of records that contains a cache of the data for the sequence of records that conforms to a specific AI rule (section 2.1.7.20.1) in the series (section 2.2.3.9) and error bars (section 2.2.3.13). * The relationship between the series and the chart data cache is specified as follows: * The first SIIndex record in the chart sheet substream, which MUST contain a numIndex field @@ -49,15 +52,18 @@ * corresponds to a series or error bar, where the zero-based index of the column, specified by the cell.col field in the Number, BoolErr, Blank, or Label records, * equals the zero-based index of the Series record in the collection of Series records that corresponds to the series or error bar. */ -public class SiIndex extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6810089722566956477L; - short type; +class SiIndex : GenericChartObject(), ChartObject { + internal var type: Short = 0 + + override fun init() { + super.init() + type = ByteTools.readShort(this.data!![0].toInt(), this.data!![1].toInt()) + } - public void init() { - super.init(); - type = ByteTools.readShort(this.getData()[0], this.getData()[1]); + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -6810089722566956477L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.java b/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.java deleted file mode 100644 index 9a3b418..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.HashMap; - -public class StackedAreaChart extends AreaChart { - - public StackedAreaChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - area = (Area) charttype; - } - - public boolean isStacked() { - return true; - } - - /** - * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double x = chartMetrics.get("x"); - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - double min = chartMetrics.get("min"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there - // y value for each point= h/YMAX - if (series.size() == 0) { - Logger.logErr("Area.getSVG: error in series"); - return ""; - } - StringBuffer svg = new StringBuffer(); - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - - double xfactor = 0, yfactor = 0; // - if (categories.length > 1) - xfactor = w / (categories.length - 1); // w/#categories - if (max != 0) - yfactor = h / max; // h/YMAXSCALE - - // first, calculate Area points- which are summed per series - int n = series.size(); - int nSeries = ((double[]) series.get(0)).length; - double[][] areapoints = new double[nSeries][n]; - for (int i = 0; i < n; i++) { - double[] seriesy = (double[]) series.get(i); - for (int j = 0; j < seriesy.length; j++) { - double yval = seriesy[j]; - areapoints[j][i] = yval + ((i > 0) ? areapoints[j][i - 1] : 0); - } - } - // for each series - for (int i = n - 1; i >= 0; i--) { // "paint" right to left - svg.append("\r\n"); - String points = ""; - double x1 = 0; - double y1 = 0; - String labels = null; - double[] curseries = (double[]) series.get(i); - for (int j = 0; j < curseries.length; j++) { - x1 = (x) + j * xfactor; - double yval = areapoints[j][i]; // current point - points += ((x) + (j) * xfactor) + "," + ((y + h) - (yval * yfactor)); - - if (j == 0) y1 = ((y + h) - (yval * yfactor)); // end point (==start point) for path statement below - points += " "; - // DATA LABELS - String l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0, i, legends, categories[j].toString()); - if (l != null) { - // if only category label, center over all series; anything else, position at data point - boolean showCategories = (dls[i] & AttachedLabel.CATEGORYLABEL) == AttachedLabel.CATEGORYLABEL; - boolean showValueLabel = (dls[i] & AttachedLabel.VALUELABEL) == AttachedLabel.VALUELABEL; - boolean showValue = (dls[i] & AttachedLabel.VALUE) == AttachedLabel.VALUE; - if (showCategories && !(showValue || showValueLabel) && j == 0) { // only 1 label, centered along category axis within area - //y1+= (seriesx[seriesx.length/2]/2)*yfactor; - double hh = (areapoints[areapoints.length / 2][i] * yfactor); - double yy = (y + h) - hh + 10; - if (labels == null) labels = ""; - labels = "" + l + "\r\n"; - } else if (showValue || showValueLabel) { // labels at each data point - if (labels == null) labels = ""; - double yy = (((y + h) - ((yval - (curseries[j] * .5)) * yfactor))); - labels += "" + l + "\r\n"; - } - } - } - // pointsends connects up area to beginning - double x0 = x; - String pointsend = x1 + "," + (y + h) + - " " + x0 + "," + (y + h) + - " " + x0 + "," + y1; - svg.append("\r\n"); - -/* john took out - - // do twice to make slightly thicker - svg.append("\r\n"); - // do twice to make slightly thicker - svg.append("\r\n");*/ - // Now print data labels, if any - if (labels != null) svg.append(labels); - svg.append("\r\n"); - } - return svg.toString(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.kt b/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.kt new file mode 100644 index 0000000..e30468e --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/StackedAreaChart.kt @@ -0,0 +1,146 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.HashMap + +class StackedAreaChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : AreaChart(charttype, cf, wb) { + + override var isStacked: Boolean + get() = true + set(value: Boolean) { + super.isStacked = value + } + + init { + area = charttype as Area + } + + /** + * returns SVG to represent the actual chart object (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val x = chartMetrics["x"] + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val min = chartMetrics["min"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + // x value for each point= w/(ncategories + 1) 1st one is xv*2 then increases from there + // y value for each point= h/YMAX + if (series!!.size == 0) { + Logger.logErr("Area.getSVG: error in series") + return "" + } + val svg = StringBuffer() + val dls = dataLabelInts // get array of data labels (can be specific per series ...) + + var xfactor = 0.0 + var yfactor = 0.0 // + if (categories!!.size > 1) + xfactor = w / (categories.size - 1) // w/#categories + if (max != 0.0) + yfactor = h / max // h/YMAXSCALE + + // first, calculate Area points- which are summed per series + val n = series.size + val nSeries = (series[0] as DoubleArray).size + val areapoints = Array(nSeries) { DoubleArray(n) } + for (i in 0 until n) { + val seriesy = series[i] as DoubleArray + for (j in seriesy.indices) { + val yval = seriesy[j] + areapoints[j][i] = yval + if (i > 0) areapoints[j][i - 1] else 0 + } + } + // for each series + for (i in n - 1 downTo 0) { // "paint" right to left + svg.append("\r\n") + var points = "" + var x1 = 0.0 + var y1 = 0.0 + var labels: String? = null + val curseries = series[i] as DoubleArray + for (j in curseries.indices) { + x1 = x + j * xfactor + val yval = areapoints[j][i] // current point + points += x + j * xfactor + "," + (y + h - yval * yfactor) + + if (j == 0) y1 = y + h - yval * yfactor // end point (==start point) for path statement below + points += " " + // DATA LABELS + val l = getSVGDataLabels(dls, axisMetrics, curseries[j], 0.0, i, legends, categories[j].toString()) + if (l != null) { + // if only category label, center over all series; anything else, position at data point + val showCategories = dls[i] and AttachedLabel.CATEGORYLABEL == AttachedLabel.CATEGORYLABEL + val showValueLabel = dls[i] and AttachedLabel.VALUELABEL == AttachedLabel.VALUELABEL + val showValue = dls[i] and AttachedLabel.VALUE == AttachedLabel.VALUE + if (showCategories && !(showValue || showValueLabel) && j == 0) { // only 1 label, centered along category axis within area + //y1+= (seriesx[seriesx.length/2]/2)*yfactor; + val hh = areapoints[areapoints.size / 2][i] * yfactor + val yy = y + h - hh + 10 + if (labels == null) labels = "" + labels = "" + l + "\r\n" + } else if (showValue || showValueLabel) { // labels at each data point + if (labels == null) labels = "" + val yy = y + h - (yval - curseries[j] * .5) * yfactor + labels += "$l\r\n" + } + } + } + // pointsends connects up area to beginning + val pointsend = x1.toString() + "," + (y + h) + + " " + x + "," + (y + h) + + " " + x + "," + y1 + svg.append("\r\n") + + /* john took out + + // do twice to make slightly thicker + svg.append("\r\n"); + // do twice to make slightly thicker + svg.append("\r\n");*/ + // Now print data labels, if any + if (labels != null) svg.append(labels) + svg.append("\r\n") + } + return svg.toString() + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/StackedColumn.java b/src/main/java/io/starter/formats/XLS/charts/StackedColumn.java deleted file mode 100644 index 26b5ef8..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/StackedColumn.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.HashMap; - -public class StackedColumn extends ColChart { - int defaultShape = 0; //???? - - public StackedColumn(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - col = (Bar) charttype; - } - - public boolean isStacked() { - return true; - } - - /** - * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) - * - * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max - * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... - * @param categories - * @param series arraylist of double[] series values - * @param seriescolors int[] of series or bar colors color ints - * @return String svg - */ - public String getSVG(HashMap chartMetrics, HashMap axisMetrics, ChartSeries s) { - double x = chartMetrics.get("x"); // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed - double y = chartMetrics.get("y"); - double w = chartMetrics.get("w"); - double h = chartMetrics.get("h"); - double max = chartMetrics.get("max"); - Object[] categories = s.getCategories(); - ArrayList series = s.getSeriesValues(); - String[] seriescolors = s.getSeriesBarColors(); - String[] legends = s.getLegends(); - if (series.size() == 0) { - Logger.logErr("Bar.getSVG: error in series"); - return ""; - } - /* - * TODO: Stacked vs 100% Stacked - */ - StringBuffer svg = new StringBuffer(); - int[] dls = getDataLabelInts(); // get array of data labels (can be specific per series ...) - - double barw = 0, yfactor = 0; - if (categories.length > 0) - barw = w / (categories.length * 2); // w/#categories (only 1 column per series) - if (max != 0) - yfactor = h / max; // h/YMAXSCALE - - double[] totalperseries = null; - boolean f100 = col.is100Percent(); - if (f100) { //FOR 100% STACKED - // first, calculate points- which are summed per series - int n = series.size(); - int nSeries = ((double[]) series.get(0)).length; - totalperseries = new double[nSeries]; - for (int i = 0; i < n; i++) { - double[] seriesy = (double[]) series.get(i); - for (int j = 0; j < seriesy.length; j++) { - double yval = seriesy[j]; - totalperseries[j] = yval + totalperseries[j]; - } - } - } - // for each series - ONE COLUMN per series - double[] previousY = new double[0]; - for (int i = 0; i < series.size(); i++) { - svg.append("\r\n"); - double[] curseries = (double[]) series.get(i); // for each data point - stacked on series column - String[] curranges = (String[]) s.getSeriesRanges().get(i); - double xx, yy = y + h; // origin - if (i == 0) { - previousY = new double[curseries.length]; - for (int j = 0; j < previousY.length; j++) - previousY[j] = yy; // origin - } - double barh; - for (int j = 0; j < curseries.length; j++) { - xx = (x) + (j * barw * 2) + barw / 2; - if (previousY.length > j) // should - yy = previousY[j]; - if (f100) - barh = ((curseries[j] / totalperseries[j]) * (h - 10)); // height of current point as a percentage of total points per series - else - barh = (curseries[j] * yfactor); // height of current point - svg.append(""); - //TODO: DATA LABELS - // Now print data labels, if any - //if (labels!=null) svg.append(labels); - if (previousY.length > j) // should - previousY[j] = yy - barh; - } - svg.append("\r\n"); - } - return svg.toString(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/StackedColumn.kt b/src/main/java/io/starter/formats/XLS/charts/StackedColumn.kt new file mode 100644 index 0000000..3408f09 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/StackedColumn.kt @@ -0,0 +1,132 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.HashMap + +class StackedColumn(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ColChart(charttype, cf, wb) { + internal var defaultShape = 0 //???? + + override var isStacked: Boolean + get() = true + set(value: Boolean) { + super.isStacked = value + } + + init { + col = charttype as Bar + } + + /** + * returns SVG to represent the actual chart object i.e. the representation of the series data in the particular format (BAR, LINE, AREA, etc.) + * + * @param chartMetrics maps chart coords in pixels x, y, w, h, canvasw, canvash, min, max + * @param axisMetrics maps specific axis options such as xAxisReversed, xPattern ... + * @param categories + * @param series arraylist of double[] series values + * @param seriescolors int[] of series or bar colors color ints + * @return String svg + */ + override fun getSVG(chartMetrics: HashMap, axisMetrics: HashMap, s: ChartSeries): String { + val x = chartMetrics["x"] // + (!yAxisReversed?0:w); // x is constant at x origin unless reversed + val y = chartMetrics["y"] + val w = chartMetrics["w"] + val h = chartMetrics["h"] + val max = chartMetrics["max"] + val categories = s.getCategories() + val series = s.seriesValues + val seriescolors = s.seriesBarColors + val legends = s.getLegends() + if (series!!.size == 0) { + Logger.logErr("Bar.getSVG: error in series") + return "" + } + /* + * TODO: Stacked vs 100% Stacked + */ + val svg = StringBuffer() + val dls = dataLabelInts // get array of data labels (can be specific per series ...) + + var barw = 0.0 + var yfactor = 0.0 + if (categories!!.size > 0) + barw = w / (categories.size * 2) // w/#categories (only 1 column per series) + if (max != 0.0) + yfactor = h / max // h/YMAXSCALE + + var totalperseries: DoubleArray? = null + val f100 = col!!.is100Percent + if (f100) { //FOR 100% STACKED + // first, calculate points- which are summed per series + val n = series.size + val nSeries = (series[0] as DoubleArray).size + totalperseries = DoubleArray(nSeries) + for (i in 0 until n) { + val seriesy = series[i] as DoubleArray + for (j in seriesy.indices) { + val yval = seriesy[j] + totalperseries[j] = yval + totalperseries[j] + } + } + } + // for each series - ONE COLUMN per series + var previousY = DoubleArray(0) + for (i in series.indices) { + svg.append("\r\n") + val curseries = series[i] as DoubleArray // for each data point - stacked on series column + val curranges = s.seriesRanges!![i] as Array + var xx: Double + var yy = y + h // origin + if (i == 0) { + previousY = DoubleArray(curseries.size) + for (j in previousY.indices) + previousY[j] = yy // origin + } + var barh: Double + for (j in curseries.indices) { + xx = x + j.toDouble() * barw * 2.0 + barw / 2 + if (previousY.size > j) + // should + yy = previousY[j] + if (f100) + barh = curseries[j] / totalperseries!![j] * (h - 10) // height of current point as a percentage of total points per series + else + barh = curseries[j] * yfactor // height of current point + svg.append("") + //TODO: DATA LABELS + // Now print data labels, if any + //if (labels!=null) svg.append(labels); + if (previousY.size > j) + // should + previousY[j] = yy - barh + } + svg.append("\r\n") + } + return svg.toString() + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/StartBlock.java b/src/main/java/io/starter/formats/XLS/charts/StartBlock.kt similarity index 79% rename from src/main/java/io/starter/formats/XLS/charts/StartBlock.java rename to src/main/java/io/starter/formats/XLS/charts/StartBlock.kt index 8aef41b..e4c167c 100644 --- a/src/main/java/io/starter/formats/XLS/charts/StartBlock.java +++ b/src/main/java/io/starter/formats/XLS/charts/StartBlock.kt @@ -20,20 +20,21 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools /** - * STARTBLOCK: Chart Future Record Type Start Block (852h) + * **STARTBLOCK: Chart Future Record Type Start Block (852h)** * Introduced in Excel 9 (2000) this BIFF record is an FRT record for Charts. * Indicates the start of an object's scope for Pre-Excel 9 objects. These * records are used to push a chart element scope onto the parent element stack. * This stack is used to determine the containing element for records that are * used by more than one type of element. The FRAME record, for instance, is used * by at least four different elements. - *

                      + * + * * The STARTBLOCK/ENDBLOCK records are used for pre-Excel 9 elements with child records * (i.e., a record for the element followed by a BEGIN/END block for the child records.) * STARTBLOCK/ENDBLOCK are only written to enclose one or more child CFRT records and can @@ -41,7 +42,8 @@ * These records allow Excel 9 or later to determine the proper parent element even after * Excel 97 moves CFRTs to the end of the stream. Since these records are CFRTs, * they will stay with and keep contained any child CFRTs. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0852h @@ -50,12 +52,14 @@ * 10 iObjectContext 2 See table below * 12 iObjectInstance1 2 See table below * 14 iObjectInstance2 2 See table below - *

                      + * + * * The following table describes the meaning of each set of possible values for iObjectKind, * iObjectContext, iObjectInstance1, iObjectInstance2. In some cases, these fields are indexed, * and the indexes are described in the documentation for the parent rt. The table also lists * whether the STARTBLOCK/ENDBLOCK or STARTOBJECT/ENDOBJECT rts are used, and the parent rt. - *

                      + * + * * iObjectKind iObjectContext iObjectInstance1 iObjectInstance2 Class rt Description * 0 0 0 0 BLOCK AXIS PARENT Primary axis group * 0 0 1 0 BLOCK AXIS PARENT Secondary axis group @@ -98,38 +102,44 @@ * 16 0 0 0 OBJECT YMULT Axis multiplier * 17 0 verChart 0 OBJECT FRTFONT LIST Fonts */ -public class StartBlock extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1895593244077899106L; - short iObjectKind = 0; - public static final int CHART = 13, AXIS = 0, CHARTFORMAT = 5; +class StartBlock : GenericChartObject(), ChartObject { + internal var iObjectKind: Short = 0 - public void init() { - super.init(); - iObjectKind = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - } // iObjectKind= 13 or 0 or 5 + private val PROTOTYPE_BYTES = byteArrayOf(82, 8, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0) - private byte[] PROTOTYPE_BYTES = new byte[]{82, 8, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0}; + override fun init() { + super.init() + iObjectKind = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + } // iObjectKind= 13 or 0 or 5 - public static XLSRecord getPrototype() { - StartBlock sb = new StartBlock(); - sb.setOpcode(STARTBLOCK); - sb.setData(sb.PROTOTYPE_BYTES); - sb.init(); - return sb; + fun setObjectKind(i: Int) { + iObjectKind = i.toShort() + updateRecord() } - public void setObjectKind(int i) { - iObjectKind = (short) i; - updateRecord(); + private fun updateRecord() { + val b = ByteTools.shortToLEBytes(iObjectKind) + this.data[4] = b[0] + this.data[5] = b[1] } - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(iObjectKind); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 1895593244077899106L + val CHART = 13 + val AXIS = 0 + val CHARTFORMAT = 5 + + val prototype: XLSRecord? + get() { + val sb = StartBlock() + sb.opcode = XLSConstants.STARTBLOCK + sb.data = sb.PROTOTYPE_BYTES + sb.init() + return sb + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/StartObject.java b/src/main/java/io/starter/formats/XLS/charts/StartObject.kt similarity index 66% rename from src/main/java/io/starter/formats/XLS/charts/StartObject.java rename to src/main/java/io/starter/formats/XLS/charts/StartObject.kt index 4a3f22d..7789f43 100644 --- a/src/main/java/io/starter/formats/XLS/charts/StartObject.java +++ b/src/main/java/io/starter/formats/XLS/charts/StartObject.kt @@ -20,16 +20,17 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * STARTOBJECT: Chart Future Record Type Start Object (854h) + * **STARTOBJECT: Chart Future Record Type Start Object (854h)** * Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. * The STARTOBJECT/ENDOBJECT records are used for Excel 9+ elements with child * records instead of the BEGIN/END records. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0854h @@ -39,27 +40,30 @@ * 12 iObjectInstance1 2 Which one from a collection, object-specific * 14 iObjectInstance2 2 Which one from a collection, object-specific. */ -public class StartObject extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8933906882043608941L; +class StartObject : GenericChartObject(), ChartObject { - public void init() { - super.init(); + private val PROTOTYPE_BYTES = byteArrayOf(84, 8, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0) + + override fun init() { + super.init() } // iObjectKind, 18 must be Chart, or maybe Chart label ... - private byte[] PROTOTYPE_BYTES = new byte[]{84, 8, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0}; + private fun updateRecord() {} - public static XLSRecord getPrototype() { - StartObject sb = new StartObject(); - sb.setOpcode(STARTOBJECT); - sb.setData(sb.PROTOTYPE_BYTES); - sb.init(); - return sb; - } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -8933906882043608941L - private void updateRecord() { + val prototype: XLSRecord? + get() { + val sb = StartObject() + sb.opcode = XLSConstants.STARTOBJECT + sb.data = sb.PROTOTYPE_BYTES + sb.init() + return sb + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/StockChart.java b/src/main/java/io/starter/formats/XLS/charts/StockChart.kt similarity index 53% rename from src/main/java/io/starter/formats/XLS/charts/StockChart.java rename to src/main/java/io/starter/formats/XLS/charts/StockChart.kt index fce0f31..4b83dc8 100644 --- a/src/main/java/io/starter/formats/XLS/charts/StockChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/StockChart.kt @@ -20,56 +20,55 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.WorkBook; +import io.starter.formats.XLS.WorkBook -public class StockChart extends LineChart { +class StockChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : LineChart(charttype, cf, wb) { - public StockChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - defaultShape = STOCKCHART; + init { + defaultShape = ChartConstants.STOCKCHART } /** * gets the chart-type specific ooxml representation: * * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) // chart data labels, if any //TODO: FINISH dlbls //cooxml.append(getDataLabelsOOXML(cf)); //dropLines - ChartLine cl = this.cf.getChartLinesRec(ChartLine.TYPE_DROPLINE); + var cl = this.cf!!.getChartLinesRec(ChartLine.TYPE_DROPLINE.toInt()) if (cl != null) - cooxml.append(cl.getOOXML()); + cooxml.append(cl.ooxml) // hiLowLines - cl = this.cf.getChartLinesRec(ChartLine.TYPE_HILOWLINE); + cl = this.cf!!.getChartLinesRec(ChartLine.TYPE_HILOWLINE.toInt()) if (cl != null) - cooxml.append(cl.getOOXML()); + cooxml.append(cl.ooxml) // upDownBars - cooxml.append(cf.getUpDownBarOOXML()); + cooxml.append(cf!!.upDownBarOOXML) // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") - return cooxml; + return cooxml } } diff --git a/src/main/java/io/starter/formats/XLS/charts/Surface.java b/src/main/java/io/starter/formats/XLS/charts/Surface.java deleted file mode 100644 index 7155cde..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Surface.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Surface: Chart Group is a Surface Chart Group (0x103f) - *

                      - * 4 grbit 2 - *

                      - * 0 0x1 fFillSurface 1= chart contains color fill for surface - * 1 0x2 f3DPhongShade 1= this surface chart has shading - */ -public class Surface extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3243029185139320374L; - private short grbit = 0; - private boolean fFillSurface = true; - private boolean f3DPhoneShade = false; - private boolean is3d = false; /* since all surface charts contain a 3d record, must store 3d setting separately */ - - public void init() { - super.init(); - grbit = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - fFillSurface = (grbit & 0x1) == 0x1; - f3DPhoneShade = (grbit & 0x2) == 0x2; - chartType = ChartConstants.SURFACECHART; - } - - // 20070703 KSC: - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - public static XLSRecord getPrototype() { - Surface b = new Surface(); - b.setOpcode(SURFACE); - b.setData(b.PROTOTYPE_BYTES); - b.init(); - return b; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0}; - - /** - * returns true if surface chart is wireframe, false if filled - * - * @return - */ - public boolean isWireframe() { - return !fFillSurface; - } - - /** - * sets this surface chart to wireframe (true) or filled(false) - */ - public void setIsWireframe(boolean wireframe) { - fFillSurface = !wireframe; - grbit = ByteTools.updateGrBit(grbit, fFillSurface, 0); - } - - - /** - * surface charts always contain a 3d record so must determine if 3d separately - */ - public boolean getIs3d() { - return is3d; - } - - /** - * set if this surface chart is "truly" 3d as all surface-type charts contain a 3d record - * - * @param is3d - */ - public void setIs3d(boolean is3d) { - this.is3d = is3d; - } - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (fFillSurface) - sb.append(" ColorFill=\"true\""); - if (f3DPhoneShade) - sb.append(" Shading=\"true\""); - return sb.toString(); - } - - /** - * Handle setting options from XML in a generic manner - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("ColorFill")) { - fFillSurface = val.equals("true"); - grbit = ByteTools.updateGrBit(grbit, fFillSurface, 0); - bHandled = true; - } else if (op.equalsIgnoreCase("Shading")) { - f3DPhoneShade = val.equals("true"); - grbit = ByteTools.updateGrBit(grbit, f3DPhoneShade, 1); - bHandled = true; - } - if (bHandled) - updateRecord(); - return bHandled; - } - - /** - * return the (dojo) type JSON for this Chart Object - * - * @return - */ - public JSONObject getTypeJSON() throws JSONException { - JSONObject typeJSON = new JSONObject(); - String dojoType; - if (!this.isStacked()) - dojoType = "Areas"; - else - dojoType = "StackedAreas"; - typeJSON.put("type", dojoType); - return typeJSON; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Surface.kt b/src/main/java/io/starter/formats/XLS/charts/Surface.kt new file mode 100644 index 0000000..ed24ff9 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Surface.kt @@ -0,0 +1,152 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools +import org.json.JSONException +import org.json.JSONObject + +/** + * **Surface: Chart Group is a Surface Chart Group (0x103f) ** + * + * + * 4 grbit 2 + * + * + * 0 0x1 fFillSurface 1= chart contains color fill for surface + * 1 0x2 f3DPhongShade 1= this surface chart has shading + */ +class Surface : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var fFillSurface = true + private var f3DPhoneShade = false + /** + * surface charts always contain a 3d record so must determine if 3d separately + */ + /** + * set if this surface chart is "truly" 3d as all surface-type charts contain a 3d record + * + * @param is3d + */ + var is3d = false /* since all surface charts contain a 3d record, must store 3d setting separately */ + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0) + + /** + * returns true if surface chart is wireframe, false if filled + * + * @return + */ + /** + * sets this surface chart to wireframe (true) or filled(false) + */ + var isWireframe: Boolean + get() = !fFillSurface + set(wireframe) { + fFillSurface = !wireframe + grbit = ByteTools.updateGrBit(grbit, fFillSurface, 0) + } + + /** + * @return String XML representation of this chart-type's options + */ + override val optionsXML: String + get() { + val sb = StringBuffer() + if (fFillSurface) + sb.append(" ColorFill=\"true\"") + if (f3DPhoneShade) + sb.append(" Shading=\"true\"") + return sb.toString() + } + + /** + * return the (dojo) type JSON for this Chart Object + * + * @return + */ + val typeJSON: JSONObject + @Throws(JSONException::class) + get() { + val typeJSON = JSONObject() + val dojoType: String + if (!this.isStacked) + dojoType = "Areas" + else + dojoType = "StackedAreas" + typeJSON.put("type", dojoType) + return typeJSON + } + + override fun init() { + super.init() + grbit = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + fFillSurface = grbit and 0x1 == 0x1 + f3DPhoneShade = grbit and 0x2 == 0x2 + chartType = ChartConstants.SURFACECHART + } + + // 20070703 KSC: + private fun updateRecord() { + val b = ByteTools.shortToLEBytes(grbit) + this.data[0] = b[0] + this.data[1] = b[1] + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("ColorFill", ignoreCase = true)) { + fFillSurface = `val` == "true" + grbit = ByteTools.updateGrBit(grbit, fFillSurface, 0) + bHandled = true + } else if (op.equals("Shading", ignoreCase = true)) { + f3DPhoneShade = `val` == "true" + grbit = ByteTools.updateGrBit(grbit, f3DPhoneShade, 1) + bHandled = true + } + if (bHandled) + updateRecord() + return bHandled + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -3243029185139320374L + + val prototype: XLSRecord? + get() { + val b = Surface() + b.opcode = XLSConstants.SURFACE + b.data = b.PROTOTYPE_BYTES + b.init() + return b + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Surface3DChart.java b/src/main/java/io/starter/formats/XLS/charts/Surface3DChart.kt similarity index 53% rename from src/main/java/io/starter/formats/XLS/charts/Surface3DChart.java rename to src/main/java/io/starter/formats/XLS/charts/Surface3DChart.kt index 9d4f0c6..75b5b62 100644 --- a/src/main/java/io/starter/formats/XLS/charts/Surface3DChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/Surface3DChart.kt @@ -20,46 +20,45 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.WorkBook; +import io.starter.formats.XLS.WorkBook -public class Surface3DChart extends SurfaceChart { - public Surface3DChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - surface = (Surface) charttype; +class Surface3DChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : SurfaceChart(charttype, cf, wb) { + init { + surface = charttype as Surface } /** * gets the chart-type specific ooxml representation: * * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") // wireframe // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) // bandfmts // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") - return cooxml; + return cooxml } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SurfaceChart.java b/src/main/java/io/starter/formats/XLS/charts/SurfaceChart.kt similarity index 50% rename from src/main/java/io/starter/formats/XLS/charts/SurfaceChart.java rename to src/main/java/io/starter/formats/XLS/charts/SurfaceChart.kt index 6112f94..ca667fc 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SurfaceChart.java +++ b/src/main/java/io/starter/formats/XLS/charts/SurfaceChart.kt @@ -20,49 +20,48 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.WorkBook; +import io.starter.formats.XLS.WorkBook -public class SurfaceChart extends ChartType { - protected Surface surface = null; +open class SurfaceChart(charttype: GenericChartObject, cf: ChartFormat, wb: WorkBook) : ChartType(charttype, cf, wb) { + protected var surface: Surface? = null - public SurfaceChart(GenericChartObject charttype, ChartFormat cf, WorkBook wb) { - super(charttype, cf, wb); - surface = (Surface) charttype; + init { + surface = charttype as Surface } /** * gets the chart-type specific ooxml representation: * * @return - */ - public StringBuffer getOOXML(String catAxisId, String valAxisId, String serAxisId) { - StringBuffer cooxml = new StringBuffer(); + */ + override fun getOOXML(catAxisId: String, valAxisId: String, serAxisId: String): StringBuffer? { + val cooxml = StringBuffer() // chart type: contains chart options and series data - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") // wireframe - if (surface.isWireframe()) - cooxml.append(""); + if (surface!!.isWireframe) + cooxml.append("") // *** Series Data: ser, cat, val for most chart types - cooxml.append(this.getParentChart().getChartSeries().getOOXML(this.getChartType(), false, 0)); + cooxml.append(this.parentChart!!.chartSeries.getOOXML(this.chartType, false, 0)) // bandfmts // axis ids - unsigned int strings - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") + cooxml.append("") + cooxml.append("\r\n") - cooxml.append(""); - cooxml.append("\r\n"); + cooxml.append("") + cooxml.append("\r\n") - return cooxml; + return cooxml } } diff --git a/src/main/java/io/starter/formats/XLS/charts/SxViewLink.java b/src/main/java/io/starter/formats/XLS/charts/SxViewLink.kt similarity index 64% rename from src/main/java/io/starter/formats/XLS/charts/SxViewLink.java rename to src/main/java/io/starter/formats/XLS/charts/SxViewLink.kt index d04434b..9999af7 100644 --- a/src/main/java/io/starter/formats/XLS/charts/SxViewLink.java +++ b/src/main/java/io/starter/formats/XLS/charts/SxViewLink.kt @@ -20,45 +20,49 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts -import io.starter.formats.XLS.XLSRecord; +import io.starter.formats.XLS.XLSRecord /** - * SXVIEWLINK: Chart PivotTable Name (858h) + * **SXVIEWLINK: Chart PivotTable Name (858h)** * Introduced in Excel 9 (2000), this BIFF record is an FRT record * for Charts. This record stores the name of the source PivotTable * when this chart is a PivotChart. New for Excel 9, PivotCharts are * charts based on PivotTables. - *

                      + * + * * Record Data * Offset Field Name Size Contents * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0858h * 6 grbitFrt 2 FRT flags; must be zero * 8 brst var String containing name of PivotTable */ -public class SxViewLink extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5291795207491688189L; - - public void init() { - super.init(); - } +class SxViewLink : GenericChartObject(), ChartObject { // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[]{}; + private val PROTOTYPE_BYTES = byteArrayOf() - public static XLSRecord getPrototype() { - SxViewLink sx = new SxViewLink(); - sx.setOpcode(SXVIEWLINK); - sx.setData(sx.PROTOTYPE_BYTES); - sx.init(); - return sx; + override fun init() { + super.init() } - private void updateRecord() { + private fun updateRecord() {} + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -5291795207491688189L + + val prototype: XLSRecord? + get() { + val sx = SxViewLink() + sx.opcode = XLSConstants.SXVIEWLINK + sx.data = sx.PROTOTYPE_BYTES + sx.init() + return sx + } } } diff --git a/src/main/java/io/starter/formats/XLS/charts/TextDisp.java b/src/main/java/io/starter/formats/XLS/charts/TextDisp.java deleted file mode 100644 index be5b975..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/TextDisp.java +++ /dev/null @@ -1,617 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.formats.XLS.BiffRec; -import io.starter.formats.XLS.Font; -import io.starter.formats.XLS.WorkBook; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -import java.util.HashMap; - -/** - * TextDisp: (Text) Defines Display of Text Fields (0x1025) - *

                      - * Used in conjunction with several other records to define alignment, color, position, - * size, and so on, of text fiedls that appear on the chart. - * The fields in this record have meaning according to the TEXT record's parent - * (CHART, LEGEND or DEFAULTTEXT) - *

                      - * 4 at 1 Horizontal alignment (1=left, 2= center, 3=bottom, 4= justify, 7=distributed) - * 5 vat 1 Vertical alignment (1= top, 2= center, 3= bottom, 4= justify, 7=distributed) - * 6 wBkgMode 2 Display mode of bg 1= transparent, 2= opaque - * 8 rgbText 4 Color of the text - * LongRGB Structure: red (1 byte): An unsigned integer that specifies the relative intensity of red. - * green (1 byte): An unsigned integer that specifies the relative intensity of green. - * blue (1 byte): An unsigned integer that specifies the relative intensity of blue. - * reserved (1 byte): MUST be zero, and MUST be ignored. - * 12 x 4 in SPRC (see Pos); ignore if preceded by DefaultText OR when followed by Pos - * 16 y 4 in SPRC (see Pos); "" - * 20 dx 4 A signed integer that specifies the horizontal size of the text, relative to the chart area in SPRC. - * This value MUST be ignored when this record is followed by a Pos record; - * otherwise MUST be greater than or equal to 0 and less than or equal to 32767. SHOULD be less than or equal to 4000. - * 24 dy 4 A signed integer that specifies the vertical size of the text, relative to the chart area in SPRC. - * This value MUST be ignored when this record is followed by a Pos record; - * otherwise MUST be greater than or equal to 0 and less than or equal to 32767. SHOULD<128> be less than or equal to 4000. - * 28 grbit 2 Option flags - * 30 icvText 2 Index to color value. icv structure. - * 32 dlp 2 (4 bits): An unsigned integer that specifies the data label positioning of the text, relative to the graph object item the text is attached to. - * For all data label text fields, MUST be a value from the following table: - * Data label position Value Value for chart group type - * Auto 0x0 Pie chart group - * Right 0x0 Line, Bubble, or Scatter chart group - * Outside 0x0 Bar or Column chart group with fStacked equal to 0 - * Center 0x0 Bar or Column chart group with fStacked equal to 1 - * Outside End 0x1 Bar, Column, or Pie chart group - * Inside End 0x2 Bar, Column, or Pie chart group - * Center 0x3 Bar, Column, Line, Bubble, Scatter, or Pie chart group - * Inside Base 0x4 Bar or Column chart group - * Above 0x5 Line, Bubble, or Scatter chart group - *

                      - * Below 0x6 Line, Bubble, or Scatter chart group - * Left 0x7 Line, Bubble, or Scatter chart group - * Right 0x8 Line, Bubble, or Scatter chart group - * Auto 0x9 Pie chart group - * Moved by user 0xA All - * unused 10 bits - * iReadingOrder 2 bits - * 34 trot An unsigned integer that specifies the text rotation. MUST be a value from the following table: - * Value Angle description - * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise - * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) - * 255 Text top-to-bottom with letters upright - *

                      - * grbit - * 0 0x1 fAutoColor 1= auto color, 0= user-selected - * 1 0x2 fShowKey 1= the text is attached to a legend key. - * 2 0x4 fShowValue 1= the value, or the vertical value on bubble or scatter chart groups, is displayed in the data label. - * If the current attached label contains a DataLabExtContents record and the fPercent field of the DataLabExtContents record equals 0, this field MUST equal the fValue field of the DataLabExtContents record. - * If the current attached label does not contain a DataLabExtContents record and fShowLabelAndPerc equals 1, this field MUST equal 0. - * This field MUST equal 0 if the current attached label does not contain a DataLabExtContents record and one or more of the following conditions are satisfied: - * The fShowLabelAndPerc field equals 1. - * The fShowPercent field equals 1. - * 3 0x8 fVert unused - * 4 0x10 fAutoText 1= autogenerated text string - * 5 0x20 fGenerated 1= default 0= modified - * 6 0x40 zfDeleted 1= an automatic label has been deleted by user - * 7 0x80 fAutoMode 1= Bg is set to auto - * 10-8 0x700 unused - * 11 0x800 fShowLblPct 1= show category name and the value, represented as a percentage of the sum of the values of the series the data label is associated with, - * MUST equal 0 if the chart group type of the corresponding chart group, series, or data point, is not a bar of pie, doughnut, pie, or pie of pie chart group. - * This field MUST equal 1 if the current attached label contains a DataLabExtContents record and both of the following conditions are satisfied: - * The fCatName and fPercent fields of the DataLabExtContents record equal 1. - * The fSerName, fValue, and fBubSizes fields of the DataLabExtContents record equal 0. - * This field MUST equal 0 if the current attached label contains a DataLabExtContents record and one or more of the following conditions is satisfied: - * The fCatName or fPercent fields of the DataLabExtContents record equal 0. - * The fSerName, fValue, or fBubSizes fields of the DataLabExtContents record equal 1. - * MUST be ignored if fAutoText equals 0. - * 12 0x1000 fShowPct A bit that specifies whether the value, represented as a percentage of the sum of the values of the series the data label is associated with, is displayed in the data label. - * MUST equal 0 if the chart group type of the corresponding chart group, series, or data point is not a bar of pie, doughnut, pie, or pie of pie chart group. - * If the current attached label contains a DataLabExtContents record, this field MUST equal the value of the fPercent field of the DataLabExtContents record. - * If the current attached label does not contain a DataLabExtContents record and fShowLabelAndPerc equals 1, this field MUST equal 1. - * MUST be ignored if fAutoText equals 0. - * 13 0x2000 fShowBubbleSizes 1= show bubble sizes - * 14 0x4000 fShowCatLabel bit that specifies whether the category (3), or the horizontal value on bubble or scatter chart groups, is displayed in the data label on a non-area chart group, or the series name is displayed in the data label on an area chart group. - * This field MUST equal the fCatNameLabel field of the DataLabExtContents record if the current attached label contains a DataLabExtContents record, the chart group is non-area, and both of the following conditions are satisfied: - * The fValue field of the DataLabExtContents record equals 0. - * The fShowLabelAndPerc field equals 1 or the fPercent field equals 0. - * This field MUST equal the fCatNameLabel field of the DataLabExtContents record if the current attached label contains a DataLabExtContents record, the chart group is area or filled radar, and the following condition is satisfied: - * The fValue field of the DataLabExtContents record equals 0. - * If the current attached label contains a DataLabExtContents record and the fValue field of the DataLabExtContents record equals 1, this field MUST equal 0. - * This field MUST equal 0 if the current attached label does not contain a DataLabExtContents record and one of the following conditions is satisfied: - * The fShowValue field equals 1. - * The fShowLabelAndPerc field equals 0 and the fShowPercent field equals 1. - * MUST be ignored if fAutoText equals 0. - */ -public class TextDisp extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7862828186455339066L; - private short grbit = 0, grbit2 = 0; - private short at = 2, vat = 1, wBkgMode = 1, icvText = 0, trot = 0, dlp; - private int x = 0, y = 0, dx = 0, dy = 0; - private java.awt.Color rgbText = null; - private boolean fAutoColor, fShowKey, fShowValue, fVert, fAutoText, fGenerated, fDeleted, fAutoMode; - private boolean fShowLblPct, fShowPct, fShowBubbleSizes, fShowCatLabel; - - public void init() { - super.init(); - byte[] data = getData(); - at = data[0]; - vat = data[1]; - wBkgMode = ByteTools.readShort(data[2], data[3]); - rgbText = new java.awt.Color((data[4] < 0 ? 255 + data[4] : data[4]), (data[5] < 0 ? 255 + data[5] : data[5]), (data[6] < 0 ? 255 + data[6] : data[6])); -// rgbText= ByteTools.readInt(this.getBytesAt(4, 4)); - x = ByteTools.readInt(this.getBytesAt(8, 4)); - y = ByteTools.readInt(this.getBytesAt(12, 4)); - dx = ByteTools.readInt(this.getBytesAt(16, 4)); - dy = ByteTools.readInt(this.getBytesAt(20, 4)); - grbit = ByteTools.readShort(data[24], data[25]); - fAutoColor = (grbit & 0x1) == 0x1; // usually T - fShowKey = (grbit & 0x2) == 0x2; - fShowValue = (grbit & 0x4) == 0x4; // pie-specific shows value label - fVert = (grbit & 0x8) == 0x8; - fAutoText = (grbit & 0x10) == 0x10; // usually T except for set labels - fGenerated = (grbit & 0x20) == 0x20; // usually T - fDeleted = (grbit & 0x40) == 0x40; - fAutoMode = (grbit & 0x80) == 0x80; // usually T - fShowLblPct = (grbit & 0x800) == 0x800; // pie-specific - fShowPct = (grbit & 0x1000) == 0x1000; // pie-specific - fShowBubbleSizes = (grbit & 0x2000) == 0x2000; // bubble-specific - fShowCatLabel = (grbit & 0x4000) == 0x4000; // pie-specific shows category label - icvText = ByteTools.readShort(data[26], data[27]); - dlp = (short) (data[28] & 0xF); - grbit2 = ByteTools.readShort(data[28], data[29]); - trot = ByteTools.readShort(data[30], data[31]); - } - - /** - * @return the type of this TextDisp (X, Y, Z axis, title, data series ... - */ - public int getType() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == OBJECTLINK) { - return ((ObjectLink) b).getType(); - } - } - return -1; - } - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - sb.append(" Label=\"" + this.toString() + "\""); - sb.append(" TextRotation=\"" + trot + "\""); - // KSC: TODO: Handle more options - if (fShowLblPct) - sb.append(" ShowLabelPct=\"" + true + "\""); - if (fShowPct) - sb.append(" ShowPct=\"" + true + "\""); - if (fShowCatLabel) - sb.append(" ShowCatLabel=\"" + true + "\""); // 20081223 KSC: try to figure out difference between AttachedLabel and TextDisp options - if (fShowBubbleSizes) - sb.append(" ShowBubbleSizes=\"" + true + "\""); - sb.append(" Font=\"" + getFontId() + "\""); - return sb.toString(); - } - - /** - * return the id of the Fontx associated with this TextDisp - * - * @return FontX id - */ - public int getFontId() { - int ret = 0; - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == FONTX) { - ret = ((Fontx) b).getIfnt(); - break; - } - } - return ret; - } - - /** - * set the id of the Fontx associated with this TextDisp - */ - public void setFontId(int id) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == FONTX) { - ((Fontx) b).setIfnt(id); - break; - } - } - } - - /** - * return the font record that sets the font properties for this text element - * - * @param wb - * @return - */ - public io.starter.formats.XLS.Font getFont(WorkBook wb) { - return wb.getFont(getFontId()); - } - - /** - * Handle setting options from XML in a generic manner - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("Label")) { - this.setText(val); - bHandled = true; - } else if (op.equalsIgnoreCase("ShowKey")) { - fShowKey = val.equals("true") || val.equals("1"); - grbit = ByteTools.updateGrBit(grbit, fShowKey, 1); - bHandled = true; - } else if (op.equalsIgnoreCase("ShowValue")) { - fShowValue = val.equals("true") || val.equals("1"); - grbit = ByteTools.updateGrBit(grbit, fShowValue, 2); - bHandled = true; - } else if (op.equalsIgnoreCase("ShowLabelPct")) { - fShowLblPct = val.equals("true") || val.equals("1"); - grbit = ByteTools.updateGrBit(grbit, fShowLblPct, 11); - bHandled = true; - } else if (op.equalsIgnoreCase("ShowPct")) { - fShowPct = val.equals("true") || val.equals("1"); - grbit = ByteTools.updateGrBit(grbit, fShowPct, 12); - bHandled = true; - } else if (op.equalsIgnoreCase("ShowCatLabel")) { // 20081223 KSC: changed from ShowLabel - fShowCatLabel = val.equals("true") || val.equals("1"); - grbit = ByteTools.updateGrBit(grbit, fShowCatLabel, 14); - bHandled = true; - } else if (op.equalsIgnoreCase("ShowBubbleSizes")) { - fShowBubbleSizes = val.equals("true") || val.equals("1"); - grbit = ByteTools.updateGrBit(grbit, fShowBubbleSizes, 13); - bHandled = true; - } else if (op.equalsIgnoreCase("TextRotation")) { - trot = Short.parseShort(val); - bHandled = true; - } else if (op.equalsIgnoreCase("Font")) { - setFontId(Short.parseShort(val)); - bHandled = true; - } - if (bHandled) - updateRecord(); - return bHandled; - } - - /** - * Return the string value of the specified option - */ - public String getChartOption(String op) { - if (op.equalsIgnoreCase("Label")) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == SERIESTEXT) { - SeriesText st = (SeriesText) b; - return st.toString(); - } - } - } else if (op.equalsIgnoreCase("ShowKey")) { - return ((fShowKey) ? "1" : "0"); - } else if (op.equalsIgnoreCase("ShowValue")) { - return ((fShowValue) ? "1" : "0"); - } else if (op.equalsIgnoreCase("ShowLabelPct")) { - return ((fShowLblPct) ? "1" : "0"); - } else if (op.equalsIgnoreCase("ShowPct")) { - return ((fShowPct) ? "1" : "0"); - } else if (op.equalsIgnoreCase("ShowCatLabel")) { - return ((fShowCatLabel) ? "1" : "0"); - } else if (op.equalsIgnoreCase("ShowBubbleSizes")) { - return ((fShowBubbleSizes) ? "1" : "0"); - } else if (op.equalsIgnoreCase("TextRotation")) { - return String.valueOf(trot); - } /*else if (op.equalsIgnoreCase("Font")) { - return setFontId(Short.parseShort(val)); - bHandled= true; - }*/ - return ""; - } - - - /** - * @return - */ - protected boolean isChartTitle() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == OBJECTLINK) { - ObjectLink ol = (ObjectLink) b; - return ol.isChartTitle(); - } - } - return false; - } - - protected boolean isXAxisLabel() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == OBJECTLINK) { - ObjectLink ol = (ObjectLink) b; - return ol.isXAxisLabel(); - } - } - return false; - } - - protected boolean isYAxisLabel() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == OBJECTLINK) { - ObjectLink ol = (ObjectLink) b; - return ol.isYAxisLabel(); - } - } - return false; - } - - /** - * @return truth of "This TextDisp represents the Z axis" - */ - protected boolean isZAxisLabel() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == OBJECTLINK) { - ObjectLink ol = (ObjectLink) b; - return ol.getType() == ObjectLink.TYPE_ZAXIS; - } - } - return false; - } - - /** - * Return the string associated with this TextDisp object. - * - * @return - */ - public String toString() { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == SERIESTEXT) { - SeriesText st = (SeriesText) b; - return st.toString(); - } - } - return ""; - } - - /** - * Set the text of this textDisplay object. - * Requires setting the child seriesText object - */ - public void setText(String newText) { - for (int i = 0; i < chartArr.size(); i++) { - BiffRec b = chartArr.get(i); - if (b.getOpcode() == SERIESTEXT) { - SeriesText st = (SeriesText) b; - st.setText(newText); - break; - } - } - } - - /** - * add new TextDisp rec with associated recs, according to type - * - * @see ObjectLink - */ - public static XLSRecord getPrototype(int type, String text, io.starter.formats.XLS.WorkBook book) { - TextDisp td = new TextDisp(); - td.setOpcode(TEXTDISP); - td.setData(td.PROTOTYPE_BYTES); - td.init(); - if (type != ObjectLink.TYPE_DATAPOINTS) { // for all TextDisps for labels i.e. axes, labels, must set fAutogenerated and fAutoText off - td.grbit = ByteTools.updateGrBit(td.grbit, false, 4); - td.grbit = ByteTools.updateGrBit(td.grbit, false, 5); - td.updateRecord(); - td.init(); - } - td.setWorkBook(book); - // add pos record - Pos p = (Pos) Pos.getPrototype(Pos.TYPE_TEXTDISP); - p.setWorkBook(book); - if (type == ObjectLink.TYPE_XAXIS) { // 20090309 KSC: set relative position - p.setX(-4); - p.setY(-3); - } - td.addChartRecord(p); - // add fontx rec, if necesssary - if (type != ObjectLink.TYPE_DATAPOINTS) { - Fontx f = (Fontx) Fontx.getPrototype(); - f.setWorkBook(book); - td.addChartRecord(f); - } - // add ai record - Ai ai = null; - if (type == ObjectLink.TYPE_TITLE) - ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_LEGEND); - else - ai = (Ai) Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND); - ai.setWorkBook(book); - td.addChartRecord(ai); - // add seriestext rec, if necessary - if (type != ObjectLink.TYPE_DATAPOINTS) { - SeriesText st = SeriesText.getPrototype(text); - td.addChartRecord(st); - } - // add objectlink rec - ObjectLink o = (ObjectLink) ObjectLink.getPrototype(type); - o.setWorkBook(book); - td.addChartRecord(o); - return td; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -79, 0, 77, 0, -96, 9, 0, 0}; - - public static XLSRecord getPrototype() { - TextDisp td = new TextDisp(); - td.setOpcode(TEXTDISP); - td.setData(td.PROTOTYPE_BYTES); - td.init(); - return td; - } - - private void updateRecord() { - this.getData()[0] = (byte) at; - this.getData()[1] = (byte) vat; - byte[] b = ByteTools.shortToLEBytes(wBkgMode); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - b = new byte[4]; - b[0] = (byte) rgbText.getRed(); - b[1] = (byte) rgbText.getGreen(); - b[2] = (byte) rgbText.getBlue(); - b[3] = 0; // reserved/0 - System.arraycopy(b, 0, this.getData(), 0, 4); - // x - // y - // dx - // dy - b = ByteTools.shortToLEBytes(grbit); - this.getData()[24] = b[0]; - this.getData()[25] = b[1]; - // icvText - b = ByteTools.shortToLEBytes(grbit2); - this.getData()[28] = b[0]; - this.getData()[29] = b[1]; - b = ByteTools.shortToLEBytes(trot); - this.getData()[30] = b[0]; - this.getData()[31] = b[1]; - } - - public static int convertType(int axis) { - int t = axis; - switch (axis) { - case XAXIS: - t = ObjectLink.TYPE_XAXIS; - break; - case YAXIS: - case XVALAXIS: - t = ObjectLink.TYPE_YAXIS; - break; - case ZAXIS: - t = ObjectLink.TYPE_ZAXIS; - break; - } - return t; - } - - /** - * return the coordinates, in pixels, of the text area, if possible - * - * @return - */ - public float[] getCoords() { - Pos p = (Pos) Chart.findRec(this.chartArr, Pos.class); - if (p != null) { - float[] coords = p.getCoords(); - return coords; - } - return new float[]{0, 0, 0, 0}; - - } - - /** - * return the rotation of this text - * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise - * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) - * 255 Text top-to-bottom with letters upright - */ - public int getRotation() { - return trot; - } - - /** - * sets the rotation of this text - * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise - * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) - * 255 Text top-to-bottom with letters upright - */ - public void setRotation(int rot) { - trot = (short) rot; - byte[] b = ByteTools.shortToLEBytes(trot); - this.getData()[30] = b[0]; - this.getData()[31] = b[1]; - } - - /** - * return the text color of the text of this text display - * - * @return String hex color string - */ - public String getTextColor() { - /* Font fx = this.getFont(wkbook); - if (fx!=null) - return fx.getFontColor(); - return 0; -*/ // it seems that font color and TextDisp.rgbText are the same - return FormatHandle.colorToHexString(rgbText); - } - - /** - * return the SVG representation of this Text Display - * - * @return - */ - public StringBuffer getSVG(HashMap chartMetrics) { - StringBuffer svg = new StringBuffer(); - - float[] coords = this.getCoords(); - coords[0] = (int) Math.ceil(Pos.convertFromSPRC(coords[0], chartMetrics.get("canvasw").floatValue(), 0)) - 3; - coords[1] = (int) Math.ceil(Pos.convertFromSPRC(coords[1], 0, chartMetrics.get("canvash").floatValue())); - Font fx = this.getFont(wkbook); - - svg.append("\r\n"); - float fh = 10; - if (fx != null) fh = (float) fx.getFontHeightInPoints(); - float x = (float) (chartMetrics.get("x") + chartMetrics.get("w") / 2); - float y = (chartMetrics.get("TITLEOFFSET").floatValue() + fh) / 2; - Frame f = (Frame) Chart.findRec(this.chartArr, Frame.class); - if (f != null) { - coords[0] = coords[0] + x; - coords[1] = y + coords[1] + fh; - coords[3] = fh * 2; // just a test really - svg.append(f.getSVG(coords)); - } else { - coords[0] = x; - coords[1] = y + fh / 2; - } - - svg.append(""); - else - svg.append(" font-family='Arial' font-size='14pt' font-weight='bold'>"); - svg.append(this.toString() + "\r\n"); - svg.append("\r\n"); - return svg; - } - - /** - * create a bg frame with the specified settings - * - * @param lw line width (-1 is none, 0= default ...) - * @param lclr line color - * @param bgcolor frame bg color - * @param coords frame coordinates - */ - public void setFrame(int lw, int lclr, int bgcolor, float[] coords) { - Frame f = (Frame) Chart.findRec(this.chartArr, Frame.class); - if (f == null) { - f = (Frame) Frame.getPrototype(); - f.addBox(lw, lclr, bgcolor); - f.setParentChart(this.getParentChart()); - this.chartArr.add(f); - } - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/TextDisp.kt b/src/main/java/io/starter/formats/XLS/charts/TextDisp.kt new file mode 100644 index 0000000..13d6b7b --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/TextDisp.kt @@ -0,0 +1,640 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.OpenXLS.FormatHandle +import io.starter.formats.XLS.BiffRec +import io.starter.formats.XLS.Font +import io.starter.formats.XLS.WorkBook +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +import java.util.HashMap + +/** + * **TextDisp: (Text) Defines Display of Text Fields (0x1025)** + * + * + * Used in conjunction with several other records to define alignment, color, position, + * size, and so on, of text fiedls that appear on the chart. + * The fields in this record have meaning according to the TEXT record's parent + * (CHART, LEGEND or DEFAULTTEXT) + * + * + * 4 at 1 Horizontal alignment (1=left, 2= center, 3=bottom, 4= justify, 7=distributed) + * 5 vat 1 Vertical alignment (1= top, 2= center, 3= bottom, 4= justify, 7=distributed) + * 6 wBkgMode 2 Display mode of bg 1= transparent, 2= opaque + * 8 rgbText 4 Color of the text + * LongRGB Structure: red (1 byte): An unsigned integer that specifies the relative intensity of red. + * green (1 byte): An unsigned integer that specifies the relative intensity of green. + * blue (1 byte): An unsigned integer that specifies the relative intensity of blue. + * reserved (1 byte): MUST be zero, and MUST be ignored. + * 12 x 4 in SPRC (see Pos); ignore if preceded by DefaultText OR when followed by Pos + * 16 y 4 in SPRC (see Pos); "" + * 20 dx 4 A signed integer that specifies the horizontal size of the text, relative to the chart area in SPRC. + * This value MUST be ignored when this record is followed by a Pos record; + * otherwise MUST be greater than or equal to 0 and less than or equal to 32767. SHOULD be less than or equal to 4000. + * 24 dy 4 A signed integer that specifies the vertical size of the text, relative to the chart area in SPRC. + * This value MUST be ignored when this record is followed by a Pos record; + * otherwise MUST be greater than or equal to 0 and less than or equal to 32767. SHOULD<128> be less than or equal to 4000. + * 28 grbit 2 Option flags + * 30 icvText 2 Index to color value. icv structure. + * 32 dlp 2 (4 bits): An unsigned integer that specifies the data label positioning of the text, relative to the graph object item the text is attached to. + * For all data label text fields, MUST be a value from the following table: + * Data label position Value Value for chart group type + * Auto 0x0 Pie chart group + * Right 0x0 Line, Bubble, or Scatter chart group + * Outside 0x0 Bar or Column chart group with fStacked equal to 0 + * Center 0x0 Bar or Column chart group with fStacked equal to 1 + * Outside End 0x1 Bar, Column, or Pie chart group + * Inside End 0x2 Bar, Column, or Pie chart group + * Center 0x3 Bar, Column, Line, Bubble, Scatter, or Pie chart group + * Inside Base 0x4 Bar or Column chart group + * Above 0x5 Line, Bubble, or Scatter chart group + * + * + * Below 0x6 Line, Bubble, or Scatter chart group + * Left 0x7 Line, Bubble, or Scatter chart group + * Right 0x8 Line, Bubble, or Scatter chart group + * Auto 0x9 Pie chart group + * Moved by user 0xA All + * unused 10 bits + * iReadingOrder 2 bits + * 34 trot An unsigned integer that specifies the text rotation. MUST be a value from the following table: + * Value Angle description + * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise + * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) + * 255 Text top-to-bottom with letters upright + * + * + * grbit + * 0 0x1 fAutoColor 1= auto color, 0= user-selected + * 1 0x2 fShowKey 1= the text is attached to a legend key. + * 2 0x4 fShowValue 1= the value, or the vertical value on bubble or scatter chart groups, is displayed in the data label. + * If the current attached label contains a DataLabExtContents record and the fPercent field of the DataLabExtContents record equals 0, this field MUST equal the fValue field of the DataLabExtContents record. + * If the current attached label does not contain a DataLabExtContents record and fShowLabelAndPerc equals 1, this field MUST equal 0. + * This field MUST equal 0 if the current attached label does not contain a DataLabExtContents record and one or more of the following conditions are satisfied: + * The fShowLabelAndPerc field equals 1. + * The fShowPercent field equals 1. + * 3 0x8 fVert unused + * 4 0x10 fAutoText 1= autogenerated text string + * 5 0x20 fGenerated 1= default 0= modified + * 6 0x40 zfDeleted 1= an automatic label has been deleted by user + * 7 0x80 fAutoMode 1= Bg is set to auto + * 10-8 0x700 unused + * 11 0x800 fShowLblPct 1= show category name and the value, represented as a percentage of the sum of the values of the series the data label is associated with, + * MUST equal 0 if the chart group type of the corresponding chart group, series, or data point, is not a bar of pie, doughnut, pie, or pie of pie chart group. + * This field MUST equal 1 if the current attached label contains a DataLabExtContents record and both of the following conditions are satisfied: + * The fCatName and fPercent fields of the DataLabExtContents record equal 1. + * The fSerName, fValue, and fBubSizes fields of the DataLabExtContents record equal 0. + * This field MUST equal 0 if the current attached label contains a DataLabExtContents record and one or more of the following conditions is satisfied: + * The fCatName or fPercent fields of the DataLabExtContents record equal 0. + * The fSerName, fValue, or fBubSizes fields of the DataLabExtContents record equal 1. + * MUST be ignored if fAutoText equals 0. + * 12 0x1000 fShowPct A bit that specifies whether the value, represented as a percentage of the sum of the values of the series the data label is associated with, is displayed in the data label. + * MUST equal 0 if the chart group type of the corresponding chart group, series, or data point is not a bar of pie, doughnut, pie, or pie of pie chart group. + * If the current attached label contains a DataLabExtContents record, this field MUST equal the value of the fPercent field of the DataLabExtContents record. + * If the current attached label does not contain a DataLabExtContents record and fShowLabelAndPerc equals 1, this field MUST equal 1. + * MUST be ignored if fAutoText equals 0. + * 13 0x2000 fShowBubbleSizes 1= show bubble sizes + * 14 0x4000 fShowCatLabel bit that specifies whether the category (3), or the horizontal value on bubble or scatter chart groups, is displayed in the data label on a non-area chart group, or the series name is displayed in the data label on an area chart group. + * This field MUST equal the fCatNameLabel field of the DataLabExtContents record if the current attached label contains a DataLabExtContents record, the chart group is non-area, and both of the following conditions are satisfied: + * The fValue field of the DataLabExtContents record equals 0. + * The fShowLabelAndPerc field equals 1 or the fPercent field equals 0. + * This field MUST equal the fCatNameLabel field of the DataLabExtContents record if the current attached label contains a DataLabExtContents record, the chart group is area or filled radar, and the following condition is satisfied: + * The fValue field of the DataLabExtContents record equals 0. + * If the current attached label contains a DataLabExtContents record and the fValue field of the DataLabExtContents record equals 1, this field MUST equal 0. + * This field MUST equal 0 if the current attached label does not contain a DataLabExtContents record and one of the following conditions is satisfied: + * The fShowValue field equals 1. + * The fShowLabelAndPerc field equals 0 and the fShowPercent field equals 1. + * MUST be ignored if fAutoText equals 0. + */ +class TextDisp : GenericChartObject(), ChartObject { + private var grbit: Short = 0 + private var grbit2: Short = 0 + private var at: Short = 2 + private var vat: Short = 1 + private var wBkgMode: Short = 1 + private var icvText: Short = 0 + private var trot: Short = 0 + private var dlp: Short = 0 + private var x = 0 + private var y = 0 + private var dx = 0 + private var dy = 0 + private var rgbText: java.awt.Color? = null + private var fAutoColor: Boolean = false + private var fShowKey: Boolean = false + private var fShowValue: Boolean = false + private var fVert: Boolean = false + private var fAutoText: Boolean = false + private var fGenerated: Boolean = false + private var fDeleted: Boolean = false + private var fAutoMode: Boolean = false + private var fShowLblPct: Boolean = false + private var fShowPct: Boolean = false + private var fShowBubbleSizes: Boolean = false + private var fShowCatLabel: Boolean = false + + /** + * @return the type of this TextDisp (X, Y, Z axis, title, data series ... + */ + val type: Int + get() { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.OBJECTLINK) { + return (b as ObjectLink).type + } + } + return -1 + } + + /** + * @return String XML representation of this chart-type's options + */ + override// KSC: TODO: Handle more options + // 20081223 KSC: try to figure out difference between AttachedLabel and TextDisp options + val optionsXML: String + get() { + val sb = StringBuffer() + sb.append(" Label=\"$this\"") + sb.append(" TextRotation=\"$trot\"") + if (fShowLblPct) + sb.append(" ShowLabelPct=\"" + true + "\"") + if (fShowPct) + sb.append(" ShowPct=\"" + true + "\"") + if (fShowCatLabel) + sb.append(" ShowCatLabel=\"" + true + "\"") + if (fShowBubbleSizes) + sb.append(" ShowBubbleSizes=\"" + true + "\"") + sb.append(" Font=\"$fontId\"") + return sb.toString() + } + + /** + * return the id of the Fontx associated with this TextDisp + * + * @return FontX id + */ + /** + * set the id of the Fontx associated with this TextDisp + */ + var fontId: Int + get() { + var ret = 0 + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.FONTX) { + ret = (b as Fontx).ifnt + break + } + } + return ret + } + set(id) { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.FONTX) { + (b as Fontx).ifnt = id + break + } + } + } + + + /** + * @return + */ + val isChartTitle: Boolean + get() { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.OBJECTLINK) { + val ol = b as ObjectLink + return ol.isChartTitle + } + } + return false + } + + protected val isXAxisLabel: Boolean + get() { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.OBJECTLINK) { + val ol = b as ObjectLink + return ol.isXAxisLabel + } + } + return false + } + + protected val isYAxisLabel: Boolean + get() { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.OBJECTLINK) { + val ol = b as ObjectLink + return ol.isYAxisLabel + } + } + return false + } + + /** + * @return truth of "This TextDisp represents the Z axis" + */ + protected val isZAxisLabel: Boolean + get() { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.OBJECTLINK) { + val ol = b as ObjectLink + return ol.type == ObjectLink.TYPE_ZAXIS + } + } + return false + } + + private val PROTOTYPE_BYTES = byteArrayOf(2, 2, 1, 0, 0, 0, 0, 0, -33, -1, -1, -1, -74, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -79, 0, 77, 0, -96, 9, 0, 0) + + /** + * return the coordinates, in pixels, of the text area, if possible + * + * @return + */ + val coords: FloatArray? + get() { + val p = Chart.findRec(this.chartArr, Pos::class.java) as Pos + return if (p != null) { + p.coords + } else floatArrayOf(0f, 0f, 0f, 0f) + + } + + /** + * return the rotation of this text + * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise + * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) + * 255 Text top-to-bottom with letters upright + */ + /** + * sets the rotation of this text + * 0 to 90 Text rotated 0 to 90 degrees counter-clockwise + * 91 to 180 Text rotated 1 to 90 degrees clockwise (angle is trot – 90) + * 255 Text top-to-bottom with letters upright + */ + var rotation: Int + get() = trot.toInt() + set(rot) { + trot = rot.toShort() + val b = ByteTools.shortToLEBytes(trot) + this.data[30] = b[0] + this.data[31] = b[1] + } + + /** + * return the text color of the text of this text display + * + * @return String hex color string + */ + /* Font fx = this.getFont(wkbook); + if (fx!=null) + return fx.getFontColor(); + return 0; +*/// it seems that font color and TextDisp.rgbText are the same + val textColor: String + get() = FormatHandle.colorToHexString(rgbText!!) + + override fun init() { + super.init() + val data = data + at = data!![0].toShort() + vat = data[1].toShort() + wBkgMode = ByteTools.readShort(data[2].toInt(), data[3].toInt()) + rgbText = java.awt.Color(if (data[4] < 0) 255 + data[4] else data[4], if (data[5] < 0) 255 + data[5] else data[5], if (data[6] < 0) 255 + data[6] else data[6]) + // rgbText= ByteTools.readInt(this.getBytesAt(4, 4)); + x = ByteTools.readInt(this.getBytesAt(8, 4)!!) + y = ByteTools.readInt(this.getBytesAt(12, 4)!!) + dx = ByteTools.readInt(this.getBytesAt(16, 4)!!) + dy = ByteTools.readInt(this.getBytesAt(20, 4)!!) + grbit = ByteTools.readShort(data[24].toInt(), data[25].toInt()) + fAutoColor = grbit and 0x1 == 0x1 // usually T + fShowKey = grbit and 0x2 == 0x2 + fShowValue = grbit and 0x4 == 0x4 // pie-specific shows value label + fVert = grbit and 0x8 == 0x8 + fAutoText = grbit and 0x10 == 0x10 // usually T except for set labels + fGenerated = grbit and 0x20 == 0x20 // usually T + fDeleted = grbit and 0x40 == 0x40 + fAutoMode = grbit and 0x80 == 0x80 // usually T + fShowLblPct = grbit and 0x800 == 0x800 // pie-specific + fShowPct = grbit and 0x1000 == 0x1000 // pie-specific + fShowBubbleSizes = grbit and 0x2000 == 0x2000 // bubble-specific + fShowCatLabel = grbit and 0x4000 == 0x4000 // pie-specific shows category label + icvText = ByteTools.readShort(data[26].toInt(), data[27].toInt()) + dlp = (data[28] and 0xF).toShort() + grbit2 = ByteTools.readShort(data[28].toInt(), data[29].toInt()) + trot = ByteTools.readShort(data[30].toInt(), data[31].toInt()) + } + + /** + * return the font record that sets the font properties for this text element + * + * @param wb + * @return + */ + fun getFont(wb: WorkBook): io.starter.formats.XLS.Font? { + return wb.getFont(fontId) + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("Label", ignoreCase = true)) { + this.setText(`val`) + bHandled = true + } else if (op.equals("ShowKey", ignoreCase = true)) { + fShowKey = `val` == "true" || `val` == "1" + grbit = ByteTools.updateGrBit(grbit, fShowKey, 1) + bHandled = true + } else if (op.equals("ShowValue", ignoreCase = true)) { + fShowValue = `val` == "true" || `val` == "1" + grbit = ByteTools.updateGrBit(grbit, fShowValue, 2) + bHandled = true + } else if (op.equals("ShowLabelPct", ignoreCase = true)) { + fShowLblPct = `val` == "true" || `val` == "1" + grbit = ByteTools.updateGrBit(grbit, fShowLblPct, 11) + bHandled = true + } else if (op.equals("ShowPct", ignoreCase = true)) { + fShowPct = `val` == "true" || `val` == "1" + grbit = ByteTools.updateGrBit(grbit, fShowPct, 12) + bHandled = true + } else if (op.equals("ShowCatLabel", ignoreCase = true)) { // 20081223 KSC: changed from ShowLabel + fShowCatLabel = `val` == "true" || `val` == "1" + grbit = ByteTools.updateGrBit(grbit, fShowCatLabel, 14) + bHandled = true + } else if (op.equals("ShowBubbleSizes", ignoreCase = true)) { + fShowBubbleSizes = `val` == "true" || `val` == "1" + grbit = ByteTools.updateGrBit(grbit, fShowBubbleSizes, 13) + bHandled = true + } else if (op.equals("TextRotation", ignoreCase = true)) { + trot = java.lang.Short.parseShort(`val`) + bHandled = true + } else if (op.equals("Font", ignoreCase = true)) { + fontId = java.lang.Short.parseShort(`val`) + bHandled = true + } + if (bHandled) + updateRecord() + return bHandled + } + + /** + * Return the string value of the specified option + */ + override fun getChartOption(op: String): String? { + if (op.equals("Label", ignoreCase = true)) { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.SERIESTEXT) { + val st = b as SeriesText + return st.toString() + } + } + } else if (op.equals("ShowKey", ignoreCase = true)) { + return if (fShowKey) "1" else "0" + } else if (op.equals("ShowValue", ignoreCase = true)) { + return if (fShowValue) "1" else "0" + } else if (op.equals("ShowLabelPct", ignoreCase = true)) { + return if (fShowLblPct) "1" else "0" + } else if (op.equals("ShowPct", ignoreCase = true)) { + return if (fShowPct) "1" else "0" + } else if (op.equals("ShowCatLabel", ignoreCase = true)) { + return if (fShowCatLabel) "1" else "0" + } else if (op.equals("ShowBubbleSizes", ignoreCase = true)) { + return if (fShowBubbleSizes) "1" else "0" + } else if (op.equals("TextRotation", ignoreCase = true)) { + return trot.toString() + } /*else if (op.equalsIgnoreCase("Font")) { + return setFontId(Short.parseShort(val)); + bHandled= true; + }*/ + return "" + } + + /** + * Return the string associated with this TextDisp object. + * + * @return + */ + override fun toString(): String { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.SERIESTEXT) { + val st = b as SeriesText + return st.toString() + } + } + return "" + } + + /** + * Set the text of this textDisplay object. + * Requires setting the child seriesText object + */ + fun setText(newText: String) { + for (i in chartArr.indices) { + val b = chartArr[i] + if (b.opcode == XLSConstants.SERIESTEXT) { + val st = b as SeriesText + st.setText(newText) + break + } + } + } + + private fun updateRecord() { + this.data[0] = at.toByte() + this.data[1] = vat.toByte() + var b = ByteTools.shortToLEBytes(wBkgMode) + this.data[2] = b[0] + this.data[3] = b[1] + b = ByteArray(4) + b[0] = rgbText!!.red.toByte() + b[1] = rgbText!!.green.toByte() + b[2] = rgbText!!.blue.toByte() + b[3] = 0 // reserved/0 + System.arraycopy(b, 0, this.data!!, 0, 4) + // x + // y + // dx + // dy + b = ByteTools.shortToLEBytes(grbit) + this.data[24] = b[0] + this.data[25] = b[1] + // icvText + b = ByteTools.shortToLEBytes(grbit2) + this.data[28] = b[0] + this.data[29] = b[1] + b = ByteTools.shortToLEBytes(trot) + this.data[30] = b[0] + this.data[31] = b[1] + } + + /** + * return the SVG representation of this Text Display + * + * @return + */ + fun getSVG(chartMetrics: HashMap): StringBuffer { + val svg = StringBuffer() + + val coords = this.coords + coords[0] = (Math.ceil(Pos.convertFromSPRC(coords!![0], chartMetrics["canvasw"].toFloat(), 0f).toDouble()).toInt() - 3).toFloat() + coords[1] = Math.ceil(Pos.convertFromSPRC(coords[1], 0f, chartMetrics["canvash"].toFloat()).toDouble()).toInt().toFloat() + val fx = this.getFont(wkbook!!) + + svg.append("\r\n") + var fh = 10f + if (fx != null) fh = fx.fontHeightInPoints.toFloat() + val x = (chartMetrics["x"] + chartMetrics["w"] / 2).toFloat() + val y = (chartMetrics["TITLEOFFSET"].toFloat() + fh) / 2 + val f = Chart.findRec(this.chartArr, Frame::class.java) as Frame + if (f != null) { + coords[0] = coords[0] + x + coords[1] = y + coords[1] + fh + coords[3] = fh * 2 // just a test really + svg.append(f.getSVG(coords)) + } else { + coords[0] = x + coords[1] = y + fh / 2 + } + + svg.append("") + else + svg.append(" font-family='Arial' font-size='14pt' font-weight='bold'>") + svg.append("$this\r\n") + svg.append("\r\n") + return svg + } + + /** + * create a bg frame with the specified settings + * + * @param lw line width (-1 is none, 0= default ...) + * @param lclr line color + * @param bgcolor frame bg color + * @param coords frame coordinates + */ + fun setFrame(lw: Int, lclr: Int, bgcolor: Int, coords: FloatArray) { + var f = Chart.findRec(this.chartArr, Frame::class.java) as Frame + if (f == null) { + f = Frame.prototype as Frame? + f.addBox(lw, lclr, bgcolor) + f.parentChart = this.parentChart + this.chartArr.add(f) + } + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -7862828186455339066L + + /** + * add new TextDisp rec with associated recs, according to type + * + * @see ObjectLink + */ + fun getPrototype(type: Int, text: String, book: io.starter.formats.XLS.WorkBook): XLSRecord { + val td = TextDisp() + td.opcode = XLSConstants.TEXTDISP + td.data = td.PROTOTYPE_BYTES + td.init() + if (type != ObjectLink.TYPE_DATAPOINTS) { // for all TextDisps for labels i.e. axes, labels, must set fAutogenerated and fAutoText off + td.grbit = ByteTools.updateGrBit(td.grbit, false, 4) + td.grbit = ByteTools.updateGrBit(td.grbit, false, 5) + td.updateRecord() + td.init() + } + td.workBook = book + // add pos record + val p = Pos.getPrototype(Pos.TYPE_TEXTDISP) as Pos + p.workBook = book + if (type == ObjectLink.TYPE_XAXIS) { // 20090309 KSC: set relative position + p.setX(-4) + p.setY(-3) + } + td.addChartRecord(p) + // add fontx rec, if necesssary + if (type != ObjectLink.TYPE_DATAPOINTS) { + val f = Fontx.prototype as Fontx? + f!!.workBook = book + td.addChartRecord(f) + } + // add ai record + var ai: Ai? = null + if (type == ObjectLink.TYPE_TITLE) + ai = Ai.getPrototype(Ai.AI_TYPE_LEGEND) as Ai + else + ai = Ai.getPrototype(Ai.AI_TYPE_NULL_LEGEND) as Ai + ai.workBook = book + td.addChartRecord(ai) + // add seriestext rec, if necessary + if (type != ObjectLink.TYPE_DATAPOINTS) { + val st = SeriesText.getPrototype(text) + td.addChartRecord(st) + } + // add objectlink rec + val o = ObjectLink.getPrototype(type) as ObjectLink + o.workBook = book + td.addChartRecord(o) + return td + } + + val prototype: XLSRecord? + get() { + val td = TextDisp() + td.opcode = XLSConstants.TEXTDISP + td.data = td.PROTOTYPE_BYTES + td.init() + return td + } + + fun convertType(axis: Int): Int { + var t = axis + when (axis) { + ChartConstants.XAXIS -> t = ObjectLink.TYPE_XAXIS + ChartConstants.YAXIS, ChartConstants.XVALAXIS -> t = ObjectLink.TYPE_YAXIS + ChartConstants.ZAXIS -> t = ObjectLink.TYPE_ZAXIS + } + return t + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/ThreeD.java b/src/main/java/io/starter/formats/XLS/charts/ThreeD.java deleted file mode 100644 index 8682b92..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ThreeD.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.OOXML.OOXMLElement; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import org.xmlpull.v1.XmlPullParser; - -import java.util.Stack; - -/** - * ThreeD(3D) Chart group is a 3-D Chart Group (0x103A) - *

                      - * anRot 2 Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for others -- def = 0 - * anElev 2 Elevation Angle (-90 to 90 degrees) (15 is default) 8 - * pcDist 2 Distance from eye to chart (0 to 100) (30 is default) 10 - * pcHeight 2 Height of plot volume relative to width and depth (100 is default) 12 - * pcDepth 2 Depth of points relative to width (100 is default) 14 - * pcGap 2 Space between points (150 is default - should be 50!!!) 16 grbit 2 - *

                      - * grbit - * 0 0x1 fPerspective 1= use perspective transform - * 1 0x2 fCluster 1= 3-D columns are clustered or stacked - * 2 0x4 f3DScaling 1= use auto-scaling - * 3 reserved - * 4 0x8 fNotPieChart 1= NOT a pie chart - * 5 0x10 f2DWalls use 2D walls and gridlines (if fPerspective MUST be ignored. if not of type BAR, AREA or - * PIE, ignore. if BAR and fCluster=0, ignore. specifies whether the walls are rendered in 2-D. - * If fPerspective is 1 then this MUST be ignored. If the chart group type is not bar, area or pie this MUST be ignored. If the chart - * group is of type bar and fCluster is 0, then this MUST be ignored. if PIE MUST be 0. - *

                      - * "http://www.extentech.com">Extentech Inc. - */ -public class ThreeD extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7501630910970731901L; - private short anRot = 0; - private short anElev = 15; - private short pcDist = 30; - private short pcHeight = 100; - private short pcDepth = 100; - private short pcGap = 150; - private short grbit = 0; // - private boolean fPerspective, fCluster, f3dScaling, f2DWalls; // 20070905 KSC: parse grbit - - public void init() { - super.init(); - anRot = ByteTools.readShort(this.getByteAt(0), this.getByteAt(1)); - anElev = ByteTools.readShort(this.getByteAt(2), this.getByteAt(3)); - pcDist = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - pcHeight = ByteTools.readShort(this.getByteAt(6), this.getByteAt(7)); - pcDepth = ByteTools.readShort(this.getByteAt(8), this.getByteAt(9)); - pcGap = ByteTools.readShort(this.getByteAt(10), this.getByteAt(11)); - grbit = ByteTools.readShort(this.getByteAt(12), this.getByteAt(13)); - fPerspective = (grbit & 0x1) == 0x1; - fCluster = (grbit & 0x2) == 0x2; - f3dScaling = (grbit & 0x4) == 0x4; - f2DWalls = (grbit & 0x10) == 0x10; - } - - // 20070716 KSC: Need to create new records - public static XLSRecord getPrototype() { - ThreeD td = new ThreeD(); - td.setOpcode(THREED); - td.setData(td.PROTOTYPE_BYTES); - td.init(); - return td; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 30, 0, 100, 0, - 100, 0, -106, 0, 0, 0}; - - private void updateRecord() { - byte[] b = ByteTools.shortToLEBytes(anRot); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - b = ByteTools.shortToLEBytes(anElev); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - b = ByteTools.shortToLEBytes(pcDist); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - b = ByteTools.shortToLEBytes(pcHeight); - this.getData()[6] = b[0]; - this.getData()[7] = b[1]; - b = ByteTools.shortToLEBytes(pcDepth); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; - b = ByteTools.shortToLEBytes(pcGap); - this.getData()[10] = b[0]; - this.getData()[11] = b[1]; - b = ByteTools.shortToLEBytes(grbit); - this.getData()[12] = b[0]; - this.getData()[13] = b[1]; - } - - /** - * Handle setting options from XML in a generic manner - */ - public boolean setChartOption(String op, String val) { - boolean bHandled = false; - if (op.equalsIgnoreCase("AnRot")) { // specifies the clockwise rotation, in degrees, of the 3-D plot area around a vertical line through the center of the 3-D plot area. - anRot = Short.parseShort(val); // usually 20 - bHandled = true; - } - if (op.equalsIgnoreCase("AnElev")) { // signed integer that specifies the rotation, in degrees, of the 3-D plot area around a horizontal line through the center of the 3-D plot area - anElev = Short.parseShort(val); // usually 15 - bHandled = true; - } - if (op.equalsIgnoreCase("PcDist")) { // view angle for the 3-D plot area. - pcDist = Short.parseShort(val); // usually 30 - bHandled = true; - } - if (op.equalsIgnoreCase("PcHeight")) { // specifies the height of the 3-D plot area as a percentage of its width - pcHeight = Short.parseShort(val); - bHandled = true; - } - if (op.equalsIgnoreCase("PcDepth")) { // specifies the depth of the 3-D plot area as a percentage of its width. - pcDepth = Short.parseShort(val); // usually 100 - bHandled = true; - } - if (op.equalsIgnoreCase("PcGap")) { // specifies the width of the gap between the series and the front and back edges of the 3-D plot area as a percentage of the data point depth divided by 2. If - // fCluster is not set to 1 and chart group type is not a bar, this field also specifies the distance between adjacent series as a percentage of the data point depth. - pcGap = Short.parseShort(val); // usually 150 - bHandled = true; - } - /* - * 20070905 KSC: parse grbit options if - * (op.equalsIgnoreCase("FormatOptions")) { grbit= - * Short.parseShort(val); bHandled= true; } - */ - if (op.equalsIgnoreCase("Perspective")) { // specifies whether the 3-D plot area is rendered with a vanishing point. - fPerspective = Boolean.valueOf(val).booleanValue(); - grbit = ByteTools.updateGrBit(grbit, fPerspective, 0); - bHandled = true; - } - if (op.equalsIgnoreCase("Cluster")) { // specifies whether data points are clustered together in a bar chart group - fCluster = Boolean.valueOf(val).booleanValue(); - grbit = ByteTools.updateGrBit(grbit, fCluster, 1); - bHandled = true; - } - if (op.equalsIgnoreCase("ThreeDScaling")) { // specifies whether the height of the 3-D plot area is automatically determined - f3dScaling = Boolean.valueOf(val).booleanValue(); - grbit = ByteTools.updateGrBit(grbit, f3dScaling, 2); - bHandled = true; - } - if (op.equalsIgnoreCase("TwoDWalls")) { // A bit that specifies whether the chart walls are rendered in 2-D - f2DWalls = Boolean.valueOf(val).booleanValue(); - grbit = ByteTools.updateGrBit(grbit, f2DWalls, 4); - bHandled = true; - } - if (bHandled) - updateRecord(); - return bHandled; - } - - /** - * return the desired option setting in string form - */ - public String getChartOption(String op) { - if (op.equalsIgnoreCase("AnRot")) { - return String.valueOf(anRot); - } - if (op.equalsIgnoreCase("AnElev")) { - return String.valueOf(anElev); - } - if (op.equalsIgnoreCase("PcDist")) { - return String.valueOf(pcDist); - } - if (op.equalsIgnoreCase("PcHeight")) { - return String.valueOf(pcHeight); - } - if (op.equalsIgnoreCase("PcDepth")) { - return String.valueOf(pcDepth); - } - if (op.equalsIgnoreCase("PcGap")) { - return String.valueOf(pcGap); - } - if (op.equalsIgnoreCase("Perspective")) { - return ((fPerspective) ? "1" : "0"); - } - if (op.equalsIgnoreCase("Cluster")) { - return ((fCluster) ? "1" : "0"); - } - if (op.equalsIgnoreCase("ThreeDScaling")) { - return ((f3dScaling) ? "1" : "0"); - } - if (op.equalsIgnoreCase("TwoDWalls")) { - return ((f2DWalls) ? "1" : "0"); - } - return ""; - } - - /** - * @return String XML representation of this chart-type's options - */ - public String getOptionsXML() { - StringBuffer sb = new StringBuffer(); - if (anRot != 0) - sb.append(" AnRot=\"" + anRot + "\""); - if (anElev != 15) - sb.append(" AnElev=\"" + anElev + "\""); - if (pcDist != 30) - sb.append(" pcDist=\"" + pcDist + "\""); - if (pcHeight != 100) - sb.append(" pcHeight=\"" + pcHeight + "\""); - if (pcDepth != 100) - sb.append(" pcDepth=\"" + pcDepth + "\""); - if (pcGap != 150) - sb.append(" pcGap=\"" + pcGap + "\""); - if (fPerspective) - sb.append(" Perspective=\"true\""); - if (f3dScaling) - sb.append(" ThreeDScaling=\"true\""); - if (f2DWalls) - sb.append(" TwoDWalls=\"true\""); - // 20070913 KSC: need to track Cluster, whether on or off - sb.append(" Cluster=\"" + fCluster + "\""); - // sb.append(" formatOptions=\"" + grbit+ "\""); - return sb.toString(); - } - - /** - * @return truth of "Chart is Clustered" - */ - public boolean isClustered() { - return fCluster; - } - - /** - * sets if this chart has clustered bar/columns - * - * @param bIsClustered - */ - public void setIsClustered(boolean bIsClustered) { - fCluster = bIsClustered; - grbit = ByteTools.updateGrBit(grbit, fCluster, 1); - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[12] = b[0]; - this.getData()[13] = b[1]; - } - - /** - * sets the proper bit for chart type (PIE or not) - * - * @param isPieChart true if pie-type chart - */ - public void setIsPie(boolean isPieChart) { - if (isPieChart) - grbit &= 0x8; // bit is true if "not a pie" - else - grbit |= 0x17; - updateRecord(); - } - - /** - * sets the Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for - * others - * - * @param rot - */ - public void setAnRot(int rot) { - anRot = (short) rot; - byte[] b = ByteTools.shortToLEBytes(anRot); - this.getData()[0] = b[0]; - this.getData()[1] = b[1]; - } - - /** - * sets the Elevation Angle (-90 to 90 degrees) (15 is default) - * - * @param elev - */ - public void setAnElev(int elev) { - anElev = (short) elev; - byte[] b = ByteTools.shortToLEBytes(anElev); - this.getData()[2] = b[0]; - this.getData()[3] = b[1]; - } - - /** - * sets the Distance from eye to chart (0 to 100) (30 is default) - * - * @param elev - */ - public void setPcDist(int dist) { - pcDist = (short) dist; - byte[] b = ByteTools.shortToLEBytes(pcDist); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - } - - /** - * sets the Height of plot volume relative to width and depth (100 is - * default) - * - * @param elev - */ - public void setPcHeight(int dist) { - pcHeight = (short) dist; - byte[] b = ByteTools.shortToLEBytes(pcHeight); - this.getData()[6] = b[0]; - this.getData()[7] = b[1]; - } - - /** - * sets the Depth of points relative to width (100 is default) - * - * @param elev - */ - public void setPcDepth(int depth) { - pcDepth = (short) depth; - byte[] b = ByteTools.shortToLEBytes(pcDepth); - this.getData()[8] = b[0]; - this.getData()[9] = b[1]; - } - - /** - * sets the Space between points (50 or 150 is default) - * - * @param gap - */ - public void setPcGap(int gap) { - pcGap = (short) gap; - byte[] b = ByteTools.shortToLEBytes(pcGap); - this.getData()[10] = b[0]; - this.getData()[11] = b[1]; - } - - public int getPcGap() { - return pcGap; - } - - /** - * return view3d OOXML representation - * - * @return - */ - public StringBuffer getOOXML() { - StringBuffer cooxml = new StringBuffer(); - cooxml.append(""); - cooxml.append("\r\n"); - // rotX == anElev - if (anElev != 0) // default - cooxml.append(""); - // hPercent -- a height percent between 5 and 500. - // rotY == anRot - if (anRot != 0 || anElev != 0) // default - cooxml.append(""); - // depthPercentage -- This element specifies the depth of a 3-D chart as a percentage of the chart width (between 20 and 2000 percent). - if (pcDepth != 100) - cooxml.append(""); - // rAngAx == !fPerspective - if (fPerspective) - cooxml.append(""); - // perspective == pcDist - if (pcDist != 30) // default - cooxml.append(""); - cooxml.append(""); - cooxml.append("\r\n"); - return cooxml; - - } - - /** - * parse shape OOXML element view3D into a ThreeD record - * - * @param xpp XmlPullParser - * @param lastTag element stack - * @return spPr object - */ - public static OOXMLElement parseOOXML(XmlPullParser xpp, Stack lastTag, - OOXMLChart cht) { - // threeD MUST NOT EXIST in a bar of pie, bubble, doughnut, filled - // radar, pie of pie, radar, or scatter chart group. - ThreeD td = cht.getChartObject().getThreeDRec(true); - try { - int eventType = xpp.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.START_TAG) { - String tnm = xpp.getName(); - String v = null; - try { - v = xpp.getAttributeValue(0); - } catch (/* XmlPullParser */Exception e) { - } - if (v != null) { - if (tnm.equals("rotX")) { - td.setAnElev(Integer.valueOf(v)); - } else if (tnm.equals("rotY")) { - td.setAnRot(Integer.valueOf(v)); - } else if (tnm.equals("perspective")) { - td.setPcDist(Integer.valueOf(v)); - } else if (tnm.equals("depthPercent")) { - td.setPcDepth(Integer.valueOf(v)); - } else if (tnm.equals("rAngAx")) { -// if (v!=null) - - } - } - } else if (eventType == XmlPullParser.END_TAG) { - String endTag = xpp.getName(); - if (endTag.equals("view3D")) { - lastTag.pop(); // pop layout tag - break; - } - } - eventType = xpp.next(); - } - } catch (Exception e) { - Logger.logErr("ThreeD.parseOOXML: " + e.toString()); - } - - return null; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ThreeD.kt b/src/main/java/io/starter/formats/XLS/charts/ThreeD.kt new file mode 100644 index 0000000..9aba45a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ThreeD.kt @@ -0,0 +1,452 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.OOXML.OOXMLElement +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import org.xmlpull.v1.XmlPullParser + +import java.util.Stack + +/** + * **ThreeD(3D) Chart group is a 3-D Chart Group (0x103A)** + * + * + * anRot 2 Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for others -- def = 0 + * anElev 2 Elevation Angle (-90 to 90 degrees) (15 is default) 8 + * pcDist 2 Distance from eye to chart (0 to 100) (30 is default) 10 + * pcHeight 2 Height of plot volume relative to width and depth (100 is default) 12 + * pcDepth 2 Depth of points relative to width (100 is default) 14 + * pcGap 2 Space between points (150 is default - should be 50!!!) 16 grbit 2 + * + * + * grbit + * 0 0x1 fPerspective 1= use perspective transform + * 1 0x2 fCluster 1= 3-D columns are clustered or stacked + * 2 0x4 f3DScaling 1= use auto-scaling + * 3 reserved + * 4 0x8 fNotPieChart 1= NOT a pie chart + * 5 0x10 f2DWalls use 2D walls and gridlines (if fPerspective MUST be ignored. if not of type BAR, AREA or + * PIE, ignore. if BAR and fCluster=0, ignore. specifies whether the walls are rendered in 2-D. + * If fPerspective is 1 then this MUST be ignored. If the chart group type is not bar, area or pie this MUST be ignored. If the chart + * group is of type bar and fCluster is 0, then this MUST be ignored. if PIE MUST be 0. + * + * + * "http://www.extentech.com">Extentech Inc. + */ +class ThreeD : GenericChartObject(), ChartObject { + private var anRot: Short = 0 + private var anElev: Short = 15 + private var pcDist: Short = 30 + private var pcHeight: Short = 100 + private var pcDepth: Short = 100 + private var pcGap: Short = 150 + private var grbit: Short = 0 // + private var fPerspective: Boolean = false + private var fCluster: Boolean = false + private var f3dScaling: Boolean = false + private var f2DWalls: Boolean = false // 20070905 KSC: parse grbit + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 30, 0, 100, 0, 100, 0, -106, 0, 0, 0) + + /** + * @return String XML representation of this chart-type's options + */ + override// 20070913 KSC: need to track Cluster, whether on or off + // sb.append(" formatOptions=\"" + grbit+ "\""); + val optionsXML: String + get() { + val sb = StringBuffer() + if (anRot.toInt() != 0) + sb.append(" AnRot=\"$anRot\"") + if (anElev.toInt() != 15) + sb.append(" AnElev=\"$anElev\"") + if (pcDist.toInt() != 30) + sb.append(" pcDist=\"$pcDist\"") + if (pcHeight.toInt() != 100) + sb.append(" pcHeight=\"$pcHeight\"") + if (pcDepth.toInt() != 100) + sb.append(" pcDepth=\"$pcDepth\"") + if (pcGap.toInt() != 150) + sb.append(" pcGap=\"$pcGap\"") + if (fPerspective) + sb.append(" Perspective=\"true\"") + if (f3dScaling) + sb.append(" ThreeDScaling=\"true\"") + if (f2DWalls) + sb.append(" TwoDWalls=\"true\"") + sb.append(" Cluster=\"$fCluster\"") + return sb.toString() + } + + /** + * @return truth of "Chart is Clustered" + */ + /** + * sets if this chart has clustered bar/columns + * + * @param bIsClustered + */ + var isClustered: Boolean + get() = fCluster + set(bIsClustered) { + fCluster = bIsClustered + grbit = ByteTools.updateGrBit(grbit, fCluster, 1) + val b = ByteTools.shortToLEBytes(grbit) + this.data[12] = b[0] + this.data[13] = b[1] + } + + /** + * return view3d OOXML representation + * + * @return + */ + // rotX == anElev + // default + // hPercent -- a height percent between 5 and 500. + // rotY == anRot + // default + // depthPercentage -- This element specifies the depth of a 3-D chart as a percentage of the chart width (between 20 and 2000 percent). + // rAngAx == !fPerspective + // perspective == pcDist + // default + val ooxml: StringBuffer + get() { + val cooxml = StringBuffer() + cooxml.append("") + cooxml.append("\r\n") + if (anElev.toInt() != 0) + cooxml.append("") + if (anRot.toInt() != 0 || anElev.toInt() != 0) + cooxml.append("") + if (pcDepth.toInt() != 100) + cooxml.append("") + if (fPerspective) + cooxml.append("") + if (pcDist.toInt() != 30) + cooxml.append("") + cooxml.append("") + cooxml.append("\r\n") + return cooxml + + } + + override fun init() { + super.init() + anRot = ByteTools.readShort(this.getByteAt(0).toInt(), this.getByteAt(1).toInt()) + anElev = ByteTools.readShort(this.getByteAt(2).toInt(), this.getByteAt(3).toInt()) + pcDist = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + pcHeight = ByteTools.readShort(this.getByteAt(6).toInt(), this.getByteAt(7).toInt()) + pcDepth = ByteTools.readShort(this.getByteAt(8).toInt(), this.getByteAt(9).toInt()) + pcGap = ByteTools.readShort(this.getByteAt(10).toInt(), this.getByteAt(11).toInt()) + grbit = ByteTools.readShort(this.getByteAt(12).toInt(), this.getByteAt(13).toInt()) + fPerspective = grbit and 0x1 == 0x1 + fCluster = grbit and 0x2 == 0x2 + f3dScaling = grbit and 0x4 == 0x4 + f2DWalls = grbit and 0x10 == 0x10 + } + + private fun updateRecord() { + var b = ByteTools.shortToLEBytes(anRot) + this.data[0] = b[0] + this.data[1] = b[1] + b = ByteTools.shortToLEBytes(anElev) + this.data[2] = b[0] + this.data[3] = b[1] + b = ByteTools.shortToLEBytes(pcDist) + this.data[4] = b[0] + this.data[5] = b[1] + b = ByteTools.shortToLEBytes(pcHeight) + this.data[6] = b[0] + this.data[7] = b[1] + b = ByteTools.shortToLEBytes(pcDepth) + this.data[8] = b[0] + this.data[9] = b[1] + b = ByteTools.shortToLEBytes(pcGap) + this.data[10] = b[0] + this.data[11] = b[1] + b = ByteTools.shortToLEBytes(grbit) + this.data[12] = b[0] + this.data[13] = b[1] + } + + /** + * Handle setting options from XML in a generic manner + */ + override fun setChartOption(op: String, `val`: String): Boolean { + var bHandled = false + if (op.equals("AnRot", ignoreCase = true)) { // specifies the clockwise rotation, in degrees, of the 3-D plot area around a vertical line through the center of the 3-D plot area. + anRot = java.lang.Short.parseShort(`val`) // usually 20 + bHandled = true + } + if (op.equals("AnElev", ignoreCase = true)) { // signed integer that specifies the rotation, in degrees, of the 3-D plot area around a horizontal line through the center of the 3-D plot area + anElev = java.lang.Short.parseShort(`val`) // usually 15 + bHandled = true + } + if (op.equals("PcDist", ignoreCase = true)) { // view angle for the 3-D plot area. + pcDist = java.lang.Short.parseShort(`val`) // usually 30 + bHandled = true + } + if (op.equals("PcHeight", ignoreCase = true)) { // specifies the height of the 3-D plot area as a percentage of its width + pcHeight = java.lang.Short.parseShort(`val`) + bHandled = true + } + if (op.equals("PcDepth", ignoreCase = true)) { // specifies the depth of the 3-D plot area as a percentage of its width. + pcDepth = java.lang.Short.parseShort(`val`) // usually 100 + bHandled = true + } + if (op.equals("PcGap", ignoreCase = true)) { // specifies the width of the gap between the series and the front and back edges of the 3-D plot area as a percentage of the data point depth divided by 2. If + // fCluster is not set to 1 and chart group type is not a bar, this field also specifies the distance between adjacent series as a percentage of the data point depth. + pcGap = java.lang.Short.parseShort(`val`) // usually 150 + bHandled = true + } + /* + * 20070905 KSC: parse grbit options if + * (op.equalsIgnoreCase("FormatOptions")) { grbit= + * Short.parseShort(val); bHandled= true; } + */ + if (op.equals("Perspective", ignoreCase = true)) { // specifies whether the 3-D plot area is rendered with a vanishing point. + fPerspective = java.lang.Boolean.valueOf(`val`).booleanValue() + grbit = ByteTools.updateGrBit(grbit, fPerspective, 0) + bHandled = true + } + if (op.equals("Cluster", ignoreCase = true)) { // specifies whether data points are clustered together in a bar chart group + fCluster = java.lang.Boolean.valueOf(`val`).booleanValue() + grbit = ByteTools.updateGrBit(grbit, fCluster, 1) + bHandled = true + } + if (op.equals("ThreeDScaling", ignoreCase = true)) { // specifies whether the height of the 3-D plot area is automatically determined + f3dScaling = java.lang.Boolean.valueOf(`val`).booleanValue() + grbit = ByteTools.updateGrBit(grbit, f3dScaling, 2) + bHandled = true + } + if (op.equals("TwoDWalls", ignoreCase = true)) { // A bit that specifies whether the chart walls are rendered in 2-D + f2DWalls = java.lang.Boolean.valueOf(`val`).booleanValue() + grbit = ByteTools.updateGrBit(grbit, f2DWalls, 4) + bHandled = true + } + if (bHandled) + updateRecord() + return bHandled + } + + /** + * return the desired option setting in string form + */ + override fun getChartOption(op: String): String? { + if (op.equals("AnRot", ignoreCase = true)) { + return anRot.toString() + } + if (op.equals("AnElev", ignoreCase = true)) { + return anElev.toString() + } + if (op.equals("PcDist", ignoreCase = true)) { + return pcDist.toString() + } + if (op.equals("PcHeight", ignoreCase = true)) { + return pcHeight.toString() + } + if (op.equals("PcDepth", ignoreCase = true)) { + return pcDepth.toString() + } + if (op.equals("PcGap", ignoreCase = true)) { + return pcGap.toString() + } + if (op.equals("Perspective", ignoreCase = true)) { + return if (fPerspective) "1" else "0" + } + if (op.equals("Cluster", ignoreCase = true)) { + return if (fCluster) "1" else "0" + } + if (op.equals("ThreeDScaling", ignoreCase = true)) { + return if (f3dScaling) "1" else "0" + } + return if (op.equals("TwoDWalls", ignoreCase = true)) { + if (f2DWalls) "1" else "0" + } else "" + } + + /** + * sets the proper bit for chart type (PIE or not) + * + * @param isPieChart true if pie-type chart + */ + fun setIsPie(isPieChart: Boolean) { + if (isPieChart) + grbit = grbit and 0x8 // bit is true if "not a pie" + else + grbit = grbit or 0x17 + updateRecord() + } + + /** + * sets the Rotation Angle (0 to 360 degrees), usually 0 for pie, 20 for + * others + * + * @param rot + */ + fun setAnRot(rot: Int) { + anRot = rot.toShort() + val b = ByteTools.shortToLEBytes(anRot) + this.data[0] = b[0] + this.data[1] = b[1] + } + + /** + * sets the Elevation Angle (-90 to 90 degrees) (15 is default) + * + * @param elev + */ + fun setAnElev(elev: Int) { + anElev = elev.toShort() + val b = ByteTools.shortToLEBytes(anElev) + this.data[2] = b[0] + this.data[3] = b[1] + } + + /** + * sets the Distance from eye to chart (0 to 100) (30 is default) + * + * @param elev + */ + fun setPcDist(dist: Int) { + pcDist = dist.toShort() + val b = ByteTools.shortToLEBytes(pcDist) + this.data[4] = b[0] + this.data[5] = b[1] + } + + /** + * sets the Height of plot volume relative to width and depth (100 is + * default) + * + * @param elev + */ + fun setPcHeight(dist: Int) { + pcHeight = dist.toShort() + val b = ByteTools.shortToLEBytes(pcHeight) + this.data[6] = b[0] + this.data[7] = b[1] + } + + /** + * sets the Depth of points relative to width (100 is default) + * + * @param elev + */ + fun setPcDepth(depth: Int) { + pcDepth = depth.toShort() + val b = ByteTools.shortToLEBytes(pcDepth) + this.data[8] = b[0] + this.data[9] = b[1] + } + + /** + * sets the Space between points (50 or 150 is default) + * + * @param gap + */ + fun setPcGap(gap: Int) { + pcGap = gap.toShort() + val b = ByteTools.shortToLEBytes(pcGap) + this.data[10] = b[0] + this.data[11] = b[1] + } + + fun getPcGap(): Int { + return pcGap.toInt() + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -7501630910970731901L + + // 20070716 KSC: Need to create new records + val prototype: XLSRecord? + get() { + val td = ThreeD() + td.opcode = XLSConstants.THREED + td.data = td.PROTOTYPE_BYTES + td.init() + return td + } + + /** + * parse shape OOXML element view3D into a ThreeD record + * + * @param xpp XmlPullParser + * @param lastTag element stack + * @return spPr object + */ + fun parseOOXML(xpp: XmlPullParser, lastTag: Stack, + cht: OOXMLChart): OOXMLElement? { + // threeD MUST NOT EXIST in a bar of pie, bubble, doughnut, filled + // radar, pie of pie, radar, or scatter chart group. + val td = cht.chartObject.getThreeDRec(true) + try { + var eventType = xpp.eventType + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + val tnm = xpp.name + var v: String? = null + try { + v = xpp.getAttributeValue(0) + } catch (/* XmlPullParser */e: Exception) { + } + + if (v != null) { + if (tnm == "rotX") { + td!!.setAnElev(Integer.valueOf(v)) + } else if (tnm == "rotY") { + td!!.setAnRot(Integer.valueOf(v)) + } else if (tnm == "perspective") { + td!!.setPcDist(Integer.valueOf(v)) + } else if (tnm == "depthPercent") { + td!!.setPcDepth(Integer.valueOf(v)) + } else if (tnm == "rAngAx") { + // if (v!=null) + + } + } + } else if (eventType == XmlPullParser.END_TAG) { + val endTag = xpp.name + if (endTag == "view3D") { + lastTag.pop() // pop layout tag + break + } + } + eventType = xpp.next() + } + } catch (e: Exception) { + Logger.logErr("ThreeD.parseOOXML: $e") + } + + return null + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/Tick.java b/src/main/java/io/starter/formats/XLS/charts/Tick.java deleted file mode 100644 index 65ff57e..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/Tick.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * Tick: Tick Marks and Labels Format (0x101e) - *

                      - * Offset Name Size Contents - * 4 tktMajor 1 Type of major tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line - * 5 tktMinor 1 Type of minor tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line - * 6 tit 1 Tick label position relative to axis line 0= invisible (none) 1 = low end of plot area 2 = high end of plot area 3 = next to axis - * 7 wBkgMode 2 Background mode: I = transparent 2 = opaque - * 8 rgb 4 Tick-label text color; ROB value, high byte = 0 - * 12 (reserved) 16 Reserved; must be zero - * 28 grbit 2 Display flags - * 30 icv 2 Index to color of tick label - * 32 (reserved) 2 Reserved; must be zero - *

                      - * The grbit field contains the following option flags. - *

                      - * Bits Mask Name - * 0 0xOl fAutoColor Automatic text color - * 1 0x02 fAutoMode Automatic text back~ - * 4-2 0xlC rot 0= no rotation (text appears left-to-right), 1= text appears top-~~ are upright, - * 2= text is rotated 90 degrees counterclockwise, 3= text is rotated - * 5 0x20 fAutoRot Automatic rotation - * 7-6 0xCO (reserved) Reserved; must be zero - * 7-0 0xFF (reserved) Reserved; must be zero - */ -public class Tick extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3363212452589555220L; - byte tktMajor, tktMinor, tit; - short grbit; - short rot; - - public void init() { - super.init(); - tktMajor = this.getByteAt(0); - tktMinor = this.getByteAt(1); - tit = this.getByteAt(2); - grbit = ByteTools.readShort(this.getByteAt(24), this.getByteAt(25)); - // TODO: Finish ops - rot = (short) ((grbit & 0x1C) >> 2); - } - - // 20070723 KSC: Need to create new records - public static XLSRecord getPrototype() { - Tick t = new Tick(); - t.setOpcode(TICK); - t.setData(t.PROTOTYPE_BYTES); - t.init(); // important when we parse options ... - return t; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{2, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 77, 0, 0, 0}; - - private void updateRecord() { - this.getData()[0] = tktMajor; - this.getData()[1] = tktMinor; - this.getData()[2] = tit; - byte[] b = ByteTools.shortToLEBytes(grbit); - this.getData()[24] = b[0]; - this.getData()[25] = b[1]; - } - - /** - * set generic Tick option - *
                      op/val can be one of: - *
                      tickLblPos none, low, high or nextTo - *
                      majorTickMark none, in, out, cross - *
                      minorTickMark none, in, out, cross - * - * @param op - * @param val - */ - public void setOption(String op, String val) { - if (op.equals("tickLblPos")) { // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick - if (val.equals("high")) - tit = 2; - else if (val.equals("low")) - tit = 1; - else if (val.equals("none")) - tit = 0; - else if (val.equals("nextTo")) - tit = 3; - } else if (op.equals("majorTickMark")) { // major tick marks (cross, in, none, out) - if (val.equals("cross")) - tktMajor = 3; - else if (val.equals("in")) - tktMajor = 1; - else if (val.equals("out")) - tktMajor = 2; - else if (val.equals("none")) - tktMajor = 0; - } else if (op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) - if (val.equals("cross")) - tktMinor = 3; - else if (val.equals("in")) - tktMinor = 1; - else if (val.equals("out")) - tktMinor = 2; - else if (val.equals("none")) - tktMinor = 0; - } - updateRecord(); - } - /* 4 tktMajor 1 Type of major tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line - * 5 tktMinor 1 Type of minor tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line - * 6 tit 1 Tick label position relative to axis line 0= invisible (none) 1 = low end of plot area 2 = high end of plot area 3 = next to axis - */ - - /** - * retrieve generic Value axis option as OOXML string - *
                      can be one of: - *
                      tickLblPos none, low, high or nextTo - *
                      majorTickMark none, in, out, cross - *
                      minorTickMark none, in, out, cross - * - * @param op - * @return - */ - public String getOption(String op) { - if (op.equals("tickLblPos")) { // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick - switch (tit) { - case 0: - return "none"; - case 1: - return "low"; - case 2: - return "high"; - case 3: - return "nextTo"; - } - } else if (op.equals("majorTickMark")) {// major tick marks (cross, in, none, out) - switch (tktMajor) { - case 0: - return "none"; - case 1: - return "in"; - case 2: - return "out"; - case 3: - return "cross"; - } - } else if (op.equals("minorTickMark")) { // minor tick marks (cross, in, none, out) - switch (tktMinor) { - case 0: - return "none"; - case 1: - return "in"; - case 2: - return "out"; - case 3: - return "cross"; - } - } - return null; - } - - /** - * returns true if should show minor tick marks - * - * @return - */ - public boolean showMinorTicks() { - return tktMinor != 0; - } - - /** - * returns true if should show major tick marks - * - * @return - */ - public boolean showMajorTicks() { - return tktMajor != 0; - } - - /** - * 0= no rotation (text appears left-to-right), - * 1= Text is drawn stacked, top-to-bottom, with the letters upright. - * 2= text is rotated 90 degrees counterclockwise, - * 3= text is rotated at 90 degrees clockwise. - * - * @return - */ - public short getRotation() { - return rot; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/charts/Tick.kt b/src/main/java/io/starter/formats/XLS/charts/Tick.kt new file mode 100644 index 0000000..b3b15e0 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/Tick.kt @@ -0,0 +1,212 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **Tick: Tick Marks and Labels Format (0x101e)** + * + * + * Offset Name Size Contents + * 4 tktMajor 1 Type of major tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line + * 5 tktMinor 1 Type of minor tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line + * 6 tit 1 Tick label position relative to axis line 0= invisible (none) 1 = low end of plot area 2 = high end of plot area 3 = next to axis + * 7 wBkgMode 2 Background mode: I = transparent 2 = opaque + * 8 rgb 4 Tick-label text color; ROB value, high byte = 0 + * 12 (reserved) 16 Reserved; must be zero + * 28 grbit 2 Display flags + * 30 icv 2 Index to color of tick label + * 32 (reserved) 2 Reserved; must be zero + * + * + * The grbit field contains the following option flags. + * + * + * Bits Mask Name + * 0 0xOl fAutoColor Automatic text color + * 1 0x02 fAutoMode Automatic text back~ + * 4-2 0xlC rot 0= no rotation (text appears left-to-right), 1= text appears top-~~ are upright, + * 2= text is rotated 90 degrees counterclockwise, 3= text is rotated + * 5 0x20 fAutoRot Automatic rotation + * 7-6 0xCO (reserved) Reserved; must be zero + * 7-0 0xFF (reserved) Reserved; must be zero + */ +class Tick : GenericChartObject(), ChartObject { + internal var tktMajor: Byte = 0 + internal var tktMinor: Byte = 0 + internal var tit: Byte = 0 + internal var grbit: Short = 0 + /** + * 0= no rotation (text appears left-to-right), + * 1= Text is drawn stacked, top-to-bottom, with the letters upright. + * 2= text is rotated 90 degrees counterclockwise, + * 3= text is rotated at 90 degrees clockwise. + * + * @return + */ + var rotation: Short = 0 + internal set + + private val PROTOTYPE_BYTES = byteArrayOf(2, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 77, 0, 0, 0) + + override fun init() { + super.init() + tktMajor = this.getByteAt(0) + tktMinor = this.getByteAt(1) + tit = this.getByteAt(2) + grbit = ByteTools.readShort(this.getByteAt(24).toInt(), this.getByteAt(25).toInt()) + // TODO: Finish ops + rotation = (grbit and 0x1C shr 2).toShort() + } + + private fun updateRecord() { + this.data[0] = tktMajor + this.data[1] = tktMinor + this.data[2] = tit + val b = ByteTools.shortToLEBytes(grbit) + this.data[24] = b[0] + this.data[25] = b[1] + } + + /** + * set generic Tick option + *

                      op/val can be one of: + *

                      tickLblPos none, low, high or nextTo + *

                      majorTickMark none, in, out, cross + *

                      minorTickMark none, in, out, cross + * + * @param op + * @param val + */ + fun setOption(op: String, `val`: String) { + if (op == "tickLblPos") { // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick + if (`val` == "high") + tit = 2 + else if (`val` == "low") + tit = 1 + else if (`val` == "none") + tit = 0 + else if (`val` == "nextTo") + tit = 3 + } else if (op == "majorTickMark") { // major tick marks (cross, in, none, out) + if (`val` == "cross") + tktMajor = 3 + else if (`val` == "in") + tktMajor = 1 + else if (`val` == "out") + tktMajor = 2 + else if (`val` == "none") + tktMajor = 0 + } else if (op == "minorTickMark") { // minor tick marks (cross, in, none, out) + if (`val` == "cross") + tktMinor = 3 + else if (`val` == "in") + tktMinor = 1 + else if (`val` == "out") + tktMinor = 2 + else if (`val` == "none") + tktMinor = 0 + } + updateRecord() + } + /* 4 tktMajor 1 Type of major tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line + * 5 tktMinor 1 Type of minor tick mark 0= invisible (none) I = inside of axis line 2 = outside of axis line 3 = cross axis line + * 6 tit 1 Tick label position relative to axis line 0= invisible (none) 1 = low end of plot area 2 = high end of plot area 3 = next to axis + */ + + /** + * retrieve generic Value axis option as OOXML string + *

                      can be one of: + *

                      tickLblPos none, low, high or nextTo + *

                      majorTickMark none, in, out, cross + *

                      minorTickMark none, in, out, cross + * + * @param op + * @return + */ + fun getOption(op: String): String? { + if (op == "tickLblPos") { // val= high (=at high end of perp. axis), low (=at low end of perp. axis), nextTo, none (=no axis labels) Tick + when (tit) { + 0 -> return "none" + 1 -> return "low" + 2 -> return "high" + 3 -> return "nextTo" + } + } else if (op == "majorTickMark") {// major tick marks (cross, in, none, out) + when (tktMajor) { + 0 -> return "none" + 1 -> return "in" + 2 -> return "out" + 3 -> return "cross" + } + } else if (op == "minorTickMark") { // minor tick marks (cross, in, none, out) + when (tktMinor) { + 0 -> return "none" + 1 -> return "in" + 2 -> return "out" + 3 -> return "cross" + } + } + return null + } + + /** + * returns true if should show minor tick marks + * + * @return + */ + fun showMinorTicks(): Boolean { + return tktMinor.toInt() != 0 + } + + /** + * returns true if should show major tick marks + * + * @return + */ + fun showMajorTicks(): Boolean { + return tktMajor.toInt() != 0 + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 3363212452589555220L + + // 20070723 KSC: Need to create new records + // important when we parse options ... + val prototype: XLSRecord? + get() { + val t = Tick() + t.opcode = XLSConstants.TICK + t.data = t.PROTOTYPE_BYTES + t.init() + return t + } + } + +} diff --git a/src/main/java/io/starter/formats/XLS/charts/AlRuns.java b/src/main/java/io/starter/formats/XLS/charts/Units.kt similarity index 71% rename from src/main/java/io/starter/formats/XLS/charts/AlRuns.java rename to src/main/java/io/starter/formats/XLS/charts/Units.kt index 7d2af01..653bb08 100644 --- a/src/main/java/io/starter/formats/XLS/charts/AlRuns.java +++ b/src/main/java/io/starter/formats/XLS/charts/Units.kt @@ -20,19 +20,21 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ - -package io.starter.formats.XLS.charts; +package io.starter.formats.XLS.charts /** - * AiRuns: Text Formatting (0x1050) + * **Units: Chart Units (0x1001) ** */ -public class AlRuns extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5255886234371123315L; +class Units : GenericChartObject(), ChartObject { + + override fun init() { + super.init() + } - public void init() { - super.init(); + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8391773940364481970L } } diff --git a/src/main/java/io/starter/formats/XLS/charts/ValueRange.java b/src/main/java/io/starter/formats/XLS/charts/ValueRange.java deleted file mode 100644 index cd27a14..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/ValueRange.java +++ /dev/null @@ -1,565 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * ValueRange: Defines Value Axis Scale (0x101f) - * Offset Name Size Contents - * 4 numMin 8 Minimum value on axis. MUST be less than the value of numMax. If the value of fAutoMin is 1, this field MUST be ignored. - * 12 numMax 8 Maximum value on axis. MUST be greater than the value of numMin. If the value of fAutoMax is 1, this field MUST be ignored. - * 20 numMajor 8 Value of major increment. MUST be greater than or equal to the value of numMinor. If the value of fAutoMajor is 1, this field MUST be ignored. - * 28 numMinor 8 Value of minor increment. MUST be greater than or equal to zero. If the value of fAutoMinor is 1, this field MUST be ignored. - * 36 numCross 8 Value where category axis crosses. If the value of fAutoCross is 1, this field MUST be ignored. - * 44 grbit 2 Format flags - *

                      - *

                      - * grbit - * 0 0x1 fAutoMin Automatic Minimum Selected - * 0 The value specified by numMin is used as the minimum value of the value axis. - * 1 numMin is calculated such that the data point with the minimum value can be displayed in the plot area. - * 1 0x2 fAutoMax Automatic Maximum Selected - * 0 The value specified by numMax is used as the maximum value of the value axis. - * 1 numMax is calculated such that the data point with the maximum value can be displayed in the plot area. - * 2 0x4 fAutoMajor Automatic Major Unit Selected - * 0 The value specified by numMax is used as the maximum value of the value axis. - * 1 numMax is calculated such that the data point with the maximum value can be displayed in the plot area. - * 3 0x8 fAutoMinor Automatic Minor Unit Selected - * 0 The value specified by numMinor is used as the interval at which minor tick marks and minor gridlines are displayed. - * 1 numMinor is calculated automatically. - * 4 0x10 fAutoCross Automatic Category Crossing Point Selected - * 0 The value specified by numCross is used as the point at which the other axes in the axis group cross this value axis. - * 1 numCross is calculated so that the crossing point is displayed in the plot area. - * 5 0x20 fLogScale Log Scale - * 0 The scale of the value axis is linear. - * 1 The scale of the value axis is logarithmic. The default base of the logarithmic scale is 10, unless a CrtMlFrt record follows this record, specifying the base in a XmlTkLogBaseFrt structure. - * 6 0x40 fReverse Values in reverse order - * 0 Values are displayed from smallest-to-largest, from left-to-right, or from bottom-to-top, respectively, depending on the orientation of the axis. - * 1 The values are displayed in reverse order, meaning largest-to-smallest, from left-to-right, or from bottom-to-top, respectively. - * 7 0x80 fMaxCross Category is to cross at maximum value - * 0 The other axes in the axis group cross this value axis at the value specified by numCross. - * 1 The other axes in the axis group cross the value axis at the maximum value. If fMaxCross is 1, then both fAutoCross and numCross MUST be ignored. - *

                      - * All 8-byte numbers in the preceding table are IEEE floating-point numbers. - * The numMin field defines the minimum numeric value that appears along the value axis. This field is all zeros if Auto Minimum is selected on the Scale tab of the Format Axis dialog box. - * The numMax field defines the maximum value displayed along the value axis and is all zeros if Auto Maximum is selected. - * The numMajor field defines the increment (unit) of the major value divisions (gridlines) along the value axis. The numMajor field is all zeros if Auto Major Unit is selected on the Scale tab of the Format Axis dialog box. - * The numMinor field defines the minor value divisions (gridlines) along the value axis and is all zeros if Auto Minor Unit is selected. - * The numCross field defines the value along the value axis at which the category axis crosses. This field is all zeros if Auto Category Axis Crosses At is selected. - */ -public class ValueRange extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2989883115978826628L; - double numMin, numMax, numMajor, numMinor, numCross; - // double yMin= 0.0, yMax= 0.0; - short grbit = 0; - boolean fAutoMin, fAutoMax, fAutoMajor, fAutoMinor, fAutoCross, fLogScale, fReverse, fMaxCross; - - public void init() { - super.init(); - numMin = ByteTools.eightBytetoLEDouble(this.getBytesAt(0, 8)); - numMax = ByteTools.eightBytetoLEDouble(this.getBytesAt(8, 16)); - numMajor = ByteTools.eightBytetoLEDouble(this.getBytesAt(16, 24)); - numMinor = ByteTools.eightBytetoLEDouble(this.getBytesAt(24, 32)); - numCross = ByteTools.eightBytetoLEDouble(this.getBytesAt(32, 40)); - grbit = ByteTools.readShort(this.getByteAt(40), this.getByteAt(41)); - fMaxCross = (grbit & 0x80) == 0x80; - fAutoMin = (grbit & 0x1) == 0x1; - fAutoMax = (grbit & 0x2) == 0x2; - fAutoMajor = (grbit & 0x4) == 0x4; - fAutoMinor = (grbit & 0x8) == 0x8; - fAutoCross = (grbit & 0x10) == 0x10; - fLogScale = (grbit & 0x20) == 0x20; - fReverse = (grbit & 0x40) == 0x40; - } - - // 20070723 KSC: Need to create new records - public static XLSRecord getPrototype() { - ValueRange vr = new ValueRange(); - vr.setOpcode(VALUERANGE); - vr.setData(vr.PROTOTYPE_BYTES); - vr.init(); // important when we parse options - return vr; - } - - private byte[] PROTOTYPE_BYTES = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 1}; - - - /** Excel automatic calculations: - * Given: - * yMax The maximum y value used in your chart. - yMin The minimum y value used in your chart. - xMax The maximum x value used in your chart. This applies only to - charts that use x values, such as scatter and bubble charts. - xMin The minimum x value used in your chart. This applies only to - charts that use x values, such as scatter and bubble charts. - - When you create a chart in Microsoft Excel, there are three possible scenarios that may apply to your data: - - The yMax and yMin values are both non-negative (greater than or equal to zero). This is Scenario One. - The yMax and yMin values are both non-positive (less than or equal to zero). This is Scenario Two. - The yMax value is positive, and the yMin value is negative. This is Scenario Three. - ************************************************************************************************************************************ - The major unit used by the y-axis is automatically determined by Microsoft Excel, based on all of the data included in the chart. - ************************************************************************************************************************************ - The following scenarios use this default major unit. - * @return - */ - - - /** - * When you create a chart in Microsoft Excel, - * there are three possible scenarios that can apply to your data: - * Scenario one: the yMax and yMin values are both positive or equal to zero. - * Scenario two: the yMax and yMin values are both negative or equal to zero. - * Scenario three: the yMax value is positive, and the yMin value is negative. - - The major unit used by the y-axis is automatically determined by Microsoft Excel, - based on all of the data included in the chart. - - The following scenarios use this default major unit. - Scenario one: - * If the chart is a 2-D area, column, bar, line or x-y scatter chart, the automatic maximum for the y-axis is the first major unit greater than or equal to the value returned by the following equation: - yMax + 0.05 * ( yMax - yMin ) - Otherwise, the automatic maximum for the y-axis is the first major unit greater than or equal to yMax. - * If the difference between yMax and yMin is greater than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is zero. - * If the difference between yMax and yMin is less than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is the first major unit less than or equal to the value returned by the following equation: - yMin - ( ( yMax - yMin ) / 2 ) - Exception: If the chart is an x-y scatter or bubble chart, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. - - Scenario two: - same as above except that - yMin + 0.05 * ( yMin - yMax ) - Otherwise, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. - and - yMax - ( ( yMin - yMax ) / 2 ) - - Scenario 3 - * The automatic maximum for the y-axis is the first major unit greater than or equal to the value returned by the following equation: - yMax + 0.05 * ( yMax - yMin ) - * The automatic minimum for the y-axis is the first major unit less than or equal to the value returned by the following equation: - yMin + 0.05 * ( yMin - yMax ) - - The above information also applies to charts that use x values, - such as x-y scatter charts and bubble charts. - For these types of charts, - substitute xMax and xMin for yMax and yMin in the above scenarios. - */ - - /** - * return minumum scale value -- see setMinMax for calc - * - * @return double - */ - public double getMin() { - return numMin; - } - - /** - * return max scale value -- see setMinMax for calc - * - * @return double - */ - public double getMax() { - return numMax; - } - - /** - * all important major tick step - - * - * @return double major tick step value - * @see setMinMax - */ - public double getMajorTick() { - return numMajor; - } - - public double getMinorTick() { - return numMinor; - } // fautominor --woundn't even know how to calculate! - - /** - * sets the max and min values for the axis and uses these - * to calculate the all-important major unit (and minor unit) step - *
                      - * NOTE that Excel keeps it's "automatic" calculation private so - * below is the best guess looking at existing chart data - *
                      max and min units are also based upon chart size, so this is only a rough approximation - * - * @param yMax double maximum series value - * @param yMin double minimum series value - */ - public void setMaxMin(double yMax, double yMin) { - /** - * When you create a chart in Microsoft Excel, there are three possible scenarios that can apply to your data: - Scenario one: the yMax and yMin values are both positive or equal to zero. - Scenario two: the yMax and yMin values are both negative or equal to zero. - Scenario three: the yMax value is positive, and the yMin value is negative. - - NOTE: the difficultly in these calculations is the major unit, of which the maximum scale is based. - There is no true information regarding the major unit, unfortunately, except that it is based on - "all the data of the chart" - */ - if (fAutoMajor && fAutoMinor) { - int charttype = this.getParentChart().getChartType(); - if (yMax >= 0 && yMin >= 0 && yMax != yMin) { - // Major Unit Calculation -- best guest TODO: would be great to find out Excel's algorithm! -// TODO: major unit is affected by height (or width, for bar charts) ***** develop algorithm!!! - // add a tiny pad for range ... - double diff = (yMax * 1.1 - yMin); - if (fAutoMax) { - double logDiff = Math.floor(Math.log10(diff)); - double f = (diff) / Math.pow(10, logDiff); - if (f <= 1) - f = 1; - else if (f <= 2) - f = 2; - else if (f <= 5) - f = 5; - else - f = 10; - f = f * Math.pow(10, logDiff); //scaled up max - numMajor = f * .1; // 1/10th of scaled up max - - } else { - numMajor = numMax / 10.0; - } - /** - * If the chart is a 2-D area, column, bar, line or x-y scatter chart, - * the automatic maximum for the y-axis is the first major unit greater - * than or equal to the value returned by the following equation: - yMax + 0.05 * ( yMax - yMin ) - Otherwise, the automatic maximum for the y-axis is the first major unit - greater than or equal to yMax. - */ - if (fAutoMax) { - if (charttype == ChartConstants.AREACHART || - charttype == ChartConstants.COLCHART || - charttype == ChartConstants.BARCHART || - charttype == ChartConstants.LINECHART || - charttype == ChartConstants.SCATTERCHART || - charttype == ChartConstants.BUBBLECHART) { - if (numMajor == (int) numMajor) // int scale - usual case - numMax = Math.ceil(yMax + 0.05 * diff * 1.1); - else - numMax = yMax + 0.05 * diff * 1.1; - if (charttype == ChartConstants.BUBBLECHART) - numMax += numMajor; // is this true in ALL CASES???? - if ((numMax % numMajor) != 0) // if not = to scale, scale up to next major unit - numMax = Math.floor((numMax + numMajor) / numMajor) * numMajor; - } else { - numMax = Math.floor((yMax + numMajor) / numMajor) * numMajor; - } - } - /** - * If the difference between yMax and yMin is greater than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is zero. - */ - if (fAutoMin) { - /** - * Exception: If the chart is an x-y scatter or bubble chart, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. - */ - if (charttype == ChartConstants.SCATTERCHART || - charttype == ChartConstants.BUBBLECHART) { - if (yMin % numMajor != 0) { - numMin = Math.floor((yMin - numMajor) / numMajor) * numMajor; - numMin = Math.round(numMin); - } - } else { - if ((yMax - yMin) > (numMax * .16667)) { - numMin = 0; - } else { // the first major unit less than or equal to the value from the below equation: - numMin = yMin - ((numMax - yMin) / 2); - if ((numMin % numMajor) != 0) - numMin = Math.floor((numMin - numMajor) / numMajor) * numMajor; - } - } - } - - // 20120905 KSC: recheck major to ensure not more than 10 steps ... - if (numMin >= 0 && numMax >= 0) { - if (((numMax - numMin) / numMajor) > 9) { - diff = (numMax * 1.1 - numMin); - double logDiff = Math.floor(Math.log10(diff)); - double f = (diff) / Math.pow(10, logDiff); - if (f <= 1) - f = 1; - else if (f <= 2) - f = 2; - else if (f <= 5) - f = 5; - else - f = 10; - f = f * Math.pow(10, logDiff); //scaled up max - numMajor = f * .1; // 1/10th of scaled up max - } - } - numMinor = numMajor / 5; // seems to be the correct calculation ... - } else if (yMax < 0 && yMin < 0) { - double diff = (yMin - yMax); - numMin = yMin + 0.05 * diff; - if (diff > (yMin * .16667)) { - numMax = 0; - } else { - numMax = yMax - (diff / 2); - } - numMax = Math.floor((numMax + numMajor) / numMajor) * numMajor; - if (fAutoMinor) { - numMinor = numMajor / 5; // just a guess, really - // Exception: If the chart is an x-y scatter or bubble chart, the automatic maximum for the y-axis is the first major unit greater than or equal to yMax. - } else { // yMax > 0 && yMin < 0 - numMax = yMax + 0.05 * (yMax - yMin); - numMin = yMin + 0.05 * (yMin - yMax); - } - } else { - numMax = yMax; - numMin = yMin; - } - } - } - - /** - * static utlity to calculate the min and max on a scale in a given area - * NOTE: this is usually the Y Axis, but scatter charts can have a value axis on the X Axis - * - * @param MaxVal Actual Maximum Value on Axis - * @param MinVal Actual Mimumum Value on Axis - * @param area Area of Axis in pixels (I think :)) - * @return - */ - public static double[] calcMaxMin(double MaxVal, double MinVal, double area) { - // h==235 is normal, and the below alg. appear correct for it - // h==776 (bar chart) ??? SIGH ... - double ymax = MaxVal; - double ymin = MinVal; - double numMajor = ymax - ymin; - if (numMajor > 0 && numMajor < 20) - numMajor = 2; - else if (numMajor > 20 && numMajor < 100) - numMajor = 20; - else if (numMajor > 100 && numMajor < 500) - numMajor = 50; - if (ymax >= 0) { - ymax = Math.floor((ymax + numMajor) / numMajor) * numMajor; - } else { - ymax = Math.floor((ymax + numMajor) / numMajor) * numMajor; - } - - double numMin = ymin - numMajor; - if ((numMin % numMajor) > 0) - numMin -= numMajor; - if (ymin >= 0) - numMin = Math.max(ymin - numMajor, 0); - numMin = Math.round(numMin); - - double numMax = 0; - if (ymax >= 0) { - numMax = (int) Math.floor((ymax + numMajor) / numMajor) * numMajor; - } else { - numMax = (int) Math.floor((ymax + numMajor) / numMajor) * numMajor; - } - - return new double[]{numMin, numMajor, ymax}; - } - - /** - * sets a specific OOXML axis option - *
                      can be one of: - *
                      crosses possible crossing points (autoZero, max, min) - *
                      crossBeteween whether axis crosses the cat. axis between or on categories (between, midCat) - *
                      crossesAt where on axis the perpendicular axis crosses (double val) - *
                      majorTickMark major tick mark position (cross, in, none, out) - *
                      minorTickMark minor tick mark position ("") - *
                      tickLblPos tick label position (high, low, nextTo, none) - *
                      majorUnit distance between major tick marks (val, date ax only) (double >= 0) - *
                      minorUnit distance between minor tick marks (val, date ax only) (double >= 0) - * - * @param op - * @param val - */ - public boolean setOption(String op, String val) { - if (op.equals("crossesAt")) - // specifies where axis crosses -- numCross or catCross - numCross = new Double(val).doubleValue(); - else if (op.equals("orientation")) { // axis orientation minMax or maxMin -- fReverse - fReverse = (val.equals("maxMin")); // means in reverse order - ByteTools.updateGrBit(grbit, fReverse, 6); - } else if (op.equals("crosses")) { // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross - if (val.equals("max")) { // TODO: this is probly wrong - fMaxCross = true; - ByteTools.updateGrBit(grbit, fMaxCross, 7); - } else if (val.equals("autoZero")) { - fAutoCross = true; // is this correct?? - ByteTools.updateGrBit(grbit, fAutoCross, 4); - } else if (val.equals("min")) { - fAutoCross = false; - ByteTools.updateGrBit(grbit, fAutoCross, 4); - } - } else if (op.equals("crossBetween")) { // val= between, midCat, crossBetween - if (val.equals("between")) - fAutoCross = true; - // otherwise do what??? - } else if (op.equals("max")) { // axis max - valueRange only? - numMax = new Double(val).doubleValue(); - // turn off automatic scaling - grbit = (short) (grbit & 0xFD); // turn off bit 2 - } else if (op.equals("min")) { // axis min- valueRange only? - numMin = new Double(val).doubleValue(); - // turn off automatic scaling - grbit = (short) (grbit & 0xFE); // turn off bit 1 - } else if (op.equals("majorUnit")) - numMajor = new Double(val).doubleValue(); - else if (op.equals("minorUnit")) - numMinor = new Double(val).doubleValue(); - else - return false; - this.updateRecord(); - return true; - } - - - /** - * retrieve generic Value axis option - *
                      can be one of: - *
                      crosses possible crossing points (autoZero, max, min) - *
                      crossBeteween whether axis crosses the cat. axis between or on categories (between, midCat) - *
                      crossesAt where on axis the perpendicular axis crosses (double val) - *
                      majorTickMark major tick mark position (cross, in, none, out) - *
                      minorTickMark minor tick mark position ("") - *
                      tickLblPos tick label position (high, low, nextTo, none) - *
                      majorUnit distance between major tick marks (val, date ax only) (double >= 0) - *
                      minorUnit distance between minor tick marks (val, date ax only) (double >= 0) - * - * @param op - * @return - */ - public String getOption(String op) { - if (op.equals("crossesAt")) - return String.valueOf(numCross); - if (op.equals("orientation")) - return (fReverse) ? "maxMin" : "minMax"; - if (op.equals("crosses")) { - if (fMaxCross) return "max"; - if (fAutoCross) return "autoZero"; // correct?? - return "min"; // correct?? - } - if (op.equals("crossBetween")) // val= between, midCat, crossBetween - return "between"; // TODO: figure out! - if (op.equals("max")) - return String.valueOf(numMax); - if (op.equals("min")) - return String.valueOf(numMin); - if (op.equals("majorUnit")) - return String.valueOf(numMajor); - if (op.equals("minorUnit")) - return String.valueOf(numMinor); - return null; - } - - /** - * set the minimum scale value of this Y or value axis - *
                      Doing so turns automatic minimum off - * - * @param min - */ - public void setMin(double min) { - numMin = min; - // turn off automatic scaling - grbit = (short) (grbit & 0xFE); // turn off bit 1 - updateRecord(); - } - - /** - * set the max scale value of this Y or value axis - *
                      Doing so turns automatic maximum off - * - * @param max - */ - public void setMax(double max) { - numMax = max; - // turn off automatic scaling - grbit = (short) (grbit & 0xFD); // turn off bit 2 - updateRecord(); - } - - public boolean isAutomaticScale() { - return fAutoMin || fAutoMax || fAutoMinor || fAutoMajor; - } - - public boolean isAutomaticMax() { - return fAutoMax; - } - - public boolean isAutomaticMin() { - return fAutoMin; - } - - public void setAutomaticMin(boolean b) { - fAutoMin = b; - if (b) { - grbit = (short) (grbit | 0x1); // turn on bit 1 - numMin = 0.0; - } else - grbit = (short) (grbit & 0xFE); // turn off bit 1 - updateRecord(); - } - - public void setAutomaticMax(boolean b) { - fAutoMax = b; - if (b) { - grbit = (short) (grbit | 0x2); // turn on bit 2 - numMax = 0.0; - } else - grbit = (short) (grbit & 0xFD); // turn off bit 2 - updateRecord(); - } - - private void updateRecord() { - byte[] b = ByteTools.doubleToLEByteArray(numMin); - System.arraycopy(b, 0, this.getData(), 0, 8); - b = ByteTools.doubleToLEByteArray(numMax); - System.arraycopy(b, 0, this.getData(), 8, 8); - b = ByteTools.doubleToLEByteArray(numMajor); - System.arraycopy(b, 0, this.getData(), 16, 8); - b = ByteTools.doubleToLEByteArray(numMinor); - System.arraycopy(b, 0, this.getData(), 24, 8); - b = ByteTools.doubleToLEByteArray(numCross); - System.arraycopy(b, 0, this.getData(), 32, 8); - b = ByteTools.shortToLEBytes(grbit); - this.getData()[40] = b[0]; - this.getData()[41] = b[1]; - } - - /** - * returns true if axis should be displayed on RHS of chart - * false for default LHS - * - * @return - */ - public boolean isReversed() { - return fReverse; - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/ValueRange.kt b/src/main/java/io/starter/formats/XLS/charts/ValueRange.kt new file mode 100644 index 0000000..9d9d3ee --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/ValueRange.kt @@ -0,0 +1,571 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **ValueRange: Defines Value Axis Scale (0x101f)** + * Offset Name Size Contents + * 4 numMin 8 Minimum value on axis. MUST be less than the value of numMax. If the value of fAutoMin is 1, this field MUST be ignored. + * 12 numMax 8 Maximum value on axis. MUST be greater than the value of numMin. If the value of fAutoMax is 1, this field MUST be ignored. + * 20 numMajor 8 Value of major increment. MUST be greater than or equal to the value of numMinor. If the value of fAutoMajor is 1, this field MUST be ignored. + * 28 numMinor 8 Value of minor increment. MUST be greater than or equal to zero. If the value of fAutoMinor is 1, this field MUST be ignored. + * 36 numCross 8 Value where category axis crosses. If the value of fAutoCross is 1, this field MUST be ignored. + * 44 grbit 2 Format flags + * + * + * + * + * grbit + * 0 0x1 fAutoMin Automatic Minimum Selected + * 0 The value specified by numMin is used as the minimum value of the value axis. + * 1 numMin is calculated such that the data point with the minimum value can be displayed in the plot area. + * 1 0x2 fAutoMax Automatic Maximum Selected + * 0 The value specified by numMax is used as the maximum value of the value axis. + * 1 numMax is calculated such that the data point with the maximum value can be displayed in the plot area. + * 2 0x4 fAutoMajor Automatic Major Unit Selected + * 0 The value specified by numMax is used as the maximum value of the value axis. + * 1 numMax is calculated such that the data point with the maximum value can be displayed in the plot area. + * 3 0x8 fAutoMinor Automatic Minor Unit Selected + * 0 The value specified by numMinor is used as the interval at which minor tick marks and minor gridlines are displayed. + * 1 numMinor is calculated automatically. + * 4 0x10 fAutoCross Automatic Category Crossing Point Selected + * 0 The value specified by numCross is used as the point at which the other axes in the axis group cross this value axis. + * 1 numCross is calculated so that the crossing point is displayed in the plot area. + * 5 0x20 fLogScale Log Scale + * 0 The scale of the value axis is linear. + * 1 The scale of the value axis is logarithmic. The default base of the logarithmic scale is 10, unless a CrtMlFrt record follows this record, specifying the base in a XmlTkLogBaseFrt structure. + * 6 0x40 fReverse Values in reverse order + * 0 Values are displayed from smallest-to-largest, from left-to-right, or from bottom-to-top, respectively, depending on the orientation of the axis. + * 1 The values are displayed in reverse order, meaning largest-to-smallest, from left-to-right, or from bottom-to-top, respectively. + * 7 0x80 fMaxCross Category is to cross at maximum value + * 0 The other axes in the axis group cross this value axis at the value specified by numCross. + * 1 The other axes in the axis group cross the value axis at the maximum value. If fMaxCross is 1, then both fAutoCross and numCross MUST be ignored. + * + * + * All 8-byte numbers in the preceding table are IEEE floating-point numbers. + * The numMin field defines the minimum numeric value that appears along the value axis. This field is all zeros if Auto Minimum is selected on the Scale tab of the Format Axis dialog box. + * The numMax field defines the maximum value displayed along the value axis and is all zeros if Auto Maximum is selected. + * The numMajor field defines the increment (unit) of the major value divisions (gridlines) along the value axis. The numMajor field is all zeros if Auto Major Unit is selected on the Scale tab of the Format Axis dialog box. + * The numMinor field defines the minor value divisions (gridlines) along the value axis and is all zeros if Auto Minor Unit is selected. + * The numCross field defines the value along the value axis at which the category axis crosses. This field is all zeros if Auto Category Axis Crosses At is selected. + */ +class ValueRange : GenericChartObject(), ChartObject { + internal var numMin: Double = 0.toDouble() + internal var numMax: Double = 0.toDouble() + /** + * all important major tick step - + * + * @return double major tick step value + * @see setMinMax + */ + var majorTick: Double = 0.toDouble() + internal set + var minorTick: Double = 0.toDouble() + internal set // fautominor --woundn't even know how to calculate! + internal var numCross: Double = 0.toDouble() + // double yMin= 0.0, yMax= 0.0; + internal var grbit: Short = 0 + internal var fAutoMin: Boolean = false + internal var fAutoMax: Boolean = false + internal var fAutoMajor: Boolean = false + internal var fAutoMinor: Boolean = false + internal var fAutoCross: Boolean = false + internal var fLogScale: Boolean = false + /** + * returns true if axis should be displayed on RHS of chart + * false for default LHS + * + * @return + */ + var isReversed: Boolean = false + internal set + internal var fMaxCross: Boolean = false + + private val PROTOTYPE_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 1) + + + /** Excel automatic calculations: + * Given: + * yMax The maximum y value used in your chart. + * yMin The minimum y value used in your chart. + * xMax The maximum x value used in your chart. This applies only to + * charts that use x values, such as scatter and bubble charts. + * xMin The minimum x value used in your chart. This applies only to + * charts that use x values, such as scatter and bubble charts. + * + * When you create a chart in Microsoft Excel, there are three possible scenarios that may apply to your data: + * + * The yMax and yMin values are both non-negative (greater than or equal to zero). This is Scenario One. + * The yMax and yMin values are both non-positive (less than or equal to zero). This is Scenario Two. + * The yMax value is positive, and the yMin value is negative. This is Scenario Three. + * + * The major unit used by the y-axis is automatically determined by Microsoft Excel, based on all of the data included in the chart. + * + * The following scenarios use this default major unit. + * @return + */ + + + /** + * When you create a chart in Microsoft Excel, + * there are three possible scenarios that can apply to your data: + * Scenario one: the yMax and yMin values are both positive or equal to zero. + * Scenario two: the yMax and yMin values are both negative or equal to zero. + * Scenario three: the yMax value is positive, and the yMin value is negative. + * + * The major unit used by the y-axis is automatically determined by Microsoft Excel, + * based on all of the data included in the chart. + * + * The following scenarios use this default major unit. + * Scenario one: + * If the chart is a 2-D area, column, bar, line or x-y scatter chart, the automatic maximum for the y-axis is the first major unit greater than or equal to the value returned by the following equation: + * yMax + 0.05 * ( yMax - yMin ) + * Otherwise, the automatic maximum for the y-axis is the first major unit greater than or equal to yMax. + * If the difference between yMax and yMin is greater than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is zero. + * If the difference between yMax and yMin is less than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is the first major unit less than or equal to the value returned by the following equation: + * yMin - ( ( yMax - yMin ) / 2 ) + * Exception: If the chart is an x-y scatter or bubble chart, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. + * + * Scenario two: + * same as above except that + * yMin + 0.05 * ( yMin - yMax ) + * Otherwise, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. + * and + * yMax - ( ( yMin - yMax ) / 2 ) + * + * Scenario 3 + * The automatic maximum for the y-axis is the first major unit greater than or equal to the value returned by the following equation: + * yMax + 0.05 * ( yMax - yMin ) + * The automatic minimum for the y-axis is the first major unit less than or equal to the value returned by the following equation: + * yMin + 0.05 * ( yMin - yMax ) + * + * The above information also applies to charts that use x values, + * such as x-y scatter charts and bubble charts. + * For these types of charts, + * substitute xMax and xMin for yMax and yMin in the above scenarios. + */ + + /** + * return minumum scale value -- see setMinMax for calc + * + * @return double + */ + /** + * set the minimum scale value of this Y or value axis + *

                      Doing so turns automatic minimum off + * + * @param min + */ + // turn off automatic scaling + // turn off bit 1 + var min: Double + get() = numMin + set(min) { + numMin = min + grbit = (grbit and 0xFE).toShort() + updateRecord() + } + + /** + * return max scale value -- see setMinMax for calc + * + * @return double + */ + /** + * set the max scale value of this Y or value axis + *

                      Doing so turns automatic maximum off + * + * @param max + */ + // turn off automatic scaling + // turn off bit 2 + var max: Double + get() = numMax + set(max) { + numMax = max + grbit = (grbit and 0xFD).toShort() + updateRecord() + } + + val isAutomaticScale: Boolean + get() = fAutoMin || fAutoMax || fAutoMinor || fAutoMajor + + // turn on bit 2 + // turn off bit 2 + var isAutomaticMax: Boolean + get() = fAutoMax + set(b) { + fAutoMax = b + if (b) { + grbit = (grbit or 0x2).toShort() + numMax = 0.0 + } else + grbit = (grbit and 0xFD).toShort() + updateRecord() + } + + // turn on bit 1 + // turn off bit 1 + var isAutomaticMin: Boolean + get() = fAutoMin + set(b) { + fAutoMin = b + if (b) { + grbit = (grbit or 0x1).toShort() + numMin = 0.0 + } else + grbit = (grbit and 0xFE).toShort() + updateRecord() + } + + override fun init() { + super.init() + numMin = ByteTools.eightBytetoLEDouble(this.getBytesAt(0, 8)!!) + numMax = ByteTools.eightBytetoLEDouble(this.getBytesAt(8, 16)!!) + majorTick = ByteTools.eightBytetoLEDouble(this.getBytesAt(16, 24)!!) + minorTick = ByteTools.eightBytetoLEDouble(this.getBytesAt(24, 32)!!) + numCross = ByteTools.eightBytetoLEDouble(this.getBytesAt(32, 40)!!) + grbit = ByteTools.readShort(this.getByteAt(40).toInt(), this.getByteAt(41).toInt()) + fMaxCross = grbit and 0x80 == 0x80 + fAutoMin = grbit and 0x1 == 0x1 + fAutoMax = grbit and 0x2 == 0x2 + fAutoMajor = grbit and 0x4 == 0x4 + fAutoMinor = grbit and 0x8 == 0x8 + fAutoCross = grbit and 0x10 == 0x10 + fLogScale = grbit and 0x20 == 0x20 + isReversed = grbit and 0x40 == 0x40 + } + + /** + * sets the max and min values for the axis and uses these + * to calculate the all-important major unit (and minor unit) step + *

                      + * NOTE that Excel keeps it's "automatic" calculation private so + * below is the best guess looking at existing chart data + *

                      max and min units are also based upon chart size, so this is only a rough approximation + * + * @param yMax double maximum series value + * @param yMin double minimum series value + */ + fun setMaxMin(yMax: Double, yMin: Double) { + /** + * When you create a chart in Microsoft Excel, there are three possible scenarios that can apply to your data: + * Scenario one: the yMax and yMin values are both positive or equal to zero. + * Scenario two: the yMax and yMin values are both negative or equal to zero. + * Scenario three: the yMax value is positive, and the yMin value is negative. + * + * NOTE: the difficultly in these calculations is the major unit, of which the maximum scale is based. + * There is no true information regarding the major unit, unfortunately, except that it is based on + * "all the data of the chart" + */ + if (fAutoMajor && fAutoMinor) { + val charttype = this.parentChart!!.chartType + if (yMax >= 0 && yMin >= 0 && yMax != yMin) { + // Major Unit Calculation -- best guest TODO: would be great to find out Excel's algorithm! + // TODO: major unit is affected by height (or width, for bar charts) ***** develop algorithm!!! + // add a tiny pad for range ... + var diff = yMax * 1.1 - yMin + if (fAutoMax) { + val logDiff = Math.floor(Math.log10(diff)) + var f = diff / Math.pow(10.0, logDiff) + if (f <= 1) + f = 1.0 + else if (f <= 2) + f = 2.0 + else if (f <= 5) + f = 5.0 + else + f = 10.0 + f = f * Math.pow(10.0, logDiff) //scaled up max + majorTick = f * .1 // 1/10th of scaled up max + + } else { + majorTick = numMax / 10.0 + } + /** + * If the chart is a 2-D area, column, bar, line or x-y scatter chart, + * the automatic maximum for the y-axis is the first major unit greater + * than or equal to the value returned by the following equation: + * yMax + 0.05 * ( yMax - yMin ) + * Otherwise, the automatic maximum for the y-axis is the first major unit + * greater than or equal to yMax. + */ + if (fAutoMax) { + if (charttype == ChartConstants.AREACHART || + charttype == ChartConstants.COLCHART || + charttype == ChartConstants.BARCHART || + charttype == ChartConstants.LINECHART || + charttype == ChartConstants.SCATTERCHART || + charttype == ChartConstants.BUBBLECHART) { + if (majorTick == majorTick.toInt().toDouble()) + // int scale - usual case + numMax = Math.ceil(yMax + 0.05 * diff * 1.1) + else + numMax = yMax + 0.05 * diff * 1.1 + if (charttype == ChartConstants.BUBBLECHART) + numMax += majorTick // is this true in ALL CASES???? + if (numMax % majorTick != 0.0) + // if not = to scale, scale up to next major unit + numMax = Math.floor((numMax + majorTick) / majorTick) * majorTick + } else { + numMax = Math.floor((yMax + majorTick) / majorTick) * majorTick + } + } + /** + * If the difference between yMax and yMin is greater than 16.667 percent of the value of yMax, the automatic minimum for the y-axis is zero. + */ + if (fAutoMin) { + /** + * Exception: If the chart is an x-y scatter or bubble chart, the automatic minimum for the y-axis is the first major unit less than or equal to yMin. + */ + if (charttype == ChartConstants.SCATTERCHART || charttype == ChartConstants.BUBBLECHART) { + if (yMin % majorTick != 0.0) { + numMin = Math.floor((yMin - majorTick) / majorTick) * majorTick + numMin = Math.round(numMin).toDouble() + } + } else { + if (yMax - yMin > numMax * .16667) { + numMin = 0.0 + } else { // the first major unit less than or equal to the value from the below equation: + numMin = yMin - (numMax - yMin) / 2 + if (numMin % majorTick != 0.0) + numMin = Math.floor((numMin - majorTick) / majorTick) * majorTick + } + } + } + + // 20120905 KSC: recheck major to ensure not more than 10 steps ... + if (numMin >= 0 && numMax >= 0) { + if ((numMax - numMin) / majorTick > 9) { + diff = numMax * 1.1 - numMin + val logDiff = Math.floor(Math.log10(diff)) + var f = diff / Math.pow(10.0, logDiff) + if (f <= 1) + f = 1.0 + else if (f <= 2) + f = 2.0 + else if (f <= 5) + f = 5.0 + else + f = 10.0 + f = f * Math.pow(10.0, logDiff) //scaled up max + majorTick = f * .1 // 1/10th of scaled up max + } + } + minorTick = majorTick / 5 // seems to be the correct calculation ... + } else if (yMax < 0 && yMin < 0) { + val diff = yMin - yMax + numMin = yMin + 0.05 * diff + if (diff > yMin * .16667) { + numMax = 0.0 + } else { + numMax = yMax - diff / 2 + } + numMax = Math.floor((numMax + majorTick) / majorTick) * majorTick + if (fAutoMinor) { + minorTick = majorTick / 5 // just a guess, really + // Exception: If the chart is an x-y scatter or bubble chart, the automatic maximum for the y-axis is the first major unit greater than or equal to yMax. + } else { // yMax > 0 && yMin < 0 + numMax = yMax + 0.05 * (yMax - yMin) + numMin = yMin + 0.05 * (yMin - yMax) + } + } else { + numMax = yMax + numMin = yMin + } + } + } + + /** + * sets a specific OOXML axis option + *

                      can be one of: + *

                      crosses possible crossing points (autoZero, max, min) + *

                      crossBeteween whether axis crosses the cat. axis between or on categories (between, midCat) + *

                      crossesAt where on axis the perpendicular axis crosses (double val) + *

                      majorTickMark major tick mark position (cross, in, none, out) + *

                      minorTickMark minor tick mark position ("") + *

                      tickLblPos tick label position (high, low, nextTo, none) + *

                      majorUnit distance between major tick marks (val, date ax only) (double >= 0) + *

                      minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + * + * @param op + * @param val + */ + fun setOption(op: String, `val`: String): Boolean { + if (op == "crossesAt") + // specifies where axis crosses -- numCross or catCross + numCross = Double(`val`) + else if (op == "orientation") { // axis orientation minMax or maxMin -- fReverse + isReversed = `val` == "maxMin" // means in reverse order + ByteTools.updateGrBit(grbit, isReversed, 6) + } else if (op == "crosses") { // specifies how axis crosses it's perpendicular axis (val= max, min, autoZero) -- fbetween + fMaxCross?/fAutoCross + fMaxCross + if (`val` == "max") { // TODO: this is probly wrong + fMaxCross = true + ByteTools.updateGrBit(grbit, fMaxCross, 7) + } else if (`val` == "autoZero") { + fAutoCross = true // is this correct?? + ByteTools.updateGrBit(grbit, fAutoCross, 4) + } else if (`val` == "min") { + fAutoCross = false + ByteTools.updateGrBit(grbit, fAutoCross, 4) + } + } else if (op == "crossBetween") { // val= between, midCat, crossBetween + if (`val` == "between") + fAutoCross = true + // otherwise do what??? + } else if (op == "max") { // axis max - valueRange only? + numMax = Double(`val`) + // turn off automatic scaling + grbit = (grbit and 0xFD).toShort() // turn off bit 2 + } else if (op == "min") { // axis min- valueRange only? + numMin = Double(`val`) + // turn off automatic scaling + grbit = (grbit and 0xFE).toShort() // turn off bit 1 + } else if (op == "majorUnit") + majorTick = Double(`val`) + else if (op == "minorUnit") + minorTick = Double(`val`) + else + return false + this.updateRecord() + return true + } + + + /** + * retrieve generic Value axis option + *

                      can be one of: + *

                      crosses possible crossing points (autoZero, max, min) + *

                      crossBeteween whether axis crosses the cat. axis between or on categories (between, midCat) + *

                      crossesAt where on axis the perpendicular axis crosses (double val) + *

                      majorTickMark major tick mark position (cross, in, none, out) + *

                      minorTickMark minor tick mark position ("") + *

                      tickLblPos tick label position (high, low, nextTo, none) + *

                      majorUnit distance between major tick marks (val, date ax only) (double >= 0) + *

                      minorUnit distance between minor tick marks (val, date ax only) (double >= 0) + * + * @param op + * @return + */ + fun getOption(op: String): String? { + if (op == "crossesAt") + return numCross.toString() + if (op == "orientation") + return if (isReversed) "maxMin" else "minMax" + if (op == "crosses") { + if (fMaxCross) return "max" + return if (fAutoCross) "autoZero" else "min" // correct?? +// correct?? + } + if (op == "crossBetween") + // val= between, midCat, crossBetween + return "between" // TODO: figure out! + if (op == "max") + return numMax.toString() + if (op == "min") + return numMin.toString() + if (op == "majorUnit") + return majorTick.toString() + return if (op == "minorUnit") minorTick.toString() else null + } + + private fun updateRecord() { + var b = ByteTools.doubleToLEByteArray(numMin) + System.arraycopy(b, 0, this.data!!, 0, 8) + b = ByteTools.doubleToLEByteArray(numMax) + System.arraycopy(b, 0, this.data!!, 8, 8) + b = ByteTools.doubleToLEByteArray(majorTick) + System.arraycopy(b, 0, this.data!!, 16, 8) + b = ByteTools.doubleToLEByteArray(minorTick) + System.arraycopy(b, 0, this.data!!, 24, 8) + b = ByteTools.doubleToLEByteArray(numCross) + System.arraycopy(b, 0, this.data!!, 32, 8) + b = ByteTools.shortToLEBytes(grbit) + this.data[40] = b[0] + this.data[41] = b[1] + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2989883115978826628L + + // 20070723 KSC: Need to create new records + // important when we parse options + val prototype: XLSRecord? + get() { + val vr = ValueRange() + vr.opcode = XLSConstants.VALUERANGE + vr.data = vr.PROTOTYPE_BYTES + vr.init() + return vr + } + + /** + * static utlity to calculate the min and max on a scale in a given area + * NOTE: this is usually the Y Axis, but scatter charts can have a value axis on the X Axis + * + * @param MaxVal Actual Maximum Value on Axis + * @param MinVal Actual Mimumum Value on Axis + * @param area Area of Axis in pixels (I think :)) + * @return + */ + fun calcMaxMin(MaxVal: Double, MinVal: Double, area: Double): DoubleArray { + // h==235 is normal, and the below alg. appear correct for it + // h==776 (bar chart) ??? SIGH ... + var ymax = MaxVal + var numMajor = ymax - MinVal + if (numMajor > 0 && numMajor < 20) + numMajor = 2.0 + else if (numMajor > 20 && numMajor < 100) + numMajor = 20.0 + else if (numMajor > 100 && numMajor < 500) + numMajor = 50.0 + if (ymax >= 0) { + ymax = Math.floor((ymax + numMajor) / numMajor) * numMajor + } else { + ymax = Math.floor((ymax + numMajor) / numMajor) * numMajor + } + + var numMin = MinVal - numMajor + if (numMin % numMajor > 0) + numMin -= numMajor + if (MinVal >= 0) + numMin = Math.max(MinVal - numMajor, 0.0) + numMin = Math.round(numMin).toDouble() + + var numMax = 0.0 + if (ymax >= 0) { + numMax = Math.floor((ymax + numMajor) / numMajor).toInt() * numMajor + } else { + numMax = Math.floor((ymax + numMajor) / numMajor).toInt() * numMajor + } + + return doubleArrayOf(numMin, numMajor, ymax) + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/charts/YMult.java b/src/main/java/io/starter/formats/XLS/charts/YMult.java deleted file mode 100644 index c2afa49..0000000 --- a/src/main/java/io/starter/formats/XLS/charts/YMult.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.charts; - -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -/** - * YMULT: Y Multiplier (857h) - * Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. - * This record describes the axis multiplier feature which scales the axis values - * displayed by the axis tick labels. For instance, an axis multiplier value of - * "millions" would cause the axis tick labels to show the axis value divided by one - * million (e.g., the tick label for an axis value of 20,000,000 would show "20".) - * This record is a "parent" record and is immediately followed by a set of records - * surrounded by rtStartObject and rtEndObject which describes the axis multiplier label. - *

                      - * Record Data - * Offset Field Name Size Contents - * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0857h - * 6 grbitFrt 2 FRT flags; must be zero - * 8 axmid 2 Axis multiplier ID, one of the following values: - * -1 = multiplier value is stored in numLabelMultiplier - * 0 = no multiplier (same as 1.0) - * 1 = Hundreds, 10 2nd - * 2 = Thousands, 10 3rd - * 3 = Ten Thousands, 10 4th - * 4 = Hundred Thousands, 10 5th - * 5 = Millions, 10 6th - * 6 = Ten Millions, 10 7th - * 7 = Hundred Millions, 10 8th - * 8 = billion - * 9 = trillion - * 16 numLabelMultiplier 4 Numeric value - * 18 grbit 2 Option flags for y axis multiplier (see description below)* - *

                      - * The grbit field contains the following category axis label option flags: - * Bits Mask Flag Name Contents - * 0 0001h fEnabled =1 if the multiplier is enabled =0 otherwise - * 1 0002h fAutoShowMultiplier =1 if the multiplier label is shown =0 otherwise - * 15-2 FFFCh (unused) Reserved; must be zero - */ -public class YMult extends GenericChartObject implements ChartObject { - /** - * serialVersionUID - */ - - private static final long serialVersionUID = -6166267220292885486L; - short axmid, grbit; - double numLabelMultiplier; - - public void init() { - super.init(); - axmid = ByteTools.readShort(this.getByteAt(4), this.getByteAt(5)); - numLabelMultiplier = ByteTools.eightBytetoLEDouble(this.getBytesAt(6, 8)); - grbit = ByteTools.readShort(this.getByteAt(14), this.getByteAt(15)); - } - - // TODO: Prototype Bytes - private byte[] PROTOTYPE_BYTES = new byte[]{}; - - public static XLSRecord getPrototype() { - YMult ym = new YMult(); - ym.setOpcode(YMULT); - ym.setData(ym.PROTOTYPE_BYTES); - ym.init(); - return ym; - } - - /** - * returns the Axis multiplier ID, one of the following values: - *

                    • -1 = multiplier value is stored in numLabelMultiplier - *
                    • 0 = no multiplier (same as 1.0) - *
                    • 1 = Hundreds, 10 2nd - *
                    • 2 = Thousands, 10 3rd - *
                    • 3 = Ten Thousands, 10 4th - *
                    • 4 = Hundred Thousands, 10 5th - *
                    • 5 = Millions, 10 6th - *
                    • 6 = Ten Millions, 10 7th - *
                    • 7 = Hundred Millions, 10 8th - *
                    • 8 = Thousand Millions, 10 9th - *
                    • 9 = Billions, 10 12th - * - * @return - */ - public short getAxMultiplierId() { - return axmid; - } - - public String getAxMultiplierIdAsString() { - switch (axmid) { - case -1: - return null; - case 0: - return null; //? - case 1: - return "hundreds"; - case 2: - return "thousands"; - case 3: - return "tenThousands"; - case 4: - return "hundredThousands"; - case 5: - return "millions"; - case 6: - return "tenMillions"; - case 7: - return "hundredMillions"; - case 8: - return "billions"; - case 9: - return "trillions"; - } - return null; - } - - /** - * Sets Axis multiplier ID, one of the following values: - *
                    • -1 = multiplier value is stored in numLabelMultiplier - *
                    • 0 = no multiplier (same as 1.0) - *
                    • 1 = Hundreds, 10 2nd - *
                    • 2 = Thousands, 10 3rd - *
                    • 3 = Ten Thousands, 10 4th - *
                    • 4 = Hundred Thousands, 10 5th - *
                    • 5 = Millions, 10 6th - *
                    • 6 = Ten Millions, 10 7th - *
                    • 7 = Hundred Millions, 10 8th - *
                    • 8 = Thousand Millions, 10 9th - *
                    • 9 = Billions, 10 12th - * - * @param m - */ - public void setAxMultiplierId(int m) { - if (!(m > -2 && m < 10)) - return; // report error? - axmid = (short) m; - byte[] b = ByteTools.shortToLEBytes(axmid); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - } - - /** - * Sets Axis multiplier ID via OOXML String value: - *
                    • hundreds Hundreds - *
                    • thousands Thousands - *
                    • tenThousands Ten Thousands - *
                    • hundredThousands Hundred Thousands - *
                    • millions Millions - *
                    • tenMillions Ten Millions - *
                    • hundredMillions Hundred Millions - *
                    • billions Billions - *
                    • trillions Trillions - */ - public void setAxMultiplierId(String m) { - if (m.equalsIgnoreCase("hundreds")) - axmid = 1; - else if (m.equalsIgnoreCase("thousands")) - axmid = 2; - else if (m.equalsIgnoreCase("tenThousands")) - axmid = 3; - else if (m.equalsIgnoreCase("hundredThousands")) - axmid = 4; - else if (m.equalsIgnoreCase("millions")) - axmid = 5; - else if (m.equalsIgnoreCase("tenMillions")) - axmid = 6; - else if (m.equalsIgnoreCase("hundredMillions")) - axmid = 7; - else if (m.equalsIgnoreCase("billions")) - axmid = 8; - else if (m.equalsIgnoreCase("trillions")) - axmid = 9; - else // default - axmid = 0; - byte[] b = ByteTools.shortToLEBytes(axmid); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - } - - // dispUnits -> builtInUnit (val: billions, - public double getCustomMultiplier() { - return numLabelMultiplier; - } - - public void setCustomMultiplier(double m) { - numLabelMultiplier = m; - axmid = -1; // custom - byte[] b = ByteTools.shortToLEBytes(axmid); - this.getData()[4] = b[0]; - this.getData()[5] = b[1]; - b = ByteTools.doubleToLEByteArray(numLabelMultiplier); - System.arraycopy(b, 0, this.getData(), 6, 8); - } -} diff --git a/src/main/java/io/starter/formats/XLS/charts/YMult.kt b/src/main/java/io/starter/formats/XLS/charts/YMult.kt new file mode 100644 index 0000000..64e1b0f --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/charts/YMult.kt @@ -0,0 +1,210 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.charts + +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +/** + * **YMULT: Y Multiplier (857h)** + * Introduced in Excel 9 (2000), this BIFF record is an FRT record for Charts. + * This record describes the axis multiplier feature which scales the axis values + * displayed by the axis tick labels. For instance, an axis multiplier value of + * "millions" would cause the axis tick labels to show the axis value divided by one + * million (e.g., the tick label for an axis value of 20,000,000 would show "20".) + * This record is a "parent" record and is immediately followed by a set of records + * surrounded by rtStartObject and rtEndObject which describes the axis multiplier label. + * + * + * Record Data + * Offset Field Name Size Contents + * 4 rt 2 Record type; this matches the BIFF rt in the first two bytes of the record; =0857h + * 6 grbitFrt 2 FRT flags; must be zero + * 8 axmid 2 Axis multiplier ID, one of the following values: + * -1 = multiplier value is stored in numLabelMultiplier + * 0 = no multiplier (same as 1.0) + * 1 = Hundreds, 10 2nd + * 2 = Thousands, 10 3rd + * 3 = Ten Thousands, 10 4th + * 4 = Hundred Thousands, 10 5th + * 5 = Millions, 10 6th + * 6 = Ten Millions, 10 7th + * 7 = Hundred Millions, 10 8th + * 8 = billion + * 9 = trillion + * 16 numLabelMultiplier 4 Numeric value + * 18 grbit 2 Option flags for y axis multiplier (see description below)* + * + * + * The grbit field contains the following category axis label option flags: + * Bits Mask Flag Name Contents + * 0 0001h fEnabled =1 if the multiplier is enabled =0 otherwise + * 1 0002h fAutoShowMultiplier =1 if the multiplier label is shown =0 otherwise + * 15-2 FFFCh (unused) Reserved; must be zero + */ +class YMult : GenericChartObject(), ChartObject { + /** + * returns the Axis multiplier ID, one of the following values: + * * -1 = multiplier value is stored in numLabelMultiplier + * * 0 = no multiplier (same as 1.0) + * * 1 = Hundreds, 10 2nd + * * 2 = Thousands, 10 3rd + * * 3 = Ten Thousands, 10 4th + * * 4 = Hundred Thousands, 10 5th + * * 5 = Millions, 10 6th + * * 6 = Ten Millions, 10 7th + * * 7 = Hundred Millions, 10 8th + * * 8 = Thousand Millions, 10 9th + * * 9 = Billions, 10 12th + * + * @return + */ + var axMultiplierId: Short = 0 + internal set + internal var grbit: Short = 0 + internal var numLabelMultiplier: Double = 0.toDouble() + + // TODO: Prototype Bytes + private val PROTOTYPE_BYTES = byteArrayOf() + + //? + val axMultiplierIdAsString: String? + get() { + when (axMultiplierId) { + -1 -> return null + 0 -> return null + 1 -> return "hundreds" + 2 -> return "thousands" + 3 -> return "tenThousands" + 4 -> return "hundredThousands" + 5 -> return "millions" + 6 -> return "tenMillions" + 7 -> return "hundredMillions" + 8 -> return "billions" + 9 -> return "trillions" + } + return null + } + + // dispUnits -> builtInUnit (val: billions, + // custom + var customMultiplier: Double + get() = numLabelMultiplier + set(m) { + numLabelMultiplier = m + axMultiplierId = -1 + var b = ByteTools.shortToLEBytes(axMultiplierId) + this.data[4] = b[0] + this.data[5] = b[1] + b = ByteTools.doubleToLEByteArray(numLabelMultiplier) + System.arraycopy(b, 0, this.data!!, 6, 8) + } + + override fun init() { + super.init() + axMultiplierId = ByteTools.readShort(this.getByteAt(4).toInt(), this.getByteAt(5).toInt()) + numLabelMultiplier = ByteTools.eightBytetoLEDouble(this.getBytesAt(6, 8)!!) + grbit = ByteTools.readShort(this.getByteAt(14).toInt(), this.getByteAt(15).toInt()) + } + + /** + * Sets Axis multiplier ID, one of the following values: + * * -1 = multiplier value is stored in numLabelMultiplier + * * 0 = no multiplier (same as 1.0) + * * 1 = Hundreds, 10 2nd + * * 2 = Thousands, 10 3rd + * * 3 = Ten Thousands, 10 4th + * * 4 = Hundred Thousands, 10 5th + * * 5 = Millions, 10 6th + * * 6 = Ten Millions, 10 7th + * * 7 = Hundred Millions, 10 8th + * * 8 = Thousand Millions, 10 9th + * * 9 = Billions, 10 12th + * + * @param m + */ + fun setAxMultiplierId(m: Int) { + if (!(m > -2 && m < 10)) + return // report error? + axMultiplierId = m.toShort() + val b = ByteTools.shortToLEBytes(axMultiplierId) + this.data[4] = b[0] + this.data[5] = b[1] + } + + /** + * Sets Axis multiplier ID via OOXML String value: + * * hundreds Hundreds + * * thousands Thousands + * * tenThousands Ten Thousands + * * hundredThousands Hundred Thousands + * * millions Millions + * * tenMillions Ten Millions + * * hundredMillions Hundred Millions + * * billions Billions + * * trillions Trillions + */ + fun setAxMultiplierId(m: String) { + if (m.equals("hundreds", ignoreCase = true)) + axMultiplierId = 1 + else if (m.equals("thousands", ignoreCase = true)) + axMultiplierId = 2 + else if (m.equals("tenThousands", ignoreCase = true)) + axMultiplierId = 3 + else if (m.equals("hundredThousands", ignoreCase = true)) + axMultiplierId = 4 + else if (m.equals("millions", ignoreCase = true)) + axMultiplierId = 5 + else if (m.equals("tenMillions", ignoreCase = true)) + axMultiplierId = 6 + else if (m.equals("hundredMillions", ignoreCase = true)) + axMultiplierId = 7 + else if (m.equals("billions", ignoreCase = true)) + axMultiplierId = 8 + else if (m.equals("trillions", ignoreCase = true)) + axMultiplierId = 9 + else + // default + axMultiplierId = 0 + val b = ByteTools.shortToLEBytes(axMultiplierId) + this.data[4] = b[0] + this.data[5] = b[1] + } + + companion object { + /** + * serialVersionUID + */ + + private val serialVersionUID = -6166267220292885486L + + val prototype: XLSRecord? + get() { + val ym = YMult() + ym.opcode = XLSConstants.YMULT + ym.data = ym.PROTOTYPE_BYTES + ym.init() + return ym + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/CalculationException.java b/src/main/java/io/starter/formats/XLS/formulas/CalculationException.java deleted file mode 100644 index 0abd6c8..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/CalculationException.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -/** - * Indicates an error occurred during formula calculation. - */ -public class CalculationException - extends Exception { - private static final long serialVersionUID = 2028428287133817627L; - static String[][] errorStrings = {{"#DIV/0!", "7"}, - {"#N/A", "42"}, - {"#NAME?", "29"}, - {"#NULL!", "0"}, - {"#NUM!", "36"}, - {"#REF!", "23"}, - {"#VALUE!", "15"}, - {"#CIR_ERR!", "15"} // false error with code for value, output for circular ref exceptions - }; - - /** - * Excel #NULL! error. - * Indicates that a range intersection returned no cells. - */ - public static final byte NULL = (byte) 0x00; - - /** - * Excel #DIV/0! error. - * Indicates that the formula attempted to divide by zero. - */ - public static final byte DIV0 = (byte) 0x07; - - /** - * Excel #VALUE! error. - * Indicates that there was an operand type mismatch. - */ - public static final byte VALUE = (byte) 0x0F; - - /** - * Excel #REF! error. - * Indicates that a reference was made to a cell that doesn't exist. - */ - public static final byte REF = (byte) 0x17; - - /** - * Excel #NAME? error. - * Indicates an unknown string was encountered in the formula. - */ - public static final byte NAME = (byte) 0x1D; - - /** - * Excel #NUM! error. - * Indicates that a calculation result overflowed the number storage. - */ - public static final byte NUM = (byte) 0x24; - - /** - * Excel #N/A error. - * Indicates that a lookup (e.g. VLOOKUP) returned no results. - */ - public static final byte NA = (byte) 0x2A; - - /** - * Custom circular exception error, internally stores as a #VALUE - */ - public static final byte CIR_ERR = (byte) 0xFF; - - /** - * The error code for this error. - */ - private final byte error; - - /** - * Creates a new CaluculationException. - * - * @param error the error code. must be one of the defined error constants. - */ - public CalculationException(byte error) { - this.error = error; - } - - /** - * Gets the BIFF8 error code for this error. - */ - public byte getErrorCode() { - if (error == CIR_ERR) return VALUE; - return error; - } - - /** - * static version, takes String error code and returns the correct error code - * - * @param error String - * @return - */ - public static byte getErrorCode(String error) { - if (error == null) return 0; // unknown - for (int i = 0; i < errorStrings.length; i++) { - if (error.equals(errorStrings[i][0])) { - return new Byte(errorStrings[i][1]).byteValue(); - } - } - return 0; - } - - /** - * Gets a human-readable message describing this error. - */ - public String getMessage() { - switch (error) { - case NULL: - return "a range intersection returned no cells"; - case DIV0: - return "attempted to divide by zero"; - case VALUE: - return "operand type mismatch"; - case REF: - return "reference to a cell that doesn't exist"; - case NAME: - return "reference to an unknown function or defined name"; - case NUM: - return "number storage overflow"; - case NA: - return "lookup returned no value for the given criteria"; - case CIR_ERR: - return "circular reference error"; - default: - return "unknown error occurred"; - } - } - - /** - * Gets the string name of this error. - */ - public String getName() { - switch (error) { - case NULL: - return "#NULL!"; - case DIV0: - return "#DIV/0!"; - case VALUE: - return "#VALUE!"; - case REF: - return "#REF!"; - case NAME: - return "#NAME?"; - case NUM: - return "#NUM!"; - case NA: - return "#N/A"; - case CIR_ERR: - return "#CIR_ERR!"; - default: - return null; - } - } - - public String toString() { - return getName(); - } -} diff --git a/src/main/java/io/starter/formats/XLS/formulas/CalculationException.kt b/src/main/java/io/starter/formats/XLS/formulas/CalculationException.kt new file mode 100644 index 0000000..b10acec --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/CalculationException.kt @@ -0,0 +1,153 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +/** + * Indicates an error occurred during formula calculation. + */ +open class CalculationException +/** + * Creates a new CaluculationException. + * + * @param error the error code. must be one of the defined error constants. + */ +( + /** + * The error code for this error. + */ + private val error: Byte) : Exception() { + + /** + * Gets the BIFF8 error code for this error. + */ + val errorCode: Byte + get() = if (error == CIR_ERR) VALUE else error + + /** + * Gets the string name of this error. + */ + open val name: String? + get() { + when (error) { + NULL -> return "#NULL!" + DIV0 -> return "#DIV/0!" + VALUE -> return "#VALUE!" + REF -> return "#REF!" + NAME -> return "#NAME?" + NUM -> return "#NUM!" + NA -> return "#N/A" + CIR_ERR -> return "#CIR_ERR!" + else -> return null + } + } + + /** + * Gets a human-readable message describing this error. + */ + override fun getMessage(): String { + when (error) { + NULL -> return "a range intersection returned no cells" + DIV0 -> return "attempted to divide by zero" + VALUE -> return "operand type mismatch" + REF -> return "reference to a cell that doesn't exist" + NAME -> return "reference to an unknown function or defined name" + NUM -> return "number storage overflow" + NA -> return "lookup returned no value for the given criteria" + CIR_ERR -> return "circular reference error" + else -> return "unknown error occurred" + } + } + + override fun toString(): String? { + return name + } + + companion object { + private val serialVersionUID = 2028428287133817627L + internal var errorStrings = arrayOf(arrayOf("#DIV/0!", "7"), arrayOf("#N/A", "42"), arrayOf("#NAME?", "29"), arrayOf("#NULL!", "0"), arrayOf("#NUM!", "36"), arrayOf("#REF!", "23"), arrayOf("#VALUE!", "15"), arrayOf("#CIR_ERR!", "15") // false error with code for value, output for circular ref exceptions + ) + + /** + * Excel #NULL! error. + * Indicates that a range intersection returned no cells. + */ + val NULL = 0x00.toByte() + + /** + * Excel #DIV/0! error. + * Indicates that the formula attempted to divide by zero. + */ + val DIV0 = 0x07.toByte() + + /** + * Excel #VALUE! error. + * Indicates that there was an operand type mismatch. + */ + val VALUE = 0x0F.toByte() + + /** + * Excel #REF! error. + * Indicates that a reference was made to a cell that doesn't exist. + */ + val REF = 0x17.toByte() + + /** + * Excel #NAME? error. + * Indicates an unknown string was encountered in the formula. + */ + val NAME = 0x1D.toByte() + + /** + * Excel #NUM! error. + * Indicates that a calculation result overflowed the number storage. + */ + val NUM = 0x24.toByte() + + /** + * Excel #N/A error. + * Indicates that a lookup (e.g. VLOOKUP) returned no results. + */ + val NA = 0x2A.toByte() + + /** + * Custom circular exception error, internally stores as a #VALUE + */ + val CIR_ERR = 0xFF.toByte() + + /** + * static version, takes String error code and returns the correct error code + * + * @param error String + * @return + */ + fun getErrorCode(error: String?): Byte { + if (error == null) return 0 // unknown + for (i in errorStrings.indices) { + if (error == errorStrings[i][0]) { + return Byte(errorStrings[i][1]) + } + } + return 0 + } + } +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/Calculator.java b/src/main/java/io/starter/formats/XLS/formulas/Calculator.java deleted file mode 100644 index fef22a6..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/Calculator.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.DateConverter; -import io.starter.formats.XLS.BiffRec; - -public class Calculator { - /** - * given a BiffRec Cell Record, an Object and an operator to compare to, - * return true if the comparison passes, false otherwise - * - * @param c BiffRec cell record - * @param o Object value - one of Double, String or Boolean - * @param op - String operator - one of "=", ">", ">=", "<", "<=" or "<> - * @return true if comparison of operator and value with cell value passes, false otherwise - */ - public static boolean compareCellValue(BiffRec c, Object o, String op) { - // doper types: numeric: ieee, rk - // string: string doper - // boolean - // error - int compare; - try { - if (o instanceof Boolean) // TODO: 1.5 use Boolean.compareTo - compare = ((Boolean) o).toString().compareTo(Boolean.valueOf(c.getBooleanVal()).toString()); - else if (o instanceof String) { - // use "matches" to handle wildcards - if (((String) o).toUpperCase().matches(c.getStringVal().toUpperCase())) - compare = 0; // equal or matches - else - compare = -1; // doesn't equal - //compare= ((String) o).toUpperCase().compareTo(c.getStringVal().toUpperCase()); - } else // it's a Double - compare = ((Double) o).compareTo(new Double(c.getDblVal())); - } catch (Exception e) { - // report error? - return false; - } - if (op.equals("=")) { - return (compare == 0); - } else if (op.equals("<")) { - return (compare > 0); - } else if (op.equals("<=")) { - return (compare >= 0); - } else if (op.equals(">")) { - return (compare < 0); - } else if (op.equals(">=")) { - return (compare <= 0); - } else if (op.equals("<>")) { - return (compare != 0); - } - return false; - } - - public static boolean compareCellValue(Object val, String compareval, String op) { - // doper types: numeric: ieee, rk - // string: string doper - // boolean - // error - int compare = -1; - try { - if (val instanceof Boolean) // TODO: 1.5 use Boolean.compareTo - compare = (Boolean.valueOf(compareval).toString()).compareTo(((Boolean) val).toString()); - else if (val instanceof String) { - if ((compareval.indexOf('?') == -1) && - (compareval.indexOf('*') == -1)) // if no wildcards - compare = (compareval).compareTo(((String) val).toUpperCase()); - else { // use "matches" to handle wildcards - if (((String) val).toUpperCase().matches(compareval)) - compare = 0; // equal or matches - else - compare = -1; // doesn't equal - } - } else if (val instanceof Number) // assume it's a number - compare = (new Double(compareval)).compareTo(new Double(((Number) val).doubleValue())); - else - return false; - } catch (Exception e) { - try { // try date compare - double dt = DateConverter.getXLSDateVal(new java.util.Date(compareval)); - compare = (new Double(dt)).compareTo(new Double(((Number) val).doubleValue())); - } catch (Exception ex) { // just try string compare - compare = compareval.compareTo(val.toString()); - } - } - if (op.equals("=")) { - return (compare == 0); - } else if (op.equals("<")) { - return (compare > 0); - } else if (op.equals("<=")) { - return (compare >= 0); - } else if (op.equals(">")) { - return (compare < 0); - } else if (op.equals(">=")) { - return (compare <= 0); - } else if (op.equals("<>")) { - return (compare != 0); - } - return false; - } - -/* } else if (op.equals("<")) { - passes= (compare < 0); - } else if (op.equals("<=")) { - passes= (compare <= 0); - } else if (op.equals(">")) { - passes= (compare > 0); - } else if (op.equals(">=")) { - passes= (compare >= 0); - } - */ - - /** - * translate Excel-style wildcards into Java wildcards in criteria string - * plus handle qualified wildcard characters + percentages ... - * - * @param sCriteria criteria string - * @return tranformed criteria string - */ - public static String translateWildcardsInCriteria(String sCriteria) { - String s = ""; // handle wildcards - boolean qualified = false; - boolean isalldigits = true; - for (int i = 0; i < sCriteria.length(); i++) { - char c = sCriteria.charAt(i); - if (c == '~') { - qualified = true; // don't add tilde unless certain it's not qualifying a * or ? - } else if (c == '*') { - if (!qualified) - s += "."; - s += c; - } else if (c == '?') { - if (!qualified) - s += "."; - s += c; - } else if (c == '%') { // translate percentage into decimals - if (isalldigits) { - s = "0" + s; - s = s.substring(s.length() - 2, 2); - s = "." + s; - } - } else { - if (qualified) // really add the tilde - s += '~'; - s += c; - qualified = false; - if (!Character.isDigit(c)) - isalldigits = false; - } - } - sCriteria = s.toUpperCase(); // matching is case-insensitive - return sCriteria; - } - - /** - * given a criteria string that starts with an operator, - * parse and return the index that the operator ends and the crtieria starts - * - * @param criteria - * @return int i position in criteria which actual criteria starts - */ - public static int splitOperator(String criteria) { - int i = 0; - for (; i < criteria.length(); i++) { - char c = criteria.charAt(i); - if (Character.isJavaIdentifierPart(c)) - break; - else if (c == '*' || c == '?') - break; - } - return i; - } - - /** - * takes a Reference Type Ptg and deferences and PtgNames, etc. - * to return a PtgArea - * - * @param p - * @return - */ - public static PtgArea getRange(Ptg p) - throws IllegalArgumentException { - if (p instanceof PtgArea) - return (PtgArea) p; - if (p instanceof PtgName) { // get source range - Ptg[] pr = null; - try { - pr = ((PtgName) p).getName().getCellRangePtgs(); - return (PtgArea) pr[0]; - } catch (Exception e) { - try { // if it's a PtgRef, convert to a PtgArea - if (!(pr[0] instanceof PtgArea) && pr[0] instanceof PtgRef) { - PtgArea pa = new PtgArea(); - pa.setParentRec(pr[0].getParentRec()); - pa.setLocation(pr[0].getLocation()); - return pa; - } else - throw new IllegalArgumentException("Expected a reference-type operand"); - } catch (Exception ex) { - throw new IllegalArgumentException("Expected a reference-type operand"); - } - } - } - return null; - } -} diff --git a/src/main/java/io/starter/formats/XLS/formulas/Calculator.kt b/src/main/java/io/starter/formats/XLS/formulas/Calculator.kt new file mode 100644 index 0000000..2854aae --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/Calculator.kt @@ -0,0 +1,239 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.DateConverter +import io.starter.formats.XLS.BiffRec + +object Calculator { + /** + * given a BiffRec Cell Record, an Object and an operator to compare to, + * return true if the comparison passes, false otherwise + * + * @param c BiffRec cell record + * @param o Object value - one of Double, String or Boolean + * @param op - String operator - one of "=", ">", ">=", "<", "<=" or "<> + * @return true if comparison of operator and value with cell value passes, false otherwise + */ + fun compareCellValue(c: BiffRec, o: Any, op: String): Boolean { + // doper types: numeric: ieee, rk + // string: string doper + // boolean + // error + val compare: Int + try { + if (o is Boolean) + // TODO: 1.5 use Boolean.compareTo + compare = o.toString().compareTo(java.lang.Boolean.valueOf(c.booleanVal).toString()) + else if (o is String) { + // use "matches" to handle wildcards + if (o.toUpperCase().matches(c.stringVal.toUpperCase().toRegex())) + compare = 0 // equal or matches + else + compare = -1 // doesn't equal + //compare= ((String) o).toUpperCase().compareTo(c.getStringVal().toUpperCase()); + } else + // it's a Double + compare = (o as Double).compareTo(c.dblVal) + } catch (e: Exception) { + // report error? + return false + } + + if (op == "=") { + return compare == 0 + } else if (op == "<") { + return compare > 0 + } else if (op == "<=") { + return compare >= 0 + } else if (op == ">") { + return compare < 0 + } else if (op == ">=") { + return compare <= 0 + } else if (op == "<>") { + return compare != 0 + } + return false + } + + fun compareCellValue(`val`: Any, compareval: String, op: String): Boolean { + // doper types: numeric: ieee, rk + // string: string doper + // boolean + // error + var compare = -1 + try { + if (`val` is Boolean) + // TODO: 1.5 use Boolean.compareTo + compare = java.lang.Boolean.valueOf(compareval).toString().compareTo(`val`.toString()) + else if (`val` is String) { + if (compareval.indexOf('?') == -1 && compareval.indexOf('*') == -1) + // if no wildcards + compare = compareval.compareTo(`val`.toUpperCase()) + else { // use "matches" to handle wildcards + if (`val`.toUpperCase().matches(compareval.toRegex())) + compare = 0 // equal or matches + else + compare = -1 // doesn't equal + } + } else if (`val` is Number) + // assume it's a number + compare = Double(compareval).compareTo(`val`.toDouble()) + else + return false + } catch (e: Exception) { + try { // try date compare + val dt = DateConverter.getXLSDateVal(java.util.Date(compareval)) + compare = dt.compareTo((`val` as Number).toDouble()) + } catch (ex: Exception) { // just try string compare + compare = compareval.compareTo(`val`.toString()) + } + + } + + if (op == "=") { + return compare == 0 + } else if (op == "<") { + return compare > 0 + } else if (op == "<=") { + return compare >= 0 + } else if (op == ">") { + return compare < 0 + } else if (op == ">=") { + return compare <= 0 + } else if (op == "<>") { + return compare != 0 + } + return false + } + + /* } else if (op.equals("<")) { + passes= (compare < 0); + } else if (op.equals("<=")) { + passes= (compare <= 0); + } else if (op.equals(">")) { + passes= (compare > 0); + } else if (op.equals(">=")) { + passes= (compare >= 0); + } + */ + + /** + * translate Excel-style wildcards into Java wildcards in criteria string + * plus handle qualified wildcard characters + percentages ... + * + * @param sCriteria criteria string + * @return tranformed criteria string + */ + fun translateWildcardsInCriteria(sCriteria: String): String { + var sCriteria = sCriteria + var s = "" // handle wildcards + var qualified = false + var isalldigits = true + for (i in 0 until sCriteria.length) { + val c = sCriteria[i] + if (c == '~') { + qualified = true // don't add tilde unless certain it's not qualifying a * or ? + } else if (c == '*') { + if (!qualified) + s += "." + s += c + } else if (c == '?') { + if (!qualified) + s += "." + s += c + } else if (c == '%') { // translate percentage into decimals + if (isalldigits) { + s = "0$s" + s = s.substring(s.length - 2, 2) + s = ".$s" + } + } else { + if (qualified) + // really add the tilde + s += '~'.toString() + s += c + qualified = false + if (!Character.isDigit(c)) + isalldigits = false + } + } + sCriteria = s.toUpperCase() // matching is case-insensitive + return sCriteria + } + + /** + * given a criteria string that starts with an operator, + * parse and return the index that the operator ends and the crtieria starts + * + * @param criteria + * @return int i position in criteria which actual criteria starts + */ + fun splitOperator(criteria: String): Int { + var i = 0 + while (i < criteria.length) { + val c = criteria[i] + if (Character.isJavaIdentifierPart(c)) + break + else if (c == '*' || c == '?') + break + i++ + } + return i + } + + /** + * takes a Reference Type Ptg and deferences and PtgNames, etc. + * to return a PtgArea + * + * @param p + * @return + */ + @Throws(IllegalArgumentException::class) + fun getRange(p: Ptg): PtgArea? { + if (p is PtgArea) + return p + if (p is PtgName) { // get source range + var pr: Array? = null + try { + pr = p.name!!.cellRangePtgs + return pr!![0] as PtgArea + } catch (e: Exception) { + try { // if it's a PtgRef, convert to a PtgArea + if (pr!![0] !is PtgArea && pr[0] is PtgRef) { + val pa = PtgArea() + pa.parentRec = pr[0].parentRec + pa.location = pr[0].location + return pa + } else + throw IllegalArgumentException("Expected a reference-type operand") + } catch (ex: Exception) { + throw IllegalArgumentException("Expected a reference-type operand") + } + + } + + } + return null + } +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.java b/src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.kt similarity index 76% rename from src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.java rename to src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.kt index f721b21..28285c6 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.java +++ b/src/main/java/io/starter/formats/XLS/formulas/CircularReferenceException.kt @@ -20,18 +20,13 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /** * Handles circular reference calls within a calculation */ -public class CircularReferenceException extends CalculationException { +class CircularReferenceException(error: Byte) : CalculationException(error) { - public CircularReferenceException(byte error) { - super(error); - } - - public String getName() { - return "#CIR_ERR!"; - } + override val name: String? + get() = "#CIR_ERR!" } diff --git a/src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.kt similarity index 90% rename from src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.java rename to src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.kt index 240f105..1ce9069 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/DDEExternalCalculator.kt @@ -20,16 +20,16 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /** * DDEExternalCalculator is a collection of static methods that operate * as the Microsoft Excel function calls do. - *

                      + * + * * All methods are called with an array of ptg's, which are then * acted upon. A Ptg of the type that makes sense (ie boolean, number) * is returned after each method. */ -public class DDEExternalCalculator { -} \ No newline at end of file +class DDEExternalCalculator \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.java deleted file mode 100644 index fd2c7f4..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.java +++ /dev/null @@ -1,1197 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.ReferenceTracker; -import io.starter.toolkit.Logger; - -import java.util.*; - - -/** - * DatabaseCalculator is a collection of static methods that operate - * as the Microsoft Excel function calls do. - *

                      - * All methods are called with an array of ptg's, which are then - * acted upon. A Ptg of the type that makes sense (ie boolean, number) - * is returned after each method. - *

                      - *

                      - * Database and List Management functions - * Microsoft Excel includes worksheet functions that analyze data stored in lists or databases. - * Each of these functions, referred to collectively as the Dfunctions, uses three arguments: database, field, and criteria. - *

                      - * These arguments refer to the worksheet ranges that are used by the function. - *

                      - * DAVERAGE Returns the average of selected database entries - *

                      - * DCOUNT Counts the cells that contain numbers in a database - *

                      - * DCOUNTA Counts nonblank cells in a database - *

                      - * DGET Extracts from a database a single record that matches the specified criteria - *

                      - * DMAX Returns the maximum value from selected database entries - *

                      - * DMIN Returns the minimum value from selected database entries - *

                      - * DPRODUCT Multiplies the values in a particular field of records that match the criteria in a database - *

                      - * DSTDEV Estimates the standard deviation based on a sample of selected database entries - *

                      - * DSTDEVP Calculates the standard deviation based on the entire population of selected database entries - *

                      - * DSUM Adds the numbers in the field column of records in the database that match the criteria - *

                      - * DVAR Estimates variance based on a sample from selected database entries - *

                      - * DVARP Calculates variance based on the entire population of selected database entries - *

                      - * GETPIVOTDATA Returns data stored in a PivotTable - *

                      - *

                      - * ABOUT DB - *

                      - * All Database Formulas take 3 arguments: - * Database is the range of cells that makes up the list or database. - *

                      - * A database is a list of related data in which rows of related information - * are records, and columns of data are fields. - *

                      - * The first row of the list contains labels for each column. - * Field indicates which column is used in the function. - * Field can be given as text with the column label - * enclosed between double quotation marks, such as "Age" or "Yield," - * or as a number that represents the position of the column within the - * list: 1 for the first column, 2 for the second column, and so on. - *

                      - * Criteria is the range of cells that contains the conditions you specify. - *

                      - * You can use any range for the criteria argument, * as long as it - * includes at least one column label and at least one cell below the column - * label for specifying a condition for the column. - *

                      - * Make sure the criteria range does not overlap the list. - *

                      - *

                      - *

                      - * TODO To change the template for this generated type comment go to - * Window - Preferences - Java - Code Style - Code Templates - */ -public class DatabaseCalculator { - public static boolean DEBUG = false; - - /** - * Fetch a DB from the cache or create a new one - *

                      - *

                      - * Dbs store Cell refs... - * - * @param operands - * @return - */ - private static DB getDb(Ptg operands) { - ReferenceTracker DBcache = operands.getParentRec().getWorkBook().getRefTracker(); - - //gonna try never caching this... painful, but if we dont' - if (DBcache.getListDBs().get(operands.toString()) != null) { - //Logger.logInfo("getDB: " + operands.toString()+ "using cache."); - return (DB) DBcache.getListDBs().get(operands.toString()); - } - //} - // create new - //Logger.logErr("getDB: " + operands.toString()+ "NOT cached."); - Ptg[] dbrange = PtgCalculator.getAllComponents(operands); - DB ret = DB.parseList(dbrange); - DBcache.getListDBs().put(operands.toString(), ret); - return ret; - } - - private static Criteria getCriteria(Ptg operands) { - ReferenceTracker DBcache = operands.getParentRec().getWorkBook().getRefTracker(); - - // test without cache - if (DBcache.getCriteriaDBs().get(operands.toString()) != null) { - //Logger.logInfo("getCriteria: " + operands.toString()+ "using cache."); - return (Criteria) DBcache.getCriteriaDBs().get(operands.toString()); - } - //Logger.logErr("getCriteria: " + operands.toString()+ "NOT cached."); - Ptg[] criteria = PtgCalculator.getAllComponents(operands); - Criteria ret = Criteria.parseCriteria(criteria); - DBcache.getCriteriaDBs().put(operands.toString(), ret); - return ret; - } - - /** - * DAVERAGE Returns the average of selected database entries - */ - protected static Ptg calcDAverage(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - double average = 0; - int count = 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i = 0; i < db.rows.length; i++) { // loop thru all db rows - // check if current row passes criteria requirements - Ptg[] rwz = db.getRow(i); // slight optimization one less call to getRow -jm - if (crit.passes(colname, rwz, db)) { - // passes; now do action - Ptg vx = rwz[fNum]; - if (vx != null) { - try { - average += Double.parseDouble(vx.getValue().toString()); - count++; // if it can be parsed into a number, increment count - } catch (NumberFormatException exp) { - } - } - } - } - if (count > 0) average = average / count; - return new PtgNumber(average); - } - - /** - * DCOUNT Counts the cells that contain numbers in a database - */ - protected static Ptg calcDCount(Ptg[] operands) { - if (operands.length != 3) - return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - - int count = 0; - int nrow = db.getNRows(); - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i = 0; i < nrow; i++) { // loop thru all db rows - // check if current row passes criteria requirements - try { - Ptg[] rr = db.getRow(i); - - /* "passes" means that there is a matching - * cell in the row of the data db cells - * - */ - if (crit.passes(colname, rr, db)) { - // passes; now do action - Ptg cx = db.getCell(i, fNum); - String vtx = cx.getValue().toString(); - if (vtx != null) { - Double.parseDouble(vtx); - count++; // if it can be parsed into a number, increment count - } - } - } catch (NumberFormatException e) { - } - } - return new PtgNumber(count); - } - - /** - * DCOUNTA Counts nonblank cells in a database - */ - protected static Ptg calcDCountA(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - int count = 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname, db.getRow(i), db)) { - // passes; now do action - String s = db.getCell(i, fNum).getValue().toString(); - if (s != null && !s.trim().equals("")) - count++; // if field is not blank, increment count - } - } - - return new PtgNumber(count); - } - - /** - * DGET Extracts from a database a single record that matches the specified criteria - */ - protected static Ptg calcDGet(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NULL); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - String val = ""; - int count = 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname, db.getRow(i), db)) { - // passes; now do action - val = db.getCell(i, fNum).getValue().toString(); - count++; - } - } - if (count == 0) return new PtgErr(PtgErr.ERROR_VALUE); // no recs match - if (count > 1) return new PtgErr(PtgErr.ERROR_NUM); // if more than one record matches criteria - return new PtgStr(val); - } - - /** - * DMAX Returns the maximum value from selected database entries - */ - protected static Ptg calcDMax(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - double max = Double.MIN_VALUE; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname, db.getRow(i), db)) { - // passes; now do action - String vtx = db.getCell(i, fNum).getValue().toString(); - if (vtx != null) { - try { - if (vtx.length() > 0) max = Math.max(max, Double.parseDouble(vtx)); - } catch (NumberFormatException exp) { - } - } - } - } - - if (max == Double.MIN_VALUE) max = 0; - return new PtgNumber(max); - } - - /** - * DMIN Returns the minimum value from selected database entries - */ - protected static Ptg calcDMin(Ptg[] operands) { - if (operands.length != 3) // sanity checks - return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - double min = Double.MAX_VALUE; - // this is the colname to match - String colnamx = operands[1].getValue().toString(); - - for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - try { - Ptg[] rwz = db.getRow(i); - - if (crit.passes(colnamx, rwz, db)) { - // passes; now do action - try { - Ptg dbx = db.getCell(i, fNum); - - if (dbx != null) { - String dnb = dbx.getValue().toString(); - if (dnb != null) { - if (dnb.length() > 0) - min = Math.min(min, Double.parseDouble(dnb)); - } - } - } catch (Exception ex) { - // normal blanks etc. - } - } - } catch (NumberFormatException e) { - } - } - if (min == Double.MAX_VALUE) min = 0; - return new PtgNumber(min); - } - - /** - * DPRODUCT Multiplies the values in a particular field of records that match the criteria in a database - */ - protected static Ptg calcDProduct(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - double product = 1; - // this is the colname to match - String colname = operands[1].getValue().toString(); - - for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - try { - if (crit.passes(colname, db.getRow(i), db)) { - // passes; now do action - String fnx = db.getCell(i, fNum).getValue().toString(); - if (fnx != null) - product *= Double.parseDouble(fnx); - } - } catch (NumberFormatException e) { - } - } - return new PtgNumber(product); - } - - /** - * DSTDEV Estimates the standard deviation based on a sample of selected database entries - */ - protected static Ptg calcDStdDev(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - java.util.ArrayList vals = new java.util.ArrayList(); - double sum = 0; - int count = 0; - // this is the colname to match - String colname = operands[1].getValue().toString(); - - for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname, db.getRow(i), db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if (fnx != null) { - double x = Double.parseDouble(fnx); - sum += x; - count++; - vals.add(Double.toString(x)); - } - } catch (NumberFormatException e) { - } - } - } - double stdev = 0; - if (count > 0) { - double average = sum / count; - // now have all values in vals - for (int i = 0; i < count; i++) { - double x = Double.parseDouble((String) vals.get(i)); - stdev += Math.pow((x - average), 2); - } - if (count > 1) count--; - stdev = Math.sqrt(stdev / count); - } - return new PtgNumber(stdev); - } - - /** - * DSTDEVP Calculates the standard deviation based on the entire population of selected database entries - */ - protected static Ptg calcDStdDevP(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - java.util.ArrayList vals = new java.util.ArrayList(); - double sum = 0; - int count = 0; - // this is the colname to match - String colname = operands[1].getValue().toString(); - - for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname, db.getRow(i), db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if (fnx != null) { - double x = Double.parseDouble(fnx); - sum += x; - count++; - vals.add(Double.toString(x)); - } - } catch (NumberFormatException e) { - } - } - } - double stdevp = 0; - if (count > 0) { - double average = sum / count; - // now have all values in vals - for (int i = 0; i < count; i++) { - double x = Double.parseDouble((String) vals.get(i)); - stdevp += Math.pow((x - average), 2); - } - stdevp = Math.sqrt(stdevp / count); - } - return new PtgNumber(stdevp); - } - - /** - * DSUM Adds the numbers in the field column of records in the database that match the criteria - */ - protected static Ptg calcDSum(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - - int count = 0; - double sum = 0.0d; - int nrow = db.getNRows(); - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i = 0; i < nrow; i++) { // loop thru all db rows - // check if current row passes criteria requirements - try { - Ptg[] rr = db.getRow(i); - - /* "passes" means that there is a matching - * cell in the row of the data db cells - * - */ - if (crit.passes(colname, rr, db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if (fnx != null) - sum += Double.parseDouble(fnx); - } catch (NumberFormatException e) { - } - } - } catch (NumberFormatException e) { - } - } - return new PtgNumber(sum); - } - - - /** - * DVAR Estimates variance based on a sample from selected database entries - */ - protected static Ptg calcDVar(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - java.util.ArrayList vals = new java.util.ArrayList(); - double sum = 0; - int count = 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname, db.getRow(i), db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if (fnx != null) { - double x = Double.parseDouble(db.getCell(i, fNum).toString()); - sum += x; - count++; - vals.add(Double.toString(x)); - } - } catch (NumberFormatException e) { - } - } - } - double variance = 0; - if (count > 0) { - double average = sum / count; - // now have all values in vals - for (int i = 0; i < count; i++) { - double x = Double.parseDouble((String) vals.get(i)); - variance += Math.pow((x - average), 2); - } - if (count > 1) count--; - variance = variance / count; - } - return new PtgNumber(variance); - } - - /* DVARP Calculates variance based on the entire population of selected database entries - */ - protected static Ptg calcDVarP(Ptg[] operands) { - if (operands.length != 3) return new PtgErr(PtgErr.ERROR_NA); - - DB db = getDb(operands[0]); - Criteria crit = getCriteria(operands[2]); - if (db == null || crit == null) - return new PtgErr(PtgErr.ERROR_NUM); - int fNum = db.findCol(operands[1].getString().trim()); - if (fNum == -1) - return new PtgErr(PtgErr.ERROR_NUM); - java.util.ArrayList vals = new java.util.ArrayList(); - double sum = 0; - int count = 0; - - // this is the colname to match - String colname = operands[1].getValue().toString(); - for (int i = 0; i < db.getNRows(); i++) { // loop thru all db rows - // check if current row passes criteria requirements - if (crit.passes(colname, db.getRow(i), db)) { - // passes; now do action - try { - String fnx = db.getCell(i, fNum).getValue().toString(); - if (fnx != null) { - double x = Double.parseDouble(fnx); - sum += x; - count++; - vals.add(Double.toString(x)); - } - } catch (NumberFormatException e) { - } - } - } - double varP = 0; - if (count > 0) { - double average = sum / count; - // now have all values in vals - for (int i = 0; i < count; i++) { - double x = Double.parseDouble((String) vals.get(i)); - varP += Math.pow((x - average), 2); - } - varP = varP / count; - } - return new PtgNumber(varP); - } - /* GETPIVOTDATA Returns data stored in a PivotTable - * - * - */ -} - - -/** - * EXPLANATION of Database Formulas - *

                      - * Database is the range of cells that makes up the list or database. - * A database is a list of related data in which rows of related - * information are records, and columns of data are fields. - * The first row of the list contains labels for each column. - *

                      - * Field indicates which column is used in the function. - * Field can be given as text with the column label enclosed between - * double quotation marks, such as "Age" or "Yield," or as a number - * that represents the position of the column within the list: - *

                      - * 1 for the first column, - * 2 for the second column, and so on. - *

                      - * NOTE: quotes around field text is optional: - * first row of columns and rows are field labels. - *

                      - * Criteria is the range of cells that contains the conditions - * you specify. You can use any range for the criteria argument, - * as long as it includes at least one column label and at least one - * cell below the column label for specifying a condition for the column. - *

                      - * Example - */ -class DB { - protected String[] colHeaders; - protected Ptg[][] rows; - - public DB(int nCols, int nRows) { - colHeaders = new String[nCols]; - // TODO: replace with PtgRef array! - rows = new Ptg[nRows][nCols]; - } - - public int getNCols() { - return colHeaders.length; - } - - public int getNRows() { - return rows.length; - } - - /** - * return the index of the col in the DB - * - * @param cname - * @return - */ - public int getCol(String cname) { - for (int t = 0; t < colHeaders.length; t++) { - if (colHeaders[t].equalsIgnoreCase(cname)) { - return t; - } - } - return -1; - } - - /** - * return a row of DB ptgs - * - * @param i - * @return - */ - public Ptg[] getRow(int i) { - if (i > -1 && i < rows.length) - return rows[i]; - return null; - } - - /** - * return a col of ?? - * - * @param i - * @return - */ - public String getCol(int i) { - if (i > -1 && i < colHeaders.length) - return colHeaders[i]; - return null; - } - - public Ptg getCell(int row, int col) { - try { - return rows[row][col]; - } catch (Exception e) { - return null; - } - } - - public int findCol(String f) { - for (int i = 0; i < colHeaders.length; i++) { - if (colHeaders[i].trim().equalsIgnoreCase(f)) { - return i; - } - } - try { - int j = Integer.parseInt(f); // one-based index into columns - return j - 1; - } catch (Exception e) { - return -1; - } - } - - /** - * Write some documentation here please... thanks! -jm - * - * @param dbrange - * @return - */ - public static DB parseList(Ptg[] dbrange) { - int prevCol = -1; - int nCols = 0, nRows = 0, maxRows = 0; - - // allocate the empty table for the dbrange - for (int i = 0; i < dbrange.length; i++) { - if (dbrange[i] instanceof PtgRef) { - PtgRef pref = (PtgRef) dbrange[i]; - int[] loc = pref.getIntLocation(); -// TODO: check rc sanity here - if (loc[1] != prevCol) { // count # cols - prevCol = loc[1]; - nCols++; - nRows = 0; - } else { // count # rows - nRows++; - maxRows = Math.max(nRows, maxRows); - } - } else return null; - } - - // now populate the table - DB dblist = new DB(nCols, maxRows); - prevCol = -1; - nCols = -1; - nRows = -1; - for (int i = 0; i < dbrange.length; i++) { - PtgRef db1 = (PtgRef) dbrange[i]; - int[] loc = db1.getIntLocation(); - Object vs = null; // 20081120 KSC: Must distinguish between blanks and 0's - if (!db1.isBlank()) - vs = db1.getValue(); - - // column headers - if (loc[1] != prevCol) { - if (vs != null) - dblist.colHeaders[++nCols] = vs.toString(); - prevCol = loc[1]; - nRows = 0; - } else { // get value Ptgs - try { - dblist.rows[nRows++][nCols] = db1; - } catch (ArrayIndexOutOfBoundsException e) { - // possible nCols==-1 - } - - } - } - return dblist; - } -} - -class Criteria extends DB { - public Criteria(int nCols, int nRows) { - super(nCols, nRows); - } - - public static Criteria parseCriteria(Ptg[] criteria) { - DB dblist = DB.parseList(criteria); - if (dblist == null) return null; - Criteria crit = new Criteria(dblist.getNCols(), dblist.getNRows()); - crit.colHeaders = dblist.colHeaders; - crit.rows = dblist.rows; - if (DatabaseCalculator.DEBUG) { - Logger.logInfo("\nCriteria:"); - for (int i = 0; i < crit.getNCols(); i++) { - Logger.logInfo("\t" + crit.getCol(i)); - } - for (int j = 0; j < crit.getNCols(); j++) { - for (int i = 0; i < crit.getNRows(); i++) { - Logger.logInfo("\t" + crit.getCell(i, j)); - } - } - } - return crit; - } - - // TODO: Handle formula criteria! - // TODO: To perform an operation on an entire column in a database, enter a blank line below the column labels in the criteria range - // TODO: Handle various EQUALS: currency, number ... - public boolean matches(String v, Object cx) { - boolean bMatches = false; - String c = ""; - - if (cx instanceof Ptg) - c = ((Ptg) cx).getValue().toString(); - else - c = cx.toString(); - - if (c == null || c.length() == 0) - return false; - if (v == null) - return false; // 20070208 KSC: null means no match! - - // TODO: handle this using calc methods - - // relational - if (c.substring(0, 1).equals(">")) { - try { - if (c.length() > 1 && c.substring(0, 2).equals(">=")) { - c = c.substring(2); - bMatches = (Double.parseDouble(v) >= Double.parseDouble(c)); - } else { - c = c.substring(1); - bMatches = (Double.parseDouble(v) > Double.parseDouble(c)); - } - } catch (NumberFormatException e) { - } - } else if (c.substring(0, 1).equals("<")) { - try { - if (c.length() > 1 && c.substring(0, 2).equals("<=")) { - c = c.substring(2); - bMatches = (Double.parseDouble(v) <= Double.parseDouble(c)); - } else { - c = c.substring(1); - bMatches = (Double.parseDouble(v) < Double.parseDouble(c)); - } - } catch (NumberFormatException e) { - } - } else {// Equals - bMatches = (v.equalsIgnoreCase(c)); - } - return bMatches; - } - - /** - * "passes" means that there is a matching - * and valid (criteria passing) cell in the - * row of the data db cells - * - * @param field - the field or column that is being compared - * @param curRow - check the current row/col - * @param db - the db of vals - * @return - */ - public boolean passes(String field, Ptg[] curRow, DB db) { - int nrows = getNRows(); - int ncols = getNCols(); - - int crit_format = -1; // determine format of criteria table - // multiple rows of criteria for one column are OR searches - // on that column (col=x OR col=y OR col=z) - if (nrows > 1 && ncols == 1) - crit_format = 0; - - // more than one column are AND searches (cola=x AND colb=y) - // ONE row of criteria is applied to N rows of data - if (nrows == 1 && ncols > 1) - crit_format = 1; - - // more than one row and more than one column: - // OR's: (cola=x AND colb=y) || (cola=z AND colb=w) - // applied to each row of data - if (nrows > 1 && ncols > 1) - crit_format = 2; - - if (nrows == 1 && ncols == 1) - crit_format = 0; - - switch (crit_format) { - - case 0: - return criteriaCheck1(curRow, db); - - case 1: - return criteriaCheck2(field, curRow, db); - - case 2: - return criteriaCheck3(field, curRow, db); - - default: - return criteriaCheck3(field, curRow, db); - } - } - - /** - * for the current db row/column, see if all criteria for that column matches - * (criteria may run over many criteria rows & signifies an OR search) - * find db column that matches criteria col - *

                      - * multiple rows of data one row of criteria - *

                      - * age height - * <29 >99 - *

                      - * age height - * 23 88 - * 21 99 - * 43 56 - * 23 56 - * 44 76 - * - * @param curRow - * @param db - * @return - */ - private boolean criteriaCheck1(Ptg[] curRow, DB db) { - boolean bColOK = false; - boolean bRowOK = true; - int nrows = getNRows(); - int ncols = getNCols(); - int ndbrows = db.getNRows(); - - String cl = getCol(0); - int j = db.findCol(cl); - if (j >= 0) { - bColOK = false; // need one bColOK= true for it to pass - for (int k = 0; k < nrows && !bColOK; k++) { // - try { - String v = curRow[j].getValue().toString(); - Ptg r = rows[k][0]; - String rv = r.getValue().toString(); - bColOK = matches(v, r); - - // fast succeed - if (bColOK) - return true; - - } catch (Exception ex) { - // Logger.logInfo("DBCalc"); // TODO: check that this is OK - } - } - if (!bColOK) - bRowOK = false; // if no criteria passes, row doesn't pass - } - return bRowOK; - } - - - /** - * There is only one row of criteria, - * but may be multiple criteria per field aka: - *

                      - * type age age height - * blue >21 <50 44 - *

                      - *

                      - * // [v1 v2 v3] - * // [v2 v3 v4] - * // ... - * // [crit1 crit2 crit3] <- val must pass this - * - * @param field - * @param curRow - * @param db - * @return - */ - private boolean criteriaCheck2(String field, - Ptg[] curRow, - DB db) { - boolean pass = true; - for (int t = 0; t < curRow.length; t++) { - String valcheck = curRow[t].getValue().toString(); - // for each value check all the criteria - for (int x = 0; x < rows.length; x++) { - List r = this.getCriteria(db.colHeaders[t]); - Iterator tx = r.iterator(); - while (tx.hasNext()) { - Ptg cv = ((Ptg) tx.next()); - String vc = cv.getValue().toString(); - if (!vc.equals("")) { - pass = matches(valcheck, vc); - // fast fail is OK - if (!pass) - return false; - } - } - } - } - return pass; - } - - - /** - * othertimes we have a 2D criteria - *

                      - * type age age height - * blue >21 >50 44 - * blue <30 <100 - * red >30 >40 99 - *

                      - *

                      - * AND criteria across criteria rows - * OR criteria down cols - * - * @param field - * @param curRow - * @param db - * @return - */ - private boolean criteriaCheck3(String field, - Ptg[] curRow, - DB db) { - boolean critRowMatch = false; - // for each value check all the criteria in a row - // multiple rows of criteria are combined - for (int x = 0; x < rows.length; x++) { - critRowMatch = true; // reset - // for each row of criteria, iterate criteria cols - for (int rs = 0; rs < this.colHeaders.length; rs++) { - String critField = this.colHeaders[rs]; - - List r = this.getCriteria(x, critField); - Iterator tx = r.iterator(); - int dv = db.getCol(critField); - String valcheck = curRow[dv].getValue().toString(); - // this criteria row may pass/fail subsequent rows may pass/fail - // only one has to pass OK to return true all crit in this row must pass - while (tx.hasNext() - && critRowMatch) { // stop if row failure - Ptg cv = ((Ptg) tx.next()); - String vc = cv.getValue().toString(); - - if (!vc.equals("")) { - critRowMatch = matches(valcheck, vc); - /* fast fail is not OK because we - * may pass one row OR another - * AND criteria across criteria rows - * OR criteria down cols - */ - } - } - } - if (critRowMatch) // fast succeed here, a row passed - return true; - } - return critRowMatch; - } - - - /** - * equal number of data rows/cols and criteria rows/cols - *

                      - * compare each criteria cell with each corresponding db cell - * - * @param field - * @param curRow - * @param db - * @return - */ - private boolean criteriaCheck4(String field, Ptg[] curRow, DB db) { - boolean bColOK = false; - boolean bRowOK = false; - int nrows = getNRows(); - int ncols = getNCols(); - - for (int k = 0; k < nrows && !bRowOK; k++) { // for each row of criteria - // for each col check for valid criteria - for (int i = 0; i < ncols; i++) { - // find db column that matches criteria column - // String coli = getCol(i); // get the col - - int coln = db.findCol(field); // which db col is this? - - if (coln >= 0) { // matching col in dblist - // the current criteria matches on colname - Ptg curcrit = null; - if (rows[k].length > ncols) { // matched criteria cols and dbcols - curcrit = rows[k][coln]; - } else { - Logger.logWarn("DatabaseCalculator.Criteria.criteriaCheck4: wrong criteria count for db value count"); - return false; - } - String rt = curcrit.getValue().toString(); - - // the current val is in the matching column - Ptg rx = curRow[coln]; - String mv = rx.getValue().toString(); - - // check field if this is a matching lookup - if ((i == 0)) { - if (!mv.equals(rt)) - return false; - } else { // check criteria, criteria is fast fail - if (rt.equals("")) { // empty criteria is not false - - } else { // for the current db row/column, see if all criteria for that column matches - //(criteria may run over many criteria rows & signifies an OR search) - bColOK = matches(rt, rx); - - if (!bColOK && nrows == 1) // fast fail - return false; - - - if (bColOK) // fast succeed - return true; - } - } - } else { // see if it's a formula - defined to NOT match db column - // Logger.logWarn("DataBaseCalculator.Criteria.passes: no matching col in dblist."); - } - } - if (bColOK) - return true; // does this cut the fat? bRowOK= true; // if any row's column criteria passes, row passes - } - return bRowOK; - } - - private Map criteriaCache = null; - - /** - * return cached array of criteria Ptgs for a given field - *

                      - * TODO: cache reset - * - * @param field - * @return - */ - private List getCriteria(String field) { - if (true) //criteriaCache==null) - criteriaCache = new Hashtable(); // map of vector criteria - else if (criteriaCache.get(field) != null) - return (List) criteriaCache.get(field); - - List crits = new Vector(); - for (int x = 0; x < rows.length; x++) { - for (int t = 0; t < colHeaders.length; t++) { - if (colHeaders[t].equals(field)) { - crits.add(rows[x][t]); - } - } - } - criteriaCache.put(field, crits); - return crits; - } - - /** - * return cached array of criteria Ptgs for a given field - *

                      - * TODO: cache reset - * - * @param field - * @return - */ - private List getCriteria(int critRow, String field) { - List crits = new Vector(); - if ((critRow != -1)) { // option to return criteria per row - for (int t = 0; t < colHeaders.length; t++) { - if (colHeaders[t].equals(field)) { - crits.add(rows[critRow][t]); - } - } - } - return crits; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.kt new file mode 100644 index 0000000..b6a053f --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/DatabaseCalculator.kt @@ -0,0 +1,1264 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.ReferenceTracker +import io.starter.toolkit.Logger + +import java.util.* + + +/** + * DatabaseCalculator is a collection of static methods that operate + * as the Microsoft Excel function calls do. + * + * + * All methods are called with an array of ptg's, which are then + * acted upon. A Ptg of the type that makes sense (ie boolean, number) + * is returned after each method. + * + * + * + * + * Database and List Management functions + * Microsoft Excel includes worksheet functions that analyze data stored in lists or databases. + * Each of these functions, referred to collectively as the Dfunctions, uses three arguments: database, field, and criteria. + * + * + * These arguments refer to the worksheet ranges that are used by the function. + * + * + * DAVERAGE Returns the average of selected database entries + * + * + * DCOUNT Counts the cells that contain numbers in a database + * + * + * DCOUNTA Counts nonblank cells in a database + * + * + * DGET Extracts from a database a single record that matches the specified criteria + * + * + * DMAX Returns the maximum value from selected database entries + * + * + * DMIN Returns the minimum value from selected database entries + * + * + * DPRODUCT Multiplies the values in a particular field of records that match the criteria in a database + * + * + * DSTDEV Estimates the standard deviation based on a sample of selected database entries + * + * + * DSTDEVP Calculates the standard deviation based on the entire population of selected database entries + * + * + * DSUM Adds the numbers in the field column of records in the database that match the criteria + * + * + * DVAR Estimates variance based on a sample from selected database entries + * + * + * DVARP Calculates variance based on the entire population of selected database entries + * + * + * GETPIVOTDATA Returns data stored in a PivotTable + * + * + * + * + * ABOUT DB + * + * + * All Database Formulas take 3 arguments: + * Database is the range of cells that makes up the list or database. + * + * + * A database is a list of related data in which rows of related information + * are records, and columns of data are fields. + * + * + * The first row of the list contains labels for each column. + * Field indicates which column is used in the function. + * Field can be given as text with the column label + * enclosed between double quotation marks, such as "Age" or "Yield," + * or as a number that represents the position of the column within the + * list: 1 for the first column, 2 for the second column, and so on. + * + * + * Criteria is the range of cells that contains the conditions you specify. + * + * + * You can use any range for the criteria argument, * as long as it + * includes at least one column label and at least one cell below the column + * label for specifying a condition for the column. + * + * + * Make sure the criteria range does not overlap the list. + * + * + * + * + * + * + * TODO To change the template for this generated type comment go to + * Window - Preferences - Java - Code Style - Code Templates + */ +object DatabaseCalculator { + var DEBUG = false + + /** + * Fetch a DB from the cache or create a new one + * + * + * + * + * Dbs store Cell refs... + * + * @param operands + * @return + */ + private fun getDb(operands: Ptg): DB? { + val DBcache = operands.parentRec.workBook!!.refTracker + + //gonna try never caching this... painful, but if we dont' + if (DBcache!!.listDBs[operands.toString()] != null) { + //Logger.logInfo("getDB: " + operands.toString()+ "using cache."); + return DBcache.listDBs[operands.toString()] as DB + } + //} + // create new + //Logger.logErr("getDB: " + operands.toString()+ "NOT cached."); + val dbrange = PtgCalculator.getAllComponents(operands) + val ret = DB.parseList(dbrange) + DBcache.listDBs[operands.toString()] = ret + return ret + } + + private fun getCriteria(operands: Ptg): Criteria? { + val DBcache = operands.parentRec.workBook!!.refTracker + + // test without cache + if (DBcache!!.criteriaDBs[operands.toString()] != null) { + //Logger.logInfo("getCriteria: " + operands.toString()+ "using cache."); + return DBcache.criteriaDBs[operands.toString()] as Criteria + } + //Logger.logErr("getCriteria: " + operands.toString()+ "NOT cached."); + val criteria = PtgCalculator.getAllComponents(operands) + val ret = Criteria.parseCriteria(criteria) + DBcache.criteriaDBs[operands.toString()] = ret + return ret + } + + /** + * DAVERAGE Returns the average of selected database entries + */ + internal fun calcDAverage(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NA) + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + var average = 0.0 + var count = 0 + + // this is the colname to match + val colname = operands[1].value.toString() + for (i in db.rows.indices) { // loop thru all db rows + // check if current row passes criteria requirements + val rwz = db.getRow(i) // slight optimization one less call to getRow -jm + if (crit.passes(colname, rwz, db)) { + // passes; now do action + val vx = rwz!![fNum] + if (vx != null) { + try { + average += java.lang.Double.parseDouble(vx.value.toString()) + count++ // if it can be parsed into a number, increment count + } catch (exp: NumberFormatException) { + } + + } + } + } + if (count > 0) average = average / count + return PtgNumber(average) + } + + /** + * DCOUNT Counts the cells that contain numbers in a database + */ + internal fun calcDCount(operands: Array): Ptg { + if (operands.size != 3) + return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + + var count = 0 + val nrow = db.nRows + // this is the colname to match + val colname = operands[1].value.toString() + for (i in 0 until nrow) { // loop thru all db rows + // check if current row passes criteria requirements + try { + val rr = db.getRow(i) + + /* "passes" means that there is a matching + * cell in the row of the data db cells + * + */ + if (crit.passes(colname, rr, db)) { + // passes; now do action + val cx = db.getCell(i, fNum) + val vtx = cx!!.value.toString() + if (vtx != null) { + java.lang.Double.parseDouble(vtx) + count++ // if it can be parsed into a number, increment count + } + } + } catch (e: NumberFormatException) { + } + + } + return PtgNumber(count.toDouble()) + } + + /** + * DCOUNTA Counts nonblank cells in a database + */ + internal fun calcDCountA(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + var count = 0 + + // this is the colname to match + val colname = operands[1].value.toString() + for (i in 0 until db.nRows) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + val s = db.getCell(i, fNum)!!.value.toString() + if (s != null && s.trim { it <= ' ' } != "") + count++ // if field is not blank, increment count + } + } + + return PtgNumber(count.toDouble()) + } + + /** + * DGET Extracts from a database a single record that matches the specified criteria + */ + internal fun calcDGet(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NULL) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + var `val` = "" + var count = 0 + + // this is the colname to match + val colname = operands[1].value.toString() + for (i in 0 until db.nRows) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + `val` = db.getCell(i, fNum)!!.value.toString() + count++ + } + } + if (count == 0) return PtgErr(PtgErr.ERROR_VALUE) // no recs match + return if (count > 1) PtgErr(PtgErr.ERROR_NUM) else PtgStr(`val`) // if more than one record matches criteria + } + + /** + * DMAX Returns the maximum value from selected database entries + */ + internal fun calcDMax(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + var max = java.lang.Double.MIN_VALUE + + // this is the colname to match + val colname = operands[1].value.toString() + for (i in 0 until db.nRows) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + val vtx = db.getCell(i, fNum)!!.value.toString() + if (vtx != null) { + try { + if (vtx.length > 0) max = Math.max(max, java.lang.Double.parseDouble(vtx)) + } catch (exp: NumberFormatException) { + } + + } + } + } + + if (max == java.lang.Double.MIN_VALUE) max = 0.0 + return PtgNumber(max) + } + + /** + * DMIN Returns the minimum value from selected database entries + */ + internal fun calcDMin(operands: Array): Ptg { + if (operands.size != 3) + // sanity checks + return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + var min = java.lang.Double.MAX_VALUE + // this is the colname to match + val colnamx = operands[1].value.toString() + + for (i in 0 until db.nRows) { // loop thru all db rows + // check if current row passes criteria requirements + try { + val rwz = db.getRow(i) + + if (crit.passes(colnamx, rwz, db)) { + // passes; now do action + try { + val dbx = db.getCell(i, fNum) + + if (dbx != null) { + val dnb = dbx.value.toString() + if (dnb != null) { + if (dnb.length > 0) + min = Math.min(min, java.lang.Double.parseDouble(dnb)) + } + } + } catch (ex: Exception) { + // normal blanks etc. + } + + } + } catch (e: NumberFormatException) { + } + + } + if (min == java.lang.Double.MAX_VALUE) min = 0.0 + return PtgNumber(min) + } + + /** + * DPRODUCT Multiplies the values in a particular field of records that match the criteria in a database + */ + internal fun calcDProduct(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + var product = 1.0 + // this is the colname to match + val colname = operands[1].value.toString() + + for (i in 0 until db.nRows) { // loop thru all db rows + // check if current row passes criteria requirements + try { + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + val fnx = db.getCell(i, fNum)!!.value.toString() + if (fnx != null) + product *= java.lang.Double.parseDouble(fnx) + } + } catch (e: NumberFormatException) { + } + + } + return PtgNumber(product) + } + + /** + * DSTDEV Estimates the standard deviation based on a sample of selected database entries + */ + internal fun calcDStdDev(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + val vals = java.util.ArrayList() + var sum = 0.0 + var count = 0 + // this is the colname to match + val colname = operands[1].value.toString() + + for (i in 0 until db.nRows) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + try { + val fnx = db.getCell(i, fNum)!!.value.toString() + if (fnx != null) { + val x = java.lang.Double.parseDouble(fnx) + sum += x + count++ + vals.add(java.lang.Double.toString(x)) + } + } catch (e: NumberFormatException) { + } + + } + } + var stdev = 0.0 + if (count > 0) { + val average = sum / count + // now have all values in vals + for (i in 0 until count) { + val x = java.lang.Double.parseDouble(vals.get(i) as String) + stdev += Math.pow(x - average, 2.0) + } + if (count > 1) count-- + stdev = Math.sqrt(stdev / count) + } + return PtgNumber(stdev) + } + + /** + * DSTDEVP Calculates the standard deviation based on the entire population of selected database entries + */ + internal fun calcDStdDevP(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + val vals = java.util.ArrayList() + var sum = 0.0 + var count = 0 + // this is the colname to match + val colname = operands[1].value.toString() + + for (i in 0 until db.nRows) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + try { + val fnx = db.getCell(i, fNum)!!.value.toString() + if (fnx != null) { + val x = java.lang.Double.parseDouble(fnx) + sum += x + count++ + vals.add(java.lang.Double.toString(x)) + } + } catch (e: NumberFormatException) { + } + + } + } + var stdevp = 0.0 + if (count > 0) { + val average = sum / count + // now have all values in vals + for (i in 0 until count) { + val x = java.lang.Double.parseDouble(vals.get(i) as String) + stdevp += Math.pow(x - average, 2.0) + } + stdevp = Math.sqrt(stdevp / count) + } + return PtgNumber(stdevp) + } + + /** + * DSUM Adds the numbers in the field column of records in the database that match the criteria + */ + internal fun calcDSum(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + + val count = 0 + var sum = 0.0 + val nrow = db.nRows + // this is the colname to match + val colname = operands[1].value.toString() + for (i in 0 until nrow) { // loop thru all db rows + // check if current row passes criteria requirements + try { + val rr = db.getRow(i) + + /* "passes" means that there is a matching + * cell in the row of the data db cells + * + */ + if (crit.passes(colname, rr, db)) { + // passes; now do action + try { + val fnx = db.getCell(i, fNum)!!.value.toString() + if (fnx != null) + sum += java.lang.Double.parseDouble(fnx) + } catch (e: NumberFormatException) { + } + + } + } catch (e: NumberFormatException) { + } + + } + return PtgNumber(sum) + } + + + /** + * DVAR Estimates variance based on a sample from selected database entries + */ + internal fun calcDVar(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + val vals = java.util.ArrayList() + var sum = 0.0 + var count = 0 + + // this is the colname to match + val colname = operands[1].value.toString() + for (i in 0 until db.nRows) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + try { + val fnx = db.getCell(i, fNum)!!.value.toString() + if (fnx != null) { + val x = java.lang.Double.parseDouble(db.getCell(i, fNum)!!.toString()) + sum += x + count++ + vals.add(java.lang.Double.toString(x)) + } + } catch (e: NumberFormatException) { + } + + } + } + var variance = 0.0 + if (count > 0) { + val average = sum / count + // now have all values in vals + for (i in 0 until count) { + val x = java.lang.Double.parseDouble(vals.get(i) as String) + variance += Math.pow(x - average, 2.0) + } + if (count > 1) count-- + variance = variance / count + } + return PtgNumber(variance) + } + + /* DVARP Calculates variance based on the entire population of selected database entries + */ + internal fun calcDVarP(operands: Array): Ptg { + if (operands.size != 3) return PtgErr(PtgErr.ERROR_NA) + + val db = getDb(operands[0]) + val crit = getCriteria(operands[2]) + if (db == null || crit == null) + return PtgErr(PtgErr.ERROR_NUM) + val fNum = db.findCol(operands[1].string.trim { it <= ' ' }) + if (fNum == -1) + return PtgErr(PtgErr.ERROR_NUM) + val vals = java.util.ArrayList() + var sum = 0.0 + var count = 0 + + // this is the colname to match + val colname = operands[1].value.toString() + for (i in 0 until db.nRows) { // loop thru all db rows + // check if current row passes criteria requirements + if (crit.passes(colname, db.getRow(i), db)) { + // passes; now do action + try { + val fnx = db.getCell(i, fNum)!!.value.toString() + if (fnx != null) { + val x = java.lang.Double.parseDouble(fnx) + sum += x + count++ + vals.add(java.lang.Double.toString(x)) + } + } catch (e: NumberFormatException) { + } + + } + } + var varP = 0.0 + if (count > 0) { + val average = sum / count + // now have all values in vals + for (i in 0 until count) { + val x = java.lang.Double.parseDouble(vals.get(i) as String) + varP += Math.pow(x - average, 2.0) + } + varP = varP / count + } + return PtgNumber(varP) + } + /* GETPIVOTDATA Returns data stored in a PivotTable + * + * + */ +} + + +/** + * EXPLANATION of Database Formulas + * + * + * Database is the range of cells that makes up the list or database. + * A database is a list of related data in which rows of related + * information are records, and columns of data are fields. + * The first row of the list contains labels for each column. + * + * + * Field indicates which column is used in the function. + * Field can be given as text with the column label enclosed between + * double quotation marks, such as "Age" or "Yield," or as a number + * that represents the position of the column within the list: + * + * + * 1 for the first column, + * 2 for the second column, and so on. + * + * + * NOTE: quotes around field text is optional: + * first row of columns and rows are field labels. + * + * + * Criteria is the range of cells that contains the conditions + * you specify. You can use any range for the criteria argument, + * as long as it includes at least one column label and at least one + * cell below the column label for specifying a condition for the column. + * + * + * Example + */ +internal open class DB(nCols: Int, nRows: Int) { + var colHeaders: Array + var rows: Array> + + val nCols: Int + get() = colHeaders.size + + val nRows: Int + get() = rows.size + + init { + colHeaders = arrayOfNulls(nCols) + // TODO: replace with PtgRef array! + rows = Array(nRows) { arrayOfNulls(nCols) } + } + + /** + * return the index of the col in the DB + * + * @param cname + * @return + */ + fun getCol(cname: String): Int { + for (t in colHeaders.indices) { + if (colHeaders[t].equals(cname, ignoreCase = true)) { + return t + } + } + return -1 + } + + /** + * return a row of DB ptgs + * + * @param i + * @return + */ + fun getRow(i: Int): Array? { + return if (i > -1 && i < rows.size) rows[i] else null + } + + /** + * return a col of ?? + * + * @param i + * @return + */ + fun getCol(i: Int): String? { + return if (i > -1 && i < colHeaders.size) colHeaders[i] else null + } + + fun getCell(row: Int, col: Int): Ptg? { + try { + return rows[row][col] + } catch (e: Exception) { + return null + } + + } + + fun findCol(f: String?): Int { + for (i in colHeaders.indices) { + if (colHeaders[i].trim { it <= ' ' }.equals(f!!, ignoreCase = true)) { + return i + } + } + try { + val j = Integer.parseInt(f!!) // one-based index into columns + return j - 1 + } catch (e: Exception) { + return -1 + } + + } + + companion object { + + /** + * Write some documentation here please... thanks! -jm + * + * @param dbrange + * @return + */ + fun parseList(dbrange: Array): DB? { + var prevCol = -1 + var nCols = 0 + var nRows = 0 + var maxRows = 0 + + // allocate the empty table for the dbrange + for (i in dbrange.indices) { + if (dbrange[i] is PtgRef) { + val pref = dbrange[i] as PtgRef + val loc = pref.intLocation + // TODO: check rc sanity here + if (loc!![1] != prevCol) { // count # cols + prevCol = loc[1] + nCols++ + nRows = 0 + } else { // count # rows + nRows++ + maxRows = Math.max(nRows, maxRows) + } + } else + return null + } + + // now populate the table + val dblist = DB(nCols, maxRows) + prevCol = -1 + nCols = -1 + nRows = -1 + for (i in dbrange.indices) { + val db1 = dbrange[i] as PtgRef + val loc = db1.intLocation + var vs: Any? = null // 20081120 KSC: Must distinguish between blanks and 0's + if (!db1.isBlank) + vs = db1.value + + // column headers + if (loc!![1] != prevCol) { + if (vs != null) + dblist.colHeaders[++nCols] = vs.toString() + prevCol = loc[1] + nRows = 0 + } else { // get value Ptgs + try { + dblist.rows[nRows++][nCols] = db1 + } catch (e: ArrayIndexOutOfBoundsException) { + // possible nCols==-1 + } + + } + } + return dblist + } + } +} + +internal class Criteria(nCols: Int, nRows: Int) : DB(nCols, nRows) { + + private var criteriaCache: MutableMap<*, *>? = null + + // TODO: Handle formula criteria! + // TODO: To perform an operation on an entire column in a database, enter a blank line below the column labels in the criteria range + // TODO: Handle various EQUALS: currency, number ... + fun matches(v: String?, cx: Any): Boolean { + var bMatches = false + var c: String? = "" + + if (cx is Ptg) + c = cx.value.toString() + else + c = cx.toString() + + if (c == null || c.length == 0) + return false + if (v == null) + return false // 20070208 KSC: null means no match! + + // TODO: handle this using calc methods + + // relational + if (c.substring(0, 1) == ">") { + try { + if (c.length > 1 && c.substring(0, 2) == ">=") { + c = c.substring(2) + bMatches = java.lang.Double.parseDouble(v) >= java.lang.Double.parseDouble(c) + } else { + c = c.substring(1) + bMatches = java.lang.Double.parseDouble(v) > java.lang.Double.parseDouble(c) + } + } catch (e: NumberFormatException) { + } + + } else if (c.substring(0, 1) == "<") { + try { + if (c.length > 1 && c.substring(0, 2) == "<=") { + c = c.substring(2) + bMatches = java.lang.Double.parseDouble(v) <= java.lang.Double.parseDouble(c) + } else { + c = c.substring(1) + bMatches = java.lang.Double.parseDouble(v) < java.lang.Double.parseDouble(c) + } + } catch (e: NumberFormatException) { + } + + } else {// Equals + bMatches = v.equals(c, ignoreCase = true) + } + return bMatches + } + + /** + * "passes" means that there is a matching + * and valid (criteria passing) cell in the + * row of the data db cells + * + * @param field - the field or column that is being compared + * @param curRow - check the current row/col + * @param db - the db of vals + * @return + */ + fun passes(field: String, curRow: Array?, db: DB): Boolean { + val nrows = nRows + val ncols = nCols + + var crit_format = -1 // determine format of criteria table + // multiple rows of criteria for one column are OR searches + // on that column (col=x OR col=y OR col=z) + if (nrows > 1 && ncols == 1) + crit_format = 0 + + // more than one column are AND searches (cola=x AND colb=y) + // ONE row of criteria is applied to N rows of data + if (nrows == 1 && ncols > 1) + crit_format = 1 + + // more than one row and more than one column: + // OR's: (cola=x AND colb=y) || (cola=z AND colb=w) + // applied to each row of data + if (nrows > 1 && ncols > 1) + crit_format = 2 + + if (nrows == 1 && ncols == 1) + crit_format = 0 + + when (crit_format) { + + 0 -> return criteriaCheck1(curRow, db) + + 1 -> return criteriaCheck2(field, curRow!!, db) + + 2 -> return criteriaCheck3(field, curRow, db) + + else -> return criteriaCheck3(field, curRow, db) + } + } + + /** + * for the current db row/column, see if all criteria for that column matches + * (criteria may run over many criteria rows & signifies an OR search) + * find db column that matches criteria col + * + * + * multiple rows of data one row of criteria + * + * + * age height + * <29 >99 + * + * + * age height + * 23 88 + * 21 99 + * 43 56 + * 23 56 + * 44 76 + * + * @param curRow + * @param db + * @return + */ + private fun criteriaCheck1(curRow: Array?, db: DB): Boolean { + var bColOK = false + var bRowOK = true + val nrows = nRows + val ncols = nCols + val ndbrows = db.nRows + + val cl = getCol(0) + val j = db.findCol(cl) + if (j >= 0) { + bColOK = false // need one bColOK= true for it to pass + var k = 0 + while (k < nrows && !bColOK) { // + try { + val v = curRow!![j].value.toString() + val r = rows[k][0] + val rv = r.value.toString() + bColOK = matches(v, r) + + // fast succeed + if (bColOK) + return true + + } catch (ex: Exception) { + // Logger.logInfo("DBCalc"); // TODO: check that this is OK + } + + k++ + } + if (!bColOK) + bRowOK = false // if no criteria passes, row doesn't pass + } + return bRowOK + } + + + /** + * There is only one row of criteria, + * but may be multiple criteria per field aka: + * + * + * type age age height + * blue >21 <50 44 + * + * + * + * + * // [v1 v2 v3] + * // [v2 v3 v4] + * // ... + * // [crit1 crit2 crit3] <- val must pass this + * + * @param field + * @param curRow + * @param db + * @return + */ + private fun criteriaCheck2(field: String, + curRow: Array, + db: DB): Boolean { + var pass = true + for (t in curRow.indices) { + val valcheck = curRow[t].value.toString() + // for each value check all the criteria + for (x in rows.indices) { + val r = this.getCriteria(db.colHeaders[t]) + val tx = r.iterator() + while (tx.hasNext()) { + val cv = tx.next() as Ptg + val vc = cv.value.toString() + if (vc != "") { + pass = matches(valcheck, vc) + // fast fail is OK + if (!pass) + return false + } + } + } + } + return pass + } + + + /** + * othertimes we have a 2D criteria + * + * + * type age age height + * blue >21 >50 44 + * blue <30 <100 + * red >30 >40 99 + * + * + * + * + * AND criteria across criteria rows + * OR criteria down cols + * + * @param field + * @param curRow + * @param db + * @return + */ + private fun criteriaCheck3(field: String, + curRow: Array?, + db: DB): Boolean { + var critRowMatch = false + // for each value check all the criteria in a row + // multiple rows of criteria are combined + for (x in rows.indices) { + critRowMatch = true // reset + // for each row of criteria, iterate criteria cols + for (rs in this.colHeaders.indices) { + val critField = this.colHeaders[rs] + + val r = this.getCriteria(x, critField) + val tx = r.iterator() + val dv = db.getCol(critField) + val valcheck = curRow!![dv].value.toString() + // this criteria row may pass/fail subsequent rows may pass/fail + // only one has to pass OK to return true all crit in this row must pass + while (tx.hasNext() && critRowMatch) { // stop if row failure + val cv = tx.next() as Ptg + val vc = cv.value.toString() + + if (vc != "") { + critRowMatch = matches(valcheck, vc) + /* fast fail is not OK because we + * may pass one row OR another + * AND criteria across criteria rows + * OR criteria down cols + */ + } + } + } + if (critRowMatch) + // fast succeed here, a row passed + return true + } + return critRowMatch + } + + + /** + * equal number of data rows/cols and criteria rows/cols + * + * + * compare each criteria cell with each corresponding db cell + * + * @param field + * @param curRow + * @param db + * @return + */ + private fun criteriaCheck4(field: String, curRow: Array, db: DB): Boolean { + var bColOK = false + val bRowOK = false + val nrows = nRows + val ncols = nCols + + var k = 0 + while (k < nrows && !bRowOK) { // for each row of criteria + // for each col check for valid criteria + for (i in 0 until ncols) { + // find db column that matches criteria column + // String coli = getCol(i); // get the col + + val coln = db.findCol(field) // which db col is this? + + if (coln >= 0) { // matching col in dblist + // the current criteria matches on colname + var curcrit: Ptg? = null + if (rows[k].size > ncols) { // matched criteria cols and dbcols + curcrit = rows[k][coln] + } else { + Logger.logWarn("DatabaseCalculator.Criteria.criteriaCheck4: wrong criteria count for db value count") + return false + } + val rt = curcrit.value.toString() + + // the current val is in the matching column + val rx = curRow[coln] + val mv = rx.value.toString() + + // check field if this is a matching lookup + if (i == 0) { + if (mv != rt) + return false + } else { // check criteria, criteria is fast fail + if (rt == "") { // empty criteria is not false + + } else { // for the current db row/column, see if all criteria for that column matches + //(criteria may run over many criteria rows & signifies an OR search) + bColOK = matches(rt, rx) + + if (!bColOK && nrows == 1) + // fast fail + return false + + + if (bColOK) + // fast succeed + return true + } + } + } else { // see if it's a formula - defined to NOT match db column + // Logger.logWarn("DataBaseCalculator.Criteria.passes: no matching col in dblist."); + } + } + if (bColOK) + return true // does this cut the fat? bRowOK= true; // if any row's column criteria passes, row passes + k++ + } + return bRowOK + } + + /** + * return cached array of criteria Ptgs for a given field + * + * + * TODO: cache reset + * + * @param field + * @return + */ + private fun getCriteria(field: String): List<*> { + if (true) + //criteriaCache==null) + criteriaCache = Hashtable() // map of vector criteria + else if (criteriaCache!!.get(field) != null) + return criteriaCache!!.get(field) as List<*> + + val crits = Vector() + for (x in rows.indices) { + for (t in colHeaders.indices) { + if (colHeaders[t] == field) { + crits.add(rows[x][t]) + } + } + } + criteriaCache!![field] = crits + return crits + } + + /** + * return cached array of criteria Ptgs for a given field + * + * + * TODO: cache reset + * + * @param field + * @return + */ + private fun getCriteria(critRow: Int, field: String): List<*> { + val crits = Vector() + if (critRow != -1) { // option to return criteria per row + for (t in colHeaders.indices) { + if (colHeaders[t] == field) { + crits.add(rows[critRow][t]) + } + } + } + return crits + } + + companion object { + + fun parseCriteria(criteria: Array): Criteria? { + val dblist = DB.parseList(criteria) ?: return null + val crit = Criteria(dblist.nCols, dblist.nRows) + crit.colHeaders = dblist.colHeaders + crit.rows = dblist.rows + if (DatabaseCalculator.DEBUG) { + Logger.logInfo("\nCriteria:") + for (i in 0 until crit.nCols) { + Logger.logInfo("\t" + crit.getCol(i)!!) + } + for (j in 0 until crit.nCols) { + for (i in 0 until crit.nRows) { + Logger.logInfo("\t" + crit.getCell(i, j)!!) + } + } + } + return crit + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java deleted file mode 100644 index 8e0338b..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.java +++ /dev/null @@ -1,633 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -//import java.text.SimpleDateFormat; - -import io.starter.OpenXLS.DateConverter; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.TimeZone; - - -/** - * DateTimeCalculator is a collection of static methods that operate - * as the Microsoft Excel function calls do. - *

                      - * All methods are called with an array of ptg's, which are then - * acted upon. A Ptg of the type that makes sense (ie boolean, number) - * is returned after each method. - */ - -public class DateTimeCalculator { - /** - * utliity that takes either a PtgStr or a Number and converts it into a calendar - * for use in below functions - * - * @return - */ - private static GregorianCalendar getDateFromPtg(Ptg op) { - Object o; - if (op instanceof PtgStr) - o = calcDateValue(new Ptg[]{op}).getValue(); - else if (op instanceof PtgRef) { - o = op.getValue(); - if (o instanceof String) - o = calcDateValue(new Ptg[]{new PtgStr(o.toString())}).getValue(); - } else if (op instanceof PtgName) { - o = op.getValue(); //getComponents()[0]; - o = op.getValue(); - if (o instanceof String) - o = calcDateValue(new Ptg[]{new PtgStr(o.toString())}).getValue(); - } else - o = op.getValue(); - - return (GregorianCalendar) DateConverter.getCalendarFromNumber(o); - } - - /** - * DATE - * - * @param operands - * @return - */ - protected static Ptg calcDate(Ptg[] operands) { - long[] alloperands = PtgCalculator.getLongValueArray(operands); - if (alloperands.length != 3) return PtgCalculator.getError(); - int year = (int) alloperands[0]; - int month = (int) alloperands[1]; - month = month - 1; - int day = (int) alloperands[2]; - GregorianCalendar c = new GregorianCalendar(year, month, day); - double date = DateConverter.getXLSDateVal(c); - int i = (int) date; - PtgInt pi = new PtgInt(i); - return pi; - } - - /** - * DATEVALUE - * Returns the serial number of the date represented by date_text. Use DATEVALUE to convert a date represented by text to a serial number. - *

                      - * Syntax - * DATEVALUE(date_text) - *

                      - * Date_text is text that represents a date in a Microsoft Excel date format. For example, "1/30/2008" or "30-Jan-2008" are text strings - * within quotation marks that represent dates. Using the default date system in Excel for Windows, - * date_text must represent a date from January 1, 1900, to December 31, 9999. Using the default date system in Excel for the Macintosh, - * date_text must represent a date from January 1, 1904, to December 31, 9999. DATEVALUE returns the #VALUE! error value if date_text is out of this range. - *

                      - * If the year portion of date_text is omitted, DATEVALUE uses the current year from your computer's built-in clock. Time information in date_text is ignored. - *

                      - * Remarks - *

                      - * Excel stores dates as sequential serial numbers so they can be used in calculations. By default, January 1, 1900 is serial number 1, and January 1, 2008 is serial number 39448 because it is 39,448 days after January 1, 1900. Excel for the Macintosh uses a different date system as its default. - * Most functions automatically convert date values to serial numbers. - * - * @param operands Ptg[] - * @return Ptg - */ - - protected static Ptg calcDateValue(Ptg[] operands) { - // TODO: there may be formats that need to be input - if (operands == null || operands[0].getString() == null) - return new PtgErr(PtgErr.ERROR_VALUE); - - String dateString = operands[0].getString(); - Double d = DateConverter.calcDateValue(dateString); - if (d == null) - return new PtgErr(PtgErr.ERROR_VALUE); - return new PtgNumber(d); - - } - - /** - * DAY - * Return the day of the month - */ - protected static Ptg calcDay(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getError(); - try { - Object o = operands[0].getValue(); - GregorianCalendar c = (GregorianCalendar) DateConverter.getCalendarFromNumber(o); - int retdate = c.get(Calendar.DAY_OF_MONTH); - PtgInt pint = new PtgInt(retdate); - return pint; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * DAYS360 - * Calculate the difference between 2 dates based on a 360 - * day year, 12 mos, 30days each. - *

                      - * first date is lower than second, otherwise a negative - * number is returned - *

                      - * Seems pretty dumb to me, but what do I know? - */ - protected static Ptg calcDays360(Ptg[] operands) { - if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); - try { - Object o1 = operands[0].getValue(); - Object o2 = operands[1].getValue(); - GregorianCalendar dt1 = (GregorianCalendar) DateConverter.getCalendarFromNumber(o1); - GregorianCalendar dt2 = (GregorianCalendar) DateConverter.getCalendarFromNumber(o2); - int yr1 = dt1.get(Calendar.YEAR); - int yr2 = dt2.get(Calendar.YEAR); - int diff = yr2 - yr1; - diff *= 360; // turn years to days. - int mo1 = dt1.get(Calendar.MONTH); - int mo2 = dt2.get(Calendar.MONTH); - int mos = 0; - if (mo2 > mo1) { - mos = mo2 - mo1; - } else { - diff -= 360; - while (mo2 != mo1) { - mos++; - mo1++; - if (mo1 == 12) { - mo1 = 0; - } - } - } - diff += mos * 30; - int dy1 = dt1.get(Calendar.DAY_OF_MONTH); - int dy2 = dt2.get(Calendar.DAY_OF_MONTH); - if (dy2 > dy1) { - diff += dy2 - dy1; - } else { - diff -= 30; - while (dy2 != dy1) { - diff++; - dy1++; - if (dy1 == 30) { - dy1 = 0; - } - } - } - PtgInt pint = new PtgInt(diff); - return pint; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - - /** - * EDATE - * Returns the serial number that represents the date that is the indicated number of months - * before or after a specified date (the start_date). - * Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. - *

                      - * EDATE(start_date,months) - *

                      - * Start_date is a date that represents the start date. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - *

                      - * Months is the number of months before or after start_date. A positive value for months yields a future date; a negative value yields a past date. - *

                      - * If start_date is not a valid date, EDATE returns the #VALUE! error value. - * If months is not an integer, it is truncated. - */ - protected static Ptg calcEdate(Ptg[] operands) { - try { - GregorianCalendar startDate = getDateFromPtg(operands[0]); - int inc = (int) PtgCalculator.getLongValue(operands[1]); - int mm = startDate.get(Calendar.MONTH) + inc; - int y = startDate.get(Calendar.YEAR); - int d = startDate.get(Calendar.DAY_OF_MONTH); - if (mm < 0) { - mm += 12; // 0-based - y--; - } else if (mm > 11) { - mm -= 12; - y++; - } - GregorianCalendar resultDate; - resultDate = new GregorianCalendar(y, mm, d); - double retdate = DateConverter.getXLSDateVal(resultDate); - int i = (int) retdate; - return new PtgInt(i); - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - - /** - * EOMONTH - * Returns the serial number for the last day of the month that is the indicated number of months - * before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on - * the last day of the month. - *

                      - * EOMONTH(start_date,months) - *

                      - * Start_date is a date that represents the starting date. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - *

                      - * Months is the number of months before or after start_date. A positive value for months yields a future date; a negative value yields a past date. - *

                      - * If months is not an integer, it is truncated. - *

                      - * If start_date is not a valid date, EOMONTH returns the #NUM! error value. - * If start_date plus months yields an invalid date, EOMONTH returns the #NUM! error value. - */ - protected static Ptg calcEOMonth(Ptg[] operands) { - try { - GregorianCalendar startDate = getDateFromPtg(operands[0]); - int inc = operands[1].getIntVal(); - int mm = startDate.get(Calendar.MONTH) + inc; - int y = startDate.get(Calendar.YEAR); - int d = startDate.get(Calendar.DAY_OF_MONTH); - if (mm < 0) { - mm += 12; // 0-based - y--; - } else if (mm > 11) { - mm -= 12; - y++; - } - if (mm == 3 || mm == 5 || mm == 8 || mm == 10) // 0-based - d = 30; - else if (mm == 1) {// february - if (y % 4 == 0) - d = 29; - else - d = 28; - } else - d = 31; - GregorianCalendar resultDate; - resultDate = new GregorianCalendar(y, mm, d); - double retdate = DateConverter.getXLSDateVal(resultDate); - int i = (int) retdate; - return new PtgInt(i); - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_NUM); - } - - - /** - * HOUR - * Converts a serial number to an hour - */ - protected static Ptg calcHour(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getError(); - GregorianCalendar dt = getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.HOUR); - PtgInt pint = new PtgInt(retdate); - return pint; - - } - - /** - * MINUTE - * Converts a serial number to a minute - */ - protected static Ptg calcMinute(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getError(); - try { - Object o = operands[0].getValue(); - GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(o); - int retdate = dt.get(Calendar.MINUTE); - PtgInt pint = new PtgInt(retdate); - return pint; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * MONTH - * Converts a serial number to a month - */ - protected static Ptg calcMonth(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getError(); - try { - GregorianCalendar dt = getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.MONTH); - retdate++; //month is ordinal - PtgInt pint = new PtgInt(retdate); - return pint; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_NA); - } - - - /** - * NETWORKDAYS - * Returns the number of whole working days between start_date and end_date. Working days exclude weekends and any dates identified in holidays. - * Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days worked during a specific term. - *

                      - * NETWORKDAYS(start_date,end_date,holidays) - *

                      - * Start_date is a date that represents the start date. - * End_date is a date that represents the end date. - * Holidays is an optional range of one or more dates to exclude from the working calendar, such as state and federal holidays and floating holidays. The list can be either a range of cells that contains the dates or an array constant of the serial numbers that represent the dates. - *

                      - * Remarks - * If any argument is not a valid date, NETWORKDAYS returns the #VALUE! error value. - */ - protected static Ptg calcNetWorkdays(Ptg[] operands) { - try { - ArrayList holidays = new ArrayList(); - GregorianCalendar startDate = getDateFromPtg(operands[0]); - GregorianCalendar endDate = getDateFromPtg(operands[1]); - if (operands.length > 2 && operands[2] != null) { - if (operands[2] instanceof PtgRef) { - Ptg[] dts = operands[2].getComponents(); - for (int i = 0; i < dts.length; i++) { - holidays.add(getDateFromPtg(dts[i])); - } - } else // assume it's a string or a number rep of a date - holidays.add(getDateFromPtg(operands[2])); - } - int count = 0; - boolean countUp = endDate.after(startDate); - while (!startDate.equals(endDate)) { - int d = startDate.get(Calendar.DAY_OF_WEEK); - if (d != Calendar.SATURDAY && d != Calendar.SUNDAY) { - boolean OKtoIncrement = true; - // check if on a holidays - if (holidays.size() > 0) { - for (int i = 0; i < holidays.size(); i++) { - if (startDate.equals(holidays.get(i))) { - OKtoIncrement = false; - break; - } - } - } - if (OKtoIncrement) { - count++; - } - } - if (countUp) - startDate.add(Calendar.DAY_OF_MONTH, 1); - else - startDate.add(Calendar.DAY_OF_MONTH, -1); - } - return new PtgInt(count); - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * NOW - * Returns the serial number of the current date and time - */ - protected static Ptg calcNow(Ptg[] operands) { - GregorianCalendar gc = new GregorianCalendar(TimeZone.getDefault());//java.sql.Date dt = new java.sql.Date(); - // io.starter.toolkit.Logger.log(dt.toGMTString()); - double retdate = DateConverter.getXLSDateVal(gc); - return new PtgNumber(retdate); - } - - /** - * SECOND - * Converts a serial number to a second - */ - protected static Ptg calcSecond(Ptg[] operands) { - GregorianCalendar dt = getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.SECOND); - PtgInt pint = new PtgInt(retdate); - return pint; - - } - - /** - * TIME - * Returns the serial number of a particular time - * takes 3 arguments, hour, minute, second; - */ - protected static Ptg calcTime(Ptg[] operands) { - Ptg o; - if (operands[0] instanceof PtgStr) - o = calcDateValue(new Ptg[]{operands[0]}); - else - o = operands[0]; - int hour = o.getIntVal(); - int minute = operands[1].getIntVal(); - int second = operands[1].getIntVal(); - GregorianCalendar g = new GregorianCalendar(2000, 1, 1, hour, minute, second); - GregorianCalendar g2 = new GregorianCalendar(2000, 1, 1, 0, 0, 0); - double dub = DateConverter.getXLSDateVal(g); - double dub2 = DateConverter.getXLSDateVal(g2); - dub -= dub2; - return new PtgNumber(dub); - } - - /** - * TIMEVALUE - * Converts a time in the form of text to a serial number - * Returns the decimal number of the time represented by a text string. - * The decimal number is a value ranging from 0 (zero) to 0.99999999, representing the times from 0:00:00 (12:00:00 AM) to 23:59:59 (11:59:59 P.M.). - * TIMEVALUE(time_text) - * Time_text is a text string that represents a time in any one of the Microsoft Excel time formats; for example, "6:45 PM" and "18:45" text strings within quotation marks that represent time. - */ - protected static Ptg calcTimevalue(Ptg[] operands) { - double result = 0; - try { - GregorianCalendar d = getDateFromPtg(operands[0]); - int h = d.get(Calendar.HOUR_OF_DAY); - int m = d.get(Calendar.MINUTE); - int s = d.get(Calendar.SECOND); - double t = h + (m / 60.0) + (s / (60 * 60.0)); - result = (t / 24.0); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - return new PtgNumber(result); - } - - - /** - * TODAY - * Returns the serial number of today's date - */ - protected static Ptg calcToday(Ptg[] operands) { - java.sql.Date dt = new java.sql.Date(System.currentTimeMillis()); - double retdate = DateConverter.getXLSDateVal(dt); - int i = (int) retdate; - return new PtgInt(i); - } - - /** - * WEEKDAY Converts a serial number to a day of the week - */ - protected static Ptg calcWeekday(Ptg[] operands) { - if (operands.length != 1) return new PtgErr(PtgErr.ERROR_VALUE); - GregorianCalendar dt = getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.DAY_OF_WEEK); - PtgInt pint = new PtgInt(retdate); - return pint; - } - - /** - * WEEKNUM - * Returns a number that indicates where the week falls numerically within a year. - *

                      - * WEEKNUM(serial_num,return_type) - *

                      - * Serial_num is a date within the week. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - *

                      - * Return_type is a number that determines on which day the week begins. The default is 1. - * - * @param operands - * @return - */ - protected static Ptg calcWeeknum(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - int returnType = 1; - GregorianCalendar dt = getDateFromPtg(operands[0]); - if (operands[1] != null) { - returnType = operands[1].getIntVal(); - } - returnType -= 1; // 1 is default, 2 =start on monday - int retdate = dt.get(Calendar.WEEK_OF_YEAR) - returnType; - PtgInt pint = new PtgInt(retdate); - return pint; - } - - /** - * WORKDAY - * Returns a number that represents a date that is the indicated number of working days before or after a date (the starting date). - * Working days exclude weekends and any dates identified as holidays. Use WORKDAY to exclude weekends or holidays when you calculate - * invoice due dates, expected delivery times, or the number of days of work performed. - *

                      - * WORKDAY(start_date,days,holidays) - *

                      - * Important Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - * Start_date is a date that represents the start date. - * Days is the number of nonweekend and nonholiday days before or after start_date. A positive value for days yields a future date; a negative value yields a past date. - * Holidays is an optional list of one or more dates to exclude from the working calendar, such as state and federal holidays and floating holidays. The list can be either a range of cells that contain the dates or an array constant of the serial numbers that represent the dates. - *

                      - * Remarks - *

                      - * If any argument is not a valid date, WORKDAY returns the #VALUE! error value. - * If start_date plus days yields an invalid date, WORKDAY returns the #NUM! error value. - * If days is not an integer, it is truncated. - * - * @param operands - * @return - */ - protected static Ptg calcWorkday(Ptg[] operands) { - int days; - ArrayList holidays = new ArrayList(); - try { - GregorianCalendar dt = getDateFromPtg(operands[0]); - days = operands[1].getIntVal(); - if (operands.length > 2 && operands[2] != null) { // holidays - if (operands[2] instanceof PtgRef) { - Ptg[] dts = operands[2].getComponents(); - for (int i = 0; i < dts.length; i++) { - holidays.add(getDateFromPtg(dts[i])); - } - } else // assume it's a string or a number rep of a date - holidays.add(getDateFromPtg(operands[2])); - } - for (int absDays = Math.abs(days); absDays > 0; ) { - int d = dt.get(Calendar.DAY_OF_WEEK); - if (d != Calendar.SATURDAY && d != Calendar.SUNDAY) { - boolean OKtoIncrement = true; - // check if on a holidays - if (holidays.size() > 0) { - for (int i = 0; i < holidays.size(); i++) { - if (dt.equals(holidays.get(i))) { - OKtoIncrement = false; - break; - } - } - } - if (OKtoIncrement) { - absDays--; - } - } - if (days > 0) - dt.add(Calendar.DAY_OF_MONTH, 1); - else - dt.add(Calendar.DAY_OF_MONTH, -1); - } - return new PtgNumber(DateConverter.getXLSDateVal(dt)); - - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * YEAR - * Converts a serial number to a year - */ - protected static Ptg calcYear(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getError(); - try { - GregorianCalendar dt = getDateFromPtg(operands[0]); - int retdate = dt.get(Calendar.YEAR); - PtgInt pint = new PtgInt(retdate); - return pint; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_NA); - } - - /** - * YEARFRAC function - * Calculates the fraction of the year represented by the number of whole days between two dates (the start_date and the end_date). Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or obligations to assign to a specific term. - * YEARFRAC(start_date,end_date,basis) - * Important Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. - * Start_date is a date that represents the start date. - * End_date is a date that represents the end date. - * Basis is the type of day count basis to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 - */ - protected static Ptg calcYearFrac(Ptg[] operands) { - long startDate, endDate; - try { - GregorianCalendar d = getDateFromPtg(operands[0]); - startDate = (new Double(DateConverter.getXLSDateVal(d))).longValue(); - d = getDateFromPtg(operands[1]); - endDate = (new Double(DateConverter.getXLSDateVal(d))).longValue(); - } catch (Exception e) { - //If start_date or end_date are not valid dates, YEARFRAC returns the #VALUE! error value. - return new PtgErr(PtgErr.ERROR_VALUE); - } - int basis = 0; - if (operands.length > 2) - basis = operands[2].getIntVal(); - //If basis < 0 or if basis > 4, YEARFRAC returns the #NUM! error value. - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - double yf = FinancialCalculator.yearFrac(basis, startDate, endDate); - if (yf < 0) yf *= -1; // =# days between dates, no negatives - return new PtgNumber(yf); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.kt new file mode 100644 index 0000000..abc9316 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/DateTimeCalculator.kt @@ -0,0 +1,664 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +//import java.text.SimpleDateFormat; + +import io.starter.OpenXLS.DateConverter + +import java.util.ArrayList +import java.util.Calendar +import java.util.GregorianCalendar +import java.util.TimeZone + + +/** + * DateTimeCalculator is a collection of static methods that operate + * as the Microsoft Excel function calls do. + * + * + * All methods are called with an array of ptg's, which are then + * acted upon. A Ptg of the type that makes sense (ie boolean, number) + * is returned after each method. + */ + +object DateTimeCalculator { + /** + * utliity that takes either a PtgStr or a Number and converts it into a calendar + * for use in below functions + * + * @return + */ + private fun getDateFromPtg(op: Ptg): GregorianCalendar { + var o: Any + if (op is PtgStr) + o = calcDateValue(arrayOf(op)).value + else if (op is PtgRef) { + o = op.value + if (o is String) + o = calcDateValue(arrayOf(PtgStr(o.toString()))).value + } else if (op is PtgName) { + o = op.value //getComponents()[0]; + o = op.value + if (o is String) + o = calcDateValue(arrayOf(PtgStr(o.toString()))).value + } else + o = op.value + + return DateConverter.getCalendarFromNumber(o) as GregorianCalendar + } + + /** + * DATE + * + * @param operands + * @return + */ + internal fun calcDate(operands: Array): Ptg { + val alloperands = PtgCalculator.getLongValueArray(operands) + if (alloperands.size != 3) return PtgCalculator.error + val year = alloperands[0].toInt() + var month = alloperands[1].toInt() + month = month - 1 + val day = alloperands[2].toInt() + val c = GregorianCalendar(year, month, day) + val date = DateConverter.getXLSDateVal(c) + val i = date.toInt() + return PtgInt(i) + } + + /** + * DATEVALUE + * Returns the serial number of the date represented by date_text. Use DATEVALUE to convert a date represented by text to a serial number. + * + * + * Syntax + * DATEVALUE(date_text) + * + * + * Date_text is text that represents a date in a Microsoft Excel date format. For example, "1/30/2008" or "30-Jan-2008" are text strings + * within quotation marks that represent dates. Using the default date system in Excel for Windows, + * date_text must represent a date from January 1, 1900, to December 31, 9999. Using the default date system in Excel for the Macintosh, + * date_text must represent a date from January 1, 1904, to December 31, 9999. DATEVALUE returns the #VALUE! error value if date_text is out of this range. + * + * + * If the year portion of date_text is omitted, DATEVALUE uses the current year from your computer's built-in clock. Time information in date_text is ignored. + * + * + * Remarks + * + * + * Excel stores dates as sequential serial numbers so they can be used in calculations. By default, January 1, 1900 is serial number 1, and January 1, 2008 is serial number 39448 because it is 39,448 days after January 1, 1900. Excel for the Macintosh uses a different date system as its default. + * Most functions automatically convert date values to serial numbers. + * + * @param operands Ptg[] + * @return Ptg + */ + + internal fun calcDateValue(operands: Array?): Ptg { + // TODO: there may be formats that need to be input + if (operands == null || operands[0].string == null) + return PtgErr(PtgErr.ERROR_VALUE) + + val dateString = operands[0].string + val d = DateConverter.calcDateValue(dateString) ?: return PtgErr(PtgErr.ERROR_VALUE) + return PtgNumber(d) + + } + + /** + * DAY + * Return the day of the month + */ + internal fun calcDay(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.error + try { + val o = operands[0].value + val c = DateConverter.getCalendarFromNumber(o) as GregorianCalendar + val retdate = c.get(Calendar.DAY_OF_MONTH) + return PtgInt(retdate) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * DAYS360 + * Calculate the difference between 2 dates based on a 360 + * day year, 12 mos, 30days each. + * + * + * first date is lower than second, otherwise a negative + * number is returned + * + * + * Seems pretty dumb to me, but what do I know? + */ + internal fun calcDays360(operands: Array): Ptg { + if (operands.size < 2) return PtgErr(PtgErr.ERROR_VALUE) + try { + val o1 = operands[0].value + val o2 = operands[1].value + val dt1 = DateConverter.getCalendarFromNumber(o1) as GregorianCalendar + val dt2 = DateConverter.getCalendarFromNumber(o2) as GregorianCalendar + val yr1 = dt1.get(Calendar.YEAR) + val yr2 = dt2.get(Calendar.YEAR) + var diff = yr2 - yr1 + diff *= 360 // turn years to days. + var mo1 = dt1.get(Calendar.MONTH) + val mo2 = dt2.get(Calendar.MONTH) + var mos = 0 + if (mo2 > mo1) { + mos = mo2 - mo1 + } else { + diff -= 360 + while (mo2 != mo1) { + mos++ + mo1++ + if (mo1 == 12) { + mo1 = 0 + } + } + } + diff += mos * 30 + var dy1 = dt1.get(Calendar.DAY_OF_MONTH) + val dy2 = dt2.get(Calendar.DAY_OF_MONTH) + if (dy2 > dy1) { + diff += dy2 - dy1 + } else { + diff -= 30 + while (dy2 != dy1) { + diff++ + dy1++ + if (dy1 == 30) { + dy1 = 0 + } + } + } + return PtgInt(diff) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + + /** + * EDATE + * Returns the serial number that represents the date that is the indicated number of months + * before or after a specified date (the start_date). + * Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. + * + * + * EDATE(start_date,months) + * + * + * Start_date is a date that represents the start date. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + * + * + * Months is the number of months before or after start_date. A positive value for months yields a future date; a negative value yields a past date. + * + * + * If start_date is not a valid date, EDATE returns the #VALUE! error value. + * If months is not an integer, it is truncated. + */ + internal fun calcEdate(operands: Array): Ptg { + try { + val startDate = getDateFromPtg(operands[0]) + val inc = PtgCalculator.getLongValue(operands[1]).toInt() + var mm = startDate.get(Calendar.MONTH) + inc + var y = startDate.get(Calendar.YEAR) + val d = startDate.get(Calendar.DAY_OF_MONTH) + if (mm < 0) { + mm += 12 // 0-based + y-- + } else if (mm > 11) { + mm -= 12 + y++ + } + val resultDate: GregorianCalendar + resultDate = GregorianCalendar(y, mm, d) + val retdate = DateConverter.getXLSDateVal(resultDate) + val i = retdate.toInt() + return PtgInt(i) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + + /** + * EOMONTH + * Returns the serial number for the last day of the month that is the indicated number of months + * before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on + * the last day of the month. + * + * + * EOMONTH(start_date,months) + * + * + * Start_date is a date that represents the starting date. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + * + * + * Months is the number of months before or after start_date. A positive value for months yields a future date; a negative value yields a past date. + * + * + * If months is not an integer, it is truncated. + * + * + * If start_date is not a valid date, EOMONTH returns the #NUM! error value. + * If start_date plus months yields an invalid date, EOMONTH returns the #NUM! error value. + */ + internal fun calcEOMonth(operands: Array): Ptg { + try { + val startDate = getDateFromPtg(operands[0]) + val inc = operands[1].intVal + var mm = startDate.get(Calendar.MONTH) + inc + var y = startDate.get(Calendar.YEAR) + var d = startDate.get(Calendar.DAY_OF_MONTH) + if (mm < 0) { + mm += 12 // 0-based + y-- + } else if (mm > 11) { + mm -= 12 + y++ + } + if (mm == 3 || mm == 5 || mm == 8 || mm == 10) + // 0-based + d = 30 + else if (mm == 1) {// february + if (y % 4 == 0) + d = 29 + else + d = 28 + } else + d = 31 + val resultDate: GregorianCalendar + resultDate = GregorianCalendar(y, mm, d) + val retdate = DateConverter.getXLSDateVal(resultDate) + val i = retdate.toInt() + return PtgInt(i) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_NUM) + } + + + /** + * HOUR + * Converts a serial number to an hour + */ + internal fun calcHour(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.error + val dt = getDateFromPtg(operands[0]) + val retdate = dt.get(Calendar.HOUR) + return PtgInt(retdate) + + } + + /** + * MINUTE + * Converts a serial number to a minute + */ + internal fun calcMinute(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.error + try { + val o = operands[0].value + val dt = DateConverter.getCalendarFromNumber(o) as GregorianCalendar + val retdate = dt.get(Calendar.MINUTE) + return PtgInt(retdate) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * MONTH + * Converts a serial number to a month + */ + internal fun calcMonth(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.error + try { + val dt = getDateFromPtg(operands[0]) + var retdate = dt.get(Calendar.MONTH) + retdate++ //month is ordinal + return PtgInt(retdate) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_NA) + } + + + /** + * NETWORKDAYS + * Returns the number of whole working days between start_date and end_date. Working days exclude weekends and any dates identified in holidays. + * Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days worked during a specific term. + * + * + * NETWORKDAYS(start_date,end_date,holidays) + * + * + * Start_date is a date that represents the start date. + * End_date is a date that represents the end date. + * Holidays is an optional range of one or more dates to exclude from the working calendar, such as state and federal holidays and floating holidays. The list can be either a range of cells that contains the dates or an array constant of the serial numbers that represent the dates. + * + * + * Remarks + * If any argument is not a valid date, NETWORKDAYS returns the #VALUE! error value. + */ + internal fun calcNetWorkdays(operands: Array): Ptg { + try { + val holidays = ArrayList() + val startDate = getDateFromPtg(operands[0]) + val endDate = getDateFromPtg(operands[1]) + if (operands.size > 2 && operands[2] != null) { + if (operands[2] is PtgRef) { + val dts = operands[2].components + for (i in dts.indices) { + holidays.add(getDateFromPtg(dts[i])) + } + } else + // assume it's a string or a number rep of a date + holidays.add(getDateFromPtg(operands[2])) + } + var count = 0 + val countUp = endDate.after(startDate) + while (startDate != endDate) { + val d = startDate.get(Calendar.DAY_OF_WEEK) + if (d != Calendar.SATURDAY && d != Calendar.SUNDAY) { + var OKtoIncrement = true + // check if on a holidays + if (holidays.size > 0) { + for (i in holidays.indices) { + if (startDate == holidays.get(i)) { + OKtoIncrement = false + break + } + } + } + if (OKtoIncrement) { + count++ + } + } + if (countUp) + startDate.add(Calendar.DAY_OF_MONTH, 1) + else + startDate.add(Calendar.DAY_OF_MONTH, -1) + } + return PtgInt(count) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * NOW + * Returns the serial number of the current date and time + */ + internal fun calcNow(operands: Array): Ptg { + val gc = GregorianCalendar(TimeZone.getDefault())//java.sql.Date dt = new java.sql.Date(); + // io.starter.toolkit.Logger.log(dt.toGMTString()); + val retdate = DateConverter.getXLSDateVal(gc) + return PtgNumber(retdate) + } + + /** + * SECOND + * Converts a serial number to a second + */ + internal fun calcSecond(operands: Array): Ptg { + val dt = getDateFromPtg(operands[0]) + val retdate = dt.get(Calendar.SECOND) + return PtgInt(retdate) + + } + + /** + * TIME + * Returns the serial number of a particular time + * takes 3 arguments, hour, minute, second; + */ + internal fun calcTime(operands: Array): Ptg { + val o: Ptg + if (operands[0] is PtgStr) + o = calcDateValue(arrayOf(operands[0])) + else + o = operands[0] + val hour = o.intVal + val minute = operands[1].intVal + val second = operands[1].intVal + val g = GregorianCalendar(2000, 1, 1, hour, minute, second) + val g2 = GregorianCalendar(2000, 1, 1, 0, 0, 0) + var dub = DateConverter.getXLSDateVal(g) + val dub2 = DateConverter.getXLSDateVal(g2) + dub -= dub2 + return PtgNumber(dub) + } + + /** + * TIMEVALUE + * Converts a time in the form of text to a serial number + * Returns the decimal number of the time represented by a text string. + * The decimal number is a value ranging from 0 (zero) to 0.99999999, representing the times from 0:00:00 (12:00:00 AM) to 23:59:59 (11:59:59 P.M.). + * TIMEVALUE(time_text) + * Time_text is a text string that represents a time in any one of the Microsoft Excel time formats; for example, "6:45 PM" and "18:45" text strings within quotation marks that represent time. + */ + internal fun calcTimevalue(operands: Array): Ptg { + var result = 0.0 + try { + val d = getDateFromPtg(operands[0]) + val h = d.get(Calendar.HOUR_OF_DAY) + val m = d.get(Calendar.MINUTE) + val s = d.get(Calendar.SECOND) + val t = h.toDouble() + m / 60.0 + s / (60 * 60.0) + result = t / 24.0 + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + return PtgNumber(result) + } + + + /** + * TODAY + * Returns the serial number of today's date + */ + internal fun calcToday(operands: Array): Ptg { + val dt = java.sql.Date(System.currentTimeMillis()) + val retdate = DateConverter.getXLSDateVal(dt) + val i = retdate.toInt() + return PtgInt(i) + } + + /** + * WEEKDAY Converts a serial number to a day of the week + */ + internal fun calcWeekday(operands: Array): Ptg { + if (operands.size != 1) return PtgErr(PtgErr.ERROR_VALUE) + val dt = getDateFromPtg(operands[0]) + val retdate = dt.get(Calendar.DAY_OF_WEEK) + return PtgInt(retdate) + } + + /** + * WEEKNUM + * Returns a number that indicates where the week falls numerically within a year. + * + * + * WEEKNUM(serial_num,return_type) + * + * + * Serial_num is a date within the week. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + * + * + * Return_type is a number that determines on which day the week begins. The default is 1. + * + * @param operands + * @return + */ + internal fun calcWeeknum(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + var returnType = 1 + val dt = getDateFromPtg(operands[0]) + if (operands[1] != null) { + returnType = operands[1].intVal + } + returnType -= 1 // 1 is default, 2 =start on monday + val retdate = dt.get(Calendar.WEEK_OF_YEAR) - returnType + return PtgInt(retdate) + } + + /** + * WORKDAY + * Returns a number that represents a date that is the indicated number of working days before or after a date (the starting date). + * Working days exclude weekends and any dates identified as holidays. Use WORKDAY to exclude weekends or holidays when you calculate + * invoice due dates, expected delivery times, or the number of days of work performed. + * + * + * WORKDAY(start_date,days,holidays) + * + * + * Important Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + * Start_date is a date that represents the start date. + * Days is the number of nonweekend and nonholiday days before or after start_date. A positive value for days yields a future date; a negative value yields a past date. + * Holidays is an optional list of one or more dates to exclude from the working calendar, such as state and federal holidays and floating holidays. The list can be either a range of cells that contain the dates or an array constant of the serial numbers that represent the dates. + * + * + * Remarks + * + * + * If any argument is not a valid date, WORKDAY returns the #VALUE! error value. + * If start_date plus days yields an invalid date, WORKDAY returns the #NUM! error value. + * If days is not an integer, it is truncated. + * + * @param operands + * @return + */ + internal fun calcWorkday(operands: Array): Ptg { + val days: Int + val holidays = ArrayList() + try { + val dt = getDateFromPtg(operands[0]) + days = operands[1].intVal + if (operands.size > 2 && operands[2] != null) { // holidays + if (operands[2] is PtgRef) { + val dts = operands[2].components + for (i in dts.indices) { + holidays.add(getDateFromPtg(dts[i])) + } + } else + // assume it's a string or a number rep of a date + holidays.add(getDateFromPtg(operands[2])) + } + var absDays = Math.abs(days) + while (absDays > 0) { + val d = dt.get(Calendar.DAY_OF_WEEK) + if (d != Calendar.SATURDAY && d != Calendar.SUNDAY) { + var OKtoIncrement = true + // check if on a holidays + if (holidays.size > 0) { + for (i in holidays.indices) { + if (dt == holidays.get(i)) { + OKtoIncrement = false + break + } + } + } + if (OKtoIncrement) { + absDays-- + } + } + if (days > 0) + dt.add(Calendar.DAY_OF_MONTH, 1) + else + dt.add(Calendar.DAY_OF_MONTH, -1) + } + return PtgNumber(DateConverter.getXLSDateVal(dt)) + + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * YEAR + * Converts a serial number to a year + */ + internal fun calcYear(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.error + try { + val dt = getDateFromPtg(operands[0]) + val retdate = dt.get(Calendar.YEAR) + return PtgInt(retdate) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_NA) + } + + /** + * YEARFRAC function + * Calculates the fraction of the year represented by the number of whole days between two dates (the start_date and the end_date). Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or obligations to assign to a specific term. + * YEARFRAC(start_date,end_date,basis) + * Important Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text. + * Start_date is a date that represents the start date. + * End_date is a date that represents the end date. + * Basis is the type of day count basis to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + */ + internal fun calcYearFrac(operands: Array): Ptg { + val startDate: Long + val endDate: Long + try { + var d = getDateFromPtg(operands[0]) + startDate = DateConverter.getXLSDateVal(d).toLong() + d = getDateFromPtg(operands[1]) + endDate = DateConverter.getXLSDateVal(d).toLong() + } catch (e: Exception) { + //If start_date or end_date are not valid dates, YEARFRAC returns the #VALUE! error value. + return PtgErr(PtgErr.ERROR_VALUE) + } + + var basis = 0 + if (operands.size > 2) + basis = operands[2].intVal + //If basis < 0 or if basis > 4, YEARFRAC returns the #NUM! error value. + if (basis < 0 || basis > 4) + return PtgErr(PtgErr.ERROR_NUM) + var yf = FinancialCalculator.yearFrac(basis, startDate, endDate) + if (yf < 0) yf *= -1.0 // =# days between dates, no negatives + return PtgNumber(yf) + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.java deleted file mode 100644 index c51a252..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.java +++ /dev/null @@ -1,2363 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; - - -/* - EngineeringCalculator is a collection of static methods that operate - as the Microsoft Excel function calls do. - - All methods are called with an array of ptg's, which are then - acted upon. A Ptg of the type that makes sense (ie boolean, number) - is returned after each method. -*/ -public class EngineeringCalculator { - public static boolean DEBUG = false; - -/* - BESSELI - Returns the modified Bessel function In(x) - - BESSELJ - Returns the Bessel function Jn(x) - - BESSELK - Returns the modified Bessel function Kn(x) - - BESSELY - Returns the Bessel function Yn(x) -*/ - - /** - * BIN2DEC - * Converts a binary number to decimal - */ - protected static Ptg calcBin2Dec(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "BIN2DEC"); - String bString = operands[0].getString().trim(); - // 10 bits at most - if (bString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - // must det. manually if binary string is negative because parseInt does not - // handle two's complement input!!! - boolean bIsNegative = (bString.length() == 10 && bString.substring(0, 1).equalsIgnoreCase("1")); - - int dec = 0; - try { - dec = Integer.parseInt(bString, 2); - if (bIsNegative) dec -= 1024; // 2^10 (= signed 11 bits) - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - PtgNumber pnum = new PtgNumber(dec); - if (DEBUG) Logger.logInfo("Result from BIN2DEC= " + pnum.getVal()); - return pnum; - } - - /** - * BIN2HEX - * Converts a binary number to hexadecimal - */ - protected static Ptg calcBin2Hex(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "BIN2HEX"); - String bString = operands[0].getString().trim(); - int places = 0; - if (operands.length > 1) - places = operands[1].getIntVal(); - - // 10 bits at most - if (bString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - // must det. manually if binary string is negative because parseInt does not - // handle two's complement input!!! - boolean bIsNegative = (bString.length() == 10 && bString.substring(0, 1).equalsIgnoreCase("1")); - - long dec; - String hString; - try { - dec = Long.parseLong(bString, 2); - if (bIsNegative) dec -= 1024; // 2^10 (= signed 11 bits) - hString = Long.toHexString(dec).toUpperCase(); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - hString = hString.substring(Math.max(hString.length() - 10, 0)); - } else if (places > 0) { - if (hString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - hString = ("0000000000" + hString); // maximum= 10 bits - hString = hString.substring(hString.length() - places); - } - - PtgStr pstr = new PtgStr(hString); - if (DEBUG) Logger.logInfo("Result from BIN2HEX= " + pstr.getString()); - return pstr; - } - - /** - * BIN2OCT - * Converts a binary number to octal - */ - protected static Ptg calcBin2Oct(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "Bin2Oct"); - String bString = operands[0].getString().trim(); - int places = 0; - if (operands.length > 1) - places = operands[1].getIntVal(); - - // 10 bits at most - if (bString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - // must det. manually if binary string is negative because parseInt does not - // handle two's complement input!!! - boolean bIsNegative = (bString.length() == 10 && bString.substring(0, 1).equalsIgnoreCase("1")); - - int dec; - String oString; - try { - dec = Integer.parseInt(bString, 2); - if (bIsNegative) dec -= 1024; // 2^10 (= signed 11 bits) - oString = Integer.toOctalString(dec); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - oString = oString.substring(Math.max(oString.length() - 10, 0)); - } else if (places > 0) { - if (oString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - oString = ("0000000000" + oString); // maximum= 10 bits - oString = oString.substring(oString.length() - places); - } - PtgStr pstr = new PtgStr(oString); - if (DEBUG) Logger.logInfo("Result from BIN2OCT= " + pstr.getString()); - return pstr; - } - - /** - * COMPLEX - * Converts real and imaginary coefficients into a complex number - */ - protected static Ptg calcComplex(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "Complex"); - int real = operands[0].getIntVal(); - int imaginary = operands[1].getIntVal(); - String suffix = "i"; - if (operands.length > 2) { - suffix = operands[2].getString().trim(); - if (!(suffix.equals("i") || suffix.equals("j"))) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - String complexString = ""; - - // result: real + imaginary suffix - // real - imaginary suffix - // real (if imaginary==0) - // real + suffix (if imaginary==1) - // imaginary suffix (if (real==0) - if (real != 0) { - complexString = String.valueOf(real); - if (imaginary > 0) - complexString += " + "; - } - - if (imaginary != 0) - complexString += (Math.abs(imaginary) != 1 ? String.valueOf(imaginary) : "") + suffix; - - if (complexString.equals("")) complexString = "0"; - - PtgStr pstr = new PtgStr(complexString); - if (DEBUG) Logger.logInfo("Result from COMPLEX= " + pstr.getString()); - return pstr; - } - - /** - * CONVERT - * Converts a number from one measurement system to another - * Weight and mass From_unit or to_unit - * Gram "g" - * Slug "sg" - * Pound mass (avoirdupois) "lbm" - * U (atomic mass unit) "u" - * Ounce mass (avoirdupois) "ozm" - *

                      - * Distance From_unit or to_unit - * Meter "m" - * Statute mile "mi" - * Nautical mile "Nmi" - * Inch "in" - * Foot "ft" - * Yard "yd" - * Angstrom "ang" - * Pica (1/72 in.) "Pica" - *

                      - * Time From_unit or to_unit - * Year "yr" - * Day "day" - * Hour "hr" - * Minute "mn" - * Second "sec" - *

                      - * Pressure From_unit or to_unit - * Pascal "Pa" - * Atmosphere "atm" - * mm of Mercury "mmHg" - *

                      - * Force From_unit or to_unit - * Newton "N" - * Dyne "dyn" - * Pound force "lbf" - *

                      - * Energy From_unit or to_unit - * Joule "J" - * Erg "e" - * Thermodynamic calorie "c" - * IT calorie "cal" - * Electron volt "eV" - * Horsepower-hour "HPh" - * Watt-hour "Wh" - * Foot-pound "flb" - * BTU "BTU" - *

                      - * Power From_unit or to_unit - * Horsepower "HP" - * Watt "W" - *

                      - * Magnetism From_unit or to_unit - * Tesla "T" - * Gauss "ga" - *

                      - * Temperature From_unit or to_unit - * Degree Celsius "C" - * Degree Fahrenheit "F" - * Degree Kelvin "K" - *

                      - * Liquid measure From_unit or to_unit - * Teaspoon "tsp" - * Tablespoon "tbs" - * Fluid ounce "oz" - * Cup "cup" - * U.S. pint "pt" - * U.K. pint "uk_pt" - * Quart "qt" - * Gallon "gal" - * Liter "l" - *

                      - *

                      - * The following abbreviated unit prefixes can be prepended to any metric - * from_unit or to_unit. - *

                      - * Prefix Multiplier Abbreviation - * exa 1E+18 "E" - * peta 1E+15 "P" - * tera 1E+12 "T" - * giga 1E+09 "G" - * mega 1E+06 "M" - * kilo 1E+03 "k" - * hecto 1E+02 "h" - * dekao 1E+01 "e" - * deci 1E-01 "d" - * centi 1E-02 "c" - * milli 1E-03 "m" - * micro 1E-06 "u" - * nano 1E-09 "n" - * pico 1E-12 "p" - * femto 1E-15 "f" - * atto 1E-18 "a" - *

                      - *

                      - * Remarks - *

                      - * If the input data types are incorrect, CONVERT returns the #VALUE! error value. - * If the unit does not exist, CONVERT returns the #N/A error value. - * If the unit does not support an abbreviated unit prefix, CONVERT returns the #N/A error value. - * If the units are in different groups, CONVERT returns the #N/A error value. - * Unit names and prefixes are case-sensitive. - */ - private static int findUnits(String u, String[] units) { - boolean bFound = false; - for (int i = 0; i < units.length; i++) { - if (u.equals(units[i])) return i; - } - return -1; - } - - private static double prefixMultiplier(String p, String[] prefixes) { - double multiplier = 1.0; - if (p.equals("")) return multiplier; - for (int i = 0; i < prefixes.length; i++) { - if (p.equals(prefixes[i])) { - switch (i) { - case 0: // "E" exa - multiplier = 1E+18; - break; - case 1: // "P" peta - multiplier = 1E+15; - break; - case 2: // "T" tera - multiplier = 1E+12; - break; - case 3: // "G" giga - multiplier = 1E+09; - break; - case 4: // "M" mega - multiplier = 1E+06; - break; - case 5: // "k" kilo - multiplier = 1E+03; - break; - case 6: // "h" hecto - multiplier = 1E+02; - break; - case 7: // "e" dekao - multiplier = 1E+01; - break; - case 8: // "d" deci - multiplier = 1E-01; - break; - case 9: // "c" centi - multiplier = 1E-02; - break; - case 10: // "m" milli - multiplier = 1E-03; - break; - case 11: // "u" micro - multiplier = 1E-06; - break; - case 12: // "n" nano - multiplier = 1E-09; - break; - case 13: // "p" pico - multiplier = 1E-12; - break; - case 14: // "f" femto - multiplier = 1E-15; - break; - case 15: // "a" atto - multiplier = 1E-18; - break; - } - return multiplier; - } - } - - return multiplier; - } - - protected static Ptg calcConvert(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "CONVERT"); - double number = operands[0].getDoubleVal(); - String fromUnits = operands[1].getString().trim(); - String toUnits = operands[2].getString().trim(); - - String[] allUnits = {"g", "sg", "lbm", "u", "ozm", "m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica", - "yr", "day", "hr", "mn", "sec", "Pa", "atm", "mmHg", "N", "dyn", "lbf", - "J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU", "HP", "W", "T", "ga", - "C", "F", "K", "tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l"}; - String[] weightUnits = {"g", "sg", "lbm", "u", "ozm"}; - String[] distanceUnits = {"m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica"}; - String[] timeUnits = {"yr", "day", "hr", "mn", "sec"}; - String[] pressureUnits = {"Pa", "atm", "mmHg"}; - String[] forceUnits = {"N", "dyn", "lbf"}; - String[] energyUnits = {"J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU"}; - String[] powerUnits = {"HP", "W"}; - String[] magnetismUnits = {"T", "ga"}; - String[] temperatureUnits = {"C", "F", "K"}; - String[] liquidMeasureUnits = {"tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l"}; - - // for any metric unit, may be prefixed with - String fromPrefix = "", toPrefix = ""; - String[] metricPrefixes = {"E", "P", "T", "G", "M", "k", "h", "e", "d", "c", "m", "u", "n", "p", "f", "a"}; - - // first, see if fromUnits and toUnits are in list of acceptable units - if (findUnits(fromUnits, allUnits) < 0) { // doesn't match; strip prefix and try again - if (fromUnits.length() > 1) { - fromPrefix = fromUnits.substring(0, 1); - fromUnits = fromUnits.substring(1); - // now recheck - if (findUnits(fromUnits, allUnits) < 0) - return new PtgErr(PtgErr.ERROR_NA); - // make sure that prefix is acceptable - if (findUnits(fromPrefix, metricPrefixes) < 0) - return new PtgErr(PtgErr.ERROR_NA); - } else return new PtgErr(PtgErr.ERROR_NA); - } - if (findUnits(toUnits, allUnits) < 0) {// doesn't match; strip prefix and try again - if (toUnits.length() > 1) { - toPrefix = toUnits.substring(0, 1); - toUnits = toUnits.substring(1); - // now recheck - if (findUnits(toUnits, allUnits) < 0) - return new PtgErr(PtgErr.ERROR_NA); - // make sure that prefix is acceptable - if (findUnits(toPrefix, metricPrefixes) < 0) - return new PtgErr(PtgErr.ERROR_NA); - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // at here, we know that the prefixes and units are found, but we don't know if they match or make sense ... - double result = 0; - double from = 0; - int i, j = -1; - - // WEIGHT conversion - if ((i = findUnits(fromUnits, weightUnits)) >= 0) { - j = findUnits(toUnits, weightUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - // { "g", "sg", "lbm", "u", "ozm" } - // get fromUnit in grams - switch (i) { - // from: - case 0: // "g" - from = number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "sg" - from = 14593.84241892870000000000 * number; - break; - case 2: // "lbm" - from = 453.5923097488115 * number; - break; - case 3: // "u" - from = 1.660531004604650E-24 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 4: // "ozm" - from = 28.349515207973 * number; - break; - } - // now convert - switch (j) { - case 0: // "g" - result = from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "sg" - result = from * 0.00006852205000534780; - break; - case 2: // "lbm" - result = from * 0.00220462291469134000; - break; - case 3: // "u" - result = from * 6.02217000000000000000E+23 / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 4: // "ozm" - result = from * 0.03527397180036270000; - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // DISTANCE conversion - if (j == -1 && (i = findUnits(fromUnits, distanceUnits)) >= 0) { - j = findUnits(toUnits, distanceUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - // "m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica" ; - // get fromUnits in m - switch (i) { - case 0: //"m" - from = number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: //"mi" - from = 1609.344000000000 * number; - break; - case 2: //"Nmi" - from = 1852.000000000000 * number; - break; - case 3: //"in" - from = 0.025400000000 * number; - break; - case 4: // "ft" - from = 0.304800000000 * number; - break; - case 5: // "yd" - from = 0.914400000300 * number; - break; - case 6: // "ang" - from = 0.000000000100 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 7: // "Pica" - from = 0.00035277777777780000 * number; - break; - } - switch (j) { - case 0: //"m" - result = from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: //"mi" - result = 0.00062137119223733400 * from; - break; - case 2: //"Nmi" - result = 0.00053995680345572400 * from; - break; - case 3: //"in" - result = 39.37007874015750000000 * from; - break; - case 4: // "ft" - result = 3.28083989501312000000 * from; - break; - case 5: // "yd" - result = 1.09361329797891000000 * from; - break; - case 6: // "ang" - result = 10000000000.000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 7: // "Pica" - result = 2834.64566929116000000000 * from; - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // TIME conversion - if (j == -1 && (i = findUnits(fromUnits, timeUnits)) >= 0) { - j = findUnits(toUnits, timeUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - //"yr", "day", "hr", "mn", "sec" - switch (i) { - case 0: // "yr" - from = number; - break; - case 1: // "day" - from = 0.00273785078713210000 * number; - break; - case 2: // "hr" - from = 0.00011407711613050400 * number; - break; - case 3: // "mn" - from = 0.00000190128526884174 * number; - break; - case 4: // "sec" - from = 0.00000003168808781403 * number; - break; - } - switch (j) { - case 0: // "yr" - result = from; - break; - case 1: // "day" - result = 365.250000000000 * from; - break; - case 2: // "hr" - result = 8766.000000000000 * from; - break; - case 3: // "mn" - result = 525960.000000000000 * from; - break; - case 4: // "sec" - result = 31557600.000000000000 * from; - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // PRESSURE conversion - if (j == -1 && (i = findUnits(fromUnits, pressureUnits)) >= 0) { - j = findUnits(toUnits, pressureUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - //"Pa", "atm", "mmHg" - switch (i) { - case 0: // "Pa" - from = number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "atm" - from = 101324.99658300000000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 2: // "mmHg" - from = 133.32236392500000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - } - switch (j) { - case 0: // "Pa" - result = from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "atm" - result = 0.00000986923299998193 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 2: // "mmHg" - result = 0.00750061707998627000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // FORCE conversion - if (j == -1 && (i = findUnits(fromUnits, forceUnits)) >= 0) { - j = findUnits(toUnits, forceUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - //"N", "dyn", "lbf" - switch (i) { - case 0: // "N" - from = number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "dyn" - from = 0.000010000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 2: // "lbf" - from = 4.448222000000 * number; - break; - } - switch (j) { - case 0: // "N" - result = from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "dyn" - result = 100000.000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 2: // "lbf" - result = 0.22480892365533900000 * from; - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // ENERGY conversion - if (j == -1 && (i = findUnits(fromUnits, energyUnits)) >= 0) { - j = findUnits(toUnits, energyUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - //"J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU" - switch (i) { - case 0: // "J" - from = number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "e" - from = 0.00000010000004806570 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 2: // "c" - from = 4.18399101363672000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 3: // "cal" - from = 4.18679484613929000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 4: // "eV" - from = 1.60217646E-19 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 5: // "HPh" - from = 2684517.41316170000000000000 * number; - break; - case 6: // "Wh" - from = 3599.99820554720000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 7: // "flb" - from = 0.04214000032364240000 * number; - break; - case 8: // "BTU" - from = 1055.05813786749000000000 * number; - break; - } - switch (j) { - case 0: // "J" - result = from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "e" - result = 9999995.19343231000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 2: // "c" - result = 0.23900624947346700000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 3: // "cal" - result = 0.23884619064201700000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 4: // "eV" - result = 6241457000000000000.00000000000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 5: // "HPh" - result = 0.00000037250643080100 * from; - break; - case 6: // "Wh" - result = 0.00027777791623871100 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 7: // "flb" - result = 23.73042221926510000000 * from; - break; - case 8: // "BTU" - result = 0.00094781506734901500 * from; - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // POWER conversion - if (j == -1 && (i = findUnits(fromUnits, powerUnits)) >= 0) { - j = findUnits(toUnits, powerUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - //"HP", "W" - switch (i) { - case 0: // "HP" - from = number; - break; - case 1: // "W" - from = 0.00134102006031908000 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - } - switch (j) { - case 0: // "HP" - result = from; - break; - case 1: // "W" - result = 745.70100000000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // MAGNETISM conversion - if (j == -1 && (i = findUnits(fromUnits, magnetismUnits)) >= 0) { - j = findUnits(toUnits, magnetismUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - //"T", "ga" - switch (i) { - case 0: // "T" - from = number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - case 1: // "ga" - from = 0.000100000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - } - switch (j) { - case 0: // "T" - result = from / prefixMultiplier(toPrefix, metricPrefixes); - break; - case 1: // "ga" - result = 10000.000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // TEMPERATURE conversion - if (j == -1 && (i = findUnits(fromUnits, temperatureUnits)) >= 0) { - j = findUnits(toUnits, temperatureUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - //"C", "F", "K" - switch (i) { - case 0: // "C" - from = number; - break; - case 1: // "F" - from = (number - 32) / 1.8; - break; - case 2: // "K" - from = (number * prefixMultiplier(fromPrefix, metricPrefixes)) - 273.15; - break; - } - switch (j) { - case 0: // "C" - result = from; - break; - case 1: // "F" - result = from * 1.8 + 32; - break; - case 2: // "K" - result = (273.15000000000000000000 + from) / prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - // LIQUID MEASURE conversion - if (j == -1 && (i = findUnits(fromUnits, liquidMeasureUnits)) >= 0) { - j = findUnits(toUnits, liquidMeasureUnits); - if (j > -1) { // both fromUnits and toUnits are in same family - //"tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l" - switch (i) { - case 0: // "tsp" - from = number; - break; - case 1: // "tbs" - from = 3.00000000000000000000 * number; - break; - case 2: // "oz" - from = 6.00000000000000000000 * number; - break; - case 3: // "cup" - from = 48.00000000000000000000 * number; - break; - case 4: // "pt" - from = 96.00000000000000000000 * number; - break; - case 5: // "uk_pt" - from = 115.26600000000000000000 * number; - break; - case 6: // "qt" - from = 192.00000000000000000000 * number; - break; - case 7: // "gal" - from = 768.00000000000000000000 * number; - break; - case 8: // "l" - from = 202.84000000000000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes); - break; - } - switch (j) { - case 0: // "tsp" - result = from; - break; - case 1: // "tbs" - result = 0.33333333333333300000 * from; - break; - case 2: // "oz" - result = 0.16666666666666700000 * from; - break; - case 3: // "cup" - result = 0.02083333333333330000 * from; - break; - case 4: // "pt" - result = 0.01041666666666670000 * from; - break; - case 5: // "uk_pt" - result = 0.00867558516821960000 * from; - break; - case 6: // "qt" - result = 0.00520833333333333000 * from; - break; - case 7: // "gal" - result = 0.00130208333333333000 * from; - break; - case 8: // "l" - result = 0.00492999408400710000 * from / prefixMultiplier(toPrefix, metricPrefixes); - break; - } - } else return new PtgErr(PtgErr.ERROR_NA); - } - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from CONVERT= " + pnum.getString()); - return pnum; - } - - /** - * DEC2BIN - * Converts a decimal number to binary - */ - protected static Ptg calcDec2Bin(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "DEC2BIN"); - int dec = operands[0].getIntVal(); - int places = 0; - if (operands.length > 1) - places = operands[1].getIntVal(); - - if (dec < -512 || dec > 511 || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - - String bString = Integer.toBinaryString(dec); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - bString = bString.substring(Math.max(bString.length() - 10, 0)); - } else if (places > 0) { - if (bString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - bString = ("0000000000" + bString); // maximum= 10 bits - bString = bString.substring(bString.length() - places); - } - PtgStr pstr = new PtgStr(bString); - if (DEBUG) Logger.logInfo("Result from DEC2BIN= " + pstr.getString()); - return pstr; - } - - /** - * DEC2HEX - * Converts a decimal number to hexadecimal - */ - protected static Ptg calcDec2Hex(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "DEC2HEX"); - long dec = PtgCalculator.getLongValue(operands[0]); - int places = 0; - if (operands.length > 1) - places = operands[1].getIntVal(); - if (dec < -549755813888L || dec > 549755813887L || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - String hString = Long.toHexString(dec).toUpperCase(); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - hString = hString.substring(Math.max(hString.length() - 10, 0)); - } else if (places > 0) { - if (hString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - hString = ("0000000000" + hString); // maximum= 10 places - hString = hString.substring(hString.length() - places); - - } - PtgStr pstr = new PtgStr(hString); - if (DEBUG) Logger.logInfo("Result from DEC2HEX= " + pstr.getString()); - return pstr; - } - - /** - * DEC2OCT - * Converts a decimal number to octal - */ - protected static Ptg calcDec2Oct(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "DEC2OCT"); - long dec = PtgCalculator.getLongValue(operands[0]); - int places = 0; - if (operands.length > 1) - places = operands[1].getIntVal(); - if (dec < -536870912L || dec > 536870911L || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - String oString = Long.toOctalString(dec); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - oString = oString.substring(Math.max(oString.length() - 10, 0)); - } else if (places > 0) { - if (oString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - oString = ("0000000000" + oString); // maximum= 10 places - oString = oString.substring(oString.length() - places); - - } - PtgStr pstr = new PtgStr(oString); - if (DEBUG) Logger.logInfo("Result from DEC2OCT= " + pstr.getString()); - return pstr; - } - - /** - * DELTA - * Tests whether two values are equal - */ - protected static Ptg calcDelta(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "DELTA"); - - double number1 = operands[0].getDoubleVal(); - double number2 = 0; - if (operands.length > 1) - number2 = operands[1].getDoubleVal(); - int result = 0; - if (number1 == number2) - result = 1; - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from DELTA= " + pnum.getString()); - return pnum; - } - - - /** - * helper erf calc - seems to work *ALRIGHT* for values over 1 - * NOTE: not accurate to 9 digits for every case - * - * @param x - * @return - */ - private static double erf_try1(double x) { - double t = x; - double x2 = Math.pow(x, 2); - for (double n = 1000; n >= 0.5; n -= 0.5) { - t = x + n / t; - } - t = 1.0 / t; - double tt = Math.exp(-x2) / Math.sqrt(Math.PI); - return (1 - tt * t); - } - - /** - * ERF - * Returns the error function integrated between lower_limit and upper_limit. - * ERF(lower_limit,upper_limit) - *

                      - * Lower_limit is the lower bound for integrating ERF. - * Upper_limit is the upper bound for integrating ERF. If omitted, ERF integrates between zero and lower_limit. - *

                      - * With a single argument ERF returns the error function, defined as - * erf(x) = 2/sqrt(pi)* integral from 0 to x of exp(-t*t) dt. - * If two arguments are supplied, they are the lower and upper limits of the integral. - * NOTE: Accuracy is not always to 9 digits - * NOTE: Version with two parameters is NOT supported - */ - protected static Ptg calcErf(Ptg[] operands) throws FunctionNotSupportedException { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - try { - double lower_limit = operands[0].getDoubleVal(); - double upper_limit = Double.NaN; //lower_limit; - if (operands.length == 2) - upper_limit = operands[1].getDoubleVal(); - // If lower_limit is negative, ERF returns the #NUM! error value. - // If upper_limit is negative, ERF returns the #NUM! error value. -// if (lower_limit < 0 /*|| upper_limitupper_limit < 0*/) return new PtgErr(PtgErr.ERROR_NUM); - boolean neg = (lower_limit < 0); - lower_limit = Math.abs(lower_limit); - - - double result; - double limit = lower_limit; - /* - // try this: from "Computation of the error function erf in arbitrary precision with correct rounding" - double r= 0; - double r1= 0; - double estimate= (2/Math.sqrt(Math.PI))*(limit - Math.pow(limit, 3)/3.0); - double convergence= Math.pow(2, estimate-15); - for (int i= 0, n= 0; n < 100; i++, n++) { - double factor= 2.0*n + 1.0; - double z= Math.pow(limit, factor); - double zz= (MathFunctionCalculator.factorial(n)*factor); - double zzz= z/zz; - if ((i % 2)!=0) - r= r-zzz; - else - r= r+zzz; - - if (Math.abs(r)-r1) < - - r1= Math.abs(r1); - } - result= r*(2.0/Math.sqrt(Math.PI));*/ - - if (limit < 0.005) { - /* A&S 7.1.1 - good to at least 6 digts ... but not for larger values ... sigh ...*/ - double r = 0; - for (int i = 0, n = 0; n < 12; i++, n++) { - double factor = 2.0 * n + 1.0; - double z = Math.pow(limit, factor); - double zz = (MathFunctionCalculator.factorial(n) * factor); - double zzz = z / zz; - if ((i % 2) != 0) - r = r - zzz; - else - r = r + zzz; - } - result = r * (2.0 / Math.sqrt(Math.PI)); - } else - result = erf_try1(limit); - - if (neg) - result *= -1; - - if (!Double.isNaN(upper_limit)) { // Erf(upper)-Erf(lower) - Ptg result2 = calcErf(new Ptg[]{operands[1]}); - if (result2 instanceof PtgNumber) - result = result2.getDoubleVal() - result; - else return new PtgErr(PtgErr.ERROR_VALUE); - } - - return new PtgNumber(result); - - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - static final double PI_SQRT = Math.sqrt(Math.PI); - static final double TSQPI = 2. / PI_SQRT; - private static boolean firstCall = true; - - /*** - * Calculate the error function erf. - * @param x the argument - * @return the value erf(x) - */ - public static double erf(final double x) { - //if (type==1) { - return erfAS(x); - //} - //return erfCody(x); - } - - /*** - * Calculate the remaining error function erfc. - * @param x the argument - * @return the value erfc(x) - */ - public static double erfc(final double x) { - //if (type==1) { - return erfcAS(x); - //} - //return erfcCody(x); - } - - /*** - * Internal helper method to calculate the error function at value x. - * This code is based on a Fortran implementation from - * W. J. Cody. - * Refactored by N.Wulff for Java. - * - * Cody vs AS algiorithm - * - * @param x the argument - * @return the approximation of erf(x) - */ - private static double erfCody(final double x) { - double result = 0; - double y = Math.abs(x); - if (firstCall) { - firstCall = false; - } - if (y <= THRESHOLD) { - result = x * calcLower(y); - } else { - result = calcUpper(y); - result = (0.5 - result) + 0.5; - if (x < 0) { - result = -result; - } - } - return result; - } - - /*** - * Internal helper method to calculate the erfc functions. - * This code is based on a Fortran implementation from - * W. J. Cody. - * Refactored by N.Wulff for Java. - * - * @param x the argument - * @return the approximation erfc(x) - */ - private static double erfcCody(final double x) { - double result = 0; - double y = Math.abs(x); - if (firstCall) { - firstCall = false; - } - if (y <= THRESHOLD) { - result = x * calcLower(y); - result = 1 - result; - } else { - result = calcUpper(y); - if (x < 0) { - result = 2.0 - result; - } - } - return result; - } - - /*** - * Internal helper method to calculate the erf/erfc functions. - * This code is based on a Fortran implementation from - * W. J. Cody. - * Refactored by N.Wulff for Java. - * - * @param y the value y=abs(x)<=THRESHOLD - * @return the series expansion - */ - private static double calcLower(final double y) { - double result; - double ySq; - double xNum; - double xDen; - ySq = 0.0; - if (y > X_SMALL) - ySq = y * y; - xNum = ERF_A[4] * ySq; - xDen = ySq; - for (int i = 0; i < 3; i++) { - xNum = (xNum + ERF_A[i]) * ySq; - xDen = (xDen + ERF_B[i]) * ySq; - } - result = (xNum + ERF_A[3]) / (xDen + ERF_B[3]); - return result; - } - - /*** - * Internal helper method to calculate the erf/erfc functions. - * This code is based on a Fortran implementation from - * W. J. Cody. - * Refactored by N.Wulff for Java. - * - * @param y the value y=abs(x)>THRESHOLD - * @return the series expansion - */ - private static double calcUpper(final double y) { - double result; - double ySq; - double xNum; - double xDen; - if (y <= 4.0) { - xNum = ERF_C[8] * y; - xDen = y; - for (int i = 0; i < 7; i++) { - xNum = (xNum + ERF_C[i]) * y; - xDen = (xDen + ERF_D[i]) * y; - } - result = (xNum + ERF_C[7]) / (xDen + ERF_D[7]); - } else { - result = 0.0; - if (y >= X_HUGE) { - result = SQRPI / y; - } else { - ySq = 1.0 / (y * y); - xNum = ERF_P[5] * ySq; - xDen = ySq; - for (int i = 0; i < 4; i++) { - xNum = (xNum + ERF_P[i]) * ySq; - xDen = (xDen + ERF_Q[i]) * ySq; - } - result = ySq * (xNum + ERF_P[4]) / (xDen + ERF_Q[4]); - result = (SQRPI - result) / y; - } - } - ySq = Math.round(y * 16.0) / 16.0; - double del = (y - ySq) * (y + ySq); - result = Math.exp(-ySq * ySq) * Math.exp(-del) * result; - return result; - } - - /*** - * Calculate the error function at value x. - * AS 7.1.5/7.1.26 - * @param x the argument - * @return the value erf(x) - */ - private static double erfAS(final double x) { - if (firstCall) { - firstCall = false; - } - if (x < 0) { - return -erfAS(-x); - } - if (x < 2) { - return erfSeries(x); - } - return erfRational(x); - } - - /*** - * Calculate the remaining erfc error function at value x. - * AS 7.1.5/7.1.26 - * @param x the argument - * @return the value erfc(x) - */ - private static double erfcAS(final double x) { - if (firstCall) { - firstCall = false; - } - return 1 - erfAS(x); - } - - /*** - * Series expansion from A&S 7.1.5. - * - * @param x the argument - * @return erf(x) - */ - private static double erfSeries(final double x) { - final double eps = 1.E-8; // we want only ~1.E-7 - final int kmax = 50; // this can be reached with ~30-40 - double an, ak = x; - double erfo, erf = ak; - int k = 1; - do { - erfo = erf; - ak *= -x * x / k; - an = ak / (2.0 * k + 1.0); - erf += an; - } while (!hasConverged(erf, erfo, eps, ++k, kmax)); - return TSQPI * erf; - } - - /** - * Indicate if an iterative algorithm has RELATIVE converged. - *


                      - * Note: - * HasConverged throws an ArithmeticException if more than max calls - * have been made. Choose hasReacherAccuracy if this is not desired. - *
                      - * - * @param xn the actual argument x[n] - * @param xo the older argument x[n-1] - * @param eps the accuracy to reach - * @param n the actual iteration counter - * @param max the maximal number of iterations - * @return flag indicating if accuracy is reached. - */ - public static boolean hasConverged(final double xn, final double xo, - final double eps, final int n, final int max) { - if (hasReachedAccuracy(xn, xo, eps)) { - return true; - } - if (n >= max) { - throw new ArithmeticException(); - } - return false; - } - - /** - * Indicate if xn and xo have the relative/absolute accuracy epsilon. - * In case that the true value is less than one this is based - * on the absolute difference, otherwise on the relative difference: - *
                      -     *     2*|x[n]-x[n-1]|/|x[n]+x[n-1]| < eps
                      -     * 
                      - * - * @param xn the actual argument x[n] - * @param xo the older argument x[n-1] - * @param eps accuracy to reach - * @return flag indicating if accuracy is reached. - */ - public static boolean hasReachedAccuracy(final double xn, final double xo, - final double eps) { - double z = Math.abs(xn + xo) / 2; - double error = Math.abs(xn - xo); - if (z > 1) { - error /= z; - } - return error <= eps; - } - - /*** - * Rational approximation A&S 7.1.26 with accuracy 1.5E-7. - * - * @param x the argument - * @return erf(x) - */ - private static double erfRational(final double x) { - /* coefficients for A&S 7.1.26. */ - final double[] a = {.254829592, -.284496736, 1.421413741, - -1.453152027, 1.061405429}; - /* constant for A&S 7.1.26 */ - final double p = .3275911; - double erf, r = 0, t = 1.0 / (1 + p * x); - for (int i = 4; i >= 0; i--) { - r = a[i] + r * t; - } - erf = 1 - t * r * Math.exp(-x * x); - return erf; - } - - // =========================================================================== - /*** Nominator coefficients for approximation to erf in first interval. */ - private static final double[] ERF_A = {3.16112374387056560E00, - 1.13864154151050156E02, 3.77485237685302021E02, - 3.20937758913846947E03, 1.85777706184603153E-1}; - /*** Denominator coefficients for approximation to erf in first interval. */ - private static final double[] ERF_B = {2.36012909523441209E01, - 2.44024637934444173E02, 1.28261652607737228E03, - 2.84423683343917062E03}; - // =========================================================================== - /*** Nominator coefficients for approximation to erfc in second interval. */ - private static final double[] ERF_C = {5.64188496988670089E-1, - 8.88314979438837594E0, 6.61191906371416295E01, - 2.98635138197400131E02, 8.81952221241769090E02, - 1.71204761263407058E03, 2.05107837782607147E03, - 1.23033935479799725E03, 2.15311535474403846E-8}; - /*** Denominator coefficients for approximation to erfc in second interval.*/ - private static final double[] ERF_D = {1.57449261107098347E01, - 1.17693950891312499E02, 5.37181101862009858E02, - 1.62138957456669019E03, 3.29079923573345963E03, - 4.36261909014324716E03, 3.43936767414372164E03, - 1.23033935480374942E03}; - // =========================================================================== - /*** Nominator coefficients for approximation to erfc in third interval. */ - private static final double[] ERF_P = {3.05326634961232344E-1, - 3.60344899949804439E-1, 1.25781726111229246E-1, - 1.60837851487422766E-2, 6.58749161529837803E-4, - 1.63153871373020978E-2}; - /*** Denominator coefficients for approximation to erfc in third interval. */ - private static final double[] ERF_Q = {2.56852019228982242, - 1.87295284992346047, 5.27905102951428412E-1, - 6.05183413124413191E-2, 2.33520497626869185E-3}; - // =========================================================================== - static final double THRESHOLD = 0.46875; - static final double SQRPI = 1. / PI_SQRT; - static final double X_INF = Double.MAX_VALUE; - static final double X_MIN = Double.MIN_VALUE; - // private static final double X_NEG = -9.38241396824444; - static final double X_NEG = -Math.sqrt(Math.log(X_INF / 2)); - static final double X_SMALL = getDEPS(); - static final double X_HUGE = 1.0 / (2.0 * Math.sqrt(X_SMALL)); - static final double X_MAX = Math.min(X_INF, (1.0 / (PI_SQRT * X_MIN))); - // static final double X_BIG = 9.194E0; - static final double X_BIG = 26.543; - - private static final float FEPS_START = 2.E-6f; - /** double valued machine precision. */ - /** - * float valued machine precision. - */ - //public static final float FEPS; - static double getDEPS() { - /** - * Calculate the machine accuracy, - * which is the smallest eps with - * 1<1+eps - */ - float feps = FEPS_START; - float fy = 1.0f + feps; - while (fy > 1.0f) { - feps /= 2.0f; - fy = 1.0f + feps; - } - //FEPS = feps; - double deps = feps * FEPS_START; - double dy = 1.0 + deps; - while (dy > 1.0) { - deps /= 2.0; - dy = 1.0 + deps; - } - //DEPS = deps; - //if (DEBUG) - - // Logger.logInfo(format("feps:%8.2E deps:%8.3G", FEPS, DEPS)); - return deps; - } -/* - ERFC - Returns the complementary error function -*/ - - /** - * GESTEP - * Tests whether a number is greater than a threshold value - */ - protected static Ptg calcGEStep(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "GESTEP"); - - double number = operands[0].getDoubleVal(); - double step = 0; - if (operands.length > 1) - step = operands[1].getDoubleVal(); - int result = 0; - if (number >= step) - result = 1; - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from GESTEP= " + pnum.getString()); - return pnum; - } - - /** - * HEX2BIN - * Converts a hexadecimal number to binary - */ - protected static Ptg calcHex2Bin(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "HEX2BIN"); - String hString = operands[0].getString().trim(); - // 10 digits (40 bits) at most - if (hString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - int places = 0; - if (operands.length > 1) - places = operands[1].getIntVal(); - - long dec; - String bString; - try { - dec = Long.parseLong(hString, 16); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 549755813888L) // 2^39 (= signed 40 bits) - dec -= 1099511627776L; // 2^40 (= signed 41 bits) - bString = Long.toBinaryString(dec); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - if (dec < -512 /*0xFFFFFFFE00*/ || dec > 0x1FF || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - bString = bString.substring(Math.max(bString.length() - 10, 0)); - } else if (places > 0) { - if (bString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - bString = ("0000000000" + bString); // maximum= 10 places - bString = bString.substring(bString.length() - places); - } - PtgStr pstr = new PtgStr(bString); - if (DEBUG) Logger.logInfo("Result from HEX2BIN= " + pstr.getString()); - return pstr; - } - - /** - * HEX2DEC - * Converts a hexadecimal number to decimal - */ - protected static Ptg calcHex2Dec(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "HEX2DEC"); - String hString = operands[0].getString().trim(); - // 10 digits (40 bits) at most - if (hString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - long dec; - String oString; - try { - dec = Long.parseLong(hString, 16); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 549755813888L) // 2^39 (= signed 40 bits) - dec -= 1099511627776L; // 2^40 (= signed 41 bits) - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - PtgNumber pnum = new PtgNumber(dec); - if (DEBUG) Logger.logInfo("Result from HEX2DEC= " + pnum.getVal()); - return pnum; - } - - /** - * HEX2OCT - * Converts a hexadecimal number to octal - */ - protected static Ptg calcHex2Oct(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "HEX2OCT"); - String hString = operands[0].getString().trim(); - // 10 digits (40 bits) at most - if (hString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - int places = 0; - if (operands.length > 1) - places = operands[1].getIntVal(); - - long dec; - String oString; - try { - dec = Long.parseLong(hString, 16); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 549755813888L) // 2^39 (= signed 40 bits) - dec -= 1099511627776L; // 2^40 (= signed 41 bits) - oString = Long.toOctalString(dec); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - if (dec < -536870912L /*0xFFE0000000*/ || dec > 0x1FFFFFFF || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - oString = oString.substring(Math.max(oString.length() - 10, 0)); - } else if (places > 0) { - if (oString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - oString = ("0000000000" + oString); // maximum= 10 places - oString = oString.substring(oString.length() - places); - } - PtgStr pstr = new PtgStr(oString); - if (DEBUG) Logger.logInfo("Result from HEX2OCT= " + pstr.getString()); - return pstr; - } - - /** - * imParseComplexNumber - *

                      - * used in the following Imaginary-based formulas to parse a complex number into its - * real and imaginary components. - * Throws a numberformat exception if the complex number is not in the format of: - * real + imaginary - * real - imaginary - * imaginary (real= 0) - * real (imaginary= 0) - * where imaginary coefficient n= ni or nj - * * - */ - private static Complex imParseComplexNumber(String complexNumber) - throws NumberFormatException { - Complex c = new Complex(); - if (complexNumber.length() > 0) { - try { - int i = complexNumber.length(); - if (complexNumber.substring(i - 1, i).equals("i") || - complexNumber.substring(i - 1, i).equals("j")) { - c.suffix = complexNumber.substring(i - 1, i); - i -= 2; - while (i >= 0 && - !(complexNumber.substring(i, i + 1).equals("+") || - complexNumber.substring(i, i + 1).equals("-"))) - i--; - if (i < 0) { // case of "#i" or "#j" i.e. no real and no sign - complexNumber = "+" + complexNumber; - i++; - } - // get imaginary coefficient + sign - String s = complexNumber.substring(i, complexNumber.length() - 1); - if (s.length() == 1) { // only a sign; means that the coefficient==1 eg. real-j or real+i - s += "1"; - } - c.imaginary = Double.parseDouble(s); - } - if (i > 0) - c.real = Double.parseDouble(complexNumber.substring(0, i)); - } catch (Exception e) { - throw new NumberFormatException(); - } - } - return c; - } - - /** - * imGetStr - * - * @param operands - * @return double formatted as an integer if no precision, otherwise rounds to 15 - */ - private static String imGetExcelStr(double d, int precision) { - String s; - if (d == (int) d) { - if ((int) d == 1) return ""; - return String.valueOf((int) d); - } - // round to precision - default= 15 - double r = Math.pow(10, precision); - d *= r; - d = Math.round(d); - d /= r; - return String.valueOf(d); - } - - private static String imGetExcelStr(double d) { - return imGetExcelStr(d, 15); - } - - /** - * IMABS - * Returns the absolute value (modulus) of a complex number - */ - protected static Ptg calcImAbs(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "IMABS"); - String complexString = StringTool.allTrim(operands[0].getString()); - - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - // Absolute of a complex number is: - // square root( real^2 + imaginary^2) - double result = Math.sqrt(Math.pow(c.real, 2) + Math.pow(c.imaginary, 2)); - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from IMABS= " + pnum.getString()); - return pnum; - } - - /** - * IMAGINARY - * Returns the imaginary coefficient of a complex number - */ - protected static Ptg calcImaginary(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "Imaginary"); - String complexString = StringTool.allTrim(operands[0].getString()); - - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - PtgNumber pnum = new PtgNumber(c.imaginary); - if (DEBUG) Logger.logInfo("Result from IMAGINARY= " + pnum.getString()); - return pnum; - } - - /** - * IMARGUMENT - * Returns the argument theta, an angle expressed in radians - */ - protected static Ptg calcImArgument(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImArgument"); - String complexString = StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - double result = Math.atan(c.imaginary / c.real); - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from IMARGUMENT= " + pnum.getString()); - return pnum; - } - - /** - * IMCONJUGATE - * Returns the complex conjugate of a complex number - */ - protected static Ptg calcImConjugate(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImCongugate"); - String complexString = StringTool.allTrim(operands[0].getString()); - - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - String congugate; - if (c.real != 0 && c.imaginary != 0) - congugate = imGetExcelStr(c.real) + (c.imaginary < 0 ? "+" : "-") + imGetExcelStr(Math.abs(c.imaginary)) + c.suffix; - else if (c.real == 0) - congugate = imGetExcelStr(Math.abs(c.imaginary)) + c.suffix; - else - congugate = imGetExcelStr(c.real); - PtgStr pstr = new PtgStr(congugate); - if (DEBUG) Logger.logInfo("Result from IMCONGUGATE= " + pstr.getString()); - return pstr; - } - - /** - * IMCOS - * Returns the cosine of a complex number - */ - protected static Ptg calcImCos(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImCos"); - String complexString = StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - // cos(a + bi)= cos(a)cosh(b) - sin(a)sinh(b)i - double cosh = (Math.pow(Math.E, c.imaginary) + Math.pow(Math.E, c.imaginary * -1)) / 2; - double sinh = (Math.pow(Math.E, c.imaginary) - Math.pow(Math.E, c.imaginary * -1)) / 2; - double a = Math.cos(c.real) * cosh; - double b = Math.sin(c.real) * sinh; - - String imCos; - if (b < 0) - imCos = imGetExcelStr(a) + "+" + imGetExcelStr(Math.abs(b)) + c.suffix; - else - imCos = imGetExcelStr(a) + "-" + imGetExcelStr(b) + c.suffix; - - PtgStr pstr = new PtgStr(imCos); - if (DEBUG) Logger.logInfo("Result from IMCOS= " + pstr.getString()); - return pstr; - } - - /** - * IMDIV - * Returns the quotient of two complex numbers, defined as (deep breath): - *

                      - * (r1 + i1j)/(r2 + i2j) == ( r1r2 + i1i2 + (r2i1 - r1i2)i ) / (r2^2 + i2^2) - */ - protected static Ptg calcImDiv(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "IMDIV"); - String complexString1 = StringTool.allTrim(operands[0].getString()); - String complexString2 = StringTool.allTrim(operands[1].getString()); - - Complex c1, c2; - - try { - c1 = imParseComplexNumber(complexString1); - c2 = imParseComplexNumber(complexString2); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - double divisor = Math.pow(c2.real, 2) + Math.pow(c2.imaginary, 2); - double a = c1.real * c2.real + c1.imaginary * c2.imaginary; - double b = c1.imaginary * c2.real - c1.real * c2.imaginary; - double c = a / divisor; - double d = b / divisor; - - String imDiv; - if (d > 0) - imDiv = imGetExcelStr(c) + "+" + imGetExcelStr(d) + "i"; - else - imDiv = imGetExcelStr(c) + "-" + imGetExcelStr(d) + "i"; - - PtgStr pstr = new PtgStr(imDiv); - if (DEBUG) Logger.logInfo("Result from IMDIV= " + pstr.getString()); - return pstr; - } - - /** - * IMEXP - * Returns the exponential of a complex number - */ - protected static Ptg calcImExp(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImExp"); - String complexString = StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - // Exponential of a complex number x+yi = e^x(cos(y) + sin(y)i) - double e_x = Math.pow(Math.E, c.real); - double a = e_x * Math.cos(c.imaginary); - double b = e_x * Math.sin(c.imaginary); - - String imExp; - if (b > 0) - imExp = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imExp = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr = new PtgStr(imExp); - if (DEBUG) Logger.logInfo("Result from IMEXP= " + pstr.getString()); - return pstr; - } - - /** - * IMLN - * Returns the natural logarithm of a complex number - */ - protected static Ptg calcImLn(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImLn"); - String complexString = StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - // Natural log of a complex number= - // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i - double a = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); - a = Math.sqrt(a); - a = Math.log(a); - double b = Math.atan(c.imaginary / c.real); - - String imLn; - if (b > 0) - imLn = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imLn = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr = new PtgStr(imLn); - if (DEBUG) Logger.logInfo("Result from IMLN= " + pstr.getString()); - return pstr; - } - - /** - * IMLOG10 - * Returns the base-10 logarithm of a complex number - */ - protected static Ptg calcImLog10(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImLog10"); - String complexString = StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - // Natural log of a complex number= - // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i - double a = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); - a = Math.sqrt(a); - a = Math.log(a); - double b = Math.atan(c.imaginary / c.real); - // now, convert to base 10 log: - double logE = Math.log(Math.E) / Math.log(10); - a = a * logE; - b = b * logE; - - String imLog10; - if (b > 0) - imLog10 = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imLog10 = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr = new PtgStr(imLog10); - if (DEBUG) Logger.logInfo("Result from IMLOG10= " + pstr.getString()); - return pstr; - } - - /** - * IMLOG2 - * Returns the base-2 logarithm of a complex number - */ - protected static Ptg calcImLog2(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImLog2"); - String complexString = StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - // Natural log of a complex number= - // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i - double a = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); - a = Math.sqrt(a); - a = Math.log(a); - double b = Math.atan(c.imaginary / c.real); - // now, convert to base 2 log: - double logE = Math.log(Math.E) / Math.log(2); - a = a * logE; - b = b * logE; - // TODO: Results only correct to 8th precision: WHY??? - String imLog2; - if (b > 0) - imLog2 = imGetExcelStr(a, 8) + "+" + imGetExcelStr(b, 8) + c.suffix; - else - imLog2 = imGetExcelStr(a, 8) + imGetExcelStr(b, 8) + c.suffix; - - PtgStr pstr = new PtgStr(imLog2); - if (DEBUG) Logger.logInfo("Result from IMLOG2= " + pstr.getString()); - return pstr; - } - - /** - * IMPOWER - * Returns a complex number raised to any power - */ - protected static Ptg calcImPower(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImPower"); - String complexString = StringTool.allTrim(operands[0].getString()); - double n = operands[1].getDoubleVal(); - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - // A complex number (x + yi) raised to a power n is: - // sqrt(x^2 + y^2)*cos(n*atan(y/x)) + sqrt(x^2 + y^2)*sin(n*atan(y/x))i - double r = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); - r = Math.sqrt(r); - r = Math.pow(r, n); - double t = Math.atan(c.imaginary / c.real); - double a = r * Math.cos(n * t); - double b = r * Math.sin(n * t); - - String imPower; - if (b > 0) - imPower = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imPower = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr = new PtgStr(imPower); - if (DEBUG) Logger.logInfo("Result from IMPOWER= " + pstr.getString()); - return pstr; - } - - /** - * IMPRODUCT - * Returns the product of two complex numbers - */ - protected static Ptg calcImProduct(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - - if (DEBUG) debugOperands(operands, "IMPRODUCT"); - Ptg[] ops = PtgCalculator.getAllComponents(operands); - String[] complexStrings = new String[ops.length]; - for (int i = 0; i < ops.length; i++) - complexStrings[i] = StringTool.allTrim(ops[i].getString()); - - Complex[] c = new Complex[complexStrings.length]; - for (int i = 0; i < complexStrings.length; i++) { - try { - c[i] = imParseComplexNumber(complexStrings[i]); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - } - - // basically, linear binomial multiplication over n terms - // (a + bi)(c + di) = (ac-bd) + (ad+bc)i for n terms - for (int i = 1; i < c.length; i++) { - double a = c[0].real; - double b = c[0].imaginary; - c[0].real = a * c[i].real - b * c[i].imaginary; - c[0].imaginary = a * c[i].imaginary + b * c[i].real; - } - - // Format Result - String imSum; - if (c[0].imaginary > 0) - imSum = imGetExcelStr(c[0].real) + "+" + imGetExcelStr(c[0].imaginary) + c[0].suffix; - else - imSum = imGetExcelStr(c[0].real) + imGetExcelStr(c[0].imaginary) + c[0].suffix; - - PtgStr pstr = new PtgStr(imSum); - if (DEBUG) Logger.logInfo("Result from IMSPRODUCT= " + pstr.getString()); - return pstr; - } - - /** - * IMREAL - * Returns the real coefficient of a complex number - */ - protected static Ptg calcImReal(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "IMREAL"); - String complexString = StringTool.allTrim(operands[0].getString()); - - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - PtgNumber pnum = new PtgNumber(c.real); - if (DEBUG) Logger.logInfo("Result from IMREAL= " + pnum.getString()); - return pnum; - } - - /** - * IMSIN - * Returns the sine of a complex number - */ - protected static Ptg calcImSin(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImSin"); - String complexString = StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - // sin(a + bi)= sin(a)cosh(b) + cos(a)sinh(b)i (Excel doc is wrong!) - double cosh = (Math.pow(Math.E, c.imaginary) + Math.pow(Math.E, c.imaginary * -1)) / 2; - double sinh = (Math.pow(Math.E, c.imaginary) - Math.pow(Math.E, c.imaginary * -1)) / 2; - double a = Math.sin(c.real) * cosh; - double b = Math.cos(c.real) * sinh; - - String imSin; - if (b > 0) - imSin = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imSin = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr = new PtgStr(imSin); - if (DEBUG) Logger.logInfo("Result from IMSIN= " + pstr.getString()); - return pstr; - } - - /** - * IMSQRT - * Returns the square root of a complex number - */ - protected static Ptg calcImSqrt(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ImSqrt"); - String complexString = StringTool.allTrim(operands[0].getString()); - Complex c; - try { - c = imParseComplexNumber(complexString); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - // The square root of a complex number (x + yi) is: - // sqrt(sqrt(x^2 + y^2))*cos(atan(y/x)/2) + - // sqrt(sqrt(x^2 + y^2))*sin(atan(y/x)/2)i - double r = Math.pow(c.real, 2) + Math.pow(c.imaginary, 2); - r = Math.sqrt(r); - r = Math.sqrt(r); - double t = Math.atan(c.imaginary / c.real); - double a = r * Math.cos(t / 2); - double b = r * Math.sin(t / 2); - - String imSqrt; - if (b > 0) - imSqrt = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix; - else - imSqrt = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix; - - PtgStr pstr = new PtgStr(imSqrt); - if (DEBUG) Logger.logInfo("Result from IMSQRT= " + pstr.getString()); - return pstr; - } - - - /** - * IMSUB - * Returns the difference of two complex numbers - */ - protected static Ptg calcImSub(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "IMSUB"); - String complexString1 = StringTool.allTrim(operands[0].getString()); - String complexString2 = StringTool.allTrim(operands[1].getString()); - - Complex c1, c2; - try { - c1 = imParseComplexNumber(complexString1); - c2 = imParseComplexNumber(complexString2); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - // basically, linear binomial subtraction: - // (a + bi) - (c + di)= (a-c) + (b-d)i - double a = c1.real - c2.real; - double b = c1.imaginary - c2.imaginary; - - String imSub; - if (b > 0) - imSub = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c1.suffix; // should have the same suffix - else - imSub = imGetExcelStr(a) + imGetExcelStr(b) + c1.suffix; - - PtgStr pstr = new PtgStr(imSub); - if (DEBUG) Logger.logInfo("Result from IMSUB= " + pstr.getString()); - return pstr; - } - - /** - * IMSUM - * Returns the sum of complex numbers - */ - protected static Ptg calcImSum(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - - if (DEBUG) debugOperands(operands, "IMSUM"); - Ptg[] ops = PtgCalculator.getAllComponents(operands); - String[] complexStrings = new String[ops.length]; - for (int i = 0; i < ops.length; i++) - complexStrings[i] = StringTool.allTrim(ops[i].getString()); - - Complex[] c = new Complex[complexStrings.length]; - for (int i = 0; i < complexStrings.length; i++) { - try { - c[i] = imParseComplexNumber(complexStrings[i]); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - } - - // basically, linear binomial addition over n terms - // (a + bi)+(c + di) = (a+c) + (b+d)i for n terms - for (int i = 1; i < c.length; i++) { - c[0].real = c[0].real + c[i].real; - c[0].imaginary = c[0].imaginary + c[i].imaginary; - } - - // Format Result - String imSum; - if (c[0].imaginary > 0) - imSum = imGetExcelStr(c[0].real) + "+" + imGetExcelStr(c[0].imaginary) + c[0].suffix; - else - imSum = imGetExcelStr(c[0].real) + imGetExcelStr(c[0].imaginary) + c[0].suffix; - - PtgStr pstr = new PtgStr(imSum); - if (DEBUG) Logger.logInfo("Result from IMSUM= " + pstr.getString()); - return pstr; - } - - /** - * OCT2BIN - * Converts an octal number to binary - */ - protected static Ptg calcOct2Bin(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "OCT2BIN"); - long l = (long) operands[0].getDoubleVal(); // avoid sci notation - String oString = String.valueOf(l).trim(); - // 10 digits at most (=30 bits) - if (oString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - int places = 0; - if (operands.length > 1) - places = operands[1].getIntVal(); - - long dec; - String bString; - try { - dec = Long.parseLong(oString, 8); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 536870912L) // 2^29 (= 30 bits, signed) - dec -= 1073741824L; // 2^30 (= 31 bits, signed) - bString = Long.toBinaryString(dec); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - - if (dec < -512 /*7777777000*/ || dec > 0777 || places < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - bString = bString.substring(Math.max(bString.length() - 10, 0)); - } else if (places > 0) { - if (bString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - bString = ("0000000000" + bString); // maximum= 10 places - bString = bString.substring(bString.length() - places); - } - PtgStr pstr = new PtgStr(bString); - if (DEBUG) Logger.logInfo("Result from OCT2BIN= " + pstr.getString()); - return pstr; - } - - /** - * OCT2DEC - * Converts an octal number to decimal - */ - protected static Ptg calcOct2Dec(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "OCT2DEC"); - long l = (long) operands[0].getDoubleVal(); // avoid sci notation - String oString = String.valueOf(l).trim(); - // 10 digits at most (=30 bits) - if (oString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - long dec; - try { - dec = Long.parseLong(oString, 8); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 536870912L) // 2^29 (= 30 bits, signed) - dec -= 1073741824L; // 2^30 (= 31 bits, signed) - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - PtgNumber pnum = new PtgNumber(dec); - if (DEBUG) Logger.logInfo("Result from OCT2DEC= " + pnum.getVal()); - return pnum; - } - - /** - * OCT2HEX - * Converts an octal number to hexadecimal - */ - protected static Ptg calcOct2Hex(Ptg[] operands) { - if (operands.length < 1) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "OCT2HEX"); - long l = (long) operands[0].getDoubleVal(); // avoid sci notation - String oString = String.valueOf(l).trim(); - // 10 digits at most (=30 bits) - if (oString.length() > 10) return new PtgErr(PtgErr.ERROR_NUM); - int places = 0; - if (operands.length > 1) - places = operands[1].getIntVal(); - - long dec; - String hString; - try { - dec = Long.parseLong(oString, 8); - // must det. manually if binary string is negative because parseInt/parseLong does not - // handle two's complement input!!! - if (dec >= 536870912L) // 2^29 (= 30 bits, signed) - dec -= 1073741824L; // 2^30 (= 31 bits, signed) - hString = Long.toHexString(dec).toUpperCase(); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NUM); - } - if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) - hString = hString.substring(Math.max(hString.length() - 10, 0)); - } else if (places > 0) { - if (hString.length() > places) return new PtgErr(PtgErr.ERROR_NUM); - hString = ("0000000000" + hString); // maximum= 10 bits - hString = hString.substring(hString.length() - places); - } - - PtgStr pstr = new PtgStr(hString); - if (DEBUG) Logger.logInfo("Result from OCT2HEX= " + pstr.getString()); - return pstr; - } - - /* - SQRTPI - Returns the square root of (number * PI) -*/ - static void debugOperands(Ptg[] operands, String f) { - if (DEBUG) { - Logger.logInfo("Operands for " + f); - for (int i = 0; i < operands.length; i++) { - String s = operands[i].getString(); - if (!(operands[i] instanceof PtgMissArg)) { - String v = operands[i].getValue().toString(); - Logger.logInfo("\tOperand[" + i + "]=" + s + " " + v); - } else - Logger.logInfo("\tOperand[" + i + "]=" + s + " is Missing"); - } - } - } - -} - -class Complex { - public double real; - public double imaginary; - public String suffix; - - Complex() { - this.suffix = "i"; - } - - Complex(double r, double i) { - this.real = r; - this.imaginary = i; - this.suffix = "i"; - } -} - diff --git a/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.kt new file mode 100644 index 0000000..2d6e105 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/EngineeringCalculator.kt @@ -0,0 +1,2290 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool + + +/* + EngineeringCalculator is a collection of static methods that operate + as the Microsoft Excel function calls do. + + All methods are called with an array of ptg's, which are then + acted upon. A Ptg of the type that makes sense (ie boolean, number) + is returned after each method. +*/ +object EngineeringCalculator { + var DEBUG = false + + internal val PI_SQRT = Math.sqrt(Math.PI) + internal val TSQPI = 2.0 / PI_SQRT + private var firstCall = true + + // =========================================================================== + /*** Nominator coefficients for approximation to erf in first interval. */ + private val ERF_A = doubleArrayOf(3.16112374387056560E00, 1.13864154151050156E02, 3.77485237685302021E02, 3.20937758913846947E03, 1.85777706184603153E-1) + /*** Denominator coefficients for approximation to erf in first interval. */ + private val ERF_B = doubleArrayOf(2.36012909523441209E01, 2.44024637934444173E02, 1.28261652607737228E03, 2.84423683343917062E03) + // =========================================================================== + /*** Nominator coefficients for approximation to erfc in second interval. */ + private val ERF_C = doubleArrayOf(5.64188496988670089E-1, 8.88314979438837594E0, 6.61191906371416295E01, 2.98635138197400131E02, 8.81952221241769090E02, 1.71204761263407058E03, 2.05107837782607147E03, 1.23033935479799725E03, 2.15311535474403846E-8) + /*** Denominator coefficients for approximation to erfc in second interval. */ + private val ERF_D = doubleArrayOf(1.57449261107098347E01, 1.17693950891312499E02, 5.37181101862009858E02, 1.62138957456669019E03, 3.29079923573345963E03, 4.36261909014324716E03, 3.43936767414372164E03, 1.23033935480374942E03) + // =========================================================================== + /*** Nominator coefficients for approximation to erfc in third interval. */ + private val ERF_P = doubleArrayOf(3.05326634961232344E-1, 3.60344899949804439E-1, 1.25781726111229246E-1, 1.60837851487422766E-2, 6.58749161529837803E-4, 1.63153871373020978E-2) + /*** Denominator coefficients for approximation to erfc in third interval. */ + private val ERF_Q = doubleArrayOf(2.56852019228982242, 1.87295284992346047, 5.27905102951428412E-1, 6.05183413124413191E-2, 2.33520497626869185E-3) + // =========================================================================== + internal val THRESHOLD = 0.46875 + internal val SQRPI = 1.0 / PI_SQRT + internal val X_INF = java.lang.Double.MAX_VALUE + internal val X_MIN = java.lang.Double.MIN_VALUE + // private static final double X_NEG = -9.38241396824444; + internal val X_NEG = -Math.sqrt(Math.log(X_INF / 2)) + internal val X_SMALL = deps + internal val X_HUGE = 1.0 / (2.0 * Math.sqrt(X_SMALL)) + internal val X_MAX = Math.min(X_INF, 1.0 / (PI_SQRT * X_MIN)) + // static final double X_BIG = 9.194E0; + internal val X_BIG = 26.543 + + private val FEPS_START = 2e-6f + /** double valued machine precision. */ + /** + * float valued machine precision. + */ + //public static final float FEPS; + internal + /** + * Calculate the machine accuracy, + * which is the smallest eps with + * 1<1+eps + *///FEPS = feps; + //DEPS = deps; + //if (DEBUG) + // Logger.logInfo(format("feps:%8.2E deps:%8.3G", FEPS, DEPS)); + val deps: Double + get() { + var feps = FEPS_START + var fy = 1.0f + feps + while (fy > 1.0f) { + feps /= 2.0f + fy = 1.0f + feps + } + var deps = (feps * FEPS_START).toDouble() + var dy = 1.0 + deps + while (dy > 1.0) { + deps /= 2.0 + dy = 1.0 + deps + } + return deps + } + + /* + BESSELI + Returns the modified Bessel function In(x) + + BESSELJ + Returns the Bessel function Jn(x) + + BESSELK + Returns the modified Bessel function Kn(x) + + BESSELY + Returns the Bessel function Yn(x) +*/ + + /** + * BIN2DEC + * Converts a binary number to decimal + */ + internal fun calcBin2Dec(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "BIN2DEC") + val bString = operands[0].string.trim { it <= ' ' } + // 10 bits at most + if (bString.length > 10) return PtgErr(PtgErr.ERROR_NUM) + // must det. manually if binary string is negative because parseInt does not + // handle two's complement input!!! + val bIsNegative = bString.length == 10 && bString.substring(0, 1).equals("1", ignoreCase = true) + + var dec = 0 + try { + dec = Integer.parseInt(bString, 2) + if (bIsNegative) dec -= 1024 // 2^10 (= signed 11 bits) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + val pnum = PtgNumber(dec.toDouble()) + if (DEBUG) Logger.logInfo("Result from BIN2DEC= " + pnum.`val`) + return pnum + } + + /** + * BIN2HEX + * Converts a binary number to hexadecimal + */ + internal fun calcBin2Hex(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "BIN2HEX") + val bString = operands[0].string.trim { it <= ' ' } + var places = 0 + if (operands.size > 1) + places = operands[1].intVal + + // 10 bits at most + if (bString.length > 10) return PtgErr(PtgErr.ERROR_NUM) + // must det. manually if binary string is negative because parseInt does not + // handle two's complement input!!! + val bIsNegative = bString.length == 10 && bString.substring(0, 1).equals("1", ignoreCase = true) + + var dec: Long + var hString: String + try { + dec = java.lang.Long.parseLong(bString, 2) + if (bIsNegative) dec -= 1024 // 2^10 (= signed 11 bits) + hString = java.lang.Long.toHexString(dec).toUpperCase() + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + hString = hString.substring(Math.max(hString.length - 10, 0)) + } else if (places > 0) { + if (hString.length > places) return PtgErr(PtgErr.ERROR_NUM) + hString = "0000000000$hString" // maximum= 10 bits + hString = hString.substring(hString.length - places) + } + + val pstr = PtgStr(hString) + if (DEBUG) Logger.logInfo("Result from BIN2HEX= " + pstr.string) + return pstr + } + + /** + * BIN2OCT + * Converts a binary number to octal + */ + internal fun calcBin2Oct(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "Bin2Oct") + val bString = operands[0].string.trim { it <= ' ' } + var places = 0 + if (operands.size > 1) + places = operands[1].intVal + + // 10 bits at most + if (bString.length > 10) return PtgErr(PtgErr.ERROR_NUM) + // must det. manually if binary string is negative because parseInt does not + // handle two's complement input!!! + val bIsNegative = bString.length == 10 && bString.substring(0, 1).equals("1", ignoreCase = true) + + var dec: Int + var oString: String + try { + dec = Integer.parseInt(bString, 2) + if (bIsNegative) dec -= 1024 // 2^10 (= signed 11 bits) + oString = Integer.toOctalString(dec) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + oString = oString.substring(Math.max(oString.length - 10, 0)) + } else if (places > 0) { + if (oString.length > places) return PtgErr(PtgErr.ERROR_NUM) + oString = "0000000000$oString" // maximum= 10 bits + oString = oString.substring(oString.length - places) + } + val pstr = PtgStr(oString) + if (DEBUG) Logger.logInfo("Result from BIN2OCT= " + pstr.string) + return pstr + } + + /** + * COMPLEX + * Converts real and imaginary coefficients into a complex number + */ + internal fun calcComplex(operands: Array): Ptg { + if (operands.size < 2) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "Complex") + val real = operands[0].intVal + val imaginary = operands[1].intVal + var suffix = "i" + if (operands.size > 2) { + suffix = operands[2].string.trim { it <= ' ' } + if (!(suffix == "i" || suffix == "j")) { + return PtgErr(PtgErr.ERROR_VALUE) + } + } + var complexString = "" + + // result: real + imaginary suffix + // real - imaginary suffix + // real (if imaginary==0) + // real + suffix (if imaginary==1) + // imaginary suffix (if (real==0) + if (real != 0) { + complexString = real.toString() + if (imaginary > 0) + complexString += " + " + } + + if (imaginary != 0) + complexString += (if (Math.abs(imaginary) != 1) imaginary.toString() else "") + suffix + + if (complexString == "") complexString = "0" + + val pstr = PtgStr(complexString) + if (DEBUG) Logger.logInfo("Result from COMPLEX= " + pstr.string) + return pstr + } + + /** + * CONVERT + * Converts a number from one measurement system to another + * Weight and mass From_unit or to_unit + * Gram "g" + * Slug "sg" + * Pound mass (avoirdupois) "lbm" + * U (atomic mass unit) "u" + * Ounce mass (avoirdupois) "ozm" + * + * + * Distance From_unit or to_unit + * Meter "m" + * Statute mile "mi" + * Nautical mile "Nmi" + * Inch "in" + * Foot "ft" + * Yard "yd" + * Angstrom "ang" + * Pica (1/72 in.) "Pica" + * + * + * Time From_unit or to_unit + * Year "yr" + * Day "day" + * Hour "hr" + * Minute "mn" + * Second "sec" + * + * + * Pressure From_unit or to_unit + * Pascal "Pa" + * Atmosphere "atm" + * mm of Mercury "mmHg" + * + * + * Force From_unit or to_unit + * Newton "N" + * Dyne "dyn" + * Pound force "lbf" + * + * + * Energy From_unit or to_unit + * Joule "J" + * Erg "e" + * Thermodynamic calorie "c" + * IT calorie "cal" + * Electron volt "eV" + * Horsepower-hour "HPh" + * Watt-hour "Wh" + * Foot-pound "flb" + * BTU "BTU" + * + * + * Power From_unit or to_unit + * Horsepower "HP" + * Watt "W" + * + * + * Magnetism From_unit or to_unit + * Tesla "T" + * Gauss "ga" + * + * + * Temperature From_unit or to_unit + * Degree Celsius "C" + * Degree Fahrenheit "F" + * Degree Kelvin "K" + * + * + * Liquid measure From_unit or to_unit + * Teaspoon "tsp" + * Tablespoon "tbs" + * Fluid ounce "oz" + * Cup "cup" + * U.S. pint "pt" + * U.K. pint "uk_pt" + * Quart "qt" + * Gallon "gal" + * Liter "l" + * + * + * + * + * The following abbreviated unit prefixes can be prepended to any metric + * from_unit or to_unit. + * + * + * Prefix Multiplier Abbreviation + * exa 1E+18 "E" + * peta 1E+15 "P" + * tera 1E+12 "T" + * giga 1E+09 "G" + * mega 1E+06 "M" + * kilo 1E+03 "k" + * hecto 1E+02 "h" + * dekao 1E+01 "e" + * deci 1E-01 "d" + * centi 1E-02 "c" + * milli 1E-03 "m" + * micro 1E-06 "u" + * nano 1E-09 "n" + * pico 1E-12 "p" + * femto 1E-15 "f" + * atto 1E-18 "a" + * + * + * + * + * Remarks + * + * + * If the input data types are incorrect, CONVERT returns the #VALUE! error value. + * If the unit does not exist, CONVERT returns the #N/A error value. + * If the unit does not support an abbreviated unit prefix, CONVERT returns the #N/A error value. + * If the units are in different groups, CONVERT returns the #N/A error value. + * Unit names and prefixes are case-sensitive. + */ + private fun findUnits(u: String, units: Array): Int { + val bFound = false + for (i in units.indices) { + if (u == units[i]) return i + } + return -1 + } + + private fun prefixMultiplier(p: String, prefixes: Array): Double { + var multiplier = 1.0 + if (p == "") return multiplier + for (i in prefixes.indices) { + if (p == prefixes[i]) { + when (i) { + 0 // "E" exa + -> multiplier = 1E+18 + 1 // "P" peta + -> multiplier = 1E+15 + 2 // "T" tera + -> multiplier = 1E+12 + 3 // "G" giga + -> multiplier = 1E+09 + 4 // "M" mega + -> multiplier = 1E+06 + 5 // "k" kilo + -> multiplier = 1E+03 + 6 // "h" hecto + -> multiplier = 1E+02 + 7 // "e" dekao + -> multiplier = 1E+01 + 8 // "d" deci + -> multiplier = 1E-01 + 9 // "c" centi + -> multiplier = 1E-02 + 10 // "m" milli + -> multiplier = 1E-03 + 11 // "u" micro + -> multiplier = 1E-06 + 12 // "n" nano + -> multiplier = 1E-09 + 13 // "p" pico + -> multiplier = 1E-12 + 14 // "f" femto + -> multiplier = 1E-15 + 15 // "a" atto + -> multiplier = 1E-18 + } + return multiplier + } + } + + return multiplier + } + + internal fun calcConvert(operands: Array): Ptg { + if (operands.size < 3) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "CONVERT") + val number = operands[0].doubleVal + var fromUnits = operands[1].string.trim { it <= ' ' } + var toUnits = operands[2].string.trim { it <= ' ' } + + val allUnits = arrayOf("g", "sg", "lbm", "u", "ozm", "m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica", "yr", "day", "hr", "mn", "sec", "Pa", "atm", "mmHg", "N", "dyn", "lbf", "J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU", "HP", "W", "T", "ga", "C", "F", "K", "tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l") + val weightUnits = arrayOf("g", "sg", "lbm", "u", "ozm") + val distanceUnits = arrayOf("m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica") + val timeUnits = arrayOf("yr", "day", "hr", "mn", "sec") + val pressureUnits = arrayOf("Pa", "atm", "mmHg") + val forceUnits = arrayOf("N", "dyn", "lbf") + val energyUnits = arrayOf("J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU") + val powerUnits = arrayOf("HP", "W") + val magnetismUnits = arrayOf("T", "ga") + val temperatureUnits = arrayOf("C", "F", "K") + val liquidMeasureUnits = arrayOf("tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l") + + // for any metric unit, may be prefixed with + var fromPrefix = "" + var toPrefix = "" + val metricPrefixes = arrayOf("E", "P", "T", "G", "M", "k", "h", "e", "d", "c", "m", "u", "n", "p", "f", "a") + + // first, see if fromUnits and toUnits are in list of acceptable units + if (findUnits(fromUnits, allUnits) < 0) { // doesn't match; strip prefix and try again + if (fromUnits.length > 1) { + fromPrefix = fromUnits.substring(0, 1) + fromUnits = fromUnits.substring(1) + // now recheck + if (findUnits(fromUnits, allUnits) < 0) + return PtgErr(PtgErr.ERROR_NA) + // make sure that prefix is acceptable + if (findUnits(fromPrefix, metricPrefixes) < 0) + return PtgErr(PtgErr.ERROR_NA) + } else + return PtgErr(PtgErr.ERROR_NA) + } + if (findUnits(toUnits, allUnits) < 0) {// doesn't match; strip prefix and try again + if (toUnits.length > 1) { + toPrefix = toUnits.substring(0, 1) + toUnits = toUnits.substring(1) + // now recheck + if (findUnits(toUnits, allUnits) < 0) + return PtgErr(PtgErr.ERROR_NA) + // make sure that prefix is acceptable + if (findUnits(toPrefix, metricPrefixes) < 0) + return PtgErr(PtgErr.ERROR_NA) + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // at here, we know that the prefixes and units are found, but we don't know if they match or make sense ... + var result = 0.0 + var from = 0.0 + var i: Int + var j = -1 + + // WEIGHT conversion + if ((i = findUnits(fromUnits, weightUnits)) >= 0) { + j = findUnits(toUnits, weightUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + // { "g", "sg", "lbm", "u", "ozm" } + // get fromUnit in grams + when (i) { + // from: + 0 // "g" + -> from = number * prefixMultiplier(fromPrefix, metricPrefixes) + 1 // "sg" + -> from = 14593.84241892870000000000 * number + 2 // "lbm" + -> from = 453.5923097488115 * number + 3 // "u" + -> from = 1.660531004604650E-24 * number * prefixMultiplier(fromPrefix, metricPrefixes) + 4 // "ozm" + -> from = 28.349515207973 * number + } + // now convert + when (j) { + 0 // "g" + -> result = from / prefixMultiplier(toPrefix, metricPrefixes) + 1 // "sg" + -> result = from * 0.00006852205000534780 + 2 // "lbm" + -> result = from * 0.00220462291469134000 + 3 // "u" + -> result = from * 6.02217000000000000000E+23 / prefixMultiplier(toPrefix, metricPrefixes) + 4 // "ozm" + -> result = from * 0.03527397180036270000 + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // DISTANCE conversion + if (j == -1 && (i = findUnits(fromUnits, distanceUnits)) >= 0) { + j = findUnits(toUnits, distanceUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + // "m", "mi", "Nmi", "in", "ft", "yd", "ang", "Pica" ; + // get fromUnits in m + when (i) { + 0 //"m" + -> from = number * prefixMultiplier(fromPrefix, metricPrefixes) + 1 //"mi" + -> from = 1609.344000000000 * number + 2 //"Nmi" + -> from = 1852.000000000000 * number + 3 //"in" + -> from = 0.025400000000 * number + 4 // "ft" + -> from = 0.304800000000 * number + 5 // "yd" + -> from = 0.914400000300 * number + 6 // "ang" + -> from = 0.000000000100 * number * prefixMultiplier(fromPrefix, metricPrefixes) + 7 // "Pica" + -> from = 0.00035277777777780000 * number + } + when (j) { + 0 //"m" + -> result = from / prefixMultiplier(toPrefix, metricPrefixes) + 1 //"mi" + -> result = 0.00062137119223733400 * from + 2 //"Nmi" + -> result = 0.00053995680345572400 * from + 3 //"in" + -> result = 39.37007874015750000000 * from + 4 // "ft" + -> result = 3.28083989501312000000 * from + 5 // "yd" + -> result = 1.09361329797891000000 * from + 6 // "ang" + -> result = 10000000000.000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes) + 7 // "Pica" + -> result = 2834.64566929116000000000 * from + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // TIME conversion + if (j == -1 && (i = findUnits(fromUnits, timeUnits)) >= 0) { + j = findUnits(toUnits, timeUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + //"yr", "day", "hr", "mn", "sec" + when (i) { + 0 // "yr" + -> from = number + 1 // "day" + -> from = 0.00273785078713210000 * number + 2 // "hr" + -> from = 0.00011407711613050400 * number + 3 // "mn" + -> from = 0.00000190128526884174 * number + 4 // "sec" + -> from = 0.00000003168808781403 * number + } + when (j) { + 0 // "yr" + -> result = from + 1 // "day" + -> result = 365.250000000000 * from + 2 // "hr" + -> result = 8766.000000000000 * from + 3 // "mn" + -> result = 525960.000000000000 * from + 4 // "sec" + -> result = 31557600.000000000000 * from + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // PRESSURE conversion + if (j == -1 && (i = findUnits(fromUnits, pressureUnits)) >= 0) { + j = findUnits(toUnits, pressureUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + //"Pa", "atm", "mmHg" + when (i) { + 0 // "Pa" + -> from = number * prefixMultiplier(fromPrefix, metricPrefixes) + 1 // "atm" + -> from = 101324.99658300000000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes) + 2 // "mmHg" + -> from = 133.32236392500000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes) + } + when (j) { + 0 // "Pa" + -> result = from / prefixMultiplier(toPrefix, metricPrefixes) + 1 // "atm" + -> result = 0.00000986923299998193 * from / prefixMultiplier(toPrefix, metricPrefixes) + 2 // "mmHg" + -> result = 0.00750061707998627000 * from / prefixMultiplier(toPrefix, metricPrefixes) + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // FORCE conversion + if (j == -1 && (i = findUnits(fromUnits, forceUnits)) >= 0) { + j = findUnits(toUnits, forceUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + //"N", "dyn", "lbf" + when (i) { + 0 // "N" + -> from = number * prefixMultiplier(fromPrefix, metricPrefixes) + 1 // "dyn" + -> from = 0.000010000000 * number * prefixMultiplier(fromPrefix, metricPrefixes) + 2 // "lbf" + -> from = 4.448222000000 * number + } + when (j) { + 0 // "N" + -> result = from / prefixMultiplier(toPrefix, metricPrefixes) + 1 // "dyn" + -> result = 100000.000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes) + 2 // "lbf" + -> result = 0.22480892365533900000 * from + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // ENERGY conversion + if (j == -1 && (i = findUnits(fromUnits, energyUnits)) >= 0) { + j = findUnits(toUnits, energyUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + //"J", "e", "c", "cal", "eV", "HPh", "Wh", "flb", "BTU" + when (i) { + 0 // "J" + -> from = number * prefixMultiplier(fromPrefix, metricPrefixes) + 1 // "e" + -> from = 0.00000010000004806570 * number * prefixMultiplier(fromPrefix, metricPrefixes) + 2 // "c" + -> from = 4.18399101363672000000 * number * prefixMultiplier(fromPrefix, metricPrefixes) + 3 // "cal" + -> from = 4.18679484613929000000 * number * prefixMultiplier(fromPrefix, metricPrefixes) + 4 // "eV" + -> from = 1.60217646E-19 * number * prefixMultiplier(fromPrefix, metricPrefixes) + 5 // "HPh" + -> from = 2684517.41316170000000000000 * number + 6 // "Wh" + -> from = 3599.99820554720000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes) + 7 // "flb" + -> from = 0.04214000032364240000 * number + 8 // "BTU" + -> from = 1055.05813786749000000000 * number + } + when (j) { + 0 // "J" + -> result = from / prefixMultiplier(toPrefix, metricPrefixes) + 1 // "e" + -> result = 9999995.19343231000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes) + 2 // "c" + -> result = 0.23900624947346700000 * from / prefixMultiplier(toPrefix, metricPrefixes) + 3 // "cal" + -> result = 0.23884619064201700000 * from / prefixMultiplier(toPrefix, metricPrefixes) + 4 // "eV" + -> result = 6241457000000000000.00000000000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes) + 5 // "HPh" + -> result = 0.00000037250643080100 * from + 6 // "Wh" + -> result = 0.00027777791623871100 * from / prefixMultiplier(toPrefix, metricPrefixes) + 7 // "flb" + -> result = 23.73042221926510000000 * from + 8 // "BTU" + -> result = 0.00094781506734901500 * from + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // POWER conversion + if (j == -1 && (i = findUnits(fromUnits, powerUnits)) >= 0) { + j = findUnits(toUnits, powerUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + //"HP", "W" + when (i) { + 0 // "HP" + -> from = number + 1 // "W" + -> from = 0.00134102006031908000 * number * prefixMultiplier(fromPrefix, metricPrefixes) + } + when (j) { + 0 // "HP" + -> result = from + 1 // "W" + -> result = 745.70100000000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes) + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // MAGNETISM conversion + if (j == -1 && (i = findUnits(fromUnits, magnetismUnits)) >= 0) { + j = findUnits(toUnits, magnetismUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + //"T", "ga" + when (i) { + 0 // "T" + -> from = number * prefixMultiplier(fromPrefix, metricPrefixes) + 1 // "ga" + -> from = 0.000100000000 * number * prefixMultiplier(fromPrefix, metricPrefixes) + } + when (j) { + 0 // "T" + -> result = from / prefixMultiplier(toPrefix, metricPrefixes) + 1 // "ga" + -> result = 10000.000000000000 * from / prefixMultiplier(toPrefix, metricPrefixes) + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // TEMPERATURE conversion + if (j == -1 && (i = findUnits(fromUnits, temperatureUnits)) >= 0) { + j = findUnits(toUnits, temperatureUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + //"C", "F", "K" + when (i) { + 0 // "C" + -> from = number + 1 // "F" + -> from = (number - 32) / 1.8 + 2 // "K" + -> from = number * prefixMultiplier(fromPrefix, metricPrefixes) - 273.15 + } + when (j) { + 0 // "C" + -> result = from + 1 // "F" + -> result = from * 1.8 + 32 + 2 // "K" + -> result = (273.15000000000000000000 + from) / prefixMultiplier(toPrefix, metricPrefixes) + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + // LIQUID MEASURE conversion + if (j == -1 && (i = findUnits(fromUnits, liquidMeasureUnits)) >= 0) { + j = findUnits(toUnits, liquidMeasureUnits) + if (j > -1) { // both fromUnits and toUnits are in same family + //"tsp", "tbs", "oz", "cup", "pt", "uk_pt", "qt", "gal", "l" + when (i) { + 0 // "tsp" + -> from = number + 1 // "tbs" + -> from = 3.00000000000000000000 * number + 2 // "oz" + -> from = 6.00000000000000000000 * number + 3 // "cup" + -> from = 48.00000000000000000000 * number + 4 // "pt" + -> from = 96.00000000000000000000 * number + 5 // "uk_pt" + -> from = 115.26600000000000000000 * number + 6 // "qt" + -> from = 192.00000000000000000000 * number + 7 // "gal" + -> from = 768.00000000000000000000 * number + 8 // "l" + -> from = 202.84000000000000000000 * number * prefixMultiplier(fromPrefix, metricPrefixes) + } + when (j) { + 0 // "tsp" + -> result = from + 1 // "tbs" + -> result = 0.33333333333333300000 * from + 2 // "oz" + -> result = 0.16666666666666700000 * from + 3 // "cup" + -> result = 0.02083333333333330000 * from + 4 // "pt" + -> result = 0.01041666666666670000 * from + 5 // "uk_pt" + -> result = 0.00867558516821960000 * from + 6 // "qt" + -> result = 0.00520833333333333000 * from + 7 // "gal" + -> result = 0.00130208333333333000 * from + 8 // "l" + -> result = 0.00492999408400710000 * from / prefixMultiplier(toPrefix, metricPrefixes) + } + } else + return PtgErr(PtgErr.ERROR_NA) + } + + val pnum = PtgNumber(result) + if (DEBUG) Logger.logInfo("Result from CONVERT= " + pnum.string) + return pnum + } + + /** + * DEC2BIN + * Converts a decimal number to binary + */ + internal fun calcDec2Bin(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "DEC2BIN") + val dec = operands[0].intVal + var places = 0 + if (operands.size > 1) + places = operands[1].intVal + + if (dec < -512 || dec > 511 || places < 0) return PtgErr(PtgErr.ERROR_NUM) + + var bString = Integer.toBinaryString(dec) + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + bString = bString.substring(Math.max(bString.length - 10, 0)) + } else if (places > 0) { + if (bString.length > places) return PtgErr(PtgErr.ERROR_NUM) + bString = "0000000000$bString" // maximum= 10 bits + bString = bString.substring(bString.length - places) + } + val pstr = PtgStr(bString) + if (DEBUG) Logger.logInfo("Result from DEC2BIN= " + pstr.string) + return pstr + } + + /** + * DEC2HEX + * Converts a decimal number to hexadecimal + */ + internal fun calcDec2Hex(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "DEC2HEX") + val dec = PtgCalculator.getLongValue(operands[0]) + var places = 0 + if (operands.size > 1) + places = operands[1].intVal + if (dec < -549755813888L || dec > 549755813887L || places < 0) return PtgErr(PtgErr.ERROR_NUM) + var hString = java.lang.Long.toHexString(dec).toUpperCase() + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + hString = hString.substring(Math.max(hString.length - 10, 0)) + } else if (places > 0) { + if (hString.length > places) return PtgErr(PtgErr.ERROR_NUM) + hString = "0000000000$hString" // maximum= 10 places + hString = hString.substring(hString.length - places) + + } + val pstr = PtgStr(hString) + if (DEBUG) Logger.logInfo("Result from DEC2HEX= " + pstr.string) + return pstr + } + + /** + * DEC2OCT + * Converts a decimal number to octal + */ + internal fun calcDec2Oct(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "DEC2OCT") + val dec = PtgCalculator.getLongValue(operands[0]) + var places = 0 + if (operands.size > 1) + places = operands[1].intVal + if (dec < -536870912L || dec > 536870911L || places < 0) return PtgErr(PtgErr.ERROR_NUM) + var oString = java.lang.Long.toOctalString(dec) + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + oString = oString.substring(Math.max(oString.length - 10, 0)) + } else if (places > 0) { + if (oString.length > places) return PtgErr(PtgErr.ERROR_NUM) + oString = "0000000000$oString" // maximum= 10 places + oString = oString.substring(oString.length - places) + + } + val pstr = PtgStr(oString) + if (DEBUG) Logger.logInfo("Result from DEC2OCT= " + pstr.string) + return pstr + } + + /** + * DELTA + * Tests whether two values are equal + */ + internal fun calcDelta(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "DELTA") + + val number1 = operands[0].doubleVal + var number2 = 0.0 + if (operands.size > 1) + number2 = operands[1].doubleVal + var result = 0 + if (number1 == number2) + result = 1 + + val pnum = PtgNumber(result.toDouble()) + if (DEBUG) Logger.logInfo("Result from DELTA= " + pnum.string) + return pnum + } + + + /** + * helper erf calc - seems to work *ALRIGHT* for values over 1 + * NOTE: not accurate to 9 digits for every case + * + * @param x + * @return + */ + private fun erf_try1(x: Double): Double { + var t = x + val x2 = Math.pow(x, 2.0) + var n = 1000.0 + while (n >= 0.5) { + t = x + n / t + n -= 0.5 + } + t = 1.0 / t + val tt = Math.exp(-x2) / Math.sqrt(Math.PI) + return 1 - tt * t + } + + /** + * ERF + * Returns the error function integrated between lower_limit and upper_limit. + * ERF(lower_limit,upper_limit) + * + * + * Lower_limit is the lower bound for integrating ERF. + * Upper_limit is the upper bound for integrating ERF. If omitted, ERF integrates between zero and lower_limit. + * + * + * With a single argument ERF returns the error function, defined as + * erf(x) = 2/sqrt(pi)* integral from 0 to x of exp(-t*t) dt. + * If two arguments are supplied, they are the lower and upper limits of the integral. + * NOTE: Accuracy is not always to 9 digits + * NOTE: Version with two parameters is NOT supported + */ + @Throws(FunctionNotSupportedException::class) + internal fun calcErf(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + try { + var lower_limit = operands[0].doubleVal + var upper_limit = java.lang.Double.NaN //lower_limit; + if (operands.size == 2) + upper_limit = operands[1].doubleVal + // If lower_limit is negative, ERF returns the #NUM! error value. + // If upper_limit is negative, ERF returns the #NUM! error value. + // if (lower_limit < 0 /*|| upper_limitupper_limit < 0*/) return new PtgErr(PtgErr.ERROR_NUM); + val neg = lower_limit < 0 + lower_limit = Math.abs(lower_limit) + + + var result: Double + val limit = lower_limit + /* + // try this: from "Computation of the error function erf in arbitrary precision with correct rounding" + double r= 0; + double r1= 0; + double estimate= (2/Math.sqrt(Math.PI))*(limit - Math.pow(limit, 3)/3.0); + double convergence= Math.pow(2, estimate-15); + for (int i= 0, n= 0; n < 100; i++, n++) { + double factor= 2.0*n + 1.0; + double z= Math.pow(limit, factor); + double zz= (MathFunctionCalculator.factorial(n)*factor); + double zzz= z/zz; + if ((i % 2)!=0) + r= r-zzz; + else + r= r+zzz; + + if (Math.abs(r)-r1) < + + r1= Math.abs(r1); + } + result= r*(2.0/Math.sqrt(Math.PI));*/ + + if (limit < 0.005) { + /* A&S 7.1.1 - good to at least 6 digts ... but not for larger values ... sigh ...*/ + var r = 0.0 + var i = 0 + var n = 0 + while (n < 12) { + val factor = 2.0 * n + 1.0 + val z = Math.pow(limit, factor) + val zz = MathFunctionCalculator.factorial(n.toLong()) * factor + val zzz = z / zz + if (i % 2 != 0) + r = r - zzz + else + r = r + zzz + i++ + n++ + } + result = r * (2.0 / Math.sqrt(Math.PI)) + } else + result = erf_try1(limit) + + if (neg) + result *= -1.0 + + if (!java.lang.Double.isNaN(upper_limit)) { // Erf(upper)-Erf(lower) + val result2 = calcErf(arrayOf(operands[1])) + if (result2 is PtgNumber) + result = result2.doubleVal - result + else + return PtgErr(PtgErr.ERROR_VALUE) + } + + return PtgNumber(result) + + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + /*** + * Calculate the error function erf. + * @param x the argument + * @return the value erf(x) + */ + fun erf(x: Double): Double { + //if (type==1) { + return erfAS(x) + //} + //return erfCody(x); + } + + /*** + * Calculate the remaining error function erfc. + * @param x the argument + * @return the value erfc(x) + */ + fun erfc(x: Double): Double { + //if (type==1) { + return erfcAS(x) + //} + //return erfcCody(x); + } + + /*** + * Internal helper method to calculate the error function at value x. + * This code is based on a Fortran implementation from + * [W. J. Cody](http://www.netlib.org/specfun/erf). + * Refactored by N.Wulff for Java. + * + * Cody vs AS algiorithm + * + * @param x the argument + * @return the approximation of erf(x) + */ + private fun erfCody(x: Double): Double { + var result = 0.0 + val y = Math.abs(x) + if (firstCall) { + firstCall = false + } + if (y <= THRESHOLD) { + result = x * calcLower(y) + } else { + result = calcUpper(y) + result = 0.5 - result + 0.5 + if (x < 0) { + result = -result + } + } + return result + } + + /*** + * Internal helper method to calculate the erfc functions. + * This code is based on a Fortran implementation from + * [W. J. Cody](http://www.netlib.org/specfun/erf). + * Refactored by N.Wulff for Java. + * + * @param x the argument + * @return the approximation erfc(x) + */ + private fun erfcCody(x: Double): Double { + var result = 0.0 + val y = Math.abs(x) + if (firstCall) { + firstCall = false + } + if (y <= THRESHOLD) { + result = x * calcLower(y) + result = 1 - result + } else { + result = calcUpper(y) + if (x < 0) { + result = 2.0 - result + } + } + return result + } + + /*** + * Internal helper method to calculate the erf/erfc functions. + * This code is based on a Fortran implementation from + * [W. J. Cody](http://www.netlib.org/specfun/erf). + * Refactored by N.Wulff for Java. + * + * @param y the value y=abs(x)<=THRESHOLD + * @return the series expansion + */ + private fun calcLower(y: Double): Double { + val result: Double + var ySq: Double + var xNum: Double + var xDen: Double + ySq = 0.0 + if (y > X_SMALL) + ySq = y * y + xNum = ERF_A[4] * ySq + xDen = ySq + for (i in 0..2) { + xNum = (xNum + ERF_A[i]) * ySq + xDen = (xDen + ERF_B[i]) * ySq + } + result = (xNum + ERF_A[3]) / (xDen + ERF_B[3]) + return result + } + + /*** + * Internal helper method to calculate the erf/erfc functions. + * This code is based on a Fortran implementation from + * [W. J. Cody](http://www.netlib.org/specfun/erf). + * Refactored by N.Wulff for Java. + * + * @param y the value y=abs(x)>THRESHOLD + * @return the series expansion + */ + private fun calcUpper(y: Double): Double { + var result: Double + var ySq: Double + var xNum: Double + var xDen: Double + if (y <= 4.0) { + xNum = ERF_C[8] * y + xDen = y + for (i in 0..6) { + xNum = (xNum + ERF_C[i]) * y + xDen = (xDen + ERF_D[i]) * y + } + result = (xNum + ERF_C[7]) / (xDen + ERF_D[7]) + } else { + result = 0.0 + if (y >= X_HUGE) { + result = SQRPI / y + } else { + ySq = 1.0 / (y * y) + xNum = ERF_P[5] * ySq + xDen = ySq + for (i in 0..3) { + xNum = (xNum + ERF_P[i]) * ySq + xDen = (xDen + ERF_Q[i]) * ySq + } + result = ySq * (xNum + ERF_P[4]) / (xDen + ERF_Q[4]) + result = (SQRPI - result) / y + } + } + ySq = Math.round(y * 16.0) / 16.0 + val del = (y - ySq) * (y + ySq) + result = Math.exp(-ySq * ySq) * Math.exp(-del) * result + return result + } + + /*** + * Calculate the error function at value x. + * AS 7.1.5/7.1.26 + * @param x the argument + * @return the value erf(x) + */ + private fun erfAS(x: Double): Double { + if (firstCall) { + firstCall = false + } + if (x < 0) { + return -erfAS(-x) + } + return if (x < 2) { + erfSeries(x) + } else erfRational(x) + } + + /*** + * Calculate the remaining erfc error function at value x. + * AS 7.1.5/7.1.26 + * @param x the argument + * @return the value erfc(x) + */ + private fun erfcAS(x: Double): Double { + if (firstCall) { + firstCall = false + } + return 1 - erfAS(x) + } + + /*** + * Series expansion from A&S 7.1.5. + * + * @param x the argument + * @return erf(x) + */ + private fun erfSeries(x: Double): Double { + val eps = 1e-8 // we want only ~1.E-7 + val kmax = 50 // this can be reached with ~30-40 + var an: Double + var ak = x + var erfo: Double + var erf = ak + var k = 1 + do { + erfo = erf + ak *= -x * x / k + an = ak / (2.0 * k + 1.0) + erf += an + } while (!hasConverged(erf, erfo, eps, ++k, kmax)) + return TSQPI * erf + } + + /** + * Indicate if an iterative algorithm has RELATIVE converged. + *


                      + * **Note**: + * HasConverged throws an ArithmeticException if more than max calls + * have been made. Choose hasReacherAccuracy if this is not desired. + *
                      + * + * @param xn the actual argument x[n] + * @param xo the older argument x[n-1] + * @param eps the accuracy to reach + * @param n the actual iteration counter + * @param max the maximal number of iterations + * @return flag indicating if accuracy is reached. + */ + fun hasConverged(xn: Double, xo: Double, + eps: Double, n: Int, max: Int): Boolean { + if (hasReachedAccuracy(xn, xo, eps)) { + return true + } + if (n >= max) { + throw ArithmeticException() + } + return false + } + + /** + * Indicate if xn and xo have the relative/absolute accuracy epsilon. + * In case that the true value is less than one this is based + * on the absolute difference, otherwise on the relative difference: + *
                      +     * 2*|x[n]-x[n-1]|/|x[n]+x[n-1]| < eps
                      +    
                      * + * + * @param xn the actual argument x[n] + * @param xo the older argument x[n-1] + * @param eps accuracy to reach + * @return flag indicating if accuracy is reached. + */ + fun hasReachedAccuracy(xn: Double, xo: Double, + eps: Double): Boolean { + val z = Math.abs(xn + xo) / 2 + var error = Math.abs(xn - xo) + if (z > 1) { + error /= z + } + return error <= eps + } + + /*** + * Rational approximation A&S 7.1.26 with accuracy 1.5E-7. + * + * @param x the argument + * @return erf(x) + */ + private fun erfRational(x: Double): Double { + /* coefficients for A&S 7.1.26. */ + val a = doubleArrayOf(.254829592, -.284496736, 1.421413741, -1.453152027, 1.061405429) + /* constant for A&S 7.1.26 */ + val p = .3275911 + val erf: Double + var r = 0.0 + val t = 1.0 / (1 + p * x) + for (i in 4 downTo 0) { + r = a[i] + r * t + } + erf = 1 - t * r * Math.exp(-x * x) + return erf + } + /* + ERFC + Returns the complementary error function +*/ + + /** + * GESTEP + * Tests whether a number is greater than a threshold value + */ + internal fun calcGEStep(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "GESTEP") + + val number = operands[0].doubleVal + var step = 0.0 + if (operands.size > 1) + step = operands[1].doubleVal + var result = 0 + if (number >= step) + result = 1 + + val pnum = PtgNumber(result.toDouble()) + if (DEBUG) Logger.logInfo("Result from GESTEP= " + pnum.string) + return pnum + } + + /** + * HEX2BIN + * Converts a hexadecimal number to binary + */ + internal fun calcHex2Bin(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "HEX2BIN") + val hString = operands[0].string.trim { it <= ' ' } + // 10 digits (40 bits) at most + if (hString.length > 10) return PtgErr(PtgErr.ERROR_NUM) + var places = 0 + if (operands.size > 1) + places = operands[1].intVal + + var dec: Long + var bString: String + try { + dec = java.lang.Long.parseLong(hString, 16) + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 549755813888L) + // 2^39 (= signed 40 bits) + dec -= 1099511627776L // 2^40 (= signed 41 bits) + bString = java.lang.Long.toBinaryString(dec) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + if (dec < -512 /*0xFFFFFFFE00*/ || dec > 0x1FF || places < 0) return PtgErr(PtgErr.ERROR_NUM) + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + bString = bString.substring(Math.max(bString.length - 10, 0)) + } else if (places > 0) { + if (bString.length > places) return PtgErr(PtgErr.ERROR_NUM) + bString = "0000000000$bString" // maximum= 10 places + bString = bString.substring(bString.length - places) + } + val pstr = PtgStr(bString) + if (DEBUG) Logger.logInfo("Result from HEX2BIN= " + pstr.string) + return pstr + } + + /** + * HEX2DEC + * Converts a hexadecimal number to decimal + */ + internal fun calcHex2Dec(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "HEX2DEC") + val hString = operands[0].string.trim { it <= ' ' } + // 10 digits (40 bits) at most + if (hString.length > 10) return PtgErr(PtgErr.ERROR_NUM) + var dec: Long + val oString: String + try { + dec = java.lang.Long.parseLong(hString, 16) + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 549755813888L) + // 2^39 (= signed 40 bits) + dec -= 1099511627776L // 2^40 (= signed 41 bits) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + val pnum = PtgNumber(dec.toDouble()) + if (DEBUG) Logger.logInfo("Result from HEX2DEC= " + pnum.`val`) + return pnum + } + + /** + * HEX2OCT + * Converts a hexadecimal number to octal + */ + internal fun calcHex2Oct(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "HEX2OCT") + val hString = operands[0].string.trim { it <= ' ' } + // 10 digits (40 bits) at most + if (hString.length > 10) return PtgErr(PtgErr.ERROR_NUM) + var places = 0 + if (operands.size > 1) + places = operands[1].intVal + + var dec: Long + var oString: String + try { + dec = java.lang.Long.parseLong(hString, 16) + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 549755813888L) + // 2^39 (= signed 40 bits) + dec -= 1099511627776L // 2^40 (= signed 41 bits) + oString = java.lang.Long.toOctalString(dec) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + if (dec < -536870912L /*0xFFE0000000*/ || dec > 0x1FFFFFFF || places < 0) return PtgErr(PtgErr.ERROR_NUM) + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + oString = oString.substring(Math.max(oString.length - 10, 0)) + } else if (places > 0) { + if (oString.length > places) return PtgErr(PtgErr.ERROR_NUM) + oString = "0000000000$oString" // maximum= 10 places + oString = oString.substring(oString.length - places) + } + val pstr = PtgStr(oString) + if (DEBUG) Logger.logInfo("Result from HEX2OCT= " + pstr.string) + return pstr + } + + /** + * imParseComplexNumber + * + * + * used in the following Imaginary-based formulas to parse a complex number into its + * real and imaginary components. + * Throws a numberformat exception if the complex number is not in the format of: + * real + imaginary + * real - imaginary + * imaginary (real= 0) + * real (imaginary= 0) + * where imaginary coefficient n= ni or nj + * * + */ + @Throws(NumberFormatException::class) + private fun imParseComplexNumber(complexNumber: String): Complex { + var complexNumber = complexNumber + val c = Complex() + if (complexNumber.length > 0) { + try { + var i = complexNumber.length + if (complexNumber.substring(i - 1, i) == "i" || complexNumber.substring(i - 1, i) == "j") { + c.suffix = complexNumber.substring(i - 1, i) + i -= 2 + while (i >= 0 && !(complexNumber.substring(i, i + 1) == "+" || complexNumber.substring(i, i + 1) == "-")) + i-- + if (i < 0) { // case of "#i" or "#j" i.e. no real and no sign + complexNumber = "+$complexNumber" + i++ + } + // get imaginary coefficient + sign + var s = complexNumber.substring(i, complexNumber.length - 1) + if (s.length == 1) { // only a sign; means that the coefficient==1 eg. real-j or real+i + s += "1" + } + c.imaginary = java.lang.Double.parseDouble(s) + } + if (i > 0) + c.real = java.lang.Double.parseDouble(complexNumber.substring(0, i)) + } catch (e: Exception) { + throw NumberFormatException() + } + + } + return c + } + + /** + * imGetStr + * + * @param operands + * @return double formatted as an integer if no precision, otherwise rounds to 15 + */ + private fun imGetExcelStr(d: Double, precision: Int = 15): String { + var d = d + val s: String + if (d == d.toInt().toDouble()) { + return if (d.toInt() == 1) "" else d.toInt().toString() + } + // round to precision - default= 15 + val r = Math.pow(10.0, precision.toDouble()) + d *= r + d = Math.round(d).toDouble() + d /= r + return d.toString() + } + + /** + * IMABS + * Returns the absolute value (modulus) of a complex number + */ + internal fun calcImAbs(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "IMABS") + val complexString = StringTool.allTrim(operands[0].string) + + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // Absolute of a complex number is: + // square root( real^2 + imaginary^2) + val result = Math.sqrt(Math.pow(c.real, 2.0) + Math.pow(c.imaginary, 2.0)) + + val pnum = PtgNumber(result) + if (DEBUG) Logger.logInfo("Result from IMABS= " + pnum.string) + return pnum + } + + /** + * IMAGINARY + * Returns the imaginary coefficient of a complex number + */ + internal fun calcImaginary(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "Imaginary") + val complexString = StringTool.allTrim(operands[0].string) + + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + val pnum = PtgNumber(c.imaginary) + if (DEBUG) Logger.logInfo("Result from IMAGINARY= " + pnum.string) + return pnum + } + + /** + * IMARGUMENT + * Returns the argument theta, an angle expressed in radians + */ + internal fun calcImArgument(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImArgument") + val complexString = StringTool.allTrim(operands[0].string) + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + val result = Math.atan(c.imaginary / c.real) + + val pnum = PtgNumber(result) + if (DEBUG) Logger.logInfo("Result from IMARGUMENT= " + pnum.string) + return pnum + } + + /** + * IMCONJUGATE + * Returns the complex conjugate of a complex number + */ + internal fun calcImConjugate(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImCongugate") + val complexString = StringTool.allTrim(operands[0].string) + + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + val congugate: String + if (c.real != 0.0 && c.imaginary != 0.0) + congugate = imGetExcelStr(c.real) + (if (c.imaginary < 0) "+" else "-") + imGetExcelStr(Math.abs(c.imaginary)) + c.suffix + else if (c.real == 0.0) + congugate = imGetExcelStr(Math.abs(c.imaginary)) + c.suffix + else + congugate = imGetExcelStr(c.real) + val pstr = PtgStr(congugate) + if (DEBUG) Logger.logInfo("Result from IMCONGUGATE= " + pstr.string) + return pstr + } + + /** + * IMCOS + * Returns the cosine of a complex number + */ + internal fun calcImCos(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImCos") + val complexString = StringTool.allTrim(operands[0].string) + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // cos(a + bi)= cos(a)cosh(b) - sin(a)sinh(b)i + val cosh = (Math.pow(Math.E, c.imaginary) + Math.pow(Math.E, c.imaginary * -1)) / 2 + val sinh = (Math.pow(Math.E, c.imaginary) - Math.pow(Math.E, c.imaginary * -1)) / 2 + val a = Math.cos(c.real) * cosh + val b = Math.sin(c.real) * sinh + + val imCos: String + if (b < 0) + imCos = imGetExcelStr(a) + "+" + imGetExcelStr(Math.abs(b)) + c.suffix + else + imCos = imGetExcelStr(a) + "-" + imGetExcelStr(b) + c.suffix + + val pstr = PtgStr(imCos) + if (DEBUG) Logger.logInfo("Result from IMCOS= " + pstr.string) + return pstr + } + + /** + * IMDIV + * Returns the quotient of two complex numbers, defined as (deep breath): + * + * + * (r1 + i1j)/(r2 + i2j) == ( r1r2 + i1i2 + (r2i1 - r1i2)i ) / (r2^2 + i2^2) + */ + internal fun calcImDiv(operands: Array): Ptg { + if (operands.size < 2) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "IMDIV") + val complexString1 = StringTool.allTrim(operands[0].string) + val complexString2 = StringTool.allTrim(operands[1].string) + + val c1: Complex + val c2: Complex + + try { + c1 = imParseComplexNumber(complexString1) + c2 = imParseComplexNumber(complexString2) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + val divisor = Math.pow(c2.real, 2.0) + Math.pow(c2.imaginary, 2.0) + val a = c1.real * c2.real + c1.imaginary * c2.imaginary + val b = c1.imaginary * c2.real - c1.real * c2.imaginary + val c = a / divisor + val d = b / divisor + + val imDiv: String + if (d > 0) + imDiv = imGetExcelStr(c) + "+" + imGetExcelStr(d) + "i" + else + imDiv = imGetExcelStr(c) + "-" + imGetExcelStr(d) + "i" + + val pstr = PtgStr(imDiv) + if (DEBUG) Logger.logInfo("Result from IMDIV= " + pstr.string) + return pstr + } + + /** + * IMEXP + * Returns the exponential of a complex number + */ + internal fun calcImExp(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImExp") + val complexString = StringTool.allTrim(operands[0].string) + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // Exponential of a complex number x+yi = e^x(cos(y) + sin(y)i) + val e_x = Math.pow(Math.E, c.real) + val a = e_x * Math.cos(c.imaginary) + val b = e_x * Math.sin(c.imaginary) + + val imExp: String + if (b > 0) + imExp = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix + else + imExp = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix + + val pstr = PtgStr(imExp) + if (DEBUG) Logger.logInfo("Result from IMEXP= " + pstr.string) + return pstr + } + + /** + * IMLN + * Returns the natural logarithm of a complex number + */ + internal fun calcImLn(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImLn") + val complexString = StringTool.allTrim(operands[0].string) + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // Natural log of a complex number= + // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i + var a = Math.pow(c.real, 2.0) + Math.pow(c.imaginary, 2.0) + a = Math.sqrt(a) + a = Math.log(a) + val b = Math.atan(c.imaginary / c.real) + + val imLn: String + if (b > 0) + imLn = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix + else + imLn = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix + + val pstr = PtgStr(imLn) + if (DEBUG) Logger.logInfo("Result from IMLN= " + pstr.string) + return pstr + } + + /** + * IMLOG10 + * Returns the base-10 logarithm of a complex number + */ + internal fun calcImLog10(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImLog10") + val complexString = StringTool.allTrim(operands[0].string) + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // Natural log of a complex number= + // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i + var a = Math.pow(c.real, 2.0) + Math.pow(c.imaginary, 2.0) + a = Math.sqrt(a) + a = Math.log(a) + var b = Math.atan(c.imaginary / c.real) + // now, convert to base 10 log: + val logE = Math.log(Math.E) / Math.log(10.0) + a = a * logE + b = b * logE + + val imLog10: String + if (b > 0) + imLog10 = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix + else + imLog10 = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix + + val pstr = PtgStr(imLog10) + if (DEBUG) Logger.logInfo("Result from IMLOG10= " + pstr.string) + return pstr + } + + /** + * IMLOG2 + * Returns the base-2 logarithm of a complex number + */ + internal fun calcImLog2(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImLog2") + val complexString = StringTool.allTrim(operands[0].string) + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // Natural log of a complex number= + // IMLN(x + yi)= ln(sqrt(x2+y2)) + atan(y/x)i + var a = Math.pow(c.real, 2.0) + Math.pow(c.imaginary, 2.0) + a = Math.sqrt(a) + a = Math.log(a) + var b = Math.atan(c.imaginary / c.real) + // now, convert to base 2 log: + val logE = Math.log(Math.E) / Math.log(2.0) + a = a * logE + b = b * logE + // TODO: Results only correct to 8th precision: WHY??? + val imLog2: String + if (b > 0) + imLog2 = imGetExcelStr(a, 8) + "+" + imGetExcelStr(b, 8) + c.suffix + else + imLog2 = imGetExcelStr(a, 8) + imGetExcelStr(b, 8) + c.suffix + + val pstr = PtgStr(imLog2) + if (DEBUG) Logger.logInfo("Result from IMLOG2= " + pstr.string) + return pstr + } + + /** + * IMPOWER + * Returns a complex number raised to any power + */ + internal fun calcImPower(operands: Array): Ptg { + if (operands.size < 2) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImPower") + val complexString = StringTool.allTrim(operands[0].string) + val n = operands[1].doubleVal + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // A complex number (x + yi) raised to a power n is: + // sqrt(x^2 + y^2)*cos(n*atan(y/x)) + sqrt(x^2 + y^2)*sin(n*atan(y/x))i + var r = Math.pow(c.real, 2.0) + Math.pow(c.imaginary, 2.0) + r = Math.sqrt(r) + r = Math.pow(r, n) + val t = Math.atan(c.imaginary / c.real) + val a = r * Math.cos(n * t) + val b = r * Math.sin(n * t) + + val imPower: String + if (b > 0) + imPower = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix + else + imPower = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix + + val pstr = PtgStr(imPower) + if (DEBUG) Logger.logInfo("Result from IMPOWER= " + pstr.string) + return pstr + } + + /** + * IMPRODUCT + * Returns the product of two complex numbers + */ + internal fun calcImProduct(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + + if (DEBUG) debugOperands(operands, "IMPRODUCT") + val ops = PtgCalculator.getAllComponents(operands) + val complexStrings = arrayOfNulls(ops.size) + for (i in ops.indices) + complexStrings[i] = StringTool.allTrim(ops[i].string) + + val c = arrayOfNulls(complexStrings.size) + for (i in complexStrings.indices) { + try { + c[i] = imParseComplexNumber(complexStrings[i]) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + } + + // basically, linear binomial multiplication over n terms + // (a + bi)(c + di) = (ac-bd) + (ad+bc)i for n terms + for (i in 1 until c.size) { + val a = c[0].real + val b = c[0].imaginary + c[0].real = a * c[i].real - b * c[i].imaginary + c[0].imaginary = a * c[i].imaginary + b * c[i].real + } + + // Format Result + val imSum: String + if (c[0].imaginary > 0) + imSum = imGetExcelStr(c[0].real) + "+" + imGetExcelStr(c[0].imaginary) + c[0].suffix + else + imSum = imGetExcelStr(c[0].real) + imGetExcelStr(c[0].imaginary) + c[0].suffix + + val pstr = PtgStr(imSum) + if (DEBUG) Logger.logInfo("Result from IMSPRODUCT= " + pstr.string) + return pstr + } + + /** + * IMREAL + * Returns the real coefficient of a complex number + */ + internal fun calcImReal(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "IMREAL") + val complexString = StringTool.allTrim(operands[0].string) + + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + val pnum = PtgNumber(c.real) + if (DEBUG) Logger.logInfo("Result from IMREAL= " + pnum.string) + return pnum + } + + /** + * IMSIN + * Returns the sine of a complex number + */ + internal fun calcImSin(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImSin") + val complexString = StringTool.allTrim(operands[0].string) + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // sin(a + bi)= sin(a)cosh(b) + cos(a)sinh(b)i (Excel doc is wrong!) + val cosh = (Math.pow(Math.E, c.imaginary) + Math.pow(Math.E, c.imaginary * -1)) / 2 + val sinh = (Math.pow(Math.E, c.imaginary) - Math.pow(Math.E, c.imaginary * -1)) / 2 + val a = Math.sin(c.real) * cosh + val b = Math.cos(c.real) * sinh + + val imSin: String + if (b > 0) + imSin = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix + else + imSin = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix + + val pstr = PtgStr(imSin) + if (DEBUG) Logger.logInfo("Result from IMSIN= " + pstr.string) + return pstr + } + + /** + * IMSQRT + * Returns the square root of a complex number + */ + internal fun calcImSqrt(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ImSqrt") + val complexString = StringTool.allTrim(operands[0].string) + val c: Complex + try { + c = imParseComplexNumber(complexString) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // The square root of a complex number (x + yi) is: + // sqrt(sqrt(x^2 + y^2))*cos(atan(y/x)/2) + + // sqrt(sqrt(x^2 + y^2))*sin(atan(y/x)/2)i + var r = Math.pow(c.real, 2.0) + Math.pow(c.imaginary, 2.0) + r = Math.sqrt(r) + r = Math.sqrt(r) + val t = Math.atan(c.imaginary / c.real) + val a = r * Math.cos(t / 2) + val b = r * Math.sin(t / 2) + + val imSqrt: String + if (b > 0) + imSqrt = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c.suffix + else + imSqrt = imGetExcelStr(a) + imGetExcelStr(b) + c.suffix + + val pstr = PtgStr(imSqrt) + if (DEBUG) Logger.logInfo("Result from IMSQRT= " + pstr.string) + return pstr + } + + + /** + * IMSUB + * Returns the difference of two complex numbers + */ + internal fun calcImSub(operands: Array): Ptg { + if (operands.size < 2) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "IMSUB") + val complexString1 = StringTool.allTrim(operands[0].string) + val complexString2 = StringTool.allTrim(operands[1].string) + + val c1: Complex + val c2: Complex + try { + c1 = imParseComplexNumber(complexString1) + c2 = imParseComplexNumber(complexString2) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + // basically, linear binomial subtraction: + // (a + bi) - (c + di)= (a-c) + (b-d)i + val a = c1.real - c2.real + val b = c1.imaginary - c2.imaginary + + val imSub: String + if (b > 0) + imSub = imGetExcelStr(a) + "+" + imGetExcelStr(b) + c1.suffix // should have the same suffix + else + imSub = imGetExcelStr(a) + imGetExcelStr(b) + c1.suffix + + val pstr = PtgStr(imSub) + if (DEBUG) Logger.logInfo("Result from IMSUB= " + pstr.string) + return pstr + } + + /** + * IMSUM + * Returns the sum of complex numbers + */ + internal fun calcImSum(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + + if (DEBUG) debugOperands(operands, "IMSUM") + val ops = PtgCalculator.getAllComponents(operands) + val complexStrings = arrayOfNulls(ops.size) + for (i in ops.indices) + complexStrings[i] = StringTool.allTrim(ops[i].string) + + val c = arrayOfNulls(complexStrings.size) + for (i in complexStrings.indices) { + try { + c[i] = imParseComplexNumber(complexStrings[i]) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + } + + // basically, linear binomial addition over n terms + // (a + bi)+(c + di) = (a+c) + (b+d)i for n terms + for (i in 1 until c.size) { + c[0].real = c[0].real + c[i].real + c[0].imaginary = c[0].imaginary + c[i].imaginary + } + + // Format Result + val imSum: String + if (c[0].imaginary > 0) + imSum = imGetExcelStr(c[0].real) + "+" + imGetExcelStr(c[0].imaginary) + c[0].suffix + else + imSum = imGetExcelStr(c[0].real) + imGetExcelStr(c[0].imaginary) + c[0].suffix + + val pstr = PtgStr(imSum) + if (DEBUG) Logger.logInfo("Result from IMSUM= " + pstr.string) + return pstr + } + + /** + * OCT2BIN + * Converts an octal number to binary + */ + internal fun calcOct2Bin(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "OCT2BIN") + val l = operands[0].doubleVal.toLong() // avoid sci notation + val oString = l.toString().trim { it <= ' ' } + // 10 digits at most (=30 bits) + if (oString.length > 10) return PtgErr(PtgErr.ERROR_NUM) + var places = 0 + if (operands.size > 1) + places = operands[1].intVal + + var dec: Long + var bString: String + try { + dec = java.lang.Long.parseLong(oString, 8) + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 536870912L) + // 2^29 (= 30 bits, signed) + dec -= 1073741824L // 2^30 (= 31 bits, signed) + bString = java.lang.Long.toBinaryString(dec) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + if (dec < -512 /*7777777000*/ || dec > 511 || places < 0) return PtgErr(PtgErr.ERROR_NUM) + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + bString = bString.substring(Math.max(bString.length - 10, 0)) + } else if (places > 0) { + if (bString.length > places) return PtgErr(PtgErr.ERROR_NUM) + bString = "0000000000$bString" // maximum= 10 places + bString = bString.substring(bString.length - places) + } + val pstr = PtgStr(bString) + if (DEBUG) Logger.logInfo("Result from OCT2BIN= " + pstr.string) + return pstr + } + + /** + * OCT2DEC + * Converts an octal number to decimal + */ + internal fun calcOct2Dec(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "OCT2DEC") + val l = operands[0].doubleVal.toLong() // avoid sci notation + val oString = l.toString().trim { it <= ' ' } + // 10 digits at most (=30 bits) + if (oString.length > 10) return PtgErr(PtgErr.ERROR_NUM) + var dec: Long + try { + dec = java.lang.Long.parseLong(oString, 8) + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 536870912L) + // 2^29 (= 30 bits, signed) + dec -= 1073741824L // 2^30 (= 31 bits, signed) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + val pnum = PtgNumber(dec.toDouble()) + if (DEBUG) Logger.logInfo("Result from OCT2DEC= " + pnum.`val`) + return pnum + } + + /** + * OCT2HEX + * Converts an octal number to hexadecimal + */ + internal fun calcOct2Hex(operands: Array): Ptg { + if (operands.size < 1) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "OCT2HEX") + val l = operands[0].doubleVal.toLong() // avoid sci notation + val oString = l.toString().trim { it <= ' ' } + // 10 digits at most (=30 bits) + if (oString.length > 10) return PtgErr(PtgErr.ERROR_NUM) + var places = 0 + if (operands.size > 1) + places = operands[1].intVal + + var dec: Long + var hString: String + try { + dec = java.lang.Long.parseLong(oString, 8) + // must det. manually if binary string is negative because parseInt/parseLong does not + // handle two's complement input!!! + if (dec >= 536870912L) + // 2^29 (= 30 bits, signed) + dec -= 1073741824L // 2^30 (= 31 bits, signed) + hString = java.lang.Long.toHexString(dec).toUpperCase() + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NUM) + } + + if (dec < 0) { // truncate to 10 digits automatically (should already be two's complement) + hString = hString.substring(Math.max(hString.length - 10, 0)) + } else if (places > 0) { + if (hString.length > places) return PtgErr(PtgErr.ERROR_NUM) + hString = "0000000000$hString" // maximum= 10 bits + hString = hString.substring(hString.length - places) + } + + val pstr = PtgStr(hString) + if (DEBUG) Logger.logInfo("Result from OCT2HEX= " + pstr.string) + return pstr + } + + /* + SQRTPI + Returns the square root of (number * PI) +*/ + internal fun debugOperands(operands: Array, f: String) { + if (DEBUG) { + Logger.logInfo("Operands for $f") + for (i in operands.indices) { + val s = operands[i].string + if (operands[i] !is PtgMissArg) { + val v = operands[i].value.toString() + Logger.logInfo("\tOperand[$i]=$s $v") + } else + Logger.logInfo("\tOperand[$i]=$s is Missing") + } + } + } + +} + +internal class Complex { + var real: Double = 0.toDouble() + var imaginary: Double = 0.toDouble() + var suffix: String + + constructor() { + this.suffix = "i" + } + + constructor(r: Double, i: Double) { + this.real = r + this.imaginary = i + this.suffix = "i" + } +} + diff --git a/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.java deleted file mode 100644 index 06445c1..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.java +++ /dev/null @@ -1,3455 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.DateConverter; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.toolkit.Logger; - -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.Vector; - - -/* - * FinancialCalculator is a collection of static methods that operate as the - * Microsoft Excel function calls do. - * - * All methods are called with an array of ptg's, which are then acted upon. A - * Ptg of the type that makes sense (ie boolean, number) is returned after each - * method. - */ - -public class FinancialCalculator { - public static boolean DEBUG = false; - - /* - * Basis - * - * 0, default= US (NASD) 30/360 - As with the European 30/360, with the - * additional provision that if the end date occurs on the 31st of a month - * it is moved to the 1st of the next month if the start date is earlier - * than the 30th. 1= Uses the exact number of elapsed days between the two - * dates, as well as the exact length of the year. 2= Uses the exact number - * of elapsed days between two dates but assumes the year only have 360 days - * 3- Uses the exact number of elapsed days between two dates but assumes - * the year always has 365 days 4= European 30/360 - Each month is assumed - * to have 30 days, such that the year has only 360 days. Start and end - * dates that occur on the 31st of a month become equal to the 30th of the - * same month. - * - * 30/360: If the accrual period ends on a 31st, do not change the date - * unless the period started on a 30th or 31st, in which case change the end - * date to 30th. In addition, if the accrual period ends on the last day of - * February, the month of February should not be extended to a 30 day month. - * 30/Actual: Method whereby interest is calculated based on a 30-day month - * and the assumed number of days in a year, i.e. the actual number of days - * in the accrual period multiplied by the number of interest payments in - * the year. Eg, a semi-annual bond (one paying two coupons per year) can - * display a period between coupons of 181 to 184 days. In this case, the - * number of days in a year will be 362 to 368 days. - * - * Euro: Method whereby interest is calculated based on a 30-day month (no - * exceptions, ie, February should always be extended to a 30 day month) and - * a 360-day year. - * - * Actual: Method whereby interest is calculated based on the actual number - * of accrued days (falling on a normal year) divided by 365, added to the - * actual number of accrued days (falling on a leap year) divided by 366. - * - * Actual/Actual: Is used for Treasury bonds and notes. This convention it - * refers to an interest accrual method that utilizes the actual number of - * days in a month and the actual number of days in a year. - * - * Actual/360: A day count fraction equal to actual days divided by 360 - * except in the - * United Kingdom and several countries where the denominator is 365 or - * actual days. It is used for bank deposits and in calculating rates pegged - * to some indices, such as LIBOR. 30/360 Rules: It is used for corporate - * bonds, U.S. Agency bonds and all mortgage backed securities. It assumes - * that all months have 30 days, and all years have 360 days. The number of - * days from M1/D1/Y1 to M2/D2/Y2 is computed according to the following - * procedure: If D1 is 31, change D1 to 30. If D2 is 31 and D1 is 30 or 31, - * then change D2 to 30. If M1 is 2, and D1 is 28 (in a non-leap year) or - * 29, then change D1 to 30. Then the number of days, N is: N = 360(Y2-Y1) + - * 30(M2-M1) + (D2-D1). - */ - static double yearFrac(int basis, long date0, long date1) { - double result; - // deep breath ... - GregorianCalendar fromDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(new Long(date0)); - GregorianCalendar toDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(new Long(date1)); - int y0 = fromDate.get(Calendar.YEAR); - int y1 = toDate.get(Calendar.YEAR); - int d0 = fromDate.get(Calendar.DAY_OF_MONTH); - int d1 = toDate.get(Calendar.DAY_OF_MONTH); - int m0 = fromDate.get(Calendar.MONTH); - int m1 = toDate.get(Calendar.MONTH); - double yearFrac = 1.0; - if (basis == 0) { // 30/360 US/NASD - if (d0 == 31) - d0 = 30; - if (d1 == 31 && d0 >= 30) - d1 = 30; - if (m0 == 1 && d0 >= 28) - d0 = 30; - yearFrac = (double) (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)) / 360.0; - } else if (basis == 1) { // Actual/Actual - int ndays = 0; - int i; // average # days between dates - for (i = y0; i <= y1; i++) { - ndays += isLeapYear(i) ? 366 : 365; - } - if (i != y0) - yearFrac = (double) (date1 - date0) / ((double) ndays / (i - y0)); // yes I know it's redundant ... - else - yearFrac = date1 - date0; - } else if (basis == 2) { // Actual/360 - yearFrac = (double) (date1 - date0) / 360.0; - } else if (basis == 3) { // Actual/365 - yearFrac = (double) (date1 - date0) / 365.0; - } else if (basis == 4) { // 30/360 EURO - // if (m0==1 && d0>=28) d0= 30; //??????????????????????????? - // if (m1==1 && d1>=28) d1= 30; //??????????????????????????? - yearFrac = (double) (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)) / 360.0; - } - return yearFrac; - } - - static double getDaysInYearFromBasis(int basis, long date0, long date1) { - double r = 0; - switch (basis) { - case 0: // 30/360 - case 2: // actual/360 - case 4: // 30/360 (EURO) - r = 360; - break; - case 1: // actual/actual - GregorianCalendar fromDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date0)); - int y0 = fromDate.get(Calendar.YEAR); - if (isLeapYear(y0)) - r = 365.4; - else - r = 365.25; -// r= (date1-date0)/yearFrac(basis, date0, date1); - break; - case 3: - r = 365; // actual/365 - break; - } - return r; - } - - static long getDaysFromBasis(int basis, long date0, long date1) { - if (basis == 0 || basis == 4) { // # months * 30 + extra days - GregorianCalendar fromDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date0)); - GregorianCalendar toDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date1)); - int y0 = fromDate.get(Calendar.YEAR); - int y1 = toDate.get(Calendar.YEAR); - int d0 = fromDate.get(Calendar.DAY_OF_MONTH); - int d1 = toDate.get(Calendar.DAY_OF_MONTH); - int m0 = fromDate.get(Calendar.MONTH); - int m1 = toDate.get(Calendar.MONTH); - if (basis == 0) { // 30/360 US/NASD - // TODO: PROBLEM: When date should be 2-28-02, get - // 2-2-02!!!!!!!!!!!!!!! - if (d0 == 31) - d0 = 30; - if (d1 == 31 && d0 >= 30) - d1 = 30; - // if (m0==1 && d0 >=28) d0= 30; // - // ?????????????????????????????????????????????????? - // if (m1 == 1 && d1 >= 28) - // d1 = 30; // ????????????????? - } else { // 30/360 EURO -- CORRECT FOR ALL DATES EXCEPT MONTH OF - // FEBRUARY (M0)!! - // if (m0==1 && d0>=28) d0= 30; // ????????????????????? - // if (m1==1 && d1>=28) d1= 30; // ????????????????????? - } - int result = (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)); - return result; - } - return date1 - date0; // actual (1, 2, 3) - } - - static int validateDay(int y, int m, int d) { - if (d > 28) { // m is 0-based - if (m == 1) // TODO: Get maximum for year - d = 28; - else if ((m == 3 || m == 5 || m == 8 || m == 10) && d == 31) - d = 30; - } - return d; - } - - /** - * ACCRINT - Returns the accrued interest for a security that pays periodic - * interest Analysis Pak function Issue is the security's issue date. - * First_interest is the security's first interest date. Settlement is the - * security's settlement date. The security settlement date is the date - * after the issue date when the security is traded to the buyer. Rate is - * the security's annual coupon rate. Par is the security's par value. If - * you omit par, ACCRINT uses $1,000. Frequency is the number of coupon - * payments per year. For annual payments, frequency = 1; for semiannual, - * frequency = 2; for quarterly, frequency = 4. Basis is the type of day - * count basis to use. - *

                      - * Basis Day count basis 0 or omitted US (NASD) 30/360 1 Actual/actual 2 - * Actual/360 3 Actual/365 4 European 30/360 - *

                      - * ACCRINT is calculated as follows: - *

                      - * par X rate/frequency X Sum Ai/NLi over NC - *

                      - * where: - *

                      - * Ai = number of accrued days for the ith quasi-coupon period within odd - * period. NC = number of quasi-coupon periods that fit in odd period. If - * this number contains a fraction, raise it to the next whole number. NLi = - * normal length in days of the ith quasi-coupon period within odd period. - */ - // TODO: Works only for basis 0 & 4 - protected static Ptg calcAccrint(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ACCRINT."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "ACCRINT"); - try { - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar fiDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - double rate = operands[3].getDoubleVal(); - double par = 1000; - if (!(operands[4] instanceof PtgMissArg)) - par = operands[4].getDoubleVal(); - int frequency = operands[5].getIntVal(); - int basis = 0; - if (operands.length > 6) - basis = operands[6].getIntVal(); - - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstInterestDate = (new Double(DateConverter.getXLSDateVal(fiDate))).longValue(); - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - - // TODO: if dates are not valid, return #VALUE! error - if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM); - if (rate <= 0 || par <= 0) return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (issueDate >= settlementDate) return new PtgErr(PtgErr.ERROR_NUM); - - // quasicoupon period: extend the series of equal payment periods to - // before or after the actual payment periods - // odd period= period between payments that differs from the usual - // equally spaced periods at which payments are made - // Ai= number of days which have accrued for the ith quasi-coupon period - // within the odd period - // equation: ACCRINT= ((par*rate)/frequency)* Sum over NC (Ai/NLi) - // where NC= # of quasi-coupon periods, rounded up - // Ai= # days in odd period i - // NLi=normal # days in odd period i - // For cases where Previous Coupon Date < Issue Date, NC=1, A= getDaysFromBasis and NLi= coupDays - - double result = (par * rate / frequency);//* Sum #Accrued days for - double sum = 0; - long E, PCD; - - // ACCRINT in EXCEL handles this case, so we should, too! - if (firstInterestDate < settlementDate) { - if (DEBUG) Logger.logInfo(">>> S > FI!"); -/* Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(firstInterestDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - - long x= PtgCalculator.getLongValue(calcCoupNCD(ops)); -*/ -// ????????????????????????????????? - E = 181; - PCD = issueDate; - } else { - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(firstInterestDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - - - E = PtgCalculator.getLongValue(calcCoupDays(ops)); - PCD = PtgCalculator.getLongValue(calcCoupPCD(ops)); - } -// testing - if (PCD == 0) { - PCD = issueDate + 1; -//Logger.logInfo(">>PCD==0"); - } - long A = 0; - if (basis == 0 || basis == 4) - // correct for basis 0, 4 - // INCORRECT for basis 1,2 and 3 - A = getDaysFromBasis(basis, issueDate, settlementDate); - else if (issueDate >= PCD) - // correct when issue >= PCD - A = getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate); - else { -// ??????????????????????????????????????????????? - A = getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate); - if (DEBUG) Logger.logInfo(">>I < PCD"); - } - - - result *= A / (double) E; - - PtgNumber pnum = new PtgNumber(result); - // TODO: Complete Accrint Alogorithm - if (DEBUG) Logger.logInfo("Result from Accrint= " + result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * ACCRINTM Returns the accrued interest for a security that pays interest - * at maturity Analysis Pak function - *

                      - * Issue is the security's issue date. Maturity is the security's maturity - * date. Rate is the security's annual coupon rate. Par is the security's - * par value. If you omit par, ACCRINTM uses $1,000. Basis is the type of - * day count basis to use. 0 or omitted US (NASD) 30/360 1 Actual/actual 2 - * Actual/360 3 Actual/365 4 European 30/360 ACCRINTM = par X rate x A/D - * where: A = Number of accrued days counted according to a monthly basis. - * For interest at maturity items, the number of days from the issue date to - * the maturity date is used. D = Annual Year Basis. - */ - protected static Ptg calcAccrintm(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "ACCRINTM"); - try { - long issueDate, maturityDate; // dates are truncated to integers - double rate, par = 1000; - int basis = 0; - - // Issue Date - GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - issueDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue(); - // Maturity Date - dt = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - maturityDate = (new Double(DateConverter.getXLSDateVal(dt))) - .longValue(); - // Annual Coupon Rate - rate = operands[2].getDoubleVal(); - // Par value. If omitted, = 1000. - if (operands.length > 3 && (!(operands[3] instanceof PtgMissArg))) - par = operands[3].getDoubleVal(); - // Basis. If omitted, = 0 - if (operands.length > 4) - basis = operands[4].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (rate <= 0 || par <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (issueDate > maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - - double result = par * rate * yearFrac(basis, issueDate, maturityDate); - if (DEBUG) Logger.logInfo("Result from Accrintm= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * AMORDEGRC Returns the depreciation for each accounting period - */ - protected static Ptg calcAmordegrc(Ptg[] operands) { - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "AMORDEGRC"); - try { - double cost = operands[0].getDoubleVal(); - GregorianCalendar dP = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar fP = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - double salvage = operands[3].getDoubleVal(); - int period = operands[4].getIntVal(); - double rate = operands[5].getDoubleVal(); - int basis = operands[6].getIntVal(); - - long datePurchased = (new Double(DateConverter.getXLSDateVal(dP))).longValue(); - long firstPeriod = (new Double(DateConverter.getXLSDateVal(fP))).longValue(); - - // TODO: if dates are not valid, return #VALUE! error - if (rate <= 0) return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4 || basis == 2) return new PtgErr(PtgErr.ERROR_NUM); - if (datePurchased > firstPeriod) return new PtgErr(PtgErr.ERROR_NUM); - - double coefficient; - double life = 1.0 / rate; - if (life < 3) - coefficient = 1; - else if (life < 5.0) // between 3 and 4 years - coefficient = 1.5; - else if (life <= 6.0) // between 5 and 6 years - coefficient = 2.0; - else - // more than 6 years - coefficient = 2.5; - - rate *= coefficient; - //cost-= Math.round(yearFrac(basis, datePurchased, - // firstPeriod)*rate*cost); - cost -= yearFrac(basis, datePurchased, firstPeriod) * rate * cost; - double Remainder = cost - salvage; - double A = 0; - if (Remainder > 0) { - for (int i = 0; i < period; i++) { - //A= Math.round(rate*cost); - A = rate * cost; - Remainder -= A; - if (Remainder < 0) { - /* - * if (period==i+1) { // A= Math.round(0.5*cost); - * Remainder=A; } - */ - if (i + 1 < period) - A = 0; - i = period; // exit loop - } - - cost -= A; - } - } else - A = Math.round(rate * salvage); - double result = Math.max(Math.round(A), 0); - if (DEBUG) - Logger.logInfo("Result from AMORDEGRC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * AMORLINC Returns the depreciation for each accounting period - */ - protected static Ptg calcAmorlinc(Ptg[] operands) { - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "AMORLINC"); - try { - double cost = operands[0].getDoubleVal(); - GregorianCalendar dP = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar fP = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[2].getValue()); - double salvage = operands[3].getDoubleVal(); - int period = operands[4].getIntVal(); - double rate = operands[5].getDoubleVal(); - int basis = operands[6].getIntVal(); - - long datePurchased = (new Double(DateConverter.getXLSDateVal(dP))) - .longValue(); - long firstPeriod = (new Double(DateConverter.getXLSDateVal(fP))) - .longValue(); - - // TODO: if dates are not valid, return #VALUE! error - if (rate <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4 || basis == 2) - return new PtgErr(PtgErr.ERROR_NUM); - if (datePurchased > firstPeriod) - return new PtgErr(PtgErr.ERROR_NUM); - - double A = 0; - double B = cost - salvage; - double C = yearFrac(basis, datePurchased, firstPeriod) * rate * cost; - double D = cost * rate; - long n = Math.round((cost - salvage - C) / D); - if (period == 0 || C == 0) - A = C; - else if (period < n) - A = D; - else if (period == n) - A = Math.min(D + (B - D * n - C), D); - else if (period == n + 1) - A = B - D * n - C; - else - A = 0; -/* - cost -= yearFrac(basis, datePurchased, firstPeriod) * rate * cost; - if (Remainder > 0) { - for (int i = 0; i < period; i++) { - //A= Math.round(rate*cost); - A = rate * cost; - Remainder -= A; - if (Remainder < 0) { - /* - * if (period==i+1) { // A= Math.round(0.5*cost); - } - * / - if (i + 1 < period) - A = 0; - i = period; // exit loop - } - cost -= A; - } - } else - A = Math.round(rate * salvage); -*/ - double result = Math.max(Math.round(A), 0); -/* - double A = cost * rate; - double B = cost - salvage; - //double yf= yearFrac(basis, datePurchased, firstPeriod); - double C = yearFrac(basis, datePurchased, firstPeriod) * rate * cost; - long n = Math.round((cost - salvage - C) / A); - double result; - if (period == 0 || C == 0) - result = C; - else if (period < n) - result = A; - else if (period == n) - result = A - C; //B - A * n - C; - else if (period == n + 1) - result = A - C; //B - A * n - C; - else - result = 0; -*/ - if (DEBUG) - Logger.logInfo("Result from AMORLINC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPDAYBS -- - *

                      - * Returns the number of days from the beginning of the coupon period to the - * settlement date Settlement is the security's settlement date. The - * security settlement date is the date after the issue date when the - * security is traded to the buyer. Maturity is the security's maturity - * date. The maturity date is the date when the security expires. Frequency - * is the number of coupon payments per year. For annual payments, frequency = - * 1; for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is - * the type of day count basis to use. (optional) - */ - protected static Ptg calcCoupDayBS(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "COUPDAYSBS"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) return new PtgErr(PtgErr.ERROR_NUM); - - long pcd = PtgCalculator.getLongValue(calcCoupPCD(operands)); - - double result = getDaysFromBasis(basis, pcd, settlementDate); - - // - - - if (DEBUG) - Logger.logInfo("Result from calcCoupDaysBS= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPDAYS Returns the number of days in the coupon period that contains - * the settlement date Settlement is the security's settlement date. The - * security settlement date is the date after the issue date when the - * security is traded to the buyer Maturity is the security's maturity date. - * The maturity date is the date when the security expires. Frequency is the - * number of coupon payments per year. For annual payments, frequency = 1; - * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the - * type of day count basis to use - */ - protected static Ptg calcCoupDays(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "COUPDAYS"); - try { - GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - long settlementDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue(); - dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - long maturityDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue(); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) return new PtgErr(PtgErr.ERROR_NUM); - - // VERY strange, but seems to be correct - double result; - if (basis == 1) { // actual/actual - long pcd = PtgCalculator.getLongValue(calcCoupPCD(operands)); - long ncd = PtgCalculator.getLongValue(calcCoupNCD(operands)); - result = getDaysFromBasis(basis, pcd, ncd); - } else if (basis == 0 || basis == 2 || basis == 4) - result = 360.0 / frequency; - else - result = 365.0 / frequency; - if (DEBUG) - Logger.logInfo("Result from calcCoupDays=" + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPDAYSNC - Returns the number of days from the settlement date to the - * next coupon date - *

                      - * Settlement is the security's settlement date. The security settlement - * date is the date after the issue date when the security is traded to the - * buyer Maturity is the security's maturity date. The maturity date is the - * date when the security expires. Frequency is the number of coupon - * payments per year. For annual payments, frequency = 1; for semiannual, - * frequency = 2; for quarterly, frequency = 4. Basis is the type of day - * count basis to use - */ - protected static Ptg calcCoupDaysNC(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "COUPDAYSNC"); - long settlementDate = new Long(operands[0].getValue().toString()).longValue(); - long maturityDate = new Long(operands[1].getValue().toString()).longValue(); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - - long ncd = PtgCalculator.getLongValue(calcCoupNCD(operands)); - - double result = getDaysFromBasis(basis, settlementDate, ncd); - if (DEBUG) - Logger.logInfo("Result from calcCoupDaysNC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * COUPNCD Returns the next coupon date after the settlement date Returns a - * number that represents the next coupon date after the settlement date. - * COUPNCD(settlement,maturity,frequency,basis) Important Dates should be - * entered by using the DATE function, or as results of other formulas or - * functions. For example, use DATE(2008,5,23) for the 23rd day of May, - * 2008. Problems can occur if dates are entered as text. Settlement is the - * security's settlement date. The security settlement date is the date - * after the issue date when the security is traded to the buyer. Maturity - * is the security's maturity date. The maturity date is the date when the - * security expires. Frequency is the number of coupon payments per year. - * For annual payments, frequency = 1; for semiannual, frequency = 2; for - * quarterly, frequency = 4. Basis is the type of day count basis to use. - */ - protected static Ptg calcCoupNCD(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "COUPNCD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - - GregorianCalendar resultDate; - int mm = mDate.get(Calendar.MONTH) + 1; // months are 0-based but calc - // needs 1-based - for now! - int sm = sDate.get(Calendar.MONTH) + 1; - int y = mDate.get(Calendar.YEAR); - int d = mDate.get(Calendar.DAY_OF_MONTH); - - if (frequency == 1) { // annual - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - y--; - } - y++; - } - if (frequency == 2) { // semi-annual - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - mm -= 6; - if (mm < 1) { - mm += 12; - y--; - } - } - mm += 6; - } else if (frequency == 4) { // quarterly - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - mm -= 3; - if (mm < 1) { - mm += 12; - y--; - } - } - mm += 3; - } - resultDate = new GregorianCalendar(y, mm - 1, d); - double date = DateConverter.getXLSDateVal(resultDate); - if (DEBUG) - Logger.logInfo("Result from calcCoupNCD= " - + date - + " " - + java.text.DateFormat.getDateInstance().format( - resultDate.getTime())); - int i = (int) date; - PtgInt pi = new PtgInt(i); - return pi; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPNUM Returns the number of coupons payable between the settlement date - * and maturity date Settlement is the security's settlement date. The - * security settlement date is the date after the issue date when the - * security is traded to the buyer Maturity is the security's maturity date. - * The maturity date is the date when the security expires. Frequency is the - * number of coupon payments per year. For annual payments, frequency = 1; - * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the - * type of day count basis to use - */ - protected static Ptg calcCoupNum(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "COUPNUM"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); -/* - double result = getDaysFromBasis(basis, settlementDate, maturityDate) - / (getDaysInYearFromBasis(basis, settlementDate, maturityDate) / frequency); - result = Math.ceil(result); - double delta= maturityDate-settlementDate; - double result= delta/calcCoupDays(operands)); -*/ - double result = Math.ceil(yearFrac(basis, settlementDate, maturityDate) * frequency); - if (DEBUG) - Logger.logInfo("Result from calcCoupNUM= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * COUPPCD Returns the previous coupon date before the settlement date - */ - protected static Ptg calcCoupPCD(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "COUPPCD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - int frequency = operands[2].getIntVal(); - int basis = 0; - if (operands.length > 3) - basis = operands[3].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - - GregorianCalendar resultDate; - int mm = mDate.get(Calendar.MONTH) + 1; // months are 0-based but calc - // needs 1-based - for now! - int sm = sDate.get(Calendar.MONTH) + 1; - int y = mDate.get(Calendar.YEAR); - int d = mDate.get(Calendar.DAY_OF_MONTH); - - if (frequency == 1) { // annual - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - y--; - } - } - if (frequency == 2) { // semi-annual - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - mm -= 6; - if (mm < 1) { - mm += 12; - y--; - } - } - } else if (frequency == 4) { // quarterly - while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) { - mm -= 3; - if (mm < 1) { - mm += 12; - y--; - } - } - } - resultDate = new GregorianCalendar(y, mm - 1, d); - double date = DateConverter.getXLSDateVal(resultDate); - if (DEBUG) - Logger.logInfo("Result from calcCoupPCD= " - + date - + " " - + java.text.DateFormat.getDateInstance().format( - resultDate.getTime())); - int i = (int) date; - PtgInt pi = new PtgInt(i); - return pi; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * CUMIPMT Returns the cumulative interest paid between two periods - * CUMIPMT(rate,nper,pv,start_period,end_period,type) All parameters are - * require Rate is the interest rate. Nper is the total number of payment - * periods. Pv is the present value. Start_period is the first period in the - * calculation. Payment periods are numbered beginning with 1. End_period is - * the last period in the calculation. Type is the timing of the payment. - *

                      - * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the - * beginning of the period - */ - protected static Ptg calcCumIPmt(Ptg[] operands) { - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - double rate = operands[0].getDoubleVal(); - double nper = operands[1].getDoubleVal(); - double pv = operands[2].getDoubleVal(); - int startperiod = operands[3].getIntVal(); - int endperiod = operands[4].getIntVal(); - int type = operands[5].getIntVal(); - - if (DEBUG) - debugOperands(operands, "CUMIPMT"); - if (rate <= 0 || pv <= 0 || nper <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (startperiod < 1 || endperiod < 1) - return new PtgErr(PtgErr.ERROR_NUM); - if (startperiod > endperiod) - return new PtgErr(PtgErr.ERROR_NUM); - if (type < 0 || type > 1) - return new PtgErr(PtgErr.ERROR_NUM); - - // CumIPMT= pmt*period - FV for start-1 - pmt - FV for end period and - // pmt - double A, B; - // PMT used in fv calc - double Rn = Math.pow(1 + rate, nper); - A = -pv * Rn * rate; - B = (Rn - 1) * (1 + rate * type); - double pmt = A / B; - - // WORKS on everything BUT type=1 AND startperiod=1 !!!!!! - double n = startperiod - 1 - type; - int period = endperiod - startperiod + 1; - - // FVa (StartPeriod) - A = Math.pow(1 + rate, n); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n) - 1) / rate; - double fva = -(pv * A + B); - // FVb (endPeriod) - A = Math.pow(1 + rate, endperiod - type); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, endperiod - type) - 1) / rate; - double fvb = -(pv * A + B); - - double result = fva - fvb - (pmt * period); //- (fva - fvb); - if (startperiod == 1 && type == 1) - result = (pmt * period) + pv; // I'm sure there's a good reason for - // this!?!?! - result *= -1; - if (DEBUG) - Logger.logInfo("Result from calcCumIPmt= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * CUMPRINC Returns the cumulative principal paid on a loan between - * start_period and end_period. Syntax - *

                      - * CUMPRINC(rate,nper,pv,start_period,end_period,type) - *

                      - * Rate is the interest rate. Nper is the total number of payment periods. - * Pv is the present value. Start_period is the first period in the - * calculation. Payment periods are numbered beginning with 1. End_period is - * the last period in the calculation. Type is the timing of the payment. - * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the - * beginning of the period - */ - protected static Ptg calcCumPrinc(Ptg[] operands) { - if (operands.length < 6) { - return new PtgErr(PtgErr.ERROR_NULL); - } - double rate = operands[0].getDoubleVal(); - double nper = operands[1].getDoubleVal(); - double pv = operands[2].getDoubleVal(); - int startperiod = operands[3].getIntVal(); - int endperiod = operands[4].getIntVal(); - int type = operands[5].getIntVal(); - - if (DEBUG) - debugOperands(operands, "CUMPRINC"); - // Cumprinc= FV for start-1 and pmt - FV for end period and pmt - double A, B; - // PMT used in fv calc - double Rn = Math.pow(1 + rate, nper); - A = -pv * Rn * rate; - B = (Rn - 1) * (1 + rate * type); - double pmt = A / B; - - // FVa (StartPeriod) - A = Math.pow(1 + rate, startperiod - type - 1); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, startperiod - type - 1) - 1) / rate; - double fva = -(pv * A + B); - // FVb (endPeriod) - A = Math.pow(1 + rate, endperiod - type); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, endperiod - type) - 1) / rate; - double fvb = -(pv * A + B); - - double result = fva - fvb; - if (startperiod == 1 && type == 1) - result = pv; // I'm sure there's a good reason for this!?!?! - if (DEBUG) - Logger.logInfo("Result from calcCUMPRINC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * DB Returns the depreciation of an asset for a specified period using the - * fixed-declining balance method - *

                      - * DB(cost,salvage,life,period,month) - *

                      - * Cost is the initial cost of the asset. - *

                      - * Salvage is the value at the end of the depreciation (sometimes called the - * salvage value of the asset). - *

                      - * Life is the number of periods over which the asset is being depreciated - * (sometimes called the useful life of the asset). - *

                      - * Period is the period for which you want to calculate the depreciation. - * Period must use the same units as life. - *

                      - * Month is the number of months in the first year. If month is omitted, it - * is assumed to be 12. - */ - - protected static Ptg calcDB(Ptg[] operands) { - if (operands.length < 4 || operands[0].getComponents() != null) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - double cost, salvage; - int life, period, month; - cost = new Double(String.valueOf(operands[0].getValue())).doubleValue(); - salvage = new Double(String.valueOf(operands[1].getValue())) - .doubleValue(); - life = Integer.valueOf(String.valueOf(operands[2].getValue())).intValue(); - period = Integer.valueOf(String.valueOf(operands[3].getValue())).intValue(); - if (operands.length > 4) { - if (operands[4] instanceof PtgMissArg) { - month = 12; - } else { - month = Integer.valueOf(String.valueOf(operands[4].getValue())) - .intValue(); - } - } else { - month = 12; - } - double salCost = salvage / cost; - // this section longhand due to some wierd calcs when calling lifdiv = - // 1/life; - double lifdiv = 1; - lifdiv /= life; - double rate = Math.pow(salCost, lifdiv); - rate = 1 - rate; - rate = rate * 1000; - rate = Math.round(rate); - rate /= 1000; - - double totalDepreciation = cost * rate * month / 12; - double result = totalDepreciation; - // 1st and last (i.e. period==life) are special cases - for (int i = 2; i < period || (i == period && period <= life); i++) { - result = (cost - totalDepreciation) * rate; - totalDepreciation += (cost - totalDepreciation) * rate; - } - if (period > life) // last depreciation is special calc - result = (cost - totalDepreciation) * rate * (12 - month) / 12; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * DDB Returns the depreciation of an asset for a spcified period using the - * double-declining balance method or some other method you specify - *

                      - * DDB(cost,salvage,life,period,factor) - *

                      - * Cost is the initial cost of the asset. - *

                      - * Salvage is the value at the end of the depreciation (sometimes called the - * salvage value of the asset). - *

                      - * Life is the number of periods over which the asset is being depreciated - * (sometimes called the useful life of the asset). - *

                      - * Period is the period for which you want to calculate the depreciation. - * Period must use the same units as life. - *

                      - * Factor is the rate at which the balance declines. If factor is omitted, - * it is assumed to be 2 (the double-declining balance method). - *

                      - * All five arguments must be positive numbers. - */ - protected static Ptg calcDDB(Ptg[] operands) { - if (operands.length < 4 || operands[0].getComponents() != null) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - double cost, salvage; - int life, period, factor; - cost = new Double(String.valueOf(operands[0].getValue())).doubleValue(); - salvage = new Double(String.valueOf(operands[1].getValue())) - .doubleValue(); - life = Integer.valueOf(String.valueOf(operands[2].getValue())).intValue(); - period = Integer.valueOf(String.valueOf(operands[3].getValue())).intValue(); - factor = 2; - if (operands.length > 4) { - if (!(operands[4] instanceof PtgMissArg)) - factor = Integer.valueOf(String.valueOf(operands[4].getValue())) - .intValue(); - } - double salCost = salvage / cost; - // this section longhand due to some wierd calcs when calling lifdiv = - // 1/life; - double facLife = factor / ((double) life); - double totalDepreciation = 0; -/* original calc - for (int i = 1; i < period; i++) { - //((cost-salvage) - total depreciation from prior periods) * (factor/life) - totalDepreciation += (cost - salvage - totalDepreciation)*facLife; - } -// double result= (cost - salvage - totalDepreciation) * (facLife); - * - */ - for (int i = 1; i < period; i++) { - totalDepreciation += (cost - totalDepreciation) * facLife; - } - double result = 0.0; - if (cost - salvage - totalDepreciation > 0) - result = (cost - totalDepreciation) * facLife; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * DISC Returns the discount rate for a security - *

                      - * Settlement is the security's settlement date. The security settlement - * date is the date after the issue date when the security is traded to the - * buyer. Maturity is the security's maturity date. The maturity date is the - * date when the security expires. Pr is the security's price per $100 face - * value. Redemption is the security's redemption value per $100 face value. - * Basis (optional) is the type of day count basis to use. - */ - protected static Ptg calcDISC(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcDISC"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - double pr = operands[2].getDoubleVal(); - double redemption = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (settlementDate > maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - if (pr <= 0 || redemption <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - -// double DSM = maturityDate - settlementDate; -// double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); -// double result = (redemption - pr) / redemption * (B / DSM); - double result = (redemption - pr) / (redemption * yearFrac(basis, settlementDate, maturityDate)); - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcDISC= " + result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * DOLLARDE Converts a dollar price, expressed as a fraction, into a dollar - * price, expressed as a decimal number Fractional_dollar is a number - * expressed as a fraction. Fraction is the integer to use in the - * denominator of the fraction. - */ - protected static Ptg calcDollarDE(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcDOLLARDE"); - double fractional_dollar = operands[0].getDoubleVal(); - int fraction = operands[1].getIntVal(); - if (fraction < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (fraction == 0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - - int n = String.valueOf(fraction).length(); - double x = Math.floor(fractional_dollar); - double y = (fractional_dollar - x) * Math.pow(10, n); - double result = x + y / fraction; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcDOLLARDE= " + result); - return pnum; - } - - /** - * DOLLARFR Converts a dollar price, expressed as a decimal number, into a - * dollar price, expressed as a fraction Decimal_dollar is a decimal number. - * Fraction is the integer to use in the denominator of a fraction - */ - protected static Ptg calcDollarFR(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcDOLLARFR"); - double decimal_dollar = operands[0].getDoubleVal(); - int fraction = operands[1].getIntVal(); - if (fraction < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (fraction == 0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - - int n = String.valueOf(fraction).length(); - - double x = Math.floor(decimal_dollar); - double y = (decimal_dollar - x); - double result = x + (y * fraction) / Math.pow(10, n); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcDOLLARFR= " + result); - return pnum; - } - - /** - * DURATION Returns the annual duration of a security with periodic interest - * payments - * DURATION(settlement,maturity,coupon,yld,frequency,basis) - * Settlement is the security's settlement date. Maturity is the security's - * maturity date. Coupon is the security's annual coupon rate. Yld is the - * security's annual yield. Frequency is the number of coupon payments per - * year. For annual payments, frequency = 1; for semiannual, frequency = 2; - * for quarterly, frequency = 4. Basis is the type of day count basis to use - */ - protected static Ptg calcDURATION(Ptg[] operands) { - if (operands.length < 5) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcDURATION"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double coupon = operands[2].getDoubleVal(); - double yld = operands[3].getDoubleVal(); - int frequency = operands[4].getIntVal(); - int basis = 0; - if (operands.length > 5) - basis = operands[5].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - if (coupon < 0 || yld < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - - Ptg[] ops = new Ptg[4]; - ops[0] = operands[0]; - ops[1] = operands[1]; - ops[2] = operands[4]; - ops[3] = new PtgInt(basis); - - /* - * Duration= (A + SumC)/(D+ SumB * 1/frequency - * - * where - * Y= 1+ yield/frequency - * R= 100*rate - * F= DSC/E - * A= (F*100)/Y^(n-1+F) - * SumC= Sum(1,n): R/(frequency*Y^(i-1+F)) * (i-1+F) - * D= 100/Y^(n-1+F) - * SumB= Sum(1,n): R/(frequency*Y^(i-1+F)) - * - */ - double n = calcCoupNum(ops).getDoubleVal(); - double DSC = calcCoupDaysNC(ops).getDoubleVal(); - double E = calcCoupDays(ops).getDoubleVal(); - double F = DSC / E; - double R = coupon * 100; - double Y = 1 + yld / frequency; - double Yx = Math.pow(Y, n - 1 + F); - double SumA = 0; - for (int i = 1; i <= n; i++) { - SumA += R * (i - 1 + F) / (Math.pow(Y, i - 1 + F) * frequency); - } - double SumB = 0; - for (int i = 1; i <= n; i++) { - SumB += R / (Math.pow(Y, i - 1 + F) * frequency); - } - double C = 0.0, D = 0.0; - if (n > 1) { - C = ((n - 1 + F) * 100) / Yx; - D = 100 / Yx; - } - double result = (SumA + C) / ((SumB + D) * frequency); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from calcDURATION= " + result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * EFFECT Returns the effective annual interest rate Nominal_rate is the - * nominal interest rate. Npery is the number of compounding periods per - * year - */ - protected static Ptg calcEffect(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcEFFECT"); - double nominal_rate = operands[0].getDoubleVal(); - int npery = operands[1].getIntVal(); - // TODO: If either argument is non-numeric, #VALUE! error - if (npery <= 0 || npery < 1) // funny guard!!!! - return new PtgErr(PtgErr.ERROR_NUM); - /* - * // KSC: TESTING for (int j= 1; j <= 10; j++) { double i; if ((j % 2) == - * 0) i= 1; else i= .5; npery= j; while (i <= 10) { nominal_rate= i / - * 100; - * - * - * double x= nominal_rate / npery; double result= Math.pow(1 + x, npery) - - * 1; Logger.logInfo("nominal_rate= " + nominal_rate + " npery= " + - * npery + " result= " + result); java.math.BigDecimal bd= new - * java.math.BigDecimal(i-Math.floor(i)).setScale(5, - * java.math.BigDecimal.ROUND_HALF_UP); if (((bd.doubleValue()*100) % 4) == - * 0) // if ((((i-Math.floor(i))*100) % 4)==0) i+= 0.3; else i+= 0.25; } } - */ - double x = nominal_rate / npery; - double result = Math.pow(1 + x, npery) - 1; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from calcEFFECT= " + result); - return pnum; - } - - /** - * FV Returns the future value of an investment FV(rate,nper,pmt,pv,type) - *

                      - *

                      - * Rate is the interest rate per period. Nper is the total number of payment - * periods in an annuity. Pmt is the payment made each period; it cannot - * change over the life of the annuity . Typically, pmt contains principal - * and interest but no other fees or taxes. If pmt is omitted, you must - * include the pv argument. Pv is the present value, or the lump-sum amount - * that a series of future payments is worth right now. If pv is omitted, it - * is assumed to be 0 (zero), and you must include the pmt argument. Type is - * the number 0 or 1 and indicates when payments are due. If type is - * omitted, it is assumed to be 0. - */ - protected static Ptg calcFV(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcFV"); - double rate = operands[0].getDoubleVal(); - int nper = operands[1].getIntVal(); - double pmt = operands[2].getDoubleVal(); - double pv = 0; - int type = 0; - if (operands.length > 3 && !(operands[3] instanceof PtgMissArg)) - pv = operands[3].getDoubleVal(); - if (operands.length > 4) - type = operands[4].getIntVal(); - - double A = Math.pow(1 + rate, nper); - double B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, nper) - 1) / rate; - double result = -(pv * A + B); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcFV= " + result); - return pnum; - - } - - /** - * FVSCHEDULE Returns the future value of an initial principal after - * applying a series of compound interest rates - */ - protected static Ptg calcFVSCHEDULE(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "calcFVSCHEDULE"); - double principal = operands[0].getDoubleVal(); - Ptg[] schedule = PtgCalculator.getAllComponents(operands[1]); - if (DEBUG) debugOperands(schedule, "calcFVSCHEDULE"); // AFTER converting - // references ... - double result = 1.0; - for (int i = 0; i < schedule.length; i++) { - result *= principal + schedule[i].getDoubleVal(); - } - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcFVSCHEDULE= " + result); - return pnum; - } - - /** - * INTRATE Returns the interest rate for a fully invested security - */ - protected static Ptg calcINTRATE(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcINTRATE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double investment = operands[2].getDoubleVal(); - double redemption = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (investment <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); -// double delta = maturityDate - settlementDate; -// double result = ((redemption - investment) / investment) * ((getDaysInYearFromBasis(basis, settlementDate, maturityDate) / delta)); - double result = ((redemption - investment) / investment) / yearFrac(basis, settlementDate, maturityDate); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcINTRATE= " + result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * IPMT Returns the interest payment for an investment for a given period. - *

                      - * Syntax IPMT(rate,per,nper,pv,fv,type) - *

                      - * Rate is the interest rate per period. Per is the period for which you - * want to find the interest and must be in the range 1 to nper. Nper is the - * total number of payment periods in an annuity. Pv is the present value, - * or the lump-sum amount that a series of future payments is worth right - * now. Fv is the future value, or a cash balance you want to attain after - * the last payment is made. If fv is omitted, it is assumed to be 0 (the - * future value of a loan, for example, is 0). Type is the number 0 or 1 and - * indicates when payments are due. If type is omitted, it is assumed to be - * 0. - */ - protected static Ptg calcIPMT(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcIPMT"); - double rate = operands[0].getDoubleVal(); - double per = operands[1].getDoubleVal(); - double nper = operands[2].getDoubleVal(); - double pv = operands[3].getDoubleVal(); - double fv = 0; - int type = 0; - if (operands.length > 4 && !(operands[4] instanceof PtgMissArg)) - fv = operands[4].getDoubleVal(); - if (operands.length > 5) - type = operands[5].getIntVal(); - - if (per < 0 || per > nper) - return new PtgErr(PtgErr.ERROR_NUM); - - // IPMT= pmt- (fv(b) - fv(a) i.e. = payement less the principal balance - // btwn two periods - - double n; - if (type == 0) - n = per; - else - n = per - 1; - // PMT - double Rn = Math.pow(1 + rate, nper); - double A = -fv * rate - pv * Rn * rate; - double B = (Rn - 1) * (1 + rate * type); - double pmt = A / B; - // FVa - A = Math.pow(1 + rate, n); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n) - 1) / rate; - double fva = -(pv * A + B); - // FVb - A = Math.pow(1 + rate, n - 1); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n - 1) - 1) / rate; - double fvb = -(pv * A + B); - - double result = pmt - (fvb - fva); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcIPMT= " + result); - return pnum; - } - - /** - * IRR Returns the internal rate of return for a series of cash flows - * Returns the internal rate of return for a series of cash flows - * represented by the numbers in values. These cash flows do not have to be - * even, as they would be for an annuity. However, the cash flows must occur - * at regular intervals, such as monthly or annually. The internal rate of - * return is the interest rate received for an investment consisting of - * payments (negative values) and income (positive values) that occur at - * regular periods. - *

                      - * SEE: NPV - *

                      - * Syntax IRR(values,guess) - *

                      - * Values is an array or a reference to cells that contain numbers for which - * you want to calculate the internal rate of return. - *

                      - * Values must contain at least one positive value and one negative value to - * calculate the internal rate of return. - *

                      - * IRR uses the order of values to interpret the order of cash flows. Be - * sure to enter your payment and income values in the sequence you want. - *

                      - * If an array or reference argument contains text, logical values, or empty - * cells, those values are ignored. Guess is a number that you guess is - * close to the result of IRR. - *

                      - * Microsoft Excel uses an iterative technique for calculating IRR. Starting - * with guess, IRR cycles through the calculation until the result is - * accurate within 0.00001 percent. If IRR can't find a result that works - * after 20 tries, the #NUM! error value is returned. - *

                      - * In most cases you do not need to provide guess for the IRR calculation. - * If guess is omitted, it is assumed to be 0.1 (10 percent). - *

                      - * If IRR gives the #NUM! error value, or if the result is not close to what - * you expected, try again with a different value for guess - */ - protected static Ptg calcIRR(Ptg[] operands) { - if (operands.length < 1 || operands[0].getComponents() == null) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcIRR"); - double guess = .1; - if (operands.length > 1) - guess = operands[1].getDoubleVal(); - Ptg[] params = PtgCalculator.getAllComponents(operands); - if (DEBUG) - debugOperands(params, "calcIRR"); // AFTER converting references ... - int n = params.length; - - // examine values array, sum all outflows (= negative values) + inflows - // (= positive values) - double outflow = 0.0, inflow = 0.0; - // get outflow (- values) - for (int i = 0; i < n; i++) { - double val = params[i].getDoubleVal(); - if (val < 0) - outflow += Math.abs(val); - else - inflow += val; - } - if (outflow == 0.0 || inflow == 0.0) - return new PtgErr(PtgErr.ERROR_VALUE); // TODO: Excel doesn't specify which - // error to return - - // iterate over possible irr values; value is correct when - // outflow-pv <= tolerance, defined as .00001% - final double TOLERANCE = 0.0000001; - boolean bIsCorrect = false; - double xl, xh, fl, fh, f, trial = guess; - xl = 0; - xh = guess; - double delta = xh - xl; - fl = outflow - inflow; - fh = outflow; - for (int i = 0; i < n; i++) { - double val = params[i].getDoubleVal(); - if (val > 0) { - fh -= val / (Math.pow(1 + xh, i)); - } - } - for (int j = 0; j < 50 && !bIsCorrect; j++) { // maximum 20 tries - need - // more!!!! - trial = xl + delta * fl / (fl - fh); - f = outflow; - for (int i = 0; i < n; i++) { - double val = params[i].getDoubleVal(); - if (val > 0) { - f -= val / (Math.pow(1 + trial, i)); - } - } - - if (f < 0) { - delta = xl - trial; - xl = trial; - fl = f; - } else { - delta = xh - trial; - xh = trial; - fh = f; - } - bIsCorrect = (Math.abs(delta) <= TOLERANCE); - delta = xh - xl; - } - if (!bIsCorrect) - return new PtgErr(PtgErr.ERROR_NUM); - - if (DEBUG) - Logger.logInfo("Result from calcIRR= " + trial); - PtgNumber pnum = new PtgNumber(trial); - return pnum; - } - - /** - * ISPMT Calculates the interest paid during a specific period of an - * investment. This function is provided for compatibility with Lotus 1-2-3. - * ISPMT(rate,per,nper,pv) Rate is the interest rate for the investment. Per - * is the period for which you want to find the interest, and must be - * between 1 and nper. Nper is the total number of payment periods for the - * investment. Pv is the present value of the investment. For a loan, pv is - * the loan amount. - */ - protected static Ptg calcISPMT(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcISPMT"); - double rate = operands[0].getDoubleVal(); - double per = operands[1].getDoubleVal(); - double nper = operands[2].getDoubleVal(); - double pv = operands[3].getDoubleVal(); - if (per < 0 || per > nper) - return new PtgErr(PtgErr.ERROR_NUM); - - double result = (-pv * rate * (nper - per)) / nper; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcISPMT= " + result); - return pnum; - } - - /** - * MDURATION Returns the Macauley modified duration for a security with an - * assumed par value of $100 - * DURATION(settlement,maturity,coupon,yld,frequency,basis) Settlement is - * the security's settlement date. Maturity is the security's maturity date. - * Coupon is the security's annual coupon rate. Yld is the security's annual - * yield. Frequency is the number of coupon payments per year. For annual - * payments, frequency = 1; for semiannual, frequency = 2; for quarterly, - * frequency = 4. Basis is the type of day count basis to use - */ - protected static Ptg calcMDURATION(Ptg[] operands) { - if (operands.length < 5) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcMDURATION"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double coupon = operands[2].getDoubleVal(); - double yld = operands[3].getDoubleVal(); - int frequency = operands[4].getIntVal(); - int basis = 0; - if (operands.length > 5) - basis = operands[5].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - if (coupon < 0 || yld < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (!(frequency == 1 || frequency == 2 || frequency == 4)) - return new PtgErr(PtgErr.ERROR_NUM); - - double result = calcDURATION(operands).getDoubleVal(); - // above is regular duration calculation; to get modified duration: - result = result / (1 + yld / frequency); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcMDURATION= " + result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * MIRR Returns the internal rate of return where positive and negative cash - * flows are financed at different rates - *

                      - * MIRR(values,finance_rate,reinvest_rate) - *

                      - * Values is an array or a reference to cells that contain numbers. These - * numbers represent a series of payments (negative values) and income - * (positive values) occurring at regular periods. - *

                      - * Values must contain at least one positive value and one negative value to - * calculate the modified internal rate of return. Otherwise, MIRR returns - * the #DIV/0! error value. - *

                      - * If an array or reference argument contains text, logical values, or empty - * cells, those values are ignored; however, cells with the value zero are - * included. - *

                      - * Finance_rate is the interest rate you pay on the money used in the cash - * flows. - *

                      - * Reinvest_rate is the interest rate you receive on the cash flows as you - * reinvest them. - */ - protected static Ptg calcMIRR(Ptg[] operands) { - if (operands.length < 3) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcMIRR"); - double finance_rate = operands[1].getDoubleVal(); - double reinvest_rate = operands[2].getDoubleVal(); - Ptg[] params = PtgCalculator.getAllComponents(operands); - if (DEBUG) - debugOperands(params, "calcMIRR"); // AFTER converting references - // ... - - // Get + Values and - Values in separate Ptg arrays - Vector posVals = new Vector(); - Vector negVals = new Vector(); - Ptg[] positiveValues; - Ptg[] negativeValues; - int n = params.length - 2; // skip last 2 params (= rates) - for (int i = 0; i < n; i++) { - double val = params[i].getDoubleVal(); - if (val < 0) - negVals.addElement(params[i]); - else - posVals.addElement(params[i]); - } - positiveValues = new Ptg[posVals.size() + 1]; - negativeValues = new Ptg[negVals.size() + 1]; - System.arraycopy(posVals.toArray(), 0, positiveValues, 1, posVals - .size()); - System.arraycopy(negVals.toArray(), 0, negativeValues, 1, negVals - .size()); - - // add rate to Ptg array for call to calcNPV - positiveValues[0] = operands[2]; // reinvest rate - negativeValues[0] = operands[1]; // finance rate - - // Calculate MIRR from NPV values - double X = calcNPV(positiveValues).getDoubleVal(); - X = -1 * X * Math.pow(1 + reinvest_rate, posVals.size()); - double Y = calcNPV(negativeValues).getDoubleVal(); - Y = Y * (1 + finance_rate); - double result = Math.pow(X / Y, 1.0 / (n - 1)) - 1; - - if (DEBUG) - Logger.logInfo("Result from calcMIRR= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * NOMINAL Returns the annual nominal interest rate - */ - protected static Ptg calcNominal(Ptg[] operands) { - if (operands.length < 2) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcNominal"); - double effect = operands[0].getDoubleVal(); - int npery = operands[1].getIntVal(); - // TODO: if either is non-numeric, return #VALUE! - if (effect <= 0 || npery < 1) - return new PtgErr(PtgErr.ERROR_NUM); - - // solve for nominal_rate in: - // effect= (1 + nominal_rate/npery)^npery -1 - // nominal_rate= ((10^(log10(y)/n)) - 1)*npery - double y = effect + 1; - double log10y = Math.log(y) / Math.log(10); // base 10 log - double z = Math.pow(10, log10y / npery); - double result = (z - 1) * npery; - if (DEBUG) - Logger.logInfo("Result from calcNominal= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * NPER Returns the number of periods for an investment NPER(rate, pmt, pv, - * fv, type) - *

                      - * Rate is the interest rate per period. Pmt is the payment made each - * period; it cannot change over the life of the annuity. Typically, pmt - * contains principal and interest but no other fees or taxes. Pv is the - * present value, or the lump-sum amount that a series of future payments is - * worth right now. Fv is the future value, or a cash balance you want to - * attain after the last payment is made. If fv is omitted, it is assumed to - * be 0 (the future value of a loan, for example, is 0). Type is the number - * 0 or 1 and indicates when payments are due. - */ - protected static Ptg calcNPER(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcNPER"); - double rate = operands[0].getDoubleVal(); - double pmt = operands[1].getDoubleVal(); - double pv = operands[2].getDoubleVal(); - double fv = 0; - if (operands.length > 3 && !(operands[3] instanceof PtgMissArg)) - fv = operands[3].getDoubleVal(); - int type = 0; - if (operands.length > 4) - type = operands[4].getIntVal(); - - double A = pmt * (1 + type * rate) - rate * fv; - double B = pmt * (1 + type * rate) + rate * pv; - double C = 1 + rate; - double result = Math.log(A / B) / Math.log(C); - if (DEBUG) - Logger.logInfo("Result from calcNPER= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * NPV(rate,value1,value2, ...) Calculates the net present value of an - * investment by using a discount rate and a series of future payments - * (negative values) and income (positive values). - *

                      - * Rate is the rate of discount over the length of one period. Value1, - * value2, ... are 1 to 29 arguments representing the payments and income. - * Value1, value2, ... must be equally spaced in time and occur at the end - * of each period - *

                      - * Returns the net present value of an investment based on a series of - * periodic cash flows and a discount rate = Sum(values / (1 + rate) ) - */ - protected static Ptg calcNPV(Ptg[] operands) { - if (operands.length < 2 || operands[0].getComponents() != null) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) debugOperands(operands, "calcNPV"); - Ptg[] params = PtgCalculator.getAllComponents(operands); - if (DEBUG) debugOperands(params, "calcNPV"); // AFTER converting references ... - double rate = params[0].getDoubleVal(); - int n = Math.min(params.length, 30); // at most 29 values - double result = 0; - for (int i = 1; i < n; i++) { - double valuei = params[i].getDoubleVal(); - // TODO: if valuei is an error, empty, etc., ignore - result += valuei / Math.pow(1 + rate, i); - } - if (DEBUG) Logger.logInfo("Result from calcNPV= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * ODDFPRICE Returns the price per $100 face value of a security with an odd - * first period - * ODDFPRICE(settlement,maturity,issue,first_coupon,rate,yld,redemption,frequency,basis) - */ - protected static Ptg calcODDFPRICE(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ODDFPRICE."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 8) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcODDFPRICE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); - double rate = operands[4].getDoubleVal(); - double yld = operands[5].getDoubleVal(); - double redemption = operands[6].getDoubleVal(); - int frequency = operands[7].getIntVal(); - int basis = 0; - if (operands.length > 8) - basis = operands[8].getIntVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - /*maturity > first_coupon > settlement > issue */ - if (issueDate >= settlementDate || settlementDate >= firstCouponDate || - firstCouponDate >= maturityDate) return new PtgErr(PtgErr.ERROR_NUM); - - - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(maturityDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double NCD = calcCoupNCD(ops).getDoubleVal(); // next coupon after 1st coupon date????? - double DFC = firstCouponDate - settlementDate; // # days from odd first coupon to next coupon date - double z = getDaysFromBasis(basis, settlementDate, firstCouponDate); - - double R = 100 * rate / frequency; - double Y = 1 + yld / frequency; - - double result = 0.0; - if (DFC < E) { // odd short first coupon - double firstTerm = redemption / Math.pow(Y, N - 1 + DSC / E); - double secondTerm = (R * DFC / E) / Math.pow(Y, DSC / E); - double thirdTerm = 0.0; - for (int i = 2; i <= N; i++) { - thirdTerm += R / Math.pow(Y, i - 1 + DSC / E); - } - double fourthTerm = R * A / E; - result = firstTerm + secondTerm + thirdTerm - fourthTerm; - } else { // odd long first coupon - - } - - if (DEBUG) Logger.logInfo("Result from calcODDFPRICE= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /* - * ODDFYIELD Returns the yield of a security with an odd first period - * NOT COMPLETED!! - */ - protected static Ptg calcODDFYIELD(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ODDFYIELD."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 8) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcODDFYIELD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); - double rate = operands[4].getDoubleVal(); - double yld = operands[5].getDoubleVal(); - double redemption = operands[6].getDoubleVal(); - int frequency = operands[7].getIntVal(); - int basis = 0; - if (operands.length > 8) - basis = operands[8].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); - - /* - * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC - */ - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(maturityDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double DFC = getDaysFromBasis(basis, firstCouponDate, (long) A); // days from 1st odd coupon to 1st coupon - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double R = 100 * rate / frequency; - double Y = 1 + yld / frequency; - - double result = 0.0; - - if (DEBUG) Logger.logInfo("Result from calcODDFYIELD= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /* - * ODDLPRICE Returns the price per $100 face value of a security with an odd - * last period - * NOT COMPLETED!!!! - */ - protected static Ptg calcODDLPRICE(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ODDLPRICE."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 8) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "calcODDLPRICE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); - double rate = operands[4].getDoubleVal(); - double yld = operands[5].getDoubleVal(); - double redemption = operands[6].getDoubleVal(); - int frequency = operands[7].getIntVal(); - int basis = 0; - if (operands.length > 8) - basis = operands[8].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); - - /* - * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC - */ - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(maturityDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double DFC = getDaysFromBasis(basis, firstCouponDate, (long) A); // days from 1st odd coupon to 1st coupon - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double R = 100 * rate / frequency; - double Y = 1 + yld / frequency; - - double result = 0.0; - - if (DEBUG) Logger.logInfo("Result from calcODDLPRICE= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /* - * ODDLYIELD Returns the yield of a security with an odd last period - * NOT COMPLETED!! - */ - protected static Ptg calcODDLYIELD(Ptg[] operands) throws - FunctionNotSupportedException { - if (true) { - String wn = "WARNING: this version of OpenXLS does not support the formula ODDLYIELD."; - Logger.logWarn(wn); - throw new FunctionNotSupportedException(wn); - } - if (operands.length < 8) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "calcODDLYIELD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue()); - GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue()); - double rate = operands[4].getDoubleVal(); - double yld = operands[5].getDoubleVal(); - double redemption = operands[6].getDoubleVal(); - int frequency = operands[7].getIntVal(); - int basis = 0; - if (operands.length > 8) - basis = operands[8].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue(); - - /* - * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC - */ - Ptg[] ops = new Ptg[4]; - ops[0] = new PtgNumber(settlementDate); - ops[1] = new PtgNumber(maturityDate); - ops[2] = new PtgInt(frequency); - ops[3] = new PtgInt(basis); - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double DFC = getDaysFromBasis(basis, firstCouponDate, (long) A); // days from 1st odd coupon to 1st coupon - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double R = 100 * rate / frequency; - double Y = 1 + yld / frequency; - - double result = 0.0; - - if (DEBUG) Logger.logInfo("Result from calcODDLYIELD= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * PMT Returns the periodic payment for an annuity - *

                      - * pmt= - (fv + (1+rate)**nper * pv) * rate / ( ( (1+rate)**nper - 1 ) * - * (1+rate*type) ) - *

                      - * if fv or type are omitted they should be treated as 0 values. - */ - protected static Ptg calcPmt(Ptg[] operands) { - if (operands.length < 3) { // not - // supported - // by - // function - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - double rate, nper, pv, fv, type; - rate = new Double(String.valueOf(operands[0].getValue())).doubleValue(); - nper = new Double(String.valueOf(operands[1].getValue())).doubleValue(); - pv = new Double(String.valueOf(operands[2].getValue())).doubleValue(); - if (operands.length > 3) { - if (operands[3] instanceof PtgMissArg) { - fv = 0; - } else { - fv = new Double(String.valueOf(operands[3].getValue())) - .doubleValue(); - } - } else { - fv = 0; - } - if (operands.length > 4) { - if (operands[4] instanceof PtgMissArg) { - type = 0; - } else { - type = new Double(String.valueOf(operands[4].getValue())) - .doubleValue(); - } - } else { - type = 0; - } - - //KSC: For some strange odd weird reason, original calculation was off - // even though this should be exactly the same thing. Go figure! - double Rn = Math.pow(1 + rate, nper); - double A = -fv * rate - pv * Rn * rate; - double B = (Rn - 1) * (1 + rate * type); - double result = A / B; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * PPMT Returns the payment on the principal for an investment for a given - * period PPMT(rate,per,nper,pv,fv,type) Rate is the interest rate per - * period. Per specifies the period and must be in the range 1 to nper. Nper - * is the total number of payment periods in an annuity. Pv is the present - * value the total amount that a series of future payments is worth now. - * Fv is the future value, or a cash balance you want to attain after the - * last payment is made. If fv is omitted, it is assumed to be 0 (zero), - * that is, the future value of a loan is 0. Type is the number 0 or 1 and - * indicates when payments are due - */ - protected static Ptg calcPPMT(Ptg[] operands) { - if (operands.length < 4) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPPMT"); - double rate = operands[0].getDoubleVal(); - int per = operands[1].getIntVal(); - int nper = operands[2].getIntVal(); - double pv = operands[3].getDoubleVal(); - double fv = 0; - int type = 0; - if (operands.length > 4 && !(operands[4] instanceof PtgMissArg)) - fv = operands[4].getDoubleVal(); - if (operands.length > 5) - type = operands[5].getIntVal(); - - double result; - // 1st, get payment for entire period - double Rn = Math.pow(1 + rate, nper); - double A = -fv * rate - pv * Rn * rate; - double B = (Rn - 1) * (1 + rate * type); - double pmt = A / B; - - double n; - if (type == 0) - n = per; - else - n = per - 1; - // FVa - A = Math.pow(1 + rate, n); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n) - 1) / rate; - double fva = -(pv * A + B); - // FVb - A = Math.pow(1 + rate, n - 1); - B = pmt * (1 + rate * type); - B *= (Math.pow(1 + rate, n - 1) - 1) / rate; - double fvb = -(pv * A + B); - - result = fvb - fva; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPPMT= " + result); - return pnum; - - } - - /** - * PRICE Returns the price per $100 face value of a security that pays - * periodic interest - * PRICE(settlement,maturity,rate,yld,redemption,frequency,basis) Settlement - * is the security's settlement date. The security settlement date is the - * date after the issue date when the security is traded to the buyer. - * Maturity is the security's maturity date. The maturity date is the date - * when the security expires. Rate is the security's annual coupon rate. Yld - * is the security's annual yield. Redemption is the security's redemption - * value per $100 face value. Frequency is the number of coupon payments per - * year. For annual payments, frequency = 1; for semiannual, frequency = 2; - * for quarterly, frequency = 4. Basis is the type of day count basis to - * use. - */ - protected static Ptg calcPRICE(Ptg[] operands) { - if (operands.length < 6) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPRICE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - double rate = operands[2].getDoubleVal(); - double yld = operands[3].getDoubleVal(); - double redemption = operands[4].getDoubleVal(); - int frequency = operands[5].getIntVal(); - int basis = 0; - if (operands.length > 6) - basis = operands[6].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - if (yld < 0 || rate < 0) - return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - - /* - * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC - */ - Ptg[] ops = new Ptg[4]; - ops[0] = operands[0]; - ops[1] = operands[1]; - ops[2] = operands[5]; - ops[3] = new PtgInt(basis); - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period in which settlementfalls - double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - double R = rate / frequency; - double Y = 1 + yld / frequency; - double result = redemption / Math.pow(Y, N - 1 + DSC / E); - for (int i = 1; i <= N; i++) { - result += (R * 100) / Math.pow(Y, i - 1 + DSC / E); - } - result -= (100 * R * A / E); - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPRICE= " + result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * PRICEDISC Returns the price per $100 face value of a discounted security - *

                      - * PRICEDISC(settlement,maturity,discount,redemption,frequency,basis) - * Settlement is the security's settlement date. Maturity is the security's - * maturity date. Discount is the security's discount rate. Redemption is - * the security's redemption value per $100 face value. Basis is the type of - * day count basis to use. - */ - protected static Ptg calcPRICEDISC(Ptg[] operands) { - if (operands.length < 4) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPRICEDISC"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double discount = operands[2].getDoubleVal(); - double redemption = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) - return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) - return new PtgErr(PtgErr.ERROR_NUM); - if (redemption <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - - /* - * Ptg[] ops; if (operands.length > 6) ops = new Ptg[4]; else ops = new - * Ptg[3]; ops[0] = operands[0]; ops[1] = operands[1]; ops[2] = - * operands[5]; if (operands.length > 6) ops[3] = operands[6]; - */ - double result = redemption - - (discount * redemption * yearFrac(basis, settlementDate, - maturityDate)); - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPRICEDISC= " + result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * PRICEMAT Returns the price per $100 face value of a security that pays - * interest at maturity - */ - protected static Ptg calcPRICEMAT(Ptg[] operands) { - if (operands.length < 4) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPRICEMAT"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[2].getValue()); - double rate = operands[3].getDoubleVal(); - double yld = operands[4].getDoubleVal(); - int basis = 0; - if (operands.length > 5) - basis = operands[5].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (rate < 0 || yld < 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue(); - - double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); - double DSM = getDaysFromBasis(basis, settlementDate, maturityDate); - double DIM = getDaysFromBasis(basis, issueDate, maturityDate); - double A = getDaysFromBasis(basis, issueDate, settlementDate); - - double result = (100 + (DIM / B) * rate * 100) / (1 + ((DSM / B) * yld)); - result -= (A / B) * rate * 100; - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPRICEMAT= " + result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * PV(rate,nper,pmt,fv,type) Returns the present value of an investment Rate - * is the interest rate per period. For example, if you obtain an automobile - * loan at a 10 percent annual interest rate and make monthly payments, your - * interest rate per month is 10%/12, or 0.83%. You would enter 10%/12, or - * 0.83%, or 0.0083, into the formula as the rate. Nper is the total number - * of payment periods in an annuity. For example, if you get a four-year car - * loan and make monthly payments, your loan has 4*12 (or 48) periods. You - * would enter 48 into the formula for nper. Pmt is the payment made each - * period and cannot change over the life of the annuity. Typically, pmt - * includes principal and interest but no other fees or taxes. For example, - * the monthly payments on a $10,000, four-year car loan at 12 percent are - * $263.33. You would enter -263.33 into the formula as the pmt. If pmt is - * omitted, you must include the fv argument. Fv is the future value, or a - * cash balance you want to attain after the last payment is made. If fv is - * omitted, it is assumed to be 0 (the future value of a loan, for example, - * is 0). For example, if you want to save $50,000 to pay for a special - * project in 18 years, then $50,000 is the future value. You could then - * make a conservative guess at an interest rate and determine how much you - * must save each month. If fv is omitted, you must include the pmt - * argument. Type is the number 0 or 1 and indicates when payments are due. - * 0 or omitted At the end of the period 1 At the beginning of the period - */ - protected static Ptg calcPV(Ptg[] operands) { - if (operands.length < 3) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcPV"); - double rate = operands[0].getDoubleVal(); - double nper = operands[1].getDoubleVal(); - double pmt = 0; - // TODO: No specified error trapping? - if (!(operands[2] instanceof PtgMissArg)) - pmt = operands[2].getDoubleVal(); - double fv = 0.0; - int type = 0; - if (operands.length > 3 && !(operands[3] instanceof PtgMissArg)) - fv = operands[3].getDoubleVal(); - if (operands.length > 4) - type = operands[4].getIntVal(); - - double A = Math.pow(1 + rate, nper); - double B = pmt * (1 + rate * type); - B *= (A - 1) / rate; - double result = (-fv - B) / A; - - //double testresults= (result * A + pmt*(1+rate*type) * (A-1)/rate + fv); - //testresults must==0 for Pv to be correct - - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) - Logger.logInfo("Result from calcPV= " + result); - return pnum; - - } - - /** - * RATE Returns the interest rate per period of an annuity. RATE is - * calculated by iteration and can have zero or more solutions. If the - * successive results of RATE do not converge to within 0.0000001 after 20 - * iterations, RATE returns the #NUM! error value. - *

                      - * RATE(nper,pmt,pv,fv,type,guess) - *

                      - * Nper is the total number of payment periods in an annuity. Pmt is the - * payment made each period and cannot change over the life of the annuity. - * Typically, pmt includes principal and interest but no other fees or - * taxes. If pmt is omitted, you must include the fv argument. Pv is the - * present value the total amount that a series of future payments is - * worth now. Fv is the future value, or a cash balance you want to attain - * after the last payment is made. If fv is omitted, it is assumed to be 0 - * (the future value of a loan, for example, is 0). Type is the number 0 or - * 1 and indicates when payments are due. Guess is your guess for what the - * rate will be. - */ - protected static Ptg calcRate(Ptg[] operands) { - if (operands.length < 3) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcRate"); - double nper = operands[0].getDoubleVal(); - double pmt = operands[1].getDoubleVal(); - double pv = 0.0, fv = 0.0; - int type = 0; - double guess = 0.1; - if (!(operands[2] instanceof PtgMissArg)) - pv = operands[2].getDoubleVal(); - if (operands.length > 3) { - if (!(operands[3] instanceof PtgMissArg)) - fv = operands[3].getDoubleVal(); - } - if (operands.length > 4) { - if (!(operands[4] instanceof PtgMissArg)) - type = operands[4].getIntVal(); - } - if (operands.length > 5) - guess = operands[5].getDoubleVal(); - // validate params - if (type != 0 && type != 1) - return new PtgErr(PtgErr.ERROR_NUM); - if (pv == 0 && fv == 0) - return new PtgErr(PtgErr.ERROR_NUM); - - // iterate over possible Rate values; value is correct when - // f <= tolerance, defined as .00001% - final double TOLERANCE = 0.000000001; - boolean bIsCorrect = false; - double x0 = guess; - double x1 = x0; - double fx0; - double fprimex0; - // iterate, using Newton's approximation - for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 20 tries - // Calculate f(x) = (a+ f*g*h + c) - double R = Math.pow(1 + x0, nper); - double U = 1 / x0; - double a = pv * R; - double f = pmt * (1 + x0 * type); - double g = R - 1; - double h = U; - fx0 = a + f * g * U + fv; - // - // (a + f*g*h + c)' - // = a' + f'gh + fg'h + fgh' - double T = Math.pow(1 + x0, nper - 1); - double aprime = pv * nper * T; - double fprime = pmt * type; - double gprime = nper * T; - double hprime = -1 * Math.pow(x0, -2); - - fprimex0 = aprime + fprime * g * h + f * gprime * h + f * g * hprime; - // calculate x1, the next iteration - x1 = x0 - fx0 / fprimex0; - double delta = x1 - x0; - bIsCorrect = (Math.abs(delta) <= TOLERANCE); - x0 = x1; - } - -/* THIS CALCULATION IS OFF!!! - // x= rate - // f(x)= PV + PMT*((1-(1+x)^-NPER)/x)*(1+x)^TYPE + FV*(1+x)^-NPER = 0 - // f'(x)= - double x0, f0, trial, f; - x0 = 0; - f0 = pv * pmt + type + fv; - trial = guess; - - // iterate, using Newton's approximation - double delta; - double fprime = 0; // derivative of f(x0) - for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 20 tries - //trial= xl+delta*fl/(fl-fh); - // Calculate f(x) - double R = Math.pow(1 + trial, -nper); - double T = Math.pow(1 + trial, type); - f = pv + pmt * ((1 - R) / trial) * T + fv * R; - // Calculate f'(x) - double gofx = fv * R; - double gprimex = fv * -nper * Math.pow(1 + trial, -nper - 1); - double hofx = T; - double hprimex = type * Math.pow(1 + trial, type - 1); - double zofx = (1 - R) / trial; - double zprimex = (1 + nper * Math.pow(1 + trial, -nper - 1)) - / Math.pow(trial, 2); - fprime = zprimex * hofx + zofx * hprimex + gprimex; - delta = f / fprime; - // testing! - if (trial - delta <= 0) - delta = trial / 2; - trial -= delta; - bIsCorrect = (Math.abs(delta) <= TOLERANCE); - } -/**/ - if (!bIsCorrect) - return new PtgErr(PtgErr.ERROR_NUM); - - if (DEBUG) - Logger.logInfo("Result from calcRate= " + x1); - PtgNumber pnum = new PtgNumber(x1); - return pnum; - } - - /** - * RECEIVED Returns the amount received at maturity for a fully invested - * security - */ - protected static Ptg calcReceived(Ptg[] operands) { - if (operands.length < 4) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcRECEIVED"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double investment = operands[2].getDoubleVal(); - double rate = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - -// double DSM = maturityDate - settlementDate; -// double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); -// double result = investment / (1 - (rate * DSM / B)); - double result = investment / (1 - (rate * yearFrac(basis, settlementDate, maturityDate))); - if (DEBUG) - Logger.logInfo("Result from calcRECEIVED= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * SLN Returns the straight-line depreciation of an asset for one period - * SLN(cost,salvage,life) Cost is the initial cost of the asset. Salvage is - * the value at the end of the depreciation (sometimes called the salvage - * value of the asset). Life is the number of periods over which the asset - * is depreciated (sometimes called the useful life of the asset). - */ - protected static Ptg calcSLN(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - double cost = operands[0].getDoubleVal(); - double salvage = operands[1].getDoubleVal(); - double life = operands[2].getDoubleVal(); - if (DEBUG) - debugOperands(operands, "calcSLN"); - if (life == 0) - return new PtgErr(PtgErr.ERROR_NUM); - double result = (cost - salvage) / life; - if (DEBUG) - Logger.logInfo("Result from calcSLN= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * SYD Returns the sum-of-years' digits depreciation of an asset for a - * specified period SYD(cost,salvage,life,per) Cost is the initial cost of - * the asset. Salvage is the value at the end of the depreciation (sometimes - * called the salvage value of the asset). Life is the number of periods - * over which the asset is depreciated (sometimes called the useful life of - * the asset). Per is the period and must use the same units as life. - */ - protected static Ptg calcSYD(Ptg[] operands) { - if (operands.length < 4) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcSYD"); - double cost = operands[0].getDoubleVal(); - double salvage = operands[1].getDoubleVal(); - double life = operands[2].getDoubleVal(); - double per = operands[3].getDoubleVal(); - if (life == 0) - return new PtgErr(PtgErr.ERROR_NUM); - double A = (cost - salvage) * (life - per + 1) * 2; - double B = life * (life + 1); - double result = A / B; - if (DEBUG) - Logger.logInfo("Result from calcSYD= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * TBILLEQ Returns the bond-equivalent yield for a Treasury bill Settlement - * is the Treasury bill's settlement date. The security settlement date is - * the date after the issue date when the Treasury bill is traded to the - * buyer. Maturity is the Treasury bill's maturity date. The maturity date - * is the date when the Treasury bill expires. Discount is the Treasury - * bill's discount rate. - */ - protected static Ptg calcTBillEq(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcTBILLEQ"); - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double rate = operands[2].getDoubleVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - if (settlementDate >= maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - if (maturityDate - settlementDate > 365) - return new PtgErr(PtgErr.ERROR_NUM); - if (rate <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - double DSM = maturityDate - settlementDate; - double result; - if (DSM <= 182) { - result = (365 * rate) / (360 - rate * DSM); - } else { - double A = DSM / 365; - double B = rate * DSM; - - double C = (2 * A - 1) * B / (B - 360); - double D = Math.pow(A, 2) - C; - result = (-2 * A + 2 * Math.sqrt(D)) / (2 * A - 1); - } - if (DEBUG) - Logger.logInfo("Result from calcTBILLEQ= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * TBILLPRICE Returns the price per $100 face value for a Treasury bill - */ - protected static Ptg calcTBillPrice(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcTBILLPRICE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double rate = operands[2].getDoubleVal(); - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - - if (settlementDate >= maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - if (maturityDate - settlementDate > 365) - return new PtgErr(PtgErr.ERROR_NUM); - if (rate <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - double DSM = maturityDate - settlementDate; - double result = 100 * (1 - (rate * DSM / 360)); - if (DEBUG) - Logger.logInfo("Result from calcTBILLPRICE= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * TBILLYIELD Returns the yield for a Treasury bill - *

                      - * VDB Returns the depreciation of an asset for a specified or partial - * period using a declining balance method - */ - protected static Ptg calcTBillYield(Ptg[] operands) { - if (operands.length < 3) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcTBILLYIELD"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double price = operands[2].getDoubleVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - if (settlementDate >= maturityDate) - return new PtgErr(PtgErr.ERROR_NUM); - if (maturityDate - settlementDate > 365) - return new PtgErr(PtgErr.ERROR_NUM); - if (price <= 0) - return new PtgErr(PtgErr.ERROR_NUM); - - double DSM = maturityDate - settlementDate; - double result = ((100 - price) / price) * (360 / DSM); - if (DEBUG) - Logger.logInfo("Result from calcTBILLYIELD= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * VDB - * Returns the depreciation of an asset for any period you specify, - * including partial periods, using the double-declining balance method or - * some other method you specify. VDB stands for variable declining balance. - *

                      - * VDB(cost,salvage,life,start_period,end_period,factor,no_switch) - */ - protected static Ptg calcVDB(Ptg[] operands) { - if (operands.length < 5) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) debugOperands(operands, "calcVDB"); - double cost = operands[0].getDoubleVal(); - double salvage = operands[1].getDoubleVal(); - int life = operands[2].getIntVal(); - int start_period = operands[3].getIntVal(); - int end_period = operands[4].getIntVal(); - int factor = 2; - if (operands.length > 5 && !(operands[5] instanceof PtgMissArg)) - factor = operands[5].getIntVal(); - boolean bNoSwitch = false; - if (operands.length > 6) - bNoSwitch = PtgCalculator.getBooleanValue(operands[6]); - - if (cost <= 0 || salvage <= 0 || life <= 0 || - start_period < 0 || end_period < 0 || - factor < 0 || end_period > life) - return new PtgErr(PtgErr.ERROR_NUM); - - double result = 0.0; - - Ptg[] ops = new Ptg[5]; - ops[0] = new PtgNumber(cost); - ops[1] = new PtgNumber(salvage); - ops[2] = new PtgInt(life); - ops[4] = new PtgInt(factor); - if (bNoSwitch) { // just sum ddb - for (int i = start_period + 1; i <= end_period; i++) { - ops[3] = new PtgInt(i); - result += calcDDB(ops).getDoubleVal(); - } - } else { // switch to straight-line depreciation when dep. > ddb calc - boolean bSwitch = false; - double A = 0.0; - int i = start_period + 1; - while (i <= end_period && !bSwitch) { - double sl = (cost - A - salvage) / (life - i + 1); - ops[3] = new PtgInt(i); - double ddb = calcDDB(ops).getDoubleVal(); - if (sl <= ddb) { - A += ddb; - i++; - } else // straight-line depreciation is greater than ddb; switch - bSwitch = true; - } - result = A; - // use straight-line depreciation for rest of period - for (int j = i; j <= end_period; j++) { - result += (cost - A - salvage) / (life - i + 1); - } - } - - if (DEBUG) Logger.logInfo("Result from calcVDB= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * XIRR Returns the internal rate of return for a schedule of cash flows - * that is not necessarily periodic - */ - protected static Ptg calcXIRR(Ptg[] operands) { - if (operands.length < 2) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcXIRR"); - double guess = .1; - if (operands.length > 2) - guess = operands[2].getDoubleVal(); - // convert references to Ptg[]s - Ptg[] values = PtgCalculator.getAllComponents(operands[0]); - Ptg[] dates = PtgCalculator.getAllComponents(operands[1]); - if (DEBUG) - debugOperands(values, "calcXIRR"); - if (DEBUG) - debugOperands(dates, "calcXIRR"); - /* - * 'Newton-Raphson method: ' Given PV a function of X, determine the - * value of X ' such that SUM[PV] = 0, using iteration. ' dPVdX = - * derivative of PV with respect to X ' NB: if X = 1 + Rate, dX/dRate = - * 1 ' ' Calculate PV and dPVdX using an arbitrary initial estimate of - * X. ' Change X by -PV/dPVdX and calculate again. ' Repeat until X - * changes by less than some arbitrary small amount. - * - * Let X = 1 + Rate - * Let EXPi = -YearFrax(i) - * PVi = Val(i) * X^(EXPi) - * dPV/dX = Val(i) * EXPi * X^(EXPi- 1) - * = Val(i) * X^(EXPi) *EXPi / X - * = PVi * EXPi / X - * dPVSum/dX = SUM[dPV/dX] - * dPVSum/dX = 1/X * SUM[PVi * EXPi] - * - */ - if (values.length != dates.length) - return new PtgErr(PtgErr.ERROR_NUM); - // validate dates - double date0 = dates[0].getDoubleVal(); - for (int i = 1; i < dates.length; i++) { - // TODO: if not valid date, return PtgErr.ERROR_VALUE - long val = PtgCalculator.getLongValue(dates[i]); - if (val < date0) - return new PtgErr(PtgErr.ERROR_NUM); - } - // validate values: sum all outflows (= negative values) + inflows (= - // positive values) - double outflow = 0.0, inflow = 0.0; - // get outflow (- values) - for (int i = 0; i < values.length; i++) { - double val = values[i].getDoubleVal(); - if (val < 0) - outflow += Math.abs(val); - else - inflow += val; - } - if (outflow == 0.0 || inflow == 0.0) - return new PtgErr(PtgErr.ERROR_NUM); - - int n = values.length; - - // iterate over possible irr values; value is correct when - // f <= tolerance, defined as .000001% - boolean bIsCorrect = false; - final double TOLERANCE = 0.00000001; - double trial = guess; - double x0, f0, f; - x0 = 1; //1+0 rate, x_0, lower bounds of guess - f0 = inflow - outflow; // =sum of values when rate is 0 - trial = 1 + guess; // x_1, upper bounds - // for secant method - // double f1= 0.0; - // double x1; - // double dx= xh-xl; - double delta = 0; - double fprime = 0; // derivative of f(x0) - for (int i = 0; i < n; i++) { - double val = values[i].getDoubleVal(); - double exp = (PtgCalculator.getLongValue(dates[i]) - date0) / 365; - // f+= val*(Math.pow(xh, -exp)); - fprime += val * -exp; // f' of x0 - } - for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 100 tries - // SECANT METHOD: trial= x0+dx*f0/(f0-f1); - // NEWTON'S: - f = 0.0; - for (int i = 0; i < n; i++) { - double val = values[i].getDoubleVal(); - double exp = (dates[i].getDoubleVal() - date0) / 365.0; - f += val * (Math.pow(trial, -exp)); - fprime += val * (Math.pow(trial, -exp)) * -exp; // derivative of - // f(trial) - } - fprime = fprime / trial; // final f' expression - delta = f / fprime; - if (trial - delta <= 0) - delta = trial / 2; - trial -= delta; - /* - * secant method if (f < 0) { delta= xl-trial; xl= trial; fl= f; } - * else { delta= xh-trial; xh= trial; fh= f; } dx= xh-xl; - */ - bIsCorrect = (Math.abs(delta) <= TOLERANCE); - } - if (!bIsCorrect) - return new PtgErr(PtgErr.ERROR_NUM); - - trial -= 1; - if (DEBUG) - Logger.logInfo("Result from calcXIRR= " + trial); - PtgNumber pnum = new PtgNumber(trial); - return pnum; - } - - /** - * XNPV Returns the net present value for a schedule of cash flows that is - * not necessarily periodic - */ - protected static Ptg calcXNPV(Ptg[] operands) { - if (operands.length < 2 || operands[1].getComponents() == null) { - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcXNPV"); - double rate = operands[0].getDoubleVal(); - // convert references to Ptg[]s - Ptg[] values = PtgCalculator.getAllComponents(operands[1]); - Ptg[] dates = PtgCalculator.getAllComponents(operands[2]); - if (DEBUG) - debugOperands(values, "calcXNPV"); - if (DEBUG) - debugOperands(dates, "calcXNPV"); - - if (values.length != dates.length) - return new PtgErr(PtgErr.ERROR_NUM); - // TODO: validate dates - double date0 = dates[0].getDoubleVal(); - for (int i = 1; i < dates.length; i++) { - // TODO: if not valid date, return PtgErr.ERROR_VALUE - long val = PtgCalculator.getLongValue(dates[i]); - if (val < date0) - return new PtgErr(PtgErr.ERROR_NUM); - } - // validate values: sum all outflows (= negative values) + inflows (= - // positive values) - double outflow = 0.0, inflow = 0.0; - // get outflow (- values) - for (int i = 0; i < values.length; i++) { - double val = values[i].getDoubleVal(); - if (val < 0) - outflow += Math.abs(val); - else - inflow += val; - } - if (outflow == 0.0 || inflow == 0.0) - return new PtgErr(PtgErr.ERROR_NUM); - - int n = values.length; - double result = 0.0; - for (int i = 0; i < n; i++) { - double val = values[i].getDoubleVal(); - double exp = (dates[i].getDoubleVal() - date0) / 365.0; - result += val / (Math.pow(1 + rate, exp)); - } - if (DEBUG) - Logger.logInfo("Result from calcXNPV= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * YIELD Returns the yield on a security that pays periodic interest - *

                      - * YIELD(settlement,maturity,rate,pr,redemption,frequency,basis) Settlement - * is the security's settlement date. The security settlement date is the - * date after the issue date when the security is traded to the buyer. - * Maturity is the security's maturity date. The maturity date is the date - * when the security expires. Rate is the security's annual coupon rate. Pr - * is the security's price per $100 face value. Redemption is the security's - * redemption value per $100 face value. Frequency is the number of coupon - * payments per year. For annual payments, frequency = 1; for semiannual, - * frequency = 2; for quarterly, frequency = 4. Basis (optional) is the type - * of day count basis to use. - */ - protected static Ptg calcYIELD(Ptg[] operands) { - if (operands.length < 6) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - if (DEBUG) - debugOperands(operands, "calcINTRATE"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue()); - double rate = operands[2].getDoubleVal(); - double pr = operands[3].getDoubleVal(); - double redemption = operands[4].getDoubleVal(); - int frequency = operands[5].getIntVal(); - int basis = 0; - if (operands.length > 6) - basis = operands[6].getIntVal(); - - // TODO: if dates are not valid, return #VALUE! error - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (!sDate.before(mDate)) return new PtgErr(PtgErr.ERROR_NUM); - if (rate < 0) return new PtgErr(PtgErr.ERROR_NUM); - if (pr <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - Ptg[] ops; - ops = new Ptg[4]; - ops[0] = operands[0]; - ops[1] = operands[1]; - ops[2] = operands[5]; - ops[3] = new PtgInt(basis); - double result = 0.0; - /* - * A= coupDayBS E= coupDays n= coupNum DSC= coupDaysNC DSR= getDaysFromBasis - */ - double n = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity - double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period in which settlementfalls - double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement - - if (n <= 1) { - double DSR = getDaysFromBasis(basis, settlementDate, maturityDate); - double R = rate / frequency; - double P = pr / 100; - result = ((redemption / 100) + R) - (P + ((A / E) * R)); - result /= P + ((A / E) * R); - result *= (frequency * E) / DSR; - } else { - // for n values > 1, must employ an iterated approach to find yield using formula for price - double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from settlement to next coupon - result = yieldIteration(DSC, E, n, A, rate, frequency, redemption, pr); - if (result == -1) {// didn't find it - return new PtgErr(PtgErr.ERROR_NUM); - } - } - - PtgNumber pnum = new PtgNumber(result); - if (DEBUG) Logger.logInfo("Result from calcYIELD= " + result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - private static double yieldIteration(double DSC, double E, double n, double A, double rate, int frequency, double redemption, double pr) { - /* Using Newton-Raphson, iterate over possible yield values; value is correct when - * f <= tolerance, defined as .000001% - // 100 iterations, formula for PRICE - // each trial= f(trial)/f'(trial) - // f(trial)= price calculation - // f'(trial): (deep breath) - * - * if f= price, and - * G and F are given - * C= redemption - * B= 100*R - * E= -(n-1+F) - * Ei = -(i-1+F) - * x= 1+trial/frequency - * - * then, - * - * ui(trial)= B*x^Ei - * f'(ui(trial))= -Ei*B*x^(Ei-1) - * = Ei*B*x^Ei*1/x - * = B*x^Ei*1/x*Ei - * = ui(trial)*Ei/x - * f'(sum(ui(trial)))= sum(f'(ui)) - * = sum(ui(trial)*Ei/x - * = 1/x*sum(ui(trial)*Ei) - * - * - * f(trial)= C*x^E + sum(ui(trial)) - B*G - * f'(trial)= f'(f(x)+sum(ui(trial)) - * = f'(x) + f'(sum(ui(trial)) - * = C*E*x^(E-1) + sum(ui(trial)*Ei/x) - * = (C*E*x^E)*(1/x) + (1/x)*sum(ui(trial)*Ei) - * = (C*E*x^E)*(1/x) + (1/x)*sum(B*x^Ei*Ei) - */ - double guess = .1; - boolean bIsCorrect = false; - final double TOLERANCE = 0.00000001; - double trial = guess; - double f; - double F = DSC / E; - double G = A / E; - double R = rate / frequency; - double Y; - double B = R * 100; - double Exp = n - 1 + F; - - // N-R iteration - double fprime; // derivative of f(x0) - double delta = 0; - for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 100 tries - // f= f(trial) - Y = 1 + trial / frequency; - f = redemption / Math.pow(Y, Exp); - fprime = 0; - for (int i = 1; i <= n; i++) { - f += B / Math.pow(Y, i - 1 + F); - fprime += (B / Math.pow(Y, i - 1 + F)) * (i - 1 + F); - } - f -= (B * G); - // pr-f =>0 - fprime /= Y; // final - fprime += redemption / Math.pow(Y, Exp) * Exp * (1 / Y); - - // N-R: use f/fprime as iterative factor - delta = (pr - f) / fprime; - if (trial < delta) - trial = delta - trial; - else - trial = trial - delta; -// while (trial - delta <= 0) delta= delta/2; // sanity check -// trial-= delta; - bIsCorrect = (Math.abs(pr - f) <= TOLERANCE); - } - if (bIsCorrect) - return (trial); - else - return -1; -/* - MinYield = -1# - MaxYield = .Rate - If MaxYield = 0 Then MaxYield = 0.1 - Do While CalculatedPrice(BondInfo, MaxYield) > .Price - MaxYield = MaxYield * 2 - Loop - - - Yld = 0.5 * (MinYield + MaxYield) - For i = 1 To MaxIterations - Diff = CalculatedPrice(BondInfo, Yld) - .Price - If Abs(Diff) < Accuracy Then Exit For - 'if calculated price is greater, correct yield is greater - If Diff > 0 Then MinYield = Yld Else MaxYield = Yld - Yld = 0.5 * (MinYield + MaxYield) - Next i - End If - BondYield = Yld -*/ - } - - /** - * YIELDDISC Returns the annual yield for a discounted security. For - * example, a treasury bill - */ - protected static Ptg calcYieldDisc(Ptg[] operands) { - if (operands.length < 4) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcYIELDDISC"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - double pr = operands[2].getDoubleVal(); - double redemption = operands[3].getDoubleVal(); - int basis = 0; - if (operands.length > 4) - basis = operands[4].getIntVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue(); - - if (pr <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM); - if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM); - if (maturityDate <= settlementDate) return new PtgErr(PtgErr.ERROR_NUM); - -// double DSM = maturityDate - settlementDate; -// double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); -// double result = ((redemption - pr) / pr) * (B / DSM); - double result = ((redemption - pr) / pr) / yearFrac(basis, settlementDate, maturityDate); - if (DEBUG) - Logger.logInfo("Result from calcYIELDDISC= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * YIELDMAT Returns the annual yield of a security that pays interest at - * maturity - */ - protected static Ptg calcYieldMat(Ptg[] operands) { - if (operands.length < 5) { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - if (DEBUG) - debugOperands(operands, "calcYIELDMAT"); - try { - GregorianCalendar sDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[0].getValue()); - GregorianCalendar mDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[1].getValue()); - GregorianCalendar iDate = (GregorianCalendar) DateConverter - .getCalendarFromNumber(operands[2].getValue()); - double rate = operands[3].getDoubleVal(); - double price = operands[4].getDoubleVal(); - int basis = 0; - if (operands.length > 5) - basis = operands[5].getIntVal(); - - long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))) - .longValue(); - long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))) - .longValue(); - long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))) - .longValue(); - - double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); - double DSM = getDaysFromBasis(basis, settlementDate, maturityDate); - double DIM = getDaysFromBasis(basis, issueDate, maturityDate); - double A = getDaysFromBasis(basis, issueDate, settlementDate); - - double result = (((1 + (DIM / B) * rate) - ((price / 100) + (A / B) * rate)) / ((price / 100) + (A / B) * rate)) * (B / DSM); - if (DEBUG) Logger.logInfo("Result from calcYIELDMAT= " + result); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - static void debugOperands(Ptg[] operands, String f) { - if (DEBUG) { - Logger.logInfo("Operands for " + f); - for (int i = 0; i < operands.length; i++) { - String s = operands[i].getString(); - if (!(operands[i] instanceof PtgMissArg)) { - String v = operands[i].getValue().toString(); - Logger.logInfo("\tOperand[" + i + "]=" + s + " " + v); - } else - Logger.logInfo("\tOperand[" + i + "]=" + s + " is Missing"); - } - } - } - - public static boolean isLeapYear(int year) { - return (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0)); - } -} diff --git a/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.kt new file mode 100644 index 0000000..cafe547 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/FinancialCalculator.kt @@ -0,0 +1,3757 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.DateConverter +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.toolkit.Logger + +import java.util.Calendar +import java.util.GregorianCalendar +import java.util.Vector + + +/* + * FinancialCalculator is a collection of static methods that operate as the + * Microsoft Excel function calls do. + * + * All methods are called with an array of ptg's, which are then acted upon. A + * Ptg of the type that makes sense (ie boolean, number) is returned after each + * method. + */ + +object FinancialCalculator { + var DEBUG = false + + /* + * Basis + * + * 0, default= US (NASD) 30/360 - As with the European 30/360, with the + * additional provision that if the end date occurs on the 31st of a month + * it is moved to the 1st of the next month if the start date is earlier + * than the 30th. 1= Uses the exact number of elapsed days between the two + * dates, as well as the exact length of the year. 2= Uses the exact number + * of elapsed days between two dates but assumes the year only have 360 days + * 3- Uses the exact number of elapsed days between two dates but assumes + * the year always has 365 days 4= European 30/360 - Each month is assumed + * to have 30 days, such that the year has only 360 days. Start and end + * dates that occur on the 31st of a month become equal to the 30th of the + * same month. + * + * 30/360: If the accrual period ends on a 31st, do not change the date + * unless the period started on a 30th or 31st, in which case change the end + * date to 30th. In addition, if the accrual period ends on the last day of + * February, the month of February should not be extended to a 30 day month. + * 30/Actual: Method whereby interest is calculated based on a 30-day month + * and the assumed number of days in a year, i.e. the actual number of days + * in the accrual period multiplied by the number of interest payments in + * the year. Eg, a semi-annual bond (one paying two coupons per year) can + * display a period between coupons of 181 to 184 days. In this case, the + * number of days in a year will be 362 to 368 days. + * + * Euro: Method whereby interest is calculated based on a 30-day month (no + * exceptions, ie, February should always be extended to a 30 day month) and + * a 360-day year. + * + * Actual: Method whereby interest is calculated based on the actual number + * of accrued days (falling on a normal year) divided by 365, added to the + * actual number of accrued days (falling on a leap year) divided by 366. + * + * Actual/Actual: Is used for Treasury bonds and notes. This convention it + * refers to an interest accrual method that utilizes the actual number of + * days in a month and the actual number of days in a year. + * + * Actual/360: A day count fraction equal to actual days divided by 360 + * except in the + * United Kingdom and several countries where the denominator is 365 or + * actual days. It is used for bank deposits and in calculating rates pegged + * to some indices, such as LIBOR. 30/360 Rules: It is used for corporate + * bonds, U.S. Agency bonds and all mortgage backed securities. It assumes + * that all months have 30 days, and all years have 360 days. The number of + * days from M1/D1/Y1 to M2/D2/Y2 is computed according to the following + * procedure: If D1 is 31, change D1 to 30. If D2 is 31 and D1 is 30 or 31, + * then change D2 to 30. If M1 is 2, and D1 is 28 (in a non-leap year) or + * 29, then change D1 to 30. Then the number of days, N is: N = 360(Y2-Y1) + + * 30(M2-M1) + (D2-D1). + */ + internal fun yearFrac(basis: Int, date0: Long, date1: Long): Double { + val result: Double + // deep breath ... + val fromDate = DateConverter + .getCalendarFromNumber(date0) as GregorianCalendar + val toDate = DateConverter + .getCalendarFromNumber(date1) as GregorianCalendar + val y0 = fromDate.get(Calendar.YEAR) + val y1 = toDate.get(Calendar.YEAR) + var d0 = fromDate.get(Calendar.DAY_OF_MONTH) + var d1 = toDate.get(Calendar.DAY_OF_MONTH) + val m0 = fromDate.get(Calendar.MONTH) + val m1 = toDate.get(Calendar.MONTH) + var yearFrac = 1.0 + if (basis == 0) { // 30/360 US/NASD + if (d0 == 31) + d0 = 30 + if (d1 == 31 && d0 >= 30) + d1 = 30 + if (m0 == 1 && d0 >= 28) + d0 = 30 + yearFrac = (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)).toDouble() / 360.0 + } else if (basis == 1) { // Actual/Actual + var ndays = 0 + var i: Int // average # days between dates + i = y0 + while (i <= y1) { + ndays += if (isLeapYear(i)) 366 else 365 + i++ + } + if (i != y0) + yearFrac = (date1 - date0).toDouble() / (ndays.toDouble() / (i - y0)) // yes I know it's redundant ... + else + yearFrac = (date1 - date0).toDouble() + } else if (basis == 2) { // Actual/360 + yearFrac = (date1 - date0).toDouble() / 360.0 + } else if (basis == 3) { // Actual/365 + yearFrac = (date1 - date0).toDouble() / 365.0 + } else if (basis == 4) { // 30/360 EURO + // if (m0==1 && d0>=28) d0= 30; //??????????????????????????? + // if (m1==1 && d1>=28) d1= 30; //??????????????????????????? + yearFrac = (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)).toDouble() / 360.0 + } + return yearFrac + } + + internal fun getDaysInYearFromBasis(basis: Int, date0: Long, date1: Long): Double { + var r = 0.0 + when (basis) { + 0 // 30/360 + , 2 // actual/360 + , 4 // 30/360 (EURO) + -> r = 360.0 + 1 // actual/actual + -> { + val fromDate = DateConverter.getCalendarFromNumber(date0) as GregorianCalendar + val y0 = fromDate.get(Calendar.YEAR) + if (isLeapYear(y0)) + r = 365.4 + else + r = 365.25 + } + 3 -> r = 365.0 // actual/365 + }// r= (date1-date0)/yearFrac(basis, date0, date1); + return r + } + + internal fun getDaysFromBasis(basis: Int, date0: Long, date1: Long): Long { + if (basis == 0 || basis == 4) { // # months * 30 + extra days + val fromDate = DateConverter.getCalendarFromNumber(date0) as GregorianCalendar + val toDate = DateConverter.getCalendarFromNumber(date1) as GregorianCalendar + val y0 = fromDate.get(Calendar.YEAR) + val y1 = toDate.get(Calendar.YEAR) + var d0 = fromDate.get(Calendar.DAY_OF_MONTH) + var d1 = toDate.get(Calendar.DAY_OF_MONTH) + val m0 = fromDate.get(Calendar.MONTH) + val m1 = toDate.get(Calendar.MONTH) + if (basis == 0) { // 30/360 US/NASD + // TODO: PROBLEM: When date should be 2-28-02, get + // 2-2-02!!!!!!!!!!!!!!! + if (d0 == 31) + d0 = 30 + if (d1 == 31 && d0 >= 30) + d1 = 30 + // if (m0==1 && d0 >=28) d0= 30; // + // ?????????????????????????????????????????????????? + // if (m1 == 1 && d1 >= 28) + // d1 = 30; // ????????????????? + } else { // 30/360 EURO -- CORRECT FOR ALL DATES EXCEPT MONTH OF + // FEBRUARY (M0)!! + // if (m0==1 && d0>=28) d0= 30; // ????????????????????? + // if (m1==1 && d1>=28) d1= 30; // ????????????????????? + } + val result = 360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0) + return result.toLong() + } + return date1 - date0 // actual (1, 2, 3) + } + + internal fun validateDay(y: Int, m: Int, d: Int): Int { + var d = d + if (d > 28) { // m is 0-based + if (m == 1) + // TODO: Get maximum for year + d = 28 + else if ((m == 3 || m == 5 || m == 8 || m == 10) && d == 31) + d = 30 + } + return d + } + + /** + * ACCRINT - Returns the accrued interest for a security that pays periodic + * interest Analysis Pak function Issue is the security's issue date. + * First_interest is the security's first interest date. Settlement is the + * security's settlement date. The security settlement date is the date + * after the issue date when the security is traded to the buyer. Rate is + * the security's annual coupon rate. Par is the security's par value. If + * you omit par, ACCRINT uses $1,000. Frequency is the number of coupon + * payments per year. For annual payments, frequency = 1; for semiannual, + * frequency = 2; for quarterly, frequency = 4. Basis is the type of day + * count basis to use. + * + * + * Basis Day count basis 0 or omitted US (NASD) 30/360 1 Actual/actual 2 + * Actual/360 3 Actual/365 4 European 30/360 + * + * + * ACCRINT is calculated as follows: + * + * + * par X rate/frequency X Sum Ai/NLi over NC + * + * + * where: + * + * + * Ai = number of accrued days for the ith quasi-coupon period within odd + * period. NC = number of quasi-coupon periods that fit in odd period. If + * this number contains a fraction, raise it to the next whole number. NLi = + * normal length in days of the ith quasi-coupon period within odd period. + */ + // TODO: Works only for basis 0 & 4 + @Throws(FunctionNotSupportedException::class) + internal fun calcAccrint(operands: Array): Ptg { + if (true) { + val wn = "WARNING: this version of OpenXLS does not support the formula ACCRINT." + Logger.logWarn(wn) + throw FunctionNotSupportedException(wn) + } + if (operands.size < 6) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) debugOperands(operands, "ACCRINT") + try { + val iDate = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar + val fiDate = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar + val sDate = DateConverter.getCalendarFromNumber(operands[2].value) as GregorianCalendar + val rate = operands[3].doubleVal + var par = 1000.0 + if (operands[4] !is PtgMissArg) + par = operands[4].doubleVal + val frequency = operands[5].intVal + var basis = 0 + if (operands.size > 6) + basis = operands[6].intVal + + val issueDate = DateConverter.getXLSDateVal(iDate).toLong() + val firstInterestDate = DateConverter.getXLSDateVal(fiDate).toLong() + val settlementDate = DateConverter.getXLSDateVal(sDate).toLong() + + // TODO: if dates are not valid, return #VALUE! error + if (!(frequency == 1 || frequency == 2 || frequency == 4)) return PtgErr(PtgErr.ERROR_NUM) + if (rate <= 0 || par <= 0) return PtgErr(PtgErr.ERROR_NUM) + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) + if (issueDate >= settlementDate) return PtgErr(PtgErr.ERROR_NUM) + + // quasicoupon period: extend the series of equal payment periods to + // before or after the actual payment periods + // odd period= period between payments that differs from the usual + // equally spaced periods at which payments are made + // Ai= number of days which have accrued for the ith quasi-coupon period + // within the odd period + // equation: ACCRINT= ((par*rate)/frequency)* Sum over NC (Ai/NLi) + // where NC= # of quasi-coupon periods, rounded up + // Ai= # days in odd period i + // NLi=normal # days in odd period i + // For cases where Previous Coupon Date < Issue Date, NC=1, A= getDaysFromBasis and NLi= coupDays + + var result = par * rate / frequency//* Sum #Accrued days for + val sum = 0.0 + val E: Long + var PCD: Long + + // ACCRINT in EXCEL handles this case, so we should, too! + if (firstInterestDate < settlementDate) { + if (DEBUG) Logger.logInfo(">>> S > FI!") + /* Ptg[] ops = new Ptg[4]; + ops[0] = new PtgNumber(settlementDate); + ops[1] = new PtgNumber(firstInterestDate); + ops[2] = new PtgInt(frequency); + ops[3] = new PtgInt(basis); + + long x= PtgCalculator.getLongValue(calcCoupNCD(ops)); +*/ + // ????????????????????????????????? + E = 181 + PCD = issueDate + } else { + val ops = arrayOfNulls(4) + ops[0] = PtgNumber(settlementDate.toDouble()) + ops[1] = PtgNumber(firstInterestDate.toDouble()) + ops[2] = PtgInt(frequency) + ops[3] = PtgInt(basis) + + + E = PtgCalculator.getLongValue(calcCoupDays(ops)) + PCD = PtgCalculator.getLongValue(calcCoupPCD(ops)) + } + // testing + if (PCD == 0L) { + PCD = issueDate + 1 + //Logger.logInfo(">>PCD==0"); + } + var A: Long = 0 + if (basis == 0 || basis == 4) + // correct for basis 0, 4 + // INCORRECT for basis 1,2 and 3 + A = getDaysFromBasis(basis, issueDate, settlementDate) + else if (issueDate >= PCD) + // correct when issue >= PCD + A = getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate) + else { + // ??????????????????????????????????????????????? + A = getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate) + if (DEBUG) Logger.logInfo(">>I < PCD") + } + + + result *= A / E.toDouble() + + val pnum = PtgNumber(result) + // TODO: Complete Accrint Alogorithm + if (DEBUG) Logger.logInfo("Result from Accrint= $result") + return pnum + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * ACCRINTM Returns the accrued interest for a security that pays interest + * at maturity Analysis Pak function + * + * + * Issue is the security's issue date. Maturity is the security's maturity + * date. Rate is the security's annual coupon rate. Par is the security's + * par value. If you omit par, ACCRINTM uses $1,000. Basis is the type of + * day count basis to use. 0 or omitted US (NASD) 30/360 1 Actual/actual 2 + * Actual/360 3 Actual/365 4 European 30/360 ACCRINTM = par X rate x A/D + * where: A = Number of accrued days counted according to a monthly basis. + * For interest at maturity items, the number of days from the issue date to + * the maturity date is used. D = Annual Year Basis. + */ + internal fun calcAccrintm(operands: Array): Ptg { + if (operands.size < 3) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) + debugOperands(operands, "ACCRINTM") + try { + val issueDate: Long + val maturityDate: Long // dates are truncated to integers + val rate: Double + var par = 1000.0 + var basis = 0 + + // Issue Date + var dt = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar + issueDate = DateConverter.getXLSDateVal(dt).toLong() + // Maturity Date + dt = DateConverter + .getCalendarFromNumber(operands[1].value) as GregorianCalendar + maturityDate = DateConverter.getXLSDateVal(dt) + .toLong() + // Annual Coupon Rate + rate = operands[2].doubleVal + // Par value. If omitted, = 1000. + if (operands.size > 3 && operands[3] !is PtgMissArg) + par = operands[3].doubleVal + // Basis. If omitted, = 0 + if (operands.size > 4) + basis = operands[4].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (rate <= 0 || par <= 0) + return PtgErr(PtgErr.ERROR_NUM) + if (basis < 0 || basis > 4) + return PtgErr(PtgErr.ERROR_NUM) + if (issueDate > maturityDate) + return PtgErr(PtgErr.ERROR_NUM) + + val result = par * rate * yearFrac(basis, issueDate, maturityDate) + if (DEBUG) Logger.logInfo("Result from Accrintm= $result") + return PtgNumber(result) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * AMORDEGRC Returns the depreciation for each accounting period + */ + internal fun calcAmordegrc(operands: Array): Ptg { + if (operands.size < 6) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) + debugOperands(operands, "AMORDEGRC") + try { + var cost = operands[0].doubleVal + val dP = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar + val fP = DateConverter.getCalendarFromNumber(operands[2].value) as GregorianCalendar + val salvage = operands[3].doubleVal + val period = operands[4].intVal + var rate = operands[5].doubleVal + val basis = operands[6].intVal + + val datePurchased = DateConverter.getXLSDateVal(dP).toLong() + val firstPeriod = DateConverter.getXLSDateVal(fP).toLong() + + // TODO: if dates are not valid, return #VALUE! error + if (rate <= 0) return PtgErr(PtgErr.ERROR_NUM) + if (basis < 0 || basis > 4 || basis == 2) return PtgErr(PtgErr.ERROR_NUM) + if (datePurchased > firstPeriod) return PtgErr(PtgErr.ERROR_NUM) + + val coefficient: Double + val life = 1.0 / rate + if (life < 3) + coefficient = 1.0 + else if (life < 5.0) + // between 3 and 4 years + coefficient = 1.5 + else if (life <= 6.0) + // between 5 and 6 years + coefficient = 2.0 + else + // more than 6 years + coefficient = 2.5 + + rate *= coefficient + //cost-= Math.round(yearFrac(basis, datePurchased, + // firstPeriod)*rate*cost); + cost -= yearFrac(basis, datePurchased, firstPeriod) * rate * cost + var Remainder = cost - salvage + var A = 0.0 + if (Remainder > 0) { + var i = 0 + while (i < period) { + //A= Math.round(rate*cost); + A = rate * cost + Remainder -= A + if (Remainder < 0) { + /* + * if (period==i+1) { // A= Math.round(0.5*cost); + * Remainder=A; } + */ + if (i + 1 < period) + A = 0.0 + i = period // exit loop + } + + cost -= A + i++ + } + } else + A = Math.round(rate * salvage).toDouble() + val result = Math.max(Math.round(A), 0).toDouble() + if (DEBUG) + Logger.logInfo("Result from AMORDEGRC= $result") + return PtgNumber(result) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * AMORLINC Returns the depreciation for each accounting period + */ + internal fun calcAmorlinc(operands: Array): Ptg { + if (operands.size < 6) { + return PtgErr(PtgErr.ERROR_NULL) + } + if (DEBUG) + debugOperands(operands, "AMORLINC") + try { + val cost = operands[0].doubleVal + val dP = DateConverter + .getCalendarFromNumber(operands[1].value) as GregorianCalendar + val fP = DateConverter + .getCalendarFromNumber(operands[2].value) as GregorianCalendar + val salvage = operands[3].doubleVal + val period = operands[4].intVal + val rate = operands[5].doubleVal + val basis = operands[6].intVal + + val datePurchased = DateConverter.getXLSDateVal(dP) + .toLong() + val firstPeriod = DateConverter.getXLSDateVal(fP) + .toLong() + + // TODO: if dates are not valid, return #VALUE! error + if (rate <= 0) + return PtgErr(PtgErr.ERROR_NUM) + if (basis < 0 || basis > 4 || basis == 2) + return PtgErr(PtgErr.ERROR_NUM) + if (datePurchased > firstPeriod) + return PtgErr(PtgErr.ERROR_NUM) + + var A = 0.0 + val B = cost - salvage + val C = yearFrac(basis, datePurchased, firstPeriod) * rate * cost + val D = cost * rate + val n = Math.round((cost - salvage - C) / D) + if (period == 0 || C == 0.0) + A = C + else if (period < n) + A = D + else if (period.toLong() == n) + A = Math.min(D + (B - D * n - C), D) + else if (period.toLong() == n + 1) + A = B - D * n - C + else + A = 0.0 +/* + cost -= yearFrac(basis, datePurchased, firstPeriod) * rate * cost; + if (Remainder > 0) { + for (int i = 0; i < period; i++) { + //A= Math.round(rate*cost); + A = rate * cost; + Remainder -= A; + if (Remainder < 0) { + /* + * if (period==i+1) { // A= Math.round(0.5*cost); + } + * / + if (i + 1 < period) + A = 0; + i = period; // exit loop + } + cost -= A; + } + } else + A = Math.round(rate * salvage); +*/ + val result = Math.max(Math.round(A), 0).toDouble() + /* + double A = cost * rate; + double B = cost - salvage; + //double yf= yearFrac(basis, datePurchased, firstPeriod); + double C = yearFrac(basis, datePurchased, firstPeriod) * rate * cost; + long n = Math.round((cost - salvage - C) / A); + double result; + if (period == 0 || C == 0) + result = C; + else if (period < n) + result = A; + else if (period == n) + result = A - C; //B - A * n - C; + else if (period == n + 1) + result = A - C; //B - A * n - C; + else + result = 0; +*/ + if (DEBUG) +Logger.logInfo("Result from AMORLINC= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * COUPDAYBS -- + * + * + * Returns the number of days from the beginning of the coupon period to the + * settlement date Settlement is the security's settlement date. The + * security settlement date is the date after the issue date when the + * security is traded to the buyer. Maturity is the security's maturity + * date. The maturity date is the date when the security expires. Frequency + * is the number of coupon payments per year. For annual payments, frequency = + * 1; for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is + * the type of day count basis to use. (optional) + */ + internal fun calcCoupDayBS(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) debugOperands(operands, "COUPDAYSBS") +try +{ +val sDate = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val settlementDate = (DateConverter.getXLSDateVal(sDate)).toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)).toLong() +val frequency = operands[2].intVal +var basis = 0 +if (operands.size > 3) +basis = operands[3].intVal + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (!(frequency == 1 || frequency == 2 || frequency == 4)) return PtgErr(PtgErr.ERROR_NUM) +if (settlementDate > maturityDate) return PtgErr(PtgErr.ERROR_NUM) + +val pcd = PtgCalculator.getLongValue(calcCoupPCD(operands)) + +val result = getDaysFromBasis(basis, pcd, settlementDate).toDouble() + + // + + + if (DEBUG) +Logger.logInfo("Result from calcCoupDaysBS= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * COUPDAYS Returns the number of days in the coupon period that contains + * the settlement date Settlement is the security's settlement date. The + * security settlement date is the date after the issue date when the + * security is traded to the buyer Maturity is the security's maturity date. + * The maturity date is the date when the security expires. Frequency is the + * number of coupon payments per year. For annual payments, frequency = 1; + * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the + * type of day count basis to use + */ + internal fun calcCoupDays(operands:Array):Ptg { +if (operands.size < 3) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "COUPDAYS") +try +{ +var dt = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val settlementDate = (DateConverter.getXLSDateVal(dt)).toLong() +dt = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val maturityDate = (DateConverter.getXLSDateVal(dt)).toLong() +val frequency = operands[2].intVal +var basis = 0 +if (operands.size > 3) +basis = operands[3].intVal + // TODO: if dates are not valid, return #VALUE! error + if (!(frequency == 1 || frequency == 2 || frequency == 4)) return PtgErr(PtgErr.ERROR_NUM) +if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (settlementDate > maturityDate) return PtgErr(PtgErr.ERROR_NUM) + + // VERY strange, but seems to be correct + val result:Double +if (basis == 1) +{ // actual/actual +val pcd = PtgCalculator.getLongValue(calcCoupPCD(operands)) +val ncd = PtgCalculator.getLongValue(calcCoupNCD(operands)) +result = getDaysFromBasis(basis, pcd, ncd).toDouble() +} +else if (basis == 0 || basis == 2 || basis == 4) +result = 360.0 / frequency +else +result = 365.0 / frequency +if (DEBUG) +Logger.logInfo("Result from calcCoupDays=" + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * COUPDAYSNC - Returns the number of days from the settlement date to the + * next coupon date + * + * + * Settlement is the security's settlement date. The security settlement + * date is the date after the issue date when the security is traded to the + * buyer Maturity is the security's maturity date. The maturity date is the + * date when the security expires. Frequency is the number of coupon + * payments per year. For annual payments, frequency = 1; for semiannual, + * frequency = 2; for quarterly, frequency = 4. Basis is the type of day + * count basis to use + */ + internal fun calcCoupDaysNC(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "COUPDAYSNC") +val settlementDate = Long(operands[0].value.toString()).toLong() +val maturityDate = Long(operands[1].value.toString()).toLong() +val frequency = operands[2].intVal +var basis = 0 +if (operands.size > 3) +basis = operands[3].intVal + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) +return PtgErr(PtgErr.ERROR_NUM) +if (!(frequency == 1 || frequency == 2 || frequency == 4)) +return PtgErr(PtgErr.ERROR_NUM) +if (settlementDate > maturityDate) +return PtgErr(PtgErr.ERROR_NUM) + +val ncd = PtgCalculator.getLongValue(calcCoupNCD(operands)) + +val result = getDaysFromBasis(basis, settlementDate, ncd).toDouble() +if (DEBUG) +Logger.logInfo("Result from calcCoupDaysNC= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * COUPNCD Returns the next coupon date after the settlement date Returns a + * number that represents the next coupon date after the settlement date. + * COUPNCD(settlement,maturity,frequency,basis) Important Dates should be + * entered by using the DATE function, or as results of other formulas or + * functions. For example, use DATE(2008,5,23) for the 23rd day of May, + * 2008. Problems can occur if dates are entered as text. Settlement is the + * security's settlement date. The security settlement date is the date + * after the issue date when the security is traded to the buyer. Maturity + * is the security's maturity date. The maturity date is the date when the + * security expires. Frequency is the number of coupon payments per year. + * For annual payments, frequency = 1; for semiannual, frequency = 2; for + * quarterly, frequency = 4. Basis is the type of day count basis to use. + */ + internal fun calcCoupNCD(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "COUPNCD") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val frequency = operands[2].intVal +var basis = 0 +if (operands.size > 3) +basis = operands[3].intVal + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) +return PtgErr(PtgErr.ERROR_NUM) +if (!(frequency == 1 || frequency == 2 || frequency == 4)) +return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) +return PtgErr(PtgErr.ERROR_NUM) + +val resultDate:GregorianCalendar +var mm = mDate.get(Calendar.MONTH) + 1 // months are 0-based but calc + // needs 1-based - for now! + val sm = sDate.get(Calendar.MONTH) + 1 +var y = mDate.get(Calendar.YEAR) +val d = mDate.get(Calendar.DAY_OF_MONTH) + +if (frequency == 1) +{ // annual +while (sDate.before((GregorianCalendar(y, mm - 1, d)))) +{ +y-- +} +y++ +} +if (frequency == 2) +{ // semi-annual +while (sDate.before((GregorianCalendar(y, mm - 1, d)))) +{ +mm -= 6 +if (mm < 1) +{ +mm += 12 +y-- +} +} +mm += 6 +} +else if (frequency == 4) +{ // quarterly +while (sDate.before((GregorianCalendar(y, mm - 1, d)))) +{ +mm -= 3 +if (mm < 1) +{ +mm += 12 +y-- +} +} +mm += 3 +} +resultDate = GregorianCalendar(y, mm - 1, d) +val date = DateConverter.getXLSDateVal(resultDate) +if (DEBUG) +Logger.logInfo(("Result from calcCoupNCD= " ++ date ++ " " ++ java.text.DateFormat.getDateInstance().format( +resultDate.getTime()))) +val i = date.toInt() +val pi = PtgInt(i) +return pi +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * COUPNUM Returns the number of coupons payable between the settlement date + * and maturity date Settlement is the security's settlement date. The + * security settlement date is the date after the issue date when the + * security is traded to the buyer Maturity is the security's maturity date. + * The maturity date is the date when the security expires. Frequency is the + * number of coupon payments per year. For annual payments, frequency = 1; + * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the + * type of day count basis to use + */ + internal fun calcCoupNum(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "COUPNUM") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() +val frequency = operands[2].intVal +var basis = 0 +if (operands.size > 3) +basis = operands[3].intVal + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) +return PtgErr(PtgErr.ERROR_NUM) +if (!(frequency == 1 || frequency == 2 || frequency == 4)) +return PtgErr(PtgErr.ERROR_NUM) +if (settlementDate > maturityDate) +return PtgErr(PtgErr.ERROR_NUM) + /* + double result = getDaysFromBasis(basis, settlementDate, maturityDate) + / (getDaysInYearFromBasis(basis, settlementDate, maturityDate) / frequency); + result = Math.ceil(result); + double delta= maturityDate-settlementDate; + double result= delta/calcCoupDays(operands)); +*/ + val result = Math.ceil(yearFrac(basis, settlementDate, maturityDate) * frequency) +if (DEBUG) +Logger.logInfo("Result from calcCoupNUM= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * COUPPCD Returns the previous coupon date before the settlement date + */ + internal fun calcCoupPCD(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) debugOperands(operands, "COUPPCD") +try +{ +val sDate = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val frequency = operands[2].intVal +var basis = 0 +if (operands.size > 3) +basis = operands[3].intVal + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (!(frequency == 1 || frequency == 2 || frequency == 4)) +return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) return PtgErr(PtgErr.ERROR_NUM) + +val resultDate:GregorianCalendar +var mm = mDate.get(Calendar.MONTH) + 1 // months are 0-based but calc + // needs 1-based - for now! + val sm = sDate.get(Calendar.MONTH) + 1 +var y = mDate.get(Calendar.YEAR) +val d = mDate.get(Calendar.DAY_OF_MONTH) + +if (frequency == 1) +{ // annual +while (sDate.before((GregorianCalendar(y, mm - 1, d)))) +{ +y-- +} +} +if (frequency == 2) +{ // semi-annual +while (sDate.before((GregorianCalendar(y, mm - 1, d)))) +{ +mm -= 6 +if (mm < 1) +{ +mm += 12 +y-- +} +} +} +else if (frequency == 4) +{ // quarterly +while (sDate.before((GregorianCalendar(y, mm - 1, d)))) +{ +mm -= 3 +if (mm < 1) +{ +mm += 12 +y-- +} +} +} +resultDate = GregorianCalendar(y, mm - 1, d) +val date = DateConverter.getXLSDateVal(resultDate) +if (DEBUG) +Logger.logInfo(("Result from calcCoupPCD= " ++ date ++ " " ++ java.text.DateFormat.getDateInstance().format( +resultDate.getTime()))) +val i = date.toInt() +val pi = PtgInt(i) +return pi +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * CUMIPMT Returns the cumulative interest paid between two periods + * CUMIPMT(rate,nper,pv,start_period,end_period,type) All parameters are + * require Rate is the interest rate. Nper is the total number of payment + * periods. Pv is the present value. Start_period is the first period in the + * calculation. Payment periods are numbered beginning with 1. End_period is + * the last period in the calculation. Type is the timing of the payment. + * + * + * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the + * beginning of the period + */ + internal fun calcCumIPmt(operands:Array):Ptg { +if (operands.size < 6) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +val rate = operands[0].doubleVal +val nper = operands[1].doubleVal +val pv = operands[2].doubleVal +val startperiod = operands[3].intVal +val endperiod = operands[4].intVal +val type = operands[5].intVal + +if (DEBUG) +debugOperands(operands, "CUMIPMT") +if (rate <= 0 || pv <= 0 || nper <= 0) +return PtgErr(PtgErr.ERROR_NUM) +if (startperiod < 1 || endperiod < 1) +return PtgErr(PtgErr.ERROR_NUM) +if (startperiod > endperiod) +return PtgErr(PtgErr.ERROR_NUM) +if (type < 0 || type > 1) +return PtgErr(PtgErr.ERROR_NUM) + + // CumIPMT= pmt*period - FV for start-1 - pmt - FV for end period and + // pmt + val A:Double +val B:Double + // PMT used in fv calc + val Rn = Math.pow(1 + rate, nper) +A = -pv * Rn * rate +B = (Rn - 1) * (1 + rate * type) +val pmt = A / B + + // WORKS on everything BUT type=1 AND startperiod=1 !!!!!! + val n = (startperiod - 1 - type).toDouble() +val period = endperiod - startperiod + 1 + + // FVa (StartPeriod) + A = Math.pow(1 + rate, n) +B = pmt * (1 + rate * type) +B *= (Math.pow(1 + rate, n) - 1) / rate +val fva = -(pv * A + B) + // FVb (endPeriod) + A = Math.pow(1 + rate, (endperiod - type).toDouble()) +B = pmt * (1 + rate * type) +B *= (Math.pow(1 + rate, (endperiod - type).toDouble()) - 1) / rate +val fvb = -(pv * A + B) + +var result = fva - fvb - (pmt * period) //- (fva - fvb); +if (startperiod == 1 && type == 1) +result = (pmt * period) + pv // I'm sure there's a good reason for + // this!?!?! + result *= -1.0 +if (DEBUG) +Logger.logInfo("Result from calcCumIPmt= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * CUMPRINC Returns the cumulative principal paid on a loan between + * start_period and end_period. Syntax + * + * + * CUMPRINC(rate,nper,pv,start_period,end_period,type) + * + * + * Rate is the interest rate. Nper is the total number of payment periods. + * Pv is the present value. Start_period is the first period in the + * calculation. Payment periods are numbered beginning with 1. End_period is + * the last period in the calculation. Type is the timing of the payment. + * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the + * beginning of the period + */ + internal fun calcCumPrinc(operands:Array):Ptg { +if (operands.size < 6) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +val rate = operands[0].doubleVal +val nper = operands[1].doubleVal +val pv = operands[2].doubleVal +val startperiod = operands[3].intVal +val endperiod = operands[4].intVal +val type = operands[5].intVal + +if (DEBUG) +debugOperands(operands, "CUMPRINC") + // Cumprinc= FV for start-1 and pmt - FV for end period and pmt + val A:Double +val B:Double + // PMT used in fv calc + val Rn = Math.pow(1 + rate, nper) +A = -pv * Rn * rate +B = (Rn - 1) * (1 + rate * type) +val pmt = A / B + + // FVa (StartPeriod) + A = Math.pow(1 + rate, (startperiod - type - 1).toDouble()) +B = pmt * (1 + rate * type) +B *= (Math.pow(1 + rate, (startperiod - type - 1).toDouble()) - 1) / rate +val fva = -(pv * A + B) + // FVb (endPeriod) + A = Math.pow(1 + rate, (endperiod - type).toDouble()) +B = pmt * (1 + rate * type) +B *= (Math.pow(1 + rate, (endperiod - type).toDouble()) - 1) / rate +val fvb = -(pv * A + B) + +var result = fva - fvb +if (startperiod == 1 && type == 1) +result = pv // I'm sure there's a good reason for this!?!?! +if (DEBUG) +Logger.logInfo("Result from calcCUMPRINC= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * DB Returns the depreciation of an asset for a specified period using the + * fixed-declining balance method + * + * + * DB(cost,salvage,life,period,month) + * + * + * Cost is the initial cost of the asset. + * + * + * Salvage is the value at the end of the depreciation (sometimes called the + * salvage value of the asset). + * + * + * Life is the number of periods over which the asset is being depreciated + * (sometimes called the useful life of the asset). + * + * + * Period is the period for which you want to calculate the depreciation. + * Period must use the same units as life. + * + * + * Month is the number of months in the first year. If month is omitted, it + * is assumed to be 12. + */ + + internal fun calcDB(operands:Array):Ptg { +if (operands.size < 4 || operands[0].components != null) +{ // not + // supported + // by + // function + val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +val cost:Double +val salvage:Double +val life:Int +val period:Int +val month:Int +cost = Double((operands[0].value).toString()).toDouble() +salvage = Double((operands[1].value).toString()) +.toDouble() +life = Integer.valueOf((operands[2].value).toString()).toInt() +period = Integer.valueOf((operands[3].value).toString()).toInt() +if (operands.size > 4) +{ +if (operands[4] is PtgMissArg) +{ +month = 12 +} +else +{ +month = Integer.valueOf((operands[4].value).toString()) +.toInt() +} +} +else +{ +month = 12 +} +val salCost = salvage / cost + // this section longhand due to some wierd calcs when calling lifdiv = + // 1/life; + var lifdiv = 1.0 +lifdiv /= life.toDouble() +var rate = Math.pow(salCost, lifdiv) +rate = 1 - rate +rate = rate * 1000 +rate = Math.round(rate).toDouble() +rate /= 1000.0 + +var totalDepreciation = cost * rate * month.toDouble() / 12 +var result = totalDepreciation + // 1st and last (i.e. period==life) are special cases + var i = 2 +while (i < period || (i == period && period <= life)) +{ +result = (cost - totalDepreciation) * rate +totalDepreciation += (cost - totalDepreciation) * rate +i++ +} +if (period > life) + // last depreciation is special calc + result = (cost - totalDepreciation) * rate * (12 - month).toDouble() / 12 +val pnum = PtgNumber(result) +return pnum +} + +/** + * DDB Returns the depreciation of an asset for a spcified period using the + * double-declining balance method or some other method you specify + * + * + * DDB(cost,salvage,life,period,factor) + * + * + * Cost is the initial cost of the asset. + * + * + * Salvage is the value at the end of the depreciation (sometimes called the + * salvage value of the asset). + * + * + * Life is the number of periods over which the asset is being depreciated + * (sometimes called the useful life of the asset). + * + * + * Period is the period for which you want to calculate the depreciation. + * Period must use the same units as life. + * + * + * Factor is the rate at which the balance declines. If factor is omitted, + * it is assumed to be 2 (the double-declining balance method). + * + * + * All five arguments must be positive numbers. + */ + internal fun calcDDB(operands:Array):Ptg { +if (operands.size < 4 || operands[0].components != null) +{ // not + // supported + // by + // function + val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +val cost:Double +val salvage:Double +val life:Int +val period:Int +val factor:Int +cost = Double((operands[0].value).toString()).toDouble() +salvage = Double((operands[1].value).toString()) +.toDouble() +life = Integer.valueOf((operands[2].value).toString()).toInt() +period = Integer.valueOf((operands[3].value).toString()).toInt() +factor = 2 +if (operands.size > 4) +{ +if (!(operands[4] is PtgMissArg)) +factor = Integer.valueOf((operands[4].value).toString()) +.toInt() +} +val salCost = salvage / cost + // this section longhand due to some wierd calcs when calling lifdiv = + // 1/life; + val facLife = factor / (life.toDouble()) +var totalDepreciation = 0.0 + /* original calc + for (int i = 1; i < period; i++) { + //((cost-salvage) - total depreciation from prior periods) * (factor/life) + totalDepreciation += (cost - salvage - totalDepreciation)*facLife; + } +// double result= (cost - salvage - totalDepreciation) * (facLife); + * + */ + for (i in 1 until period) +{ +totalDepreciation += (cost - totalDepreciation) * facLife +} +var result = 0.0 +if (cost - salvage - totalDepreciation > 0) +result = (cost - totalDepreciation) * facLife +val pnum = PtgNumber(result) +return pnum +} + +/** + * DISC Returns the discount rate for a security + * + * + * Settlement is the security's settlement date. The security settlement + * date is the date after the issue date when the security is traded to the + * buyer. Maturity is the security's maturity date. The maturity date is the + * date when the security expires. Pr is the security's price per $100 face + * value. Redemption is the security's redemption value per $100 face value. + * Basis (optional) is the type of day count basis to use. + */ + internal fun calcDISC(operands:Array):Ptg { +if (operands.size < 4) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcDISC") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() +val pr = operands[2].doubleVal +val redemption = operands[3].doubleVal +var basis = 0 +if (operands.size > 4) +basis = operands[4].intVal + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) +return PtgErr(PtgErr.ERROR_NUM) +if (settlementDate > maturityDate) +return PtgErr(PtgErr.ERROR_NUM) +if (pr <= 0 || redemption <= 0) +return PtgErr(PtgErr.ERROR_NUM) + + + // double DSM = maturityDate - settlementDate; + // double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); + // double result = (redemption - pr) / redemption * (B / DSM); + val result = (redemption - pr) / (redemption * yearFrac(basis, settlementDate, maturityDate)) + +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcDISC= " + result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * DOLLARDE Converts a dollar price, expressed as a fraction, into a dollar + * price, expressed as a decimal number Fractional_dollar is a number + * expressed as a fraction. Fraction is the integer to use in the + * denominator of the fraction. + */ + internal fun calcDollarDE(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcDOLLARDE") +val fractional_dollar = operands[0].doubleVal +val fraction = operands[1].intVal +if (fraction < 0) +return PtgErr(PtgErr.ERROR_NUM) +if (fraction == 0) +return PtgErr(PtgErr.ERROR_DIV_ZERO) + +val n = (fraction).toString().length +val x = Math.floor(fractional_dollar) +val y = (fractional_dollar - x) * Math.pow(10.0, n.toDouble()) +val result = x + y / fraction +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcDOLLARDE= " + result) +return pnum +} + +/** + * DOLLARFR Converts a dollar price, expressed as a decimal number, into a + * dollar price, expressed as a fraction Decimal_dollar is a decimal number. + * Fraction is the integer to use in the denominator of a fraction + */ + internal fun calcDollarFR(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcDOLLARFR") +val decimal_dollar = operands[0].doubleVal +val fraction = operands[1].intVal +if (fraction < 0) +return PtgErr(PtgErr.ERROR_NUM) +if (fraction == 0) +return PtgErr(PtgErr.ERROR_DIV_ZERO) + +val n = (fraction).toString().length + +val x = Math.floor(decimal_dollar) +val y = (decimal_dollar - x) +val result = x + (y * fraction) / Math.pow(10.0, n.toDouble()) +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcDOLLARFR= " + result) +return pnum +} + +/** + * DURATION Returns the annual duration of a security with periodic interest + * payments + * DURATION(settlement,maturity,coupon,yld,frequency,basis) + * Settlement is the security's settlement date. Maturity is the security's + * maturity date. Coupon is the security's annual coupon rate. Yld is the + * security's annual yield. Frequency is the number of coupon payments per + * year. For annual payments, frequency = 1; for semiannual, frequency = 2; + * for quarterly, frequency = 4. Basis is the type of day count basis to use + */ + internal fun calcDURATION(operands:Array):Ptg { +if (operands.size < 5) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcDURATION") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val coupon = operands[2].doubleVal +val yld = operands[3].doubleVal +val frequency = operands[4].intVal +var basis = 0 +if (operands.size > 5) +basis = operands[5].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) +return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) +return PtgErr(PtgErr.ERROR_NUM) +if (coupon < 0 || yld < 0) +return PtgErr(PtgErr.ERROR_NUM) +if (!(frequency == 1 || frequency == 2 || frequency == 4)) +return PtgErr(PtgErr.ERROR_NUM) + +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() + +val ops = arrayOfNulls(4) +ops[0] = operands[0] +ops[1] = operands[1] +ops[2] = operands[4] +ops[3] = PtgInt(basis) + + /* + * Duration= (A + SumC)/(D+ SumB * 1/frequency + * + * where + * Y= 1+ yield/frequency + * R= 100*rate + * F= DSC/E + * A= (F*100)/Y^(n-1+F) + * SumC= Sum(1,n): R/(frequency*Y^(i-1+F)) * (i-1+F) + * D= 100/Y^(n-1+F) + * SumB= Sum(1,n): R/(frequency*Y^(i-1+F)) + * + */ + val n = calcCoupNum(ops).doubleVal +val DSC = calcCoupDaysNC(ops).doubleVal +val E = calcCoupDays(ops).doubleVal +val F = DSC / E +val R = coupon * 100 +val Y = 1 + yld / frequency +val Yx = Math.pow(Y, n - 1 + F) +var SumA = 0.0 +run({ var i = 1 +while (i <= n) +{ +SumA += R * (i - 1 + F) / (Math.pow(Y, i - 1 + F) * frequency) +i++ +} }) +var SumB = 0.0 +var i = 1 +while (i <= n) +{ +SumB += R / (Math.pow(Y, i - 1 + F) * frequency) +i++ +} +var C = 0.0 +var D = 0.0 +if (n > 1) +{ +C = ((n - 1 + F) * 100) / Yx +D = 100 / Yx +} +val result = (SumA + C) / ((SumB + D) * frequency) +val pnum = PtgNumber(result) +if (DEBUG) Logger.logInfo("Result from calcDURATION= " + result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * EFFECT Returns the effective annual interest rate Nominal_rate is the + * nominal interest rate. Npery is the number of compounding periods per + * year + */ + internal fun calcEffect(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcEFFECT") +val nominal_rate = operands[0].doubleVal +val npery = operands[1].intVal + // TODO: If either argument is non-numeric, #VALUE! error + if (npery <= 0 || npery < 1) + // funny guard!!!! + return PtgErr(PtgErr.ERROR_NUM) + /* + * // KSC: TESTING for (int j= 1; j <= 10; j++) { double i; if ((j % 2) == + * 0) i= 1; else i= .5; npery= j; while (i <= 10) { nominal_rate= i / + * 100; + * + * + * double x= nominal_rate / npery; double result= Math.pow(1 + x, npery) - + * 1; Logger.logInfo("nominal_rate= " + nominal_rate + " npery= " + + * npery + " result= " + result); java.math.BigDecimal bd= new + * java.math.BigDecimal(i-Math.floor(i)).setScale(5, + * java.math.BigDecimal.ROUND_HALF_UP); if (((bd.doubleValue()*100) % 4) == + * 0) // if ((((i-Math.floor(i))*100) % 4)==0) i+= 0.3; else i+= 0.25; } } + */ + val x = nominal_rate / npery +val result = Math.pow(1 + x, npery.toDouble()) - 1 +val pnum = PtgNumber(result) +if (DEBUG) Logger.logInfo("Result from calcEFFECT= " + result) +return pnum +} + +/** + * FV Returns the future value of an investment FV(rate,nper,pmt,pv,type) + * + * + * + * + * Rate is the interest rate per period. Nper is the total number of payment + * periods in an annuity. Pmt is the payment made each period; it cannot + * change over the life of the annuity . Typically, pmt contains principal + * and interest but no other fees or taxes. If pmt is omitted, you must + * include the pv argument. Pv is the present value, or the lump-sum amount + * that a series of future payments is worth right now. If pv is omitted, it + * is assumed to be 0 (zero), and you must include the pmt argument. Type is + * the number 0 or 1 and indicates when payments are due. If type is + * omitted, it is assumed to be 0. + */ + internal fun calcFV(operands:Array):Ptg { +if (operands.size < 3) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcFV") +val rate = operands[0].doubleVal +val nper = operands[1].intVal +val pmt = operands[2].doubleVal +var pv = 0.0 +var type = 0 +if (operands.size > 3 && !(operands[3] is PtgMissArg)) +pv = operands[3].doubleVal +if (operands.size > 4) +type = operands[4].intVal + +val A = Math.pow(1 + rate, nper.toDouble()) +var B = pmt * (1 + rate * type) +B *= (Math.pow(1 + rate, nper.toDouble()) - 1) / rate +val result = -(pv * A + B) +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcFV= " + result) +return pnum + +} + +/** + * FVSCHEDULE Returns the future value of an initial principal after + * applying a series of compound interest rates + */ + internal fun calcFVSCHEDULE(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) debugOperands(operands, "calcFVSCHEDULE") +val principal = operands[0].doubleVal +val schedule = PtgCalculator.getAllComponents(operands[1]) +if (DEBUG) debugOperands(schedule, "calcFVSCHEDULE") // AFTER converting + // references ... + var result = 1.0 +for (i in schedule.indices) +{ +result *= principal + schedule[i].doubleVal +} +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcFVSCHEDULE= " + result) +return pnum +} + +/** + * INTRATE Returns the interest rate for a fully invested security + */ + internal fun calcINTRATE(operands:Array):Ptg { +if (operands.size < 4) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcINTRATE") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val investment = operands[2].doubleVal +val redemption = operands[3].doubleVal +var basis = 0 +if (operands.size > 4) +basis = operands[4].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) return PtgErr(PtgErr.ERROR_NUM) +if (investment <= 0 || redemption <= 0) return PtgErr(PtgErr.ERROR_NUM) + +val settlementDate = (DateConverter.getXLSDateVal(sDate)).toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)).toLong() + // double delta = maturityDate - settlementDate; + // double result = ((redemption - investment) / investment) * ((getDaysInYearFromBasis(basis, settlementDate, maturityDate) / delta)); + val result = ((redemption - investment) / investment) / yearFrac(basis, settlementDate, maturityDate) +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcINTRATE= " + result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * IPMT Returns the interest payment for an investment for a given period. + * + * + * Syntax IPMT(rate,per,nper,pv,fv,type) + * + * + * Rate is the interest rate per period. Per is the period for which you + * want to find the interest and must be in the range 1 to nper. Nper is the + * total number of payment periods in an annuity. Pv is the present value, + * or the lump-sum amount that a series of future payments is worth right + * now. Fv is the future value, or a cash balance you want to attain after + * the last payment is made. If fv is omitted, it is assumed to be 0 (the + * future value of a loan, for example, is 0). Type is the number 0 or 1 and + * indicates when payments are due. If type is omitted, it is assumed to be + * 0. + */ + internal fun calcIPMT(operands:Array):Ptg { +if (operands.size < 4) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcIPMT") +val rate = operands[0].doubleVal +val per = operands[1].doubleVal +val nper = operands[2].doubleVal +val pv = operands[3].doubleVal +var fv = 0.0 +var type = 0 +if (operands.size > 4 && !(operands[4] is PtgMissArg)) +fv = operands[4].doubleVal +if (operands.size > 5) +type = operands[5].intVal + +if (per < 0 || per > nper) +return PtgErr(PtgErr.ERROR_NUM) + + // IPMT= pmt- (fv(b) - fv(a) i.e. = payement less the principal balance + // btwn two periods + + val n:Double +if (type == 0) +n = per +else +n = per - 1 + // PMT + val Rn = Math.pow(1 + rate, nper) +var A = -fv * rate - pv * Rn * rate +var B = (Rn - 1) * (1 + rate * type) +val pmt = A / B + // FVa + A = Math.pow(1 + rate, n) +B = pmt * (1 + rate * type) +B *= (Math.pow(1 + rate, n) - 1) / rate +val fva = -(pv * A + B) + // FVb + A = Math.pow(1 + rate, n - 1) +B = pmt * (1 + rate * type) +B *= (Math.pow(1 + rate, n - 1) - 1) / rate +val fvb = -(pv * A + B) + +val result = pmt - (fvb - fva) +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcIPMT= " + result) +return pnum +} + +/** + * IRR Returns the internal rate of return for a series of cash flows + * Returns the internal rate of return for a series of cash flows + * represented by the numbers in values. These cash flows do not have to be + * even, as they would be for an annuity. However, the cash flows must occur + * at regular intervals, such as monthly or annually. The internal rate of + * return is the interest rate received for an investment consisting of + * payments (negative values) and income (positive values) that occur at + * regular periods. + * + * + * SEE: NPV + * + * + * Syntax IRR(values,guess) + * + * + * Values is an array or a reference to cells that contain numbers for which + * you want to calculate the internal rate of return. + * + * + * Values must contain at least one positive value and one negative value to + * calculate the internal rate of return. + * + * + * IRR uses the order of values to interpret the order of cash flows. Be + * sure to enter your payment and income values in the sequence you want. + * + * + * If an array or reference argument contains text, logical values, or empty + * cells, those values are ignored. Guess is a number that you guess is + * close to the result of IRR. + * + * + * Microsoft Excel uses an iterative technique for calculating IRR. Starting + * with guess, IRR cycles through the calculation until the result is + * accurate within 0.00001 percent. If IRR can't find a result that works + * after 20 tries, the #NUM! error value is returned. + * + * + * In most cases you do not need to provide guess for the IRR calculation. + * If guess is omitted, it is assumed to be 0.1 (10 percent). + * + * + * If IRR gives the #NUM! error value, or if the result is not close to what + * you expected, try again with a different value for guess + */ + internal fun calcIRR(operands:Array):Ptg { +if (operands.size < 1 || operands[0].components == null) +{ // not + // supported + // by + // function + val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcIRR") +var guess = .1 +if (operands.size > 1) +guess = operands[1].doubleVal +val params = PtgCalculator.getAllComponents(operands) +if (DEBUG) +debugOperands(params, "calcIRR") // AFTER converting references ... +val n = params.size + + // examine values array, sum all outflows (= negative values) + inflows + // (= positive values) + var outflow = 0.0 +var inflow = 0.0 + // get outflow (- values) + for (i in 0 until n) +{ +val `val` = params[i].doubleVal +if (`val` < 0) +outflow += Math.abs(`val`) +else +inflow += `val` +} +if (outflow == 0.0 || inflow == 0.0) +return PtgErr(PtgErr.ERROR_VALUE) // TODO: Excel doesn't specify which + // error to return + + // iterate over possible irr values; value is correct when + // outflow-pv <= tolerance, defined as .00001% + val TOLERANCE = 0.0000001 +var bIsCorrect = false +val xl:Double +val xh:Double +val fl:Double +val fh:Double +val f:Double +var trial = guess +xl = 0.0 +xh = guess +var delta = xh - xl +fl = outflow - inflow +fh = outflow +for (i in 0 until n) +{ +val `val` = params[i].doubleVal +if (`val` > 0) +{ +fh -= `val` / (Math.pow(1 + xh, i.toDouble())) +} +} +var j = 0 +while (j < 50 && !bIsCorrect) +{ // maximum 20 tries - need + // more!!!! + trial = xl + delta * fl / (fl - fh) +f = outflow +for (i in 0 until n) +{ +val `val` = params[i].doubleVal +if (`val` > 0) +{ +f -= `val` / (Math.pow(1 + trial, i.toDouble())) +} +} + +if (f < 0) +{ +delta = xl - trial +xl = trial +fl = f +} +else +{ +delta = xh - trial +xh = trial +fh = f +} +bIsCorrect = (Math.abs(delta) <= TOLERANCE) +delta = xh - xl +j++ +} +if (!bIsCorrect) +return PtgErr(PtgErr.ERROR_NUM) + +if (DEBUG) +Logger.logInfo("Result from calcIRR= " + trial) +val pnum = PtgNumber(trial) +return pnum +} + +/** + * ISPMT Calculates the interest paid during a specific period of an + * investment. This function is provided for compatibility with Lotus 1-2-3. + * ISPMT(rate,per,nper,pv) Rate is the interest rate for the investment. Per + * is the period for which you want to find the interest, and must be + * between 1 and nper. Nper is the total number of payment periods for the + * investment. Pv is the present value of the investment. For a loan, pv is + * the loan amount. + */ + internal fun calcISPMT(operands:Array):Ptg { +if (operands.size < 4) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcISPMT") +val rate = operands[0].doubleVal +val per = operands[1].doubleVal +val nper = operands[2].doubleVal +val pv = operands[3].doubleVal +if (per < 0 || per > nper) +return PtgErr(PtgErr.ERROR_NUM) + +val result = (-pv * rate * (nper - per)) / nper +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcISPMT= " + result) +return pnum +} + +/** + * MDURATION Returns the Macauley modified duration for a security with an + * assumed par value of $100 + * DURATION(settlement,maturity,coupon,yld,frequency,basis) Settlement is + * the security's settlement date. Maturity is the security's maturity date. + * Coupon is the security's annual coupon rate. Yld is the security's annual + * yield. Frequency is the number of coupon payments per year. For annual + * payments, frequency = 1; for semiannual, frequency = 2; for quarterly, + * frequency = 4. Basis is the type of day count basis to use + */ + internal fun calcMDURATION(operands:Array):Ptg { +if (operands.size < 5) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcMDURATION") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val coupon = operands[2].doubleVal +val yld = operands[3].doubleVal +val frequency = operands[4].intVal +var basis = 0 +if (operands.size > 5) +basis = operands[5].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) +return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) +return PtgErr(PtgErr.ERROR_NUM) +if (coupon < 0 || yld < 0) +return PtgErr(PtgErr.ERROR_NUM) +if (!(frequency == 1 || frequency == 2 || frequency == 4)) +return PtgErr(PtgErr.ERROR_NUM) + +var result = calcDURATION(operands).doubleVal + // above is regular duration calculation; to get modified duration: + result = result / (1 + yld / frequency) +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcMDURATION= " + result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * MIRR Returns the internal rate of return where positive and negative cash + * flows are financed at different rates + * + * + * MIRR(values,finance_rate,reinvest_rate) + * + * + * Values is an array or a reference to cells that contain numbers. These + * numbers represent a series of payments (negative values) and income + * (positive values) occurring at regular periods. + * + * + * Values must contain at least one positive value and one negative value to + * calculate the modified internal rate of return. Otherwise, MIRR returns + * the #DIV/0! error value. + * + * + * If an array or reference argument contains text, logical values, or empty + * cells, those values are ignored; however, cells with the value zero are + * included. + * + * + * Finance_rate is the interest rate you pay on the money used in the cash + * flows. + * + * + * Reinvest_rate is the interest rate you receive on the cash flows as you + * reinvest them. + */ + internal fun calcMIRR(operands:Array):Ptg { +if (operands.size < 3) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcMIRR") +val finance_rate = operands[1].doubleVal +val reinvest_rate = operands[2].doubleVal +val params = PtgCalculator.getAllComponents(operands) +if (DEBUG) +debugOperands(params, "calcMIRR") // AFTER converting references + // ... + + // Get + Values and - Values in separate Ptg arrays + val posVals = Vector() +val negVals = Vector() +val positiveValues:Array +val negativeValues:Array +val n = params.size - 2 // skip last 2 params (= rates) +for (i in 0 until n) +{ +val `val` = params[i].doubleVal +if (`val` < 0) +negVals.addElement(params[i]) +else +posVals.addElement(params[i]) +} +positiveValues = arrayOfNulls(posVals.size + 1) +negativeValues = arrayOfNulls(negVals.size + 1) +System.arraycopy(posVals.toTypedArray(), 0, positiveValues, 1, posVals +.size) +System.arraycopy(negVals.toTypedArray(), 0, negativeValues, 1, negVals +.size) + + // add rate to Ptg array for call to calcNPV + positiveValues[0] = operands[2] // reinvest rate +negativeValues[0] = operands[1] // finance rate + + // Calculate MIRR from NPV values + var X = calcNPV(positiveValues).doubleVal +X = -1.0 * X * Math.pow(1 + reinvest_rate, posVals.size.toDouble()) +var Y = calcNPV(negativeValues).doubleVal +Y = Y * (1 + finance_rate) +val result = Math.pow(X / Y, 1.0 / (n - 1)) - 1 + +if (DEBUG) +Logger.logInfo("Result from calcMIRR= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * NOMINAL Returns the annual nominal interest rate + */ + internal fun calcNominal(operands:Array):Ptg { +if (operands.size < 2) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcNominal") +val effect = operands[0].doubleVal +val npery = operands[1].intVal + // TODO: if either is non-numeric, return #VALUE! + if (effect <= 0 || npery < 1) +return PtgErr(PtgErr.ERROR_NUM) + + // solve for nominal_rate in: + // effect= (1 + nominal_rate/npery)^npery -1 + // nominal_rate= ((10^(log10(y)/n)) - 1)*npery + val y = effect + 1 +val log10y = Math.log(y) / Math.log(10.0) // base 10 log +val z = Math.pow(10.0, log10y / npery) +val result = (z - 1) * npery +if (DEBUG) +Logger.logInfo("Result from calcNominal= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * NPER Returns the number of periods for an investment NPER(rate, pmt, pv, + * fv, type) + * + * + * Rate is the interest rate per period. Pmt is the payment made each + * period; it cannot change over the life of the annuity. Typically, pmt + * contains principal and interest but no other fees or taxes. Pv is the + * present value, or the lump-sum amount that a series of future payments is + * worth right now. Fv is the future value, or a cash balance you want to + * attain after the last payment is made. If fv is omitted, it is assumed to + * be 0 (the future value of a loan, for example, is 0). Type is the number + * 0 or 1 and indicates when payments are due. + */ + internal fun calcNPER(operands:Array):Ptg { +if (operands.size < 3) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcNPER") +val rate = operands[0].doubleVal +val pmt = operands[1].doubleVal +val pv = operands[2].doubleVal +var fv = 0.0 +if (operands.size > 3 && !(operands[3] is PtgMissArg)) +fv = operands[3].doubleVal +var type = 0 +if (operands.size > 4) +type = operands[4].intVal + +val A = pmt * (1 + type * rate) - rate * fv +val B = pmt * (1 + type * rate) + rate * pv +val C = 1 + rate +val result = Math.log(A / B) / Math.log(C) +if (DEBUG) +Logger.logInfo("Result from calcNPER= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * NPV(rate,value1,value2, ...) Calculates the net present value of an + * investment by using a discount rate and a series of future payments + * (negative values) and income (positive values). + * + * + * Rate is the rate of discount over the length of one period. Value1, + * value2, ... are 1 to 29 arguments representing the payments and income. + * Value1, value2, ... must be equally spaced in time and occur at the end + * of each period + * + * + * Returns the net present value of an investment based on a series of + * periodic cash flows and a discount rate = Sum(values / (1 + rate) ) + */ + internal fun calcNPV(operands:Array):Ptg { +if (operands.size < 2 || operands[0].components != null) +{ // not + // supported + // by + // function + val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) debugOperands(operands, "calcNPV") +val params = PtgCalculator.getAllComponents(operands) +if (DEBUG) debugOperands(params, "calcNPV") // AFTER converting references ... +val rate = params[0].doubleVal +val n = Math.min(params.size, 30) // at most 29 values +var result = 0.0 +for (i in 1 until n) +{ +val valuei = params[i].doubleVal + // TODO: if valuei is an error, empty, etc., ignore + result += valuei / Math.pow(1 + rate, i.toDouble()) +} +if (DEBUG) Logger.logInfo("Result from calcNPV= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * ODDFPRICE Returns the price per $100 face value of a security with an odd + * first period + * ODDFPRICE(settlement,maturity,issue,first_coupon,rate,yld,redemption,frequency,basis) + */ + @Throws(FunctionNotSupportedException::class) +internal fun calcODDFPRICE(operands:Array):Ptg { +if (true) +{ +val wn = "WARNING: this version of OpenXLS does not support the formula ODDFPRICE." +Logger.logWarn(wn) +throw FunctionNotSupportedException(wn) +} +if (operands.size < 8) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcODDFPRICE") +try +{ +val sDate = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val iDate = DateConverter.getCalendarFromNumber(operands[2].value) as GregorianCalendar +val fcDate = DateConverter.getCalendarFromNumber(operands[3].value) as GregorianCalendar +val rate = operands[4].doubleVal +val yld = operands[5].doubleVal +val redemption = operands[6].doubleVal +val frequency = operands[7].intVal +var basis = 0 +if (operands.size > 8) +basis = operands[8].intVal + +val settlementDate = (DateConverter.getXLSDateVal(sDate)).toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)).toLong() +val issueDate = (DateConverter.getXLSDateVal(iDate)).toLong() +val firstCouponDate = (DateConverter.getXLSDateVal(fcDate)).toLong() + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) return PtgErr(PtgErr.ERROR_NUM) +if (yld < 0 || rate < 0) return PtgErr(PtgErr.ERROR_NUM) +if (redemption <= 0) return PtgErr(PtgErr.ERROR_NUM) + /*maturity > first_coupon > settlement > issue */ + if ((issueDate >= settlementDate || settlementDate >= firstCouponDate || +firstCouponDate >= maturityDate)) +return PtgErr(PtgErr.ERROR_NUM) + + +val ops = arrayOfNulls(4) +ops[0] = PtgNumber(settlementDate.toDouble()) +ops[1] = PtgNumber(maturityDate.toDouble()) +ops[2] = PtgInt(frequency) +ops[3] = PtgInt(basis) +val A = calcCoupDayBS(ops).doubleVal // days from beg. coupon period to settlement +val DSC = calcCoupDaysNC(ops).doubleVal // days from settlement to next coupon +val E = calcCoupDays(ops).doubleVal // total # days in coupon period +val N = PtgCalculator.getLongValue(calcCoupNum(ops)).toDouble() // n is the number of coupons btwn settlement and maturity +val NCD = calcCoupNCD(ops).doubleVal // next coupon after 1st coupon date????? +val DFC = (firstCouponDate - settlementDate).toDouble() // # days from odd first coupon to next coupon date +val z = getDaysFromBasis(basis, settlementDate, firstCouponDate).toDouble() + +val R = 100 * rate / frequency +val Y = 1 + yld / frequency + +var result = 0.0 +if (DFC < E) +{ // odd short first coupon +val firstTerm = redemption / Math.pow(Y, N - 1 + DSC / E) +val secondTerm = (R * DFC / E) / Math.pow(Y, DSC / E) +var thirdTerm = 0.0 +var i = 2 +while (i <= N) +{ +thirdTerm += R / Math.pow(Y, i - 1 + DSC / E) +i++ +} +val fourthTerm = R * A / E +result = firstTerm + secondTerm + thirdTerm - fourthTerm +} +else +{ // odd long first coupon + +} + +if (DEBUG) Logger.logInfo("Result from calcODDFPRICE= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + + /* + * ODDFYIELD Returns the yield of a security with an odd first period + * NOT COMPLETED!! + */ + @Throws(FunctionNotSupportedException::class) +internal fun calcODDFYIELD(operands:Array):Ptg { +if (true) +{ +val wn = "WARNING: this version of OpenXLS does not support the formula ODDFYIELD." +Logger.logWarn(wn) +throw FunctionNotSupportedException(wn) +} +if (operands.size < 8) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcODDFYIELD") +try +{ +val sDate = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val iDate = DateConverter.getCalendarFromNumber(operands[2].value) as GregorianCalendar +val fcDate = DateConverter.getCalendarFromNumber(operands[3].value) as GregorianCalendar +val rate = operands[4].doubleVal +val yld = operands[5].doubleVal +val redemption = operands[6].doubleVal +val frequency = operands[7].intVal +var basis = 0 +if (operands.size > 8) +basis = operands[8].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) return PtgErr(PtgErr.ERROR_NUM) +if (yld < 0 || rate < 0) return PtgErr(PtgErr.ERROR_NUM) +if (redemption <= 0) return PtgErr(PtgErr.ERROR_NUM) + +val settlementDate = (DateConverter.getXLSDateVal(sDate)).toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)).toLong() +val issueDate = (DateConverter.getXLSDateVal(iDate)).toLong() +val firstCouponDate = (DateConverter.getXLSDateVal(fcDate)).toLong() + + /* + * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC + */ + val ops = arrayOfNulls(4) +ops[0] = PtgNumber(settlementDate.toDouble()) +ops[1] = PtgNumber(maturityDate.toDouble()) +ops[2] = PtgInt(frequency) +ops[3] = PtgInt(basis) +val A = calcCoupDayBS(ops).doubleVal // days from beg. coupon period to settlement +val DFC = getDaysFromBasis(basis, firstCouponDate, A.toLong()).toDouble() // days from 1st odd coupon to 1st coupon +val DSC = calcCoupDaysNC(ops).doubleVal // days from settlement to next coupon +val E = calcCoupDays(ops).doubleVal // total # days in coupon period +val N = PtgCalculator.getLongValue(calcCoupNum(ops)).toDouble() // n is the number of coupons btwn settlement and maturity +val R = 100 * rate / frequency +val Y = 1 + yld / frequency + +val result = 0.0 + +if (DEBUG) Logger.logInfo("Result from calcODDFYIELD= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + + /* + * ODDLPRICE Returns the price per $100 face value of a security with an odd + * last period + * NOT COMPLETED!!!! + */ + @Throws(FunctionNotSupportedException::class) +internal fun calcODDLPRICE(operands:Array):Ptg { +if (true) +{ +val wn = "WARNING: this version of OpenXLS does not support the formula ODDLPRICE." +Logger.logWarn(wn) +throw FunctionNotSupportedException(wn) +} +if (operands.size < 8) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) debugOperands(operands, "calcODDLPRICE") +try +{ +val sDate = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val iDate = DateConverter.getCalendarFromNumber(operands[2].value) as GregorianCalendar +val fcDate = DateConverter.getCalendarFromNumber(operands[3].value) as GregorianCalendar +val rate = operands[4].doubleVal +val yld = operands[5].doubleVal +val redemption = operands[6].doubleVal +val frequency = operands[7].intVal +var basis = 0 +if (operands.size > 8) +basis = operands[8].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) return PtgErr(PtgErr.ERROR_NUM) +if (yld < 0 || rate < 0) return PtgErr(PtgErr.ERROR_NUM) +if (redemption <= 0) return PtgErr(PtgErr.ERROR_NUM) + +val settlementDate = (DateConverter.getXLSDateVal(sDate)).toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)).toLong() +val issueDate = (DateConverter.getXLSDateVal(iDate)).toLong() +val firstCouponDate = (DateConverter.getXLSDateVal(fcDate)).toLong() + + /* + * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC + */ + val ops = arrayOfNulls(4) +ops[0] = PtgNumber(settlementDate.toDouble()) +ops[1] = PtgNumber(maturityDate.toDouble()) +ops[2] = PtgInt(frequency) +ops[3] = PtgInt(basis) +val A = calcCoupDayBS(ops).doubleVal // days from beg. coupon period to settlement +val DFC = getDaysFromBasis(basis, firstCouponDate, A.toLong()).toDouble() // days from 1st odd coupon to 1st coupon +val DSC = calcCoupDaysNC(ops).doubleVal // days from settlement to next coupon +val E = calcCoupDays(ops).doubleVal // total # days in coupon period +val N = PtgCalculator.getLongValue(calcCoupNum(ops)).toDouble() // n is the number of coupons btwn settlement and maturity +val R = 100 * rate / frequency +val Y = 1 + yld / frequency + +val result = 0.0 + +if (DEBUG) Logger.logInfo("Result from calcODDLPRICE= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + + /* + * ODDLYIELD Returns the yield of a security with an odd last period + * NOT COMPLETED!! + */ + @Throws(FunctionNotSupportedException::class) +internal fun calcODDLYIELD(operands:Array):Ptg { +if (true) +{ +val wn = "WARNING: this version of OpenXLS does not support the formula ODDLYIELD." +Logger.logWarn(wn) +throw FunctionNotSupportedException(wn) +} +if (operands.size < 8) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) debugOperands(operands, "calcODDLYIELD") +try +{ +val sDate = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val iDate = DateConverter.getCalendarFromNumber(operands[2].value) as GregorianCalendar +val fcDate = DateConverter.getCalendarFromNumber(operands[3].value) as GregorianCalendar +val rate = operands[4].doubleVal +val yld = operands[5].doubleVal +val redemption = operands[6].doubleVal +val frequency = operands[7].intVal +var basis = 0 +if (operands.size > 8) +basis = operands[8].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) return PtgErr(PtgErr.ERROR_NUM) +if (yld < 0 || rate < 0) return PtgErr(PtgErr.ERROR_NUM) +if (redemption <= 0) return PtgErr(PtgErr.ERROR_NUM) + +val settlementDate = (DateConverter.getXLSDateVal(sDate)).toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)).toLong() +val issueDate = (DateConverter.getXLSDateVal(iDate)).toLong() +val firstCouponDate = (DateConverter.getXLSDateVal(fcDate)).toLong() + + /* + * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC + */ + val ops = arrayOfNulls(4) +ops[0] = PtgNumber(settlementDate.toDouble()) +ops[1] = PtgNumber(maturityDate.toDouble()) +ops[2] = PtgInt(frequency) +ops[3] = PtgInt(basis) +val A = calcCoupDayBS(ops).doubleVal // days from beg. coupon period to settlement +val DFC = getDaysFromBasis(basis, firstCouponDate, A.toLong()).toDouble() // days from 1st odd coupon to 1st coupon +val DSC = calcCoupDaysNC(ops).doubleVal // days from settlement to next coupon +val E = calcCoupDays(ops).doubleVal // total # days in coupon period +val N = PtgCalculator.getLongValue(calcCoupNum(ops)).toDouble() // n is the number of coupons btwn settlement and maturity +val R = 100 * rate / frequency +val Y = 1 + yld / frequency + +val result = 0.0 + +if (DEBUG) Logger.logInfo("Result from calcODDLYIELD= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * PMT Returns the periodic payment for an annuity + * + * + * pmt= - (fv + (1+rate)**nper * pv) * rate / ( ( (1+rate)**nper - 1 ) * + * (1+rate*type) ) + * + * + * if fv or type are omitted they should be treated as 0 values. + */ + internal fun calcPmt(operands:Array):Ptg { +if (operands.size < 3) +{ // not + // supported + // by + // function + val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +val rate:Double +val nper:Double +val pv:Double +val fv:Double +val type:Double +rate = Double((operands[0].value).toString()).toDouble() +nper = Double((operands[1].value).toString()).toDouble() +pv = Double((operands[2].value).toString()).toDouble() +if (operands.size > 3) +{ +if (operands[3] is PtgMissArg) +{ +fv = 0.0 +} +else +{ +fv = Double((operands[3].value).toString()) +.toDouble() +} +} +else +{ +fv = 0.0 +} +if (operands.size > 4) +{ +if (operands[4] is PtgMissArg) +{ +type = 0.0 +} +else +{ +type = Double((operands[4].value).toString()) +.toDouble() +} +} +else +{ +type = 0.0 +} + + //KSC: For some strange odd weird reason, original calculation was off + // even though this should be exactly the same thing. Go figure! + val Rn = Math.pow(1 + rate, nper) +val A = -fv * rate - pv * Rn * rate +val B = (Rn - 1) * (1 + rate * type) +val result = A / B +val pnum = PtgNumber(result) +return pnum +} + +/** + * PPMT Returns the payment on the principal for an investment for a given + * period PPMT(rate,per,nper,pv,fv,type) Rate is the interest rate per + * period. Per specifies the period and must be in the range 1 to nper. Nper + * is the total number of payment periods in an annuity. Pv is the present + * value the total amount that a series of future payments is worth now. + * Fv is the future value, or a cash balance you want to attain after the + * last payment is made. If fv is omitted, it is assumed to be 0 (zero), + * that is, the future value of a loan is 0. Type is the number 0 or 1 and + * indicates when payments are due + */ + internal fun calcPPMT(operands:Array):Ptg { +if (operands.size < 4) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcPPMT") +val rate = operands[0].doubleVal +val per = operands[1].intVal +val nper = operands[2].intVal +val pv = operands[3].doubleVal +var fv = 0.0 +var type = 0 +if (operands.size > 4 && !(operands[4] is PtgMissArg)) +fv = operands[4].doubleVal +if (operands.size > 5) +type = operands[5].intVal + +val result:Double + // 1st, get payment for entire period + val Rn = Math.pow(1 + rate, nper.toDouble()) +var A = -fv * rate - pv * Rn * rate +var B = (Rn - 1) * (1 + rate * type) +val pmt = A / B + +val n:Double +if (type == 0) +n = per.toDouble() +else +n = (per - 1).toDouble() + // FVa + A = Math.pow(1 + rate, n) +B = pmt * (1 + rate * type) +B *= (Math.pow(1 + rate, n) - 1) / rate +val fva = -(pv * A + B) + // FVb + A = Math.pow(1 + rate, n - 1) +B = pmt * (1 + rate * type) +B *= (Math.pow(1 + rate, n - 1) - 1) / rate +val fvb = -(pv * A + B) + +result = fvb - fva +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcPPMT= " + result) +return pnum + +} + +/** + * PRICE Returns the price per $100 face value of a security that pays + * periodic interest + * PRICE(settlement,maturity,rate,yld,redemption,frequency,basis) Settlement + * is the security's settlement date. The security settlement date is the + * date after the issue date when the security is traded to the buyer. + * Maturity is the security's maturity date. The maturity date is the date + * when the security expires. Rate is the security's annual coupon rate. Yld + * is the security's annual yield. Redemption is the security's redemption + * value per $100 face value. Frequency is the number of coupon payments per + * year. For annual payments, frequency = 1; for semiannual, frequency = 2; + * for quarterly, frequency = 4. Basis is the type of day count basis to + * use. + */ + internal fun calcPRICE(operands:Array):Ptg { +if (operands.size < 6) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcPRICE") +try +{ +val sDate = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val rate = operands[2].doubleVal +val yld = operands[3].doubleVal +val redemption = operands[4].doubleVal +val frequency = operands[5].intVal +var basis = 0 +if (operands.size > 6) +basis = operands[6].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) +return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) +return PtgErr(PtgErr.ERROR_NUM) +if (yld < 0 || rate < 0) +return PtgErr(PtgErr.ERROR_NUM) +if (redemption <= 0) +return PtgErr(PtgErr.ERROR_NUM) + +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() + + /* + * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC + */ + val ops = arrayOfNulls(4) +ops[0] = operands[0] +ops[1] = operands[1] +ops[2] = operands[5] +ops[3] = PtgInt(basis) +val DSC = calcCoupDaysNC(ops).doubleVal // days from settlement to next coupon +val E = calcCoupDays(ops).doubleVal // total # days in coupon period in which settlementfalls +val N = PtgCalculator.getLongValue(calcCoupNum(ops)).toDouble() // n is the number of coupons btwn settlement and maturity +val A = calcCoupDayBS(ops).doubleVal // days from beg. coupon period to settlement +val R = rate / frequency +val Y = 1 + yld / frequency +var result = redemption / Math.pow(Y, N - 1 + DSC / E) +var i = 1 +while (i <= N) +{ +result += (R * 100) / Math.pow(Y, i - 1 + DSC / E) +i++ +} +result -= (100.0 * R * A / E) + +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcPRICE= " + result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * PRICEDISC Returns the price per $100 face value of a discounted security + * + * + * PRICEDISC(settlement,maturity,discount,redemption,frequency,basis) + * Settlement is the security's settlement date. Maturity is the security's + * maturity date. Discount is the security's discount rate. Redemption is + * the security's redemption value per $100 face value. Basis is the type of + * day count basis to use. + */ + internal fun calcPRICEDISC(operands:Array):Ptg { +if (operands.size < 4) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcPRICEDISC") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val discount = operands[2].doubleVal +val redemption = operands[3].doubleVal +var basis = 0 +if (operands.size > 4) +basis = operands[4].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) +return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) +return PtgErr(PtgErr.ERROR_NUM) +if (redemption <= 0) +return PtgErr(PtgErr.ERROR_NUM) + +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() + + /* + * Ptg[] ops; if (operands.length > 6) ops = new Ptg[4]; else ops = new + * Ptg[3]; ops[0] = operands[0]; ops[1] = operands[1]; ops[2] = + * operands[5]; if (operands.length > 6) ops[3] = operands[6]; + */ + val result = (redemption - ((discount * redemption * yearFrac(basis, settlementDate, +maturityDate)))) +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcPRICEDISC= " + result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * PRICEMAT Returns the price per $100 face value of a security that pays + * interest at maturity + */ + internal fun calcPRICEMAT(operands:Array):Ptg { +if (operands.size < 4) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcPRICEMAT") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val iDate = DateConverter +.getCalendarFromNumber(operands[2].value) as GregorianCalendar +val rate = operands[3].doubleVal +val yld = operands[4].doubleVal +var basis = 0 +if (operands.size > 5) +basis = operands[5].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) return PtgErr(PtgErr.ERROR_NUM) +if (rate < 0 || yld < 0) return PtgErr(PtgErr.ERROR_NUM) + +val settlementDate = (DateConverter.getXLSDateVal(sDate)).toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)).toLong() +val issueDate = (DateConverter.getXLSDateVal(iDate)).toLong() + +val B = getDaysInYearFromBasis(basis, settlementDate, maturityDate) +val DSM = getDaysFromBasis(basis, settlementDate, maturityDate).toDouble() +val DIM = getDaysFromBasis(basis, issueDate, maturityDate).toDouble() +val A = getDaysFromBasis(basis, issueDate, settlementDate).toDouble() + +var result = (100 + (DIM / B) * rate * 100.0) / (1 + ((DSM / B) * yld)) +result -= (A / B) * rate * 100.0 +val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcPRICEMAT= " + result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * PV(rate,nper,pmt,fv,type) Returns the present value of an investment Rate + * is the interest rate per period. For example, if you obtain an automobile + * loan at a 10 percent annual interest rate and make monthly payments, your + * interest rate per month is 10%/12, or 0.83%. You would enter 10%/12, or + * 0.83%, or 0.0083, into the formula as the rate. Nper is the total number + * of payment periods in an annuity. For example, if you get a four-year car + * loan and make monthly payments, your loan has 4*12 (or 48) periods. You + * would enter 48 into the formula for nper. Pmt is the payment made each + * period and cannot change over the life of the annuity. Typically, pmt + * includes principal and interest but no other fees or taxes. For example, + * the monthly payments on a $10,000, four-year car loan at 12 percent are + * $263.33. You would enter -263.33 into the formula as the pmt. If pmt is + * omitted, you must include the fv argument. Fv is the future value, or a + * cash balance you want to attain after the last payment is made. If fv is + * omitted, it is assumed to be 0 (the future value of a loan, for example, + * is 0). For example, if you want to save $50,000 to pay for a special + * project in 18 years, then $50,000 is the future value. You could then + * make a conservative guess at an interest rate and determine how much you + * must save each month. If fv is omitted, you must include the pmt + * argument. Type is the number 0 or 1 and indicates when payments are due. + * 0 or omitted At the end of the period 1 At the beginning of the period + */ + internal fun calcPV(operands:Array):Ptg { +if (operands.size < 3) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcPV") +val rate = operands[0].doubleVal +val nper = operands[1].doubleVal +var pmt = 0.0 + // TODO: No specified error trapping? + if (!(operands[2] is PtgMissArg)) +pmt = operands[2].doubleVal +var fv = 0.0 +var type = 0 +if (operands.size > 3 && !(operands[3] is PtgMissArg)) +fv = operands[3].doubleVal +if (operands.size > 4) +type = operands[4].intVal + +val A = Math.pow(1 + rate, nper) +var B = pmt * (1 + rate * type) +B *= (A - 1) / rate +val result = (-fv - B) / A + + //double testresults= (result * A + pmt*(1+rate*type) * (A-1)/rate + fv); + //testresults must==0 for Pv to be correct + + + val pnum = PtgNumber(result) +if (DEBUG) +Logger.logInfo("Result from calcPV= " + result) +return pnum + +} + +/** + * RATE Returns the interest rate per period of an annuity. RATE is + * calculated by iteration and can have zero or more solutions. If the + * successive results of RATE do not converge to within 0.0000001 after 20 + * iterations, RATE returns the #NUM! error value. + * + * + * RATE(nper,pmt,pv,fv,type,guess) + * + * + * Nper is the total number of payment periods in an annuity. Pmt is the + * payment made each period and cannot change over the life of the annuity. + * Typically, pmt includes principal and interest but no other fees or + * taxes. If pmt is omitted, you must include the fv argument. Pv is the + * present value the total amount that a series of future payments is + * worth now. Fv is the future value, or a cash balance you want to attain + * after the last payment is made. If fv is omitted, it is assumed to be 0 + * (the future value of a loan, for example, is 0). Type is the number 0 or + * 1 and indicates when payments are due. Guess is your guess for what the + * rate will be. + */ + internal fun calcRate(operands:Array):Ptg { +if (operands.size < 3) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcRate") +val nper = operands[0].doubleVal +val pmt = operands[1].doubleVal +var pv = 0.0 +var fv = 0.0 +var type = 0 +var guess = 0.1 +if (!(operands[2] is PtgMissArg)) +pv = operands[2].doubleVal +if (operands.size > 3) +{ +if (!(operands[3] is PtgMissArg)) +fv = operands[3].doubleVal +} +if (operands.size > 4) +{ +if (!(operands[4] is PtgMissArg)) +type = operands[4].intVal +} +if (operands.size > 5) +guess = operands[5].doubleVal + // validate params + if (type != 0 && type != 1) +return PtgErr(PtgErr.ERROR_NUM) +if (pv == 0.0 && fv == 0.0) +return PtgErr(PtgErr.ERROR_NUM) + + // iterate over possible Rate values; value is correct when + // f <= tolerance, defined as .00001% + val TOLERANCE = 0.000000001 +var bIsCorrect = false +var x0 = guess +var x1 = x0 +val fx0:Double +val fprimex0:Double + // iterate, using Newton's approximation + var j = 0 +while (j < 100 && !bIsCorrect) +{ // maximum 20 tries + // Calculate f(x) = (a+ f*g*h + c) + val R = Math.pow(1 + x0, nper) +val U = 1 / x0 +val a = pv * R +val f = pmt * (1 + x0 * type) +val g = R - 1 +val h = U +fx0 = a + f * g * U + fv + // + // (a + f*g*h + c)' + // = a' + f'gh + fg'h + fgh' + val T = Math.pow(1 + x0, nper - 1) +val aprime = pv * nper * T +val fprime = pmt * type +val gprime = nper * T +val hprime = -1 * Math.pow(x0, -2.0) + +fprimex0 = aprime + fprime * g * h + f * gprime * h + f * g * hprime + // calculate x1, the next iteration + x1 = x0 - fx0 / fprimex0 +val delta = x1 - x0 +bIsCorrect = (Math.abs(delta) <= TOLERANCE) +x0 = x1 +j++ +} + + /* THIS CALCULATION IS OFF!!! + // x= rate + // f(x)= PV + PMT*((1-(1+x)^-NPER)/x)*(1+x)^TYPE + FV*(1+x)^-NPER = 0 + // f'(x)= + double x0, f0, trial, f; + x0 = 0; + f0 = pv * pmt + type + fv; + trial = guess; + + // iterate, using Newton's approximation + double delta; + double fprime = 0; // derivative of f(x0) + for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 20 tries + //trial= xl+delta*fl/(fl-fh); + // Calculate f(x) + double R = Math.pow(1 + trial, -nper); + double T = Math.pow(1 + trial, type); + f = pv + pmt * ((1 - R) / trial) * T + fv * R; + // Calculate f'(x) + double gofx = fv * R; + double gprimex = fv * -nper * Math.pow(1 + trial, -nper - 1); + double hofx = T; + double hprimex = type * Math.pow(1 + trial, type - 1); + double zofx = (1 - R) / trial; + double zprimex = (1 + nper * Math.pow(1 + trial, -nper - 1)) + / Math.pow(trial, 2); + fprime = zprimex * hofx + zofx * hprimex + gprimex; + delta = f / fprime; + // testing! + if (trial - delta <= 0) + delta = trial / 2; + trial -= delta; + bIsCorrect = (Math.abs(delta) <= TOLERANCE); + } +/**/ + if (!bIsCorrect) +return PtgErr(PtgErr.ERROR_NUM) + +if (DEBUG) +Logger.logInfo("Result from calcRate= " + x1) +val pnum = PtgNumber(x1) +return pnum +} + +/** + * RECEIVED Returns the amount received at maturity for a fully invested + * security + */ + internal fun calcReceived(operands:Array):Ptg { +if (operands.size < 4) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcRECEIVED") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val investment = operands[2].doubleVal +val rate = operands[3].doubleVal +var basis = 0 +if (operands.size > 4) +basis = operands[4].intVal + +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() + + // double DSM = maturityDate - settlementDate; + // double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); + // double result = investment / (1 - (rate * DSM / B)); + val result = investment / (1 - (rate * yearFrac(basis, settlementDate, maturityDate))) +if (DEBUG) +Logger.logInfo("Result from calcRECEIVED= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * SLN Returns the straight-line depreciation of an asset for one period + * SLN(cost,salvage,life) Cost is the initial cost of the asset. Salvage is + * the value at the end of the depreciation (sometimes called the salvage + * value of the asset). Life is the number of periods over which the asset + * is depreciated (sometimes called the useful life of the asset). + */ + internal fun calcSLN(operands:Array):Ptg { +if (operands.size < 3) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +val cost = operands[0].doubleVal +val salvage = operands[1].doubleVal +val life = operands[2].doubleVal +if (DEBUG) +debugOperands(operands, "calcSLN") +if (life == 0.0) +return PtgErr(PtgErr.ERROR_NUM) +val result = (cost - salvage) / life +if (DEBUG) +Logger.logInfo("Result from calcSLN= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * SYD Returns the sum-of-years' digits depreciation of an asset for a + * specified period SYD(cost,salvage,life,per) Cost is the initial cost of + * the asset. Salvage is the value at the end of the depreciation (sometimes + * called the salvage value of the asset). Life is the number of periods + * over which the asset is depreciated (sometimes called the useful life of + * the asset). Per is the period and must use the same units as life. + */ + internal fun calcSYD(operands:Array):Ptg { +if (operands.size < 4) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcSYD") +val cost = operands[0].doubleVal +val salvage = operands[1].doubleVal +val life = operands[2].doubleVal +val per = operands[3].doubleVal +if (life == 0.0) +return PtgErr(PtgErr.ERROR_NUM) +val A = (cost - salvage) * (life - per + 1) * 2.0 +val B = life * (life + 1) +val result = A / B +if (DEBUG) +Logger.logInfo("Result from calcSYD= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * TBILLEQ Returns the bond-equivalent yield for a Treasury bill Settlement + * is the Treasury bill's settlement date. The security settlement date is + * the date after the issue date when the Treasury bill is traded to the + * buyer. Maturity is the Treasury bill's maturity date. The maturity date + * is the date when the Treasury bill expires. Discount is the Treasury + * bill's discount rate. + */ + internal fun calcTBillEq(operands:Array):Ptg { +if (operands.size < 3) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcTBILLEQ") +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val rate = operands[2].doubleVal + +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() +if (settlementDate >= maturityDate) +return PtgErr(PtgErr.ERROR_NUM) +if (maturityDate - settlementDate > 365) +return PtgErr(PtgErr.ERROR_NUM) +if (rate <= 0) +return PtgErr(PtgErr.ERROR_NUM) + +val DSM = (maturityDate - settlementDate).toDouble() +val result:Double +if (DSM <= 182) +{ +result = (365 * rate) / (360 - rate * DSM) +} +else +{ +val A = DSM / 365 +val B = rate * DSM + +val C = (2 * A - 1) * B / (B - 360) +val D = Math.pow(A, 2.0) - C +result = (-2 * A + 2 * Math.sqrt(D)) / (2 * A - 1) +} +if (DEBUG) +Logger.logInfo("Result from calcTBILLEQ= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * TBILLPRICE Returns the price per $100 face value for a Treasury bill + */ + internal fun calcTBillPrice(operands:Array):Ptg { +if (operands.size < 3) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcTBILLPRICE") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val rate = operands[2].doubleVal +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() + +if (settlementDate >= maturityDate) +return PtgErr(PtgErr.ERROR_NUM) +if (maturityDate - settlementDate > 365) +return PtgErr(PtgErr.ERROR_NUM) +if (rate <= 0) +return PtgErr(PtgErr.ERROR_NUM) + +val DSM = (maturityDate - settlementDate).toDouble() +val result = 100 * (1 - (rate * DSM / 360)) +if (DEBUG) +Logger.logInfo("Result from calcTBILLPRICE= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * TBILLYIELD Returns the yield for a Treasury bill + * + * + * VDB Returns the depreciation of an asset for a specified or partial + * period using a declining balance method + */ + internal fun calcTBillYield(operands:Array):Ptg { +if (operands.size < 3) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcTBILLYIELD") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val price = operands[2].doubleVal + +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() +if (settlementDate >= maturityDate) +return PtgErr(PtgErr.ERROR_NUM) +if (maturityDate - settlementDate > 365) +return PtgErr(PtgErr.ERROR_NUM) +if (price <= 0) +return PtgErr(PtgErr.ERROR_NUM) + +val DSM = (maturityDate - settlementDate).toDouble() +val result = ((100 - price) / price) * (360 / DSM) +if (DEBUG) +Logger.logInfo("Result from calcTBILLYIELD= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * VDB + * Returns the depreciation of an asset for any period you specify, + * including partial periods, using the double-declining balance method or + * some other method you specify. VDB stands for variable declining balance. + * + * + * VDB(cost,salvage,life,start_period,end_period,factor,no_switch) + */ + internal fun calcVDB(operands:Array):Ptg { +if (operands.size < 5) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) debugOperands(operands, "calcVDB") +val cost = operands[0].doubleVal +val salvage = operands[1].doubleVal +val life = operands[2].intVal +val start_period = operands[3].intVal +val end_period = operands[4].intVal +var factor = 2 +if (operands.size > 5 && !(operands[5] is PtgMissArg)) +factor = operands[5].intVal +var bNoSwitch = false +if (operands.size > 6) +bNoSwitch = PtgCalculator.getBooleanValue(operands[6]) + +if ((cost <= 0 || salvage <= 0 || life <= 0 || +start_period < 0 || end_period < 0 || +factor < 0 || end_period > life)) +return PtgErr(PtgErr.ERROR_NUM) + +var result = 0.0 + +val ops = arrayOfNulls(5) +ops[0] = PtgNumber(cost) +ops[1] = PtgNumber(salvage) +ops[2] = PtgInt(life) +ops[4] = PtgInt(factor) +if (bNoSwitch) +{ // just sum ddb +for (i in start_period + 1..end_period) +{ +ops[3] = PtgInt(i) +result += calcDDB(ops).doubleVal +} +} +else +{ // switch to straight-line depreciation when dep. > ddb calc +var bSwitch = false +var A = 0.0 +var i = start_period + 1 +while (i <= end_period && !bSwitch) +{ +val sl = (cost - A - salvage) / (life - i + 1) +ops[3] = PtgInt(i) +val ddb = calcDDB(ops).doubleVal +if (sl <= ddb) +{ +A += ddb +i++ +} +else + // straight-line depreciation is greater than ddb; switch + bSwitch = true +} +result = A + // use straight-line depreciation for rest of period + for (j in i..end_period) +{ +result += (cost - A - salvage) / (life - i + 1) +} +} + +if (DEBUG) Logger.logInfo("Result from calcVDB= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * XIRR Returns the internal rate of return for a schedule of cash flows + * that is not necessarily periodic + */ + internal fun calcXIRR(operands:Array):Ptg { +if (operands.size < 2) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcXIRR") +var guess = .1 +if (operands.size > 2) +guess = operands[2].doubleVal + // convert references to Ptg[]s + val values = PtgCalculator.getAllComponents(operands[0]) +val dates = PtgCalculator.getAllComponents(operands[1]) +if (DEBUG) +debugOperands(values, "calcXIRR") +if (DEBUG) +debugOperands(dates, "calcXIRR") + /* + * 'Newton-Raphson method: ' Given PV a function of X, determine the + * value of X ' such that SUM[PV] = 0, using iteration. ' dPVdX = + * derivative of PV with respect to X ' NB: if X = 1 + Rate, dX/dRate = + * 1 ' ' Calculate PV and dPVdX using an arbitrary initial estimate of + * X. ' Change X by -PV/dPVdX and calculate again. ' Repeat until X + * changes by less than some arbitrary small amount. + * + * Let X = 1 + Rate + * Let EXPi = -YearFrax(i) + * PVi = Val(i) * X^(EXPi) + * dPV/dX = Val(i) * EXPi * X^(EXPi- 1) + * = Val(i) * X^(EXPi) *EXPi / X + * = PVi * EXPi / X + * dPVSum/dX = SUM[dPV/dX] + * dPVSum/dX = 1/X * SUM[PVi * EXPi] + * + */ + if (values.size != dates.size) +return PtgErr(PtgErr.ERROR_NUM) + // validate dates + val date0 = dates[0].doubleVal +for (i in 1 until dates.size) +{ + // TODO: if not valid date, return PtgErr.ERROR_VALUE + val `val` = PtgCalculator.getLongValue(dates[i]) +if (`val` < date0) +return PtgErr(PtgErr.ERROR_NUM) +} + // validate values: sum all outflows (= negative values) + inflows (= + // positive values) + var outflow = 0.0 +var inflow = 0.0 + // get outflow (- values) + for (i in values.indices) +{ +val `val` = values[i].doubleVal +if (`val` < 0) +outflow += Math.abs(`val`) +else +inflow += `val` +} +if (outflow == 0.0 || inflow == 0.0) +return PtgErr(PtgErr.ERROR_NUM) + +val n = values.size + + // iterate over possible irr values; value is correct when + // f <= tolerance, defined as .000001% + var bIsCorrect = false +val TOLERANCE = 0.00000001 +var trial = guess +val x0:Double +val f0:Double +val f:Double +x0 = 1.0 //1+0 rate, x_0, lower bounds of guess +f0 = inflow - outflow // =sum of values when rate is 0 +trial = 1 + guess // x_1, upper bounds + // for secant method + // double f1= 0.0; + // double x1; + // double dx= xh-xl; + var delta = 0.0 +var fprime = 0.0 // derivative of f(x0) +for (i in 0 until n) +{ +val `val` = values[i].doubleVal +val exp = (PtgCalculator.getLongValue(dates[i]) - date0) / 365 + // f+= val*(Math.pow(xh, -exp)); + fprime += `val` * -exp // f' of x0 +} +var j = 0 +while (j < 100 && !bIsCorrect) +{ // maximum 100 tries + // SECANT METHOD: trial= x0+dx*f0/(f0-f1); + // NEWTON'S: + f = 0.0 +for (i in 0 until n) +{ +val `val` = values[i].doubleVal +val exp = (dates[i].doubleVal - date0) / 365.0 +f += `val` * (Math.pow(trial, -exp)) +fprime += `val` * (Math.pow(trial, -exp)) * -exp // derivative of + // f(trial) + } +fprime = fprime / trial // final f' expression +delta = f / fprime +if (trial - delta <= 0) +delta = trial / 2 +trial -= delta + /* + * secant method if (f < 0) { delta= xl-trial; xl= trial; fl= f; } + * else { delta= xh-trial; xh= trial; fh= f; } dx= xh-xl; + */ + bIsCorrect = (Math.abs(delta) <= TOLERANCE) +j++ +} +if (!bIsCorrect) +return PtgErr(PtgErr.ERROR_NUM) + +trial -= 1.0 +if (DEBUG) +Logger.logInfo("Result from calcXIRR= " + trial) +val pnum = PtgNumber(trial) +return pnum +} + +/** + * XNPV Returns the net present value for a schedule of cash flows that is + * not necessarily periodic + */ + internal fun calcXNPV(operands:Array):Ptg { +if (operands.size < 2 || operands[1].components == null) +{ +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcXNPV") +val rate = operands[0].doubleVal + // convert references to Ptg[]s + val values = PtgCalculator.getAllComponents(operands[1]) +val dates = PtgCalculator.getAllComponents(operands[2]) +if (DEBUG) +debugOperands(values, "calcXNPV") +if (DEBUG) +debugOperands(dates, "calcXNPV") + +if (values.size != dates.size) +return PtgErr(PtgErr.ERROR_NUM) + // TODO: validate dates + val date0 = dates[0].doubleVal +for (i in 1 until dates.size) +{ + // TODO: if not valid date, return PtgErr.ERROR_VALUE + val `val` = PtgCalculator.getLongValue(dates[i]) +if (`val` < date0) +return PtgErr(PtgErr.ERROR_NUM) +} + // validate values: sum all outflows (= negative values) + inflows (= + // positive values) + var outflow = 0.0 +var inflow = 0.0 + // get outflow (- values) + for (i in values.indices) +{ +val `val` = values[i].doubleVal +if (`val` < 0) +outflow += Math.abs(`val`) +else +inflow += `val` +} +if (outflow == 0.0 || inflow == 0.0) +return PtgErr(PtgErr.ERROR_NUM) + +val n = values.size +var result = 0.0 +for (i in 0 until n) +{ +val `val` = values[i].doubleVal +val exp = (dates[i].doubleVal - date0) / 365.0 +result += `val` / (Math.pow(1 + rate, exp)) +} +if (DEBUG) +Logger.logInfo("Result from calcXNPV= " + result) +val pnum = PtgNumber(result) +return pnum +} + +/** + * YIELD Returns the yield on a security that pays periodic interest + * + * + * YIELD(settlement,maturity,rate,pr,redemption,frequency,basis) Settlement + * is the security's settlement date. The security settlement date is the + * date after the issue date when the security is traded to the buyer. + * Maturity is the security's maturity date. The maturity date is the date + * when the security expires. Rate is the security's annual coupon rate. Pr + * is the security's price per $100 face value. Redemption is the security's + * redemption value per $100 face value. Frequency is the number of coupon + * payments per year. For annual payments, frequency = 1; for semiannual, + * frequency = 2; for quarterly, frequency = 4. Basis (optional) is the type + * of day count basis to use. + */ + internal fun calcYIELD(operands:Array):Ptg { +if (operands.size < 6) +{ // not supported by function +return PtgErr(PtgErr.ERROR_NULL) +} +if (DEBUG) +debugOperands(operands, "calcINTRATE") +try +{ +val sDate = DateConverter.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val rate = operands[2].doubleVal +val pr = operands[3].doubleVal +val redemption = operands[4].doubleVal +val frequency = operands[5].intVal +var basis = 0 +if (operands.size > 6) +basis = operands[6].intVal + + // TODO: if dates are not valid, return #VALUE! error + if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (!sDate.before(mDate)) return PtgErr(PtgErr.ERROR_NUM) +if (rate < 0) return PtgErr(PtgErr.ERROR_NUM) +if (pr <= 0 || redemption <= 0) return PtgErr(PtgErr.ERROR_NUM) + +val settlementDate = (DateConverter.getXLSDateVal(sDate)).toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)).toLong() +val ops:Array +ops = arrayOfNulls(4) +ops[0] = operands[0] +ops[1] = operands[1] +ops[2] = operands[5] +ops[3] = PtgInt(basis) +var result = 0.0 + /* + * A= coupDayBS E= coupDays n= coupNum DSC= coupDaysNC DSR= getDaysFromBasis + */ + val n = PtgCalculator.getLongValue(calcCoupNum(ops)).toDouble() // n is the number of coupons btwn settlement and maturity +val E = calcCoupDays(ops).doubleVal // total # days in coupon period in which settlementfalls +val A = calcCoupDayBS(ops).doubleVal // days from beg. coupon period to settlement + +if (n <= 1) +{ +val DSR = getDaysFromBasis(basis, settlementDate, maturityDate).toDouble() +val R = rate / frequency +val P = pr / 100 +result = ((redemption / 100) + R) - (P + ((A / E) * R)) +result /= P + ((A / E) * R) +result *= (frequency * E) / DSR +} +else +{ + // for n values > 1, must employ an iterated approach to find yield using formula for price + val DSC = calcCoupDaysNC(ops).doubleVal // days from settlement to next coupon +result = yieldIteration(DSC, E, n, A, rate, frequency, redemption, pr) +if (result == -1.0) +{// didn't find it +return PtgErr(PtgErr.ERROR_NUM) +} +} + +val pnum = PtgNumber(result) +if (DEBUG) Logger.logInfo("Result from calcYIELD= " + result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +private fun yieldIteration(DSC:Double, E:Double, n:Double, A:Double, rate:Double, frequency:Int, redemption:Double, pr:Double):Double { + /* Using Newton-Raphson, iterate over possible yield values; value is correct when + * f <= tolerance, defined as .000001% + // 100 iterations, formula for PRICE + // each trial= f(trial)/f'(trial) + // f(trial)= price calculation + // f'(trial): (deep breath) + * + * if f= price, and + * G and F are given + * C= redemption + * B= 100*R + * E= -(n-1+F) + * Ei = -(i-1+F) + * x= 1+trial/frequency + * + * then, + * + * ui(trial)= B*x^Ei + * f'(ui(trial))= -Ei*B*x^(Ei-1) + * = Ei*B*x^Ei*1/x + * = B*x^Ei*1/x*Ei + * = ui(trial)*Ei/x + * f'(sum(ui(trial)))= sum(f'(ui)) + * = sum(ui(trial)*Ei/x + * = 1/x*sum(ui(trial)*Ei) + * + * + * f(trial)= C*x^E + sum(ui(trial)) - B*G + * f'(trial)= f'(f(x)+sum(ui(trial)) + * = f'(x) + f'(sum(ui(trial)) + * = C*E*x^(E-1) + sum(ui(trial)*Ei/x) + * = (C*E*x^E)*(1/x) + (1/x)*sum(ui(trial)*Ei) + * = (C*E*x^E)*(1/x) + (1/x)*sum(B*x^Ei*Ei) + */ + val guess = .1 +var bIsCorrect = false +val TOLERANCE = 0.00000001 +var trial = guess +val f:Double +val F = DSC / E +val G = A / E +val R = rate / frequency +val Y:Double +val B = R * 100 +val Exp = n - 1 + F + + // N-R iteration + val fprime:Double // derivative of f(x0) +var delta = 0.0 +var j = 0 +while (j < 100 && !bIsCorrect) +{ // maximum 100 tries + // f= f(trial) + Y = 1 + trial / frequency +f = redemption / Math.pow(Y, Exp) +fprime = 0.0 +var i = 1 +while (i <= n) +{ +f += B / Math.pow(Y, i - 1 + F) +fprime += (B / Math.pow(Y, i - 1 + F)) * (i - 1 + F) +i++ +} +f -= (B * G) + // pr-f =>0 + fprime /= Y // final +fprime += redemption / Math.pow(Y, Exp) * Exp * (1 / Y) + + // N-R: use f/fprime as iterative factor + delta = (pr - f) / fprime +if (trial < delta) +trial = delta - trial +else +trial = trial - delta + // while (trial - delta <= 0) delta= delta/2; // sanity check + // trial-= delta; + bIsCorrect = (Math.abs(pr - f) <= TOLERANCE) +j++ +} +if (bIsCorrect) +return (trial) +else +return -1.0 + /* + MinYield = -1# + MaxYield = .Rate + If MaxYield = 0 Then MaxYield = 0.1 + Do While CalculatedPrice(BondInfo, MaxYield) > .Price + MaxYield = MaxYield * 2 + Loop + + + Yld = 0.5 * (MinYield + MaxYield) + For i = 1 To MaxIterations + Diff = CalculatedPrice(BondInfo, Yld) - .Price + If Abs(Diff) < Accuracy Then Exit For + 'if calculated price is greater, correct yield is greater + If Diff > 0 Then MinYield = Yld Else MaxYield = Yld + Yld = 0.5 * (MinYield + MaxYield) + Next i + End If + BondYield = Yld +*/ + } + +/** + * YIELDDISC Returns the annual yield for a discounted security. For + * example, a treasury bill + */ + internal fun calcYieldDisc(operands:Array):Ptg { +if (operands.size < 4) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcYIELDDISC") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val pr = operands[2].doubleVal +val redemption = operands[3].doubleVal +var basis = 0 +if (operands.size > 4) +basis = operands[4].intVal + +val settlementDate = (DateConverter.getXLSDateVal(sDate)).toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)).toLong() + +if (pr <= 0 || redemption <= 0) return PtgErr(PtgErr.ERROR_NUM) +if (basis < 0 || basis > 4) return PtgErr(PtgErr.ERROR_NUM) +if (maturityDate <= settlementDate) return PtgErr(PtgErr.ERROR_NUM) + + // double DSM = maturityDate - settlementDate; + // double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate); + // double result = ((redemption - pr) / pr) * (B / DSM); + val result = ((redemption - pr) / pr) / yearFrac(basis, settlementDate, maturityDate) +if (DEBUG) +Logger.logInfo("Result from calcYIELDDISC= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +/** + * YIELDMAT Returns the annual yield of a security that pays interest at + * maturity + */ + internal fun calcYieldMat(operands:Array):Ptg { +if (operands.size < 5) +{ +val perr = PtgErr(PtgErr.ERROR_NULL) +return perr +} +if (DEBUG) +debugOperands(operands, "calcYIELDMAT") +try +{ +val sDate = DateConverter +.getCalendarFromNumber(operands[0].value) as GregorianCalendar +val mDate = DateConverter +.getCalendarFromNumber(operands[1].value) as GregorianCalendar +val iDate = DateConverter +.getCalendarFromNumber(operands[2].value) as GregorianCalendar +val rate = operands[3].doubleVal +val price = operands[4].doubleVal +var basis = 0 +if (operands.size > 5) +basis = operands[5].intVal + +val settlementDate = (DateConverter.getXLSDateVal(sDate)) +.toLong() +val maturityDate = (DateConverter.getXLSDateVal(mDate)) +.toLong() +val issueDate = (DateConverter.getXLSDateVal(iDate)) +.toLong() + +val B = getDaysInYearFromBasis(basis, settlementDate, maturityDate) +val DSM = getDaysFromBasis(basis, settlementDate, maturityDate).toDouble() +val DIM = getDaysFromBasis(basis, issueDate, maturityDate).toDouble() +val A = getDaysFromBasis(basis, issueDate, settlementDate).toDouble() + +val result = (((1 + (DIM / B) * rate) - ((price / 100) + (A / B) * rate)) / ((price / 100) + (A / B) * rate)) * (B / DSM) +if (DEBUG) Logger.logInfo("Result from calcYIELDMAT= " + result) +val pnum = PtgNumber(result) +return pnum +} +catch (e:Exception) {} + +return PtgErr(PtgErr.ERROR_VALUE) +} + +internal fun debugOperands(operands:Array, f:String) { +if (DEBUG) +{ +Logger.logInfo("Operands for " + f) +for (i in operands.indices) +{ +val s = operands[i].string +if (!(operands[i] is PtgMissArg)) +{ +val v = operands[i].value.toString() +Logger.logInfo("\tOperand[" + i + "]=" + s + " " + v) +} +else +Logger.logInfo("\tOperand[" + i + "]=" + s + " is Missing") +} +} +} + + fun isLeapYear(year:Int):Boolean { +return (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0)) +} +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.java deleted file mode 100644 index 2529225..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.toolkit.Logger; - -import java.util.Stack; - - -/** - * Formula Calculator. - *

                      - * Translates an excel calc stack into a value. The stack is in a modified - * reverse polish notation. For details please look into the Excel 97 reference for BIFF8. - *

                      - * Formula Calculators do not exist per formula, rather it uses a factory pattern. - * Put a formula in & calculate it. - *

                      - * Actual calculation methods exist within the OperatorPtg's themselves, so this just handles - * the grunt work of parsing and passing ptgs back and forth along with formatting the output. - * - * @see Formula - */ -public class FormulaCalculator { - - /** - * Calculates the value of calcStac This is handled by - * running through the stack, adding operands to tempstack until - * an operator PTG is found. At that point pass the relevant ptg's from - * tempstack into the calculate method of the operator PTG. The operator - * ptg should return a valid value PTG. - */ - public static Object calculateFormula(Stack expression) - throws FunctionNotSupportedException { - int sz = expression.size(); - Ptg[] stck = new Ptg[sz]; - stck = (Ptg[]) expression.toArray(stck); - Stack calcStack = new Stack(); - for (int t = 0; t < sz; t++) { // flip the stack TODO: investigate why needed - calcStack.add(0, stck[t]); - } - Stack tempstack = new Stack(); - Stack newstck = calcStack; - while (!newstck.isEmpty()) {// loop while there are Ptgs - handlePtg(newstck, tempstack); - } - Ptg finalptg = (Ptg) tempstack.pop(); - return finalptg.getValue(); - } - - - /** - * Calculates the final Ptg result of calcStack. This is handled by - * running through the stack, adding operands to tempstack until - * an operator PTG is found. At that point pass the relevant ptg's from - * tempstack into the calculate method of the operator PTG. The operator - * ptg should return a valid value PTG. - */ - public static Ptg calculateFormulaPtg(Stack expression) - throws FunctionNotSupportedException { - int sz = expression.size(); - Ptg[] stck = new Ptg[sz]; - stck = (Ptg[]) expression.toArray(stck); - Stack calcStack = new Stack(); - for (int t = 0; t < sz; t++) { // flip the stack TODO: investigate why needed - calcStack.add(0, stck[t]); - } - Stack tempstack = new Stack(); - Stack newstck = calcStack; - while (!newstck.isEmpty()) {// loop while there are Ptgs - handlePtg(newstck, tempstack); - } - Ptg finalptg = (Ptg) tempstack.pop(); - return finalptg; - } - - private static boolean DEBUG = false; // just use this to see ptg calcing - - /** - * This is a very similar method to the handle ptg method in formula parser. - * Instead of creating a tree however it calculates in the order recommended by - * the book of knowledge (excel developers guide). That is, FILO. First In Last Out. - * We also don't really care about things like parens, they are just for display purposes. - */ - static void handlePtg(Stack newstck, Stack vals) - throws FunctionNotSupportedException { - Ptg p = (Ptg) newstck.pop(); - int x = 0, t = 0; - if (p.getIsOperator() || p.getIsControl() || p.getIsFunction()) { - // Get rid of the parens ptgs - if (p.getIsControl() && !vals.isEmpty()) { - if (p.getOpcode() == 0x15) { // its a parens! - // the parens is already pop'd so just return and it is gone... - return; - } - // we didn't use it, back it goes. - if (DEBUG) Logger.logInfo("opr: " + p.toString()); - } - // make sure we have the correct amount popped back in.. - if (p.getIsBinaryOperator()) t = 2; - if (p.getIsUnaryOperator()) t = 1; - if (p.getIsStandAloneOperator()) t = 0; - if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62) { - t = p.getNumParams(); - }// it's a ptgfunkvar! - if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61) { - t = p.getNumParams(); - }// guess that ptgfunc is not only one.. - - Ptg[] vx = new Ptg[t]; - for (; x < t; x++) vx[(t - 1) - x] = (Ptg) vals.pop();// get'em - - // QUITE AN IMPORTANT LINE... FYI. -jm - try { - p = p.calculatePtg(vx); - } catch (CalculationException e) { - p = new PtgErr(e.getErrorCode()); - if (e.getName().equals("#CIR_ERR!")) { - ((PtgErr) p).setCircularError(true); - } - } - - /* useful for debugging*/ - if (DEBUG) { - String adr = ""; - if (p.getParentRec() != null) - adr = "addr: " + p.getParentRec().getCellAddress(); - Logger.logInfo(adr + " val: " + p.toString()); - } - vals.push(p);// push it back on the stack - - } else if (p.getIsOperand()) { - - if (DEBUG) Logger.logInfo("opr: " + p.toString()); - - vals.push(p); - - } else if (p instanceof PtgAtr) { - - // this is probably just a space at this point, don't output error message - - } else { - throw new FunctionNotSupportedException("WARNING: Calculating Formula failed: Unsupported/Incorrect Ptg Type: 0x" + p.getOpcode() + " " + p.getString()); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.kt new file mode 100644 index 0000000..522ac98 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/FormulaCalculator.kt @@ -0,0 +1,171 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.toolkit.Logger + +import java.util.Stack + + +/** + * Formula Calculator. + * + * + * Translates an excel calc stack into a value. The stack is in a modified + * reverse polish notation. For details please look into the Excel 97 reference for BIFF8. + * + * + * Formula Calculators do not exist per formula, rather it uses a factory pattern. + * Put a formula in & calculate it. + * + * + * Actual calculation methods exist within the OperatorPtg's themselves, so this just handles + * the grunt work of parsing and passing ptgs back and forth along with formatting the output. + * + * @see Formula + */ +object FormulaCalculator { + + private val DEBUG = false // just use this to see ptg calcing + + /** + * Calculates the value of calcStac This is handled by + * running through the stack, adding operands to tempstack until + * an operator PTG is found. At that point pass the relevant ptg's from + * tempstack into the calculate method of the operator PTG. The operator + * ptg should return a valid value PTG. + */ + @Throws(FunctionNotSupportedException::class) + fun calculateFormula(expression: Stack<*>): Any { + val sz = expression.size + var stck = arrayOfNulls(sz) + stck = expression.toTypedArray() as Array + val calcStack = Stack() + for (t in 0 until sz) { // flip the stack TODO: investigate why needed + calcStack.add(0, stck[t]) + } + val tempstack = Stack() + while (!calcStack.isEmpty()) {// loop while there are Ptgs + handlePtg(calcStack, tempstack) + } + val finalptg = tempstack.pop() as Ptg + return finalptg.value + } + + + /** + * Calculates the final Ptg result of calcStack. This is handled by + * running through the stack, adding operands to tempstack until + * an operator PTG is found. At that point pass the relevant ptg's from + * tempstack into the calculate method of the operator PTG. The operator + * ptg should return a valid value PTG. + */ + @Throws(FunctionNotSupportedException::class) + fun calculateFormulaPtg(expression: Stack<*>): Ptg { + val sz = expression.size + var stck = arrayOfNulls(sz) + stck = expression.toTypedArray() as Array + val calcStack = Stack() + for (t in 0 until sz) { // flip the stack TODO: investigate why needed + calcStack.add(0, stck[t]) + } + val tempstack = Stack() + while (!calcStack.isEmpty()) {// loop while there are Ptgs + handlePtg(calcStack, tempstack) + } + return tempstack.pop() + } + + /** + * This is a very similar method to the handle ptg method in formula parser. + * Instead of creating a tree however it calculates in the order recommended by + * the book of knowledge (excel developers guide). That is, FILO. First In Last Out. + * We also don't really care about things like parens, they are just for display purposes. + */ + @Throws(FunctionNotSupportedException::class) + internal fun handlePtg(newstck: Stack<*>, vals: Stack<*>) { + var p = newstck.pop() as Ptg + var x = 0 + var t = 0 + if (p.isOperator || p.isControl || p.isFunction) { + // Get rid of the parens ptgs + if (p.isControl && !vals.isEmpty()) { + if (p.opcode.toInt() == 0x15) { // its a parens! + // the parens is already pop'd so just return and it is gone... + return + } + // we didn't use it, back it goes. + if (DEBUG) Logger.logInfo("opr: $p") + } + // make sure we have the correct amount popped back in.. + if (p.isBinaryOperator) t = 2 + if (p.isUnaryOperator) t = 1 + if (p.isStandAloneOperator) t = 0 + if (p.opcode.toInt() == 0x22 || p.opcode.toInt() == 0x42 || p.opcode.toInt() == 0x62) { + t = p.numParams + }// it's a ptgfunkvar! + if (p.opcode.toInt() == 0x21 || p.opcode.toInt() == 0x41 || p.opcode.toInt() == 0x61) { + t = p.numParams + }// guess that ptgfunc is not only one.. + + val vx = arrayOfNulls(t) + while (x < t) { + vx[t - 1 - x] = vals.pop() as Ptg + x++ + }// get'em + + // QUITE AN IMPORTANT LINE... FYI. -jm + try { + p = p.calculatePtg(vx) + } catch (e: CalculationException) { + p = PtgErr(e.errorCode) + if (e.name == "#CIR_ERR!") { + p.isCircularError = true + } + } + + /* useful for debugging*/ + if (DEBUG) { + var adr = "" + if (p.parentRec != null) + adr = "addr: " + p.parentRec.cellAddress + Logger.logInfo("$adr val: $p") + } + vals.push(p)// push it back on the stack + + } else if (p.isOperand) { + + if (DEBUG) Logger.logInfo("opr: $p") + + vals.push(p) + + } else if (p is PtgAtr) { + + // this is probably just a space at this point, don't output error message + + } else { + throw FunctionNotSupportedException("WARNING: Calculating Formula failed: Unsupported/Incorrect Ptg Type: 0x" + p.opcode + " " + p.string) + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/FormulaParser.java b/src/main/java/io/starter/formats/XLS/formulas/FormulaParser.java deleted file mode 100644 index cfc43a8..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/FormulaParser.java +++ /dev/null @@ -1,1683 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.*; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; - -import java.util.Locale; -import java.util.Stack; -import java.util.Vector; - -//import com.sun.org.apache.xerces.internal.impl.xs.identity.Selector.Matcher; - - -/** - * Formula Parser. - *

                      - * Translates Excel-compatible Strings into Biff8/OpenXLS Compatible Formulas and vice-versa. - * - * @see Formula - */ -public final class FormulaParser { - - - static int DEBUGLEVEL = -1; - // KSC: handling unary operators necessitated, in the end, a complete rewrite ... - - /** - * getPtgsFromFormulaString - * returns ordered stack of Ptgs parsed from formula string fmla - * - * @param Formula form formula record - * @param String fmla string rep. of current state of formula (either original "=F(Y)" or a recurrsed state e.g. "Y") - * @returns Stack ordered Ptgs that represent formula expression - */ - public static Stack getPtgsFromFormulaString(XLSRecord form, String fmla) { - return getPtgsFromFormulaString(form, fmla, true); - } - - /** - * getPtgsFromFormulaString is the main entry point for parsing a string and creating a formula. - * The formula passed in at this point can either be an existing formula with an expression, or a - * templated formula with no expression. The string gets parsed and entered - * as the expression for the formula. - * - * @param Formula form formula record - * @param String fmla string rep. of current state of formula (either original "=F(Y)" or a recurrsed state e.g. "Y") - * @param boolean bIsCompleteExpression truth of "formula fmla represents a complete formula i.e. we are not currently in a recurrsed state" - * @returns Stack ordered Ptgs that represent formula expression - */ - /** - * should handle all sorts of variations of formula strings such as: - * =PV(C17,1+-(1*1)-9, 0, 1) - * =100*0.5 - * =(B2-B3)*B4 - * =SUM(IF(A1:A10=B1:B10, 1, 0)) - * =IF(B4<=10,"10", if(b4<=100, "15", "20")) - * ="STRING"&IF(A<>"",A,"N/A")&" - &IF(C<>"",C,"N/A")&" Result " - *

                      - * in basic essence, handles signatures such as - * a op f(b op c, d, uop e ...) op g - *

                      - * where op is any binary operator, uop is a unary operator f is a formula - * ... - */ - protected static Stack getPtgsFromFormulaString(XLSRecord form, String fmla, boolean bMergeWithLast) { - Object[] operands = new Object[2]; - - fmla = fmla.trim(); - if (fmla.startsWith("=")) fmla = fmla.substring(1); - fmla = fmla.trim(); - - - if (fmla.equals("")) { // 20081120 KSC: Handle Missing Argument - Stack s = new Stack(); - s.add(new PtgMissArg()); - return s; - } - - if (fmla.startsWith("{")) { // must process array formula first, PtgArray expects full function string - PtgArray pa = new PtgArray(); - pa.setParentRec(form); - int endarray = getMatchOperator(fmla, 0, '{', '}'); - pa.setVal(fmla.substring(0, endarray + 1)); - fmla = fmla.substring(endarray + 1); - Stack s = new Stack(); - s.add(pa); - operands[0] = s; - bMergeWithLast = false; - } -// TODO: complex ranges?? - boolean inQuote = false; - boolean inRange = false; - boolean inOp = false; - String prefix = ""; - Stack ops = new Stack(); - String op = ""; - for (int i = 0; i < fmla.length(); i++) { - char c = fmla.charAt(i); - if (c == '"' || c == '\'') { // get to ending quote - if (inQuote) { - inQuote = (c != prefix.trim().charAt(0)); // if start quote == end quote, inQuote is false - } else - inQuote = true; - if (inQuote) { - if (inOp) { - inOp = false; - if (!op.equals("")) ops.add(0, op); - op = ""; - if (operands[0] != null && operands[1] != null) - addOperands(form, /*functionStack, */operands, ops); - } - } - prefix += c; - } else if (inQuote) { - prefix += c; - } else if (c == ':') { - if (i > 0) - inRange = true; - prefix += c; - } else if (c == '(') { // found a formula?? check out - // if the parenthesis is part of a complex range, keep going i.e. keep entire expression together - if (inRange) { - prefix += c; - continue; - } - if (inOp) { - inOp = false; - if (!op.equals("")) ops.add(0, op); - op = ""; - if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { - addOperands(form, /*functionStack, */operands, ops); - } - } - String funcName = ""; - for (int k = prefix.length() - 1; k >= 0; k--) { - if (Character.isLetterOrDigit(prefix.charAt(k)) || Character.toString(prefix.charAt(k)).equals(".")) { - funcName = prefix.charAt(k) + funcName; - prefix = prefix.substring(0, k); - } else - break; - } - // prefix= anything before function name - if (!prefix.trim().equals("")) { - if (operands[0] == null) - operands[0] = prefix.trim(); - else - operands[1] = prefix.trim(); - prefix = ""; - } - // function name should = part just before parents - if (!funcName.equals("")) { - Ptg funcPtg = null; - funcPtg = getFuncPtg(funcName, form); - - // do we have a valid function Ptg? - if (funcPtg != null) { // yes, then handle function paramters i.e. evertyhing between the parentheses - int endparen = getMatchOperator(fmla, i, '(', ')'); - if (endparen < fmla.length() - 1) { - if (fmla.charAt(endparen + 1) == ':') { // it's a VERY complex range :) - inRange = true; - prefix = funcName + fmla.substring(i, endparen + 1); // keep function name together ... - i = endparen; - continue; - } - } - // things like: xyz + f(x) - if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { - addOperands(form, /*functionStack, */operands, ops); - inOp = false; - } // have [xyz, xyz, OP] + [abc, def, ghi] - // parse function - Stack s = parseFunctionPtg(form, funcName, fmla.substring(i + 1, endparen), funcPtg); - if (operands[0] == null) - operands[0] = s; - else - operands[1] = s; - //functionStack.addAll(parseFunctionPtg(form, funcName, fmla.substring(i+1, endparen), funcPtg)); - i = endparen; // inc. pointer to past processing point - } else // else, we have *something* in front of the parentheses ... - throw new FunctionNotSupportedException(funcName + " is not a supported function"); - } else { // enclosing parens - // complexities occur for complex ranges and enclosing parens ... - int endparen = getMatchOperator(fmla, i, '(', ')'); - if (endparen == -1 || (endparen < fmla.length() - 1 && fmla.charAt(endparen + 1) == ':')) { // it's a VERY complex range :) - inRange = true; - prefix = "("; - continue; - } - String f = fmla.substring(i + 1, endparen); // the statement less the parenthesis - i = endparen; // skip parens ... - // see if the enclosed expression is a complex range - must parse as 1 unit, rather than parsing particular ptgs - if (FormulaParser.isComplexRange('(' + f + ')')) { - Stack s = new Stack(); - s.push(parseSinglePtg(form, '(' + f + ')', false)); - s.push(parseSinglePtg(form, ")", false)); - if (operands[0] == null) - operands[0] = s; - else - operands[1] = s; - } else { // embedded functions, keep parsing - Stack s = getPtgsFromFormulaString(form, f, true); // flag as a complete expression - s.push(new PtgParen()); // add ending parens to stack - if (operands[0] == null) - operands[0] = s; - else - operands[1] = s; - if (!ops.isEmpty()) { - addOperands(form, /*functionStack, */operands, ops); - } - } - } - } else { // see if we have found an operataor - if (!Character.isJavaIdentifierPart(c) && c != ' ' && c != '%') { - // if (inRange && !Character.isJavaIdentifierPart(c) && c!=',' && c!=' ') - if (inRange) { - if (c != ',' && c != ' ' && c != ')' && c != '!') { - inRange = false; - if (!prefix.trim().equals("")) { - if (operands[0] == null) - operands[0] = prefix.trim(); - else - operands[1] = prefix.trim(); - prefix = ""; - } - } else { - prefix += c; - continue; - } - } - if (c != '!' && c != '#' && c != '.') { // ignore ! - // FOUND AN OPERATOR - ready to add operands yet? - inOp = true; - if (!prefix.trim().equals("")) { - if (operands[0] == null) - operands[0] = prefix.trim(); - else - operands[1] = prefix.trim(); - prefix = ""; - } - if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { - addOperands(form, /*functionStack, */operands, ops); - } - if (!op.equals("")) { - if (!(c == '=' || c == '>')) { - ops.add(0, op); - op = ""; - } - } - op += c; // >,<,-,/, ,+ - continue; - } - } else if (inOp) { - inOp = false; - if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { - addOperands(form, /*functionStack, */operands, ops); - } - if (!op.equals("")) - ops.add(0, op); - op = ""; - } - prefix += c; - } - - } - if (!prefix.trim().equals("")) { // get any remaining elements - if (operands[0] == null) - operands[0] = prefix.trim(); - else - operands[1] = prefix.trim(); - prefix = ""; - } - - if (!op.equals("")) - ops.add(0, op); - addOperands(form, operands, ops); -//if (((Stack)operands[0]).isEmpty()) -//return functionStack; - return (Stack) operands[0]; - - } - - /** - * Given two operands (objects) and operators (can be up to 2 if there is a unary operator present) - * organize and add to functionStack in reverse polish notation i.e. - * OPERAND, OPERAND, OP [...] - * - * @param form - * @param functionStack - * @param operands - * @param ops - */ - private static void addOperands(XLSRecord form, /*Stack functionStack, */Object[] operands, Stack ops) { - Ptg pOp = null; - if (!ops.isEmpty()) - pOp = parseSinglePtg(form, (String) ops.pop(), (operands[1] == null)); - - Stack s = new Stack(); - s.addAll(handleOperatorPrecedence(form, /*functionStack, */operands, pOp)); - operands[0] = s; - //functionStack.clear(); - if (!ops.isEmpty()) { - pOp = parseSinglePtg(form, (String) ops.pop(), true); - s = new Stack(); - s.addAll(handleOperatorPrecedence(form, /*functionStack, */operands, pOp)); - operands[0] = s; - } - - } - - private static Stack handleOperatorPrecedence(XLSRecord form, /*Stack functionStack, */Object[] operands, Ptg pOp) { - Stack functionStack = new Stack(); - if (operands[0] instanceof Stack) { - functionStack = (Stack) operands[0]; - operands[0] = null; - } - if (!functionStack.isEmpty() && pOp != null) { - Ptg lastOp = (Ptg) functionStack.peek(); - if (lastOp != null && lastOp.getIsOperator()) { - if (lastOp.getIsOperator()) { - functionStack.pop(); //= lastOp - int group1 = rankPrecedence(pOp); - int group2 = rankPrecedence(lastOp); - if (group2 >= group1) { - functionStack.push(lastOp); - } else { // current op has higher priority - if (operands[0] != null) { - if (operands[0] instanceof String) - functionStack.push(parseSinglePtg(form, (String) operands[0], functionStack.isEmpty())); - else - functionStack.addAll((Stack) operands[0]); - } - if (operands[1] != null) { - if (operands[1] instanceof String) - functionStack.push(parseSinglePtg(form, (String) operands[1], functionStack.isEmpty())); - else - functionStack.addAll((Stack) operands[1]); - } - operands[0] = null; - operands[1] = null; - functionStack.push(pOp); - pOp = lastOp; - } - } - } - } - if (operands[0] != null) { - if (operands[0] instanceof String) - functionStack.push(parseSinglePtg(form, (String) operands[0], functionStack.isEmpty())); - else - functionStack.addAll((Stack) operands[0]); - } - if (operands[1] != null) { - if (operands[1] instanceof String) - functionStack.push(parseSinglePtg(form, (String) operands[1], functionStack.isEmpty())); - else - functionStack.addAll((Stack) operands[1]); - } - operands[0] = null; - operands[1] = null; - if (pOp != null) - functionStack.push(pOp); - return functionStack; - } - - - /** - * merge last stacks to ensure operator order is correct - * - * @param functionStack - * @return - */ - private static Stack mergeStacks(Stack prevStack, Stack curStack, boolean bIsCompleteExpression) { - if (prevStack.isEmpty()) - return curStack; - - Ptg lastOp = (Ptg) prevStack.peek(); - Ptg curOp = (curStack.isEmpty() ? null : ((Ptg) curStack.peek())); - int group1 = rankPrecedence(lastOp); - int group2 = rankPrecedence(curOp); - if (group1 >= 0 && (group1 < group2 || group2 == -1)) { - lastOp = (Ptg) prevStack.pop(); - curStack.push(lastOp); -/* while (curOp.getIsOperator()) { - lastOp= curOp; - curStack.push(curOp); - if (!prevStack.isEmpty()) { - curOp = (Ptg) prevStack.pop(); - // handle precedence - group1=rankPrecedence(curOp); - group2=rankPrecedence(lastOp); - if (!(group1>=0 && (group1 < group2 || group2==-1))) - break; - }else - return curStack; - } -*/ - } - prevStack.addAll(curStack); - return prevStack; - } - - - /** - * parse and add to Stack a valid Excel function represented by funcPtg and fmla string - * called from getPtgsFromFormulaString - * - * @param form formula record - * @param fmla function parameters in the form of (x, y, z) - * @param funcPtg function data for the formula represented by fmla - * @para func function name f - * @return Stack ordered parsed Stack of Ptgs - */ - private static Stack parseFunctionPtg(XLSRecord form, String func, String fmla, Ptg funcPtg) { - Stack returnStack = new Stack(); - fmla = fmla.trim(); - int nParens = 0; - boolean enclosing = false; - // change: only remove 1 set of parens: - if (fmla.length() > 0 && fmla.charAt(0) == '(') { - if (getMatchOperator(fmla, 0, '(', ')') == fmla.length() - 1) { // then strip enclosing parens - nParens++; - enclosing = true; - } - fmla = fmla.trim(); - } - - // NOTE: all memfuncs/complex ranges are enclosed by parentheses - // IF enclosed by parens, DO NOT split apart into operands: - int funcLen = 1; - if (enclosing) { - returnStack.addAll(FormulaParser.getPtgsFromFormulaString(form, fmla, true)); - } else { - CompatibleVector cv = splitFunctionOperands(fmla); - funcLen = cv.size(); - // loop through the operands to the function and recurse - for (int y = 0; y < cv.size(); y++) { - String s = (String) cv.elementAt(y); // flag as a complete expression - returnStack.addAll(FormulaParser.getPtgsFromFormulaString(form, s, true)); - } - } - - // Handle PtgFuncVar-specifics such as number of parameters and add-in PtgNameX record - if (funcPtg instanceof PtgFuncVar) { - if (((PtgFuncVar) funcPtg).getVal() == FunctionConstants.xlfADDIN) { - // if an add-in, must add PtgNameX to stack - PtgNameX pn = new PtgNameX(); - pn.setParentRec(form); - pn.setName(func); - returnStack.add(0, pn); // add to bottom of stack - funcLen++; - funcPtg.setParentRec(form); // nec. to resolve external name - } - ((PtgFuncVar) funcPtg).setNumParams((byte) funcLen); - } - returnStack.push(funcPtg); - return returnStack; - } - - /** - * combine two stacks of Ptgs, popping the operator of the sourceStack and - * adding it to the end of the destination stack to - * ensure it is in the correct order in the destination stack - * - * @param sourceStack - * @param destStack - * / - private static Stack addPtgStacks(Stack sourceStack, Stack destStack) { - Ptg opPtg = (Ptg) sourceStack.pop(); - Ptg lastOp= (destStack.isEmpty()?null:((Ptg)destStack.peek())); - - // handle precedence: unaries before ^(power) before *, / before +, - before &(concat), before comparisons (=, <>, <=, >=, <, >) - int group1=rankPrecedence(opPtg); - int group2=rankPrecedence(lastOp); - - if (group1>=0 && (group1 < group2 || group2==-1)) { - while (opPtg.getIsOperator()) { - lastOp= opPtg; - destStack.push(opPtg); - if (!sourceStack.isEmpty()) { - opPtg = (Ptg) sourceStack.pop(); - // handle precedence - group1=rankPrecedence(opPtg); - group2=rankPrecedence(lastOp); - if (!(group1>=0 && (group1 < group2 || group2==-1))) - break; - }else - return destStack; - } - } - sourceStack.push(opPtg); - - // after sorting out operators, assemble two stacks into one - Stack nwstack = destStack; - destStack = new Stack(); - destStack.addAll(sourceStack); - destStack.addAll(nwstack); - - return destStack; - } - */ - - /** - * rank a Ptg Operator's precedence (lower - * - * @param curOp - * @return - */ - static int rankPrecedence(Ptg curOp) { - if (curOp == null) return -1; -// if (curOp==null || !curOp.getIsOperator()) return -1; - if (curOp instanceof PtgUMinus || curOp instanceof PtgUPlus) - return 7; - if (curOp instanceof PtgPercent) - return 6; - if (curOp instanceof PtgPower) - return 5; - if (curOp instanceof PtgMlt || curOp instanceof PtgDiv) - return 4; - else if (curOp instanceof PtgAdd || curOp instanceof PtgSub) - return 3; - else if (curOp instanceof PtgConcat) - return 2; - else if (curOp instanceof PtgEQ || curOp instanceof PtgNE || - curOp instanceof PtgLE || curOp instanceof PtgLT || - curOp instanceof PtgGE || curOp instanceof PtgGT) - return 1; -// else if (curOp instanceof PtgParen) -// return 0; - return -1; - } - - /* - * getMatchOperator takes a string and starting operator location. - * It then parses the string and determines which closing operator - * matches the opening parens specified by startParenLoc. Returns - * -1 if it cannot find a match. - */ - public static int getMatchOperator(String input, int startParenLoc, char matchOpenChar, char matchCloseChar) { - // 20081112 KSC: do a different way as it wasn't working for all cases - int openCnt = 0; - for (int i = startParenLoc; i < input.length(); i++) { - if (input.charAt(i) == '"' || input.charAt(i) == '\'') {// handle quoted strings within input (quoted strings may of course contain match chars ... - char endquote = input.charAt(i); - while (++i < input.length()) { - if (input.charAt(i) == endquote) { - break; - } - } - } - if (i == input.length()) return i - 1; - if (input.charAt(i) == matchOpenChar) - openCnt++; - else if (input.charAt(i) == matchCloseChar) { - openCnt--; - if (openCnt == 0) - return i; - } - } - - // no parens for you! - return -1; - } - - /** - * Looks up a function string and returns a funcPtg if it is found - * - * @param func function string without parents i.e. SUM or DB - * @returns Ptg valid funcPtg or null if not found - */ - // 20090210 KSC: add form so can set parent record for PtgFunc and PtgFuncVar - nec for self-referential formulas such as COLUMN - private static Ptg getFuncPtg(String func, XLSRecord form) { - Ptg funcPtg = null; - // if (true) { - if (Locale.JAPAN.equals(Locale.getDefault())) { - for (int y = 0; y < FunctionConstants.jRecArr.length; y++) { - if (func.equalsIgnoreCase(FunctionConstants.jRecArr[y][0])) { - int FID = Integer.parseInt(FunctionConstants.jRecArr[y][1]); - int Ftype = Integer.parseInt(FunctionConstants.jRecArr[y][2]); - if (Ftype == FunctionConstants.FTYPE_PTGFUNC) { - funcPtg = new PtgFunc(FID, form); - } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR) { - funcPtg = new PtgFuncVar(FID, 0, form); - } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR_ADDIN) { - funcPtg = new PtgFuncVar(FunctionConstants.xlfADDIN, 0, form); - } - return funcPtg; - } - } - } - for (int y = 0; y < FunctionConstants.recArr.length; y++) { - if (func.equalsIgnoreCase(FunctionConstants.recArr[y][0])) { - int FID = Integer.parseInt(FunctionConstants.recArr[y][1]); - int Ftype = Integer.parseInt(FunctionConstants.recArr[y][2]); - if (Ftype == FunctionConstants.FTYPE_PTGFUNC) { - funcPtg = new PtgFunc(FID, form); - } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR) { - funcPtg = new PtgFuncVar(FID, 0, form); - } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR_ADDIN) { - funcPtg = new PtgFuncVar(FunctionConstants.xlfADDIN, 0, form); - } - return funcPtg; - } - } - for (int y = 0; y < FunctionConstants.unimplRecArr.length; y++) { - if (func.equalsIgnoreCase(FunctionConstants.unimplRecArr[y][0])) { - int FID = Integer.parseInt(FunctionConstants.unimplRecArr[y][1]); - int Ftype = Integer.parseInt(FunctionConstants.unimplRecArr[y][2]); - if (Ftype == FunctionConstants.FTYPE_PTGFUNC) { - funcPtg = new PtgFunc(FID, form); - } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR) { - funcPtg = new PtgFuncVar(FID, 0, form); - } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR_ADDIN) { - funcPtg = new PtgFuncVar(FunctionConstants.xlfADDIN, 0, form); - } - return funcPtg; - } - } - return funcPtg; - - } - - - /** - * take a string guaranteed to be a single Ptg (operator, reference, string, etc) and convert to correct Ptg - * - * @param form - * @param fmla - * @param bIsUnary -- operator is a unary version - * @return - */ - private static Ptg parseSinglePtg(XLSRecord form, String fmla, boolean bIsUnary) { - WorkBook bk = form.getWorkBook(); // nec. to determine if parsed element is a valid name handle name - - String val = fmla; - String name = convertString(val, bk); - if (name.equals("+") && bIsUnary) - name = "u+"; - else if (name.equals("-") && bIsUnary) - name = "u-"; - - Ptg pthing = null; - try { - pthing = XLSRecordFactory.getPtgRecord(name); - if (pthing == null && name.equals("PtgName")) - // TODO: MUST evaluate which type of PtgName is correct: understand usage! - if (form.getOpcode() == XLSConstants.FORMULA || form.getOpcode() == XLSConstants.ARRAY) - pthing = new PtgName(0x43); // assume this token to be of type Value (i.e PtgNameV) instead of Reference (PtgNameR) - else // DV needs ref-type name - pthing = new PtgName(0x23); // PtgNameR - } catch (InvalidRecordException e) { - Logger.logInfo("parsing formula string. Invalid Ptg: " + name + " error: " + e); - } - // if it is an operator we don't need to do anything with it! - if (pthing != null) { - pthing.setParentRec(form); - - if (!pthing.getIsOperator()) { - if (pthing.getIsReference()) { - // createPtgRefFromString will handle any type of string reference - // will return a PtgRefErr if cannot parse location - pthing = PtgRef.createPtgRefFromString(val, form); - } else if (pthing instanceof PtgStr) { - PtgStr pstr = (PtgStr) pthing; - val = StringTool.strip(val, '\"'); - pstr.setVal(val); - } else if (pthing instanceof PtgNumber) { - PtgNumber pnum = (PtgNumber) pthing; - if (val.indexOf("%") == -1) - pnum.setVal((new Double(val).doubleValue())); - else - pnum.setVal(val); - } else if (pthing instanceof PtgInt) { - PtgInt pint = (PtgInt) pthing; - pint.setVal((Integer.valueOf(val).intValue())); - } else if (pthing instanceof PtgBool) { - PtgBool pbool = (PtgBool) pthing; - pbool.setVal(Boolean.valueOf(val).booleanValue()); - } else if (pthing instanceof PtgArray) { - PtgArray parr = (PtgArray) pthing; - parr.setVal(val); - } else if (pthing instanceof PtgName) { // SHOULD really return PtgErr("#NAME!") as it's a missing Name instead of adding a new name - PtgName pname = (PtgName) pthing; - pname.setName(val); - pname.addToRefTracker(); - } else if (pthing instanceof PtgNameX) { - PtgNameX pnameX = (PtgNameX) pthing; - pnameX.setName(val); - } else if (pthing instanceof PtgMissArg) { - ((PtgMissArg) pthing).init(new byte[]{22}); - } else if (pthing instanceof PtgErr) { - pthing = new PtgErr(PtgErr.convertStringToLookupByte(val)); - } else if (pthing instanceof PtgAtr) { - pthing = new PtgAtr((byte) 0x40); // assume space - } - } - } else { - PtgMissArg pname = new PtgMissArg(); - } - return pthing; - } - - private static String findPtg(String fmla, boolean bUnary) { - String s = StringTool.allTrim(fmla); - - if (s.startsWith("\"") || s.startsWith("'")) { - //return s; // it's a string - return s; - } - - for (int i = 0; i < XLSRecordFactory.ptgOps.length; i++) { - String ptgOpStr = XLSRecordFactory.ptgOps[i][0]; - - int x = s.indexOf(ptgOpStr); - if (x == 0) { // found instance of an operator - // if encounter a parenthesis, must determine if it is an expression limit OR - // if it is part of a complex range, in which case the expression must be kept together - if (ptgOpStr.equals("(")) { - return s; // parens means a whole complex range==>PtgMemFunc - } - if (bUnary) { - // unary ops +, - ... have a diff't Ptg than regular vers of the operator - if (ptgOpStr.equals("-") && x == 1 && ptgOpStr.length() > 1) break; //negative number, NOT a unary - - for (int j = 0; j < XLSRecordFactory.ptgPrefixOperators.length; j++) { - if (ptgOpStr.startsWith(XLSRecordFactory.ptgPrefixOperators[j][0].toString())) { - ptgOpStr = XLSRecordFactory.ptgPrefixOperators[j][1].toString(); - } - } - } - return XLSRecordFactory.ptgOps[i][1]; - } - } - return s; - } - - /** - * parseFinalLevel is where strings get converted into ptg's - * This method can handle multiple ptg's within a string, but cannot handle - * recursion. If you are having recursion problems look into getPtgsFromFormulaString above. - *

                      - * This method should be called from the final level of parsing. - * There should not be additional sub-expressions at this point. - * Example (1+2) or (3,4,5) - * NOT ((1<2),3,4) or TAN(23); - *

                      - *

                      - * TODO: HANDLE these references: - *

                      - * =SUM(table[[#This Row];['#Head3]:[Calced]]) - *

                      - * table[['#Head3]:[Calced]] - *

                      - * I assume this means a table of data, the Head3 table? and the calced column? - */ - private static Stack parseFinalLevel(XLSRecord form, String fmla, boolean bIsComplete) { - Stack returnStack = new Stack(); - CompatibleVector parseThings = new CompatibleVector(); - - // break it up into components first - Vector elements = new Vector(); - elements = splitString(fmla, bIsComplete); - - WorkBook bk = form.getWorkBook(); // nec. to determine if parsed element is a valid name handle name - - // convert each element into Ptg's - // each element at this point should be a named operand, or an unidentified operator - for (int x = 0; x < elements.size(); x++) { - String val = (String) elements.elementAt(x); - String name = convertString(val, bk); - - Ptg pthing = null; - try { - pthing = XLSRecordFactory.getPtgRecord(name); - if (pthing == null && name.equals("PtgName")) - // TODO: MUST evaluate which type of PtgName is correct: understand usage! - if (form.getOpcode() == XLSConstants.FORMULA || form.getOpcode() == XLSConstants.ARRAY) - pthing = new PtgName(0x43); // assume this token to be of type Value (i.e PtgNameV) instead of Reference (PtgNameR) - else // DV needs ref-type name - pthing = new PtgName(0x23); // PtgNameR - } catch (InvalidRecordException e) { - Logger.logInfo("parsing formula string. Invalid Ptg: " + name + " error: " + e); - } - // if it is an operator we don't need to do anything with it! - if (pthing != null) { - pthing.setParentRec(form); - - if (!pthing.getIsOperator()) { - if (pthing.getIsReference()) { - // createPtgRefFromString will handle any type of string reference - // will return a PtgRefErr if cannot parse location - pthing = PtgRef.createPtgRefFromString(val, form); - } else if (pthing instanceof PtgStr) { - PtgStr pstr = (PtgStr) pthing; - val = StringTool.strip(val, '\"'); - pstr.setVal(val); - } else if (pthing instanceof PtgNumber) { - PtgNumber pnum = (PtgNumber) pthing; - if (val.indexOf("%") == -1) - pnum.setVal((new Double(val).doubleValue())); - else - pnum.setVal(val); - } else if (pthing instanceof PtgInt) { - PtgInt pint = (PtgInt) pthing; - pint.setVal((Integer.valueOf(val).intValue())); - } else if (pthing instanceof PtgBool) { - PtgBool pbool = (PtgBool) pthing; - pbool.setVal(Boolean.valueOf(val).booleanValue()); - } else if (pthing instanceof PtgArray) { - PtgArray parr = (PtgArray) pthing; - parr.setVal(val); - } else if (pthing instanceof PtgName) { // SHOULD really return PtgErr("#NAME!") as it's a missing Name instead of adding a new name - PtgName pname = (PtgName) pthing; - pname.setName(val); - } else if (pthing instanceof PtgNameX) { - PtgNameX pnameX = (PtgNameX) pthing; - pnameX.setName(val); - } else if (pthing instanceof PtgMissArg) { - ((PtgMissArg) pthing).init(new byte[]{22}); - } else if (pthing instanceof PtgErr) { - pthing = new PtgErr(PtgErr.convertStringToLookupByte(val)); - } - } - parseThings.add(pthing); - } else { - PtgMissArg pname = new PtgMissArg(); - } - - } - //reorder in polish notation and add to stack. - // 20081128 KSC: Do later as reordering will depend upon position of this segment in formula returnStack = reorderStack(parseThings); see getPtgsFromFormulaString - returnStack = convertToStack(parseThings); - return returnStack; - } - - /* - private static Stack reorderStack(Stack sourceStack, boolean bIsComplete) { - Stack returnStack = new Stack(); - Stack pOperators= new Stack(); - for (int x = 0;x 0) { - returnStack.push(pOperators.pop()); - } - if (((Ptg)returnStack.peek()).getIsOperator()) { - int precedence= rankPrecedence(pthing); - Ptg p= (Ptg) returnStack.pop(); - int prevprecedence= rankPrecedence(p); - if (precedence>prevprecedence) { - pOperators.push(p); // switch less precedence operator with greater - } - else - returnStack.push(p); // back to normal - } - } else if (!pOperators.isEmpty()) { // added for instances such as (2)^-(2); - //code below prevents the switching of the operators ^- - Ptg p= (Ptg) pOperators.pop(); - pOperators.push(pthing); // save operators - pthing= p; - } - pOperators.push(pthing); // save operators - }else{ // it's not an operand; put on stack and pop all operators thus far - returnStack.push(pthing); - while (pOperators.size() > 0) { - returnStack.push(pOperators.pop()); - } - } - } - while (pOperators.size() > 0) { - returnStack.push(pOperators.pop()); - } - return returnStack; - } - */ - - - /** - * convert list of parseThings without reordering - * - * @param parseThings - * @return - */ - private static Stack convertToStack(CompatibleVector parseThings) { - Stack returnStack = new Stack(); - Stack pOperators = new Stack(); - for (int x = 0; x < parseThings.size(); x++) { - Ptg pthing = (Ptg) parseThings.elementAt(x); - returnStack.push(pthing); - } - return returnStack; - } - - /* - * Parses an internal string for a function, splitting out elements. - * for instance,(1<2), 3, tan(5); should return - * [(1<2)][3][tan(5)]. Currently just working off of commas, but this may change... - * - * One of the keys here is to not split on a comma from an internal function, for instance, - * "IF((1<2),MOD(45,6),0) should not split between 45 & 6! Note the badLocs vector that handles this. - */ - private static CompatibleVector splitFunctionOperands(String formStr) { - CompatibleVector locs = new CompatibleVector(); - // if there are no commas then we don't have to do all of this... - if (formStr.equals("")) return locs; // KSC: Handle no parameters by returning a null vector - - // first handle quoted strings 20081111 KSC - boolean loop = true; - int pos = 0; - CompatibleVector badLocs = new CompatibleVector(); - while (loop) { - char c = '"'; - int start = formStr.indexOf(c, pos); - if (start == -1) { // process single quotes as well - c = '\''; - start = formStr.indexOf(c, pos); - } - if (start != -1) { - int end = formStr.indexOf(c, start + 1); - end += 1; //include trailing quote - // check for being a part of a reference ... - if (end < formStr.length() && formStr.charAt(end) == '!') {// then it's part of a reference - end++; - while (end < formStr.length() && loop) { - c = formStr.charAt(end); - if (!(Character.isLetterOrDigit(c) || c == ':' || c == '$') || c == '-' || c == '+') - loop = false; - else - end++; - } - } - for (int y = start; y < end; y++) { - //make sure it is not a segment of a previous operand, like <> and >; - badLocs.add(Integer.valueOf(y)); - } - if (end == 0) { // means it didn't find an end quote - end = formStr.length() - 1; - loop = false; - } else { - pos = end; - loop = true; - } - } else { - loop = false; - } - } - - - if (formStr.indexOf(",") == -1) { - locs.add(formStr); - } else { - // Handle each parameter (delimited by ,) - // fill the badLocs vector with string locations we should disregard for comma proccesing - for (int i = 0; i < formStr.length(); i++) { - int openparen = formStr.indexOf("(", i); - if (openparen != -1) { - if (!badLocs.contains(Integer.valueOf(openparen))) { - int closeparen = getMatchOperator(formStr, openparen, '(', ')'); - if (closeparen == -1) closeparen = formStr.length(); - for (i = openparen; i < closeparen; i++) { - Integer in = Integer.valueOf(i); - badLocs.add(in); - } - } else { // open paren nested in quoted string - i = openparen + 1; - } - } else // 20081112 KSC - break; - } - // lets do the same for the array items - for (int i = 0; i < formStr.length(); i++) { - int openparen = formStr.indexOf("{", i); - if (openparen != -1) { - if (!badLocs.contains(Integer.valueOf(openparen))) { - int closeparen = getMatchOperator(formStr, openparen, '{', '}'); - if (closeparen == -1) closeparen = formStr.length(); - for (i = openparen; i < closeparen; i++) { - Integer in = Integer.valueOf(i); - badLocs.add(in); - } - } else { // open paren nested in quoted string - i = openparen + 1; - } - } else // 20081112 KSC - break; - } - // now check bad locations: - int placeholder = 0; - int holder = 0; - while (holder != -1) { - int i = formStr.indexOf(",", holder); - if (i != -1) { - Integer ing = Integer.valueOf(i); - if (!badLocs.contains(ing)) { - String s = formStr.substring(placeholder, i); - locs.add(s); - placeholder = i + 1; - } - holder = i + 1; - } else { - String s = formStr.substring(placeholder); - locs.add(s); - return locs; - } - } - } - return locs; - } - - /** - * parse a given string into known Ptg operators - * - * @param s - * @return - */ - private static CompatibleVector parsePtgOperators(String s, boolean bUnary) { - CompatibleVector ret = new CompatibleVector(); - s = StringTool.allTrim(s); - - for (int i = 0; i < XLSRecordFactory.ptgOps.length; i++) { - String ptgOpStr = XLSRecordFactory.ptgOps[i][0]; - if (s.startsWith("\"") || s.startsWith("'")) { - int end = s.substring(1).indexOf(s.charAt(0)); - end += 1; //include trailing quote - // TEST IF The quoted item is a sheet name - if (end < s.length() && s.charAt(end) == '!') {// then it's part of a reference - end++; - boolean loop = true; - while (end < s.length() && loop) { // if the quoted string is a sheet ref, get rest of reference - char c = s.charAt(end); - if (c == '#' && s.endsWith("#REF!")) { - end += 5; - loop = false; - } else if (!(Character.isLetterOrDigit(c) || c == ':' || c == '$') || c == '-' || c == '+') { - loop = false; - } else - end++; - } - } - ret.add(s.substring(0, end + 1)); - s = s.substring(end + 1); - bUnary = false; - if (!s.equals("")) - ret.addAll(parsePtgOperators(s, bUnary)); - break; - } - - int x = s.indexOf(ptgOpStr); - if (x > -1) { // found instance of an operator - // if encounter a parenthesis, must determine if it is an expression limit OR - // if it is part of a complex range, in which case the expression must be kept together - if (ptgOpStr.equals("(")) { - int end = getMatchOperator(s, x, '(', ')'); - ret.add(s); // add entire - break; -/* String ss= s.substring(x, end+1); - ret.add(ss.substring(x)); // add entire - if (FormulaParser.isComplexRange(ss)) { -// ret.add(ss.substring(x+1)); // skip beginning paren as screws up later parsing - ret.add(ss.substring(x)); - s= s.substring(end+1); - bUnary= false; - if (!s.isEmpty()) - ret.addAll(parsePtgOperators(s, bUnary)); - break; - } - } else if (ptgOpStr.equals(")")) { - try { - String ss= s.substring(x+1); - char nextChar= ss.charAt(0); - if (nextChar==' ') { // see if there is another operand after the space - ss= ss.trim(); - if (ss.length()>0 && ss.matches("[^(a-zA-Z].*")) { - nextChar= ss.charAt(0); - } - } - // complex ranges can contain parentheses in combo with these operators: :, ( - if (nextChar==' ' || nextChar==',' || nextChar==':' || nextChar==')') - continue; // keep complex range expression together - } catch (Exception e) { ; } -*/ - } - if (ptgOpStr.equals(")")) // parens are there to keep expression together - continue; - if (x > 0) {// process prefix, if any - unary since it's the first operand - // exception here-- error range in the form of "Sheet!#REF! (eg) needs to be kept whole - if (!(XLSRecordFactory.ptgLookup[i][1].equals("PtgErr") && s.charAt(x - 1) == '!')) { - ret.addAll(parsePtgOperators(s.substring(0, x), bUnary)); - bUnary = false; - } else { // keep entire error reference together - ptgOpStr = s; - } - } - x = x + ptgOpStr.length(); - if (bUnary) { - // unary ops +, - ... have a diff't Ptg than regular vers of the operator - if (ptgOpStr.equals("-") && x == 1 && ptgOpStr.length() > 1) break; //negative number, NOT a unary - - for (int j = 0; j < XLSRecordFactory.ptgPrefixOperators.length; j++) { - if (ptgOpStr.startsWith(XLSRecordFactory.ptgPrefixOperators[j][0].toString())) { - ptgOpStr = XLSRecordFactory.ptgPrefixOperators[j][1].toString(); - } - } - } - ret.add(ptgOpStr); - if (x < s.length()) // process suffix, if any - ret.addAll(parsePtgOperators(s.substring(x), true)); - break; - } - } - if (ret.isEmpty()) - ret.add(s); - return ret; - } - - /* - * Parses a string and returns an array based on contents - * Assumed to be 1 "final-level" operand i.e a range, complex range, a op b[...] - */ - private static CompatibleVector splitString(String formStr, boolean bIsComplete) { - // Use a vector, and the collections methods to sort in natural order - CompatibleVector locs = new CompatibleVector(); - CompatibleVector retVect = new CompatibleVector(); - - // check for escaped string literals & add positions to vector if needed - formStr = StringTool.allTrim(formStr); - if (formStr.equals("")) { - retVect.add(formStr); - return retVect; - } - if (true) { - retVect.addAll(parsePtgOperators(formStr, bIsComplete)); // cleanString if not an array formula???? s= cleanString(s); - bIsComplete = false; - return retVect; - } - - // 20081207 KSC: redo completely to handle complex formula strings e.g. strings containing quoted commas, parens ... - // first, pre-process to parse quoted strings, parentheses and array formulas - boolean isArray = false; - boolean loop = true; - String s = ""; - boolean inRange = false; - char prevc = 0; - for (int i = 0; i < formStr.length(); i++) { - char c = formStr.charAt(i); - if (c == '"' || c == '\'') { -/* if (!s.equals("")) { - locs.add(s); - s= ""; - } -*/ - int end = formStr.indexOf(c, i + 1); - end += 1; //include trailing quote - // TEST IF The quoted item is a sheet name - if (end < formStr.length() && formStr.charAt(end) == '!') {// then it's part of a reference - end++; - loop = true; - while (end < formStr.length() && loop) { // if the quoted string is a sheet ref, get rest of reference - c = formStr.charAt(end); - if (c == '#' && formStr.endsWith("#REF!")) { - end += 5; - loop = false; - } else if (!(Character.isLetterOrDigit(c) || c == ':' || c == '$') || c == '-' || c == '+') { - loop = false; - } else - end++; - } - } - locs.add(s + formStr.substring(i, end)); - s += formStr.substring(i, end); - i = end - 1; - } else if (c == '(') { // may be a complex range if s=="" - if (!s.equals("") && !inRange) { -// char prevc= s.charAt(s.length()-1); - if (!(prevc == ' ' || prevc == ':' || prevc == ',' || prevc == '(')) { - locs.add(s); - s = ""; - } else { // DO NOT split apart complex ranges - they parse to PtgMemFuncs - //Logger.logInfo("FormulaParser.splitString: PtgMemFunc" + formStr); - s += c; - inRange = true; - } - } - } else if (c == ':') { - if (prevc == ')' && locs.size() > 0) // complex range in style of: F(x):Y(x) - s = (String) locs.get(locs.size() - 1) + '(' + s; - inRange = true; - s += c; - } else if (c == '{') { - if (!s.equals("")) { - locs.add(s); - s = ""; - } - int end = formStr.indexOf("}", i + 1); - end += 1; //include trailing } - locs.add(formStr.substring(i, end)); - i = end - 1; - } else - s += c; - if (c != ' ') - prevc = c; - } - if (!s.equals("")) { - locs.add(s); - s = ""; - } - - // loop through the possible operator ptg's and get locations & length of them - for (int j = 0; j < locs.size(); j++) { - s = (String) locs.get(j); - if (s.startsWith("\"") || s.startsWith("'")) - retVect.add(s); // quoted strings - else { - if (s.startsWith("{")) // it's an array formula - isArray = true; // Do what?? else, cleanString?? - retVect.addAll(parsePtgOperators(s, bIsComplete)); // cleanString if not an array formula???? s= cleanString(s); - } - bIsComplete = false; // already parsed part of the formula string so cannot be unary :) - } - return retVect; - } - - /** - * helper method that turns operator & operand strings into the Ptg\ equivalent - * if there is no equivalant it leaves the string alone. - */ - private static String convertString(String ptg, WorkBook bk) { - // first check for operators - for (int i = 0; i < XLSRecordFactory.ptgLookup.length; i++) { - if (ptg.equalsIgnoreCase(XLSRecordFactory.ptgLookup[i][0])) { - return ptg; - } - } - - // KSC: Added for missing arguments ("") - if (StringTool.allTrim(ptg).equals("")) - //return "PtgMissArg"; - //if (ptg.equals("")) - return "PtgAtr"; // a space - - // Now we need to figure out what type of operand it is - // see if it is a string, should be encased by "" - if (ptg.substring(0, 1).equalsIgnoreCase("\"")) { - return "PtgStr"; - } - // is it an array? - if (ptg.substring(0, 1).equalsIgnoreCase("{")) { - return "PtgArray"; - } - // see if it is an integer - if (ptg.indexOf(".") == -1) { - try { - Integer i = Integer.valueOf(ptg); - if (i.intValue() >= 0 && i.intValue() <= 65535) // PtgInts are UNSIGNED + <=65535 - return "PtgInt"; - else - return "PtgNumber"; - } catch (NumberFormatException e) { - } - } - if (ptg.indexOf("%") == ptg.length() - 1) { // see if it's a percentage - try { - Double d = new Double(ptg.substring(0, ptg.indexOf("%"))); - return "PtgNumber"; - } catch (NumberFormatException e) { - } - } - // see if it is a Number - try { - Double d = new Double(ptg); - return "PtgNumber"; - } catch (NumberFormatException e) { - } - - - // at this point it is probably some sort of ptgref - if (ptg.indexOf(":") != -1 || ptg.indexOf(',') != -1 || ptg.indexOf("!") != -1) { - // ptgarea or ptgarea3d or ptgmemfunc - return "PtgArea"; // in ParseFinalLevel, PtgRef.createPtgRefFromString will handle all types of string refs - } - - - // maybe it is a garbage string, or a reference to a name (unsupported right now....) - // check if the last character is a number. If not, it sure isn't a reference, no? - // NO! Can have named ranges with numbers at the end -- better to try to parse it - try { - if (bk.getName(ptg) == null) {// it's not a named range - ExcelTools.getRowColFromString(ptg); // if passes it's a PtgRef - return "PtgRef"; - } else - return "PtgName"; - } catch (IllegalArgumentException e) { - return "PtgName"; - } - } - - /* - * helper method that cleans out unneccesary parts of the formula string. - */ - private static String cleanString(String dirtystring) { - String cleanstring = StringTool.allTrim(dirtystring); - cleanstring = StringTool.strip(cleanstring, "("); - cleanstring = StringTool.strip(cleanstring, ","); - return cleanstring; - } - - protected static Stack getPtgsFromFormulaString(String fmla) { - return null; - - } - - /** - * parse a formula in string form and create a formula record from it - * caluclate the new formula based on boolean setting calculate - * - * @param form Formula rec - * @param fmla String formula either =EXPRESSION or {=EXPRESSION} for array formulas - * @param calculate boolean truth of "calculate formula after setting" - * @return Formula rec - */ - public static Formula setFormula(Formula form, String fmla, int[] rc) { - if (fmla.charAt(0) != '{') { - try { - Stack newptgs = FormulaParser.getPtgsFromFormulaString(form, fmla); - FormulaParser.adjustParameterIds(newptgs); // 20100614 KSC: adjust function parameter id's, if necessary, for Value, Array or Reference type - form.setExpression(newptgs); - } catch (FunctionNotSupportedException e) { // 200902 KSC: still add record if function is not found (using N/A in place of said function) - Logger.logErr("Adding new Formula at " + form.getSheet() + "!" + ExcelTools.formatLocation(rc) + " failed: " + e.toString() + "."); - Stack newptgs = new Stack(); - newptgs.push(new PtgErr(PtgErr.ERROR_NA)); - form.setExpression(newptgs); - } - } else { // Handle Array Formulas - PtgExp pe = new PtgExp(); - pe.setParentRec(form); - // rowcol reference is from PARENT PtgExp not (necessarily) this formula's cell address - // [BugTracker 2683 + OOXML Array Formulas] - Object o = form.getSheet().getArrayFormulaParent(rc); - if (o != null) // there is a parent array formula; use it's rowcol - rc = (int[]) o; - else { // no parent yet- add - String addr = ExcelTools.formatLocation(rc); - form.getSheet().addParentArrayRef(addr, addr); - } - pe.init(rc[0], rc[1]); - Stack e = new Stack(); - e.push(pe); - FormulaParser.adjustParameterIds(e); // adjust function parameter id's, if necessary, for Value, Array or Reference type - form.setExpression(e); // add PtgExp to Formula Stack - Array a = new Array(); // Create new Array Record - a.setSheet(form.getSheet()); - a.setWorkBook(form.getWorkBook()); - a.init(fmla, rc[0], rc[1]); // init Array record from Formula String - form.addInternalRecord(a); // link array record to parent formula - } - - /* is this calc necessary? - Object val = null; - try{ - if (form.getWorkBook().getCalcMode() == WorkBook.CALCULATE_ALWAYS) - val = form.calculateFormula(); - }catch (Exception e){ - Logger.logWarn("Unsupported Function: " + e + ". OpenXLS calculation will be unavailable: " + fmla); //20081118 KSC: display a little more info ... - } - if(DEBUGLEVEL > 0)Logger.logInfo("FormulaParser.setFormula() string:" +fmla + " value: " + val); - */ - - return form; - } - - - public static String getFormulaString(Formula form) { - Stack expression = form.getExpression(); - return FormulaParser.getExpressionString(expression); - } - - public static String getExpressionString(Stack expression) { - StringBuffer retval = new StringBuffer(); - int sz = expression.size(); - Ptg[] stck = new Ptg[sz]; - stck = (Ptg[]) expression.toArray(stck); - Stack newstck = new Stack(); - for (int t = 0; t < sz; t++) { // flip the stack - newstck.add(0, stck[t]); - } - Stack vals = new Stack(); - while (!newstck.isEmpty()) { - handlePtg(newstck, vals); - } - String s = ""; - while (!vals.isEmpty()) { - Ptg topP = (Ptg) vals.pop(); - s = topP.getTextString() + s; - } - retval = new StringBuffer("=" + s); - return retval.toString(); - } - - /** - * adjustParameterIds pre-processes the function expression stack, - * analyzing function parameters to ensure that function operands - * contain the correct PtgID (which indicates Value, Reference or Array) - *
                      - * Reference-type ptg's contain an ID which indicates the type - * required by the calling function: Value, Reference or Array. - *
                      - * When formula strings are parsed into formula expressions, - * ptg reference-type parameters are assigned a default id, - * but this id may not be correct for all functions. - */ - public static void adjustParameterIds(Stack expression) { - StringBuffer retval = new StringBuffer(); - int sz = expression.size(); - Ptg[] stck = new Ptg[sz]; - stck = (Ptg[]) expression.toArray(stck); - Stack newstck = new Stack(); - for (int t = 0; t < sz; t++) { // flip the stack - newstck.add(0, stck[t]); - } - Stack params = new Stack(); - // we only care about PtgFuncVar and PtgFunc's but need to process the expression - // stack thoroughly to get the correct parameters - // Process function stack, gathering parameters. When we have all the parameters - // for a PtgFunc or a PtgFuncVar, adjust any PtgRef types, if necessary - while (!newstck.isEmpty()) { - Ptg p = (Ptg) newstck.pop(); - int x = 0, t = 0;// cargs = p.getNumParams(); - if (p.getIsControl()) { - // do the parens thing here... - if (p.getOpcode() == 0x15) { // its a parens... and there is a val - if (t > 0) { - // 20060128 - KSC: handle parens - Ptg[] vx = new Ptg[1]; // parens are unary ops so only 1 var allowed - vx[0] = (Ptg) params.pop(); - p.setVars(vx); - params.push(p); // put paren (with var) back on stack - } else { // this paren wraps other parens... - params.push(p); - } - } - } else if (p.getIsOperator() || p.getIsFunction()) { - if (p.getIsBinaryOperator()) t = 2; - if (p.getIsUnaryOperator()) t = 1; - if (p.getIsStandAloneOperator()) t = 0; - if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62) { - t = p.getNumParams(); - }// it's a ptgfuncvar - if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61) { - t = p.getNumParams(); - }// it's a ptgFunc - - if (t > params.size()) { - t = params.size(); - } - Ptg[] vx = new Ptg[t]; - while (t > 0) - vx[--t] = (Ptg) params.pop();// get'em - p.setVars(vx); // set'em - // here is where we adjust the ptg's of the func or funcvar parameters - if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62) /* it's a ptgfuncvar*/ - ((PtgFuncVar) p).adjustParameterIds(); - else if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61)/* it's a ptgFunc */ - ((PtgFunc) p).adjustParameterIds(); - params.push(p);// push it back on the stack - } else if (p.getIsOperand()) { - params.push(p); - } - } - } - - /** - * set up the Formula chain - *

                      - * A5 - * 10 - *

                      - * 3 - * EXP( - * + - * SUM( - * =SUM(A5*10+EXP(3)) - * =SUM(EXP(A5*103)) - *

                      - * A3 add to vals - * E5 add to vals - * + pop last2 vals add to vals - * ( check last -- if oper add oper, else add last, add to vals - *

                      - * A1:A2 at this point we should see: (A3+E5) push to vals - * SUM( check last -- if oper add oper, else add last, add to vals - * + pop last2 add to vals - * SUM( check last -- if oper add oper, else add last, add to vals - * =SUM((A3+E5)+SUM(A1:A2)) - * =SUM((A3+E5)+SUM(A1:A2)) - *

                      - * A1 add to vals - * A2 add to vals - * + pop last2 vals add to vals? - * ( check last -- if oper add oper, else add last, add to vals - * A3 add to vals - * A4 add to vals - * + check last -- if paren pop last2 vals add to vals? - * ( check last -- if oper add oper, else add last, add to vals - * / pop last2 vals add to vals - *

                      - * =(A1+A2)/(A3+A4) - * =(A1+A2)/(A3+A4) - *

                      - *

                      - * PtgFunc taking 3 vals again can only have 1 - * We know IF has 3 vals - * Do we need logic which knows how many vars a Ptg takes? (we sure do... -nick) Might help. - *

                      - * ---> WRITE CODE TO SWITCH ON NUMBER OF PARAMS. Should Fix. - *

                      - *

                      - * =IF(SUM(CONCATENATE(SUM((EXP(C2,D4,4))*2SUM(A5,C7,A2)A1:A5))=1),SUM(3),SUM(22)) - * =IF(CONCATENATE(C2,(D4+EXP(4))*2,SUM(A5,C7,A2),SUM(A1:A5))=1,3,22) - */ - static void handlePtg(Stack newstck, Stack vals) { - Ptg p = (Ptg) newstck.pop(); - int x = 0, t = 0;// cargs = p.getNumParams(); - if (p.getIsOperator() || p.getIsControl() || p.getIsFunction()) { - t = vals.size(); //this is faulty logic. We don't care what is there, the operator should tell us. - // do the parens thing here... - if (p.getIsControl() /* !vals.isEmpty()*/) { - if (p.getOpcode() == 0x15) { // its a parens... and there is a val - if (t > 0) { - // 20060128 - KSC: handle parens - Ptg[] vx = new Ptg[1]; // parens are unary ops so only 1 var allowed - vx[0] = (Ptg) vals.pop(); - p.setVars(vx); - vals.push(p); // put paren (with var) back on stack - } else { // this paren wraps other parens... - vals.push(p); - } - return; - } - } - if (t > 0) { - // make sure we have the correct amount popped back in.. - if (p.getIsBinaryOperator()) t = 2; - if (p.getIsUnaryOperator()) t = 1; - if (p.getIsStandAloneOperator()) t = 0; - if (p.getOpcode() == 0x22 || p.getOpcode() == 0x42 || p.getOpcode() == 0x62) { - t = p.getNumParams(); - }// it's a ptgfuncvar! - if (p.getOpcode() == 0x21 || p.getOpcode() == 0x41 || p.getOpcode() == 0x61) { - t = p.getNumParams(); - }// it's a ptgFunc - - if (t > vals.size()) { - - // is this a real error? throw an exception? - if (DEBUGLEVEL > 0) - Logger.logWarn("FormulaParser.handlePtg: number of parameters " + t + " is greater than available " + vals.size()); - t = vals.size(); - } - Ptg[] vx = new Ptg[t]; - while (t > 0) - vx[--t] = (Ptg) vals.pop();// get'em - p.setVars(vx); // set'em - } - vals.push(p);// push it back on the stack - } else if (p.getIsOperand()) { - vals.push(p); - } else if (p instanceof PtgAtr) { - // this is probably just a space at this point, don't output error message - } else { - if (DEBUGLEVEL > -1) - Logger.logInfo("FormulaParser Error - Ptg Type: " + p.getOpcode() + " " + p.getString()); - } - } - - /** - * create a new formula record at row column rc using formula string formStr - * - * @param formStr String - * @param st - * @param rc int[] - * @throws Exception - * @return new Formula record - */ - public static Formula getFormulaFromString(String formStr, Boundsheet st, int[] rc) - throws Exception { - Formula f = new Formula(); - if (st != null) { - f.setSheet(st); - f.setWorkBook(st.getWorkBook()); - } - f.setData(new byte[6]); // necessary for setRowCol - f.setRowCol(rc); // do before calculateFormula as array formulas use rowcol 20090817 KSC: [BugTracker 2683 + OOXML Array Formulas] - f = FormulaParser.setFormula(f, formStr, rc); - - return f; - } - - /** - * create a new formula record at row column rc using formula string formStr - * - * @param formStr String - * @param rc int[] - * @throws Exception - * @return new Formula record - */ - public static Formula setFormulaString(String formStr, int[] rc) - throws /* 20070212 KSC: FunctionNotSupported*/Exception { - Formula f = new Formula(); - f = FormulaParser.setFormula(f, formStr, rc); - return f; - } - - - /** - * returns true of string s is in the form of a basic reference e.g. A1 - * - * @param s - * @return - */ - public static boolean isRef(String s) { - if (s == null) return false; - String simpleOne = "(([ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})"; - return s.matches(simpleOne); - } - - /** - * returns true if the stirng in question is in the form of a range - * - * @param s - * @return - */ - public static boolean isRange(String s) { - if (s == null) return false; - String one = "(([(]*[ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+[)]*){1})"; - String aRange = one + "(:" + one + ")?"; - String rangeop = "([ ]*[: ,][ ]*)"; - String rangeMatchString = aRange + rangeop + aRange + "(" + rangeop + aRange + ")*"; - String simpleOne = "(([ ]*[']?([a-zA-Z0-9 ]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})"; - String simpleRangeMatchString = "(" + simpleOne + "[ ]*[:][ ]*" + simpleOne + ")"; - return (s.matches(rangeMatchString)); - } - - /** - * returns true if the string represents a complex range (i.e. one containing multiple range values separated by one or more of: , : or space - * - * @param s - * @return - */ - public static boolean isComplexRange(String s) { - String one = "(([(]*[ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+[)]*){1})"; - String aRange = one + "(:" + one + ")?"; - String rangeop = "([ ]*[: ,][ ]*)"; - String rangeMatchString = aRange + rangeop + aRange + "(" + rangeop + aRange + ")*"; - String simpleOne = "(([ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})"; - String simpleRangeMatchString = "(" + simpleOne + "[ ]*[:][ ]*" + simpleOne + ")"; - return (isRange(s)) && !(s.matches(simpleRangeMatchString)); - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/FormulaParser.kt b/src/main/java/io/starter/formats/XLS/formulas/FormulaParser.kt new file mode 100644 index 0000000..0ff3bf3 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/FormulaParser.kt @@ -0,0 +1,1756 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.* +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool + +import java.util.Locale +import java.util.Stack +import java.util.Vector + +//import com.sun.org.apache.xerces.internal.impl.xs.identity.Selector.Matcher; + + +/** + * Formula Parser. + * + * + * Translates Excel-compatible Strings into Biff8/OpenXLS Compatible Formulas and vice-versa. + * + * @see Formula + */ +object FormulaParser { + + + internal var DEBUGLEVEL = -1 + // KSC: handling unary operators necessitated, in the end, a complete rewrite ... + + /** + * getPtgsFromFormulaString + * returns ordered stack of Ptgs parsed from formula string fmla + * + * @param Formula form formula record + * @param String fmla string rep. of current state of formula (either original "=F(Y)" or a recurrsed state e.g. "Y") + * @returns Stack ordered Ptgs that represent formula expression + */ + fun getPtgsFromFormulaString(form: XLSRecord, fmla: String): Stack<*> { + return getPtgsFromFormulaString(form, fmla, true) + } + + /** + * getPtgsFromFormulaString is the main entry point for parsing a string and creating a formula. + * The formula passed in at this point can either be an existing formula with an expression, or a + * templated formula with no expression. The string gets parsed and entered + * as the expression for the formula. + * + * @param Formula form formula record + * @param String fmla string rep. of current state of formula (either original "=F(Y)" or a recurrsed state e.g. "Y") + * @param boolean bIsCompleteExpression truth of "formula fmla represents a complete formula i.e. we are not currently in a recurrsed state" + * @returns Stack ordered Ptgs that represent formula expression + */ + /** + * should handle all sorts of variations of formula strings such as: + * =PV(C17,1+-(1*1)-9, 0, 1) + * =100*0.5 + * =(B2-B3)*B4 + * =SUM(IF(A1:A10=B1:B10, 1, 0)) + * =IF(B4<=10,"10", if(b4<=100, "15", "20")) + * ="STRING"&IF(A<>"",A,"N/A")&" - &IF(C<>"",C,"N/A")&" Result " + * + * + * in basic essence, handles signatures such as + * a op f(b op c, d, uop e ...) op g + * + * + * where op is any binary operator, uop is a unary operator f is a formula + * ... + */ + internal fun getPtgsFromFormulaString(form: XLSRecord, fmla: String, bMergeWithLast: Boolean): Stack<*> { + var fmla = fmla + var bMergeWithLast = bMergeWithLast + val operands = arrayOfNulls(2) + + fmla = fmla.trim { it <= ' ' } + if (fmla.startsWith("=")) fmla = fmla.substring(1) + fmla = fmla.trim { it <= ' ' } + + + if (fmla == "") { // 20081120 KSC: Handle Missing Argument + val s = Stack() + s.add(PtgMissArg()) + return s + } + + if (fmla.startsWith("{")) { // must process array formula first, PtgArray expects full function string + val pa = PtgArray() + pa.parentRec = form + val endarray = getMatchOperator(fmla, 0, '{', '}') + pa.setVal(fmla.substring(0, endarray + 1)) + fmla = fmla.substring(endarray + 1) + val s = Stack() + s.add(pa) + operands[0] = s + bMergeWithLast = false + } + // TODO: complex ranges?? + var inQuote = false + var inRange = false + var inOp = false + var prefix = "" + val ops = Stack() + var op = "" + var i = 0 + while (i < fmla.length) { + val c = fmla[i] + if (c == '"' || c == '\'') { // get to ending quote + if (inQuote) { + inQuote = c != prefix.trim { it <= ' ' }.get(0) // if start quote == end quote, inQuote is false + } else + inQuote = true + if (inQuote) { + if (inOp) { + inOp = false + if (op != "") ops.add(0, op) + op = "" + if (operands[0] != null && operands[1] != null) + addOperands(form, /*functionStack, */operands, ops) + } + } + prefix += c + } else if (inQuote) { + prefix += c + } else if (c == ':') { + if (i > 0) + inRange = true + prefix += c + } else if (c == '(') { // found a formula?? check out + // if the parenthesis is part of a complex range, keep going i.e. keep entire expression together + if (inRange) { + prefix += c + i++ + continue + } + if (inOp) { + inOp = false + if (op != "") ops.add(0, op) + op = "" + if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { + addOperands(form, /*functionStack, */operands, ops) + } + } + var funcName = "" + for (k in prefix.length - 1 downTo 0) { + if (Character.isLetterOrDigit(prefix[k]) || Character.toString(prefix[k]) == ".") { + funcName = prefix[k] + funcName + prefix = prefix.substring(0, k) + } else + break + } + // prefix= anything before function name + if (prefix.trim { it <= ' ' } != "") { + if (operands[0] == null) + operands[0] = prefix.trim { it <= ' ' } + else + operands[1] = prefix.trim { it <= ' ' } + prefix = "" + } + // function name should = part just before parents + if (funcName != "") { + var funcPtg: Ptg? = null + funcPtg = getFuncPtg(funcName, form) + + // do we have a valid function Ptg? + if (funcPtg != null) { // yes, then handle function paramters i.e. evertyhing between the parentheses + val endparen = getMatchOperator(fmla, i, '(', ')') + if (endparen < fmla.length - 1) { + if (fmla[endparen + 1] == ':') { // it's a VERY complex range :) + inRange = true + prefix = funcName + fmla.substring(i, endparen + 1) // keep function name together ... + i = endparen + i++ + continue + } + } + // things like: xyz + f(x) + if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { + addOperands(form, /*functionStack, */operands, ops) + inOp = false + } // have [xyz, xyz, OP] + [abc, def, ghi] + // parse function + val s = parseFunctionPtg(form, funcName, fmla.substring(i + 1, endparen), funcPtg) + if (operands[0] == null) + operands[0] = s + else + operands[1] = s + //functionStack.addAll(parseFunctionPtg(form, funcName, fmla.substring(i+1, endparen), funcPtg)); + i = endparen // inc. pointer to past processing point + } else + // else, we have *something* in front of the parentheses ... + throw FunctionNotSupportedException("$funcName is not a supported function") + } else { // enclosing parens + // complexities occur for complex ranges and enclosing parens ... + val endparen = getMatchOperator(fmla, i, '(', ')') + if (endparen == -1 || endparen < fmla.length - 1 && fmla[endparen + 1] == ':') { // it's a VERY complex range :) + inRange = true + prefix = "(" + i++ + continue + } + val f = fmla.substring(i + 1, endparen) // the statement less the parenthesis + i = endparen // skip parens ... + // see if the enclosed expression is a complex range - must parse as 1 unit, rather than parsing particular ptgs + if (FormulaParser.isComplexRange("($f)")) { + val s = Stack() + s.push(parseSinglePtg(form, "($f)", false)) + s.push(parseSinglePtg(form, ")", false)) + if (operands[0] == null) + operands[0] = s + else + operands[1] = s + } else { // embedded functions, keep parsing + val s = getPtgsFromFormulaString(form, f, true) // flag as a complete expression + s.push(PtgParen()) // add ending parens to stack + if (operands[0] == null) + operands[0] = s + else + operands[1] = s + if (!ops.isEmpty()) { + addOperands(form, /*functionStack, */operands, ops) + } + } + } + } else { // see if we have found an operataor + if (!Character.isJavaIdentifierPart(c) && c != ' ' && c != '%') { + // if (inRange && !Character.isJavaIdentifierPart(c) && c!=',' && c!=' ') + if (inRange) { + if (c != ',' && c != ' ' && c != ')' && c != '!') { + inRange = false + if (prefix.trim { it <= ' ' } != "") { + if (operands[0] == null) + operands[0] = prefix.trim { it <= ' ' } + else + operands[1] = prefix.trim { it <= ' ' } + prefix = "" + } + } else { + prefix += c + i++ + continue + } + } + if (c != '!' && c != '#' && c != '.') { // ignore ! + // FOUND AN OPERATOR - ready to add operands yet? + inOp = true + if (prefix.trim { it <= ' ' } != "") { + if (operands[0] == null) + operands[0] = prefix.trim { it <= ' ' } + else + operands[1] = prefix.trim { it <= ' ' } + prefix = "" + } + if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { + addOperands(form, /*functionStack, */operands, ops) + } + if (op != "") { + if (!(c == '=' || c == '>')) { + ops.add(0, op) + op = "" + } + } + op += c // >,<,-,/, ,+ + i++ + continue + } + } else if (inOp) { + inOp = false + if (!ops.isEmpty() && operands[0] != null && operands[1] != null) { + addOperands(form, /*functionStack, */operands, ops) + } + if (op != "") + ops.add(0, op) + op = "" + } + prefix += c + } + i++ + + } + if (prefix.trim { it <= ' ' } != "") { // get any remaining elements + if (operands[0] == null) + operands[0] = prefix.trim { it <= ' ' } + else + operands[1] = prefix.trim { it <= ' ' } + prefix = "" + } + + if (op != "") + ops.add(0, op) + addOperands(form, operands, ops) + //if (((Stack)operands[0]).isEmpty()) + //return functionStack; + return operands[0] as Stack<*> + + } + + /** + * Given two operands (objects) and operators (can be up to 2 if there is a unary operator present) + * organize and add to functionStack in reverse polish notation i.e. + * OPERAND, OPERAND, OP [...] + * + * @param form + * @param functionStack + * @param operands + * @param ops + */ + private fun addOperands(form: XLSRecord, /*Stack functionStack, */operands: Array, ops: Stack<*>) { + var pOp: Ptg? = null + if (!ops.isEmpty()) + pOp = parseSinglePtg(form, ops.pop() as String, operands[1] == null) + + var s = Stack() + s.addAll(handleOperatorPrecedence(form, /*functionStack, */operands, pOp)) + operands[0] = s + //functionStack.clear(); + if (!ops.isEmpty()) { + pOp = parseSinglePtg(form, ops.pop() as String, true) + s = Stack() + s.addAll(handleOperatorPrecedence(form, /*functionStack, */operands, pOp)) + operands[0] = s + } + + } + + private fun handleOperatorPrecedence(form: XLSRecord, /*Stack functionStack, */operands: Array, pOp: Ptg?): Stack<*> { + var pOp = pOp + var functionStack = Stack() + if (operands[0] is Stack<*>) { + functionStack = operands[0] as Stack<*> + operands[0] = null + } + if (!functionStack.isEmpty() && pOp != null) { + val lastOp = functionStack.peek() as Ptg + if (lastOp != null && lastOp.isOperator) { + if (lastOp.isOperator) { + functionStack.pop() //= lastOp + val group1 = rankPrecedence(pOp) + val group2 = rankPrecedence(lastOp) + if (group2 >= group1) { + functionStack.push(lastOp) + } else { // current op has higher priority + if (operands[0] != null) { + if (operands[0] is String) + functionStack.push(parseSinglePtg(form, operands[0] as String, functionStack.isEmpty())) + else + functionStack.addAll(operands[0] as Stack<*>) + } + if (operands[1] != null) { + if (operands[1] is String) + functionStack.push(parseSinglePtg(form, operands[1] as String, functionStack.isEmpty())) + else + functionStack.addAll(operands[1] as Stack<*>) + } + operands[0] = null + operands[1] = null + functionStack.push(pOp) + pOp = lastOp + } + } + } + } + if (operands[0] != null) { + if (operands[0] is String) + functionStack.push(parseSinglePtg(form, operands[0] as String, functionStack.isEmpty())) + else + functionStack.addAll(operands[0] as Stack<*>) + } + if (operands[1] != null) { + if (operands[1] is String) + functionStack.push(parseSinglePtg(form, operands[1] as String, functionStack.isEmpty())) + else + functionStack.addAll(operands[1] as Stack<*>) + } + operands[0] = null + operands[1] = null + if (pOp != null) + functionStack.push(pOp) + return functionStack + } + + + /** + * merge last stacks to ensure operator order is correct + * + * @param functionStack + * @return + */ + private fun mergeStacks(prevStack: Stack<*>, curStack: Stack<*>, bIsCompleteExpression: Boolean): Stack<*> { + if (prevStack.isEmpty()) + return curStack + + var lastOp = prevStack.peek() as Ptg + val curOp = if (curStack.isEmpty()) null else curStack.peek() as Ptg + val group1 = rankPrecedence(lastOp) + val group2 = rankPrecedence(curOp) + if (group1 >= 0 && (group1 < group2 || group2 == -1)) { + lastOp = prevStack.pop() as Ptg + curStack.push(lastOp) + /* while (curOp.getIsOperator()) { + lastOp= curOp; + curStack.push(curOp); + if (!prevStack.isEmpty()) { + curOp = (Ptg) prevStack.pop(); + // handle precedence + group1=rankPrecedence(curOp); + group2=rankPrecedence(lastOp); + if (!(group1>=0 && (group1 < group2 || group2==-1))) + break; + }else + return curStack; + } +*/ + } + prevStack.addAll(curStack) + return prevStack + } + + + /** + * parse and add to Stack a valid Excel function represented by funcPtg and fmla string + * called from getPtgsFromFormulaString + * + * @param form formula record + * @param fmla function parameters in the form of (x, y, z) + * @param funcPtg function data for the formula represented by fmla + * @para func function name f + * @return Stack ordered parsed Stack of Ptgs + */ + private fun parseFunctionPtg(form: XLSRecord, func: String, fmla: String, funcPtg: Ptg): Stack<*> { + var fmla = fmla + val returnStack = Stack() + fmla = fmla.trim { it <= ' ' } + var nParens = 0 + var enclosing = false + // change: only remove 1 set of parens: + if (fmla.length > 0 && fmla[0] == '(') { + if (getMatchOperator(fmla, 0, '(', ')') == fmla.length - 1) { // then strip enclosing parens + nParens++ + enclosing = true + } + fmla = fmla.trim { it <= ' ' } + } + + // NOTE: all memfuncs/complex ranges are enclosed by parentheses + // IF enclosed by parens, DO NOT split apart into operands: + var funcLen = 1 + if (enclosing) { + returnStack.addAll(FormulaParser.getPtgsFromFormulaString(form, fmla, true)) + } else { + val cv = splitFunctionOperands(fmla) + funcLen = cv.size + // loop through the operands to the function and recurse + for (y in cv.indices) { + val s = cv.elementAt(y) as String // flag as a complete expression + returnStack.addAll(FormulaParser.getPtgsFromFormulaString(form, s, true)) + } + } + + // Handle PtgFuncVar-specifics such as number of parameters and add-in PtgNameX record + if (funcPtg is PtgFuncVar) { + if (funcPtg.`val` == FunctionConstants.xlfADDIN) { + // if an add-in, must add PtgNameX to stack + val pn = PtgNameX() + pn.parentRec = form + pn.setName(func) + returnStack.add(0, pn) // add to bottom of stack + funcLen++ + funcPtg.parentRec = form // nec. to resolve external name + } + funcPtg.setNumParams(funcLen.toByte()) + } + returnStack.push(funcPtg) + return returnStack + } + + /** + * combine two stacks of Ptgs, popping the operator of the sourceStack and + * adding it to the end of the destination stack to + * ensure it is in the correct order in the destination stack + * + * @param sourceStack + * @param destStack + * / + * private static Stack addPtgStacks(Stack sourceStack, Stack destStack) { + * Ptg opPtg = (Ptg) sourceStack.pop(); + * Ptg lastOp= (destStack.isEmpty()?null:((Ptg)destStack.peek())); + * + * // handle precedence: unaries before ^(power) before *, / before +, - before &(concat), before comparisons (=, <>, <=, >=, <, >) + * int group1=rankPrecedence(opPtg); + * int group2=rankPrecedence(lastOp); + * + * if (group1>=0 && (group1 < group2 || group2==-1)) { + * while (opPtg.getIsOperator()) { + * lastOp= opPtg; + * destStack.push(opPtg); + * if (!sourceStack.isEmpty()) { + * opPtg = (Ptg) sourceStack.pop(); + * // handle precedence + * group1=rankPrecedence(opPtg); + * group2=rankPrecedence(lastOp); + * if (!(group1>=0 && (group1 < group2 || group2==-1))) + * break; + * }else + * return destStack; + * } + * } + * sourceStack.push(opPtg); + * + * // after sorting out operators, assemble two stacks into one + * Stack nwstack = destStack; + * destStack = new Stack(); + * destStack.addAll(sourceStack); + * destStack.addAll(nwstack); + * + * return destStack; + * } + */ + + /** + * rank a Ptg Operator's precedence (lower + * + * @param curOp + * @return + */ + internal fun rankPrecedence(curOp: Ptg?): Int { + if (curOp == null) return -1 + // if (curOp==null || !curOp.getIsOperator()) return -1; + if (curOp is PtgUMinus || curOp is PtgUPlus) + return 7 + if (curOp is PtgPercent) + return 6 + if (curOp is PtgPower) + return 5 + if (curOp is PtgMlt || curOp is PtgDiv) + return 4 + else if (curOp is PtgAdd || curOp is PtgSub) + return 3 + else if (curOp is PtgConcat) + return 2 + else if (curOp is PtgEQ || curOp is PtgNE || + curOp is PtgLE || curOp is PtgLT || + curOp is PtgGE || curOp is PtgGT) + return 1 + // else if (curOp instanceof PtgParen) + // return 0; + return -1 + } + + /* + * getMatchOperator takes a string and starting operator location. + * It then parses the string and determines which closing operator + * matches the opening parens specified by startParenLoc. Returns + * -1 if it cannot find a match. + */ + fun getMatchOperator(input: String, startParenLoc: Int, matchOpenChar: Char, matchCloseChar: Char): Int { + // 20081112 KSC: do a different way as it wasn't working for all cases + var openCnt = 0 + var i = startParenLoc + while (i < input.length) { + if (input[i] == '"' || input[i] == '\'') {// handle quoted strings within input (quoted strings may of course contain match chars ... + val endquote = input[i] + while (++i < input.length) { + if (input[i] == endquote) { + break + } + } + } + if (i == input.length) return i - 1 + if (input[i] == matchOpenChar) + openCnt++ + else if (input[i] == matchCloseChar) { + openCnt-- + if (openCnt == 0) + return i + } + i++ + } + + // no parens for you! + return -1 + } + + /** + * Looks up a function string and returns a funcPtg if it is found + * + * @param func function string without parents i.e. SUM or DB + * @returns Ptg valid funcPtg or null if not found + */ + // 20090210 KSC: add form so can set parent record for PtgFunc and PtgFuncVar - nec for self-referential formulas such as COLUMN + private fun getFuncPtg(func: String, form: XLSRecord): Ptg? { + var funcPtg: Ptg? = null + // if (true) { + if (Locale.JAPAN == Locale.getDefault()) { + for (y in FunctionConstants.jRecArr.indices) { + if (func.equals(FunctionConstants.jRecArr[y][0], ignoreCase = true)) { + val FID = Integer.parseInt(FunctionConstants.jRecArr[y][1]) + val Ftype = Integer.parseInt(FunctionConstants.jRecArr[y][2]) + if (Ftype == FunctionConstants.FTYPE_PTGFUNC) { + funcPtg = PtgFunc(FID, form) + } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR) { + funcPtg = PtgFuncVar(FID, 0, form) + } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR_ADDIN) { + funcPtg = PtgFuncVar(FunctionConstants.xlfADDIN, 0, form) + } + return funcPtg + } + } + } + for (y in FunctionConstants.recArr.indices) { + if (func.equals(FunctionConstants.recArr[y][0], ignoreCase = true)) { + val FID = Integer.parseInt(FunctionConstants.recArr[y][1]) + val Ftype = Integer.parseInt(FunctionConstants.recArr[y][2]) + if (Ftype == FunctionConstants.FTYPE_PTGFUNC) { + funcPtg = PtgFunc(FID, form) + } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR) { + funcPtg = PtgFuncVar(FID, 0, form) + } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR_ADDIN) { + funcPtg = PtgFuncVar(FunctionConstants.xlfADDIN, 0, form) + } + return funcPtg + } + } + for (y in FunctionConstants.unimplRecArr.indices) { + if (func.equals(FunctionConstants.unimplRecArr[y][0], ignoreCase = true)) { + val FID = Integer.parseInt(FunctionConstants.unimplRecArr[y][1]) + val Ftype = Integer.parseInt(FunctionConstants.unimplRecArr[y][2]) + if (Ftype == FunctionConstants.FTYPE_PTGFUNC) { + funcPtg = PtgFunc(FID, form) + } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR) { + funcPtg = PtgFuncVar(FID, 0, form) + } else if (Ftype == FunctionConstants.FTYPE_PTGFUNCVAR_ADDIN) { + funcPtg = PtgFuncVar(FunctionConstants.xlfADDIN, 0, form) + } + return funcPtg + } + } + return funcPtg + + } + + + /** + * take a string guaranteed to be a single Ptg (operator, reference, string, etc) and convert to correct Ptg + * + * @param form + * @param fmla + * @param bIsUnary -- operator is a unary version + * @return + */ + private fun parseSinglePtg(form: XLSRecord, fmla: String, bIsUnary: Boolean): Ptg? { + val bk = form.workBook // nec. to determine if parsed element is a valid name handle name + + var `val` = fmla + var name = convertString(`val`, bk) + if (name == "+" && bIsUnary) + name = "u+" + else if (name == "-" && bIsUnary) + name = "u-" + + var pthing: Ptg? = null + try { + pthing = XLSRecordFactory.getPtgRecord(name) + if (pthing == null && name == "PtgName") + // TODO: MUST evaluate which type of PtgName is correct: understand usage! + if (form.opcode == XLSConstants.FORMULA || form.opcode == XLSConstants.ARRAY) + pthing = PtgName(0x43) // assume this token to be of type Value (i.e PtgNameV) instead of Reference (PtgNameR) + else + // DV needs ref-type name + pthing = PtgName(0x23) // PtgNameR + } catch (e: InvalidRecordException) { + Logger.logInfo("parsing formula string. Invalid Ptg: $name error: $e") + } + + // if it is an operator we don't need to do anything with it! + if (pthing != null) { + pthing.parentRec = form + + if (!pthing.isOperator) { + if (pthing.isReference) { + // createPtgRefFromString will handle any type of string reference + // will return a PtgRefErr if cannot parse location + pthing = PtgRef.createPtgRefFromString(`val`, form) + } else if (pthing is PtgStr) { + val pstr = pthing as PtgStr? + `val` = StringTool.strip(`val`, '\"') + pstr!!.`val` = `val` + } else if (pthing is PtgNumber) { + val pnum = pthing as PtgNumber? + if (`val`.indexOf("%") == -1) + pnum!!.`val` = Double(`val`) + else + pnum!!.setVal(`val`) + } else if (pthing is PtgInt) { + val pint = pthing as PtgInt? + pint!!.`val` = Integer.valueOf(`val`).toInt() + } else if (pthing is PtgBool) { + val pbool = pthing as PtgBool? + pbool!!.setVal(java.lang.Boolean.valueOf(`val`).booleanValue()) + } else if (pthing is PtgArray) { + val parr = pthing as PtgArray? + parr!!.setVal(`val`) + } else if (pthing is PtgName) { // SHOULD really return PtgErr("#NAME!") as it's a missing Name instead of adding a new name + val pname = pthing as PtgName? + pname!!.setName(`val`) + pname.addToRefTracker() + } else if (pthing is PtgNameX) { + val pnameX = pthing as PtgNameX? + pnameX!!.setName(`val`) + } else if (pthing is PtgMissArg) { + pthing.init(byteArrayOf(22)) + } else if (pthing is PtgErr) { + pthing = PtgErr(PtgErr.convertStringToLookupByte(`val`)) + } else if (pthing is PtgAtr) { + pthing = PtgAtr(0x40.toByte()) // assume space + } + } + } else { + val pname = PtgMissArg() + } + return pthing + } + + private fun findPtg(fmla: String, bUnary: Boolean): String { + val s = StringTool.allTrim(fmla) + + if (s.startsWith("\"") || s.startsWith("'")) { + //return s; // it's a string + return s + } + + for (i in XLSRecordFactory.ptgOps.indices) { + var ptgOpStr = XLSRecordFactory.ptgOps[i][0] + + val x = s.indexOf(ptgOpStr) + if (x == 0) { // found instance of an operator + // if encounter a parenthesis, must determine if it is an expression limit OR + // if it is part of a complex range, in which case the expression must be kept together + if (ptgOpStr == "(") { + return s // parens means a whole complex range==>PtgMemFunc + } + if (bUnary) { + // unary ops +, - ... have a diff't Ptg than regular vers of the operator + if (ptgOpStr == "-" && x == 1 && ptgOpStr.length > 1) break //negative number, NOT a unary - + for (j in XLSRecordFactory.ptgPrefixOperators.indices) { + if (ptgOpStr.startsWith(XLSRecordFactory.ptgPrefixOperators[j][0].toString())) { + ptgOpStr = XLSRecordFactory.ptgPrefixOperators[j][1].toString() + } + } + } + return XLSRecordFactory.ptgOps[i][1] + } + } + return s + } + + /** + * parseFinalLevel is where strings get converted into ptg's + * This method can handle multiple ptg's within a string, but cannot handle + * recursion. If you are having recursion problems look into getPtgsFromFormulaString above. + * + * + * This method should be called from the final level of parsing. + * There should not be additional sub-expressions at this point. + * Example (1+2) or (3,4,5) + * NOT ((1<2),3,4) or TAN(23); + * + * + * + * + * TODO: HANDLE these references: + * + * + * =SUM(table[[#This Row];['#Head3]:[Calced]]) + * + * + * table[['#Head3]:[Calced]] + * + * + * I assume this means a table of data, the Head3 table? and the calced column? + */ + private fun parseFinalLevel(form: XLSRecord, fmla: String, bIsComplete: Boolean): Stack<*> { + var returnStack = Stack() + val parseThings = CompatibleVector() + + // break it up into components first + var elements = Vector() + elements = splitString(fmla, bIsComplete) + + val bk = form.workBook // nec. to determine if parsed element is a valid name handle name + + // convert each element into Ptg's + // each element at this point should be a named operand, or an unidentified operator + for (x in elements.indices) { + var `val` = elements.elementAt(x) as String + val name = convertString(`val`, bk) + + var pthing: Ptg? = null + try { + pthing = XLSRecordFactory.getPtgRecord(name) + if (pthing == null && name == "PtgName") + // TODO: MUST evaluate which type of PtgName is correct: understand usage! + if (form.opcode == XLSConstants.FORMULA || form.opcode == XLSConstants.ARRAY) + pthing = PtgName(0x43) // assume this token to be of type Value (i.e PtgNameV) instead of Reference (PtgNameR) + else + // DV needs ref-type name + pthing = PtgName(0x23) // PtgNameR + } catch (e: InvalidRecordException) { + Logger.logInfo("parsing formula string. Invalid Ptg: $name error: $e") + } + + // if it is an operator we don't need to do anything with it! + if (pthing != null) { + pthing.parentRec = form + + if (!pthing.isOperator) { + if (pthing.isReference) { + // createPtgRefFromString will handle any type of string reference + // will return a PtgRefErr if cannot parse location + pthing = PtgRef.createPtgRefFromString(`val`, form) + } else if (pthing is PtgStr) { + val pstr = pthing as PtgStr? + `val` = StringTool.strip(`val`, '\"') + pstr!!.`val` = `val` + } else if (pthing is PtgNumber) { + val pnum = pthing as PtgNumber? + if (`val`.indexOf("%") == -1) + pnum!!.`val` = Double(`val`) + else + pnum!!.setVal(`val`) + } else if (pthing is PtgInt) { + val pint = pthing as PtgInt? + pint!!.`val` = Integer.valueOf(`val`).toInt() + } else if (pthing is PtgBool) { + val pbool = pthing as PtgBool? + pbool!!.setVal(java.lang.Boolean.valueOf(`val`).booleanValue()) + } else if (pthing is PtgArray) { + val parr = pthing as PtgArray? + parr!!.setVal(`val`) + } else if (pthing is PtgName) { // SHOULD really return PtgErr("#NAME!") as it's a missing Name instead of adding a new name + val pname = pthing as PtgName? + pname!!.setName(`val`) + } else if (pthing is PtgNameX) { + val pnameX = pthing as PtgNameX? + pnameX!!.setName(`val`) + } else if (pthing is PtgMissArg) { + pthing.init(byteArrayOf(22)) + } else if (pthing is PtgErr) { + pthing = PtgErr(PtgErr.convertStringToLookupByte(`val`)) + } + } + parseThings.add(pthing) + } else { + val pname = PtgMissArg() + } + + } + //reorder in polish notation and add to stack. + // 20081128 KSC: Do later as reordering will depend upon position of this segment in formula returnStack = reorderStack(parseThings); see getPtgsFromFormulaString + returnStack = convertToStack(parseThings) + return returnStack + } + + /* + private static Stack reorderStack(Stack sourceStack, boolean bIsComplete) { + Stack returnStack = new Stack(); + Stack pOperators= new Stack(); + for (int x = 0;x 0) { + returnStack.push(pOperators.pop()); + } + if (((Ptg)returnStack.peek()).getIsOperator()) { + int precedence= rankPrecedence(pthing); + Ptg p= (Ptg) returnStack.pop(); + int prevprecedence= rankPrecedence(p); + if (precedence>prevprecedence) { + pOperators.push(p); // switch less precedence operator with greater + } + else + returnStack.push(p); // back to normal + } + } else if (!pOperators.isEmpty()) { // added for instances such as (2)^-(2); + //code below prevents the switching of the operators ^- + Ptg p= (Ptg) pOperators.pop(); + pOperators.push(pthing); // save operators + pthing= p; + } + pOperators.push(pthing); // save operators + }else{ // it's not an operand; put on stack and pop all operators thus far + returnStack.push(pthing); + while (pOperators.size() > 0) { + returnStack.push(pOperators.pop()); + } + } + } + while (pOperators.size() > 0) { + returnStack.push(pOperators.pop()); + } + return returnStack; + } + */ + + + /** + * convert list of parseThings without reordering + * + * @param parseThings + * @return + */ + private fun convertToStack(parseThings: CompatibleVector): Stack<*> { + val returnStack = Stack() + val pOperators = Stack() + for (x in parseThings.indices) { + val pthing = parseThings.elementAt(x) as Ptg + returnStack.push(pthing) + } + return returnStack + } + + /* + * Parses an internal string for a function, splitting out elements. + * for instance,(1<2), 3, tan(5); should return + * [(1<2)][3][tan(5)]. Currently just working off of commas, but this may change... + * + * One of the keys here is to not split on a comma from an internal function, for instance, + * "IF((1<2),MOD(45,6),0) should not split between 45 & 6! Note the badLocs vector that handles this. + */ + private fun splitFunctionOperands(formStr: String): CompatibleVector { + val locs = CompatibleVector() + // if there are no commas then we don't have to do all of this... + if (formStr == "") return locs // KSC: Handle no parameters by returning a null vector + + // first handle quoted strings 20081111 KSC + var loop = true + var pos = 0 + val badLocs = CompatibleVector() + while (loop) { + var c = '"' + var start = formStr.indexOf(c.toInt(), pos) + if (start == -1) { // process single quotes as well + c = '\'' + start = formStr.indexOf(c.toInt(), pos) + } + if (start != -1) { + var end = formStr.indexOf(c.toInt(), start + 1) + end += 1 //include trailing quote + // check for being a part of a reference ... + if (end < formStr.length && formStr[end] == '!') {// then it's part of a reference + end++ + while (end < formStr.length && loop) { + c = formStr[end] + if (!(Character.isLetterOrDigit(c) || c == ':' || c == '$') || c == '-' || c == '+') + loop = false + else + end++ + } + } + for (y in start until end) { + //make sure it is not a segment of a previous operand, like <> and >; + badLocs.add(Integer.valueOf(y)) + } + if (end == 0) { // means it didn't find an end quote + end = formStr.length - 1 + loop = false + } else { + pos = end + loop = true + } + } else { + loop = false + } + } + + + if (formStr.indexOf(",") == -1) { + locs.add(formStr) + } else { + // Handle each parameter (delimited by ,) + // fill the badLocs vector with string locations we should disregard for comma proccesing + run { + var i = 0 + while (i < formStr.length) { + val openparen = formStr.indexOf("(", i) + if (openparen != -1) { + if (!badLocs.contains(Integer.valueOf(openparen))) { + var closeparen = getMatchOperator(formStr, openparen, '(', ')') + if (closeparen == -1) closeparen = formStr.length + i = openparen + while (i < closeparen) { + val `in` = Integer.valueOf(i) + badLocs.add(`in`) + i++ + } + } else { // open paren nested in quoted string + i = openparen + 1 + } + } else + // 20081112 KSC + break + i++ + } + } + // lets do the same for the array items + run { + var i = 0 + while (i < formStr.length) { + val openparen = formStr.indexOf("{", i) + if (openparen != -1) { + if (!badLocs.contains(Integer.valueOf(openparen))) { + var closeparen = getMatchOperator(formStr, openparen, '{', '}') + if (closeparen == -1) closeparen = formStr.length + i = openparen + while (i < closeparen) { + val `in` = Integer.valueOf(i) + badLocs.add(`in`) + i++ + } + } else { // open paren nested in quoted string + i = openparen + 1 + } + } else + // 20081112 KSC + break + i++ + } + } + // now check bad locations: + var placeholder = 0 + var holder = 0 + while (holder != -1) { + val i = formStr.indexOf(",", holder) + if (i != -1) { + val ing = Integer.valueOf(i) + if (!badLocs.contains(ing)) { + val s = formStr.substring(placeholder, i) + locs.add(s) + placeholder = i + 1 + } + holder = i + 1 + } else { + val s = formStr.substring(placeholder) + locs.add(s) + return locs + } + } + } + return locs + } + + /** + * parse a given string into known Ptg operators + * + * @param s + * @return + */ + private fun parsePtgOperators(s: String, bUnary: Boolean): CompatibleVector { + var s = s + var bUnary = bUnary + val ret = CompatibleVector() + s = StringTool.allTrim(s) + + for (i in XLSRecordFactory.ptgOps.indices) { + var ptgOpStr = XLSRecordFactory.ptgOps[i][0] + if (s.startsWith("\"") || s.startsWith("'")) { + var end = s.substring(1).indexOf(s[0].toInt()) + end += 1 //include trailing quote + // TEST IF The quoted item is a sheet name + if (end < s.length && s[end] == '!') {// then it's part of a reference + end++ + var loop = true + while (end < s.length && loop) { // if the quoted string is a sheet ref, get rest of reference + val c = s[end] + if (c == '#' && s.endsWith("#REF!")) { + end += 5 + loop = false + } else if (!(Character.isLetterOrDigit(c) || c == ':' || c == '$') || c == '-' || c == '+') { + loop = false + } else + end++ + } + } + ret.add(s.substring(0, end + 1)) + s = s.substring(end + 1) + bUnary = false + if (s != "") + ret.addAll(parsePtgOperators(s, bUnary)) + break + } + + var x = s.indexOf(ptgOpStr) + if (x > -1) { // found instance of an operator + // if encounter a parenthesis, must determine if it is an expression limit OR + // if it is part of a complex range, in which case the expression must be kept together + if (ptgOpStr == "(") { + val end = getMatchOperator(s, x, '(', ')') + ret.add(s) // add entire + break + /* String ss= s.substring(x, end+1); + ret.add(ss.substring(x)); // add entire + if (FormulaParser.isComplexRange(ss)) { +// ret.add(ss.substring(x+1)); // skip beginning paren as screws up later parsing + ret.add(ss.substring(x)); + s= s.substring(end+1); + bUnary= false; + if (!s.isEmpty()) + ret.addAll(parsePtgOperators(s, bUnary)); + break; + } + } else if (ptgOpStr.equals(")")) { + try { + String ss= s.substring(x+1); + char nextChar= ss.charAt(0); + if (nextChar==' ') { // see if there is another operand after the space + ss= ss.trim(); + if (ss.length()>0 && ss.matches("[^(a-zA-Z].*")) { + nextChar= ss.charAt(0); + } + } + // complex ranges can contain parentheses in combo with these operators: :, ( + if (nextChar==' ' || nextChar==',' || nextChar==':' || nextChar==')') + continue; // keep complex range expression together + } catch (Exception e) { ; } +*/ + } + if (ptgOpStr == ")") + // parens are there to keep expression together + continue + if (x > 0) {// process prefix, if any - unary since it's the first operand + // exception here-- error range in the form of "Sheet!#REF! (eg) needs to be kept whole + if (!(XLSRecordFactory.ptgLookup[i][1] == "PtgErr" && s[x - 1] == '!')) { + ret.addAll(parsePtgOperators(s.substring(0, x), bUnary)) + bUnary = false + } else { // keep entire error reference together + ptgOpStr = s + } + } + x = x + ptgOpStr.length + if (bUnary) { + // unary ops +, - ... have a diff't Ptg than regular vers of the operator + if (ptgOpStr == "-" && x == 1 && ptgOpStr.length > 1) break //negative number, NOT a unary - + for (j in XLSRecordFactory.ptgPrefixOperators.indices) { + if (ptgOpStr.startsWith(XLSRecordFactory.ptgPrefixOperators[j][0].toString())) { + ptgOpStr = XLSRecordFactory.ptgPrefixOperators[j][1].toString() + } + } + } + ret.add(ptgOpStr) + if (x < s.length) + // process suffix, if any + ret.addAll(parsePtgOperators(s.substring(x), true)) + break + } + } + if (ret.isEmpty()) + ret.add(s) + return ret + } + + /* + * Parses a string and returns an array based on contents + * Assumed to be 1 "final-level" operand i.e a range, complex range, a op b[...] + */ + private fun splitString(formStr: String, bIsComplete: Boolean): CompatibleVector { + var formStr = formStr + var bIsComplete = bIsComplete + // Use a vector, and the collections methods to sort in natural order + val locs = CompatibleVector() + val retVect = CompatibleVector() + + // check for escaped string literals & add positions to vector if needed + formStr = StringTool.allTrim(formStr) + if (formStr == "") { + retVect.add(formStr) + return retVect + } + if (true) { + retVect.addAll(parsePtgOperators(formStr, bIsComplete)) // cleanString if not an array formula???? s= cleanString(s); + bIsComplete = false + return retVect + } + + // 20081207 KSC: redo completely to handle complex formula strings e.g. strings containing quoted commas, parens ... + // first, pre-process to parse quoted strings, parentheses and array formulas + var isArray = false + var loop = true + var s = "" + var inRange = false + var prevc: Char = 0.toChar() + var i = 0 + while (i < formStr.length) { + var c = formStr[i] + if (c == '"' || c == '\'') { + /* if (!s.equals("")) { + locs.add(s); + s= ""; + } +*/ + var end = formStr.indexOf(c.toInt(), i + 1) + end += 1 //include trailing quote + // TEST IF The quoted item is a sheet name + if (end < formStr.length && formStr[end] == '!') {// then it's part of a reference + end++ + loop = true + while (end < formStr.length && loop) { // if the quoted string is a sheet ref, get rest of reference + c = formStr[end] + if (c == '#' && formStr.endsWith("#REF!")) { + end += 5 + loop = false + } else if (!(Character.isLetterOrDigit(c) || c == ':' || c == '$') || c == '-' || c == '+') { + loop = false + } else + end++ + } + } + locs.add(s + formStr.substring(i, end)) + s += formStr.substring(i, end) + i = end - 1 + } else if (c == '(') { // may be a complex range if s=="" + if (s != "" && !inRange) { + // char prevc= s.charAt(s.length()-1); + if (!(prevc == ' ' || prevc == ':' || prevc == ',' || prevc == '(')) { + locs.add(s) + s = "" + } else { // DO NOT split apart complex ranges - they parse to PtgMemFuncs + //Logger.logInfo("FormulaParser.splitString: PtgMemFunc" + formStr); + s += c + inRange = true + } + } + } else if (c == ':') { + if (prevc == ')' && locs.size > 0) + // complex range in style of: F(x):Y(x) + s = locs[locs.size - 1] as String + '('.toString() + s + inRange = true + s += c + } else if (c == '{') { + if (s != "") { + locs.add(s) + s = "" + } + var end = formStr.indexOf("}", i + 1) + end += 1 //include trailing } + locs.add(formStr.substring(i, end)) + i = end - 1 + } else + s += c + if (c != ' ') + prevc = c + i++ + } + if (s != "") { + locs.add(s) + s = "" + } + + // loop through the possible operator ptg's and get locations & length of them + for (j in locs.indices) { + s = locs[j] as String + if (s.startsWith("\"") || s.startsWith("'")) + retVect.add(s) // quoted strings + else { + if (s.startsWith("{")) + // it's an array formula + isArray = true // Do what?? else, cleanString?? + retVect.addAll(parsePtgOperators(s, bIsComplete)) // cleanString if not an array formula???? s= cleanString(s); + } + bIsComplete = false // already parsed part of the formula string so cannot be unary :) + } + return retVect + } + + /** + * helper method that turns operator & operand strings into the Ptg\ equivalent + * if there is no equivalant it leaves the string alone. + */ + private fun convertString(ptg: String, bk: WorkBook?): String { + // first check for operators + for (i in XLSRecordFactory.ptgLookup.indices) { + if (ptg.equals(XLSRecordFactory.ptgLookup[i][0], ignoreCase = true)) { + return ptg + } + } + + // KSC: Added for missing arguments ("") + if (StringTool.allTrim(ptg) == "") + //return "PtgMissArg"; + //if (ptg.equals("")) + return "PtgAtr" // a space + + // Now we need to figure out what type of operand it is + // see if it is a string, should be encased by "" + if (ptg.substring(0, 1).equals("\"", ignoreCase = true)) { + return "PtgStr" + } + // is it an array? + if (ptg.substring(0, 1).equals("{", ignoreCase = true)) { + return "PtgArray" + } + // see if it is an integer + if (ptg.indexOf(".") == -1) { + try { + val i = Integer.valueOf(ptg) + return if (i.toInt() >= 0 && i.toInt() <= 65535) + // PtgInts are UNSIGNED + <=65535 + "PtgInt" + else + "PtgNumber" + } catch (e: NumberFormatException) { + } + + } + if (ptg.indexOf("%") == ptg.length - 1) { // see if it's a percentage + try { + val d = Double(ptg.substring(0, ptg.indexOf("%"))) + return "PtgNumber" + } catch (e: NumberFormatException) { + } + + } + // see if it is a Number + try { + val d = Double(ptg) + return "PtgNumber" + } catch (e: NumberFormatException) { + } + + + // at this point it is probably some sort of ptgref + if (ptg.indexOf(":") != -1 || ptg.indexOf(',') != -1 || ptg.indexOf("!") != -1) { + // ptgarea or ptgarea3d or ptgmemfunc + return "PtgArea" // in ParseFinalLevel, PtgRef.createPtgRefFromString will handle all types of string refs + } + + + // maybe it is a garbage string, or a reference to a name (unsupported right now....) + // check if the last character is a number. If not, it sure isn't a reference, no? + // NO! Can have named ranges with numbers at the end -- better to try to parse it + try { + if (bk!!.getName(ptg) == null) {// it's not a named range + ExcelTools.getRowColFromString(ptg) // if passes it's a PtgRef + return "PtgRef" + } else + return "PtgName" + } catch (e: IllegalArgumentException) { + return "PtgName" + } + + } + + /* + * helper method that cleans out unneccesary parts of the formula string. + */ + private fun cleanString(dirtystring: String): String { + var cleanstring = StringTool.allTrim(dirtystring) + cleanstring = StringTool.strip(cleanstring, "(") + cleanstring = StringTool.strip(cleanstring, ",") + return cleanstring + } + + internal fun getPtgsFromFormulaString(fmla: String): Stack<*>? { + return null + + } + + /** + * parse a formula in string form and create a formula record from it + * caluclate the new formula based on boolean setting calculate + * + * @param form Formula rec + * @param fmla String formula either =EXPRESSION or {=EXPRESSION} for array formulas + * @param calculate boolean truth of "calculate formula after setting" + * @return Formula rec + */ + fun setFormula(form: Formula, fmla: String, rc: IntArray): Formula { + var rc = rc + if (fmla[0] != '{') { + try { + val newptgs = FormulaParser.getPtgsFromFormulaString(form, fmla) + FormulaParser.adjustParameterIds(newptgs) // 20100614 KSC: adjust function parameter id's, if necessary, for Value, Array or Reference type + form.expression = newptgs + } catch (e: FunctionNotSupportedException) { // 200902 KSC: still add record if function is not found (using N/A in place of said function) + Logger.logErr("Adding new Formula at " + form.sheet + "!" + ExcelTools.formatLocation(rc) + " failed: " + e.toString() + ".") + val newptgs = Stack() + newptgs.push(PtgErr(PtgErr.ERROR_NA)) + form.expression = newptgs + } + + } else { // Handle Array Formulas + val pe = PtgExp() + pe.parentRec = form + // rowcol reference is from PARENT PtgExp not (necessarily) this formula's cell address + // [BugTracker 2683 + OOXML Array Formulas] + val o = form.sheet!!.getArrayFormulaParent(rc) + if (o != null) + // there is a parent array formula; use it's rowcol + rc = o as IntArray + else { // no parent yet- add + val addr = ExcelTools.formatLocation(rc) + form.sheet!!.addParentArrayRef(addr, addr) + } + pe.init(rc[0], rc[1]) + val e = Stack() + e.push(pe) + FormulaParser.adjustParameterIds(e) // adjust function parameter id's, if necessary, for Value, Array or Reference type + form.expression = e // add PtgExp to Formula Stack + val a = Array() // Create new Array Record + a.setSheet(form.sheet) + a.workBook = form.workBook + a.init(fmla, rc[0], rc[1]) // init Array record from Formula String + form.addInternalRecord(a) // link array record to parent formula + } + + /* is this calc necessary? + Object val = null; + try{ + if (form.getWorkBook().getCalcMode() == WorkBook.CALCULATE_ALWAYS) + val = form.calculateFormula(); + }catch (Exception e){ + Logger.logWarn("Unsupported Function: " + e + ". OpenXLS calculation will be unavailable: " + fmla); //20081118 KSC: display a little more info ... + } + if(DEBUGLEVEL > 0)Logger.logInfo("FormulaParser.setFormula() string:" +fmla + " value: " + val); + */ + + return form + } + + + fun getFormulaString(form: Formula): String { + val expression = form.expression + return FormulaParser.getExpressionString(expression!!) + } + + fun getExpressionString(expression: Stack<*>): String { + var retval = StringBuffer() + val sz = expression.size + var stck = arrayOfNulls(sz) + stck = expression.toTypedArray() as Array + val newstck = Stack() + for (t in 0 until sz) { // flip the stack + newstck.add(0, stck[t]) + } + val vals = Stack() + while (!newstck.isEmpty()) { + handlePtg(newstck, vals) + } + var s = "" + while (!vals.isEmpty()) { + val topP = vals.pop() as Ptg + s = topP.textString + s + } + retval = StringBuffer("=$s") + return retval.toString() + } + + /** + * adjustParameterIds pre-processes the function expression stack, + * analyzing function parameters to ensure that function operands + * contain the correct PtgID (which indicates Value, Reference or Array) + *

                      + * Reference-type ptg's contain an ID which indicates the type + * required by the calling function: Value, Reference or Array. + *

                      + * When formula strings are parsed into formula expressions, + * ptg reference-type parameters are assigned a default id, + * but this id may not be correct for all functions. + */ + fun adjustParameterIds(expression: Stack<*>) { + val retval = StringBuffer() + val sz = expression.size + var stck = arrayOfNulls(sz) + stck = expression.toTypedArray() as Array + val newstck = Stack() + for (t in 0 until sz) { // flip the stack + newstck.add(0, stck[t]) + } + val params = Stack() + // we only care about PtgFuncVar and PtgFunc's but need to process the expression + // stack thoroughly to get the correct parameters + // Process function stack, gathering parameters. When we have all the parameters + // for a PtgFunc or a PtgFuncVar, adjust any PtgRef types, if necessary + while (!newstck.isEmpty()) { + val p = newstck.pop() as Ptg + val x = 0 + var t = 0// cargs = p.getNumParams(); + if (p.isControl) { + // do the parens thing here... + if (p.opcode.toInt() == 0x15) { // its a parens... and there is a val + if (t > 0) { + // 20060128 - KSC: handle parens + val vx = arrayOfNulls(1) // parens are unary ops so only 1 var allowed + vx[0] = params.pop() as Ptg + p.setVars(vx) + params.push(p) // put paren (with var) back on stack + } else { // this paren wraps other parens... + params.push(p) + } + } + } else if (p.isOperator || p.isFunction) { + if (p.isBinaryOperator) t = 2 + if (p.isUnaryOperator) t = 1 + if (p.isStandAloneOperator) t = 0 + if (p.opcode.toInt() == 0x22 || p.opcode.toInt() == 0x42 || p.opcode.toInt() == 0x62) { + t = p.numParams + }// it's a ptgfuncvar + if (p.opcode.toInt() == 0x21 || p.opcode.toInt() == 0x41 || p.opcode.toInt() == 0x61) { + t = p.numParams + }// it's a ptgFunc + + if (t > params.size) { + t = params.size + } + val vx = arrayOfNulls(t) + while (t > 0) + vx[--t] = params.pop() as Ptg// get'em + p.setVars(vx) // set'em + // here is where we adjust the ptg's of the func or funcvar parameters + if (p.opcode.toInt() == 0x22 || p.opcode.toInt() == 0x42 || p.opcode.toInt() == 0x62) + /* it's a ptgfuncvar*/ + (p as PtgFuncVar).adjustParameterIds() + else if (p.opcode.toInt() == 0x21 || p.opcode.toInt() == 0x41 || p.opcode.toInt() == 0x61) + /* it's a ptgFunc */ + (p as PtgFunc).adjustParameterIds() + params.push(p)// push it back on the stack + } else if (p.isOperand) { + params.push(p) + } + } + } + + /** + * set up the Formula chain + * + * + * A5 + * 10 + * + * + * 3 + * EXP( + * + + * SUM( + * =SUM(A5*10+EXP(3)) + * =SUM(EXP(A5*103)) + * + * + * A3 add to vals + * E5 add to vals + * + pop last2 vals add to vals + * ( check last -- if oper add oper, else add last, add to vals + * + * + * A1:A2 at this point we should see: (A3+E5) push to vals + * SUM( check last -- if oper add oper, else add last, add to vals + * + pop last2 add to vals + * SUM( check last -- if oper add oper, else add last, add to vals + * =SUM((A3+E5)+SUM(A1:A2)) + * =SUM((A3+E5)+SUM(A1:A2)) + * + * + * A1 add to vals + * A2 add to vals + * + pop last2 vals add to vals? + * ( check last -- if oper add oper, else add last, add to vals + * A3 add to vals + * A4 add to vals + * + check last -- if paren pop last2 vals add to vals? + * ( check last -- if oper add oper, else add last, add to vals + * / pop last2 vals add to vals + * + * + * =(A1+A2)/(A3+A4) + * =(A1+A2)/(A3+A4) + * + * + * + * + * PtgFunc taking 3 vals again can only have 1 + * We know IF has 3 vals + * Do we need logic which knows how many vars a Ptg takes? (we sure do... -nick) Might help. + * + * + * ---> WRITE CODE TO SWITCH ON NUMBER OF PARAMS. Should Fix. + * + * + * + * + * =IF(SUM(CONCATENATE(SUM((EXP(C2,D4,4))*2SUM(A5,C7,A2)A1:A5))=1),SUM(3),SUM(22)) + * =IF(CONCATENATE(C2,(D4+EXP(4))*2,SUM(A5,C7,A2),SUM(A1:A5))=1,3,22) + */ + internal fun handlePtg(newstck: Stack<*>, vals: Stack<*>) { + val p = newstck.pop() as Ptg + val x = 0 + var t = 0// cargs = p.getNumParams(); + if (p.isOperator || p.isControl || p.isFunction) { + t = vals.size //this is faulty logic. We don't care what is there, the operator should tell us. + // do the parens thing here... + if (p.isControl /* !vals.isEmpty()*/) { + if (p.opcode.toInt() == 0x15) { // its a parens... and there is a val + if (t > 0) { + // 20060128 - KSC: handle parens + val vx = arrayOfNulls(1) // parens are unary ops so only 1 var allowed + vx[0] = vals.pop() as Ptg + p.setVars(vx) + vals.push(p) // put paren (with var) back on stack + } else { // this paren wraps other parens... + vals.push(p) + } + return + } + } + if (t > 0) { + // make sure we have the correct amount popped back in.. + if (p.isBinaryOperator) t = 2 + if (p.isUnaryOperator) t = 1 + if (p.isStandAloneOperator) t = 0 + if (p.opcode.toInt() == 0x22 || p.opcode.toInt() == 0x42 || p.opcode.toInt() == 0x62) { + t = p.numParams + }// it's a ptgfuncvar! + if (p.opcode.toInt() == 0x21 || p.opcode.toInt() == 0x41 || p.opcode.toInt() == 0x61) { + t = p.numParams + }// it's a ptgFunc + + if (t > vals.size) { + + // is this a real error? throw an exception? + if (DEBUGLEVEL > 0) + Logger.logWarn("FormulaParser.handlePtg: number of parameters " + t + " is greater than available " + vals.size) + t = vals.size + } + val vx = arrayOfNulls(t) + while (t > 0) + vx[--t] = vals.pop() as Ptg// get'em + p.setVars(vx) // set'em + } + vals.push(p)// push it back on the stack + } else if (p.isOperand) { + vals.push(p) + } else if (p is PtgAtr) { + // this is probably just a space at this point, don't output error message + } else { + if (DEBUGLEVEL > -1) + Logger.logInfo("FormulaParser Error - Ptg Type: " + p.opcode + " " + p.string) + } + } + + /** + * create a new formula record at row column rc using formula string formStr + * + * @param formStr String + * @param st + * @param rc int[] + * @throws Exception + * @return new Formula record + */ + @Throws(Exception::class) + fun getFormulaFromString(formStr: String, st: Boundsheet?, rc: IntArray): Formula { + var f = Formula() + if (st != null) { + f.setSheet(st) + f.workBook = st.workBook + } + f.data = ByteArray(6) // necessary for setRowCol + f.setRowCol(rc) // do before calculateFormula as array formulas use rowcol 20090817 KSC: [BugTracker 2683 + OOXML Array Formulas] + f = FormulaParser.setFormula(f, formStr, rc) + + return f + } + + /** + * create a new formula record at row column rc using formula string formStr + * + * @param formStr String + * @param rc int[] + * @throws Exception + * @return new Formula record + */ + @Throws(/* 20070212 KSC: FunctionNotSupported*/Exception::class) + fun setFormulaString(formStr: String, rc: IntArray): Formula { + var f = Formula() + f = FormulaParser.setFormula(f, formStr, rc) + return f + } + + + /** + * returns true of string s is in the form of a basic reference e.g. A1 + * + * @param s + * @return + */ + fun isRef(s: String?): Boolean { + if (s == null) return false + val simpleOne = "(([ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})" + return s.matches(simpleOne.toRegex()) + } + + /** + * returns true if the stirng in question is in the form of a range + * + * @param s + * @return + */ + fun isRange(s: String?): Boolean { + if (s == null) return false + val one = "(([(]*[ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+[)]*){1})" + val aRange = "$one(:$one)?" + val rangeop = "([ ]*[: ,][ ]*)" + val rangeMatchString = "$aRange$rangeop$aRange($rangeop$aRange)*" + val simpleOne = "(([ ]*[']?([a-zA-Z0-9 ]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})" + val simpleRangeMatchString = "($simpleOne[ ]*[:][ ]*$simpleOne)" + return s.matches(rangeMatchString.toRegex()) + } + + /** + * returns true if the string represents a complex range (i.e. one containing multiple range values separated by one or more of: , : or space + * + * @param s + * @return + */ + fun isComplexRange(s: String): Boolean { + val one = "(([(]*[ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+[)]*){1})" + val aRange = "$one(:$one)?" + val rangeop = "([ ]*[: ,][ ]*)" + val rangeMatchString = "$aRange$rangeop$aRange($rangeop$aRange)*" + val simpleOne = "(([ ]*[']?([a-zA-Z0-9 _]*[']*[!])?[$]*[a-zA-Z]{1,2}[$]*[0-9]+){1})" + val simpleRangeMatchString = "($simpleOne[ ]*[:][ ]*$simpleOne)" + return isRange(s) && !s.matches(simpleRangeMatchString.toRegex()) + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.java b/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.java deleted file mode 100644 index eb93771..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.java +++ /dev/null @@ -1,1895 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -/** - * Function Constants for all Formula Types (PtgFunc, PtgFuncVar - regular and PtgFuncVar - AddIns) - * Modifications: - * all xlfXXX constants were originally in FunctionHandler - * getFunctionString was orignally in PtgFuncVar - * FUNCTION_STRINGS were originally in FunctionHandler - * getNumVars was originally in PtgFunc - * - * @see - */ -public class FunctionConstants { - /* HOW TO USE: - * - * 1- If implementing a formula, MAKE SURE to put it in recArr AND getFunctionString list. If it is a ptgFunc, also input - * the number of args in getNumArgs. If it's an Add-in, add xlfXX constant to the end of the Excel function numbers list. - * - * All function ID's/number MUST exist in the xlfXXX constants list - * - * - */ - public static int FTYPE_PTGFUNC = 0; - public static int FTYPE_PTGFUNCVAR = 1; - public static int FTYPE_PTGFUNCVAR_ADDIN = 2; - - /**************************************** - * * - * Excel function numbers * - * * - ****************************************/ - - public static final int XLF_COUNT = 0; - public static final int XLF_IS = 1; - public static final int XLF_IS_NA = 2; - public static final int XLF_IS_ERROR = 3; - public static final int XLF_SUM = 4; - public static final int XLF_AVERAGE = 5; - public static final int XLF_MIN = 6; - public static final int XLF_MAX = 7; - public static final int XLF_ROW = 8; - public static final int xlfColumn = 9; - public static final int xlfNa = 10; - public static final int xlfNpv = 11; - public static final int xlfStdev = 12; - public static final int xlfDollar = 13; - public static final int xlfFixed = 14; - public static final int xlfSin = 15; - public static final int xlfCos = 16; - public static final int xlfTan = 17; - public static final int xlfAtan = 18; - public static final int xlfPi = 19; - public static final int xlfSqrt = 20; - public static final int xlfExp = 21; - public static final int xlfLn = 22; - public static final int xlfLog10 = 23; - public static final int xlfAbs = 24; - public static final int xlfInt = 25; - public static final int xlfSign = 26; - public static final int xlfRound = 27; - public static final int xlfLookup = 28; - public static final int xlfIndex = 29; - public static final int xlfRept = 30; - public static final int xlfMid = 31; - public static final int xlfLen = 32; - public static final int xlfValue = 33; - public static final int xlfTrue = 34; - public static final int xlfFalse = 35; - public static final int xlfAnd = 36; - public static final int xlfOr = 37; - public static final int xlfNot = 38; - public static final int xlfMod = 39; - public static final int xlfDcount = 40; - public static final int xlfDsum = 41; - public static final int xlfDaverage = 42; - public static final int xlfDmin = 43; - public static final int xlfDmax = 44; - public static final int xlfDstdev = 45; - public static final int xlfVar = 46; - public static final int xlfDvar = 47; - public static final int xlfText = 48; - public static final int xlfLinest = 49; - public static final int xlfTrend = 50; - public static final int xlfLogest = 51; - public static final int xlfGrowth = 52; - public static final int xlfGoto = 53; - public static final int xlfHalt = 54; - public static final int xlfPv = 56; - public static final int xlfFv = 57; - public static final int xlfNper = 58; - public static final int xlfPmt = 59; - public static final int xlfRate = 60; - public static final int xlfMirr = 61; - public static final int xlfIrr = 62; - public static final int xlfRand = 63; - public static final int xlfMatch = 64; - public static final int xlfDate = 65; - public static final int xlfTime = 66; - public static final int xlfDay = 67; - public static final int xlfMonth = 68; - public static final int xlfYear = 69; - public static final int xlfWeekday = 70; - public static final int xlfHour = 71; - public static final int xlfMinute = 72; - public static final int xlfSecond = 73; - public static final int xlfNow = 74; - public static final int xlfAreas = 75; - public static final int xlfRows = 76; - public static final int xlfColumns = 77; - public static final int xlfOffset = 78; - public static final int xlfAbsref = 79; - public static final int xlfRelref = 80; - public static final int xlfArgument = 81; - public static final int xlfSearch = 82; - public static final int xlfTranspose = 83; - public static final int xlfError = 84; - public static final int xlfStep = 85; - public static final int xlfType = 86; - public static final int xlfEcho = 87; - public static final int xlfSetName = 88; - public static final int xlfCaller = 89; - public static final int xlfDeref = 90; - public static final int xlfWindows = 91; - public static final int xlfSeries = 92; - public static final int xlfDocuments = 93; - public static final int xlfActiveCell = 94; - public static final int xlfSelection = 95; - public static final int xlfResult = 96; - public static final int xlfAtan2 = 97; - public static final int xlfAsin = 98; - public static final int xlfAcos = 99; - public static final int xlfChoose = 100; - public static final int xlfHlookup = 101; - public static final int xlfVlookup = 102; - public static final int xlfLinks = 103; - public static final int xlfInput = 104; - public static final int xlfIsref = 105; - public static final int xlfGetFormula = 106; - public static final int xlfGetName = 107; - public static final int xlfSetValue = 108; - public static final int xlfLog = 109; - public static final int xlfExec = 110; - public static final int xlfChar = 111; - public static final int xlfLower = 112; - public static final int xlfUpper = 113; - public static final int xlfProper = 114; - public static final int xlfLeft = 115; - public static final int xlfRight = 116; - public static final int xlfExact = 117; - public static final int xlfTrim = 118; - public static final int xlfReplace = 119; - public static final int xlfSubstitute = 120; - public static final int xlfCode = 121; - public static final int xlfNames = 122; - public static final int xlfDirectory = 123; - public static final int xlfFind = 124; - public static final int xlfCell = 125; - public static final int xlfIserr = 126; - public static final int xlfIstext = 127; - public static final int xlfIsnumber = 128; - public static final int xlfIsblank = 129; - public static final int xlfT = 130; - public static final int xlfN = 131; - public static final int xlfFopen = 132; - public static final int xlfFclose = 133; - public static final int xlfFsize = 134; - public static final int xlfFreadln = 135; - public static final int xlfFread = 136; - public static final int xlfFwriteln = 137; - public static final int xlfFwrite = 138; - public static final int xlfFpos = 139; - public static final int xlfDatevalue = 140; - public static final int xlfTimevalue = 141; - public static final int xlfSln = 142; - public static final int xlfSyd = 143; - public static final int xlfDdb = 144; - public static final int xlfGetDef = 145; - public static final int xlfReftext = 146; - public static final int xlfTextref = 147; - public static final int XLF_INDIRECT = 148; - public static final int xlfRegister = 149; - public static final int xlfCall = 150; - public static final int xlfAddBar = 151; - public static final int xlfAddMenu = 152; - public static final int xlfAddCommand = 153; - public static final int xlfEnableCommand = 154; - public static final int xlfCheckCommand = 155; - public static final int xlfRenameCommand = 156; - public static final int xlfShowBar = 157; - public static final int xlfDeleteMenu = 158; - public static final int xlfDeleteCommand = 159; - public static final int xlfGetChartItem = 160; - public static final int xlfDialogBox = 161; - public static final int xlfClean = 162; - public static final int xlfMdeterm = 163; - public static final int xlfMinverse = 164; - public static final int xlfMmult = 165; - public static final int xlfFiles = 166; - public static final int xlfIpmt = 167; - public static final int xlfPpmt = 168; - public static final int xlfCounta = 169; - public static final int xlfCancelKey = 170; - public static final int xlfInitiate = 175; - public static final int xlfRequest = 176; - public static final int xlfPoke = 177; - public static final int xlfExecute = 178; - public static final int xlfTerminate = 179; - public static final int xlfRestart = 180; - public static final int xlfHelp = 181; - public static final int xlfGetBar = 182; - public static final int xlfProduct = 183; - public static final int xlfFact = 184; - public static final int xlfGetCell = 185; - public static final int xlfGetWorkspace = 186; - public static final int xlfGetWindow = 187; - public static final int xlfGetDocument = 188; - public static final int xlfDproduct = 189; - public static final int xlfIsnontext = 190; - public static final int xlfGetNote = 191; - public static final int xlfNote = 192; - public static final int xlfStdevp = 193; - public static final int xlfVarp = 194; - public static final int xlfDstdevp = 195; - public static final int xlfDvarp = 196; - public static final int xlfTrunc = 197; - public static final int xlfIslogical = 198; - public static final int xlfDcounta = 199; - public static final int xlfDeleteBar = 200; - public static final int xlfUnregister = 201; - public static final int xlfUsdollar = 204; - public static final int xlfFindb = 205; - public static final int xlfSearchb = 206; - public static final int xlfReplaceb = 207; - public static final int xlfLeftb = 208; - public static final int xlfRightb = 209; - public static final int xlfMidb = 210; - public static final int xlfLenb = 211; - public static final int xlfRoundup = 212; - public static final int xlfRounddown = 213; - public static final int xlfAsc = 214; - public static final int xlfDbcs = 215; - public static final int xlfRank = 216; - public static final int xlfAddress = 219; - public static final int xlfDays360 = 220; - public static final int xlfToday = 221; - public static final int xlfVdb = 222; - public static final int xlfMedian = 227; - public static final int xlfSumproduct = 228; - public static final int xlfSinh = 229; - public static final int xlfCosh = 230; - public static final int xlfTanh = 231; - public static final int xlfAsinh = 232; - public static final int xlfAcosh = 233; - public static final int xlfAtanh = 234; - public static final int xlfDget = 235; - public static final int xlfCreateObject = 236; - public static final int xlfVolatile = 237; - public static final int xlfLastError = 238; - public static final int xlfCustomUndo = 239; - public static final int xlfCustomRepeat = 240; - public static final int xlfFormulaConvert = 241; - public static final int xlfGetLinkInfo = 242; - public static final int xlfTextBox = 243; - public static final int xlfInfo = 244; - public static final int xlfGroup = 245; - public static final int xlfGetObject = 246; - public static final int xlfDb = 247; - public static final int xlfPause = 248; - public static final int xlfResume = 251; - public static final int xlfFrequency = 252; - public static final int xlfAddToolbar = 253; - public static final int xlfDeleteToolbar = 254; - public static final int xlfADDIN = 255; // KSC: Added; Excel function ID for add-ins - public static final int xlfResetToolbar = 256; - public static final int xlfEvaluate = 257; - public static final int xlfGetToolbar = 258; - public static final int xlfGetTool = 259; - public static final int xlfSpellingCheck = 260; - public static final int xlfErrorType = 261; - public static final int xlfAppTitle = 262; - public static final int xlfWindowTitle = 263; - public static final int xlfSaveToolbar = 264; - public static final int xlfEnableTool = 265; - public static final int xlfPressTool = 266; - public static final int xlfRegisterId = 267; - public static final int xlfGetWorkbook = 268; - public static final int xlfAvedev = 269; - public static final int xlfBetadist = 270; - public static final int xlfGammaln = 271; - public static final int xlfBetainv = 272; - public static final int xlfBinomdist = 273; - public static final int xlfChidist = 274; - public static final int xlfChiinv = 275; - public static final int xlfCombin = 276; - public static final int xlfConfidence = 277; - public static final int xlfCritbinom = 278; - public static final int xlfEven = 279; - public static final int xlfExpondist = 280; - public static final int xlfFdist = 281; - public static final int xlfFinv = 282; - public static final int xlfFisher = 283; - public static final int xlfFisherinv = 284; - public static final int xlfFloor = 285; - public static final int xlfGammadist = 286; - public static final int xlfGammainv = 287; - public static final int xlfCeiling = 288; - public static final int xlfHypgeomdist = 289; - public static final int xlfLognormdist = 290; - public static final int xlfLoginv = 291; - public static final int xlfNegbinomdist = 292; - public static final int xlfNormdist = 293; - public static final int xlfNormsdist = 294; - public static final int xlfNorminv = 295; - public static final int xlfNormsinv = 296; - public static final int xlfStandardize = 297; - public static final int xlfOdd = 298; - public static final int xlfPermut = 299; - public static final int xlfPoisson = 300; - public static final int xlfTdist = 301; - public static final int xlfWeibull = 302; - public static final int xlfSumxmy2 = 303; - public static final int xlfSumx2my2 = 304; - public static final int xlfSumx2py2 = 305; - public static final int xlfChitest = 306; - public static final int xlfCorrel = 307; - public static final int xlfCovar = 308; - public static final int xlfForecast = 309; - public static final int xlfFtest = 310; - public static final int xlfIntercept = 311; - public static final int xlfPearson = 312; - public static final int xlfRsq = 313; - public static final int xlfSteyx = 314; - public static final int xlfSlope = 315; - public static final int xlfTtest = 316; - public static final int xlfProb = 317; - public static final int xlfDevsq = 318; - public static final int xlfGeomean = 319; - public static final int xlfHarmean = 320; - public static final int xlfSumsq = 321; - public static final int xlfKurt = 322; - public static final int xlfSkew = 323; - public static final int xlfZtest = 324; - public static final int xlfLarge = 325; - public static final int xlfSmall = 326; - public static final int xlfQuartile = 327; - public static final int xlfPercentile = 328; - public static final int xlfPercentrank = 329; - public static final int xlfMode = 330; - public static final int xlfTrimmean = 331; - public static final int xlfTinv = 332; - public static final int xlfMovieCommand = 334; - public static final int xlfGetMovie = 335; - public static final int xlfConcatenate = 336; - public static final int xlfPower = 337; - public static final int xlfPivotAddData = 338; - public static final int xlfGetPivotTable = 339; - public static final int xlfGetPivotField = 340; - public static final int xlfGetPivotItem = 341; - public static final int xlfRadians = 342; - public static final int xlfDegrees = 343; - public static final int xlfSubtotal = 344; - public static final int XLF_SUM_IF = 345; - public static final int xlfCountif = 346; - public static final int xlfCountblank = 347; - public static final int xlfScenarioGet = 348; - public static final int xlfOptionsListsGet = 349; - public static final int xlfIspmt = 350; - public static final int xlfDatedif = 351; - public static final int xlfDatestring = 352; - public static final int xlfNumberstring = 353; - public static final int xlfRoman = 354; - public static final int xlfOpenDialog = 355; - public static final int xlfSaveDialog = 356; - public static final int xlfViewGet = 357; - public static final int xlfGetPivotData = 358; - public static final int xlfHyperlink = 359; - public static final int xlfPhonetic = 360; - public static final int xlfAverageA = 361; - public static final int xlfMaxA = 362; - public static final int xlfMinA = 363; - public static final int xlfStDevPA = 364; - public static final int xlfVarPA = 365; - public static final int xlfStDevA = 366; - public static final int xlfVarA = 367; - // KSC: ADD-IN formulas - use any index; name must be present in FunctionConstants.addIns - // Financial Formulas - public static final int xlfAccrintm = 368; - public static final int xlfAccrint = 369; - public static final int xlfCoupDayBS = 370; - public static final int xlfCoupDays = 371; - public static final int xlfCumIPmt = 372; - public static final int xlfCumPrinc = 373; - public static final int xlfCoupNCD = 374; - public static final int xlfCoupDaysNC = 375; - public static final int xlfCoupPCD = 376; - public static final int xlfCoupNUM = 377; - public static final int xlfDollarDE = 378; - public static final int xlfDollarFR = 379; - public static final int xlfEffect = 380; - public static final int xlfINTRATE = 381; - public static final int xlfXIRR = 382; - public static final int xlfXNPV = 383; - public static final int xlfYIELD = 384; - public static final int xlfPRICE = 385; - public static final int xlfPRICEDISC = 386; - public static final int xlfPRICEMAT = 387; - public static final int xlfDURATION = 388; - public static final int xlfMDURATION = 389; - public static final int xlfTBillEq = 390; - public static final int xlfTBillPrice = 391; - public static final int xlfTBillYield = 392; - public static final int xlfYieldDisc = 393; - public static final int xlfYieldMat = 394; - public static final int xlfFVSchedule = 395; - public static final int xlfAmorlinc = 396; - public static final int xlfAmordegrc = 397; - public static final int xlfOddFPrice = 398; - public static final int xlfOddLPrice = 399; - public static final int xlfOddFYield = 400; - public static final int xlfOddLYield = 401; - public static final int xlfNOMINAL = 402; - public static final int xlfDISC = 403; - public static final int xlfRECEIVED = 404; - // Engineering Formulas - public static final int xlfBIN2DEC = 405; - public static final int xlfBIN2HEX = 406; - public static final int xlfBIN2OCT = 407; - public static final int xlfDEC2BIN = 408; - public static final int xlfDEC2HEX = 409; - public static final int xlfDEC2OCT = 410; - public static final int xlfHEX2BIN = 411; - public static final int xlfHEX2DEC = 412; - public static final int xlfHEX2OCT = 413; - public static final int xlfOCT2BIN = 414; - public static final int xlfOCT2DEC = 415; - public static final int xlfOCT2HEX = 416; - public static final int xlfCOMPLEX = 417; - public static final int xlfGESTEP = 418; - public static final int xlfDELTA = 419; - public static final int xlfIMAGINARY = 420; - public static final int xlfIMABS = 421; - public static final int xlfIMDIV = 422; - public static final int xlfIMCONJUGATE = 423; - public static final int xlfIMCOS = 424; - public static final int xlfIMSIN = 425; - public static final int xlfIMREAL = 426; - public static final int xlfIMEXP = 427; - public static final int xlfIMSUB = 428; - public static final int xlfIMSUM = 429; - public static final int xlfIMPRODUCT = 430; - public static final int xlfIMLN = 431; - public static final int xlfIMLOG10 = 432; - public static final int xlfIMLOG2 = 433; - public static final int xlfIMPOWER = 434; - public static final int xlfIMSQRT = 435; - public static final int xlfIMARGUMENT = 436; - public static final int xlfCONVERT = 437; - public static final int xlfERF = 460; - public static final int xlfERFC = 461; - // Math Add-in Formulas - public static final int xlfDOUBLEFACT = 438; - public static final int xlfGCD = 439; - public static final int xlfLCM = 440; - public static final int xlfMROUND = 441; - public static final int xlfMULTINOMIAL = 442; - public static final int xlfQUOTIENT = 443; - public static final int xlfRANDBETWEEN = 444; - public static final int xlfSERIESSUM = 445; - public static final int xlfSQRTPI = 446; - public static final int xlfSUMIFS = 456; - // Information Add-in Formulas - public static final int xlfISEVEN = 447; - public static final int xlfISODD = 448; - // Date/Time Add-in Formulas - public static final int xlfNETWORKDAYS = 449; - public static final int xlfEDATE = 450; - public static final int xlfEOMONTH = 451; - public static final int xlfWEEKNUM = 452; - public static final int xlfWORKDAY = 453; - public static final int xlfYEARFRAC = 459; - // Statistical - public static final int xlfAVERAGEIF = 454; - public static final int xlfAVERAGEIFS = 457; - public static final int xlfCOUNTIFS = 458; - // Logical - public static final int xlfIFERROR = 455; - public static final int MAXXLF = 462; - - - /** - * Japanese Excel contains some different values and string output than US English Excel. - *

                      - * This recArr is checked if locale = japan... if null value is returned then the main list is checked - */ - public static String[][] jRecArr = { - {"YEN", String.valueOf(xlfDollar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"DOLLAR", String.valueOf(xlfUsdollar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"JIS", String.valueOf(xlfDbcs), String.valueOf(FTYPE_PTGFUNC)}, - }; - - /** - * Unimplemented records. This exists to allow writing of functions that are unsupported for calculation - */ - public static String[][] unimplRecArr = { - {"ASC", String.valueOf(xlfAsc), String.valueOf(FTYPE_PTGFUNC)}, - {"DBCS", String.valueOf(xlfDbcs), String.valueOf(FTYPE_PTGFUNC)}, - {"MDETERM", String.valueOf(xlfMdeterm), String.valueOf(FTYPE_PTGFUNC)}, - {"SEARCHB", String.valueOf(xlfSearchb), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TRANSPOSE", String.valueOf(xlfTranspose), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"BETAINV", String.valueOf(xlfBetainv), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"BETADIST", String.valueOf(xlfBetadist), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TIMEVALUE", String.valueOf(xlfTimevalue), String.valueOf(FTYPE_PTGFUNC)}, - {"MINVERSE", String.valueOf(xlfMinverse), String.valueOf(FTYPE_PTGFUNC)}, - {"MDETERM", String.valueOf(xlfMdeterm), String.valueOf(FTYPE_PTGFUNC)}, - {"GETPIVOTDATA", String.valueOf(xlfGetPivotData), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"HYPERLINK", String.valueOf(xlfHyperlink), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"PHONETIC", String.valueOf(xlfPhonetic), String.valueOf(FTYPE_PTGFUNC)}, - {"PERCENTILE", String.valueOf(xlfPercentile), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TRUNC", String.valueOf(xlfTrunc), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"PERCENTRANK", String.valueOf(xlfPercentrank), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"RIGHTB", String.valueOf(xlfRightb), String.valueOf(FTYPE_PTGFUNC)}, - {"REPLACEB", String.valueOf(xlfReplaceb), String.valueOf(FTYPE_PTGFUNC)}, - {"FINDB", String.valueOf(xlfFindb), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MIDB", String.valueOf(xlfMidb), String.valueOf(FTYPE_PTGFUNC)}, - {"ROWS", String.valueOf(xlfRows), String.valueOf(FTYPE_PTGFUNC)}, - {"COLUMNS", String.valueOf(xlfColumns), String.valueOf(FTYPE_PTGFUNC)}, - {"OFFSET", String.valueOf(xlfOffset), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ISTEXT", String.valueOf(xlfIstext), String.valueOf(FTYPE_PTGFUNC)}, - {"LOOKUP", String.valueOf(xlfLookup), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"EXPONDIST", String.valueOf(xlfExpondist), String.valueOf(FTYPE_PTGFUNC)}, - {"FDIST", String.valueOf(xlfFdist), String.valueOf(FTYPE_PTGFUNC)}, - {"FINV", String.valueOf(xlfFinv), String.valueOf(FTYPE_PTGFUNC)}, - {"FTEST", String.valueOf(xlfFtest), String.valueOf(FTYPE_PTGFUNC)}, - {"FISHER", String.valueOf(xlfFisher), String.valueOf(FTYPE_PTGFUNC)}, - {"FISHERINV", String.valueOf(xlfFisherinv), String.valueOf(FTYPE_PTGFUNC)}, - {"STANDARDIZE", String.valueOf(xlfStandardize), String.valueOf(FTYPE_PTGFUNC)}, - {"PERMUT", String.valueOf(xlfPermut), String.valueOf(FTYPE_PTGFUNC)}, - {"POISSON", String.valueOf(xlfPoisson), String.valueOf(FTYPE_PTGFUNC)}, - {"SUMXMY2", String.valueOf(xlfSumxmy2), String.valueOf(FTYPE_PTGFUNC)}, - {"SUMX2MY2", String.valueOf(xlfSumx2my2), String.valueOf(FTYPE_PTGFUNC)}, - {"SUMX2PY2", String.valueOf(xlfSumx2py2), String.valueOf(FTYPE_PTGFUNC)}, - {"ERFC", String.valueOf(xlfERFC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"CONFIDENCE", String.valueOf(xlfConfidence), String.valueOf(FTYPE_PTGFUNC)}, - {"CRITBINOM", String.valueOf(xlfCritbinom), String.valueOf(FTYPE_PTGFUNC)}, - {"DEVSQ", String.valueOf(xlfDevsq), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SERIESSUM", String.valueOf(xlfSERIESSUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"SUBTOTAL", String.valueOf(xlfSubtotal), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUMSQ", String.valueOf(xlfSumsq), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"CHIDIST", String.valueOf(xlfChidist), String.valueOf(FTYPE_PTGFUNC)}, - {"CHIINV", String.valueOf(xlfChiinv), String.valueOf(FTYPE_PTGFUNC)}, - {"CHITEST", String.valueOf(xlfChitest), String.valueOf(FTYPE_PTGFUNC)}, - {"GAMMADIST", String.valueOf(xlfGammadist), String.valueOf(FTYPE_PTGFUNC)}, - {"GAMMAINV", String.valueOf(xlfGammainv), String.valueOf(FTYPE_PTGFUNC)}, - {"GAMMALN", String.valueOf(xlfGammaln), String.valueOf(FTYPE_PTGFUNC)}, - {"GEOMEAN", String.valueOf(xlfGeomean), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"GROWTH", String.valueOf(xlfGrowth), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"HARMEAN", String.valueOf(xlfHarmean), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"HYPGEOMDIST", String.valueOf(xlfHypgeomdist), String.valueOf(FTYPE_PTGFUNC)}, - {"KURT", String.valueOf(xlfKurt), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"LOGEST", String.valueOf(xlfLogest), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"LOGINV", String.valueOf(xlfLoginv), String.valueOf(FTYPE_PTGFUNC)}, - {"LOGNORMDIST", String.valueOf(xlfLognormdist), String.valueOf(FTYPE_PTGFUNC)}, - {"NEGBINOMDIST", String.valueOf(xlfNegbinomdist), String.valueOf(FTYPE_PTGFUNC)}, - {"PROB", String.valueOf(xlfProb), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SKEW", String.valueOf(xlfSkew), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"STDEVPA", String.valueOf(xlfStDevPA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"STDEVP", String.valueOf(xlfStdevp), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"STDEVA", String.valueOf(xlfStDevA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TDIST", String.valueOf(xlfTdist), String.valueOf(FTYPE_PTGFUNC)}, - {"TINV", String.valueOf(xlfTinv), String.valueOf(FTYPE_PTGFUNC)}, - {"TTEST", String.valueOf(xlfTtest), String.valueOf(FTYPE_PTGFUNC)}, - {"VARA", String.valueOf(xlfVarA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"VARPA", String.valueOf(xlfVarPA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"WEIBULL", String.valueOf(xlfWeibull), String.valueOf(FTYPE_PTGFUNC)}, - {"ZTEST", String.valueOf(xlfZtest), String.valueOf(FTYPE_PTGFUNCVAR)}, - }; - - // Contains function name, id and type of ALL Formulas (PtgFuncs, PtgFuncVars and Add-in PtgFuncVars) - - // fetch the pattern match from: http://office.microsoft.com/client/helpcategory.aspx?CategoryID=CH100645029990&lcid=1033&NS=EXCEL&Version=12&CTT=4 - public static String[][] recArr = { - {"Pi", String.valueOf(xlfPi), String.valueOf(FTYPE_PTGFUNC)}, - {"Round", String.valueOf(xlfRound), String.valueOf(FTYPE_PTGFUNC)}, - {"Rept", String.valueOf(xlfRept), String.valueOf(FTYPE_PTGFUNC)}, - {"Mid", String.valueOf(xlfMid), String.valueOf(FTYPE_PTGFUNC)}, - {"Mod", String.valueOf(xlfMod), String.valueOf(FTYPE_PTGFUNC)}, - {"MMult", String.valueOf(xlfMmult), String.valueOf(FTYPE_PTGFUNC)}, - {"Rand", String.valueOf(xlfRand), String.valueOf(FTYPE_PTGFUNC)}, - {"Date", String.valueOf(xlfDate), String.valueOf(FTYPE_PTGFUNC)}, - {"Time", String.valueOf(xlfTime), String.valueOf(FTYPE_PTGFUNC)}, - {"Day", String.valueOf(xlfDay), String.valueOf(FTYPE_PTGFUNC)}, - {"Now", String.valueOf(xlfNow), String.valueOf(FTYPE_PTGFUNC)}, - {"TAN", String.valueOf(xlfTan), String.valueOf(FTYPE_PTGFUNC)}, - {"Atan2", String.valueOf(xlfAtan2), String.valueOf(FTYPE_PTGFUNC)}, - {"Replace", String.valueOf(xlfReplace), String.valueOf(FTYPE_PTGFUNC)}, - {"Exact", String.valueOf(xlfExact), String.valueOf(FTYPE_PTGFUNC)}, - {"Trim", String.valueOf(xlfTrim), String.valueOf(FTYPE_PTGFUNC)}, - {"Text", String.valueOf(xlfText), String.valueOf(FTYPE_PTGFUNC)}, - {"Roundup", String.valueOf(xlfRoundup), String.valueOf(FTYPE_PTGFUNC)}, - {"RoundDown", String.valueOf(xlfRounddown), String.valueOf(FTYPE_PTGFUNC)}, - {"today", String.valueOf(xlfToday), String.valueOf(FTYPE_PTGFUNC)}, - {"Combin", String.valueOf(xlfCombin), String.valueOf(FTYPE_PTGFUNC)}, - {"Floor", String.valueOf(xlfFloor), String.valueOf(FTYPE_PTGFUNC)}, - {"Ceiling", String.valueOf(xlfCeiling), String.valueOf(FTYPE_PTGFUNC)}, - {"Power", String.valueOf(xlfPower), String.valueOf(FTYPE_PTGFUNC)}, - {"Hour", String.valueOf(xlfHour), String.valueOf(FTYPE_PTGFUNC)}, - {"Minute", String.valueOf(xlfMinute), String.valueOf(FTYPE_PTGFUNC)}, - {"Month", String.valueOf(xlfMonth), String.valueOf(FTYPE_PTGFUNC)}, - {"Year", String.valueOf(xlfYear), String.valueOf(FTYPE_PTGFUNC)}, - {"Second", String.valueOf(xlfSecond), String.valueOf(FTYPE_PTGFUNC)}, - {"Quartile", String.valueOf(xlfQuartile), String.valueOf(FTYPE_PTGFUNC)}, - {"Frequency", String.valueOf(xlfFrequency), String.valueOf(FTYPE_PTGFUNC)}, - {"Linest", String.valueOf(xlfLinest), String.valueOf(FTYPE_PTGFUNC)}, - {"Correl", String.valueOf(xlfCorrel), String.valueOf(FTYPE_PTGFUNC)}, - {"Slope", String.valueOf(xlfSlope), String.valueOf(FTYPE_PTGFUNC)}, - {"Intercept", String.valueOf(xlfIntercept), String.valueOf(FTYPE_PTGFUNC)}, - {"Pearson", String.valueOf(xlfPearson), String.valueOf(FTYPE_PTGFUNC)}, - {"Rsq", String.valueOf(xlfRsq), String.valueOf(FTYPE_PTGFUNC)}, - {"Steyx", String.valueOf(xlfSteyx), String.valueOf(FTYPE_PTGFUNC)}, - {"Forecast", String.valueOf(xlfForecast), String.valueOf(FTYPE_PTGFUNC)}, - {"Covar", String.valueOf(xlfCovar), String.valueOf(FTYPE_PTGFUNC)}, - {"IsNumber", String.valueOf(xlfIsnumber), String.valueOf(FTYPE_PTGFUNC)}, - {"DAVERAGE", String.valueOf(xlfDaverage), String.valueOf(FTYPE_PTGFUNC)}, - {"DCOUNT", String.valueOf(xlfDcount), String.valueOf(FTYPE_PTGFUNC)}, - {"DCOUNTA", String.valueOf(xlfDcounta), String.valueOf(FTYPE_PTGFUNC)}, - {"DGET", String.valueOf(xlfDget), String.valueOf(FTYPE_PTGFUNC)}, - {"DMIN", String.valueOf(xlfDmin), String.valueOf(FTYPE_PTGFUNC)}, - {"DMAX", String.valueOf(xlfDmax), String.valueOf(FTYPE_PTGFUNC)}, - {"DPRODUCT", String.valueOf(xlfDproduct), String.valueOf(FTYPE_PTGFUNC)}, - {"DSTDEVP", String.valueOf(xlfDstdevp), String.valueOf(FTYPE_PTGFUNC)}, - {"DSTDEV", String.valueOf(xlfDstdev), String.valueOf(FTYPE_PTGFUNC)}, - {"DSUM", String.valueOf(xlfDsum), String.valueOf(FTYPE_PTGFUNC)}, - {"DVAR", String.valueOf(xlfDvar), String.valueOf(FTYPE_PTGFUNC)}, - {"DVARP", String.valueOf(xlfDvarp), String.valueOf(FTYPE_PTGFUNC)}, - {"SQRT", String.valueOf(xlfSqrt), String.valueOf(FTYPE_PTGFUNC)}, - {"NA", String.valueOf(xlfNa), String.valueOf(FTYPE_PTGFUNC)}, - {"EXP", String.valueOf(xlfExp), String.valueOf(FTYPE_PTGFUNC)}, - {"MIRR", String.valueOf(xlfMirr), String.valueOf(FTYPE_PTGFUNC)}, - {"SLN", String.valueOf(xlfSln), String.valueOf(FTYPE_PTGFUNC)}, - {"SYD", String.valueOf(xlfSyd), String.valueOf(FTYPE_PTGFUNC)}, - {"ISPMT", String.valueOf(xlfIspmt), String.valueOf(FTYPE_PTGFUNC)}, - {"UPPER", String.valueOf(xlfUpper), String.valueOf(FTYPE_PTGFUNC)}, - {"LOWER", String.valueOf(xlfLower), String.valueOf(FTYPE_PTGFUNC)}, - {"LEN", String.valueOf(xlfLen), String.valueOf(FTYPE_PTGFUNC)}, - {"ISLOGICAL", String.valueOf(xlfIslogical), String.valueOf(FTYPE_PTGFUNC)}, - {"ISERROR", String.valueOf(XLF_IS_ERROR), String.valueOf(FTYPE_PTGFUNC)}, - {"ISNONTEXT", String.valueOf(xlfIsnontext), String.valueOf(FTYPE_PTGFUNC)}, - {"ISBLANK", String.valueOf(xlfIsblank), String.valueOf(FTYPE_PTGFUNC)}, - {"ISREF", String.valueOf(xlfIsref), String.valueOf(FTYPE_PTGFUNC)}, - {"SIN", String.valueOf(xlfSin), String.valueOf(FTYPE_PTGFUNC)}, - {"SINH", String.valueOf(xlfSinh), String.valueOf(FTYPE_PTGFUNC)}, - {"ASIN", String.valueOf(xlfAsin), String.valueOf(FTYPE_PTGFUNC)}, - {"ASINH", String.valueOf(xlfAsinh), String.valueOf(FTYPE_PTGFUNC)}, - {"COS", String.valueOf(xlfCos), String.valueOf(FTYPE_PTGFUNC)}, - {"COSH", String.valueOf(xlfCosh), String.valueOf(FTYPE_PTGFUNC)}, - {"ACOS", String.valueOf(xlfAcos), String.valueOf(FTYPE_PTGFUNC)}, - {"ACOSH", String.valueOf(xlfAcosh), String.valueOf(FTYPE_PTGFUNC)}, - {"ATAN", String.valueOf(xlfAtan), String.valueOf(FTYPE_PTGFUNC)}, - {"ATANH", String.valueOf(xlfAtanh), String.valueOf(FTYPE_PTGFUNC)}, - {"INT", String.valueOf(xlfInt), String.valueOf(FTYPE_PTGFUNC)}, - {"ABS", String.valueOf(xlfAbs), String.valueOf(FTYPE_PTGFUNC)}, - {"NOT", String.valueOf(xlfNot), String.valueOf(FTYPE_PTGFUNC)}, - {"DEGREES", String.valueOf(xlfDegrees), String.valueOf(FTYPE_PTGFUNC)}, - {"SIGN", String.valueOf(xlfSign), String.valueOf(FTYPE_PTGFUNC)}, - {"EVEN", String.valueOf(xlfEven), String.valueOf(FTYPE_PTGFUNC)}, - {"ODD", String.valueOf(xlfOdd), String.valueOf(FTYPE_PTGFUNC)}, - {"LN", String.valueOf(xlfLn), String.valueOf(FTYPE_PTGFUNC)}, - {"FACT", String.valueOf(xlfFact), String.valueOf(FTYPE_PTGFUNC)}, - {"RADIANS", String.valueOf(xlfRadians), String.valueOf(FTYPE_PTGFUNC)}, - {"PROPER", String.valueOf(xlfProper), String.valueOf(FTYPE_PTGFUNC)}, - {"CHAR", String.valueOf(xlfChar), String.valueOf(FTYPE_PTGFUNC)}, - {"ERROR.TYPE", String.valueOf(xlfErrorType), String.valueOf(FTYPE_PTGFUNC)}, - {"T", String.valueOf(xlfT), String.valueOf(FTYPE_PTGFUNC)}, - {"LOG10", String.valueOf(xlfLog10), String.valueOf(FTYPE_PTGFUNC)}, - {"VALUE", String.valueOf(xlfValue), String.valueOf(FTYPE_PTGFUNC)}, - {"CODE", String.valueOf(xlfCode), String.valueOf(FTYPE_PTGFUNC)}, - {"N", String.valueOf(xlfN), String.valueOf(FTYPE_PTGFUNC)}, - {"DATEVALUE", String.valueOf(xlfDatevalue), String.valueOf(FTYPE_PTGFUNC)}, - {"SMALL", String.valueOf(xlfSmall), String.valueOf(FTYPE_PTGFUNC)}, - {"LARGE", String.valueOf(xlfLarge), String.valueOf(FTYPE_PTGFUNC)}, - {"NORMDIST", String.valueOf(xlfNormdist), String.valueOf(FTYPE_PTGFUNC)}, - {"NORMSDIST", String.valueOf(xlfNormsdist), String.valueOf(FTYPE_PTGFUNC)}, - {"NORMSINV", String.valueOf(xlfNormsinv), String.valueOf(FTYPE_PTGFUNC)}, - {"NORMINV", String.valueOf(xlfNorminv), String.valueOf(FTYPE_PTGFUNC)}, - {"LENB", String.valueOf(xlfLenb), String.valueOf(FTYPE_PTGFUNC)}, - {"INFO", String.valueOf(xlfInfo), String.valueOf(FTYPE_PTGFUNC)}, - {"LEFTB", String.valueOf(xlfLeftb), String.valueOf(FTYPE_PTGFUNC)}, - {"TRUE", String.valueOf(xlfTrue), String.valueOf(FTYPE_PTGFUNC)}, - {"FALSE", String.valueOf(xlfFalse), String.valueOf(FTYPE_PTGFUNC)}, - // PtgFuncVars - {"COUNT", String.valueOf(XLF_COUNT), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"COUNTA", String.valueOf(xlfCounta), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"COUNTIF", String.valueOf(xlfCountif), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"COUNTBLANK", String.valueOf(xlfCountblank), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"IF", String.valueOf(XLF_IS), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ISNA", String.valueOf(XLF_IS_NA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ISERR", String.valueOf(xlfIserr), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUM", String.valueOf(XLF_SUM), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUMIF", String.valueOf(XLF_SUM_IF), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"AVERAGE", String.valueOf(XLF_AVERAGE), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MINA", String.valueOf(xlfMinA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MIN", String.valueOf(XLF_MIN), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MAXA", String.valueOf(xlfMaxA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MAX", String.valueOf(XLF_MAX), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ROW", String.valueOf(XLF_ROW), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"COLUMN", String.valueOf(xlfColumn), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"NPV", String.valueOf(xlfNpv), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"PMT", String.valueOf(xlfPmt), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"DB", String.valueOf(xlfDb), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"FIND", String.valueOf(xlfFind), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"DAYS360", String.valueOf(xlfDays360), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"LEFT", String.valueOf(xlfLeft), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"LOG", String.valueOf(xlfLog), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MEDIAN", String.valueOf(xlfMedian), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MODE", String.valueOf(xlfMode), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"RANK", String.valueOf(xlfRank), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"RIGHT", String.valueOf(xlfRight), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"STDEV", String.valueOf(xlfStdev), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"VAR", String.valueOf(xlfVar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"VARP", String.valueOf(xlfVarp), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TANH", String.valueOf(xlfTanh), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"VLOOKUP", String.valueOf(xlfVlookup), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"HLOOKUP", String.valueOf(xlfHlookup), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"CONCATENATE", String.valueOf(xlfConcatenate), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"INDEX", String.valueOf(xlfIndex), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"MATCH", String.valueOf(xlfMatch), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"FIXED", String.valueOf(xlfFixed), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"AND", String.valueOf(xlfAnd), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"OR", String.valueOf(xlfOr), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"CHOOSE", String.valueOf(xlfChoose), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ADDRESS", String.valueOf(xlfAddress), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"ROMAN", String.valueOf(xlfRoman), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"DOLLAR", String.valueOf(xlfDollar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"USDOLLAR", String.valueOf(xlfUsdollar), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"AVEDEV", String.valueOf(xlfAvedev), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUBSTITUTE", String.valueOf(xlfSubstitute), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"PRODUCT", String.valueOf(xlfProduct), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SEARCH", String.valueOf(xlfSearch), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"AVERAGEA", String.valueOf(xlfAverageA), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"TREND", String.valueOf(xlfTrend), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"SUMPRODUCT", String.valueOf(xlfSumproduct), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"INDIRECT", String.valueOf(XLF_INDIRECT), String.valueOf(FTYPE_PTGFUNCVAR)}, - // Add-in Formulas - // Financial Formulas - {"ACCRINTM", String.valueOf(xlfAccrintm), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ACCRINT", String.valueOf(xlfAccrint), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPDAYBS", String.valueOf(xlfCoupDayBS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPDAYS", String.valueOf(xlfCoupDays), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PV", String.valueOf(xlfPv), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"FV", String.valueOf(xlfFv), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IPMT", String.valueOf(xlfIpmt), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"CUMIPMT", String.valueOf(xlfCumIPmt), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"CUMPRINC", String.valueOf(xlfCumPrinc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPNCD", String.valueOf(xlfCoupNCD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPDAYSNC", String.valueOf(xlfCoupDaysNC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPPCD", String.valueOf(xlfCoupPCD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUPNUM", String.valueOf(xlfCoupNUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DOLLARDE", String.valueOf(xlfDollarDE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DOLLARFR", String.valueOf(xlfDollarFR), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"EFFECT", String.valueOf(xlfEffect), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"INTRATE", String.valueOf(xlfINTRATE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IRR", String.valueOf(xlfIrr), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"XIRR", String.valueOf(xlfXIRR), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"XNPV", String.valueOf(xlfXNPV), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"RATE", String.valueOf(xlfRate), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"YIELD", String.valueOf(xlfYIELD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PRICE", String.valueOf(xlfPRICE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PRICEDISC", String.valueOf(xlfPRICEDISC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DISC", String.valueOf(xlfDISC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PRICEMAT", String.valueOf(xlfPRICEMAT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DURATION", String.valueOf(xlfDURATION), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"MDURATION", String.valueOf(xlfMDURATION), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"NPER", String.valueOf(xlfNper), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"TBILLEQ", String.valueOf(xlfTBillEq), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"TBILLPRICE", String.valueOf(xlfTBillPrice), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"TBILLYIELD", String.valueOf(xlfTBillYield), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"YIELDDISC", String.valueOf(xlfYieldDisc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"YIELDMAT", String.valueOf(xlfYieldMat), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"PPMT", String.valueOf(xlfPpmt), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"FVSCHEDULE", String.valueOf(xlfFVSchedule), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"AMORLINC", String.valueOf(xlfAmorlinc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"AMORDEGRC", String.valueOf(xlfAmordegrc), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ODDFPRICE", String.valueOf(xlfOddFPrice), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ODDLPRICE", String.valueOf(xlfOddLPrice), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ODDFYIELD", String.valueOf(xlfOddFYield), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ODDLYIELD", String.valueOf(xlfOddLYield), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"NOMINAL", String.valueOf(xlfNOMINAL), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"VDB", String.valueOf(xlfVdb), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DDB", String.valueOf(xlfDdb), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"RECEIVED", String.valueOf(xlfRECEIVED), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Engineering Formulas - {"BIN2DEC", String.valueOf(xlfBIN2DEC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"BIN2HEX", String.valueOf(xlfBIN2HEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"BIN2OCT", String.valueOf(xlfBIN2OCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DEC2BIN", String.valueOf(xlfDEC2BIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DEC2HEX", String.valueOf(xlfDEC2HEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DEC2OCT", String.valueOf(xlfDEC2OCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"HEX2BIN", String.valueOf(xlfHEX2BIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"HEX2DEC", String.valueOf(xlfHEX2DEC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"HEX2OCT", String.valueOf(xlfHEX2OCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"OCT2BIN", String.valueOf(xlfOCT2BIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"OCT2DEC", String.valueOf(xlfOCT2DEC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"OCT2HEX", String.valueOf(xlfOCT2HEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COMPLEX", String.valueOf(xlfCOMPLEX), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"GESTEP", String.valueOf(xlfGESTEP), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"DELTA", String.valueOf(xlfDELTA), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMAGINARY", String.valueOf(xlfIMAGINARY), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMREAL", String.valueOf(xlfIMREAL), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMARGUMENT", String.valueOf(xlfIMARGUMENT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMABS", String.valueOf(xlfIMABS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMDIV", String.valueOf(xlfIMDIV), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMCONJUGATE", String.valueOf(xlfIMCONJUGATE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMCOS", String.valueOf(xlfIMCOS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMSIN", String.valueOf(xlfIMSIN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMEXP", String.valueOf(xlfIMEXP), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMPOWER", String.valueOf(xlfIMPOWER), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMSQRT", String.valueOf(xlfIMSQRT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMSUB", String.valueOf(xlfIMSUB), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMSUM", String.valueOf(xlfIMSUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMPRODUCT", String.valueOf(xlfIMPRODUCT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMLN", String.valueOf(xlfIMLN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMLOG10", String.valueOf(xlfIMLOG10), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"IMLOG2", String.valueOf(xlfIMLOG2), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"CONVERT", String.valueOf(xlfCONVERT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ERF", String.valueOf(xlfERF), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Math Add-In Formulas - {"FACTDOUBLE", String.valueOf(xlfDOUBLEFACT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"GCD", String.valueOf(xlfGCD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"LCM", String.valueOf(xlfLCM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"MROUND", String.valueOf(xlfMROUND), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"MULTINOMIAL", String.valueOf(xlfMULTINOMIAL), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"QUOTIENT", String.valueOf(xlfQUOTIENT), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"RANDBETWEEN", String.valueOf(xlfRANDBETWEEN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"SERIESSUM", String.valueOf(xlfSERIESSUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"SQRTPI", String.valueOf(xlfSQRTPI), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"SUMIFS", String.valueOf(xlfSUMIFS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Information Add-Ins - {"ISEVEN", String.valueOf(xlfISEVEN), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"ISODD", String.valueOf(xlfISODD), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Date/Time Add-in Formulas - {"NETWORKDAYS", String.valueOf(xlfNETWORKDAYS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"EDATE", String.valueOf(xlfEDATE), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"EOMONTH", String.valueOf(xlfEOMONTH), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"WEEKNUM", String.valueOf(xlfWEEKNUM), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"WEEKDAY", String.valueOf(xlfWeekday), String.valueOf(FTYPE_PTGFUNCVAR)}, - {"WORKDAY", String.valueOf(xlfWORKDAY), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"YEARFRAC", String.valueOf(xlfYEARFRAC), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Statistical - {"AVERAGEIF", String.valueOf(xlfAVERAGEIF), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"AVERAGEIFS", String.valueOf(xlfAVERAGEIFS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - {"COUNTIFS", String.valueOf(xlfCOUNTIFS), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - // Logical - {"IFERROR", String.valueOf(xlfIFERROR), String.valueOf(FTYPE_PTGFUNCVAR_ADDIN)}, - }; - - /** - * Handles differences - * in japanese locale xls - * - * @param iftb - * @return - */ - public static String getJFunctionString(short iftb) { - switch (iftb) { - case xlfDollar: - return "YEN("; - case xlfUsdollar: - return "DOLLAR("; - case xlfDbcs: - return "JIS("; - } - return null; - } - - - public static String getFunctionString(short iftb) { - switch (iftb) { - case xlfADDIN: - return ""; - case XLF_COUNT: - return "COUNT("; - case XLF_IS: - return "IF("; - case XLF_IS_NA: - return "ISNA("; - case XLF_IS_ERROR: - return "ISERROR("; - case XLF_SUM: - return "SUM("; - case XLF_AVERAGE: - return "AVERAGE("; - case XLF_MIN: - return "MIN("; - case XLF_MAX: - return "MAX("; - case XLF_ROW: - return "ROW("; - case xlfColumn: - return "COLUMN("; - case xlfNa: - return "NA("; - case xlfNpv: - return "NPV("; - case xlfStdev: - return "STDEV("; - case xlfDollar: - return "DOLLAR("; - case xlfFixed: - return "FIXED("; - case xlfSin: - return "SIN("; - case xlfCos: - return "COS("; - case xlfTan: - return "TAN("; - case xlfAtan: - return "ATAN("; - case xlfPi: - return "PI("; - case xlfSqrt: - return "SQRT("; - case xlfExp: - return "EXP("; - case xlfLn: - return "LN("; - case xlfLog10: - return "LOG10("; - case xlfAbs: - return "ABS("; - case xlfInt: - return "INT("; - case xlfSign: - return "SIGN("; - case xlfRound: - return "ROUND("; - case xlfLookup: - return "LOOKUP("; - case xlfIndex: - return "INDEX("; - case xlfRept: - return "REPT("; - case xlfMid: - return "MID("; - case xlfLen: - return "LEN("; - case xlfValue: - return "VALUE("; - case xlfTrue: - return "TRUE("; - case xlfFalse: - return "FALSE("; - case xlfAnd: - return "AND("; - case xlfOr: - return "OR("; - case xlfNot: - return "NOT("; - case xlfMod: - return "MOD("; - case xlfDaverage: - return "DAVERAGE("; - case xlfDcount: - return "DCOUNT("; - case xlfDcounta: - return "DCOUNTA("; - case xlfDget: - return "DGET("; - case xlfDmax: - return "DMAX("; - case xlfDmin: - return "DMIN("; - case xlfDproduct: - return "DPRODUCT("; - case xlfDstdev: - return "DSTDEV("; - case xlfDstdevp: - return "DSTDEVP("; - case xlfDsum: - return "DSUM("; - case xlfDvar: - return "DVAR("; - case xlfDvarp: - return "DVARP("; - case xlfVar: - return "VAR("; - case xlfText: - return "TEXT("; - case xlfLinest: - return "LINEST("; - case xlfTrend: - return "TREND("; - case xlfLogest: - return "LOGEST("; - case xlfGrowth: - return "GROWTH("; - case xlfGoto: - return "GOTO("; - case xlfHalt: - return "HALT("; - case xlfPv: - return "PV("; - case xlfFv: - return "FV("; - case xlfNper: - return "NPER("; - case xlfPmt: - return "PMT("; - case xlfRate: - return "RATE("; - case xlfMirr: - return "MIRR("; - case xlfIrr: - return "IRR("; - case xlfRand: - return "RAND("; - case xlfMatch: - return "MATCH("; - case xlfDate: - return "DATE("; - case xlfTime: - return "TIME("; - case xlfDay: - return "DAY("; - case xlfMonth: - return "MONTH("; - case xlfYear: - return "YEAR("; - case xlfWeekday: - return "WEEKDAY("; - case xlfHour: - return "HOUR("; - case xlfMinute: - return "MINUTE("; - case xlfSecond: - return "SECOND("; - case xlfNow: - return "NOW("; - case xlfAreas: - return "AREAS("; - case xlfRows: - return "ROWS("; - case xlfColumns: - return "COLUMNS("; - case xlfOffset: - return "OFFSET("; - case xlfAbsref: - return "ABSREF("; - case xlfRelref: - return "RELREF("; - case xlfArgument: - return "ARGUMENT("; - case xlfSearch: - return "SEARCH("; - case xlfTranspose: - return "TRANSPOSE("; - case xlfError: - return "ERROR("; - case xlfStep: - return "STEP("; - case xlfType: - return "TYPE("; - case xlfEcho: - return "ECHO("; - case xlfSetName: - return "SETNAME("; - case xlfCaller: - return "CALLER("; - case xlfDeref: - return "DEREF("; - case xlfWindows: - return "WINDOWS("; - case xlfSeries: - return "SERIES("; - case xlfDocuments: - return "DOCUMENTS("; - case xlfActiveCell: - return "ACTIVECELL("; - case xlfSelection: - return "SELECTION("; - case xlfResult: - return "RESULT("; - case xlfAtan2: - return "ATAN2("; - case xlfAsin: - return "ASIN("; - case xlfAcos: - return "ACOS("; - case xlfChoose: - return "CHOOSE("; - case xlfHlookup: - return "HLOOKUP("; - case xlfVlookup: - return "VLOOKUP("; - case xlfLinks: - return "LINKS("; - case xlfInput: - return "INPUT("; - case xlfIsref: - return "ISREF("; - case xlfGetFormula: - return "GETFORMULA("; - case xlfGetName: - return "GETNAME("; - case xlfSetValue: - return "SETVALUE("; - case xlfLog: - return "LOG("; - case xlfExec: - return "EXEC("; - case xlfChar: - return "CHAR("; - case xlfLower: - return "LOWER("; - case xlfUpper: - return "UPPER("; - case xlfProper: - return "PROPER("; - case xlfLeft: - return "LEFT("; - case xlfRight: - return "RIGHT("; - case xlfExact: - return "EXACT("; - case xlfTrim: - return "TRIM("; - case xlfReplace: - return "REPLACE("; - case xlfSubstitute: - return "SUBSTITUTE("; - case xlfCode: - return "CODE("; - case xlfNames: - return "NAMES("; - case xlfDirectory: - return "DIRECTORY("; - case xlfFind: - return "FIND("; - case xlfCell: - return "CELL("; - case xlfIserr: - return "ISERR("; - case xlfIstext: - return "ISTEXT("; - case xlfIsnumber: - return "ISNUMBER("; - case xlfIsblank: - return "ISBLANK("; - case xlfT: - return "T("; - case xlfN: - return "N("; - case xlfFopen: - return "FOPEN("; - case xlfFclose: - return "FCLOSE("; - case xlfFsize: - return "SIZE("; - case xlfFreadln: - return "FREADLN("; - case xlfFread: - return "FREAD("; - case xlfFwriteln: - return "FWRITELN("; - case xlfFwrite: - return "FWRITE("; - case xlfFpos: - return "FPOS("; - case xlfDatevalue: - return "DATEVALUE("; - case xlfTimevalue: - return "TIMEVALUE("; - case xlfSln: - return "SLN("; - case xlfSyd: - return "SYD("; - case xlfDdb: - return "DDB("; - case xlfGetDef: - return "GETDEF("; - case xlfReftext: - return "REFTEXT("; - case xlfTextref: - return "TEXTREF("; - case XLF_INDIRECT: - return "INDIRECT("; - case xlfRegister: - return "REGISTER("; - case xlfCall: - return "CALL("; - case xlfAddBar: - return "ADDBAR("; - case xlfAddMenu: - return "ADDMENU("; - case xlfAddCommand: - return "ADDCOMMAND("; - case xlfEnableCommand: - return "ENABLECOMMAND("; - case xlfCheckCommand: - return "CHECKCOMMAND("; - case xlfRenameCommand: - return "RENAMECOMMAND("; - case xlfShowBar: - return "SHOWBAR("; - case xlfDeleteMenu: - return "DELETEMENU("; - case xlfDeleteCommand: - return "DELETECOMMAND("; - case xlfGetChartItem: - return "CHARTITEM("; - case xlfDialogBox: - return "DIALOGBOX("; - case xlfClean: - return "CLEAN("; - case xlfMdeterm: - return "MDETERM("; - case xlfMinverse: - return "MINVERSE("; - case xlfMmult: - return "MMULT("; - case xlfFiles: - return "FILES("; - case xlfIpmt: - return "IPMT("; - case xlfPpmt: - return "PPMT("; - case xlfCounta: - return "COUNTA("; - case xlfCancelKey: - return "CANCELKEY("; - case xlfInitiate: - return "INITIATE("; - case xlfRequest: - return "REQUEST("; - case xlfPoke: - return "POKE("; - case xlfExecute: - return "EXECUTE("; - case xlfTerminate: - return "TERMINATE("; - case xlfRestart: - return "RESTART("; - case xlfHelp: - return "HELP("; - case xlfGetBar: - return "GETBAR("; - case xlfProduct: - return "PRODUCT("; - case xlfFact: - return "FACT("; - case xlfGetCell: - return "GETCELL("; - case xlfGetWorkspace: - return "GETWORKSPACE("; - case xlfGetWindow: - return "GETWINDOW("; - case xlfGetDocument: - return "GETDOCUMENT("; - case xlfIsnontext: - return "ISNONTEXT("; - case xlfGetNote: - return "GETNOTE("; - case xlfNote: - return "NOTE("; - case xlfStdevp: - return "STDEVP("; - case xlfVarp: - return "VARP("; - case xlfTrunc: - return "TRUNC("; - case xlfIslogical: - return "ISLOGICAL("; - case xlfDeleteBar: - return "DELETEBAR("; - case xlfUnregister: - return "UNREGISTER("; - case xlfUsdollar: - return "USDOLLAR("; - case xlfFindb: - return "FINDB("; - case xlfSearchb: - return "SEARCHB("; - case xlfReplaceb: - return "REPLACEB("; - case xlfLeftb: - return "LEFTB("; - case xlfRightb: - return "RIGHTB("; - case xlfMidb: - return "MIDB("; - case xlfLenb: - return "LENB("; - case xlfRoundup: - return "ROUNDUP("; - case xlfRounddown: - return "ROUNDDOWN("; - case xlfAsc: - return "ASC("; - case xlfDbcs: - return "DBCS("; - case xlfRank: - return "RANK("; - case xlfAddress: - return "ADDRESS("; - case xlfDays360: - return "DAYS360("; - case xlfToday: - return "TODAY("; - case xlfVdb: - return "VDB("; - case xlfMedian: - return "MEDIAN("; - case xlfSumproduct: - return "SUMPRODUCT("; - case xlfSinh: - return "SINH("; - case xlfCosh: - return "COSH("; - case xlfTanh: - return "TANH("; - case xlfAsinh: - return "ASINH("; - case xlfAcosh: - return "ACOSH("; - case xlfAtanh: - return "ATANH("; - case xlfCreateObject: - return "CREATEOBJECT("; - case xlfVolatile: - return "VOLATILE("; - case xlfLastError: - return "LASTERROR("; - case xlfCustomUndo: - return "CUSTOMUNDO("; - case xlfCustomRepeat: - return "CUSTOMREPEAT("; - case xlfFormulaConvert: - return "FORMULACONVERT("; - case xlfGetLinkInfo: - return "GETLINKINFO("; - case xlfTextBox: - return "TEXTBOX("; - case xlfInfo: - return "INFO("; - case xlfGroup: - return "GROUP("; - case xlfGetObject: - return "GETOBJECT("; - case xlfDb: - return "DB("; - case xlfPause: - return "PAUSE("; - case xlfResume: - return "RESUME("; - case xlfFrequency: - return "FREQUENCY("; - case xlfAddToolbar: - return "ADDTOOLBAR("; - case xlfDeleteToolbar: - return "DELETETOOLBAR("; - case xlfResetToolbar: - return "RESETTOOLBAR("; - case xlfEvaluate: - return "EVALUATE("; - case xlfGetToolbar: - return "GETTOOLBAR("; - case xlfGetTool: - return "GETTOOL("; - case xlfSpellingCheck: - return "SPELLINGCHECK("; - case xlfErrorType: - return "ERROR.TYPE("; - case xlfAppTitle: - return "APPTITLE("; - case xlfWindowTitle: - return "WINDOWTITLE("; - case xlfSaveToolbar: - return "SAVETOOLBAR("; - case xlfEnableTool: - return "ENABLETOOL("; - case xlfPressTool: - return "PRESSTOOL("; - case xlfRegisterId: - return "REGISTERID("; - case xlfGetWorkbook: - return "GETWORKBOOK("; - case xlfAvedev: - return "AVEDEV("; - case xlfBetadist: - return "BETADIST("; - case xlfGammaln: - return "GAMMALN("; - case xlfBetainv: - return "BETAINV("; - case xlfBinomdist: - return "BINOMDIST("; - case xlfChidist: - return "CHIDIST("; - case xlfChiinv: - return "CHIINV("; - case xlfCombin: - return "COMBIN("; - case xlfConfidence: - return "CONFIDENCE("; - case xlfCritbinom: - return "CRITBINOM("; - case xlfEven: - return "EVEN("; - case xlfExpondist: - return "EXPONDIST("; - case xlfFdist: - return "FDIST("; - case xlfFinv: - return "FINV("; - case xlfFisher: - return "FISHER("; - case xlfFisherinv: - return "FISHERINV("; - case xlfFloor: - return "FLOOR("; - case xlfGammadist: - return "GAMMADIST("; - case xlfGammainv: - return "GAMMAINV("; - case xlfCeiling: - return "CEILING("; - case xlfHypgeomdist: - return "HYPGEOMDIST("; - case xlfLognormdist: - return "LOGNORMDIST("; - case xlfLoginv: - return "LOGINV("; - case xlfNegbinomdist: - return "NEGBINOMDIST("; - case xlfNormdist: - return "NORMDIST("; - case xlfNormsdist: - return "NORMSDIST("; - case xlfNorminv: - return "NORMINV("; - case xlfNormsinv: - return "NORMSINV("; - case xlfStandardize: - return "STANDARDIZE("; - case xlfOdd: - return "ODD("; - case xlfPermut: - return "PERMUT("; - case xlfPoisson: - return "POISSON("; - case xlfTdist: - return "TDIST("; - case xlfWeibull: - return "WEIBULL("; - case xlfSumxmy2: - return "SUMXMY2("; - case xlfSumx2my2: - return "SUMX2MY2("; - case xlfSumx2py2: - return "SUMX2PY2("; - case xlfChitest: - return "CHITEST("; - case xlfCorrel: - return "CORREL("; - case xlfCovar: - return "COVAR("; - case xlfForecast: - return "FORECAST("; - case xlfFtest: - return "FTEST("; - case xlfIntercept: - return "INTERCEPT("; - case xlfPearson: - return "PEARSON("; - case xlfRsq: - return "RSQ("; - case xlfSteyx: - return "STEYX("; - case xlfSlope: - return "SLOPE("; - case xlfTtest: - return "TTEST("; - case xlfProb: - return "PROB("; - case xlfDevsq: - return "DEVSQ("; - case xlfGeomean: - return "GEOMEAN("; - case xlfHarmean: - return "HARMEAN("; - case xlfSumsq: - return "SUMSQ("; - case xlfKurt: - return "KURT("; - case xlfSkew: - return "SKEW("; - case xlfZtest: - return "ZTEST("; - case xlfLarge: - return "LARGE("; - case xlfSmall: - return "SMALL("; - case xlfQuartile: - return "QUARTILE("; - case xlfPercentile: - return "PERCENTILE("; - case xlfPercentrank: - return "PERCENTRANK("; - case xlfMode: - return "MODE("; - case xlfTrimmean: - return "TRIMMEAN("; - case xlfTinv: - return "TINV("; - case xlfMovieCommand: - return "MOVIECOMMAND("; - case xlfGetMovie: - return "GETMOVIE("; - case xlfConcatenate: - return "CONCATENATE("; - case xlfPower: - return "POWER("; - case xlfPivotAddData: - return "PIVOTADDDATA("; - case xlfGetPivotTable: - return "GETPIVOTTABLE("; - case xlfGetPivotField: - return "GETPIVOTFIELD("; - case xlfGetPivotItem: - return "GETPIVOTITEM("; - case xlfRadians: - return "RADIANS("; - case xlfDegrees: - return "DEGREES("; - case xlfSubtotal: - return "SUBTOTAL("; - case XLF_SUM_IF: - return "SUMIF("; - case xlfCountif: - return "COUNTIF("; - case xlfCountblank: - return "COUNTBLANK("; - case xlfScenarioGet: - return "SCENARIOGET("; - case xlfOptionsListsGet: - return "OPTIONSLISTSGET("; - case xlfIspmt: - return "ISPMT("; - case xlfDatedif: - return "DATEDIF("; - case xlfDatestring: - return "DATESTRING("; - case xlfNumberstring: - return "NUMBERSTRING("; - case xlfRoman: - return "ROMAN("; - case xlfOpenDialog: - return "OPENDIALOG("; - case xlfSaveDialog: - return "SAVEDIALOG("; - case xlfViewGet: - return "VIEWGET("; - case xlfGetPivotData: - return "GETPIVOTDATA("; - case xlfHyperlink: - return "HYPERLINK("; - case xlfPhonetic: - return "PHONETIC("; - case xlfAverageA: - return "AVERAGEA("; - case xlfMaxA: - return "MAXA("; - case xlfMinA: - return "MINA("; - case xlfStDevPA: - return "STDEVPA("; - case xlfVarPA: - return "VARPA("; - case xlfStDevA: - return "STDEVA("; - case xlfVarA: - return "VARA("; - // ADD-IN FORMULAS - // Financial Formulas AddIns - case xlfAccrintm: - return "ACCRINTM("; - case xlfAccrint: - return "ACCRINT("; - case xlfCoupDayBS: - return "COUPDAYBS("; - case xlfCoupDays: - return "COUPDAYS("; - case xlfCoupDaysNC: - return "COUPDAYSNC("; - case xlfCumIPmt: - return "CUMIPMT("; - case xlfCumPrinc: - return "CUMPRINC("; - case xlfCoupNCD: - return "COUPNCD("; - case xlfCoupPCD: - return "COUPPCD("; - case xlfCoupNUM: - return "COUPNUM("; - case xlfDollarDE: - return "DOLLARDE("; - case xlfDollarFR: - return "DOLLARFR("; - case xlfEffect: - return "EFFECT("; - case xlfINTRATE: - return "INTRATE("; - case xlfXIRR: - return "XIRR("; - case xlfXNPV: - return "XNPV("; - case xlfYIELD: - return "YIELD("; - case xlfPRICE: - return "PRICE("; - case xlfPRICEDISC: - return "PRICEDISC("; - case xlfDISC: - return "DISC("; - case xlfPRICEMAT: - return "PRICEMAT("; - case xlfDURATION: - return "DURATION("; - case xlfMDURATION: - return "MDURATION("; - case xlfTBillEq: - return "TBILLEQ("; - case xlfTBillPrice: - return "TBILLPRICE("; - case xlfTBillYield: - return "TBILLYIELD("; - case xlfYieldDisc: - return "YIELDDISC("; - case xlfYieldMat: - return "YIELDMAT("; - case xlfFVSchedule: - return "FVSCHEDULE("; - case xlfAmorlinc: - return "AMORLINC("; - case xlfAmordegrc: - return "AMORDEGRC("; - case xlfOddFPrice: - return "ODDFPRICE("; - case xlfOddFYield: - return "ODDFYIELD("; - case xlfOddLPrice: - return "ODDLPRICE("; - case xlfOddLYield: - return "ODDLYIELD("; - case xlfNOMINAL: - return "NOMINAL("; - case xlfRECEIVED: - return "RECEIVED("; - // Engineering Formulas AddIns - case xlfBIN2DEC: - return "BIN2DEC("; - case xlfBIN2HEX: - return "BIN2HEX("; - case xlfBIN2OCT: - return "BIN2OCT("; - case xlfDEC2BIN: - return "DEC2BIN("; - case xlfDEC2HEX: - return "DEC2HEX("; - case xlfDEC2OCT: - return "DEC2OCT("; - case xlfHEX2BIN: - return "HEX2BIN("; - case xlfHEX2DEC: - return "HEX2DEC("; - case xlfHEX2OCT: - return "HEX2OCT("; - case xlfOCT2BIN: - return "OCT2BIN("; - case xlfOCT2DEC: - return "OCT2DEC("; - case xlfOCT2HEX: - return "OCT2HEX("; - case xlfCOMPLEX: - return "COMPLEX("; - case xlfGESTEP: - return "GESTEP("; - case xlfDELTA: - return "DELTA("; - case xlfIMAGINARY: - return "IMAGINARY("; - case xlfIMREAL: - return "IMREAL("; - case xlfIMARGUMENT: - return "IMARGUMENT("; - case xlfIMABS: - return "IMABS("; - case xlfIMDIV: - return "IMDIV("; - case xlfIMCONJUGATE: - return "IMCONJUGATE("; - case xlfIMCOS: - return "IMCOS("; - case xlfIMSIN: - return "IMSIN("; - case xlfIMEXP: - return "IMEXP("; - case xlfIMPOWER: - return "IMPOWER("; - case xlfIMSQRT: - return "IMSQRT("; - case xlfIMSUB: - return "IMSUB("; - case xlfIMSUM: - return "IMSUM("; - case xlfIMPRODUCT: - return "IMPRODUCT("; - case xlfIMLN: - return "IMLN("; - case xlfIMLOG10: - return "IMLOG10("; - case xlfIMLOG2: - return "IMLOG2("; - case xlfCONVERT: - return "CONVERT("; - case xlfDOUBLEFACT: - return "FACTDOUBLE("; - case xlfGCD: - return "GCD("; - case xlfLCM: - return "LCM("; - case xlfMROUND: - return "MROUND("; - case xlfMULTINOMIAL: - return "MULTINOMIAL("; - case xlfQUOTIENT: - return "QUOTIENT("; - case xlfRANDBETWEEN: - return "RANDBETWEEN("; - case xlfSERIESSUM: - return "SERIESSUM("; - case xlfSQRTPI: - return "SQRTPI("; - case xlfERF: - return "ERF("; - // information Add-ins - case xlfISEVEN: - return "ISEVEN("; - case xlfISODD: - return "ISODD("; - // Date/Time Add-in Formulas - case xlfNETWORKDAYS: - return "NETWORKDAYS("; - case xlfEDATE: - return "EDATE("; - case xlfEOMONTH: - return "EOMONTH("; - case xlfWEEKNUM: - return "WEEKNUM("; - case xlfWORKDAY: - return "WORKDAY("; - case xlfYEARFRAC: - return "YEARFRAC("; - // Statistical - case xlfAVERAGEIF: - return "AVERAGEIF("; - case xlfAVERAGEIFS: - return "AVERAGEIFS("; - case xlfCOUNTIFS: - return "COUNTIFS("; - // Logical - case xlfIFERROR: - return "IFERROR("; - // Math - case xlfSUMIFS: - return "SUMIFS("; - } - return ""; - } - - // Num Params for all PTGFUNCs - ptgfuncvar's have variable # args (hence the name ...) - public static int getNumParams(int iftab) { - if (iftab == xlfNa) return 0; // na - if (iftab == xlfPi) return 0; // Pi - if (iftab == xlfRound) return 2; // Round - if (iftab == xlfRept) return 2; // rept - if (iftab == xlfMid) return 3; // Mid - if (iftab == xlfMod) return 2; // Mod - if (iftab >= xlfDcount && iftab <= xlfDstdev) return 3; // Dxxx formulas - if (iftab == xlfDvar) return 3; // DVar - if (iftab == xlfRand) return 0; // Rand - if (iftab == xlfDate) return 3; // Date - if (iftab == xlfTime) return 3; // Time - if (iftab == xlfDay) return 1; // Day - if (iftab == xlfNow) return 0; // now - if (iftab == xlfAtan2) return 2; // Atan2 - if (iftab == xlfLog) return 2; // Log - if (iftab == xlfLeft) return 2; // Left - if (iftab == xlfRight) return 2; // Right - if (iftab == xlfTrim) return 1; // Trim - if (iftab == xlfText) return 2; // Text - if (iftab == xlfReplace) return 4; // Replace - if (iftab == xlfExact) return 2; // Exact - if (iftab == 165) return 2; //TODO: - if (iftab == xlfDproduct) return 3; // DProduct - if (iftab == xlfDstdevp) return 3; // DStdDevp - if (iftab == xlfDvarp) return 3; // DVarP - if (iftab == xlfDcounta) return 3; // DCountA - if (iftab == xlfRoundup) return 2; // Roundup - if (iftab == xlfRounddown) return 2; // Rounddown - if (iftab == xlfToday) return 0; // today - if (iftab == xlfDget) return 3; // DGet - if (iftab == xlfCombin) return 2; // Combin - if (iftab == xlfFloor) return 2; // Floor - if (iftab == xlfCeiling) return 2; // Ceiling - if (iftab == xlfPower) return 2; // Power - if (iftab == xlfCountif) return 2; // CountIf - if (iftab == xlfQuartile) return 2; - if (iftab == xlfFrequency) return 2; - if (iftab == xlfCorrel) return 2; - if (iftab == xlfCovar) return 2; - if (iftab == xlfSlope) return 2; - if (iftab == xlfIntercept) return 2; - if (iftab == xlfPearson) return 2; - if (iftab == xlfRsq) return 2; - if (iftab == xlfSteyx) return 2; - if (iftab == xlfCritbinom) return 3; - if (iftab == xlfForecast) return 3; - if (iftab == xlfTrend) return 2; - if (iftab == xlfIsnumber) return 1; - if (iftab == xlfMmult) return 2; - if (iftab == xlfHour) return 1; - if (iftab == xlfMinute) return 1; - if (iftab == xlfMonth) return 1; - if (iftab == xlfYear) return 1; - if (iftab == xlfSecond) return 1; - if (iftab == xlfSqrt) return 1; - if (iftab == xlfExp) return 1; - if (iftab == xlfMirr) return 3; - if (iftab == xlfSyd) return 4; - if (iftab == xlfSln) return 3; - if (iftab == xlfIspmt) return 4; - if (iftab == xlfBinomdist) return 4; - if (iftab == xlfChidist) return 2; - if (iftab == xlfChiinv) return 2; - if (iftab == xlfChitest) return 2; - if (iftab == xlfConfidence) return 3; - if (iftab == xlfFtest) return 2; - if (iftab == xlfSumx2my2) return 2; - if (iftab == xlfSumx2py2) return 2; - if (iftab == xlfSumxmy2) return 2; - if (iftab == xlfLookup) return 3; - if (iftab == xlfTrue) return 0; - if (iftab == xlfFalse) return 0; - if (iftab == xlfExpondist) return 3; - if (iftab == xlfFdist) return 3; - if (iftab == xlfFinv) return 3; - if (iftab == xlfLoginv) return 2; - if (iftab == xlfNegbinomdist) return 3; - if (iftab == xlfNormdist) return 4; - if (iftab == xlfNorminv) return 3; - if (iftab == xlfNormsinv) return 1; - if (iftab == xlfStandardize) return 3; - if (iftab == xlfPermut) return 2; - if (iftab == xlfPoisson) return 3; - if (iftab == xlfSumx2my2) return 2; - if (iftab == xlfSumx2py2) return 2; - if (iftab == xlfSumxmy2) return 2; - if (iftab == xlfTdist) return 2; - if (iftab == xlfLarge) return 2; - if (iftab == xlfSmall) return 2; - return 1; //if we are lucky - rest all should be 1 param! - } -} diff --git a/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.kt b/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.kt new file mode 100644 index 0000000..009546e --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/FunctionConstants.kt @@ -0,0 +1,1116 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +/** + * Function Constants for all Formula Types (PtgFunc, PtgFuncVar - regular and PtgFuncVar - AddIns) + * Modifications: + * all xlfXXX constants were originally in FunctionHandler + * getFunctionString was orignally in PtgFuncVar + * FUNCTION_STRINGS were originally in FunctionHandler + * getNumVars was originally in PtgFunc + * + * @see + */ +object FunctionConstants { + /* HOW TO USE: + * + * 1- If implementing a formula, MAKE SURE to put it in recArr AND getFunctionString list. If it is a ptgFunc, also input + * the number of args in getNumArgs. If it's an Add-in, add xlfXX constant to the end of the Excel function numbers list. + * + * All function ID's/number MUST exist in the xlfXXX constants list + * + * + */ + var FTYPE_PTGFUNC = 0 + var FTYPE_PTGFUNCVAR = 1 + var FTYPE_PTGFUNCVAR_ADDIN = 2 + + /**************************************** + * * + * Excel function numbers * + * * + */ + + val XLF_COUNT = 0 + val XLF_IS = 1 + val XLF_IS_NA = 2 + val XLF_IS_ERROR = 3 + val XLF_SUM = 4 + val XLF_AVERAGE = 5 + val XLF_MIN = 6 + val XLF_MAX = 7 + val XLF_ROW = 8 + val xlfColumn = 9 + val xlfNa = 10 + val xlfNpv = 11 + val xlfStdev = 12 + val xlfDollar = 13 + val xlfFixed = 14 + val xlfSin = 15 + val xlfCos = 16 + val xlfTan = 17 + val xlfAtan = 18 + val xlfPi = 19 + val xlfSqrt = 20 + val xlfExp = 21 + val xlfLn = 22 + val xlfLog10 = 23 + val xlfAbs = 24 + val xlfInt = 25 + val xlfSign = 26 + val xlfRound = 27 + val xlfLookup = 28 + val xlfIndex = 29 + val xlfRept = 30 + val xlfMid = 31 + val xlfLen = 32 + val xlfValue = 33 + val xlfTrue = 34 + val xlfFalse = 35 + val xlfAnd = 36 + val xlfOr = 37 + val xlfNot = 38 + val xlfMod = 39 + val xlfDcount = 40 + val xlfDsum = 41 + val xlfDaverage = 42 + val xlfDmin = 43 + val xlfDmax = 44 + val xlfDstdev = 45 + val xlfVar = 46 + val xlfDvar = 47 + val xlfText = 48 + val xlfLinest = 49 + val xlfTrend = 50 + val xlfLogest = 51 + val xlfGrowth = 52 + val xlfGoto = 53 + val xlfHalt = 54 + val xlfPv = 56 + val xlfFv = 57 + val xlfNper = 58 + val xlfPmt = 59 + val xlfRate = 60 + val xlfMirr = 61 + val xlfIrr = 62 + val xlfRand = 63 + val xlfMatch = 64 + val xlfDate = 65 + val xlfTime = 66 + val xlfDay = 67 + val xlfMonth = 68 + val xlfYear = 69 + val xlfWeekday = 70 + val xlfHour = 71 + val xlfMinute = 72 + val xlfSecond = 73 + val xlfNow = 74 + val xlfAreas = 75 + val xlfRows = 76 + val xlfColumns = 77 + val xlfOffset = 78 + val xlfAbsref = 79 + val xlfRelref = 80 + val xlfArgument = 81 + val xlfSearch = 82 + val xlfTranspose = 83 + val xlfError = 84 + val xlfStep = 85 + val xlfType = 86 + val xlfEcho = 87 + val xlfSetName = 88 + val xlfCaller = 89 + val xlfDeref = 90 + val xlfWindows = 91 + val xlfSeries = 92 + val xlfDocuments = 93 + val xlfActiveCell = 94 + val xlfSelection = 95 + val xlfResult = 96 + val xlfAtan2 = 97 + val xlfAsin = 98 + val xlfAcos = 99 + val xlfChoose = 100 + val xlfHlookup = 101 + val xlfVlookup = 102 + val xlfLinks = 103 + val xlfInput = 104 + val xlfIsref = 105 + val xlfGetFormula = 106 + val xlfGetName = 107 + val xlfSetValue = 108 + val xlfLog = 109 + val xlfExec = 110 + val xlfChar = 111 + val xlfLower = 112 + val xlfUpper = 113 + val xlfProper = 114 + val xlfLeft = 115 + val xlfRight = 116 + val xlfExact = 117 + val xlfTrim = 118 + val xlfReplace = 119 + val xlfSubstitute = 120 + val xlfCode = 121 + val xlfNames = 122 + val xlfDirectory = 123 + val xlfFind = 124 + val xlfCell = 125 + val xlfIserr = 126 + val xlfIstext = 127 + val xlfIsnumber = 128 + val xlfIsblank = 129 + val xlfT = 130 + val xlfN = 131 + val xlfFopen = 132 + val xlfFclose = 133 + val xlfFsize = 134 + val xlfFreadln = 135 + val xlfFread = 136 + val xlfFwriteln = 137 + val xlfFwrite = 138 + val xlfFpos = 139 + val xlfDatevalue = 140 + val xlfTimevalue = 141 + val xlfSln = 142 + val xlfSyd = 143 + val xlfDdb = 144 + val xlfGetDef = 145 + val xlfReftext = 146 + val xlfTextref = 147 + val XLF_INDIRECT = 148 + val xlfRegister = 149 + val xlfCall = 150 + val xlfAddBar = 151 + val xlfAddMenu = 152 + val xlfAddCommand = 153 + val xlfEnableCommand = 154 + val xlfCheckCommand = 155 + val xlfRenameCommand = 156 + val xlfShowBar = 157 + val xlfDeleteMenu = 158 + val xlfDeleteCommand = 159 + val xlfGetChartItem = 160 + val xlfDialogBox = 161 + val xlfClean = 162 + val xlfMdeterm = 163 + val xlfMinverse = 164 + val xlfMmult = 165 + val xlfFiles = 166 + val xlfIpmt = 167 + val xlfPpmt = 168 + val xlfCounta = 169 + val xlfCancelKey = 170 + val xlfInitiate = 175 + val xlfRequest = 176 + val xlfPoke = 177 + val xlfExecute = 178 + val xlfTerminate = 179 + val xlfRestart = 180 + val xlfHelp = 181 + val xlfGetBar = 182 + val xlfProduct = 183 + val xlfFact = 184 + val xlfGetCell = 185 + val xlfGetWorkspace = 186 + val xlfGetWindow = 187 + val xlfGetDocument = 188 + val xlfDproduct = 189 + val xlfIsnontext = 190 + val xlfGetNote = 191 + val xlfNote = 192 + val xlfStdevp = 193 + val xlfVarp = 194 + val xlfDstdevp = 195 + val xlfDvarp = 196 + val xlfTrunc = 197 + val xlfIslogical = 198 + val xlfDcounta = 199 + val xlfDeleteBar = 200 + val xlfUnregister = 201 + val xlfUsdollar = 204 + val xlfFindb = 205 + val xlfSearchb = 206 + val xlfReplaceb = 207 + val xlfLeftb = 208 + val xlfRightb = 209 + val xlfMidb = 210 + val xlfLenb = 211 + val xlfRoundup = 212 + val xlfRounddown = 213 + val xlfAsc = 214 + val xlfDbcs = 215 + val xlfRank = 216 + val xlfAddress = 219 + val xlfDays360 = 220 + val xlfToday = 221 + val xlfVdb = 222 + val xlfMedian = 227 + val xlfSumproduct = 228 + val xlfSinh = 229 + val xlfCosh = 230 + val xlfTanh = 231 + val xlfAsinh = 232 + val xlfAcosh = 233 + val xlfAtanh = 234 + val xlfDget = 235 + val xlfCreateObject = 236 + val xlfVolatile = 237 + val xlfLastError = 238 + val xlfCustomUndo = 239 + val xlfCustomRepeat = 240 + val xlfFormulaConvert = 241 + val xlfGetLinkInfo = 242 + val xlfTextBox = 243 + val xlfInfo = 244 + val xlfGroup = 245 + val xlfGetObject = 246 + val xlfDb = 247 + val xlfPause = 248 + val xlfResume = 251 + val xlfFrequency = 252 + val xlfAddToolbar = 253 + val xlfDeleteToolbar = 254 + val xlfADDIN = 255 // KSC: Added; Excel function ID for add-ins + val xlfResetToolbar = 256 + val xlfEvaluate = 257 + val xlfGetToolbar = 258 + val xlfGetTool = 259 + val xlfSpellingCheck = 260 + val xlfErrorType = 261 + val xlfAppTitle = 262 + val xlfWindowTitle = 263 + val xlfSaveToolbar = 264 + val xlfEnableTool = 265 + val xlfPressTool = 266 + val xlfRegisterId = 267 + val xlfGetWorkbook = 268 + val xlfAvedev = 269 + val xlfBetadist = 270 + val xlfGammaln = 271 + val xlfBetainv = 272 + val xlfBinomdist = 273 + val xlfChidist = 274 + val xlfChiinv = 275 + val xlfCombin = 276 + val xlfConfidence = 277 + val xlfCritbinom = 278 + val xlfEven = 279 + val xlfExpondist = 280 + val xlfFdist = 281 + val xlfFinv = 282 + val xlfFisher = 283 + val xlfFisherinv = 284 + val xlfFloor = 285 + val xlfGammadist = 286 + val xlfGammainv = 287 + val xlfCeiling = 288 + val xlfHypgeomdist = 289 + val xlfLognormdist = 290 + val xlfLoginv = 291 + val xlfNegbinomdist = 292 + val xlfNormdist = 293 + val xlfNormsdist = 294 + val xlfNorminv = 295 + val xlfNormsinv = 296 + val xlfStandardize = 297 + val xlfOdd = 298 + val xlfPermut = 299 + val xlfPoisson = 300 + val xlfTdist = 301 + val xlfWeibull = 302 + val xlfSumxmy2 = 303 + val xlfSumx2my2 = 304 + val xlfSumx2py2 = 305 + val xlfChitest = 306 + val xlfCorrel = 307 + val xlfCovar = 308 + val xlfForecast = 309 + val xlfFtest = 310 + val xlfIntercept = 311 + val xlfPearson = 312 + val xlfRsq = 313 + val xlfSteyx = 314 + val xlfSlope = 315 + val xlfTtest = 316 + val xlfProb = 317 + val xlfDevsq = 318 + val xlfGeomean = 319 + val xlfHarmean = 320 + val xlfSumsq = 321 + val xlfKurt = 322 + val xlfSkew = 323 + val xlfZtest = 324 + val xlfLarge = 325 + val xlfSmall = 326 + val xlfQuartile = 327 + val xlfPercentile = 328 + val xlfPercentrank = 329 + val xlfMode = 330 + val xlfTrimmean = 331 + val xlfTinv = 332 + val xlfMovieCommand = 334 + val xlfGetMovie = 335 + val xlfConcatenate = 336 + val xlfPower = 337 + val xlfPivotAddData = 338 + val xlfGetPivotTable = 339 + val xlfGetPivotField = 340 + val xlfGetPivotItem = 341 + val xlfRadians = 342 + val xlfDegrees = 343 + val xlfSubtotal = 344 + val XLF_SUM_IF = 345 + val xlfCountif = 346 + val xlfCountblank = 347 + val xlfScenarioGet = 348 + val xlfOptionsListsGet = 349 + val xlfIspmt = 350 + val xlfDatedif = 351 + val xlfDatestring = 352 + val xlfNumberstring = 353 + val xlfRoman = 354 + val xlfOpenDialog = 355 + val xlfSaveDialog = 356 + val xlfViewGet = 357 + val xlfGetPivotData = 358 + val xlfHyperlink = 359 + val xlfPhonetic = 360 + val xlfAverageA = 361 + val xlfMaxA = 362 + val xlfMinA = 363 + val xlfStDevPA = 364 + val xlfVarPA = 365 + val xlfStDevA = 366 + val xlfVarA = 367 + // KSC: ADD-IN formulas - use any index; name must be present in FunctionConstants.addIns + // Financial Formulas + val xlfAccrintm = 368 + val xlfAccrint = 369 + val xlfCoupDayBS = 370 + val xlfCoupDays = 371 + val xlfCumIPmt = 372 + val xlfCumPrinc = 373 + val xlfCoupNCD = 374 + val xlfCoupDaysNC = 375 + val xlfCoupPCD = 376 + val xlfCoupNUM = 377 + val xlfDollarDE = 378 + val xlfDollarFR = 379 + val xlfEffect = 380 + val xlfINTRATE = 381 + val xlfXIRR = 382 + val xlfXNPV = 383 + val xlfYIELD = 384 + val xlfPRICE = 385 + val xlfPRICEDISC = 386 + val xlfPRICEMAT = 387 + val xlfDURATION = 388 + val xlfMDURATION = 389 + val xlfTBillEq = 390 + val xlfTBillPrice = 391 + val xlfTBillYield = 392 + val xlfYieldDisc = 393 + val xlfYieldMat = 394 + val xlfFVSchedule = 395 + val xlfAmorlinc = 396 + val xlfAmordegrc = 397 + val xlfOddFPrice = 398 + val xlfOddLPrice = 399 + val xlfOddFYield = 400 + val xlfOddLYield = 401 + val xlfNOMINAL = 402 + val xlfDISC = 403 + val xlfRECEIVED = 404 + // Engineering Formulas + val xlfBIN2DEC = 405 + val xlfBIN2HEX = 406 + val xlfBIN2OCT = 407 + val xlfDEC2BIN = 408 + val xlfDEC2HEX = 409 + val xlfDEC2OCT = 410 + val xlfHEX2BIN = 411 + val xlfHEX2DEC = 412 + val xlfHEX2OCT = 413 + val xlfOCT2BIN = 414 + val xlfOCT2DEC = 415 + val xlfOCT2HEX = 416 + val xlfCOMPLEX = 417 + val xlfGESTEP = 418 + val xlfDELTA = 419 + val xlfIMAGINARY = 420 + val xlfIMABS = 421 + val xlfIMDIV = 422 + val xlfIMCONJUGATE = 423 + val xlfIMCOS = 424 + val xlfIMSIN = 425 + val xlfIMREAL = 426 + val xlfIMEXP = 427 + val xlfIMSUB = 428 + val xlfIMSUM = 429 + val xlfIMPRODUCT = 430 + val xlfIMLN = 431 + val xlfIMLOG10 = 432 + val xlfIMLOG2 = 433 + val xlfIMPOWER = 434 + val xlfIMSQRT = 435 + val xlfIMARGUMENT = 436 + val xlfCONVERT = 437 + val xlfERF = 460 + val xlfERFC = 461 + // Math Add-in Formulas + val xlfDOUBLEFACT = 438 + val xlfGCD = 439 + val xlfLCM = 440 + val xlfMROUND = 441 + val xlfMULTINOMIAL = 442 + val xlfQUOTIENT = 443 + val xlfRANDBETWEEN = 444 + val xlfSERIESSUM = 445 + val xlfSQRTPI = 446 + val xlfSUMIFS = 456 + // Information Add-in Formulas + val xlfISEVEN = 447 + val xlfISODD = 448 + // Date/Time Add-in Formulas + val xlfNETWORKDAYS = 449 + val xlfEDATE = 450 + val xlfEOMONTH = 451 + val xlfWEEKNUM = 452 + val xlfWORKDAY = 453 + val xlfYEARFRAC = 459 + // Statistical + val xlfAVERAGEIF = 454 + val xlfAVERAGEIFS = 457 + val xlfCOUNTIFS = 458 + // Logical + val xlfIFERROR = 455 + val MAXXLF = 462 + + + /** + * Japanese Excel contains some different values and string output than US English Excel. + * + * + * This recArr is checked if locale = japan... if null value is returned then the main list is checked + */ + var jRecArr = arrayOf(arrayOf("YEN", xlfDollar.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("DOLLAR", xlfUsdollar.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("JIS", xlfDbcs.toString(), FTYPE_PTGFUNC.toString())) + + /** + * Unimplemented records. This exists to allow writing of functions that are unsupported for calculation + */ + var unimplRecArr = arrayOf(arrayOf("ASC", xlfAsc.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DBCS", xlfDbcs.toString(), FTYPE_PTGFUNC.toString()), arrayOf("MDETERM", xlfMdeterm.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SEARCHB", xlfSearchb.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("TRANSPOSE", xlfTranspose.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("BETAINV", xlfBetainv.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("BETADIST", xlfBetadist.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("TIMEVALUE", xlfTimevalue.toString(), FTYPE_PTGFUNC.toString()), arrayOf("MINVERSE", xlfMinverse.toString(), FTYPE_PTGFUNC.toString()), arrayOf("MDETERM", xlfMdeterm.toString(), FTYPE_PTGFUNC.toString()), arrayOf("GETPIVOTDATA", xlfGetPivotData.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("HYPERLINK", xlfHyperlink.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("PHONETIC", xlfPhonetic.toString(), FTYPE_PTGFUNC.toString()), arrayOf("PERCENTILE", xlfPercentile.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("TRUNC", xlfTrunc.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("PERCENTRANK", xlfPercentrank.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("RIGHTB", xlfRightb.toString(), FTYPE_PTGFUNC.toString()), arrayOf("REPLACEB", xlfReplaceb.toString(), FTYPE_PTGFUNC.toString()), arrayOf("FINDB", xlfFindb.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("MIDB", xlfMidb.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ROWS", xlfRows.toString(), FTYPE_PTGFUNC.toString()), arrayOf("COLUMNS", xlfColumns.toString(), FTYPE_PTGFUNC.toString()), arrayOf("OFFSET", xlfOffset.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("ISTEXT", xlfIstext.toString(), FTYPE_PTGFUNC.toString()), arrayOf("LOOKUP", xlfLookup.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("EXPONDIST", xlfExpondist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("FDIST", xlfFdist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("FINV", xlfFinv.toString(), FTYPE_PTGFUNC.toString()), arrayOf("FTEST", xlfFtest.toString(), FTYPE_PTGFUNC.toString()), arrayOf("FISHER", xlfFisher.toString(), FTYPE_PTGFUNC.toString()), arrayOf("FISHERINV", xlfFisherinv.toString(), FTYPE_PTGFUNC.toString()), arrayOf("STANDARDIZE", xlfStandardize.toString(), FTYPE_PTGFUNC.toString()), arrayOf("PERMUT", xlfPermut.toString(), FTYPE_PTGFUNC.toString()), arrayOf("POISSON", xlfPoisson.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SUMXMY2", xlfSumxmy2.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SUMX2MY2", xlfSumx2my2.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SUMX2PY2", xlfSumx2py2.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ERFC", xlfERFC.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("CONFIDENCE", xlfConfidence.toString(), FTYPE_PTGFUNC.toString()), arrayOf("CRITBINOM", xlfCritbinom.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DEVSQ", xlfDevsq.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("SERIESSUM", xlfSERIESSUM.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("SUBTOTAL", xlfSubtotal.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("SUMSQ", xlfSumsq.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("CHIDIST", xlfChidist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("CHIINV", xlfChiinv.toString(), FTYPE_PTGFUNC.toString()), arrayOf("CHITEST", xlfChitest.toString(), FTYPE_PTGFUNC.toString()), arrayOf("GAMMADIST", xlfGammadist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("GAMMAINV", xlfGammainv.toString(), FTYPE_PTGFUNC.toString()), arrayOf("GAMMALN", xlfGammaln.toString(), FTYPE_PTGFUNC.toString()), arrayOf("GEOMEAN", xlfGeomean.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("GROWTH", xlfGrowth.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("HARMEAN", xlfHarmean.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("HYPGEOMDIST", xlfHypgeomdist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("KURT", xlfKurt.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("LOGEST", xlfLogest.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("LOGINV", xlfLoginv.toString(), FTYPE_PTGFUNC.toString()), arrayOf("LOGNORMDIST", xlfLognormdist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("NEGBINOMDIST", xlfNegbinomdist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("PROB", xlfProb.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("SKEW", xlfSkew.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("STDEVPA", xlfStDevPA.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("STDEVP", xlfStdevp.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("STDEVA", xlfStDevA.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("TDIST", xlfTdist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("TINV", xlfTinv.toString(), FTYPE_PTGFUNC.toString()), arrayOf("TTEST", xlfTtest.toString(), FTYPE_PTGFUNC.toString()), arrayOf("VARA", xlfVarA.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("VARPA", xlfVarPA.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("WEIBULL", xlfWeibull.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ZTEST", xlfZtest.toString(), FTYPE_PTGFUNCVAR.toString())) + + // Contains function name, id and type of ALL Formulas (PtgFuncs, PtgFuncVars and Add-in PtgFuncVars) + + // fetch the pattern match from: http://office.microsoft.com/client/helpcategory.aspx?CategoryID=CH100645029990&lcid=1033&NS=EXCEL&Version=12&CTT=4 + var recArr = arrayOf(arrayOf("Pi", xlfPi.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Round", xlfRound.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Rept", xlfRept.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Mid", xlfMid.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Mod", xlfMod.toString(), FTYPE_PTGFUNC.toString()), arrayOf("MMult", xlfMmult.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Rand", xlfRand.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Date", xlfDate.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Time", xlfTime.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Day", xlfDay.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Now", xlfNow.toString(), FTYPE_PTGFUNC.toString()), arrayOf("TAN", xlfTan.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Atan2", xlfAtan2.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Replace", xlfReplace.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Exact", xlfExact.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Trim", xlfTrim.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Text", xlfText.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Roundup", xlfRoundup.toString(), FTYPE_PTGFUNC.toString()), arrayOf("RoundDown", xlfRounddown.toString(), FTYPE_PTGFUNC.toString()), arrayOf("today", xlfToday.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Combin", xlfCombin.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Floor", xlfFloor.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Ceiling", xlfCeiling.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Power", xlfPower.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Hour", xlfHour.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Minute", xlfMinute.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Month", xlfMonth.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Year", xlfYear.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Second", xlfSecond.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Quartile", xlfQuartile.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Frequency", xlfFrequency.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Linest", xlfLinest.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Correl", xlfCorrel.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Slope", xlfSlope.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Intercept", xlfIntercept.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Pearson", xlfPearson.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Rsq", xlfRsq.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Steyx", xlfSteyx.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Forecast", xlfForecast.toString(), FTYPE_PTGFUNC.toString()), arrayOf("Covar", xlfCovar.toString(), FTYPE_PTGFUNC.toString()), arrayOf("IsNumber", xlfIsnumber.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DAVERAGE", xlfDaverage.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DCOUNT", xlfDcount.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DCOUNTA", xlfDcounta.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DGET", xlfDget.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DMIN", xlfDmin.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DMAX", xlfDmax.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DPRODUCT", xlfDproduct.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DSTDEVP", xlfDstdevp.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DSTDEV", xlfDstdev.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DSUM", xlfDsum.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DVAR", xlfDvar.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DVARP", xlfDvarp.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SQRT", xlfSqrt.toString(), FTYPE_PTGFUNC.toString()), arrayOf("NA", xlfNa.toString(), FTYPE_PTGFUNC.toString()), arrayOf("EXP", xlfExp.toString(), FTYPE_PTGFUNC.toString()), arrayOf("MIRR", xlfMirr.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SLN", xlfSln.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SYD", xlfSyd.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ISPMT", xlfIspmt.toString(), FTYPE_PTGFUNC.toString()), arrayOf("UPPER", xlfUpper.toString(), FTYPE_PTGFUNC.toString()), arrayOf("LOWER", xlfLower.toString(), FTYPE_PTGFUNC.toString()), arrayOf("LEN", xlfLen.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ISLOGICAL", xlfIslogical.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ISERROR", XLF_IS_ERROR.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ISNONTEXT", xlfIsnontext.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ISBLANK", xlfIsblank.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ISREF", xlfIsref.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SIN", xlfSin.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SINH", xlfSinh.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ASIN", xlfAsin.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ASINH", xlfAsinh.toString(), FTYPE_PTGFUNC.toString()), arrayOf("COS", xlfCos.toString(), FTYPE_PTGFUNC.toString()), arrayOf("COSH", xlfCosh.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ACOS", xlfAcos.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ACOSH", xlfAcosh.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ATAN", xlfAtan.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ATANH", xlfAtanh.toString(), FTYPE_PTGFUNC.toString()), arrayOf("INT", xlfInt.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ABS", xlfAbs.toString(), FTYPE_PTGFUNC.toString()), arrayOf("NOT", xlfNot.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DEGREES", xlfDegrees.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SIGN", xlfSign.toString(), FTYPE_PTGFUNC.toString()), arrayOf("EVEN", xlfEven.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ODD", xlfOdd.toString(), FTYPE_PTGFUNC.toString()), arrayOf("LN", xlfLn.toString(), FTYPE_PTGFUNC.toString()), arrayOf("FACT", xlfFact.toString(), FTYPE_PTGFUNC.toString()), arrayOf("RADIANS", xlfRadians.toString(), FTYPE_PTGFUNC.toString()), arrayOf("PROPER", xlfProper.toString(), FTYPE_PTGFUNC.toString()), arrayOf("CHAR", xlfChar.toString(), FTYPE_PTGFUNC.toString()), arrayOf("ERROR.TYPE", xlfErrorType.toString(), FTYPE_PTGFUNC.toString()), arrayOf("T", xlfT.toString(), FTYPE_PTGFUNC.toString()), arrayOf("LOG10", xlfLog10.toString(), FTYPE_PTGFUNC.toString()), arrayOf("VALUE", xlfValue.toString(), FTYPE_PTGFUNC.toString()), arrayOf("CODE", xlfCode.toString(), FTYPE_PTGFUNC.toString()), arrayOf("N", xlfN.toString(), FTYPE_PTGFUNC.toString()), arrayOf("DATEVALUE", xlfDatevalue.toString(), FTYPE_PTGFUNC.toString()), arrayOf("SMALL", xlfSmall.toString(), FTYPE_PTGFUNC.toString()), arrayOf("LARGE", xlfLarge.toString(), FTYPE_PTGFUNC.toString()), arrayOf("NORMDIST", xlfNormdist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("NORMSDIST", xlfNormsdist.toString(), FTYPE_PTGFUNC.toString()), arrayOf("NORMSINV", xlfNormsinv.toString(), FTYPE_PTGFUNC.toString()), arrayOf("NORMINV", xlfNorminv.toString(), FTYPE_PTGFUNC.toString()), arrayOf("LENB", xlfLenb.toString(), FTYPE_PTGFUNC.toString()), arrayOf("INFO", xlfInfo.toString(), FTYPE_PTGFUNC.toString()), arrayOf("LEFTB", xlfLeftb.toString(), FTYPE_PTGFUNC.toString()), arrayOf("TRUE", xlfTrue.toString(), FTYPE_PTGFUNC.toString()), arrayOf("FALSE", xlfFalse.toString(), FTYPE_PTGFUNC.toString()), + // PtgFuncVars + arrayOf("COUNT", XLF_COUNT.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("COUNTA", xlfCounta.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("COUNTIF", xlfCountif.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("COUNTBLANK", xlfCountblank.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("IF", XLF_IS.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("ISNA", XLF_IS_NA.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("ISERR", xlfIserr.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("SUM", XLF_SUM.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("SUMIF", XLF_SUM_IF.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("AVERAGE", XLF_AVERAGE.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("MINA", xlfMinA.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("MIN", XLF_MIN.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("MAXA", xlfMaxA.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("MAX", XLF_MAX.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("ROW", XLF_ROW.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("COLUMN", xlfColumn.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("NPV", xlfNpv.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("PMT", xlfPmt.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("DB", xlfDb.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("FIND", xlfFind.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("DAYS360", xlfDays360.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("LEFT", xlfLeft.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("LOG", xlfLog.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("MEDIAN", xlfMedian.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("MODE", xlfMode.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("RANK", xlfRank.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("RIGHT", xlfRight.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("STDEV", xlfStdev.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("VAR", xlfVar.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("VARP", xlfVarp.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("TANH", xlfTanh.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("VLOOKUP", xlfVlookup.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("HLOOKUP", xlfHlookup.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("CONCATENATE", xlfConcatenate.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("INDEX", xlfIndex.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("MATCH", xlfMatch.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("FIXED", xlfFixed.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("AND", xlfAnd.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("OR", xlfOr.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("CHOOSE", xlfChoose.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("ADDRESS", xlfAddress.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("ROMAN", xlfRoman.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("DOLLAR", xlfDollar.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("USDOLLAR", xlfUsdollar.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("AVEDEV", xlfAvedev.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("SUBSTITUTE", xlfSubstitute.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("PRODUCT", xlfProduct.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("SEARCH", xlfSearch.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("AVERAGEA", xlfAverageA.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("TREND", xlfTrend.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("SUMPRODUCT", xlfSumproduct.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("INDIRECT", XLF_INDIRECT.toString(), FTYPE_PTGFUNCVAR.toString()), + // Add-in Formulas + // Financial Formulas + arrayOf("ACCRINTM", xlfAccrintm.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("ACCRINT", xlfAccrint.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("COUPDAYBS", xlfCoupDayBS.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("COUPDAYS", xlfCoupDays.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("PV", xlfPv.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("FV", xlfFv.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IPMT", xlfIpmt.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("CUMIPMT", xlfCumIPmt.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("CUMPRINC", xlfCumPrinc.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("COUPNCD", xlfCoupNCD.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("COUPDAYSNC", xlfCoupDaysNC.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("COUPPCD", xlfCoupPCD.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("COUPNUM", xlfCoupNUM.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("DOLLARDE", xlfDollarDE.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("DOLLARFR", xlfDollarFR.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("EFFECT", xlfEffect.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("INTRATE", xlfINTRATE.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IRR", xlfIrr.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("XIRR", xlfXIRR.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("XNPV", xlfXNPV.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("RATE", xlfRate.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("YIELD", xlfYIELD.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("PRICE", xlfPRICE.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("PRICEDISC", xlfPRICEDISC.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("DISC", xlfDISC.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("PRICEMAT", xlfPRICEMAT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("DURATION", xlfDURATION.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("MDURATION", xlfMDURATION.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("NPER", xlfNper.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("TBILLEQ", xlfTBillEq.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("TBILLPRICE", xlfTBillPrice.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("TBILLYIELD", xlfTBillYield.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("YIELDDISC", xlfYieldDisc.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("YIELDMAT", xlfYieldMat.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("PPMT", xlfPpmt.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("FVSCHEDULE", xlfFVSchedule.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("AMORLINC", xlfAmorlinc.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("AMORDEGRC", xlfAmordegrc.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("ODDFPRICE", xlfOddFPrice.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("ODDLPRICE", xlfOddLPrice.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("ODDFYIELD", xlfOddFYield.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("ODDLYIELD", xlfOddLYield.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("NOMINAL", xlfNOMINAL.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("VDB", xlfVdb.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("DDB", xlfDdb.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("RECEIVED", xlfRECEIVED.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), + // Engineering Formulas + arrayOf("BIN2DEC", xlfBIN2DEC.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("BIN2HEX", xlfBIN2HEX.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("BIN2OCT", xlfBIN2OCT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("DEC2BIN", xlfDEC2BIN.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("DEC2HEX", xlfDEC2HEX.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("DEC2OCT", xlfDEC2OCT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("HEX2BIN", xlfHEX2BIN.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("HEX2DEC", xlfHEX2DEC.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("HEX2OCT", xlfHEX2OCT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("OCT2BIN", xlfOCT2BIN.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("OCT2DEC", xlfOCT2DEC.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("OCT2HEX", xlfOCT2HEX.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("COMPLEX", xlfCOMPLEX.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("GESTEP", xlfGESTEP.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("DELTA", xlfDELTA.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMAGINARY", xlfIMAGINARY.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMREAL", xlfIMREAL.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMARGUMENT", xlfIMARGUMENT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMABS", xlfIMABS.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMDIV", xlfIMDIV.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMCONJUGATE", xlfIMCONJUGATE.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMCOS", xlfIMCOS.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMSIN", xlfIMSIN.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMEXP", xlfIMEXP.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMPOWER", xlfIMPOWER.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMSQRT", xlfIMSQRT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMSUB", xlfIMSUB.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMSUM", xlfIMSUM.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMPRODUCT", xlfIMPRODUCT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMLN", xlfIMLN.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMLOG10", xlfIMLOG10.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("IMLOG2", xlfIMLOG2.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("CONVERT", xlfCONVERT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("ERF", xlfERF.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), + // Math Add-In Formulas + arrayOf("FACTDOUBLE", xlfDOUBLEFACT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("GCD", xlfGCD.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("LCM", xlfLCM.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("MROUND", xlfMROUND.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("MULTINOMIAL", xlfMULTINOMIAL.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("QUOTIENT", xlfQUOTIENT.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("RANDBETWEEN", xlfRANDBETWEEN.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("SERIESSUM", xlfSERIESSUM.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("SQRTPI", xlfSQRTPI.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("SUMIFS", xlfSUMIFS.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), + // Information Add-Ins + arrayOf("ISEVEN", xlfISEVEN.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("ISODD", xlfISODD.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), + // Date/Time Add-in Formulas + arrayOf("NETWORKDAYS", xlfNETWORKDAYS.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("EDATE", xlfEDATE.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("EOMONTH", xlfEOMONTH.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("WEEKNUM", xlfWEEKNUM.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("WEEKDAY", xlfWeekday.toString(), FTYPE_PTGFUNCVAR.toString()), arrayOf("WORKDAY", xlfWORKDAY.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("YEARFRAC", xlfYEARFRAC.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), + // Statistical + arrayOf("AVERAGEIF", xlfAVERAGEIF.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("AVERAGEIFS", xlfAVERAGEIFS.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), arrayOf("COUNTIFS", xlfCOUNTIFS.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString()), + // Logical + arrayOf("IFERROR", xlfIFERROR.toString(), FTYPE_PTGFUNCVAR_ADDIN.toString())) + + /** + * Handles differences + * in japanese locale xls + * + * @param iftb + * @return + */ + fun getJFunctionString(iftb: Short): String? { + when (iftb) { + xlfDollar -> return "YEN(" + xlfUsdollar -> return "DOLLAR(" + xlfDbcs -> return "JIS(" + } + return null + } + + + fun getFunctionString(iftb: Short): String { + when (iftb) { + xlfADDIN -> return "" + XLF_COUNT -> return "COUNT(" + XLF_IS -> return "IF(" + XLF_IS_NA -> return "ISNA(" + XLF_IS_ERROR -> return "ISERROR(" + XLF_SUM -> return "SUM(" + XLF_AVERAGE -> return "AVERAGE(" + XLF_MIN -> return "MIN(" + XLF_MAX -> return "MAX(" + XLF_ROW -> return "ROW(" + xlfColumn -> return "COLUMN(" + xlfNa -> return "NA(" + xlfNpv -> return "NPV(" + xlfStdev -> return "STDEV(" + xlfDollar -> return "DOLLAR(" + xlfFixed -> return "FIXED(" + xlfSin -> return "SIN(" + xlfCos -> return "COS(" + xlfTan -> return "TAN(" + xlfAtan -> return "ATAN(" + xlfPi -> return "PI(" + xlfSqrt -> return "SQRT(" + xlfExp -> return "EXP(" + xlfLn -> return "LN(" + xlfLog10 -> return "LOG10(" + xlfAbs -> return "ABS(" + xlfInt -> return "INT(" + xlfSign -> return "SIGN(" + xlfRound -> return "ROUND(" + xlfLookup -> return "LOOKUP(" + xlfIndex -> return "INDEX(" + xlfRept -> return "REPT(" + xlfMid -> return "MID(" + xlfLen -> return "LEN(" + xlfValue -> return "VALUE(" + xlfTrue -> return "TRUE(" + xlfFalse -> return "FALSE(" + xlfAnd -> return "AND(" + xlfOr -> return "OR(" + xlfNot -> return "NOT(" + xlfMod -> return "MOD(" + xlfDaverage -> return "DAVERAGE(" + xlfDcount -> return "DCOUNT(" + xlfDcounta -> return "DCOUNTA(" + xlfDget -> return "DGET(" + xlfDmax -> return "DMAX(" + xlfDmin -> return "DMIN(" + xlfDproduct -> return "DPRODUCT(" + xlfDstdev -> return "DSTDEV(" + xlfDstdevp -> return "DSTDEVP(" + xlfDsum -> return "DSUM(" + xlfDvar -> return "DVAR(" + xlfDvarp -> return "DVARP(" + xlfVar -> return "VAR(" + xlfText -> return "TEXT(" + xlfLinest -> return "LINEST(" + xlfTrend -> return "TREND(" + xlfLogest -> return "LOGEST(" + xlfGrowth -> return "GROWTH(" + xlfGoto -> return "GOTO(" + xlfHalt -> return "HALT(" + xlfPv -> return "PV(" + xlfFv -> return "FV(" + xlfNper -> return "NPER(" + xlfPmt -> return "PMT(" + xlfRate -> return "RATE(" + xlfMirr -> return "MIRR(" + xlfIrr -> return "IRR(" + xlfRand -> return "RAND(" + xlfMatch -> return "MATCH(" + xlfDate -> return "DATE(" + xlfTime -> return "TIME(" + xlfDay -> return "DAY(" + xlfMonth -> return "MONTH(" + xlfYear -> return "YEAR(" + xlfWeekday -> return "WEEKDAY(" + xlfHour -> return "HOUR(" + xlfMinute -> return "MINUTE(" + xlfSecond -> return "SECOND(" + xlfNow -> return "NOW(" + xlfAreas -> return "AREAS(" + xlfRows -> return "ROWS(" + xlfColumns -> return "COLUMNS(" + xlfOffset -> return "OFFSET(" + xlfAbsref -> return "ABSREF(" + xlfRelref -> return "RELREF(" + xlfArgument -> return "ARGUMENT(" + xlfSearch -> return "SEARCH(" + xlfTranspose -> return "TRANSPOSE(" + xlfError -> return "ERROR(" + xlfStep -> return "STEP(" + xlfType -> return "TYPE(" + xlfEcho -> return "ECHO(" + xlfSetName -> return "SETNAME(" + xlfCaller -> return "CALLER(" + xlfDeref -> return "DEREF(" + xlfWindows -> return "WINDOWS(" + xlfSeries -> return "SERIES(" + xlfDocuments -> return "DOCUMENTS(" + xlfActiveCell -> return "ACTIVECELL(" + xlfSelection -> return "SELECTION(" + xlfResult -> return "RESULT(" + xlfAtan2 -> return "ATAN2(" + xlfAsin -> return "ASIN(" + xlfAcos -> return "ACOS(" + xlfChoose -> return "CHOOSE(" + xlfHlookup -> return "HLOOKUP(" + xlfVlookup -> return "VLOOKUP(" + xlfLinks -> return "LINKS(" + xlfInput -> return "INPUT(" + xlfIsref -> return "ISREF(" + xlfGetFormula -> return "GETFORMULA(" + xlfGetName -> return "GETNAME(" + xlfSetValue -> return "SETVALUE(" + xlfLog -> return "LOG(" + xlfExec -> return "EXEC(" + xlfChar -> return "CHAR(" + xlfLower -> return "LOWER(" + xlfUpper -> return "UPPER(" + xlfProper -> return "PROPER(" + xlfLeft -> return "LEFT(" + xlfRight -> return "RIGHT(" + xlfExact -> return "EXACT(" + xlfTrim -> return "TRIM(" + xlfReplace -> return "REPLACE(" + xlfSubstitute -> return "SUBSTITUTE(" + xlfCode -> return "CODE(" + xlfNames -> return "NAMES(" + xlfDirectory -> return "DIRECTORY(" + xlfFind -> return "FIND(" + xlfCell -> return "CELL(" + xlfIserr -> return "ISERR(" + xlfIstext -> return "ISTEXT(" + xlfIsnumber -> return "ISNUMBER(" + xlfIsblank -> return "ISBLANK(" + xlfT -> return "T(" + xlfN -> return "N(" + xlfFopen -> return "FOPEN(" + xlfFclose -> return "FCLOSE(" + xlfFsize -> return "SIZE(" + xlfFreadln -> return "FREADLN(" + xlfFread -> return "FREAD(" + xlfFwriteln -> return "FWRITELN(" + xlfFwrite -> return "FWRITE(" + xlfFpos -> return "FPOS(" + xlfDatevalue -> return "DATEVALUE(" + xlfTimevalue -> return "TIMEVALUE(" + xlfSln -> return "SLN(" + xlfSyd -> return "SYD(" + xlfDdb -> return "DDB(" + xlfGetDef -> return "GETDEF(" + xlfReftext -> return "REFTEXT(" + xlfTextref -> return "TEXTREF(" + XLF_INDIRECT -> return "INDIRECT(" + xlfRegister -> return "REGISTER(" + xlfCall -> return "CALL(" + xlfAddBar -> return "ADDBAR(" + xlfAddMenu -> return "ADDMENU(" + xlfAddCommand -> return "ADDCOMMAND(" + xlfEnableCommand -> return "ENABLECOMMAND(" + xlfCheckCommand -> return "CHECKCOMMAND(" + xlfRenameCommand -> return "RENAMECOMMAND(" + xlfShowBar -> return "SHOWBAR(" + xlfDeleteMenu -> return "DELETEMENU(" + xlfDeleteCommand -> return "DELETECOMMAND(" + xlfGetChartItem -> return "CHARTITEM(" + xlfDialogBox -> return "DIALOGBOX(" + xlfClean -> return "CLEAN(" + xlfMdeterm -> return "MDETERM(" + xlfMinverse -> return "MINVERSE(" + xlfMmult -> return "MMULT(" + xlfFiles -> return "FILES(" + xlfIpmt -> return "IPMT(" + xlfPpmt -> return "PPMT(" + xlfCounta -> return "COUNTA(" + xlfCancelKey -> return "CANCELKEY(" + xlfInitiate -> return "INITIATE(" + xlfRequest -> return "REQUEST(" + xlfPoke -> return "POKE(" + xlfExecute -> return "EXECUTE(" + xlfTerminate -> return "TERMINATE(" + xlfRestart -> return "RESTART(" + xlfHelp -> return "HELP(" + xlfGetBar -> return "GETBAR(" + xlfProduct -> return "PRODUCT(" + xlfFact -> return "FACT(" + xlfGetCell -> return "GETCELL(" + xlfGetWorkspace -> return "GETWORKSPACE(" + xlfGetWindow -> return "GETWINDOW(" + xlfGetDocument -> return "GETDOCUMENT(" + xlfIsnontext -> return "ISNONTEXT(" + xlfGetNote -> return "GETNOTE(" + xlfNote -> return "NOTE(" + xlfStdevp -> return "STDEVP(" + xlfVarp -> return "VARP(" + xlfTrunc -> return "TRUNC(" + xlfIslogical -> return "ISLOGICAL(" + xlfDeleteBar -> return "DELETEBAR(" + xlfUnregister -> return "UNREGISTER(" + xlfUsdollar -> return "USDOLLAR(" + xlfFindb -> return "FINDB(" + xlfSearchb -> return "SEARCHB(" + xlfReplaceb -> return "REPLACEB(" + xlfLeftb -> return "LEFTB(" + xlfRightb -> return "RIGHTB(" + xlfMidb -> return "MIDB(" + xlfLenb -> return "LENB(" + xlfRoundup -> return "ROUNDUP(" + xlfRounddown -> return "ROUNDDOWN(" + xlfAsc -> return "ASC(" + xlfDbcs -> return "DBCS(" + xlfRank -> return "RANK(" + xlfAddress -> return "ADDRESS(" + xlfDays360 -> return "DAYS360(" + xlfToday -> return "TODAY(" + xlfVdb -> return "VDB(" + xlfMedian -> return "MEDIAN(" + xlfSumproduct -> return "SUMPRODUCT(" + xlfSinh -> return "SINH(" + xlfCosh -> return "COSH(" + xlfTanh -> return "TANH(" + xlfAsinh -> return "ASINH(" + xlfAcosh -> return "ACOSH(" + xlfAtanh -> return "ATANH(" + xlfCreateObject -> return "CREATEOBJECT(" + xlfVolatile -> return "VOLATILE(" + xlfLastError -> return "LASTERROR(" + xlfCustomUndo -> return "CUSTOMUNDO(" + xlfCustomRepeat -> return "CUSTOMREPEAT(" + xlfFormulaConvert -> return "FORMULACONVERT(" + xlfGetLinkInfo -> return "GETLINKINFO(" + xlfTextBox -> return "TEXTBOX(" + xlfInfo -> return "INFO(" + xlfGroup -> return "GROUP(" + xlfGetObject -> return "GETOBJECT(" + xlfDb -> return "DB(" + xlfPause -> return "PAUSE(" + xlfResume -> return "RESUME(" + xlfFrequency -> return "FREQUENCY(" + xlfAddToolbar -> return "ADDTOOLBAR(" + xlfDeleteToolbar -> return "DELETETOOLBAR(" + xlfResetToolbar -> return "RESETTOOLBAR(" + xlfEvaluate -> return "EVALUATE(" + xlfGetToolbar -> return "GETTOOLBAR(" + xlfGetTool -> return "GETTOOL(" + xlfSpellingCheck -> return "SPELLINGCHECK(" + xlfErrorType -> return "ERROR.TYPE(" + xlfAppTitle -> return "APPTITLE(" + xlfWindowTitle -> return "WINDOWTITLE(" + xlfSaveToolbar -> return "SAVETOOLBAR(" + xlfEnableTool -> return "ENABLETOOL(" + xlfPressTool -> return "PRESSTOOL(" + xlfRegisterId -> return "REGISTERID(" + xlfGetWorkbook -> return "GETWORKBOOK(" + xlfAvedev -> return "AVEDEV(" + xlfBetadist -> return "BETADIST(" + xlfGammaln -> return "GAMMALN(" + xlfBetainv -> return "BETAINV(" + xlfBinomdist -> return "BINOMDIST(" + xlfChidist -> return "CHIDIST(" + xlfChiinv -> return "CHIINV(" + xlfCombin -> return "COMBIN(" + xlfConfidence -> return "CONFIDENCE(" + xlfCritbinom -> return "CRITBINOM(" + xlfEven -> return "EVEN(" + xlfExpondist -> return "EXPONDIST(" + xlfFdist -> return "FDIST(" + xlfFinv -> return "FINV(" + xlfFisher -> return "FISHER(" + xlfFisherinv -> return "FISHERINV(" + xlfFloor -> return "FLOOR(" + xlfGammadist -> return "GAMMADIST(" + xlfGammainv -> return "GAMMAINV(" + xlfCeiling -> return "CEILING(" + xlfHypgeomdist -> return "HYPGEOMDIST(" + xlfLognormdist -> return "LOGNORMDIST(" + xlfLoginv -> return "LOGINV(" + xlfNegbinomdist -> return "NEGBINOMDIST(" + xlfNormdist -> return "NORMDIST(" + xlfNormsdist -> return "NORMSDIST(" + xlfNorminv -> return "NORMINV(" + xlfNormsinv -> return "NORMSINV(" + xlfStandardize -> return "STANDARDIZE(" + xlfOdd -> return "ODD(" + xlfPermut -> return "PERMUT(" + xlfPoisson -> return "POISSON(" + xlfTdist -> return "TDIST(" + xlfWeibull -> return "WEIBULL(" + xlfSumxmy2 -> return "SUMXMY2(" + xlfSumx2my2 -> return "SUMX2MY2(" + xlfSumx2py2 -> return "SUMX2PY2(" + xlfChitest -> return "CHITEST(" + xlfCorrel -> return "CORREL(" + xlfCovar -> return "COVAR(" + xlfForecast -> return "FORECAST(" + xlfFtest -> return "FTEST(" + xlfIntercept -> return "INTERCEPT(" + xlfPearson -> return "PEARSON(" + xlfRsq -> return "RSQ(" + xlfSteyx -> return "STEYX(" + xlfSlope -> return "SLOPE(" + xlfTtest -> return "TTEST(" + xlfProb -> return "PROB(" + xlfDevsq -> return "DEVSQ(" + xlfGeomean -> return "GEOMEAN(" + xlfHarmean -> return "HARMEAN(" + xlfSumsq -> return "SUMSQ(" + xlfKurt -> return "KURT(" + xlfSkew -> return "SKEW(" + xlfZtest -> return "ZTEST(" + xlfLarge -> return "LARGE(" + xlfSmall -> return "SMALL(" + xlfQuartile -> return "QUARTILE(" + xlfPercentile -> return "PERCENTILE(" + xlfPercentrank -> return "PERCENTRANK(" + xlfMode -> return "MODE(" + xlfTrimmean -> return "TRIMMEAN(" + xlfTinv -> return "TINV(" + xlfMovieCommand -> return "MOVIECOMMAND(" + xlfGetMovie -> return "GETMOVIE(" + xlfConcatenate -> return "CONCATENATE(" + xlfPower -> return "POWER(" + xlfPivotAddData -> return "PIVOTADDDATA(" + xlfGetPivotTable -> return "GETPIVOTTABLE(" + xlfGetPivotField -> return "GETPIVOTFIELD(" + xlfGetPivotItem -> return "GETPIVOTITEM(" + xlfRadians -> return "RADIANS(" + xlfDegrees -> return "DEGREES(" + xlfSubtotal -> return "SUBTOTAL(" + XLF_SUM_IF -> return "SUMIF(" + xlfCountif -> return "COUNTIF(" + xlfCountblank -> return "COUNTBLANK(" + xlfScenarioGet -> return "SCENARIOGET(" + xlfOptionsListsGet -> return "OPTIONSLISTSGET(" + xlfIspmt -> return "ISPMT(" + xlfDatedif -> return "DATEDIF(" + xlfDatestring -> return "DATESTRING(" + xlfNumberstring -> return "NUMBERSTRING(" + xlfRoman -> return "ROMAN(" + xlfOpenDialog -> return "OPENDIALOG(" + xlfSaveDialog -> return "SAVEDIALOG(" + xlfViewGet -> return "VIEWGET(" + xlfGetPivotData -> return "GETPIVOTDATA(" + xlfHyperlink -> return "HYPERLINK(" + xlfPhonetic -> return "PHONETIC(" + xlfAverageA -> return "AVERAGEA(" + xlfMaxA -> return "MAXA(" + xlfMinA -> return "MINA(" + xlfStDevPA -> return "STDEVPA(" + xlfVarPA -> return "VARPA(" + xlfStDevA -> return "STDEVA(" + xlfVarA -> return "VARA(" + // ADD-IN FORMULAS + // Financial Formulas AddIns + xlfAccrintm -> return "ACCRINTM(" + xlfAccrint -> return "ACCRINT(" + xlfCoupDayBS -> return "COUPDAYBS(" + xlfCoupDays -> return "COUPDAYS(" + xlfCoupDaysNC -> return "COUPDAYSNC(" + xlfCumIPmt -> return "CUMIPMT(" + xlfCumPrinc -> return "CUMPRINC(" + xlfCoupNCD -> return "COUPNCD(" + xlfCoupPCD -> return "COUPPCD(" + xlfCoupNUM -> return "COUPNUM(" + xlfDollarDE -> return "DOLLARDE(" + xlfDollarFR -> return "DOLLARFR(" + xlfEffect -> return "EFFECT(" + xlfINTRATE -> return "INTRATE(" + xlfXIRR -> return "XIRR(" + xlfXNPV -> return "XNPV(" + xlfYIELD -> return "YIELD(" + xlfPRICE -> return "PRICE(" + xlfPRICEDISC -> return "PRICEDISC(" + xlfDISC -> return "DISC(" + xlfPRICEMAT -> return "PRICEMAT(" + xlfDURATION -> return "DURATION(" + xlfMDURATION -> return "MDURATION(" + xlfTBillEq -> return "TBILLEQ(" + xlfTBillPrice -> return "TBILLPRICE(" + xlfTBillYield -> return "TBILLYIELD(" + xlfYieldDisc -> return "YIELDDISC(" + xlfYieldMat -> return "YIELDMAT(" + xlfFVSchedule -> return "FVSCHEDULE(" + xlfAmorlinc -> return "AMORLINC(" + xlfAmordegrc -> return "AMORDEGRC(" + xlfOddFPrice -> return "ODDFPRICE(" + xlfOddFYield -> return "ODDFYIELD(" + xlfOddLPrice -> return "ODDLPRICE(" + xlfOddLYield -> return "ODDLYIELD(" + xlfNOMINAL -> return "NOMINAL(" + xlfRECEIVED -> return "RECEIVED(" + // Engineering Formulas AddIns + xlfBIN2DEC -> return "BIN2DEC(" + xlfBIN2HEX -> return "BIN2HEX(" + xlfBIN2OCT -> return "BIN2OCT(" + xlfDEC2BIN -> return "DEC2BIN(" + xlfDEC2HEX -> return "DEC2HEX(" + xlfDEC2OCT -> return "DEC2OCT(" + xlfHEX2BIN -> return "HEX2BIN(" + xlfHEX2DEC -> return "HEX2DEC(" + xlfHEX2OCT -> return "HEX2OCT(" + xlfOCT2BIN -> return "OCT2BIN(" + xlfOCT2DEC -> return "OCT2DEC(" + xlfOCT2HEX -> return "OCT2HEX(" + xlfCOMPLEX -> return "COMPLEX(" + xlfGESTEP -> return "GESTEP(" + xlfDELTA -> return "DELTA(" + xlfIMAGINARY -> return "IMAGINARY(" + xlfIMREAL -> return "IMREAL(" + xlfIMARGUMENT -> return "IMARGUMENT(" + xlfIMABS -> return "IMABS(" + xlfIMDIV -> return "IMDIV(" + xlfIMCONJUGATE -> return "IMCONJUGATE(" + xlfIMCOS -> return "IMCOS(" + xlfIMSIN -> return "IMSIN(" + xlfIMEXP -> return "IMEXP(" + xlfIMPOWER -> return "IMPOWER(" + xlfIMSQRT -> return "IMSQRT(" + xlfIMSUB -> return "IMSUB(" + xlfIMSUM -> return "IMSUM(" + xlfIMPRODUCT -> return "IMPRODUCT(" + xlfIMLN -> return "IMLN(" + xlfIMLOG10 -> return "IMLOG10(" + xlfIMLOG2 -> return "IMLOG2(" + xlfCONVERT -> return "CONVERT(" + xlfDOUBLEFACT -> return "FACTDOUBLE(" + xlfGCD -> return "GCD(" + xlfLCM -> return "LCM(" + xlfMROUND -> return "MROUND(" + xlfMULTINOMIAL -> return "MULTINOMIAL(" + xlfQUOTIENT -> return "QUOTIENT(" + xlfRANDBETWEEN -> return "RANDBETWEEN(" + xlfSERIESSUM -> return "SERIESSUM(" + xlfSQRTPI -> return "SQRTPI(" + xlfERF -> return "ERF(" + // information Add-ins + xlfISEVEN -> return "ISEVEN(" + xlfISODD -> return "ISODD(" + // Date/Time Add-in Formulas + xlfNETWORKDAYS -> return "NETWORKDAYS(" + xlfEDATE -> return "EDATE(" + xlfEOMONTH -> return "EOMONTH(" + xlfWEEKNUM -> return "WEEKNUM(" + xlfWORKDAY -> return "WORKDAY(" + xlfYEARFRAC -> return "YEARFRAC(" + // Statistical + xlfAVERAGEIF -> return "AVERAGEIF(" + xlfAVERAGEIFS -> return "AVERAGEIFS(" + xlfCOUNTIFS -> return "COUNTIFS(" + // Logical + xlfIFERROR -> return "IFERROR(" + // Math + xlfSUMIFS -> return "SUMIFS(" + } + return "" + } + + // Num Params for all PTGFUNCs - ptgfuncvar's have variable # args (hence the name ...) + fun getNumParams(iftab: Int): Int { + if (iftab == xlfNa) return 0 // na + if (iftab == xlfPi) return 0 // Pi + if (iftab == xlfRound) return 2 // Round + if (iftab == xlfRept) return 2 // rept + if (iftab == xlfMid) return 3 // Mid + if (iftab == xlfMod) return 2 // Mod + if (iftab >= xlfDcount && iftab <= xlfDstdev) return 3 // Dxxx formulas + if (iftab == xlfDvar) return 3 // DVar + if (iftab == xlfRand) return 0 // Rand + if (iftab == xlfDate) return 3 // Date + if (iftab == xlfTime) return 3 // Time + if (iftab == xlfDay) return 1 // Day + if (iftab == xlfNow) return 0 // now + if (iftab == xlfAtan2) return 2 // Atan2 + if (iftab == xlfLog) return 2 // Log + if (iftab == xlfLeft) return 2 // Left + if (iftab == xlfRight) return 2 // Right + if (iftab == xlfTrim) return 1 // Trim + if (iftab == xlfText) return 2 // Text + if (iftab == xlfReplace) return 4 // Replace + if (iftab == xlfExact) return 2 // Exact + if (iftab == 165) return 2 //TODO: + if (iftab == xlfDproduct) return 3 // DProduct + if (iftab == xlfDstdevp) return 3 // DStdDevp + if (iftab == xlfDvarp) return 3 // DVarP + if (iftab == xlfDcounta) return 3 // DCountA + if (iftab == xlfRoundup) return 2 // Roundup + if (iftab == xlfRounddown) return 2 // Rounddown + if (iftab == xlfToday) return 0 // today + if (iftab == xlfDget) return 3 // DGet + if (iftab == xlfCombin) return 2 // Combin + if (iftab == xlfFloor) return 2 // Floor + if (iftab == xlfCeiling) return 2 // Ceiling + if (iftab == xlfPower) return 2 // Power + if (iftab == xlfCountif) return 2 // CountIf + if (iftab == xlfQuartile) return 2 + if (iftab == xlfFrequency) return 2 + if (iftab == xlfCorrel) return 2 + if (iftab == xlfCovar) return 2 + if (iftab == xlfSlope) return 2 + if (iftab == xlfIntercept) return 2 + if (iftab == xlfPearson) return 2 + if (iftab == xlfRsq) return 2 + if (iftab == xlfSteyx) return 2 + if (iftab == xlfCritbinom) return 3 + if (iftab == xlfForecast) return 3 + if (iftab == xlfTrend) return 2 + if (iftab == xlfIsnumber) return 1 + if (iftab == xlfMmult) return 2 + if (iftab == xlfHour) return 1 + if (iftab == xlfMinute) return 1 + if (iftab == xlfMonth) return 1 + if (iftab == xlfYear) return 1 + if (iftab == xlfSecond) return 1 + if (iftab == xlfSqrt) return 1 + if (iftab == xlfExp) return 1 + if (iftab == xlfMirr) return 3 + if (iftab == xlfSyd) return 4 + if (iftab == xlfSln) return 3 + if (iftab == xlfIspmt) return 4 + if (iftab == xlfBinomdist) return 4 + if (iftab == xlfChidist) return 2 + if (iftab == xlfChiinv) return 2 + if (iftab == xlfChitest) return 2 + if (iftab == xlfConfidence) return 3 + if (iftab == xlfFtest) return 2 + if (iftab == xlfSumx2my2) return 2 + if (iftab == xlfSumx2py2) return 2 + if (iftab == xlfSumxmy2) return 2 + if (iftab == xlfLookup) return 3 + if (iftab == xlfTrue) return 0 + if (iftab == xlfFalse) return 0 + if (iftab == xlfExpondist) return 3 + if (iftab == xlfFdist) return 3 + if (iftab == xlfFinv) return 3 + if (iftab == xlfLoginv) return 2 + if (iftab == xlfNegbinomdist) return 3 + if (iftab == xlfNormdist) return 4 + if (iftab == xlfNorminv) return 3 + if (iftab == xlfNormsinv) return 1 + if (iftab == xlfStandardize) return 3 + if (iftab == xlfPermut) return 2 + if (iftab == xlfPoisson) return 3 + if (iftab == xlfSumx2my2) return 2 + if (iftab == xlfSumx2py2) return 2 + if (iftab == xlfSumxmy2) return 2 + if (iftab == xlfTdist) return 2 + if (iftab == xlfLarge) return 2 + return if (iftab == xlfSmall) 2 else 1 +//if we are lucky - rest all should be 1 param! + } +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.java b/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.java deleted file mode 100644 index 0d6ec91..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.java +++ /dev/null @@ -1,1789 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.formats.XLS.XLSRecord; - -import java.util.Locale; - -/** - * Function Handler takes an array of PTG's with a header PtgFunc or PtgFuncVar, calcuates - * those ptgs in the determined way, then return a relevant PtgValue - *

                      - * Descriptions of these functions are available on the msdn site, - * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/office97/html/S88F9.asp - */ - - -public class FunctionHandler { - - - /* - Calculates the function and returns a relevant Ptg as a value - This is the main entry method. - - */ - public static final Ptg calculateFunction(Ptg[] ptgs) throws FunctionNotSupportedException, CalculationException { - Ptg funk; // the function identifier - Ptg[] operands; // the ptgs acted upon by the function - int funkId = 0; //what function are we calling? - - funk = ptgs[0]; - // if ptgs are missing parent_recs, populate from funk - XLSRecord bpar = funk.getParentRec(); - if (bpar != null) { - for (int t = 0; t < ptgs.length; t++) { - if (ptgs[t].getParentRec() == null) - ptgs[t].setParentRec(bpar); - } - } - - int oplen = ptgs.length - 1; - operands = new Ptg[oplen]; - System.arraycopy(ptgs, 1, operands, 0, oplen); - if (funk.getOpcode() == 0x21 || funk.getOpcode() == 0x41 || funk.getOpcode() == 0x61) { // ptgfunc - return calculatePtgFunc(funk, funkId, operands); - } else if (funk.getOpcode() == 0x22 || funk.getOpcode() == 0x42 || funk.getOpcode() == 0x62) { // ptgfuncvar - return calculatePtgFuncVar(funk, funkId, operands); - } - return null; - } - - /* - Keep the calculation of ptgfunc & ptgfuncvar seperate in case any differences show up - */ - public static final Ptg calculatePtgFunc(Ptg funk, int funkId, Ptg[] operands) - throws FunctionNotSupportedException, CalculationException { - PtgFunc pf = (PtgFunc) funk; - funkId = pf.getVal(); - return parse_n_calc(funk, funkId, operands); - } - - - /** - * Keep the calculation of ptgfunc & ptgfuncvar seperate in case any differences show up - * - * @throws CalculationException - */ - public static final Ptg calculatePtgFuncVar(Ptg funk, int funkId, Ptg[] operands) throws FunctionNotSupportedException, CalculationException { - PtgFuncVar pf = (PtgFuncVar) funk; - funkId = pf.getVal(); - // Handle Add-in Formulas - which have a name operand 1st - if (funkId == FunctionConstants.xlfADDIN) { // XL flag that formula is an add-in - // must pop the PtgNameX record to get the correct function id - String s = ""; - boolean foundit = false; - if (operands[0] instanceof PtgNameX) { - int index = ((PtgNameX) operands[0]).getVal(); - s = pf.getParentRec().getSheet().getWorkBook().getExternalName(index); - } else if (operands[0] instanceof PtgName) { - s = ((PtgName) operands[0]).getStoredName(); - } - if (s.startsWith("_xlfn.")) { // Excel "new" functions - s = s.substring(6); - } - if (Locale.JAPAN.equals(Locale.getDefault())) { - for (int y = 0; y < FunctionConstants.jRecArr.length; y++) { - if (s.equalsIgnoreCase(FunctionConstants.jRecArr[y][0])) { - funkId = Integer.valueOf(FunctionConstants.jRecArr[y][1]).intValue(); - y = FunctionConstants.jRecArr.length; // exit loop - foundit = true; - } - } - } - if (!foundit) { - for (int y = 0; y < FunctionConstants.recArr.length; y++) { // Use FunctionConstants instead of PtFuncVar - if (s.equalsIgnoreCase(FunctionConstants.recArr[y][0])) { - funkId = Integer.valueOf(FunctionConstants.recArr[y][1]).intValue(); - y = FunctionConstants.recArr.length; // exit loop - } - } - } - if (funkId == 255)// it's not found - throw new FunctionNotSupportedException(s); - -// now get rid of PtgNameX operand before calling function - Ptg[] ops = new Ptg[operands.length - 1]; - System.arraycopy(operands, 1, ops, 0, operands.length - 1); - operands = new Ptg[ops.length]; - System.arraycopy(ops, 0, operands, 0, ops.length); - } // end KSC added - return parse_n_calc(funk, funkId, operands); - } - - - /************************************************************************************ - * * - * Your standard big case statement, calling methods based on what the funkid is. * - * You will notice that these are seperated out into packages based on the MS * - * documentation (Link above). Each package calls a different class full of * - * static method calls. There are a lot of these :-) * - * * - * PLEASE: Remove function from comment list when you enable it!!!! * - * * - * @throws CalculationException - *************************************************************************************/ - public static final Ptg parse_n_calc(Ptg function, int functionId, Ptg[] operands) throws FunctionNotSupportedException, CalculationException { - Ptg resultPtg = null; - Ptg[] resultArrPtg = null; - - switch (functionId) { - /******************************************** - * Database and List package functions ** - ********************************************/ - case FunctionConstants.xlfDaverage: - resultPtg = DatabaseCalculator.calcDAverage(operands); - break; - - case FunctionConstants.xlfDcount: - resultPtg = DatabaseCalculator.calcDCount(operands); - break; - - case FunctionConstants.xlfDcounta: - resultPtg = DatabaseCalculator.calcDCountA(operands); - break; - - case FunctionConstants.xlfDget: - resultPtg = DatabaseCalculator.calcDGet(operands); - break; - - case FunctionConstants.xlfDmax: - resultPtg = DatabaseCalculator.calcDMax(operands); - break; - - case FunctionConstants.xlfDmin: - resultPtg = DatabaseCalculator.calcDMin(operands); - break; - - case FunctionConstants.xlfDproduct: - resultPtg = DatabaseCalculator.calcDProduct(operands); - break; - - case FunctionConstants.xlfDstdev: - resultPtg = DatabaseCalculator.calcDStdDev(operands); - break; - - case FunctionConstants.xlfDstdevp: - resultPtg = DatabaseCalculator.calcDStdDevP(operands); - break; - - case FunctionConstants.xlfDsum: - resultPtg = DatabaseCalculator.calcDSum(operands); - break; - - case FunctionConstants.xlfDvar: - resultPtg = DatabaseCalculator.calcDVar(operands); - break; - - case FunctionConstants.xlfDvarp: - resultPtg = DatabaseCalculator.calcDVarP(operands); - break; - - /******************************************** - * Date and time functions ********* - ********************************************/ - case FunctionConstants.xlfDate: - resultPtg = DateTimeCalculator.calcDate(operands); - break; - - case FunctionConstants.xlfDay: - resultPtg = DateTimeCalculator.calcDay(operands); - break; - - case FunctionConstants.xlfDays360: - resultPtg = DateTimeCalculator.calcDays360(operands); - break; - - case FunctionConstants.xlfHour: - resultPtg = DateTimeCalculator.calcHour(operands); - break; - - case FunctionConstants.xlfMinute: - resultPtg = DateTimeCalculator.calcMinute(operands); - break; - - case FunctionConstants.xlfMonth: - resultPtg = DateTimeCalculator.calcMonth(operands); - break; - - case FunctionConstants.xlfYear: - resultPtg = DateTimeCalculator.calcYear(operands); - break; - - case FunctionConstants.xlfSecond: - resultPtg = DateTimeCalculator.calcSecond(operands); - break; - - case FunctionConstants.xlfTimevalue: - resultPtg = DateTimeCalculator.calcTimevalue(operands); - break; - - case FunctionConstants.xlfWeekday: - resultPtg = DateTimeCalculator.calcWeekday(operands); - break; - - case FunctionConstants.xlfWEEKNUM: - resultPtg = DateTimeCalculator.calcWeeknum(operands); - break; - - case FunctionConstants.xlfWORKDAY: - resultPtg = DateTimeCalculator.calcWorkday(operands); - break; - - case FunctionConstants.xlfYEARFRAC: - resultPtg = DateTimeCalculator.calcYearFrac(operands); - break; - - case FunctionConstants.xlfNow: - resultPtg = DateTimeCalculator.calcNow(operands); - break; - - case FunctionConstants.xlfTime: - resultPtg = DateTimeCalculator.calcTime(operands); - break; - - case FunctionConstants.xlfToday: - resultPtg = DateTimeCalculator.calcToday(operands); - break; - - case FunctionConstants.xlfDatevalue: - resultPtg = DateTimeCalculator.calcDateValue(operands); - break; - - case FunctionConstants.xlfEDATE: - resultPtg = DateTimeCalculator.calcEdate(operands); - break; - - case FunctionConstants.xlfEOMONTH: - resultPtg = DateTimeCalculator.calcEOMonth(operands); - break; - - case FunctionConstants.xlfNETWORKDAYS: - resultPtg = DateTimeCalculator.calcNetWorkdays(operands); - break; - - /******************************************** - * DDE and External functions **** - ********************************************/ - - - /******************************************** - * Engineering functions ********* - ********************************************/ - case FunctionConstants.xlfBIN2DEC: - resultPtg = EngineeringCalculator.calcBin2Dec(operands); - break; - - case FunctionConstants.xlfBIN2HEX: - resultPtg = EngineeringCalculator.calcBin2Hex(operands); - break; - - case FunctionConstants.xlfBIN2OCT: - resultPtg = EngineeringCalculator.calcBin2Oct(operands); - break; - - case FunctionConstants.xlfDEC2BIN: - resultPtg = EngineeringCalculator.calcDec2Bin(operands); - break; - - case FunctionConstants.xlfDEC2HEX: - resultPtg = EngineeringCalculator.calcDec2Hex(operands); - break; - - case FunctionConstants.xlfDEC2OCT: - resultPtg = EngineeringCalculator.calcDec2Oct(operands); - break; - - case FunctionConstants.xlfHEX2BIN: - resultPtg = EngineeringCalculator.calcHex2Bin(operands); - break; - - case FunctionConstants.xlfHEX2DEC: - resultPtg = EngineeringCalculator.calcHex2Dec(operands); - break; - - case FunctionConstants.xlfHEX2OCT: - resultPtg = EngineeringCalculator.calcHex2Oct(operands); - break; - - case FunctionConstants.xlfOCT2BIN: - resultPtg = EngineeringCalculator.calcOct2Bin(operands); - break; - - case FunctionConstants.xlfOCT2DEC: - resultPtg = EngineeringCalculator.calcOct2Dec(operands); - break; - - case FunctionConstants.xlfOCT2HEX: - resultPtg = EngineeringCalculator.calcOct2Hex(operands); - break; - - case FunctionConstants.xlfCOMPLEX: - resultPtg = EngineeringCalculator.calcComplex(operands); - break; - - case FunctionConstants.xlfGESTEP: - resultPtg = EngineeringCalculator.calcGEStep(operands); - break; - - case FunctionConstants.xlfDELTA: - resultPtg = EngineeringCalculator.calcDelta(operands); - break; - - case FunctionConstants.xlfIMAGINARY: - resultPtg = EngineeringCalculator.calcImaginary(operands); - break; - - case FunctionConstants.xlfIMREAL: - resultPtg = EngineeringCalculator.calcImReal(operands); - break; - - case FunctionConstants.xlfIMARGUMENT: - resultPtg = EngineeringCalculator.calcImArgument(operands); - break; - - case FunctionConstants.xlfIMABS: - resultPtg = EngineeringCalculator.calcImAbs(operands); - break; - - case FunctionConstants.xlfIMDIV: - resultPtg = EngineeringCalculator.calcImDiv(operands); - break; - - case FunctionConstants.xlfIMCONJUGATE: - resultPtg = EngineeringCalculator.calcImConjugate(operands); - break; - - case FunctionConstants.xlfIMCOS: - resultPtg = EngineeringCalculator.calcImCos(operands); - break; - - case FunctionConstants.xlfIMSIN: - resultPtg = EngineeringCalculator.calcImSin(operands); - break; - - case FunctionConstants.xlfIMEXP: - resultPtg = EngineeringCalculator.calcImExp(operands); - break; - - case FunctionConstants.xlfIMSUB: - resultPtg = EngineeringCalculator.calcImSub(operands); - break; - - case FunctionConstants.xlfIMSUM: - resultPtg = EngineeringCalculator.calcImSum(operands); - break; - - case FunctionConstants.xlfIMPRODUCT: - resultPtg = EngineeringCalculator.calcImProduct(operands); - break; - - case FunctionConstants.xlfIMLN: - resultPtg = EngineeringCalculator.calcImLn(operands); - break; - - case FunctionConstants.xlfIMLOG10: - resultPtg = EngineeringCalculator.calcImLog10(operands); - break; - - case FunctionConstants.xlfIMLOG2: - resultPtg = EngineeringCalculator.calcImLog2(operands); - break; - - case FunctionConstants.xlfIMPOWER: - resultPtg = EngineeringCalculator.calcImPower(operands); - break; - - case FunctionConstants.xlfIMSQRT: - resultPtg = EngineeringCalculator.calcImSqrt(operands); - break; - - case FunctionConstants.xlfCONVERT: - resultPtg = EngineeringCalculator.calcConvert(operands); - break; - - case FunctionConstants.xlfERF: - resultPtg = EngineeringCalculator.calcErf(operands); - break; - - - /******************************************** - * Financial functions ********* - ********************************************/ - - case FunctionConstants.xlfDb: - resultPtg = FinancialCalculator.calcDB(operands); - break; - - case FunctionConstants.xlfDdb: - resultPtg = FinancialCalculator.calcDDB(operands); - break; - - case FunctionConstants.xlfPmt: - resultPtg = FinancialCalculator.calcPmt(operands); - break; - - // KSC: Added - case FunctionConstants.xlfAccrintm: - resultPtg = FinancialCalculator.calcAccrintm(operands); - break; - - case FunctionConstants.xlfAccrint: - resultPtg = FinancialCalculator.calcAccrint(operands); - break; - - case FunctionConstants.xlfCoupDayBS: - resultPtg = FinancialCalculator.calcCoupDayBS(operands); - break; - - case FunctionConstants.xlfCoupDays: - resultPtg = FinancialCalculator.calcCoupDays(operands); - break; - - case FunctionConstants.xlfNpv: - resultPtg = FinancialCalculator.calcNPV(operands); - break; - - case FunctionConstants.xlfPv: - resultPtg = FinancialCalculator.calcPV(operands); - break; - - case FunctionConstants.xlfFv: - resultPtg = FinancialCalculator.calcFV(operands); - break; - - case FunctionConstants.xlfIpmt: - resultPtg = FinancialCalculator.calcIPMT(operands); - break; - - case FunctionConstants.xlfCumIPmt: - resultPtg = FinancialCalculator.calcCumIPmt(operands); - break; - - case FunctionConstants.xlfCumPrinc: - resultPtg = FinancialCalculator.calcCumPrinc(operands); - break; - - case FunctionConstants.xlfCoupNCD: - resultPtg = FinancialCalculator.calcCoupNCD(operands); - break; - - case FunctionConstants.xlfCoupDaysNC: - resultPtg = FinancialCalculator.calcCoupDaysNC(operands); - break; - - case FunctionConstants.xlfCoupPCD: - resultPtg = FinancialCalculator.calcCoupPCD(operands); - break; - - case FunctionConstants.xlfCoupNUM: - resultPtg = FinancialCalculator.calcCoupNum(operands); - break; - - case FunctionConstants.xlfDollarDE: - resultPtg = FinancialCalculator.calcDollarDE(operands); - break; - - case FunctionConstants.xlfDollarFR: - resultPtg = FinancialCalculator.calcDollarFR(operands); - break; - - case FunctionConstants.xlfEffect: - resultPtg = FinancialCalculator.calcEffect(operands); - break; - - case FunctionConstants.xlfRECEIVED: - resultPtg = FinancialCalculator.calcReceived(operands); - break; - - case FunctionConstants.xlfINTRATE: - resultPtg = FinancialCalculator.calcINTRATE(operands); - break; - - case FunctionConstants.xlfIrr: - resultPtg = FinancialCalculator.calcIRR(operands); - break; - - case FunctionConstants.xlfMirr: - resultPtg = FinancialCalculator.calcMIRR(operands); - break; - - case FunctionConstants.xlfXIRR: - resultPtg = FinancialCalculator.calcXIRR(operands); - break; - - case FunctionConstants.xlfXNPV: - resultPtg = FinancialCalculator.calcXNPV(operands); - break; - - case FunctionConstants.xlfRate: - resultPtg = FinancialCalculator.calcRate(operands); - break; - - case FunctionConstants.xlfYIELD: - resultPtg = FinancialCalculator.calcYIELD(operands); - break; - - case FunctionConstants.xlfPRICE: - resultPtg = FinancialCalculator.calcPRICE(operands); - break; - - case FunctionConstants.xlfPRICEDISC: - resultPtg = FinancialCalculator.calcPRICEDISC(operands); - break; - - case FunctionConstants.xlfPRICEMAT: - resultPtg = FinancialCalculator.calcPRICEMAT(operands); - break; - - case FunctionConstants.xlfDISC: - resultPtg = FinancialCalculator.calcDISC(operands); - break; - - case FunctionConstants.xlfNper: - resultPtg = FinancialCalculator.calcNPER(operands); - break; - - case FunctionConstants.xlfSln: - resultPtg = FinancialCalculator.calcSLN(operands); - break; - - case FunctionConstants.xlfSyd: - resultPtg = FinancialCalculator.calcSYD(operands); - break; - - case FunctionConstants.xlfDURATION: - resultPtg = FinancialCalculator.calcDURATION(operands); - break; - - case FunctionConstants.xlfMDURATION: - resultPtg = FinancialCalculator.calcMDURATION(operands); - break; - - case FunctionConstants.xlfTBillEq: - resultPtg = FinancialCalculator.calcTBillEq(operands); - break; - - case FunctionConstants.xlfTBillPrice: - resultPtg = FinancialCalculator.calcTBillPrice(operands); - break; - - case FunctionConstants.xlfTBillYield: - resultPtg = FinancialCalculator.calcTBillYield(operands); - break; - - case FunctionConstants.xlfYieldDisc: - resultPtg = FinancialCalculator.calcYieldDisc(operands); - break; - - case FunctionConstants.xlfYieldMat: - resultPtg = FinancialCalculator.calcYieldMat(operands); - break; - - case FunctionConstants.xlfPpmt: - resultPtg = FinancialCalculator.calcPPMT(operands); - break; - - case FunctionConstants.xlfFVSchedule: - resultPtg = FinancialCalculator.calcFVSCHEDULE(operands); - break; - - case FunctionConstants.xlfIspmt: - resultPtg = FinancialCalculator.calcISPMT(operands); - break; - - case FunctionConstants.xlfAmorlinc: - resultPtg = FinancialCalculator.calcAmorlinc(operands); - break; - - case FunctionConstants.xlfAmordegrc: - resultPtg = FinancialCalculator.calcAmordegrc(operands); - break; - - case FunctionConstants.xlfOddFPrice: - resultPtg = FinancialCalculator.calcODDFPRICE(operands); - break; - - case FunctionConstants.xlfOddFYield: - resultPtg = FinancialCalculator.calcODDFYIELD(operands); - break; - - case FunctionConstants.xlfOddLPrice: - resultPtg = FinancialCalculator.calcODDLPRICE(operands); - break; - - case FunctionConstants.xlfOddLYield: - resultPtg = FinancialCalculator.calcODDLYIELD(operands); - break; - - case FunctionConstants.xlfNOMINAL: - resultPtg = FinancialCalculator.calcNominal(operands); - break; - - case FunctionConstants.xlfVdb: - resultPtg = FinancialCalculator.calcVDB(operands); - break; - /******************************************** - * Information functions ********* - ********************************************/ - - case FunctionConstants.xlfCell: - resultPtg = InformationCalculator.calcCell(operands); - break; - - case FunctionConstants.xlfInfo: - resultPtg = InformationCalculator.calcInfo(operands); - break; - - case FunctionConstants.XLF_IS_NA: - resultPtg = InformationCalculator.calcIsna(operands); - break; - - case FunctionConstants.XLF_IS_ERROR: - resultPtg = InformationCalculator.calcIserror(operands); - break; - - case FunctionConstants.xlfIserr: - resultPtg = InformationCalculator.calcIserr(operands); - break; - - case FunctionConstants.xlfErrorType: - resultPtg = InformationCalculator.calcErrorType(operands); - break; - - case FunctionConstants.xlfNa: - resultPtg = InformationCalculator.calcNa(operands); - break; - - case FunctionConstants.xlfIsblank: - resultPtg = InformationCalculator.calcIsBlank(operands); - break; - - case FunctionConstants.xlfIslogical: - resultPtg = InformationCalculator.calcIsLogical(operands); - break; - - case FunctionConstants.xlfIsnontext: - resultPtg = InformationCalculator.calcIsNonText(operands); - break; - - case FunctionConstants.xlfIstext: - resultPtg = InformationCalculator.calcIsText(operands); - break; - - case FunctionConstants.xlfIsref: - resultPtg = InformationCalculator.calcIsRef(operands); - break; - - case FunctionConstants.xlfN: - resultPtg = InformationCalculator.calcN(operands); - break; - - case FunctionConstants.xlfIsnumber: - resultPtg = InformationCalculator.calcIsNumber(operands); - break; - - case FunctionConstants.xlfISEVEN: - resultPtg = InformationCalculator.calcIsEven(operands); - break; - - case FunctionConstants.xlfISODD: - resultPtg = InformationCalculator.calcIsOdd(operands); - break; - - case FunctionConstants.xlfType: - resultPtg = InformationCalculator.calcType(operands); - break; - - /******************************************** - * Logical functions ***** - ********************************************/ - case FunctionConstants.xlfAnd: - resultPtg = LogicalCalculator.calcAnd(operands); - break; - - case FunctionConstants.xlfFalse: - resultPtg = LogicalCalculator.calcFalse(operands); - break; - - case FunctionConstants.xlfTrue: - resultPtg = LogicalCalculator.calcTrue(operands); - break; - - case FunctionConstants.XLF_IS: - resultPtg = LogicalCalculator.calcIf(operands); - break; - - case FunctionConstants.xlfNot: - resultPtg = LogicalCalculator.calcNot(operands); - break; - - case FunctionConstants.xlfOr: - resultPtg = LogicalCalculator.calcOr(operands); - break; - - case FunctionConstants.xlfIFERROR: - resultPtg = LogicalCalculator.calcIferror(operands); - break; - - /******************************************** - * Lookup and reference functions ***** - ********************************************/ - case FunctionConstants.xlfAddress: - resultPtg = LookupReferenceCalculator.calcAddress(operands); - break; - - case FunctionConstants.xlfAreas: - resultPtg = LookupReferenceCalculator.calcAreas(operands); - break; - - case FunctionConstants.xlfChoose: - resultPtg = LookupReferenceCalculator.calcChoose(operands); - break; - - case FunctionConstants.xlfColumn: - if (operands.length == 0) { - operands = new Ptg[1]; - operands[0] = function; - } - resultPtg = LookupReferenceCalculator.calcColumn(operands); - break; - - case FunctionConstants.xlfColumns: - resultPtg = LookupReferenceCalculator.calcColumns(operands); - break; - - case FunctionConstants.xlfHyperlink: - resultPtg = LookupReferenceCalculator.calcHyperlink(operands); - break; - - - case FunctionConstants.xlfIndex: - resultPtg = LookupReferenceCalculator.calcIndex(operands); - break; - - case FunctionConstants.XLF_INDIRECT: - resultPtg = LookupReferenceCalculator.calcIndirect(operands); - break; - - case FunctionConstants.XLF_ROW: - if (operands.length == 0) { - operands = new Ptg[1]; - operands[0] = function; - } - resultPtg = LookupReferenceCalculator.calcRow(operands); - break; - - case FunctionConstants.xlfRows: - resultPtg = LookupReferenceCalculator.calcRows(operands); - break; - - case FunctionConstants.xlfTranspose: - resultPtg = LookupReferenceCalculator.calcTranspose(operands); - break; - - case FunctionConstants.xlfLookup: - resultPtg = LookupReferenceCalculator.calcLookup(operands); - // KSC: Clear out lookup caches! -// function.getParentRec().getWorkBook().getRefTracker().clearLookupCaches(); - break; - - case FunctionConstants.xlfHlookup: - resultPtg = LookupReferenceCalculator.calcHlookup(operands); - // KSC: Clear out lookup caches! -// function.getParentRec().getWorkBook().getRefTracker().clearLookupCaches(); - break; - - case FunctionConstants.xlfVlookup: - resultPtg = LookupReferenceCalculator.calcVlookup(operands); - break; - - case FunctionConstants.xlfMatch: - resultPtg = LookupReferenceCalculator.calcMatch(operands); - break; - - case FunctionConstants.xlfOffset: - resultPtg = LookupReferenceCalculator.calcOffset(operands); - break; - - /******************************************** - * Math & Trigonometry functions ***** - ********************************************/ - - case FunctionConstants.XLF_SUM: - resultPtg = MathFunctionCalculator.calcSum(operands); - break; - - case FunctionConstants.XLF_SUM_IF: - resultPtg = MathFunctionCalculator.calcSumif(operands); - break; - - case FunctionConstants.xlfSUMIFS: - resultPtg = MathFunctionCalculator.calcSumIfS(operands); - break; - - case FunctionConstants.xlfSumproduct: - resultPtg = MathFunctionCalculator.calcSumproduct(operands); - break; - - case FunctionConstants.xlfExp: - resultPtg = MathFunctionCalculator.calcExp(operands); - break; - - case FunctionConstants.xlfAbs: - resultPtg = MathFunctionCalculator.calcAbs(operands); - break; - - case FunctionConstants.xlfAcos: - resultPtg = MathFunctionCalculator.calcAcos(operands); - break; - - case FunctionConstants.xlfAcosh: - resultPtg = MathFunctionCalculator.calcAcosh(operands); - break; - - case FunctionConstants.xlfAsin: - resultPtg = MathFunctionCalculator.calcAsin(operands); - break; - - case FunctionConstants.xlfAsinh: - resultPtg = MathFunctionCalculator.calcAsinh(operands); - break; - - case FunctionConstants.xlfAtan: - resultPtg = MathFunctionCalculator.calcAtan(operands); - break; - - case FunctionConstants.xlfAtan2: - resultPtg = MathFunctionCalculator.calcAtan2(operands); - break; - - case FunctionConstants.xlfAtanh: - resultPtg = MathFunctionCalculator.calcAtanh(operands); - break; - - case FunctionConstants.xlfCeiling: - resultPtg = MathFunctionCalculator.calcCeiling(operands); - break; - - case FunctionConstants.xlfCombin: - resultPtg = MathFunctionCalculator.calcCombin(operands); - break; - - case FunctionConstants.xlfCos: - resultPtg = MathFunctionCalculator.calcCos(operands); - break; - - case FunctionConstants.xlfCosh: - resultPtg = MathFunctionCalculator.calcCosh(operands); - break; - - case FunctionConstants.xlfDegrees: - resultPtg = MathFunctionCalculator.calcDegrees(operands); - break; - - case FunctionConstants.xlfEven: - resultPtg = MathFunctionCalculator.calcEven(operands); - break; - - case FunctionConstants.xlfFact: - resultPtg = MathFunctionCalculator.calcFact(operands); - break; - - case FunctionConstants.xlfDOUBLEFACT: - resultPtg = MathFunctionCalculator.calcFactDouble(operands); - break; - - case FunctionConstants.xlfFloor: - resultPtg = MathFunctionCalculator.calcFloor(operands); - break; - - case FunctionConstants.xlfGCD: - resultPtg = MathFunctionCalculator.calcGCD(operands); - break; - - case FunctionConstants.xlfInt: - resultPtg = MathFunctionCalculator.calcInt(operands); - break; - - case FunctionConstants.xlfLCM: - resultPtg = MathFunctionCalculator.calcLCM(operands); - break; - - case FunctionConstants.xlfMROUND: - resultPtg = MathFunctionCalculator.calcMRound(operands); - break; - - case FunctionConstants.xlfMmult: - resultPtg = MathFunctionCalculator.calcMMult(operands); - break; - - case FunctionConstants.xlfMULTINOMIAL: - resultPtg = MathFunctionCalculator.calcMultinomial(operands); - break; - - case FunctionConstants.xlfLn: - resultPtg = MathFunctionCalculator.calcLn(operands); - break; - - case FunctionConstants.xlfLog: - resultPtg = MathFunctionCalculator.calcLog(operands); - break; - - case FunctionConstants.xlfLog10: - resultPtg = MathFunctionCalculator.calcLog10(operands); - break; - - case FunctionConstants.xlfMod: - resultPtg = MathFunctionCalculator.calcMod(operands); - break; - - case FunctionConstants.xlfOdd: - resultPtg = MathFunctionCalculator.calcOdd(operands); - break; - - case FunctionConstants.xlfPi: - resultPtg = MathFunctionCalculator.calcPi(operands); - break; - - case FunctionConstants.xlfPower: - resultPtg = MathFunctionCalculator.calcPower(operands); - break; - - case FunctionConstants.xlfProduct: - resultPtg = MathFunctionCalculator.calcProduct(operands); - break; - - case FunctionConstants.xlfQUOTIENT: - resultPtg = MathFunctionCalculator.calcQuotient(operands); - break; - - case FunctionConstants.xlfRadians: - resultPtg = MathFunctionCalculator.calcRadians(operands); - break; - - case FunctionConstants.xlfRand: - resultPtg = MathFunctionCalculator.calcRand(operands); - break; - - case FunctionConstants.xlfRANDBETWEEN: - resultPtg = MathFunctionCalculator.calcRandBetween(operands); - break; - - case FunctionConstants.xlfRoman: - resultPtg = MathFunctionCalculator.calcRoman(operands); - break; - - case FunctionConstants.xlfRound: - resultPtg = MathFunctionCalculator.calcRound(operands); - break; - - case FunctionConstants.xlfRounddown: - resultPtg = MathFunctionCalculator.calcRoundDown(operands); - break; - - case FunctionConstants.xlfRoundup: - resultPtg = MathFunctionCalculator.calcRoundUp(operands); - break; - - case FunctionConstants.xlfSign: - resultPtg = MathFunctionCalculator.calcSign(operands); - break; - - case FunctionConstants.xlfSin: - resultPtg = MathFunctionCalculator.calcSin(operands); - break; - - case FunctionConstants.xlfSinh: - resultPtg = MathFunctionCalculator.calcSinh(operands); - break; - - case FunctionConstants.xlfSqrt: - resultPtg = MathFunctionCalculator.calcSqrt(operands); - break; - - case FunctionConstants.xlfSQRTPI: - resultPtg = MathFunctionCalculator.calcSqrtPi(operands); - break; - - case FunctionConstants.xlfTan: - resultPtg = MathFunctionCalculator.calcTan(operands); - break; - - case FunctionConstants.xlfTanh: - resultPtg = MathFunctionCalculator.calcTanh(operands); - break; - - case FunctionConstants.xlfTrunc: - resultPtg = MathFunctionCalculator.calcTrunc(operands); - break; - - /******************************************** - * Statistical functions ***** - ********************************************/ - - case FunctionConstants.XLF_COUNT: - resultPtg = StatisticalCalculator.calcCount(operands); - break; - - case FunctionConstants.xlfCounta: - resultPtg = StatisticalCalculator.calcCountA(operands); - break; - - case FunctionConstants.xlfCountblank: - resultPtg = StatisticalCalculator.calcCountBlank(operands); - break; - - case FunctionConstants.xlfCountif: - resultPtg = StatisticalCalculator.calcCountif(operands); - break; - - case FunctionConstants.xlfCOUNTIFS: - resultPtg = StatisticalCalculator.calcCountIfS(operands); - break; - - case FunctionConstants.XLF_MIN: - resultPtg = StatisticalCalculator.calcMin(operands); - break; - - case FunctionConstants.xlfMinA: - resultPtg = StatisticalCalculator.calcMinA(operands); - break; - - case FunctionConstants.XLF_MAX: - resultPtg = StatisticalCalculator.calcMax(operands); - break; - - case FunctionConstants.xlfMaxA: - resultPtg = StatisticalCalculator.calcMaxA(operands); - break; - - case FunctionConstants.xlfNormdist: - resultPtg = StatisticalCalculator.calcNormdist(operands); - break; - - case FunctionConstants.xlfNormsdist: - resultPtg = StatisticalCalculator.calcNormsdist(operands); - break; - - case FunctionConstants.xlfNormsinv: - resultPtg = StatisticalCalculator.calcNormsInv(operands); - break; - - case FunctionConstants.xlfNorminv: - resultPtg = StatisticalCalculator.calcNormInv(operands); - break; - - case FunctionConstants.XLF_AVERAGE: - resultPtg = StatisticalCalculator.calcAverage(operands); - break; - - case FunctionConstants.xlfAVERAGEIF: - resultPtg = StatisticalCalculator.calcAverageIf(operands); - break; - - case FunctionConstants.xlfAVERAGEIFS: - resultPtg = StatisticalCalculator.calcAverageIfS(operands); - break; - - case FunctionConstants.xlfAvedev: - resultPtg = StatisticalCalculator.calcAveDev(operands); - break; - - case FunctionConstants.xlfAverageA: - resultPtg = StatisticalCalculator.calcAverageA(operands); - break; - - case FunctionConstants.xlfMedian: - resultPtg = StatisticalCalculator.calcMedian(operands); - break; - - case FunctionConstants.xlfMode: - resultPtg = StatisticalCalculator.calcMode(operands); - break; - - case FunctionConstants.xlfQuartile: - resultPtg = StatisticalCalculator.calcQuartile(operands); - break; - - case FunctionConstants.xlfRank: - resultPtg = StatisticalCalculator.calcRank(operands); - break; - - case FunctionConstants.xlfStdev: - resultPtg = StatisticalCalculator.calcStdev(operands); - break; - - case FunctionConstants.xlfVar: - resultPtg = StatisticalCalculator.calcVar(operands); - break; - - case FunctionConstants.xlfVarp: - resultPtg = StatisticalCalculator.calcVarp(operands); - break; - - case FunctionConstants.xlfCovar: - resultPtg = StatisticalCalculator.calcCovar(operands); - break; - - case FunctionConstants.xlfCorrel: - resultPtg = StatisticalCalculator.calcCorrel(operands); - break; - - case FunctionConstants.xlfFrequency: - resultPtg = StatisticalCalculator.calcFrequency(operands); - break; - - case FunctionConstants.xlfLinest: - resultPtg = StatisticalCalculator.calcLineSt(operands); - break; - - case FunctionConstants.xlfSlope: - resultPtg = StatisticalCalculator.calcSlope(operands); - break; - - case FunctionConstants.xlfIntercept: - resultPtg = StatisticalCalculator.calcIntercept(operands); - break; - - case FunctionConstants.xlfPearson: - resultPtg = StatisticalCalculator.calcPearson(operands); - break; - - case FunctionConstants.xlfRsq: - resultPtg = StatisticalCalculator.calcRsq(operands); - break; - - case FunctionConstants.xlfSteyx: - resultPtg = StatisticalCalculator.calcSteyx(operands); - break; - - case FunctionConstants.xlfForecast: - resultPtg = StatisticalCalculator.calcForecast(operands); - break; - - case FunctionConstants.xlfTrend: - resultPtg = StatisticalCalculator.calcTrend(operands); - break; - - case FunctionConstants.xlfLarge: - resultPtg = StatisticalCalculator.calcLarge(operands); - break; - - case FunctionConstants.xlfSmall: - resultPtg = StatisticalCalculator.calcSmall(operands); - break; - - /******************************************** - * Text functions ***** - ********************************************/ - /* - * these DBCS functions are not working yet - case FunctionConstants.xlfAsc: - resultPtg= TextCalculator.calcAsc(operands); - break; - - case FunctionConstants.xlfDbcs: - resultPtg= TextCalculator.calcJIS(operands); - break; - */ - case FunctionConstants.xlfChar: - resultPtg = TextCalculator.calcChar(operands); - break; - - case FunctionConstants.xlfClean: - resultPtg = TextCalculator.calcClean(operands); - break; - - case FunctionConstants.xlfCode: - resultPtg = TextCalculator.calcCode(operands); - break; - - case FunctionConstants.xlfConcatenate: - resultPtg = TextCalculator.calcConcatenate(operands); - break; - - case FunctionConstants.xlfDollar: - resultPtg = TextCalculator.calcDollar(operands); - break; - - case FunctionConstants.xlfExact: - resultPtg = TextCalculator.calcExact(operands); - break; - - case FunctionConstants.xlfFind: - resultPtg = TextCalculator.calcFind(operands); - break; - - // DBCS functions are not working 100% yet - case FunctionConstants.xlfFindb: - resultPtg = TextCalculator.calcFindB(operands); - break; - - case FunctionConstants.xlfFixed: - resultPtg = TextCalculator.calcFixed(operands); - break; - - case FunctionConstants.xlfLeft: - resultPtg = TextCalculator.calcLeft(operands); - break; - - case FunctionConstants.xlfLeftb: - resultPtg = TextCalculator.calcLeftB(operands); - break; - - case FunctionConstants.xlfLen: - resultPtg = TextCalculator.calcLen(operands); - break; - - case FunctionConstants.xlfLenb: - resultPtg = TextCalculator.calcLenB(operands); - break; - - case FunctionConstants.xlfLower: - resultPtg = TextCalculator.calcLower(operands); - break; - - case FunctionConstants.xlfUpper: - resultPtg = TextCalculator.calcUpper(operands); - break; - - case FunctionConstants.xlfMid: - resultPtg = TextCalculator.calcMid(operands); - break; - - case FunctionConstants.xlfProper: - resultPtg = TextCalculator.calcProper(operands); - break; - - case FunctionConstants.xlfReplace: - resultPtg = TextCalculator.calcReplace(operands); - break; - - case FunctionConstants.xlfRept: - resultPtg = TextCalculator.calcRept(operands); - break; - - case FunctionConstants.xlfRight: - resultPtg = TextCalculator.calcRight(operands); - break; - - case FunctionConstants.xlfSearch: - resultPtg = TextCalculator.calcSearch(operands); - break; - - case FunctionConstants.xlfSearchb: - resultPtg = TextCalculator.calcSearchB(operands); - break; - - case FunctionConstants.xlfSubstitute: - resultPtg = TextCalculator.calcSubstitute(operands); - break; - - case FunctionConstants.xlfT: - resultPtg = TextCalculator.calcT(operands); - break; - - case FunctionConstants.xlfTrim: - resultPtg = TextCalculator.calcTrim(operands); - break; - - case FunctionConstants.xlfText: - resultPtg = TextCalculator.calcText(operands); - break; - - - case FunctionConstants.xlfValue: - resultPtg = TextCalculator.calcValue(operands); - break; - - - default: - String s = FunctionConstants.getFunctionString((short) functionId); - if (s != null && !s.equals("")) - s = s.substring(0, s.length() - 1); - else - s = Integer.toHexString(functionId); - //throw new FunctionNotSupportedException( (!.equals(""))?FunctionConstants.getFunctionString((short)funkId).substring(0, ):Integer.toHexString((int)funkId)); - throw new FunctionNotSupportedException(s); // 20081118 KSC: add a little more info ... - - } - - - return resultPtg; - } - - /**************************************************************************** - * * - * The following section is made up of the calcuations for each of the * - * function types. These map directly to the name of the function * - * declared in the header and is called from the coresponding switch * - * statement above. ENJOY! * - * * - *****************************************************************************/ - - - /**************************************** - * * - * Excel function numbers * - * * - ****************************************/ -/* - public static final int xlfCount = 0; - public static final int xlfIf = 1; - public static final int xlfIsna = 2; - public static final int xlfIserror = 3; - public static final int xlfSum = 4; - public static final int xlfAverage = 5; - public static final int xlfMin = 6; - public static final int xlfMax = 7; - public static final int xlfRow = 8; - public static final int xlfColumn = 9; - public static final int xlfNa = 10; - public static final int xlfNpv = 11; - public static final int xlfStdev = 12; - public static final int xlfDollar = 13; - public static final int xlfFixed = 14; - public static final int xlfSin = 15; - public static final int xlfCos = 16; - public static final int xlfTan = 17; - public static final int xlfAtan = 18; - public static final int xlfPi = 19; - public static final int xlfSqrt = 20; - public static final int xlfExp = 21; - public static final int xlfLn = 22; - public static final int xlfLog10 = 23; - public static final int xlfAbs = 24; - public static final int xlfInt = 25; - public static final int xlfSign = 26; - public static final int xlfRound = 27; - public static final int xlfLookup = 28; - public static final int xlfIndex = 29; - public static final int xlfRept = 30; - public static final int xlfMid = 31; - public static final int xlfLen = 32; - public static final int xlfValue = 33; - public static final int xlfTrue = 34; - public static final int xlfFalse = 35; - public static final int xlfAnd = 36; - public static final int xlfOr = 37; - public static final int xlfNot = 38; - public static final int xlfMod = 39; - public static final int xlfDcount = 40; - public static final int xlfDsum = 41; - public static final int xlfDaverage = 42; - public static final int xlfDmin = 43; - public static final int xlfDmax = 44; - public static final int xlfDstdev = 45; - public static final int xlfVar = 46; - public static final int xlfDvar = 47; - public static final int xlfText = 48; - public static final int xlfLinest = 49; - public static final int xlfTrend = 50; - public static final int xlfLogest = 51; - public static final int xlfGrowth = 52; - public static final int xlfGoto = 53; - public static final int xlfHalt = 54; - public static final int xlfPv = 56; - public static final int xlfFv = 57; - public static final int xlfNper = 58; - public static final int xlfPmt = 59; - public static final int xlfRate = 60; - public static final int xlfMirr = 61; - public static final int xlfIrr = 62; - public static final int xlfRand = 63; - public static final int xlfMatch = 64; - public static final int xlfDate = 65; - public static final int xlfTime = 66; - public static final int xlfDay = 67; - public static final int xlfMonth = 68; - public static final int xlfYear = 69; - public static final int xlfWeekday = 70; - public static final int xlfHour = 71; - public static final int xlfMinute = 72; - public static final int xlfSecond = 73; - public static final int xlfNow = 74; - public static final int xlfAreas = 75; - public static final int xlfRows = 76; - public static final int xlfColumns = 77; - public static final int xlfOffset = 78; - public static final int xlfAbsref = 79; - public static final int xlfRelref = 80; - public static final int xlfArgument = 81; - public static final int xlfSearch = 82; - public static final int xlfTranspose = 83; - public static final int xlfError = 84; - public static final int xlfStep = 85; - public static final int xlfType = 86; - public static final int xlfEcho = 87; - public static final int xlfSetName = 88; - public static final int xlfCaller = 89; - public static final int xlfDeref = 90; - public static final int xlfWindows = 91; - public static final int xlfSeries = 92; - public static final int xlfDocuments = 93; - public static final int xlfActiveCell = 94; - public static final int xlfSelection = 95; - public static final int xlfResult = 96; - public static final int xlfAtan2 = 97; - public static final int xlfAsin = 98; - public static final int xlfAcos = 99; - public static final int xlfChoose = 100; - public static final int xlfHlookup = 101; - public static final int xlfVlookup = 102; - public static final int xlfLinks = 103; - public static final int xlfInput = 104; - public static final int xlfIsref = 105; - public static final int xlfGetFormula = 106; - public static final int xlfGetName = 107; - public static final int xlfSetValue = 108; - public static final int xlfLog = 109; - public static final int xlfExec = 110; - public static final int xlfChar = 111; - public static final int xlfLower = 112; - public static final int xlfUpper = 113; - public static final int xlfProper = 114; - public static final int xlfLeft = 115; - public static final int xlfRight = 116; - public static final int xlfExact = 117; - public static final int xlfTrim = 118; - public static final int xlfReplace = 119; - public static final int xlfSubstitute = 120; - public static final int xlfCode = 121; - public static final int xlfNames = 122; - public static final int xlfDirectory = 123; - public static final int xlfFind = 124; - public static final int xlfCell = 125; - public static final int xlfIserr = 126; - public static final int xlfIstext = 127; - public static final int xlfIsnumber = 128; - public static final int xlfIsblank = 129; - public static final int xlfT = 130; - public static final int xlfN = 131; - public static final int xlfFopen = 132; - public static final int xlfFclose = 133; - public static final int xlfFsize = 134; - public static final int xlfFreadln = 135; - public static final int xlfFread = 136; - public static final int xlfFwriteln = 137; - public static final int xlfFwrite = 138; - public static final int xlfFpos = 139; - public static final int xlfDatevalue = 140; - public static final int xlfTimevalue = 141; - public static final int xlfSln = 142; - public static final int xlfSyd = 143; - public static final int xlfDdb = 144; - public static final int xlfGetDef = 145; - public static final int xlfReftext = 146; - public static final int xlfTextref = 147; - public static final int xlfIndirect = 148; - public static final int xlfRegister = 149; - public static final int xlfCall = 150; - public static final int xlfAddBar = 151; - public static final int xlfAddMenu = 152; - public static final int xlfAddCommand = 153; - public static final int xlfEnableCommand = 154; - public static final int xlfCheckCommand = 155; - public static final int xlfRenameCommand = 156; - public static final int xlfShowBar = 157; - public static final int xlfDeleteMenu = 158; - public static final int xlfDeleteCommand = 159; - public static final int xlfGetChartItem = 160; - public static final int xlfDialogBox = 161; - public static final int xlfClean = 162; - public static final int xlfMdeterm = 163; - public static final int xlfMinverse = 164; - public static final int xlfMmult = 165; - public static final int xlfFiles = 166; - public static final int xlfIpmt = 167; - public static final int xlfPpmt = 168; - public static final int xlfCounta = 169; - public static final int xlfCancelKey = 170; - public static final int xlfInitiate = 175; - public static final int xlfRequest = 176; - public static final int xlfPoke = 177; - public static final int xlfExecute = 178; - public static final int xlfTerminate = 179; - public static final int xlfRestart = 180; - public static final int xlfHelp = 181; - public static final int xlfGetBar = 182; - public static final int xlfProduct = 183; - public static final int xlfFact = 184; - public static final int xlfGetCell = 185; - public static final int xlfGetWorkspace = 186; - public static final int xlfGetWindow = 187; - public static final int xlfGetDocument = 188; - public static final int xlfDproduct = 189; - public static final int xlfIsnontext = 190; - public static final int xlfGetNote = 191; - public static final int xlfNote = 192; - public static final int xlfStdevp = 193; - public static final int xlfVarp = 194; - public static final int xlfDstdevp = 195; - public static final int xlfDvarp = 196; - public static final int xlfTrunc = 197; - public static final int xlfIslogical = 198; - public static final int xlfDcounta = 199; - public static final int xlfDeleteBar = 200; - public static final int xlfUnregister = 201; - public static final int xlfUsdollar = 204; - public static final int xlfFindb = 205; - public static final int xlfSearchb = 206; - public static final int xlfReplaceb = 207; - public static final int xlfLeftb = 208; - public static final int xlfRightb = 209; - public static final int xlfMidb = 210; - public static final int xlfLenb = 211; - public static final int xlfRoundup = 212; - public static final int xlfRounddown = 213; - public static final int xlfAsc = 214; - public static final int xlfDbcs = 215; - public static final int xlfRank = 216; - public static final int xlfAddress = 219; - public static final int xlfDays360 = 220; - public static final int xlfToday = 221; - public static final int xlfVdb = 222; - public static final int xlfMedian = 227; - public static final int xlfSumproduct = 228; - public static final int xlfSinh = 229; - public static final int xlfCosh = 230; - public static final int xlfTanh = 231; - public static final int xlfAsinh = 232; - public static final int xlfAcosh = 233; - public static final int xlfAtanh = 234; - public static final int xlfDget = 235; - public static final int xlfCreateObject = 236; - public static final int xlfVolatile = 237; - public static final int xlfLastError = 238; - public static final int xlfCustomUndo = 239; - public static final int xlfCustomRepeat = 240; - public static final int xlfFormulaConvert = 241; - public static final int xlfGetLinkInfo = 242; - public static final int xlfTextBox = 243; - public static final int xlfInfo = 244; - public static final int xlfGroup = 245; - public static final int xlfGetObject = 246; - public static final int xlfDb = 247; - public static final int xlfPause = 248; - public static final int xlfResume = 251; - public static final int xlfFrequency = 252; - public static final int xlfAddToolbar = 253; - public static final int xlfDeleteToolbar = 254; - public static final int xlfADDIN = 255; // KSC: Added; Excel function ID for add-ins - public static final int xlfResetToolbar = 256; - public static final int xlfEvaluate = 257; - public static final int xlfGetToolbar = 258; - public static final int xlfGetTool = 259; - public static final int xlfSpellingCheck = 260; - public static final int xlfErrorType = 261; - public static final int xlfAppTitle = 262; - public static final int xlfWindowTitle = 263; - public static final int xlfSaveToolbar = 264; - public static final int xlfEnableTool = 265; - public static final int xlfPressTool = 266; - public static final int xlfRegisterId = 267; - public static final int xlfGetWorkbook = 268; - public static final int xlfAvedev = 269; - public static final int xlfBetadist = 270; - public static final int xlfGammaln = 271; - public static final int xlfBetainv = 272; - public static final int xlfBinomdist = 273; - public static final int xlfChidist = 274; - public static final int xlfChiinv = 275; - public static final int xlfCombin = 276; - public static final int xlfConfidence = 277; - public static final int xlfCritbinom = 278; - public static final int xlfEven = 279; - public static final int xlfExpondist = 280; - public static final int xlfFdist = 281; - public static final int xlfFinv = 282; - public static final int xlfFisher = 283; - public static final int xlfFisherinv = 284; - public static final int xlfFloor = 285; - public static final int xlfGammadist = 286; - public static final int xlfGammainv = 287; - public static final int xlfCeiling = 288; - public static final int xlfHypgeomdist = 289; - public static final int xlfLognormdist = 290; - public static final int xlfLoginv = 291; - public static final int xlfNegbinomdist = 292; - public static final int xlfNormdist = 293; - public static final int xlfNormsdist = 294; - public static final int xlfNorminv = 295; - public static final int xlfNormsinv = 296; - public static final int xlfStandardize = 297; - public static final int xlfOdd = 298; - public static final int xlfPermut = 299; - public static final int xlfPoisson = 300; - public static final int xlfTdist = 301; - public static final int xlfWeibull = 302; - public static final int xlfSumxmy2 = 303; - public static final int xlfSumx2my2 = 304; - public static final int xlfSumx2py2 = 305; - public static final int xlfChitest = 306; - public static final int xlfCorrel = 307; - public static final int xlfCovar = 308; - public static final int xlfForecast = 309; - public static final int xlfFtest = 310; - public static final int xlfIntercept = 311; - public static final int xlfPearson = 312; - public static final int xlfRsq = 313; - public static final int xlfSteyx = 314; - public static final int xlfSlope = 315; - public static final int xlfTtest = 316; - public static final int xlfProb = 317; - public static final int xlfDevsq = 318; - public static final int xlfGeomean = 319; - public static final int xlfHarmean = 320; - public static final int xlfSumsq = 321; - public static final int xlfKurt = 322; - public static final int xlfSkew = 323; - public static final int xlfZtest = 324; - public static final int xlfLarge = 325; - public static final int xlfSmall = 326; - public static final int xlfQuartile = 327; - public static final int xlfPercentile = 328; - public static final int xlfPercentrank = 329; - public static final int xlfMode = 330; - public static final int xlfTrimmean = 331; - public static final int xlfTinv = 332; - public static final int xlfMovieCommand = 334; - public static final int xlfGetMovie = 335; - public static final int xlfConcatenate = 336; - public static final int xlfPower = 337; - public static final int xlfPivotAddData = 338; - public static final int xlfGetPivotTable = 339; - public static final int xlfGetPivotField = 340; - public static final int xlfGetPivotItem = 341; - public static final int xlfRadians = 342; - public static final int xlfDegrees = 343; - public static final int xlfSubtotal = 344; - public static final int xlfSumif = 345; - public static final int xlfCountif = 346; - public static final int xlfCountblank = 347; - public static final int xlfScenarioGet = 348; - public static final int xlfOptionsListsGet = 349; - public static final int xlfIspmt = 350; - public static final int xlfDatedif = 351; - public static final int xlfDatestring = 352; - public static final int xlfNumberstring = 353; - public static final int xlfRoman = 354; - public static final int xlfOpenDialog = 355; - public static final int xlfSaveDialog = 356; - public static final int xlfViewGet = 357; - public static final int xlfGetPivotData = 358; - public static final int xlfHyperlink = 359; - public static final int xlfPhonetic = 360; - public static final int xlfAverageA = 361; - public static final int xlfMaxA = 362; - public static final int xlfMinA = 363; - public static final int xlfStDevPA = 364; - public static final int xlfVarPA = 365; - public static final int xlfStDevA = 366; - public static final int xlfVarA = 367; - // KSC: ADD-IN formulas - use any index; name must be present in FunctionConstants.addIns - // Financial Formulas - public static final int xlfAccrintm= 368; - public static final int xlfAccrint= 369; - public static final int xlfCoupDayBS= 370; - public static final int xlfCoupDays= 371; - public static final int xlfCumIPmt= 372; - public static final int xlfCumPrinc= 373; - public static final int xlfCoupNCD= 374; - public static final int xlfCoupDaysNC= 375; - public static final int xlfCoupPCD= 376; - public static final int xlfCoupNUM= 377; - public static final int xlfDollarDE= 378; - public static final int xlfDollarFR= 379; - public static final int xlfEffect= 380; - public static final int xlfINTRATE= 381; - public static final int xlfXIRR= 382; - public static final int xlfXNPV= 383; - public static final int xlfYIELD= 384; - public static final int xlfPRICE= 385; - public static final int xlfPRICEDISC= 386; - public static final int xlfPRICEMAT= 387; - public static final int xlfDURATION= 388; - public static final int xlfMDURATION= 389; - public static final int xlfTBillEq= 390; - public static final int xlfTBillPrice= 391; - public static final int xlfTBillYield= 392; - public static final int xlfYieldDisc= 393; - public static final int xlfYieldMat= 394; - public static final int xlfFVSchedule= 395; - public static final int xlfAmorlinc= 396; - public static final int xlfAmordegrc= 397; - public static final int xlfOddFPrice= 398; - public static final int xlfOddLPrice= 399; - public static final int xlfOddFYield= 400; - public static final int xlfOddLYield= 401; - public static final int xlfNOMINAL= 402; - public static final int xlfDISC= 403; - public static final int xlfRECEIVED= 404; - // Engineering Formulas - public static final int xlfBIN2DEC= 405; - public static final int xlfBIN2HEX= 406; - public static final int xlfBIN2OCT= 407; - public static final int xlfDEC2BIN= 408; - public static final int xlfDEC2HEX= 409; - public static final int xlfDEC2OCT= 410; - public static final int xlfHEX2BIN= 411; - public static final int xlfHEX2DEC= 412; - public static final int xlfHEX2OCT= 413; - public static final int xlfOCT2BIN= 414; - public static final int xlfOCT2DEC= 415; - public static final int xlfOCT2HEX= 416; - public static final int xlfCOMPLEX= 417; - public static final int xlfGESTEP= 418; - public static final int xlfDELTA= 419; - public static final int xlfIMAGINARY= 420; - public static final int xlfIMABS= 421; - public static final int xlfIMDIV= 422; - public static final int xlfIMCONJUGATE= 423; - public static final int xlfIMCOS= 424; - public static final int xlfIMSIN= 425; - public static final int xlfIMREAL= 426; - public static final int xlfIMEXP= 427; - public static final int xlfIMSUB= 428; - public static final int xlfIMSUM= 429; - public static final int xlfIMPRODUCT= 430; - public static final int xlfIMLN= 431; - public static final int xlfIMLOG10= 432; - public static final int xlfIMLOG2= 433; - public static final int xlfIMPOWER= 434; - public static final int xlfIMSQRT= 435; - public static final int xlfIMARGUMENT= 436; - public static final int xlfCONVERT= 437; - // Math Add-in Formulas - public static final int xlfDOUBLEFACT= 438; - public static final int xlfGCD= 439; - public static final int xlfLCM= 440; - public static final int xlfMROUND= 441; - public static final int xlfMULTINOMIAL= 442; - public static final int xlfQUOTIENT= 443; - public static final int xlfRANDBETWEEN= 444; - public static final int xlfSERIESSUM= 445; - public static final int xlfSQRTPI= 446; -*/ -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.kt b/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.kt new file mode 100644 index 0000000..2786ae8 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/FunctionHandler.kt @@ -0,0 +1,1253 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.formats.XLS.XLSRecord + +import java.util.Locale + +/** + * Function Handler takes an array of PTG's with a header PtgFunc or PtgFuncVar, calcuates + * those ptgs in the determined way, then return a relevant PtgValue + * + * + * Descriptions of these functions are available on the msdn site, + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/office97/html/S88F9.asp + */ + + +object FunctionHandler { + + + /* + Calculates the function and returns a relevant Ptg as a value + This is the main entry method. + + */ + @Throws(FunctionNotSupportedException::class, CalculationException::class) + fun calculateFunction(ptgs: Array): Ptg? { + val funk: Ptg // the function identifier + val operands: Array // the ptgs acted upon by the function + val funkId = 0 //what function are we calling? + + funk = ptgs[0] + // if ptgs are missing parent_recs, populate from funk + val bpar = funk.parentRec + if (bpar != null) { + for (t in ptgs.indices) { + if (ptgs[t].parentRec == null) + ptgs[t].parentRec = bpar + } + } + + val oplen = ptgs.size - 1 + operands = arrayOfNulls(oplen) + System.arraycopy(ptgs, 1, operands, 0, oplen) + if (funk.opcode.toInt() == 0x21 || funk.opcode.toInt() == 0x41 || funk.opcode.toInt() == 0x61) { // ptgfunc + return calculatePtgFunc(funk, funkId, operands) + } else if (funk.opcode.toInt() == 0x22 || funk.opcode.toInt() == 0x42 || funk.opcode.toInt() == 0x62) { // ptgfuncvar + return calculatePtgFuncVar(funk, funkId, operands) + } + return null + } + + /* + Keep the calculation of ptgfunc & ptgfuncvar seperate in case any differences show up + */ + @Throws(FunctionNotSupportedException::class, CalculationException::class) + fun calculatePtgFunc(funk: Ptg, funkId: Int, operands: Array): Ptg? { + var funkId = funkId + val pf = funk as PtgFunc + funkId = pf.`val` + return parse_n_calc(funk, funkId, operands) + } + + + /** + * Keep the calculation of ptgfunc & ptgfuncvar seperate in case any differences show up + * + * @throws CalculationException + */ + @Throws(FunctionNotSupportedException::class, CalculationException::class) + fun calculatePtgFuncVar(funk: Ptg, funkId: Int, operands: Array): Ptg? { + var funkId = funkId + var operands = operands + val pf = funk as PtgFuncVar + funkId = pf.`val` + // Handle Add-in Formulas - which have a name operand 1st + if (funkId == FunctionConstants.xlfADDIN) { // XL flag that formula is an add-in + // must pop the PtgNameX record to get the correct function id + var s = "" + var foundit = false + if (operands[0] is PtgNameX) { + val index = (operands[0] as PtgNameX).`val` + s = pf.parentRec!!.sheet!!.workBook!!.getExternalName(index) + } else if (operands[0] is PtgName) { + s = (operands[0] as PtgName).storedName + } + if (s.startsWith("_xlfn.")) { // Excel "new" functions + s = s.substring(6) + } + if (Locale.JAPAN == Locale.getDefault()) { + var y = 0 + while (y < FunctionConstants.jRecArr.size) { + if (s.equals(FunctionConstants.jRecArr[y][0], ignoreCase = true)) { + funkId = Integer.valueOf(FunctionConstants.jRecArr[y][1]).toInt() + y = FunctionConstants.jRecArr.size // exit loop + foundit = true + } + y++ + } + } + if (!foundit) { + var y = 0 + while (y < FunctionConstants.recArr.size) { // Use FunctionConstants instead of PtFuncVar + if (s.equals(FunctionConstants.recArr[y][0], ignoreCase = true)) { + funkId = Integer.valueOf(FunctionConstants.recArr[y][1]).toInt() + y = FunctionConstants.recArr.size // exit loop + } + y++ + } + } + if (funkId == 255) + // it's not found + throw FunctionNotSupportedException(s) + + // now get rid of PtgNameX operand before calling function + val ops = arrayOfNulls(operands.size - 1) + System.arraycopy(operands, 1, ops, 0, operands.size - 1) + operands = arrayOfNulls(ops.size) + System.arraycopy(ops, 0, operands, 0, ops.size) + } // end KSC added + return parse_n_calc(funk, funkId, operands) + } + + + /************************************************************************************ + * * + * Your standard big case statement, calling methods based on what the funkid is. * + * You will notice that these are seperated out into packages based on the MS * + * documentation (Link above). Each package calls a different class full of * + * static method calls. There are a lot of these :-) * + * * + * PLEASE: Remove function from comment list when you enable it!!!! * + * * + * @throws CalculationException + */ + @Throws(FunctionNotSupportedException::class, CalculationException::class) + fun parse_n_calc(function: Ptg, functionId: Int, operands: Array): Ptg? { + var operands = operands + var resultPtg: Ptg? = null + val resultArrPtg: Array? = null + + when (functionId) { + /******************************************** + * Database and List package functions ** + */ + FunctionConstants.xlfDaverage -> resultPtg = DatabaseCalculator.calcDAverage(operands) + + FunctionConstants.xlfDcount -> resultPtg = DatabaseCalculator.calcDCount(operands) + + FunctionConstants.xlfDcounta -> resultPtg = DatabaseCalculator.calcDCountA(operands) + + FunctionConstants.xlfDget -> resultPtg = DatabaseCalculator.calcDGet(operands) + + FunctionConstants.xlfDmax -> resultPtg = DatabaseCalculator.calcDMax(operands) + + FunctionConstants.xlfDmin -> resultPtg = DatabaseCalculator.calcDMin(operands) + + FunctionConstants.xlfDproduct -> resultPtg = DatabaseCalculator.calcDProduct(operands) + + FunctionConstants.xlfDstdev -> resultPtg = DatabaseCalculator.calcDStdDev(operands) + + FunctionConstants.xlfDstdevp -> resultPtg = DatabaseCalculator.calcDStdDevP(operands) + + FunctionConstants.xlfDsum -> resultPtg = DatabaseCalculator.calcDSum(operands) + + FunctionConstants.xlfDvar -> resultPtg = DatabaseCalculator.calcDVar(operands) + + FunctionConstants.xlfDvarp -> resultPtg = DatabaseCalculator.calcDVarP(operands) + + /******************************************** + * Date and time functions ********* + */ + FunctionConstants.xlfDate -> resultPtg = DateTimeCalculator.calcDate(operands) + + FunctionConstants.xlfDay -> resultPtg = DateTimeCalculator.calcDay(operands) + + FunctionConstants.xlfDays360 -> resultPtg = DateTimeCalculator.calcDays360(operands) + + FunctionConstants.xlfHour -> resultPtg = DateTimeCalculator.calcHour(operands) + + FunctionConstants.xlfMinute -> resultPtg = DateTimeCalculator.calcMinute(operands) + + FunctionConstants.xlfMonth -> resultPtg = DateTimeCalculator.calcMonth(operands) + + FunctionConstants.xlfYear -> resultPtg = DateTimeCalculator.calcYear(operands) + + FunctionConstants.xlfSecond -> resultPtg = DateTimeCalculator.calcSecond(operands) + + FunctionConstants.xlfTimevalue -> resultPtg = DateTimeCalculator.calcTimevalue(operands) + + FunctionConstants.xlfWeekday -> resultPtg = DateTimeCalculator.calcWeekday(operands) + + FunctionConstants.xlfWEEKNUM -> resultPtg = DateTimeCalculator.calcWeeknum(operands) + + FunctionConstants.xlfWORKDAY -> resultPtg = DateTimeCalculator.calcWorkday(operands) + + FunctionConstants.xlfYEARFRAC -> resultPtg = DateTimeCalculator.calcYearFrac(operands) + + FunctionConstants.xlfNow -> resultPtg = DateTimeCalculator.calcNow(operands) + + FunctionConstants.xlfTime -> resultPtg = DateTimeCalculator.calcTime(operands) + + FunctionConstants.xlfToday -> resultPtg = DateTimeCalculator.calcToday(operands) + + FunctionConstants.xlfDatevalue -> resultPtg = DateTimeCalculator.calcDateValue(operands) + + FunctionConstants.xlfEDATE -> resultPtg = DateTimeCalculator.calcEdate(operands) + + FunctionConstants.xlfEOMONTH -> resultPtg = DateTimeCalculator.calcEOMonth(operands) + + FunctionConstants.xlfNETWORKDAYS -> resultPtg = DateTimeCalculator.calcNetWorkdays(operands) + + /******************************************** + * DDE and External functions **** + */ + + + /******************************************** + * Engineering functions ********* + */ + FunctionConstants.xlfBIN2DEC -> resultPtg = EngineeringCalculator.calcBin2Dec(operands) + + FunctionConstants.xlfBIN2HEX -> resultPtg = EngineeringCalculator.calcBin2Hex(operands) + + FunctionConstants.xlfBIN2OCT -> resultPtg = EngineeringCalculator.calcBin2Oct(operands) + + FunctionConstants.xlfDEC2BIN -> resultPtg = EngineeringCalculator.calcDec2Bin(operands) + + FunctionConstants.xlfDEC2HEX -> resultPtg = EngineeringCalculator.calcDec2Hex(operands) + + FunctionConstants.xlfDEC2OCT -> resultPtg = EngineeringCalculator.calcDec2Oct(operands) + + FunctionConstants.xlfHEX2BIN -> resultPtg = EngineeringCalculator.calcHex2Bin(operands) + + FunctionConstants.xlfHEX2DEC -> resultPtg = EngineeringCalculator.calcHex2Dec(operands) + + FunctionConstants.xlfHEX2OCT -> resultPtg = EngineeringCalculator.calcHex2Oct(operands) + + FunctionConstants.xlfOCT2BIN -> resultPtg = EngineeringCalculator.calcOct2Bin(operands) + + FunctionConstants.xlfOCT2DEC -> resultPtg = EngineeringCalculator.calcOct2Dec(operands) + + FunctionConstants.xlfOCT2HEX -> resultPtg = EngineeringCalculator.calcOct2Hex(operands) + + FunctionConstants.xlfCOMPLEX -> resultPtg = EngineeringCalculator.calcComplex(operands) + + FunctionConstants.xlfGESTEP -> resultPtg = EngineeringCalculator.calcGEStep(operands) + + FunctionConstants.xlfDELTA -> resultPtg = EngineeringCalculator.calcDelta(operands) + + FunctionConstants.xlfIMAGINARY -> resultPtg = EngineeringCalculator.calcImaginary(operands) + + FunctionConstants.xlfIMREAL -> resultPtg = EngineeringCalculator.calcImReal(operands) + + FunctionConstants.xlfIMARGUMENT -> resultPtg = EngineeringCalculator.calcImArgument(operands) + + FunctionConstants.xlfIMABS -> resultPtg = EngineeringCalculator.calcImAbs(operands) + + FunctionConstants.xlfIMDIV -> resultPtg = EngineeringCalculator.calcImDiv(operands) + + FunctionConstants.xlfIMCONJUGATE -> resultPtg = EngineeringCalculator.calcImConjugate(operands) + + FunctionConstants.xlfIMCOS -> resultPtg = EngineeringCalculator.calcImCos(operands) + + FunctionConstants.xlfIMSIN -> resultPtg = EngineeringCalculator.calcImSin(operands) + + FunctionConstants.xlfIMEXP -> resultPtg = EngineeringCalculator.calcImExp(operands) + + FunctionConstants.xlfIMSUB -> resultPtg = EngineeringCalculator.calcImSub(operands) + + FunctionConstants.xlfIMSUM -> resultPtg = EngineeringCalculator.calcImSum(operands) + + FunctionConstants.xlfIMPRODUCT -> resultPtg = EngineeringCalculator.calcImProduct(operands) + + FunctionConstants.xlfIMLN -> resultPtg = EngineeringCalculator.calcImLn(operands) + + FunctionConstants.xlfIMLOG10 -> resultPtg = EngineeringCalculator.calcImLog10(operands) + + FunctionConstants.xlfIMLOG2 -> resultPtg = EngineeringCalculator.calcImLog2(operands) + + FunctionConstants.xlfIMPOWER -> resultPtg = EngineeringCalculator.calcImPower(operands) + + FunctionConstants.xlfIMSQRT -> resultPtg = EngineeringCalculator.calcImSqrt(operands) + + FunctionConstants.xlfCONVERT -> resultPtg = EngineeringCalculator.calcConvert(operands) + + FunctionConstants.xlfERF -> resultPtg = EngineeringCalculator.calcErf(operands) + + + /******************************************** + * Financial functions ********* + */ + + FunctionConstants.xlfDb -> resultPtg = FinancialCalculator.calcDB(operands) + + FunctionConstants.xlfDdb -> resultPtg = FinancialCalculator.calcDDB(operands) + + FunctionConstants.xlfPmt -> resultPtg = FinancialCalculator.calcPmt(operands) + + // KSC: Added + FunctionConstants.xlfAccrintm -> resultPtg = FinancialCalculator.calcAccrintm(operands) + + FunctionConstants.xlfAccrint -> resultPtg = FinancialCalculator.calcAccrint(operands) + + FunctionConstants.xlfCoupDayBS -> resultPtg = FinancialCalculator.calcCoupDayBS(operands) + + FunctionConstants.xlfCoupDays -> resultPtg = FinancialCalculator.calcCoupDays(operands) + + FunctionConstants.xlfNpv -> resultPtg = FinancialCalculator.calcNPV(operands) + + FunctionConstants.xlfPv -> resultPtg = FinancialCalculator.calcPV(operands) + + FunctionConstants.xlfFv -> resultPtg = FinancialCalculator.calcFV(operands) + + FunctionConstants.xlfIpmt -> resultPtg = FinancialCalculator.calcIPMT(operands) + + FunctionConstants.xlfCumIPmt -> resultPtg = FinancialCalculator.calcCumIPmt(operands) + + FunctionConstants.xlfCumPrinc -> resultPtg = FinancialCalculator.calcCumPrinc(operands) + + FunctionConstants.xlfCoupNCD -> resultPtg = FinancialCalculator.calcCoupNCD(operands) + + FunctionConstants.xlfCoupDaysNC -> resultPtg = FinancialCalculator.calcCoupDaysNC(operands) + + FunctionConstants.xlfCoupPCD -> resultPtg = FinancialCalculator.calcCoupPCD(operands) + + FunctionConstants.xlfCoupNUM -> resultPtg = FinancialCalculator.calcCoupNum(operands) + + FunctionConstants.xlfDollarDE -> resultPtg = FinancialCalculator.calcDollarDE(operands) + + FunctionConstants.xlfDollarFR -> resultPtg = FinancialCalculator.calcDollarFR(operands) + + FunctionConstants.xlfEffect -> resultPtg = FinancialCalculator.calcEffect(operands) + + FunctionConstants.xlfRECEIVED -> resultPtg = FinancialCalculator.calcReceived(operands) + + FunctionConstants.xlfINTRATE -> resultPtg = FinancialCalculator.calcINTRATE(operands) + + FunctionConstants.xlfIrr -> resultPtg = FinancialCalculator.calcIRR(operands) + + FunctionConstants.xlfMirr -> resultPtg = FinancialCalculator.calcMIRR(operands) + + FunctionConstants.xlfXIRR -> resultPtg = FinancialCalculator.calcXIRR(operands) + + FunctionConstants.xlfXNPV -> resultPtg = FinancialCalculator.calcXNPV(operands) + + FunctionConstants.xlfRate -> resultPtg = FinancialCalculator.calcRate(operands) + + FunctionConstants.xlfYIELD -> resultPtg = FinancialCalculator.calcYIELD(operands) + + FunctionConstants.xlfPRICE -> resultPtg = FinancialCalculator.calcPRICE(operands) + + FunctionConstants.xlfPRICEDISC -> resultPtg = FinancialCalculator.calcPRICEDISC(operands) + + FunctionConstants.xlfPRICEMAT -> resultPtg = FinancialCalculator.calcPRICEMAT(operands) + + FunctionConstants.xlfDISC -> resultPtg = FinancialCalculator.calcDISC(operands) + + FunctionConstants.xlfNper -> resultPtg = FinancialCalculator.calcNPER(operands) + + FunctionConstants.xlfSln -> resultPtg = FinancialCalculator.calcSLN(operands) + + FunctionConstants.xlfSyd -> resultPtg = FinancialCalculator.calcSYD(operands) + + FunctionConstants.xlfDURATION -> resultPtg = FinancialCalculator.calcDURATION(operands) + + FunctionConstants.xlfMDURATION -> resultPtg = FinancialCalculator.calcMDURATION(operands) + + FunctionConstants.xlfTBillEq -> resultPtg = FinancialCalculator.calcTBillEq(operands) + + FunctionConstants.xlfTBillPrice -> resultPtg = FinancialCalculator.calcTBillPrice(operands) + + FunctionConstants.xlfTBillYield -> resultPtg = FinancialCalculator.calcTBillYield(operands) + + FunctionConstants.xlfYieldDisc -> resultPtg = FinancialCalculator.calcYieldDisc(operands) + + FunctionConstants.xlfYieldMat -> resultPtg = FinancialCalculator.calcYieldMat(operands) + + FunctionConstants.xlfPpmt -> resultPtg = FinancialCalculator.calcPPMT(operands) + + FunctionConstants.xlfFVSchedule -> resultPtg = FinancialCalculator.calcFVSCHEDULE(operands) + + FunctionConstants.xlfIspmt -> resultPtg = FinancialCalculator.calcISPMT(operands) + + FunctionConstants.xlfAmorlinc -> resultPtg = FinancialCalculator.calcAmorlinc(operands) + + FunctionConstants.xlfAmordegrc -> resultPtg = FinancialCalculator.calcAmordegrc(operands) + + FunctionConstants.xlfOddFPrice -> resultPtg = FinancialCalculator.calcODDFPRICE(operands) + + FunctionConstants.xlfOddFYield -> resultPtg = FinancialCalculator.calcODDFYIELD(operands) + + FunctionConstants.xlfOddLPrice -> resultPtg = FinancialCalculator.calcODDLPRICE(operands) + + FunctionConstants.xlfOddLYield -> resultPtg = FinancialCalculator.calcODDLYIELD(operands) + + FunctionConstants.xlfNOMINAL -> resultPtg = FinancialCalculator.calcNominal(operands) + + FunctionConstants.xlfVdb -> resultPtg = FinancialCalculator.calcVDB(operands) + /******************************************** + * Information functions ********* + */ + + FunctionConstants.xlfCell -> resultPtg = InformationCalculator.calcCell(operands) + + FunctionConstants.xlfInfo -> resultPtg = InformationCalculator.calcInfo(operands) + + FunctionConstants.XLF_IS_NA -> resultPtg = InformationCalculator.calcIsna(operands) + + FunctionConstants.XLF_IS_ERROR -> resultPtg = InformationCalculator.calcIserror(operands) + + FunctionConstants.xlfIserr -> resultPtg = InformationCalculator.calcIserr(operands) + + FunctionConstants.xlfErrorType -> resultPtg = InformationCalculator.calcErrorType(operands) + + FunctionConstants.xlfNa -> resultPtg = InformationCalculator.calcNa(operands) + + FunctionConstants.xlfIsblank -> resultPtg = InformationCalculator.calcIsBlank(operands) + + FunctionConstants.xlfIslogical -> resultPtg = InformationCalculator.calcIsLogical(operands) + + FunctionConstants.xlfIsnontext -> resultPtg = InformationCalculator.calcIsNonText(operands) + + FunctionConstants.xlfIstext -> resultPtg = InformationCalculator.calcIsText(operands) + + FunctionConstants.xlfIsref -> resultPtg = InformationCalculator.calcIsRef(operands) + + FunctionConstants.xlfN -> resultPtg = InformationCalculator.calcN(operands) + + FunctionConstants.xlfIsnumber -> resultPtg = InformationCalculator.calcIsNumber(operands) + + FunctionConstants.xlfISEVEN -> resultPtg = InformationCalculator.calcIsEven(operands) + + FunctionConstants.xlfISODD -> resultPtg = InformationCalculator.calcIsOdd(operands) + + FunctionConstants.xlfType -> resultPtg = InformationCalculator.calcType(operands) + + /******************************************** + * Logical functions ***** + */ + FunctionConstants.xlfAnd -> resultPtg = LogicalCalculator.calcAnd(operands) + + FunctionConstants.xlfFalse -> resultPtg = LogicalCalculator.calcFalse(operands) + + FunctionConstants.xlfTrue -> resultPtg = LogicalCalculator.calcTrue(operands) + + FunctionConstants.XLF_IS -> resultPtg = LogicalCalculator.calcIf(operands) + + FunctionConstants.xlfNot -> resultPtg = LogicalCalculator.calcNot(operands) + + FunctionConstants.xlfOr -> resultPtg = LogicalCalculator.calcOr(operands) + + FunctionConstants.xlfIFERROR -> resultPtg = LogicalCalculator.calcIferror(operands) + + /******************************************** + * Lookup and reference functions ***** + */ + FunctionConstants.xlfAddress -> resultPtg = LookupReferenceCalculator.calcAddress(operands) + + FunctionConstants.xlfAreas -> resultPtg = LookupReferenceCalculator.calcAreas(operands) + + FunctionConstants.xlfChoose -> resultPtg = LookupReferenceCalculator.calcChoose(operands) + + FunctionConstants.xlfColumn -> { + if (operands.size == 0) { + operands = arrayOfNulls(1) + operands[0] = function + } + resultPtg = LookupReferenceCalculator.calcColumn(operands) + } + + FunctionConstants.xlfColumns -> resultPtg = LookupReferenceCalculator.calcColumns(operands) + + FunctionConstants.xlfHyperlink -> resultPtg = LookupReferenceCalculator.calcHyperlink(operands) + + + FunctionConstants.xlfIndex -> resultPtg = LookupReferenceCalculator.calcIndex(operands) + + FunctionConstants.XLF_INDIRECT -> resultPtg = LookupReferenceCalculator.calcIndirect(operands) + + FunctionConstants.XLF_ROW -> { + if (operands.size == 0) { + operands = arrayOfNulls(1) + operands[0] = function + } + resultPtg = LookupReferenceCalculator.calcRow(operands) + } + + FunctionConstants.xlfRows -> resultPtg = LookupReferenceCalculator.calcRows(operands) + + FunctionConstants.xlfTranspose -> resultPtg = LookupReferenceCalculator.calcTranspose(operands) + + FunctionConstants.xlfLookup -> resultPtg = LookupReferenceCalculator.calcLookup(operands) + + FunctionConstants.xlfHlookup -> resultPtg = LookupReferenceCalculator.calcHlookup(operands) + + FunctionConstants.xlfVlookup -> resultPtg = LookupReferenceCalculator.calcVlookup(operands) + + FunctionConstants.xlfMatch -> resultPtg = LookupReferenceCalculator.calcMatch(operands) + + FunctionConstants.xlfOffset -> resultPtg = LookupReferenceCalculator.calcOffset(operands) + + /******************************************** + * Math & Trigonometry functions ***** + */ + + FunctionConstants.XLF_SUM -> resultPtg = MathFunctionCalculator.calcSum(operands) + + FunctionConstants.XLF_SUM_IF -> resultPtg = MathFunctionCalculator.calcSumif(operands) + + FunctionConstants.xlfSUMIFS -> resultPtg = MathFunctionCalculator.calcSumIfS(operands) + + FunctionConstants.xlfSumproduct -> resultPtg = MathFunctionCalculator.calcSumproduct(operands) + + FunctionConstants.xlfExp -> resultPtg = MathFunctionCalculator.calcExp(operands) + + FunctionConstants.xlfAbs -> resultPtg = MathFunctionCalculator.calcAbs(operands) + + FunctionConstants.xlfAcos -> resultPtg = MathFunctionCalculator.calcAcos(operands) + + FunctionConstants.xlfAcosh -> resultPtg = MathFunctionCalculator.calcAcosh(operands) + + FunctionConstants.xlfAsin -> resultPtg = MathFunctionCalculator.calcAsin(operands) + + FunctionConstants.xlfAsinh -> resultPtg = MathFunctionCalculator.calcAsinh(operands) + + FunctionConstants.xlfAtan -> resultPtg = MathFunctionCalculator.calcAtan(operands) + + FunctionConstants.xlfAtan2 -> resultPtg = MathFunctionCalculator.calcAtan2(operands) + + FunctionConstants.xlfAtanh -> resultPtg = MathFunctionCalculator.calcAtanh(operands) + + FunctionConstants.xlfCeiling -> resultPtg = MathFunctionCalculator.calcCeiling(operands) + + FunctionConstants.xlfCombin -> resultPtg = MathFunctionCalculator.calcCombin(operands) + + FunctionConstants.xlfCos -> resultPtg = MathFunctionCalculator.calcCos(operands) + + FunctionConstants.xlfCosh -> resultPtg = MathFunctionCalculator.calcCosh(operands) + + FunctionConstants.xlfDegrees -> resultPtg = MathFunctionCalculator.calcDegrees(operands) + + FunctionConstants.xlfEven -> resultPtg = MathFunctionCalculator.calcEven(operands) + + FunctionConstants.xlfFact -> resultPtg = MathFunctionCalculator.calcFact(operands) + + FunctionConstants.xlfDOUBLEFACT -> resultPtg = MathFunctionCalculator.calcFactDouble(operands) + + FunctionConstants.xlfFloor -> resultPtg = MathFunctionCalculator.calcFloor(operands) + + FunctionConstants.xlfGCD -> resultPtg = MathFunctionCalculator.calcGCD(operands) + + FunctionConstants.xlfInt -> resultPtg = MathFunctionCalculator.calcInt(operands) + + FunctionConstants.xlfLCM -> resultPtg = MathFunctionCalculator.calcLCM(operands) + + FunctionConstants.xlfMROUND -> resultPtg = MathFunctionCalculator.calcMRound(operands) + + FunctionConstants.xlfMmult -> resultPtg = MathFunctionCalculator.calcMMult(operands) + + FunctionConstants.xlfMULTINOMIAL -> resultPtg = MathFunctionCalculator.calcMultinomial(operands) + + FunctionConstants.xlfLn -> resultPtg = MathFunctionCalculator.calcLn(operands) + + FunctionConstants.xlfLog -> resultPtg = MathFunctionCalculator.calcLog(operands) + + FunctionConstants.xlfLog10 -> resultPtg = MathFunctionCalculator.calcLog10(operands) + + FunctionConstants.xlfMod -> resultPtg = MathFunctionCalculator.calcMod(operands) + + FunctionConstants.xlfOdd -> resultPtg = MathFunctionCalculator.calcOdd(operands) + + FunctionConstants.xlfPi -> resultPtg = MathFunctionCalculator.calcPi(operands) + + FunctionConstants.xlfPower -> resultPtg = MathFunctionCalculator.calcPower(operands) + + FunctionConstants.xlfProduct -> resultPtg = MathFunctionCalculator.calcProduct(operands) + + FunctionConstants.xlfQUOTIENT -> resultPtg = MathFunctionCalculator.calcQuotient(operands) + + FunctionConstants.xlfRadians -> resultPtg = MathFunctionCalculator.calcRadians(operands) + + FunctionConstants.xlfRand -> resultPtg = MathFunctionCalculator.calcRand(operands) + + FunctionConstants.xlfRANDBETWEEN -> resultPtg = MathFunctionCalculator.calcRandBetween(operands) + + FunctionConstants.xlfRoman -> resultPtg = MathFunctionCalculator.calcRoman(operands) + + FunctionConstants.xlfRound -> resultPtg = MathFunctionCalculator.calcRound(operands) + + FunctionConstants.xlfRounddown -> resultPtg = MathFunctionCalculator.calcRoundDown(operands) + + FunctionConstants.xlfRoundup -> resultPtg = MathFunctionCalculator.calcRoundUp(operands) + + FunctionConstants.xlfSign -> resultPtg = MathFunctionCalculator.calcSign(operands) + + FunctionConstants.xlfSin -> resultPtg = MathFunctionCalculator.calcSin(operands) + + FunctionConstants.xlfSinh -> resultPtg = MathFunctionCalculator.calcSinh(operands) + + FunctionConstants.xlfSqrt -> resultPtg = MathFunctionCalculator.calcSqrt(operands) + + FunctionConstants.xlfSQRTPI -> resultPtg = MathFunctionCalculator.calcSqrtPi(operands) + + FunctionConstants.xlfTan -> resultPtg = MathFunctionCalculator.calcTan(operands) + + FunctionConstants.xlfTanh -> resultPtg = MathFunctionCalculator.calcTanh(operands) + + FunctionConstants.xlfTrunc -> resultPtg = MathFunctionCalculator.calcTrunc(operands) + + /******************************************** + * Statistical functions ***** + */ + + FunctionConstants.XLF_COUNT -> resultPtg = StatisticalCalculator.calcCount(operands) + + FunctionConstants.xlfCounta -> resultPtg = StatisticalCalculator.calcCountA(operands) + + FunctionConstants.xlfCountblank -> resultPtg = StatisticalCalculator.calcCountBlank(operands) + + FunctionConstants.xlfCountif -> resultPtg = StatisticalCalculator.calcCountif(operands) + + FunctionConstants.xlfCOUNTIFS -> resultPtg = StatisticalCalculator.calcCountIfS(operands) + + FunctionConstants.XLF_MIN -> resultPtg = StatisticalCalculator.calcMin(operands) + + FunctionConstants.xlfMinA -> resultPtg = StatisticalCalculator.calcMinA(operands) + + FunctionConstants.XLF_MAX -> resultPtg = StatisticalCalculator.calcMax(operands) + + FunctionConstants.xlfMaxA -> resultPtg = StatisticalCalculator.calcMaxA(operands) + + FunctionConstants.xlfNormdist -> resultPtg = StatisticalCalculator.calcNormdist(operands) + + FunctionConstants.xlfNormsdist -> resultPtg = StatisticalCalculator.calcNormsdist(operands) + + FunctionConstants.xlfNormsinv -> resultPtg = StatisticalCalculator.calcNormsInv(operands) + + FunctionConstants.xlfNorminv -> resultPtg = StatisticalCalculator.calcNormInv(operands) + + FunctionConstants.XLF_AVERAGE -> resultPtg = StatisticalCalculator.calcAverage(operands) + + FunctionConstants.xlfAVERAGEIF -> resultPtg = StatisticalCalculator.calcAverageIf(operands) + + FunctionConstants.xlfAVERAGEIFS -> resultPtg = StatisticalCalculator.calcAverageIfS(operands) + + FunctionConstants.xlfAvedev -> resultPtg = StatisticalCalculator.calcAveDev(operands) + + FunctionConstants.xlfAverageA -> resultPtg = StatisticalCalculator.calcAverageA(operands) + + FunctionConstants.xlfMedian -> resultPtg = StatisticalCalculator.calcMedian(operands) + + FunctionConstants.xlfMode -> resultPtg = StatisticalCalculator.calcMode(operands) + + FunctionConstants.xlfQuartile -> resultPtg = StatisticalCalculator.calcQuartile(operands) + + FunctionConstants.xlfRank -> resultPtg = StatisticalCalculator.calcRank(operands) + + FunctionConstants.xlfStdev -> resultPtg = StatisticalCalculator.calcStdev(operands) + + FunctionConstants.xlfVar -> resultPtg = StatisticalCalculator.calcVar(operands) + + FunctionConstants.xlfVarp -> resultPtg = StatisticalCalculator.calcVarp(operands) + + FunctionConstants.xlfCovar -> resultPtg = StatisticalCalculator.calcCovar(operands) + + FunctionConstants.xlfCorrel -> resultPtg = StatisticalCalculator.calcCorrel(operands) + + FunctionConstants.xlfFrequency -> resultPtg = StatisticalCalculator.calcFrequency(operands) + + FunctionConstants.xlfLinest -> resultPtg = StatisticalCalculator.calcLineSt(operands) + + FunctionConstants.xlfSlope -> resultPtg = StatisticalCalculator.calcSlope(operands) + + FunctionConstants.xlfIntercept -> resultPtg = StatisticalCalculator.calcIntercept(operands) + + FunctionConstants.xlfPearson -> resultPtg = StatisticalCalculator.calcPearson(operands) + + FunctionConstants.xlfRsq -> resultPtg = StatisticalCalculator.calcRsq(operands) + + FunctionConstants.xlfSteyx -> resultPtg = StatisticalCalculator.calcSteyx(operands) + + FunctionConstants.xlfForecast -> resultPtg = StatisticalCalculator.calcForecast(operands) + + FunctionConstants.xlfTrend -> resultPtg = StatisticalCalculator.calcTrend(operands) + + FunctionConstants.xlfLarge -> resultPtg = StatisticalCalculator.calcLarge(operands) + + FunctionConstants.xlfSmall -> resultPtg = StatisticalCalculator.calcSmall(operands) + + /******************************************** + * Text functions ***** + */ + /* + * these DBCS functions are not working yet + case FunctionConstants.xlfAsc: + resultPtg= TextCalculator.calcAsc(operands); + break; + + case FunctionConstants.xlfDbcs: + resultPtg= TextCalculator.calcJIS(operands); + break; + */ + FunctionConstants.xlfChar -> resultPtg = TextCalculator.calcChar(operands) + + FunctionConstants.xlfClean -> resultPtg = TextCalculator.calcClean(operands) + + FunctionConstants.xlfCode -> resultPtg = TextCalculator.calcCode(operands) + + FunctionConstants.xlfConcatenate -> resultPtg = TextCalculator.calcConcatenate(operands) + + FunctionConstants.xlfDollar -> resultPtg = TextCalculator.calcDollar(operands) + + FunctionConstants.xlfExact -> resultPtg = TextCalculator.calcExact(operands) + + FunctionConstants.xlfFind -> resultPtg = TextCalculator.calcFind(operands) + + // DBCS functions are not working 100% yet + FunctionConstants.xlfFindb -> resultPtg = TextCalculator.calcFindB(operands) + + FunctionConstants.xlfFixed -> resultPtg = TextCalculator.calcFixed(operands) + + FunctionConstants.xlfLeft -> resultPtg = TextCalculator.calcLeft(operands) + + FunctionConstants.xlfLeftb -> resultPtg = TextCalculator.calcLeftB(operands) + + FunctionConstants.xlfLen -> resultPtg = TextCalculator.calcLen(operands) + + FunctionConstants.xlfLenb -> resultPtg = TextCalculator.calcLenB(operands) + + FunctionConstants.xlfLower -> resultPtg = TextCalculator.calcLower(operands) + + FunctionConstants.xlfUpper -> resultPtg = TextCalculator.calcUpper(operands) + + FunctionConstants.xlfMid -> resultPtg = TextCalculator.calcMid(operands) + + FunctionConstants.xlfProper -> resultPtg = TextCalculator.calcProper(operands) + + FunctionConstants.xlfReplace -> resultPtg = TextCalculator.calcReplace(operands) + + FunctionConstants.xlfRept -> resultPtg = TextCalculator.calcRept(operands) + + FunctionConstants.xlfRight -> resultPtg = TextCalculator.calcRight(operands) + + FunctionConstants.xlfSearch -> resultPtg = TextCalculator.calcSearch(operands) + + FunctionConstants.xlfSearchb -> resultPtg = TextCalculator.calcSearchB(operands) + + FunctionConstants.xlfSubstitute -> resultPtg = TextCalculator.calcSubstitute(operands) + + FunctionConstants.xlfT -> resultPtg = TextCalculator.calcT(operands) + + FunctionConstants.xlfTrim -> resultPtg = TextCalculator.calcTrim(operands) + + FunctionConstants.xlfText -> resultPtg = TextCalculator.calcText(operands) + + + FunctionConstants.xlfValue -> resultPtg = TextCalculator.calcValue(operands) + + + else -> { + var s: String? = FunctionConstants.getFunctionString(functionId.toShort()) + if (s != null && s != "") + s = s.substring(0, s.length - 1) + else + s = Integer.toHexString(functionId) + //throw new FunctionNotSupportedException( (!.equals(""))?FunctionConstants.getFunctionString((short)funkId).substring(0, ):Integer.toHexString((int)funkId)); + throw FunctionNotSupportedException(s) // 20081118 KSC: add a little more info ... + } + }// KSC: Clear out lookup caches! + // function.getParentRec().getWorkBook().getRefTracker().clearLookupCaches(); + // KSC: Clear out lookup caches! + // function.getParentRec().getWorkBook().getRefTracker().clearLookupCaches(); + + + return resultPtg + } + + /**************************************************************************** + * * + * The following section is made up of the calcuations for each of the * + * function types. These map directly to the name of the function * + * declared in the header and is called from the coresponding switch * + * statement above. ENJOY! * + * * + */ + + + /**************************************** + * * + * Excel function numbers * + * * + */ + /* + public static final int xlfCount = 0; + public static final int xlfIf = 1; + public static final int xlfIsna = 2; + public static final int xlfIserror = 3; + public static final int xlfSum = 4; + public static final int xlfAverage = 5; + public static final int xlfMin = 6; + public static final int xlfMax = 7; + public static final int xlfRow = 8; + public static final int xlfColumn = 9; + public static final int xlfNa = 10; + public static final int xlfNpv = 11; + public static final int xlfStdev = 12; + public static final int xlfDollar = 13; + public static final int xlfFixed = 14; + public static final int xlfSin = 15; + public static final int xlfCos = 16; + public static final int xlfTan = 17; + public static final int xlfAtan = 18; + public static final int xlfPi = 19; + public static final int xlfSqrt = 20; + public static final int xlfExp = 21; + public static final int xlfLn = 22; + public static final int xlfLog10 = 23; + public static final int xlfAbs = 24; + public static final int xlfInt = 25; + public static final int xlfSign = 26; + public static final int xlfRound = 27; + public static final int xlfLookup = 28; + public static final int xlfIndex = 29; + public static final int xlfRept = 30; + public static final int xlfMid = 31; + public static final int xlfLen = 32; + public static final int xlfValue = 33; + public static final int xlfTrue = 34; + public static final int xlfFalse = 35; + public static final int xlfAnd = 36; + public static final int xlfOr = 37; + public static final int xlfNot = 38; + public static final int xlfMod = 39; + public static final int xlfDcount = 40; + public static final int xlfDsum = 41; + public static final int xlfDaverage = 42; + public static final int xlfDmin = 43; + public static final int xlfDmax = 44; + public static final int xlfDstdev = 45; + public static final int xlfVar = 46; + public static final int xlfDvar = 47; + public static final int xlfText = 48; + public static final int xlfLinest = 49; + public static final int xlfTrend = 50; + public static final int xlfLogest = 51; + public static final int xlfGrowth = 52; + public static final int xlfGoto = 53; + public static final int xlfHalt = 54; + public static final int xlfPv = 56; + public static final int xlfFv = 57; + public static final int xlfNper = 58; + public static final int xlfPmt = 59; + public static final int xlfRate = 60; + public static final int xlfMirr = 61; + public static final int xlfIrr = 62; + public static final int xlfRand = 63; + public static final int xlfMatch = 64; + public static final int xlfDate = 65; + public static final int xlfTime = 66; + public static final int xlfDay = 67; + public static final int xlfMonth = 68; + public static final int xlfYear = 69; + public static final int xlfWeekday = 70; + public static final int xlfHour = 71; + public static final int xlfMinute = 72; + public static final int xlfSecond = 73; + public static final int xlfNow = 74; + public static final int xlfAreas = 75; + public static final int xlfRows = 76; + public static final int xlfColumns = 77; + public static final int xlfOffset = 78; + public static final int xlfAbsref = 79; + public static final int xlfRelref = 80; + public static final int xlfArgument = 81; + public static final int xlfSearch = 82; + public static final int xlfTranspose = 83; + public static final int xlfError = 84; + public static final int xlfStep = 85; + public static final int xlfType = 86; + public static final int xlfEcho = 87; + public static final int xlfSetName = 88; + public static final int xlfCaller = 89; + public static final int xlfDeref = 90; + public static final int xlfWindows = 91; + public static final int xlfSeries = 92; + public static final int xlfDocuments = 93; + public static final int xlfActiveCell = 94; + public static final int xlfSelection = 95; + public static final int xlfResult = 96; + public static final int xlfAtan2 = 97; + public static final int xlfAsin = 98; + public static final int xlfAcos = 99; + public static final int xlfChoose = 100; + public static final int xlfHlookup = 101; + public static final int xlfVlookup = 102; + public static final int xlfLinks = 103; + public static final int xlfInput = 104; + public static final int xlfIsref = 105; + public static final int xlfGetFormula = 106; + public static final int xlfGetName = 107; + public static final int xlfSetValue = 108; + public static final int xlfLog = 109; + public static final int xlfExec = 110; + public static final int xlfChar = 111; + public static final int xlfLower = 112; + public static final int xlfUpper = 113; + public static final int xlfProper = 114; + public static final int xlfLeft = 115; + public static final int xlfRight = 116; + public static final int xlfExact = 117; + public static final int xlfTrim = 118; + public static final int xlfReplace = 119; + public static final int xlfSubstitute = 120; + public static final int xlfCode = 121; + public static final int xlfNames = 122; + public static final int xlfDirectory = 123; + public static final int xlfFind = 124; + public static final int xlfCell = 125; + public static final int xlfIserr = 126; + public static final int xlfIstext = 127; + public static final int xlfIsnumber = 128; + public static final int xlfIsblank = 129; + public static final int xlfT = 130; + public static final int xlfN = 131; + public static final int xlfFopen = 132; + public static final int xlfFclose = 133; + public static final int xlfFsize = 134; + public static final int xlfFreadln = 135; + public static final int xlfFread = 136; + public static final int xlfFwriteln = 137; + public static final int xlfFwrite = 138; + public static final int xlfFpos = 139; + public static final int xlfDatevalue = 140; + public static final int xlfTimevalue = 141; + public static final int xlfSln = 142; + public static final int xlfSyd = 143; + public static final int xlfDdb = 144; + public static final int xlfGetDef = 145; + public static final int xlfReftext = 146; + public static final int xlfTextref = 147; + public static final int xlfIndirect = 148; + public static final int xlfRegister = 149; + public static final int xlfCall = 150; + public static final int xlfAddBar = 151; + public static final int xlfAddMenu = 152; + public static final int xlfAddCommand = 153; + public static final int xlfEnableCommand = 154; + public static final int xlfCheckCommand = 155; + public static final int xlfRenameCommand = 156; + public static final int xlfShowBar = 157; + public static final int xlfDeleteMenu = 158; + public static final int xlfDeleteCommand = 159; + public static final int xlfGetChartItem = 160; + public static final int xlfDialogBox = 161; + public static final int xlfClean = 162; + public static final int xlfMdeterm = 163; + public static final int xlfMinverse = 164; + public static final int xlfMmult = 165; + public static final int xlfFiles = 166; + public static final int xlfIpmt = 167; + public static final int xlfPpmt = 168; + public static final int xlfCounta = 169; + public static final int xlfCancelKey = 170; + public static final int xlfInitiate = 175; + public static final int xlfRequest = 176; + public static final int xlfPoke = 177; + public static final int xlfExecute = 178; + public static final int xlfTerminate = 179; + public static final int xlfRestart = 180; + public static final int xlfHelp = 181; + public static final int xlfGetBar = 182; + public static final int xlfProduct = 183; + public static final int xlfFact = 184; + public static final int xlfGetCell = 185; + public static final int xlfGetWorkspace = 186; + public static final int xlfGetWindow = 187; + public static final int xlfGetDocument = 188; + public static final int xlfDproduct = 189; + public static final int xlfIsnontext = 190; + public static final int xlfGetNote = 191; + public static final int xlfNote = 192; + public static final int xlfStdevp = 193; + public static final int xlfVarp = 194; + public static final int xlfDstdevp = 195; + public static final int xlfDvarp = 196; + public static final int xlfTrunc = 197; + public static final int xlfIslogical = 198; + public static final int xlfDcounta = 199; + public static final int xlfDeleteBar = 200; + public static final int xlfUnregister = 201; + public static final int xlfUsdollar = 204; + public static final int xlfFindb = 205; + public static final int xlfSearchb = 206; + public static final int xlfReplaceb = 207; + public static final int xlfLeftb = 208; + public static final int xlfRightb = 209; + public static final int xlfMidb = 210; + public static final int xlfLenb = 211; + public static final int xlfRoundup = 212; + public static final int xlfRounddown = 213; + public static final int xlfAsc = 214; + public static final int xlfDbcs = 215; + public static final int xlfRank = 216; + public static final int xlfAddress = 219; + public static final int xlfDays360 = 220; + public static final int xlfToday = 221; + public static final int xlfVdb = 222; + public static final int xlfMedian = 227; + public static final int xlfSumproduct = 228; + public static final int xlfSinh = 229; + public static final int xlfCosh = 230; + public static final int xlfTanh = 231; + public static final int xlfAsinh = 232; + public static final int xlfAcosh = 233; + public static final int xlfAtanh = 234; + public static final int xlfDget = 235; + public static final int xlfCreateObject = 236; + public static final int xlfVolatile = 237; + public static final int xlfLastError = 238; + public static final int xlfCustomUndo = 239; + public static final int xlfCustomRepeat = 240; + public static final int xlfFormulaConvert = 241; + public static final int xlfGetLinkInfo = 242; + public static final int xlfTextBox = 243; + public static final int xlfInfo = 244; + public static final int xlfGroup = 245; + public static final int xlfGetObject = 246; + public static final int xlfDb = 247; + public static final int xlfPause = 248; + public static final int xlfResume = 251; + public static final int xlfFrequency = 252; + public static final int xlfAddToolbar = 253; + public static final int xlfDeleteToolbar = 254; + public static final int xlfADDIN = 255; // KSC: Added; Excel function ID for add-ins + public static final int xlfResetToolbar = 256; + public static final int xlfEvaluate = 257; + public static final int xlfGetToolbar = 258; + public static final int xlfGetTool = 259; + public static final int xlfSpellingCheck = 260; + public static final int xlfErrorType = 261; + public static final int xlfAppTitle = 262; + public static final int xlfWindowTitle = 263; + public static final int xlfSaveToolbar = 264; + public static final int xlfEnableTool = 265; + public static final int xlfPressTool = 266; + public static final int xlfRegisterId = 267; + public static final int xlfGetWorkbook = 268; + public static final int xlfAvedev = 269; + public static final int xlfBetadist = 270; + public static final int xlfGammaln = 271; + public static final int xlfBetainv = 272; + public static final int xlfBinomdist = 273; + public static final int xlfChidist = 274; + public static final int xlfChiinv = 275; + public static final int xlfCombin = 276; + public static final int xlfConfidence = 277; + public static final int xlfCritbinom = 278; + public static final int xlfEven = 279; + public static final int xlfExpondist = 280; + public static final int xlfFdist = 281; + public static final int xlfFinv = 282; + public static final int xlfFisher = 283; + public static final int xlfFisherinv = 284; + public static final int xlfFloor = 285; + public static final int xlfGammadist = 286; + public static final int xlfGammainv = 287; + public static final int xlfCeiling = 288; + public static final int xlfHypgeomdist = 289; + public static final int xlfLognormdist = 290; + public static final int xlfLoginv = 291; + public static final int xlfNegbinomdist = 292; + public static final int xlfNormdist = 293; + public static final int xlfNormsdist = 294; + public static final int xlfNorminv = 295; + public static final int xlfNormsinv = 296; + public static final int xlfStandardize = 297; + public static final int xlfOdd = 298; + public static final int xlfPermut = 299; + public static final int xlfPoisson = 300; + public static final int xlfTdist = 301; + public static final int xlfWeibull = 302; + public static final int xlfSumxmy2 = 303; + public static final int xlfSumx2my2 = 304; + public static final int xlfSumx2py2 = 305; + public static final int xlfChitest = 306; + public static final int xlfCorrel = 307; + public static final int xlfCovar = 308; + public static final int xlfForecast = 309; + public static final int xlfFtest = 310; + public static final int xlfIntercept = 311; + public static final int xlfPearson = 312; + public static final int xlfRsq = 313; + public static final int xlfSteyx = 314; + public static final int xlfSlope = 315; + public static final int xlfTtest = 316; + public static final int xlfProb = 317; + public static final int xlfDevsq = 318; + public static final int xlfGeomean = 319; + public static final int xlfHarmean = 320; + public static final int xlfSumsq = 321; + public static final int xlfKurt = 322; + public static final int xlfSkew = 323; + public static final int xlfZtest = 324; + public static final int xlfLarge = 325; + public static final int xlfSmall = 326; + public static final int xlfQuartile = 327; + public static final int xlfPercentile = 328; + public static final int xlfPercentrank = 329; + public static final int xlfMode = 330; + public static final int xlfTrimmean = 331; + public static final int xlfTinv = 332; + public static final int xlfMovieCommand = 334; + public static final int xlfGetMovie = 335; + public static final int xlfConcatenate = 336; + public static final int xlfPower = 337; + public static final int xlfPivotAddData = 338; + public static final int xlfGetPivotTable = 339; + public static final int xlfGetPivotField = 340; + public static final int xlfGetPivotItem = 341; + public static final int xlfRadians = 342; + public static final int xlfDegrees = 343; + public static final int xlfSubtotal = 344; + public static final int xlfSumif = 345; + public static final int xlfCountif = 346; + public static final int xlfCountblank = 347; + public static final int xlfScenarioGet = 348; + public static final int xlfOptionsListsGet = 349; + public static final int xlfIspmt = 350; + public static final int xlfDatedif = 351; + public static final int xlfDatestring = 352; + public static final int xlfNumberstring = 353; + public static final int xlfRoman = 354; + public static final int xlfOpenDialog = 355; + public static final int xlfSaveDialog = 356; + public static final int xlfViewGet = 357; + public static final int xlfGetPivotData = 358; + public static final int xlfHyperlink = 359; + public static final int xlfPhonetic = 360; + public static final int xlfAverageA = 361; + public static final int xlfMaxA = 362; + public static final int xlfMinA = 363; + public static final int xlfStDevPA = 364; + public static final int xlfVarPA = 365; + public static final int xlfStDevA = 366; + public static final int xlfVarA = 367; + // KSC: ADD-IN formulas - use any index; name must be present in FunctionConstants.addIns + // Financial Formulas + public static final int xlfAccrintm= 368; + public static final int xlfAccrint= 369; + public static final int xlfCoupDayBS= 370; + public static final int xlfCoupDays= 371; + public static final int xlfCumIPmt= 372; + public static final int xlfCumPrinc= 373; + public static final int xlfCoupNCD= 374; + public static final int xlfCoupDaysNC= 375; + public static final int xlfCoupPCD= 376; + public static final int xlfCoupNUM= 377; + public static final int xlfDollarDE= 378; + public static final int xlfDollarFR= 379; + public static final int xlfEffect= 380; + public static final int xlfINTRATE= 381; + public static final int xlfXIRR= 382; + public static final int xlfXNPV= 383; + public static final int xlfYIELD= 384; + public static final int xlfPRICE= 385; + public static final int xlfPRICEDISC= 386; + public static final int xlfPRICEMAT= 387; + public static final int xlfDURATION= 388; + public static final int xlfMDURATION= 389; + public static final int xlfTBillEq= 390; + public static final int xlfTBillPrice= 391; + public static final int xlfTBillYield= 392; + public static final int xlfYieldDisc= 393; + public static final int xlfYieldMat= 394; + public static final int xlfFVSchedule= 395; + public static final int xlfAmorlinc= 396; + public static final int xlfAmordegrc= 397; + public static final int xlfOddFPrice= 398; + public static final int xlfOddLPrice= 399; + public static final int xlfOddFYield= 400; + public static final int xlfOddLYield= 401; + public static final int xlfNOMINAL= 402; + public static final int xlfDISC= 403; + public static final int xlfRECEIVED= 404; + // Engineering Formulas + public static final int xlfBIN2DEC= 405; + public static final int xlfBIN2HEX= 406; + public static final int xlfBIN2OCT= 407; + public static final int xlfDEC2BIN= 408; + public static final int xlfDEC2HEX= 409; + public static final int xlfDEC2OCT= 410; + public static final int xlfHEX2BIN= 411; + public static final int xlfHEX2DEC= 412; + public static final int xlfHEX2OCT= 413; + public static final int xlfOCT2BIN= 414; + public static final int xlfOCT2DEC= 415; + public static final int xlfOCT2HEX= 416; + public static final int xlfCOMPLEX= 417; + public static final int xlfGESTEP= 418; + public static final int xlfDELTA= 419; + public static final int xlfIMAGINARY= 420; + public static final int xlfIMABS= 421; + public static final int xlfIMDIV= 422; + public static final int xlfIMCONJUGATE= 423; + public static final int xlfIMCOS= 424; + public static final int xlfIMSIN= 425; + public static final int xlfIMREAL= 426; + public static final int xlfIMEXP= 427; + public static final int xlfIMSUB= 428; + public static final int xlfIMSUM= 429; + public static final int xlfIMPRODUCT= 430; + public static final int xlfIMLN= 431; + public static final int xlfIMLOG10= 432; + public static final int xlfIMLOG2= 433; + public static final int xlfIMPOWER= 434; + public static final int xlfIMSQRT= 435; + public static final int xlfIMARGUMENT= 436; + public static final int xlfCONVERT= 437; + // Math Add-in Formulas + public static final int xlfDOUBLEFACT= 438; + public static final int xlfGCD= 439; + public static final int xlfLCM= 440; + public static final int xlfMROUND= 441; + public static final int xlfMULTINOMIAL= 442; + public static final int xlfQUOTIENT= 443; + public static final int xlfRANDBETWEEN= 444; + public static final int xlfSERIESSUM= 445; + public static final int xlfSQRTPI= 446; +*/ +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/GenericPtg.java b/src/main/java/io/starter/formats/XLS/formulas/GenericPtg.java deleted file mode 100644 index 7039db8..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/GenericPtg.java +++ /dev/null @@ -1,721 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - - -import io.starter.OpenXLS.DateConverter; -import io.starter.formats.XLS.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.Calendar; - -public abstract class GenericPtg - implements Ptg, Cloneable { - - double doublePrecision = 0.00000001; // doubles/floats cannot be compared for exactness so use precision comparator - public static final long serialVersionUID = 666555444333222l; - byte ptgId; - byte[] record; - - Ptg[] vars = null; - int lock_id = -1; - private int locationLocked = Ptg.PTG_LOCATION_POLICY_UNLOCKED; - private BiffRec trackercell = null; - - - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // This is, in theory, impossible - return null; - } - } - - - /** - * a locking mechanism so that Ptgs are not endlessly - * re-calculated - * - * @return - */ - public int getLock() { - return lock_id; - } - - /** - * a locking mechanism so that Ptgs are not endlessly - * re-calculated - * - * @return - */ - public void setLock(int x) { - lock_id = x; - } - - // determine behavior - public boolean getIsOperator() { - return false; - } - - public boolean getIsBinaryOperator() { - return false; - } - - public boolean getIsUnaryOperator() { - return false; - } - - public boolean getIsStandAloneOperator() { - return false; - } - - public boolean getIsPrimitiveOperator() { - return false; - } - - public boolean getIsOperand() { - return false; - } - - public boolean getIsFunction() { - return false; - } - - public boolean getIsControl() { - return false; - } - - public boolean getIsArray() { - return false; - } - - public boolean getIsReference() { - return false; - } - - /** - * returns the Location Policy of the Ptg is locked - * used during automated BiffRec movement updates - * - * @return int - */ - public int getLocationPolicy() { - return locationLocked; - } - - /** - * lock the Location of the Ptg so that it will not - * be updated during automated BiffRec movement updates - * - * @param b setting of the lock the location policy for this Ptg - */ - public void setLocationPolicy(int b) { - locationLocked = b; - } - - /** - * update the Ptg - */ - public void updateRecord() { - - } - - /** - * Returns the number of Params to pass to the Ptg - */ - public int getNumParams() { - if (getIsPrimitiveOperator()) return 2; - return 0; - } - -/* ################################################### EXPLANATION ################################################### - - 1. set string varetvar in all Ptgs - 2. varetvar goes between ptg return vals if any - 3. if this is a funcvar then we loop ptgs and out - 4. when we call getString or evaluate, we loop into the - recursive tree and execute on up. - - ################################################### EXPLANATION ###################################################*/ - - - /** - * Operator Ptgs take other Ptgs as arguments - * so we need to pass them in to get a meaningful - * value. - */ - public void setVars(Ptg[] parr) { - this.vars = parr; - } - - /* - Return all of the cells in this range as an array - of Ptg's. This is used for range calculations. - */ - public Ptg[] getComponents() { - return null; - } - - /** - * pass in arbitrary number of values (probably other Ptgs) - * and return the resultant value. - *

                      - * This effectively calculates the Expression. - */ - public Object evaluate(Object[] obj) { - // do something useful - return this.getString(); - } - - /** - * return the human-readable String representation of - * this ptg -- if applicable - */ - public String getTextString() { - - String strx = ""; - - try { - strx = getString(); - } catch (Exception e) { - Logger.logErr("Function not supported: " + this.parent_rec.toString()); - } - - if (strx == null) - return ""; - - StringBuffer out = new StringBuffer(strx); - if (vars != null) { - int numvars = vars.length; - if (this.getIsPrimitiveOperator() && this.getIsUnaryOperator()) { - if (numvars > 0) - out.append(vars[0].getTextString()); - - } else if (this.getIsPrimitiveOperator()) { - out.setLength(0); - for (int x = 0; x < numvars; x++) { - out.append(vars[x].getTextString()); - if (x + 1 < numvars) out.append(this.getString()); - } - } else if (this.getIsControl()) { - for (int x = 0; x < numvars; x++) { - out.append(vars[x].getTextString()); - } - } else { - for (int x = 0; x < vars.length; x++) { - if (!(x == 0 && vars[x] instanceof PtgNameX)) { // KSC: added to skip External name reference for Add-in Formulas - String part = vars[x].getTextString(); - // 20060408 KSC: added quoting in PtgStr.getTextString -// if (vars[x] instanceof PtgStr) // 20060214 KSC: Quote string params -// part= "\"" + part + "\""; - out.append(part); - /*if(!part.equals(""))*/ - out.append(","); - } - } - if (vars.length > 0) // don't strip 1st paren if no params! 20060501 KSC - out.setLength(out.length() - 1); // strip trailing comma - } - } - out.append(getString2()); - return out.toString(); - } - - /*text1 and 2 for this Ptg - */ - public String getString() { - return toString(); - } - - /** - * return the human-readable String representation of - * the "closing" portion of this Ptg - * such as a closing parenthesis. - */ - - public String getString2() { - if (this.getIsPrimitiveOperator()) return ""; - if (this.getIsOperator()) return ")"; - return ""; - } - - public byte getOpcode() { - return ptgId; - } - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - } - - /** - * return a Ptg consisting of the calculated values - * of the ptg's passed in. Returns null for any non-operand - * ptg. - * - * @throws CalculationException - */ - public Ptg calculatePtg(Ptg[] parsething) throws FunctionNotSupportedException, CalculationException { - return null; - - } - - /** - * Gets the (return) value of this Ptg as an operand Ptg. - */ - public Ptg getPtgVal() { - Object value = this.getValue(); - if (value instanceof Ptg) return (Ptg) value; - else if (value instanceof Boolean) - return new PtgBool(((Boolean) value).booleanValue()); - else if (value instanceof Integer) - return new PtgInt(((Integer) value).intValue()); - else if (value instanceof Number) - return new PtgNumber(((Number) value).doubleValue()); - else if (value instanceof String) - return new PtgStr((String) value); - else return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * returns the value of an operand ptg. - * - * @return null for non-operand Ptg. - */ - public Object getValue() { - return null; - } - - /** - * Gets the value of the ptg represented as an int. - *

                      - * This can result in loss of precision for floating point values. - *

                      - * overridden in PtgInt to natively return value. - * - * @return integer representing the ptg, or NAN - */ - public int getIntVal() { - try { - return new Double(this.getValue().toString()).intValue(); - } catch (NumberFormatException e) { - // we should be throwing something better - if (!(this instanceof PtgErr)) // don't report an error if it's already an error - Logger.logErr("GetIntVal failed for formula: " + this.getParentRec().toString() + " " + e); - return 0; - /// RIIIIGHT! throw new FormulaCalculationException(); - } - } - - /** - * Gets the value of the ptg represented as an double. - *

                      - * This can result in loss of precision for floating point values. - *

                      - * NAN will be returned for values that are not translateable to an double - *

                      - * overrideen in PtgNumber - * - * @return integer representing the ptg, or NAN - */ - public double getDoubleVal() { - Object pob = null; - Double d = null; - try { - pob = this.getValue(); - if (pob == null) { - Logger.logErr("Unable to calculate Formula at " + this.getLocation()); - return java.lang.Double.NaN; - } - d = (Double) pob; - } catch (ClassCastException e) { - try { - Float f = (Float) pob; - d = new Double(f.doubleValue()); - } catch (ClassCastException e2) { - try { - Integer in = (Integer) pob; - d = new Double(in.doubleValue()); - } catch (Exception e3) { - if (pob == null || pob.toString().equals("")) { - d = new Double(0); - } else { - try { - Double dd = new Double(pob.toString()); - return dd.doubleValue(); - } catch (Exception e4) {// Logger.logWarn("Error in Ptg Calculator getting Double Value: " + e3); - return java.lang.Double.NaN; - } - } - } - } - } catch (Throwable exp) { - Logger.logErr("Unexpected Exception in PtgCalculator.getDoubleValue()", exp); - } - return d.doubleValue(); - } - - /** - * So, here you see we can get the static type from the record itself - * then format the output record. Some shorthand techniques are shown. - */ - public byte[] getRecord() { - return record; - } - - // these do nothing here... - public void setLocation(String s) { - } - - public String getLocation() throws FormulaNotFoundException { - return null; - } - - public int[] getIntLocation() throws FormulaNotFoundException { - return null; - } - - // Parent Rec is the BiffRec record referenced by Operand Ptgs - protected XLSRecord parent_rec; - - public void setParentRec(XLSRecord f) { - parent_rec = f; - } - - public XLSRecord getParentRec() { - return parent_rec; - } - - /** - * Returns an array of doubles from number-type ptg's sent in. - * This should only be referenced by sub-classes. - *

                      - * Null values accessed are treated as 0. Within excel (empty cell values == 0) Tested! - * Sometimes as well you can get empty string values, "". These are NOT EQUAL ("" != 0) - * - * @param pthings - * @return - */ - protected static Object[] getValuesFromPtgs(Ptg[] pthings) { - Object[] obar = new Object[pthings.length]; - for (int t = 0; t < obar.length; t++) { - if (pthings[t] instanceof PtgErr) - return null; - if (pthings[t] instanceof PtgArray) { - obar[t] = pthings[t].getComponents(); // get all items in array as Ptgs - Object v = null; - try { - v = getValuesFromObjects((Object[]) obar[t]); // get value array from the ptgs - } catch (NumberFormatException e) { // string or non-numeric values - v = getStringValuesFromPtgs((Ptg[]) obar[t]); - } - obar[t] = v; - } else { - Object pval = pthings[t].getValue(); - if (pval instanceof PtgArray) { - obar[t] = ((PtgArray) pval).getComponents(); // get all items in array as Ptgs - Object v = null; - try { - v = getValuesFromObjects((Object[]) obar[t]); // get value array from the ptgs - } catch (NumberFormatException e) { // string or non-numeric values - v = getStringValuesFromPtgs((Ptg[]) obar[t]); - } - obar[t] = v; - } else if (pval instanceof Name) { // then get it's components ... - obar[t] = pthings[t].getComponents(); - Object v = null; - try { - v = getValuesFromPtgs((Ptg[]) obar[t]); // get value array from the ptgs - } catch (NumberFormatException e) { // string or non-numeric values - v = getStringValuesFromPtgs((Ptg[]) obar[t]); - } - obar[t] = v; - } else { // it's a single value - try { - obar[t] = new Double(getDoubleValueFromObject(pval)); - } catch (NumberFormatException e) { - if (pval instanceof CalculationException) - obar[t] = pval.toString(); - else - obar[t] = pval; - } - } - } - } - return obar; - } - - - /** - * Returns an array of doubles from number-type ptg's sent in. - * This should only be referenced by sub-classes. - *

                      - * Null values accessed are treated as 0. Within excel (empty cell values == 0) Tested! - * Sometimes as well you can get empty string values, "". These are NOT EQUAL ("" != 0) - * - * @param pthings - * @return - */ - protected static double[] getValuesFromObjects(Object[] pthings) throws NumberFormatException { - double[] returnDbl = new double[pthings.length]; - for (int i = 0; i < pthings.length; i++) { - - // Object o = pthings[i].getValue(); - Object o = pthings[i]; - - if (o == null) { // NO!! "" is NOT "0", blank is, but not a zero length string. Causes calc errors, need to handle diff somehow20081103 KSC: don't error out if "" */ - returnDbl[i] = 0.0; - } else if (o instanceof Double) { - returnDbl[i] = ((Double) o).doubleValue(); - } else if (o instanceof Integer) { - returnDbl[i] = (double) ((Integer) o).intValue(); - } else if (o instanceof Boolean) { // Excel converts booleans to numbers in calculations 20090129 KSC - returnDbl[i] = (((Boolean) o).booleanValue() ? 1.0 : 0.0); - } else if (o instanceof PtgBool) { - returnDbl[i] = (((Boolean) (((PtgBool) o).getValue())).booleanValue() ? 1.0 : 0.0); - } else if (o instanceof PtgErr) { - // ? - } else { - String s = o.toString(); - Double d = new Double(s); - returnDbl[i] = d.doubleValue(); - } - } - return returnDbl; - } - - /** - * convert a value to a double, throws exception if cannot - * - * @param o - * @throws NumberFormatException - * @return double value if possible - */ - public static double getDoubleValue(Object o, XLSRecord parent) - throws NumberFormatException { - if (o instanceof Double) - return ((Double) o).doubleValue(); - if (o == null || o.toString().equals("")) { - // empty string is interpreted as 0 if show zero values - if (parent != null && parent.getSheet().getWindow2().getShowZeroValues()) - return 0.0; - // otherwise, throw error - throw new NumberFormatException(); - } - return new Double(o.toString()).doubleValue(); // will throw NumberFormatException if cannot convert - } - - /** - * converts a single Ptg number-type value to a double - */ - public static double getDoubleValueFromObject(Object o) { - double ret = 0.0; - if (o == null) { // 20081103 KSC: don't error out if "" */ - ret = 0.0; - } else if (o instanceof Double) { - ret = ((Double) o).doubleValue(); - } else if (o instanceof Integer) { - ret = (double) ((Integer) o).intValue(); - } else if (o instanceof Boolean) { // Excel converts booleans to numbers in calculations 20090129 KSC - ret = (((Boolean) o).booleanValue() ? 1.0 : 0.0); - } else if (o instanceof PtgErr) { - // ? - } else { - String s = o.toString(); - // handle formatted dates from fields like TEXT() calcs - if (s.indexOf("/") > -1) { - try { - Calendar c = DateConverter.convertStringToCalendar(s); - if (c != null) ret = DateConverter.getXLSDateVal(c); - } catch (Exception e) {//guess not - } - } - if (ret == 0.0) { - Double d = new Double(s); - ret = d.doubleValue(); - } - } - return ret; - } - - /** - * returns an array of strings from ptg's sent in. - * This should only be referenced by sub-classes. - */ - protected static String[] getStringValuesFromPtgs(Ptg[] pthings) { - String[] returnStr = new String[pthings.length]; - for (int i = 0; i < pthings.length; i++) { - if (pthings[i] instanceof PtgErr) - return new String[]{"#VALUE!"}; // 20081202 KSC: return error value ala Excel - - Object o = pthings[i].getValue(); - if (o != null) { // 20070215 KSC: avoid nullpointererror - try { // 20090205 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 - returnStr[i] = String.valueOf(((Double) o).intValue()); - } catch (Exception e) { - String s = o.toString(); - returnStr[i] = s; - } - } else - returnStr[i] = "null"; // 20070216 KSC: Shouldn't match empty string! - } - return returnStr; - } - - /** - * if the Ptg needs to keep a handle to a cell, this is it... - * tells the Ptg to get it on its own... - */ - public void updateAddressFromTrackerCell() { - this.initTrackerCell(); - BiffRec trk = getTrackercell(); - if (trk != null) { - String nad = trk.getCellAddress(); - setLocation(nad); - } - } - - /** - * if the Ptg needs to keep a handle to a cell, this is it... - * tells the Ptg to get it on its own... - */ - public void initTrackerCell() { - if (getTrackercell() == null) { - try { - BiffRec trk = this.getParentRec().getSheet().getCell(this.getLocation()); - setTrackercell(trk); - } catch (Exception e) { - Logger.logWarn("Formula reference could not initialize:" + e.toString()); - } - } - } - - /** - * @return Returns the trackercell. - */ - public BiffRec getTrackercell() { - return trackercell; - } - - /** - * @param trackercell The trackercell to set. - */ - public void setTrackercell(BiffRec trackercell) { - this.trackercell = trackercell; - } - - //TODO: PtgRef.isBlank should override! - public boolean isBlank() { - return false; - } - - /** - * return properly quoted sheetname - * - * @param s - * @return - */ - public static final String qualifySheetname(String s) { - if (s == null || s.equals("")) return s; - try { - if (s.charAt(0) != '\'' && (s.indexOf(' ') > -1 || s.indexOf('&') > -1 || s.indexOf(',') > -1 || s.indexOf('(') > -1)) { - if (s.indexOf("'") == -1) // normal case of no embedded ' s - return "'" + s + "'"; - return "\"" + s + "\""; - } - } catch (StringIndexOutOfBoundsException e) { - } - return s; - } - - /** - * return cell address with $'s e.g. - * cell AB12 ==> $AB$12 - * cell Sheet1!C2=>Sheet1!$C$2 - * Does NOT handle ranges - * - * @param s - * @return - */ - public static String qualifyCellAddress(String s) { - String prefix = ""; - if (s.indexOf("$") == -1) { // it's not qualified yet - int i = s.indexOf("!"); - if (i > -1) { - prefix = s.substring(0, i + 1); - s = s.substring(i + 1); - } - s = "$" + s; - i = 1; - while (i < s.length() && !Character.isDigit(s.charAt(i++))) ; - i--; - if (i > 0 && i < s.length()) - s = s.substring(0, i) + "$" + s.substring(i); - } - return prefix + s; - } - - public static int getArrayLen(Object o) { - int len = 0; - if (o instanceof double[]) - len = ((double[]) o).length; - return len; - } - - /** - * generic reading of a row byte pair with handling for Excel 2007 if necessary - * - * @param b0 - * @param b1 - * @return int row - */ - public int readRow(byte b0, byte b1) { - if ((parent_rec != null && !parent_rec.getWorkBook().getIsExcel2007())) { - int rw = io.starter.toolkit.ByteTools.readInt(b0, b1, (byte) 0, (byte) 0); - if (rw >= MAXROWS_BIFF8 - 1 || rw < 0 || this instanceof PtgRefN) // PtgRefN's are ALWAYS relative and therefore never over 32xxx - rw = ByteTools.readShort(b0, b1); - return rw; - } - // issue when reading Excel2007 rw from bytes as limits exceed ... try to interpret as best one can - int rw = io.starter.toolkit.ByteTools.readInt(b0, b1, (byte) 0, (byte) 0); - if (rw == 65535) { // have to assume that this means a wholeCol reference - rw = -1; - ((PtgRef) this).wholeCol = true; - } - return rw; - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - parent_rec = null; - trackercell = null; - // vars?? - - } - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/GenericPtg.kt b/src/main/java/io/starter/formats/XLS/formulas/GenericPtg.kt new file mode 100644 index 0000000..146edff --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/GenericPtg.kt @@ -0,0 +1,697 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + + +import io.starter.OpenXLS.DateConverter +import io.starter.formats.XLS.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.Calendar + +abstract class GenericPtg : Ptg, Cloneable { + + internal var doublePrecision = 0.00000001 // doubles/floats cannot be compared for exactness so use precision comparator + override var opcode: Byte = 0 + internal set + /** + * So, here you see we can get the static type from the record itself + * then format the output record. Some shorthand techniques are shown. + */ + override var record: ByteArray + internal set + + internal var vars: Array? = null + /** + * a locking mechanism so that Ptgs are not endlessly + * re-calculated + * + * @return + */ + /** + * a locking mechanism so that Ptgs are not endlessly + * re-calculated + * + * @return + */ + override var lock = -1 + /** + * returns the Location Policy of the Ptg is locked + * used during automated BiffRec movement updates + * + * @return int + */ + /** + * lock the Location of the Ptg so that it will not + * be updated during automated BiffRec movement updates + * + * @param b setting of the lock the location policy for this Ptg + */ + override var locationPolicy = Ptg.PTG_LOCATION_POLICY_UNLOCKED + /** + * @return Returns the trackercell. + */ + /** + * @param trackercell The trackercell to set. + */ + override var trackercell: BiffRec? = null + + // determine behavior + override val isOperator: Boolean + get() = false + + override val isBinaryOperator: Boolean + get() = false + + override val isUnaryOperator: Boolean + get() = false + + override val isStandAloneOperator: Boolean + get() = false + + override val isPrimitiveOperator: Boolean + get() = false + + override val isOperand: Boolean + get() = false + + override val isFunction: Boolean + get() = false + + override val isControl: Boolean + get() = false + + override val isArray: Boolean + get() = false + + override val isReference: Boolean + get() = false + + /** + * Returns the number of Params to pass to the Ptg + */ + override val numParams: Int + get() = if (isPrimitiveOperator) 2 else 0 + + /* + Return all of the cells in this range as an array + of Ptg's. This is used for range calculations. + */ + override val components: Array? + get() = null + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + override// KSC: added to skip External name reference for Add-in Formulas + // 20060408 KSC: added quoting in PtgStr.getTextString + // if (vars[x] instanceof PtgStr) // 20060214 KSC: Quote string params + // part= "\"" + part + "\""; + /*if(!part.equals(""))*/// don't strip 1st paren if no params! 20060501 KSC + // strip trailing comma + val textString: String + get() { + + var strx: String? = "" + + try { + strx = string + } catch (e: Exception) { + Logger.logErr("Function not supported: " + this.parentRec!!.toString()) + } + + if (strx == null) + return "" + + val out = StringBuffer(strx) + if (vars != null) { + val numvars = vars!!.size + if (this.isPrimitiveOperator && this.isUnaryOperator) { + if (numvars > 0) + out.append(vars!![0].textString) + + } else if (this.isPrimitiveOperator) { + out.setLength(0) + for (x in 0 until numvars) { + out.append(vars!![x].textString) + if (x + 1 < numvars) out.append(this.string) + } + } else if (this.isControl) { + for (x in 0 until numvars) { + out.append(vars!![x].textString) + } + } else { + for (x in vars!!.indices) { + if (!(x == 0 && vars!![x] is PtgNameX)) { + val part = vars!![x].textString + out.append(part) + out.append(",") + } + } + if (vars!!.size > 0) + out.setLength(out.length - 1) + } + } + out.append(string2) + return out.toString() + } + + /*text1 and 2 for this Ptg + */ + override val string: String + get() = toString() + + /** + * return the human-readable String representation of + * the "closing" portion of this Ptg + * such as a closing parenthesis. + */ + + override val string2: String + get() { + if (this.isPrimitiveOperator) return "" + return if (this.isOperator) ")" else "" + } + + /** + * Gets the (return) value of this Ptg as an operand Ptg. + */ + override val ptgVal: Ptg + get() { + val value = this.value + return value as? Ptg ?: if (value is Boolean) + PtgBool(value.booleanValue()) + else if (value is Int) + PtgInt(value.toInt()) + else if (value is Number) + PtgNumber(value.toDouble()) + else if (value is String) + PtgStr(value as String?) + else + PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * returns the value of an operand ptg. + * + * @return null for non-operand Ptg. + */ + override val value: Any? + get() = null + + /** + * Gets the value of the ptg represented as an int. + * + * + * This can result in loss of precision for floating point values. + * + * + * overridden in PtgInt to natively return value. + * + * @return integer representing the ptg, or NAN + */ + override// we should be throwing something better + // don't report an error if it's already an error + /// RIIIIGHT! throw new FormulaCalculationException(); + val intVal: Int + get() { + try { + return Double(this.value!!.toString()).toInt() + } catch (e: NumberFormatException) { + if (this !is PtgErr) + Logger.logErr("GetIntVal failed for formula: " + this.parentRec!!.toString() + " " + e) + return 0 + } + + } + + /** + * Gets the value of the ptg represented as an double. + * + * + * This can result in loss of precision for floating point values. + * + * + * NAN will be returned for values that are not translateable to an double + * + * + * overrideen in PtgNumber + * + * @return integer representing the ptg, or NAN + */ + override// Logger.logWarn("Error in Ptg Calculator getting Double Value: " + e3); + val doubleVal: Double + get() { + var pob: Any? = null + var d: Double? = null + try { + pob = this.value + if (pob == null) { + Logger.logErr("Unable to calculate Formula at " + this.location!!) + return java.lang.Double.NaN + } + d = pob as Double? + } catch (e: ClassCastException) { + try { + val f = pob as Float? + d = f!!.toDouble() + } catch (e2: ClassCastException) { + try { + val `in` = pob as Int? + d = `in`!!.toDouble() + } catch (e3: Exception) { + if (pob == null || pob.toString() == "") { + d = 0 + } else { + try { + return Double(pob.toString()) + } catch (e4: Exception) { + return java.lang.Double.NaN + } + + } + } + + } + + } catch (exp: Throwable) { + Logger.logErr("Unexpected Exception in PtgCalculator.getDoubleValue()", exp) + } + + return d!!.toDouble() + } + + // these do nothing here... + override var location: String? + @Throws(FormulaNotFoundException::class) + get() = null + set(s) {} + + override val intLocation: IntArray? + @Throws(FormulaNotFoundException::class) + get() = null + + // Parent Rec is the BiffRec record referenced by Operand Ptgs + override var parentRec: XLSRecord? = null + + //TODO: PtgRef.isBlank should override! + override val isBlank: Boolean + get() = false + + + override fun clone(): Any? { + try { + return super.clone() + } catch (e: CloneNotSupportedException) { + // This is, in theory, impossible + return null + } + + } + + /** + * update the Ptg + */ + override fun updateRecord() { + + } + + /* ################################################### EXPLANATION ################################################### + + 1. set string varetvar in all Ptgs + 2. varetvar goes between ptg return vals if any + 3. if this is a funcvar then we loop ptgs and out + 4. when we call getString or evaluate, we loop into the + recursive tree and execute on up. + + ################################################### EXPLANATION ###################################################*/ + + + /** + * Operator Ptgs take other Ptgs as arguments + * so we need to pass them in to get a meaningful + * value. + */ + override fun setVars(parr: Array) { + this.vars = parr + } + + /** + * pass in arbitrary number of values (probably other Ptgs) + * and return the resultant value. + * + * + * This effectively calculates the Expression. + */ + override fun evaluate(obj: Array): Any { + // do something useful + return this.string + } + + open fun init(b: ByteArray) { + opcode = b[0] + record = b + } + + /** + * return a Ptg consisting of the calculated values + * of the ptg's passed in. Returns null for any non-operand + * ptg. + * + * @throws CalculationException + */ + @Throws(FunctionNotSupportedException::class, CalculationException::class) + override fun calculatePtg(parsething: Array): Ptg? { + return null + + } + + /** + * if the Ptg needs to keep a handle to a cell, this is it... + * tells the Ptg to get it on its own... + */ + override fun updateAddressFromTrackerCell() { + this.initTrackerCell() + val trk = trackercell + if (trk != null) { + val nad = trk.cellAddress + location = nad + } + } + + /** + * if the Ptg needs to keep a handle to a cell, this is it... + * tells the Ptg to get it on its own... + */ + override fun initTrackerCell() { + if (trackercell == null) { + try { + val trk = this.parentRec!!.sheet!!.getCell(this.location) + trackercell = trk + } catch (e: Exception) { + Logger.logWarn("Formula reference could not initialize:$e") + } + + } + } + + /** + * generic reading of a row byte pair with handling for Excel 2007 if necessary + * + * @param b0 + * @param b1 + * @return int row + */ + fun readRow(b0: Byte, b1: Byte): Int { + if (parentRec != null && !parentRec!!.workBook!!.isExcel2007) { + var rw = io.starter.toolkit.ByteTools.readInt(b0, b1, 0.toByte(), 0.toByte()) + if (rw >= XLSConstants.MAXROWS_BIFF8 - 1 || rw < 0 || this is PtgRefN) + // PtgRefN's are ALWAYS relative and therefore never over 32xxx + rw = ByteTools.readShort(b0.toInt(), b1.toInt()).toInt() + return rw + } + // issue when reading Excel2007 rw from bytes as limits exceed ... try to interpret as best one can + var rw = io.starter.toolkit.ByteTools.readInt(b0, b1, 0.toByte(), 0.toByte()) + if (rw == 65535) { // have to assume that this means a wholeCol reference + rw = -1 + (this as PtgRef).isWholeCol = true + } + return rw + } + + /** + * clear out object references in prep for closing workbook + */ + override fun close() { + parentRec = null + trackercell = null + // vars?? + + } + + companion object { + val serialVersionUID = 666555444333222L + + /** + * Returns an array of doubles from number-type ptg's sent in. + * This should only be referenced by sub-classes. + * + * + * Null values accessed are treated as 0. Within excel (empty cell values == 0) Tested! + * Sometimes as well you can get empty string values, "". These are NOT EQUAL ("" != 0) + * + * @param pthings + * @return + */ + protected fun getValuesFromPtgs(pthings: Array): Array? { + val obar = arrayOfNulls(pthings.size) + for (t in obar.indices) { + if (pthings[t] is PtgErr) + return null + if (pthings[t] is PtgArray) { + obar[t] = pthings[t].components // get all items in array as Ptgs + var v: Any? = null + try { + v = getValuesFromObjects(obar[t] as Array) // get value array from the ptgs + } catch (e: NumberFormatException) { // string or non-numeric values + v = getStringValuesFromPtgs(obar[t] as Array) + } + + obar[t] = v + } else { + val pval = pthings[t].value + if (pval is PtgArray) { + obar[t] = pval.components // get all items in array as Ptgs + var v: Any? = null + try { + v = getValuesFromObjects(obar[t] as Array) // get value array from the ptgs + } catch (e: NumberFormatException) { // string or non-numeric values + v = getStringValuesFromPtgs(obar[t] as Array) + } + + obar[t] = v + } else if (pval is Name) { // then get it's components ... + obar[t] = pthings[t].components + var v: Any? = null + try { + v = getValuesFromPtgs(obar[t] as Array) // get value array from the ptgs + } catch (e: NumberFormatException) { // string or non-numeric values + v = getStringValuesFromPtgs(obar[t] as Array) + } + + obar[t] = v + } else { // it's a single value + try { + obar[t] = getDoubleValueFromObject(pval) + } catch (e: NumberFormatException) { + if (pval is CalculationException) + obar[t] = pval.toString() + else + obar[t] = pval + } + + } + } + } + return obar + } + + + /** + * Returns an array of doubles from number-type ptg's sent in. + * This should only be referenced by sub-classes. + * + * + * Null values accessed are treated as 0. Within excel (empty cell values == 0) Tested! + * Sometimes as well you can get empty string values, "". These are NOT EQUAL ("" != 0) + * + * @param pthings + * @return + */ + @Throws(NumberFormatException::class) + protected fun getValuesFromObjects(pthings: Array): DoubleArray { + val returnDbl = DoubleArray(pthings.size) + for (i in pthings.indices) { + + // Object o = pthings[i].getValue(); + val o = pthings[i] + + if (o == null) { // NO!! "" is NOT "0", blank is, but not a zero length string. Causes calc errors, need to handle diff somehow20081103 KSC: don't error out if "" */ + returnDbl[i] = 0.0 + } else if (o is Double) { + returnDbl[i] = o.toDouble() + } else if (o is Int) { + returnDbl[i] = o.toInt().toDouble() + } else if (o is Boolean) { // Excel converts booleans to numbers in calculations 20090129 KSC + returnDbl[i] = if (o.booleanValue()) 1.0 else 0.0 + } else if (o is PtgBool) { + returnDbl[i] = if ((o.value as Boolean).booleanValue()) 1.0 else 0.0 + } else if (o is PtgErr) { + // ? + } else { + val s = o.toString() + val d = Double(s) + returnDbl[i] = d + } + } + return returnDbl + } + + /** + * convert a value to a double, throws exception if cannot + * + * @param o + * @throws NumberFormatException + * @return double value if possible + */ + @Throws(NumberFormatException::class) + fun getDoubleValue(o: Any?, parent: XLSRecord?): Double { + if (o is Double) + return o.toDouble() + if (o == null || o.toString() == "") { + // empty string is interpreted as 0 if show zero values + if (parent != null && parent.sheet!!.window2!!.showZeroValues) + return 0.0 + // otherwise, throw error + throw NumberFormatException() + } + return Double(o.toString()) // will throw NumberFormatException if cannot convert + } + + /** + * converts a single Ptg number-type value to a double + */ + fun getDoubleValueFromObject(o: Any?): Double { + var ret = 0.0 + if (o == null) { // 20081103 KSC: don't error out if "" */ + ret = 0.0 + } else if (o is Double) { + ret = o.toDouble() + } else if (o is Int) { + ret = o.toInt().toDouble() + } else if (o is Boolean) { // Excel converts booleans to numbers in calculations 20090129 KSC + ret = if (o.booleanValue()) 1.0 else 0.0 + } else if (o is PtgErr) { + // ? + } else { + val s = o.toString() + // handle formatted dates from fields like TEXT() calcs + if (s.indexOf("/") > -1) { + try { + val c = DateConverter.convertStringToCalendar(s) + if (c != null) ret = DateConverter.getXLSDateVal(c) + } catch (e: Exception) {//guess not + } + + } + if (ret == 0.0) { + val d = Double(s) + ret = d + } + } + return ret + } + + /** + * returns an array of strings from ptg's sent in. + * This should only be referenced by sub-classes. + */ + protected fun getStringValuesFromPtgs(pthings: Array): Array { + val returnStr = arrayOfNulls(pthings.size) + for (i in pthings.indices) { + if (pthings[i] is PtgErr) + return arrayOf("#VALUE!") // 20081202 KSC: return error value ala Excel + + val o = pthings[i].value + if (o != null) { // 20070215 KSC: avoid nullpointererror + try { // 20090205 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 + returnStr[i] = (o as Double).toInt().toString() + } catch (e: Exception) { + val s = o.toString() + returnStr[i] = s + } + + } else + returnStr[i] = "null" // 20070216 KSC: Shouldn't match empty string! + } + return returnStr + } + + /** + * return properly quoted sheetname + * + * @param s + * @return + */ + fun qualifySheetname(s: String?): String? { + if (s == null || s == "") return s + try { + if (s[0] != '\'' && (s.indexOf(' ') > -1 || s.indexOf('&') > -1 || s.indexOf(',') > -1 || s.indexOf('(') > -1)) { + return if (s.indexOf("'") == -1) "'$s'" else "\"" + s + "\"" + } + } catch (e: StringIndexOutOfBoundsException) { + } + + return s + } + + /** + * return cell address with $'s e.g. + * cell AB12 ==> $AB$12 + * cell Sheet1!C2=>Sheet1!$C$2 + * Does NOT handle ranges + * + * @param s + * @return + */ + fun qualifyCellAddress(s: String): String { + var s = s + var prefix = "" + if (s.indexOf("$") == -1) { // it's not qualified yet + var i = s.indexOf("!") + if (i > -1) { + prefix = s.substring(0, i + 1) + s = s.substring(i + 1) + } + s = "$$s" + i = 1 + while (i < s.length && !Character.isDigit(s[i++])); + i-- + if (i > 0 && i < s.length) + s = s.substring(0, i) + "$" + s.substring(i) + } + return prefix + s + } + + fun getArrayLen(o: Any): Int { + var len = 0 + if (o is DoubleArray) + len = o.size + return len + } + } + + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/IlblListener.java b/src/main/java/io/starter/formats/XLS/formulas/IlblListener.kt similarity index 82% rename from src/main/java/io/starter/formats/XLS/formulas/IlblListener.java rename to src/main/java/io/starter/formats/XLS/formulas/IlblListener.kt index 9a01f01..ccfc8a1 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/IlblListener.java +++ b/src/main/java/io/starter/formats/XLS/formulas/IlblListener.kt @@ -20,42 +20,41 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /** * An IlblListener is aware of changes to the Named range references. - *

                      + * + * * Notably, the PtgName recs... */ -public interface IlblListener { +interface IlblListener { /** * @return Returns the Ilbl. */ - short getIlbl(); - /** * @param Ilbl The Ilbl to set. */ - void setIlbl(short ixti); + var ilbl: Short /** - * Add this to the ilbl listeners + * get the name of the named range this refers to + * + * @return */ - void addListener(); + val storedName: String /** - * Store the name string for matching missing references - * - * @param name + * Add this to the ilbl listeners */ - void storeName(String name); + fun addListener() /** - * get the name of the named range this refers to + * Store the name string for matching missing references * - * @return + * @param name */ - String getStoredName(); + fun storeName(name: String) } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.java deleted file mode 100644 index bd22fcb..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.java +++ /dev/null @@ -1,722 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.*; -import io.starter.toolkit.Logger; - -import java.text.DecimalFormat; - - - -/* - InformationCalculator is a collection of static methods that operate - as the Microsoft Excel function calls do. - - All methods are called with an array of ptg's, which are then - acted upon. A Ptg of the type that makes sense (ie boolean, number) - is returned after each method. -*/ - -public class InformationCalculator { - - - /** - * CELL - * Returns information about the formatting, location, or contents of a cell - * The CELL function returns information about the formatting, location, or contents of a cell. - *

                      - * CELL(info_type, [reference]) - * info_type Required. A text value that specifies what type of cell information you want to return. The following list shows the possible values of the info_type argument and the corresponding results.info_type Returns - * "address" Reference of the first cell in reference, as text. - * "col" Column number of the cell in reference. - * "color" The value 1 if the cell is formatted in color for negative values; otherwise returns 0 (zero). - * "contents" Value of the upper-left cell in reference; not a formula. - * "filename" Filename (including full path) of the file that contains reference, as text. Returns empty text ("") if the worksheet that contains reference has not yet been saved. - * "format" Text value corresponding to the number format of the cell. The text values for the various formats are shown in the following table. Returns "-" at the end of the text value if the cell is formatted in color for negative values. Returns "()" at the end of the text value if the cell is formatted with parentheses for positive or all values. - * "parentheses" The value 1 if the cell is formatted with parentheses for positive or all values; otherwise returns 0. - * "prefix" Text value corresponding to the "label prefix" of the cell. Returns single quotation mark (') if the cell contains left-aligned text, double quotation mark (") if the cell contains right-aligned text, caret (^) if the cell contains centered text, backslash (\) if the cell contains fill-aligned text, and empty text ("") if the cell contains anything else. - * "protect" The value 0 if the cell is not locked; otherwise returns 1 if the cell is locked. - * "row" Row number of the cell in reference. - * "type" Text value corresponding to the type of data in the cell. Returns "b" for blank if the cell is empty, "l" for label if the cell contains a text constant, and "v" for value if the cell contains anything else. - * "width" Column width of the cell, rounded off to an integer. Each unit of column width is equal to the width of one character in the default font size. - *

                      - * reference Optional. The cell that you want information about. If omitted, the information specified in the info_type argument is returned for the last cell that was changed. If the reference argument is a range of cells, the CELL function returns the information for only the upper left cell of the range. - */ - protected static Ptg calcCell(Ptg[] operands) - throws FunctionNotSupportedException { - String type = operands[0].getValue().toString().toLowerCase(); - PtgRef ref = null; - BiffRec cell = null; - if (operands.length > 1) { - ref = (PtgRef) operands[1]; - try { - cell = ref.getParentRec().getWorkBook().getCell(ref.getLocationWithSheet()); - } catch (CellNotFoundException e) { - try { - String sh = null; - try { - sh = ref.getSheetName(); - } catch (WorkSheetNotFoundException we) { - } - if (sh == null) sh = ref.getParentRec().getSheet().getSheetName(); - cell = ref.getParentRec().getWorkBook().getWorkSheetByName(sh).addValue(null, ref.getLocation()); - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - // If ref param is omitted, the information specified in the info_type argument - // is returned for the last cell that was changed - } else if (!type.equals("filename"))// no ref was passed in and option is not "filename" - // We cannot determine which is the "last cell" they are referencing; - throw new FunctionNotSupportedException("Worsheet function CELL with no reference parameter is not supported"); - else // filename option can use any biffrec ... - cell = operands[0].getParentRec(); - - // at this point both ref (PtgRef) and r (BiffRec) should be valid - try { - if (type.equals("address")) { - PtgRef newref = ref; - newref.clearLocationCache(); - newref.fColRel = false; // make absolute - newref.fRwRel = false; - return new PtgStr(newref.getLocation()); - } else if (type.equals("col")) { - return new PtgNumber(ref.getIntLocation()[1] + 1); - } else if (type.equals("color")) { // The value 1 if the cell is formatted in color for negative values; otherwise returns 0 (zero). - String s = cell.getFormatPattern(); - if (s.indexOf(";[Red") > -1) - return new PtgNumber(1); - return new PtgNumber(0); - } else if (type.equals("contents")) {// Value of the upper-left cell in reference; not a formula. - return new PtgStr(cell.getStringVal()); - } else if (type.equals("filename")) { - String f = cell.getWorkBook().getFileName(); - String sh = cell.getSheet().getSheetName(); - int i = f.lastIndexOf(java.io.File.separatorChar); - f = f.substring(0, i + 1) + "[" + f.substring(i + 1); - f += "]" + sh; - return new PtgStr(f); - } else if (type.equals("format")) { // Text value corresponding to the number format of the cell. The text values for the various formats are shown in the following table. Returns "-" at the end of the text value if the cell is formatted in color for negative values. Returns "()" at the end of the text value if the cell is formatted with parentheses for positive or all values. - String s = cell.getFormatPattern(); - String ret = "G"; // default? - if (s.equals("General") || - s.equals("# ?/?") || - s.equals("# ??/??")) { - ret = "G"; - } else if (s.equals("0")) { - ret = "F0"; - } else if (s.equals("#,##0")) { - ret = ",0"; - } else if (s.equals("0.00")) { - ret = "F2"; - } else if (s.equals("#,##0.00")) { - ret = ", 2"; - } else if (s.equals("$#,##0_);($#,##0)")) { - ret = "C0"; - } else if (s.equals("$#,##0_);[Red]($#,##0)")) { - ret = "C0-"; - } else if (s.equals("$#,##0.00_);($#,##0.00)")) { - ret = "C2"; - } else if (s.equals("$#,##0.00_);[Red]($#,##0.00)")) { - ret = "C2-"; - } else if (s.equals("0%")) { - ret = "P0"; - } else if (s.equals("0.00%")) { - ret = "P2"; - } else if (s.equals("0.00E+00")) { - ret = "S2"; -// m/d/yy or m/d/yy h:mm or mm/dd/yy "D4" - } else if (s.equals("m/d/yy") || - s.equals("m/d/yy h:mm") || - s.equals("mm/dd/yy") || - s.equals("mm-dd-yy")) { // added last to accomodate Excel's regional short date setting (format #14) - ret = "D4"; - } else if (s.equals("d-mmm-yy") || - s.equals("dd-mmm-yy")) { - ret = "D1"; - } else if (s.equals("d-mmm") || - s.equals("dd-mmm")) { - ret = "D2"; - } else if (s.equals("mmm-yy")) { - ret = "D3"; - } else if (s.equals("mm/dd")) { - ret = "D5"; - } else if (s.equals("h:mm AM/PM")) { - ret = "D7"; - } else if (s.equals("h:mm:ss AM/PM")) { - ret = "D6"; - } else if (s.equals("h:mm")) { - ret = "D9"; - } else if (s.equals("h:mm:ss")) { - ret = "D8"; - } - return new PtgStr(ret); - } else if (type.equals("parentheses")) { - String s = cell.getFormatPattern(); - if (s.startsWith("(")) - return new PtgNumber(1); - return new PtgNumber(0); - } else if (type.equals("prefix")) { - // TODO: THIS IS NOT CORRECT - EITHER INFORM USER OR ?? -// DOESN'T APPEAR TO MATCH EXCEL - //Text value corresponding to the "label prefix" of the cell. - // Returns single quotation mark (') if the cell contains left-aligned text, double quotation mark (") if the cell contains right-aligned text, - // caret (^) if the cell contains centered text, backslash (\) if the cell contains fill-aligned text, and empty text ("") if the cell contains anything else. - int al = cell.getXfRec().getHorizontalAlignment(); - if (al == FormatConstants.ALIGN_LEFT) - return new PtgStr("'"); - if (al == FormatConstants.ALIGN_CENTER) - return new PtgStr("^"); - if (al == FormatConstants.ALIGN_RIGHT) - return new PtgStr("\""); - if (al == FormatConstants.ALIGN_FILL) - return new PtgStr("\\"); - return new PtgStr(""); - } else if (type.equals("protect")) { - if (cell.getXfRec().isLocked()) - return new PtgNumber(1); - return new PtgNumber(0); - } else if (type.equals("row")) { - return new PtgNumber(ref.getIntLocation()[0] + 1); - } else if (type.equals("type")) { - //Text value corresponding to the type of data in the cell. - // Returns "b" for blank if the cell is empty, - //"l" for label if the cell contains a text constant, and - // "v" for value if the cell contains anything else. - if (((XLSRecord) cell).isBlank) - return new PtgStr("b"); - if (cell instanceof Labelsst) - return new PtgStr("l"); - return new PtgStr("v"); - } else if (type.equals("width")) { - int n = 0; - n = cell.getSheet().getColInfo(cell.getColNumber()).getColWidthInChars(); - return new PtgNumber(n); - } - } catch (Exception e) { - Logger.logWarn("CELL: unable to calculate: " + e.toString()); - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * ERROR.TYPE - * Returns a number corresponding to an error type - * If error_val is - * ERROR.TYPE returns - *

                      - * #NULL! 1 - * #DIV/0! 2 - * #VALUE! 3 - * #REF! 4 - * #NAME? 5 - * #NUM! 6 - * #N/A 7 - * Anything else #N/A - */ - protected static Ptg calcErrorType(Ptg[] operands) { - Object o = operands[0].getValue(); - String s = o.toString(); - if (s.equalsIgnoreCase("#NULL!")) return new PtgInt(1); - if (s.equalsIgnoreCase("#DIV/0!")) return new PtgInt(2); - if (s.equalsIgnoreCase("#VALUE!")) return new PtgInt(3); - if (s.equalsIgnoreCase("#REF!")) return new PtgInt(4); - if (s.equalsIgnoreCase("#NAME?")) return new PtgInt(5); - if (s.equalsIgnoreCase("#NUM!")) return new PtgInt(6); - if (s.equalsIgnoreCase("#N/A")) return new PtgInt(7); - return new PtgErr(PtgErr.ERROR_NA); - } - - /** - * INFO - * Returns information about the current operating environment - * INFO(type_text) - *

                      - * NOTE: Several options are incomplete: - * "osversion" -- only valid for Windows versions - * "system" -- only valid for Windows and Mac - * "release" -- incomplete - * "origin" -- does not return R1C1 format - */ - protected static Ptg calcInfo(Ptg[] operands) { - // validate - if (operands == null || operands.length == 0 || operands[0].getParentRec() == null) - return new PtgErr(PtgErr.ERROR_VALUE); - String type_text = operands[0].getString(); - String ret = ""; - if (type_text.equals("directory")) // Path of the current directory or folder - return new PtgStr(System.getProperty("user.dir").toLowerCase() + "\\"); - else if (type_text.equals("numfile")) // number of active worksheets in the current workbook -// TODO: what is correct definition of "Active Worksheets" - hidden state doesn't seem to affect" - return new PtgNumber(operands[0].getParentRec().getWorkBook().getNumWorkSheets()); - else if (type_text.equals("origin")) { /* Returns the absolute cell reference of the top and leftmost - cell visible in the window, based on the current scrolling - position, as text prepended with "$A:". - This value is intended for for Lotus 1-2-3 release 3.x compatibility. - The actual value returned depends on the current reference - style setting. Using D9 as an example, the return value would be: - A1 reference style "$A:$D$9". - R1C1 reference style "$A:R9C4" - */ -// TODO: FINISH R1C1 reference style - String cell = operands[0].getParentRec().getSheet().getWindow2().getTopLeftCell(); - for (int i = cell.length() - 1; i >= 0; i--) { - if (!Character.isDigit(cell.charAt(i))) { - cell = cell.substring(0, i + 1) + "$" + cell.substring(i + 1); - break; - } - } - cell = "$A:$" + cell; - return new PtgStr(cell); - } else if (type_text.equals("osversion")) { //Current operating system version, as text. - // see end of file for os info - String osversion = System.getProperty("os.version"); - String n = System.getProperty("os.name"); // Windows Vista - String os = ""; - // TODO: need a list of osversions to compare to! have know idea for mac, linux ... - if (n.startsWith("Windows")) { - double v = new Double(osversion); - os = "Windows (32-bit) "; - if (v >= 5) - os += "NT "; - DecimalFormat df = new DecimalFormat("##.00"); - os += df.format(v); - } // otherwise have NO idea as cannot find any info on net - else os += osversion; - return new PtgStr(os); - } else if (type_text.equals("recalc")) { //Current recalculation mode; returns "Automatic" or "Manual". - if (operands[0].getParentRec().getWorkBook().getRecalculationMode() == 0) // manual - return new PtgStr("Manual"); - return new PtgStr("Automatic"); - } else if (type_text.equals("release")) { //Version of Microsoft Excel, as text. - // TODO: Finish! 97= 8.0, 2000= 9.0, 2002 (XP)= 10.0, 2003= 11.0, 2007= 12.0 - Logger.logWarn("Worksheet Function INFO(\"release\") is not supported"); - return new PtgStr(""); - } else if (type_text.equals("system")) // Name of the operating environment: Macintosh = "mac" Windows = "pcdos" - // TODO: linux? **************** - if (System.getProperty("os.name").indexOf("Windows") >= 0) - return new PtgStr("pcdos"); - else - return new PtgStr("mac"); - // In previous versions of Microsoft Office Excel, the "memavail", "memused", and "totmem" type_text values, returned memory information. - // These type_text values are no longer supported and now return a #N/A error value. - else if (type_text.equals("memavail") || - type_text.equals("memused") || - type_text.equals("totmem")) - return new PtgErr(PtgErr.ERROR_NA); - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * ISBLANK - * ISBLANK determines if the cell referenced is blank, and returns - * a boolean ptg based off that - */ - protected static Ptg calcIsBlank(Ptg[] operands) { - Ptg[] allops = PtgCalculator.getAllComponents(operands); - for (int i = 0; i < allops.length; i++) { - // 20081120 KSC: blanks are handled differently now as Excel counts blank cells as 0's - /*Object o = allops[i].getValue(); - if (o != null) return new PtgBool(false); - */ - if (!allops[i].isBlank()) - return new PtgBool(false); - } - return new PtgBool(true); - } - - /** - * ISERROR - * Value refers to any error value - * (#N/A, #VALUE!, #REF!, #DIV/0!, #NUM!, #NAME?, or #NULL!). - * Usage@ ISERROR(value) - * Return@ PtgBool - */ - protected static Ptg calcIserror(Ptg[] operands) { - if (operands[0] instanceof PtgErr) { - return new PtgBool(true); - } - String[] errorstr = {"#N/A", "#VALUE!", "#REF!", "#DIV/0!", "#NUM!", "#NAME?", "#NULL!"}; - Object o = operands[0].getValue(); - String opval = o.toString(); - for (int i = 0; i < errorstr.length; i++) { - if (opval.equalsIgnoreCase(errorstr[i])) { - return new PtgBool(true); - } - } - return new PtgBool(false); - } - - /** - * ISERR - * Returns TRUE if the value is any error value except #N/A - */ - protected static Ptg calcIserr(Ptg[] operands) { - String[] errorstr = {"#VALUE!", "#REF!", "#DIV/0!", "#NUM!", "#NAME?", "#NULL!"}; - if (operands.length != 1) return new PtgErr(PtgErr.ERROR_VALUE); - Object o = operands[0].getValue(); - String opval = o.toString(); - for (int i = 0; i < errorstr.length; i++) { - if (opval.equalsIgnoreCase(errorstr[i])) { - return new PtgBool(true); - } - } - return new PtgBool(false); - } - - /** - * ISEVEN(number) - *

                      - * Number is the value to test. If number is not an integer, it is truncated. - *

                      - * Remarks - * If number is nonnumeric, ISEVEN returns the #VALUE! error value. - * Examples - * ISEVEN(-1) equals FALSE - * ISEVEN(2.5) equals TRUE - * ISEVEN(5) equals FALSE - *

                      - * author: John - */ - protected static Ptg calcIsEven(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - Object o = operands[0].getValue(); - if (o != null) { - try { // KSC: mod for different number types + mod typo - if (o instanceof Integer) { - int s = ((Integer) o).intValue(); - if (s < 0) return new PtgBool(false); - return new PtgBool((s % 2 == 0)); - } else if (o instanceof Float) { - float s = ((Float) o).floatValue(); - if (s < 0) return new PtgBool(false); - return new PtgBool((s % 2 == 0)); - } else if (o instanceof Double) { - double s = ((Double) o).doubleValue(); - if (s < 0) return new PtgBool(false); - return new PtgBool((s % 2 == 0)); - } - } catch (Exception e) { - } - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * ISLOGICAL - * Returns TRUE if the value is a logical value - */ - protected static Ptg calcIsLogical(Ptg[] operands) { - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - Object o = operands[0].getValue(); - // unfortunately we need to know the difference between - // "true" and true, if it's a reference this can be difficult - try { - Boolean b = (Boolean) o; - return new PtgBool(true); - } catch (ClassCastException e) { - } - return new PtgBool(false); - } - - /** - * ISNUMBER - * Returns TRUE if the value is a number - */ - protected static Ptg calcIsNumber(Ptg[] operands) { - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - Object o = operands[0].getValue(); - try { - Float f = (Float) o; - return new PtgBool(true); - } catch (ClassCastException e) { - try { - Double d = (Double) o; - return new PtgBool(true); - } catch (ClassCastException ee) { - try { - Integer ii = (Integer) o; - return new PtgBool(true); - } catch (ClassCastException eee) { - } - } - } - return new PtgBool(false); - } - - /** - * ISNONTEXT - * Returns TRUE if the value is not text - */ - protected static Ptg calcIsNonText(Ptg[] operands) { - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - // blanks return true for this test - if (allops[0].isBlank()) - return new PtgBool(true); - Object o = operands[0].getValue(); - if (o != null) { - try { - String s = (String) o; - return new PtgBool(false); - } catch (ClassCastException e) { - } - } - return new PtgBool(true); - } - - /** - * ISNA - * Value refers to the #N/A - * (value not available) error value. - *

                      - * usage@ ISNA(value) - * return@ PtgBool - */ - protected static Ptg calcIsna(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getError(); - if (operands[0] instanceof PtgErr) { - PtgErr per = (PtgErr) operands[0]; - if (per.getErrorType() == PtgErr.ERROR_NA) { - return new PtgBool(true); - } - } else if (operands[0].getIsReference()) { - Object o = operands[0].getValue(); - if (o.toString().equalsIgnoreCase(new PtgErr(PtgErr.ERROR_NA).toString())) { - return new PtgBool(true); - } - } - return new PtgBool(false); - } - - /** - * NA - * Returns the error value #N/A - */ - protected static Ptg calcNa(Ptg[] operands) { - return new PtgErr(PtgErr.ERROR_NA); - } - - /** - * ISTEXT - * Returns TRUE if the value is text - */ - protected static Ptg calcIsText(Ptg[] operands) { - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - Object o = operands[0].getValue(); - if (o != null) { - try { - String s = (String) o; - return new PtgBool(true); - } catch (ClassCastException e) { - } - } - return new PtgBool(false); - } - - - /** - * ISODD - * Returns TRUE if the number is odd - *

                      - * author: John - */ - protected static Ptg calcIsOdd(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg[] allops = PtgCalculator.getAllComponents(operands); - if (allops.length > 1) return new PtgBool(false); - Object o = operands[0].getValue(); - if (o != null) { - try { // KSC: mod for different number types + mod typo - if (o instanceof Integer) { - int s = ((Integer) o).intValue(); - if (s < 0) return new PtgBool(false); - return new PtgBool((s % 2 != 0)); - } else if (o instanceof Float) { - float s = ((Float) o).floatValue(); - if (s < 0) return new PtgBool(false); - return new PtgBool((s % 2 != 0)); - } else if (o instanceof Double) { - double s = ((Double) o).doubleValue(); - if (s < 0) return new PtgBool(false); - return new PtgBool((s % 2 != 0)); - } - } catch (Exception e) { - } - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * ISREF - * Returns TRUE if the value is a reference - */ - protected static Ptg calcIsRef(Ptg[] operands) { - if (operands[0].getIsReference()) { - return new PtgBool(true); - } - return new PtgBool(false); - } - - /** - * N - * Returns a value converted to a number. - *

                      - * Syntax - *

                      - * N(value) - *

                      - * Value is the value you want converted. N converts values listed in the following table. - *

                      - * If value is or refers to - * N returns - *

                      - * A number - * That number - *

                      - * A date, in one of the built-in date formats available in Microsoft Excel - * The serial number of that date --- Note that to us, this is just a number, the date - * format is just that, a format. - *

                      - * TRUE - * 1 - *

                      - * Anything else - * 0 - */ - protected static Ptg calcN(Ptg[] operands) { - Object o = operands[0].getValue(); - if (o instanceof Double || o instanceof Integer || o instanceof Float || o instanceof Long) { - Double d = new Double(o.toString()); - return new PtgNumber(d.doubleValue()); - } - if (o instanceof Boolean) { - Boolean b = (Boolean) o; - boolean bo = b.booleanValue(); - if (bo) return new PtgInt(1); - } - return new PtgInt(0); - } - - /** - * TYPE - * Returns a number indicating the data type of a value - * Value can be any Microsoft Excel value, such as a number, text, logical value, and so on. - * If value is TYPE returns - * Number 1 - * Text 2 - * Logical value 4 - * Error value 16 - * Array 64 - */ - protected static Ptg calcType(Ptg[] operands) { - if (operands[0] instanceof PtgArray) - return new PtgNumber(64); // avoid value calc for arrays - else if (operands[0] instanceof PtgErr) - return new PtgNumber(16); - - // otherwise, test value of operand - Object value = operands[0].getValue(); - int type = 0; - if (value instanceof String) - type = 2; - else if (value instanceof Number) - type = 1; - else if (value instanceof Boolean) - type = 4; - return new PtgNumber(type); - } - -} - -/* - * known INFO function operating systems: - * TODO: need complete list -Windows Vista Windows (32-bit) NT 6.00 -Windows XP Windows (32-bit) NT 5.01 -Windows2000 Windows (32-bit) NT 5.00 -Windows98 Windows (32-bit) 4.10 -Windows95 Windows (32-bit) 4.00 -*/ - -/* -Linux 2.0.31 x86 IBM Java 1.3 -Linux (*) i386 Sun Java 1.3.1, 1.4 or Blackdown Java; (*) os.version depends on Linux Kernel version -Linux (*) x86_64 Blackdown Java; note x86_64 might change to amd64; (*) os.version depends on Linux Kernel version -Linux (*) sparc Blackdown Java; (*) os.version depends on Linux Kernel version -Linux (*) ppc Blackdown Java; (*) os.version depends on Linux Kernel version -Linux (*) armv41 Blackdown Java; (*) os.version depends on Linux Kernel version -Linux (*) i686 GNU Java Compiler (GCJ); (*) os.version depends on Linux Kernel version -Linux (*) ppc64 IBM Java 1.3; (*) os.version depends on Linux Kernel version -Mac OS 7.5.1 PowerPC -Mac OS 8.1 PowerPC -Mac OS 9.0, 9.2.2 PowerPC MacOS 9.0: java.version=1.1.8, mrj.version=2.2.5; MacOS 9.2.2: java.version=1.1.8 mrj.version=2.2.5 -Mac OS X 10.1.3 ppc -Mac OS X 10.2.6 ppc Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_01-39) -Java HotSpot(TM) Client VM (build 1.4.1_01-14, mixed mode) -Mac OS X 10.2.8 ppc using 1.3 JVM: java.vm.version=1.3.1_03-74, mrj.version=3.3.2; using 1.4 JVM: java.vm.version=1.4.1_01-24, mrj.version=69.1 -Mac OS X 10.3.1, 10.3.2, 10.3.3, 10.3.4 ppc JDK 1.4.x -Mac OS X 10.3.8 ppc Mac OS X 10.3.8 Server; using 1.3 JVM: java.vm.version=1.3.1_03-76, mrj.version=3.3.3; using 1.4 JVM: java.vm.version=1.4.2-38; mrj.version=141.3 -Windows 95 4.0 x86 -Windows 98 4.10 x86 Note, that if you run Sun JDK 1.2.1 or 1.2.2 Windows 98 identifies itself as Windows 95. -Windows Me 4.90 x86 -Windows NT 4.0 x86 -Windows 2000 5.0 x86 -Windows XP 5.1 x86 Note, that if you run older Java runtimes Windows XP identifies itself as Windows 2000. -Windows 2003 5.2 x86 java.vm.version=1.4.2_06-b03; Note, that Windows Server 2003 identifies itself only as Windows 2003. -Windows CE 3.0 build 11171 arm Compaq iPAQ 3950 (PocketPC 2002) -OS/2 20.40 x86 -Solaris 2.x sparc -SunOS 5.7 sparc Sun Ultra 5 running Solaris 2.7 -SunOS 5.8 sparc Sun Ultra 2 running Solaris 8 -SunOS 5.9 sparc Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0_01-b03) -Java HotSpot(TM) Client VM (build 1.4.0_01-b03, mixed mode) -MPE/iX C.55.00 PA-RISC -HP-UX B.10.20 PA-RISC JDK 1.1.x -HP-UX B.11.00 PA-RISC JDK 1.1.x -HP-UX B.11.11 PA-RISC JDK 1.1.x -HP-UX B.11.11 PA_RISC JDK 1.2.x/1.3.x; note Java 2 returns PA_RISC and Java 1 returns PA-RISC -HP-UX B.11.00 PA_RISC JDK 1.2.x/1.3.x -HP-UX B.11.23 IA64N JDK 1.4.x -HP-UX B.11.11 PA_RISC2.0 JDK 1.3.x or JDK 1.4.x, when run on a PA-RISC 2.0 system -HP-UX B.11.11 PA_RISC JDK 1.2.x, even when run on a PA-RISC 2.0 system -HP-UX B.11.11 PA-RISC JDK 1.1.x, even when run on a PA-RISC 2.0 system -AIX 5.2 ppc64 sun.arch.data.model=64 -AIX 4.3 Power -AIX 4.1 POWER_RS -OS/390 390 02.10.00 J2RE 1.3.1 IBM OS/390 Persistent Reusable VM -FreeBSD 2.2.2-RELEASE x86 -Irix 6.3 mips -Digital Unix 4.0 alpha -NetWare 4.11 4.11 x86 -OSF1 V5.1 alpha Java 1.3.1 on Compaq (now HP) Tru64 Unix V5.1 -OpenVMS V7.2-1 alpha Java 1.3.1_1 on OpenVMS 7.2 -*/ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.kt new file mode 100644 index 0000000..9bc350b --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/InformationCalculator.kt @@ -0,0 +1,732 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.* +import io.starter.toolkit.Logger + +import java.text.DecimalFormat + + +/* + InformationCalculator is a collection of static methods that operate + as the Microsoft Excel function calls do. + + All methods are called with an array of ptg's, which are then + acted upon. A Ptg of the type that makes sense (ie boolean, number) + is returned after each method. +*/ + +object InformationCalculator { + + + /** + * CELL + * Returns information about the formatting, location, or contents of a cell + * The CELL function returns information about the formatting, location, or contents of a cell. + * + * + * CELL(info_type, [reference]) + * info_type Required. A text value that specifies what type of cell information you want to return. The following list shows the possible values of the info_type argument and the corresponding results.info_type Returns + * "address" Reference of the first cell in reference, as text. + * "col" Column number of the cell in reference. + * "color" The value 1 if the cell is formatted in color for negative values; otherwise returns 0 (zero). + * "contents" Value of the upper-left cell in reference; not a formula. + * "filename" Filename (including full path) of the file that contains reference, as text. Returns empty text ("") if the worksheet that contains reference has not yet been saved. + * "format" Text value corresponding to the number format of the cell. The text values for the various formats are shown in the following table. Returns "-" at the end of the text value if the cell is formatted in color for negative values. Returns "()" at the end of the text value if the cell is formatted with parentheses for positive or all values. + * "parentheses" The value 1 if the cell is formatted with parentheses for positive or all values; otherwise returns 0. + * "prefix" Text value corresponding to the "label prefix" of the cell. Returns single quotation mark (') if the cell contains left-aligned text, double quotation mark (") if the cell contains right-aligned text, caret (^) if the cell contains centered text, backslash (\) if the cell contains fill-aligned text, and empty text ("") if the cell contains anything else. + * "protect" The value 0 if the cell is not locked; otherwise returns 1 if the cell is locked. + * "row" Row number of the cell in reference. + * "type" Text value corresponding to the type of data in the cell. Returns "b" for blank if the cell is empty, "l" for label if the cell contains a text constant, and "v" for value if the cell contains anything else. + * "width" Column width of the cell, rounded off to an integer. Each unit of column width is equal to the width of one character in the default font size. + * + * + * reference Optional. The cell that you want information about. If omitted, the information specified in the info_type argument is returned for the last cell that was changed. If the reference argument is a range of cells, the CELL function returns the information for only the upper left cell of the range. + */ + @Throws(FunctionNotSupportedException::class) + internal fun calcCell(operands: Array): Ptg { + val type = operands[0].value.toString().toLowerCase() + var ref: PtgRef? = null + var cell: BiffRec? = null + if (operands.size > 1) { + ref = operands[1] as PtgRef + try { + cell = ref.parentRec!!.workBook!!.getCell(ref.locationWithSheet) + } catch (e: CellNotFoundException) { + try { + var sh: String? = null + try { + sh = ref.sheetName + } catch (we: WorkSheetNotFoundException) { + } + + if (sh == null) sh = ref.parentRec!!.sheet!!.sheetName + cell = ref.parentRec!!.workBook!!.getWorkSheetByName(sh).addValue(null, ref.location) + } catch (ex: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + // If ref param is omitted, the information specified in the info_type argument + // is returned for the last cell that was changed + } else if (type != "filename") + // no ref was passed in and option is not "filename" + // We cannot determine which is the "last cell" they are referencing; + throw FunctionNotSupportedException("Worsheet function CELL with no reference parameter is not supported") + else + // filename option can use any biffrec ... + cell = operands[0].parentRec + + // at this point both ref (PtgRef) and r (BiffRec) should be valid + try { + if (type == "address") { + val newref = ref + newref!!.clearLocationCache() + newref.fColRel = false // make absolute + newref.fRwRel = false + return PtgStr(newref.location) + } else if (type == "col") { + return PtgNumber((ref!!.intLocation!![1] + 1).toDouble()) + } else if (type == "color") { // The value 1 if the cell is formatted in color for negative values; otherwise returns 0 (zero). + val s = cell!!.formatPattern + return if (s.indexOf(";[Red") > -1) PtgNumber(1.0) else PtgNumber(0.0) + } else if (type == "contents") {// Value of the upper-left cell in reference; not a formula. + return PtgStr(cell!!.stringVal) + } else if (type == "filename") { + var f = cell!!.workBook.fileName + val sh = cell.sheet.sheetName + val i = f.lastIndexOf(java.io.File.separatorChar.toInt()) + f = f.substring(0, i + 1) + "[" + f.substring(i + 1) + f += "]$sh" + return PtgStr(f) + } else if (type == "format") { // Text value corresponding to the number format of the cell. The text values for the various formats are shown in the following table. Returns "-" at the end of the text value if the cell is formatted in color for negative values. Returns "()" at the end of the text value if the cell is formatted with parentheses for positive or all values. + val s = cell!!.formatPattern + var ret = "G" // default? + if (s == "General" || + s == "# ?/?" || + s == "# ??/??") { + ret = "G" + } else if (s == "0") { + ret = "F0" + } else if (s == "#,##0") { + ret = ",0" + } else if (s == "0.00") { + ret = "F2" + } else if (s == "#,##0.00") { + ret = ", 2" + } else if (s == "$#,##0_);($#,##0)") { + ret = "C0" + } else if (s == "$#,##0_);[Red]($#,##0)") { + ret = "C0-" + } else if (s == "$#,##0.00_);($#,##0.00)") { + ret = "C2" + } else if (s == "$#,##0.00_);[Red]($#,##0.00)") { + ret = "C2-" + } else if (s == "0%") { + ret = "P0" + } else if (s == "0.00%") { + ret = "P2" + } else if (s == "0.00E+00") { + ret = "S2" + // m/d/yy or m/d/yy h:mm or mm/dd/yy "D4" + } else if (s == "m/d/yy" || + s == "m/d/yy h:mm" || + s == "mm/dd/yy" || + s == "mm-dd-yy") { // added last to accomodate Excel's regional short date setting (format #14) + ret = "D4" + } else if (s == "d-mmm-yy" || s == "dd-mmm-yy") { + ret = "D1" + } else if (s == "d-mmm" || s == "dd-mmm") { + ret = "D2" + } else if (s == "mmm-yy") { + ret = "D3" + } else if (s == "mm/dd") { + ret = "D5" + } else if (s == "h:mm AM/PM") { + ret = "D7" + } else if (s == "h:mm:ss AM/PM") { + ret = "D6" + } else if (s == "h:mm") { + ret = "D9" + } else if (s == "h:mm:ss") { + ret = "D8" + } + return PtgStr(ret) + } else if (type == "parentheses") { + val s = cell!!.formatPattern + return if (s.startsWith("(")) PtgNumber(1.0) else PtgNumber(0.0) + } else if (type == "prefix") { + // TODO: THIS IS NOT CORRECT - EITHER INFORM USER OR ?? + // DOESN'T APPEAR TO MATCH EXCEL + //Text value corresponding to the "label prefix" of the cell. + // Returns single quotation mark (') if the cell contains left-aligned text, double quotation mark (") if the cell contains right-aligned text, + // caret (^) if the cell contains centered text, backslash (\) if the cell contains fill-aligned text, and empty text ("") if the cell contains anything else. + val al = cell!!.xfRec.horizontalAlignment + if (al == FormatConstants.ALIGN_LEFT) + return PtgStr("'") + if (al == FormatConstants.ALIGN_CENTER) + return PtgStr("^") + if (al == FormatConstants.ALIGN_RIGHT) + return PtgStr("\"") + return if (al == FormatConstants.ALIGN_FILL) PtgStr("\\") else PtgStr("") + } else if (type == "protect") { + return if (cell!!.xfRec.isLocked) PtgNumber(1.0) else PtgNumber(0.0) + } else if (type == "row") { + return PtgNumber((ref!!.intLocation!![0] + 1).toDouble()) + } else if (type == "type") { + //Text value corresponding to the type of data in the cell. + // Returns "b" for blank if the cell is empty, + //"l" for label if the cell contains a text constant, and + // "v" for value if the cell contains anything else. + if ((cell as XLSRecord).isBlank) + return PtgStr("b") + return if (cell is Labelsst) PtgStr("l") else PtgStr("v") + } else if (type == "width") { + var n = 0 + n = cell!!.sheet.getColInfo(cell.colNumber.toInt())!!.colWidthInChars + return PtgNumber(n.toDouble()) + } + } catch (e: Exception) { + Logger.logWarn("CELL: unable to calculate: $e") + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * ERROR.TYPE + * Returns a number corresponding to an error type + * If error_val is + * ERROR.TYPE returns + * + * + * #NULL! 1 + * #DIV/0! 2 + * #VALUE! 3 + * #REF! 4 + * #NAME? 5 + * #NUM! 6 + * #N/A 7 + * Anything else #N/A + */ + internal fun calcErrorType(operands: Array): Ptg { + val o = operands[0].value + val s = o.toString() + if (s.equals("#NULL!", ignoreCase = true)) return PtgInt(1) + if (s.equals("#DIV/0!", ignoreCase = true)) return PtgInt(2) + if (s.equals("#VALUE!", ignoreCase = true)) return PtgInt(3) + if (s.equals("#REF!", ignoreCase = true)) return PtgInt(4) + if (s.equals("#NAME?", ignoreCase = true)) return PtgInt(5) + if (s.equals("#NUM!", ignoreCase = true)) return PtgInt(6) + return if (s.equals("#N/A", ignoreCase = true)) PtgInt(7) else PtgErr(PtgErr.ERROR_NA) + } + + /** + * INFO + * Returns information about the current operating environment + * INFO(type_text) + * + * + * NOTE: Several options are incomplete: + * "osversion" -- only valid for Windows versions + * "system" -- only valid for Windows and Mac + * "release" -- incomplete + * "origin" -- does not return R1C1 format + */ + internal fun calcInfo(operands: Array?): Ptg { + // validate + if (operands == null || operands.size == 0 || operands[0].parentRec == null) + return PtgErr(PtgErr.ERROR_VALUE) + val type_text = operands[0].string + val ret = "" + if (type_text == "directory") + // Path of the current directory or folder + return PtgStr(System.getProperty("user.dir").toLowerCase() + "\\") + else if (type_text == "numfile") + // number of active worksheets in the current workbook + // TODO: what is correct definition of "Active Worksheets" - hidden state doesn't seem to affect" + return PtgNumber(operands[0].parentRec.workBook!!.numWorkSheets.toDouble()) + else if (type_text == "origin") { /* Returns the absolute cell reference of the top and leftmost + cell visible in the window, based on the current scrolling + position, as text prepended with "$A:". + This value is intended for for Lotus 1-2-3 release 3.x compatibility. + The actual value returned depends on the current reference + style setting. Using D9 as an example, the return value would be: + A1 reference style "$A:$D$9". + R1C1 reference style "$A:R9C4" + */ + // TODO: FINISH R1C1 reference style + var cell = operands[0].parentRec.sheet!!.window2!!.topLeftCell + for (i in cell.length - 1 downTo 0) { + if (!Character.isDigit(cell[i])) { + cell = cell.substring(0, i + 1) + "$" + cell.substring(i + 1) + break + } + } + cell = "\$A:$$cell" + return PtgStr(cell) + } else if (type_text == "osversion") { //Current operating system version, as text. + // see end of file for os info + val osversion = System.getProperty("os.version") + val n = System.getProperty("os.name") // Windows Vista + var os = "" + // TODO: need a list of osversions to compare to! have know idea for mac, linux ... + if (n.startsWith("Windows")) { + val v = Double(osversion) + os = "Windows (32-bit) " + if (v >= 5) + os += "NT " + val df = DecimalFormat("##.00") + os += df.format(v) + } // otherwise have NO idea as cannot find any info on net + else + os += osversion + return PtgStr(os) + } else if (type_text == "recalc") { //Current recalculation mode; returns "Automatic" or "Manual". + return if (operands[0].parentRec.workBook!!.recalculationMode == 0) PtgStr("Manual") else PtgStr("Automatic") + } else if (type_text == "release") { //Version of Microsoft Excel, as text. + // TODO: Finish! 97= 8.0, 2000= 9.0, 2002 (XP)= 10.0, 2003= 11.0, 2007= 12.0 + Logger.logWarn("Worksheet Function INFO(\"release\") is not supported") + return PtgStr("") + } else if (type_text == "system") + // Name of the operating environment: Macintosh = "mac" Windows = "pcdos" + // TODO: linux? **************** + return if (System.getProperty("os.name").indexOf("Windows") >= 0) + PtgStr("pcdos") + else + PtgStr("mac") + else if (type_text == "memavail" || + type_text == "memused" || + type_text == "totmem") + return PtgErr(PtgErr.ERROR_NA)// In previous versions of Microsoft Office Excel, the "memavail", "memused", and "totmem" type_text values, returned memory information. + // These type_text values are no longer supported and now return a #N/A error value. + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * ISBLANK + * ISBLANK determines if the cell referenced is blank, and returns + * a boolean ptg based off that + */ + internal fun calcIsBlank(operands: Array): Ptg { + val allops = PtgCalculator.getAllComponents(operands) + for (i in allops.indices) { + // 20081120 KSC: blanks are handled differently now as Excel counts blank cells as 0's + /*Object o = allops[i].getValue(); + if (o != null) return new PtgBool(false); + */ + if (!allops[i].isBlank) + return PtgBool(false) + } + return PtgBool(true) + } + + /** + * ISERROR + * Value refers to any error value + * (#N/A, #VALUE!, #REF!, #DIV/0!, #NUM!, #NAME?, or #NULL!). + * Usage@ ISERROR(value) + * Return@ PtgBool + */ + internal fun calcIserror(operands: Array): Ptg { + if (operands[0] is PtgErr) { + return PtgBool(true) + } + val errorstr = arrayOf("#N/A", "#VALUE!", "#REF!", "#DIV/0!", "#NUM!", "#NAME?", "#NULL!") + val o = operands[0].value + val opval = o.toString() + for (i in errorstr.indices) { + if (opval.equals(errorstr[i], ignoreCase = true)) { + return PtgBool(true) + } + } + return PtgBool(false) + } + + /** + * ISERR + * Returns TRUE if the value is any error value except #N/A + */ + internal fun calcIserr(operands: Array): Ptg { + val errorstr = arrayOf("#VALUE!", "#REF!", "#DIV/0!", "#NUM!", "#NAME?", "#NULL!") + if (operands.size != 1) return PtgErr(PtgErr.ERROR_VALUE) + val o = operands[0].value + val opval = o.toString() + for (i in errorstr.indices) { + if (opval.equals(errorstr[i], ignoreCase = true)) { + return PtgBool(true) + } + } + return PtgBool(false) + } + + /** + * ISEVEN(number) + * + * + * Number is the value to test. If number is not an integer, it is truncated. + * + * + * Remarks + * If number is nonnumeric, ISEVEN returns the #VALUE! error value. + * Examples + * ISEVEN(-1) equals FALSE + * ISEVEN(2.5) equals TRUE + * ISEVEN(5) equals FALSE + * + * + * author: John + */ + internal fun calcIsEven(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + val allops = PtgCalculator.getAllComponents(operands) + if (allops.size > 1) return PtgBool(false) + val o = operands[0].value + if (o != null) { + try { // KSC: mod for different number types + mod typo + if (o is Int) { + val s = o.toInt() + return if (s < 0) PtgBool(false) else PtgBool(s % 2 == 0) + } else if (o is Float) { + val s = o.toFloat() + return if (s < 0) PtgBool(false) else PtgBool(s % 2 == 0f) + } else if (o is Double) { + val s = o.toDouble() + return if (s < 0) PtgBool(false) else PtgBool(s % 2 == 0.0) + } + } catch (e: Exception) { + } + + } + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * ISLOGICAL + * Returns TRUE if the value is a logical value + */ + internal fun calcIsLogical(operands: Array): Ptg { + val allops = PtgCalculator.getAllComponents(operands) + if (allops.size > 1) return PtgBool(false) + val o = operands[0].value + // unfortunately we need to know the difference between + // "true" and true, if it's a reference this can be difficult + try { + val b = o as Boolean + return PtgBool(true) + } catch (e: ClassCastException) { + } + + return PtgBool(false) + } + + /** + * ISNUMBER + * Returns TRUE if the value is a number + */ + internal fun calcIsNumber(operands: Array): Ptg { + val allops = PtgCalculator.getAllComponents(operands) + if (allops.size > 1) return PtgBool(false) + val o = operands[0].value + try { + val f = o as Float + return PtgBool(true) + } catch (e: ClassCastException) { + try { + val d = o as Double + return PtgBool(true) + } catch (ee: ClassCastException) { + try { + val ii = o as Int + return PtgBool(true) + } catch (eee: ClassCastException) { + } + + } + + } + + return PtgBool(false) + } + + /** + * ISNONTEXT + * Returns TRUE if the value is not text + */ + internal fun calcIsNonText(operands: Array): Ptg { + val allops = PtgCalculator.getAllComponents(operands) + if (allops.size > 1) return PtgBool(false) + // blanks return true for this test + if (allops[0].isBlank) + return PtgBool(true) + val o = operands[0].value + if (o != null) { + try { + val s = o as String + return PtgBool(false) + } catch (e: ClassCastException) { + } + + } + return PtgBool(true) + } + + /** + * ISNA + * Value refers to the #N/A + * (value not available) error value. + * + * + * usage@ ISNA(value) + * return@ PtgBool + */ + internal fun calcIsna(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.error + if (operands[0] is PtgErr) { + val per = operands[0] as PtgErr + if (per.errorType == PtgErr.ERROR_NA) { + return PtgBool(true) + } + } else if (operands[0].isReference) { + val o = operands[0].value + if (o.toString().equals(PtgErr(PtgErr.ERROR_NA).toString(), ignoreCase = true)) { + return PtgBool(true) + } + } + return PtgBool(false) + } + + /** + * NA + * Returns the error value #N/A + */ + internal fun calcNa(operands: Array): Ptg { + return PtgErr(PtgErr.ERROR_NA) + } + + /** + * ISTEXT + * Returns TRUE if the value is text + */ + internal fun calcIsText(operands: Array): Ptg { + val allops = PtgCalculator.getAllComponents(operands) + if (allops.size > 1) return PtgBool(false) + val o = operands[0].value + if (o != null) { + try { + val s = o as String + return PtgBool(true) + } catch (e: ClassCastException) { + } + + } + return PtgBool(false) + } + + + /** + * ISODD + * Returns TRUE if the number is odd + * + * + * author: John + */ + internal fun calcIsOdd(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + val allops = PtgCalculator.getAllComponents(operands) + if (allops.size > 1) return PtgBool(false) + val o = operands[0].value + if (o != null) { + try { // KSC: mod for different number types + mod typo + if (o is Int) { + val s = o.toInt() + return if (s < 0) PtgBool(false) else PtgBool(s % 2 != 0) + } else if (o is Float) { + val s = o.toFloat() + return if (s < 0) PtgBool(false) else PtgBool(s % 2 != 0f) + } else if (o is Double) { + val s = o.toDouble() + return if (s < 0) PtgBool(false) else PtgBool(s % 2 != 0.0) + } + } catch (e: Exception) { + } + + } + return PtgErr(PtgErr.ERROR_VALUE) + } + + /** + * ISREF + * Returns TRUE if the value is a reference + */ + internal fun calcIsRef(operands: Array): Ptg { + return if (operands[0].isReference) { + PtgBool(true) + } else PtgBool(false) + } + + /** + * N + * Returns a value converted to a number. + * + * + * Syntax + * + * + * N(value) + * + * + * Value is the value you want converted. N converts values listed in the following table. + * + * + * If value is or refers to + * N returns + * + * + * A number + * That number + * + * + * A date, in one of the built-in date formats available in Microsoft Excel + * The serial number of that date --- Note that to us, this is just a number, the date + * format is just that, a format. + * + * + * TRUE + * 1 + * + * + * Anything else + * 0 + */ + internal fun calcN(operands: Array): Ptg { + val o = operands[0].value + if (o is Double || o is Int || o is Float || o is Long) { + val d = Double(o.toString()) + return PtgNumber(d) + } + if (o is Boolean) { + val bo = o.booleanValue() + if (bo) return PtgInt(1) + } + return PtgInt(0) + } + + /** + * TYPE + * Returns a number indicating the data type of a value + * Value can be any Microsoft Excel value, such as a number, text, logical value, and so on. + * If value is TYPE returns + * Number 1 + * Text 2 + * Logical value 4 + * Error value 16 + * Array 64 + */ + internal fun calcType(operands: Array): Ptg { + if (operands[0] is PtgArray) + return PtgNumber(64.0) // avoid value calc for arrays + else if (operands[0] is PtgErr) + return PtgNumber(16.0) + + // otherwise, test value of operand + val value = operands[0].value + var type = 0 + if (value is String) + type = 2 + else if (value is Number) + type = 1 + else if (value is Boolean) + type = 4 + return PtgNumber(type.toDouble()) + } + +} + +/* + * known INFO function operating systems: + * TODO: need complete list +Windows Vista Windows (32-bit) NT 6.00 +Windows XP Windows (32-bit) NT 5.01 +Windows2000 Windows (32-bit) NT 5.00 +Windows98 Windows (32-bit) 4.10 +Windows95 Windows (32-bit) 4.00 +*/ + +/* +Linux 2.0.31 x86 IBM Java 1.3 +Linux (*) i386 Sun Java 1.3.1, 1.4 or Blackdown Java; (*) os.version depends on Linux Kernel version +Linux (*) x86_64 Blackdown Java; note x86_64 might change to amd64; (*) os.version depends on Linux Kernel version +Linux (*) sparc Blackdown Java; (*) os.version depends on Linux Kernel version +Linux (*) ppc Blackdown Java; (*) os.version depends on Linux Kernel version +Linux (*) armv41 Blackdown Java; (*) os.version depends on Linux Kernel version +Linux (*) i686 GNU Java Compiler (GCJ); (*) os.version depends on Linux Kernel version +Linux (*) ppc64 IBM Java 1.3; (*) os.version depends on Linux Kernel version +Mac OS 7.5.1 PowerPC +Mac OS 8.1 PowerPC +Mac OS 9.0, 9.2.2 PowerPC MacOS 9.0: java.version=1.1.8, mrj.version=2.2.5; MacOS 9.2.2: java.version=1.1.8 mrj.version=2.2.5 +Mac OS X 10.1.3 ppc +Mac OS X 10.2.6 ppc Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_01-39) +Java HotSpot(TM) Client VM (build 1.4.1_01-14, mixed mode) +Mac OS X 10.2.8 ppc using 1.3 JVM: java.vm.version=1.3.1_03-74, mrj.version=3.3.2; using 1.4 JVM: java.vm.version=1.4.1_01-24, mrj.version=69.1 +Mac OS X 10.3.1, 10.3.2, 10.3.3, 10.3.4 ppc JDK 1.4.x +Mac OS X 10.3.8 ppc Mac OS X 10.3.8 Server; using 1.3 JVM: java.vm.version=1.3.1_03-76, mrj.version=3.3.3; using 1.4 JVM: java.vm.version=1.4.2-38; mrj.version=141.3 +Windows 95 4.0 x86 +Windows 98 4.10 x86 Note, that if you run Sun JDK 1.2.1 or 1.2.2 Windows 98 identifies itself as Windows 95. +Windows Me 4.90 x86 +Windows NT 4.0 x86 +Windows 2000 5.0 x86 +Windows XP 5.1 x86 Note, that if you run older Java runtimes Windows XP identifies itself as Windows 2000. +Windows 2003 5.2 x86 java.vm.version=1.4.2_06-b03; Note, that Windows Server 2003 identifies itself only as Windows 2003. +Windows CE 3.0 build 11171 arm Compaq iPAQ 3950 (PocketPC 2002) +OS/2 20.40 x86 +Solaris 2.x sparc +SunOS 5.7 sparc Sun Ultra 5 running Solaris 2.7 +SunOS 5.8 sparc Sun Ultra 2 running Solaris 8 +SunOS 5.9 sparc Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0_01-b03) +Java HotSpot(TM) Client VM (build 1.4.0_01-b03, mixed mode) +MPE/iX C.55.00 PA-RISC +HP-UX B.10.20 PA-RISC JDK 1.1.x +HP-UX B.11.00 PA-RISC JDK 1.1.x +HP-UX B.11.11 PA-RISC JDK 1.1.x +HP-UX B.11.11 PA_RISC JDK 1.2.x/1.3.x; note Java 2 returns PA_RISC and Java 1 returns PA-RISC +HP-UX B.11.00 PA_RISC JDK 1.2.x/1.3.x +HP-UX B.11.23 IA64N JDK 1.4.x +HP-UX B.11.11 PA_RISC2.0 JDK 1.3.x or JDK 1.4.x, when run on a PA-RISC 2.0 system +HP-UX B.11.11 PA_RISC JDK 1.2.x, even when run on a PA-RISC 2.0 system +HP-UX B.11.11 PA-RISC JDK 1.1.x, even when run on a PA-RISC 2.0 system +AIX 5.2 ppc64 sun.arch.data.model=64 +AIX 4.3 Power +AIX 4.1 POWER_RS +OS/390 390 02.10.00 J2RE 1.3.1 IBM OS/390 Persistent Reusable VM +FreeBSD 2.2.2-RELEASE x86 +Irix 6.3 mips +Digital Unix 4.0 alpha +NetWare 4.11 4.11 x86 +OSF1 V5.1 alpha Java 1.3.1 on Compaq (now HP) Tru64 Unix V5.1 +OpenVMS V7.2-1 alpha Java 1.3.1_1 on OpenVMS 7.2 +*/ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/IxtiListener.java b/src/main/java/io/starter/formats/XLS/formulas/IxtiListener.kt similarity index 85% rename from src/main/java/io/starter/formats/XLS/formulas/IxtiListener.java rename to src/main/java/io/starter/formats/XLS/formulas/IxtiListener.kt index 64f4f5e..683d99b 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/IxtiListener.java +++ b/src/main/java/io/starter/formats/XLS/formulas/IxtiListener.kt @@ -20,30 +20,29 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /** * An IxtiListener is aware of changes to the Externsheet Ixti references. - *

                      + * + * * Notably, the PtgRef3D and PtgArea recs... */ -public interface IxtiListener { +interface IxtiListener { /** * @return Returns the ixti. */ - short getIxti(); - /** * @param ixti The ixti to set. */ - void setIxti(short ixti); + var ixti: Short /** * Add this to the ixti listeners */ - void addListener(); + fun addListener() } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.kt similarity index 50% rename from src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.java rename to src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.kt index 496afa4..bab18f1 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.java +++ b/src/main/java/io/starter/formats/XLS/formulas/LogicalCalculator.kt @@ -20,7 +20,7 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /* @@ -31,19 +31,23 @@ acted upon. A Ptg of the type that makes sense (ie boolean, number) is returned after each method. */ -public class LogicalCalculator { +object LogicalCalculator { /** * AND - *

                      + * + * * Returns TRUE if all its arguments are TRUE; * returns FALSE if one or more arguments is FALSE. - *

                      + * + * * Syntax * AND(logical1,logical2, ...) - *

                      + * + * * Logical1, logical2, ... are 1 to 30 conditions you want to test * that can be either TRUE or FALSE. - *

                      + * + * * The arguments must evaluate to logical values such as TRUE or FALSE, * or the arguments must be arrays or references that contain logical values. * If an array or reference argument contains text or empty cells, @@ -51,44 +55,52 @@ public class LogicalCalculator { * If the specified range contains no logical values, * AND returns the #VALUE! error value. */ - protected static Ptg calcAnd(Ptg[] operands) { - boolean b = true; - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); - for (int i = 0; i < alloperands.length; i++) { - if (alloperands[i] instanceof PtgBool) { - PtgBool bo = (PtgBool) alloperands[i]; - Boolean bool = (Boolean) bo.getValue(); + internal fun calcAnd(operands: Array): Ptg { + val b = true + val alloperands = PtgCalculator.getAllComponents(operands) + for (i in alloperands.indices) { + if (alloperands[i] is PtgBool) { + val bo = alloperands[i] as PtgBool + val bool = bo.value as Boolean if (bool.booleanValue() == false) { - return new PtgBool(false); + return PtgBool(false) } } else { // probably a ref, hopefully to a bool - String s = String.valueOf(alloperands[i].getValue()); - if (s.equalsIgnoreCase("false")) return new PtgBool(false); + val s = alloperands[i].value.toString() + if (s.equals("false", ignoreCase = true)) return PtgBool(false) } } - return new PtgBool(true); + return PtgBool(true) } /** * IF * Returns one value if a condition you specify evaluates to * TRUE and another value if it evaluates to FALSE. - *

                      + * + * * Use IF to conduct conditional tests on values and formulas. - *

                      + * + * * Syntax 1 - *

                      + * + * * IF(logical_test,value_if_true,value_if_false) - *

                      + * + * * Logical_test is any value or expression that can be evaluated to TRUE or FALSE. - *

                      + * + * * Value_if_true is the value that is returned if logical_test is TRUE. If logical_test is TRUE and value_if_true is omitted, TRUE is returned. Value_if_true can be another formula. - *

                      + * + * * Value_if_false is the value that is returned if logical_test is FALSE. If logical_test is FALSE and value_if_false is omitted, FALSE is returned. Value_if_false can be another formula. - *

                      + * + * * Remarks - *

                      + * + * * Up to seven IF functions can be nested as value_if_true and value_if_false * arguments to construct more elaborate tests. See the following last example. * When the value_if_true and value_if_false arguments are evaluated, @@ -98,120 +110,120 @@ protected static Ptg calcAnd(Ptg[] operands) { * value_if_true and value_if_false arguments are action-taking functions, * all of the actions are taken. */ - protected static Ptg calcIf(Ptg[] operands) { + internal fun calcIf(operands: Array): Ptg { // lets assume for now there are always 3 operands.. NOPE! sometimes the missarg gets // lost for some reason, so we need to treat it like that. - if (operands.length < 2) - return new PtgErr(PtgErr.ERROR_VALUE); - Ptg determine = operands[0]; - Ptg iftrue; - if (operands.length > 1) - iftrue = operands[1]; // 20070212 KSC: if this is blank, return 0 (according to help!) + if (operands.size < 2) + return PtgErr(PtgErr.ERROR_VALUE) + val determine = operands[0] + var iftrue: Ptg + if (operands.size > 1) + iftrue = operands[1] // 20070212 KSC: if this is blank, return 0 (according to help!) else { // return strings - if (operands[0] instanceof PtgStr) - return operands[0]; - return (new PtgInt(0)); + return if (operands[0] is PtgStr) operands[0] else PtgInt(0) } - Ptg iffalse; - if (operands.length > 2) { - iffalse = operands[2]; + var iffalse: Ptg + if (operands.size > 2) { + iffalse = operands[2] } else { - iffalse = new PtgMissArg(); + iffalse = PtgMissArg() } - if (iftrue instanceof PtgMissArg) { - iftrue = new PtgNumber(0); + if (iftrue is PtgMissArg) { + iftrue = PtgNumber(0.0) } - if (iffalse instanceof PtgMissArg) { - iffalse = new PtgNumber(0); + if (iffalse is PtgMissArg) { + iffalse = PtgNumber(0.0) } - if (!(determine instanceof PtgArray)) { - String strval = null; - if (!(determine instanceof PtgRef)) - strval = determine.toString(); + if (determine !is PtgArray) { + var strval: String? = null + if (determine !is PtgRef) + strval = determine.toString() else { try { - strval = determine.getValue().toString(); - } catch (Exception e) { // could be a formula not found error, etc. don't ignore - return new PtgErr(PtgErr.ERROR_VALUE); + strval = determine.value.toString() + } catch (e: Exception) { // could be a formula not found error, etc. don't ignore + return PtgErr(PtgErr.ERROR_VALUE) } + } - if (strval.equalsIgnoreCase("true")) { - return iftrue; + return if (strval.equals("true", ignoreCase = true)) { + iftrue } else { - return iffalse; + iffalse } } else { try { // see what type of operands iftrue and iffalse arrays are - String retArry = ""; - Ptg[] p = determine.getComponents(); -// boolean trueIsArray= iftrue instanceof - boolean res = true; - boolean trueValueIsArray = (iftrue instanceof PtgArray); - boolean falseValueIsArray = (iffalse instanceof PtgArray); - for (int i = 0; i < p.length; i++) { - res = (p[i].toString().equalsIgnoreCase("true")); + var retArry = "" + val p = determine.components + // boolean trueIsArray= iftrue instanceof + var res = true + val trueValueIsArray = iftrue is PtgArray + val falseValueIsArray = iffalse is PtgArray + for (i in p.indices) { + res = p[i].toString().equals("true", ignoreCase = true) if (res) { if (trueValueIsArray) - retArry = retArry + ((PtgArray) iftrue).arrVals.get(i).toString() + ","; + retArry = retArry + (iftrue as PtgArray).arrVals[i].toString() + "," else - retArry = retArry + iftrue + ","; + retArry = "$retArry$iftrue," } else { // false if (falseValueIsArray) - retArry = retArry + ((PtgArray) iffalse).arrVals.get(i).toString() + ","; + retArry = retArry + (iffalse as PtgArray).arrVals[i].toString() + "," else - retArry = retArry + iffalse + ","; + retArry = "$retArry$iffalse," } } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; - } catch (Exception e) { // this should hit if iftrue and iffalse are array types .... TODO: handle! - return iffalse; + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa + } catch (e: Exception) { // this should hit if iftrue and iffalse are array types .... TODO: handle! + return iffalse } + } } /** * Returns the logical function False */ - protected static Ptg calcFalse(Ptg[] operands) { - return new PtgBool(false); + internal fun calcFalse(operands: Array): Ptg { + return PtgBool(false) } /** * Returns the logical function true */ - protected static Ptg calcTrue(Ptg[] operands) { - return new PtgBool(true); + internal fun calcTrue(operands: Array): Ptg { + return PtgBool(true) } /** * Returns the opposite boolean */ - protected static Ptg calcNot(Ptg[] operands) { - if (String.valueOf(operands[0].getValue()).equalsIgnoreCase("false")) { - return new PtgBool(true); + internal fun calcNot(operands: Array): Ptg { + return if (operands[0].value.toString().equals("false", ignoreCase = true)) { + PtgBool(true) } else { - return new PtgBool(false); + PtgBool(false) } } /** * Returns the opposite boolean */ - protected static Ptg calcOr(Ptg[] operands) { - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); -// KSC: TESTING -//System.out.print("\tOR " + operands[0].toString() + " " + operands[1].toString() + "? "); - for (int i = 0; i < alloperands.length; i++) { - if (String.valueOf(alloperands[i].getValue()).equalsIgnoreCase("true")) { - return new PtgBool(true); + internal fun calcOr(operands: Array): Ptg { + val alloperands = PtgCalculator.getAllComponents(operands) + // KSC: TESTING + //System.out.print("\tOR " + operands[0].toString() + " " + operands[1].toString() + "? "); + for (i in alloperands.indices) { + if (alloperands[i].value.toString().equals("true", ignoreCase = true)) { + return PtgBool(true) } } - return new PtgBool(false); + return PtgBool(false) } @@ -219,42 +231,50 @@ protected static Ptg calcOr(Ptg[] operands) { * IFERROR function * Returns a value you specify if a formula evaluates to an error; otherwise, returns the result of the formula. * Use the IFERROR function to trap and handle errors in a formula (formula: A sequence of values, cell references, names, functions, or operators in a cell that together produce a new value. A formula always begins with an equal sign (=).). - *

                      + * + * * Value is the argument that is checked for an error. - *

                      + * + * * Value_if_error is the value to return if the formula evaluates to an error. The following error types are evaluated: #N/A, #VALUE!, #REF!, #DIV/0!, #NUM!, #NAME?, or #NULL!. - *

                      + * + * * Remarks - *

                      + * + * * If value or value_if_error is an empty cell, IFERROR treats it as an empty string value (""). * If value is an array formula, IFERROR returns an array of results for each cell in the range specified in value. See the second example below. * Example: Trapping division errors by using a regular formula */ - protected static Ptg calcIferror(Ptg[] operands) { - if (operands == null || operands.length != 2) - return new PtgErr(PtgErr.ERROR_VALUE); - if (!(operands[0] instanceof PtgArray)) { - PtgBool ret = (PtgBool) InformationCalculator.calcIserror(operands); - if (ret.getBooleanValue()) // it's an error - return operands[1]; - else // it's not; return calculated results of 1st operand - return operands[0]; + internal fun calcIferror(operands: Array?): Ptg { + if (operands == null || operands.size != 2) + return PtgErr(PtgErr.ERROR_VALUE) + if (operands[0] !is PtgArray) { + val ret = InformationCalculator.calcIserror(operands) as PtgBool + return if (ret.booleanValue) + // it's an error + operands[1] + else + // it's not; return calculated results of 1st operand + operands[0] } else { - Ptg[] components = operands[0].getComponents(); - String retArray = ""; - for (int i = 0; i < components.length; i++) { - Ptg[] test = new Ptg[1]; - test[0] = components[i]; - PtgBool ret = (PtgBool) InformationCalculator.calcIserror(test); - if (ret.getBooleanValue()) // it's an error - retArray = retArray + operands[1] + ","; - else // it's not; return calculated results of 1st operand - retArray = retArray + test[0] + ","; + val components = operands[0].components + var retArray = "" + for (i in components.indices) { + val test = arrayOfNulls(1) + test[0] = components[i] + val ret = InformationCalculator.calcIserror(test) as PtgBool + if (ret.booleanValue) + // it's an error + retArray = retArray + operands[1] + "," + else + // it's not; return calculated results of 1st operand + retArray = retArray + test[0] + "," } - retArray = "{" + retArray.substring(0, retArray.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArray); - return pa; + retArray = "{" + retArray.substring(0, retArray.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArray) + return pa } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.java deleted file mode 100644 index 4d253f5..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.java +++ /dev/null @@ -1,1333 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.*; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; - -/** - * LookupReferenceCalculator is a collection of static methods that operate - * as the Microsoft Excel function calls do. - *

                      - * All methods are called with an array of ptg's, which are then - * acted upon. A Ptg of the type that makes sense (ie boolean, number) - * is returned after each method. - */ - -public class LookupReferenceCalculator { - - /** - * ADDRESS - * Creates a cell address as text, given specified row and column numbers. - *

                      - * Syntax - * ADDRESS(row_num,column_num,abs_num,a1,sheet_text) - * Row_num is the row number to use in the cell reference. - * Column_num is the column number to use in the cell reference. - * Abs_num specifies the type of reference to return. - *

                      - * Abs_num Returns this type of reference - * 1 or omitted Absolute - *

                      - * 2 Absolute row; relative column - *

                      - * 3 Relative row; absolute column - *

                      - * 4 Relative - *

                      - * A1 is a logical value that specifies the A1 or R1C1 reference style. - * If a1 is TRUE or omitted, ADDRESS returns an A1-style reference; - * if FALSE, ADDRESS returns an R1C1-style reference. - *

                      - * Sheet_text is text specifying the name of the worksheet to be - * used as the external reference. If sheet_text is omitted, no sheet name is used. - *

                      - * Examples - *

                      - * ADDRESS(2,3) equals "$C$2" - *

                      - * ADDRESS(2,3,2) equals "C$2" - *

                      - * ADDRESS(2,3,2,FALSE) equals "R2C[3]" - *

                      - * ADDRESS(2,3,1,FALSE,"[Book1]Sheet1") equals "[Book1]Sheet1!R2C3" - *

                      - * ADDRESS(2,3,1,FALSE,"EXCEL SHEET") equals "'EXCEL SHEET'!R2C3" - */ - protected static Ptg calcAddress(Ptg[] operands) { - if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); - // deal with floating point refs - String rx1 = operands[0].getValue().toString(); - if (rx1.indexOf(".") > -1) - rx1 = rx1.substring(0, rx1.indexOf(".")); - int row = Integer.valueOf(rx1).intValue(); - // deal with floating point refs - String cx1 = operands[1].getValue().toString(); - if (cx1.indexOf(".") > -1) - cx1 = cx1.substring(0, cx1.indexOf(".")); - - int col = Integer.valueOf(cx1).intValue(); - int abs_num = 1; - boolean ref_style = true; - String sheettext = ""; - if (operands.length > 2) { - if (operands[2].getValue() != null) { //checking for a ptgmissarg - abs_num = ((Integer) operands[2].getValue()).intValue(); - } - } - if (operands.length > 3) { - if (operands[3].getValue() != null) { //checking for a ptgmissarg - Boolean b = Boolean.valueOf(String.valueOf(operands[3].getValue())); - ref_style = b.booleanValue(); - } - } - if (operands.length > 4) { - if (operands[4].getValue() != null) { //checking for a ptgmissarg - sheettext = operands[4].getValue() + "!"; - } - } - String loc = ""; - String colstr = ExcelTools.getAlphaVal(col - 1); - if (ref_style) { - if (abs_num == 1) { - loc = "$" + colstr + "$" + row; - } else if (abs_num == 2) { - loc = colstr + "$" + row; - } else if (abs_num == 3) { - loc = "$" + colstr + row; - } else if (abs_num == 4) { - loc = colstr + row; - } - } else { - if (abs_num == 1) { - loc = "R" + row + "C" + col; // this is transposed with abs_num 4. Error in Excel - } else if (abs_num == 2) { - loc = "R" + row + "C[" + col + "]"; - } else if (abs_num == 3) { - loc = "R[" + row + "]C" + col; - } else if (abs_num == 4) { - loc = "R[" + row + "]C[" + col + "]"; - - } - } - loc = sheettext + loc; - return new PtgStr(loc); - - } - - /** - * AREAS - * Returns the number of areas in a reference. An area is a range of contiguous cells or a single cell. - *

                      - * Reference is a reference to a cell or range of cells and can refer to multiple areas. - * If you want to specify several references as a single argument, then you must include extra sets of parentheses so that Microsoft Excel will not interpret the comma as a field separator. - *

                      - * NOTE: this appears to be correct given Excel information but logic is not 100% known - * e.g. =AREAS(B2:D4 D3) = 1 - * =AREAS(B2:D4 E3) gives a #NULL! error - why? - * =AREAS(B2:D4,E3) = 2 - */ - protected static Ptg calcAreas(Ptg[] operands) { - Ptg ref = operands[0]; - String s = ref.toString(); - String[] areas = s.split(",(?=([^'|\"]*'[^'|\"]*'|\")*[^'|\"]*$)"); - return new PtgNumber(areas.length); - } - - /** - * CHOOSE - * Chooses a value from a list of values - *

                      - * Note, this function does not support one specific use-case. That is choosing a ptgref - * and using that ptgref to complete a ptgarea. Example - * =SUM(E6:CHOOSE(3,G4,G5,G6)) - * =SUM(E6:G6) - */ - protected static Ptg calcChoose(Ptg[] operands) { - if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); - Object o = operands[0].getValue(); - try { - Double dd = new Double(o.toString()); // this can be non-integer, so truncate it if so... - double e = dd.doubleValue(); - int i = (int) e; - if (i > operands.length + 1 || i < 1) return new PtgErr(PtgErr.ERROR_REF); - o = operands[i].getValue(); - Double d = (Double) o; - return new PtgNumber(d.doubleValue()); - } catch (Exception ex) { - new PtgErr(PtgErr.ERROR_VALUE); - } - return new PtgStr(o.toString()); - - } - - /** - * COLUMN - * Returns the column number of a reference - */ - protected static Ptg calcColumn(Ptg[] operands) { - if (operands[0] instanceof PtgFuncVar) { - // we need to return the col where the formula is. - PtgFuncVar pfunk = (PtgFuncVar) operands[0]; - try { - int loc = pfunk.getParentRec().getColNumber(); - loc += 1; - return new PtgInt(loc); - } catch (Exception e) { - } - } else { - // It's ugly, but we are going to handle the four types of references seperately, as there is no good way - // to generically get this info - try { - if (operands[0] instanceof PtgArea) { - PtgArea pa = (PtgArea) operands[0]; - int[] loc = pa.getIntLocation(); - return new PtgInt(loc[1] + 1); - } else if (operands[0] instanceof PtgRef) { - PtgRef pref = (PtgRef) operands[0]; - int loc = pref.getIntLocation()[1]; - loc += 1; - return new PtgInt(loc); - } else if (operands[0] instanceof PtgName) { // table??? - String range = ((PtgName) operands[0]).getName().getLocation(); - int[] loc = ExcelTools.getRangeCoords(range); - return new PtgInt(loc[1] + 1); - } - } catch (Exception e) { - } - } - return new PtgInt(-1); - } - - /** - * COLUMNS - * Returns the number of columns in an array reference or array formula - */ - // TODO: Not finished yet! - protected static Ptg calcColumns(Ptg[] operands) { - // - if (operands[0] instanceof PtgFuncVar) { - // we need to return the col where the formula is. - PtgFuncVar pfunk = (PtgFuncVar) operands[0]; - try { - int loc = pfunk.getParentRec().getColNumber(); - loc += 1; - return new PtgInt(loc); - } catch (Exception e) { - } - } else { - // It's ugly, but we are going to handle the four types of references seperately, as there is no good way - // to generically get this info - try { - if (operands[0] instanceof PtgArea) { - PtgArea pa = (PtgArea) operands[0]; - int[] loc = pa.getIntLocation(); - int ncols = loc[3] - loc[1] + 1; - return new PtgInt(ncols); - } else if (operands[0] instanceof PtgArray) { - PtgArray parr = (PtgArray) operands[0]; - return new PtgInt(parr.nc + 1); - } - } catch (Exception e) { - } - } - return new PtgInt(-1); - } - - /** - * HLOOKUP: Looks in the top row of an array and returns the value of the - * indicated cell. - */ - protected static Ptg calcHlookup(Ptg[] operands) - throws FunctionNotSupportedException { - - if (operands.length < 3) return new PtgErr(PtgErr.ERROR_VALUE); - boolean sorted = true; - boolean isNumber = true; - Ptg lookup_value = operands[0]; - Ptg table_array = operands[1]; - PtgInt row_index_num = (PtgInt) operands[2]; - int rowNum = row_index_num.getVal() - 1;// reduce 1 for ordinal base off firstcol - if (operands.length > 3) { - if (operands[3].getValue() != null) { - Object o = operands[3].getValue(); - if (o instanceof Boolean) - sorted = ((Boolean) o).booleanValue(); - else if (o instanceof Integer) - sorted = (((Integer) o).intValue() != 0); - } - } - int[] retarea = {0, 0}; - Boundsheet bs = lookup_value.getParentRec().getSheet(); - WorkBook bk = table_array.getParentRec().getWorkBook(); - PtgRef[] lookupComponents = null; - PtgRef[] valueComponents = null; - // first, get the lookup Column Vals - if (table_array instanceof PtgName) { - // Handle getting vals out of name - - } else if (table_array instanceof PtgArea || table_array instanceof PtgArea3d) { - try { - PtgArea pa = (PtgArea) table_array; - int[] range = table_array.getIntLocation(); - - // TODO: check rc sanity here - int firstrow = range[0]; - lookupComponents = (PtgRef[]) pa.getRowComponents(firstrow); - valueComponents = (PtgRef[]) pa.getRowComponents(firstrow + rowNum); - } catch (/*20070209 KSC: FormulaNotFound*/Exception e) { - Logger.logWarn("Error in LookupReferenceCalculator: Cannot determine PtgArea location. " + e); - } - - } - // error check - if (lookupComponents == null || lookupComponents.length == 0) return new PtgErr(PtgErr.ERROR_REF); - // lets check if we are dealing with strings or numbers.... - try { - String val = lookupComponents[0].getValue().toString(); - Double d = new Double(val); - } catch (NumberFormatException e) { - isNumber = false; - } - - if (isNumber) { - double match_num; - try { - match_num = Double.parseDouble( - lookup_value.getValue().toString()); - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_NA); - } - - for (int i = 0; i < lookupComponents.length; i++) { - double val; - try { - val = Double.parseDouble( - lookupComponents[i].getValue().toString()); - } catch (NumberFormatException e) { - // Ignore entries in the table that aren't numbers. - continue; - } - - if (val == match_num) { - return valueComponents[i].getPtgVal(); - } else if (sorted && val > match_num) { - if (i == 0) return new PtgErr(PtgErr.ERROR_NA); - else return valueComponents[i - 1].getPtgVal(); - } - } - - if (sorted) return valueComponents[lookupComponents.length - 1].getPtgVal(); - else return new PtgErr(PtgErr.ERROR_NA); - - } else { - //TODO: need to handle as string - } - - return new PtgErr(PtgErr.ERROR_NULL); - } - - /** - * HYPERLINK - * Creates a shortcut or jump that opens a document - * stored on a network server, an intranet, or the Internet - *

                      - * Function just returns the "friendly name" of the link, - * Excel doesn't appear to validate the url ... - */ - - protected static Ptg calcHyperlink(Ptg[] operands) { - try { - if (operands.length == 2) - return new PtgStr(operands[1].getValue().toString()); - return new PtgStr(operands[0].getValue().toString()); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - /** - * INDEX - * Returns a value or the reference to a value from within a table or range. - * There are two forms of the INDEX function: the array form and the reference form. - *

                      - * Array Form: - * Returns the value of an element in a table or an array selected by the row and column number indexes. - * Use the array form if the first argument to INDEX is an array constant. - * INDEX(array,row_num,column_num) - * Array is a range of cells or an array constant. - * If array contains only one row or column, - * the corresponding row_num or column_num argument is optional. - * If array has more than one row and more than one column, and only row_num or column_num is used, - * INDEX returns an array of the entire row or column in array. - * Row_num selects the row in array from which to return a value. If row_num is omitted, column_num is required. - * Column_num selects the column in array from which to return a value. If column_num is omitted, row_num is required. - *

                      - * Reference Form: - * Returns the reference of the cell at the intersection of a particular row and column. - * If the reference is made up of nonadjacent selections, you can pick the selection to look in. - * INDEX(reference,row_num,column_num,area_num) - * Reference is a reference to one or more cell ranges. - * If you are entering a nonadjacent range for the reference, enclose reference in parentheses. - * If each area in reference contains only one row or column, the row_num or column_num argument, respectively, is optional. For example, for a single row reference, use INDEX(reference,,column_num). - * Row_num is the number of the row in reference from which to return a reference. - * Column_num is the number of the column in reference from which to return a reference. - * Area_num selects a range in reference from which to return the intersection of row_num and column_num. The first area selected or entered is numbered 1, the second is 2, and so on. If area_num is omitted, INDEX uses area 1. - *

                      - * Given a BiffRec Range, choose the cell within referenced by the - * row and column operands. - *

                      - * example: - * =INDEX(G3:L8,6,4) - * returns the value of the BiffRec at row 6, col 4 in the following table - * which is 0.11 - *

                      - * G H I J K L - * 1 - * 2 - * 3 0.007 0.005 0.003 0.002 0.002 0.001 - * 4 0.025 0.017 0.012 0.008 0.006 0.005 - * 5 0.062 0.044 0.032 0.023 0.018 0.015 - * 6 0.116 0.086 0.064 0.049 0.04 0.035 - * 7 0.171 0.13 0.101 0.082 0.07 0.062 - * 8 0.211 0.165 0.132 0.11 0.096 0.088 - **/ - protected static Ptg calcIndex(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg o = operands[0]; - // only 1st operand is required; changed below (see doc snippet below) - /*If each area in reference contains only one row or column, - * the row_num or column_num argument, respectively, is optional. - * For example, for a single row reference, use INDEX(reference,,column_num). - */ - Object rowref = new Double(1); // defaults (1-based) - Object colref = new Double(1); - double areanum = 1; - int[] retarea = null; - String sht = null; - try { - if (operands.length > 1) { - Ptg rowrefp = operands[1]; - rowref = rowrefp.getValue(); - } - if (operands.length > 2) { - Ptg colrefp = operands[2]; - colref = colrefp.getValue(); - } - if (operands.length > 3) { - areanum = operands[3].getDoubleVal(); - } - if (o instanceof PtgArea) { - retarea = ((PtgArea) o).getIntLocation(); - sht = ((PtgArea) o).getSheetName(); - } else if (o instanceof PtgName) { - //Ptg[] p=((PtgName) o).getName().getComponents(); //CellRangePtgs(); - //o= p[0]; - String r = ((PtgName) o).getName().getLocation(); - retarea = ExcelTools.getRangeRowCol(r); - sht = (r.indexOf("!") == -1) ? null : r.substring(0, r.indexOf("!")); - } else if (o instanceof PtgMemFunc) { - Ptg[] ps = o.getComponents(); - areanum--; - if (areanum >= 0 && areanum < ps.length) - o = ps[(int) areanum]; - else - o = ps[0]; - retarea = ((PtgArea) o).getIntLocation(); - sht = ((PtgArea) o).getSheetName(); - } else if (o instanceof PtgArray) { - Ptg[] ps = o.getComponents(); - areanum--; - if (areanum >= 0 && areanum < ps.length) - o = ps[(int) areanum]; - else - o = ps[0]; - retarea = ((PtgArea) o).getIntLocation(); - // TODO: Sheet! - } - - // now should have the correct area - if (retarea != null) { - // really we can just use the first position to get a ref - // then the second just checks bounds -// TODO: check rc sanity here - int rowoff = retarea[0]; - int coloff = retarea[1]; - int rowck = retarea[2] + 1; - int colck = retarea[3] + 1; - - int[] dims = new int[2]; - try { - int rr; - if ((rowref instanceof Integer)) { - rr = ((Integer) rowref).intValue(); - } else { - String rw = rowref.toString(); - if (rw.indexOf(".") > -1) rw = rw.substring(0, rw.indexOf(".")); - // string non Integer Chars... - rr = Integer.parseInt(rw); - } - int cr; - if ((colref instanceof Integer)) { - cr = ((Integer) colref).intValue(); - } else { - String cl = colref.toString(); - if (cl.indexOf(".") > -1) cl = cl.substring(0, cl.indexOf(".")); - // string non Integer Chars... - cr = Integer.parseInt(cl); - } - - if ((rr > rowck) || (cr > colck)) { - return new PtgErr(PtgErr.ERROR_REF); - } - dims[0] = rr + rowoff - 1; - dims[1] = cr + coloff - 1; - - // here's a nice new ref... - PtgRef refp = new PtgRef(); - if (o instanceof PtgArea3d) refp = new PtgRef3d(); - refp.setParentRec(o.getParentRec()); - refp.setUseReferenceTracker(false); - if (sht != null) refp.setSheetName(sht); - refp.setLocation(dims); - if (o instanceof PtgArea3d) { - refp.setLocation(((PtgArea3d) o).getSheetName() + "!" + ExcelTools.formatLocation(dims)); - } else { - - } - return refp; - - } catch (NumberFormatException e) { - //Logger.logWarn("could not calculate INDEX function: " + o.toString() + ":" + e); - return new PtgErr(PtgErr.ERROR_NULL); // ERR or #VALUE ?? - } - } - } catch (Exception e) { - Logger.logWarn("could not calculate INDEX function: " + o.toString() + ":" + e); - } - return new PtgErr(PtgErr.ERROR_NULL); - - } - - - /** - * INDIRECT - * Returns a reference indicated by a text value - *

                      - * INDIRECT(ref_text,a1) - *

                      - * Ref_text is a reference to a cell that contains an A1-style - * reference, an R1C1-style reference, a name defined as a reference, - * or a reference to a cell as a text string. If ref_text is not a valid - * cell reference, INDIRECT returns the #REF! error value. - *

                      - * If ref_text refers to another workbook (an external reference), - * the other workbook must be open. If the source workbook is not open, - * INDIRECT returns the #REF! error value. - *

                      - * A1 is a logical value that specifies what type of reference is contained in the cell ref_text. - *

                      - * If a1 is TRUE or omitted, ref_text is interpreted as an A1-style reference. - * If a1 is FALSE, ref_text is interpreted as an R1C1-style reference. - */ - protected static Ptg calcIndirect(Ptg[] operands) { - try { - if (operands[0] instanceof PtgStr) { - PtgStr ps = (PtgStr) operands[0]; - - String locx = ps.toString(); - // detect range - if (!(FormulaParser.isRef(locx) || - FormulaParser.isRange(locx))) { // see if it's a named range - Name nmx = ps.getParentRec().getWorkBook().getName(locx); - if (nmx != null) { // there is a named range - locx = nmx.getLocation(); - } else - return ps; //it's just a value - } - if ("".equals(locx)) - return new PtgInt(0); // that's what Excel does! - PtgArea3d refp = new PtgArea3d(false); - refp.setParentRec(ps.getParentRec()); - refp.setUseReferenceTracker(true); // very important!!! :) - refp.setLocation(locx); - return refp; - - } else if (operands[0] instanceof PtgRef) { - // check if the ptgRef value is a string representing a Named range - Object o = operands[0].getValue(); - PtgStr ps = new PtgStr(o.toString()); - ps.setParentRec(operands[0].getParentRec()); - operands = new Ptg[1]; - operands[0] = ps; - return calcIndirect(operands); - } else if (operands[0] instanceof PtgName) { - return calcIndirect(operands[0].getComponents()); - } - } catch (Exception e) { - //Logger.logErr("INDIRECT: " + e.toString()); - } - return new PtgErr(PtgErr.ERROR_REF); // 's what Excel does ... - } - - /** - * LOOKUP - * The LOOKUP function returns a value either from a one-row or one-column range - * You can also use the LOOKUP function as an alternative to the IF function for elaborate tests or - * tests that exceed the limit for nesting of functions. See the examples in the array form. - * For the LOOKUP function to work correctly, the data being looked up must be sorted in - * ascending order. If this is not possible, consider using the VLOOKUP, HLOOKUP, or MATCH functions. - *

                      - * A vector is a range of only one row or one column. - * The vector form of LOOKUP looks in a one-row or one-column range (known as a vector) for a value and returns a value from the same position in a second one-row or one-column range. Use this form of the LOOKUP function when you want to specify the range that contains the values that you want to match. The other form of LOOKUP automatically looks in the first column or row. - */ - public static Ptg calcLookup(Ptg[] operands) { - String lookup = operands[0].getValue().toString().toUpperCase(); - if (operands.length > 2) { //normal version of lookup - Ptg[] vector = operands[1].getComponents(); - Ptg[] returnvector = operands[2].getComponents(); - if (returnvector == null) // happens when operands[2] is a PtgRef - return new PtgNumber(0); // this is what excel does - - //If the LOOKUP function can't find the lookup_value, the function matches the largest value in lookup_vector that is less than or equal to lookup_value. - //If lookup_value is smaller than the smallest value in lookup_vector, LOOKUP returns the #N/A error value - Object retval = null; - for (int i = 0; i < vector.length; i++) { - if (Calculator.compareCellValue(vector[i].getValue(), lookup, ">")) - break; - if (i < returnvector.length) - retval = returnvector[i].getValue(); - } - if (retval instanceof Number) - return new PtgNumber(((Number) retval).doubleValue()); - else if (retval instanceof Boolean) - return new PtgBool(((Boolean) retval).booleanValue()); - else if (retval == null) - return new PtgErr(PtgErr.ERROR_NA); - else // assume string - return new PtgStr(retval.toString()); - } else { //array form of lookup - /* - * The array form of LOOKUP looks in the first row or column of an array for the specified value - * and returns a value from the same position in the last row or column of the array. - * Use this form of LOOKUP when the values that you want to match are in the first row - * or column of the array. Use the other form of LOOKUP when you want to specify the location - * of the column or row. - In general, it's best to use the HLOOKUP or VLOOKUP function instead of the array form of LOOKUP. This form of LOOKUP is provided for compatibility with other spreadsheet programs. - - With the HLOOKUP and VLOOKUP functions, you can index down or across, but LOOKUP always selects the last value in the row or column. - - */ - try { - Ptg[] array = operands[1].getComponents(); - int nrs = ((PtgArray) operands[1]).getNumberOfRows(); - int ncs = ((PtgArray) operands[1]).getNumberOfColumns(); - //If array covers an area that is wider than it is tall (more columns than rows), LOOKUP searches for the value of lookup_value in the first row. - //If an array is square or is taller than it is wide (more rows than columns), LOOKUP searches in the first column. - Object retval = null; - boolean found = false; - boolean rowbased = (ncs > nrs); - ncs++; // make 1-based - int i = 0; - for (int j = 0; j < nrs && !found; j++) { - int start = i; - for (; i < start + ncs && !found; i++) { - if (Calculator.compareCellValue(array[i].getValue(), lookup, ">")) { - found = true; - break; - } - // returns a value from the same position in the last row or column of the array - if (rowbased) { - retval = array[i + ncs].getValue(); - } else { - retval = array[i + 1].getValue(); - i++; - } - } - } - if (retval instanceof Number) - return new PtgNumber(((Number) retval).doubleValue()); - else if (retval instanceof Boolean) - return new PtgBool(((Boolean) retval).booleanValue()); - else if (retval == null) - return new PtgErr(PtgErr.ERROR_NA); - else // assume string - return new PtgStr(retval.toString()); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_NA); - } - } - } - - - /** - * MATCH - * Looks up values in a reference or array - * Returns the relative position of an item in an array that matches a specified value in a - * specified order. Use MATCH instead of one of the LOOKUP functions when you need the position - * of an item in a range instead of the item itself. - *

                      - * MATCH(lookup_value,lookup_array,match_type) - *

                      - * Lookup_value is the value you want to match in lookup_array. For example, when you look up someone's number - * in a telephone book, you are using the person's name as the lookup value, but the telephone number is - * the value you want. - * Lookup_value can be a value (number, text, or logical value) or a cell reference to a number, - * text, or logical value. - *

                      - * Lookup_array is a contiguous range of cells containing possible lookup values. Lookup_array must - * be an array or an array reference. - *

                      - * Match_type is the number -1, 0, or 1. Match_type specifies how Microsoft Excel matches lookup_value - * with values in lookup_array. - *

                      - * If match_type is 1, MATCH finds the largest value that is less than or equal to lookup_value. - * Lookup_array must be placed in ascending order: ...-2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE. - *

                      - * If match_type is 0, MATCH finds the first value that is exactly equal to lookup_value. - * Lookup_array can be in any order. - *

                      - * If match_type is -1, MATCH finds the smallest value that is greater than or equal to lookup_value. - * Lookup_array must be placed in descending order: TRUE, FALSE, Z-A, ...2, 1, 0, -1, -2, ..., and so on. - *

                      - * If match_type is omitted, it is assumed to be 1. - *

                      - * MATCH returns the position of the matched value within lookup_array, not the value itself. For example, MATCH("b",{"a","b","c"},0) returns 2, the relative position of "b" within the array {"a","b","c"}. - * MATCH does not distinguish between uppercase and lowercase letters when matching text values. - * If MATCH is unsuccessful in finding a match, it returns the #N/A error value. - * If match_type is 0 and lookup_value is text, lookup_value can contain the wildcard characters asterisk (*) and question mark (?). An asterisk matches any sequence of characters; a question mark matches any single character. - */ - public static Ptg calcMatch(Ptg[] operands) { - try { - Object lookupValue = operands[0].getValue(); // should be one value or a reference - Ptg lookupArray = operands[1]; // array or array reference (PtgArea) - Ptg[] values = null; - int matchType = 1; - if (operands.length > 2) { - Object o = operands[2].getValue(); - if (o instanceof Integer) - matchType = ((Integer) o).intValue(); - else - matchType = ((Double) o).intValue(); - - } - // Step 1- get all the components of the lookupArray (Array or Array Reference - if (lookupArray instanceof PtgName) { - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(lookupArray.getParentRec()); - pa.setLocation(((PtgName) lookupArray).getName().getLocation()); - lookupArray = pa; - } - if (lookupArray instanceof PtgArea) { - PtgArea pa = (PtgArea) lookupArray; - values = pa.getComponents(); - } else if (lookupArray instanceof PtgMemFunc) { - PtgMemFunc pa = (PtgMemFunc) lookupArray; - values = pa.getComponents(); - } else if (lookupArray instanceof PtgArray) { - PtgArray pa = (PtgArray) lookupArray; - values = pa.getComponents(); - } else if (lookupArray instanceof PtgMystery) { - // PtgMystery is return from PtgMemFunc/MemArrays - ArrayList ptgs = new ArrayList(); - Ptg[] p = ((PtgMystery) lookupArray).vars; - for (int j = 0; j < p.length; j++) { - if (p[j] instanceof PtgArea) { - Ptg[] pa = p[j].getComponents(); - for (int k = 0; k < pa.length; k++) - ptgs.add(pa[k]); - } else - ptgs.add(p[j]); - } - values = new Ptg[ptgs.size()]; - ptgs.toArray(values); - } else if (lookupArray instanceof PtgStr) { - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(lookupArray.getParentRec()); - pa.setLocation(lookupArray.toString()); - values = pa.getComponents(); - } else { // testing! - Logger.logErr("match: unknown type of lookup array"); - } - - // Step # 2- traverse thru value array to find lookupValue using matchType rules - // ALSO must ensure for matchType!=0 that array is in ascending or descending order - int retIndex = -1; - // TODO: matchType==0 Strings can match wildcards ... - for (int i = 1; i <= values.length; i++) { - Object v0 = values[i - 1].getValue(); - Object v1 = null; - if (i < values.length) - v1 = values[i].getValue(); - int mType = -2; // -1 means v0v1 - int match = -2; // test lookupValue against v0 (i-1) - if (v0 instanceof Integer) { - if (v1 != null) mType = (((Integer) v0).compareTo((Integer) v1)); - match = (((Integer) v0).compareTo((Integer) lookupValue)); - } else if (v0 instanceof Double) { - if (v1 != null) mType = (((Double) v0).compareTo((Double) v1)); - match = (((Double) v0).compareTo((Double) lookupValue)); - } else if (v0 instanceof Boolean) { - boolean bv0 = ((Boolean) v0).booleanValue(); - // 1.6 only if (v1!=null) mType= (((Boolean) v0).compareTo((Boolean)v1)); - if (v1 != null) { - boolean bv1 = ((Boolean) v1).booleanValue(); - mType = ((bv0 == bv1) ? 0 : ((!bv0 && bv1) ? -1 : +1)); - } - // 1.6 only match= (((Boolean) v0).compareTo((Boolean)lookupValue)); - boolean bv1 = ((Boolean) lookupValue).booleanValue(); - match = ((bv0 == bv1) ? 0 : ((!bv0 && bv1) ? -1 : +1)); - } else if (v0 instanceof String) { - if (v1 != null) mType = (((String) v0).compareTo((String) v1)); - match = (((String) v0).compareTo((String) lookupValue)); - } - if (i < values.length) { // only check order - if ((matchType == 1 && mType > 0)// not in ascending order - || - (matchType == -1 && mType < 0)) // not in descending order -// DOCUMENTATION SEZ MUST BE IN DESCENDING ORDER FOR -1 BUT EXCEL ALLOWS IT IN CERTAIN CIRCUMSTANCES -// ) - return new PtgErr(PtgErr.ERROR_NA); - } - if (matchType == 0 && match == 0) { - retIndex = i; // 1-based - break; - } - if (matchType == 1 && match <= 0) { - retIndex = i; // 1-based - } else if (matchType == -1 && match >= 0) { - retIndex = i; // 1-based - } - } -/* - for (int i= 0; i < values.length; i++) { - Object val=values[i].getValue(); - int mType= -2; - if (val instanceof Integer) - mType= ((Integer)val).compareTo((Integer)lookupValue); - else if (val instanceof Double) - mType= ((Double)val).compareTo((Double)lookupValue); - else if (val instanceof Boolean) - mType= ((Boolean)val).compareTo((Boolean)lookupValue); - else if (val instanceof String) - mType= ((String)val).toLowerCase().compareTo(((String)lookupValue).toLowerCase()); - if (matchType==0) {// matches if equal - if (mType==0) {// then got it! - retIndex= i; - break; - } - } else if (matchType==-1) { - } else { // default is 1 - } - } -*/ - if (retIndex > -1) - return new PtgInt(retIndex); - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_NA); - } - - /** - * calcOffset - * Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells. - * The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and the number of columns to be returned. - *

                      - * OFFSET(reference,rows,cols,height,width) - *

                      - * Reference is the reference from which you want to base the offset. Reference must refer to a cell or range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value. - * Rows is the number of rows, up or down, that you want the upper-left cell to refer to. Using 5 as the rows argument specifies that the upper-left cell in the reference is five rows below reference. Rows can be positive (which means below the starting reference) or negative (which means above the starting reference). - * Cols is the number of columns, to the left or right, that you want the upper-left cell of the result to refer to. Using 5 as the cols argument specifies that the upper-left cell in the reference is five columns to the right of reference. Cols can be positive (which means to the right of the starting reference) or negative (which means to the left of the starting reference). - * Height is the height, in number of rows, that you want the returned reference to be. Height must be a positive number. - * Width is the width, in number of columns, that you want the returned reference to be. Width must be a positive number. - */ - protected static Ptg calcOffset(Ptg[] operands) { - Ptg ref = operands[0]; // Reference must refer to a cell or range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value - if (!(ref instanceof PtgRef)) - return new PtgErr(PtgErr.ERROR_VALUE); - int nrows = operands[1].getIntVal(); - int ncols = operands[2].getIntVal(); - int height = -1; // Height if present, how many rows to return - must be positive - if (operands.length > 3) { - height = operands[3].getIntVal(); - if (height < 0) - return new PtgErr(PtgErr.ERROR_VALUE); - } - int width = -1; // Width if present, how many columns to return - must be positive - if (operands.length > 4) { - width = operands[4].getIntVal(); - if (width < 0) - return new PtgErr(PtgErr.ERROR_VALUE); - } - int[] rc = ((PtgRef) ref).getIntLocation(); - rc[0] += nrows; - rc[1] += ncols; - if (rc.length > 3) { // it's an area - rc[2] += nrows; - rc[3] += ncols; - } - // A height/width of 1, 1= a single reference - // When we increase either the row height or column width in the offset function "=OFFSET(A1,2,0,1,1)" - // to more than 1, the reference is converted to a range. - // OK, this may be the wrong interpretation of the height and width, but, from research, this is what I've come up with: - // height and width are only really applicable for an initial single reference - // in Excel, a height and width value of more than 1 returns #VALUE! unless wrapped in SUM - // ???? - if (height == 1 && width == 1) { // it's a single reference - if (rc.length > 3) { // truncate - int[] temp = new int[2]; - System.arraycopy(rc, 0, temp, 0, 2); - rc = temp; - } - } else if (!(height == -1 && width == -1)) { - if (rc.length < 3) { // make a range - int[] temp = new int[4]; - System.arraycopy(rc, 0, temp, 0, 2); - rc = temp; - } - if (height > 0) { - rc[2] = rc[0] + height - 1; // is this correct???? - } - if (width > 0) { - rc[3] = rc[1] + width - 1; // " " - } - } - if (rc.length > 3) { - // If rows and cols offset reference over the edge of the worksheet, OFFSET returns the #REF! error value. - if (rc[0] < 0 || rc[1] < 0 || rc[2] < 0 || rc[3] < 0) - return new PtgErr(PtgErr.ERROR_REF); - PtgArea pa = new PtgArea(false); - pa.setParentRec(ref.getParentRec()); - try { - String sh = ref.getLocation(); - int z = sh.indexOf('!'); - if (z > 0) { - pa.setSheetName(sh.substring(0, z)); - } - } catch (Exception e) { - } - pa.setLocation(rc); - return pa; - } else { // it's a single reference - // If rows and cols offset reference over the edge of the worksheet, OFFSET returns the #REF! error value. - if (rc[0] < 0 || rc[1] < 0) - return new PtgErr(PtgErr.ERROR_REF); - PtgRef pr = new PtgRef(); - pr.setParentRec(ref.getParentRec()); - pr.setLocation(rc); - return pr; - } - } - - /** - * TRANSPOSE - * Returns the transpose of an array - * The TRANSPOSE function returns a vertical range of cells as a horizontal range, or vice versa. - * The TRANSPOSE function must be entered as an array formula has columns and rows. - * Use TRANSPOSE to shift the vertical and horizontal orientation of an array or range on a - * worksheet. - *

                      - * array Required. An array or range of cells on a worksheet that you want to transpose. The transpose of an array is created by using the first row of the array as the first column of the new array, the second row of the array as the second column of the new array, and so on. - */ - protected static Ptg calcTranspose(Ptg[] operands) { - String retArray = ""; - PtgArray ret = new PtgArray(); - if (!(operands[0] instanceof PtgArray)) { - Ptg[] arr = operands[0].getComponents(); - //it's a list of values, convert to row-based - for (int i = 0; i < arr.length; i++) { - retArray = retArray + arr[i].getValue().toString() + ";"; - } - retArray = "{" + retArray.substring(0, retArray.length() - 1) + "}"; - ret.setVal(retArray); - } else { // transpose row/cols of an existing array - PtgArray pa = (PtgArray) operands[0]; - Ptg[] arr = pa.getComponents(); - int nc = pa.getNumberOfColumns() + 1; - int nr = pa.getNumberOfRows() + 1; - for (int i = 0; i < nc; i++) { - for (int j = 0; j < nc * nr; j += nc) { - retArray = retArray + arr[i + j].getValue().toString() + ","; - } - retArray = retArray.substring(0, retArray.length() - 1) + ";"; - } - retArray = "{" + retArray.substring(0, retArray.length() - 1) + "}"; - ret.setVal(retArray); - } - return ret; - } - - /** - * ROW - * Returns the row number of a reference - *

                      - * Note this is 1 based, ie Row1 = 1. - */ - protected static Ptg calcRow(Ptg[] operands) - throws FunctionNotSupportedException { - if (operands == null || operands.length != 1) return new PtgErr(PtgErr.ERROR_VALUE); - if (operands[0] instanceof PtgFuncVar) { - // we need to return the col where the formula is. - PtgFuncVar pfunk = (PtgFuncVar) operands[0]; - try { - int loc = pfunk.getParentRec().getRowNumber() + 1; - return new PtgInt(loc); - } catch (Exception e) { - Logger.logErr("Error running calcRow " + e); - } - } - try { // process as an array formula ... - boolean isArray = (operands[0].getParentRec() instanceof Array); - if (!isArray) { - if (operands[0] instanceof PtgRef) { - return new PtgInt((((PtgRef) operands[0]).getRowCol()[0]) + 1); - } else if (operands[0] instanceof PtgName) { // table??? - String range = ((PtgName) operands[0]).getName().getLocation(); - return new PtgInt(ExcelTools.getRowColFromString(range)[0] + 1); - } - return new PtgInt((operands[0].getIntLocation()[0]) + 1); - } else { - String retArry = ""; - Ptg[] comps = null; - if (operands[0] instanceof PtgRef) { - comps = operands[0].getComponents(); - } else if (operands[0] instanceof PtgName) { // table??? - comps = operands[0].getComponents(); - } - if (comps == null) { - return new PtgInt((((PtgRef) operands[0]).getRowCol()[0]) + 1); - } - for (int i = 0; i < comps.length; i++) { - try { - retArry = retArry + (((PtgRef) comps[i]).getIntLocation()[0] + 1) + ","; - } catch (Exception e) { - } - } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; - } - } catch (Exception ex) { - return new PtgRefErr(); - } - } - - - /** - * ROWS - * Returns the number of rows in a reference - *

                      - * ROWS(array) - *

                      - * Array is an array, an array formula, or a reference to a range of cells for which you want the number of rows. - *

                      - * =ROWS(C1:E4) Number of rows in the reference (4) - * =ROWS({1,2,3;4,5,6}) Number of rows in the array constant (2) - */ - protected static Ptg calcRows(Ptg[] operands) - throws FunctionNotSupportedException { - try { - int rsz = 0; - if (operands[0] instanceof PtgStr) { - String rangestr = operands[0].getValue().toString(); - String startx = rangestr.substring(0, rangestr.indexOf(":")); - String endx = rangestr.substring(rangestr.indexOf(":") + 1); - - int[] startints = ExcelTools.getRowColFromString(startx); - int[] endints = ExcelTools.getRowColFromString(endx); - rsz = endints[0] - startints[0]; - rsz++; // inclusive - } else if (operands[0] instanceof PtgName) { - int[] rc = ExcelTools.getRangeCoords(operands[0].getLocation()); - rsz = rc[2] - rc[0]; - rsz++; // inclusive - } else if (operands[0] instanceof PtgRef) { - int[] rc = ExcelTools.getRangeCoords(((PtgRef) operands[0]).getLocation()); - rsz = rc[2] - rc[0]; - rsz++; // inclusive - } else if (operands[0] instanceof PtgMemFunc) { - Ptg[] p = operands[0].getComponents(); - if (p != null && p.length > 0) { - int[] rc0 = p[0].getIntLocation(); - int[] rc1 = null; - if (p.length > 1) - rc1 = p[p.length - 1].getIntLocation(); - if (rc1 == null) - rsz = 0; - else - rsz = rc1[0] - rc0[0]; - rsz++; - } else - return new PtgErr(PtgErr.ERROR_VALUE); - } else - return new PtgErr(PtgErr.ERROR_VALUE); - return new PtgInt(rsz); - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - - /** - * VLOOKUP - * Looks in the first column of an array and moves across the row to return the value of a cell - * Searches for a value in the leftmost column of a table, and then returns a value in the same row - * from a column you specify in the table. Use VLOOKUP instead of HLOOKUP when your comparison - * values are located in a column to the left of the data you want to find. - *

                      - * Syntax - *

                      - * VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) - *

                      - * Lookup_value is the value to be found in the first column of the array. Lookup_value can be a value, - * a reference, or a text string. - *

                      - * Table_array is the table of information in which data is looked up. Use a reference to a range or - * a range name, such as Database or List. - *

                      - * If range_lookup is TRUE, the values in the first column of table_array must be placed in ascending order: - * ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE; otherwise VLOOKUP may not give the correct value. - * If range_lookup is FALSE, table_array does not need to be sorted. - * You can put the values in ascending order by choosing the Sort command from the Data menu and selecting Ascending. - * The values in the first column of table_array can be text, numbers, or logical values. - * Uppercase and lowercase text are equivalent. - * Col_index_num is the column number in table_array from which the matching value must be returned. - * A col_index_num of 1 returns the value in the first column in table_array; a col_index_num of 2 returns the value - * in the second column in table_array, and so on. If col_index_num is less than 1, - * VLOOKUP returns the #VALUE! error value; if col_index_num is greater than the number of columns in table_array, - * VLOOKUP returns the #REF! error value. - *

                      - * Range_lookup is a logical value that specifies whether you want VLOOKUP to find an exact match or an - * approximate match. If TRUE or omitted, an approximate match is returned. In other words, - * if an exact match is not found, the next largest value that is less than lookup_value is returned. - * If FALSE, VLOOKUP will find an exact match. If one is not found, the error value #N/A is returned. - *

                      - * Remarks - *

                      - * If VLOOKUP can't find lookup_value, and range_lookup is TRUE, it uses the largest value that is less than or - * equal to lookup_value. - * If lookup_value is smaller than the smallest value in the first column of table_array, VLOOKUP returns the - * #N/A error value. - * If VLOOKUP can't find lookup_value, and range_lookup is FALSE, VLOOKUP returns the #N/A value. - *

                      - * VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) - *

                      - * On the preceding worksheet, where the range A4:C12 is named Range: - *

                      - * VLOOKUP(1,Range,1,TRUE) equals 0.946 - *

                      - * VLOOKUP(1,Range,2) equals 2.17 - *

                      - * VLOOKUP(1,Range,3,TRUE) equals 100 - *

                      - * VLOOKUP(.746,Range,3,FALSE) equals 200 - *

                      - * VLOOKUP(0.1,Range,2,TRUE) equals #N/A, because 0.1 is less than the smallest value in column A - *

                      - * VLOOKUP(2,Range,2,TRUE) equals 1.71 - */ - protected static Ptg calcVlookup(Ptg[] operands) - throws FunctionNotSupportedException { - - boolean rangeLookup = true; // truth of "approximate match"; must be sorted - boolean isNumber = true; - try { - Ptg lookup_value = operands[0]; - Ptg table_array = operands[1]; - - // can't assume that it's a PtgInt - //PtgInt col_index_num = (PtgInt)operands[2].getValue(); - //int colNum = col_index_num.getVal() -1;// reduce 1 for ordinal base off firstcol - Object o = operands[2].getValue(); - int colNum = 0; - if (o instanceof Double) - colNum = ((Double) o).intValue() - 1; // reduce 1 for ordinal base off firstcol - else // assume int? - colNum = ((Integer) o).intValue() - 1; // reduce 1 for ordinal base off firstcol - if (operands.length > 3) { - Object vx = operands[3].getValue(); - if (vx != null) { - try { - Boolean sort = (Boolean) vx; - rangeLookup = sort.booleanValue(); - } catch (ClassCastException e) { - Integer bool = (Integer) vx; - if (bool.intValue() == 0) rangeLookup = false; - } - } - } - - PtgRef[] lookupComponents = null; - PtgRef[] valueComponents = null; - // first, get the lookup Column Vals - if (table_array instanceof PtgName) { // 20090211 KSC: - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(table_array.getParentRec()); - - pa.setLocation(((PtgName) table_array).getName().getLocation()); - table_array = pa; - if (((PtgArea3d) table_array).isExternalRef()) { - Logger.logWarn("LookupReferenceCalculator.calcVlookup External References are disallowed"); - return new PtgErr(PtgErr.ERROR_REF); - } - } - if (table_array instanceof PtgArea) { - try { - PtgArea pa = (PtgArea) table_array; - int[] range = table_array.getIntLocation(); - // TODO: check rc sanity here - int firstcol = range[1]; - lookupComponents = (PtgRef[]) pa.getColComponents(firstcol); - valueComponents = (PtgRef[]) pa.getColComponents(firstcol + colNum); - } catch (/*20070209 KSC: FormulaNotFound*/Exception e) { - Logger.logWarn("LookupReferenceCalculator.calcVlookup cannot determine PtgArea location. " + e); - } - - } else if (table_array instanceof PtgMemFunc) { // || table_array instanceof PtgMemArea){ - try { - PtgMemFunc pa = (PtgMemFunc) table_array; - // int[] range = table_array.getIntLocation(); - int firstcol = -1; - - try { - int[] rc1 = pa.getFirstloc().getIntLocation(); - - // TODO: check rc sanity here - firstcol = rc1[1]; - } catch (Exception e) { - Logger.logWarn("LookupReferenceCalculator.calcVlookup could not determine row col from PtgMemFunc."); - } - - lookupComponents = (PtgRef[]) pa.getColComponents(firstcol); - valueComponents = (PtgRef[]) pa.getColComponents(firstcol + colNum); - } catch (/*20070209 KSC: FormulaNotFound*/Exception e) { - Logger.logWarn("LookupReferenceCalculator.calcVlookup cannot determine PtgArea location. " + e); - } - } - // error check - if (lookupComponents == null || lookupComponents.length == 0) - return new PtgErr(PtgErr.ERROR_REF); - if (lookup_value == null || lookup_value.getValue() == null) // 20070221 KSC: Error trap getValue - return new PtgErr(PtgErr.ERROR_NULL); - // lets check if we are dealing with strings or numbers.... - try { - String val = lookup_value.getValue().toString(); - if (val.length() == 0) // 20090205 KSC - return new PtgErr(PtgErr.ERROR_NA); - Double d = new Double(val); - } catch (NumberFormatException e) { - isNumber = false; - } - - // TODO: - //if the value you supply for the lookup_value argument is smaller than the smallest value in the first column of the table_array argument, VLOOKUP returns the #N/A error value. - - if (isNumber) { - double match_num; - try { - match_num = Double.parseDouble( - lookup_value.getValue().toString()); - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_NA); - } - - for (int i = 0; i < lookupComponents.length; i++) { - double val; - - try { - val = Double.parseDouble( - lookupComponents[i].getValue().toString()); - if (val == 0) { // VLOOKUP does NOT treat blanks as 0's - if (lookupComponents[i].refCell[0] == null) { - continue; - } - } - } catch (NumberFormatException e) { - // Ignore entries in the table that aren't numbers. - continue; - } - - if (val == match_num) { - return valueComponents[i].getPtgVal(); - } else if (rangeLookup && val > match_num) { - if (i == 0) return new PtgErr(PtgErr.ERROR_NA); - else return valueComponents[i - 1].getPtgVal(); - } - } - - if (rangeLookup) return valueComponents[lookupComponents.length - 1].getPtgVal(); - else return new PtgErr(PtgErr.ERROR_NA); - } - - // It's a String - else { - if (rangeLookup) { // approximate match - String match_str = lookup_value.getValue().toString(); - int match_len = match_str.length(); - for (int i = 0; i < lookupComponents.length; i++) { - try { - String val = lookupComponents[i].getValue().toString(); - if (val.equalsIgnoreCase(match_str)) {// we found it - return valueComponents[i].getPtgVal(); - } else if (val.length() >= match_len && val.substring(0, match_len).equalsIgnoreCase(match_str)) { // matches up to length, but not all, return previous - return valueComponents[i - 1].getPtgVal(); - } else if (ExcelTools.getIntVal(val.substring(0, 1)) > ExcelTools.getIntVal(match_str.substring(0, 1))) { - return valueComponents[i - 1].getPtgVal(); - } else if (i == lookupComponents.length - 1) {// we reached the last one so use this - return valueComponents[i].getPtgVal(); - } - } catch (Exception e) { - } // 20070209 KSC: ignore errors in lookup cells - } - } else { // unsorted - String match_str = lookup_value.getValue().toString(); - for (int i = 0; i < lookupComponents.length; i++) { - try { - String val = lookupComponents[i].getValue().toString(); - try { - if (val.equalsIgnoreCase(match_str)) {// we found it - return valueComponents[i].getPtgVal(); - } else if (i == lookupComponents.length - 1) {// we reached the last one so error out - return new PtgErr(PtgErr.ERROR_NA); - } - } catch (Exception e) { - Logger.logErr("LookupReferenceCalculator.calcVLookup error: " + e.toString()); - return new PtgErr(PtgErr.ERROR_NA); - } - } catch (Exception e) { - } // 20070209 KSC: ignore errors in lookup cells - } - } - } - } catch (Exception e) { // appears that an error with operands results in a #NA error - return new PtgErr(PtgErr.ERROR_NA); - - } - return new PtgErr(PtgErr.ERROR_NULL); - } - -} diff --git a/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.kt new file mode 100644 index 0000000..e567487 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/LookupReferenceCalculator.kt @@ -0,0 +1,1424 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.* +import io.starter.toolkit.Logger + +import java.util.ArrayList + +/** + * LookupReferenceCalculator is a collection of static methods that operate + * as the Microsoft Excel function calls do. + * + * + * All methods are called with an array of ptg's, which are then + * acted upon. A Ptg of the type that makes sense (ie boolean, number) + * is returned after each method. + */ + +object LookupReferenceCalculator { + + /** + * ADDRESS + * Creates a cell address as text, given specified row and column numbers. + * + * + * Syntax + * ADDRESS(row_num,column_num,abs_num,a1,sheet_text) + * Row_num is the row number to use in the cell reference. + * Column_num is the column number to use in the cell reference. + * Abs_num specifies the type of reference to return. + * + * + * Abs_num Returns this type of reference + * 1 or omitted Absolute + * + * + * 2 Absolute row; relative column + * + * + * 3 Relative row; absolute column + * + * + * 4 Relative + * + * + * A1 is a logical value that specifies the A1 or R1C1 reference style. + * If a1 is TRUE or omitted, ADDRESS returns an A1-style reference; + * if FALSE, ADDRESS returns an R1C1-style reference. + * + * + * Sheet_text is text specifying the name of the worksheet to be + * used as the external reference. If sheet_text is omitted, no sheet name is used. + * + * + * Examples + * + * + * ADDRESS(2,3) equals "$C$2" + * + * + * ADDRESS(2,3,2) equals "C$2" + * + * + * ADDRESS(2,3,2,FALSE) equals "R2C[3]" + * + * + * ADDRESS(2,3,1,FALSE,"[Book1]Sheet1") equals "[Book1]Sheet1!R2C3" + * + * + * ADDRESS(2,3,1,FALSE,"EXCEL SHEET") equals "'EXCEL SHEET'!R2C3" + */ + internal fun calcAddress(operands: Array): Ptg { + if (operands.size < 2) return PtgErr(PtgErr.ERROR_VALUE) + // deal with floating point refs + var rx1 = operands[0].value.toString() + if (rx1.indexOf(".") > -1) + rx1 = rx1.substring(0, rx1.indexOf(".")) + val row = Integer.valueOf(rx1).toInt() + // deal with floating point refs + var cx1 = operands[1].value.toString() + if (cx1.indexOf(".") > -1) + cx1 = cx1.substring(0, cx1.indexOf(".")) + + val col = Integer.valueOf(cx1).toInt() + var abs_num = 1 + var ref_style = true + var sheettext = "" + if (operands.size > 2) { + if (operands[2].value != null) { //checking for a ptgmissarg + abs_num = (operands[2].value as Int).toInt() + } + } + if (operands.size > 3) { + if (operands[3].value != null) { //checking for a ptgmissarg + val b = java.lang.Boolean.valueOf(operands[3].value.toString()) + ref_style = b.booleanValue() + } + } + if (operands.size > 4) { + if (operands[4].value != null) { //checking for a ptgmissarg + sheettext = operands[4].value.toString() + "!" + } + } + var loc = "" + val colstr = ExcelTools.getAlphaVal(col - 1) + if (ref_style) { + if (abs_num == 1) { + loc = "$$colstr$$row" + } else if (abs_num == 2) { + loc = "$colstr$$row" + } else if (abs_num == 3) { + loc = "$$colstr$row" + } else if (abs_num == 4) { + loc = colstr + row + } + } else { + if (abs_num == 1) { + loc = "R" + row + "C" + col // this is transposed with abs_num 4. Error in Excel + } else if (abs_num == 2) { + loc = "R" + row + "C[" + col + "]" + } else if (abs_num == 3) { + loc = "R[$row]C$col" + } else if (abs_num == 4) { + loc = "R[$row]C[$col]" + + } + } + loc = sheettext + loc + return PtgStr(loc) + + } + + /** + * AREAS + * Returns the number of areas in a reference. An area is a range of contiguous cells or a single cell. + * + * + * Reference is a reference to a cell or range of cells and can refer to multiple areas. + * If you want to specify several references as a single argument, then you must include extra sets of parentheses so that Microsoft Excel will not interpret the comma as a field separator. + * + * + * NOTE: this appears to be correct given Excel information but logic is not 100% known + * e.g. =AREAS(B2:D4 D3) = 1 + * =AREAS(B2:D4 E3) gives a #NULL! error - why? + * =AREAS(B2:D4,E3) = 2 + */ + internal fun calcAreas(operands: Array): Ptg { + val ref = operands[0] + val s = ref.toString() + val areas = s.split(",(?=([^'|\"]*'[^'|\"]*'|\")*[^'|\"]*$)".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + return PtgNumber(areas.size.toDouble()) + } + + /** + * CHOOSE + * Chooses a value from a list of values + * + * + * Note, this function does not support one specific use-case. That is choosing a ptgref + * and using that ptgref to complete a ptgarea. Example + * =SUM(E6:CHOOSE(3,G4,G5,G6)) + * =SUM(E6:G6) + */ + internal fun calcChoose(operands: Array): Ptg { + if (operands.size < 2) return PtgErr(PtgErr.ERROR_VALUE) + var o = operands[0].value + try { + val dd = Double(o.toString()) // this can be non-integer, so truncate it if so... + val i = dd.toInt() + if (i > operands.size + 1 || i < 1) return PtgErr(PtgErr.ERROR_REF) + o = operands[i].value + val d = o as Double + return PtgNumber(d) + } catch (ex: Exception) { + PtgErr(PtgErr.ERROR_VALUE) + } + + return PtgStr(o.toString()) + + } + + /** + * COLUMN + * Returns the column number of a reference + */ + internal fun calcColumn(operands: Array): Ptg { + if (operands[0] is PtgFuncVar) { + // we need to return the col where the formula is. + val pfunk = operands[0] as PtgFuncVar + try { + var loc = pfunk.parentRec!!.colNumber.toInt() + loc += 1 + return PtgInt(loc) + } catch (e: Exception) { + } + + } else { + // It's ugly, but we are going to handle the four types of references seperately, as there is no good way + // to generically get this info + try { + if (operands[0] is PtgArea) { + val pa = operands[0] as PtgArea + val loc = pa.intLocation + return PtgInt(loc!![1] + 1) + } else if (operands[0] is PtgRef) { + val pref = operands[0] as PtgRef + var loc = pref.intLocation!![1] + loc += 1 + return PtgInt(loc) + } else if (operands[0] is PtgName) { // table??? + val range = (operands[0] as PtgName).name!!.location + val loc = ExcelTools.getRangeCoords(range) + return PtgInt(loc[1] + 1) + } + } catch (e: Exception) { + } + + } + return PtgInt(-1) + } + + /** + * COLUMNS + * Returns the number of columns in an array reference or array formula + */ + // TODO: Not finished yet! + internal fun calcColumns(operands: Array): Ptg { + // + if (operands[0] is PtgFuncVar) { + // we need to return the col where the formula is. + val pfunk = operands[0] as PtgFuncVar + try { + var loc = pfunk.parentRec!!.colNumber.toInt() + loc += 1 + return PtgInt(loc) + } catch (e: Exception) { + } + + } else { + // It's ugly, but we are going to handle the four types of references seperately, as there is no good way + // to generically get this info + try { + if (operands[0] is PtgArea) { + val pa = operands[0] as PtgArea + val loc = pa.intLocation + val ncols = loc!![3] - loc[1] + 1 + return PtgInt(ncols) + } else if (operands[0] is PtgArray) { + val parr = operands[0] as PtgArray + return PtgInt(parr.numberOfColumns + 1) + } + } catch (e: Exception) { + } + + } + return PtgInt(-1) + } + + /** + * HLOOKUP: Looks in the top row of an array and returns the value of the + * indicated cell. + */ + @Throws(FunctionNotSupportedException::class) + internal fun calcHlookup(operands: Array): Ptg { + + if (operands.size < 3) return PtgErr(PtgErr.ERROR_VALUE) + var sorted = true + var isNumber = true + val lookup_value = operands[0] + val table_array = operands[1] + val row_index_num = operands[2] as PtgInt + val rowNum = row_index_num.`val` - 1// reduce 1 for ordinal base off firstcol + if (operands.size > 3) { + if (operands[3].value != null) { + val o = operands[3].value + if (o is Boolean) + sorted = o.booleanValue() + else if (o is Int) + sorted = o.toInt() != 0 + } + } + val retarea = intArrayOf(0, 0) + val bs = lookup_value.parentRec.sheet + val bk = table_array.parentRec.workBook + var lookupComponents: Array? = null + var valueComponents: Array? = null + // first, get the lookup Column Vals + if (table_array is PtgName) { + // Handle getting vals out of name + + } else if (table_array is PtgArea || table_array is PtgArea3d) { + try { + val pa = table_array as PtgArea + val range = table_array.intLocation + + // TODO: check rc sanity here + val firstrow = range[0] + lookupComponents = pa.getRowComponents(firstrow) as Array + valueComponents = pa.getRowComponents(firstrow + rowNum) as Array + } catch (/*20070209 KSC: FormulaNotFound*/e: Exception) { + Logger.logWarn("Error in LookupReferenceCalculator: Cannot determine PtgArea location. $e") + } + + } + // error check + if (lookupComponents == null || lookupComponents.size == 0) return PtgErr(PtgErr.ERROR_REF) + // lets check if we are dealing with strings or numbers.... + try { + val `val` = lookupComponents[0].value!!.toString() + val d = Double(`val`) + } catch (e: NumberFormatException) { + isNumber = false + } + + if (isNumber) { + val match_num: Double + try { + match_num = java.lang.Double.parseDouble( + lookup_value.value.toString()) + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_NA) + } + + for (i in lookupComponents.indices) { + val `val`: Double + try { + `val` = java.lang.Double.parseDouble( + lookupComponents[i].value!!.toString()) + } catch (e: NumberFormatException) { + // Ignore entries in the table that aren't numbers. + continue + } + + if (`val` == match_num) { + return valueComponents!![i].ptgVal + } else if (sorted && `val` > match_num) { + return if (i == 0) + PtgErr(PtgErr.ERROR_NA) + else + valueComponents!![i - 1].ptgVal + } + } + + return if (sorted) + valueComponents!![lookupComponents.size - 1].ptgVal + else + PtgErr(PtgErr.ERROR_NA) + + } else { + //TODO: need to handle as string + } + + return PtgErr(PtgErr.ERROR_NULL) + } + + /** + * HYPERLINK + * Creates a shortcut or jump that opens a document + * stored on a network server, an intranet, or the Internet + * + * + * Function just returns the "friendly name" of the link, + * Excel doesn't appear to validate the url ... + */ + + internal fun calcHyperlink(operands: Array): Ptg { + try { + return if (operands.size == 2) PtgStr(operands[1].value.toString()) else PtgStr(operands[0].value.toString()) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + /** + * INDEX + * Returns a value or the reference to a value from within a table or range. + * There are two forms of the INDEX function: the array form and the reference form. + * + * + * Array Form: + * Returns the value of an element in a table or an array selected by the row and column number indexes. + * Use the array form if the first argument to INDEX is an array constant. + * INDEX(array,row_num,column_num) + * Array is a range of cells or an array constant. + * If array contains only one row or column, + * the corresponding row_num or column_num argument is optional. + * If array has more than one row and more than one column, and only row_num or column_num is used, + * INDEX returns an array of the entire row or column in array. + * Row_num selects the row in array from which to return a value. If row_num is omitted, column_num is required. + * Column_num selects the column in array from which to return a value. If column_num is omitted, row_num is required. + * + * + * Reference Form: + * Returns the reference of the cell at the intersection of a particular row and column. + * If the reference is made up of nonadjacent selections, you can pick the selection to look in. + * INDEX(reference,row_num,column_num,area_num) + * Reference is a reference to one or more cell ranges. + * If you are entering a nonadjacent range for the reference, enclose reference in parentheses. + * If each area in reference contains only one row or column, the row_num or column_num argument, respectively, is optional. For example, for a single row reference, use INDEX(reference,,column_num). + * Row_num is the number of the row in reference from which to return a reference. + * Column_num is the number of the column in reference from which to return a reference. + * Area_num selects a range in reference from which to return the intersection of row_num and column_num. The first area selected or entered is numbered 1, the second is 2, and so on. If area_num is omitted, INDEX uses area 1. + * + * + * Given a BiffRec Range, choose the cell within referenced by the + * row and column operands. + * + * + * example: + * =INDEX(G3:L8,6,4) + * returns the value of the BiffRec at row 6, col 4 in the following table + * which is 0.11 + * + * + * G H I J K L + * 1 + * 2 + * 3 0.007 0.005 0.003 0.002 0.002 0.001 + * 4 0.025 0.017 0.012 0.008 0.006 0.005 + * 5 0.062 0.044 0.032 0.023 0.018 0.015 + * 6 0.116 0.086 0.064 0.049 0.04 0.035 + * 7 0.171 0.13 0.101 0.082 0.07 0.062 + * 8 0.211 0.165 0.132 0.11 0.096 0.088 + */ + internal fun calcIndex(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + var o = operands[0] + // only 1st operand is required; changed below (see doc snippet below) + /*If each area in reference contains only one row or column, + * the row_num or column_num argument, respectively, is optional. + * For example, for a single row reference, use INDEX(reference,,column_num). + */ + var rowref: Any = 1 // defaults (1-based) + var colref: Any = 1 + var areanum = 1.0 + var retarea: IntArray? = null + var sht: String? = null + try { + if (operands.size > 1) { + val rowrefp = operands[1] + rowref = rowrefp.value + } + if (operands.size > 2) { + val colrefp = operands[2] + colref = colrefp.value + } + if (operands.size > 3) { + areanum = operands[3].doubleVal + } + if (o is PtgArea) { + retarea = o.intLocation + sht = o.sheetName + } else if (o is PtgName) { + //Ptg[] p=((PtgName) o).getName().getComponents(); //CellRangePtgs(); + //o= p[0]; + val r = o.name!!.location + retarea = ExcelTools.getRangeRowCol(r!!) + sht = if (r!!.indexOf("!") == -1) null else r!!.substring(0, r!!.indexOf("!")) + } else if (o is PtgMemFunc) { + val ps = o.components + areanum-- + if (areanum >= 0 && areanum < ps.size) + o = ps[areanum.toInt()] + else + o = ps[0] + retarea = (o as PtgArea).intLocation + sht = o.sheetName + } else if (o is PtgArray) { + val ps = o.components + areanum-- + if (areanum >= 0 && areanum < ps.size) + o = ps[areanum.toInt()] + else + o = ps[0] + retarea = (o as PtgArea).intLocation + // TODO: Sheet! + } + + // now should have the correct area + if (retarea != null) { + // really we can just use the first position to get a ref + // then the second just checks bounds + // TODO: check rc sanity here + val rowoff = retarea[0] + val coloff = retarea[1] + val rowck = retarea[2] + 1 + val colck = retarea[3] + 1 + + val dims = IntArray(2) + try { + val rr: Int + if (rowref is Int) { + rr = rowref.toInt() + } else { + var rw = rowref.toString() + if (rw.indexOf(".") > -1) rw = rw.substring(0, rw.indexOf(".")) + // string non Integer Chars... + rr = Integer.parseInt(rw) + } + val cr: Int + if (colref is Int) { + cr = colref.toInt() + } else { + var cl = colref.toString() + if (cl.indexOf(".") > -1) cl = cl.substring(0, cl.indexOf(".")) + // string non Integer Chars... + cr = Integer.parseInt(cl) + } + + if (rr > rowck || cr > colck) { + return PtgErr(PtgErr.ERROR_REF) + } + dims[0] = rr + rowoff - 1 + dims[1] = cr + coloff - 1 + + // here's a nice new ref... + var refp = PtgRef() + if (o is PtgArea3d) refp = PtgRef3d() + refp.parentRec = o.parentRec + refp.useReferenceTracker = false + if (sht != null) refp.sheetName = sht + refp.setLocation(dims) + if (o is PtgArea3d) { + refp.location = o.sheetName + "!" + ExcelTools.formatLocation(dims) + } else { + + } + return refp + + } catch (e: NumberFormatException) { + //Logger.logWarn("could not calculate INDEX function: " + o.toString() + ":" + e); + return PtgErr(PtgErr.ERROR_NULL) // ERR or #VALUE ?? + } + + } + } catch (e: Exception) { + Logger.logWarn("could not calculate INDEX function: $o:$e") + } + + return PtgErr(PtgErr.ERROR_NULL) + + } + + + /** + * INDIRECT + * Returns a reference indicated by a text value + * + * + * INDIRECT(ref_text,a1) + * + * + * Ref_text is a reference to a cell that contains an A1-style + * reference, an R1C1-style reference, a name defined as a reference, + * or a reference to a cell as a text string. If ref_text is not a valid + * cell reference, INDIRECT returns the #REF! error value. + * + * + * If ref_text refers to another workbook (an external reference), + * the other workbook must be open. If the source workbook is not open, + * INDIRECT returns the #REF! error value. + * + * + * A1 is a logical value that specifies what type of reference is contained in the cell ref_text. + * + * + * If a1 is TRUE or omitted, ref_text is interpreted as an A1-style reference. + * If a1 is FALSE, ref_text is interpreted as an R1C1-style reference. + */ + internal fun calcIndirect(operands: Array): Ptg { + var operands = operands + try { + if (operands[0] is PtgStr) { + val ps = operands[0] as PtgStr + + var locx: String? = ps.toString() + // detect range + if (!(FormulaParser.isRef(locx) || FormulaParser.isRange(locx))) { // see if it's a named range + val nmx = ps.parentRec!!.workBook!!.getName(locx) + if (nmx != null) { // there is a named range + locx = nmx.location + } else + return ps //it's just a value + } + if ("" == locx) + return PtgInt(0) // that's what Excel does! + val refp = PtgArea3d(false) + refp.parentRec = ps.parentRec + refp.useReferenceTracker = true // very important!!! :) + refp.location = locx + return refp + + } else if (operands[0] is PtgRef) { + // check if the ptgRef value is a string representing a Named range + val o = operands[0].value + val ps = PtgStr(o.toString()) + ps.parentRec = operands[0].parentRec + operands = arrayOfNulls(1) + operands[0] = ps + return calcIndirect(operands) + } else if (operands[0] is PtgName) { + return calcIndirect(operands[0].components) + } + } catch (e: Exception) { + //Logger.logErr("INDIRECT: " + e.toString()); + } + + return PtgErr(PtgErr.ERROR_REF) // 's what Excel does ... + } + + /** + * LOOKUP + * The LOOKUP function returns a value either from a one-row or one-column range + * You can also use the LOOKUP function as an alternative to the IF function for elaborate tests or + * tests that exceed the limit for nesting of functions. See the examples in the array form. + * For the LOOKUP function to work correctly, the data being looked up must be sorted in + * ascending order. If this is not possible, consider using the VLOOKUP, HLOOKUP, or MATCH functions. + * + * + * A vector is a range of only one row or one column. + * The vector form of LOOKUP looks in a one-row or one-column range (known as a vector) for a value and returns a value from the same position in a second one-row or one-column range. Use this form of the LOOKUP function when you want to specify the range that contains the values that you want to match. The other form of LOOKUP automatically looks in the first column or row. + */ + fun calcLookup(operands: Array): Ptg { + val lookup = operands[0].value.toString().toUpperCase() + if (operands.size > 2) { //normal version of lookup + val vector = operands[1].components + val returnvector = operands[2].components ?: // happens when operands[2] is a PtgRef + return PtgNumber(0.0) +// this is what excel does + + //If the LOOKUP function can't find the lookup_value, the function matches the largest value in lookup_vector that is less than or equal to lookup_value. + //If lookup_value is smaller than the smallest value in lookup_vector, LOOKUP returns the #N/A error value + var retval: Any? = null + for (i in vector.indices) { + if (Calculator.compareCellValue(vector[i].value, lookup, ">")) + break + if (i < returnvector.size) + retval = returnvector[i].value + } + return if (retval is Number) + PtgNumber(retval.toDouble()) + else if (retval is Boolean) + PtgBool(retval.booleanValue()) + else if (retval == null) + PtgErr(PtgErr.ERROR_NA) + else + // assume string + PtgStr(retval.toString()) + } else { //array form of lookup + /* + * The array form of LOOKUP looks in the first row or column of an array for the specified value + * and returns a value from the same position in the last row or column of the array. + * Use this form of LOOKUP when the values that you want to match are in the first row + * or column of the array. Use the other form of LOOKUP when you want to specify the location + * of the column or row. + In general, it's best to use the HLOOKUP or VLOOKUP function instead of the array form of LOOKUP. This form of LOOKUP is provided for compatibility with other spreadsheet programs. + + With the HLOOKUP and VLOOKUP functions, you can index down or across, but LOOKUP always selects the last value in the row or column. + + */ + try { + val array = operands[1].components + val nrs = (operands[1] as PtgArray).numberOfRows + var ncs = (operands[1] as PtgArray).numberOfColumns + //If array covers an area that is wider than it is tall (more columns than rows), LOOKUP searches for the value of lookup_value in the first row. + //If an array is square or is taller than it is wide (more rows than columns), LOOKUP searches in the first column. + var retval: Any? = null + var found = false + val rowbased = ncs > nrs + ncs++ // make 1-based + var i = 0 + var j = 0 + while (j < nrs && !found) { + val start = i + while (i < start + ncs && !found) { + if (Calculator.compareCellValue(array[i].value, lookup, ">")) { + found = true + break + } + // returns a value from the same position in the last row or column of the array + if (rowbased) { + retval = array[i + ncs].value + } else { + retval = array[i + 1].value + i++ + } + i++ + } + j++ + } + return if (retval is Number) + PtgNumber(retval.toDouble()) + else if (retval is Boolean) + PtgBool(retval.booleanValue()) + else if (retval == null) + PtgErr(PtgErr.ERROR_NA) + else + // assume string + PtgStr(retval.toString()) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_NA) + } + + } + } + + + /** + * MATCH + * Looks up values in a reference or array + * Returns the relative position of an item in an array that matches a specified value in a + * specified order. Use MATCH instead of one of the LOOKUP functions when you need the position + * of an item in a range instead of the item itself. + * + * + * MATCH(lookup_value,lookup_array,match_type) + * + * + * Lookup_value is the value you want to match in lookup_array. For example, when you look up someone's number + * in a telephone book, you are using the person's name as the lookup value, but the telephone number is + * the value you want. + * Lookup_value can be a value (number, text, or logical value) or a cell reference to a number, + * text, or logical value. + * + * + * Lookup_array is a contiguous range of cells containing possible lookup values. Lookup_array must + * be an array or an array reference. + * + * + * Match_type is the number -1, 0, or 1. Match_type specifies how Microsoft Excel matches lookup_value + * with values in lookup_array. + * + * + * If match_type is 1, MATCH finds the largest value that is less than or equal to lookup_value. + * Lookup_array must be placed in ascending order: ...-2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE. + * + * + * If match_type is 0, MATCH finds the first value that is exactly equal to lookup_value. + * Lookup_array can be in any order. + * + * + * If match_type is -1, MATCH finds the smallest value that is greater than or equal to lookup_value. + * Lookup_array must be placed in descending order: TRUE, FALSE, Z-A, ...2, 1, 0, -1, -2, ..., and so on. + * + * + * If match_type is omitted, it is assumed to be 1. + * + * + * MATCH returns the position of the matched value within lookup_array, not the value itself. For example, MATCH("b",{"a","b","c"},0) returns 2, the relative position of "b" within the array {"a","b","c"}. + * MATCH does not distinguish between uppercase and lowercase letters when matching text values. + * If MATCH is unsuccessful in finding a match, it returns the #N/A error value. + * If match_type is 0 and lookup_value is text, lookup_value can contain the wildcard characters asterisk (*) and question mark (?). An asterisk matches any sequence of characters; a question mark matches any single character. + */ + fun calcMatch(operands: Array): Ptg { + try { + val lookupValue = operands[0].value // should be one value or a reference + var lookupArray = operands[1] // array or array reference (PtgArea) + var values: Array? = null + var matchType = 1 + if (operands.size > 2) { + val o = operands[2].value + if (o is Int) + matchType = o.toInt() + else + matchType = (o as Double).toInt() + + } + // Step 1- get all the components of the lookupArray (Array or Array Reference + if (lookupArray is PtgName) { + val pa = PtgArea3d(false) + pa.parentRec = lookupArray.parentRec + pa.location = lookupArray.name!!.location + lookupArray = pa + } + if (lookupArray is PtgArea) { + values = lookupArray.components + } else if (lookupArray is PtgMemFunc) { + values = lookupArray.components + } else if (lookupArray is PtgArray) { + values = lookupArray.components + } else if (lookupArray is PtgMystery) { + // PtgMystery is return from PtgMemFunc/MemArrays + val ptgs = ArrayList() + val p = lookupArray.vars + for (j in p!!.indices) { + if (p[j] is PtgArea) { + val pa = p[j].components + for (k in pa.indices) + ptgs.add(pa[k]) + } else + ptgs.add(p[j]) + } + values = arrayOfNulls(ptgs.size) + ptgs.toTypedArray() + } else if (lookupArray is PtgStr) { + val pa = PtgArea3d(false) + pa.parentRec = lookupArray.parentRec + pa.location = lookupArray.toString() + values = pa.components + } else { // testing! + Logger.logErr("match: unknown type of lookup array") + } + + // Step # 2- traverse thru value array to find lookupValue using matchType rules + // ALSO must ensure for matchType!=0 that array is in ascending or descending order + var retIndex = -1 + // TODO: matchType==0 Strings can match wildcards ... + for (i in 1..values!!.size) { + val v0 = values[i - 1].value + var v1: Any? = null + if (i < values.size) + v1 = values[i].value + var mType = -2 // -1 means v0v1 + var match = -2 // test lookupValue against v0 (i-1) + if (v0 is Int) { + if (v1 != null) mType = v0.compareTo((v1 as Int?)!!) + match = v0.compareTo(lookupValue as Int) + } else if (v0 is Double) { + if (v1 != null) mType = v0.compareTo((v1 as Double?)!!) + match = v0.compareTo(lookupValue as Double) + } else if (v0 is Boolean) { + val bv0 = v0.booleanValue() + // 1.6 only if (v1!=null) mType= (((Boolean) v0).compareTo((Boolean)v1)); + if (v1 != null) { + val bv1 = (v1 as Boolean).booleanValue() + mType = if (bv0 == bv1) 0 else if (!bv0 && bv1) -1 else +1 + } + // 1.6 only match= (((Boolean) v0).compareTo((Boolean)lookupValue)); + val bv1 = (lookupValue as Boolean).booleanValue() + match = if (bv0 == bv1) 0 else if (!bv0 && bv1) -1 else +1 + } else if (v0 is String) { + if (v1 != null) mType = v0.compareTo((v1 as String?)!!) + match = v0.compareTo(lookupValue as String) + } + if (i < values.size) { // only check order + if (matchType == 1 && mType > 0// not in ascending order + || matchType == -1 && mType < 0) + // not in descending order + // DOCUMENTATION SEZ MUST BE IN DESCENDING ORDER FOR -1 BUT EXCEL ALLOWS IT IN CERTAIN CIRCUMSTANCES + // ) + return PtgErr(PtgErr.ERROR_NA) + } + if (matchType == 0 && match == 0) { + retIndex = i // 1-based + break + } + if (matchType == 1 && match <= 0) { + retIndex = i // 1-based + } else if (matchType == -1 && match >= 0) { + retIndex = i // 1-based + } + } + /* + for (int i= 0; i < values.length; i++) { + Object val=values[i].getValue(); + int mType= -2; + if (val instanceof Integer) + mType= ((Integer)val).compareTo((Integer)lookupValue); + else if (val instanceof Double) + mType= ((Double)val).compareTo((Double)lookupValue); + else if (val instanceof Boolean) + mType= ((Boolean)val).compareTo((Boolean)lookupValue); + else if (val instanceof String) + mType= ((String)val).toLowerCase().compareTo(((String)lookupValue).toLowerCase()); + if (matchType==0) {// matches if equal + if (mType==0) {// then got it! + retIndex= i; + break; + } + } else if (matchType==-1) { + } else { // default is 1 + } + } +*/ + if (retIndex > -1) + return PtgInt(retIndex) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_NA) + } + + /** + * calcOffset + * Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells. + * The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and the number of columns to be returned. + * + * + * OFFSET(reference,rows,cols,height,width) + * + * + * Reference is the reference from which you want to base the offset. Reference must refer to a cell or range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value. + * Rows is the number of rows, up or down, that you want the upper-left cell to refer to. Using 5 as the rows argument specifies that the upper-left cell in the reference is five rows below reference. Rows can be positive (which means below the starting reference) or negative (which means above the starting reference). + * Cols is the number of columns, to the left or right, that you want the upper-left cell of the result to refer to. Using 5 as the cols argument specifies that the upper-left cell in the reference is five columns to the right of reference. Cols can be positive (which means to the right of the starting reference) or negative (which means to the left of the starting reference). + * Height is the height, in number of rows, that you want the returned reference to be. Height must be a positive number. + * Width is the width, in number of columns, that you want the returned reference to be. Width must be a positive number. + */ + internal fun calcOffset(operands: Array): Ptg { + val ref = operands[0] as? PtgRef + ?: return PtgErr(PtgErr.ERROR_VALUE) // Reference must refer to a cell or range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value + val nrows = operands[1].intVal + val ncols = operands[2].intVal + var height = -1 // Height if present, how many rows to return - must be positive + if (operands.size > 3) { + height = operands[3].intVal + if (height < 0) + return PtgErr(PtgErr.ERROR_VALUE) + } + var width = -1 // Width if present, how many columns to return - must be positive + if (operands.size > 4) { + width = operands[4].intVal + if (width < 0) + return PtgErr(PtgErr.ERROR_VALUE) + } + var rc = ref.intLocation + rc[0] += nrows + rc[1] += ncols + if (rc!!.size > 3) { // it's an area + rc[2] += nrows + rc[3] += ncols + } + // A height/width of 1, 1= a single reference + // When we increase either the row height or column width in the offset function "=OFFSET(A1,2,0,1,1)" + // to more than 1, the reference is converted to a range. + // OK, this may be the wrong interpretation of the height and width, but, from research, this is what I've come up with: + // height and width are only really applicable for an initial single reference + // in Excel, a height and width value of more than 1 returns #VALUE! unless wrapped in SUM + // ???? + if (height == 1 && width == 1) { // it's a single reference + if (rc.size > 3) { // truncate + val temp = IntArray(2) + System.arraycopy(rc, 0, temp, 0, 2) + rc = temp + } + } else if (!(height == -1 && width == -1)) { + if (rc.size < 3) { // make a range + val temp = IntArray(4) + System.arraycopy(rc, 0, temp, 0, 2) + rc = temp + } + if (height > 0) { + rc[2] = rc[0] + height - 1 // is this correct???? + } + if (width > 0) { + rc[3] = rc[1] + width - 1 // " " + } + } + if (rc.size > 3) { + // If rows and cols offset reference over the edge of the worksheet, OFFSET returns the #REF! error value. + if (rc[0] < 0 || rc[1] < 0 || rc[2] < 0 || rc[3] < 0) + return PtgErr(PtgErr.ERROR_REF) + val pa = PtgArea(false) + pa.parentRec = ref.parentRec + try { + val sh = ref.location + val z = sh.indexOf('!') + if (z > 0) { + pa.sheetName = sh.substring(0, z) + } + } catch (e: Exception) { + } + + pa.setLocation(rc) + return pa + } else { // it's a single reference + // If rows and cols offset reference over the edge of the worksheet, OFFSET returns the #REF! error value. + if (rc[0] < 0 || rc[1] < 0) + return PtgErr(PtgErr.ERROR_REF) + val pr = PtgRef() + pr.parentRec = ref.parentRec + pr.setLocation(rc) + return pr + } + } + + /** + * TRANSPOSE + * Returns the transpose of an array + * The TRANSPOSE function returns a vertical range of cells as a horizontal range, or vice versa. + * The TRANSPOSE function must be entered as an array formula has columns and rows. + * Use TRANSPOSE to shift the vertical and horizontal orientation of an array or range on a + * worksheet. + * + * + * array Required. An array or range of cells on a worksheet that you want to transpose. The transpose of an array is created by using the first row of the array as the first column of the new array, the second row of the array as the second column of the new array, and so on. + */ + internal fun calcTranspose(operands: Array): Ptg { + var retArray = "" + val ret = PtgArray() + if (operands[0] !is PtgArray) { + val arr = operands[0].components + //it's a list of values, convert to row-based + for (i in arr.indices) { + retArray = retArray + arr[i].value.toString() + ";" + } + retArray = "{" + retArray.substring(0, retArray.length - 1) + "}" + ret.setVal(retArray) + } else { // transpose row/cols of an existing array + val pa = operands[0] as PtgArray + val arr = pa.components + val nc = pa.numberOfColumns + 1 + val nr = pa.numberOfRows + 1 + for (i in 0 until nc) { + var j = 0 + while (j < nc * nr) { + retArray = retArray + arr!![i + j].value.toString() + "," + j += nc + } + retArray = retArray.substring(0, retArray.length - 1) + ";" + } + retArray = "{" + retArray.substring(0, retArray.length - 1) + "}" + ret.setVal(retArray) + } + return ret + } + + /** + * ROW + * Returns the row number of a reference + * + * + * Note this is 1 based, ie Row1 = 1. + */ + @Throws(FunctionNotSupportedException::class) + internal fun calcRow(operands: Array?): Ptg { + if (operands == null || operands.size != 1) return PtgErr(PtgErr.ERROR_VALUE) + if (operands[0] is PtgFuncVar) { + // we need to return the col where the formula is. + val pfunk = operands[0] as PtgFuncVar + try { + val loc = pfunk.parentRec!!.rowNumber + 1 + return PtgInt(loc) + } catch (e: Exception) { + Logger.logErr("Error running calcRow $e") + } + + } + try { // process as an array formula ... + val isArray = operands[0].parentRec is Array + if (!isArray) { + if (operands[0] is PtgRef) { + return PtgInt((operands[0] as PtgRef).rowCol[0] + 1) + } else if (operands[0] is PtgName) { // table??? + val range = (operands[0] as PtgName).name!!.location + return PtgInt(ExcelTools.getRowColFromString(range!!)[0] + 1) + } + return PtgInt(operands[0].intLocation[0] + 1) + } else { + var retArry = "" + var comps: Array? = null + if (operands[0] is PtgRef) { + comps = operands[0].components + } else if (operands[0] is PtgName) { // table??? + comps = operands[0].components + } + if (comps == null) { + return PtgInt((operands[0] as PtgRef).rowCol[0] + 1) + } + for (i in comps.indices) { + try { + retArry = retArry + ((comps[i] as PtgRef).intLocation!![0] + 1) + "," + } catch (e: Exception) { + } + + } + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa + } + } catch (ex: Exception) { + return PtgRefErr() + } + + } + + + /** + * ROWS + * Returns the number of rows in a reference + * + * + * ROWS(array) + * + * + * Array is an array, an array formula, or a reference to a range of cells for which you want the number of rows. + * + * + * =ROWS(C1:E4) Number of rows in the reference (4) + * =ROWS({1,2,3;4,5,6}) Number of rows in the array constant (2) + */ + @Throws(FunctionNotSupportedException::class) + internal fun calcRows(operands: Array): Ptg { + try { + var rsz = 0 + if (operands[0] is PtgStr) { + val rangestr = operands[0].value.toString() + val startx = rangestr.substring(0, rangestr.indexOf(":")) + val endx = rangestr.substring(rangestr.indexOf(":") + 1) + + val startints = ExcelTools.getRowColFromString(startx) + val endints = ExcelTools.getRowColFromString(endx) + rsz = endints[0] - startints[0] + rsz++ // inclusive + } else if (operands[0] is PtgName) { + val rc = ExcelTools.getRangeCoords(operands[0].location) + rsz = rc[2] - rc[0] + rsz++ // inclusive + } else if (operands[0] is PtgRef) { + val rc = ExcelTools.getRangeCoords((operands[0] as PtgRef).location) + rsz = rc[2] - rc[0] + rsz++ // inclusive + } else if (operands[0] is PtgMemFunc) { + val p = operands[0].components + if (p != null && p.size > 0) { + val rc0 = p[0].intLocation + var rc1: IntArray? = null + if (p.size > 1) + rc1 = p[p.size - 1].intLocation + if (rc1 == null) + rsz = 0 + else + rsz = rc1[0] - rc0[0] + rsz++ + } else + return PtgErr(PtgErr.ERROR_VALUE) + } else + return PtgErr(PtgErr.ERROR_VALUE) + return PtgInt(rsz) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + + /** + * VLOOKUP + * Looks in the first column of an array and moves across the row to return the value of a cell + * Searches for a value in the leftmost column of a table, and then returns a value in the same row + * from a column you specify in the table. Use VLOOKUP instead of HLOOKUP when your comparison + * values are located in a column to the left of the data you want to find. + * + * + * Syntax + * + * + * VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) + * + * + * Lookup_value is the value to be found in the first column of the array. Lookup_value can be a value, + * a reference, or a text string. + * + * + * Table_array is the table of information in which data is looked up. Use a reference to a range or + * a range name, such as Database or List. + * + * + * If range_lookup is TRUE, the values in the first column of table_array must be placed in ascending order: + * ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE; otherwise VLOOKUP may not give the correct value. + * If range_lookup is FALSE, table_array does not need to be sorted. + * You can put the values in ascending order by choosing the Sort command from the Data menu and selecting Ascending. + * The values in the first column of table_array can be text, numbers, or logical values. + * Uppercase and lowercase text are equivalent. + * Col_index_num is the column number in table_array from which the matching value must be returned. + * A col_index_num of 1 returns the value in the first column in table_array; a col_index_num of 2 returns the value + * in the second column in table_array, and so on. If col_index_num is less than 1, + * VLOOKUP returns the #VALUE! error value; if col_index_num is greater than the number of columns in table_array, + * VLOOKUP returns the #REF! error value. + * + * + * Range_lookup is a logical value that specifies whether you want VLOOKUP to find an exact match or an + * approximate match. If TRUE or omitted, an approximate match is returned. In other words, + * if an exact match is not found, the next largest value that is less than lookup_value is returned. + * If FALSE, VLOOKUP will find an exact match. If one is not found, the error value #N/A is returned. + * + * + * Remarks + * + * + * If VLOOKUP can't find lookup_value, and range_lookup is TRUE, it uses the largest value that is less than or + * equal to lookup_value. + * If lookup_value is smaller than the smallest value in the first column of table_array, VLOOKUP returns the + * #N/A error value. + * If VLOOKUP can't find lookup_value, and range_lookup is FALSE, VLOOKUP returns the #N/A value. + * + * + * VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) + * + * + * On the preceding worksheet, where the range A4:C12 is named Range: + * + * + * VLOOKUP(1,Range,1,TRUE) equals 0.946 + * + * + * VLOOKUP(1,Range,2) equals 2.17 + * + * + * VLOOKUP(1,Range,3,TRUE) equals 100 + * + * + * VLOOKUP(.746,Range,3,FALSE) equals 200 + * + * + * VLOOKUP(0.1,Range,2,TRUE) equals #N/A, because 0.1 is less than the smallest value in column A + * + * + * VLOOKUP(2,Range,2,TRUE) equals 1.71 + */ + @Throws(FunctionNotSupportedException::class) + internal fun calcVlookup(operands: Array): Ptg { + + var rangeLookup = true // truth of "approximate match"; must be sorted + var isNumber = true + try { + val lookup_value = operands[0] + var table_array = operands[1] + + // can't assume that it's a PtgInt + //PtgInt col_index_num = (PtgInt)operands[2].getValue(); + //int colNum = col_index_num.getVal() -1;// reduce 1 for ordinal base off firstcol + val o = operands[2].value + var colNum = 0 + if (o is Double) + colNum = o.toInt() - 1 // reduce 1 for ordinal base off firstcol + else + // assume int? + colNum = (o as Int).toInt() - 1 // reduce 1 for ordinal base off firstcol + if (operands.size > 3) { + val vx = operands[3].value + if (vx != null) { + try { + val sort = vx as Boolean + rangeLookup = sort.booleanValue() + } catch (e: ClassCastException) { + val bool = vx as Int + if (bool == 0) rangeLookup = false + } + + } + } + + var lookupComponents: Array? = null + var valueComponents: Array? = null + // first, get the lookup Column Vals + if (table_array is PtgName) { // 20090211 KSC: + val pa = PtgArea3d(false) + pa.parentRec = table_array.parentRec + + pa.location = table_array.name!!.location + table_array = pa + if (table_array.isExternalRef) { + Logger.logWarn("LookupReferenceCalculator.calcVlookup External References are disallowed") + return PtgErr(PtgErr.ERROR_REF) + } + } + if (table_array is PtgArea) { + try { + val pa = table_array + val range = table_array.intLocation + // TODO: check rc sanity here + val firstcol = range[1] + lookupComponents = pa.getColComponents(firstcol) as Array + valueComponents = pa.getColComponents(firstcol + colNum) as Array + } catch (/*20070209 KSC: FormulaNotFound*/e: Exception) { + Logger.logWarn("LookupReferenceCalculator.calcVlookup cannot determine PtgArea location. $e") + } + + } else if (table_array is PtgMemFunc) { // || table_array instanceof PtgMemArea){ + try { + val pa = table_array + // int[] range = table_array.getIntLocation(); + var firstcol = -1 + + try { + val rc1 = pa.firstloc!!.intLocation + + // TODO: check rc sanity here + firstcol = rc1[1] + } catch (e: Exception) { + Logger.logWarn("LookupReferenceCalculator.calcVlookup could not determine row col from PtgMemFunc.") + } + + lookupComponents = pa.getColComponents(firstcol) as Array + valueComponents = pa.getColComponents(firstcol + colNum) as Array + } catch (/*20070209 KSC: FormulaNotFound*/e: Exception) { + Logger.logWarn("LookupReferenceCalculator.calcVlookup cannot determine PtgArea location. $e") + } + + } + // error check + if (lookupComponents == null || lookupComponents.size == 0) + return PtgErr(PtgErr.ERROR_REF) + if (lookup_value == null || lookup_value.value == null) + // 20070221 KSC: Error trap getValue + return PtgErr(PtgErr.ERROR_NULL) + // lets check if we are dealing with strings or numbers.... + try { + val `val` = lookup_value.value.toString() + if (`val`.length == 0) + // 20090205 KSC + return PtgErr(PtgErr.ERROR_NA) + val d = Double(`val`) + } catch (e: NumberFormatException) { + isNumber = false + } + + // TODO: + //if the value you supply for the lookup_value argument is smaller than the smallest value in the first column of the table_array argument, VLOOKUP returns the #N/A error value. + + if (isNumber) { + val match_num: Double + try { + match_num = java.lang.Double.parseDouble( + lookup_value.value.toString()) + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_NA) + } + + for (i in lookupComponents.indices) { + val `val`: Double + + try { + `val` = java.lang.Double.parseDouble( + lookupComponents[i].value!!.toString()) + if (`val` == 0.0) { // VLOOKUP does NOT treat blanks as 0's + if (lookupComponents[i].refCell!![0] == null) { + continue + } + } + } catch (e: NumberFormatException) { + // Ignore entries in the table that aren't numbers. + continue + } + + if (`val` == match_num) { + return valueComponents!![i].ptgVal + } else if (rangeLookup && `val` > match_num) { + return if (i == 0) + PtgErr(PtgErr.ERROR_NA) + else + valueComponents!![i - 1].ptgVal + } + } + + return if (rangeLookup) + valueComponents!![lookupComponents.size - 1].ptgVal + else + PtgErr(PtgErr.ERROR_NA) + } else { + if (rangeLookup) { // approximate match + val match_str = lookup_value.value.toString() + val match_len = match_str.length + for (i in lookupComponents.indices) { + try { + val `val` = lookupComponents[i].value!!.toString() + if (`val`.equals(match_str, ignoreCase = true)) {// we found it + return valueComponents!![i].ptgVal + } else if (`val`.length >= match_len && `val`.substring(0, match_len).equals(match_str, ignoreCase = true)) { // matches up to length, but not all, return previous + return valueComponents!![i - 1].ptgVal + } else if (ExcelTools.getIntVal(`val`.substring(0, 1)) > ExcelTools.getIntVal(match_str.substring(0, 1))) { + return valueComponents!![i - 1].ptgVal + } else if (i == lookupComponents.size - 1) {// we reached the last one so use this + return valueComponents!![i].ptgVal + } + } catch (e: Exception) { + } + // 20070209 KSC: ignore errors in lookup cells + } + } else { // unsorted + val match_str = lookup_value.value.toString() + for (i in lookupComponents.indices) { + try { + val `val` = lookupComponents[i].value!!.toString() + try { + if (`val`.equals(match_str, ignoreCase = true)) {// we found it + return valueComponents!![i].ptgVal + } else if (i == lookupComponents.size - 1) {// we reached the last one so error out + return PtgErr(PtgErr.ERROR_NA) + } + } catch (e: Exception) { + Logger.logErr("LookupReferenceCalculator.calcVLookup error: $e") + return PtgErr(PtgErr.ERROR_NA) + } + + } catch (e: Exception) { + } + // 20070209 KSC: ignore errors in lookup cells + } + } + }// It's a String + } catch (e: Exception) { // appears that an error with operands results in a #NA error + return PtgErr(PtgErr.ERROR_NA) + + } + + return PtgErr(PtgErr.ERROR_NULL) + } + +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/MathFunctionCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/MathFunctionCalculator.java deleted file mode 100644 index 2c13080..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/MathFunctionCalculator.java +++ /dev/null @@ -1,1434 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.toolkit.Logger; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Random; - - -/* - MathFunctionCalculator is a collection of static methods that operate - as the Microsoft Excel function calls do. - - All methods are called with an array of ptg's, which are then - acted upon. A Ptg of the type that makes sense (ie boolean, number) - is returned after each method. -*/ - -public class MathFunctionCalculator { - - - /** - * SUM - * Adds all the numbers in a range of cells. - * Ignores non-number fields - *

                      - * Usage@ SUM(number1,number2, ...) - * Return@ PtgNumber - * - * @throws CalculationException - */ - protected static Ptg calcSum(Ptg[] operands) throws CalculationException { - if (operands.length < 1) return PtgCalculator.getNAError(); - double result = 0; - double[] dub = PtgCalculator.getDoubleValueArray(operands); - if (dub == null) return PtgCalculator.getNAError(); - for (int i = 0; i < dub.length; i++) { - result += dub[i]; - } - return new PtgNumber(result); - } - - /** - * ABS - * Returns the absolute value of a number - */ - protected static Ptg calcAbs(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - dd = Math.abs(dd); - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - PtgNumber ptnum = new PtgNumber(dd); - return ptnum; - - } - - /** - * ACOS - * Returns the arccosine of a number - */ - protected static Ptg calcAcos(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - dd = Math.acos(dd); - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - PtgNumber ptnum = new PtgNumber(dd); - return ptnum; - } - - /** - * ACOSH - * Returns the inverse hyperbolic cosine of a number - */ - protected static Ptg calcAcosh(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double x = 0.0; - try { - x = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - double dd = Math.log(x + (1.0 + x) * Math.sqrt((x - 1.0) / (x + 1.0))); - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - PtgNumber ptnum = new PtgNumber(dd); - return ptnum; - } - - /** - * ASIN - * Returns the arcsine of a number - */ - protected static Ptg calcAsin(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - dd = Math.asin(dd); - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - PtgNumber ptnum = new PtgNumber(dd); - return ptnum; - } - - /** - * ASINH - * Returns the inverse hyperbolic sine of a number - */ - protected static Ptg calcAsinh(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double x = 0.0; - try { - x = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(x).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - -// KSC: TESTING: I BELIEVE THE CALCULATION IS NOT CORRECT! - BigDecimal bd = new BigDecimal((x > 0.0 ? 1.0 : -1.0) * getAcosh(Math.sqrt(1.0 + (x * x)))); - bd.setScale(15, BigDecimal.ROUND_HALF_UP); -// PtgNumber ptnum = new PtgNumber(dd); - PtgNumber ptnum = new PtgNumber(bd.doubleValue()); - return ptnum; - } - - - /** - * ATAN - * Returns the arctangent of a number - */ - protected static Ptg calcAtan(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - dd = Math.atan(dd); - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - PtgNumber ptnum = new PtgNumber(dd); - return ptnum; - } - - /** - * ATAN2 - * Returns the arctangent from x- and y- coordinates - * - * @throws CalculationException - */ - protected static Ptg calcAtan2(Ptg[] operands) throws CalculationException { - if (operands.length != 2) return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA); - double res = Math.atan2(dd[0], dd[1]); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * ATANH - * Returns the inverse hyperbolic tangent of a number - */ - protected static Ptg calcAtanh(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (dd > 1 || dd < -1) return PtgCalculator.getError(); - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - double res = (0.5 * Math.log((1.0 + dd) / (1.0 - dd))); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * CEILING - * Rounds a number to the nearest multiple of significance; - * This takes 2 values, first the number to round, next the value of signifigance. - * Ick. This is pretty intensive, so maybe a better way? - * - * @throws CalculationException - */ - protected static Ptg calcCeiling(Ptg[] operands) throws CalculationException { - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getNAError(); - double num = dd[0]; - double multiple = dd[1]; - double res = 0; - while (res < num) { - res += multiple; - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * COMBIN - * Returns the number of combinations for a given number of objects - * - * @throws CalculationException - */ - protected static Ptg calcCombin(Ptg[] operands) throws CalculationException { - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getNAError(); - long num1 = Math.round(dd[0]); - long num2 = Math.round(dd[1]); - if (num1 < num2) return PtgCalculator.getError(); - long res1 = stepFactorial(num1, (int) num2); - long res2 = factorial(num2); - double res = res1 / res2; - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * COS - * Returns the cosine of a number - */ - protected static Ptg calcCos(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - double res = Math.cos(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * COSH - * Returns the hyperbolic cosine of a number - */ - protected static Ptg calcCosh(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - double res = 0.5 * (Math.exp(dd) + Math.exp(-dd)); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * DEGREES - * Converts radians to degrees - */ - protected static Ptg calcDegrees(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - double res = Math.toDegrees(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * EVEN - * Rounds a number up to the nearest even integer - */ - protected static Ptg calcEven(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - long resnum = Math.round(dd); - double remainder = dd % 2; - if (remainder != 0) { - resnum++; - } - PtgInt pint = new PtgInt((int) resnum); - return pint; - } - - /** - * EXP - * Returns e raised to the power of number. - * The constant e equals 2.71828182845904, - * the base of the natural logarithm. - *

                      - * Example - * EXP(2) equals e2, or 7.389056 - */ - protected static Ptg calcExp(Ptg[] operands) { - if (operands.length > 1 || operands[0].getComponents() != null) { // not supported by function - return new PtgErr(PtgErr.ERROR_NULL); - } - double d = 2.718281828459045; - Ptg p = operands[0]; - try { - Double dub = new Double(String.valueOf(p.getValue())); -// double result = Math.pow(d, dub.doubleValue()); - BigDecimal result = new BigDecimal(Math.pow(d, dub.doubleValue())); - result.setScale(15, BigDecimal.ROUND_HALF_UP); -// PtgNumber pnum = new PtgNumber(result); - PtgNumber pnum = new PtgNumber(result.doubleValue()); - return pnum; - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - /** - * FACT - * Returns the factorial of a number - */ - protected static Ptg calcFact(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - long dd = 0; - try { - dd = PtgCalculator.getLongValue(operands); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - long res = MathFunctionCalculator.factorial(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * FACTDOUBLE - * Returns the double factorial of a number - * Example - * !!6 = 6*4*2 - * !!7 = 7*5*3*1; - */ - protected static Ptg calcFactDouble(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - long n = PtgCalculator.getLongValue(operands); - if (new Double(n).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - if (n < 0) - return new PtgErr(PtgErr.ERROR_NUM); - double res = 1; - long endPoint = (n % 2 == 0 ? 2 : 1); - for (long i = n; i >= endPoint; i -= 2) { - res *= i; - } - if (n == 0) - res = -1; // by convention ... -// KSC: not quite right!!! replaced with above - //long res = MathFunctionCalculator.doubleFactorial(n); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * FLOOR - * Rounds a number down, toward zero. Works just like Celing with two operands - * See comment above for celing for more info. - * - * @throws CalculationException - */ - protected static Ptg calcFloor(Ptg[] operands) throws CalculationException { - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getError(); - double num = dd[0]; - double multiple = dd[1]; - double res = 0; - while (res < num) { - res += multiple; - } - // drop one from the celing code to get the floor... - res -= multiple; - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * GCD - * Returns the greatest common divisor - * TODO: Finish! - */ - protected static Ptg calcGCD(Ptg[] operands) { - if (operands.length < 1) return PtgCalculator.getNAError(); - Ptg[] numbers = PtgCalculator.getAllComponents(operands); - long gcd = 0; - try { - long n1; - if ((n1 = PtgCalculator.getLongValue(numbers[0])) < 0) - return new PtgErr(PtgErr.ERROR_NUM); - - gcd = n1; - for (int i = 1; i < numbers.length; i++) { - long n2; - if ((n2 = PtgCalculator.getLongValue(numbers[i])) < 0) - return new PtgErr(PtgErr.ERROR_NUM); - - long bigger, smaller, r; - bigger = Math.max(n2, n1); - smaller = Math.min(n2, n1); - r = bigger % smaller; - while (r != 0) { - bigger = smaller; - smaller = r; - r = bigger % smaller; - } - gcd = Math.min(gcd, smaller); - } - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - - PtgNumber pnum = new PtgNumber(gcd); - return pnum; - } - - /** - * INT - * Rounds a number down to the nearest integer - */ - protected static Ptg calcInt(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - long res = Math.round(dd); - if (res > dd) res--; - PtgInt pint = new PtgInt((int) res); - return pint; - } - - /** - * LCM - * Returns the least common multiple - */ - protected static Ptg calcLCM(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_NA); - Ptg[] numbers = PtgCalculator.getAllComponents(operands); - // algorithm: - // LCM(a, b)= (a*b)/GCD(a, b) - long lcm = 0; - try { - Ptg[] ops = new Ptg[2]; - if ((lcm = PtgCalculator.getLongValue(numbers[0])) < 0) - return new PtgErr(PtgErr.ERROR_NUM); - - for (int i = 1; i < numbers.length; i++) { - long n2; - if ((n2 = PtgCalculator.getLongValue(numbers[i])) < 0) - return new PtgErr(PtgErr.ERROR_NUM); - - ops[0] = new PtgNumber(lcm); - ops[1] = new PtgNumber(n2); - long gcd = PtgCalculator.getLongValue(calcGCD(ops)); - lcm = (lcm * n2) / gcd; - } - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - - PtgNumber pnum = new PtgNumber(lcm); - return pnum; - } - - /** - * LN - * Returns the natural logarithm of a number - */ - protected static Ptg calcLn(Ptg[] operands) { - if (operands.length > 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.log(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * LOG - * Returns the logarithm of a number to a specified base - * - * @throws CalculationException - */ - protected static Ptg calcLog(Ptg[] operands) throws CalculationException { - if (operands.length < 1) return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length > 2) return PtgCalculator.getError(); - double num1; - double num2; - if (dd.length == 1) { - num1 = dd[0]; - num2 = 10; - } else { - num1 = dd[0]; - num2 = dd[1]; - } - double res = Math.log(num1) / Math.log(num2); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * LOG10 - * Returns the base-10 logarithm of a number - */ - protected static Ptg calcLog10(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - -// double res = Math.log(dd)/Math.log(10); - - BigDecimal res = new BigDecimal(Math.log10(dd)); - res.setScale(15, BigDecimal.ROUND_HALF_UP); - PtgNumber ptnum = new PtgNumber(res.doubleValue()); - return ptnum; - } -/* -MDETERM -Returns the matrix determinant of an array - -MINVERSE -Returns the matrix inverse of an array -*/ - - /** - * MMULT - * Returns the matrix product of two arrays: - * The number of columns in array1 must be the same as the number of rows in array2, - * and both arrays must contain only numbers. - * MMULT returns the #VALUE! error when: - * Any cells are empty or contain text. - * The number of columns in array1 is different from the number of rows in array2. - */ - protected static Ptg calcMMult(Ptg[] operands) { - if (operands.length != 2) return PtgCalculator.getNAError(); - try { - // error trap params: must be numeric arrays with no empty spaces - double[][] a1 = PtgCalculator.getArray(operands[0]); - double[][] a2 = PtgCalculator.getArray(operands[1]); - if (a1[0].length != a2.length) - PtgCalculator.getValueError(); - double sum = 0.0; - for (int i = 0; i < a1[0].length; i++) { - sum += a1[0][i] * a2[i][0]; - } - return new PtgNumber(sum); - } catch (Exception e) { -// Logger.logErr("MMULT: error in operands " + e.toString()); - } - return PtgCalculator.getValueError(); - } - - /** - * MOD - * Returns the remainder from division - * - * @throws CalculationException - */ - protected static Ptg calcMod(Ptg[] operands) throws CalculationException { - if (operands.length < 1) return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getError(); - double num1 = dd[0]; - double num2 = dd[1]; - double res = num1 % num2; - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * MROUND - * Returns a number rounded to the desired multiple - */ - protected static Ptg calcMRound(Ptg[] operands) { - if (operands.length != 2) return new PtgErr(PtgErr.ERROR_NA); - double m = 0.0, n = 0.0; - try { - n = operands[0].getDoubleVal(); - m = operands[1].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if ((n < 0 && m > 0) || (n > 0 && m < 0)) return new PtgErr(PtgErr.ERROR_NUM); - double result = Math.round(n / m) * m; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - - /** - * MULTINOMIAL - * Returns the multinomial of a set of numbers - */ - protected static Ptg calcMultinomial(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_NA); - Ptg[] numbers = PtgCalculator.getAllComponents(operands); - long sum = 0; - double facts = 1; - for (int i = 0; i < numbers.length; i++) { - long n = PtgCalculator.getLongValue(operands[i]); - if (n < 1) return new PtgErr(PtgErr.ERROR_NUM); - sum += n; - facts *= factorial(n); - } - double result = factorial(sum) / facts; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * ODD - * Rounds a number up to the nearest odd integer - */ - protected static Ptg calcOdd(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - long resnum = Math.round(dd); - // always round up!! - if (resnum < dd) resnum++; - double remainder = resnum % 2; - if (remainder == 0) { - resnum++; - } - PtgInt pint = new PtgInt((int) resnum); - return pint; - } - - /** - * PI - * Returns the value of Pi - */ - protected static Ptg calcPi(Ptg[] operands) { - double pi = Math.PI; - PtgNumber pnum = new PtgNumber(pi); - return pnum; - } - - /** - * POWER - * Returns the result of a number raised to a power - * - * @throws CalculationException - */ - protected static Ptg calcPower(Ptg[] operands) throws CalculationException { - if (operands.length < 1) return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - if (dd.length != 2) return PtgCalculator.getError(); -// double num1 = dd[0]; -// double num2 = dd[1]; - BigDecimal num1 = new BigDecimal(dd[0]); - num1.setScale(15, BigDecimal.ROUND_HALF_UP); - BigDecimal num2 = new BigDecimal(dd[1]); - num2.setScale(15, BigDecimal.ROUND_HALF_UP); - BigDecimal res = new BigDecimal(Math.pow(num1.doubleValue(), num2.doubleValue())); - res.setScale(15, BigDecimal.ROUND_HALF_UP); - PtgNumber ptnum = new PtgNumber(res.doubleValue()); - return ptnum; - } - - /** - * PRODUCT - * Multiplies its arguments - * NOTE: we gotta deal with ranges/refs/numbers here - * - * @throws CalculationException - */ - protected static Ptg calcProduct(Ptg[] operands) throws CalculationException { - if (operands.length < 1) return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//propagate error - double result = dd[0]; - for (int i = 1; i < dd.length; i++) { - result = result * dd[i]; - } - PtgNumber ptnum = new PtgNumber(result); - return ptnum; - } - - - /** - * QUOTIENT - * Returns the integer portion of a division - */ - protected static Ptg calcQuotient(Ptg[] operands) { - if (operands.length != 2) return new PtgErr(PtgErr.ERROR_NA); - double numerator = 0.0, denominator = 0.0; - try { - numerator = operands[0].getDoubleVal(); - denominator = operands[1].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - long result = new Double(numerator / denominator).longValue(); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * RADIANS - * Converts degrees to radians - */ - protected static Ptg calcRadians(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - double res = Math.toRadians(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - - /** - * RAND - * Returns a random number between 0 and 1 - */ - protected static Ptg calcRand(Ptg[] operands) { - double dd = Math.random(); - PtgNumber pnum = new PtgNumber(dd); - return pnum; - } - - /** - * RANDBETWEEN - * Returns a random number between the numbers you specify - */ - protected static Ptg calcRandBetween(Ptg[] operands) { - if (operands.length != 2) return new PtgErr(PtgErr.ERROR_NA); - int lower = 0, upper = 0; - try { - lower = operands[0].getIntVal(); - upper = operands[1].getIntVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - Random r = new Random(); - double result = r.nextInt(upper - lower + 1) + lower; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - - /** - * ROMAN - * Converts an Arabic numeral to Roman, as text - * This one is a trip! - */ - protected static Ptg calcRoman(Ptg[] operands) { - int[] numbers = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; - String[] letters = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; - String roman = ""; - if (operands.length == 1) { - double dd = operands[0].getDoubleVal(); - if (new Double(dd).isNaN()) // Not a Num -- possibly PtgErr - return PtgCalculator.getError(); - - int i = (int) dd; - if (i < 0 || i > 3999) return PtgCalculator.getError(); // can't write nums that high! - for (int z = 0; z < numbers.length; z++) { - while (i >= numbers[z]) { - roman += letters[z]; - i -= numbers[z]; - } - } - } - PtgStr pstr = new PtgStr(roman); - return pstr; - } - - /** - * ROUND - * Rounds a number to a specified number of digits - * This one is kind of nasty. 3 cases of rounding. If the rounding is a positive - * integer this is the number of digits to round to. If negative, round up past the - * decimal, if 0, give an integer - * - * @throws CalculationException - */ - protected static Ptg calcRound(Ptg[] operands) throws CalculationException { - if (operands.length < 1) return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - //if (dd.length != 2) return PtgCalculator.getError(); - // we need to handle arrays sent in, just use the first and last elements. - double num = dd[0]; - double round = dd[dd.length - 1]; - double res = 0; - if (round == 0) {//return an int - res = Math.round(num); - } else if (round > 0) { //round the decimal that number of spaces - double tempnum = num * Math.pow(10, round); - tempnum = Math.round(tempnum); - res = tempnum / Math.pow(10, round); - } else { //round up the decimal that numbe of places - round = round * -1; - double tempnum = num / Math.pow(10, round); - tempnum = Math.round(tempnum); - res = tempnum * Math.pow(10, round); - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * ROUNDDOWN - * Rounds a number down, toward zero. Acts much like round above - * - * @throws CalculationException - */ - protected static Ptg calcRoundDown(Ptg[] operands) throws CalculationException { - if (operands.length < 1) return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - //if (dd.length != 2) return PtgCalculator.getError(); - double num = dd[0]; - double round = dd[dd.length - 1]; - double res = 0; - if (round == 0) {//return an int - res = Math.round(num); - if (res > num) res--; - } else if (round > 0) { //round the decimal that number of spaces - double tempnum = num * Math.pow(10, round); - double tempnum2 = Math.round(tempnum); - if (tempnum2 > tempnum) tempnum2--; - res = tempnum2 / Math.pow(10, round); - } else { //round up the decimal that numbe of places - round = round * -1; - double tempnum = num / Math.pow(10, round); - double tempnum2 = Math.round(tempnum); - if (tempnum2 > tempnum) tempnum2--; - res = tempnum2 * Math.pow(10, round); - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * ROUNDUP - * Rounds a number up, away from zero - * - * @throws CalculationException - */ - protected static Ptg calcRoundUp(Ptg[] operands) throws CalculationException { - if (operands.length < 1) return PtgCalculator.getNAError(); - double[] dd = PtgCalculator.getDoubleValueArray(operands); - if (dd == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - //if (dd.length != 2) return PtgCalculator.getError(); - double num = dd[0]; - double round = dd[dd.length - 1]; - double res = 0; - if (round == 0) {//return an int - res = Math.round(num); - if (res < num) res++; - } else if (round > 0) { //round the decimal that number of spaces - double tempnum = num * Math.pow(10, round); - double tempnum2 = Math.round(tempnum); - if (tempnum2 < tempnum) tempnum2++; - res = tempnum2 / Math.pow(10, round); - } else { //round up the decimal that numbe of places - round = round * -1; - double tempnum = num / Math.pow(10, round); - double tempnum2 = Math.round(tempnum); - if (tempnum2 < tempnum) tempnum2++; - res = tempnum2 * Math.pow(10, round); - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } -/* -SERIESSUM -Returns the sum of a power series based on the formula -TODO: requires pack to run -*/ - - /** - * SIGN - * Returns the sign of a number - * return 1 if positive, -1 if negative, or 0 if 0; - */ - protected static Ptg calcSign(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - int res = 0; - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - if (dd == 0) res = 0; - if (dd > 0) res = 1; - if (dd < 0) res = -1; - PtgInt pint = new PtgInt(res); - return pint; - } - - /** - * SIN - * Returns the sine of the given angle - */ - protected static Ptg calcSin(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.sin(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * SINH - * Returns the hyperbolic sine of a number - */ - protected static Ptg calcSinh(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = 0.5 * (Math.exp(dd) - Math.exp(-dd)); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * SQRT - * Returns a positive square root - */ - protected static Ptg calcSqrt(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.sqrt(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * SQRTPI - * Returns the square root of (number * PI) - */ - protected static Ptg calcSqrtPi(Ptg[] operands) { - if (operands.length < 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.sqrt(dd * Math.PI); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - - } -/* -SUBTOTAL -Returns a subtotal in a list or database -*/ - - /** - * SUMIF - * Adds the cells specified by a given criteria - *

                      - * You use the SUMIF function to sum the values in a range that meet criteria that you specify. - * For example, suppose that in a column that contains numbers, you want to sum only the values that are larger than 5. You can use the following formula: - *

                      - * =SUMIF(B2:B25,">5") - *

                      - * you can apply the criteria to one range and sum the corresponding values in a different range. - * For example, the formula =SUMIF(B2:B5, "John", C2:C5) sums only the values in the range C2:C5, where the corresponding cells in the range B2:B5 equal "John." - *

                      - * range Required. The range of cells that you want evaluated by criteria. Cells in each range must be numbers or names, arrays, or references that contain numbers. Blank and text values are ignored. - * criteria Required. The criteria in the form of a number, expression, a cell reference, text, or a function that defines which cells will be added. For example, criteria can be expressed as 32, ">32", B5, 32, "32", "apples", or TODAY(). - * Important Any text criteria or any criteria that includes logical or mathematical symbols must be enclosed in double quotation marks ("). If the criteria is numeric, double quotation marks are not required. - * sum_range Optional. The actual cells to add, if you want to add cells other than those specified in the range argument. If the sum_range argument is omitted, Excel adds the cells that are specified in the range argument - * (the same cells to which the criteria is applied). - *

                      - * The sum_range argument does not have to be the same size and shape as the range argument. - * The actual cells that are added are determined by using theupper leftmost cell in the sum_range argument as the beginning cell, - * and then including cells that correspond in size and shape to the range argument. - * For example:If range is And sum_range is Then the actual cells are - * A1:A5 B1:B5 B1:B5 - * A1:A5 B1:B3 B1:B5 - * A1:B4 C1:D4 C1:D4 - * A1:B4 C1:C2 C1:D4 - *

                      - * You can use the wildcard characters — the question mark (?) and asterisk (*) — as the criteria argument. - * A question mark matches any single character; an asterisk matches any sequence of characters. - * If you want to find an actual question mark or asterisk, type a tilde (~) preceding the character. - */ - protected static Ptg calcSumif(Ptg[] operands) { - try { - PtgArea range = Calculator.getRange(operands[0]); - PtgArea sum_range = null; - - try { - Ptg criteria = operands[1]; - if (operands.length > 2) { // see if has a sum_range; if not, source range is used for values as well as test - sum_range = Calculator.getRange(operands[2]); - } - // OK at this point should have criteria, range and, if necessary, sum_range - // algorithm: for each entry that meets the criterium in range, get the cell; - // if there is a sum_range, sum the values of those cells in the sum_range that correspond to the range - - // Parse the criteria string: can be a double, a comparison, a string with wildcards ... - // more info: You can use the wildcard characters — the question mark (?) and asterisk (*) — as the criteria argument. - // A question mark matches any single character; an asterisk matches any sequence of characters. - // If you want to find an actual question mark or asterisk, type a tilde (~) preceding the character. - String op = "="; // operator, default is = - String sCriteria = criteria.getString(); // criteria in string form - // strip operator, if any, and parse criteria - int j = Calculator.splitOperator(sCriteria); - if (j > 0) op = sCriteria.substring(0, j); // extract operator, if any - sCriteria = sCriteria.substring(j); - sCriteria = Calculator.translateWildcardsInCriteria(sCriteria); - - // stores the cells that pass the criteria expression and therefore will be summed up - ArrayList passesList = new ArrayList(); - - // test criteria for all cells in range, storing those cells (or sum_range cells) - // that pass in passesList - Ptg[] cells = range.getComponents(); - Ptg[] sumrangecells = null; - if (sum_range != null) - sumrangecells = sum_range.getComponents(); - for (int i = 0; i < cells.length; i++) { - boolean passes = false; - try { - Object v = cells[i].getValue(); - passes = Calculator.compareCellValue(v, sCriteria, op); - } catch (Exception e) { // don't report error - // Logger.logErr("MathFunctionCalculator.calcSumif: error parsing " + e.toString()); // debugging only; take out when fully tested - } - if (passes) { - if (sumrangecells != null) - passesList.add(sumrangecells[i]); - else - passesList.add(cells[i]); - } - } - - // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); - // Now we sum up the values of these cells and return - double ret = 0.0; - for (int i = 0; i < passesList.size(); i++) { - Ptg cell = (Ptg) passesList.get(i); - try { - ret += cell.getDoubleVal(); - } catch (Exception e) { - Logger.logErr("MathFunctionCalculator.calcSumif: error obtaining cell value: " + e.toString()); // debugging only; take out when fully tested - // keep going - } - } - return new PtgNumber(ret); - } catch (Exception e) { - Logger.logWarn("could not calculate SUMIF function: " + e.toString()); - } - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_NULL); - } - - /** - * SUMIFS - * Adds the cells in a range (range: Two or more cells on a sheet. - * The cells in a range can be adjacent or nonadjacent.) that meet multiple criteria - *

                      - * sum_range Required. One or more cells to sum, including numbers or names, ranges, or cell references (cell reference: The set of coordinates that a cell occupies on a worksheet. For example, the reference of the cell that appears at the intersection of column B and row 3 is B3.) that contain numbers. Blank and text values are ignored. - * criteria_range1 Required. The first range in which to evaluate the associated criteria. - * criteria1 Required. The criteria in the form of a number, expression, cell reference, or text that define which cells in the criteria_range1 argument will be added. For example, criteria can be expressed as 32, ">32", B4, "apples", or "32." - * criteria_range2, criteria2, … Optional. Additional ranges and their associated criteria. Up to 127 range/criteria pairs are allowed. - * - * @param operands - * @return - */ - protected static Ptg calcSumIfS(Ptg[] operands) { - try { - PtgArea sum_range = Calculator.getRange(operands[0]); - Ptg[] sumrangecells = sum_range.getComponents(); - String[] ops = new String[(operands.length - 1) / 2]; - String[] criteria = new String[(operands.length - 1) / 2]; - Ptg[][] criteria_cells = new Ptg[(operands.length - 1) / 2][]; - int j = 0; - for (int i = 1; i + 1 < operands.length; i += 2) { - //criteria range - parse and get comprising cells - PtgArea cr = Calculator.getRange(operands[i]); - criteria_cells[j] = cr.getComponents(); - // each criteria_range must contain the same number of rows and columns as the sum_range - if (criteria_cells[j].length != sumrangecells.length) - return new PtgErr(PtgErr.ERROR_VALUE); - // criteria for comparison, including operator - criteria[j] = operands[i + 1].toString(); - // strip operator, if any, and parse criteria - ops[j] = "="; // operator, default is = - int k = Calculator.splitOperator(criteria[j]); - if (k > 0) ops[j] = criteria[j].substring(0, k); // extract operator, if any - criteria[j] = criteria[j].substring(k); - criteria[j] = Calculator.translateWildcardsInCriteria(criteria[j]); - j++; - } - - // test criteria for all cells in range, storing those corresponding sum_range cells - // that pass in passesList - // stores the cells that pass the criteria expression and therefore will be summed up - ArrayList passesList = new ArrayList(); - // for each set of criteria, test all cells in range and evaluate - // NOTE: this is an implicit AND evaluation - for (int i = 0; i < sumrangecells.length; i++) { - boolean passes = true; - for (int k = 0; k < criteria.length; k++) { - try { - Object v = criteria_cells[k][i].getValue(); - passes = Calculator.compareCellValue(v, criteria[k], ops[k]) && passes; - if (!passes) - break; // no need to continue - } catch (Exception e) { // don't report error - } - } - if (passes) { - passesList.add(sumrangecells[i]); - } - } - // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); - // Now we sum up the values of these cells and return - double ret = 0.0; - for (int i = 0; i < passesList.size(); i++) { - Ptg cell = (Ptg) passesList.get(i); - try { - ret += cell.getDoubleVal(); - } catch (Exception e) { - Logger.logErr("MathFunctionCalculator.calcSumif: error obtaining cell value: " + e.toString()); // debugging only; take out when fully tested - // keep going - } - } - - return new PtgNumber(ret); - - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_NULL); - } - - /** - * SUMPRODUCT - * Returns the sum of the products of corresponding array components - */ - protected static Ptg calcSumproduct(Ptg[] operands) { - double res = 0; - int dim = 0; // all arrays must have same dimension see below - ArrayList arrays = new ArrayList(); - for (int i = 0; i < operands.length; i++) { - if (operands[i] instanceof PtgErr) return new PtgErr(PtgErr.ERROR_NA); // it's what excel does - Ptg[] a = operands[i].getComponents(); - if (a == null) { - arrays.add(operands[i]); - if (dim == 0) { - dim = 1; - continue; - } else { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - if (dim == 0) - dim = a.length; - else if (dim != a.length) - return new PtgErr(PtgErr.ERROR_VALUE); - arrays.add(a); - } - for (int j = 0; j < dim; j++) { - double d = 1; - for (int i = 0; i < arrays.size(); i++) { - Object o = ((Ptg[]) arrays.get(i))[j].getValue(); - if (o instanceof Double) - d = d * ((Double) o).doubleValue(); - else if (o instanceof Integer) - d = d * ((Integer) o).intValue(); - else if (o instanceof Float) - d = d * ((Float) o).floatValue(); - else - d = 0; // non-numeric values are treated as 0's - } - res += d; - } - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - -/* -SUMSQ -Returns the sum of the squares of the arguments - -SUMX2MY2 -Returns the sum of the difference of squares of corresponding values in two arrays - -SUMX2PY2 -Returns the sum of the sum of squares of corresponding values in two arrays - -SUMXMY2 -Returns the sum of squares of differences of corresponding values in two arrays -*/ - - /** - * TAN - * Returns the tangent of a number - */ - protected static Ptg calcTan(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - double res = Math.tan(dd); - PtgNumber ptnum = new PtgNumber(res); - return ptnum; - } - - /** - * TANH - * Returns the hyperbolic tangent of a number - */ - protected static Ptg calcTanh(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double x = 0.0; - try { - x = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - double A = Math.pow(Math.E, x); - double B = Math.pow(Math.E, -x); - double result = (A - B) / (A + B); - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * TRUNC - * Truncates a number to an integer - */ - protected static Ptg calcTrunc(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getNAError(); - double dd = 0.0; - try { - dd = operands[0].getDoubleVal(); - } catch (NumberFormatException e) { - return PtgCalculator.getValueError(); - } - if (new Double(dd).isNaN()) return PtgCalculator.getError(); // Not a Num -- possibly PtgErr - int res = (int) dd; - PtgInt pint = new PtgInt(res); - return pint; - } - - /* - * - * These are some helper methods for the more brutal of the math functions - */ -//helper for asinh - private static double getAcosh(double x) { - return Math.log(x + (1.0 + x) * Math.sqrt((x - 1.0) / (x + 1.0))); - } - - // factorial helper - public static long factorial(long n) { - long result; - if (n <= 1) { - result = 1; // 1! is 1 - } - // The recursive part - else { - result = n; - long partial = factorial(n - 1); - result = result * partial; - } - return result; - } - - /* - * Step factoral calculates a factorial of a number - * the number of steps specified - * - */ -//Combin helper, steps factorials - private static long stepFactorial(long n, int numsteps) { - long result = n; - if (n < numsteps) return -1; - while (numsteps > 1) { - long partial = n - 1; - result = partial * result; - n--; - numsteps--; - - } - return result; - } - - // double factorial helper - private static long doubleFactorial(long n) { - long result; - if (n <= 1) { - result = 1; // 1! is 1 - } else if (n == 2) { - result = 2; - } - // The recursive part - else { - result = n; - long partial = factorial(n - 2); - result = result * partial; - } - return result; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/formulas/MathFunctionCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/MathFunctionCalculator.kt new file mode 100644 index 0000000..3513930 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/MathFunctionCalculator.kt @@ -0,0 +1,1454 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.toolkit.Logger + +import java.math.BigDecimal +import java.util.ArrayList +import java.util.Random + + +/* + MathFunctionCalculator is a collection of static methods that operate + as the Microsoft Excel function calls do. + + All methods are called with an array of ptg's, which are then + acted upon. A Ptg of the type that makes sense (ie boolean, number) + is returned after each method. +*/ + +object MathFunctionCalculator { + + + /** + * SUM + * Adds all the numbers in a range of cells. + * Ignores non-number fields + * + * + * Usage@ SUM(number1,number2, ...) + * Return@ PtgNumber + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcSum(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + var result = 0.0 + val dub = PtgCalculator.getDoubleValueArray(operands) ?: return PtgCalculator.naError + for (i in dub.indices) { + result += dub[i] + } + return PtgNumber(result) + } + + /** + * ABS + * Returns the absolute value of a number + */ + internal fun calcAbs(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + dd = Math.abs(dd) + return if (dd.isNaN()) PtgCalculator.error else PtgNumber(dd) + + } + + /** + * ACOS + * Returns the arccosine of a number + */ + internal fun calcAcos(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + dd = Math.acos(dd) + return if (dd.isNaN()) PtgCalculator.error else PtgNumber(dd) + } + + /** + * ACOSH + * Returns the inverse hyperbolic cosine of a number + */ + internal fun calcAcosh(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var x = 0.0 + try { + x = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + val dd = Math.log(x + (1.0 + x) * Math.sqrt((x - 1.0) / (x + 1.0))) + return if (dd.isNaN()) PtgCalculator.error else PtgNumber(dd) + } + + /** + * ASIN + * Returns the arcsine of a number + */ + internal fun calcAsin(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + dd = Math.asin(dd) + return if (dd.isNaN()) PtgCalculator.error else PtgNumber(dd) + } + + /** + * ASINH + * Returns the inverse hyperbolic sine of a number + */ + internal fun calcAsinh(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var x = 0.0 + try { + x = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (x.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + // KSC: TESTING: I BELIEVE THE CALCULATION IS NOT CORRECT! + val bd = BigDecimal((if (x > 0.0) 1.0 else -1.0) * getAcosh(Math.sqrt(1.0 + x * x))) + bd.setScale(15, BigDecimal.ROUND_HALF_UP) + // PtgNumber ptnum = new PtgNumber(dd); + return PtgNumber(bd.toDouble()) + } + + + /** + * ATAN + * Returns the arctangent of a number + */ + internal fun calcAtan(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + dd = Math.atan(dd) + return if (dd.isNaN()) PtgCalculator.error else PtgNumber(dd) + } + + /** + * ATAN2 + * Returns the arctangent from x- and y- coordinates + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcAtan2(operands: Array): Ptg { + if (operands.size != 2) return PtgCalculator.naError + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) + val res = Math.atan2(dd[0], dd[1]) + return PtgNumber(res) + } + + /** + * ATANH + * Returns the inverse hyperbolic tangent of a number + */ + internal fun calcAtanh(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd > 1 || dd < -1) return PtgCalculator.error + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + val res = 0.5 * Math.log((1.0 + dd) / (1.0 - dd)) + return PtgNumber(res) + } + + /** + * CEILING + * Rounds a number to the nearest multiple of significance; + * This takes 2 values, first the number to round, next the value of signifigance. + * Ick. This is pretty intensive, so maybe a better way? + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcCeiling(operands: Array): Ptg { + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + if (dd.size != 2) return PtgCalculator.naError + val num = dd[0] + val multiple = dd[1] + var res = 0.0 + while (res < num) { + res += multiple + } + return PtgNumber(res) + } + + /** + * COMBIN + * Returns the number of combinations for a given number of objects + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcCombin(operands: Array): Ptg { + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + if (dd.size != 2) return PtgCalculator.naError + val num1 = Math.round(dd[0]) + val num2 = Math.round(dd[1]) + if (num1 < num2) return PtgCalculator.error + val res1 = stepFactorial(num1, num2.toInt()) + val res2 = factorial(num2) + val res = (res1 / res2).toDouble() + return PtgNumber(res) + } + + /** + * COS + * Returns the cosine of a number + */ + internal fun calcCos(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + val res = Math.cos(dd) + return PtgNumber(res) + } + + /** + * COSH + * Returns the hyperbolic cosine of a number + */ + internal fun calcCosh(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + val res = 0.5 * (Math.exp(dd) + Math.exp(-dd)) + return PtgNumber(res) + } + + /** + * DEGREES + * Converts radians to degrees + */ + internal fun calcDegrees(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + val res = Math.toDegrees(dd) + return PtgNumber(res) + } + + /** + * EVEN + * Rounds a number up to the nearest even integer + */ + internal fun calcEven(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + var resnum = Math.round(dd) + val remainder = dd % 2 + if (remainder != 0.0) { + resnum++ + } + return PtgInt(resnum.toInt()) + } + + /** + * EXP + * Returns e raised to the power of number. + * The constant e equals 2.71828182845904, + * the base of the natural logarithm. + * + * + * Example + * EXP(2) equals e2, or 7.389056 + */ + internal fun calcExp(operands: Array): Ptg { + if (operands.size > 1 || operands[0].components != null) { // not supported by function + return PtgErr(PtgErr.ERROR_NULL) + } + val d = 2.718281828459045 + val p = operands[0] + try { + val dub = Double(p.value.toString()) + // double result = Math.pow(d, dub.doubleValue()); + val result = BigDecimal(Math.pow(d, dub)) + result.setScale(15, BigDecimal.ROUND_HALF_UP) + // PtgNumber pnum = new PtgNumber(result); + return PtgNumber(result.toDouble()) + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + /** + * FACT + * Returns the factorial of a number + */ + internal fun calcFact(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd: Long = 0 + try { + dd = PtgCalculator.getLongValue(operands) + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + val res = MathFunctionCalculator.factorial(dd) + return PtgNumber(res.toDouble()) + } + + /** + * FACTDOUBLE + * Returns the double factorial of a number + * Example + * !!6 = 6*4*2 + * !!7 = 7*5*3*1; + */ + internal fun calcFactDouble(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + val n = PtgCalculator.getLongValue(operands) + if (n.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + if (n < 0) + return PtgErr(PtgErr.ERROR_NUM) + var res = 1.0 + val endPoint = (if (n % 2 == 0L) 2 else 1).toLong() + var i = n + while (i >= endPoint) { + res *= i.toDouble() + i -= 2 + } + if (n == 0L) + res = -1.0 // by convention ... + // KSC: not quite right!!! replaced with above + //long res = MathFunctionCalculator.doubleFactorial(n); + return PtgNumber(res) + } + + /** + * FLOOR + * Rounds a number down, toward zero. Works just like Celing with two operands + * See comment above for celing for more info. + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcFloor(operands: Array): Ptg { + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + if (dd.size != 2) return PtgCalculator.error + val num = dd[0] + val multiple = dd[1] + var res = 0.0 + while (res < num) { + res += multiple + } + // drop one from the celing code to get the floor... + res -= multiple + return PtgNumber(res) + } + + /** + * GCD + * Returns the greatest common divisor + * TODO: Finish! + */ + internal fun calcGCD(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + val numbers = PtgCalculator.getAllComponents(operands) + var gcd: Long = 0 + try { + val n1: Long + if ((n1 = PtgCalculator.getLongValue(numbers[0])) < 0) + return PtgErr(PtgErr.ERROR_NUM) + + gcd = n1 + for (i in 1 until numbers.size) { + val n2: Long + if ((n2 = PtgCalculator.getLongValue(numbers[i])) < 0) + return PtgErr(PtgErr.ERROR_NUM) + + var bigger: Long + var smaller: Long + var r: Long + bigger = Math.max(n2, n1) + smaller = Math.min(n2, n1) + r = bigger % smaller + while (r != 0L) { + bigger = smaller + smaller = r + r = bigger % smaller + } + gcd = Math.min(gcd, smaller) + } + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + return PtgNumber(gcd.toDouble()) + } + + /** + * INT + * Rounds a number down to the nearest integer + */ + internal fun calcInt(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + var res = Math.round(dd) + if (res > dd) res-- + return PtgInt(res.toInt()) + } + + /** + * LCM + * Returns the least common multiple + */ + internal fun calcLCM(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_NA) + val numbers = PtgCalculator.getAllComponents(operands) + // algorithm: + // LCM(a, b)= (a*b)/GCD(a, b) + var lcm: Long = 0 + try { + val ops = arrayOfNulls(2) + if ((lcm = PtgCalculator.getLongValue(numbers[0])) < 0) + return PtgErr(PtgErr.ERROR_NUM) + + for (i in 1 until numbers.size) { + val n2: Long + if ((n2 = PtgCalculator.getLongValue(numbers[i])) < 0) + return PtgErr(PtgErr.ERROR_NUM) + + ops[0] = PtgNumber(lcm.toDouble()) + ops[1] = PtgNumber(n2.toDouble()) + val gcd = PtgCalculator.getLongValue(calcGCD(ops)) + lcm = lcm * n2 / gcd + } + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + return PtgNumber(lcm.toDouble()) + } + + /** + * LN + * Returns the natural logarithm of a number + */ + internal fun calcLn(operands: Array): Ptg { + if (operands.size > 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) return PtgCalculator.error // Not a Num -- possibly PtgErr + val res = Math.log(dd) + return PtgNumber(res) + } + + /** + * LOG + * Returns the logarithm of a number to a specified base + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcLog(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + if (dd.size > 2) return PtgCalculator.error + val num1: Double + val num2: Double + if (dd.size == 1) { + num1 = dd[0] + num2 = 10.0 + } else { + num1 = dd[0] + num2 = dd[1] + } + val res = Math.log(num1) / Math.log(num2) + return PtgNumber(res) + } + + /** + * LOG10 + * Returns the base-10 logarithm of a number + */ + internal fun calcLog10(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + // double res = Math.log(dd)/Math.log(10); + + val res = BigDecimal(Math.log10(dd)) + res.setScale(15, BigDecimal.ROUND_HALF_UP) + return PtgNumber(res.toDouble()) + } + /* +MDETERM +Returns the matrix determinant of an array + +MINVERSE +Returns the matrix inverse of an array +*/ + + /** + * MMULT + * Returns the matrix product of two arrays: + * The number of columns in array1 must be the same as the number of rows in array2, + * and both arrays must contain only numbers. + * MMULT returns the #VALUE! error when: + * Any cells are empty or contain text. + * The number of columns in array1 is different from the number of rows in array2. + */ + internal fun calcMMult(operands: Array): Ptg { + if (operands.size != 2) return PtgCalculator.naError + try { + // error trap params: must be numeric arrays with no empty spaces + val a1 = PtgCalculator.getArray(operands[0]) + val a2 = PtgCalculator.getArray(operands[1]) + if (a1[0].size != a2.size) + PtgCalculator.valueError + var sum = 0.0 + for (i in 0 until a1[0].size) { + sum += a1[0][i] * a2[i][0] + } + return PtgNumber(sum) + } catch (e: Exception) { + // Logger.logErr("MMULT: error in operands " + e.toString()); + } + + return PtgCalculator.valueError + } + + /** + * MOD + * Returns the remainder from division + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcMod(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + if (dd.size != 2) return PtgCalculator.error + val num1 = dd[0] + val num2 = dd[1] + val res = num1 % num2 + return PtgNumber(res) + } + + /** + * MROUND + * Returns a number rounded to the desired multiple + */ + internal fun calcMRound(operands: Array): Ptg { + if (operands.size != 2) return PtgErr(PtgErr.ERROR_NA) + var m = 0.0 + var n = 0.0 + try { + n = operands[0].doubleVal + m = operands[1].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (n < 0 && m > 0 || n > 0 && m < 0) return PtgErr(PtgErr.ERROR_NUM) + val result = Math.round(n / m) * m + return PtgNumber(result) + } + + + /** + * MULTINOMIAL + * Returns the multinomial of a set of numbers + */ + internal fun calcMultinomial(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_NA) + val numbers = PtgCalculator.getAllComponents(operands) + var sum: Long = 0 + var facts = 1.0 + for (i in numbers.indices) { + val n = PtgCalculator.getLongValue(operands[i]) + if (n < 1) return PtgErr(PtgErr.ERROR_NUM) + sum += n + facts *= factorial(n).toDouble() + } + val result = factorial(sum) / facts + return PtgNumber(result) + } + + /** + * ODD + * Rounds a number up to the nearest odd integer + */ + internal fun calcOdd(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + var resnum = Math.round(dd) + // always round up!! + if (resnum < dd) resnum++ + val remainder = (resnum % 2).toDouble() + if (remainder == 0.0) { + resnum++ + } + return PtgInt(resnum.toInt()) + } + + /** + * PI + * Returns the value of Pi + */ + internal fun calcPi(operands: Array): Ptg { + val pi = Math.PI + return PtgNumber(pi) + } + + /** + * POWER + * Returns the result of a number raised to a power + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcPower(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + if (dd.size != 2) return PtgCalculator.error + // double num1 = dd[0]; + // double num2 = dd[1]; + val num1 = BigDecimal(dd[0]) + num1.setScale(15, BigDecimal.ROUND_HALF_UP) + val num2 = BigDecimal(dd[1]) + num2.setScale(15, BigDecimal.ROUND_HALF_UP) + val res = BigDecimal(Math.pow(num1.toDouble(), num2.toDouble())) + res.setScale(15, BigDecimal.ROUND_HALF_UP) + return PtgNumber(res.toDouble()) + } + + /** + * PRODUCT + * Multiplies its arguments + * NOTE: we gotta deal with ranges/refs/numbers here + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcProduct(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//propagate error + var result = dd[0] + for (i in 1 until dd.size) { + result = result * dd[i] + } + return PtgNumber(result) + } + + + /** + * QUOTIENT + * Returns the integer portion of a division + */ + internal fun calcQuotient(operands: Array): Ptg { + if (operands.size != 2) return PtgErr(PtgErr.ERROR_NA) + var numerator = 0.0 + var denominator = 0.0 + try { + numerator = operands[0].doubleVal + denominator = operands[1].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + val result = (numerator / denominator).toLong() + return PtgNumber(result.toDouble()) + } + + /** + * RADIANS + * Converts degrees to radians + */ + internal fun calcRadians(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + val res = Math.toRadians(dd) + return PtgNumber(res) + } + + + /** + * RAND + * Returns a random number between 0 and 1 + */ + internal fun calcRand(operands: Array): Ptg { + val dd = Math.random() + return PtgNumber(dd) + } + + /** + * RANDBETWEEN + * Returns a random number between the numbers you specify + */ + internal fun calcRandBetween(operands: Array): Ptg { + if (operands.size != 2) return PtgErr(PtgErr.ERROR_NA) + var lower = 0 + var upper = 0 + try { + lower = operands[0].intVal + upper = operands[1].intVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + val r = Random() + val result = (r.nextInt(upper - lower + 1) + lower).toDouble() + return PtgNumber(result) + } + + + /** + * ROMAN + * Converts an Arabic numeral to Roman, as text + * This one is a trip! + */ + internal fun calcRoman(operands: Array): Ptg { + val numbers = intArrayOf(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) + val letters = arrayOf("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") + var roman = "" + if (operands.size == 1) { + val dd = operands[0].doubleVal + if (dd.isNaN()) + // Not a Num -- possibly PtgErr + return PtgCalculator.error + + var i = dd.toInt() + if (i < 0 || i > 3999) return PtgCalculator.error // can't write nums that high! + for (z in numbers.indices) { + while (i >= numbers[z]) { + roman += letters[z] + i -= numbers[z] + } + } + } + return PtgStr(roman) + } + + /** + * ROUND + * Rounds a number to a specified number of digits + * This one is kind of nasty. 3 cases of rounding. If the rounding is a positive + * integer this is the number of digits to round to. If negative, round up past the + * decimal, if 0, give an integer + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcRound(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + //if (dd.length != 2) return PtgCalculator.getError(); + // we need to handle arrays sent in, just use the first and last elements. + val num = dd[0] + var round = dd[dd.size - 1] + var res = 0.0 + if (round == 0.0) {//return an int + res = Math.round(num).toDouble() + } else if (round > 0) { //round the decimal that number of spaces + var tempnum = num * Math.pow(10.0, round) + tempnum = Math.round(tempnum).toDouble() + res = tempnum / Math.pow(10.0, round) + } else { //round up the decimal that numbe of places + round = round * -1 + var tempnum = num / Math.pow(10.0, round) + tempnum = Math.round(tempnum).toDouble() + res = tempnum * Math.pow(10.0, round) + } + return PtgNumber(res) + } + + /** + * ROUNDDOWN + * Rounds a number down, toward zero. Acts much like round above + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcRoundDown(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + //if (dd.length != 2) return PtgCalculator.getError(); + val num = dd[0] + var round = dd[dd.size - 1] + var res = 0.0 + if (round == 0.0) {//return an int + res = Math.round(num).toDouble() + if (res > num) res-- + } else if (round > 0) { //round the decimal that number of spaces + val tempnum = num * Math.pow(10.0, round) + var tempnum2 = Math.round(tempnum).toDouble() + if (tempnum2 > tempnum) tempnum2-- + res = tempnum2 / Math.pow(10.0, round) + } else { //round up the decimal that numbe of places + round = round * -1 + val tempnum = num / Math.pow(10.0, round) + var tempnum2 = Math.round(tempnum).toDouble() + if (tempnum2 > tempnum) tempnum2-- + res = tempnum2 * Math.pow(10.0, round) + } + return PtgNumber(res) + } + + /** + * ROUNDUP + * Rounds a number up, away from zero + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + internal fun calcRoundUp(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + val dd = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + //if (dd.length != 2) return PtgCalculator.getError(); + val num = dd[0] + var round = dd[dd.size - 1] + var res = 0.0 + if (round == 0.0) {//return an int + res = Math.round(num).toDouble() + if (res < num) res++ + } else if (round > 0) { //round the decimal that number of spaces + val tempnum = num * Math.pow(10.0, round) + var tempnum2 = Math.round(tempnum).toDouble() + if (tempnum2 < tempnum) tempnum2++ + res = tempnum2 / Math.pow(10.0, round) + } else { //round up the decimal that numbe of places + round = round * -1 + val tempnum = num / Math.pow(10.0, round) + var tempnum2 = Math.round(tempnum).toDouble() + if (tempnum2 < tempnum) tempnum2++ + res = tempnum2 * Math.pow(10.0, round) + } + return PtgNumber(res) + } + /* +SERIESSUM +Returns the sum of a power series based on the formula +TODO: requires pack to run +*/ + + /** + * SIGN + * Returns the sign of a number + * return 1 if positive, -1 if negative, or 0 if 0; + */ + internal fun calcSign(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + var res = 0 + if (dd.isNaN()) return PtgCalculator.error // Not a Num -- possibly PtgErr + if (dd == 0.0) res = 0 + if (dd > 0) res = 1 + if (dd < 0) res = -1 + return PtgInt(res) + } + + /** + * SIN + * Returns the sine of the given angle + */ + internal fun calcSin(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) return PtgCalculator.error // Not a Num -- possibly PtgErr + val res = Math.sin(dd) + return PtgNumber(res) + } + + /** + * SINH + * Returns the hyperbolic sine of a number + */ + internal fun calcSinh(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) return PtgCalculator.error // Not a Num -- possibly PtgErr + val res = 0.5 * (Math.exp(dd) - Math.exp(-dd)) + return PtgNumber(res) + } + + /** + * SQRT + * Returns a positive square root + */ + internal fun calcSqrt(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) return PtgCalculator.error // Not a Num -- possibly PtgErr + val res = Math.sqrt(dd) + return PtgNumber(res) + } + + /** + * SQRTPI + * Returns the square root of (number * PI) + */ + internal fun calcSqrtPi(operands: Array): Ptg { + if (operands.size < 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) return PtgCalculator.error // Not a Num -- possibly PtgErr + val res = Math.sqrt(dd * Math.PI) + return PtgNumber(res) + + } + /* +SUBTOTAL +Returns a subtotal in a list or database +*/ + + /** + * SUMIF + * Adds the cells specified by a given criteria + * + * + * You use the SUMIF function to sum the values in a range that meet criteria that you specify. + * For example, suppose that in a column that contains numbers, you want to sum only the values that are larger than 5. You can use the following formula: + * + * + * =SUMIF(B2:B25,">5") + * + * + * you can apply the criteria to one range and sum the corresponding values in a different range. + * For example, the formula =SUMIF(B2:B5, "John", C2:C5) sums only the values in the range C2:C5, where the corresponding cells in the range B2:B5 equal "John." + * + * + * range Required. The range of cells that you want evaluated by criteria. Cells in each range must be numbers or names, arrays, or references that contain numbers. Blank and text values are ignored. + * criteria Required. The criteria in the form of a number, expression, a cell reference, text, or a function that defines which cells will be added. For example, criteria can be expressed as 32, ">32", B5, 32, "32", "apples", or TODAY(). + * Important Any text criteria or any criteria that includes logical or mathematical symbols must be enclosed in double quotation marks ("). If the criteria is numeric, double quotation marks are not required. + * sum_range Optional. The actual cells to add, if you want to add cells other than those specified in the range argument. If the sum_range argument is omitted, Excel adds the cells that are specified in the range argument + * (the same cells to which the criteria is applied). + * + * + * The sum_range argument does not have to be the same size and shape as the range argument. + * The actual cells that are added are determined by using theupper leftmost cell in the sum_range argument as the beginning cell, + * and then including cells that correspond in size and shape to the range argument. + * For example:If range is And sum_range is Then the actual cells are + * A1:A5 B1:B5 B1:B5 + * A1:A5 B1:B3 B1:B5 + * A1:B4 C1:D4 C1:D4 + * A1:B4 C1:C2 C1:D4 + * + * + * You can use the wildcard characters — the question mark (?) and asterisk (*) — as the criteria argument. + * A question mark matches any single character; an asterisk matches any sequence of characters. + * If you want to find an actual question mark or asterisk, type a tilde (~) preceding the character. + */ + internal fun calcSumif(operands: Array): Ptg { + try { + val range = Calculator.getRange(operands[0]) + var sum_range: PtgArea? = null + + try { + val criteria = operands[1] + if (operands.size > 2) { // see if has a sum_range; if not, source range is used for values as well as test + sum_range = Calculator.getRange(operands[2]) + } + // OK at this point should have criteria, range and, if necessary, sum_range + // algorithm: for each entry that meets the criterium in range, get the cell; + // if there is a sum_range, sum the values of those cells in the sum_range that correspond to the range + + // Parse the criteria string: can be a double, a comparison, a string with wildcards ... + // more info: You can use the wildcard characters — the question mark (?) and asterisk (*) — as the criteria argument. + // A question mark matches any single character; an asterisk matches any sequence of characters. + // If you want to find an actual question mark or asterisk, type a tilde (~) preceding the character. + var op = "=" // operator, default is = + var sCriteria = criteria.string // criteria in string form + // strip operator, if any, and parse criteria + val j = Calculator.splitOperator(sCriteria) + if (j > 0) op = sCriteria.substring(0, j) // extract operator, if any + sCriteria = sCriteria.substring(j) + sCriteria = Calculator.translateWildcardsInCriteria(sCriteria) + + // stores the cells that pass the criteria expression and therefore will be summed up + val passesList = ArrayList() + + // test criteria for all cells in range, storing those cells (or sum_range cells) + // that pass in passesList + val cells = range!!.components + var sumrangecells: Array? = null + if (sum_range != null) + sumrangecells = sum_range.components + for (i in cells!!.indices) { + var passes = false + try { + val v = cells[i].value + passes = Calculator.compareCellValue(v, sCriteria, op) + } catch (e: Exception) { // don't report error + // Logger.logErr("MathFunctionCalculator.calcSumif: error parsing " + e.toString()); // debugging only; take out when fully tested + } + + if (passes) { + if (sumrangecells != null) + passesList.add(sumrangecells[i]) + else + passesList.add(cells[i]) + } + } + + // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); + // Now we sum up the values of these cells and return + var ret = 0.0 + for (i in passesList.indices) { + val cell = passesList.get(i) as Ptg + try { + ret += cell.doubleVal + } catch (e: Exception) { + Logger.logErr("MathFunctionCalculator.calcSumif: error obtaining cell value: $e") // debugging only; take out when fully tested + // keep going + } + + } + return PtgNumber(ret) + } catch (e: Exception) { + Logger.logWarn("could not calculate SUMIF function: $e") + } + + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_NULL) + } + + /** + * SUMIFS + * Adds the cells in a range (range: Two or more cells on a sheet. + * The cells in a range can be adjacent or nonadjacent.) that meet multiple criteria + * + * + * sum_range Required. One or more cells to sum, including numbers or names, ranges, or cell references (cell reference: The set of coordinates that a cell occupies on a worksheet. For example, the reference of the cell that appears at the intersection of column B and row 3 is B3.) that contain numbers. Blank and text values are ignored. + * criteria_range1 Required. The first range in which to evaluate the associated criteria. + * criteria1 Required. The criteria in the form of a number, expression, cell reference, or text that define which cells in the criteria_range1 argument will be added. For example, criteria can be expressed as 32, ">32", B4, "apples", or "32." + * criteria_range2, criteria2, … Optional. Additional ranges and their associated criteria. Up to 127 range/criteria pairs are allowed. + * + * @param operands + * @return + */ + internal fun calcSumIfS(operands: Array): Ptg { + try { + val sum_range = Calculator.getRange(operands[0]) + val sumrangecells = sum_range!!.components + val ops = arrayOfNulls((operands.size - 1) / 2) + val criteria = arrayOfNulls((operands.size - 1) / 2) + val criteria_cells = arrayOfNulls>((operands.size - 1) / 2) + var j = 0 + run { + var i = 1 + while (i + 1 < operands.size) { + //criteria range - parse and get comprising cells + val cr = Calculator.getRange(operands[i]) + criteria_cells[j] = cr!!.components + // each criteria_range must contain the same number of rows and columns as the sum_range + if (criteria_cells[j].size != sumrangecells!!.size) + return PtgErr(PtgErr.ERROR_VALUE) + // criteria for comparison, including operator + criteria[j] = operands[i + 1].toString() + // strip operator, if any, and parse criteria + ops[j] = "=" // operator, default is = + val k = Calculator.splitOperator(criteria[j]) + if (k > 0) ops[j] = criteria[j].substring(0, k) // extract operator, if any + criteria[j] = criteria[j].substring(k) + criteria[j] = Calculator.translateWildcardsInCriteria(criteria[j]) + j++ + i += 2 + } + } + + // test criteria for all cells in range, storing those corresponding sum_range cells + // that pass in passesList + // stores the cells that pass the criteria expression and therefore will be summed up + val passesList = ArrayList() + // for each set of criteria, test all cells in range and evaluate + // NOTE: this is an implicit AND evaluation + for (i in sumrangecells!!.indices) { + var passes = true + for (k in criteria.indices) { + try { + val v = criteria_cells[k][i].value + passes = Calculator.compareCellValue(v, criteria[k], ops[k]) && passes + if (!passes) + break // no need to continue + } catch (e: Exception) { // don't report error + } + + } + if (passes) { + passesList.add(sumrangecells[i]) + } + } + // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); + // Now we sum up the values of these cells and return + var ret = 0.0 + for (i in passesList.indices) { + val cell = passesList.get(i) as Ptg + try { + ret += cell.doubleVal + } catch (e: Exception) { + Logger.logErr("MathFunctionCalculator.calcSumif: error obtaining cell value: $e") // debugging only; take out when fully tested + // keep going + } + + } + + return PtgNumber(ret) + + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_NULL) + } + + /** + * SUMPRODUCT + * Returns the sum of the products of corresponding array components + */ + internal fun calcSumproduct(operands: Array): Ptg { + var res = 0.0 + var dim = 0 // all arrays must have same dimension see below + val arrays = ArrayList() + for (i in operands.indices) { + if (operands[i] is PtgErr) return PtgErr(PtgErr.ERROR_NA) // it's what excel does + val a = operands[i].components + if (a == null) { + arrays.add(operands[i]) + if (dim == 0) { + dim = 1 + continue + } else { + return PtgErr(PtgErr.ERROR_VALUE) + } + } + if (dim == 0) + dim = a.size + else if (dim != a.size) + return PtgErr(PtgErr.ERROR_VALUE) + arrays.add(a) + } + for (j in 0 until dim) { + var d = 1.0 + for (i in arrays.indices) { + val o = arrays.get(i)[j].value + if (o is Double) + d = d * o.toDouble() + else if (o is Int) + d = d * o.toInt() + else if (o is Float) + d = d * o.toFloat() + else + d = 0.0 // non-numeric values are treated as 0's + } + res += d + } + return PtgNumber(res) + } + + /* +SUMSQ +Returns the sum of the squares of the arguments + +SUMX2MY2 +Returns the sum of the difference of squares of corresponding values in two arrays + +SUMX2PY2 +Returns the sum of the sum of squares of corresponding values in two arrays + +SUMXMY2 +Returns the sum of squares of differences of corresponding values in two arrays +*/ + + /** + * TAN + * Returns the tangent of a number + */ + internal fun calcTan(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) return PtgCalculator.error // Not a Num -- possibly PtgErr + val res = Math.tan(dd) + return PtgNumber(res) + } + + /** + * TANH + * Returns the hyperbolic tangent of a number + */ + internal fun calcTanh(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var x = 0.0 + try { + x = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + val A = Math.pow(Math.E, x) + val B = Math.pow(Math.E, -x) + val result = (A - B) / (A + B) + return PtgNumber(result) + } + + /** + * TRUNC + * Truncates a number to an integer + */ + internal fun calcTrunc(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.naError + var dd = 0.0 + try { + dd = operands[0].doubleVal + } catch (e: NumberFormatException) { + return PtgCalculator.valueError + } + + if (dd.isNaN()) return PtgCalculator.error // Not a Num -- possibly PtgErr + val res = dd.toInt() + return PtgInt(res) + } + + /* + * + * These are some helper methods for the more brutal of the math functions + */ + //helper for asinh + private fun getAcosh(x: Double): Double { + return Math.log(x + (1.0 + x) * Math.sqrt((x - 1.0) / (x + 1.0))) + } + + // factorial helper + fun factorial(n: Long): Long { + var result: Long + if (n <= 1) { + result = 1 // 1! is 1 + } else { + result = n + val partial = factorial(n - 1) + result = result * partial + }// The recursive part + return result + } + + /* + * Step factoral calculates a factorial of a number + * the number of steps specified + * + */ + //Combin helper, steps factorials + private fun stepFactorial(n: Long, numsteps: Int): Long { + var n = n + var numsteps = numsteps + var result = n + if (n < numsteps) return -1 + while (numsteps > 1) { + val partial = n - 1 + result = partial * result + n-- + numsteps-- + + } + return result + } + + // double factorial helper + private fun doubleFactorial(n: Long): Long { + var result: Long + if (n <= 1) { + result = 1 // 1! is 1 + } else if (n == 2L) { + result = 2 + } else { + result = n + val partial = factorial(n - 2) + result = result * partial + }// The recursive part + return result + } + +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/Ptg.java b/src/main/java/io/starter/formats/XLS/formulas/Ptg.kt similarity index 58% rename from src/main/java/io/starter/formats/XLS/formulas/Ptg.java rename to src/main/java/io/starter/formats/XLS/formulas/Ptg.kt index 325f6a1..e5c9e5e 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/Ptg.java +++ b/src/main/java/io/starter/formats/XLS/formulas/Ptg.kt @@ -20,11 +20,11 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.formats.XLS.*; +import io.starter.formats.XLS.* -import java.io.Serializable; +import java.io.Serializable /** @@ -34,134 +34,33 @@ * See the docs under Formula for more information. * * @see Ptg + * * @see Formula */ -public interface Ptg extends XLSConstants, Serializable { - - /** - * VALUE type Reference (Id=0x44) - */ - short VALUE = 0; - /** - * REFERENCE type Reference (Id=0x24) - */ - short REFERENCE = 1; - /** - * ARRAY type Reference (Id=0x64) - */ - short ARRAY = 2; - - int PTG_LOCATION_POLICY_UNLOCKED = 0; - int PTG_LOCATION_POLICY_LOCKED = 1; - int PTG_LOCATION_POLICY_TRACK = 2; - - - int PTG_TYPE_SINGLE = 1; // single-byte record - int PTG_TYPE_ARRAY = 2; // array of bytes record - - //ptg lengths - int PTG_NUM_LENGTH = 9; - int PTG_ADD_LENGTH = 1; - int PTG_AREA_LENGTH = 9; - int PTG_AREA3D_LENGTH = 11; - int PTG_AREAERR3D_LENGTH = 11; - int PTG_ATR_LENGTH = 4; - int PTG_CONCAT_LENGTH = 1; - int PTG_DIV_LENGTH = 1; - int PTG_EQ_LENGTH = 1; - int PTG_EXP_LENGTH = 5; - int PTG_FUNC_LENGTH = 3; - int PTG_FUNCVAR_LENGTH = 4; - int PTG_GE_LENGTH = 1; - int PTG_GT_LENGTH = 1; - int PTG_INT_LENGTH = 3; - int PTG_ISECT_LENGTH = 1; - int PTG_LE_LENGTH = 1; - int PTG_LT_LENGTH = 1; - int PTG_MEMERR_LENGTH = 7; - int PTG_MEM_AREA_N_LENGTH = 7; - int PTG_MEM_AREA_NV_LENGTH = 7; - int PTG_MLT_LENGTH = 1; - int PTG_MYSTERY_LENGTH = 1; - int PTG_NE_LENGTH = 1; - int PTG_NAME_LENGTH = 5; - int PTG_NAMEX_LENGTH = 7; - int PTG_PAREN_LENGTH = 1; - int PTG_POWER_LENGTH = 1; - int PTG_RANGE_LENGTH = 1; - int PTG_REF_LENGTH = 5; - int PTG_REF3D_LENGTH = 7; - int PTG_REFERR_LENGTH = 5; - int PTG_REFERR3D_LENGTH = 7; - int PTG_ENDSHEET_LENGTH = 1; - int PTG_SUB_LENGTH = 1; - int PTG_UNION_LENGTH = 1; - int PTG_BOOL_LENGTH = 2; - int PTG_UPLUS_LENGTH = 1; - int PTG_UMINUS_LENGTH = 1; - int PTG_PERCENT_LENGTH = 1; - - - //TODO: add all the opcodes here - byte PTG_INT = 0x1e; - - /** - * Creates a deep clone of this Ptg. - */ - Object clone(); - - /** - * constructor must pass in 'parent' XLSRecord so that there - * is a handle for updating... - * - * @return - */ - void setParentRec(XLSRecord x); - - /** - * update the values of the Ptg - */ - void updateRecord(); +interface Ptg : XLSConstants, Serializable { /** * return the length of the Ptg */ - int getLength(); + val length: Int /** * return the number of parameters to this Ptg */ - int getNumParams(); - - int CALCULATED = 0; - int UNCALCULATED = -1; - - /** - * if the Ptg needs to keep a handle to a cell, this is it... - * tells the Ptg to get it on its own... - */ - void updateAddressFromTrackerCell(); - - /** - * if the Ptg needs to keep a handle to a cell, this is it... - * tells the Ptg to get it on its own... - */ - void initTrackerCell(); + val numParams: Int /** * if the Ptg needs to keep a handle to a cell, this is it... * * @return trackercell The trackercell to set. */ - BiffRec getTrackercell(); - /** * if the Ptg needs to keep a handle to a cell, this is it... * * @param trackercell The trackercell to set. */ - void setTrackercell(BiffRec trackercell); + var trackercell: BiffRec /** * a locking mechanism so that Ptgs are not endlessly @@ -169,90 +68,81 @@ public interface Ptg extends XLSConstants, Serializable { * * @return */ - int getLock(); - /** * a locking mechanism so that Ptgs are not endlessly * re-calculated * * @return */ - void setLock(int x); + var lock: Int /** * determine the general Ptg type */ - boolean getIsOperator(); + val isOperator: Boolean - boolean getIsBinaryOperator(); + val isBinaryOperator: Boolean - boolean getIsUnaryOperator(); + val isUnaryOperator: Boolean - boolean getIsStandAloneOperator(); + val isStandAloneOperator: Boolean - boolean getIsOperand(); + val isOperand: Boolean - boolean getIsControl(); + val isControl: Boolean - boolean getIsFunction(); + val isFunction: Boolean - boolean getIsReference(); - - /** - * Operator Ptgs take other Ptgs as arguments - * so we need to pass them in to get a meaningful - * value. - */ - void setVars(Ptg[] parr); + val isReference: Boolean /** * determines whether this operator is a 'primitive' such as +,-,=,<,>,!=,==,etc. * the upshot is that primitives go BETWEEN operands, and non-primitives * encapsulate - *

                      + * + * * ie: - *

                      + * + * * SUM(A1:A4) non-primitive * A1+A4 primitive */ - boolean getIsPrimitiveOperator(); + val isPrimitiveOperator: Boolean /* Determines whether the ptg represents multiple ptg's in reality. ie ptgArea ia actually a collection of ptgRef's, so ptgArea.getIsArray returns 'true' */ - boolean getIsArray(); + val isArray: Boolean /** * return the human-readable String representation of * this ptg -- if applicable */ - String getTextString(); - - /** - * pass in arbitrary number of values (probably other Ptgs) - * and return the resultant value. - *

                      - * This effectively calculates the Expression. - */ - Object evaluate(Object[] obj); + val textString: String /** * If a record consists of multiple sub records (ie PtgArea) return those * records, else return null; */ - Ptg[] getComponents(); + val components: Array /** * @return byte[] containing the whole ptg, including identifying opcode */ - byte[] getRecord(); + val record: ByteArray /* @return XLSRecord containing the whole ptg */ - XLSRecord getParentRec(); + /** + * constructor must pass in 'parent' XLSRecord so that there + * is a handle for updating... + * + * @return + */ + var parentRec: XLSRecord /** * returns whether the Location of the Ptg is locked @@ -260,87 +150,197 @@ public interface Ptg extends XLSConstants, Serializable { * * @return location policy */ - int getLocationPolicy(); - /** * lock the Location of the Ptg so that it will not * be updated during automated BiffRec movement updates * * @param b whether to lock the location of this Ptg */ - void setLocationPolicy(int b); + var locationPolicy: Int /** - * setLocation moves a ptg that is a reference to a location, such as - * a ptg range being modified + * When the ptg is a reference to a location this returns that location * - * @param String location, such as A1:D4 + * @return Location */ - void setLocation(String s); - /** - * When the ptg is a reference to a location this returns that location + * setLocation moves a ptg that is a reference to a location, such as + * a ptg range being modified * - * @return Location + * @param String location, such as A1:D4 */ - String getLocation() throws FormulaNotFoundException; + var location: String - int[] getIntLocation() throws FormulaNotFoundException; + val intLocation: IntArray /** * return the human-readable String representation of * this ptg -- if applicable */ - String getString(); + val string: String /** * return the byte header for the Ptg */ - byte getOpcode(); + val opcode: Byte /** * return the human-readable String representation of * the "closing" portion of this Ptg * such as a closing parenthesis. */ - String getString2(); - - /** - * return a Ptg consisting of the calculated values - * of the ptg's passed in. Returns null for any non-operater - * ptg. - * - * @throws CalculationException - */ - Ptg calculatePtg(Ptg[] parsething) throws FunctionNotSupportedException, CalculationException; + val string2: String /** * Gets the (return) value of this Ptg as an operand Ptg. */ - Ptg getPtgVal(); + val ptgVal: Ptg /** * returns the value of an operand ptg. * * @return null for non-operand Ptg. */ - Object getValue(); + val value: Any /** * Gets the value of the ptg represented as an int. - *

                      + * + * * This can result in loss of precision for floating point values. - *

                      + * + * * -1 will be returned for values that are not translateable to an integer * * @return integer representing the ptg, or NAN */ - int getIntVal(); + val intVal: Int + + val doubleVal: Double + + val isBlank: Boolean // 20081112 KSC + + /** + * Creates a deep clone of this Ptg. + */ + override fun clone(): Any + + /** + * update the values of the Ptg + */ + fun updateRecord() + + /** + * if the Ptg needs to keep a handle to a cell, this is it... + * tells the Ptg to get it on its own... + */ + fun updateAddressFromTrackerCell() - double getDoubleVal(); + /** + * if the Ptg needs to keep a handle to a cell, this is it... + * tells the Ptg to get it on its own... + */ + fun initTrackerCell() + + /** + * Operator Ptgs take other Ptgs as arguments + * so we need to pass them in to get a meaningful + * value. + */ + fun setVars(parr: Array) - boolean isBlank(); // 20081112 KSC + /** + * pass in arbitrary number of values (probably other Ptgs) + * and return the resultant value. + * + * + * This effectively calculates the Expression. + */ + fun evaluate(obj: Array): Any - void close(); + /** + * return a Ptg consisting of the calculated values + * of the ptg's passed in. Returns null for any non-operater + * ptg. + * + * @throws CalculationException + */ + @Throws(FunctionNotSupportedException::class, CalculationException::class) + fun calculatePtg(parsething: Array): Ptg + + fun close() + + companion object { + + /** + * VALUE type Reference (Id=0x44) + */ + val VALUE: Short = 0 + /** + * REFERENCE type Reference (Id=0x24) + */ + val REFERENCE: Short = 1 + /** + * ARRAY type Reference (Id=0x64) + */ + val ARRAY: Short = 2 + + val PTG_LOCATION_POLICY_UNLOCKED = 0 + val PTG_LOCATION_POLICY_LOCKED = 1 + val PTG_LOCATION_POLICY_TRACK = 2 + + + val PTG_TYPE_SINGLE = 1 // single-byte record + val PTG_TYPE_ARRAY = 2 // array of bytes record + + //ptg lengths + val PTG_NUM_LENGTH = 9 + val PTG_ADD_LENGTH = 1 + val PTG_AREA_LENGTH = 9 + val PTG_AREA3D_LENGTH = 11 + val PTG_AREAERR3D_LENGTH = 11 + val PTG_ATR_LENGTH = 4 + val PTG_CONCAT_LENGTH = 1 + val PTG_DIV_LENGTH = 1 + val PTG_EQ_LENGTH = 1 + val PTG_EXP_LENGTH = 5 + val PTG_FUNC_LENGTH = 3 + val PTG_FUNCVAR_LENGTH = 4 + val PTG_GE_LENGTH = 1 + val PTG_GT_LENGTH = 1 + val PTG_INT_LENGTH = 3 + val PTG_ISECT_LENGTH = 1 + val PTG_LE_LENGTH = 1 + val PTG_LT_LENGTH = 1 + val PTG_MEMERR_LENGTH = 7 + val PTG_MEM_AREA_N_LENGTH = 7 + val PTG_MEM_AREA_NV_LENGTH = 7 + val PTG_MLT_LENGTH = 1 + val PTG_MYSTERY_LENGTH = 1 + val PTG_NE_LENGTH = 1 + val PTG_NAME_LENGTH = 5 + val PTG_NAMEX_LENGTH = 7 + val PTG_PAREN_LENGTH = 1 + val PTG_POWER_LENGTH = 1 + val PTG_RANGE_LENGTH = 1 + val PTG_REF_LENGTH = 5 + val PTG_REF3D_LENGTH = 7 + val PTG_REFERR_LENGTH = 5 + val PTG_REFERR3D_LENGTH = 7 + val PTG_ENDSHEET_LENGTH = 1 + val PTG_SUB_LENGTH = 1 + val PTG_UNION_LENGTH = 1 + val PTG_BOOL_LENGTH = 2 + val PTG_UPLUS_LENGTH = 1 + val PTG_UMINUS_LENGTH = 1 + val PTG_PERCENT_LENGTH = 1 + + + //TODO: add all the opcodes here + val PTG_INT: Byte = 0x1e + + val CALCULATED = 0 + val UNCALCULATED = -1 + } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.java b/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.java deleted file mode 100644 index 34f74f1..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import java.lang.reflect.Array; - - -/* - Ptg that is an addition operand - - * @see Ptg - * @see Formula - - -*/ -public class PtgAdd extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -964400139336259946L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public PtgAdd() { - ptgId = 0x3; - record = new byte[1]; - record[0] = 0x3; - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return "+"; - } - - public String toString() { - return this.getString(); - } - - public int getLength() { - return PTG_ADD_LENGTH; - } - - /* Operator specific calculate method, this one adds two values. - - */ - public Ptg calculatePtg(Ptg[] form) { - try { - Object[] o = getValuesFromPtgs(form); - if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); - if (!o[0].getClass().isArray()) { - if (o.length != 2) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - double o0 = 0, o1 = 0; - try { - o0 = getDoubleValue(o[0], this.parent_rec); - o1 = getDoubleValue(o[1], this.parent_rec); - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - double returnVal = o0 + o1; - PtgNumber n = new PtgNumber(returnVal); - return n; - } else { // handle array fomulas - String retArry = ""; - int nArrays = java.lang.reflect.Array.getLength(o); - if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i = 0; i < nArrays - 1; i += 2) { - Object secondOp = null; - boolean comparitorIsArray = o[i + 1].getClass().isArray(); - if (!comparitorIsArray) secondOp = o[i + 1]; - for (int j = 0; j < nVals; j++) { - Object firstOp = Array.get(o[i], j); // first array index j - if (comparitorIsArray) - secondOp = Array.get(o[i + 1], j); // second array index j - double o0 = 0, o1 = 0; - try { - o0 = getDoubleValue(firstOp, this.parent_rec); - o1 = getDoubleValue(secondOp, this.parent_rec); - } catch (NumberFormatException e) { - retArry = retArry + "#VALUE!" + ","; - continue; - } - double retVal = o0 + o1; - retArry = retArry + retVal + ","; - } - } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; - } - - } catch (NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } catch (Exception e) { // handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.kt new file mode 100644 index 0000000..abee6c1 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgAdd.kt @@ -0,0 +1,137 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import java.lang.reflect.Array + + +/* + Ptg that is an addition operand + + * @see Ptg + * @see Formula + + +*/ +class PtgAdd : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val string: String + get() = "+" + + override val length: Int + get() = Ptg.PTG_ADD_LENGTH + + init { + opcode = 0x3 + record = ByteArray(1) + record[0] = 0x3 + } + + override fun toString(): String { + return this.string + } + + /* Operator specific calculate method, this one adds two values. + + */ + override fun calculatePtg(form: Array): Ptg? { + try { + val o = GenericPtg.getValuesFromPtgs(form) ?: return PtgErr(PtgErr.ERROR_VALUE) + if (!o[0].javaClass.isArray()) { + if (o.size != 2) { + return PtgErr(PtgErr.ERROR_VALUE) + } + var o0 = 0.0 + var o1 = 0.0 + try { + o0 = GenericPtg.getDoubleValue(o[0], this.parentRec) + o1 = GenericPtg.getDoubleValue(o[1], this.parentRec) + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + val returnVal = o0 + o1 + return PtgNumber(returnVal) + } else { // handle array fomulas + var retArry = "" + val nArrays = java.lang.reflect.Array.getLength(o) + if (nArrays != 2) return PtgErr(PtgErr.ERROR_VALUE) + val nVals = java.lang.reflect.Array.getLength(o[0]) // use first array element to determine length of values as subsequent vals might not be arrays + var i = 0 + while (i < nArrays - 1) { + var secondOp: Any? = null + val comparitorIsArray = o[i + 1].javaClass.isArray() + if (!comparitorIsArray) secondOp = o[i + 1] + for (j in 0 until nVals) { + val firstOp = Array.get(o[i], j) // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j) // second array index j + var o0 = 0.0 + var o1 = 0.0 + try { + o0 = GenericPtg.getDoubleValue(firstOp, this.parentRec) + o1 = GenericPtg.getDoubleValue(secondOp, this.parentRec) + } catch (e: NumberFormatException) { + retArry = "$retArry#VALUE!," + continue + } + + val retVal = o0 + o1 + retArry = "$retArry$retVal," + } + i += 2 + } + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa + } + + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } catch (e: Exception) { // handle error ala Excel + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -964400139336259946L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgArea.java b/src/main/java/io/starter/formats/XLS/formulas/PtgArea.java deleted file mode 100644 index 0b4f977..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgArea.java +++ /dev/null @@ -1,910 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.CellHandle; -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.*; -import io.starter.toolkit.FastAddVector; -import io.starter.toolkit.Logger; - -import java.util.Vector; - - -/** - * ptgArea is a reference to an area (rectangle) of cells. - * Essentially it is a collection of two ptgRef's, so it will be - * treated that way in the code... - * - *

                      - * Offset      Size    Contents
                      - * ----------------------------------------------------
                      - * 0			2 		Index to first row (065535) or offset of first row (method [B], -3276832767)
                      - * 2 			2 		Index to last row (065535) or offset of last row (method [B], -3276832767)
                      - * 4 			2 		Index to first column or offset of first column, with relative flags (see table above)
                      - * 6 			2 		Index to last column or offset of last column, with relative flags (see table above)
                      - *
                      - * Only the low-order 14 bits specify the Col, the other bits specify
                      - * relative vs absolute for both the col or the row.
                      - *
                      - * Bits        Mask        Name    Contents
                      - * -----------------------------------------------------
                      - * 15          8000h       fRwRel  =1 if row offset relative,
                      - * =0 if otherwise
                      - * 14          4000h       fColRel =1 if row offset relative,
                      - * =0 if otherwise
                      - * 13-0        3FFFh       col     Ordinal column offset or number
                      - * 
                      - * - * @see Ptg - * @see Formula - */ -public class PtgArea extends PtgRef implements Ptg { - - public static final long serialVersionUID = 666555444333222l; - - public boolean getIsOperand() { - return true; - } - - public boolean getIsReference() { - return true; - } - - protected PtgRef firstPtg; - protected PtgRef lastPtg; - - /* constructor, takes the array of the ptgRef, including - the identifier so we do not need to figure it out again later... - */ - public void init(byte[] b) { - locax = null; // cache reset - ptgId = b[0]; - record = b; - this.populateVals(); - } - - /* - Throw this data into two ptgref's - */ - public void populateVals() { - byte[] temp1 = new byte[5]; - byte[] temp2 = new byte[5]; - temp1[0] = 0x24; - temp2[0] = 0x24; - System.arraycopy(record, 1, temp1, 1, 2); - System.arraycopy(record, 5, temp1, 3, 2); - System.arraycopy(record, 3, temp2, 1, 2); - System.arraycopy(record, 7, temp2, 3, 2); - try { - getSheetName(); // 20080212 KSC: - } catch (WorkSheetNotFoundException we) { - Logger.logErr(we); - } - firstPtg = new PtgRef(temp1, parent_rec, false); // don't add to ref tracker as it's part of area - firstPtg.sheetname = sheetname; - - lastPtg = new PtgRef(temp2, parent_rec, false); // don't add to ref tracker as it's part of area - lastPtg.sheetname = sheetname; - setWholeRowCol(); - this.hashcode = getHashCode(); - } - - /** - * Returns all of the cells of this range as PtgRef's. - * This includes empty cells, values, formulas, etc. - * Note the setting of parent-rec requires finding the cell - * the PtgRef refer's to. If that is null then the PtgRef - * will exist, just with a null value. This could cause issues when - * programatically populating cells. - */ - public Ptg[] getComponents() { - Vector v = new Vector(); - try { -// TODO: check rc sanity here - int startcol = -1; - int startrow = -1; - int endrow = -1; - int endcol = -1; - int[] startloc = null; - int[] endloc = null; - - /*if (this.wholeRow) { - startcol= 0; - endcol= this.getSheet().getMaxCol(); - startrow= endrow= firstPtg.rw; - } if (this.wholeCol) { - startrow= 0; // Get Actual Coordinates - startcol= endcol= firstPtg.col; - endrow= this.getSheet().getMaxRow(); - } */ - if (firstPtg != null) { - startloc = firstPtg.getRealIntLocation(); - startcol = startloc[1]; - startrow = startloc[0]; - } else { - startloc = ExcelTools.getRangeRowCol(locax); - startcol = startloc[1]; - startrow = startloc[0]; - } - - if (lastPtg != null) { - endloc = lastPtg.getRealIntLocation(); - endcol = endloc[1]; - endrow = endloc[0]; - } else { - endloc = ExcelTools.getRangeRowCol(locax); - endcol = endloc[3]; - endrow = endloc[2]; - } - - // usually don't need to set sheet on setlocation becuase uses parent_rec's sheet - // cases of named range or if location sheet does not = parent_rec sheet, set sheet explicitly - String sht = null; // usual case, don't need to set sheet - Boundsheet sh = parent_rec.getSheet(); - if (sh == null || (this.sheetname != null && !this.sheetname.equals(sh.getSheetName()))) { - if (sh == null || !GenericPtg.qualifySheetname(this.sheetname).equals(GenericPtg.qualifySheetname(sh.getSheetName()))) - sht = this.sheetname + "!"; - } - // loop through the cols - for (; startcol <= endcol; startcol++) { - // loop through the rows inside - int rowholder = startrow; - for (; rowholder <= endrow; rowholder++) { - String displaycol = ExcelTools.getAlphaVal(startcol); - int displayrow = rowholder + 1; - PtgRef pref; - if (sht == null) - pref = new PtgRef(displaycol + displayrow, parent_rec, false); - else - pref = new PtgRef(sht + displaycol + displayrow, parent_rec, false); - v.add(pref); - } - } - } catch (Exception e) { - Logger.logErr("calculating formula range value failed.", e); - } - PtgRef[] pref = new PtgRef[v.size()]; - v.toArray(pref); - return pref; - } - - - /** - * returns the row/col ints for the ref - *

                      - * Format is FirstRow,FirstCol,LastRow,LastCol - * - * @return - */ - public int[] getRowCol() { - if (firstPtg == null) { - return null; - } - if (lastPtg == null && firstPtg != null) { - int[] rc1 = firstPtg.getRowCol(); - int[] ret = {rc1[0], rc1[1], rc1[0], rc1[1]}; - return ret; - } - int[] rc1 = firstPtg.getRowCol(); - int[] rc2 = lastPtg.getRowCol(); - int[] ret = {rc1[0], rc1[1], rc2[0], rc2[1]}; - return ret; - } - - /** - * returns whether this CellRange Contains a Cell - * - * @param the cell to test - * @return whether the cell is in the range - */ - public boolean contains(CellHandle ch) { - String chsheet = ch.getWorkSheetName(); - String mysheet = ""; - if (this.getParentRec() != null) { - BiffRec b = this.getParentRec(); - if (b.getSheet() != null) { - mysheet = b.getSheet().getSheetName(); - } - } - if (!chsheet.equalsIgnoreCase(mysheet)) - return false; - String adr = ch.getCellAddress(); -// FIX broken COLROW - int[] rc = ExcelTools.getRowColFromString(adr); - return contains(rc); - } - - /** - * check to see if the sheet and row/col are contained - * in this ref - * - * @param sheetname - * @param rc - * @return - */ - public boolean contains(String sn, int[] rc) { - if (sheetname == null) { - try { - sheetname = this.getSheetName(); - } catch (Exception e) { - } - } - if (!sn.equalsIgnoreCase(sheetname)) - return false; - return contains(rc); - } - - /** - * returns whether this PtgArea Contains the specified row/col coordinate - *

                      - *

                      - * [0] = firstrow - * [1] = firstcol - * [2] = lastrow - * [3] = lastcol - * - * @param the rc coordinates to test - * @return whether the coordinates are in the range - */ - public boolean contains(int[] rc) { - int[] thisRange = this.getIntLocation(); - // test the first rc - if (rc[0] < thisRange[0]) return false; // row above the first ref row? - if (rc[0] > thisRange[2]) return false; // row after the last ref row? - - if (rc[1] < thisRange[1]) return false; // col before the first ref col? - return rc[1] <= thisRange[3]; // col after the last ref col? - } - - - // private byte[] PROTOTYPE_BYTES = {0x25, 0, 0, 0, 0, 0, 0, 0, 0}; - - /** - * return the human-readable String representation of - * this ptg -- if applicable - */ - public String getString() { - return this.getLocation(); - } - - /* - * Creates a new PtgArea. The parent rec is needed - * as getting a value goes to the boundsheet to determine - * values. The parent rec *must* be on the same sheet - * as the PtgArea referenced! - */ - public PtgArea(String range, XLSRecord parent) { - this(range, parent, true); - } - - /** - * Creates a new PtgArea from 2 component ptgs, used by shared formula - * to create ptgareas. ptg1 should be upperleft corner, ptg2 bottomright - */ - public PtgArea(PtgRef ptg1, PtgRef ptg2, XLSRecord parent) { - this(); - firstPtg = ptg1; - lastPtg = ptg2; - parent_rec = parent; - this.hashcode = getHashCode(); - this.updateRecord(); - } - - /* - * Creates a new PtgArea. The parent rec is needed - * as getting a value goes to the boundsheet to determine - * values. The parent rec *must* be on the same sheet - * as the PtgArea referenced! - * - * relativeRefs = true is excel default - */ - public PtgArea(String range, XLSRecord parent, boolean relativeRefs) { - this(); - int[] loc = ExcelTools.getRangeRowCol(range); - int[] temp = new int[2]; - temp[0] = loc[0]; - temp[1] = loc[1]; - String res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs); - firstPtg = new PtgRef(res, parent, false); - temp[0] = loc[2]; - temp[1] = loc[3]; - res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs); - lastPtg = new PtgRef(res, parent, false); - setWholeRowCol(); - parent_rec = parent; - this.hashcode = getHashCode(); - this.updateRecord(); - } - - /* - * Creates a new PtgArea using an int array as [r,c,r1,c1]. - * The parent rec is needed - * as getting a value goes to the boundsheet to determine - * values. The parent rec *must* be on the same sheet - * as the PtgArea referenced! - * - * relativeRefs = true is excel default - */ - public PtgArea(int[] loc, XLSRecord parent, boolean relativeRefs) { - int[] temp = new int[2]; - temp[0] = loc[0]; - temp[1] = loc[1]; - String res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs); - firstPtg = new PtgRef(res, parent, false); - temp[0] = loc[2]; - temp[1] = loc[3]; - res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs); - lastPtg = new PtgRef(res, parent, false); - setWholeRowCol(); - parent_rec = parent; - this.hashcode = getHashCode(); - this.updateRecord(); - } - - /** - * set the wholeRow and/or wholeCol flag for this PtgArea - * for ranges such as: - * $B:$B and $5:%9 - */ - public void setWholeRowCol() { - if (firstPtg.rw <= 1 && lastPtg.wholeCol) // TODO: inconsistencies in 0-based or 1-based rows - this.wholeCol = true; - this.wholeRow = lastPtg.wholeRow; - if (this.wholeCol) - useReferenceTracker = false; - } - - /* - * Default constructor - */ - public PtgArea() { - record = new byte[9]; - ptgId = 0x25; - record[0] = 0x25; - } - - public PtgArea(boolean useReferenceTracker) { - this(); - this.useReferenceTracker = useReferenceTracker; - } - - /** - * set the Ptg Id type to one of: - * VALUE, REFERENCE or Array - * 25H (tAreaR), 45H (tAreaV), 65H (tAreaA) - *
                      The Ptg type is important for certain - * functions which require a specific type of operand - */ - public void setPtgType(short type) { - switch (type) { - case VALUE: - ptgId = 0x45; - break; - case REFERENCE: - ptgId = 0x25; - break; - case Ptg.ARRAY: - ptgId = 0x65; - break; - } - record[0] = ptgId; - } - - - public String toString() { - String ret = getString(); - - if (getParentRec() != null) - if (ret.indexOf("!") < 0) - try { // Catch WorkSheetNotFoundException to handle Unresolved External refs - getSheetName(); - if (sheetname != null) - ret = sheetname + "!" + ret; - } catch (WorkSheetNotFoundException we) { - Logger.logErr(we); - } - return ret; - } - - public void setParentRec(XLSRecord rec) { - super.setParentRec(rec); - // 20080221 KSC: just set parent_rec super.setParentRec(rec); - if (firstPtg != null) - firstPtg.setParentRec(parent_rec); - if (lastPtg != null) - lastPtg.setParentRec(parent_rec); - } - - - /* Set the location of this PtgRef. This takes a location - such as "a14:b15" - */ - public void setLocation(String address) { - String[] s = ExcelTools.stripSheetNameFromRange(address); - setLocation(s); - this.hashcode = getHashCode(); - } - - /** - * set Ptg to parsed location - * - * @param loc String[] sheet1, range, sheet2, exref1, exref2 - */ - public void setLocation(String[] loc) { - locax = null; // cache reset - if (firstPtg == null) { - this.record = new byte[]{0x25, 0, 0, 0, 0, 0, 0, 0, 0}; - - if (this.getParentRec() != null) this.populateVals(); - } else if (this.useReferenceTracker) - this.removeFromRefTracker(); - int i = loc[1].indexOf(":"); - // handle single cell addresses as: A1:A1 - if (i == -1) { - loc[1] = loc[1] + ":" + loc[1]; - i = loc[1].indexOf(":"); - } - String firstloc = loc[1].substring(0, i); - String lastloc = loc[1].substring(i + 1); - if (loc[0] != null) - firstloc = loc[0] + "!" + firstloc; - if (loc[2] != null) - lastloc = loc[2] + "!" + lastloc; - if (loc[3] != null) // 20090325 KSC: store OOXML External References - firstloc = loc[3] + firstloc; - if (loc[4] != null) // 20090325 KSC: store OOXML External References - lastloc = loc[4] + lastloc; - - // TODO: do we need to remove refs from tracker? - firstPtg.setParentRec(this.getParentRec()); - lastPtg.setParentRec(this.getParentRec()); - - firstPtg.setUseReferenceTracker(false); - lastPtg.setUseReferenceTracker(false); - firstPtg.setLocation(firstloc); - lastPtg.setLocation(lastloc); - setWholeRowCol(); - this.hashcode = getHashCode(); - this.updateRecord(); - if (this.useReferenceTracker) {// check of boolean useReferenceTracker - if (!this.getIsWholeCol() && !this.getIsWholeRow()) - this.addToRefTracker(); - else - useReferenceTracker = false; - } - } - - /** - * returns the location of the ptg as an array of ints. - * [0] = firstRow - * [1] = firstCol - * [2] = lastRow - * [3] = lastCol - */ - public int[] getIntLocation() { - int[] first = firstPtg.getIntLocation(); - int[] last = lastPtg.getIntLocation(); - int[] returning = new int[4]; - System.arraycopy(first, 0, returning, 0, 2); - System.arraycopy(last, 0, returning, 2, 2); - return returning; - } - - - /** - * Set the location of this PtgArea. This takes a location - * such as {1,2,3,4} - */ - public void setLocation(int[] rowcol) { - locax = null; // cache reset - if (firstPtg == null) { - //this.record = new byte[] {0x25, 0, 0, 0, 0, 0, 0, 0, 0}; -- don't as can be called from PtgArea3d - if (this.getParentRec() != null) this.populateVals(); - } else if (this.useReferenceTracker) - this.removeFromRefTracker(); - - // TODO: do we need to remove refs from tracker? - firstPtg.setParentRec(this.getParentRec()); - firstPtg.setSheetName(sheetname); - lastPtg.setParentRec(this.getParentRec()); - lastPtg.setSheetName(sheetname); - - firstPtg.setUseReferenceTracker(false); - lastPtg.setUseReferenceTracker(false); - firstPtg.setLocation(rowcol); - int[] rc = new int[2]; - rc[0] = rowcol[2]; - rc[1] = rowcol[3]; - lastPtg.setLocation(rc); - - this.hashcode = getHashCode(); - this.updateRecord(); - if (this.useReferenceTracker) // check of boolean useReferenceTracker - this.addToRefTracker(); - } - - /* - Returns the location of the Ptg as a string - */ - public String getLocation() { - String lc = getLocationHelper(); - locax = lc; - return lc; - } - - private String getLocationHelper() { - //String loc= null; - if (firstPtg == null || lastPtg == null) { - this.populateVals(); - if (firstPtg == null || lastPtg == null) // we tried! - throw new AssertionError("PtgArea.getLocationHelper null ptgs"); - } - String s = firstPtg.getLocation(); - String y = lastPtg.getLocation(); - - String[] loc1 = ExcelTools.stripSheetNameFromRange(s); // sheet, addr - String[] loc2 = ExcelTools.stripSheetNameFromRange(y); // sheet, addr - - String sh1 = loc1[0]; - String sh2 = loc2[0]; - String addr1 = loc1[1]; - String addr2 = loc2[1]; - - if (!(this instanceof PtgArea3d)) { - //if (addr1.equals(addr2)) // this is proper but makes so many assertions fail, revert for now - //return addr2; - return addr1 + ":" + addr2; - } - - if (sh1 == null) sh1 = sheetname; - if (sh1 == null) // no sheetname avail - return addr1 + ":" + addr2; - - // handle OOXML external references - if (externalLink1 > 0) - sh1 = "[" + externalLink1 + "]" + sh1; - if (externalLink2 > 0 && sh2 != null) - sh2 = "[" + externalLink2 + "]" + sh2; - - sh1 = qualifySheetname(sh1); - - // have sheetname - if (sh1.equals(sh2)) { // range is in one sheet - if (!sh1.equals("")) { - if (!addr1.equals(addr2)) - return sh1 + "!" + addr1 + ":" + addr2; - else - return sh1 + "!" + addr1; - } else if (sheetname != null) { // both sheets in sub-ptgs are null - sh1 = sheetname; - // 20090325 KSC: handle OOXML external references - if (externalLink1 > 0) - sh1 = "[" + externalLink1 + "]" + sh1; - sh1 = qualifySheetname(sh1); - if (!addr1.equals(addr2)) // 20081215 KSC: - return sh1 + "!" + addr1 + ":" + addr2; - return sh1 + "!" + addr1; - } - } else if (sh2 == null) { // only 1 sheetnaame specified - if (!addr1.equals(addr2)) // 20081215 KSC: - return sh1 + "!" + addr1 + ":" + addr2; - return sh1 + "!" + addr1; - } - // otherwise, include both sheets in return string - sh2 = qualifySheetname(sh2); - return sh1 + ":" + sh2 + "!" + addr1 + ":" + addr2; - } - - /* Updates the record bytes so it can be pulled back out. - */ - public void updateRecord() { - locax = null; // cache reset - int[] pols = {firstPtg.getLocationPolicy(), lastPtg.getLocationPolicy()}; - byte[] first = firstPtg.getRecord(); - byte[] last = lastPtg.getRecord(); - // the last record has an extra identifier on it. - byte[] newrecord = new byte[9]; - newrecord[0] = record[0]; - System.arraycopy(first, 1, newrecord, 1, 2); - System.arraycopy(last, 1, newrecord, 3, 2); - System.arraycopy(first, 3, newrecord, 5, 2); - System.arraycopy(last, 3, newrecord, 7, 2); - record = newrecord; -// this.populateVals(); - if (parent_rec != null) { - if (this.parent_rec instanceof Formula) - ((Formula) this.parent_rec).updateRecord(); - else if (this.parent_rec instanceof Name) - ((Name) this.parent_rec).updatePtgs(); - } - firstPtg.setLocationPolicy(pols[0]); - lastPtg.setLocationPolicy(pols[1]); - } - - public int getLength() { - return PTG_AREA_LENGTH; - } - - /* - returns the sum of all fields within this range - ** this may need to be modified, as we might not always want sum's - *20080730 KSC: Excel does *NOT* sum these values except in array formulas - - TODO: Calculate cell values that are result of formula - take care of - in cell? - From Excel File Format Documentation: - Value class tokens will be changed dependent on further conditions. In array type functions and name type - functions, or if the forced array class state is set, it is changed to array class. In all other cases (cell type formula - without forced array class), value class is retained. - */ - public Object getValue() { - // 20080214 KSC: underlying cells may have changed ...if(refCell==null) - refCell = this.getRefCells(); - Object returnval = new Double(0); - String retstr = null; - String array = ""; - boolean isArray = (this.parent_rec instanceof Array); - for (int t = 0; t < refCell.length; t++) { - BiffRec cel = refCell[t]; - if (cel == null) { // 20090203 KSC - continue; - } - - try { - Formula f = cel.getFormulaRec(); - if (f != null) { - Object oby = f.calculateFormula(); - String s = String.valueOf(oby); - try { - //Double d = new Double(s); - returnval = new Double(s); - } catch (NumberFormatException ex) { - retstr = s; // 20090202 KSC: was += - } - - } else { - returnval = cel.getInternalVal(); //DblVal(); // 20090202 KSC: was += - } - } catch (FunctionNotSupportedException e) { - // keep going??? - } catch (Exception e) { - returnval = cel.getInternalVal(); //DblVal(); // 20090202 KSC: was += - } - if (!isArray) // 20080730 KSC: if not an array, retrieve only 1st referenced cell value - break; - // - if (retstr != null) - array = array + retstr + ","; - else - array = array + returnval + ","; - retstr = null; - } - if (isArray && array != null && array.length() > 1) { // 20090817 KSC: [BugTracker 2683] - array = "{" + array.substring(0, array.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(array); - return pa; - } - if (retstr != null) - return retstr; - - return returnval; //new Double(returnval); - } - - - /* - Returns all of the cells of this range as PtgRef's. - This includes empty cells, values, formulas, etc. - Note the setting of parent-rec requires finding the cell - the PtgRef refer's to. If that is null then the PtgRef - will exist, just with a null value. This could cause issues when - programatically populating cells. - */ - public Ptg[] getColComponents(int colNum) { - if (colNum < 0) return null; - String lu = this.toString(); - Object p = parent_rec.getWorkBook().getRefTracker().getVlookups().get(lu); - - if (p != null) { - PtgArea par = (PtgArea) p; - Ptg[] ret = (Ptg[]) par.getParentRec().getWorkBook() - .getRefTracker().getLookupColCache().get(lu + ":" + Integer.valueOf(colNum)); - if (ret != null) - return ret; - } - - PtgRef[] v = null; - try { -// TODO: check rc sanity here - int[] startloc = firstPtg.getRealIntLocation(); - int startcol = colNum; // startloc[0]; - int startrow = startloc[0]; - int[] endloc = lastPtg.getRealIntLocation(); - int endrow = endloc[0]; - // error trap - if (endrow < startrow) // can happen if wholerow/wholecol, getMaxRow may be less than startRow - endrow = startrow; - int sz = endrow - startrow; - sz++; - v = new PtgRef[sz]; - // loop through the cols - // loop through the rows inside - int rowholder = startrow; - int pos = 0; - String sht = this.toString(); - if (sht.indexOf("!") > -1) sht = sht.substring(0, sht.indexOf("!")); - for (; rowholder <= endrow; rowholder++) { - String displaycol = ExcelTools.getAlphaVal(startcol); - int displayrow = rowholder + 1; - String loc = sht + "!" + displaycol + displayrow; - - PtgRef pref = new PtgRef(loc, parent_rec, this.useReferenceTracker); - - v[pos++] = pref; - } - } catch (Exception e) { - Logger.logErr("Getting column range in PtgArea failed.", e); - } - - // cache - parent_rec.getWorkBook().getRefTracker().getVlookups().put(this.toString(), this); - parent_rec.getWorkBook().getRefTracker().getLookupColCache().put(lu + ":" + Integer.valueOf(colNum), v); - return v; - } - - /** - * return the ptg components for a certain column within a ptgArea() - * - * @param rowNum - * @return all Ptg's within colNum - */ - public Ptg[] getRowComponents(int rowNum) { - FastAddVector v = new FastAddVector(); - Ptg[] allComponents = this.getComponents(); - for (int i = 0; i < allComponents.length; i++) { - PtgRef p = (PtgRef) allComponents[i]; -// TODO: check rc sanity here - int[] x = p.getRealIntLocation(); - if (x[0] == rowNum) v.add(p); - } - PtgRef[] pref = new PtgRef[v.size()]; - v.toArray(pref); - return pref; - } - - /** - * @return - */ - public PtgRef getFirstPtg() { - return firstPtg; - } - - /** - * @return - */ - public PtgRef getLastPtg() { - return lastPtg; - } - - /** - * @param ref - */ - public void setFirstPtg(PtgRef ref) { - locax = null; // cache reset - firstPtg = ref; - } - - /** - * @param ref - */ - public void setLastPtg(PtgRef ref) { - locax = null; // cache reset - lastPtg = ref; - } - - - /** - * @return Returns the refCell. - */ - public BiffRec[] getRefCells() { - double returnval = 0; - try { - Boundsheet bs = null; // this.parent_rec.getWorkBook().getWorkSheetByName(this.getSheetName()); - getSheetName(); - // handle misc sheets - if (sheetname != null) { - try { - bs = this.parent_rec.getWorkBook().getWorkSheetByName(sheetname); - } catch (Exception ex) { // guard against NPEs - bs = parent_rec.getSheet(); - } - } else { - bs = parent_rec.getSheet(); - sheetname = bs.getSheetName(); // 20080212 KSC - } - -// TODO: check rc sanity here - int[] startloc = firstPtg.getIntLocation(); - int startcol = startloc[1]; - int startrow = startloc[0]; - int[] endloc = lastPtg.getRealIntLocation(); - int endcol = endloc[1]; - int endrow = endloc[0]; - // loop through the cols - int numcols = endcol - startcol; - if (numcols < 0) - numcols = startcol - endcol; // 20090521 KSC: may have range switched so that firstPtg>lastPtg (example in named ranges in tcr_formatted_2007.xlsm) - numcols++; - int numrows = endrow - startrow; - if (numrows < 0) - numrows = startrow - endrow; // 20090521 KSC: may have range switched so that firstPtg>lastPtg (example in named ranges in tcr_formatted_2007.xlsm) - numrows++; - int totcell = numcols * numrows; - if (totcell == 0) totcell++; - if (totcell < 0) { - Logger.logErr("PtgArea.getRefCells. Error in Ptg locations: " + firstPtg.toString() + ":" + lastPtg.toString()); - totcell = 0; - } - refCell = new BiffRec[totcell]; - int rowctr = 0; - // 20090521 KSC: try to handle both cases i.e. ranges such that firstlast - if (startcol < endcol) endcol++; - else endcol--; - if (startrow < endrow) endrow++; - else endrow--; - while (startcol != endcol) { - int rowpos = startrow; - while (rowpos != endrow) { - Row r = bs.getRowByNumber(rowpos); - if (r != null) - refCell[rowctr] = r.getCell((short) (startcol)); - rowctr++; - if (rowpos < endrow) rowpos++; - else rowpos--; - } - if (startcol < endcol) startcol++; - else startcol--; - } - - /* - for (;startcol<=endcol;startcol++){ // loop the cols - int rowpos = startrow; - for (;rowpos<=endrow;rowpos++){ // loop through the rows - Row r = bs.getRowByNumber(rowpos); - if (r!=null) - refCell[rowctr] = (BiffRec)r.getCell((short)(startcol)); - rowctr++; - } - } - */ - } catch (Exception ex) { - Logger.logErr("PtgArea.getRefCells failed.", ex); - } - return refCell; - } - - protected long getHashCode() { - return lastPtg.hashcode + (firstPtg.hashcode) * ((long) MAXCOLS + (long) MAXROWS * MAXCOLS); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgArea.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgArea.kt new file mode 100644 index 0000000..13c36ad --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgArea.kt @@ -0,0 +1,929 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.CellHandle +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.* +import io.starter.toolkit.FastAddVector +import io.starter.toolkit.Logger + +import java.util.Vector + + +/** + * ptgArea is a reference to an area (rectangle) of cells. + * Essentially it is a collection of two ptgRef's, so it will be + * treated that way in the code... + * + *

                      + * Offset      Size    Contents
                      + * ----------------------------------------------------
                      + * 0			2 		Index to first row (065535) or offset of first row (method [B], -3276832767)
                      + * 2 			2 		Index to last row (065535) or offset of last row (method [B], -3276832767)
                      + * 4 			2 		Index to first column or offset of first column, with relative flags (see table above)
                      + * 6 			2 		Index to last column or offset of last column, with relative flags (see table above)
                      + *
                      + * Only the low-order 14 bits specify the Col, the other bits specify
                      + * relative vs absolute for both the col or the row.
                      + *
                      + * Bits        Mask        Name    Contents
                      + * -----------------------------------------------------
                      + * 15          8000h       fRwRel  =1 if row offset relative,
                      + * =0 if otherwise
                      + * 14          4000h       fColRel =1 if row offset relative,
                      + * =0 if otherwise
                      + * 13-0        3FFFh       col     Ordinal column offset or number
                      +
                      * + * + * @see Ptg + * + * @see Formula + */ +open class PtgArea : PtgRef, Ptg { + + override val isOperand: Boolean + get() = true + + override val isReference: Boolean + get() = true + + var firstPtg: PtgRef? = null + var lastPtg: PtgRef? = null + + /** + * Returns all of the cells of this range as PtgRef's. + * This includes empty cells, values, formulas, etc. + * Note the setting of parent-rec requires finding the cell + * the PtgRef refer's to. If that is null then the PtgRef + * will exist, just with a null value. This could cause issues when + * programatically populating cells. + */ + override// TODO: check rc sanity here + /*if (this.wholeRow) { + startcol= 0; + endcol= this.getSheet().getMaxCol(); + startrow= endrow= firstPtg.rw; + } if (this.wholeCol) { + startrow= 0; // Get Actual Coordinates + startcol= endcol= firstPtg.col; + endrow= this.getSheet().getMaxRow(); + } */// usually don't need to set sheet on setlocation becuase uses parent_rec's sheet + // cases of named range or if location sheet does not = parent_rec sheet, set sheet explicitly + // usual case, don't need to set sheet + // loop through the cols + // loop through the rows inside + val components: Array? + get() { + val v = Vector() + try { + var startcol = -1 + var startrow = -1 + var endrow = -1 + var endcol = -1 + var startloc: IntArray? = null + var endloc: IntArray? = null + if (firstPtg != null) { + startloc = firstPtg!!.realIntLocation + startcol = startloc!![1] + startrow = startloc[0] + } else { + startloc = ExcelTools.getRangeRowCol(locax!!) + startcol = startloc!![1] + startrow = startloc[0] + } + + if (lastPtg != null) { + endloc = lastPtg!!.realIntLocation + endcol = endloc!![1] + endrow = endloc[0] + } else { + endloc = ExcelTools.getRangeRowCol(locax!!) + endcol = endloc!![3] + endrow = endloc[2] + } + var sht: String? = null + val sh = parentRec.sheet + if (sh == null || this.sheetname != null && this.sheetname != sh.sheetName) { + if (sh == null || GenericPtg.qualifySheetname(this.sheetname) != GenericPtg.qualifySheetname(sh.sheetName)) + sht = this.sheetname!! + "!" + } + while (startcol <= endcol) { + var rowholder = startrow + while (rowholder <= endrow) { + val displaycol = ExcelTools.getAlphaVal(startcol) + val displayrow = rowholder + 1 + val pref: PtgRef + if (sht == null) + pref = PtgRef(displaycol + displayrow, parentRec, false) + else + pref = PtgRef(sht + displaycol + displayrow, parentRec, false) + v.add(pref) + rowholder++ + } + startcol++ + } + } catch (e: Exception) { + Logger.logErr("calculating formula range value failed.", e) + } + + val pref = arrayOfNulls(v.size) + v.toTypedArray() + return pref + } + + + /** + * returns the row/col ints for the ref + * + * + * Format is FirstRow,FirstCol,LastRow,LastCol + * + * @return + */ + override val rowCol: IntArray? + get() { + if (firstPtg == null) { + return null + } + if (lastPtg == null && firstPtg != null) { + val rc1 = firstPtg!!.rowCol + return intArrayOf(rc1[0], rc1[1], rc1[0], rc1[1]) + } + val rc1 = firstPtg!!.rowCol + val rc2 = lastPtg!!.rowCol + return intArrayOf(rc1[0], rc1[1], rc2[0], rc2[1]) + } + + + // private byte[] PROTOTYPE_BYTES = {0x25, 0, 0, 0, 0, 0, 0, 0, 0}; + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + override val string: String? + get() = this.location + + override// 20080221 KSC: just set parent_rec super.setParentRec(rec); + var parentRec: XLSRecord + get + set(rec) { + super.parentRec = rec + if (firstPtg != null) + firstPtg!!.parentRec = parentRec + if (lastPtg != null) + lastPtg!!.parentRec = parentRec + } + + /** + * returns the location of the ptg as an array of ints. + * [0] = firstRow + * [1] = firstCol + * [2] = lastRow + * [3] = lastCol + */ + override val intLocation: IntArray? + get() { + val first = firstPtg!!.intLocation + val last = lastPtg!!.intLocation + val returning = IntArray(4) + System.arraycopy(first!!, 0, returning, 0, 2) + System.arraycopy(last!!, 0, returning, 2, 2) + return returning + } + + /* + Returns the location of the Ptg as a string + */ + /* Set the location of this PtgRef. This takes a location + such as "a14:b15" + */ + override var location: String? + get() { + val lc = locationHelper + locax = lc + return lc + } + set(address) { + val s = ExcelTools.stripSheetNameFromRange(address) + setLocation(s) + this.hashcode = hashCode + } + + private//String loc= null; + // we tried! + // sheet, addr + // sheet, addr + //if (addr1.equals(addr2)) // this is proper but makes so many assertions fail, revert for now + //return addr2; + // no sheetname avail + // handle OOXML external references + // have sheetname + // range is in one sheet + // both sheets in sub-ptgs are null + // 20090325 KSC: handle OOXML external references + // 20081215 KSC: + // only 1 sheetnaame specified + // 20081215 KSC: + // otherwise, include both sheets in return string + val locationHelper: String + get() { + if (firstPtg == null || lastPtg == null) { + this.populateVals() + if (firstPtg == null || lastPtg == null) + throw AssertionError("PtgArea.getLocationHelper null ptgs") + } + val s = firstPtg!!.location + val y = lastPtg!!.location + + val loc1 = ExcelTools.stripSheetNameFromRange(s!!) + val loc2 = ExcelTools.stripSheetNameFromRange(y!!) + + var sh1: String? = loc1[0] + var sh2: String? = loc2[0] + val addr1 = loc1[1] + val addr2 = loc2[1] + + if (this !is PtgArea3d) { + return "$addr1:$addr2" + } + + if (sh1 == null) sh1 = sheetname + if (sh1 == null) + return "$addr1:$addr2" + if (externalLink1 > 0) + sh1 = "[$externalLink1]$sh1" + if (externalLink2 > 0 && sh2 != null) + sh2 = "[$externalLink2]$sh2" + + sh1 = GenericPtg.qualifySheetname(sh1) + if (sh1 == sh2) { + if (sh1 != "") { + return if (addr1 != addr2) + "$sh1!$addr1:$addr2" + else + "$sh1!$addr1" + } else if (sheetname != null) { + sh1 = sheetname + if (externalLink1 > 0) + sh1 = "[$externalLink1]$sh1" + sh1 = GenericPtg.qualifySheetname(sh1) + return if (addr1 != addr2) "$sh1!$addr1:$addr2" else "$sh1!$addr1" + } + } else if (sh2 == null) { + return if (addr1 != addr2) "$sh1!$addr1:$addr2" else "$sh1!$addr1" + } + sh2 = GenericPtg.qualifySheetname(sh2) + return "$sh1:$sh2!$addr1:$addr2" + } + + override val length: Int + get() = Ptg.PTG_AREA_LENGTH + + /* + returns the sum of all fields within this range + ** this may need to be modified, as we might not always want sum's + *20080730 KSC: Excel does *NOT* sum these values except in array formulas + + TODO: Calculate cell values that are result of formula - take care of + in cell? + From Excel File Format Documentation: + Value class tokens will be changed dependent on further conditions. In array type functions and name type + functions, or if the forced array class state is set, it is changed to array class. In all other cases (cell type formula + without forced array class), value class is retained. + */ + override// 20080214 KSC: underlying cells may have changed ...if(refCell==null) + // 20090203 KSC + //Double d = new Double(s); + // 20090202 KSC: was += + //DblVal(); // 20090202 KSC: was += + // keep going??? + //DblVal(); // 20090202 KSC: was += + // 20080730 KSC: if not an array, retrieve only 1st referenced cell value + // + // 20090817 KSC: [BugTracker 2683] + //new Double(returnval); + val value: Any? + get() { + refCell = this.refCells + var returnval: Any = 0 + var retstr: String? = null + var array: String? = "" + val isArray = this.parentRec is Array + for (t in refCell!!.indices) { + val cel = refCell!![t] ?: continue + + try { + val f = cel.formulaRec + if (f != null) { + val oby = f.calculateFormula() + val s = oby.toString() + try { + returnval = Double(s) + } catch (ex: NumberFormatException) { + retstr = s + } + + } else { + returnval = cel.internalVal + } + } catch (e: FunctionNotSupportedException) { + } catch (e: Exception) { + returnval = cel.internalVal + } + + if (!isArray) + break + if (retstr != null) + array = "$array$retstr," + else + array = "$array$returnval," + retstr = null + } + if (isArray && array != null && array.length > 1) { + array = "{" + array.substring(0, array.length - 1) + "}" + val pa = PtgArray() + pa.setVal(array) + return pa + } + return retstr ?: returnval + + } + + + /** + * @return Returns the refCell. + */ + override// this.parent_rec.getWorkBook().getWorkSheetByName(this.getSheetName()); + // handle misc sheets + // guard against NPEs + // 20080212 KSC + // TODO: check rc sanity here + // loop through the cols + // 20090521 KSC: may have range switched so that firstPtg>lastPtg (example in named ranges in tcr_formatted_2007.xlsm) + // 20090521 KSC: may have range switched so that firstPtg>lastPtg (example in named ranges in tcr_formatted_2007.xlsm) + // 20090521 KSC: try to handle both cases i.e. ranges such that firstlast + /* + for (;startcol<=endcol;startcol++){ // loop the cols + int rowpos = startrow; + for (;rowpos<=endrow;rowpos++){ // loop through the rows + Row r = bs.getRowByNumber(rowpos); + if (r!=null) + refCell[rowctr] = (BiffRec)r.getCell((short)(startcol)); + rowctr++; + } + } + */ val refCells: Array? + get() { + val returnval = 0.0 + try { + var bs: Boundsheet? = null + sheetName + if (sheetname != null) { + try { + bs = this.parentRec.workBook!!.getWorkSheetByName(sheetname) + } catch (ex: Exception) { + bs = parentRec.sheet + } + + } else { + bs = parentRec.sheet + sheetname = bs!!.sheetName + } + val startloc = firstPtg!!.intLocation + var startcol = startloc!![1] + val startrow = startloc[0] + val endloc = lastPtg!!.realIntLocation + var endcol = endloc[1] + var endrow = endloc[0] + var numcols = endcol - startcol + if (numcols < 0) + numcols = startcol - endcol + numcols++ + var numrows = endrow - startrow + if (numrows < 0) + numrows = startrow - endrow + numrows++ + var totcell = numcols * numrows + if (totcell == 0) totcell++ + if (totcell < 0) { + Logger.logErr("PtgArea.getRefCells. Error in Ptg locations: " + firstPtg!!.toString() + ":" + lastPtg!!.toString()) + totcell = 0 + } + refCell = arrayOfNulls(totcell) + var rowctr = 0 + if (startcol < endcol) + endcol++ + else + endcol-- + if (startrow < endrow) + endrow++ + else + endrow-- + while (startcol != endcol) { + var rowpos = startrow + while (rowpos != endrow) { + val r = bs!!.getRowByNumber(rowpos) + if (r != null) + refCell[rowctr] = r.getCell(startcol.toShort()) + rowctr++ + if (rowpos < endrow) + rowpos++ + else + rowpos-- + } + if (startcol < endcol) + startcol++ + else + startcol-- + } + } catch (ex: Exception) { + Logger.logErr("PtgArea.getRefCells failed.", ex) + } + + return refCell + } + + protected override val hashCode: Long + get() = lastPtg!!.hashcode + firstPtg!!.hashcode * (XLSConstants.MAXCOLS.toLong() + XLSConstants.MAXROWS.toLong() * XLSConstants.MAXCOLS) + + /* constructor, takes the array of the ptgRef, including + the identifier so we do not need to figure it out again later... + */ + override fun init(b: ByteArray) { + locax = null // cache reset + opcode = b[0] + record = b + this.populateVals() + } + + /* + Throw this data into two ptgref's + */ + public override fun populateVals() { + val temp1 = ByteArray(5) + val temp2 = ByteArray(5) + temp1[0] = 0x24 + temp2[0] = 0x24 + System.arraycopy(record, 1, temp1, 1, 2) + System.arraycopy(record, 5, temp1, 3, 2) + System.arraycopy(record, 3, temp2, 1, 2) + System.arraycopy(record, 7, temp2, 3, 2) + try { + sheetName // 20080212 KSC: + } catch (we: WorkSheetNotFoundException) { + Logger.logErr(we) + } + + firstPtg = PtgRef(temp1, parentRec, false) // don't add to ref tracker as it's part of area + firstPtg!!.sheetname = sheetname + + lastPtg = PtgRef(temp2, parentRec, false) // don't add to ref tracker as it's part of area + lastPtg!!.sheetname = sheetname + setWholeRowCol() + this.hashcode = hashCode + } + + /** + * returns whether this CellRange Contains a Cell + * + * @param the cell to test + * @return whether the cell is in the range + */ + open operator fun contains(ch: CellHandle): Boolean { + val chsheet = ch.workSheetName + var mysheet = "" + if (this.parentRec != null) { + val b = this.parentRec + if (b.sheet != null) { + mysheet = b.sheet.sheetName + } + } + if (!chsheet.equals(mysheet, ignoreCase = true)) + return false + val adr = ch.cellAddress + // FIX broken COLROW + val rc = ExcelTools.getRowColFromString(adr) + return contains(rc) + } + + /** + * check to see if the sheet and row/col are contained + * in this ref + * + * @param sheetname + * @param rc + * @return + */ + fun contains(sn: String, rc: IntArray): Boolean { + if (sheetname == null) { + try { + sheetname = this.sheetName + } catch (e: Exception) { + } + + } + return if (!sn.equals(sheetname!!, ignoreCase = true)) false else contains(rc) + } + + /** + * returns whether this PtgArea Contains the specified row/col coordinate + * + * + * + * + * [0] = firstrow + * [1] = firstcol + * [2] = lastrow + * [3] = lastcol + * + * @param the rc coordinates to test + * @return whether the coordinates are in the range + */ + operator fun contains(rc: IntArray): Boolean { + val thisRange = this.intLocation + // test the first rc + if (rc[0] < thisRange!![0]) return false // row above the first ref row? + if (rc[0] > thisRange[2]) return false // row after the last ref row? + + return if (rc[1] < thisRange[1]) false else rc[1] <= thisRange[3] // col before the first ref col? +// col after the last ref col? + } + + /** + * Creates a new PtgArea from 2 component ptgs, used by shared formula + * to create ptgareas. ptg1 should be upperleft corner, ptg2 bottomright + */ + constructor(ptg1: PtgRef, ptg2: PtgRef, parent: XLSRecord) : this() { + firstPtg = ptg1 + lastPtg = ptg2 + parentRec = parent + this.hashcode = hashCode + this.updateRecord() + } + + /* + * Creates a new PtgArea. The parent rec is needed + * as getting a value goes to the boundsheet to determine + * values. The parent rec *must* be on the same sheet + * as the PtgArea referenced! + * + * relativeRefs = true is excel default + */ + @JvmOverloads + constructor(range: String, parent: XLSRecord, relativeRefs: Boolean = true) : this() { + val loc = ExcelTools.getRangeRowCol(range) + val temp = IntArray(2) + temp[0] = loc[0] + temp[1] = loc[1] + var res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs) + firstPtg = PtgRef(res, parent, false) + temp[0] = loc[2] + temp[1] = loc[3] + res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs) + lastPtg = PtgRef(res, parent, false) + setWholeRowCol() + parentRec = parent + this.hashcode = hashCode + this.updateRecord() + } + + /* + * Creates a new PtgArea using an int array as [r,c,r1,c1]. + * The parent rec is needed + * as getting a value goes to the boundsheet to determine + * values. The parent rec *must* be on the same sheet + * as the PtgArea referenced! + * + * relativeRefs = true is excel default + */ + constructor(loc: IntArray, parent: XLSRecord, relativeRefs: Boolean) { + val temp = IntArray(2) + temp[0] = loc[0] + temp[1] = loc[1] + var res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs) + firstPtg = PtgRef(res, parent, false) + temp[0] = loc[2] + temp[1] = loc[3] + res = ExcelTools.formatLocation(temp, relativeRefs, relativeRefs) + lastPtg = PtgRef(res, parent, false) + setWholeRowCol() + parentRec = parent + this.hashcode = hashCode + this.updateRecord() + } + + /** + * set the wholeRow and/or wholeCol flag for this PtgArea + * for ranges such as: + * $B:$B and $5:%9 + */ + fun setWholeRowCol() { + if (firstPtg!!.rw <= 1 && lastPtg!!.isWholeCol) + // TODO: inconsistencies in 0-based or 1-based rows + this.isWholeCol = true + this.isWholeRow = lastPtg!!.isWholeRow + if (this.isWholeCol) + useReferenceTracker = false + } + + /* + * Default constructor + */ + constructor() { + record = ByteArray(9) + opcode = 0x25 + record[0] = 0x25 + } + + constructor(useReferenceTracker: Boolean) : this() { + this.useReferenceTracker = useReferenceTracker + } + + /** + * set the Ptg Id type to one of: + * VALUE, REFERENCE or Array + * 25H (tAreaR), 45H (tAreaV), 65H (tAreaA) + *

                      The Ptg type is important for certain + * functions which require a specific type of operand + */ + override fun setPtgType(type: Short) { + when (type) { + Ptg.VALUE -> opcode = 0x45 + Ptg.REFERENCE -> opcode = 0x25 + Ptg.ARRAY -> opcode = 0x65 + } + record[0] = opcode + } + + + override fun toString(): String? { + var ret = string + + if (parentRec != null) + if (ret!!.indexOf("!") < 0) + try { // Catch WorkSheetNotFoundException to handle Unresolved External refs + sheetName + if (sheetname != null) + ret = "$sheetname!$ret" + } catch (we: WorkSheetNotFoundException) { + Logger.logErr(we) + } + + return ret + } + + /** + * set Ptg to parsed location + * + * @param loc String[] sheet1, range, sheet2, exref1, exref2 + */ + override fun setLocation(loc: Array) { + locax = null // cache reset + if (firstPtg == null) { + this.record = byteArrayOf(0x25, 0, 0, 0, 0, 0, 0, 0, 0) + + if (this.parentRec != null) this.populateVals() + } else if (this.useReferenceTracker) + this.removeFromRefTracker() + var i = loc[1].indexOf(":") + // handle single cell addresses as: A1:A1 + if (i == -1) { + loc[1] = loc[1] + ":" + loc[1] + i = loc[1].indexOf(":") + } + var firstloc = loc[1].substring(0, i) + var lastloc = loc[1].substring(i + 1) + if (loc[0] != null) + firstloc = loc[0] + "!" + firstloc + if (loc[2] != null) + lastloc = loc[2] + "!" + lastloc + if (loc[3] != null) + // 20090325 KSC: store OOXML External References + firstloc = loc[3] + firstloc + if (loc[4] != null) + // 20090325 KSC: store OOXML External References + lastloc = loc[4] + lastloc + + // TODO: do we need to remove refs from tracker? + firstPtg!!.parentRec = this.parentRec + lastPtg!!.parentRec = this.parentRec + + firstPtg!!.useReferenceTracker = false + lastPtg!!.useReferenceTracker = false + firstPtg!!.location = firstloc + lastPtg!!.location = lastloc + setWholeRowCol() + this.hashcode = hashCode + this.updateRecord() + if (this.useReferenceTracker) {// check of boolean useReferenceTracker + if (!this.isWholeCol && !this.isWholeRow) + this.addToRefTracker() + else + useReferenceTracker = false + } + } + + + /** + * Set the location of this PtgArea. This takes a location + * such as {1,2,3,4} + */ + override fun setLocation(rowcol: IntArray) { + locax = null // cache reset + if (firstPtg == null) { + //this.record = new byte[] {0x25, 0, 0, 0, 0, 0, 0, 0, 0}; -- don't as can be called from PtgArea3d + if (this.parentRec != null) this.populateVals() + } else if (this.useReferenceTracker) + this.removeFromRefTracker() + + // TODO: do we need to remove refs from tracker? + firstPtg!!.parentRec = this.parentRec + firstPtg!!.sheetName = sheetname + lastPtg!!.parentRec = this.parentRec + lastPtg!!.sheetName = sheetname + + firstPtg!!.useReferenceTracker = false + lastPtg!!.useReferenceTracker = false + firstPtg!!.setLocation(rowcol) + val rc = IntArray(2) + rc[0] = rowcol[2] + rc[1] = rowcol[3] + lastPtg!!.setLocation(rc) + + this.hashcode = hashCode + this.updateRecord() + if (this.useReferenceTracker) + // check of boolean useReferenceTracker + this.addToRefTracker() + } + + /* Updates the record bytes so it can be pulled back out. + */ + override fun updateRecord() { + locax = null // cache reset + val pols = intArrayOf(firstPtg!!.locationPolicy, lastPtg!!.locationPolicy) + val first = firstPtg!!.record + val last = lastPtg!!.record + // the last record has an extra identifier on it. + val newrecord = ByteArray(9) + newrecord[0] = record[0] + System.arraycopy(first, 1, newrecord, 1, 2) + System.arraycopy(last, 1, newrecord, 3, 2) + System.arraycopy(first, 3, newrecord, 5, 2) + System.arraycopy(last, 3, newrecord, 7, 2) + record = newrecord + // this.populateVals(); + if (parentRec != null) { + if (this.parentRec is Formula) + (this.parentRec as Formula).updateRecord() + else if (this.parentRec is Name) + (this.parentRec as Name).updatePtgs() + } + firstPtg!!.locationPolicy = pols[0] + lastPtg!!.locationPolicy = pols[1] + } + + + /* + Returns all of the cells of this range as PtgRef's. + This includes empty cells, values, formulas, etc. + Note the setting of parent-rec requires finding the cell + the PtgRef refer's to. If that is null then the PtgRef + will exist, just with a null value. This could cause issues when + programatically populating cells. + */ + fun getColComponents(colNum: Int): Array? { + if (colNum < 0) return null + val lu = this.toString() + val p = parentRec.workBook!!.refTracker!!.vlookups[lu] + + if (p != null) { + val par = p as PtgArea? + val ret = par!!.parentRec.workBook!! + .refTracker!!.lookupColCache[lu + ":" + Integer.valueOf(colNum)] as Array + if (ret != null) + return ret + } + + var v: Array? = null + try { + // TODO: check rc sanity here + val startloc = firstPtg!!.realIntLocation + val startrow = startloc[0] + val endloc = lastPtg!!.realIntLocation + var endrow = endloc[0] + // error trap + if (endrow < startrow) + // can happen if wholerow/wholecol, getMaxRow may be less than startRow + endrow = startrow + var sz = endrow - startrow + sz++ + v = arrayOfNulls(sz) + // loop through the cols + // loop through the rows inside + var rowholder = startrow + var pos = 0 + var sht = this.toString() + if (sht!!.indexOf("!") > -1) sht = sht.substring(0, sht.indexOf("!")) + while (rowholder <= endrow) { + val displaycol = ExcelTools.getAlphaVal(colNum) + val displayrow = rowholder + 1 + val loc = "$sht!$displaycol$displayrow" + + val pref = PtgRef(loc, parentRec, this.useReferenceTracker) + + v[pos++] = pref + rowholder++ + } + } catch (e: Exception) { + Logger.logErr("Getting column range in PtgArea failed.", e) + } + + // cache + parentRec.workBook!!.refTracker!!.vlookups[this.toString()] = this + parentRec.workBook!!.refTracker!!.lookupColCache[lu + ":" + Integer.valueOf(colNum)] = v + return v + } + + /** + * return the ptg components for a certain column within a ptgArea() + * + * @param rowNum + * @return all Ptg's within colNum + */ + fun getRowComponents(rowNum: Int): Array { + val v = FastAddVector() + val allComponents = this.components + for (i in allComponents!!.indices) { + val p = allComponents[i] as PtgRef + // TODO: check rc sanity here + val x = p.realIntLocation + if (x[0] == rowNum) v.add(p) + } + val pref = arrayOfNulls(v.size) + v.toTypedArray() + return pref + } + + /** + * @return + */ + fun getFirstPtg(): PtgRef? { + return firstPtg + } + + /** + * @return + */ + fun getLastPtg(): PtgRef? { + return lastPtg + } + + /** + * @param ref + */ + fun setFirstPtg(ref: PtgRef) { + locax = null // cache reset + firstPtg = ref + } + + /** + * @param ref + */ + fun setLastPtg(ref: PtgRef) { + locax = null // cache reset + lastPtg = ref + } + + companion object { + + val serialVersionUID = 666555444333222L + } +}/* + * Creates a new PtgArea. The parent rec is needed + * as getting a value goes to the boundsheet to determine + * values. The parent rec *must* be on the same sheet + * as the PtgArea referenced! + */ \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgArea3d.java b/src/main/java/io/starter/formats/XLS/formulas/PtgArea3d.java deleted file mode 100644 index 11abd47..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgArea3d.java +++ /dev/null @@ -1,737 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.CellHandle; -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; - - -/** - * ptgArea3d is a reference to an area (rectangle) of cells. - * Essentially it is a collection of two ptgRef's, so it will be - * treated that way in the code... - * implies external sheet ref (rather than ptgarea) - * - *
                      - * Offset      Name        Size    Contents
                      - * ----------------------------------------------------
                      - * 0           ixti        2       index into the Externsheet record
                      - * 2           rwFirst     2       The First row of the reference
                      - * 4           rwLast      2       The Last row of the reference
                      - * 6           grbitColFirst   2       (see following table)
                      - * 8           grbitColLast    2       (see following table)
                      - *
                      - * Only the low-order 14 bits specify the Col, the other bits specify
                      - * relative vs absolute for both the col or the row.
                      - *
                      - * Bits        Mask        Name    Contents
                      - * -----------------------------------------------------
                      - * 15          8000h       fRwRel  =1 if row offset relative,
                      - * =0 if otherwise
                      - * 14          4000h       fColRel =1 if row offset relative,
                      - * =0 if otherwise
                      - * 13-0        3FFFh       col     Ordinal column offset or number
                      - * 
                      - *

                      - * For 3D references, the tokens contain a negative EXTERNSHEET index, indicating a reference into the own workbook. - * The absolute value is the one-based index of the EXTERNSHEET record that contains the name of the first sheet. The - * tokens additionally contain absolute indexes of the first and last referenced sheet. These indexes are independent of the - * EXTERNSHEET record list. If the referenced sheets do not exist anymore, these indexes contain the value FFFFH (3D - * reference to a deleted sheet), and an EXTERNSHEET record with the special name <04H> (own document) is used. - * Each external reference contains the positive one-based index to an EXTERNSHEET record containing the URL of the - * external document and the name of the sheet used. The sheet index fields of the tokens are not used. - *

                      - * is the above correct?? Documentation sez different!!!!! - * - * @see Ptg - * @see Formula - */ -public class PtgArea3d extends PtgArea implements Ptg, IxtiListener { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1176168076050592292L; - - public boolean getIsOperand() { - return true; - } - - public boolean getIsReference() { - return true; - } - - boolean quoted = false; - public short ixti; - private boolean isExternalRef = false; // true if this ptg area is a reference in another workbook - private Ptg[] comps = null; - - /** - * return the human-readable String representation of - * this ptg -- if applicable - */ - public String getString() { - try { - if (this.getIsWholeCol() || this.getIsWholeRow()) { // handle non-standard ranges i.e. $B:$C or $1:$3 - String s = firstPtg.getLocation(); - String y = lastPtg.getLocation(); - - String[] loc1 = ExcelTools.stripSheetNameFromRange(s); // sheet, addr - String[] loc2 = ExcelTools.stripSheetNameFromRange(y); // sheet, addr - if (this.getIsWholeCol()) { - int i = loc1[1].length(); - if (Character.isDigit(loc1[1].charAt(i - 1))) - while (Character.isDigit(loc1[1].charAt(--i))) ; - loc1[1] = loc1[1].substring(0, i); - i = loc2[1].length(); - if (Character.isDigit(loc2[1].charAt(i - 1))) - while (Character.isDigit(loc2[1].charAt(--i))) ; - loc2[1] = loc2[1].substring(0, i); - } else if (this.getIsWholeRow()) { - int i = 0; - while (!Character.isDigit(loc1[1].charAt(i++))) ; - loc1[1] = "$" + loc1[1].substring(i - 1); - i = 0; - while (!Character.isDigit(loc2[1].charAt(i++))) ; - loc2[1] = "$" + loc2[1].substring(i - 1); - } - sheetname = qualifySheetname(sheetname); - - return sheetname + "!" + loc1[1] + ":" + loc2[1]; - } - // otherwise, - return getLocation(); - } catch (Exception e) { - // is ok if new ptg... - return null; - } - } - - /** - * link to the externsheet to be automatically updated upon removals - */ - public void addListener() { - try { - getParentRec().getWorkBook().getExternSheet().addPtgListener(this); - } catch (Exception e) { - // no need to output here. NullPointer occurs when a ref has an invalid ixti, such as when a sheet was removed Worksheet exception could never really happen. - } - } - - public String toString() { - String ret = getString(); - return ret; - } - - /** - * create new PtgArea3d - */ - public PtgArea3d() { - ptgId = 0x3b; - record = new byte[11]; - record[0] = ptgId; // "" - this.is3dRef = true; - } - - public PtgArea3d(boolean useReferenceTracker) { - this(); - this.useReferenceTracker = useReferenceTracker; - } - - /** - * set the Ptg Id type to one of: - * VALUE, REFERENCE or Array - * 25H (tAreaR), 45H (tAreaV), 65H (tAreaA) - *
                      The Ptg type is important for certain - * functions which require a specific type of operand - */ - public void setPtgType(short type) { - switch (type) { - case VALUE: - ptgId = 0x5B; - break; - case REFERENCE: - ptgId = 0x3B; - break; - case Ptg.ARRAY: - ptgId = 0x7B; - break; - } - record[0] = ptgId; - } - - - /** - * @return Returns the ixti. - */ - public short getIxti() { // only valid for 3d refs!!!! - return ixti; - } - - - /** - * set the pointer into the Externsheet Rec. - * this is only valid for 3d refs - */ - public void setIxti(short ixf) { - if (ixti != ixf) { - ixti = ixf; - // this seems to be only one byte... - if (record != null) { - record[1] = (byte) ixf; - populateVals(); // add listener is done here - } - } - } - - /** - * return true if this PtgArea3d is an external reference - * i.e. defined in another, external workbook - * - * @return - */ - public boolean isExternalRef() { - return isExternalRef; - } - - /** - * return the first sheet referenced - * - * @return - */ - public Boundsheet getFirstSheet() { - if (parent_rec != null) { - WorkBook wb = parent_rec.getWorkBook(); - if (sheetname != null) - try { - return wb.getWorkSheetByName(sheetname); - } catch (WorkSheetNotFoundException e) { - // fall thru -- see sheet copy operations -- appears correct - } - if (wb != null && wb.getExternSheet() != null) { - Boundsheet[] bsa = wb.getExternSheet().getBoundSheets(this.ixti); - if (bsa != null) - return bsa[0]; - } - } - return null; - } - - /** - * return the last sheet referenced - * - * @return - */ - public Boundsheet getLastSheet() { - if (parent_rec != null) { - WorkBook wb = parent_rec.getWorkBook(); - if (wb != null && wb.getExternSheet() != null) { - Boundsheet[] bsa = wb.getExternSheet().getBoundSheets(this.ixti); - if (bsa != null) { - if (bsa.length > 1) - return bsa[bsa.length - 1]; - return bsa[0]; - } - - } - } - return null; - } - - public void setParentRec(XLSRecord rec) { - super.setParentRec(rec); - if (firstPtg != null) - firstPtg.setParentRec(parent_rec); - if (lastPtg != null) - lastPtg.setParentRec(parent_rec); - } - - /** - * return the first sheet referenced - * - * @return - */ - public Boundsheet getSheet() { - return getFirstSheet(); - } - - /** - * get the sheet name from the 1st 3d reference - */ - public String getSheetName() { - if (sheetname == null) { - if (parent_rec != null) { - WorkBook wb = parent_rec.getWorkBook(); - if (wb != null && wb.getExternSheet() != null) { - String[] sheets = wb.getExternSheet().getBoundSheetNames(this.ixti); - if (sheets != null && sheets.length > 0) { - sheetname = sheets[0]; - sheetname = qualifySheetname(sheetname); - } - } - - if (sheetname == null && parent_rec != null && parent_rec.getSheet() != null) { // try this: - sheetname = parent_rec.getSheet().getSheetName(); - sheetname = qualifySheetname(sheetname); - } - } - } - return sheetname; - } - - /** - * return the name of the last sheet referenced if it's an external ref - * - * @return - */ - public String getLastSheetName() { - String sheetname = this.sheetname; // 20100217 KSC: default to 1st sheet - if (parent_rec != null) { - WorkBook wb = parent_rec.getWorkBook(); - if (wb != null && wb.getExternSheet() != null) { - String[] sheets = wb.getExternSheet().getBoundSheetNames(this.ixti); - if (sheets != null && sheets.length > 0) - sheetname = sheets[sheets.length - 1]; - } - } - return sheetname; - } - - - /** - * get the worksheet that this ref is on - */ - public Boundsheet[] getSheets(WorkBook b) { - Boundsheet[] bsa = b.getExternSheet().getBoundSheets(this.ixti); - if (bsa[0] == null) // 20080303 KSC: Catch Unresolved External refs - Logger.logErr("PtgArea3d.getSheet: Unresolved External Worksheet"); - return bsa; - } - - - /** - * constructor, takes the array of the ptgRef, including - * the identifier so we do not need to figure it out again later... - * also takes the parent rec -- needed to init the sub-ptgs - * - * @param b - * @param parent - */ - public void init(byte[] b, XLSRecord parent) { - ixti = ByteTools.readShort(b[1], b[2]); - record = b; - this.setParentRec(parent); - populateVals(); - } - - /** - * Throw this data into two ptgref's - */ - public void populateVals() { - byte[] temp1 = new byte[7]; // PtgRef3d is 7 bytes - byte[] temp2 = new byte[7]; - // Encoded Cell Range Address: - // 0-2= first row - // 2-4= last row - // 4-6= first col - // 6-8= last col - // Encoded Cell Address: - // 0-2= row index - // 2-4= col index + relative flags - try { - temp1[0] = 0x3a; - temp1[1] = record[1]; // ixti - temp1[2] = record[2]; // "" - temp1[3] = record[3]; // first row - temp1[4] = record[4]; // "" - temp1[5] = record[7]; // first col - temp1[6] = record[8]; // "" - - temp2[0] = 0x3a; - temp2[1] = record[1]; // ixti - temp2[2] = record[2]; // "" - temp2[3] = record[5]; // last row - temp2[4] = record[6]; // "" - temp2[5] = record[9]; // last col - temp2[6] = record[10]; // "" - } catch (Exception e) { - //should never happen! - return; - } - // pass in parent_rec so can properly set formulaRow/formulaCol - firstPtg = new PtgRef3d(false); - - // the following method registers the Ptg with the ReferenceTracker - firstPtg.setParentRec(parent_rec); - firstPtg.setSheetName(this.getSheetName()); - firstPtg.init(temp1); - - lastPtg = new PtgRef3d(false); - lastPtg.setParentRec(parent_rec); - lastPtg.setSheetName(this.getLastSheetName()); - lastPtg.init(temp2); - // flag if it's an external reference - - isExternalRef = (((PtgRef3d) firstPtg).isExternalLink() || ((PtgRef3d) lastPtg).isExternalLink()); - - setWholeRowCol(); - - // take 1st Ptg as sample for relative state - this.fColRel = firstPtg.isColRel(); - this.fRwRel = firstPtg.isRowRel(); - //init sets formula row to 1st row for a shared formula; adjust here - if (parent_rec != null && parent_rec instanceof Shrfmla) { - lastPtg.formulaRow = ((Shrfmla) parent_rec).getLastRow(); - lastPtg.formulaCol = ((Shrfmla) parent_rec).getLastCol(); - } - this.hashcode = super.getHashCode(); - } - - /** - * Set the location of this PtgRef. This takes a location - * such as "a14:b15" - *

                      - * NOTE: the reference stays on the same sheet! - */ - public void setLocation(String address) { - String[] s = ExcelTools.stripSheetNameFromRange(address); - setLocation(s); - } - - - /** - * set Ptg Location to parsed location - * - * @param loc String[] sheet1, range, sheet2, exref1, exref2 - */ - public void setLocation(String[] s) { - try { - if (useReferenceTracker && locax != null) // if in tracker already, remove - this.removeFromRefTracker(); - } catch (Exception e) { - // will happen if this is not in tracker yet - } - String sheetname2 = null; - String range = ""; - range = s[1]; - if (s[0] != null) { // has a sheet in the address - sheetname = s[0]; - sheetname2 = s[2]; - if (sheetname2 == null) - sheetname2 = sheetname; - // revised so can set ixti on error'd references - WorkBook b = null; - Externsheet xsht = null; - if (parent_rec != null) { - b = parent_rec.getWorkBook(); - if (b == null) - b = parent_rec.getSheet().getWorkBook(); - } - try { - xsht = b.getExternSheet(); - int boundnum = b.getWorkSheetByName(sheetname).getSheetNum(); - int boundnum2 = boundnum; // it could possibly be a 3d ref - check - if (!sheetname.equals(sheetname2) && sheetname2 != null) - boundnum2 = b.getWorkSheetByName(sheetname2).getSheetNum(); - this.setIxti((short) xsht.insertLocation(boundnum, boundnum2)); - } catch (WorkSheetNotFoundException e) { - try { - // try to link to external sheet, if possible - int boundnum = xsht.getXtiReference(s[0], s[0]); - if (boundnum == -1) { // can't resolve - this.setIxti((short) xsht.insertLocation(boundnum, boundnum)); - } else { - this.setIxti((short) boundnum); - this.isExternalRef = true; - } - } catch (Exception ex) { - } - } - } else if (parent_rec != null) { - sheetname = sheetname2 = parent_rec.getSheet().getSheetName(); // use parent rec's sheet - } - int i = range.indexOf(":"); - if (i < 0) { - range = range + ":" + range; - i = range.indexOf(":"); - } - - String firstcell = range.substring(0, i); - String lastcell = range.substring(i + 1); - if (sheetname != null) - firstcell = sheetname + "!" + firstcell; - if (sheetname2 != null) - lastcell = sheetname2 + "!" + lastcell; - if (s[3] != null) // store OOXML External References - firstcell = s[3] + firstcell; - if (s[4] != null) - lastcell = s[4] + lastcell; - - if (firstPtg == null) { - firstPtg = new PtgRef3d(false); - firstPtg.setParentRec(this.getParentRec()); - } - firstPtg.sheetname = sheetname; - firstPtg.setLocation(firstcell); - ((PtgRef3d) firstPtg).setIxti(this.ixti); - - if (lastPtg == null) { - lastPtg = new PtgRef3d(false); - lastPtg.setParentRec(this.getParentRec()); - } - lastPtg.sheetname = sheetname2; - lastPtg.setLocation(lastcell); - ((PtgRef3d) lastPtg).setIxti(this.ixti); - - this.setWholeRowCol(); - this.updateRecord(); - // TODO: must deal with non-symmetrical absolute i.e. if first and last ptgs don't match - this.fRwRel = firstPtg.fRwRel; - this.fColRel = firstPtg.fColRel; - hashcode = getHashCode(); - if (useReferenceTracker) { - if (!this.getIsWholeCol() && !this.getIsWholeRow()) - this.addToRefTracker(); - else - useReferenceTracker = false; - } - } - - - /** - * returns the location of the ptg as an array of shorts. - * [0] = firstrow - * [1] = firstcol - * [2] = lastrow - * [3] = lastcol - */ - public int[] getIntLocation() { - int[] first = firstPtg.getIntLocation(); - int[] last = lastPtg.getIntLocation(); - int[] returning = new int[4]; - System.arraycopy(first, 0, returning, 0, 2); - System.arraycopy(last, 0, returning, 2, 2); - return returning; - } - - - /** - * returns whether this CellRange Contains a Cell - * - * @param the cell to test - * @return whether the cell is in the range - */ - public boolean contains(CellHandle ch) { - String chsheet = ch.getWorkSheetName(); - getSheetName(); - if (!chsheet.equalsIgnoreCase(sheetname)) return false; - String adr = ch.getCellAddress(); -// FIX broken COLROW - int[] rc = ExcelTools.getRowColFromString(adr); - return contains(rc); - } - - /** - * Switches the two internal ptgref3ds to a new - * sheet. - */ - public void setReferencedSheet(Boundsheet b) { - ((PtgRef3d) firstPtg).setReferencedSheet(b); - ((PtgRef3d) lastPtg).setReferencedSheet(b); - int boundnum = b.getSheetNum(); - Externsheet xsht = b.getWorkBook().getExternSheet(true); - //TODO: add handling for multi-sheet reference. Already handled in externsheet - try { - this.sheetname = null; // 20100218 KSC: RESET - int xloc = xsht.insertLocation(boundnum, boundnum); - setIxti((short) xloc); - } catch (WorkSheetNotFoundException e) { - Logger.logErr("Unable to set referenced sheet in PtgRef3d " + e); - } - } - - /** - * return all of the Ptg values represented in this array - *

                      - * will have to reference the workbook cells as well as - * any upstream formulas... - * - * @return - */ - public Object[] getAllVals() { - - return null; - } - - /** - * Updates the record bytes so it can be pulled back out. - */ - public void updateRecord() { - comps = null; - byte[] first = firstPtg.getRecord(); - byte[] last = lastPtg.getRecord(); - // KSC: this apparently is what excel wants: - if (wholeRow) - first[5] = 0; - if (wholeCol) { - first[3] = 0; - first[4] = 0; - } - // the last record has an extra identifier on it. - byte[] newrecord = new byte[PTG_AREA3D_LENGTH]; - newrecord[0] = 0x3B; - System.arraycopy(first, 1, newrecord, 1, 2); - System.arraycopy(first, 3, newrecord, 3, 2); - System.arraycopy(last, 3, newrecord, 5, 2); - System.arraycopy(first, 5, newrecord, 7, 2); - System.arraycopy(last, 5, newrecord, 9, 2); - record = newrecord; - if (parent_rec != null) { - if (this.parent_rec instanceof Formula) - ((Formula) this.parent_rec).updateRecord(); - else if (this.parent_rec instanceof Name) - ((Name) this.parent_rec).updatePtgs(); - } - } - - public int getLength() { - return PTG_AREA3D_LENGTH; - } - - - /** - * Returns all of the cells of this range as PtgRef's. - * This includes empty cells, values, formulas, etc. - * Note the setting of parent-rec requires finding the cell - * the PtgRef refer's to. If that is null then the PtgRef - * will exist, just with a null value. This could cause issues when - * programatically populating cells. - */ - public Ptg[] getComponents() { - if (comps != null) - return comps; - - ArrayList components = new ArrayList(); - try { - // loop through the cols - String sht = ""; - if (this.toString().indexOf("!") > -1) { - sht = this.toString(); - sht = sht.substring(0, sht.indexOf("!")) + "!"; - } - int startrow = 0, startcol = 0, endrow = 0, endcol = 0; - if (!this.wholeCol && !this.wholeRow) { // normal case -// TODO: check rc sanity here - int[] startloc = firstPtg.getRealIntLocation(); // Get Actual Coordinates - startcol = startloc[1]; - startrow = startloc[0]; - int[] endloc = lastPtg.getRealIntLocation(); // Get Actual Coordinates - endcol = endloc[1]; - endrow = endloc[0]; - } else if (this.wholeRow) { // like $1:$1 - startcol = 0; - try { - endcol = this.getSheet().getMaxCol(); - } catch (NullPointerException ne) { // can happens when Name record is being init'd and sheet records are not set yet - return null; - } - startrow = endrow = firstPtg.rw; - } else if (this.wholeCol) { // like $J:$J - startrow = 0; // Get Actual Coordinates - startcol = endcol = firstPtg.col; - try { - endrow = this.getSheet().getMaxRow(); - } catch (NullPointerException ne) { // can happens when Name record is being init'd and sheet records are not set yet - return null; - } - } - for (; startcol <= endcol; startcol++) { - // loop through the rows inside - int rowholder = startrow; - for (; rowholder <= endrow; rowholder++) { - String displaycol = ExcelTools.getAlphaVal(startcol); - int displayrow = rowholder + 1; - String loc = sht + displaycol + displayrow; - - // cache these suckers! - Ptg pref = new PtgRef3d(false); - pref.setParentRec(parent_rec); // must set parentrec before setLocation - pref.setLocation(loc); - components.add(pref); - } - } - } catch (Exception e) { - Logger.logErr("calculating range value in PtgArea3d failed.", e); - } - PtgRef[] pref = new PtgRef[components.size()]; - components.toArray(pref); - comps = pref; - return comps; - } - - - /** - * sets the column to be relative (relative is true) or absolute (relative is false) - *
                      absolute references do not shift upon column inserts or deletes - *
                      NOTE: DOES NOT handle asymmetrical ranges i.e. 1st ref is absolute, 2nd is relative - * - * @param boolean relative - */ - public void setColRel(boolean relative) { - this.fColRel = relative; - firstPtg.setColRel(relative); - lastPtg.setColRel(relative); - updateRecord(); - } - - /** - * sets the row to be relative (relative is true) or absolute (relative is false) - *
                      absolute references do not shift upon row inserts or deletes - *
                      NOTE: DOES NOT handle asymmetrical ranges i.e. 1st ref is absolute, 2nd is relative - * - * @param boolean relative - */ - public void setRowRel(boolean relative) { - if (this.fRwRel != relative) { - this.fRwRel = relative; - firstPtg.setRowRel(relative); - lastPtg.setRowRel(relative); - updateRecord(); - } - } - - public void close() { - super.close(); - if (comps != null) - for (int i = 0; i < comps.length; i++) { - comps[i].close(); - comps[i] = null; - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgArea3d.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgArea3d.kt new file mode 100644 index 0000000..6ac58ee --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgArea3d.kt @@ -0,0 +1,780 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.CellHandle +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.ArrayList + + +/** + * ptgArea3d is a reference to an area (rectangle) of cells. + * Essentially it is a collection of two ptgRef's, so it will be + * treated that way in the code... + * implies external sheet ref (rather than ptgarea) + * + *

                      + * Offset      Name        Size    Contents
                      + * ----------------------------------------------------
                      + * 0           ixti        2       index into the Externsheet record
                      + * 2           rwFirst     2       The First row of the reference
                      + * 4           rwLast      2       The Last row of the reference
                      + * 6           grbitColFirst   2       (see following table)
                      + * 8           grbitColLast    2       (see following table)
                      + *
                      + * Only the low-order 14 bits specify the Col, the other bits specify
                      + * relative vs absolute for both the col or the row.
                      + *
                      + * Bits        Mask        Name    Contents
                      + * -----------------------------------------------------
                      + * 15          8000h       fRwRel  =1 if row offset relative,
                      + * =0 if otherwise
                      + * 14          4000h       fColRel =1 if row offset relative,
                      + * =0 if otherwise
                      + * 13-0        3FFFh       col     Ordinal column offset or number
                      +
                      * + * + * + * For 3D references, the tokens contain a negative EXTERNSHEET index, indicating a reference into the own workbook. + * The absolute value is the one-based index of the EXTERNSHEET record that contains the name of the first sheet. The + * tokens additionally contain absolute indexes of the first and last referenced sheet. These indexes are independent of the + * EXTERNSHEET record list. If the referenced sheets do not exist anymore, these indexes contain the value FFFFH (3D + * reference to a deleted sheet), and an EXTERNSHEET record with the special name <04H> (own document) is used. + * Each external reference contains the positive one-based index to an EXTERNSHEET record containing the URL of the + * external document and the name of the sheet used. The sheet index fields of the tokens are not used. + * + * + * is the above correct?? Documentation sez different!!!!! + * + * @see Ptg + * + * @see Formula + */ +open class PtgArea3d +/** + * create new PtgArea3d + */ +() : PtgArea(), Ptg, IxtiListener { + + override val isOperand: Boolean + get() = true + + override val isReference: Boolean + get() = true + + internal var quoted = false + var ixti: Short = 0 + /** + * return true if this PtgArea3d is an external reference + * i.e. defined in another, external workbook + * + * @return + */ + var isExternalRef = false + private set // true if this ptg area is a reference in another workbook + private var comps: Array? = null + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + override// handle non-standard ranges i.e. $B:$C or $1:$3 + // sheet, addr + // sheet, addr + // otherwise, + // is ok if new ptg... + val string: String? + get() { + try { + if (this.isWholeCol || this.isWholeRow) { + val s = firstPtg!!.location + val y = lastPtg!!.location + + val loc1 = ExcelTools.stripSheetNameFromRange(s!!) + val loc2 = ExcelTools.stripSheetNameFromRange(y!!) + if (this.isWholeCol) { + var i = loc1[1].length + if (Character.isDigit(loc1[1][i - 1])) + while (Character.isDigit(loc1[1][--i])); + loc1[1] = loc1[1].substring(0, i) + i = loc2[1].length + if (Character.isDigit(loc2[1][i - 1])) + while (Character.isDigit(loc2[1][--i])); + loc2[1] = loc2[1].substring(0, i) + } else if (this.isWholeRow) { + var i = 0 + while (!Character.isDigit(loc1[1][i++])); + loc1[1] = "$" + loc1[1].substring(i - 1) + i = 0 + while (!Character.isDigit(loc2[1][i++])); + loc2[1] = "$" + loc2[1].substring(i - 1) + } + sheetname = GenericPtg.qualifySheetname(sheetname) + + return sheetname + "!" + loc1[1] + ":" + loc2[1] + } + return location + } catch (e: Exception) { + return null + } + + } + + /** + * return the first sheet referenced + * + * @return + */ + // fall thru -- see sheet copy operations -- appears correct + val firstSheet: Boundsheet? + get() { + if (parentRec != null) { + val wb = parentRec.workBook + if (sheetname != null) + try { + return wb!!.getWorkSheetByName(sheetname) + } catch (e: WorkSheetNotFoundException) { + } + + if (wb != null && wb.externSheet != null) { + val bsa = wb.externSheet!!.getBoundSheets(this.ixti.toInt()) + if (bsa != null) + return bsa[0] + } + } + return null + } + + /** + * return the last sheet referenced + * + * @return + */ + val lastSheet: Boundsheet? + get() { + if (parentRec != null) { + val wb = parentRec.workBook + if (wb != null && wb.externSheet != null) { + val bsa = wb.externSheet!!.getBoundSheets(this.ixti.toInt()) + if (bsa != null) { + return if (bsa.size > 1) bsa[bsa.size - 1] else bsa[0] + } + + } + } + return null + } + + override var parentRec: XLSRecord + get + set(rec) { + super.parentRec = rec + if (firstPtg != null) + firstPtg!!.parentRec = parentRec + if (lastPtg != null) + lastPtg!!.parentRec = parentRec + } + + /** + * return the first sheet referenced + * + * @return + */ + val sheet: Boundsheet? + get() = firstSheet + + /** + * get the sheet name from the 1st 3d reference + */ + override// try this: + var sheetName: String? + get() { + if (sheetname == null) { + if (parentRec != null) { + val wb = parentRec.workBook + if (wb != null && wb.externSheet != null) { + val sheets = wb.externSheet!!.getBoundSheetNames(this.ixti.toInt()) + if (sheets != null && sheets.size > 0) { + sheetname = sheets[0] + sheetname = GenericPtg.qualifySheetname(sheetname) + } + } + + if (sheetname == null && parentRec != null && parentRec.sheet != null) { + sheetname = parentRec.sheet!!.sheetName + sheetname = GenericPtg.qualifySheetname(sheetname) + } + } + } + return sheetname + } + set(value: String?) { + super.sheetName = value + } + + /** + * return the name of the last sheet referenced if it's an external ref + * + * @return + */ + // 20100217 KSC: default to 1st sheet + val lastSheetName: String? + get() { + var sheetname = this.sheetname + if (parentRec != null) { + val wb = parentRec.workBook + if (wb != null && wb.externSheet != null) { + val sheets = wb.externSheet!!.getBoundSheetNames(this.ixti.toInt()) + if (sheets != null && sheets.size > 0) + sheetname = sheets[sheets.size - 1] + } + } + return sheetname + } + + /** + * Set the location of this PtgRef. This takes a location + * such as "a14:b15" + * + * + * NOTE: the reference stays on the same sheet! + */ + override var location: String + get + set(address) { + val s = ExcelTools.stripSheetNameFromRange(address) + setLocation(s) + } + + + /** + * returns the location of the ptg as an array of shorts. + * [0] = firstrow + * [1] = firstcol + * [2] = lastrow + * [3] = lastcol + */ + override val intLocation: IntArray? + get() { + val first = firstPtg!!.intLocation + val last = lastPtg!!.intLocation + val returning = IntArray(4) + System.arraycopy(first!!, 0, returning, 0, 2) + System.arraycopy(last!!, 0, returning, 2, 2) + return returning + } + + /** + * return all of the Ptg values represented in this array + * + * + * will have to reference the workbook cells as well as + * any upstream formulas... + * + * @return + */ + val allVals: Array? + get() = null + + override val length: Int + get() = Ptg.PTG_AREA3D_LENGTH + + + /** + * Returns all of the cells of this range as PtgRef's. + * This includes empty cells, values, formulas, etc. + * Note the setting of parent-rec requires finding the cell + * the PtgRef refer's to. If that is null then the PtgRef + * will exist, just with a null value. This could cause issues when + * programatically populating cells. + */ + override// loop through the cols + // normal case + // TODO: check rc sanity here + // Get Actual Coordinates + // Get Actual Coordinates + // like $1:$1 + // can happens when Name record is being init'd and sheet records are not set yet + // like $J:$J + // Get Actual Coordinates + // can happens when Name record is being init'd and sheet records are not set yet + // loop through the rows inside + // cache these suckers! + // must set parentrec before setLocation + val components: Array? + get() { + if (comps != null) + return comps + + val components = ArrayList() + try { + var sht: String? = "" + if (this.toString()!!.indexOf("!") > -1) { + sht = this.toString() + sht = sht!!.substring(0, sht.indexOf("!")) + "!" + } + var startrow = 0 + var startcol = 0 + var endrow = 0 + var endcol = 0 + if (!this.isWholeCol && !this.isWholeRow) { + val startloc = firstPtg!!.realIntLocation + startcol = startloc[1] + startrow = startloc[0] + val endloc = lastPtg!!.realIntLocation + endcol = endloc[1] + endrow = endloc[0] + } else if (this.isWholeRow) { + startcol = 0 + try { + endcol = this.sheet!!.maxCol + } catch (ne: NullPointerException) { + return null + } + + endrow = firstPtg!!.rw + startrow = endrow + } else if (this.isWholeCol) { + startrow = 0 + endcol = firstPtg!!.col + startcol = endcol + try { + endrow = this.sheet!!.maxRow + } catch (ne: NullPointerException) { + return null + } + + } + while (startcol <= endcol) { + var rowholder = startrow + while (rowholder <= endrow) { + val displaycol = ExcelTools.getAlphaVal(startcol) + val displayrow = rowholder + 1 + val loc = sht + displaycol + displayrow + val pref = PtgRef3d(false) + pref.parentRec = parentRec + pref.location = loc + components.add(pref) + rowholder++ + } + startcol++ + } + } catch (e: Exception) { + Logger.logErr("calculating range value in PtgArea3d failed.", e) + } + + val pref = arrayOfNulls(components.size) + components.toTypedArray() + comps = pref + return comps + } + + + /** + * sets the column to be relative (relative is true) or absolute (relative is false) + *

                      absolute references do not shift upon column inserts or deletes + *

                      NOTE: DOES NOT handle asymmetrical ranges i.e. 1st ref is absolute, 2nd is relative + * + * @param boolean relative + */ + override var isColRel: Boolean + get() = super.isColRel + set(relative) { + this.fColRel = relative + firstPtg!!.isColRel = relative + lastPtg!!.isColRel = relative + updateRecord() + } + + /** + * sets the row to be relative (relative is true) or absolute (relative is false) + *

                      absolute references do not shift upon row inserts or deletes + *

                      NOTE: DOES NOT handle asymmetrical ranges i.e. 1st ref is absolute, 2nd is relative + * + * @param boolean relative + */ + override var isRowRel: Boolean + get() = super.isRowRel + set(relative) { + if (this.fRwRel != relative) { + this.fRwRel = relative + firstPtg!!.isRowRel = relative + lastPtg!!.isRowRel = relative + updateRecord() + } + } + + /** + * link to the externsheet to be automatically updated upon removals + */ + override fun addListener() { + try { + parentRec.workBook!!.externSheet!!.addPtgListener(this) + } catch (e: Exception) { + // no need to output here. NullPointer occurs when a ref has an invalid ixti, such as when a sheet was removed Worksheet exception could never really happen. + } + + } + + override fun toString(): String? { + return string + } + + init { + opcode = 0x3b + record = ByteArray(11) + record[0] = opcode // "" + this.is3dRef = true + } + + constructor(useReferenceTracker: Boolean) : this() { + this.useReferenceTracker = useReferenceTracker + } + + /** + * set the Ptg Id type to one of: + * VALUE, REFERENCE or Array + * 25H (tAreaR), 45H (tAreaV), 65H (tAreaA) + *

                      The Ptg type is important for certain + * functions which require a specific type of operand + */ + override fun setPtgType(type: Short) { + when (type) { + Ptg.VALUE -> opcode = 0x5B + Ptg.REFERENCE -> opcode = 0x3B + Ptg.ARRAY -> opcode = 0x7B + } + record[0] = opcode + } + + + /** + * @return Returns the ixti. + */ + override fun getIxti(): Short { // only valid for 3d refs!!!! + return ixti + } + + + /** + * set the pointer into the Externsheet Rec. + * this is only valid for 3d refs + */ + override fun setIxti(ixf: Short) { + if (ixti != ixf) { + ixti = ixf + // this seems to be only one byte... + if (record != null) { + record[1] = ixf.toByte() + populateVals() // add listener is done here + } + } + } + + + /** + * get the worksheet that this ref is on + */ + fun getSheets(b: WorkBook): Array { + val bsa = b.externSheet!!.getBoundSheets(this.ixti.toInt()) + if (bsa!![0] == null) + // 20080303 KSC: Catch Unresolved External refs + Logger.logErr("PtgArea3d.getSheet: Unresolved External Worksheet") + return bsa + } + + + /** + * constructor, takes the array of the ptgRef, including + * the identifier so we do not need to figure it out again later... + * also takes the parent rec -- needed to init the sub-ptgs + * + * @param b + * @param parent + */ + fun init(b: ByteArray, parent: XLSRecord) { + ixti = ByteTools.readShort(b[1].toInt(), b[2].toInt()) + record = b + this.parentRec = parent + populateVals() + } + + /** + * Throw this data into two ptgref's + */ + override fun populateVals() { + val temp1 = ByteArray(7) // PtgRef3d is 7 bytes + val temp2 = ByteArray(7) + // Encoded Cell Range Address: + // 0-2= first row + // 2-4= last row + // 4-6= first col + // 6-8= last col + // Encoded Cell Address: + // 0-2= row index + // 2-4= col index + relative flags + try { + temp1[0] = 0x3a + temp1[1] = record[1] // ixti + temp1[2] = record[2] // "" + temp1[3] = record[3] // first row + temp1[4] = record[4] // "" + temp1[5] = record[7] // first col + temp1[6] = record[8] // "" + + temp2[0] = 0x3a + temp2[1] = record[1] // ixti + temp2[2] = record[2] // "" + temp2[3] = record[5] // last row + temp2[4] = record[6] // "" + temp2[5] = record[9] // last col + temp2[6] = record[10] // "" + } catch (e: Exception) { + //should never happen! + return + } + + // pass in parent_rec so can properly set formulaRow/formulaCol + firstPtg = PtgRef3d(false) + + // the following method registers the Ptg with the ReferenceTracker + firstPtg!!.parentRec = parentRec + firstPtg!!.sheetName = this.sheetName + firstPtg!!.init(temp1) + + lastPtg = PtgRef3d(false) + lastPtg!!.parentRec = parentRec + lastPtg!!.sheetName = this.lastSheetName + lastPtg!!.init(temp2) + // flag if it's an external reference + + isExternalRef = (firstPtg as PtgRef3d).isExternalLink || (lastPtg as PtgRef3d).isExternalLink + + setWholeRowCol() + + // take 1st Ptg as sample for relative state + this.fColRel = firstPtg!!.isColRel + this.fRwRel = firstPtg!!.isRowRel + //init sets formula row to 1st row for a shared formula; adjust here + if (parentRec != null && parentRec is Shrfmla) { + lastPtg!!.formulaRow = (parentRec as Shrfmla).lastRow + lastPtg!!.formulaCol = (parentRec as Shrfmla).lastCol + } + this.hashcode = super.hashCode + } + + + /** + * set Ptg Location to parsed location + * + * @param loc String[] sheet1, range, sheet2, exref1, exref2 + */ + override fun setLocation(s: Array) { + try { + if (useReferenceTracker && locax != null) + // if in tracker already, remove + this.removeFromRefTracker() + } catch (e: Exception) { + // will happen if this is not in tracker yet + } + + var sheetname2: String? = null + var range = "" + range = s[1] + if (s[0] != null) { // has a sheet in the address + sheetname = s[0] + sheetname2 = s[2] + if (sheetname2 == null) + sheetname2 = sheetname + // revised so can set ixti on error'd references + var b: WorkBook? = null + var xsht: Externsheet? = null + if (parentRec != null) { + b = parentRec.workBook + if (b == null) + b = parentRec.sheet!!.workBook + } + try { + xsht = b!!.externSheet + val boundnum = b.getWorkSheetByName(sheetname).sheetNum + var boundnum2 = boundnum // it could possibly be a 3d ref - check + if (sheetname != sheetname2 && sheetname2 != null) + boundnum2 = b.getWorkSheetByName(sheetname2).sheetNum + this.setIxti(xsht!!.insertLocation(boundnum, boundnum2).toShort()) + } catch (e: WorkSheetNotFoundException) { + try { + // try to link to external sheet, if possible + val boundnum = xsht!!.getXtiReference(s[0], s[0]) + if (boundnum == -1) { // can't resolve + this.setIxti(xsht.insertLocation(boundnum, boundnum).toShort()) + } else { + this.setIxti(boundnum.toShort()) + this.isExternalRef = true + } + } catch (ex: Exception) { + } + + } + + } else if (parentRec != null) { + sheetname2 = parentRec.sheet!!.sheetName + sheetname = sheetname2 // use parent rec's sheet + } + var i = range.indexOf(":") + if (i < 0) { + range = "$range:$range" + i = range.indexOf(":") + } + + var firstcell = range.substring(0, i) + var lastcell = range.substring(i + 1) + if (sheetname != null) + firstcell = "$sheetname!$firstcell" + if (sheetname2 != null) + lastcell = "$sheetname2!$lastcell" + if (s[3] != null) + // store OOXML External References + firstcell = s[3] + firstcell + if (s[4] != null) + lastcell = s[4] + lastcell + + if (firstPtg == null) { + firstPtg = PtgRef3d(false) + firstPtg!!.parentRec = this.parentRec + } + firstPtg!!.sheetname = sheetname + firstPtg!!.location = firstcell + (firstPtg as PtgRef3d).setIxti(this.ixti) + + if (lastPtg == null) { + lastPtg = PtgRef3d(false) + lastPtg!!.parentRec = this.parentRec + } + lastPtg!!.sheetname = sheetname2 + lastPtg!!.location = lastcell + (lastPtg as PtgRef3d).setIxti(this.ixti) + + this.setWholeRowCol() + this.updateRecord() + // TODO: must deal with non-symmetrical absolute i.e. if first and last ptgs don't match + this.fRwRel = firstPtg!!.fRwRel + this.fColRel = firstPtg!!.fColRel + hashcode = hashCode + if (useReferenceTracker) { + if (!this.isWholeCol && !this.isWholeRow) + this.addToRefTracker() + else + useReferenceTracker = false + } + } + + + /** + * returns whether this CellRange Contains a Cell + * + * @param the cell to test + * @return whether the cell is in the range + */ + override fun contains(ch: CellHandle): Boolean { + val chsheet = ch.workSheetName + sheetName + if (!chsheet.equals(sheetname!!, ignoreCase = true)) return false + val adr = ch.cellAddress + // FIX broken COLROW + val rc = ExcelTools.getRowColFromString(adr) + return contains(rc) + } + + /** + * Switches the two internal ptgref3ds to a new + * sheet. + */ + open fun setReferencedSheet(b: Boundsheet) { + (firstPtg as PtgRef3d).setReferencedSheet(b) + (lastPtg as PtgRef3d).setReferencedSheet(b) + val boundnum = b.sheetNum + val xsht = b.workBook!!.getExternSheet(true) + //TODO: add handling for multi-sheet reference. Already handled in externsheet + try { + this.sheetname = null // 20100218 KSC: RESET + val xloc = xsht!!.insertLocation(boundnum, boundnum) + setIxti(xloc.toShort()) + } catch (e: WorkSheetNotFoundException) { + Logger.logErr("Unable to set referenced sheet in PtgRef3d $e") + } + + } + + /** + * Updates the record bytes so it can be pulled back out. + */ + override fun updateRecord() { + comps = null + val first = firstPtg!!.record + val last = lastPtg!!.record + // KSC: this apparently is what excel wants: + if (isWholeRow) + first[5] = 0 + if (isWholeCol) { + first[3] = 0 + first[4] = 0 + } + // the last record has an extra identifier on it. + val newrecord = ByteArray(Ptg.PTG_AREA3D_LENGTH) + newrecord[0] = 0x3B + System.arraycopy(first, 1, newrecord, 1, 2) + System.arraycopy(first, 3, newrecord, 3, 2) + System.arraycopy(last, 3, newrecord, 5, 2) + System.arraycopy(first, 5, newrecord, 7, 2) + System.arraycopy(last, 5, newrecord, 9, 2) + record = newrecord + if (parentRec != null) { + if (this.parentRec is Formula) + (this.parentRec as Formula).updateRecord() + else if (this.parentRec is Name) + (this.parentRec as Name).updatePtgs() + } + } + + override fun close() { + super.close() + if (comps != null) + for (i in comps!!.indices) { + comps!![i].close() + comps[i] = null + } + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -1176168076050592292L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.java b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.java deleted file mode 100644 index cad5653..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.Externsheet; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - - -/** - * An Erroneous BiffRec range spanning 3rd dimension of WorkSheets. - *

                      - * identical to PtgArea3d - * - * @see Ptg - * @see GenericPtgFunc - **/ -public class PtgAreaErr3d extends PtgArea3d implements Ptg { - - // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -9091097082897614748L; - - public boolean getIsRefErr() { - return true; - } - - public String getString() { - if (sheetname == null) - return "#REF!"; - return sheetname + "!#REF!"; - } - - public int getLength() { - return PTG_AREAERR3D_LENGTH; - } - - /* constructor, takes the array of the ptgRef, including - the identifier so we do not need to figure it out again later... - */ - public void init(byte[] b) { - record = b; - ixti = ByteTools.readShort(record[1], record[2]); - if (ixti > 0) this.sheetname = GenericPtg.qualifySheetname(this.getSheetName()); - - } - - public Object getValue() { - if (sheetname == null) - return "#REF!"; - return sheetname + "!#REF!"; - } - - /** - * sets referenced sheet - * called from copy worksheet - * different from PtgArea3d as PtgAreaErr3d's have not set their firstPtg and lastPtg - */ - public void setReferencedSheet(Boundsheet b) { - int boundnum = b.getSheetNum(); - Externsheet xsht = b.getWorkBook().getExternSheet(true); - //TODO: add handling for multi-sheet reference. Already handled in externsheet - try { - int xloc = xsht.insertLocation(boundnum, boundnum); - this.ixti = (short) xloc; - if (ixti > 0) this.sheetname = GenericPtg.qualifySheetname(this.getSheetName()); - } catch (WorkSheetNotFoundException e) { - Logger.logErr("Unable to set referenced sheet in PtgRef3d " + e); - } - } - - public void setLocation(String[] s) { - sheetname = GenericPtg.qualifySheetname(s[0]); - } - - public int[] getRowCol() { - return new int[]{-1, -1}; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.kt new file mode 100644 index 0000000..cbaf422 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaErr3d.kt @@ -0,0 +1,101 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.Boundsheet +import io.starter.formats.XLS.Externsheet +import io.starter.formats.XLS.WorkSheetNotFoundException +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + + +/** + * An Erroneous BiffRec range spanning 3rd dimension of WorkSheets. + * + * + * identical to PtgArea3d + * + * @see Ptg + * + * @see GenericPtgFunc + */ +class PtgAreaErr3d : PtgArea3d(), Ptg { + + override val isRefErr: Boolean + get() = true + + override val string: String? + get() = if (sheetname == null) "#REF!" else sheetname!! + "!#REF!" + + override val length: Int + get() = Ptg.PTG_AREAERR3D_LENGTH + + override val value: Any? + get() = if (sheetname == null) "#REF!" else sheetname!! + "!#REF!" + + override val rowCol: IntArray? + get() = intArrayOf(-1, -1) + + /* constructor, takes the array of the ptgRef, including + the identifier so we do not need to figure it out again later... + */ + override fun init(b: ByteArray) { + record = b + ixti = ByteTools.readShort(record[1].toInt(), record[2].toInt()) + if (ixti > 0) this.sheetname = GenericPtg.qualifySheetname(this.sheetName) + + } + + /** + * sets referenced sheet + * called from copy worksheet + * different from PtgArea3d as PtgAreaErr3d's have not set their firstPtg and lastPtg + */ + override fun setReferencedSheet(b: Boundsheet) { + val boundnum = b.sheetNum + val xsht = b.workBook!!.getExternSheet(true) + //TODO: add handling for multi-sheet reference. Already handled in externsheet + try { + val xloc = xsht!!.insertLocation(boundnum, boundnum) + this.ixti = xloc.toShort() + if (ixti > 0) this.sheetname = GenericPtg.qualifySheetname(this.sheetName) + } catch (e: WorkSheetNotFoundException) { + Logger.logErr("Unable to set referenced sheet in PtgRef3d $e") + } + + } + + override fun setLocation(s: Array) { + sheetname = GenericPtg.qualifySheetname(s[0]) + } + + companion object { + + // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... + + /** + * serialVersionUID + */ + private val serialVersionUID = -9091097082897614748L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.java b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.java deleted file mode 100644 index 9b9c480..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.Shrfmla; -import io.starter.formats.XLS.XLSConstants; -import io.starter.formats.XLS.XLSRecord; - - -/** - * ptgArea is a reference to an area (rectangle) of cells. - * Essentially it is a collection of two ptgRef's, so it will be - * treated that way in the code... - * - *

                      - * Offset      Name        Size    Contents
                      - * ----------------------------------------------------
                      - * 0           rwFirst     	2       The First row of the reference
                      - * 2           rwLast     		2       The Last row of the reference
                      - * 4           grbitColFirst   2       (see following table)
                      - * 6           grbitColLast    2       (see following table)
                      - *
                      - * Only the low-order 14 bits specify the Col, the other bits specify
                      - * relative vs absolute for both the col or the row.
                      - *
                      - * Bits        Mask        Name    Contents
                      - * -----------------------------------------------------
                      - * 15          8000h       fRwRel  =1 if row offset relative,
                      - * =0 if otherwise
                      - * 14          4000h       fColRel =1 if row offset relative,
                      - * =0 if otherwise
                      - * 13-0        3FFFh       col     Ordinal column offset or number
                      - * 
                      - * - * @see Ptg - * @see Formula - */ -public class PtgAreaN extends PtgArea { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8433468704529379504L; - - public boolean getIsOperand() { - return true; - } - - public boolean getIsReference() { - return true; - } - - PtgRefN firstPtgN; - PtgRefN lastPtgN; - private PtgArea parea = null; - - /* - Throw this data into two ptgref's - */ - public void populateVals() { - byte[] temp1 = new byte[5]; - byte[] temp2 = new byte[5]; - temp1[0] = 0x24; - temp2[0] = 0x24; - System.arraycopy(record, 1, temp1, 1, 2); - System.arraycopy(record, 5, temp1, 3, 2); - System.arraycopy(record, 3, temp2, 1, 2); - System.arraycopy(record, 7, temp2, 3, 2); - -/* - * 0 rwFirst 2 The First row of the reference - 2 rwLast 2 The Last row of the reference - 4 grbitColFirst 2 (see following table) - 6 grbitColLast 2 (see following table) - - Only the low-order 14 bits specify the Col, the other bits specify - relative vs absolute for both the col or the row. - - Bits Mask Name Contents - ----------------------------------------------------- - 15 8000h fRwRel =1 if row offset relative, - =0 if otherwise - 14 4000h fColRel =1 if row offset relative, - =0 if otherwise - 13-0 3FFFh col Ordinal column offset or number - - */ - firstPtgN = new PtgRefN(false); - firstPtgN.setParentRec(parent_rec); - firstPtgN.init(temp1); - lastPtgN = new PtgRefN(false); - lastPtgN.setParentRec(parent_rec); - lastPtgN.init(temp2); - if (parent_rec != null && parent_rec instanceof Shrfmla) { - // 20060301 KSC: init sets formula row to 1st row for a shared formula; adjust here - lastPtgN.setFormulaRow(((Shrfmla) parent_rec).getLastRow()); - lastPtgN.setFormulaCol(((Shrfmla) parent_rec).getLastCol()); - } -// if (this.useReferenceTracker) -// this.addToRefTracker(); - } - - /** - * returns the row/col ints for the ref - * - * @return - */ - public int[] getRowCol() { - if (firstPtgN == null) { - int[] rc1 = firstPtgN.getRowCol(); - int[] ret = {rc1[0], rc1[1], rc1[0], rc1[1]}; - return ret; - } - int[] rc1 = firstPtgN.getRowCol(); - int[] rc2 = lastPtgN.getRowCol(); - int[] ret = {rc1[0], rc1[1], rc2[0], rc2[1]}; - return ret; - } - - /** - * returns the uncoverted, actual row col - * - * @return - */ - public int[] getRealRowCol() { - return new int[]{firstPtgN.rw, firstPtgN.col, lastPtgN.rw, lastPtgN.col}; - } - - public PtgArea convertToPtgArea(io.starter.formats.XLS.XLSRecord r) { - PtgRef p1 = firstPtgN.convertToPtgRef(r); - PtgRef p2 = lastPtgN.convertToPtgRef(r); - PtgArea par = new PtgArea(p1, p2, r); - return par; - } - - - /** - * update record bytes - */ - // 20060223 KSC - public void updateRecord() { - byte[] first = firstPtgN.getRecord(); - byte[] last = lastPtgN.getRecord(); - // the last record has an extra identifier on it. - byte[] newrecord = new byte[9]; - newrecord[0] = record[0]; - System.arraycopy(first, 1, newrecord, 1, 2); - System.arraycopy(last, 1, newrecord, 3, 2); - System.arraycopy(first, 3, newrecord, 5, 2); - System.arraycopy(last, 3, newrecord, 7, 2); - record = newrecord; - } - - /* - Returns the location of the Ptg as a string -*/ - public String getLocation() { - if (firstPtgN == null || lastPtgN == null) { - this.populateVals(); - if (firstPtgN == null || lastPtgN == null) // we tried - throw new AssertionError("PtgAreaN.getLocation null ptgs"); - } - String s = firstPtgN.getLocation(); - String y = lastPtgN.getLocation(); - - return s + ":" + y; - } - - /** - * returns an array of the first and last addresses in the PtgAreaN - */ - // 20060223: KSC: customize from ptgArea - public int[] getIntLocation() { - int[] returning = new int[4]; - try { - int[] first = firstPtgN.getIntLocation(); - int[] last = lastPtgN.getIntLocation(); - System.arraycopy(first, 0, returning, 0, 2); - System.arraycopy(last, 0, returning, 2, 2); - } catch (Exception e) { - } - return returning; - } - - /** - * @return lastPtgN - */ - public PtgRefN getLastPtgN() { - return lastPtgN; - } - - /** - * @return firstPtgN - */ - public PtgRefN getFirstPtgN() { - return firstPtgN; - } - - /** - * custom RefTracker usage: uses entire range covered by all shared formulas - */ - public PtgArea getArea() { - Shrfmla sh = (Shrfmla) this.getParentRec(); - int[] i = new int[4]; - if (fRwRel) { - i[0] = sh.getFirstRow() + firstPtgN.rw; - } else { - i[0] = firstPtgN.rw; - } - if (fColRel) { - i[1] = sh.getFirstCol() + firstPtgN.col; - } else { - i[1] = firstPtgN.col; - } - if (fRwRel) { - i[2] = sh.getLastRow() + lastPtgN.rw; - } else { - i[2] = lastPtgN.rw; - } - if (fColRel) { - i[3] = sh.getLastCol() + lastPtgN.col; - } else { - i[3] = lastPtgN.col; - } - - if (i[1] >= MAXCOLS_BIFF8 && - !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[1] -= MAXCOLS_BIFF8; - if (i[3] >= MAXCOLS_BIFF8 && - !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[3] -= MAXCOLS_BIFF8; - - PtgArea parea = new PtgArea(i, sh, true); - return parea; - } - - /** - * add "true" area to reference tracker i.e. entire range referenced by all shared formula members - */ - public void addToRefTracker() { - int iParent = this.getParentRec().getOpcode(); - if (iParent == XLSConstants.SHRFMLA) { - // KSC: TESTING - local ptgarea gets finalized and messes up ref. tracker on multiple usages without close - //getArea(); - //parea.addToRefTracker(); - PtgArea parea = this.getArea(); // is finalized if local var --- but take out ptgarea finalize for now - parea.addToRefTracker(); - } - } - - /** - * remove "true" area from reference tracker i.e. entire range referenced by all shared formula members - */ - public void removeFromRefTracker() { - int iParent = this.getParentRec().getOpcode(); - if (iParent == XLSConstants.SHRFMLA) { - PtgArea parea = this.getArea(); - parea.removeFromRefTracker(); - } - //if (parea!=null) { - // parea.removeFromRefTracker(); - //parea.close(); - //} - //parea= null; - } - - public void close() { - removeFromRefTracker(); - if (parea != null) - parea.close(); - parea = null; - if (firstPtgN != null) - firstPtgN.close(); - firstPtgN = null; - if (lastPtgN != null) - lastPtgN.close(); - lastPtgN = null; - } -} diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.kt new file mode 100644 index 0000000..246d6f4 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgAreaN.kt @@ -0,0 +1,297 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.Shrfmla +import io.starter.formats.XLS.XLSConstants +import io.starter.formats.XLS.XLSRecord + + +/** + * ptgArea is a reference to an area (rectangle) of cells. + * Essentially it is a collection of two ptgRef's, so it will be + * treated that way in the code... + * + *
                      + * Offset      Name        Size    Contents
                      + * ----------------------------------------------------
                      + * 0           rwFirst     	2       The First row of the reference
                      + * 2           rwLast     		2       The Last row of the reference
                      + * 4           grbitColFirst   2       (see following table)
                      + * 6           grbitColLast    2       (see following table)
                      + *
                      + * Only the low-order 14 bits specify the Col, the other bits specify
                      + * relative vs absolute for both the col or the row.
                      + *
                      + * Bits        Mask        Name    Contents
                      + * -----------------------------------------------------
                      + * 15          8000h       fRwRel  =1 if row offset relative,
                      + * =0 if otherwise
                      + * 14          4000h       fColRel =1 if row offset relative,
                      + * =0 if otherwise
                      + * 13-0        3FFFh       col     Ordinal column offset or number
                      +
                      * + * + * @see Ptg + * + * @see Formula + */ +class PtgAreaN : PtgArea() { + + override val isOperand: Boolean + get() = true + + override val isReference: Boolean + get() = true + + /** + * @return firstPtgN + */ + var firstPtgN: PtgRefN? = null + internal set + /** + * @return lastPtgN + */ + var lastPtgN: PtgRefN? = null + internal set + private var parea: PtgArea? = null + + /** + * returns the row/col ints for the ref + * + * @return + */ + override val rowCol: IntArray? + get() { + if (firstPtgN == null) { + val rc1 = firstPtgN!!.rowCol + return intArrayOf(rc1[0], rc1[1], rc1[0], rc1[1]) + } + val rc1 = firstPtgN!!.rowCol + val rc2 = lastPtgN!!.rowCol + return intArrayOf(rc1[0], rc1[1], rc2[0], rc2[1]) + } + + /** + * returns the uncoverted, actual row col + * + * @return + */ + val realRowCol: IntArray + get() = intArrayOf(firstPtgN!!.rw, firstPtgN!!.col, lastPtgN!!.rw, lastPtgN!!.col) + + /* + Returns the location of the Ptg as a string +*/ + override// we tried + var location: String? + get() { + if (firstPtgN == null || lastPtgN == null) { + this.populateVals() + if (firstPtgN == null || lastPtgN == null) + throw AssertionError("PtgAreaN.getLocation null ptgs") + } + val s = firstPtgN!!.location + val y = lastPtgN!!.location + + return "$s:$y" + } + set + + /** + * returns an array of the first and last addresses in the PtgAreaN + */ + // 20060223: KSC: customize from ptgArea + override val intLocation: IntArray? + get() { + val returning = IntArray(4) + try { + val first = firstPtgN!!.intLocation + val last = lastPtgN!!.intLocation + System.arraycopy(first!!, 0, returning, 0, 2) + System.arraycopy(last!!, 0, returning, 2, 2) + } catch (e: Exception) { + } + + return returning + } + + /** + * custom RefTracker usage: uses entire range covered by all shared formulas + */ + // TODO: determine if this is an OK maxcol (Excel 2007) + // TODO: determine if this is an OK maxcol (Excel 2007) + val area: PtgArea + get() { + val sh = this.parentRec as Shrfmla + val i = IntArray(4) + if (fRwRel) { + i[0] = sh.firstRow + firstPtgN!!.rw + } else { + i[0] = firstPtgN!!.rw + } + if (fColRel) { + i[1] = sh.firstCol + firstPtgN!!.col + } else { + i[1] = firstPtgN!!.col + } + if (fRwRel) { + i[2] = sh.lastRow + lastPtgN!!.rw + } else { + i[2] = lastPtgN!!.rw + } + if (fColRel) { + i[3] = sh.lastCol + lastPtgN!!.col + } else { + i[3] = lastPtgN!!.col + } + + if (i[1] >= XLSConstants.MAXCOLS_BIFF8 && !this.parentRec!!.workBook!!.isExcel2007) + i[1] -= XLSConstants.MAXCOLS_BIFF8 + if (i[3] >= XLSConstants.MAXCOLS_BIFF8 && !this.parentRec!!.workBook!!.isExcel2007) + i[3] -= XLSConstants.MAXCOLS_BIFF8 + + return PtgArea(i, sh, true) + } + + /* + Throw this data into two ptgref's + */ + override fun populateVals() { + val temp1 = ByteArray(5) + val temp2 = ByteArray(5) + temp1[0] = 0x24 + temp2[0] = 0x24 + System.arraycopy(record, 1, temp1, 1, 2) + System.arraycopy(record, 5, temp1, 3, 2) + System.arraycopy(record, 3, temp2, 1, 2) + System.arraycopy(record, 7, temp2, 3, 2) + + /* + * 0 rwFirst 2 The First row of the reference + 2 rwLast 2 The Last row of the reference + 4 grbitColFirst 2 (see following table) + 6 grbitColLast 2 (see following table) + + Only the low-order 14 bits specify the Col, the other bits specify + relative vs absolute for both the col or the row. + + Bits Mask Name Contents + ----------------------------------------------------- + 15 8000h fRwRel =1 if row offset relative, + =0 if otherwise + 14 4000h fColRel =1 if row offset relative, + =0 if otherwise + 13-0 3FFFh col Ordinal column offset or number + + */ + firstPtgN = PtgRefN(false) + firstPtgN!!.parentRec = parentRec + firstPtgN!!.init(temp1) + lastPtgN = PtgRefN(false) + lastPtgN!!.parentRec = parentRec + lastPtgN!!.init(temp2) + if (parentRec != null && parentRec is Shrfmla) { + // 20060301 KSC: init sets formula row to 1st row for a shared formula; adjust here + lastPtgN!!.setFormulaRow((parentRec as Shrfmla).lastRow) + lastPtgN!!.setFormulaCol((parentRec as Shrfmla).lastCol) + } + // if (this.useReferenceTracker) + // this.addToRefTracker(); + } + + fun convertToPtgArea(r: io.starter.formats.XLS.XLSRecord): PtgArea { + val p1 = firstPtgN!!.convertToPtgRef(r) + val p2 = lastPtgN!!.convertToPtgRef(r) + return PtgArea(p1, p2, r) + } + + + /** + * update record bytes + */ + // 20060223 KSC + override fun updateRecord() { + val first = firstPtgN!!.record + val last = lastPtgN!!.record + // the last record has an extra identifier on it. + val newrecord = ByteArray(9) + newrecord[0] = record[0] + System.arraycopy(first, 1, newrecord, 1, 2) + System.arraycopy(last, 1, newrecord, 3, 2) + System.arraycopy(first, 3, newrecord, 5, 2) + System.arraycopy(last, 3, newrecord, 7, 2) + record = newrecord + } + + /** + * add "true" area to reference tracker i.e. entire range referenced by all shared formula members + */ + override fun addToRefTracker() { + val iParent = this.parentRec!!.opcode.toInt() + if (iParent == XLSConstants.SHRFMLA.toInt()) { + // KSC: TESTING - local ptgarea gets finalized and messes up ref. tracker on multiple usages without close + //getArea(); + //parea.addToRefTracker(); + val parea = this.area // is finalized if local var --- but take out ptgarea finalize for now + parea.addToRefTracker() + } + } + + /** + * remove "true" area from reference tracker i.e. entire range referenced by all shared formula members + */ + override fun removeFromRefTracker() { + val iParent = this.parentRec!!.opcode.toInt() + if (iParent == XLSConstants.SHRFMLA.toInt()) { + val parea = this.area + parea.removeFromRefTracker() + } + //if (parea!=null) { + // parea.removeFromRefTracker(); + //parea.close(); + //} + //parea= null; + } + + override fun close() { + removeFromRefTracker() + if (parea != null) + parea!!.close() + parea = null + if (firstPtgN != null) + firstPtgN!!.close() + firstPtgN = null + if (lastPtgN != null) + lastPtgN!!.close() + lastPtgN = null + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -8433468704529379504L + } +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java b/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java deleted file mode 100644 index 899f40a..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgArray.java +++ /dev/null @@ -1,651 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.ExpressionParser; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; - -/* - * ARRAY CONSTANT followed by 7 reserved bytes. - * - * The token value for ptgArray consists of the array - * dimensions and the array values - * - * ptgArray differs from most other operand tokens in that - * the token value doesn't follow the token type. - * - * Instead, the token value is appended to the saved parsed - * expression, immediately following the last token. - * - * Offset Name Size Contents - * --------------------------------------------------------- - * -- - * 0 nc 1 number of columns -1 in array constant (0 = 256) - * 1 nr 2 number of rows -1 in array constant - * 3 rgval var the array vals (k+1)*(nr+1) length - * - * - * The format of the token value is shown in the following - * table. - * - * The number of values in the array constant is equal to - * the product of the array dimensions, (nc+1)*(nr+1_ - * - * Each value is either an 8-byte IEEE fp numbr or a string. - * The two formats for these values are shown in the - * following tables. - * - * - * IEEE FP Number - * Offset Name Size Contents - * --------------------------------------------------------- - * -- - * 0 grbit 1 =01h - * 1 num 8 IEEE FP number - * - * String - * Offset Name Size Contents - * --------------------------------------------------------- - * -- - * 0 grbit 1 =02h - * 1 cch 1 Length of the String - * 2 rgch var the string. - * - * If a formula contains more than one array constant, the - * token values for the array constants are appended to the - * saved - * parsed expression in order: first the values for the - * first array constant, - * then the values for the second array constant, etc. - * - * If a formula contains very long array constants, the - * FORMULA, ARRAY, or NAME record contaniing the parsed - * expression - * may overflow into CONTINUE records. In such cases, an - * individual array value is NEVER SPLIT between records, - * but record boundaries are established between adjacent - * array values. - * - * The reference class ptgArray never appears in an Excel - * formula, only the ptgArrayV and ptgArrayA classes are - * used. - * - * - * @see Ptg - * - * @see Formula - * - */ -// 20090119-22 KSC: Many, many changes changes -public class PtgArray extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4416140231168551393L; - int nc = -1; - int nr = -1; - byte[] rgval; - CompatibleVector arrVals = new CompatibleVector(); - boolean isIntermediary = false; // 20090824 - // KSC: - // true - // if - // this - // PtgArray - // is - // only - // part - // of - // a - // calcualtion - // process; - // if - // so, - // apparently - // can - // have - // more - // than - // 256 - // columns - // [BugTracker - // 2683] - - @Override - public boolean getIsOperand() { - return true; - } - - /** - * return the first 8 bytes of the ptgArray record - * this represents the id byte and 7 reserved bytes - * - * @return - */ - public byte[] getPreRecord() { - return record; - } - - /** - * these are the bytes appended to the formula token array, after all other ptg's - * - * @return - */ - public byte[] getPostRecord() { - return rgval; - } - - @Override - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - } - - private void populateVals() { - if (record.length > 8) { // means that array data has already been - // appeneded to end of record array; store - // in rgvals - rgval = new byte[record.length - 8]; - System.arraycopy(record, 8, rgval, 0, rgval.length); // save post - // array= - // nc, nr + - // array - // data - } - if (rgval != null) { - // clear out record array:0= id 1-7=reserved - byte[] b = new byte[8]; - b[0] = record[0]; - record = b; - this.parseArrayComponents(); - } // otherwise, it's just the initial input of the 1st 8 bytes - // record - see Formula - } - - /** - * given "extra info" at end of formula expression, parse array values - */ - public void parseArrayComponents() { - int nitems = 0; - arrVals.clear(); // 20090820 KSC: makes sense to! [BugTracker 2683] - if (!isIntermediary) // 20090824 KSC: sometimes an intermediary ptgarry - // can have more than 256 columns [BugTracker - // 2683] - nc = rgval[0] & 0xFF; // number of columns - nr = ByteTools.readShort(rgval[1], rgval[2]); // number of rows - try { - // (nc+1)*(nr+1) compoments - for (int i = 3; i < rgval.length; ) { // 20090820 KSC: post array - // contains nc & nr so i - // should be initially 3 - // instead of 0 [BugTracker - // 2683] - if (rgval[i] == 0) { // empty value - i++; - i += 8; - arrVals.add(""); // TODO: Empty Constant should be null? - } else if (rgval[i] == 0x1) { // its a number - i++; - byte[] barr = new byte[8]; - System.arraycopy(rgval, i, barr, 0, 8); - double val = ByteTools.eightBytetoLEDouble(barr); - Double d = new Double(val); - arrVals.add(d); - i = i + 8; - } else if (rgval[i] == 0x2) { // its a string - int strLen = ByteTools - .readShort(rgval[i + 1], rgval[i + 2]); - i += 3; - int grbt = rgval[i++]; - byte[] barr = new byte[strLen]; - System.arraycopy(rgval, i, barr, 0, strLen); - String strVal = ""; - try { - if ((grbt & 0x1) == 0x1) { - strVal = new String(barr, UNICODEENCODING); - } else { - strVal = new String(barr, DEFAULTENCODING); - } - } catch (UnsupportedEncodingException e) { - Logger.logInfo("decoding formula string in array failed: " - + e); - } - arrVals.add(strVal); - i += strLen; - } else if (rgval[i] == 0x4) { // its a boolean - if (rgval[++i] == 0) - arrVals.add(Boolean.valueOf(false)); - else - arrVals.add(Boolean.valueOf(true)); - i = i + 8; - } else if (rgval[i] == 0x10) { // it's an error value - int errCode = rgval[++i]; - switch (errCode) { - case 0: - arrVals.add("#NULL!"); - break; - case 0x7: - arrVals.add("#DIV/0!"); - break; - case 0x0F: - arrVals.add("#VALUE!"); - break; - case 0x17: - arrVals.add("#REF!"); - break; - case 0x1D: - arrVals.add("#NAME!"); - break; - case 0x24: - arrVals.add("#NUM!"); - break; - case 0x2A: - arrVals.add("#N/A!"); - break; - } - i = i + 8; - } - nitems++; - if (nitems == ((nc + 1) * (nr + 1))) { // Finished with this - // array! - int length = i; - i = rgval.length; - // length may be less than rgval.length for cases of more - // than one array parameter - // see ExpressionParser.parseExpression - if (rgval.length != length) {// then truncate both record + - // rgval - byte[] tmp = new byte[length]; - System.arraycopy(rgval, 0, tmp, 0, length); - rgval = tmp; - } - } - } - } catch (Exception e) { - Logger.logErr("Error Processing Array Formula: " + e.toString()); - return; - } - } - - public int getVal() { - return -1; - } - - @Override - public Object getValue() { - // 20090820 KSC: value = entire array instead of 1st value; - // desired value is determined by cell position as compared - // to current formula; see Formula.calculate [BugTracker - // 2683] - // return elementAt(0).getValue(-1); // default: return 1st - // value - return getString(); - } - - /* - * returns the string value of the name - * - * @see io.starter.formats.XLS.formulas.Ptg#getValue() - */ - @Override - public String getString() { - Object retVal = null; - Ptg[] p = this.getComponents(); - String retstr = ""; - if (nc == 0 && nr == 0) { // if it's a single value, just return val - for (int i = 0; i < p.length; i++) { - if (i != 0) - retstr += ","; - retstr += p[i].getValue().toString(); - } - } else { - retstr = ""; - int loc = 0; - for (int x = 0; x < nr + 1; x++) { - if (x != 0) - retstr += ";"; - for (int i = 0; i < nc + 1; i++) { - if (i != 0) - retstr += ","; - retstr += p[loc++].getValue().toString(); - } - } - // retstr += "}"; - // retVal = retstr.substring(0,retstr.length()-1); - } - retVal = retstr; - return "{" + retVal + "}"; - } - - @Override - public String getTextString() { - return getString(); - } - - public void setVal(String arrStr) { - // remove the initial { and ending } - arrStr = arrStr.substring(1, arrStr.length() - 1); - if (arrStr.indexOf("{") != -1) { // SHOULDN'T -- see - // FormulaParser.getPtgsFromFormulaString - Logger.logErr("PtgArray.setVal: Multiple Arrays Encountered"); - } - - // parse all array strings into rows, cols - String[] rows = null; - String[][] cols = null; - // split rows - rows = arrStr.split(";"); - cols = new String[rows.length][]; - for (int i = 0; i < rows.length; i++) { - String[] s = rows[i].split(",", -1); // include empty strings - cols[i] = s; - } - byte[] databytes = new byte[11]; - databytes[0] = 0x60; // 20h=tArrayR, 40h=tArrayV, 60h=tArrayA - isIntermediary = false; // init value - if (cols[0].length >= 255) { // 20090824 KSC: apparently sometimes an - // intermediary calculations step can - // include > 256 array elements ... - isIntermediary = true; - nc = cols[0].length - 1; - } - databytes[8] = (byte) ((cols[0].length - 1) & 0xFF); // nc-1 // 20090819 - // KSC: placed - // in wrong pt - // of record: - // was [1] - // [BugTracker - // 2683] - // databytes[8] = (byte)((cols[0].length-1)); // nc-1 // - // 20090819 KSC: placed in wrong pt of record: was [1] - // [BugTracker 2683] - System.arraycopy(ByteTools - .shortToLEBytes((short) (rows.length - 1)), 0, databytes, 9, 2); // nr-1 - // // - // 20090819 - // KSC: - // placed - // in - // wrong - // pt - // of - // record: - // was - // 2,3 - // [BugTracker - // 2683] - // iterate the array and fill out the data section - for (int j = 0; j < rows.length; j++) { - for (int i = 0; i < cols[0].length; i++) { - byte[] valbytes = this.valuesIntoByteArray(cols[j][i]); - databytes = ByteTools.append(valbytes, databytes); - } - } - // populate primary values for rec - record = databytes; - this.init(databytes); - } - - /** - * Turns a vector of values into a byte array representation for the data section of this record - * - * @param compVect - * @return - */ - private byte[] valuesIntoByteArray(String constVal) { - byte[] databytes = new byte[0]; - byte[] thisElement = new byte[9]; - - try { // number? - Double d = new Double(constVal); - thisElement[0] = 0x1; // id for number value - byte[] b = ByteTools.toBEByteArray(d.doubleValue()); - System.arraycopy(b, 0, thisElement, 1, b.length); - databytes = ByteTools.append(thisElement, databytes); - } catch (NumberFormatException ee) { - try { - if (constVal.equalsIgnoreCase("true") - || constVal.equalsIgnoreCase("false")) { - Boolean bb = Boolean.valueOf(constVal); - thisElement[0] = 0x4; // id for boolean value - thisElement[1] = (byte) (bb.booleanValue() ? 1 : 0); - } else if (constVal == null || constVal.equals("")) { // emtpy - // or - // null - // value - thisElement[0] = 0x0; // id for empty value - } else if (constVal.charAt(0) == '#') { // it's an error value - thisElement[0] = 0x10; // id for error value - int errCode = 0; - if (constVal.equals("#NULL!")) - errCode = 0; - else if (constVal.equals("#DIV/0!")) - errCode = 0x7; - else if (constVal.equals("#VALUE!")) - errCode = 0x0F; - else if (constVal.equals("#REF!")) - errCode = 0x17; - else if (constVal.equals("#NAME!")) - errCode = 0x1D; - else if (constVal.equals("#NUM!")) - errCode = 0x24; - else if (constVal.equals("#N/A!") || constVal.equals("#N/A") - || constVal.equals("N/A")) - errCode = 0x2A; - thisElement[1] = (byte) errCode; - } else { // assume string - thisElement = new byte[3]; - try { - thisElement = new byte[4]; - thisElement[0] = 0x2; // id for string - byte[] b = constVal.getBytes(UNICODEENCODING); - System.arraycopy(ByteTools - .shortToLEBytes((short) b.length), 0, thisElement, 1, 2); - thisElement[3] = 1; // compressed= 0, uncompressed= 1 - // (16-bit chars) - thisElement = ByteTools.append(b, thisElement); - } catch (UnsupportedEncodingException z) { - Logger.logWarn("encoding formula array:" + z); - } - } - databytes = ByteTools.append(thisElement, databytes); - } catch (Exception ex) { - Logger.logWarn("PtgArray.valuesIntoByteArray: error parsing array element:" - + ex); - } - } - return databytes; - } - - /** - * Returns the second section of bytes for the PtgArray. - * These are the bytes that are split off the end of the - * formula - - public void getComponentBytes(){ - - } - //public void updateRecord(){ - //}*/ - - /** - * Override due to mystery extra byte - * occasionally found in ptgName recs. - */ - @Override - public int getLength() { - /* - * 20090820 KSC: really want record length not rgval length, - * which now is separate [BugTracker 2683] - * if (rgval!=null) - * return rgval.length; - */ - return 8; - } - - /* - * not used - * public int getLength(byte[] b){ - * int co = b[1]; - * int rw = ByteTools.readShort(b[2], b[3]); - * rw++; // appears that rows are not ordinal here... - * int numrecs = co*rw; - * int len = 4; - * int loc = 4; - * for (int i=0;i<=numrecs;i++){ - * if (b[len] == 0x1){ // its a number - * len += 9; - * }else{ - * len += b[len+1] + 2; - * } - * } - * length = len; - * return length; - * } - */ - - @Override - public String toString() { - return this.getString(); - } - - @Override - public Ptg[] getComponents() { - Ptg[] retVals = new Ptg[arrVals.size()]; - for (int i = 0; i < arrVals.size(); i++) { - Object o = arrVals.elementAt(i); - if (o instanceof Double) { - Double d = (Double) o; - PtgNumber pnum = new PtgNumber(d.doubleValue()); - retVals[i] = pnum; - } else if (o instanceof Boolean) { - PtgBool pb = new PtgBool(((Boolean) o).booleanValue()); - retVals[i] = pb; - } else { - if (FormulaParser.isRef((String) o) - || FormulaParser.isRange((String) o)) { // it's a range - PtgArea3d pa = new PtgArea3d(); - pa.setParentRec(this.getParentRec()); - pa.setUseReferenceTracker(true); - pa.setLocation((String) o); - Ptg[] pacomps = pa.getComponents(); - Ptg[] temp = new Ptg[retVals.length - 1 + pacomps.length]; - System.arraycopy(retVals, 0, temp, 0, retVals.length - 1); - System.arraycopy(pacomps, 0, temp, retVals.length - - 1, pacomps.length); - retVals = temp; - } else { - PtgStr pstr = new PtgStr((String) o); - retVals[i] = pstr; - } - } - } - return retVals; - } - - /** - * returns the 0-based number of rows in this array - * if nr>1 then the array is in the form of: - * a,b,c;d,e,f; .... where the semicolons delineate rows - * - * @return - */ - public int getNumberOfRows() { - return nr; - } - - /** - * returns the 0-based number of columns in this array - * number of columns is the amount of elements before the semicolon (if present) - * a,b,c;d,e,f; .... - * - * @return - */ - public int getNumberOfColumns() { - return nc; - } - - /** - * sets the array components values for this PtgArray - * returns the actual array components length - * - * @see ExpressionParser.parseExpression - */ - public int setArrVals(byte[] by) { - rgval = by; - if (rgval != null) { - // clear out record array:0= id 1-7=reserved - byte[] b = new byte[8]; - b[0] = record[0]; - record = b; - this.parseArrayComponents(); - } - return rgval.length; - - } - - public byte[] getArrVals() { - return rgval; - } - - /** - * returns a ptg at the specified location. Assumes that it is a one-dimensional - * array. If you need a multidimensional array please use the other elementAt(int,int)method - * - * @param loc - * @return - */ - public Ptg elementAt(int loc) { - Ptg[] p = this.getComponents(); - return p[loc]; - } - - public Ptg elementAt(int col, int row) { - Ptg[] p = this.getComponents(); - try { - int loc = 0; - for (int i = 0; i < row; i++) { - loc += (nc); // 20090816 KSC: why +1???? +1); [BugTracker 2683] - } - loc += col; - return elementAt(loc); - } catch (ArrayIndexOutOfBoundsException e) { - Logger.logErr("PtgArray.elementAt: error retrieving value at [" - + row + "," + col + "]: " + e); - } - return null; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgArray.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgArray.kt new file mode 100644 index 0000000..527438b --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgArray.kt @@ -0,0 +1,623 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.ExpressionParser +import io.starter.toolkit.ByteTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + +import java.io.UnsupportedEncodingException + +/* + * ARRAY CONSTANT followed by 7 reserved bytes. + * + * The token value for ptgArray consists of the array + * dimensions and the array values + * + * ptgArray differs from most other operand tokens in that + * the token value doesn't follow the token type. + * + * Instead, the token value is appended to the saved parsed + * expression, immediately following the last token. + * + * Offset Name Size Contents + * --------------------------------------------------------- + * -- + * 0 nc 1 number of columns -1 in array constant (0 = 256) + * 1 nr 2 number of rows -1 in array constant + * 3 rgval var the array vals (k+1)*(nr+1) length + * + * + * The format of the token value is shown in the following + * table. + * + * The number of values in the array constant is equal to + * the product of the array dimensions, (nc+1)*(nr+1_ + * + * Each value is either an 8-byte IEEE fp numbr or a string. + * The two formats for these values are shown in the + * following tables. + * + * + * IEEE FP Number + * Offset Name Size Contents + * --------------------------------------------------------- + * -- + * 0 grbit 1 =01h + * 1 num 8 IEEE FP number + * + * String + * Offset Name Size Contents + * --------------------------------------------------------- + * -- + * 0 grbit 1 =02h + * 1 cch 1 Length of the String + * 2 rgch var the string. + * + * If a formula contains more than one array constant, the + * token values for the array constants are appended to the + * saved + * parsed expression in order: first the values for the + * first array constant, + * then the values for the second array constant, etc. + * + * If a formula contains very long array constants, the + * FORMULA, ARRAY, or NAME record contaniing the parsed + * expression + * may overflow into CONTINUE records. In such cases, an + * individual array value is NEVER SPLIT between records, + * but record boundaries are established between adjacent + * array values. + * + * The reference class ptgArray never appears in an Excel + * formula, only the ptgArrayV and ptgArrayA classes are + * used. + * + * + * @see Ptg + * + * @see Formula + * + */ +// 20090119-22 KSC: Many, many changes changes +class PtgArray : GenericPtg(), Ptg { + /** + * returns the 0-based number of columns in this array + * number of columns is the amount of elements before the semicolon (if present) + * a,b,c;d,e,f; .... + * + * @return + */ + var numberOfColumns = -1 + internal set + /** + * returns the 0-based number of rows in this array + * if nr>1 then the array is in the form of: + * a,b,c;d,e,f; .... where the semicolons delineate rows + * + * @return + */ + var numberOfRows = -1 + internal set + /** + * these are the bytes appended to the formula token array, after all other ptg's + * + * @return + */ + var postRecord: ByteArray? = null + internal set + internal var arrVals = CompatibleVector() + internal var isIntermediary = false // 20090824 + // KSC: + // true + // if + // this + // PtgArray + // is + // only + // part + // of + // a + // calcualtion + // process; + // if + // so, + // apparently + // can + // have + // more + // than + // 256 + // columns + // [BugTracker + // 2683] + + override val isOperand: Boolean + get() = true + + /** + * return the first 8 bytes of the ptgArray record + * this represents the id byte and 7 reserved bytes + * + * @return + */ + val preRecord: ByteArray + get() = record + + val `val`: Int + get() = -1 + + override// 20090820 KSC: value = entire array instead of 1st value; + // desired value is determined by cell position as compared + // to current formula; see Formula.calculate [BugTracker + // 2683] + // return elementAt(0).getValue(-1); // default: return 1st + // value + val value: Any? + get() = string + + /* + * returns the string value of the name + * + * @see io.starter.formats.XLS.formulas.Ptg#getValue() + */ + override// if it's a single value, just return val + // retstr += "}"; + // retVal = retstr.substring(0,retstr.length()-1); + val string: String + get() { + var retVal: Any? = null + val p = this.components + var retstr = "" + if (numberOfColumns == 0 && numberOfRows == 0) { + for (i in p!!.indices) { + if (i != 0) + retstr += "," + retstr += p[i].value.toString() + } + } else { + retstr = "" + var loc = 0 + for (x in 0 until numberOfRows + 1) { + if (x != 0) + retstr += ";" + for (i in 0 until numberOfColumns + 1) { + if (i != 0) + retstr += "," + retstr += p!![loc++].value.toString() + } + } + } + retVal = retstr + return "{$retVal}" + } + + override val textString: String + get() = string + + /** + * Returns the second section of bytes for the PtgArray. + * These are the bytes that are split off the end of the + * formula + * + * public void getComponentBytes(){ + * + * } + * //public void updateRecord(){ + * //} */ + + /** + * Override due to mystery extra byte + * occasionally found in ptgName recs. + */ + override/* + * 20090820 KSC: really want record length not rgval length, + * which now is separate [BugTracker 2683] + * if (rgval!=null) + * return rgval.length; + */ val length: Int + get() = 8 + + override// it's a range + val components: Array? + get() { + var retVals = arrayOfNulls(arrVals.size) + for (i in arrVals.indices) { + val o = arrVals.elementAt(i) + if (o is Double) { + val pnum = PtgNumber(o.toDouble()) + retVals[i] = pnum + } else if (o is Boolean) { + val pb = PtgBool(o.booleanValue()) + retVals[i] = pb + } else { + if (FormulaParser.isRef(o as String) || FormulaParser.isRange(o)) { + val pa = PtgArea3d() + pa.parentRec = this.parentRec + pa.useReferenceTracker = true + pa.location = o + val pacomps = pa.components + val temp = arrayOfNulls(retVals.size - 1 + pacomps!!.size) + System.arraycopy(retVals, 0, temp, 0, retVals.size - 1) + System.arraycopy(pacomps, 0, temp, retVals.size - 1, pacomps.size) + retVals = temp + } else { + val pstr = PtgStr(o) + retVals[i] = pstr + } + } + } + return retVals + } + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() + } + + private fun populateVals() { + if (record.size > 8) { // means that array data has already been + // appeneded to end of record array; store + // in rgvals + postRecord = ByteArray(record.size - 8) + System.arraycopy(record, 8, postRecord!!, 0, postRecord!!.size) // save post + // array= + // nc, nr + + // array + // data + } + if (postRecord != null) { + // clear out record array:0= id 1-7=reserved + val b = ByteArray(8) + b[0] = record[0] + record = b + this.parseArrayComponents() + } // otherwise, it's just the initial input of the 1st 8 bytes + // record - see Formula + } + + /** + * given "extra info" at end of formula expression, parse array values + */ + fun parseArrayComponents() { + var nitems = 0 + arrVals.clear() // 20090820 KSC: makes sense to! [BugTracker 2683] + if (!isIntermediary) + // 20090824 KSC: sometimes an intermediary ptgarry + // can have more than 256 columns [BugTracker + // 2683] + numberOfColumns = postRecord!![0] and 0xFF // number of columns + numberOfRows = ByteTools.readShort(postRecord!![1].toInt(), postRecord!![2].toInt()).toInt() // number of rows + try { + // (nc+1)*(nr+1) compoments + var i = 3 + while (i < postRecord!!.size) { // 20090820 KSC: post array + // contains nc & nr so i + // should be initially 3 + // instead of 0 [BugTracker + // 2683] + if (postRecord!![i].toInt() == 0) { // empty value + i++ + i += 8 + arrVals.add("") // TODO: Empty Constant should be null? + } else if (postRecord!![i].toInt() == 0x1) { // its a number + i++ + val barr = ByteArray(8) + System.arraycopy(postRecord!!, i, barr, 0, 8) + val `val` = ByteTools.eightBytetoLEDouble(barr) + val d = `val` + arrVals.add(d) + i = i + 8 + } else if (postRecord!![i].toInt() == 0x2) { // its a string + val strLen = ByteTools + .readShort(postRecord!![i + 1].toInt(), postRecord!![i + 2].toInt()).toInt() + i += 3 + val grbt = postRecord!![i++].toInt() + val barr = ByteArray(strLen) + System.arraycopy(postRecord!!, i, barr, 0, strLen) + var strVal = "" + try { + if (grbt and 0x1 == 0x1) { + strVal = String(barr, XLSConstants.UNICODEENCODING) + } else { + strVal = String(barr, XLSConstants.DEFAULTENCODING) + } + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("decoding formula string in array failed: $e") + } + + arrVals.add(strVal) + i += strLen + } else if (postRecord!![i].toInt() == 0x4) { // its a boolean + if (postRecord!![++i].toInt() == 0) + arrVals.add(java.lang.Boolean.valueOf(false)) + else + arrVals.add(java.lang.Boolean.valueOf(true)) + i = i + 8 + } else if (postRecord!![i].toInt() == 0x10) { // it's an error value + val errCode = postRecord!![++i].toInt() + when (errCode) { + 0 -> arrVals.add("#NULL!") + 0x7 -> arrVals.add("#DIV/0!") + 0x0F -> arrVals.add("#VALUE!") + 0x17 -> arrVals.add("#REF!") + 0x1D -> arrVals.add("#NAME!") + 0x24 -> arrVals.add("#NUM!") + 0x2A -> arrVals.add("#N/A!") + } + i = i + 8 + } + nitems++ + if (nitems == (numberOfColumns + 1) * (numberOfRows + 1)) { // Finished with this + // array! + val length = i + i = postRecord!!.size + // length may be less than rgval.length for cases of more + // than one array parameter + // see ExpressionParser.parseExpression + if (postRecord!!.size != length) {// then truncate both record + + // rgval + val tmp = ByteArray(length) + System.arraycopy(postRecord!!, 0, tmp, 0, length) + postRecord = tmp + } + } + } + } catch (e: Exception) { + Logger.logErr("Error Processing Array Formula: $e") + return + } + + } + + fun setVal(arrStr: String) { + var arrStr = arrStr + // remove the initial { and ending } + arrStr = arrStr.substring(1, arrStr.length - 1) + if (arrStr.indexOf("{") != -1) { // SHOULDN'T -- see + // FormulaParser.getPtgsFromFormulaString + Logger.logErr("PtgArray.setVal: Multiple Arrays Encountered") + } + + // parse all array strings into rows, cols + var rows: Array? = null + var cols: Array>? = null + // split rows + rows = arrStr.split(";".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + cols = arrayOfNulls(rows!!.size) + for (i in rows!!.indices) { + val s = rows[i].split(",".toRegex()).toTypedArray() // include empty strings + cols[i] = s + } + var databytes = ByteArray(11) + databytes[0] = 0x60 // 20h=tArrayR, 40h=tArrayV, 60h=tArrayA + isIntermediary = false // init value + if (cols[0].size >= 255) { // 20090824 KSC: apparently sometimes an + // intermediary calculations step can + // include > 256 array elements ... + isIntermediary = true + numberOfColumns = cols[0].size - 1 + } + databytes[8] = (cols[0].size - 1 and 0xFF).toByte() // nc-1 // 20090819 + // KSC: placed + // in wrong pt + // of record: + // was [1] + // [BugTracker + // 2683] + // databytes[8] = (byte)((cols[0].length-1)); // nc-1 // + // 20090819 KSC: placed in wrong pt of record: was [1] + // [BugTracker 2683] + System.arraycopy(ByteTools + .shortToLEBytes((rows.size - 1).toShort()), 0, databytes, 9, 2) // nr-1 + // // + // 20090819 + // KSC: + // placed + // in + // wrong + // pt + // of + // record: + // was + // 2,3 + // [BugTracker + // 2683] + // iterate the array and fill out the data section + for (j in rows.indices) { + for (i in 0 until cols[0].size) { + val valbytes = this.valuesIntoByteArray(cols[j][i]) + databytes = ByteTools.append(valbytes, databytes) + } + } + // populate primary values for rec + record = databytes + this.init(databytes) + } + + /** + * Turns a vector of values into a byte array representation for the data section of this record + * + * @param compVect + * @return + */ + private fun valuesIntoByteArray(constVal: String?): ByteArray { + var databytes = ByteArray(0) + var thisElement = ByteArray(9) + + try { // number? + val d = Double(constVal!!) + thisElement[0] = 0x1 // id for number value + val b = ByteTools.toBEByteArray(d) + System.arraycopy(b, 0, thisElement, 1, b.size) + databytes = ByteTools.append(thisElement, databytes) + } catch (ee: NumberFormatException) { + try { + if (constVal!!.equals("true", ignoreCase = true) || constVal.equals("false", ignoreCase = true)) { + val bb = java.lang.Boolean.valueOf(constVal) + thisElement[0] = 0x4 // id for boolean value + thisElement[1] = (if (bb.booleanValue()) 1 else 0).toByte() + } else if (constVal == null || constVal == "") { // emtpy + // or + // null + // value + thisElement[0] = 0x0 // id for empty value + } else if (constVal[0] == '#') { // it's an error value + thisElement[0] = 0x10 // id for error value + var errCode = 0 + if (constVal == "#NULL!") + errCode = 0 + else if (constVal == "#DIV/0!") + errCode = 0x7 + else if (constVal == "#VALUE!") + errCode = 0x0F + else if (constVal == "#REF!") + errCode = 0x17 + else if (constVal == "#NAME!") + errCode = 0x1D + else if (constVal == "#NUM!") + errCode = 0x24 + else if (constVal == "#N/A!" || constVal == "#N/A" + || constVal == "N/A") + errCode = 0x2A + thisElement[1] = errCode.toByte() + } else { // assume string + thisElement = ByteArray(3) + try { + thisElement = ByteArray(4) + thisElement[0] = 0x2 // id for string + val b = constVal.toByteArray(charset(XLSConstants.UNICODEENCODING)) + System.arraycopy(ByteTools + .shortToLEBytes(b.size.toShort()), 0, thisElement, 1, 2) + thisElement[3] = 1 // compressed= 0, uncompressed= 1 + // (16-bit chars) + thisElement = ByteTools.append(b, thisElement) + } catch (z: UnsupportedEncodingException) { + Logger.logWarn("encoding formula array:$z") + } + + } + databytes = ByteTools.append(thisElement, databytes) + } catch (ex: Exception) { + Logger.logWarn("PtgArray.valuesIntoByteArray: error parsing array element:$ex") + } + + } + + return databytes + } + + /* + * not used + * public int getLength(byte[] b){ + * int co = b[1]; + * int rw = ByteTools.readShort(b[2], b[3]); + * rw++; // appears that rows are not ordinal here... + * int numrecs = co*rw; + * int len = 4; + * int loc = 4; + * for (int i=0;i<=numrecs;i++){ + * if (b[len] == 0x1){ // its a number + * len += 9; + * }else{ + * len += b[len+1] + 2; + * } + * } + * length = len; + * return length; + * } + */ + + override fun toString(): String { + return this.string + } + + /** + * sets the array components values for this PtgArray + * returns the actual array components length + * + * @see ExpressionParser.parseExpression + */ + fun setArrVals(by: ByteArray): Int { + postRecord = by + if (postRecord != null) { + // clear out record array:0= id 1-7=reserved + val b = ByteArray(8) + b[0] = record[0] + record = b + this.parseArrayComponents() + } + return postRecord!!.size + + } + + fun getArrVals(): ByteArray? { + return postRecord + } + + /** + * returns a ptg at the specified location. Assumes that it is a one-dimensional + * array. If you need a multidimensional array please use the other elementAt(int,int)method + * + * @param loc + * @return + */ + fun elementAt(loc: Int): Ptg { + val p = this.components + return p!![loc] + } + + fun elementAt(col: Int, row: Int): Ptg? { + val p = this.components + try { + var loc = 0 + for (i in 0 until row) { + loc += numberOfColumns // 20090816 KSC: why +1???? +1); [BugTracker 2683] + } + loc += col + return elementAt(loc) + } catch (e: ArrayIndexOutOfBoundsException) { + Logger.logErr("PtgArray.elementAt: error retrieving value at [" + + row + "," + col + "]: " + e) + } + + return null + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 4416140231168551393L + } + +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.java deleted file mode 100644 index 60c2f30..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.Formula; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.toolkit.ByteTools; - -/** - * Displays "special" attributes like spaces and "optimized SUMs" - *

                      - * Offset Size Contents - * 0 1 19H - * 1 1 Attribute type flags: - * 01H = This is a tAttrVolatile token (volatile function) - * 02H = This is a tAttrIf token (IF function control) - * 04H = This is a tAttrChoose token (CHOOSE function control) - * 08H = This is a tAttrSkip token (skip part of token array) - * 10H = This is a tAttrSum token (SUM function with one parameter) - * 20H = This is a tAttrAssign token (assignment-style formula in a macro sheet) - * 40H = This is a tAttrSpace token (spaces and carriage returns, BIFF3-BIFF8) - * 41H = This is a tAttrSpaceVolatile token (BIFF3-BIFF8, see below) - * 2 var. Additional information dependent on the attribute type - *

                      - * tAttrSpace: - * 0 1 19H - * 1 1 40H (identifier for the tAttrSpace token), or - * 41H (identifier for the tAttrSpaceVolatile token) - * 2 1 Type and position of the inserted character(s): - * 00H = Spaces before the next token (not allowed before tParen token) - * 01H = Carriage returns before the next token (not allowed before tParen token) - * 02H = Spaces before opening parenthesis (only allowed before tParen token) - * 03H = Carriage returns before opening parenthesis (only allowed before tParen token) - * 04H = Spaces before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) - * 05H = Carriage returns before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) - * 06H = Spaces following the equality sign (only in macro sheets) - * 3 1 Number of inserted spaces or carriage returns - * - * @see Ptg - * @see Formula - */ -public class PtgAtr extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2825828785221803436L; - byte grbit = 0x0; - // int bitAttrSemi = 0; // changed to BitAttrVolatile - int bitAttrVolatile = 0; - int bitAttrIf = 0; - int bitAttrChoose = 0; - int bitAttrGoto = 0; // == bitAttrSkip - int bitAttrSum = 0; - int bitAttrAssign = 0; // changed from bitAttrBaxcel - int bitAttrSpace = 0; - int bitAttrSpaceVolatile = 0; // added - - public PtgAtr() { - } - - public PtgAtr(byte type) { - record = new byte[4]; - record[0] = 0x19; - record[1] = type; - } - - public String getString() { - /* We may not want any text from this record.. */ - this.init(); - if (bitAttrIf > 0) return ""; //"IF("; this is already taken care of by another ptg. - if (bitAttrSum > 0) return "SUM("; -// if(bitAttrSemi > 0) return "SEMI("; - if (bitAttrVolatile > 0) return ""; - if (bitAttrAssign > 0) return " EQUALS "; - if (bitAttrChoose > 0) return "CHOOSE("; - if (bitAttrSpace > 0) return " "; - if (bitAttrGoto > 0) - return ""; // this may be wrong, but as far as I can tell it is just internal for calc purposes. - return "UNKNOWN("; - - - } - - public String toString() { - return this.getString() + this.getString2(); - } - - /** - * return the human-readable String representation of - * the "closing" portion of this Ptg - * such as a closing parenthesis. - */ - public String getString2() { - if (bitAttrIf > 0) return ""; //")"; this is already taken care of by another ptg. - if (bitAttrSum > 0) return ")"; -// if(bitAttrSemi > 0) return ")"; - if (bitAttrVolatile > 0) return ""; - if (bitAttrAssign > 0) return ")"; - if (bitAttrChoose > 0) return ")"; - if (bitAttrGoto > 0) - return ""; // this may be wrong, but as far as I can tell it is just internal for calc purposes. - return ""; - } - - public boolean getIsControl() { - this.init(); - if (getIsPrimitiveOperator()) return false; - /* TODO: Rework bitAttrIf. It optimizes the calculation of if statements - * should not normally be a big deal, but saves the calculation of one of - * the result fields if needed.*/ - if (bitAttrIf > 0) return false; - if (bitAttrSum > 0) return true; -// if(bitAttrSemi > 0)return false; - if (bitAttrVolatile > 0) return false; - if (bitAttrAssign > 0) return true; - if (bitAttrGoto > 0) return false; - if (bitAttrChoose > 0) return false; - return false; - } - - /** - * is the space special -- does it go between vars? - * for now we say sure why not. - */ - public boolean getIsPrimitiveOperator() { - this.init(); - return bitAttrSpace > 0; - } - - public boolean getIsUnaryOperator() { - if (bitAttrIf > 0) return false; - return bitAttrChoose <= 0; - } - - - public boolean getIsOperator() { - return false; - } - - public boolean getIsSpace() { - return bitAttrSpace > 0; - } - - public boolean getIsOperand() { - // - // if(bitAttrSpace > 0)return true; -// Old version? if(bitAttrSemi > 0)return true; // it just shows that this is a volatile function - return bitAttrVolatile > 0; // it just shows that this is a volatile function - } - - /* - Sets the grbit for the record - *///[25, 2, 10, 0] grbit= 2 - public void init() { - grbit = this.getRecord()[1]; - /* john, the following syntax was not reliable, switched with syntax below.... - bitAttrIf = ((grbit & 0x2) >> 4); - */ - // 20060501 KSC: Changed bitAttrVolatile operation + some names -// if ((grbit & 0x1)== 0x1){bitAttrSemi = 1;} - if ((grbit & 0x1) == 0x1) { - bitAttrVolatile = 1; - } // volatile= a function that needs to be recalculated always, such as NOW() - if ((grbit & 0x2) == 0x2) { - bitAttrIf = 1; - } - if ((grbit & 0x4) == 0x4) { - bitAttrChoose = 1; - } - if ((grbit & 0x8) == 0x8) { - bitAttrGoto = 1; - } - if ((grbit & 0x10) == 0x10) { - bitAttrSum = 1; - } - if ((grbit & 0x20) == 0x20) { - bitAttrAssign = 1; - } // changed name from bitAttrBaxcel - if ((grbit & 0x40) == 0x40) { - bitAttrSpace = 1; - } - if ((grbit & 0x41) == 0x41) { - bitAttrSpaceVolatile = 1; - } - - - } - - /** - * return the human-readable String representation of - * this ptg -- if applicable - *

                      - * public String getString(){ - * byte[] br = this.getRecord(); - * byte[] db = new byte[br.length-1]; // strip opcode - * System.arraycopy(br, 1, db, 0, db.length); - * return ""; // new String(db); - * } - */ - public int getLength(byte[] b) { - if ((b[0] & 0x4) == 0x4) { - int i = ByteTools.readShort(b[1], b[2]); - i += 4; - return i; - } - return getLength(); - } - - public int getLength() { - return PTG_ATR_LENGTH; - } - - - Ptg[] alloperands = null; // cached! - - /* - Calculate the value of this ptg. - */ - public Ptg calculatePtg(Ptg[] pthing) - throws FunctionNotSupportedException, CalculationException { - Ptg returnPtg = null; - if (this.bitAttrSum > 0) { - returnPtg = MathFunctionCalculator.calcSum(pthing); - } - return returnPtg; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.kt new file mode 100644 index 0000000..0f292d2 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgAtr.kt @@ -0,0 +1,250 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.Formula +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.toolkit.ByteTools + +/** + * Displays "special" attributes like spaces and "optimized SUMs" + * + * + * Offset Size Contents + * 0 1 19H + * 1 1 Attribute type flags: + * 01H = This is a tAttrVolatile token (volatile function) + * 02H = This is a tAttrIf token (IF function control) + * 04H = This is a tAttrChoose token (CHOOSE function control) + * 08H = This is a tAttrSkip token (skip part of token array) + * 10H = This is a tAttrSum token (SUM function with one parameter) + * 20H = This is a tAttrAssign token (assignment-style formula in a macro sheet) + * 40H = This is a tAttrSpace token (spaces and carriage returns, BIFF3-BIFF8) + * 41H = This is a tAttrSpaceVolatile token (BIFF3-BIFF8, see below) + * 2 var. Additional information dependent on the attribute type + * + * + * tAttrSpace: + * 0 1 19H + * 1 1 40H (identifier for the tAttrSpace token), or + * 41H (identifier for the tAttrSpaceVolatile token) + * 2 1 Type and position of the inserted character(s): + * 00H = Spaces before the next token (not allowed before tParen token) + * 01H = Carriage returns before the next token (not allowed before tParen token) + * 02H = Spaces before opening parenthesis (only allowed before tParen token) + * 03H = Carriage returns before opening parenthesis (only allowed before tParen token) + * 04H = Spaces before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) + * 05H = Carriage returns before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) + * 06H = Spaces following the equality sign (only in macro sheets) + * 3 1 Number of inserted spaces or carriage returns + * + * @see Ptg + * + * @see Formula + */ +class PtgAtr : GenericPtg, Ptg { + internal var grbit: Byte = 0x0 + // int bitAttrSemi = 0; // changed to BitAttrVolatile + internal var bitAttrVolatile = 0 + internal var bitAttrIf = 0 + internal var bitAttrChoose = 0 + internal var bitAttrGoto = 0 // == bitAttrSkip + internal var bitAttrSum = 0 + internal var bitAttrAssign = 0 // changed from bitAttrBaxcel + internal var bitAttrSpace = 0 + internal var bitAttrSpaceVolatile = 0 // added + + override/* We may not want any text from this record.. *///"IF("; this is already taken care of by another ptg. + // if(bitAttrSemi > 0) return "SEMI("; + // this may be wrong, but as far as I can tell it is just internal for calc purposes. + val string: String + get() { + this.init() + if (bitAttrIf > 0) return "" + if (bitAttrSum > 0) return "SUM(" + if (bitAttrVolatile > 0) return "" + if (bitAttrAssign > 0) return " EQUALS " + if (bitAttrChoose > 0) return "CHOOSE(" + if (bitAttrSpace > 0) return " " + return if (bitAttrGoto > 0) "" else "UNKNOWN(" + + + } + + /** + * return the human-readable String representation of + * the "closing" portion of this Ptg + * such as a closing parenthesis. + */ + override//")"; this is already taken care of by another ptg. + // if(bitAttrSemi > 0) return ")"; + // this may be wrong, but as far as I can tell it is just internal for calc purposes. + val string2: String + get() { + if (bitAttrIf > 0) return "" + if (bitAttrSum > 0) return ")" + if (bitAttrVolatile > 0) return "" + if (bitAttrAssign > 0) return ")" + if (bitAttrChoose > 0) return ")" + return if (bitAttrGoto > 0) "" else "" + } + + override/* TODO: Rework bitAttrIf. It optimizes the calculation of if statements + * should not normally be a big deal, but saves the calculation of one of + * the result fields if needed.*/// if(bitAttrSemi > 0)return false; + val isControl: Boolean + get() { + this.init() + if (isPrimitiveOperator) return false + if (bitAttrIf > 0) return false + if (bitAttrSum > 0) return true + if (bitAttrVolatile > 0) return false + if (bitAttrAssign > 0) return true + if (bitAttrGoto > 0) return false + return if (bitAttrChoose > 0) false else false + } + + /** + * is the space special -- does it go between vars? + * for now we say sure why not. + */ + override val isPrimitiveOperator: Boolean + get() { + this.init() + return bitAttrSpace > 0 + } + + override val isUnaryOperator: Boolean + get() = if (bitAttrIf > 0) false else bitAttrChoose <= 0 + + + override val isOperator: Boolean + get() = false + + val isSpace: Boolean + get() = bitAttrSpace > 0 + + override// + // if(bitAttrSpace > 0)return true; + // Old version? if(bitAttrSemi > 0)return true; // it just shows that this is a volatile function + // it just shows that this is a volatile function + val isOperand: Boolean + get() = bitAttrVolatile > 0 + + override val length: Int + get() = Ptg.PTG_ATR_LENGTH + + + internal var alloperands: Array? = null // cached! + + constructor() {} + + constructor(type: Byte) { + record = ByteArray(4) + record[0] = 0x19 + record[1] = type + } + + override fun toString(): String { + return this.string + this.string2 + } + + /* + Sets the grbit for the record + *///[25, 2, 10, 0] grbit= 2 + fun init() { + grbit = this.record[1] + /* john, the following syntax was not reliable, switched with syntax below.... + bitAttrIf = ((grbit & 0x2) >> 4); + */ + // 20060501 KSC: Changed bitAttrVolatile operation + some names + // if ((grbit & 0x1)== 0x1){bitAttrSemi = 1;} + if (grbit and 0x1 == 0x1) { + bitAttrVolatile = 1 + } // volatile= a function that needs to be recalculated always, such as NOW() + if (grbit and 0x2 == 0x2) { + bitAttrIf = 1 + } + if (grbit and 0x4 == 0x4) { + bitAttrChoose = 1 + } + if (grbit and 0x8 == 0x8) { + bitAttrGoto = 1 + } + if (grbit and 0x10 == 0x10) { + bitAttrSum = 1 + } + if (grbit and 0x20 == 0x20) { + bitAttrAssign = 1 + } // changed name from bitAttrBaxcel + if (grbit and 0x40 == 0x40) { + bitAttrSpace = 1 + } + if (grbit and 0x41 == 0x41) { + bitAttrSpaceVolatile = 1 + } + + + } + + /** + * return the human-readable String representation of + * this ptg -- if applicable + * + * + * public String getString(){ + * byte[] br = this.getRecord(); + * byte[] db = new byte[br.length-1]; // strip opcode + * System.arraycopy(br, 1, db, 0, db.length); + * return ""; // new String(db); + * } + */ + fun getLength(b: ByteArray): Int { + if (b[0] and 0x4 == 0x4) { + var i = ByteTools.readShort(b[1].toInt(), b[2].toInt()).toInt() + i += 4 + return i + } + return length + } + + /* + Calculate the value of this ptg. + */ + @Throws(FunctionNotSupportedException::class, CalculationException::class) + override fun calculatePtg(pthing: Array): Ptg? { + var returnPtg: Ptg? = null + if (this.bitAttrSum > 0) { + returnPtg = MathFunctionCalculator.calcSum(pthing) + } + return returnPtg + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -2825828785221803436L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgBool.java b/src/main/java/io/starter/formats/XLS/formulas/PtgBool.java deleted file mode 100644 index 9dca64f..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgBool.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.ExpressionParser; - -/* - A parse thing that represents a boolean value. This is made up of two bytes, - the PtgID (0x1D) and a byte representing the boolean value (0 or 1); -*/ - -public class PtgBool extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7270271326251770439L; - - public boolean getIsOperator() { - return false; - } - - public boolean getIsOperand() { - return true; - } - - boolean val = false; - - /** - * return the human-readable String representation of the ptg - */ - public String getString() { - return String.valueOf(val); - } - - public String toString() { - return String.valueOf(val); - } - - public Object getValue() { - Boolean b = Boolean.valueOf(val); - return b; - } - - public void setVal(boolean boo) { - val = boo; - this.updateRecord(); - } - - public void init(byte[] rec) { - this.record = rec; - ptgId = rec[0]; - val = rec[1] != 0; - } - - public PtgBool() { - record = new byte[2]; - ptgId = ExpressionParser.ptgBool; - record[0] = ptgId; - } - - - public PtgBool(boolean b) { - ptgId = ExpressionParser.ptgBool; - val = b; - this.updateRecord(); - } - - public boolean getBooleanValue() { - return val; - } - - public void updateRecord() { - record = new byte[2]; - record[0] = ptgId; - if (val) { - record[1] = 1; - } else { - record[1] = 0; - } - } - - public int getLength() { - return PTG_BOOL_LENGTH; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgBool.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgBool.kt new file mode 100644 index 0000000..9307e05 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgBool.kt @@ -0,0 +1,101 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.ExpressionParser + +/* + A parse thing that represents a boolean value. This is made up of two bytes, + the PtgID (0x1D) and a byte representing the boolean value (0 or 1); +*/ + +class PtgBool : GenericPtg, Ptg { + + override val isOperator: Boolean + get() = false + + override val isOperand: Boolean + get() = true + + var booleanValue = false + internal set + + /** + * return the human-readable String representation of the ptg + */ + override val string: String + get() = booleanValue.toString() + + override val value: Any? + get() = java.lang.Boolean.valueOf(booleanValue) + + override val length: Int + get() = Ptg.PTG_BOOL_LENGTH + + override fun toString(): String { + return booleanValue.toString() + } + + fun setVal(boo: Boolean) { + booleanValue = boo + this.updateRecord() + } + + override fun init(rec: ByteArray) { + this.record = rec + opcode = rec[0] + booleanValue = rec[1].toInt() != 0 + } + + constructor() { + record = ByteArray(2) + opcode = ExpressionParser.ptgBool.toByte() + record[0] = opcode + } + + + constructor(b: Boolean) { + opcode = ExpressionParser.ptgBool.toByte() + booleanValue = b + this.updateRecord() + } + + override fun updateRecord() { + record = ByteArray(2) + record[0] = opcode + if (booleanValue) { + record[1] = 1 + } else { + record[1] = 0 + } + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -7270271326251770439L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.java deleted file mode 100644 index 0c0a7d7..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.FastAddVector; -import io.starter.toolkit.Logger; - -import java.util.Enumeration; - -/** - * PtgCalculator handles some of the standard calls that all of the - * calculator classes need. - */ - - -public class PtgCalculator { - - - /** - * getLongValue is for single-operand functions. - * It returns NaN for calculations that have too many operands. - * - * @param operands - * @return - */ - protected static long getLongValue(Ptg[] operands) { - Ptg[] components = operands[0].getComponents(); - if (components != null) { // check if too many operands TODO: check that ONE is ok?? - if (components.length > 1) { - return (long) Double.NaN; - } - } - if (operands.length > 1) { // not supported by function - Logger.logWarn("PtgCalculator getting Long Value for operand failed: - UNSUPPORTED BY FUNCTION"); - return (long) Double.NaN; - } - Double d = null; - try { - d = new Double(operands[0].getDoubleVal()); - } catch (NumberFormatException e) { - Logger.logWarn("PtgCalculator getting Long Value for operand failed: " + e); - return (long) Double.NaN; - } - return d.longValue(); - } - - /* - * See getLongValue(operand[]) - * Does the same thing with a single operand - */ - protected static long getLongValue(Ptg operand) { - Ptg[] ptgArr = new Ptg[1]; - ptgArr[0] = operand; - return getLongValue(ptgArr); - } - - - // returns an array of longs from an array of ptg's - protected static long[] getLongValueArray(Ptg[] operands) { - Ptg[] alloperands = getAllComponents(operands); - long[] l = new long[alloperands.length]; - for (int i = 0; i < alloperands.length; i++) { - try { - Double dd = new Double(operands[i].getDoubleVal()); - l[i] = dd.longValue(); - } catch (NumberFormatException e) { - Logger.logWarn("PtgCalculator getting Long value array failed: " + e); - l[i] = (long) Double.NaN; - } - } - return l; - } - - /** - * getDoubleValue is for multi-operand functions. It returns NaN - * for calculations that have to many operands. - * - * @param operands - * @return - * @throws CircularReferenceException TODO - */ - protected static double[] getDoubleValueArray(Ptg[] operands) throws CalculationException { - - Double d = null; - // we don't know the size ahead of time, so use a vector for now. - CompatibleVector cv = new CompatibleVector(); - - for (int i = 0; i < operands.length; i++) { - // is it multidimensional? - Ptg[] pthings = operands[i].getComponents(); // optimized -- do it once! -jm - if (pthings != null) { - for (int x = 0; x < pthings.length; x++) { - cv.add(pthings[x]); - } - } else { - cv.add(operands[i]); - } - } - - double[] darr = new double[cv.size()]; - int i = 0; - Enumeration en = cv.elements(); - while (en.hasMoreElements()) { - d = null;//new Double(0.0); // 20081229 KSC: reset - Ptg pthing = (Ptg) en.nextElement(); - Object ob = pthing.getValue(); - if (ob == null || ob.toString().trim().equals("")) { // 20060802 KSC: added trim - darr[i] = 0; - } else if (ob.toString().equals("#CIR_ERR!")) { - throw new CircularReferenceException(CalculationException.VALUE); - } else { - - try { - if (ob instanceof Double) { - d = (Double) ob; - } else { - String s = ob.toString(); - d = new Double(s); - } - } catch (NumberFormatException e) { - try { - String s = ob.toString(); - if (s.equals("#N/A")) { // 20090130 KSC: if error value, propagate error (ala Excel) -- null caught in calling method propagates "#N/A" - return null; - } - } catch (Exception ee) { - Logger.logWarn("PtgCalculator getting Double value array failed: " + ee); - d = new Double(Double.NaN); - } - } - if (d != null) - darr[i] = d.doubleValue(); - } - i++; - } - return darr; - } - - protected static double[] getDoubleValueArray(Ptg operands) throws CalculationException { - Ptg[] ptgarr = new Ptg[1]; - ptgarr[0] = operands; - return getDoubleValueArray(ptgarr); - } - - /** - * return a 2-dimenional array of double values - * i.e. keep array structure of reference and array type parameters - *
                      ASSUMPTIONS: - *
                      1- accepts only 2-d references i.e. ranges are on 1 sheet - *
                      2- assumes that range reference are in proper notation i.e A1:B6, NOT B6:A1 - * - * @param operand Ptg - * @return double[][] - */ - protected static double[][] getArray(Ptg operand) throws Exception { - int nrows, ncols; - double[][] arr = null; - - if (operand instanceof PtgRef) { - int[] rc = ((PtgRef) operand).getIntLocation(); - String sheet = ((PtgRef) operand).getSheetName(); - io.starter.formats.XLS.WorkBook bk = operand.getParentRec().getWorkBook(); - nrows = rc[2] - rc[0] + 1; - ncols = rc[3] - rc[1] + 1; - arr = new double[nrows][ncols]; - for (int j = rc[1]; j <= rc[3]; j++) { - for (int i = rc[0]; i <= rc[2]; i++) { - String cell = ExcelTools.formatLocation(new int[]{i, j}); - arr[i - rc[0]][j - rc[1]] = bk.getCell(sheet, cell).getDblVal(); - } - } - - } else { // should be an array - String arrStr = operand.toString().substring(1); - arrStr = arrStr.substring(0, arrStr.length() - 1); - String[] rows = arrStr.split(";"); - arr = new double[rows.length][]; - for (int i = 0; i < rows.length; i++) { - String[] s = rows[i].split(",", -1); // include empty strings - arr[i] = new double[s.length]; - for (int j = 0; j < s.length; j++) { - arr[i][j] = new Double(s[j]).doubleValue(); - } - } - } - return arr; - } - - /* - * creates a generic error ptg - */ - protected static Ptg getError() { - PtgErr perr = new PtgErr(PtgErr.ERROR_NULL); - return perr; - } - - /** - * returns an #VALUE! error ptg - * - * @return - */ - protected static Ptg getValueError() { - return new PtgErr(PtgErr.ERROR_VALUE); - } - - /** - * returns an #NA! error ptg - * - * @return - */ - protected static Ptg getNAError() { - return new PtgErr(PtgErr.ERROR_NA); - } - - - /* - * Get all components recurses through the ptg's and returns an array - * of single ptg's for all the ptgs in the operands array. This means it - * converts arrays to ref's, etc. - */ - protected static Ptg[] getAllComponents(Ptg[] operands) { - - if (operands.length == 1) { - Ptg[] ret = operands[0].getComponents(); - if (ret == null) - return operands; - } - - FastAddVector v = new FastAddVector(); - for (int i = 0; i < operands.length; i++) { - Ptg[] pthings = operands[i].getComponents(); // optimized -- do it once! -jm - if (pthings != null) { - for (int x = 0; x < pthings.length; x++) { - v.add(pthings[x]); - } - } else { - v.add(operands[i]); - } - } - Ptg[] res = new Ptg[v.size()]; - res = (Ptg[]) v.toArray(res); - return res; - } - - /* - * Get all components recurses through the ptg's and returns an array - * of single ptg's for all the ptgs in the operands array. This means it - * converts arrays to ref's, etc. - */ - protected static Ptg[] getAllComponents(Ptg operand) { - Ptg[] ptgArr = new Ptg[1]; - ptgArr[0] = operand; - return getAllComponents(ptgArr); - } - - /* - * Returns the boolean value of a PTG. If no boolean available then it returns false - * Add more types here if they are available/needed - */ - protected static boolean getBooleanValue(Ptg operand) { - if (operand instanceof PtgBool) { - PtgBool b = (PtgBool) operand; - return b.getBooleanValue(); - } else if (operand instanceof PtgInt) { - PtgInt i = (PtgInt) operand; - return i.getBooleanVal(); - } - return false; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.kt new file mode 100644 index 0000000..5220cc6 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgCalculator.kt @@ -0,0 +1,293 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.ExcelTools +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.FastAddVector +import io.starter.toolkit.Logger + +import java.util.Enumeration + +/** + * PtgCalculator handles some of the standard calls that all of the + * calculator classes need. + */ + + +object PtgCalculator { + + /* + * creates a generic error ptg + */ + val error: Ptg + get() = PtgErr(PtgErr.ERROR_NULL) + + /** + * returns an #VALUE! error ptg + * + * @return + */ + val valueError: Ptg + get() = PtgErr(PtgErr.ERROR_VALUE) + + /** + * returns an #NA! error ptg + * + * @return + */ + val naError: Ptg + get() = PtgErr(PtgErr.ERROR_NA) + + + /** + * getLongValue is for single-operand functions. + * It returns NaN for calculations that have too many operands. + * + * @param operands + * @return + */ + internal fun getLongValue(operands: Array): Long { + val components = operands[0].components + if (components != null) { // check if too many operands TODO: check that ONE is ok?? + if (components.size > 1) { + return java.lang.Double.NaN.toLong() + } + } + if (operands.size > 1) { // not supported by function + Logger.logWarn("PtgCalculator getting Long Value for operand failed: - UNSUPPORTED BY FUNCTION") + return java.lang.Double.NaN.toLong() + } + var d: Double? = null + try { + d = operands[0].doubleVal + } catch (e: NumberFormatException) { + Logger.logWarn("PtgCalculator getting Long Value for operand failed: $e") + return java.lang.Double.NaN.toLong() + } + + return d.toLong() + } + + /* + * See getLongValue(operand[]) + * Does the same thing with a single operand + */ + internal fun getLongValue(operand: Ptg): Long { + val ptgArr = arrayOfNulls(1) + ptgArr[0] = operand + return getLongValue(ptgArr) + } + + + // returns an array of longs from an array of ptg's + internal fun getLongValueArray(operands: Array): LongArray { + val alloperands = getAllComponents(operands) + val l = LongArray(alloperands.size) + for (i in alloperands.indices) { + try { + val dd = operands[i].doubleVal + l[i] = dd.toLong() + } catch (e: NumberFormatException) { + Logger.logWarn("PtgCalculator getting Long value array failed: $e") + l[i] = java.lang.Double.NaN.toLong() + } + + } + return l + } + + /** + * getDoubleValue is for multi-operand functions. It returns NaN + * for calculations that have to many operands. + * + * @param operands + * @return + * @throws CircularReferenceException TODO + */ + @Throws(CalculationException::class) + internal fun getDoubleValueArray(operands: Array): DoubleArray? { + + var d: Double? = null + // we don't know the size ahead of time, so use a vector for now. + val cv = CompatibleVector() + + for (i in operands.indices) { + // is it multidimensional? + val pthings = operands[i].components // optimized -- do it once! -jm + if (pthings != null) { + for (x in pthings.indices) { + cv.add(pthings[x]) + } + } else { + cv.add(operands[i]) + } + } + + val darr = DoubleArray(cv.size) + var i = 0 + val en = cv.elements() + while (en.hasMoreElements()) { + d = null//new Double(0.0); // 20081229 KSC: reset + val pthing = en.nextElement() as Ptg + val ob = pthing.value + if (ob == null || ob.toString().trim { it <= ' ' } == "") { // 20060802 KSC: added trim + darr[i] = 0.0 + } else if (ob.toString() == "#CIR_ERR!") { + throw CircularReferenceException(CalculationException.VALUE) + } else { + + try { + if (ob is Double) { + d = ob + } else { + val s = ob.toString() + d = Double(s) + } + } catch (e: NumberFormatException) { + try { + val s = ob.toString() + if (s == "#N/A") { // 20090130 KSC: if error value, propagate error (ala Excel) -- null caught in calling method propagates "#N/A" + return null + } + } catch (ee: Exception) { + Logger.logWarn("PtgCalculator getting Double value array failed: $ee") + d = java.lang.Double.NaN + } + + } + + if (d != null) + darr[i] = d.toDouble() + } + i++ + } + return darr + } + + @Throws(CalculationException::class) + internal fun getDoubleValueArray(operands: Ptg): DoubleArray? { + val ptgarr = arrayOfNulls(1) + ptgarr[0] = operands + return getDoubleValueArray(ptgarr) + } + + /** + * return a 2-dimenional array of double values + * i.e. keep array structure of reference and array type parameters + *

                      ASSUMPTIONS: + *

                      1- accepts only 2-d references i.e. ranges are on 1 sheet + *

                      2- assumes that range reference are in proper notation i.e A1:B6, NOT B6:A1 + * + * @param operand Ptg + * @return double[][] + */ + @Throws(Exception::class) + internal fun getArray(operand: Ptg): Array { + val nrows: Int + val ncols: Int + var arr: Array? = null + + if (operand is PtgRef) { + val rc = operand.intLocation + val sheet = operand.sheetName + val bk = operand.parentRec.workBook + nrows = rc!![2] - rc[0] + 1 + ncols = rc[3] - rc[1] + 1 + arr = Array(nrows) { DoubleArray(ncols) } + for (j in rc[1]..rc[3]) { + for (i in rc[0]..rc[2]) { + val cell = ExcelTools.formatLocation(intArrayOf(i, j)) + arr[i - rc[0]][j - rc[1]] = bk!!.getCell(sheet, cell).dblVal + } + } + + } else { // should be an array + var arrStr = operand.toString().substring(1) + arrStr = arrStr.substring(0, arrStr.length - 1) + val rows = arrStr.split(";".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + arr = arrayOfNulls(rows.size) + for (i in rows.indices) { + val s = rows[i].split(",".toRegex()).toTypedArray() // include empty strings + arr[i] = DoubleArray(s.size) + for (j in s.indices) { + arr[i][j] = Double(s[j]) + } + } + } + return arr + } + + + /* + * Get all components recurses through the ptg's and returns an array + * of single ptg's for all the ptgs in the operands array. This means it + * converts arrays to ref's, etc. + */ + internal fun getAllComponents(operands: Array): Array { + + if (operands.size == 1) { + val ret = operands[0].components ?: return operands + } + + val v = FastAddVector() + for (i in operands.indices) { + val pthings = operands[i].components // optimized -- do it once! -jm + if (pthings != null) { + for (x in pthings.indices) { + v.add(pthings[x]) + } + } else { + v.add(operands[i]) + } + } + var res = arrayOfNulls(v.size) + res = v.toTypedArray() as Array + return res + } + + /* + * Get all components recurses through the ptg's and returns an array + * of single ptg's for all the ptgs in the operands array. This means it + * converts arrays to ref's, etc. + */ + internal fun getAllComponents(operand: Ptg): Array { + val ptgArr = arrayOfNulls(1) + ptgArr[0] = operand + return getAllComponents(ptgArr) + } + + /* + * Returns the boolean value of a PTG. If no boolean available then it returns false + * Add more types here if they are available/needed + */ + internal fun getBooleanValue(operand: Ptg): Boolean { + if (operand is PtgBool) { + return operand.booleanValue + } else if (operand is PtgInt) { + return operand.booleanVal + } + return false + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgConcat.java b/src/main/java/io/starter/formats/XLS/formulas/PtgConcat.java deleted file mode 100644 index a7dffbc..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgConcat.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -/* - Ptg that is a Concatenation Operand. - Appends the top operand to the second-to-top Operand - - * @see Ptg - * @see Formula - - -*/ -public class PtgConcat extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6671404163121438253L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } // 20060512 KSC: added - - public PtgConcat() { - ptgId = 0x8; - record = new byte[1]; - record[0] = 0x8; - } - - /** - * return the human-readable String representation of - */ - public String getString() { -// return "CONCAT("; // 20060512 KSC: mod - return "&"; - } - - public String getString2() { -// return ")"; - return ""; - } - - public int getLength() { - return PTG_CONCAT_LENGTH; - } - - public String toString() { // KSC added - return this.getString(); - } - - /** - * Operator specific calculate method, this Concatenates two values - */ - public Ptg calculatePtg(Ptg[] form) { - try { - Object[] o = getStringValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o == null || o.length != 2) { - //if (o!=null) - // Logger.logWarn("calculating formula failed, wrong number of values in PtgConcat"); - return new PtgErr(PtgErr.ERROR_VALUE); - } - if (!o[0].getClass().isArray()) { - String[] s = new String[2]; - try { // 20090216 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 - s[0] = String.valueOf(((Double) o[0]).intValue()); - } catch (Exception e) { - s[0] = o[0].toString(); - } - try { // 20090216 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 - s[1] = String.valueOf(((Double) o[1]).intValue()); - } catch (Exception e) { - s[1] = o[1].toString(); - } - - String returnVal = s[0] + s[1]; - PtgStr pstr = new PtgStr(returnVal); - return pstr; - } else { - return null; - } - } catch (Exception e) { // handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgConcat.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgConcat.kt new file mode 100644 index 0000000..202837e --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgConcat.kt @@ -0,0 +1,113 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +/* + Ptg that is a Concatenation Operand. + Appends the top operand to the second-to-top Operand + + * @see Ptg + * @see Formula + + +*/ +class PtgConcat : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true // 20060512 KSC: added + + /** + * return the human-readable String representation of + */ + override// return "CONCAT("; // 20060512 KSC: mod + val string: String + get() = "&" + + override// return ")"; + val string2: String + get() = "" + + override val length: Int + get() = Ptg.PTG_CONCAT_LENGTH + + init { + opcode = 0x8 + record = ByteArray(1) + record[0] = 0x8 + } + + override fun toString(): String { // KSC added + return this.string + } + + /** + * Operator specific calculate method, this Concatenates two values + */ + override fun calculatePtg(form: Array): Ptg? { + try { + val o = GenericPtg.getStringValuesFromPtgs(form) + // there should always be only two ptg's in this, error if not. + if (o == null || o.size != 2) { + //if (o!=null) + // Logger.logWarn("calculating formula failed, wrong number of values in PtgConcat"); + return PtgErr(PtgErr.ERROR_VALUE) + } + if (!o[0].javaClass.isArray()) { + val s = arrayOfNulls(2) + try { // 20090216 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 + s[0] = (o[0] as Double).toInt().toString() + } catch (e: Exception) { + s[0] = o[0].toString() + } + + try { // 20090216 KSC: try to convert numbers to ints when converting to string as otherwise all numbers come out as x.0 + s[1] = (o[1] as Double).toInt().toString() + } catch (e: Exception) { + s[1] = o[1].toString() + } + + val returnVal = s[0] + s[1] + return PtgStr(returnVal) + } else { + return null + } + } catch (e: Exception) { // handle error ala Excel + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 6671404163121438253L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.java b/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.java deleted file mode 100644 index aa668cf..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import java.lang.reflect.Array; - - -/* - Ptg that is a division operand - - * @see Ptg - * @see Formula - - -*/ -public final class PtgDiv extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4046772548262378126L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public PtgDiv() { - ptgId = 0x6; - record = new byte[1]; - record[0] = 0x6; - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return "/"; - } - - public String toString() { - return this.getString(); - } - - public int getLength() { - return PTG_DIV_LENGTH; - } - - /* Operator specific calculate method, this one divides two values. - - */ - public Ptg calculatePtg(Ptg[] form) { - try { - Object[] o = getValuesFromPtgs(form); - if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o.length != 2) { - //Logger.logInfo("calculating formula, wrong number of values in PtgDiv"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null - } - double o0 = 0, o1 = 0; - try { - o0 = getDoubleValue(o[0], this.parent_rec); - o1 = getDoubleValue(o[1], this.parent_rec); - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - if (o1 == 0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - double returnVal = (o0 / o1); - // create a container ptg for these. - PtgNumber n = new PtgNumber(returnVal); - return n; - } else { // handle array formulas - String retArry = ""; - int nArrays = java.lang.reflect.Array.getLength(o); - if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i = 0; i < nArrays - 1; i += 2) { - Object secondOp = null; - boolean comparitorIsArray = o[i + 1].getClass().isArray(); - if (!comparitorIsArray) secondOp = o[i + 1]; - for (int j = 0; j < nVals; j++) { - Object firstOp = Array.get(o[i], j); // first array index j - if (comparitorIsArray) - secondOp = Array.get(o[i + 1], j); // second array index j - double o0 = 0, o1 = 0; - try { - o0 = getDoubleValue(firstOp, this.parent_rec); - o1 = getDoubleValue(secondOp, this.parent_rec); - } catch (NumberFormatException e) { - retArry = retArry + "#VALUE!" + ","; - continue; - } - if (o1 != 0) { - double retVal = o0 / o1; - retArry = retArry + retVal + ","; - } else - retArry = retArry + "#DIV/0!" + ","; - } - } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; - } - } catch (NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } catch (Exception e) { // 20081125 KSC: handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.kt new file mode 100644 index 0000000..74f2a1a --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgDiv.kt @@ -0,0 +1,146 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import java.lang.reflect.Array + + +/* + Ptg that is a division operand + + * @see Ptg + * @see Formula + + +*/ +class PtgDiv : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val string: String + get() = "/" + + override val length: Int + get() = Ptg.PTG_DIV_LENGTH + + init { + opcode = 0x6 + record = ByteArray(1) + record[0] = 0x6 + } + + override fun toString(): String { + return this.string + } + + /* Operator specific calculate method, this one divides two values. + + */ + override fun calculatePtg(form: Array): Ptg? { + try { + val o = GenericPtg.getValuesFromPtgs(form) ?: return PtgErr(PtgErr.ERROR_VALUE) +// some error in value(s) + if (!o[0].javaClass.isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o.size != 2) { + //Logger.logInfo("calculating formula, wrong number of values in PtgDiv"); + return PtgErr(PtgErr.ERROR_VALUE) // 20081203 KSC: handle error's ala Excel return null + } + var o0 = 0.0 + var o1 = 0.0 + try { + o0 = GenericPtg.getDoubleValue(o[0], this.parentRec) + o1 = GenericPtg.getDoubleValue(o[1], this.parentRec) + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + if (o1 == 0.0) + return PtgErr(PtgErr.ERROR_DIV_ZERO) + val returnVal = o0 / o1 + // create a container ptg for these. + return PtgNumber(returnVal) + } else { // handle array formulas + var retArry = "" + val nArrays = java.lang.reflect.Array.getLength(o) + if (nArrays != 2) return PtgErr(PtgErr.ERROR_VALUE) + val nVals = java.lang.reflect.Array.getLength(o[0]) // use first array element to determine length of values as subsequent vals might not be arrays + var i = 0 + while (i < nArrays - 1) { + var secondOp: Any? = null + val comparitorIsArray = o[i + 1].javaClass.isArray() + if (!comparitorIsArray) secondOp = o[i + 1] + for (j in 0 until nVals) { + val firstOp = Array.get(o[i], j) // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j) // second array index j + var o0 = 0.0 + var o1 = 0.0 + try { + o0 = GenericPtg.getDoubleValue(firstOp, this.parentRec) + o1 = GenericPtg.getDoubleValue(secondOp, this.parentRec) + } catch (e: NumberFormatException) { + retArry = "$retArry#VALUE!," + continue + } + + if (o1 != 0.0) { + val retVal = o0 / o1 + retArry = "$retArry$retVal," + } else + retArry = "$retArry#DIV/0!," + } + i += 2 + } + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa + } + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } catch (e: Exception) { // 20081125 KSC: handle error ala Excel + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -4046772548262378126L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.java b/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.java deleted file mode 100644 index 75f66a8..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.toolkit.Logger; - -import java.lang.reflect.Array; - - -/* - Equals operand - - Evaluates to true if the top two operands are equal, otherwise FALSE - - - * @see Ptg - * @see Formula - - -*/ -public class PtgEQ extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5446048862531696036L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public PtgEQ() { - ptgId = 0xB; - record = new byte[1]; - record[0] = 0xB; - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return "="; - } - - public String toString() { - return this.getString(); - } - - public int getLength() { - return PTG_EQ_LENGTH; - } - - /* Operator specific calculate method, this one determines if the second-to-top - operand is equal to the top operand; Returns a PtgBool - - */ - public Ptg calculatePtg(Ptg[] form) { - boolean res = false; - // there should always be only two ptg's in this, error if not. - if (form.length != 2) { - Logger.logInfo("calculating formula, wrong number of values in PtgEQ"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel - } - // check for null referenced values, a null reference is equal to the string ""; - Object[] o = getValuesFromPtgs(form); - if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (o[1].getClass().isArray() && !o[0].getClass().isArray()) { - Object tmp = o[0]; - o[0] = o[1]; - o[1] = tmp; - } - if (!o[0].getClass().isArray()) { - if (o.length != 2) - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; - // blank handling: - // determine if any of the operands are double - if true, - // then blank comparisons will be treated as 0's - boolean isDouble = false; - for (int i = 0; i < 2 && !isDouble; i++) { - //if (!form[i].isBlank()) - isDouble = ((o[i] instanceof Double)); - } - for (int i = 0; i < 2; i++) { - //if (form[i].isBlank()) { - if (o[i] != null && o[i].toString().equals("")) { - if (isDouble) - o[i] = new Double(0.0); - else - o[i] = ""; // in this case, empty cells are handled as blank, not zero - } - } - if (o[0] == o[1]) - res = true; - else if (o[0] == null || o[1] == null) - res = false; - else if (o[0] instanceof Double && o[1] instanceof Double) - res = (Math.abs((((Double) o[0]).doubleValue()) - ((Double) o[1]).doubleValue())) < doublePrecision; // compare equality to certain precision - else if (o[0].toString().equalsIgnoreCase(o[1].toString())) - res = true; - // handle empty cell references vs string case, 0.0 does not match - } else { // handle array formulas - String retArry = ""; - int nArrays = java.lang.reflect.Array.getLength(o); - if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - if (nVals == 0) { - retArry = "{false}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; - } - for (int i = 0; i < nArrays - 1; i += 2) { - res = false; - Object secondOp = null; - boolean comparitorIsArray = o[i + 1].getClass().isArray(); - if (!comparitorIsArray) secondOp = o[i + 1]; - for (int j = 0; j < nVals; j++) { - Object firstOp = Array.get(o[i], j); // first array index j - if (comparitorIsArray) - secondOp = Array.get(o[i + 1], j); // second array index j - double fd = 0, sd = 0; - try { - fd = new Double(firstOp.toString()).doubleValue(); - sd = new Double(secondOp.toString()).doubleValue(); - res = ((Math.abs(fd - sd)) <= doublePrecision); // compare to certain precision instead of equality - - } catch (Exception e) { - //if (firstOp instanceof Double && secondOp instanceof Double) - //res= (Math.abs((((Double)firstOp).doubleValue())-((Double)secondOp).doubleValue()))<=doublePrecision; // compare to certain precision instead of equality - //else - res = firstOp.toString().equalsIgnoreCase(secondOp.toString()); - } - retArry = retArry + res + ","; - } - } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; - } - PtgBool pboo = new PtgBool(res); - return pboo; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.kt new file mode 100644 index 0000000..eb6f745 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgEQ.kt @@ -0,0 +1,177 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.toolkit.Logger + +import java.lang.reflect.Array + + +/* + Equals operand + + Evaluates to true if the top two operands are equal, otherwise FALSE + + + * @see Ptg + * @see Formula + + +*/ +class PtgEQ : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val string: String + get() = "=" + + override val length: Int + get() = Ptg.PTG_EQ_LENGTH + + init { + opcode = 0xB + record = ByteArray(1) + record[0] = 0xB + } + + override fun toString(): String { + return this.string + } + + /* Operator specific calculate method, this one determines if the second-to-top + operand is equal to the top operand; Returns a PtgBool + + */ + override fun calculatePtg(form: Array): Ptg? { + var res = false + // there should always be only two ptg's in this, error if not. + if (form.size != 2) { + Logger.logInfo("calculating formula, wrong number of values in PtgEQ") + return PtgErr(PtgErr.ERROR_VALUE) // 20081203 KSC: handle error's ala Excel + } + // check for null referenced values, a null reference is equal to the string ""; + val o = GenericPtg.getValuesFromPtgs(form) ?: return PtgErr(PtgErr.ERROR_VALUE) +// some error in value(s) + if (o[1].javaClass.isArray() && !o[0].javaClass.isArray()) { + val tmp = o[0] + o[0] = o[1] + o[1] = tmp + } + if (!o[0].javaClass.isArray()) { + if (o.size != 2) + return PtgErr(PtgErr.ERROR_VALUE) // 20081203 KSC: handle error's ala Excel return null; + // blank handling: + // determine if any of the operands are double - if true, + // then blank comparisons will be treated as 0's + var isDouble = false + run { + var i = 0 + while (i < 2 && !isDouble) { + //if (!form[i].isBlank()) + isDouble = o[i] is Double + i++ + } + } + for (i in 0..1) { + //if (form[i].isBlank()) { + if (o[i] != null && o[i].toString() == "") { + if (isDouble) + o[i] = 0.0 + else + o[i] = "" // in this case, empty cells are handled as blank, not zero + } + } + if (o[0] === o[1]) + res = true + else if (o[0] == null || o[1] == null) + res = false + else if (o[0] is Double && o[1] is Double) + res = Math.abs((o[0] as Double).toDouble() - (o[1] as Double).toDouble()) < doublePrecision // compare equality to certain precision + else if (o[0].toString().equals(o[1].toString(), ignoreCase = true)) + res = true + // handle empty cell references vs string case, 0.0 does not match + } else { // handle array formulas + var retArry = "" + val nArrays = java.lang.reflect.Array.getLength(o) + if (nArrays != 2) return PtgErr(PtgErr.ERROR_VALUE) + val nVals = java.lang.reflect.Array.getLength(o[0]) // use first array element to determine length of values as subsequent vals might not be arrays + if (nVals == 0) { + retArry = "{false}" + val pa = PtgArray() + pa.setVal(retArry) + return pa + } + var i = 0 + while (i < nArrays - 1) { + res = false + var secondOp: Any? = null + val comparitorIsArray = o[i + 1].javaClass.isArray() + if (!comparitorIsArray) secondOp = o[i + 1] + for (j in 0 until nVals) { + val firstOp = Array.get(o[i], j) // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j) // second array index j + var fd = 0.0 + var sd = 0.0 + try { + fd = Double(firstOp.toString()) + sd = Double(secondOp!!.toString()) + res = Math.abs(fd - sd) <= doublePrecision // compare to certain precision instead of equality + + } catch (e: Exception) { + //if (firstOp instanceof Double && secondOp instanceof Double) + //res= (Math.abs((((Double)firstOp).doubleValue())-((Double)secondOp).doubleValue()))<=doublePrecision; // compare to certain precision instead of equality + //else + res = firstOp.toString().equals(secondOp!!.toString(), ignoreCase = true) + } + + retArry = "$retArry$res," + } + i += 2 + } + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa + } + return PtgBool(res) + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5446048862531696036L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgEndSheet.java b/src/main/java/io/starter/formats/XLS/formulas/PtgEndSheet.kt similarity index 63% rename from src/main/java/io/starter/formats/XLS/formulas/PtgEndSheet.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgEndSheet.kt index f2a0cd1..b2c933c 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgEndSheet.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgEndSheet.kt @@ -20,11 +20,11 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /* - + Undocumented PTG, the only thing we know is "ptg DELETED" from MEFF pg 446. I am going to treat as a PtgRefErr for now. @@ -32,31 +32,30 @@ * @see Formula */ -public class PtgEndSheet extends GenericPtg implements Ptg { - // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... +class PtgEndSheet : GenericPtg(), Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2395432053363123361L; - - public boolean getIsOperand() { - return true; - } + override val isOperand: Boolean + get() = true /** * return the human-readable String representation of */ - public String getString() { - return "End Sheet Error"; - } + override val string: String + get() = "End Sheet Error" - public Object getValue() { - return "#REF!"; - } + override val value: Any? + get() = "#REF!" + + override val length: Int + get() = Ptg.PTG_ENDSHEET_LENGTH + + companion object { + // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... - public int getLength() { - return PTG_ENDSHEET_LENGTH; + /** + * serialVersionUID + */ + private val serialVersionUID = -2395432053363123361L } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgErr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgErr.java deleted file mode 100644 index 60d046e..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgErr.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -/* - * PtgErr is exactly what one would think it is, a ptg that - * describes an Error - * value. - *

                      - * Offset Name Size Contents
                      - * -------------------------------------------
                      - * 0 err 1 An error value
                      - * 
                      - * - */ - -public class PtgErr extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5201871987022621869L; - public static byte ERROR_NULL = 0x0; - public static byte ERROR_DIV_ZERO = 0x7; - public static byte ERROR_VALUE = 0xF; - public static byte ERROR_REF = 0x17; - public static byte ERROR_NAME = 0x1D; - public static byte ERROR_NUM = 0x24; - public static byte ERROR_NA = 0x2A; - - private boolean isCircularError = false; - - public boolean isCircularError() { - return isCircularError; - } - - public void setCircularError(boolean isCircularError) { - this.isCircularError = isCircularError; - } - - private String errorValue = null; - - @Override - public boolean getIsOperand() { - return true; - } - - public PtgErr() { - // default constructor - } - - public PtgErr(byte errorV) { - record = new byte[2]; - record[0] = 0x1C; - record[1] = errorV; - } - - public byte getErrorType() { - return record[1]; - } - - @Override - public Object getValue() { - return toString(); - } - - @Override - public void init(byte[] b) { - ptgId = b[0]; - record = b; - } - - @Override - public String toString() { - if (isCircularError()) - return "#CIR_ERR!"; - byte b = record[1]; - // duh, should have done a switch - if (b == ERROR_NULL) - errorValue = "#ERROR!"; - else if (b == ERROR_DIV_ZERO) - errorValue = "#DIV/0!"; - else if (b == ERROR_VALUE) - errorValue = "#VALUE!"; - else if (b == ERROR_REF) - errorValue = "#REF!"; - else if (b == ERROR_NAME) - errorValue = "#NAME?"; - else if (b == ERROR_NUM) - errorValue = "#NUM!"; - else if (b == ERROR_NA) - errorValue = "#N/A"; - return errorValue; - } - - public static byte convertStringToLookupByte(String errorString) { - if (errorString.equals("#ERROR!")) - return ERROR_NULL; - if (errorString.equals("#DIV/0!")) - return ERROR_DIV_ZERO; - if (errorString.equals("#REF!")) - return ERROR_VALUE; - if (errorString.equals("#ERROR!")) - return ERROR_REF; - if (errorString.equals("#NAME?")) - return ERROR_NAME; - if (errorString.equals("#NUM!")) - return ERROR_NUM; - if (errorString.equals("#N/A")) - return ERROR_NA; - return ERROR_NULL; - } - - @Override - public int getLength() { - return 2; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgErr.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgErr.kt new file mode 100644 index 0000000..f4f1bf2 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgErr.kt @@ -0,0 +1,130 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +/* + * PtgErr is exactly what one would think it is, a ptg that + * describes an Error + * value. + *
                      + * Offset Name Size Contents
                      + * -------------------------------------------
                      + * 0 err 1 An error value
                      + * 
                      + * + */ + +class PtgErr : GenericPtg, Ptg { + + var isCircularError = false + + private var errorValue: String? = null + + override val isOperand: Boolean + get() = true + + val errorType: Byte + get() = record[1] + + override val value: Any? + get() = toString() + + override val length: Int + get() = 2 + + constructor() { + // default constructor + } + + constructor(errorV: Byte) { + record = ByteArray(2) + record[0] = 0x1C + record[1] = errorV + } + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + } + + override fun toString(): String { + if (isCircularError) + return "#CIR_ERR!" + val b = record[1] + // duh, should have done a switch + if (b == ERROR_NULL) + errorValue = "#ERROR!" + else if (b == ERROR_DIV_ZERO) + errorValue = "#DIV/0!" + else if (b == ERROR_VALUE) + errorValue = "#VALUE!" + else if (b == ERROR_REF) + errorValue = "#REF!" + else if (b == ERROR_NAME) + errorValue = "#NAME?" + else if (b == ERROR_NUM) + errorValue = "#NUM!" + else if (b == ERROR_NA) + errorValue = "#N/A" + return errorValue + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -5201871987022621869L + var ERROR_NULL: Byte = 0x0 + var ERROR_DIV_ZERO: Byte = 0x7 + var ERROR_VALUE: Byte = 0xF + var ERROR_REF: Byte = 0x17 + var ERROR_NAME: Byte = 0x1D + var ERROR_NUM: Byte = 0x24 + var ERROR_NA: Byte = 0x2A + + fun convertStringToLookupByte(errorString: String): Byte { + if (errorString == "#ERROR!") + return ERROR_NULL + if (errorString == "#DIV/0!") + return ERROR_DIV_ZERO + if (errorString == "#REF!") + return ERROR_VALUE + if (errorString == "#ERROR!") + return ERROR_REF + if (errorString == "#NAME?") + return ERROR_NAME + if (errorString == "#NUM!") + return ERROR_NUM + return if (errorString == "#N/A") ERROR_NA else ERROR_NULL + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgExp.java b/src/main/java/io/starter/formats/XLS/formulas/PtgExp.java deleted file mode 100644 index 7354459..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgExp.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.*; -import io.starter.toolkit.ByteTools; - -import java.util.Stack; - -/** - * ptgExp indicates an Array Formula or Shared Formula - *

                      - * When ptgExp occurs in a formula, it's the only token in the formula. - * this indicates that the cell containing the formula - * is part of an array or opartof a shared formula. - * The actual formula is found in an array record. - *

                      - * The value for ptgExp consists of the row and the column of the - * upper-left corner of the array formula. - * - * @see Ptg - * @see Formula - * @see Array - * @see Shrfmla - */ -public class PtgExp extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2150560716287810448L; - int rwFirst; - int colFirst; - - public boolean getIsControl() { - return true; - } - - public boolean getIsStandAloneOperator() { - return true; - } - - public int getLength() { - return PTG_EXP_LENGTH; - } - - public int getRwFirst() { - return rwFirst; - } - - public int getColFirst() { - return colFirst; - } - - /** - * init from row, col - * - * @param row - * @param col - */ - public void init(int row, int col) { - byte[] r = ByteTools.shortToLEBytes((short) row); - byte[] c = ByteTools.shortToLEBytes((short) col); - record = new byte[]{0x1, r[0], r[1], c[0], c[1]}; - ptgId = record[0]; - this.populateVals(); - //this.addToReferenceTracker(); - } - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - //this.addToReferenceTracker(); - } - - private void populateVals() { - rwFirst = readRow(record[1], record[2]); - colFirst = ByteTools.readShort(record[3], record[4]); - } - - /** - * Returns the location this PtgExp points to. - */ - public String getReferent() { - return ExcelTools.formatLocation(new int[]{rwFirst, colFirst}); - } - - /** - * Looks up into it's parent shared formula, and returns - * the expression as if it were a regular formula. - * - * @return converted Calculation Expression - */ - public Ptg[] getConvertedExpression() { - Formula f = (Formula) this.getParentRec(); - if (f.isSharedFormula()) { - Stack expression = f.shared.instantiate(f); - Ptg[] retPtg = new Ptg[expression.size()]; - for (int i = 0; i < expression.size(); i++) { - Ptg p = (Ptg) expression.get(i); - retPtg[i] = p; - } - return retPtg; -// throw new UnsupportedOperationException ( -// "Shared formulas must be instantiated for calculation"); - } else { // if it's an array formula, return ptg's as well - Array a = (Array) (f.getInternalRecords().get(0)); // this.getParentRec().getSheet().getArrayFormula(getParentLocation()); - Stack calcStack = a.getExpression(); - Ptg[] retPtg = new Ptg[calcStack.size()]; - for (int i = 0; i < calcStack.size(); i++) { - Ptg p = (Ptg) calcStack.get(i); - retPtg[i] = p; - } - return retPtg; - } - } - - public Object getValue() { - Object o = null; - Formula f = (Formula) this.getParentRec(); - if (f.isSharedFormula()) { - o = FormulaCalculator.calculateFormula(f.shared.instantiate(f)); -// throw new UnsupportedOperationException ( -// "Shared formulas must be instantiated for calculation"); - } else { - Object r = null; - if (f.getInternalRecords().size() > 0) - r = f.getInternalRecords().get(0); - else { // it's part of an array formula but not the parent - r = this.getParentRec().getSheet().getArrayFormula(getReferent()); - } - if (r instanceof Array) { - Array arr = (Array) r; - o = arr.getValue(this); - } else if (r instanceof StringRec) { - o = ((StringRec) r).getStringVal(); - } - } - return o; - } - - public Ptg calculatePtg(Ptg[] parsething) { - Object o = null; - Formula f = ((Formula) this.getParentRec()); - if (f.isSharedFormula()) { - o = FormulaCalculator.calculateFormula(f.shared.instantiate(f)); - } else { - Object r = null; - if (f.getInternalRecords().size() > 0) - r = f.getInternalRecords().get(0); - else { // it's part of an array formula but not the parent - r = this.getParentRec().getSheet().getArrayFormula(getReferent()); - } - if (r instanceof Array) { - Array arr = (Array) r; - o = arr.getValue(this); - } else if (r instanceof StringRec) { - o = ((StringRec) r).getStringVal(); - } else // should never happen - throw new UnsupportedOperationException( - "Expected records parsing Formula were not present"); - } - Ptg p = null; - // conversion isn't necessary -// try{ - if (o instanceof Integer) - return new PtgInt(((Integer) o).intValue()); -// Double d = new Double(o.toString()); - else if (o instanceof Double) - return new PtgNumber(((Double) o).doubleValue()); - //p = new PtgNumber(d.doubleValue()); -// }catch(NumberFormatException e){ - if (o.toString().equalsIgnoreCase("true") || o.toString().equalsIgnoreCase("false")) - p = new PtgBool(o.toString().equalsIgnoreCase("true")); - else - p = new PtgStr(o.toString()); -// } - return p; - } - - /** - * return the location of this PtgExp - * 20060302 KSC - */ - public String getLocation() throws FormulaNotFoundException { - String s = ""; - try { - s = this.getParentRec().getCellAddress(); - s = this.getParentRec().getSheet().getSheetName() + "!" + s; - } catch (Exception e) { - - } - return s; - } - - /** - * return the human-readable String representation of the linked shared formula - */ - public String getString() { - try { - try { - // Object o= ((Formula) this.getParentRec()).getInternalRecords().get(0); PARENT REC of ARRAY or SHRFMLA is determined by referent (record) NOT necessarily same as actual Parent Rec - Boundsheet sht = this.getParentRec().getSheet(); -// Formula pr= (Formula) sht.getCell(this.getReferent()); - Formula f = ((Formula) this.getParentRec()); - Object o; - if (f.isSharedFormula()) { - o = FormulaParser.getExpressionString(f.shared.instantiate(f)); - if (o != null && o.toString().startsWith("=")) - return o.toString().substring(1); - return o.toString(); - } - Formula pr = (Formula) sht.getCell(this.getReferent()); - o = pr.getInternalRecords().get(0); - if (o instanceof Array) { - Array a = (Array) o; - return a.getFormulaString(); - } else if (o instanceof StringRec) { - //if this is a shared formula the attached string is the RESULT, not the formula string itself - if (((Formula) this.getParentRec()).isSharedFormula()) { - throw new IndexOutOfBoundsException("parse it"); - } - StringRec s = (StringRec) o; - return s.getStringVal(); - } - } catch (IndexOutOfBoundsException e) { // subsequent formulas use same shared formula rec so find - throw new UnsupportedOperationException( - "Shared formulas must be instantiated for calculation"); - } - } catch (Exception e) { - return "Array-Entered or Shared Formula"; - } - return "Array-Entered or Shared Formula"; - } - - /** - * updateRecord from local rwFirst and colFirst values - * - * @see - */ - public void updateRecord() { - System.arraycopy(ByteTools.shortToLEBytes((short) rwFirst), 0, record, 1, 2); - System.arraycopy(ByteTools.shortToLEBytes((short) colFirst), 0, record, 3, 2); - } - - /** - * setLocation vars from address string - * - * @param s String address - */ - public void setLocation(String s) { - int[] rc = ExcelTools.getRowColFromString(s); - rwFirst = rc[0]; - colFirst = rc[1]; - updateRecord(); - } - - public void setColFirst(int c) { - this.colFirst = c; - } - - public void setRowFirst(int r) { - this.rwFirst = r; - } - - public String toString() { - return "PtgExp: Parent Formula at [" + rwFirst + "," + colFirst + "]"; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgExp.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgExp.kt new file mode 100644 index 0000000..0bdd678 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgExp.kt @@ -0,0 +1,293 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.* +import io.starter.toolkit.ByteTools + +import java.util.Stack + +/** + * ptgExp indicates an Array Formula or Shared Formula + * + * + * When ptgExp occurs in a formula, it's the only token in the formula. + * this indicates that the cell containing the formula + * is part of an array or opartof a shared formula. + * The actual formula is found in an array record. + * + * + * The value for ptgExp consists of the row and the column of the + * upper-left corner of the array formula. + * + * @see Ptg + * + * @see Formula + * + * @see Array + * + * @see Shrfmla + */ +class PtgExp : GenericPtg(), Ptg { + var rwFirst: Int = 0 + internal set + var colFirst: Int = 0 + + override val isControl: Boolean + get() = true + + override val isStandAloneOperator: Boolean + get() = true + + override val length: Int + get() = Ptg.PTG_EXP_LENGTH + + /** + * Returns the location this PtgExp points to. + */ + val referent: String + get() = ExcelTools.formatLocation(intArrayOf(rwFirst, colFirst)) + + /** + * Looks up into it's parent shared formula, and returns + * the expression as if it were a regular formula. + * + * @return converted Calculation Expression + */ + // throw new UnsupportedOperationException ( + // "Shared formulas must be instantiated for calculation"); + // if it's an array formula, return ptg's as well + // this.getParentRec().getSheet().getArrayFormula(getParentLocation()); + val convertedExpression: Array + get() { + val f = this.parentRec as Formula + if (f.isSharedFormula) { + val expression = f.shared!!.instantiate(f) + val retPtg = arrayOfNulls(expression.size) + for (i in expression.indices) { + val p = expression[i] as Ptg + retPtg[i] = p + } + return retPtg + } else { + val a = f.internalRecords[0] as Array + val calcStack = a.expression + val retPtg = arrayOfNulls(calcStack!!.size) + for (i in calcStack!!.indices) { + val p = calcStack!!.get(i) as Ptg + retPtg[i] = p + } + return retPtg + } + } + + override// throw new UnsupportedOperationException ( + // "Shared formulas must be instantiated for calculation"); + // it's part of an array formula but not the parent + val value: Any? + get() { + var o: Any? = null + val f = this.parentRec as Formula + if (f.isSharedFormula) { + o = FormulaCalculator.calculateFormula(f.shared!!.instantiate(f)) + } else { + var r: Any? = null + if (f.internalRecords.size > 0) + r = f.internalRecords[0] + else { + r = this.parentRec!!.sheet!!.getArrayFormula(referent) + } + if (r is Array) { + val arr = r as Array? + o = arr!!.getValue(this) + } else if (r is StringRec) { + o = r.stringVal + } + } + return o + } + + /** + * return the location of this PtgExp + * 20060302 KSC + */ + /** + * setLocation vars from address string + * + * @param s String address + */ + override var location: String? + @Throws(FormulaNotFoundException::class) + get() { + var s = "" + try { + s = this.parentRec!!.cellAddress + s = this.parentRec!!.sheet!!.sheetName + "!" + s + } catch (e: Exception) { + + } + + return s + } + set(s) { + val rc = ExcelTools.getRowColFromString(s) + rwFirst = rc[0] + colFirst = rc[1] + updateRecord() + } + + /** + * return the human-readable String representation of the linked shared formula + */ + override// Object o= ((Formula) this.getParentRec()).getInternalRecords().get(0); PARENT REC of ARRAY or SHRFMLA is determined by referent (record) NOT necessarily same as actual Parent Rec + // Formula pr= (Formula) sht.getCell(this.getReferent()); + //if this is a shared formula the attached string is the RESULT, not the formula string itself + // subsequent formulas use same shared formula rec so find + val string: String? + get() { + try { + try { + val sht = this.parentRec!!.sheet + val f = this.parentRec as Formula + val o: Any? + if (f.isSharedFormula) { + o = FormulaParser.getExpressionString(f.shared!!.instantiate(f)) + return if (o != null && o.toString().startsWith("=")) o.toString().substring(1) else o!!.toString() + } + val pr = sht!!.getCell(this.referent) as Formula + o = pr.internalRecords[0] + if (o is Array) { + val a = o as Array? + return a!!.formulaString + } else if (o is StringRec) { + if ((this.parentRec as Formula).isSharedFormula) { + throw IndexOutOfBoundsException("parse it") + } + val s = o as StringRec? + return s!!.stringVal + } + } catch (e: IndexOutOfBoundsException) { + throw UnsupportedOperationException( + "Shared formulas must be instantiated for calculation") + } + + } catch (e: Exception) { + return "Array-Entered or Shared Formula" + } + + return "Array-Entered or Shared Formula" + } + + /** + * init from row, col + * + * @param row + * @param col + */ + fun init(row: Int, col: Int) { + val r = ByteTools.shortToLEBytes(row.toShort()) + val c = ByteTools.shortToLEBytes(col.toShort()) + record = byteArrayOf(0x1, r[0], r[1], c[0], c[1]) + opcode = record[0] + this.populateVals() + //this.addToReferenceTracker(); + } + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() + //this.addToReferenceTracker(); + } + + private fun populateVals() { + rwFirst = readRow(record[1], record[2]) + colFirst = ByteTools.readShort(record[3].toInt(), record[4].toInt()).toInt() + } + + override fun calculatePtg(parsething: Array): Ptg? { + var o: Any? = null + val f = this.parentRec as Formula + if (f.isSharedFormula) { + o = FormulaCalculator.calculateFormula(f.shared!!.instantiate(f)) + } else { + var r: Any? = null + if (f.internalRecords.size > 0) + r = f.internalRecords[0] + else { // it's part of an array formula but not the parent + r = this.parentRec!!.sheet!!.getArrayFormula(referent) + } + if (r is Array) { + val arr = r as Array? + o = arr!!.getValue(this) + } else if (r is StringRec) { + o = r.stringVal + } else + // should never happen + throw UnsupportedOperationException( + "Expected records parsing Formula were not present") + } + var p: Ptg? = null + // conversion isn't necessary + // try{ + if (o is Int) + return PtgInt(o.toInt()) + else if (o is Double) + return PtgNumber(o.toDouble())// Double d = new Double(o.toString()); + //p = new PtgNumber(d.doubleValue()); + // }catch(NumberFormatException e){ + if (o!!.toString().equals("true", ignoreCase = true) || o.toString().equals("false", ignoreCase = true)) + p = PtgBool(o.toString().equals("true", ignoreCase = true)) + else + p = PtgStr(o.toString()) + // } + return p + } + + /** + * updateRecord from local rwFirst and colFirst values + * + * @see + */ + override fun updateRecord() { + System.arraycopy(ByteTools.shortToLEBytes(rwFirst.toShort()), 0, record, 1, 2) + System.arraycopy(ByteTools.shortToLEBytes(colFirst.toShort()), 0, record, 3, 2) + } + + fun setRowFirst(r: Int) { + this.rwFirst = r + } + + override fun toString(): String { + return "PtgExp: Parent Formula at [$rwFirst,$colFirst]" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -2150560716287810448L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgFunc.java b/src/main/java/io/starter/formats/XLS/formulas/PtgFunc.kt similarity index 67% rename from src/main/java/io/starter/formats/XLS/formulas/PtgFunc.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgFunc.kt index 5d45356..f26b991 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgFunc.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgFunc.kt @@ -20,19 +20,20 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools -import java.util.Locale; +import java.util.Locale /** * PtgFunc is a fuction operator that refers to the header file in order to * use the correct function. PtgFunc is only used with fixed number of argument * functions - *

                      + * + * * Opcode = 21h * *

                      @@ -40,16 +41,13 @@
                        * --------------------------------------------------------
                        * 0           iftab       2           The index to the function table
                        * see GenericPtgFunc for details
                      - * 
                      + * * * @see Ptg + * * @see GenericPtgFunc - **/ -public class PtgFunc extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4435263700288188538L; + */ +class PtgFunc : GenericPtg, Ptg { /* 20060425 KSC: see FunctionConstants for Formula Consolidation details public static String[][] recArr = { {"Pi","19"}, @@ -115,41 +113,19 @@ public class PtgFunc extends GenericPtg implements Ptg { }; */ - private int iftab = -1; - + private var iftab = -1 - public PtgFunc(int funcType, XLSRecord parentRec) { - this(funcType); - this.setParentRec(parentRec); - } - - public PtgFunc(int funcType) { - byte[] recbyte = new byte[3]; -// recbyte[0]= 0x21; - recbyte[0] = 0x41; // 20060126 - KSC: Excel seems to need this code for PtgFunc - byte[] b = ByteTools.shortToLEBytes((short) funcType); - recbyte[1] = b[0]; - recbyte[2] = b[1]; - this.init(recbyte); - } - - public PtgFunc() { - // placeholder - } - - public boolean getIsFunction() { - return true; - } + override val isFunction: Boolean + get() = true /** * Returns the number of Params to pass to the Ptg * Unfortunately this seems to vary depending on the formula. * fill in the non-1's as you get them. */ - public int getNumParams() { - // 20060425 KSC: Formula consolidation - see FunctionConstants for details - return FunctionConstants.getNumParams(iftab); - } + override// 20060425 KSC: Formula consolidation - see FunctionConstants for details + val numParams: Int + get() = FunctionConstants.getNumParams(iftab) /* if (iftab == 10) return 0; // na @@ -201,90 +177,111 @@ public int getNumParams() { return 1; //if we are lucky } */ - public boolean getIsUnaryOperator() { - return true; - } + override val isUnaryOperator: Boolean + get() = true - public String getString() { - short iftb = (short) iftab; - String f = null; - if (Locale.JAPAN.equals(Locale.getDefault())) { - f = FunctionConstants.getJFunctionString(iftb); + override val string: String + get() { + val iftb = iftab.toShort() + var f: String? = null + if (Locale.JAPAN == Locale.getDefault()) { + f = FunctionConstants.getJFunctionString(iftb) + } + if (f == null) { + f = FunctionConstants.getFunctionString(iftb) + } + return f } - if (f == null) { - f = FunctionConstants.getFunctionString(iftb); + + override val string2: String + get() = ")" + + var `val`: Int + get() = iftab + set(i) { + iftab = i + this.updateRecord() } - return f; - } - public String getString2() { - return ")"; - } + override val length: Int + get() = Ptg.PTG_FUNC_LENGTH + - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); + constructor(funcType: Int, parentRec: XLSRecord) : this(funcType) { + this.parentRec = parentRec } - private void populateVals() { - iftab = ByteTools.readShort(record[1], record[2]); + constructor(funcType: Int) { + val recbyte = ByteArray(3) + // recbyte[0]= 0x21; + recbyte[0] = 0x41 // 20060126 - KSC: Excel seems to need this code for PtgFunc + val b = ByteTools.shortToLEBytes(funcType.toShort()) + recbyte[1] = b[0] + recbyte[2] = b[1] + this.init(recbyte) } - public int getVal() { - return iftab; + constructor() { + // placeholder } - public void setVal(int i) { - iftab = i; - this.updateRecord(); + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() } - public void updateRecord() { - byte[] tmp = new byte[1]; - tmp[0] = record[0]; - byte[] brow = ByteTools.cLongToLEBytes(iftab); - tmp = ByteTools.append(brow, tmp); - record = tmp; + private fun populateVals() { + iftab = ByteTools.readShort(record[1].toInt(), record[2].toInt()).toInt() } - public int getLength() { - return PTG_FUNC_LENGTH; + override fun updateRecord() { + var tmp = ByteArray(1) + tmp[0] = record[0] + val brow = ByteTools.cLongToLEBytes(iftab) + tmp = ByteTools.append(brow, tmp) + record = tmp } - public Ptg calculatePtg(Ptg[] pthings) throws FunctionNotSupportedException, CalculationException { - Ptg[] ptgarr = new Ptg[pthings.length + 1]; - ptgarr[0] = this; + @Throws(FunctionNotSupportedException::class, CalculationException::class) + override fun calculatePtg(pthings: Array): Ptg? { + val ptgarr = arrayOfNulls(pthings.size + 1) + ptgarr[0] = this // add this into the array so the functionHandler has a handle to the function - System.arraycopy(pthings, 0, ptgarr, 1, pthings.length); - Ptg resPtg = FunctionHandler.calculateFunction(ptgarr); - return resPtg; + System.arraycopy(pthings, 0, ptgarr, 1, pthings.size) + return FunctionHandler.calculateFunction(ptgarr) } /** * given this specific Func, ensure that it's parameters are of the correct Ptg type - *
                      Value, Reference or Array - *
                      This is necessary when functions are added via String - *
                      NOTE: This method is a stub; eventually ALL Functions which require + *

                      Value, Reference or Array + *

                      This is necessary when functions are added via String + *

                      NOTE: This method is a stub; eventually ALL Functions which require * specific types of parameters will be handled here * * @see FormulaParser.adjustParameterIds */ - public void adjustParameterIds() { - if (vars == null) return; // no parameters to worry about + fun adjustParameterIds() { + if (vars == null) return // no parameters to worry about /*TODO Eventually will have a list of Function Id's which require a certain type of parameter*/ - switch (iftab) { - case FunctionConstants.xlfRows: - for (int i = 0; i < vars.length; i++) { - if (vars[0] instanceof PtgRef) { - ((PtgRef) vars[0]).setPtgType(PtgRef.REFERENCE); - } else if (vars[0] instanceof PtgName) { - ((PtgName) vars[0]).setPtgType(PtgRef.REFERENCE); - } - break; + when (iftab) { + FunctionConstants.xlfRows -> for (i in vars!!.indices) { + if (vars!![0] is PtgRef) { + (vars!![0] as PtgRef).setPtgType(PtgRef.REFERENCE) + } else if (vars!![0] is PtgName) { + (vars!![0] as PtgName).setPtgType(PtgRef.REFERENCE) } + break + } } } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 4435263700288188538L + } + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgFuncVar.java b/src/main/java/io/starter/formats/XLS/formulas/PtgFuncVar.java deleted file mode 100644 index 364c293..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgFuncVar.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.formats.XLS.XLSRecord; -import io.starter.toolkit.ByteTools; - -import java.util.Locale; - -/** - * PtgFunc is a fuction operator that refers to the header file in order to - * use the correct function. - *

                      - * PtgFuncVar is only used with a variable number of arguments. - *

                      - * Opcode = 22h - * - *

                      - * Offset      Bits    Name        Mask        Contents
                      - * --------------------------------------------------------
                      - * 0           6-0     cargs       7Fh         The number of arguments to the function
                      - * 7       fPrompt     80h         =1, function prompts the user
                      - * 1           14-0    iftab       7FFFh       The index to the function table
                      - * see GenericPtgFunc for details
                      - * 15      fCE         8000h       This function is a command equivalent
                      - * 
                      - * - * @see Ptg - * @see GenericPtgFunc - **/ -public class PtgFuncVar extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1478629759437556620L; - public static int LENGTH = 3; - byte ptgId; - String loc; - byte cargs; - boolean fprompt; - short iftab; - boolean fCE; - - - public PtgFuncVar(int funcType, int numArgs, XLSRecord parentRec) { - this(funcType, numArgs); - this.setParentRec(parentRec); - } - - public PtgFuncVar(int funcType, int numArgs) { - byte[] recbyte = new byte[4]; - // 20100609 KSC: there are three types of funcvars: - // 22H (tFuncVarR), 42H (tFuncVarV), 62H (tFuncVarA) - // tFUncVarR = reference return value (most common?) - // tFuncVarV = value type of return value (ROW, SUM) - // tFuncVarA = Array return type (TREND) - // TODO: figure out which other functions are type V or type A - switch (funcType) { - case FunctionConstants.XLF_ROW: // ROW - case FunctionConstants.xlfColumn: // COLUMN - case FunctionConstants.xlfIndex: // INDEX - case FunctionConstants.xlfVlookup: // VLOOKUP - case FunctionConstants.xlfSumproduct: // SUMPRODUCT - recbyte[0] = 0x42; - break; - default: // default= tFuncVarR - recbyte[0] = 0x22; - } - byte[] b = ByteTools.shortToLEBytes((short) funcType); - recbyte[1] = (byte) numArgs; - recbyte[2] = b[0]; - recbyte[3] = b[1]; - this.init(recbyte); - } - - public PtgFuncVar() { - // placeholder - } - - public boolean getIsFunction() { - return true; - } - - /** - * Returns the number of Params to pass to the Ptg - */ - public int getNumParams() { - return cargs; - } - - /** - * set the number of parmeters in the FuncVar record - * - * @param byte nParams - */ - // 20060131 KSC: Added to set # params separately from init - public void setNumParams(byte nParams) { - record[1] = nParams; - this.populateVals(); - } - - // should be handled by super? - public byte getOpcode() { - return ptgId; - } - - - /** - * GetString - is this toString, what is it returning? - */ - public String getString() { - if (iftab != FunctionConstants.xlfADDIN) { - String f = null; - if (Locale.JAPAN.equals(Locale.getDefault())) { - f = FunctionConstants.getJFunctionString(iftab); - } - if (f == null) f = FunctionConstants.getFunctionString(iftab); - return f; - } - return getAddInFunctionString(); - } - - // KSC: added to handle string version of add-in formulas - private String getAddInFunctionString() { - if (vars != null && vars[0] instanceof PtgNameX) { - return vars[0].toString() + "("; - } - return "("; - } - - public String getString2() { - return ")"; - } - - public void init(byte[] b) { - ptgId = 0x22; - record = b; - fprompt = false; - fCE = false; - this.populateVals(); - } - - /** - * Get the function ID for this PtgFuncVar - * - * @return function Id - */ - public short getFunctionId() { - return iftab; - } - - /** - * parse all the values out of the byte array and - * populate the classes values - */ - private void populateVals() { - - cargs = record[1]; - if ((cargs & 0x80) == 0x80) { // is fprompt set? - fprompt = true; - } - cargs = (byte) (cargs & 0x7f); - iftab = ByteTools.readShort(record[2], record[3]); - if ((iftab & 0x8000) == 0x8000) { // is fCE set? - fCE = true; - } - iftab = (short) (iftab & 0x7fff); // cut out the fCE - } - - - public int getVal() { - return iftab; - } - - - // THis will have to be modified when we start modifying the record. - public byte[] getRecord() { - return record; - } - - public int getLength() { - return PTG_FUNCVAR_LENGTH; - } - - public Ptg calculatePtg(Ptg[] pthings) throws FunctionNotSupportedException, CalculationException { - Ptg[] ptgarr = new Ptg[pthings.length + 1]; - ptgarr[0] = this; - // add this into the array so the functionHandler has a handle to the function - System.arraycopy(pthings, 0, ptgarr, 1, pthings.length); - Ptg resPtg = FunctionHandler.calculateFunction(ptgarr); - return resPtg; - } - - /** - * return String representation of function id for this funcvar - */ - public String toString() { - return "FUNCVAR " + iftab; - } - - /** - * given this specific Func Var, ensure that it's parameters are of the correct Ptg type - *
                      Value, Reference or Array - *
                      This is necessary when functions are added via String - *
                      NOTE: eventually all FuncVars which require a specific type of parameter will be handled here - * - * @see FormulaParser.adjustParameterIds - */ - public void adjustParameterIds() { - if (vars == null) return; // no parameters to worry about - switch (iftab) { - case FunctionConstants.xlfVlookup: - setParameterType(0, PtgRef.VALUE); - setParameterType(1, PtgRef.REFERENCE); - setParameterType(2, PtgRef.VALUE); - setParameterType(3, PtgRef.VALUE); - break; - case FunctionConstants.xlfColumn: - case FunctionConstants.XLF_ROW: - setParameterType(0, PtgRef.REFERENCE); - break; - case FunctionConstants.xlfIndex: - setParameterType(0, PtgRef.REFERENCE); - setParameterType(1, PtgRef.VALUE); - break; - case FunctionConstants.XLF_SUM_IF: - setParameterType(0, PtgRef.REFERENCE); - break; - case FunctionConstants.xlfSumproduct: - setParameterType(0, PtgRef.ARRAY); - setParameterType(1, PtgRef.ARRAY); - break; - default: - break; - } - } - - - /** - * utility for adjustParameterIds to set the PtgRef-type or PtgName-type pareameter to the correct type - * either PtgRef.REFERENCE, PtgRef.VALUE or PtgRef.ARRAY - * dependent upon the function they are used in - * - * @param n - * @param type - */ - private void setParameterType(int n, short type) { - if (vars.length > n) { - if (vars[n] instanceof PtgArea) { - ((PtgArea) vars[n]).setPtgType(type); - } else if (vars[n] instanceof PtgRef) { - ((PtgRef) vars[n]).setPtgType(type); - } else if (vars[n] instanceof PtgName) { - ((PtgName) vars[n]).setPtgType(type); - } - } - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgFuncVar.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgFuncVar.kt new file mode 100644 index 0000000..9b1d8c4 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgFuncVar.kt @@ -0,0 +1,265 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.formats.XLS.XLSRecord +import io.starter.toolkit.ByteTools + +import java.util.Locale + +/** + * PtgFunc is a fuction operator that refers to the header file in order to + * use the correct function. + * + * + * PtgFuncVar is only used with a variable number of arguments. + * + * + * Opcode = 22h + * + *
                      + * Offset      Bits    Name        Mask        Contents
                      + * --------------------------------------------------------
                      + * 0           6-0     cargs       7Fh         The number of arguments to the function
                      + * 7       fPrompt     80h         =1, function prompts the user
                      + * 1           14-0    iftab       7FFFh       The index to the function table
                      + * see GenericPtgFunc for details
                      + * 15      fCE         8000h       This function is a command equivalent
                      +
                      * + * + * @see Ptg + * + * @see GenericPtgFunc + */ +class PtgFuncVar : GenericPtg, Ptg { + // should be handled by super? + override var opcode: Byte = 0 + internal set + internal var loc: String? = null + internal var cargs: Byte = 0 + internal var fprompt: Boolean = false + /** + * Get the function ID for this PtgFuncVar + * + * @return function Id + */ + var functionId: Short = 0 + internal set + internal var fCE: Boolean = false + + override val isFunction: Boolean + get() = true + + /** + * Returns the number of Params to pass to the Ptg + */ + override val numParams: Int + get() = cargs.toInt() + + + /** + * GetString - is this toString, what is it returning? + */ + override val string: String + get() { + if (functionId.toInt() != FunctionConstants.xlfADDIN) { + var f: String? = null + if (Locale.JAPAN == Locale.getDefault()) { + f = FunctionConstants.getJFunctionString(functionId) + } + if (f == null) f = FunctionConstants.getFunctionString(functionId) + return f + } + return addInFunctionString + } + + // KSC: added to handle string version of add-in formulas + private val addInFunctionString: String + get() = if (vars != null && vars!![0] is PtgNameX) { + vars!![0].toString() + "(" + } else "(" + + override val string2: String + get() = ")" + + + val `val`: Int + get() = functionId.toInt() + + + // THis will have to be modified when we start modifying the record. + override val record: ByteArray + get() = record + + override val length: Int + get() = Ptg.PTG_FUNCVAR_LENGTH + + + constructor(funcType: Int, numArgs: Int, parentRec: XLSRecord) : this(funcType, numArgs) { + this.parentRec = parentRec + } + + constructor(funcType: Int, numArgs: Int) { + val recbyte = ByteArray(4) + // 20100609 KSC: there are three types of funcvars: + // 22H (tFuncVarR), 42H (tFuncVarV), 62H (tFuncVarA) + // tFUncVarR = reference return value (most common?) + // tFuncVarV = value type of return value (ROW, SUM) + // tFuncVarA = Array return type (TREND) + // TODO: figure out which other functions are type V or type A + when (funcType) { + FunctionConstants.XLF_ROW // ROW + , FunctionConstants.xlfColumn // COLUMN + , FunctionConstants.xlfIndex // INDEX + , FunctionConstants.xlfVlookup // VLOOKUP + , FunctionConstants.xlfSumproduct // SUMPRODUCT + -> recbyte[0] = 0x42 + else // default= tFuncVarR + -> recbyte[0] = 0x22 + } + val b = ByteTools.shortToLEBytes(funcType.toShort()) + recbyte[1] = numArgs.toByte() + recbyte[2] = b[0] + recbyte[3] = b[1] + this.init(recbyte) + } + + constructor() { + // placeholder + } + + /** + * set the number of parmeters in the FuncVar record + * + * @param byte nParams + */ + // 20060131 KSC: Added to set # params separately from init + fun setNumParams(nParams: Byte) { + record[1] = nParams + this.populateVals() + } + + override fun init(b: ByteArray) { + opcode = 0x22 + record = b + fprompt = false + fCE = false + this.populateVals() + } + + /** + * parse all the values out of the byte array and + * populate the classes values + */ + private fun populateVals() { + + cargs = record[1] + if (cargs and 0x80 == 0x80) { // is fprompt set? + fprompt = true + } + cargs = (cargs and 0x7f).toByte() + functionId = ByteTools.readShort(record[2].toInt(), record[3].toInt()) + if (functionId and 0x8000 == 0x8000) { // is fCE set? + fCE = true + } + functionId = (functionId and 0x7fff).toShort() // cut out the fCE + } + + @Throws(FunctionNotSupportedException::class, CalculationException::class) + override fun calculatePtg(pthings: Array): Ptg? { + val ptgarr = arrayOfNulls(pthings.size + 1) + ptgarr[0] = this + // add this into the array so the functionHandler has a handle to the function + System.arraycopy(pthings, 0, ptgarr, 1, pthings.size) + return FunctionHandler.calculateFunction(ptgarr) + } + + /** + * return String representation of function id for this funcvar + */ + override fun toString(): String { + return "FUNCVAR $functionId" + } + + /** + * given this specific Func Var, ensure that it's parameters are of the correct Ptg type + *

                      Value, Reference or Array + *

                      This is necessary when functions are added via String + *

                      NOTE: eventually all FuncVars which require a specific type of parameter will be handled here + * + * @see FormulaParser.adjustParameterIds + */ + fun adjustParameterIds() { + if (vars == null) return // no parameters to worry about + when (functionId) { + FunctionConstants.xlfVlookup -> { + setParameterType(0, PtgRef.VALUE) + setParameterType(1, PtgRef.REFERENCE) + setParameterType(2, PtgRef.VALUE) + setParameterType(3, PtgRef.VALUE) + } + FunctionConstants.xlfColumn, FunctionConstants.XLF_ROW -> setParameterType(0, PtgRef.REFERENCE) + FunctionConstants.xlfIndex -> { + setParameterType(0, PtgRef.REFERENCE) + setParameterType(1, PtgRef.VALUE) + } + FunctionConstants.XLF_SUM_IF -> setParameterType(0, PtgRef.REFERENCE) + FunctionConstants.xlfSumproduct -> { + setParameterType(0, PtgRef.ARRAY) + setParameterType(1, PtgRef.ARRAY) + } + else -> { + } + } + } + + + /** + * utility for adjustParameterIds to set the PtgRef-type or PtgName-type pareameter to the correct type + * either PtgRef.REFERENCE, PtgRef.VALUE or PtgRef.ARRAY + * dependent upon the function they are used in + * + * @param n + * @param type + */ + private fun setParameterType(n: Int, type: Short) { + if (vars!!.size > n) { + if (vars!![n] is PtgArea) { + (vars!![n] as PtgArea).setPtgType(type) + } else if (vars!![n] is PtgRef) { + (vars!![n] as PtgRef).setPtgType(type) + } else if (vars!![n] is PtgName) { + (vars!![n] as PtgName).setPtgType(type) + } + } + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 1478629759437556620L + var LENGTH = 3 + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgGE.java b/src/main/java/io/starter/formats/XLS/formulas/PtgGE.kt similarity index 51% rename from src/main/java/io/starter/formats/XLS/formulas/PtgGE.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgGE.kt index 58cb1ad..b0aceae 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgGE.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgGE.kt @@ -20,110 +20,105 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.formats.XLS.Formula; -import io.starter.toolkit.Logger; +import io.starter.formats.XLS.Formula +import io.starter.toolkit.Logger -import java.lang.reflect.Array; +import java.lang.reflect.Array /* Ptg that is a Greater than or equal to operand - + Evaluates to TRUE if the second operand is greater than or equal to the top operand, otherwise FALSE - - + + * @see Ptg * @see Formula - + */ -public class PtgGE extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5154662904662222256L; +class PtgGE : GenericPtg(), Ptg { - public boolean getIsOperator() { - return true; - } + override val isOperator: Boolean + get() = true - public boolean getIsPrimitiveOperator() { - return true; - } + override val isPrimitiveOperator: Boolean + get() = true - public boolean getIsBinaryOperator() { - return true; - } - - public PtgGE() { - ptgId = 0xC; - record = new byte[1]; - record[0] = 0xC; - } + override val isBinaryOperator: Boolean + get() = true /** * return the human-readable String representation of */ - public String getString() { - return ">="; - } + override val string: String + get() = ">=" + + override val length: Int + get() = Ptg.PTG_GE_LENGTH - public String toString() { - return this.getString(); + init { + opcode = 0xC + record = ByteArray(1) + record[0] = 0xC } - public int getLength() { - return PTG_GE_LENGTH; + override fun toString(): String { + return this.string } /* Operator specific calculate method, this one determines if the second-to-top operand is less than the top operand; Returns a PtgBool */ - public Ptg calculatePtg(Ptg[] form) { + override fun calculatePtg(form: Array): Ptg? { try { // 20090202 KSC: Handle array formulas - Object[] o = getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { + val o = GenericPtg.getValuesFromPtgs(form) + if (!o!![0].javaClass.isArray()) { //double[] dub = super.getValuesFromPtgs(form); // there should always be only two ptg's in this, error if not. - if (o == null || o.length != 2) { - Logger.logInfo("calculating formula failed, wrong number of values in PtgGE"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel + if (o == null || o.size != 2) { + Logger.logInfo("calculating formula failed, wrong number of values in PtgGE") + return PtgErr(PtgErr.ERROR_VALUE) // 20081203 KSC: handle error's ala Excel } // blank handling: // determine if any of the operands are double - if true, // then blank comparisons will be treated as 0's - boolean isDouble = false; - for (int i = 0; i < 2 && !isDouble; i++) { - if (!form[i].isBlank()) - isDouble = ((o[i] instanceof Double)); + var isDouble = false + run { + var i = 0 + while (i < 2 && !isDouble) { + if (!form[i].isBlank) + isDouble = o[i] is Double + i++ + } } - for (int i = 0; i < 2; i++) { - if (form[i].isBlank()) { + for (i in 0..1) { + if (form[i].isBlank) { if (isDouble) - o[i] = new Double(0.0); + o[i] = 0.0 else - o[i] = ""; // in this case, empty cells are handled as blank, not zero + o[i] = "" // in this case, empty cells are handled as blank, not zero } } - boolean res; - if (o[0] instanceof Double && o[1] instanceof Double) { + val res: Boolean + if (o[0] is Double && o[1] is Double) { //if (dub[0].doubleValue() <= dub[1].doubleValue()){ - res = ((Double) o[0]).doubleValue() >= ((Double) o[1]).doubleValue(); + res = (o[0] as Double).toDouble() >= (o[1] as Double).toDouble() } else { // string comparison?? // This is what Excel does ... if (Formula.isErrorValue(o[0].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())) if (Formula.isErrorValue(o[1].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())) // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res = (o[0].toString().compareTo(o[1].toString()) >= 0); -/* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = o[0].toString().compareTo(o[1].toString()) >= 0 + /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); try { @@ -136,33 +131,34 @@ public Ptg calculatePtg(Ptg[] form) { }*/ } - return new PtgBool(res); + return PtgBool(res) } else { // handle array fomulas - boolean res = false; - String retArry = ""; - int nArrays = java.lang.reflect.Array.getLength(o); // TODO: Should always be 2 ???????????????????? - int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i = 0; i < nArrays - 1; i += 2) { - res = false; - Object secondOp = null; - boolean comparitorIsArray = o[i + 1].getClass().isArray(); - if (!comparitorIsArray) secondOp = o[i + 1]; - for (int j = 0; j < nVals; j++) { - Object firstOp = Array.get(o[i], j); // first array index j + var res = false + var retArry = "" + val nArrays = java.lang.reflect.Array.getLength(o) // TODO: Should always be 2 ???????????????????? + val nVals = java.lang.reflect.Array.getLength(o[0]) // use first array element to determine length of values as subsequent vals might not be arrays + var i = 0 + while (i < nArrays - 1) { + res = false + var secondOp: Any? = null + val comparitorIsArray = o[i + 1].javaClass.isArray() + if (!comparitorIsArray) secondOp = o[i + 1] + for (j in 0 until nVals) { + val firstOp = Array.get(o[i], j) // first array index j if (comparitorIsArray) - secondOp = Array.get(o[i + 1], j); // second array index j + secondOp = Array.get(o[i + 1], j) // second array index j - if (firstOp instanceof Double && secondOp instanceof Double) - res = ((Double) firstOp).compareTo((Double) secondOp) >= 0; + if (firstOp is Double && secondOp is Double) + res = firstOp.compareTo((secondOp as Double?)!!) >= 0 else { // string comparison? // This is what Excel does ... if (Formula.isErrorValue(o[0].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())) if (Formula.isErrorValue(o[1].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())) // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res = (o[0].toString().compareTo(o[1].toString()) >= 0); - /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = o[0].toString().compareTo(o[1].toString()) >= 0 + /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); try { @@ -174,15 +170,16 @@ public Ptg calculatePtg(Ptg[] form) { res= false; }*/ } - retArry = retArry + res + ","; + retArry = "$retArry$res," } + i += 2 } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa } - /* }catch(NumberFormatException e){ 20090203 KSC: Handled above + /* }catch(NumberFormatException e){ 20090203 KSC: Handled above String[] s = getStringValuesFromPtgs(form); if (s==null || s.length<2) { // 20081203 KSC: Handle errors ala Excel if (!(s.length==1 && s[0].equals(PtgErr.ERROR_VALUE))) { @@ -204,9 +201,17 @@ public Ptg calculatePtg(Ptg[] form) { // Unfortuately <, >, and <> can all deal with strings as well... } */ - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); + } catch (ex: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) } + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5154662904662222256L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgGT.java b/src/main/java/io/starter/formats/XLS/formulas/PtgGT.kt similarity index 50% rename from src/main/java/io/starter/formats/XLS/formulas/PtgGT.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgGT.kt index ed1f24b..d551123 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgGT.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgGT.kt @@ -20,109 +20,103 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.formats.XLS.Formula; -import io.starter.toolkit.Logger; +import io.starter.formats.XLS.Formula +import io.starter.toolkit.Logger -import java.lang.reflect.Array; +import java.lang.reflect.Array /* - Evaluates to TRUE if the second-to-top operand is greater than the + Evaluates to TRUE if the second-to-top operand is greater than the top operand, otherwise FALSE - - + + * @see Ptg * @see Formula - -*/ -public class PtgGT extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1779961703481749272L; - - public boolean getIsOperator() { - return true; - } +*/ +class PtgGT : GenericPtg(), Ptg { - public boolean getIsPrimitiveOperator() { - return true; - } + override val isOperator: Boolean + get() = true - public boolean getIsBinaryOperator() { - return true; - } + override val isPrimitiveOperator: Boolean + get() = true - public PtgGT() { - ptgId = 0xD; - record = new byte[1]; - record[0] = 0xD; - } + override val isBinaryOperator: Boolean + get() = true /** * return the human-readable String representation of */ - public String getString() { - return ">"; - } + override val string: String + get() = ">" - public String toString() { - return this.getString(); + override val length: Int + get() = Ptg.PTG_GT_LENGTH + + init { + opcode = 0xD + record = ByteArray(1) + record[0] = 0xD } - public int getLength() { - return PTG_GT_LENGTH; + override fun toString(): String { + return this.string } /* Operator specific calculate method, this one determines if the second-to-top operand is less than the top operand; Returns a PtgBool */ - public Ptg calculatePtg(Ptg[] form) { + override fun calculatePtg(form: Array): Ptg? { try { // 20090202 KSC: Handle array formulas - Object[] o = getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { + val o = GenericPtg.getValuesFromPtgs(form) + if (!o!![0].javaClass.isArray()) { //double[] dub = super.getValuesFromPtgs(form); // there should always be only two ptg's in this, error if not. - if (o == null || o.length != 2) { - Logger.logInfo("calculating formula failesd, wrong number of values in PtgGT"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel + if (o == null || o.size != 2) { + Logger.logInfo("calculating formula failesd, wrong number of values in PtgGT") + return PtgErr(PtgErr.ERROR_VALUE) // 20081203 KSC: handle error's ala Excel } // blank handling: // determine if any of the operands are double - if true, // then blank comparisons will be treated as 0's - boolean isDouble = false; - for (int i = 0; i < 2 && !isDouble; i++) { - if (!form[i].isBlank()) - isDouble = ((o[i] instanceof Double)); + var isDouble = false + run { + var i = 0 + while (i < 2 && !isDouble) { + if (!form[i].isBlank) + isDouble = o[i] is Double + i++ + } } - for (int i = 0; i < 2; i++) { - if (form[i].isBlank()) { + for (i in 0..1) { + if (form[i].isBlank) { if (isDouble) - o[i] = new Double(0.0); + o[i] = 0.0 else - o[i] = ""; // in this case, empty cells are handled as blank, not zero + o[i] = "" // in this case, empty cells are handled as blank, not zero } } - boolean res; - if (o[0] instanceof Double && o[1] instanceof Double) { + val res: Boolean + if (o[0] is Double && o[1] is Double) { //if (dub[0].doubleValue() <= dub[1].doubleValue()){ - res = ((Double) o[0]).doubleValue() > ((Double) o[1]).doubleValue(); + res = (o[0] as Double).toDouble() > (o[1] as Double).toDouble() } else { // string comparison?? // This is what Excel does ... if (Formula.isErrorValue(o[0].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())) if (Formula.isErrorValue(o[1].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())) // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res = (o[0].toString().compareTo(o[1].toString()) > 0); -/* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = o[0].toString().compareTo(o[1].toString()) > 0 + /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); try { @@ -135,33 +129,33 @@ public Ptg calculatePtg(Ptg[] form) { }*/ } - PtgBool pboo = new PtgBool(res); - return pboo; + return PtgBool(res) } else { // handle array fomulas - boolean res = false; - String retArry = ""; - int nArrays = java.lang.reflect.Array.getLength(o); // TODO: Should always be 2 ???????????????????? - int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i = 0; i < nArrays - 1; i += 2) { - res = false; - Object secondOp = null; - boolean comparitorIsArray = o[i + 1].getClass().isArray(); - if (!comparitorIsArray) secondOp = o[i + 1]; - for (int j = 0; j < nVals; j++) { - Object firstOp = Array.get(o[i], j); // first array index j + var res = false + var retArry = "" + val nArrays = java.lang.reflect.Array.getLength(o) // TODO: Should always be 2 ???????????????????? + val nVals = java.lang.reflect.Array.getLength(o[0]) // use first array element to determine length of values as subsequent vals might not be arrays + var i = 0 + while (i < nArrays - 1) { + res = false + var secondOp: Any? = null + val comparitorIsArray = o[i + 1].javaClass.isArray() + if (!comparitorIsArray) secondOp = o[i + 1] + for (j in 0 until nVals) { + val firstOp = Array.get(o[i], j) // first array index j if (comparitorIsArray) - secondOp = Array.get(o[i + 1], j); // second array index j - if (firstOp instanceof Double && secondOp instanceof Double) - res = ((Double) firstOp).compareTo((Double) secondOp) > 0; + secondOp = Array.get(o[i + 1], j) // second array index j + if (firstOp is Double && secondOp is Double) + res = firstOp.compareTo((secondOp as Double?)!!) > 0 else { // string comparison? // This is what Excel does ... if (Formula.isErrorValue(o[0].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())) if (Formula.isErrorValue(o[1].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())) // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res = (o[0].toString().compareTo(o[1].toString()) > 0); - /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = o[0].toString().compareTo(o[1].toString()) > 0 + /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); try { @@ -173,22 +167,23 @@ public Ptg calculatePtg(Ptg[] form) { res= false; }*/ } - retArry = retArry + res + ","; + retArry = "$retArry$res," } + i += 2 } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa } - /*}catch(NumberFormatException e){ 20090203 KSC: ahndled above + /*}catch(NumberFormatException e){ 20090203 KSC: ahndled above String[] s = getStringValuesFromPtgs(form); if (s==null || s.length<2) { // 20081203 KSC: Handle errors ala Excel if (!(s.length==1 && s[0].equals(PtgErr.ERROR_VALUE))) { // report error? } return new PtgErr(PtgErr.ERROR_VALUE); - } + } // Unfortuately <, >, and <> can all deal with strings as well... if (s[0].equalsIgnoreCase(s[1])) return new PtgBool(false); try { @@ -200,10 +195,19 @@ public Ptg calculatePtg(Ptg[] form) { } return new PtgBool(true); */ - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); + } catch (ex: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) } + /*}*/ } + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -1779961703481749272L + } + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgInt.java b/src/main/java/io/starter/formats/XLS/formulas/PtgInt.java deleted file mode 100644 index 95d9fa6..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgInt.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.toolkit.ByteTools; - - -/* - Ptg that stores an integer value - - Offset Name Size Contents - ------------------------------------ - 0 w 2 An unsigned integer value - - * @see Ptg - * @see Formula - - -*/ -public class PtgInt extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2129624418815329359L; - - public boolean getIsOperand() { - return true; - } - - int val; - - /** - * return the human-readable String representation of - */ - public String getString() { - return String.valueOf(val); - } - - public PtgInt() { - } - - /* - * constructer to create ptgint's on the fly, from formulas - */ - public PtgInt(int i) { - val = i; - this.updateRecord(); - } - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - } - - // 0 to 65535 - outside of these bounds must be a PtgNumber - private void populateVals() { - byte b = 0; - int s = ByteTools.readInt(record[1], record[2], b, b); - val = s; - } - - public int getVal() { - return val; - } - - public int getIntVal() { - return val; - } - - public Object getValue() { - Integer i = Integer.valueOf(val); - return i; - } - - public void setVal(int i) { - val = i; - this.updateRecord(); - } - - public boolean getBooleanVal() { - return val == 1; - } - - public void updateRecord() { - byte[] tmp = new byte[1]; - tmp[0] = PTG_INT; - byte[] brow = ByteTools.shortToLEBytes((short) val); - tmp = ByteTools.append(brow, tmp); - record = tmp; - } - - public int getLength() { - return PTG_INT_LENGTH; - } - - public String toString() { - return String.valueOf(this.getVal()); - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgInt.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgInt.kt new file mode 100644 index 0000000..744a8dd --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgInt.kt @@ -0,0 +1,113 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.toolkit.ByteTools + + +/* + Ptg that stores an integer value + + Offset Name Size Contents + ------------------------------------ + 0 w 2 An unsigned integer value + + * @see Ptg + * @see Formula + + +*/ +class PtgInt : GenericPtg, Ptg { + + override val isOperand: Boolean + get() = true + + override var intVal: Int = 0 + internal set + + /** + * return the human-readable String representation of + */ + override val string: String + get() = intVal.toString() + + var `val`: Int + get() = intVal + set(i) { + intVal = i + this.updateRecord() + } + + override val value: Any? + get() = Integer.valueOf(intVal) + + val booleanVal: Boolean + get() = intVal == 1 + + override val length: Int + get() = Ptg.PTG_INT_LENGTH + + constructor() {} + + /* + * constructer to create ptgint's on the fly, from formulas + */ + constructor(i: Int) { + intVal = i + this.updateRecord() + } + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() + } + + // 0 to 65535 - outside of these bounds must be a PtgNumber + private fun populateVals() { + val b: Byte = 0 + val s = ByteTools.readInt(record[1], record[2], b, b) + intVal = s + } + + override fun updateRecord() { + var tmp = ByteArray(1) + tmp[0] = Ptg.PTG_INT + val brow = ByteTools.shortToLEBytes(intVal.toShort()) + tmp = ByteTools.append(brow, tmp) + record = tmp + } + + override fun toString(): String { + return this.`val`.toString() + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -2129624418815329359L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.java b/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.java deleted file mode 100644 index a33a9cf..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import java.util.ArrayList; - - -/* - Computes the intersection of the two top operands. Essentially - this is a space operator. Makes me think of space and drums, just - about the only thing more boring than these binary operand PTG's. - - - * @see Ptg - * @see Formula - - -*/ -public class PtgIsect extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2131759675781833457L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public PtgIsect() { - ptgId = 0xF; - record = new byte[1]; - record[0] = 0xF; - } - - public String toString() { - return this.getString(); - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return " "; - } - - public int getLength() { - return PTG_ISECT_LENGTH; - } - - /** - * Intersection = Where A and B are shared. - * The ISECT operator (space) - * Returns the intersected range of two ranges. If the resulting cell - * range is empty, the formula will return the error code “#NULL!” (for instance A1:A2 B3). - * A1:B2 B2:C3 ==> B2 - */ - public Ptg calculatePtg(Ptg[] form) { - if (form.length != 2) - return new PtgErr(PtgErr.ERROR_VALUE); - - try { - String sourceSheet = null; - try { - sourceSheet = this.getParentRec().getSheet().getSheetName(); - } catch (NullPointerException ne) { - } - ArrayList first = null; - ArrayList last = null; - for (int i = 0; i < 2; i++) { - Ptg p = form[i]; - ArrayList a = new ArrayList(); - if (p instanceof PtgArea) { - if (!((PtgArea) p).wholeCol || ((PtgArea) p).wholeRow) { - Ptg[] pc = p.getComponents(); - if (pc != null) { - for (int j = 0; j < pc.length; j++) { - ((PtgRef) pc[j]).setSheetName(((PtgArea) p).getSheetName()); - a.add(pc[j]); - } - } - } else - a.add(p); // TODO: what????????? - } else if (p instanceof PtgRef3d) { - a.add(p); - } else if (p instanceof PtgRef) { - a.add(p); - } else if (p instanceof PtgName) { - Ptg[] pc = p.getComponents(); - for (int j = 0; j < pc.length; j++) - a.add(pc[j]); - } else if (p instanceof PtgStr) { - String[] comps = (p.toString()).split(","); - for (int j = 0; j < comps.length; j++) { - if (comps[j].indexOf(":") == -1) { - if (!comps[j].equals("#REF!") && - !comps[j].equals("#NULL!")) { - PtgRef3d pr = new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(comps[j]); - a.add(pr); - } else { - PtgRefErr3d pr = new PtgRefErr3d(); - pr.setParentRec(this.getParentRec()); - a.add(pr); - } - } else { - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(comps[j]); - Ptg[] pcs = pa.getComponents(); - if (pcs != null) { - for (int k = 0; k < pcs.length; k++) { - ((PtgRef) pcs[k]).setSheetName(pa.getSheetName()); - a.add(pcs[k]); - } - } - } - } - } else if (p instanceof PtgArray) { - // parse array components and create refs - Ptg[] pc = p.getComponents(); - if (pc != null) { - for (int j = 0; j < pc.length; j++) { - String loc = pc[j].toString(); - if (loc.indexOf(":") == -1) { - if (loc.indexOf("!") == -1) { - PtgRef pr = new PtgRef(); - pr.setUseReferenceTracker(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } else { - PtgRef3d pr = new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } - } else { - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(loc); - Ptg[] pcs = pa.getComponents(); - for (int k = 0; k < pcs.length; k++) { - ((PtgRef) pcs[k]).setSheetName(pa.getSheetName()); - a.add(pcs[k]); - } - } - } - } - } else if (p instanceof PtgErr || p instanceof PtgRefErr || p instanceof PtgAreaErr3d) { - // DO WHAT??? - } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array - Ptg[] pc = ((GenericPtg) p).vars; - for (int j = 0; j < pc.length; j++) { - if (pc[j] instanceof PtgArea & !(pc[j] instanceof PtgAreaErr3d)) { - Ptg[] pa = pc[j].getComponents(); - for (int k = 0; k < pa.length; k++) - a.add(pa[k]); - } else - a.add(pc[j]); - } - } - if (first == null) - first = a; - else - last = a; - } - // now have components for both operands - // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars - GenericPtg retp = new PtgMystery(); - ArrayList retptgs = new ArrayList(); - for (int k = 0; k < first.size(); k++) { - PtgRef pr = (PtgRef) first.get(k); - int[] rc = pr.getIntLocation(); - for (int m = 0; m < last.size(); m++) { - PtgRef pc = (PtgRef) last.get(m); - int[] rc2 = pc.getIntLocation(); - if (java.util.Arrays.equals(rc, rc2)) { - retptgs.add(pc); - last.remove(m); - m--; - } - } - } - Ptg[] ptgs = new Ptg[retptgs.size()]; - retptgs.toArray(ptgs); - retp.setVars(ptgs); - return retp; - } catch (NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } catch (Exception e) { // handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.kt new file mode 100644 index 0000000..55bbbfd --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgIsect.kt @@ -0,0 +1,225 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import java.util.ArrayList + + +/* + Computes the intersection of the two top operands. Essentially + this is a space operator. Makes me think of space and drums, just + about the only thing more boring than these binary operand PTG's. + + + * @see Ptg + * @see Formula + + +*/ +class PtgIsect : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val string: String + get() = " " + + override val length: Int + get() = Ptg.PTG_ISECT_LENGTH + + init { + opcode = 0xF + record = ByteArray(1) + record[0] = 0xF + } + + override fun toString(): String { + return this.string + } + + /** + * Intersection = Where A and B are shared. + * The ISECT operator (space) + * Returns the intersected range of two ranges. If the resulting cell + * range is empty, the formula will return the error code “#NULL!” (for instance A1:A2 B3). + * A1:B2 B2:C3 ==> B2 + */ + override fun calculatePtg(form: Array): Ptg? { + if (form.size != 2) + return PtgErr(PtgErr.ERROR_VALUE) + + try { + var sourceSheet: String? = null + try { + sourceSheet = this.parentRec!!.sheet!!.sheetName + } catch (ne: NullPointerException) { + } + + var first: ArrayList<*>? = null + var last: ArrayList<*>? = null + for (i in 0..1) { + val p = form[i] + val a = ArrayList() + if (p is PtgArea) { + if (!p.isWholeCol || p.isWholeRow) { + val pc = p.components + if (pc != null) { + for (j in pc.indices) { + (pc[j] as PtgRef).sheetName = p.sheetName + a.add(pc[j]) + } + } + } else + a.add(p) // TODO: what????????? + } else if (p is PtgRef3d) { + a.add(p) + } else if (p is PtgRef) { + a.add(p) + } else if (p is PtgName) { + val pc = p.components + for (j in pc.indices) + a.add(pc[j]) + } else if (p is PtgStr) { + val comps = p.toString().split(",".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + for (j in comps.indices) { + if (comps[j].indexOf(":") == -1) { + if (comps[j] != "#REF!" && comps[j] != "#NULL!") { + val pr = PtgRef3d(false) + pr.parentRec = this.parentRec + pr.location = comps[j] + a.add(pr) + } else { + val pr = PtgRefErr3d() + pr.parentRec = this.parentRec + a.add(pr) + } + } else { + val pa = PtgArea3d(false) + pa.parentRec = this.parentRec + pa.location = comps[j] + val pcs = pa.components + if (pcs != null) { + for (k in pcs.indices) { + (pcs[k] as PtgRef).sheetName = pa.sheetName + a.add(pcs[k]) + } + } + } + } + } else if (p is PtgArray) { + // parse array components and create refs + val pc = p.components + if (pc != null) { + for (j in pc.indices) { + val loc = pc[j].toString() + if (loc.indexOf(":") == -1) { + if (loc.indexOf("!") == -1) { + val pr = PtgRef() + pr.useReferenceTracker = false + pr.parentRec = this.parentRec + pr.location = loc + a.add(pr) + } else { + val pr = PtgRef3d(false) + pr.parentRec = this.parentRec + pr.location = loc + a.add(pr) + } + } else { + val pa = PtgArea3d(false) + pa.parentRec = this.parentRec + pa.location = loc + val pcs = pa.components + for (k in pcs!!.indices) { + (pcs[k] as PtgRef).sheetName = pa.sheetName + a.add(pcs[k]) + } + } + } + } + } else if (p is PtgErr || p is PtgRefErr || p is PtgAreaErr3d) { + // DO WHAT??? + } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array + val pc = (p as GenericPtg).vars + for (j in pc!!.indices) { + if ((pc[j] is PtgArea) and (pc[j] !is PtgAreaErr3d)) { + val pa = pc[j].components + for (k in pa.indices) + a.add(pa[k]) + } else + a.add(pc[j]) + } + } + if (first == null) + first = a + else + last = a + } + // now have components for both operands + // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars + val retp = PtgMystery() + val retptgs = ArrayList() + for (k in first!!.indices) { + val pr = first[k] as PtgRef + val rc = pr.intLocation + var m = 0 + while (m < last!!.size) { + val pc = last[m] as PtgRef + val rc2 = pc.intLocation + if (java.util.Arrays.equals(rc, rc2)) { + retptgs.add(pc) + last.removeAt(m) + m-- + } + m++ + } + } + val ptgs = arrayOfNulls(retptgs.size) + retptgs.toTypedArray() + retp.setVars(ptgs) + return retp + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } catch (e: Exception) { // handle error ala Excel + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -2131759675781833457L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgLE.java b/src/main/java/io/starter/formats/XLS/formulas/PtgLE.kt similarity index 51% rename from src/main/java/io/starter/formats/XLS/formulas/PtgLE.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgLE.kt index ab15b59..8e6cc52 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgLE.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgLE.kt @@ -20,107 +20,101 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.formats.XLS.Formula; -import io.starter.toolkit.Logger; +import io.starter.formats.XLS.Formula +import io.starter.toolkit.Logger -import java.lang.reflect.Array; +import java.lang.reflect.Array /* Ptg that is a Less than or equal to operand - + Evaluates to TRUE if the second operand is less than or equal to the top operand, otherwise FALSE - - + + * @see Ptg * @see Formula - -*/ -public class PtgLE extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -4356555760240325388L; - public boolean getIsOperator() { - return true; - } +*/ +class PtgLE : GenericPtg(), Ptg { - public boolean getIsPrimitiveOperator() { - return true; - } + override val isOperator: Boolean + get() = true - public boolean getIsBinaryOperator() { - return true; - } + override val isPrimitiveOperator: Boolean + get() = true - public PtgLE() { - ptgId = 0xA; - record = new byte[1]; - record[0] = 0xA; - } + override val isBinaryOperator: Boolean + get() = true /** * return the human-readable String representation of */ - public String getString() { - return "<="; - } + override val string: String + get() = "<=" - public int getLength() { - return PTG_LE_LENGTH; + override val length: Int + get() = Ptg.PTG_LE_LENGTH + + init { + opcode = 0xA + record = ByteArray(1) + record[0] = 0xA } /* Operator specific calculate method, this one determines if the second-to-top operand is less than or equal to the top operand; Returns a PtgBool */ - public Ptg calculatePtg(Ptg[] form) { + override fun calculatePtg(form: Array): Ptg? { try { // 20090202 KSC: Handle array formulas - Object[] o = getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { + val o = GenericPtg.getValuesFromPtgs(form) + if (!o!![0].javaClass.isArray()) { //double[] dub = super.getValuesFromPtgs(form); // there should always be only two ptg's in this, error if not. - if (o == null || o.length != 2) { - Logger.logWarn("calculating formula failed, wrong number of values in PtgLE"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel + if (o == null || o.size != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgLE") + return PtgErr(PtgErr.ERROR_VALUE) // 20081203 KSC: handle error's ala Excel } // blank handling: // determine if any of the operands are double - if true, // then blank comparisons will be treated as 0's - boolean isDouble = false; - for (int i = 0; i < 2 && !isDouble; i++) { - if (!form[i].isBlank()) - isDouble = ((o[i] instanceof Double)); + var isDouble = false + run { + var i = 0 + while (i < 2 && !isDouble) { + if (!form[i].isBlank) + isDouble = o[i] is Double + i++ + } } - for (int i = 0; i < 2; i++) { - if (form[i].isBlank()) { + for (i in 0..1) { + if (form[i].isBlank) { if (isDouble) - o[i] = new Double(0.0); + o[i] = 0.0 else - o[i] = ""; // in this case, empty cells are handled as blank, not zero + o[i] = "" // in this case, empty cells are handled as blank, not zero } } - boolean res; - if (o[0] instanceof Double && o[1] instanceof Double) { + val res: Boolean + if (o[0] is Double && o[1] is Double) { //if (dub[0].doubleValue() <= dub[1].doubleValue()){ - res = ((Double) o[0]).doubleValue() <= ((Double) o[1]).doubleValue(); + res = (o[0] as Double).toDouble() <= (o[1] as Double).toDouble() } else { // string comparison?? // This is what Excel does if (Formula.isErrorValue(o[0].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())) if (Formula.isErrorValue(o[1].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())) // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res = (o[0].toString().compareTo(o[1].toString()) <= 0); -/* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = o[0].toString().compareTo(o[1].toString()) <= 0 + /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); try { @@ -133,33 +127,33 @@ public Ptg calculatePtg(Ptg[] form) { }*/ } - PtgBool pboo = new PtgBool(res); - return pboo; + return PtgBool(res) } else { // handle array fomulas - boolean res = false; - String retArry = ""; - int nArrays = java.lang.reflect.Array.getLength(o); // TODO: Should always be 2 ???????????????????? - int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i = 0; i < nArrays - 1; i += 2) { - res = false; - Object secondOp = null; - boolean comparitorIsArray = o[i + 1].getClass().isArray(); - if (!comparitorIsArray) secondOp = o[i + 1]; - for (int j = 0; j < nVals; j++) { - Object firstOp = Array.get(o[i], j); // first array index j + var res = false + var retArry = "" + val nArrays = java.lang.reflect.Array.getLength(o) // TODO: Should always be 2 ???????????????????? + val nVals = java.lang.reflect.Array.getLength(o[0]) // use first array element to determine length of values as subsequent vals might not be arrays + var i = 0 + while (i < nArrays - 1) { + res = false + var secondOp: Any? = null + val comparitorIsArray = o[i + 1].javaClass.isArray() + if (!comparitorIsArray) secondOp = o[i + 1] + for (j in 0 until nVals) { + val firstOp = Array.get(o[i], j) // first array index j if (comparitorIsArray) - secondOp = Array.get(o[i + 1], j); // second array index j - if (firstOp instanceof Double && secondOp instanceof Double) - res = ((Double) firstOp).compareTo((Double) secondOp) <= 0; + secondOp = Array.get(o[i + 1], j) // second array index j + if (firstOp is Double && secondOp is Double) + res = firstOp.compareTo((secondOp as Double?)!!) <= 0 else { // string comparison? // This is what Excel does ... if (Formula.isErrorValue(o[0].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())) if (Formula.isErrorValue(o[1].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); + return PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())) // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res = (o[0].toString().compareTo(o[1].toString()) <= 0); - /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = o[0].toString().compareTo(o[1].toString()) <= 0 + /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); try { @@ -171,15 +165,16 @@ public Ptg calculatePtg(Ptg[] form) { res= false; }*/ } - retArry = retArry + res + ","; + retArry = "$retArry$res," } + i += 2 } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa } - /*}catch(NumberFormatException e){ 20090203 KSC: Handled above + /*}catch(NumberFormatException e){ 20090203 KSC: Handled above String[] s = getStringValuesFromPtgs(form); if (s==null || s.length<2) { // 20081203 KSC: Handle errors ala Excel if (!(s.length==1 && s[0].equals("#VALUE!"))) { @@ -195,12 +190,21 @@ public Ptg calculatePtg(Ptg[] form) { if (i1[i] < i2[i])return new PtgBool(true); } return new PtgBool(true); - + // Unfortuately <, >, and <> can all deal with strings as well... }*/ - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); + } catch (ex: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) } + + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -4356555760240325388L } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgLT.java b/src/main/java/io/starter/formats/XLS/formulas/PtgLT.java deleted file mode 100644 index 37992ad..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgLT.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.Formula; -import io.starter.toolkit.Logger; - -import java.lang.reflect.Array; - - -/* - Ptg that is a Less than operand - - Evaluates to TRUE if the second operand is less than the top - operand, otherwise FALSE - - * @see Ptg - * @see Formula - - -*/ -public class PtgLT extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2568203115024599915L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public PtgLT() { - ptgId = 0x9; - record = new byte[1]; - record[0] = 0x9; - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return "<"; - } - - public int getLength() { - return PTG_LT_LENGTH; - } - - /* Operator specific calculate method, this one determines if the second-to-top - operand is less than the top operand; Returns a PtgBool - - */ - public Ptg calculatePtg(Ptg[] form) { - try { - // 20090202 KSC: Handle array formulas - Object[] o = getValuesFromPtgs(form); - if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o.length != 2) { - Logger.logWarn("calculating formula failed, wrong number of values in PtgLT"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; - } - // blank handling: - // determine if any of the operands are double - if true, - // then blank comparisons will be treated as 0's - boolean isDouble = false; - for (int i = 0; i < 2 && !isDouble; i++) { - if (!form[i].isBlank()) - isDouble = ((o[i] instanceof Double)); - } - for (int i = 0; i < 2; i++) { - if (form[i].isBlank()) { - if (isDouble) - o[i] = new Double(0.0); - else - o[i] = ""; // in this case, empty cells are handled as blank, not zero - } - } - - boolean res; - //if (dub[0].doubleValue() < dub[1].doubleValue()){ - if (o[0] instanceof Double && o[1] instanceof Double) { - res = ((Double) o[0]).doubleValue() < ((Double) o[1]).doubleValue(); - } else { // string comparison?? - // This is what Excel does ... - if (Formula.isErrorValue(o[0].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())); - if (Formula.isErrorValue(o[1].toString())) - return new PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())); - // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - res = (o[0].toString().compareTo(o[1].toString()) < 0); -/* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... - int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); - int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); - try { - res= true; - for(int k=0;k, and <> can all deal with strings as well... - String[] s = getStringValuesFromPtgs(form); - if (s[0].equalsIgnoreCase(s[1])) return new PtgBool(false); - int[] i1 = ExcelTools.transformStringToIntVals(s[0]); - int[] i2 = ExcelTools.transformStringToIntVals(s[1]); - for(int i=0;i i2[i])return new PtgBool(false); - if (i1[i] < i2[i])return new PtgBool(true); - } - return new PtgBool(true); - */ - } catch (Exception ex) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - /*}*/ - } - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgLT.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgLT.kt new file mode 100644 index 0000000..4c789a4 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgLT.kt @@ -0,0 +1,207 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.Formula +import io.starter.toolkit.Logger + +import java.lang.reflect.Array + + +/* + Ptg that is a Less than operand + + Evaluates to TRUE if the second operand is less than the top + operand, otherwise FALSE + + * @see Ptg + * @see Formula + + +*/ +class PtgLT : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val string: String + get() = "<" + + override val length: Int + get() = Ptg.PTG_LT_LENGTH + + init { + opcode = 0x9 + record = ByteArray(1) + record[0] = 0x9 + } + + /* Operator specific calculate method, this one determines if the second-to-top + operand is less than the top operand; Returns a PtgBool + + */ + override fun calculatePtg(form: Array): Ptg? { + try { + // 20090202 KSC: Handle array formulas + val o = GenericPtg.getValuesFromPtgs(form) ?: return PtgErr(PtgErr.ERROR_VALUE) +// some error in value(s) + if (!o[0].javaClass.isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o.size != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgLT") + return PtgErr(PtgErr.ERROR_VALUE) // 20081203 KSC: handle error's ala Excel return null; + } + // blank handling: + // determine if any of the operands are double - if true, + // then blank comparisons will be treated as 0's + var isDouble = false + run { + var i = 0 + while (i < 2 && !isDouble) { + if (!form[i].isBlank) + isDouble = o[i] is Double + i++ + } + } + for (i in 0..1) { + if (form[i].isBlank) { + if (isDouble) + o[i] = 0.0 + else + o[i] = "" // in this case, empty cells are handled as blank, not zero + } + } + + val res: Boolean + //if (dub[0].doubleValue() < dub[1].doubleValue()){ + if (o[0] is Double && o[1] is Double) { + res = (o[0] as Double).toDouble() < (o[1] as Double).toDouble() + } else { // string comparison?? + // This is what Excel does ... + if (Formula.isErrorValue(o[0].toString())) + return PtgErr(PtgErr.convertStringToLookupByte(o[0].toString())) + if (Formula.isErrorValue(o[1].toString())) + return PtgErr(PtgErr.convertStringToLookupByte(o[1].toString())) + // KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + res = o[0].toString().compareTo(o[1].toString()) < 0 + /* KSC: ExcelTools.transformStringToIntVals does not work in all cases- think of date strings ... + int[] i1 = ExcelTools.transformStringToIntVals(o[0].toString()); + int[] i2 = ExcelTools.transformStringToIntVals(o[1].toString()); + try { + res= true; + for(int k=0;k, and <> can all deal with strings as well... + String[] s = getStringValuesFromPtgs(form); + if (s[0].equalsIgnoreCase(s[1])) return new PtgBool(false); + int[] i1 = ExcelTools.transformStringToIntVals(s[0]); + int[] i2 = ExcelTools.transformStringToIntVals(s[1]); + for(int i=0;i i2[i])return new PtgBool(false); + if (i1[i] < i2[i])return new PtgBool(true); + } + return new PtgBool(true); + */ + } catch (ex: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + /*}*/ + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -2568203115024599915L + } + + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java deleted file mode 100644 index 9661128..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemArea.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.ExpressionParser; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.Stack; - -/** - * PtgMemArea is an optimization of referenced areas. Sweet! - * ******************************************************************************* - * NOTE: Below is from documentation but DOES NOT APPEAR to be what happens in actuality; - * PtgMemArea token is followed by several ptg reference-types plus ptgunion(s), ends with a PtgParen. - * The cce field is the length of all of these following tokens. - * These following Ptgs are set and parsed in .setPostRecord - *

                      - *

                      - *

                      - * Like most optimizations it really sucks. It is also one of the few Ptg's that - * has a variable length. - *

                      - * Format of length section - *

                      - * Offset      Name        Size    Contents
                      - * ----------------------------------------------------
                      - * 0           (reserved)     4       Whatever it may be
                      - * 2           cce			   2	   length of the reference subexpression
                      - *
                      - * Format of reference Subexpression
                      - * Offset      Name        Size    Contents
                      - * ----------------------------------------------------
                      - * 0			cref		2			The number of rectangles to follow
                      - * 2			rgref		var			An Array of rectangles
                      - *
                      - * Format of Rectangles
                      - * Offset      Name        Size    Contents
                      - * ----------------------------------------------------
                      - * 0           rwFirst     2       The First row of the reference
                      - * 2           rwLast     2       The Last row of the reference
                      - * 4           ColFirst    1       (see following table)
                      - * 6           ColLast    1       (see following table)
                      - * 
                      - * - * @see Ptg - * @see Formula - */ -public class PtgMemArea extends GenericPtg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6869393084367355874L; - int cce = 0; - Stack subexpression = null; - Ptg[] ptgs = null; - - public boolean getIsOperand() { - return true; - } - - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - } - - public int getnTokens() { - return cce; - } // KSC: - - /** - * sets the bytes to describe the subexpression (set of ptgs) - * and parses the subexpression - * - * @param b - */ - public void setSubExpression(byte[] b) { - byte[] retbytes = new byte[7]; - System.arraycopy(record, 0, retbytes, 0, record.length); - retbytes = ByteTools.append(b, retbytes); - record = retbytes; - this.populateVals(); - // TODO: - } - - // really no need to keep postexpression as can regenerate easily ... - byte[] postExp = null; - - /** - * sets the PtgExtraMem structure which is appended to the end of the function array - * - * @param b - */ - public void setPostExpression(byte[] b, int expressionLen) { - int len = b.length - expressionLen; - postExp = new byte[len]; - System.arraycopy(b, expressionLen, postExp, 0, len); -/* - * parsing PtgExtraMem - not really necessary - * - if (b.length > expressionLen+2) { - int z= expressionLen; - int count= ByteTools.readShort(b[z++], b[z++]); - for (int i= 0; i < count && z+7 < b.length; i++) { - int[] rc= new int[4]; - rc[0]= ByteTools.readShort(b[z++], b[z++]); // rw first - rc[2]= ByteTools.readShort(b[z++], b[z++]); // rw last - rc[1]= ByteTools.readShort(b[z++], b[z++]);// col first - rc[3]= ByteTools.readShort(b[z++], b[z++]); // col last - ExcelTools.formatRangeRowCol(rc); - } - }*/ - } - - /** - * retrieves the PtgExtraMem structure that is located at the end of the function record - * - * @return - */ - public byte[] getPostRecord() { - short cce = 0; - // first count # refs (excluding ptgUnion, range, etc ) - for (int i = 0; i < subexpression.size(); i++) { - Ptg p = (Ptg) subexpression.get(i); - if (p instanceof PtgRef) { - cce++; - } - } - - // input cce + cce*Ref8U (8 bytes) describing reference - byte[] b = ByteTools.shortToLEBytes(cce); - byte[] recbytes = new byte[(cce * 8) + 2]; - recbytes[0] = b[0]; - recbytes[1] = b[1]; - int pos = 2; - for (int i = 0; i < subexpression.size() && pos + 7 < recbytes.length; i++) { - Ptg p = (Ptg) subexpression.get(i); - if (p instanceof PtgRef) { - int[] rc = ((PtgRef) p).getRowCol(); - System.arraycopy(ByteTools.shortToLEBytes((short) rc[0]), 0, recbytes, pos, 2); // rw first - System.arraycopy(ByteTools.shortToLEBytes((short) rc[1]), 0, recbytes, pos + 4, 2); // col first - if (rc.length == 2) { // a single ref; repeat - System.arraycopy(ByteTools.shortToLEBytes((short) rc[0]), 0, recbytes, pos + 2, 2); // rw last - System.arraycopy(ByteTools.shortToLEBytes((short) rc[1]), 0, recbytes, pos + 6, 2); // col last - } else { // a range - System.arraycopy(ByteTools.shortToLEBytes((short) rc[2]), 0, recbytes, pos + 2, 2); // rw last - System.arraycopy(ByteTools.shortToLEBytes((short) rc[3]), 0, recbytes, pos + 6, 2); // col last - } - pos += 8; - } - } -/* KSC: TESTING - if (!Arrays.equals(recbytes, postExp)) - io.starter.toolkit.Logger.log("ISSUE!!!");*/ - return recbytes; - } - - ArrayList refsheets = new ArrayList(); - - void populateVals() { - // 1st byte = ID, next 4 are ignored - // cce= size of following sub-expressions - cce = ByteTools.readShort(record[5], record[6]); - // this is not really correct! - if (record.length > 7) { - byte[] subexp; - subexp = new byte[cce]; - System.arraycopy(record, 7, subexp, 0, cce); - subexpression = ExpressionParser.parseExpression(subexp, this.parent_rec); - // subexpression stack in form of: REFERENCE, REFERENCE, OP [,REFERENCE, OP] ... - // op can be one of: PtgUnion [,] PtgIsect [ ] or PtgRange [:] - // calculate subexpression to obtain ptgs - try { - Object o = FormulaCalculator.calculateFormula(this.subexpression); - ArrayList components = new ArrayList(); - if (o != null && o instanceof Ptg[]) { - ptgs = (Ptg[]) o; - for (int i = 0; i < ptgs.length; i++) { - if (!refsheets.contains(((PtgRef) ptgs[i]).getSheetName())) - refsheets.add(((PtgRef) ptgs[i]).getSheetName()); - if (ptgs[i] instanceof PtgArea) { - Ptg[] p = ptgs[i].getComponents(); - for (int j = 0; j < p.length; j++) - components.add(p[j]); - } else - components.add(ptgs[i]); - } - } else { // often a single reference surrounded by parens - for (int i = 0; i < subexpression.size(); i++) { - try { - PtgRef pr = (PtgRef) subexpression.get(i); - if (!refsheets.contains(pr.getSheetName())) - refsheets.add(pr.getSheetName()); - if (pr instanceof PtgArea) { - Ptg[] pa = pr.getComponents(); - for (int j = 0; j < pa.length; j++) - components.add(pa[j]); - } else - components.add(pr); - } catch (Exception e) { - } - } - } - ptgs = new Ptg[components.size()]; - components.toArray(ptgs); - } catch (Exception e) { - Logger.logErr("PtgMemArea init: " + e.toString()); - } - - //int z= subexpression.size(); - // to get # of references (PtgRefs) = stack size/2 + 1 - } - } - - /** - * generate the bytes necessary to describe this PtgMemArea; - * extra data described by getPostRecord is necessary for completion - * - * @see getPostRecord - */ - public byte[] getRecord() { - int len = 0; - for (int i = 0; i < subexpression.size(); i++) { - Ptg p = (Ptg) subexpression.get(i); - len += p.getRecord().length; - } - cce = len; - byte[] rec = new byte[len + 7]; - byte[] b = ByteTools.shortToLEBytes((short) cce); - rec[0] = 0x26; - // bytes 1-4 are unused - rec[5] = b[0]; - rec[6] = b[1]; - int offset = 7; - for (int i = 0; i < subexpression.size(); i++) { - Ptg p = (Ptg) subexpression.get(i); - System.arraycopy(p.getRecord(), 0, rec, offset, p.getRecord().length); - offset += p.getRecord().length; - } - record = rec; - return record; - } - - public int getLength() { - return cce + 7; - } - - //PtgRef[] comps = null; not used anymore; see note below - - /** - * Returns all of the cells of this range as PtgRef's. - * This includes empty cells, values, formulas, etc. - * Note the setting of parent-rec requires finding the cell - * the PtgRef refer's to. If that is null then the PtgRef - * will exist, just with a null value. This could cause issues when - * programatically populating cells. - *

                      - * NOTE: now obtaining component ptgs is done in populateValues as it is - * a more complex operation than simply gathering all referenced ptgs - */ - public Ptg[] getComponents() { - /*if(comps!=null) // cache - return comps; - - ArrayList v = new ArrayList(); - try { - for (int i= 0; i < ptgs.length; i++) { - if (ptgs[i] instanceof PtgArea) { - Ptg[] ps= ((PtgRef) ptgs[i]).getComponents(); - for (int j= 0; j< ps.length; j++) - v.add(ps[j]); - } else if (ptgs[i] instanceof PtgRef) { - v.add((PtgRef) ptgs[i]); - } else { // it's a PtgName - Ptg[] pcomps= ((PtgName) ptgs[i]).getComponents(); - for (int j= 0; j. + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.ExpressionParser +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.Stack + +/** + * PtgMemArea is an optimization of referenced areas. Sweet! + * ******************************************************************************* + * NOTE: Below is from documentation but DOES NOT APPEAR to be what happens in actuality; + * PtgMemArea token is followed by several ptg reference-types plus ptgunion(s), ends with a PtgParen. + * The cce field is the length of all of these following tokens. + * These following Ptgs are set and parsed in .setPostRecord + * + * + * + * + * + * + * Like most optimizations it really sucks. It is also one of the few Ptg's that + * has a variable length. + * + * + * Format of length section + *

                      + * Offset      Name        Size    Contents
                      + * ----------------------------------------------------
                      + * 0           (reserved)     4       Whatever it may be
                      + * 2           cce			   2	   length of the reference subexpression
                      + *
                      + * Format of reference Subexpression
                      + * Offset      Name        Size    Contents
                      + * ----------------------------------------------------
                      + * 0			cref		2			The number of rectangles to follow
                      + * 2			rgref		var			An Array of rectangles
                      + *
                      + * Format of Rectangles
                      + * Offset      Name        Size    Contents
                      + * ----------------------------------------------------
                      + * 0           rwFirst     2       The First row of the reference
                      + * 2           rwLast     2       The Last row of the reference
                      + * 4           ColFirst    1       (see following table)
                      + * 6           ColLast    1       (see following table)
                      +
                      * + * + * @see Ptg + * + * @see Formula + */ +open class PtgMemArea : GenericPtg() { + internal var cce = 0 + internal var subexpression: Stack<*>? = null + //PtgRef[] comps = null; not used anymore; see note below + + /** + * Returns all of the cells of this range as PtgRef's. + * This includes empty cells, values, formulas, etc. + * Note the setting of parent-rec requires finding the cell + * the PtgRef refer's to. If that is null then the PtgRef + * will exist, just with a null value. This could cause issues when + * programatically populating cells. + * + * + * NOTE: now obtaining component ptgs is done in populateValues as it is + * a more complex operation than simply gathering all referenced ptgs + */ + override/*if(comps!=null) // cache + return comps; + + ArrayList v = new ArrayList(); + try { + for (int i= 0; i < ptgs.length; i++) { + if (ptgs[i] instanceof PtgArea) { + Ptg[] ps= ((PtgRef) ptgs[i]).getComponents(); + for (int j= 0; j< ps.length; j++) + v.add(ps[j]); + } else if (ptgs[i] instanceof PtgRef) { + v.add((PtgRef) ptgs[i]); + } else { // it's a PtgName + Ptg[] pcomps= ((PtgName) ptgs[i]).getComponents(); + for (int j= 0; j? = null + internal set + + override val isOperand: Boolean + get() = true + + // really no need to keep postexpression as can regenerate easily ... + internal var postExp: ByteArray? = null + + /** + * retrieves the PtgExtraMem structure that is located at the end of the function record + * + * @return + */ + // first count # refs (excluding ptgUnion, range, etc ) + // input cce + cce*Ref8U (8 bytes) describing reference + // rw first + // col first + // a single ref; repeat + // rw last + // col last + // a range + // rw last + // col last + /* KSC: TESTING + if (!Arrays.equals(recbytes, postExp)) + io.starter.toolkit.Logger.log("ISSUE!!!");*/ val postRecord: ByteArray + get() { + var cce: Short = 0 + for (i in subexpression!!.indices) { + val p = subexpression!![i] as Ptg + if (p is PtgRef) { + cce++ + } + } + val b = ByteTools.shortToLEBytes(cce) + val recbytes = ByteArray(cce * 8 + 2) + recbytes[0] = b[0] + recbytes[1] = b[1] + var pos = 2 + var i = 0 + while (i < subexpression!!.size && pos + 7 < recbytes.size) { + val p = subexpression!![i] as Ptg + if (p is PtgRef) { + val rc = p.rowCol + System.arraycopy(ByteTools.shortToLEBytes(rc[0].toShort()), 0, recbytes, pos, 2) + System.arraycopy(ByteTools.shortToLEBytes(rc[1].toShort()), 0, recbytes, pos + 4, 2) + if (rc.size == 2) { + System.arraycopy(ByteTools.shortToLEBytes(rc[0].toShort()), 0, recbytes, pos + 2, 2) + System.arraycopy(ByteTools.shortToLEBytes(rc[1].toShort()), 0, recbytes, pos + 6, 2) + } else { + System.arraycopy(ByteTools.shortToLEBytes(rc[2].toShort()), 0, recbytes, pos + 2, 2) + System.arraycopy(ByteTools.shortToLEBytes(rc[3].toShort()), 0, recbytes, pos + 6, 2) + } + pos += 8 + } + i++ + } + return recbytes + } + + internal var refsheets = ArrayList() + + /** + * generate the bytes necessary to describe this PtgMemArea; + * extra data described by getPostRecord is necessary for completion + * + * @see getPostRecord + */ + override// bytes 1-4 are unused + val record: ByteArray + get() { + var len = 0 + for (i in subexpression!!.indices) { + val p = subexpression!![i] as Ptg + len += p.record.size + } + cce = len + val rec = ByteArray(len + 7) + val b = ByteTools.shortToLEBytes(cce.toShort()) + rec[0] = 0x26 + rec[5] = b[0] + rec[6] = b[1] + var offset = 7 + for (i in subexpression!!.indices) { + val p = subexpression!![i] as Ptg + System.arraycopy(p.record, 0, rec, offset, p.record.size) + offset += p.record.size + } + record = rec + return record + } + + override val length: Int + get() = cce + 7 + + override val value: Any? + get() { + try { + val dub: DoubleArray? + try { + dub = PtgCalculator.getDoubleValueArray(components!!) + } catch (e: CalculationException) { + return null + } + + var result = 0.0 + for (i in dub!!.indices) { + result += dub[i] + } + return result + } catch (e: FunctionNotSupportedException) { + Logger.logWarn("Function Unsupported error in PtgMemFunction: $e") + return null + } + + } + + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() + } + + fun getnTokens(): Int { + return cce + } // KSC: + + /** + * sets the bytes to describe the subexpression (set of ptgs) + * and parses the subexpression + * + * @param b + */ + fun setSubExpression(b: ByteArray) { + var retbytes = ByteArray(7) + System.arraycopy(record, 0, retbytes, 0, record.size) + retbytes = ByteTools.append(b, retbytes) + record = retbytes + this.populateVals() + // TODO: + } + + /** + * sets the PtgExtraMem structure which is appended to the end of the function array + * + * @param b + */ + fun setPostExpression(b: ByteArray, expressionLen: Int) { + val len = b.size - expressionLen + postExp = ByteArray(len) + System.arraycopy(b, expressionLen, postExp!!, 0, len) + /* + * parsing PtgExtraMem - not really necessary + * + if (b.length > expressionLen+2) { + int z= expressionLen; + int count= ByteTools.readShort(b[z++], b[z++]); + for (int i= 0; i < count && z+7 < b.length; i++) { + int[] rc= new int[4]; + rc[0]= ByteTools.readShort(b[z++], b[z++]); // rw first + rc[2]= ByteTools.readShort(b[z++], b[z++]); // rw last + rc[1]= ByteTools.readShort(b[z++], b[z++]);// col first + rc[3]= ByteTools.readShort(b[z++], b[z++]); // col last + ExcelTools.formatRangeRowCol(rc); + } + }*/ + } + + internal open fun populateVals() { + // 1st byte = ID, next 4 are ignored + // cce= size of following sub-expressions + cce = ByteTools.readShort(record[5].toInt(), record[6].toInt()).toInt() + // this is not really correct! + if (record.size > 7) { + val subexp: ByteArray + subexp = ByteArray(cce) + System.arraycopy(record, 7, subexp, 0, cce) + subexpression = ExpressionParser.parseExpression(subexp, this.parentRec) + // subexpression stack in form of: REFERENCE, REFERENCE, OP [,REFERENCE, OP] ... + // op can be one of: PtgUnion [,] PtgIsect [ ] or PtgRange [:] + // calculate subexpression to obtain ptgs + try { + val o = FormulaCalculator.calculateFormula(this.subexpression!!) + val components = ArrayList() + if (o != null && o is Array) { + this.components = o + for (i in this.components!!.indices) { + if (!refsheets.contains((this.components!![i] as PtgRef).sheetName)) + refsheets.add((this.components!![i] as PtgRef).sheetName) + if (this.components!![i] is PtgArea) { + val p = this.components!![i].components + for (j in p.indices) + components.add(p[j]) + } else + components.add(this.components!![i]) + } + } else { // often a single reference surrounded by parens + for (i in subexpression!!.indices) { + try { + val pr = subexpression!![i] as PtgRef + if (!refsheets.contains(pr.sheetName)) + refsheets.add(pr.sheetName) + if (pr is PtgArea) { + val pa = pr.components + for (j in pa!!.indices) + components.add(pa[j]) + } else + components.add(pr) + } catch (e: Exception) { + } + + } + } + this.components = arrayOfNulls(components.size) + components.toTypedArray() + } catch (e: Exception) { + Logger.logErr("PtgMemArea init: $e") + } + + //int z= subexpression.size(); + // to get # of references (PtgRefs) = stack size/2 + 1 + } + } + + /** + * traverse through expression to retrieve set of ranges + * either discontiguous union (,), intersected ( ) or regular range (:) + */ + override fun toString(): String { + return FormulaParser.getExpressionString(subexpression!!).substring(1) // avoid "=" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -6869393084367355874L + } + + +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaA.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaA.kt similarity index 51% rename from src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaA.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaA.kt index 2ac23c2..4ae7a4f 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaA.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaA.kt @@ -20,17 +20,19 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.OpenXLS.ExcelTools; -import io.starter.toolkit.ByteTools; +import io.starter.OpenXLS.ExcelTools +import io.starter.toolkit.ByteTools /** * PtgMemArea is an optimization of referenced areas. Sweet! - *

                      + * + * * Like most optimizations it really sucks. It is also one of the few Ptg's that * has a variable length. - *

                      + * + * * Format of length section *

                        * Offset      Name        Size    Contents
                      @@ -51,55 +53,51 @@
                        * 2           rwLast     2       The Last row of the reference
                        * 4           ColFirst    1       (see following table)
                        * 6           ColLast    1       (see following table)
                      - * 
                      + * * * @see Ptg + * * @see Formula */ -public class PtgMemAreaA extends PtgMemArea { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5528547215693511069L; - int cce = 0; - int cref = 0; - MemArea[] areas; +open class PtgMemAreaA : PtgMemArea() { + internal var cce = 0 + internal var cref = 0 + internal var areas: Array - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - } + override val length: Int + get() = -1 - void populateVals() { - cce = ByteTools.readInt(record[6], record[5]); - cref = ByteTools.readInt(record[8], record[7]); - areas = new MemArea[cref]; - int holder = 9; - for (int i = 0; i < cref; i++) { - byte[] arr = new byte[6]; - System.arraycopy(record, holder, arr, 0, 6); - areas[i].init(arr); - holder += 6; + /* + * return a string representation of all of the ranges, seperated by comma. + */ + override val value: Any? + get() { + var res = "" + for (i in areas.indices) { + res += areas[i].string + if (i != areas.size - 1) res += "," + } + return res } + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() } - public int getLength() { - return -1; - } - - /* - * return a string representation of all of the ranges, seperated by comma. - */ - public Object getValue() { - String res = ""; - for (int i = 0; i < areas.length; i++) { - res += areas[i].getString(); - if (i != (areas.length - 1)) res += ","; + internal override fun populateVals() { + cce = ByteTools.readInt(record[6].toInt(), record[5].toInt()) + cref = ByteTools.readInt(record[8].toInt(), record[7].toInt()) + areas = arrayOfNulls(cref) + var holder = 9 + for (i in 0 until cref) { + val arr = ByteArray(6) + System.arraycopy(record, holder, arr, 0, 6) + areas[i].init(arr) + holder += 6 } - Object o = res; - return o; + } /* @@ -107,33 +105,41 @@ public Object getValue() { * reference to the cells, just the description! * */ - private class MemArea { - int rwFirst; - int rwLast; - int colFirst; - int colLast; - - void init(byte[] b) { - rwFirst = ByteTools.readInt(b[0], b[1]); - rwLast = ByteTools.readInt(b[2], b[3]); - colFirst = (int) b[4]; - colLast = (int) b[5]; - } + private inner class MemArea { + internal var rwFirst: Int = 0 + internal var rwLast: Int = 0 + internal var colFirst: Int = 0 + internal var colLast: Int = 0 /* * returns a string representation of the area, or cell if only one. */ - String getString() { - if (rwFirst == rwLast && colFirst == colLast) { - // it is a single cell amoeba - String retstr = ExcelTools.getAlphaVal(colLast); - retstr = retstr + (rwLast + 1); - return retstr; + internal// it is a single cell amoeba + val string: String + get() { + if (rwFirst == rwLast && colFirst == colLast) { + var retstr = ExcelTools.getAlphaVal(colLast) + retstr = retstr + (rwLast + 1) + return retstr + } + val arr = intArrayOf(rwFirst, colFirst, rwLast, colLast) + return ExcelTools.formatRangeRowCol(arr) } - int[] arr = {rwFirst, colFirst, rwLast, colLast}; - return ExcelTools.formatRangeRowCol(arr); + + internal fun init(b: ByteArray) { + rwFirst = ByteTools.readInt(b[0].toInt(), b[1].toInt()) + rwLast = ByteTools.readInt(b[2].toInt(), b[3].toInt()) + colFirst = b[4].toInt() + colLast = b[5].toInt() } } + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5528547215693511069L + } + } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaN.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaN.kt similarity index 71% rename from src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaN.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaN.kt index cb8ed93..500ec30 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaN.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaN.kt @@ -20,34 +20,35 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /* Currently just a placeholder class - + * @see Ptg * @see Formula - + */ -public class PtgMemAreaN extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7497529044241909689L; +class PtgMemAreaN : GenericPtg(), Ptg { /** * return the human-readable String representation of */ - public String getString() { - return "MEMAREAN"; - } + override val string: String + get() = "MEMAREAN" + + override val length: Int + get() = Ptg.PTG_MEM_AREA_N_LENGTH - public int getLength() { - return PTG_MEM_AREA_N_LENGTH; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 7497529044241909689L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.kt similarity index 75% rename from src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.kt index f85a56f..e11b6ed 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemAreaNV.kt @@ -20,34 +20,37 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /* Currently just a placeholder class - + * @see Ptg * @see Formula - + */ -public class PtgMemAreaNV extends PtgMemAreaA implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7912318247202256986L; +class PtgMemAreaNV : PtgMemAreaA(), Ptg { /** * return the human-readable String representation of - *

                      + * + * * public String getString(){ * return "MEMAREANV"; * } */ - public int getLength() { - return PTG_MEM_AREA_NV_LENGTH; + override val length: Int + get() = Ptg.PTG_MEM_AREA_NV_LENGTH + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -7912318247202256986L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.kt similarity index 72% rename from src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.kt index f55444a..b8e48b7 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemErr.kt @@ -20,32 +20,33 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /* Currently just a placeholder class - + * @see Ptg * @see Formula - + */ -public class PtgMemErr extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8754800881832127368L; +class PtgMemErr : GenericPtg(), Ptg { /** * return the human-readable String representation of */ - public String getString() { - return "MEMERR"; - } + override val string: String + get() = "MEMERR" + + override val length: Int + get() = Ptg.PTG_MEMERR_LENGTH - public int getLength() { - return PTG_MEMERR_LENGTH; + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -8754800881832127368L } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.java deleted file mode 100644 index f652122..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.java +++ /dev/null @@ -1,649 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.Boundsheet; -import io.starter.formats.XLS.ExpressionParser; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.FastAddVector; -import io.starter.toolkit.Logger; - -import java.util.ArrayList; -import java.util.Stack; - -/** - * PtgMemFunc refers to a reference subexpression that doesn't evaluate - * to a constant reference. This is still somewhat unclear to me how it functions, - * or why it exists for that matter. - *

                      - * This token encapsulates a reference subexpression that results in a non-constant cell address, - * cell range address, or cell range list. For - *

                      - * A little update on it. Apparently this is used in situations where a record only contains one ptg, but needs to refer to a whole stack of them. - * An example is in the Name record. For a built in name that has both row & col repeat regions, the name expression is a ptgmemfunc, but contains - * 2 ptgArea3d's. - * Also used in Ai's (Chart Series Range Refs) when non-contiguous range refs are required - *

                      - * PtgMemFunc basically represents a complex range and is used where only one PtgRef-type ptg is expected - *

                      - * NOTE: that this represents a NON-CONSTANT expression while PtgMemArea represents a CONSTANT expression - * - *

                      - * 	OFFSET		NAME		SIZE		CONTENTS
                      - * -------------------------------------------------------
                      - * 	0			cce			2			The length of the reference subexpression
                      - */
                      -public class PtgMemFunc extends GenericPtg {
                      -
                      -    public static final long serialVersionUID = 666555444333222l;
                      -
                      -    Stack subexpression = null; //
                      -
                      -    Ptg[] ptgs = null;    // 20090905 KSC: can be PtgRef3d, PtgArea3d, PtgName  ...
                      -
                      -    public boolean getIsOperand() {
                      -        return true;
                      -    }
                      -
                      -    public boolean getIsReference() {
                      -        return true;
                      -    }    // 20100202 KSC
                      -
                      -    int cce;
                      -    byte[] subexp;
                      -
                      -    public void init(byte[] b) {
                      -        ptgId = b[0];
                      -        record = b;
                      -        try {
                      -            this.populateVals();
                      -        } catch (Exception e) {
                      -            Logger.logErr("PtgMemFunc init: " + e.toString());
                      -        }
                      -    }
                      -
                      -    ArrayList refsheets = new ArrayList();
                      -
                      -    void populateVals() throws Exception {
                      -        cce = ByteTools.readShort(record[1], record[2]);
                      -        subexp = new byte[cce];
                      -        System.arraycopy(record, 3, subexp, 0, cce);
                      -        // subexpression stack in form of:  REFERENCE, REFERENCE, OP [,REFERENCE, OP] ...
                      -        // op can be one of:  PtgUnion [,] PtgIsect [ ] or PtgRange [:]
                      -        subexpression = ExpressionParser.parseExpression(subexp, this.parent_rec);
                      -        // try parsing/calculating on-demand rather than upon init
                      -        //parseSubexpression(); 
                      -    }
                      -
                      -    /**
                      -     * update the record internally for ptgmemfunc
                      -     */
                      -    public byte[] getRecord() {
                      -        int len = 0;
                      -        for (int i = 0; i < subexpression.size(); i++) {
                      -            Ptg p = (Ptg) subexpression.get(i);
                      -            len += p.getRecord().length;
                      -        }
                      -        byte[] b = new byte[len + 3];
                      -        byte[] leng = ByteTools.shortToLEBytes((short) len);
                      -        b[0] = 0x29;
                      -        b[1] = leng[0];
                      -        b[2] = leng[1];
                      -        int offset = 3;
                      -        for (int i = 0; i < subexpression.size(); i++) {
                      -            Ptg p = (Ptg) subexpression.get(i);
                      -            System.arraycopy(p.getRecord(), 0, b, offset, p.getRecord().length);
                      -            offset += p.getRecord().length;
                      -        }
                      -        record = b;
                      -        return record;
                      -    }
                      -
                      -    public int getLength() {
                      -        return cce + 3;
                      -    }
                      -
                      -    int calc_id = 1;
                      -
                      -    public Object getValue() {
                      -        if (ptgs == null)
                      -            parseSubexpression();    // not parsed yet
                      -        try {
                      -            double[] dub;
                      -            try {
                      -                dub = PtgCalculator.getDoubleValueArray(ptgs);
                      -            } catch (CalculationException e) {
                      -                return null;
                      -            }
                      -            double result = 0.0;
                      -            for (int i = 0; i < dub.length; i++) {
                      -                result += dub[i];
                      -            }
                      -            return new Double(result);
                      -        } catch (FunctionNotSupportedException e) {
                      -            Logger.logWarn("Function Unsupported error in PtgMemFunction: " + e);
                      -            return null;
                      -        }
                      -    }
                      -
                      -    PtgRef[] colrefs = null;
                      -
                      -    /**
                      -     * return the ptg components for a certain column within a ptgArea()
                      -     *
                      -     * @param colNum
                      -     * @return all Ptg's within colNum
                      -     */
                      -    public Ptg[] getColComponents(int colNum) {
                      -        if (colrefs != null) // cache
                      -            return colrefs;
                      -        FastAddVector v = new FastAddVector();
                      -        Ptg[] allComponents = this.getComponents();
                      -        for (int i = 0; i < allComponents.length; i++) {
                      -            PtgRef p = (PtgRef) allComponents[i];
                      -//			 TODO: check rc sanity here
                      -            int[] x = p.getIntLocation();
                      -            if (x[1] == colNum) v.add(p);
                      -        }
                      -        colrefs = new PtgRef[v.size()];
                      -        v.toArray(colrefs);
                      -        return colrefs;
                      -    }
                      -
                      -    PtgRef[] comps = null;
                      -
                      -
                      -    /**
                      -     * parses subexpression into ptgs array + traps referenced sheets
                      -     */
                      -    private void parseSubexpression() {
                      -        // calculate subexpression to obtain ptgs
                      -        Object o = FormulaCalculator.calculateFormula(this.subexpression);
                      -        ArrayList components = new ArrayList();
                      -        if (o != null && o instanceof Ptg[]) {
                      -            // Firstly: take subexpression and remove reference-tracked elements; calcualted elements are ref-tracked below
                      -            for (int i = 0; i < subexpression.size(); i++) {
                      -                try {
                      -                    ((PtgRef) subexpression.get(i)).removeFromRefTracker();
                      -                } catch (Exception e) {
                      -                }
                      -            }
                      -            ptgs = (Ptg[]) o;
                      -            for (int i = 0; i < ptgs.length; i++) {
                      -                try {
                      -                    if (!refsheets.contains(((PtgRef) ptgs[i]).getSheetName()))
                      -                        refsheets.add(((PtgRef) ptgs[i]).getSheetName());
                      -                    ((PtgRef) ptgs[i]).addToRefTracker();
                      -                    if (ptgs[i] instanceof PtgArea & !(ptgs[i] instanceof PtgAreaErr3d)) {
                      -                        Ptg[] p = ptgs[i].getComponents();
                      -                        for (int j = 0; j < p.length; j++)
                      -                            components.add(p[j]);
                      -                    } else
                      -                        components.add(ptgs[i]);
                      -                } catch (Exception e) {
                      -                    Logger.logErr("PtgMemFunc init: " + e.toString());
                      -                }
                      -            }
                      -        } else {    // often a single reference surrounded by parens
                      -            for (int i = 0; i < subexpression.size(); i++) {
                      -                try {
                      -                    PtgRef pr = (PtgRef) subexpression.get(i);
                      -                    if (!refsheets.contains(pr.getSheetName()))
                      -                        refsheets.add(pr.getSheetName());
                      -                    if (pr instanceof PtgArea) {
                      -                        Ptg[] pa = pr.getComponents();
                      -                        for (int j = 0; j < pa.length; j++)
                      -                            components.add(pa[j]);
                      -                    } else
                      -                        components.add(pr);
                      -                } catch (Exception e) {
                      -                }
                      -            }
                      -        }
                      -        ptgs = new Ptg[components.size()];
                      -        components.toArray(ptgs);
                      -    }
                      -
                      -    /**
                      -     * Returns all of the cells of this range as PtgRef's.
                      -     * This includes empty cells, values, formulas, etc.
                      -     * Note the setting of parent-rec requires finding the cell
                      -     * the PtgRef refer's to.  If that is null then the PtgRef
                      -     * will exist, just with a null value.  This could cause issues when
                      -     * programatically populating cells.
                      -     */
                      -    public Ptg[] getComponents() {
                      -        if (ptgs == null)
                      -            parseSubexpression();    // not parsed yet
                      -        return ptgs;
                      -    }
                      -
                      -    /**
                      -     * @return Returns the firstPtg.
                      -     */
                      -    public Ptg getFirstloc() {
                      -        if (ptgs == null)
                      -            parseSubexpression();    // not parsed yet
                      -        if (ptgs != null)
                      -            return ptgs[0];
                      -        return null;
                      -    }
                      -
                      -    /**
                      -     * Ptgs upkeep their mapping in reference tracker, however, some ptgs
                      -     * are components of other Ptgs, such as individual ptg cells in a PtgArea.  These
                      -     * should not be stored in the RT.
                      -     */
                      -    private boolean useReferenceTracker = true;
                      -
                      -    public void setUseReferenceTracker(boolean b) {
                      -        useReferenceTracker = b;
                      -    }
                      -
                      -    public boolean getUseReferenceTracker() {
                      -        return useReferenceTracker;
                      -    }
                      -
                      -    /**
                      -     * @return Returns the lastPtg.
                      -     */
                      -    public Stack getSubExpression() {
                      -        return subexpression;
                      -    }
                      -
                      -    /**
                      -     * given a complex range, parse and set this PtgMemFunc's associated ptgs
                      -     *
                      -     * @param String complexrange  String representing a complex range
                      -     */
                      -    // some possibilities
                      -    // a:b,c:d
                      -    // a:b c:d,e:f g:h
                      -    // a, b, c, d
                      -    // Q34:Q36:Q35:Q37 Q36:Q38
                      -    // name1:name2:name3:name4
                      -    /*
                      -     * results of parsing complex ranges from Excel:
                      -     *  @V@27, ), @V$27, ), " "		=(($V$27) ($V$27))
                      -		Q27:Q29, Q28:Q30, ","		'=SUM((Q27:Q29,Q28:Q30))
                      -		Q27:Q29, Q28:Q30, " "		'=SUM(Q27:Q29 Q28:Q30)
                      -		Q27:Q29, Q28:Q30, ":"		'=SUM(Q27:Q29:Q28:Q30)
                      -		Q34:Q36, Q35:Q37, ":", Q36:Q38, " ", Q37:Q39, ","	'=SUM((Q34:Q36:Q35:Q37 Q36:Q38,Q37:Q39))
                      -		Q34:Q36, Q35:Q37, ":", ), Q36:Q38, " ", Q37:Q39, ","	'=SUM(((Q34:Q36:Q35:Q37) Q36:Q38,Q37:Q39))
                      -		Q34:Q36, Q35:Q37, Q36:Q38, " ", ), ":", Q37:Q39, ","	'=SUM((Q34:Q36:(Q35:Q37 Q36:Q38),Q37:Q39))
                      -		Q34:Q36, Q35:Q37, ":", Q36:Q38, Q37:Q39, ",", ), " "	'=SUM((Q34:Q36:Q35:Q37 (Q36:Q38,Q37:Q39)))
                      -		Q34:Q36, Q35:Q37, ":", Q36:Q38, " ", ), Q37:Q39, ","	'=SUM(((Q34:Q36:Q35:Q37 Q36:Q38),Q37:Q39))
                      -		Q34:Q36, Q35:Q37, Q36:Q38, " ", Q37:Q39, ",", ), ":"	'=SUM((Q34:Q36:(Q35:Q37 Q36:Q38,Q37:Q39)))
                      -		Q40:Q42, Q41:Q43, Q42:Q44, Q43:Q45, ":", " ", ","		'=SUM((Q40:Q42,Q41:Q43 Q42:Q44:Q43:Q45))
                      -		Q40:Q42, Q41:Q43, ",", ), Q42:Q44, Q43:Q45, ":", " "	'=SUM(((Q40:Q42,Q41:Q43) Q42:Q44:Q43:Q45))
                      -		Q40:Q42, Q41:Q43, " ", ), Q43:Q44, ":", Q45, ":", ","	'=SUM((Q40:Q42,(Q41:Q43 Q42):Q43:Q44:Q45))
                      -		Q40:Q42, Q41:Q43, Q42:Q44, Q43:Q45, ":", ), " ", ","	'=SUM((Q40:Q42,Q41:Q43 (Q42:Q44:Q43:Q45)))
                      -		Q40:Q42, Q41:Q43, Q42:Q44, " ", ",", ), Q43:Q45, ":"	'=SUM(((Q40:Q42,Q41:Q43 Q42:Q44):Q43:Q45))
                      -		Q40:Q42, Q41:Q43, Q42:Q44, Q43:Q45, ":", " ", ), ","	'=SUM((Q40:Q42,(Q41:Q43 Q42:Q44:Q43:Q45)))
                      -		Q46, Q46, " "											'=-Q46 Q46
                      -		Q47, ), Q47, ":"										'=-(Q47):Q47
                      -		Q48, Q48, ), ":"										'=-Q48:(Q48)
                      -     */
                      -    public void setLocation(String complexrange) {
                      -        byte[] newData = new byte[3];    // 1st 3 bytes= id + cce (length of following data)
                      -        String sheetName = "";
                      -        WorkBook bk = null;
                      -        ArrayList sheets = new ArrayList();
                      -        try {
                      -            bk = this.getParentRec().getWorkBook();
                      -            for (int i = 0; i < bk.getSheetVect().size(); i++) {
                      -                sheets.add(bk.getWorkSheetByNumber(i).getSheetName());
                      -            }
                      -            sheetName = this.getParentRec().getSheet().getSheetName() + "!";
                      -        } catch (Exception e) {
                      -            //?
                      -        }
                      -
                      -
                      -        if (complexrange.startsWith("(") && complexrange.endsWith(")"))    // memfuncs are assumed to be wrapped in parens, no need to specify
                      -            complexrange = complexrange.substring(0, complexrange.length() - 1);
                      -
                      -        // KSC: TESTING: revert settng subsexpression here for now as tests fail ((;
                      -        //this.subexpression= new Stack();
                      -        Stack refs = parseFmla(complexrange);
                      -        try {
                      -            // structure:
                      -            // ref, ref, op, [op?] [, ref, op ...]
                      -            String ref;
                      -            while (refs.size() != 0) {
                      -                while (refs.size() > 0) {
                      -                    if (refs.get(0) instanceof Character) { // it's an operator
                      -                        Character cOp = (Character) refs.get(0);
                      -                        if (cOp.charValue() == ',') {
                      -                            PtgUnion pu = new PtgUnion();
                      -                            cce += pu.getRecord().length;
                      -                            newData = ByteTools.append(pu.getRecord(), newData);
                      -                            //this.subexpression.add(pu);
                      -                        } else if (cOp.charValue() == ' ') {
                      -                            PtgIsect pi = new PtgIsect();
                      -                            cce += pi.getRecord().length;
                      -                            newData = ByteTools.append(pi.getRecord(), newData);
                      -                            //this.subexpression.add(pi);
                      -                        } else if (cOp.charValue() == ':') {
                      -                            PtgRange pr = new PtgRange();
                      -                            cce += pr.getRecord().length;
                      -                            newData = ByteTools.append(pr.getRecord(), newData);
                      -                            //this.subexpression.add(pr);
                      -                        } else if (cOp.charValue() == ')') {
                      -                            PtgParen pp = new PtgParen();
                      -                            cce += pp.getRecord().length;
                      -                            newData = ByteTools.append(pp.getRecord(), newData);
                      -                            //this.subexpression.add(pp);
                      -                        }
                      -                    } else {
                      -                        Object o = refs.get(0);
                      -                        if (o instanceof Ptg) {    // in the rare case of PtgMemFuncs which contain embedded formulas, Ptgs are already created (see parseFmla)
                      -                            cce += ((Ptg) o).getRecord().length;
                      -                            newData = ByteTools.append(((Ptg) o).getRecord(), newData);
                      -                            //this.subexpression.add(o);
                      -                        } else {
                      -                            ref = (String) o;
                      -                            boolean isName = (this.getParentRec().getWorkBook().getName(ref) != null);
                      -                            Ptg p = null;
                      -                            if (isName) {
                      -                                p = new PtgName();
                      -                                p.setParentRec(this.parent_rec);
                      -                                ((PtgName) p).setName(ref);
                      -                                cce += p.getRecord().length;
                      -                                newData = ByteTools.append(p.getRecord(), newData);
                      -                            } else if (ref.indexOf(":") > 0) { // TODO: handle in quote!!!
                      -                                if (ref.indexOf("!") == -1) ref = sheetName + ref;
                      -                                p = new PtgArea3d();
                      -                                p.setParentRec(this.parent_rec);
                      -                                p.setLocation(ref);
                      -                                cce += p.getRecord().length;
                      -                                newData = ByteTools.append(p.getRecord(), newData);
                      -                            } else {
                      -                                if (ref.indexOf("!") == -1) ref = sheetName + ref;
                      -                                p = new PtgRef3d();
                      -                                p.setParentRec(this.parent_rec);
                      -                                p.setLocation(ref);
                      -                                ((PtgRef3d) p).setPtgType(PtgRef.REFERENCE);//important for charting/ptgmemfuncs in series/categories - will error on open otherwise
                      -                                cce += p.getRecord().length;
                      -                                newData = ByteTools.append(p.getRecord(), newData);
                      -                            }
                      -                            //this.subexpression.add(p);
                      -                        }
                      -                    }
                      -                    refs.remove(0);
                      -                }
                      -            }
                      -        } catch (Exception e) {
                      -            throw new IllegalArgumentException("PtgMemFunc Error Parsing Location " + complexrange + ":" + e.toString());
                      -        }
                      -        byte[] ix = ByteTools.shortToLEBytes((short) cce);
                      -        System.arraycopy(ix, 0, newData, 1, 2);
                      -        newData[0] = 41;    // ptgId
                      -        record = newData;
                      -        try {
                      -            // KSC: don't re-parse as already have all the ptgs ... also, rw/col bytes for Excel2007 exceed maximums so conversion can't be 100%
                      -            // KSC: TESTING: revert for now tests fail ((;
                      -            populateVals();
                      -/*			cce = ByteTools.readShort(record[1], record[2]);
                      -			subexp = new byte[cce];
                      -	        System.arraycopy(record, 3, subexp, 0, cce);/**/
                      -        } catch (Exception e) {
                      -            Logger.logErr("PtgMemFunc setLocation failed for: " + complexrange + " " + e.toString());
                      -        }
                      -
                      -    }
                      -
                      -    /**
                      -     * takes a range string which may contain operators:
                      -     * union [,] isect [ ] range [:} or paren
                      -     * plus range elements and/or named range
                      -     * parse and order each element correctly
                      -     * may be called recurrsively
                      -     * NOTE: may also be VERY complex, of type OFFSET(x,y,0):OFFSET(z,w,0)
                      -     * ALSO INDEX and INDIRECT ...
                      -     *
                      -     * @param complexrange
                      -     * @return ordered stack containing parsed range elements
                      -     */
                      -    private Stack parseFmla(String complexrange) {
                      -        Stack ops = new Stack();
                      -        int lastOp = 0;
                      -        boolean finishRange = false;
                      -        Stack refs = new Stack();
                      -        String ref = "";
                      -        boolean inquote = false;
                      -        String range = null;    // holds partial range
                      -        for (int i = 0; i < complexrange.length(); i++) {
                      -            char c = complexrange.charAt(i);
                      -            if (c == '\'') {
                      -                inquote = !inquote;
                      -                ref += c;
                      -            } else if (!inquote) {
                      -                if (c == ',' || c == ' ' || c == ')' || (c == ':' && finishRange)) {    // it's an operand
                      -                    if (c == ' ' && lastOp == ' ')
                      -                        continue;    // skip 2nd space op (Isect)
                      -                    if (finishRange) { // add ref to rest of range
                      -                        refs.push(range + ref);
                      -                        if (!refs.isEmpty() && !ops.isEmpty())
                      -                            refs = handleOpearatorPreference(refs, ops);
                      -                        while (!ops.isEmpty())
                      -                            refs.push(ops.pop());
                      -                        range = null;
                      -                        ref = "";
                      -                        finishRange = false;
                      -                        ops.push(new Character(c));
                      -                    } else if (refs.isEmpty()) {    // no operands yet - put in 1st
                      -                        if (!ref.equals(""))
                      -                            refs.push(ref);
                      -                        ref = "";
                      -                        ops.push(new Character(c));
                      -                    } else {    // have all we need to process
                      -                        if (!ref.equals(""))
                      -                            refs.push(ref);
                      -                        while (!ops.isEmpty())
                      -                            refs.push(ops.pop());
                      -                        ref = "";    // handle case of two spaces ... unfortunately
                      -                        ops.push(new Character(c));
                      -                    }
                      -                    lastOp = c;
                      -                } else if (c == ':') {
                      -                    if (this.getParentRec().getWorkBook().getName(ref) == null) { // it's a regular range
                      -                        // check if the ref is a sheet name in a 3d ref
                      -                        if (!ref.equals("")) {
                      -                            range = ref + c;
                      -                            finishRange = true;
                      -                        } else { // happens in cases such as (opopop):ref:ref
                      -                            ops.push(new Character(c));
                      -                        }
                      -                        ref = "";
                      -                    } else {    // it's a named range
                      -                        refs.push(ref);
                      -                        ref = "";
                      -                        ops.push(new Character(c));
                      -                        finishRange = false;    // it's not a regular range
                      -                    }
                      -                } else if (c == '(') {
                      -                    int endparen = FormulaParser.getMatchOperator(complexrange, i, '(', ')');
                      -                    if (endparen == -1)
                      -                        endparen = complexrange.length() - 1;
                      -                    else if (!ref.equals("")) {
                      -                        // rare case of a PtgMemFunc containing a formula:
                      -                        String f = ref + "(" + complexrange.substring(i + 1, endparen + 1);
                      -                        ref = "";
                      -                        refs = mergeStacks(refs, FormulaParser.getPtgsFromFormulaString(this.getParentRec(), f, true));
                      -                        i = endparen;
                      -                        if (!ops.isEmpty())
                      -                            refs = handleOpearatorPreference(refs, ops);
                      -                        while (!ops.isEmpty())
                      -                            refs.push(ops.pop());
                      -                        continue;
                      -                    }
                      -                    refs = mergeStacks(refs, parseFmla(complexrange.substring(i + 1, endparen + 1)));
                      -                    i = endparen;
                      -                    if (!ops.isEmpty())
                      -                        refs = handleOpearatorPreference(refs, ops);
                      -                    while (!ops.isEmpty())
                      -                        refs.push(ops.pop());
                      -                } else
                      -                    ref += c;
                      -            } else
                      -                ref += c;
                      -        }
                      -        // get any remaining
                      -        if (finishRange) { // add ref to rest of range
                      -            // range op has more precedence than others ...
                      -            if (!ops.isEmpty() && ((Character) ops.peek()).charValue() == ':' && !refs.isEmpty() &&
                      -                    refs.peek() instanceof Character) {
                      -                while (refs.peek() instanceof Character) {
                      -                    if (((Character) refs.peek()).charValue() != ':')
                      -                        ops.add(0, refs.pop());
                      -                    else
                      -                        break;
                      -                }
                      -            }
                      -            if (!ref.equals(""))
                      -                refs.push(range + ref);
                      -            else {
                      -                refs.push(range.substring(0, range.length() - 1));
                      -                ops.push(':');
                      -            }
                      -        } else {
                      -            if (!ref.equals(""))
                      -                refs.push(ref);
                      -        }
                      -        while (!ops.isEmpty())
                      -            refs.push(ops.pop());
                      -        return refs;
                      -    }
                      -
                      -
                      -    /**
                      -     * handle precedence of complex range operators:  : before , before ' '
                      -     *
                      -     * @param sourceStack
                      -     * @param destStack
                      -     */
                      -    private static Stack handleOpearatorPreference(Stack refs, Stack ops) {
                      -        char lastOp = ((Character) ops.pop()).charValue();
                      -        if (refs.peek() instanceof Character) {
                      -            char curOp = ((Character) refs.pop()).charValue();
                      -            int group1 = rankPrecedence(lastOp);
                      -            int group2 = rankPrecedence(curOp);
                      -            if (group2 >= group1) {
                      -                ops.push(new Character(lastOp));
                      -                refs.push(new Character(curOp));
                      -            } else {
                      -                ops.push(new Character(curOp));
                      -                refs.push(new Character(lastOp));
                      -            }
                      -
                      -        } else
                      -            ops.push(new Character(lastOp));
                      -        return refs;
                      -    }
                      -
                      -    /**
                      -     * rank a Ptg Operator's precedence (lower
                      -     *
                      -     * @param curOp
                      -     * @return
                      -     */
                      -    static int rankPrecedence(char curOp) {
                      -        if (curOp == 0) return -1;
                      -        if (curOp == ')')
                      -            return 6;
                      -        if (curOp == ':')
                      -            return 5;
                      -        if (curOp == ',' || curOp == ' ')    // same level????
                      -            return 4;
                      -        return 0;    // ' '
                      -    }
                      -
                      -
                      -    /**
                      -     * when parenthesed sub-functions
                      -     *
                      -     * @param first
                      -     * @param last
                      -     * @return
                      -     */
                      -    private Stack mergeStacks(Stack first, Stack last) {
                      -        first.addAll(last);
                      -        return first;
                      -    }
                      -
                      -    /**
                      -     * traverse through expression to retrieve set of ranges
                      -     * either discontiguous union (,), intersected ( ) or regular range (:)
                      -     */
                      -    public String toString() {
                      -        return FormulaParser.getExpressionString(subexpression).substring(1);    // avoid "="
                      -    }
                      -
                      -    /**
                      -     * return the boundsheet associated with this complex range
                      -     * 
                      NOTE: since complex ranges may contain more than one sheet, this is incomplete for those instanaces - * - * @param b - * @return - */ - public Boundsheet[] getSheets(WorkBook b) { - if (ptgs == null) - parseSubexpression(); - if (this.refsheets != null || this.refsheets.size() != 0) { - try { - Boundsheet[] sheets = new Boundsheet[this.refsheets.size()]; - for (int i = 0; i < sheets.length; i++) - sheets[i] = b.getWorkSheetByName(refsheets.get(i)); - return sheets; - } catch (Exception e) { - // TODO: report error? - } - } - return null; - - } - - public void close() { - if (ptgs != null) { - for (int i = 0; i < ptgs.length; i++) { - if (ptgs[i] instanceof PtgRef) - ptgs[i].close(); - else - ptgs[i].close(); - } - } - ptgs = null; - super.close(); - } -/* protected void finalize() { - this.close(); - }*/ -} - - - - - diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.kt new file mode 100644 index 0000000..3a720bf --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMemFunc.kt @@ -0,0 +1,754 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.Boundsheet +import io.starter.formats.XLS.ExpressionParser +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.ByteTools +import io.starter.toolkit.FastAddVector +import io.starter.toolkit.Logger + +import java.util.ArrayList +import java.util.Stack + +/** + * PtgMemFunc refers to a reference subexpression that doesn't evaluate + * to a constant reference. This is still somewhat unclear to me how it functions, + * or why it exists for that matter. + * + * + * This token encapsulates a reference subexpression that results in a non-constant cell address, + * cell range address, or cell range list. For + * + * + * A little update on it. Apparently this is used in situations where a record only contains one ptg, but needs to refer to a whole stack of them. + * An example is in the Name record. For a built in name that has both row & col repeat regions, the name expression is a ptgmemfunc, but contains + * 2 ptgArea3d's. + * Also used in Ai's (Chart Series Range Refs) when non-contiguous range refs are required + * + * + * PtgMemFunc basically represents a complex range and is used where only one PtgRef-type ptg is expected + * + * + * NOTE: that this represents a NON-CONSTANT expression while PtgMemArea represents a CONSTANT expression + * + *
                      + * OFFSET		NAME		SIZE		CONTENTS
                      + * -------------------------------------------------------
                      + * 0			cce			2			The length of the reference subexpression
                      +
                      */ +class PtgMemFunc : GenericPtg() { + + /** + * @return Returns the lastPtg. + */ + var subExpression: Stack<*>? = null + internal set // + + internal var ptgs: Array? = null // 20090905 KSC: can be PtgRef3d, PtgArea3d, PtgName ... + + override val isOperand: Boolean + get() = true + + override val isReference: Boolean + get() = true // 20100202 KSC + + internal var cce: Int = 0 + internal var subexp: ByteArray + + internal var refsheets: ArrayList? = ArrayList() + + /** + * update the record internally for ptgmemfunc + */ + override val record: ByteArray + get() { + var len = 0 + for (i in subExpression!!.indices) { + val p = subExpression!![i] as Ptg + len += p.record.size + } + val b = ByteArray(len + 3) + val leng = ByteTools.shortToLEBytes(len.toShort()) + b[0] = 0x29 + b[1] = leng[0] + b[2] = leng[1] + var offset = 3 + for (i in subExpression!!.indices) { + val p = subExpression!![i] as Ptg + System.arraycopy(p.record, 0, b, offset, p.record.size) + offset += p.record.size + } + record = b + return record + } + + override val length: Int + get() = cce + 3 + + internal var calc_id = 1 + + override// not parsed yet + val value: Any? + get() { + if (ptgs == null) + parseSubexpression() + try { + val dub: DoubleArray? + try { + dub = PtgCalculator.getDoubleValueArray(ptgs!!) + } catch (e: CalculationException) { + return null + } + + var result = 0.0 + for (i in dub!!.indices) { + result += dub[i] + } + return result + } catch (e: FunctionNotSupportedException) { + Logger.logWarn("Function Unsupported error in PtgMemFunction: $e") + return null + } + + } + + internal var colrefs: Array? = null + + internal var comps: Array? = null + + /** + * Returns all of the cells of this range as PtgRef's. + * This includes empty cells, values, formulas, etc. + * Note the setting of parent-rec requires finding the cell + * the PtgRef refer's to. If that is null then the PtgRef + * will exist, just with a null value. This could cause issues when + * programatically populating cells. + */ + override// not parsed yet + val components: Array? + get() { + if (ptgs == null) + parseSubexpression() + return ptgs + } + + /** + * @return Returns the firstPtg. + */ + // not parsed yet + val firstloc: Ptg? + get() { + if (ptgs == null) + parseSubexpression() + return if (ptgs != null) ptgs!![0] else null + } + + /** + * Ptgs upkeep their mapping in reference tracker, however, some ptgs + * are components of other Ptgs, such as individual ptg cells in a PtgArea. These + * should not be stored in the RT. + */ + var useReferenceTracker = true + + /** + * given a complex range, parse and set this PtgMemFunc's associated ptgs + * + * @param String complexrange String representing a complex range + */ + // some possibilities + // a:b,c:d + // a:b c:d,e:f g:h + // a, b, c, d + // Q34:Q36:Q35:Q37 Q36:Q38 + // name1:name2:name3:name4 + /* + * results of parsing complex ranges from Excel: + * @V@27, ), @V$27, ), " " =(($V$27) ($V$27)) + Q27:Q29, Q28:Q30, "," '=SUM((Q27:Q29,Q28:Q30)) + Q27:Q29, Q28:Q30, " " '=SUM(Q27:Q29 Q28:Q30) + Q27:Q29, Q28:Q30, ":" '=SUM(Q27:Q29:Q28:Q30) + Q34:Q36, Q35:Q37, ":", Q36:Q38, " ", Q37:Q39, "," '=SUM((Q34:Q36:Q35:Q37 Q36:Q38,Q37:Q39)) + Q34:Q36, Q35:Q37, ":", ), Q36:Q38, " ", Q37:Q39, "," '=SUM(((Q34:Q36:Q35:Q37) Q36:Q38,Q37:Q39)) + Q34:Q36, Q35:Q37, Q36:Q38, " ", ), ":", Q37:Q39, "," '=SUM((Q34:Q36:(Q35:Q37 Q36:Q38),Q37:Q39)) + Q34:Q36, Q35:Q37, ":", Q36:Q38, Q37:Q39, ",", ), " " '=SUM((Q34:Q36:Q35:Q37 (Q36:Q38,Q37:Q39))) + Q34:Q36, Q35:Q37, ":", Q36:Q38, " ", ), Q37:Q39, "," '=SUM(((Q34:Q36:Q35:Q37 Q36:Q38),Q37:Q39)) + Q34:Q36, Q35:Q37, Q36:Q38, " ", Q37:Q39, ",", ), ":" '=SUM((Q34:Q36:(Q35:Q37 Q36:Q38,Q37:Q39))) + Q40:Q42, Q41:Q43, Q42:Q44, Q43:Q45, ":", " ", "," '=SUM((Q40:Q42,Q41:Q43 Q42:Q44:Q43:Q45)) + Q40:Q42, Q41:Q43, ",", ), Q42:Q44, Q43:Q45, ":", " " '=SUM(((Q40:Q42,Q41:Q43) Q42:Q44:Q43:Q45)) + Q40:Q42, Q41:Q43, " ", ), Q43:Q44, ":", Q45, ":", "," '=SUM((Q40:Q42,(Q41:Q43 Q42):Q43:Q44:Q45)) + Q40:Q42, Q41:Q43, Q42:Q44, Q43:Q45, ":", ), " ", "," '=SUM((Q40:Q42,Q41:Q43 (Q42:Q44:Q43:Q45))) + Q40:Q42, Q41:Q43, Q42:Q44, " ", ",", ), Q43:Q45, ":" '=SUM(((Q40:Q42,Q41:Q43 Q42:Q44):Q43:Q45)) + Q40:Q42, Q41:Q43, Q42:Q44, Q43:Q45, ":", " ", ), "," '=SUM((Q40:Q42,(Q41:Q43 Q42:Q44:Q43:Q45))) + Q46, Q46, " " '=-Q46 Q46 + Q47, ), Q47, ":" '=-(Q47):Q47 + Q48, Q48, ), ":" '=-Q48:(Q48) + */ + override// 1st 3 bytes= id + cce (length of following data) +//? +// memfuncs are assumed to be wrapped in parens, no need to specify +// KSC: TESTING: revert settng subsexpression here for now as tests fail ((; +//this.subexpression= new Stack(); +// structure: +// ref, ref, op, [op?] [, ref, op ...] +// it's an operator +//this.subexpression.add(pu); +//this.subexpression.add(pi); +//this.subexpression.add(pr); +//this.subexpression.add(pp); +// in the rare case of PtgMemFuncs which contain embedded formulas, Ptgs are already created (see parseFmla) +//this.subexpression.add(o); +// TODO: handle in quote!!! +//important for charting/ptgmemfuncs in series/categories - will error on open otherwise +//this.subexpression.add(p); +// ptgId +// KSC: don't re-parse as already have all the ptgs ... also, rw/col bytes for Excel2007 exceed maximums so conversion can't be 100% +// KSC: TESTING: revert for now tests fail ((; +/* cce = ByteTools.readShort(record[1], record[2]); + subexp = new byte[cce]; + System.arraycopy(record, 3, subexp, 0, cce);/**/ var location:String +get + set(complexrange) { +var complexrange = complexrange +var newData = ByteArray(3) +var sheetName = "" +var bk:WorkBook? = null +val sheets = ArrayList() +try +{ +bk = this.parentRec!!.workBook +for (i in 0 until bk!!.sheetVect.size) +{ +sheets.add(bk!!.getWorkSheetByNumber(i).sheetName) +} +sheetName = this.parentRec!!.sheet!!.sheetName + "!" +} +catch (e:Exception) {} + + +if (complexrange.startsWith("(") && complexrange.endsWith(")")) +complexrange = complexrange.substring(0, complexrange.length - 1) +val refs = parseFmla(complexrange) +try +{ +val ref:String +while (refs.size != 0) +{ +while (refs.size > 0) +{ +if (refs.get(0) is Char) +{ +val cOp = refs.get(0) as Char +if (cOp!!.charValue() == ',') +{ +val pu = PtgUnion() +cce += pu.record.size +newData = ByteTools.append(pu.record, newData) +} +else if (cOp!!.charValue() == ' ') +{ +val pi = PtgIsect() +cce += pi.record.size +newData = ByteTools.append(pi.record, newData) +} +else if (cOp!!.charValue() == ':') +{ +val pr = PtgRange() +cce += pr.record.size +newData = ByteTools.append(pr.record, newData) +} +else if (cOp!!.charValue() == ')') +{ +val pp = PtgParen() +cce += pp.record.size +newData = ByteTools.append(pp.record, newData) +} +} +else +{ +val o = refs.get(0) +if (o is Ptg) +{ +cce += (o as Ptg).record.size +newData = ByteTools.append((o as Ptg).record, newData) +} +else +{ +ref = o as String +val isName = (this.parentRec!!.workBook!!.getName(ref) != null) +var p:Ptg? = null +if (isName) +{ +p = PtgName() +p!!.parentRec = this.parentRec +(p as PtgName).setName(ref) +cce += p!!.record.size +newData = ByteTools.append(p!!.record, newData) +} +else if (ref.indexOf(":") > 0) +{ +if (ref.indexOf("!") == -1) ref = sheetName + ref +p = PtgArea3d() +p!!.parentRec = this.parentRec +p!!.location = ref +cce += p!!.record.size +newData = ByteTools.append(p!!.record, newData) +} +else +{ +if (ref.indexOf("!") == -1) ref = sheetName + ref +p = PtgRef3d() +p!!.parentRec = this.parentRec +p!!.location = ref +(p as PtgRef3d).setPtgType(PtgRef.REFERENCE) +cce += p!!.record.size +newData = ByteTools.append(p!!.record, newData) +} +} +} +refs.removeAt(0) +} +} +} +catch (e:Exception) { +throw IllegalArgumentException("PtgMemFunc Error Parsing Location " + complexrange + ":" + e.toString()) +} + +val ix = ByteTools.shortToLEBytes(cce.toShort()) +System.arraycopy(ix, 0, newData, 1, 2) +newData[0] = 41 +record = newData +try +{ +populateVals() +} +catch (e:Exception) { +Logger.logErr("PtgMemFunc setLocation failed for: " + complexrange + " " + e.toString()) +} + +} + +public override fun init(b:ByteArray) { +opcode = b[0] +record = b +try +{ +this.populateVals() +} +catch (e:Exception) { +Logger.logErr("PtgMemFunc init: " + e.toString()) +} + +} + +@Throws(Exception::class) +internal fun populateVals() { +cce = ByteTools.readShort(record[1].toInt(), record[2].toInt()).toInt() +subexp = ByteArray(cce) +System.arraycopy(record, 3, subexp, 0, cce) + // subexpression stack in form of: REFERENCE, REFERENCE, OP [,REFERENCE, OP] ... + // op can be one of: PtgUnion [,] PtgIsect [ ] or PtgRange [:] + subExpression = ExpressionParser.parseExpression(subexp, this.parentRec) + // try parsing/calculating on-demand rather than upon init + //parseSubexpression(); + } + +/** + * return the ptg components for a certain column within a ptgArea() + * + * @param colNum + * @return all Ptg's within colNum + */ + fun getColComponents(colNum:Int):Array { +if (colrefs != null) + // cache + return colrefs +val v = FastAddVector() +val allComponents = this.components +for (i in allComponents!!.indices) +{ +val p = allComponents!![i] as PtgRef + // TODO: check rc sanity here + val x = p.intLocation +if (x!![1] == colNum) v.add(p) +} +colrefs = arrayOfNulls(v.size) +v.toTypedArray() +return colrefs +} + + +/** + * parses subexpression into ptgs array + traps referenced sheets + */ + private fun parseSubexpression() { + // calculate subexpression to obtain ptgs + val o = FormulaCalculator.calculateFormula(this.subExpression!!) +val components = ArrayList() +if (o != null && o is Array) +{ + // Firstly: take subexpression and remove reference-tracked elements; calcualted elements are ref-tracked below + for (i in subExpression!!.indices) +{ +try +{ +(subExpression!!.get(i) as PtgRef).removeFromRefTracker() +} +catch (e:Exception) {} + +} +ptgs = o as Array? +for (i in ptgs!!.indices) +{ +try +{ +if (!refsheets!!.contains((ptgs!![i] as PtgRef).sheetName)) +refsheets!!.add((ptgs!![i] as PtgRef).sheetName) +(ptgs!![i] as PtgRef).addToRefTracker() +if ((ptgs!![i] is PtgArea) and !(ptgs!![i] is PtgAreaErr3d)) +{ +val p = ptgs!![i].components +for (j in p.indices) +components.add(p[j]) +} +else +components.add(ptgs!![i]) +} +catch (e:Exception) { +Logger.logErr("PtgMemFunc init: " + e.toString()) +} + +} +} +else +{ // often a single reference surrounded by parens +for (i in subExpression!!.indices) +{ +try +{ +val pr = subExpression!!.get(i) as PtgRef +if (!refsheets!!.contains(pr.sheetName)) +refsheets!!.add(pr.sheetName) +if (pr is PtgArea) +{ +val pa = pr.components +for (j in pa!!.indices) +components.add(pa!![j]) +} +else +components.add(pr) +} +catch (e:Exception) {} + +} +} +ptgs = arrayOfNulls(components.size) +components.toTypedArray() +} + +/** + * takes a range string which may contain operators: + * union [,] isect [ ] range [:} or paren + * plus range elements and/or named range + * parse and order each element correctly + * may be called recurrsively + * NOTE: may also be VERY complex, of type OFFSET(x,y,0):OFFSET(z,w,0) + * ALSO INDEX and INDIRECT ... + * + * @param complexrange + * @return ordered stack containing parsed range elements + */ + private fun parseFmla(complexrange:String):Stack> { +val ops = Stack>() +var lastOp = 0 +var finishRange = false +var refs = Stack>() +var ref = "" +var inquote = false +var range:String? = null // holds partial range +var i = 0 +while (i < complexrange.length) +{ +val c = complexrange.get(i) +if (c == '\'') +{ +inquote = !inquote +ref += c +} +else if (!inquote) +{ +if (c == ',' || c == ' ' || c == ')' || (c == ':' && finishRange)) +{ // it's an operand +if (c == ' ' && lastOp == ' '.toInt()) +{ +i++ +continue +} // skip 2nd space op (Isect) +if (finishRange) +{ // add ref to rest of range +refs.push(range!! + ref) +if (!refs.isEmpty() && !ops.isEmpty()) +refs = handleOpearatorPreference(refs, ops) +while (!ops.isEmpty()) +refs.push(ops.pop()) +range = null +ref = "" +finishRange = false +ops.push(c) +} +else if (refs.isEmpty()) +{ // no operands yet - put in 1st +if (ref != "") +refs.push(ref) +ref = "" +ops.push(c) +} +else +{ // have all we need to process +if (ref != "") +refs.push(ref) +while (!ops.isEmpty()) +refs.push(ops.pop()) +ref = "" // handle case of two spaces ... unfortunately +ops.push(c) +} +lastOp = c.toInt() +} +else if (c == ':') +{ +if (this.parentRec!!.workBook!!.getName(ref) == null) +{ // it's a regular range + // check if the ref is a sheet name in a 3d ref + if (ref != "") +{ +range = ref + c +finishRange = true +} +else +{ // happens in cases such as (opopop):ref:ref +ops.push(c) +} +ref = "" +} +else +{ // it's a named range +refs.push(ref) +ref = "" +ops.push(c) +finishRange = false // it's not a regular range +} +} +else if (c == '(') +{ +var endparen = FormulaParser.getMatchOperator(complexrange, i, '(', ')') +if (endparen == -1) +endparen = complexrange.length - 1 +else if (ref != "") +{ + // rare case of a PtgMemFunc containing a formula: + val f = ref + "(" + complexrange.substring(i + 1, endparen + 1) +ref = "" +refs = mergeStacks(refs, FormulaParser.getPtgsFromFormulaString(this.parentRec, f, true)) +i = endparen +if (!ops.isEmpty()) +refs = handleOpearatorPreference(refs, ops) +while (!ops.isEmpty()) +refs.push(ops.pop()) +i++ +continue +} +refs = mergeStacks(refs, parseFmla(complexrange.substring(i + 1, endparen + 1))) +i = endparen +if (!ops.isEmpty()) +refs = handleOpearatorPreference(refs, ops) +while (!ops.isEmpty()) +refs.push(ops.pop()) +} +else +ref += c +} +else +ref += c +i++ +} + // get any remaining + if (finishRange) +{ // add ref to rest of range + // range op has more precedence than others ... + if ((!ops.isEmpty() && (ops.peek() as Char).charValue() == ':' && !refs.isEmpty() && +refs.peek() is Char)) +{ +while (refs.peek() is Char) +{ +if ((refs.peek() as Char).charValue() != ':') +ops.add(0, refs.pop()) +else +break +} +} +if (ref != "") +refs.push(range!! + ref) +else +{ +refs.push(range!!.substring(0, range!!.length - 1)) +ops.push(':') +} +} +else +{ +if (ref != "") +refs.push(ref) +} +while (!ops.isEmpty()) +refs.push(ops.pop()) +return refs +} + + +/** + * when parenthesed sub-functions + * + * @param first + * @param last + * @return + */ + private fun mergeStacks(first:Stack>, last:Stack>):Stack> { +first.addAll(last) +return first +} + +/** + * traverse through expression to retrieve set of ranges + * either discontiguous union (,), intersected ( ) or regular range (:) + */ + public override fun toString():String { +return FormulaParser.getExpressionString(subExpression!!).substring(1) // avoid "=" +} + +/** + * return the boundsheet associated with this complex range + *

                      NOTE: since complex ranges may contain more than one sheet, this is incomplete for those instanaces + * + * @param b + * @return + */ + fun getSheets(b:WorkBook):Array? { +if (ptgs == null) +parseSubexpression() +if (this.refsheets != null || this.refsheets!!.size != 0) +{ +try +{ +val sheets = arrayOfNulls(this.refsheets!!.size) +for (i in sheets.indices) +sheets[i] = b.getWorkSheetByName(refsheets!!.get(i)) +return sheets +} +catch (e:Exception) { + // TODO: report error? + } + +} +return null + +} + +public override fun close() { +if (ptgs != null) +{ +for (i in ptgs!!.indices) +{ +if (ptgs!![i] is PtgRef) +ptgs!![i].close() +else +ptgs!![i].close() +} +} +ptgs = null +super.close() +} + +companion object { + + val serialVersionUID = 666555444333222L + + +/** + * handle precedence of complex range operators: : before , before ' ' + * + * @param sourceStack + * @param destStack + */ + private fun handleOpearatorPreference(refs:Stack>, ops:Stack>):Stack> { +val lastOp = (ops.pop() as Char).charValue() +if (refs.peek() is Char) +{ +val curOp = (refs.pop() as Char).charValue() +val group1 = rankPrecedence(lastOp) +val group2 = rankPrecedence(curOp) +if (group2 >= group1) +{ +ops.push(lastOp) +refs.push(curOp) +} +else +{ +ops.push(curOp) +refs.push(lastOp) +} + +} +else +ops.push(lastOp) +return refs +} + +/** + * rank a Ptg Operator's precedence (lower + * + * @param curOp + * @return + */ + internal fun rankPrecedence(curOp:Char):Int { +if (curOp.toInt() == 0) return -1 +if (curOp == ')') +return 6 +if (curOp == ':') +return 5 +if (curOp == ',' || curOp == ' ') + // same level???? + return 4 +return 0 // ' ' +} +} + /* protected void finalize() { + this.close(); + }*/ +} + + + + + diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.kt similarity index 56% rename from src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.kt index c3f88a0..668a768 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMissArg.kt @@ -20,59 +20,54 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; - +package io.starter.formats.XLS.formulas /* Ptg that is an missing operand - + * @see Ptg * @see Formula - -*/ - -public class PtgMissArg extends GenericPtg implements Ptg { - private static final long serialVersionUID = 8995314621921283625L; +*/ - public boolean getIsOperand() { - return true; - } +class PtgMissArg : GenericPtg(), Ptg { - public boolean getIsOperator() { - return false; - } + override val isOperand: Boolean + get() = true - public boolean getIsBinaryOperator() { - return false; - } + override val isOperator: Boolean + get() = false - public boolean getIsPrimitiveOperator() { - return false; - } + override val isBinaryOperator: Boolean + get() = false - public PtgMissArg() { - this.init(new byte[]{22}); - } + override val isPrimitiveOperator: Boolean + get() = false /** * return the human-readable String representation of */ - public String getString() { - return ""; - } + override val string: String + get() = "" - public String toString() { - return ""; + override val length: Int + get() = 1 + + override val value: Any? + get() = null + + init { + this.init(byteArrayOf(22)) } - public int getLength() { - return 1; + override fun toString(): String { + return "" } - public Object getValue() { - return null; + companion object { + + private val serialVersionUID = 8995314621921283625L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.java deleted file mode 100644 index 60cbbc7..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.toolkit.Logger; - -import java.lang.reflect.Array; - - -/* - Ptg that is a multiplier operand - - * @see Ptg - * @see Formula - - -*/ -public class PtgMlt extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2670754297349356254L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return "*"; - } - - public String toString() { - return this.getString(); - } - - public PtgMlt() { - ptgId = 0x5; - record = new byte[1]; - record[0] = 0x5; - } - - public int getLength() { - return PTG_MLT_LENGTH; - } - - /* Operator specific calculate method, this one multiplies two values. - - */ - public Ptg calculatePtg(Ptg[] form) { - // handle ref errs - if (form[0] instanceof PtgErr || form[0] instanceof PtgRefErr || form[0] instanceof PtgRefErr3d || form[0] instanceof PtgAreaErr3d) - return form[0]; - if (form[1] instanceof PtgErr || form[1] instanceof PtgRefErr || form[1] instanceof PtgRefErr3d || form[1] instanceof PtgAreaErr3d) - return form[1]; - - try { - // 20090202 KSC: Handle array formulas - Object[] o = getValuesFromPtgs(form); - if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o.length != 2) { - Logger.logWarn("calculating formula failed, wrong number of values in PtgMlt"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; - } - double o0 = 0, o1 = 0; - try { - o0 = getDoubleValue(o[0], this.parent_rec); - o1 = getDoubleValue(o[1], this.parent_rec); - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - double returnVal = o0 * o1; - // create a container ptg for these. - PtgNumber n = new PtgNumber(returnVal); - return n; - } else { // handle array fomulas - String retArry = ""; - int nArrays = java.lang.reflect.Array.getLength(o); - if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i = 0; i < nArrays - 1; i += 2) { - Object secondOp = null; - boolean comparitorIsArray = o[i + 1].getClass().isArray(); - if (!comparitorIsArray) secondOp = o[i + 1]; - for (int j = 0; j < nVals; j++) { - Object firstOp = Array.get(o[i], j); // first array index j - if (comparitorIsArray) - secondOp = Array.get(o[i + 1], j); // second array index j - double o0 = 0, o1 = 0; - try { - o0 = getDoubleValue(firstOp, this.parent_rec); - o1 = getDoubleValue(secondOp, this.parent_rec); - } catch (NumberFormatException e) { - retArry = retArry + "#VALUE!" + ","; - continue; - } - double retVal = o0 * o1; - retArry = retArry + retVal + ","; - } - } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; - } - } catch (NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } catch (Exception e) { // 20081125 KSC: handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.kt new file mode 100644 index 0000000..8b782dc --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMlt.kt @@ -0,0 +1,150 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.toolkit.Logger + +import java.lang.reflect.Array + + +/* + Ptg that is a multiplier operand + + * @see Ptg + * @see Formula + + +*/ +class PtgMlt : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val string: String + get() = "*" + + override val length: Int + get() = Ptg.PTG_MLT_LENGTH + + override fun toString(): String { + return this.string + } + + init { + opcode = 0x5 + record = ByteArray(1) + record[0] = 0x5 + } + + /* Operator specific calculate method, this one multiplies two values. + + */ + override fun calculatePtg(form: Array): Ptg? { + // handle ref errs + if (form[0] is PtgErr || form[0] is PtgRefErr || form[0] is PtgRefErr3d || form[0] is PtgAreaErr3d) + return form[0] + if (form[1] is PtgErr || form[1] is PtgRefErr || form[1] is PtgRefErr3d || form[1] is PtgAreaErr3d) + return form[1] + + try { + // 20090202 KSC: Handle array formulas + val o = GenericPtg.getValuesFromPtgs(form) ?: return PtgErr(PtgErr.ERROR_VALUE) +// some error in value(s) + if (!o[0].javaClass.isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o.size != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgMlt") + return PtgErr(PtgErr.ERROR_VALUE) // 20081203 KSC: handle error's ala Excel return null; + } + var o0 = 0.0 + var o1 = 0.0 + try { + o0 = GenericPtg.getDoubleValue(o[0], this.parentRec) + o1 = GenericPtg.getDoubleValue(o[1], this.parentRec) + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + val returnVal = o0 * o1 + // create a container ptg for these. + return PtgNumber(returnVal) + } else { // handle array fomulas + var retArry = "" + val nArrays = java.lang.reflect.Array.getLength(o) + if (nArrays != 2) return PtgErr(PtgErr.ERROR_VALUE) + val nVals = java.lang.reflect.Array.getLength(o[0]) // use first array element to determine length of values as subsequent vals might not be arrays + var i = 0 + while (i < nArrays - 1) { + var secondOp: Any? = null + val comparitorIsArray = o[i + 1].javaClass.isArray() + if (!comparitorIsArray) secondOp = o[i + 1] + for (j in 0 until nVals) { + val firstOp = Array.get(o[i], j) // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j) // second array index j + var o0 = 0.0 + var o1 = 0.0 + try { + o0 = GenericPtg.getDoubleValue(firstOp, this.parentRec) + o1 = GenericPtg.getDoubleValue(secondOp, this.parentRec) + } catch (e: NumberFormatException) { + retArry = "$retArry#VALUE!," + continue + } + + val retVal = o0 * o1 + retArry = "$retArry$retVal," + } + i += 2 + } + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa + } + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } catch (e: Exception) { // 20081125 KSC: handle error ala Excel + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -2670754297349356254L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgMystery.java b/src/main/java/io/starter/formats/XLS/formulas/PtgMystery.kt similarity index 73% rename from src/main/java/io/starter/formats/XLS/formulas/PtgMystery.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgMystery.kt index 83119f0..c98263d 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgMystery.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgMystery.kt @@ -20,37 +20,30 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /* Unhandled or Unimplemented PTG, that is stored as a 1 byte ptg. Eases ability to reconstruct these formulas - + * @see Ptg * @see Formula - -*/ -public class PtgMystery extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5170319810844024569L; +*/ +class PtgMystery : GenericPtg(), Ptg { /** * return the human-readable String representation of */ - public String getString() { - return "!UNKNOWN!"; - } + override val string: String + get() = "!UNKNOWN!" - public int getLength() { - return getRecord().length; - } + override val length: Int + get() = record.size // KSC: Using PtgMystery as a Generic, lightweight Ptg @@ -59,8 +52,15 @@ public int getLength() { * functioning as a Generic, lightweight Ptg * see PtgRange, PtgUnion, PtgIsect */ - public Object getValue() { - return vars; + override val value: Any? + get() = vars + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 5170319810844024569L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgNE.java b/src/main/java/io/starter/formats/XLS/formulas/PtgNE.java deleted file mode 100644 index 1705fac..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgNE.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.toolkit.Logger; - -import java.lang.reflect.Array; - - -/* - Ptg that is a not equal to operand - - Evaluates to TRUE if the two top operands are not equal, - otherwise evaluates as FALSE; - - - * @see Ptg - * @see Formula - - -*/ -public class PtgNE extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 6901661166166179786L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public PtgNE() { - ptgId = 0xE; - record = new byte[1]; - record[0] = 0xE; - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return "<>"; - } - - public int getLength() { - return PTG_NE_LENGTH; - } - - /* Operator specific calculate method, this one determines if the second-to-top - operand is less than the top operand; Returns a PtgBool - - */ - public Ptg calculatePtg(Ptg[] form) { - try { - // 20090202 KSC: Handle array formulas - Object[] o = getValuesFromPtgs(form); - boolean res; - if (!o[0].getClass().isArray()) { - //double[] dub = super.getValuesFromPtgs(form); - // there should always be only two ptg's in this, error if not. - if (o == null || o.length != 2) { - Logger.logWarn("calculating formula failed, wrong number of values in PtgNE"); - return null; - } - // blank handling: - // determine if any of the operands are double - if true, - // then blank comparisons will be treated as 0's - boolean isDouble = false; - for (int i = 0; i < 2 && !isDouble; i++) { - //if (!form[i].isBlank()) - isDouble = ((o[i] instanceof Double)); - } - for (int i = 0; i < 2; i++) { - //if (form[i].isBlank()) { - if (o[i] != null && o[i].toString().equals("")) { - if (isDouble) - o[i] = new Double(0.0); - else - o[i] = ""; // in this case, empty cells are handled as blank, not zero - } - } - if (o[0] instanceof Double && o[1] instanceof Double) - res = (Math.abs((((Double) o[0]).doubleValue()) - ((Double) o[1]).doubleValue())) > doublePrecision; // compare equality to certain precision - else res = !o[0].toString().equalsIgnoreCase(o[1].toString()); - PtgBool pboo = new PtgBool(res); - return pboo; - } else { // handle array fomulas - String retArry = ""; - int nArrays = java.lang.reflect.Array.getLength(o); - if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i = 0; i < nArrays - 1; i += 2) { - res = false; - Object secondOp = null; - boolean comparitorIsArray = o[i + 1].getClass().isArray(); - if (!comparitorIsArray) secondOp = o[i + 1]; - for (int j = 0; j < nVals; j++) { - Object firstOp = Array.get(o[i], j); // first array index j - if (comparitorIsArray) - secondOp = Array.get(o[i + 1], j); // second array index j - - if (firstOp instanceof Double && secondOp instanceof Double) - res = (Math.abs((((Double) firstOp).doubleValue()) - ((Double) secondOp).doubleValue())) > doublePrecision; // compare to certain precision instead of equality - else - res = firstOp.toString().equalsIgnoreCase(secondOp.toString()); - retArry = retArry + res + ","; - } - } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; - } - /*}catch(NumberFormatException e){ // shouldn't get here!! see new code above - String[] s = getStringValuesFromPtgs(form); - if (s[0].equalsIgnoreCase(s[1]))return new PtgBool(false); - return new PtgBool(true); - }*/ - } catch (Exception e) { // 20090212 KSC: handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgNE.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgNE.kt new file mode 100644 index 0000000..249427e --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgNE.kt @@ -0,0 +1,158 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.toolkit.Logger + +import java.lang.reflect.Array + + +/* + Ptg that is a not equal to operand + + Evaluates to TRUE if the two top operands are not equal, + otherwise evaluates as FALSE; + + + * @see Ptg + * @see Formula + + +*/ +class PtgNE : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val string: String + get() = "<>" + + override val length: Int + get() = Ptg.PTG_NE_LENGTH + + init { + opcode = 0xE + record = ByteArray(1) + record[0] = 0xE + } + + /* Operator specific calculate method, this one determines if the second-to-top + operand is less than the top operand; Returns a PtgBool + + */ + override fun calculatePtg(form: Array): Ptg? { + try { + // 20090202 KSC: Handle array formulas + val o = GenericPtg.getValuesFromPtgs(form) + var res: Boolean + if (!o!![0].javaClass.isArray()) { + //double[] dub = super.getValuesFromPtgs(form); + // there should always be only two ptg's in this, error if not. + if (o == null || o.size != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgNE") + return null + } + // blank handling: + // determine if any of the operands are double - if true, + // then blank comparisons will be treated as 0's + var isDouble = false + run { + var i = 0 + while (i < 2 && !isDouble) { + //if (!form[i].isBlank()) + isDouble = o[i] is Double + i++ + } + } + for (i in 0..1) { + //if (form[i].isBlank()) { + if (o[i] != null && o[i].toString() == "") { + if (isDouble) + o[i] = 0.0 + else + o[i] = "" // in this case, empty cells are handled as blank, not zero + } + } + if (o[0] is Double && o[1] is Double) + res = Math.abs((o[0] as Double).toDouble() - (o[1] as Double).toDouble()) > doublePrecision // compare equality to certain precision + else + res = !o[0].toString().equals(o[1].toString(), ignoreCase = true) + return PtgBool(res) + } else { // handle array fomulas + var retArry = "" + val nArrays = java.lang.reflect.Array.getLength(o) + if (nArrays != 2) return PtgErr(PtgErr.ERROR_VALUE) + val nVals = java.lang.reflect.Array.getLength(o[0]) // use first array element to determine length of values as subsequent vals might not be arrays + var i = 0 + while (i < nArrays - 1) { + res = false + var secondOp: Any? = null + val comparitorIsArray = o[i + 1].javaClass.isArray() + if (!comparitorIsArray) secondOp = o[i + 1] + for (j in 0 until nVals) { + val firstOp = Array.get(o[i], j) // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j) // second array index j + + if (firstOp is Double && secondOp is Double) + res = Math.abs(firstOp.toDouble() - secondOp.toDouble()) > doublePrecision // compare to certain precision instead of equality + else + res = firstOp.toString().equals(secondOp!!.toString(), ignoreCase = true) + retArry = "$retArry$res," + } + i += 2 + } + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa + } + /*}catch(NumberFormatException e){ // shouldn't get here!! see new code above + String[] s = getStringValuesFromPtgs(form); + if (s[0].equalsIgnoreCase(s[1]))return new PtgBool(false); + return new PtgBool(true); + }*/ + } catch (e: Exception) { // 20090212 KSC: handle error ala Excel + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 6901661166166179786L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgName.java b/src/main/java/io/starter/formats/XLS/formulas/PtgName.java deleted file mode 100644 index a497a5b..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgName.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.Formula; -import io.starter.formats.XLS.FormulaNotFoundException; -import io.starter.formats.XLS.Name; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.FastAddVector; -import io.starter.toolkit.Logger; - - -/** - * This PTG stores an index to a name. The ilbl field is a 1 based index to the table - * of NAME records in the workbook - *

                      - * OFFSET NAME sIZE CONTENTS - * --------------------------------------------- - * 0 ilbl 2 Index to the NAME table - * 2 (reserved) 2 ` Must be 0; - * - * @see Ptg - * @see Formula - */ -public class PtgName extends GenericPtg implements Ptg, IlblListener { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8047146848365098162L; - short ilbl; - String name; - - public boolean getIsOperand() { - - return true; - } - - // lookup Name object in Workbook and return handle - public Name getName() { - WorkBook b = this.getParentRec().getWorkBook(); - Name n = null; - try { - n = b.getName(ilbl); - } catch (Exception ex) { - } - return n; - } - - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - addToRefTracker(); - } - - //default constructor - public PtgName() { - ptgId = 0x23; // reference type is default - } - - /** - * set the Ptg Id type to one of: - * VALUE, REFERENCE or Array - *
                      The Ptg type is important for certain - * functions which require a specific type of operand - */ - public void setPtgType(short type) { - switch (type) { - case VALUE: - ptgId = 0x43; - break; - case REFERENCE: - ptgId = 0x23; - break; - case Ptg.ARRAY: - ptgId = 0x63; - break; - } - record[0] = ptgId; - } - - // 20100218 KSC: - // constructor which sets a specific id - // to specify whether this PtgName is of value, ref or array type - // (PtgNameV, PtgNameR or PtgNameA) - public PtgName(int id) { - ptgId = (byte) id; -// 0x23= Ref -// ptgId = 0x43; Value - } - - - /** - * add this reference to the ReferenceTracker... this - * is crucial if we are to update this Ptg when cells - * are changed or added... - */ - public void addToRefTracker() { - //Logger.logInfo("Adding :" + this.toString() + " to tracker"); - try { - if (parent_rec != null) - parent_rec.getWorkBook().getRefTracker().addPtgNameReference(this); - } catch (Exception ex) { - Logger.logErr("PtgRef.addToRefTracker() failed.", ex); - } - } - - /** - * For creating a ptg name from formula parser - */ - public void setName(String name) { - record = new byte[5]; - record[0] = ptgId; - WorkBook b = this.getParentRec().getWorkBook(); - ilbl = (short) b.getNameNumber(name); - this.addListener(); - record[1] = (byte) ilbl; - } - - - private void populateVals() { - ilbl = ByteTools.readShort(record[1], record[2]); - } - - public int getVal() { - return (int) ilbl; - } - - public short getIlbl() { - return ilbl; - } - - public void storeName(String nm) { - name = nm; - } - - - public void setIlbl(short i) { - if (ilbl != i) { - ilbl = i; - this.updateRecord(); - } - } - - /* - * - * returns the string value of the name - @see io.starter.formats.XLS.formulas.Ptg#getValue() - */ - public Object getValue() { - Name n = getName(); - try { - Ptg[] p = n.getCellRangePtgs(); - if (p.length == 0) { - return "#NAME?"; - } else if (p.length == 1 || !(this.parent_rec instanceof io.starter.formats.XLS.Array)) { // usual case - return p[0].getValue(); - } else { // multiple values; create an array - String retarry = ""; - for (int i = 0; i < p.length; i++) { - retarry = retarry + p[i].getValue() + ","; - } - retarry = "{" + retarry.substring(0, retarry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retarry); - return pa; - } - } catch (Exception e) { - } - // String s = n.getName(); - //return n; - return "#NAME?"; - } - - public String getTextString() { - Name n = getName(); - if (n == null) - return "#NAME!"; - return n.getName(); - } - - public String getStoredName() { - return name; - } - - public void setVal(int i) { - ilbl = (short) i; - this.updateRecord(); - } - - public void updateRecord() { - byte[] brow = ByteTools.cLongToLEBytes(ilbl); - record[1] = brow[0]; - record[2] = brow[1]; - if (parent_rec != null) { - if (parent_rec instanceof Formula) - ((Formula) parent_rec).updateRecord(); - } - } - - /** - * Override due to mystery extra byte - * occasionally found in ptgName recs. - */ - public int getLength() { - if (record != null) return record.length; - return PTG_NAME_LENGTH; - } - - public String toString() { - if (this.getName() != null) - return this.getName().getName(); - return "[Null]"; - } - - public Ptg[] getComponents() { - FastAddVector v = new FastAddVector(); - Ptg p = this.getName().getPtga(); - Ptg[] pcomps = p.getComponents(); - if (pcomps != null) { - for (int x = 0; x < pcomps.length; x++) { - v.add(pcomps[x]); - } - } else { - v.add(p); - } - Ptg[] retPtgs = new Ptg[v.size()]; - retPtgs = (Ptg[]) v.toArray(retPtgs); - return retPtgs; - -/* Ptg[] p = this.getName().getComponents(); - for (int i=0;i. + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.Formula +import io.starter.formats.XLS.FormulaNotFoundException +import io.starter.formats.XLS.Name +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.ByteTools +import io.starter.toolkit.FastAddVector +import io.starter.toolkit.Logger + + +/** + * This PTG stores an index to a name. The ilbl field is a 1 based index to the table + * of NAME records in the workbook + * + * + * OFFSET NAME sIZE CONTENTS + * --------------------------------------------- + * 0 ilbl 2 Index to the NAME table + * 2 (reserved) 2 ` Must be 0; + * + * @see Ptg + * + * @see Formula + */ +open class PtgName : GenericPtg, Ptg, IlblListener { + internal var ilbl: Short = 0 + override var storedName: String + internal set + + override val isOperand: Boolean + get() = true + + // lookup Name object in Workbook and return handle + open val name: Name? + get() { + val b = this.parentRec!!.workBook + var n: Name? = null + try { + n = b!!.getName(ilbl.toInt()) + } catch (ex: Exception) { + } + + return n + } + + open var `val`: Int + get() = ilbl.toInt() + set(i) { + ilbl = i.toShort() + this.updateRecord() + } + + /* + * + * returns the string value of the name + @see io.starter.formats.XLS.formulas.Ptg#getValue() + */ + override// usual case + // multiple values; create an array + // String s = n.getName(); + //return n; + val value: Any? + get() { + val n = name + try { + val p = n!!.cellRangePtgs + if (p.size == 0) { + return "#NAME?" + } else if (p.size == 1 || this.parentRec !is io.starter.formats.XLS.Array) { + return p[0].value + } else { + var retarry = "" + for (i in p.indices) { + retarry = retarry + p[i].value + "," + } + retarry = "{" + retarry.substring(0, retarry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retarry) + return pa + } + } catch (e: Exception) { + } + + return "#NAME?" + } + + override val textString: String + get() { + val n = name ?: return "#NAME!" + return n.name + } + + /** + * Override due to mystery extra byte + * occasionally found in ptgName recs. + */ + override val length: Int + get() = if (record != null) record.size else Ptg.PTG_NAME_LENGTH + + override/* Ptg[] p = this.getName().getComponents(); + for (int i=0;i? + get() { + val v = FastAddVector() + val p = this.name!!.ptga + val pcomps = p!!.components + if (pcomps != null) { + for (x in pcomps.indices) { + v.add(pcomps[x]) + } + } else { + v.add(p) + } + var retPtgs = arrayOfNulls(v.size) + retPtgs = v.toTypedArray() as Array + return retPtgs + } + + /** + * return referenced Names' location + * + * @see io.starter.formats.XLS.formulas.GenericPtg.getLocation + */ + override var location: String? + @Throws(FormulaNotFoundException::class) + get() { + if (this.name != null) + try { + return this.name!!.location + } catch (e: Exception) { + } + + return null + } + set + + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() + addToRefTracker() + } + + //default constructor + constructor() { + opcode = 0x23 // reference type is default + } + + /** + * set the Ptg Id type to one of: + * VALUE, REFERENCE or Array + *

                      The Ptg type is important for certain + * functions which require a specific type of operand + */ + fun setPtgType(type: Short) { + when (type) { + Ptg.VALUE -> opcode = 0x43 + Ptg.REFERENCE -> opcode = 0x23 + Ptg.ARRAY -> opcode = 0x63 + } + record[0] = opcode + } + + // 20100218 KSC: + // constructor which sets a specific id + // to specify whether this PtgName is of value, ref or array type + // (PtgNameV, PtgNameR or PtgNameA) + constructor(id: Int) { + opcode = id.toByte() + // 0x23= Ref + // ptgId = 0x43; Value + } + + + /** + * add this reference to the ReferenceTracker... this + * is crucial if we are to update this Ptg when cells + * are changed or added... + */ + fun addToRefTracker() { + //Logger.logInfo("Adding :" + this.toString() + " to tracker"); + try { + if (parentRec != null) + parentRec!!.workBook!!.refTracker!!.addPtgNameReference(this) + } catch (ex: Exception) { + Logger.logErr("PtgRef.addToRefTracker() failed.", ex) + } + + } + + /** + * For creating a ptg name from formula parser + */ + open fun setName(name: String) { + record = ByteArray(5) + record[0] = opcode + val b = this.parentRec!!.workBook + ilbl = b!!.getNameNumber(name).toShort() + this.addListener() + record[1] = ilbl.toByte() + } + + + private fun populateVals() { + ilbl = ByteTools.readShort(record[1].toInt(), record[2].toInt()) + } + + override fun getIlbl(): Short { + return ilbl + } + + override fun storeName(nm: String) { + storedName = nm + } + + + override fun setIlbl(i: Short) { + if (ilbl != i) { + ilbl = i + this.updateRecord() + } + } + + override fun updateRecord() { + val brow = ByteTools.cLongToLEBytes(ilbl.toInt()) + record[1] = brow[0] + record[2] = brow[1] + if (parentRec != null) { + if (parentRec is Formula) + (parentRec as Formula).updateRecord() + } + } + + override fun toString(): String { + return if (this.name != null) this.name!!.name else "[Null]" + } + + override fun addListener() { + val n = this.name + if (n != null) { + n.addIlblListener(this) + this.storeName(n.name) + } + + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 8047146848365098162L + } +} + + \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgNameX.java b/src/main/java/io/starter/formats/XLS/formulas/PtgNameX.java deleted file mode 100644 index 8c28b69..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgNameX.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.formats.XLS.Name; -import io.starter.formats.XLS.WorkBook; -import io.starter.toolkit.ByteTools; - - -/* - This PTG stores an index to a name. The ilbl field is a 1 based index to the table - of NAME records in the workbook - - OFFSET NAME sIZE CONTENTS - --------------------------------------------- - 0 ixti 2 index to externsheet - 2 ilbl 2 Index to the NAME table - 4 (reserved) 2 ` Must be 0; - - * @see Ptg - * @see Formula - -*/ -public class PtgNameX extends PtgName implements Ptg, IxtiListener { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1240996941619495505L; - short ixti; - int ilbl; - - - public boolean getIsOperand() { - - return true; - } - - //lookup Name object in Workbook and return handle - public Name getName() { - WorkBook b = this.getParentRec().getSheet().getWorkBook(); - // the externsheet reference is negative, there seems to be a problem - // off the docs. Just use a placeholder boundsheet, as the PtgRef3D internally will - // get the value correctly - //Externsheet x = b.getExternSheet(); - Name n = null; - - try { - n = b.getName(ilbl); - n.setSheet(this.getParentRec().getSheet()); - } catch (Exception e) { - // it's an AddInFormula... -jm - } - //Boundsheet[] bound = x.getBoundSheets(ixti); - return n; - } - - public void addListener() { - try { - getParentRec().getWorkBook().getExternSheet().addPtgListener(this); - } catch (Exception e) { - // no need to output here. NullPointer occurs when a ref has an invalid ixti, such as when a sheet was removed Worksheet exception could never really happen. - } - } - - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - } - - private void populateVals() { - ixti = ByteTools.readShort(record[1], record[2]); - - ilbl = ByteTools.readShort(record[3], record[4]); - } - - public int getVal() { - return ilbl; - } - - /* - * - * returns the string value of the name - @see io.starter.formats.XLS.formulas.Ptg#getValue() - */ - public Object getValue() { - - WorkBook b = this.getParentRec().getSheet().getWorkBook(); - String externalname = null; - try { - externalname = b.getExternalName(ilbl); - } catch (Exception e) { - } - if (externalname != null) - return externalname; - - Name n = getName(); - return n.getCalculatedValue(); - } - - public String toString() { - if (this.parent_rec.getSheet() != null) - return (String) getValue(); - else - return "Uninitialized PtgNameX"; - } - - public String getTextString() { - Object o = getValue(); - if (o == null) - return ""; - return o.toString(); - } - - - public int getLength() { - return PTG_NAMEX_LENGTH; - } - - /** - * @return Returns the ixti. - */ - public short getIxti() { - return ixti; - } - - /** - * @param ixti The ixti to set. - */ - public void setIxti(short ixti) { - this.ixti = ixti; - } - - // KSC: Added to handle External names (denoted by PtgNameX records in ExpressionParser) - - /** - * For creating a ptg namex from formula parser - */ - public void setName(String name) { - ptgId = 0x39; // PtgNameX - record = new byte[PTG_NAMEX_LENGTH]; - record[0] = ptgId; - WorkBook b = this.getParentRec().getSheet().getWorkBook(); - ilbl = b.getExtenalNameNumber(name); - ixti = (short) b.getExternSheet().getVirtualReference(); - byte[] bb = ByteTools.shortToLEBytes(ixti); - record[1] = bb[0]; - record[2] = bb[1]; - byte[] bbb = ByteTools.cLongToLEBytes(ilbl); - record[3] = bbb[0]; - record[4] = bbb[1]; - } -} - - \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgNameX.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgNameX.kt new file mode 100644 index 0000000..f0edfe5 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgNameX.kt @@ -0,0 +1,172 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.formats.XLS.Name +import io.starter.formats.XLS.WorkBook +import io.starter.toolkit.ByteTools + + +/* + This PTG stores an index to a name. The ilbl field is a 1 based index to the table + of NAME records in the workbook + + OFFSET NAME sIZE CONTENTS + --------------------------------------------- + 0 ixti 2 index to externsheet + 2 ilbl 2 Index to the NAME table + 4 (reserved) 2 ` Must be 0; + + * @see Ptg + * @see Formula + +*/ +class PtgNameX : PtgName(), Ptg, IxtiListener { + /** + * @return Returns the ixti. + */ + /** + * @param ixti The ixti to set. + */ + override var ixti: Short = 0 + override var `val`: Int = 0 + internal set(value: Int) { + super.`val` = value + } + + + override val isOperand: Boolean + get() = true + + //lookup Name object in Workbook and return handle + override// the externsheet reference is negative, there seems to be a problem + // off the docs. Just use a placeholder boundsheet, as the PtgRef3D internally will + // get the value correctly + //Externsheet x = b.getExternSheet(); + // it's an AddInFormula... -jm + //Boundsheet[] bound = x.getBoundSheets(ixti); + val name: Name? + get() { + val b = this.parentRec!!.sheet!!.workBook + var n: Name? = null + + try { + n = b!!.getName(`val`) + n!!.setSheet(this.parentRec!!.sheet) + } catch (e: Exception) { + } + + return n + } + + /* + * + * returns the string value of the name + @see io.starter.formats.XLS.formulas.Ptg#getValue() + */ + override val value: Any? + get() { + + val b = this.parentRec!!.sheet!!.workBook + var externalname: String? = null + try { + externalname = b!!.getExternalName(`val`) + } catch (e: Exception) { + } + + if (externalname != null) + return externalname + + val n = name + return n!!.calculatedValue + } + + override val textString: String + get() { + val o = value ?: return "" + return o.toString() + } + + + override val length: Int + get() = Ptg.PTG_NAMEX_LENGTH + + override fun addListener() { + try { + parentRec!!.workBook!!.externSheet!!.addPtgListener(this) + } catch (e: Exception) { + // no need to output here. NullPointer occurs when a ref has an invalid ixti, such as when a sheet was removed Worksheet exception could never really happen. + } + + } + + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() + } + + private fun populateVals() { + ixti = ByteTools.readShort(record[1].toInt(), record[2].toInt()) + + `val` = ByteTools.readShort(record[3].toInt(), record[4].toInt()).toInt() + } + + override fun toString(): String { + return if (this.parentRec!!.sheet != null) + value as String? + else + "Uninitialized PtgNameX" + } + + // KSC: Added to handle External names (denoted by PtgNameX records in ExpressionParser) + + /** + * For creating a ptg namex from formula parser + */ + override fun setName(name: String) { + opcode = 0x39 // PtgNameX + record = ByteArray(Ptg.PTG_NAMEX_LENGTH) + record[0] = opcode + val b = this.parentRec!!.sheet!!.workBook + `val` = b!!.getExtenalNameNumber(name) + ixti = b.externSheet!!.virtualReference.toShort() + val bb = ByteTools.shortToLEBytes(ixti) + record[1] = bb[0] + record[2] = bb[1] + val bbb = ByteTools.cLongToLEBytes(`val`) + record[3] = bbb[0] + record[4] = bbb[1] + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 1240996941619495505L + } +} + + \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.java b/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.java deleted file mode 100644 index e93da6f..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.toolkit.ByteTools; - - -/* - Ptg that stores an IEEE value - - Offset Name Size Contents - ------------------------------------ - 0 num 8 An IEEE floating point nubmer - - * @see Ptg - * @see Formula - - -*/ -public class PtgNumber extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1650136303920724485L; - - public boolean getIsOperand() { - return true; - } - - double val; - boolean percentage = false; // 20081208 KSC: so can handle percentage values in String formulas - - /** - * return the human-readable String representation of - */ - public String getString() { - if (!percentage) - return String.valueOf(val); - return val * 100 + "%"; - } - - public Object getValue() { - Double d = new Double(val); - return d; - } - - public PtgNumber() { - ptgId = 0x1F; - val = 0; - this.updateRecord(); - } - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - } - - /** - * Constructer to create these on the fly, this is needed - * for value storage in calculations of formulas. - */ - public PtgNumber(double d) { - ptgId = 0x1F; - val = d; - this.updateRecord(); - } - - - private void populateVals() { - byte[] barr = new byte[8]; - System.arraycopy(record, 1, barr, 0, 8); - val = ByteTools.eightBytetoLEDouble(barr); - } - - public double getVal() { - return val; - } - - /** - * override of GenericPtg.getDoubleVal(); - */ - public double getDoubleVal() { - return val; - } - - public void setVal(double i) { - val = i; - this.updateRecord(); - } - - // 20081208 KSC: handle percentage values - public void setVal(String s) { - s = s.trim(); - if (s.indexOf("%") == s.length() - 1) { - percentage = true; - s = s.substring(0, s.indexOf("%")); - val = new Double(s).doubleValue() / 100; - } else - val = new Double(s).doubleValue(); - } - - public void updateRecord() { - byte[] tmp = new byte[1]; - tmp[0] = ptgId; - byte[] brow = ByteTools.toBEByteArray(val); - tmp = ByteTools.append(brow, tmp); - record = tmp; - } - - public int getLength() { - return PTG_NUM_LENGTH; - } - - public String toString() { - return getString(); - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.kt new file mode 100644 index 0000000..6d2bdae --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgNumber.kt @@ -0,0 +1,132 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.toolkit.ByteTools + + +/* + Ptg that stores an IEEE value + + Offset Name Size Contents + ------------------------------------ + 0 num 8 An IEEE floating point nubmer + + * @see Ptg + * @see Formula + + +*/ +class PtgNumber : GenericPtg, Ptg { + + override val isOperand: Boolean + get() = true + + /** + * override of GenericPtg.getDoubleVal(); + */ + override var doubleVal: Double = 0.toDouble() + internal set + internal var percentage = false // 20081208 KSC: so can handle percentage values in String formulas + + /** + * return the human-readable String representation of + */ + override val string: String + get() = if (!percentage) doubleVal.toString() else (doubleVal * 100).toString() + "%" + + override val value: Any? + get() = doubleVal + + var `val`: Double + get() = doubleVal + set(i) { + doubleVal = i + this.updateRecord() + } + + override val length: Int + get() = Ptg.PTG_NUM_LENGTH + + constructor() { + opcode = 0x1F + doubleVal = 0.0 + this.updateRecord() + } + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() + } + + /** + * Constructer to create these on the fly, this is needed + * for value storage in calculations of formulas. + */ + constructor(d: Double) { + opcode = 0x1F + doubleVal = d + this.updateRecord() + } + + + private fun populateVals() { + val barr = ByteArray(8) + System.arraycopy(record, 1, barr, 0, 8) + doubleVal = ByteTools.eightBytetoLEDouble(barr) + } + + // 20081208 KSC: handle percentage values + fun setVal(s: String) { + var s = s + s = s.trim { it <= ' ' } + if (s.indexOf("%") == s.length - 1) { + percentage = true + s = s.substring(0, s.indexOf("%")) + doubleVal = Double(s) / 100 + } else + doubleVal = Double(s) + } + + override fun updateRecord() { + var tmp = ByteArray(1) + tmp[0] = opcode + val brow = ByteTools.toBEByteArray(doubleVal) + tmp = ByteTools.append(brow, tmp) + record = tmp + } + + override fun toString(): String { + return string + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -1650136303920724485L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgParen.java b/src/main/java/io/starter/formats/XLS/formulas/PtgParen.kt similarity index 61% rename from src/main/java/io/starter/formats/XLS/formulas/PtgParen.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgParen.kt index 91ee988..0a5e560 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgParen.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgParen.kt @@ -20,85 +20,83 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /* Indicate the placing of operatands in parenthesis. - - Does not affect calculation. - + + Does not affect calculation. + =1+(2) - + ptgInt 1 ptgInt 2 ptgParen ptgAdd - + =(1+2) - + ptgInt 1 ptgInt 2 ptgAdd - ptgParen - - + ptgParen + + * @see Ptg * @see Formula - . + . */ -public class PtgParen extends GenericPtg implements Ptg { +class PtgParen : GenericPtg(), Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8081397558698615537L; - - public boolean getIsControl() { - return true; - } + override val isControl: Boolean + get() = true /** * return the human-readable String representation of */ - public String getString() { - // TODO: add logic to return proper paren 12/02 -jm - - return "("; - } + override// TODO: add logic to return proper paren 12/02 -jm + val string: String + get() = "(" /** * return the human-readable String representation of * the "closing" portion of this Ptg * such as a closing parenthesis. */ - public String getString2() { - return ")"; - } + override val string2: String + get() = ")" + + override val length: Int + get() = Ptg.PTG_PAREN_LENGTH /** * Pass in the last 3 ptgs to evaluate * where to place the String parens. */ - public Object evaluate(Object[] b) { - return null; - } - - public int getLength() { - return PTG_PAREN_LENGTH; + override fun evaluate(b: Array): Any? { + return null } // KSC: added //default constructor - public PtgParen() { - ptgId = 0x15; - record = new byte[1]; - record[0] = ptgId; + init { + opcode = 0x15 + record = ByteArray(1) + record[0] = opcode } - public String toString() { - return ")"; + override fun toString(): String { + return ")" + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -8081397558698615537L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgPercent.java b/src/main/java/io/starter/formats/XLS/formulas/PtgPercent.kt similarity index 61% rename from src/main/java/io/starter/formats/XLS/formulas/PtgPercent.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgPercent.kt index ce0eed5..459c912 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgPercent.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgPercent.kt @@ -28,62 +28,60 @@ */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.toolkit.Logger; +import io.starter.toolkit.Logger /** * */ -public class PtgPercent extends GenericPtg implements Ptg { +class PtgPercent : GenericPtg(), Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8559541841405018157L; - - public boolean getIsOperator() { - return true; - } + override val isOperator: Boolean + get() = true - public boolean getIsPrimitiveOperator() { - return true; - } + override val isPrimitiveOperator: Boolean + get() = true - public boolean getIsUnaryOperator() { - return true; - } + override val isUnaryOperator: Boolean + get() = true /** * return the human-readable String representation of */ - public String getString() { - return "%"; - } + override val string: String + get() = "%" - public int getLength() { - return PTG_PERCENT_LENGTH; - } + override val length: Int + get() = Ptg.PTG_PERCENT_LENGTH /* Operator specific calculate method, this one returns a single value sent to it. */ - public Ptg calculatePtg(Ptg[] form) { + override fun calculatePtg(form: Array): Ptg? { // 20090202 KSC: Handle array formulas - Object[] o = getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { + val o = GenericPtg.getValuesFromPtgs(form) + if (!o!![0].javaClass.isArray()) { //double[] dub = super.getValuesFromPtgs(form); // there should always be only two ptg's in this, error if not. - if (o == null || o.length != 1) { + if (o == null || o.size != 1) { // there should always be only one ptg in this, error if not. //if (form.length != 1){ - Logger.logWarn("calculating formula failed, wrong number of values in PtgPercent"); - return null; + Logger.logWarn("calculating formula failed, wrong number of values in PtgPercent") + return null } } // TODO: finish for Array formulas - double res = (((Double) o[0]).doubleValue()) / 100; - return new PtgNumber(res); + val res = (o[0] as Double).toDouble() / 100 + return PtgNumber(res) + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -8559541841405018157L } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgPower.java b/src/main/java/io/starter/formats/XLS/formulas/PtgPower.kt similarity index 54% rename from src/main/java/io/starter/formats/XLS/formulas/PtgPower.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgPower.kt index 895e368..ea36132 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgPower.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgPower.kt @@ -20,81 +20,79 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.toolkit.Logger; +import io.starter.toolkit.Logger /* Ptg that is a exponent operand - Raises the second-to-top operand to the power of the + Raises the second-to-top operand to the power of the top operand - + * @see Ptg * @see Formula - -*/ -public class PtgPower extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4675566993519011450L; - public boolean getIsOperator() { - return true; - } +*/ +class PtgPower : GenericPtg(), Ptg { - public boolean getIsPrimitiveOperator() { - return true; - } + override val isOperator: Boolean + get() = true - public boolean getIsBinaryOperator() { - return true; - } + override val isPrimitiveOperator: Boolean + get() = true - public PtgPower() { - ptgId = 0x7; - record = new byte[1]; - record[0] = 0x7; - } + override val isBinaryOperator: Boolean + get() = true /** * return the human-readable String representation of */ - public String getString() { - return "^"; - } + override val string: String + get() = "^" + + override val length: Int + get() = Ptg.PTG_POWER_LENGTH - public int getLength() { - return PTG_POWER_LENGTH; + init { + opcode = 0x7 + record = ByteArray(1) + record[0] = 0x7 } /* Operator specific calculate method, this one raises the second-to-top operand to the power of the top operand */ - public Ptg calculatePtg(Ptg[] form) { + override fun calculatePtg(form: Array): Ptg? { try { // 20090202 KSC: Handle array formulas - Object[] o = getValuesFromPtgs(form); - if (!o[0].getClass().isArray()) { + val o = GenericPtg.getValuesFromPtgs(form) + if (!o!![0].javaClass.isArray()) { //double[] dub = super.getValuesFromPtgs(form); // there should always be only two ptg's in this, error if not. - if (o == null || o.length != 2) { - Logger.logWarn("calculating formula failed, wrong number of values in PtgPower"); - return null; + if (o == null || o.size != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgPower") + return null } //double returnVal = Math.pow(dub[0].doubleValue(), dub[1].doubleValue()); - double returnVal = Math.pow(((Double) o[0]).doubleValue(), ((Double) o[1]).doubleValue()); + val returnVal = Math.pow((o[0] as Double).toDouble(), (o[1] as Double).toDouble()) // create a container ptg for these. - PtgNumber n = new PtgNumber(returnVal); - return n; + return PtgNumber(returnVal) } // TODO: FINISH ARRAY FORMULAS - return new PtgErr(PtgErr.ERROR_VALUE); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); + return PtgErr(PtgErr.ERROR_VALUE) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) } + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 4675566993519011450L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRange.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRange.java deleted file mode 100644 index 78d3060..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRange.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.ExcelTools; - -import java.util.ArrayList; - - -/* - Computes the minimal bounding rectangle of the top two operands. - This is excel's ":" colon operator. - - - * @see Ptg - * @see Formula - - -*/ -public class PtgRange extends GenericPtg implements Ptg { - - private static final long serialVersionUID = 7181427387507157013L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } // 20091019 KSC - /*? public boolean getIsOperand(){return true;} - public boolean getIsControl(){return true;} - */ - - public PtgRange() { - ptgId = 0x11; - record = new byte[1]; - record[0] = 0x11; - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return ":"; - } - - - public int getLength() { - return PTG_RANGE_LENGTH; - } - - /** - * The RANGE operator (:) - * Returns the minimal rectangular range that contains both parameters. - * A1:B2:B2:C3 ==>A1:C3 - *

                      - * NOTE: assumption is NO 3d refs i.e. all on same sheet ******* - */ - public Ptg calculatePtg(Ptg[] form) { - if (form.length != 2) - return new PtgErr(PtgErr.ERROR_VALUE); - - try { - String sheet = null; - String sourceSheet = null; - try { - sourceSheet = this.getParentRec().getSheet().getSheetName(); - } catch (NullPointerException ne) { - } - ArrayList first = null; - ArrayList last = null; - for (int i = 0; i < 2; i++) { - Ptg p = form[i]; - ArrayList a = new ArrayList(); - if (p instanceof PtgArea) { - a.add(p); - } else if (p instanceof PtgRef) { - a.add(p); - } else if (p instanceof PtgName) { - Ptg[] pc = p.getComponents(); - for (int j = 0; j < pc.length; j++) - a.add(pc[j]); - } else if (p instanceof PtgStr) { - String[] comps = (p.toString()).split(","); - for (int j = 0; j < comps.length; j++) { - if (comps[j].indexOf(":") == -1) { - if (!comps[j].equals("#REF!") && - !comps[j].equals("#NULL!")) { - PtgRef3d pr = new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(comps[j]); - a.add(pr); - } else { - PtgRefErr3d pr = new PtgRefErr3d(); - pr.setParentRec(this.getParentRec()); - a.add(pr); - } - } else { - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(comps[j]); - Ptg[] pcs = pa.getComponents(); - if (pcs != null) { - for (int k = 0; k < pcs.length; k++) { - ((PtgRef) pcs[k]).setSheetName(pa.getSheetName()); - a.add(pcs[k]); - } - } - } - } - } else if (p instanceof PtgArray) { - // parse array components and create refs - Ptg[] pc = p.getComponents(); - for (int j = 0; j < pc.length; j++) { - String loc = pc[j].toString(); - if (loc.indexOf(":") == -1) { - if (loc.indexOf("!") == -1) { - PtgRef pr = new PtgRef(); - pr.setUseReferenceTracker(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } else { - PtgRef3d pr = new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } - } else { - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(loc); - a.add(pa); - } - } - } else if (p instanceof PtgErr || p instanceof PtgRefErr || p instanceof PtgAreaErr3d) { - // DO WHAT??? - // ignore - } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array - Ptg[] pc = ((GenericPtg) p).vars; - for (int j = 0; j < pc.length; j++) { - if (pc[j] instanceof PtgArea & !(pc[j] instanceof PtgAreaErr3d)) { - Ptg[] pa = pc[j].getComponents(); - for (int k = 0; k < pa.length; k++) - a.add(pa[k]); - } else - a.add(pc[j]); - } - } - - if (first == null) - first = a; - else - last = a; - } - // now have components for both operands - // range op returns the range that encompasses all referenced ptgs - int[] rng = new int[]{Short.MAX_VALUE, Short.MAX_VALUE, 0, 0}; - for (int k = 0; k < first.size(); k++) { - PtgRef pr = (PtgRef) first.get(k); - if (sheet == null) sheet = pr.getSheetName(); // TODO: 3d ranges?????? - int[] rc = pr.getIntLocation(); - if (rc.length > 2) { // it's a range - int numrows = (rc[2] - rc[0]) + 1; - int numcols = (rc[3] - rc[1]) + 1; - int numcells = numrows * numcols; - if (numcells < 0) - numcells *= -1; // handle swapped cells ie: "B1:A1" - int rowctr = rc[0]; - int cellctr = rc[1] - 1; - for (int i = 0; i < numcells; i++) { - if (cellctr == rc[3]) {// if its the end of the row,increment row. - cellctr = rc[1] - 1; - rowctr++; - } - ++cellctr; - int[] addr = new int[]{rowctr, cellctr}; - adjustRange(addr, rng); - } - } else { - adjustRange(rc, rng); - } - } - for (int k = 0; k < last.size(); k++) { - PtgRef pr = (PtgRef) last.get(k); - if (sheet == null) sheet = pr.getSheetName(); // TODO: 3d ranges?????? - int[] rc = pr.getIntLocation(); - if (rc.length > 2) { // it's a range - if (rc.length > 2) { // it's a range - int numrows = (rc[2] - rc[0]) + 1; - int numcols = (rc[3] - rc[1]) + 1; - int numcells = numrows * numcols; - if (numcells < 0) - numcells *= -1; // handle swapped cells ie: "B1:A1" - int rowctr = rc[0]; - int cellctr = rc[1] - 1; - for (int i = 0; i < numcells; i++) { - if (cellctr == rc[3]) {// if its the end of the row,increment row. - cellctr = rc[1] - 1; - rowctr++; - } - ++cellctr; - int[] addr = new int[]{rowctr, cellctr}; - adjustRange(addr, rng); - } - } - } else { - adjustRange(rc, rng); - } - } - // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars - GenericPtg retp = new PtgMystery(); - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - // TODO: 3d ranges???? - pa.setSheetName(sheet); - pa.setLocation(rng); - retp.setVars(new Ptg[]{pa}); - return retp; - } catch (NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } catch (Exception e) { // handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - } - - - private void adjustRange(int[] rc, int[] rng) { - if (ExcelTools.isBeforeRange(rc, rng)) { - rng[0] = rc[0]; - rng[1] = rc[1]; - } - if (ExcelTools.isAfterRange(rc, rng)) { - rng[2] = rc[0]; - rng[3] = rc[1]; - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRange.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgRange.kt new file mode 100644 index 0000000..a676388 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRange.kt @@ -0,0 +1,264 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.ExcelTools + +import java.util.ArrayList + + +/* + Computes the minimal bounding rectangle of the top two operands. + This is excel's ":" colon operator. + + + * @see Ptg + * @see Formula + + +*/ +class PtgRange : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true // 20091019 KSC + + /** + * return the human-readable String representation of + */ + override val string: String + get() = ":" + + + override val length: Int + get() = Ptg.PTG_RANGE_LENGTH + /*? public boolean getIsOperand(){return true;} + public boolean getIsControl(){return true;} + */ + + init { + opcode = 0x11 + record = ByteArray(1) + record[0] = 0x11 + } + + /** + * The RANGE operator (:) + * Returns the minimal rectangular range that contains both parameters. + * A1:B2:B2:C3 ==>A1:C3 + * + * + * NOTE: assumption is NO 3d refs i.e. all on same sheet ******* + */ + override fun calculatePtg(form: Array): Ptg? { + if (form.size != 2) + return PtgErr(PtgErr.ERROR_VALUE) + + try { + var sheet: String? = null + var sourceSheet: String? = null + try { + sourceSheet = this.parentRec!!.sheet!!.sheetName + } catch (ne: NullPointerException) { + } + + var first: ArrayList<*>? = null + var last: ArrayList<*>? = null + for (i in 0..1) { + val p = form[i] + val a = ArrayList() + if (p is PtgArea) { + a.add(p) + } else if (p is PtgRef) { + a.add(p) + } else if (p is PtgName) { + val pc = p.components + for (j in pc.indices) + a.add(pc[j]) + } else if (p is PtgStr) { + val comps = p.toString().split(",".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + for (j in comps.indices) { + if (comps[j].indexOf(":") == -1) { + if (comps[j] != "#REF!" && comps[j] != "#NULL!") { + val pr = PtgRef3d(false) + pr.parentRec = this.parentRec + pr.location = comps[j] + a.add(pr) + } else { + val pr = PtgRefErr3d() + pr.parentRec = this.parentRec + a.add(pr) + } + } else { + val pa = PtgArea3d(false) + pa.parentRec = this.parentRec + pa.location = comps[j] + val pcs = pa.components + if (pcs != null) { + for (k in pcs.indices) { + (pcs[k] as PtgRef).sheetName = pa.sheetName + a.add(pcs[k]) + } + } + } + } + } else if (p is PtgArray) { + // parse array components and create refs + val pc = p.components + for (j in pc.indices) { + val loc = pc[j].toString() + if (loc.indexOf(":") == -1) { + if (loc.indexOf("!") == -1) { + val pr = PtgRef() + pr.useReferenceTracker = false + pr.parentRec = this.parentRec + pr.location = loc + a.add(pr) + } else { + val pr = PtgRef3d(false) + pr.parentRec = this.parentRec + pr.location = loc + a.add(pr) + } + } else { + val pa = PtgArea3d(false) + pa.parentRec = this.parentRec + pa.location = loc + a.add(pa) + } + } + } else if (p is PtgErr || p is PtgRefErr || p is PtgAreaErr3d) { + // DO WHAT??? + // ignore + } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array + val pc = (p as GenericPtg).vars + for (j in pc!!.indices) { + if ((pc[j] is PtgArea) and (pc[j] !is PtgAreaErr3d)) { + val pa = pc[j].components + for (k in pa.indices) + a.add(pa[k]) + } else + a.add(pc[j]) + } + } + + if (first == null) + first = a + else + last = a + } + // now have components for both operands + // range op returns the range that encompasses all referenced ptgs + val rng = intArrayOf(java.lang.Short.MAX_VALUE.toInt(), java.lang.Short.MAX_VALUE.toInt(), 0, 0) + for (k in first!!.indices) { + val pr = first[k] as PtgRef + if (sheet == null) sheet = pr.sheetName // TODO: 3d ranges?????? + val rc = pr.intLocation + if (rc!!.size > 2) { // it's a range + val numrows = rc[2] - rc[0] + 1 + val numcols = rc[3] - rc[1] + 1 + var numcells = numrows * numcols + if (numcells < 0) + numcells *= -1 // handle swapped cells ie: "B1:A1" + var rowctr = rc[0] + var cellctr = rc[1] - 1 + for (i in 0 until numcells) { + if (cellctr == rc[3]) {// if its the end of the row,increment row. + cellctr = rc[1] - 1 + rowctr++ + } + ++cellctr + val addr = intArrayOf(rowctr, cellctr) + adjustRange(addr, rng) + } + } else { + adjustRange(rc, rng) + } + } + for (k in last!!.indices) { + val pr = last[k] as PtgRef + if (sheet == null) sheet = pr.sheetName // TODO: 3d ranges?????? + val rc = pr.intLocation + if (rc!!.size > 2) { // it's a range + if (rc.size > 2) { // it's a range + val numrows = rc[2] - rc[0] + 1 + val numcols = rc[3] - rc[1] + 1 + var numcells = numrows * numcols + if (numcells < 0) + numcells *= -1 // handle swapped cells ie: "B1:A1" + var rowctr = rc[0] + var cellctr = rc[1] - 1 + for (i in 0 until numcells) { + if (cellctr == rc[3]) {// if its the end of the row,increment row. + cellctr = rc[1] - 1 + rowctr++ + } + ++cellctr + val addr = intArrayOf(rowctr, cellctr) + adjustRange(addr, rng) + } + } + } else { + adjustRange(rc, rng) + } + } + // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars + val retp = PtgMystery() + val pa = PtgArea3d(false) + pa.parentRec = this.parentRec + // TODO: 3d ranges???? + pa.sheetName = sheet + pa.setLocation(rng) + retp.setVars(arrayOf(pa)) + return retp + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } catch (e: Exception) { // handle error ala Excel + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + + private fun adjustRange(rc: IntArray, rng: IntArray) { + if (ExcelTools.isBeforeRange(rc, rng)) { + rng[0] = rc[0] + rng[1] = rc[1] + } + if (ExcelTools.isAfterRange(rc, rng)) { + rng[2] = rc[0] + rng[3] = rc[1] + } + } + + companion object { + + private val serialVersionUID = 7181427387507157013L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRef.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRef.java deleted file mode 100644 index 2afa7b4..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRef.java +++ /dev/null @@ -1,1105 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.*; -import io.starter.formats.cellformat.CellFormatFactory; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; - - -/** - * ptgRef is a reference to a single cell. It contains row and - * column information, plus a grbit to determine whether these - * values are relative or absolute. This grbit is, stupidly, but expectedly, - * encoded within the column value. - *

                      - * Offset      Name        Size    Contents
                      - * ----------------------------------------------------
                      - * 0           rw          2       the row
                      - * 2           grbitCol    2       (see following table)
                      - *
                      - * Only the low-order 14 bits specify the Col, the other bits specify
                      - * relative vs absolute for both the col or the row.
                      - *
                      - * Bits        Mask        Name    Contents
                      - * -----------------------------------------------------
                      - * 15          8000h       fRwRel  =1 if row offset relative,
                      - * =0 if otherwise
                      - * 14          4000h       fColRel =1 if col offset relative,
                      - * =0 if otherwise
                      - * 13-0        3FFFh       col     Ordinal column offset or number
                      - * 
                      - * - * @see WorkBook - * @see Boundsheet - * @see Dbcell - * @see Row - * @see Cell - * @see XLSRecord - */ -public class PtgRef extends GenericPtg implements Ptg { - /** - * - */ - private static final long serialVersionUID = -7776520933300730470L; - protected int rw; - // TODO: We actually are talking about 2 different notions of relativity: - // 1- Relativity based on shared formula parent formula row/col - // 2- Relative/Absolute in terms of row movement ($'s mean reference is ABSOLUTE) - // we are combining the two concepts erroneously - protected boolean fRwRel = true; //true is relative, false is absolute (=$'s) - protected boolean fColRel = true; - protected int col; - protected boolean is3dRef = false; - private String cachedLocation = null; - - protected int formulaRow; - protected int formulaCol; - BiffRec[] refCell = new BiffRec[1]; - protected String sheetname = null; - - protected int externalLink1 = 0; - protected int externalLink2 = 0; - protected boolean useReferenceTracker = true; - - String locax = null; - // String locstrax = null; - public long hashcode = -1L; - - - public boolean equals(Object ob) { - return ob.hashCode() == this.hashCode(); - } - - public boolean getIsWholeRow() { - return wholeRow; - } - - public void setIsWholeRow(boolean b) { - wholeRow = b; - } - - public boolean getIsWholeCol() { - return wholeCol; - } - - public void setIsWholeCol(boolean b) { - wholeCol = b; - } - - public boolean getIsRefErr() { - return false; - } - - public boolean wholeRow = false, wholeCol = false; // denotes a range which spans the entire row or column, a shorthand for checking end col or row # as this will vary between excel versions - - public boolean getIsOperand() { - return true; - } - - public boolean getIsReference() { - return true; - } - - public PtgRef(int[] rowcol, XLSRecord x, boolean useRefTracker) { - this(); - setParentRec(x); - this.useReferenceTracker = useRefTracker; - setLocation(rowcol); - updateRecord(); - } - - - /** - * This constructor is for programmatic creation of Ptg's - * in this case we do not have the ptgid, just the refereced location - */ - public PtgRef(String location, XLSRecord x, boolean utilizeRefTracker) { - this.setUseReferenceTracker(utilizeRefTracker); - ptgId = 0x44; //0x24; defaulting to value operand - record = new byte[5]; - record[0] = ptgId; - setParentRec(x); // MUST set before setLocation also sets formulaRow ... - this.setLocation(location); - this.setIsWholeRowCol(); - if (useReferenceTracker) - addToRefTracker(); - } - - - /** - * - * @param id - */ - - /** - * set the Ptg Id type to one of: - * VALUE, REFERENCE or Array - *
                      The Ptg type is important for certain - * functions which require a specific type of operand - */ - public void setPtgType(short type) { - switch (type) { - case VALUE: - ptgId = 0x44; - break; - case REFERENCE: - ptgId = 0x24; - break; - case Ptg.ARRAY: - ptgId = 0x64; - break; - } - record[0] = ptgId; - } - - - /** - * This constructor is for programmatic creation of Ptg's - * in this case we do not have the ptgid, just the refereced location - *

                      - * this version sets the value of useReferenceTracker to avoid multiple entries due to area parent - */ - public PtgRef(byte[] bin, XLSRecord x, boolean utilizeRefTracker) { - this(); - this.setUseReferenceTracker(utilizeRefTracker); - setParentRec(x); //MUST DO BEFORE INIT ... also sets formulaRow ... - init(bin); - if (useReferenceTracker) - addToRefTracker(); // TODO: check subreference issue (if it's not a 'real' ptg) - } - - /** - * default constructor - */ - public PtgRef() { - // 24H (tRefR), 44H (tRefV), 64H (tRefA) - ptgId = 0x44; // default to value operand - record = new byte[5]; - record[0] = ptgId; - } - - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - this.populateVals(); - } - - /** - * Ptgs upkeep their mapping in reference tracker, however, some ptgs - * are components of other Ptgs, such as individual ptg cells in a PtgArea. These - * should not be stored in the RT. - */ - public void setUseReferenceTracker(boolean b) { - useReferenceTracker = b; - } - - public boolean getUseReferenceTracker() { - return useReferenceTracker; - } - - /** - * parse all the values out of the byte array and - * populate the classes values - */ - protected void populateVals() { - rw = readRow(record[1], record[2]); - short column = ByteTools.readShort(record[3], record[4]); - // is the Row relative? - fRwRel = (column & 0x8000) == 0x8000; - // is the Column relative? - fColRel = (column & 0x4000) == 0x4000; - col = (short) (column & 0x3fff); - setRelativeRowCol(); // set formulaRow/Col for relative references if necessary - this.setIsWholeRowCol(); - hashcode = getHashCode(); - } - - public boolean is3dRef() { - return is3dRef; - } - - /** - * return the human-readable String representation of - * this ptg -- if applicable - */ - public String getString() { - return this.getLocation(); - } - - /** - * returns the String address of this ptg including sheet reference - * - * @return - */ - public String getLocationWithSheet() { - String ret = getString(); - - // AI PtgRefs do not have location info - if ((ret == null) && (parent_rec.getOpcode() == XLSRecord.AI)) - return parent_rec.toString(); - - if (ret == null) - return ""; - - if (ret.indexOf("!") > -1) - return ret; - - ret = sheetname + "!" + ret; - - return ret; - } - - - public String toString() { - return getString(); - } - - - /** - * returns the row/col ints for the ref - * - * @return - */ - public int[] getRowCol() { - int[] ret = {this.rw, this.col}; - if (this.rw < 0) {// if row truly references MAXROWS_BIFF8 comes out - - ret[0] = MAXROWS_BIFF8; - this.wholeCol = true; - } - return ret; - } - - /** - * sets the sheetname for this - * - * @param sheetname - */ - public void setSheetName(String sheetname) { - this.sheetname = sheetname; - } - - - /** - * Returns the location of the Ptg as a string (ie c4) - * - * @see io.starter.formats.XLS.formulas.GenericPtg#getLocation() - */ - public String getLocation() { - if (locax != null)//cache - return locax; - - int[] adjusted = this.getIntLocation(); - String s; - if (this.wholeCol) { - s = (fColRel ? "" : "$") + ExcelTools.getAlphaVal(adjusted[1]); - } else if (this.wholeRow) { - s = (fRwRel ? "" : "$") + (adjusted[0] + 1); - } else { - if (rw < 0 || col < 0) - return new PtgRefErr().toString(); - - s = (fColRel ? "" : "$") + ExcelTools.getAlphaVal(adjusted[1]) + - (fRwRel ? "" : "$") + (adjusted[0] + 1); - } - locax = s; - return locax; - } - - /** - * Get the location of this ptgRef as an int array {row, col}. 0 based - */ - public int[] getIntLocation() { - - this.setIsWholeRowCol(); - int rowNew = rw; - int colNew = col; - try { - boolean isExcel2007 = this.parent_rec.getWorkBook().getIsExcel2007(); - if (fRwRel) { // the row is a relative location - rowNew += formulaRow; - } - if (fColRel) { // the column is a relative location - colNew += formulaCol; - } - if (wholeRow) { - if (!isExcel2007) - colNew = MAXCOLS_BIFF8; - else - colNew = MAXCOLS; - } - if (wholeCol) { - if (isExcel2007) - rowNew = MAXROWS - 1; - else - rowNew = MAXROWS_BIFF8 - 1; - } - - } catch (NullPointerException e) { - } - return new int[]{rowNew, colNew}; - } - - /** - * Get the location of this ptgRef as an int array {row, col}. 0 based - * NOTE: this version of getIntLocation returns the actual or real coordinates - * This may be different from getIntLocation when rw designates MAXROWS - in these cases, - * this method will return real max rows - */ - public int[] getRealIntLocation() { - int rowNew = rw; - int colNew = col; - if (fRwRel) { // the row is a relative location - rowNew += formulaRow; - } - if (fColRel) { // the column is a relative location - colNew += formulaCol; - } - - if (wholeCol || rowNew < 0) - try { - if (rowNew < 0) - wholeCol = true; - rowNew = this.getParentRec().getSheet().getMaxRow(); - } catch (Exception e) { - } - - if (wholeRow || colNew >= MAXCOLS) - try { - colNew = this.getParentRec().getSheet().getMaxCol(); - } catch (Exception e) { - } - int[] ret = {rowNew, colNew}; - return ret; - } - - /** - * Get the worksheet name this ptgref refers to - * - * @throws WorkSheetNotFoundException - */ - public String getSheetName() - throws WorkSheetNotFoundException { - - if (locax != null) { // reference on different sheet than parent - if (locax.indexOf("!") > -1) { - sheetname = locax.substring(0, locax.indexOf("!")); - } - } - if (sheetname == null && - parent_rec != null) { - if (parent_rec.getSheet() != null) { - sheetname = parent_rec.getSheet().getSheetName(); - } - } - - if (sheetname == null) { - return ""; // no sheetname - } else { - - //handle external references (OOXML-specific) - if (externalLink1 > 0) { - if (sheetname.charAt(0) == '\'') - sheetname = sheetname.substring(1, sheetname.length() - 1); - sheetname = "[" + externalLink1 + "]" + sheetname; - } - sheetname = qualifySheetname(sheetname); - - } - return sheetname; - } - - /** - * Set the location of this PtgRef. This takes a location - * such as "a14", also can take a absolute location, such as $A14 - */ - public void setLocation(String address) { - locax = null; - refCell = null; - if (record != null) { - String[] s = ExcelTools.stripSheetNameFromRange(address); - setLocation(s); - locax = s[1]; - } else { - Logger.logWarn("PtgRef.setLocation() failed: NO record data: " + address); - } - } - - /** - * Clears the location cache when needed - */ - public void clearLocationCache() { - locax = null; - } - - /** - * Does this ref reference an entire row (ie $1); - * - * @return - */ - private boolean referencesEntireRow() { - boolean isExcel2007 = this.parent_rec.getWorkBook().getIsExcel2007(); - int colNew = col; - if (fColRel) { // the row is a relative location - colNew += formulaRow; - } - if (colNew < 0) { // have to assume that it's a wholeRow even if 2007 - return true; - } else if (colNew >= MAXCOLS_BIFF8 - 1 && !isExcel2007) { - return true; - } - if (this.cachedLocation != null && isExcel2007) { - return this.locationStringReferencesEntireRow(); - } - // This is unfortunately a bit of a hack due to biff 8 incompatibilies - return colNew == MAXCOLS_BIFF8 - 1 && isExcel2007; - - } - - /** - * Check if the cached string location referrs to a full row - * - * @return - */ - private boolean locationStringReferencesEntireRow() { - if (this.cachedLocation != null) { - int[] res = ExcelTools.getRowColFromString(cachedLocation); - return res[1] < 0; - } - return false; - } - - /** - * Does this ref reference an entire col (ie $A); - * - * @return - */ - private boolean referencesEntireCol() { - int rowNew = rw; - boolean isExcel2007 = this.parent_rec.getWorkBook().getIsExcel2007(); - if (fRwRel) { // the row is a relative location - rowNew += formulaRow; - } - if (rowNew < 0) { - return true; - } else if (rowNew >= MAXROWS_BIFF8 - 1 && !isExcel2007) { - rowNew = -1; - return true; - } - return false; - } - - /** - * Inspects the record to determin if it references whole - * rows or columns and sets the values as required. - * - * @return - */ - protected void setIsWholeRowCol() { - this.wholeCol = referencesEntireCol(); - this.wholeRow = referencesEntireRow(); - } - - /** - * set Ptg to parsed location - * - * @param loc String[] sheet1, range, sheet2, exref1, exref2 - */ - public void setLocation(String[] loc) { - if (useReferenceTracker) - removeFromRefTracker(); - locax = null; - sheetname = loc[0]; - String addr = loc[1]; - cachedLocation = addr; - fRwRel = true; - fColRel = true; - if (addr.indexOf("$") == -1) { // both row and col are relative refs, meaning moves/copies will change ref - // relative link - if (!addr.equals("#REF!") && !addr.equals("")) { - int[] res = ExcelTools.getRowColFromString(addr); - col = res[1]; - rw = res[0]; - } else { - col = -1; - rw = -1; - } - } else { - // absolute reference - if (addr.substring(0, 1).equalsIgnoreCase("$")) { - fColRel = false; - addr = addr.substring(1); - } - if (addr.indexOf("$") != -1) { - fRwRel = false; - addr = StringTool.strip(addr, "$"); - } - int[] res = null; - try { - res = ExcelTools.getRowColFromString(addr); - col = res[1]; - rw = res[0]; - if (col == -1 || rw == -1) { // if wholerow or wholecol, must be absolute - fColRel = false; - fRwRel = false; - } - } catch (IllegalArgumentException ie) { //is it a wholerow/wholecol issue? - if (Character.isDigit(addr.charAt(0))) { //assume wholecol ref - col = MAXCOLS_BIFF8 - 1; - rw = Integer.valueOf(addr).intValue() - 1; - fColRel = false; - fRwRel = false; - } else { //wholerow ref? - rw = -1; - col = ExcelTools.getIntVal(addr); - fColRel = false; - fRwRel = false; - } - } - } - if (col == -1) - wholeRow = true; - if (rw == -1) - wholeCol = true; - this.setIsWholeRowCol(); - this.updateRecord(); - hashcode = getHashCode(); - // trap OOXML external reference link, if any - if (loc[3] != null) - externalLink1 = Integer.valueOf(loc[3].substring(1, loc[3].length() - 1)).intValue(); - if (loc[4] != null) - externalLink2 = Integer.valueOf(loc[4].substring(1, loc[4].length() - 1)).intValue(); - if (useReferenceTracker) { - if (!getIsRefErr() && !this.getIsWholeCol() && !this.getIsWholeRow()) - addToRefTracker(); - } - } - - - /** - * Set the location of this PtgRef. This takes a location - * such as {1,2} - */ - public void setLocation(int[] rowcol) { - locax = null; - cachedLocation = null; - if (record != null) { - if (useReferenceTracker) - removeFromRefTracker(); - rw = rowcol[0]; - col = rowcol[1]; - fRwRel = true; // default - fColRel = true; - this.updateRecord(); - hashcode = getHashCode(); - if (useReferenceTracker) - addToRefTracker(); - } else { - Logger.logWarn("PtgRef.setLocation() failed: NO record data: " + rowcol.toString()); - } - } - - /** - * given an address string, parse and assign to the appropriate PtgRef-type object - *
                      #REF! 's return either PtgRefErr or PtgRefErr3d - *
                      Ranges return either PtgArea or PtgArea3d - *
                      Single addresses return either PtgRef or PtgRef3d - *
                      NOTE: This method does not extract names embedded within the address string - * - * @param address - * @param parent parent record to assign the ptg to - * @return - */ - public static Ptg createPtgRefFromString(String address, XLSRecord parent) { - try { - String[] s = ExcelTools.stripSheetNameFromRange(address); - String sh1 = s[0]; - String range = s[1]; - Ptg ptg; - if (range == null || range.equals("#REF!") || (sh1 != null && sh1.equals("#REF"))) { - if (sh1 != null) { - PtgRefErr3d pe3 = new PtgRefErr3d(); - pe3.setParentRec(parent); - pe3.setLocation(s); - return pe3; - } else { - PtgRefErr pe = new PtgRefErr(); - pe.setParentRec(parent); - pe.setLocation(s); - return pe; - } - } - WorkBook bk = parent.getWorkBook(); - - - String sht = "((?:\\\\?+.)*?!)?+"; - String rangeMatch = "(.*(:).*){2,}?"; //matches 2 or more range ops (:'s) - String opMatch = "(.*([ ,]).*)+"; //matches union or isect op ( " " or ,) - String m = sht + "((" + opMatch + ")|(" + rangeMatch + "))"; - // is address a complex range?? - if (address.matches(m) || range.indexOf("(") > -1) { - //NOTE: this can be a MemFunc OR a MemArea -- - // PtgMemFunc= a NON-CONSTANT cell address, cell range address or cell range list - // Whenever one operand of the reference subexpression is a function, a defined name, a 3D - // reference, or an external reference (and no error occurs), a PtgMemFunc token is used. - // PtgMemArea= constant cell address, cell range address, or cell range list on the same sheet - PtgMemFunc pmf = new PtgMemFunc(); - pmf.setParentRec(parent); - pmf.setLocation(address); // TODO HANDLE FUNCTION MEMFUNCS ALA OFFSET(x,y,0):OFFSET(x,y,0) - ptg = pmf; - } else if (range.indexOf(":") > 0) { // it's a range, either PtgRef3d or PtgArea3d - String[] ops = StringTool.getTokensUsingDelim(range, ":"); - if ((bk.getName(ops[0]) != null || bk.getName(ops[1]) != null)) { - PtgMemFunc pmf = new PtgMemFunc(); - pmf.setParentRec(parent); - pmf.setLocation(address); - ptg = pmf; - } else if (sh1 != null) { - int[] rc = ExcelTools.getRowColFromString(ops[0]); // see if a wholerow/wholecol ref - if (!(ops[0].equals(ops[1]) && rc[0] != -1 && rc[1] != -1)) { - PtgArea3d pta = new PtgArea3d(); - pta.setParentRec(parent); - pta.setLocation(s); - ptg = pta; - } else { - ptg = new PtgRef3d(); - ((PtgRef3d) ptg).setPtgType(REFERENCE); - ptg.setParentRec(parent); - ((PtgRef) ptg).setUseReferenceTracker(false); - ((PtgRef3d) ptg).setLocation(s); - ((PtgRef) ptg).setUseReferenceTracker(true); - ((PtgRef3d) ptg).addToRefTracker(); - } - } else { - PtgArea pa = new PtgArea(); - pa.setParentRec(parent); - pa.setUseReferenceTracker(false); - pa.setLocation(s); - pa.setUseReferenceTracker(true); - pa.addToRefTracker(); - ptg = pa; - } - } else { // it's a single ref NOT a range e.g. Sheet1!A1 - if (sh1 != null) { - ptg = new PtgRef3d(); - ((PtgRef3d) ptg).setPtgType(REFERENCE); - ptg.setParentRec(parent); - ((PtgRef) ptg).setUseReferenceTracker(false); - ((PtgRef3d) ptg).setLocation(s); - ((PtgRef) ptg).setUseReferenceTracker(true); - ((PtgRef3d) ptg).addToRefTracker(); - } else { - PtgRef pr = new PtgRef(); - pr.setParentRec(parent); - pr.setUseReferenceTracker(false); - pr.setLocation(s); - pr.setUseReferenceTracker(true); - pr.addToRefTracker(); - ptg = pr; - } - } - return ptg; - } catch (Exception e) { // any error in parsing return a referr -- makes sense!!! - PtgRefErr3d pe3 = new PtgRefErr3d(); - pe3.setParentRec(parent); - return pe3; - } - } - - /** - * set the location of this PtgRef - * - * @param rowcol int[] rowcol - * @param bRowRel true if row is relative (i.e. A1 not A$1) - * @param bColRel true if col is relative (i.e. A1 not $A1) - */ - public void setLocation(int[] rowcol, boolean bRowRel, boolean bColRel) { - locax = null; - cachedLocation = null; - if (record != null) { - if (useReferenceTracker) removeFromRefTracker(); - rw = rowcol[0]; - col = rowcol[1]; - fRwRel = bRowRel; - fColRel = bColRel; - this.updateRecord(); - if (useReferenceTracker) - addToRefTracker(); - } else { - Logger.logWarn("PtgRef.setLocation() failed: NO record data: " + rowcol.toString()); - } - } - - /** - * Updates the record bytes so it can be pulled back out. - */ - public void updateRecord() { - byte[] tmp = new byte[5]; - tmp[0] = record[0]; - byte[] brow = ByteTools.cLongToLEBytes(rw); - System.arraycopy(brow, 0, tmp, 1, 2); - if (fRwRel) { - col = (short) (0x8000 | col); - } - if (fColRel) { - col = (short) (0x4000 | col); - } - byte[] bcol = ByteTools.cLongToLEBytes(col); - if (col == -1) { // KSC: what excel expects - bcol[1] = 0; - } - System.arraycopy(bcol, 0, tmp, 3, 2); - - record = tmp; - if (parent_rec != null) { - if (this.parent_rec instanceof Formula) - ((Formula) this.parent_rec).updateRecord(); - else if (this.parent_rec instanceof Name) - ((Name) this.parent_rec).updatePtgs(); - } - - col = (short) col & 0x3FFF; //get lower 14 bits which represent the actual column; - } - - public int getLength() { - return PTG_REF_LENGTH; - } - - /** - * return truth of "reference is blank" - * - * @return - */ - public boolean isBlank() { - getRefCells(); - return (refCell[0] == null || ((XLSRecord) refCell[0]).isBlank);//getOpcode()==BLANK); - } - - /** - * returns the value of the cell refereced by the PtgRef - */ - public Object getValue() { - getRefCells(); - Object retValue = null; - if (refCell[0] != null) { - if (refCell[0].getFormulaRec() != null) { - Formula f = refCell[0].getFormulaRec(); - retValue = f.calculateFormula(); - return retValue; - } else { - if (refCell[0].getDataType().equals("Float")) { - retValue = new Double(refCell[0].getDblVal()); - return retValue; - } else { - retValue = refCell[0].getInternalVal(); - return retValue; - } - } - } else { - try { - if (!this.parent_rec.getSheet().getWindow2().getShowZeroValues()) - return null; - } catch (NullPointerException e) { - // assume zero, which the vast majority of cases are - } - return Integer.valueOf(0); - } - } - - /** - * returns the value of the ptg formatted via the underlying cell's number format - * - * @return String underlying cell value formatted via cell's format pattern - */ - public String getFormattedValue() { - getRefCells(); - Object retValue = null; - BiffRec cell = refCell[0]; - - if (cell != null) { - if (cell.getFormulaRec() != null) { - Formula f = cell.getFormulaRec(); - retValue = f.calculateFormula(); - } else { - if (cell.getDataType().equals("Float")) { - retValue = new Double(cell.getDblVal()); - } else { - retValue = cell.getInternalVal(); - } - } - return CellFormatFactory.fromPatternString( - cell.getXfRec().getFormatPattern()).format(retValue); - } else { - try { - if (!this.parent_rec.getSheet().getWindow2().getShowZeroValues()) - return ""; - } catch (NullPointerException e) { - // assume zero, which the vast majority of cases are - } - return "0"; - } - } - - /** - * @return Returns the refCell. - */ - public BiffRec[] getRefCells() { - refCell = new BiffRec[1]; - try { - Boundsheet bs = null; - if ((sheetname != null) && (parent_rec != null)) { - bs = this.parent_rec.getWorkBook().getWorkSheetByName(sheetname); - } else if (parent_rec != null) { - bs = parent_rec.getSheet(); - } - refCell[0] = bs.getCell(rw, col); - } catch (Exception ex) { - } - return refCell; - } - - public boolean changeLocation(String newLoc, Formula f) { - locax = null; - Ptg ptg = null; - int z = -1; - try { - z = ExpressionParser.getExpressionLocByPtg(this, f.getExpression()); - ptg = (Ptg) f.getExpression().get(z); - } catch (Exception e) { - - } - String unstripped = newLoc; - - if (newLoc.indexOf("!") > -1) newLoc = newLoc.substring(newLoc.indexOf("!") + 1); - if (unstripped.indexOf(":") > 0) { // then either PtgRef3d or PtgArea - if (unstripped.indexOf("!") > unstripped.indexOf(":")) { // than it's a PtgRef3d or PtgArea3d - if (unstripped.indexOf(":") != unstripped.lastIndexOf(":")) { // it's a PtgArea3d ala Sheet1:Sheet3!A1:D1 - PtgArea3d pta3 = new PtgArea3d(); - pta3.setLocation(unstripped); - ptg = pta3; - } else { // it's a PtgRef3d ala Sheet1!Sheet3:A1 - PtgRef3d prd = new PtgRef3d(); - prd.setParentRec(f); - prd.setLocation(unstripped); - ptg = prd; - } - // no sheet ref ... - } else { // it's a PtgArea3d (according to Excel's Ai recs !!) - PtgArea pta = new PtgArea(); - pta.setParentRec(f); - pta.setLocation(unstripped); - ptg = pta; - } - } else if (ptg != null) { - // it's a single location - if (!unstripped.equals("")) { - ptg.setParentRec(f); - ptg.setLocation(unstripped); - } else { - ptg = new PtgRef3d(); - ptg.setParentRec(f); - } - } else { // ptg is null, create a new one - ptg = new PtgRef(); - ptg.setParentRec(f); - ptg.setLocation(unstripped); - } - if (z != -1) - f.getExpression().set(z, ptg); // update expression with new Ptg - else - f.getExpression().add(ptg); - return true; - } - - public void setParentRec(XLSRecord f) { - parent_rec = f; - setRelativeRowCol(); // trap formulaRow, Col for relative PtgRefs - } - - /** - * removes this reference from the tracker... - *

                      - * used mostly when we've updated the ref and want - * to re-register it. - */ - public void removeFromRefTracker() { - try { - if (parent_rec != null) { - parent_rec.getWorkBook().getRefTracker().removeCellRange(this); - if (parent_rec.getOpcode() == FORMULA) ((Formula) parent_rec).setCachedValue(null); - } - } catch (Exception ex) { - // no need to error here, sometimes this is called before its in Logger.logErr("PtgRef.removeFromRefTracker() failed.", ex); - } - } - - /** - * add this reference to the ReferenceTracker... this - * is crucial if we are to update this Ptg when cells - * are changed or added... - */ - public void addToRefTracker() { - //Logger.logInfo("Adding :" + this.toString() + " to tracker"); - try { - if (parent_rec != null) - parent_rec.getWorkBook().getRefTracker().addCellRange(this); - } catch (Exception ex) { - Logger.logErr("PtgRef.addToRefTracker() failed.", ex); - } - } - - /** - * update existing tracked ptg with new parent in reference tracker - * - * @param parent - */ - public void updateInRefTracker(XLSRecord parent) { - try { - if (parent != null) - parent.getWorkBook().getRefTracker().updateInRefTracker(this, parent); - } catch (Exception ex) { - Logger.logErr("updateInRefTracker() failed.", ex); - } - } - - /** - * set the formulaRow and formulaCol for relatively-referenced PtgRefs - */ - public void setRelativeRowCol() { - if (fRwRel || fColRel) { - short opc = 0; - if (parent_rec != null) - opc = parent_rec.getOpcode(); - // protocol for shared formulas, conditional formatting, data validity and defined names only (type B cell addresses!) - if (opc == SHRFMLA || opc == DVAL) { - this.formulaRow = parent_rec.getRowNumber(); - this.formulaCol = parent_rec.getColNumber(); - } - } - } - - /** - * set this Ptg to an External Location - used when copying a sheet from another workbook - * - * @param f parent formula rec - */ - public void setExternalReference(String externalWorkbook) { - if (this instanceof PtgArea3d) { - PtgArea3d ptg = (PtgArea3d) this; - WorkBook b = parent_rec.getWorkBook(); - if (b == null) - b = parent_rec.getSheet().getWorkBook(); - short ixti = b.getExternSheet().addExternalSheetRef(externalWorkbook, ptg.getSheetName()); //20080714 KSC: May not reflect external reference! this.sheetname); - ptg.setIxti(ixti); - if (ptg.firstPtg != null) { // it's not a Ref3d - ptg.firstPtg.updateRecord(); - ptg.lastPtg.updateRecord(); - } - ptg.updateRecord(); - } else if (this instanceof PtgRef3d) { - WorkBook b = parent_rec.getWorkBook(); - PtgRef3d pr = (PtgRef3d) this; - if (b == null) - b = parent_rec.getSheet().getWorkBook(); - short ixti = b.getExternSheet().addExternalSheetRef(externalWorkbook, pr.getSheetName()); //20080714 KSC: May not reflect external reference! this.sheetname); - pr.setIxti(ixti); - } else { // TODO: convert to ref3d? - Logger.logWarn("PtgRef.setExternalReference: unable to convert ref"); - } - } - - public boolean isRowRel() { - return fRwRel; - } - - public boolean isColRel() { - return fColRel; - } - - /** - * sets the column to be relative (relative is true) or absolute (relative is false) - *
                      absolute references do not shift upon column inserts or deletes - * - * @param boolean relative - */ - public void setColRel(boolean relative) { - if (fColRel != relative) { - locax = null; - fColRel = relative; - updateRecord(); - } - } - - /** - * sets the row to be relative (relative is true) or absolute (relative is false) - *
                      absolute references do not shift upon row inserts or deletes - * - * @param boolean relative - */ - public void setRowRel(boolean relative) { - if (fRwRel != relative) { - locax = null; - fRwRel = relative; - updateRecord(); - } - } - - public void setArrayTypeRef() { - byte b = (byte) ((record[0] | 0x60)); - record[0] = b; - } - - /** - * uniquely identifies a row/col - * to unencrypt: - * col= hashcode%maxcols - * row= hashcode/maxcols -1 - */ - protected long getHashCode() { - if (rw >= 0) - return col + ((rw + 1) * MAXCOLS); - else - return col + ((MAXROWS - rw + 1) * MAXCOLS); - } - - public static long getHashCode(int row, int col) { - return col + ((row + 1) * MAXCOLS); - } - - /** - * clear out object references in prep for closing workbook - */ - public void close() { - if (useReferenceTracker) removeFromRefTracker(); - useReferenceTracker = false; - super.close(); - if (refCell != null && refCell.length > 0 && refCell[0] != null) // clear out object references - ((XLSRecord) refCell[0]).close(); - refCell = null; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRef.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgRef.kt new file mode 100644 index 0000000..f307c2d --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRef.kt @@ -0,0 +1,1099 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.* +import io.starter.formats.cellformat.CellFormatFactory +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool + + +/** + * ptgRef is a reference to a single cell. It contains row and + * column information, plus a grbit to determine whether these + * values are relative or absolute. This grbit is, stupidly, but expectedly, + * encoded within the column value. + *

                      + * Offset      Name        Size    Contents
                      + * ----------------------------------------------------
                      + * 0           rw          2       the row
                      + * 2           grbitCol    2       (see following table)
                      + *
                      + * Only the low-order 14 bits specify the Col, the other bits specify
                      + * relative vs absolute for both the col or the row.
                      + *
                      + * Bits        Mask        Name    Contents
                      + * -----------------------------------------------------
                      + * 15          8000h       fRwRel  =1 if row offset relative,
                      + * =0 if otherwise
                      + * 14          4000h       fColRel =1 if col offset relative,
                      + * =0 if otherwise
                      + * 13-0        3FFFh       col     Ordinal column offset or number
                      +
                      * + * + * @see WorkBook + * + * @see Boundsheet + * + * @see Dbcell + * + * @see Row + * + * @see Cell + * + * @see XLSRecord + */ +open class PtgRef : GenericPtg, Ptg { + var rw: Int = 0 + // TODO: We actually are talking about 2 different notions of relativity: + // 1- Relativity based on shared formula parent formula row/col + // 2- Relative/Absolute in terms of row movement ($'s mean reference is ABSOLUTE) + // we are combining the two concepts erroneously + var fRwRel = true //true is relative, false is absolute (=$'s) + var fColRel = true + var col: Int = 0 + var is3dRef = false + protected set + private var cachedLocation: String? = null + + var formulaRow: Int = 0 + var formulaCol: Int = 0 + internal var refCell: Array? = arrayOfNulls(1) + var sheetname: String? = null + + protected var externalLink1 = 0 + protected var externalLink2 = 0 + /** + * Ptgs upkeep their mapping in reference tracker, however, some ptgs + * are components of other Ptgs, such as individual ptg cells in a PtgArea. These + * should not be stored in the RT. + */ + var useReferenceTracker = true + + internal var locax: String? = null + // String locstrax = null; + var hashcode = -1L + + open val isRefErr: Boolean + get() = false + + var isWholeRow = false + var isWholeCol = false // denotes a range which spans the entire row or column, a shorthand for checking end col or row # as this will vary between excel versions + + override val isOperand: Boolean + get() = true + + override val isReference: Boolean + get() = true + + /** + * return the human-readable String representation of + * this ptg -- if applicable + */ + override val string: String? + get() = this.location + + /** + * returns the String address of this ptg including sheet reference + * + * @return + */ + // AI PtgRefs do not have location info + val locationWithSheet: String + get() { + var ret = string + if (ret == null && parentRec.opcode == XLSRecord.AI) + return parentRec.toString() + + if (ret == null) + return "" + + if (ret.indexOf("!") > -1) + return ret + + ret = "$sheetname!$ret" + + return ret + } + + + /** + * returns the row/col ints for the ref + * + * @return + */ + open// if row truly references MAXROWS_BIFF8 comes out - + val rowCol: IntArray + get() { + val ret = intArrayOf(this.rw, this.col) + if (this.rw < 0) { + ret[0] = XLSConstants.MAXROWS_BIFF8 + this.isWholeCol = true + } + return ret + } + + + /** + * Returns the location of the Ptg as a string (ie c4) + * + * @see io.starter.formats.XLS.formulas.GenericPtg.getLocation + */ + /** + * Set the location of this PtgRef. This takes a location + * such as "a14", also can take a absolute location, such as $A14 + */ + override//cache + var location: String? + get() { + if (locax != null) + return locax + + val adjusted = this.intLocation + val s: String + if (this.isWholeCol) { + s = (if (fColRel) "" else "$") + ExcelTools.getAlphaVal(adjusted!![1]) + } else if (this.isWholeRow) { + s = (if (fRwRel) "" else "$") + (adjusted!![0] + 1) + } else { + if (rw < 0 || col < 0) + return PtgRefErr().toString() + + s = (if (fColRel) "" else "$") + ExcelTools.getAlphaVal(adjusted!![1]) + + (if (fRwRel) "" else "$") + (adjusted[0] + 1) + } + locax = s + return locax + } + set(address) { + locax = null + refCell = null + if (record != null) { + val s = ExcelTools.stripSheetNameFromRange(address) + setLocation(s) + locax = s[1] + } else { + Logger.logWarn("PtgRef.setLocation() failed: NO record data: $address") + } + } + + /** + * Get the location of this ptgRef as an int array {row, col}. 0 based + */ + override// the row is a relative location + // the column is a relative location + val intLocation: IntArray? + get() { + + this.setIsWholeRowCol() + var rowNew = rw + var colNew = col + try { + val isExcel2007 = this.parentRec.workBook!!.isExcel2007 + if (fRwRel) { + rowNew += formulaRow + } + if (fColRel) { + colNew += formulaCol + } + if (isWholeRow) { + if (!isExcel2007) + colNew = XLSConstants.MAXCOLS_BIFF8 + else + colNew = XLSConstants.MAXCOLS + } + if (isWholeCol) { + if (isExcel2007) + rowNew = XLSConstants.MAXROWS - 1 + else + rowNew = XLSConstants.MAXROWS_BIFF8 - 1 + } + + } catch (e: NullPointerException) { + } + + return intArrayOf(rowNew, colNew) + } + + /** + * Get the location of this ptgRef as an int array {row, col}. 0 based + * NOTE: this version of getIntLocation returns the actual or real coordinates + * This may be different from getIntLocation when rw designates MAXROWS - in these cases, + * this method will return real max rows + */ + // the row is a relative location + // the column is a relative location + val realIntLocation: IntArray + get() { + var rowNew = rw + var colNew = col + if (fRwRel) { + rowNew += formulaRow + } + if (fColRel) { + colNew += formulaCol + } + + if (isWholeCol || rowNew < 0) + try { + if (rowNew < 0) + isWholeCol = true + rowNew = this.parentRec.sheet!!.maxRow + } catch (e: Exception) { + } + + if (isWholeRow || colNew >= XLSConstants.MAXCOLS) + try { + colNew = this.parentRec.sheet!!.maxCol + } catch (e: Exception) { + } + + return intArrayOf(rowNew, colNew) + } + + /** + * Get the worksheet name this ptgref refers to + * + * @throws WorkSheetNotFoundException + */ + /** + * sets the sheetname for this + * + * @param sheetname + */ + open// reference on different sheet than parent + // no sheetname + //handle external references (OOXML-specific) + var sheetName: String? + @Throws(WorkSheetNotFoundException::class) + get() { + + if (locax != null) { + if (locax!!.indexOf("!") > -1) { + sheetname = locax!!.substring(0, locax!!.indexOf("!")) + } + } + if (sheetname == null && parentRec != null) { + if (parentRec.sheet != null) { + sheetname = parentRec.sheet!!.sheetName + } + } + + if (sheetname == null) { + return "" + } else { + if (externalLink1 > 0) { + if (sheetname!![0] == '\'') + sheetname = sheetname!!.substring(1, sheetname!!.length - 1) + sheetname = "[$externalLink1]$sheetname" + } + sheetname = GenericPtg.qualifySheetname(sheetname) + + } + return sheetname + } + set(sheetname) { + this.sheetname = sheetname + } + + override val length: Int + get() = Ptg.PTG_REF_LENGTH + + /** + * return truth of "reference is blank" + * + * @return + */ + override//getOpcode()==BLANK); + val isBlank: Boolean + get() { + refCells + return refCell!![0] == null || (refCell!![0] as XLSRecord).isBlank + } + + /** + * returns the value of the cell refereced by the PtgRef + */ + override// assume zero, which the vast majority of cases are + val value: Any? + get() { + refCells + var retValue: Any? = null + if (refCell!![0] != null) { + if (refCell!![0].formulaRec != null) { + val f = refCell!![0].formulaRec + retValue = f.calculateFormula() + return retValue + } else { + if (refCell!![0].dataType == "Float") { + retValue = refCell!![0].dblVal + return retValue + } else { + retValue = refCell!![0].internalVal + return retValue + } + } + } else { + try { + if (!this.parentRec.sheet!!.window2!!.showZeroValues) + return null + } catch (e: NullPointerException) { + } + + return Integer.valueOf(0) + } + } + + /** + * returns the value of the ptg formatted via the underlying cell's number format + * + * @return String underlying cell value formatted via cell's format pattern + */ + // assume zero, which the vast majority of cases are + val formattedValue: String + get() { + refCells + var retValue: Any? = null + val cell = refCell!![0] + + if (cell != null) { + if (cell.formulaRec != null) { + val f = cell.formulaRec + retValue = f.calculateFormula() + } else { + if (cell.dataType == "Float") { + retValue = cell.dblVal + } else { + retValue = cell.internalVal + } + } + return CellFormatFactory.fromPatternString( + cell.xfRec.formatPattern).format(retValue) + } else { + try { + if (!this.parentRec.sheet!!.window2!!.showZeroValues) + return "" + } catch (e: NullPointerException) { + } + + return "0" + } + } + + /** + * @return Returns the refCell. + */ + open val refCells: Array? + get() { + refCell = arrayOfNulls(1) + try { + var bs: Boundsheet? = null + if (sheetname != null && parentRec != null) { + bs = this.parentRec.workBook!!.getWorkSheetByName(sheetname) + } else if (parentRec != null) { + bs = parentRec.sheet + } + refCell[0] = bs!!.getCell(rw, col) + } catch (ex: Exception) { + } + + return refCell + } + + override// trap formulaRow, Col for relative PtgRefs + var parentRec: XLSRecord + get + set(f) { + parentRec = f + setRelativeRowCol() + } + + /** + * sets the row to be relative (relative is true) or absolute (relative is false) + *

                      absolute references do not shift upon row inserts or deletes + * + * @param boolean relative + */ + open var isRowRel: Boolean + get() = fRwRel + set(relative) { + if (fRwRel != relative) { + locax = null + fRwRel = relative + updateRecord() + } + } + + /** + * sets the column to be relative (relative is true) or absolute (relative is false) + *

                      absolute references do not shift upon column inserts or deletes + * + * @param boolean relative + */ + open var isColRel: Boolean + get() = fColRel + set(relative) { + if (fColRel != relative) { + locax = null + fColRel = relative + updateRecord() + } + } + + /** + * uniquely identifies a row/col + * to unencrypt: + * col= hashcode%maxcols + * row= hashcode/maxcols -1 + */ + protected open val hashCode: Long + get() = if (rw >= 0) + (col + (rw + 1) * XLSConstants.MAXCOLS).toLong() + else + (col + (XLSConstants.MAXROWS - rw + 1) * XLSConstants.MAXCOLS).toLong() + + + override fun equals(ob: Any?): Boolean { + return ob!!.hashCode() == this.hashCode() + } + + constructor(rowcol: IntArray, x: XLSRecord, useRefTracker: Boolean) : this() { + parentRec = x + this.useReferenceTracker = useRefTracker + setLocation(rowcol) + updateRecord() + } + + + /** + * This constructor is for programmatic creation of Ptg's + * in this case we do not have the ptgid, just the refereced location + */ + constructor(location: String, x: XLSRecord, utilizeRefTracker: Boolean) { + this.useReferenceTracker = utilizeRefTracker + opcode = 0x44 //0x24; defaulting to value operand + record = ByteArray(5) + record[0] = opcode + parentRec = x // MUST set before setLocation also sets formulaRow ... + this.location = location + this.setIsWholeRowCol() + if (useReferenceTracker) + addToRefTracker() + } + + + /** + * + * @param id + */ + + /** + * set the Ptg Id type to one of: + * VALUE, REFERENCE or Array + *

                      The Ptg type is important for certain + * functions which require a specific type of operand + */ + open fun setPtgType(type: Short) { + when (type) { + Ptg.VALUE -> opcode = 0x44 + Ptg.REFERENCE -> opcode = 0x24 + Ptg.ARRAY -> opcode = 0x64 + } + record[0] = opcode + } + + + /** + * This constructor is for programmatic creation of Ptg's + * in this case we do not have the ptgid, just the refereced location + * + * + * this version sets the value of useReferenceTracker to avoid multiple entries due to area parent + */ + constructor(bin: ByteArray, x: XLSRecord, utilizeRefTracker: Boolean) : this() { + this.useReferenceTracker = utilizeRefTracker + parentRec = x //MUST DO BEFORE INIT ... also sets formulaRow ... + init(bin) + if (useReferenceTracker) + addToRefTracker() // TODO: check subreference issue (if it's not a 'real' ptg) + } + + /** + * default constructor + */ + constructor() { + // 24H (tRefR), 44H (tRefV), 64H (tRefA) + opcode = 0x44 // default to value operand + record = ByteArray(5) + record[0] = opcode + } + + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + this.populateVals() + } + + /** + * parse all the values out of the byte array and + * populate the classes values + */ + protected open fun populateVals() { + rw = readRow(record[1], record[2]) + val column = ByteTools.readShort(record[3].toInt(), record[4].toInt()) + // is the Row relative? + fRwRel = column and 0x8000 == 0x8000 + // is the Column relative? + fColRel = column and 0x4000 == 0x4000 + col = (column and 0x3fff).toShort().toInt() + setRelativeRowCol() // set formulaRow/Col for relative references if necessary + this.setIsWholeRowCol() + hashcode = hashCode + } + + + override fun toString(): String? { + return string + } + + /** + * Clears the location cache when needed + */ + fun clearLocationCache() { + locax = null + } + + /** + * Does this ref reference an entire row (ie $1); + * + * @return + */ + private fun referencesEntireRow(): Boolean { + val isExcel2007 = this.parentRec.workBook!!.isExcel2007 + var colNew = col + if (fColRel) { // the row is a relative location + colNew += formulaRow + } + if (colNew < 0) { // have to assume that it's a wholeRow even if 2007 + return true + } else if (colNew >= XLSConstants.MAXCOLS_BIFF8 - 1 && !isExcel2007) { + return true + } + return if (this.cachedLocation != null && isExcel2007) { + this.locationStringReferencesEntireRow() + } else colNew == XLSConstants.MAXCOLS_BIFF8 - 1 && isExcel2007 + // This is unfortunately a bit of a hack due to biff 8 incompatibilies + + } + + /** + * Check if the cached string location referrs to a full row + * + * @return + */ + private fun locationStringReferencesEntireRow(): Boolean { + if (this.cachedLocation != null) { + val res = ExcelTools.getRowColFromString(cachedLocation!!) + return res[1] < 0 + } + return false + } + + /** + * Does this ref reference an entire col (ie $A); + * + * @return + */ + private fun referencesEntireCol(): Boolean { + var rowNew = rw + val isExcel2007 = this.parentRec.workBook!!.isExcel2007 + if (fRwRel) { // the row is a relative location + rowNew += formulaRow + } + if (rowNew < 0) { + return true + } else if (rowNew >= XLSConstants.MAXROWS_BIFF8 - 1 && !isExcel2007) { + rowNew = -1 + return true + } + return false + } + + /** + * Inspects the record to determin if it references whole + * rows or columns and sets the values as required. + * + * @return + */ + protected fun setIsWholeRowCol() { + this.isWholeCol = referencesEntireCol() + this.isWholeRow = referencesEntireRow() + } + + /** + * set Ptg to parsed location + * + * @param loc String[] sheet1, range, sheet2, exref1, exref2 + */ + open fun setLocation(loc: Array) { + if (useReferenceTracker) + removeFromRefTracker() + locax = null + sheetname = loc[0] + var addr = loc[1] + cachedLocation = addr + fRwRel = true + fColRel = true + if (addr.indexOf("$") == -1) { // both row and col are relative refs, meaning moves/copies will change ref + // relative link + if (addr != "#REF!" && addr != "") { + val res = ExcelTools.getRowColFromString(addr) + col = res[1] + rw = res[0] + } else { + col = -1 + rw = -1 + } + } else { + // absolute reference + if (addr.substring(0, 1).equals("$", ignoreCase = true)) { + fColRel = false + addr = addr.substring(1) + } + if (addr.indexOf("$") != -1) { + fRwRel = false + addr = StringTool.strip(addr, "$") + } + var res: IntArray? = null + try { + res = ExcelTools.getRowColFromString(addr) + col = res!![1] + rw = res[0] + if (col == -1 || rw == -1) { // if wholerow or wholecol, must be absolute + fColRel = false + fRwRel = false + } + } catch (ie: IllegalArgumentException) { //is it a wholerow/wholecol issue? + if (Character.isDigit(addr[0])) { //assume wholecol ref + col = XLSConstants.MAXCOLS_BIFF8 - 1 + rw = Integer.valueOf(addr).toInt() - 1 + fColRel = false + fRwRel = false + } else { //wholerow ref? + rw = -1 + col = ExcelTools.getIntVal(addr) + fColRel = false + fRwRel = false + } + } + + } + if (col == -1) + isWholeRow = true + if (rw == -1) + isWholeCol = true + this.setIsWholeRowCol() + this.updateRecord() + hashcode = hashCode + // trap OOXML external reference link, if any + if (loc[3] != null) + externalLink1 = Integer.valueOf(loc[3].substring(1, loc[3].length - 1)).toInt() + if (loc[4] != null) + externalLink2 = Integer.valueOf(loc[4].substring(1, loc[4].length - 1)).toInt() + if (useReferenceTracker) { + if (!isRefErr && !this.isWholeCol && !this.isWholeRow) + addToRefTracker() + } + } + + + /** + * Set the location of this PtgRef. This takes a location + * such as {1,2} + */ + open fun setLocation(rowcol: IntArray) { + locax = null + cachedLocation = null + if (record != null) { + if (useReferenceTracker) + removeFromRefTracker() + rw = rowcol[0] + col = rowcol[1] + fRwRel = true // default + fColRel = true + this.updateRecord() + hashcode = hashCode + if (useReferenceTracker) + addToRefTracker() + } else { + Logger.logWarn("PtgRef.setLocation() failed: NO record data: $rowcol") + } + } + + /** + * set the location of this PtgRef + * + * @param rowcol int[] rowcol + * @param bRowRel true if row is relative (i.e. A1 not A$1) + * @param bColRel true if col is relative (i.e. A1 not $A1) + */ + fun setLocation(rowcol: IntArray, bRowRel: Boolean, bColRel: Boolean) { + locax = null + cachedLocation = null + if (record != null) { + if (useReferenceTracker) removeFromRefTracker() + rw = rowcol[0] + col = rowcol[1] + fRwRel = bRowRel + fColRel = bColRel + this.updateRecord() + if (useReferenceTracker) + addToRefTracker() + } else { + Logger.logWarn("PtgRef.setLocation() failed: NO record data: $rowcol") + } + } + + /** + * Updates the record bytes so it can be pulled back out. + */ + override fun updateRecord() { + val tmp = ByteArray(5) + tmp[0] = record[0] + val brow = ByteTools.cLongToLEBytes(rw) + System.arraycopy(brow, 0, tmp, 1, 2) + if (fRwRel) { + col = (0x8000 or col).toShort().toInt() + } + if (fColRel) { + col = (0x4000 or col).toShort().toInt() + } + val bcol = ByteTools.cLongToLEBytes(col) + if (col == -1) { // KSC: what excel expects + bcol[1] = 0 + } + System.arraycopy(bcol, 0, tmp, 3, 2) + + record = tmp + if (parentRec != null) { + if (this.parentRec is Formula) + (this.parentRec as Formula).updateRecord() + else if (this.parentRec is Name) + (this.parentRec as Name).updatePtgs() + } + + col = col.toShort() and 0x3FFF //get lower 14 bits which represent the actual column; + } + + fun changeLocation(newLoc: String, f: Formula): Boolean { + var newLoc = newLoc + locax = null + var ptg: Ptg? = null + var z = -1 + try { + z = ExpressionParser.getExpressionLocByPtg(this, f.expression!!) + ptg = f.expression!![z] as Ptg + } catch (e: Exception) { + + } + + val unstripped = newLoc + + if (newLoc.indexOf("!") > -1) newLoc = newLoc.substring(newLoc.indexOf("!") + 1) + if (unstripped.indexOf(":") > 0) { // then either PtgRef3d or PtgArea + if (unstripped.indexOf("!") > unstripped.indexOf(":")) { // than it's a PtgRef3d or PtgArea3d + if (unstripped.indexOf(":") != unstripped.lastIndexOf(":")) { // it's a PtgArea3d ala Sheet1:Sheet3!A1:D1 + val pta3 = PtgArea3d() + pta3.location = unstripped + ptg = pta3 + } else { // it's a PtgRef3d ala Sheet1!Sheet3:A1 + val prd = PtgRef3d() + prd.parentRec = f + prd.location = unstripped + ptg = prd + } + // no sheet ref ... + } else { // it's a PtgArea3d (according to Excel's Ai recs !!) + val pta = PtgArea() + pta.parentRec = f + pta.location = unstripped + ptg = pta + } + } else if (ptg != null) { + // it's a single location + if (unstripped != "") { + ptg.parentRec = f + ptg.location = unstripped + } else { + ptg = PtgRef3d() + ptg.parentRec = f + } + } else { // ptg is null, create a new one + ptg = PtgRef() + ptg.parentRec = f + ptg.location = unstripped + } + if (z != -1) + f.expression!![z] = ptg // update expression with new Ptg + else + f.expression!!.add(ptg) + return true + } + + /** + * removes this reference from the tracker... + * + * + * used mostly when we've updated the ref and want + * to re-register it. + */ + open fun removeFromRefTracker() { + try { + if (parentRec != null) { + parentRec.workBook!!.refTracker!!.removeCellRange(this) + if (parentRec.opcode == XLSConstants.FORMULA) (parentRec as Formula).setCachedValue(null) + } + } catch (ex: Exception) { + // no need to error here, sometimes this is called before its in Logger.logErr("PtgRef.removeFromRefTracker() failed.", ex); + } + + } + + /** + * add this reference to the ReferenceTracker... this + * is crucial if we are to update this Ptg when cells + * are changed or added... + */ + open fun addToRefTracker() { + //Logger.logInfo("Adding :" + this.toString() + " to tracker"); + try { + if (parentRec != null) + parentRec.workBook!!.refTracker!!.addCellRange(this) + } catch (ex: Exception) { + Logger.logErr("PtgRef.addToRefTracker() failed.", ex) + } + + } + + /** + * update existing tracked ptg with new parent in reference tracker + * + * @param parent + */ + fun updateInRefTracker(parent: XLSRecord?) { + try { + if (parent != null) + parent.workBook!!.refTracker!!.updateInRefTracker(this, parent) + } catch (ex: Exception) { + Logger.logErr("updateInRefTracker() failed.", ex) + } + + } + + /** + * set the formulaRow and formulaCol for relatively-referenced PtgRefs + */ + fun setRelativeRowCol() { + if (fRwRel || fColRel) { + var opc: Short = 0 + if (parentRec != null) + opc = parentRec.opcode + // protocol for shared formulas, conditional formatting, data validity and defined names only (type B cell addresses!) + if (opc == XLSConstants.SHRFMLA || opc == XLSConstants.DVAL) { + this.formulaRow = parentRec.rowNumber + this.formulaCol = parentRec.colNumber.toInt() + } + } + } + + /** + * set this Ptg to an External Location - used when copying a sheet from another workbook + * + * @param f parent formula rec + */ + fun setExternalReference(externalWorkbook: String) { + if (this is PtgArea3d) { + val ptg = this + var b = parentRec.workBook + if (b == null) + b = parentRec.sheet!!.workBook + val ixti = b!!.externSheet!!.addExternalSheetRef(externalWorkbook, ptg.sheetName) //20080714 KSC: May not reflect external reference! this.sheetname); + ptg.setIxti(ixti) + if (ptg.firstPtg != null) { // it's not a Ref3d + ptg.firstPtg!!.updateRecord() + ptg.lastPtg!!.updateRecord() + } + ptg.updateRecord() + } else if (this is PtgRef3d) { + var b = parentRec.workBook + val pr = this + if (b == null) + b = parentRec.sheet!!.workBook + val ixti = b!!.externSheet!!.addExternalSheetRef(externalWorkbook, pr.sheetName) //20080714 KSC: May not reflect external reference! this.sheetname); + pr.setIxti(ixti) + } else { // TODO: convert to ref3d? + Logger.logWarn("PtgRef.setExternalReference: unable to convert ref") + } + } + + fun setArrayTypeRef() { + val b = (record[0] or 0x60).toByte() + record[0] = b + } + + /** + * clear out object references in prep for closing workbook + */ + override fun close() { + if (useReferenceTracker) removeFromRefTracker() + useReferenceTracker = false + super.close() + if (refCell != null && refCell!!.size > 0 && refCell!![0] != null) + // clear out object references + (refCell!![0] as XLSRecord).close() + refCell = null + } + + companion object { + /** + * + */ + private val serialVersionUID = -7776520933300730470L + + /** + * given an address string, parse and assign to the appropriate PtgRef-type object + *

                      #REF! 's return either PtgRefErr or PtgRefErr3d + *

                      Ranges return either PtgArea or PtgArea3d + *

                      Single addresses return either PtgRef or PtgRef3d + *

                      NOTE: This method does not extract names embedded within the address string + * + * @param address + * @param parent parent record to assign the ptg to + * @return + */ + fun createPtgRefFromString(address: String, parent: XLSRecord): Ptg { + try { + val s = ExcelTools.stripSheetNameFromRange(address) + val sh1 = s[0] + val range = s[1] + val ptg: Ptg + if (range == null || range == "#REF!" || sh1 != null && sh1 == "#REF") { + if (sh1 != null) { + val pe3 = PtgRefErr3d() + pe3.parentRec = parent + pe3.setLocation(s) + return pe3 + } else { + val pe = PtgRefErr() + pe.parentRec = parent + pe.setLocation(s) + return pe + } + } + val bk = parent.workBook + + + val sht = "((?:\\\\?+.)*?!)?+" + val rangeMatch = "(.*(:).*){2,}?" //matches 2 or more range ops (:'s) + val opMatch = "(.*([ ,]).*)+" //matches union or isect op ( " " or ,) + val m = "$sht(($opMatch)|($rangeMatch))" + // is address a complex range?? + if (address.matches(m.toRegex()) || range.indexOf("(") > -1) { + //NOTE: this can be a MemFunc OR a MemArea -- + // PtgMemFunc= a NON-CONSTANT cell address, cell range address or cell range list + // Whenever one operand of the reference subexpression is a function, a defined name, a 3D + // reference, or an external reference (and no error occurs), a PtgMemFunc token is used. + // PtgMemArea= constant cell address, cell range address, or cell range list on the same sheet + val pmf = PtgMemFunc() + pmf.parentRec = parent + pmf.location = address // TODO HANDLE FUNCTION MEMFUNCS ALA OFFSET(x,y,0):OFFSET(x,y,0) + ptg = pmf + } else if (range.indexOf(":") > 0) { // it's a range, either PtgRef3d or PtgArea3d + val ops = StringTool.getTokensUsingDelim(range, ":") + if (bk!!.getName(ops[0]) != null || bk.getName(ops[1]) != null) { + val pmf = PtgMemFunc() + pmf.parentRec = parent + pmf.location = address + ptg = pmf + } else if (sh1 != null) { + val rc = ExcelTools.getRowColFromString(ops[0]) // see if a wholerow/wholecol ref + if (!(ops[0] == ops[1] && rc[0] != -1 && rc[1] != -1)) { + val pta = PtgArea3d() + pta.parentRec = parent + pta.setLocation(s) + ptg = pta + } else { + ptg = PtgRef3d() + ptg.setPtgType(Ptg.REFERENCE) + ptg.parentRec = parent + (ptg as PtgRef).useReferenceTracker = false + ptg.setLocation(s) + (ptg as PtgRef).useReferenceTracker = true + ptg.addToRefTracker() + } + } else { + val pa = PtgArea() + pa.parentRec = parent + pa.useReferenceTracker = false + pa.setLocation(s) + pa.useReferenceTracker = true + pa.addToRefTracker() + ptg = pa + } + } else { // it's a single ref NOT a range e.g. Sheet1!A1 + if (sh1 != null) { + ptg = PtgRef3d() + ptg.setPtgType(Ptg.REFERENCE) + ptg.parentRec = parent + (ptg as PtgRef).useReferenceTracker = false + ptg.setLocation(s) + (ptg as PtgRef).useReferenceTracker = true + ptg.addToRefTracker() + } else { + val pr = PtgRef() + pr.parentRec = parent + pr.useReferenceTracker = false + pr.setLocation(s) + pr.useReferenceTracker = true + pr.addToRefTracker() + ptg = pr + } + } + return ptg + } catch (e: Exception) { // any error in parsing return a referr -- makes sense!!! + val pe3 = PtgRefErr3d() + pe3.parentRec = parent + return pe3 + } + + } + + fun getHashCode(row: Int, col: Int): Long { + return (col + (row + 1) * XLSConstants.MAXCOLS).toLong() + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.java deleted file mode 100644 index 89d87d6..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.java +++ /dev/null @@ -1,458 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.*; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.FastAddVector; -import io.starter.toolkit.Logger; - - -/** - * A BiffRec range spanning 3rd dimension of WorkSheets. - * ` - *

                      - * offset  name        size    contents
                      - * ---
                      - * 0       ixti       2        Index to Externsheet Sheet Record
                      - * 2       row         2       The row
                      - * 4       grCol       2       The col, or the col offset (see next table)
                      - *
                      - * the low-order 8 bytes store the col numbers.  The 2 MSBs specify whether the row
                      - * and col refs are relative or absolute.
                      - *
                      - * bits    mask        name    content
                      - * ---
                      - * 15      8000h       fRwRel  =1 if the row is relative, 0 if absolute
                      - * 14      4000h       fColRel =1 if the col is relative, 0 if absolute
                      - * 13-8    3F00h       (reserved)
                      - * 7-0     00FFh       col     the col number or col offset (0-based)
                      - *
                      - * For 3D references, the tokens contain a negative EXTERNSHEET index, indicating a reference into the own workbook.
                      - * The absolute value is the one-based index of the EXTERNSHEET record that contains the name of the first sheet. The
                      - * tokens additionally contain absolute indexes of the first and last referenced sheet. These indexes are independent of the
                      - * EXTERNSHEET record list. If the referenced sheets do not exist anymore, these indexes contain the value FFFFH (3D
                      - * reference to a deleted sheet), and an EXTERNSHEET record with the special name <04H> (own document) is used.
                      - * Each external reference contains the positive one-based index to an EXTERNSHEET record containing the URL of the
                      - * external document and the name of the sheet used. The sheet index fields of the tokens are not used.
                      - *
                      - * @see Ptg
                      - * @see Formula
                      - */
                      -public class PtgRef3d extends PtgRef implements Ptg, IxtiListener {
                      -
                      -    boolean quoted = false;
                      -    public short ixti;
                      -
                      -    private static final long serialVersionUID = -441121385905948168L;
                      -
                      -
                      -    public void setParentRec(XLSRecord r) {
                      -        super.setParentRec(r);
                      -    }
                      -
                      -    public void addListener() {
                      -        try {
                      -            getParentRec().getWorkBook().getExternSheet().addPtgListener(this);
                      -        } catch (Exception e) {
                      -            // no need to output here.  NullPointer occurs when a ref has an invalid ixti, such as when a sheet was removed  Worksheet exception could never really happen.
                      -        }
                      -    }
                      -
                      -
                      -    /**
                      -     * @return Returns the ixti.
                      -     */
                      -    public short getIxti() {    // only valid for 3d refs
                      -        return ixti;
                      -    }
                      -
                      -    /**
                      -     * returns true if this PtgRef3d's ixti refers to an external sheet reference
                      -     *
                      -     * @return
                      -     */
                      -    public boolean isExternalLink() {
                      -        try {
                      -            return (getParentRec().getWorkBook().getExternSheet().getIsExternalLink(ixti));
                      -        } catch (Exception e) {
                      -            return false;
                      -        }
                      -    }
                      -
                      -
                      -    public int getLength() {
                      -        return PTG_REF3D_LENGTH;
                      -    }
                      -
                      -    public boolean getIsOperand() {
                      -        return true;
                      -    }
                      -
                      -    public boolean getIsReference() {
                      -        return true;
                      -    }
                      -    //true is relative, false is absolute
                      -
                      -    /**
                      -     * 0x3A Reference class token: The reference address itself, independent of the cell contents.
                      -     * • 0x5A Value class token: A value (a constant, a function result, or one specific value from a dereferenced cell range).
                      -     * • 0x7A Array class token: An array of values (array of constant values, an array function result, or all values of a cell range).
                      -     */
                      -    public PtgRef3d() {
                      -        record = new byte[PTG_REF3D_LENGTH];
                      -        ptgId = 0x5A;  // id varies with type of token see above and setPtgType below
                      -        record[0] = ptgId; // ""
                      -        this.is3dRef = true;
                      -    }
                      -
                      -    /**
                      -     * set the Ptg Id type to one of:
                      -     * VALUE, REFERENCE or Array
                      -     * 
                      The Ptg type is important for certain - * functions which require a specific type of operand - */ - public void setPtgType(short type) { - switch (type) { - case VALUE: - ptgId = 0x5A; - break; - case REFERENCE: - ptgId = 0x3A; - break; - case Ptg.ARRAY: - ptgId = 0x7A; - break; - } - record[0] = ptgId; - } - - public PtgRef3d(boolean addToRefTracker) { - this.setUseReferenceTracker(addToRefTracker); - ptgId = 0x5A; // TODO: id varies with type of token see above - record[0] = ptgId; // "" - this.is3dRef = true; - - } - - public PtgRef3d(String addr, short _ixti) { - this(); - setLocation(addr); - this.is3dRef = true; - } - - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - populateVals(); - } - - /** - * get the worksheet that this ref is on - * for some reason this seems to be backwards in Ref3d - */ - public Boundsheet getSheet(WorkBook b) { - Boundsheet[] bsa = b.getExternSheet().getBoundSheets(ixti); - if (bsa != null && bsa[0] == null) { // 20080303 KSC: catch error - // try harder... - if (parent_rec.getSheet() != null) { - return parent_rec.getSheet(); // sheetless names belong to parent rec - } else { - if (b.getFactory().getDebugLevel() > 1) // 20080925 KSC - Logger.logErr("PtgRef3d.getSheet: Unresolved External or Deleted Sheet Reference Found"); // [BUGTRACKER 1836] Claritas extenXLS22677.rec (Deleted Sheet/Named Range causes errant value in B3) - return null; //20080805 KSC: Don't just return the 1st sheet, may be wrong, deleted, etc! - } - } else if (bsa == null) - return null; - return bsa[0]; - } - - /** - * set Ptg to parsed location - * - * @param loc String[] sheet1, range, sheet2, exref1, exref2 - */ - public void setLocation(String[] s) { - if (useReferenceTracker && !getIsRefErr()) - this.getParentRec().getWorkBook().getRefTracker().removeCellRange(this); - sheetname = null; - if (s[0] != null) { - sheetname = s[0]; - } else { - try { // if not provided, assume that parent rec sheet is correct - sheetname = this.getParentRec().getSheet().getSheetName(); - } catch (NullPointerException e) { - } - } - String loc = s[1]; - if (sheetname != null) { - loc = sheetname + "!" + loc; // loc uses quoted vers of sheet - if (sheetname.indexOf("'") == 0) { - sheetname = sheetname.substring(1, sheetname.length() - 1); - quoted = true; - } - } - if (sheetname != null) { - Externsheet xsht = null; - WorkBook b = parent_rec.getWorkBook(); - if (b == null) - b = parent_rec.getSheet().getWorkBook(); - try { - - int boundnum = b.getWorkSheetByName(sheetname).getSheetNum(); - xsht = b.getExternSheet(); - try { - int xloc = xsht.insertLocation(boundnum, boundnum); - setIxti((short) xloc); - } catch (Exception e) { - Logger.logWarn("PtgRef3d.setLocation could not update Externsheet:" + e.toString()); - } - } catch (WorkSheetNotFoundException e) { - try { - xsht = b.getExternSheet(); - int boundnum = xsht.getXtiReference(s[0], s[0]); - if (boundnum == -1) { // can't resolve - this.setIxti((short) xsht.insertLocation(boundnum, boundnum)); - } else { - this.setIxti((short) boundnum); - } - } catch (Exception ex) { - } - } - } - super.setLocation(s); - } - - - /** - * Set Location can take either a local page address (ie A54) or - * a reference to a page and location(ie Sheet2!A22). It then changes - * the location reference of the Ptg. - * - * @see io.starter.formats.XLS.formulas.Ptg#setLocation(java.lang.String) - */ - public void setLocation(String address) { - String[] s = ExcelTools.stripSheetNameFromRange(address); - setLocation(s); - } - - /** - * Throw this data into a ptgref's - */ - public void populateVals() { - ixti = ByteTools.readShort(record[1], record[2]); - this.sheetname = this.getSheetName(); - - rw = readRow(record[3], record[4]); - short column = ByteTools.readShort(record[5], record[6]); - // is the Row relative? - fRwRel = (column & 0x8000) == 0x8000; - // is the Column relative? - fColRel = (column & 0x4000) == 0x4000; - col = (short) (column & 0x3fff); - setRelativeRowCol(); // set formulaRow/Col for relative references if necessary - this.getIntLocation(); // sets the wholeRow and/or wholeCol flag for certain refs - this.hashcode = super.getHashCode(); - } - - - /** - * Set the location of this PtgRef. This takes a location - * such as "a14" - */ - public void setLocation(String address, short ix) { - ixti = ix; - String[] s = ExcelTools.stripSheetNameFromRange(address); - this.setLocation(s); - } - - public String toString() { - String ret = ""; - try { - ret = getLocation(); - if ((ret.indexOf("!") == -1) && (sheetname != null)) { // prepend sheetname - if (sheetname.indexOf(' ') == -1 && sheetname.charAt(0) != '\'') // 20081211 KSC: Sheet names with spaces must have surrounding quotes - ret = sheetname + "!" + ret; - else - ret = "'" + sheetname + "'!" + ret; - } - } catch (Exception ex) { - Logger.logErr("PtgRef3d.toString() failed", ex); - } - return ret; - } - - public void setIxti(short ixf) { - if (ixti != ixf) { - ixti = ixf; - // this seems to be only one byte... - if (record != null) { - record[1] = (byte) ixf; - } - updateRecord(); - } - } - - /** - * Change the sheet reference to the passed in boundsheet - * - * @see io.starter.formats.XLS.formulas.PtgArea3d#setReferencedSheet(io.starter.formats.XLS.Boundsheet) - */ - public void setReferencedSheet(Boundsheet b) { - int boundnum = b.getSheetNum(); - Externsheet xsht = b.getWorkBook().getExternSheet(true); - //TODO: add handling for multi-sheet reference. Already handled in externsheet - try { - int xloc = xsht.insertLocation(boundnum, boundnum); - setIxti((short) xloc); - this.sheetname = null; // 20100218 KSC: RESET - this.getSheetName(); - locax = null; - } catch (WorkSheetNotFoundException e) { - Logger.logErr("Unable to set referenced sheet in PtgRef3d " + e); - } - } - - /** - * Returns the location of the Ptg as a string, including sheet name - */ - public String getLocation() { - String ret = super.getLocation(); - if (ret.indexOf("!") == -1) { // doesn't have a sheet ref - // NOTE: Our tests error when PtgRefs have fully qualified range syntax - if (sheetname == null) - sheetname = this.getSheetName(); - if (this.sheetname != null) { - if (sheetname.equals("#REF!")) - return sheetname + ret; - sheetname = qualifySheetname(sheetname); - return sheetname + "!" + ret; // PtgRef does not have ixti - } - } - return ret; - } - - /** - * Updates the record bytes so it can be pulled back out. - */ - public void updateRecord() { - byte[] tmp = new byte[PTG_REF3D_LENGTH]; - tmp[0] = record[0]; - byte[] ix = ByteTools.shortToLEBytes(ixti); - System.arraycopy(ix, 0, tmp, 1, 2); - byte[] brow = ByteTools.cLongToLEBytes(rw); - System.arraycopy(brow, 0, tmp, 3, 2); - if (fRwRel) { - col = (short) (0x8000 | col); - } - if (fColRel) { - col = (short) (0x4000 | col); - } - byte[] bcol = ByteTools.cLongToLEBytes(col); - if (col == -1) { // KSC: what excel expects - bcol[1] = 0; - } - System.arraycopy(bcol, 0, tmp, 5, 2); - record = tmp; - if (parent_rec != null) { - if (this.parent_rec instanceof Formula) - ((Formula) this.parent_rec).updateRecord(); - else if (this.parent_rec instanceof Name) - ((Name) this.parent_rec).updatePtgs(); - } - - col = (short) col & 0x3FFF; //get lower 14 bits which represent the actual column; - } - - - public Boundsheet getSheet() { - if (parent_rec != null) { - WorkBook wb = parent_rec.getWorkBook(); - if (wb != null && wb.getExternSheet() != null) { - Boundsheet[] bsa = wb.getExternSheet().getBoundSheets(this.ixti); - if (bsa == null || bsa[0] == null) {// 20080303 KSC: Catch Unresolved External refs - if (parent_rec instanceof Formula) - Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet in Formula " + parent_rec.getCellAddressWithSheet()); - else if (parent_rec instanceof Name) - Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet in Name " + ((Name) parent_rec).getName()); - else - Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet for " + parent_rec.getCellAddressWithSheet()); - return null; - } - return bsa[0]; - } - } - return null; - } - - /** - * return the sheet name for this 3d reference - */ - public String getSheetName() { - if (this.sheetname == null) { - if (parent_rec != null) { - WorkBook wb = parent_rec.getWorkBook(); - if (wb != null && wb.getExternSheet() != null) { // 20080306 KSC: new way is to get sheet names rather than sheets as can be external refs - String[] sheets = wb.getExternSheet().getBoundSheetNames(this.ixti); - if (sheets != null && sheets[0] != null) - sheetname = sheets[0]; - } - } - } - return sheetname; - } - - - /** - * @return Returns the refCell. - */ - public BiffRec[] getRefCells() { - if (sheetname == null) sheetname = this.getSheetName(); - refCell = super.getRefCells(); - return refCell; - } - - /** - * PtgRef's have no sub-compnents - */ - public Ptg[] getComponents() { - return null; // only one - } - - /** - * return the ptg components for a certain column within a ptgArea() - * - * @param colNum - * @return all Ptg's within colNum - */ - public Ptg[] getColComponents(int colNum) { - FastAddVector v = new FastAddVector(); - int[] x = this.getIntLocation(); - if (x[1] == colNum) v.add(this); - PtgRef[] pref = new PtgRef[v.size()]; - v.toArray(pref); - return pref; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.kt new file mode 100644 index 0000000..b9c5895 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRef3d.kt @@ -0,0 +1,475 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.* +import io.starter.toolkit.ByteTools +import io.starter.toolkit.FastAddVector +import io.starter.toolkit.Logger + + +/** + * A BiffRec range spanning 3rd dimension of WorkSheets. + * ` + * + *
                      + * offset  name        size    contents
                      + * ---
                      + * 0       ixti       2        Index to Externsheet Sheet Record
                      + * 2       row         2       The row
                      + * 4       grCol       2       The col, or the col offset (see next table)
                      + *
                      + * the low-order 8 bytes store the col numbers.  The 2 MSBs specify whether the row
                      + * and col refs are relative or absolute.
                      + *
                      + * bits    mask        name    content
                      + * ---
                      + * 15      8000h       fRwRel  =1 if the row is relative, 0 if absolute
                      + * 14      4000h       fColRel =1 if the col is relative, 0 if absolute
                      + * 13-8    3F00h       (reserved)
                      + * 7-0     00FFh       col     the col number or col offset (0-based)
                      + *
                      + * For 3D references, the tokens contain a negative EXTERNSHEET index, indicating a reference into the own workbook.
                      + * The absolute value is the one-based index of the EXTERNSHEET record that contains the name of the first sheet. The
                      + * tokens additionally contain absolute indexes of the first and last referenced sheet. These indexes are independent of the
                      + * EXTERNSHEET record list. If the referenced sheets do not exist anymore, these indexes contain the value FFFFH (3D
                      + * reference to a deleted sheet), and an EXTERNSHEET record with the special name <04H> (own document) is used.
                      + * Each external reference contains the positive one-based index to an EXTERNSHEET record containing the URL of the
                      + * external document and the name of the sheet used. The sheet index fields of the tokens are not used.
                      + *
                      + * @see Ptg
                      + *
                      + * @see Formula
                      +
                      */ +open class PtgRef3d : PtgRef, Ptg, IxtiListener { + + internal var quoted = false + var ixti: Short = 0 + + + override var parentRec: XLSRecord + get + set(r) { + super.parentRec = r + } + + /** + * returns true if this PtgRef3d's ixti refers to an external sheet reference + * + * @return + */ + val isExternalLink: Boolean + get() { + try { + return parentRec.workBook!!.externSheet!!.getIsExternalLink(ixti.toInt()) + } catch (e: Exception) { + return false + } + + } + + + override val length: Int + get() = Ptg.PTG_REF3D_LENGTH + + override val isOperand: Boolean + get() = true + + override val isReference: Boolean + get() = true + + /** + * Returns the location of the Ptg as a string, including sheet name + */ + /** + * Set Location can take either a local page address (ie A54) or + * a reference to a page and location(ie Sheet2!A22). It then changes + * the location reference of the Ptg. + * + * @see io.starter.formats.XLS.formulas.Ptg.setLocation + */ + override// doesn't have a sheet ref + // NOTE: Our tests error when PtgRefs have fully qualified range syntax + // PtgRef does not have ixti + var location: String? + get() { + val ret = super.location + if (ret!!.indexOf("!") == -1) { + if (sheetname == null) + sheetname = this.sheetName + if (this.sheetname != null) { + if (sheetname == "#REF!") + return sheetname!! + ret!! + sheetname = GenericPtg.qualifySheetname(sheetname) + return "$sheetname!$ret" + } + } + return ret + } + set(address) { + val s = ExcelTools.stripSheetNameFromRange(address) + setLocation(s) + } + + + // 20080303 KSC: Catch Unresolved External refs + val sheet: Boundsheet? + get() { + if (parentRec != null) { + val wb = parentRec.workBook + if (wb != null && wb.externSheet != null) { + val bsa = wb.externSheet!!.getBoundSheets(this.ixti.toInt()) + if (bsa == null || bsa[0] == null) { + if (parentRec is Formula) + Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet in Formula " + parentRec.cellAddressWithSheet) + else if (parentRec is Name) + Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet in Name " + (parentRec as Name).name) + else + Logger.logErr("PtgRef3d.getSheet: Unresolved External Worksheet for " + parentRec.cellAddressWithSheet) + return null + } + return bsa[0] + } + } + return null + } + + /** + * return the sheet name for this 3d reference + */ + override// 20080306 KSC: new way is to get sheet names rather than sheets as can be external refs + var sheetName: String? + get() { + if (this.sheetname == null) { + if (parentRec != null) { + val wb = parentRec.workBook + if (wb != null && wb.externSheet != null) { + val sheets = wb.externSheet!!.getBoundSheetNames(this.ixti.toInt()) + if (sheets != null && sheets[0] != null) + sheetname = sheets[0] + } + } + } + return sheetname + } + set(value: String?) { + super.sheetName = value + } + + + /** + * @return Returns the refCell. + */ + override val refCells: Array? + get() { + if (sheetname == null) sheetname = this.sheetName + refCell = super.refCells + return refCell + } + + /** + * PtgRef's have no sub-compnents + */ + override// only one + val components: Array? + get() = null + + override fun addListener() { + try { + parentRec.workBook!!.externSheet!!.addPtgListener(this) + } catch (e: Exception) { + // no need to output here. NullPointer occurs when a ref has an invalid ixti, such as when a sheet was removed Worksheet exception could never really happen. + } + + } + + + /** + * @return Returns the ixti. + */ + override fun getIxti(): Short { // only valid for 3d refs + return ixti + } + //true is relative, false is absolute + + /** + * 0x3A Reference class token: The reference address itself, independent of the cell contents. + * • 0x5A Value class token: A value (a constant, a function result, or one specific value from a dereferenced cell range). + * • 0x7A Array class token: An array of values (array of constant values, an array function result, or all values of a cell range). + */ + constructor() { + record = ByteArray(Ptg.PTG_REF3D_LENGTH) + opcode = 0x5A // id varies with type of token see above and setPtgType below + record[0] = opcode // "" + this.is3dRef = true + } + + /** + * set the Ptg Id type to one of: + * VALUE, REFERENCE or Array + *

                      The Ptg type is important for certain + * functions which require a specific type of operand + */ + override fun setPtgType(type: Short) { + when (type) { + Ptg.VALUE -> opcode = 0x5A + Ptg.REFERENCE -> opcode = 0x3A + Ptg.ARRAY -> opcode = 0x7A + } + record[0] = opcode + } + + constructor(addToRefTracker: Boolean) { + this.useReferenceTracker = addToRefTracker + opcode = 0x5A // TODO: id varies with type of token see above + record[0] = opcode // "" + this.is3dRef = true + + } + + constructor(addr: String, _ixti: Short) : this() { + location = addr + this.is3dRef = true + } + + + override fun init(b: ByteArray) { + opcode = b[0] + record = b + populateVals() + } + + /** + * get the worksheet that this ref is on + * for some reason this seems to be backwards in Ref3d + */ + fun getSheet(b: WorkBook): Boundsheet? { + val bsa = b.externSheet!!.getBoundSheets(ixti.toInt()) + if (bsa != null && bsa[0] == null) { // 20080303 KSC: catch error + // try harder... + if (parentRec.sheet != null) { + return parentRec.sheet // sheetless names belong to parent rec + } else { + if (b.factory!!.debugLevel > 1) + // 20080925 KSC + Logger.logErr("PtgRef3d.getSheet: Unresolved External or Deleted Sheet Reference Found") // [BUGTRACKER 1836] Claritas extenXLS22677.rec (Deleted Sheet/Named Range causes errant value in B3) + return null //20080805 KSC: Don't just return the 1st sheet, may be wrong, deleted, etc! + } + } else if (bsa == null) + return null + return bsa[0] + } + + /** + * set Ptg to parsed location + * + * @param loc String[] sheet1, range, sheet2, exref1, exref2 + */ + override fun setLocation(s: Array) { + if (useReferenceTracker && !isRefErr) + this.parentRec.workBook!!.refTracker!!.removeCellRange(this) + sheetname = null + if (s[0] != null) { + sheetname = s[0] + } else { + try { // if not provided, assume that parent rec sheet is correct + sheetname = this.parentRec.sheet!!.sheetName + } catch (e: NullPointerException) { + } + + } + var loc = s[1] + if (sheetname != null) { + loc = "$sheetname!$loc" // loc uses quoted vers of sheet + if (sheetname!!.indexOf("'") == 0) { + sheetname = sheetname!!.substring(1, sheetname!!.length - 1) + quoted = true + } + } + if (sheetname != null) { + var xsht: Externsheet? = null + var b = parentRec.workBook + if (b == null) + b = parentRec.sheet!!.workBook + try { + + val boundnum = b!!.getWorkSheetByName(sheetname).sheetNum + xsht = b.externSheet + try { + val xloc = xsht!!.insertLocation(boundnum, boundnum) + setIxti(xloc.toShort()) + } catch (e: Exception) { + Logger.logWarn("PtgRef3d.setLocation could not update Externsheet:$e") + } + + } catch (e: WorkSheetNotFoundException) { + try { + xsht = b!!.externSheet + val boundnum = xsht!!.getXtiReference(s[0], s[0]) + if (boundnum == -1) { // can't resolve + this.setIxti(xsht.insertLocation(boundnum, boundnum).toShort()) + } else { + this.setIxti(boundnum.toShort()) + } + } catch (ex: Exception) { + } + + } + + } + super.setLocation(s) + } + + /** + * Throw this data into a ptgref's + */ + public override fun populateVals() { + ixti = ByteTools.readShort(record[1].toInt(), record[2].toInt()) + this.sheetname = this.sheetName + + rw = readRow(record[3], record[4]) + val column = ByteTools.readShort(record[5].toInt(), record[6].toInt()) + // is the Row relative? + fRwRel = column and 0x8000 == 0x8000 + // is the Column relative? + fColRel = column and 0x4000 == 0x4000 + col = (column and 0x3fff).toShort().toInt() + setRelativeRowCol() // set formulaRow/Col for relative references if necessary + this.intLocation // sets the wholeRow and/or wholeCol flag for certain refs + this.hashcode = super.hashCode + } + + + /** + * Set the location of this PtgRef. This takes a location + * such as "a14" + */ + fun setLocation(address: String, ix: Short) { + ixti = ix + val s = ExcelTools.stripSheetNameFromRange(address) + this.setLocation(s) + } + + override fun toString(): String? { + var ret: String? = "" + try { + ret = location + if (ret!!.indexOf("!") == -1 && sheetname != null) { // prepend sheetname + if (sheetname!!.indexOf(' ') == -1 && sheetname!![0] != '\'') + // 20081211 KSC: Sheet names with spaces must have surrounding quotes + ret = "$sheetname!$ret" + else + ret = "'$sheetname'!$ret" + } + } catch (ex: Exception) { + Logger.logErr("PtgRef3d.toString() failed", ex) + } + + return ret + } + + override fun setIxti(ixf: Short) { + if (ixti != ixf) { + ixti = ixf + // this seems to be only one byte... + if (record != null) { + record[1] = ixf.toByte() + } + updateRecord() + } + } + + /** + * Change the sheet reference to the passed in boundsheet + * + * @see io.starter.formats.XLS.formulas.PtgArea3d.setReferencedSheet + */ + fun setReferencedSheet(b: Boundsheet) { + val boundnum = b.sheetNum + val xsht = b.workBook!!.getExternSheet(true) + //TODO: add handling for multi-sheet reference. Already handled in externsheet + try { + val xloc = xsht!!.insertLocation(boundnum, boundnum) + setIxti(xloc.toShort()) + this.sheetname = null // 20100218 KSC: RESET + this.sheetName + locax = null + } catch (e: WorkSheetNotFoundException) { + Logger.logErr("Unable to set referenced sheet in PtgRef3d $e") + } + + } + + /** + * Updates the record bytes so it can be pulled back out. + */ + override fun updateRecord() { + val tmp = ByteArray(Ptg.PTG_REF3D_LENGTH) + tmp[0] = record[0] + val ix = ByteTools.shortToLEBytes(ixti) + System.arraycopy(ix, 0, tmp, 1, 2) + val brow = ByteTools.cLongToLEBytes(rw) + System.arraycopy(brow, 0, tmp, 3, 2) + if (fRwRel) { + col = (0x8000 or col).toShort().toInt() + } + if (fColRel) { + col = (0x4000 or col).toShort().toInt() + } + val bcol = ByteTools.cLongToLEBytes(col) + if (col == -1) { // KSC: what excel expects + bcol[1] = 0 + } + System.arraycopy(bcol, 0, tmp, 5, 2) + record = tmp + if (parentRec != null) { + if (this.parentRec is Formula) + (this.parentRec as Formula).updateRecord() + else if (this.parentRec is Name) + (this.parentRec as Name).updatePtgs() + } + + col = col.toShort() and 0x3FFF //get lower 14 bits which represent the actual column; + } + + /** + * return the ptg components for a certain column within a ptgArea() + * + * @param colNum + * @return all Ptg's within colNum + */ + fun getColComponents(colNum: Int): Array { + val v = FastAddVector() + val x = this.intLocation + if (x!![1] == colNum) v.add(this) + val pref = arrayOfNulls(v.size) + v.toTypedArray() + return pref + } + + companion object { + + private val serialVersionUID = -441121385905948168L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.kt similarity index 57% rename from src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.kt index d65e786..ff74eb9 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr.kt @@ -20,60 +20,57 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas /* - + Specifies a cell reference that was changed to #REF! due to worksheet editing * @see Ptg * @see Formula */ -public class PtgRefErr extends PtgRef implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2553420345077869256L; +class PtgRefErr : PtgRef(), Ptg { - public boolean getIsRefErr() { - return true; - } - - public void init(byte[] b) { - record = b; - } + override val isRefErr: Boolean + get() = true // Excel can handle PtgRefErrors within formulas, as long as they are not the result so... - public boolean getIsOperand() { - return true; - } + override val isOperand: Boolean + get() = true /** * return the human-readable String representation of */ - public String getString() { - return "#REF!"; //Invalid Reference Error"; - } + override//Invalid Reference Error"; + val string: String? + get() = "#REF!" - public Object getValue() { - return "#REF!"; - } + override val value: Any? + get() = "#REF!" - public int getLength() { - return PTG_REFERR_LENGTH; - } + override val length: Int + get() = Ptg.PTG_REFERR_LENGTH - public int[] getRowCol() { - return new int[]{-1, -1}; - } + override val rowCol: IntArray + get() = intArrayOf(-1, -1) + + override var location: String? + get() = "#REF!" + set - public String getLocation() { - return "#REF!"; + override fun init(b: ByteArray) { + record = b } - public void setLocation(String[] s) { + override fun setLocation(s: Array) {} + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 2553420345077869256L } } \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.kt similarity index 50% rename from src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.kt index db2eb88..55d9f5a 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRefErr3d.kt @@ -20,46 +20,49 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.toolkit.ByteTools; +import io.starter.toolkit.ByteTools /* An Erroneous BiffRec range spanning 3rd dimension of WorkSheets. - identical to PtgRef3d - + identical to PtgRef3d + * @see Ptg * @see Formula - + */ -public class PtgRefErr3d extends PtgRef3d implements Ptg { +class PtgRefErr3d : PtgRef3d(), Ptg { + override val isRefErr: Boolean + get() = true - private static final long serialVersionUID = 8691902605148033701L; + override val string: String? + get() = if (sheetname == null) "#REF!" else sheetname!! + "!#REF!" - public boolean getIsRefErr() { - return true; - } + override val length: Int + get() = Ptg.PTG_REFERR3D_LENGTH - // IDs: 3C (R) 5C (V) 7C (A) - public PtgRefErr3d() { - record = new byte[PTG_REFERR3D_LENGTH]; - record[0] = 0x3c; - // record[1]= index to REF entry in EXTERNSHEET - } + override val rowCol: IntArray + get() = intArrayOf(-1, -1) - public String getString() { - if (sheetname == null) - return "#REF!"; - return sheetname + "!#REF!"; - } + override val value: Any? + get() = if (sheetname == null) "#REF!" else sheetname!! + "!#REF!" + + override var location: String? + get() = if (sheetname == null) "#REF!" else sheetname!! + "!#REF!" + set + + // IDs: 3C (R) 5C (V) 7C (A) + init { + record = ByteArray(Ptg.PTG_REFERR3D_LENGTH) + record[0] = 0x3c + // record[1]= index to REF entry in EXTERNSHEET - public int getLength() { - return PTG_REFERR3D_LENGTH; } /* @@ -67,30 +70,19 @@ public int getLength() { Ixti can reference sheets that don't exist, causing np error. As we don't perform any functions upon a PTGRef3D error, just swallow */ - public void populateVals() { - ixti = ByteTools.readShort(record[1], record[2]); - if (ixti > 0) this.sheetname = GenericPtg.qualifySheetname(this.getSheetName()); + override fun populateVals() { + ixti = ByteTools.readShort(record[1].toInt(), record[2].toInt()) + if (ixti > 0) this.sheetname = GenericPtg.qualifySheetname(this.sheetName) } - - public int[] getRowCol() { - return new int[]{-1, -1}; + override fun setLocation(s: Array) { + sheetname = GenericPtg.qualifySheetname(s[0]) } - public Object getValue() { - if (sheetname == null) - return "#REF!"; - return sheetname + "!#REF!"; - } + companion object { - public String getLocation() { - if (sheetname == null) - return "#REF!"; - return sheetname + "!#REF!"; - } - public void setLocation(String[] s) { - sheetname = GenericPtg.qualifySheetname(s[0]); + private val serialVersionUID = 8691902605148033701L } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.java b/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.java deleted file mode 100644 index 7d1b9c6..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.*; -import io.starter.toolkit.Logger; - - -/** - * PtgRefN is a modified PtgRef that is for shared formulas. - * Put here by M$ to make us miserable, - *

                      - * it would have made much more sense to just use a PtgRef. - *

                      - * Offset      Name        Size    Contents
                      - * ----------------------------------------------------
                      - * 0           rw          2       The row of the reference (so says the docs, but it is the row I think
                      - * 2           grbitCol    2       (see following table)
                      - *
                      - * Only the low-order 14 bits specify the Col, the other bits specify
                      - * relative vs absolute for both the col or the row.
                      - *
                      - * Bits        Mask        Name    Contents
                      - * -----------------------------------------------------
                      - * 15          8000h       fRwRel  =1 if row offset relative,
                      - * =0 if otherwise
                      - * 14          4000h       fColRel =1 if row offset relative,
                      - * =0 if otherwise
                      - * 13-0        3FFFh       col     Ordinal column offset or number
                      - * 
                      - *

                      - *

                      - * This token contains the relative reference to a cell in the same sheet. - * It stores relative components as signed offsets and is used in shared formulas, conditional formatting, and data validity. - * - * @see WorkBook - * @see Boundsheet - * @see Dbcell - * @see Row - * @see Cell - * @see XLSRecord - */ -public class PtgRefN extends PtgRef { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 2652944516984815274L; - /* private int formulaRow; - private int formulaCol;*/ - private int realRow; - private int realCol; - short column; - private PtgArea parea = null; - - public boolean getIsReference() { - return true; - } - - public void init(byte[] b) { - ptgId = b[0]; - record = b; - populateVals(); - hashcode = getHashCode(); // different from PtgRef calc - } - - public PtgRefN(boolean useReference) { - this.setUseReferenceTracker(useReference); - } - - /** - * Returns the location of the Ptg as a string (ie c4) - *

                      - * TODO: look into this possible bug - *

                      - * There is a problem here as the location will always be relative and incorrect. - * this is deprecated and should be calling convertToPtgRef - */ - public String getLocation() { - //if (!populated){throw new FormulaNotFoundException("Cannot set location, no Formula Present");} - realRow = rw; - realCol = col; - if (fRwRel) { // the row is a relative location - realRow += (short) formulaRow; - } - if (fColRel) { // the column is a relative location - realCol = (short) formulaCol;// - colNew; - if (realCol >= MAXCOLS) realCol -= MAXCOLS; - } - String s = ExcelTools.getAlphaVal(realCol); - String y = String.valueOf(realRow + 1); - - return (fColRel ? "" : "$") + s + (fRwRel ? "" : "$") + y; - } - - /* Set the location of this PtgRef. This takes a location - such as {1,2} - */ - public void setLocation(int[] rowcol) { - if (useReferenceTracker) this.removeFromRefTracker(); - if (record != null) { // 20090217 KSC: had some errors here, redid - if (fRwRel) - formulaRow = rowcol[0]; - else - rw = rowcol[0]; - if (fColRel) { - formulaCol = rowcol[1]; - } else { - col = rowcol[1]; - } - this.updateRecord(); - init(record); - } else { - Logger.logWarn("PtgRefN.setLocation() failed: NO record data: " + rowcol.toString()); - } - hashcode = getHashCode(); - if (useReferenceTracker) - this.addToRefTracker(); - - } - - /** - * returns the row/col ints for the ref - * adjusted for the host cell - * - * @return - */ - public int[] getRowCol() { - realRow = rw; - realCol = col; - if (fRwRel) { // the row is a relative location - realRow += (short) formulaRow; - } - if (fColRel) { // the column is a relative location - realCol = (short) formulaCol;// - colNew; - if (realCol >= MAXCOLS) realCol -= MAXCOLS; - } - int[] ret = {realRow, realCol}; - return ret; - } - - /** - * this - * - * @return - */ - public int[] getRealRowCol() { - return new int[]{rw, col}; - } - - /* Set the location of this PtgRef. This takes a location - such as "a14" - - TODO: check why this is overridden / reversed 12/02 -jm - */ - public void setLocation(String address) { - if (record != null) { - // 20080215 KSC: replace address stripping - String[] s = ExcelTools.stripSheetNameFromRange(address); - address = s[1]; //stripped of sheet name, if any ... - - int[] res = ExcelTools.getRowColFromString(address); - // 20060301 KSC: Keep relativity - if (fRwRel) { - rw += formulaRow - res[0]; - if (rw < 0) // handle row shifting issues - rw = 0; - formulaRow = res[0]; - } else { - rw = res[0]; - } - if (fColRel) { - col += formulaCol - res[1]; - formulaCol = res[1]; - } else { - col = res[1]; - } - - updateRecord(); - init(record); - // 20090325 KSC: trap OOXML external reference link, if any - if (s[3] != null) - externalLink1 = Integer.valueOf(s[3].substring(1, s[3].length() - 1)).intValue(); - if (s[4] != null) - externalLink2 = Integer.valueOf(s[4].substring(1, s[4].length() - 1)).intValue(); - - } else { - Logger.logWarn("PtgRefN.setLocation() failed: NO record data: " + address); - } - } - - /** - * Convert this PtgRefN to a PtgRef based on the offsets included in the PtgExp & - * if this uses relative or absolute offsets - * - * @param pxp - * @return - */ - public PtgRef convertToPtgRef(XLSRecord r/*PtgExp pxp*/) { - //XLSRecord r = (XLSRecord)pxp.getParentRec(); - int[] i = new int[2]; - if (fRwRel) { - i[0] = r.getRowNumber() + rw; - } else { - i[0] = rw; - } - if (fColRel) { - i[1] = r.getColNumber() + col; - } else { - i[1] = col; - } - - if (i[1] >= MAXCOLS_BIFF8 && - !r.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[1] -= MAXCOLS_BIFF8; - - PtgRef prf = new PtgRef(i, r, false); -// String s = ExcelTools.formatLocation(i, fRwRel, fColRel); -// PtgRef prf = new PtgRef(s, r /*pxp.getParentRec()*/, false); //false); - - return prf; - } - - /** - * /* - * (try to) return int[] array containing the row/column - * referenced by this PtgRefN. - * - * @returns int[] row/col absolute (non-offset) location - * @see io.starter.formats.XLS.formulas.PtgRef#getIntLocation() - */ - public int[] getIntLocation() { - - int rowNew = rw; - int colNew = col; - if (fRwRel) { // the row is a relative location - rowNew += formulaRow; - } - if (fColRel) { // the column is a relative location - colNew += formulaCol; - } - if (colNew >= MAXCOLS) colNew -= MAXCOLS; // 20070205 KSC: Added 20080102 KSC: added = - - int[] returning = new int[2]; - returning[0] = rowNew; - returning[1] = colNew; - return returning; - } - - /** - * set formula row - * - * @param r int new row - */ - // 20060301 KSC: access to formula row/col - public void setFormulaRow(int r) { - formulaRow = r; - } - - /** - * set formula col - * - * @param c int new col - */ - // 20060301 KSC: access to formula row/col - public void setFormulaCol(int c) { - formulaCol = c; - } - - - /** - * custom RefTracker usage: uses entire range covered by all shared formulas - */ - public PtgArea getArea() { - Shrfmla sh = (Shrfmla) this.getParentRec(); - int[] i = new int[4]; - if (fRwRel) { - i[0] = sh.getFirstRow() + rw; - } else { - i[0] = rw; - } - if (fColRel) { - i[1] = sh.getFirstCol() + col; - } else { - i[1] = col; - } - if (fRwRel) { - i[2] = sh.getLastRow() + rw; - } else { - i[2] = rw; - } - if (fColRel) { - i[3] = sh.getLastCol() + col; - } else { - i[3] = col; - } - - if (i[1] >= MAXCOLS_BIFF8 && - !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[1] -= MAXCOLS_BIFF8; - if (i[3] >= MAXCOLS_BIFF8 && - !this.parent_rec.getWorkBook().getIsExcel2007()) // TODO: determine if this is an OK maxcol (Excel 2007) - i[3] -= MAXCOLS_BIFF8; - - - PtgArea parea = new PtgArea(i, sh, true); - return parea; - } - - /** - * add "true" area to reference tracker i.e. entire range referenced by all shared formula members - */ - public void addToRefTracker() { - int iParent = this.getParentRec().getOpcode(); - if (iParent == XLSConstants.SHRFMLA) { - // KSC: TESTING - local ptgarea gets finalized and messes up ref. tracker on multiple usages without close -// getArea(); -// parea.addToRefTracker(); - PtgArea parea = getArea(); // otherwise is finalized if local var --- but take out ptgarea finalize for now - parea.addToRefTracker(); - } - } - - /** - * remove "true" area from reference tracker i.e. entire range referenced by all shared formula members - */ - public void removeFromRefTracker() { - int iParent = this.getParentRec().getOpcode(); - if (iParent == XLSConstants.SHRFMLA) { - PtgArea parea = getArea(); // otherwise is finalized if local var --- but take out ptgarea finalize for now - if (parea != null) { - parea.removeFromRefTracker(); -// parea.close(); - } - // parea= null; - } - } - - public void close() { - //removeFromRefTracker(); - if (parea != null) - parea.close(); - parea = null; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.kt new file mode 100644 index 0000000..32b9066 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgRefN.kt @@ -0,0 +1,420 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.* +import io.starter.toolkit.Logger + + +/** + * PtgRefN is a modified PtgRef that is for shared formulas. + * Put here by M$ to make us miserable, + * + * + * it would have made much more sense to just use a PtgRef. + *

                      + * Offset      Name        Size    Contents
                      + * ----------------------------------------------------
                      + * 0           rw          2       The row of the reference (so says the docs, but it is the row I think
                      + * 2           grbitCol    2       (see following table)
                      + *
                      + * Only the low-order 14 bits specify the Col, the other bits specify
                      + * relative vs absolute for both the col or the row.
                      + *
                      + * Bits        Mask        Name    Contents
                      + * -----------------------------------------------------
                      + * 15          8000h       fRwRel  =1 if row offset relative,
                      + * =0 if otherwise
                      + * 14          4000h       fColRel =1 if row offset relative,
                      + * =0 if otherwise
                      + * 13-0        3FFFh       col     Ordinal column offset or number
                      +
                      * + * + * + * + * + * This token contains the relative reference to a cell in the same sheet. + * It stores relative components as signed offsets and is used in shared formulas, conditional formatting, and data validity. + * + * @see WorkBook + * + * @see Boundsheet + * + * @see Dbcell + * + * @see Row + * + * @see Cell + * + * @see XLSRecord + */ +class PtgRefN(useReference: Boolean) : PtgRef() { + /* private int formulaRow; + private int formulaCol;*/ + private var realRow: Int = 0 + private var realCol: Int = 0 + internal var column: Short = 0 + private var parea: PtgArea? = null + + override val isReference: Boolean + get() = true + + /** + * Returns the location of the Ptg as a string (ie c4) + * + * + * TODO: look into this possible bug + * + * + * There is a problem here as the location will always be relative and incorrect. + * this is deprecated and should be calling convertToPtgRef + */ + /* Set the location of this PtgRef. This takes a location + such as "a14" + + TODO: check why this is overridden / reversed 12/02 -jm + */ + override//if (!populated){throw new FormulaNotFoundException("Cannot set location, no Formula Present");} + // the row is a relative location + // the column is a relative location + // - colNew; + // 20080215 KSC: replace address stripping + //stripped of sheet name, if any ... + // 20060301 KSC: Keep relativity + // handle row shifting issues + // 20090325 KSC: trap OOXML external reference link, if any + var location: String? + get() { + realRow = rw + realCol = col + if (fRwRel) { + realRow += formulaRow.toShort().toInt() + } + if (fColRel) { + realCol = formulaCol.toShort().toInt() + if (realCol >= XLSConstants.MAXCOLS) realCol -= XLSConstants.MAXCOLS + } + val s = ExcelTools.getAlphaVal(realCol) + val y = (realRow + 1).toString() + + return (if (fColRel) "" else "$") + s + (if (fRwRel) "" else "$") + y + } + set(address) { + var address = address + if (record != null) { + val s = ExcelTools.stripSheetNameFromRange(address) + address = s[1] + + val res = ExcelTools.getRowColFromString(address) + if (fRwRel) { + rw += formulaRow - res[0] + if (rw < 0) + rw = 0 + formulaRow = res[0] + } else { + rw = res[0] + } + if (fColRel) { + col += formulaCol - res[1] + formulaCol = res[1] + } else { + col = res[1] + } + + updateRecord() + init(record) + if (s[3] != null) + externalLink1 = Integer.valueOf(s[3].substring(1, s[3].length - 1)).toInt() + if (s[4] != null) + externalLink2 = Integer.valueOf(s[4].substring(1, s[4].length - 1)).toInt() + + } else { + Logger.logWarn("PtgRefN.setLocation() failed: NO record data: $address") + } + } + + /** + * returns the row/col ints for the ref + * adjusted for the host cell + * + * @return + */ + override// the row is a relative location + // the column is a relative location + // - colNew; + val rowCol: IntArray + get() { + realRow = rw + realCol = col + if (fRwRel) { + realRow += formulaRow.toShort().toInt() + } + if (fColRel) { + realCol = formulaCol.toShort().toInt() + if (realCol >= XLSConstants.MAXCOLS) realCol -= XLSConstants.MAXCOLS + } + return intArrayOf(realRow, realCol) + } + + /** + * this + * + * @return + */ + val realRowCol: IntArray + get() = intArrayOf(rw, col) + +/** + * /* + * (try to) return int[] array containing the row/column + * referenced by this PtgRefN. + * + * @returns int[] row/col absolute (non-offset) location + * @see io.starter.formats.XLS.formulas.PtgRef.getIntLocation +*/ +public override// the row is a relative location +// the column is a relative location +// 20070205 KSC: Added 20080102 KSC: added = +val intLocation:IntArray? +get() { + +var rowNew = rw +var colNew = col +if (fRwRel) +{ +rowNew += formulaRow +} +if (fColRel) +{ +colNew += formulaCol +} +if (colNew >= XLSConstants.MAXCOLS) colNew -= XLSConstants.MAXCOLS + +val returning = IntArray(2) +returning[0] = rowNew +returning[1] = colNew +return returning +} + + +/** + * custom RefTracker usage: uses entire range covered by all shared formulas +*/ +// TODO: determine if this is an OK maxcol (Excel 2007) +// TODO: determine if this is an OK maxcol (Excel 2007) +val area:PtgArea +get() { +val sh = this.parentRec as Shrfmla? +val i = IntArray(4) +if (fRwRel) +{ +i[0] = sh!!.firstRow + rw +} +else +{ +i[0] = rw +} +if (fColRel) +{ +i[1] = sh!!.firstCol + col +} +else +{ +i[1] = col +} +if (fRwRel) +{ +i[2] = sh!!.lastRow + rw +} +else +{ +i[2] = rw +} +if (fColRel) +{ +i[3] = sh!!.lastCol + col +} +else +{ +i[3] = col +} + +if ((i[1] >= XLSConstants.MAXCOLS_BIFF8 && !this.parentRec!!.workBook!!.isExcel2007)) +i[1] -= XLSConstants.MAXCOLS_BIFF8 +if ((i[3] >= XLSConstants.MAXCOLS_BIFF8 && !this.parentRec!!.workBook!!.isExcel2007)) +i[3] -= XLSConstants.MAXCOLS_BIFF8 + + +val parea = PtgArea(i, sh, true) +return parea +} + +public override fun init(b:ByteArray) { +opcode = b[0] +record = b +populateVals() +hashcode = hashCode // different from PtgRef calc +} + +init{ +this.useReferenceTracker = useReference +} + +/* Set the location of this PtgRef. This takes a location +such as {1,2} +*/ +public override fun setLocation(rowcol:IntArray) { +if (useReferenceTracker) this.removeFromRefTracker() +if (record != null) +{ // 20090217 KSC: had some errors here, redid +if (fRwRel) +formulaRow = rowcol[0] +else +rw = rowcol[0] +if (fColRel) +{ +formulaCol = rowcol[1] +} +else +{ +col = rowcol[1] +} +this.updateRecord() +init(record) +} +else +{ +Logger.logWarn("PtgRefN.setLocation() failed: NO record data: " + rowcol.toString()) +} +hashcode = hashCode +if (useReferenceTracker) +this.addToRefTracker() + +} + +/** + * Convert this PtgRefN to a PtgRef based on the offsets included in the PtgExp & + * if this uses relative or absolute offsets + * + * @param pxp + * @return +*/ +fun convertToPtgRef(r:XLSRecord/*PtgExp pxp*/):PtgRef { +//XLSRecord r = (XLSRecord)pxp.getParentRec(); +val i = IntArray(2) +if (fRwRel) +{ +i[0] = r.rowNumber + rw +} +else +{ +i[0] = rw +} +if (fColRel) +{ +i[1] = r.colNumber + col +} +else +{ +i[1] = col +} + +if ((i[1] >= XLSConstants.MAXCOLS_BIFF8 && !r.workBook!!.isExcel2007)) +// TODO: determine if this is an OK maxcol (Excel 2007) +i[1] -= XLSConstants.MAXCOLS_BIFF8 + +val prf = PtgRef(i, r, false) +// String s = ExcelTools.formatLocation(i, fRwRel, fColRel); +// PtgRef prf = new PtgRef(s, r /*pxp.getParentRec()*/, false); //false); + +return prf +} + +/** + * set formula row + * + * @param r int new row +*/ +// 20060301 KSC: access to formula row/col +fun setFormulaRow(r:Int) { +formulaRow = r +} + +/** + * set formula col + * + * @param c int new col +*/ +// 20060301 KSC: access to formula row/col +fun setFormulaCol(c:Int) { +formulaCol = c +} + +/** + * add "true" area to reference tracker i.e. entire range referenced by all shared formula members +*/ +public override fun addToRefTracker() { +val iParent = this.parentRec!!.opcode.toInt() +if (iParent == XLSConstants.SHRFMLA.toInt()) +{ +// KSC: TESTING - local ptgarea gets finalized and messes up ref. tracker on multiple usages without close +// getArea(); +// parea.addToRefTracker(); +val parea = area // otherwise is finalized if local var --- but take out ptgarea finalize for now +parea.addToRefTracker() +} +} + +/** + * remove "true" area from reference tracker i.e. entire range referenced by all shared formula members +*/ +public override fun removeFromRefTracker() { +val iParent = this.parentRec!!.opcode.toInt() +if (iParent == XLSConstants.SHRFMLA.toInt()) +{ +val parea = area // otherwise is finalized if local var --- but take out ptgarea finalize for now +if (parea != null) +{ +parea.removeFromRefTracker() +// parea.close(); +} +// parea= null; +} +} + +public override fun close() { +//removeFromRefTracker(); +if (parea != null) +parea!!.close() +parea = null +} + +companion object { + +/** + * serialVersionUID +*/ +private val serialVersionUID = 2652944516984815274L +} +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgStr.java b/src/main/java/io/starter/formats/XLS/formulas/PtgStr.java deleted file mode 100644 index b5d2aba..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgStr.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.io.UnsupportedEncodingException; - - -/** - * PTG that stores a unicode string - *

                      - * Offset Name Size Contents - * ------------------------------------ - * 0 cch 1 Length of the string - * 1 rgch var The string - *

                      - * * I think the string includes a grbit itself, see UnicodeString. Internationalization issues - * may exist here!!! - *

                      - * -- Yes, it did include grbit, all handled now. - * - * @see Ptg - * @see Formula - */ -public class PtgStr extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -1427051673654768400L; - - public boolean getIsOperand() { - return true; - } - - short cch; - byte grbit; - boolean negativeCch = false; - - public String getString() { - String strVal = null; - try { - if ((grbit & 0x1) == 0x1) { // hits on Japanese strings in formulas - byte[] barr = new byte[cch * 2]; - System.arraycopy(record, 3, barr, 0, cch * 2); - strVal = new String(barr, UNICODEENCODING); - } else { - byte[] barr = new byte[cch]; - System.arraycopy(record, 3, barr, 0, cch); - strVal = new String(barr, DEFAULTENCODING); - } - } catch (Exception e) { - byte[] barr = new byte[cch]; - System.arraycopy(record, 3, barr, 0, cch); - strVal = new String(barr); - } - return strVal; - } - - public String toString() { - return getString(); - } - - /** - * return the human-readable String representation of - */ - public String getTextString() { - try { - Double d = new Double(getString()); - } catch (NumberFormatException e) { - } - return "\"" + getString() + "\""; - } - - public Object getValue() { - return getString(); - } - - public PtgStr() { - // default constructor - } - - public PtgStr(String s) { - ptgId = 0x17; - setVal(s); - } - - public void init(byte[] b) { - grbit = b[2]; - cch = (short) (b[1] & 0xff); // this is the cch - ptgId = b[0]; - record = b; - this.populateVals(); - } - - /** - * Constructer to create these on the fly, this is needed - * for value storage in calculations of formulas. - */ - private void populateVals() { - // no longer does anything, no String value stored - } - - public String getVal() { - return getString(); - } - - private String tempstr = null; - - public void setVal(String s) { - tempstr = s; - this.updateRecord(); - } - - public void updateRecord() { - String ts = tempstr; - if (ts == null) - return; - - if (ByteTools.isUnicode(ts)) { - grbit = (byte) (grbit | 0x1); - } - try { - byte[] strbytes = null; - if ((grbit & 0x1) == 0x1) { - strbytes = ts.getBytes(UNICODEENCODING); - } else { - strbytes = ts.getBytes(DEFAULTENCODING); - } - - short strbytelen = (short) strbytes.length; - cch = strbytelen; - if ((grbit & 0x1) == 0x1) cch = (short) (strbytelen / 2); - //cch = (short)( getString().length() + 3); - record = new byte[strbytelen + 3]; - //record = new byte[(cch*times) + 3]; - record[0] = 0x17; - record[1] = (byte) cch; - record[2] = grbit; - System.arraycopy(strbytes, 0, record, 3, strbytelen); - - } catch (UnsupportedEncodingException e) { - Logger.logInfo("decoding formula string failed: " + e); - } - } - - public int getLength() { - return record.length; - } - - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgStr.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgStr.kt new file mode 100644 index 0000000..72f03d4 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgStr.kt @@ -0,0 +1,180 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.io.UnsupportedEncodingException + + +/** + * PTG that stores a unicode string + * + * + * Offset Name Size Contents + * ------------------------------------ + * 0 cch 1 Length of the string + * 1 rgch var The string + * + * + * * I think the string includes a grbit itself, see UnicodeString. Internationalization issues + * may exist here!!! + * + * + * -- Yes, it did include grbit, all handled now. + * + * @see Ptg + * + * @see Formula + */ +class PtgStr : GenericPtg, Ptg { + + override val isOperand: Boolean + get() = true + + internal var cch: Short = 0 + internal var grbit: Byte = 0 + internal var negativeCch = false + + override// hits on Japanese strings in formulas + val string: String + get() { + var strVal: String? = null + try { + if (grbit and 0x1 == 0x1) { + val barr = ByteArray(cch * 2) + System.arraycopy(record, 3, barr, 0, cch * 2) + strVal = String(barr, XLSConstants.UNICODEENCODING) + } else { + val barr = ByteArray(cch) + System.arraycopy(record, 3, barr, 0, cch.toInt()) + strVal = String(barr, XLSConstants.DEFAULTENCODING) + } + } catch (e: Exception) { + val barr = ByteArray(cch) + System.arraycopy(record, 3, barr, 0, cch.toInt()) + strVal = String(barr) + } + + return strVal + } + + /** + * return the human-readable String representation of + */ + override val textString: String + get() { + try { + val d = Double(string) + } catch (e: NumberFormatException) { + } + + return "\"" + string + "\"" + } + + override val value: Any? + get() = string + + var `val`: String + get() = string + set(s) { + tempstr = s + this.updateRecord() + } + + private var tempstr: String? = null + + override val length: Int + get() = record.size + + override fun toString(): String { + return string + } + + constructor() { + // default constructor + } + + constructor(s: String) { + opcode = 0x17 + `val` = s + } + + override fun init(b: ByteArray) { + grbit = b[2] + cch = (b[1] and 0xff).toShort() // this is the cch + opcode = b[0] + record = b + this.populateVals() + } + + /** + * Constructer to create these on the fly, this is needed + * for value storage in calculations of formulas. + */ + private fun populateVals() { + // no longer does anything, no String value stored + } + + override fun updateRecord() { + val ts = tempstr ?: return + + if (ByteTools.isUnicode(ts)) { + grbit = (grbit or 0x1).toByte() + } + try { + var strbytes: ByteArray? = null + if (grbit and 0x1 == 0x1) { + strbytes = ts.toByteArray(charset(XLSConstants.UNICODEENCODING)) + } else { + strbytes = ts.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + } + + val strbytelen = strbytes!!.size.toShort() + cch = strbytelen + if (grbit and 0x1 == 0x1) cch = (strbytelen / 2).toShort() + //cch = (short)( getString().length() + 3); + record = ByteArray(strbytelen + 3) + //record = new byte[(cch*times) + 3]; + record[0] = 0x17 + record[1] = cch.toByte() + record[2] = grbit + System.arraycopy(strbytes, 0, record, 3, strbytelen.toInt()) + + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("decoding formula string failed: $e") + } + + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -1427051673654768400L + } + + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgSub.java b/src/main/java/io/starter/formats/XLS/formulas/PtgSub.java deleted file mode 100644 index 4141816..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgSub.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.toolkit.Logger; - -import java.lang.reflect.Array; - - -/** - * Ptg that indicates substitution (ie minus) - * - * @see Ptg - * @see Formula - */ -public class PtgSub extends GenericPtg implements Ptg { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3252464873846778499L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public PtgSub() { - ptgId = 0x4; - record = new byte[1]; - record[0] = 0x4; - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return "-"; - } - - public String toString() { - return getString(); - } - - public int getLength() { - return PTG_SUB_LENGTH; - } - - /** - * Operator specific calculate method, this one subtracts one value from another - */ - public Ptg calculatePtg(Ptg[] form) { - try { - Object[] o = getValuesFromPtgs(form); - if (o == null) return new PtgErr(PtgErr.ERROR_VALUE); // some error in value(s) - if (!o[0].getClass().isArray()) { - if (o.length != 2) { - Logger.logWarn("calculating formula failed, wrong number of values in PtgSub"); - return new PtgErr(PtgErr.ERROR_VALUE); // 20081203 KSC: handle error's ala Excel return null; - } - // blank handling: - if (form[0].isBlank()) o[0] = new Double(0); - if (form[1].isBlank()) o[1] = new Double(0); - // the following should only return #VALUE! if ??? - if (!(o[0] instanceof Double && o[1] instanceof Double)) { - if (this.parent_rec == null) { - return new PtgErr(PtgErr.ERROR_VALUE); - } else if (this.parent_rec.getSheet().getWindow2().getShowZeroValues()) { - return new PtgInt(0); - } else { - return new PtgStr(""); - } - } - double returnVal = (((Double) o[0]).doubleValue() - ((Double) o[1]).doubleValue()); - PtgNumber n = new PtgNumber(returnVal); - return n; - } else { // handle array fomulas - String retArry = ""; - int nArrays = java.lang.reflect.Array.getLength(o); - if (nArrays != 2) return new PtgErr(PtgErr.ERROR_VALUE); - int nVals = java.lang.reflect.Array.getLength(o[0]); // use first array element to determine length of values as subsequent vals might not be arrays - for (int i = 0; i < nArrays - 1; i += 2) { - Object secondOp = null; - boolean comparitorIsArray = o[i + 1].getClass().isArray(); - if (!comparitorIsArray) secondOp = o[i + 1]; - for (int j = 0; j < nVals; j++) { - Object firstOp = Array.get(o[i], j); // first array index j - if (comparitorIsArray) - secondOp = Array.get(o[i + 1], j); // second array index j - if (!(firstOp instanceof Double && secondOp instanceof Double)) { - if (this.parent_rec == null) { - return new PtgErr(PtgErr.ERROR_VALUE); - } else if (this.parent_rec.getSheet().getWindow2().getShowZeroValues()) { - return new PtgInt(0); - } else { - return new PtgStr(""); - } - } // 20081203 KSC: handle error's ala Excel - double retVal = ((Double) firstOp).doubleValue() - ((Double) secondOp).doubleValue(); - retArry = retArry + retVal + ","; - } - } - retArry = "{" + retArry.substring(0, retArry.length() - 1) + "}"; - PtgArray pa = new PtgArray(); - pa.setVal(retArry); - return pa; - } - } catch (NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } catch (Exception e) { // 20081125 KSC: handle error ala Excel - // Logger.logErr("PtgSub failed:" + e); - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgSub.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgSub.kt new file mode 100644 index 0000000..ebbd3cd --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgSub.kt @@ -0,0 +1,143 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.toolkit.Logger + +import java.lang.reflect.Array + + +/** + * Ptg that indicates substitution (ie minus) + * + * @see Ptg + * + * @see Formula + */ +class PtgSub : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val string: String + get() = "-" + + override val length: Int + get() = Ptg.PTG_SUB_LENGTH + + init { + opcode = 0x4 + record = ByteArray(1) + record[0] = 0x4 + } + + override fun toString(): String { + return string + } + + /** + * Operator specific calculate method, this one subtracts one value from another + */ + override fun calculatePtg(form: Array): Ptg? { + try { + val o = GenericPtg.getValuesFromPtgs(form) ?: return PtgErr(PtgErr.ERROR_VALUE) +// some error in value(s) + if (!o[0].javaClass.isArray()) { + if (o.size != 2) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgSub") + return PtgErr(PtgErr.ERROR_VALUE) // 20081203 KSC: handle error's ala Excel return null; + } + // blank handling: + if (form[0].isBlank) o[0] = 0 + if (form[1].isBlank) o[1] = 0 + // the following should only return #VALUE! if ??? + if (!(o[0] is Double && o[1] is Double)) { + return if (this.parentRec == null) { + PtgErr(PtgErr.ERROR_VALUE) + } else if (this.parentRec!!.sheet!!.window2!!.showZeroValues) { + PtgInt(0) + } else { + PtgStr("") + } + } + val returnVal = (o[0] as Double).toDouble() - (o[1] as Double).toDouble() + return PtgNumber(returnVal) + } else { // handle array fomulas + var retArry = "" + val nArrays = java.lang.reflect.Array.getLength(o) + if (nArrays != 2) return PtgErr(PtgErr.ERROR_VALUE) + val nVals = java.lang.reflect.Array.getLength(o[0]) // use first array element to determine length of values as subsequent vals might not be arrays + var i = 0 + while (i < nArrays - 1) { + var secondOp: Any? = null + val comparitorIsArray = o[i + 1].javaClass.isArray() + if (!comparitorIsArray) secondOp = o[i + 1] + for (j in 0 until nVals) { + val firstOp = Array.get(o[i], j) // first array index j + if (comparitorIsArray) + secondOp = Array.get(o[i + 1], j) // second array index j + if (!(firstOp is Double && secondOp is Double)) { + return if (this.parentRec == null) { + PtgErr(PtgErr.ERROR_VALUE) + } else if (this.parentRec!!.sheet!!.window2!!.showZeroValues) { + PtgInt(0) + } else { + PtgStr("") + } + } // 20081203 KSC: handle error's ala Excel + val retVal = firstOp.toDouble() - secondOp.toDouble() + retArry = "$retArry$retVal," + } + i += 2 + } + retArry = "{" + retArry.substring(0, retArry.length - 1) + "}" + val pa = PtgArray() + pa.setVal(retArry) + return pa + } + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } catch (e: Exception) { // 20081125 KSC: handle error ala Excel + // Logger.logErr("PtgSub failed:" + e); + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -3252464873846778499L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.java b/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.java deleted file mode 100644 index 04de666..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -/* - Ptg that indicates Unary minus, negates the operand on top of the stack - - * @see Ptg - * @see Formula - - -*/ -package io.starter.formats.XLS.formulas; - -import io.starter.toolkit.Logger; - -/** - * - */ -public class PtgUMinus extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8448419489380791823L; - - public PtgUMinus() { // 20060504 KSC: Added to fill record bytes upon creation - ptgId = 0x13; - record = new byte[1]; - record[0] = 0x13; - } - - public boolean getIsOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public boolean getIsUnaryOperator() { - return true; - } - - /** - * return the human-readable String representation of - */ - public int getLength() { - return PTG_UMINUS_LENGTH; - } - - /** - * Operator specific calculate method, this one returns a single value sent to it. - */ - public Ptg calculatePtg(Ptg[] form) { - if (form.length != 1) { - Logger.logWarn("PtgMinus calculating formula failed, wrong number of values."); - return new PtgErr(PtgErr.ERROR_VALUE); - } - try { - Ptg p = form[0]; - Ptg ret = null; - if (p instanceof PtgInt) { - int val = p.getIntVal(); - val *= -1; - ret = new PtgInt(val); - } else { - double val = p.getDoubleVal(); - val *= -1; - ret = new PtgNumber(val); - } - return ret; - } catch (Exception e) { - Logger.logWarn("PtgMinus calculating formula failed, could not negate operand " + form[0].toString() + " : " + e.toString()); - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - public String getString() { - return "-"; - } - - public String toString() { - return "u-"; - } - -} diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.kt new file mode 100644 index 0000000..4c026df --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgUMinus.kt @@ -0,0 +1,103 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +/* + Ptg that indicates Unary minus, negates the operand on top of the stack + + * @see Ptg + * @see Formula + + +*/ +package io.starter.formats.XLS.formulas + +import io.starter.toolkit.Logger + +/** + * + */ +class PtgUMinus : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + override val isUnaryOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val length: Int + get() = Ptg.PTG_UMINUS_LENGTH + + override val string: String + get() = "-" + + init { // 20060504 KSC: Added to fill record bytes upon creation + opcode = 0x13 + record = ByteArray(1) + record[0] = 0x13 + } + + /** + * Operator specific calculate method, this one returns a single value sent to it. + */ + override fun calculatePtg(form: Array): Ptg? { + if (form.size != 1) { + Logger.logWarn("PtgMinus calculating formula failed, wrong number of values.") + return PtgErr(PtgErr.ERROR_VALUE) + } + try { + val p = form[0] + var ret: Ptg? = null + if (p is PtgInt) { + var `val` = p.intVal + `val` *= -1 + ret = PtgInt(`val`) + } else { + var `val` = p.doubleVal + `val` *= -1.0 + ret = PtgNumber(`val`) + } + return ret + } catch (e: Exception) { + Logger.logWarn("PtgMinus calculating formula failed, could not negate operand " + form[0].toString() + " : " + e.toString()) + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + override fun toString(): String { + return "u-" + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8448419489380791823L + } + +} diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.java b/src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.kt similarity index 58% rename from src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.java rename to src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.kt index 6dd7ea5..76ebc2b 100644 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.java +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgUPlus.kt @@ -28,58 +28,56 @@ */ -package io.starter.formats.XLS.formulas; +package io.starter.formats.XLS.formulas -import io.starter.toolkit.Logger; +import io.starter.toolkit.Logger /** * */ -public class PtgUPlus extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -3514760881731524419L; +class PtgUPlus : GenericPtg(), Ptg { - public PtgUPlus() { // 20060504 KSC: Added to fill record bytes upon creation - ptgId = 0x12; - record = new byte[1]; - record[0] = 0x12; - } + override val isOperator: Boolean + get() = true - public boolean getIsOperator() { - return true; - } + override val isPrimitiveOperator: Boolean + get() = true - public boolean getIsPrimitiveOperator() { - return true; - } - - public boolean getIsUnaryOperator() { - return true; - } + override val isUnaryOperator: Boolean + get() = true /** * return the human-readable String representation of */ - public String getString() { - return "+"; - } + override val string: String + get() = "+" + + override val length: Int + get() = Ptg.PTG_UPLUS_LENGTH - public int getLength() { - return PTG_UPLUS_LENGTH; + init { // 20060504 KSC: Added to fill record bytes upon creation + opcode = 0x12 + record = ByteArray(1) + record[0] = 0x12 } /* Operator specific calculate method, this one returns a single value sent to it. */ - public Ptg calculatePtg(Ptg[] form) { + override fun calculatePtg(form: Array): Ptg? { // there should always be only one ptg in this, error if not. - if (form.length != 1) { - Logger.logWarn("calculating formula failed, wrong number of values in PtgUPlus"); - return null; + if (form.size != 1) { + Logger.logWarn("calculating formula failed, wrong number of values in PtgUPlus") + return null } - return form[0]; + return form[0] + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -3514760881731524419L } } diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.java b/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.java deleted file mode 100644 index a61f7fd..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import java.util.ArrayList; - - -/* - * Computes the Union of the two top operands. Supposedly this is - * "Microsoft Excel's comma operator" Brought to you by the same people that - * created those space saving RK's - * - * - * "http://www.extentech.com">Extentech Inc. - * - * - * - * @see Ptg - * - * @see Formula - */ -public class PtgUnion extends GenericPtg implements Ptg { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8333035819099274707L; - - public boolean getIsOperator() { - return true; - } - - public boolean getIsBinaryOperator() { - return true; - } - - public boolean getIsPrimitiveOperator() { - return true; - } - - public PtgUnion() { - ptgId = 0x10; - record = new byte[1]; - record[0] = 0x10; - } - - /** - * return the human-readable String representation of - */ - public String getString() { - return ","; - } - - public int getLength() { - return PTG_UNION_LENGTH; - } - - /** - * Union = All items from A and B, shared and not shared. Range list - * operator, represented by the system's list separator sign (for example - * comma sign). Treats two ranges as one operator (A1:B2,B2:C3) B1:B3 - * ==>B1:B2, B2:B3 (A1:B2,B2:C3) ==>A1:B2, B2:C3 - */ - // just add together? seems that is the case - public Ptg calculatePtg(Ptg[] form) { - if (form.length != 2) - return new PtgErr(PtgErr.ERROR_VALUE); - - try { - String sourceSheet = null; - try { - if (this.getParentRec().getSheet() != null) // could be a Name rec ... - sourceSheet = this.getParentRec().getSheet().getSheetName(); - } catch (NullPointerException ne) { - } - ArrayList first = null; - ArrayList last = null; - for (int i = 0; i < 2; i++) { - Ptg p = form[i]; - ArrayList a = new ArrayList(); - if (p instanceof PtgArea) { - a.add(p); - } else if (p instanceof PtgRef3d) { - a.add(p); - } else if (p instanceof PtgRef) { - a.add(p); - } else if (p instanceof PtgName) { - Ptg[] pc = p.getComponents(); - for (int j = 0; j < pc.length; j++) - a.add(pc[j]); - } else if (p instanceof PtgStr) { - String[] comps = (p.toString()).split(","); - for (int j = 0; j < comps.length; j++) { - if (comps[j].indexOf(":") == -1) { - if (!comps[j].equals("#REF!") && - !comps[j].equals("#NULL!")) { - PtgRef3d pr = new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(comps[j]); - a.add(pr); - } else { - PtgRefErr3d pr = new PtgRefErr3d(); - pr.setParentRec(this.getParentRec()); - a.add(pr); - } - } else { - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(comps[j]); - Ptg[] pcs = pa.getComponents(); - if (pcs != null) { - for (int k = 0; k < pcs.length; k++) { - ((PtgRef) pcs[k]).setSheetName(pa.getSheetName()); - a.add(pcs[k]); - } - } - } - } - } else if (p instanceof PtgArray) { - // parse array components and create refs - Ptg[] pc = p.getComponents(); - for (int j = 0; j < pc.length; j++) { - String loc = pc[j].toString(); - if (!loc.startsWith("#")) { // skip errors -- TODO: IS THIS CORRECT? - if (loc.indexOf(":") == -1) { - if (loc.indexOf("!") == -1) { - PtgRef pr = new PtgRef(); - pr.setUseReferenceTracker(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } else { - PtgRef3d pr = new PtgRef3d(false); - pr.setParentRec(this.getParentRec()); - pr.setLocation(loc); - a.add(pr); - } - } else { - PtgArea3d pa = new PtgArea3d(false); - pa.setParentRec(this.getParentRec()); - pa.setLocation(loc); - a.add(pa); - } - } - } - } else if (p instanceof PtgErr || p instanceof PtgRefErr || p instanceof PtgAreaErr3d) { - // DO WHAT??? - } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array - Ptg[] pc = ((GenericPtg) p).vars; - for (int j = 0; j < pc.length; j++) { - if (pc[j] instanceof PtgArea & !(pc[j] instanceof PtgAreaErr3d)) { - Ptg[] pa = pc[j].getComponents(); - for (int k = 0; k < pa.length; k++) - a.add(pa[k]); - } else - a.add(pc[j]); - } - } - if (first == null) - first = a; - else - last = a; - } - // now have components for both operands - SUM and package into 1 ptg for return - // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars - GenericPtg retp = new PtgMystery(); - ArrayList retptgs = new ArrayList(); - for (int k = 0; k < first.size(); k++) - retptgs.add(first.get(k)); - for (int k = 0; k < last.size(); k++) - retptgs.add(last.get(k)); - Ptg[] ptgs = new Ptg[retptgs.size()]; - retptgs.toArray(ptgs); - retp.setVars(ptgs); - return retp; - } catch (NumberFormatException e) { - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } catch (Exception e) { // handle error ala Excel - PtgErr perr = new PtgErr(PtgErr.ERROR_VALUE); - return perr; - } - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.kt b/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.kt new file mode 100644 index 0000000..ac8daad --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/PtgUnion.kt @@ -0,0 +1,201 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import java.util.ArrayList + + +/* + * Computes the Union of the two top operands. Supposedly this is + * "Microsoft Excel's comma operator" Brought to you by the same people that + * created those space saving RK's + * + * + * "http://www.extentech.com">Extentech Inc. + * + * + * + * @see Ptg + * + * @see Formula + */ +class PtgUnion : GenericPtg(), Ptg { + + override val isOperator: Boolean + get() = true + + override val isBinaryOperator: Boolean + get() = true + + override val isPrimitiveOperator: Boolean + get() = true + + /** + * return the human-readable String representation of + */ + override val string: String + get() = "," + + override val length: Int + get() = Ptg.PTG_UNION_LENGTH + + init { + opcode = 0x10 + record = ByteArray(1) + record[0] = 0x10 + } + + /** + * Union = All items from A and B, shared and not shared. Range list + * operator, represented by the system's list separator sign (for example + * comma sign). Treats two ranges as one operator (A1:B2,B2:C3) B1:B3 + * ==>B1:B2, B2:B3 (A1:B2,B2:C3) ==>A1:B2, B2:C3 + */ + // just add together? seems that is the case + override fun calculatePtg(form: Array): Ptg? { + if (form.size != 2) + return PtgErr(PtgErr.ERROR_VALUE) + + try { + var sourceSheet: String? = null + try { + if (this.parentRec!!.sheet != null) + // could be a Name rec ... + sourceSheet = this.parentRec!!.sheet!!.sheetName + } catch (ne: NullPointerException) { + } + + var first: ArrayList<*>? = null + var last: ArrayList<*>? = null + for (i in 0..1) { + val p = form[i] + val a = ArrayList() + if (p is PtgArea) { + a.add(p) + } else if (p is PtgRef3d) { + a.add(p) + } else if (p is PtgRef) { + a.add(p) + } else if (p is PtgName) { + val pc = p.components + for (j in pc.indices) + a.add(pc[j]) + } else if (p is PtgStr) { + val comps = p.toString().split(",".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + for (j in comps.indices) { + if (comps[j].indexOf(":") == -1) { + if (comps[j] != "#REF!" && comps[j] != "#NULL!") { + val pr = PtgRef3d(false) + pr.parentRec = this.parentRec + pr.location = comps[j] + a.add(pr) + } else { + val pr = PtgRefErr3d() + pr.parentRec = this.parentRec + a.add(pr) + } + } else { + val pa = PtgArea3d(false) + pa.parentRec = this.parentRec + pa.location = comps[j] + val pcs = pa.components + if (pcs != null) { + for (k in pcs.indices) { + (pcs[k] as PtgRef).sheetName = pa.sheetName + a.add(pcs[k]) + } + } + } + } + } else if (p is PtgArray) { + // parse array components and create refs + val pc = p.components + for (j in pc.indices) { + val loc = pc[j].toString() + if (!loc.startsWith("#")) { // skip errors -- TODO: IS THIS CORRECT? + if (loc.indexOf(":") == -1) { + if (loc.indexOf("!") == -1) { + val pr = PtgRef() + pr.useReferenceTracker = false + pr.parentRec = this.parentRec + pr.location = loc + a.add(pr) + } else { + val pr = PtgRef3d(false) + pr.parentRec = this.parentRec + pr.location = loc + a.add(pr) + } + } else { + val pa = PtgArea3d(false) + pa.parentRec = this.parentRec + pa.location = loc + a.add(pa) + } + } + } + } else if (p is PtgErr || p is PtgRefErr || p is PtgAreaErr3d) { + // DO WHAT??? + } else { // if an intermediary value returned from PtgRange, PtgUnion or PtgIsect, will be a GenericPtg which holds intermediary values in its vars array + val pc = (p as GenericPtg).vars + for (j in pc!!.indices) { + if ((pc[j] is PtgArea) and (pc[j] !is PtgAreaErr3d)) { + val pa = pc[j].components + for (k in pa.indices) + a.add(pa[k]) + } else + a.add(pc[j]) + } + } + if (first == null) + first = a + else + last = a + } + // now have components for both operands - SUM and package into 1 ptg for return + // For performance reasons, instantiate a PtgMystery as a lightweight GenericPtg which holds intermediary values in it's vars + val retp = PtgMystery() + val retptgs = ArrayList() + for (k in first!!.indices) + retptgs.add(first[k]) + for (k in last!!.indices) + retptgs.add(last[k]) + val ptgs = arrayOfNulls(retptgs.size) + retptgs.toTypedArray() + retp.setVars(ptgs) + return retp + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } catch (e: Exception) { // handle error ala Excel + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 8333035819099274707L + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.java deleted file mode 100644 index 6c54968..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.java +++ /dev/null @@ -1,2176 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.ExcelTools; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.toolkit.CompatibleVector; -import io.starter.toolkit.Logger; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Vector; - - -/* - StatisticalCalculator is a collection of static methods that operate - as the Microsoft Excel function calls do. - - All methods are called with an array of ptg's, which are then - acted upon. A Ptg of the type that makes sense (ie boolean, number) - is returned after each method. -*/ -public class StatisticalCalculator { - - - /** - * AVERAGE - * Returns the average (arithmetic mean) of the arguments. - * Ignores non-numbers - * This cannot recurse, due to averaging needs. - *

                      - * Usage@ AVERAGE(number1,number2, ...) - * Returns@ PtgNumber - */ - protected static Ptg calcAverage(Ptg[] operands) { - Vector vect = new Vector(); - - for (int i = 0; i < operands.length; i++) { - Ptg[] pthings = operands[i].getComponents(); // optimized -- do it once!! -jm - if (pthings != null) { - for (int z = 0; z < pthings.length; z++) { - vect.add(pthings[z]); - } - } else { - Ptg p = operands[i]; - vect.add(p); - } - } - int count = 0; -// double total = 0; - BigDecimal bd = new BigDecimal(0); - for (int i = 0; i < vect.size(); i++) { - Ptg p = (Ptg) vect.elementAt(i); - try { - if (p.isBlank()) continue; - Object ov = p.getValue(); - if (ov != null) { -// total += Double.parseDouble(String.valueOf(ov)); - bd = bd.add(new BigDecimal(Double.parseDouble(String.valueOf(ov)))); - count++; - } - } catch (NumberFormatException e) { - } - } - bd = bd.setScale(15, java.math.RoundingMode.HALF_UP); - double total = bd.doubleValue(); - if (count == 0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - double result = total / count; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * AVERAGEIF function - * Returns the average (arithmetic mean) of all the cells in a range that meet a given criteria. - *

                      - * AVERAGEIF(range,criteria,average_range) - *

                      - * Range is one or more cells to average, including numbers or names, arrays, or references that contain numbers. - * Criteria is the criteria in the form of a number, expression, cell reference, or text that defines which cells are averaged. For example, criteria can be expressed as 32, "32", ">32", "apples", or B4. - * Average_range is the actual set of cells to average. If omitted, range is used. - * Average_range does not have to be the same size and shape as range. The actual cells that are averaged are determined by using the top, left cell in average_range as the beginning cell, and then including cells that correspond in size and shape to range. - * - * @param operands - * @return - */ - protected static Ptg calcAverageIf(Ptg[] operands) { - if (operands.length < 2) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - // range used to test criteria - Ptg[] range = operands[0].getComponents(); - // TODO: if range is blank or a text value returns ERROR_DIV_ZERO - String criteria = operands[1].getString().trim(); - // Parse criteria into op + criteria - int i = Calculator.splitOperator(criteria); - String op = criteria.substring(0, i); // extract operand - criteria = criteria.substring(i); - criteria = Calculator.translateWildcardsInCriteria(criteria); - // Average_range, if present, is used for return values if range passes criteria - Ptg[] average_range = null; - boolean varyRow = false; - if (operands.length > 2) { - //The actual cells that are averaged are determined by using the top, left cell in average_range as the beginning cell, - // and then including cells that correspond in size and shape to range. - int[] rc = null; - average_range = new Ptg[range.length]; - average_range[0] = operands[2].getComponents()[0]; // start with top left of average range - String sheet = ""; - try { - rc = average_range[0].getIntLocation(); - if (range[0].getIntLocation()[0] != range[range.length - 1].getIntLocation()[0]) // determine if range is varied across row or column - varyRow = true; - sheet = ((PtgRef) average_range[0]).getSheetName() + "!"; - } catch (Exception e) { - } - for (int j = 1; j < average_range.length; j++) { - if (varyRow) rc[0]++; - else rc[1]++; - average_range[j] = new PtgRef3d(); - average_range[j].setParentRec(range[0].getParentRec()); - average_range[j].setLocation(sheet + ExcelTools.formatLocation(rc)); - } - } - int nresults = 0; - double result = 0.0; - for (int j = 0; j < range.length; j++) { - Object val = range[j].getValue(); - // TODO: TRUE and FALSE values are ignored - // TODO: blank cells are treated as 0's - if (Calculator.compareCellValue(val, criteria, op)) { - try { - if (average_range != null) { - val = average_range[j].getValue(); - if (val == null) // if a cell is empty it's ignored -- - continue; - } - result += ((Number) val).doubleValue(); - } catch (ClassCastException e) { - } - nresults++; - } - } - - // If no cells in the range meet the criteria, AVERAGEIF returns the #DIV/0! error value - if (nresults == 0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - // otherwise, average - return new PtgNumber(result / nresults); - } - - /** - * AVERAGEIFS - * Returns the average (arithmetic mean) of all cells that meet multiple criteria. - * AVERAGEIFS(average_range,criteria_range1,criteria1,criteria_range2,criteria2…) - * Average_range is one or more cells to average, including numbers or names, arrays, or references that contain numbers. - * Criteria_range1, criteria_range2, … are 1 to 127 ranges in which to evaluate the associated criteria. - * Criteria1, criteria2, … are 1 to 127 criteria in the form of a number, expression, cell reference, or text that define which cells will be averaged. For example, criteria can be expressed as 32, "32", ">32", "apples", or B4. - * - * @param operands - * @return - */ - protected static Ptg calcAverageIfS(Ptg[] operands) { - try { - PtgArea average_range = Calculator.getRange(operands[0]); - Ptg[] averagerangecells = average_range.getComponents(); - if (averagerangecells.length == 0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - String[] ops = new String[(operands.length - 1) / 2]; - String[] criteria = new String[(operands.length - 1) / 2]; - Ptg[][] criteria_cells = new Ptg[(operands.length - 1) / 2][]; - int j = 0; - for (int i = 1; i + 1 < operands.length; i += 2) { - //criteria range - parse and get comprising cells - PtgArea cr = Calculator.getRange(operands[i]); - criteria_cells[j] = cr.getComponents(); - // each criteria_range must contain the same number of rows and columns as the sum_range - if (criteria_cells[j].length != averagerangecells.length) - return new PtgErr(PtgErr.ERROR_VALUE); - // criteria for comparison, including operator - criteria[j] = operands[i + 1].toString(); - // strip operator, if any, and parse criteria - ops[j] = "="; // operator, default is = - int k = Calculator.splitOperator(criteria[j]); - if (k > 0) ops[j] = criteria[j].substring(0, k); // extract operator, if any - criteria[j] = criteria[j].substring(k); - criteria[j] = Calculator.translateWildcardsInCriteria(criteria[j]); - j++; - } - - // test criteria for all cells in range, storing those corresponding average_range cells - // that pass in passesList - // stores the cells that pass the criteria expression and therefore will be averaged - ArrayList passesList = new ArrayList(); - // for each set of criteria, test all cells in range and evaluate - // NOTE: this is an implicit AND evaluation - for (int i = 0; i < averagerangecells.length; i++) { - boolean passes = true; - for (int k = 0; k < criteria.length; k++) { - try { - Object v = criteria_cells[k][i].getValue(); - // If cells in average_range cannot be translated into numbers, AVERAGEIFS returns the #DIV0! error value. - passes = Calculator.compareCellValue(v, criteria[k], ops[k]) && passes; - if (!passes) - break; // no need to continue - } catch (Exception e) { // don't report error - } - } - if (passes) { - passesList.add(averagerangecells[i]); - } - } - // If no cells in the range meet the criteria, AVERAGEIF returns the #DIV/0! error value - if (passesList.size() == 0) - return new PtgErr(PtgErr.ERROR_DIV_ZERO); - - // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); - // Now we sum up the values of these cells and return - double result = 0.0; - for (int i = 0; i < passesList.size(); i++) { - Ptg cell = (Ptg) passesList.get(i); - try { - result += cell.getDoubleVal(); - } catch (Exception e) { - Logger.logErr("MathFunctionCalculator.calcAverageIfS: error obtaining cell value: " + e.toString()); // debugging only; take out when fully tested - // keep going - } - } - - // otherwise, average - return new PtgNumber(result / passesList.size()); - - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_NULL); - } - - /** - * AVEDEV(number1,number2, ...) - * Number1, number2, ... are 1 to 30 arguments for which you want - * the average of the absolute deviations. You can also use a - * single array or a reference to an array instead of arguments - * separated by commas. - *

                      - * The arguments must be either numbers or names, - * arrays, or references that contain numbers. - *

                      - * If an array or reference argument contains text, - * logical values, or empty cells, those values are ignored; - * however, cells with the value zero are included. - */ - protected static Ptg calcAveDev(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - // Get the average for the mean - PtgNumber av = (PtgNumber) StatisticalCalculator.calcAverage(operands); - double average = -0.001; - try { - Double dd = new Double(String.valueOf(av.getValue())); - average = dd.doubleValue(); - } catch (NumberFormatException e) { - } - if (average == -0.001) return PtgCalculator.getError(); - - // work out the total deviation - double total = 0; - int count = 0; - Double d; - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); - for (int i = 0; i < alloperands.length; i++) { - Ptg resPtg = alloperands[i]; - try { // some fields may be text, so handle gracefully - if (resPtg.getValue() != null) { - d = new Double(String.valueOf(resPtg.getValue())); - double dub = d.doubleValue(); - dub = average - dub; - dub = Math.abs(dub); - total += dub; - count++; - } - } catch (NumberFormatException e) { - } - } - // work out the mean deviation - double mean = total / count; - PtgNumber pnum = new PtgNumber(mean); - return pnum; - } - - /** - * AVERAGEA - * Returns the average of its arguments, including numbers, text, - * and logical values - *

                      - * The arguments must be numbers, names, arrays, or references. - * Array or reference arguments that contain text evaluate as 0 (zero). - * Empty text ("") evaluates as 0 (zero). - * Arguments that contain TRUE evaluate as 1; - * arguments that contain FALSE evaluate as 0 (zero). - */ - protected static Ptg calcAverageA(Ptg[] operands) { - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); - double total = 0; - for (int i = 0; i < alloperands.length; i++) { - Ptg p = alloperands[i]; - try { - Object ov = p.getValue(); - if (ov != null) { - if (String.valueOf(ov) == "true") { - total++; - } else { - total += Double.parseDouble(String.valueOf(ov)); - } - } - } catch (NumberFormatException e) { - } - - } - double result = total / alloperands.length; - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - /* -BETADIST - Returns the cumulative beta probability density function - -BETAINV - Returns the inverse of the cumulative beta probability density function - -BINOMDIST - Returns the individual term binomial distribution probability - -CHIDIST - Returns the one-tailed probability of the chi-squared distribution - -CHIINV - Returns the inverse of the one-tailed probability of the chi-squared distribution - -CHITEST - Returns the test for independence - -CONFIDENCE - Returns the confidence interval for a population mean - */ - - /** - * CORREL - * Returns the correlation coefficient between two data sets - * - * @throws CalculationException - */ - protected static Ptg calcCorrel(Ptg[] operands) throws CalculationException { - // get the covariance - PtgNumber pnum = (PtgNumber) calcCovar(operands); - double covar = pnum.getVal(); - Ptg[] xPtg = new Ptg[1]; - xPtg[0] = operands[0]; - Ptg[] yPtg = new Ptg[1]; - yPtg[0] = operands[1]; - pnum = (PtgNumber) calcAverage(xPtg); - double xMean = pnum.getVal(); - pnum = (PtgNumber) calcAverage(yPtg); - double yMean = pnum.getVal(); - double[] xVals = PtgCalculator.getDoubleValueArray(xPtg); - double[] yVals = PtgCalculator.getDoubleValueArray(yPtg); - if (xVals == null || yVals == null) - return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double xstat = 0; - for (int i = 0; i < xVals.length; i++) { - xstat += Math.pow((xVals[i] - xMean), 2); - } - xstat = xstat / xVals.length; - xstat = Math.sqrt(xstat); - double ystat = 0; - for (int i = 0; i < yVals.length; i++) { - ystat += Math.pow((yVals[i] - yMean), 2); - } - ystat = ystat / yVals.length; - ystat = Math.sqrt(ystat); - double retval = covar / (ystat * xstat); - return new PtgNumber(retval); - } - - /** - COUNT - Counts how many numbers are in the list of arguments - */ - /** - * Counts the number of cells that contain numbers - * and or dates. - * Use COUNT to get the number of entries in a number - * field in a range or array of numbers. - *

                      - * Usage@ COUNT(A1:A5,A9) - * Return@ PtgInt - * TODO: implement counting of dates! - */ - protected static Ptg calcCount(Ptg[] operands) { - int count = 0; - for (int i = 0; i < operands.length; i++) { - Ptg[] pref = operands[i].getComponents(); // optimized -- do it once!! -jm - if (pref != null) { // it is some sort of range - for (int z = 0; z < pref.length; z++) { - Object o = pref[z].getValue(); - if (o != null) { - try { - Double n = new Double(String.valueOf(o)); - count++; - } catch (NumberFormatException e) { - } - } - } - } else { // it's a single ptgref - Object o = operands[i].getValue(); - if (o != null) { - try { - Double n = new Double(String.valueOf(o)); - count++; - } catch (NumberFormatException e) { - } - } - } - } - PtgInt pint = new PtgInt(count); - return pint; - - } - - /** - * COUNTA - * Counts the number of non-blank cells within a range - */ - protected static Ptg calcCountA(Ptg[] operands) { - Ptg[] allops = PtgCalculator.getAllComponents(operands); - int count = 0; - for (int i = 0; i < allops.length; i++) { - /* 20081120 KSC: blnaks are handled differently as Excel counts blank cells as 0's - Object o = allops[i].getValue(); - if (o != null) count++; - */ - if (!allops[i].isBlank()) - count++; - } - return new PtgInt(count); - } - - /** - * COUNTBLANK - * Counts the number of blank cells within a range - */ - protected static Ptg calcCountBlank(Ptg[] operands) { - Ptg[] allops = PtgCalculator.getAllComponents(operands); - int count = 0; - for (int i = 0; i < allops.length; i++) { - if (allops[i].isBlank()) // 20081112 KSC: was Object o = getValue(); if (o==null) count++; - count++; - } - return new PtgInt(count); - } - - - /** - * COUNTIF - * Counts the number of non-blank cells within a range which meet the given criteria - * BRUTAL!! - */ - protected static Ptg calcCountif(Ptg[] operands) - throws FunctionNotSupportedException { - if (operands.length != 2) return PtgCalculator.getError(); - String matchStr = String.valueOf(operands[1].getValue()); - boolean donumber = true; - double matchDub = 0; - try { // this method matches strings or numbers, here is where we differentiate - Double d = new Double(matchStr); - matchDub = d.doubleValue(); - } catch (Exception e) { - donumber = false; - } - double count = 0; - Ptg[] pref = operands[0].getComponents(); // optimize by doing it one time!!! this thing gets slow....-jm - if (pref != null) { // it is some sort of range - for (int z = 0; z < pref.length; z++) { - Object o = pref[z].getValue(); - if (o != null) { - String match2 = o.toString(); - if (donumber) { - try { - Double d = new Double(match2); - double matchDub2 = d.doubleValue(); - if (matchDub == matchDub2) count++; - } catch (NumberFormatException e) { - } - } else { - if (matchStr.equalsIgnoreCase(match2)) count++; - } - } - } - } else { // it's a single ptgref - Object o = operands[0].getValue(); - if (o != null) { - if (o != null) { - String match2 = o.toString(); - if (donumber) { - try { - Double d = new Double(match2); - double matchDub2 = d.doubleValue(); - if (matchDub == matchDub2) count++; - } catch (NumberFormatException e) { - } - } else { - if (matchStr.equalsIgnoreCase(match2)) count++; - } - } - - } - } - PtgNumber pnum = new PtgNumber(count); - return pnum; - } - - /** - * COUNTIFS - * criteria_range1 Required. The first range in which to evaluate the associated criteria. - * criteria1 Required. The criteria in the form of a number, expression, cell reference, or text that define which cells will be counted. For example, criteria can be expressed as 32, ">32", B4, "apples", or "32". - * criteria_range2, criteria2, ... Optional. Additional ranges and their associated criteria. Up to 127 range/criteria pairs are allowed. - * - * @param operands - * @return - */ - protected static Ptg calcCountIfS(Ptg[] operands) { - try { - String[] ops = new String[operands.length / 2]; - String[] criteria = new String[operands.length / 2]; - Ptg[][] criteria_cells = new Ptg[operands.length / 2][]; - for (int i = 0; i + 1 < operands.length; i += 2) { - //criteria range - parse and get comprising cells - PtgArea cr = Calculator.getRange(operands[i]); - criteria_cells[i / 2] = cr.getComponents(); - // each criteria_range must contain the same number of rows and columns as the criteriarange - if (i > 0 && criteria_cells[i / 2].length != criteria_cells[0].length) - return new PtgErr(PtgErr.ERROR_VALUE); - // criteria for comparison, including operator - criteria[i / 2] = operands[i + 1].toString(); - // strip operator, if any, and parse criteria - ops[i / 2] = "="; // operator, default is = - int k = Calculator.splitOperator(criteria[i / 2]); - if (k > 0) ops[i / 2] = criteria[i / 2].substring(0, k); // extract operator, if any - criteria[i / 2] = criteria[i / 2].substring(k); - criteria[i / 2] = Calculator.translateWildcardsInCriteria(criteria[i / 2]); - } - - // test criteria for all cells in range, counting each cell that passes - // for each set of criteria, test all cells in range and evaluate - int count = 0; - for (int i = 0; i < criteria_cells[0].length; i++) { - boolean passes = true; - for (int k = 0; k < criteria.length; k++) { - try { - Object v = criteria_cells[k][i].getValue(); - // the criteria argument is a reference to an empty cell, the COUNTIFS function treats the empty cell as a 0 value. - passes = Calculator.compareCellValue(v, criteria[k], ops[k]) && passes; - if (!passes) - break; // no need to continue - } catch (Exception e) { // don't report error - } - } - if (passes) - count++; - } - - return new PtgNumber(count); - - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_NULL); - } - - /** - * COVAR - * Returns covariance, the average of the products of paired deviations - * - * @throws CalculationException - */ - protected static Ptg calcCovar(Ptg[] operands) throws CalculationException { - Ptg[] xMeanPtg = new Ptg[1]; - xMeanPtg[0] = operands[0]; - Ptg[] yMeanPtg = new Ptg[1]; - yMeanPtg[0] = operands[1]; - PtgNumber pnum = (PtgNumber) calcAverage(xMeanPtg); - double xMean = pnum.getVal(); - pnum = (PtgNumber) calcAverage(yMeanPtg); - double yMean = pnum.getVal(); - double[] xVals = PtgCalculator.getDoubleValueArray(xMeanPtg); - double[] yVals = PtgCalculator.getDoubleValueArray(yMeanPtg); - if (xVals == null || yVals == null) - return new PtgErr(PtgErr.ERROR_NA);//propagate error - double xyMean = 0; - if (xVals.length == yVals.length) { - int addvals = 0; - for (int i = 0; i < xVals.length; i++) { - addvals += (xVals[i] * yVals[i]); - } - xyMean = addvals / xVals.length; - } else { - return new PtgErr(PtgErr.ERROR_NA); - } - - double retval = xyMean - (xMean * yMean); - return new PtgNumber(retval); - } - /* -CRITBINOM - Returns the smallest value for which the cumulative binomial distribution is less than or equal to a criterion value - -DEVSQ - Returns the sum of squares of deviations - -EXPONDIST - Returns the exponential distribution - -FDIST - Returns the F probability distribution - -FINV - Returns the inverse of the F probability distribution - -FISHER - Returns the Fisher transformation - -FISHERINV - Returns the inverse of the Fisher transformation - */ - - /** - * FORECAST - * Returns a value along a linear trend - * - * @throws CalculationException - */ - protected static Ptg calcForecast(Ptg[] operands) throws CalculationException { - if (operands.length != 2) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg[] p = new Ptg[2]; - p[0] = operands[0]; - p[1] = operands[1]; - PtgNumber icept = (PtgNumber) calcIntercept(p); - double intercept = icept.getVal(); - PtgNumber slp = (PtgNumber) calcSlope(p); - double slope = slp.getVal(); - Ptg px = operands[0]; - double knownX = new Double(String.valueOf(px.getValue())).doubleValue(); - double retval = (slope * knownX) + intercept; - return new PtgNumber(retval); - } - - /** - * FREQUENCY - * Returns a frequency distribution as a vertical array - * - * @throws CalculationException - */ - protected static Ptg calcFrequency(Ptg[] operands) throws CalculationException { - Ptg[] firstArr = PtgCalculator.getAllComponents(operands[0]); - Ptg[] secondArr = PtgCalculator.getAllComponents(operands[1]); - CompatibleVector t = new CompatibleVector(); - for (int i = 0; i < secondArr.length; i++) { - Ptg p = secondArr[i]; - try { - Double d = new Double(String.valueOf(p.getValue())); - t.addOrderedDouble(d); - } catch (NumberFormatException e) { - } - } - Double[] binsArr = new Double[t.size()]; - double[] dataArr; - dataArr = PtgCalculator.getDoubleValueArray(firstArr); - if (dataArr == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - t.toArray(binsArr); - int[] retvals = new int[secondArr.length + 1]; - for (int i = 0; i < dataArr.length; i++) { - for (int x = 0; x < binsArr.length; x++) { - if (dataArr[i] <= binsArr[x].doubleValue()) { - retvals[x]++; - x = binsArr.length; - } else if (dataArr[i] > binsArr[binsArr.length - 1].doubleValue()) { - retvals[binsArr.length]++; - x = binsArr.length; - } - } - } - // keep the original locations, so we can put the end result array in the correct order. - // not used! double[] originalLocs = PtgCalculator.getDoubleValueArray(secondArr); - String ret = "{"; - for (int i = 0; i < retvals.length; i++) { - ret += retvals[i] + ","; - } - ret = ret.substring(0, ret.length() - 1); // get rid of final comma - ret += "}"; - - PtgArray returnArr = new PtgArray(); - returnArr.setVal(ret); - return returnArr; - } - /* -FTEST - Returns the result of an F-test - -GAMMADIST - Returns the gamma distribution - -GAMMAINV - Returns the inverse of the gamma cumulative distribution - -GAMMALN - Returns the natural logarithm of the gamma function, G(x) - -GEOMEAN - Returns the geometric mean - -GROWTH - Returns values along an exponential trend - -HARMEAN - Returns the harmonic mean - -HYPGEOMDIST - Returns the hypergeometric distribution - */ - - /** - * INTERCEPT - * Returns the intercept of the linear regression line - * - * @throws CalculationException - */ - protected static Ptg calcIntercept(Ptg[] operands) throws CalculationException { - double[] yvals; - yvals = PtgCalculator.getDoubleValueArray(operands[0]); - - double[] xvals = PtgCalculator.getDoubleValueArray(operands[1]); - if (xvals == null || yvals == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double sumXVals = 0; - for (int i = 0; i < xvals.length; i++) { - sumXVals += xvals[i]; - } - double sumYVals = 0; - for (int i = 0; i < yvals.length; i++) { - sumYVals += yvals[i]; - } - double sumXYVals = 0; - for (int i = 0; i < yvals.length; i++) { - sumXYVals += xvals[i] * yvals[i]; - } - double sqrXVals = 0; - for (int i = 0; i < xvals.length; i++) { - sqrXVals += xvals[i] * xvals[i]; - } - double toparg = (sumXVals * sumXYVals) - (sumYVals * sqrXVals); - double bottomarg = (sumXVals * sumXVals) - (sqrXVals * xvals.length); - double res = toparg / bottomarg; - return new PtgNumber(res); - } - /* -KURT - Returns the kurtosis of a data set - */ - - /** - * LARGE - *

                      - * Returns the k-th largest value in a data set. You can use this function to select a value based on its relative standing. - * For example, you can use LARGE to return the highest, runner-up, or third-place score. - *

                      - * LARGE(array,k) - *

                      - * Array is the array or range of data for which you want to determine the k-th largest value. - * K is the position (from the largest) in the array or cell range of data to return. - *

                      - * If array is empty, LARGE returns the #NUM! error value. - * If k ≤ 0 or if k is greater than the number of data points, LARGE returns the #NUM! error value. - * If n is the number of data points in a range, then LARGE(array,1) returns the largest value, and LARGE(array,n) returns the smallest value. - * - * @throws CalculationException - */ - protected static Ptg calcLarge(Ptg[] operands) throws CalculationException { - if (operands.length != 2) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg rng = operands[0]; - Ptg[] array = PtgCalculator.getAllComponents(rng); - if (array.length == 0) return new PtgErr(PtgErr.ERROR_NUM); - int k = new Double(PtgCalculator.getDoubleValueArray(operands[1])[0]).intValue(); - if (k <= 0 || k > array.length) - return new PtgErr(PtgErr.ERROR_NUM); - - CompatibleVector sortedValues = new CompatibleVector(); - for (int i = 0; i < array.length; i++) { - Ptg p = array[i]; - try { - Double d = new Double(String.valueOf(p.getValue())); - sortedValues.addOrderedDouble(d); - } catch (NumberFormatException e) { - } - } - // reverse array - Double[] dubRefs = new Double[sortedValues.size()]; - for (int i = 0; i < dubRefs.length; i++) { - dubRefs[i] = (Double) sortedValues.last(); - sortedValues.remove(sortedValues.size() - 1); - } - - return new PtgNumber((dubRefs[k - 1]).doubleValue()); -/* - - try { - Ptg[] parray= PtgCalculator.getAllComponents(operands[0]); - Object[] array= new Object[parray.length]; - for (int i= 0; i < array.length; i++) { - array[i]= parray[i].getValue(); - if (array[i] instanceof Integer) { // convert all to double if possible for sort below (cannot have mixed array for Arrays.sort) - try { - array[i]= new Double(((Integer)array[i]).intValue()); - } catch (Exception e) { - } - } - } - // now sort - java.util.Arrays.sort(array); - int position= (int) PtgCalculator.getLongValue(operands[1]); - // now return the nth item in the sorted (asc) array - if (position >=0 && position <=array.length) { - Object ret= array[array.length-position]; - if (ret instanceof Double) - return new PtgNumber(((Double)ret).doubleValue()); - else if (ret instanceof Boolean) - return new PtgBool(((Boolean)ret).booleanValue()); - else if (ret instanceof String) - return new PtgStr((String)ret); - } - } catch (Exception e) { - - } - return new PtgErr(PtgErr.ERROR_NUM); -*/ - } - - - /** - * LINEST - * Returns the parameters of a linear trend - * - * @throws CalculationException - */ - protected static Ptg calcLineSt(Ptg[] operands) throws CalculationException { - double[] ys = PtgCalculator.getDoubleValueArray(operands[0]); - if (ys == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double[] xs; - if (operands.length == 1 || operands[1] instanceof PtgMissArg) { - // create a 1,2,3 array - xs = new double[ys.length]; - for (int i = 0; i < ys.length; i++) - xs[i] = i; - } else { - xs = PtgCalculator.getDoubleValueArray(operands[1]); - if (xs == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - } - - boolean getIntercept = false; - if (operands.length > 2) { - if (!(operands[2] instanceof PtgMissArg)) - getIntercept = PtgCalculator.getBooleanValue(operands[2]); - } - boolean statistics = false; - if (operands.length > 3) { - if (!(operands[3] instanceof PtgMissArg)) - statistics = PtgCalculator.getBooleanValue(operands[3]); - } - - Ptg ps = calcSlope(operands); - if (ps instanceof PtgErr) return ps; - PtgNumber Pslope = (PtgNumber) calcSlope(operands); - double slope = Pslope.getVal(); // a1 val - PtgNumber Pintercept = (PtgNumber) calcIntercept(operands); - double intercept = Pintercept.getVal(); // b1 val - - if (operands.length > 3 && (operands[3] instanceof PtgBool || operands[3] instanceof PtgInt)) { - boolean b = PtgCalculator.getBooleanValue(operands[3]); - if (!b) { - String retstr = "{" + slope + "," + intercept + "},"; - retstr += "{" + slope + "," + intercept + "},"; - retstr += "{" + slope + "," + intercept + "},"; - retstr += "{" + slope + "," + intercept + "},"; - retstr += "{" + slope + "," + intercept + "}"; - PtgArray para = new PtgArray(); - para.setVal(retstr); - return para; - } - } - Ptg[] p = new Ptg[1]; - - // figure out the stdev of the slope - PtgNumber Psteyx = (PtgNumber) calcSteyx(operands); - double steyx = Psteyx.getVal(); // b3 val - - // calc the y error percentage - double yError = steyx * steyx; - p[0] = operands[1]; - PtgNumber vp = (PtgNumber) calcVarp(p); - double Sxx = vp.getVal() * ys.length; - yError = yError / Sxx; - yError = Math.sqrt(yError); // A2 val - - // calculate degrees of freedom - int degFreedom = ys.length - 2; // b4 val - - // calculate standard error of intercept - double sumXsquared = 0; - double sumSquaredX = 0; - double sumXYsquared = 0; - for (int i = 0; i < xs.length; i++) { - sumSquaredX += (xs[i] * xs[i]); - sumXsquared += xs[i]; - sumXYsquared += (xs[i] * ys[i]); - } - sumXsquared *= sumXsquared; - sumXYsquared *= sumXYsquared; - double interceptError = 1 / (xs.length - (sumXsquared / sumSquaredX)); - interceptError = Math.sqrt(interceptError); - interceptError *= steyx; //b2val - - // calculate residual SS - // first create array of predicted values for the linear array - double[] predicted = new double[xs.length]; - double residualSS = 0; // b5value - for (int i = 0; i < xs.length; i++) { - predicted[i] = intercept + (xs[i] * slope); - double d = (predicted[i] - ys[i]); - residualSS += (d * d); - } - - // calculate regression SS - p[0] = operands[0]; - PtgNumber pnum = (PtgNumber) calcAverage(p); - double average = pnum.getVal(); - double regressionSS = 0; - for (int i = 0; i < xs.length; i++) { - double d = (predicted[i] - average); - regressionSS += (d * d);//A5 value - } - p = new Ptg[2]; - p[0] = operands[0]; - p[1] = operands[1]; - pnum = (PtgNumber) calcRsq(p); - double r2 = pnum.getVal(); // A3 - - // calculate the F value - double F = (regressionSS / 1) / (residualSS / degFreedom); // A4 - - // construct the string for creating ptgarray - String retstr = "{" + slope + "," + intercept + "},"; - retstr += "{" + yError + "," + interceptError + "},"; - retstr += "{" + r2 + "," + steyx + "},"; - retstr += "{" + F + "," + degFreedom + "},"; - retstr += "{" + regressionSS + "," + residualSS + "}"; - - PtgArray parr = new PtgArray(); - parr.setVal(retstr); - - return parr; - } - /* -LOGEST - Returns the parameters of an exponential trend - -LOGINV - Returns the inverse of the lognormal distribution - -LOGNORMDIST - Returns the cumulative lognormal distribution - */ - - - /** - * MAX - * Returns the largest value in a set of values. - * Ignores non-number fields - * Recursively calls for ranges. - *

                      - * Usage@ MAX(number1,number2, ...) - * returns@ PtgNumber - */ - //untested - protected static Ptg calcMax(Ptg[] operands) { - double result = java.lang.Double.MIN_VALUE; // 20090129 KSC -1; - Double d = null; - for (int i = 0; i < operands.length; i++) { - Ptg[] pthings = operands[i].getComponents(); // optimized -- do it once!! -jm - if (pthings != null) { - Ptg resPtg = StatisticalCalculator.calcMax(pthings); - try { // some fields may be text, so handle gracefully - if (resPtg.getValue() != null) d = new Double(String.valueOf(resPtg.getValue())); - if (d.doubleValue() > result) { - result = d.doubleValue(); - } - } catch (NumberFormatException e) { - } - } else { - Ptg p = operands[i]; - try { - Object ov = p.getValue(); - if (ov != null) - d = new Double(String.valueOf(ov)); - if (d.doubleValue() > result) { - result = d.doubleValue(); - } - } catch (NumberFormatException e) { - } catch (NullPointerException e) { - } - } - } - if (result == java.lang.Double.MIN_VALUE) // 20090129 KSC: - result = 0; //appears to be default in error situations - PtgNumber pnum = new PtgNumber(result); - return pnum; - } - - /** - * MAXA - * Returns the maximum value in a list of arguments, including numbers, text, and logical values - *

                      - * Arguments can be the following: numbers; names, arrays, or references that contain numbers; text representations of numbers; or logical values, such as TRUE and FALSE, in a reference. - * Logical values and text representations of numbers that you type directly into the list of arguments are counted. - */ - protected static Ptg calcMaxA(Ptg[] operands) { - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); - if (alloperands.length == 0) - return new PtgNumber(0); - double max = Double.MIN_VALUE; - for (int i = 0; i < alloperands.length; i++) { - Object o = alloperands[i].getValue(); - try { - double d = Double.MIN_VALUE; - if (o instanceof Number) - d = ((Number) o).doubleValue(); - else if (o instanceof Boolean) - d = (((Boolean) o).booleanValue() ? 1 : 0); - else - d = new Double(o.toString()).doubleValue(); - max = Math.max(max, d); - } catch (NumberFormatException e) { - // Arguments that are error values or text that cannot be translated into numbers cause errors. - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - return new PtgNumber(max); - } - - /** - * MEDIAN - * Returns the median of the given numbers - */ - protected static Ptg calcMedian(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); - CompatibleVector t = new CompatibleVector(); - double retval = 0; - for (int i = 0; i < alloperands.length; i++) { - Ptg p = alloperands[i]; - try { - Double d = new Double(String.valueOf(p.getValue())); - t.addOrderedDouble(d); - } catch (NumberFormatException e) { - } - } - - try { - Double[] dub = new Double[t.size()]; - t.toArray(dub); - double dd = (double) t.size() % 2; - if ((double) t.size() % 2 == 0) { - int firstValLoc = ((t.size()) / 2) - 1; - int lastValLoc = firstValLoc + 1; - double firstVal = dub[firstValLoc].doubleValue(); - double lastVal = dub[lastValLoc].doubleValue(); - retval = (firstVal + lastVal) / 2; - } else { - // it's odd - int firstValLoc = ((t.size() - 1) / 2); - double firstVal = dub[firstValLoc].doubleValue(); - retval = firstVal; - } - PtgNumber pnum = new PtgNumber(retval); - return pnum; - } catch (ArrayIndexOutOfBoundsException e) { // 20090701 KSC: catch exception - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - /** - * MIN - * Returns the smallest number in a set of values. - * Ignores non-number fields. Note that it also recursivly calls itself - * for things like PtgRange. - *

                      - * Usage@ MIN(number1,number2, ...) - * returns PtgNumber - */ - protected static Ptg calcMin(Ptg[] operands) { - double result = java.lang.Double.MAX_VALUE; - Double d = null; - for (int i = 0; i < operands.length; i++) { - Ptg[] pthings = operands[i].getComponents(); // optimized -- do it once!! -jm - if (pthings != null) { - Ptg resPtg = StatisticalCalculator.calcMin(pthings); - try { // some fields may be text, so handle gracefully - if (resPtg instanceof PtgErr) return resPtg; // 20090205 KSC: propagate error - if (resPtg.getValue() != null) { - d = new Double(String.valueOf(resPtg.getValue())); - // 20090129 KSC; if (d.doubleValue() < result || result == -1){result = d.doubleValue();} // 20070215 KSC: only access d if not null! - if (d.doubleValue() < result) { - result = d.doubleValue(); - } // 20070215 KSC: only access d if not null! - } - } catch (NumberFormatException e) { - } catch (NullPointerException e) { - }// 20070209 KSC - } else { - Ptg p = operands[i]; - try { - Object ov = p.getValue(); - if (ov != null) { - if (ov.toString().equals(new PtgErr(PtgErr.ERROR_NA).toString())) // 20090205 KSC: propagate error value - return new PtgErr(PtgErr.ERROR_NA); - d = new Double(String.valueOf(ov)); - // 20090129 KSC; result is defaulted to max - if (d.doubleValue() < result) { - result = d.doubleValue(); - } // 20070215 KSC: only access d if not null! - } - } catch (NumberFormatException e) { - } catch (NullPointerException e) { - } - } - } - if (result == java.lang.Double.MAX_VALUE) // 20090129 KSC: - result = 0; //appears to be default in error situations - return new PtgNumber(result); -// return pnum; - } - - /** - * MINA - * Returns the smallest value in a list of arguments, including numbers, text, and logical values - *

                      - * Arguments can be the following: numbers; names, arrays, or references that contain numbers; text representations of numbers; or logical values, such as TRUE and FALSE, in a reference. - * Logical values and text representations of numbers that you type directly into the list of arguments are counted. - */ - protected static Ptg calcMinA(Ptg[] operands) { - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); - if (alloperands.length == 0) - return new PtgNumber(0); - double min = Double.MAX_VALUE; - for (int i = 0; i < alloperands.length; i++) { - Object o = alloperands[i].getValue(); - try { - double d = Double.MAX_VALUE; - if (o instanceof Number) - d = ((Number) o).doubleValue(); - else if (o instanceof Boolean) - d = (((Boolean) o).booleanValue() ? 1 : 0); - else - d = new Double(o.toString()).doubleValue(); - min = Math.min(min, d); - } catch (NumberFormatException e) { - // Arguments that are error values or text that cannot be translated into numbers cause errors. - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - return new PtgNumber(min); - } - - /** - * MODE - * Returns the most common value in a data set - */ - protected static Ptg calcMode(Ptg[] operands) { - Ptg[] alloperands = PtgCalculator.getAllComponents(operands); - Vector vals = new Vector(); - Vector occurences = new Vector(); - double retval = 0; - for (int i = 0; i < alloperands.length; i++) { - Ptg p = alloperands[i]; - try { - Double d = new Double(String.valueOf(p.getValue())); - if (vals.contains(d)) { - int loc = vals.indexOf(d); - Double nums = (Double) occurences.get(loc); - Double newnum = new Double(nums.doubleValue() + 1); - occurences.setElementAt(newnum, loc); - } else { - vals.add(d); - occurences.add(new Double(1)); - } - } catch (NumberFormatException e) { - } - } - double biggest = 0; - double numvalues = 0; - for (int i = 0; i < vals.size(); i++) { - Double size = (Double) occurences.elementAt(i); - if (size.doubleValue() > biggest) { - biggest = size.doubleValue(); - Double newhigh = (Double) vals.elementAt(i); - retval = newhigh.doubleValue(); - } - } - PtgNumber pnum = new PtgNumber(retval); - return pnum; - } - - /* -NEGBINOMDIST - Returns the negative binomial distribution - */ - - - /** - * NORMDIST - * Returns the normal cumulative distribution - * NORMDIST(x,mean,standard_dev,cumulative) - * X is the value for which you want the distribution. - * Mean is the arithmetic mean of the distribution. - * Standard_dev is the standard deviation of the distribution. - * Cumulative is a logical value that determines the form of the function. - * If cumulative is TRUE, NORMDIST returns the cumulative distribution function; - * if FALSE, it returns the probability mass function. - *

                      - * ******************************************************************************** - * IMPORTANT NOTE: when Cumulative=TRUE the results are not accurate to 9 siginfiicant digits in all cases - * (When cumulative = TRUE, the formula is the integral from negative infinity to x of the given formula) - * ******************************************************************************** - */ - protected static Ptg calcNormdist(Ptg[] operands) { - if (operands.length < 4) return new PtgErr(PtgErr.ERROR_VALUE); - try { - //If mean or standard_dev is nonnumeric, NORMDIST returns the #VALUE! error value. - double x = operands[0].getDoubleVal(); - double mean = operands[1].getDoubleVal(); - // if standard_dev ≤ 0, NORMDIST returns the #NUM! error value. - double stddev = operands[2].getDoubleVal(); - if (stddev <= 0) return new PtgErr(PtgErr.ERROR_NUM); - boolean cumulative = PtgCalculator.getBooleanValue(operands[3]); - // If mean = 0, standard_dev = 1, and cumulative = TRUE, NORMDIST returns the standard normal distribution, NORMSDIST. - if (mean == 0 && stddev == 1.0 && cumulative) - return calcNormsdist(operands); - - if (!cumulative) { // return the probability mass function. *** definite excel algorithm - double a = Math.sqrt(2 * Math.PI * Math.pow(stddev, 2)); - a = 1.0 / a; - double exp = Math.pow(x - mean, 2); - exp = exp / (2 * Math.pow(stddev, 2)); - double b = Math.exp(-exp); - return new PtgNumber(a * b); - } else { - // When cumulative = TRUE, the formula is the integral from negative infinity to x of the given formula. - // = the cumulative distribution function - Ptg[] o = {new PtgNumber((x - mean) / (stddev * Math.sqrt(2)))}; - Ptg erf = EngineeringCalculator.calcErf(o); - double cdf = 0.5 * (1 + erf.getDoubleVal()); - return new PtgNumber(cdf); -/* // try this: - Ptg[] o= { new PtgNumber((x-mean)/(stddev))}; - return calcNormsdist(o); - */ - } - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - /** - The NORMSDIST function returns the result of the standard normal cumulative distribution function - for a particular value of the random variable X. The Excel function adheres to the - following mathematical approximation, P(x), of the following - standard normal cumulative distribution function (CDF): - * P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - t = 1/(1+px) - p = 0.2316419 - b1 = 0.319381530 - b2 = -0.356563782 - b3 = 1.781477937 - b4 = -1.821255978 - b5 = 1.330274429 - - with the following parameters: - - abs(error(x))<7.5 * 10^-8 - - The NORMSDIST function returns the result of the standard normal - CDF for a standard normal random variable Z with a mean of 0 (zero) - and a standard deviation of 1. The CDF is found by taking the integral - of the following standard normal probability density function - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - - from negative infinity to the value (z) of the random variable in question. - The result of the integral gives the probability that Z will occur between the - values of negative infinity and z. - - NORMSDIST(z) must be evaluated by using an approximation procedure. - Earlier versions of Excel used the same procedure for all values of z. - For Excel 2003, two different approximations are used: - one for |z| less than or equal to five, and a second for |z| greater than five. - The two new procedures are each more accurate than the previous procedure - over the range that they are applied. In earlier versions of Excel, - accuracy deteriorates in the tails of the distribution yielding three significant - digits for z = 4 as reported in Knusel's paper. Also, in the neighborhood of z = 1.2, - NORMSDIST yields only six significant digits. However, in practice, this is likely - to be sufficient for most users. - - INFO ATP DEFINITION NORMDIST NOVEMBER 2006: - The NORMSDIST function returns the result of the standard normal cumulative distribution function for a particular value of the random variable X. - The Microsoft Excel function adheres to the following mathematical approximation, P(x), of the standard normal CDF - - P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - t = 1/(1+px) - p = 0.2316419 - b1 = 0.319381530 - b2 = -0.356563782 - b3 = 1.781477937 - b4 = -1.821255978 - b5 = 1.330274429 - - - with these parameters, abs(error(x))<7.5 * 10^-8. - - - In summary, if you use Excel 2002 and earlier, you should be satisfied with NORMSDIST. - However, if you must have highly accurate NORMSDIST(z) values for z far from 0 - (such as |z| greater than or equal to four), Excel 2003 might be required. - NORMSDIST(-4) = 0.0000316712; earlier versions would be accurate only as far as 0.0000317. - - from a forum: - Take into consideration that Z is related to x, xm(mean) and s(std.dev.) - through the expression Z = (x - xm) / s. - This means that as soon as you get Z, you can proceed and calculate the integral of the - CDF by using P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x). - - I wish you good code. - - Some other identities that express NORMSDIST in terms of other functions - that have no closed form are - NormSDist(x) = ErfC(-x/Sqrt(2))/2 = (1-Erf(-x/Sqrt(2)))/2 for x<=0 - NormSDist(x) = 1-ErfC(x/Sqrt(2))/2 = (1+Erf(x/Sqrt(2)))/2 for x>=0 - NormSDist(x) = (1–GammaDist(x^2/2,1/2,1,TRUE))/2 for x<=0 - NormSDist(x) = (1+GammaDist(x^2/2,1/2,1,TRUE))/2 for x>=0 - NormSDist(x) = ChiDist(x^2,1)/2 for x<=0 - NormSDist(x) = 1-ChiDist(x^2,1)/2 for x>=0 - - // for 2002: - The NORMSDIST function returns the result of the standard normal cumulative distribution - function for a particular value of the random variable X. The Excel function adheres to the - following mathematical approximation, P(x), of the following standard normal cumulative - distribution function (CDF) - - P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - t = 1/(1+px) - p = 0.2316419 - b1 = 0.319381530 - b2 = -0.356563782 - b3 = 1.781477937 - b4 = -1.821255978 - b5 = 1.330274429 - - - with the following parameters: - abs(error(x))<7.5 * 10^-8 - - The NORMSDIST function returns the result of the standard normal CDF for a standard - normal random variable Z with a mean of 0 (zero) and a standard deviation of 1. The CDF - is found by taking the integral of the following standard normal probability density - function - - Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) - - from negative infinity to the value (z) of the random variable in question. The result - of the integral gives the probability that Z will occur between the values of negative - infinity and z. * - - from openoffice: - The wrong results in NORMSDIST are due to cancellation for small negative - values, where gauss() is near -0.5 - The problem can be solved in two ways: - (1) Use NORMSDIST(x)= 0.5*ERFC(-x/SQRT(2)). Unfortunaly ERFC is only an addin - function, see my issue 97091. - (2) Use NORMSDIST(x) - = 0.5+0.5*GetLowRegIGamma(0.5,0.5*x*x) for x>=0 - = 0.5*GetUpRegIGamma(0.5,0.5*x*x) for x<0 - - - From a forum: - For z less than 2, ERF = 2/SQRT (pi) * e^(-z^2) * z (1+ (2z^2)/3 + ((2z^2)^2)/15 + … - For z greater than 2, ERF = 1- (e^(-z^2))/(SQRT(pi)) * (1/z - 1/(2z^3) + 3/(4z^5) -….) - */ - /** - * NORMSDIST - * Returns the standard normal cumulative distribution - *

                      - * NORMSDIST(z) returns the probability that the observed value of a - * standard normal random variable will be less than or equal to z. - * A standard normal random variable has mean 0 and standard deviation 1 - * (and also variance 1 because variance = standard deviation squared). - *

                      - * NOTE: THIS FUNCTION IS ACCURATE AS COMPARED TO EXCEL VALUES ONLY UP TO 7 SIGNIFICANT DIGITS - */ - protected static Ptg calcNormsdist(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - try { - double result; - double x = operands[0].getDoubleVal(); - final double b1 = 0.319381530; - final double b2 = -0.356563782; - final double b3 = 1.781477937; - final double b4 = -1.821255978; - final double b5 = 1.330274429; - final double p = 0.2316419; - final double c = 0.39894228; - - // below is consistently correct to at least 7 decimals using a range of test values - if (x >= 0.0) { - double t = 1.0 / (1.0 + p * x); - result = (1.0 - c * Math.exp(-x * x / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1)); - } else { - double t = 1.0 / (1.0 - p * x); - result = (c * Math.exp(-x * x / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1)); - } -/* - // try this one: - double z= (1/(Math.sqrt(2*Math.PI))*Math.exp(-Math.pow(x, 2)/2.0)); - double t = 1/(1+p*x); - double e= EngineeringCalculator.calcErf(operands).getDoubleVal(); - result = 1 -z*(b1*t+b2*Math.pow(t, 2)+b3*Math.pow(t, 3)+b4*Math.pow(t, 4)+b5*Math.pow(t, 5))+e; -*/ - BigDecimal bd = new BigDecimal(result); - bd.setScale(15, java.math.RoundingMode.HALF_UP); - return new PtgNumber(bd.doubleValue()); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - /** - * NORMSINV - * Returns the inverse of the standard normal cumulative distribution. The distribution has a mean of zero and a standard deviation of one. - *

                      - * Syntax - * NORMSINV(probability) - * Probability is a probability corresponding to the normal distribution. - *

                      - * If probability is nonnumeric, NORMSINV returns the #VALUE! error value. - * If probability < 0 or if probability > 1, NORMSINV returns the #NUM! error value. - *

                      - * Because the calculation of the NORMSINV function uses a systematic search - * over the returned values of the NORMSDIST function, the accuracy of the - * NORMSDIST function is critical. - *

                      - * Also, the search must be sufficiently refined that it "homes in" on an appropriate - * answer. To use the textbook Normal probability distribution table as an analogy, - * entries in the table must be accurate. Also, the table must contain so many entries - * that you can find the appropriate row of the table that yields a probability that is - * correct to a specific number of decimal places. Instead, individual entries are computed - * on demand as the search through the "table" - *

                      - * However, the table must be accurate and the search must continue far enough - * that it does not stop prematurely at an answer that has a corresponding probability - * (or row of the table) that is too far from the value of p that you use in the call to - * NORMSINV(p). Therefore, the NORMSINV function has been improved in the following ways: - *

                      - * - The accuracy of the NORMSDIST function has been improved. - * - The search process has been improved to increase refinement. - *

                      - * The NORMSDIST function has been improved in Excel 2003 and in later versions of Excel. - * Typically, inaccuracies in earlier versions of Excel occur for extremely small or extremely - * large values of p in NORMSINV(p). The values in Excel 2003 and in later versions of Excel - * are much more accurate. - *

                      - * Accuracy of NORMSDIST has been improved in Excel 2003 and in later versions of Excel. - * In earlier versions of Excel, a single computational procedure was used for all values - * of z. Results were essentially accurate to 7 decimal places, more than sufficient for - * most practical examples. - *

                      - * Results in earlier versions of Excel - * ** The accuracy of the NORMSINV function depends on two factors. Because the calculation of the NORMSINV - * function uses a systematic search over the returned values of the NORMSDIST function, the accuracy of - * the NORMSDIST function is critical. - *

                      - * Also, the search must be sufficiently refined that it "homes in" on an appropriate answer. - * To use the textbook Normal probability distribution table as an analogy, entries in the table - * must be accurate. Also, the table must contain so many entries that you can find the - * appropriate row of the table that yields a probability that is correct to a specific number - * of decimal places. - *

                      - * ' This function is a replacement for the Microsoft Excel Worksheet function NORMSINV. - * ' It uses the algorithm of Peter J. Acklam to compute the inverse normal cumulative - * ' distribution. Refer to http://home.online.no/~pjacklam/notes/invnorm/index.html for - * ' a description of the algorithm. - * ' Adapted to VB by Christian d'Heureuse, http://www.source-code.biz. - * Public Function NormSInv(ByVal p As Double) As Double - * Const a1 = -39.6968302866538, a2 = 220.946098424521, a3 = -275.928510446969 - * Const a4 = 138.357751867269, a5 = -30.6647980661472, a6 = 2.50662827745924 - * Const b1 = -54.4760987982241, b2 = 161.585836858041, b3 = -155.698979859887 - * Const b4 = 66.8013118877197, b5 = -13.2806815528857, c1 = -7.78489400243029E-03 - * Const c2 = -0.322396458041136, c3 = -2.40075827716184, c4 = -2.54973253934373 - * Const c5 = 4.37466414146497, c6 = 2.93816398269878, d1 = 7.78469570904146E-03 - * Const d2 = 0.32246712907004, d3 = 2.445134137143, d4 = 3.75440866190742 - * Const p_low = 0.02425, p_high = 1 - p_low - * Dim q As Double, r As Double - * If p < 0 Or p > 1 Then - * Err.Raise vbObjectError, , "NormSInv: Argument out of range." - * ElseIf p < p_low Then - * q = Sqr(-2 * Log(p)) - * NormSInv = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _ - * ((((d1 * q + d2) * q + d3) * q + d4) * q + 1) - * ElseIf p <= p_high Then - * q = p - 0.5: r = q * q - * NormSInv = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q / _ - * (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1) - * Else - * q = Sqr(-2 * Log(1 - p)) - * NormSInv = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _ - * ((((d1 * q + d2) * q + d3) * q + d4) * q + 1) - * End If - * End Function - *

                      - * NORMSINV= NORMINV(p; 0; 1) - */ - private static double expm1(double x) { - final double DBL_EPSILON = 0.0000001; - double y, a = Math.abs(x); - - if (a < DBL_EPSILON) return x; - if (a > 0.697) return Math.exp(x) - 1; /* negligible cancellation */ - - if (a > 1e-8) - y = Math.exp(x) - 1; - else /* Taylor expansion, more accurate in this range */ - y = (x / 2 + 1) * x; - - /* Newton step for solving log(1 + y) = x for y : */ - /* WARNING: does not work for y ~ -1: bug in 1.5.0 -- fixed??*/ - y -= (1 + y) * (Math.log(1 + y) - x); - return y; - } - - private int R_Q_P01_check(int p, boolean log_p) { - if ((log_p && p > 0) || (!log_p && (p < 0 || p > 1))) - return 0; - return 1; - } - - private static double quartile(double p, double mu, double sigma) { - boolean lower_tail = true; - boolean log_p = false; - double R_D__0 = 0; - double R_D__1 = 1; - double R_DT_0 = 0; //((lower_tail) ? R_D__0 : R_D__1); /* 0 */ - double R_DT_1 = 1; // ((lower_tail) ? R_D__1 : R_D__0) /* 1 */ - - double p_, q, r, val; - if (p == R_DT_0) return Double.NEGATIVE_INFINITY; - if (p == R_DT_1) return Double.POSITIVE_INFINITY; - //R_Q_P01_check(p); - - if (sigma < 0) return 0; - if (sigma == 0) return mu; - - p = (log_p ? (lower_tail ? Math.exp(p) : -expm1(p)) : (lower_tail ? (p) : (1 - (p)))); - p_ = (log_p ? (lower_tail ? Math.exp(p) : -expm1(p)) : (lower_tail ? (p) : (1 - (p))));/* real lower_tail prob. p */ - q = p_ - 0.5; - - if (Math.abs(q) <= .425) {/* 0.075 <= p <= 0.925 */ - r = .180625 - q * q; - val = - q * (((((((r * 2509.0809287301226727 + - 33430.575583588128105) * r + 67265.770927008700853) * r + - 45921.953931549871457) * r + 13731.693765509461125) * r + - 1971.5909503065514427) * r + 133.14166789178437745) * r + - 3.387132872796366608) - / (((((((r * 5226.495278852854561 + - 28729.085735721942674) * r + 39307.89580009271061) * r + - 21213.794301586595867) * r + 5394.1960214247511077) * r + - 687.1870074920579083) * r + 42.313330701600911252) * r + 1.); - } else { /* closer than 0.075 from {0,1} boundary */ - - /* r = min(p, 1-p) < 0.075 */ - if (q > 0) - r = (log_p ? (lower_tail ? -expm1(p) : Math.exp(p)) : (lower_tail ? (1 - (p)) : (p)));/* 1-p */ - else - r = p_;/* = R_DT_Iv(p) ^= p */ - - r = Math.sqrt(-((log_p && ((lower_tail && q <= 0) || (!lower_tail && q > 0))) ? p : /* else */ Math.log(r))); - /* r = sqrt(-log(r)) <==> min(p, 1-p) = exp( - r^2 ) */ - - if (r <= 5.) { /* <==> min(p,1-p) >= exp(-25) ~= 1.3888e-11 */ - r += -1.6; - val = (((((((r * 7.7454501427834140764e-4 + - .0227238449892691845833) * r + .24178072517745061177) * - r + 1.27045825245236838258) * r + - 3.64784832476320460504) * r + 5.7694972214606914055) * - r + 4.6303378461565452959) * r + - 1.42343711074968357734) - / (((((((r * - 1.05075007164441684324e-9 + 5.475938084995344946e-4) * - r + .0151986665636164571966) * r + - .14810397642748007459) * r + .68976733498510000455) * - r + 1.6763848301838038494) * r + - 2.05319162663775882187) * r + 1.); - } else { /* very close to 0 or 1 */ - r += -5.; - val = (((((((r * 2.01033439929228813265e-7 + - 2.71155556874348757815e-5) * r + - .0012426609473880784386) * r + .026532189526576123093) * - r + .29656057182850489123) * r + - 1.7848265399172913358) * r + 5.4637849111641143699) * - r + 6.6579046435011037772) - / (((((((r * - 2.04426310338993978564e-15 + 1.4215117583164458887e-7) * - r + 1.8463183175100546818e-5) * r + - 7.868691311456132591e-4) * r + .0148753612908506148525) - * r + .13692988092273580531) * r + - .59983220655588793769) * r + 1.); - } - - if (q < 0.0) - val = -val; - /* return (q >= 0.)? r : -r ;*/ - } - return mu + sigma * val; - } - - public static Ptg calcNormInv(Ptg[] operands) { - try { - - double p = operands[0].getDoubleVal(); - if (p < 0 || p > 1) return new PtgErr(PtgErr.ERROR_NUM); - double mean = operands[1].getDoubleVal(); - double stddev = operands[2].getDoubleVal(); - if (stddev <= 0) return new PtgErr(PtgErr.ERROR_NUM); - // If mean = 0 and standard_dev = 1, NORMINV uses the standard normal inverse (see NORMSINV). - double result = quartile(p, mean, stddev); - return new PtgNumber(result); - } catch (Exception e) { - } - return new PtgErr(PtgErr.ERROR_VALUE); - } - - - public static Ptg calcNormsInv(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getValueError(); - try { - double x = operands[0].getDoubleVal(); - if (x < 0 || x > 1) - return new PtgErr(PtgErr.ERROR_NUM); - /* - * the algorithm is supposed to iterate over NORMSDIST values using Newton-Raphson's approximation - * Newton-Raphson uses an iterative process to approach one root of a function (i.e the zero of the function or - * where the function = 0) - * Newton-Raphson is in the form of: - * - * Xn+1= Xn- (f(xn)/f'(xn) - * where Xn is the current known X value, f(xn) is the value of the function at X, f'(Xn) is the derivative or slope - * at X, Xn+1 is the next X value. Essentially, f'(xn) represents (f(x)/delta x) so f(xn)/f'(xn)== delta x. - * the more iterations we run over, the closer delta x will be to 0. - * - * The Newton-Raphson method does not always work, however. It runs into problems in several places. - What would happen if we chose an initial x-value of x=0? We would have a "division by zero" error, and would not be able to proceed. - You may also consider operating the process on the function f(x) = x1/3, using an inital x-value of x=1. - Do the x-values converge? Does the delta-x decrease toward zero (0)? - - is the derivative of the standard normal distribution = the standard probability density function??? - */ - /* below is not accurate enough - but N-R approximation is impossible ((:*/ - // Coefficients in rational approximations - double[] a = new double[]{-3.969683028665376e+01, 2.209460984245205e+02, - -2.759285104469687e+02, 1.383577518672690e+02, - -3.066479806614716e+01, 2.506628277459239e+00}; - - double[] b = new double[]{-5.447609879822406e+01, 1.615858368580409e+02, - -1.556989798598866e+02, 6.680131188771972e+01, - -1.328068155288572e+01}; - - double[] c = new double[]{-7.784894002430293e-03, -3.223964580411365e-01, - -2.400758277161838e+00, -2.549732539343734e+00, - 4.374664141464968e+00, 2.938163982698783e+00}; - - double[] d = new double[]{7.784695709041462e-03, 3.224671290700398e-01, - 2.445134137142996e+00, 3.754408661907416e+00}; - - // Define break-points. - double plow = 0.02425; - double phigh = 1 - plow; - double result; - // Rational approximation for lower region: - if (x < plow) { - double q = Math.sqrt(-2 * Math.log(x)); - BigDecimal r = new BigDecimal((((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / - ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)); - r.setScale(15, java.math.RoundingMode.HALF_UP); - return new PtgNumber(r.doubleValue()); - } - - // Rational approximation for upper region: - if (phigh < x) { - double q = Math.sqrt(-2 * Math.log(1 - x)); - BigDecimal r = new BigDecimal(-(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / - ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)); - r.setScale(15, java.math.RoundingMode.HALF_UP); - return new PtgNumber(r.doubleValue()); - } - - // Rational approximation for central region: - double q = x - 0.5; - double r = q * q; - BigDecimal rr = new BigDecimal((((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q / - (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1)); - - rr.setScale(15, java.math.RoundingMode.HALF_UP); - return new PtgNumber(rr.doubleValue()); - } catch (Exception e) { - return PtgCalculator.getValueError(); - } - } - - /** - * PEARSON - * Returns the Pearson product moment correlation coefficient - * - * @throws CalculationException - */ - public static Ptg calcPearson(Ptg[] operands) throws CalculationException { - return calcCorrel(operands); - } - /* -PERCENTILE - Returns the k-th percentile of values in a range - -PERCENTRANK - Returns the percentage rank of a value in a data set - -PERMUT - Returns the number of permutations for a given number of objects - -POISSON - Returns the Poisson distribution - -PROB - Returns the probability that values in a range are between two limits - */ - - /** - * QUARTILE - * Returns the quartile of a data set - */ - protected static Ptg calcQuartile(Ptg[] operands) { - Ptg[] aveoperands = new Ptg[1]; - aveoperands[0] = operands[0]; - Ptg[] allVals = PtgCalculator.getAllComponents(aveoperands); - CompatibleVector t = new CompatibleVector(); - double retval = 0; - for (int i = 0; i < allVals.length; i++) { - Ptg p = allVals[i]; - try { - Double d = new Double(String.valueOf(p.getValue())); - t.addOrderedDouble(d); - } catch (NumberFormatException e) { - Logger.logErr(e); - } - } - - Double[] dub = new Double[t.size()]; - t.toArray(dub); - Integer quart; - Object o = operands[1].getValue(); - if (o instanceof Integer) - quart = (Integer) operands[1].getValue(); - else - quart = Integer.valueOf(((Double) operands[1].getValue()).intValue()); - - float quartile = quart.floatValue(); - if (quart.intValue() == 0) { // return minimum value - return new PtgNumber(dub[0].doubleValue()); - } else if (quart.intValue() == 4) { // return maximum value - return new PtgNumber(dub[t.size() - 1].doubleValue()); - } else if (quart.intValue() > 4 || quart.intValue() < 0) { - return new PtgErr(PtgErr.ERROR_NUM); - } - // find the kth smallest - float kk = quartile / 4; - kk = (dub.length - 1) * kk; - kk++; - // truncate k, but keep the remainder. - int k = -1; - float remainder = 0; - if (kk % 1 != 0) { - remainder = kk % 1; - String s = String.valueOf(kk); - String ss = s.substring(s.indexOf(".")); - ss = "0" + ss; - remainder = new Float(ss).floatValue(); - s = s.substring(0, s.indexOf(".")); - k = Integer.valueOf(s).intValue(); - } else { - k = (int) kk / 1; - } - if (k >= dub.length) - return new PtgErr(PtgErr.ERROR_VALUE); - double firstVal = dub[k - 1].doubleValue(); - double secondVal = dub[k].doubleValue(); - double output = firstVal + (remainder * (secondVal - firstVal)); - PtgNumber pn = new PtgNumber(output); - return pn; - } - - /** - * RANK - * Returns the rank of a number in a list of numbers - *

                      - * RANK(number,ref,order) - * Number is the number whose rank you want to find. - * Ref is an array of, or a reference to, a list of numbers. Nonnumeric values in ref are ignored. - * Order is a number specifying how to rank number. - *

                      - * If order is 0 (zero) or omitted, Microsoft Excel ranks number as if ref were a list sorted in descending order. - * If order is any nonzero value, Microsoft Excel ranks number as if ref were a list sorted in ascending order. - */ - protected static Ptg calcRank(Ptg[] operands) { - // the number - if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg num = operands[0]; - Double theNum = null; - try { - Object o = num.getValue(); - if (o.equals("")) - theNum = new Double(0.0d); - else - theNum = new Double(o.toString()); - } catch (NumberFormatException nfm) { - return new PtgErr(); - } - - //ascending or decending? - boolean ascending = true; - if (operands.length < 3) { - ascending = false; - } else if (operands[2] instanceof PtgMissArg) { - ascending = false; - } else { - PtgInt order = (PtgInt) operands[2]; - int i = order.getVal(); - if (i == 0) ascending = false; - } - Ptg[] aveoperands = new Ptg[1]; - aveoperands[0] = operands[1]; - Ptg[] refs = PtgCalculator.getAllComponents(aveoperands); - CompatibleVector retList = new CompatibleVector(); - double retval = 0; - for (int i = 0; i < refs.length; i++) { - Ptg p = refs[i]; - try { - Double d = new Double(String.valueOf(p.getValue())); - retList.addOrderedDouble(d); - } catch (NumberFormatException e) { - } - } - Double[] dubRefs = new Double[retList.size()]; - if (ascending) { - retList.toArray(dubRefs); - } else { - for (int i = 0; i < dubRefs.length; i++) { - dubRefs[i] = (Double) retList.last(); - retList.remove(retList.size() - 1); - } - } - int res = -1; - for (int i = 0; i < dubRefs.length; i++) { - if (dubRefs[i].toString().equalsIgnoreCase(theNum.toString())) { - res = i + 1; - i = dubRefs.length; - } - } - if (res == -1) { - return new PtgErr(PtgErr.ERROR_NA); - } else { - return new PtgInt(res); - } - - } - - - /** - * RSQ - * Returns the square of the Pearson product moment correlatin coefficient - * - * @throws CalculationException - */ - protected static Ptg calcRsq(Ptg[] operands) throws CalculationException { - PtgNumber p = (PtgNumber) calcPearson(operands); - double d = p.getVal(); - d = (d * d); - return new PtgNumber(d); - } - /* -SKEW - Returns the skewness of a distribution - */ - - /** - * SLOPE - * Returns the slope of the linear regression line - * - * @throws CalculationException - */ - protected static Ptg calcSlope(Ptg[] operands) throws CalculationException { - if (operands.length != 2) - return new PtgErr(PtgErr.ERROR_VALUE); - double[] yvals = PtgCalculator.getDoubleValueArray(operands[0]); - double[] xvals = PtgCalculator.getDoubleValueArray(operands[1]); - if (xvals == null || yvals == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double sumXVals = 0; - for (int i = 0; i < xvals.length; i++) { - sumXVals += xvals[i]; - } - double sumYVals = 0; - for (int i = 0; i < yvals.length; i++) { - sumYVals += yvals[i]; - } - double sumXYVals = 0; - for (int i = 0; i < yvals.length; i++) { - sumXYVals += xvals[i] * yvals[i]; - } - double sqrXVals = 0; - for (int i = 0; i < xvals.length; i++) { - sqrXVals += xvals[i] * xvals[i]; - } - double toparg = (sumXVals * sumYVals) - (sumXYVals * yvals.length); - double bottomarg = (sumXVals * sumXVals) - (sqrXVals * xvals.length); - double res = toparg / bottomarg; - return new PtgNumber(res); - } - - /** - * SMALL - * Returns the k-th smallest value in a data set - *

                      - * SMALL(array,k) - * Array is an array or range of numerical data for which you want to determine the k-th smallest value. - * K is the position (from the smallest) in the array or range of data to return. - * - * @throws CalculationException - */ - protected static Ptg calcSmall(Ptg[] operands) throws CalculationException { - if (operands.length != 2) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg rng = operands[0]; - Ptg[] array = PtgCalculator.getAllComponents(rng); - if (array.length == 0) return new PtgErr(PtgErr.ERROR_NUM); - int k = new Double(PtgCalculator.getDoubleValueArray(operands[1])[0]).intValue(); - if (k <= 0 || k > array.length) - return new PtgErr(PtgErr.ERROR_NUM); - - CompatibleVector sortedValues = new CompatibleVector(); - for (int i = 0; i < array.length; i++) { - Ptg p = array[i]; - try { - Double d = new Double(String.valueOf(p.getValue())); - sortedValues.addOrderedDouble(d); - } catch (NumberFormatException e) { - } - } - try { - return new PtgNumber(((Double) sortedValues.get(k - 1)).doubleValue()); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - /* -STANDARDIZE - Returns a normalized value - */ - - /** - * STDEV(number1,number2, ...) - *

                      - * Number1,number2, ... are 1 to 255 number arguments corresponding to a sample of a population. - * You can also use a single array or a reference to an array instead of arguments separated by commas. - * - * @throws CalculationException - */ - protected static Ptg calcStdev(Ptg[] operands) throws CalculationException { - double[] allVals = PtgCalculator.getDoubleValueArray(operands); - if (allVals == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double sqrDev = 0; - for (int i = 0; i < allVals.length; i++) { - PtgNumber p = (PtgNumber) calcAverage(operands); - double ave = p.getVal(); - sqrDev += Math.pow((allVals[i] - ave), 2); - } - double retval = Math.sqrt(sqrDev / (allVals.length - 1)); - return new PtgNumber(retval); - } - /* -STDEVA - Estimates standard deviation based on a sample, including numbers, text, and logical values - -STDEVP - Calculates standard deviation based on the entire population - -STDEVPA - Calculates standard deviation based on the entire population, including numbers, text, and logical values - */ - - /** - * STEYX - * Returns the standard error of the predicted y-value for each x in the regression - * - * @throws CalculationException - */ - public static Ptg calcSteyx(Ptg[] operands) throws CalculationException { - Ptg[] arr = new Ptg[1]; - arr[0] = operands[0]; - PtgNumber pn = (PtgNumber) calcVarp(arr); - double yVarp = pn.getVal(); - arr[0] = operands[1]; - pn = (PtgNumber) calcVarp(arr); - double xVarp = pn.getVal(); - double[] y = PtgCalculator.getDoubleValueArray(operands[0]); - if (y == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - yVarp *= y.length; - xVarp *= y.length; - pn = (PtgNumber) calcSlope(operands); - double slope = pn.getVal(); - double retval = yVarp - ((slope * slope) * xVarp); - retval = retval / (y.length - 2); - retval = Math.sqrt(retval); - return new PtgNumber(retval); - } - /* -TDIST - Returns the Student's t-distribution - -TINV - Returns the inverse of the Student's t-distribution - */ - - /** - * TREND - * Returns values along a linear trend - * - * @throws CalculationException - */ - protected static Ptg calcTrend(Ptg[] operands) throws CalculationException { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); -// KSC: THIS FUNCTION DOES NOT WORK AS EXPECTED: TODO: FIX! - - if (true) return new PtgErr(PtgErr.ERROR_VALUE); -// Ptg[] forecast = new Ptg[3]; - Ptg[] forecast = new Ptg[2]; - forecast[0] = operands[0]; - if (operands.length > 1) - forecast[1] = operands[1]; -// TODO: -// else // If known_x's is omitted, it is assumed to be the array {1,2,3,...} that is the same size as known_y's. - Ptg[] newXs; - if (operands.length > 2) - newXs = PtgCalculator.getAllComponents(operands[2]); - else - newXs = PtgCalculator.getAllComponents(operands[1]); - - String retval = ""; - for (int i = 0; i < newXs.length; i++) { - //forecast[0] = newXs[i]; - PtgNumber p = (PtgNumber) calcForecast(forecast); - double forcst = p.getVal(); - retval += "{" + forcst + "},"; - } - // get rid of trailing comma - retval = retval.substring(0, retval.length() - 1); - PtgArray pa = new PtgArray(); - pa.setVal(retval); - return pa; - } - - /* -TRIMMEAN - Returns the mean of the interior of a data set - -TTEST - Returns the probability associated with a Student's t-Test - */ - - /** - * VAR - * Estimates variance based on a sample - * - * @throws CalculationException - */ - protected static Ptg calcVar(Ptg[] operands) throws CalculationException { - double[] allVals = PtgCalculator.getDoubleValueArray(operands); - if (allVals == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double sqrDev = 0; - for (int i = 0; i < allVals.length; i++) { - PtgNumber p = (PtgNumber) calcAverage(operands); - double ave = p.getVal(); - sqrDev += Math.pow((allVals[i] - ave), 2); - } - double retval = (sqrDev / (allVals.length - 1)); - return new PtgNumber(retval); - } - /* -VARA - Estimates variance based on a sample, including numbers, text, and logical values - */ - - /** - * VARp - * Estimates variance based on a full population - * - * @throws CalculationException - */ - protected static Ptg calcVarp(Ptg[] operands) throws CalculationException { - double[] allVals = PtgCalculator.getDoubleValueArray(operands); - if (allVals == null) return new PtgErr(PtgErr.ERROR_NA);//20090130 KSC: propagate error - double sqrDev = 0; - for (int i = 0; i < allVals.length; i++) { - PtgNumber p = (PtgNumber) calcAverage(operands); - double ave = p.getVal(); - sqrDev += Math.pow((allVals[i] - ave), 2); - } - double retval = (sqrDev / allVals.length); - return new PtgNumber(retval); - } - /* -VARPA - Calculates variance based on the entire population, including numbers, text, and logical values - -WEIBULL - Returns the Weibull distribution - -ZTEST - Returns the two-tailed P-value of a z-test - -*/ - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.kt new file mode 100644 index 0000000..19b1386 --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/StatisticalCalculator.kt @@ -0,0 +1,2233 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.ExcelTools +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.toolkit.CompatibleVector +import io.starter.toolkit.Logger + +import java.math.BigDecimal +import java.util.ArrayList +import java.util.Vector + + +/* + StatisticalCalculator is a collection of static methods that operate + as the Microsoft Excel function calls do. + + All methods are called with an array of ptg's, which are then + acted upon. A Ptg of the type that makes sense (ie boolean, number) + is returned after each method. +*/ +class StatisticalCalculator { + + private fun R_Q_P01_check(p: Int, log_p: Boolean): Int { + return if (log_p && p > 0 || !log_p && (p < 0 || p > 1)) 0 else 1 + } + + companion object { + + + /** + * AVERAGE + * Returns the average (arithmetic mean) of the arguments. + * Ignores non-numbers + * This cannot recurse, due to averaging needs. + * + * + * Usage@ AVERAGE(number1,number2, ...) + * Returns@ PtgNumber + */ + fun calcAverage(operands: Array): Ptg { + val vect = Vector() + + for (i in operands.indices) { + val pthings = operands[i].components // optimized -- do it once!! -jm + if (pthings != null) { + for (z in pthings.indices) { + vect.add(pthings[z]) + } + } else { + val p = operands[i] + vect.add(p) + } + } + var count = 0 + // double total = 0; + var bd = BigDecimal(0) + for (i in vect.indices) { + val p = vect.elementAt(i) as Ptg + try { + if (p.isBlank) continue + val ov = p.value + if (ov != null) { + // total += Double.parseDouble(String.valueOf(ov)); + bd = bd.add(BigDecimal(java.lang.Double.parseDouble(ov.toString()))) + count++ + } + } catch (e: NumberFormatException) { + } + + } + bd = bd.setScale(15, java.math.RoundingMode.HALF_UP) + val total = bd.toDouble() + if (count == 0) + return PtgErr(PtgErr.ERROR_DIV_ZERO) + val result = total / count + return PtgNumber(result) + } + + /** + * AVERAGEIF function + * Returns the average (arithmetic mean) of all the cells in a range that meet a given criteria. + * + * + * AVERAGEIF(range,criteria,average_range) + * + * + * Range is one or more cells to average, including numbers or names, arrays, or references that contain numbers. + * Criteria is the criteria in the form of a number, expression, cell reference, or text that defines which cells are averaged. For example, criteria can be expressed as 32, "32", ">32", "apples", or B4. + * Average_range is the actual set of cells to average. If omitted, range is used. + * Average_range does not have to be the same size and shape as range. The actual cells that are averaged are determined by using the top, left cell in average_range as the beginning cell, and then including cells that correspond in size and shape to range. + * + * @param operands + * @return + */ + fun calcAverageIf(operands: Array): Ptg { + if (operands.size < 2) + return PtgErr(PtgErr.ERROR_DIV_ZERO) + // range used to test criteria + val range = operands[0].components + // TODO: if range is blank or a text value returns ERROR_DIV_ZERO + var criteria = operands[1].string.trim { it <= ' ' } + // Parse criteria into op + criteria + val i = Calculator.splitOperator(criteria) + val op = criteria.substring(0, i) // extract operand + criteria = criteria.substring(i) + criteria = Calculator.translateWildcardsInCriteria(criteria) + // Average_range, if present, is used for return values if range passes criteria + var average_range: Array? = null + var varyRow = false + if (operands.size > 2) { + //The actual cells that are averaged are determined by using the top, left cell in average_range as the beginning cell, + // and then including cells that correspond in size and shape to range. + var rc: IntArray? = null + average_range = arrayOfNulls(range.size) + average_range[0] = operands[2].components[0] // start with top left of average range + var sheet = "" + try { + rc = average_range[0].intLocation + if (range[0].intLocation[0] != range[range.size - 1].intLocation[0]) + // determine if range is varied across row or column + varyRow = true + sheet = (average_range[0] as PtgRef).sheetName!! + "!" + } catch (e: Exception) { + } + + for (j in 1 until average_range.size) { + if (varyRow) + rc!![0]++ + else + rc!![1]++ + average_range[j] = PtgRef3d() + average_range[j].parentRec = range[0].parentRec + average_range[j].location = sheet + ExcelTools.formatLocation(rc) + } + } + var nresults = 0 + var result = 0.0 + for (j in range.indices) { + var `val`: Any? = range[j].value + // TODO: TRUE and FALSE values are ignored + // TODO: blank cells are treated as 0's + if (Calculator.compareCellValue(`val`, criteria, op)) { + try { + if (average_range != null) { + `val` = average_range[j].value + if (`val` == null) + // if a cell is empty it's ignored -- + continue + } + result += (`val` as Number).toDouble() + } catch (e: ClassCastException) { + } + + nresults++ + } + } + + // If no cells in the range meet the criteria, AVERAGEIF returns the #DIV/0! error value + return if (nresults == 0) PtgErr(PtgErr.ERROR_DIV_ZERO) else PtgNumber(result / nresults) + // otherwise, average + } + + /** + * AVERAGEIFS + * Returns the average (arithmetic mean) of all cells that meet multiple criteria. + * AVERAGEIFS(average_range,criteria_range1,criteria1,criteria_range2,criteria2…) + * Average_range is one or more cells to average, including numbers or names, arrays, or references that contain numbers. + * Criteria_range1, criteria_range2, … are 1 to 127 ranges in which to evaluate the associated criteria. + * Criteria1, criteria2, … are 1 to 127 criteria in the form of a number, expression, cell reference, or text that define which cells will be averaged. For example, criteria can be expressed as 32, "32", ">32", "apples", or B4. + * + * @param operands + * @return + */ + fun calcAverageIfS(operands: Array): Ptg { + try { + val average_range = Calculator.getRange(operands[0]) + val averagerangecells = average_range!!.components + if (averagerangecells!!.size == 0) + return PtgErr(PtgErr.ERROR_DIV_ZERO) + val ops = arrayOfNulls((operands.size - 1) / 2) + val criteria = arrayOfNulls((operands.size - 1) / 2) + val criteria_cells = arrayOfNulls>((operands.size - 1) / 2) + var j = 0 + run { + var i = 1 + while (i + 1 < operands.size) { + //criteria range - parse and get comprising cells + val cr = Calculator.getRange(operands[i]) + criteria_cells[j] = cr!!.components + // each criteria_range must contain the same number of rows and columns as the sum_range + if (criteria_cells[j].size != averagerangecells.size) + return PtgErr(PtgErr.ERROR_VALUE) + // criteria for comparison, including operator + criteria[j] = operands[i + 1].toString() + // strip operator, if any, and parse criteria + ops[j] = "=" // operator, default is = + val k = Calculator.splitOperator(criteria[j]) + if (k > 0) ops[j] = criteria[j].substring(0, k) // extract operator, if any + criteria[j] = criteria[j].substring(k) + criteria[j] = Calculator.translateWildcardsInCriteria(criteria[j]) + j++ + i += 2 + } + } + + // test criteria for all cells in range, storing those corresponding average_range cells + // that pass in passesList + // stores the cells that pass the criteria expression and therefore will be averaged + val passesList = ArrayList() + // for each set of criteria, test all cells in range and evaluate + // NOTE: this is an implicit AND evaluation + for (i in averagerangecells.indices) { + var passes = true + for (k in criteria.indices) { + try { + val v = criteria_cells[k][i].value + // If cells in average_range cannot be translated into numbers, AVERAGEIFS returns the #DIV0! error value. + passes = Calculator.compareCellValue(v, criteria[k], ops[k]) && passes + if (!passes) + break // no need to continue + } catch (e: Exception) { // don't report error + } + + } + if (passes) { + passesList.add(averagerangecells[i]) + } + } + // If no cells in the range meet the criteria, AVERAGEIF returns the #DIV/0! error value + if (passesList.size == 0) + return PtgErr(PtgErr.ERROR_DIV_ZERO) + + // At this point we have a collection of all the cells that pass (or their corresponding cell in sum_range); + // Now we sum up the values of these cells and return + var result = 0.0 + for (i in passesList.indices) { + val cell = passesList.get(i) as Ptg + try { + result += cell.doubleVal + } catch (e: Exception) { + Logger.logErr("MathFunctionCalculator.calcAverageIfS: error obtaining cell value: $e") // debugging only; take out when fully tested + // keep going + } + + } + + // otherwise, average + return PtgNumber(result / passesList.size) + + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_NULL) + } + + /** + * AVEDEV(number1,number2, ...) + * Number1, number2, ... are 1 to 30 arguments for which you want + * the average of the absolute deviations. You can also use a + * single array or a reference to an array instead of arguments + * separated by commas. + * + * + * The arguments must be either numbers or names, + * arrays, or references that contain numbers. + * + * + * If an array or reference argument contains text, + * logical values, or empty cells, those values are ignored; + * however, cells with the value zero are included. + */ + fun calcAveDev(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + // Get the average for the mean + val av = StatisticalCalculator.calcAverage(operands) as PtgNumber + var average = -0.001 + try { + val dd = Double(av.value.toString()) + average = dd + } catch (e: NumberFormatException) { + } + + if (average == -0.001) return PtgCalculator.error + + // work out the total deviation + var total = 0.0 + var count = 0 + var d: Double? + val alloperands = PtgCalculator.getAllComponents(operands) + for (i in alloperands.indices) { + val resPtg = alloperands[i] + try { // some fields may be text, so handle gracefully + if (resPtg.value != null) { + d = Double(resPtg.value.toString()) + var dub = d.toDouble() + dub = average - dub + dub = Math.abs(dub) + total += dub + count++ + } + } catch (e: NumberFormatException) { + } + + } + // work out the mean deviation + val mean = total / count + return PtgNumber(mean) + } + + /** + * AVERAGEA + * Returns the average of its arguments, including numbers, text, + * and logical values + * + * + * The arguments must be numbers, names, arrays, or references. + * Array or reference arguments that contain text evaluate as 0 (zero). + * Empty text ("") evaluates as 0 (zero). + * Arguments that contain TRUE evaluate as 1; + * arguments that contain FALSE evaluate as 0 (zero). + */ + fun calcAverageA(operands: Array): Ptg { + val alloperands = PtgCalculator.getAllComponents(operands) + var total = 0.0 + for (i in alloperands.indices) { + val p = alloperands[i] + try { + val ov = p.value + if (ov != null) { + if (ov.toString() === "true") { + total++ + } else { + total += java.lang.Double.parseDouble(ov.toString()) + } + } + } catch (e: NumberFormatException) { + } + + } + val result = total / alloperands.size + return PtgNumber(result) + } + /* +BETADIST + Returns the cumulative beta probability density function + +BETAINV + Returns the inverse of the cumulative beta probability density function + +BINOMDIST + Returns the individual term binomial distribution probability + +CHIDIST + Returns the one-tailed probability of the chi-squared distribution + +CHIINV + Returns the inverse of the one-tailed probability of the chi-squared distribution + +CHITEST + Returns the test for independence + +CONFIDENCE + Returns the confidence interval for a population mean + */ + + /** + * CORREL + * Returns the correlation coefficient between two data sets + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcCorrel(operands: Array): Ptg { + // get the covariance + var pnum = calcCovar(operands) as PtgNumber + val covar = pnum.`val` + val xPtg = arrayOfNulls(1) + xPtg[0] = operands[0] + val yPtg = arrayOfNulls(1) + yPtg[0] = operands[1] + pnum = calcAverage(xPtg) as PtgNumber + val xMean = pnum.`val` + pnum = calcAverage(yPtg) as PtgNumber + val yMean = pnum.`val` + val xVals = PtgCalculator.getDoubleValueArray(xPtg) + val yVals = PtgCalculator.getDoubleValueArray(yPtg) + if (xVals == null || yVals == null) + return PtgErr(PtgErr.ERROR_NA)//20090130 KSC: propagate error + var xstat = 0.0 + for (i in xVals.indices) { + xstat += Math.pow(xVals[i] - xMean, 2.0) + } + xstat = xstat / xVals.size + xstat = Math.sqrt(xstat) + var ystat = 0.0 + for (i in yVals.indices) { + ystat += Math.pow(yVals[i] - yMean, 2.0) + } + ystat = ystat / yVals.size + ystat = Math.sqrt(ystat) + val retval = covar / (ystat * xstat) + return PtgNumber(retval) + } + + /** + * COUNT + * Counts how many numbers are in the list of arguments + */ + /** + * Counts the number of cells that contain numbers + * and or dates. + * Use COUNT to get the number of entries in a number + * field in a range or array of numbers. + * + * + * Usage@ COUNT(A1:A5,A9) + * Return@ PtgInt + * TODO: implement counting of dates! + */ + fun calcCount(operands: Array): Ptg { + var count = 0 + for (i in operands.indices) { + val pref = operands[i].components // optimized -- do it once!! -jm + if (pref != null) { // it is some sort of range + for (z in pref.indices) { + val o = pref[z].value + if (o != null) { + try { + val n = Double(o.toString()) + count++ + } catch (e: NumberFormatException) { + } + + } + } + } else { // it's a single ptgref + val o = operands[i].value + if (o != null) { + try { + val n = Double(o.toString()) + count++ + } catch (e: NumberFormatException) { + } + + } + } + } + return PtgInt(count) + + } + + /** + * COUNTA + * Counts the number of non-blank cells within a range + */ + fun calcCountA(operands: Array): Ptg { + val allops = PtgCalculator.getAllComponents(operands) + var count = 0 + for (i in allops.indices) { + /* 20081120 KSC: blnaks are handled differently as Excel counts blank cells as 0's + Object o = allops[i].getValue(); + if (o != null) count++; + */ + if (!allops[i].isBlank) + count++ + } + return PtgInt(count) + } + + /** + * COUNTBLANK + * Counts the number of blank cells within a range + */ + fun calcCountBlank(operands: Array): Ptg { + val allops = PtgCalculator.getAllComponents(operands) + var count = 0 + for (i in allops.indices) { + if (allops[i].isBlank) + // 20081112 KSC: was Object o = getValue(); if (o==null) count++; + count++ + } + return PtgInt(count) + } + + + /** + * COUNTIF + * Counts the number of non-blank cells within a range which meet the given criteria + * BRUTAL!! + */ + @Throws(FunctionNotSupportedException::class) + fun calcCountif(operands: Array): Ptg { + if (operands.size != 2) return PtgCalculator.error + val matchStr = operands[1].value.toString() + var donumber = true + var matchDub = 0.0 + try { // this method matches strings or numbers, here is where we differentiate + val d = Double(matchStr) + matchDub = d + } catch (e: Exception) { + donumber = false + } + + var count = 0.0 + val pref = operands[0].components // optimize by doing it one time!!! this thing gets slow....-jm + if (pref != null) { // it is some sort of range + for (z in pref.indices) { + val o = pref[z].value + if (o != null) { + val match2 = o.toString() + if (donumber) { + try { + val d = Double(match2) + if (matchDub == d) count++ + } catch (e: NumberFormatException) { + } + + } else { + if (matchStr.equals(match2, ignoreCase = true)) count++ + } + } + } + } else { // it's a single ptgref + val o = operands[0].value + if (o != null) { + if (o != null) { + val match2 = o.toString() + if (donumber) { + try { + val d = Double(match2) + if (matchDub == d) count++ + } catch (e: NumberFormatException) { + } + + } else { + if (matchStr.equals(match2, ignoreCase = true)) count++ + } + } + + } + } + return PtgNumber(count) + } + + /** + * COUNTIFS + * criteria_range1 Required. The first range in which to evaluate the associated criteria. + * criteria1 Required. The criteria in the form of a number, expression, cell reference, or text that define which cells will be counted. For example, criteria can be expressed as 32, ">32", B4, "apples", or "32". + * criteria_range2, criteria2, ... Optional. Additional ranges and their associated criteria. Up to 127 range/criteria pairs are allowed. + * + * @param operands + * @return + */ + fun calcCountIfS(operands: Array): Ptg { + try { + val ops = arrayOfNulls(operands.size / 2) + val criteria = arrayOfNulls(operands.size / 2) + val criteria_cells = arrayOfNulls>(operands.size / 2) + run { + var i = 0 + while (i + 1 < operands.size) { + //criteria range - parse and get comprising cells + val cr = Calculator.getRange(operands[i]) + criteria_cells[i / 2] = cr!!.components + // each criteria_range must contain the same number of rows and columns as the criteriarange + if (i > 0 && criteria_cells[i / 2].size != criteria_cells[0].size) + return PtgErr(PtgErr.ERROR_VALUE) + // criteria for comparison, including operator + criteria[i / 2] = operands[i + 1].toString() + // strip operator, if any, and parse criteria + ops[i / 2] = "=" // operator, default is = + val k = Calculator.splitOperator(criteria[i / 2]) + if (k > 0) ops[i / 2] = criteria[i / 2].substring(0, k) // extract operator, if any + criteria[i / 2] = criteria[i / 2].substring(k) + criteria[i / 2] = Calculator.translateWildcardsInCriteria(criteria[i / 2]) + i += 2 + } + } + + // test criteria for all cells in range, counting each cell that passes + // for each set of criteria, test all cells in range and evaluate + var count = 0 + for (i in 0 until criteria_cells[0].size) { + var passes = true + for (k in criteria.indices) { + try { + val v = criteria_cells[k][i].value + // the criteria argument is a reference to an empty cell, the COUNTIFS function treats the empty cell as a 0 value. + passes = Calculator.compareCellValue(v, criteria[k], ops[k]) && passes + if (!passes) + break // no need to continue + } catch (e: Exception) { // don't report error + } + + } + if (passes) + count++ + } + + return PtgNumber(count.toDouble()) + + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_NULL) + } + + /** + * COVAR + * Returns covariance, the average of the products of paired deviations + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcCovar(operands: Array): Ptg { + val xMeanPtg = arrayOfNulls(1) + xMeanPtg[0] = operands[0] + val yMeanPtg = arrayOfNulls(1) + yMeanPtg[0] = operands[1] + var pnum = calcAverage(xMeanPtg) as PtgNumber + val xMean = pnum.`val` + pnum = calcAverage(yMeanPtg) as PtgNumber + val yMean = pnum.`val` + val xVals = PtgCalculator.getDoubleValueArray(xMeanPtg) + val yVals = PtgCalculator.getDoubleValueArray(yMeanPtg) + if (xVals == null || yVals == null) + return PtgErr(PtgErr.ERROR_NA)//propagate error + var xyMean = 0.0 + if (xVals.size == yVals.size) { + var addvals = 0 + for (i in xVals.indices) { + addvals += (xVals[i] * yVals[i]).toInt() + } + xyMean = (addvals / xVals.size).toDouble() + } else { + return PtgErr(PtgErr.ERROR_NA) + } + + val retval = xyMean - xMean * yMean + return PtgNumber(retval) + } + /* +CRITBINOM + Returns the smallest value for which the cumulative binomial distribution is less than or equal to a criterion value + +DEVSQ + Returns the sum of squares of deviations + +EXPONDIST + Returns the exponential distribution + +FDIST + Returns the F probability distribution + +FINV + Returns the inverse of the F probability distribution + +FISHER + Returns the Fisher transformation + +FISHERINV + Returns the inverse of the Fisher transformation + */ + + /** + * FORECAST + * Returns a value along a linear trend + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcForecast(operands: Array): Ptg { + if (operands.size != 2) return PtgErr(PtgErr.ERROR_VALUE) + val p = arrayOfNulls(2) + p[0] = operands[0] + p[1] = operands[1] + val icept = calcIntercept(p) as PtgNumber + val intercept = icept.`val` + val slp = calcSlope(p) as PtgNumber + val slope = slp.`val` + val px = operands[0] + val knownX = Double(px.value.toString()) + val retval = slope * knownX + intercept + return PtgNumber(retval) + } + + /** + * FREQUENCY + * Returns a frequency distribution as a vertical array + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcFrequency(operands: Array): Ptg { + val firstArr = PtgCalculator.getAllComponents(operands[0]) + val secondArr = PtgCalculator.getAllComponents(operands[1]) + val t = CompatibleVector() + for (i in secondArr.indices) { + val p = secondArr[i] + try { + val d = Double(p.value.toString()) + t.addOrderedDouble(d) + } catch (e: NumberFormatException) { + } + + } + val binsArr = arrayOfNulls(t.size) + val dataArr: DoubleArray? + dataArr = PtgCalculator.getDoubleValueArray(firstArr) + if (dataArr == null) return PtgErr(PtgErr.ERROR_NA)//20090130 KSC: propagate error + t.toTypedArray() + val retvals = IntArray(secondArr.size + 1) + for (i in dataArr.indices) { + var x = 0 + while (x < binsArr.size) { + if (dataArr[i] <= binsArr[x].toDouble()) { + retvals[x]++ + x = binsArr.size + } else if (dataArr[i] > binsArr[binsArr.size - 1].toDouble()) { + retvals[binsArr.size]++ + x = binsArr.size + } + x++ + } + } + // keep the original locations, so we can put the end result array in the correct order. + // not used! double[] originalLocs = PtgCalculator.getDoubleValueArray(secondArr); + var ret = "{" + for (i in retvals.indices) { + ret += retvals[i].toString() + "," + } + ret = ret.substring(0, ret.length - 1) // get rid of final comma + ret += "}" + + val returnArr = PtgArray() + returnArr.setVal(ret) + return returnArr + } + /* +FTEST + Returns the result of an F-test + +GAMMADIST + Returns the gamma distribution + +GAMMAINV + Returns the inverse of the gamma cumulative distribution + +GAMMALN + Returns the natural logarithm of the gamma function, G(x) + +GEOMEAN + Returns the geometric mean + +GROWTH + Returns values along an exponential trend + +HARMEAN + Returns the harmonic mean + +HYPGEOMDIST + Returns the hypergeometric distribution + */ + + /** + * INTERCEPT + * Returns the intercept of the linear regression line + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcIntercept(operands: Array): Ptg { + val yvals: DoubleArray? + yvals = PtgCalculator.getDoubleValueArray(operands[0]) + + val xvals = PtgCalculator.getDoubleValueArray(operands[1]) + if (xvals == null || yvals == null) return PtgErr(PtgErr.ERROR_NA)//20090130 KSC: propagate error + var sumXVals = 0.0 + for (i in xvals.indices) { + sumXVals += xvals[i] + } + var sumYVals = 0.0 + for (i in yvals.indices) { + sumYVals += yvals[i] + } + var sumXYVals = 0.0 + for (i in yvals.indices) { + sumXYVals += xvals[i] * yvals[i] + } + var sqrXVals = 0.0 + for (i in xvals.indices) { + sqrXVals += xvals[i] * xvals[i] + } + val toparg = sumXVals * sumXYVals - sumYVals * sqrXVals + val bottomarg = sumXVals * sumXVals - sqrXVals * xvals.size + val res = toparg / bottomarg + return PtgNumber(res) + } + /* +KURT + Returns the kurtosis of a data set + */ + + /** + * LARGE + * + * + * Returns the k-th largest value in a data set. You can use this function to select a value based on its relative standing. + * For example, you can use LARGE to return the highest, runner-up, or third-place score. + * + * + * LARGE(array,k) + * + * + * Array is the array or range of data for which you want to determine the k-th largest value. + * K is the position (from the largest) in the array or cell range of data to return. + * + * + * If array is empty, LARGE returns the #NUM! error value. + * If k ≤ 0 or if k is greater than the number of data points, LARGE returns the #NUM! error value. + * If n is the number of data points in a range, then LARGE(array,1) returns the largest value, and LARGE(array,n) returns the smallest value. + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcLarge(operands: Array): Ptg { + if (operands.size != 2) return PtgErr(PtgErr.ERROR_VALUE) + val rng = operands[0] + val array = PtgCalculator.getAllComponents(rng) + if (array.size == 0) return PtgErr(PtgErr.ERROR_NUM) + val k = PtgCalculator.getDoubleValueArray(operands[1])!![0].toInt() + if (k <= 0 || k > array.size) + return PtgErr(PtgErr.ERROR_NUM) + + val sortedValues = CompatibleVector() + for (i in array.indices) { + val p = array[i] + try { + val d = Double(p.value.toString()) + sortedValues.addOrderedDouble(d) + } catch (e: NumberFormatException) { + } + + } + // reverse array + val dubRefs = arrayOfNulls(sortedValues.size) + for (i in dubRefs.indices) { + dubRefs[i] = sortedValues.last() as Double + sortedValues.removeAt(sortedValues.size - 1) + } + + return PtgNumber(dubRefs[k - 1].toDouble()) + /* + + try { + Ptg[] parray= PtgCalculator.getAllComponents(operands[0]); + Object[] array= new Object[parray.length]; + for (int i= 0; i < array.length; i++) { + array[i]= parray[i].getValue(); + if (array[i] instanceof Integer) { // convert all to double if possible for sort below (cannot have mixed array for Arrays.sort) + try { + array[i]= new Double(((Integer)array[i]).intValue()); + } catch (Exception e) { + } + } + } + // now sort + java.util.Arrays.sort(array); + int position= (int) PtgCalculator.getLongValue(operands[1]); + // now return the nth item in the sorted (asc) array + if (position >=0 && position <=array.length) { + Object ret= array[array.length-position]; + if (ret instanceof Double) + return new PtgNumber(((Double)ret).doubleValue()); + else if (ret instanceof Boolean) + return new PtgBool(((Boolean)ret).booleanValue()); + else if (ret instanceof String) + return new PtgStr((String)ret); + } + } catch (Exception e) { + + } + return new PtgErr(PtgErr.ERROR_NUM); +*/ + } + + + /** + * LINEST + * Returns the parameters of a linear trend + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcLineSt(operands: Array): Ptg { + val ys = PtgCalculator.getDoubleValueArray(operands[0]) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + val xs: DoubleArray? + if (operands.size == 1 || operands[1] is PtgMissArg) { + // create a 1,2,3 array + xs = DoubleArray(ys.size) + for (i in ys.indices) + xs[i] = i.toDouble() + } else { + xs = PtgCalculator.getDoubleValueArray(operands[1]) + if (xs == null) return PtgErr(PtgErr.ERROR_NA)//20090130 KSC: propagate error + } + + var getIntercept = false + if (operands.size > 2) { + if (operands[2] !is PtgMissArg) + getIntercept = PtgCalculator.getBooleanValue(operands[2]) + } + var statistics = false + if (operands.size > 3) { + if (operands[3] !is PtgMissArg) + statistics = PtgCalculator.getBooleanValue(operands[3]) + } + + val ps = calcSlope(operands) + if (ps is PtgErr) return ps + val Pslope = calcSlope(operands) as PtgNumber + val slope = Pslope.`val` // a1 val + val Pintercept = calcIntercept(operands) as PtgNumber + val intercept = Pintercept.`val` // b1 val + + if (operands.size > 3 && (operands[3] is PtgBool || operands[3] is PtgInt)) { + val b = PtgCalculator.getBooleanValue(operands[3]) + if (!b) { + var retstr = "{$slope,$intercept}," + retstr += "{$slope,$intercept}," + retstr += "{$slope,$intercept}," + retstr += "{$slope,$intercept}," + retstr += "{$slope,$intercept}" + val para = PtgArray() + para.setVal(retstr) + return para + } + } + var p = arrayOfNulls(1) + + // figure out the stdev of the slope + val Psteyx = calcSteyx(operands) as PtgNumber + val steyx = Psteyx.`val` // b3 val + + // calc the y error percentage + var yError = steyx * steyx + p[0] = operands[1] + val vp = calcVarp(p) as PtgNumber + val Sxx = vp.`val` * ys.size + yError = yError / Sxx + yError = Math.sqrt(yError) // A2 val + + // calculate degrees of freedom + val degFreedom = ys.size - 2 // b4 val + + // calculate standard error of intercept + var sumXsquared = 0.0 + var sumSquaredX = 0.0 + var sumXYsquared = 0.0 + for (i in xs.indices) { + sumSquaredX += xs[i] * xs[i] + sumXsquared += xs[i] + sumXYsquared += xs[i] * ys[i] + } + sumXsquared *= sumXsquared + sumXYsquared *= sumXYsquared + var interceptError = 1 / (xs.size - sumXsquared / sumSquaredX) + interceptError = Math.sqrt(interceptError) + interceptError *= steyx //b2val + + // calculate residual SS + // first create array of predicted values for the linear array + val predicted = DoubleArray(xs.size) + var residualSS = 0.0 // b5value + for (i in xs.indices) { + predicted[i] = intercept + xs[i] * slope + val d = predicted[i] - ys[i] + residualSS += d * d + } + + // calculate regression SS + p[0] = operands[0] + var pnum = calcAverage(p) as PtgNumber + val average = pnum.`val` + var regressionSS = 0.0 + for (i in xs.indices) { + val d = predicted[i] - average + regressionSS += d * d//A5 value + } + p = arrayOfNulls(2) + p[0] = operands[0] + p[1] = operands[1] + pnum = calcRsq(p) as PtgNumber + val r2 = pnum.`val` // A3 + + // calculate the F value + val F = regressionSS / 1 / (residualSS / degFreedom) // A4 + + // construct the string for creating ptgarray + var retstr = "{$slope,$intercept}," + retstr += "{$yError,$interceptError}," + retstr += "{$r2,$steyx}," + retstr += "{$F,$degFreedom}," + retstr += "{$regressionSS,$residualSS}" + + val parr = PtgArray() + parr.setVal(retstr) + + return parr + } + /* +LOGEST + Returns the parameters of an exponential trend + +LOGINV + Returns the inverse of the lognormal distribution + +LOGNORMDIST + Returns the cumulative lognormal distribution + */ + + + /** + * MAX + * Returns the largest value in a set of values. + * Ignores non-number fields + * Recursively calls for ranges. + * + * + * Usage@ MAX(number1,number2, ...) + * returns@ PtgNumber + */ + //untested + fun calcMax(operands: Array): Ptg { + var result = java.lang.Double.MIN_VALUE // 20090129 KSC -1; + var d: Double? = null + for (i in operands.indices) { + val pthings = operands[i].components // optimized -- do it once!! -jm + if (pthings != null) { + val resPtg = StatisticalCalculator.calcMax(pthings) + try { // some fields may be text, so handle gracefully + if (resPtg.value != null) d = Double(resPtg.value.toString()) + if (d!!.toDouble() > result) { + result = d.toDouble() + } + } catch (e: NumberFormatException) { + } + + } else { + val p = operands[i] + try { + val ov = p.value + if (ov != null) + d = Double(ov.toString()) + if (d!!.toDouble() > result) { + result = d.toDouble() + } + } catch (e: NumberFormatException) { + } catch (e: NullPointerException) { + } + + } + } + if (result == java.lang.Double.MIN_VALUE) + // 20090129 KSC: + result = 0.0 //appears to be default in error situations + return PtgNumber(result) + } + + /** + * MAXA + * Returns the maximum value in a list of arguments, including numbers, text, and logical values + * + * + * Arguments can be the following: numbers; names, arrays, or references that contain numbers; text representations of numbers; or logical values, such as TRUE and FALSE, in a reference. + * Logical values and text representations of numbers that you type directly into the list of arguments are counted. + */ + fun calcMaxA(operands: Array): Ptg { + val alloperands = PtgCalculator.getAllComponents(operands) + if (alloperands.size == 0) + return PtgNumber(0.0) + var max = java.lang.Double.MIN_VALUE + for (i in alloperands.indices) { + val o = alloperands[i].value + try { + var d = java.lang.Double.MIN_VALUE + if (o is Number) + d = o.toDouble() + else if (o is Boolean) + d = (if (o.booleanValue()) 1 else 0).toDouble() + else + d = Double(o.toString()) + max = Math.max(max, d) + } catch (e: NumberFormatException) { + // Arguments that are error values or text that cannot be translated into numbers cause errors. + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + return PtgNumber(max) + } + + /** + * MEDIAN + * Returns the median of the given numbers + */ + fun calcMedian(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + val alloperands = PtgCalculator.getAllComponents(operands) + val t = CompatibleVector() + var retval = 0.0 + for (i in alloperands.indices) { + val p = alloperands[i] + try { + val d = Double(p.value.toString()) + t.addOrderedDouble(d) + } catch (e: NumberFormatException) { + } + + } + + try { + val dub = arrayOfNulls(t.size) + t.toTypedArray() + val dd = t.size.toDouble() % 2 + if (t.size.toDouble() % 2 == 0.0) { + val firstValLoc = t.size / 2 - 1 + val lastValLoc = firstValLoc + 1 + val firstVal = dub[firstValLoc].toDouble() + val lastVal = dub[lastValLoc].toDouble() + retval = (firstVal + lastVal) / 2 + } else { + // it's odd + val firstValLoc = (t.size - 1) / 2 + val firstVal = dub[firstValLoc].toDouble() + retval = firstVal + } + return PtgNumber(retval) + } catch (e: ArrayIndexOutOfBoundsException) { // 20090701 KSC: catch exception + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + /** + * MIN + * Returns the smallest number in a set of values. + * Ignores non-number fields. Note that it also recursivly calls itself + * for things like PtgRange. + * + * + * Usage@ MIN(number1,number2, ...) + * returns PtgNumber + */ + fun calcMin(operands: Array): Ptg { + var result = java.lang.Double.MAX_VALUE + var d: Double? = null + for (i in operands.indices) { + val pthings = operands[i].components // optimized -- do it once!! -jm + if (pthings != null) { + val resPtg = StatisticalCalculator.calcMin(pthings) + try { // some fields may be text, so handle gracefully + if (resPtg is PtgErr) return resPtg // 20090205 KSC: propagate error + if (resPtg.value != null) { + d = Double(resPtg.value.toString()) + // 20090129 KSC; if (d.doubleValue() < result || result == -1){result = d.doubleValue();} // 20070215 KSC: only access d if not null! + if (d.toDouble() < result) { + result = d.toDouble() + } // 20070215 KSC: only access d if not null! + } + } catch (e: NumberFormatException) { + } catch (e: NullPointerException) { + } + // 20070209 KSC + } else { + val p = operands[i] + try { + val ov = p.value + if (ov != null) { + if (ov.toString() == PtgErr(PtgErr.ERROR_NA).toString()) + // 20090205 KSC: propagate error value + return PtgErr(PtgErr.ERROR_NA) + d = Double(ov.toString()) + // 20090129 KSC; result is defaulted to max + if (d.toDouble() < result) { + result = d.toDouble() + } // 20070215 KSC: only access d if not null! + } + } catch (e: NumberFormatException) { + } catch (e: NullPointerException) { + } + + } + } + if (result == java.lang.Double.MAX_VALUE) + // 20090129 KSC: + result = 0.0 //appears to be default in error situations + return PtgNumber(result) + // return pnum; + } + + /** + * MINA + * Returns the smallest value in a list of arguments, including numbers, text, and logical values + * + * + * Arguments can be the following: numbers; names, arrays, or references that contain numbers; text representations of numbers; or logical values, such as TRUE and FALSE, in a reference. + * Logical values and text representations of numbers that you type directly into the list of arguments are counted. + */ + fun calcMinA(operands: Array): Ptg { + val alloperands = PtgCalculator.getAllComponents(operands) + if (alloperands.size == 0) + return PtgNumber(0.0) + var min = java.lang.Double.MAX_VALUE + for (i in alloperands.indices) { + val o = alloperands[i].value + try { + var d = java.lang.Double.MAX_VALUE + if (o is Number) + d = o.toDouble() + else if (o is Boolean) + d = (if (o.booleanValue()) 1 else 0).toDouble() + else + d = Double(o.toString()) + min = Math.min(min, d) + } catch (e: NumberFormatException) { + // Arguments that are error values or text that cannot be translated into numbers cause errors. + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + return PtgNumber(min) + } + + /** + * MODE + * Returns the most common value in a data set + */ + fun calcMode(operands: Array): Ptg { + val alloperands = PtgCalculator.getAllComponents(operands) + val vals = Vector() + val occurences = Vector() + var retval = 0.0 + for (i in alloperands.indices) { + val p = alloperands[i] + try { + val d = Double(p.value.toString()) + if (vals.contains(d)) { + val loc = vals.indexOf(d) + val nums = occurences.get(loc) as Double + val newnum = nums + 1 + occurences.setElementAt(newnum, loc) + } else { + vals.add(d) + occurences.add(1) + } + } catch (e: NumberFormatException) { + } + + } + var biggest = 0.0 + val numvalues = 0.0 + for (i in vals.indices) { + val size = occurences.elementAt(i) as Double + if (size > biggest) { + biggest = size + val newhigh = vals.elementAt(i) as Double + retval = newhigh + } + } + return PtgNumber(retval) + } + + /* +NEGBINOMDIST + Returns the negative binomial distribution + */ + + + /** + * NORMDIST + * Returns the normal cumulative distribution + * NORMDIST(x,mean,standard_dev,cumulative) + * X is the value for which you want the distribution. + * Mean is the arithmetic mean of the distribution. + * Standard_dev is the standard deviation of the distribution. + * Cumulative is a logical value that determines the form of the function. + * If cumulative is TRUE, NORMDIST returns the cumulative distribution function; + * if FALSE, it returns the probability mass function. + * + * + * ******************************************************************************** + * IMPORTANT NOTE: when Cumulative=TRUE the results are not accurate to 9 siginfiicant digits in all cases + * (When cumulative = TRUE, the formula is the integral from negative infinity to x of the given formula) + * ******************************************************************************** + */ + fun calcNormdist(operands: Array): Ptg { + if (operands.size < 4) return PtgErr(PtgErr.ERROR_VALUE) + try { + //If mean or standard_dev is nonnumeric, NORMDIST returns the #VALUE! error value. + val x = operands[0].doubleVal + val mean = operands[1].doubleVal + // if standard_dev ≤ 0, NORMDIST returns the #NUM! error value. + val stddev = operands[2].doubleVal + if (stddev <= 0) return PtgErr(PtgErr.ERROR_NUM) + val cumulative = PtgCalculator.getBooleanValue(operands[3]) + // If mean = 0, standard_dev = 1, and cumulative = TRUE, NORMDIST returns the standard normal distribution, NORMSDIST. + if (mean == 0.0 && stddev == 1.0 && cumulative) + return calcNormsdist(operands) + + if (!cumulative) { // return the probability mass function. *** definite excel algorithm + var a = Math.sqrt(2.0 * Math.PI * Math.pow(stddev, 2.0)) + a = 1.0 / a + var exp = Math.pow(x - mean, 2.0) + exp = exp / (2 * Math.pow(stddev, 2.0)) + val b = Math.exp(-exp) + return PtgNumber(a * b) + } else { + // When cumulative = TRUE, the formula is the integral from negative infinity to x of the given formula. + // = the cumulative distribution function + val o = arrayOf(PtgNumber((x - mean) / (stddev * Math.sqrt(2.0)))) + val erf = EngineeringCalculator.calcErf(o) + val cdf = 0.5 * (1 + erf.doubleVal) + return PtgNumber(cdf) + /* // try this: + Ptg[] o= { new PtgNumber((x-mean)/(stddev))}; + return calcNormsdist(o); + */ + } + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + /** + * The NORMSDIST function returns the result of the standard normal cumulative distribution function + * for a particular value of the random variable X. The Excel function adheres to the + * following mathematical approximation, P(x), of the following + * standard normal cumulative distribution function (CDF): + * P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where + * + * Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + * t = 1/(1+px) + * p = 0.2316419 + * b1 = 0.319381530 + * b2 = -0.356563782 + * b3 = 1.781477937 + * b4 = -1.821255978 + * b5 = 1.330274429 + * + * with the following parameters: + * + * abs(error(x))<7.5 * 10^-8 + * + * The NORMSDIST function returns the result of the standard normal + * CDF for a standard normal random variable Z with a mean of 0 (zero) + * and a standard deviation of 1. The CDF is found by taking the integral + * of the following standard normal probability density function + * + * Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + * + * from negative infinity to the value (z) of the random variable in question. + * The result of the integral gives the probability that Z will occur between the + * values of negative infinity and z. + * + * NORMSDIST(z) must be evaluated by using an approximation procedure. + * Earlier versions of Excel used the same procedure for all values of z. + * For Excel 2003, two different approximations are used: + * one for |z| less than or equal to five, and a second for |z| greater than five. + * The two new procedures are each more accurate than the previous procedure + * over the range that they are applied. In earlier versions of Excel, + * accuracy deteriorates in the tails of the distribution yielding three significant + * digits for z = 4 as reported in Knusel's paper. Also, in the neighborhood of z = 1.2, + * NORMSDIST yields only six significant digits. However, in practice, this is likely + * to be sufficient for most users. + * + * INFO ATP DEFINITION NORMDIST NOVEMBER 2006: + * The NORMSDIST function returns the result of the standard normal cumulative distribution function for a particular value of the random variable X. + * The Microsoft Excel function adheres to the following mathematical approximation, P(x), of the standard normal CDF + * + * P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where + * + * Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + * t = 1/(1+px) + * p = 0.2316419 + * b1 = 0.319381530 + * b2 = -0.356563782 + * b3 = 1.781477937 + * b4 = -1.821255978 + * b5 = 1.330274429 + * + * + * with these parameters, abs(error(x))<7.5 * 10^-8. + * + * + * In summary, if you use Excel 2002 and earlier, you should be satisfied with NORMSDIST. + * However, if you must have highly accurate NORMSDIST(z) values for z far from 0 + * (such as |z| greater than or equal to four), Excel 2003 might be required. + * NORMSDIST(-4) = 0.0000316712; earlier versions would be accurate only as far as 0.0000317. + * + * from a forum: + * Take into consideration that Z is related to x, xm(mean) and s(std.dev.) + * through the expression Z = (x - xm) / s. + * This means that as soon as you get Z, you can proceed and calculate the integral of the + * CDF by using P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x). + * + * I wish you good code. + * + * Some other identities that express NORMSDIST in terms of other functions + * that have no closed form are + * NormSDist(x) = ErfC(-x/Sqrt(2))/2 = (1-Erf(-x/Sqrt(2)))/2 for x<=0 + * NormSDist(x) = 1-ErfC(x/Sqrt(2))/2 = (1+Erf(x/Sqrt(2)))/2 for x>=0 + * NormSDist(x) = (1–GammaDist(x^2/2,1/2,1,TRUE))/2 for x<=0 + * NormSDist(x) = (1+GammaDist(x^2/2,1/2,1,TRUE))/2 for x>=0 + * NormSDist(x) = ChiDist(x^2,1)/2 for x<=0 + * NormSDist(x) = 1-ChiDist(x^2,1)/2 for x>=0 + * + * // for 2002: + * The NORMSDIST function returns the result of the standard normal cumulative distribution + * function for a particular value of the random variable X. The Excel function adheres to the + * following mathematical approximation, P(x), of the following standard normal cumulative + * distribution function (CDF) + * + * P(x) = 1 -Z(x)*(b1*t+b2*t^2+b3t^3+b4t^4+b5t^5)+error(x), where + * + * Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + * t = 1/(1+px) + * p = 0.2316419 + * b1 = 0.319381530 + * b2 = -0.356563782 + * b3 = 1.781477937 + * b4 = -1.821255978 + * b5 = 1.330274429 + * + * + * with the following parameters: + * abs(error(x))<7.5 * 10^-8 + * + * The NORMSDIST function returns the result of the standard normal CDF for a standard + * normal random variable Z with a mean of 0 (zero) and a standard deviation of 1. The CDF + * is found by taking the integral of the following standard normal probability density + * function + * + * Z(x) = (1/(sqrt(2*pi()))*exp(-x^2/2)) + * + * from negative infinity to the value (z) of the random variable in question. The result + * of the integral gives the probability that Z will occur between the values of negative + * infinity and z. * + * + * from openoffice: + * The wrong results in NORMSDIST are due to cancellation for small negative + * values, where gauss() is near -0.5 + * The problem can be solved in two ways: + * (1) Use NORMSDIST(x)= 0.5*ERFC(-x/SQRT(2)). Unfortunaly ERFC is only an addin + * function, see my issue 97091. + * (2) Use NORMSDIST(x) + * = 0.5+0.5*GetLowRegIGamma(0.5,0.5*x*x) for x>=0 + * = 0.5*GetUpRegIGamma(0.5,0.5*x*x) for x<0 + * + * + * From a forum: + * For z less than 2, ERF = 2/SQRT (pi) * e^(-z^2) * z (1+ (2z^2)/3 + ((2z^2)^2)/15 + … + * For z greater than 2, ERF = 1- (e^(-z^2))/(SQRT(pi)) * (1/z - 1/(2z^3) + 3/(4z^5) -….) + */ + /** + * NORMSDIST + * Returns the standard normal cumulative distribution + * + * + * NORMSDIST(z) returns the probability that the observed value of a + * standard normal random variable will be less than or equal to z. + * A standard normal random variable has mean 0 and standard deviation 1 + * (and also variance 1 because variance = standard deviation squared). + * + * + * NOTE: THIS FUNCTION IS ACCURATE AS COMPARED TO EXCEL VALUES ONLY UP TO 7 SIGNIFICANT DIGITS + */ + fun calcNormsdist(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + try { + val result: Double + val x = operands[0].doubleVal + val b1 = 0.319381530 + val b2 = -0.356563782 + val b3 = 1.781477937 + val b4 = -1.821255978 + val b5 = 1.330274429 + val p = 0.2316419 + val c = 0.39894228 + + // below is consistently correct to at least 7 decimals using a range of test values + if (x >= 0.0) { + val t = 1.0 / (1.0 + p * x) + result = 1.0 - c * Math.exp(-x * x / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1) + } else { + val t = 1.0 / (1.0 - p * x) + result = c * Math.exp(-x * x / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1) + } + /* + // try this one: + double z= (1/(Math.sqrt(2*Math.PI))*Math.exp(-Math.pow(x, 2)/2.0)); + double t = 1/(1+p*x); + double e= EngineeringCalculator.calcErf(operands).getDoubleVal(); + result = 1 -z*(b1*t+b2*Math.pow(t, 2)+b3*Math.pow(t, 3)+b4*Math.pow(t, 4)+b5*Math.pow(t, 5))+e; +*/ + val bd = BigDecimal(result) + bd.setScale(15, java.math.RoundingMode.HALF_UP) + return PtgNumber(bd.toDouble()) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + /** + * NORMSINV + * Returns the inverse of the standard normal cumulative distribution. The distribution has a mean of zero and a standard deviation of one. + * + * + * Syntax + * NORMSINV(probability) + * Probability is a probability corresponding to the normal distribution. + * + * + * If probability is nonnumeric, NORMSINV returns the #VALUE! error value. + * If probability < 0 or if probability > 1, NORMSINV returns the #NUM! error value. + * + * + * Because the calculation of the NORMSINV function uses a systematic search + * over the returned values of the NORMSDIST function, the accuracy of the + * NORMSDIST function is critical. + * + * + * Also, the search must be sufficiently refined that it "homes in" on an appropriate + * answer. To use the textbook Normal probability distribution table as an analogy, + * entries in the table must be accurate. Also, the table must contain so many entries + * that you can find the appropriate row of the table that yields a probability that is + * correct to a specific number of decimal places. Instead, individual entries are computed + * on demand as the search through the "table" + * + * + * However, the table must be accurate and the search must continue far enough + * that it does not stop prematurely at an answer that has a corresponding probability + * (or row of the table) that is too far from the value of p that you use in the call to + * NORMSINV(p). Therefore, the NORMSINV function has been improved in the following ways: + * + * + * - The accuracy of the NORMSDIST function has been improved. + * - The search process has been improved to increase refinement. + * + * + * The NORMSDIST function has been improved in Excel 2003 and in later versions of Excel. + * Typically, inaccuracies in earlier versions of Excel occur for extremely small or extremely + * large values of p in NORMSINV(p). The values in Excel 2003 and in later versions of Excel + * are much more accurate. + * + * + * Accuracy of NORMSDIST has been improved in Excel 2003 and in later versions of Excel. + * In earlier versions of Excel, a single computational procedure was used for all values + * of z. Results were essentially accurate to 7 decimal places, more than sufficient for + * most practical examples. + * + * + * Results in earlier versions of Excel + * ** The accuracy of the NORMSINV function depends on two factors. Because the calculation of the NORMSINV + * function uses a systematic search over the returned values of the NORMSDIST function, the accuracy of + * the NORMSDIST function is critical. + * + * + * Also, the search must be sufficiently refined that it "homes in" on an appropriate answer. + * To use the textbook Normal probability distribution table as an analogy, entries in the table + * must be accurate. Also, the table must contain so many entries that you can find the + * appropriate row of the table that yields a probability that is correct to a specific number + * of decimal places. + * + * + * ' This function is a replacement for the Microsoft Excel Worksheet function NORMSINV. + * ' It uses the algorithm of Peter J. Acklam to compute the inverse normal cumulative + * ' distribution. Refer to http://home.online.no/~pjacklam/notes/invnorm/index.html for + * ' a description of the algorithm. + * ' Adapted to VB by Christian d'Heureuse, http://www.source-code.biz. + * Public Function NormSInv(ByVal p As Double) As Double + * Const a1 = -39.6968302866538, a2 = 220.946098424521, a3 = -275.928510446969 + * Const a4 = 138.357751867269, a5 = -30.6647980661472, a6 = 2.50662827745924 + * Const b1 = -54.4760987982241, b2 = 161.585836858041, b3 = -155.698979859887 + * Const b4 = 66.8013118877197, b5 = -13.2806815528857, c1 = -7.78489400243029E-03 + * Const c2 = -0.322396458041136, c3 = -2.40075827716184, c4 = -2.54973253934373 + * Const c5 = 4.37466414146497, c6 = 2.93816398269878, d1 = 7.78469570904146E-03 + * Const d2 = 0.32246712907004, d3 = 2.445134137143, d4 = 3.75440866190742 + * Const p_low = 0.02425, p_high = 1 - p_low + * Dim q As Double, r As Double + * If p < 0 Or p > 1 Then + * Err.Raise vbObjectError, , "NormSInv: Argument out of range." + * ElseIf p < p_low Then + * q = Sqr(-2 * Log(p)) + * NormSInv = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _ + * ((((d1 * q + d2) * q + d3) * q + d4) * q + 1) + * ElseIf p <= p_high Then + * q = p - 0.5: r = q * q + * NormSInv = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q / _ + * (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1) + * Else + * q = Sqr(-2 * Log(1 - p)) + * NormSInv = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / _ + * ((((d1 * q + d2) * q + d3) * q + d4) * q + 1) + * End If + * End Function + * + * + * NORMSINV= NORMINV(p; 0; 1) + */ + private fun expm1(x: Double): Double { + val DBL_EPSILON = 0.0000001 + var y: Double + val a = Math.abs(x) + + if (a < DBL_EPSILON) return x + if (a > 0.697) return Math.exp(x) - 1 /* negligible cancellation */ + + if (a > 1e-8) + y = Math.exp(x) - 1 + else + /* Taylor expansion, more accurate in this range */ + y = (x / 2 + 1) * x + + /* Newton step for solving log(1 + y) = x for y : */ + /* WARNING: does not work for y ~ -1: bug in 1.5.0 -- fixed??*/ + y -= (1 + y) * (Math.log(1 + y) - x) + return y + } + + private fun quartile(p: Double, mu: Double, sigma: Double): Double { + var p = p + val lower_tail = true + val log_p = false + val R_D__0 = 0.0 + val R_D__1 = 1.0 + val R_DT_0 = 0.0 //((lower_tail) ? R_D__0 : R_D__1); /* 0 */ + val R_DT_1 = 1.0 // ((lower_tail) ? R_D__1 : R_D__0) /* 1 */ + + val p_: Double + val q: Double + var r: Double + var `val`: Double + if (p == R_DT_0) return java.lang.Double.NEGATIVE_INFINITY + if (p == R_DT_1) return java.lang.Double.POSITIVE_INFINITY + //R_Q_P01_check(p); + + if (sigma < 0) return 0.0 + if (sigma == 0.0) return mu + + p = if (log_p) if (lower_tail) Math.exp(p) else -expm1(p) else if (lower_tail) p else 1 - p + p_ = if (log_p) if (lower_tail) Math.exp(p) else -expm1(p) else if (lower_tail) p else 1 - p/* real lower_tail prob. p */ + q = p_ - 0.5 + + if (Math.abs(q) <= .425) {/* 0.075 <= p <= 0.925 */ + r = .180625 - q * q + `val` = q * (((((((r * 2509.0809287301226727 + 33430.575583588128105) * r + 67265.770927008700853) * r + 45921.953931549871457) * r + 13731.693765509461125) * r + 1971.5909503065514427) * r + 133.14166789178437745) * r + 3.387132872796366608) / (((((((r * 5226.495278852854561 + 28729.085735721942674) * r + 39307.89580009271061) * r + 21213.794301586595867) * r + 5394.1960214247511077) * r + 687.1870074920579083) * r + 42.313330701600911252) * r + 1.0) + } else { /* closer than 0.075 from {0,1} boundary */ + + /* r = min(p, 1-p) < 0.075 */ + if (q > 0) + r = (if (log_p) if (lower_tail) -expm1(p) else Math.exp(p) else if (lower_tail) 1 - p else p)/* 1-p */ + else + r = p_/* = R_DT_Iv(p) ^= p */ + + r = Math.sqrt(-/* else */if (log_p && (lower_tail && q <= 0 || !lower_tail && q > 0)) p else Math.log(r)) + /* r = sqrt(-log(r)) <==> min(p, 1-p) = exp( - r^2 ) */ + + if (r <= 5.0) { /* <==> min(p,1-p) >= exp(-25) ~= 1.3888e-11 */ + r += -1.6 + `val` = (((((((r * 7.7454501427834140764e-4 + .0227238449892691845833) * r + .24178072517745061177) * r + 1.27045825245236838258) * r + 3.64784832476320460504) * r + 5.7694972214606914055) * r + 4.6303378461565452959) * r + 1.42343711074968357734) / (((((((r * 1.05075007164441684324e-9 + 5.475938084995344946e-4) * r + .0151986665636164571966) * r + .14810397642748007459) * r + .68976733498510000455) * r + 1.6763848301838038494) * r + 2.05319162663775882187) * r + 1.0) + } else { /* very close to 0 or 1 */ + r += -5.0 + `val` = (((((((r * 2.01033439929228813265e-7 + 2.71155556874348757815e-5) * r + .0012426609473880784386) * r + .026532189526576123093) * r + .29656057182850489123) * r + 1.7848265399172913358) * r + 5.4637849111641143699) * r + 6.6579046435011037772) / (((((((r * 2.04426310338993978564e-15 + 1.4215117583164458887e-7) * r + 1.8463183175100546818e-5) * r + 7.868691311456132591e-4) * r + .0148753612908506148525) * r + .13692988092273580531) * r + .59983220655588793769) * r + 1.0) + } + + if (q < 0.0) + `val` = -`val` + /* return (q >= 0.)? r : -r ;*/ + } + return mu + sigma * `val` + } + + fun calcNormInv(operands: Array): Ptg { + try { + + val p = operands[0].doubleVal + if (p < 0 || p > 1) return PtgErr(PtgErr.ERROR_NUM) + val mean = operands[1].doubleVal + val stddev = operands[2].doubleVal + if (stddev <= 0) return PtgErr(PtgErr.ERROR_NUM) + // If mean = 0 and standard_dev = 1, NORMINV uses the standard normal inverse (see NORMSINV). + val result = quartile(p, mean, stddev) + return PtgNumber(result) + } catch (e: Exception) { + } + + return PtgErr(PtgErr.ERROR_VALUE) + } + + + fun calcNormsInv(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.valueError + try { + val x = operands[0].doubleVal + if (x < 0 || x > 1) + return PtgErr(PtgErr.ERROR_NUM) + /* + * the algorithm is supposed to iterate over NORMSDIST values using Newton-Raphson's approximation + * Newton-Raphson uses an iterative process to approach one root of a function (i.e the zero of the function or + * where the function = 0) + * Newton-Raphson is in the form of: + * + * Xn+1= Xn- (f(xn)/f'(xn) + * where Xn is the current known X value, f(xn) is the value of the function at X, f'(Xn) is the derivative or slope + * at X, Xn+1 is the next X value. Essentially, f'(xn) represents (f(x)/delta x) so f(xn)/f'(xn)== delta x. + * the more iterations we run over, the closer delta x will be to 0. + * + * The Newton-Raphson method does not always work, however. It runs into problems in several places. + What would happen if we chose an initial x-value of x=0? We would have a "division by zero" error, and would not be able to proceed. + You may also consider operating the process on the function f(x) = x1/3, using an inital x-value of x=1. + Do the x-values converge? Does the delta-x decrease toward zero (0)? + + is the derivative of the standard normal distribution = the standard probability density function??? + */ + /* below is not accurate enough - but N-R approximation is impossible ((:*/ + // Coefficients in rational approximations + val a = doubleArrayOf(-3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02, 1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00) + + val b = doubleArrayOf(-5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02, 6.680131188771972e+01, -1.328068155288572e+01) + + val c = doubleArrayOf(-7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00, -2.549732539343734e+00, 4.374664141464968e+00, 2.938163982698783e+00) + + val d = doubleArrayOf(7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00, 3.754408661907416e+00) + + // Define break-points. + val plow = 0.02425 + val phigh = 1 - plow + val result: Double + // Rational approximation for lower region: + if (x < plow) { + val q = Math.sqrt(-2 * Math.log(x)) + val r = BigDecimal((((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)) + r.setScale(15, java.math.RoundingMode.HALF_UP) + return PtgNumber(r.toDouble()) + } + + // Rational approximation for upper region: + if (phigh < x) { + val q = Math.sqrt(-2 * Math.log(1 - x)) + val r = BigDecimal(-(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)) + r.setScale(15, java.math.RoundingMode.HALF_UP) + return PtgNumber(r.toDouble()) + } + + // Rational approximation for central region: + val q = x - 0.5 + val r = q * q + val rr = BigDecimal((((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1)) + + rr.setScale(15, java.math.RoundingMode.HALF_UP) + return PtgNumber(rr.toDouble()) + } catch (e: Exception) { + return PtgCalculator.valueError + } + + } + + /** + * PEARSON + * Returns the Pearson product moment correlation coefficient + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcPearson(operands: Array): Ptg { + return calcCorrel(operands) + } + /* +PERCENTILE + Returns the k-th percentile of values in a range + +PERCENTRANK + Returns the percentage rank of a value in a data set + +PERMUT + Returns the number of permutations for a given number of objects + +POISSON + Returns the Poisson distribution + +PROB + Returns the probability that values in a range are between two limits + */ + + /** + * QUARTILE + * Returns the quartile of a data set + */ + fun calcQuartile(operands: Array): Ptg { + val aveoperands = arrayOfNulls(1) + aveoperands[0] = operands[0] + val allVals = PtgCalculator.getAllComponents(aveoperands) + val t = CompatibleVector() + val retval = 0.0 + for (i in allVals.indices) { + val p = allVals[i] + try { + val d = Double(p.value.toString()) + t.addOrderedDouble(d) + } catch (e: NumberFormatException) { + Logger.logErr(e) + } + + } + + val dub = arrayOfNulls(t.size) + t.toTypedArray() + val quart: Int? + val o = operands[1].value + if (o is Int) + quart = operands[1].value as Int + else + quart = Integer.valueOf((operands[1].value as Double).toInt()) + + val quartile = quart!!.toFloat() + if (quart.toInt() == 0) { // return minimum value + return PtgNumber(dub[0].toDouble()) + } else if (quart.toInt() == 4) { // return maximum value + return PtgNumber(dub[t.size - 1].toDouble()) + } else if (quart.toInt() > 4 || quart.toInt() < 0) { + return PtgErr(PtgErr.ERROR_NUM) + } + // find the kth smallest + var kk = quartile / 4 + kk = (dub.size - 1) * kk + kk++ + // truncate k, but keep the remainder. + var k = -1 + var remainder = 0f + if (kk % 1 != 0f) { + remainder = kk % 1 + var s = kk.toString() + var ss = s.substring(s.indexOf(".")) + ss = "0$ss" + remainder = Float(ss) + s = s.substring(0, s.indexOf(".")) + k = Integer.valueOf(s).toInt() + } else { + k = kk.toInt() / 1 + } + if (k >= dub.size) + return PtgErr(PtgErr.ERROR_VALUE) + val firstVal = dub[k - 1].toDouble() + val secondVal = dub[k].toDouble() + val output = firstVal + remainder * (secondVal - firstVal) + return PtgNumber(output) + } + + /** + * RANK + * Returns the rank of a number in a list of numbers + * + * + * RANK(number,ref,order) + * Number is the number whose rank you want to find. + * Ref is an array of, or a reference to, a list of numbers. Nonnumeric values in ref are ignored. + * Order is a number specifying how to rank number. + * + * + * If order is 0 (zero) or omitted, Microsoft Excel ranks number as if ref were a list sorted in descending order. + * If order is any nonzero value, Microsoft Excel ranks number as if ref were a list sorted in ascending order. + */ + fun calcRank(operands: Array): Ptg { + // the number + if (operands.size < 2) return PtgErr(PtgErr.ERROR_VALUE) + val num = operands[0] + var theNum: Double? = null + try { + val o = num.value + if (o == "") + theNum = 0.0 + else + theNum = Double(o.toString()) + } catch (nfm: NumberFormatException) { + return PtgErr() + } + + //ascending or decending? + var ascending = true + if (operands.size < 3) { + ascending = false + } else if (operands[2] is PtgMissArg) { + ascending = false + } else { + val order = operands[2] as PtgInt + val i = order.`val` + if (i == 0) ascending = false + } + val aveoperands = arrayOfNulls(1) + aveoperands[0] = operands[1] + val refs = PtgCalculator.getAllComponents(aveoperands) + val retList = CompatibleVector() + val retval = 0.0 + for (i in refs.indices) { + val p = refs[i] + try { + val d = Double(p.value.toString()) + retList.addOrderedDouble(d) + } catch (e: NumberFormatException) { + } + + } + val dubRefs = arrayOfNulls(retList.size) + if (ascending) { + retList.toTypedArray() + } else { + for (i in dubRefs.indices) { + dubRefs[i] = retList.last() as Double + retList.removeAt(retList.size - 1) + } + } + var res = -1 + var i = 0 + while (i < dubRefs.size) { + if (dubRefs[i].toString().equals(theNum.toString(), ignoreCase = true)) { + res = i + 1 + i = dubRefs.size + } + i++ + } + return if (res == -1) { + PtgErr(PtgErr.ERROR_NA) + } else { + PtgInt(res) + } + + } + + + /** + * RSQ + * Returns the square of the Pearson product moment correlatin coefficient + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcRsq(operands: Array): Ptg { + val p = calcPearson(operands) as PtgNumber + var d = p.`val` + d = d * d + return PtgNumber(d) + } + /* +SKEW + Returns the skewness of a distribution + */ + + /** + * SLOPE + * Returns the slope of the linear regression line + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcSlope(operands: Array): Ptg { + if (operands.size != 2) + return PtgErr(PtgErr.ERROR_VALUE) + val yvals = PtgCalculator.getDoubleValueArray(operands[0]) + val xvals = PtgCalculator.getDoubleValueArray(operands[1]) + if (xvals == null || yvals == null) return PtgErr(PtgErr.ERROR_NA)//20090130 KSC: propagate error + var sumXVals = 0.0 + for (i in xvals.indices) { + sumXVals += xvals[i] + } + var sumYVals = 0.0 + for (i in yvals.indices) { + sumYVals += yvals[i] + } + var sumXYVals = 0.0 + for (i in yvals.indices) { + sumXYVals += xvals[i] * yvals[i] + } + var sqrXVals = 0.0 + for (i in xvals.indices) { + sqrXVals += xvals[i] * xvals[i] + } + val toparg = sumXVals * sumYVals - sumXYVals * yvals.size + val bottomarg = sumXVals * sumXVals - sqrXVals * xvals.size + val res = toparg / bottomarg + return PtgNumber(res) + } + + /** + * SMALL + * Returns the k-th smallest value in a data set + * + * + * SMALL(array,k) + * Array is an array or range of numerical data for which you want to determine the k-th smallest value. + * K is the position (from the smallest) in the array or range of data to return. + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcSmall(operands: Array): Ptg { + if (operands.size != 2) return PtgErr(PtgErr.ERROR_VALUE) + val rng = operands[0] + val array = PtgCalculator.getAllComponents(rng) + if (array.size == 0) return PtgErr(PtgErr.ERROR_NUM) + val k = PtgCalculator.getDoubleValueArray(operands[1])!![0].toInt() + if (k <= 0 || k > array.size) + return PtgErr(PtgErr.ERROR_NUM) + + val sortedValues = CompatibleVector() + for (i in array.indices) { + val p = array[i] + try { + val d = Double(p.value.toString()) + sortedValues.addOrderedDouble(d) + } catch (e: NumberFormatException) { + } + + } + try { + return PtgNumber((sortedValues[k - 1] as Double).toDouble()) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + /* +STANDARDIZE + Returns a normalized value + */ + + /** + * STDEV(number1,number2, ...) + * + * + * Number1,number2, ... are 1 to 255 number arguments corresponding to a sample of a population. + * You can also use a single array or a reference to an array instead of arguments separated by commas. + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcStdev(operands: Array): Ptg { + val allVals = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + var sqrDev = 0.0 + for (i in allVals.indices) { + val p = calcAverage(operands) as PtgNumber + val ave = p.`val` + sqrDev += Math.pow(allVals[i] - ave, 2.0) + } + val retval = Math.sqrt(sqrDev / (allVals.size - 1)) + return PtgNumber(retval) + } + /* +STDEVA + Estimates standard deviation based on a sample, including numbers, text, and logical values + +STDEVP + Calculates standard deviation based on the entire population + +STDEVPA + Calculates standard deviation based on the entire population, including numbers, text, and logical values + */ + + /** + * STEYX + * Returns the standard error of the predicted y-value for each x in the regression + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcSteyx(operands: Array): Ptg { + val arr = arrayOfNulls(1) + arr[0] = operands[0] + var pn = calcVarp(arr) as PtgNumber + var yVarp = pn.`val` + arr[0] = operands[1] + pn = calcVarp(arr) as PtgNumber + var xVarp = pn.`val` + val y = PtgCalculator.getDoubleValueArray(operands[0]) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + yVarp *= y.size.toDouble() + xVarp *= y.size.toDouble() + pn = calcSlope(operands) as PtgNumber + val slope = pn.`val` + var retval = yVarp - slope * slope * xVarp + retval = retval / (y.size - 2) + retval = Math.sqrt(retval) + return PtgNumber(retval) + } + /* +TDIST + Returns the Student's t-distribution + +TINV + Returns the inverse of the Student's t-distribution + */ + + /** + * TREND + * Returns values along a linear trend + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcTrend(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + // KSC: THIS FUNCTION DOES NOT WORK AS EXPECTED: TODO: FIX! + + if (true) return PtgErr(PtgErr.ERROR_VALUE) + // Ptg[] forecast = new Ptg[3]; + val forecast = arrayOfNulls(2) + forecast[0] = operands[0] + if (operands.size > 1) + forecast[1] = operands[1] + // TODO: + // else // If known_x's is omitted, it is assumed to be the array {1,2,3,...} that is the same size as known_y's. + val newXs: Array + if (operands.size > 2) + newXs = PtgCalculator.getAllComponents(operands[2]) + else + newXs = PtgCalculator.getAllComponents(operands[1]) + + var retval = "" + for (i in newXs.indices) { + //forecast[0] = newXs[i]; + val p = calcForecast(forecast) as PtgNumber + val forcst = p.`val` + retval += "{$forcst}," + } + // get rid of trailing comma + retval = retval.substring(0, retval.length - 1) + val pa = PtgArray() + pa.setVal(retval) + return pa + } + + /* +TRIMMEAN + Returns the mean of the interior of a data set + +TTEST + Returns the probability associated with a Student's t-Test + */ + + /** + * VAR + * Estimates variance based on a sample + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcVar(operands: Array): Ptg { + val allVals = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + var sqrDev = 0.0 + for (i in allVals.indices) { + val p = calcAverage(operands) as PtgNumber + val ave = p.`val` + sqrDev += Math.pow(allVals[i] - ave, 2.0) + } + val retval = sqrDev / (allVals.size - 1) + return PtgNumber(retval) + } + /* +VARA + Estimates variance based on a sample, including numbers, text, and logical values + */ + + /** + * VARp + * Estimates variance based on a full population + * + * @throws CalculationException + */ + @Throws(CalculationException::class) + fun calcVarp(operands: Array): Ptg { + val allVals = PtgCalculator.getDoubleValueArray(operands) ?: return PtgErr(PtgErr.ERROR_NA) +//20090130 KSC: propagate error + var sqrDev = 0.0 + for (i in allVals.indices) { + val p = calcAverage(operands) as PtgNumber + val ave = p.`val` + sqrDev += Math.pow(allVals[i] - ave, 2.0) + } + val retval = sqrDev / allVals.size + return PtgNumber(retval) + } + } + /* +VARPA + Calculates variance based on the entire population, including numbers, text, and logical values + +WEIBULL + Returns the Weibull distribution + +ZTEST + Returns the two-tailed P-value of a z-test + +*/ + +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/TextCalculator.java b/src/main/java/io/starter/formats/XLS/formulas/TextCalculator.java deleted file mode 100644 index 1869607..0000000 --- a/src/main/java/io/starter/formats/XLS/formulas/TextCalculator.java +++ /dev/null @@ -1,830 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.XLS.formulas; - -import io.starter.OpenXLS.DateConverter; -import io.starter.OpenXLS.ExcelTools; -import io.starter.OpenXLS.WorkBookHandle; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.Formula; -import io.starter.formats.XLS.XLSConstants; -import io.starter.formats.XLS.Xf; -import io.starter.toolkit.Logger; -import io.starter.toolkit.StringTool; - -import java.io.UnsupportedEncodingException; -import java.text.DecimalFormat; -import java.text.Format; -import java.util.Date; - -/** - * TextCalculator is a collection of static methods that operate - * as the Microsoft Excel function calls do. - *

                      - * All methods are called with an array of ptg's, which are then - * acted upon. A Ptg of the type that makes sense (ie boolean, number) - * is returned after each method. - */ -public class TextCalculator { - - /** - * ASC function - * For Double-byte character set (DBCS) languages, changes full-width (double-byte) characters to half-width (single-byte) characters. - * ASC(text) - * Text is the text or a reference to a cell that contains the text you want to change. If text does not contain any full-width letters, text is not changed. - * NOTE: in order to use this and other DBCS Methods in Excel, - * the input language must be set to a DBCS language such as Japanese - * Otherwise, the ASC function does nothing (apparently) - */ - protected static Ptg calcAsc(Ptg[] operands) { - if (operands == null || operands[0] == null) - return new PtgErr(PtgErr.ERROR_VALUE); - // determine if Excel's language is set up for DBCS; if not, returns normal string - io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); - if (bk.defaultLanguageIsDBCS()) { // otherwise just returns normal string - byte[] strbytes = getUnicodeBytesFromOp(operands[0]); - if (strbytes == null) - strbytes = (operands[0].getValue()).toString().getBytes(); - try { - return new PtgStr(new String(strbytes, XLSConstants.UNICODEENCODING)); - } catch (Exception e) { - } - } - return new PtgStr(operands[0].getValue().toString()); - } - - /*BAHTTEXT function - Converts a number to Thai text and adds a suffix of "Baht." - */ - - /** - * CHAR - * Returns the character specified by the code number - */ - protected static Ptg calcChar(Ptg[] operands) { - Object o = operands[0].getValue(); - Byte s = new Byte(o.toString()); - if (s.intValue() > 255 || s.intValue() < 1) return PtgCalculator.getError(); - byte[] b = new byte[1]; - b[0] = s.byteValue(); - String str = ""; - try { - str = new String(b, XLSConstants.DEFAULTENCODING); - } catch (UnsupportedEncodingException e) { - } - return new PtgStr(str); - } - - /** - * CLEAN - * Removes all nonprintable characters from text. Use CLEAN on text - * imported from other applications that contains characters that may - * not print with your operating system. For example, you can use - * CLEAN to remove some low-level computer code that is frequently - * at the beginning and end of data files and cannot be printed. - *

                      - * Syntax - *

                      - * CLEAN(text) - *

                      - * Text is any worksheet information from which you want to remove nonprintable characters. - *

                      - * The CLEAN function was designed to remove the first 32 nonprinting characters in the 7-bit ASCII code (values 0 through 31) from text. In the Unicode character set (Unicode: A character encoding standard developed by the Unicode Consortium. By using more than one byte to represent each character, Unicode enables almost all of the written languages in the world to be represented by using a single character set.), there are additional nonprinting characters (values 127, 129, 141, 143, 144, and 157). By itself, the CLEAN function does not remove these additional nonprinting characters. - */ - protected static Ptg calcClean(Ptg[] operands) { - String retString = ""; - try { - Object o = operands[0].getValue(); - String s = o.toString(); - for (int i = 0; i < s.length(); i++) { - int c = s.charAt(i); - if (c >= 32) - retString += (char) c; - } - } catch (Exception e) { - } - return new PtgStr(retString); - } - - - /** - * CODE - * Returns a numeric code for the first character in a text string - */ - protected static Ptg calcCode(Ptg[] operands) { - Object o = operands[0].getValue(); - String s = o.toString(); - byte[] b = null; - try { - b = s.getBytes(XLSConstants.DEFAULTENCODING); - } catch (UnsupportedEncodingException e) { - } - Integer i = Integer.valueOf(b[0]); - return new PtgInt(i.intValue()); - } - - - /** - * CONCATENATE - * Joins several text items into one text item - */ - protected static Ptg calcConcatenate(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - Ptg[] allops = PtgCalculator.getAllComponents(operands); - String s = ""; - for (int i = 0; i < allops.length; i++) { - s += allops[i].getValue().toString(); - } - Ptg str = new PtgStr(s); - str.setParentRec(operands[0].getParentRec()); - return str; - } - - /** - * DOLLAR - * Converts a number to text, using currency format. Can - * have a separate operand to determine POP. - */ - protected static Ptg calcDollar(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - int pop = 0; - if (operands.length > 1) { - pop = operands[1].getIntVal(); - } - double d = operands[0].getDoubleVal(); - d = d * Math.pow(10, pop); - d = Math.round(d); - d = d / Math.pow(10, pop); - String res = "$" + d; - return new PtgStr(res); - } - - /** - * EXACT - * Checks to see if two text values are identical - */ - protected static Ptg calcExact(Ptg[] operands) { - if (operands.length != 2) return PtgCalculator.getError(); - String s1 = operands[0].getValue().toString(); - String s2 = operands[1].getValue().toString(); - if (s1.equals(s2)) return new PtgBool(true); - return new PtgBool(false); - } - - /** - * FIND - * Finds one text value within another (case-sensitive) - */ - protected static Ptg calcFind(Ptg[] operands) { - String instring = ""; - String wholestr = ""; - int start = 0; - if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); - if (operands.length == 3) { - start = operands[2].getIntVal() - 1; - } - Object o = operands[0].getValue(); - Object oo = operands[1].getValue(); - - if (o == null || oo == null) return new PtgErr(PtgErr.ERROR_VALUE); - - instring = o.toString(); - wholestr = oo.toString(); - // note this uses a starting position to search for the string, - // but does not account for that starting position in respects to it's - // result. Pretty strange - int i = wholestr.indexOf(instring, start); - if (i != -1) { - i = wholestr.indexOf(instring); - i++; - return new PtgInt(i); - } - return new PtgErr(PtgErr.ERROR_VALUE); - - } - - /** - * FINDB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, FINDB counts each character as 1. - * NOTES: search is case sensitive and doesn't allow for wildcards - */ - protected static Ptg calcFindB(Ptg[] operands) { - if (operands == null || operands.length < 2 || operands[0] == null) - return new PtgErr(PtgErr.ERROR_VALUE); - // determine if Excel's language is set up for DBCS; if not, returns normal string - io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); - if (!bk.defaultLanguageIsDBCS()) { // otherwise just use calcFind - return calcFind(operands); - } - int startnum = 0; - if (operands.length > 2) - startnum = operands[2].getIntVal(); - byte[] strToFind = getUnicodeBytesFromOp(operands[0]); - byte[] str = getUnicodeBytesFromOp(operands[1]); - int index = -1; - if (strToFind == null || strToFind.length == 0 || str == null || startnum < 0 || str.length < startnum) - return new PtgInt(startnum); - for (int i = startnum; i < str.length && index == -1; i++) { - if (strToFind[0] == str[i]) { - index = i; - for (int j = 0; j < strToFind.length && (i + j) < str.length && index == i; j++) { - if (strToFind[j] != str[i + j]) { - index = -1; // start over - break; - } - } - } - } - if (index == -1)// not found - new PtgErr(PtgErr.ERROR_VALUE); - return new PtgInt(index + 1); // return 1-based index of found bytes - } - - /** - * FIXED - * Formats a number as text with a fixed number of decimals - */ - protected static Ptg calcFixed(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - boolean nocommas = false; - if (operands.length == 3) { - Boolean boo = (Boolean) operands[2].getValue(); - nocommas = boo.booleanValue(); - } - double dub = operands[0].getDoubleVal(); - if (dub == Double.NaN) dub = 0; - int pop = operands[1].getIntVal(); - dub = dub * Math.pow(10, pop); - dub = Math.round(dub); - dub = dub / Math.pow(10, pop); - String res = String.valueOf(dub); - if (pop == 0) { - if (res.indexOf(".") > -1) { - res = res.substring(0, res.indexOf(".")); - return new PtgStr(res); - } - } - // pad w/zeros if need be. - if (res.indexOf(".") == -1 && pop > 0) { - res = res + ".0"; - } - String mantissa = res.substring(res.indexOf(".")); - while (mantissa.length() <= pop) { - res += 0; - mantissa = res.substring(res.indexOf(".")); - } - if (nocommas || dub < 999.99) { - return new PtgStr(res); - } - - int e = res.indexOf("."); - String mant = res.substring(e); - String begin = res.substring(0, e); - int counter = 0; - int s = begin.length(); - // this adds the commas; - for (int v = 0; v < s; ) { - String ch = begin.substring((s - v) - 1, s - v); - mant = ch + mant; - v++; - if (counter == 2 && v != s) mant = "," + mant; - counter++; - if (counter == 3) counter = 0; - } - return new PtgStr(mant); - - } - - /** - * JIS function - * The function described in this Help topic converts half-width (single-byte) - * letters within a character string to full-width (double-byte) characters. - * The name of the function (and the characters that it converts) depends upon your language settings. - * For Japanese, this function changes half-width (single-byte) English letters or - * katakana within a character string to full-width (double-byte) characters. - * JIS(text) - * Text is the text or a reference to a cell that contains the text you want to change. If text does not contain any half-width English letters or katakana, text is not changed. - *

                      - * TODO: STRING ENCODING IS NOT CORRECT ************** - */ - /* - * encoding info: - Shift_JIS DBCS 16-bit Japanese encoding (Note that you must use an underscore character (_), not a hyphen (-) in the name in CFML attributes.) - (same as MS932) - EUC-KR DBCS 16-bit Korean encoding - UCS-2 DBCS Two-byte Unicode encoding - UTF-8 MBCS Multibyte Unicode encoding. ASCII is 7-bit; non-ASCII characters used in European and many Middle Eastern languages are two-byte; and most Asian characters are three-byte -*/ - protected static Ptg calcJIS(Ptg[] operands) { - if (operands == null || operands[0] == null) - return new PtgErr(PtgErr.ERROR_VALUE); - // determine if Excel's language is set up for DBCS; if not, returns normal string - io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); - if (bk.defaultLanguageIsDBCS()) { // otherwise just returns normal string - byte[] strbytes = getUnicodeBytesFromOp(operands[0]); - if (strbytes == null) - strbytes = (operands[0].getValue()).toString().getBytes(); - try { - return new PtgStr(new String(strbytes, "Shift_JIS")); - } catch (Exception e) { - } - } - return new PtgStr(operands[0].getValue().toString()); - } - - /** - * LEFT - * Returns the leftmost characters from a text value - */ - protected static Ptg calcLeft(Ptg[] operands) { - int numchars = 1; - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - if (operands[0] instanceof PtgErr) - return new PtgErr(PtgErr.ERROR_NA); // 'tis what excel does - if (operands.length == 2) { - if (operands[1] instanceof PtgErr) - return new PtgErr(PtgErr.ERROR_VALUE); - numchars = operands[1].getIntVal(); - } - Object o = operands[0].getValue(); - if (o == null) return new PtgStr(""); - String str = String.valueOf(o); - if (str == null || numchars > str.length()) - return new PtgStr(""); // 20081202 KSC: Don't error out if not enough chars ala Excel - String res = str.substring(0, numchars); - return new PtgStr(res); - } - - /** - * LEFTB counts each double-byte character as 2 when you have enabled the editing of a - * language that supports DBCS and then set it as the default language. - * Otherwise, LEFTB counts each character as 1. - */ - protected static Ptg calcLeftB(Ptg[] operands) { - io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); - if (bk.defaultLanguageIsDBCS()) {// otherwise just returns normal string - int numchars = 1; - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - if (operands.length == 2) { - if (operands[1] instanceof PtgErr) - return new PtgErr(PtgErr.ERROR_VALUE); - try { - numchars = operands[1].getIntVal(); - byte[] b = new byte[numchars]; - System.arraycopy(getUnicodeBytesFromOp(operands[0]), 0, b, 0, numchars); - return new PtgStr(new String(b, XLSConstants.UNICODEENCODING)); - } catch (Exception e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - } - return calcLeft(operands); - } - - /** - * LEN - * Returns the number of characters in a text string - */ - protected static Ptg calcLen(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getError(); - String s = String.valueOf(operands[0].getValue()); - return new PtgInt(s.length()); - } - - /** - * LENB counts each double-byte character as 2 when you have enabled the editing of - * a language that supports DBCS and then set it as the default language. - * Otherwise, LENB counts each character as 1. - */ - protected static Ptg calcLenB(Ptg[] operands) { - if (operands.length != 1) return PtgCalculator.getError(); - io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); - if (bk.defaultLanguageIsDBCS()) // otherwise just returns normal string - return new PtgInt(getUnicodeBytesFromOp(operands[0]).length); - String s = String.valueOf(operands[0].getValue()); - return new PtgInt(s.length()); - } - - /** - * LOWER - * Converts text to lowercase - */ - protected static Ptg calcLower(Ptg[] operands) { - if (operands.length > 1) return PtgCalculator.getError(); - String s = String.valueOf(operands[0].getValue()); - s = s.toLowerCase(); - return new PtgStr(s); - } - - /** - * MID - * Returns a specific number of characters from a text string starting at the position you specify - */ - protected static Ptg calcMid(Ptg[] operands) { - String s = String.valueOf(operands[0].getValue()); - if (s == null || s.equals("")) return new PtgStr(""); // Don't error out if "" ala Excel - if (operands[1] instanceof PtgErr || operands[2] instanceof PtgErr) return new PtgErr(PtgErr.ERROR_VALUE); - int start = operands[1].getIntVal() - 1; - int len = operands[2].getIntVal(); - if (len < 0) { - len = start + len; - } - if (s.length() < start) return new PtgStr(""); - if (start == -1) return new PtgErr(PtgErr.ERROR_VALUE); - s = s.substring(start); - if (len > s.length()) return new PtgStr(s); - s = s.substring(0, len); - return new PtgStr(s); - } - /* - * MIDB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, MIDB counts each character as 1. - */ - /* - * PHONETIC function - Extracts the phonetic (furigana) characters from a text string. - PHONETIC(reference) - Reference is a text string or a reference to a single cell or a range of cells that contain a furigana text string. - */ - - /** - * PROPER - * Capitalizes the first letter in each word of a text value - */ - protected static Ptg calcProper(Ptg[] operands) { - String s = String.valueOf(operands[0].getValue()); - s = StringTool.proper(s); - return new PtgStr(s); - } - - /** - * REPLACE - * Replaces characters within text - */ - protected static Ptg calcReplace(Ptg[] operands) { - String origstr = String.valueOf(operands[0].getValue()); - int start = operands[1].getIntVal(); - int repamount = operands[2].getIntVal(); - String repstr = String.valueOf(operands[3].getValue()); - String begin = origstr.substring(0, (start - 1)); - String end = origstr.substring(start + repamount - 1); - String returnstr = begin + repstr + end; - return new PtgStr(returnstr); - - } - /* - * REPLACEB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, REPLACEB counts each character as 1. - */ - - /** - * REPT - * Repeats text a given number of times - */ - protected static Ptg calcRept(Ptg[] operands) { - String origstr = String.valueOf(operands[0].getValue()); - int numtimes = operands[1].getIntVal(); - String retstr = ""; - for (int i = 0; i < numtimes; i++) { - retstr += origstr; - } - return new PtgStr(retstr); - } - - /** - * RIGHT - * Returns the rightmost characters from a text value - */ - protected static Ptg calcRight(Ptg[] operands) { - if (operands.length < 1) return new PtgErr(PtgErr.ERROR_VALUE); - String origstr = String.valueOf(operands[0].getValue()); - if (origstr.equals("")) return new PtgStr(""); - int numchars = operands[1].getIntVal(); - if (numchars > origstr.length()) numchars = origstr.length(); - if (numchars < 0) return new PtgErr(PtgErr.ERROR_VALUE); - String res = origstr.substring(origstr.length() - numchars); - return new PtgStr(res); - } - /* - * RIGHTB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, RIGHTB counts each character as 1. - */ - - /** - * SEARCH - * Finds one text value within another (not case-sensitive) - */ - protected static Ptg calcSearch(Ptg[] operands) { - if (operands.length < 2) return new PtgErr(PtgErr.ERROR_VALUE); - int start = 0; - if (operands.length == 3) { - start = operands[2].getIntVal() - 1; - } - String search = operands[0].getValue().toString().toLowerCase(); - String orig = operands[1].getValue().toString().toLowerCase(); - String tmp = orig.substring(start).toLowerCase(); - int i = tmp.indexOf(search); - if (i == -1) return new PtgErr(PtgErr.ERROR_VALUE); - i = orig.indexOf(search); - i++; - return new PtgInt(i); - } - - /** - * SEARCHB counts each double-byte character as 2 when you have enabled the editing of a - * language that supports DBCS and then set it as the default language. - * Otherwise, SEARCHB counts each character as 1. - *

                      - * TODO: THIS IS NOT COMPLETE - */ - protected static Ptg calcSearchB(Ptg[] operands) { - if (operands == null || operands.length < 2 || operands[0] == null) - return new PtgErr(PtgErr.ERROR_VALUE); - // determine if Excel's language is set up for DBCS; if not, returns normal string - io.starter.formats.XLS.WorkBook bk = operands[0].getParentRec().getWorkBook(); - if (!bk.defaultLanguageIsDBCS()) { // otherwise just use calcFind - return calcSearch(operands); - } - int startnum = 0; - if (operands.length > 2) - startnum = operands[2].getIntVal(); - byte[] strToFind = getUnicodeBytesFromOp(operands[0]); - byte[] str = getUnicodeBytesFromOp(operands[1]); - int index = -1; - if (strToFind == null || strToFind.length == 0 || str == null || startnum < 0 || str.length < startnum) - return new PtgInt(startnum); - - String search = operands[0].getValue().toString().toLowerCase(); - String orig = operands[1].getValue().toString().toLowerCase(); - String tmp = orig.substring(startnum).toLowerCase(); - index = tmp.indexOf(search); - - if (index == -1)// not found - new PtgErr(PtgErr.ERROR_VALUE); - else - index *= 2; // count the bytes as double - return new PtgInt(index + 1); // return 1-based index of found bytes - } - - /** - * SUBSTITUTE - * Substitutes new text for old text in a text string - */ - protected static Ptg calcSubstitute(Ptg[] operands) { - int whichreplace = 0; - if (operands.length < 3) return new PtgErr(PtgErr.ERROR_VALUE); - if (operands.length == 4) - whichreplace = operands[3].getIntVal() - 1; - String origstr = operands[0].getValue().toString(); - String srchstr = operands[1].getValue().toString(); - String repstr = operands[2].getValue().toString(); - String finalstr = StringTool.replaceText(origstr, srchstr, repstr, whichreplace, true); - return new PtgStr(finalstr); - } - - /** - * T - * According to documentation converts its arguments to text - - *

                      - * not really though, it just returns value if they are text - */ - protected static Ptg calcT(Ptg[] operands) { - String res = ""; - try { - res = (String) operands[0].getValue(); - } catch (ClassCastException e) { - } - return new PtgStr(res); - - } - - /** - * TEXT - * Formats a number and converts it to text - *

                      - * Converts a value to text in a specific number format. - *

                      - * Syntax - *

                      - * TEXT(value,format_text) - *

                      - * Value is a numeric value, a formula that evaluates to a numeric value, or a reference to a cell containing a numeric value. - *

                      - * Format_text is a number format in text form from in the Category box on the Number tab in the Format Cells dialog box. - *

                      - * Remarks - *

                      - * Format_text cannot contain an asterisk (*). - *

                      - * Formatting a cell with an option on the Number tab (Cells command, Format menu) changes only the format, not the value. Using the TEXT function converts a value to formatted text, and the result is no longer calculated as a number. - *

                      - * Salesperson Sales - * Buchanan 2800 - * Dodsworth 40% - *

                      - * Formula Description (Result) - * =A2&" sold "&TEXT(B2, "$0.00")&" worth of units." Combines contents above into a phrase (Buchanan sold $2800.00 worth of units.) - * =A3&" sold "&TEXT(B3,"0%")&" of the total sales." Combines contents above into a phrase (Dodsworth sold 40% of the total sales.) - */ - protected static Ptg calcText(Ptg[] operands) { - if (operands.length != 2) - return new PtgErr(PtgErr.ERROR_VALUE); - String res = "#ERR!"; - try { - res = String.valueOf(operands[0].getValue()); - } catch (Exception e) { - res = operands[0].toString(); - } - - String fmt = operands[1].toString(); - Format fmtx = null; - // convert a string like "0" - // to a format pattern like: "##"; - for (int t = 0; t < FormatConstants.NUMERIC_FORMATS.length; t++) { - String fmx = FormatConstants.NUMERIC_FORMATS[t][0]; - if (fmx.equals(fmt)) { - fmt = FormatConstants.NUMERIC_FORMATS[t][2]; - fmtx = new DecimalFormat(fmt); - } - } - if (fmtx == null) { - for (int t = 0; t < FormatConstants.CURRENCY_FORMATS.length; t++) { - String fmx = FormatConstants.CURRENCY_FORMATS[t][0]; - if (fmx.equals(fmt)) { - fmt = FormatConstants.CURRENCY_FORMATS[t][2]; - fmtx = new DecimalFormat(fmt); - } - } - } - if (fmtx != null) { - try { - if (res != null && !res.equals("")) // 20090527 KSC: when cell=="", Excel treats as 0 - return new PtgStr(fmtx.format(new Float(res))); - else if (res != null) - return new PtgStr(fmtx.format(0)); - } catch (Exception e) { -// Logger.logWarn("getting formatted string value for :" + res.toString() + " failed: " + e.toString()) ; - try { - return new PtgStr(res); // 20080211 KSC: Double.valueOf(ret.toString()); - } catch (NumberFormatException nbe) { - // who knew? - of course, functions don't have to return numbers! - } - } - } - for (int x = 0; x < FormatConstants.DATE_FORMATS.length; x++) { - String fmx = FormatConstants.DATE_FORMATS[x][0]; - if (fmx.equals(fmt)) { - fmt = FormatConstants.DATE_FORMATS[x][2]; - - try { - Date d; - try { - d = DateConverter.getDateFromNumber(new Double(res)); - } catch (NumberFormatException e) { - d = DateConverter.getDate(res); // try to convert a string date - if (d == null) - // what excel does, if it's an empty date, it reverts to jan 0, 1900 - d = new Date("1/1/1990"); - } - //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); - try { - //sdf = new SimpleDateFormat(fmt); - WorkBookHandle.simpledateformat.applyPattern(fmt); - } catch (Exception ex) { - Logger.logWarn("Simple Date Format could not parse: " + fmt + ". Returning default."); //not a valid date format - } - //return new PtgStr(sdf.format(d)); - return new PtgStr(WorkBookHandle.simpledateformat.format(d)); - } catch (Exception e) { - Logger.logErr("Unable to calcText formatting correctly for a date" + e); - } - } - } - - // we've been unable to format, try based on the string - try { - if (Xf.isDatePattern(fmt)) { - //fmtx = new SimpleDateFormat( fmt ); - WorkBookHandle.simpledateformat.applyPattern(fmt); - fmtx = WorkBookHandle.simpledateformat; - } else fmtx = new DecimalFormat(fmt); - - if (res != null && !res.equals("")) - return new PtgStr(fmtx.format(new Float(res))); - else if (res != null) - return new PtgStr(fmtx.format(0)); - } catch (Exception e) { - //Logger.logWarn("getting formatted string value for :" + res.toString() + " failed: " + e.toString()) ; - try { - return new PtgStr(res); - } catch (NumberFormatException nbe) { - } - } - return new PtgStr(res); - } - - /** - * TRIM - * According to documentation Trim() removes leading and trailing spaces from the cell value. - *

                      - * Actually it removes all spaces except for single spaces - * between words. - */ - protected static Ptg calcTrim(Ptg[] operands) { - Object o = operands[0].getValue(); - String res; - if (o instanceof Double) { - res = ExcelTools.getNumberAsString(((Double) o).doubleValue()); - } else { - res = String.valueOf(o); - } - if (res == null || res.equals(new PtgErr(PtgErr.ERROR_NA).toString())) - return new PtgErr(PtgErr.ERROR_NA); - // first let's remove the beginning and trailing spaces. - if (res.length() > 0) { - while (res.substring(0, 1).equals(" ")) { - res = res.substring(1); - } - while (res.substring(res.length() - 1).equals(" ")) { - res = res.substring(0, res.length() - 1); - } - // now we need to remove double spaces - while (res.indexOf(" ") != -1) { - int i = res.indexOf(" "); - String prestring = res.substring(0, i); - String poststring = res.substring(i + 1); - res = prestring + poststring; - } - } - return new PtgStr(res); - - } - - /** - * UPPER - * Converts text to uppercase - */ - protected static Ptg calcUpper(Ptg[] operands) { - if (operands.length > 1) return PtgCalculator.getError(); - String s = String.valueOf(operands[0].getValue()); - s = s.toUpperCase(); - return new PtgStr(s); - } - - /** - * VALUE - * Converts a text argument to a number - */ - protected static Ptg calcValue(Ptg[] operands) { - try { - String s = String.valueOf(operands[0].getValue()); - if (s.equals("")) s = "0"; // Excel returns a zero for a blank value if VALUE is called upon it. - Double d = new Double(s); - return new PtgNumber(d.doubleValue()); - } catch (NumberFormatException e) { - return new PtgErr(PtgErr.ERROR_VALUE); - } - } - - /** - * helper method for all DBCS-related worksheet functions - * - * @param op - * @return - */ - private static byte[] getUnicodeBytesFromOp(Ptg op) { - byte[] strbytes = null; - if (op instanceof PtgRef) { - io.starter.formats.XLS.BiffRec rec = ((PtgRef) op).getRefCells()[0]; - if (rec instanceof io.starter.formats.XLS.Labelsst) - strbytes = ((io.starter.formats.XLS.Labelsst) rec).getUnsharedString().readStr(); - else if (rec instanceof Formula) { - strbytes = op.getValue().toString().getBytes(); - } else // DEBUGGING- Take out when done - Logger.logWarn("getUnicodeBytes: Unexpected rec encountered: " + op.getClass()); - } else if (op instanceof PtgStr) { - strbytes = new byte[((PtgStr) op).record.length - 3]; - System.arraycopy(((PtgStr) op).record, 3, strbytes, 0, strbytes.length); - } else { - // DEBUGGING- Take out when done - Logger.logWarn("getUnicodeBytes: Unexpected operand encountered: " + op.getClass()); - } - return strbytes; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/formulas/TextCalculator.kt b/src/main/java/io/starter/formats/XLS/formulas/TextCalculator.kt new file mode 100644 index 0000000..080c37e --- /dev/null +++ b/src/main/java/io/starter/formats/XLS/formulas/TextCalculator.kt @@ -0,0 +1,873 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.XLS.formulas + +import io.starter.OpenXLS.DateConverter +import io.starter.OpenXLS.ExcelTools +import io.starter.OpenXLS.WorkBookHandle +import io.starter.formats.XLS.FormatConstants +import io.starter.formats.XLS.Formula +import io.starter.formats.XLS.XLSConstants +import io.starter.formats.XLS.Xf +import io.starter.toolkit.Logger +import io.starter.toolkit.StringTool + +import java.io.UnsupportedEncodingException +import java.text.DecimalFormat +import java.text.Format +import java.util.Date + +/** + * TextCalculator is a collection of static methods that operate + * as the Microsoft Excel function calls do. + * + * + * All methods are called with an array of ptg's, which are then + * acted upon. A Ptg of the type that makes sense (ie boolean, number) + * is returned after each method. + */ +object TextCalculator { + + /** + * ASC function + * For Double-byte character set (DBCS) languages, changes full-width (double-byte) characters to half-width (single-byte) characters. + * ASC(text) + * Text is the text or a reference to a cell that contains the text you want to change. If text does not contain any full-width letters, text is not changed. + * NOTE: in order to use this and other DBCS Methods in Excel, + * the input language must be set to a DBCS language such as Japanese + * Otherwise, the ASC function does nothing (apparently) + */ + internal fun calcAsc(operands: Array?): Ptg { + if (operands == null || operands[0] == null) + return PtgErr(PtgErr.ERROR_VALUE) + // determine if Excel's language is set up for DBCS; if not, returns normal string + val bk = operands[0].parentRec.workBook + if (bk!!.defaultLanguageIsDBCS()) { // otherwise just returns normal string + var strbytes = getUnicodeBytesFromOp(operands[0]) + if (strbytes == null) + strbytes = operands[0].value.toString().toByteArray() + try { + return PtgStr(String(strbytes!!, XLSConstants.UNICODEENCODING)) + } catch (e: Exception) { + } + + } + return PtgStr(operands[0].value.toString()) + } + + /*BAHTTEXT function + Converts a number to Thai text and adds a suffix of "Baht." + */ + + /** + * CHAR + * Returns the character specified by the code number + */ + internal fun calcChar(operands: Array): Ptg { + val o = operands[0].value + val s = Byte(o.toString()) + if (s.toInt() > 255 || s.toInt() < 1) return PtgCalculator.error + val b = ByteArray(1) + b[0] = s + var str = "" + try { + str = String(b, XLSConstants.DEFAULTENCODING) + } catch (e: UnsupportedEncodingException) { + } + + return PtgStr(str) + } + + /** + * CLEAN + * Removes all nonprintable characters from text. Use CLEAN on text + * imported from other applications that contains characters that may + * not print with your operating system. For example, you can use + * CLEAN to remove some low-level computer code that is frequently + * at the beginning and end of data files and cannot be printed. + * + * + * Syntax + * + * + * CLEAN(text) + * + * + * Text is any worksheet information from which you want to remove nonprintable characters. + * + * + * The CLEAN function was designed to remove the first 32 nonprinting characters in the 7-bit ASCII code (values 0 through 31) from text. In the Unicode character set (Unicode: A character encoding standard developed by the Unicode Consortium. By using more than one byte to represent each character, Unicode enables almost all of the written languages in the world to be represented by using a single character set.), there are additional nonprinting characters (values 127, 129, 141, 143, 144, and 157). By itself, the CLEAN function does not remove these additional nonprinting characters. + */ + internal fun calcClean(operands: Array): Ptg { + var retString = "" + try { + val o = operands[0].value + val s = o.toString() + for (i in 0 until s.length) { + val c = s[i].toInt() + if (c >= 32) + retString += c.toChar() + } + } catch (e: Exception) { + } + + return PtgStr(retString) + } + + + /** + * CODE + * Returns a numeric code for the first character in a text string + */ + internal fun calcCode(operands: Array): Ptg { + val o = operands[0].value + val s = o.toString() + var b: ByteArray? = null + try { + b = s.toByteArray(charset(XLSConstants.DEFAULTENCODING)) + } catch (e: UnsupportedEncodingException) { + } + + val i = Integer.valueOf(b!![0].toInt()) + return PtgInt(i!!.toInt()) + } + + + /** + * CONCATENATE + * Joins several text items into one text item + */ + internal fun calcConcatenate(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + val allops = PtgCalculator.getAllComponents(operands) + var s = "" + for (i in allops.indices) { + s += allops[i].value.toString() + } + val str = PtgStr(s) + str.parentRec = operands[0].parentRec + return str + } + + /** + * DOLLAR + * Converts a number to text, using currency format. Can + * have a separate operand to determine POP. + */ + internal fun calcDollar(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + var pop = 0 + if (operands.size > 1) { + pop = operands[1].intVal + } + var d = operands[0].doubleVal + d = d * Math.pow(10.0, pop.toDouble()) + d = Math.round(d).toDouble() + d = d / Math.pow(10.0, pop.toDouble()) + val res = "$$d" + return PtgStr(res) + } + + /** + * EXACT + * Checks to see if two text values are identical + */ + internal fun calcExact(operands: Array): Ptg { + if (operands.size != 2) return PtgCalculator.error + val s1 = operands[0].value.toString() + val s2 = operands[1].value.toString() + return if (s1 == s2) PtgBool(true) else PtgBool(false) + } + + /** + * FIND + * Finds one text value within another (case-sensitive) + */ + internal fun calcFind(operands: Array): Ptg { + var instring = "" + var wholestr = "" + var start = 0 + if (operands.size < 2) return PtgErr(PtgErr.ERROR_VALUE) + if (operands.size == 3) { + start = operands[2].intVal - 1 + } + val o = operands[0].value + val oo = operands[1].value + + if (o == null || oo == null) return PtgErr(PtgErr.ERROR_VALUE) + + instring = o.toString() + wholestr = oo.toString() + // note this uses a starting position to search for the string, + // but does not account for that starting position in respects to it's + // result. Pretty strange + var i = wholestr.indexOf(instring, start) + if (i != -1) { + i = wholestr.indexOf(instring) + i++ + return PtgInt(i) + } + return PtgErr(PtgErr.ERROR_VALUE) + + } + + /** + * FINDB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, FINDB counts each character as 1. + * NOTES: search is case sensitive and doesn't allow for wildcards + */ + internal fun calcFindB(operands: Array?): Ptg { + if (operands == null || operands.size < 2 || operands[0] == null) + return PtgErr(PtgErr.ERROR_VALUE) + // determine if Excel's language is set up for DBCS; if not, returns normal string + val bk = operands[0].parentRec.workBook + if (!bk!!.defaultLanguageIsDBCS()) { // otherwise just use calcFind + return calcFind(operands) + } + var startnum = 0 + if (operands.size > 2) + startnum = operands[2].intVal + val strToFind = getUnicodeBytesFromOp(operands[0]) + val str = getUnicodeBytesFromOp(operands[1]) + var index = -1 + if (strToFind == null || strToFind.size == 0 || str == null || startnum < 0 || str.size < startnum) + return PtgInt(startnum) + var i = startnum + while (i < str.size && index == -1) { + if (strToFind[0] == str[i]) { + index = i + var j = 0 + while (j < strToFind.size && i + j < str.size && index == i) { + if (strToFind[j] != str[i + j]) { + index = -1 // start over + break + } + j++ + } + } + i++ + } + if (index == -1) + // not found + PtgErr(PtgErr.ERROR_VALUE) + return PtgInt(index + 1) // return 1-based index of found bytes + } + + /** + * FIXED + * Formats a number as text with a fixed number of decimals + */ + internal fun calcFixed(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + var nocommas = false + if (operands.size == 3) { + val boo = operands[2].value as Boolean + nocommas = boo.booleanValue() + } + var dub = operands[0].doubleVal + if (dub == java.lang.Double.NaN) dub = 0.0 + val pop = operands[1].intVal + dub = dub * Math.pow(10.0, pop.toDouble()) + dub = Math.round(dub).toDouble() + dub = dub / Math.pow(10.0, pop.toDouble()) + var res = dub.toString() + if (pop == 0) { + if (res.indexOf(".") > -1) { + res = res.substring(0, res.indexOf(".")) + return PtgStr(res) + } + } + // pad w/zeros if need be. + if (res.indexOf(".") == -1 && pop > 0) { + res = "$res.0" + } + var mantissa = res.substring(res.indexOf(".")) + while (mantissa.length <= pop) { + res += 0 + mantissa = res.substring(res.indexOf(".")) + } + if (nocommas || dub < 999.99) { + return PtgStr(res) + } + + val e = res.indexOf(".") + var mant = res.substring(e) + val begin = res.substring(0, e) + var counter = 0 + val s = begin.length + // this adds the commas; + var v = 0 + while (v < s) { + val ch = begin.substring(s - v - 1, s - v) + mant = ch + mant + v++ + if (counter == 2 && v != s) mant = ",$mant" + counter++ + if (counter == 3) counter = 0 + } + return PtgStr(mant) + + } + + /** + * JIS function + * The function described in this Help topic converts half-width (single-byte) + * letters within a character string to full-width (double-byte) characters. + * The name of the function (and the characters that it converts) depends upon your language settings. + * For Japanese, this function changes half-width (single-byte) English letters or + * katakana within a character string to full-width (double-byte) characters. + * JIS(text) + * Text is the text or a reference to a cell that contains the text you want to change. If text does not contain any half-width English letters or katakana, text is not changed. + * + * + * TODO: STRING ENCODING IS NOT CORRECT ************** + */ + /* + * encoding info: + Shift_JIS DBCS 16-bit Japanese encoding (Note that you must use an underscore character (_), not a hyphen (-) in the name in CFML attributes.) + (same as MS932) + EUC-KR DBCS 16-bit Korean encoding + UCS-2 DBCS Two-byte Unicode encoding + UTF-8 MBCS Multibyte Unicode encoding. ASCII is 7-bit; non-ASCII characters used in European and many Middle Eastern languages are two-byte; and most Asian characters are three-byte +*/ + internal fun calcJIS(operands: Array?): Ptg { + if (operands == null || operands[0] == null) + return PtgErr(PtgErr.ERROR_VALUE) + // determine if Excel's language is set up for DBCS; if not, returns normal string + val bk = operands[0].parentRec.workBook + if (bk!!.defaultLanguageIsDBCS()) { // otherwise just returns normal string + var strbytes = getUnicodeBytesFromOp(operands[0]) + if (strbytes == null) + strbytes = operands[0].value.toString().toByteArray() + try { + return PtgStr(String(strbytes!!, "Shift_JIS")) + } catch (e: Exception) { + } + + } + return PtgStr(operands[0].value.toString()) + } + + /** + * LEFT + * Returns the leftmost characters from a text value + */ + internal fun calcLeft(operands: Array): Ptg { + var numchars = 1 + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + if (operands[0] is PtgErr) + return PtgErr(PtgErr.ERROR_NA) // 'tis what excel does + if (operands.size == 2) { + if (operands[1] is PtgErr) + return PtgErr(PtgErr.ERROR_VALUE) + numchars = operands[1].intVal + } + val o = operands[0].value ?: return PtgStr("") + val str = o.toString() + if (str == null || numchars > str.length) + return PtgStr("") // 20081202 KSC: Don't error out if not enough chars ala Excel + val res = str.substring(0, numchars) + return PtgStr(res) + } + + /** + * LEFTB counts each double-byte character as 2 when you have enabled the editing of a + * language that supports DBCS and then set it as the default language. + * Otherwise, LEFTB counts each character as 1. + */ + internal fun calcLeftB(operands: Array): Ptg { + val bk = operands[0].parentRec.workBook + if (bk!!.defaultLanguageIsDBCS()) {// otherwise just returns normal string + var numchars = 1 + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + if (operands.size == 2) { + if (operands[1] is PtgErr) + return PtgErr(PtgErr.ERROR_VALUE) + try { + numchars = operands[1].intVal + val b = ByteArray(numchars) + System.arraycopy(getUnicodeBytesFromOp(operands[0])!!, 0, b, 0, numchars) + return PtgStr(String(b, XLSConstants.UNICODEENCODING)) + } catch (e: Exception) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + } + return calcLeft(operands) + } + + /** + * LEN + * Returns the number of characters in a text string + */ + internal fun calcLen(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.error + val s = operands[0].value.toString() + return PtgInt(s.length) + } + + /** + * LENB counts each double-byte character as 2 when you have enabled the editing of + * a language that supports DBCS and then set it as the default language. + * Otherwise, LENB counts each character as 1. + */ + internal fun calcLenB(operands: Array): Ptg { + if (operands.size != 1) return PtgCalculator.error + val bk = operands[0].parentRec.workBook + if (bk!!.defaultLanguageIsDBCS()) + // otherwise just returns normal string + return PtgInt(getUnicodeBytesFromOp(operands[0])!!.size) + val s = operands[0].value.toString() + return PtgInt(s.length) + } + + /** + * LOWER + * Converts text to lowercase + */ + internal fun calcLower(operands: Array): Ptg { + if (operands.size > 1) return PtgCalculator.error + var s = operands[0].value.toString() + s = s.toLowerCase() + return PtgStr(s) + } + + /** + * MID + * Returns a specific number of characters from a text string starting at the position you specify + */ + internal fun calcMid(operands: Array): Ptg { + var s: String? = operands[0].value.toString() + if (s == null || s == "") return PtgStr("") // Don't error out if "" ala Excel + if (operands[1] is PtgErr || operands[2] is PtgErr) return PtgErr(PtgErr.ERROR_VALUE) + val start = operands[1].intVal - 1 + var len = operands[2].intVal + if (len < 0) { + len = start + len + } + if (s.length < start) return PtgStr("") + if (start == -1) return PtgErr(PtgErr.ERROR_VALUE) + s = s.substring(start) + if (len > s.length) return PtgStr(s) + s = s.substring(0, len) + return PtgStr(s) + } + /* + * MIDB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, MIDB counts each character as 1. + */ + /* + * PHONETIC function + Extracts the phonetic (furigana) characters from a text string. + PHONETIC(reference) + Reference is a text string or a reference to a single cell or a range of cells that contain a furigana text string. + */ + + /** + * PROPER + * Capitalizes the first letter in each word of a text value + */ + internal fun calcProper(operands: Array): Ptg { + var s = operands[0].value.toString() + s = StringTool.proper(s) + return PtgStr(s) + } + + /** + * REPLACE + * Replaces characters within text + */ + internal fun calcReplace(operands: Array): Ptg { + val origstr = operands[0].value.toString() + val start = operands[1].intVal + val repamount = operands[2].intVal + val repstr = operands[3].value.toString() + val begin = origstr.substring(0, start - 1) + val end = origstr.substring(start + repamount - 1) + val returnstr = begin + repstr + end + return PtgStr(returnstr) + + } + /* + * REPLACEB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, REPLACEB counts each character as 1. + */ + + /** + * REPT + * Repeats text a given number of times + */ + internal fun calcRept(operands: Array): Ptg { + val origstr = operands[0].value.toString() + val numtimes = operands[1].intVal + var retstr = "" + for (i in 0 until numtimes) { + retstr += origstr + } + return PtgStr(retstr) + } + + /** + * RIGHT + * Returns the rightmost characters from a text value + */ + internal fun calcRight(operands: Array): Ptg { + if (operands.size < 1) return PtgErr(PtgErr.ERROR_VALUE) + val origstr = operands[0].value.toString() + if (origstr == "") return PtgStr("") + var numchars = operands[1].intVal + if (numchars > origstr.length) numchars = origstr.length + if (numchars < 0) return PtgErr(PtgErr.ERROR_VALUE) + val res = origstr.substring(origstr.length - numchars) + return PtgStr(res) + } + /* + * RIGHTB counts each double-byte character as 2 when you have enabled the editing of a language that supports DBCS and then set it as the default language. Otherwise, RIGHTB counts each character as 1. + */ + + /** + * SEARCH + * Finds one text value within another (not case-sensitive) + */ + internal fun calcSearch(operands: Array): Ptg { + if (operands.size < 2) return PtgErr(PtgErr.ERROR_VALUE) + var start = 0 + if (operands.size == 3) { + start = operands[2].intVal - 1 + } + val search = operands[0].value.toString().toLowerCase() + val orig = operands[1].value.toString().toLowerCase() + val tmp = orig.substring(start).toLowerCase() + var i = tmp.indexOf(search) + if (i == -1) return PtgErr(PtgErr.ERROR_VALUE) + i = orig.indexOf(search) + i++ + return PtgInt(i) + } + + /** + * SEARCHB counts each double-byte character as 2 when you have enabled the editing of a + * language that supports DBCS and then set it as the default language. + * Otherwise, SEARCHB counts each character as 1. + * + * + * TODO: THIS IS NOT COMPLETE + */ + internal fun calcSearchB(operands: Array?): Ptg { + if (operands == null || operands.size < 2 || operands[0] == null) + return PtgErr(PtgErr.ERROR_VALUE) + // determine if Excel's language is set up for DBCS; if not, returns normal string + val bk = operands[0].parentRec.workBook + if (!bk!!.defaultLanguageIsDBCS()) { // otherwise just use calcFind + return calcSearch(operands) + } + var startnum = 0 + if (operands.size > 2) + startnum = operands[2].intVal + val strToFind = getUnicodeBytesFromOp(operands[0]) + val str = getUnicodeBytesFromOp(operands[1]) + var index = -1 + if (strToFind == null || strToFind.size == 0 || str == null || startnum < 0 || str.size < startnum) + return PtgInt(startnum) + + val search = operands[0].value.toString().toLowerCase() + val orig = operands[1].value.toString().toLowerCase() + val tmp = orig.substring(startnum).toLowerCase() + index = tmp.indexOf(search) + + if (index == -1) + // not found + PtgErr(PtgErr.ERROR_VALUE) + else + index *= 2 // count the bytes as double + return PtgInt(index + 1) // return 1-based index of found bytes + } + + /** + * SUBSTITUTE + * Substitutes new text for old text in a text string + */ + internal fun calcSubstitute(operands: Array): Ptg { + var whichreplace = 0 + if (operands.size < 3) return PtgErr(PtgErr.ERROR_VALUE) + if (operands.size == 4) + whichreplace = operands[3].intVal - 1 + val origstr = operands[0].value.toString() + val srchstr = operands[1].value.toString() + val repstr = operands[2].value.toString() + val finalstr = StringTool.replaceText(origstr, srchstr, repstr, whichreplace, true) + return PtgStr(finalstr) + } + + /** + * T + * According to documentation converts its arguments to text - + * + * + * not really though, it just returns value if they are text + */ + internal fun calcT(operands: Array): Ptg { + var res = "" + try { + res = operands[0].value as String + } catch (e: ClassCastException) { + } + + return PtgStr(res) + + } + + /** + * TEXT + * Formats a number and converts it to text + * + * + * Converts a value to text in a specific number format. + * + * + * Syntax + * + * + * TEXT(value,format_text) + * + * + * Value is a numeric value, a formula that evaluates to a numeric value, or a reference to a cell containing a numeric value. + * + * + * Format_text is a number format in text form from in the Category box on the Number tab in the Format Cells dialog box. + * + * + * Remarks + * + * + * Format_text cannot contain an asterisk (*). + * + * + * Formatting a cell with an option on the Number tab (Cells command, Format menu) changes only the format, not the value. Using the TEXT function converts a value to formatted text, and the result is no longer calculated as a number. + * + * + * Salesperson Sales + * Buchanan 2800 + * Dodsworth 40% + * + * + * Formula Description (Result) + * =A2&" sold "&TEXT(B2, "$0.00")&" worth of units." Combines contents above into a phrase (Buchanan sold $2800.00 worth of units.) + * =A3&" sold "&TEXT(B3,"0%")&" of the total sales." Combines contents above into a phrase (Dodsworth sold 40% of the total sales.) + */ + internal fun calcText(operands: Array): Ptg { + if (operands.size != 2) + return PtgErr(PtgErr.ERROR_VALUE) + var res: String? = "#ERR!" + try { + res = operands[0].value.toString() + } catch (e: Exception) { + res = operands[0].toString() + } + + var fmt = operands[1].toString() + var fmtx: Format? = null + // convert a string like "0" + // to a format pattern like: "##"; + for (t in FormatConstants.NUMERIC_FORMATS.indices) { + val fmx = FormatConstants.NUMERIC_FORMATS[t][0] + if (fmx == fmt) { + fmt = FormatConstants.NUMERIC_FORMATS[t][2] + fmtx = DecimalFormat(fmt) + } + } + if (fmtx == null) { + for (t in FormatConstants.CURRENCY_FORMATS.indices) { + val fmx = FormatConstants.CURRENCY_FORMATS[t][0] + if (fmx == fmt) { + fmt = FormatConstants.CURRENCY_FORMATS[t][2] + fmtx = DecimalFormat(fmt) + } + } + } + if (fmtx != null) { + try { + if (res != null && res != "") + // 20090527 KSC: when cell=="", Excel treats as 0 + return PtgStr(fmtx.format(Float(res))) + else if (res != null) + return PtgStr(fmtx.format(0)) + } catch (e: Exception) { + // Logger.logWarn("getting formatted string value for :" + res.toString() + " failed: " + e.toString()) ; + try { + return PtgStr(res) // 20080211 KSC: Double.valueOf(ret.toString()); + } catch (nbe: NumberFormatException) { + // who knew? - of course, functions don't have to return numbers! + } + + } + + } + for (x in FormatConstants.DATE_FORMATS.indices) { + val fmx = FormatConstants.DATE_FORMATS[x][0] + if (fmx == fmt) { + fmt = FormatConstants.DATE_FORMATS[x][2] + + try { + var d: Date? + try { + d = DateConverter.getDateFromNumber(Double(res!!)) + } catch (e: NumberFormatException) { + d = DateConverter.getDate(res) // try to convert a string date + if (d == null) + // what excel does, if it's an empty date, it reverts to jan 0, 1900 + d = Date("1/1/1990") + } + + //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + try { + //sdf = new SimpleDateFormat(fmt); + WorkBookHandle.simpledateformat.applyPattern(fmt) + } catch (ex: Exception) { + Logger.logWarn("Simple Date Format could not parse: $fmt. Returning default.") //not a valid date format + } + + //return new PtgStr(sdf.format(d)); + return PtgStr(WorkBookHandle.simpledateformat.format(d)) + } catch (e: Exception) { + Logger.logErr("Unable to calcText formatting correctly for a date$e") + } + + } + } + + // we've been unable to format, try based on the string + try { + if (Xf.isDatePattern(fmt)) { + //fmtx = new SimpleDateFormat( fmt ); + WorkBookHandle.simpledateformat.applyPattern(fmt) + fmtx = WorkBookHandle.simpledateformat + } else + fmtx = DecimalFormat(fmt) + + if (res != null && res != "") + return PtgStr(fmtx!!.format(Float(res))) + else if (res != null) + return PtgStr(fmtx!!.format(0)) + } catch (e: Exception) { + //Logger.logWarn("getting formatted string value for :" + res.toString() + " failed: " + e.toString()) ; + try { + return PtgStr(res) + } catch (nbe: NumberFormatException) { + } + + } + + return PtgStr(res) + } + + /** + * TRIM + * According to documentation Trim() removes leading and trailing spaces from the cell value. + * + * + * Actually it removes all spaces except for single spaces + * between words. + */ + internal fun calcTrim(operands: Array): Ptg { + val o = operands[0].value + var res: String? + if (o is Double) { + res = ExcelTools.getNumberAsString(o.toDouble()) + } else { + res = o.toString() + } + if (res == null || res == PtgErr(PtgErr.ERROR_NA).toString()) + return PtgErr(PtgErr.ERROR_NA) + // first let's remove the beginning and trailing spaces. + if (res.length > 0) { + while (res!!.substring(0, 1) == " ") { + res = res.substring(1) + } + while (res!!.substring(res.length - 1) == " ") { + res = res.substring(0, res.length - 1) + } + // now we need to remove double spaces + while (res!!.indexOf(" ") != -1) { + val i = res.indexOf(" ") + val prestring = res.substring(0, i) + val poststring = res.substring(i + 1) + res = prestring + poststring + } + } + return PtgStr(res) + + } + + /** + * UPPER + * Converts text to uppercase + */ + internal fun calcUpper(operands: Array): Ptg { + if (operands.size > 1) return PtgCalculator.error + var s = operands[0].value.toString() + s = s.toUpperCase() + return PtgStr(s) + } + + /** + * VALUE + * Converts a text argument to a number + */ + internal fun calcValue(operands: Array): Ptg { + try { + var s = operands[0].value.toString() + if (s == "") s = "0" // Excel returns a zero for a blank value if VALUE is called upon it. + val d = Double(s) + return PtgNumber(d) + } catch (e: NumberFormatException) { + return PtgErr(PtgErr.ERROR_VALUE) + } + + } + + /** + * helper method for all DBCS-related worksheet functions + * + * @param op + * @return + */ + private fun getUnicodeBytesFromOp(op: Ptg): ByteArray? { + var strbytes: ByteArray? = null + if (op is PtgRef) { + val rec = op.refCells!![0] + if (rec is io.starter.formats.XLS.Labelsst) + strbytes = rec.unsharedString!!.readStr() + else if (rec is Formula) { + strbytes = op.value.toString().toByteArray() + } else + // DEBUGGING- Take out when done + Logger.logWarn("getUnicodeBytes: Unexpected rec encountered: " + op.javaClass) + } else if (op is PtgStr) { + strbytes = ByteArray(op.record.size - 3) + System.arraycopy(op.record, 3, strbytes, 0, strbytes.size) + } else { + // DEBUGGING- Take out when done + Logger.logWarn("getUnicodeBytes: Unexpected operand encountered: " + op.javaClass) + } + return strbytes + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XML/UnicodeInputStream.java b/src/main/java/io/starter/formats/XML/UnicodeInputStream.java deleted file mode 100644 index 1bcb4c7..0000000 --- a/src/main/java/io/starter/formats/XML/UnicodeInputStream.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -/** - * BOMInputstream.java - *

                      - *

                      - * Oct 14, 2010 - */ -package io.starter.formats.XML; - -/** - Original pseudocode : Thomas Weidenfeller - Implementation tweaked: Aki Nieminen - - http://www.unicode.org/unicode/faq/utf_bom.html - BOMs: - 00 00 FE FF = UTF-32, big-endian - FF FE 00 00 = UTF-32, little-endian - FE FF = UTF-16, big-endian - FF FE = UTF-16, little-endian - EF BB BF = UTF-8 - - Win2k Notepad: - Unicode format = UTF-16LE - ***/ - -import java.io.IOException; -import java.io.InputStream; -import java.io.PushbackInputStream; - -/** - * This inputstream will recognize unicode BOM marks - * and will skip bytes if getEncoding() method is called - * before any of the read(...) methods. - * - * Usage pattern: - String enc = "ISO-8859-1"; // or NULL to use - systemdefault - FileInputStream fis = new FileInputStream(file); - UnicodeInputStream uin = new UnicodeInputStream(fis, - enc); - enc = uin.getEncoding(); // check for BOM mark and skip - bytes - InputStreamReader in; - if (enc == null) in = new InputStreamReader(uin); - else in = new InputStreamReader(uin, enc); - */ -public class UnicodeInputStream extends InputStream { - PushbackInputStream internalIn; - boolean isInited = false; - String defaultEnc; - String encoding; - - private static final int BOM_SIZE = 4; - - public UnicodeInputStream(InputStream in, String defaultEnc) { - internalIn = new PushbackInputStream(in, BOM_SIZE); - this.defaultEnc = defaultEnc; - } - - public String getDefaultEncoding() { - return defaultEnc; - } - - public String getEncoding() { - if (!isInited) { - try { - init(); - } catch (IOException ex) { - throw new IllegalStateException("Init method failed." + ex); -// (Throwable)ex); - } - } - return encoding; - } - - /** - * Read-ahead four bytes and check for BOM marks. Extra - bytes are - * unread back to the stream, only BOM bytes are skipped. - */ - protected void init() throws IOException { - if (isInited) return; - - byte[] bom = new byte[BOM_SIZE]; - int n, unread; - n = internalIn.read(bom, 0, bom.length); - - if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) - && - (bom[2] == (byte) 0xBF)) { - encoding = "UTF-8"; - unread = n - 3; - } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) - 0xFF)) { - encoding = "UTF-16BE"; - unread = n - 2; - } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) - 0xFE)) { - encoding = "UTF-16LE"; - unread = n - 2; - } else if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) - 0x00) && - (bom[2] == (byte) 0xFE) && (bom[3] == (byte) - 0xFF)) { - encoding = "UTF-32BE"; - unread = n - 4; - } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) - 0xFE) && - (bom[2] == (byte) 0x00) && (bom[3] == (byte) - 0x00)) { - encoding = "UTF-32LE"; - unread = n - 4; - } else { - // Unicode BOM mark not found, unread all bytes - encoding = defaultEnc; - unread = n; - } - // io.starter.toolkit.Logger.log("read=" + n + ", unread=" + unread); - - if (unread > 0) internalIn.unread(bom, (n - unread), - unread); - - isInited = true; - } - - public void close() throws IOException { - //init(); - isInited = true; - internalIn.close(); - } - - public int read() throws IOException { - //init(); - isInited = true; - return internalIn.read(); - } -} diff --git a/src/main/java/io/starter/formats/XML/UnicodeInputStream.kt b/src/main/java/io/starter/formats/XML/UnicodeInputStream.kt new file mode 100644 index 0000000..8a4208c --- /dev/null +++ b/src/main/java/io/starter/formats/XML/UnicodeInputStream.kt @@ -0,0 +1,160 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +/** + * BOMInputstream.java + * + * + * + * + * Oct 14, 2010 + */ +package io.starter.formats.XML + +/** + * Original pseudocode : Thomas Weidenfeller + * Implementation tweaked: Aki Nieminen + * + * http://www.unicode.org/unicode/faq/utf_bom.html + * BOMs: + * 00 00 FE FF = UTF-32, big-endian + * FF FE 00 00 = UTF-32, little-endian + * FE FF = UTF-16, big-endian + * FF FE = UTF-16, little-endian + * EF BB BF = UTF-8 + * + * Win2k Notepad: + * Unicode format = UTF-16LE + */ + +import java.io.IOException +import java.io.InputStream +import java.io.PushbackInputStream + +/** + * This inputstream will recognize unicode BOM marks + * and will skip bytes if getEncoding() method is called + * before any of the read(...) methods. + * + * Usage pattern: + * String enc = "ISO-8859-1"; // or NULL to use + * systemdefault + * FileInputStream fis = new FileInputStream(file); + * UnicodeInputStream uin = new UnicodeInputStream(fis, + * enc); + * enc = uin.getEncoding(); // check for BOM mark and skip + * bytes + * InputStreamReader in; + * if (enc == null) in = new InputStreamReader(uin); + * else in = new InputStreamReader(uin, enc); + */ +class UnicodeInputStream(`in`: InputStream, defaultEnc: String) : InputStream() { + internal var internalIn: PushbackInputStream + internal var isInited = false + var defaultEncoding: String + internal set + internal var encoding: String + + init { + internalIn = PushbackInputStream(`in`, BOM_SIZE) + this.defaultEncoding = defaultEnc + } + + fun getEncoding(): String { + if (!isInited) { + try { + init() + } catch (ex: IOException) { + throw IllegalStateException("Init method failed.$ex") + // (Throwable)ex); + } + + } + return encoding + } + + /** + * Read-ahead four bytes and check for BOM marks. Extra + * bytes are + * unread back to the stream, only BOM bytes are skipped. + */ + @Throws(IOException::class) + protected fun init() { + if (isInited) return + + val bom = ByteArray(BOM_SIZE) + val n: Int + val unread: Int + n = internalIn.read(bom, 0, bom.size) + + if (bom[0] == 0xEF.toByte() && bom[1] == 0xBB.toByte() + && + bom[2] == 0xBF.toByte()) { + encoding = "UTF-8" + unread = n - 3 + } else if (bom[0] == 0xFE.toByte() && bom[1] == 0xFF.toByte()) { + encoding = "UTF-16BE" + unread = n - 2 + } else if (bom[0] == 0xFF.toByte() && bom[1] == 0xFE.toByte()) { + encoding = "UTF-16LE" + unread = n - 2 + } else if (bom[0] == 0x00.toByte() && bom[1] == 0x00.toByte() && + bom[2] == 0xFE.toByte() && bom[3] == 0xFF.toByte()) { + encoding = "UTF-32BE" + unread = n - 4 + } else if (bom[0] == 0xFF.toByte() && bom[1] == 0xFE.toByte() && + bom[2] == 0x00.toByte() && bom[3] == 0x00.toByte()) { + encoding = "UTF-32LE" + unread = n - 4 + } else { + // Unicode BOM mark not found, unread all bytes + encoding = defaultEncoding + unread = n + } + // io.starter.toolkit.Logger.log("read=" + n + ", unread=" + unread); + + if (unread > 0) + internalIn.unread(bom, n - unread, + unread) + + isInited = true + } + + @Throws(IOException::class) + override fun close() { + //init(); + isInited = true + internalIn.close() + } + + @Throws(IOException::class) + override fun read(): Int { + //init(); + isInited = true + return internalIn.read() + } + + companion object { + + private val BOM_SIZE = 4 + } +} diff --git a/src/main/java/io/starter/formats/cellformat/CellFormat.java b/src/main/java/io/starter/formats/cellformat/CellFormat.kt similarity index 81% rename from src/main/java/io/starter/formats/cellformat/CellFormat.java rename to src/main/java/io/starter/formats/cellformat/CellFormat.kt index 7966005..3e13e58 100644 --- a/src/main/java/io/starter/formats/cellformat/CellFormat.java +++ b/src/main/java/io/starter/formats/cellformat/CellFormat.kt @@ -20,12 +20,12 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.cellformat; +package io.starter.formats.cellformat -import io.starter.OpenXLS.Cell; +import io.starter.OpenXLS.Cell -public interface CellFormat { - String format(Object input); +interface CellFormat { + fun format(input: Any): String - String format(Cell cell); + fun format(cell: Cell): String } diff --git a/src/main/java/io/starter/formats/cellformat/CellFormatFactory.java b/src/main/java/io/starter/formats/cellformat/CellFormatFactory.java deleted file mode 100644 index b513b6c..0000000 --- a/src/main/java/io/starter/formats/cellformat/CellFormatFactory.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.cellformat; - -import io.starter.toolkit.StringTool; - -public class CellFormatFactory { - private CellFormatFactory() { - // this is a static-only class - throw new UnsupportedOperationException(); - } - - public static CellFormat fromPatternString(String pattern) { - if (null == pattern || "".equals(pattern) - || "General".equalsIgnoreCase(pattern)) { - return new GeneralCellFormat(); - } - - String[] pats = pattern.split(";"); - - String tester = StringTool.convertPatternExtractBracketedExpression(pats[0]); - if (tester.matches(".*(((y{1,4}|m{1,5}|d{1,4}|h{1,2}|s{1,2}).*)+).*")) { - String string; - if (pats.length > 3) { - string = StringTool.convertPatternFromExcelToStringFormatter(pats[3], false); - } else { - string = "%s"; - } - - return new DateCellFormat( - StringTool.convertDatePatternFromExcelToStringFormatter(tester), string); - } - - String positive, negative, zero, string; - positive = StringTool.convertPatternFromExcelToStringFormatter(pats[0], false); - - negative = StringTool.convertPatternFromExcelToStringFormatter( - pats[pats.length > 1 ? 1 : 0], true); - - if (pats.length > 2) { - zero = StringTool.convertPatternFromExcelToStringFormatter(pats[2], false); - } else { - zero = positive; - } - - if (pats.length > 3) { - string = StringTool.convertPatternFromExcelToStringFormatter(pats[3], false); - } else { - string = "%s"; - } - - return new NumberCellFormat(positive, negative, zero, string); - } -} diff --git a/src/main/java/io/starter/formats/cellformat/CellFormatFactory.kt b/src/main/java/io/starter/formats/cellformat/CellFormatFactory.kt new file mode 100644 index 0000000..1a88f04 --- /dev/null +++ b/src/main/java/io/starter/formats/cellformat/CellFormatFactory.kt @@ -0,0 +1,80 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.cellformat + +import io.starter.toolkit.StringTool + +class CellFormatFactory private constructor() { + init { + // this is a static-only class + throw UnsupportedOperationException() + } + + companion object { + + fun fromPatternString(pattern: String?): CellFormat { + if (null == pattern || "" == pattern + || "General".equals(pattern, ignoreCase = true)) { + return GeneralCellFormat() + } + + val pats = pattern.split(";".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + + val tester = StringTool.convertPatternExtractBracketedExpression(pats[0]) + if (tester.matches(".*(((y{1,4}|m{1,5}|d{1,4}|h{1,2}|s{1,2}).*)+).*".toRegex())) { + val string: String + if (pats.size > 3) { + string = StringTool.convertPatternFromExcelToStringFormatter(pats[3], false) + } else { + string = "%s" + } + + return DateCellFormat( + StringTool.convertDatePatternFromExcelToStringFormatter(tester), string) + } + + val positive: String + val negative: String + val zero: String + val string: String + positive = StringTool.convertPatternFromExcelToStringFormatter(pats[0], false) + + negative = StringTool.convertPatternFromExcelToStringFormatter( + pats[if (pats.size > 1) 1 else 0], true) + + if (pats.size > 2) { + zero = StringTool.convertPatternFromExcelToStringFormatter(pats[2], false) + } else { + zero = positive + } + + if (pats.size > 3) { + string = StringTool.convertPatternFromExcelToStringFormatter(pats[3], false) + } else { + string = "%s" + } + + return NumberCellFormat(positive, negative, zero, string) + } + } +} diff --git a/src/main/java/io/starter/formats/cellformat/DateCellFormat.java b/src/main/java/io/starter/formats/cellformat/DateCellFormat.kt similarity index 51% rename from src/main/java/io/starter/formats/cellformat/DateCellFormat.java rename to src/main/java/io/starter/formats/cellformat/DateCellFormat.kt index b4c7b40..b2eb3e4 100644 --- a/src/main/java/io/starter/formats/cellformat/DateCellFormat.java +++ b/src/main/java/io/starter/formats/cellformat/DateCellFormat.kt @@ -20,39 +20,32 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.cellformat; +package io.starter.formats.cellformat -import io.starter.OpenXLS.Cell; -import io.starter.OpenXLS.CellHandle; -import io.starter.OpenXLS.DateConverter; +import io.starter.OpenXLS.Cell +import io.starter.OpenXLS.CellHandle +import io.starter.OpenXLS.DateConverter -import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.text.SimpleDateFormat +import java.util.Calendar -public class DateCellFormat - extends SimpleDateFormat - implements CellFormat { - private static final long serialVersionUID = 1896075041723437260L; +class DateCellFormat internal constructor(date: String, private val text_format: String) : SimpleDateFormat(date), CellFormat { - private final String text_format; - - DateCellFormat(String date, String text) { - super(date); - this.text_format = text; - } - - public String format(Cell cell) { + override fun format(cell: Cell): String { // make sure to return the empty string for blank cells // getting the calendar coerces to double and thus gets zero - if ((cell instanceof CellHandle && ((CellHandle) cell).isBlank()) - || "".equals(cell.getVal())) - return ""; + if (cell is CellHandle && cell.isBlank || "" == cell.`val`) + return "" - if (cell.getCellType() == Cell.TYPE_STRING) { - return String.format(this.text_format, (String) cell.getVal()); + if (cell.cellType == Cell.TYPE_STRING) { + return String.format(this.text_format, cell.`val` as String) } - Calendar date = DateConverter.getCalendarFromCell(cell); - return this.format(date.getTime()); + val date = DateConverter.getCalendarFromCell(cell) + return this.format(date.time) + } + + companion object { + private val serialVersionUID = 1896075041723437260L } } diff --git a/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.java b/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.java deleted file mode 100644 index 550b994..0000000 --- a/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.cellformat; - -import io.starter.OpenXLS.Cell; -import io.starter.OpenXLS.ExcelTools; - -import java.text.FieldPosition; -import java.text.Format; -import java.text.ParsePosition; - -public class GeneralCellFormat - extends Format - implements CellFormat { - private static final long serialVersionUID = -3530672760714160988L; - - // make the constructor package-private - GeneralCellFormat() { - } - - public StringBuffer format(Object obj, StringBuffer buffer, - FieldPosition pos) { - // try to parse strings as numbers - if (obj instanceof String) try { - obj = Double.valueOf((String) obj); - } catch (NumberFormatException ex) { - // this is OK, it just wasn't a number - } - - if (obj instanceof Number) { - Number num = (Number) obj; - if (num.longValue() == num.doubleValue()) { - // it's an integer - return buffer.append(num.longValue()); - } else { - // it's floating-point - return buffer.append( - ExcelTools.getNumberAsString(num.doubleValue())); - } - } - - return buffer.append(obj.toString()); - } - - public Object parseObject(String source, ParsePosition pos) { - throw new UnsupportedOperationException(); - } - - public String format(Cell cell) { - return this.format(cell.getVal()); - } - - public String format(String val) { - return this.format(val); - } -} diff --git a/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.kt b/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.kt new file mode 100644 index 0000000..4cd0b96 --- /dev/null +++ b/src/main/java/io/starter/formats/cellformat/GeneralCellFormat.kt @@ -0,0 +1,76 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.cellformat + +import io.starter.OpenXLS.Cell +import io.starter.OpenXLS.ExcelTools + +import java.text.FieldPosition +import java.text.Format +import java.text.ParsePosition + +class GeneralCellFormat// make the constructor package-private +internal constructor() : Format(), CellFormat { + + override fun format(obj: Any, buffer: StringBuffer, + pos: FieldPosition): StringBuffer { + var obj = obj + // try to parse strings as numbers + if (obj is String) + try { + obj = java.lang.Double.valueOf(obj) + } catch (ex: NumberFormatException) { + // this is OK, it just wasn't a number + } + + if (obj is Number) { + val num = obj + return if (num.toLong().toDouble() == num.toDouble()) { + // it's an integer + buffer.append(num.toLong()) + } else { + // it's floating-point + buffer.append( + ExcelTools.getNumberAsString(num.toDouble())) + } + } + + return buffer.append(obj.toString()) + } + + override fun parseObject(source: String, pos: ParsePosition): Any { + throw UnsupportedOperationException() + } + + override fun format(cell: Cell): String { + return this.format(cell.`val`) + } + + fun format(`val`: String): String { + return this.format(`val`) + } + + companion object { + private val serialVersionUID = -3530672760714160988L + } +} diff --git a/src/main/java/io/starter/formats/cellformat/NumberCellFormat.java b/src/main/java/io/starter/formats/cellformat/NumberCellFormat.java deleted file mode 100644 index c2d7da6..0000000 --- a/src/main/java/io/starter/formats/cellformat/NumberCellFormat.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.cellformat; - -import io.starter.OpenXLS.Cell; - -import java.text.FieldPosition; -import java.text.NumberFormat; -import java.text.ParsePosition; - -public class NumberCellFormat - extends NumberFormat - implements CellFormat { - private static final long serialVersionUID = -7191923168789058338L; - - private final String positive, negative, zero, string; - - NumberCellFormat(String positive, String negative, String zero, String string) { - this.positive = positive; - this.negative = negative; - this.zero = zero; - this.string = string; - } - - public StringBuffer format(Object input, StringBuffer buffer, - FieldPosition pos) { - if (input instanceof String) { - // hack to make useless @ pattern work - if ("%s".equals(this.positive)) { - return buffer.append(input); - } - try { - Double d = new Double(input.toString()); - input = d; - } catch (NumberFormatException e) { - return buffer.append( - String.format(this.string, (String) input)); - } - } - if (input instanceof Number) { - String format; - double value = ((Number) input).doubleValue(); - - if (value > 0) format = this.positive; - else if (value < 0) { - format = this.negative; - value = Math.abs(value); - } else format = this.zero; - - // hack to make percentage formats work - if (format.contains("%%")) { - value *= 100; - } - - // hack to make useless @ pattern work - if ("%s".equals(format)) { - return buffer.append(input); - } - - return buffer.append( - String.format(format, Double.valueOf(value))); - } else { - throw new IllegalArgumentException("unsupported input type"); - } - } - - public StringBuffer format(double number, StringBuffer buffer, - FieldPosition pos) { - return buffer.append(this.format(Double.valueOf(number))); - } - - public StringBuffer format(long number, StringBuffer buffer, - FieldPosition pos) { - return buffer.append(this.format(Long.valueOf(number))); - } - - public Number parse(String source, ParsePosition parsePosition) { - throw new UnsupportedOperationException(); - } - - public String format(Cell cell) { - return this.format(cell.getVal()); - } - -} diff --git a/src/main/java/io/starter/formats/cellformat/NumberCellFormat.kt b/src/main/java/io/starter/formats/cellformat/NumberCellFormat.kt new file mode 100644 index 0000000..17d143e --- /dev/null +++ b/src/main/java/io/starter/formats/cellformat/NumberCellFormat.kt @@ -0,0 +1,100 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.cellformat + +import io.starter.OpenXLS.Cell + +import java.text.FieldPosition +import java.text.NumberFormat +import java.text.ParsePosition + +class NumberCellFormat internal constructor(private val positive: String, private val negative: String, private val zero: String, private val string: String) : NumberFormat(), CellFormat { + + override fun format(input: Any, buffer: StringBuffer, + pos: FieldPosition): StringBuffer { + var input = input + if (input is String) { + // hack to make useless @ pattern work + if ("%s" == this.positive) { + return buffer.append(input) + } + try { + val d = Double(input.toString()) + input = d + } catch (e: NumberFormatException) { + return buffer.append( + String.format(this.string, input)) + } + + } + if (input is Number) { + val format: String + var value = input.toDouble() + + if (value > 0) + format = this.positive + else if (value < 0) { + format = this.negative + value = Math.abs(value) + } else + format = this.zero + + // hack to make percentage formats work + if (format.contains("%%")) { + value *= 100.0 + } + + // hack to make useless @ pattern work + return if ("%s" == format) { + buffer.append(input) + } else buffer.append( + String.format(format, java.lang.Double.valueOf(value))) + + } else { + throw IllegalArgumentException("unsupported input type") + } + } + + override fun format(number: Double, buffer: StringBuffer, + pos: FieldPosition): StringBuffer { + return buffer.append(this.format(java.lang.Double.valueOf(number))) + } + + override fun format(number: Long, buffer: StringBuffer, + pos: FieldPosition): StringBuffer { + return buffer.append(this.format(java.lang.Long.valueOf(number))) + } + + override fun parse(source: String, parsePosition: ParsePosition): Number { + throw UnsupportedOperationException() + } + + override fun format(cell: Cell): String { + return this.format(cell.`val`) + } + + companion object { + private val serialVersionUID = -7191923168789058338L + } + +} diff --git a/src/main/java/io/starter/formats/escher/EscherRecord.java b/src/main/java/io/starter/formats/escher/EscherRecord.java deleted file mode 100644 index 05f113d..0000000 --- a/src/main/java/io/starter/formats/escher/EscherRecord.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -import io.starter.toolkit.ByteTools; - -import java.io.Serializable; - -abstract class EscherRecord implements Serializable { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7987132917889379656L; - byte[] header; - byte[] data; - int length; - int inst; - int fbt; - int version; - boolean isDirty = false; - - /** - * no param constructor for Serializable - */ - public void EscherRecord() { - } - - EscherRecord(int fbt, int inst, int version) { - this.fbt = fbt; - this.inst = inst; - this.version = version; - } - - public int getFbt() { - - return fbt; - } - - public int getInst() { - - return inst; - } - - public int getLength() { - return length; - } - - public void setFbt(int value) { - fbt = value; - } - - public void setInst(int value) { - inst = value; - } - - public void setLength(int value) { - length = value; - } - - protected abstract byte[] getData(); - - private byte[] getHeaderBytes() { - //TODO: Reverse the process of header decoding here - byte[] headerBytes = new byte[4]; - - headerBytes[0] = (byte) ((0xF & version) | (0xF0 & (inst << 4))); - headerBytes[1] = (byte) ((0x00000FF0 & inst) >> 4); - headerBytes[2] = (byte) ((0x000000FF & fbt)); - headerBytes[3] = (byte) ((0x0000FF00 & fbt) >> 8); - - - int version2 = (0x0F & headerBytes[0]); - int inst2 = ((0xFF & headerBytes[1]) >> 4) | ((0xF0 & headerBytes[0]) >> 4); - - byte[] lenBytes = ByteTools.cLongToLEBytes(length); - - byte[] retData = new byte[8]; - System.arraycopy(headerBytes, 0, retData, 0, 4); - System.arraycopy(lenBytes, 0, retData, headerBytes.length, 4); - - return retData; - } - - public byte[] toByteArray() { - byte[] dataBytes = getData(); //Have it in this sequence as some records adjust their header byte length from getData - byte[] headerBytes = getHeaderBytes(); - - byte[] retData = new byte[headerBytes.length + dataBytes.length]; - System.arraycopy(headerBytes, 0, retData, 0, headerBytes.length); - if (dataBytes.length > 0) - System.arraycopy(dataBytes, 0, retData, headerBytes.length, dataBytes.length); - return retData; - - } -} diff --git a/src/main/java/io/starter/formats/escher/EscherRecord.kt b/src/main/java/io/starter/formats/escher/EscherRecord.kt new file mode 100644 index 0000000..87cc138 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/EscherRecord.kt @@ -0,0 +1,84 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +import io.starter.toolkit.ByteTools + +import java.io.Serializable + +internal abstract class EscherRecord(var fbt: Int, var inst: Int, var version: Int) : Serializable { + var header: ByteArray? = null + var data: ByteArray? = null + var length: Int = 0 + var isDirty = false + + private//TODO: Reverse the process of header decoding here + val headerBytes: ByteArray + get() { + val headerBytes = ByteArray(4) + + headerBytes[0] = (0xF and version or (0xF0 and (inst shl 4))).toByte() + headerBytes[1] = (0x00000FF0 and inst shr 4).toByte() + headerBytes[2] = (0x000000FF and fbt).toByte() + headerBytes[3] = (0x0000FF00 and fbt shr 8).toByte() + + + val version2 = 0x0F and headerBytes[0] + val inst2 = 0xFF and headerBytes[1] shr 4 or (0xF0 and headerBytes[0] shr 4) + + val lenBytes = ByteTools.cLongToLEBytes(length) + + val retData = ByteArray(8) + System.arraycopy(headerBytes, 0, retData, 0, 4) + System.arraycopy(lenBytes, 0, retData, headerBytes.size, 4) + + return retData + } + + /** + * no param constructor for Serializable + */ + fun EscherRecord() {} + + protected abstract fun getData(): ByteArray + + fun toByteArray(): ByteArray { + val dataBytes = getData() //Have it in this sequence as some records adjust their header byte length from getData + val headerBytes = headerBytes + + val retData = ByteArray(headerBytes.size + dataBytes.size) + System.arraycopy(headerBytes, 0, retData, 0, headerBytes.size) + if (dataBytes.size > 0) + System.arraycopy(dataBytes, 0, retData, headerBytes.size, dataBytes.size) + return retData + + } + + companion object { + + /** + * serialVersionUID + */ + private const val serialVersionUID = -7987132917889379656L + } +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtBSE.java b/src/main/java/io/starter/formats/escher/MsofbtBSE.java deleted file mode 100644 index f8357b6..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtBSE.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -import io.starter.formats.XLS.MSODrawingConstants; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.MD4Digest; - - -//0xF007 -public class MsofbtBSE extends EscherRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -9072016434347371029L; - private byte[] imageData; - private int imageType; - private int numShapes; - private int cRef = 1; // default reference count is 1; 0= hidden; > 1= multiple refs to same image 20071120 KSC - - - public MsofbtBSE(int fbt, int inst, int version) { - super(fbt, inst, version); - } - - protected byte[] getData() { - byte[] imgHeader = new byte[61]; // BSE header = 36 bytes, BLIP record header = 24 bytes, then imageData bytes follow - imgHeader[0] = (byte) imageType; //btWin32 - imgHeader[1] = (byte) imageType; //btMac - - // 20071004 KSC: Unknown type of record, does not follow below rules - // and will cause file error ... - // TODO: Research more - if (imageType == 0) { - byte[] retData = new byte[36]; - retData[18] = (byte) -1; // tag - setLength(retData.length); - return retData; - } - //The digest of image data should be here. 2-17 - MD4Digest md4Digest = new MD4Digest(); - byte[] digest = md4Digest.getDigest(imageData); - System.arraycopy(digest, 0, imgHeader, 2, 16); - - imgHeader[18] = (byte) -1; //First tag byte is always -1 - imgHeader[19] = (byte) 0; //Second tag byte is always 0 - -/* 20071119 KSC: original code; this was wrong - int mod = (imageData.length+25)%MAXROWS_BIFF8; - imgHeader[20] = (byte)((0x000000FF&mod)); - imgHeader[21] = (byte)((0x0000FF00&mod)>>8); - - //isze is always - int size = MAXROWS_BIFF8 + (int)((imageData.length-36)/MAXROWS_BIFF8); - byte[] tempBytes = ByteTools.cLongToLEBytes(size); - imgHeader[22]=tempBytes[0]; - imgHeader[23]=tempBytes[1]; - imgHeader[24]=tempBytes[2]; - imgHeader[25]=tempBytes[3]; - - // cRef is always zero - tempBytes = ByteTools.cLongToLEBytes(0); - imgHeader[26]=tempBytes[0]; - imgHeader[27]=tempBytes[1]; - imgHeader[28]=tempBytes[2]; - imgHeader[29]=tempBytes[3]; - - //foDelay is always zero - tempBytes = ByteTools.cLongToLEBytes(0); - imgHeader[30]=tempBytes[0]; - imgHeader[31]=tempBytes[1]; - imgHeader[32]=tempBytes[2]; - imgHeader[33]=tempBytes[3]; - - imgHeader[34]=(byte)1; //usage is numShapes - imgHeader[35]=(byte)0; //cbName is always zero -*/ - // new code - // Size of data + 25 (for header stuff, I assume) (4 bytes) - int sz = imageData.length + 25; - byte[] tempBytes = ByteTools.cLongToLEBytes(sz); - imgHeader[20] = tempBytes[0]; - imgHeader[21] = tempBytes[1]; - imgHeader[22] = tempBytes[2]; - imgHeader[23] = tempBytes[3]; - - // cRef (4 bytes) = reference count; 1 unless it's referenced more than once ... :) 0 if hidden - tempBytes = ByteTools.cLongToLEBytes(cRef); - imgHeader[24] = tempBytes[0]; - imgHeader[25] = tempBytes[1]; - imgHeader[26] = tempBytes[2]; - imgHeader[27] = tempBytes[3]; - - // foDelay is always zero = image bytes are not in delay stream - tempBytes = ByteTools.cLongToLEBytes(0); - imgHeader[28] = tempBytes[0]; - imgHeader[29] = tempBytes[1]; - imgHeader[30] = tempBytes[2]; - imgHeader[31] = tempBytes[3]; - - imgHeader[32] = (byte) 0; //usage - should be 0=default usage - imgHeader[33] = (byte) 0; //cbName is always zero = no name following this header - // bytes 34 and 35 are unused at this point and should be 0 - - // ******************************************************************************** - // BLIP RECORD follows BSE Header UNLESS file delayOffset is > 0 or cbNameLen > 0 - // ******************************************************************************** - - // BLIP RECORD for Metafile/PICT Blips (msobiEMF, msobiWMF, or msobiPICT): - /* The secondary, or data, UID - should always be set. */ - // BYTE m_rgbUid[16]; - /* The primary UID - this defaults to 0, in which case the primary ID is - that of the internal data. NOTE!: The primary UID is only saved to disk - if (blip_instance ^ blip_signature == 1). Blip_instance is MSOFBH.inst and - blip_signature is one of the values defined in MSOBI */ - //BYTE m_rgbUidPrimary[16]; // optional based on the above check - - /* Metafile Blip overhead = 34 bytes. m_cb gives the number of - bytes required to store an uncompressed version of the file, m_cbSave - is the compressed size. m_mfBounds gives the boundary of all the - drawing calls within the metafile (this may just be the bounding box - or it may allow some whitespace, for a WMF this comes from the - SetWindowOrg and SetWindowExt records of the metafile). - */ - /* - int m_cb; // Cache of the metafile size - RECT m_rcBounds; // Boundary of metafile drawing commands - POINT m_ptSize; // Size of metafile in EMUs - int m_cbSave; // Cache of saved size (size of m_pvBits) - BYTE m_fCompression; // MSOBLIPCOMPRESSION - BYTE m_fFilter; // always msofilterNone - void *m_pvBits; // Compressed bits of metafile. - */ - - // BLIP RECORD for Bitmap Blips (msobiJPEG, msobiPNG, or msobiDIB) : - /* - * They have the same UID header as described in the Metafile Blip case. The data after the header is just a single BYTE "tag" - * value and is followed by the compressed data of the bitmap in the relevant format (JFIF or PNG, bytes as would be stored in a file). - * For the msobiDIB format, the data is in the standard DIB format as a BITMAPINFO ER, BITMAPCORE ER or BITMAPV4 ER followed by - * the color map (DIB_RGB_COLORS) and the bits. This data is not compressed (the format is used for very small DIB bitmaps only). - - To determine where the bits are located, refer to the following header: - - /* The secondary, or data, UID - should always be set. */ - /* - BYTE m_rgbUid[16]; - /* The primary UID - this defaults to 0, in which case the primary ID is - that of the internal data. NOTE!: The primary UID is only saved to disk - if (blip_instance ^ blip_signature == 1). Blip_instance is MSOFBH.finst and - blip_signature is one of the values defined in MSOBI - */ - /* - BYTE m_rgbUidPrimary[16]; // optional based on the above check - BYTE m_bTag; - void *m_pvBits; // raster bits of the blip. - */ - - - // 20080226 KSC: instead of handling only two image types, handle them all :) - int inst1 = MSODrawingConstants.msobiJPEG; - switch (imageType) { - case MSODrawingConstants.IMAGE_TYPE_GIF: - // TODO: ??? - break; - case MSODrawingConstants.IMAGE_TYPE_EMF: - inst1 = MSODrawingConstants.msobiEMF; - break; - case MSODrawingConstants.IMAGE_TYPE_WMF: - inst1 = MSODrawingConstants.msobiWMF; - break; - case MSODrawingConstants.IMAGE_TYPE_PICT: - inst1 = MSODrawingConstants.msobiPICT; - break; - case MSODrawingConstants.IMAGE_TYPE_JPG: - inst1 = MSODrawingConstants.msobiJPEG; - break; - case MSODrawingConstants.IMAGE_TYPE_PNG: - inst1 = MSODrawingConstants.msobiPNG; - break; - case MSODrawingConstants.IMAGE_TYPE_DIB: - inst1 = MSODrawingConstants.msobiDIB; - break; - } - //int inst1=(imageType==5)?MsodrawingConstants.msobiJPEG:MsodrawingConstants.msobiPNG; - int version1 = 0; - int fbt1 = MSODrawingConstants.msofbtBlipFirst + imageType; - int len1 = imageData.length + 17; - imgHeader[36] = (byte) ((0x0F & inst1) << 4 | (0x0F & version1)); - imgHeader[37] = (byte) ((0xFF0 & inst1) >> 4); - imgHeader[38] = (byte) ((0xFF & fbt1)); - imgHeader[39] = (byte) ((0xFF00 & fbt1) >> 8); - - //length with size + 25; - tempBytes = ByteTools.cLongToLEBytes(len1); - imgHeader[40] = tempBytes[0]; - imgHeader[41] = tempBytes[1]; - imgHeader[42] = tempBytes[2]; - imgHeader[43] = tempBytes[3]; - - ///Here will again be the md4 digest of the image data 44-60 - System.arraycopy(digest, 0, imgHeader, 44, 16); - imgHeader[60] = (byte) 0xff; //marker to indicate start of image data - int len = 61; - byte[] retData = new byte[len + imageData.length]; - - System.arraycopy(imgHeader, 0, retData, 0, len); - System.arraycopy(imageData, 0, retData, len, imageData.length); - - setLength(retData.length); - return retData; - } - - public void setImageData(byte[] d) { - imageData = d; - } - - /** - * set the reference count for this image Data rec - * - * @param cRef - */ - public void setRefCount(int cRef) { - this.cRef = cRef; - } - - /** - * returns the ref count for this image Data Rec - * - * @return - */ - public int getRefCount() { - return this.cRef; - } - - public int getImageType() { - return imageType; - } - - public void setImageType(int imageType) { - this.imageType = imageType; - } - - public int getNumShapes() { - return numShapes; - } - - public void setNumShapes(int numShapes) { - this.numShapes = numShapes; - } - -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtBSE.kt b/src/main/java/io/starter/formats/escher/MsofbtBSE.kt new file mode 100644 index 0000000..8bdcf8e --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtBSE.kt @@ -0,0 +1,232 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +import io.starter.formats.XLS.MSODrawingConstants +import io.starter.toolkit.ByteTools +import io.starter.toolkit.MD4Digest + + +//0xF007 +class MsofbtBSE(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + private var imageData: ByteArray? = null + var imageType: Int = 0 + var numShapes: Int = 0 + /** + * returns the ref count for this image Data Rec + * + * @return + */ + /** + * set the reference count for this image Data rec + * + * @param cRef + */ + var refCount = 1 // default reference count is 1; 0= hidden; > 1= multiple refs to same image 20071120 KSC + + override fun getData(): ByteArray { + val imgHeader = ByteArray(61) // BSE header = 36 bytes, BLIP record header = 24 bytes, then imageData bytes follow + imgHeader[0] = imageType.toByte() //btWin32 + imgHeader[1] = imageType.toByte() //btMac + + // 20071004 KSC: Unknown type of record, does not follow below rules + // and will cause file error ... + // TODO: Research more + if (imageType == 0) { + val retData = ByteArray(36) + retData[18] = (-1).toByte() // tag + length = retData.size + return retData + } + //The digest of image data should be here. 2-17 + val md4Digest = MD4Digest() + val digest = md4Digest.getDigest(imageData) + System.arraycopy(digest, 0, imgHeader, 2, 16) + + imgHeader[18] = (-1).toByte() //First tag byte is always -1 + imgHeader[19] = 0.toByte() //Second tag byte is always 0 + + /* 20071119 KSC: original code; this was wrong + int mod = (imageData.length+25)%MAXROWS_BIFF8; + imgHeader[20] = (byte)((0x000000FF&mod)); + imgHeader[21] = (byte)((0x0000FF00&mod)>>8); + + //isze is always + int size = MAXROWS_BIFF8 + (int)((imageData.length-36)/MAXROWS_BIFF8); + byte[] tempBytes = ByteTools.cLongToLEBytes(size); + imgHeader[22]=tempBytes[0]; + imgHeader[23]=tempBytes[1]; + imgHeader[24]=tempBytes[2]; + imgHeader[25]=tempBytes[3]; + + // cRef is always zero + tempBytes = ByteTools.cLongToLEBytes(0); + imgHeader[26]=tempBytes[0]; + imgHeader[27]=tempBytes[1]; + imgHeader[28]=tempBytes[2]; + imgHeader[29]=tempBytes[3]; + + //foDelay is always zero + tempBytes = ByteTools.cLongToLEBytes(0); + imgHeader[30]=tempBytes[0]; + imgHeader[31]=tempBytes[1]; + imgHeader[32]=tempBytes[2]; + imgHeader[33]=tempBytes[3]; + + imgHeader[34]=(byte)1; //usage is numShapes + imgHeader[35]=(byte)0; //cbName is always zero +*/ + // new code + // Size of data + 25 (for header stuff, I assume) (4 bytes) + val sz = imageData!!.size + 25 + var tempBytes = ByteTools.cLongToLEBytes(sz) + imgHeader[20] = tempBytes[0] + imgHeader[21] = tempBytes[1] + imgHeader[22] = tempBytes[2] + imgHeader[23] = tempBytes[3] + + // cRef (4 bytes) = reference count; 1 unless it's referenced more than once ... :) 0 if hidden + tempBytes = ByteTools.cLongToLEBytes(refCount) + imgHeader[24] = tempBytes[0] + imgHeader[25] = tempBytes[1] + imgHeader[26] = tempBytes[2] + imgHeader[27] = tempBytes[3] + + // foDelay is always zero = image bytes are not in delay stream + tempBytes = ByteTools.cLongToLEBytes(0) + imgHeader[28] = tempBytes[0] + imgHeader[29] = tempBytes[1] + imgHeader[30] = tempBytes[2] + imgHeader[31] = tempBytes[3] + + imgHeader[32] = 0.toByte() //usage - should be 0=default usage + imgHeader[33] = 0.toByte() //cbName is always zero = no name following this header +// bytes 34 and 35 are unused at this point and should be 0 + +// ******************************************************************************** +// BLIP RECORD follows BSE Header UNLESS file delayOffset is > 0 or cbNameLen > 0 +// ******************************************************************************** + +// BLIP RECORD for Metafile/PICT Blips (msobiEMF, msobiWMF, or msobiPICT): +/* The secondary, or data, UID - should always be set. */ +// BYTE m_rgbUid[16]; +/* The primary UID - this defaults to 0, in which case the primary ID is + that of the internal data. NOTE!: The primary UID is only saved to disk + if (blip_instance ^ blip_signature == 1). Blip_instance is MSOFBH.inst and + blip_signature is one of the values defined in MSOBI */ +//BYTE m_rgbUidPrimary[16]; // optional based on the above check + +/* Metafile Blip overhead = 34 bytes. m_cb gives the number of + bytes required to store an uncompressed version of the file, m_cbSave + is the compressed size. m_mfBounds gives the boundary of all the + drawing calls within the metafile (this may just be the bounding box + or it may allow some whitespace, for a WMF this comes from the + SetWindowOrg and SetWindowExt records of the metafile). + */ +/* + int m_cb; // Cache of the metafile size + RECT m_rcBounds; // Boundary of metafile drawing commands + POINT m_ptSize; // Size of metafile in EMUs + int m_cbSave; // Cache of saved size (size of m_pvBits) + BYTE m_fCompression; // MSOBLIPCOMPRESSION + BYTE m_fFilter; // always msofilterNone + void *m_pvBits; // Compressed bits of metafile. + */ + +// BLIP RECORD for Bitmap Blips (msobiJPEG, msobiPNG, or msobiDIB) : +/* + * They have the same UID header as described in the Metafile Blip case. The data after the header is just a single BYTE "tag" + * value and is followed by the compressed data of the bitmap in the relevant format (JFIF or PNG, bytes as would be stored in a file). + * For the msobiDIB format, the data is in the standard DIB format as a BITMAPINFO ER, BITMAPCORE ER or BITMAPV4 ER followed by + * the color map (DIB_RGB_COLORS) and the bits. This data is not compressed (the format is used for very small DIB bitmaps only). + + To determine where the bits are located, refer to the following header: + + /* The secondary, or data, UID - should always be set. */ + /* + BYTE m_rgbUid[16]; + /* The primary UID - this defaults to 0, in which case the primary ID is + that of the internal data. NOTE!: The primary UID is only saved to disk + if (blip_instance ^ blip_signature == 1). Blip_instance is MSOFBH.finst and + blip_signature is one of the values defined in MSOBI + */ + /* + BYTE m_rgbUidPrimary[16]; // optional based on the above check + BYTE m_bTag; + void *m_pvBits; // raster bits of the blip. + */ + + + // 20080226 KSC: instead of handling only two image types, handle them all :) + var inst1 = MSODrawingConstants.msobiJPEG +when (imageType) { +MSODrawingConstants.IMAGE_TYPE_GIF -> {} +MSODrawingConstants.IMAGE_TYPE_EMF -> inst1 = MSODrawingConstants.msobiEMF +MSODrawingConstants.IMAGE_TYPE_WMF -> inst1 = MSODrawingConstants.msobiWMF +MSODrawingConstants.IMAGE_TYPE_PICT -> inst1 = MSODrawingConstants.msobiPICT +MSODrawingConstants.IMAGE_TYPE_JPG -> inst1 = MSODrawingConstants.msobiJPEG +MSODrawingConstants.IMAGE_TYPE_PNG -> inst1 = MSODrawingConstants.msobiPNG +MSODrawingConstants.IMAGE_TYPE_DIB -> inst1 = MSODrawingConstants.msobiDIB +}// TODO: ??? + //int inst1=(imageType==5)?MsodrawingConstants.msobiJPEG:MsodrawingConstants.msobiPNG; + val version1 = 0 +val fbt1 = MSODrawingConstants.msofbtBlipFirst + imageType +val len1 = imageData!!.size + 17 +imgHeader[36] = (((0x0F and inst1) shl 4) or (0x0F and version1)).toByte() +imgHeader[37] = ((0xFF0 and inst1) shr 4).toByte() +imgHeader[38] = ((0xFF and fbt1)).toByte() +imgHeader[39] = ((0xFF00 and fbt1) shr 8).toByte() + + //length with size + 25; + tempBytes = ByteTools.cLongToLEBytes(len1) +imgHeader[40] = tempBytes[0] +imgHeader[41] = tempBytes[1] +imgHeader[42] = tempBytes[2] +imgHeader[43] = tempBytes[3] + + ///Here will again be the md4 digest of the image data 44-60 + System.arraycopy(digest, 0, imgHeader, 44, 16) +imgHeader[60] = 0xff.toByte() //marker to indicate start of image data +val len = 61 +val retData = ByteArray(len + imageData!!.size) + +System.arraycopy(imgHeader, 0, retData, 0, len) +System.arraycopy(imageData!!, 0, retData, len, imageData!!.size) + +length = retData.size +return retData +} + + fun setImageData(d:ByteArray) { +imageData = d +} + +companion object { + +/** + * serialVersionUID + */ + private val serialVersionUID = -9072016434347371029L +} + +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.kt b/src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.kt new file mode 100644 index 0000000..c11b009 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtBstoreContainer.kt @@ -0,0 +1,41 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +//0xf001 +class MsofbtBstoreContainer(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + + override fun getData(): ByteArray { + + return ByteArray(0) + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -8979132545323492655L + } + +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.java b/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.java deleted file mode 100644 index 6e5abed..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -import io.starter.toolkit.ByteTools; - -import java.io.ByteArrayOutputStream; - -//0xf010 -public class MsofbtClientAnchor extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 7946989934447191433L; - // 20070914 KSC: this record doesn't store sheet index; 1st two bytes are a flag (seems always to be 2) - short flag = 2, leftColumnIndex, xOffsetL, topRowIndex, yOffsetT, rightColIndex, xOffsetR, bottomRowIndex, yOffsetB; - - public MsofbtClientAnchor(int fbt, int inst, int version) { - super(fbt, inst, version); - } - - public byte[] getData() { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try { - bos.write(ByteTools.shortToLEBytes(flag)); - bos.write(ByteTools.shortToLEBytes(leftColumnIndex)); - bos.write(ByteTools.shortToLEBytes(xOffsetL)); - bos.write(ByteTools.shortToLEBytes(topRowIndex)); - bos.write(ByteTools.shortToLEBytes(yOffsetT)); - bos.write(ByteTools.shortToLEBytes(rightColIndex)); - bos.write(ByteTools.shortToLEBytes(xOffsetR)); - bos.write(ByteTools.shortToLEBytes(bottomRowIndex)); - bos.write(ByteTools.shortToLEBytes(yOffsetB)); - } catch (Exception e) { - - } - this.setLength(bos.toByteArray().length); - return bos.toByteArray(); - - } - - public void setBounds(short[] bounds) { - if (bounds == null) return; - leftColumnIndex = bounds[0]; - xOffsetL = bounds[1]; - topRowIndex = bounds[2]; - yOffsetT = bounds[3]; - rightColIndex = bounds[4]; - xOffsetR = bounds[5]; - bottomRowIndex = bounds[6]; - yOffsetB = bounds[7]; - - } - - public void setFlag(short flag) { - this.flag = flag; - } - -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.kt b/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.kt new file mode 100644 index 0000000..6946c6b --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtClientAnchor.kt @@ -0,0 +1,87 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +import io.starter.toolkit.ByteTools + +import java.io.ByteArrayOutputStream + +//0xf010 +class MsofbtClientAnchor(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + // 20070914 KSC: this record doesn't store sheet index; 1st two bytes are a flag (seems always to be 2) + internal var flag: Short = 2 + internal var leftColumnIndex: Short = 0 + internal var xOffsetL: Short = 0 + internal var topRowIndex: Short = 0 + internal var yOffsetT: Short = 0 + internal var rightColIndex: Short = 0 + internal var xOffsetR: Short = 0 + internal var bottomRowIndex: Short = 0 + internal var yOffsetB: Short = 0 + + public override fun getData(): ByteArray { + val bos = ByteArrayOutputStream() + try { + bos.write(ByteTools.shortToLEBytes(flag)) + bos.write(ByteTools.shortToLEBytes(leftColumnIndex)) + bos.write(ByteTools.shortToLEBytes(xOffsetL)) + bos.write(ByteTools.shortToLEBytes(topRowIndex)) + bos.write(ByteTools.shortToLEBytes(yOffsetT)) + bos.write(ByteTools.shortToLEBytes(rightColIndex)) + bos.write(ByteTools.shortToLEBytes(xOffsetR)) + bos.write(ByteTools.shortToLEBytes(bottomRowIndex)) + bos.write(ByteTools.shortToLEBytes(yOffsetB)) + } catch (e: Exception) { + + } + + this.length = bos.toByteArray().size + return bos.toByteArray() + + } + + fun setBounds(bounds: ShortArray?) { + if (bounds == null) return + leftColumnIndex = bounds[0] + xOffsetL = bounds[1] + topRowIndex = bounds[2] + yOffsetT = bounds[3] + rightColIndex = bounds[4] + xOffsetR = bounds[5] + bottomRowIndex = bounds[6] + yOffsetB = bounds[7] + + } + + fun setFlag(flag: Short) { + this.flag = flag + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 7946989934447191433L + } + +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtClientData.java b/src/main/java/io/starter/formats/escher/MsofbtClientData.kt similarity index 69% rename from src/main/java/io/starter/formats/escher/MsofbtClientData.java rename to src/main/java/io/starter/formats/escher/MsofbtClientData.kt index 1aebc02..cbc95b6 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtClientData.java +++ b/src/main/java/io/starter/formats/escher/MsofbtClientData.kt @@ -20,21 +20,20 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.escher; +package io.starter.formats.escher //0xf011 -public class MsofbtClientData extends EscherRecord { +class MsofbtClientData(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -937992933535504843L; - - public MsofbtClientData(int fbt, int inst, int version) { - super(fbt, inst, version); + public override fun getData(): ByteArray { + return ByteArray(0) } - public byte[] getData() { - return new byte[0]; + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -937992933535504843L } } diff --git a/src/main/java/io/starter/formats/escher/MsofbtDg.java b/src/main/java/io/starter/formats/escher/MsofbtDg.java deleted file mode 100644 index 54133cd..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtDg.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -import io.starter.toolkit.ByteTools; - - -//0xF008 -public class MsofbtDg extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5218802290529676567L; - int csp, lastSPID; - - public MsofbtDg(int fbt, int inst, int version) { - super(fbt, inst, version); - } - - protected byte[] getData() { - byte[] cspBytes, spIdBytes; - - cspBytes = ByteTools.cLongToLEBytes(csp); // Number of shapes - spIdBytes = ByteTools.cLongToLEBytes(lastSPID); // last SPID - - byte[] retBytes = new byte[8]; - System.arraycopy(cspBytes, 0, retBytes, 0, 4); - System.arraycopy(spIdBytes, 0, retBytes, 4, 4); - - this.setLength(retBytes.length); - return retBytes; - } - - public void setNumShapes(int value) { - csp = value; - } - - public void setLastSPID(int value) { - - lastSPID = value; - } -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtDg.kt b/src/main/java/io/starter/formats/escher/MsofbtDg.kt new file mode 100644 index 0000000..f947282 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtDg.kt @@ -0,0 +1,63 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +import io.starter.toolkit.ByteTools + + +//0xF008 +class MsofbtDg(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + internal var csp: Int = 0 + internal var lastSPID: Int = 0 + + override fun getData(): ByteArray { + val cspBytes: ByteArray + val spIdBytes: ByteArray + + cspBytes = ByteTools.cLongToLEBytes(csp) // Number of shapes + spIdBytes = ByteTools.cLongToLEBytes(lastSPID) // last SPID + + val retBytes = ByteArray(8) + System.arraycopy(cspBytes, 0, retBytes, 0, 4) + System.arraycopy(spIdBytes, 0, retBytes, 4, 4) + + this.length = retBytes.size + return retBytes + } + + fun setNumShapes(value: Int) { + csp = value + } + + fun setLastSPID(value: Int) { + + lastSPID = value + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5218802290529676567L + } +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtDgContainer.java b/src/main/java/io/starter/formats/escher/MsofbtDgContainer.java deleted file mode 100644 index d8fd643..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtDgContainer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -//0xF002 -public class MsofbtDgContainer extends EscherRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5481466773321268095L; - - public MsofbtDgContainer(int fbt, int inst, int version) { - super(fbt, inst, version); - } - - protected byte[] getData() { - return new byte[0]; - } - -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtDgContainer.kt b/src/main/java/io/starter/formats/escher/MsofbtDgContainer.kt new file mode 100644 index 0000000..3e76c17 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtDgContainer.kt @@ -0,0 +1,40 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +//0xF002 +class MsofbtDgContainer(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + + override fun getData(): ByteArray { + return ByteArray(0) + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -5481466773321268095L + } + +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtDgg.java b/src/main/java/io/starter/formats/escher/MsofbtDgg.java deleted file mode 100644 index 687ed11..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtDgg.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -import io.starter.toolkit.ByteTools; - -//0xF006 -public class MsofbtDgg extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -7933328640935994167L; - int spidMax = 1024, numIdClusters, numShapes, numDrawings; - /* 20071115 KSC: Unused at this point IdClusters[] clusters = new IdClusters[1]; - - public class IdClusters{ - public int dgid,cspidCur; - } - */ - - public MsofbtDgg(int fbt, int inst, int version) { - super(fbt, inst, version); - - /* 20071115 KSC: THIS IS UNUSED RIGHT NOW - //Data from experimental results - this.clusters = new IdClusters[1]; - this.clusters[0] = new IdClusters(); - this.clusters[0].dgid = 1; - this.clusters[0].cspidCur = 4; - */ - } - - protected byte[] getData() { - byte[] spidMaxBytes, cidclBytes, cspSavedBytes, cdgSavedBytes; -// spidMaxBytes = ByteTools.cLongToLEBytes(spidMax+numShapes); 20071113 KSC: can't assume this - spidMaxBytes = ByteTools.cLongToLEBytes(spidMax); - numIdClusters = (spidMax / 1024) + ((spidMax % 1024 != 0) ? 1 : 0); // 20080903 KSC: # id clusters is based upon # shapes used - cidclBytes = ByteTools.cLongToLEBytes(numIdClusters); - cspSavedBytes = ByteTools.cLongToLEBytes(numShapes); - cdgSavedBytes = ByteTools.cLongToLEBytes(numDrawings); - - // new code - int lenOfFIDCL = numIdClusters - 1; - byte[] retBytes = new byte[16 + lenOfFIDCL * 8]; // HOLDS initial info plus FIDCL array of 8 bytes*numIdClusters - System.arraycopy(spidMaxBytes, 0, retBytes, 0, 4); - System.arraycopy(cidclBytes, 0, retBytes, 4, 4); - System.arraycopy(cspSavedBytes, 0, retBytes, 8, 4); - System.arraycopy(cdgSavedBytes, 0, retBytes, 12, 4); - int pos = 16; - - /* 20071120 KSC: new code */ - //20071115 KSC: try to interpret -- these changes seems correct in byte comparisons ... but unsure if it's correct for all cases - for (int i = 0; i < lenOfFIDCL; i++) { - System.arraycopy(ByteTools.cLongToLEBytes(1), 0, retBytes, pos, 4); // dgid owning the SPID's in this cluster - System.arraycopy(ByteTools.cLongToLEBytes(((i == 0) ? (numShapes) : 1)), 0, retBytes, pos + 4, 4); // # SPID's used so far - pos += 8; - } - - /* old code - byte[] b1,b2; - - b1 = ByteTools.cLongToLEBytes(1); - System.arraycopy(b1, 0, retBytes, pos, 4); - b2 = ByteTools.cLongToLEBytes(numShapes); //TODO: Can you find any logic for this?? -- No!! remove!! - System.arraycopy(b2, 0, retBytes, pos+4, 4); - pos+=8; - */ - this.setLength(retBytes.length); - return retBytes; - } - - - /* 20071115 KSC: THIS IS UNUSED RIGHT NOW - public void setIdClusters(IdClusters[] c){ - clusters = c; - } - */ - - public int getNumDrawings() { - return numDrawings; - } - - public void setNumDrawings(int numDrawings) { - this.numDrawings = numDrawings; - } - - public int getNumIdClusters() { - return numIdClusters; - } -/* 20080903 KSC: numIdClusters is soley a function of spidMax/1024 - public void setNumIdClusters(int numIdClusters) { - this.numIdClusters = numIdClusters; - } -*/ - - public int getNumShapes() { - return numShapes; - } - - public void setNumShapes(int numShapes) { - this.numShapes = numShapes; - } - - // 20071113 KSC - - /** - * set SpidMax - */ - public void setSpidMax(int spid) { - this.spidMax = spid; - } -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtDgg.kt b/src/main/java/io/starter/formats/escher/MsofbtDgg.kt new file mode 100644 index 0000000..a71e823 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtDgg.kt @@ -0,0 +1,117 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +import io.starter.toolkit.ByteTools + +//0xF006 +class MsofbtDgg +/* 20071115 KSC: Unused at this point IdClusters[] clusters = new IdClusters[1]; + + public class IdClusters{ + public int dgid,cspidCur; + } + */ +(fbt: Int, inst: Int, version: Int)/* 20071115 KSC: THIS IS UNUSED RIGHT NOW + //Data from experimental results + this.clusters = new IdClusters[1]; + this.clusters[0] = new IdClusters(); + this.clusters[0].dgid = 1; + this.clusters[0].cspidCur = 4; + */ : EscherRecord(fbt, inst, version) { + internal var spidMax = 1024 + var numIdClusters: Int = 0 + internal set + /* 20080903 KSC: numIdClusters is soley a function of spidMax/1024 + public void setNumIdClusters(int numIdClusters) { + this.numIdClusters = numIdClusters; + } +*/ + + var numShapes: Int = 0 + /* 20071115 KSC: THIS IS UNUSED RIGHT NOW + public void setIdClusters(IdClusters[] c){ + clusters = c; + } + */ + + var numDrawings: Int = 0 + + override fun getData(): ByteArray { + val spidMaxBytes: ByteArray + val cidclBytes: ByteArray + val cspSavedBytes: ByteArray + val cdgSavedBytes: ByteArray + // spidMaxBytes = ByteTools.cLongToLEBytes(spidMax+numShapes); 20071113 KSC: can't assume this + spidMaxBytes = ByteTools.cLongToLEBytes(spidMax) + numIdClusters = spidMax / 1024 + if (spidMax % 1024 != 0) 1 else 0 // 20080903 KSC: # id clusters is based upon # shapes used + cidclBytes = ByteTools.cLongToLEBytes(numIdClusters) + cspSavedBytes = ByteTools.cLongToLEBytes(numShapes) + cdgSavedBytes = ByteTools.cLongToLEBytes(numDrawings) + + // new code + val lenOfFIDCL = numIdClusters - 1 + val retBytes = ByteArray(16 + lenOfFIDCL * 8) // HOLDS initial info plus FIDCL array of 8 bytes*numIdClusters + System.arraycopy(spidMaxBytes, 0, retBytes, 0, 4) + System.arraycopy(cidclBytes, 0, retBytes, 4, 4) + System.arraycopy(cspSavedBytes, 0, retBytes, 8, 4) + System.arraycopy(cdgSavedBytes, 0, retBytes, 12, 4) + var pos = 16 + + /* 20071120 KSC: new code */ + //20071115 KSC: try to interpret -- these changes seems correct in byte comparisons ... but unsure if it's correct for all cases + for (i in 0 until lenOfFIDCL) { + System.arraycopy(ByteTools.cLongToLEBytes(1), 0, retBytes, pos, 4) // dgid owning the SPID's in this cluster + System.arraycopy(ByteTools.cLongToLEBytes(if (i == 0) numShapes else 1), 0, retBytes, pos + 4, 4) // # SPID's used so far + pos += 8 + } + + /* old code + byte[] b1,b2; + + b1 = ByteTools.cLongToLEBytes(1); + System.arraycopy(b1, 0, retBytes, pos, 4); + b2 = ByteTools.cLongToLEBytes(numShapes); //TODO: Can you find any logic for this?? -- No!! remove!! + System.arraycopy(b2, 0, retBytes, pos+4, 4); + pos+=8; + */ + this.length = retBytes.size + return retBytes + } + + // 20071113 KSC + + /** + * set SpidMax + */ + fun setSpidMax(spid: Int) { + this.spidMax = spid + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -7933328640935994167L + } +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtDggContainer.kt b/src/main/java/io/starter/formats/escher/MsofbtDggContainer.kt new file mode 100644 index 0000000..9d23958 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtDggContainer.kt @@ -0,0 +1,41 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +//0xf000 +class MsofbtDggContainer(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + + override fun getData(): ByteArray { + + return ByteArray(0) + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = -2332486523666942739L + } + +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtOPT.java b/src/main/java/io/starter/formats/escher/MsofbtOPT.java deleted file mode 100644 index 9d5c71a..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtOPT.java +++ /dev/null @@ -1,549 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -import io.starter.OpenXLS.FormatHandle; -import io.starter.formats.XLS.FormatConstants; -import io.starter.formats.XLS.MSODrawingConstants; -import io.starter.formats.XLS.XLSConstants; -import io.starter.toolkit.ByteTools; -import io.starter.toolkit.Logger; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedHashMap; -//0xf00b - -/** - * shape properties table - */ -public class MsofbtOPT extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 465530579513265882L; - byte[] recordData = new byte[0]; - boolean bBackground, bActive, bPrint; - int imageIndex = -1; - java.awt.Color fillColor = null; - int fillType = 0; - String imageName = "", shapeName = "", alternateText = ""; - int[] lineprops = null; // Line properties -- weight, color, style ... - static final int LINEPROPTS_STYLE = 0; - static final int LINEPROPTS_WEIGHT = 1; - static final int LINEPROPTS_COLOR = 2; - boolean hasTextId = false; // true if OPT contains msofbtlTxid - necessary to calculate container lengths correctly - see MSODrawing.updateRecord - LinkedHashMap props = new LinkedHashMap(); // parsed property table note: properties are ordered via property set; - - public MsofbtOPT(int fbt, int inst, int version) { - super(fbt, inst, version); - } - - public void setImageIndex(int value) { - imageIndex = value; - if (imageIndex > -1) - setProperty(MSODrawingConstants.msooptpib, true, false, value, null); - else // remove property - props.remove(Integer.valueOf(MSODrawingConstants.msooptpib)); - - } - - /** - * returns true if this OPT subrecord contains an msofbtlTxid entry- - * necessary to calculate container lengths correctly - see MSODrawing.updateRecord - * - * @return - */ - public boolean hasTextId() { - return hasTextId; - } - - /** - * set the image name for this shape - * msooptpibName - * - * @param name - */ - public void setImageName(String name) { - try { - imageName = name; - if (imageName == null || imageName.equals("")) { - // remove property - props.remove(Integer.valueOf(MSODrawingConstants.msooptpibName)); - return; - } - byte[] imageNameBytes = name.getBytes(XLSConstants.UNICODEENCODING); - byte[] newbytes = new byte[imageNameBytes.length]; - System.arraycopy(imageNameBytes, 0, newbytes, 0, imageNameBytes.length); - imageNameBytes = newbytes; - setProperty(MSODrawingConstants.msooptpibName, true, true, imageNameBytes.length, imageNameBytes); - } catch (Exception e) { - Logger.logErr("Msofbt.setImageName failed.", e); - } - } - - /** - * set the shape name atom in this OPT record - * - * @param name - */ - public void setShapeName(String name) { - try { - shapeName = name; - if (shapeName == null || shapeName.equals("")) { - // remove property - props.remove(Integer.valueOf(MSODrawingConstants.msooptwzName)); - return; - } - byte[] shapeNameBytes = name.getBytes(XLSConstants.UNICODEENCODING); - byte[] newbytes = new byte[shapeNameBytes.length]; - System.arraycopy(shapeNameBytes, 0, newbytes, 0, shapeNameBytes.length); - shapeNameBytes = newbytes; - setProperty(MSODrawingConstants.msooptwzName, true, true, shapeNameBytes.length, shapeNameBytes); - } catch (Exception e) { - Logger.logErr("Msofbt.setShapeName failed.", e); - } - } - - /** - * generate the recordData from the stored props hashmap if anything has changed - */ - protected byte[] getData() { - if (isDirty) { // regenerate recordData as contents have changed - byte[] tmp = new byte[inst * 6]; // basic property table - byte[] complexData = new byte[0]; // extra complex data, if any, after basic property table - int pos = 0; - // try to extract properties in order - java.util.ArrayList keys = new java.util.ArrayList(props.keySet()); - // try in numerical order-- appears to MOSTLY be the case ... - Object[] k = keys.toArray(); - Arrays.sort(k); - - // write out properties in (numerical) order - for (int i = 0; i < k.length; i++) { - Integer propId = ((Integer) k[i]); - Object[] o = (Object[]) props.get(propId); - boolean isComplex = ((Boolean) o[0]).booleanValue(); - boolean isBid = ((Boolean) o[2]).booleanValue(); - int flag = 0; - if (isComplex) flag = flag | 0x80; - if (isBid) flag = flag | 0x40; - int dtx; - if (!isComplex) - dtx = ((Integer) o[1]).intValue(); // non-complex data is just an integer - else { - dtx = ((byte[]) o[1]).length + 2; // stored data is a byte array; get length + 2 - complexData = ByteTools.append((byte[]) o[1], complexData); - complexData = ByteTools.append(new byte[]{0, 0}, complexData); - } - // the basic part of the property table - tmp[pos++] = ((byte) (0xFF & propId.intValue())); - tmp[pos++] = (byte) (flag | (0x3F00 & propId.intValue()) >> 8); - byte[] dtxBytes = ByteTools.cLongToLEBytes(dtx); - System.arraycopy(dtxBytes, 0, tmp, pos, 4); - pos += 4; - } - recordData = new byte[tmp.length + complexData.length]; - System.arraycopy(tmp, 0, recordData, 0, tmp.length); - // after the basic property table (PropID, IsBID, IsCOMPEX, dtx), store the complex data - System.arraycopy(complexData, 0, recordData, tmp.length, complexData.length); - isDirty = false; - } - this.setLength(recordData.length); - return recordData; - } - - public void setData(byte[] b) { - recordData = b; - props.clear(); - imageIndex = -1; - imageName = ""; - shapeName = ""; - alternateText = ""; - parseData(); - } - - /** - * given property table bytes, parse into props hashmap - */ - private void parseData() { - /* - * First part of an OPT record is an array of FOPTEs (propertyId, fBid, fComplex, data) - * If fComplex is set, the actual data (Unicode strings, arrays, etc.) is stored AFTER the last FOPTE (sorted by property id???); - * the length of the complex data is stored in the data field. - * if fComplex is not set, the meaining of the data field is dependent upon the propertyId - * if fBid is set and fComplex is not set, the data = a BLIP id (= an index into the BLIP store) - * The number of FOPTES is the inst field read above - */ - int propertyId, fBid, fComplex; - //int n= inst; // number of properties to parse - int pos = 0; // pointer to current property in data/property table - if (inst == 0 && recordData.length > 0) { // called from GelFrame ... - byte[] dat = new byte[8]; // read header - System.arraycopy(recordData, 0, dat, 0, 8); - version = (0xF & dat[0]); // 15 for containers, version for atoms - inst = ((0xFF & dat[1]) << 4) | (0xF0 & dat[0]) >> 4; - fbt = ((0xFF & dat[3]) << 8) | (0xFF & dat[2]); // record type id==0xF00B - pos = 8; // skip header - } - for (int i = 0; i < inst; i++) { - propertyId = (0x3F & recordData[pos + 1]) << 8 | (0xFF & recordData[pos]); // 14 bits - fBid = ((0x40 & recordData[pos + 1]) >> 6); // specifies whether the value in the dtx field is a BLIP identifier- only valid if fComplex= FALSE - fComplex = ((0x80 & recordData[pos + 1]) >> 7); // if complex property, value is length. Data is parsed after. - int dtx = ByteTools.readInt(recordData[pos + 2], recordData[pos + 3], recordData[pos + 4], recordData[pos + 5]); - // TODO: if property number is of type bool/long/msoarray/... parse accordingly .. - if (propertyId == MSODrawingConstants.msooptpib) // blip to display - imageIndex = dtx; - else if (propertyId == MSODrawingConstants.msooptFillType) { - fillType = dtx; - } else if (propertyId == MSODrawingConstants.msooptfillColor) - fillColor = setFillColor(dtx); - else if (propertyId == MSODrawingConstants.msooptfBackground) - bBackground = (dtx != 0); - else if (propertyId == MSODrawingConstants.msooptGroupShapeProperties) { - //bPrint= (dtx!=0); // NOT TRUE!! TODO: parse real GroupShapeProperties (many) - } else if (propertyId == MSODrawingConstants.msooptpictureActive) - bActive = (dtx != 0); - else if (propertyId == MSODrawingConstants.msooptlineWidth) { // appears that this controls display of line - if (lineprops == null) lineprops = new int[3]; - lineprops[LINEPROPTS_WEIGHT] = dtx; - } else if (propertyId == MSODrawingConstants.msooptlineColor) { // appears that this is always present, even if no line - if (lineprops == null) lineprops = new int[3]; - lineprops[LINEPROPTS_COLOR] = dtx; - } else if (propertyId == MSODrawingConstants.msooptLineStyle) { - if (lineprops == null) lineprops = new int[3]; - lineprops[LINEPROPTS_STYLE] = dtx; - } else if (propertyId == MSODrawingConstants.msofbtlTxid) { - hasTextId = true; - } // msooptFillWidth - props.put(Integer.valueOf(propertyId), new Object[]{Boolean.valueOf(fComplex != 0), Integer.valueOf(dtx), Boolean.valueOf(fBid != 0)}); - pos += 6; - } - - // now parse complex data after all "tightly packed" properties have been parsed. Order of data is original order - Iterator ii = props.keySet().iterator(); - while (ii.hasNext()) { - Integer propId = (Integer) ii.next(); - Object[] o = (Object[]) props.get(propId); // Object[]: 0= isComplex, 1= dtx (value or len of complex data -- filled in below), 2= isBid - if (((Boolean) o[0]).booleanValue()) { - int len = ((Integer) o[1]).intValue(); - if (len >= 2) { - // apparently each record is delimited by a double byte 0 -- so decrement by 2 here and increment pos by 2 below - byte[] complexdata = new byte[len - 2]; // retrieve complex data at end of record - System.arraycopy(recordData, pos, complexdata, 0, complexdata.length); // get property data after main property table - props.put(propId, new Object[]{o[0], complexdata, o[2]}); //store complex data for later retrieval - if (propId.intValue() == MSODrawingConstants.msooptpibName) { // = image name - try { - imageName = new String(complexdata, XLSConstants.UNICODEENCODING); - } catch (Exception e) { - imageName = "Unnamed"; - } - } else if (propId.intValue() == MSODrawingConstants.msooptwzName) { // = shape name - try { - shapeName = new String(complexdata, XLSConstants.UNICODEENCODING); - } catch (Exception e) { - } - } else if (propId.intValue() == MSODrawingConstants.msooptwzDescription) { // = Alternate Text - try { - alternateText = new String(complexdata, XLSConstants.UNICODEENCODING); - } catch (Exception e) { - } - } else if (propId.intValue() == MSODrawingConstants.msooptFillBlipName) { // = the comment, file name, or the full URL that is used as a fill - try { - String fillName = new String(complexdata, XLSConstants.UNICODEENCODING); - } catch (Exception e) { - } - } - - pos += complexdata.length + 2; - } - } - } - } - - /** - * @param propId msofbtopt property ide see Msoconstants - * @param isBid value is a BLIP id - only valid if isComplex is false - * @param isComplex if false, dtx is used; if true, complexBytes are used and dtx=length - * @param dtx if not iscomplex, the value of property id; if iscomplex, length of complex data following the property table - * @param complexBytes if iscomplex, holds value of complex property e.g. shape name - */ - public void setProperty(int propId, boolean isBid, boolean isComplex, int dtx, byte[] complexBytes) { - // a general order of common properties is (via property id): - /* - * 127 - * 267 - * 261 - * 262 - * 128 - * 133 - * 139 - * 191 - * 385 - * 447 - * 448 - * 459 - * 511 - */ - if (isComplex) // complexBytes shouldn't be null - props.put(Integer.valueOf(propId), new Object[]{Boolean.valueOf(isComplex), complexBytes, Boolean.valueOf(isBid)}); - else - props.put(Integer.valueOf(propId), new Object[]{Boolean.valueOf(isComplex), Integer.valueOf(dtx), Boolean.valueOf(isBid)}); - - this.inst = props.size(); - isDirty = true; // flag to regenerate recordData - } - - public boolean hasBorder() { - return lineprops != null && lineprops[LINEPROPTS_WEIGHT] > 1; - } - - public int getBorderLineWidth() { - if (lineprops != null) - return lineprops[LINEPROPTS_WEIGHT]; - return -1; - } - - public int getImageIndex() { - return imageIndex; - } - - public String getImageName() { - return imageName; - } - - public String getShapeName() { - return shapeName; - } - - /** - * Debug Output -- For Internal Use Only - */ - public String debugOutput() { - int propertyId; - StringBuffer log = new StringBuffer(); -/* java.util.ArrayList keys= new java.util.ArrayList(props.keySet()); - int n= keys.size(); - for (int i= 0; i < n; i++) { - log.append("\r\n"); - propertyId = ((Integer)keys.get(i)).intValue(); // (0x3F&recordData[pos+1])<<8|(0xFF&recordData[pos]); - Object[] o= (Object[]) props.get(keys.get(i)); - boolean isComplex= ((Boolean)o[0]).booleanValue(); - boolean isBid= ((Boolean)o[2]).booleanValue(); - if (isComplex) fComplex=1; - if (isBid) fBid= 1; - int dtx; - if (!isComplex) - dtx= ((Integer)o[1]).intValue(); // non-complex data is just an integer - else - dtx= ((byte[])o[1]).length + 2; // stored data is a byte array; get length + 2 - //fBid = ((0x40&recordData[pos+1])>>6); // value is a BLIP ID - only valid if fComplex= FALSE - //fComplex = ((0x80&recordData[pos+1])>>7); // if complex property, value is length. Data is parsed after. - //int dtx = ByteTools.readInt(recordData[pos+2],recordData[pos+3],recordData[pos+4],recordData[pos+5]); - log.append("\t\t" + propertyId + "/" + fBid + "/" + fComplex + "/" + dtx); - //pos+=6; - } -*/ - int n = inst; // number of properties to parse - // pointer to current property in data/property table - int fBid = 0, fComplex = 0; - int end = recordData.length; - for (int pos = 0; pos < end; pos += 6) { - propertyId = (0x3F & recordData[pos + 1]) << 8 | (0xFF & recordData[pos]); - fBid = ((0x40 & recordData[pos + 1]) >> 6); - fComplex = ((0x80 & recordData[pos + 1]) >> 7); // if complex property, value is length. Data is parsed after. - int dtx = ByteTools.readInt(recordData[pos + 2], recordData[pos + 3], recordData[pos + 4], recordData[pos + 5]); - if (fComplex != 0) - end -= dtx; - log.append("\t\t" + propertyId + "/" + fBid + "/" + fComplex + "/" + dtx); - } - return log.toString(); - } - - /** - * Interpret an OfficeArtCOLORREF (used in fillColor, lineColor and many other opts) - * - * @param clrStructure
                      More information - * The OfficeArtCOLORREF structure specifies a color. The high 8 bits MAY be set to 0xFF, in which case the color MUST be ignored. - * The color properties that are specified in the following table have a set of extended-color properties. The color property specifies the main color. - * The colorExt and colorExtMod properties specify the extended colors that can be used to define the main color more precisely. - * If neither extended-color property is set, the main color property contains the full color definition. - * Otherwise, the colorExt property specifies the base color, and the colorExtMod property specifies a tint or shade modification that is applied to the colorExt property. - * In this case, the main color property contains the flattened RGB color that is computed by applying the specified tint or shade modification to the specified base color. - *

                      - *

                      - * A - unused1 (1 bit): A bit that is undefined and MUST be ignored. - *

                      - * B - unused2 (1 bit): A bit that is undefined and MUST be ignored. - *

                      - * C - unused3 (1 bit): A bit that is undefined and MUST be ignored. - *

                      - * D - fSysIndex (1 bit): A bit that specifies whether the system color scheme will be used to determine the color. - * A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index into the system color table. Values less than 0x00F0 map directly to system colors. - * For more information, see [MSDN-GetSysColor] (below) - * The following table specifies values that have special meaning. - * Value Meaning - * 0x00F0 Use the fill color of the shape. - * 0x00F1 If the shape contains a line, use the line color of the shape. Otherwise, use the fill color. - * 0x00F2 Use the line color of the shape. - * 0x00F3 Use the shadow color of the shape. - * 0x00F4 Use the current, or last-used, color. - * 0x00F5 Use the fill background color of the shape. - * 0x00F6 Use the line background color of the shape. - * 0x00F7 If the shape contains a fill, use the fill color of the shape. Otherwise, use the line color. - * The following table specifies values that indicate special procedural properties that are used to modify the color components of another color. - * These values are combined with those in the preceding table or with a user-specified color. The first six values are mutually exclusive. - * Value Meaning - * 0x0100 Darken the color by the value that is specified in the blue field. A blue value of 0xFF specifies that the color is to be left unchanged, whereas a blue value of 0x00 specifies that the color is to be completely darkened. - * 0x0200 Lighten the color by the value that is specified in the blue field. A blue value of 0xFF specifies that the color is to be left unchanged, whereas a blue value of 0x00 specifies that the color is to be completely lightened. - * 0x0300 Add a gray level RGB value. The blue field contains the gray level to add: NewColor = SourceColor + gray - * 0x0400 Subtract a gray level RGB value. The blue field contains the gray level to subtract: NewColor = SourceColor - gray - * 0x0500 Reverse-subtract a gray level RGB value. The blue field contains the gray level from which to subtract: NewColor = gray - SourceColor - * 0x0600 If the color component being modified is less than the parameter contained in the blue field, set it to the minimum intensity. - * If the color component being modified is greater than or equal to the parameter, set it to the maximum intensity. - * 0x2000 After making other modifications, invert the color. - * 0x4000 After making other modifications, invert the color by toggling just the high bit of each color channel. - * 0x8000 Before making other modifications, convert the color to grayscale. - * E - fSchemeIndex (1 bit): A bit that specifies whether the current application-defined color scheme will be used to determine the color. - * A value of 0x1 specifies that red will be treated as an index into the current color scheme table. If this value is 0x1, green and blue MUST be 0x00. - * F - fSystemRGB (1 bit): A bit that specifies whether the color is a standard RGB color. The following table specifies the meaning of each value for this field. - * Value Meaning - * 0x0 The RGB color MAY use halftone dithering to display. - * 0x1 The color MUST be a solid color. - * G - fPaletteRGB (1 bit): A bit that specifies whether the current palette will be used to determine the color. - * A value of 0x1 specifies that red, green, and blue contain an RGB value that will be matched in the current color palette. This color MUST be solid. - * H - fPaletteIndex (1 bit): A bit that specifies whether the current palette will be used to determine the color. - * A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index into the current color palette. This color MAY<1> be dithered. - * If this value is 0x1, blue MUST be 0x00. - * blue (1 byte): An unsigned integer that specifies the intensity of the blue color channel. A value of 0x00 has the minimum blue intensity. A value of 0xFF has the maximum blue intensity. - * green (1 byte): An unsigned integer that specifies the intensity of the green color channel. A value of 0x00 has the minimum green intensity. A value of 0xFF has the maximum green intensity. - * red (1 byte): An unsigned integer that specifies the intensity of the red color channel. A value of 0x00 has the minimum red intensity. A value of 0xFF has the maximum red intensity. - *

                      - * ... - *

                      - * MSDN-GetSysColor - * Value Meaning - * COLOR_3DDKSHADOW 21 Dark shadow for three-dimensional display elements. - * COLOR_3DFACE 15 Face color for three-dimensional display elements and for dialog box backgrounds. - * COLOR_3DHIGHLIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) - * COLOR_3DHILIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) - * COLOR_3DLIGHT 22 Light color for three-dimensional display elements (for edges facing the light source.) - * COLOR_3DSHADOW 16 Shadow color for three-dimensional display elements (for edges facing away from the light source). - * COLOR_ACTIVEBORDER 10 Active window border. - * COLOR_ACTIVECAPTION 2 Active window title bar. Specifies the left side color in the color gradient of an active window's title bar if the gradient effect is enabled. - * COLOR_APPWORKSPACE 12 Background color of multiple document interface (MDI) applications. - * COLOR_BACKGROUND 1 Desktop. - * COLOR_BTNFACE 15 Face color for three-dimensional display elements and for dialog box backgrounds. - * COLOR_BTNHIGHLIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) - * COLOR_BTNHILIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) - * COLOR_BTNSHADOW 16 Shadow color for three-dimensional display elements (for edges facing away from the light source). - * COLOR_BTNTEXT 18 Text on push buttons. - * COLOR_CAPTIONTEXT 9 Text in caption, size box, and scroll bar arrow box. - * COLOR_DESKTOP 1 Desktop. - * COLOR_GRADIENTACTIVECAPTION 27 Right side color in the color gradient of an active window's title bar. - * COLOR_ACTIVECAPTION specifies the left side color. Use SPI_GETGRADIENTCAPTIONS with the SystemParametersInfo function to determine whether the gradient effect is enabled. - * COLOR_GRADIENTINACTIVECAPTION 28 Right side color in the color gradient of an inactive window's title bar. COLOR_INACTIVECAPTION specifies the left side color. - * COLOR_GRAYTEXT 17 Grayed (disabled) text. This color is set to 0 if the current display driver does not support a solid gray color. - * COLOR_HIGHLIGHT 13 Item(s) selected in a control. - * COLOR_HIGHLIGHTTEXT 14 Text of item(s) selected in a control. - * COLOR_HOTLIGHT 26 Color for a hyperlink or hot-tracked item. - * COLOR_INACTIVEBORDER11 Inactive window border. - * COLOR_INACTIVECAPTION3 Inactive window caption. - * Specifies the left side color in the color gradient of an inactive window's title bar if the gradient effect is enabled. - * COLOR_INACTIVECAPTIONTEXT19 Color of text in an inactive caption. - * COLOR_INFOBK 24 Background color for tooltip controls. - * COLOR_INFOTEXT 23 Text color for tooltip controls. - * COLOR_MENU 4 Menu background. - * COLOR_MENUHILIGHT 29 The color used to highlight menu items when the menu appears as a flat menu (see SystemParametersInfo). The highlighted menu item is outlined with COLOR_HIGHLIGHT. - * Windows 2000: This value is not supported. - * COLOR_MENUBAR 30 The background color for the menu bar when menus appear as flat menus (see SystemParametersInfo). However, COLOR_MENU continues to specify the background color of the menu popup. - * Windows 2000: This value is not supported. - * COLOR_MENUTEXT 7 Text in menus. - * COLOR_SCROLLBAR 0 Scroll bar gray area. - * COLOR_WINDOW 5 Window background. - * COLOR_WINDOWFRAME 6 Window frame. - * COLOR_WINDOWTEXT 8 Text in windows. - */ - private java.awt.Color setFillColor(int clrStructure) { - byte[] b = ByteTools.longToByteArray(clrStructure); - boolean bPaletteIndex, bSchemeIndex, bSysIndex; - short fillclr; - - bPaletteIndex = (b[4] & 0x1) == 0x1; // specifies whether the current palette will be used to determine the color - bSchemeIndex = (b[4] & 0x8) == 0x8; // specifies whether the current application defined color scheme will be used to determine the color. - bSysIndex = (b[4] & 0x10) == 0x10; // specifies whether the system color scheme will be used to determine the color. - - if (bPaletteIndex) { // // GREEN and RED are treated as an unsigned 16-bit index into the current color palette. This color MAY be dithered. BLUE MUST be 0x00. - } - if (bSchemeIndex) { // RED is an index into the current scheme color table. GREEN and BLUE MUST be 0x00. - fillclr = b[7]; // what does 80 mean?????? - if (fillclr > FormatHandle.COLORTABLE.length) - fillclr = FormatHandle.interpretSpecialColorIndex(fillclr); - fillColor = FormatHandle.COLORTABLE[fillclr]; - return fillColor; - } - if (bSysIndex) { // GREEN and RED will be treated as an unsigned 16-bit index into the system color table. Values less than 0x00F0 map directly to system colors. - fillclr = ByteTools.readShort(b[6], b[7]); - if ((fillclr == 0x00F0) // Use the fill color of the shape. - || (fillclr == 0x00F1) //If the shape contains a line, use the line color of the shape. Otherwise, use the fill color. - || (fillclr == 0x00F2) //Use the line color of the shape. - || (fillclr == 0x00F3) //Use the shadow color of the shape. - || (fillclr == 0x00F4) //Use the current, or last-used, color. - || (fillclr == 0x00F5) //Use the fill background color of the shape. - || (fillclr == 0x00F6) //Use the line background color of the shape. - || (fillclr == 0x00F7)) //If the shape contains a fill, use the fill color of the shape. Otherwise, use the line color. - fillclr = FormatConstants.COLOR_WHITE; - if (fillclr == 0x40) // default fg color - fillclr = FormatConstants.COLOR_WHITE; - else if (fillclr == 0x41) // default bg color - fillclr = FormatConstants.COLOR_WHITE; - else if (fillclr == 0x4D) { // default CHART fg color -- INDEX SPECIFIC! - fillColor = null; // flag to map via series (bar) color defaults - return fillColor; - } else if (fillclr == 0x4E) // default CHART fg color - fillclr = FormatConstants.COLOR_WHITE; - else if (fillclr == 0x4F) // chart neutral color == black - fillclr = FormatConstants.COLOR_BLACK; - - if (fillclr < 0 || fillclr > FormatHandle.COLORTABLE.length) - fillclr = FormatConstants.COLOR_WHITE; - fillColor = FormatHandle.COLORTABLE[fillclr]; - return fillColor; - } - - // otherwise, r, g and blue are color values 0-255 - int bl = (b[5] < 0 ? 255 + b[5] : b[5]); - int g = (b[6] < 0 ? 255 + b[6] : b[6]); - int r = (b[7] < 0 ? 255 + b[7] : b[7]); - fillColor = new java.awt.Color(r, g, bl); - return fillColor; - } - - public java.awt.Color getFillColor() { - return fillColor; - } - - public int getFillType() { - return fillType; - } -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtOPT.kt b/src/main/java/io/starter/formats/escher/MsofbtOPT.kt new file mode 100644 index 0000000..116163a --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtOPT.kt @@ -0,0 +1,578 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +import io.starter.OpenXLS.FormatHandle +import io.starter.formats.XLS.FormatConstants +import io.starter.formats.XLS.MSODrawingConstants +import io.starter.formats.XLS.XLSConstants +import io.starter.toolkit.ByteTools +import io.starter.toolkit.Logger + +import java.util.Arrays +import java.util.LinkedHashMap + +//0xf00b + +/** + * shape properties table + */ +class MsofbtOPT(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + internal var recordData = ByteArray(0) + internal var bBackground: Boolean = false + internal var bActive: Boolean = false + internal var bPrint: Boolean = false + internal var imageIndex = -1 + var fillColor: java.awt.Color? = null + internal set + var fillType = 0 + internal set + internal var imageName: String? = "" + internal var shapeName: String? = "" + internal var alternateText = "" + internal var lineprops: IntArray? = null // Line properties -- weight, color, style ... + internal var hasTextId = false // true if OPT contains msofbtlTxid - necessary to calculate container lengths correctly - see MSODrawing.updateRecord + internal var props = LinkedHashMap() // parsed property table note: properties are ordered via property set; + + val borderLineWidth: Int + get() = if (lineprops != null) lineprops!![LINEPROPTS_WEIGHT] else -1 + + fun setImageIndex(value: Int) { + imageIndex = value + if (imageIndex > -1) + setProperty(MSODrawingConstants.msooptpib, true, false, value, null) + else + // remove property + props.remove(Integer.valueOf(MSODrawingConstants.msooptpib)) + + } + + /** + * returns true if this OPT subrecord contains an msofbtlTxid entry- + * necessary to calculate container lengths correctly - see MSODrawing.updateRecord + * + * @return + */ + fun hasTextId(): Boolean { + return hasTextId + } + + /** + * set the image name for this shape + * msooptpibName + * + * @param name + */ + fun setImageName(name: String) { + try { + imageName = name + if (imageName == null || imageName == "") { + // remove property + props.remove(Integer.valueOf(MSODrawingConstants.msooptpibName)) + return + } + var imageNameBytes = name.toByteArray(charset(XLSConstants.UNICODEENCODING)) + val newbytes = ByteArray(imageNameBytes.size) + System.arraycopy(imageNameBytes, 0, newbytes, 0, imageNameBytes.size) + imageNameBytes = newbytes + setProperty(MSODrawingConstants.msooptpibName, true, true, imageNameBytes.size, imageNameBytes) + } catch (e: Exception) { + Logger.logErr("Msofbt.setImageName failed.", e) + } + + } + + /** + * set the shape name atom in this OPT record + * + * @param name + */ + fun setShapeName(name: String) { + try { + shapeName = name + if (shapeName == null || shapeName == "") { + // remove property + props.remove(Integer.valueOf(MSODrawingConstants.msooptwzName)) + return + } + var shapeNameBytes = name.toByteArray(charset(XLSConstants.UNICODEENCODING)) + val newbytes = ByteArray(shapeNameBytes.size) + System.arraycopy(shapeNameBytes, 0, newbytes, 0, shapeNameBytes.size) + shapeNameBytes = newbytes + setProperty(MSODrawingConstants.msooptwzName, true, true, shapeNameBytes.size, shapeNameBytes) + } catch (e: Exception) { + Logger.logErr("Msofbt.setShapeName failed.", e) + } + + } + + /** + * generate the recordData from the stored props hashmap if anything has changed + */ + override fun getData(): ByteArray { + if (isDirty) { // regenerate recordData as contents have changed + val tmp = ByteArray(inst * 6) // basic property table + var complexData = ByteArray(0) // extra complex data, if any, after basic property table + var pos = 0 + // try to extract properties in order + val keys = java.util.ArrayList(props.keys) + // try in numerical order-- appears to MOSTLY be the case ... + val k = keys.toTypedArray() + Arrays.sort(k) + + // write out properties in (numerical) order + for (i in k.indices) { + val propId = k[i] as Int + val o = props.get(propId) as Array + val isComplex = (o[0] as Boolean).booleanValue() + val isBid = (o[2] as Boolean).booleanValue() + var flag = 0 + if (isComplex) flag = flag or 0x80 + if (isBid) flag = flag or 0x40 + val dtx: Int + if (!isComplex) + dtx = (o[1] as Int).toInt() // non-complex data is just an integer + else { + dtx = (o[1] as ByteArray).size + 2 // stored data is a byte array; get length + 2 + complexData = ByteTools.append(o[1] as ByteArray, complexData) + complexData = ByteTools.append(byteArrayOf(0, 0), complexData) + } + // the basic part of the property table + tmp[pos++] = (0xFF and propId).toByte() + tmp[pos++] = (flag or (0x3F00 and propId shr 8)).toByte() + val dtxBytes = ByteTools.cLongToLEBytes(dtx) + System.arraycopy(dtxBytes, 0, tmp, pos, 4) + pos += 4 + } + recordData = ByteArray(tmp.size + complexData.size) + System.arraycopy(tmp, 0, recordData, 0, tmp.size) + // after the basic property table (PropID, IsBID, IsCOMPEX, dtx), store the complex data + System.arraycopy(complexData, 0, recordData, tmp.size, complexData.size) + isDirty = false + } + this.length = recordData.size + return recordData + } + + fun setData(b: ByteArray) { + recordData = b + props.clear() + imageIndex = -1 + imageName = "" + shapeName = "" + alternateText = "" + parseData() + } + + /** + * given property table bytes, parse into props hashmap + */ + private fun parseData() { + /* + * First part of an OPT record is an array of FOPTEs (propertyId, fBid, fComplex, data) + * If fComplex is set, the actual data (Unicode strings, arrays, etc.) is stored AFTER the last FOPTE (sorted by property id???); + * the length of the complex data is stored in the data field. + * if fComplex is not set, the meaining of the data field is dependent upon the propertyId + * if fBid is set and fComplex is not set, the data = a BLIP id (= an index into the BLIP store) + * The number of FOPTES is the inst field read above + */ + var propertyId: Int + var fBid: Int + var fComplex: Int + //int n= inst; // number of properties to parse + var pos = 0 // pointer to current property in data/property table + if (inst == 0 && recordData.size > 0) { // called from GelFrame ... + val dat = ByteArray(8) // read header + System.arraycopy(recordData, 0, dat, 0, 8) + version = 0xF and dat[0] // 15 for containers, version for atoms + inst = 0xFF and dat[1] shl 4 or (0xF0 and dat[0] shr 4) + fbt = 0xFF and dat[3] shl 8 or (0xFF and dat[2]) // record type id==0xF00B + pos = 8 // skip header + } + for (i in 0 until inst) { + propertyId = 0x3F and recordData[pos + 1] shl 8 or (0xFF and recordData[pos]) // 14 bits + fBid = 0x40 and recordData[pos + 1] shr 6 // specifies whether the value in the dtx field is a BLIP identifier- only valid if fComplex= FALSE + fComplex = 0x80 and recordData[pos + 1] shr 7 // if complex property, value is length. Data is parsed after. + val dtx = ByteTools.readInt(recordData[pos + 2], recordData[pos + 3], recordData[pos + 4], recordData[pos + 5]) + // TODO: if property number is of type bool/long/msoarray/... parse accordingly .. + if (propertyId == MSODrawingConstants.msooptpib) + // blip to display + imageIndex = dtx + else if (propertyId == MSODrawingConstants.msooptFillType) { + fillType = dtx + } else if (propertyId == MSODrawingConstants.msooptfillColor) + fillColor = setFillColor(dtx) + else if (propertyId == MSODrawingConstants.msooptfBackground) + bBackground = dtx != 0 + else if (propertyId == MSODrawingConstants.msooptGroupShapeProperties) { + //bPrint= (dtx!=0); // NOT TRUE!! TODO: parse real GroupShapeProperties (many) + } else if (propertyId == MSODrawingConstants.msooptpictureActive) + bActive = dtx != 0 + else if (propertyId == MSODrawingConstants.msooptlineWidth) { // appears that this controls display of line + if (lineprops == null) lineprops = IntArray(3) + lineprops[LINEPROPTS_WEIGHT] = dtx + } else if (propertyId == MSODrawingConstants.msooptlineColor) { // appears that this is always present, even if no line + if (lineprops == null) lineprops = IntArray(3) + lineprops[LINEPROPTS_COLOR] = dtx + } else if (propertyId == MSODrawingConstants.msooptLineStyle) { + if (lineprops == null) lineprops = IntArray(3) + lineprops[LINEPROPTS_STYLE] = dtx + } else if (propertyId == MSODrawingConstants.msofbtlTxid) { + hasTextId = true + } // msooptFillWidth + props.put(Integer.valueOf(propertyId), arrayOf(java.lang.Boolean.valueOf(fComplex != 0), Integer.valueOf(dtx), java.lang.Boolean.valueOf(fBid != 0))) + pos += 6 + } + + // now parse complex data after all "tightly packed" properties have been parsed. Order of data is original order + val ii = props.keys.iterator() + while (ii.hasNext()) { + val propId = ii.next() as Int + val o = props.get(propId) as Array // Object[]: 0= isComplex, 1= dtx (value or len of complex data -- filled in below), 2= isBid + if ((o[0] as Boolean).booleanValue()) { + val len = (o[1] as Int).toInt() + if (len >= 2) { + // apparently each record is delimited by a double byte 0 -- so decrement by 2 here and increment pos by 2 below + val complexdata = ByteArray(len - 2) // retrieve complex data at end of record + System.arraycopy(recordData, pos, complexdata, 0, complexdata.size) // get property data after main property table + props.put(propId, arrayOf(o[0], complexdata, o[2])) //store complex data for later retrieval + if (propId == MSODrawingConstants.msooptpibName) { // = image name + try { + imageName = String(complexdata, XLSConstants.UNICODEENCODING) + } catch (e: Exception) { + imageName = "Unnamed" + } + + } else if (propId == MSODrawingConstants.msooptwzName) { // = shape name + try { + shapeName = String(complexdata, XLSConstants.UNICODEENCODING) + } catch (e: Exception) { + } + + } else if (propId == MSODrawingConstants.msooptwzDescription) { // = Alternate Text + try { + alternateText = String(complexdata, XLSConstants.UNICODEENCODING) + } catch (e: Exception) { + } + + } else if (propId == MSODrawingConstants.msooptFillBlipName) { // = the comment, file name, or the full URL that is used as a fill + try { + val fillName = String(complexdata, XLSConstants.UNICODEENCODING) + } catch (e: Exception) { + } + + } + + pos += complexdata.size + 2 + } + } + } + } + + /** + * @param propId msofbtopt property ide see Msoconstants + * @param isBid value is a BLIP id - only valid if isComplex is false + * @param isComplex if false, dtx is used; if true, complexBytes are used and dtx=length + * @param dtx if not iscomplex, the value of property id; if iscomplex, length of complex data following the property table + * @param complexBytes if iscomplex, holds value of complex property e.g. shape name + */ + fun setProperty(propId: Int, isBid: Boolean, isComplex: Boolean, dtx: Int, complexBytes: ByteArray?) { + // a general order of common properties is (via property id): + /* + * 127 + * 267 + * 261 + * 262 + * 128 + * 133 + * 139 + * 191 + * 385 + * 447 + * 448 + * 459 + * 511 + */ + if (isComplex) + // complexBytes shouldn't be null + props.put(Integer.valueOf(propId), arrayOf(java.lang.Boolean.valueOf(isComplex), complexBytes, java.lang.Boolean.valueOf(isBid))) + else + props.put(Integer.valueOf(propId), arrayOf(java.lang.Boolean.valueOf(isComplex), Integer.valueOf(dtx), java.lang.Boolean.valueOf(isBid))) + + this.inst = props.size + isDirty = true // flag to regenerate recordData + } + + fun hasBorder(): Boolean { + return lineprops != null && lineprops!![LINEPROPTS_WEIGHT] > 1 + } + + fun getImageIndex(): Int { + return imageIndex + } + + fun getImageName(): String? { + return imageName + } + + fun getShapeName(): String? { + return shapeName + } + + /** + * Debug Output -- For Internal Use Only + */ + fun debugOutput(): String { + var propertyId: Int + val log = StringBuffer() + /* java.util.ArrayList keys= new java.util.ArrayList(props.keySet()); + int n= keys.size(); + for (int i= 0; i < n; i++) { + log.append("\r\n"); + propertyId = ((Integer)keys.get(i)).intValue(); // (0x3F&recordData[pos+1])<<8|(0xFF&recordData[pos]); + Object[] o= (Object[]) props.get(keys.get(i)); + boolean isComplex= ((Boolean)o[0]).booleanValue(); + boolean isBid= ((Boolean)o[2]).booleanValue(); + if (isComplex) fComplex=1; + if (isBid) fBid= 1; + int dtx; + if (!isComplex) + dtx= ((Integer)o[1]).intValue(); // non-complex data is just an integer + else + dtx= ((byte[])o[1]).length + 2; // stored data is a byte array; get length + 2 + //fBid = ((0x40&recordData[pos+1])>>6); // value is a BLIP ID - only valid if fComplex= FALSE + //fComplex = ((0x80&recordData[pos+1])>>7); // if complex property, value is length. Data is parsed after. + //int dtx = ByteTools.readInt(recordData[pos+2],recordData[pos+3],recordData[pos+4],recordData[pos+5]); + log.append("\t\t" + propertyId + "/" + fBid + "/" + fComplex + "/" + dtx); + //pos+=6; + } +*/ + val n = inst // number of properties to parse + // pointer to current property in data/property table + var fBid = 0 + var fComplex = 0 + var end = recordData.size + var pos = 0 + while (pos < end) { + propertyId = 0x3F and recordData[pos + 1] shl 8 or (0xFF and recordData[pos]) + fBid = 0x40 and recordData[pos + 1] shr 6 + fComplex = 0x80 and recordData[pos + 1] shr 7 // if complex property, value is length. Data is parsed after. + val dtx = ByteTools.readInt(recordData[pos + 2], recordData[pos + 3], recordData[pos + 4], recordData[pos + 5]) + if (fComplex != 0) + end -= dtx + log.append("\t\t$propertyId/$fBid/$fComplex/$dtx") + pos += 6 + } + return log.toString() + } + + /** + * Interpret an OfficeArtCOLORREF (used in fillColor, lineColor and many other opts) + * + * @param clrStructure

                      More information + * The OfficeArtCOLORREF structure specifies a color. The high 8 bits MAY be set to 0xFF, in which case the color MUST be ignored. + * The color properties that are specified in the following table have a set of extended-color properties. The color property specifies the main color. + * The colorExt and colorExtMod properties specify the extended colors that can be used to define the main color more precisely. + * If neither extended-color property is set, the main color property contains the full color definition. + * Otherwise, the colorExt property specifies the base color, and the colorExtMod property specifies a tint or shade modification that is applied to the colorExt property. + * In this case, the main color property contains the flattened RGB color that is computed by applying the specified tint or shade modification to the specified base color. + * + * + * + * + * A - unused1 (1 bit): A bit that is undefined and MUST be ignored. + * + * + * B - unused2 (1 bit): A bit that is undefined and MUST be ignored. + * + * + * C - unused3 (1 bit): A bit that is undefined and MUST be ignored. + * + * + * D - fSysIndex (1 bit): A bit that specifies whether the system color scheme will be used to determine the color. + * A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index into the system color table. Values less than 0x00F0 map directly to system colors. + * For more information, see [MSDN-GetSysColor] (below) + * The following table specifies values that have special meaning. + * Value Meaning + * 0x00F0 Use the fill color of the shape. + * 0x00F1 If the shape contains a line, use the line color of the shape. Otherwise, use the fill color. + * 0x00F2 Use the line color of the shape. + * 0x00F3 Use the shadow color of the shape. + * 0x00F4 Use the current, or last-used, color. + * 0x00F5 Use the fill background color of the shape. + * 0x00F6 Use the line background color of the shape. + * 0x00F7 If the shape contains a fill, use the fill color of the shape. Otherwise, use the line color. + * The following table specifies values that indicate special procedural properties that are used to modify the color components of another color. + * These values are combined with those in the preceding table or with a user-specified color. The first six values are mutually exclusive. + * Value Meaning + * 0x0100 Darken the color by the value that is specified in the blue field. A blue value of 0xFF specifies that the color is to be left unchanged, whereas a blue value of 0x00 specifies that the color is to be completely darkened. + * 0x0200 Lighten the color by the value that is specified in the blue field. A blue value of 0xFF specifies that the color is to be left unchanged, whereas a blue value of 0x00 specifies that the color is to be completely lightened. + * 0x0300 Add a gray level RGB value. The blue field contains the gray level to add: NewColor = SourceColor + gray + * 0x0400 Subtract a gray level RGB value. The blue field contains the gray level to subtract: NewColor = SourceColor - gray + * 0x0500 Reverse-subtract a gray level RGB value. The blue field contains the gray level from which to subtract: NewColor = gray - SourceColor + * 0x0600 If the color component being modified is less than the parameter contained in the blue field, set it to the minimum intensity. + * If the color component being modified is greater than or equal to the parameter, set it to the maximum intensity. + * 0x2000 After making other modifications, invert the color. + * 0x4000 After making other modifications, invert the color by toggling just the high bit of each color channel. + * 0x8000 Before making other modifications, convert the color to grayscale. + * E - fSchemeIndex (1 bit): A bit that specifies whether the current application-defined color scheme will be used to determine the color. + * A value of 0x1 specifies that red will be treated as an index into the current color scheme table. If this value is 0x1, green and blue MUST be 0x00. + * F - fSystemRGB (1 bit): A bit that specifies whether the color is a standard RGB color. The following table specifies the meaning of each value for this field. + * Value Meaning + * 0x0 The RGB color MAY use halftone dithering to display. + * 0x1 The color MUST be a solid color. + * G - fPaletteRGB (1 bit): A bit that specifies whether the current palette will be used to determine the color. + * A value of 0x1 specifies that red, green, and blue contain an RGB value that will be matched in the current color palette. This color MUST be solid. + * H - fPaletteIndex (1 bit): A bit that specifies whether the current palette will be used to determine the color. + * A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index into the current color palette. This color MAY<1> be dithered. + * If this value is 0x1, blue MUST be 0x00. + * blue (1 byte): An unsigned integer that specifies the intensity of the blue color channel. A value of 0x00 has the minimum blue intensity. A value of 0xFF has the maximum blue intensity. + * green (1 byte): An unsigned integer that specifies the intensity of the green color channel. A value of 0x00 has the minimum green intensity. A value of 0xFF has the maximum green intensity. + * red (1 byte): An unsigned integer that specifies the intensity of the red color channel. A value of 0x00 has the minimum red intensity. A value of 0xFF has the maximum red intensity. + * + * + * ... + * + * + * MSDN-GetSysColor + * Value Meaning + * COLOR_3DDKSHADOW 21 Dark shadow for three-dimensional display elements. + * COLOR_3DFACE 15 Face color for three-dimensional display elements and for dialog box backgrounds. + * COLOR_3DHIGHLIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) + * COLOR_3DHILIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) + * COLOR_3DLIGHT 22 Light color for three-dimensional display elements (for edges facing the light source.) + * COLOR_3DSHADOW 16 Shadow color for three-dimensional display elements (for edges facing away from the light source). + * COLOR_ACTIVEBORDER 10 Active window border. + * COLOR_ACTIVECAPTION 2 Active window title bar. Specifies the left side color in the color gradient of an active window's title bar if the gradient effect is enabled. + * COLOR_APPWORKSPACE 12 Background color of multiple document interface (MDI) applications. + * COLOR_BACKGROUND 1 Desktop. + * COLOR_BTNFACE 15 Face color for three-dimensional display elements and for dialog box backgrounds. + * COLOR_BTNHIGHLIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) + * COLOR_BTNHILIGHT 20 Highlight color for three-dimensional display elements (for edges facing the light source.) + * COLOR_BTNSHADOW 16 Shadow color for three-dimensional display elements (for edges facing away from the light source). + * COLOR_BTNTEXT 18 Text on push buttons. + * COLOR_CAPTIONTEXT 9 Text in caption, size box, and scroll bar arrow box. + * COLOR_DESKTOP 1 Desktop. + * COLOR_GRADIENTACTIVECAPTION 27 Right side color in the color gradient of an active window's title bar. + * COLOR_ACTIVECAPTION specifies the left side color. Use SPI_GETGRADIENTCAPTIONS with the SystemParametersInfo function to determine whether the gradient effect is enabled. + * COLOR_GRADIENTINACTIVECAPTION 28 Right side color in the color gradient of an inactive window's title bar. COLOR_INACTIVECAPTION specifies the left side color. + * COLOR_GRAYTEXT 17 Grayed (disabled) text. This color is set to 0 if the current display driver does not support a solid gray color. + * COLOR_HIGHLIGHT 13 Item(s) selected in a control. + * COLOR_HIGHLIGHTTEXT 14 Text of item(s) selected in a control. + * COLOR_HOTLIGHT 26 Color for a hyperlink or hot-tracked item. + * COLOR_INACTIVEBORDER11 Inactive window border. + * COLOR_INACTIVECAPTION3 Inactive window caption. + * Specifies the left side color in the color gradient of an inactive window's title bar if the gradient effect is enabled. + * COLOR_INACTIVECAPTIONTEXT19 Color of text in an inactive caption. + * COLOR_INFOBK 24 Background color for tooltip controls. + * COLOR_INFOTEXT 23 Text color for tooltip controls. + * COLOR_MENU 4 Menu background. + * COLOR_MENUHILIGHT 29 The color used to highlight menu items when the menu appears as a flat menu (see SystemParametersInfo). The highlighted menu item is outlined with COLOR_HIGHLIGHT. + * Windows 2000: This value is not supported. + * COLOR_MENUBAR 30 The background color for the menu bar when menus appear as flat menus (see SystemParametersInfo). However, COLOR_MENU continues to specify the background color of the menu popup. + * Windows 2000: This value is not supported. + * COLOR_MENUTEXT 7 Text in menus. + * COLOR_SCROLLBAR 0 Scroll bar gray area. + * COLOR_WINDOW 5 Window background. + * COLOR_WINDOWFRAME 6 Window frame. + * COLOR_WINDOWTEXT 8 Text in windows. + */ + private fun setFillColor(clrStructure: Int): java.awt.Color? { + val b = ByteTools.longToByteArray(clrStructure.toLong()) + val bPaletteIndex: Boolean + val bSchemeIndex: Boolean + val bSysIndex: Boolean + var fillclr: Short + + bPaletteIndex = b[4] and 0x1 == 0x1 // specifies whether the current palette will be used to determine the color + bSchemeIndex = b[4] and 0x8 == 0x8 // specifies whether the current application defined color scheme will be used to determine the color. + bSysIndex = b[4] and 0x10 == 0x10 // specifies whether the system color scheme will be used to determine the color. + + if (bPaletteIndex) { // // GREEN and RED are treated as an unsigned 16-bit index into the current color palette. This color MAY be dithered. BLUE MUST be 0x00. + } + if (bSchemeIndex) { // RED is an index into the current scheme color table. GREEN and BLUE MUST be 0x00. + fillclr = b[7].toShort() // what does 80 mean?????? + if (fillclr > FormatHandle.COLORTABLE.size) + fillclr = FormatHandle.interpretSpecialColorIndex(fillclr.toInt()) + fillColor = FormatHandle.COLORTABLE[fillclr] + return fillColor + } + if (bSysIndex) { // GREEN and RED will be treated as an unsigned 16-bit index into the system color table. Values less than 0x00F0 map directly to system colors. + fillclr = ByteTools.readShort(b[6].toInt(), b[7].toInt()) + if (fillclr.toInt() == 0x00F0 // Use the fill color of the shape. + + || fillclr.toInt() == 0x00F1 //If the shape contains a line, use the line color of the shape. Otherwise, use the fill color. + + || fillclr.toInt() == 0x00F2 //Use the line color of the shape. + + || fillclr.toInt() == 0x00F3 //Use the shadow color of the shape. + + || fillclr.toInt() == 0x00F4 //Use the current, or last-used, color. + + || fillclr.toInt() == 0x00F5 //Use the fill background color of the shape. + + || fillclr.toInt() == 0x00F6 //Use the line background color of the shape. + + || fillclr.toInt() == 0x00F7) + //If the shape contains a fill, use the fill color of the shape. Otherwise, use the line color. + fillclr = FormatConstants.COLOR_WHITE.toShort() + if (fillclr.toInt() == 0x40) + // default fg color + fillclr = FormatConstants.COLOR_WHITE.toShort() + else if (fillclr.toInt() == 0x41) + // default bg color + fillclr = FormatConstants.COLOR_WHITE.toShort() + else if (fillclr.toInt() == 0x4D) { // default CHART fg color -- INDEX SPECIFIC! + fillColor = null // flag to map via series (bar) color defaults + return fillColor + } else if (fillclr.toInt() == 0x4E) + // default CHART fg color + fillclr = FormatConstants.COLOR_WHITE.toShort() + else if (fillclr.toInt() == 0x4F) + // chart neutral color == black + fillclr = FormatConstants.COLOR_BLACK.toShort() + + if (fillclr < 0 || fillclr > FormatHandle.COLORTABLE.size) + fillclr = FormatConstants.COLOR_WHITE.toShort() + fillColor = FormatHandle.COLORTABLE[fillclr] + return fillColor + } + + // otherwise, r, g and blue are color values 0-255 + val bl = if (b[5] < 0) 255 + b[5] else b[5] + val g = if (b[6] < 0) 255 + b[6] else b[6] + val r = if (b[7] < 0) 255 + b[7] else b[7] + fillColor = java.awt.Color(r, g, bl) + return fillColor + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 465530579513265882L + internal val LINEPROPTS_STYLE = 0 + internal val LINEPROPTS_WEIGHT = 1 + internal val LINEPROPTS_COLOR = 2 + } +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSp.java b/src/main/java/io/starter/formats/escher/MsofbtSp.java deleted file mode 100644 index 51762e4..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtSp.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -import io.starter.toolkit.ByteTools; - - -//0xF00A -public class MsofbtSp extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5355585244930369889L; - int id; - int grfPersistence; - - public MsofbtSp(int fbt, int inst, int version) { - super(fbt, inst, version); - } - - public byte[] getData() { - byte[] idBytes, flagBytes; - idBytes = ByteTools.cLongToLEBytes(id); - flagBytes = ByteTools.cLongToLEBytes(grfPersistence); - byte[] retData = new byte[8]; - System.arraycopy(idBytes, 0, retData, 0, 4); - System.arraycopy(flagBytes, 0, retData, 4, 4); - - this.setLength(8); - return retData; - - } - - public void setId(int value) { - - id = value; - } - - public void setGrfPersistence(int value) { - grfPersistence = value; - } -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSp.kt b/src/main/java/io/starter/formats/escher/MsofbtSp.kt new file mode 100644 index 0000000..a5255f9 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtSp.kt @@ -0,0 +1,62 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +import io.starter.toolkit.ByteTools + + +//0xF00A +class MsofbtSp(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + internal var id: Int = 0 + internal var grfPersistence: Int = 0 + + public override fun getData(): ByteArray { + val idBytes: ByteArray + val flagBytes: ByteArray + idBytes = ByteTools.cLongToLEBytes(id) + flagBytes = ByteTools.cLongToLEBytes(grfPersistence) + val retData = ByteArray(8) + System.arraycopy(idBytes, 0, retData, 0, 4) + System.arraycopy(flagBytes, 0, retData, 4, 4) + + this.length = 8 + return retData + + } + + fun setId(value: Int) { + + id = value + } + + fun setGrfPersistence(value: Int) { + grfPersistence = value + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -5355585244930369889L + } +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSpContainer.kt b/src/main/java/io/starter/formats/escher/MsofbtSpContainer.kt new file mode 100644 index 0000000..fcfdef6 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtSpContainer.kt @@ -0,0 +1,39 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +//0xf004 +class MsofbtSpContainer(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + + override fun getData(): ByteArray { + return ByteArray(0) + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 3635215155760095395L + } +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSpgr.java b/src/main/java/io/starter/formats/escher/MsofbtSpgr.java deleted file mode 100644 index b42f861..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtSpgr.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -import io.starter.toolkit.ByteTools; - -//0xf009 - -/* - * This record is present only in group shapes (not shapes in groups, shapes that are groups). - * The group shape record defines the coordinate system of the shape, which the anchors of the - * child shape are expressed in. All other information is stored in the shape records that follow. - */ -public class MsofbtSpgr extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5591214948365806058L; - int left = 0, top = 0, right = 0, bottom = 0; - - public MsofbtSpgr(int fbt, int inst, int version) { - super(fbt, inst, version); - } - - protected byte[] getData() { - byte[] leftBytes, topBytes, rightBytes, bottomBytes; - leftBytes = ByteTools.cLongToLEBytes(left); - topBytes = ByteTools.cLongToLEBytes(top); - rightBytes = ByteTools.cLongToLEBytes(right); - bottomBytes = ByteTools.cLongToLEBytes(bottom); - - byte[] retBytes = new byte[16]; - System.arraycopy(leftBytes, 0, retBytes, 0, 4); - System.arraycopy(topBytes, 0, retBytes, 4, 4); - System.arraycopy(rightBytes, 0, retBytes, 8, 4); - System.arraycopy(bottomBytes, 0, retBytes, 12, 4); - - this.setLength(retBytes.length); - - return retBytes; - } - - public void setRect(int left, int top, int right, int bottom) { - this.left = left; - this.right = right; - this.bottom = bottom; - this.top = top; - - } - -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSpgr.kt b/src/main/java/io/starter/formats/escher/MsofbtSpgr.kt new file mode 100644 index 0000000..68cf542 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtSpgr.kt @@ -0,0 +1,76 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +import io.starter.toolkit.ByteTools + +//0xf009 + +/* + * This record is present only in group shapes (not shapes in groups, shapes that are groups). + * The group shape record defines the coordinate system of the shape, which the anchors of the + * child shape are expressed in. All other information is stored in the shape records that follow. + */ +class MsofbtSpgr(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + internal var left = 0 + internal var top = 0 + internal var right = 0 + internal var bottom = 0 + + override fun getData(): ByteArray { + val leftBytes: ByteArray + val topBytes: ByteArray + val rightBytes: ByteArray + val bottomBytes: ByteArray + leftBytes = ByteTools.cLongToLEBytes(left) + topBytes = ByteTools.cLongToLEBytes(top) + rightBytes = ByteTools.cLongToLEBytes(right) + bottomBytes = ByteTools.cLongToLEBytes(bottom) + + val retBytes = ByteArray(16) + System.arraycopy(leftBytes, 0, retBytes, 0, 4) + System.arraycopy(topBytes, 0, retBytes, 4, 4) + System.arraycopy(rightBytes, 0, retBytes, 8, 4) + System.arraycopy(bottomBytes, 0, retBytes, 12, 4) + + this.length = retBytes.size + + return retBytes + } + + fun setRect(left: Int, top: Int, right: Int, bottom: Int) { + this.left = left + this.right = right + this.bottom = bottom + this.top = top + + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5591214948365806058L + } + +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.java b/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.java deleted file mode 100644 index 6ef4a7b..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -//0xf003 -public class MsofbtSpgrContainer extends EscherRecord { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 8652169989794277582L; - - public MsofbtSpgrContainer(int fbt, int inst, int version) { - super(fbt, inst, version); - } - - protected byte[] getData() { - return new byte[0]; - } -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.kt b/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.kt new file mode 100644 index 0000000..2338940 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtSpgrContainer.kt @@ -0,0 +1,39 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +//0xf003 +class MsofbtSpgrContainer(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + + override fun getData(): ByteArray { + return ByteArray(0) + } + + companion object { + + /** + * serialVersionUID + */ + private val serialVersionUID = 8652169989794277582L + } +} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.java b/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.java deleted file mode 100644 index d80ee4f..0000000 --- a/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.formats.escher; - -import io.starter.toolkit.ByteTools; - -public class MsofbtSplitMenuColors extends EscherRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5888748984363726576L; - //These values are from experimental records. - int fillColor = 0x800000D, lineColor = 0x800000C, shadowColor = 0x8000017, _3dColor = 0x100000f7; - - public MsofbtSplitMenuColors(int fbt, int inst, int version) { - super(fbt, inst, version); - } - - // @Override - protected byte[] getData() { - byte[] fillColorBytes = ByteTools.cLongToLEBytes(fillColor); - byte[] lineColorBytes = ByteTools.cLongToLEBytes(lineColor); - byte[] shadowColorBytes = ByteTools.cLongToLEBytes(shadowColor); - byte[] _3dColorBytes = ByteTools.cLongToLEBytes(_3dColor); - - byte[] totalBytes = new byte[16]; - - System.arraycopy(fillColorBytes, 0, totalBytes, 0, 4); - System.arraycopy(lineColorBytes, 0, totalBytes, 4, 4); - System.arraycopy(shadowColorBytes, 0, totalBytes, 8, 4); - System.arraycopy(_3dColorBytes, 0, totalBytes, 12, 4); - - this.setLength(16); - this.setInst(4); - - return totalBytes; - } - - public int get3dColor() { - return _3dColor; - } - - public void set3dColor(int color) { - _3dColor = color; - } - - public int getFillColor() { - return fillColor; - } - - public void setFillColor(int fillColor) { - this.fillColor = fillColor; - } - - public int getLineColor() { - return lineColor; - } - - public void setLineColor(int lineColor) { - this.lineColor = lineColor; - } - - public int getShadowColor() { - return shadowColor; - } - - public void setShadowColor(int shadowColor) { - this.shadowColor = shadowColor; - } - -} diff --git a/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.kt b/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.kt new file mode 100644 index 0000000..b4d14c3 --- /dev/null +++ b/src/main/java/io/starter/formats/escher/MsofbtSplitMenuColors.kt @@ -0,0 +1,61 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.formats.escher + +import io.starter.toolkit.ByteTools + +class MsofbtSplitMenuColors(fbt: Int, inst: Int, version: Int) : EscherRecord(fbt, inst, version) { + //These values are from experimental records. + var fillColor = 0x800000D + var lineColor = 0x800000C + var shadowColor = 0x8000017 + var 3dColor = 0x100000f7 + + // @Override + override fun getData(): ByteArray { + val fillColorBytes = ByteTools.cLongToLEBytes(fillColor) + val lineColorBytes = ByteTools.cLongToLEBytes(lineColor) + val shadowColorBytes = ByteTools.cLongToLEBytes(shadowColor) + val _3dColorBytes = ByteTools.cLongToLEBytes(3 dColor) + + val totalBytes = ByteArray(16) + + System.arraycopy(fillColorBytes, 0, totalBytes, 0, 4) + System.arraycopy(lineColorBytes, 0, totalBytes, 4, 4) + System.arraycopy(shadowColorBytes, 0, totalBytes, 8, 4) + System.arraycopy(_3dColorBytes, 0, totalBytes, 12, 4) + + this.length = 16 + this.inst = 4 + + return totalBytes + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5888748984363726576L + } + +} diff --git a/src/main/java/io/starter/naming/InitialContextFactoryImpl.java b/src/main/java/io/starter/naming/InitialContextFactoryImpl.kt similarity index 67% rename from src/main/java/io/starter/naming/InitialContextFactoryImpl.java rename to src/main/java/io/starter/naming/InitialContextFactoryImpl.kt index 2693d5b..cc83b78 100644 --- a/src/main/java/io/starter/naming/InitialContextFactoryImpl.java +++ b/src/main/java/io/starter/naming/InitialContextFactoryImpl.kt @@ -20,23 +20,22 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.naming; +package io.starter.naming -import javax.naming.Context; -import javax.naming.NamingException; -import javax.naming.spi.InitialContextFactory; -import java.util.Hashtable; +import javax.naming.Context +import javax.naming.NamingException +import javax.naming.spi.InitialContextFactory +import java.util.Hashtable /** * Read the details at: http://java.sun.com/j2se/1.3/docs/guide/jndi/spec/spi/jndispiTOC.fm.html */ -public class InitialContextFactoryImpl implements InitialContextFactory { +class InitialContextFactoryImpl : InitialContextFactory { - public Context getInitialContext(Hashtable env) - throws NamingException { - InitialContextImpl contimple = new InitialContextImpl(); + @Throws(NamingException::class) + override fun getInitialContext(env: Hashtable<*, *>): Context { - return contimple; + return InitialContextImpl() } } diff --git a/src/main/java/io/starter/naming/InitialContextImpl.java b/src/main/java/io/starter/naming/InitialContextImpl.java deleted file mode 100644 index 1368626..0000000 --- a/src/main/java/io/starter/naming/InitialContextImpl.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.naming; - -import javax.naming.*; -import java.util.Hashtable; - -/** - * A basic JNDI Context which holds a flat lookup of names - */ -public class InitialContextImpl implements javax.naming.Context { - - NameParser nameParser = new NameParserImpl(); - protected Hashtable env; - - // provide persistence between instantiations - public static String CONTEXT_ID = "io.starter.naming.InitialContextImpl_instance"; - public static String LOAD_CONTEXT = "io.starter.naming.load_context"; - - public InitialContextImpl() { - if (System.getProperties().get(CONTEXT_ID) != null) - this.env = (Hashtable) System.getProperties().get(CONTEXT_ID); - else { - String loadme = System.getProperty(LOAD_CONTEXT); - env = new Hashtable(); // 20070518 KSC: Moved so gets init even if no LOAD_CONTEXT - if (loadme != null) { - if (loadme.equals("true")) { - // env = new Hashtable(); KSC: See above - // this breaks properties - System.getProperties().put(CONTEXT_ID, env); - } - } - } - } - - // check return... -jm - public Object addToEnvironment(String propName, Object propVal) throws NamingException { - if (env.contains(propVal)) { - throw new NamingException("Object " + propName + " already exists in NamingContext."); - } else { - env.put(propName, propVal); - return propVal; - } - } - - // we use string to bind -- is that bad? - public void bind(Name name, Object obj) throws NamingException { - String str = name.toString(); - this.bind(str, obj); - } - - public void bind(String name, Object obj) throws NamingException { - try { - this.addToEnvironment(name, obj); - } catch (NamingException e) { - env.remove(obj); - env.put(name, obj); // override - } - } - - private boolean closed = false; - - public void close() throws NamingException { - closed = true; - } - - // ? - public Name composeName(Name name, Name prefix) throws NamingException { - NameImpl retval = new NameImpl(); - retval.addAll(prefix); - retval.addAll(name); - return retval; - } - - public String composeName(String name, String prefix) throws NamingException { - StringBuffer sb = new StringBuffer(); - sb.append(name); - sb.append(prefix); - return sb.toString(); - } - - public Hashtable getEnvironment() throws NamingException { - return env; - } - - public NameParser getNameParser(String name) throws NamingException { - this.nameParser.parse(name); - return this.nameParser; - } - - public NameParser getNameParser(Name name) throws NamingException { - return this.nameParser; - } - - public Object lookup(Name name) throws NamingException { - return env.get(name); - } - - public Object lookup(String name) throws NamingException { - return env.get(name); - } - - public Object lookupLink(Name name) throws NamingException { - return env.get(name); - } - - public Object lookupLink(String name) throws NamingException { - return env.get(name); - } - - public void rebind(Name name, Object obj) throws NamingException { - this.bind(name, obj); - } - - public void rebind(String name, Object obj) throws NamingException { - this.bind(name, obj); - } - - public Object removeFromEnvironment(String propName) throws NamingException { - return env.remove(propName); - } - - public void rename(String oldName, String newName) throws NamingException { - Object ob = env.get(oldName); - env.remove(oldName); - env.put(newName, ob); - } - - public void rename(Name oldName, Name newName) throws NamingException { - Object ob = env.get(oldName); - env.remove(oldName); - env.put(newName, ob); - } - - public void unbind(String name) throws NamingException { - try { - env.remove(env.get(name)); - } catch (Exception e) { - throw new NamingException(e.toString()); - } - } - - public void unbind(Name name) throws NamingException { - try { - env.remove(env.get(name)); - } catch (Exception e) { - throw new NamingException(e.toString()); - } - } - - // TODO: Implement the following mehods -jm 9/27/2004 - - public NamingEnumeration list(String name) throws NamingException { - return null; - } - - public NamingEnumeration list(Name name) throws NamingException { - return null; - } - - public NamingEnumeration listBindings(Name name) throws NamingException { - return null; - } - - public NamingEnumeration listBindings(String name) throws NamingException { - return null; - } - - public Context createSubcontext(Name name) throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - return null; - } - - public Context createSubcontext(String name) throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - return null; - } - - public void destroySubcontext(String name) throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - } - - public void destroySubcontext(Name name) throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - } - - public String getNameInNamespace() throws NamingException { - // This method is derived from interface javax.naming.Context - // to do: code goes here - return null; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/naming/InitialContextImpl.kt b/src/main/java/io/starter/naming/InitialContextImpl.kt new file mode 100644 index 0000000..130b5ce --- /dev/null +++ b/src/main/java/io/starter/naming/InitialContextImpl.kt @@ -0,0 +1,251 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.naming + +import javax.naming.* +import java.util.Hashtable + +/** + * A basic JNDI Context which holds a flat lookup of names + */ +open class InitialContextImpl : javax.naming.Context { + + internal var nameParser: NameParser = NameParserImpl() + protected var env: Hashtable, Any> + + private var closed = false + + init { + if (System.getProperties()[CONTEXT_ID] != null) + this.env = System.getProperties()[CONTEXT_ID] as Hashtable, Any> + else { + val loadme = System.getProperty(LOAD_CONTEXT) + env = Hashtable() // 20070518 KSC: Moved so gets init even if no LOAD_CONTEXT + if (loadme != null) { + if (loadme == "true") { + // env = new Hashtable(); KSC: See above + // this breaks properties + System.getProperties()[CONTEXT_ID] = env + } + } + } + } + + // check return... -jm + @Throws(NamingException::class) + override fun addToEnvironment(propName: String, propVal: Any): Any { + if (env.contains(propVal)) { + throw NamingException("Object $propName already exists in NamingContext.") + } else { + env[propName] = propVal + return propVal + } + } + + // we use string to bind -- is that bad? + @Throws(NamingException::class) + override fun bind(name: Name, obj: Any) { + val str = name.toString() + this.bind(str, obj) + } + + @Throws(NamingException::class) + override fun bind(name: String, obj: Any) { + try { + this.addToEnvironment(name, obj) + } catch (e: NamingException) { + env.remove(obj) + env[name] = obj // override + } + + } + + @Throws(NamingException::class) + override fun close() { + closed = true + } + + // ? + @Throws(NamingException::class) + override fun composeName(name: Name, prefix: Name): Name { + val retval = NameImpl() + retval.addAll(prefix) + retval.addAll(name) + return retval + } + + @Throws(NamingException::class) + override fun composeName(name: String, prefix: String): String { + val sb = StringBuffer() + sb.append(name) + sb.append(prefix) + return sb.toString() + } + + @Throws(NamingException::class) + override fun getEnvironment(): Hashtable, Any> { + return env + } + + @Throws(NamingException::class) + override fun getNameParser(name: String): NameParser { + this.nameParser.parse(name) + return this.nameParser + } + + @Throws(NamingException::class) + override fun getNameParser(name: Name): NameParser { + return this.nameParser + } + + @Throws(NamingException::class) + override fun lookup(name: Name): Any { + return env[name] + } + + @Throws(NamingException::class) + override fun lookup(name: String): Any { + return env[name] + } + + @Throws(NamingException::class) + override fun lookupLink(name: Name): Any { + return env[name] + } + + @Throws(NamingException::class) + override fun lookupLink(name: String): Any { + return env[name] + } + + @Throws(NamingException::class) + override fun rebind(name: Name, obj: Any) { + this.bind(name, obj) + } + + @Throws(NamingException::class) + override fun rebind(name: String, obj: Any) { + this.bind(name, obj) + } + + @Throws(NamingException::class) + override fun removeFromEnvironment(propName: String): Any { + return env.remove(propName) + } + + @Throws(NamingException::class) + override fun rename(oldName: String, newName: String) { + val ob = env[oldName] + env.remove(oldName) + env[newName] = ob + } + + @Throws(NamingException::class) + override fun rename(oldName: Name, newName: Name) { + val ob = env[oldName] + env.remove(oldName) + env[newName] = ob + } + + @Throws(NamingException::class) + override fun unbind(name: String) { + try { + env.remove(env[name]) + } catch (e: Exception) { + throw NamingException(e.toString()) + } + + } + + @Throws(NamingException::class) + override fun unbind(name: Name) { + try { + env.remove(env[name]) + } catch (e: Exception) { + throw NamingException(e.toString()) + } + + } + + // TODO: Implement the following mehods -jm 9/27/2004 + + @Throws(NamingException::class) + override fun list(name: String): NamingEnumeration<*>? { + return null + } + + @Throws(NamingException::class) + override fun list(name: Name): NamingEnumeration<*>? { + return null + } + + @Throws(NamingException::class) + override fun listBindings(name: Name): NamingEnumeration<*>? { + return null + } + + @Throws(NamingException::class) + override fun listBindings(name: String): NamingEnumeration<*>? { + return null + } + + @Throws(NamingException::class) + override fun createSubcontext(name: Name): Context? { + // This method is derived from interface javax.naming.Context + // to do: code goes here + return null + } + + @Throws(NamingException::class) + override fun createSubcontext(name: String): Context? { + // This method is derived from interface javax.naming.Context + // to do: code goes here + return null + } + + @Throws(NamingException::class) + override fun destroySubcontext(name: String) { + // This method is derived from interface javax.naming.Context + // to do: code goes here + } + + @Throws(NamingException::class) + override fun destroySubcontext(name: Name) { + // This method is derived from interface javax.naming.Context + // to do: code goes here + } + + @Throws(NamingException::class) + override fun getNameInNamespace(): String? { + // This method is derived from interface javax.naming.Context + // to do: code goes here + return null + } + + companion object { + + // provide persistence between instantiations + var CONTEXT_ID = "io.starter.naming.InitialContextImpl_instance" + var LOAD_CONTEXT = "io.starter.naming.load_context" + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/naming/NameImpl.java b/src/main/java/io/starter/naming/NameImpl.java deleted file mode 100644 index 648f7f0..0000000 --- a/src/main/java/io/starter/naming/NameImpl.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.naming; - -import io.starter.toolkit.CompatibleVector; - -import javax.naming.InvalidNameException; -import javax.naming.Name; -import java.util.Enumeration; - -/* - Name add(int posn, String comp) - Adds a single component at a specified position within this name. - Name add(String comp) - Adds a single component to the end of this name. - Name addAll(int posn, Name n) - - Name addAll(Name suffix) - Adds the components of a name -- in order -- to the end of this name. - Object clone() - Generates a new copy of this name. - int compareTo(Object obj) - Compares this name with another name for order. - boolean endsWith(Name n) - Determines whether this name ends with a specified suffix. - String get(int posn) - Retrieves a component of this name. - Enumeration getAll() - Retrieves the components of this name as an enumeration of strings. - boolean isEmpty() - Determines whether this name is empty. - Object remove(int posn) - Removes a component from this name. - int size() - Returns the number of components in this name. - boolean startsWith(Name n) - Determines whether this name starts with a specified prefix. -*/ - -public class NameImpl implements Name { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 4387233472850688497L; - CompatibleVector vals = new CompatibleVector(); /* - * (non-Javadoc) - * - * - * * @see javax.naming.Name#clone() - */ - - @Override - public Object clone() { - NameImpl nimple = new NameImpl(); - CompatibleVector newvals = new CompatibleVector(); - newvals.addAll(vals); - nimple.vals = newvals; - return nimple; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#remove(int) - */ - public Object remove(int arg0) throws InvalidNameException { - return vals.remove(arg0); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#get(int) - */ - public String get(int arg0) { - return vals.get(arg0).toString(); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#getAll() - */ - public Enumeration getAll() { - return vals.elements(); - } - - /* - * Creates a name whose components consist of a prefix of the components of this - * name. - * - * @see javax.naming.Name#getPrefix(int) - */ - public Name getPrefix(int arg0) { - return null; - } - - /* - * Creates a name whose components consist of a suffix of the components in this - * name. - * - * @see javax.naming.Name#getSuffix(int) - */ - public Name getSuffix(int arg0) { - return null; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#add(java.lang.String) - */ - public Name add(String arg0) throws InvalidNameException { - return null; - } - - /* - * Adds the components of a name -- in order -- at a specified position within - * this name. - * - * @see javax.naming.Name#addAll(int, javax.naming.Name) - */ - public Name addAll(int arg0, Name arg1) throws InvalidNameException { - this.vals.addAll(arg0, ((NameImpl) arg1).getVals()); - return this; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#addAll(javax.naming.Name) - */ - public Name addAll(Name arg0) throws InvalidNameException { - this.vals.addAll(((NameImpl) arg0).getVals()); - return this; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#size() - */ - public int size() { - return vals.size(); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#isEmpty() - */ - public boolean isEmpty() { - return vals.size() > 0; - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#compareTo(java.lang.Object) - */ - public int compareTo(Object arg0) { - return this.compareTo(arg0); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#endsWith(javax.naming.Name) - */ - public boolean endsWith(Name arg0) { - Object ob1 = arg0.get(arg0.size() - 1); - Object ob2 = this.get(this.size() - 1); - return ob1.equals(ob2); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#startsWith(javax.naming.Name) - */ - public boolean startsWith(Name arg0) { - Object ob1 = arg0.get(0); - Object ob2 = this.get(0); - return ob1.equals(ob2); - } - - /* - * (non-Javadoc) - * - * @see javax.naming.Name#add(int, java.lang.String) - */ - public Name add(int arg0, String arg1) throws InvalidNameException { - vals.set(arg0, arg1); - return this; - } - - /** - * @return - */ - CompatibleVector getVals() { - return vals; - } - - /** - * @param vector - */ - void setVals(CompatibleVector vector) { - vals = vector; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/naming/NameImpl.kt b/src/main/java/io/starter/naming/NameImpl.kt new file mode 100644 index 0000000..c699d9a --- /dev/null +++ b/src/main/java/io/starter/naming/NameImpl.kt @@ -0,0 +1,230 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.naming + +import io.starter.toolkit.CompatibleVector + +import javax.naming.InvalidNameException +import javax.naming.Name +import java.util.Enumeration + +/* + Name add(int posn, String comp) + Adds a single component at a specified position within this name. + Name add(String comp) + Adds a single component to the end of this name. + Name addAll(int posn, Name n) + + Name addAll(Name suffix) + Adds the components of a name -- in order -- to the end of this name. + Object clone() + Generates a new copy of this name. + int compareTo(Object obj) + Compares this name with another name for order. + boolean endsWith(Name n) + Determines whether this name ends with a specified suffix. + String get(int posn) + Retrieves a component of this name. + Enumeration getAll() + Retrieves the components of this name as an enumeration of strings. + boolean isEmpty() + Determines whether this name is empty. + Object remove(int posn) + Removes a component from this name. + int size() + Returns the number of components in this name. + boolean startsWith(Name n) + Determines whether this name starts with a specified prefix. +*/ + +class NameImpl : Name { + /** + * @return + */ + /** + * @param vector + */ + internal var vals = CompatibleVector() /* + * (non-Javadoc) + * + * + * * @see javax.naming.Name#clone() + */ + + override fun clone(): Any { + val nimple = NameImpl() + val newvals = CompatibleVector() + newvals.addAll(vals) + nimple.vals = newvals + return nimple + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#remove(int) + */ + @Throws(InvalidNameException::class) + override fun remove(arg0: Int): Any { + return vals.removeAt(arg0) + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#get(int) + */ + override fun get(arg0: Int): String { + return vals[arg0].toString() + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#getAll() + */ + override fun getAll(): Enumeration<*> { + return vals.elements() + } + + /* + * Creates a name whose components consist of a prefix of the components of this + * name. + * + * @see javax.naming.Name#getPrefix(int) + */ + override fun getPrefix(arg0: Int): Name? { + return null + } + + /* + * Creates a name whose components consist of a suffix of the components in this + * name. + * + * @see javax.naming.Name#getSuffix(int) + */ + override fun getSuffix(arg0: Int): Name? { + return null + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#add(java.lang.String) + */ + @Throws(InvalidNameException::class) + override fun add(arg0: String): Name? { + return null + } + + /* + * Adds the components of a name -- in order -- at a specified position within + * this name. + * + * @see javax.naming.Name#addAll(int, javax.naming.Name) + */ + @Throws(InvalidNameException::class) + override fun addAll(arg0: Int, arg1: Name): Name { + this.vals.addAll(arg0, (arg1 as NameImpl).vals) + return this + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#addAll(javax.naming.Name) + */ + @Throws(InvalidNameException::class) + override fun addAll(arg0: Name): Name { + this.vals.addAll((arg0 as NameImpl).vals) + return this + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#size() + */ + override fun size(): Int { + return vals.size + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#isEmpty() + */ + override fun isEmpty(): Boolean { + return vals.size > 0 + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#compareTo(java.lang.Object) + */ + override fun compareTo(arg0: Any): Int { + return this.compareTo(arg0) + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#endsWith(javax.naming.Name) + */ + override fun endsWith(arg0: Name): Boolean { + val ob1 = arg0.get(arg0.size() - 1) + val ob2 = this.get(this.size() - 1) + return ob1 == ob2 + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#startsWith(javax.naming.Name) + */ + override fun startsWith(arg0: Name): Boolean { + val ob1 = arg0.get(0) + val ob2 = this.get(0) + return ob1 == ob2 + } + + /* + * (non-Javadoc) + * + * @see javax.naming.Name#add(int, java.lang.String) + */ + @Throws(InvalidNameException::class) + override fun add(arg0: Int, arg1: String): Name { + vals[arg0] = arg1 + return this + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 4387233472850688497L + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/naming/NameParserImpl.java b/src/main/java/io/starter/naming/NameParserImpl.kt similarity index 73% rename from src/main/java/io/starter/naming/NameParserImpl.java rename to src/main/java/io/starter/naming/NameParserImpl.kt index d7bf55a..2b580fc 100644 --- a/src/main/java/io/starter/naming/NameParserImpl.java +++ b/src/main/java/io/starter/naming/NameParserImpl.kt @@ -20,25 +20,25 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.naming; +package io.starter.naming -import javax.naming.Name; -import javax.naming.NameParser; -import javax.naming.NamingException; +import javax.naming.Name +import javax.naming.NameParser +import javax.naming.NamingException -/* +/* This class is used for parsing names from a hierarchical - namespace. The NameParser contains knowledge of the syntactic - information (like left-to-right orientation, name separator, etc.) - needed to parse names. - - The equals() method, when used to compare two NameParsers, returns - true if and only if they serve the same namespace. - - + namespace. The NameParser contains knowledge of the syntactic + information (like left-to-right orientation, name separator, etc.) + needed to parse names. + + The equals() method, when used to compare two NameParsers, returns + true if and only if they serve the same namespace. + + @see:CompoundName, Name */ -class NameParserImpl implements NameParser { +internal class NameParserImpl : NameParser { /* Parses a name into its components. Parameters: @@ -50,12 +50,13 @@ class NameParserImpl implements NameParser { NamingException - If a naming exception was encountered. * @see javax.naming.NameParser#parse(java.lang.String) */ - public Name parse(String arg0) throws NamingException { - Name nm = new NameImpl(); + @Throws(NamingException::class) + override fun parse(arg0: String): Name { + val nm = NameImpl() - nm.add(arg0); // just plop it in for now... + nm.add(arg0) // just plop it in for now... - return nm; + return nm } } \ No newline at end of file diff --git a/src/main/java/io/starter/naming/NamingEnumerationImpl.java b/src/main/java/io/starter/naming/NamingEnumerationImpl.kt similarity index 61% rename from src/main/java/io/starter/naming/NamingEnumerationImpl.java rename to src/main/java/io/starter/naming/NamingEnumerationImpl.kt index 2b96d28..96e1f5b 100644 --- a/src/main/java/io/starter/naming/NamingEnumerationImpl.java +++ b/src/main/java/io/starter/naming/NamingEnumerationImpl.kt @@ -20,54 +20,57 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.naming; +package io.starter.naming -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import java.util.Enumeration; +import javax.naming.NamingEnumeration +import javax.naming.NamingException +import java.util.Enumeration -public class NamingEnumerationImpl implements NamingEnumeration { +class NamingEnumerationImpl : NamingEnumeration { - private Enumeration e = null; + private var e: Enumeration<*>? = null - void setEnumeration(Enumeration ex) { - e = ex; + internal fun setEnumeration(ex: Enumeration<*>) { + e = ex } /* (non-Javadoc) * @see javax.naming.NamingEnumeration#close() */ - public void close() throws NamingException { - e = null; + @Throws(NamingException::class) + override fun close() { + e = null } /* (non-Javadoc) * @see javax.naming.NamingEnumeration#hasMore() */ - public boolean hasMore() throws NamingException { - return e.hasMoreElements(); + @Throws(NamingException::class) + override fun hasMore(): Boolean { + return e!!.hasMoreElements() } /* (non-Javadoc) * @see javax.naming.NamingEnumeration#next() */ - public Object next() throws NamingException { - return e.nextElement(); + @Throws(NamingException::class) + override fun next(): Any { + return e!!.nextElement() } /* (non-Javadoc) * @see java.util.Enumeration#hasMoreElements() */ - public boolean hasMoreElements() { - return e.hasMoreElements(); + override fun hasMoreElements(): Boolean { + return e!!.hasMoreElements() } /* (non-Javadoc) * @see java.util.Enumeration#nextElement() */ - public Object nextElement() { - return e.nextElement(); + override fun nextElement(): Any { + return e!!.nextElement() } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/Base64.java b/src/main/java/io/starter/toolkit/Base64.java deleted file mode 100644 index da2a330..0000000 --- a/src/main/java/io/starter/toolkit/Base64.java +++ /dev/null @@ -1,1372 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -/** - * Encodes and decodes to and from Base64 notation. - * - *

                      - * Change Log: - *

                      - *
                        - *
                      • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added - * some convenience methods for reading and writing to and from files.
                      • - *
                      • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems - * with other encodings (like EBCDIC).
                      • - *
                      • v2.0.1 - Fixed an error when decoding a single byte, that is, when the - * encoded data was a single byte.
                      • - *
                      • v2.0 - I got rid of methods that used booleans to set options. - * Now everything is more consolidated and cleaner. The code now detects - * when data that's being decoded is gzip-compressed and will decompress it - * automatically. Generally things are cleaner. You'll probably have to - * change some method calls that you were making to support the new - * options format (ints that you "OR" together).
                      • - *
                      • v1.5.1 - Fixed bug when decompressing and decoding to a - * byte[] using decode( String s, boolean gzipCompressed ). - * Added the ability to "suspend" encoding in the Output Stream so - * you can turn on and off the encoding if you need to embed base64 - * data in an otherwise "normal" stream (like an XML file).
                      • - *
                      • v1.5 - Output stream pases on flush() command but doesn't do anything itself. - * This helps when using GZIP streams. - * Added the ability to GZip-compress objects before encoding them.
                      • - *
                      • v1.4 - Added helper methods to read/write files.
                      • - *
                      • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
                      • - *
                      • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream - * where last buffer being read, if not completely full, was not returned.
                      • - *
                      • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
                      • - *
                      • v1.3.3 - Fixed I/O streams which were totally messed up.
                      • - *
                      - * - *

                      - * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit http://iharder.net/base64 - * periodically to check for updates or to contribute improvements. - *

                      - * - * @author Robert Harder - * @author rob@iharder.net - * @version 2.1 - */ -public class Base64 { - - /* ******** P U B L I C F I E L D S ******** */ - - - /** - * No options specified. Value is zero. - */ - public final static int NO_OPTIONS = 0; - - /** - * Specify encoding. - */ - public final static int ENCODE = 1; - - - /** - * Specify decoding. - */ - public final static int DECODE = 0; - - - /** - * Specify that data should be gzip-compressed. - */ - public final static int GZIP = 2; - - - /** - * Don't break lines when encoding (violates strict Base64 specification) - */ - public final static int DONT_BREAK_LINES = 8; - - - /* ******** P R I V A T E F I E L D S ******** */ - - - /** - * Maximum line length (76) of Base64 output. - */ - private final static int MAX_LINE_LENGTH = 76; - - - /** - * The equals sign (=) as a byte. - */ - private final static byte EQUALS_SIGN = (byte) '='; - - - /** - * The new line character (\n) as a byte. - */ - private final static byte NEW_LINE = (byte) '\n'; - - - /** - * Preferred encoding. - */ - private final static String PREFERRED_ENCODING = "UTF-8"; - - - /** - * The 64 valid Base64 values. - */ - private final static byte[] ALPHABET; - private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */ - { - (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', - (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', - (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', - (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', - (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', - (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', - (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' - }; - - /** Determine which ALPHABET to use. */ - static { - byte[] __bytes; - try { - __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException use) { - __bytes = _NATIVE_ALPHABET; // Fall back to native encoding - } // end catch - ALPHABET = __bytes; - } // end static - - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] DECODABET = - { - -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, -9, -9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9 // Decimal 123 - 126 - /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - // I think I end up not using the BAD_ENCODING indicator. - //private final static byte BAD_ENCODING = -9; // Indicates error in encoding - private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - - /** - * Defeats instantiation. - */ - private Base64() { - } - - - - /* ******** E N C O D I N G M E T H O D S ******** */ - - - /** - * Encodes up to the first three bytes of array threeBytes - * and returns a four-byte array in Base64 notation. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * The array threeBytes needs only be as big as - * numSigBytes. - * Code can reuse a byte array by passing a four-byte array as b4. - * - * @param b4 A reusable byte array to reduce array instantiation - * @param threeBytes the array to convert - * @param numSigBytes the number of significant bytes in your array - * @return four byte array in Base64 notation. - */ - private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes) { - encode3to4(threeBytes, 0, numSigBytes, b4, 0); - return b4; - } // end encode3to4 - - - /** - * Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the destination array - */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset) { - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index ALPHABET - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) - | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) - | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - - switch (numSigBytes) { - case 3: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; - return destination; - - case 2: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - case 1: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - default: - return destination; - } // end switch - } // end encode3to4 - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. If the object - * cannot be serialized or there is another error, - * the method will return null. - * The object is not GZip-compressed before being encoded. - * - * @param serializableObject The object to encode - * @return The Base64-encoded object - */ - public static String encodeObject(java.io.Serializable serializableObject) { - return encodeObject(serializableObject, NO_OPTIONS); - } // end encodeObject - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. If the object - * cannot be serialized or there is another error, - * the method will return null. - *

                      - * Valid options:

                      -     *   GZIP: gzip-compresses object before encoding it.
                      -     *   DONT_BREAK_LINES: don't break lines at 76 characters
                      -     *     Note: Technically, this makes your encoding non-compliant.
                      -     * 
                      - *

                      - * Example: encodeObject( myObj, Base64.GZIP ) or - *

                      - * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) - * - * @param serializableObject The object to encode - * @param options Specified options - * @return The Base64-encoded object - * @see Base64#GZIP - * @see Base64#DONT_BREAK_LINES - */ - public static String encodeObject(java.io.Serializable serializableObject, int options) { - // Streams - java.io.ByteArrayOutputStream baos = null; - java.io.OutputStream b64os = null; - java.io.ObjectOutputStream oos = null; - java.util.zip.GZIPOutputStream gzos = null; - - // Isolate options - int gzip = (options & GZIP); - int dontBreakLines = (options & DONT_BREAK_LINES); - - try { - // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines); - - // GZip? - if (gzip == GZIP) { - gzos = new java.util.zip.GZIPOutputStream(b64os); - oos = new java.io.ObjectOutputStream(gzos); - } // end if: gzip - else - oos = new java.io.ObjectOutputStream(b64os); - - oos.writeObject(serializableObject); - } // end try - catch (java.io.IOException e) { - e.printStackTrace(); - return null; - } // end catch - finally { - try { - oos.close(); - } catch (Exception e) { - } - try { - gzos.close(); - } catch (Exception e) { - } - try { - b64os.close(); - } catch (Exception e) { - } - try { - baos.close(); - } catch (Exception e) { - } - } // end finally - - // Return value according to relevant encoding. - try { - return new String(baos.toByteArray(), PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uue) { - return new String(baos.toByteArray()); - } // end catch - - } // end encode - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - * @param source The data to convert - */ - public static String encodeBytes(byte[] source) { - return encodeBytes(source, 0, source.length, NO_OPTIONS); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - *

                      - * Valid options:

                      -     *   GZIP: gzip-compresses object before encoding it.
                      -     *   DONT_BREAK_LINES: don't break lines at 76 characters
                      -     *     Note: Technically, this makes your encoding non-compliant.
                      -     * 
                      - *

                      - * Example: encodeBytes( myData, Base64.GZIP ) or - *

                      - * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) - * - * @param source The data to convert - * @param options Specified options - * @see Base64#GZIP - * @see Base64#DONT_BREAK_LINES - */ - public static String encodeBytes(byte[] source, int options) { - return encodeBytes(source, 0, source.length, options); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - */ - public static String encodeBytes(byte[] source, int off, int len) { - return encodeBytes(source, off, len, NO_OPTIONS); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - *

                      - * Valid options:

                      -     *   GZIP: gzip-compresses object before encoding it.
                      -     *   DONT_BREAK_LINES: don't break lines at 76 characters
                      -     *     Note: Technically, this makes your encoding non-compliant.
                      -     * 
                      - *

                      - * Example: encodeBytes( myData, Base64.GZIP ) or - *

                      - * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @see Base64#GZIP - * @see Base64#DONT_BREAK_LINES - */ - public static String encodeBytes(byte[] source, int off, int len, int options) { - // Isolate options - int dontBreakLines = (options & DONT_BREAK_LINES); - int gzip = (options & GZIP); - - // Compress? - if (gzip == GZIP) { - java.io.ByteArrayOutputStream baos = null; - java.util.zip.GZIPOutputStream gzos = null; - Base64.OutputStream b64os = null; - - - try { - // GZip -> Base64 -> ByteArray - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines); - gzos = new java.util.zip.GZIPOutputStream(b64os); - - gzos.write(source, off, len); - gzos.close(); - } // end try - catch (java.io.IOException e) { - e.printStackTrace(); - return null; - } // end catch - finally { - try { - gzos.close(); - } catch (Exception e) { - } - try { - b64os.close(); - } catch (Exception e) { - } - try { - baos.close(); - } catch (Exception e) { - } - } // end finally - - // Return value according to relevant encoding. - try { - return new String(baos.toByteArray(), PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uue) { - return new String(baos.toByteArray()); - } // end catch - } // end if: compress - - // Else, don't compress. Better not to use streams at all then. - else { - // Convert option to boolean in way that code likes it. - boolean breakLines = dontBreakLines == 0; - - int len43 = len * 4 / 3; - byte[] outBuff = new byte[(len43) // Main 4:3 - + ((len % 3) > 0 ? 4 : 0) // Account for padding - + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for (; d < len2; d += 3, e += 4) { - encode3to4(source, d + off, 3, outBuff, e); - - lineLength += 4; - if (breakLines && lineLength == MAX_LINE_LENGTH) { - outBuff[e + 4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // en dfor: each piece of array - - if (d < len) { - encode3to4(source, d + off, len - d, outBuff, e); - e += 4; - } // end if: some padding needed - - - // Return value according to relevant encoding. - try { - return new String(outBuff, 0, e, PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uue) { - return new String(outBuff, 0, e); - } // end catch - - } // end else: don't compress - - } // end encodeBytes - - - - - - /* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the number of decoded bytes converted - */ - private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) { - // Example: Dk== - if (source[srcOffset + 2] == EQUALS_SIGN) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); - - destination[destOffset] = (byte) (outBuff >>> 16); - return 1; - } - - // Example: DkL= - else if (source[srcOffset + 3] == EQUALS_SIGN) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) - | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); - - destination[destOffset] = (byte) (outBuff >>> 16); - destination[destOffset + 1] = (byte) (outBuff >>> 8); - return 2; - } - - // Example: DkLE - else { - try { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) - | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) - | ((DECODABET[source[srcOffset + 3]] & 0xFF)); - - - destination[destOffset] = (byte) (outBuff >> 16); - destination[destOffset + 1] = (byte) (outBuff >> 8); - destination[destOffset + 2] = (byte) (outBuff); - - return 3; - } catch (Exception e) { - io.starter.toolkit.Logger.log("" + source[srcOffset] + ": " + (DECODABET[source[srcOffset]])); - io.starter.toolkit.Logger.log("" + source[srcOffset + 1] + ": " + (DECODABET[source[srcOffset + 1]])); - io.starter.toolkit.Logger.log("" + source[srcOffset + 2] + ": " + (DECODABET[source[srcOffset + 2]])); - io.starter.toolkit.Logger.log("" + source[srcOffset + 3] + ": " + (DECODABET[source[srcOffset + 3]])); - return -1; - } //e nd catch - } - } // end decodeToBytes - - - /** - * Very low-level access to decoding ASCII characters in - * the form of a byte array. Does not support automatically - * gunzipping or any other "fancy" features. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @return decoded data - */ - public static byte[] decode(byte[] source, int off, int len) { - int len34 = len * 3 / 4; - byte[] outBuff = new byte[len34]; // Upper limit on size of output - int outBuffPosn = 0; - - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i = 0; - byte sbiCrop = 0; - byte sbiDecode = 0; - for (i = off; i < off + len; i++) { - sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits - sbiDecode = DECODABET[sbiCrop]; - - if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better - { - if (sbiDecode >= EQUALS_SIGN_ENC) { - b4[b4Posn++] = sbiCrop; - if (b4Posn > 3) { - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if (sbiCrop == EQUALS_SIGN) - break; - } // end if: quartet built - - } // end if: equals sign or better - - } // end if: white space, equals sign or better - else { - System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)"); - return null; - } // end else: - } // each input character - - byte[] out = new byte[outBuffPosn]; - System.arraycopy(outBuff, 0, out, 0, outBuffPosn); - return out; - } // end decode - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @return the decoded data - */ - public static byte[] decode(String s) { - byte[] bytes; - try { - bytes = s.getBytes(PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uee) { - bytes = s.getBytes(); - } // end catch - // - - // Decode - bytes = decode(bytes, 0, bytes.length); - - - // Check to see if it's gzip-compressed - // GZIP Magic Two-Byte Number: 0x8b1f (35615) - if (bytes != null && bytes.length >= 4) { - - int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { - java.io.ByteArrayInputStream bais = null; - java.util.zip.GZIPInputStream gzis = null; - java.io.ByteArrayOutputStream baos = null; - byte[] buffer = new byte[2048]; - int length = 0; - - try { - baos = new java.io.ByteArrayOutputStream(); - bais = new java.io.ByteArrayInputStream(bytes); - gzis = new java.util.zip.GZIPInputStream(bais); - - while ((length = gzis.read(buffer)) >= 0) { - baos.write(buffer, 0, length); - } // end while: reading input - - // No error? Get new bytes. - bytes = baos.toByteArray(); - - } // end try - catch (java.io.IOException e) { - // Just return originally-decoded bytes - } // end catch - finally { - try { - baos.close(); - } catch (Exception e) { - } - try { - gzis.close(); - } catch (Exception e) { - } - try { - bais.close(); - } catch (Exception e) { - } - } // end finally - - } // end if: gzipped - } // end if: bytes.length >= 2 - - return bytes; - } // end decode - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * - * @param encodedObject The Base64 data to decode - * @return The decoded and deserialized object - */ - public static Object decodeToObject(String encodedObject) { - // Decode and gunzip if necessary - byte[] objBytes = decode(encodedObject); - - java.io.ByteArrayInputStream bais = null; - java.io.ObjectInputStream ois = null; - Object obj = null; - - try { - bais = new java.io.ByteArrayInputStream(objBytes); - ois = new java.io.ObjectInputStream(bais); - - obj = ois.readObject(); - } // end try - catch (java.io.IOException e) { - e.printStackTrace(); - obj = null; - } // end catch - catch (java.lang.ClassNotFoundException e) { - e.printStackTrace(); - obj = null; - } // end catch - finally { - try { - bais.close(); - } catch (Exception e) { - } - try { - ois.close(); - } catch (Exception e) { - } - } // end finally - - return obj; - } // end decodeObject - - - /** - * Convenience method for encoding data to a file. - * - * @param dataToEncode byte array of data to encode in base64 form - * @param filename Filename for saving encoded data - * @return true if successful, false otherwise - */ - public static boolean encodeToFile(byte[] dataToEncode, String filename) { - boolean success = false; - Base64.OutputStream bos = null; - try { - bos = new Base64.OutputStream( - new java.io.FileOutputStream(filename), Base64.ENCODE); - bos.write(dataToEncode); - success = true; - } // end try - catch (java.io.IOException e) { - - success = false; - } // end catch: IOException - finally { - try { - bos.close(); - } catch (Exception e) { - } - } // end finally - - return success; - } // end encodeToFile - - - /** - * Convenience method for decoding data to a file. - * - * @param dataToDecode Base64-encoded data as a string - * @param filename Filename for saving decoded data - * @return true if successful, false otherwise - */ - public static boolean decodeToFile(String dataToDecode, String filename) { - boolean success = false; - Base64.OutputStream bos = null; - try { - bos = new Base64.OutputStream( - new java.io.FileOutputStream(filename), Base64.DECODE); - bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); - success = true; - } // end try - catch (java.io.IOException e) { - success = false; - } // end catch: IOException - finally { - try { - bos.close(); - } catch (Exception e) { - } - } // end finally - - return success; - } // end decodeToFile - - - /** - * Convenience method for reading a base64-encoded - * file and decoding it. - * - * @param filename Filename for reading encoded data - * @return decoded byte array or null if unsuccessful - */ - public static byte[] decodeFromFile(String filename) { - byte[] decodedData = null; - Base64.InputStream bis = null; - try { - // Set up some useful variables - java.io.File file = new java.io.File(filename); - byte[] buffer = null; - int length = 0; - int numBytes = 0; - - // Check for size of file - if (file.length() > Integer.MAX_VALUE) { - System.err.println("File is too big for this convenience method (" + file.length() + " bytes)."); - return null; - } // end if: file too big for int index - buffer = new byte[(int) file.length()]; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream(file)), Base64.DECODE); - - // Read until done - while ((numBytes = bis.read(buffer, length, 4096)) >= 0) - length += numBytes; - - // Save in a variable to return - decodedData = new byte[length]; - System.arraycopy(buffer, 0, decodedData, 0, length); - - } // end try - catch (java.io.IOException e) { - System.err.println("Error decoding from file " + filename); - } // end catch: IOException - finally { - try { - bis.close(); - } catch (Exception e) { - } - } // end finally - - return decodedData; - } // end decodeFromFile - - - /** - * Convenience method for reading a binary file - * and base64-encoding it. - * - * @param filename Filename for reading binary data - * @return base64-encoded string or null if unsuccessful - */ - public static String encodeFromFile(String filename) { - String encodedData = null; - Base64.InputStream bis = null; - try { - // Set up some useful variables - java.io.File file = new java.io.File(filename); - byte[] buffer = new byte[(int) (file.length() * 1.4)]; - int length = 0; - int numBytes = 0; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream(file)), Base64.ENCODE); - - // Read until done - while ((numBytes = bis.read(buffer, length, 4096)) >= 0) - length += numBytes; - - // Save in a variable to return - encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING); - - } // end try - catch (java.io.IOException e) { - System.err.println("Error encoding from file " + filename); - } // end catch: IOException - finally { - try { - bis.close(); - } catch (Exception e) { - } - } // end finally - - return encodedData; - } // end encodeFromFile - - - - - /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ - - - /** - * A {@link Base64.InputStream} will read data from another - * java.io.InputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - */ - public static class InputStream extends java.io.FilterInputStream { - private boolean encode; // Encoding or decoding - private int position; // Current position in the buffer - private byte[] buffer; // Small buffer holding converted data - private int bufferLength; // Length of buffer (3 or 4) - private int numSigBytes; // Number of meaningful bytes in the buffer - private int lineLength; - private boolean breakLines; // Break lines at less than 80 characters - - - /** - * Constructs a {@link Base64.InputStream} in DECODE mode. - * - * @param in the java.io.InputStream from which to read data. - */ - public InputStream(java.io.InputStream in) { - this(in, DECODE); - } // end constructor - - - /** - * Constructs a {@link Base64.InputStream} in - * either ENCODE or DECODE mode. - *

                      - * Valid options:

                      -         *   ENCODE or DECODE: Encode or Decode as data is read.
                      -         *   DONT_BREAK_LINES: don't break lines at 76 characters
                      -         *     (only meaningful when encoding)
                      -         *     Note: Technically, this makes your encoding non-compliant.
                      -         * 
                      - *

                      - * Example: new Base64.InputStream( in, Base64.DECODE ) - * - * @param in the java.io.InputStream from which to read data. - * @param options Specified options - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DONT_BREAK_LINES - */ - public InputStream(java.io.InputStream in, int options) { - super(in); - this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; - this.encode = (options & ENCODE) == ENCODE; - this.bufferLength = encode ? 4 : 3; - this.buffer = new byte[bufferLength]; - this.position = -1; - this.lineLength = 0; - } // end constructor - - /** - * Reads enough of the input stream to convert - * to/from Base64 and returns the next byte. - * - * @return next byte - */ - public int read() throws java.io.IOException { - // Do we need to get data? - if (position < 0) { - if (encode) { - byte[] b3 = new byte[3]; - int numBinaryBytes = 0; - for (int i = 0; i < 3; i++) { - try { - int b = in.read(); - - // If end of stream, b is -1. - if (b >= 0) { - b3[i] = (byte) b; - numBinaryBytes++; - } // end if: not end of stream - - } // end try: read - catch (java.io.IOException e) { - // Only a problem if we got no data at all. - if (i == 0) - throw e; - - } // end catch - } // end for: each needed input byte - - if (numBinaryBytes > 0) { - encode3to4(b3, 0, numBinaryBytes, buffer, 0); - position = 0; - numSigBytes = 4; - } // end if: got data - else { - return -1; - } // end else - } // end if: encoding - - // Else decoding - else { - byte[] b4 = new byte[4]; - int i = 0; - for (i = 0; i < 4; i++) { - // Read four "meaningful" bytes: - int b = 0; - do { - b = in.read(); - } - while (b >= 0 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC); - - if (b < 0) - break; // Reads a -1 if end of stream - - b4[i] = (byte) b; - } // end for: each needed input byte - - if (i == 4) { - numSigBytes = decode4to3(b4, 0, buffer, 0); - position = 0; - } // end if: got four characters - else if (i == 0) { - return -1; - } // end else if: also padded correctly - else { - // Must have broken out from above. - throw new java.io.IOException("Improperly padded Base64 input."); - } // end - - } // end else: decode - } // end else: get data - - // Got data? - if (position >= 0) { - // End of relevant data? - if ( /*!encode &&*/ position >= numSigBytes) - return -1; - - if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { - lineLength = 0; - return '\n'; - } // end if - else { - lineLength++; // This isn't important when decoding - // but throwing an extra "if" seems - // just as wasteful. - - int b = buffer[position++]; - - if (position >= bufferLength) - position = -1; - - return b & 0xFF; // This is how you "cast" a byte that's - // intended to be unsigned. - } // end else - } // end if: position >= 0 - - // Else error - else { - // When JDK1.4 is more accepted, use an assertion here. - throw new java.io.IOException("Error in Base64 code reading stream."); - } // end else - } // end read - - - /** - * Calls {@link #read()} repeatedly until the end of stream - * is reached or len bytes are read. - * Returns number of bytes read into array or -1 if - * end of stream is encountered. - * - * @param dest array to hold values - * @param off offset for array - * @param len max number of bytes to read into array - * @return bytes read into array or -1 if end of stream is encountered. - */ - public int read(byte[] dest, int off, int len) throws java.io.IOException { - int i; - int b; - for (i = 0; i < len; i++) { - b = read(); - - //if( b < 0 && i == 0 ) - // return -1; - - if (b >= 0) - dest[off + i] = (byte) b; - else if (i == 0) - return -1; - else - break; // Out of 'for' loop - } // end for: each byte read - return i; - } // end read - - } // end inner class InputStream - - - - - - - /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ - - - /** - * A {@link Base64.OutputStream} will write data to another - * java.io.OutputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - */ - public static class OutputStream extends java.io.FilterOutputStream { - private boolean encode; - private int position; - private byte[] buffer; - private int bufferLength; - private int lineLength; - private boolean breakLines; - private byte[] b4; // Scratch used in a few places - private boolean suspendEncoding; - - /** - * Constructs a {@link Base64.OutputStream} in ENCODE mode. - * - * @param out the java.io.OutputStream to which data will be written. - */ - public OutputStream(java.io.OutputStream out) { - this(out, ENCODE); - } // end constructor - - - /** - * Constructs a {@link Base64.OutputStream} in - * either ENCODE or DECODE mode. - *

                      - * Valid options:

                      -         *   ENCODE or DECODE: Encode or Decode as data is read.
                      -         *   DONT_BREAK_LINES: don't break lines at 76 characters
                      -         *     (only meaningful when encoding)
                      -         *     Note: Technically, this makes your encoding non-compliant.
                      -         * 
                      - *

                      - * Example: new Base64.OutputStream( out, Base64.ENCODE ) - * - * @param out the java.io.OutputStream to which data will be written. - * @param options Specified options. - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DONT_BREAK_LINES - */ - public OutputStream(java.io.OutputStream out, int options) { - super(out); - this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; - this.encode = (options & ENCODE) == ENCODE; - this.bufferLength = encode ? 3 : 4; - this.buffer = new byte[bufferLength]; - this.position = 0; - this.lineLength = 0; - this.suspendEncoding = false; - this.b4 = new byte[4]; - } // end constructor - - - /** - * Writes the byte to the output stream after - * converting to/from Base64 notation. - * When encoding, bytes are buffered three - * at a time before the output stream actually - * gets a write() call. - * When decoding, bytes are buffered four - * at a time. - * - * @param theByte the byte to write - */ - public void write(int theByte) throws java.io.IOException { - // Encoding suspended? - if (suspendEncoding) { - super.out.write(theByte); - return; - } // end if: supsended - - // Encode? - if (encode) { - buffer[position++] = (byte) theByte; - if (position >= bufferLength) // Enough to encode. - { - out.write(encode3to4(b4, buffer, bufferLength)); - - lineLength += 4; - if (breakLines && lineLength >= MAX_LINE_LENGTH) { - out.write(NEW_LINE); - lineLength = 0; - } // end if: end of line - - position = 0; - } // end if: enough to output - } // end if: encoding - - // Else, Decoding - else { - // Meaningful Base64 character? - if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) { - buffer[position++] = (byte) theByte; - if (position >= bufferLength) // Enough to output. - { - int len = Base64.decode4to3(buffer, 0, b4, 0); - out.write(b4, 0, len); - //out.write( Base64.decode4to3( buffer ) ); - position = 0; - } // end if: enough to output - } // end if: meaningful base64 character - else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) { - throw new java.io.IOException("Invalid character in Base64 data."); - } // end else: not white space either - } // end else: decoding - } // end write - - - /** - * Calls {@link #write(int)} repeatedly until len - * bytes are written. - * - * @param theBytes array from which to read bytes - * @param off offset for array - * @param len max number of bytes to read into array - */ - public void write(byte[] theBytes, int off, int len) throws java.io.IOException { - // Encoding suspended? - if (suspendEncoding) { - super.out.write(theBytes, off, len); - return; - } // end if: supsended - - for (int i = 0; i < len; i++) { - write(theBytes[off + i]); - } // end for: each byte written - - } // end write - - - /** - * Method added by PHIL. [Thanks, PHIL. -Rob] - * This pads the buffer without closing the stream. - */ - public void flushBase64() throws java.io.IOException { - if (position > 0) { - if (encode) { - out.write(encode3to4(b4, buffer, position)); - position = 0; - } // end if: encoding - else { - throw new java.io.IOException("Base64 input not properly padded."); - } // end else: decoding - } // end if: buffer partially full - - } // end flush - - - /** - * Flushes and closes (I think, in the superclass) the stream. - */ - public void close() throws java.io.IOException { - // 1. Ensure that pending characters are written - flushBase64(); - - // 2. Actually close the stream - // Base class both flushes and closes. - super.close(); - - buffer = null; - out = null; - } // end close - - - /** - * Suspends encoding of the stream. - * May be helpful if you need to embed a piece of - * base640-encoded data in a stream. - */ - public void suspendEncoding() throws java.io.IOException { - flushBase64(); - this.suspendEncoding = true; - } // end suspendEncoding - - - /** - * Resumes encoding of the stream. - * May be helpful if you need to embed a piece of - * base640-encoded data in a stream. - */ - public void resumeEncoding() { - this.suspendEncoding = false; - } // end resumeEncoding - - - } // end inner class OutputStream - - -} // end class Base64 diff --git a/src/main/java/io/starter/toolkit/Base64.kt b/src/main/java/io/starter/toolkit/Base64.kt new file mode 100644 index 0000000..b2ca14a --- /dev/null +++ b/src/main/java/io/starter/toolkit/Base64.kt @@ -0,0 +1,1380 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +/** + * Encodes and decodes to and from Base64 notation. + * + * + * + * Change Log: + * + * + * * v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files. + * * v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC). + * * v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte. + * * v2.0 - I got rid of methods that used booleans to set options. + * Now everything is more consolidated and cleaner. The code now detects + * when data that's being decoded is gzip-compressed and will decompress it + * automatically. Generally things are cleaner. You'll probably have to + * change some method calls that you were making to support the new + * options format (ints that you "OR" together). + * * v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file). + * * v1.5 - Output stream pases on flush() command but doesn't do anything itself. + * This helps when using GZIP streams. + * Added the ability to GZip-compress objects before encoding them. + * * v1.4 - Added helper methods to read/write files. + * * v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset. + * * v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream + * where last buffer being read, if not completely full, was not returned. + * * v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time. + * * v1.3.3 - Fixed I/O streams which were totally messed up. + * + * + * + * + * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit [http://iharder.net/base64](http://iharder.net/base64) + * periodically to check for updates or to contribute improvements. + * + * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.1 + */ +object Base64 { + + /* ******** P U B L I C F I E L D S ******** */ + + + /** + * No options specified. Value is zero. + */ + val NO_OPTIONS = 0 + + /** + * Specify encoding. + */ + val ENCODE = 1 + + + /** + * Specify decoding. + */ + val DECODE = 0 + + + /** + * Specify that data should be gzip-compressed. + */ + val GZIP = 2 + + + /** + * Don't break lines when encoding (violates strict Base64 specification) + */ + val DONT_BREAK_LINES = 8 + + + /* ******** P R I V A T E F I E L D S ******** */ + + + /** + * Maximum line length (76) of Base64 output. + */ + private val MAX_LINE_LENGTH = 76 + + + /** + * The equals sign (=) as a byte. + */ + private val EQUALS_SIGN = '='.toByte() + + + /** + * The new line character (\n) as a byte. + */ + private val NEW_LINE = '\n'.toByte() + + + /** + * Preferred encoding. + */ + private val PREFERRED_ENCODING = "UTF-8" + + + /** + * The 64 valid Base64 values. + */ + private val ALPHABET: ByteArray + private val _NATIVE_ALPHABET = byteArrayOf('A'.toByte(), 'B'.toByte(), 'C'.toByte(), 'D'.toByte(), 'E'.toByte(), 'F'.toByte(), 'G'.toByte(), 'H'.toByte(), 'I'.toByte(), 'J'.toByte(), 'K'.toByte(), 'L'.toByte(), 'M'.toByte(), 'N'.toByte(), 'O'.toByte(), 'P'.toByte(), 'Q'.toByte(), 'R'.toByte(), 'S'.toByte(), 'T'.toByte(), 'U'.toByte(), 'V'.toByte(), 'W'.toByte(), 'X'.toByte(), 'Y'.toByte(), 'Z'.toByte(), 'a'.toByte(), 'b'.toByte(), 'c'.toByte(), 'd'.toByte(), 'e'.toByte(), 'f'.toByte(), 'g'.toByte(), 'h'.toByte(), 'i'.toByte(), 'j'.toByte(), 'k'.toByte(), 'l'.toByte(), 'm'.toByte(), 'n'.toByte(), 'o'.toByte(), 'p'.toByte(), 'q'.toByte(), 'r'.toByte(), 's'.toByte(), 't'.toByte(), 'u'.toByte(), 'v'.toByte(), 'w'.toByte(), 'x'.toByte(), 'y'.toByte(), 'z'.toByte(), '0'.toByte(), '1'.toByte(), '2'.toByte(), '3'.toByte(), '4'.toByte(), '5'.toByte(), '6'.toByte(), '7'.toByte(), '8'.toByte(), '9'.toByte(), '+'.toByte(), '/'.toByte())/* May be something funny like EBCDIC */ + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + */ + private val DECODABET = byteArrayOf(-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9 // Decimal 123 - 126 + )/*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + + // I think I end up not using the BAD_ENCODING indicator. + //private final static byte BAD_ENCODING = -9; // Indicates error in encoding + private val WHITE_SPACE_ENC: Byte = -5 // Indicates white space in encoding + private val EQUALS_SIGN_ENC: Byte = -1 // Indicates equals sign in encoding + + /** Determine which ALPHABET to use. */ + init { + var __bytes: ByteArray + try { + __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toByteArray(charset(PREFERRED_ENCODING)) + } // end try + catch (use: java.io.UnsupportedEncodingException) { + __bytes = _NATIVE_ALPHABET // Fall back to native encoding + } + // end catch + ALPHABET = __bytes + } // end static + + + /* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + */ + private fun encode3to4(b4: ByteArray, threeBytes: ByteArray?, numSigBytes: Int): ByteArray { + encode3to4(threeBytes, 0, numSigBytes, b4, 0) + return b4 + } // end encode3to4 + + + /** + * Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + */ + private fun encode3to4( + source: ByteArray?, srcOffset: Int, numSigBytes: Int, + destination: ByteArray, destOffset: Int): ByteArray { + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + val inBuff = ((if (numSigBytes > 0) (source!![srcOffset] shl 24).ushr(8) else 0) + or (if (numSigBytes > 1) (source!![srcOffset + 1] shl 24).ushr(16) else 0) + or if (numSigBytes > 2) (source!![srcOffset + 2] shl 24).ushr(24) else 0) + + when (numSigBytes) { + 3 -> { + destination[destOffset] = ALPHABET[inBuff.ushr(18)] + destination[destOffset + 1] = ALPHABET[inBuff.ushr(12) and 0x3f] + destination[destOffset + 2] = ALPHABET[inBuff.ushr(6) and 0x3f] + destination[destOffset + 3] = ALPHABET[inBuff and 0x3f] + return destination + } + + 2 -> { + destination[destOffset] = ALPHABET[inBuff.ushr(18)] + destination[destOffset + 1] = ALPHABET[inBuff.ushr(12) and 0x3f] + destination[destOffset + 2] = ALPHABET[inBuff.ushr(6) and 0x3f] + destination[destOffset + 3] = EQUALS_SIGN + return destination + } + + 1 -> { + destination[destOffset] = ALPHABET[inBuff.ushr(18)] + destination[destOffset + 1] = ALPHABET[inBuff.ushr(12) and 0x3f] + destination[destOffset + 2] = EQUALS_SIGN + destination[destOffset + 3] = EQUALS_SIGN + return destination + } + + else -> return destination + } // end switch + } // end encode3to4 + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + * + * + * Valid options:

                      +     * GZIP: gzip-compresses object before encoding it.
                      +     * DONT_BREAK_LINES: don't break lines at 76 characters
                      +     * *Note: Technically, this makes your encoding non-compliant.*
                      +    
                      * + * + * + * Example: `encodeObject( myObj, Base64.GZIP )` or + * + * + * Example: `encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )` + * + * @param serializableObject The object to encode + * @param options Specified options + * @return The Base64-encoded object + * @see Base64.GZIP + * + * @see Base64.DONT_BREAK_LINES + */ + @JvmOverloads + fun encodeObject(serializableObject: java.io.Serializable, options: Int = NO_OPTIONS): String? { + // Streams + var baos: java.io.ByteArrayOutputStream? = null + var b64os: java.io.OutputStream? = null + var oos: java.io.ObjectOutputStream? = null + var gzos: java.util.zip.GZIPOutputStream? = null + + // Isolate options + val gzip = options and GZIP + val dontBreakLines = options and DONT_BREAK_LINES + + try { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = java.io.ByteArrayOutputStream() + b64os = Base64.OutputStream(baos, ENCODE or dontBreakLines) + + // GZip? + if (gzip == GZIP) { + gzos = java.util.zip.GZIPOutputStream(b64os) + oos = java.io.ObjectOutputStream(gzos) + } // end if: gzip + else + oos = java.io.ObjectOutputStream(b64os) + + oos.writeObject(serializableObject) + } // end try + catch (e: java.io.IOException) { + e.printStackTrace() + return null + } // end catch + finally { + try { + oos!!.close() + } catch (e: Exception) { + } + + try { + gzos!!.close() + } catch (e: Exception) { + } + + try { + b64os!!.close() + } catch (e: Exception) { + } + + try { + baos!!.close() + } catch (e: Exception) { + } + + } // end finally + + // Return value according to relevant encoding. + try { + return String(baos!!.toByteArray(), PREFERRED_ENCODING) + } // end try + catch (uue: java.io.UnsupportedEncodingException) { + return String(baos!!.toByteArray()) + } + // end catch + + } // end encode + + + /** + * Encodes a byte array into Base64 notation. + * + * + * Valid options:
                      +     * GZIP: gzip-compresses object before encoding it.
                      +     * DONT_BREAK_LINES: don't break lines at 76 characters
                      +     * *Note: Technically, this makes your encoding non-compliant.*
                      +    
                      * + * + * + * Example: `encodeBytes( myData, Base64.GZIP )` or + * + * + * Example: `encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )` + * + * @param source The data to convert + * @param options Specified options + * @see Base64.GZIP + * + * @see Base64.DONT_BREAK_LINES + */ + fun encodeBytes(source: ByteArray, options: Int): String? { + return encodeBytes(source, 0, source.size, options) + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * + * + * Valid options:
                      +     * GZIP: gzip-compresses object before encoding it.
                      +     * DONT_BREAK_LINES: don't break lines at 76 characters
                      +     * *Note: Technically, this makes your encoding non-compliant.*
                      +    
                      * + * + * + * Example: `encodeBytes( myData, Base64.GZIP )` or + * + * + * Example: `encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )` + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @see Base64.GZIP + * + * @see Base64.DONT_BREAK_LINES + */ + @JvmOverloads + fun encodeBytes(source: ByteArray, off: Int = 0, len: Int = source.size, options: Int = NO_OPTIONS): String? { + // Isolate options + val dontBreakLines = options and DONT_BREAK_LINES + val gzip = options and GZIP + + // Compress? + if (gzip == GZIP) { + var baos: java.io.ByteArrayOutputStream? = null + var gzos: java.util.zip.GZIPOutputStream? = null + var b64os: Base64.OutputStream? = null + + + try { + // GZip -> Base64 -> ByteArray + baos = java.io.ByteArrayOutputStream() + b64os = Base64.OutputStream(baos, ENCODE or dontBreakLines) + gzos = java.util.zip.GZIPOutputStream(b64os) + + gzos.write(source, off, len) + gzos.close() + } // end try + catch (e: java.io.IOException) { + e.printStackTrace() + return null + } // end catch + finally { + try { + gzos!!.close() + } catch (e: Exception) { + } + + try { + b64os!!.close() + } catch (e: Exception) { + } + + try { + baos!!.close() + } catch (e: Exception) { + } + + } // end finally + + // Return value according to relevant encoding. + try { + return String(baos!!.toByteArray(), PREFERRED_ENCODING) + } // end try + catch (uue: java.io.UnsupportedEncodingException) { + return String(baos!!.toByteArray()) + } + // end catch + } // end if: compress + else { + // Convert option to boolean in way that code likes it. + val breakLines = dontBreakLines == 0 + + val len43 = len * 4 / 3 + val outBuff = ByteArray(len43 // Main 4:3 + + + (if (len % 3 > 0) 4 else 0) // Account for padding + + + if (breakLines) len43 / MAX_LINE_LENGTH else 0) // New lines + var d = 0 + var e = 0 + val len2 = len - 2 + var lineLength = 0 + while (d < len2) { + encode3to4(source, d + off, 3, outBuff, e) + + lineLength += 4 + if (breakLines && lineLength == MAX_LINE_LENGTH) { + outBuff[e + 4] = NEW_LINE + e++ + lineLength = 0 + } // end if: end of line + d += 3 + e += 4 + } // en dfor: each piece of array + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e) + e += 4 + } // end if: some padding needed + + + // Return value according to relevant encoding. + try { + return String(outBuff, 0, e, PREFERRED_ENCODING) + } // end try + catch (uue: java.io.UnsupportedEncodingException) { + return String(outBuff, 0, e) + } + // end catch + + }// Else, don't compress. Better not to use streams at all then. + // end else: don't compress + + } // end encodeBytes + + + /* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the number of decoded bytes converted + */ + private fun decode4to3(source: ByteArray, srcOffset: Int, destination: ByteArray, destOffset: Int): Int { + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + val outBuff = DECODABET[source[srcOffset]] and 0xFF shl 18 or (DECODABET[source[srcOffset + 1]] and 0xFF shl 12) + + destination[destOffset] = outBuff.ushr(16).toByte() + return 1 + } else if (source[srcOffset + 3] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + val outBuff = (DECODABET[source[srcOffset]] and 0xFF shl 18 + or (DECODABET[source[srcOffset + 1]] and 0xFF shl 12) + or (DECODABET[source[srcOffset + 2]] and 0xFF shl 6)) + + destination[destOffset] = outBuff.ushr(16).toByte() + destination[destOffset + 1] = outBuff.ushr(8).toByte() + return 2 + } else { + try { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + val outBuff = (DECODABET[source[srcOffset]] and 0xFF shl 18 + or (DECODABET[source[srcOffset + 1]] and 0xFF shl 12) + or (DECODABET[source[srcOffset + 2]] and 0xFF shl 6) + or (DECODABET[source[srcOffset + 3]] and 0xFF)) + + + destination[destOffset] = (outBuff shr 16).toByte() + destination[destOffset + 1] = (outBuff shr 8).toByte() + destination[destOffset + 2] = outBuff.toByte() + + return 3 + } catch (e: Exception) { + io.starter.toolkit.Logger.log("" + source[srcOffset] + ": " + DECODABET[source[srcOffset]]) + io.starter.toolkit.Logger.log("" + source[srcOffset + 1] + ": " + DECODABET[source[srcOffset + 1]]) + io.starter.toolkit.Logger.log("" + source[srcOffset + 2] + ": " + DECODABET[source[srcOffset + 2]]) + io.starter.toolkit.Logger.log("" + source[srcOffset + 3] + ": " + DECODABET[source[srcOffset + 3]]) + return -1 + } + //e nd catch + }// Example: DkLE + // Example: DkL= + } // end decodeToBytes + + + /** + * Very low-level access to decoding ASCII characters in + * the form of a byte array. Does not support automatically + * gunzipping or any other "fancy" features. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @return decoded data + */ + fun decode(source: ByteArray, off: Int, len: Int): ByteArray? { + val len34 = len * 3 / 4 + val outBuff = ByteArray(len34) // Upper limit on size of output + var outBuffPosn = 0 + + val b4 = ByteArray(4) + var b4Posn = 0 + var i = 0 + var sbiCrop: Byte = 0 + var sbiDecode: Byte = 0 + i = off + while (i < off + len) { + sbiCrop = (source[i] and 0x7f).toByte() // Only the low seven bits + sbiDecode = DECODABET[sbiCrop] + + if (sbiDecode >= WHITE_SPACE_ENC) + // White space, Equals sign or better + { + if (sbiDecode >= EQUALS_SIGN_ENC) { + b4[b4Posn++] = sbiCrop + if (b4Posn > 3) { + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn) + b4Posn = 0 + + // If that was the equals sign, break out of 'for' loop + if (sbiCrop == EQUALS_SIGN) + break + } // end if: quartet built + + } // end if: equals sign or better + + } // end if: white space, equals sign or better + else { + System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)") + return null + } // end else: + i++ + } // each input character + + val out = ByteArray(outBuffPosn) + System.arraycopy(outBuff, 0, out, 0, outBuffPosn) + return out + } // end decode + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @return the decoded data + */ + fun decode(s: String): ByteArray? { + var bytes: ByteArray? + try { + bytes = s.toByteArray(charset(PREFERRED_ENCODING)) + } // end try + catch (uee: java.io.UnsupportedEncodingException) { + bytes = s.toByteArray() + } + // end catch + // + + // Decode + bytes = decode(bytes, 0, bytes!!.size) + + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + if (bytes != null && bytes.size >= 4) { + + val head = bytes[0].toInt() and 0xff or (bytes[1] shl 8 and 0xff00) + if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { + var bais: java.io.ByteArrayInputStream? = null + var gzis: java.util.zip.GZIPInputStream? = null + var baos: java.io.ByteArrayOutputStream? = null + val buffer = ByteArray(2048) + var length = 0 + + try { + baos = java.io.ByteArrayOutputStream() + bais = java.io.ByteArrayInputStream(bytes) + gzis = java.util.zip.GZIPInputStream(bais) + + while ((length = gzis.read(buffer)) >= 0) { + baos.write(buffer, 0, length) + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray() + + } // end try + catch (e: java.io.IOException) { + // Just return originally-decoded bytes + } // end catch + finally { + try { + baos!!.close() + } catch (e: Exception) { + } + + try { + gzis!!.close() + } catch (e: Exception) { + } + + try { + bais!!.close() + } catch (e: Exception) { + } + + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes + } // end decode + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + */ + fun decodeToObject(encodedObject: String): Any? { + // Decode and gunzip if necessary + val objBytes = decode(encodedObject) + + var bais: java.io.ByteArrayInputStream? = null + var ois: java.io.ObjectInputStream? = null + var obj: Any? = null + + try { + bais = java.io.ByteArrayInputStream(objBytes!!) + ois = java.io.ObjectInputStream(bais) + + obj = ois.readObject() + } // end try + catch (e: java.io.IOException) { + e.printStackTrace() + obj = null + } // end catch + catch (e: java.lang.ClassNotFoundException) { + e.printStackTrace() + obj = null + } // end catch + finally { + try { + bais!!.close() + } catch (e: Exception) { + } + + try { + ois!!.close() + } catch (e: Exception) { + } + + } // end finally + + return obj + } // end decodeObject + + + /** + * Convenience method for encoding data to a file. + * + * @param dataToEncode byte array of data to encode in base64 form + * @param filename Filename for saving encoded data + * @return true if successful, false otherwise + */ + fun encodeToFile(dataToEncode: ByteArray, filename: String): Boolean { + var success = false + var bos: Base64.OutputStream? = null + try { + bos = Base64.OutputStream( + java.io.FileOutputStream(filename), Base64.ENCODE) + bos.write(dataToEncode) + success = true + } // end try + catch (e: java.io.IOException) { + + success = false + } // end catch: IOException + finally { + try { + bos!!.close() + } catch (e: Exception) { + } + + } // end finally + + return success + } // end encodeToFile + + + /** + * Convenience method for decoding data to a file. + * + * @param dataToDecode Base64-encoded data as a string + * @param filename Filename for saving decoded data + * @return true if successful, false otherwise + */ + fun decodeToFile(dataToDecode: String, filename: String): Boolean { + var success = false + var bos: Base64.OutputStream? = null + try { + bos = Base64.OutputStream( + java.io.FileOutputStream(filename), Base64.DECODE) + bos.write(dataToDecode.toByteArray(charset(PREFERRED_ENCODING))) + success = true + } // end try + catch (e: java.io.IOException) { + success = false + } // end catch: IOException + finally { + try { + bos!!.close() + } catch (e: Exception) { + } + + } // end finally + + return success + } // end decodeToFile + + + /** + * Convenience method for reading a base64-encoded + * file and decoding it. + * + * @param filename Filename for reading encoded data + * @return decoded byte array or null if unsuccessful + */ + fun decodeFromFile(filename: String): ByteArray? { + var decodedData: ByteArray? = null + var bis: Base64.InputStream? = null + try { + // Set up some useful variables + val file = java.io.File(filename) + var buffer: ByteArray? = null + var length = 0 + var numBytes = 0 + + // Check for size of file + if (file.length() > Integer.MAX_VALUE) { + System.err.println("File is too big for this convenience method (" + file.length() + " bytes).") + return null + } // end if: file too big for int index + buffer = ByteArray(file.length().toInt()) + + // Open a stream + bis = Base64.InputStream( + java.io.BufferedInputStream( + java.io.FileInputStream(file)), Base64.DECODE) + + // Read until done + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) + length += numBytes + + // Save in a variable to return + decodedData = ByteArray(length) + System.arraycopy(buffer, 0, decodedData, 0, length) + + } // end try + catch (e: java.io.IOException) { + System.err.println("Error decoding from file $filename") + } // end catch: IOException + finally { + try { + bis!!.close() + } catch (e: Exception) { + } + + } // end finally + + return decodedData + } // end decodeFromFile + + + /** + * Convenience method for reading a binary file + * and base64-encoding it. + * + * @param filename Filename for reading binary data + * @return base64-encoded string or null if unsuccessful + */ + fun encodeFromFile(filename: String): String? { + var encodedData: String? = null + var bis: Base64.InputStream? = null + try { + // Set up some useful variables + val file = java.io.File(filename) + val buffer = ByteArray((file.length() * 1.4).toInt()) + var length = 0 + var numBytes = 0 + + // Open a stream + bis = Base64.InputStream( + java.io.BufferedInputStream( + java.io.FileInputStream(file)), Base64.ENCODE) + + // Read until done + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) + length += numBytes + + // Save in a variable to return + encodedData = String(buffer, 0, length, Base64.PREFERRED_ENCODING) + + } // end try + catch (e: java.io.IOException) { + System.err.println("Error encoding from file $filename") + } // end catch: IOException + finally { + try { + bis!!.close() + } catch (e: Exception) { + } + + } // end finally + + return encodedData + } // end encodeFromFile + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + /** + * A [Base64.InputStream] will read data from another + * java.io.InputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + */ + class InputStream + /** + * Constructs a [Base64.InputStream] in + * either ENCODE or DECODE mode. + * + * + * Valid options:
                      +     * ENCODE or DECODE: Encode or Decode as data is read.
                      +     * DONT_BREAK_LINES: don't break lines at 76 characters
                      +     * (only meaningful when encoding)
                      +     * *Note: Technically, this makes your encoding non-compliant.*
                      +    
                      * + * + * + * Example: `new Base64.InputStream( in, Base64.DECODE )` + * + * @param in the java.io.InputStream from which to read data. + * @param options Specified options + * @see Base64.ENCODE + * + * @see Base64.DECODE + * + * @see Base64.DONT_BREAK_LINES + */ + @JvmOverloads constructor(`in`: java.io.InputStream, options: Int = DECODE) : java.io.FilterInputStream(`in`) { + private val encode: Boolean // Encoding or decoding + private var position: Int = 0 // Current position in the buffer + private val buffer: ByteArray // Small buffer holding converted data + private val bufferLength: Int // Length of buffer (3 or 4) + private var numSigBytes: Int = 0 // Number of meaningful bytes in the buffer + private var lineLength: Int = 0 + private val breakLines: Boolean // Break lines at less than 80 characters + + + init { + this.breakLines = options and DONT_BREAK_LINES != DONT_BREAK_LINES + this.encode = options and ENCODE == ENCODE + this.bufferLength = if (encode) 4 else 3 + this.buffer = ByteArray(bufferLength) + this.position = -1 + this.lineLength = 0 + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * + * @return next byte + */ + @Throws(java.io.IOException::class) + override fun read(): Int { + // Do we need to get data? + if (position < 0) { + if (encode) { + val b3 = ByteArray(3) + var numBinaryBytes = 0 + for (i in 0..2) { + try { + val b = `in`.read() + + // If end of stream, b is -1. + if (b >= 0) { + b3[i] = b.toByte() + numBinaryBytes++ + } // end if: not end of stream + + } // end try: read + catch (e: java.io.IOException) { + // Only a problem if we got no data at all. + if (i == 0) + throw e + + } + // end catch + } // end for: each needed input byte + + if (numBinaryBytes > 0) { + encode3to4(b3, 0, numBinaryBytes, buffer, 0) + position = 0 + numSigBytes = 4 + } // end if: got data + else { + return -1 + } // end else + } // end if: encoding + else { + val b4 = ByteArray(4) + var i = 0 + i = 0 + while (i < 4) { + // Read four "meaningful" bytes: + var b = 0 + do { + b = `in`.read() + } while (b >= 0 && DECODABET[b and 0x7f] <= WHITE_SPACE_ENC) + + if (b < 0) + break // Reads a -1 if end of stream + + b4[i] = b.toByte() + i++ + } // end for: each needed input byte + + if (i == 4) { + numSigBytes = decode4to3(b4, 0, buffer, 0) + position = 0 + } // end if: got four characters + else return if (i == 0) { + -1 + } // end else if: also padded correctly + else { + // Must have broken out from above. + throw java.io.IOException("Improperly padded Base64 input.") + } // end + + }// Else decoding + // end else: decode + } // end else: get data + + // Got data? + if (position >= 0) { + // End of relevant data? + if (/*!encode &&*/ position >= numSigBytes) + return -1 + + if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { + lineLength = 0 + return '\n'.toInt() + } // end if + else { + lineLength++ // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + val b = buffer[position++].toInt() + + if (position >= bufferLength) + position = -1 + + return b and 0xFF // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + else { + // When JDK1.4 is more accepted, use an assertion here. + throw java.io.IOException("Error in Base64 code reading stream.") + }// Else error + // end else + } // end read + + + /** + * Calls [.read] repeatedly until the end of stream + * is reached or len bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + */ + @Throws(java.io.IOException::class) + override fun read(dest: ByteArray, off: Int, len: Int): Int { + var i: Int + var b: Int + i = 0 + while (i < len) { + b = read() + + //if( b < 0 && i == 0 ) + // return -1; + + if (b >= 0) + dest[off + i] = b.toByte() + else return if (i == 0) + -1 + else + break // Out of 'for' loop + i++ + } // end for: each byte read + return i + } // end read + + } + /** + * Constructs a [Base64.InputStream] in DECODE mode. + * + * @param in the java.io.InputStream from which to read data. + */// end constructor + // end inner class InputStream + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + /** + * A [Base64.OutputStream] will write data to another + * java.io.OutputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + */ + class OutputStream + /** + * Constructs a [Base64.OutputStream] in + * either ENCODE or DECODE mode. + * + * + * Valid options:
                      +     * ENCODE or DECODE: Encode or Decode as data is read.
                      +     * DONT_BREAK_LINES: don't break lines at 76 characters
                      +     * (only meaningful when encoding)
                      +     * *Note: Technically, this makes your encoding non-compliant.*
                      +    
                      * + * + * + * Example: `new Base64.OutputStream( out, Base64.ENCODE )` + * + * @param out the java.io.OutputStream to which data will be written. + * @param options Specified options. + * @see Base64.ENCODE + * + * @see Base64.DECODE + * + * @see Base64.DONT_BREAK_LINES + */ + @JvmOverloads constructor(out: java.io.OutputStream, options: Int = ENCODE) : java.io.FilterOutputStream(out) { + private val encode: Boolean + private var position: Int = 0 + private var buffer: ByteArray? = null + private val bufferLength: Int + private var lineLength: Int = 0 + private val breakLines: Boolean + private val b4: ByteArray // Scratch used in a few places + private var suspendEncoding: Boolean = false + + + init { + this.breakLines = options and DONT_BREAK_LINES != DONT_BREAK_LINES + this.encode = options and ENCODE == ENCODE + this.bufferLength = if (encode) 3 else 4 + this.buffer = ByteArray(bufferLength) + this.position = 0 + this.lineLength = 0 + this.suspendEncoding = false + this.b4 = ByteArray(4) + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * + * @param theByte the byte to write + */ + @Throws(java.io.IOException::class) + override fun write(theByte: Int) { + // Encoding suspended? + if (suspendEncoding) { + super.out.write(theByte) + return + } // end if: supsended + + // Encode? + if (encode) { + buffer[position++] = theByte.toByte() + if (position >= bufferLength) + // Enough to encode. + { + out.write(encode3to4(b4, buffer, bufferLength)) + + lineLength += 4 + if (breakLines && lineLength >= MAX_LINE_LENGTH) { + out.write(NEW_LINE.toInt()) + lineLength = 0 + } // end if: end of line + + position = 0 + } // end if: enough to output + } // end if: encoding + else { + // Meaningful Base64 character? + if (DECODABET[theByte and 0x7f] > WHITE_SPACE_ENC) { + buffer[position++] = theByte.toByte() + if (position >= bufferLength) + // Enough to output. + { + val len = Base64.decode4to3(buffer!!, 0, b4, 0) + out.write(b4, 0, len) + //out.write( Base64.decode4to3( buffer ) ); + position = 0 + } // end if: enough to output + } // end if: meaningful base64 character + else if (DECODABET[theByte and 0x7f] != WHITE_SPACE_ENC) { + throw java.io.IOException("Invalid character in Base64 data.") + } // end else: not white space either + }// Else, Decoding + // end else: decoding + } // end write + + + /** + * Calls [.write] repeatedly until len + * bytes are written. + * + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + */ + @Throws(java.io.IOException::class) + override fun write(theBytes: ByteArray, off: Int, len: Int) { + // Encoding suspended? + if (suspendEncoding) { + super.out.write(theBytes, off, len) + return + } // end if: supsended + + for (i in 0 until len) { + write(theBytes[off + i].toInt()) + } // end for: each byte written + + } // end write + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + */ + @Throws(java.io.IOException::class) + fun flushBase64() { + if (position > 0) { + if (encode) { + out.write(encode3to4(b4, buffer, position)) + position = 0 + } // end if: encoding + else { + throw java.io.IOException("Base64 input not properly padded.") + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + */ + @Throws(java.io.IOException::class) + override fun close() { + // 1. Ensure that pending characters are written + flushBase64() + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close() + + buffer = null + out = null + } // end close + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + */ + @Throws(java.io.IOException::class) + fun suspendEncoding() { + flushBase64() + this.suspendEncoding = true + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + */ + fun resumeEncoding() { + this.suspendEncoding = false + } // end resumeEncoding + + + } + /** + * Constructs a [Base64.OutputStream] in ENCODE mode. + * + * @param out the java.io.OutputStream to which data will be written. + */// end constructor + // end inner class OutputStream + + +} +/** + * Defeats instantiation. + */ +/** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + */// end encodeObject +/** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + */// end encodeBytes +/** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + */// end encodeBytes +// end class Base64 diff --git a/src/main/java/io/starter/toolkit/BrowserLauncher.java b/src/main/java/io/starter/toolkit/BrowserLauncher.kt similarity index 57% rename from src/main/java/io/starter/toolkit/BrowserLauncher.java rename to src/main/java/io/starter/toolkit/BrowserLauncher.kt index 1d335a2..478d07d 100644 --- a/src/main/java/io/starter/toolkit/BrowserLauncher.java +++ b/src/main/java/io/starter/toolkit/BrowserLauncher.kt @@ -20,7 +20,7 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit /* Based on Bare Bones Browser Launcher by Dem Pilafian, * which is in the public domain. You may find it at: @@ -33,88 +33,84 @@ * @author Dem Pilafian * @author Sam Hanes */ -public class BrowserLauncher { +object BrowserLauncher { /** * List of potential browsers on systems without a default mechanism. */ - public static final String[] browsers = { - "google-chrome", "firefox", "opera", "konqueror", "epiphany", - "seamonkey", "galeon", "kazehakase", "mozilla"}; + val browsers = arrayOf("google-chrome", "firefox", "opera", "konqueror", "epiphany", "seamonkey", "galeon", "kazehakase", "mozilla") /** * The browser that was last successfully run. */ - private static String browser = null; + private var browser: String? = null /** * Opens the specified web page in the user's default browser. * * @param url the URL of the page to be opened * @throws Exception if an error occurred attempting to launch the browser. - * If the browser is successfully started but later fails for - * some reason, no exception will be thrown. + * If the browser is successfully started but later fails for + * some reason, no exception will be thrown. */ - public static void open(String url) - throws Exception { + @Throws(Exception::class) + fun open(url: String) { // Attempt to use the Desktop class from JDK 1.6+ (even if on 1.5) // This uses reflection to mimic the call: // java.awt.Desktop.getDesktop().browse( java.net.URI.create(url) ); try { - Class desktop = Class.forName("java.awt.Desktop"); + val desktop = Class.forName("java.awt.Desktop") desktop.getDeclaredMethod( - "browse", new Class[]{java.net.URI.class}) + "browse", *arrayOf(java.net.URI::class.java)) .invoke( desktop.getDeclaredMethod( - "getDesktop", (Class[]) null) - .invoke(null, (Object[]) null), - java.net.URI.create(url)); + "getDesktop", *null as Array>?) + .invoke(null, *null as Array?), + java.net.URI.create(url)) // If that didn't throw an exception, we're done - return; - } catch (ClassNotFoundException e) { + return + } catch (e: ClassNotFoundException) { // Intentionally empty, falls back to platform-dependent code - } catch (NoSuchMethodException e) { + } catch (e: NoSuchMethodException) { // Intentionally empty, falls back to platform-dependent code - } catch (Exception e) { - throw new Exception("failed to launch browser", e); + } catch (e: Exception) { + throw Exception("failed to launch browser", e) } - String osName = System.getProperty("os.name"); + val osName = System.getProperty("os.name") try { // If this is OS X, use the FileManager class if (osName.startsWith("Mac OS")) Class.forName("com.apple.eio.FileManager") - .getDeclaredMethod("openURL", new Class[]{String.class}) - .invoke(null, url); - - // If this is Windows, call the FileProtocolHandler via rundll + .getDeclaredMethod("openURL", *arrayOf(String::class.java)) + .invoke(null, url) else if (osName.startsWith("Windows")) Runtime.getRuntime().exec( - "rundll32 url.dll,FileProtocolHandler " + url); - - // Otherwise, assume this is a POSIX-like system and - // start trying possible browser commands + "rundll32 url.dll,FileProtocolHandler $url") else { // If we haven't found a browser yet, try some possible ones if (browser == null) { - for (int idx = 0; idx < browsers.length; idx++) { + for (idx in browsers.indices) { if (Runtime.getRuntime().exec( - new String[]{"which", browsers[idx]}) - .waitFor() == 0) { - browser = browsers[idx]; + arrayOf("which", browsers[idx])) + .waitFor() == 0) { + browser = browsers[idx] } } // If we couldn't find one, throw an exception if (browser == null) - throw new Exception("no browser found"); + throw Exception("no browser found") } // Call the browser with the URL - Runtime.getRuntime().exec(new String[]{browser, url}); - } - } catch (Exception e) { - throw new Exception("failed to launch browser", e); + Runtime.getRuntime().exec(arrayOf(browser, url)) + }// Otherwise, assume this is a POSIX-like system and + // start trying possible browser commands + // If this is Windows, call the FileProtocolHandler via rundll + } catch (e: Exception) { + throw Exception("failed to launch browser", e) } + } } diff --git a/src/main/java/io/starter/toolkit/ByteTools.java b/src/main/java/io/starter/toolkit/ByteTools.java deleted file mode 100644 index 818e975..0000000 --- a/src/main/java/io/starter/toolkit/ByteTools.java +++ /dev/null @@ -1,622 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.io.*; -import java.util.Iterator; -import java.util.List; - - -/** - * Helper methods for working with byte arrays and XLS files. - */ - -public final class ByteTools implements Serializable { - - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 1220042103372057083L; - - /** - * Returns a string representation of the byte array. - * - * @param bt the byte array - * @param offset - offset into byte array - * @return the string representation - */ - public static String getByteDump(byte[] bt, int offset) { - return ByteTools.getByteDump(bt, offset, bt.length); - } - - /** - * Returns a string representation of the byte array. - * - * @param bt the byte array - * @param offset - offset into byte array - * @param len - length of byte array segment to return - * @return the string representation - */ - public static String getByteDump(byte[] bt, int offset, int len) { - if (bt == null) - return ""; - StringBuffer buf = new StringBuffer(); - int every4 = 0; - int every16 = 0; - buf.append("\r\n");// start on a new line - String offst = (Integer.toHexString(offset)); - // now calculate where the 4 byte words should be offset so display matches... - //int remainder = offset%16; - while (offst.length() < 4) offst = "0" + offst; - buf.append(offst); - buf.append(": "); - int origOffset = offset; - offset += 16; - for (int i = origOffset; i < /*origOffset+*/len; ++i) { - buf.append(hexits.charAt((bt[i] >>> 4) & 0xf)); - buf.append(hexits.charAt((bt[i]) & 0xf)); - buf.append(" "); - every4++; - if (every4 == 4) { - every4 = 0; - buf.append(" "); - every16++; - if (every16 == 4) { - buf.append("\r\n"); - offst = (Integer.toHexString(offset)); - while (offst.length() < 4) offst = "0" + offst; - buf.append(offst); - buf.append(": "); - offset += 16; - every16 = 0; - } - } - } - return buf + ""; - } - - private static String hexits = "0123456789ABCDEF"; - - /** - * Returns a string representation of the byte array. - * - * @param bt the byte array - * @param pad whether to pad the strings so they align - * @return the string representation - */ - public static String getByteString(byte[] bt, boolean pad) { - if (bt.length == 0) return "null"; - StringBuffer ret = new StringBuffer(); - for (int x = 0; x < bt.length; x++) { - if (((x % 8) == 0) && (x > 0)) ret.append("\r\n"); - //String bstr = Integer.toOctalString(bt[x]); // toBinaryString(bt[x]); // Byte.toString(bt[x]); - String bstr = Byte.toString(bt[x]); // toBinaryString(bt[x]); // Byte.toString(bt[x]); - if (pad) while (bstr.length() < 4) bstr = " " + bstr; - ret.append(bstr); - ret.append(","); - } - ret.setLength(ret.length() - 1); - //ret.append(); - return ret.toString(); - } - - /** - * Appends one byte array to another. - * If either input (but not both) is null, a clone of the other will be - * returned. This method is guaranteed to always return an array different - * from either of those passed in. - * - * @param src the array which will be appended to dest - * @param dest the array to which src will be appended - * @throws NullPointerException if both inputs are null - */ - public static byte[] append(byte[] src, byte[] dest) { - // Deal with null input correctly - if (src == null) return dest.clone(); - if (dest == null) return src.clone(); - - int srclen = src.length; - int destlen = dest.length; - - byte[] ret = new byte[srclen + destlen]; - System.arraycopy(dest, 0, ret, 0, destlen); - System.arraycopy(src, 0, ret, destlen, srclen); - - return ret; - } - - - /** - * append one byte array to an empty array - * of the proper size - * usage: - * newarray = bytetool.append(sourcearray, destinationarray, position to start copy at); - */ - public static byte[] append(byte[] src, byte[] dest, int pos) { - int srclen = src.length; - if (dest == null) dest = new byte[srclen]; - int destlen = dest.length; - if (destlen < srclen) { - Logger.logInfo("Your destination byte array is too small to copy into: srclen=" + srclen + ": destlen=" + destlen); - srclen = destlen; - } - System.arraycopy(src, 0, dest, pos, srclen); - return dest; - } - - - public static byte[] cLongToLEBytes(int i) { - byte[] ret = new byte[4]; - ret[0] = (byte) (i & 0xff); - ret[1] = (byte) ((i >> 8) & 0xff); - ret[2] = (byte) ((i >> 16) & 0xff); - ret[3] = (byte) ((i >> 24) & 0xff); - return ret; - } - - /** - * C Longs are only 32 bits, Java Longs are 64. - * This method converts a 32-bit 'C' long to a - * pair of java shorts. - *

                      - * Also performs 'little-endian' conversion. - */ - public static short[] cLongToLEShorts(int x) { - short[] buf = new short[2]; - short high = (short) (x >>> 16); - short low = (short) x; - buf[0] = low; - buf[1] = high; - // if(DEBUG)Logger.logInfo(Info( ("x=" + x + " high=" + high + " low=" + low ); - return buf; - } - - public static byte[] doubleToLEByteArray(double d) { - byte[] bite = new byte[8]; // A long is 8 bytes - long l = Double.doubleToLongBits(d); - int i; - long t; - t = l; // variable t will be shifted right each time thru the loop. - for (i = bite.length - 1; i > -1; i--) { //High order byte will be in b[0] - long irr = (t & 0xff); - bite[i] = Integer.valueOf((int) irr).byteValue(); //get the last 8 bits into the byte array. - t = t >> 8; //Shifts the long 1 byte. Same as divide by 256 - } - byte[] ret = new byte[bite.length]; - for (int x = 0; x < bite.length; x++) - ret[x] = bite[(bite.length - 1) - x]; - return ret; - } - - public static byte[] doubleToByteArray(double d) { - byte[] bite = new byte[8]; // A long is 8 bytes - long l = Double.doubleToLongBits(d); - int i; - long t; - t = l; // variable t will be shifted right each time thru the loop. - for (i = bite.length - 1; i > -1; i--) { //High order byte will be in b[0] - long irr = (t & 0xff); - bite[i] = Integer.valueOf((int) irr).byteValue(); //get the last 8 bits into the byte array. - t = t >> 8; //Shifts the long 1 byte. Same as divide by 256 - } - return bite; - } - - /** - * converts and bitswaps an eight bite byte array into an IEEE double. - */ - public static double eightBytetoLEDouble(byte[] bite) { - byte[] b = new byte[8]; - b[0] = bite[7]; - b[1] = bite[6]; - b[2] = bite[5]; - b[3] = bite[4]; - b[4] = bite[3]; - b[5] = bite[2]; - b[6] = bite[1]; - b[7] = bite[0]; - double d = 0; - java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(b); - java.io.DataInputStream dis = new java.io.DataInputStream(bais); - try { - Double dbl = new Double(dis.readDouble()); - d = dbl.doubleValue(); - } catch (java.io.IOException e) { - Logger.logInfo("io exception in byte to Double conversion" + e); - } - return d; - } - - - /** - * converts and bitswaps an eight bite byte array into an IEEE double. - */ - public static long eightBytetoLELong(byte[] bite) { - byte[] b = new byte[8]; - b[0] = bite[7]; - b[1] = bite[6]; - b[2] = bite[5]; - b[3] = bite[4]; - b[4] = bite[3]; - b[5] = bite[2]; - b[6] = bite[1]; - b[7] = bite[0]; - long l = 0; - java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(b); - java.io.DataInputStream dis = new java.io.DataInputStream(bais); - try { - Long lg = new Long(dis.readLong()); - l = lg.longValue(); - } catch (java.io.IOException e) { - Logger.logInfo("io exception in byte to Double conversion" + e); - } - return l; - } - - /** - * Get an array of bytes from a collection of byte arrays - *

                      - * Seems slow, why 2 iterations, should be faster way? - */ - public static byte[] getBytes(List records) { - Iterator e = records.iterator(); - int buflen = 0; - while (e.hasNext()) { - byte[] barr = (byte[]) e.next(); - buflen += barr.length; - } - - byte[] outbytes = new byte[buflen]; - int pos = 0; - for (int i = 0; i < records.size(); i++) { - byte[] stream = (byte[]) records.get(i); - outbytes = append(stream, outbytes, pos); - pos += stream.length; - } - return outbytes; - } - - /* - Makes sure unicode strings are in the correct format to match Excel's strings. - If the string has all low order bytes as 0x0 then return original string, as we do not - want that extra space. - - */ - public static byte[] getExcelEncoding(String s) { - byte[] strbytes = null; - try { - strbytes = s.getBytes("UnicodeLittleUnmarked"); - } catch (UnsupportedEncodingException e) { - Logger.logInfo("Error creating encoded string: " + e); - } - boolean unicode = false; - for (int i = 0; i < strbytes.length; i++) { - i = i + 1; - if (strbytes[i] != 0x0) { - unicode = true; - i = strbytes.length; - } - - } - if (unicode) { - //try{ - return strbytes; - //}catch(UnsupportedEncodingException e){Logger.logInfo("Error creating encoded string: " + e);} - } - return s.getBytes(); - } - - /** - * This is a working longToLEByteArray. I'm not sure whats up with the other ones - * - * @param l - * @return - */ - public static byte[] longToLEByteArray(long l) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - try { - dos.writeLong(l); - dos.flush(); - } catch (IOException e) { - - } - byte[] bite = bos.toByteArray(); - byte[] b = new byte[8]; - b[0] = bite[7]; - b[1] = bite[6]; - b[2] = bite[5]; - b[3] = bite[4]; - b[4] = bite[3]; - b[5] = bite[2]; - b[6] = bite[1]; - b[7] = bite[0]; - return b; - } - - public static boolean isUnicode(String s) { - byte[] strbytes = null; - try { - strbytes = s.getBytes("UnicodeLittleUnmarked"); - } catch (UnsupportedEncodingException e) { - } - for (int i = 0; i < strbytes.length; i++) { - if (strbytes[i] >= 0x7f) - return true; // deal with non-compressible Eastern Strings - i = i + 1; - if (strbytes[i] != 0x0) { - return true; // there is a non-zero high-byte - } - } - return false; - } - - public static byte[] longToByteArray(long l) { - byte[] bite = new byte[8]; // A long is 8 bytes - int i; - long t; - t = l; // variable t will be shifted right each time thru the loop. - for (i = bite.length - 1; i > -1; i--) { //High order byte will be in b[0] - long irr = (t & 0xff); - bite[i] = Integer.valueOf((int) irr).byteValue(); //get the last 8 bits into the byte array. - t = t >> 8; //Shifts the long 1 byte. Same as divide by 256 - } - return bite; - } - - - /** - * same as readInt, but takes 4 raw bytes instead of 2 shorts - */ - public static int readInt(byte[] bs) { - return readInt(readShort(bs[2], bs[3]), readShort(bs[0], bs[1])); - } - - /** - * same as readInt, but takes 4 raw bytes instead of 2 shorts - */ - public static int readInt(byte b1, byte b2, byte b3, byte b4) { - return readInt(readShort(b3, b4), readShort(b1, b2)); - } - - /** - * Reads a 4 byte int from a byte array at the specified position - * and handles a little endian conversion - */ - public static int readInt(byte[] b, int offset) { - return readInt(b[offset++], b[offset++], b[offset++], b[offset++]); - } - - - /** - * bit-flipping action converting a 'little-endian' - * pair of shorts to a 'big-endian' long. - * This is really a java int as it represents a C-language - * long value which is only 32 bits, like the java int. - */ - public static int readInt(int low, int high) { - if (low == 0x0 && high == 0x0) return 0; - low = low & 0xffff; - high = high & 0xffff; - return low << 16 | high; - } - - - /** - * bit-flipping action converting a 'little-endian' - * pair of bytes to a 'big-endian' short. Returns an int as - * excel uses unsigned shorts which can exceed the boundary - * of a java signed short - */ - public static int readUnsignedShort(byte low, byte high) { - return readInt(low, high, (byte) 0x0, (byte) 0x0); - } - - /** - * bit-flipping action converting a 'little-endian' - * pair of bytes to a 'big-endian' short. - *

                      - * This will break if you pass it any values larger than a byte. Will - * probably return a value, but I wouldn't trust it. Fix in R2 -Rab - */ - public static short readShort(int low, int high) { - // 2 bytes - low = low & 0xff; - high = high & 0xff; - return (short) (high << 8 | low); - } - - - /** bit-flipping action converting a 'little-endian' - pair of bytes to a 'big-endian' short. - - public static short readShort(byte low, byte high) - { - return (short)(high << 8 | low); - } - */ - - /** - * take 16-bit short apart into two 8-bit bytes. - */ - public static byte[] shortToLEBytes(short x) { - byte[] buf = new byte[2]; - buf[1] = (byte) (x >>> 8); - buf[0] = (byte) x;/* cast implies & 0xff */ - return buf; - } - - public static byte[] toBEByteArray(double d) { - byte[] bite = new byte[8]; // A long is 8 bytes - long l = Double.doubleToLongBits(d); - int i; - long t; - t = l; // variable t will be shifted right each time thru the loop. - for (i = bite.length - 1; i > -1; i--) { //High order byte will be in b[0] - long irr = (t & 0xff); - bite[i] = Integer.valueOf((int) irr).byteValue(); //get the last 8 bits into the byte array. - t = t >> 8; //Shifts the long 1 byte. Same as divide by 256 - } - byte[] b = new byte[8]; - b[0] = bite[7]; - b[1] = bite[6]; - b[2] = bite[5]; - b[3] = bite[4]; - b[4] = bite[3]; - b[5] = bite[2]; - b[6] = bite[1]; - b[7] = bite[0]; - return b; - } - // private boolean DEBUG = false; - - /** - * write bytes to a file - */ - public static void writeToFile(byte[] b, String fname) { - try { - FileOutputStream fos = new FileOutputStream(fname); - fos.write(b); - fos.flush(); - fos.close(); - } catch (IOException e) { - Logger.logInfo("Error writing bytes to file in ByteTools: " + e); - } - - } - - /** - * C Longs are only 32 bits, Java Longs are 64. - * This method converts a 32-bit 'C' long to a - * byte array. - *

                      - * Also performs 'little-endian' conversion. - */ - public byte[] cLongToLEBytesOLD(int i) { - //if(true)return Integer. - short[] sbuf = cLongToLEShorts(i); - byte[] b1 = shortToLEBytes(sbuf[0]); - byte[] b2 = shortToLEBytes(sbuf[1]); - - byte[] bbuf = new byte[4]; - bbuf[0] = b1[0]; - bbuf[1] = b1[1]; - bbuf[2] = b2[0]; - bbuf[3] = b2[1]; - - // System.arraycopy(b1, 0, bbuf, 0, 2); - // System.arraycopy(b2, 0, bbuf, 2, 2); - return bbuf; - } - - - // NICK -- let's put the following in a JUnit Test. -jm - - /** this is a good test for some of the above methods.. - byte[] bytes; - java.io.ByteArrayInputStream bis = new java.io.ByteArrayInputStream(bite); - java.io.DataInputStream dis = new java.io.DataInputStream(bis); - try { - long lon = dis.readLong(); - Logger.logInfo("pleeze stop here"); - } catch (java.io.IOException e){ Logger.logInfo("io exception in byte to long conversion" + e);} - */ - /** - * generic method to update (set or clear) a short at bitNum - * - * @param set - * @param bitNum - */ - public static short updateGrBit(short grbit, boolean set, int bitNum) { - switch (bitNum) { - case 0: - if (set) grbit |= (0x1); - else grbit &= (0xFFFE); - break; - case 1: - if (set) grbit |= (0x2); - else grbit &= (0xFFFD); - break; - case 2: - if (set) grbit |= (0x4); - else grbit &= (0xFFFB); - break; - case 3: - if (set) grbit |= (0x8); - else grbit &= (0xFFF7); - break; - case 4: - if (set) grbit |= (0x10); - else grbit &= (0xFFEF); - break; - case 5: - if (set) grbit |= (0x20); - else grbit &= (0xFFDF); - break; - case 6: - if (set) grbit |= (0x40); - else grbit &= (0xFFBF); - break; - case 7: - if (set) grbit |= (0x80); - else grbit &= (0xFF7F); - break; - case 8: - if (set) grbit |= (0x100); - else grbit &= (0xFEFF); - break; - case 9: - if (set) grbit |= (0x200); - else grbit &= (0xFDFF); - break; - case 10: - if (set) grbit |= (0x400); - else grbit &= (0xFBFF); - break; - case 11: - if (set) grbit |= (0x800); - else grbit &= (0xF7FF); - break; - case 12: - if (set) grbit |= (0x1000); - else grbit &= (0xEFFF); - break; - case 13: - if (set) grbit |= (0x2000); - else grbit &= (0xDFFF); - break; - case 14: - if (set) grbit |= (0x4000); - else grbit &= (0xBFFF); - break; - case 15: - if (set) grbit |= (0x8000); - else grbit &= (0x7FFF); - break; - } - return grbit; - } -} - - \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/ByteTools.kt b/src/main/java/io/starter/toolkit/ByteTools.kt new file mode 100644 index 0000000..3f0283f --- /dev/null +++ b/src/main/java/io/starter/toolkit/ByteTools.kt @@ -0,0 +1,651 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.io.* + + +/** + * Helper methods for working with byte arrays and XLS files. + */ + +class ByteTools : Serializable { + + /** + * C Longs are only 32 bits, Java Longs are 64. + * This method converts a 32-bit 'C' long to a + * byte array. + * + * + * Also performs 'little-endian' conversion. + */ + fun cLongToLEBytesOLD(i: Int): ByteArray { + //if(true)return Integer. + val sbuf = cLongToLEShorts(i) + val b1 = shortToLEBytes(sbuf[0]) + val b2 = shortToLEBytes(sbuf[1]) + + val bbuf = ByteArray(4) + bbuf[0] = b1[0] + bbuf[1] = b1[1] + bbuf[2] = b2[0] + bbuf[3] = b2[1] + + // System.arraycopy(b1, 0, bbuf, 0, 2); + // System.arraycopy(b2, 0, bbuf, 2, 2); + return bbuf + } + + companion object { + + + /** + * serialVersionUID + */ + private const val serialVersionUID = 1220042103372057083L + + /** + * Returns a string representation of the byte array. + * + * @param bt the byte array + * @param offset - offset into byte array + * @return the string representation + */ + fun getByteDump(bt: ByteArray, offset: Int): String { + return ByteTools.getByteDump(bt, offset, bt.size) + } + + /** + * Returns a string representation of the byte array. + * + * @param bt the byte array + * @param offset - offset into byte array + * @param len - length of byte array segment to return + * @return the string representation + */ + fun getByteDump(bt: ByteArray?, offset: Int, len: Int): String { + var offset = offset + if (bt == null) + return "" + val buf = StringBuffer() + var every4 = 0 + var every16 = 0 + buf.append("\r\n")// start on a new line + var offst = Integer.toHexString(offset) + // now calculate where the 4 byte words should be offset so display matches... + //int remainder = offset%16; + while (offst.length < 4) offst = "0$offst" + buf.append(offst) + buf.append(": ") + val origOffset = offset + offset += 16 + for (i in origOffset until /*origOffset+*/len) { + buf.append(hexits[bt[i].ushr(4) and 0xf]) + buf.append(hexits[bt[i] and 0xf]) + buf.append(" ") + every4++ + if (every4 == 4) { + every4 = 0 + buf.append(" ") + every16++ + if (every16 == 4) { + buf.append("\r\n") + offst = Integer.toHexString(offset) + while (offst.length < 4) offst = "0$offst" + buf.append(offst) + buf.append(": ") + offset += 16 + every16 = 0 + } + } + } + return buf.toString() + "" + } + + private val hexits = "0123456789ABCDEF" + + /** + * Returns a string representation of the byte array. + * + * @param bt the byte array + * @param pad whether to pad the strings so they align + * @return the string representation + */ + fun getByteString(bt: ByteArray, pad: Boolean): String { + if (bt.size == 0) return "null" + val ret = StringBuffer() + for (x in bt.indices) { + if (x % 8 == 0 && x > 0) ret.append("\r\n") + //String bstr = Integer.toOctalString(bt[x]); // toBinaryString(bt[x]); // Byte.toString(bt[x]); + var bstr = java.lang.Byte.toString(bt[x]) // toBinaryString(bt[x]); // Byte.toString(bt[x]); + if (pad) while (bstr.length < 4) bstr = " $bstr" + ret.append(bstr) + ret.append(",") + } + ret.setLength(ret.length - 1) + //ret.append(); + return ret.toString() + } + + /** + * Appends one byte array to another. + * If either input (but not both) is null, a clone of the other will be + * returned. This method is guaranteed to always return an array different + * from either of those passed in. + * + * @param src the array which will be appended to `dest` + * @param dest the array to which `src` will be appended + * @throws NullPointerException if both inputs are null + */ + fun append(src: ByteArray?, dest: ByteArray?): ByteArray { + // Deal with null input correctly + if (src == null) return dest!!.clone() + if (dest == null) return src.clone() + + val srclen = src.size + val destlen = dest.size + + val ret = ByteArray(srclen + destlen) + System.arraycopy(dest, 0, ret, 0, destlen) + System.arraycopy(src, 0, ret, destlen, srclen) + + return ret + } + + + /** + * append one byte array to an empty array + * of the proper size + * usage: + * newarray = bytetool.append(sourcearray, destinationarray, position to start copy at); + */ + fun append(src: ByteArray, dest: ByteArray?, pos: Int): ByteArray { + var dest = dest + var srclen = src.size + if (dest == null) dest = ByteArray(srclen) + val destlen = dest.size + if (destlen < srclen) { + Logger.logInfo("Your destination byte array is too small to copy into: srclen=$srclen: destlen=$destlen") + srclen = destlen + } + System.arraycopy(src, 0, dest, pos, srclen) + return dest + } + + + fun cLongToLEBytes(i: Int): ByteArray { + val ret = ByteArray(4) + ret[0] = (i and 0xff).toByte() + ret[1] = (i shr 8 and 0xff).toByte() + ret[2] = (i shr 16 and 0xff).toByte() + ret[3] = (i shr 24 and 0xff).toByte() + return ret + } + + /** + * C Longs are only 32 bits, Java Longs are 64. + * This method converts a 32-bit 'C' long to a + * pair of java shorts. + * + * + * Also performs 'little-endian' conversion. + */ + fun cLongToLEShorts(x: Int): ShortArray { + val buf = ShortArray(2) + val high = x.ushr(16).toShort() + val low = x.toShort() + buf[0] = low + buf[1] = high + // if(DEBUG)Logger.logInfo(Info( ("x=" + x + " high=" + high + " low=" + low ); + return buf + } + + fun doubleToLEByteArray(d: Double): ByteArray { + val bite = ByteArray(8) // A long is 8 bytes + val l = java.lang.Double.doubleToLongBits(d) + var i: Int + var t: Long + t = l // variable t will be shifted right each time thru the loop. + i = bite.size - 1 + while (i > -1) { //High order byte will be in b[0] + val irr = t and 0xff + bite[i] = Integer.valueOf(irr.toInt())!!.toByte() //get the last 8 bits into the byte array. + t = t shr 8 //Shifts the long 1 byte. Same as divide by 256 + i-- + } + val ret = ByteArray(bite.size) + for (x in bite.indices) + ret[x] = bite[bite.size - 1 - x] + return ret + } + + fun doubleToByteArray(d: Double): ByteArray { + val bite = ByteArray(8) // A long is 8 bytes + val l = java.lang.Double.doubleToLongBits(d) + var i: Int + var t: Long + t = l // variable t will be shifted right each time thru the loop. + i = bite.size - 1 + while (i > -1) { //High order byte will be in b[0] + val irr = t and 0xff + bite[i] = Integer.valueOf(irr.toInt())!!.toByte() //get the last 8 bits into the byte array. + t = t shr 8 //Shifts the long 1 byte. Same as divide by 256 + i-- + } + return bite + } + + /** + * converts and bitswaps an eight bite byte array into an IEEE double. + */ + fun eightBytetoLEDouble(bite: ByteArray): Double { + val b = ByteArray(8) + b[0] = bite[7] + b[1] = bite[6] + b[2] = bite[5] + b[3] = bite[4] + b[4] = bite[3] + b[5] = bite[2] + b[6] = bite[1] + b[7] = bite[0] + var d = 0.0 + val bais = java.io.ByteArrayInputStream(b) + val dis = java.io.DataInputStream(bais) + try { + val dbl = dis.readDouble() + d = dbl + } catch (e: java.io.IOException) { + Logger.logInfo("io exception in byte to Double conversion$e") + } + + return d + } + + + /** + * converts and bitswaps an eight bite byte array into an IEEE double. + */ + fun eightBytetoLELong(bite: ByteArray): Long { + val b = ByteArray(8) + b[0] = bite[7] + b[1] = bite[6] + b[2] = bite[5] + b[3] = bite[4] + b[4] = bite[3] + b[5] = bite[2] + b[6] = bite[1] + b[7] = bite[0] + var l: Long = 0 + val bais = java.io.ByteArrayInputStream(b) + val dis = java.io.DataInputStream(bais) + try { + val lg = dis.readLong() + l = lg + } catch (e: java.io.IOException) { + Logger.logInfo("io exception in byte to Double conversion$e") + } + + return l + } + + /** + * Get an array of bytes from a collection of byte arrays + * + * + * Seems slow, why 2 iterations, should be faster way? + */ + fun getBytes(records: List<*>): ByteArray { + val e = records.iterator() + var buflen = 0 + while (e.hasNext()) { + val barr = e.next() as ByteArray + buflen += barr.size + } + + var outbytes = ByteArray(buflen) + var pos = 0 + for (i in records.indices) { + val stream = records[i] as ByteArray + outbytes = append(stream, outbytes, pos) + pos += stream.size + } + return outbytes + } + + /* + Makes sure unicode strings are in the correct format to match Excel's strings. + If the string has all low order bytes as 0x0 then return original string, as we do not + want that extra space. + + */ + fun getExcelEncoding(s: String): ByteArray { + var strbytes: ByteArray? = null + try { + strbytes = s.toByteArray(charset("UnicodeLittleUnmarked")) + } catch (e: UnsupportedEncodingException) { + Logger.logInfo("Error creating encoded string: $e") + } + + var unicode = false + var i = 0 + while (i < strbytes!!.size) { + i = i + 1 + if (strbytes[i].toInt() != 0x0) { + unicode = true + i = strbytes.size + } + i++ + + } + return if (unicode) { + //try{ + strbytes + //}catch(UnsupportedEncodingException e){Logger.logInfo("Error creating encoded string: " + e);} + } else s.toByteArray() + } + + /** + * This is a working longToLEByteArray. I'm not sure whats up with the other ones + * + * @param l + * @return + */ + fun longToLEByteArray(l: Long): ByteArray { + val bos = ByteArrayOutputStream() + val dos = DataOutputStream(bos) + try { + dos.writeLong(l) + dos.flush() + } catch (e: IOException) { + + } + + val bite = bos.toByteArray() + val b = ByteArray(8) + b[0] = bite[7] + b[1] = bite[6] + b[2] = bite[5] + b[3] = bite[4] + b[4] = bite[3] + b[5] = bite[2] + b[6] = bite[1] + b[7] = bite[0] + return b + } + + fun isUnicode(s: String): Boolean { + var strbytes: ByteArray? = null + try { + strbytes = s.toByteArray(charset("UnicodeLittleUnmarked")) + } catch (e: UnsupportedEncodingException) { + } + + var i = 0 + while (i < strbytes!!.size) { + if (strbytes[i] >= 0x7f) + return true // deal with non-compressible Eastern Strings + i = i + 1 + if (strbytes[i].toInt() != 0x0) { + return true // there is a non-zero high-byte + } + i++ + } + return false + } + + fun longToByteArray(l: Long): ByteArray { + val bite = ByteArray(8) // A long is 8 bytes + var i: Int + var t: Long + t = l // variable t will be shifted right each time thru the loop. + i = bite.size - 1 + while (i > -1) { //High order byte will be in b[0] + val irr = t and 0xff + bite[i] = Integer.valueOf(irr.toInt())!!.toByte() //get the last 8 bits into the byte array. + t = t shr 8 //Shifts the long 1 byte. Same as divide by 256 + i-- + } + return bite + } + + + /** + * same as readInt, but takes 4 raw bytes instead of 2 shorts + */ + fun readInt(bs: ByteArray): Int { + return readInt(readShort(bs[2].toInt(), bs[3].toInt()).toInt(), readShort(bs[0].toInt(), bs[1].toInt()).toInt()) + } + + /** + * same as readInt, but takes 4 raw bytes instead of 2 shorts + */ + fun readInt(b1: Byte, b2: Byte, b3: Byte, b4: Byte): Int { + return readInt(readShort(b3.toInt(), b4.toInt()).toInt(), readShort(b1.toInt(), b2.toInt()).toInt()) + } + + /** + * Reads a 4 byte int from a byte array at the specified position + * and handles a little endian conversion + */ + fun readInt(b: ByteArray, offset: Int): Int { + var offset = offset + return readInt(b[offset++], b[offset++], b[offset++], b[offset++]) + } + + + /** + * bit-flipping action converting a 'little-endian' + * pair of shorts to a 'big-endian' long. + * This is really a java int as it represents a C-language + * long value which is only 32 bits, like the java int. + */ + fun readInt(low: Int, high: Int): Int { + var low = low + var high = high + if (low == 0x0 && high == 0x0) return 0 + low = low and 0xffff + high = high and 0xffff + return low shl 16 or high + } + + + /** + * bit-flipping action converting a 'little-endian' + * pair of bytes to a 'big-endian' short. Returns an int as + * excel uses unsigned shorts which can exceed the boundary + * of a java signed short + */ + fun readUnsignedShort(low: Byte, high: Byte): Int { + return readInt(low, high, 0x0.toByte(), 0x0.toByte()) + } + + /** + * bit-flipping action converting a 'little-endian' + * pair of bytes to a 'big-endian' short. + * + * + * This will break if you pass it any values larger than a byte. Will + * probably return a value, but I wouldn't trust it. Fix in R2 -Rab + */ + fun readShort(low: Int, high: Int): Short { + var low = low + var high = high + // 2 bytes + low = low and 0xff + high = high and 0xff + return (high shl 8 or low).toShort() + } + + + /** bit-flipping action converting a 'little-endian' + * pair of bytes to a 'big-endian' short. + * + * public static short readShort(byte low, byte high) + * { + * return (short)(high << 8 | low); + * } + */ + + /** + * take 16-bit short apart into two 8-bit bytes. + */ + fun shortToLEBytes(x: Short): ByteArray { + val buf = ByteArray(2) + buf[1] = x.ushr(8).toByte() + buf[0] = x.toByte()/* cast implies & 0xff */ + return buf + } + + fun toBEByteArray(d: Double): ByteArray { + val bite = ByteArray(8) // A long is 8 bytes + val l = java.lang.Double.doubleToLongBits(d) + var i: Int + var t: Long + t = l // variable t will be shifted right each time thru the loop. + i = bite.size - 1 + while (i > -1) { //High order byte will be in b[0] + val irr = t and 0xff + bite[i] = Integer.valueOf(irr.toInt())!!.toByte() //get the last 8 bits into the byte array. + t = t shr 8 //Shifts the long 1 byte. Same as divide by 256 + i-- + } + val b = ByteArray(8) + b[0] = bite[7] + b[1] = bite[6] + b[2] = bite[5] + b[3] = bite[4] + b[4] = bite[3] + b[5] = bite[2] + b[6] = bite[1] + b[7] = bite[0] + return b + } + // private boolean DEBUG = false; + + /** + * write bytes to a file + */ + fun writeToFile(b: ByteArray, fname: String) { + try { + val fos = FileOutputStream(fname) + fos.write(b) + fos.flush() + fos.close() + } catch (e: IOException) { + Logger.logInfo("Error writing bytes to file in ByteTools: $e") + } + + } + + + // NICK -- let's put the following in a JUnit Test. -jm + + /** this is a good test for some of the above methods.. + * byte[] bytes; + * java.io.ByteArrayInputStream bis = new java.io.ByteArrayInputStream(bite); + * java.io.DataInputStream dis = new java.io.DataInputStream(bis); + * try { + * long lon = dis.readLong(); + * Logger.logInfo("pleeze stop here"); + * } catch (java.io.IOException e){ Logger.logInfo("io exception in byte to long conversion" + e);} + */ + /** + * generic method to update (set or clear) a short at bitNum + * + * @param set + * @param bitNum + */ + fun updateGrBit(grbit: Short, set: Boolean, bitNum: Int): Short { + var grbit = grbit + when (bitNum) { + 0 -> if (set) + grbit = grbit or 0x1.toShort() + else + grbit = grbit and 0xFFFE.toShort() + 1 -> if (set) + grbit = grbit or 0x2.toShort() + else + grbit = grbit and 0xFFFD.toShort() + 2 -> if (set) + grbit = grbit or 0x4.toShort() + else + grbit = grbit and 0xFFFB.toShort() + 3 -> if (set) + grbit = grbit or 0x8.toShort() + else + grbit = grbit and 0xFFF7.toShort() + 4 -> if (set) + grbit = grbit or 0x10.toShort() + else + grbit = grbit and 0xFFEF.toShort() + 5 -> if (set) + grbit = grbit or 0x20.toShort() + else + grbit = grbit and 0xFFDF.toShort() + 6 -> if (set) + grbit = grbit or 0x40.toShort() + else + grbit = grbit and 0xFFBF.toShort() + 7 -> if (set) + grbit = grbit or 0x80.toShort() + else + grbit = grbit and 0xFF7F.toShort() + 8 -> if (set) + grbit = grbit or 0x100.toShort() + else + grbit = grbit and 0xFEFF.toShort() + 9 -> if (set) + grbit = grbit or 0x200.toShort() + else + grbit = grbit and 0xFDFF.toShort() + 10 -> if (set) + grbit = grbit or 0x400.toShort() + else + grbit = grbit and 0xFBFF.toShort() + 11 -> if (set) + grbit = grbit or 0x800.toShort() + else + grbit = grbit and 0xF7FF.toShort() + 12 -> if (set) + grbit = grbit or 0x1000.toShort() + else + grbit = grbit and 0xEFFF.toShort() + 13 -> if (set) + grbit = grbit or 0x2000.toShort() + else + grbit = grbit and 0xDFFF.toShort() + 14 -> if (set) + grbit = grbit or 0x4000.toShort() + else + grbit = grbit and 0xBFFF.toShort() + 15 -> if (set) + grbit = grbit or 0x8000.toShort() + else + grbit = grbit and 0x7FFF.toShort() + } + return grbit + } + } +} + + \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/CompatibleBigDecimal.java b/src/main/java/io/starter/toolkit/CompatibleBigDecimal.kt similarity index 57% rename from src/main/java/io/starter/toolkit/CompatibleBigDecimal.java rename to src/main/java/io/starter/toolkit/CompatibleBigDecimal.kt index 9eb3919..5265b13 100644 --- a/src/main/java/io/starter/toolkit/CompatibleBigDecimal.java +++ b/src/main/java/io/starter/toolkit/CompatibleBigDecimal.kt @@ -20,71 +20,78 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit + /** * CompatibleBigDecimal.java - *

                      - *

                      + * + * + * + * * CompatibleBigDecimal deals with the java 1.4-1.5 transition error of BigDecimal.toString(). - *

                      + * + * * Prior to 1.5, BigDecimal.toString would not return Scientific Notation. JDK1.5 now allows Scientific Notation * to be returned in some cases for this method. A new method has been created, .toPlainString that mimics the * behavior of the old .toString. As we do not know the runtime JDK, and returning correctly formatted numbers * is critical to the functionality of OpenXLS, this class mimics 1.4 functionality for toString under either JDK. - *

                      + * + * * A static method is used to increase performance for multiple calls. */ -import java.lang.reflect.Method; -import java.math.BigDecimal; +import java.lang.reflect.Method +import java.math.BigDecimal -public class CompatibleBigDecimal extends BigDecimal { +class CompatibleBigDecimal : BigDecimal { /** - * serialVersionUID + * Constructor */ - private static final long serialVersionUID = -6816994951413033200L; - private static Method _methodToString = null; + constructor(bd: BigDecimal) : super(bd.unscaledValue(), bd.scale()) {} + + constructor(num: String) : super(num) {} - // Create the static method we can access later. This could be .toPlainString or .toString. - static { - try { - _methodToString = BigDecimal.class.getMethod("toPlainString", (Class[]) null); - } catch (NoSuchMethodException e) { + /** + * Compatible toString functionality + * + * @see java.math.BigDecimal.toString + */ + fun toCompatibleString(): String? { + if (_methodToString != null) { try { - _methodToString = BigDecimal.class.getMethod("toString", (Class[]) null); - } catch (NoSuchMethodException ex) { - Logger.logWarn("Error creating toString method in CompatibleBigDecimal"); + return _methodToString!!.invoke(this, *null as Array?) as String + } catch (e: Exception) { + Logger.logWarn("Error in calling CompatibleBigDecimal.toString") } + } + return null } - /** - * Constructor - **/ - public CompatibleBigDecimal(BigDecimal bd) { - super(bd.unscaledValue(), bd.scale()); - } + companion object { - public CompatibleBigDecimal(String num) { - super(num); - } + /** + * serialVersionUID + */ + private val serialVersionUID = -6816994951413033200L + private var _methodToString: Method? = null - /** - * Compatible toString functionality - * - * @see java.math.BigDecimal#toString() - */ - public String toCompatibleString() { - if (_methodToString != null) { + // Create the static method we can access later. This could be .toPlainString or .toString. + init { try { - return (String) _methodToString.invoke(this, (Object[]) null); - } catch (Exception e) { - Logger.logWarn("Error in calling CompatibleBigDecimal.toString"); + _methodToString = BigDecimal::class.java!!.getMethod("toPlainString", *null as Array>?) + } catch (e: NoSuchMethodException) { + try { + _methodToString = BigDecimal::class.java!!.getMethod("toString", *null as Array>?) + } catch (ex: NoSuchMethodException) { + Logger.logWarn("Error creating toString method in CompatibleBigDecimal") + } + } + } - return null; } } diff --git a/src/main/java/io/starter/toolkit/CompatibleVector.java b/src/main/java/io/starter/toolkit/CompatibleVector.kt similarity index 52% rename from src/main/java/io/starter/toolkit/CompatibleVector.java rename to src/main/java/io/starter/toolkit/CompatibleVector.kt index 2c46d7c..40764d3 100644 --- a/src/main/java/io/starter/toolkit/CompatibleVector.java +++ b/src/main/java/io/starter/toolkit/CompatibleVector.kt @@ -20,94 +20,99 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.Vector; +import java.util.NoSuchElementException +import java.util.Vector /** * a Vector class designed to provide forwards compatibility for JDK1.1 * programs. */ -public class CompatibleVector extends Vector { +class CompatibleVector : Vector { + private var change_offset = 0 + private val reindex_change_size = 1000 + + internal var hits = 0 + internal var misses = 0 + + /* */ /** - * serialVersionUID + * Index of element to be returned by subsequent call to next. + */ + internal var cursor = 0 + + /** + * Index of element returned by most recent call to next or previous. Reset to + * -1 if this element is deleted by a call to remove. */ - private static final long serialVersionUID = 6805047965683753637L; - private int change_offset = 0; - private int reindex_change_size = 1000; + internal var lastRet = -1 /** * reset the hints for all vector elements expense is linear to size but will * increase accuracy of subsequent 'indexOf' calls. */ - public void resetHints(boolean ignore_records) { + fun resetHints(ignore_records: Boolean) { // ExcelTools.benchmark("Re-indexing CompatibleVector" + reindex_change_size++); if (!ignore_records) { - for (int t = 0; t < this.size(); t++) { + for (t in 0 until this.size) { try { - ((CompatibleVectorHints) this.get(t)).setRecordIndexHint(t); - } catch (Exception e) { - return; + (this[t] as CompatibleVectorHints).recordIndexHint = t + } catch (e: Exception) { + return } + } } - change_offset = 0; + change_offset = 0 } - public CompatibleVector() { - super(); - } + constructor() : super() {} - public CompatibleVector(int i) { - super(i); - } + constructor(i: Int) : super(i) {} - @Override - public Iterator iterator() { - return new Itr(); + override fun iterator(): Iterator<*> { + return Itr() } - int hits = 0, misses = 0; - /** * if the object being checked implements index hints, the lookup can be * performed much faster - *

                      + * + * * speed of lookups is affected by 'shuffling' positions of vector elements. */ - public int indexOf(CompatibleVectorHints r) { + fun indexOf(r: CompatibleVectorHints): Int { // return super.indexOf(r); - int x = r.getRecordIndexHint(); - if ((x > 0) && (x < super.size())) + var x = r.recordIndexHint + if (x > 0 && x < super.size) if (super.elementAt(x) != null) { - if (super.elementAt(x).equals(r)) { + if (super.elementAt(x) == r) { // Logger.logInfo("hit/miss="+ hits++ + ":"+ misses); // hits++; - return x; + return x } } - x -= change_offset; - if ((x > 0) && (x < super.size())) - if (super.elementAt(x).equals(r)) { + x -= change_offset + if (x > 0 && x < super.size) + if (super.elementAt(x) == r) { // Logger.logInfo("hit/miss="+ hits++ + ":"+ misses); // hits++; - return x; + return x } - int t = -1; + var t = -1 if (change_offset > reindex_change_size) - this.resetHints(false); + this.resetHints(false) // if(x>0)t = super.indexOf(r,x); if (x > 0) - t = super.indexOf(r); + t = super.indexOf(r) if (t < 0) - t = super.indexOf(r); - r.setRecordIndexHint(t); + t = super.indexOf(r) + r.recordIndexHint = t // Logger.logInfo("hit/miss="+ hits++ + ":"+ misses++); - return t; + return t /**/ /* * int idx = r.getRecordIndexHint(); int retval = -1; int recsz = this.size(); @@ -128,35 +133,23 @@ public int indexOf(CompatibleVectorHints r) { */ } - /* */ - /** - * Index of element to be returned by subsequent call to next. - */ - int cursor = 0; - - /** - * Index of element returned by most recent call to next or previous. Reset to - * -1 if this element is deleted by a call to remove. - */ - int lastRet = -1; - /** * overriding AbstractList so we can have concurrent mods... */ - public Object next() { + operator fun next(): Any { // checkForComodification(); try { - Object next = get(cursor); - lastRet = cursor++; - return next; - } catch (IndexOutOfBoundsException e) { - throw new NoSuchElementException(); + val next = get(cursor) + lastRet = cursor++ + return next + } catch (e: IndexOutOfBoundsException) { + throw NoSuchElementException() } + } - @Override - public Object get(int idx) { - return super.elementAt(idx); + override fun get(idx: Int): Any { + return super.elementAt(idx) } /* @@ -164,136 +157,141 @@ public Object get(int idx) { * order (1,2,3) Returns true if all values can be represented by double and the * element is inserted */ - public boolean addOrderedDouble(Double obj) { - double d = obj.doubleValue(); + fun addOrderedDouble(obj: Double): Boolean { try { - for (int i = 0; i < super.size(); i++) { - Double dd = (Double) super.elementAt(i); - if (dd.doubleValue() > d) { - super.insertElementAt(obj, i); - return true; + for (i in 0 until super.size) { + val dd = super.elementAt(i) as Double + if (dd > obj) { + super.insertElementAt(obj, i) + return true } } - } catch (Exception e) { - return false; + } catch (e: Exception) { + return false } - super.add(obj); - return true; + + super.add(obj) + return true } /* * Returns the last object in the collection * */ - public Object last() { - return super.elementAt(super.size() - 1); + fun last(): Any { + return super.elementAt(super.size - 1) } - public boolean add(CompatibleVectorHints obj) { - this.change_offset++; // - int idx = super.size(); + fun add(obj: CompatibleVectorHints?): Boolean { + this.change_offset++ // + val idx = super.size if (obj != null) - obj.setRecordIndexHint(idx); + obj.recordIndexHint = idx try { - super.insertElementAt(obj, idx); - return true; - } catch (Exception e) { - return false; + super.insertElementAt(obj, idx) + return true + } catch (e: Exception) { + return false } + } - public void add(int idx, CompatibleVectorHints obj) { - this.change_offset++;// - obj.setRecordIndexHint(idx); - super.insertElementAt(obj, idx); + fun add(idx: Int, obj: CompatibleVectorHints) { + this.change_offset++// + obj.recordIndexHint = idx + super.insertElementAt(obj, idx) } - public void addAll(CompatibleVector cv) { - for (int i = 0; i < cv.size(); i++) { - Object b = cv.get(i); - if (b instanceof CompatibleVectorHints) { - this.add((CompatibleVectorHints) b); + fun addAll(cv: CompatibleVector) { + for (i in cv.indices) { + val b = cv[i] + if (b is CompatibleVectorHints) { + this.add(b) } else { - this.add(b); + this.add(b) } } } - @Override - public boolean remove(Object obj) { + override fun remove(obj: Any?): Boolean { if (super.remove(obj)) { - this.change_offset--; - return true; + this.change_offset-- + return true } - return false; + return false } - @Override - public void clear() { - this.change_offset = 0; - super.removeAllElements(); + override fun clear() { + this.change_offset = 0 + super.removeAllElements() } - @Override - public Object[] toArray() { - Object[] obj = new Object[super.size()]; - for (int i = 0; i < super.size(); i++) { - obj[i] = super.elementAt(i); + override fun toArray(): Array { + val obj = arrayOfNulls(super.size) + for (i in 0 until super.size) { + obj[i] = super.elementAt(i) } - return obj; + return obj } - @Override - public Object[] toArray(Object[] obj) { - for (int i = 0; i < super.size(); i++) { + override fun toArray(obj: Array): Array { + for (i in 0 until super.size) { try { - obj[i] = super.elementAt(i); - } catch (Exception e) { - Logger.logInfo("CompatibleVector.toArray() failed."); + obj[i] = super.elementAt(i) + } catch (e: Exception) { + Logger.logInfo("CompatibleVector.toArray() failed.") } + } - return obj; + return obj } - private class Itr implements Iterator { + private inner class Itr : Iterator { /** * Index of element to be returned by subsequent call to next. */ - int cursor = 0; + internal var cursor = 0 /** * Index of element returned by most recent call to next or previous. Reset to * -1 if this element is deleted by a call to remove. */ - int lastRet = -1; + internal var lastRet = -1 /** * The modCount value that the iterator believes that the backing List should * have. If this expectation is violated, the iterator has detected concurrent * modification. */ - int expectedModCount = modCount; + internal var expectedModCount = modCount - public boolean hasNext() { - return cursor != size(); + override fun hasNext(): Boolean { + return cursor != size } - public Object next() { - Object next = get(cursor); - lastRet = cursor++; - return next; + override fun next(): Any { + val next = get(cursor) + lastRet = cursor++ + return next } - public void remove() { + override fun remove() { if (lastRet == -1) - throw new IllegalStateException(); + throw IllegalStateException() - CompatibleVector.this.remove(lastRet); + this@CompatibleVector.removeAt(lastRet) if (lastRet < cursor) - cursor--; - lastRet = -1; - expectedModCount = modCount; + cursor-- + lastRet = -1 + expectedModCount = modCount } } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 6805047965683753637L + } } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/CompatibleVectorHints.java b/src/main/java/io/starter/toolkit/CompatibleVectorHints.kt similarity index 85% rename from src/main/java/io/starter/toolkit/CompatibleVectorHints.java rename to src/main/java/io/starter/toolkit/CompatibleVectorHints.kt index bbdfdda..5816013 100644 --- a/src/main/java/io/starter/toolkit/CompatibleVectorHints.java +++ b/src/main/java/io/starter/toolkit/CompatibleVectorHints.kt @@ -20,19 +20,16 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit -public interface CompatibleVectorHints { +interface CompatibleVectorHints { /** * provide a hint to the CompatibleVector * about this objects likely position. */ - int getRecordIndexHint(); - - /** * set index information about this * objects likely position. */ - void setRecordIndexHint(int i); + var recordIndexHint: Int } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.java b/src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.kt similarity index 53% rename from src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.java rename to src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.kt index 8d36e41..e00d6c6 100644 --- a/src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.java +++ b/src/main/java/io/starter/toolkit/DefaultTempFileGeneratorImpl.kt @@ -22,46 +22,48 @@ */ /** * TempFileManager.java - *

                      - *

                      + * + * + * + * * Feb 27, 2012 */ -package io.starter.toolkit; +package io.starter.toolkit -import java.io.File; -import java.io.IOException; +import java.io.File +import java.io.IOException /** * */ -public class DefaultTempFileGeneratorImpl implements TempFileGenerator { +class DefaultTempFileGeneratorImpl : TempFileGenerator { - public File createTempFile(String prefix, String extension) - throws IOException { - String tmpdir = System.getProperty("java.io.tmpdir"); - String lastchar = tmpdir.substring(tmpdir.length() - 1); - if (!lastchar.equals(File.separator)) { - tmpdir = tmpdir + File.separator; + @Throws(IOException::class) + override fun createTempFile(prefix: String, extension: String): File { + var tmpdir = System.getProperty("java.io.tmpdir") + val lastchar = tmpdir.substring(tmpdir.length - 1) + if (lastchar != File.separator) { + tmpdir = tmpdir + File.separator } - tmpdir += "extentech/"; - File target = null; + tmpdir += "extentech/" + var target: File? = null try { - File tdir = new File(tmpdir); + val tdir = File(tmpdir) if (!tdir.exists()) { - tdir.mkdirs(); + tdir.mkdirs() } - tdir.deleteOnExit(); + tdir.deleteOnExit() - target = File.createTempFile(prefix, extension, tdir); - } catch (Exception e) { - Logger.logInfo("Could not access temp dir: " + tmpdir);// could not create the temp folder fallback to unspecified temp file - target = File.createTempFile(prefix, extension); + target = File.createTempFile(prefix, extension, tdir) + } catch (e: Exception) { + Logger.logInfo("Could not access temp dir: $tmpdir")// could not create the temp folder fallback to unspecified temp file + target = File.createTempFile(prefix, extension) } - target.deleteOnExit(); + target!!.deleteOnExit() // target.delete(); // triggers the deleteonexit - return target; + return target } } diff --git a/src/main/java/io/starter/toolkit/DelimitedValuesParser.java b/src/main/java/io/starter/toolkit/DelimitedValuesParser.kt similarity index 54% rename from src/main/java/io/starter/toolkit/DelimitedValuesParser.java rename to src/main/java/io/starter/toolkit/DelimitedValuesParser.kt index 620b96d..716f43c 100644 --- a/src/main/java/io/starter/toolkit/DelimitedValuesParser.java +++ b/src/main/java/io/starter/toolkit/DelimitedValuesParser.kt @@ -20,85 +20,84 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit -import java.io.IOException; -import java.io.Reader; +import java.io.IOException +import java.io.Reader /** * Stream parser for delimiter-separated values formats. * These include comma separated values (CSV) and tab separated values (TSV). */ -public class DelimitedValuesParser { - /** - * Represents the type of a token. - */ - public enum Token {VALUE, NEWLINE, EOF} - - private Reader source; +class DelimitedValuesParser(private val source: Reader) { /** * The delimiter used to separate values. */ - private char delimiter = '\t'; + private val delimiter = '\t' /** * Contains the current value, if any. */ - private StringBuilder value = new StringBuilder(); + private val value = StringBuilder() /** * The last token returned. */ - private Token current = null; + private var current: Token? = null /** * The next token to be returned. * This is used when a single character ends a token and is itself a token. */ - private Token next = null; + private var next: Token? = null - public DelimitedValuesParser(Reader source) { - this.source = source; + /** + * Represents the type of a token. + */ + enum class Token { + VALUE, NEWLINE, EOF } - public Token next() - throws IOException { + @Throws(IOException::class) + operator fun next(): Token { // reset the value builder - value.setLength(0); + value.setLength(0) // if there's a token waiting, return it if (next != null) { - current = next; - next = null; - return current; + current = next + next = null + return current } while (true) { - int read = source.read(); + val read = source.read() if (read == -1) { - if (value.length() == 0) return current = Token.EOF; - else return current = Token.VALUE; + return if (value.length == 0) + current = Token.EOF + else + current = Token.VALUE } - if (read == delimiter) return current = Token.VALUE; + if (read == delimiter.toInt()) return current = Token.VALUE - if (read == '\n') { - if (value.length() > 0) { - if (value.charAt(value.length() - 1) == '\r') - value.setLength(value.length() - 1); - next = Token.NEWLINE; - return current = Token.VALUE; + if (read == '\n'.toInt()) { + if (value.length > 0) { + if (value[value.length - 1] == '\r') + value.setLength(value.length - 1) + next = Token.NEWLINE + return current = Token.VALUE } else { - return current = Token.NEWLINE; + return current = Token.NEWLINE } } - value.append((char) read); + value.append(read.toChar()) } } - public String getValue() { - return value.length() > 0 ? value.toString() : null; + fun getValue(): String? { + return if (value.length > 0) value.toString() else null } } diff --git a/src/main/java/io/starter/toolkit/ExceptionListener.java b/src/main/java/io/starter/toolkit/ExceptionListener.kt similarity index 86% rename from src/main/java/io/starter/toolkit/ExceptionListener.java rename to src/main/java/io/starter/toolkit/ExceptionListener.kt index 790492c..e6d4696 100644 --- a/src/main/java/io/starter/toolkit/ExceptionListener.java +++ b/src/main/java/io/starter/toolkit/ExceptionListener.kt @@ -20,11 +20,11 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit -public interface ExceptionListener { +interface ExceptionListener { - void exceptionThrown(Exception e); + fun exceptionThrown(e: Exception) } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/ExtenClassLoader.java b/src/main/java/io/starter/toolkit/ExtenClassLoader.java deleted file mode 100644 index 798b3dc..0000000 --- a/src/main/java/io/starter/toolkit/ExtenClassLoader.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - -public class ExtenClassLoader extends java.lang.ClassLoader { - private String targetClassName; - - public ExtenClassLoader(String target) { - targetClassName = target; - } - - public ExtenClassLoader() { - } - - private String wd = ""; - - public void setDirectory(String _wd) { - wd = _wd; - } - - protected byte[] loadClassFromFile(String name) { - byte[] classBytes = null; - try { - File file = null; - FileInputStream stream = null; - // name = name.substring(name.indexOf(wd)+wd.length()); // strip the working directory - name = this.wd + "/" + name; - name = StringTool.replaceChars(".", name, "/"); - file = new File(name + ".class"); - classBytes = new byte[(int) file.length()]; - stream = new FileInputStream(file); - stream.read(classBytes); - stream.close(); - } catch (IOException io) { - } - return classBytes; - } - - public synchronized Class loadClass(String name) throws ClassNotFoundException { - return loadClass(name, false); - } - - public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - Class loadedClass; - byte[] bytes; - if (!name.equals(targetClassName)) { - try { - loadedClass = super.findSystemClass(name); - return loadedClass; - } catch (ClassNotFoundException e) { - } - } - bytes = loadClassFromFile(name); - if (bytes == null) { - throw new ClassNotFoundException(); - } - loadedClass = defineClass(name, bytes, 0, bytes.length); - if (loadedClass == null) { - Logger.logInfo("Class cannot be loaded: " + name); - throw new ClassFormatError(); - } - if (resolve) { - resolveClass(loadedClass); - } - - return loadedClass; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/ExtenClassLoader.kt b/src/main/java/io/starter/toolkit/ExtenClassLoader.kt new file mode 100644 index 0000000..371bcf1 --- /dev/null +++ b/src/main/java/io/starter/toolkit/ExtenClassLoader.kt @@ -0,0 +1,98 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.io.File +import java.io.FileInputStream +import java.io.IOException + +class ExtenClassLoader : java.lang.ClassLoader { + private val targetClassName: String + + private var wd = "" + + constructor(target: String) { + targetClassName = target + } + + constructor() {} + + fun setDirectory(_wd: String) { + wd = _wd + } + + protected fun loadClassFromFile(name: String): ByteArray { + var name = name + var classBytes: ByteArray? = null + try { + var file: File? = null + var stream: FileInputStream? = null + // name = name.substring(name.indexOf(wd)+wd.length()); // strip the working directory + name = this.wd + "/" + name + name = StringTool.replaceChars(".", name, "/") + file = File("$name.class") + classBytes = ByteArray(file.length().toInt()) + stream = FileInputStream(file) + stream.read(classBytes) + stream.close() + } catch (io: IOException) { + } + + return classBytes + } + + @Synchronized + @Throws(ClassNotFoundException::class) + override fun loadClass(name: String): Class<*>? { + return loadClass(name, false) + } + + @Synchronized + @Throws(ClassNotFoundException::class) + public override fun loadClass(name: String, resolve: Boolean): Class<*>? { + var loadedClass: Class<*>? + val bytes: ByteArray? + if (name != targetClassName) { + try { + loadedClass = super.findSystemClass(name) + return loadedClass + } catch (e: ClassNotFoundException) { + } + + } + bytes = loadClassFromFile(name) + if (bytes == null) { + throw ClassNotFoundException() + } + loadedClass = defineClass(name, bytes, 0, bytes.size) + if (loadedClass == null) { + Logger.logInfo("Class cannot be loaded: $name") + throw ClassFormatError() + } + if (resolve) { + resolveClass(loadedClass) + } + + return loadedClass + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/FastAddVector.java b/src/main/java/io/starter/toolkit/FastAddVector.java deleted file mode 100644 index 697a52a..0000000 --- a/src/main/java/io/starter/toolkit/FastAddVector.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.util.Enumeration; -import java.util.Iterator; - -/** - * a Vector class designed to provide forwards compatibility for JDK1.1 - * programs. - *

                      - *

                      - * // add; toArray; iterator; insert; get; indexOf; remove // TreeList = 1260 - * 7360; 3080; 160; 170; 3400; 170; // ArrayList = 220 1480; 1760; 6870; 50; - * 1540; 7200; // LinkedList = 270 7360; 3350; 55860; 290720; 2910; 55200; - */ -public class FastAddVector extends SpecialArrayList implements java.io.Serializable { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -5615615290731997512L; - - public FastAddVector() { - super(); - } - - public FastAddVector(int i) { - // super(i); - super(); - } - - @Override - public Iterator iterator() { - return new Itr(); - } - - int hits = 0, misses = 0; - - /* - * If passed in a Double, it stores it as a Double in the vector in logical - * order (1,2,3) Returns true if all values can be represented by double and the - * element is inserted - */ - public boolean addOrderedDouble(Double obj) { - double d = obj.doubleValue(); - try { - for (int i = 0; i < super.size(); i++) { - Double dd = (Double) super.get(i); - if (dd.doubleValue() > d) { - super.add(i, obj); - return true; - } - } - } catch (Exception e) { - return false; - } - super.add(obj); - return true; - } - - /* - * Returns the last object in the collection - * - */ - public Object last() { - return super.get(super.size() - 1); - } - - public void add(int idx, CompatibleVectorHints obj) { - obj.setRecordIndexHint(idx); - super.add(idx, obj); - } - - @Override - public boolean remove(Object obj) { - return super.remove(obj); - } - - @Override - public void clear() { - super.clear(); - } - - @Override - public Object[] toArray() { - if (true) - return elementData; - Object[] obj = new Object[super.size()]; - for (int i = 0; i < super.size(); i++) { - obj[i] = super.get(i); - } - return obj; - } - - public void removeAllElements() { - super.clear(); - } - - /** - * @param obar - */ - public void copyInto(Object[] obar) { - for (int x = 0; x < obar.length; x++) { - super.add(obar[x]); - } - } - - public void insertElementAt(Object ob, int i) { - super.add(i, ob); - } - - public Object lastElement() { - return super.get(super.size() - 1); - } - - public Enumeration elements() { - FastAddVectorEnumerator cve = new FastAddVectorEnumerator(this); - return cve; - } - - public Object elementAt(int t) { - return super.get(t); - } - - @Override - public Object[] toArray(Object[] obj) { - for (int i = 0; i < super.size(); i++) { - obj[i] = super.get(i); - } - return obj; - } - - final class FastAddVectorEnumerator implements Enumeration { - - private FastAddVector it = null; - int x = 0; - - FastAddVectorEnumerator(FastAddVector itx) { - it = itx; - } - - public Object nextElement() { - return it.elementAt(x++); - } - - public boolean hasMoreElements() { - return (x < it.size()); - } - - } - -} diff --git a/src/main/java/io/starter/toolkit/FastAddVector.kt b/src/main/java/io/starter/toolkit/FastAddVector.kt new file mode 100644 index 0000000..ee76ec3 --- /dev/null +++ b/src/main/java/io/starter/toolkit/FastAddVector.kt @@ -0,0 +1,166 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.util.Enumeration + +/** + * a Vector class designed to provide forwards compatibility for JDK1.1 + * programs. + * + * + * + * + * // add; toArray; iterator; insert; get; indexOf; remove // TreeList = 1260 + * 7360; 3080; 160; 170; 3400; 170; // ArrayList = 220 1480; 1760; 6870; 50; + * 1540; 7200; // LinkedList = 270 7360; 3350; 55860; 290720; 2910; 55200; + */ +class FastAddVector : SpecialArrayList, java.io.Serializable { + + internal var hits = 0 + internal var misses = 0 + + constructor() : super() {} + + constructor(i: Int) : super() {}// super(i); + + override fun iterator(): Iterator<*> { + return SpecialArrayList.Itr() + } + + /* + * If passed in a Double, it stores it as a Double in the vector in logical + * order (1,2,3) Returns true if all values can be represented by double and the + * element is inserted + */ + fun addOrderedDouble(obj: Double): Boolean { + try { + for (i in 0 until super.size) { + val dd = super.get(i) as Double + if (dd > obj) { + super.add(i, obj) + return true + } + } + } catch (e: Exception) { + return false + } + + super.add(obj) + return true + } + + /* + * Returns the last object in the collection + * + */ + fun last(): Any { + return super.get(super.size - 1) + } + + fun add(idx: Int, obj: CompatibleVectorHints) { + obj.recordIndexHint = idx + super.add(idx, obj) + } + + override fun remove(obj: Any?): Boolean { + return super.remove(obj) + } + + override fun clear() { + super.clear() + } + + override fun toArray(): Array { + if (true) + return elementData + val obj = arrayOfNulls(super.size) + for (i in 0 until super.size) { + obj[i] = super.get(i) + } + return obj + } + + fun removeAllElements() { + super.clear() + } + + /** + * @param obar + */ + fun copyInto(obar: Array) { + for (x in obar.indices) { + super.add(obar[x]) + } + } + + fun insertElementAt(ob: Any, i: Int) { + super.add(i, ob) + } + + fun lastElement(): Any { + return super.get(super.size - 1) + } + + fun elements(): Enumeration<*> { + return FastAddVectorEnumerator(this) + } + + fun elementAt(t: Int): Any { + return super.get(t) + } + + override fun toArray(obj: Array): Array { + for (i in 0 until super.size) { + obj[i] = super.get(i) + } + return obj + } + + internal inner class FastAddVectorEnumerator(itx: FastAddVector) : Enumeration { + + private var it: FastAddVector? = null + var x = 0 + + init { + it = itx + } + + override fun nextElement(): Any { + return it!!.elementAt(x++) + } + + override fun hasMoreElements(): Boolean { + return x < it!!.size + } + + } + + companion object { + /** + * serialVersionUID + */ + private const val serialVersionUID = -5615615290731997512L + } + +} diff --git a/src/main/java/io/starter/toolkit/FastGetVector.java b/src/main/java/io/starter/toolkit/FastGetVector.java deleted file mode 100644 index 232c0e3..0000000 --- a/src/main/java/io/starter/toolkit/FastGetVector.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.util.ArrayList; -import java.util.Enumeration; - - -/** - * a Vector class designed to provide forwards compatibility - * for JDK1.1 programs. - */ -public class FastGetVector extends ArrayList { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -6701901995748359720L; - private int change_offset = 0; - private int reindex_change_size = 1000; - - /** - * reset the hints for all vector elements - * expense is linear to size but will increase - * accuracy of subsequent 'indexOf' calls. - */ - public void resetHints(boolean ignore_records) { - // ExcelTools.benchmark("Re-indexing CompatibleVector" + reindex_change_size++); - if (!ignore_records) { - for (int t = 0; t < this.size(); t++) { - try { - ((CompatibleVectorHints) this.get(t)).setRecordIndexHint(t); - } catch (Exception e) { - return; - } - } - } - change_offset = 0; - } - - public FastGetVector() { - super(); - } - - public FastGetVector(int i) { - // super(i); - super(); - } - - int hits = 0, misses = 0; - - /* - * If passed in a Double, it stores it as a Double in the vector - * in logical order (1,2,3) - * Returns true if all values can be represented by double and the element is inserted - */ - public boolean addOrderedDouble(Double obj) { - double d = obj.doubleValue(); - try { - for (int i = 0; i < super.size(); i++) { - Double dd = (Double) super.get(i); - if (dd.doubleValue() > d) { - super.add(i, obj); - return true; - } - } - } catch (Exception e) { - return false; - } - super.add(obj); - return true; - } - - /* - * Returns the last object in the collection - * - */ - public Object last() { - return super.get(super.size() - 1); - } - - - public void add(int idx, CompatibleVectorHints obj) { - this.change_offset++;// - obj.setRecordIndexHint(idx); - super.add(idx, obj); - } - - /* - public void addAll(CompatibleVector cv){ - for(int i=0;i elements() { - FastGetVectorEnumerator cve = new FastGetVectorEnumerator(this); - return cve; - } - - public Object elementAt(int t) { - return super.get(t); - } - - public Object[] toArray(Object[] obj) { - for (int i = 0; i < super.size(); i++) { - obj[i] = super.get(i); - } - return obj; - } - -} - -final class FastGetVectorEnumerator implements Enumeration { - - private FastGetVector it = null; - int x = 0; - - FastGetVectorEnumerator(FastGetVector itx) { - it = itx; - } - - public Object nextElement() { - return it.elementAt(x++); - } - - public boolean hasMoreElements() { - return (x < it.size()); - } - -} diff --git a/src/main/java/io/starter/toolkit/FastGetVector.kt b/src/main/java/io/starter/toolkit/FastGetVector.kt new file mode 100644 index 0000000..5045313 --- /dev/null +++ b/src/main/java/io/starter/toolkit/FastGetVector.kt @@ -0,0 +1,190 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.util.ArrayList +import java.util.Enumeration + + +/** + * a Vector class designed to provide forwards compatibility + * for JDK1.1 programs. + */ +class FastGetVector : ArrayList { + private var change_offset = 0 + private val reindex_change_size = 1000 + + internal var hits = 0 + internal var misses = 0 + + /** + * reset the hints for all vector elements + * expense is linear to size but will increase + * accuracy of subsequent 'indexOf' calls. + */ + fun resetHints(ignore_records: Boolean) { + // ExcelTools.benchmark("Re-indexing CompatibleVector" + reindex_change_size++); + if (!ignore_records) { + for (t in 0 until this.size) { + try { + (this[t] as CompatibleVectorHints).recordIndexHint = t + } catch (e: Exception) { + return + } + + } + } + change_offset = 0 + } + + constructor() : super() {} + + constructor(i: Int) : super() {}// super(i); + + /* + * If passed in a Double, it stores it as a Double in the vector + * in logical order (1,2,3) + * Returns true if all values can be represented by double and the element is inserted + */ + fun addOrderedDouble(obj: Double): Boolean { + try { + for (i in 0 until super.size) { + val dd = super.get(i) as Double + if (dd > obj) { + super.add(i, obj) + return true + } + } + } catch (e: Exception) { + return false + } + + super.add(obj) + return true + } + + /* + * Returns the last object in the collection + * + */ + fun last(): Any { + return super.get(super.size - 1) + } + + + fun add(idx: Int, obj: CompatibleVectorHints) { + this.change_offset++// + obj.recordIndexHint = idx + super.add(idx, obj) + } + + /* + public void addAll(CompatibleVector cv){ + for(int i=0;i { + val obj = arrayOfNulls(super.size) + for (i in 0 until super.size) { + obj[i] = super.get(i) + } + return obj + } + + fun removeAllElements() { + super.clear() + } + + fun copyInto(obar: Array) { + for (x in obar.indices) { + super.add(obar) + } + } + + fun insertElementAt(ob: Any, i: Int) { + super.add(i, ob) + } + + fun lastElement(): Any { + return super.get(super.size - 1) + } + + fun elements(): Enumeration<*> { + return FastGetVectorEnumerator(this) + } + + fun elementAt(t: Int): Any { + return super.get(t) + } + + override fun toArray(obj: Array): Array { + for (i in 0 until super.size) { + obj[i] = super.get(i) + } + return obj + } + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -6701901995748359720L + } + +} + +internal class FastGetVectorEnumerator(itx: FastGetVector) : Enumeration { + + private var it: FastGetVector? = null + var x = 0 + + init { + it = itx + } + + override fun nextElement(): Any { + return it!!.elementAt(x++) + } + + override fun hasMoreElements(): Boolean { + return x < it!!.size + } + +} diff --git a/src/main/java/io/starter/toolkit/FileFilter.java b/src/main/java/io/starter/toolkit/FileFilter.kt similarity index 68% rename from src/main/java/io/starter/toolkit/FileFilter.java rename to src/main/java/io/starter/toolkit/FileFilter.kt index 7de58a4..a3c2b20 100644 --- a/src/main/java/io/starter/toolkit/FileFilter.java +++ b/src/main/java/io/starter/toolkit/FileFilter.kt @@ -26,32 +26,37 @@ * TODO To change the template for this generated file go to * Window - Preferences - Java - Code Style - Code Templates */ -package io.starter.toolkit; +package io.starter.toolkit -import java.io.File; +import java.io.File /** * @ */ -public class FileFilter implements java.io.FileFilter { +class FileFilter +/** + * + */ +(fx: String) : java.io.FileFilter { - String fix = "*"; // the pattern to match + internal var fix = "*" // the pattern to match - /** - * - */ - public FileFilter(String fx) { - this.fix = fx; + init { + this.fix = fx } /** - * @see java.io.FileFilter#accept(java.io.File) + * @see java.io.FileFilter.accept */ - public boolean accept(File pathname) { - String fn = pathname.getName(); - return fn.endsWith(fix); + override fun accept(pathname: File): Boolean { + val fn = pathname.name + return fn.endsWith(fix) } - public static void main(String[] args) { + companion object { + + @JvmStatic + fun main(args: Array) { + } } } diff --git a/src/main/java/io/starter/toolkit/GenericRecycleBin.java b/src/main/java/io/starter/toolkit/GenericRecycleBin.java deleted file mode 100644 index 5a950ff..0000000 --- a/src/main/java/io/starter/toolkit/GenericRecycleBin.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.util.*; - -/** - * A recycling cache, items are checked at intervals - */ -public abstract class GenericRecycleBin extends java.lang.Thread - implements Map, io.starter.toolkit.RecycleBin { - protected Map map = new java.util.HashMap(); - protected Vector active = new Vector(); - protected Stack spares = new Stack(); - - /** - * add an item - */ - public void addItem(Recyclable r) throws RecycleBinFullException { - if ((MAXITEMS == -1) || (map.size() < MAXITEMS)) { - addItem(Integer.valueOf(map.size()), r); - } else { - throw new RecycleBinFullException(); - } - } - - /** - * returns number of items in cache - * - * @return - */ - public int getNumItems() { - return active.size(); - } - - public void addItem(Object key, Recyclable r) throws RecycleBinFullException { - // recycle(); - if ((MAXITEMS == -1) || (map.size() < MAXITEMS)) { - active.add(r); - map.put(key, r); - - } else { - throw new RecycleBinFullException(); - } - } - - /** - * iterate all active items and try to recycle - */ - public synchronized void recycle() { - Recyclable[] rs = new Recyclable[active.size()]; - active.copyInto(rs); - for (int t = 0; t < rs.length; t++) { - try { - Recyclable rb = rs[t]; - if (!rb.inUse()) { - // recycle it - rb.recycle(); - - // remove from active and lookup - active.remove(rb); - map.remove(rb); - - // put in spares - spares.push(rb); - - } - } catch (Exception ex) { - Logger.logErr("recycle failed", ex); - } - } - - } - - public void empty() { - map.clear(); - active.clear(); - } - - public synchronized List getAll() { - return active; - } - - /** - * returns a new or recycled item from the spares pool - * - * @see io.starter.toolkit.RecycleBin#getItem() - */ - public synchronized Recyclable getItem() throws RecycleBinFullException { - Recyclable active = null; - // spares contains the recycled - if (spares.size() > 0) { - active = spares.pop(); - addItem(active); - return active; - } - recycle(); - - // technically infinite loop until exception thrown - return getItem(); - } - - protected int MAXITEMS = -1; // no limit is default - - /** - * max number of items to be put in this bin. - */ - public void setMaxItems(int i) { - MAXITEMS = i; - } - - public int getMaxItems() { - return MAXITEMS; - } - - public int getSpareCount() { - return spares.size(); - } - - public GenericRecycleBin() { - } - - public void clear() { - map.clear(); - active.clear(); - } - - public boolean containsKey(Object key) { - return map.containsKey(key); - - } - - public boolean containsValue(Object value) { - return map.containsValue(value); - } - - public Set entrySet() { - return map.entrySet(); - } - - @Override - public boolean equals(Object o) { - return map.equals(o); - } - - public Object get(Object key) { - return map.get(key); - } - - @Override - public int hashCode() { - return map.hashCode(); - } - - public boolean isEmpty() { - return map.isEmpty(); - } - - public Set keySet() { - return map.keySet(); - } - - public Object put(Object arg0, Object arg1) { - active.add(arg1); - return map.put(arg0, arg1); - } - - public void putAll(Map arg0) { - active.addAll(arg0.entrySet()); - map.putAll(arg0); - } - - public Object remove(Object key) { - active.remove(map.get(key)); - return map.remove(key); - } - - public int size() { - return map.size(); - } - - @Override - public String toString() { - return map.toString(); - } - - public Collection values() { - return map.values(); - } - - public java.util.Map getMap() { - return map; - } - - public void setMap(java.util.HashMap _map) { - map = _map; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/GenericRecycleBin.kt b/src/main/java/io/starter/toolkit/GenericRecycleBin.kt new file mode 100644 index 0000000..9215b00 --- /dev/null +++ b/src/main/java/io/starter/toolkit/GenericRecycleBin.kt @@ -0,0 +1,214 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.util.* + +/** + * A recycling cache, items are checked at intervals + */ +abstract class GenericRecycleBin : java.lang.Thread(), Map, io.starter.toolkit.RecycleBin { + protected var map: MutableMap = java.util.HashMap() + protected var active = Vector() + protected var spares = Stack() + + /** + * returns number of items in cache + * + * @return + */ + val numItems: Int + get() = active.size + + override val all: List + @Synchronized get() = active + + /** + * returns a new or recycled item from the spares pool + * + * @see io.starter.toolkit.RecycleBin.getItem + */ + override// spares contains the recycled + // technically infinite loop until exception thrown + val item: Recyclable? + @Synchronized @Throws(RecycleBinFullException::class) + get() { + var active: Recyclable? = null + if (spares.size > 0) { + active = spares.pop() + addItem(active) + return active + } + recycle() + return item + } + + protected var MAXITEMS = -1 // no limit is default + + val spareCount: Int + get() = spares.size + + /** + * add an item + */ + @Throws(RecycleBinFullException::class) + override fun addItem(r: Recyclable?) { + if (MAXITEMS == -1 || map.size < MAXITEMS) { + addItem(Integer.valueOf(map.size), r) + } else { + throw RecycleBinFullException() + } + } + + @Throws(RecycleBinFullException::class) + override fun addItem(key: Any?, r: Recyclable?) { + // recycle(); + if (MAXITEMS == -1 || map.size < MAXITEMS) { + active.add(r) + map[key] = r + + } else { + throw RecycleBinFullException() + } + } + + /** + * iterate all active items and try to recycle + */ + @Synchronized + fun recycle() { + val rs = arrayOfNulls(active.size) + active.copyInto(rs) + for (t in rs.indices) { + try { + val rb = rs[t] + if (!rb.inUse()) { + // recycle it + rb.recycle() + + // remove from active and lookup + active.remove(rb) + map.remove(rb) + + // put in spares + spares.push(rb) + + } + } catch (ex: Exception) { + Logger.logErr("recycle failed", ex) + } + + } + + } + + override fun empty() { + map.clear() + active.clear() + } + + /** + * max number of items to be put in this bin. + */ + override fun setMaxItems(i: Int) { + MAXITEMS = i + } + + fun getMaxItems(): Int { + return MAXITEMS + } + + override fun clear() { + map.clear() + active.clear() + } + + override fun containsKey(key: Any): Boolean { + return map.containsKey(key) + + } + + override fun containsValue(value: Any): Boolean { + return map.containsValue(value) + } + + override fun entrySet(): Set<*> { + return map.entries + } + + override fun equals(o: Any?): Boolean { + return map == o + } + + override fun get(key: Any): Any { + return map[key] + } + + override fun hashCode(): Int { + return map.hashCode() + } + + override fun isEmpty(): Boolean { + return map.isEmpty() + } + + override fun keySet(): Set { + return map.keys + } + + override fun put(arg0: Any, arg1: Any): Any? { + active.add(arg1) + return map.put(arg0, arg1) + } + + override fun putAll(arg0: Map<*, *>) { + active.addAll(arg0.entries) + map.putAll(arg0) + } + + override fun remove(key: Any): Any { + active.remove(map[key]) + return map.remove(key) + } + + override fun size(): Int { + return map.size + } + + override fun toString(): String { + return map.toString() + } + + override fun values(): Collection { + return map.values + } + + fun getMap(): Map<*, *> { + return map + } + + fun setMap(_map: java.util.HashMap<*, *>) { + map = _map + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/InFile.java b/src/main/java/io/starter/toolkit/InFile.java deleted file mode 100644 index 0c9dfc3..0000000 --- a/src/main/java/io/starter/toolkit/InFile.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.io.*; - -public class InFile extends DataInputStream { - - StringBuffer sb = new StringBuffer(); - - public InFile(String filename) - throws FileNotFoundException { - super(new BufferedInputStream(new FileInputStream(new File(filename)))); - } - - /* public InFile(String filename) - throws FileNotFoundException { - super(new BufferedInputStream(new FileInputStream(new File(filename)))); - } - */ - - public InFile(File file) - throws FileNotFoundException { - this(file.getPath()); - } - - /** - * Reads File from Disk - * - * @param fname path to file - */ - public String readFile() { - try { - while (this.available() != 0) { - sb.append(this.readLine()); - } - } catch (FileNotFoundException e) { - Logger.logInfo("FNF Exception in InFile: " + e); - } catch (IOException e) { - Logger.logInfo("IO Exception in InFile: " + e); - } - return sb.toString(); - } - - - /** - * Gets a byte arrray from a file - * - * @param file File the file to get bytes from - * @return byte[] Returns byte[] array file contents - */ - public static byte[] getBytesFromFile(File file) throws IOException { - InputStream fis = new FileInputStream(file); - long length = file.length(); - byte[] ret = new byte[(int) length]; - int offset = 0; - int numRead = 0; - while ((offset < ret.length) && ((numRead = fis.read(ret, offset, ret.length - offset)) >= 0)) { - offset += numRead; - } - if (offset < ret.length) { - throw new IOException("Read file failed -- all bytes not retreived. " + file.getName()); - } - fis.close(); - return ret; - - } - -} - diff --git a/src/main/java/io/starter/toolkit/InFile.kt b/src/main/java/io/starter/toolkit/InFile.kt new file mode 100644 index 0000000..38fd38f --- /dev/null +++ b/src/main/java/io/starter/toolkit/InFile.kt @@ -0,0 +1,90 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.io.* + +class InFile @Throws(FileNotFoundException::class) +constructor(filename: String) : DataInputStream(BufferedInputStream(FileInputStream(File(filename)))) { + + internal var sb = StringBuffer() + + /* public InFile(String filename) + throws FileNotFoundException { + super(new BufferedInputStream(new FileInputStream(new File(filename)))); + } + */ + + @Throws(FileNotFoundException::class) + constructor(file: File) : this(file.path) { + } + + /** + * Reads File from Disk + * + * @param fname path to file + */ + fun readFile(): String { + try { + while (this.available() != 0) { + sb.append(this.readLine()) + } + } catch (e: FileNotFoundException) { + Logger.logInfo("FNF Exception in InFile: $e") + } catch (e: IOException) { + Logger.logInfo("IO Exception in InFile: $e") + } + + return sb.toString() + } + + companion object { + + + /** + * Gets a byte arrray from a file + * + * @param file File the file to get bytes from + * @return byte[] Returns byte[] array file contents + */ + @Throws(IOException::class) + fun getBytesFromFile(file: File): ByteArray { + val fis = FileInputStream(file) + val length = file.length() + val ret = ByteArray(length.toInt()) + var offset = 0 + var numRead = 0 + while (offset < ret.size && (numRead = fis.read(ret, offset, ret.size - offset)) >= 0) { + offset += numRead + } + if (offset < ret.size) { + throw IOException("Read file failed -- all bytes not retreived. " + file.name) + } + fis.close() + return ret + + } + } + +} + diff --git a/src/main/java/io/starter/toolkit/IndirectOutputStream.java b/src/main/java/io/starter/toolkit/IndirectOutputStream.java deleted file mode 100644 index d96a2f6..0000000 --- a/src/main/java/io/starter/toolkit/IndirectOutputStream.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * An IndirectOutputStream forwards all requests unmodified to - * another output stream which may be changed at runtime. By default an - * IOException will be thrown if no sink is configured when a - * request is received. The stream may be configured to drop such requests - * instead. - */ -public class IndirectOutputStream - extends OutputStream { - private OutputStream sink; - private boolean discardOnNull; - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - /** - * Creates a new IndirectOutputStream with no sink which - * fails when no sink is present. - */ - public IndirectOutputStream() { - this(null); - } - - /** - * Creates a new IndirectOutputStream with the given sink - * which fails when no sink is present. - * - * @param sink the initial sink - */ - public IndirectOutputStream(OutputStream sink) { - this(sink, false); - } - - /** - * Creates a new IndirectOutputStream with the given sink - * and behavior when no sink is present. - * - * @param sink the initial sink - * @param discard whether to discard requests when no sink is present - */ - public IndirectOutputStream(OutputStream sink, boolean discard) { - this.sink = sink; - this.discardOnNull = discard; - } - - /** - * Gets the currently configured sink. - * - * @return the stream to which requests are currently being forwarded - * or null if no sink present - */ - public OutputStream getSink() { - // don't bother with a read lock, a single read is atomic - return sink; - } - - /** - * Sets the stream to which requests are forwarded. - * - * @param sink the stream to which requests should be forwarded - * or null to remove the current sink - */ - public void setSink(OutputStream sink) { - lock.writeLock().lock(); - try { - this.sink = sink; - } finally { - lock.writeLock().unlock(); - } - } - - /** - * Gets the current behavior when no wink is present. - * - * @return whether requests will be discarded when no sink is present - */ - public boolean discardOnNoSink() { - // don't bother with a read lock, a single read is atomic - return discardOnNull; - } - - /** - * Sets the behavior when no sink is present. - * - * @param discard whether to discard requests when no sink is present - */ - public void discardOnNoSink(boolean discard) { - // don't bother with a write lock, a single write is atomic - // as are all uses of this field - this.discardOnNull = discard; - } - - private boolean checkSink() - throws IOException { - if (null == sink) { - if (discardOnNull) return true; - else throw new IOException("sink not connected"); - } - return false; - } - - @Override - public synchronized void write(int b) - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.write(b); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public synchronized void write(byte[] b) - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.write(b); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public synchronized void write(byte[] b, int off, int len) - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.write(b, off, len); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public synchronized void flush() - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.flush(); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public synchronized void close() - throws IOException { - lock.readLock().lock(); - try { - if (this.checkSink()) return; - sink.close(); - } finally { - lock.readLock().unlock(); - } - } -} diff --git a/src/main/java/io/starter/toolkit/IndirectOutputStream.kt b/src/main/java/io/starter/toolkit/IndirectOutputStream.kt new file mode 100644 index 0000000..e1a96ce --- /dev/null +++ b/src/main/java/io/starter/toolkit/IndirectOutputStream.kt @@ -0,0 +1,173 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.io.IOException +import java.io.OutputStream +import java.util.concurrent.locks.ReentrantReadWriteLock + +/** + * An `IndirectOutputStream` forwards all requests unmodified to + * another output stream which may be changed at runtime. By default an + * `IOException` will be thrown if no sink is configured when a + * request is received. The stream may be configured to drop such requests + * instead. + */ +class IndirectOutputStream +/** + * Creates a new `IndirectOutputStream` with the given sink + * and behavior when no sink is present. + * + * @param sink the initial sink + * @param discard whether to discard requests when no sink is present + */ +@JvmOverloads constructor(private var sink: OutputStream? = null, private var discardOnNull: Boolean = false) : OutputStream() { + private val lock = ReentrantReadWriteLock() + + /** + * Gets the currently configured sink. + * + * @return the stream to which requests are currently being forwarded + * or `null` if no sink present + */ + fun getSink(): OutputStream? { + // don't bother with a read lock, a single read is atomic + return sink + } + + /** + * Sets the stream to which requests are forwarded. + * + * @param sink the stream to which requests should be forwarded + * or `null` to remove the current sink + */ + fun setSink(sink: OutputStream) { + lock.writeLock().lock() + try { + this.sink = sink + } finally { + lock.writeLock().unlock() + } + } + + /** + * Gets the current behavior when no wink is present. + * + * @return whether requests will be discarded when no sink is present + */ + fun discardOnNoSink(): Boolean { + // don't bother with a read lock, a single read is atomic + return discardOnNull + } + + /** + * Sets the behavior when no sink is present. + * + * @param discard whether to discard requests when no sink is present + */ + fun discardOnNoSink(discard: Boolean) { + // don't bother with a write lock, a single write is atomic + // as are all uses of this field + this.discardOnNull = discard + } + + @Throws(IOException::class) + private fun checkSink(): Boolean { + return if (null == sink) { + if (discardOnNull) + true + else + throw IOException("sink not connected") + } else false + } + + @Synchronized + @Throws(IOException::class) + override fun write(b: Int) { + lock.readLock().lock() + try { + if (this.checkSink()) return + sink!!.write(b) + } finally { + lock.readLock().unlock() + } + } + + @Synchronized + @Throws(IOException::class) + override fun write(b: ByteArray) { + lock.readLock().lock() + try { + if (this.checkSink()) return + sink!!.write(b) + } finally { + lock.readLock().unlock() + } + } + + @Synchronized + @Throws(IOException::class) + override fun write(b: ByteArray, off: Int, len: Int) { + lock.readLock().lock() + try { + if (this.checkSink()) return + sink!!.write(b, off, len) + } finally { + lock.readLock().unlock() + } + } + + @Synchronized + @Throws(IOException::class) + override fun flush() { + lock.readLock().lock() + try { + if (this.checkSink()) return + sink!!.flush() + } finally { + lock.readLock().unlock() + } + } + + @Synchronized + @Throws(IOException::class) + override fun close() { + lock.readLock().lock() + try { + if (this.checkSink()) return + sink!!.close() + } finally { + lock.readLock().unlock() + } + } +} +/** + * Creates a new `IndirectOutputStream` with no sink which + * fails when no sink is present. + */ +/** + * Creates a new `IndirectOutputStream` with the given sink + * which fails when no sink is present. + * + * @param sink the initial sink + */ diff --git a/src/main/java/io/starter/toolkit/JFileWriter.java b/src/main/java/io/starter/toolkit/JFileWriter.java deleted file mode 100644 index 90c4a06..0000000 --- a/src/main/java/io/starter/toolkit/JFileWriter.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.io.*; -import java.nio.channels.FileChannel; -import java.nio.charset.StandardCharsets; - -/** - * File utilities. - */ -public class JFileWriter { - - java.lang.String - path = "", - filename = "", - data = ""; - byte newLine = Character.LINE_SEPARATOR; - - public void setPath(String p) { - path = p; - } - - public void setFileName(String f) { - filename = f; - } - - public void setData(String d) { - data = d; - } - - void printErr(String err) { - Logger.logInfo("Error in JFileWriter: " + err); - Logger.logWarn("Error in JFileWriter: " + err); - } - - - /** - * append text to the end of a text file - */ - public static final synchronized void appendToFile(String pth, String text) { - try { - byte[] bbuf = text.getBytes(StandardCharsets.UTF_8); - File outp = new File(pth); - - if (!outp.exists()) { - outp.mkdirs(); - outp.delete(); - outp = new java.io.File(pth); - } - - RandomAccessFile outputFile = new RandomAccessFile(outp, "rw"); - outputFile.skipBytes((int) outputFile.length()); - int strt = 0; - if (outp.exists()) strt = (int) outputFile.length(); - outputFile.write(bbuf, 0, bbuf.length); - outputFile.close(); - } catch (Exception e) { - Logger.logInfo("JFileWriter.appendToFile() IO Error : " + e.toString()); - } - } - - /** - * write the inputstream contents to file - * - * @param is - * @param file - * @throws IOException - */ - public static void writeToFile(InputStream is, File file) throws IOException { - DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); - - // Transfer bytes from in to out - byte[] buf = new byte[1024]; - int len; - while ((len = is.read(buf)) > 0) { - out.write(buf, 0, len); - } - is.close(); - out.close(); - } - - public boolean writeIt() { - try { - path += filename; - StringReader SR = new StringReader(data); - File outputFile = new File(path); - FileWriter out = new FileWriter(outputFile); - int c; - if (outputFile.length() > 0) { - return false; - } - while ((c = SR.read()) != -1) out.write(c); - out.flush(); - out.close(); - } catch (IOException e) { - Logger.logInfo("JFileWriter IO Error : " + e.toString()); - } - return true; - } - - - public boolean writeIt(String data, String filename, String path) { - try { - path += filename; - StringReader SR = new StringReader(data); - File outputFile = new File(path); - FileWriter out = new FileWriter(outputFile); - int c; - if (outputFile.length() > 0) { - return false; - } - while ((c = SR.read()) != -1) { - out.write(c); - } - out.flush(); - out.close(); - } catch (IOException e) { - Logger.logInfo("JFileWriter IO Error : " + e.toString()); - } - return true; - } - - public String readFile(String fname) { - StringBuffer addTxt = new StringBuffer(); - try { - BufferedReader d - = new BufferedReader(new FileReader(fname)); - - while (d.ready()) addTxt.append(d.readLine()); - d.close(); - } catch (Exception e) { - printErr("problem reading file: " + e); - } - return addTxt.toString(); - } - - public static void copyFile(String infile, String outfile) - throws IOException { - File fx = new File(infile); - copyFile(fx, outfile); - - // this.writeLine(outfile, this.readFile(infile)); - } - - - /** - * Copy method, using FileChannel#transferTo - * NOTE: will overwrite existing files - * - * @param File source - * @param File target - * @throws IOException - */ - public static void copyFile(File source, String target) - throws IOException { - File fout = new File(target); - fout.mkdirs(); - fout.delete(); - fout = new File(target); - FileChannel in = new FileInputStream(source).getChannel(); - FileChannel out = new FileOutputStream(target).getChannel(); - in.transferTo(0, in.size(), out); - in.close(); - out.close(); - } - - public void writeLine(String file, String line) { - String s; - try { - File f = new File(file); - // f.mkdirs(); - FileWriter out = new FileWriter(f); - DataInputStream inStream = new DataInputStream(new StringBufferInputStream(line)); - while ((s = inStream.readLine()) != null) { - out.write(s); - out.write(newLine); - } - out.close(); - } catch (FileNotFoundException e) { - printErr(e.toString()); - } catch (Exception e) { - printErr(e.toString()); - } - } - - public void writeLogToFile(String fname, javax.swing.JTextArea jta) { - try { - OutFile n = new OutFile(fname); - String logText = jta.getText(); - n.writeBytes(logText); - jta.setText(""); - n.close(); - } catch (FileNotFoundException e) { - printErr(e.toString()); - } catch (IOException e) { - printErr(e.toString()); - } - } - - public String readLog(String logFname) { - String addTxt = ""; - try { - InFile n = new InFile(logFname); - while (n.available() != 0) { - addTxt += n.readLine(); - } - } catch (FileNotFoundException e) { - printErr(e.toString()); - } catch (IOException e) { - printErr(e.toString()); - } - return addTxt += "\r\n"; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/JFileWriter.kt b/src/main/java/io/starter/toolkit/JFileWriter.kt new file mode 100644 index 0000000..bd00d6d --- /dev/null +++ b/src/main/java/io/starter/toolkit/JFileWriter.kt @@ -0,0 +1,246 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.io.* +import java.nio.channels.FileChannel +import java.nio.charset.StandardCharsets + +/** + * File utilities. + */ +class JFileWriter { + + internal var path: java.lang.String = "" + internal var filename: java.lang.String = "" + internal var data: java.lang.String = "" + internal var newLine = Character.LINE_SEPARATOR + + fun setPath(p: String) { + path = p + } + + fun setFileName(f: String) { + filename = f + } + + fun setData(d: String) { + data = d + } + + internal fun printErr(err: String) { + Logger.logInfo("Error in JFileWriter: $err") + Logger.logWarn("Error in JFileWriter: $err") + } + + fun writeIt(): Boolean { + try { + path += filename + val SR = StringReader(data) + val outputFile = File(path) + val out = FileWriter(outputFile) + var c: Int + if (outputFile.length() > 0) { + return false + } + while ((c = SR.read()) != -1) out.write(c) + out.flush() + out.close() + } catch (e: IOException) { + Logger.logInfo("JFileWriter IO Error : $e") + } + + return true + } + + + fun writeIt(data: String, filename: String, path: String): Boolean { + var path = path + try { + path += filename + val SR = StringReader(data) + val outputFile = File(path) + val out = FileWriter(outputFile) + var c: Int + if (outputFile.length() > 0) { + return false + } + while ((c = SR.read()) != -1) { + out.write(c) + } + out.flush() + out.close() + } catch (e: IOException) { + Logger.logInfo("JFileWriter IO Error : $e") + } + + return true + } + + fun readFile(fname: String): String { + val addTxt = StringBuffer() + try { + val d = BufferedReader(FileReader(fname)) + + while (d.ready()) addTxt.append(d.readLine()) + d.close() + } catch (e: Exception) { + printErr("problem reading file: $e") + } + + return addTxt.toString() + } + + fun writeLine(file: String, line: String) { + var s: String + try { + val f = File(file) + // f.mkdirs(); + val out = FileWriter(f) + val inStream = DataInputStream(StringBufferInputStream(line)) + while ((s = inStream.readLine()) != null) { + out.write(s) + out.write(newLine.toInt()) + } + out.close() + } catch (e: FileNotFoundException) { + printErr(e.toString()) + } catch (e: Exception) { + printErr(e.toString()) + } + + } + + fun writeLogToFile(fname: String, jta: javax.swing.JTextArea) { + try { + val n = OutFile(fname) + val logText = jta.text + n.writeBytes(logText) + jta.text = "" + n.close() + } catch (e: FileNotFoundException) { + printErr(e.toString()) + } catch (e: IOException) { + printErr(e.toString()) + } + + } + + fun readLog(logFname: String): String { + var addTxt = "" + try { + val n = InFile(logFname) + while (n.available() != 0) { + addTxt += n.readLine() + } + } catch (e: FileNotFoundException) { + printErr(e.toString()) + } catch (e: IOException) { + printErr(e.toString()) + } + + return addTxt += "\r\n" + } + + companion object { + + + /** + * append text to the end of a text file + */ + @Synchronized + fun appendToFile(pth: String, text: String) { + try { + val bbuf = text.toByteArray(StandardCharsets.UTF_8) + var outp = File(pth) + + if (!outp.exists()) { + outp.mkdirs() + outp.delete() + outp = java.io.File(pth) + } + + val outputFile = RandomAccessFile(outp, "rw") + outputFile.skipBytes(outputFile.length().toInt()) + var strt = 0 + if (outp.exists()) strt = outputFile.length().toInt() + outputFile.write(bbuf, 0, bbuf.size) + outputFile.close() + } catch (e: Exception) { + Logger.logInfo("JFileWriter.appendToFile() IO Error : $e") + } + + } + + /** + * write the inputstream contents to file + * + * @param is + * @param file + * @throws IOException + */ + @Throws(IOException::class) + fun writeToFile(`is`: InputStream, file: File) { + val out = DataOutputStream(BufferedOutputStream(FileOutputStream(file))) + + // Transfer bytes from in to out + val buf = ByteArray(1024) + var len: Int + while ((len = `is`.read(buf)) > 0) { + out.write(buf, 0, len) + } + `is`.close() + out.close() + } + + @Throws(IOException::class) + fun copyFile(infile: String, outfile: String) { + val fx = File(infile) + copyFile(fx, outfile) + + // this.writeLine(outfile, this.readFile(infile)); + } + + + /** + * Copy method, using FileChannel#transferTo + * NOTE: will overwrite existing files + * + * @param File source + * @param File target + * @throws IOException + */ + @Throws(IOException::class) + fun copyFile(source: File, target: String) { + var fout = File(target) + fout.mkdirs() + fout.delete() + fout = File(target) + val `in` = FileInputStream(source).channel + val out = FileOutputStream(target).channel + `in`.transferTo(0, `in`.size(), out) + `in`.close() + out.close() + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/LogOutputter.java b/src/main/java/io/starter/toolkit/LogOutputter.kt similarity index 87% rename from src/main/java/io/starter/toolkit/LogOutputter.java rename to src/main/java/io/starter/toolkit/LogOutputter.kt index 719ba1b..7f7fc9c 100644 --- a/src/main/java/io/starter/toolkit/LogOutputter.java +++ b/src/main/java/io/starter/toolkit/LogOutputter.kt @@ -27,10 +27,10 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit -public interface LogOutputter { +interface LogOutputter { - void log(String string); + fun log(string: String) } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/Logger.java b/src/main/java/io/starter/toolkit/Logger.java deleted file mode 100644 index a94c2c8..0000000 --- a/src/main/java/io/starter/toolkit/Logger.java +++ /dev/null @@ -1,509 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -/** - * Logger.java - */ -package io.starter.toolkit; - -import java.io.*; -import java.nio.charset.Charset; -import java.text.SimpleDateFormat; -import java.util.Date; - -/** System-wide Logging facility - * - *
                      - * Logger can be used to output standardized messages to System.out and System.err, as well as - * to pluggable Logger implementations. - *

                      - * To install a custom Logger implementation, instantiate a class that implements Logger, then - * set the system property: "io.starter.toolkit.logger" - *

                      - * For example: - *
                      - *  CustomLog mylogr = new CustomLog();
                      - *  Properties props = System.getProperties();
                      - props.put("io.starter.toolkit.logger", mylogr );
                      - *  
                      - *
                      - * The default Logger settings can be controlled using System properties. - *
                      - * 	props.put("io.starter.toolkit.logger.dateformat", "MMM yyyy mm:ss" );
                      - * 	props.put("io.starter.toolkit.logger.dateformat", "none" );
                      - *
                      - * 
                      - * - * - */ -public class Logger extends PrintStream { - /** @deprecated Just use this. */ - @Deprecated - protected PrintStream ous = this; - - /** Copy of line.separator system property to save lookups. */ - private static final String endl = System - .getProperty("line.separator"); - - private static Logger targetLogger; - private static BufferedWriter targetWriter; - private StringBuffer lineBuffer = new StringBuffer(); - private static boolean autoFlush; - - public Logger(Logger target) { - this(); - this.init(target); - } - - public Logger(Logger target, String charset) - throws UnsupportedEncodingException { - this(charset); - this.init(target); - } - - public Logger(OutputStream target) { - this(target, false); - } - - public Logger(OutputStream target, boolean autoFlush) { - this(new OutputStreamWriter(target), autoFlush); - } - - public Logger(OutputStream target, String charset, boolean autoFlush) - throws UnsupportedEncodingException { - this(new OutputStreamWriter(target, charset), charset, autoFlush); - } - - public Logger(Writer target) { - this(target, false); - } - - public Logger(Writer target, boolean autoFlush) { - this(); - this.init(target, autoFlush); - } - - public Logger(Writer target, String charset, boolean autoFlush) - throws UnsupportedEncodingException { - this(charset); - this.init(target, autoFlush); - } - - private Logger() { - super(new IndirectOutputStream(), true); - ((IndirectOutputStream) out).setSink(new WriterOutputStream(this, - Charset.defaultCharset())); - } - - private Logger(String charset) throws UnsupportedEncodingException { - super(new IndirectOutputStream(), true, charset); - ((IndirectOutputStream) out) - .setSink(new WriterOutputStream(this, charset)); - } - - private void init(Logger target) { - targetLogger = target; - targetWriter = null; - autoFlush = false; // has no meaning for Logger target - } - - private void init(Writer target, boolean autoFlush) { - targetLogger = null; - targetWriter = new BufferedWriter(target); - // autoFlush = autoFlush; - } - - /** Installs this logger as the default logger and replaces the standard - * output and error streams. - */ - public void install() { - setLogger(this); - System.setOut(this); - System.setErr(this); - } - - /* ---------- Logger methods ---------- */ - - public static void log(String message) { - if (null != targetLogger) - Logger.log(message); - else - synchronized (targetWriter) { - try { - targetWriter.write(getLogDate()); - targetWriter.write(" "); - targetWriter.write(message); - targetWriter.newLine(); - if (autoFlush) - targetWriter.flush(); - } catch (IOException ex) { - // we're the logger, so we can't exactly log about it - // the interface doesn't support exceptions so just drop it - } - } - } - - public void log(String message, Exception ex, boolean trace) { - if (null != targetLogger) - targetLogger.log(message, ex, trace); - else - log(formatThrowable(message, ex, trace)); - } - - public void log(String message, Exception ex) { - if (null != targetLogger) - targetLogger.log(message, ex); - else - log(formatThrowable(message, ex, false)); - } - - /* ---------- PrintStream methods ---------- */ - - public void logLine() { - synchronized (lineBuffer) { - // if the line buffer ends with a newline, strip it - int length = lineBuffer.length(); - if (length >= endl.length() && endl.equals(lineBuffer - .substring(length - endl.length(), length))) - lineBuffer.setLength(length - endl.length()); - - // log and reset the line buffer but don't log empty lines - if (lineBuffer.length() > 0) { - log(lineBuffer.toString()); - lineBuffer.setLength(0); - } - } - } - - @Override - public Logger append(char value) { - lineBuffer.append(value); - return this; - } - - @Override - public Logger append(CharSequence value) { - lineBuffer.append(value); - return this; - } - - @Override - public Logger append(CharSequence value, int start, int end) { - lineBuffer.append(value, start, end); - return this; - } - - @Override - public void print(boolean b) { - lineBuffer.append(b); - } - - @Override - public void print(char c) { - lineBuffer.append(c); - } - - @Override - public void print(int i) { - lineBuffer.append(i); - } - - @Override - public void print(long l) { - lineBuffer.append(l); - } - - @Override - public void print(float f) { - lineBuffer.append(f); - } - - @Override - public void print(double d) { - lineBuffer.append(d); - } - - @Override - public void print(char[] s) { - lineBuffer.append(s); - } - - @Override - public void print(String s) { - synchronized (lineBuffer) { - lineBuffer.append(s); - if (s.endsWith(endl)) - this.println(); - } - } - - @Override - public void print(Object obj) { - lineBuffer.append(obj); - } - - @Override - public void println(boolean x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(char x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(int x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(long x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(float x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(double x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(char[] x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(String x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println(Object x) { - lineBuffer.append(x); - this.println(); - } - - @Override - public void println() { - synchronized (lineBuffer) { - // flush the input stream into the line buffer - super.flush(); - - // log the current line - logLine(); - } - } - - /* - * ---------- static convenience methods for logging - * ---------- - */ - - public static final String INFO_STRING = ""; - public static final String WARN_STRING = "WARNING: "; - public static final String ERROR_STRING = "ERROR: "; - - /** Gets the current system logger. - */ - public static Logger getLogger() { - Logger logger; - - try { - logger = (Logger) System.getProperties() - .get("io.starter.toolkit.logger"); - } catch (Exception ex) { - logger = null; - } - - if (null == logger) { - if (System.err instanceof Logger) - logger = (Logger) System.err; - else { - logger = new Logger(System.err, true); - } - setLogger(logger); - } - - return logger; - } - - /** Replaces the system logger. */ - public static void setLogger(Logger logger) { - System.getProperties().put("io.starter.toolkit.logger", logger); - } - - public static String formatThrowable(String message, Throwable ex, boolean trace) { - StringWriter writer = new StringWriter(); - writer.write(message); - - if (trace) { - writer.write(endl); - writer.write(endl); - - PrintWriter printer = new PrintWriter(writer); - ex.printStackTrace(printer); - printer.flush(); - } else { - writer.write(": "); - writer.write(ex.toString()); - } - - return writer.toString(); - } - - /** Logs a fatal error message to the system logger. - */ - public static void logErr(String message, Exception ex) { - getLogger().log(ERROR_STRING + message, ex); - } - - /** Logs a fatal error message to the system logger. - */ - public static void logErr(String message, Throwable ex) { - getLogger(); - Logger.log(formatThrowable(ERROR_STRING + message, ex, false)); - } - - /** Logs a fatal error message to the system logger. - */ - public static void logErr(String message) { - getLogger(); - Logger.log(ERROR_STRING + message); - } - - /** Logs a fatal error message to the system logger. - */ - public static void logErr(String message, Exception ex, boolean trace) { - getLogger().log(ERROR_STRING + message, ex, trace); - } - - /** Logs the string conversion of an object to the system logger. - */ - public static void log(Object object) { - logInfo(object.toString()); - } - - /** Logs a non-fatal warning to the system logger. - */ - public static void logWarn(String message) { - getLogger(); - Logger.log(WARN_STRING + message); - } - - /** Logs the string conversion of an exception to the system logger as a - * fatal error message. - */ - public static void logErr(Exception ex) { - logErr(ex.toString()); - } - - /** Logs an informational message to the system logger. - */ - public static void logInfo(String message) { - getLogger(); - Logger.log(INFO_STRING + message); - } - - /** Attempts to replace the standard output stream with a - * Logger instance that writes to the named file. If the - * operation fails a message will be logged to the system logger and the - * method will return without throwing an exception. - */ - public static void setOut(String filename) { - try { - java.io.File logfile = new java.io.File(filename); - FileOutputStream sysout = new FileOutputStream(logfile); - System.setOut(new Logger(sysout)); - } catch (Exception e) { - Logger.logErr("Setting System Output Stream in Logger failed: ", e); - } - } - - /** Attempts to replace the standard error stream with a - * Logger instance that writes to the named file. If the - * operation fails a message will be logged to the system logger and the - * method will return without throwing an exception. - */ - public static void setErr(String filename) { - try { - java.io.File logfile = new java.io.File(filename); - FileOutputStream sysout = new FileOutputStream(logfile); - System.setErr(new Logger(sysout)); - } catch (Exception e) { - Logger.logErr("Setting System Error Stream in Logger failed: ", e); - } - } - - /** The default time stamp format for {@link #getLogDate()}. */ - public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss:SSSS"; - - private static SimpleDateFormat dateFormat = new SimpleDateFormat( - DATE_FORMAT); - private static String dateSpec = DATE_FORMAT; - - /** Returns the current time in a configurable format. - * If the system property io.starter.toolkit.logger.dateformat - * exists and is a valid date format pattern it will be used. Otherwise the - * {@linkplain #DATE_FORMAT default format pattern} will be used. - */ - public static String getLogDate() { - String spec = System - .getProperty("io.starter.toolkit.logger.dateformat"); - if (null != spec) { - if ("none".equalsIgnoreCase(spec)) - return ""; - - if (!dateSpec.equals(spec)) { - try { - dateFormat.applyPattern(spec); - } catch (IllegalArgumentException e) { - dateFormat.applyPattern(DATE_FORMAT); - } - - dateSpec = spec; - } - } - - return dateFormat.format(new Date()); - } - -} diff --git a/src/main/java/io/starter/toolkit/Logger.kt b/src/main/java/io/starter/toolkit/Logger.kt new file mode 100644 index 0000000..de66b3c --- /dev/null +++ b/src/main/java/io/starter/toolkit/Logger.kt @@ -0,0 +1,479 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +/** + * Logger.java + */ +package io.starter.toolkit + +import java.io.* +import java.nio.charset.Charset +import java.text.SimpleDateFormat +import java.util.Date + +/** System-wide Logging facility + * + *

                      + * Logger can be used to output standardized messages to System.out and System.err, as well as + * to pluggable Logger implementations. + *



                      + * To install a custom Logger implementation, instantiate a class that implements Logger, then + * set the system property: "io.starter.toolkit.logger" + *



                      + * For example: + *
                      + * CustomLog mylogr = new CustomLog();
                      + * Properties props = System.getProperties();
                      + * props.put("io.starter.toolkit.logger", mylogr );
                      +
                      * + *

                      + * The default Logger settings can be controlled using System properties. + *
                      + * props.put("io.starter.toolkit.logger.dateformat", "MMM yyyy mm:ss" );
                      + * props.put("io.starter.toolkit.logger.dateformat", "none" );
                      + *
                      +
                      * + * + * + */ +class Logger : PrintStream { + + @Deprecated("Just use this. ") + protected var ous: PrintStream = this + private val lineBuffer = StringBuffer() + + constructor(target: Logger) : this() { + this.init(target) + } + + @Throws(UnsupportedEncodingException::class) + constructor(target: Logger, charset: String) : this(charset) { + this.init(target) + } + + @JvmOverloads + constructor(target: OutputStream, autoFlush: Boolean = false) : this(OutputStreamWriter(target), autoFlush) { + } + + @Throws(UnsupportedEncodingException::class) + constructor(target: OutputStream, charset: String, autoFlush: Boolean) : this(OutputStreamWriter(target, charset), charset, autoFlush) { + } + + @JvmOverloads + constructor(target: Writer, autoFlush: Boolean = false) : this() { + this.init(target, autoFlush) + } + + @Throws(UnsupportedEncodingException::class) + constructor(target: Writer, charset: String, autoFlush: Boolean) : this(charset) { + this.init(target, autoFlush) + } + + private constructor() : super(IndirectOutputStream(), true) { + (out as IndirectOutputStream).sink = WriterOutputStream(this, + Charset.defaultCharset()) + } + + @Throws(UnsupportedEncodingException::class) + private constructor(charset: String) : super(IndirectOutputStream(), true, charset) { + (out as IndirectOutputStream).sink = WriterOutputStream(this, charset) + } + + private fun init(target: Logger) { + targetLogger = target + targetWriter = null + autoFlush = false // has no meaning for Logger target + } + + private fun init(target: Writer, autoFlush: Boolean) { + targetLogger = null + targetWriter = BufferedWriter(target) + // autoFlush = autoFlush; + } + + /** Installs this logger as the default logger and replaces the standard + * output and error streams. + */ + fun install() { + logger = this + System.setOut(this) + System.setErr(this) + } + + fun log(message: String, ex: Exception, trace: Boolean) { + if (null != targetLogger) + targetLogger!!.log(message, ex, trace) + else + log(formatThrowable(message, ex, trace)) + } + + fun log(message: String, ex: Exception) { + if (null != targetLogger) + targetLogger!!.log(message, ex) + else + log(formatThrowable(message, ex, false)) + } + + /* ---------- PrintStream methods ---------- */ + + fun logLine() { + synchronized(lineBuffer) { + // if the line buffer ends with a newline, strip it + val length = lineBuffer.length + if (length >= endl.length && endl == lineBuffer + .substring(length - endl.length, length)) + lineBuffer.setLength(length - endl.length) + + // log and reset the line buffer but don't log empty lines + if (lineBuffer.length > 0) { + log(lineBuffer.toString()) + lineBuffer.setLength(0) + } + } + } + + override fun append(value: Char): Logger { + lineBuffer.append(value) + return this + } + + override fun append(value: CharSequence?): Logger { + lineBuffer.append(value) + return this + } + + override fun append(value: CharSequence?, start: Int, end: Int): Logger { + lineBuffer.append(value, start, end) + return this + } + + override fun print(b: Boolean) { + lineBuffer.append(b) + } + + override fun print(c: Char) { + lineBuffer.append(c) + } + + override fun print(i: Int) { + lineBuffer.append(i) + } + + override fun print(l: Long) { + lineBuffer.append(l) + } + + override fun print(f: Float) { + lineBuffer.append(f) + } + + override fun print(d: Double) { + lineBuffer.append(d) + } + + override fun print(s: CharArray) { + lineBuffer.append(s) + } + + override fun print(s: String?) { + synchronized(lineBuffer) { + lineBuffer.append(s) + if (s!!.endsWith(endl)) + this.println() + } + } + + override fun print(obj: Any?) { + lineBuffer.append(obj) + } + + override fun println(x: Boolean) { + lineBuffer.append(x) + this.println() + } + + override fun println(x: Char) { + lineBuffer.append(x) + this.println() + } + + override fun println(x: Int) { + lineBuffer.append(x) + this.println() + } + + override fun println(x: Long) { + lineBuffer.append(x) + this.println() + } + + override fun println(x: Float) { + lineBuffer.append(x) + this.println() + } + + override fun println(x: Double) { + lineBuffer.append(x) + this.println() + } + + override fun println(x: CharArray) { + lineBuffer.append(x) + this.println() + } + + override fun println(x: String) { + lineBuffer.append(x) + this.println() + } + + override fun println(x: Any?) { + lineBuffer.append(x) + this.println() + } + + override fun println() { + synchronized(lineBuffer) { + // flush the input stream into the line buffer + super.flush() + + // log the current line + logLine() + } + } + + companion object { + + /** Copy of `line.separator` system property to save lookups. */ + private val endl = System + .getProperty("line.separator") + + private var targetLogger: Logger? = null + private var targetWriter: BufferedWriter? = null + private var autoFlush: Boolean = false + + /* ---------- Logger methods ---------- */ + + fun log(message: String) { + if (null != targetLogger) + Logger.log(message) + else + synchronized(targetWriter) { + try { + targetWriter!!.write(logDate) + targetWriter!!.write(" ") + targetWriter!!.write(message) + targetWriter!!.newLine() + if (autoFlush) + targetWriter!!.flush() + } catch (ex: IOException) { + // we're the logger, so we can't exactly log about it + // the interface doesn't support exceptions so just drop it + } + + } + } + + /* + * ---------- static convenience methods for logging + * ---------- + */ + + val INFO_STRING = "" + val WARN_STRING = "WARNING: " + val ERROR_STRING = "ERROR: " + + /** Gets the current system logger. + */ + /** Replaces the system logger. */ + var logger: Logger? + get() { + var logger: Logger? + + try { + logger = System.getProperties()["io.starter.toolkit.logger"] as Logger + } catch (ex: Exception) { + logger = null + } + + if (null == logger) { + if (System.err is Logger) + logger = System.err as Logger + else { + logger = Logger(System.err, true) + } + logger = logger + } + + return logger + } + set(logger) { + System.getProperties()["io.starter.toolkit.logger"] = logger + } + + fun formatThrowable(message: String, ex: Throwable, trace: Boolean): String { + val writer = StringWriter() + writer.write(message) + + if (trace) { + writer.write(endl) + writer.write(endl) + + val printer = PrintWriter(writer) + ex.printStackTrace(printer) + printer.flush() + } else { + writer.write(": ") + writer.write(ex.toString()) + } + + return writer.toString() + } + + /** Logs a fatal error message to the system logger. + */ + fun logErr(message: String, ex: Exception) { + logger!!.log(ERROR_STRING + message, ex) + } + + /** Logs a fatal error message to the system logger. + */ + fun logErr(message: String, ex: Throwable) { + logger + Logger.log(formatThrowable(ERROR_STRING + message, ex, false)) + } + + /** Logs a fatal error message to the system logger. + */ + fun logErr(message: String) { + logger + Logger.log(ERROR_STRING + message) + } + + /** Logs a fatal error message to the system logger. + */ + fun logErr(message: String, ex: Exception, trace: Boolean) { + logger!!.log(ERROR_STRING + message, ex, trace) + } + + /** Logs the string conversion of an object to the system logger. + */ + fun log(`object`: Any) { + logInfo(`object`.toString()) + } + + /** Logs a non-fatal warning to the system logger. + */ + fun logWarn(message: String) { + logger + Logger.log(WARN_STRING + message) + } + + /** Logs the string conversion of an exception to the system logger as a + * fatal error message. + */ + fun logErr(ex: Exception) { + logErr(ex.toString()) + } + + /** Logs an informational message to the system logger. + */ + fun logInfo(message: String) { + logger + Logger.log(INFO_STRING + message) + } + + /** Attempts to replace the standard output stream with a + * `Logger` instance that writes to the named file. If the + * operation fails a message will be logged to the system logger and the + * method will return without throwing an exception. + */ + fun setOut(filename: String) { + try { + val logfile = java.io.File(filename) + val sysout = FileOutputStream(logfile) + System.setOut(Logger(sysout)) + } catch (e: Exception) { + Logger.logErr("Setting System Output Stream in Logger failed: ", e) + } + + } + + /** Attempts to replace the standard error stream with a + * `Logger` instance that writes to the named file. If the + * operation fails a message will be logged to the system logger and the + * method will return without throwing an exception. + */ + fun setErr(filename: String) { + try { + val logfile = java.io.File(filename) + val sysout = FileOutputStream(logfile) + System.setErr(Logger(sysout)) + } catch (e: Exception) { + Logger.logErr("Setting System Error Stream in Logger failed: ", e) + } + + } + + /** The default time stamp format for [.getLogDate]. */ + val DATE_FORMAT = "yyyy-MM-dd HH:mm:ss:SSSS" + + private val dateFormat = SimpleDateFormat( + DATE_FORMAT) + private var dateSpec = DATE_FORMAT + + /** Returns the current time in a configurable format. + * If the system property `io.starter.toolkit.logger.dateformat` + * exists and is a valid date format pattern it will be used. Otherwise the + * [default format pattern][.DATE_FORMAT] will be used. + */ + val logDate: String + get() { + val spec = System + .getProperty("io.starter.toolkit.logger.dateformat") + if (null != spec) { + if ("none".equals(spec, ignoreCase = true)) + return "" + + if (dateSpec != spec) { + try { + dateFormat.applyPattern(spec) + } catch (e: IllegalArgumentException) { + dateFormat.applyPattern(DATE_FORMAT) + } + + dateSpec = spec + } + } + + return dateFormat.format(Date()) + } + } + +} diff --git a/src/main/java/io/starter/toolkit/MD4Digest.java b/src/main/java/io/starter/toolkit/MD4Digest.java deleted file mode 100644 index 73e500f..0000000 --- a/src/main/java/io/starter/toolkit/MD4Digest.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -/** - * implementation of MD4 as RFC 1320 by R. Rivest, MIT Laboratory for - * Computer Science and RSA Data Security, Inc. - *

                      - * NOTE: This algorithm is only included for backwards compatability - * with legacy applications, it's not secure, don't use it for anything new! - */ - -public class MD4Digest { - - private int H1, H2, H3, H4; // IV's - private int[] X = new int[16]; - private int xOff; - private static final int BYTE_LENGTH = 64; - private byte[] xBuf; - private int xBufOff; - private long byteCount; - - /** - * Standard constructor - */ - public MD4Digest() { - xBuf = new byte[4]; - xBufOff = 0; - reset(); - } - - public byte[] getDigest(byte[] data) { - - this.update(data, 0, data.length); - byte[] digest = new byte[16]; - this.doFinal(digest, 0); - - return digest; - - } - - - protected void processWord( - byte[] in, - int inOff) { - X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) - | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); - - if (xOff == 16) { - processBlock(); - } - } - - protected void processLength( - long bitLength) { - if (xOff > 14) { - processBlock(); - } - - X[14] = (int) (bitLength & 0xffffffff); - X[15] = (int) (bitLength >>> 32); - } - - private void unpackWord( - int word, - byte[] out, - int outOff) { - out[outOff] = (byte) word; - out[outOff + 1] = (byte) (word >>> 8); - out[outOff + 2] = (byte) (word >>> 16); - out[outOff + 3] = (byte) (word >>> 24); - } - - public int doFinal( - byte[] out, - int outOff) { - finish(); - - unpackWord(H1, out, outOff); - unpackWord(H2, out, outOff + 4); - unpackWord(H3, out, outOff + 8); - unpackWord(H4, out, outOff + 12); - - reset(); - - return 16; - } - - /** - * reset the chaining variables to the IV values. - */ - public void reset() { - byteCount = 0; - - xBufOff = 0; - for (int i = 0; i < xBuf.length; i++) { - xBuf[i] = 0; - } - - H1 = 0x67452301; - H2 = 0xefcdab89; - H3 = 0x98badcfe; - H4 = 0x10325476; - - xOff = 0; - - for (int i = 0; i != X.length; i++) { - X[i] = 0; - } - } - - // - // round 1 left rotates - // - private static final int S11 = 3; - private static final int S12 = 7; - private static final int S13 = 11; - private static final int S14 = 19; - - // - // round 2 left rotates - // - private static final int S21 = 3; - private static final int S22 = 5; - private static final int S23 = 9; - private static final int S24 = 13; - - // - // round 3 left rotates - // - private static final int S31 = 3; - private static final int S32 = 9; - private static final int S33 = 11; - private static final int S34 = 15; - - /* - * rotate int x left n bits. - */ - private int rotateLeft( - int x, - int n) { - return (x << n) | (x >>> (32 - n)); - } - - /* - * F, G, H and I are the basic MD4 functions. - */ - private int F( - int u, - int v, - int w) { - return (u & v) | (~u & w); - } - - private int G( - int u, - int v, - int w) { - return (u & v) | (u & w) | (v & w); - } - - private int H( - int u, - int v, - int w) { - return u ^ v ^ w; - } - - protected void processBlock() { - int a = H1; - int b = H2; - int c = H3; - int d = H4; - - // - // Round 1 - F cycle, 16 times. - // - a = rotateLeft(a + F(b, c, d) + X[0], S11); - d = rotateLeft(d + F(a, b, c) + X[1], S12); - c = rotateLeft(c + F(d, a, b) + X[2], S13); - b = rotateLeft(b + F(c, d, a) + X[3], S14); - a = rotateLeft(a + F(b, c, d) + X[4], S11); - d = rotateLeft(d + F(a, b, c) + X[5], S12); - c = rotateLeft(c + F(d, a, b) + X[6], S13); - b = rotateLeft(b + F(c, d, a) + X[7], S14); - a = rotateLeft(a + F(b, c, d) + X[8], S11); - d = rotateLeft(d + F(a, b, c) + X[9], S12); - c = rotateLeft(c + F(d, a, b) + X[10], S13); - b = rotateLeft(b + F(c, d, a) + X[11], S14); - a = rotateLeft(a + F(b, c, d) + X[12], S11); - d = rotateLeft(d + F(a, b, c) + X[13], S12); - c = rotateLeft(c + F(d, a, b) + X[14], S13); - b = rotateLeft(b + F(c, d, a) + X[15], S14); - - // - // Round 2 - G cycle, 16 times. - // - a = rotateLeft(a + G(b, c, d) + X[0] + 0x5a827999, S21); - d = rotateLeft(d + G(a, b, c) + X[4] + 0x5a827999, S22); - c = rotateLeft(c + G(d, a, b) + X[8] + 0x5a827999, S23); - b = rotateLeft(b + G(c, d, a) + X[12] + 0x5a827999, S24); - a = rotateLeft(a + G(b, c, d) + X[1] + 0x5a827999, S21); - d = rotateLeft(d + G(a, b, c) + X[5] + 0x5a827999, S22); - c = rotateLeft(c + G(d, a, b) + X[9] + 0x5a827999, S23); - b = rotateLeft(b + G(c, d, a) + X[13] + 0x5a827999, S24); - a = rotateLeft(a + G(b, c, d) + X[2] + 0x5a827999, S21); - d = rotateLeft(d + G(a, b, c) + X[6] + 0x5a827999, S22); - c = rotateLeft(c + G(d, a, b) + X[10] + 0x5a827999, S23); - b = rotateLeft(b + G(c, d, a) + X[14] + 0x5a827999, S24); - a = rotateLeft(a + G(b, c, d) + X[3] + 0x5a827999, S21); - d = rotateLeft(d + G(a, b, c) + X[7] + 0x5a827999, S22); - c = rotateLeft(c + G(d, a, b) + X[11] + 0x5a827999, S23); - b = rotateLeft(b + G(c, d, a) + X[15] + 0x5a827999, S24); - - // - // Round 3 - H cycle, 16 times. - // - a = rotateLeft(a + H(b, c, d) + X[0] + 0x6ed9eba1, S31); - d = rotateLeft(d + H(a, b, c) + X[8] + 0x6ed9eba1, S32); - c = rotateLeft(c + H(d, a, b) + X[4] + 0x6ed9eba1, S33); - b = rotateLeft(b + H(c, d, a) + X[12] + 0x6ed9eba1, S34); - a = rotateLeft(a + H(b, c, d) + X[2] + 0x6ed9eba1, S31); - d = rotateLeft(d + H(a, b, c) + X[10] + 0x6ed9eba1, S32); - c = rotateLeft(c + H(d, a, b) + X[6] + 0x6ed9eba1, S33); - b = rotateLeft(b + H(c, d, a) + X[14] + 0x6ed9eba1, S34); - a = rotateLeft(a + H(b, c, d) + X[1] + 0x6ed9eba1, S31); - d = rotateLeft(d + H(a, b, c) + X[9] + 0x6ed9eba1, S32); - c = rotateLeft(c + H(d, a, b) + X[5] + 0x6ed9eba1, S33); - b = rotateLeft(b + H(c, d, a) + X[13] + 0x6ed9eba1, S34); - a = rotateLeft(a + H(b, c, d) + X[3] + 0x6ed9eba1, S31); - d = rotateLeft(d + H(a, b, c) + X[11] + 0x6ed9eba1, S32); - c = rotateLeft(c + H(d, a, b) + X[7] + 0x6ed9eba1, S33); - b = rotateLeft(b + H(c, d, a) + X[15] + 0x6ed9eba1, S34); - - H1 += a; - H2 += b; - H3 += c; - H4 += d; - - // - // reset the offset and clean out the word buffer. - // - xOff = 0; - for (int i = 0; i != X.length; i++) { - X[i] = 0; - } - } - - - public void update( - byte in) { - xBuf[xBufOff++] = in; - - if (xBufOff == xBuf.length) { - processWord(xBuf, 0); - xBufOff = 0; - } - - byteCount++; - } - - public void update( - byte[] in, - int inOff, - int len) { - // - // fill the current word - // - while ((xBufOff != 0) && (len > 0)) { - update(in[inOff]); - - inOff++; - len--; - } - - // - // process whole words. - // - while (len > xBuf.length) { - processWord(in, inOff); - - inOff += xBuf.length; - len -= xBuf.length; - byteCount += xBuf.length; - } - - // - // load in the remainder. - // - while (len > 0) { - update(in[inOff]); - - inOff++; - len--; - } - } - - public void finish() { - long bitLength = (byteCount << 3); - - // - // add the pad bytes. - // - update((byte) 128); - - while (xBufOff != 0) { - update((byte) 0); - } - - processLength(bitLength); - - processBlock(); - } - - - public int getByteLength() { - return BYTE_LENGTH; - } - - -} - - - - - - diff --git a/src/main/java/io/starter/toolkit/MD4Digest.kt b/src/main/java/io/starter/toolkit/MD4Digest.kt new file mode 100644 index 0000000..2e22b6f --- /dev/null +++ b/src/main/java/io/starter/toolkit/MD4Digest.kt @@ -0,0 +1,355 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +/** + * implementation of MD4 as RFC 1320 by R. Rivest, MIT Laboratory for + * Computer Science and RSA Data Security, Inc. + * + * + * **NOTE**: This algorithm is only included for backwards compatability + * with legacy applications, it's not secure, don't use it for anything new! + */ + +class MD4Digest { + + private var H1: Int = 0 + private var H2: Int = 0 + private var H3: Int = 0 + private var H4: Int = 0 // IV's + private val X = IntArray(16) + private var xOff: Int = 0 + private val xBuf: ByteArray + private var xBufOff: Int = 0 + private var byteCount: Long = 0 + + + val byteLength: Int + get() = BYTE_LENGTH + + /** + * Standard constructor + */ + init { + xBuf = ByteArray(4) + xBufOff = 0 + reset() + } + + fun getDigest(data: ByteArray): ByteArray { + + this.update(data, 0, data.size) + val digest = ByteArray(16) + this.doFinal(digest, 0) + + return digest + + } + + + protected fun processWord( + `in`: ByteArray, + inOff: Int) { + X[xOff++] = (`in`[inOff] and 0xff or (`in`[inOff + 1] and 0xff shl 8) + or (`in`[inOff + 2] and 0xff shl 16) or (`in`[inOff + 3] and 0xff shl 24)) + + if (xOff == 16) { + processBlock() + } + } + + protected fun processLength( + bitLength: Long) { + if (xOff > 14) { + processBlock() + } + + X[14] = (bitLength and -0x1).toInt() + X[15] = bitLength.ushr(32).toInt() + } + + private fun unpackWord( + word: Int, + out: ByteArray, + outOff: Int) { + out[outOff] = word.toByte() + out[outOff + 1] = word.ushr(8).toByte() + out[outOff + 2] = word.ushr(16).toByte() + out[outOff + 3] = word.ushr(24).toByte() + } + + fun doFinal( + out: ByteArray, + outOff: Int): Int { + finish() + + unpackWord(H1, out, outOff) + unpackWord(H2, out, outOff + 4) + unpackWord(H3, out, outOff + 8) + unpackWord(H4, out, outOff + 12) + + reset() + + return 16 + } + + /** + * reset the chaining variables to the IV values. + */ + fun reset() { + byteCount = 0 + + xBufOff = 0 + for (i in xBuf.indices) { + xBuf[i] = 0 + } + + H1 = 0x67452301 + H2 = -0x10325477 + H3 = -0x67452302 + H4 = 0x10325476 + + xOff = 0 + + for (i in X.indices) { + X[i] = 0 + } + } + + /* + * rotate int x left n bits. + */ + private fun rotateLeft( + x: Int, + n: Int): Int { + return x shl n or x.ushr(32 - n) + } + + /* + * F, G, H and I are the basic MD4 functions. + */ + private fun F( + u: Int, + v: Int, + w: Int): Int { + return u and v or (u.inv() and w) + } + + private fun G( + u: Int, + v: Int, + w: Int): Int { + return u and v or (u and w) or (v and w) + } + + private fun H( + u: Int, + v: Int, + w: Int): Int { + return u xor v xor w + } + + protected fun processBlock() { + var a = H1 + var b = H2 + var c = H3 + var d = H4 + + // + // Round 1 - F cycle, 16 times. + // + a = rotateLeft(a + F(b, c, d) + X[0], S11) + d = rotateLeft(d + F(a, b, c) + X[1], S12) + c = rotateLeft(c + F(d, a, b) + X[2], S13) + b = rotateLeft(b + F(c, d, a) + X[3], S14) + a = rotateLeft(a + F(b, c, d) + X[4], S11) + d = rotateLeft(d + F(a, b, c) + X[5], S12) + c = rotateLeft(c + F(d, a, b) + X[6], S13) + b = rotateLeft(b + F(c, d, a) + X[7], S14) + a = rotateLeft(a + F(b, c, d) + X[8], S11) + d = rotateLeft(d + F(a, b, c) + X[9], S12) + c = rotateLeft(c + F(d, a, b) + X[10], S13) + b = rotateLeft(b + F(c, d, a) + X[11], S14) + a = rotateLeft(a + F(b, c, d) + X[12], S11) + d = rotateLeft(d + F(a, b, c) + X[13], S12) + c = rotateLeft(c + F(d, a, b) + X[14], S13) + b = rotateLeft(b + F(c, d, a) + X[15], S14) + + // + // Round 2 - G cycle, 16 times. + // + a = rotateLeft(a + G(b, c, d) + X[0] + 0x5a827999, S21) + d = rotateLeft(d + G(a, b, c) + X[4] + 0x5a827999, S22) + c = rotateLeft(c + G(d, a, b) + X[8] + 0x5a827999, S23) + b = rotateLeft(b + G(c, d, a) + X[12] + 0x5a827999, S24) + a = rotateLeft(a + G(b, c, d) + X[1] + 0x5a827999, S21) + d = rotateLeft(d + G(a, b, c) + X[5] + 0x5a827999, S22) + c = rotateLeft(c + G(d, a, b) + X[9] + 0x5a827999, S23) + b = rotateLeft(b + G(c, d, a) + X[13] + 0x5a827999, S24) + a = rotateLeft(a + G(b, c, d) + X[2] + 0x5a827999, S21) + d = rotateLeft(d + G(a, b, c) + X[6] + 0x5a827999, S22) + c = rotateLeft(c + G(d, a, b) + X[10] + 0x5a827999, S23) + b = rotateLeft(b + G(c, d, a) + X[14] + 0x5a827999, S24) + a = rotateLeft(a + G(b, c, d) + X[3] + 0x5a827999, S21) + d = rotateLeft(d + G(a, b, c) + X[7] + 0x5a827999, S22) + c = rotateLeft(c + G(d, a, b) + X[11] + 0x5a827999, S23) + b = rotateLeft(b + G(c, d, a) + X[15] + 0x5a827999, S24) + + // + // Round 3 - H cycle, 16 times. + // + a = rotateLeft(a + H(b, c, d) + X[0] + 0x6ed9eba1, S31) + d = rotateLeft(d + H(a, b, c) + X[8] + 0x6ed9eba1, S32) + c = rotateLeft(c + H(d, a, b) + X[4] + 0x6ed9eba1, S33) + b = rotateLeft(b + H(c, d, a) + X[12] + 0x6ed9eba1, S34) + a = rotateLeft(a + H(b, c, d) + X[2] + 0x6ed9eba1, S31) + d = rotateLeft(d + H(a, b, c) + X[10] + 0x6ed9eba1, S32) + c = rotateLeft(c + H(d, a, b) + X[6] + 0x6ed9eba1, S33) + b = rotateLeft(b + H(c, d, a) + X[14] + 0x6ed9eba1, S34) + a = rotateLeft(a + H(b, c, d) + X[1] + 0x6ed9eba1, S31) + d = rotateLeft(d + H(a, b, c) + X[9] + 0x6ed9eba1, S32) + c = rotateLeft(c + H(d, a, b) + X[5] + 0x6ed9eba1, S33) + b = rotateLeft(b + H(c, d, a) + X[13] + 0x6ed9eba1, S34) + a = rotateLeft(a + H(b, c, d) + X[3] + 0x6ed9eba1, S31) + d = rotateLeft(d + H(a, b, c) + X[11] + 0x6ed9eba1, S32) + c = rotateLeft(c + H(d, a, b) + X[7] + 0x6ed9eba1, S33) + b = rotateLeft(b + H(c, d, a) + X[15] + 0x6ed9eba1, S34) + + H1 += a + H2 += b + H3 += c + H4 += d + + // + // reset the offset and clean out the word buffer. + // + xOff = 0 + for (i in X.indices) { + X[i] = 0 + } + } + + + fun update( + `in`: Byte) { + xBuf[xBufOff++] = `in` + + if (xBufOff == xBuf.size) { + processWord(xBuf, 0) + xBufOff = 0 + } + + byteCount++ + } + + fun update( + `in`: ByteArray, + inOff: Int, + len: Int) { + var inOff = inOff + var len = len + // + // fill the current word + // + while (xBufOff != 0 && len > 0) { + update(`in`[inOff]) + + inOff++ + len-- + } + + // + // process whole words. + // + while (len > xBuf.size) { + processWord(`in`, inOff) + + inOff += xBuf.size + len -= xBuf.size + byteCount += xBuf.size.toLong() + } + + // + // load in the remainder. + // + while (len > 0) { + update(`in`[inOff]) + + inOff++ + len-- + } + } + + fun finish() { + val bitLength = byteCount shl 3 + + // + // add the pad bytes. + // + update(128.toByte()) + + while (xBufOff != 0) { + update(0.toByte()) + } + + processLength(bitLength) + + processBlock() + } + + companion object { + private val BYTE_LENGTH = 64 + + // + // round 1 left rotates + // + private val S11 = 3 + private val S12 = 7 + private val S13 = 11 + private val S14 = 19 + + // + // round 2 left rotates + // + private val S21 = 3 + private val S22 = 5 + private val S23 = 9 + private val S24 = 13 + + // + // round 3 left rotates + // + private val S31 = 3 + private val S32 = 9 + private val S33 = 11 + private val S34 = 15 + } + + +} + + + + + + diff --git a/src/main/java/io/starter/toolkit/Money.java b/src/main/java/io/starter/toolkit/Money.kt similarity index 56% rename from src/main/java/io/starter/toolkit/Money.java rename to src/main/java/io/starter/toolkit/Money.kt index 6fc764e..449676c 100644 --- a/src/main/java/io/starter/toolkit/Money.java +++ b/src/main/java/io/starter/toolkit/Money.kt @@ -20,34 +20,38 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit -import java.io.Serializable; -import java.math.BigDecimal; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.text.ParseException; +import java.io.Serializable +import java.math.BigDecimal +import java.text.DecimalFormat +import java.text.NumberFormat +import java.text.ParseException /** - * Class Money + * **Class Money** + * + * * - *

                      * The Money class represents a United States * monetary value, expressed in dollars and cents. Internally, * the * value is represented using Java's BigDecimal class. - *

                      * - *

                      + * + * + * * Methods are provided to perform all the usual arithmetic * manipulations required when dealing with monetary data, * including add, subtract, multiply, and divide. - *

                      * - *

                      - * Rounding - *

                      - *

                      + * + * + * + * **Rounding** + * + * + * * Rounding does not occur during intermediate computations; * maximum precision * (and accuracy) is thus preserved throughout all computations. @@ -59,12 +63,14 @@ * can be specified. The default rounding mode is to discard any * fractional * cent and truncate the monetary value to 2 decimal places. - *

                      * - *

                      - * Currency Format - *

                      - *

                      + * + * + * + * **Currency Format** + * + * + * * A Currency Format (an instance of DecimalFormat) is used to * control formatting of * monetary values for display as well as the parsing of strings @@ -79,157 +85,226 @@ * separates the dollars and cents, and a comma (",") separates * each group * of 3 consecutive digits in the dollar amount. - *

                      - *

                      + * + * + * * Examples: $1,234.56 ($1,234.56) - *

                      * - *

                      - * Immutability - *

                      - *

                      - * Money objects, like String objects, are immutable. + * + * + * + * **Immutability** + * + * + * + * Money objects, like String objects, are **immutable**. * An operation on a Money object (such as add, subtract, etc.) * does not alter the object in any way. * Rather, a new Money object is returned whose state reflects * the result of the operation. * Thus, a statement like - *

                      - *

                      + * + * + * * money1.add(money2); - *

                      - *

                      + * + * + * * has no effect; it does not modify money1 in any way, and the * result is effectively discarded. * If the intent is to modify money1, then you should code - *

                      - *

                      + * + * + * * money1 = money1.add(money2); - *

                      - *

                      + * + * + * * which effectively replaces money1 with the result. - *

                      + * * * @see BigDecimal + * * @see DecimalFormat */ -public class Money // Money Class - implements Cloneable, // Money objects can be cloned +class Money // Money Class + : Cloneable, // Money objects can be cloned Serializable // Money objects are serializable { /** - * serialVersionUID + * The monetary value. */ - private static final long serialVersionUID = 2055249101317798370L; /** - * The monetary value. + * Returns the monetary value as a BigDecimal object. + * + * + * + * + * @return The monetary value + * */ - protected BigDecimal value = null; // The monetary value + // Return the monetary value. + var value: BigDecimal? = null + protected set // The monetary value + // Method Money.getValue() /** * The Rounding Mode. Specifies if and how the monetary value is * to be rounded off to an integral cent. */ - protected int roundingMode = BigDecimal.ROUND_DOWN; // Rounding Mode + protected var roundingMode = BigDecimal.ROUND_DOWN // Rounding Mode /** * The Currency Format, used for formatting and parsing a * monetary value. Refer to the Java API * documentation for the DecimalFormat class for information on * formats. */ - protected DecimalFormat currencyFormat = - (DecimalFormat) NumberFormat.getCurrencyInstance(); - // Currency format + protected var currencyFormat = NumberFormat.getCurrencyInstance() as DecimalFormat + + /** + * Returns an indication of whether or not this monetary value + * is zero (equal to $0.00). + * + * + * + * + * @return **true ** The monetary value is zero + *

                      + * **false** The monetary value is not zero + * + */ + // Return true if value is zero. + val isZero: Boolean + get() = value!!.compareTo(ZERO) == 0 // Method Money.isZero() + + /** + * Returns an indication of whether or not this monetary value + * is negative (less than $0.00). + * + * + * + * + * @return **true ** The monetary value is negative + *

                      + * **false** The monetary value is not negative + * + */ + // Return true if value is less + // than zero. + val isNegative: Boolean + get() = value!!.compareTo(ZERO) < 0 // Method Money.isNegative() + /** - * The special monetary value of zero ($0.00). + * Returns an indication of whether or not this monetary value + * is positive (greater than or equal to $0.00). + * + * + * + * + * @return **true ** The monetary value is positive + *

                      + * **false** The monetary value is not positive + * */ - protected static final BigDecimal ZERO = new BigDecimal("0.00"); + // Return true if value is + // greater than zero. + val isPositive: Boolean + get() = value!!.compareTo(ZERO) >= 0 // Method Money.isPositive() // The value $0.00 /** - * Class InvalidScaleFactorException + * **Class InvalidScaleFactorException** + * + * * - *

                      * The InvalidScaleFactorException is thrown if an invalid scale * factor is specified (valid scale factors are 0, 1, and 2). * This is a non-checked exception and will be detected at * runtime only. - *

                      + * */ - public static class InvalidScaleFactorException - extends RuntimeException // Non-checked exception + class InvalidScaleFactorException : RuntimeException // Non-checked exception { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -8038085965896123803L; /** * Default constructor for a InvalidScaleFactorException object. */ - public InvalidScaleFactorException() { - super(); // Invoke super class constructor. - } + constructor() : super() {}// Invoke super class constructor. /** * Constructor for a InvalidScaleFactorException object. * - *

                      + * + * * * @param info Descriptive information - *

                      + * */ - public InvalidScaleFactorException(String info) // Descriptive info - { - super(info); // Invoke super class constructor. + constructor(info: String) // Descriptive info + : super(info) { + }// Invoke super class constructor. + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = -8038085965896123803L } } // Class InvalidScaleFactorException /** - * Class InvalidRoundingModeException + * **Class InvalidRoundingModeException** + * + * * - *

                      * The InvalidRoundingModeException is thrown if an invalid * Rounding * Mode is specified (all rounding modes except * ROUND_UNNECESSARY are valid). * This is a non-checked exception and will be detected at * runtime only. - *

                      - * - * @see BigDecimal#ROUND_UP - * @see BigDecimal#ROUND_DOWN - * @see BigDecimal#ROUND_CEILING - * @see BigDecimal#ROUND_FLOOR - * @see BigDecimal#ROUND_HALF_UP - * @see BigDecimal#ROUND_HALF_DOWN - * @see BigDecimal#ROUND_HALF_EVEN + * + * + * @see BigDecimal.ROUND_UP + * + * @see BigDecimal.ROUND_DOWN + * + * @see BigDecimal.ROUND_CEILING + * + * @see BigDecimal.ROUND_FLOOR + * + * @see BigDecimal.ROUND_HALF_UP + * + * @see BigDecimal.ROUND_HALF_DOWN + * + * @see BigDecimal.ROUND_HALF_EVEN */ - public static class InvalidRoundingModeException - extends RuntimeException // Non-checked exception + class InvalidRoundingModeException : RuntimeException // Non-checked exception { - /** - * serialVersionUID - */ - private static final long serialVersionUID = 5658836125641516151L; /** * Default constructor for a InvalidRoundingModeException object. */ - public InvalidRoundingModeException() { - super(); // Invoke super class constructor. - } // Default Constructor InvalidRoundingModeException() + constructor() : super() {}// Invoke super class constructor. + // Default Constructor InvalidRoundingModeException() /** * Constructor for a InvalidRoundingModeException object. * - *

                      + * + * * * @param info Descriptive information - *

                      + * */ - public InvalidRoundingModeException(String info) // Descriptive info - { - super(info); // Invoke super class constructor. + constructor(info: String) // Descriptive info + : super(info) { + }// Invoke super class constructor. + + companion object { + /** + * serialVersionUID + */ + private val serialVersionUID = 5658836125641516151L } } // Class InvalidRoundingModeException @@ -237,8 +312,8 @@ public InvalidRoundingModeException(String info) // Descriptive info * Default Constructor for a Money object; creates an object * whose value is $0.00. */ - public Money() { - value = ZERO; // Initialize the monetary value + constructor() { + value = ZERO // Initialize the monetary value // to $0.00. } // Default Constructor Money() @@ -248,21 +323,23 @@ public Money() { * The Currency Format is set to the default format for the * current locale. * - *

                      + * + * * The integral part of the value represents whole dollars, and * the fractional * part of the value represents fractional dollars (cents). As * an example, * the value 19.95 would represent $19.95. - *

                      * - *

                      * - * @param amount The monetary amount, in dollars - * and cents - *

                      + * + * + * + * @param **amount** The monetary amount, in dollars + * and cents + * */ - public Money(double amount) // Monetary Value, dollars and cents + constructor(amount: Double) // Monetary Value, dollars and cents { // In general, a double floating point value cannot represent a // decimal value exactly, and @@ -284,7 +361,7 @@ public Money(double amount) // Monetary Value, dollars and cents // already implemented by the format's parse() method. // Convert the parsed value to a simple string (decimal point only) // and use it to set the monetary value. - value = new BigDecimal(Double.toString(amount)); + value = BigDecimal(java.lang.Double.toString(amount)) } // Constructor Money(double amount) /** @@ -295,15 +372,16 @@ public Money(double amount) // Monetary Value, dollars and cents * to be 00). For example, the integer 25 would represent a * monetary value of $25.00. * - *

                      * - * @param amount The monetary amount, in whole - * dollars (no cents) - *

                      + * + * + * @param **amount** The monetary amount, in whole + * dollars (no cents) + * */ - public Money(long amount) // Monetary amount, whole dollars (no cents) + constructor(amount: Long) // Monetary amount, whole dollars (no cents) { - value = new BigDecimal(Long.toString(amount)); // Set monetary value. + value = BigDecimal(java.lang.Long.toString(amount)) // Set monetary value. } // Constructor Money(long amount) /** @@ -313,36 +391,40 @@ public Money(long amount) // Monetary amount, whole dollars (no cents) * to the right of an * implied decimal point. * - *

                      + * + * * For example, the value 1995 would be interpreted as a * monetary value of * $1995.00, $199.50, and $19.95 for scale factors of 0, 1, or * 2, respectively. - *

                      * - *

                      * - * @param amount The monetary amount, in dollars - * and cents - * @param scale Scale factor (must be 0, 1, or 2) - *

                      * - *

                      + * + * + * @param **amount** The monetary amount, in dollars + * and cents + * @param **scale** Scale factor (must be 0, 1, or 2) + * + * + * + * * @throws InvalidScaleFactorException The scale factor - * specified is not valid (must be 0, 1, or 2) - *

                      + * specified is not valid (must be 0, 1, or 2) + * */ - public Money(long amount, // Montetary amount - int scale) // Scale Factor (0, 1, 2) - throws InvalidScaleFactorException { + // Scale Factor (0, 1, 2) + @Throws(InvalidScaleFactorException::class) + constructor(amount: Long, // Montetary amount + scale: Int) { if (// If the Scale Factor is not - (scale < 0) // 0, 1, or - || (scale > 2) // 2, + scale < 0 // 0, 1, or + || scale > 2 // 2, ) - throw new InvalidScaleFactorException( - "Invalid scale factor: " + scale + " (must be 0, 1, or 2)"); + throw InvalidScaleFactorException( + "Invalid scale factor: $scale (must be 0, 1, or 2)") // Set the monetary value and scale as specified. - value = (new BigDecimal(Long.toString(amount))).movePointLeft(scale); + value = BigDecimal(java.lang.Long.toString(amount)).movePointLeft(scale) } // Constructor Money(long amount, int scale) /** @@ -351,37 +433,42 @@ public Money(long amount, // Montetary amount * format of the string must be consistent with the Currency * Format; otherwise, * a ParseException is recognized. - *

                      + * + * * Refer to the Java API documentation for the DecimalFormat * class for information on * Decimal Formats in general, and Currency Formats in * particular. * - *

                      * - * @param string A String representing a monetary - * value - *

                      * - *

                      + * + * @param **string** A String representing a monetary + * value + * + * + * + * * @throws ParseException The string is inconsistent with - * the Currency Format - *

                      + * the Currency Format + * * @see DecimalFormat - * @see #setCurrencyFormat + * + * @see .setCurrencyFormat */ - public Money(String string) // String representing a Monetary Value - throws ParseException // If string is inconsistent with + // String representing a Monetary Value + @Throws(ParseException::class) // If string is inconsistent with + constructor(string: String) // Currency Format { // We make use of the format's parse() method, which parses the string // according to the format and returns either a Double or a Long // object representing the value. - Number number; + val number: Number // Attempt to parse the string as a monetary value. May throw // ParseException if the string is not consistent with the Currency // Format. - number = currencyFormat.parse(string); + number = currencyFormat.parse(string) // In general, a double floating point value cannot represent a // decimal value exactly, and // therefore is only a very close approximation of the actual decimal @@ -402,258 +489,283 @@ public Money(String string) // String representing a Monetary Value // already implemented by the format's parse() method. // Convert the parsed value to a simple string (decimal point only) // and use it to set the monetary value. - value = new BigDecimal(number.toString()); + value = BigDecimal(number.toString()) } // Constructor Money(String) /** * Constructs a Money object from a BigDecimal object. * - *

                      * - * @param amount A BigDecimal value representing a - * monetary amount, in dollars and - * cents - *

                      + * + * + * @param **amount** A BigDecimal value representing a + * monetary amount, in dollars and + * cents + * */ - public Money(BigDecimal amount) // A BigDecimal object representing a + constructor(amount: BigDecimal) // A BigDecimal object representing a // monetary amount { - value = new BigDecimal(amount.toString()); // Set the monetary value. + value = BigDecimal(amount.toString()) // Set the monetary value. } // Constructor Money(BigDecimal amount) /** * Copy Constructor; constructs a Money object from another * Money object. * - *

                      * - * @param amount A Money object - *

                      + * + * + * @param **amount** A Money object + * */ - public Money(Money amount) // Money Object + constructor(amount: Money) // Money Object { - roundingMode = amount.roundingMode; // Copy the Rounding Mode. + roundingMode = amount.roundingMode // Copy the Rounding Mode. // Clone the Currency Format. - currencyFormat = (DecimalFormat) amount.currencyFormat.clone(); - value = amount.value; // Copy the Monetary Value. + currencyFormat = amount.currencyFormat.clone() as DecimalFormat + value = amount.value // Copy the Monetary Value. } // Copy Constructor Money(Money money) /** * Adds a specified monetary value to this monetary value. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      * - * @param money The monetary value to be added - *

                      * - *

                      + * + * + * @param **money** The monetary value to be added + * + * + * + * * @return A new Money object representing the * result - *

                      + * */ - public Money add(Money money) // The Monetary Value to be added - { - Money result = new Money(this); // Create a new Money object for + fun add(money: Money) // The Monetary Value to be added + : Money { + val result = Money(this) // Create a new Money object for // the result. - result.value = value.add(money.value); // Add the two monetary values. - return result; // Return the result. + result.value = value!!.add(money.value!!) // Add the two monetary values. + return result // Return the result. } // Method Money.add() /** * Subtracts a specified monetary value from this monetary value. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      * - * @param money The monetary value to be subtracted - *

                      * - *

                      + * + * + * @param **money** The monetary value to be subtracted + * + * + * + * * @return A new Money object representing the * result - *

                      + * */ - public Money subtract(Money money) // The monetary value to be + fun subtract(money: Money) // The monetary value to be + : Money // subtracted { - Money result = new Money(this); // Create a new Money object for + val result = Money(this) // Create a new Money object for // the result. - result.value = value.subtract(money.value); // Subtract the specified + result.value = value!!.subtract(money.value!!) // Subtract the specified // monetary value. - return result; // Return the result. + return result // Return the result. } // Method Money.subtract() /** * Multiplies this monetary value by a specified value. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      * - * @param mult The multiplier value - *

                      * - *

                      + * + * + * @param **mult** The multiplier value + * + * + * + * * @return A new Money object representing the * result - *

                      + * */ - public Money multiply(double mult) // The multiplier value - { - Money result = new Money(this); // Create a new Money object for + fun multiply(mult: Double) // The multiplier value + : Money { + val result = Money(this) // Create a new Money object for // the result. // Multiply by the specified value. - result.value = value.multiply(new BigDecimal(mult)); - return result; // Return the result. + result.value = value!!.multiply(BigDecimal(mult)) + return result // Return the result. } // Method Money.multiply() /** * Multiplies this monetary value by a specified value. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      * - * @param mult The multiplier value - *

                      * - *

                      + * + * + * @param **mult** The multiplier value + * + * + * + * * @return A new Money object representing the * result - *

                      + * */ - public Money multiply(long mult) // The multiplier value - { - Money result = new Money(this); // Create a new Money object for + fun multiply(mult: Long) // The multiplier value + : Money { + val result = Money(this) // Create a new Money object for // the result. // Multiply by the specified value. - result.value = value.multiply(new BigDecimal(Long.toString(mult))); - return result; // Return the result. + result.value = value!!.multiply(BigDecimal(java.lang.Long.toString(mult))) + return result // Return the result. } // Method Money.multiply() /** * Divides this monetary value by a specified value. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      * - * @param div The divisor value - *

                      * - *

                      + * + * + * @param **div** The divisor value + * + * + * + * * @return A new Money object representing the * result - *

                      + * */ - public Money divide(double div) // The divisor value - { - Money result = new Money(this); // Create a new Money object for + fun divide(div: Double) // The divisor value + : Money { + val result = Money(this) // Create a new Money object for // the result. // Divide the monetary value by the specified value and round if // necessary. - result.value = - value.divide(new BigDecimal(div), BigDecimal.ROUND_HALF_UP); - return result; // Return the result. + result.value = value!!.divide(BigDecimal(div), BigDecimal.ROUND_HALF_UP) + return result // Return the result. } // Method Money.divide() /** * Divides this monetary value by a specified value. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      * - * @param div The divisor value - *

                      * - *

                      + * + * + * @param **div** The divisor value + * + * + * + * * @return A new Money object representing the * result - *

                      + * */ - public Money divide(long div) // The divisor value - { - Money result = new Money(this); // Create a new Money object for + fun divide(div: Long) // The divisor value + : Money { + val result = Money(this) // Create a new Money object for // the result. // Divide the monetary value by the specified value and round if // necessary. - result.value = - value.divide( - new BigDecimal(Long.toString(div)), - BigDecimal.ROUND_HALF_UP); - return result; // Return the result. + result.value = value!!.divide( + BigDecimal(java.lang.Long.toString(div)), + BigDecimal.ROUND_HALF_UP) + return result // Return the result. } // Method Money.divide() /** * Negates this monetary value. - *

                      + * + * * Positive values become negative, and negative values become * positive. The effect is * the same as if the monetary value were multiplied by -1. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      + * + * + * * * @return A new Money object representing the * result - *

                      + * */ - public Money negate() { - Money result = new Money(this); // Create a new Money object for + fun negate(): Money { + val result = Money(this) // Create a new Money object for // the result. - result.value = value.negate(); // Negate the monetary value. - return result; // Return the result. + result.value = value!!.negate() // Negate the monetary value. + return result // Return the result. } // Method Money.negate() /** * Returns the absolute monetary value. - *

                      + * + * * A positive value is returned, irrespective of whether the * monetary * value is positive or negative. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      + * + * + * * * @return A new Money object representing the * result - *

                      + * */ - public Money abs() { - Money result = new Money(this); // Create a new Money object for + fun abs(): Money { + val result = Money(this) // Create a new Money object for // the result. - result.value = value.abs(); // Get the abolute monetary value. - return result; // Return the result. + result.value = value!!.abs() // Get the abolute monetary value. + return result // Return the result. } // Method Money.abs() /** @@ -666,88 +778,90 @@ public Money abs() { * rounded, if necessary, according to the specified Rounding * Mode. * - *

                      + * + * * * @return The monetary value - *

                      + * */ - public long toLong() { + fun toLong(): Long { // Round off the monetary value to 2 decimal places. - BigDecimal result = value.setScale(2, roundingMode); - result = result.movePointRight(2); // Move decimal point 2 places to + var result = value!!.setScale(2, roundingMode) + result = result.movePointRight(2) // Move decimal point 2 places to // the right to preserve cents. - return result.longValue(); // Return the result. + return result.toLong() // Return the result. } // Method Money.toLong() /** * Returns the monetary value as a double-precision, * floating-point value. * - *

                      + * + * * Note: Exercise care when converting monetary values to * floating point * values, because floating-point arithmetic is not * well-suited for * use with monetary data. - *

                      * - *

                      + * + * + * * * @return The monetary value - *

                      + * */ - public double toDouble() { - return value.doubleValue(); // Return the monetary value as a + fun toDouble(): Double { + return value!!.toDouble() // Return the monetary value as a // double floating point value. } // Method Money.toDouble() /** - * Returns the monetary value as a BigDecimal object. + * Returns the Rounding Mode. * - *

                      * - * @return The monetary value - *

                      - */ - public BigDecimal getValue() { - return value; // Return the monetary value. - } // Method Money.getValue() - - /** - * Returns the Rounding Mode. - *

                      * Refer to Java's BigDecimal object for a description of the * possible * rounding modes. * - *

                      + * + * * * @return The Rounding Mode - *

                      - * @see BigDecimal#ROUND_UP - * @see BigDecimal#ROUND_DOWN - * @see BigDecimal#ROUND_CEILING - * @see BigDecimal#ROUND_FLOOR - * @see BigDecimal#ROUND_HALF_UP - * @see BigDecimal#ROUND_HALF_DOWN - * @see BigDecimal#ROUND_HALF_EVEN + * + * @see BigDecimal.ROUND_UP + * + * @see BigDecimal.ROUND_DOWN + * + * @see BigDecimal.ROUND_CEILING + * + * @see BigDecimal.ROUND_FLOOR + * + * @see BigDecimal.ROUND_HALF_UP + * + * @see BigDecimal.ROUND_HALF_DOWN + * + * @see BigDecimal.ROUND_HALF_EVEN */ - public int getRoundingMode() { - return roundingMode; // Return the Rounding Mode. + fun getRoundingMode(): Int { + return roundingMode // Return the Rounding Mode. } // Method Money.getRoundingMode() /** * Sets the Rounding Mode. - *

                      + * + * * Refer to the Java API documentation for the BigDecimal class * for a description of * the possible Rounding Modes. - *

                      + * + * * The default Rounding Mode is * BigDecimal.ROUND_DOWN, which effectively discards any * fractional cent amount * and truncates the monetary value to 2 decimal places. - *

                      + * + * * A Rounding Mode of BigDecimal.ROUND_UNNECESSARY is not valid * for use with * monetary data since certain operations result in a loss of @@ -755,73 +869,89 @@ public int getRoundingMode() { * therefore require that a rounding mode be explicitly * specified. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      * - * @param int The Rounding Mode - *

                      * - *

                      + * + * + * @param **int** The Rounding Mode + * + * + * + * * @return A new Money object representing the * result - *

                      * - *

                      + * + * + * * @throws InvalidRoundingModeException The Rounding Mode - * specified is not valid for monetary data - *

                      - * @see BigDecimal#ROUND_UP - * @see BigDecimal#ROUND_DOWN - * @see BigDecimal#ROUND_CEILING - * @see BigDecimal#ROUND_FLOOR - * @see BigDecimal#ROUND_HALF_UP - * @see BigDecimal#ROUND_HALF_DOWN - * @see BigDecimal#ROUND_HALF_EVEN + * specified is not valid for monetary data + * + * @see BigDecimal.ROUND_UP + * + * @see BigDecimal.ROUND_DOWN + * + * @see BigDecimal.ROUND_CEILING + * + * @see BigDecimal.ROUND_FLOOR + * + * @see BigDecimal.ROUND_HALF_UP + * + * @see BigDecimal.ROUND_HALF_DOWN + * + * @see BigDecimal.ROUND_HALF_EVEN */ - public Money setRoundingMode(int mode) // Rounding Mode - throws InvalidRoundingModeException { - if (mode == BigDecimal.ROUND_UNNECESSARY) // If Rounding Mode is not - // valid, - throw new InvalidRoundingModeException( - "Rounding mode not valid for monetary data: " + mode); - Money result = new Money(this); // Create a new Money object for + // Rounding Mode + @Throws(InvalidRoundingModeException::class) + fun setRoundingMode(mode: Int): Money { + if (mode == BigDecimal.ROUND_UNNECESSARY) + // If Rounding Mode is not + // valid, + throw InvalidRoundingModeException( + "Rounding mode not valid for monetary data: $mode") + val result = Money(this) // Create a new Money object for // the result. - result.roundingMode = mode; // Set the Rounding Mode. - return result; // Return the result. + result.roundingMode = mode // Set the Rounding Mode. + return result // Return the result. } // Method Money.setRoundingMode(int mode) /** * Returns the Currency Format, used to format and parse * monetary values. - *

                      + * + * * Refer to the Java API documentation for the DecimalFormat * class for information on * Decimal Formats in general, and Currency Formats in * particular. * - *

                      + * + * * * @return The Currency Format - *

                      + * * @see DecimalFormat */ - public DecimalFormat getCurrencyFormat() { - return currencyFormat; // Return the Currency Format. + fun getCurrencyFormat(): DecimalFormat { + return currencyFormat // Return the Currency Format. } // Method Money.getCurrencyFormat() /** * Sets the Currency Format, used for formatting and parsing * monetary values. - *

                      + * + * * Refer to the Java API documentation for the DecimalFormat * class for information on * Decimal Formats in general, and Currency Formats in particular. * - *

                      + * + * * By default, the Currency Format for the current locale is * used. For the * United States, the default Currency Symbol is the Dollar Sign @@ -831,59 +961,66 @@ public DecimalFormat getCurrencyFormat() { * separates the dollars and cents, and a comma (",") separates * each group * of 3 consecutive digits in the dollar amount. - *

                      * - *

                      + * + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      * - * @param format The Currency Format - *

                      * - *

                      + * + * + * @param **format** The Currency Format + * + * + * + * * @return A new Money object representing the * result - *

                      + * * @see DecimalFormat */ - public Money setCurrencyFormat(DecimalFormat format) // Currency Format - { - Money result = new Money(this); // Create a new Money object for + fun setCurrencyFormat(format: DecimalFormat) // Currency Format + : Money { + val result = Money(this) // Create a new Money object for // the result. - result.currencyFormat = format; // Set the Currency Format. - return result; // Return the result. + result.currencyFormat = format // Set the Currency Format. + return result // Return the result. } // Method Money.setCurrencyFormat() /** * Returns a formatted string representation of the monetary * value. The format * of the string is determined by the Currency Format. - *

                      + * + * * Refer to the Java API documentation for the DecimalFormat * class for information on * Decimal Formats in general, and Currency Formats in * particular. * - *

                      + * + * * By default, the Currency Format for the current locale is * used. - *

                      * - *

                      + * + * + * * * @return A string representation of the * monetary value - *

                      + * * @see DecimalFormat - * @see #setCurrencyFormat + * + * @see .setCurrencyFormat */ - public String toString() { + override fun toString(): String { // Round off the monetary value to 2 decimal places. - BigDecimal result = value.setScale(2, roundingMode); - return currencyFormat.format(result); // Format the result according + val result = value!!.setScale(2, roundingMode) + return currencyFormat.format(result) // Format the result according // to the Currency Format. } @@ -894,50 +1031,57 @@ public String toString() { * must be consistent * with the Currency Format; otherwise, a ParseException is * recognized. - *

                      + * + * * Refer to the Java API documentation for the DecimalFormat * class for information on * Decimal Formats in general, and Currency Formats in * particular. * - *

                      + * + * * The value of this object is not affected in any way. A new * object is returned reflecting the result of the operation. - *

                      * - *

                      * - * @param string A String representing a monetary - * value - *

                      * - *

                      + * + * + * @param **string** A String representing a monetary + * value + * + * + * + * * @return A new Money object representating * the parsed monetary value - *

                      * - *

                      + * + * + * * @throws ParseException The string is inconsistent with - * the Currency Format - *

                      + * the Currency Format + * * @see DecimalFormat - * @see #setCurrencyFormat + * + * @see .setCurrencyFormat */ - public Money parse(String string) // String representing a Monetary + // String representing a Monetary // Value - throws ParseException // If string is inconsistent with + @Throws(ParseException::class) // If string is inconsistent with + fun parse(string: String): Money // Currency Format { - Money result = new Money(this); // Create a Money object for the + val result = Money(this) // Create a Money object for the // result. // We make use of the format's parse() method, which parses the string // according to the format and returns either a Double or a Long // object representing the value. - Number number; + val number: Number // Attempt to parse the string as a monetary value. May throw a // ParseException if the string is not consistent with the Currency // Format. - number = currencyFormat.parse(string); + number = currencyFormat.parse(string) // In general, a double floating point value cannot represent a // decimal value exactly, and // therefore is only a very close approximation of the actual decimal @@ -958,77 +1102,32 @@ public Money parse(String string) // String representing a Monetary // already implemented by the format's parse() method. // Convert the parsed value to a simple string (decimal point only) // and use it to set the monetary value. - result.value = new BigDecimal(number.toString()); - return result; // Return the new Money object. + result.value = BigDecimal(number.toString()) + return result // Return the new Money object. } // Method Money.parse() /** * Returns an indication of whether or not this monetary value - * is zero (equal to $0.00). + * is equal to + * another monetary value. * - *

                      * - * @return true The monetary value is zero - *
                      - * false The monetary value is not zero - *

                      - */ - public boolean isZero() { - return (value.compareTo(ZERO) == 0); // Return true if value is zero. - } // Method Money.isZero() - - /** - * Returns an indication of whether or not this monetary value - * is negative (less than $0.00). * - *

                      * - * @return true The monetary value is negative - *
                      - * false The monetary value is not negative - *

                      - */ - public boolean isNegative() { - return (value.compareTo(ZERO) < 0); // Return true if value is less - // than zero. - } // Method Money.isNegative() - - /** - * Returns an indication of whether or not this monetary value - * is positive (greater than or equal to $0.00). + * @param **other** A monetary value with which this + * monetary value is to be compared * - *

                      * - * @return true The monetary value is positive - *
                      - * false The monetary value is not positive - *

                      - */ - public boolean isPositive() { - return (value.compareTo(ZERO) >= 0); // Return true if value is - // greater than zero. - } // Method Money.isPositive() - - /** - * Returns an indication of whether or not this monetary value - * is equal to - * another monetary value. * - *

                      * - * @param other A monetary value with which this - * monetary value is to be compared - *

                      + * @return **true ** This monetary values are equal + *

                      + * **false** This monetary values are not equal * - *

                      - * @return true This monetary values are equal - *
                      - * false This monetary values are not equal - *

                      */ - public boolean isEqual(Money other) // Monetary value for comparison - { - return (value.compareTo(other.value) == 0); // Return true if equal to + fun isEqual(other: Money) // Monetary value for comparison + : Boolean { + return value!!.compareTo(other.value!!) == 0 // Return true if equal to // the other amount. } // Method Money.isEqual() @@ -1037,24 +1136,27 @@ public boolean isEqual(Money other) // Monetary value for comparison * is less than * another monetary value. * - *

                      * - * @param other A monetary value with which this - * monetary value is to be compared - *

                      * - *

                      - * @return true This monetary value is less than + * + * @param **other** A monetary value with which this + * monetary value is to be compared + * + * + * + * + * @return **true ** This monetary value is less than * the specified monetary value - *
                      - * false This monetary value is not less + *

                      + * **false** This monetary value is not less * than the specified monetary value - *

                      + * */ - public boolean isLessThan(Money other) // Monetary value for + fun isLessThan(other: Money) // Monetary value for + : Boolean // comparison { - return (value.compareTo(other.value) < 0); // Return true if less than + return value!!.compareTo(other.value!!) < 0 // Return true if less than // the other amount. } // Method Money.isLessThan() @@ -1063,27 +1165,30 @@ public boolean isLessThan(Money other) // Monetary value for * is less than * or equal to another monetary value. * - *

                      * - * @param other A monetary value with which this - * monetary value is to be compared - *

                      * - *

                      - * @return true This monetary value is less than + * + * @param **other** A monetary value with which this + * monetary value is to be compared + * + * + * + * + * @return **true ** This monetary value is less than * or equal to the specified monetary * value - *
                      - * false This monetary value is not less + *

                      + * **false** This monetary value is not less * than or equal to the specified * monetary value - *

                      + * */ - public boolean isLessThanOrEqual(Money other) // Monetary value for + fun isLessThanOrEqual(other: Money) // Monetary value for + : Boolean // comparison { // Return true if less than or equal to the other amount. - return (value.compareTo(other.value) <= 0); + return value!!.compareTo(other.value!!) <= 0 } // Method Money.isLessThanOrEqual() /** @@ -1091,25 +1196,28 @@ public boolean isLessThanOrEqual(Money other) // Monetary value for * is greater than * another monetary value. * - *

                      * - * @param other A monetary value with which this - * monetary value is to be compared - *

                      * - *

                      - * @return true This monetary value is greater + * + * @param **other** A monetary value with which this + * monetary value is to be compared + * + * + * + * + * @return **true ** This monetary value is greater * than the specified monetary value - *
                      - * false This monetary value is not greater + *

                      + * **false** This monetary value is not greater * than the specified monetary value - *

                      + * */ - public boolean isGreaterThan(Money other) // Monetary value for + fun isGreaterThan(other: Money) // Monetary value for + : Boolean // comparison { // Return true if greater than the other amount. - return (value.compareTo(other.value) > 0); + return value!!.compareTo(other.value!!) > 0 } // Method Money.isGreaterThan() /** @@ -1117,27 +1225,30 @@ public boolean isGreaterThan(Money other) // Monetary value for * is greater than * or equal to another monetary value. * - *

                      * - * @param other A monetary value with which this - * monetary value is to be compared - *

                      * - *

                      - * @return true This monetary value is greater + * + * @param **other** A monetary value with which this + * monetary value is to be compared + * + * + * + * + * @return **true ** This monetary value is greater * than or equal to the specified * monetary value - *
                      - * false This monetary value is not greater + *

                      + * **false** This monetary value is not greater * than or equal to the specified * monetary value - *

                      + * */ - public boolean isGreaterThanOrEqual(Money other) // Monetary value for + fun isGreaterThanOrEqual(other: Money) // Monetary value for + : Boolean // comparison { // Return true if greater than or equal to the other amount. - return (value.compareTo(other.value) >= 0); + return value!!.compareTo(other.value!!) >= 0 } // Method Money.isGreaterThanOrEqual() /** @@ -1147,31 +1258,32 @@ public boolean isGreaterThanOrEqual(Money other) // Monetary value for * object, and has * the same monetary value as this object. * - *

                      * - * @param object Some object - *

                      * - *

                      - * @return true The objects are equal - *
                      - * false The objects are not equal - *

                      + * + * @param **object** Some object + * + * + * + * + * @return **true ** The objects are equal + *

                      + * **false** The objects are not equal + * */ - public boolean equals(Object object) // Object to compare - { - if (object == this) // If the object is this object, - return true; // the objects are equal by + override fun equals(`object`: Any?) // Object to compare + : Boolean { + if (`object` === this) + // If the object is this object, + return true // the objects are equal by // definition. - if (object == null) // If the object is null, - return false; // the objects are not equal by + if (`object` == null) + // If the object is null, + return false // the objects are not equal by // definition. - if (!(object instanceof Money)) // If the object is not an instance - // of Money, - return false; // the objects are not equal by + return if (`object` !is Money) false else value!!.compareTo(`object`.value!!) == 0 // the objects are not equal by // definition. // Return true if monetary values are the same. - return (value.compareTo(((Money) object).value) == 0); } // Method Money.equals() /** @@ -1179,26 +1291,39 @@ public boolean equals(Object object) // Object to compare * to that for the BigDecimal * object that represents the monetary value. * - *

                      + * + * * * @return The hashcode - *

                      + * */ - public int hashCode() { - return value.hashCode(); // Return the hashcode for the + override fun hashCode(): Int { + return value!!.hashCode() // Return the hashcode for the } /** * Clones a Money object. The new object is an exact copy of * this object, and inherits the object's monetary value and Currency Format. * - *

                      * - * @return Money The cloned object - *

                      + * + * + * @return **Money** The cloned object + * */ - public Object clone() { - Money result = new Money(this); // Create a copy of this Money object. - return result; // Return the cloned object. + public override fun clone(): Any { + return Money(this) // Return the cloned object. + } + + companion object { + /** + * serialVersionUID + */ + private const val serialVersionUID = 2055249101317798370L + // Currency format + /** + * The special monetary value of zero ($0.00). + */ + protected val ZERO = BigDecimal("0.00") } } // Class Money diff --git a/src/main/java/io/starter/toolkit/OutFile.java b/src/main/java/io/starter/toolkit/OutFile.java deleted file mode 100644 index b8cdaec..0000000 --- a/src/main/java/io/starter/toolkit/OutFile.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -public class OutFile extends java.io.DataOutputStream { - public OutFile(String filename) - throws IOException { - super(new BufferedOutputStream(new FileOutputStream(filename))); - } - - public OutFile(File file) - throws IOException { - this(file.getPath()); - } -} - diff --git a/src/main/java/io/starter/toolkit/OutFile.kt b/src/main/java/io/starter/toolkit/OutFile.kt new file mode 100644 index 0000000..ba22f5b --- /dev/null +++ b/src/main/java/io/starter/toolkit/OutFile.kt @@ -0,0 +1,37 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + +class OutFile @Throws(IOException::class) +constructor(filename: String) : java.io.DataOutputStream(BufferedOutputStream(FileOutputStream(filename))) { + + @Throws(IOException::class) + constructor(file: File) : this(file.path) { + } +} + diff --git a/src/main/java/io/starter/toolkit/ProgressListener.java b/src/main/java/io/starter/toolkit/ProgressListener.java deleted file mode 100644 index ca83783..0000000 --- a/src/main/java/io/starter/toolkit/ProgressListener.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - - -public interface ProgressListener { - void setMaxProgress(int i); - - boolean updateProgress(); - - void addTarget(ProgressNotifier t); - - void removeTarget(ProgressNotifier t); - - void clear(); - - void setDisplayText(String t); - - void setValue(int progress); -} \ No newline at end of file diff --git a/src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.java b/src/main/java/io/starter/toolkit/ProgressListener.kt similarity index 72% rename from src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.java rename to src/main/java/io/starter/toolkit/ProgressListener.kt index 148c7da..d5aa587 100644 --- a/src/main/java/io/starter/OpenXLS/DocumentObjectNotFoundException.java +++ b/src/main/java/io/starter/toolkit/ProgressListener.kt @@ -20,15 +20,21 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.OpenXLS; +package io.starter.toolkit -public class DocumentObjectNotFoundException extends Exception { +interface ProgressListener { + fun setMaxProgress(i: Int) - private static final long serialVersionUID = 6605511680058750453L; + fun updateProgress(): Boolean - public DocumentObjectNotFoundException(String string) { - super(string); - } + fun addTarget(t: ProgressNotifier) -} + fun removeTarget(t: ProgressNotifier) + + fun clear() + + fun setDisplayText(t: String) + + fun setValue(progress: Int) +} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/ProgressNotifier.java b/src/main/java/io/starter/toolkit/ProgressNotifier.java deleted file mode 100644 index b16f54c..0000000 --- a/src/main/java/io/starter/toolkit/ProgressNotifier.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - - -public interface ProgressNotifier { - void register(ProgressListener j); - - void fireProgressChanged(); - - int getProgress(); - - void setProgress(int progress); - - String getProgressText(); - - void setProgressText(String ptext); - - boolean iscompleted(); - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/XLS/UsrExcl.java b/src/main/java/io/starter/toolkit/ProgressNotifier.kt similarity index 78% rename from src/main/java/io/starter/formats/XLS/UsrExcl.java rename to src/main/java/io/starter/toolkit/ProgressNotifier.kt index 8fecf15..b368043 100644 --- a/src/main/java/io/starter/formats/XLS/UsrExcl.java +++ b/src/main/java/io/starter/toolkit/ProgressNotifier.kt @@ -20,17 +20,18 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.XLS; +package io.starter.toolkit -/** - * - */ -public class UsrExcl extends UnencryptedXLSRecord { - /** - * serialVersionUID - */ - private static final long serialVersionUID = -9123474758922477791L; +interface ProgressNotifier { + + var progress: Int + + var progressText: String + fun register(j: ProgressListener) + + fun fireProgressChanged() + fun iscompleted(): Boolean -} +} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/Recyclable.java b/src/main/java/io/starter/toolkit/Recyclable.kt similarity index 86% rename from src/main/java/io/starter/toolkit/Recyclable.java rename to src/main/java/io/starter/toolkit/Recyclable.kt index 6a38f68..5b6a3c2 100644 --- a/src/main/java/io/starter/toolkit/Recyclable.java +++ b/src/main/java/io/starter/toolkit/Recyclable.kt @@ -20,25 +20,26 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit + +interface Recyclable { + + /** + * returns the number of times + * this object has been reused + */ + val numTimesReused: Int -public interface Recyclable { /** * remove all data and prepare for * re-use */ - Recyclable recycle(); + fun recycle(): Recyclable /** * returns whether this object * is in use */ - boolean inUse(); - - /** - * returns the number of times - * this object has been reused - */ - int getNumTimesReused(); + fun inUse(): Boolean } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/RecycleBin.java b/src/main/java/io/starter/toolkit/RecycleBin.kt similarity index 76% rename from src/main/java/io/starter/toolkit/RecycleBin.java rename to src/main/java/io/starter/toolkit/RecycleBin.kt index fbc85c6..be71dfe 100644 --- a/src/main/java/io/starter/toolkit/RecycleBin.java +++ b/src/main/java/io/starter/toolkit/RecycleBin.kt @@ -20,46 +20,46 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit -import java.util.List; - -public interface RecycleBin { +interface RecycleBin { /** - * add a Recyclable to the - * bin + * get an unused Recyclable item from the bin + * + * @throws RecycleBinFullException */ - void addItem(Recyclable r) throws RecycleBinFullException; + val item: Recyclable /** - * add a Recyclable to the - * bin + * get all of the items in this bin */ - void addItem(Object key, Recyclable r) throws RecycleBinFullException; + val all: List<*> /** - * get an unused Recyclable item from the bin - * - * @throws RecycleBinFullException + * add a Recyclable to the + * bin */ - Recyclable getItem() throws RecycleBinFullException; + @Throws(RecycleBinFullException::class) + fun addItem(r: Recyclable) /** - * get all of the items in this bin + * add a Recyclable to the + * bin */ - List getAll(); + @Throws(RecycleBinFullException::class) + fun addItem(key: Any, r: Recyclable) /** * empty the current contents of the bin */ - void empty(); + fun empty() /** * set the maximum number of items for this * bin */ - void setMaxItems(int i); + fun setMaxItems(i: Int) } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/RecycleBinFullException.java b/src/main/java/io/starter/toolkit/RecycleBinFullException.java deleted file mode 100644 index 3c876ec..0000000 --- a/src/main/java/io/starter/toolkit/RecycleBinFullException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -public class RecycleBinFullException extends java.lang.Exception { - - /** - * serialVersionUID - */ - private static final long serialVersionUID = 3065931314209459102L; - String err = "Too many items in Cache."; - - public RecycleBinFullException(String errmsg) { - super(); - err = errmsg; - } - - public RecycleBinFullException() { - super(); - } - - public String toString() { - return err; - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/formats/escher/MsofbtDggContainer.java b/src/main/java/io/starter/toolkit/RecycleBinFullException.kt similarity index 65% rename from src/main/java/io/starter/formats/escher/MsofbtDggContainer.java rename to src/main/java/io/starter/toolkit/RecycleBinFullException.kt index 9ed1cb4..a7c908d 100644 --- a/src/main/java/io/starter/formats/escher/MsofbtDggContainer.java +++ b/src/main/java/io/starter/toolkit/RecycleBinFullException.kt @@ -20,23 +20,27 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.formats.escher; +package io.starter.toolkit -//0xf000 -public class MsofbtDggContainer extends EscherRecord { +class RecycleBinFullException : java.lang.Exception { + internal var err = "Too many items in Cache." - /** - * serialVersionUID - */ - private static final long serialVersionUID = -2332486523666942739L; + constructor(errmsg: String) : super() { + err = errmsg + } + + constructor() : super() {} - public MsofbtDggContainer(int fbt, int inst, int version) { - super(fbt, inst, version); + override fun toString(): String { + return err } - protected byte[] getData() { + companion object { - return new byte[0]; + /** + * serialVersionUID + */ + private val serialVersionUID = 3065931314209459102L } -} +} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/ResourceLoader.java b/src/main/java/io/starter/toolkit/ResourceLoader.java deleted file mode 100644 index 899d599..0000000 --- a/src/main/java/io/starter/toolkit/ResourceLoader.java +++ /dev/null @@ -1,547 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import io.starter.OpenXLS.GetInfo; -import io.starter.naming.InitialContextImpl; - -import java.io.*; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLDecoder; -import java.util.Enumeration; -import java.util.MissingResourceException; -import java.util.Properties; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -/** - * Resource Loader which implements a basic JNDI Context and performs: - * - *
                    • Classloading mapped to variable names in properties files allows for easy - * abstraction of implementation classes - *
                    • Configuration strings loaded from properties files - *
                    • Arbitrary resource binding - */ -public class ResourceLoader extends InitialContextImpl implements Serializable, javax.naming.Context { - - /** - * - */ - private static final long serialVersionUID = 12345245254L; - private String resloc = ""; - private File propsfile = null; - public static boolean DEBUG = false; - private Properties resources = new Properties(); - - @Override - public String toString() { - return "Extentech ResourceLoader v." + ResourceLoader.getVersion(); - } - - public static String getVersion() { - return GetInfo.getVersion(); - } - - public Enumeration getKeys() { - if (!snagged) - return resources.keys(); - return env.keys(); - } - - public Object getObject(String key) { - if (!snagged) - return resources.get(key); - return env.get(key); - } - - private boolean snagged = false; - - /** - * put the properties file vals in the ResourceLoader - */ - private void snagVals() { - snagged = true; - Enumeration a = resources.keys(); - while (a.hasMoreElements()) { - String mystr = (String) a.nextElement(); - env.put(mystr, resources.get(mystr)); - } - } - - /** - * Constructor which takes a path to the properties file containing the initial - * ResourceLoader values. - *

                      - * Uses the resources from the proper locale. - * - * @param s - */ - public ResourceLoader(String s) { - super(); - if (true) { - if (s.indexOf("resources/") == -1) - s = "resources/" + s; // StringTool.strip(s,".properties"); - } - Logger.logInfo("ResourceLoader INIT: " + s); - - resloc = s; - try { - try { - propsfile = new File(s + ".properties"); - FileInputStream fis = new FileInputStream(propsfile); - resources.load(fis); - } catch (Exception e) { - try { - // propsfile.mkdirs(); - - propsfile.createNewFile(); - FileInputStream fis = new FileInputStream(propsfile); - resources.load(fis); - } catch (Exception ex) { - Logger.logWarn("Could not init Resourceloader from: " + propsfile.getAbsolutePath()); - } - } - - // handle private values - boolean hidevals = false; - try { - if (resources.get("public") != null) - if (resources.get("visibility").equals("private")) - hidevals = true; - } catch (MissingResourceException mre) { // do not load properties into JNDI environment - // Logger.logInfo("ResourceLoader - getting resources failed: " + - // mre.toString()); - } - if (!hidevals) - this.snagVals(); - } catch (MissingResourceException mre) { - Logger.logErr("ResourceLoader getting resources failed: " + mre.toString()); - } - } - - public ResourceLoader() { - super(); - // empty constructor - } - - /** - * Returns a String from the properties file - * - * @param nm - * @return - */ - public String getResourceString(String nm) { - String str; - try { - str = resources.get(nm).toString(); - } catch (Exception mre) { - str = ""; - // Logger.logWarn("Resource string: " + nm + " not found in: " + this.resloc); - } - return str; - } - - /** - * Sets a String value in the properties file - * - * @param nm - * @return - */ - public void setResourceString(String nm, String v) { - try { - resources.setProperty(nm, v); - FileOutputStream fos = new FileOutputStream(propsfile); - resources.store(fos, null); - fos.flush(); - fos.close(); - } catch (Exception mre) { - Logger.logWarn("Resource string: " + nm + " could not be set to " + v + " in:" + this.resloc); - } - } - - /** - * Returns an Array of Objects which are class loaded based on a comma-delimited - * list of class names listed in the properties file. - */ - public Object[] getObjects(String propname) { - String objnames = getResourceString(propname); - if (objnames != null) { - Object[] obj = new Object[1]; - obj[0] = loadClass(objnames); - return obj; - } - return null; - } - - /** - * Load a Class by name - * - * @param className - * @return - */ - public static Object loadClass(String className) { - - ExtenClassLoader cl = new ExtenClassLoader(); - - // Make a new one of whatever type of Obj it is... - Object obj = null; - try { - Class c = cl.loadClass(className, true); - obj = c.newInstance(); - return obj; - } catch (ClassFormatError t) { - Logger.logErr(t.toString()); - return null; - } catch (ClassNotFoundException t) { - Logger.logErr(t); - return null; - } catch (ClassCastException t) { - Logger.logErr(t); - return null; - } catch (InstantiationException t) { - Logger.logErr(t); - return null; - } catch (IllegalAccessException t) { - Logger.logErr(t); - return null; - } - } - - /** - * Loads the named resource from the class path. - */ - public static byte[] getBytesFromJar(String name) throws IOException { - - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - InputStream source = classLoader.getResourceAsStream("io/starter/OpenXLS/templates/prototype.ser"); - - if (source == null) - return null; - - ByteArrayOutputStream sink = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int count = 0; - - while (count != -1) { - sink.write(buffer, 0, count); - count = source.read(buffer); - } - - source.close(); - return sink.toByteArray(); - } - - /** - * Returns the file system-specific path to a given resource in the classpath - * for the VM. - * - * @param resource - * @return - */ - public static String getFilePathForResource(String resource) { - URL u = new ResourceLoader().getClass().getResource(resource); - // 20070107 KSC: report error - if (u == null) { - Logger.logErr("ResourceLoader.getFilePathForResource: " + resource + " not found."); - return null; - } - if (DEBUG) - Logger.logInfo("ResourceLoader.getFilePathForResource() got:" + u.toString()); - String s = u.getFile(); - - if (DEBUG) - Logger.logInfo("ResourceLoader.getFilePathForResource Decoding:" + s); - s = ResourceLoader.Decode(s); - if (DEBUG) - Logger.logInfo("ResourceLoader.getFilePathForResource Decoded:" + s); - - int i = s.indexOf("!"); - if (i > -1) { // file is in a jar - String zipstring = s.substring(0, i); - - // cut off the internal zip file part & the file:/ - int begin = zipstring.indexOf(":"); - begin += 1; - zipstring = zipstring.substring(begin); - if (zipstring.indexOf(":") != -1) { // windoze box - if (zipstring.indexOf("/") == 0) { - zipstring = zipstring.substring(1); - } - } - if (DEBUG) - Logger.logInfo("Resourceloader.getFilePathForResource(): Successfully obtained " + zipstring); - return zipstring; - } else { // file is not in a jar - if (DEBUG) - Logger.logErr("ResourceLoader.getFilePathForResource(): File is not in jar:" + s); - return s; - } - } - - /** - * write file f to jar referenced by jarandresource ( ) and set - * path/name to resource - * - * @param jarandResource - * @param f - */ - public static void addFileToJar(String jarandResource, String f) { - String[] tmp = extractJarAndResourceName(jarandResource); - try { - ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tmp[0])); // open Archive for outptu - ZipInputStream fin = new ZipInputStream(new FileInputStream(f)); - out.putNextEntry(new ZipEntry(tmp[1])); - byte[] buf = new byte[fin.available()]; - fin.read(buf); - out.write(buf); - out.flush(); - out.closeEntry(); - out.close(); - } catch (Exception e) { - Logger.logErr("addFileToJar: Jar: " + tmp[0] + " File: " + tmp[1] + " : " + e.toString()); - } - } - - /** - * returns truth of "file is a jar/archive file" - * - * @param f - * @return - */ - public static boolean isJarFile(String f) { - f = f.toLowerCase(); - int i = f.indexOf(".war"); - if (i < 0) - i = f.indexOf(".jar"); - if (i < 0) - i = f.indexOf(".rar"); - if (i < 0) - i = f.indexOf(".zip"); - return (i > -1); - } - - /** - * separate and return the jar portion and resource portion of a jar and - * resource string: - * - * @param jarAndResource - * @return String[2] - */ - public static String[] extractJarAndResourceName(String jarAndResource) { - jarAndResource = jarAndResource.toLowerCase(); - int i = jarAndResource.indexOf(".war"); - if (i < 0) - i = jarAndResource.indexOf(".jar"); - if (i < 0) - i = jarAndResource.indexOf(".rar"); - if (i < 0) - i = jarAndResource.indexOf(".zip"); - return new String[]{jarAndResource.substring(0, i + 4), jarAndResource.substring(i + 5)}; - } - - /** - * Get the path to a directory by locating the jar file in the classpath - * containing the given resource name. - * - * @param resource - * @return - */ - public static String getWorkingDirectoryFromJar(String resource) { - String s; - // if jarloc property is set, use it to find working directory - if (System.getProperty("io.starter.OpenXLS.jarloc") != null) { - s = System.getProperty("io.starter.OpenXLS.jarloc") + "!"; - } else { - URL u = ResourceLoader.class.getResource(resource); - s = u.getFile(); - } - if (DEBUG) - Logger.logInfo("Resource: " + resource + " found in: " + s); - - // cut off the internal zip file part & the file:/ - int begin = -1; - begin = s.indexOf("file:"); - if (begin < 0) { - begin = s.indexOf(":"); - begin += 1; - } else - begin += 5; - s = s.substring(begin); - if (s.indexOf(":") != -1) { // windoze box - if (s.indexOf("/") == 0) - s = s.substring(1); - } - if (DEBUG) - Logger.logInfo("ResourceLoader() after stripping:" + s); - int i = s.indexOf("!"); - if (i > -1) { - String zipstring = s.substring(0, i); - i = zipstring.lastIndexOf("/"); - if (i == -1) { - i = zipstring.lastIndexOf("\\"); - } - zipstring = zipstring.substring(0, i); - if (DEBUG) - Logger.logInfo("ResourceLoader() returning zipstring Final Working Directory Setting: " + zipstring); - return zipstring; - } else { - if (DEBUG) - Logger.logInfo("ResourceLoader() returning Final Working Directory Setting: " + s); - return s; - } - } - - private static URLDecoder decodr = new URLDecoder(); - - /** - * Decode a URL String, if supported by the JDK version in use this method will - * utilize the - * - * @param s - * @return - */ - public static String Decode(String s) { - String[] tmpstr = {s, "ISO-8859-1"}; - // first attempt using encoding... - String ret = s; - ret = (String) ResourceLoader.executeIfSupported(decodr, tmpstr, "decode"); - if (ret == null) - try { - ret = URLDecoder.decode(s, "ISO-8859-1"); - } catch (Exception e) { - Logger.logErr("ResourceLoader.Decode resource failed: " + e.toString()); - } - return ret; - } - - /** - * Decode a URL String, if supported by the JDK version in use this method will - * utilize the non-deprecated method of decoding. - * - * @param s, string to decode - * @param encoding, the encoding type to use - * @return - */ - public static String Decode(String s, String encoding) { - String[] tmpstr = {s, "Encoding"}; - // first attempt using encoding... - String ret = s; - ret = (String) ResourceLoader.executeIfSupported(decodr, tmpstr, "decode"); - if (ret == null) - try { - ret = URLDecoder.decode(s); - } catch (Exception e) { - Logger.logErr("ResourceLoader.Decode resource failed: " + e.toString()); - } - return ret; - } - - /** - * Attempt to execute a Method on an Object - * - * @param ob the Object which contains the method you want to execute - * @param args an array of arguments to the Method, null if none - * @param methname the name of the Method you are executing - * @return the return value of the method if any - */ - public static Object executeIfSupported(Object ob, Object[] args, String methname) { - try { - Object retob = null; - Method[] mt = ob.getClass().getMethods(); - int t = 0; - for (; t < mt.length; t++) { - // Make JDK-specific call to method - // Logger.logInfo(mt[t].getName()); - int numparms = 0, numargs = 0; - if (args != null) - numargs = args.length; - if (mt[t].getParameterTypes() != null) - numparms = mt[t].getParameterTypes().length; - String nm = mt[t].getName(); - if ((nm.equals(methname)) && (numparms == numargs)) { - try { - Method mx = mt[t]; - retob = mx.invoke(ob, args); - break; - } catch (Exception e) { - if (false) - Logger.logWarn("ResourceLoader.executeIfSupported() Method NOT supported: " + methname - + " in " + ob.getClass().getName() + " for arguments " - + StringTool.arrayToString(args)); - return null; - } - } - } - if (false) - if (t == mt.length) - Logger.logWarn("ResourceLoader.executeIfSupported() Method NOT found: " + methname + " in " - + ob.getClass().getName() + " for arguments " + StringTool.arrayToString(args)); - return retob; - } catch (NoSuchMethodError e) { - return null; - } - } - - /** - * Execute a Method on an Object - * - * @param ob the Object which contains the method you want to execute - * @param args an array of arguments to the Method, null if none - * @param methname the name of the Method you are executing - * @return the return value of the method if any - */ - public static Object execute(Object ob, Object[] args, String methname) throws Exception { - Class[] pc = new Class[args.length]; - for (int r = 0; r < args.length; r++) { - pc[r] = args[r].getClass(); - } - Method mt = null; - try { - mt = ob.getClass().getMethod(methname, pc); - } catch (NoSuchMethodException e) { - // deal with 'unwrapping' primitives - return executeIfSupported(ob, args, methname); - } - // Logger.logInfo("ResourceLoader.execute() Invoking:" + mt.getName() +" on " + - // ob.getClass().getName()); - try { - return mt.invoke(ob, args); - } catch (Exception e) { - Logger.logErr("ResourceLoader.execute " + methname + " on " + ob.getClass().getName() + " failed: " - + e.toString()); - e.printStackTrace(); - return null; - } - } - - /** - * Sets the debugging level for the ResourceLoader - * - * @param b - */ - public void setDebug(boolean b) { - DEBUG = b; - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/ResourceLoader.kt b/src/main/java/io/starter/toolkit/ResourceLoader.kt new file mode 100644 index 0000000..a935862 --- /dev/null +++ b/src/main/java/io/starter/toolkit/ResourceLoader.kt @@ -0,0 +1,559 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import io.starter.OpenXLS.GetInfo +import io.starter.naming.InitialContextImpl + +import java.io.* +import java.lang.reflect.Method +import java.net.URL +import java.net.URLDecoder +import java.util.Enumeration +import java.util.MissingResourceException +import java.util.Properties +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +/** + * Resource Loader which implements a basic JNDI Context and performs: + * + * * Classloading mapped to variable names in properties files allows for easy + * abstraction of implementation classes + * * Configuration strings loaded from properties files + * * Arbitrary resource binding + */ +class ResourceLoader : InitialContextImpl, Serializable, javax.naming.Context { + private var resloc = "" + private var propsfile: File? = null + private val resources = Properties() + + val keys: Enumeration<*> + get() = if (!snagged) resources.keys() else env.keys() + + private var snagged = false + + override fun toString(): String { + return "Extentech ResourceLoader v." + ResourceLoader.version + } + + fun getObject(key: String): Any { + return if (!snagged) resources[key] else env[key] + } + + /** + * put the properties file vals in the ResourceLoader + */ + private fun snagVals() { + snagged = true + val a = resources.keys() + while (a.hasMoreElements()) { + val mystr = a.nextElement() as String + env[mystr] = resources[mystr] + } + } + + /** + * Constructor which takes a path to the properties file containing the initial + * ResourceLoader values. + * + * + * Uses the resources from the proper locale. + * + * @param s + */ + constructor(s: String) : super() { + var s = s + if (true) { + if (s.indexOf("resources/") == -1) + s = "resources/$s" // StringTool.strip(s,".properties"); + } + Logger.logInfo("ResourceLoader INIT: $s") + + resloc = s + try { + try { + propsfile = File("$s.properties") + val fis = FileInputStream(propsfile!!) + resources.load(fis) + } catch (e: Exception) { + try { + // propsfile.mkdirs(); + + propsfile!!.createNewFile() + val fis = FileInputStream(propsfile!!) + resources.load(fis) + } catch (ex: Exception) { + Logger.logWarn("Could not init Resourceloader from: " + propsfile!!.absolutePath) + } + + } + + // handle private values + var hidevals = false + try { + if (resources["public"] != null) + if (resources["visibility"] == "private") + hidevals = true + } catch (mre: MissingResourceException) { // do not load properties into JNDI environment + // Logger.logInfo("ResourceLoader - getting resources failed: " + + // mre.toString()); + } + + if (!hidevals) + this.snagVals() + } catch (mre: MissingResourceException) { + Logger.logErr("ResourceLoader getting resources failed: $mre") + } + + } + + constructor() : super() { + // empty constructor + } + + /** + * Returns a String from the properties file + * + * @param nm + * @return + */ + fun getResourceString(nm: String): String? { + var str: String + try { + str = resources[nm].toString() + } catch (mre: Exception) { + str = "" + // Logger.logWarn("Resource string: " + nm + " not found in: " + this.resloc); + } + + return str + } + + /** + * Sets a String value in the properties file + * + * @param nm + * @return + */ + fun setResourceString(nm: String, v: String) { + try { + resources.setProperty(nm, v) + val fos = FileOutputStream(propsfile!!) + resources.store(fos, null) + fos.flush() + fos.close() + } catch (mre: Exception) { + Logger.logWarn("Resource string: " + nm + " could not be set to " + v + " in:" + this.resloc) + } + + } + + /** + * Returns an Array of Objects which are class loaded based on a comma-delimited + * list of class names listed in the properties file. + */ + fun getObjects(propname: String): Array? { + val objnames = getResourceString(propname) + if (objnames != null) { + val obj = arrayOfNulls(1) + obj[0] = loadClass(objnames) + return obj + } + return null + } + + /** + * Sets the debugging level for the ResourceLoader + * + * @param b + */ + fun setDebug(b: Boolean) { + DEBUG = b + } + + companion object { + + /** + * + */ + private const val serialVersionUID = 12345245254L + var DEBUG = false + + val version: String + get() = GetInfo.getVersion() + + /** + * Load a Class by name + * + * @param className + * @return + */ + fun loadClass(className: String): Any? { + + val cl = ExtenClassLoader() + + // Make a new one of whatever type of Obj it is... + var obj: Any? = null + try { + val c = cl.loadClass(className, true) + obj = c!!.newInstance() + return obj + } catch (t: ClassFormatError) { + Logger.logErr(t.toString()) + return null + } catch (t: ClassNotFoundException) { + Logger.logErr(t) + return null + } catch (t: ClassCastException) { + Logger.logErr(t) + return null + } catch (t: InstantiationException) { + Logger.logErr(t) + return null + } catch (t: IllegalAccessException) { + Logger.logErr(t) + return null + } + + } + + /** + * Loads the named resource from the class path. + */ + @Throws(IOException::class) + fun getBytesFromJar(name: String): ByteArray? { + + val classLoader = Thread.currentThread().contextClassLoader + val source = classLoader.getResourceAsStream("io/starter/OpenXLS/templates/prototype.ser") ?: return null + + val sink = ByteArrayOutputStream() + val buffer = ByteArray(1024) + var count = 0 + + while (count != -1) { + sink.write(buffer, 0, count) + count = source.read(buffer) + } + + source.close() + return sink.toByteArray() + } + + /** + * Returns the file system-specific path to a given resource in the classpath + * for the VM. + * + * @param resource + * @return + */ + fun getFilePathForResource(resource: String): String? { + val u = ResourceLoader().javaClass.getResource(resource) + // 20070107 KSC: report error + if (u == null) { + Logger.logErr("ResourceLoader.getFilePathForResource: $resource not found.") + return null + } + if (DEBUG) + Logger.logInfo("ResourceLoader.getFilePathForResource() got:" + u!!.toString()) + var s: String? = u!!.getFile() + + if (DEBUG) + Logger.logInfo("ResourceLoader.getFilePathForResource Decoding:" + s!!) + s = ResourceLoader.Decode(s) + if (DEBUG) + Logger.logInfo("ResourceLoader.getFilePathForResource Decoded:" + s!!) + + val i = s!!.indexOf("!") + if (i > -1) { // file is in a jar + var zipstring = s.substring(0, i) + + // cut off the internal zip file part & the file:/ + var begin = zipstring.indexOf(":") + begin += 1 + zipstring = zipstring.substring(begin) + if (zipstring.indexOf(":") != -1) { // windoze box + if (zipstring.indexOf("/") == 0) { + zipstring = zipstring.substring(1) + } + } + if (DEBUG) + Logger.logInfo("Resourceloader.getFilePathForResource(): Successfully obtained $zipstring") + return zipstring + } else { // file is not in a jar + if (DEBUG) + Logger.logErr("ResourceLoader.getFilePathForResource(): File is not in jar:$s") + return s + } + } + + /** + * write file f to jar referenced by jarandresource ( ) and set + * path/name to resource + * + * @param jarandResource + * @param f + */ + fun addFileToJar(jarandResource: String, f: String) { + val tmp = extractJarAndResourceName(jarandResource) + try { + val out = ZipOutputStream(FileOutputStream(tmp[0])) // open Archive for outptu + val fin = ZipInputStream(FileInputStream(f)) + out.putNextEntry(ZipEntry(tmp[1])) + val buf = ByteArray(fin.available()) + fin.read(buf) + out.write(buf) + out.flush() + out.closeEntry() + out.close() + } catch (e: Exception) { + Logger.logErr("addFileToJar: Jar: " + tmp[0] + " File: " + tmp[1] + " : " + e.toString()) + } + + } + + /** + * returns truth of "file is a jar/archive file" + * + * @param f + * @return + */ + fun isJarFile(f: String): Boolean { + var f = f + f = f.toLowerCase() + var i = f.indexOf(".war") + if (i < 0) + i = f.indexOf(".jar") + if (i < 0) + i = f.indexOf(".rar") + if (i < 0) + i = f.indexOf(".zip") + return i > -1 + } + + /** + * separate and return the jar portion and resource portion of a jar and + * resource string: /.zip/.jar/.rar)> + * + * @param jarAndResource + * @return String[2] + */ + fun extractJarAndResourceName(jarAndResource: String): Array { + var jarAndResource = jarAndResource + jarAndResource = jarAndResource.toLowerCase() + var i = jarAndResource.indexOf(".war") + if (i < 0) + i = jarAndResource.indexOf(".jar") + if (i < 0) + i = jarAndResource.indexOf(".rar") + if (i < 0) + i = jarAndResource.indexOf(".zip") + return arrayOf(jarAndResource.substring(0, i + 4), jarAndResource.substring(i + 5)) + } + + /** + * Get the path to a directory by locating the jar file in the classpath + * containing the given resource name. + * + * @param resource + * @return + */ + fun getWorkingDirectoryFromJar(resource: String): String { + var s: String + // if jarloc property is set, use it to find working directory + if (System.getProperty("io.starter.OpenXLS.jarloc") != null) { + s = System.getProperty("io.starter.OpenXLS.jarloc") + "!" + } else { + val u = ResourceLoader::class.java!!.getResource(resource) + s = u.getFile() + } + if (DEBUG) + Logger.logInfo("Resource: $resource found in: $s") + + // cut off the internal zip file part & the file:/ + var begin = -1 + begin = s.indexOf("file:") + if (begin < 0) { + begin = s.indexOf(":") + begin += 1 + } else + begin += 5 + s = s.substring(begin) + if (s.indexOf(":") != -1) { // windoze box + if (s.indexOf("/") == 0) + s = s.substring(1) + } + if (DEBUG) + Logger.logInfo("ResourceLoader() after stripping:$s") + var i = s.indexOf("!") + if (i > -1) { + var zipstring = s.substring(0, i) + i = zipstring.lastIndexOf("/") + if (i == -1) { + i = zipstring.lastIndexOf("\\") + } + zipstring = zipstring.substring(0, i) + if (DEBUG) + Logger.logInfo("ResourceLoader() returning zipstring Final Working Directory Setting: $zipstring") + return zipstring + } else { + if (DEBUG) + Logger.logInfo("ResourceLoader() returning Final Working Directory Setting: $s") + return s + } + } + + private val decodr = URLDecoder() + + /** + * Decode a URL String, if supported by the JDK version in use this method will + * utilize the + * + * @param s + * @return + */ + fun Decode(s: String?): String? { + val tmpstr = arrayOf(s, "ISO-8859-1") + // first attempt using encoding... + var ret = s + ret = ResourceLoader.executeIfSupported(decodr, tmpstr, "decode") as String? + if (ret == null) + try { + ret = URLDecoder.decode(s!!, "ISO-8859-1") + } catch (e: Exception) { + Logger.logErr("ResourceLoader.Decode resource failed: $e") + } + + return ret + } + + /** + * Decode a URL String, if supported by the JDK version in use this method will + * utilize the non-deprecated method of decoding. + * + * @param s, string to decode + * @param encoding, the encoding type to use + * @return + */ + fun Decode(s: String, encoding: String): String? { + val tmpstr = arrayOf(s, "Encoding") + // first attempt using encoding... + var ret: String? = s + ret = ResourceLoader.executeIfSupported(decodr, tmpstr, "decode") as String? + if (ret == null) + try { + ret = URLDecoder.decode(s) + } catch (e: Exception) { + Logger.logErr("ResourceLoader.Decode resource failed: $e") + } + + return ret + } + + /** + * Attempt to execute a Method on an Object + * + * @param ob the Object which contains the method you want to execute + * @param args an array of arguments to the Method, null if none + * @param methname the name of the Method you are executing + * @return the return value of the method if any + */ + fun executeIfSupported(ob: Any, args: Array?, methname: String): Any? { + try { + var retob: Any? = null + val mt = ob.javaClass.getMethods() + var t = 0 + while (t < mt.size) { + // Make JDK-specific call to method + // Logger.logInfo(mt[t].getName()); + var numparms = 0 + var numargs = 0 + if (args != null) + numargs = args.size + if (mt[t].getParameterTypes() != null) + numparms = mt[t].getParameterTypes().size + val nm = mt[t].getName() + if (nm == methname && numparms == numargs) { + try { + val mx = mt[t] + retob = mx.invoke(ob, *args) + break + } catch (e: Exception) { + if (false) + Logger.logWarn("ResourceLoader.executeIfSupported() Method NOT supported: " + methname + + " in " + ob.javaClass.getName() + " for arguments " + + StringTool.arrayToString(args!!)) + return null + } + + } + t++ + } + if (false) + if (t == mt.size) + Logger.logWarn("ResourceLoader.executeIfSupported() Method NOT found: " + methname + " in " + + ob.javaClass.getName() + " for arguments " + StringTool.arrayToString(args!!)) + return retob + } catch (e: NoSuchMethodError) { + return null + } + + } + + /** + * Execute a Method on an Object + * + * @param ob the Object which contains the method you want to execute + * @param args an array of arguments to the Method, null if none + * @param methname the name of the Method you are executing + * @return the return value of the method if any + */ + @Throws(Exception::class) + fun execute(ob: Any, args: Array, methname: String): Any? { + val pc = arrayOfNulls>(args.size) + for (r in args.indices) { + pc[r] = args[r].javaClass + } + var mt: Method? = null + try { + mt = ob.javaClass.getMethod(methname, *pc) + } catch (e: NoSuchMethodException) { + // deal with 'unwrapping' primitives + return executeIfSupported(ob, args, methname) + } + + // Logger.logInfo("ResourceLoader.execute() Invoking:" + mt.getName() +" on " + + // ob.getClass().getName()); + try { + return mt!!.invoke(ob, *args) + } catch (e: Exception) { + Logger.logErr("ResourceLoader.execute " + methname + " on " + ob.javaClass.getName() + " failed: " + + e.toString()) + e.printStackTrace() + return null + } + + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/SpecialArrayList.java b/src/main/java/io/starter/toolkit/SpecialArrayList.kt similarity index 62% rename from src/main/java/io/starter/toolkit/SpecialArrayList.java rename to src/main/java/io/starter/toolkit/SpecialArrayList.kt index 37ad6b4..4c8b6c5 100644 --- a/src/main/java/io/starter/toolkit/SpecialArrayList.java +++ b/src/main/java/io/starter/toolkit/SpecialArrayList.kt @@ -20,9 +20,9 @@ * . * ---------- END COPYRIGHT NOTICE ---------- */ -package io.starter.toolkit; +package io.starter.toolkit -import java.util.*; +import java.util.* /** * Resizable-array implementation of the List interface. Implements all @@ -31,32 +31,39 @@ * methods to manipulate the size of the array that is used internally to store * the list. (This class is roughly equivalent to Vector, except that * it is unsynchronized.) - *

                      - *

                      + * + * + * + * * The size, isEmpty, get, set, * iterator, and listIterator operations run in constant time. - * The add operation runs in amortized constant time, that is, + * The add operation runs in *amortized constant time*, that is, * adding n elements requires O(n) time. All of the other operations run in * linear time (roughly speaking). The constant factor is low compared to that * for the LinkedList implementation. - *

                      - *

                      - * Each ArrayList instance has a capacity. The capacity is the + * + * + * + * + * Each ArrayList instance has a *capacity*. The capacity is the * size of the array used to store the elements in the list. It is always at * least as large as the list size. As elements are added to an ArrayList, its * capacity grows automatically. The details of the growth policy are not * specified beyond the fact that adding an element has constant amortized time * cost. - *

                      - *

                      + * + * + * + * * An application can increase the capacity of an ArrayList instance * before adding a large number of elements using the ensureCapacity * operation. This may reduce the amount of incremental reallocation. - *

                      * - * Note that this implementation is not synchronized. If + * + * + * **Note that this implementation is not synchronized.** If * multiple threads access an ArrayList instance concurrently, and at - * least one of the threads modifies the list structurally, it must be + * least one of the threads modifies the list structurally, it *must* be * synchronized externally. (A structural modification is any operation that * adds or deletes one or more elements, or explicitly resizes the backing * array; merely setting the value of an element is not a structural @@ -67,100 +74,106 @@ * access to the list: * *

                      - *  List list = Collections.synchronizedList(new ArrayList(...));
                      - * 
                      - *

                      - *

                      + * List list = Collections.synchronizedList(new ArrayList(...)); + * + * + * + * + * * The iterators returned by this class's iterator and - * listIterator methods are fail-fast: if list is structurally + * listIterator methods are *fail-fast*: if list is structurally * modified at any time after the iterator is created, in any way except through * the iterator's own remove or add methods, the iterator will throw a * ConcurrentModificationException. Thus, in the face of concurrent * modification, the iterator fails quickly and cleanly, rather than risking * arbitrary, non-deterministic behavior at an undetermined time in the future. - *

                      - *

                      + * + * + * + * * Note that the fail-fast behavior of an iterator cannot be guaranteed as it * is, generally speaking, impossible to make any hard guarantees in the * presence of unsynchronized concurrent modification. Fail-fast iterators throw * ConcurrentModificationException on a best-effort basis. Therefore, * it would be wrong to write a program that depended on this exception for its - * correctness: the fail-fast behavior of iterators should be used only to - * detect bugs. - *

                      - *

                      + * correctness: *the fail-fast behavior of iterators should be used only to + * detect bugs.* + * + * + * + * * This class is a member of the - * Java Collections - * Framework. + * [ Java Collections + * Framework]({@docRoot}/../guide/collections/index.html). * * @see Collection + * * @see List + * * @see LinkedList + * * @see Vector - * @see Collections#synchronizedList(List) + * + * @see Collections.synchronizedList */ -class SpecialArrayList extends AbstractList - implements List, RandomAccess, Cloneable, java.io.Serializable { - private static final long serialVersionUID = 8683452581122892189L; +internal open class SpecialArrayList : AbstractList, List, RandomAccess, Cloneable, java.io.Serializable { + + /** + * The array buffer into which the elements of the ArrayList are stored. The + * capacity of the ArrayList is the length of this array buffer. + */ + var elementData: Array + + /** + * The size of the ArrayList (the number of elements it contains). + * + * @serial + */ + private var size: Int = 0 /** * Returns an iterator over the elements in this list in proper sequence. - *

                      - *

                      + * + * + * + * * This implementation returns a straightforward implementation of the iterator * interface, relying on the backing list's size(), get(int), * and remove(int) methods. - *

                      - *

                      + * + * + * + * * Note that the iterator returned by this method will throw an * UnsupportedOperationException in response to its remove * method unless the list's remove(int) method is overridden. - *

                      - *

                      + * + * + * + * * This implementation can be made to throw runtime exceptions in the face of * concurrent modification, as described in the specification for the * (protected) modCount field. * * @return an iterator over the elements in this list in proper sequence. - * @see #modCount + * @see .modCount */ - @Override - public Iterator iterator() { - return new Itr(); + override fun iterator(): Iterator<*> { + return Itr() } - /** - * The array buffer into which the elements of the ArrayList are stored. The - * capacity of the ArrayList is the length of this array buffer. - */ - Object[] elementData; - - /** - * The size of the ArrayList (the number of elements it contains). - * - * @serial - */ - private int size; - /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list. * @throws IllegalArgumentException if the specified initial capacity is negative */ - public SpecialArrayList(int initialCapacity) { - super(); + @JvmOverloads + constructor(initialCapacity: Int = 10) : super() { if (initialCapacity < 0) - throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); - this.elementData = new Object[initialCapacity]; - } - - /** - * Constructs an empty list with an initial capacity of ten. - */ - public SpecialArrayList() { - this(10); + throw IllegalArgumentException("Illegal Capacity: $initialCapacity") + this.elementData = arrayOfNulls(initialCapacity) } /** @@ -172,11 +185,11 @@ public SpecialArrayList() { * @param c the collection whose elements are to be placed into this list. * @throws NullPointerException if the specified collection is null. */ - public SpecialArrayList(Collection c) { - size = c.size(); + constructor(c: Collection<*>) { + size = c.size // Allow 10% room for growth - elementData = new Object[(int) Math.min((size * 110L) / 100, Integer.MAX_VALUE)]; - c.toArray(elementData); + elementData = arrayOfNulls(Math.min(size * 110L / 100, Integer.MAX_VALUE.toLong()).toInt()) + c.toTypedArray() } /** @@ -184,13 +197,13 @@ public SpecialArrayList(Collection c) { * current size. An application can use this operation to minimize the storage * of an ArrayList instance. */ - public void trimToSize() { - modCount++; - int oldCapacity = elementData.length; + fun trimToSize() { + modCount++ + val oldCapacity = elementData.size if (size < oldCapacity) { - Object[] oldData = elementData; - elementData = new Object[size]; - System.arraycopy(oldData, 0, elementData, 0, size); + val oldData = elementData + elementData = arrayOfNulls(size) + System.arraycopy(oldData, 0, elementData, 0, size) } } @@ -201,16 +214,16 @@ public void trimToSize() { * * @param minCapacity the desired minimum capacity. */ - public void ensureCapacity(int minCapacity) { - modCount++; - int oldCapacity = elementData.length; + fun ensureCapacity(minCapacity: Int) { + modCount++ + val oldCapacity = elementData.size if (minCapacity > oldCapacity) { - Object[] oldData = elementData; - int newCapacity = (oldCapacity * 3) / 2 + 1; + val oldData = elementData + var newCapacity = oldCapacity * 3 / 2 + 1 if (newCapacity < minCapacity) - newCapacity = minCapacity; - elementData = new Object[newCapacity]; - System.arraycopy(oldData, 0, elementData, 0, size); + newCapacity = minCapacity + elementData = arrayOfNulls(newCapacity) + System.arraycopy(oldData, 0, elementData, 0, size) } } @@ -219,9 +232,8 @@ public void ensureCapacity(int minCapacity) { * * @return the number of elements in this list. */ - @Override - public int size() { - return size; + override fun size(): Int { + return size } /** @@ -229,21 +241,19 @@ public int size() { * * @return true if this list has no elements; false otherwise. */ - @Override - public boolean isEmpty() { - return size == 0; + override fun isEmpty(): Boolean { + return size == 0 } /** * Returns true if this list contains the specified element. * * @param elem element whose presence in this List is to be tested. - * @return true if the specified element is present; - * false otherwise. + * @return `true` if the specified element is present; + * `false` otherwise. */ - @Override - public boolean contains(Object elem) { - return indexOf(elem) >= 0; + override fun contains(elem: Any?): Boolean { + return indexOf(elem) >= 0 } /** @@ -253,20 +263,19 @@ public boolean contains(Object elem) { * @param elem an object. * @return the index of the first occurrence of the argument in this list; * returns -1 if the object is not found. - * @see Object#equals(Object) + * @see Object.equals */ - @Override - public int indexOf(Object elem) { + override fun indexOf(elem: Any?): Int { if (elem == null) { - for (int i = 0; i < size; i++) + for (i in 0 until size) if (elementData[i] == null) - return i; + return i } else { - for (int i = 0; i < size; i++) - if (elem.equals(elementData[i])) - return i; + for (i in 0 until size) + if (elem == elementData[i]) + return i } - return -1; + return -1 } /** @@ -277,18 +286,17 @@ public int indexOf(Object elem) { * @return the index of the last occurrence of the specified object in this * list; returns -1 if the object is not found. */ - @Override - public int lastIndexOf(Object elem) { + override fun lastIndexOf(elem: Any?): Int { if (elem == null) { - for (int i = size - 1; i >= 0; i--) + for (i in size - 1 downTo 0) if (elementData[i] == null) - return i; + return i } else { - for (int i = size - 1; i >= 0; i--) - if (elem.equals(elementData[i])) - return i; + for (i in size - 1 downTo 0) + if (elem == elementData[i]) + return i } - return -1; + return -1 } /** @@ -297,18 +305,18 @@ public int lastIndexOf(Object elem) { * * @return a clone of this ArrayList instance. */ - @Override - public Object clone() { + public override fun clone(): Any { try { - SpecialArrayList v = (SpecialArrayList) super.clone(); - v.elementData = new Object[size]; - System.arraycopy(elementData, 0, v.elementData, 0, size); - v.modCount = 0; - return v; - } catch (CloneNotSupportedException e) { + val v = super.clone() as SpecialArrayList + v.elementData = arrayOfNulls(size) + System.arraycopy(elementData, 0, v.elementData, 0, size) + v.modCount = 0 + return v + } catch (e: CloneNotSupportedException) { // this shouldn't happen, since we are Cloneable - throw new InternalError(); + throw InternalError() } + } /** @@ -318,11 +326,10 @@ public Object clone() { * @return an array containing all of the elements in this list in the correct * order. */ - @Override - public Object[] toArray() { - Object[] result = new Object[size]; - System.arraycopy(elementData, 0, result, 0, size); - return result; + override fun toArray(): Array { + val result = arrayOfNulls(size) + System.arraycopy(elementData, 0, result, 0, size) + return result } /** @@ -331,32 +338,34 @@ public Object[] toArray() { * If the list fits in the specified array, it is returned therein. Otherwise, a * new array is allocated with the runtime type of the specified array and the * size of this list. - *

                      - *

                      + * + * + * + * * If the list fits in the specified array with room to spare (i.e., the array * has more elements than the list), the element in the array immediately * following the end of the collection is set to null. This is useful - * in determining the length of the list only if the caller knows that + * in determining the length of the list *only* if the caller knows that * the list does not contain any null elements. * * @param a the array into which the elements of the list are to be stored, if - * it is big enough; otherwise, a new array of the same runtime type - * is allocated for this purpose. + * it is big enough; otherwise, a new array of the same runtime type + * is allocated for this purpose. * @return an array containing the elements of the list. * @throws ArrayStoreException if the runtime type of a is not a supertype of the runtime type - * of every element in this list. + * of every element in this list. */ - @Override - public Object[] toArray(Object[] a) { - if (a.length < size) - a = (Object[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); + override fun toArray(a: Array): Array { + var a = a + if (a.size < size) + a = java.lang.reflect.Array.newInstance(a.javaClass.getComponentType(), size) as Array - System.arraycopy(elementData, 0, a, 0, size); + System.arraycopy(elementData, 0, a, 0, size) - if (a.length > size) - a[size] = null; + if (a.size > size) + a[size] = null - return a; + return a } // Positional Access Operations @@ -367,12 +376,11 @@ public Object[] toArray(Object[] a) { * @param index index of element to return. * @return the element at the specified position in this list. * @throws IndexOutOfBoundsException if index is out of range (index - * < 0 || index >= size()). + * < 0 || index >= size()). */ - @Override - public Object get(int index) { - RangeCheck(index); - return elementData[index]; + override fun get(index: Int): Any { + RangeCheck(index) + return elementData[index] } /** @@ -383,15 +391,14 @@ public Object get(int index) { * @param element element to be stored at the specified position. * @return the element previously at the specified position. * @throws IndexOutOfBoundsException if index out of range - * (index < 0 || index >= size()). + * (index < 0 || index >= size()). */ - @Override - public Object set(int index, Object element) { - RangeCheck(index); + override fun set(index: Int, element: Any?): Any { + RangeCheck(index) - Object oldValue = elementData[index]; - elementData[index] = element; - return oldValue; + val oldValue = elementData[index] + elementData[index] = element + return oldValue } /** @@ -400,11 +407,10 @@ public Object set(int index, Object element) { * @param o element to be appended to this list. * @return true (as per the general contract of Collection.add). */ - @Override - public boolean add(Object o) { - ensureCapacity(size + 1); // Increments modCount!! - elementData[size++] = o; - return true; + override fun add(o: Any?): Boolean { + ensureCapacity(size + 1) // Increments modCount!! + elementData[size++] = o + return true } /** @@ -415,17 +421,16 @@ public boolean add(Object o) { * @param index index at which the specified element is to be inserted. * @param element element to be inserted. * @throws IndexOutOfBoundsException if index is out of range - * (index < 0 || index > size()). + * (index < 0 || index > size()). */ - @Override - public void add(int index, Object element) { + override fun add(index: Int, element: Any?) { if (index > size || index < 0) - throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + throw IndexOutOfBoundsException("Index: $index, Size: $size") - ensureCapacity(size + 1); // Increments modCount!! - System.arraycopy(elementData, index, elementData, index + 1, size - index); - elementData[index] = element; - size++; + ensureCapacity(size + 1) // Increments modCount!! + System.arraycopy(elementData, index, elementData, index + 1, size - index) + elementData[index] = element + size++ } /** @@ -435,36 +440,34 @@ public void add(int index, Object element) { * @param index the index of the element to removed. * @return the element that was removed from the list. * @throws IndexOutOfBoundsException if index out of range (index - * < 0 || index >= size()). + * < 0 || index >= size()). */ - @Override - public Object remove(int index) { - RangeCheck(index); + override fun remove(index: Int): Any { + RangeCheck(index) - modCount++; - Object oldValue = elementData[index]; + modCount++ + val oldValue = elementData[index] - int numMoved = size - index - 1; + val numMoved = size - index - 1 if (numMoved > 0) - System.arraycopy(elementData, index + 1, elementData, index, numMoved); - elementData[--size] = null; // Let gc do its work + System.arraycopy(elementData, index + 1, elementData, index, numMoved) + elementData[--size] = null // Let gc do its work - return oldValue; + return oldValue } /** * Removes all of the elements from this list. The list will be empty after this * call returns. */ - @Override - public void clear() { - modCount++; + override fun clear() { + modCount++ // Let gc do its work - for (int i = 0; i < size; i++) - elementData[i] = null; + for (i in 0 until size) + elementData[i] = null - size = 0; + size = 0 } /** @@ -479,19 +482,18 @@ public void clear() { * @return true if this list changed as a result of the call. * @throws NullPointerException if the specified collection is null. */ - @Override - public boolean addAll(Collection c) { - Object[] a = c.toArray(); + override fun addAll(c: Collection<*>): Boolean { + val a = c.toTypedArray() // 20080124 KSC: replace with arraycopy // this.elementData = a; - int numNew = a.length; - elementData = new Object[numNew]; - System.arraycopy(a, 0, elementData, 0, numNew); + val numNew = a.size + elementData = arrayOfNulls(numNew) + System.arraycopy(a, 0, elementData, 0, numNew) // ensureCapacity(size + numNew); // Increments modCount - size += c.size(); - modCount += size; - modCount++; - return numNew != 0; + size += c.size + modCount += size + modCount++ + return numNew != 0 } /** @@ -502,29 +504,28 @@ public boolean addAll(Collection c) { * returned by the specified Collection's iterator. * * @param index index at which to insert first element from the specified - * collection. + * collection. * @param c elements to be inserted into this list. * @return true if this list changed as a result of the call. * @throws IndexOutOfBoundsException if index out of range (index - * < 0 || index > size()). + * < 0 || index > size()). * @throws NullPointerException if the specified Collection is null. */ - @Override - public boolean addAll(int index, Collection c) { + override fun addAll(index: Int, c: Collection<*>): Boolean { if (index > size || index < 0) - throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + throw IndexOutOfBoundsException("Index: $index, Size: $size") - Object[] a = c.toArray(); - int numNew = c.size(); // 20080121 KSC: a.length; - ensureCapacity(size + numNew); // Increments modCount + val a = c.toTypedArray() + val numNew = c.size // 20080121 KSC: a.length; + ensureCapacity(size + numNew) // Increments modCount - int numMoved = size - index; + val numMoved = size - index if (numMoved > 0) - System.arraycopy(elementData, index, elementData, index + numNew, numMoved); + System.arraycopy(elementData, index, elementData, index + numNew, numMoved) - System.arraycopy(a, 0, elementData, index, numNew); - size += numNew; - return numNew != 0; + System.arraycopy(a, 0, elementData, index, numNew) + size += numNew + return numNew != 0 } /** @@ -537,16 +538,15 @@ public boolean addAll(int index, Collection c) { * @param fromIndex index of first element to be removed. * @param toIndex index after last element to be removed. */ - @Override - protected void removeRange(int fromIndex, int toIndex) { - modCount++; - int numMoved = size - toIndex; - System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); + override fun removeRange(fromIndex: Int, toIndex: Int) { + modCount++ + val numMoved = size - toIndex + System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved) // Let gc do its work - int newSize = size - (toIndex - fromIndex); + val newSize = size - (toIndex - fromIndex) while (size != newSize) - elementData[--size] = null; + elementData[--size] = null } /** @@ -555,64 +555,73 @@ protected void removeRange(int fromIndex, int toIndex) { * always used immediately prior to an array access, which throws an * ArrayIndexOutOfBoundsException if index is negative. */ - private void RangeCheck(int index) { + private fun RangeCheck(index: Int) { if (index >= size) - throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + throw IndexOutOfBoundsException("Index: $index, Size: $size") } - class Itr implements Iterator { + internal inner class Itr : Iterator { /** * Index of element to be returned by subsequent call to next. */ - int cursor = 0; + var cursor = 0 /** * Index of element returned by most recent call to next or previous. Reset to * -1 if this element is deleted by a call to remove. */ - int lastRet = -1; + var lastRet = -1 /** * The modCount value that the iterator believes that the backing List should * have. If this expectation is violated, the iterator has detected concurrent * modification. */ - int expectedModCount = modCount; + var expectedModCount = modCount - public boolean hasNext() { - return cursor != size; + override fun hasNext(): Boolean { + return cursor != size } - public Object next() { + override fun next(): Any { // checkForComodification(); try { - Object next = elementData[cursor]; - lastRet = cursor++; - return next; - } catch (IndexOutOfBoundsException e) { + val next = elementData[cursor] + lastRet = cursor++ + return next + } catch (e: IndexOutOfBoundsException) { // checkForComodification(); - throw new NoSuchElementException(); + throw NoSuchElementException() } + } - public void remove() { + override fun remove() { if (lastRet == -1) - throw new IllegalStateException(); + throw IllegalStateException() // checkForComodification(); try { - SpecialArrayList.this.remove(lastRet); + this@SpecialArrayList.removeAt(lastRet) if (lastRet < cursor) - cursor--; - lastRet = -1; - expectedModCount = modCount; - } catch (IndexOutOfBoundsException e) { - throw new ConcurrentModificationException(); + cursor-- + lastRet = -1 + expectedModCount = modCount + } catch (e: IndexOutOfBoundsException) { + throw ConcurrentModificationException() } + } - final void checkForComodification() { + fun checkForComodification() { // NOT! } } -} \ No newline at end of file + + companion object { + private const val serialVersionUID = 8683452581122892189L + } +} +/** + * Constructs an empty list with an initial capacity of ten. + */ \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/StringTool.java b/src/main/java/io/starter/toolkit/StringTool.java deleted file mode 100644 index b56e11c..0000000 --- a/src/main/java/io/starter/toolkit/StringTool.java +++ /dev/null @@ -1,1404 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.io.*; -import java.nio.CharBuffer; -import java.util.StringTokenizer; - -/** - * A collection of useful methods for manipulating Strings. - */ -public class StringTool implements Serializable { - - // static final long serialVersionUID = - // -5757918511951798619l; - static final long serialVersionUID = -2761264230959133529l; - - /** - * replace illegal XML characters with their html counterparts - *

                      - * ie: "&" is converted to "&" - *

                      - * check out the w3 list of XML - * characters - * - * @param rep - * @return - */ - public static String convertXMLChars(String rep) { - return io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(rep) - .toString(); // 20110815 KSC: this method is more complete - } - - // test stuff - public static void main(String[] args) { - - String majorHTML = "Testing yes

                      • item1
                      • item2
                      • item3

                      newline
                      newline2
                      newline3 yes no 124<>something
                      newline4bold"; - - Logger.logInfo(StringTool.stripHTML(majorHTML)); - - } - - /** - * strip out all most HTML tags - * - * @param rep - * @return a string stripped of all html tags - */ - public static String stripHTML(String rep) { - - // first convert newlines - rep = rep.replaceAll("<[B,b][R,r]?>", "\r\n"); - rep = rep.replaceAll("<[B,b][R,r]?/>", "\r\n"); - - rep = rep.replaceAll("<[L,l][I,i]?>", "\r\n\r\n"); - - StringBuffer ret = new StringBuffer(); - char[] cx = rep.toCharArray(); - boolean skipping = false; - for (int t = 0; t < cx.length; t++) { - char tt = cx[t]; - // begin match - if (tt == '<') { - skipping = true; - t++; - } else if (tt == '>') { - skipping = false; - } - if (!skipping && tt != '>') - ret.append(cx[t]); - - } - return ret.toString(); - } - - /** - * replace endoded text with normal text ie: "&" is converted to "&" - *

                      - * check out the w3 list of XML - * characters - * - * @param rep - * @return - */ - public static String convertHTML(String rep) { - // if(true)return rep; - rep.replaceAll("&", "&"); - rep.replaceAll("'", "'"); - rep.replaceAll(""", "\""); - rep.replaceAll("<", "<"); - rep.replaceAll(">", ">"); - rep.replaceAll("©", ""); - return rep; - } - - /** - * If the string matches any part of the pattern, strip the pattern from the - * string. - */ - public static String stripMatch(String pattern, String matchstr) { - String upat = pattern.toUpperCase(); - String umat = matchstr.toUpperCase(); - String retval = ""; - if (umat.lastIndexOf(upat) > -1) { - int pos = umat.lastIndexOf(upat) + upat.length(); - retval = matchstr.substring(pos); - Logger.logInfo("foundpos: " + pos); - } - return retval; - } - - /** - * get the variable name for a "getXXXX" a field name per JavaBean java - * naming conventions. - *

                      - * ie: Converts "getFirstName" to "firstName" - */ - public static String getVarNameFromGetMethod(String thismethod) { - int getidx = thismethod.indexOf("get"); - if (getidx < 0) - return ""; - String retval = thismethod.substring(getidx + 3); - retval = retval.substring(0, retval.length() - 2); - String upcase = retval.substring(0, 1); - upcase = upcase.toUpperCase(); - retval = retval.substring(1); - retval = upcase + retval; - return retval; - } - - /** - * converts java member naming convention to underscored DB-style naming - * convention - *

                      - * ie: take upperCamelCase and turn into upper_camel_case - */ - public static String convertJavaStyletoDBConvention(String name) { - char[] chars = name.toCharArray(); - StringBuffer buf = new StringBuffer(); - for (int i = 0; i < chars.length; i++) { - // if there is a single upper-case letter, then it's a - // case-word - if (Character.isUpperCase(chars[i])) { - if ((i > 0) && (i + 1 < chars.length)) { - if (!Character.isUpperCase(chars[i])) - buf.append("_"); - } - buf.append(chars[i]); - } else { - buf.append(Character.toUpperCase(chars[i])); - } - } - return buf.toString(); - } - - /** - * converts java member naming convention to underscored DB-style naming - * convention - *

                      - * ie: take upperCamelCase and turn into upper_camel_case - */ - public static String convertJavaStyletoFriendlyConvention(String name) { - if (name.equals("")) - return ""; - StringBuffer buf = new StringBuffer(); - char[] chars = name.toCharArray(); - buf.append(String.valueOf(chars[0]).toUpperCase()); - for (int i = 1; i < chars.length; i++) { - if (chars[i] == '_') { - chars[i++] = Character.toUpperCase(chars[i + 1]); - buf.append(" "); - buf.append(chars[i]); - } else { - buf.append(String.valueOf(chars[i]).toLowerCase()); - } - } - return buf.toString(); - } - - /** - * convert an Array to a String representation of its objects - * - * @param name - * @return - */ - public static String arrayToString(Object[] objs) { - StringBuffer ret = new StringBuffer("["); - for (int x = 0; x < objs.length; x++) { - ret.append(objs[x].toString()); - ret.append(", "); - } - ret.setLength(ret.length() - 1); - ret.append("]"); - return ret.toString(); - } - - /** - * Returns the given throwable's stack trace as a string. - * - * @param target the Throwable whose stack trace should be returned - * @return the stack trace of the given Throwable as a String - * @throws NullPointerException if target is null - */ - public static final String stackTraceToString(Throwable target) { - StringWriter trace = new StringWriter(); - PrintWriter writer = new PrintWriter(trace); - - target.printStackTrace(writer); - writer.flush(); - - return trace.toString(); - } - - /** - * converts strings to "proper" capitalization - *

                      - * ie: take "mr. fraNK sMITH" and turn into "Mr. Frank Smith" - */ - public static String proper(String name) { - if (name.equals("")) - return ""; - StringBuffer buf = new StringBuffer(); - char[] chars = name.toCharArray(); - buf.append(String.valueOf(chars[0]).toUpperCase()); - for (int i = 1; i < chars.length; i++) { - if (chars[i] == ' ') { - buf.append(" "); - i++; - if (chars[i] != ' ') { - chars[i] = Character.toUpperCase(chars[i]); - buf.append(chars[i]); - } - } else { - buf.append(String.valueOf(chars[i]).toLowerCase()); - } - } - return buf.toString(); - } - - /** - * Each object in the array must support the .toString() method, as it will - * be used to render the object to its string representation. - */ - - public static String makeDelimitedList(Object[] list, String delimiter) { - StringBuffer listBuf = new StringBuffer(); - for (int i = 0; i < list.length; i++) { - if (i != 0) - listBuf.append(delimiter); - listBuf.append(list[i].toString()); - } - return listBuf.toString(); - } - - /** - * Returns an array of strings from a single string, similar to - * String.split() in JavaScript - */ - public static String[] splitString(String value, String delimeter) { - - StringTokenizer stoken = new StringTokenizer(value, delimeter); - String[] returnValue = new String[stoken.countTokens()]; - int i = 0; - while (stoken.hasMoreTokens()) { - returnValue[i] = stoken.nextToken(); - i++; - } - return returnValue; - } - - /** - * get compressed UNICODE string from uncompressed string - */ - public static String getCompressedUnicode(byte[] input) { - byte[] output = new byte[input.length / 2]; - int pos = 0; - for (int i = 0; i < input.length; i++) { - output[pos++] = input[i]; - i++; - } - return new String(output); - } - - public static String getLowerCaseFirstLetter(String thismember) { - String upcase = thismember.substring(0, 1); - upcase = upcase.toLowerCase(); - thismember = thismember.substring(1); - thismember = upcase + thismember; - return thismember; - } - - /** - * generate a "setXXXX" string from a field name per Extentech java naming - * conventions. - *

                      - * ie: Converts "firstName" to "setFirstName" - */ - public static String getSetMethodNameFromVar(String thismember) { - return "set" + getUpperCaseFirstLetter(thismember); - } - - public static String getUpperCaseFirstLetter(String thismember) { - String upcase = thismember.substring(0, 1); - upcase = upcase.toUpperCase(); - thismember = thismember.substring(1); - thismember = upcase + thismember; - return thismember; - } - - /** - * generate a "getXXXX" string from a field name per Extentech java naming - * conventions. - *

                      - * ie: Converts "firstName" to "getFirstName" - */ - public static String getGetMethodNameFromVar(String thismember) { - return "get" + getUpperCaseFirstLetter(thismember); - } - - /** - * converts underscored DB-style naming convention to java member naming - * convention - */ - public static String convertDBtoJavaStyleConvention(String name) { - int scoreloc = name.indexOf("_"); - StringBuffer buf = new StringBuffer(); - if (scoreloc < 0) - return name; - else { - char[] chars = name.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (chars[i] == '_') { - chars[i + 1] = Character.toUpperCase(chars[i + 1]); - } else { - buf.append(chars[i]); - } - } - } - return buf.toString(); - } - - /** - * converts underscored DB-style naming convention to java member naming - * convention - */ - public static String convertFilenameToJSPName(String name) { - StringBuffer buf = new StringBuffer(); - char[] chars = name.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (chars[i] == '_') { - chars[i + 1] = Character.toUpperCase(chars[i + 1]); - } else if (chars[i] == '-') { - chars[i + 1] = Character.toUpperCase(chars[i + 1]); - } else if (chars[i] == ' ') { - chars[i + 1] = Character.toUpperCase(chars[i + 1]); - } else { - buf.append(chars[i]); - } - } - return buf.toString(); - } - - /** - * Basically a String tokenizer - * - * @param instr - * @param token - * @return - */ - public static String[] getTokensUsingDelim(String instr, String token) { - if (instr.indexOf(token) < 0) { - String[] ret = new String[1]; - ret[0] = instr; - return ret; - } - CompatibleVector output = new CompatibleVector(); - new StringBuffer(); - int lastpos = 0, offset = 0; - int toklen = token.length(); - int pos = instr.indexOf(token); - // pos--; - while (pos > -1) { - if (lastpos > 0)// if the line starts with a token - offset = lastpos + toklen; - else - offset = 0; - String st = instr.substring(offset, pos); - output.add(st); - lastpos = pos; - pos = instr.indexOf(token, lastpos + 1); - } - if (lastpos < instr.length()) { - String st = instr.substring(lastpos + toklen); - output.add(st); - } - String[] retval = new String[output.size()]; - for (int i = 0; i < output.size(); i++) - retval[i] = (String) output.get(i); - return retval; - } - - // escaped slashes - String oneslash = String.valueOf((char) 0x005C); - String twoslash = oneslash + oneslash; - - /** - * - */ - public static String dbencode(String holder) { - return replaceText(holder, "'", "''", 0); - } - - /** - * Lose the whitespace at the end of strings... - * - * @param holder The String that you want stripped. - * @return Your stripped string. - */ - - public static String allTrim(String holder) { - holder = holder.trim(); - return rTrim(holder); - } - - /** - * strip trailing spaces - */ - public static String stripTrailingSpaces(String s) { - while (s.endsWith(" ")) - s = s.substring(0, s.length() - 1); - return s; - } - - /** - * Strips all occurences of a string from a given string. - * - * @param tostrip The String that you want stripped. - * @param stripchar The char you want stripped from the String. - * @return Your stripped string. - */ - - public static String strip(String tostrip, String stripstr) { - StringBuffer stripped = new StringBuffer(tostrip.length()); - while (tostrip.indexOf(stripstr) > -1) { - stripped.append(tostrip, 0, tostrip.indexOf(stripstr)); - tostrip = tostrip - .substring(tostrip.indexOf(stripstr) + stripstr.length()); - } - stripped.append(tostrip); - return (stripped.toString()); - } - - /** - * Strips all occurences of a character from a given string. - * - * @param tostrip The String that you want stripped. - * @param stripchar The char you want stripped from the String. - * @return Your stripped string. - */ - - public static String strip(String tostrip, char stripchar) { - StringBuffer stripped = new StringBuffer(tostrip.length()); - int i = 0; - char currentChar; - while (i < tostrip.length()) { - currentChar = tostrip.charAt(i); - if (currentChar == stripchar) { - i++; - } else { - stripped.append(currentChar); - i++; - } - } - return (stripped.toString()); - } - - /** - * Replaces an occurence of String B with String C within String A. This - * method is case sensitive. - *

                      - * Example: String A = "I am a happy dog."; String A = - * stringtool.replaceText(A, "happy", "sad", 0); - *

                      - * The result is A="I am a sad dog." - * - * @param originalText Original text - * @param replaceText Text to replace. - * @param replacementText Text to replace with. - * @param offset offset of replacement within original string. - * @return Processed text. - */ - public static String replaceText(String originalText, String replaceText, String replacementText, int offset, boolean skipmatch) { - if (!skipmatch) - return replaceText(originalText, replaceText, replacementText, offset); - - StringBuffer sb = new StringBuffer(); - if (originalText.indexOf(replaceText) < 0) { - return originalText; - } else { - int nextidx = 0, lastidx = 0, pos = 0; - int textlen = replaceText.length(); - int stringlen = originalText.length(); - - while (nextidx <= originalText.lastIndexOf(replaceText)) { - pos++; - nextidx = originalText.indexOf(replaceText, lastidx); - sb.append(originalText, lastidx, nextidx); - if (pos > offset) - sb.append(replacementText); - else - sb.append(replaceText); - nextidx += textlen; - if (textlen == 0) - break;// case of "" - lastidx = nextidx; - } - if (nextidx < stringlen) { - sb.append(originalText.substring(nextidx)); - } - return sb.toString(); - } - } - - /** - * Replaces an occurence of String B with String C within String A. This - * method is case sensitive. - *

                      - * Example: String A = "I am a happy dog."; String A = - * stringtool.replaceText(A, "happy", "sad", 0); - *

                      - * The result is A="I am a sad dog." - * - * @param originalText Original text - * @param replaceText Text to replace. - * @param replacementText Text to replace with. - * @param offset offset of replacement within original string. - * @return Processed text. - */ - public static String replaceText(String originalText, String replaceText, String replacementText, int offset) { - - int newlen = originalText.length() - replaceText.length() - + replacementText.length(); - - if (newlen < 1) - newlen = 0; - StringBuffer sb = new StringBuffer(newlen); - if (originalText.indexOf(replaceText) < 0) { - return originalText; - } else { - if (replaceText != null && replaceText.equals(replacementText)) - return originalText; // avoid infinite loops - int nextidx = 0, lastidx = 0; - int textlen = replaceText.length(); - int stringlen = originalText.length(); - while (nextidx <= originalText.lastIndexOf(replaceText)) { - nextidx = originalText.indexOf(replaceText, lastidx); - sb.append(originalText, lastidx, nextidx + offset); - sb.append(replacementText); - nextidx += textlen; - if (textlen == 0) - break; // case of "" - lastidx = nextidx; - } - if (nextidx < stringlen) { - sb.append(originalText.substring(nextidx)); - } - return sb.toString(); - } - } - - /** - * Trims whitespace from the right side of strings. - * - * @param originalText Text to trim. - * @return Trimmed text. - */ - public static String rTrim(String originalText) { - StringBuffer sb = new StringBuffer(originalText); - sb.reverse(); - String rstr = sb.toString(); - rstr.trim(); - sb = new StringBuffer(rstr); - sb.reverse(); - return sb.toString(); - } - - /** - * This method will retrieve the first instance of text between any two - * given patterns. This method is case sensitive. - *

                      - * Example: - *

                      - * String A = "I am a happy dog."; B = getTextBetweenDelims(A,"a","."); - *

                      - * B is now equal to "m a happy dog". C declines comment. - * - * @param originalText Text to process. - * @param beginDelim Delimeter for beginning of retrieved section. - * @param endDelim Delimeter for end of retrieved section. - * @return Text between delims or "" if not found. - */ - public static String getTextBetweenNestedDelims(String originalText, String beginDelim, String endDelim) { - StringBuffer sb = new StringBuffer(originalText.length()); - // Check to see that both delimiters exist in the string - if ((originalText.indexOf(beginDelim) < 0) - || (originalText.lastIndexOf(endDelim) < 0)) { - return ""; - } else { - int begidx = originalText.indexOf(beginDelim) + beginDelim.length(); - int endidx = originalText.lastIndexOf(endDelim); - int holder = 0; - if (begidx < endidx) { - sb.append(originalText, begidx, endidx); - } else { - while ((begidx > endidx) && (endidx > -1)) { - holder = endidx; - endidx = originalText.lastIndexOf(endDelim, holder + 1); - } - if ((begidx < endidx) && (endidx > -1)) - sb.append(originalText, begidx, endidx); - } - return sb.toString(); - } - } - - /** - * This method will retrieve the first instance of text between any two - * given patterns. This method is case sensitive. - *

                      - * Example: - *

                      - * String A = "I am a happy dog."; B = getTextBetweenDelims(A,"a","."); - *

                      - * B is now equal to "m a happy dog". C declines comment. - * - * @param originalText Text to process. - * @param beginDelim Delimeter for beginning of retrieved section. - * @param endDelim Delimeter for end of retrieved section. - * @return Text between delims or "" if not found. - */ - public static String getTextBetweenDelims(String originalText, String beginDelim, String endDelim) { - StringBuffer sb = new StringBuffer(originalText.length()); - // Check to see that both delimiters exist in the string - if ((originalText.indexOf(beginDelim) < 0) - || (originalText.indexOf(endDelim) < 0)) { - return ""; - } else { - int begidx = originalText.indexOf(beginDelim) + beginDelim.length(); - int endidx = originalText.indexOf(endDelim); - int holder = 0; - if (begidx < endidx) { - sb.append(originalText, begidx, endidx); - } else { - while ((begidx > endidx) && (endidx > -1)) { - holder = endidx; - endidx = originalText.indexOf(endDelim, holder + 1); - } - if ((begidx < endidx) && (endidx > -1)) - sb.append(originalText, begidx, endidx); - } - return sb.toString(); - } - } - - /** - * This method will replace any instance of given text within another - * string. This method is case sensitive. - *

                      - * Example: - *

                      - * String A = "I am a happy dog."; A = - * replaceSection(A,"happy","hippie cat", "dog"); - *

                      - * A is now equal to "I am a hippie cat.". - * - * @param originalText Text to process. - * @param replaceBegin Beggining pattern of replaced section. - * @param replacementText Text to replace with. - * @param replaceEnd End pattern of replaced section. - * @return Processed text. - */ - - public static String replaceSection(String originalText, String replaceBegin, String replacementText, String replaceEnd) { - StringBuffer sb = new StringBuffer(originalText.length()); - if ((originalText.indexOf(replaceBegin) < 0) - || (originalText.indexOf(replaceEnd) < 0)) { - return originalText; - } else { - int begidx = originalText.indexOf(replaceBegin); - int endlen = replaceEnd.length(); - int endidx = originalText.indexOf(replaceEnd) + endlen; - int holder = 0; - if (begidx < endidx) { - sb.append(originalText, 0, begidx); - sb.append(replacementText); - sb.append(originalText.substring(endidx)); - } else { - while ((begidx > endidx) && (endidx > -1)) { - holder = endidx; - endidx = originalText.indexOf(replaceEnd, holder + 1); - } - if ((begidx < endidx) && (endidx > -1)) { - sb.append(originalText, 0, begidx); - sb.append(replacementText); - sb.append(originalText.substring(endidx + endlen)); - } - } - return sb.toString(); - } - } - - public static String StripChars(String theFilter, String theString) { - StringBuffer strOut = new StringBuffer(theString.length()); - char curChar; - for (int i = 0; i < theString.length(); i++) { - curChar = theString.charAt(i); - if (theFilter.indexOf(curChar) < 0) { // if it's not in the filter, - // send it thru - strOut.append(curChar); - } - } - return strOut.toString(); - } - - public static String UseOnlyChars(String theFilter, String theString) { - StringBuffer strOut = new StringBuffer(theString.length()); - char curChar; - for (int i = 0; i < theString.length(); i++) { - curChar = theString.charAt(i); - if (theFilter.indexOf(curChar) > -1) { // if it's in the filter, - // send it thru - strOut.append(curChar); - } - } - return strOut.toString(); - } - - public static String replaceChars(String theFilter, String theString, String replacement) { - StringBuffer strOut = new StringBuffer(theString.length()); - char curChar; - for (int i = 0; i < theString.length(); i++) { - curChar = theString.charAt(i); - if (theFilter.indexOf(curChar) < 0) { // if it's not in the filter, - // send it thru - strOut.append(curChar); - } else { - strOut.append(replacement); - } - } - return strOut.toString(); - } - - /** - * replace a section of text based on pattern match throughout string. - */ - public static String replaceText(String theString, String theFilter, String replacement) { - return replaceText(theString, theFilter, replacement, 0); - } - - public static boolean AllInRange(int x, int y, String theString) { - char curChar; - for (int i = 0; i < theString.length(); i++) { - curChar = theString.charAt(i); - if (curChar < x || curChar > y) { - return false; - } - } - return true; - } - - /** - * Replaces a specified token in a string with a value from the passed - * through array this is done in matching order from String to Arrray. - */ - public static String replaceTokenFromArray(String replace, String token, String[] vals) { - StringBuffer sb = new StringBuffer(); - StringTokenizer toke = new StringTokenizer(replace, token, false); - int i = 0; - // add the first element - while (toke.hasMoreTokens()) { - sb.append(toke.nextToken()); - if (i <= vals.length - 1) { - sb.append(vals[i]); - ++i; - } - } - return String.valueOf(sb); - } - - /** - * returns file path fPath qualified with an ending slash - * - * @param fPath - * @return fPath with an ending slash - */ - public static String qualifyFilePath(String fPath) { - StringTool.replaceChars("\\", fPath, "/"); - fPath = fPath.trim(); - if (!fPath.endsWith("/")) - fPath += "/"; - return fPath; - } - - /** - * splits a filepath into directory and filename - * - * @param filePath - * @return - */ - public static String[] splitFilepath(String filePath) { - String[] path = new String[2]; - filePath = StringTool.replaceText(filePath, "\\", "/"); - int lastpath = filePath.lastIndexOf("/"); - if (lastpath > -1) { // strip path and directory - path[0] = filePath.substring(0, lastpath + 1); // get directory - path[1] = filePath.substring(lastpath + 1); // strip directory from - // filename - } else - path[1] = filePath; - return path; - } - - /** - * strips the path portion from a filepath and returns the filename - * - * @param filePath - * @return - */ - public static String stripPath(String filePath) { - filePath = StringTool.replaceText(filePath, "\\", "/"); - int lastpath = filePath.lastIndexOf("/"); - if (lastpath > -1) - return filePath.substring(lastpath + 1); // strip directory from - // filename - - return filePath; - } - - /** - * strips the path portion from a filepath and returns it - * - * @param filePath - * @return - */ - public static String getPath(String filePath) { - filePath = StringTool.replaceText(filePath, "\\", "/"); - int lastpath = filePath.lastIndexOf("/"); - return filePath.substring(0, lastpath + 1); - } - - /** - * replaces the extension of a filepath with an new extension - * - * @param filepath Source FilePaht - * @param ext - * @return - */ - public static String replaceExtension(String filepath, String ext) { - int i = filepath.lastIndexOf("."); - String f = filepath; - if (i > 0) - f = filepath.substring(0, i) + ext; - else - f = filepath + ext; - return f; - } - - /** - * given a string, return the maximum of the width in pixels in the given - * the awt Font.
                      - * NOTE: this method does not account for line feeds contained within - * strings - * - * @param f awt Font - * @param s String to compute - * @return double approximate width in pixels - */ - public static double getApproximateStringWidth(java.awt.Font f, String s) { - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() - .getFontMetrics(f); - /* - * width/height in pixels = (w/h field) * DPI of the display - * device / 72 - */ - double conversion = java.awt.Toolkit.getDefaultToolkit() - .getScreenResolution() / 72.0; - return fm.stringWidth(s) * conversion; // pixels * conversion - } - - /** - * given a string, return the maximum of the width in pixels in the given - * the awt Font Observing Line Breaks.
                      - * - * @param f awt Font - * @param s String to compute - * @return double approximate width in pixels - */ - public static double getApproximateStringWidthLB(java.awt.Font f, String s) { - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() - .getFontMetrics(f); - /* - * width/height in pixels = (w/h field) * DPI of the display - * device / 72 - */ - double conversion = java.awt.Toolkit.getDefaultToolkit() - .getScreenResolution() / 72.0; - String[] ss = s.split("\n"); - double len = 0; - for (String st : ss) { - len = Math.max(len, fm.stringWidth(st) * conversion); - } - // return fm.stringWidth(s) * conversion; - return len; - } - - /** - * return the approximate witdth in width in pixels of the given character - * - * @param f awt Font - * @param c character - * @return double approximate width in pixels - */ - public static double getApproximateCharWidth(java.awt.Font f, Character c) { - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() - .getFontMetrics(f); - /* - * width/height in pixels = (w/h field) * DPI of the display - * device / 72 - */ - double conversion = java.awt.Toolkit.getDefaultToolkit() - .getScreenResolution() / 72.0; - return fm.charWidth(c) * conversion; - } - - /** - * return the approximate height it takes to display the given string in the - * given font in the given width - * - * @param f java.awt.Font - * @param s String - * @param w width in points - * @return - */ - public static double getApproximateHeight(java.awt.Font f, String s, double w) { - double len = StringTool.getApproximateStringWidth(f, s); - while (len > w) { - int lastSpace = -1; - int j = s.lastIndexOf("\n") + 1; - len = 0; - while (len < w && j < s.length()) { - len += StringTool.getApproximateCharWidth(f, s.charAt(j)); - if (s.charAt(j) == ' ') - lastSpace = j; - j++; - } - if (len < w) { - break; // got it - } - if (lastSpace == -1) { // no spaces to break apart - if (s.indexOf(' ') == -1) - break; - lastSpace = s.lastIndexOf(' '); // break at - } - s = s.substring(0, lastSpace) + "\n" + s.substring(lastSpace + 1); - } - int nl = s.split("\n").length; - java.awt.FontMetrics fm = java.awt.Toolkit.getDefaultToolkit() - .getFontMetrics(f); - java.awt.font.LineMetrics lm = f - .getLineMetrics(s, fm.getFontRenderContext()); - // this calc appears to match Excel's ... - float l = lm.getLeading(); - // float h= lm.getHeight(); - // io.starter.toolkit.Logger.log("Font: " + - // f.toString()); - // io.starter.toolkit.Logger.log("l-i:" + - // fm.getLeading() + " l:" + l + " h-i:" + fm.getHeight() + - // " h:" + h + " a-i:" + fm.getAscent() + " a:" + - // lm.getAscent() + " d-i:" + fm.getDescent() + " d:" + - // lm.getDescent()); - float h = fm.getHeight(); // KSC: revert for now ... - l/3; // i don't - // know why but this seems to match Excel's - // the closest - return Math.ceil(h * (nl));// +1)); // KSC: added + 1 for testing - } - - /** - * converts an excel-style custom format to String.format custom format i.e. - * %flags-width-precision-conversion - *

                      NOTE: the pattern should be a single item in the excel-style format i.e.one of the terms in positive;negative;zero;text - * without semicolon. - *
                      NOTE: the java-specific pattern returned will have the negative formatting (sign, parenthesis ...) and so, when used, - * the double value must be it's absolute value i.e String.format(pattern, Math.abs(d)) - *
                      NOTE: date formats are handled separately. this only applies to number and currency formats - *
                      Excel-style: - * 0 (zero) Digit placeholder. This code pads the value with zeros to fill the format. - * # Digit placeholder. This code does not display extra zeros. - * ? Digit placeholder. This code leaves a space for insignificant zeros but does not display them. - * . (period) Decimal number. - * % Percentage. Microsoft Excel multiplies by 100 and adds the % character. - * , (comma) Thousands separator. A comma followed by a placeholder scales the number by a thousand. - * E+ E- e+ e- Scientific notation. - * Text Code Description - * $ - + / ( ) : space These characters are displayed in the number. To display any other character, enclose the character in quotation marks or precede it with a backslash. - * \character This code displays the character you specify. - * "text" This code displays text. - * This code repeats the next character in the format to fill the column width. Note Only one asterisk per section of a format is allowed. - * _ (underscore) This code skips the width of the next character. - * This code is commonly used as "_)" (without the quotation marks) - * to leave space for a closing parenthesis in a positive number format - * when the negative number format includes parentheses. - * This allows the values to line up at the decimal point. - * - * @param pattern String format pattern in Excel format - * @param isNegative true if the source is a negative number - * @return - * @ Text placeholder. - */ - public static String convertPatternFromExcelToStringFormatter(String pattern, boolean isNegative) { - String curPattern = pattern; - String jpattern = ""; // return pattern - int w = 0; - int precision = 0; - String flags = ""; - char conversion = 'f'; // default - boolean inConversion = false; - boolean inPrecision = false; - boolean removeSign = false; // true if value is negative and pattern - // calls for parens or color change or ... - // i.e. don't display the negative sign - /* - * TODO: \ uXXX is Locale-specific to display? works - * manually ... - * TODO; finish fractional formats: ?/? - */ - for (int i = 0; i < curPattern.length(); i++) { - int c = curPattern.charAt(i); - switch (c) { - case '0': - w++; - if (!inConversion) { - jpattern += "%"; - inConversion = true; - } - if (inPrecision && conversion != 'E') - precision++; - break; - case '?': // don't really know what to do with this one! - break; - case '#': - if (!inConversion) { - jpattern += "%"; - inConversion = true; - } - // TODO: handle such as: ###0.00######### --- what's the - // format spec for that????? - // if (inPrecision) precision++; - break; - case ',': - flags += ","; - break; - case '.': - inPrecision = true; - break; - case 'E': - case 'e': - if (!inConversion) { - jpattern += "%"; - inConversion = true; - } - conversion = 'E'; - i++; // format is e+, E+, e- or E- - break; - case '[': // either color code or local-specific formatting - int j = ++i; - int k = j; - for (; i < curPattern.length(); i++) { // skip colors for now - c = curPattern.charAt(i); - if (c == '-') // got end of an extended char sequence - skip - // rest (Locale code ...) - k = i; - if (c == ']') - break; - } - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - if (k == j) // then it was a color string - removeSign = true; - else // it was a locale-specific string ... - jpattern += curPattern.substring(++j, k); - break; - case '"': // start of delimited text - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - for (i++; i < curPattern.length(); i++) { - c = curPattern.charAt(i); - if (c == '"') - break; - jpattern += (char) c; - - } - break; - // ignore - case '@': // text placeholder - jpattern += "%s"; - break; - case '*': // repeats the next char to fill -- IGNORE!!! - break; - case '(': // enclose negative #'s in parens - case ')': - // flags+="("; - if (isNegative) { - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - jpattern += (char) c; - removeSign = true; - } - break; - case '_': // skips the width of the next char - usually _) - to - // leave space for a closing parenthesis in a positive - // number format when the negative number format - // includes parentheses. This allows the values to line - // up at the decimal point. - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - i++; // skip next char -- true in all cases??? - break; - case '%': - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - jpattern += "%%"; - break; - case '\\': - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - int z; - if (i + 1 < curPattern.length() - && curPattern.charAt(i + 1) == 'u') - z = i + 6; - else - z = i + 1; - for (; i < z && i < curPattern.length(); i++) - jpattern += curPattern.charAt(i); - break; - default: // %, $, - space -- keep - if (inConversion) { - inConversion = false; - inPrecision = false; - jpattern += flags + (w > 0 ? w : "") + "." + precision - + conversion; - } - jpattern += (char) c; - break; - } - } - if (inConversion) { - jpattern += flags + (w > 0 ? w : "") + "." + precision + conversion; - } - if (isNegative && !removeSign) - jpattern = "-" + jpattern; - // System.out.print("Original Pattern " + pattern + " new " - // + jpattern); - // patterns[z]= jpattern; - pattern = jpattern; - return pattern; - } - - public static String convertDatePatternFromExcelToStringFormatter(String pattern) { - String jpattern = ""; // return pattern - String dString = ""; // d string -- ddd ==> EEE and dddd ==> EEEE - String mString = ""; // m string -- either month (M, MM, MMM, MMMM) or - // minute - int prev = 0; - for (int i = 0; i < pattern.length(); i++) { - int c = pattern.charAt(i); - if (c != 'd' && !dString.equals("")) { - if (dString.length() <= 2) - jpattern += dString; - else if (dString.length() == 3) - jpattern += "EEE"; - else if (dString.length() == 4) - jpattern += "EEEE"; - dString = ""; - } else if (c != 'm' && !mString.equals("")) { - if (c == ':' || prev == 'h') { // it's time - jpattern += mString; - prev = c; - } else - jpattern += mString.toUpperCase(); - mString = ""; - } - - switch (c) { - case 'y': - jpattern += (char) c; - break; - case 'h': - jpattern += 'H'; // h in java is 1-24 excel h= 0-23 - prev = 'h'; - break; - case '\\': // found case of erroneous use of backslash, as in: - // mm\-dd\-yy ignore! - case '[': // no java equivalent of [h] [m] or [ss] == elapsed time - case ']': - break; - case 's': - jpattern += (char) c; - break; - case 'A': - if (pattern.substring(i, i + 5).equals("AM/PM")) { - jpattern += "a"; - i += 5; - for (int z = jpattern.length() - 2; z >= 0; z--) { - if (jpattern.charAt(z) == 'H') { - jpattern = jpattern.substring(0, z) + 'h' - + jpattern.substring(z + 1); - } - } - } - break; - case 'd': - dString += (char) c; - break; - case 'm': - mString += (char) c; - break; - default: - if (c != ':' && c != 'm') - prev = c; - jpattern += (char) c; - } - } - if (!mString.equals("")) { - if (prev == 'h') // it's time - jpattern += mString; - else - jpattern += mString.toUpperCase(); // remaining month string - } else if (!dString.equals("")) { - if (dString.length() <= 2) - jpattern += dString; - else if (dString.length() == 3) - jpattern += "EEE"; - else if (dString.length() == 4) - jpattern += "EEEE"; - dString = ""; - } - return jpattern; - } - - /** - * extract info, if any, from bracketed expressions within Excel custom number formats - * - * @param pattern String Excel number format - * @return String returned number format without the bracketed expression - */ - public static String convertPatternExtractBracketedExpression(String pattern) { - String[] s = pattern.split("\\["); - if (s.length > 1) { - pattern = ""; - for (int i = 0; i < s.length; i++) { - int zz = s[i].indexOf("]"); - if (zz != -1) { - String term = ""; - if (s[i].charAt(0) == '$') - term = s[i].substring(1, zz); // skip first $ - else - term = s[i].substring(0, zz); - if (term.indexOf("-") != -1) // extract character TODO: - // locale specifics - pattern += term.substring(0, term.indexOf("-")); - else - pattern += term; - } - pattern += s[i].substring(zz + 1); - } - } - return pattern; - } - - /** - * qualifies a pattern string to make valid for applying the pattern - * - * @param pattern - * @return - */ - public static String qualifyPatternString(String pattern) { - pattern = StringTool.strip(pattern, "*"); - pattern = StringTool.strip(pattern, "_("); // width placeholder - pattern = StringTool.strip(pattern, "_)"); // width placeholder - pattern = StringTool.strip(pattern, "_"); - pattern = pattern.replaceAll("\"", ""); - pattern = StringTool.strip(pattern, "?"); - // there are more bracketed expressions to deal with - // see - // http://office.microsoft.com/en-us/excel-help/creating-international-number-formats-HA001034635.aspx?redir=0 - // pattern = StringTool.strip(pattern, "[Red]"); // [Black] - // [h] [hhh] [=1] [=2] - // pattern = StringTool.strip(pattern, "Red]"); - // TODO: implement locale-specific entries: [$-409] [$-404] - // ... ******************** - // pattern= pattern.replaceAll("\\[.+?\\]", ""); - /* - * if (s.length > 1) { - * io.starter.toolkit.Logger.log(s[0]); - * java.util.regex.Pattern p = - * java.util.regex.Pattern.compile("\\[(.*?)\\]"); - * java.util.regex.Matcher m = p.matcher(pattern); - * - * while(m.find()) { - * io.starter.toolkit.Logger.log(m.group(1)); - * } - * } - */ - String[] s = pattern.split("\\["); - if (s.length > 1) { - pattern = ""; - for (int i = 0; i < s.length; i++) { - int zz = s[i].indexOf("]"); - if (zz != -1) { - String term = s[i].substring(1, zz); // skip first $ - if (term.indexOf("-") != -1) { // extract character TODO: - // locale specifics - pattern += term.substring(0, term.indexOf("-")); - } - } - pattern += s[i].substring(zz + 1); - } - } - return pattern; - } - - /** - * Reads from a Reader into a String. - * Blocking reads will be issued to the reader and the results will be - * concatenated into a string, which will be returned once the reader - * reports end-of-input. - * - * @param reader the Reader from which to read - * @return a string containing all characters read from the input - */ - public static String readString(Reader reader) throws IOException { - StringBuilder builder = new StringBuilder(); - CharBuffer buffer = CharBuffer.allocate(512); - - while (-1 != reader.read(buffer)) { - buffer.flip(); - builder.append(buffer); - buffer.clear(); - } - - return builder.toString(); - } -} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/StringTool.kt b/src/main/java/io/starter/toolkit/StringTool.kt new file mode 100644 index 0000000..707f0b1 --- /dev/null +++ b/src/main/java/io/starter/toolkit/StringTool.kt @@ -0,0 +1,1454 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.io.* +import java.nio.CharBuffer +import java.util.StringTokenizer + +/** + * A collection of useful methods for manipulating Strings. + */ +class StringTool : Serializable { + + // escaped slashes + internal var oneslash = 0x005C.toChar().toString() + internal var twoslash = oneslash + oneslash + + companion object { + + // static final long serialVersionUID = + // -5757918511951798619l; + internal const val serialVersionUID = -2761264230959133529L + + /** + * replace illegal XML characters with their html counterparts + * + * + * ie: "&" is converted to "&" + * + * + * check out [the w3 list of XML + * characters](http://www.w3.org/TR/REC-xml/) + * + * @param rep + * @return + */ + fun convertXMLChars(rep: String): String { + return io.starter.formats.XLS.OOXMLAdapter.stripNonAscii(rep) + .toString() // 20110815 KSC: this method is more complete + } + + // test stuff + @JvmStatic + fun main(args: Array) { + + val majorHTML = "Testing yes

                      • item1
                      • item2
                      • item3

                      newline
                      newline2
                      newline3 yes no 124<>something
                      newline4bold" + + Logger.logInfo(StringTool.stripHTML(majorHTML)) + + } + + /** + * strip out all most HTML tags + * + * @param rep + * @return a string stripped of all html tags + */ + fun stripHTML(rep: String): String { + var rep = rep + + // first convert newlines + rep = rep.replace("<[B,b][R,r]?>".toRegex(), "\r\n") + rep = rep.replace("<[B,b][R,r]?/>".toRegex(), "\r\n") + + rep = rep.replace("<[L,l][I,i]?>".toRegex(), "\r\n\r\n") + + val ret = StringBuffer() + val cx = rep.toCharArray() + var skipping = false + var t = 0 + while (t < cx.size) { + val tt = cx[t] + // begin match + if (tt == '<') { + skipping = true + t++ + } else if (tt == '>') { + skipping = false + } + if (!skipping && tt != '>') + ret.append(cx[t]) + t++ + + } + return ret.toString() + } + + /** + * replace endoded text with normal text ie: "&" is converted to "&" + * + * + * check out [the w3 list of XML + * characters](http://www.w3.org/TR/REC-xml/) + * + * @param rep + * @return + */ + fun convertHTML(rep: String): String { + // if(true)return rep; + rep.replace("&".toRegex(), "&") + rep.replace("'".toRegex(), "'") + rep.replace(""".toRegex(), "\"") + rep.replace("<".toRegex(), "<") + rep.replace(">".toRegex(), ">") + rep.replace("©".toRegex(), "") + return rep + } + + /** + * If the string matches any part of the pattern, strip the pattern from the + * string. + */ + fun stripMatch(pattern: String, matchstr: String): String { + val upat = pattern.toUpperCase() + val umat = matchstr.toUpperCase() + var retval = "" + if (umat.lastIndexOf(upat) > -1) { + val pos = umat.lastIndexOf(upat) + upat.length + retval = matchstr.substring(pos) + Logger.logInfo("foundpos: $pos") + } + return retval + } + + /** + * get the variable name for a "getXXXX" a field name per JavaBean java + * naming conventions. + * + * + * ie: Converts "getFirstName" to "firstName" + */ + fun getVarNameFromGetMethod(thismethod: String): String { + val getidx = thismethod.indexOf("get") + if (getidx < 0) + return "" + var retval = thismethod.substring(getidx + 3) + retval = retval.substring(0, retval.length - 2) + var upcase = retval.substring(0, 1) + upcase = upcase.toUpperCase() + retval = retval.substring(1) + retval = upcase + retval + return retval + } + + /** + * converts java member naming convention to underscored DB-style naming + * convention + * + * + * ie: take upperCamelCase and turn into upper_camel_case + */ + fun convertJavaStyletoDBConvention(name: String): String { + val chars = name.toCharArray() + val buf = StringBuffer() + for (i in chars.indices) { + // if there is a single upper-case letter, then it's a + // case-word + if (Character.isUpperCase(chars[i])) { + if (i > 0 && i + 1 < chars.size) { + if (!Character.isUpperCase(chars[i])) + buf.append("_") + } + buf.append(chars[i]) + } else { + buf.append(Character.toUpperCase(chars[i])) + } + } + return buf.toString() + } + + /** + * converts java member naming convention to underscored DB-style naming + * convention + * + * + * ie: take upperCamelCase and turn into upper_camel_case + */ + fun convertJavaStyletoFriendlyConvention(name: String): String { + if (name == "") + return "" + val buf = StringBuffer() + val chars = name.toCharArray() + buf.append(chars[0].toString().toUpperCase()) + var i = 1 + while (i < chars.size) { + if (chars[i] == '_') { + chars[i++] = Character.toUpperCase(chars[i + 1]) + buf.append(" ") + buf.append(chars[i]) + } else { + buf.append(chars[i].toString().toLowerCase()) + } + i++ + } + return buf.toString() + } + + /** + * convert an Array to a String representation of its objects + * + * @param name + * @return + */ + fun arrayToString(objs: Array): String { + val ret = StringBuffer("[") + for (x in objs.indices) { + ret.append(objs[x].toString()) + ret.append(", ") + } + ret.setLength(ret.length - 1) + ret.append("]") + return ret.toString() + } + + /** + * Returns the given throwable's stack trace as a string. + * + * @param target the Throwable whose stack trace should be returned + * @return the stack trace of the given Throwable as a String + * @throws NullPointerException if `target` is null + */ + fun stackTraceToString(target: Throwable): String { + val trace = StringWriter() + val writer = PrintWriter(trace) + + target.printStackTrace(writer) + writer.flush() + + return trace.toString() + } + + /** + * converts strings to "proper" capitalization + * + * + * ie: take "mr. fraNK sMITH" and turn into "Mr. Frank Smith" + */ + fun proper(name: String): String { + if (name == "") + return "" + val buf = StringBuffer() + val chars = name.toCharArray() + buf.append(chars[0].toString().toUpperCase()) + var i = 1 + while (i < chars.size) { + if (chars[i] == ' ') { + buf.append(" ") + i++ + if (chars[i] != ' ') { + chars[i] = Character.toUpperCase(chars[i]) + buf.append(chars[i]) + } + } else { + buf.append(chars[i].toString().toLowerCase()) + } + i++ + } + return buf.toString() + } + + /** + * Each object in the array must support the .toString() method, as it will + * be used to render the object to its string representation. + */ + + fun makeDelimitedList(list: Array, delimiter: String): String { + val listBuf = StringBuffer() + for (i in list.indices) { + if (i != 0) + listBuf.append(delimiter) + listBuf.append(list[i].toString()) + } + return listBuf.toString() + } + + /** + * Returns an array of strings from a single string, similar to + * String.split() in JavaScript + */ + fun splitString(value: String, delimeter: String): Array { + + val stoken = StringTokenizer(value, delimeter) + val returnValue = arrayOfNulls(stoken.countTokens()) + var i = 0 + while (stoken.hasMoreTokens()) { + returnValue[i] = stoken.nextToken() + i++ + } + return returnValue + } + + /** + * get compressed UNICODE string from uncompressed string + */ + fun getCompressedUnicode(input: ByteArray): String { + val output = ByteArray(input.size / 2) + var pos = 0 + var i = 0 + while (i < input.size) { + output[pos++] = input[i] + i++ + i++ + } + return String(output) + } + + fun getLowerCaseFirstLetter(thismember: String): String { + var thismember = thismember + var upcase = thismember.substring(0, 1) + upcase = upcase.toLowerCase() + thismember = thismember.substring(1) + thismember = upcase + thismember + return thismember + } + + /** + * generate a "setXXXX" string from a field name per Extentech java naming + * conventions. + * + * + * ie: Converts "firstName" to "setFirstName" + */ + fun getSetMethodNameFromVar(thismember: String): String { + return "set" + getUpperCaseFirstLetter(thismember) + } + + fun getUpperCaseFirstLetter(thismember: String): String { + var thismember = thismember + var upcase = thismember.substring(0, 1) + upcase = upcase.toUpperCase() + thismember = thismember.substring(1) + thismember = upcase + thismember + return thismember + } + + /** + * generate a "getXXXX" string from a field name per Extentech java naming + * conventions. + * + * + * ie: Converts "firstName" to "getFirstName" + */ + fun getGetMethodNameFromVar(thismember: String): String { + return "get" + getUpperCaseFirstLetter(thismember) + } + + /** + * converts underscored DB-style naming convention to java member naming + * convention + */ + fun convertDBtoJavaStyleConvention(name: String): String { + val scoreloc = name.indexOf("_") + val buf = StringBuffer() + if (scoreloc < 0) + return name + else { + val chars = name.toCharArray() + for (i in chars.indices) { + if (chars[i] == '_') { + chars[i + 1] = Character.toUpperCase(chars[i + 1]) + } else { + buf.append(chars[i]) + } + } + } + return buf.toString() + } + + /** + * converts underscored DB-style naming convention to java member naming + * convention + */ + fun convertFilenameToJSPName(name: String): String { + val buf = StringBuffer() + val chars = name.toCharArray() + for (i in chars.indices) { + if (chars[i] == '_') { + chars[i + 1] = Character.toUpperCase(chars[i + 1]) + } else if (chars[i] == '-') { + chars[i + 1] = Character.toUpperCase(chars[i + 1]) + } else if (chars[i] == ' ') { + chars[i + 1] = Character.toUpperCase(chars[i + 1]) + } else { + buf.append(chars[i]) + } + } + return buf.toString() + } + + /** + * Basically a String tokenizer + * + * @param instr + * @param token + * @return + */ + fun getTokensUsingDelim(instr: String, token: String): Array { + if (instr.indexOf(token) < 0) { + val ret = arrayOfNulls(1) + ret[0] = instr + return ret + } + val output = CompatibleVector() + StringBuffer() + var lastpos = 0 + var offset = 0 + val toklen = token.length + var pos = instr.indexOf(token) + // pos--; + while (pos > -1) { + if (lastpos > 0) + // if the line starts with a token + offset = lastpos + toklen + else + offset = 0 + val st = instr.substring(offset, pos) + output.add(st) + lastpos = pos + pos = instr.indexOf(token, lastpos + 1) + } + if (lastpos < instr.length) { + val st = instr.substring(lastpos + toklen) + output.add(st) + } + val retval = arrayOfNulls(output.size) + for (i in output.indices) + retval[i] = output[i] as String + return retval + } + + /** + * + */ + fun dbencode(holder: String): String { + return replaceText(holder, "'", "''", 0) + } + + /** + * Lose the whitespace at the end of strings... + * + * @param holder The String that you want stripped. + * @return Your stripped string. + */ + + fun allTrim(holder: String): String { + var holder = holder + holder = holder.trim { it <= ' ' } + return rTrim(holder) + } + + /** + * strip trailing spaces + */ + fun stripTrailingSpaces(s: String): String { + var s = s + while (s.endsWith(" ")) + s = s.substring(0, s.length - 1) + return s + } + + /** + * Strips all occurences of a string from a given string. + * + * @param tostrip The String that you want stripped. + * @param stripchar The char you want stripped from the String. + * @return Your stripped string. + */ + + fun strip(tostrip: String, stripstr: String): String { + var tostrip = tostrip + val stripped = StringBuffer(tostrip.length) + while (tostrip.indexOf(stripstr) > -1) { + stripped.append(tostrip, 0, tostrip.indexOf(stripstr)) + tostrip = tostrip + .substring(tostrip.indexOf(stripstr) + stripstr.length) + } + stripped.append(tostrip) + return stripped.toString() + } + + /** + * Strips all occurences of a character from a given string. + * + * @param tostrip The String that you want stripped. + * @param stripchar The char you want stripped from the String. + * @return Your stripped string. + */ + + fun strip(tostrip: String, stripchar: Char): String { + val stripped = StringBuffer(tostrip.length) + var i = 0 + var currentChar: Char + while (i < tostrip.length) { + currentChar = tostrip[i] + if (currentChar == stripchar) { + i++ + } else { + stripped.append(currentChar) + i++ + } + } + return stripped.toString() + } + + /** + * Replaces an occurence of String B with String C within String A. This + * method is case sensitive. + * + * + * Example: String A = "I am a happy dog."; String A = + * stringtool.replaceText(A, "happy", "sad", 0); + * + * + * The result is A="I am a sad dog." + * + * @param originalText Original text + * @param replaceText Text to replace. + * @param replacementText Text to replace with. + * @param offset offset of replacement within original string. + * @return Processed text. + */ + fun replaceText(originalText: String, replaceText: String, replacementText: String, offset: Int, skipmatch: Boolean): String { + if (!skipmatch) + return replaceText(originalText, replaceText, replacementText, offset) + + val sb = StringBuffer() + if (originalText.indexOf(replaceText) < 0) { + return originalText + } else { + var nextidx = 0 + var lastidx = 0 + var pos = 0 + val textlen = replaceText.length + val stringlen = originalText.length + + while (nextidx <= originalText.lastIndexOf(replaceText)) { + pos++ + nextidx = originalText.indexOf(replaceText, lastidx) + sb.append(originalText, lastidx, nextidx) + if (pos > offset) + sb.append(replacementText) + else + sb.append(replaceText) + nextidx += textlen + if (textlen == 0) + break// case of "" + lastidx = nextidx + } + if (nextidx < stringlen) { + sb.append(originalText.substring(nextidx)) + } + return sb.toString() + } + } + + /** + * Replaces an occurence of String B with String C within String A. This + * method is case sensitive. + * + * + * Example: String A = "I am a happy dog."; String A = + * stringtool.replaceText(A, "happy", "sad", 0); + * + * + * The result is A="I am a sad dog." + * + * @param originalText Original text + * @param replaceText Text to replace. + * @param replacementText Text to replace with. + * @param offset offset of replacement within original string. + * @return Processed text. + */ + fun replaceText(originalText: String, replaceText: String, replacementText: String, offset: Int): String { + + var newlen = originalText.length - replaceText.length + replacementText.length + + if (newlen < 1) + newlen = 0 + val sb = StringBuffer(newlen) + if (originalText.indexOf(replaceText) < 0) { + return originalText + } else { + if (replaceText != null && replaceText == replacementText) + return originalText // avoid infinite loops + var nextidx = 0 + var lastidx = 0 + val textlen = replaceText.length + val stringlen = originalText.length + while (nextidx <= originalText.lastIndexOf(replaceText)) { + nextidx = originalText.indexOf(replaceText, lastidx) + sb.append(originalText, lastidx, nextidx + offset) + sb.append(replacementText) + nextidx += textlen + if (textlen == 0) + break // case of "" + lastidx = nextidx + } + if (nextidx < stringlen) { + sb.append(originalText.substring(nextidx)) + } + return sb.toString() + } + } + + /** + * Trims whitespace from the right side of strings. + * + * @param originalText Text to trim. + * @return Trimmed text. + */ + fun rTrim(originalText: String): String { + var sb = StringBuffer(originalText) + sb.reverse() + val rstr = sb.toString() + rstr.trim { it <= ' ' } + sb = StringBuffer(rstr) + sb.reverse() + return sb.toString() + } + + /** + * This method will retrieve the first instance of text between any two + * given patterns. This method is case sensitive. + * + * + * Example: + * + * + * String A = "I am a happy dog."; B = getTextBetweenDelims(A,"a","."); + * + * + * B is now equal to "m a happy dog". C declines comment. + * + * @param originalText Text to process. + * @param beginDelim Delimeter for beginning of retrieved section. + * @param endDelim Delimeter for end of retrieved section. + * @return Text between delims or "" if not found. + */ + fun getTextBetweenNestedDelims(originalText: String, beginDelim: String, endDelim: String): String { + val sb = StringBuffer(originalText.length) + // Check to see that both delimiters exist in the string + if (originalText.indexOf(beginDelim) < 0 || originalText.lastIndexOf(endDelim) < 0) { + return "" + } else { + val begidx = originalText.indexOf(beginDelim) + beginDelim.length + var endidx = originalText.lastIndexOf(endDelim) + var holder = 0 + if (begidx < endidx) { + sb.append(originalText, begidx, endidx) + } else { + while (begidx > endidx && endidx > -1) { + holder = endidx + endidx = originalText.lastIndexOf(endDelim, holder + 1) + } + if (begidx < endidx && endidx > -1) + sb.append(originalText, begidx, endidx) + } + return sb.toString() + } + } + + /** + * This method will retrieve the first instance of text between any two + * given patterns. This method is case sensitive. + * + * + * Example: + * + * + * String A = "I am a happy dog."; B = getTextBetweenDelims(A,"a","."); + * + * + * B is now equal to "m a happy dog". C declines comment. + * + * @param originalText Text to process. + * @param beginDelim Delimeter for beginning of retrieved section. + * @param endDelim Delimeter for end of retrieved section. + * @return Text between delims or "" if not found. + */ + fun getTextBetweenDelims(originalText: String, beginDelim: String, endDelim: String): String { + val sb = StringBuffer(originalText.length) + // Check to see that both delimiters exist in the string + if (originalText.indexOf(beginDelim) < 0 || originalText.indexOf(endDelim) < 0) { + return "" + } else { + val begidx = originalText.indexOf(beginDelim) + beginDelim.length + var endidx = originalText.indexOf(endDelim) + var holder = 0 + if (begidx < endidx) { + sb.append(originalText, begidx, endidx) + } else { + while (begidx > endidx && endidx > -1) { + holder = endidx + endidx = originalText.indexOf(endDelim, holder + 1) + } + if (begidx < endidx && endidx > -1) + sb.append(originalText, begidx, endidx) + } + return sb.toString() + } + } + + /** + * This method will replace any instance of given text within another + * string. This method is case sensitive. + * + * + * Example: + * + * + * String A = "I am a happy dog."; A = + * replaceSection(A,"happy","hippie cat", "dog"); + * + * + * A is now equal to "I am a hippie cat.". + * + * @param originalText Text to process. + * @param replaceBegin Beggining pattern of replaced section. + * @param replacementText Text to replace with. + * @param replaceEnd End pattern of replaced section. + * @return Processed text. + */ + + fun replaceSection(originalText: String, replaceBegin: String, replacementText: String, replaceEnd: String): String { + val sb = StringBuffer(originalText.length) + if (originalText.indexOf(replaceBegin) < 0 || originalText.indexOf(replaceEnd) < 0) { + return originalText + } else { + val begidx = originalText.indexOf(replaceBegin) + val endlen = replaceEnd.length + var endidx = originalText.indexOf(replaceEnd) + endlen + var holder = 0 + if (begidx < endidx) { + sb.append(originalText, 0, begidx) + sb.append(replacementText) + sb.append(originalText.substring(endidx)) + } else { + while (begidx > endidx && endidx > -1) { + holder = endidx + endidx = originalText.indexOf(replaceEnd, holder + 1) + } + if (begidx < endidx && endidx > -1) { + sb.append(originalText, 0, begidx) + sb.append(replacementText) + sb.append(originalText.substring(endidx + endlen)) + } + } + return sb.toString() + } + } + + fun StripChars(theFilter: String, theString: String): String { + val strOut = StringBuffer(theString.length) + var curChar: Char + for (i in 0 until theString.length) { + curChar = theString[i] + if (theFilter.indexOf(curChar.toInt()) < 0) { // if it's not in the filter, + // send it thru + strOut.append(curChar) + } + } + return strOut.toString() + } + + fun UseOnlyChars(theFilter: String, theString: String): String { + val strOut = StringBuffer(theString.length) + var curChar: Char + for (i in 0 until theString.length) { + curChar = theString[i] + if (theFilter.indexOf(curChar.toInt()) > -1) { // if it's in the filter, + // send it thru + strOut.append(curChar) + } + } + return strOut.toString() + } + + fun replaceChars(theFilter: String, theString: String, replacement: String): String { + val strOut = StringBuffer(theString.length) + var curChar: Char + for (i in 0 until theString.length) { + curChar = theString[i] + if (theFilter.indexOf(curChar.toInt()) < 0) { // if it's not in the filter, + // send it thru + strOut.append(curChar) + } else { + strOut.append(replacement) + } + } + return strOut.toString() + } + + /** + * replace a section of text based on pattern match throughout string. + */ + fun replaceText(theString: String, theFilter: String, replacement: String): String { + return replaceText(theString, theFilter, replacement, 0) + } + + fun AllInRange(x: Int, y: Int, theString: String): Boolean { + var curChar: Char + for (i in 0 until theString.length) { + curChar = theString[i] + if (curChar.toInt() < x || curChar.toInt() > y) { + return false + } + } + return true + } + + /** + * Replaces a specified token in a string with a value from the passed + * through array this is done in matching order from String to Arrray. + */ + fun replaceTokenFromArray(replace: String, token: String, vals: Array): String { + val sb = StringBuffer() + val toke = StringTokenizer(replace, token, false) + var i = 0 + // add the first element + while (toke.hasMoreTokens()) { + sb.append(toke.nextToken()) + if (i <= vals.size - 1) { + sb.append(vals[i]) + ++i + } + } + return sb.toString() + } + + /** + * returns file path fPath qualified with an ending slash + * + * @param fPath + * @return fPath with an ending slash + */ + fun qualifyFilePath(fPath: String): String { + var fPath = fPath + StringTool.replaceChars("\\", fPath, "/") + fPath = fPath.trim { it <= ' ' } + if (!fPath.endsWith("/")) + fPath += "/" + return fPath + } + + /** + * splits a filepath into directory and filename + * + * @param filePath + * @return + */ + fun splitFilepath(filePath: String): Array { + var filePath = filePath + val path = arrayOfNulls(2) + filePath = StringTool.replaceText(filePath, "\\", "/") + val lastpath = filePath.lastIndexOf("/") + if (lastpath > -1) { // strip path and directory + path[0] = filePath.substring(0, lastpath + 1) // get directory + path[1] = filePath.substring(lastpath + 1) // strip directory from + // filename + } else + path[1] = filePath + return path + } + + /** + * strips the path portion from a filepath and returns the filename + * + * @param filePath + * @return + */ + fun stripPath(filePath: String): String { + var filePath = filePath + filePath = StringTool.replaceText(filePath, "\\", "/") + val lastpath = filePath.lastIndexOf("/") + return if (lastpath > -1) filePath.substring(lastpath + 1) else filePath // strip directory from + // filename + + } + + /** + * strips the path portion from a filepath and returns it + * + * @param filePath + * @return + */ + fun getPath(filePath: String): String { + var filePath = filePath + filePath = StringTool.replaceText(filePath, "\\", "/") + val lastpath = filePath.lastIndexOf("/") + return filePath.substring(0, lastpath + 1) + } + + /** + * replaces the extension of a filepath with an new extension + * + * @param filepath Source FilePaht + * @param ext + * @return + */ + fun replaceExtension(filepath: String, ext: String): String { + val i = filepath.lastIndexOf(".") + var f = filepath + if (i > 0) + f = filepath.substring(0, i) + ext + else + f = filepath + ext + return f + } + + /** + * given a string, return the maximum of the width in pixels in the given + * the awt Font.

                      + * NOTE: this method does not account for line feeds contained within + * strings + * + * @param f awt Font + * @param s String to compute + * @return double approximate width in pixels + */ + fun getApproximateStringWidth(f: java.awt.Font, s: String): Double { + val fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f) + /* + * width/height in pixels = (w/h field) * DPI of the display + * device / 72 + */ + val conversion = java.awt.Toolkit.getDefaultToolkit() + .screenResolution / 72.0 + return fm.stringWidth(s) * conversion // pixels * conversion + } + + /** + * given a string, return the maximum of the width in pixels in the given + * the awt Font Observing Line Breaks.

                      + * + * @param f awt Font + * @param s String to compute + * @return double approximate width in pixels + */ + fun getApproximateStringWidthLB(f: java.awt.Font, s: String): Double { + val fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f) + /* + * width/height in pixels = (w/h field) * DPI of the display + * device / 72 + */ + val conversion = java.awt.Toolkit.getDefaultToolkit() + .screenResolution / 72.0 + val ss = s.split("\n".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + var len = 0.0 + for (st in ss) { + len = Math.max(len, fm.stringWidth(st) * conversion) + } + // return fm.stringWidth(s) * conversion; + return len + } + + /** + * return the approximate witdth in width in pixels of the given character + * + * @param f awt Font + * @param c character + * @return double approximate width in pixels + */ + fun getApproximateCharWidth(f: java.awt.Font, c: Char?): Double { + val fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f) + /* + * width/height in pixels = (w/h field) * DPI of the display + * device / 72 + */ + val conversion = java.awt.Toolkit.getDefaultToolkit() + .screenResolution / 72.0 + return fm.charWidth(c!!) * conversion + } + + /** + * return the approximate height it takes to display the given string in the + * given font in the given width + * + * @param f java.awt.Font + * @param s String + * @param w width in points + * @return + */ + fun getApproximateHeight(f: java.awt.Font, s: String, w: Double): Double { + var s = s + var len = StringTool.getApproximateStringWidth(f, s) + while (len > w) { + var lastSpace = -1 + var j = s.lastIndexOf("\n") + 1 + len = 0.0 + while (len < w && j < s.length) { + len += StringTool.getApproximateCharWidth(f, s[j]) + if (s[j] == ' ') + lastSpace = j + j++ + } + if (len < w) { + break // got it + } + if (lastSpace == -1) { // no spaces to break apart + if (s.indexOf(' ') == -1) + break + lastSpace = s.lastIndexOf(' ') // break at + } + s = s.substring(0, lastSpace) + "\n" + s.substring(lastSpace + 1) + } + val nl = s.split("\n".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray().size + val fm = java.awt.Toolkit.getDefaultToolkit() + .getFontMetrics(f) + val lm = f + .getLineMetrics(s, fm.fontRenderContext) + // this calc appears to match Excel's ... + val l = lm.leading + // float h= lm.getHeight(); + // io.starter.toolkit.Logger.log("Font: " + + // f.toString()); + // io.starter.toolkit.Logger.log("l-i:" + + // fm.getLeading() + " l:" + l + " h-i:" + fm.getHeight() + + // " h:" + h + " a-i:" + fm.getAscent() + " a:" + + // lm.getAscent() + " d-i:" + fm.getDescent() + " d:" + + // lm.getDescent()); + val h = fm.height.toFloat() // KSC: revert for now ... - l/3; // i don't + // know why but this seems to match Excel's + // the closest + return Math.ceil((h * nl).toDouble())// +1)); // KSC: added + 1 for testing + } + + /** + * converts an excel-style custom format to String.format custom format i.e. + * %flags-width-precision-conversion + * + * NOTE: the pattern should be a single item in the excel-style format i.e.one of the terms in positive;negative;zero;text + * without semicolon. + *

                      NOTE: the java-specific pattern returned will have the negative formatting (sign, parenthesis ...) and so, when used, + * the double value must be it's absolute value i.e String.format(pattern, Math.abs(d)) + *

                      NOTE: date formats are handled separately. this only applies to number and currency formats + *

                      Excel-style: + * 0 (zero) Digit placeholder. This code pads the value with zeros to fill the format. + * # Digit placeholder. This code does not display extra zeros. + * ? Digit placeholder. This code leaves a space for insignificant zeros but does not display them. + * . (period) Decimal number. + * % Percentage. Microsoft Excel multiplies by 100 and adds the % character. + * , (comma) Thousands separator. A comma followed by a placeholder scales the number by a thousand. + * E+ E- e+ e- Scientific notation. + * Text Code Description + * $ - + / ( ) : space These characters are displayed in the number. To display any other character, enclose the character in quotation marks or precede it with a backslash. + * \character This code displays the character you specify. + * "text" This code displays text. + * This code repeats the next character in the format to fill the column width. Note Only one asterisk per section of a format is allowed. + * _ (underscore) This code skips the width of the next character. + * This code is commonly used as "_)" (without the quotation marks) + * to leave space for a closing parenthesis in a positive number format + * when the negative number format includes parentheses. + * This allows the values to line up at the decimal point. + * + * @param pattern String format pattern in Excel format + * @param isNegative true if the source is a negative number + * @return + * @ Text placeholder. + */ + fun convertPatternFromExcelToStringFormatter(pattern: String, isNegative: Boolean): String { + var pattern = pattern + val curPattern = pattern + var jpattern = "" // return pattern + var w = 0 + var precision = 0 + var flags = "" + var conversion = 'f' // default + var inConversion = false + var inPrecision = false + var removeSign = false // true if value is negative and pattern + // calls for parens or color change or ... + // i.e. don't display the negative sign + /* + * TODO: \ uXXX is Locale-specific to display? works + * manually ... + * TODO; finish fractional formats: ?/? + */ + var i = 0 + while (i < curPattern.length) { + var c = curPattern[i].toInt() + when (c) { + '0' -> { + w++ + if (!inConversion) { + jpattern += "%" + inConversion = true + } + if (inPrecision && conversion != 'E') + precision++ + } + '?' // don't really know what to do with this one! + -> { + } + '#' -> if (!inConversion) { + jpattern += "%" + inConversion = true + } + ',' -> flags += "," + '.' -> inPrecision = true + 'E', 'e' -> { + if (!inConversion) { + jpattern += "%" + inConversion = true + } + conversion = 'E' + i++ // format is e+, E+, e- or E- + } + '[' // either color code or local-specific formatting + -> { + var j = ++i + var k = j + while (i < curPattern.length) { // skip colors for now + c = curPattern[i].toInt() + if (c == '-'.toInt()) + // got end of an extended char sequence - skip + // rest (Locale code ...) + k = i + if (c == ']'.toInt()) + break + i++ + } + if (inConversion) { + inConversion = false + inPrecision = false + jpattern += (flags + (if (w > 0) w else "") + "." + precision + + conversion) + } + if (k == j) + // then it was a color string + removeSign = true + else + // it was a locale-specific string ... + jpattern += curPattern.substring(++j, k) + } + '"' // start of delimited text + -> { + if (inConversion) { + inConversion = false + inPrecision = false + jpattern += (flags + (if (w > 0) w else "") + "." + precision + + conversion) + } + i++ + while (i < curPattern.length) { + c = curPattern[i].toInt() + if (c == '"'.toInt()) + break + jpattern += c.toChar() + i++ + + } + } + // ignore + '@' // text placeholder + -> jpattern += "%s" + '*' // repeats the next char to fill -- IGNORE!!! + -> { + } + '(' // enclose negative #'s in parens + , ')' -> + // flags+="("; + if (isNegative) { + if (inConversion) { + inConversion = false + inPrecision = false + jpattern += (flags + (if (w > 0) w else "") + "." + precision + + conversion) + } + jpattern += c.toChar() + removeSign = true + } + '_' // skips the width of the next char - usually _) - to + -> { + // leave space for a closing parenthesis in a positive + // number format when the negative number format + // includes parentheses. This allows the values to line + // up at the decimal point. + if (inConversion) { + inConversion = false + inPrecision = false + jpattern += (flags + (if (w > 0) w else "") + "." + precision + + conversion) + } + i++ // skip next char -- true in all cases??? + } + '%' -> { + if (inConversion) { + inConversion = false + inPrecision = false + jpattern += (flags + (if (w > 0) w else "") + "." + precision + + conversion) + } + jpattern += "%%" + } + '\\' -> { + if (inConversion) { + inConversion = false + inPrecision = false + jpattern += (flags + (if (w > 0) w else "") + "." + precision + + conversion) + } + val z: Int + if (i + 1 < curPattern.length && curPattern[i + 1] == 'u') + z = i + 6 + else + z = i + 1 + while (i < z && i < curPattern.length) { + jpattern += curPattern[i] + i++ + } + } + else // %, $, - space -- keep + -> { + if (inConversion) { + inConversion = false + inPrecision = false + jpattern += (flags + (if (w > 0) w else "") + "." + precision + + conversion) + } + jpattern += c.toChar() + } + }// TODO: handle such as: ###0.00######### --- what's the + // format spec for that????? + // if (inPrecision) precision++; + i++ + } + if (inConversion) { + jpattern += flags + (if (w > 0) w else "") + "." + precision + conversion + } + if (isNegative && !removeSign) + jpattern = "-$jpattern" + // System.out.print("Original Pattern " + pattern + " new " + // + jpattern); + // patterns[z]= jpattern; + pattern = jpattern + return pattern + } + + fun convertDatePatternFromExcelToStringFormatter(pattern: String): String { + var jpattern = "" // return pattern + var dString = "" // d string -- ddd ==> EEE and dddd ==> EEEE + var mString = "" // m string -- either month (M, MM, MMM, MMMM) or + // minute + var prev = 0 + var i = 0 + while (i < pattern.length) { + val c = pattern[i].toInt() + if (c != 'd'.toInt() && dString != "") { + if (dString.length <= 2) + jpattern += dString + else if (dString.length == 3) + jpattern += "EEE" + else if (dString.length == 4) + jpattern += "EEEE" + dString = "" + } else if (c != 'm'.toInt() && mString != "") { + if (c == ':'.toInt() || prev == 'h'.toInt()) { // it's time + jpattern += mString + prev = c + } else + jpattern += mString.toUpperCase() + mString = "" + } + + when (c) { + 'y' -> jpattern += c.toChar() + 'h' -> { + jpattern += 'H'.toString() // h in java is 1-24 excel h= 0-23 + prev = 'h'.toInt() + } + '\\' // found case of erroneous use of backslash, as in: + , + // mm\-dd\-yy ignore! + '[' // no java equivalent of [h] [m] or [ss] == elapsed time + , ']' -> { + } + 's' -> jpattern += c.toChar() + 'A' -> if (pattern.substring(i, i + 5) == "AM/PM") { + jpattern += "a" + i += 5 + for (z in jpattern.length - 2 downTo 0) { + if (jpattern[z] == 'H') { + jpattern = (jpattern.substring(0, z) + 'h'.toString() + + jpattern.substring(z + 1)) + } + } + } + 'd' -> dString += c.toChar() + 'm' -> mString += c.toChar() + else -> { + if (c != ':'.toInt() && c != 'm'.toInt()) + prev = c + jpattern += c.toChar() + } + } + i++ + } + if (mString != "") { + if (prev == 'h'.toInt()) + // it's time + jpattern += mString + else + jpattern += mString.toUpperCase() // remaining month string + } else if (dString != "") { + if (dString.length <= 2) + jpattern += dString + else if (dString.length == 3) + jpattern += "EEE" + else if (dString.length == 4) + jpattern += "EEEE" + dString = "" + } + return jpattern + } + + /** + * extract info, if any, from bracketed expressions within Excel custom number formats + * + * @param pattern String Excel number format + * @return String returned number format without the bracketed expression + */ + fun convertPatternExtractBracketedExpression(pattern: String): String { + var pattern = pattern + val s = pattern.split("\\[".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + if (s.size > 1) { + pattern = "" + for (i in s.indices) { + val zz = s[i].indexOf("]") + if (zz != -1) { + var term = "" + if (s[i].get(0) == '$') + term = s[i].substring(1, zz) // skip first $ + else + term = s[i].substring(0, zz) + if (term.indexOf("-") != -1) + // extract character TODO: + // locale specifics + pattern += term.substring(0, term.indexOf("-")) + else + pattern += term + } + pattern += s[i].substring(zz + 1) + } + } + return pattern + } + + /** + * qualifies a pattern string to make valid for applying the pattern + * + * @param pattern + * @return + */ + fun qualifyPatternString(pattern: String): String { + var pattern = pattern + pattern = StringTool.strip(pattern, "*") + pattern = StringTool.strip(pattern, "_(") // width placeholder + pattern = StringTool.strip(pattern, "_)") // width placeholder + pattern = StringTool.strip(pattern, "_") + pattern = pattern.replace("\"".toRegex(), "") + pattern = StringTool.strip(pattern, "?") + // there are more bracketed expressions to deal with + // see + // http://office.microsoft.com/en-us/excel-help/creating-international-number-formats-HA001034635.aspx?redir=0 + // pattern = StringTool.strip(pattern, "[Red]"); // [Black] + // [h] [hhh] [=1] [=2] + // pattern = StringTool.strip(pattern, "Red]"); + // TODO: implement locale-specific entries: [$-409] [$-404] + // ... ******************** + // pattern= pattern.replaceAll("\\[.+?\\]", ""); + /* + * if (s.length > 1) { + * io.starter.toolkit.Logger.log(s[0]); + * java.util.regex.Pattern p = + * java.util.regex.Pattern.compile("\\[(.*?)\\]"); + * java.util.regex.Matcher m = p.matcher(pattern); + * + * while(m.find()) { + * io.starter.toolkit.Logger.log(m.group(1)); + * } + * } + */ + val s = pattern.split("\\[".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() + if (s.size > 1) { + pattern = "" + for (i in s.indices) { + val zz = s[i].indexOf("]") + if (zz != -1) { + val term = s[i].substring(1, zz) // skip first $ + if (term.indexOf("-") != -1) { // extract character TODO: + // locale specifics + pattern += term.substring(0, term.indexOf("-")) + } + } + pattern += s[i].substring(zz + 1) + } + } + return pattern + } + + /** + * Reads from a `Reader` into a `String`. + * Blocking reads will be issued to the reader and the results will be + * concatenated into a string, which will be returned once the reader + * reports end-of-input. + * + * @param reader the `Reader` from which to read + * @return a string containing all characters read from the input + */ + @Throws(IOException::class) + fun readString(reader: Reader): String { + val builder = StringBuilder() + val buffer = CharBuffer.allocate(512) + + while (-1 != reader.read(buffer)) { + buffer.flip() + builder.append(buffer) + buffer.clear() + } + + return builder.toString() + } + } +} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/TempFileGenerator.java b/src/main/java/io/starter/toolkit/TempFileGenerator.kt similarity index 78% rename from src/main/java/io/starter/toolkit/TempFileGenerator.java rename to src/main/java/io/starter/toolkit/TempFileGenerator.kt index 8309f02..78d481c 100644 --- a/src/main/java/io/starter/toolkit/TempFileGenerator.java +++ b/src/main/java/io/starter/toolkit/TempFileGenerator.kt @@ -22,22 +22,25 @@ */ /** * TempFileGenerator.java - *

                      - *

                      + * + * + * + * * Mar 14, 2012 */ -package io.starter.toolkit; +package io.starter.toolkit -import java.io.File; -import java.io.IOException; +import java.io.File +import java.io.IOException /** * */ -public interface TempFileGenerator { +interface TempFileGenerator { - File createTempFile(String prefix, String extension) throws IOException; + @Throws(IOException::class) + fun createTempFile(prefix: String, extension: String): File } \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/TempFileManager.java b/src/main/java/io/starter/toolkit/TempFileManager.kt similarity index 51% rename from src/main/java/io/starter/toolkit/TempFileManager.java rename to src/main/java/io/starter/toolkit/TempFileManager.kt index ec49629..2db3d9f 100644 --- a/src/main/java/io/starter/toolkit/TempFileManager.java +++ b/src/main/java/io/starter/toolkit/TempFileManager.kt @@ -22,15 +22,17 @@ */ /** * TempFileManager.java - *

                      - *

                      + * + * + * + * * Feb 27, 2012 */ -package io.starter.toolkit; +package io.starter.toolkit -import io.starter.OpenXLS.DocumentHandle; +import io.starter.OpenXLS.DocumentHandle -import java.io.*; +import java.io.* /** * The TempFileManager allows for consolidated handling of all TempFiles used by OpenXLS. @@ -38,29 +40,30 @@ * TempFileManager is pluggable and allows you to implement a custom TempFileGenerator and install using * System properties. * - * ie: + * ie: * * System.setProperty(TempFileManager.TEMPFILE_MANAGER_CLASSNAME, "com.acme.CustomTempFileGenerator"); * WorkBookHandle bkx = new WorkBookHandle("test.xlsx"); // use custom tempfile generator * * */ -public class TempFileManager { +object TempFileManager { - public static String TEMPFILE_MANAGER_CLASSNAME = "io.starter.OpenXLS.tempfilemanager"; + var TEMPFILE_MANAGER_CLASSNAME = "io.starter.OpenXLS.tempfilemanager" - public static File createTempFile(String prefix, String extension) - throws IOException { - String tmpfu = System.getProperty(TEMPFILE_MANAGER_CLASSNAME); + @Throws(IOException::class) + fun createTempFile(prefix: String, extension: String): File { + val tmpfu = System.getProperty(TEMPFILE_MANAGER_CLASSNAME) if (tmpfu != null) { try { - TempFileGenerator tgen = (TempFileGenerator) Class.forName(tmpfu).newInstance(); - return tgen.createTempFile(prefix, extension); - } catch (Exception e) { - Logger.logErr("Could not load custom TempFileGenerator: " + tmpfu + ". Falling back to default TempFileGenerator."); + val tgen = Class.forName(tmpfu).newInstance() as TempFileGenerator + return tgen.createTempFile(prefix, extension) + } catch (e: Exception) { + Logger.logErr("Could not load custom TempFileGenerator: $tmpfu. Falling back to default TempFileGenerator.") } + } - return new DefaultTempFileGeneratorImpl().createTempFile(prefix, extension); + return DefaultTempFileGeneratorImpl().createTempFile(prefix, extension) } /** @@ -71,10 +74,11 @@ public static File createTempFile(String prefix, String extension) * @param extension * @return */ - public static File createTempFile(InputStream input, String prefix, String extension) throws IOException { - File tmpfile = TempFileManager.createTempFile(prefix, extension); - JFileWriter.writeToFile(input, tmpfile); - return tmpfile; + @Throws(IOException::class) + fun createTempFile(input: InputStream, prefix: String, extension: String): File { + val tmpfile = TempFileManager.createTempFile(prefix, extension) + JFileWriter.writeToFile(input, tmpfile) + return tmpfile } /** @@ -85,22 +89,23 @@ public static File createTempFile(InputStream input, String prefix, String exten * @return * @throws IOException */ - public static File createTempFile(String prefix, String extension, File dir) throws IOException { - prefix = dir.getAbsolutePath() + prefix; - return createTempFile(prefix, extension); + @Throws(IOException::class) + fun createTempFile(prefix: String, extension: String, dir: File): File { + var prefix = prefix + prefix = dir.absolutePath + prefix + return createTempFile(prefix, extension) } - public static File writeToTempFile(String prefix, String extension, DocumentHandle doc) - throws IOException { - File file = createTempFile(prefix, extension); + @Throws(IOException::class) + fun writeToTempFile(prefix: String, extension: String, doc: DocumentHandle): File { + val file = createTempFile(prefix, extension) - BufferedOutputStream stream = - new BufferedOutputStream(new FileOutputStream(file)); - doc.write(stream, DocumentHandle.FORMAT_NATIVE); + val stream = BufferedOutputStream(FileOutputStream(file)) + doc.write(stream, DocumentHandle.FORMAT_NATIVE) - stream.flush(); - stream.close(); + stream.flush() + stream.close() - return file; + return file } } diff --git a/src/main/java/io/starter/toolkit/WriterOutputStream.java b/src/main/java/io/starter/toolkit/WriterOutputStream.java deleted file mode 100644 index 3c49d7c..0000000 --- a/src/main/java/io/starter/toolkit/WriterOutputStream.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.Writer; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CoderResult; -import java.nio.charset.CodingErrorAction; - -/** - * A WriterOutputStream is a bridge from byte streams to character - * streams: bytes written to it are decoded into characters using a specified - * {@linkplain Charset charset}. The charset that it uses may be specified by - * name or given explicitly, or the system default charset may be used. The - * decoded characters are written to a provided {@link Appendable}, which will - * usually be a {@link Writer}. - * - *

                      The input is buffered so that writes don't need to be aligned to - * character boundaries. Because conversion is only performed when the input - * buffer is full (or when the stream is flushed) output also behaves as if it - * were buffered. It is therefore usually unnecessary to externally buffer the - * input or output.

                      - * - *

                      In some charsets some or all characters are represented by multi-byte - * sequences. If a byte sequence is encountered that is not valid in the input - * charset or that cannot be mapped to a valid Unicode character it will be - * replaced in the output with the value "\uFFFD". If more control - * over the decoding process is required use {@link CharsetDecoder}.

                      - * - * @see InputStreamReader - * @see OutputStreamWriter - */ -public class WriterOutputStream - extends OutputStream { - private static final int BUFFER_SIZE = 8192; - - private Appendable target; - private CharsetDecoder decoder; - private float bytesPerChar; - - private ByteBuffer inputBuffer; - private CharBuffer outputBuffer; - - /** - * Creates a WriterOutputStream with the default charset. - * - * @param target the sink for the decoded characters - */ - public WriterOutputStream(Appendable target) { - this(target, Charset.defaultCharset()); - } - - /** - * Creates a WriterOutputStream with the named charset. - * - * @param target the sink for the decoded characters - * @param charset the character set with which to interpret the input bytes - */ - public WriterOutputStream(Appendable target, String charset) { - this(target, Charset.forName(charset)); - } - - /** - * Creates a WriterOutputStream with the given charset. - * - * @param target the sink for the decoded characters - * @param charset the character set with which to interpret the input bytes - */ - public WriterOutputStream(Appendable target, Charset charset) { - this.target = target; - - bytesPerChar = charset.newEncoder().maxBytesPerChar(); - decoder = charset.newDecoder(); - decoder.onMalformedInput(CodingErrorAction.REPLACE); - decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); - - inputBuffer = ByteBuffer.allocate( - (int) Math.ceil(BUFFER_SIZE * bytesPerChar)); - outputBuffer = CharBuffer.allocate(BUFFER_SIZE); - } - - @Override - public synchronized void write(byte[] buffer, int offset, int length) - throws IOException { - if (null == decoder) - throw new IOException("this stream has been closed"); - - // if the input buffer is too full decode it first - if (inputBuffer.remaining() < bytesPerChar) - this.decodeInputBuffer(); - - // Append the input to the buffer if it'll fit. If not and there are - // bytes left in the buffer fill it anyway so we don't lose them. - if (length <= inputBuffer.remaining() || inputBuffer.position() > 0) { - int fill = Math.min(inputBuffer.remaining(), length); - inputBuffer.put(buffer, offset, fill); - - // if we've buffered the entire input, we're done - if (fill == length) return; - - // otherwise, decode the input buffer - inputBuffer.flip(); - this.decode(inputBuffer); - - fill -= inputBuffer.remaining(); - offset += fill; - length -= fill; - inputBuffer.clear(); - } - - // if the remaining input won't fit in the buffer decode it directly - if (length > inputBuffer.remaining()) { - ByteBuffer tempBuffer = ByteBuffer.wrap(buffer, offset, length); - this.decode(tempBuffer); - - // if any bytes are left over, put them in the input buffer - if (tempBuffer.hasRemaining()) - inputBuffer.put(tempBuffer); - } - - // otherwise, just append it to the buffer - else inputBuffer.put(buffer, offset, length); - } - - @Override - public synchronized void write(int b) - throws IOException { - if (null == decoder) - throw new IOException("this stream has been closed"); - - // if the buffer is full, decode it first - if (!inputBuffer.hasRemaining()) - this.decodeInputBuffer(); - - // append the input to the buffer - inputBuffer.put((byte) b); - } - - /** - * Flushes the input buffer through the decoder. - * If the input buffer ends with an incomplete character it will remain in - * the buffer. If the underlying character sink is a {@link Writer} its - * flush method will be called after all buffered input has - * been flushed. - */ - @Override - public void flush() - throws IOException { - synchronized (this) { - if (null == decoder) - throw new IOException("this stream has been closed"); - - this.decodeInputBuffer(); - } - - // flush the underlying Writer, if any - if (target instanceof Writer) - ((Writer) target).flush(); - } - - private void decodeInputBuffer() - throws IOException { - inputBuffer.flip(); - this.decode(inputBuffer); - inputBuffer.compact(); - } - - private void decode(ByteBuffer bytes) - throws IOException { - CoderResult result; - - do { - outputBuffer.clear(); - result = decoder.decode(bytes, outputBuffer, false); - - outputBuffer.flip(); - target.append(outputBuffer); - } while (result == CoderResult.OVERFLOW); - } - - /** - * Closes the stream, flushing it first. - * If any partial characters remain in the input buffer the replacement - * value will be output in their place. If the underlying character sink is - * a {@link Writer} its close method will be called after all - * buffered input has been flushed. Once the stream has been closed further - * calls to write or flush will cause an - * IOException to be thrown. Closing a previously closed - * stream has no effect. - */ - @Override - public void close() - throws IOException { - synchronized (this) { - CoderResult result; - if (null == decoder) return; - - // flush the input buffer - inputBuffer.flip(); - do { - outputBuffer.clear(); - result = decoder.decode(inputBuffer, outputBuffer, true); - - outputBuffer.flip(); - target.append(outputBuffer); - } while (result == CoderResult.OVERFLOW); - - // flush the decoder - do { - outputBuffer.clear(); - result = decoder.flush(outputBuffer); - - outputBuffer.flip(); - target.append(outputBuffer); - } while (result == CoderResult.OVERFLOW); - - // release the buffers and decoder - inputBuffer = null; - outputBuffer = null; - decoder = null; - } - - // close the underlying Writer, if any - if (target instanceof Writer) - ((Writer) target).close(); - } -} diff --git a/src/main/java/io/starter/toolkit/WriterOutputStream.kt b/src/main/java/io/starter/toolkit/WriterOutputStream.kt new file mode 100644 index 0000000..4c1504e --- /dev/null +++ b/src/main/java/io/starter/toolkit/WriterOutputStream.kt @@ -0,0 +1,247 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.io.IOException +import java.io.OutputStream +import java.io.Writer +import java.nio.ByteBuffer +import java.nio.CharBuffer +import java.nio.charset.Charset +import java.nio.charset.CharsetDecoder +import java.nio.charset.CoderResult +import java.nio.charset.CodingErrorAction + +/** + * A `WriterOutputStream` is a bridge from byte streams to character + * streams: bytes written to it are decoded into characters using a specified + * [charset][Charset]. The charset that it uses may be specified by + * name or given explicitly, or the system default charset may be used. The + * decoded characters are written to a provided [Appendable], which will + * usually be a [Writer]. + * + * + * The input is buffered so that writes don't need to be aligned to + * character boundaries. Because conversion is only performed when the input + * buffer is full (or when the stream is flushed) output also behaves as if it + * were buffered. It is therefore usually unnecessary to externally buffer the + * input or output. + * + * + * In some charsets some or all characters are represented by multi-byte + * sequences. If a byte sequence is encountered that is not valid in the input + * charset or that cannot be mapped to a valid Unicode character it will be + * replaced in the output with the value `"\uFFFD"`. If more control + * over the decoding process is required use [CharsetDecoder]. + * + * @see InputStreamReader + * + * @see OutputStreamWriter + */ +class WriterOutputStream +/** + * Creates a `WriterOutputStream` with the given charset. + * + * @param target the sink for the decoded characters + * @param charset the character set with which to interpret the input bytes + */ +@JvmOverloads constructor(private val target: Appendable, charset: Charset = Charset.defaultCharset()) : OutputStream() { + private var decoder: CharsetDecoder? = null + private val bytesPerChar: Float + + private var inputBuffer: ByteBuffer? = null + private var outputBuffer: CharBuffer? = null + + /** + * Creates a `WriterOutputStream` with the named charset. + * + * @param target the sink for the decoded characters + * @param charset the character set with which to interpret the input bytes + */ + constructor(target: Appendable, charset: String) : this(target, Charset.forName(charset)) {} + + init { + + bytesPerChar = charset.newEncoder().maxBytesPerChar() + decoder = charset.newDecoder() + decoder!!.onMalformedInput(CodingErrorAction.REPLACE) + decoder!!.onUnmappableCharacter(CodingErrorAction.REPLACE) + + inputBuffer = ByteBuffer.allocate( + Math.ceil((BUFFER_SIZE * bytesPerChar).toDouble()).toInt()) + outputBuffer = CharBuffer.allocate(BUFFER_SIZE) + } + + @Synchronized + @Throws(IOException::class) + override fun write(buffer: ByteArray, offset: Int, length: Int) { + var offset = offset + var length = length + if (null == decoder) + throw IOException("this stream has been closed") + + // if the input buffer is too full decode it first + if (inputBuffer!!.remaining() < bytesPerChar) + this.decodeInputBuffer() + + // Append the input to the buffer if it'll fit. If not and there are + // bytes left in the buffer fill it anyway so we don't lose them. + if (length <= inputBuffer!!.remaining() || inputBuffer!!.position() > 0) { + var fill = Math.min(inputBuffer!!.remaining(), length) + inputBuffer!!.put(buffer, offset, fill) + + // if we've buffered the entire input, we're done + if (fill == length) return + + // otherwise, decode the input buffer + inputBuffer!!.flip() + this.decode(inputBuffer) + + fill -= inputBuffer!!.remaining() + offset += fill + length -= fill + inputBuffer!!.clear() + } + + // if the remaining input won't fit in the buffer decode it directly + if (length > inputBuffer!!.remaining()) { + val tempBuffer = ByteBuffer.wrap(buffer, offset, length) + this.decode(tempBuffer) + + // if any bytes are left over, put them in the input buffer + if (tempBuffer.hasRemaining()) + inputBuffer!!.put(tempBuffer) + } else + inputBuffer!!.put(buffer, offset, length)// otherwise, just append it to the buffer + } + + @Synchronized + @Throws(IOException::class) + override fun write(b: Int) { + if (null == decoder) + throw IOException("this stream has been closed") + + // if the buffer is full, decode it first + if (!inputBuffer!!.hasRemaining()) + this.decodeInputBuffer() + + // append the input to the buffer + inputBuffer!!.put(b.toByte()) + } + + /** + * Flushes the input buffer through the decoder. + * If the input buffer ends with an incomplete character it will remain in + * the buffer. If the underlying character sink is a [Writer] its + * `flush` method will be called after all buffered input has + * been flushed. + */ + @Throws(IOException::class) + override fun flush() { + synchronized(this) { + if (null == decoder) + throw IOException("this stream has been closed") + + this.decodeInputBuffer() + } + + // flush the underlying Writer, if any + if (target is Writer) + target.flush() + } + + @Throws(IOException::class) + private fun decodeInputBuffer() { + inputBuffer!!.flip() + this.decode(inputBuffer) + inputBuffer!!.compact() + } + + @Throws(IOException::class) + private fun decode(bytes: ByteBuffer) { + var result: CoderResult + + do { + outputBuffer!!.clear() + result = decoder!!.decode(bytes, outputBuffer, false) + + outputBuffer!!.flip() + target.append(outputBuffer) + } while (result === CoderResult.OVERFLOW) + } + + /** + * Closes the stream, flushing it first. + * If any partial characters remain in the input buffer the replacement + * value will be output in their place. If the underlying character sink is + * a [Writer] its `close` method will be called after all + * buffered input has been flushed. Once the stream has been closed further + * calls to `write` or `flush` will cause an + * `IOException` to be thrown. Closing a previously closed + * stream has no effect. + */ + @Throws(IOException::class) + override fun close() { + synchronized(this) { + var result: CoderResult + if (null == decoder) return + + // flush the input buffer + inputBuffer!!.flip() + do { + outputBuffer!!.clear() + result = decoder!!.decode(inputBuffer, outputBuffer, true) + + outputBuffer!!.flip() + target.append(outputBuffer) + } while (result === CoderResult.OVERFLOW) + + // flush the decoder + do { + outputBuffer!!.clear() + result = decoder!!.flush(outputBuffer) + + outputBuffer!!.flip() + target.append(outputBuffer) + } while (result === CoderResult.OVERFLOW) + + // release the buffers and decoder + inputBuffer = null + outputBuffer = null + decoder = null + } + + // close the underlying Writer, if any + if (target is Writer) + target.close() + } + + companion object { + private val BUFFER_SIZE = 8192 + } +} +/** + * Creates a `WriterOutputStream` with the default charset. + * + * @param target the sink for the decoded characters + */ diff --git a/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.java b/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.java deleted file mode 100644 index 138b04f..0000000 --- a/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.java +++ /dev/null @@ -1,936 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -package io.starter.toolkit; - -import java.util.*; - -/* - * Copyright 2004 The Apache Software Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * A List implementation that is optimised for fast insertions and - * removals at any index in the list. - *

                      - * This list implementation utilises a tree structure internally to ensure that - * all insertions and removals are O(log n). This provides much faster - * performance than both an ArrayList and a LinkedList - * where elements are inserted and removed repeatedly from anywhere in the list. - *

                      - * The following relative performance statistics are indicative of this class: - * - *

                      - *              get  add  insert  iterate  remove
                      - * TreeList       3    5       1       2       1
                      - * ArrayList      1    1      40       1      40
                      - * LinkedList  5800    1     350       2     325
                      - * 
                      - * - * ArrayList is a good general purpose list implementation. It is - * faster than TreeList for most operations except inserting and - * removing in the middle of the list. ArrayList also uses less - * memory as TreeList uses one object per entry. - *

                      - * LinkedList is rarely a good choice of implementation. - * TreeList is almost always a good replacement for it, although it - * does use sligtly more memory. - * - * @author Joerg Schmuecker - * @author Stephen Colebourne - * @version $Revision: 1.2 $ $Date: 2010/01/26 14:53:49 $ - * @since Commons Collections 3.1 - */ -class XLSOptimizedTreeList extends AbstractList { - - /** - * The root node in the AVL tree - */ - private AVLNode root; - - /** - * The current size of the list - */ - private int size; - - // ----------- - - /** - * Constructs a new empty list. - */ - public XLSOptimizedTreeList() { - super(); - } - - /** - * Constructs a new empty list that copies the specified list. - * - * @param coll the collection to copy - * @throws NullPointerException if the collection is null - */ - public XLSOptimizedTreeList(Collection coll) { - super(); - addAll(coll); - } - - // ----------- - - /** - * Gets the element at the specified index. - * - * @param index the index to retrieve - * @return the element at the specified index - */ - @Override - public Object get(int index) { - checkInterval(index, 0, size() - 1); - return root.get(index).getValue(); - } - - /** - * Gets the current size of the list. - * - * @return the current size - */ - @Override - public int size() { - return size; - } - - /** - * Gets an iterator over the list. - * - * @return an iterator over the list - */ - @Override - public Iterator iterator() { - // override to go 75% faster - return listIterator(0); - } - - /** - * Gets a ListIterator over the list. - * - * @return the new iterator - */ - @Override - public ListIterator listIterator() { - // override to go 75% faster - return listIterator(0); - } - - /** - * Gets a ListIterator over the list. - * - * @param fromIndex the index to start from - * @return the new iterator - */ - @Override - public ListIterator listIterator(int fromIndex) { - // override to go 75% faster - // cannot use EmptyIterator as iterator.add() must work - checkInterval(fromIndex, 0, size()); - return new TreeListIterator(this, fromIndex); - } - - /** - * Searches for the index of an object in the list. - * - * @return the index of the object, -1 if not found - */ - @Override - public int indexOf(Object object) { - // override to go 75% faster - if (root == null) { - return -1; - } - return root.indexOf(object, root.relativePosition); - } - - /** - * Searches for the presence of an object in the list. - * - * @return true if the object is found - */ - @Override - public boolean contains(Object object) { - return (indexOf(object) >= 0); - } - - /** - * Converts the list into an array. - * - * @return the list as an array - */ - @Override - public Object[] toArray() { - // override to go 20% faster - Object[] array = new Object[size()]; - if (root != null) { - root.toArray(array, root.relativePosition); - } - return array; - } - - // ----------- - - /** - * Adds a new element to the list. - * - * @param index the index to add before - * @param obj the element to add - */ - @Override - public void add(int index, Object obj) { - modCount++; - checkInterval(index, 0, size()); - if (root == null) { - root = new AVLNode(index, obj, null, null); - } else { - root = root.insert(index, obj); - } - size++; - } - - /** - * Sets the element at the specified index. - * - * @param index the index to set - * @param obj the object to store at the specified index - * @return the previous object at that index - * @throws IndexOutOfBoundsException if the index is invalid - */ - @Override - public Object set(int index, Object obj) { - checkInterval(index, 0, size() - 1); - AVLNode node = root.get(index); - Object result = node.value; - node.setValue(obj); - return result; - } - - /** - * Removes the element at the specified index. - * - * @param index the index to remove - * @return the previous object at that index - */ - @Override - public Object remove(int index) { - modCount++; - checkInterval(index, 0, size() - 1); - Object result = get(index); - root = root.remove(index); - size--; - return result; - } - - /** - * Clears the list, removing all entries. - */ - @Override - public void clear() { - modCount++; - root = null; - size = 0; - } - - // ----------- - - /** - * Checks whether the index is valid. - * - * @param index the index to check - * @param startIndex the first allowed index - * @param endIndex the last allowed index - * @throws IndexOutOfBoundsException if the index is invalid - */ - private void checkInterval(int index, int startIndex, int endIndex) { - if (index < startIndex || index > endIndex) { - throw new IndexOutOfBoundsException("Invalid index:" + index + ", size=" + size()); - } - } - - // ----------- - - /** - * Implements an AVLNode which keeps the offset updated. - *

                      - * This node contains the real work. TreeList is just there to implement - * {@link java.util.List}. The nodes don't know the index of the object they are - * holding. They do know however their position relative to their parent node. - * This allows to calculate the index of a node while traversing the tree. - *

                      - * The Faedelung calculation stores a flag for both the left and right child to - * indicate if they are a child (false) or a link as in linked list (true). - */ - static class AVLNode { - /** - * The left child node or the predecessor if {@link #leftIsPrevious}. - */ - private AVLNode left; - /** - * Flag indicating that left reference is not a subtree but the predecessor. - */ - private boolean leftIsPrevious; - /** - * The right child node or the successor if {@link #rightIsNext}. - */ - private AVLNode right; - /** - * Flag indicating that right reference is not a subtree but the successor. - */ - private boolean rightIsNext; - /** - * How many levels of left/right are below this one. - */ - private int height; - /** - * The relative position, root holds absolute position. - */ - private int relativePosition; - /** - * The stored element. - */ - private Object value; - - /** - * Constructs a new node with a relative position. - * - * @param relativePosition the relative position of the node - * @param obj the value for the ndoe - * @param rightFollower the node with the value following this one - * @param leftFollower the node with the value leading this one - */ - private AVLNode(int relativePosition, Object obj, AVLNode rightFollower, AVLNode leftFollower) { - this.relativePosition = relativePosition; - value = obj; - rightIsNext = true; - leftIsPrevious = true; - right = rightFollower; - left = leftFollower; - } - - /** - * Gets the value. - * - * @return the value of this node - */ - Object getValue() { - return value; - } - - /** - * Sets the value. - * - * @param obj the value to store - */ - void setValue(Object obj) { - this.value = obj; - } - - /** - * Locate the element with the given index relative to the offset of the parent - * of this node. - */ - AVLNode get(int index) { - int indexRelativeToMe = index - relativePosition; - - if (indexRelativeToMe == 0) { - return this; - } - - AVLNode nextNode = ((indexRelativeToMe < 0) ? getLeftSubTree() : getRightSubTree()); - if (nextNode == null) { - return null; - } - return nextNode.get(indexRelativeToMe); - } - - /** - * Locate the index that contains the specified object. - */ - int indexOf(Object object, int index) { - if (getLeftSubTree() != null) { - int result = left.indexOf(object, index + left.relativePosition); - if (result != -1) { - return result; - } - } - if (value == null ? value == object : value.equals(object)) { - return index; - } - if (getRightSubTree() != null) { - return right.indexOf(object, index + right.relativePosition); - } - return -1; - } - - /** - * Stores the node and its children into the array specified. - * - * @param array the array to be filled - * @param index the index of this node - */ - void toArray(Object[] array, int index) { - array[index] = value; - if (getLeftSubTree() != null) { - left.toArray(array, index + left.relativePosition); - } - if (getRightSubTree() != null) { - right.toArray(array, index + right.relativePosition); - } - } - - /** - * Gets the next node in the list after this one. - * - * @return the next node - */ - AVLNode next() { - if (rightIsNext || right == null) { - return right; - } - return right.min(); - } - - /** - * Gets the node in the list before this one. - * - * @return the previous node - */ - AVLNode previous() { - if (leftIsPrevious || left == null) { - return left; - } - return left.max(); - } - - /** - * Inserts a node at the position index. - * - * @param index is the index of the position relative to the position of the - * parent node. - * @param obj is the object to be stored in the position. - */ - AVLNode insert(int index, Object obj) { - int indexRelativeToMe = index - relativePosition; - - if (indexRelativeToMe <= 0) { - return insertOnLeft(indexRelativeToMe, obj); - } else { - return insertOnRight(indexRelativeToMe, obj); - } - } - - private AVLNode insertOnLeft(int indexRelativeToMe, Object obj) { - AVLNode ret = this; - - if (getLeftSubTree() == null) { - setLeft(new AVLNode(-1, obj, this, left), null); - } else { - setLeft(left.insert(indexRelativeToMe, obj), null); - } - - if (relativePosition >= 0) { - relativePosition++; - } - ret = balance(); - recalcHeight(); - return ret; - } - - private AVLNode insertOnRight(int indexRelativeToMe, Object obj) { - AVLNode ret = this; - - if (getRightSubTree() == null) { - setRight(new AVLNode(+1, obj, right, this), null); - } else { - setRight(right.insert(indexRelativeToMe, obj), null); - } - if (relativePosition < 0) { - relativePosition--; - } - ret = balance(); - recalcHeight(); - return ret; - } - - // ----------- - - /** - * Gets the left node, returning null if its a faedelung. - */ - private AVLNode getLeftSubTree() { - return (leftIsPrevious ? null : left); - } - - /** - * Gets the right node, returning null if its a faedelung. - */ - private AVLNode getRightSubTree() { - return (rightIsNext ? null : right); - } - - /** - * Gets the rightmost child of this node. - * - * @return the rightmost child (greatest index) - */ - private AVLNode max() { - return (getRightSubTree() == null) ? this : right.max(); - } - - /** - * Gets the leftmost child of this node. - * - * @return the leftmost child (smallest index) - */ - private AVLNode min() { - return (getLeftSubTree() == null) ? this : left.min(); - } - - /** - * Removes the node at a given position. - * - * @param index is the index of the element to be removed relative to the position - * of the parent node of the current node. - */ - AVLNode remove(int index) { - int indexRelativeToMe = index - relativePosition; - - if (indexRelativeToMe == 0) { - return removeSelf(); - } - if (indexRelativeToMe > 0) { - setRight(right.remove(indexRelativeToMe), right.right); - if (relativePosition < 0) { - relativePosition++; - } - } else { - setLeft(left.remove(indexRelativeToMe), left.left); - if (relativePosition > 0) { - relativePosition--; - } - } - recalcHeight(); - return balance(); - } - - private AVLNode removeMax() { - if (getRightSubTree() == null) { - return removeSelf(); - } - setRight(right.removeMax(), right.right); - if (relativePosition < 0) { - relativePosition++; - } - recalcHeight(); - return balance(); - } - - private AVLNode removeMin() { - if (getLeftSubTree() == null) { - return removeSelf(); - } - setLeft(left.removeMin(), left.left); - if (relativePosition > 0) { - relativePosition--; - } - recalcHeight(); - return balance(); - } - - private AVLNode removeSelf() { - if (getRightSubTree() == null && getLeftSubTree() == null) - return null; - if (getRightSubTree() == null) { - if (relativePosition > 0) { - left.relativePosition += relativePosition + (relativePosition > 0 ? 0 : 1); - } - left.max().setRight(null, right); - return left; - } - if (getLeftSubTree() == null) { - right.relativePosition += relativePosition - (relativePosition < 0 ? 0 : 1); - right.min().setLeft(null, left); - return right; - } - - if (heightRightMinusLeft() > 0) { - AVLNode rightMin = right.min(); - value = rightMin.value; - if (leftIsPrevious) { - left = rightMin.left; - } - right = right.removeMin(); - if (relativePosition < 0) { - relativePosition++; - } - } else { - AVLNode leftMax = left.max(); - value = leftMax.value; - if (rightIsNext) { - right = leftMax.right; - } - left = left.removeMax(); - if (relativePosition > 0) { - relativePosition--; - } - } - recalcHeight(); - return this; - } - - // ----------- - - /** - * Balances according to the AVL algorithm. - */ - private AVLNode balance() { - switch (heightRightMinusLeft()) { - case 1: - case 0: - case -1: - return this; - case -2: - if (left.heightRightMinusLeft() > 0) { - setLeft(left.rotateLeft(), null); - } - return rotateRight(); - case 2: - if (right.heightRightMinusLeft() < 0) { - setRight(right.rotateRight(), null); - } - return rotateLeft(); - default: - throw new RuntimeException("tree inconsistent!"); - } - } - - /** - * Gets the relative position. - */ - private int getOffset(AVLNode node) { - if (node == null) { - return 0; - } - return node.relativePosition; - } - - /** - * Sets the relative position. - */ - private int setOffset(AVLNode node, int newOffest) { - if (node == null) { - return 0; - } - int oldOffset = getOffset(node); - node.relativePosition = newOffest; - return oldOffset; - } - - /** - * Sets the height by calculation. - */ - private void recalcHeight() { - height = Math.max(getLeftSubTree() == null ? -1 : getLeftSubTree().height, - getRightSubTree() == null ? -1 : getRightSubTree().height) + 1; - } - - /** - * Returns the height of the node or -1 if the node is null. - */ - private int getHeight(AVLNode node) { - return (node == null ? -1 : node.height); - } - - /** - * Returns the height difference right - left - */ - private int heightRightMinusLeft() { - return getHeight(getRightSubTree()) - getHeight(getLeftSubTree()); - } - - private AVLNode rotateLeft() { - AVLNode newTop = right; // can't be faedelung! - AVLNode movedNode = getRightSubTree().getLeftSubTree(); - - int newTopPosition = relativePosition + getOffset(newTop); - int myNewPosition = -newTop.relativePosition; - int movedPosition = getOffset(newTop) + getOffset(movedNode); - - setRight(movedNode, newTop); - newTop.setLeft(this, null); - - setOffset(newTop, newTopPosition); - setOffset(this, myNewPosition); - setOffset(movedNode, movedPosition); - return newTop; - } - - private AVLNode rotateRight() { - AVLNode newTop = left; // can't be faedelung - AVLNode movedNode = getLeftSubTree().getRightSubTree(); - - int newTopPosition = relativePosition + getOffset(newTop); - int myNewPosition = -newTop.relativePosition; - int movedPosition = getOffset(newTop) + getOffset(movedNode); - - setLeft(movedNode, newTop); - newTop.setRight(this, null); - - setOffset(newTop, newTopPosition); - setOffset(this, myNewPosition); - setOffset(movedNode, movedPosition); - return newTop; - } - - private void setLeft(AVLNode node, AVLNode previous) { - leftIsPrevious = (node == null); - left = (leftIsPrevious ? previous : node); - recalcHeight(); - } - - private void setRight(AVLNode node, AVLNode next) { - rightIsNext = (node == null); - right = (rightIsNext ? next : node); - recalcHeight(); - } - - // private void checkFaedelung() { - // AVLNode maxNode = left.max(); - // if (!maxNode.rightIsFaedelung || maxNode.right != this) { - // throw new RuntimeException(maxNode + " should right-faedel to " + this); - // } - // AVLNode minNode = right.min(); - // if (!minNode.leftIsFaedelung || minNode.left != this) { - // throw new RuntimeException(maxNode + " should left-faedel to " + this); - // } - // } - // - // private int checkTreeDepth() { - // int hright = (getRightSubTree() == null ? -1 : - // getRightSubTree().checkTreeDepth()); - // // Logger.logInfo("checkTreeDepth"); - // // Logger.logInfo(this); - // // Logger.logInfo(" left: "); - // // Logger.logInfo(_left); - // // Logger.logInfo(" right: "); - // // Logger.logInfo(_right); - // - // int hleft = (left == null ? -1 : left.checkTreeDepth()); - // if (height != Math.max(hright, hleft) + 1) { - // throw new RuntimeException( - // "height should be max" + hleft + "," + hright + " but is " + height); - // } - // return height; - // } - // - // private int checkLeftSubNode() { - // if (getLeftSubTree() == null) { - // return 0; - // } - // int count = 1 + left.checkRightSubNode(); - // if (left.relativePosition != -count) { - // throw new RuntimeException(); - // } - // return count + left.checkLeftSubNode(); - // } - // - // private int checkRightSubNode() { - // AVLNode right = getRightSubTree(); - // if (right == null) { - // return 0; - // } - // int count = 1; - // count += right.checkLeftSubNode(); - // if (right.relativePosition != count) { - // throw new RuntimeException(); - // } - // return count + right.checkRightSubNode(); - // } - - /** - * Used for debugging. - */ - @Override - public String toString() { - return "AVLNode(" + relativePosition + "," + (left != null) + "," + value + "," - + (getRightSubTree() != null) + ", faedelung " + rightIsNext + " )"; - } - } - - /** - * A list iterator over the linked list. - */ - static class TreeListIterator implements ListIterator { - /** - * The parent list - */ - protected final XLSOptimizedTreeList parent; - /** - * The node that will be returned by {@link #next()}. If this is equal to - * {@link AbstractLinkedList#header} then there are no more values to return. - */ - protected AVLNode next; - /** - * The index of {@link #next}. - */ - protected int nextIndex; - /** - * The last node that was returned by {@link #next()} or {@link #previous()}. - * Set to null if {@link #next()} or {@link #previous()} haven't - * been called, or if the node has been removed with {@link #remove()} or a new - * node added with {@link #add(Object)}. Should be accessed through - * {@link #getLastNodeReturned()} to enforce this behaviour. - */ - protected AVLNode current; - /** - * The index of {@link #current}. - */ - protected int currentIndex; - /** - * The modification count that the list is expected to have. If the list doesn't - * have this count, then a {@link java.util.ConcurrentModificationException} may - * be thrown by the operations. - */ - protected int expectedModCount; - - /** - * Create a ListIterator for a list. - * - * @param parent the parent list - * @param fromIndex the index to start at - */ - protected TreeListIterator(XLSOptimizedTreeList parent, int fromIndex) throws IndexOutOfBoundsException { - super(); - this.parent = parent; - this.expectedModCount = parent.modCount; - this.next = (parent.root == null ? null : parent.root.get(fromIndex)); - this.nextIndex = fromIndex; - } - - /** - * Checks the modification count of the list is the value that this object - * expects. - * - * @throws ConcurrentModificationException If the list's modification count isn't the value that was - * expected. - */ - protected void checkModCount() { - if (parent.modCount != expectedModCount) { - // How about Allow it??? - // throw new ConcurrentModificationException(); - } - } - - public boolean hasNext() { - return (nextIndex < parent.size()); - } - - public Object next() { - checkModCount(); - if (!hasNext()) { - throw new NoSuchElementException("No element at index " + nextIndex + "."); - } - if (next == null) { - next = parent.root.get(nextIndex); - } - Object value = next.getValue(); - current = next; - currentIndex = nextIndex++; - next = next.next(); - return value; - } - - public boolean hasPrevious() { - return (nextIndex > 0); - } - - public Object previous() { - checkModCount(); - if (!hasPrevious()) { - throw new NoSuchElementException("Already at start of list."); - } - if (next == null) { - next = parent.root.get(nextIndex - 1); - } else { - next = next.previous(); - } - Object value = next.getValue(); - current = next; - currentIndex = --nextIndex; - return value; - } - - public int nextIndex() { - return nextIndex; - } - - public int previousIndex() { - return nextIndex() - 1; - } - - public void remove() { - checkModCount(); - if (current == null) { - throw new IllegalStateException(); - } - parent.remove(currentIndex); - current = null; - currentIndex = -1; - nextIndex--; - expectedModCount++; - } - - public void set(Object obj) { - checkModCount(); - if (current == null) { - throw new IllegalStateException(); - } - current.setValue(obj); - } - - public void add(Object obj) { - checkModCount(); - parent.add(nextIndex, obj); - current = null; - currentIndex = -1; - nextIndex++; - expectedModCount++; - } - } - -} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.kt b/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.kt new file mode 100644 index 0000000..b8605da --- /dev/null +++ b/src/main/java/io/starter/toolkit/XLSOptimizedTreeList.kt @@ -0,0 +1,899 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +package io.starter.toolkit + +import java.util.* + +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A `List` implementation that is optimised for fast insertions and + * removals at any index in the list. + * + * + * This list implementation utilises a tree structure internally to ensure that + * all insertions and removals are O(log n). This provides much faster + * performance than both an `ArrayList` and a `LinkedList` + * where elements are inserted and removed repeatedly from anywhere in the list. + * + * + * The following relative performance statistics are indicative of this class: + * + *
                      + * get  add  insert  iterate  remove
                      + * TreeList       3    5       1       2       1
                      + * ArrayList      1    1      40       1      40
                      + * LinkedList  5800    1     350       2     325
                      +
                      * + * + * `ArrayList` is a good general purpose list implementation. It is + * faster than `TreeList` for most operations except inserting and + * removing in the middle of the list. `ArrayList` also uses less + * memory as `TreeList` uses one object per entry. + * + * + * `LinkedList` is rarely a good choice of implementation. + * `TreeList` is almost always a good replacement for it, although it + * does use sligtly more memory. + * + * @author Joerg Schmuecker + * @author Stephen Colebourne + * @version $Revision: 1.2 $ $Date: 2010/01/26 14:53:49 $ + * @since Commons Collections 3.1 + */ +internal class XLSOptimizedTreeList : AbstractList { + + /** + * The root node in the AVL tree + */ + private var root: AVLNode? = null + + /** + * The current size of the list + */ + private var size: Int = 0 + + // ----------- + + /** + * Constructs a new empty list. + */ + constructor() : super() {} + + /** + * Constructs a new empty list that copies the specified list. + * + * @param coll the collection to copy + * @throws NullPointerException if the collection is null + */ + constructor(coll: Collection<*>) : super() { + addAll(coll) + } + + // ----------- + + /** + * Gets the element at the specified index. + * + * @param index the index to retrieve + * @return the element at the specified index + */ + override fun get(index: Int): Any? { + checkInterval(index, 0, size - 1) + return root!![index]!!.value + } + + /** + * Gets the current size of the list. + * + * @return the current size + */ + override fun size(): Int { + return size + } + + /** + * Gets an iterator over the list. + * + * @return an iterator over the list + */ + override fun iterator(): Iterator<*> { + // override to go 75% faster + return listIterator(0) + } + + /** + * Gets a ListIterator over the list. + * + * @return the new iterator + */ + override fun listIterator(): ListIterator<*> { + // override to go 75% faster + return listIterator(0) + } + + /** + * Gets a ListIterator over the list. + * + * @param fromIndex the index to start from + * @return the new iterator + */ + override fun listIterator(fromIndex: Int): ListIterator<*> { + // override to go 75% faster + // cannot use EmptyIterator as iterator.add() must work + checkInterval(fromIndex, 0, size) + return TreeListIterator(this, fromIndex) + } + + /** + * Searches for the index of an object in the list. + * + * @return the index of the object, -1 if not found + */ + override fun indexOf(`object`: Any?): Int { + // override to go 75% faster + return if (root == null) { + -1 + } else root!!.indexOf(`object`, root!!.relativePosition) + } + + /** + * Searches for the presence of an object in the list. + * + * @return true if the object is found + */ + override fun contains(`object`: Any?): Boolean { + return indexOf(`object`) >= 0 + } + + /** + * Converts the list into an array. + * + * @return the list as an array + */ + override fun toArray(): Array { + // override to go 20% faster + val array = arrayOfNulls(size) + if (root != null) { + root!!.toArray(array, root!!.relativePosition) + } + return array + } + + // ----------- + + /** + * Adds a new element to the list. + * + * @param index the index to add before + * @param obj the element to add + */ + override fun add(index: Int, obj: Any?) { + modCount++ + checkInterval(index, 0, size) + if (root == null) { + root = AVLNode(index, obj, null, null) + } else { + root = root!!.insert(index, obj) + } + size++ + } + + /** + * Sets the element at the specified index. + * + * @param index the index to set + * @param obj the object to store at the specified index + * @return the previous object at that index + * @throws IndexOutOfBoundsException if the index is invalid + */ + override fun set(index: Int, obj: Any?): Any? { + checkInterval(index, 0, size - 1) + val node = root!![index] + val result = node!!.value + node.value = obj + return result + } + + /** + * Removes the element at the specified index. + * + * @param index the index to remove + * @return the previous object at that index + */ + override fun remove(index: Int): Any? { + modCount++ + checkInterval(index, 0, size - 1) + val result = get(index) + root = root!!.remove(index) + size-- + return result + } + + /** + * Clears the list, removing all entries. + */ + override fun clear() { + modCount++ + root = null + size = 0 + } + + // ----------- + + /** + * Checks whether the index is valid. + * + * @param index the index to check + * @param startIndex the first allowed index + * @param endIndex the last allowed index + * @throws IndexOutOfBoundsException if the index is invalid + */ + private fun checkInterval(index: Int, startIndex: Int, endIndex: Int) { + if (index < startIndex || index > endIndex) { + throw IndexOutOfBoundsException("Invalid index:$index, size=$size") + } + } + + // ----------- + + /** + * Implements an AVLNode which keeps the offset updated. + * + * + * This node contains the real work. TreeList is just there to implement + * [java.util.List]. The nodes don't know the index of the object they are + * holding. They do know however their position relative to their parent node. + * This allows to calculate the index of a node while traversing the tree. + * + * + * The Faedelung calculation stores a flag for both the left and right child to + * indicate if they are a child (false) or a link as in linked list (true). + */ + internal class AVLNode + /** + * Constructs a new node with a relative position. + * + * @param relativePosition the relative position of the node + * @param obj the value for the ndoe + * @param rightFollower the node with the value following this one + * @param leftFollower the node with the value leading this one + */ + private constructor( + /** + * The relative position, root holds absolute position. + */ + private var relativePosition: Int, + /** + * The stored element. + */ + /** + * Gets the value. + * + * @return the value of this node + */ + /** + * Sets the value. + * + * @param obj the value to store + */ + var value: Any?, + /** + * The right child node or the successor if [.rightIsNext]. + */ + private var right: AVLNode?, + /** + * The left child node or the predecessor if [.leftIsPrevious]. + */ + private var left: AVLNode?) { + /** + * Flag indicating that left reference is not a subtree but the predecessor. + */ + private var leftIsPrevious: Boolean = false + /** + * Flag indicating that right reference is not a subtree but the successor. + */ + private var rightIsNext: Boolean = false + /** + * How many levels of left/right are below this one. + */ + private var height: Int = 0 + + // ----------- + + /** + * Gets the left node, returning null if its a faedelung. + */ + private val leftSubTree: AVLNode? + get() = if (leftIsPrevious) null else left + + /** + * Gets the right node, returning null if its a faedelung. + */ + private val rightSubTree: AVLNode? + get() = if (rightIsNext) null else right + + init { + rightIsNext = true + leftIsPrevious = true + } + + /** + * Locate the element with the given index relative to the offset of the parent + * of this node. + */ + operator fun get(index: Int): AVLNode? { + val indexRelativeToMe = index - relativePosition + + if (indexRelativeToMe == 0) { + return this + } + + val nextNode = (if (indexRelativeToMe < 0) leftSubTree else rightSubTree) ?: return null + return nextNode[indexRelativeToMe] + } + + /** + * Locate the index that contains the specified object. + */ + fun indexOf(`object`: Any?, index: Int): Int { + if (leftSubTree != null) { + val result = left!!.indexOf(`object`, index + left!!.relativePosition) + if (result != -1) { + return result + } + } + if (if (value == null) value === `object` else value == `object`) { + return index + } + return if (rightSubTree != null) { + right!!.indexOf(`object`, index + right!!.relativePosition) + } else -1 + } + + /** + * Stores the node and its children into the array specified. + * + * @param array the array to be filled + * @param index the index of this node + */ + fun toArray(array: Array, index: Int) { + array[index] = value + if (leftSubTree != null) { + left!!.toArray(array, index + left!!.relativePosition) + } + if (rightSubTree != null) { + right!!.toArray(array, index + right!!.relativePosition) + } + } + + /** + * Gets the next node in the list after this one. + * + * @return the next node + */ + operator fun next(): AVLNode? { + return if (rightIsNext || right == null) { + right + } else right!!.min() + } + + /** + * Gets the node in the list before this one. + * + * @return the previous node + */ + fun previous(): AVLNode? { + return if (leftIsPrevious || left == null) { + left + } else left!!.max() + } + + /** + * Inserts a node at the position index. + * + * @param index is the index of the position relative to the position of the + * parent node. + * @param obj is the object to be stored in the position. + */ + fun insert(index: Int, obj: Any?): AVLNode { + val indexRelativeToMe = index - relativePosition + + return if (indexRelativeToMe <= 0) { + insertOnLeft(indexRelativeToMe, obj) + } else { + insertOnRight(indexRelativeToMe, obj) + } + } + + private fun insertOnLeft(indexRelativeToMe: Int, obj: Any?): AVLNode { + var ret = this + + if (leftSubTree == null) { + setLeft(AVLNode(-1, obj, this, left), null) + } else { + setLeft(left!!.insert(indexRelativeToMe, obj), null) + } + + if (relativePosition >= 0) { + relativePosition++ + } + ret = balance() + recalcHeight() + return ret + } + + private fun insertOnRight(indexRelativeToMe: Int, obj: Any?): AVLNode { + var ret = this + + if (rightSubTree == null) { + setRight(AVLNode(+1, obj, right, this), null) + } else { + setRight(right!!.insert(indexRelativeToMe, obj), null) + } + if (relativePosition < 0) { + relativePosition-- + } + ret = balance() + recalcHeight() + return ret + } + + /** + * Gets the rightmost child of this node. + * + * @return the rightmost child (greatest index) + */ + private fun max(): AVLNode { + return if (rightSubTree == null) this else right!!.max() + } + + /** + * Gets the leftmost child of this node. + * + * @return the leftmost child (smallest index) + */ + private fun min(): AVLNode { + return if (leftSubTree == null) this else left!!.min() + } + + /** + * Removes the node at a given position. + * + * @param index is the index of the element to be removed relative to the position + * of the parent node of the current node. + */ + fun remove(index: Int): AVLNode? { + val indexRelativeToMe = index - relativePosition + + if (indexRelativeToMe == 0) { + return removeSelf() + } + if (indexRelativeToMe > 0) { + setRight(right!!.remove(indexRelativeToMe), right!!.right) + if (relativePosition < 0) { + relativePosition++ + } + } else { + setLeft(left!!.remove(indexRelativeToMe), left!!.left) + if (relativePosition > 0) { + relativePosition-- + } + } + recalcHeight() + return balance() + } + + private fun removeMax(): AVLNode? { + if (rightSubTree == null) { + return removeSelf() + } + setRight(right!!.removeMax(), right!!.right) + if (relativePosition < 0) { + relativePosition++ + } + recalcHeight() + return balance() + } + + private fun removeMin(): AVLNode? { + if (leftSubTree == null) { + return removeSelf() + } + setLeft(left!!.removeMin(), left!!.left) + if (relativePosition > 0) { + relativePosition-- + } + recalcHeight() + return balance() + } + + private fun removeSelf(): AVLNode? { + if (rightSubTree == null && leftSubTree == null) + return null + if (rightSubTree == null) { + if (relativePosition > 0) { + left!!.relativePosition += relativePosition + if (relativePosition > 0) 0 else 1 + } + left!!.max().setRight(null, right) + return left + } + if (leftSubTree == null) { + right!!.relativePosition += relativePosition - if (relativePosition < 0) 0 else 1 + right!!.min().setLeft(null, left) + return right + } + + if (heightRightMinusLeft() > 0) { + val rightMin = right!!.min() + value = rightMin.value + if (leftIsPrevious) { + left = rightMin.left + } + right = right!!.removeMin() + if (relativePosition < 0) { + relativePosition++ + } + } else { + val leftMax = left!!.max() + value = leftMax.value + if (rightIsNext) { + right = leftMax.right + } + left = left!!.removeMax() + if (relativePosition > 0) { + relativePosition-- + } + } + recalcHeight() + return this + } + + // ----------- + + /** + * Balances according to the AVL algorithm. + */ + private fun balance(): AVLNode { + when (heightRightMinusLeft()) { + 1, 0, -1 -> return this + -2 -> { + if (left!!.heightRightMinusLeft() > 0) { + setLeft(left!!.rotateLeft(), null) + } + return rotateRight() + } + 2 -> { + if (right!!.heightRightMinusLeft() < 0) { + setRight(right!!.rotateRight(), null) + } + return rotateLeft() + } + else -> throw RuntimeException("tree inconsistent!") + } + } + + /** + * Gets the relative position. + */ + private fun getOffset(node: AVLNode?): Int { + return node?.relativePosition ?: 0 + } + + /** + * Sets the relative position. + */ + private fun setOffset(node: AVLNode?, newOffest: Int): Int { + if (node == null) { + return 0 + } + val oldOffset = getOffset(node) + node.relativePosition = newOffest + return oldOffset + } + + /** + * Sets the height by calculation. + */ + private fun recalcHeight() { + height = Math.max(if (leftSubTree == null) -1 else leftSubTree!!.height, + if (rightSubTree == null) -1 else rightSubTree!!.height) + 1 + } + + /** + * Returns the height of the node or -1 if the node is null. + */ + private fun getHeight(node: AVLNode?): Int { + return node?.height ?: -1 + } + + /** + * Returns the height difference right - left + */ + private fun heightRightMinusLeft(): Int { + return getHeight(rightSubTree) - getHeight(leftSubTree) + } + + private fun rotateLeft(): AVLNode { + val newTop = right // can't be faedelung! + val movedNode = rightSubTree!!.leftSubTree + + val newTopPosition = relativePosition + getOffset(newTop) + val myNewPosition = -newTop!!.relativePosition + val movedPosition = getOffset(newTop) + getOffset(movedNode) + + setRight(movedNode, newTop) + newTop.setLeft(this, null) + + setOffset(newTop, newTopPosition) + setOffset(this, myNewPosition) + setOffset(movedNode, movedPosition) + return newTop + } + + private fun rotateRight(): AVLNode { + val newTop = left // can't be faedelung + val movedNode = leftSubTree!!.rightSubTree + + val newTopPosition = relativePosition + getOffset(newTop) + val myNewPosition = -newTop!!.relativePosition + val movedPosition = getOffset(newTop) + getOffset(movedNode) + + setLeft(movedNode, newTop) + newTop.setRight(this, null) + + setOffset(newTop, newTopPosition) + setOffset(this, myNewPosition) + setOffset(movedNode, movedPosition) + return newTop + } + + private fun setLeft(node: AVLNode?, previous: AVLNode?) { + leftIsPrevious = node == null + left = if (leftIsPrevious) previous else node + recalcHeight() + } + + private fun setRight(node: AVLNode?, next: AVLNode?) { + rightIsNext = node == null + right = if (rightIsNext) next else node + recalcHeight() + } + + // private void checkFaedelung() { + // AVLNode maxNode = left.max(); + // if (!maxNode.rightIsFaedelung || maxNode.right != this) { + // throw new RuntimeException(maxNode + " should right-faedel to " + this); + // } + // AVLNode minNode = right.min(); + // if (!minNode.leftIsFaedelung || minNode.left != this) { + // throw new RuntimeException(maxNode + " should left-faedel to " + this); + // } + // } + // + // private int checkTreeDepth() { + // int hright = (getRightSubTree() == null ? -1 : + // getRightSubTree().checkTreeDepth()); + // // Logger.logInfo("checkTreeDepth"); + // // Logger.logInfo(this); + // // Logger.logInfo(" left: "); + // // Logger.logInfo(_left); + // // Logger.logInfo(" right: "); + // // Logger.logInfo(_right); + // + // int hleft = (left == null ? -1 : left.checkTreeDepth()); + // if (height != Math.max(hright, hleft) + 1) { + // throw new RuntimeException( + // "height should be max" + hleft + "," + hright + " but is " + height); + // } + // return height; + // } + // + // private int checkLeftSubNode() { + // if (getLeftSubTree() == null) { + // return 0; + // } + // int count = 1 + left.checkRightSubNode(); + // if (left.relativePosition != -count) { + // throw new RuntimeException(); + // } + // return count + left.checkLeftSubNode(); + // } + // + // private int checkRightSubNode() { + // AVLNode right = getRightSubTree(); + // if (right == null) { + // return 0; + // } + // int count = 1; + // count += right.checkLeftSubNode(); + // if (right.relativePosition != count) { + // throw new RuntimeException(); + // } + // return count + right.checkRightSubNode(); + // } + + /** + * Used for debugging. + */ + override fun toString(): String { + return ("AVLNode(" + relativePosition + "," + (left != null) + "," + value + "," + + (rightSubTree != null) + ", faedelung " + rightIsNext + " )") + } + } + + /** + * A list iterator over the linked list. + */ + internal class TreeListIterator + /** + * Create a ListIterator for a list. + * + * @param parent the parent list + * @param fromIndex the index to start at + */ + @Throws(IndexOutOfBoundsException::class) + constructor( + /** + * The parent list + */ + protected val parent: XLSOptimizedTreeList, + /** + * The index of [.next]. + */ + protected var nextIndex: Int) : ListIterator { + /** + * The node that will be returned by [.next]. If this is equal to + * [AbstractLinkedList.header] then there are no more values to return. + */ + protected var next: AVLNode? = null + /** + * The last node that was returned by [.next] or [.previous]. + * Set to `null` if [.next] or [.previous] haven't + * been called, or if the node has been removed with [.remove] or a new + * node added with [.add]. Should be accessed through + * [.getLastNodeReturned] to enforce this behaviour. + */ + protected var current: AVLNode? = null + /** + * The index of [.current]. + */ + protected var currentIndex: Int = 0 + /** + * The modification count that the list is expected to have. If the list doesn't + * have this count, then a [java.util.ConcurrentModificationException] may + * be thrown by the operations. + */ + protected var expectedModCount: Int = 0 + + init { + this.expectedModCount = parent.modCount + this.next = if (parent.root == null) null else parent.root!![nextIndex] + } + + /** + * Checks the modification count of the list is the value that this object + * expects. + * + * @throws ConcurrentModificationException If the list's modification count isn't the value that was + * expected. + */ + protected fun checkModCount() { + if (parent.modCount != expectedModCount) { + // How about Allow it??? + // throw new ConcurrentModificationException(); + } + } + + override fun hasNext(): Boolean { + return nextIndex < parent.size + } + + override fun next(): Any? { + checkModCount() + if (!hasNext()) { + throw NoSuchElementException("No element at index $nextIndex.") + } + if (next == null) { + next = parent.root!![nextIndex] + } + val value = next!!.value + current = next + currentIndex = nextIndex++ + next = next!!.next() + return value + } + + override fun hasPrevious(): Boolean { + return nextIndex > 0 + } + + override fun previous(): Any? { + checkModCount() + if (!hasPrevious()) { + throw NoSuchElementException("Already at start of list.") + } + if (next == null) { + next = parent.root!![nextIndex - 1] + } else { + next = next!!.previous() + } + val value = next!!.value + current = next + currentIndex = --nextIndex + return value + } + + override fun nextIndex(): Int { + return nextIndex + } + + override fun previousIndex(): Int { + return nextIndex() - 1 + } + + override fun remove() { + checkModCount() + if (current == null) { + throw IllegalStateException() + } + parent.removeAt(currentIndex) + current = null + currentIndex = -1 + nextIndex-- + expectedModCount++ + } + + override fun set(obj: Any) { + checkModCount() + if (current == null) { + throw IllegalStateException() + } + current!!.value = obj + } + + override fun add(obj: Any) { + checkModCount() + parent.add(nextIndex, obj) + current = null + currentIndex = -1 + nextIndex++ + expectedModCount++ + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/starter/toolkit/XSLConverterTool.java b/src/main/java/io/starter/toolkit/XSLConverterTool.java deleted file mode 100644 index 73a7ac2..0000000 --- a/src/main/java/io/starter/toolkit/XSLConverterTool.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ -/* - * XSLConverterTool is a collection of methods that are of use in the xml/xsl conversion for sheetster. - * - * These methods are called from XSL to populate various fields correctly - * - * Created on Mar 17, 2006 - * - */ -package io.starter.toolkit; - -import io.starter.OpenXLS.ExcelTools; - -import java.text.NumberFormat; -import java.util.Hashtable; - -public class XSLConverterTool { - - private Hashtable styles = new Hashtable(); - private String lastCell = "A1"; - - /** - * Gets a date format pattern based off of an ifmt. This applies only to built - * in dates. - * - * @param ifmt from XF record - * @return date format pattern - */ - public String getDateFormatPattern(String ifmt) { - if (ifmt.equals("14")) { - return "m/d/yy"; - } else if (ifmt.equals("15")) { - return "d-mmm-yy"; - } else if (ifmt.equals("16")) { - return "d-mmm"; - } else if (ifmt.equals("17")) { - return "mmm-yy"; - } else if (ifmt.equals("22")) { - return "m/d/yy h:mm"; - } - return "m/d/yy"; - - } - - /** - * Gets a calendar format pattern based off of an ifmt. This applies only to - * built in dates. - * - * @param ifmt from XF record - * @return calendar format pattern - */ - public String getJsCalendarFormatPattern(String ifmt) { - if (ifmt.equals("14")) { - return "%m/%d/%Y"; - } else if (ifmt.equals("15")) { - return "%d-%b-%y"; - } else if (ifmt.equals("16")) { - return "%d-%b"; - } else if (ifmt.equals("17")) { - return "%m-%y"; - } else if (ifmt.equals("22")) { - return "%m/%d/%Y %h:%M"; - } else - return "%m/%d/%Y"; - } - - /** - * Returns a formatted currency string based off the local format and the string - * format passed in. TODO: implement formatting patterns - * - * @param fmt - * @return - */ - public String getCurrencyFormat(String fmt, String value) { - try { - NumberFormat nf = NumberFormat.getCurrencyInstance(); - Double d = new Double(value); - String retStr = nf.format(d); - return retStr; - } catch (NumberFormatException e) { - return value; - } - } - - /** - * Get a style based of a style ID. If the style does not yet exist, create a - * new one, and add it to the hashtable of styles - * - * @param styleId - * @return Style - */ - private Style getStyle(String styleId) { - Object o = styles.get(styleId); - if (o != null) { - return (Style) o; - } - Style thisStyle = new Style(styleId); - styles.put(styleId, thisStyle); - return thisStyle; - } - - /** - * returns a String populated with cell data for missing cells since the last - * cell read. Requires the first and last cells to exist for a row. - * - * @return html fragment - */ - public String getPreviousCellData(String sheet, String currCell, String colspan) { - StringBuffer returnString = new StringBuffer(); - int colSpan = 1; - if (colspan != null && colspan != "") { - colSpan = Integer.parseInt(colspan); - } - int[] newCell = ExcelTools.getRowColFromString(currCell); - int[] oldCell = ExcelTools.getRowColFromString(lastCell); - int newCol = newCell[1]; - int oldCol = oldCell[1] + 1; - for (; oldCol < newCol; oldCol++) { - String newAddress = ExcelTools.getAlphaVal(oldCol) + (oldCell[0] + 1); - returnString.append(getEmptyCellHTML(sheet, newAddress)); - } - newCell[1] = newCol + colSpan - 1; - currCell = ExcelTools.formatLocation(newCell); - lastCell = currCell; - return returnString.toString(); - } - - private String getEmptyCellHTML(String sheet, String address) { - return " "; - } - - /******************************************************************************************************* - *********************** DELEGATING METHODS *********************************************** - *******************************************************************************************************/ - public void setStyleColor(String styleId, String color) { - Style thisStyle = getStyle(styleId); - thisStyle.setColor(color); - } - - public String getStyleColor(String styleId) { - Style thisStyle = getStyle(styleId); - return thisStyle.getColor(); - } - - public void setIsDate(String styleId, String isDate) { - Style thisStyle = getStyle(styleId); - thisStyle.setIsDate(isDate); - } - - public String getIsDate(String styleId) { - Style thisStyle = getStyle(styleId); - String s = thisStyle.getIsDate(); - if (s == null) - return "0"; - return s; - } - - public void setIsCurrency(String styleId, String isCurrency) { - if (isCurrency.equals("")) - isCurrency = "0"; - Style thisStyle = getStyle(styleId); - thisStyle.setIsCurrency(isCurrency); - } - - public String getIsCurrency(String styleId) { - Style thisStyle = getStyle(styleId); - String s = thisStyle.getIsCurrency(); - if (s == null) - return "0"; - return s; - } - - public void setFormatId(String styleId, String formatId) { - Style thisStyle = getStyle(styleId); - thisStyle.setFormatId(formatId); - } - - public String getFormatId(String styleId) { - Style thisStyle = getStyle(styleId); - String s = thisStyle.getFormatId(); - if (s == null) - return "0"; - return s; - } - - public String getFormatPattern(String styleId) { - Style thisStyle = getStyle(styleId); - return thisStyle.getFormatPattern(); - } - - public void setFormatPattern(String styleId, String pattern) { - Style thisStyle = getStyle(styleId); - thisStyle.setFormatPattern(pattern); - } - - /** - * Style holds style information about a certain style in the xsl spreadsheet - */ - private class Style { - private String ID; - private String formatId; - private String color = ""; - private String test; - private String fontFamily; - private String fontSize; - private String fontWeight; - private String fontColor; - private String textAlign; - private String isDate; - private String isCurrency; - private String formatPattern; - - public String getFormatPattern() { - return formatPattern; - } - - public void setFormatPattern(String pattern) { - this.formatPattern = pattern; - } - - protected Style(String id) { - ID = id; - } - - public String getColor() { - return color; - } - - public void setColor(String color) { - this.color = color; - } - - public String getFontColor() { - return fontColor; - } - - public void setFontColor(String fontColor) { - this.fontColor = fontColor; - } - - public String getFontFamily() { - return fontFamily; - } - - public void setFontFamily(String fontFamily) { - this.fontFamily = fontFamily; - } - - public String getFontSize() { - return fontSize; - } - - public void setFontSize(String fontSize) { - this.fontSize = fontSize; - } - - public String getFontWeight() { - return fontWeight; - } - - public void setFontWeight(String fontWeight) { - this.fontWeight = fontWeight; - } - - public String getID() { - return ID; - } - - public void setID(String id) { - ID = id; - } - - public String getFormatId() { - return formatId; - } - - public void setFormatId(String formatId) { - this.formatId = formatId; - } - - public String getTextAlign() { - return textAlign; - } - - public void setTextAlign(String textAlign) { - this.textAlign = textAlign; - } - - public String getIsCurrency() { - return isCurrency; - } - - public void setIsCurrency(String isCurrency) { - this.isCurrency = isCurrency; - } - - public String getIsDate() { - return isDate; - } - - public void setIsDate(String isDate) { - this.isDate = isDate; - } - - } -} diff --git a/src/main/java/io/starter/toolkit/XSLConverterTool.kt b/src/main/java/io/starter/toolkit/XSLConverterTool.kt new file mode 100644 index 0000000..cef7875 --- /dev/null +++ b/src/main/java/io/starter/toolkit/XSLConverterTool.kt @@ -0,0 +1,228 @@ +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ +/* + * XSLConverterTool is a collection of methods that are of use in the xml/xsl conversion for sheetster. + * + * These methods are called from XSL to populate various fields correctly + * + * Created on Mar 17, 2006 + * + */ +package io.starter.toolkit + +import io.starter.OpenXLS.ExcelTools + +import java.text.NumberFormat +import java.util.Hashtable + +class XSLConverterTool { + + private val styles = Hashtable() + private var lastCell = "A1" + + /** + * Gets a date format pattern based off of an ifmt. This applies only to built + * in dates. + * + * @param ifmt from XF record + * @return date format pattern + */ + fun getDateFormatPattern(ifmt: String): String { + if (ifmt == "14") { + return "m/d/yy" + } else if (ifmt == "15") { + return "d-mmm-yy" + } else if (ifmt == "16") { + return "d-mmm" + } else if (ifmt == "17") { + return "mmm-yy" + } else if (ifmt == "22") { + return "m/d/yy h:mm" + } + return "m/d/yy" + + } + + /** + * Gets a calendar format pattern based off of an ifmt. This applies only to + * built in dates. + * + * @param ifmt from XF record + * @return calendar format pattern + */ + fun getJsCalendarFormatPattern(ifmt: String): String { + return if (ifmt == "14") { + "%m/%d/%Y" + } else if (ifmt == "15") { + "%d-%b-%y" + } else if (ifmt == "16") { + "%d-%b" + } else if (ifmt == "17") { + "%m-%y" + } else if (ifmt == "22") { + "%m/%d/%Y %h:%M" + } else + "%m/%d/%Y" + } + + /** + * Returns a formatted currency string based off the local format and the string + * format passed in. TODO: implement formatting patterns + * + * @param fmt + * @return + */ + fun getCurrencyFormat(fmt: String, value: String): String? { + try { + val nf = NumberFormat.getCurrencyInstance() + val d = Double(value) + return nf.format(d) + } catch (e: NumberFormatException) { + return value + } + + } + + /** + * Get a style based of a style ID. If the style does not yet exist, create a + * new one, and add it to the hashtable of styles + * + * @param styleId + * @return Style + */ + private fun getStyle(styleId: String): Style { + val o = styles[styleId] + if (o != null) { + return o + } + val thisStyle = Style(styleId) + styles[styleId] = thisStyle + return thisStyle + } + + /** + * returns a String populated with cell data for missing cells since the last + * cell read. Requires the first and last cells to exist for a row. + * + * @return html fragment + */ + fun getPreviousCellData(sheet: String, currCell: String, colspan: String?): String { + var currCell = currCell + val returnString = StringBuffer() + var colSpan = 1 + if (colspan != null && colspan !== "") { + colSpan = Integer.parseInt(colspan) + } + val newCell = ExcelTools.getRowColFromString(currCell) + val oldCell = ExcelTools.getRowColFromString(lastCell) + val newCol = newCell[1] + var oldCol = oldCell[1] + 1 + while (oldCol < newCol) { + val newAddress = ExcelTools.getAlphaVal(oldCol) + (oldCell[0] + 1) + returnString.append(getEmptyCellHTML(sheet, newAddress)) + oldCol++ + } + newCell[1] = newCol + colSpan - 1 + currCell = ExcelTools.formatLocation(newCell) + lastCell = currCell + return returnString.toString() + } + + private fun getEmptyCellHTML(sheet: String, address: String): String { + return " " + } + + /******************************************************************************************************* + * DELEGATING METHODS *********************************************** + */ + fun setStyleColor(styleId: String, color: String) { + val thisStyle = getStyle(styleId) + thisStyle.color = color + } + + fun getStyleColor(styleId: String): String { + val thisStyle = getStyle(styleId) + return thisStyle.color + } + + fun setIsDate(styleId: String, isDate: String) { + val thisStyle = getStyle(styleId) + thisStyle.isDate = isDate + } + + fun getIsDate(styleId: String): String { + val thisStyle = getStyle(styleId) + return thisStyle.isDate ?: return "0" + } + + fun setIsCurrency(styleId: String, isCurrency: String) { + var isCurrency = isCurrency + if (isCurrency == "") + isCurrency = "0" + val thisStyle = getStyle(styleId) + thisStyle.isCurrency = isCurrency + } + + fun getIsCurrency(styleId: String): String { + val thisStyle = getStyle(styleId) + return thisStyle.isCurrency ?: return "0" + } + + fun setFormatId(styleId: String, formatId: String) { + val thisStyle = getStyle(styleId) + thisStyle.formatId = formatId + } + + fun getFormatId(styleId: String): String { + val thisStyle = getStyle(styleId) + return thisStyle.formatId ?: return "0" + } + + fun getFormatPattern(styleId: String): String? { + val thisStyle = getStyle(styleId) + return thisStyle.formatPattern + } + + fun setFormatPattern(styleId: String, pattern: String) { + val thisStyle = getStyle(styleId) + thisStyle.formatPattern = pattern + } + + /** + * Style holds style information about a certain style in the xsl spreadsheet + */ + private inner class Style(var id: String?) { + var formatId: String? = null + var color = "" + private val test: String? = null + var fontFamily: String? = null + var fontSize: String? = null + var fontWeight: String? = null + var fontColor: String? = null + var textAlign: String? = null + var isDate: String? = null + var isCurrency: String? = null + var formatPattern: String? = null + + } +} diff --git a/src/test/java/io/starter/OpenXLS/FormulaTest.java b/src/test/java/io/starter/OpenXLS/FormulaTest.java deleted file mode 100644 index 324f0c5..0000000 --- a/src/test/java/io/starter/OpenXLS/FormulaTest.java +++ /dev/null @@ -1,371 +0,0 @@ -package io.starter.OpenXLS; - -/* - * --------- BEGIN COPYRIGHT NOTICE --------- - * Copyright 2002-2012 Extentech Inc. - * Copyright 2013 Infoteria America Corp. - * - * This file is part of OpenXLS. - * - * OpenXLS is free software: you can redistribute it and/or - * modify - * it under the terms of the GNU Lesser General Public - * License as - * published by the Free Software Foundation, either version - * 3 of - * the License, or (at your option) any later version. - * - * OpenXLS is distributed in the hope that it will be - * useful, - * but WITHOUT ANY WARRANTY; without even the implied - * warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General - * Public - * License along with OpenXLS. If not, see - * . - * ---------- END COPYRIGHT NOTICE ---------- - */ - -import io.starter.formats.XLS.CellNotFoundException; -import io.starter.formats.XLS.FormulaNotFoundException; -import io.starter.formats.XLS.FunctionNotSupportedException; -import io.starter.formats.XLS.WorkSheetNotFoundException; -import io.starter.toolkit.Logger; -import org.junit.Test; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; - -/** - * This Class Demonstrates the functionality of of OpenXLS Formula manipulation. - */ - -public class FormulaTest { - - @Test - public void testFormulaParse() { - testformula t = new testformula(); - t.testFormula(); - } - - @Test - public void testHandlerFunctions() { - testformula t = new testformula(); - t.testHandlerFunctions(); - } - - @Test - public void testMultiChange() { - testformula t = new testformula(); - t.testMultiChange(); - } - - @Test - public void changeSingleCellLoc() { - testformula t = new testformula(); - t.changeSingleCellLoc(); - } - -} - -/** - * Test the manipulation of Formulas within a worksheet. - */ -class testformula { - WorkBookHandle book = null; - WorkSheetHandle sheet = null; - String sheetname = "Sheet1"; - String wd = System.getProperty("user.dir") - + "/docs/samples/Formulas/"; - String finpath = wd + "testFormula.xls"; - - /** - * thrash multiple changes to formula references and recalc - * ------------------------------------------------------------ - */ - public void testMultiChange() { - try { - Logger.logInfo("Testing multiple changes to formula references and recalc"); - WorkBookHandle wbx = new WorkBookHandle(); - WorkSheetHandle sheet1 = wbx.getWorkSheet(0); - sheet1.add(new Double(100.123), "A1"); - sheet1.add(new Double(200.123), "A2"); - CellHandle cx = sheet1.add("=sum(A1*A2)", "A3"); - Logger.logInfo(String.valueOf(cx)); - Logger.logInfo("start setting 100k vals"); - for (int t = 0; t < 100000; t++) { - sheet1.getCell("A1").setVal(Math.random() * 10000); - sheet1.getCell("A2").setVal(Math.random() * 10000); - Object calced = cx.getVal(); - Logger.logInfo(calced.toString()); - } - Logger.logInfo("done setting 100k vals"); - wbx.write(new FileOutputStream(new File( - wd + "testFormulas_out.xls")), WorkBookHandle.FORMAT_XLS); - - } catch (Exception ex) { - Logger.logErr("testFormulas.testMultiChange: " + ex.toString()); - } - } - - /** - * Demonstrates Dynamic Formula Calculation - */ - public void testCalculation() { - try { - this.openSheet(finpath, sheetname); - // c4 + d4 = f4 - CellHandle mycell1 = sheet.getCell("C4"); - CellHandle mycell2 = sheet.getCell("D4"); - CellHandle myformulacell = sheet.getCell("F4"); - - // output the calculated values - FormulaHandle form = myformulacell.getFormulaHandle(); - io.starter.toolkit.Logger.log(form.calculate().toString()); - - // change the values then recalc - mycell1.setVal(99); - mycell2.setVal(420); - io.starter.toolkit.Logger.log(form.calculate().toString()); - - testWrite("testCalculation_out.xls"); - } catch (CellNotFoundException e) { - io.starter.toolkit.Logger.log("cell not found" + e); - } catch (FormulaNotFoundException e) { - io.starter.toolkit.Logger.log("No formula to change" + e); - } catch (Exception e) { - Logger.logErr("TestFormulas failed.", e); - } - } - - /** - * Move a Cell Reference within a Formula - */ - public void changeSingleCellLoc() { - try { - this.openSheet(finpath, sheetname); - CellHandle mycell = sheet.getCell("A10"); - FormulaHandle form = mycell.getFormulaHandle(); - form.changeFormulaLocation("A3", "G10"); - testWrite("testChangeSingleCellLoc_out.xls"); - } catch (CellNotFoundException e) { - io.starter.toolkit.Logger.log("cell not found" + e); - } catch (FormulaNotFoundException e) { - io.starter.toolkit.Logger.log("No formula to change" + e); - } - } - - /** - * Move a Cell range reference within a Formula - */ - public void testHandlerFunctions() { - try { - this.openSheet(finpath, sheetname); - CellHandle mycell = sheet.getCell("E8"); - FormulaHandle myhandle = mycell.getFormulaHandle(); - boolean b = myhandle.changeFormulaLocation("A1:B2", "D1:D28"); - testWrite("testHandlerFunctions_out.xls"); - } catch (CellNotFoundException e) { - io.starter.toolkit.Logger.log("cell not found" + e); - } catch (FormulaNotFoundException e) { - io.starter.toolkit.Logger.log("No formula to change" + e); - } - } - - /** - * Add a cell to a Cell range reference within a Formula - */ - public void testCellHandlerFunctions() { - try { - this.openSheet(finpath, sheetname); - CellHandle mycell = sheet.getCell("E8"); - CellHandle secondcell = sheet.getCell("D19"); - FormulaHandle myhandle = mycell.getFormulaHandle(); - boolean b = myhandle.addCellToRange("A1:B2", secondcell); - testWrite("testCellHandlerFunctions_out.xls"); - } catch (CellNotFoundException e) { - io.starter.toolkit.Logger.log("cell not found" + e); - } catch (FormulaNotFoundException e) { - io.starter.toolkit.Logger.log("No formula to change" + e); - } - } - - /** - * Run tests - */ - public void testFormula() { - try { - String finpath = wd + "testFormula.xls"; - String sheetname = "Sheet1"; - this.openSheet(finpath, sheetname); - sheet.removeRow(2, true); - testWrite("testFormula_out.xls"); - } catch (Exception e) { - io.starter.toolkit.Logger - .log("Exception in testFORMULA.testFormulaSeries(): " + e); - } - } - - WorkSheetHandle sht = null; - - /** - * Demonstrates calculation of formulas - *

                      - * Jan 19, 2010 - * - * @param fs - * @param sh - */ - void testFormulaCalc(String fs, String sh) { - WorkBookHandle book = new WorkBookHandle(fs); - sheetname = sh; - try { - sht = book.getWorkSheet(sheetname); - } catch (Exception e) { - Logger.logErr("TestFormulas failed.", e); - } - - FormulaHandle f = null; - Double i = null; - - /************************************ - * Formula Parse test - **************************************/ - if (sheetname.equalsIgnoreCase("Sheet1")) { - try { - - // one ref & ptgadd - sht.add(null, "A1"); - CellHandle c = sht.getCell("A1"); - c.setFormula("b1+5"); - f = c.getFormulaHandle(); - i = (Double) f.calculate(); - - // two refs & ptgadd - sht.add(null, "A2"); - c = sht.getCell("A2"); - c.setFormula("B1+ A1"); - f = c.getFormulaHandle(); - i = (Double) f.calculate(); - - // ptgsub - f.setFormula("B1 - 5"); - i = (Double) f.calculate(); - - // ptgmul - f.setFormula("D1 * F1"); - i = (Double) f.calculate(); - - // ptgdiv - f.setFormula("E1 / F1"); - i = (Double) f.calculate(); - - // ptgpower - f.setFormula("E1 ^ F1"); - i = (Double) f.calculate(); - - f.setFormula("E1 > F1"); - Boolean b = (Boolean) f.calculate(); - - f.setFormula("E1 >= F1"); - b = (Boolean) f.calculate(); - - f.setFormula("E1 < F1"); - b = (Boolean) f.calculate(); - - f.setFormula("E1 <= F1"); - b = (Boolean) f.calculate(); - - f.setFormula("Pi()"); - i = (Double) f.calculate(); - - f.setFormula("LOG(10,2)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("ROUND(32.443,1)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("MOD(45,6)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("DATE(1998,2,4)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("SUM(1998,2,4)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("IF(TRUE,1,0)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("ISERR(\"test\")"); - b = (Boolean) f.calculate(); - io.starter.toolkit.Logger.log(b.toString()); - - // many operand ptgfuncvar - f.setFormula("SUM(12,3,2,4,5,1)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - // test with a sub-calc - f.setFormula("IF((1<2),1,0)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("IF((1<2),MOD(45,6),1)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("IF((1<2),if((true),8,1),1)"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - f.setFormula("IF((SUM(23,2,3,4)<12),if((true),8,1),DATE(1998,2,4))"); - i = (Double) f.calculate(); - io.starter.toolkit.Logger.log(i.toString()); - - } catch (CellNotFoundException e) { - Logger.logErr("TestFormulas failed.", e); - } catch (FunctionNotSupportedException e) { - Logger.logErr("TestFormulas failed.", e); - } catch (Exception e) { - Logger.logErr("TestFormulas failed.", e); - } - testWrite("testCalcFormulas_out.xls"); - } - } - - public void openSheet(String finp, String sheetnm) { - book = new WorkBookHandle(finp); - try { - sheet = book.getWorkSheet(sheetnm); - } catch (WorkSheetNotFoundException e) { - io.starter.toolkit.Logger.log("couldn't find worksheet" + e); - } - - } - - public void testWrite(String fname) { - try { - java.io.File f = new java.io.File(wd + fname); - FileOutputStream fos = new FileOutputStream(f); - BufferedOutputStream bbout = new BufferedOutputStream(fos); - book.write(bbout); - bbout.flush(); - fos.close(); - } catch (java.io.IOException e) { - Logger.logInfo("IOException in Tester. " + e); - } - } - -} \ No newline at end of file diff --git a/src/test/java/io/starter/OpenXLS/FormulaTest.kt b/src/test/java/io/starter/OpenXLS/FormulaTest.kt new file mode 100644 index 0000000..5d92aa0 --- /dev/null +++ b/src/test/java/io/starter/OpenXLS/FormulaTest.kt @@ -0,0 +1,379 @@ +package io.starter.OpenXLS + +/* + * --------- BEGIN COPYRIGHT NOTICE --------- + * Copyright 2002-2012 Extentech Inc. + * Copyright 2013 Infoteria America Corp. + * + * This file is part of OpenXLS. + * + * OpenXLS is free software: you can redistribute it and/or + * modify + * it under the terms of the GNU Lesser General Public + * License as + * published by the Free Software Foundation, either version + * 3 of + * the License, or (at your option) any later version. + * + * OpenXLS is distributed in the hope that it will be + * useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public + * License along with OpenXLS. If not, see + * . + * ---------- END COPYRIGHT NOTICE ---------- + */ + +import io.starter.formats.XLS.CellNotFoundException +import io.starter.formats.XLS.FormulaNotFoundException +import io.starter.formats.XLS.FunctionNotSupportedException +import io.starter.formats.XLS.WorkSheetNotFoundException +import io.starter.toolkit.Logger +import org.junit.Test + +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream + +/** + * This Class Demonstrates the functionality of of OpenXLS Formula manipulation. + */ + +class FormulaTest { + + @Test + fun testFormulaParse() { + val t = testformula() + t.testFormula() + } + + @Test + fun testHandlerFunctions() { + val t = testformula() + t.testHandlerFunctions() + } + + @Test + fun testMultiChange() { + val t = testformula() + t.testMultiChange() + } + + @Test + fun changeSingleCellLoc() { + val t = testformula() + t.changeSingleCellLoc() + } + +} + +/** + * Test the manipulation of Formulas within a worksheet. + */ +internal class testformula { + var book: WorkBookHandle? = null + var sheet: WorkSheetHandle? = null + var sheetname = "Sheet1" + var wd = System.getProperty("user.dir") + "/docs/samples/Formulas/" + var finpath = wd + "testFormula.xls" + + var sht: WorkSheetHandle? = null + + /** + * thrash multiple changes to formula references and recalc + * ------------------------------------------------------------ + */ + fun testMultiChange() { + try { + Logger.logInfo("Testing multiple changes to formula references and recalc") + val wbx = WorkBookHandle() + val sheet1 = wbx.getWorkSheet(0) + sheet1.add(100.123, "A1") + sheet1.add(200.123, "A2") + val cx = sheet1.add("=sum(A1*A2)", "A3") + Logger.logInfo(cx.toString()) + Logger.logInfo("start setting 100k vals") + for (t in 0..99999) { + sheet1.getCell("A1").setVal(Math.random() * 10000) + sheet1.getCell("A2").setVal(Math.random() * 10000) + val calced = cx!!.`val` + Logger.logInfo(calced!!.toString()) + } + Logger.logInfo("done setting 100k vals") + wbx.write(FileOutputStream(File( + wd + "testFormulas_out.xls")), WorkBookHandle.FORMAT_XLS) + + } catch (ex: Exception) { + Logger.logErr("testFormulas.testMultiChange: $ex") + } + + } + + /** + * Demonstrates Dynamic Formula Calculation + */ + fun testCalculation() { + try { + this.openSheet(finpath, sheetname) + // c4 + d4 = f4 + val mycell1 = sheet!!.getCell("C4") + val mycell2 = sheet!!.getCell("D4") + val myformulacell = sheet!!.getCell("F4") + + // output the calculated values + val form = myformulacell.formulaHandle + io.starter.toolkit.Logger.log(form.calculate()!!.toString()) + + // change the values then recalc + mycell1.setVal(99) + mycell2.setVal(420) + io.starter.toolkit.Logger.log(form.calculate()!!.toString()) + + testWrite("testCalculation_out.xls") + } catch (e: CellNotFoundException) { + io.starter.toolkit.Logger.log("cell not found$e") + } catch (e: FormulaNotFoundException) { + io.starter.toolkit.Logger.log("No formula to change$e") + } catch (e: Exception) { + Logger.logErr("TestFormulas failed.", e) + } + + } + + /** + * Move a Cell Reference within a Formula + */ + fun changeSingleCellLoc() { + try { + this.openSheet(finpath, sheetname) + val mycell = sheet!!.getCell("A10") + val form = mycell.formulaHandle + form.changeFormulaLocation("A3", "G10") + testWrite("testChangeSingleCellLoc_out.xls") + } catch (e: CellNotFoundException) { + io.starter.toolkit.Logger.log("cell not found$e") + } catch (e: FormulaNotFoundException) { + io.starter.toolkit.Logger.log("No formula to change$e") + } + + } + + /** + * Move a Cell range reference within a Formula + */ + fun testHandlerFunctions() { + try { + this.openSheet(finpath, sheetname) + val mycell = sheet!!.getCell("E8") + val myhandle = mycell.formulaHandle + val b = myhandle.changeFormulaLocation("A1:B2", "D1:D28") + testWrite("testHandlerFunctions_out.xls") + } catch (e: CellNotFoundException) { + io.starter.toolkit.Logger.log("cell not found$e") + } catch (e: FormulaNotFoundException) { + io.starter.toolkit.Logger.log("No formula to change$e") + } + + } + + /** + * Add a cell to a Cell range reference within a Formula + */ + fun testCellHandlerFunctions() { + try { + this.openSheet(finpath, sheetname) + val mycell = sheet!!.getCell("E8") + val secondcell = sheet!!.getCell("D19") + val myhandle = mycell.formulaHandle + val b = myhandle.addCellToRange("A1:B2", secondcell) + testWrite("testCellHandlerFunctions_out.xls") + } catch (e: CellNotFoundException) { + io.starter.toolkit.Logger.log("cell not found$e") + } catch (e: FormulaNotFoundException) { + io.starter.toolkit.Logger.log("No formula to change$e") + } + + } + + /** + * Run tests + */ + fun testFormula() { + try { + val finpath = wd + "testFormula.xls" + val sheetname = "Sheet1" + this.openSheet(finpath, sheetname) + sheet!!.removeRow(2, true) + testWrite("testFormula_out.xls") + } catch (e: Exception) { + io.starter.toolkit.Logger + .log("Exception in testFORMULA.testFormulaSeries(): $e") + } + + } + + /** + * Demonstrates calculation of formulas + * + * + * Jan 19, 2010 + * + * @param fs + * @param sh + */ + fun testFormulaCalc(fs: String, sh: String) { + val book = WorkBookHandle(fs) + sheetname = sh + try { + sht = book.getWorkSheet(sheetname) + } catch (e: Exception) { + Logger.logErr("TestFormulas failed.", e) + } + + var f: FormulaHandle? = null + var i: Double? = null + + /************************************ + * Formula Parse test + */ + if (sheetname.equals("Sheet1", ignoreCase = true)) { + try { + + // one ref & ptgadd + sht!!.add(null, "A1") + var c = sht!!.getCell("A1") + c.setFormula("b1+5") + f = c.formulaHandle + i = f!!.calculate() as Double + + // two refs & ptgadd + sht!!.add(null, "A2") + c = sht!!.getCell("A2") + c.setFormula("B1+ A1") + f = c.formulaHandle + i = f!!.calculate() as Double + + // ptgsub + f.setFormula("B1 - 5") + i = f.calculate() as Double + + // ptgmul + f.setFormula("D1 * F1") + i = f.calculate() as Double + + // ptgdiv + f.setFormula("E1 / F1") + i = f.calculate() as Double + + // ptgpower + f.setFormula("E1 ^ F1") + i = f.calculate() as Double + + f.setFormula("E1 > F1") + var b = f.calculate() as Boolean + + f.setFormula("E1 >= F1") + b = f.calculate() as Boolean + + f.setFormula("E1 < F1") + b = f.calculate() as Boolean + + f.setFormula("E1 <= F1") + b = f.calculate() as Boolean + + f.setFormula("Pi()") + i = f.calculate() as Double + + f.setFormula("LOG(10,2)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + f.setFormula("ROUND(32.443,1)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + f.setFormula("MOD(45,6)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + f.setFormula("DATE(1998,2,4)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + f.setFormula("SUM(1998,2,4)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + f.setFormula("IF(TRUE,1,0)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + f.setFormula("ISERR(\"test\")") + b = f.calculate() as Boolean + io.starter.toolkit.Logger.log(b.toString()) + + // many operand ptgfuncvar + f.setFormula("SUM(12,3,2,4,5,1)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + // test with a sub-calc + f.setFormula("IF((1<2),1,0)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + f.setFormula("IF((1<2),MOD(45,6),1)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + f.setFormula("IF((1<2),if((true),8,1),1)") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + f.setFormula("IF((SUM(23,2,3,4)<12),if((true),8,1),DATE(1998,2,4))") + i = f.calculate() as Double + io.starter.toolkit.Logger.log(i.toString()) + + } catch (e: CellNotFoundException) { + Logger.logErr("TestFormulas failed.", e) + } catch (e: FunctionNotSupportedException) { + Logger.logErr("TestFormulas failed.", e) + } catch (e: Exception) { + Logger.logErr("TestFormulas failed.", e) + } + + testWrite("testCalcFormulas_out.xls") + } + } + + fun openSheet(finp: String, sheetnm: String) { + book = WorkBookHandle(finp) + try { + sheet = book!!.getWorkSheet(sheetnm) + } catch (e: WorkSheetNotFoundException) { + io.starter.toolkit.Logger.log("couldn't find worksheet$e") + } + + } + + fun testWrite(fname: String) { + try { + val f = java.io.File(wd + fname) + val fos = FileOutputStream(f) + val bbout = BufferedOutputStream(fos) + book!!.write(bbout) + bbout.flush() + fos.close() + } catch (e: java.io.IOException) { + Logger.logInfo("IOException in Tester. $e") + } + + } + +} \ No newline at end of file